@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.
Files changed (31) 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/plugin.d.ts +12 -12
  7. package/dist/plugin.js +34 -26
  8. package/package.json +1 -1
  9. package/plugin/skills/customize/SKILL.md +33 -403
  10. package/plugin/skills/customize/step-0-reconcile.md +261 -0
  11. package/plugin/skills/customize/step-3-round-1.md +170 -0
  12. package/plugin/src/plugin-map.ts +131 -10
  13. package/templates/core/.claude/agents/Explore.md +1 -1
  14. package/templates/core/.claude/hooks/guard-no-commonjs.mjs +3 -2
  15. package/templates/core/.claude/hooks/inject-decision-gate.mjs +7 -7
  16. package/templates/core/.claude/skills/triaging-ci/SKILL.md +2 -2
  17. package/templates/core/.claude/skills/writing-commits/SKILL.md +4 -4
  18. package/templates/core/.prettierignore +1 -0
  19. package/templates/core/_gitignore +1 -0
  20. package/templates/core/bin/check-exports.mjs +5 -3
  21. package/templates/core/bin/lib/harness-rules.mjs +3 -1
  22. package/templates/core/tsconfig.base.json +10 -6
  23. package/templates/packs/README.md +6 -0
  24. package/templates/packs/github/files/.github/workflows/claude-pr-review.yml +1 -1
  25. package/templates/packs/github/files/.github/workflows/claude.yml +1 -1
  26. package/templates/packs/github/pack.json +1 -1
  27. package/templates/packs/harness-extras/files/.claude/hooks/subagent-statusline.mjs +29 -14
  28. package/templates/packs/harness-extras/pack.json +1 -1
  29. package/templates/packs/publishing/pack.json +1 -1
  30. package/templates/packs/quality/files/.claude/agents/type-design-analyzer.md +1 -1
  31. package/templates/packs/quality/pack.json +1 -1
