@monte3l/groundwork 1.0.0-rc.2 → 1.0.0-rc.4
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/README.md +13 -5
- package/bin/m3l-groundwork.mjs +36 -2
- package/dist/assets.d.ts +10 -0
- package/dist/assets.js +14 -0
- package/dist/baseline-stage.d.ts +173 -0
- package/dist/baseline-stage.js +215 -0
- package/dist/caps.d.ts +4 -1
- package/dist/caps.js +17 -3
- package/dist/conflicts.d.ts +23 -2
- package/dist/conflicts.js +89 -11
- package/dist/customize-paths.d.ts +92 -0
- package/dist/customize-paths.js +115 -0
- package/dist/emit.d.ts +50 -1
- package/dist/emit.js +121 -13
- package/dist/fatal.d.ts +70 -0
- package/dist/fatal.js +132 -0
- package/dist/format-error.d.ts +113 -0
- package/dist/format-error.js +553 -0
- package/dist/fs-guard.d.ts +142 -0
- package/dist/fs-guard.js +222 -0
- package/dist/git.js +10 -1
- package/dist/harness/conformance.js +2 -0
- package/dist/harness/frontmatter.js +2 -13
- package/dist/harness/grade.js +37 -8
- package/dist/harness/rules.d.ts +20 -3
- package/dist/harness/rules.js +108 -6
- package/dist/harness/types.d.ts +13 -0
- package/dist/harness/types.js +3 -0
- package/dist/inventory.d.ts +77 -3
- package/dist/inventory.js +103 -12
- package/dist/jsonc.d.ts +49 -2
- package/dist/jsonc.js +140 -7
- package/dist/main.d.ts +62 -3
- package/dist/main.js +574 -67
- package/dist/merge-json.d.ts +47 -4
- package/dist/merge-json.js +120 -8
- package/dist/mode.js +12 -2
- package/dist/pack-stage.d.ts +210 -0
- package/dist/pack-stage.js +287 -0
- package/dist/packs.d.ts +21 -13
- package/dist/packs.js +241 -28
- package/dist/palette.d.ts +23 -0
- package/dist/palette.js +22 -0
- package/dist/plugin.d.ts +122 -6
- package/dist/plugin.js +687 -47
- package/dist/report.d.ts +21 -2
- package/dist/report.js +93 -5
- package/dist/staging.d.ts +176 -0
- package/dist/staging.js +375 -0
- package/dist/survey/fs-walk.d.ts +34 -2
- package/dist/survey/fs-walk.js +70 -5
- package/dist/survey/internal/blocked-path.d.ts +27 -0
- package/dist/survey/internal/blocked-path.js +129 -0
- package/dist/survey/internal/package-json.d.ts +13 -0
- package/dist/survey/internal/package-json.js +37 -0
- package/dist/survey/internal/read-guard.d.ts +178 -0
- package/dist/survey/internal/read-guard.js +276 -0
- package/dist/survey/survey-docs.d.ts +17 -2
- package/dist/survey/survey-docs.js +61 -21
- package/dist/survey/survey-harness.d.ts +20 -2
- package/dist/survey/survey-harness.js +87 -46
- package/dist/survey/survey-shape.d.ts +17 -2
- package/dist/survey/survey-shape.js +60 -46
- package/dist/survey/survey-toolchain.d.ts +16 -1
- package/dist/survey/survey-toolchain.js +69 -66
- package/dist/survey/survey.js +6 -4
- package/dist/survey/types.d.ts +79 -0
- package/dist/survey/types.js +2 -6
- package/dist/term.d.ts +80 -0
- package/dist/term.js +145 -0
- package/dist/tokens.js +2 -0
- package/dist/toolchain/conformance.js +2 -0
- package/dist/toolchain/grade.js +26 -8
- package/dist/toolchain/rules.d.ts +13 -4
- package/dist/toolchain/rules.js +16 -0
- package/dist/toolchain/tsconfig-chain.d.ts +2 -0
- package/dist/toolchain/tsconfig-chain.js +32 -8
- package/dist/toolchain/types.d.ts +16 -4
- package/dist/toolchain/types.js +3 -0
- package/package.json +4 -3
- package/plugin/skills/customize/SKILL.md +292 -18
- package/plugin/src/domain-map.ts +39 -14
- package/plugin/src/index.ts +5 -1
- package/plugin/src/kind-facet-map.ts +25 -8
- package/plugin/src/pack-map.ts +147 -25
- package/plugin/src/plugin-map.ts +236 -0
- package/templates/core/.claude/agents/Explore.md +0 -1
- package/templates/core/.claude/agents/code-implementer.md +4 -4
- package/templates/core/.claude/agents/code-reviewer.md +4 -4
- package/templates/core/.claude/agents/silent-failure-hunter.md +2 -2
- package/templates/core/.claude/agents/test-author.md +7 -5
- package/templates/core/.claude/hooks/guard-branch-isolation.mjs +56 -10
- package/templates/core/.claude/hooks/guard-double-background.mjs +13 -8
- package/templates/core/.claude/hooks/guard-git-push-signed.mjs +12 -9
- package/templates/core/.claude/hooks/guard-hub-src-writes.mjs +1890 -38
- package/templates/core/.claude/hooks/guard-js-extension.mjs +2 -1
- package/templates/core/.claude/hooks/guard-no-commonjs.mjs +7 -5
- package/templates/core/.claude/hooks/guard-secret-writes.mjs +7 -5
- package/templates/core/.claude/hooks/inject-decision-gate.mjs +12 -9
- package/templates/core/.claude/hooks/post-edit-verify.mjs +276 -98
- package/templates/core/.claude/rules/agent-dispatch.md +7 -0
- package/templates/core/.claude/rules/tests.md +2 -2
- package/templates/core/.claude/settings.json +5 -0
- package/templates/core/.claude/skills/finishing-work/SKILL.md +58 -10
- package/templates/core/.claude/skills/harness-guidance/SKILL.md +16 -7
- package/templates/core/.claude/skills/starting-work/SKILL.md +5 -0
- package/templates/core/.claude/skills/triaging-ci/SKILL.md +9 -8
- package/templates/core/.claude/skills/typescript-guidance/SKILL.md +137 -20
- package/templates/core/.claude/skills/typescript-guidance/references/area-catalog.md +135 -0
- package/templates/core/.claude/skills/typescript-guidance/references/tooling-sources.md +113 -0
- package/templates/core/.claude/skills/writing-commits/SKILL.md +2 -2
- package/templates/core/.github/dependabot.yml +18 -0
- package/templates/core/.github/workflows/ci.yml +15 -15
- package/templates/core/.github/workflows/dependency-review.yml +2 -2
- package/templates/core/.github/workflows/security-audit.yml +10 -4
- package/templates/core/.prettierignore +4 -0
- package/templates/core/CLAUDE.md +53 -3
- package/templates/core/README.md +30 -10
- package/templates/core/_gitignore +17 -0
- package/templates/core/bin/check-exports.mjs +11 -5
- package/templates/core/bin/lib/agent-roster.mjs +1 -1
- package/templates/core/bin/lib/frontmatter.mjs +4 -2
- package/templates/core/bin/lib/harness-rules.mjs +169 -20
- package/templates/core/bin/lib/protected-paths.mjs +93 -5
- package/templates/core/bin/lib/toolchain-rules.mjs +187 -26
- package/templates/core/bin/lib/verify-steps.mjs +29 -11
- package/templates/core/bin/verify.mjs +12 -0
- package/templates/core/eslint.config.js +5 -0
- package/templates/core/package.json +7 -7
- package/templates/core/vitest.config.ts +8 -1
- package/templates/packs/README.md +44 -24
- package/templates/packs/github/files/.claude/skills/reviewing-dependabot-prs/SKILL.md +133 -0
- package/templates/packs/github/files/.claude/skills/triaging-scan-alerts/SKILL.md +88 -0
- package/templates/packs/github/files/.claude/skills/watching-pr-checks/SKILL.md +94 -0
- package/templates/packs/github/files/.github/workflows/claude-pr-review.yml +267 -0
- package/templates/packs/github/files/.github/workflows/claude.yml +106 -0
- package/templates/packs/github/pack.json +19 -0
- package/templates/packs/harness-extras/files/.claude/hooks/guard-readonly-bash.mjs +1122 -65
- package/templates/packs/harness-extras/files/.claude/hooks/reinject-compact-handoff.mjs +147 -13
- package/templates/packs/{statusline → harness-extras}/files/.claude/hooks/statusline.mjs +6 -2
- package/templates/packs/harness-extras/files/.claude/hooks/write-compact-handoff.mjs +227 -44
- package/templates/packs/harness-extras/pack.json +18 -13
- package/templates/packs/publishing/files/.changeset/README.md +25 -0
- package/templates/packs/publishing/files/.changeset/config.json +7 -0
- package/templates/packs/publishing/files/.github/release-tools/package.json +9 -0
- package/templates/packs/publishing/files/.github/workflows/release.yml +295 -0
- package/templates/packs/publishing/files/REUSE.toml +28 -0
- package/templates/packs/publishing/files/bin/check-dts-deps.mjs +234 -0
- package/templates/packs/publishing/files/bin/check-license-headers.mjs +217 -0
- package/templates/packs/publishing/files/bin/check-publish-version.mjs +149 -0
- package/templates/packs/publishing/files/bin/lib/npm-publish-args.mjs +86 -0
- package/templates/packs/publishing/files/bin/pnpm-publish-shim.mjs +86 -0
- package/templates/packs/publishing/pack.json +41 -0
- package/templates/packs/{harness-extras → quality}/files/bin/check-file-budget.mjs +6 -4
- package/templates/packs/quality/pack.json +29 -0
- package/templates/packs/supply-chain/files/.github/workflows/gitleaks.yml +52 -0
- package/templates/packs/supply-chain/files/.github/workflows/scorecard.yml +48 -0
- package/templates/packs/supply-chain/files/.gitleaks.toml +2 -0
- package/templates/packs/supply-chain/pack.json +19 -0
- package/templates/packs/worktrees/files/.claude/hooks/ensure-worktree-deps.mjs +283 -0
- package/templates/packs/worktrees/files/.claude/hooks/guard-worktree-only.mjs +245 -0
- package/templates/packs/worktrees/files/.claude/hooks/repair-core-bare.mjs +335 -0
- package/templates/packs/worktrees/files/.claude/skills/working-in-worktrees/SKILL.md +139 -0
- package/templates/packs/worktrees/files/.worktreeinclude +11 -0
- package/templates/packs/worktrees/pack.json +64 -0
- package/templates/packs/statusline/pack.json +0 -31
- /package/templates/packs/{statusline → harness-extras}/files/.claude/hooks/statusline-layout.mjs +0 -0
- /package/templates/packs/{statusline → harness-extras}/files/.claude/hooks/subagent-statusline.mjs +0 -0
- /package/templates/packs/{harness-extras → quality}/files/.claude/agents/type-design-analyzer.md +0 -0
- /package/templates/packs/{harness-extras → quality}/files/bin/file-budget-baseline.json +0 -0
package/plugin/src/pack-map.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: Copyright the m3l-groundwork contributors
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* The pack recommendation table `/customize` reads in Step 1 to pre-select
|
|
3
6
|
* which `templates/packs/` pack(s) to offer, with the evidence shown
|
|
@@ -16,50 +19,169 @@ export interface PackRecommendation {
|
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
/**
|
|
19
|
-
* `harness-extras`'s
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
22
|
+
* `harness-extras`'s remaining artifacts (the compaction-handoff hook pair
|
|
23
|
+
* and a read-only Bash guard) are harness-level, not domain-level -- they
|
|
24
|
+
* apply to any TypeScript project regardless of what it's building. The
|
|
25
|
+
* type-design-analyzer agent and the file-budget gate it once also carried
|
|
26
|
+
* now live in the separate `quality` pack. Its folded-in
|
|
27
|
+
* statusLine scripts read only the stdin payload, `.git/HEAD` (via
|
|
28
|
+
* `node:fs`, never a `git` subprocess) and `os.freemem()`/`os.totalmem()`,
|
|
29
|
+
* and `statusLine` is the only documented surface carrying live
|
|
30
|
+
* `context_window.used_percentage`: no hook event receives token or context
|
|
31
|
+
* data, so "when to compact" can live nowhere else.
|
|
23
32
|
*/
|
|
24
33
|
function recommendHarnessExtras(): PackRecommendation {
|
|
25
34
|
return {
|
|
26
35
|
name: "harness-extras",
|
|
27
36
|
recommended: true,
|
|
28
37
|
because:
|
|
29
|
-
"its
|
|
30
|
-
"
|
|
31
|
-
"
|
|
38
|
+
"its compaction-handoff hooks (which carry work across a context " +
|
|
39
|
+
"compaction) and read-only Bash guard are harness-level, not tied " +
|
|
40
|
+
"to any particular project kind; its " +
|
|
41
|
+
"statusLine is the only surface that exposes live context-window " +
|
|
42
|
+
"pressure -- no hook event receives token data -- and reads only the " +
|
|
43
|
+
"stdin payload plus local git and memory state. It does occupy five " +
|
|
44
|
+
"terminal rows (session, model, context, quota, work).",
|
|
32
45
|
};
|
|
33
46
|
}
|
|
34
47
|
|
|
35
48
|
/**
|
|
36
|
-
* `
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
49
|
+
* `github`'s workflow assumes only GitHub, which this baseline's whole
|
|
50
|
+
* toolchain (CI, Dependency Review, rulesets) already does -- nothing about
|
|
51
|
+
* any project kind. Beyond the mention-mode Action (`claude.yml`) it ships a
|
|
52
|
+
* second workflow, `claude-pr-review.yml`, which posts an automated Claude
|
|
53
|
+
* review comment on every PR, plus three GitHub-operations skills:
|
|
54
|
+
* `reviewing-dependabot-prs` (classifies and batch-merges open Dependabot
|
|
55
|
+
* PRs), `triaging-scan-alerts` (triages open code-scanning alerts to
|
|
56
|
+
* file:line) and `watching-pr-checks`. It is not zero-setup: it adds two
|
|
57
|
+
* GitHub Actions workflows and needs an auth secret the pack cannot create
|
|
58
|
+
* itself.
|
|
41
59
|
*/
|
|
42
|
-
function
|
|
60
|
+
function recommendGithub(): PackRecommendation {
|
|
43
61
|
return {
|
|
44
|
-
name: "
|
|
62
|
+
name: "github",
|
|
45
63
|
recommended: true,
|
|
46
64
|
because:
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
65
|
+
"the baseline's toolchain already assumes GitHub (CI, Dependency " +
|
|
66
|
+
"Review, rulesets), so a Claude Code Action integration applies to " +
|
|
67
|
+
"any project kind. Alongside the mention-mode Action it adds a " +
|
|
68
|
+
"second workflow (claude-pr-review.yml) that posts an automated " +
|
|
69
|
+
"Claude review comment on every PR, plus skills for classifying and " +
|
|
70
|
+
"batch-merging open Dependabot PRs (reviewing-dependabot-prs), " +
|
|
71
|
+
"triaging open code-scanning alerts to file:line " +
|
|
72
|
+
"(triaging-scan-alerts) and watching a PR's checks " +
|
|
73
|
+
"(watching-pr-checks). It does add two GitHub Actions workflows and " +
|
|
74
|
+
"needs an auth secret configured by hand -- the pack cannot create it.",
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* `publishing` is the one kind-scoped pack: a release pipeline (a changesets
|
|
80
|
+
* version PR, then a staged, provenance-attested npm publish via trusted
|
|
81
|
+
* publishing) plus SPDX/REUSE license headers. Secret scanning and OpenSSF
|
|
82
|
+
* Scorecard are not part of it -- they are the separate `supply-chain` pack,
|
|
83
|
+
* since they apply to any repo, published or not. A `library` or `cli`
|
|
84
|
+
* typically publishes an npm package; a
|
|
85
|
+
* `frontend` or `service` is typically deployed instead, so it is not
|
|
86
|
+
* pre-selected there -- but it stays on offer. It is fresh-mode only and
|
|
87
|
+
* needs one-time npm and GitHub setup the pack cannot perform itself.
|
|
88
|
+
*/
|
|
89
|
+
function recommendPublishing(answers: InterviewAnswers): PackRecommendation {
|
|
90
|
+
const recommended = answers.kind === "library" || answers.kind === "cli";
|
|
91
|
+
return {
|
|
92
|
+
name: "publishing",
|
|
93
|
+
recommended,
|
|
94
|
+
because: recommended
|
|
95
|
+
? "a library or CLI typically ships an npm package, so this pack's " +
|
|
96
|
+
"release pipeline applies: a changesets version PR, then a staged, " +
|
|
97
|
+
"provenance-attested npm publish via trusted publishing -- plus " +
|
|
98
|
+
"SPDX/REUSE license headers. The repository-hygiene scans that " +
|
|
99
|
+
"apply to any project, published or not, are the separate " +
|
|
100
|
+
"supply-chain pack. It is fresh-mode only and needs one-time npm and GitHub setup (the " +
|
|
101
|
+
"trusted publisher, the release credentials) the pack cannot do " +
|
|
102
|
+
"itself."
|
|
103
|
+
: "a frontend or service is typically deployed rather than published " +
|
|
104
|
+
"to a package registry, so a changesets/npm release pipeline is not " +
|
|
105
|
+
"recommended by default -- it is still available if this project " +
|
|
106
|
+
"does publish an npm package.",
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* `worktrees` is opt-in for every project kind: rather than adding a
|
|
112
|
+
* nicety on top of the existing workflow, it changes the workflow itself --
|
|
113
|
+
* every `src/`/`tests/` write must happen inside a git worktree, not the
|
|
114
|
+
* main checkout. That is a day-to-day process decision for the maintainer,
|
|
115
|
+
* not something any interview answer can infer, so it is never pre-selected
|
|
116
|
+
* -- but it stays on offer.
|
|
117
|
+
*/
|
|
118
|
+
function recommendWorktrees(): PackRecommendation {
|
|
119
|
+
return {
|
|
120
|
+
name: "worktrees",
|
|
121
|
+
recommended: false,
|
|
122
|
+
because:
|
|
123
|
+
"it changes the day-to-day workflow rather than adding a nicety on " +
|
|
124
|
+
"top of it: every src/ and tests/ write has to happen inside a git " +
|
|
125
|
+
"worktree instead of the main checkout. That is a process choice " +
|
|
126
|
+
"for whoever works in this repo, not something any project kind " +
|
|
127
|
+
"implies, so it is opt-in -- still available if parallel, isolated " +
|
|
128
|
+
"worktree sessions are how this project wants to work.",
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* `supply-chain` is the half carved out of `publishing`: gitleaks secret
|
|
134
|
+
* scanning and an OpenSSF Scorecard run. Neither depends on shipping to a
|
|
135
|
+
* registry -- a deployed frontend or service leaks a secret or drifts on
|
|
136
|
+
* Scorecard's checks just as easily as a published library -- so it is
|
|
137
|
+
* recommended for every project kind. Both are CI workflows only, so it
|
|
138
|
+
* installs into an already-established project without touching its code.
|
|
139
|
+
*/
|
|
140
|
+
function recommendSupplyChain(): PackRecommendation {
|
|
141
|
+
return {
|
|
142
|
+
name: "supply-chain",
|
|
143
|
+
recommended: true,
|
|
144
|
+
because:
|
|
145
|
+
"secret scanning (gitleaks) and an OpenSSF Scorecard run are useful " +
|
|
146
|
+
"to any project on GitHub, published or not, and install into an " +
|
|
147
|
+
"already-established project without touching its code.",
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* `quality` is the half carved out of `harness-extras`: a per-file size
|
|
153
|
+
* ratchet (`check-file-budget`) over `src/` and `tests/`, and the
|
|
154
|
+
* type-design-analyzer agent reviewing exported symbols' type design. Both
|
|
155
|
+
* are language-level -- they apply to any TypeScript project whatever it is
|
|
156
|
+
* building -- so it is recommended for every project kind.
|
|
157
|
+
*/
|
|
158
|
+
function recommendQuality(): PackRecommendation {
|
|
159
|
+
return {
|
|
160
|
+
name: "quality",
|
|
161
|
+
recommended: true,
|
|
162
|
+
because:
|
|
163
|
+
"its per-file size ratchet (check-file-budget) stops src/ and tests/ " +
|
|
164
|
+
"files growing unchecked and its type-design-analyzer agent reviews " +
|
|
165
|
+
"the type design of exported symbols -- both are language-level, not " +
|
|
166
|
+
"tied to any project kind.",
|
|
51
167
|
};
|
|
52
168
|
}
|
|
53
169
|
|
|
54
170
|
/**
|
|
55
|
-
* Every pack's recommendation for the given interview answers.
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* change here.
|
|
171
|
+
* Every pack's recommendation for the given interview answers. Only
|
|
172
|
+
* `publishing` varies by `answers.kind` today; `harness-extras`, `github`,
|
|
173
|
+
* `supply-chain` and `quality` are recommended for every kind, and
|
|
174
|
+
* `worktrees` is opt-in for every kind.
|
|
60
175
|
*/
|
|
61
176
|
export function recommendPacks(
|
|
62
|
-
|
|
177
|
+
answers: InterviewAnswers,
|
|
63
178
|
): PackRecommendation[] {
|
|
64
|
-
return [
|
|
179
|
+
return [
|
|
180
|
+
recommendHarnessExtras(),
|
|
181
|
+
recommendGithub(),
|
|
182
|
+
recommendPublishing(answers),
|
|
183
|
+
recommendWorktrees(),
|
|
184
|
+
recommendSupplyChain(),
|
|
185
|
+
recommendQuality(),
|
|
186
|
+
];
|
|
65
187
|
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: Copyright the m3l-groundwork contributors
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The plugin recommendation table `/customize` reads in Step 3 to pre-select
|
|
6
|
+
* which built-in `claude-plugins-official` marketplace plugin(s) to offer,
|
|
7
|
+
* with the evidence shown alongside each recommendation -- the same
|
|
8
|
+
* "inference happens once, visibly, with its reasoning attached" principle
|
|
9
|
+
* `pack-map.ts` applies to `templates/packs/` bundles. A stored, unit-tested
|
|
10
|
+
* module rather than a judgment made afresh each run, so the same interview
|
|
11
|
+
* answers and context always produce the same recommendation.
|
|
12
|
+
*/
|
|
13
|
+
import type { InterviewAnswers } from "./kind-facet-map.js";
|
|
14
|
+
|
|
15
|
+
/** The marketplace every plugin in this table ships from. */
|
|
16
|
+
const MARKETPLACE = "claude-plugins-official";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* One plugin's recommendation: its fully-qualified `name@marketplace` id,
|
|
20
|
+
* whether `/customize` pre-selects it, the reasoning shown to the user, and
|
|
21
|
+
* any host prerequisite the plugin cannot install for itself.
|
|
22
|
+
*/
|
|
23
|
+
export interface PluginRecommendation {
|
|
24
|
+
readonly id: string;
|
|
25
|
+
readonly recommended: boolean;
|
|
26
|
+
readonly because: string;
|
|
27
|
+
readonly prerequisites?: readonly string[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 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".
|
|
34
|
+
*/
|
|
35
|
+
export interface PluginRecommendationContext {
|
|
36
|
+
readonly chosenPacks?: readonly string[];
|
|
37
|
+
readonly hasCustomSkills?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function pluginId(name: string): string {
|
|
41
|
+
return `${name}@${MARKETPLACE}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* `context7` is not optional for a project bootstrapped from this baseline:
|
|
46
|
+
* `templates/core/.claude/agents/code-implementer.md` declares
|
|
47
|
+
* `mcpServers: [context7]`, and an agent's MCP grant names a server that
|
|
48
|
+
* some installed source must actually provide -- without the plugin enabled
|
|
49
|
+
* the grant resolves to nothing and fails silently (the gap the
|
|
50
|
+
* `agent-mcp-source` harness rubric rule flags). It works anonymously; an
|
|
51
|
+
* API key only raises rate limits.
|
|
52
|
+
*/
|
|
53
|
+
function recommendContext7(): PluginRecommendation {
|
|
54
|
+
return {
|
|
55
|
+
id: pluginId("context7"),
|
|
56
|
+
recommended: true,
|
|
57
|
+
because:
|
|
58
|
+
"the baseline's code-implementer agent declares mcpServers: " +
|
|
59
|
+
"[context7], so without this plugin enabled that grant silently " +
|
|
60
|
+
"resolves to nothing -- the agent loses the library-docs lookup it " +
|
|
61
|
+
"was written to rely on. It works anonymously; a CONTEXT7_API_KEY is " +
|
|
62
|
+
"optional and only raises the rate limit.",
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Every project this bootstrapper produces is TypeScript, so a TypeScript
|
|
68
|
+
* language server gives Claude go-to-definition, references and live
|
|
69
|
+
* diagnostics on any of them. The plugin wires the server up but does not
|
|
70
|
+
* ship the binary itself -- it must already be on `PATH`.
|
|
71
|
+
*/
|
|
72
|
+
function recommendTypescriptLsp(): PluginRecommendation {
|
|
73
|
+
return {
|
|
74
|
+
id: pluginId("typescript-lsp"),
|
|
75
|
+
recommended: true,
|
|
76
|
+
because:
|
|
77
|
+
"every project this bootstrapper emits is TypeScript, so a language " +
|
|
78
|
+
"server gives Claude real go-to-definition, find-references and " +
|
|
79
|
+
"compiler diagnostics instead of text search, whatever the project " +
|
|
80
|
+
"kind. The plugin only wires the server up -- the binary itself has " +
|
|
81
|
+
"to be installed separately.",
|
|
82
|
+
prerequisites: [
|
|
83
|
+
"the typescript-language-server binary on PATH (for example via " +
|
|
84
|
+
"`pnpm add -g typescript-language-server typescript`)",
|
|
85
|
+
],
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The baseline ships a `CLAUDE.md` that is only useful while it stays true.
|
|
91
|
+
* `harness-guidance` refreshes the harness against upstream in periodic
|
|
92
|
+
* sweeps; `claude-md-management` covers the other half -- keeping the file
|
|
93
|
+
* in step with what the project itself learns between those sweeps.
|
|
94
|
+
*/
|
|
95
|
+
function recommendClaudeMdManagement(): PluginRecommendation {
|
|
96
|
+
return {
|
|
97
|
+
id: pluginId("claude-md-management"),
|
|
98
|
+
recommended: true,
|
|
99
|
+
because:
|
|
100
|
+
"the baseline's CLAUDE.md only helps while it stays accurate; this " +
|
|
101
|
+
"plugin audits and updates it as the project changes, complementing " +
|
|
102
|
+
"harness-guidance's periodic refresh sweeps, which check the harness " +
|
|
103
|
+
"against upstream guidance rather than against the project's own " +
|
|
104
|
+
"day-to-day drift.",
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The `github` plugin is the natural companion to the `github` pack: that
|
|
110
|
+
* pack's skills (`reviewing-dependabot-prs`, `triaging-scan-alerts`,
|
|
111
|
+
* `watching-pr-checks`) are `gh`-CLI workflows, and the plugin gives Claude
|
|
112
|
+
* structured GitHub access alongside them. Without the pack there is no
|
|
113
|
+
* GitHub-operations workflow in the project for it to serve, so it is only
|
|
114
|
+
* offered, not pre-selected.
|
|
115
|
+
*/
|
|
116
|
+
function recommendGithub(
|
|
117
|
+
context: PluginRecommendationContext | undefined,
|
|
118
|
+
): PluginRecommendation {
|
|
119
|
+
const recommended = context?.chosenPacks?.includes("github") ?? false;
|
|
120
|
+
return {
|
|
121
|
+
id: pluginId("github"),
|
|
122
|
+
recommended,
|
|
123
|
+
because: recommended
|
|
124
|
+
? "the github pack was chosen, and its skills are gh-CLI GitHub " +
|
|
125
|
+
"workflows -- reviewing and batch-merging Dependabot PRs, triaging " +
|
|
126
|
+
"code-scanning alerts to file:line, watching a PR's checks -- so " +
|
|
127
|
+
"structured GitHub access through this plugin serves them directly."
|
|
128
|
+
: "not preselected because the github pack was not chosen: without " +
|
|
129
|
+
"its GitHub-operations skills there is no workflow here that needs " +
|
|
130
|
+
"structured GitHub access. It stays on offer if the project works " +
|
|
131
|
+
"heavily with issues and PRs anyway.",
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* `security-guidance` earns its cost where the attack surface or the
|
|
137
|
+
* rigor bar is highest: a `service` handles untrusted network input, and a
|
|
138
|
+
* `thorough` CI depth is the user asking for the stricter posture
|
|
139
|
+
* explicitly. The cost is real and named rather than hidden -- Edit/Write
|
|
140
|
+
* pattern hooks, a Stop-hook LLM review that spends tokens on every turn,
|
|
141
|
+
* and a commit reviewer -- so it is not pre-selected elsewhere.
|
|
142
|
+
*/
|
|
143
|
+
function recommendSecurityGuidance(
|
|
144
|
+
answers: InterviewAnswers,
|
|
145
|
+
): PluginRecommendation {
|
|
146
|
+
const recommended =
|
|
147
|
+
answers.kind === "service" || answers.ciDepth === "thorough";
|
|
148
|
+
return {
|
|
149
|
+
id: pluginId("security-guidance"),
|
|
150
|
+
recommended,
|
|
151
|
+
because: recommended
|
|
152
|
+
? "a service handles untrusted input, or thorough CI depth asks for " +
|
|
153
|
+
"the stricter posture, so security review is worth its cost here. " +
|
|
154
|
+
"That cost is real: pattern hooks on every Edit/Write, a Stop-hook " +
|
|
155
|
+
"LLM review that spends tokens at the end of every turn, and a " +
|
|
156
|
+
"commit reviewer."
|
|
157
|
+
: "for a non-service project at standard or minimal CI depth, its " +
|
|
158
|
+
"Edit/Write pattern hooks, per-turn Stop-hook LLM review (tokens " +
|
|
159
|
+
"on every turn) and commit reviewer cost more than the added " +
|
|
160
|
+
"scrutiny is likely to return -- still available if the project " +
|
|
161
|
+
"handles sensitive data.",
|
|
162
|
+
prerequisites: ["Python 3.8+ on PATH (its hooks are Python scripts)"],
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* `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.
|
|
170
|
+
*/
|
|
171
|
+
function recommendSkillCreator(
|
|
172
|
+
context: PluginRecommendationContext | undefined,
|
|
173
|
+
): PluginRecommendation {
|
|
174
|
+
const recommended = context?.hasCustomSkills === true;
|
|
175
|
+
return {
|
|
176
|
+
id: pluginId("skill-creator"),
|
|
177
|
+
recommended,
|
|
178
|
+
because: recommended
|
|
179
|
+
? "this project authors its own skills, and skill-creator helps " +
|
|
180
|
+
"draft, evaluate and tune them -- including their triggering " +
|
|
181
|
+
"descriptions -- rather than iterating on them by hand."
|
|
182
|
+
: "only useful to a project that authors its own skills; the " +
|
|
183
|
+
"baseline's skills come maintained from upstream, so there is " +
|
|
184
|
+
"nothing here for it to work on yet.",
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* `claude-code-setup` recommends automations (hooks, skills, MCP servers,
|
|
190
|
+
* subagents) for a codebase -- which is exactly the job `/customize` is
|
|
191
|
+
* already doing, against a baseline designed for it. Running both would
|
|
192
|
+
* produce two competing sets of suggestions, so it is never pre-selected;
|
|
193
|
+
* it is listed with that reason rather than silently omitted, so the user
|
|
194
|
+
* can see why. TypeScript-toolchain gaps are a distinct question from Claude
|
|
195
|
+
* Code automation and are answered instead by the baseline
|
|
196
|
+
* `typescript-guidance` skill's `gaps` mode.
|
|
197
|
+
*/
|
|
198
|
+
function recommendClaudeCodeSetup(): PluginRecommendation {
|
|
199
|
+
return {
|
|
200
|
+
id: pluginId("claude-code-setup"),
|
|
201
|
+
recommended: false,
|
|
202
|
+
because:
|
|
203
|
+
"its automation recommender overlaps what /customize is already " +
|
|
204
|
+
"doing -- tailoring hooks, skills and agents to this project against " +
|
|
205
|
+
"the baseline -- so enabling it would produce a second, competing " +
|
|
206
|
+
"set of suggestions. TypeScript-toolchain gaps are a separate " +
|
|
207
|
+
"question from Claude Code automation, and are answered instead by " +
|
|
208
|
+
"the baseline typescript-guidance skill's gaps mode with " +
|
|
209
|
+
"live-researched, cited recommendations. " +
|
|
210
|
+
"Listed so the choice is visible, not silently left out.",
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* 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.
|
|
222
|
+
*/
|
|
223
|
+
export function recommendPlugins(
|
|
224
|
+
answers: InterviewAnswers,
|
|
225
|
+
context?: PluginRecommendationContext,
|
|
226
|
+
): PluginRecommendation[] {
|
|
227
|
+
return [
|
|
228
|
+
recommendContext7(),
|
|
229
|
+
recommendTypescriptLsp(),
|
|
230
|
+
recommendClaudeMdManagement(),
|
|
231
|
+
recommendGithub(context),
|
|
232
|
+
recommendSecurityGuidance(answers),
|
|
233
|
+
recommendSkillCreator(context),
|
|
234
|
+
recommendClaudeCodeSetup(),
|
|
235
|
+
];
|
|
236
|
+
}
|
|
@@ -4,8 +4,8 @@ description: Writer spoke for the TDD build pipeline. Given a contract and a set
|
|
|
4
4
|
tools: Read, Write, Edit, Grep, Glob, Bash, mcp__context7__resolve-library-id, mcp__context7__query-docs
|
|
5
5
|
disallowedTools: Agent
|
|
6
6
|
mcpServers: [context7]
|
|
7
|
-
model: claude-
|
|
8
|
-
effort:
|
|
7
|
+
model: claude-opus-5-5
|
|
8
|
+
effort: medium
|
|
9
9
|
permissionMode: acceptEdits
|
|
10
10
|
maxTurns: 40
|
|
11
11
|
color: cyan
|
|
@@ -111,8 +111,8 @@ id first, then query for the specific behavior in question — don't fetch
|
|
|
111
111
|
broad documentation you won't use.
|
|
112
112
|
|
|
113
113
|
**Precedence: installed types are the pinned truth and win on conflict.**
|
|
114
|
-
|
|
115
|
-
whatever version it has indexed — a disagreement between the two is expected
|
|
114
|
+
The installed dependency version (see `pnpm-lock.yaml`) is what runs, while
|
|
115
|
+
context7 returns docs for whatever version it has indexed — a disagreement between the two is expected
|
|
116
116
|
and is not evidence the types are wrong. Never widen or reinterpret a type
|
|
117
117
|
based on context7 output alone; use it to understand behavior the types are
|
|
118
118
|
silent on, not to override them.
|
|
@@ -3,8 +3,8 @@ name: code-reviewer
|
|
|
3
3
|
description: Read-only reviewer for this project's source changes. Applies the four-part quality checklist and SOLID checks to a diff. Use after writing or changing source code, before commit.
|
|
4
4
|
tools: Read, Grep, Glob, Bash
|
|
5
5
|
disallowedTools: Agent
|
|
6
|
-
model: claude-
|
|
7
|
-
effort:
|
|
6
|
+
model: claude-opus-5-5
|
|
7
|
+
effort: medium
|
|
8
8
|
maxTurns: 40
|
|
9
9
|
color: blue
|
|
10
10
|
---
|
|
@@ -69,8 +69,8 @@ on it.
|
|
|
69
69
|
a PreToolUse hook then exits 0, which means _allow_, and a verify gate goes
|
|
70
70
|
green having checked nothing. `.pathname` is percent-encoded while
|
|
71
71
|
`process.argv[1]` is a decoded path, so it also never matches on a path with
|
|
72
|
-
spaces or non-ASCII characters. Inline the helper
|
|
73
|
-
|
|
72
|
+
spaces or non-ASCII characters. Inline the helper: every hook carries its own copy of this guard block,
|
|
73
|
+
so each one stays an independent entrypoint.
|
|
74
74
|
- The package's `exports` map is the public contract — flag any change to it
|
|
75
75
|
as a semver event and check the Conventional Commit matches.
|
|
76
76
|
- TSDoc on exported symbols.
|
|
@@ -3,8 +3,8 @@ name: silent-failure-hunter
|
|
|
3
3
|
description: Read-only error-handling auditor. Hunts for silent failures — swallowed exceptions, unchained causes, empty catch blocks, optional-chaining that masks errors, and retry/poll logic that exhausts without surfacing — against this project's error hierarchy and error-handling rules. Use after implementing or changing any code that has try/catch, async/await, optional chaining on fallible calls, or retry/poll loops. Complements code-reviewer (general quality).
|
|
4
4
|
tools: Read, Grep, Glob, Bash
|
|
5
5
|
disallowedTools: Agent
|
|
6
|
-
model: claude-
|
|
7
|
-
effort:
|
|
6
|
+
model: claude-opus-5-5
|
|
7
|
+
effort: medium
|
|
8
8
|
maxTurns: 40
|
|
9
9
|
color: yellow
|
|
10
10
|
---
|
|
@@ -3,8 +3,8 @@ name: test-author
|
|
|
3
3
|
description: Writes Vitest tests for a source export — happy path, failure path, and expectTypeOf type-level tests where the type is the contract. This is the tests-first (RED) spoke of the TDD loop; it writes tests from the documented contract before the implementation exists and confirms they fail for the right reason. Also usable to backfill tests for existing code. It writes tests only — never the implementation, and never reviews implementation quality.
|
|
4
4
|
tools: Read, Grep, Glob, Edit, Write, Bash
|
|
5
5
|
disallowedTools: Agent
|
|
6
|
-
model: claude-sonnet-5
|
|
7
|
-
effort:
|
|
6
|
+
model: claude-sonnet-5-5
|
|
7
|
+
effort: medium
|
|
8
8
|
permissionMode: acceptEdits
|
|
9
9
|
maxTurns: 40
|
|
10
10
|
color: green
|
|
@@ -180,9 +180,11 @@ expect(() =>
|
|
|
180
180
|
so the hub can dispatch `code-implementer` precisely. This keeps the suite
|
|
181
181
|
green and self-resolves visibly: once the real fix lands, `test.fails`
|
|
182
182
|
reports an XPASS, the signal to flip it to a normal `test`.
|
|
183
|
-
-
|
|
184
|
-
`
|
|
185
|
-
|
|
183
|
+
- Mock the filesystem by default (`vi.spyOn(fs, method)` or
|
|
184
|
+
`vi.mock('node:fs')`). The one exception, matching `tests.md`: when the
|
|
185
|
+
unit under test's contract IS real filesystem behavior (a walker, a
|
|
186
|
+
parser reading files), use a per-test `mkdtemp` sandbox torn down in the
|
|
187
|
+
same test -- never a fixed path, and never a mutation outside it.
|
|
186
188
|
- **The mock target must track the implementation's I/O primitive.** If the
|
|
187
189
|
implementation moves from one primitive to another, your tests must
|
|
188
190
|
re-mock the **new** one — the old mock silently stops intercepting
|
|
@@ -26,11 +26,15 @@
|
|
|
26
26
|
* Blocks by exiting 2 with a message on stderr.
|
|
27
27
|
*/
|
|
28
28
|
import process from "node:process";
|
|
29
|
-
import { realpathSync } from "node:fs";
|
|
29
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
30
30
|
import { execFileSync } from "node:child_process";
|
|
31
31
|
import { dirname, relative, resolve } from "node:path";
|
|
32
32
|
import { fileURLToPath } from "node:url";
|
|
33
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
canonicalize,
|
|
35
|
+
isAbsoluteLike,
|
|
36
|
+
isProtectedPath,
|
|
37
|
+
} from "../../bin/lib/protected-paths.mjs";
|
|
34
38
|
export { isProtectedPath };
|
|
35
39
|
|
|
36
40
|
/**
|
|
@@ -73,11 +77,13 @@ export function isMainOrDetachedOnMain(git = defaultGit) {
|
|
|
73
77
|
return false;
|
|
74
78
|
}
|
|
75
79
|
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
80
|
+
// Kept as a duplicated, self-contained block in every hook file rather than
|
|
81
|
+
// imported from a shared helper -- each hook stays a single independent
|
|
82
|
+
// file, which keeps this project's hook count easy to reason about against
|
|
83
|
+
// CLAUDE.md's hook budget. `import.meta.url` is symlink-resolved but
|
|
84
|
+
// `process.argv[1]` is not, so comparing them directly would be false under
|
|
85
|
+
// a symlinked invocation path -- and the guard below would then never run,
|
|
86
|
+
// i.e. silently fail open (exit 0) instead of blocking.
|
|
81
87
|
function isEntryPoint() {
|
|
82
88
|
try {
|
|
83
89
|
return realpathSync(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
@@ -98,13 +104,53 @@ if (isEntryPoint()) {
|
|
|
98
104
|
}
|
|
99
105
|
|
|
100
106
|
const filePath = input.tool_input?.file_path ?? "";
|
|
107
|
+
// Cheap, unscoped pre-check first (same cost as before this file's
|
|
108
|
+
// scoping fix): anything that can't possibly be a src/tests path by raw
|
|
109
|
+
// substring is dismissed with no git shell-out at all.
|
|
101
110
|
if (!isProtectedPath(filePath)) process.exit(0);
|
|
102
111
|
|
|
103
|
-
|
|
104
|
-
|
|
112
|
+
// Bind git to the nearest EXISTING ancestor of the write target, not the
|
|
113
|
+
// target directory itself -- a Write creating a brand-new nested
|
|
114
|
+
// directory (e.g. `src/newdir/a.ts` where `newdir/` doesn't exist yet)
|
|
115
|
+
// means `git -C <that dir>` fails outright ("cannot change to ... No
|
|
116
|
+
// such file or directory"), which would silently break EVERY git call
|
|
117
|
+
// below, including branch detection -- the guard would then never learn
|
|
118
|
+
// it's on `main` at all and allow the write through unblocked. Any
|
|
119
|
+
// ancestor within the same worktree resolves the same repo root and
|
|
120
|
+
// branch, so walking up costs nothing in accuracy.
|
|
121
|
+
let probeDir = dirname(resolve(filePath));
|
|
122
|
+
while (!existsSync(probeDir)) {
|
|
123
|
+
const parent = dirname(probeDir);
|
|
124
|
+
if (parent === probeDir) break; // filesystem root; let git fail naturally
|
|
125
|
+
probeDir = parent;
|
|
126
|
+
}
|
|
127
|
+
const git = defaultGitFor(probeDir);
|
|
128
|
+
const worktreeRoot = git(["rev-parse", "--show-toplevel"]);
|
|
129
|
+
// Re-check scoped to the file's OWN worktree root (more accurate than a
|
|
130
|
+
// guess from CLAUDE_PROJECT_DIR/cwd, and what this guard already resolves
|
|
131
|
+
// everything else against): a checkout whose own path merely contains the
|
|
132
|
+
// substring "/src/" above the worktree root (e.g. a clone at
|
|
133
|
+
// ~/src/other-project) must not count as protected just because of that.
|
|
134
|
+
// A non-git directory leaves worktreeRoot "" and skips this re-check,
|
|
135
|
+
// same as the header comment's existing "never blocks" behavior. Both
|
|
136
|
+
// sides go through `canonicalize` (case-correct, symlinks resolved)
|
|
137
|
+
// before comparing -- see its own doc comment for why a raw string
|
|
138
|
+
// comparison isn't safe here (macOS's case-insensitive filesystem and its
|
|
139
|
+
// `/tmp`/`/var` symlinks both make two spellings of the identical file
|
|
140
|
+
// compare as different paths otherwise). Only attempted for an ABSOLUTE
|
|
141
|
+
// filePath: canonicalize() resolves a relative one against this process's
|
|
142
|
+
// own cwd, which isn't necessarily the anchor a relative payload was
|
|
143
|
+
// meant against -- the fast pre-check above already matched it correctly
|
|
144
|
+
// as-is, so a relative path just keeps that verdict.
|
|
145
|
+
if (
|
|
146
|
+
worktreeRoot !== "" &&
|
|
147
|
+
isAbsoluteLike(filePath) &&
|
|
148
|
+
!isProtectedPath(canonicalize(filePath), canonicalize(worktreeRoot))
|
|
149
|
+
) {
|
|
150
|
+
process.exit(0);
|
|
151
|
+
}
|
|
105
152
|
|
|
106
153
|
if (isMainOrDetachedOnMain(git)) {
|
|
107
|
-
const worktreeRoot = git(["rev-parse", "--show-toplevel"]);
|
|
108
154
|
const inDifferentTree =
|
|
109
155
|
worktreeRoot !== "" && resolve(worktreeRoot) !== resolve(process.cwd());
|
|
110
156
|
const location = inDifferentTree
|