diff --git a/codev-skeleton/templates/arch.starter.md b/codev-skeleton/templates/arch.starter.md new file mode 100644 index 000000000..680910cbe --- /dev/null +++ b/codev-skeleton/templates/arch.starter.md @@ -0,0 +1,9 @@ +# Architecture + + + +This document evolves as the project grows. Update it during the review phase of any work that introduces or changes architectural patterns. + +_No architecture documented yet._ diff --git a/codev-skeleton/templates/lessons-learned.starter.md b/codev-skeleton/templates/lessons-learned.starter.md new file mode 100644 index 000000000..2e09adc8d --- /dev/null +++ b/codev-skeleton/templates/lessons-learned.starter.md @@ -0,0 +1,9 @@ +# Lessons Learned + + + +Durable engineering wisdom captured across the project's work. Update it during the review phase of any work that surfaces a generally-applicable pattern, gotcha, or constraint. + +_No lessons captured yet._ diff --git a/codev/plans/1012-scaffold-codev-init-bootstraps.md b/codev/plans/1012-scaffold-codev-init-bootstraps.md new file mode 100644 index 000000000..c1f639bd1 --- /dev/null +++ b/codev/plans/1012-scaffold-codev-init-bootstraps.md @@ -0,0 +1,105 @@ +# PIR Plan: `codev init` bootstraps `codev/resources/` cold-tier files (arch.md + lessons-learned.md) + +> **Rebased on main 2026-06-13.** Spec 987 (two-tier governance docs) landed since this plan was first drafted and changes the picture materially. This revision re-scopes the work to ride the 987 rails. See "What Spec 987 already did" below. + +## Understanding + +The issue: fresh `codev init` projects have no `codev/resources/arch.md` or `lessons-learned.md`, so review prompts that read them error out. + +### What Spec 987 already did (post-rebase reality) + +Spec 987 introduced a **hot/cold two-tier** governance-doc model and, as part of it, wired resource materialization into init/adopt/update — but **only for the HOT tier**: + +- `copyHotTierDefaults()` (`scaffold.ts:161`) copies skeleton `templates/arch-critical.md` + `templates/lessons-critical.md` into `codev/resources/`, with `skipExisting` for adopt/update. It is called at all three sites: + - `init.ts:117` (no skip — fresh project) + - `adopt.ts:159` (`skipExisting: true`) + - `update.ts:263` (`skipExisting: true`, with a `dryRun` branch + `result.newFiles` reporting) +- `update` already **backfills** missing hot files for existing adopters (`update.ts:258-268`). The "should update touch resources?" question we discussed at the gate is therefore already settled in the codebase: backfill-missing-only via `skipExisting` is the shipped house style. +- The COLD files `resources/arch.md` and `resources/lessons-learned.md` are **already registered as protected user data** (`templates.ts:83-84`), so update's clean step will never overwrite them — they're just never *created*. + +### What's still broken (the residual #1012 gap) + +The **COLD** files are still not materialized by any command. The review prompts reference them directly: + +- `spir/prompts/review.md:156` — "Read `arch-critical.md` (hot) and **skim `arch.md`** (cold)." +- `spir/prompts/review.md:163` — skim `lessons-learned.md` (cold). +- `pir/prompts/review.md:88,99-100` — routes changes into / `git add`s `arch.md` and `lessons-learned.md`. + +Beyond the prompts, the cold files are the **archive that the hot-tier maps point into**: each hot template carries a "Map of arch.md (consult when…)" section that directs readers into `arch.md`. Materializing the cold tier is the coherent completion of 987's model, not just an error-avoidance patch. + +So the fix is: **materialize a minimal placeholder for each cold file, wired into the same three commands that 987 already uses to materialize the hot tier.** + +## Proposed Change + +### Content decision (gate-approved): one-line placeholders, skeleton templates untouched + +The materialized cold files are **minimal placeholders**, using the issue's suggested text verbatim: + +`codev/resources/arch.md`: +```markdown +# Architecture + +This document evolves as the project grows. Update it during the review phase of any work that introduces or changes architectural patterns. + +_No architecture documented yet._ +``` + +`codev/resources/lessons-learned.md`: +```markdown +# Lessons Learned + +Durable engineering wisdom captured across the project's work. Update it during the review phase of any work that surfaces a generally-applicable pattern, gotcha, or constraint. + +_No lessons captured yet._ +``` + +**Consequence — the skeleton templates are NOT edited.** A placeholder has no skeleton file worth copying, so we do not copy `templates/arch.md`/`lessons-learned.md` and therefore do not need to trim their self-referential "Note on propagation" / MAINTAIN footer. The rich skeleton templates stay exactly as-is, preserving the manual-`cp` escape hatch they document. This is simpler and lower-risk than the copy-and-trim approach considered earlier. + +**Why a small inline write is correct here, not a "forked mechanism":** the hot tier *copies* because its skeleton files (`arch-critical.md`/`lessons-critical.md`) are genuine, curated starters meant to land verbatim. The cold tier *writes a placeholder* because the desired content is an intentionally-trivial stub with no skeleton equivalent. Each tier uses the minimal mechanism its content nature calls for; there is no second copy of the *same* logic. + +### Implementation + +1. **`scaffold.ts`** — add a `createColdTierDefaults(targetDir, options)` function next to `copyHotTierDefaults` (Spec 987's materialization neighborhood): + - A `COLD_TIER_STARTERS` map of `{ 'arch.md': , 'lessons-learned.md': }` (two short const strings). + - Ensure `codev/resources/` exists; for each entry, **skip if the file already exists**, else `writeFileSync` the placeholder. Return `{ created, skipped }` matching the existing result-shape convention. + - No `skeletonDir` parameter (nothing is copied). + +2. **Wire `createColdTierDefaults` into the three commands**, immediately after each existing `copyHotTierDefaults` call, with identical logging / `fileCount` / `result.newFiles` handling: + - `init.ts:~117` — `createColdTierDefaults(targetDir)` (no skip; fresh project). + - `adopt.ts:~159` — `createColdTierDefaults(targetDir, { skipExisting: true })`. + - `update.ts:~263` — `createColdTierDefaults(targetDir, { skipExisting: true })`, inside the same `dryRun` if/else, pushing created files to `result.newFiles` / logging `+ (new)`. Extend the dry-run message to mention `{arch,lessons}.md`. + + `update` backfilling missing cold files is consistent with the shipped 987 behavior (it already backfills missing hot files there via `skipExisting`); the cold files are already protected user-data (`templates.ts:83-84`), so a customized cold file is never overwritten. + +## Files to Change + +- `packages/codev/src/lib/scaffold.ts` — add `COLD_TIER_STARTERS` + `createColdTierDefaults` (~20 LOC). **Skeleton templates untouched.** +- `packages/codev/src/commands/init.ts` — import + call `createColdTierDefaults` after `copyHotTierDefaults` (~line 117). +- `packages/codev/src/commands/adopt.ts` — same (~line 159, `skipExisting`). +- `packages/codev/src/commands/update.ts` — same, inside the hot-tier `dryRun` block (~line 263); extend dry-run log line. +- `packages/codev/src/__tests__/hot-tier-materialization.test.ts` (or a new parallel `cold-tier-materialization.test.ts`) — unit tests: `createColdTierDefaults` creates both placeholder files (and the dir); `skipExisting` preserves a curated cold file; an update-integration test that `update` backfills the cold files into a project missing them. +- `packages/codev/src/__tests__/init.test.ts:74` — replace the stale comment ("resources/ is NOT created in minimal structure") with positive assertions that all four resource files exist after init. +- `packages/codev/src/__tests__/adopt.test.ts` — assert cold files appear after adopt. + +Estimated net diff: ~20 LOC source + ~70 LOC tests. No skeleton or framework-template changes. + +## Risks & Alternatives Considered + +- **Content-source deviation from the issue**: none — this uses the issue's suggested placeholder text verbatim. +- **Risk: stale negative assertions break.** `init.test.ts:74` is only a comment (no assertion), so no breakage; I update it to a positive assertion. Full suite run will confirm nothing else asserts absence. +- **`copyResourceTemplates` remains dead code** (987's own comment flags it as such). Out of scope here; flagged for the architect to retire separately if desired. +- **Alternative: copy + trim the rich skeleton templates.** Rejected in favor of placeholders — copying drags the rich framework template (and its now-false propagation note) into every project, and the issue explicitly preferred minimal placeholder content. +- **Alternative: do nothing in `update`** (init/adopt only). Rejected — update is the only command that reaches pre-987/pre-fix projects, and 987 already backfills the hot tier there; leaving the cold tier out would be asymmetric and re-open the gap for existing projects. +- **Alternative: generalize `copyHotTierDefaults` into a shared copy helper.** Not applicable now — the cold tier writes placeholders rather than copying, so there is no shared copy body to extract. `copyHotTierDefaults` is left completely untouched (zero risk to the load-bearing 987 hot path). + +## Test Plan + +- **Unit**: `createColdTierDefaults` writes both placeholder files (creating `resources/`); `skipExisting` preserves a curated `arch.md` while creating the missing sibling. +- **Unit (init)**: `init --yes` yields all four `codev/resources/*.md` files. +- **Unit (adopt)**: `adopt --yes` on a plain repo yields the cold files. +- **Integration (update)**: update on a project missing the cold files creates both and reports them in `result.newFiles`; a customized `arch.md` survives byte-identical while `lessons-learned.md` is created; `--dry-run` writes nothing. (Mirror the existing hot-tier update integration test.) +- **Build + full suite**: `pnpm --filter @cluesmith/codev build && pnpm --filter @cluesmith/codev test` from the worktree. +- **Manual (dev-approval reviewer)**: + 1. Build, then run the built CLI `init` into a temp dir; confirm output lists `+ codev/resources/arch.md` and `+ codev/resources/lessons-learned.md` alongside the hot files. + 2. `cat` both cold files — present and readable (the original failure mode is gone). + 3. In a codev project missing the cold files (pre-fix simulation), run `codev update`; confirm both are backfilled and a pre-existing customized `arch.md` is untouched. diff --git a/codev/projects/1012-scaffold-codev-init-bootstraps/status.yaml b/codev/projects/1012-scaffold-codev-init-bootstraps/status.yaml new file mode 100644 index 000000000..1d6137dc4 --- /dev/null +++ b/codev/projects/1012-scaffold-codev-init-bootstraps/status.yaml @@ -0,0 +1,29 @@ +id: '1012' +title: scaffold-codev-init-bootstraps +protocol: pir +phase: review +plan_phases: [] +current_plan_phase: null +gates: + plan-approval: + status: approved + requested_at: '2026-06-12T19:28:14.611Z' + approved_at: '2026-06-13T06:18:30.528Z' + dev-approval: + status: approved + requested_at: '2026-06-13T06:25:00.899Z' + approved_at: '2026-06-14T00:18:04.391Z' + pr: + status: pending + requested_at: '2026-06-14T00:30:03.080Z' +iteration: 1 +build_complete: false +history: [] +started_at: '2026-06-12T19:24:46.703Z' +updated_at: '2026-06-14T00:30:03.080Z' +pr_history: + - phase: review + pr_number: 1046 + branch: builder/pir-1012 + created_at: '2026-06-14T00:21:22.368Z' +pr_ready_for_human: true diff --git a/codev/resources/arch.md b/codev/resources/arch.md index b6e545104..e84f7b292 100644 --- a/codev/resources/arch.md +++ b/codev/resources/arch.md @@ -1925,7 +1925,7 @@ Spec 987 split the two governance docs into a **hot/cold** two-tier model so dur - **porch builders** — `buildHotTierContext()` in `packages/codev/src/commands/porch/prompts.ts` resolves the hot files via the runtime four-tier resolver (`resolveCodevFile`) and prepends them to *every* phase prompt. - **interactive sessions** — a generated managed block (`packages/codev/src/lib/managed-block.ts`, delimited by ``) is written into `CLAUDE.md`/`AGENTS.md` at `codev init`/`update` time (non-clobbering; preserves user content). -Hot files are materialized into projects by `copyHotTierDefaults` (wired into init/adopt/update) and resolve from the skeleton at tier-4 until a project curates its own. Producers **route** new facts/lessons by tier at review time (see the review prompts); MAINTAIN + the `update-arch-docs` skill police the hot caps, displacement (demote to cold when full), and cold-doc map accuracy. The cap is load-bearing: it is what keeps the hot tier cheap enough to inject everywhere. +Hot files are materialized into projects by `copyHotTierDefaults` (wired into init/adopt/update) and resolve from the skeleton at tier-4 until a project curates its own. The cold files are likewise bootstrapped on init/adopt/update by `copyColdTierDefaults`, which copies minimal placeholder starters from the skeleton's `templates/{arch,lessons-learned}.starter.md` into `codev/resources/{arch,lessons-learned}.md` (issue #1012) — distinct from the rich `templates/{arch,lessons-learned}.md` reference templates, which are a manual-`cp` opt-in and are never auto-copied. Both materializers are skip-existing, so a project's curated copy is never overwritten; the cold files are registered as protected user data in `templates.ts`. Producers **route** new facts/lessons by tier at review time (see the review prompts); MAINTAIN + the `update-arch-docs` skill police the hot caps, displacement (demote to cold when full), and cold-doc map accuracy. The cap is load-bearing: it is what keeps the hot tier cheap enough to inject everywhere. ## Troubleshooting diff --git a/codev/resources/lessons-learned.md b/codev/resources/lessons-learned.md index 82075f2f1..42d76ebd5 100644 --- a/codev/resources/lessons-learned.md +++ b/codev/resources/lessons-learned.md @@ -337,6 +337,8 @@ Generalizable wisdom extracted from review documents, ordered by impact. Updated - [From 0376] The research agent pattern (spawning a subagent to read all review files in parallel and return structured data) should be documented as a standard approach for future analyses. - [From #909] Cross-file content references in framework files are brittle. Deduplicating shared content across `CLAUDE.md` and a role file via "see X for the table" pointers is a novel pattern with no precedent in this repo (every existing CLAUDE.md mention in framework files is for diffing or scaffolding, not content lookup). The pointer is redundant when the referenced file is auto-loaded and misleading if it isn't. Keep each file self-contained for its audience: `CLAUDE.md` for everyone-loaded content (vocabulary + policy), role files for role-specific content (recipes, workflows). - [From #909] Codev's skeleton has a two-layer design that's intentional: the **internal automation layer** (`packages/codev/scripts/forge//` concept commands, dispatched via `packages/codev/src/lib/forge.ts`) is forge-agnostic; the **user-facing layer** (skeleton docs, AI prompts, protocol prompts) hardcodes `gh` directly throughout. The forge concept set is read-mostly (`issue-view`, `pr-list`, etc.) — no concepts for label management, jq-piping, or interactive ops. When adding new skeleton content, match the established `gh`-direct pattern. Localized forge-CLI awareness in one section creates inconsistency vs. the rest of the skeleton. +- [From #1012] Materialized project starter files belong in `codev-skeleton/templates/` and are copied by the scaffold, never hardcoded as TS string constants — keep the copy-from-skeleton convention uniform across every materialized file. When the desired starter differs from an existing rich template (minimal placeholder vs. the full `arch.md` reference template), add a dedicated `*.starter.md` source rather than inlining content or gutting the reference template. +- [From #1012] A placeholder filled by the agent-driven review path needs an explicit "replace me" marker (mirror the hot-tier `` convention), not just an italic `_None yet._` line — otherwise the builder may append below it, leaving a self-contradictory file. The review prompts and `update-arch-docs` skill never mention the placeholder, so the file itself must signal replacement. - [From 778] In a self-hosted Codev repo the four-tier resolver means `codev/` instance copies *shadow* `codev-skeleton/`, so the two trees (and the `codev/` copies themselves) drift independently. A terminology/backend change (Gemini-CLI → `agy`) cost 3 review iterations because each round surfaced another stale copy (skeleton → `DEPENDENCIES.md` → `resources/commands/consult.md` → `codev.md` + `arch.md`). When changing any shared doc, grep BOTH trees in one pass and run `diff codev/ codev-skeleton/` for every shared file — empty diff is the consistency proof. Distinguish in-scope current docs from historical artifacts (`specs/`, `plans/`, dated analyses) which must keep their original wording. ## 3-Way Reviews diff --git a/codev/reviews/1012-scaffold-codev-init-bootstraps.md b/codev/reviews/1012-scaffold-codev-init-bootstraps.md new file mode 100644 index 000000000..8300f1660 --- /dev/null +++ b/codev/reviews/1012-scaffold-codev-init-bootstraps.md @@ -0,0 +1,60 @@ +# PIR Review: Bootstrap cold-tier `codev/resources/` files on init/adopt/update + +Fixes #1012 + +## Summary + +Fresh `codev init` projects had no `codev/resources/arch.md` or `lessons-learned.md`, so the first PIR/SPIR/ASPIR/MAINTAIN review phase failed when it read those files. Spec 987 had already wired up materialization of the *hot*-tier governance files (`arch-critical.md`, `lessons-critical.md`) but left the *cold* tier uncreated. This change adds `copyColdTierDefaults`, which materializes `arch.md` and `lessons-learned.md` from minimal skeleton placeholder starters (`templates/{arch,lessons-learned}.starter.md`) on init/adopt/update — mirroring the hot-tier pattern, skip-existing so curated copies are never overwritten, and `update` backfills the cold files for projects created before this fix. + +## Files Changed + +- `codev-skeleton/templates/arch.starter.md` (+9 / -0) — new minimal cold starter with an explicit `STARTER:` replace-me marker +- `codev-skeleton/templates/lessons-learned.starter.md` (+9 / -0) — same, for lessons +- `packages/codev/src/lib/scaffold.ts` (+56 / -0) — `COLD_TIER_FILES` mapping + `copyColdTierDefaults` +- `packages/codev/src/commands/init.ts` (+9 / -0) — wire cold materialization beside hot +- `packages/codev/src/commands/adopt.ts` (+8 / -0) — same (skip-existing) +- `packages/codev/src/commands/update.ts` (+13 / -0) — same, with dry-run + `newFiles` reporting (backfill for existing adopters) +- `packages/codev/src/__tests__/cold-tier-materialization.test.ts` (+129 / -0) — new test file (7 tests) +- `packages/codev/src/__tests__/init.test.ts` (+7 / -1) — positive assertions for all four resource files +- `packages/codev/src/__tests__/adopt.test.ts` (+4 / -0) — assert cold files created on adopt +- `codev/resources/arch.md`, `codev/resources/lessons-learned.md` — governance-doc updates (see below) + +## Commits + +- `f0e75182` [PIR #1012] Bootstrap cold-tier resources (arch.md, lessons-learned.md) on init/adopt/update +- `04e4deb1` [PIR #1012] Tests: cold-tier materialization + init/adopt assertions +- `2d133f13` [PIR #1012] Source cold-tier content from skeleton *.starter.md instead of inline constants +- `04478a98` [PIR #1012] Tests: cold-tier copies from skeleton starters +- `f5f4b118` [PIR #1012] Mark cold-tier starters with explicit STARTER replace-me comment +- (plus `[PIR #1012] Thread:` / `Plan:` housekeeping commits) + +## Test Results + +- `pnpm build`: ✓ pass (build core first from worktree root — `codev-core` isn't pre-built in a fresh worktree) +- `pnpm test`: ✓ pass — full suite 163 files / 3310 tests, 48 pre-existing skips; 7 new cold-tier tests +- Manual verification (and at the dev-approval gate): built CLI `init` into a temp dir creates `codev/resources/{arch,lessons-learned}.md` with the placeholder + `STARTER:` marker; `.starter.md` source files do not leak into the project; `update` backfills missing cold files without clobbering a customized one; `--dry-run` writes nothing. + +## Architecture Updates + +**COLD** (`codev/resources/arch.md`) — updated. The "Governance Docs (Hot/Cold Tiers)" section previously stated only the hot files were materialized; extended it to record that the cold files are now bootstrapped by `copyColdTierDefaults` from `*.starter.md` placeholders (distinct from the rich manual-`cp` reference templates), both materializers skip-existing. No **HOT** (`arch-critical.md`) change: the existing hot fact about two-tier routing is unaffected — this adds materialization detail, which is reference-tier, not a capped always-on fact. + +## Lessons Learned Updates + +**COLD** (`codev/resources/lessons-learned.md`) — added two entries under Documentation: (1) materialized starter files belong in `codev-skeleton/templates/` and are copied, never hardcoded as TS constants — add a dedicated `*.starter.md` when the desired starter differs from an existing rich template; (2) a placeholder filled by the agent-driven review path needs an explicit "replace me" marker (mirroring the hot-tier `STARTER:` convention), since the review prompts and `update-arch-docs` skill never mention the placeholder. No **HOT** (`lessons-critical.md`) change: these are scaffold-convention reference tips, not behavior-changing rules warranting a capped slot. + +## Things to Look At During PR Review + +- **`COLD_TIER_FILES` is a `{ src, dest }` mapping** (`arch.starter.md` → `arch.md`), unlike the hot tier's same-name copy. This is deliberate: the plain `templates/arch.md` is the rich reference template (with a "this file is not copied into projects" note) and must NOT be the copied starter, so the minimal starter lives in a separate `*.starter.md` source. +- **`copyHotTierDefaults` is untouched** — the cold function is a sibling, so the load-bearing Spec 987 hot path carries zero risk from this change. +- **`update` backfill**: cold files are already in `USER_DATA_PATTERNS` (`templates.ts`), so update's clean step never overwrote them; this change makes update *create* the missing ones (consistent with how 987 backfills the hot files). +- **3-way consult outcome**: Gemini APPROVE (HIGH), Codex APPROVE (HIGH), Claude COMMENT (HIGH). Claude's COMMENT flagged apparent removals of unrelated governance entries (#859 from arch.md, #913 from lessons-learned.md). **Verified false positive**: `git diff ...HEAD` showed the governance edits are purely additive (no removals); the branch was simply behind main, and those entries were added to main after the branch point. Resolved by rebasing onto current main — the branch is now 0 commits behind, `#859` is present, and the net governance diff remains additive (arch.md +1 sentence, lessons-learned.md +2 entries). + +## How to Test Locally + +For reviewers pulling the branch: + +- **View diff**: VSCode sidebar → right-click builder pir-1012 → **Review Diff** +- **What to verify**: + 1. `pnpm build` (from worktree root), then `node packages/codev/dist/cli.js init /tmp/p --yes` → inspect `/tmp/p/codev/resources/` (all four `.md` present; `arch.md`/`lessons-learned.md` carry the `STARTER:` marker; no `.starter.md` files). + 2. In a codev project missing the cold files, `codev update` backfills both; a customized `arch.md` survives byte-identical. + 3. `codev update --dry-run` writes nothing. diff --git a/codev/state/pir-1012_thread.md b/codev/state/pir-1012_thread.md new file mode 100644 index 000000000..9874c05f8 --- /dev/null +++ b/codev/state/pir-1012_thread.md @@ -0,0 +1,69 @@ +# Builder thread: pir-1012 + +Issue #1012 — scaffold: `codev init` bootstraps `codev/resources/` with arch.md + lessons-learned.md starters. Protocol: PIR (strict, porch-driven). + +## Plan phase + +- Investigated scaffold flow. Root cause confirmed: `createUserDirs` (scaffold.ts:23) only creates specs/plans/reviews; nothing in init/adopt creates `codev/resources/`. +- Found `copyResourceTemplates` (scaffold.ts:113) is **dead code** — exported, tested, but no command calls it. It would copy the skeleton's rich template stubs plus framework docs (cheatsheet.md, lifecycle.md → #1011 territory). Plan proposes inline minimal starters instead, leaving the dead function untouched (flagged as an open question at the plan gate). +- `codev update` already never touches resources — acceptance criterion satisfied by status quo; adding a guard test. +- `codev adopt` aborts when `codev/` exists, so the "don't clobber" criterion is structurally unreachable; new function uses per-file skip-if-exists anyway. +- Plan written to `codev/plans/1012-scaffold-codev-init-bootstraps.md`. Awaiting plan-approval gate. +- Gate discussion: architect asked about VSCode extension behavior on uninitialized workspaces (answer: extension never invokes init/adopt; degrades gracefully, user bootstraps manually) and whether adopt should backfill pre-fix projects. Adopt can't (aborts when `codev/` exists), so architect approved a scope amendment: `codev update` backfills missing-only resources files (create-if-missing, never modifies existing). Plan revised accordingly. + +## Rebase on main (2026-06-13) + +- Rebased onto origin/main (was 63 behind). **Spec 987 had landed** — two-tier governance docs (HOT arch-critical.md/lessons-critical.md + COLD arch.md/lessons-learned.md). This re-scopes the issue significantly. +- 987 already wired `copyHotTierDefaults` into init/adopt/update (incl. update-backfill via skipExisting + dryRun handling) — but ONLY for the HOT tier. The COLD files (arch.md, lessons-learned.md), which #1012 is actually about, are STILL not materialized. +- COLD files are already in USER_DATA_PATTERNS (templates.ts:83-84) so update never overwrites them — they're just never created. +- The update-backfill "scope amendment" I discussed with the architect is now MOOT: 987 already ships update backfilling resources. Extending to cold is consistent house style, not a deviation. +- Revised plan: add `copyColdTierDefaults` (sibling of `copyHotTierDefaults`) + wire into the same 3 sites. Main open question for the gate: copy skeleton cold templates (consistent with 987) vs the issue's original "minimal inline content" preference. Plan recommends skeleton templates; awaiting architect sign-off. +- Dead `copyResourceTemplates` still dead; 987's own comment flags it. Out of scope. + +## Gate iter: "why copy at all / use the proper code path" + +- Architect challenged the inline-content idea: don't fork the mechanism, use the existing materialization code. +- Verified: (a) NO production code reads templates/arch.md or templates/lessons-learned.md (pure copy sources — safe to edit); (b) no resolver/lazy path serves cold files — review prompts read literal `codev/resources/arch.md`, which is project-owned user-data (templates.ts:83-84) with no fallback by design. So the file MUST physically exist; materialization is unavoidable. +- Also found: the skeleton `templates/arch.md` has a "Note on propagation" section asserting it is NOT copied into projects — copying it verbatim would be self-contradicting. That's the real reason inline content was tempting. +- Final design (plan revised): generalize `copyHotTierDefaults` body into a private `copyResourceDefaults(files,...)`, redefine hot in terms of it (no behavior change), add `copyColdTierDefaults` for ['arch.md','lessons-learned.md']; wire at the same 3 sites. Fix the SOURCE: trim the propagation note from skeleton arch.md + false footer from lessons-learned.md. One mechanism, one source of truth, no inline strings. +- Open sub-decision for gate: how lean to trim the templates (light trim keeping structural stubs [recommended] vs ultra-minimal one-liner). + +## Second rebase on main (2026-06-13) + +- Rebased again (was 38 behind). Verified NONE of the 38 new commits touched any plan-target file (scaffold.ts, init/adopt/update.ts, templates.ts, skeleton templates, review prompts). All plan assumptions re-confirmed: hot-tier mechanism intact, cold files still user-data with no fallback, propagation note still in skeleton arch.md:116, no production reader of cold template sources, review prompts still reference cold files. Plan unchanged. Force-pushed. Still at plan-approval gate. + +## Content decision settled: one-line placeholders + +- Architect chose the one-line placeholder option (issue's suggested text verbatim) over copying/trimming the rich skeleton templates. +- This SIMPLIFIES the plan: skeleton templates are NOT edited at all (rich templates + their manual-cp note stay as-is). No `copyHotTierDefaults` refactor needed. +- Final implementation: `createColdTierDefaults(targetDir, opts)` in scaffold.ts writes two small placeholder consts (arch.md, lessons-learned.md) with skip-if-exists; wired at the same 3 sites next to copyHotTierDefaults; update backfills (consistent with 987). copyHotTierDefaults left completely untouched. +- Plan finalized. No open decisions remain. Awaiting plan-approval. + +## Implement phase + +- plan-approval approved; advanced to implement. +- Added `createColdTierDefaults` + `COLD_TIER_STARTERS` to scaffold.ts; wired into init/adopt/update next to copyHotTierDefaults. copyHotTierDefaults + skeleton templates untouched. +- Tests: new cold-tier-materialization.test.ts (7 tests: create/placeholder-marker/skip-existing/user-data-protection/update-backfill/customized-survives/dry-run); init.test.ts comment flipped to positive assertions for all 4 resource files; adopt.test.ts asserts cold files. +- Build: needed full `pnpm build` from worktree root first (codev-core wasn't built — pre-existing infra, not my change). Then green. +- Full suite: 163 files / 3310 tests pass, 48 pre-existing skips. Awaiting dev-approval gate. + +## dev-approval iter: convention fix (skeleton-sourced content) + +- Architect flagged: inline `COLD_TIER_STARTERS` constants in scaffold.ts break the copy-from-skeleton convention. Chose (via question) "minimal starter files in skeleton". +- Refactored: added `codev-skeleton/templates/arch.starter.md` + `lessons-learned.starter.md` (the 4-line placeholders); replaced inline `createColdTierDefaults` with copy-based `copyColdTierDefaults(targetDir, skeletonDir, opts)` that maps starter→dest (arch.starter.md→arch.md). Rich `templates/{arch,lessons-learned}.md` left untouched (manual-cp reference). copyHotTierDefaults untouched. +- COLD_TIER_FILES is now `[{src,dest}]`. Callers pass skeletonDir/templatesDir and use `.copied`. +- Tests updated to mock a skeleton dir; full suite green (163/3310). Smoke: init copies starters → arch.md/lessons-learned.md, no .starter.md leaks into project. + +## dev-approval iter: explicit replace-me marker + +- Architect asked whether a builder/architect would know to remove the placeholder. Found the gap: review prompts + update-arch-docs skill never mention the placeholder, so the agent path could append below it. Hot-tier starters use an explicit `STARTER: replace…` comment; cold lacked one. +- Added a `` HTML comment to both `*.starter.md` files, mirroring the hot-tier convention. Satisfies the issue AC "clearly marked as a placeholder ready to be replaced." Full suite green; smoke confirms the comment lands in the materialized arch.md. + +## Review phase + +- dev-approval approved; advanced to review. +- Wrote codev/reviews/1012-scaffold-codev-init-bootstraps.md. Routed cold-tier governance updates: arch.md "Governance Docs" section extended to cover copyColdTierDefaults; lessons-learned.md +2 Documentation entries (skeleton-sourced starters; explicit replace-me marker). No hot-tier changes (reference-tier detail, not capped facts). +- PR #1046 opened (Fixes #1012), recorded with porch. porch done → structural checks pass. 3-way consult (gemini/codex/claude, type=impl) running in background — single advisory pass (max_iterations:1). Awaiting verdicts → pr gate. +- Verdicts: gemini APPROVE/HIGH, codex APPROVE/HIGH, claude COMMENT/HIGH. Claude flagged "removed unrelated governance entries (#859 arch, #913 lessons)". VERIFIED FALSE POSITIVE via `git diff merge-base...HEAD` (purely additive) — branch was 71 commits behind main; #859 was added to main after my branch point (#913 never existed in either tree). Root cause: stale diff base. +- Resolved by rebasing onto current main (was 71 behind → 0; clean, no conflicts; code targets untouched by the 71 commits — only governance docs overlapped, combined cleanly). #859 now present; my edits intact; net governance diff still additive. Rebuilt + full suite green (164/3313). Documented in review "Things to Look At"; refreshed PR body; force-pushed. +- porch next → **pr gate pending** (gemini/codex APPROVE, claude COMMENT). Notified architect leading with the false-positive disposition. Waiting at pr gate for human merge approval. diff --git a/packages/codev/src/__tests__/adopt.test.ts b/packages/codev/src/__tests__/adopt.test.ts index 8bf894b1f..d5423aeca 100644 --- a/packages/codev/src/__tests__/adopt.test.ts +++ b/packages/codev/src/__tests__/adopt.test.ts @@ -83,6 +83,10 @@ describe('adopt command', () => { // Verify user data directories expect(fs.existsSync(path.join(projectDir, 'codev', 'specs'))).toBe(true); expect(fs.existsSync(path.join(projectDir, 'codev', 'plans'))).toBe(true); + + // Issue #1012: cold-tier governance files are bootstrapped. + expect(fs.existsSync(path.join(projectDir, 'codev', 'resources', 'arch.md'))).toBe(true); + expect(fs.existsSync(path.join(projectDir, 'codev', 'resources', 'lessons-learned.md'))).toBe(true); }); it('should throw error if codev directory already exists', async () => { diff --git a/packages/codev/src/__tests__/cold-tier-materialization.test.ts b/packages/codev/src/__tests__/cold-tier-materialization.test.ts new file mode 100644 index 000000000..b12faf5f7 --- /dev/null +++ b/packages/codev/src/__tests__/cold-tier-materialization.test.ts @@ -0,0 +1,129 @@ +/** + * Issue #1012 — cold-tier governance files (arch.md, lessons-learned.md) materialized + * into projects on init/adopt/update with minimal placeholder content. Companion to the + * Spec 987 hot-tier materialization; mirrors its test shape. + */ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import * as os from 'node:os'; +import { copyColdTierDefaults, COLD_TIER_FILES } from '../lib/scaffold.js'; +import { isUserDataPath } from '../lib/templates.js'; + +const COLD_DEST_FILES = COLD_TIER_FILES.map(f => f.dest); + +describe('issue #1012 — copyColdTierDefaults', () => { + let tmp: string; + let skeleton: string; + + beforeEach(() => { + tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'cold-mat-')); + skeleton = path.join(tmp, 'skeleton'); + fs.mkdirSync(path.join(skeleton, 'templates'), { recursive: true }); + fs.writeFileSync( + path.join(skeleton, 'templates', 'arch.starter.md'), + '# Architecture\n\n_No architecture documented yet._\n' + ); + fs.writeFileSync( + path.join(skeleton, 'templates', 'lessons-learned.starter.md'), + '# Lessons Learned\n\n_No lessons captured yet._\n' + ); + }); + afterEach(() => fs.rmSync(tmp, { recursive: true, force: true })); + + it('copies both cold files into codev/resources/ under their dest names, creating the dir', () => { + const target = path.join(tmp, 'proj'); + const result = copyColdTierDefaults(target, skeleton); + expect(result.copied.sort()).toEqual([...COLD_DEST_FILES].sort()); + for (const f of COLD_DEST_FILES) { + expect(fs.existsSync(path.join(target, 'codev', 'resources', f))).toBe(true); + } + }); + + it('writes the placeholder marker so the file is clearly a stub to replace', () => { + const target = path.join(tmp, 'proj'); + copyColdTierDefaults(target, skeleton); + const res = path.join(target, 'codev', 'resources'); + expect(fs.readFileSync(path.join(res, 'arch.md'), 'utf-8')).toContain('_No architecture documented yet._'); + expect(fs.readFileSync(path.join(res, 'lessons-learned.md'), 'utf-8')).toContain('_No lessons captured yet._'); + }); + + it('skip-existing preserves a curated copy', () => { + const target = path.join(tmp, 'proj'); + const res = path.join(target, 'codev', 'resources'); + fs.mkdirSync(res, { recursive: true }); + fs.writeFileSync(path.join(res, 'arch.md'), 'MY CURATED CONTENT'); + + const result = copyColdTierDefaults(target, skeleton, { skipExisting: true }); + expect(result.skipped).toContain('arch.md'); + expect(result.copied).toContain('lessons-learned.md'); + // Curated content untouched. + expect(fs.readFileSync(path.join(res, 'arch.md'), 'utf-8')).toBe('MY CURATED CONTENT'); + }); +}); + +describe('issue #1012 — cold files are protected user data', () => { + it('treats the cold files as user data (never overwritten by update)', () => { + expect(isUserDataPath('resources/arch.md')).toBe(true); + expect(isUserDataPath('resources/lessons-learned.md')).toBe(true); + }); +}); + +// Integration: `codev update` on a project lacking the cold files must backfill them, +// while never clobbering a customized one. Mirrors the hot-tier update integration test. +describe('issue #1012 — codev update backfills cold files', () => { + let originalCwd: string; + let projectDir: string; + + beforeEach(() => { + originalCwd = process.cwd(); + projectDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cold-update-')); + fs.mkdirSync(path.join(projectDir, 'codev'), { recursive: true }); + fs.writeFileSync(path.join(projectDir, 'CLAUDE.md'), '# Codev\n\nKEEP_USER_CONTENT'); + fs.writeFileSync(path.join(projectDir, 'AGENTS.md'), '# Codev\n\nKEEP_USER_CONTENT'); + vi.spyOn(console, 'log').mockImplementation(() => {}); + vi.spyOn(console, 'error').mockImplementation(() => {}); + }); + + afterEach(() => { + process.chdir(originalCwd); + vi.restoreAllMocks(); + fs.rmSync(projectDir, { recursive: true, force: true }); + }); + + it('creates missing cold files and reports them in newFiles', async () => { + process.chdir(projectDir); + const { update } = await import('../commands/update.js'); + const result = await update(); + + for (const f of COLD_DEST_FILES) { + expect(fs.existsSync(path.join(projectDir, 'codev', 'resources', f)), `${f} created`).toBe(true); + expect(result.newFiles).toContain(`codev/resources/${f}`); + } + }); + + it('leaves a customized cold file byte-identical while backfilling the missing sibling', async () => { + const res = path.join(projectDir, 'codev', 'resources'); + fs.mkdirSync(res, { recursive: true }); + fs.writeFileSync(path.join(res, 'arch.md'), 'MY CURATED ARCH'); + + process.chdir(projectDir); + const { update } = await import('../commands/update.js'); + const result = await update(); + + expect(fs.readFileSync(path.join(res, 'arch.md'), 'utf-8')).toBe('MY CURATED ARCH'); + expect(result.newFiles).not.toContain('codev/resources/arch.md'); + expect(fs.existsSync(path.join(res, 'lessons-learned.md'))).toBe(true); + expect(result.newFiles).toContain('codev/resources/lessons-learned.md'); + }); + + it('--dry-run writes nothing', async () => { + process.chdir(projectDir); + const { update } = await import('../commands/update.js'); + await update({ dryRun: true }); + + for (const f of COLD_DEST_FILES) { + expect(fs.existsSync(path.join(projectDir, 'codev', 'resources', f)), `${f} not created in dry-run`).toBe(false); + } + }); +}); diff --git a/packages/codev/src/__tests__/init.test.ts b/packages/codev/src/__tests__/init.test.ts index 269982683..f571564a3 100644 --- a/packages/codev/src/__tests__/init.test.ts +++ b/packages/codev/src/__tests__/init.test.ts @@ -71,7 +71,12 @@ describe('init command', () => { expect(fs.existsSync(path.join(projectDir, 'codev', 'reviews'))).toBe(true); // Spec 0126: projectlist.md is no longer created expect(fs.existsSync(path.join(projectDir, 'codev', 'projectlist.md'))).toBe(false); - // Note: resources/ is NOT created in minimal structure (created by user if needed) + // Spec 987 (hot tier) + issue #1012 (cold tier): codev/resources/ is bootstrapped + // with all four governance files. + expect(fs.existsSync(path.join(projectDir, 'codev', 'resources', 'arch-critical.md'))).toBe(true); + expect(fs.existsSync(path.join(projectDir, 'codev', 'resources', 'lessons-critical.md'))).toBe(true); + expect(fs.existsSync(path.join(projectDir, 'codev', 'resources', 'arch.md'))).toBe(true); + expect(fs.existsSync(path.join(projectDir, 'codev', 'resources', 'lessons-learned.md'))).toBe(true); } finally { process.chdir(originalCwd); } diff --git a/packages/codev/src/commands/adopt.ts b/packages/codev/src/commands/adopt.ts index bd8f0908c..59738c499 100644 --- a/packages/codev/src/commands/adopt.ts +++ b/packages/codev/src/commands/adopt.ts @@ -17,6 +17,7 @@ import { copySkills, copyRootFiles, copyHotTierDefaults, + copyColdTierDefaults, } from '../lib/scaffold.js'; import { updateGitignore } from '../lib/gitignore.js'; @@ -161,6 +162,13 @@ export async function adopt(options: AdoptOptions = {}): Promise { fileCount++; } + // Materialize the cold-tier governance files (arch.md, lessons-learned.md) from the + // skeleton's *.starter.md placeholders (issue #1012), skip-existing so a curated copy is preserved. + for (const file of copyColdTierDefaults(targetDir, skeletonDir, { skipExisting: true }).copied) { + console.log(chalk.green(' +'), `codev/resources/${file}`); + fileCount++; + } + // Create .codev/config.json if it doesn't exist const codevConfigDir = path.join(targetDir, '.codev'); const codevConfigPath = path.join(codevConfigDir, 'config.json'); diff --git a/packages/codev/src/commands/init.ts b/packages/codev/src/commands/init.ts index c11900888..c475175b2 100644 --- a/packages/codev/src/commands/init.ts +++ b/packages/codev/src/commands/init.ts @@ -16,6 +16,7 @@ import { copySkills, copyRootFiles, copyHotTierDefaults, + copyColdTierDefaults, } from '../lib/scaffold.js'; import { syncHotContextBlock } from '../lib/managed-block.js'; import { createGitignore } from '../lib/gitignore.js'; @@ -119,6 +120,14 @@ export async function init(projectName?: string, options: InitOptions = {}): Pro fileCount++; } + // Materialize the cold-tier governance files (arch.md, lessons-learned.md) from the + // skeleton's *.starter.md placeholders (issue #1012) so the first review-phase read + // succeeds against a real file. + for (const file of copyColdTierDefaults(targetDir, skeletonDir).copied) { + console.log(chalk.green(' +'), `codev/resources/${file}`); + fileCount++; + } + // Inject the always-on hot-tier managed block into CLAUDE.md / AGENTS.md (Spec 987). for (const file of syncHotContextBlock(targetDir)) { console.log(chalk.green(' ~'), `${file} (hot-tier context)`); diff --git a/packages/codev/src/commands/update.ts b/packages/codev/src/commands/update.ts index e62025766..eae3ffe52 100644 --- a/packages/codev/src/commands/update.ts +++ b/packages/codev/src/commands/update.ts @@ -26,6 +26,7 @@ import { copySkills, copyRootFiles, copyHotTierDefaults, + copyColdTierDefaults, } from '../lib/scaffold.js'; import { syncHotContextBlock } from '../lib/managed-block.js'; import { @@ -267,6 +268,18 @@ export async function update(options: UpdateOptions = {}): Promise } } + // Backfill the cold-tier governance files for existing adopters from the skeleton's + // *.starter.md placeholders (issue #1012), skip-existing so a curated file is never overwritten. + if (dryRun) { + log(chalk.dim(' + (cold-tier) would create missing codev/resources/{arch,lessons-learned}.md')); + } else { + for (const file of copyColdTierDefaults(targetDir, templatesDir, { skipExisting: true }).copied) { + const rel = `codev/resources/${file}`; + result.newFiles.push(rel); + log(chalk.green(' + (new)'), rel); + } + } + // Refresh the always-on hot-tier managed block in CLAUDE.md / AGENTS.md (Spec 987). // Non-clobbering: only the marked block is replaced; user content is preserved. // Logged as a side effect (not added to result.updated, which tracks template copies). diff --git a/packages/codev/src/lib/scaffold.ts b/packages/codev/src/lib/scaffold.ts index 64245f2c1..9584ab299 100644 --- a/packages/codev/src/lib/scaffold.ts +++ b/packages/codev/src/lib/scaffold.ts @@ -189,6 +189,62 @@ export function copyHotTierDefaults( return { copied, skipped }; } +/** + * Cold-tier governance files: the skeleton starter to copy from → the project-local + * filename it materializes as (issue #1012). + * + * The starter sources are minimal placeholders (`*.starter.md`) kept separate from the rich + * `templates/{arch,lessons-learned}.md` reference templates — those carry a "this file is not + * copied into projects" note and are the manual-`cp` opt-in, so they must not be the copied + * starter. Each project instead gets a trivial placeholder it grows over time, enough for the + * review-phase read to succeed against a real, locally-owned file. + */ +export const COLD_TIER_FILES = [ + { src: 'arch.starter.md', dest: 'arch.md' }, + { src: 'lessons-learned.starter.md', dest: 'lessons-learned.md' }, +] as const; + +/** + * Copy the cold-tier governance files (arch.md, lessons-learned.md) from the skeleton's + * `*.starter.md` placeholders into the project's codev/resources/. + * + * Companion to `copyHotTierDefaults`: Spec 987 materializes the hot tier on + * init/adopt/update but left the cold tier — which the review prompts read and the hot-tier + * maps point into — uncreated. `skipExisting` so a curated file is never overwritten (the + * cold files are already registered as protected user data in templates.ts). Results are + * keyed by the destination filename. + */ +export function copyColdTierDefaults( + targetDir: string, + skeletonDir: string, + options: CopyResourceTemplatesOptions = {} +): CopyResourceTemplatesResult { + const { skipExisting = false } = options; + const resourcesDir = path.join(targetDir, 'codev', 'resources'); + const copied: string[] = []; + const skipped: string[] = []; + + if (!fs.existsSync(resourcesDir)) { + fs.mkdirSync(resourcesDir, { recursive: true }); + } + + for (const { src, dest } of COLD_TIER_FILES) { + const destPath = path.join(resourcesDir, dest); + const srcPath = path.join(skeletonDir, 'templates', src); + + if (skipExisting && fs.existsSync(destPath)) { + skipped.push(dest); + continue; + } + if (fs.existsSync(srcPath)) { + fs.copyFileSync(srcPath, destPath); + copied.push(dest); + } + } + + return { copied, skipped }; +} + interface CopyRootFilesOptions { handleConflicts?: boolean; }