Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
f17c9c2
record: open TASK-126..129; dispatch TASK-127; TASK-128 awaits its si…
ranjiao Aug 20, 2026
cb401e2
contract: work writes .perry/agents.jsonl → .perry/roles/ (V5, Ran Ji…
ranjiao Aug 20, 2026
a8cc312
record: TASK-127 to review — KR-O2.4 measures 17; open TASK-130..132
ranjiao Aug 20, 2026
d90612a
config: declare the first non-project track, and record what it cost
ranjiao Aug 20, 2026
f3c4461
fix(portability): stat -f succeeds on GNU, so the mtime fallback neve…
ranjiao Aug 20, 2026
4dac664
record: TASK-126 spec; the suite is 1 red, down from 3
ranjiao Aug 20, 2026
b524f43
design(DESIGN-008): all six user decisions resolved
ranjiao Aug 20, 2026
d1b3623
record: DESIGN-001 is shipped; pending_handoff is a false positive (T…
ranjiao Aug 20, 2026
6830c59
design(DESIGN-008): locked
ranjiao Aug 20, 2026
1d109c4
record: DESIGN-008 handed off; TASK-140 opened and its spec written
ranjiao Aug 20, 2026
1eec64b
handoff: 2026-08-20
ranjiao Aug 20, 2026
4c7e841
fix(board): TASK-037 and TASK-045 were blocked by rows that had closed
ranjiao Aug 20, 2026
d0574ac
record: open TASK-142 — stranded-row checks belong in conformance
ranjiao Aug 20, 2026
13cfe2f
fix(tests): two PRs green on their own base merged into a red tree
ranjiao Aug 20, 2026
7c0bb99
record: dispatch TASK-126 and TASK-140, both worktrees pinned to 13cfe2f
ranjiao Aug 20, 2026
e71d7c0
record: dispatch TASK-120 — KR progress has provenance, not a fabrica…
ranjiao Aug 20, 2026
6c01b93
record: TASK-126 to review (suite fully green); spec TASK-122 from a …
ranjiao Aug 20, 2026
c15636d
fix(store): TASK-122 — a bullet slot is not a cell, so it invents no …
ranjiao Aug 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .perry/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,25 @@
- Code repo path: —
- Last updated: 2026-08-16

## Tracks

| Track | Mode | Spine | Stages | WIP | SLA | Cycle | Default rung |
|---|---|---|---|---|---|---|---|
| main | project | phase/ | — | — | — | — | V3 |
| intake | queue | standing | new→triaged→in_progress→resolved | 6 | 5d | weekly | V3 |

`intake` carries the work that ARRIVES — a defect an agent found mid-run, a
sibling a sweep turned up, a review finding. It is not decomposed from a goal,
it shows up, and its useful questions are queue questions: what has been
waiting longest, how deep is the backlog, what keeps recurring.

`main` carries the work that is DECOMPOSED — the phase, its KRs, the rows that
serve them.

Declared 2026-08-20 as the experiment in TASK-133. `Default rung` is V3 rather
than queue mode's V2 default: an arriving row here is a code defect, and a
resolution note is not evidence that it is fixed.

## Why the state root is not `.`

Perry's own `design/` directory is the **design lane skill**
Expand Down
63 changes: 63 additions & 0 deletions .perry/events.jsonl

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ The table is that sentence applied to a file list. It is a **file-ownership** co
| Lane | Only writer of | Proposes, never writes |
|---|---|---|
| **`goals`** (`goals/`) | `OKR.md` — **including `## Commitments`** — and `phase/<NNN>-<slug>.md` | weekly tasks, handed to `work` |
| **`work`** (`work/`) | `BOARD.md` (incl. `## Intake`, `## Cadence`), `journal/`, `PROJECT_STATE.md`, `evidence/`, `weekly/`, `handoff/` | KR attribution edges, handed to `goals` |
| **`work`** (`work/`) | `BOARD.md` (incl. `## Intake`, `## Cadence`), `journal/`, `PROJECT_STATE.md`, `evidence/`, `weekly/`, `handoff/`, **`.perry/agents.jsonl` → `.perry/roles/`** | KR attribution edges, handed to `goals` |
| **`decide`** (`decide/`) | `design/<DESIGN-ID>-<slug>.md`, **`DECISIONS.md` and `decisions/`** | implementation tasks on lock, handed to `work` |

**Two changes from the previous contract** — `DECISIONS.md` + `decisions/` moved from `work` to `decide`, and `OKR.md § Commitments` became explicitly `goals`. **The lane names and the directories now agree**, an edit needing no second signature because the ownership set above is byte-identical across it. Both accounts: `reference/hand-off-contract.md`.
Expand Down
17 changes: 16 additions & 1 deletion bin/perry-codex-preflight
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,21 @@ USAGE
esac
done

mtime_of() {
# See `bin/perry-dispatch-limit § mtime` (TASK-138). GNU `stat -f` is
# `--file-system` and SUCCEEDS, so the `-f || -c` chain never falls through
# on Linux. Try GNU first (BSD rejects `-c` outright) and accept digits only.
local v
v=$(stat -c %Y "$1" 2>/dev/null) && case "$v" in
""|*[!0-9]*) : ;;
*) printf '%s\n' "$v"; return 0 ;;
esac
v=$(stat -f %m "$1" 2>/dev/null) && case "$v" in
""|*[!0-9]*) : ;;
*) printf '%s\n' "$v"; return 0 ;;
esac
echo 0
}
log() { [ "$QUIET" -eq 1 ] || echo "$@"; }
warn() { echo "$@" >&2; }
fail() { echo "$@" >&2; exit 1; }
Expand Down Expand Up @@ -81,7 +96,7 @@ mkdir -p "$CACHE_DIR"
NOW=$(date +%s)

