@monte3l/groundwork 1.0.0-rc.3 → 1.0.0-rc.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/customize-paths.d.ts +51 -15
  2. package/dist/customize-paths.js +32 -13
  3. package/dist/fs-guard.d.ts +2 -2
  4. package/dist/harness/rules.d.ts +2 -1
  5. package/dist/harness/rules.js +3 -1
  6. package/dist/main.js +36 -0
  7. package/dist/packs.d.ts +3 -1
  8. package/dist/packs.js +11 -1
  9. package/dist/plugin.d.ts +12 -12
  10. package/dist/plugin.js +34 -26
  11. package/package.json +1 -1
  12. package/plugin/skills/customize/SKILL.md +33 -403
  13. package/plugin/skills/customize/step-0-reconcile.md +261 -0
  14. package/plugin/skills/customize/step-3-round-1.md +170 -0
  15. package/plugin/src/plugin-map.ts +131 -10
  16. package/templates/core/.claude/agents/Explore.md +1 -1
  17. package/templates/core/.claude/hooks/guard-no-commonjs.mjs +3 -2
  18. package/templates/core/.claude/hooks/inject-decision-gate.mjs +7 -7
  19. package/templates/core/.claude/skills/triaging-ci/SKILL.md +2 -2
  20. package/templates/core/.claude/skills/writing-commits/SKILL.md +4 -4
  21. package/templates/core/.prettierignore +1 -0
  22. package/templates/core/_gitignore +1 -0
  23. package/templates/core/bin/check-exports.mjs +5 -3
  24. package/templates/core/bin/lib/harness-rules.mjs +3 -1
  25. package/templates/core/tsconfig.base.json +10 -6
  26. package/templates/packs/README.md +15 -0
  27. package/templates/packs/github/files/.github/workflows/claude-pr-review.yml +1 -1
  28. package/templates/packs/github/files/.github/workflows/claude.yml +1 -1
  29. package/templates/packs/github/pack.json +1 -1
  30. package/templates/packs/harness-extras/files/.claude/hooks/subagent-statusline.mjs +29 -14
  31. package/templates/packs/harness-extras/pack.json +1 -1
  32. package/templates/packs/publishing/files/.github/release-tools/package.json +1 -1
  33. package/templates/packs/publishing/files/.github/workflows/release.yml +8 -6
  34. package/templates/packs/publishing/pack.json +7 -1
  35. package/templates/packs/quality/files/.claude/agents/type-design-analyzer.md +1 -1
  36. package/templates/packs/quality/pack.json +1 -1
@@ -53,253 +53,26 @@ may or may not touch.
53
53
 
54
54
  ## Step 0 — Reconcile (adopt mode only)
55
55
 
