From a79cb81369d8bca0ee1fcbf9dcf01be4e3112d62 Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Fri, 3 Jul 2026 07:44:56 -0700 Subject: [PATCH 01/29] chore(porch): 1134 init spir --- .../status.yaml | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml diff --git a/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml b/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml new file mode 100644 index 000000000..df064d323 --- /dev/null +++ b/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml @@ -0,0 +1,20 @@ +id: '1134' +title: afx-whoami-ship-arch-init-comm +protocol: spir +phase: specify +plan_phases: [] +current_plan_phase: null +gates: + spec-approval: + status: pending + plan-approval: + status: pending + pr: + status: pending + verify-approval: + status: pending +iteration: 1 +build_complete: false +history: [] +started_at: '2026-07-03T14:44:55.927Z' +updated_at: '2026-07-03T14:44:55.927Z' From eb8ad0f258ec5bb5284280534671486ee7e91e90 Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Fri, 3 Jul 2026 07:48:35 -0700 Subject: [PATCH 02/29] [Spec 1134] Initial specification draft --- .../1134-afx-whoami-ship-arch-init-comm.md | 294 ++++++++++++++++++ codev/state/spir-1134_thread.md | 18 ++ 2 files changed, 312 insertions(+) create mode 100644 codev/specs/1134-afx-whoami-ship-arch-init-comm.md create mode 100644 codev/state/spir-1134_thread.md diff --git a/codev/specs/1134-afx-whoami-ship-arch-init-comm.md b/codev/specs/1134-afx-whoami-ship-arch-init-comm.md new file mode 100644 index 000000000..24fd40954 --- /dev/null +++ b/codev/specs/1134-afx-whoami-ship-arch-init-comm.md @@ -0,0 +1,294 @@ +# Spec 1134: `afx whoami` + ship `/arch-init` command in codev (multi-architect identity) + +## Metadata + +- **ID**: 1134 +- **Status**: draft +- **GitHub Issue**: [#1134](https://github.com/cluesmith/codev/issues/1134) +- **Protocol**: SPIR + +## Problem Statement + +Multi-architect workspaces (Spec 755/786) mean a single codev workspace can host +several architect terminals (`main`, `triage`, `ob-refine`, …) plus many builder +terminals. Agents need a reliable, first-class way to answer the question **"who +am I, from Tower's perspective?"** Today there is no such command, and the two +places that need the answer both work around its absence badly: + +1. **No `afx whoami`.** Identity signals exist — `CODEV_ARCHITECT_NAME` in + architect PTY env (`tower-terminals.ts:654`, `:897`), builder-worktree cwd + matching against global.db (`detectCurrentBuilderId()` in + `packages/codev/src/agent-farm/commands/send.ts`), architect rows in the + `architect` table of `~/.agent-farm/global.db` (#1118) — but they are + internal plumbing. There is no user/agent-facing command that surfaces them. + +2. **The `/architect` slash command is personal and fragile.** It lives only in + the architect's private `~/.claude/commands/architect.md`, is + Shannon-workspace-flavored, and auto-detects identity by matching `ps -p + $PPID` process ancestry against `afx status` output — a heuristic that is + easy to get wrong. Adopting projects get no equivalent command at all. + +Issue #1094 documented why silent identity fallbacks are dangerous: an +unverified identity misroutes `afx send architect` to `main`. Identity +resolution must **fail loud, never guess**. + +### Current State + +- Architect terminals carry `CODEV_ARCHITECT_NAME` (Tower-injected), but nothing + reports it. +- Builders can be resolved from cwd via `detectCurrentBuilderId()`, but only + `afx send` uses it, internally. +- `/architect` exists only on one machine, with workspace-specific wording and + `ps`-ancestry identity guessing. +- A fresh `codev init` project has no identity-adoption command for architects. + +### Desired State + +- `afx whoami` reports workspace, agent type, and name from any Tower-managed + terminal — architect or builder — and exits non-zero with a clear message + when identity cannot be determined. +- `/arch-init` ships with codev (skeleton → all adopting projects), is + workspace-agnostic, resolves identity via `afx whoami` (explicit argument + overrides), and recovers architect state from `codev/state/.md`. + +## Stakeholders + +- **Architects** (human + AI, `main` and siblings): need `/arch-init` to adopt + identity and resume state after a terminal restart or context loss. +- **Builders**: get a diagnostic to confirm their own identity (and spawning + architect) matches Tower's records. +- **Adopting projects**: receive `/arch-init` automatically via + `codev init` / `codev update`. +- **Codev maintainers**: replace ad-hoc `ps`-ancestry heuristics with one + supported resolution path. + +## Constraints + +- **Fail loud, no guessing** (#1094): when identity cannot be determined, + `whoami` must exit non-zero with a helpful message. It must never fall back + to `main` implicitly. (Note: `currentArchitectName()` defaults to `main` by + design for `afx status --mine`; `whoami` must NOT reuse that defaulting + behavior — it reads the env signal directly and treats absence as unknown.) +- **Identity precedence** (fixed by the issue): builder-worktree cwd match → + `CODEV_ARCHITECT_NAME` env → unknown (non-zero exit). +- **Reuse, don't duplicate**: `whoami` must share `detectCurrentBuilderId()` + (and `lookupBuilderSpawningArchitect()`), not reimplement worktree/db + matching. +- **Two-tree rule**: `/arch-init` must exist in BOTH `codev-skeleton/` (shipped + to adopters) and our own instance (`.claude/skills/`). +- **`whoami` works in both contexts**: from the main workspace root (architect) + and from inside a `.builders//` worktree (builder). +- **`/arch-init` guardrails preserved**: never auto-approve gates, only touch + your own builders, never `cd` into worktrees, stay on the default branch at + the workspace root. +- No Tower requirement: `whoami` reads `global.db` (read-only) and process env; + it must not require Tower to be running. + +## Solution Exploration + +### Deliverable 1: `afx whoami` + +#### Approach A (chosen): new CLI subcommand reusing existing resolvers + +A new `afx whoami` command in `packages/codev/src/agent-farm/` that: + +1. Calls `detectCurrentBuilderId()` (exported from `send.ts` or extracted to a + shared module). Three outcomes: + - **Canonical builder id returned** → type `builder`. Look up + `spawned_by_architect` via `lookupBuilderSpawningArchitect()` for the + `architect:` field (omit when NULL/legacy). + - **`BuilderIdResolutionError` thrown** → we ARE in a builder worktree but + identity can't be verified: print the error's message (it already explains + the #1094 rationale) and exit non-zero. Do NOT fall through to the env + check. + - **`null` returned** → not a builder worktree; continue to step 2. +2. Reads `CODEV_ARCHITECT_NAME` from the environment (trimmed, non-empty) → + type `architect` with that name. No `main` default. +3. Neither resolves → print a clear "cannot determine identity" message (what + was checked, likely causes: plain shell, terminal not started by Tower, + pre-#786 architect terminal) and exit non-zero. + +Workspace field: the workspace root is the cwd for architects, or the path +prefix before `/.builders/` for builders. The display name is resolved from the +`known_workspaces` registry in global.db when present, else the directory +basename. (The workspace name is informational context, not identity — a +basename fallback here does not violate the fail-loud rule, which applies to +type/name.) + +- **Pros**: single source of truth; inherits #1094 fail-loud semantics for + free; works offline (no Tower dependency); minimal new code. +- **Cons**: `detectCurrentBuilderId()` needs export/relocation out of + `send.ts`; env-based architect identity is trusted, not cross-checked (see + Open Questions). +- **Complexity**: low. **Risk**: low. + +#### Approach B (rejected): query Tower's HTTP API + +Ask Tower "which agent owns this terminal?" via terminal id / pid. + +- **Pros**: authoritative live view. +- **Cons**: requires Tower running (fails in exactly the recovery scenarios + `/arch-init` exists for); needs a terminal-identity handshake that doesn't + exist today; more moving parts. Rejected. + +#### Approach C (rejected): env-var-only + +Report `CODEV_ARCHITECT_NAME` / a hypothetical builder env var. + +- **Cons**: builders have no such env var today; env vars leak across nested + shells; contradicts "from Tower/global.db's perspective" in the issue. + Rejected. + +#### Output format + +Human-readable (default), `key: value` lines: + +``` +workspace: codev +type: architect +name: main +``` + +Builder: + +``` +workspace: codev +type: builder +name: builder-spir-984 +architect: main +``` + +(`architect:` line omitted when `spawned_by_architect` is NULL — a legacy row.) + +`--json` emits a single JSON object on stdout: + +```json +{ "workspace": "codev", "type": "builder", "name": "builder-spir-984", "architect": "main" } +``` + +`architect` is omitted (not `null`) when unknown, keeping the schema minimal. + +On failure (identity unknown): exit code 1; human mode prints the explanation +to stderr; `--json` mode prints `{ "error": "" }` to stdout and still +exits 1, so scripted callers get structured output on both paths. + +### Deliverable 2: ship `/arch-init` + +#### Vehicle (chosen): a skill in `.claude/skills/arch-init/` + +The scaffold pipeline ships slash-invocable commands as skills: +`codev init` / `adopt` / `update` copy `codev-skeleton/.claude/skills/` into +projects (`init.ts:98`, `adopt.ts:134`, `update.ts:223`). A +`.claude/commands/` file is NOT shipped by any scaffold path, so the command +ships as `codev-skeleton/.claude/skills/arch-init/SKILL.md`, mirrored in our +own `.claude/skills/arch-init/` (two-tree rule). Users invoke it as +`/arch-init [name]`. + +#### Content: generalized from `~/.claude/commands/architect.md` + +Workspace-agnostic rewrite of the existing personal command, with the identity +step replaced: + +1. **Resolve the architect name.** Precedence: + 1. Explicit `[name]` argument → use verbatim (normal path; human named you). + 2. Else run `afx whoami`. If it resolves `type: architect`, adopt that + `name`. If it resolves `type: builder`, STOP — this command is for + architect terminals; report the mismatch. If it fails (non-zero), STOP + and ask the human which architect this terminal is — never guess. + + The old `ps -p $PPID` / `afx status` ancestry matching is removed entirely. +2. **Read `codev/state/.md`** (relative to the workspace root). If + missing: list `codev/state/*.md`, tell the human, and ask whether to start a + fresh state file — do not fabricate state. +3. **Confirm identity + orient**: report adopted name, the state file read, and + the current-state/open-loops summary; then follow the state file's resume + instructions. +4. **Guardrails** (workspace-agnostic, kept from the original): + - Never auto-approve porch gates — gate notifications are for the human. + - Touch only your own builders / spawns / filings; siblings own theirs. + - Never `cd` into a builder worktree; use `git -C` + absolute paths. + - Stay on the default branch at the workspace root. + +All Shannon-specific wording (workspace name, roster references, example +architect names beyond generic ones) is removed. + +## Out of Scope + +- Deleting/migrating the user's personal `~/.claude/commands/architect.md` + (their file; they can retire it themselves). +- A builder-side equivalent of `/arch-init` (builders get identity from their + spawn prompt). +- Tower HTTP API changes or new terminal-identity handshakes. +- Changing `currentArchitectName()`'s `main`-defaulting behavior for + `afx status --mine` (that default is intentional there). +- Architect state-file (`codev/state/.md`) format or lifecycle changes. + +## Open Questions + +- **Important — cross-check architect env against the `architect` table?** + `CODEV_ARCHITECT_NAME` only exists because Tower injected it, so trusting it + satisfies "from Tower's perspective". `whoami` COULD additionally verify a + matching row in the `architect` table (keyed `workspace_path, id`) and warn — + not fail — on mismatch (rows may be absent after crashes; pid matching is + the fragility we're removing). Default: trust env, no cross-check, unless + reviewers argue otherwise. +- **Nice-to-know — should `whoami` print `--json` errors to stderr instead?** + Proposed: stdout `{ "error": ... }` + exit 1 (structured for scripts). +- **Nice-to-know — top-level `codev whoami` alias?** Not proposed; `afx` is + the agent-farm surface and the issue asks for `afx whoami`. + +## Success Criteria + +### Functional (MUST) + +1. `afx whoami` from an architect terminal prints workspace/type/name matching + Tower's records (i.e. the injected `CODEV_ARCHITECT_NAME`). +2. `afx whoami` from inside a `.builders//` worktree prints the canonical + builder id from global.db, plus `architect: ` when + `spawned_by_architect` is recorded. +3. `afx whoami` in an unrecognized context (plain shell, unregistered + terminal) exits non-zero with a helpful message; it never implicitly + reports `main`. +4. `afx whoami` inside a builder worktree whose identity cannot be verified + (missing/unopenable global.db, no matching row) exits non-zero with the + `BuilderIdResolutionError` explanation — it does not fall back to the env + check or the bare directory name. +5. `afx whoami --json` emits the documented JSON schema on success and + `{ "error": ... }` + exit 1 on failure. +6. `/arch-init [name]` uses the explicit argument when given; otherwise + resolves identity via `afx whoami`; otherwise stops and asks the human. +7. `/arch-init` reads `codev/state/.md` and reports the resume summary; + when the file is missing it lists available state files and asks before + creating anything. +8. `/arch-init` ships in `codev-skeleton/.claude/skills/arch-init/` and is + installed by `codev init` (and offered by `codev update`); it is mirrored + in this repo's `.claude/skills/arch-init/`. +9. `whoami` requires no running Tower. + +### Non-functional + +- `whoami` opens global.db read-only (no state mutation). +- Builder-identity logic is shared with `afx send` (one implementation). +- `/arch-init` skill text contains no Shannon-/workspace-specific references. + +### Test Scenarios + +1. Env `CODEV_ARCHITECT_NAME=ob-refine`, cwd = workspace root → architect + `ob-refine`, exit 0 (text + `--json`). +2. Cwd inside `.builders/spir-984/` with matching global.db row + (`spawned_by_architect = 'main'`) → builder `builder-spir-984`, + `architect: main`, exit 0. +3. Same, `spawned_by_architect` NULL → no `architect` line/field, exit 0. +4. Cwd inside a builder worktree, no matching row → exit 1, #1094-style + message; env var (even if set) is NOT consulted. +5. Plain shell, no env, cwd outside any worktree → exit 1, helpful message. +6. Env set AND cwd inside a builder worktree → builder identity wins + (precedence rule). +7. Whitespace-only `CODEV_ARCHITECT_NAME` treated as unset. +8. Workspace registered in `known_workspaces` → its `name`; unregistered → + directory basename. +9. Fresh `codev init` project contains `.claude/skills/arch-init/SKILL.md`. + +## Consultation Log + +*(To be filled by porch-driven 3-way review.)* diff --git a/codev/state/spir-1134_thread.md b/codev/state/spir-1134_thread.md new file mode 100644 index 000000000..0bd96c187 --- /dev/null +++ b/codev/state/spir-1134_thread.md @@ -0,0 +1,18 @@ +# Builder thread — spir-1134 (afx whoami + ship /arch-init) + +## Specify phase + +- No pre-existing spec; drafted `codev/specs/1134-afx-whoami-ship-arch-init-comm.md` + directly from issue #1134 (issue body was detailed enough — design notes, + precedence rules, acceptance criteria — that clarifying questions weren't needed). +- Key grounding decisions made while drafting: + - `/arch-init` ships as a **skill** (`.claude/skills/arch-init/SKILL.md`), not a + `.claude/commands` file — the scaffold (`init.ts:98`, `adopt.ts:134`, + `update.ts:223`) only copies `.claude/skills/` into adopting projects. + - `whoami` reuses `detectCurrentBuilderId()` (send.ts) + + `lookupBuilderSpawningArchitect()` (state.ts) + `CODEV_ARCHITECT_NAME` env; + `BuilderIdResolutionError` inside a worktree is terminal (no env fallthrough). + - Workspace display name from `known_workspaces` in global.db, basename fallback + (informational field only; fail-loud applies to type/name). +- Open question flagged for reviewers: whether to cross-check `CODEV_ARCHITECT_NAME` + against the `architect` table (default: trust env, no cross-check). From 57c022db1d91d35b9a8a9a797b1abc8ce16d5ebe Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Fri, 3 Jul 2026 07:48:42 -0700 Subject: [PATCH 03/29] chore(porch): 1134 specify build-complete --- .../projects/1134-afx-whoami-ship-arch-init-comm/status.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml b/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml index df064d323..6bf79eed6 100644 --- a/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml +++ b/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml @@ -14,7 +14,7 @@ gates: verify-approval: status: pending iteration: 1 -build_complete: false +build_complete: true history: [] started_at: '2026-07-03T14:44:55.927Z' -updated_at: '2026-07-03T14:44:55.927Z' +updated_at: '2026-07-03T14:48:42.253Z' From 52963f06128ba51ea2ff31b35af045ca13c09086 Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Fri, 3 Jul 2026 07:54:56 -0700 Subject: [PATCH 04/29] [Spec 1134] Specification with multi-agent review --- .../1134-specify-iter1-rebuttals.md | 66 ++++++++ .../1134-afx-whoami-ship-arch-init-comm.md | 142 ++++++++++++++---- 2 files changed, 179 insertions(+), 29 deletions(-) create mode 100644 codev/projects/1134-afx-whoami-ship-arch-init-comm/1134-specify-iter1-rebuttals.md diff --git a/codev/projects/1134-afx-whoami-ship-arch-init-comm/1134-specify-iter1-rebuttals.md b/codev/projects/1134-afx-whoami-ship-arch-init-comm/1134-specify-iter1-rebuttals.md new file mode 100644 index 000000000..d795238cc --- /dev/null +++ b/codev/projects/1134-afx-whoami-ship-arch-init-comm/1134-specify-iter1-rebuttals.md @@ -0,0 +1,66 @@ +# Spec 1134 — Iteration 1 rebuttal + +Verdicts: Gemini APPROVE, Codex REQUEST_CHANGES, Claude APPROVE. +All four Codex points were accepted and the spec updated; Gemini's two +recommendations were adopted, one declined with reasons. Details below. + +## Codex (REQUEST_CHANGES) + +### 1. Architect state-file contract underdefined — ACCEPTED + +Codex is right that `codev/state/` today holds builder thread files +(`_thread.md`), not architect state files, and that "reads the state file +and reports the resume summary" was not implementable/testable as written. +Changes: + +- Added an "Architect state files: minimum contract" subsection: free-form + markdown, authoritative as-is; the resume summary is defined as the opening + role/banner line plus the most recent dated section (or leading content); + file absence is a normal first-run condition. +- Explicitly disambiguated architect state files from builder `*_thread.md` + files sharing the directory; the missing-file listing excludes thread files. +- Narrowed the `/arch-init` MUST acceptance criteria (6–7) to assertions about + the **shipped skill text** — the testable artifact — rather than runtime + agent behavior, per Codex's suggestion to "narrow acceptance to shipping the + skill + documented missing-file behavior." + +### 2. Explicit `[name]` needs validation — ACCEPTED + +Added: before any file path is built, the name must pass the established +architect-name rule (`[a-z][a-z0-9-]*`, ≤64 chars — `validateArchitectName` in +`utils/architect-name.ts`); slashes/`..`/anything else rejected with the rule +spelled out. Test scenario 12 asserts the skill text carries this rule. + +### 3. Architect workspace root ≠ cwd — ACCEPTED + +(Claude flagged the same gap.) The spec now names `detectWorkspaceRoot()` +(send.ts:30) as the resolution mechanism: `.builders/` prefix extraction for +builders, cwd-to-root walking (`.codev/config.json` or `.git` marker) for +architects. New test scenario 10 covers running `whoami` from a subdirectory. + +### 4. `/arch-init` testing underspecified — ACCEPTED + +Added test scenario 12 (skill text identical across both trees; references +`afx whoami` + validation rule; no `ps`/`$PPID` ancestry matching; no +Shannon-specific wording) alongside the existing scaffold scenario 9 +(`codev init` installs the skill). Criteria 6–7 rephrased as skill-text +assertions. + +## Gemini (APPROVE, with recommendations) + +- **Warn when env-resolved architect has no `architect` row** — ADOPTED as a + SHOULD: best-effort stderr warning, never gating (rows are legitimately + absent in crash-recovery — the exact scenario `/arch-init` serves). New test + scenario 11. +- **`--json` failure output** — ADOPTED: structured `{ "error": ... }` on + stdout AND human-readable explanation on stderr, exit 1. +- **`codev whoami` alias** — DECLINED for this spec: the issue asks for + `afx whoami`, and agents (the primary consumers) use `afx`. Noted as a + possible follow-up; keeping scope tight. + +## Claude (APPROVE, minor notes) + +- **Workspace-root gap** — fixed (see Codex point 3). +- **`describeStateDbOpenFailure()` still says "state.db"** — noted in the + spec's Consultation Log as an allowed one-line drive-by at implementation + time (or a follow-up issue); not made a requirement. diff --git a/codev/specs/1134-afx-whoami-ship-arch-init-comm.md b/codev/specs/1134-afx-whoami-ship-arch-init-comm.md index 24fd40954..a9e606f5b 100644 --- a/codev/specs/1134-afx-whoami-ship-arch-init-comm.md +++ b/codev/specs/1134-afx-whoami-ship-arch-init-comm.md @@ -108,12 +108,15 @@ A new `afx whoami` command in `packages/codev/src/agent-farm/` that: was checked, likely causes: plain shell, terminal not started by Tower, pre-#786 architect terminal) and exit non-zero. -Workspace field: the workspace root is the cwd for architects, or the path -prefix before `/.builders/` for builders. The display name is resolved from the -`known_workspaces` registry in global.db when present, else the directory -basename. (The workspace name is informational context, not identity — a -basename fallback here does not violate the fail-loud rule, which applies to -type/name.) +Workspace field: the workspace root is resolved via the existing +`detectWorkspaceRoot()` (send.ts) — it extracts the prefix before +`/.builders/` for builders and otherwise walks up from cwd to the nearest +directory containing `.codev/config.json` or `.git`. This handles an architect +running `whoami` from a subdirectory (cwd is NOT assumed to be the root). The +display name is resolved from the `known_workspaces` registry in global.db +when present, else the directory basename. (The workspace name is +informational context, not identity — a basename fallback here does not +violate the fail-loud rule, which applies to type/name.) - **Pros**: single source of truth; inherits #1094 fail-loud semantics for free; works offline (no Tower dependency); minimal new code. @@ -169,8 +172,17 @@ architect: main `architect` is omitted (not `null`) when unknown, keeping the schema minimal. On failure (identity unknown): exit code 1; human mode prints the explanation -to stderr; `--json` mode prints `{ "error": "" }` to stdout and still -exits 1, so scripted callers get structured output on both paths. +to stderr; `--json` mode prints `{ "error": "" }` to stdout AND the +human-readable explanation to stderr, still exiting 1 — scripted callers get +structured stdout on both paths while humans keep an informative stderr +(consultation feedback, Gemini). + +Architect-row cross-check: when identity resolves via `CODEV_ARCHITECT_NAME`, +`whoami` SHOULD best-effort check the `architect` table for a matching +`(workspace_path, id)` row and emit a warning to stderr when none is found +(e.g. after a Tower crash). The warning never changes the output or exit code +— rows being absent is expected in exactly the recovery scenarios `/arch-init` +serves, so this is diagnostics, not gating. ### Deliverable 2: ship `/arch-init` @@ -190,16 +202,26 @@ Workspace-agnostic rewrite of the existing personal command, with the identity step replaced: 1. **Resolve the architect name.** Precedence: - 1. Explicit `[name]` argument → use verbatim (normal path; human named you). + 1. Explicit `[name]` argument → use it (normal path; human named you), but + only after validating its shape (below). 2. Else run `afx whoami`. If it resolves `type: architect`, adopt that `name`. If it resolves `type: builder`, STOP — this command is for architect terminals; report the mismatch. If it fails (non-zero), STOP and ask the human which architect this terminal is — never guess. + **Name validation** (consultation feedback, Codex): before the name is used + to build any file path, it must match the established architect-name rule + (`[a-z][a-z0-9-]*`, max 64 chars — the `validateArchitectName` rule in + `packages/codev/src/agent-farm/utils/architect-name.ts`). Anything else — + slashes, `..`, uppercase, spaces — is rejected with the rule spelled out. + This closes the `/arch-init ../../foo` path-traversal hole. + The old `ps -p $PPID` / `afx status` ancestry matching is removed entirely. 2. **Read `codev/state/.md`** (relative to the workspace root). If - missing: list `codev/state/*.md`, tell the human, and ask whether to start a - fresh state file — do not fabricate state. + missing: list the architect state files in `codev/state/` — excluding + builder thread files, which share the directory and are named + `_thread.md` — tell the human the file is missing, and ask + whether to start a fresh state file — do not fabricate state. 3. **Confirm identity + orient**: report adopted name, the state file read, and the current-state/open-loops summary; then follow the state file's resume instructions. @@ -212,6 +234,27 @@ step replaced: All Shannon-specific wording (workspace name, roster references, example architect names beyond generic ones) is removed. +#### Architect state files: minimum contract + +`codev/state/` is a shared directory: builder threads live at +`codev/state/_thread.md`, and architect state files live at +`codev/state/.md` where `` is a valid architect name. This spec +does not impose a schema on architect state files — they are free-form +markdown owned by each architect — but `/arch-init` assumes this minimum +contract so its behavior is well-defined: + +- The file is **authoritative free text**: whatever it says about resuming + (role banner, "read FIRST on resume" notes, open loops) is followed as-is. +- The "resume summary" `/arch-init` reports is: the file's opening role/banner + line (if any) plus the most recent dated section (or, absent dated sections, + the file's leading content). No parsing beyond that is required or promised. +- Absence of the file is a normal, first-run condition — handled by the + missing-file flow above, never by fabricating content. + +Creating/maintaining these files remains out of scope; this contract exists +only so the skill's read-and-summarize behavior is implementable and +reviewable (consultation feedback, Codex). + ## Out of Scope - Deleting/migrating the user's personal `~/.claude/commands/architect.md` @@ -225,17 +268,19 @@ architect names beyond generic ones) is removed. ## Open Questions -- **Important — cross-check architect env against the `architect` table?** - `CODEV_ARCHITECT_NAME` only exists because Tower injected it, so trusting it - satisfies "from Tower's perspective". `whoami` COULD additionally verify a - matching row in the `architect` table (keyed `workspace_path, id`) and warn — - not fail — on mismatch (rows may be absent after crashes; pid matching is - the fragility we're removing). Default: trust env, no cross-check, unless - reviewers argue otherwise. -- **Nice-to-know — should `whoami` print `--json` errors to stderr instead?** - Proposed: stdout `{ "error": ... }` + exit 1 (structured for scripts). -- **Nice-to-know — top-level `codev whoami` alias?** Not proposed; `afx` is - the agent-farm surface and the issue asks for `afx whoami`. +All resolved during the iteration-1 consultation: + +- **Cross-check architect env against the `architect` table?** RESOLVED: + trust `CODEV_ARCHITECT_NAME` (it exists only because Tower injected it), but + emit a best-effort stderr **warning** when no matching `architect` row is + found — never failing, never changing output/exit code (Gemini's + recommendation; rows are legitimately absent in crash-recovery scenarios). +- **`--json` errors to stdout or stderr?** RESOLVED: both — structured + `{ "error": ... }` on stdout for scripts, human-readable explanation on + stderr, exit 1 (Gemini's recommendation). +- **Top-level `codev whoami` alias?** RESOLVED: declined for this spec. The + issue asks for `afx whoami`; agents are the primary consumers and use `afx`. + An alias can be a follow-up if humans ask for it. ## Success Criteria @@ -255,16 +300,24 @@ architect names beyond generic ones) is removed. check or the bare directory name. 5. `afx whoami --json` emits the documented JSON schema on success and `{ "error": ... }` + exit 1 on failure. -6. `/arch-init [name]` uses the explicit argument when given; otherwise - resolves identity via `afx whoami`; otherwise stops and asks the human. -7. `/arch-init` reads `codev/state/.md` and reports the resume summary; - when the file is missing it lists available state files and asks before - creating anything. +6. The `/arch-init` skill text instructs: explicit `[name]` argument wins + (after `[a-z][a-z0-9-]*`/64-char validation, rejecting path-traversal + shapes); otherwise identity comes from `afx whoami`; otherwise STOP and ask + the human. It contains no `ps`/`$PPID`/process-ancestry instructions. +7. The `/arch-init` skill text instructs reading `codev/state/.md` and + reporting the resume summary per the minimum contract; the missing-file + flow lists architect state files (excluding `*_thread.md` builder threads) + and asks before creating anything. 8. `/arch-init` ships in `codev-skeleton/.claude/skills/arch-init/` and is installed by `codev init` (and offered by `codev update`); it is mirrored - in this repo's `.claude/skills/arch-init/`. + in this repo's `.claude/skills/arch-init/` with identical content. 9. `whoami` requires no running Tower. +(6–7 are phrased as assertions about the shipped skill text — that is the +testable artifact; the runtime behavior it produces is exercised manually, +since a skill is instructions to an agent, not code. Consultation feedback, +Codex.) + ### Non-functional - `whoami` opens global.db read-only (no state mutation). @@ -288,7 +341,38 @@ architect names beyond generic ones) is removed. 8. Workspace registered in `known_workspaces` → its `name`; unregistered → directory basename. 9. Fresh `codev init` project contains `.claude/skills/arch-init/SKILL.md`. +10. Architect terminal, cwd in a subdirectory of the workspace (e.g. + `packages/codev/src/`) → workspace root still resolved correctly via + `detectWorkspaceRoot()`. +11. Env resolves an architect name but no matching `architect` row exists → + success output unchanged, warning on stderr, exit 0. +12. Skill-text assertions: `codev-skeleton/.claude/skills/arch-init/SKILL.md` + and `.claude/skills/arch-init/SKILL.md` are identical; text references + `afx whoami` and the name-validation rule; text contains no `ps `/`$PPID` + ancestry matching and no Shannon-specific wording. ## Consultation Log -*(To be filled by porch-driven 3-way review.)* +### Iteration 1 (spec review): Gemini APPROVE, Codex REQUEST_CHANGES, Claude APPROVE + +Changes made in response: + +- **Codex**: defined a minimum contract for architect state files (free-form + markdown, authoritative; resume summary = banner + latest dated section) and + disambiguated them from builder `*_thread.md` files sharing `codev/state/`; + added `[name]` validation via the `validateArchitectName` rule to close the + path-traversal hole; replaced "workspace root is cwd for architects" with + `detectWorkspaceRoot()` cwd-to-root walking; rephrased `/arch-init` MUST + criteria as assertions about the shipped skill text (the testable artifact) + and added skill-text/scaffold test scenarios (10–12). +- **Gemini**: adopted stderr warning when env-resolved architect has no + `architect` table row (non-gating); adopted dual-output `--json` failure + (JSON stdout + human stderr). Declined the `codev whoami` alias (scope: + issue asks for `afx whoami`; agents are the consumers). +- **Claude**: same workspace-root gap as Codex (fixed above). Noted for the + plan: `describeStateDbOpenFailure()`'s message text still says "state.db" + post-#1118; the implementation MAY do that one-line wording drive-by (or + file a follow-up issue) since `whoami` will surface these messages. + +Full reviews: `codev/projects/1134-afx-whoami-ship-arch-init-comm/1134-specify-iter1-{gemini,codex,claude}.txt`; +rebuttal: `1134-specify-iter1-rebuttals.md`. From 1e154d9aa6c9af93cf8813c8ef4edbee368cbf1c Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Fri, 3 Jul 2026 07:54:56 -0700 Subject: [PATCH 05/29] chore(porch): 1134 spec-approval gate-requested --- codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml b/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml index 6bf79eed6..36d72b997 100644 --- a/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml +++ b/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml @@ -7,6 +7,7 @@ current_plan_phase: null gates: spec-approval: status: pending + requested_at: '2026-07-03T14:54:56.796Z' plan-approval: status: pending pr: @@ -17,4 +18,4 @@ iteration: 1 build_complete: true history: [] started_at: '2026-07-03T14:44:55.927Z' -updated_at: '2026-07-03T14:48:42.253Z' +updated_at: '2026-07-03T14:54:56.797Z' From b9d12c1007897a2344440d9f959492c58bfd8af2 Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Fri, 3 Jul 2026 07:55:33 -0700 Subject: [PATCH 06/29] [Spec 1134] Update builder thread: spec-approval gate reached --- codev/state/spir-1134_thread.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/codev/state/spir-1134_thread.md b/codev/state/spir-1134_thread.md index 0bd96c187..4ddcb9073 100644 --- a/codev/state/spir-1134_thread.md +++ b/codev/state/spir-1134_thread.md @@ -16,3 +16,17 @@ (informational field only; fail-loud applies to type/name). - Open question flagged for reviewers: whether to cross-check `CODEV_ARCHITECT_NAME` against the `architect` table (default: trust env, no cross-check). + +## Specify — CMAP iteration 1 + +- Verdicts: Gemini APPROVE, Claude APPROVE, Codex REQUEST_CHANGES. +- Accepted all 4 Codex points: architect state-file minimum contract (+ + disambiguation from `*_thread.md` builder threads), `[name]` validation via + `validateArchitectName` rule (path-traversal guard), `detectWorkspaceRoot()` + instead of "cwd = root" for architects, `/arch-init` acceptance rephrased as + shipped-skill-text assertions + new test scenarios 10–12. +- Adopted Gemini: stderr warning when env-resolved architect lacks an + `architect` row (non-gating); `--json` failures emit JSON stdout + human + stderr. Declined `codev whoami` alias (scope). +- Committed `[Spec 1134] Specification with multi-agent review`. +- **Gate reached: spec-approval.** Architect notified via afx send. Waiting. From 4c74d4c92308e5457656924129256e903b773cd1 Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Fri, 3 Jul 2026 08:02:32 -0700 Subject: [PATCH 07/29] chore(porch): 1134 spec-approval gate-approved --- .../projects/1134-afx-whoami-ship-arch-init-comm/status.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml b/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml index 36d72b997..ed19404e4 100644 --- a/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml +++ b/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml @@ -6,8 +6,9 @@ plan_phases: [] current_plan_phase: null gates: spec-approval: - status: pending + status: approved requested_at: '2026-07-03T14:54:56.796Z' + approved_at: '2026-07-03T15:02:32.243Z' plan-approval: status: pending pr: @@ -18,4 +19,4 @@ iteration: 1 build_complete: true history: [] started_at: '2026-07-03T14:44:55.927Z' -updated_at: '2026-07-03T14:54:56.797Z' +updated_at: '2026-07-03T15:02:32.244Z' From 5a57ad5faf26dfb51a46b45d99703da20b24f47c Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Fri, 3 Jul 2026 08:02:43 -0700 Subject: [PATCH 08/29] chore(porch): 1134 plan phase-transition --- .../1134-afx-whoami-ship-arch-init-comm/status.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml b/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml index ed19404e4..837861fe4 100644 --- a/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml +++ b/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml @@ -1,7 +1,7 @@ id: '1134' title: afx-whoami-ship-arch-init-comm protocol: spir -phase: specify +phase: plan plan_phases: [] current_plan_phase: null gates: @@ -16,7 +16,7 @@ gates: verify-approval: status: pending iteration: 1 -build_complete: true +build_complete: false history: [] started_at: '2026-07-03T14:44:55.927Z' -updated_at: '2026-07-03T15:02:32.244Z' +updated_at: '2026-07-03T15:02:43.419Z' From 87a87403436852fa69e9196e40a64044c3a032cf Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Fri, 3 Jul 2026 08:05:31 -0700 Subject: [PATCH 09/29] [Spec 1134] Initial implementation plan --- .../1134-afx-whoami-ship-arch-init-comm.md | 385 ++++++++++++++++++ 1 file changed, 385 insertions(+) create mode 100644 codev/plans/1134-afx-whoami-ship-arch-init-comm.md diff --git a/codev/plans/1134-afx-whoami-ship-arch-init-comm.md b/codev/plans/1134-afx-whoami-ship-arch-init-comm.md new file mode 100644 index 000000000..cb3afa3ed --- /dev/null +++ b/codev/plans/1134-afx-whoami-ship-arch-init-comm.md @@ -0,0 +1,385 @@ +# Plan: `afx whoami` + ship `/arch-init` command in codev (multi-architect identity) + +## Metadata + +- **ID**: 1134 +- **Status**: draft +- **Specification**: codev/specs/1134-afx-whoami-ship-arch-init-comm.md +- **Created**: 2026-07-03 + +## Executive Summary + +Implement the spec's chosen Approach A in three small, independently +committable phases: + +1. **`afx whoami`** — a new `commands/whoami.ts` that composes the existing, + already-exported identity primitives (`detectCurrentBuilderId`, + `BuilderIdResolutionError`, `detectWorkspaceRoot` from `send.ts`; + `lookupBuilderSpawningArchitect` from `state.ts`), plus a read-only + `known_workspaces` name lookup. No new resolution logic. +2. **`/arch-init` skill** — one new `SKILL.md` authored once and placed + identically in `codev-skeleton/.claude/skills/arch-init/` and + `.claude/skills/arch-init/` (two-tree rule). `copySkills()` enumerates + skill directories dynamically (`scaffold.ts:361`), so no scaffold code + changes are needed for init/adopt/update to ship it. +3. **Docs sync** — `afx whoami` added to the CLI reference and the `afx` + skill, in both trees. + +Key implementation decision: `whoami` **imports** the identity helpers from +`commands/send.ts` rather than relocating them to a shared module. They are +already exported and covered by existing tests (`send.test.ts`, +`spec-755-lookup-builder.test.ts`); relocation would churn those imports for +zero behavior gain. (The spec allows either; revisit only if a third consumer +appears.) + +## Success Metrics + +- [ ] All spec MUST criteria (1–9) met +- [ ] All 12 spec test scenarios covered by automated tests where automatable + (1–8, 10–12; scenario 9 covered by existing dynamic `copySkills` behavior + + skill-presence test) +- [ ] No reduction in existing test coverage; all existing tests pass +- [ ] Zero new lint errors +- [ ] Both trees updated (skeleton + instance) for every framework file touched + +## Phases (Machine Readable) + +```json +{ + "phases": [ + {"id": "phase_1", "title": "afx whoami command + tests"}, + {"id": "phase_2", "title": "arch-init skill in both trees + tests"}, + {"id": "phase_3", "title": "CLI reference and afx-skill docs sync"} + ] +} +``` + +## Phase Breakdown + +### Phase 1: `afx whoami` command + tests + +**Dependencies**: None + +#### Objectives + +- Ship the `afx whoami` identity diagnostic with the spec's exact precedence, + output, and fail-loud semantics. + +#### Deliverables + +- [ ] `packages/codev/src/agent-farm/commands/whoami.ts` (new) +- [ ] `whoami` registration in `packages/codev/src/agent-farm/cli.ts` +- [ ] `packages/codev/src/agent-farm/__tests__/spec-1134-whoami.test.ts` (new) + +#### Implementation Details + +**`commands/whoami.ts`** exports `whoami(options: { json?: boolean })` plus a +pure, testable core `resolveIdentity(env, cwd)` returning a discriminated +result: + +```ts +type WhoamiIdentity = + | { type: 'builder'; workspace: string; name: string; architect?: string } + | { type: 'architect'; workspace: string; name: string; rowMissing?: boolean }; +// failure path: throws WhoamiError (message for humans) — the command maps it +// to exit 1 + stderr (and `{ "error": ... }` on stdout under --json) +``` + +Resolution algorithm (spec precedence, verbatim): + +1. `detectCurrentBuilderId()` (import from `./send.js`): + - returns id → type `builder`; `architect` field from + `lookupBuilderSpawningArchitect(id)` (`state.ts:537`), omitted when + `null`/`undefined`. + - throws `BuilderIdResolutionError` → rethrow as the failure result + (message passed through verbatim — it already carries the #1094 + rationale). **No fallthrough to the env check.** + - returns `null` → step 2. +2. `env.CODEV_ARCHITECT_NAME?.trim()` non-empty → type `architect` with that + name. **Not** via `currentArchitectName()` (its `main` default is exactly + what whoami must not do). Best-effort cross-check: query the `architect` + table for `(workspace_path, id)`; when no row, set `rowMissing` → the + command prints a warning to **stderr** (never affects stdout/exit code). + DB errors during this check are swallowed (it is diagnostics only). +3. Neither → failure: message names what was checked (not in a + `.builders//` worktree; `CODEV_ARCHITECT_NAME` unset) and likely causes + (plain shell, terminal not started by Tower, pre-#786 architect terminal). + +Workspace field: `detectWorkspaceRoot()` (import from `./send.js`; +`send.ts:30` — handles both the `.builders/` prefix extraction and cwd-to-root +marker walking). If it returns `null` while identity resolved via env, use +cwd basename as the path's display fallback. Display name: open global.db +**read-only** (same pattern as `detectCurrentBuilderId`) and +`SELECT name FROM known_workspaces WHERE workspace_path = ?` (canonicalized); +missing db / no row / open error → directory basename (informational field — +graceful fallback is allowed here by the spec, unlike type/name). + +Output (exact formats from the spec): + +- Human: `workspace:` / `type:` / `name:` lines (+ `architect:` for builders + when known) to stdout. +- `--json`: single JSON object, `architect` key omitted when unknown. +- Failure: exit 1; human message to stderr always; under `--json` also + `{ "error": "" }` to stdout. + +**`cli.ts` registration** (after `status`, mirroring its shape): + +```ts +program + .command('whoami') + .description("Report this terminal's agent identity (workspace, type, name)") + .option('--json', 'Output machine-readable JSON') + .action(async (options) => { /* dynamic import, try/catch, exit 1 on error */ }); +``` + +Error handling in the action must distinguish "identity unknown" (already +formatted, exit 1) — print without a redundant `logger.error` prefix under +`--json`. + +#### Acceptance Criteria + +- [ ] Spec test scenarios 1–8, 10, 11 pass as automated tests +- [ ] Existing `send.test.ts` / `spec-755-lookup-builder.test.ts` unaffected +- [ ] `afx whoami` manual smoke: from this worktree prints + `type: builder / name: builder-spir-1134`; from a plain shell in `/tmp` + exits 1 with a helpful message + +#### Test Plan + +- **Unit tests** (`spec-1134-whoami.test.ts`, vitest): drive + `resolveIdentity(env, cwd)` and the output formatter against a temp + `global.db` fixture (pattern: `spec-755-lookup-builder.test.ts` / + `send.test.ts` — point `AGENT_FARM_DIR`/db-path helper at a tmpdir): + - architect via env (trimmed; whitespace-only = unset) → scenario 1, 7 + - builder row match incl. `spawned_by_architect` present/NULL → scenarios 2, 3 + - builder worktree, no row / unopenable db → `BuilderIdResolutionError` + surfaces, env NOT consulted → scenario 4 + - no signals → failure → scenario 5 + - env + builder cwd → builder wins → scenario 6 + - `known_workspaces` name vs basename fallback → scenario 8 + - subdirectory cwd for architect → scenario 10 + - env architect with no `architect` row → `rowMissing` warning, exit 0 → + scenario 11 + - `--json` success shape and `{ "error": ... }` failure shape +- **Manual**: the two smokes in acceptance criteria. + +#### Rollback Strategy + +Single additive command; revert the phase commit. No state, schema, or +behavior changes to existing commands. + +#### Risks + +- **Risk**: `getDb()` (used by `lookupBuilderSpawningArchitect`) opens + global.db read-write and may run migrations on a stale db. + - **Mitigation**: acceptable — `afx send` already does this from CLI + context on every builder message; whoami adds no new exposure. The + whoami-specific lookups (`known_workspaces`, `architect` cross-check) use + their own read-only connections. +- **Risk**: importing from `commands/send.ts` creates a command→command + dependency. + - **Mitigation**: exports are already public and tested; noted in code + comment; extraction deferred until a third consumer exists. + +--- + +### Phase 2: `/arch-init` skill in both trees + tests + +**Dependencies**: Phase 1 (the skill instructs running `afx whoami`) + +#### Objectives + +- Ship the workspace-agnostic `/arch-init` identity-adoption command to every + codev project via the skeleton, mirrored in our instance. + +#### Deliverables + +- [ ] `codev-skeleton/.claude/skills/arch-init/SKILL.md` (new) +- [ ] `.claude/skills/arch-init/SKILL.md` (new, byte-identical) +- [ ] `packages/codev/src/agent-farm/__tests__/spec-1134-arch-init-skill.test.ts` (new) + +#### Implementation Details + +Frontmatter follows the existing skeleton-skill pattern (`name`, +`description`; description written for trigger accuracy: "adopt an architect +identity and recover its state… use when a terminal needs to know which +architect it is"). `argument-hint: "[name]"`. + +Body (generalized from the personal `~/.claude/commands/architect.md`, per +spec): + +1. **Resolve name**: explicit `$ARGUMENTS` wins — but first validate against + `[a-z][a-z0-9-]*`, ≤64 chars (the `validateArchitectName` rule); reject + slashes/`..`/uppercase/spaces with the rule spelled out (path-traversal + guard). Else run `afx whoami`: `type: architect` → adopt `name`; + `type: builder` → STOP, report mismatch (this command is for architect + terminals); non-zero → STOP and ask the human. Never guess; never default + to `main`. No `ps`/`$PPID` ancestry matching anywhere. +2. **Read state**: `codev/state/.md` at the workspace root. Missing → + list architect state files in `codev/state/` **excluding `*_thread.md`** + (builder threads share the directory), tell the human, ask before creating + anything. Never fabricate state. +3. **Confirm + orient**: report adopted name, file read, resume summary + (opening role/banner line + most recent dated section, or leading content — + the spec's minimum contract); then follow the state file's own resume + instructions. +4. **Guardrails** (workspace-agnostic): never auto-approve porch gates; touch + only your own builders/spawns/filings; never `cd` into a builder worktree + (use `git -C` + absolute paths); stay on the default branch at the + workspace root. + +No Shannon-specific wording (no workspace names, rosters, or +workspace-specific architect examples). + +No scaffold code changes: `copySkills()` (`packages/codev/src/lib/scaffold.ts:361`) +enumerates `codev-skeleton/.claude/skills/*` dynamically, so `codev init` / +`adopt` / `update` pick the new directory up automatically (spec scenario 9). + +#### Acceptance Criteria + +- [ ] Spec test scenario 12 passes: both copies byte-identical; text + references `afx whoami` and the name-validation rule; no `ps -p` / + `$PPID` / ancestry matching; no "Shannon" +- [ ] Skill loads in Claude Code (manual: `/arch-init` visible from this repo) +- [ ] Spec MUST criteria 6–8 satisfied by the shipped text + +#### Test Plan + +- **Unit tests** (`spec-1134-arch-init-skill.test.ts`): read both SKILL.md + files from the repo; assert byte equality; assert required content + (`afx whoami`, `[a-z][a-z0-9-]*`, `_thread.md` exclusion, the four + guardrails) and forbidden content (`ps -p`, `$PPID`, `Shannon`). This is the + spec's "skill-text assertions" approach — the shipped text is the testable + artifact. +- **Manual**: run `/arch-init` with an explicit name in an architect terminal + (happy path) and confirm it reads `codev/state/.md`; run with a + path-shaped arg (`../../foo`) and confirm rejection. + +#### Rollback Strategy + +Delete the two skill directories (additive files only). + +#### Risks + +- **Risk**: skill copies drift between trees over time. + - **Mitigation**: the byte-equality unit test fails on any future drift. +- **Risk**: skill description triggers too eagerly/rarely in Claude Code. + - **Mitigation**: description follows the existing afx/porch skill style; + invocation is primarily explicit (`/arch-init`). + +--- + +### Phase 3: CLI reference and afx-skill docs sync + +**Dependencies**: Phase 1 (documents the shipped command) + +#### Objectives + +- Make `afx whoami` discoverable everywhere the afx surface is documented, in + both trees. + +#### Deliverables + +- [ ] `codev/resources/commands/agent-farm.md` — add `afx whoami` section + (syntax, output fields, `--json`, exit codes, precedence note) +- [ ] `codev-skeleton/resources/commands/agent-farm.md` — same addition +- [ ] `.claude/skills/afx/SKILL.md` — add `whoami` to the command reference +- [ ] `codev-skeleton/.claude/skills/afx/SKILL.md` — same addition +- [ ] Drive-by (allowed by spec Consultation Log): fix the two "state.db" + strings in `describeStateDbOpenFailure()` / `BuilderIdResolutionError` + doc comment in `send.ts` to say global.db (post-#1118 wording; whoami + surfaces these messages) + +#### Implementation Details + +Documentation-only phase plus the one-line message-wording drive-by. The +agent-farm.md section mirrors the spec's output examples verbatim so docs and +behavior share one source of shape. Cross-tree grep before commit: +`grep -rn "whoami" codev/ codev-skeleton/ .claude/` to confirm both trees +carry the same content (lessons-critical: grep BOTH trees after any framework +change). + +#### Acceptance Criteria + +- [ ] `afx whoami` documented in all four files, examples matching actual + output +- [ ] Instance and skeleton copies of each doc pair carry the same whoami + content +- [ ] `send.ts` user-facing messages no longer say "state.db"; existing send + tests still pass (update any message-text assertions) + +#### Test Plan + +- **Unit tests**: existing send tests re-run (message-text assertions updated + if they pin the old wording). No new tests — docs phase. +- **Manual**: render check of the four markdown files. + +#### Rollback Strategy + +Revert the phase commit (docs + string literals only). + +#### Risks + +- **Risk**: a test pins the exact "state.db" message text. + - **Mitigation**: grep `__tests__` for the string in the same commit. + +## Dependency Map + +``` +Phase 1 (whoami) ──→ Phase 2 (arch-init skill) + └──────────→ Phase 3 (docs sync) +``` + +(Phases 2 and 3 are independent of each other; both need Phase 1.) + +## Integration Points + +- **global.db (`~/.agent-farm/global.db`)**: read-only queries + (`builders` via `detectCurrentBuilderId`, `known_workspaces`, `architect`); + read-write only via the pre-existing `getDb()` path that `afx send` already + exercises. No schema changes. +- **Scaffold pipeline**: consumed as-is (`copySkills` dynamic enumeration); + no changes. +- **Tower**: not required at runtime (spec constraint); no API changes. + +## Validation Checkpoints + +1. **After Phase 1**: full vitest suite green; manual smoke from this + worktree (`builder`) and `/tmp` (failure path). +2. **After Phase 2**: byte-equality + content tests green; `/arch-init` + loads and resolves via whoami manually. +3. **Before PR**: `pnpm --filter @cluesmith/codev build` clean; + cross-tree grep for `whoami`/`arch-init` shows both trees in sync; all 12 + spec scenarios accounted for. + +## Documentation Updates Required + +- [ ] `codev/resources/commands/agent-farm.md` + skeleton mirror (Phase 3) +- [ ] `.claude/skills/afx/SKILL.md` + skeleton mirror (Phase 3) +- [ ] `codev/resources/arch.md` routing of any durable facts — deferred to + Review phase per SPIR (with hot/cold tier discipline) + +## Expert Review + +*(To be filled by porch-driven 3-way plan review.)* + +## Change Log + +| Date | Change | Reason | Author | +|------|--------|--------|--------| +| 2026-07-03 | Initial plan | — | builder spir-1134 | + +## Notes + +- **No time estimates** by protocol. +- Phase commits use `[Spec 1134][Phase: ]` format; all phases ship in a + single PR (per spawn-prompt PR strategy). +- The spec's scenario 9 (`codev init` installs the skill) rides on existing + `copySkills` behavior; the presence/equality tests in Phase 2 are the + regression net for the artifact itself. + +--- + +## Amendment History + + From f063ae6d0beaf9d54cf2a5ed25bea67311d7b167 Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Fri, 3 Jul 2026 08:05:31 -0700 Subject: [PATCH 10/29] chore(porch): 1134 plan build-complete --- .../projects/1134-afx-whoami-ship-arch-init-comm/status.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml b/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml index 837861fe4..3ce4a55e2 100644 --- a/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml +++ b/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml @@ -16,7 +16,7 @@ gates: verify-approval: status: pending iteration: 1 -build_complete: false +build_complete: true history: [] started_at: '2026-07-03T14:44:55.927Z' -updated_at: '2026-07-03T15:02:43.419Z' +updated_at: '2026-07-03T15:05:31.153Z' From a3c326a361ee83b20ccd863ecc002688d8ed56e3 Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Fri, 3 Jul 2026 08:10:13 -0700 Subject: [PATCH 11/29] [Spec 1134] Plan with multi-agent review --- .../1134-afx-whoami-ship-arch-init-comm.md | 88 +++++++++++++++---- .../1134-plan-iter1-rebuttals.md | 61 +++++++++++++ 2 files changed, 131 insertions(+), 18 deletions(-) create mode 100644 codev/projects/1134-afx-whoami-ship-arch-init-comm/1134-plan-iter1-rebuttals.md diff --git a/codev/plans/1134-afx-whoami-ship-arch-init-comm.md b/codev/plans/1134-afx-whoami-ship-arch-init-comm.md index cb3afa3ed..c997ecc97 100644 --- a/codev/plans/1134-afx-whoami-ship-arch-init-comm.md +++ b/codev/plans/1134-afx-whoami-ship-arch-init-comm.md @@ -36,8 +36,8 @@ appears.) - [ ] All spec MUST criteria (1–9) met - [ ] All 12 spec test scenarios covered by automated tests where automatable - (1–8, 10–12; scenario 9 covered by existing dynamic `copySkills` behavior - + skill-presence test) + (1–8, 10–12; scenario 9 covered by new `copySkills()` regression tests + in `scaffold.test.ts` + skill-presence test) - [ ] No reduction in existing test coverage; all existing tests pass - [ ] Zero new lint errors - [ ] Both trees updated (skeleton + instance) for every framework file touched @@ -74,8 +74,13 @@ appears.) #### Implementation Details **`commands/whoami.ts`** exports `whoami(options: { json?: boolean })` plus a -pure, testable core `resolveIdentity(env, cwd)` returning a discriminated -result: +testable core `resolveIdentity(env)` returning a discriminated result. +`resolveIdentity` takes `env` as a parameter but reads cwd implicitly through +the reused send.ts helpers (`detectCurrentBuilderId` / `detectWorkspaceRoot` +call `process.cwd()` internally — they are NOT parameterized; that churn isn't +worth it for two consumers). Tests control cwd via `process.chdir()` into +tmpdir fixtures, the established pattern in `send.test.ts:108–122` +(consultation feedback, Codex): ```ts type WhoamiIdentity = @@ -87,9 +92,21 @@ type WhoamiIdentity = Resolution algorithm (spec precedence, verbatim): +**Read-only invariant** (spec non-functional requirement; consultation +feedback, Codex): `whoami` never opens global.db read-write. It opens ONE +`new Database(path, { readonly: true })` connection for all whoami-specific +queries (`spawned_by_architect`, `known_workspaces`, `architect` cross-check) +and closes it before exit. To reuse the shared SQL without the read-write +`getDb()` singleton, `lookupBuilderSpawningArchitect(builderId, workspacePath?)` +gains an optional third parameter `db?: Database.Database` — defaulting to +`getDb()` so all existing callers are byte-for-byte unaffected; whoami passes +its read-only connection. (`detectCurrentBuilderId` already opens its own +read-only connection internally.) + 1. `detectCurrentBuilderId()` (import from `./send.js`): - returns id → type `builder`; `architect` field from - `lookupBuilderSpawningArchitect(id)` (`state.ts:537`), omitted when + `lookupBuilderSpawningArchitect(id, undefined, roDb)` (`state.ts:537`, + read-only handle per the invariant above), omitted when `null`/`undefined`. - throws `BuilderIdResolutionError` → rethrow as the failure result (message passed through verbatim — it already carries the #1094 @@ -170,12 +187,12 @@ behavior changes to existing commands. #### Risks -- **Risk**: `getDb()` (used by `lookupBuilderSpawningArchitect`) opens - global.db read-write and may run migrations on a stale db. - - **Mitigation**: acceptable — `afx send` already does this from CLI - context on every builder message; whoami adds no new exposure. The - whoami-specific lookups (`known_workspaces`, `architect` cross-check) use - their own read-only connections. +- **Risk**: the optional `db?` parameter on `lookupBuilderSpawningArchitect` + changes a long-lived API signature. + - **Mitigation**: additive optional parameter with `getDb()` default — + existing callers (grep before commit) and their tests are unaffected; + `spec-755-lookup-builder.test.ts` gains a case passing an explicit + read-only handle. - **Risk**: importing from `commands/send.ts` creates a command→command dependency. - **Mitigation**: exports are already public and tested; noted in code @@ -197,6 +214,11 @@ behavior changes to existing commands. - [ ] `codev-skeleton/.claude/skills/arch-init/SKILL.md` (new) - [ ] `.claude/skills/arch-init/SKILL.md` (new, byte-identical) - [ ] `packages/codev/src/agent-farm/__tests__/spec-1134-arch-init-skill.test.ts` (new) +- [ ] `copySkills()` regression tests added to + `packages/codev/src/__tests__/scaffold.test.ts` (none exist today — + consultation feedback, Codex): copying from the real skeleton into a + tmp target installs `arch-init/SKILL.md`; `skipExisting: true` + preserves an existing customized copy #### Implementation Details @@ -251,6 +273,10 @@ enumerates `codev-skeleton/.claude/skills/*` dynamically, so `codev init` / guardrails) and forbidden content (`ps -p`, `$PPID`, `Shannon`). This is the spec's "skill-text assertions" approach — the shipped text is the testable artifact. +- **Scaffold tests** (`scaffold.test.ts`): `copySkills()` from the real + skeleton dir into a tmpdir target installs `arch-init/SKILL.md` (proves the + init/adopt/update install path — spec scenario 9); `skipExisting: true` + leaves a pre-existing target copy untouched. - **Manual**: run `/arch-init` with an explicit name in an architect terminal (happy path) and confirm it reads `codev/state/.md`; run with a path-shaped arg (`../../foo`) and confirm rejection. @@ -334,10 +360,11 @@ Phase 1 (whoami) ──→ Phase 2 (arch-init skill) ## Integration Points -- **global.db (`~/.agent-farm/global.db`)**: read-only queries - (`builders` via `detectCurrentBuilderId`, `known_workspaces`, `architect`); - read-write only via the pre-existing `getDb()` path that `afx send` already - exercises. No schema changes. +- **global.db (`~/.agent-farm/global.db`)**: strictly read-only — `builders` + via `detectCurrentBuilderId` (own readonly connection) and + `spawned_by_architect` / `known_workspaces` / `architect` via whoami's + single shared readonly connection. `getDb()` (read-write) is never invoked + by whoami. No schema changes. - **Scaffold pipeline**: consumed as-is (`copySkills` dynamic enumeration); no changes. - **Tower**: not required at runtime (spec constraint); no API changes. @@ -361,7 +388,32 @@ Phase 1 (whoami) ──→ Phase 2 (arch-init skill) ## Expert Review -*(To be filled by porch-driven 3-way plan review.)* +### Iteration 1 (plan review): Gemini APPROVE, Codex REQUEST_CHANGES, Claude APPROVE + +**Key feedback and adjustments:** + +- **Codex — read-only violation (accepted)**: the draft accepted + `lookupBuilderSpawningArchitect()` via read-write `getDb()`, contradicting + the spec's "opens global.db read-only" requirement. Fixed: whoami uses one + shared read-only connection for all its queries; + `lookupBuilderSpawningArchitect` gains an optional `db?` parameter + (defaults to `getDb()`, existing callers unaffected). +- **Codex — scenario 9 coverage overstated (accepted)**: no `copySkills()` + tests exist today. Added Phase 2 deliverable: `copySkills()` regression + tests in `scaffold.test.ts` (installs `arch-init`, respects + `skipExisting`). +- **Codex — `resolveIdentity` purity under-specified (accepted)**: clarified + that the send.ts helpers are NOT parameterized; `resolveIdentity(env)` + reads cwd through them, and tests control cwd via `process.chdir()` + (send.test.ts pattern). Claude's review raised the same purity point. +- **Gemini — test assertions pinned to old "state.db" wording must update in + the same Phase 3 commit**: already covered under Phase 3 risks; kept. +- **Claude — drive-by scope**: confirmed only the user-facing message + + doc comment change to "global.db"; historical "state.db is retired" + migration comments stay. + +Full reviews: `codev/projects/1134-afx-whoami-ship-arch-init-comm/1134-plan-iter1-{gemini,codex,claude}.txt`; +rebuttal: `1134-plan-iter1-rebuttals.md`. ## Change Log @@ -375,8 +427,8 @@ Phase 1 (whoami) ──→ Phase 2 (arch-init skill) - Phase commits use `[Spec 1134][Phase: ]` format; all phases ship in a single PR (per spawn-prompt PR strategy). - The spec's scenario 9 (`codev init` installs the skill) rides on existing - `copySkills` behavior; the presence/equality tests in Phase 2 are the - regression net for the artifact itself. + `copySkills` behavior, which gets its first regression tests in Phase 2 + (none exist today); the presence/equality tests cover the artifact itself. --- diff --git a/codev/projects/1134-afx-whoami-ship-arch-init-comm/1134-plan-iter1-rebuttals.md b/codev/projects/1134-afx-whoami-ship-arch-init-comm/1134-plan-iter1-rebuttals.md new file mode 100644 index 000000000..218538402 --- /dev/null +++ b/codev/projects/1134-afx-whoami-ship-arch-init-comm/1134-plan-iter1-rebuttals.md @@ -0,0 +1,61 @@ +# Plan 1134 — Iteration 1 rebuttal + +Verdicts: Gemini APPROVE, Codex REQUEST_CHANGES, Claude APPROVE. +All three Codex points accepted; plan updated. Gemini's and Claude's minor +notes folded in. + +## Codex (REQUEST_CHANGES) + +### 1. Read-only requirement violated by `getDb()` path — ACCEPTED + +Codex is right: the spec's non-functional requirement says `whoami` opens +global.db read-only, and the draft plan explicitly accepted the read-write +`getDb()` singleton (with migrations) for `lookupBuilderSpawningArchitect()`. +That was a spec violation dressed up as a risk item. Fixed in Phase 1: + +- whoami opens ONE `new Database(path, { readonly: true })` connection for + all its queries (`spawned_by_architect`, `known_workspaces`, `architect` + cross-check), closed before exit. +- `lookupBuilderSpawningArchitect(builderId, workspacePath?)` gains an + optional third parameter `db?: Database.Database` defaulting to `getDb()` — + existing callers byte-for-byte unaffected, single source of truth for the + SQL preserved (the spec's "reuse, don't duplicate" constraint), and + `spec-755-lookup-builder.test.ts` gains a case passing an explicit + read-only handle. +- The old "acceptable, afx send does it too" risk item was replaced with the + API-signature risk + mitigation. +- Integration Points updated: `getDb()` is never invoked by whoami. + +### 2. Scenario 9 coverage overstated — ACCEPTED + +Verified: `packages/codev/src/__tests__/scaffold.test.ts` exists but has no +`copySkills()` tests, so "existing dynamic behavior" proved nothing. Added a +Phase 2 deliverable + test-plan entry: `copySkills()` regression tests in +`scaffold.test.ts` — copying from the real skeleton into a tmpdir target +installs `arch-init/SKILL.md` (the init/adopt/update install path), and +`skipExisting: true` preserves a pre-existing customized copy. Success +Metrics and Notes updated to match. + +### 3. `resolveIdentity` purity vs `process.cwd()` helpers — ACCEPTED + +Clarified in Phase 1: the send.ts helpers are NOT parameterized (not worth +the churn for two consumers); the core is `resolveIdentity(env)` which reads +cwd implicitly through `detectCurrentBuilderId()` / `detectWorkspaceRoot()`, +and tests control cwd via `process.chdir()` into tmpdir fixtures — the +established pattern at `send.test.ts:108–122`. No mid-implementation +impedance surprise. (Claude's review raised the same point independently; +same resolution.) + +## Gemini (APPROVE, one suggestion) + +- **Update legacy test assertions pinned to "state.db" wording in the same + Phase 3 commit** — already in the plan (Phase 3 risk + acceptance + criterion "update any message-text assertions"); kept as-is. + +## Claude (APPROVE, minor observations) + +- **Purity note** — same as Codex point 3; resolved above. +- **Drive-by scope (5 "state.db" occurrences, only 2 are stale wording)** — + confirmed and reflected in the Expert Review section: only the user-facing + message (`send.ts:84`) and the doc comment (`send.ts:52`) change; + historical "state.db is retired" migration-context comments stay. From 9b7cc5a267e473f6286ecca6a2c249119af4859f Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Fri, 3 Jul 2026 08:10:13 -0700 Subject: [PATCH 12/29] chore(porch): 1134 plan-approval gate-requested --- codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml b/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml index 3ce4a55e2..be9a509b0 100644 --- a/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml +++ b/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml @@ -11,6 +11,7 @@ gates: approved_at: '2026-07-03T15:02:32.243Z' plan-approval: status: pending + requested_at: '2026-07-03T15:10:13.750Z' pr: status: pending verify-approval: @@ -19,4 +20,4 @@ iteration: 1 build_complete: true history: [] started_at: '2026-07-03T14:44:55.927Z' -updated_at: '2026-07-03T15:05:31.153Z' +updated_at: '2026-07-03T15:10:13.751Z' From f6f5834b3f0b316cc1d2886846a1ec864b56d496 Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Fri, 3 Jul 2026 08:10:56 -0700 Subject: [PATCH 13/29] [Spec 1134] Update builder thread: plan-approval gate reached --- codev/state/spir-1134_thread.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/codev/state/spir-1134_thread.md b/codev/state/spir-1134_thread.md index 4ddcb9073..1eb865310 100644 --- a/codev/state/spir-1134_thread.md +++ b/codev/state/spir-1134_thread.md @@ -30,3 +30,26 @@ stderr. Declined `codev whoami` alias (scope). - Committed `[Spec 1134] Specification with multi-agent review`. - **Gate reached: spec-approval.** Architect notified via afx send. Waiting. + +## Spec approved → Plan phase + +- Waleed approved the spec as written (relayed by architect); recorded gate via + `porch approve 1134 spec-approval --a-human-explicitly-approved-this`. +- Drafted plan: 3 phases — (1) `whoami` command + tests, (2) `arch-init` skill + in both trees + tests, (3) docs sync (agent-farm.md + afx SKILL.md, both + trees) + state.db→global.db wording drive-by. +- Plan decision: import identity helpers from `commands/send.ts` (already + exported/tested) rather than relocating them. + +## Plan — CMAP iteration 1 + +- Verdicts: Gemini APPROVE, Claude APPROVE, Codex REQUEST_CHANGES. +- Accepted all 3 Codex points: (a) whoami is now strictly read-only — + `lookupBuilderSpawningArchitect` gains optional `db?` param so whoami passes + its own readonly connection instead of read-write `getDb()` (draft had a + genuine spec violation here); (b) new `copySkills()` regression tests in + scaffold.test.ts (none exist today — scenario 9 was uncovered); (c) pinned + the resolveIdentity(env) + process.chdir test approach (no helper + parameterization). +- Committed `[Spec 1134] Plan with multi-agent review`. +- **Gate reached: plan-approval.** Architect notified via afx send. Waiting. From 169845af64e3fb5da5922c7a3d443ef19c22464f Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Fri, 3 Jul 2026 09:50:06 -0700 Subject: [PATCH 14/29] chore(porch): 1134 plan-approval gate-approved --- .../projects/1134-afx-whoami-ship-arch-init-comm/status.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml b/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml index be9a509b0..593f6dd98 100644 --- a/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml +++ b/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml @@ -10,8 +10,9 @@ gates: requested_at: '2026-07-03T14:54:56.796Z' approved_at: '2026-07-03T15:02:32.243Z' plan-approval: - status: pending + status: approved requested_at: '2026-07-03T15:10:13.750Z' + approved_at: '2026-07-03T16:50:06.085Z' pr: status: pending verify-approval: @@ -20,4 +21,4 @@ iteration: 1 build_complete: true history: [] started_at: '2026-07-03T14:44:55.927Z' -updated_at: '2026-07-03T15:10:13.751Z' +updated_at: '2026-07-03T16:50:06.085Z' From cc23432de3d3f2ebeac39c8cc51b564bdf4e823b Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Fri, 3 Jul 2026 09:50:08 -0700 Subject: [PATCH 15/29] chore(porch): 1134 implement phase-transition --- .../status.yaml | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml b/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml index 593f6dd98..20add25e8 100644 --- a/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml +++ b/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml @@ -1,9 +1,18 @@ id: '1134' title: afx-whoami-ship-arch-init-comm protocol: spir -phase: plan -plan_phases: [] -current_plan_phase: null +phase: implement +plan_phases: + - id: phase_1 + title: afx whoami command + tests + status: in_progress + - id: phase_2 + title: arch-init skill in both trees + tests + status: pending + - id: phase_3 + title: CLI reference and afx-skill docs sync + status: pending +current_plan_phase: phase_1 gates: spec-approval: status: approved @@ -18,7 +27,7 @@ gates: verify-approval: status: pending iteration: 1 -build_complete: true +build_complete: false history: [] started_at: '2026-07-03T14:44:55.927Z' -updated_at: '2026-07-03T16:50:06.085Z' +updated_at: '2026-07-03T16:50:08.259Z' From 29464209168f49c87bcaabea1b29d19b6ad2ec46 Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Fri, 3 Jul 2026 09:58:23 -0700 Subject: [PATCH 16/29] [Spec 1134][Phase: whoami-command] feat: Add afx whoami identity diagnostic Resolves identity per spec precedence (builder-worktree cwd match via detectCurrentBuilderId -> CODEV_ARCHITECT_NAME -> fail loud, exit 1; no implicit 'main'). Strictly read-only against global.db: lookupBuilderSpawningArchitect gains an optional db handle so whoami can pass its readonly connection. Workspace display name from known_workspaces with basename fallback; best-effort architect-row cross-check warns on stderr only. --json emits the documented schema, and failure emits { "error": ... } on stdout alongside the stderr explanation. --- .../__tests__/spec-1134-whoami.test.ts | 300 ++++++++++++++++++ .../__tests__/spec-755-lookup-builder.test.ts | 31 ++ packages/codev/src/agent-farm/cli.ts | 15 + .../codev/src/agent-farm/commands/whoami.ts | 215 +++++++++++++ packages/codev/src/agent-farm/state.ts | 7 +- 5 files changed, 567 insertions(+), 1 deletion(-) create mode 100644 packages/codev/src/agent-farm/__tests__/spec-1134-whoami.test.ts create mode 100644 packages/codev/src/agent-farm/commands/whoami.ts diff --git a/packages/codev/src/agent-farm/__tests__/spec-1134-whoami.test.ts b/packages/codev/src/agent-farm/__tests__/spec-1134-whoami.test.ts new file mode 100644 index 000000000..fd9161136 --- /dev/null +++ b/packages/codev/src/agent-farm/__tests__/spec-1134-whoami.test.ts @@ -0,0 +1,300 @@ +/** + * Spec 1134 — `afx whoami` identity resolution and output. + * + * Covers the spec's test scenarios 1–8, 10, 11 plus the --json shapes: + * precedence (builder-worktree cwd match → CODEV_ARCHITECT_NAME → fail loud), + * the #1094 no-fallthrough rule (an unverifiable builder worktree is an + * error, never a reason to consult the env var), workspace display-name + * resolution via known_workspaces with basename fallback, and the + * best-effort architect-row cross-check warning. + * + * Fixture pattern mirrors bugfix-774-detect-builder-id.test.ts: redirect + * getGlobalDbPath() to a per-test temp file, seed via GLOBAL_SCHEMA, and + * control cwd with process.chdir(). + */ + +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import { mkdirSync, mkdtempSync, rmSync, realpathSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join, basename } from 'node:path'; +import Database from 'better-sqlite3'; +import { GLOBAL_SCHEMA } from '../db/schema.js'; + +// Redirect global.db to a per-test temp file (Issue #1118 pattern). +const dbState = vi.hoisted(() => ({ globalDbPath: '' })); +vi.mock('../db/index.js', async (importOriginal) => { + const actual = await importOriginal(); + return { ...actual, getGlobalDbPath: () => dbState.globalDbPath }; +}); + +const { resolveIdentity, formatIdentity, identityToJson, whoami, WhoamiError } = await import( + '../commands/whoami.js' +); +const { BuilderIdResolutionError } = await import('../commands/send.js'); + +describe('Spec 1134 — afx whoami', () => { + let tmpRoot: string; + let workspacePath: string; + let worktreePath: string; + const origCwd = process.cwd(); + const origArchitectName = process.env.CODEV_ARCHITECT_NAME; + + /** Env fixtures: resolveIdentity takes env explicitly. */ + const noEnv: NodeJS.ProcessEnv = {}; + const archEnv = (name: string): NodeJS.ProcessEnv => ({ CODEV_ARCHITECT_NAME: name }); + + function openDb(): Database.Database { + const db = new Database(dbState.globalDbPath); + db.exec(GLOBAL_SCHEMA); + return db; + } + + function seedBuilder(id: string, spawnedByArchitect: string | null): void { + const db = openDb(); + db.prepare( + `INSERT INTO builders (workspace_path, id, name, worktree, branch, type, status, spawned_by_architect) + VALUES (?, ?, ?, ?, ?, 'spec', 'implementing', ?)`, + ).run(realpathSync(workspacePath), id, id, worktreePath, `builder/${id}`, spawnedByArchitect); + db.close(); + } + + function seedKnownWorkspace(name: string): void { + const db = openDb(); + db.prepare('INSERT INTO known_workspaces (workspace_path, name) VALUES (?, ?)').run( + realpathSync(workspacePath), + name, + ); + db.close(); + } + + function seedArchitect(name: string): void { + const db = openDb(); + db.prepare( + 'INSERT INTO architect (workspace_path, id, pid, port, cmd) VALUES (?, ?, 1234, 0, ?)', + ).run(realpathSync(workspacePath), name, 'claude'); + db.close(); + } + + beforeEach(() => { + tmpRoot = mkdtempSync(join(tmpdir(), 'spec-1134-')); + workspacePath = join(tmpRoot, 'workspace'); + worktreePath = join(workspacePath, '.builders', 'spir-984'); + mkdirSync(worktreePath, { recursive: true }); + // Workspace-root marker so detectWorkspaceRoot() resolves from subdirs. + mkdirSync(join(workspacePath, '.git'), { recursive: true }); + + dbState.globalDbPath = join(tmpRoot, 'global.db'); + delete process.env.CODEV_ARCHITECT_NAME; + }); + + afterEach(() => { + process.chdir(origCwd); + if (origArchitectName === undefined) delete process.env.CODEV_ARCHITECT_NAME; + else process.env.CODEV_ARCHITECT_NAME = origArchitectName; + process.exitCode = undefined; + vi.restoreAllMocks(); + rmSync(tmpRoot, { recursive: true, force: true }); + }); + + // Scenario 1: architect via env, cwd = workspace root. + it('resolves an architect from CODEV_ARCHITECT_NAME at the workspace root', () => { + openDb().close(); // db exists but empty — no rows needed for env identity + process.chdir(workspacePath); + const identity = resolveIdentity(archEnv('ob-refine')); + expect(identity).toMatchObject({ type: 'architect', name: 'ob-refine' }); + expect(identity.workspace).toBe(basename(realpathSync(workspacePath))); + }); + + // Scenario 2: builder with recorded spawning architect. + it('resolves a builder with its spawning architect from global.db', () => { + seedBuilder('builder-spir-984', 'main'); + process.chdir(worktreePath); + const identity = resolveIdentity(noEnv); + expect(identity).toMatchObject({ type: 'builder', name: 'builder-spir-984', architect: 'main' }); + }); + + // Scenario 3: legacy row — NULL spawned_by_architect → field omitted. + it('omits the architect field for a legacy builder row (NULL spawned_by_architect)', () => { + seedBuilder('builder-spir-984', null); + process.chdir(worktreePath); + const identity = resolveIdentity(noEnv); + expect(identity).toMatchObject({ type: 'builder', name: 'builder-spir-984' }); + expect('architect' in identity).toBe(false); + expect(identityToJson(identity)).not.toHaveProperty('architect'); + }); + + // Scenario 4: unverifiable builder worktree throws; env NOT consulted. + it('throws BuilderIdResolutionError inside a worktree with no matching row — env is not consulted', () => { + openDb().close(); // schema exists, but no builder row + process.chdir(worktreePath); + // Env var set — the #1094 rule forbids falling through to it. + expect(() => resolveIdentity(archEnv('main'))).toThrow(BuilderIdResolutionError); + }); + + it('throws BuilderIdResolutionError inside a worktree when global.db is missing', () => { + process.chdir(worktreePath); + expect(() => resolveIdentity(archEnv('main'))).toThrow(BuilderIdResolutionError); + }); + + // Scenario 5: no signals at all. + it('throws WhoamiError from a plain shell (no worktree, no env)', () => { + process.chdir(tmpRoot); + expect(() => resolveIdentity(noEnv)).toThrow(WhoamiError); + expect(() => resolveIdentity(noEnv)).toThrow(/CODEV_ARCHITECT_NAME is not set/); + }); + + // Scenario 6: precedence — builder cwd beats env. + it('prefers builder identity over CODEV_ARCHITECT_NAME inside a worktree', () => { + seedBuilder('builder-spir-984', 'main'); + process.chdir(worktreePath); + const identity = resolveIdentity(archEnv('ob-refine')); + expect(identity.type).toBe('builder'); + expect(identity.name).toBe('builder-spir-984'); + }); + + // Scenario 7: whitespace-only env var is unset. + it('treats a whitespace-only CODEV_ARCHITECT_NAME as unset', () => { + process.chdir(tmpRoot); + expect(() => resolveIdentity(archEnv(' '))).toThrow(WhoamiError); + }); + + // Scenario 8: known_workspaces display name vs basename fallback. + it('uses the known_workspaces name when registered', () => { + seedKnownWorkspace('my-codev'); + process.chdir(workspacePath); + expect(resolveIdentity(archEnv('main')).workspace).toBe('my-codev'); + }); + + it('falls back to the directory basename when the workspace is unregistered', () => { + seedBuilder('builder-spir-984', 'main'); + process.chdir(worktreePath); + expect(resolveIdentity(noEnv).workspace).toBe(basename(realpathSync(workspacePath))); + }); + + // Scenario 10: architect running whoami from a subdirectory. + it('resolves the workspace root from a subdirectory cwd (architect)', () => { + seedKnownWorkspace('my-codev'); + const subdir = join(workspacePath, 'packages', 'codev', 'src'); + mkdirSync(subdir, { recursive: true }); + process.chdir(subdir); + const identity = resolveIdentity(archEnv('main')); + expect(identity).toMatchObject({ type: 'architect', name: 'main', workspace: 'my-codev' }); + }); + + // Scenario 11: env architect without an architect table row → rowMissing. + it('flags rowMissing when no architect row matches the env name', () => { + openDb().close(); + process.chdir(workspacePath); + const identity = resolveIdentity(archEnv('ghost')); + expect(identity).toMatchObject({ type: 'architect', name: 'ghost', rowMissing: true }); + }); + + it('does not flag rowMissing when the architect row exists', () => { + seedArchitect('ob-refine'); + process.chdir(workspacePath); + const identity = resolveIdentity(archEnv('ob-refine')); + expect(identity.type).toBe('architect'); + expect((identity as { rowMissing?: boolean }).rowMissing).toBeUndefined(); + }); + + // Output formats. + describe('output formats', () => { + it('formats the human-readable lines exactly as specified', () => { + expect( + formatIdentity({ type: 'builder', workspace: 'codev', name: 'builder-spir-984', architect: 'main' }), + ).toBe('workspace: codev\ntype: builder\nname: builder-spir-984\narchitect: main'); + expect(formatIdentity({ type: 'architect', workspace: 'codev', name: 'main' })).toBe( + 'workspace: codev\ntype: architect\nname: main', + ); + }); + + it('emits the JSON schema with architect omitted (not null) when unknown', () => { + expect(identityToJson({ type: 'builder', workspace: 'codev', name: 'b', architect: 'main' })).toEqual({ + workspace: 'codev', + type: 'builder', + name: 'b', + architect: 'main', + }); + const withoutArchitect = identityToJson({ type: 'builder', workspace: 'codev', name: 'b' }); + expect(withoutArchitect).toEqual({ workspace: 'codev', type: 'builder', name: 'b' }); + expect(JSON.stringify(withoutArchitect)).not.toContain('architect'); + }); + }); + + // The whoami() command wrapper: exit codes and streams. + describe('whoami() command', () => { + let stdout: string[]; + let stderr: string[]; + + beforeEach(() => { + stdout = []; + stderr = []; + vi.spyOn(process.stdout, 'write').mockImplementation((chunk) => { + stdout.push(String(chunk)); + return true; + }); + vi.spyOn(process.stderr, 'write').mockImplementation((chunk) => { + stderr.push(String(chunk)); + return true; + }); + }); + + it('prints identity to stdout and exits 0 on success', async () => { + seedBuilder('builder-spir-984', 'main'); + process.chdir(worktreePath); + await whoami({}); + expect(process.exitCode).toBeUndefined(); + expect(stdout.join('')).toContain('type: builder'); + expect(stdout.join('')).toContain('architect: main'); + }); + + it('prints a parseable JSON object under --json', async () => { + seedBuilder('builder-spir-984', 'main'); + process.chdir(worktreePath); + await whoami({ json: true }); + const payload = JSON.parse(stdout.join('')); + expect(payload).toEqual({ + workspace: basename(realpathSync(workspacePath)), + type: 'builder', + name: 'builder-spir-984', + architect: 'main', + }); + }); + + it('exits 1 with stderr explanation when identity is unknown', async () => { + process.chdir(tmpRoot); + await whoami({}); + expect(process.exitCode).toBe(1); + expect(stderr.join('')).toContain('Cannot determine agent identity'); + expect(stdout.join('')).toBe(''); + }); + + it('emits { "error": ... } on stdout AND the explanation on stderr under --json failure', async () => { + process.chdir(tmpRoot); + await whoami({ json: true }); + expect(process.exitCode).toBe(1); + const payload = JSON.parse(stdout.join('')); + expect(payload).toHaveProperty('error'); + expect(payload.error).toContain('Cannot determine agent identity'); + expect(stderr.join('')).toContain('Cannot determine agent identity'); + }); + + it('surfaces the BuilderIdResolutionError message verbatim on the worktree failure path', async () => { + openDb().close(); + process.chdir(worktreePath); + await whoami({}); + expect(process.exitCode).toBe(1); + expect(stderr.join('')).toContain('no matching builder row'); + }); + + it('warns on stderr (exit 0) when the architect row is missing', async () => { + openDb().close(); + process.chdir(workspacePath); + process.env.CODEV_ARCHITECT_NAME = 'ghost'; + await whoami({}); + expect(process.exitCode).toBeUndefined(); + expect(stderr.join('')).toContain('no matching architect row'); + expect(stdout.join('')).toContain('name: ghost'); + }); + }); +}); diff --git a/packages/codev/src/agent-farm/__tests__/spec-755-lookup-builder.test.ts b/packages/codev/src/agent-farm/__tests__/spec-755-lookup-builder.test.ts index 306ac0b61..fb0f4d8ea 100644 --- a/packages/codev/src/agent-farm/__tests__/spec-755-lookup-builder.test.ts +++ b/packages/codev/src/agent-farm/__tests__/spec-755-lookup-builder.test.ts @@ -115,4 +115,35 @@ describe('Issue #1118 — lookupBuilderSpawningArchitect (single shared global.d insertBuilder(wsA, 'only-in-a', 'sibling'); expect(lookupBuilderSpawningArchitect('only-in-a', wsB)).toBeUndefined(); }); + + it('uses an explicitly supplied db handle instead of getDb() (Spec 1134 read-only callers)', () => { + // Row exists only in the singleton (mocked getDb) db, NOT in the explicit + // handle — proving the explicit handle is the one queried. + insertBuilder(wsA, 'spir-100', 'sibling'); + + const explicit = new Database(':memory:'); + explicit.exec(` + CREATE TABLE builders ( + workspace_path TEXT NOT NULL, + id TEXT NOT NULL, + name TEXT NOT NULL, + worktree TEXT NOT NULL, + branch TEXT NOT NULL, + spawned_by_architect TEXT, + PRIMARY KEY (workspace_path, id) + ); + `); + try { + expect(lookupBuilderSpawningArchitect('spir-100', wsA, explicit)).toBeUndefined(); + + explicit + .prepare( + 'INSERT INTO builders (workspace_path, id, name, worktree, branch, spawned_by_architect) VALUES (?, ?, ?, ?, ?, ?)', + ) + .run(realpathSync(wsA), 'spir-100', 'x', join(wsA, '.builders', 'spir-100'), 'main', 'ro-architect'); + expect(lookupBuilderSpawningArchitect('spir-100', wsA, explicit)).toBe('ro-architect'); + } finally { + explicit.close(); + } + }); }); diff --git a/packages/codev/src/agent-farm/cli.ts b/packages/codev/src/agent-farm/cli.ts index 6a9db535f..5c6757faa 100644 --- a/packages/codev/src/agent-farm/cli.ts +++ b/packages/codev/src/agent-farm/cli.ts @@ -230,6 +230,21 @@ export async function runAgentFarm(args: string[]): Promise { } }); + // Whoami command (Spec 1134) — report this terminal's agent identity + program + .command('whoami') + .description("Report this terminal's agent identity (workspace, type, name)") + .option('--json', 'Output machine-readable JSON') + .action(async (options) => { + const { whoami } = await import('./commands/whoami.js'); + try { + await whoami({ json: options.json }); + } catch (error) { + logger.error(error instanceof Error ? error.message : String(error)); + process.exit(1); + } + }); + // Attach command program .command('attach') diff --git a/packages/codev/src/agent-farm/commands/whoami.ts b/packages/codev/src/agent-farm/commands/whoami.ts new file mode 100644 index 000000000..470c29cd5 --- /dev/null +++ b/packages/codev/src/agent-farm/commands/whoami.ts @@ -0,0 +1,215 @@ +/** + * Whoami command — report this terminal's agent identity (Spec 1134). + * + * Answers "who am I, from Tower/global.db's perspective?" for any terminal: + * builders resolve from their worktree cwd against global.db, architects from + * the Tower-injected CODEV_ARCHITECT_NAME env var. Identity precedence is + * fixed by the spec: builder-worktree cwd match → CODEV_ARCHITECT_NAME → + * unknown (exit non-zero). There is deliberately NO implicit fallback to + * 'main' — an unverified identity misroutes downstream consumers (issue + * #1094), so unknown must fail loud. + * + * Read-only invariant: this command never opens global.db read-write. Builder + * resolution reuses detectCurrentBuilderId() (its own read-only connection); + * all whoami-specific queries share one read-only connection opened here. + * + * The identity helpers are imported from ./send.js rather than relocated: + * they are already exported and tested there, and whoami is only their second + * consumer (extraction deferred until a third appears). + */ + +import { basename } from 'node:path'; +import Database from 'better-sqlite3'; +import { detectCurrentBuilderId, detectWorkspaceRoot, BuilderIdResolutionError } from './send.js'; +import { lookupBuilderSpawningArchitect } from '../state.js'; +import { getGlobalDbPath } from '../db/index.js'; +import { normalizeWorkspacePath } from '../utils/workspace-path.js'; + +export interface WhoamiOptions { + json?: boolean; +} + +/** + * Resolved identity. `architect` (builder) is omitted when the row's + * `spawned_by_architect` is NULL (legacy) or missing. `rowMissing` + * (architect) marks a best-effort diagnostic: the env var named an architect + * but no matching `architect` table row exists — surfaced as a stderr + * warning, never a failure (rows are legitimately absent after crashes). + */ +export type WhoamiIdentity = + | { type: 'builder'; workspace: string; name: string; architect?: string } + | { type: 'architect'; workspace: string; name: string; rowMissing?: boolean }; + +/** Thrown when no identity signal resolves. Message is the user-facing text. */ +export class WhoamiError extends Error { + constructor(message: string) { + super(message); + this.name = 'WhoamiError'; + } +} + +/** + * Open global.db read-only, or return null when it is missing/unopenable. + * Null degrades gracefully: the informational lookups (workspace display + * name, spawning architect, architect-row cross-check) are skipped. Identity + * itself never depends on this handle — the builder path already verified + * against the db inside detectCurrentBuilderId(), and the architect path is + * env-based. + */ +function openReadonlyGlobalDb(): Database.Database | null { + try { + return new Database(getGlobalDbPath(), { readonly: true }); + } catch { + return null; + } +} + +/** + * Display name for the workspace: the `known_workspaces` registry entry when + * present, else the directory basename. Informational context only — the + * spec's fail-loud rule applies to type/name, not this field. + */ +function workspaceDisplayName(workspaceRoot: string | null, db: Database.Database | null): string { + const root = workspaceRoot ?? process.cwd(); + if (db) { + try { + const row = db + .prepare('SELECT name FROM known_workspaces WHERE workspace_path = ?') + .get(normalizeWorkspacePath(root)) as { name: string } | undefined; + if (row?.name) return row.name; + } catch { + // Table missing or query failure — informational field, fall through. + } + } + return basename(root); +} + +/** + * Resolve the current terminal's identity per the spec precedence. + * + * Throws `BuilderIdResolutionError` when cwd IS a builder worktree whose + * identity cannot be verified (no fallthrough to the env check — #1094), and + * `WhoamiError` when no identity signal resolves at all. + */ +export function resolveIdentity(env: NodeJS.ProcessEnv = process.env): WhoamiIdentity { + // 1. Builder-worktree cwd match. A BuilderIdResolutionError propagates + // verbatim: inside a confirmed worktree, an unverifiable identity is an + // error, never a reason to consult the env var. + const builderId = detectCurrentBuilderId(); + const workspaceRoot = detectWorkspaceRoot(); + const db = openReadonlyGlobalDb(); + try { + if (builderId) { + const identity: WhoamiIdentity = { + type: 'builder', + workspace: workspaceDisplayName(workspaceRoot, db), + name: builderId, + }; + if (db) { + try { + const spawnedBy = lookupBuilderSpawningArchitect(builderId, workspaceRoot ?? undefined, db); + if (spawnedBy) identity.architect = spawnedBy; + } catch { + // Enrichment only — the identity is already verified; a lookup + // failure means "not recorded", not "unknown identity". + } + } + return identity; + } + + // 2. Tower-injected architect env var. Read directly, NOT via + // currentArchitectName() — its default of 'main' is exactly the + // implicit fallback whoami must not have. + const architectName = env.CODEV_ARCHITECT_NAME?.trim(); + if (architectName) { + const identity: WhoamiIdentity = { + type: 'architect', + workspace: workspaceDisplayName(workspaceRoot, db), + name: architectName, + }; + // Best-effort cross-check against the architect table (diagnostics + // only): a missing row is a warning, never a failure — rows are + // legitimately absent in the crash-recovery scenarios whoami serves. + if (db && workspaceRoot) { + try { + const row = db + .prepare('SELECT 1 AS present FROM architect WHERE workspace_path = ? AND id = ?') + .get(normalizeWorkspacePath(workspaceRoot), architectName); + if (!row) identity.rowMissing = true; + } catch { + // Swallowed by design — this check never affects output or exit code. + } + } + return identity; + } + } finally { + db?.close(); + } + + // 3. Fail loud. No implicit 'main'. + throw new WhoamiError( + 'Cannot determine agent identity: the current directory is not inside a ' + + '.builders// worktree and CODEV_ARCHITECT_NAME is not set. ' + + 'Likely causes: a plain shell, a terminal not started by Tower, or a ' + + 'pre-#786 architect terminal. Refusing to guess (issue #1094) — ' + + 'identity must come from a verified signal, never a default.', + ); +} + +/** Format an identity as the human-readable `key: value` lines. */ +export function formatIdentity(identity: WhoamiIdentity): string { + const lines = [`workspace: ${identity.workspace}`, `type: ${identity.type}`, `name: ${identity.name}`]; + if (identity.type === 'builder' && identity.architect) { + lines.push(`architect: ${identity.architect}`); + } + return lines.join('\n'); +} + +/** Format an identity as the JSON payload (`architect` omitted when unknown). */ +export function identityToJson(identity: WhoamiIdentity): Record { + const payload: Record = { + workspace: identity.workspace, + type: identity.type, + name: identity.name, + }; + if (identity.type === 'builder' && identity.architect) { + payload.architect = identity.architect; + } + return payload; +} + +/** + * `afx whoami` — print this terminal's identity. + * + * Success: identity to stdout (text or --json), exit 0. + * Unknown identity: explanation to stderr (always), `{ "error": ... }` to + * stdout under --json, exit 1 via process.exitCode (streams flush). + */ +export async function whoami(options: WhoamiOptions = {}): Promise { + let identity: WhoamiIdentity; + try { + identity = resolveIdentity(); + } catch (err) { + if (!(err instanceof WhoamiError) && !(err instanceof BuilderIdResolutionError)) throw err; + process.stderr.write(`${err.message}\n`); + if (options.json) { + process.stdout.write(`${JSON.stringify({ error: err.message })}\n`); + } + process.exitCode = 1; + return; + } + + if (identity.type === 'architect' && identity.rowMissing) { + process.stderr.write( + `warning: CODEV_ARCHITECT_NAME='${identity.name}' but no matching architect row ` + + `in global.db for this workspace (Tower may not have registered this terminal, ` + + `or it predates a crash). Identity is reported from the env signal.\n`, + ); + } + + if (options.json) { + process.stdout.write(`${JSON.stringify(identityToJson(identity))}\n`); + } else { + process.stdout.write(`${formatIdentity(identity)}\n`); + } +} diff --git a/packages/codev/src/agent-farm/state.ts b/packages/codev/src/agent-farm/state.ts index 79757ba14..9fa1f5511 100644 --- a/packages/codev/src/agent-farm/state.ts +++ b/packages/codev/src/agent-farm/state.ts @@ -6,6 +6,7 @@ */ import path from 'node:path'; +import type Database from 'better-sqlite3'; import type { DashboardState, ArchitectState, Builder, UtilTerminal, Annotation } from './types.js'; import { getDb, closeDb } from './db/index.js'; import type { DbArchitect, DbBuilder, DbUtil, DbAnnotation } from './db/types.js'; @@ -533,12 +534,16 @@ export function getArchitectByName(workspacePath: string, name: string): Archite * spoofing check must resolve to the *correct* workspace's spawning architect. * When omitted (a CLI caller already inside one workspace), it falls back to * match by id alone. + * + * Spec 1134: `db` lets a caller supply its own connection — `afx whoami` passes + * a read-only handle so it never opens global.db read-write (its spec + * requirement). Defaults to the shared `getDb()` singleton. */ export function lookupBuilderSpawningArchitect( builderId: string, workspacePath?: string, + db: Database.Database = getDb(), ): string | null | undefined { - const db = getDb(); let row: { spawned_by_architect: string | null } | undefined; if (workspacePath) { row = db From 883cf71260868510f7ab1ee32586dfcdef57c3a6 Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Fri, 3 Jul 2026 09:58:57 -0700 Subject: [PATCH 17/29] chore(porch): 1134 implement build-complete --- .../projects/1134-afx-whoami-ship-arch-init-comm/status.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml b/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml index 20add25e8..7119602f6 100644 --- a/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml +++ b/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml @@ -27,7 +27,7 @@ gates: verify-approval: status: pending iteration: 1 -build_complete: false +build_complete: true history: [] started_at: '2026-07-03T14:44:55.927Z' -updated_at: '2026-07-03T16:50:08.259Z' +updated_at: '2026-07-03T16:58:57.315Z' From 55909bbcc9b79bb9e7b5b1f093c9fd49aefff56c Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Fri, 3 Jul 2026 10:09:58 -0700 Subject: [PATCH 18/29] =?UTF-8?q?chore(porch):=201134=20advance=20plan=20p?= =?UTF-8?q?hase=20=E2=86=92=20phase=5F2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../1134-afx-whoami-ship-arch-init-comm/status.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml b/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml index 7119602f6..3c32d55a9 100644 --- a/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml +++ b/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml @@ -5,14 +5,14 @@ phase: implement plan_phases: - id: phase_1 title: afx whoami command + tests - status: in_progress + status: complete - id: phase_2 title: arch-init skill in both trees + tests - status: pending + status: in_progress - id: phase_3 title: CLI reference and afx-skill docs sync status: pending -current_plan_phase: phase_1 +current_plan_phase: phase_2 gates: spec-approval: status: approved @@ -27,7 +27,7 @@ gates: verify-approval: status: pending iteration: 1 -build_complete: true +build_complete: false history: [] started_at: '2026-07-03T14:44:55.927Z' -updated_at: '2026-07-03T16:58:57.315Z' +updated_at: '2026-07-03T17:09:58.503Z' From 27ea712c332b59d89099259b217d0fed0e8e5936 Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Fri, 3 Jul 2026 10:13:34 -0700 Subject: [PATCH 19/29] [Spec 1134][Phase: arch-init-skill] feat: Ship /arch-init skill in both trees MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Workspace-agnostic identity-adoption command for architect terminals: explicit [name] argument (validated against the architect-name rule, path-traversal guarded) or afx whoami resolution; STOP-and-ask when neither resolves — never guess, never default to main. Reads codev/state/.md; missing-file flow excludes builder *_thread.md files. Byte-identical in codev-skeleton/ and .claude/skills/ with a drift-guard test, plus first copySkills() regression tests proving the init/adopt/update install path. --- .claude/skills/arch-init/SKILL.md | 62 +++++++++++++ .../.claude/skills/arch-init/SKILL.md | 62 +++++++++++++ packages/codev/src/__tests__/scaffold.test.ts | 50 ++++++++++ .../spec-1134-arch-init-skill.test.ts | 93 +++++++++++++++++++ 4 files changed, 267 insertions(+) create mode 100644 .claude/skills/arch-init/SKILL.md create mode 100644 codev-skeleton/.claude/skills/arch-init/SKILL.md create mode 100644 packages/codev/src/agent-farm/__tests__/spec-1134-arch-init-skill.test.ts diff --git a/.claude/skills/arch-init/SKILL.md b/.claude/skills/arch-init/SKILL.md new file mode 100644 index 000000000..26cacde9d --- /dev/null +++ b/.claude/skills/arch-init/SKILL.md @@ -0,0 +1,62 @@ +--- +name: arch-init +description: Adopt an architect identity and recover its state from codev/state/.md. Use when an architect terminal needs to (re)establish which architect it is — after a restart, context loss, or session handoff — or when the user says "/arch-init", "you are the X architect", or "recover your architect state". Identity resolves via `afx whoami` (an explicit name argument overrides); if neither resolves, ask the human — never guess. +argument-hint: "[name] (e.g. main; omit to auto-detect via afx whoami)" +--- + +# /arch-init — become architect `` and recover state + +You are an **architect agent** in a codev workspace. This command tells you +which architect you are and where your durable state lives, so you can resume +mid-stream. + +`$ARGUMENTS` is the architect name (e.g. `main`, or a sibling architect's +name in a multi-architect workspace). + +## What to do + +1. **Resolve your name.** + - If `$ARGUMENTS` is non-empty, that is your name — the human named you + explicitly, which removes all identity-resolution risk. **Validate it + first**: an architect name must match `[a-z][a-z0-9-]*` and be at most + 64 characters (lowercase letters, digits, hyphens; starts with a + letter). Reject anything else — slashes, `..`, uppercase, spaces — and + tell the human the rule. Never build a file path from an unvalidated + name (path-traversal guard). + - If `$ARGUMENTS` is empty, run `afx whoami` and read its output: + - `type: architect` → adopt the reported `name`. + - `type: builder` → STOP. This terminal is a builder, not an architect; + report the mismatch to the human and do not adopt an architect + identity. + - Non-zero exit (identity unknown) → STOP and ask the human which + architect you are. Do NOT guess, and do NOT default to `main` — + adopting the wrong identity and writing to another architect's state + file is the exact failure this command exists to prevent. + +2. **Read your state file: `codev/state/.md`** (relative to the + workspace root). + - If it does not exist: list the architect state files in `codev/state/` + — **excluding `*_thread.md` files**, which are builder thread logs that + share the directory — tell the human the file is missing, and ask + whether to start a fresh state file for ``. Do not fabricate + state. + - The state file is authoritative free text. It typically opens with a + role banner and may carry resume instructions; follow whatever it says. + +3. **Confirm identity + orient, then follow the state file.** In one tight + block, report: who you now are (name + one-line role from the banner, if + present), the file you read, and the current-state / open-loops summary + from the most recent dated section (or the file's leading content if it + has no dated sections). Then carry out whatever the state file says to do + on resume. Do not invent a new agenda — resume the one the state file + describes. + +## Guardrails (architect-wide; the state file may add more) + +- **Never auto-approve porch gates.** A gate notification is for the human, + not you. +- **Touch only your own builders / spawns / filings.** Sibling architects own + theirs. +- **Never `cd` into a builder worktree**; use `git -C` + absolute paths. +- **Stay on the default branch at the workspace root**; verify with + `git branch` if unsure. diff --git a/codev-skeleton/.claude/skills/arch-init/SKILL.md b/codev-skeleton/.claude/skills/arch-init/SKILL.md new file mode 100644 index 000000000..26cacde9d --- /dev/null +++ b/codev-skeleton/.claude/skills/arch-init/SKILL.md @@ -0,0 +1,62 @@ +--- +name: arch-init +description: Adopt an architect identity and recover its state from codev/state/.md. Use when an architect terminal needs to (re)establish which architect it is — after a restart, context loss, or session handoff — or when the user says "/arch-init", "you are the X architect", or "recover your architect state". Identity resolves via `afx whoami` (an explicit name argument overrides); if neither resolves, ask the human — never guess. +argument-hint: "[name] (e.g. main; omit to auto-detect via afx whoami)" +--- + +# /arch-init — become architect `` and recover state + +You are an **architect agent** in a codev workspace. This command tells you +which architect you are and where your durable state lives, so you can resume +mid-stream. + +`$ARGUMENTS` is the architect name (e.g. `main`, or a sibling architect's +name in a multi-architect workspace). + +## What to do + +1. **Resolve your name.** + - If `$ARGUMENTS` is non-empty, that is your name — the human named you + explicitly, which removes all identity-resolution risk. **Validate it + first**: an architect name must match `[a-z][a-z0-9-]*` and be at most + 64 characters (lowercase letters, digits, hyphens; starts with a + letter). Reject anything else — slashes, `..`, uppercase, spaces — and + tell the human the rule. Never build a file path from an unvalidated + name (path-traversal guard). + - If `$ARGUMENTS` is empty, run `afx whoami` and read its output: + - `type: architect` → adopt the reported `name`. + - `type: builder` → STOP. This terminal is a builder, not an architect; + report the mismatch to the human and do not adopt an architect + identity. + - Non-zero exit (identity unknown) → STOP and ask the human which + architect you are. Do NOT guess, and do NOT default to `main` — + adopting the wrong identity and writing to another architect's state + file is the exact failure this command exists to prevent. + +2. **Read your state file: `codev/state/.md`** (relative to the + workspace root). + - If it does not exist: list the architect state files in `codev/state/` + — **excluding `*_thread.md` files**, which are builder thread logs that + share the directory — tell the human the file is missing, and ask + whether to start a fresh state file for ``. Do not fabricate + state. + - The state file is authoritative free text. It typically opens with a + role banner and may carry resume instructions; follow whatever it says. + +3. **Confirm identity + orient, then follow the state file.** In one tight + block, report: who you now are (name + one-line role from the banner, if + present), the file you read, and the current-state / open-loops summary + from the most recent dated section (or the file's leading content if it + has no dated sections). Then carry out whatever the state file says to do + on resume. Do not invent a new agenda — resume the one the state file + describes. + +## Guardrails (architect-wide; the state file may add more) + +- **Never auto-approve porch gates.** A gate notification is for the human, + not you. +- **Touch only your own builders / spawns / filings.** Sibling architects own + theirs. +- **Never `cd` into a builder worktree**; use `git -C` + absolute paths. +- **Stay on the default branch at the workspace root**; verify with + `git branch` if unsure. diff --git a/packages/codev/src/__tests__/scaffold.test.ts b/packages/codev/src/__tests__/scaffold.test.ts index 1c7bf970c..de84ebb9b 100644 --- a/packages/codev/src/__tests__/scaffold.test.ts +++ b/packages/codev/src/__tests__/scaffold.test.ts @@ -13,6 +13,7 @@ import { copyResourceTemplates, copyRoles, copyRootFiles, + copySkills, } from '../lib/scaffold.js'; describe('Scaffold Utilities', () => { @@ -290,4 +291,53 @@ describe('Scaffold Utilities', () => { expect(scaffoldSource).not.toContain('copyProjectlistArchive'); }); }); + + // Spec 1134 — copySkills() is the install path for .claude/skills/ (used by + // init/adopt/update). First regression tests for it: the dynamic directory + // enumeration must pick up new skills (arch-init), and skipExisting must + // preserve a user's customized copy. + describe('copySkills (Spec 1134)', () => { + // The REAL repo skeleton — proves the shipped arch-init skill installs, + // not just a mock (spec 1134 test scenario 9). + const realSkeletonDir = path.resolve(__dirname, '..', '..', '..', '..', 'codev-skeleton'); + + it('installs arch-init/SKILL.md from the real skeleton into a fresh target', () => { + const result = copySkills(tempDir, realSkeletonDir); + expect(result.copied).toContain('arch-init'); + expect( + fs.existsSync(path.join(tempDir, '.claude', 'skills', 'arch-init', 'SKILL.md')) + ).toBe(true); + }); + + it('enumerates skill directories dynamically (copies every skeleton skill)', () => { + const result = copySkills(tempDir, realSkeletonDir); + const skeletonSkills = fs + .readdirSync(path.join(realSkeletonDir, '.claude', 'skills'), { withFileTypes: true }) + .filter((e) => e.isDirectory()) + .map((e) => e.name); + expect(result.copied.sort()).toEqual(skeletonSkills.sort()); + }); + + it('skipExisting: true preserves an existing customized copy', () => { + const customized = path.join(tempDir, '.claude', 'skills', 'arch-init'); + fs.mkdirSync(customized, { recursive: true }); + fs.writeFileSync(path.join(customized, 'SKILL.md'), 'user-customized content'); + + const result = copySkills(tempDir, realSkeletonDir, { skipExisting: true }); + expect(result.skipped).toContain('arch-init'); + expect(fs.readFileSync(path.join(customized, 'SKILL.md'), 'utf-8')).toBe( + 'user-customized content' + ); + }); + + it('overwrites by default (skipExisting omitted) so update refreshes skills', () => { + const existing = path.join(tempDir, '.claude', 'skills', 'arch-init'); + fs.mkdirSync(existing, { recursive: true }); + fs.writeFileSync(path.join(existing, 'SKILL.md'), 'stale content'); + + const result = copySkills(tempDir, realSkeletonDir); + expect(result.copied).toContain('arch-init'); + expect(fs.readFileSync(path.join(existing, 'SKILL.md'), 'utf-8')).not.toBe('stale content'); + }); + }); }); diff --git a/packages/codev/src/agent-farm/__tests__/spec-1134-arch-init-skill.test.ts b/packages/codev/src/agent-farm/__tests__/spec-1134-arch-init-skill.test.ts new file mode 100644 index 000000000..750b203ce --- /dev/null +++ b/packages/codev/src/agent-farm/__tests__/spec-1134-arch-init-skill.test.ts @@ -0,0 +1,93 @@ +/** + * Spec 1134 — `/arch-init` skill-text assertions (spec test scenario 12). + * + * The shipped SKILL.md text is the testable artifact (a skill is instructions + * to an agent, not code). These tests pin the two-tree byte equality and the + * spec's required/forbidden content: + * - identity via `afx whoami` with explicit-arg override + validation rule + * - no `ps`/`$PPID` process-ancestry matching (the fragility being replaced) + * - no Shannon-specific wording (workspace-agnostic) + * - builder-thread exclusion in the missing-state-file flow + * - the four architect guardrails + */ + +import { describe, it, expect } from 'vitest'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; + +// Resolve repo root (packages/codev/src/agent-farm/__tests__ -> repo root) +const repoRoot = path.resolve(__dirname, '..', '..', '..', '..', '..'); + +const SKELETON_SKILL = path.join(repoRoot, 'codev-skeleton', '.claude', 'skills', 'arch-init', 'SKILL.md'); +const INSTANCE_SKILL = path.join(repoRoot, '.claude', 'skills', 'arch-init', 'SKILL.md'); + +describe('Spec 1134 — /arch-init skill ships in both trees', () => { + it('exists in the skeleton (shipped to adopters) and in our instance', () => { + expect(fs.existsSync(SKELETON_SKILL)).toBe(true); + expect(fs.existsSync(INSTANCE_SKILL)).toBe(true); + }); + + it('is byte-identical across the two trees (drift guard)', () => { + expect(fs.readFileSync(INSTANCE_SKILL, 'utf-8')).toBe(fs.readFileSync(SKELETON_SKILL, 'utf-8')); + }); + + describe('required content', () => { + const text = () => fs.readFileSync(SKELETON_SKILL, 'utf-8'); + + it('resolves identity via afx whoami', () => { + expect(text()).toContain('afx whoami'); + }); + + it('carries the architect-name validation rule (path-traversal guard)', () => { + expect(text()).toContain('[a-z][a-z0-9-]*'); + expect(text()).toMatch(/64/); + expect(text()).toContain('..'); + }); + + it('excludes builder thread files when listing state files', () => { + expect(text()).toContain('_thread.md'); + }); + + it('handles both whoami identity types and the unknown case', () => { + expect(text()).toContain('type: architect'); + expect(text()).toContain('type: builder'); + expect(text()).toMatch(/ask the human/i); + }); + + it('reads architect state from codev/state/.md', () => { + expect(text()).toContain('codev/state/.md'); + }); + + it('carries the four architect guardrails', () => { + const t = text(); + expect(t).toMatch(/never auto-approve porch gates/i); + expect(t).toMatch(/only your own builders/i); + expect(t).toMatch(/never `?cd`? into a builder worktree/i); + expect(t).toMatch(/default branch/i); + }); + + it('never defaults to main', () => { + expect(text()).toMatch(/do NOT default to `main`/i); + }); + }); + + describe('forbidden content', () => { + const text = () => fs.readFileSync(SKELETON_SKILL, 'utf-8'); + + it('has no ps/$PPID process-ancestry matching (replaced by afx whoami)', () => { + expect(text()).not.toContain('ps -p'); + expect(text()).not.toContain('$PPID'); + expect(text()).not.toMatch(/ancestry/i); + }); + + it('has no Shannon-specific wording (workspace-agnostic)', () => { + expect(text()).not.toMatch(/shannon/i); + }); + }); + + it('has the expected skill frontmatter (name + description)', () => { + const t = fs.readFileSync(SKELETON_SKILL, 'utf-8'); + expect(t).toMatch(/^---\nname: arch-init\n/); + expect(t).toMatch(/\ndescription: /); + }); +}); From b30053923378475753cb74af21f5f246c3994f27 Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Fri, 3 Jul 2026 10:14:01 -0700 Subject: [PATCH 20/29] chore(porch): 1134 implement build-complete --- .../projects/1134-afx-whoami-ship-arch-init-comm/status.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml b/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml index 3c32d55a9..99c79471e 100644 --- a/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml +++ b/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml @@ -27,7 +27,7 @@ gates: verify-approval: status: pending iteration: 1 -build_complete: false +build_complete: true history: [] started_at: '2026-07-03T14:44:55.927Z' -updated_at: '2026-07-03T17:09:58.503Z' +updated_at: '2026-07-03T17:14:01.037Z' From 8ef691057afb44b9e182b80a9acd0d699a2388ca Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Fri, 3 Jul 2026 10:17:22 -0700 Subject: [PATCH 21/29] =?UTF-8?q?chore(porch):=201134=20advance=20plan=20p?= =?UTF-8?q?hase=20=E2=86=92=20phase=5F3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../1134-afx-whoami-ship-arch-init-comm/status.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml b/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml index 99c79471e..e2ecd2b7d 100644 --- a/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml +++ b/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml @@ -8,11 +8,11 @@ plan_phases: status: complete - id: phase_2 title: arch-init skill in both trees + tests - status: in_progress + status: complete - id: phase_3 title: CLI reference and afx-skill docs sync - status: pending -current_plan_phase: phase_2 + status: in_progress +current_plan_phase: phase_3 gates: spec-approval: status: approved @@ -27,7 +27,7 @@ gates: verify-approval: status: pending iteration: 1 -build_complete: true +build_complete: false history: [] started_at: '2026-07-03T14:44:55.927Z' -updated_at: '2026-07-03T17:14:01.037Z' +updated_at: '2026-07-03T17:17:21.962Z' From daf703f29c6d5c4e834cef1568314566cb2d0a1b Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Fri, 3 Jul 2026 10:20:59 -0700 Subject: [PATCH 22/29] [Spec 1134][Phase: docs-sync] docs: Document afx whoami in CLI reference and afx skill Adds the whoami section (syntax, precedence, output examples, --json, exit codes) to agent-farm.md and the afx SKILL.md in BOTH trees, with identical section content. Drive-by: three stale 'state.db' wordings in send.ts user-facing/doc text now say global.db (post-#1118); historical 'state.db is retired' migration comments left as-is. --- .claude/skills/afx/SKILL.md | 13 ++++ codev-skeleton/.claude/skills/afx/SKILL.md | 13 ++++ .../resources/commands/agent-farm.md | 59 +++++++++++++++++++ codev/resources/commands/agent-farm.md | 59 +++++++++++++++++++ .../codev/src/agent-farm/commands/send.ts | 6 +- 5 files changed, 147 insertions(+), 3 deletions(-) diff --git a/.claude/skills/afx/SKILL.md b/.claude/skills/afx/SKILL.md index 4e8fdf4ca..2b365b5ea 100644 --- a/.claude/skills/afx/SKILL.md +++ b/.claude/skills/afx/SKILL.md @@ -121,6 +121,19 @@ afx status # Show all builders and workspace status No flags needed. Shows Tower status, workspace, and all active builders. +## afx whoami + +```bash +afx whoami # Report this terminal's identity (workspace, type, name) +afx whoami --json # Same, as a single JSON object +``` + +Resolves identity from Tower/global.db's perspective: builder-worktree cwd +match first, then the Tower-injected `CODEV_ARCHITECT_NAME` env var. Exits 1 +with an explanation when identity cannot be determined — it never guesses and +never defaults to `main` (issue #1094). Builders also get an `architect:` +field naming their spawning architect when recorded. Works without Tower. + ## afx tower ```bash diff --git a/codev-skeleton/.claude/skills/afx/SKILL.md b/codev-skeleton/.claude/skills/afx/SKILL.md index 2757f38ea..43f49ae4d 100644 --- a/codev-skeleton/.claude/skills/afx/SKILL.md +++ b/codev-skeleton/.claude/skills/afx/SKILL.md @@ -99,6 +99,19 @@ afx status # Show all builders and workspace status No flags needed. Shows Tower status, workspace, and all active builders. +## afx whoami + +```bash +afx whoami # Report this terminal's identity (workspace, type, name) +afx whoami --json # Same, as a single JSON object +``` + +Resolves identity from Tower/global.db's perspective: builder-worktree cwd +match first, then the Tower-injected `CODEV_ARCHITECT_NAME` env var. Exits 1 +with an explanation when identity cannot be determined — it never guesses and +never defaults to `main` (issue #1094). Builders also get an `architect:` +field naming their spawning architect when recorded. Works without Tower. + ## afx tower ```bash diff --git a/codev-skeleton/resources/commands/agent-farm.md b/codev-skeleton/resources/commands/agent-farm.md index 307ee0090..6f2ed0226 100644 --- a/codev-skeleton/resources/commands/agent-farm.md +++ b/codev-skeleton/resources/commands/agent-farm.md @@ -237,6 +237,65 @@ Status values: --- +### afx whoami + +Report this terminal's agent identity — workspace, type, and name — from +Tower/global.db's perspective. + +```bash +afx whoami +afx whoami --json +``` + +**Description:** + +Resolves who the current terminal's agent is. Identity precedence: + +1. **Builder worktree** — when CWD is inside `.builders//`, the canonical + builder id is verified against `global.db` (the same resolution `afx send` + uses). An unverifiable worktree identity is an error, never a fallthrough + to the next signal. +2. **`CODEV_ARCHITECT_NAME`** — the env var Tower injects into architect + terminals. +3. **Unknown** — exits non-zero with an explanation. There is no implicit + fallback to `main` (issue #1094: unverified identities misroute messages). + +Works without Tower running (reads `global.db` read-only). + +**Example output (architect terminal):** + +``` +workspace: codev +type: architect +name: main +``` + +**Example output (builder worktree):** + +``` +workspace: codev +type: builder +name: builder-spir-984 +architect: main +``` + +The `architect:` line is the builder's spawning architect; it is omitted when +not recorded (legacy rows). + +**JSON output:** + +```bash +afx whoami --json +# {"workspace":"codev","type":"builder","name":"builder-spir-984","architect":"main"} +``` + +On failure, `--json` prints `{"error":"..."}` to stdout (the human-readable +explanation still goes to stderr) and exits 1. + +**Exit codes:** `0` identity resolved; `1` identity unknown or unverifiable. + +--- + ### afx cleanup Clean up a builder worktree and branch. diff --git a/codev/resources/commands/agent-farm.md b/codev/resources/commands/agent-farm.md index 95c22f8e2..5b7e33a93 100644 --- a/codev/resources/commands/agent-farm.md +++ b/codev/resources/commands/agent-farm.md @@ -396,6 +396,65 @@ Builder status values: --- +### afx whoami + +Report this terminal's agent identity — workspace, type, and name — from +Tower/global.db's perspective. + +```bash +afx whoami +afx whoami --json +``` + +**Description:** + +Resolves who the current terminal's agent is. Identity precedence: + +1. **Builder worktree** — when CWD is inside `.builders//`, the canonical + builder id is verified against `global.db` (the same resolution `afx send` + uses). An unverifiable worktree identity is an error, never a fallthrough + to the next signal. +2. **`CODEV_ARCHITECT_NAME`** — the env var Tower injects into architect + terminals. +3. **Unknown** — exits non-zero with an explanation. There is no implicit + fallback to `main` (issue #1094: unverified identities misroute messages). + +Works without Tower running (reads `global.db` read-only). + +**Example output (architect terminal):** + +``` +workspace: codev +type: architect +name: main +``` + +**Example output (builder worktree):** + +``` +workspace: codev +type: builder +name: builder-spir-984 +architect: main +``` + +The `architect:` line is the builder's spawning architect; it is omitted when +not recorded (legacy rows). + +**JSON output:** + +```bash +afx whoami --json +# {"workspace":"codev","type":"builder","name":"builder-spir-984","architect":"main"} +``` + +On failure, `--json` prints `{"error":"..."}` to stdout (the human-readable +explanation still goes to stderr) and exits 1. + +**Exit codes:** `0` identity resolved; `1` identity unknown or unverifiable. + +--- + ### afx cleanup Clean up a builder worktree and branch. diff --git a/packages/codev/src/agent-farm/commands/send.ts b/packages/codev/src/agent-farm/commands/send.ts index f7940b05c..624fdb2de 100644 --- a/packages/codev/src/agent-farm/commands/send.ts +++ b/packages/codev/src/agent-farm/commands/send.ts @@ -49,7 +49,7 @@ export function detectWorkspaceRoot(): string | null { /** * Thrown when CWD is confirmed to be inside `.builders//` but the canonical - * builder identity cannot be verified against the workspace `state.db`. + * builder identity cannot be verified against `global.db`. * * We refuse to fall back to the bare worktree directory name (e.g. `bugfix-774`) * here: that non-canonical id does not match any `builders.id` (`builder-bugfix-774`), @@ -68,7 +68,7 @@ export class BuilderIdResolutionError extends Error { } /** - * Build an actionable message for a `state.db` open failure, naming the likely + * Build an actionable message for a `global.db` open failure, naming the likely * cause. A better-sqlite3 ABI mismatch (a `node` on PATH built for a different * NODE_MODULE_VERSION than codev's native module) is the real-world trigger * from issue #1094 and gets a specific reinstall hint. @@ -81,7 +81,7 @@ export function describeStateDbOpenFailure(dbPath: string, worktreeDirName: stri : 'Check the file for corruption, a permissions problem, or a stale lock.'; return ( `Cannot resolve builder identity for worktree '${worktreeDirName}': ` + - `failed to open workspace state.db at ${dbPath} (${detail}). ${hint} ` + + `failed to open global.db at ${dbPath} (${detail}). ${hint} ` + `Refusing to send with an unverified identity — it would silently misroute to 'main' (issue #1094).` ); } From cfb016a96d3a8e0710f8fc0727573361c371197b Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Fri, 3 Jul 2026 10:21:26 -0700 Subject: [PATCH 23/29] chore(porch): 1134 implement build-complete --- .../projects/1134-afx-whoami-ship-arch-init-comm/status.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml b/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml index e2ecd2b7d..56bb83b14 100644 --- a/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml +++ b/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml @@ -27,7 +27,7 @@ gates: verify-approval: status: pending iteration: 1 -build_complete: false +build_complete: true history: [] started_at: '2026-07-03T14:44:55.927Z' -updated_at: '2026-07-03T17:17:21.962Z' +updated_at: '2026-07-03T17:21:26.237Z' From 5ab9f7487adfb4425993a35e6ed57a74ad551102 Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Fri, 3 Jul 2026 10:24:47 -0700 Subject: [PATCH 24/29] =?UTF-8?q?chore(porch):=201134=20all=20plan=20phase?= =?UTF-8?q?s=20complete=20=E2=86=92=20review?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../1134-afx-whoami-ship-arch-init-comm/status.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml b/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml index 56bb83b14..c7b557e62 100644 --- a/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml +++ b/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml @@ -1,7 +1,7 @@ id: '1134' title: afx-whoami-ship-arch-init-comm protocol: spir -phase: implement +phase: review plan_phases: - id: phase_1 title: afx whoami command + tests @@ -11,8 +11,8 @@ plan_phases: status: complete - id: phase_3 title: CLI reference and afx-skill docs sync - status: in_progress -current_plan_phase: phase_3 + status: complete +current_plan_phase: null gates: spec-approval: status: approved @@ -27,7 +27,7 @@ gates: verify-approval: status: pending iteration: 1 -build_complete: true +build_complete: false history: [] started_at: '2026-07-03T14:44:55.927Z' -updated_at: '2026-07-03T17:21:26.237Z' +updated_at: '2026-07-03T17:24:46.989Z' From 82c59ed6ca320b18616459aaf0a8828433a9a367 Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Fri, 3 Jul 2026 10:27:36 -0700 Subject: [PATCH 25/29] [Spec 1134] Review: lessons learned, consultation ledger, governance-doc routing --- .../1134-afx-whoami-ship-arch-init-comm.md | 2 + codev/resources/arch.md | 6 + codev/resources/lessons-learned.md | 1 + .../1134-afx-whoami-ship-arch-init-comm.md | 221 ++++++++++++++++++ codev/state/spir-1134_thread.md | 32 +++ 5 files changed, 262 insertions(+) create mode 100644 codev/reviews/1134-afx-whoami-ship-arch-init-comm.md diff --git a/codev/plans/1134-afx-whoami-ship-arch-init-comm.md b/codev/plans/1134-afx-whoami-ship-arch-init-comm.md index c997ecc97..5762e8415 100644 --- a/codev/plans/1134-afx-whoami-ship-arch-init-comm.md +++ b/codev/plans/1134-afx-whoami-ship-arch-init-comm.md @@ -420,6 +420,8 @@ rebuttal: `1134-plan-iter1-rebuttals.md`. | Date | Change | Reason | Author | |------|--------|--------|--------| | 2026-07-03 | Initial plan | — | builder spir-1134 | +| 2026-07-03 | Iteration-1 CMAP revisions (read-only invariant, copySkills tests, resolveIdentity test approach) | Codex REQUEST_CHANGES | builder spir-1134 | +| 2026-07-03 | Phases 1–3 completed, each with unanimous 3-way CMAP approval | — | builder spir-1134 | ## Notes diff --git a/codev/resources/arch.md b/codev/resources/arch.md index b53ed1afb..797aa9f24 100644 --- a/codev/resources/arch.md +++ b/codev/resources/arch.md @@ -1714,6 +1714,12 @@ Messages sent via `afx send` are not injected immediately — they pass through `afx send` resolves addresses via Tower API with tail-matching: `"0109"` matches `"builder-spir-0109"`. Supports `--all` for broadcast, `--file` for file attachments (48KB max), and `--raw` to skip structured formatting. +### 8. Identity Resolution (`afx whoami`) (Spec 1134) + +**Location**: `commands/whoami.ts` (composes `detectCurrentBuilderId`/`detectWorkspaceRoot` from `commands/send.ts` and `lookupBuilderSpawningArchitect` from `state.ts`) + +`afx whoami` reports the current terminal's agent identity (workspace, type, name) from Tower/global.db's perspective. Identity precedence is fixed: **builder-worktree cwd match** (canonical id verified against global.db — same resolution `afx send` uses, including the #1094 rule that an unverifiable worktree identity throws rather than falling through) → **`CODEV_ARCHITECT_NAME`** (the Tower-injected architect env var, read directly — NOT via `currentArchitectName()`, whose `main` default is deliberately not used here) → **unknown** (exit 1, no implicit `main`). Strictly read-only against global.db: `lookupBuilderSpawningArchitect(builderId, workspacePath?, db?)` accepts an optional connection so whoami passes its own readonly handle instead of the read-write `getDb()` singleton. Workspace display name comes from `known_workspaces` with directory-basename fallback (informational field only — fail-loud applies to type/name). `--json` emits `{workspace, type, name, architect?}`; failures emit `{"error": ...}` on stdout plus a human explanation on stderr. Works without Tower running. The shipped `/arch-init` skill (`.claude/skills/arch-init/`, mirrored in `codev-skeleton/`) builds on whoami for architect identity adoption + state recovery from `codev/state/.md`. + ## Installation Architecture **Entry Point**: `INSTALL.md` - Instructions for AI agents to install Codev diff --git a/codev/resources/lessons-learned.md b/codev/resources/lessons-learned.md index d6d096589..fca5ed222 100644 --- a/codev/resources/lessons-learned.md +++ b/codev/resources/lessons-learned.md @@ -258,6 +258,7 @@ Generalizable wisdom extracted from review documents, ordered by impact. Updated - [From 456] Vitest 4 constructor mocks require class syntax — `vi.fn(() => ({...}))` throws "is not a constructor". Use `vi.mock('module', () => ({ ClassName: class MockClass { ... } }))` with `vi.hoisted()` for shared mock functions. - [From 936] `vi.mock('@cluesmith/codev-*')` still requires the mocked workspace package to *resolve* — Vitest resolves the module ID before substituting the factory. Packages whose `exports` point at an unbuilt `dist/` (e.g. `@cluesmith/codev-types`, `@cluesmith/codev-core/*`) fail with "Failed to resolve entry for package" until built. In a fresh worktree, `pnpm --filter build` the workspace deps before `vitest run`, even for deps you intend to mock. - [From 456] Avoid duplicate React effects that fire on the same dependency change. Two `useEffect` hooks both depending on `isActive` will both fire when `isActive` transitions, causing double-fetches. Merge into a single effect. +- [From 1134] Declarative agent artifacts (skills, prompts) ARE testable — pin the shipped text, not the runtime behavior. Assert byte-equality across the two trees (drift guard), required content (the load-bearing instructions: commands invoked, validation rules, guardrails), and forbidden content (the patterns being replaced, e.g. `ps -p`/`$PPID`; workspace-specific wording). Phrase spec acceptance criteria for skills as assertions about the shipped text so they're mechanically checkable. - [From 0058] Debouncing is essential for search inputs to prevent excessive DOM updates. A global Escape key handler adds resilience by ensuring modals/overlays can always be dismissed. - [From 0076] Test bugfixes with the actual user workflow before marking complete. "It compiled" is not "it works." - [From 0076] When bugfixes involve process management, document the full process chain. Incorrect assumptions about which layer owns which process lead to wrong fixes. diff --git a/codev/reviews/1134-afx-whoami-ship-arch-init-comm.md b/codev/reviews/1134-afx-whoami-ship-arch-init-comm.md new file mode 100644 index 000000000..4db92accf --- /dev/null +++ b/codev/reviews/1134-afx-whoami-ship-arch-init-comm.md @@ -0,0 +1,221 @@ +# Review: afx whoami + ship /arch-init command in codev (multi-architect identity) + +## Summary + +Implemented issue #1134 in three phases, all unanimously CMAP-approved: + +1. **`afx whoami`** (`packages/codev/src/agent-farm/commands/whoami.ts` + + `cli.ts` registration): reports the current terminal's agent identity + (workspace, type, name) with the spec's fixed precedence — builder-worktree + cwd match (via the existing `detectCurrentBuilderId()`) → + `CODEV_ARCHITECT_NAME` env → fail loud with exit 1. No implicit `main` + (#1094). Strictly read-only against global.db: + `lookupBuilderSpawningArchitect()` gained an optional `db?` parameter so + whoami passes its own readonly connection. `--json` support, spawning + architect enrichment for builders, `known_workspaces` display name with + basename fallback, best-effort architect-row cross-check (stderr warning + only). 20 new unit tests + 1 new lookup test. +2. **`/arch-init` skill** shipped byte-identically in + `codev-skeleton/.claude/skills/arch-init/` and `.claude/skills/arch-init/`: + workspace-agnostic generalization of the personal `/architect` command — + identity via validated explicit `[name]` arg or `afx whoami`, STOP-and-ask + when neither resolves, state recovery from `codev/state/.md` + (excluding builder `*_thread.md` files), the four architect guardrails. + Skill-text assertion tests + the first `copySkills()` regression tests. +3. **Docs sync**: `afx whoami` documented in + `codev/resources/commands/agent-farm.md`, its skeleton mirror, and both + afx `SKILL.md` copies (section content verified identical cross-tree), + plus the allowed drive-by fixing three stale "state.db" wordings in + `send.ts` to "global.db". + +## Spec Compliance + +All nine MUST criteria met: + +- [x] 1. Architect terminal → workspace/type/name from `CODEV_ARCHITECT_NAME` + (verified by unit tests + live smoke). +- [x] 2. Builder worktree → canonical id + `architect:` when recorded (live + smoke from this worktree printed `builder-spir-1134` / `architect: main`). +- [x] 3. Unrecognized context → exit 1, helpful message, never `main` (live + smoke from `/tmp`). +- [x] 4. Unverifiable builder worktree → exit 1 with the + `BuilderIdResolutionError` message; env not consulted (unit tests). +- [x] 5. `--json` schema on success; `{ "error": ... }` + exit 1 on failure. +- [x] 6. Skill text: validated `[name]` override → `afx whoami` → ask the + human; no `ps`/`$PPID` (asserted by tests). +- [x] 7. Skill text: reads `codev/state/.md` per the minimum contract; + missing-file flow excludes `*_thread.md` (asserted by tests). +- [x] 8. Ships in `codev-skeleton/.claude/skills/arch-init/`, mirrored + identically in `.claude/skills/arch-init/`; `copySkills()` install path + regression-tested against the real skeleton. +- [x] 9. No Tower required (whoami reads global.db read-only + env). + +Non-functional: read-only db access (enforced by design — `getDb()` never +invoked by whoami), shared builder-identity logic with `afx send` (one +implementation), no Shannon-specific wording in the skill (asserted). + +All 12 spec test scenarios are covered: 1–8, 10, 11 by +`spec-1134-whoami.test.ts` (automated), 9 by the `copySkills()` tests in +`scaffold.test.ts`, 12 by `spec-1134-arch-init-skill.test.ts`. + +## Deviations from Plan + +- **None of substance.** The plan was followed phase-by-phase. One wording + count correction: the plan said "two 'state.db' strings" in send.ts; three + were in scope once found in-file (the `describeStateDbOpenFailure` doc + comment, its message string, and the `BuilderIdResolutionError` doc + comment). Historical "state.db is retired" migration comments were left + untouched as planned. + +## Lessons Learned + +### What Went Well + +- **Reusing existing primitives paid off exactly as designed**: whoami is a + thin composition of `detectCurrentBuilderId`, `detectWorkspaceRoot`, + `lookupBuilderSpawningArchitect`, and one readonly connection — no new + resolution logic, so the #1094 fail-loud semantics came for free and the + test fixtures (`bugfix-774` pattern: mock `getGlobalDbPath`, seed + `GLOBAL_SCHEMA`, `process.chdir`) transplanted directly. +- **CMAP caught a real spec violation at plan time**: the draft plan accepted + read-write `getDb()` for the spawning-architect lookup, contradicting the + spec's read-only requirement. Fixing it at plan time (optional `db?` param) + cost one paragraph; at PR time it would have been a code churn iteration. +- **Skill-text assertions made a "soft" artifact hard-testable**: byte + equality across trees + required/forbidden content checks turned the skill + into a regression-protected artifact. + +### Challenges Encountered + +- **Concurrent global reinstall broke the codex consult lane mid-phase-1**: + the machine-global `@cluesmith/codev` install was being rebuilt while my + CMAP ran (vendored codex binary ENOENT, then SIGKILL, then a 122-byte + symlink mid-download). Resolved by polling for binary stability before + retrying — the retry approved cleanly. Environmental, not code. +- **Fresh worktree needed `pnpm install` + full `pnpm build` before tests**: + vitest imports `@cluesmith/codev-core` subpath exports from `dist/`, so + unbuilt workspace deps fail module resolution even for mocked packages + (consistent with the existing [From 936] lesson). + +### What Would Be Done Differently + +- Verify the toolchain (deps installed, workspace built) immediately after + spawn instead of at first test run — it cost one wasted test invocation. + +### Methodology Improvements + +- None proposed for SPIR itself; porch's strict-mode loop (including the + rebuttal mechanism) worked smoothly across 5 CMAP rounds. + +## Technical Debt + +- `whoami` imports identity helpers from `commands/send.ts` (a + command→command dependency). Deliberate: they're already exported and + tested there; extraction to a shared module is deferred until a third + consumer appears (noted in whoami.ts's header comment). +- The `codev whoami` top-level alias was declined as out of scope (agents use + `afx`); can be a follow-up if humans ask. + +## Consultation Feedback + +### Specify Phase (Round 1) — Gemini APPROVE, Codex REQUEST_CHANGES, Claude APPROVE + +#### Gemini +- **Suggestion**: warn on stderr when the env-resolved architect has no + `architect` table row. — **Addressed**: adopted as a non-gating SHOULD. +- **Suggestion**: `--json` failures should emit JSON on stdout AND a human + message on stderr. — **Addressed**: adopted. +- **Suggestion**: add a `codev whoami` alias. — **Rebutted**: out of scope; + the issue asks for `afx whoami` and agents are the primary consumers. + +#### Codex +- **Concern**: architect state-file contract underdefined (`codev/state/` + holds builder threads today). — **Addressed**: added a minimum contract + + `*_thread.md` disambiguation; narrowed acceptance to shipped-text + assertions. +- **Concern**: explicit `[name]` unvalidated → path traversal. — + **Addressed**: `validateArchitectName` rule required before any path use. +- **Concern**: "workspace root is cwd" wrong for subdirectories. — + **Addressed**: spec now names `detectWorkspaceRoot()`. +- **Concern**: `/arch-init` testing underspecified. — **Addressed**: new test + scenarios 10–12 (skill text, scaffold, subdirectory cwd). + +#### Claude +- **Concern (minor)**: same workspace-root gap as Codex. — **Addressed** (see + above). +- **Note**: stale "state.db" wording in `describeStateDbOpenFailure`. — + **Addressed**: allowed as a Phase 3 drive-by, executed there. + +### Plan Phase (Round 1) — Gemini APPROVE, Codex REQUEST_CHANGES, Claude APPROVE + +#### Gemini +- **Suggestion**: update any tests pinning old "state.db" text in the same + commit. — **Addressed**: covered in Phase 3 risk; grep found no pinning + tests. + +#### Codex +- **Concern**: read-write `getDb()` path violated the spec's read-only + requirement. — **Addressed**: optional `db?` param on + `lookupBuilderSpawningArchitect`; whoami uses one readonly connection. +- **Concern**: scenario 9 coverage overstated (no `copySkills()` tests + exist). — **Addressed**: first `copySkills()` regression tests added as a + Phase 2 deliverable. +- **Concern**: `resolveIdentity` purity vs `process.cwd()`-reading helpers + under-specified. — **Addressed**: pinned to `resolveIdentity(env)` + + `process.chdir()` test pattern; helpers not parameterized. + +#### Claude +- **Note**: same purity observation; drive-by scope (2 of 5 "state.db" + occurrences are stale wording). — **Addressed**: scope honored (3 stale + wordings found in-file; historical comments kept). + +### Implement Phase — phase_1 (Round 1) + +- **Gemini**: APPROVE (HIGH) — no concerns. +- **Codex**: APPROVE (HIGH) — no concerns. (First two attempts failed on the + environment: the global codev install was mid-reinstall and the vendored + codex binary was ENOENT/churning. Retried after the install settled.) +- **Claude**: APPROVE (HIGH) — no concerns. + +### Implement Phase — phase_2 (Round 1) + +- **Gemini**: APPROVE (HIGH); **Codex**: APPROVE (MEDIUM); **Claude**: + APPROVE (HIGH) — no concerns raised. + +### Implement Phase — phase_3 (Round 1) + +- **Gemini**: APPROVE (HIGH); **Codex**: APPROVE (HIGH); **Claude**: APPROVE + (HIGH) — no concerns raised. + +## Architecture Updates + +Routed to the **COLD** tier (`codev/resources/arch.md`): new Core Components +subsection "8. Identity Resolution (`afx whoami`) (Spec 1134)" documenting the +identity precedence, the read-only invariant (optional `db?` handle on +`lookupBuilderSpawningArchitect`), output contract, and the `/arch-init` +skill relationship. This is subsystem reference detail, not a cross-cutting +invariant a builder must know before any task, so the **HOT** tier +(`arch-critical.md`) is unchanged — the hot file is at its cap and nothing +here displaces an existing entry. + +## Lessons Learned Updates + +Routed to the **COLD** tier (`codev/resources/lessons-learned.md`, Testing +section): `[From 1134]` — declarative agent artifacts (skills, prompts) are +testable by pinning the shipped text (cross-tree byte equality, required +content, forbidden content) and spec acceptance criteria for skills should be +phrased as text assertions. Spec-narrow testing recipe → cold by the routing +rule; **HOT** tier (`lessons-critical.md`) unchanged. + +## Flaky Tests + +No flaky tests encountered. + +## Follow-up Items + +- Optional `codev whoami` alias if humans (vs agents) want it on the codev + CLI surface. +- The user's personal `~/.claude/commands/architect.md` can now be retired in + favor of the shipped `/arch-init` (their call; out of scope here). +- Consider extracting the send.ts identity helpers to a shared module if a + third consumer appears. diff --git a/codev/state/spir-1134_thread.md b/codev/state/spir-1134_thread.md index 1eb865310..32831f813 100644 --- a/codev/state/spir-1134_thread.md +++ b/codev/state/spir-1134_thread.md @@ -53,3 +53,35 @@ parameterization). - Committed `[Spec 1134] Plan with multi-agent review`. - **Gate reached: plan-approval.** Architect notified via afx send. Waiting. + +## Plan approved → Implement + +- Waleed approved the plan as written; recorded gate, advanced to implement. +- **Phase 1 (whoami)**: implemented `commands/whoami.ts` + cli.ts registration + + optional `db?` param on `lookupBuilderSpawningArchitect`. 20 new unit tests + (scenarios 1–8, 10, 11 + JSON shapes). Manual smokes pass end-to-end: this + worktree → `builder-spir-1134 / architect: main`; /tmp → exit 1 fail-loud. + CMAP: 3× APPROVE (all HIGH). + - Environment hiccup during phase-1 CMAP: the GLOBAL codev install was being + reinstalled concurrently (~10:00), codex vendor binary ENOENT/churning. + Waited for it to settle (binary became a symlink to standalone + @openai/codex), then codex APPROVE. Worktree needed `pnpm install + --frozen-lockfile` + `pnpm build` before tests could run (fresh worktree). +- **Phase 2 (arch-init skill)**: SKILL.md byte-identical in codev-skeleton/ and + .claude/skills/ + drift-guard test + first-ever copySkills() regression tests + (4). Skill confirmed loading in Claude Code. CMAP: 3× APPROVE. +- **Phase 3 (docs sync)**: whoami section added to agent-farm.md + afx SKILL.md + in BOTH trees (verified identical section content cross-tree); state.db → + global.db wording drive-by in send.ts (3 stale spots; historical migration + comments kept). Full suite 3470 green. CMAP: 3× APPROVE (all HIGH). + +## Review phase + +- All 3 phases unanimously approved (9 APPROVE verdicts total across + implement; 0 REQUEST_CHANGES after the spec/plan iterations). +- Review doc written with full consultation-feedback ledger. +- Governance docs routed: arch.md COLD gets "Identity Resolution (afx whoami)" + Core Components subsection; lessons-learned.md COLD gets the + "skill text is a testable artifact" lesson. Both HOT files unchanged + (nothing cross-cutting enough to displace capped entries). +- PR next, then porch done → pr gate. From 5b1800546e2a9eb519d6302ae71ac12d04d82b00 Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Fri, 3 Jul 2026 10:28:17 -0700 Subject: [PATCH 26/29] chore(porch): 1134 review build-complete --- .../projects/1134-afx-whoami-ship-arch-init-comm/status.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml b/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml index c7b557e62..514b91a41 100644 --- a/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml +++ b/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml @@ -27,7 +27,7 @@ gates: verify-approval: status: pending iteration: 1 -build_complete: false +build_complete: true history: [] started_at: '2026-07-03T14:44:55.927Z' -updated_at: '2026-07-03T17:24:46.989Z' +updated_at: '2026-07-03T17:28:17.714Z' From fae6d18047d2e5118aecf07afcea132225827320 Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Fri, 3 Jul 2026 10:31:34 -0700 Subject: [PATCH 27/29] chore(porch): 1134 pr gate-requested --- .../1134-afx-whoami-ship-arch-init-comm/status.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml b/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml index 514b91a41..98ab100b7 100644 --- a/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml +++ b/codev/projects/1134-afx-whoami-ship-arch-init-comm/status.yaml @@ -24,10 +24,12 @@ gates: approved_at: '2026-07-03T16:50:06.085Z' pr: status: pending + requested_at: '2026-07-03T17:31:34.065Z' verify-approval: status: pending iteration: 1 -build_complete: true +build_complete: false history: [] started_at: '2026-07-03T14:44:55.927Z' -updated_at: '2026-07-03T17:28:17.714Z' +updated_at: '2026-07-03T17:31:34.065Z' +pr_ready_for_human: true From 51edd3eec66dc120aa8bd6e379ee253b8262648b Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Fri, 3 Jul 2026 10:31:57 -0700 Subject: [PATCH 28/29] [Spec 1134] Update builder thread: pr gate reached --- codev/state/spir-1134_thread.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/codev/state/spir-1134_thread.md b/codev/state/spir-1134_thread.md index 32831f813..0bab89078 100644 --- a/codev/state/spir-1134_thread.md +++ b/codev/state/spir-1134_thread.md @@ -85,3 +85,10 @@ "skill text is a testable artifact" lesson. Both HOT files unchanged (nothing cross-cutting enough to displace capped entries). - PR next, then porch done → pr gate. + +## PR gate + +- PR #1136 created: https://github.com/cluesmith/codev/pull/1136 +- PR-level CMAP: Gemini APPROVE (HIGH), Codex APPROVE (MEDIUM), Claude APPROVE + (HIGH) — no key issues anywhere in the project after spec/plan iterations. +- **Gate reached: pr.** Architect notified. Waiting for human approval to merge. From f2e6c21fc36783c6bf9fdf79e3c8f4024031acbf Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Fri, 3 Jul 2026 21:24:49 -0700 Subject: [PATCH 29/29] [Spec 1134] Thread: hold at pr gate pending amrmelsayed review --- codev/state/spir-1134_thread.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/codev/state/spir-1134_thread.md b/codev/state/spir-1134_thread.md index 0bab89078..8d4f1be5a 100644 --- a/codev/state/spir-1134_thread.md +++ b/codev/state/spir-1134_thread.md @@ -92,3 +92,6 @@ - PR-level CMAP: Gemini APPROVE (HIGH), Codex APPROVE (MEDIUM), Claude APPROVE (HIGH) — no key issues anywhere in the project after spec/plan iterations. - **Gate reached: pr.** Architect notified. Waiting for human approval to merge. +- 2026-07-04: Architect integration review posted on PR #1136 (APPROVE, no key + issues), but HOLD at pr gate: Waleed assigned final human review to + amrmelsayed. No merge until that review lands + explicit go-ahead.