if [ "$FORCE" -eq 0 ] && [ -f "$SMOKE_FILE" ]; then
LAST=$(stat -f %m "$SMOKE_FILE" 2>/dev/null || stat -c %Y "$SMOKE_FILE" 2>/dev/null || echo 0)
LAST=$(mtime_of "$SMOKE_FILE")
ELAPSED=$((NOW - LAST))
if [ "$ELAPSED" -lt "$SMOKE_TTL" ]; then
log "🟢 codex smoke test cached ($((ELAPSED / 60))m ago, TTL $((SMOKE_TTL / 60))m)"
Expand Down
18 changes: 17 additions & 1 deletion bin/perry-dispatch-limit
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,24 @@ Cache: ~/.cache/perry/in-flight/<TASK-ID>-<EXECUTOR>.json
USAGE
}

# GNU `stat -f` is `--file-system`: it SUCCEEDS and prints a filesystem block,
# so a `-f %m || -c %Y` chain never reaches its fallback on Linux and hands
# `$((now - last))` four lines of prose. That was green on every mac and red on
# every CI run (TASK-138). Two rules, both load-bearing: try the GNU form
# FIRST — `-c` is rejected outright by BSD stat, so it fails cleanly, which
# `-f` does not — and accept an answer only if it is all digits, because an
# exit code is not evidence that the output means what was asked for.
mtime() {
stat -f %m "$1" 2>/dev/null || stat -c %Y "$1" 2>/dev/null || echo 0
local v
v=$(stat -c %Y "$1" 2>/dev/null) && case "$v" in
""|*[!0-9]*) : ;;
*) printf '%s\n' "$v"; return 0 ;;
esac
v=$(stat -f %m "$1" 2>/dev/null) && case "$v" in
""|*[!0-9]*) : ;;
*) printf '%s\n' "$v"; return 0 ;;
esac
echo 0
}