56
- 1. Look for `.groundwork/inventory.json`.
57
- - **Absent, and no `.groundwork/` directory either → this is a fresh
58
- bootstrap; skip straight to Step 1.** Everything below this step applies
59
- only when an inventory exists.
60
- - **Absent, but `.groundwork/` exists → a previous CLI adopt run did not
61
- complete** (the CLI deletes the old inventory before it stages anything
62
- and writes the new one last, so no inventory beside a `.groundwork/`
63
- directory means the run died part-way). **Stop. Never fall through to
64
- the fresh flow** -- that would run fresh-mode tailoring on an
65
- established project. Tell the user: "`.groundwork/` is incomplete: a
66
- previous adopt run did not finish, or `inventory.json` was removed.
67
- If this project was never adopted (it was bootstrapped fresh), delete
68
- `.groundwork/` and run `/customize` again. Otherwise re-run
69
- `npx @monte3l/groundwork@rc .` and then run `/customize` again."
70
- Change nothing.
71
- - **Present, but not valid JSON, or its `schemaVersion` is not an integer
72
- of at least 1 → stop and change nothing.** Tell the user: "`.groundwork/inventory.json`
73
- is not valid JSON or has no usable `schemaVersion`. Re-run
74
- `npx @monte3l/groundwork@rc .` and then run `/customize` again."
75
-
76
- **Check `inventory.schemaVersion` before reading anything else.** This
77
- skill understands schema versions **1 through 5** (the highest it knows is
78
- 5). If `schemaVersion` is **higher than 5**, the CLI that wrote it is newer
79
- than this plugin: **stop and change nothing**, do not interpret the
80
- inventory (a newer schema may have renamed or repurposed fields, and a
81
- confident misreading is worse than none), and tell the user to update the
82
- plugin and re-run `/customize`. Say which update applies to the copy
83
- that is running. For the plugin install, run `/plugin update`. For a
84
- project-local copy in `.claude/skills/customize/`, `/plugin update` does
85
- not touch it, and re-running the CLI alone does not either: the CLI
86
- never overwrites a copy that differs (nor removes any other entry the
87
- project owns there, nor writes through a symlinked `.claude`), it writes a
88
- fresh one to `.groundwork/customize/`, which Claude Code does not load. So delete
89
- that directory first (this discards any local edits the project made to
90
- its copy) and then re-run `npx @monte3l/groundwork@rc .`. A
91
- copy in `.groundwork/customize/` is refreshed by re-running the CLI. What
92
- each version added:
93
-
94
- | `schemaVersion` | Adds | If absent |
95
- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
96
- | 1 | the survey, `conflicts` | (the floor) |
97
- | 2 | `packs` | no packs to offer; skip the pack question |
98
- | 3 | `harnessGrade`, `harnessConformance` | skip the harness-grade starting point |
99
- | 4 | `toolchainGrade`, `toolchainConformance` | skip the toolchain-grade starting point |
100
- | 5 | `stagedBaseline` (`{ dir, suffix, files }`): the baseline additions staged at `.groundwork/baseline/`, each as `{ path, staged, sha256 }`; `stagedPacks` (per pack: `{ name, dir, suffix, manifest, files }`): every pack staged at `.groundwork/packs/<name>/`, its manifest and each file as `{ path, staged, sha256 }` | schema 1-4 only: read baseline additions from `inventory.templateRoot`; packs use the unsuffixed copy under `.groundwork/packs/` |
101
-
102
- **For a schema 5 inventory only: verify the staged baseline now, before
103
- anything is offered to the user.** A schema 1-4 inventory has no
104
- `stagedBaseline`: skip this whole block (every bullet below, including
105
- its stop list) and use the `inventory.templateRoot` fallback in Round 1.
106
- The inventory lives in the project tree and is untrusted input, so check
107
- it before trusting any entry:
108
-
109
- - `stagedBaseline.dir` must equal `.groundwork/baseline` exactly, and
110
- `stagedBaseline.suffix` must equal `.staged` exactly. Take both values
111
- from this list, not from the inventory: a `dir` of `.` with an empty
112
- `suffix` would make you "verify" live project files.
113
- - For every entry of `inventory.stagedBaseline.files`, require
114
- `staged === path + ".staged"`, and require `path` to be relative, free
115
- of any `..` segment, and not absolute. Reject the entry if `path` is
116
- `""` or `"."`. Reject it too if `path` contains a `\` or a `:` (a
117
- Windows `..\..` or `C:foo` would slip past the other checks). Reject a
118
- duplicate `path` or a duplicate `staged` among the entries. These fields
119
- use `/` separators on every platform: `conflicts[].relPath`,
120
- `packs[].fileConflicts[].relPath`, `stagedBaseline.dir`,
121
- `stagedBaseline.files[].path` and its `.staged` name, `stagedPacks[].dir`,
122
- `stagedPacks[].files[].path` and `stagedPacks[].files[].staged`. Other paths in
123
- the inventory are not normalized: `templateRoot` and `targetDir` are
124
- absolute native paths, as are the survey's tsconfig chain entries.
125
- - The `absent` conflicts in `inventory.conflicts` and the paths in
126
- `stagedBaseline.files` must name the same set of files. If they
127
- disagree (an `absent` conflict with no staged entry, or a staged entry
128
- that is not an `absent` conflict), that is a mismatch: stop, with the
129
- same message as a hash mismatch, before anything is offered or approved.
130
- - Read each staged file at `.groundwork/baseline/<staged>` **once**.
131
- It must exist, and the SHA-256 of its **raw bytes** (no end-of-line
132
- normalization, no decoding) must equal the entry's `sha256`. Compute it
133
- with, for example, this one-liner (it targets a POSIX shell or Git Bash,
134
- not `cmd.exe` or an old PowerShell), passing the file as a single quoted
135
- argument in place of `<file>`:
136
- `node -e 'process.stdout.write(require("crypto").createHash("sha256").update(require("fs").readFileSync(process.argv[1])).digest("hex"))' <file>`
137
- Keep those same bytes for the install in Round 1 (substitute tokens into
138
- them) rather than reading the file a second time.
139
- - **Staged packs, in the same block.** For every entry of
140
- `inventory.stagedPacks`:
141
- - `dir` must equal `.groundwork/packs/<name>`, built from the entry's
142
- own `name`, and `suffix` must equal `.staged`. Take both from this
143
- list, not from the inventory.
144
- - `name` must be a single path segment: not empty, not `.` or `..`, and
145
- free of `/`, `\` and `:`. Reject a duplicate pack `name`.
146
- - `manifest.path` must equal `pack.json` and `manifest.staged` must
147
- equal `pack.json.staged`.
148
- - For every entry of its `files`, require `staged === path + ".staged"`,
149
- and apply the same path checks as the staged baseline's files above
150
- (relative, no `..` segment, not absolute, not `""` or `"."`, no `\` or
151
- `:`). Reject a duplicate `path` or a duplicate `staged` among one
152
- pack's files.
153
- - The pack names in `inventory.packs` and in `inventory.stagedPacks`
154
- must be the same set, and for each pack its `fileConflicts[].relPath`
155
- set must equal its `files[].path` set.
156
- - Read the staged manifest at `<dir>/pack.json.staged` and each staged
157
- file at `<dir>/files/<staged>` once, and apply the SHA-256 check above
158
- to the raw bytes of each. Keep the bytes Step 0.1 read (hash-checked
159
- when a command could run): Round 1's install and the prototype-key
160
- check in Step 0.4(c) use them, with no second read.
161
- - **No shell tool available.** If a Bash or other command-running tool is
162
- available, compute the hash as above. If you cannot run a command, do
163
- not work around it: write no scratch script; while verifying, make no
164
- `Write` or `Edit` outside `.groundwork/` (Round 1's confirmed installs are
165
- not part of verification), dispatch no subagent to look for a shell, and
166
- search for a command tool at most once. Skip only the SHA-256
167
- comparison. Run every other check in this block with the file tools
168
- (`Read`, `Glob`, `Grep`): the files exist, `staged === path + ".staged"`,
169
- the paths are relative with no `..`, `\` or `:`, no duplicates, `dir`
170
- and `suffix` exact, the `absent` conflicts and `stagedBaseline.files`
171
- name the same set, and no extra staged file exists: list the staged
172
- files with the `Glob` pattern `.groundwork/baseline/**/*.staged` and
173
- compare with `stagedBaseline.files`. A `Glob` can skip hidden paths
174
- (most staged files are dot-paths), honour an ignore file or truncate a
175
- long list, so a result shorter than `stagedBaseline.files.length`, or
176
- that looks truncated, is undetermined: report it in the Step 0.4 summary
177
- and continue, since every listed entry's existence is already checked
178
- one by one; only an extra `*.staged` file, one whose path is not in
179
- `stagedBaseline.files`, stops the run. Run the same structural checks
180
- for `inventory.stagedPacks`: each pack's `<dir>/pack.json.staged` and
181
- every `<dir>/files/<staged>` exists, `staged === path + ".staged"` with
182
- paths that are relative and free of `..`, `\` and `:`, no duplicates,
183
- `name`, `dir`, `suffix`, `manifest.path` and `manifest.staged` exact,
184
- the pack names in `inventory.packs` and `inventory.stagedPacks` the
185
- same set, and each pack's `fileConflicts[].relPath` and `files[].path`
186
- the same set. List each pack's staged files with the `Glob` pattern
187
- `.groundwork/packs/**/*.staged` (the same hidden-path and
188
- truncation caveats apply) and compare with every listed manifest and
189
- file: an extra `*.staged` file at any depth under `.groundwork/packs/` that is neither a listed manifest nor a listed file stops the run. This is a partial no-shell
190
- substitute for what the hash proves, and it is not tamper-resistance.
191
- Then spend no further turns on verification and go on to the deep
192
- read. State this plainly at the top of your first message to the user (the Step 0.4 summary, not a
193
- separate earlier stop), and ask whether to continue or stop in that
194
- same confirmation: the SHA-256 check was skipped because no command
195
- could be run; a passing check would only have proven that the staging
196
- is complete and matches the inventory, not that the files are
197
- untampered; and which structural checks you did verify instead. Also
198
- record the skip in `.groundwork/adoption-report.md` when you write the
199
- findings back in Step 0.3. A structural failure, including an extra staged file, still stops, exactly as the stop list below says.
200
- - An empty `files` list is legitimate (nothing was missing from the
201
- project) and creates no `.groundwork/baseline/` directory; that alone is
202
- not a failure.
203
- - **For a schema 5 inventory, a `stagedBaseline` that is missing, not an
204
- object, or whose `files` is not an array also stops the run** (a
205
- `files: {}` is not an empty list); so does a `stagedPacks` that is
206
- missing or not an array, or a pack entry that is not an object or whose
207
- `files` is not an array. **Any invalid entry, missing file or hash mismatch (including a
208
- wrong `dir` or `suffix`), any duplicate `staged` or `path` or pack `name`, any
209
- `absent` conflicts and `stagedBaseline.files` that do not name the same
210
- set, any pack whose names or file paths disagree with `inventory.packs`, and
211
- (no-shell path) an extra `*.staged` file not named in
212
- `stagedBaseline.files`, a pack's `files` or a pack's manifest
213
- means stop -- all of Round 1, including conflicts and packs -- and
214
- change nothing.** This is the single stop list for the staged baseline and the staged packs.
215
- Tell the user: "The staged baseline in `.groundwork/baseline/` or a staged pack in `.groundwork/packs/` is incomplete or does not match `.groundwork/inventory.json` (<the first entry that failed and why>). Re-run `npx @monte3l/groundwork@rc .` and then run `/customize` again." **Never fall back to `inventory.templateRoot` for a schema 5 inventory, packs included:** that fallback exists for a schema 1-4 inventory only, and only for the baseline additions (a schema 1-4 inventory's packs are read from their unsuffixed copy, see Step 0.4(c) and Round 1).
216
- - What a passing check proves: the staging is complete and matches the
217
- inventory. It does **not** prove the files are untampered -- anyone who
218
- can edit the staged files can edit the inventory's hashes too.
219
-
220
- An inventory with `schemaVersion` below 5 has no staged copy; its
221
- approved additions are read from `inventory.templateRoot` (see Round 1).
222
-
223
- 2. **The deep read.** The CLI's survey is an index, not an interpretation —
224
- it flagged what it found but could not parse (`needsReading: true` on
225
- git-hook config, workflow files; anything in `survey.undetermined`) and
226
- what it could only index, not summarize (`docs`). Read all of it for
227
- real: the eslint config, the git-hook manager's actual stage commands,
228
- the CI workflow job steps, `CLAUDE.md`, `CONTRIBUTING.md`, and any
229
- docs/ADR files the survey indexed. Dispatch this as parallel read-only
230
- `Explore` agents, one per discovery area (shape/toolchain, harness, docs),
231
- so you aggregate their findings rather than reading everything yourself.
232
- The harness agent also starts from `inventory.harnessGrade` (the report's
233
- `## Harness grade` section): a deterministic, offline check of the
234
- existing `.claude/` wiring. Its **wiring findings** (a hook registration
235
- naming a missing file, a skill or agent with unreadable frontmatter, a
236
- `CLAUDE.md` path that no longer exists) are facts to verify against the
237
- real files, not verdicts to take on trust. Its **quality findings** are
238
- advisory. `inventory.harnessConformance` counts how far the harness has
239
- drifted from the baseline's — information only, since divergence from the
240
- baseline is the point of adopting. An inventory with `schemaVersion` below
241
- 3 carries neither field; skip this and continue.
242
-
243
- The toolchain agent likewise starts from `inventory.toolchainGrade` (the
244
- report's `## Toolchain grade` section): a deterministic, offline check of
245
- the tsconfig chain, ESLint and vitest config, verify-step wiring, and
246
- toolchain pins. It reads files and never runs them, and it reads
247
- `eslint.config.js`/`vitest.config.ts` by pattern rather than by evaluating
248
- them -- so a **wiring finding** (a build project that emits nowhere, a
249
- verify step naming a script or file that does not exist, a `.node-version`
250
- that contradicts `engines.node`) is a fact to verify against the real files,
251
- and a **quality finding** (a missing strict flag, an option TypeScript has
252
- deprecated, ESLint without type-aware linting, a coverage gate that is not
253
- per-file) is advisory. Absence is never a finding: a project with no vitest
254
- config simply has no coverage-gate line. `inventory.toolchainConformance`
255
- counts drift from the baseline's toolchain files -- information only. An
256
- inventory with `schemaVersion` below 4 carries neither field; skip this and
257
- continue.
258
-
259
- 3. **Write the findings back** into `.groundwork/adoption-report.md`,
260
- replacing the CLI's index-level sections ("a `lefthook.yml` exists")
261
- with semantic ones ("pre-push runs lint and typecheck; tests do not
262
- gate"). If the no-shell bullet in step 1 applied, carry its skip note
263
- into the rewritten report: this step replaces the report's sections, so
264
- a note written earlier would be dropped.
265
- 4. **Confirm.** Give a short summary in chat, then ask **one**
266
- `AskUserQuestion` covering: (a) _did this miss anything about your
267
- project?_ — the free-text option is the point of this question, not a
268
- formality — (b) the conflict resolutions from the inventory's conflict
269
- table, batched by facet (toolchain config, harness) rather than one
270
- question per file (the harness facet's batch also carries any wiring
271
- findings you confirmed in the deep read, offered as fixes to make, and the
272
- toolchain facet's batch does the same for confirmed toolchain wiring findings) — and
273
- (c) **which pack(s) to install**, from `inventory.packs`. For each pack, show its `budget`, its
274
- `wiringObservations` (facts about how it would land — e.g. "no
275
- `bin/lib/verify-steps.packs.json` found: no `bin/verify.mjs`-shaped gate
276
- runner detected", or "`.claude/settings.json` already sets a top-level
277
- `statusLine`"), and its `adoptNotes` verbatim; a pack whose gate
278
- dependency the project doesn't have is still offered for its other
279
- artifacts, with that limitation stated plainly rather than silently
280
- dropped. This is index-level evidence from the CLI, not a kind-based
281
- judgment — see Step 3's note on revisiting it once the interview confirms
282
- the project's kind.
283
-
284
- The same question also carries (d) if the no-shell bullet applied: say the
285
- SHA-256 check was skipped, what a passing check would have proven,
286
- which checks ran instead and any undetermined result, and
287
- ask whether to continue or stop.
288
-
289
- Before offering any pack, check each staged `.groundwork/packs/<name>/pack.json.staged` (the bytes Step 0.1 read, hash-checked when a command could run; not a fresh read):
290
- if `__proto__`, `constructor` or `prototype` is a key of its `wiring.settings`
291
- (hook event names), `wiring.settingsTopLevel` or `wiring.packageScripts`
292
- (script names), stop. The project tree is untrusted, and the CLI refuses such a
293
- pack before staging it, so a staged manifest carrying one was edited after
294
- staging. Name the pack, the field and the key, tell the user to delete
295
- `.groundwork/` and re-run the CLI, and offer nothing from this run. Change nothing.
296
- For a schema 1-4 inventory (the layout of the CLI releases before the `.staged` convention) there is no `.staged` copy and no hash: read the unsuffixed `.groundwork/packs/<name>/pack.json` (not hash-verified) and run this same prototype-key check on it.
297
-
56
+ **Read [`step-0-reconcile.md`](step-0-reconcile.md) in full before acting on
57
+ this step, and again after any compaction or resume** — it holds the whole
58
+ procedure, and a compacted session keeps only the start of this file.
59
+
60
+ In outline:
61
+
62
+ 1. Look for `.groundwork/inventory.json`. No `.groundwork/` at all means a
63
+ fresh bootstrap: skip to Step 1. A `.groundwork/` with no inventory means an
64
+ interrupted CLI run, and an unreadable inventory is invalid: in both cases
65
+ stop and change nothing, and never treat the project as a fresh bootstrap.
66
+ 2. **The deep read.** The CLI's survey is an index, not an interpretation:
67
+ read the real files it flagged `needsReading`, and reconcile its facts
68
+ against the repository.
69
+ 3. **Write the findings back** into `.groundwork/adoption-report.md`.
70
+ 4. **Confirm** the additions, the conflict resolutions and which packs to
71
+ install, in one `AskUserQuestion` round. Nothing is written to a project
72
+ file before this.
298
73
  5. **Record the confirmed decisions** to `.groundwork/adoption-decisions.json`
299
- so a compacted or resumed session doesn't silently lose them and re-ask.
300
- A CLI re-run deletes this file along with the old inventory, because its
301
- decisions were made against the previous staging; a decisions file found
302
- beside a fresh inventory therefore belongs to this inventory.
74
+ so a compacted or resumed session does not re-ask them. After a compaction,
75
+ read that file first.
303
76
 
304
77
  ## Step 1 — Interview
305
78
 
@@ -352,162 +125,18 @@ Round 2's two skill invocations will be told to emphasize.
352
125
 
353
126
  ## Step 3 — Round 1: deterministic tailoring
354
127
 
355
- **Fresh bootstrap** applies directly, no research needed:
356
-
357
- - **Project kind ≠ library**: if `check:exports` (publint/attw) doesn't
358
- apply to the chosen kind (CLI, frontend, service), remove the
359
- `check:exports` step from `bin/lib/verify-steps.mjs` and the
360
- corresponding `.github/workflows/ci.yml` line, and drop the `exports`
361
- field from `package.json` in favor of a `bin` field (CLI) or leave `main`/
362
- no public export map at all (service).
363
- - **Runtime target = browser or both**: note that `tsconfig.base.json`'s
364
- `lib` and `moduleResolution` will very likely need to change — but leave
365
- the actual edit to Round 2's `typescript-guidance` sweep, which has full
366
- authority over that file and access to current bundler-resolution
367
- guidance you don't have without a live source.
368
- - **Tests mandatory = warn only**: change the `test` lane in `lefthook.yml`
369
- and `ci.yml` from a hard failure to a non-blocking report.
370
- - **CI depth = minimal**: drop the `test` lane's coverage gate from CI
371
- (still run locally); minimal keeps only format/lint/typecheck/build.
372
- **CI depth = thorough**: note this for Round 2 — `harness-guidance` may
373
- recommend additional current-best-practice lanes (e.g. a scheduled
374
- dependency audit) beyond what the baseline ships.
375
- - **Agents not kept**: delete their `.claude/agents/<name>.md` file. Never
376
- delete `Explore`, `test-author`, or `code-implementer` even if unselected
377
- — they're load-bearing for the hub-and-spoke loop `CLAUDE.md` documents.
378
- - **Packs**: nothing to do here. A fresh bootstrap's packs were installed
379
- (or not) by the CLI at `m3l-groundwork <dir> --pack <name>` invocation
380
- time, before this skill ever ran — there is no fresh-mode install path in
381
- `/customize` itself. To add a pack after the fact, re-run the CLI against
382
- this now-non-empty directory (it auto-detects adopt mode) and run
383
- `/customize` again; its Step 0 will offer the pack through the adopt path
384
- below.
385
-
386
- **Adopt mode** re-expresses each of the same five outcomes against whatever
387
- the project actually has, instead of a named baseline path — "tests must not
388
- hard-fail `pre-push`" is applied to _the gate the inventory found_ (jest in
389
- CI, husky locally, whatever it is), not to `lefthook.yml`/`ci.yml` by name.
390
- Concretely, adopt-mode Round 1 applies exactly three things, all already
391
- confirmed in Step 0.4:
392
-
393
- - The **approved additions** — files `templates/core` would add that the
394
- project doesn't have and the user approved adding. For a schema 5 inventory, install them from the staged copy Step 0.1 already verified,
395
- using the bytes you read then (Step 0.1 also already checked that the
396
- staged files match the `absent` conflicts). Write them to the project at `path` (the
397
- `.staged` suffix stripped, never to the staged name), filling in the
398
- `__KEY__` tokens with the project's real values as you copy, same as
399
- staged packs. Only a **schema 1-4** inventory has no staged copy and reads
400
- additions from `inventory.templateRoot`; if that path no longer exists (a
401
- pruned `npx` cache, a deleted temp checkout), say so and ask for a CLI
402
- re-run rather than guessing at the baseline's contents.
403
- - The **approved conflict resolutions** — for each divergent file the user
404
- decided on, apply that decision (keep theirs / take groundwork's / merge
405
- the named keys).
406
- - The **approved packs** — installed from `.groundwork/packs/<name>/` (the
407
- CLI's staged, self-contained copy of inert `.staged` files — never
408
- `inventory.templateRoot`, which may not exist by the time this runs). Before installing, call
409
- `recommendPacks(answers)` from `pack-map.ts` (alongside this file, same
410
- copy mechanism as `kind-facet-map.ts`) with the now-confirmed
411
- `InterviewAnswers` and compare its verdict against Step 0.4's decision.
412
- `harness-extras`, `github`, `supply-chain`, `quality`, and `worktrees`
413
- never disagree (none of the five's recommendation varies by kind or
414
- answer -- `worktrees` is always `recommended: false`, since it changes the
415
- day-to-day workflow rather than adding a nicety, and the other four are
416
- always `recommended: true`), but `publishing`'s does (recommended for
417
- `library`/`cli`, not for `frontend`/`service`) — if the comparison surfaces a real
418
- conflict there or for any future kind-scoped pack, raise it rather than
419
- silently overriding the user's Step 0.4 answer, mirroring Step 4's "the
420
- one exception" rule for guidance findings. Before installing, check the
421
- pack's own `modes`: a pack whose `modes` doesn't include `"adopt"` (today,
422
- `publishing` — its release flow encodes decisions too project-specific to
423
- apply blind) is never auto-installed here even if staged and approved;
424
- instead, state in Step 6 that it needs a manual install (point at `.groundwork/packs/<name>/` and the pack's own `adoptNotes`, and say to strip the `.staged` suffix from every name when copying by hand and to replace every `__KEY__` token with the project's real value) and stop
425
- there for that pack. Before merging any staged `pack.json.staged` wiring, repeat
426
- the prototype-key check from Step 0.4(c): if a key of `wiring.settings`,
427
- `wiring.settingsTopLevel` or `wiring.packageScripts` is `__proto__`,
428
- `constructor` or `prototype`, stop the same way and change nothing. For an
429
- adopt-capable pack, install each file from
430
- `.groundwork/packs/<name>/files/<path>.staged`, using the bytes Step 0.1 read (hash-checked when a command could run), and write it to the project at `path` (the `.staged` suffix
431
- stripped, never to the staged name), filling in the `__KEY__` tokens with
432
- the project's real values as you copy (respecting any approved per-file
433
- conflict decision the same way the baseline's own additions are applied).
434
- For a schema 1-4 inventory, install each file from the unsuffixed
435
- `.groundwork/packs/<name>/files/<path>` instead (no `.staged` suffix to strip,
436
- not hash-verified; token substitution still applies).
437
- Then, for either schema, translate `pack.json`'s `wiring` by hand, reading it
438
- from `pack.json.staged` (the bytes Step 0.1 read) for a schema 5 inventory and
439
- from the unsuffixed `pack.json` for schema 1-4,
440
- against what Step 0.2's deep read already found — a `.claude/settings.json`
441
- hook fragment merges the same way the baseline's own hook entries would;
442
- `wiring.settingsTopLevel` is a set of top-level keys (e.g. `statusLine`)
443
- planted whole, and only when the project doesn't already define that key —
444
- when it does, show the existing value and ask, since the CLI's own
445
- `mergeSettingsTopLevel` treats a differing value as a hard error and this
446
- hand-applied path must not be laxer than the automated one (also check
447
- `.claude/settings.local.json` and the user's `~/.claude/settings.json`,
448
- either of which can shadow a project `statusLine`). For `harness-extras`
449
- specifically, a `statusLine`/`subagentStatusLine` collision is never a
450
- reason to fail the whole pack install: skip just those two settings keys
451
- and the three statusline scripts (`statusline.mjs`, `statusline-layout.mjs`,
452
- `subagent-statusline.mjs`) and install the pack's other artifacts (the
453
- compaction-handoff hooks and `guard-readonly-bash`) normally, stating the
454
- skip plainly in Step 6;
455
- `wiring.verifySteps` becomes a step in whatever this project's real gate
456
- runner is (a `package.json` script plus a line in its `lefthook.yml`/
457
- `.husky/pre-push`/CI workflow, written by hand to match its actual shape)
458
- — or, if the project has no such gate runner at all, install the pack's
459
- other artifacts and state plainly in Step 6 that the gate was not wired,
460
- rather than inventing a runner the project never asked for.
461
-
462
- **Plugins (both modes), after packs are settled above.** Build a
463
- `PluginRecommendationContext`: `chosenPacks` is whatever the packs decision
464
- just above actually landed on (fresh: what `--pack` installed at bootstrap
465
- time, detectable from the installed files -- e.g. `.changeset/config.json`
466
- means `publishing`, `.github/workflows/claude-pr-review.yml` means `github`
467
- (the only marker `plugin-map.ts` actually reads; `harness-extras` has no
468
- plugin that varies by its presence, so it needs no marker here); adopt: the
469
- packs Step 0.4(c) confirmed, revised by this step's own `recommendPacks`
470
- comparison if it changed anything); `hasCustomSkills` is `false` for a fresh
471
- bootstrap (nothing has authored a skill yet) and, for adopt mode, whatever
472
- Step 0's survey found beyond the baseline's own known skill names and any
473
- already-installed pack's skills (e.g. the `github` pack's three). Call
474
- `recommendPlugins(answers, context)` from `plugin-map.ts`
475
- (alongside this file, same copy mechanism as `pack-map.ts`) with the
476
- now-confirmed `InterviewAnswers`, and ask **one** `AskUserQuestion`
477
- (multi-select) offering all seven, pre-selected per each entry's
478
- `recommended` boolean with its `because` shown as the evidence -- same
479
- "visible reasoning" principle as every other inference in this skill.
480
-
481
- Write every confirmed `true` entry into `.claude/settings.json`'s
482
- `enabledPlugins` (`{"<id>": true}` per entry, e.g.
483
- `"context7@claude-plugins-official": true`) -- additive and entry-by-entry,
484
- same discipline as the CLI's own `mergeSettingsHooks` (which owns the
485
- `hooks` block entry-by-entry, as opposed to `mergeSettingsTopLevel`'s
486
- whole-key-at-a-time semantics): never remove or flip an entry the project
487
- already sets explicitly (an existing `false` is a decision the user made,
488
- not an oversight to correct), and never touch `.claude/settings.local.json`
489
- or the user's own `~/.claude/settings.json` scope. `claude-plugins-official`
490
- is a built-in marketplace, so no `extraKnownMarketplaces` entry is needed
491
- for any of the seven.
492
-
493
- **Committing this entry does not install the plugin for anyone.** A
494
- project-scope `enabledPlugins: true` with no local install produces no
495
- folder-trust auto-prompt -- Claude Code's `/plugin` Errors tab instead shows
496
- "enabled in project settings but isn't installed here" until someone runs
497
- the install by hand. So for every newly-`true` entry, print the exact
498
- follow-up command in Step 6's report: `claude plugin install <id> --scope
499
- project` (or `/plugin install <id>` inside a running session) -- the user,
500
- and every collaborator who pulls this change, still has to run it once. Do
501
- the same for each recommendation's `prerequisites` (the
502
- `typescript-language-server` binary, Python 3.8+) -- print them as
503
- follow-ups, never attempt to install them.
504
-
505
- Nothing else is touched. A project file the user didn't approve a change to
506
- stays exactly as it was.
507
-
508
- Run `pnpm verify` (fresh) or the project's own equivalent (adopt) after
509
- Round 1's edits to confirm the tailored result still passes before moving to
510
- Round 2.
128
+ **Read [`step-3-round-1.md`](step-3-round-1.md) in full before applying any
129
+ Round 1 edit, and again after any compaction or resume** — it holds every
130
+ tailoring rule, and a compacted session keeps only the start of this file.
131
+
132
+ In outline: a fresh bootstrap applies the interview's answers directly, with
133
+ no research. It prunes by project kind, runtime target, test strictness, CI
134
+ depth and which reviewer agents to keep; its packs were already installed by
135
+ the CLI. An adopted project re-expresses the same five outcomes against its own
136
+ files, applies only what Step 0 confirmed, and installs the staged packs the
137
+ user chose. Both modes then settle plugin recommendations. Run `pnpm verify`
138
+ (fresh) or the project's own equivalent (adopt) after Round 1's edits, before
139
+ moving to Round 2.
511
140
 
512
141
  ## Step 4 — Round 2: the guidance pass
513
142
 
@@ -575,5 +204,6 @@ Also list **which plugins were enabled** (each newly-`true`
575
204
  `enabledPlugins` entry, with the exact `claude plugin install <id> --scope
576
205
  project` follow-up command it still needs) and which were offered but
577
206
  declined, plus any prerequisite named against an enabled plugin
578
- (`typescript-language-server` on `PATH`, Python 3.8+) as a follow-up the
207
+ (e.g. `typescript-language-server` on `PATH`, or the Python version a
208
+ plugin's `prerequisites` names) as a follow-up the
579
209
  user still has to satisfy.