Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .claude/skills/arch-init/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ name in a multi-architect workspace).
state.
- The state file is authoritative free text. It typically opens with a
role banner and may carry resume instructions; follow whatever it says.
- Architect state files are per-person and gitignored
(`codev/state/*.md`); never commit them. Builder `*_thread.md` files
are the opposite: versioned, shipping with each builder PR.

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
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ worktrees/
.architect-role.md
.codev/config.json

# Architect state files are per-person; builder *_thread.md files ARE versioned (#1192)
codev/state/*.md
!codev/state/*_thread.md

# OS files
.DS_Store

Expand Down
3 changes: 3 additions & 0 deletions codev-skeleton/.claude/skills/arch-init/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ name in a multi-architect workspace).
state.
- The state file is authoritative free text. It typically opens with a
role banner and may carry resume instructions; follow whatever it says.
- Architect state files are per-person and gitignored
(`codev/state/*.md`); never commit them. Builder `*_thread.md` files
are the opposite: versioned, shipping with each builder PR.

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
Expand Down
183 changes: 183 additions & 0 deletions codev/plans/1192-gitignore-architect-state-file.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
# PIR Plan: Gitignore Architect State Files Across init/adopt/update/doctor

## Understanding

PR #1136 shipped `/arch-init`, which reads and offers to create architect state
files at `codev/state/<name>.md`. These files are per-person: every team member
has their own `main` architect, so a committed `main.md` collides across
members. Codev currently takes no stance on their versioning: they are neither
tracked nor gitignored (`git check-ignore codev/state/main.md` exits 1 in this
repo today).

Builder thread files (`codev/state/<id>_thread.md`) have the opposite lifecycle
by design: written in the builder worktree, committed with the PR, landing on
main at merge. Any ignore rule must preserve that split. The rule pair is:

```
codev/state/*.md
!codev/state/*_thread.md
```

Order matters: in gitignore, the last matching rule wins, so the negation must
come after the ignore pattern.

The good news structurally: all three scaffold commands already flow through a
single constant, so the rule pair lands in one place.

- **init** — `packages/codev/src/commands/init.ts:137` calls
`createGitignore`, which writes `FULL_GITIGNORE_CONTENT` (built from
`CODEV_GITIGNORE_ENTRIES`).
- **adopt** — `packages/codev/src/commands/adopt.ts:197` calls
`updateGitignore`, which either creates the file from
`CODEV_GITIGNORE_ENTRIES` or delegates to the line-level backfill.
- **update** — `packages/codev/src/commands/update.ts:295` calls
`backfillGitignore(targetDir, CODEV_GITIGNORE_ENTRIES, ...)`, appending only
the missing lines under a dated `# Codev (added by codev update ...)` header.
- **doctor** — `packages/codev/src/commands/doctor.ts` has no gitignore
awareness today; it needs a new check. The established pattern is an audit
helper in `lib/` (`auditPrGates`, `auditFrameworkRefs`) surfaced as warnings
in `doctor()`.

`backfillGitignore` (`packages/codev/src/lib/gitignore.ts:112-151`) is
append-only, line-level, and preserves the order of entries within the source
block, so the pair appends in the correct order for existing installs.

## Proposed Change

### 1. Core rule pair: `packages/codev/src/lib/gitignore.ts`

Append two lines to `CODEV_GITIGNORE_ENTRIES` (lines 18-24), keeping the
ignore rule before the negation:

```
codev/state/*.md
!codev/state/*_thread.md
```

This single edit covers init (fresh `.gitignore`), adopt (create-or-backfill),
and update (dated backfill for existing installs). No changes needed in
`init.ts`, `adopt.ts`, or `update.ts` themselves.

### 2. Doctor audit: new helper + wiring

Add `auditStateFileIgnore(workspaceRoot)` to
`packages/codev/src/lib/gitignore.ts`, returning a list of warning strings.
Checks, in order:

1. **Not a git repo** (`git rev-parse --is-inside-work-tree` fails) → return
no findings (nothing to audit).
2. **Ignore rule missing or ineffective** — probe with
`git check-ignore -q codev/state/__doctor-probe__.md` (check-ignore works on
paths that don't exist). Non-ignored → warn: architect state files are not
gitignored; run `codev update` (backfills the rule) or add
`codev/state/*.md` + `!codev/state/*_thread.md` to `.gitignore`. Probing
git behavior rather than string-matching `.gitignore` means a user's
equivalent hand-written rules pass the check.
3. **Thread files accidentally swallowed** — probe
`git check-ignore -q codev/state/__doctor-probe___thread.md`. If ignored →
warn: builder thread files must stay versioned (their ship-with-the-PR
lifecycle breaks otherwise); the `!codev/state/*_thread.md` negation is
missing or shadowed.
4. **Architect state file already tracked** — `git ls-files codev/state/*.md`
filtered to exclude `*_thread.md`. Any hit → warn per file, recommending
`git rm --cached <file>` (pre-existing installs may have committed one
before the rule existed; gitignore has no effect on already-tracked files).

Wire it into `doctor.ts` inside `checkCodevStructure`
(`packages/codev/src/commands/doctor.ts:475-492`), which already aggregates
project-structure warnings and runs git commands (`checkGitRemote`). Each
finding prints as a `⚠` line and joins the end-of-run warning summary, matching
existing behavior.

### 3. This repo's own `.gitignore`

Add the rule pair with a short comment. Verified: only `*_thread.md` files are
tracked under `codev/state/` in this repo, so no `git rm --cached` migration is
needed; the untracked `codev/state/main.md` in the main checkout becomes
ignored the moment this merges.

### 4. Documentation: `/arch-init` skill in both trees

The skill is the surface that promotes state files into a shipped concept, so
it should state the versioning stance. Add one short note to its state-file
section (step 2, "Read your state file"): architect state files are per-person
and gitignored (`codev/state/*.md`); do not commit them — unlike builder
`*_thread.md` files, which are versioned and ship with PRs.

Files (kept byte-identical to each other where they already are):
- `.claude/skills/arch-init/SKILL.md`
- `codev-skeleton/.claude/skills/arch-init/SKILL.md`

## Files to Change

- `packages/codev/src/lib/gitignore.ts:18-24` — add the two lines to
`CODEV_GITIGNORE_ENTRIES`; add `auditStateFileIgnore()` helper.
- `packages/codev/src/commands/doctor.ts:475-492` — call
`auditStateFileIgnore` from `checkCodevStructure`, append findings to its
warnings.
- `.gitignore` — add the rule pair under a short comment.
- `.claude/skills/arch-init/SKILL.md` — one-note versioning stance.
- `codev-skeleton/.claude/skills/arch-init/SKILL.md` — same note.
- `packages/codev/src/__tests__/gitignore.test.ts` — extend existing suites;
add `auditStateFileIgnore` suite (temp dir + `git init`).

## Risks & Alternatives Considered

- **Risk: backfill appends the ignore rule after a pre-existing negation.**
`backfillGitignore` is line-level; if a user's `.gitignore` already contained
`!codev/state/*_thread.md` but not `codev/state/*.md` (a state no Codev
tooling ever produces — the negation alone is a no-op), the appended ignore
rule would land after the negation and re-ignore thread files. Mitigation:
the doctor thread-file probe (check 3) catches exactly this ordering bug and
warns. Not worth complicating the append-only backfill for a self-inflicted
edge case.
- **Risk: `codev/state/*.md` is broader than architect files.** Any future
non-thread `.md` dropped in `codev/state/` gets ignored by default. That is
the intended stance per the issue (per-person by default; versioned files
must opt in via a negation, as `*_thread.md` does).
- **Alternative: string-match `.gitignore` in doctor instead of
`git check-ignore`.** Rejected — probing git's actual behavior validates
rule ordering and honors user-written equivalent rules; string matching
false-positives on both.
- **Alternative: scope the doc note into CLAUDE.md/AGENTS.md templates.**
Rejected for this PR — the discovery text there
(`codev-skeleton/templates/CLAUDE.md:142`) describes thread files only and
stays correct as-is. The `/arch-init` skill is where the architect state
file concept lives.

## Test Plan

Extend `packages/codev/src/__tests__/gitignore.test.ts`:

- **Constant**: `CODEV_GITIGNORE_ENTRIES` contains both lines, with
`codev/state/*.md` appearing before `!codev/state/*_thread.md`.
- **createGitignore (init)**: generated file contains both lines.
- **updateGitignore (adopt)**: partial-block backfill adds both lines.
- **backfillGitignore (update)**: a pre-#1192 `.gitignore` gains exactly the
two lines under the dated header; idempotent on second run.
- **End-to-end pattern validity** (new): in a temp dir with `git init` and the
generated `.gitignore`, assert `git check-ignore codev/state/main.md` exits
0 and `git check-ignore codev/state/x_thread.md` exits non-zero — proving
the pair actually produces the intended split, not just that the text is
present.
- **auditStateFileIgnore** (new suite, temp dir + `git init`):
- no rule → "not gitignored" warning
- rule pair present → no warnings
- negation shadowed (ignore rule after negation) → thread-file warning
- tracked `codev/state/main.md` (committed before the rule) → tracked-file
warning naming the file
- non-git directory → no findings, no crash

Manual verification at the dev-approval gate:

- `pnpm --filter @cluesmith/codev build && pnpm --filter @cluesmith/codev test`
from the worktree.
- In a scratch dir: `codev init` (via the built CLI) → inspect `.gitignore`;
`git check-ignore codev/state/main.md` → ignored;
`git check-ignore codev/state/x_thread.md` → not ignored.
- In a scratch repo with a pre-existing partial `.gitignore`: run the built
`codev update` → both lines appended under the dated header.
- Remove the rule → `codev doctor` shows the warning; `git add -f` a fake
`codev/state/main.md` → doctor warns it is tracked.
- In this worktree: `git check-ignore codev/state/main.md` succeeds and
`git status` still shows thread files as trackable.
30 changes: 30 additions & 0 deletions codev/projects/1192-gitignore-architect-state-file/status.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
id: '1192'
title: gitignore-architect-state-file
protocol: pir
phase: verified
plan_phases: []
current_plan_phase: null
gates:
plan-approval:
status: approved
requested_at: '2026-07-19T04:10:22.911Z'
approved_at: '2026-07-19T04:19:10.428Z'
dev-approval:
status: approved
requested_at: '2026-07-19T04:26:28.933Z'
approved_at: '2026-07-20T09:18:31.722Z'
pr:
status: approved
requested_at: '2026-07-20T09:23:28.096Z'
approved_at: '2026-07-21T07:08:05.011Z'
iteration: 1
build_complete: false
history: []
started_at: '2026-07-19T04:07:14.007Z'
updated_at: '2026-07-21T07:08:21.995Z'
pr_history:
- phase: review
pr_number: 1213
branch: builder/pir-1192
created_at: '2026-07-20T09:21:07.279Z'
pr_ready_for_human: false
2 changes: 1 addition & 1 deletion codev/resources/arch.md
Original file line number Diff line number Diff line change
Expand Up @@ -1733,7 +1733,7 @@ Messages sent via `afx send` are not injected immediately — they pass through

**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/<name>.md`.
`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/<name>.md`. Architect state files are per-person (every team member has their own `main`, so a committed one collides across the team) and gitignored via `codev/state/*.md`; builder thread files (`codev/state/<id>_thread.md`) have the opposite lifecycle and are versioned via a `!codev/state/*_thread.md` negation, since they ship with each builder's PR (Issue #1192). `codev init`/`adopt`/`update` all source this rule pair from the single `CODEV_GITIGNORE_ENTRIES` constant (`packages/codev/src/lib/gitignore.ts`); `codev doctor` audits the split via `auditStateFileIgnore()`.

## Installation Architecture

Expand Down
1 change: 1 addition & 0 deletions codev/resources/lessons-learned.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ Generalizable wisdom extracted from review documents, ordered by impact. Updated
- [From 0755] Export error messages as functions, not inline strings, when tests must assert verbatim spec text. Producer and asserter import the same function — drift between spec and code becomes a failing test rather than a silent regression. Caught a real verbatim-text drift in iter-1 of Phase 3 and made the iter-2 fix mechanical.
- [From 0755] Test the public address grammar, not the internal resolver shape. Phase 3 iter-1 tests called `resolveTarget('sibling', ...)` directly, bypassing the `parseAddress` step. That hid a real bug (`architect:<name>` was being misparsed as `project:agent`). Tests of routing logic should send the same string the CLI sends, not the internal data shape.
- [From 0755] A CI guardrail test for "this access pattern should never reappear" is cheap insurance for sweep-style refactors. The `entry.architect` (singular) grep test took 30 minutes to write and would catch any future re-introduction. Worth doing whenever a sweep removes a long-lived pattern.
- [From #1192] When auditing config-driven behavior (gitignore rules, etc.), probe the tool's actual resolved decision (`git check-ignore` on a phantom path) rather than string-matching the config file. String matching misses rule-ordering bugs (a negation shadowed by a later conflicting rule) and false-positives on a user's equivalent-but-differently-worded rule. Applies to `codev doctor`'s `auditStateFileIgnore` and generalizes to any "is this configuration actually in effect" check.

## UI/UX

Expand Down
Loading
Loading