release_lock() {
Expand Down
17 changes: 16 additions & 1 deletion bin/perry-update-check
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,21 @@ USAGE
esac
done

mtime_of() {
# See `bin/perry-dispatch-limit § mtime` (TASK-138). GNU `stat -f` is
# `--file-system` and SUCCEEDS, so the `-f || -c` chain never falls through
# on Linux. Try GNU first (BSD rejects `-c` outright) and accept digits only.
local v
v=$(stat -c %Y "$1" 2>/dev/null) && case "$v" in
""|*[!0-9]*) : ;;
*) printf '%s\n' "$v"; return 0 ;;
esac
v=$(stat -f %m "$1" 2>/dev/null) && case "$v" in
""|*[!0-9]*) : ;;
*) printf '%s\n' "$v"; return 0 ;;
esac
echo 0
}
log() { [ "$QUIET" -eq 1 ] || echo "$@"; }
warn() { echo "$@" >&2; }
fail() { warn "$@"; [ "$STRICT" -eq 1 ] && exit 1 || exit 0; }
Expand Down Expand Up @@ -110,7 +125,7 @@ NOW=$(date +%s)

if [ "$FORCE" -eq 0 ] && [ -f "$CHECK_FILE" ]; then
# Portable mtime: try macOS stat first, fall back to GNU
LAST=$(stat -f %m "$CHECK_FILE" 2>/dev/null || stat -c %Y "$CHECK_FILE" 2>/dev/null || echo 0)
LAST=$(mtime_of "$CHECK_FILE")
ELAPSED=$((NOW - LAST))
if [ "$ELAPSED" -lt "$WEEK_SECS" ]; then
# Recently checked; bail without noise
Expand Down
29 changes: 24 additions & 5 deletions bin/perry_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,8 +265,10 @@ def cell_text(field: str, rec: dict, escape: bool = True) -> str:
`escape` is False for a slot that is not inside a markdown table — a
`- PMO repo path: …` bullet in `.perry/config.md` carries no cell
boundaries, and escaping a pipe there would write a backslash the file
never had. It is the ONE thing that differs between a table cell and a
bullet slot, so it is one flag rather than a second function.
never had. "Is this inside a table?" is the ONE question a table cell and
a bullet slot answer differently, so it is one flag rather than a second
function — and it is the same flag `describe_cell` reads to decide whether
a value may be handed padding it did not come with, for the same reason.
"""
v = rec.get(field, "")
if isinstance(v, list) or field == "depends_on":
Expand Down Expand Up @@ -314,11 +316,28 @@ def describe_cell(raw: str, field: str, rec: dict, escape: bool = True) -> dict:
body = raw.strip()
lead = raw[:len(raw) - len(raw.lstrip())]
trail = raw[len(raw.rstrip()):]
# **A table cell has boundaries; a bullet slot has neighbours** — so only
# the cell may be given padding it did not come with. `render_line` joins
# cells on `|`, a character that carries no whitespace of its own, so a
# cell whose own padding is missing has to be handed some or `|split|` is
# what gets written. A bullet slot is joined on `""` between literal spans
# that already hold every character around it: the span before the slot in
# `- Repo layout: single` is `'- Repo layout: '`, separator space included.
# A space invented there is written TWICE — two after the colon, and a
# trailing one `git diff --check` reports — on a line the tool's own
# refusal message told the reader to run `render --write` to repair.
# `escape` is the same flag that tells a cell from a slot in `cell_text`,
# for the same reason: a slot is not inside a table.
pad = " " if escape else ""
if not body:
# A whitespace-only cell is ALL padding, and splitting it into a
# leading half and a trailing half counts it twice — `| |` came back
# `| |` on every empty `Depends on` cell of Perry's own board.
c = {"f": field, "lead": " ", "trail": " ", "blank": raw}
# `| |` on every empty `Depends on` cell of Perry's own board. A
# bullet slot has only one side to pad against, so its whitespace is
# kept whole and on the left, where the separator it follows is: an
# empty `- Code repo path: ` gains a value without gaining a second
# space and without losing the one it had.
c = {"f": field, "lead": pad or raw, "trail": pad, "blank": raw}
if want:
c["disagrees"] = body
return c
Expand All @@ -341,7 +360,7 @@ def describe_cell(raw: str, field: str, rec: dict, escape: bool = True) -> dict:
at = body.index(want)
return {"f": field, "lead": lead, "trail": trail,
"p": body[:at], "s": body[at + len(want):]}
return {"f": field, "lead": lead or " ", "trail": trail or " ",
return {"f": field, "lead": lead or pad, "trail": trail or pad,
"disagrees": body}


Expand Down
Loading
Loading