@monte3l/groundwork 1.0.0-rc.4 → 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.
- package/dist/customize-paths.d.ts +51 -15
- package/dist/customize-paths.js +32 -13
- package/dist/fs-guard.d.ts +2 -2
- package/dist/harness/rules.d.ts +2 -1
- package/dist/harness/rules.js +3 -1
- package/dist/plugin.d.ts +12 -12
- package/dist/plugin.js +34 -26
- package/package.json +1 -1
- package/plugin/skills/customize/SKILL.md +33 -403
- package/plugin/skills/customize/step-0-reconcile.md +261 -0
- package/plugin/skills/customize/step-3-round-1.md +170 -0
- package/plugin/src/plugin-map.ts +131 -10
- package/templates/core/.claude/agents/Explore.md +1 -1
- package/templates/core/.claude/hooks/guard-no-commonjs.mjs +3 -2
- package/templates/core/.claude/hooks/inject-decision-gate.mjs +7 -7
- package/templates/core/.claude/skills/triaging-ci/SKILL.md +2 -2
- package/templates/core/.claude/skills/writing-commits/SKILL.md +4 -4
- package/templates/core/.prettierignore +1 -0
- package/templates/core/_gitignore +1 -0
- package/templates/core/bin/check-exports.mjs +5 -3
- package/templates/core/bin/lib/harness-rules.mjs +3 -1
- package/templates/core/tsconfig.base.json +10 -6
- package/templates/packs/README.md +6 -0
- package/templates/packs/github/files/.github/workflows/claude-pr-review.yml +1 -1
- package/templates/packs/github/files/.github/workflows/claude.yml +1 -1
- package/templates/packs/github/pack.json +1 -1
- package/templates/packs/harness-extras/files/.claude/hooks/subagent-statusline.mjs +29 -14
- package/templates/packs/harness-extras/pack.json +1 -1
- package/templates/packs/publishing/pack.json +1 -1
- package/templates/packs/quality/files/.claude/agents/type-design-analyzer.md +1 -1
- 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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
|
300
|
-
|
|
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
|
-
**
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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
|
|
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.
|