Repository navigation
scaffold: .architect-role.md missing from CODEV_GITIGNORE_ENTRIES — every fresh codev init/adopt leaves a dirty git status after first architect spawn #880
Description
Symptom
After running `codev init ` or `codev adopt` in a project, the first invocation of `afx architect` (or any operation that triggers Tower to spawn the architect terminal) writes `.architect-role.md` to the workspace root. This file is the architect's role prompt, materialized from the resolver chain for the harness layer to load — a runtime bootstrap artifact, not source.
But the file is not in the gitignore that codev's scaffolding writes. Result:
```
$ codev init my-project && cd my-project && git init . && git add -A && git commit -m initial
$ afx architect
...architect spawns; .architect-role.md is written...
$ git status
On branch main
Untracked files:
(use "git add ..." to include in what will be committed)
.architect-role.md
```
Every user's first interaction with Codev produces an untracked file they didn't create deliberately. First-impression friction. Existing Codev users hit the same problem on every machine where they cloned a Codev-managed repo before the entry was added manually.
Root cause
`packages/codev/src/lib/scaffold.ts:12`:
```ts
export const CODEV_GITIGNORE_ENTRIES = `# Codev
.agent-farm/
.consult/
codev/.update-hashes.json
.builders/
`;
```
Four entries; `.architect-role.md` is missing.
The sibling builder runtime files (`.builder-role.md`, `.builder-prompt.txt`, `.builder-start.sh`) are covered transitively because they live inside `.builders//` and `.builders/` itself is gitignored. But `.architect-role.md` lives at workspace root with no ignored ancestor, so it needs its own explicit entry.
Why this affects every fresh project AND every existing project
- `codev init` uses `FULL_GITIGNORE_CONTENT` = `CODEV_GITIGNORE_ENTRIES` + node_modules/dist/OS-files. Missing `.architect-role.md` propagates to every new project.
- `codev adopt` appends `CODEV_GITIGNORE_ENTRIES` to existing `.gitignore` (when `.agent-farm/` isn't already present). Same omission.
- `codev update` doesn't touch `.gitignore` at all today (verified by grep — zero references in `packages/codev/src/commands/update.ts`). So existing Codev-managed projects with stale gitignore content have no path to receive newer entries.
Verification: this codev repo's own `.gitignore` does include the entry (line 10 in `/.gitignore`). It was almost certainly added manually by the original author. `CODEV_GITIGNORE_ENTRIES` doesn't put it there, and `codev update` wouldn't have added it either.
Fix — two parts (scope decision 2026-05-27)
Part 1 — Add the missing entry to the canonical list
One-line addition to `packages/codev/src/lib/scaffold.ts:12`:
```diff
export const CODEV_GITIGNORE_ENTRIES = `# Codev
.agent-farm/
.consult/
codev/.update-hashes.json
.builders/
+.architect-role.md
`;
```
This covers `codev init` (via `FULL_GITIGNORE_CONTENT`) and `codev adopt` (direct append) — fresh projects get the entry automatically.
Part 2 — Backfill from `codev update` for existing projects
Extend `codev update` so it scans the existing `.gitignore` for each entry in `CODEV_GITIGNORE_ENTRIES` and appends any missing ones. This makes the upgrade path self-healing: a user running `codev update` on a long-lived project picks up the new entries without having to know they need to.
Design rules:
- Idempotent. Re-running `codev update` after a clean state does nothing — every line check is "if not present, append."
- Append-only. Never delete or reorder existing lines. The user may have curated their `.gitignore` with custom entries; preserve all of them.
- Grouped append. Missing entries are appended together under a `# Codev (added by codev update YYYY-MM-DD)` header comment so the user can see where they came from. Avoids scattering the additions through the file.
- Dry-run support. `codev update --dry-run` (if it exists, or add it) prints what would be added without writing — lets users review before accepting.
- Don't touch a missing .gitignore. If the project has no `.gitignore`, that's a deliberate signal (not all projects use git); skip rather than create one. Creation belongs to `init` / `adopt`.
Why this is a new pattern, not an extension of existing `codev update` behavior.
`codev update` today uses two patterns for the files it manages — verified by reading `packages/codev/src/commands/update.ts` (309 lines, zero references to `gitignore`):
| Existing pattern | Used for | Mechanism |
|---|---|---|
| `{ skipExisting: true }` | `.claude/skills//` | Add new skills from package skeleton; never modify existing ones (preserves user customizations) |
| `{ handleConflicts: true }` | `CLAUDE.md`, `AGENTS.md` | Add if missing, OR write a `.codev-new` sibling when content differs from the package template — user merges manually |
Neither pattern fits `.gitignore`:
- Skills pattern doesn't work — the file already exists (from `init`/`adopt`); the problem is that it's missing one line inside it. "Skip if exists" leaves the gap intact forever.
- Conflict-file pattern doesn't fit — gitignore isn't whole-file ownership. Codev owns SOME lines (the `# Codev` block) and the user owns OTHER lines (their custom ignores). A `.gitignore.codev-new` sibling forces the user to manually merge what's trivially line-mergeable.
This issue introduces a third pattern: line-level append-only merge. Distinct from the existing two but structurally similar — scan an existing file for entries from a managed block, append the missing ones under a labeled header, never touch anything else.
Day-one scope: keep it gitignore-specific. The function is `backfillGitignore(targetDir, entries)` with gitignore-specific decisions (read the file, append at end, header comment format) baked in. Do not generalize to `backfillManagedBlock(filePath, entries, headerComment)` on day one — that's premature abstraction with no second use case. If a future Codev feature needs the same behavior for another managed-block-in-shared-file case (e.g. a managed `.editorconfig` section, a managed `package.json` `scripts` entry), the abstraction can be promoted then. Until that second case appears, the gitignore-specific shape is the right surface.
Implementation:
- New function `backfillGitignore(targetDir, entries)` in `packages/codev/src/lib/scaffold.ts` next to `createGitignore` / `updateGitignore`. Returns a result like `{ added: string[], alreadyPresent: string[] }`.
- `packages/codev/src/commands/update.ts` calls it as part of its existing `# A. One-time migration` and `# B. Framework refresh` flow, prints a summary line.
Acceptance criteria
Part 1 (fresh projects)
- `CODEV_GITIGNORE_ENTRIES` in `packages/codev/src/lib/scaffold.ts` includes `.architect-role.md`
- `codev init ` writes a `.gitignore` containing `.architect-role.md`
- `codev adopt` in a project without an existing `.gitignore` writes one containing `.architect-role.md`
- `codev adopt` in a project with an existing `.gitignore` (without `.agent-farm/`) appends the block including `.architect-role.md`
Part 2 (existing projects via codev update)
- `backfillGitignore(targetDir, entries)` helper exists in `scaffold.ts`; idempotent, append-only, returns `{ added, alreadyPresent }`
- `codev update` calls the helper for `CODEV_GITIGNORE_ENTRIES` against the project's existing `.gitignore`
- Missing entries appended together under `# Codev (added by codev update YYYY-MM-DD)` header
- Existing entries (including any custom user entries) are never deleted, reordered, or duplicated
- If the project has no `.gitignore`, `codev update` does nothing (skips silently) — creation belongs to init/adopt
- `codev update --dry-run` (if/where the flag exists) prints what would be added but doesn't write
- Update summary line: `gitignore: added entries` or `gitignore: up to date`
Verification
- Unit tests in `packages/codev/src/tests/scaffold.test.ts` cover: (a) `.architect-role.md` present in both constants, (b) `backfillGitignore` adds missing entries, (c) `backfillGitignore` is idempotent (second run = no-op), (d) `backfillGitignore` preserves custom user entries
- Integration test for `codev update`: project with stale `.gitignore` (missing `.architect-role.md`) → run `codev update` → `.gitignore` now contains the entry, other lines unchanged
- End-to-end smoke: after `codev init my-test && cd my-test && git init . && afx architect`, `git status` shows clean working tree (no untracked `.architect-role.md`)
Out of scope
- Other workspace-root runtime files — none currently exist outside `.builders/`. If future Codev features add new runtime artifacts at workspace root, they'd be addressed in their own PRs (and would benefit from this same backfill mechanism)
- `.codev/config.json` gitignore handling — intentionally not added to the codev-managed block; it's project config that some users will want to commit and some won't
- Builder runtime files (`.builder-role.md`, `.builder-prompt.txt`, `.builder-start.sh`) — already covered transitively by the `.builders/` entry; no change needed
- Removing entries from `.gitignore` when Codev no longer needs them — append-only design intentionally; pruning belongs to a separate `codev maintain`-style operation, not `update`
- Reformatting existing `.gitignore` content — never touch existing lines; only append
Discovered while
Auditing the architect role file lifecycle on 2026-05-27. Traced `.architect-role.md` to `buildArchitectArgs` in `packages/codev/src/agent-farm/servers/tower-utils.ts:184` (Tower writes it on every architect spawn). Cross-checked the codev scaffolding's gitignore generation and found the gap — neither init, adopt, nor update places `.architect-role.md` in the gitignore. Scope expanded 2026-05-27 to include the update backfill so existing projects self-heal on next `codev update`.
Activity
amrmelsayed commentedon May 27, 2026
On it! Working on a fix now.
Fix #880: scaffold gitignores .architect-role.md + add update backfill
Fix #880: replace .agent-farm/ sentinel in updateGitignore with line-…
Merge pull request #881 from cluesmith/builder/bugfix-880