@@ -0,0 +1,261 @@
1
+ # Step 0 — Reconcile (adopt mode only): detail
2
+
3
+ > Full text of Step 0 from `SKILL.md`. Read this whole file before acting on Step 0, and re-read it after a compaction: a compacted session keeps only the start of `SKILL.md`.
4
+
5
+ Contents:
6
+
7
+ 1. Look for `.groundwork/inventory.json` (fresh vs. interrupted vs. adopt)
8
+ 2. The deep read (the survey is an index, not an interpretation)
9
+ 3. Write the findings back into `.groundwork/adoption-report.md`
10
+ 4. Confirm (one `AskUserQuestion` round: additions, conflicts, packs)
11
+ 5. Record the confirmed decisions to `.groundwork/adoption-decisions.json`
12
+
13
+ ## Step 0 — Reconcile (adopt mode only)
14
+
15
+ 1. Look for `.groundwork/inventory.json`.
16
+ - **Absent, and no `.groundwork/` directory either → this is a fresh
17
+ bootstrap; skip straight to Step 1.** Everything below this step applies
18
+ only when an inventory exists.
19
+ - **Absent, but `.groundwork/` exists → a previous CLI adopt run did not
20
+ complete** (the CLI deletes the old inventory before it stages anything
21
+ and writes the new one last, so no inventory beside a `.groundwork/`
22
+ directory means the run died part-way). **Stop. Never fall through to
23
+ the fresh flow** -- that would run fresh-mode tailoring on an
24
+ established project. Tell the user: "`.groundwork/` is incomplete: a
25
+ previous adopt run did not finish, or `inventory.json` was removed.
26
+ If this project was never adopted (it was bootstrapped fresh), delete
27
+ `.groundwork/` and run `/customize` again. Otherwise re-run
28
+ `npx @monte3l/groundwork@rc .` and then run `/customize` again."
29
+ Change nothing.
30
+ - **Present, but not valid JSON, or its `schemaVersion` is not an integer
31
+ of at least 1 → stop and change nothing.** Tell the user: "`.groundwork/inventory.json`
32
+ is not valid JSON or has no usable `schemaVersion`. Re-run
33
+ `npx @monte3l/groundwork@rc .` and then run `/customize` again."
34
+
35
+ **Check `inventory.schemaVersion` before reading anything else.** This
36
+ skill understands schema versions **1 through 5** (the highest it knows is
37
+ 5). If `schemaVersion` is **higher than 5**, the CLI that wrote it is newer
38
+ than this plugin: **stop and change nothing**, do not interpret the
39
+ inventory (a newer schema may have renamed or repurposed fields, and a
40
+ confident misreading is worse than none), and tell the user to update the
41
+ plugin and re-run `/customize`. Say which update applies to the copy
42
+ that is running. For the plugin install, run `/plugin update`. For a
43
+ project-local copy in `.claude/skills/customize/`, `/plugin update` does
44
+ not touch it, and re-running the CLI alone does not either: the CLI
45
+ never overwrites a copy that differs (nor removes any other entry the
46
+ project owns there, nor writes through a symlinked `.claude`), it writes a
47
+ fresh one to `.groundwork/customize/`, which Claude Code does not load. So delete
48
+ that directory first (this discards any local edits the project made to
49
+ its copy) and then re-run `npx @monte3l/groundwork@rc .`. A
50
+ copy in `.groundwork/customize/` is refreshed by re-running the CLI. What
51
+ each version added:
52
+
53
+ | `schemaVersion` | Adds | If absent |
54
+ | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
55
+ | 1 | the survey, `conflicts` | (the floor) |
56
+ | 2 | `packs` | no packs to offer; skip the pack question |
57
+ | 3 | `harnessGrade`, `harnessConformance` | skip the harness-grade starting point |
58
+ | 4 | `toolchainGrade`, `toolchainConformance` | skip the toolchain-grade starting point |
59
+ | 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/` |
60
+
61
+ **For a schema 5 inventory only: verify the staged baseline now, before
62
+ anything is offered to the user.** A schema 1-4 inventory has no
63
+ `stagedBaseline`: skip this whole block (every bullet below, including
64
+ its stop list) and use the `inventory.templateRoot` fallback in Round 1.
65
+ The inventory lives in the project tree and is untrusted input, so check
66
+ it before trusting any entry:
67
+
68
+ - `stagedBaseline.dir` must equal `.groundwork/baseline` exactly, and
69
+ `stagedBaseline.suffix` must equal `.staged` exactly. Take both values
70
+ from this list, not from the inventory: a `dir` of `.` with an empty
71
+ `suffix` would make you "verify" live project files.
72
+ - For every entry of `inventory.stagedBaseline.files`, require
73
+ `staged === path + ".staged"`, and require `path` to be relative, free
74
+ of any `..` segment, and not absolute. Reject the entry if `path` is
75
+ `""` or `"."`. Reject it too if `path` contains a `\` or a `:` (a
76
+ Windows `..\..` or `C:foo` would slip past the other checks). Reject a
77
+ duplicate `path` or a duplicate `staged` among the entries. These fields
78
+ use `/` separators on every platform: `conflicts[].relPath`,
79
+ `packs[].fileConflicts[].relPath`, `stagedBaseline.dir`,
80
+ `stagedBaseline.files[].path` and its `.staged` name, `stagedPacks[].dir`,
81
+ `stagedPacks[].files[].path` and `stagedPacks[].files[].staged`. Other paths in
82
+ the inventory are not normalized: `templateRoot` and `targetDir` are
83
+ absolute native paths, as are the survey's tsconfig chain entries.
84
+ - The `absent` conflicts in `inventory.conflicts` and the paths in
85
+ `stagedBaseline.files` must name the same set of files. If they
86
+ disagree (an `absent` conflict with no staged entry, or a staged entry
87
+ that is not an `absent` conflict), that is a mismatch: stop, with the
88
+ same message as a hash mismatch, before anything is offered or approved.
89
+ - Read each staged file at `.groundwork/baseline/<staged>` **once**.
90
+ It must exist, and the SHA-256 of its **raw bytes** (no end-of-line
91
+ normalization, no decoding) must equal the entry's `sha256`. Compute it
92
+ with, for example, this one-liner (it targets a POSIX shell or Git Bash,
93
+ not `cmd.exe` or an old PowerShell), passing the file as a single quoted
94
+ argument in place of `<file>`:
95
+ `node -e 'process.stdout.write(require("crypto").createHash("sha256").update(require("fs").readFileSync(process.argv[1])).digest("hex"))' <file>`
96
+ Keep those same bytes for the install in Round 1 (substitute tokens into
97
+ them) rather than reading the file a second time.
98
+ - **Staged packs, in the same block.** For every entry of
99
+ `inventory.stagedPacks`:
100
+ - `dir` must equal `.groundwork/packs/<name>`, built from the entry's
101
+ own `name`, and `suffix` must equal `.staged`. Take both from this
102
+ list, not from the inventory.
103
+ - `name` must be a single path segment: not empty, not `.` or `..`, and
104
+ free of `/`, `\` and `:`. Reject a duplicate pack `name`.
105
+ - `manifest.path` must equal `pack.json` and `manifest.staged` must
106
+ equal `pack.json.staged`.
107
+ - For every entry of its `files`, require `staged === path + ".staged"`,
108
+ and apply the same path checks as the staged baseline's files above
109
+ (relative, no `..` segment, not absolute, not `""` or `"."`, no `\` or
110
+ `:`). Reject a duplicate `path` or a duplicate `staged` among one
111
+ pack's files.
112
+ - The pack names in `inventory.packs` and in `inventory.stagedPacks`
113
+ must be the same set, and for each pack its `fileConflicts[].relPath`
114
+ set must equal its `files[].path` set.
115
+ - Read the staged manifest at `<dir>/pack.json.staged` and each staged
116
+ file at `<dir>/files/<staged>` once, and apply the SHA-256 check above
117
+ to the raw bytes of each. Keep the bytes Step 0.1 read (hash-checked
118
+ when a command could run): Round 1's install and the prototype-key
119
+ check in Step 0.4(c) use them, with no second read.
120
+ - **No shell tool available.** If a Bash or other command-running tool is
121
+ available, compute the hash as above. If you cannot run a command, do
122
+ not work around it: write no scratch script; while verifying, make no
123
+ `Write` or `Edit` outside `.groundwork/` (Round 1's confirmed installs are
124
+ not part of verification), dispatch no subagent to look for a shell, and
125
+ search for a command tool at most once. Skip only the SHA-256
126
+ comparison. Run every other check in this block with the file tools
127
+ (`Read`, `Glob`, `Grep`): the files exist, `staged === path + ".staged"`,
128
+ the paths are relative with no `..`, `\` or `:`, no duplicates, `dir`
129
+ and `suffix` exact, the `absent` conflicts and `stagedBaseline.files`
130
+ name the same set, and no extra staged file exists: list the staged
131
+ files with the `Glob` pattern `.groundwork/baseline/**/*.staged` and
132
+ compare with `stagedBaseline.files`. A `Glob` can skip hidden paths
133
+ (most staged files are dot-paths), honour an ignore file or truncate a
134
+ long list, so a result shorter than `stagedBaseline.files.length`, or
135
+ that looks truncated, is undetermined: report it in the Step 0.4 summary
136
+ and continue, since every listed entry's existence is already checked
137
+ one by one; only an extra `*.staged` file, one whose path is not in
138
+ `stagedBaseline.files`, stops the run. Run the same structural checks
139
+ for `inventory.stagedPacks`: each pack's `<dir>/pack.json.staged` and
140
+ every `<dir>/files/<staged>` exists, `staged === path + ".staged"` with
141
+ paths that are relative and free of `..`, `\` and `:`, no duplicates,
142
+ `name`, `dir`, `suffix`, `manifest.path` and `manifest.staged` exact,
143
+ the pack names in `inventory.packs` and `inventory.stagedPacks` the
144
+ same set, and each pack's `fileConflicts[].relPath` and `files[].path`
145
+ the same set. List each pack's staged files with the `Glob` pattern
146
+ `.groundwork/packs/**/*.staged` (the same hidden-path and
147
+ truncation caveats apply) and compare with every listed manifest and
148
+ 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
149
+ substitute for what the hash proves, and it is not tamper-resistance.
150
+ Then spend no further turns on verification and go on to the deep
151
+ read. State this plainly at the top of your first message to the user (the Step 0.4 summary, not a
152
+ separate earlier stop), and ask whether to continue or stop in that
153
+ same confirmation: the SHA-256 check was skipped because no command
154
+ could be run; a passing check would only have proven that the staging
155
+ is complete and matches the inventory, not that the files are
156
+ untampered; and which structural checks you did verify instead. Also
157
+ record the skip in `.groundwork/adoption-report.md` when you write the
158
+ findings back in Step 0.3. A structural failure, including an extra staged file, still stops, exactly as the stop list below says.
159
+ - An empty `files` list is legitimate (nothing was missing from the
160
+ project) and creates no `.groundwork/baseline/` directory; that alone is
161
+ not a failure.
162
+ - **For a schema 5 inventory, a `stagedBaseline` that is missing, not an
163
+ object, or whose `files` is not an array also stops the run** (a
164
+ `files: {}` is not an empty list); so does a `stagedPacks` that is
165
+ missing or not an array, or a pack entry that is not an object or whose
166
+ `files` is not an array. **Any invalid entry, missing file or hash mismatch (including a
167
+ wrong `dir` or `suffix`), any duplicate `staged` or `path` or pack `name`, any
168
+ `absent` conflicts and `stagedBaseline.files` that do not name the same
169
+ set, any pack whose names or file paths disagree with `inventory.packs`, and
170
+ (no-shell path) an extra `*.staged` file not named in
171
+ `stagedBaseline.files`, a pack's `files` or a pack's manifest
172
+ means stop -- all of Round 1, including conflicts and packs -- and
173
+ change nothing.** This is the single stop list for the staged baseline and the staged packs.
174
+ 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).
175
+ - What a passing check proves: the staging is complete and matches the
176
+ inventory. It does **not** prove the files are untampered -- anyone who
177
+ can edit the staged files can edit the inventory's hashes too.
178
+
179
+ An inventory with `schemaVersion` below 5 has no staged copy; its
180
+ approved additions are read from `inventory.templateRoot` (see Round 1).
181
+
182
+ 2. **The deep read.** The CLI's survey is an index, not an interpretation —
183
+ it flagged what it found but could not parse (`needsReading: true` on
184
+ git-hook config, workflow files; anything in `survey.undetermined`) and
185
+ what it could only index, not summarize (`docs`). Read all of it for
186
+ real: the eslint config, the git-hook manager's actual stage commands,
187
+ the CI workflow job steps, `CLAUDE.md`, `CONTRIBUTING.md`, and any
188
+ docs/ADR files the survey indexed. Dispatch this as parallel read-only
189
+ `Explore` agents, one per discovery area (shape/toolchain, harness, docs),
190
+ so you aggregate their findings rather than reading everything yourself.
191
+ The harness agent also starts from `inventory.harnessGrade` (the report's
192
+ `## Harness grade` section): a deterministic, offline check of the
193
+ existing `.claude/` wiring. Its **wiring findings** (a hook registration
194
+ naming a missing file, a skill or agent with unreadable frontmatter, a
195
+ `CLAUDE.md` path that no longer exists) are facts to verify against the
196
+ real files, not verdicts to take on trust. Its **quality findings** are
197
+ advisory. `inventory.harnessConformance` counts how far the harness has
198
+ drifted from the baseline's — information only, since divergence from the
199
+ baseline is the point of adopting. An inventory with `schemaVersion` below
200
+ 3 carries neither field; skip this and continue.
201
+
202
+ The toolchain agent likewise starts from `inventory.toolchainGrade` (the
203
+ report's `## Toolchain grade` section): a deterministic, offline check of
204
+ the tsconfig chain, ESLint and vitest config, verify-step wiring, and
205
+ toolchain pins. It reads files and never runs them, and it reads
206
+ `eslint.config.js`/`vitest.config.ts` by pattern rather than by evaluating
207
+ them -- so a **wiring finding** (a build project that emits nowhere, a
208
+ verify step naming a script or file that does not exist, a `.node-version`
209
+ that contradicts `engines.node`) is a fact to verify against the real files,
210
+ and a **quality finding** (a missing strict flag, an option TypeScript has
211
+ deprecated, ESLint without type-aware linting, a coverage gate that is not
212
+ per-file) is advisory. Absence is never a finding: a project with no vitest
213
+ config simply has no coverage-gate line. `inventory.toolchainConformance`
214
+ counts drift from the baseline's toolchain files -- information only. An
215
+ inventory with `schemaVersion` below 4 carries neither field; skip this and
216
+ continue.
217
+
218
+ 3. **Write the findings back** into `.groundwork/adoption-report.md`,
219
+ replacing the CLI's index-level sections ("a `lefthook.yml` exists")
220
+ with semantic ones ("pre-push runs lint and typecheck; tests do not
221
+ gate"). If the no-shell bullet in step 1 applied, carry its skip note
222
+ into the rewritten report: this step replaces the report's sections, so
223
+ a note written earlier would be dropped.
224
+ 4. **Confirm.** Give a short summary in chat, then ask **one**
225
+ `AskUserQuestion` covering: (a) _did this miss anything about your
226
+ project?_ — the free-text option is the point of this question, not a
227
+ formality — (b) the conflict resolutions from the inventory's conflict
228
+ table, batched by facet (toolchain config, harness) rather than one
229
+ question per file (the harness facet's batch also carries any wiring
230
+ findings you confirmed in the deep read, offered as fixes to make, and the
231
+ toolchain facet's batch does the same for confirmed toolchain wiring findings) — and
232
+ (c) **which pack(s) to install**, from `inventory.packs`. For each pack, show its `budget`, its
233
+ `wiringObservations` (facts about how it would land — e.g. "no
234
+ `bin/lib/verify-steps.packs.json` found: no `bin/verify.mjs`-shaped gate
235
+ runner detected", or "`.claude/settings.json` already sets a top-level
236
+ `statusLine`"), and its `adoptNotes` verbatim; a pack whose gate
237
+ dependency the project doesn't have is still offered for its other
238
+ artifacts, with that limitation stated plainly rather than silently
239
+ dropped. This is index-level evidence from the CLI, not a kind-based
240
+ judgment — see Step 3's note on revisiting it once the interview confirms
241
+ the project's kind.
242
+
243
+ The same question also carries (d) if the no-shell bullet applied: say the
244
+ SHA-256 check was skipped, what a passing check would have proven,
245
+ which checks ran instead and any undetermined result, and
246
+ ask whether to continue or stop.
247
+
248
+ 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):
249
+ if `__proto__`, `constructor` or `prototype` is a key of its `wiring.settings`
250
+ (hook event names), `wiring.settingsTopLevel` or `wiring.packageScripts`
251
+ (script names), stop. The project tree is untrusted, and the CLI refuses such a
252
+ pack before staging it, so a staged manifest carrying one was edited after
253
+ staging. Name the pack, the field and the key, tell the user to delete
254
+ `.groundwork/` and re-run the CLI, and offer nothing from this run. Change nothing.
255
+ 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.
256
+
257
+ 5. **Record the confirmed decisions** to `.groundwork/adoption-decisions.json`
258
+ so a compacted or resumed session doesn't silently lose them and re-ask.
259
+ A CLI re-run deletes this file along with the old inventory, because its
260
+ decisions were made against the previous staging; a decisions file found
261
+ beside a fresh inventory therefore belongs to this inventory.
@@ -0,0 +1,170 @@
1
+ # Step 3 — Round 1: deterministic tailoring: detail
2
+
3
+ > Full text of Step 3 from `SKILL.md`. Read this whole file before applying Round 1's edits, and re-read it after a compaction: a compacted session keeps only the start of `SKILL.md`.
4
+
5
+ ## Step 3 — Round 1: deterministic tailoring
6
+
7
+ **Fresh bootstrap** applies directly, no research needed:
8
+
9
+ - **Project kind ≠ library**: if `check:exports` (publint/attw) doesn't
10
+ apply to the chosen kind (CLI, frontend, service), remove the
11
+ `check:exports` step from `bin/lib/verify-steps.mjs` and the
12
+ corresponding `.github/workflows/ci.yml` line, and drop the `exports`
13
+ field from `package.json` in favor of a `bin` field (CLI) or leave `main`/
14
+ no public export map at all (service).
15
+ - **Runtime target = browser or both**: note that `tsconfig.base.json`'s
16
+ `lib` and `moduleResolution` will very likely need to change — but leave
17
+ the actual edit to Round 2's `typescript-guidance` sweep, which has full
18
+ authority over that file and access to current bundler-resolution
19
+ guidance you don't have without a live source.
20
+ - **Tests mandatory = warn only**: change the `test` lane in `lefthook.yml`
21
+ and `ci.yml` from a hard failure to a non-blocking report.
22
+ - **CI depth = minimal**: drop the `test` lane's coverage gate from CI
23
+ (still run locally); minimal keeps only format/lint/typecheck/build.
24
+ **CI depth = thorough**: note this for Round 2 — `harness-guidance` may
25
+ recommend additional current-best-practice lanes (e.g. a scheduled
26
+ dependency audit) beyond what the baseline ships.
27
+ - **Agents not kept**: delete their `.claude/agents/<name>.md` file. Never
28
+ delete `Explore`, `test-author`, or `code-implementer` even if unselected
29
+ — they're load-bearing for the hub-and-spoke loop `CLAUDE.md` documents.
30
+ - **Packs**: nothing to do here. A fresh bootstrap's packs were installed
31
+ (or not) by the CLI at `m3l-groundwork <dir> --pack <name>` invocation
32
+ time, before this skill ever ran — there is no fresh-mode install path in
33
+ `/customize` itself. To add a pack after the fact, re-run the CLI against
34
+ this now-non-empty directory (it auto-detects adopt mode) and run
35
+ `/customize` again; its Step 0 will offer the pack through the adopt path
36
+ below.
37
+
38
+ **Adopt mode** re-expresses each of the same five outcomes against whatever
39
+ the project actually has, instead of a named baseline path — "tests must not
40
+ hard-fail `pre-push`" is applied to _the gate the inventory found_ (jest in
41
+ CI, husky locally, whatever it is), not to `lefthook.yml`/`ci.yml` by name.
42
+ Concretely, adopt-mode Round 1 applies exactly three things, all already
43
+ confirmed in Step 0.4:
44
+
45
+ - The **approved additions** — files `templates/core` would add that the
46
+ 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,
47
+ using the bytes you read then (Step 0.1 also already checked that the
48
+ staged files match the `absent` conflicts). Write them to the project at `path` (the
49
+ `.staged` suffix stripped, never to the staged name), filling in the
50
+ `__KEY__` tokens with the project's real values as you copy, same as
51
+ staged packs. Only a **schema 1-4** inventory has no staged copy and reads
52
+ additions from `inventory.templateRoot`; if that path no longer exists (a
53
+ pruned `npx` cache, a deleted temp checkout), say so and ask for a CLI
54
+ re-run rather than guessing at the baseline's contents.
55
+ - The **approved conflict resolutions** — for each divergent file the user
56
+ decided on, apply that decision (keep theirs / take groundwork's / merge
57
+ the named keys).
58
+ - The **approved packs** — installed from `.groundwork/packs/<name>/` (the
59
+ CLI's staged, self-contained copy of inert `.staged` files — never
60
+ `inventory.templateRoot`, which may not exist by the time this runs). Before installing, call
61
+ `recommendPacks(answers)` from `pack-map.ts` (alongside this file, same
62
+ copy mechanism as `kind-facet-map.ts`) with the now-confirmed
63
+ `InterviewAnswers` and compare its verdict against Step 0.4's decision.
64
+ `harness-extras`, `github`, `supply-chain`, `quality`, and `worktrees`
65
+ never disagree (none of the five's recommendation varies by kind or
66
+ answer -- `worktrees` is always `recommended: false`, since it changes the
67
+ day-to-day workflow rather than adding a nicety, and the other four are
68
+ always `recommended: true`), but `publishing`'s does (recommended for
69
+ `library`/`cli`, not for `frontend`/`service`) — if the comparison surfaces a real
70
+ conflict there or for any future kind-scoped pack, raise it rather than
71
+ silently overriding the user's Step 0.4 answer, mirroring Step 4's "the
72
+ one exception" rule for guidance findings. Before installing, check the
73
+ pack's own `modes`: a pack whose `modes` doesn't include `"adopt"` (today,
74
+ `publishing` — its release flow encodes decisions too project-specific to
75
+ apply blind) is never auto-installed here even if staged and approved;
76
+ 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
77
+ there for that pack. Before merging any staged `pack.json.staged` wiring, repeat
78
+ the prototype-key check from Step 0.4(c): if a key of `wiring.settings`,
79
+ `wiring.settingsTopLevel` or `wiring.packageScripts` is `__proto__`,
80
+ `constructor` or `prototype`, stop the same way and change nothing. For an
81
+ adopt-capable pack, install each file from
82
+ `.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
83
+ stripped, never to the staged name), filling in the `__KEY__` tokens with
84
+ the project's real values as you copy (respecting any approved per-file
85
+ conflict decision the same way the baseline's own additions are applied).
86
+ For a schema 1-4 inventory, install each file from the unsuffixed
87
+ `.groundwork/packs/<name>/files/<path>` instead (no `.staged` suffix to strip,
88
+ not hash-verified; token substitution still applies).
89
+ Then, for either schema, translate `pack.json`'s `wiring` by hand, reading it
90
+ from `pack.json.staged` (the bytes Step 0.1 read) for a schema 5 inventory and
91
+ from the unsuffixed `pack.json` for schema 1-4,
92
+ against what Step 0.2's deep read already found — a `.claude/settings.json`
93
+ hook fragment merges the same way the baseline's own hook entries would;
94
+ `wiring.settingsTopLevel` is a set of top-level keys (e.g. `statusLine`)
95
+ planted whole, and only when the project doesn't already define that key —
96
+ when it does, show the existing value and ask, since the CLI's own
97
+ `mergeSettingsTopLevel` treats a differing value as a hard error and this
98
+ hand-applied path must not be laxer than the automated one (also check
99
+ `.claude/settings.local.json` and the user's `~/.claude/settings.json`,
100
+ either of which can shadow a project `statusLine`). For `harness-extras`
101
+ specifically, a `statusLine`/`subagentStatusLine` collision is never a
102
+ reason to fail the whole pack install: skip just those two settings keys
103
+ and the three statusline scripts (`statusline.mjs`, `statusline-layout.mjs`,
104
+ `subagent-statusline.mjs`) and install the pack's other artifacts (the
105
+ compaction-handoff hooks and `guard-readonly-bash`) normally, stating the
106
+ skip plainly in Step 6;
107
+ `wiring.verifySteps` becomes a step in whatever this project's real gate
108
+ runner is (a `package.json` script plus a line in its `lefthook.yml`/
109
+ `.husky/pre-push`/CI workflow, written by hand to match its actual shape)
110
+ — or, if the project has no such gate runner at all, install the pack's
111
+ other artifacts and state plainly in Step 6 that the gate was not wired,
112
+ rather than inventing a runner the project never asked for.
113
+
114
+ **Plugins (both modes), after packs are settled above.** Build a
115
+ `PluginRecommendationContext`: `chosenPacks` is whatever the packs decision
116
+ just above actually landed on (fresh: what `--pack` installed at bootstrap
117
+ time, detectable from the installed files -- e.g. `.changeset/config.json`
118
+ means `publishing`, `.github/workflows/claude-pr-review.yml` means `github`
119
+ (the only marker `plugin-map.ts` actually reads; `harness-extras` has no
120
+ plugin that varies by its presence, so it needs no marker here); adopt: the
121
+ packs Step 0.4(c) confirmed, revised by this step's own `recommendPacks`
122
+ comparison if it changed anything); `hasCustomSkills` is `false` for a fresh
123
+ bootstrap (nothing has authored a skill yet) and, for adopt mode, whatever
124
+ Step 0's survey found beyond the baseline's own known skill names and any
125
+ already-installed pack's skills (e.g. the `github` pack's three);
126
+ `dependencies` is the key names under `dependencies` and `devDependencies`
127
+ in the project's root `package.json` (the same read in both modes; a fresh
128
+ bootstrap carries only the baseline's own devDependencies, so no SDK plugin
129
+ is pre-selected there, and a monorepo whose SDK dependency sits only in a
130
+ workspace package is not pre-selected either -- the user can still pick it). Call
131
+ `recommendPlugins(answers, context)` from `plugin-map.ts`
132
+ (alongside this file, same copy mechanism as `pack-map.ts`) with the
133
+ now-confirmed `InterviewAnswers`, and offer all ten in **one**
134
+ `AskUserQuestion` call holding three multi-select questions (a question
135
+ takes at most four options, so split the list in its fixed order: entries
136
+ 1-4, 5-7, 8-10), each option pre-selected per its entry's `recommended`
137
+ boolean with its `because` shown as the evidence -- same
138
+ "visible reasoning" principle as every other inference in this skill.
139
+
140
+ Write every confirmed `true` entry into `.claude/settings.json`'s
141
+ `enabledPlugins` (`{"<id>": true}` per entry, e.g.
142
+ `"context7@claude-plugins-official": true`) -- additive and entry-by-entry,
143
+ same discipline as the CLI's own `mergeSettingsHooks` (which owns the
144
+ `hooks` block entry-by-entry, as opposed to `mergeSettingsTopLevel`'s
145
+ whole-key-at-a-time semantics): never remove or flip an entry the project
146
+ already sets explicitly (an existing `false` is a decision the user made,
147
+ not an oversight to correct), and never touch `.claude/settings.local.json`
148
+ or the user's own `~/.claude/settings.json` scope. `claude-plugins-official`
149
+ is a built-in marketplace, so no `extraKnownMarketplaces` entry is needed
150
+ for any of the ten.
151
+
152
+ **Committing this entry does not install the plugin for anyone.** A
153
+ project-scope `enabledPlugins: true` with no local install produces no
154
+ folder-trust auto-prompt -- Claude Code's `/plugin` Errors tab instead shows
155
+ "enabled in project settings but isn't installed here" until someone runs
156
+ the install by hand. So for every newly-`true` entry, print the exact
157
+ follow-up command in Step 6's report: `claude plugin install <id> --scope
158
+ project` (or `/plugin install <id>` inside a running session) -- the user,
159
+ and every collaborator who pulls this change, still has to run it once. Do
160
+ the same for each recommendation's `prerequisites` (the
161
+ `typescript-language-server` binary, or the Python version a plugin names) --
162
+ print them as
163
+ follow-ups, never attempt to install them.
164
+
165
+ Nothing else is touched. A project file the user didn't approve a change to
166
+ stays exactly as it was.
167
+
168
+ Run `pnpm verify` (fresh) or the project's own equivalent (adopt) after
169
+ Round 1's edits to confirm the tailored result still passes before moving to
170
+ Round 2.
@@ -9,6 +9,29 @@
9
9
  * `pack-map.ts` applies to `templates/packs/` bundles. A stored, unit-tested
10
10
  * module rather than a judgment made afresh each run, so the same interview
11
11
  * answers and context always produce the same recommendation.
12
+ *
13
+ * Evaluated and deliberately not offered, recorded so they are not proposed
14
+ * again:
15
+ *
16
+ * - `plugin-dev` -- only for projects that author Claude Code plugins;
17
+ * m3l-groundwork itself enables it, a bootstrapped TypeScript project has
18
+ * no plugin to build, and `skill-creator` covers skill authoring.
19
+ * - `code-review` -- duplicates the built-in `/code-review` and the optional
20
+ * `github` pack's PR-review Action.
21
+ * - `code-simplifier` -- duplicates the built-in `/simplify`; its
22
+ * unrestricted-tools agent is blocked by the baseline's hub-and-spoke
23
+ * write guard on `src/`/`tests/` anyway.
24
+ * - `feature-dev` -- duplicates plan mode, `starting-work` and the TDD loop;
25
+ * its `code-reviewer` competes with the baseline's.
26
+ * - `pr-review-toolkit` -- overlaps the baseline's `code-reviewer` and
27
+ * `silent-failure-hunter` and the optional `quality` pack's
28
+ * `type-design-analyzer`.
29
+ * - `hookify` -- runs `python3` on every tool call, and keeps its rules in
30
+ * gitignored `.local.md` files that bypass the graded, committed hooks and
31
+ * the hook cap.
32
+ * - `remember` -- third-party under a source-available license that forbids
33
+ * modification and redistribution; it sends transcripts to `claude -p`,
34
+ * writes `.remember/`, and can push memory to a remote.
12
35
  */
13
36
  import type { InterviewAnswers } from "./kind-facet-map.js";
14
37
 
@@ -29,12 +52,18 @@ export interface PluginRecommendation {
29
52
 
30
53
  /**
31
54
  * What `/customize` knows beyond the interview answers by the time it
32
- * recommends plugins: which packs the user chose, and whether the project
33
- * authors its own skills. Both are optional; an absent field reads as "no".
55
+ * recommends plugins: which packs the user chose, whether the project
56
+ * authors its own skills, and which packages it depends on. All are
57
+ * optional; an absent field reads as "no" (or "none").
34
58
  */
35
59
  export interface PluginRecommendationContext {
36
60
  readonly chosenPacks?: readonly string[];
37
61
  readonly hasCustomSkills?: boolean;
62
+ /**
63
+ * Package names from the project's `package.json` `dependencies` and
64
+ * `devDependencies` combined. Absent reads as none.
65
+ */
66
+ readonly dependencies?: readonly string[];
38
67
  }
39
68
 
40
69
  function pluginId(name: string): string {
@@ -163,10 +192,80 @@ function recommendSecurityGuidance(
163
192
  };
164
193
  }
165
194
 
195
+ /**
196
+ * `claude-security` is the on-demand counterpart to `security-guidance`: it
197
+ * runs only when invoked, so unlike that plugin's per-turn Stop-hook LLM
198
+ * review it costs nothing while idle, and is pre-selected for every project.
199
+ */
200
+ function recommendClaudeSecurity(): PluginRecommendation {
201
+ return {
202
+ id: pluginId("claude-security"),
203
+ recommended: true,
204
+ because:
205
+ "it runs on demand -- only when invoked -- so it costs nothing while " +
206
+ "idle, unlike security-guidance's Stop-hook LLM review on every " +
207
+ "turn. When run it scans the repo or a diff, challenges each finding " +
208
+ "before reporting it, and can draft patches. Its post-push scan tip " +
209
+ "can be turned off with CLAUDE_SECURITY_SCAN_TIP=off.",
210
+ prerequisites: ["Python 3.9+ on PATH"],
211
+ };
212
+ }
213
+
214
+ function dependsOn(
215
+ context: PluginRecommendationContext | undefined,
216
+ packageName: string,
217
+ ): boolean {
218
+ return context?.dependencies?.includes(packageName) ?? false;
219
+ }
220
+
221
+ /**
222
+ * `agent-sdk-dev` scaffolds and verifies Claude Agent SDK applications, so it
223
+ * is pre-selected only when the project depends on
224
+ * `@anthropic-ai/claude-agent-sdk`.
225
+ */
226
+ function recommendAgentSdkDev(
227
+ context: PluginRecommendationContext | undefined,
228
+ ): PluginRecommendation {
229
+ const recommended = dependsOn(context, "@anthropic-ai/claude-agent-sdk");
230
+ return {
231
+ id: pluginId("agent-sdk-dev"),
232
+ recommended,
233
+ because: recommended
234
+ ? "the project depends on @anthropic-ai/claude-agent-sdk, and this " +
235
+ "plugin scaffolds new SDK apps (/new-sdk-app) and verifies existing " +
236
+ "ones against the SDK's current guidance."
237
+ : "only useful to a project that builds on the Claude Agent SDK; " +
238
+ "@anthropic-ai/claude-agent-sdk is not among this project's " +
239
+ "dependencies.",
240
+ };
241
+ }
242
+
243
+ /**
244
+ * `mcp-server-dev` carries skills for building MCP servers and MCP apps and
245
+ * for MCPB packaging, so it is pre-selected only when the project depends on
246
+ * `@modelcontextprotocol/sdk`.
247
+ */
248
+ function recommendMcpServerDev(
249
+ context: PluginRecommendationContext | undefined,
250
+ ): PluginRecommendation {
251
+ const recommended = dependsOn(context, "@modelcontextprotocol/sdk");
252
+ return {
253
+ id: pluginId("mcp-server-dev"),
254
+ recommended,
255
+ because: recommended
256
+ ? "the project depends on @modelcontextprotocol/sdk, and this plugin " +
257
+ "brings skills for building MCP servers and MCP apps and for " +
258
+ "packaging them as MCPB bundles."
259
+ : "only useful to a project that builds on the MCP SDK; " +
260
+ "@modelcontextprotocol/sdk is not among this project's " +
261
+ "dependencies.",
262
+ };
263
+ }
264
+
166
265
  /**
167
266
  * `skill-creator` scaffolds, evaluates and iterates on skills. The baseline's
168
- * own skills are maintained by this repo, not by the project, so it only
169
- * pays for itself in a project that writes skills of its own.
267
+ * own skills are maintained upstream by m3l-groundwork, not by the project,
268
+ * so it only pays for itself in a project that writes skills of its own.
170
269
  */
171
270
  function recommendSkillCreator(
172
271
  context: PluginRecommendationContext | undefined,
@@ -213,12 +312,31 @@ function recommendClaudeCodeSetup(): PluginRecommendation {
213
312
 
214
313
  /**
215
314
  * Every built-in marketplace plugin's recommendation for the given interview
216
- * answers and context, always the same seven in the same order: `context7`,
217
- * `typescript-lsp` and `claude-md-management` unconditionally; `github` when
218
- * the `github` pack was chosen; `security-guidance` for a `service` or a
219
- * `thorough` CI depth; `skill-creator` when the project has its own skills;
220
- * `claude-code-setup` never. A pure function -- identical inputs always
221
- * produce an equal result.
315
+ * answers and context, always the same ten in the same order. Pre-selected
316
+ * unconditionally: `context7`, `typescript-lsp`, `claude-md-management` and
317
+ * `claude-security`. Conditionally: `github` when the `github` pack was
318
+ * chosen; `security-guidance` for a `service` or a `thorough` CI depth;
319
+ * `skill-creator` when the project has its own skills; `agent-sdk-dev` and
320
+ * `mcp-server-dev` when the project depends on the Claude Agent SDK or the
321
+ * MCP SDK respectively. Never: `claude-code-setup`. A pure function --
322
+ * identical inputs always produce an equal result.
323
+ *
324
+ * @example
325
+ * ```ts
326
+ * import { recommendPlugins } from "./plugin-map.js";
327
+ *
328
+ * const recommendations = recommendPlugins(
329
+ * {
330
+ * kind: "service",
331
+ * runtime: "node",
332
+ * testsMandatory: true,
333
+ * ciDepth: "standard",
334
+ * keepAgents: ["code-reviewer"],
335
+ * },
336
+ * { chosenPacks: ["github"], dependencies: ["@modelcontextprotocol/sdk"] },
337
+ * );
338
+ * const preselected = recommendations.filter((r) => r.recommended);
339
+ * ```
222
340
  */
223
341
  export function recommendPlugins(
224
342
  answers: InterviewAnswers,
@@ -230,7 +348,10 @@ export function recommendPlugins(
230
348
  recommendClaudeMdManagement(),
231
349
  recommendGithub(context),
232
350
  recommendSecurityGuidance(answers),
351
+ recommendClaudeSecurity(),
233
352
  recommendSkillCreator(context),
234
353
  recommendClaudeCodeSetup(),
354
+ recommendAgentSdkDev(context),
355
+ recommendMcpServerDev(context),
235
356
  ];
236
357
  }
@@ -3,7 +3,7 @@ name: Explore
3
3
  description: Fast read-only search agent for locating and understanding code. Use it to find files by pattern (e.g. "src/components/**/*.tsx"), grep for symbols or keywords (e.g. "API endpoints"), answer "where is X defined / which files reference Y," or read a bounded set of files in full when the caller says so. Do NOT use it for code review, design-doc auditing, or open-ended cross-file consistency judgment across the whole repo — those need a specialized reviewer. When calling, specify search breadth ("quick" for a single targeted lookup, "medium" for moderate exploration, "very thorough" to search across multiple locations and naming conventions) and say explicitly if the task requires reading matched files in full rather than excerpting them.
4
4
  tools: Read, Grep, Glob, Bash, WebSearch, WebFetch
5
5
  disallowedTools: Agent
6
- model: claude-haiku-4-5
6
+ model: claude-haiku-5-5
7
7
  maxTurns: 40
8
8
  color: cyan
9
9
  ---
@@ -97,8 +97,9 @@ if (isEntryPoint()) {
97
97
  process.stderr.write(
98
98
  `Blocked: CommonJS construct(s) found (this project is ESM only):\n` +
99
99
  hits.map((h) => ` - ${h}`).join("\n") +
100
- `\nUse ESM equivalents: import/export, import.meta.url, ` +
101
- `fileURLToPath(import.meta.url).\n`,
100
+ `\nUse ESM equivalents: import/export, import.meta.dirname and ` +
101
+ `import.meta.filename (stable on Node 24+) in place of __dirname and ` +
102
+ `__filename, or fileURLToPath(import.meta.url).\n`,
102
103
  );
103
104
  process.exit(2);
104
105
  }