@monte3l/groundwork 0.0.0

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 (151) hide show
  1. package/README.md +23 -0
  2. package/bin/m3l-groundwork.mjs +10 -0
  3. package/dist/assets.d.ts +20 -0
  4. package/dist/assets.js +79 -0
  5. package/dist/caps.d.ts +25 -0
  6. package/dist/caps.js +69 -0
  7. package/dist/conflicts.d.ts +12 -0
  8. package/dist/conflicts.js +77 -0
  9. package/dist/emit.d.ts +7 -0
  10. package/dist/emit.js +42 -0
  11. package/dist/git.d.ts +3 -0
  12. package/dist/git.js +9 -0
  13. package/dist/harness/conformance.d.ts +20 -0
  14. package/dist/harness/conformance.js +18 -0
  15. package/dist/harness/frontmatter.d.ts +38 -0
  16. package/dist/harness/frontmatter.js +204 -0
  17. package/dist/harness/grade.d.ts +4 -0
  18. package/dist/harness/grade.js +105 -0
  19. package/dist/harness/rules.d.ts +55 -0
  20. package/dist/harness/rules.js +580 -0
  21. package/dist/harness/types.d.ts +32 -0
  22. package/dist/harness/types.js +9 -0
  23. package/dist/inventory.d.ts +63 -0
  24. package/dist/inventory.js +66 -0
  25. package/dist/jsonc.d.ts +14 -0
  26. package/dist/jsonc.js +83 -0
  27. package/dist/main.d.ts +24 -0
  28. package/dist/main.js +297 -0
  29. package/dist/merge-json.d.ts +74 -0
  30. package/dist/merge-json.js +135 -0
  31. package/dist/mode.d.ts +19 -0
  32. package/dist/mode.js +53 -0
  33. package/dist/packs.d.ts +61 -0
  34. package/dist/packs.js +186 -0
  35. package/dist/plugin.d.ts +23 -0
  36. package/dist/plugin.js +79 -0
  37. package/dist/report.d.ts +4 -0
  38. package/dist/report.js +323 -0
  39. package/dist/survey/fs-walk.d.ts +14 -0
  40. package/dist/survey/fs-walk.js +60 -0
  41. package/dist/survey/survey-docs.d.ts +4 -0
  42. package/dist/survey/survey-docs.js +69 -0
  43. package/dist/survey/survey-harness.d.ts +4 -0
  44. package/dist/survey/survey-harness.js +121 -0
  45. package/dist/survey/survey-shape.d.ts +4 -0
  46. package/dist/survey/survey-shape.js +182 -0
  47. package/dist/survey/survey-toolchain.d.ts +4 -0
  48. package/dist/survey/survey-toolchain.js +217 -0
  49. package/dist/survey/survey.d.ts +5 -0
  50. package/dist/survey/survey.js +21 -0
  51. package/dist/survey/types.d.ts +117 -0
  52. package/dist/survey/types.js +8 -0
  53. package/dist/tokens.d.ts +13 -0
  54. package/dist/tokens.js +13 -0
  55. package/dist/toolchain/conformance.d.ts +20 -0
  56. package/dist/toolchain/conformance.js +30 -0
  57. package/dist/toolchain/grade.d.ts +4 -0
  58. package/dist/toolchain/grade.js +244 -0
  59. package/dist/toolchain/rules.d.ts +118 -0
  60. package/dist/toolchain/rules.js +706 -0
  61. package/dist/toolchain/tsconfig-chain.d.ts +36 -0
  62. package/dist/toolchain/tsconfig-chain.js +116 -0
  63. package/dist/toolchain/types.d.ts +27 -0
  64. package/dist/toolchain/types.js +9 -0
  65. package/package.json +59 -0
  66. package/plugin/skills/customize/SKILL.md +305 -0
  67. package/plugin/src/domain-map.ts +134 -0
  68. package/plugin/src/index.ts +4 -0
  69. package/plugin/src/kind-facet-map.ts +174 -0
  70. package/plugin/src/pack-map.ts +65 -0
  71. package/templates/core/.claude/agents/Explore.md +43 -0
  72. package/templates/core/.claude/agents/code-implementer.md +258 -0
  73. package/templates/core/.claude/agents/code-reviewer.md +163 -0
  74. package/templates/core/.claude/agents/silent-failure-hunter.md +191 -0
  75. package/templates/core/.claude/agents/test-author.md +211 -0
  76. package/templates/core/.claude/hooks/guard-branch-isolation.mjs +123 -0
  77. package/templates/core/.claude/hooks/guard-double-background.mjs +113 -0
  78. package/templates/core/.claude/hooks/guard-git-push-signed.mjs +90 -0
  79. package/templates/core/.claude/hooks/guard-hub-src-writes.mjs +88 -0
  80. package/templates/core/.claude/hooks/guard-js-extension.mjs +66 -0
  81. package/templates/core/.claude/hooks/guard-no-commonjs.mjs +105 -0
  82. package/templates/core/.claude/hooks/guard-protected-paths.mjs +45 -0
  83. package/templates/core/.claude/hooks/guard-secret-writes.mjs +183 -0
  84. package/templates/core/.claude/hooks/inject-decision-gate.mjs +119 -0
  85. package/templates/core/.claude/hooks/post-edit-verify.mjs +150 -0
  86. package/templates/core/.claude/rules/agent-dispatch.md +121 -0
  87. package/templates/core/.claude/rules/refactoring.md +52 -0
  88. package/templates/core/.claude/rules/src.md +114 -0
  89. package/templates/core/.claude/rules/tests.md +129 -0
  90. package/templates/core/.claude/settings.json +111 -0
  91. package/templates/core/.claude/skills/creating-prs/SKILL.md +132 -0
  92. package/templates/core/.claude/skills/finishing-work/SKILL.md +117 -0
  93. package/templates/core/.claude/skills/harness-guidance/SKILL.md +140 -0
  94. package/templates/core/.claude/skills/harness-guidance/references/official-sources.md +58 -0
  95. package/templates/core/.claude/skills/starting-work/SKILL.md +94 -0
  96. package/templates/core/.claude/skills/triaging-ci/SKILL.md +111 -0
  97. package/templates/core/.claude/skills/typescript-guidance/SKILL.md +143 -0
  98. package/templates/core/.claude/skills/typescript-guidance/references/typescript-sources.md +102 -0
  99. package/templates/core/.claude/skills/writing-commits/SKILL.md +248 -0
  100. package/templates/core/.github/workflows/ci.yml +123 -0
  101. package/templates/core/.github/workflows/dependency-review.yml +26 -0
  102. package/templates/core/.github/workflows/security-audit.yml +54 -0
  103. package/templates/core/.node-version +1 -0
  104. package/templates/core/.prettierignore +5 -0
  105. package/templates/core/.prettierrc.json +4 -0
  106. package/templates/core/CLAUDE.md +127 -0
  107. package/templates/core/README.md +24 -0
  108. package/templates/core/_gitignore +19 -0
  109. package/templates/core/_npmrc +1 -0
  110. package/templates/core/bin/check-exports.mjs +92 -0
  111. package/templates/core/bin/check-harness.mjs +27 -0
  112. package/templates/core/bin/check-node-version.mjs +51 -0
  113. package/templates/core/bin/check-toolchain.mjs +20 -0
  114. package/templates/core/bin/lib/agent-roster.mjs +8 -0
  115. package/templates/core/bin/lib/frontmatter.mjs +210 -0
  116. package/templates/core/bin/lib/harness-rules.mjs +916 -0
  117. package/templates/core/bin/lib/protected-paths.mjs +23 -0
  118. package/templates/core/bin/lib/report.mjs +56 -0
  119. package/templates/core/bin/lib/signed-range.mjs +178 -0
  120. package/templates/core/bin/lib/toolchain-rules.mjs +1264 -0
  121. package/templates/core/bin/lib/verify-steps.mjs +131 -0
  122. package/templates/core/bin/lib/verify-steps.packs.json +1 -0
  123. package/templates/core/bin/lint-commit.mjs +50 -0
  124. package/templates/core/bin/strip-claude-trailers.mjs +25 -0
  125. package/templates/core/bin/verify.mjs +64 -0
  126. package/templates/core/commitlint.config.js +11 -0
  127. package/templates/core/docs/research/harness-refresh.md +27 -0
  128. package/templates/core/docs/research/typescript-refresh.md +32 -0
  129. package/templates/core/eslint.config.js +105 -0
  130. package/templates/core/knip.json +6 -0
  131. package/templates/core/lefthook.yml +39 -0
  132. package/templates/core/package.json +58 -0
  133. package/templates/core/pnpm-workspace.yaml +13 -0
  134. package/templates/core/src/index.ts +12 -0
  135. package/templates/core/tests/index.test.ts +8 -0
  136. package/templates/core/tsconfig.base.json +36 -0
  137. package/templates/core/tsconfig.build.json +10 -0
  138. package/templates/core/tsconfig.json +11 -0
  139. package/templates/core/vitest.config.ts +32 -0
  140. package/templates/packs/README.md +81 -0
  141. package/templates/packs/harness-extras/files/.claude/agents/type-design-analyzer.md +188 -0
  142. package/templates/packs/harness-extras/files/.claude/hooks/guard-readonly-bash.mjs +324 -0
  143. package/templates/packs/harness-extras/files/.claude/hooks/reinject-compact-handoff.mjs +197 -0
  144. package/templates/packs/harness-extras/files/.claude/hooks/write-compact-handoff.mjs +180 -0
  145. package/templates/packs/harness-extras/files/bin/check-file-budget.mjs +407 -0
  146. package/templates/packs/harness-extras/files/bin/file-budget-baseline.json +1 -0
  147. package/templates/packs/harness-extras/pack.json +65 -0
  148. package/templates/packs/statusline/files/.claude/hooks/statusline-layout.mjs +365 -0
  149. package/templates/packs/statusline/files/.claude/hooks/statusline.mjs +996 -0
  150. package/templates/packs/statusline/files/.claude/hooks/subagent-statusline.mjs +203 -0
  151. package/templates/packs/statusline/pack.json +31 -0
@@ -0,0 +1,36 @@
1
+ interface ChainFile {
2
+ /** Absolute path. */
3
+ abs: string;
4
+ /** Path relative to the project root, forward slashes. */
5
+ rel: string;
6
+ /** Why the file could not be used, or `undefined` when it parsed. */
7
+ error: string | undefined;
8
+ /** This file's own `compilerOptions`, unmerged. */
9
+ options: Record<string, unknown>;
10
+ }
11
+ export interface ChainLink {
12
+ /** The file (project-relative) holding the `extends`. */
13
+ from: string;
14
+ specifier: string;
15
+ kind: "relative" | "package";
16
+ resolved: boolean;
17
+ /** Absolute path a relative specifier points at; what a missing-file message names. */
18
+ attempted: string;
19
+ }
20
+ export interface TsconfigChain {
21
+ /** The entry file, project-relative. */
22
+ entry: string;
23
+ /** Effective `compilerOptions` after folding the whole chain. */
24
+ options: Record<string, unknown>;
25
+ /** False if any file in the chain existed but failed to parse. */
26
+ parsed: boolean;
27
+ /** False if any file failed to parse or any `extends` could not be followed. */
28
+ complete: boolean;
29
+ /** Every file visited, entry first (child-first), each once. */
30
+ files: ChainFile[];
31
+ links: ChainLink[];
32
+ }
33
+ /** Loads `entry` (project-relative) under `root` and folds its `extends` chain. */
34
+ export declare function loadTsconfigChain(root: string, entry: string): TsconfigChain;
35
+ export {};
36
+ //# sourceMappingURL=tsconfig-chain.d.ts.map
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Follows one tsconfig's `extends` chain and folds `compilerOptions` the way
3
+ * TypeScript does: array entries (TS 5.0+) left to right, the file's own
4
+ * options last. Shared by the toolchain grader and the adopt-mode survey so
5
+ * the two can never disagree about what a project's effective flags are.
6
+ *
7
+ * Handles a relative target (`x`, `x.json`, `x/tsconfig.json`) and a bare
8
+ * package specifier looked up under every ancestor `node_modules`. A package
9
+ * `exports` map is out of scope -- such a specifier simply stays unresolved.
10
+ * Nothing here throws: a file that cannot be read or parsed is recorded on
11
+ * the chain, and the chain is marked incomplete.
12
+ */
13
+ import { statSync } from "node:fs";
14
+ import { dirname, isAbsolute, join, relative, resolve } from "node:path";
15
+ import { readJsoncFile } from "../jsonc.js";
16
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
17
+ function isFile(path) {
18
+ try {
19
+ return statSync(path).isFile();
20
+ }
21
+ catch {
22
+ return false;
23
+ }
24
+ }
25
+ /** The `extends` value as a list: a string, or TypeScript 5.0+'s array form. */
26
+ function extendsList(value) {
27
+ if (typeof value === "string")
28
+ return [value];
29
+ if (Array.isArray(value)) {
30
+ return value.filter((entry) => typeof entry === "string");
31
+ }
32
+ return [];
33
+ }
34
+ function resolveExtends(specifier, fromAbs) {
35
+ const candidates = (base) => [
36
+ base,
37
+ `${base}.json`,
38
+ join(base, "tsconfig.json"),
39
+ ];
40
+ if (specifier.startsWith(".") || isAbsolute(specifier)) {
41
+ const base = resolve(dirname(fromAbs), specifier);
42
+ return {
43
+ kind: "relative",
44
+ abs: candidates(base).find(isFile),
45
+ attempted: base.endsWith(".json") ? base : `${base}.json`,
46
+ };
47
+ }
48
+ let dir = dirname(fromAbs);
49
+ for (;;) {
50
+ const base = join(dir, "node_modules", specifier);
51
+ const abs = candidates(base).find(isFile);
52
+ if (abs !== undefined)
53
+ return { kind: "package", abs, attempted: base };
54
+ const parent = dirname(dir);
55
+ if (parent === dir)
56
+ return { kind: "package", abs: undefined, attempted: base };
57
+ dir = parent;
58
+ }
59
+ }
60
+ /** Loads `entry` (project-relative) under `root` and folds its `extends` chain. */
61
+ export function loadTsconfigChain(root, entry) {
62
+ const files = [];
63
+ const links = [];
64
+ let parsed = true;
65
+ let complete = true;
66
+ const visit = (abs, stack) => {
67
+ if (stack.includes(abs))
68
+ return {};
69
+ const read = readJsoncFile(abs);
70
+ const usable = read.ok && isRecord(read.value);
71
+ const value = read.ok && isRecord(read.value) ? read.value : undefined;
72
+ const own = value !== undefined && isRecord(value["compilerOptions"])
73
+ ? value["compilerOptions"]
74
+ : {};
75
+ if (!files.some((file) => file.abs === abs)) {
76
+ files.push({
77
+ abs,
78
+ rel: relative(root, abs).split("\\").join("/"),
79
+ error: usable
80
+ ? undefined
81
+ : read.ok
82
+ ? "top level is not an object"
83
+ : read.error,
84
+ options: own,
85
+ });
86
+ }
87
+ if (value === undefined) {
88
+ parsed = false;
89
+ complete = false;
90
+ return {};
91
+ }
92
+ let merged = {};
93
+ const from = relative(root, abs).split("\\").join("/");
94
+ for (const specifier of extendsList(value["extends"])) {
95
+ const target = resolveExtends(specifier, abs);
96
+ if (!links.some((l) => l.from === from && l.specifier === specifier)) {
97
+ links.push({
98
+ from,
99
+ specifier,
100
+ kind: target.kind,
101
+ resolved: target.abs !== undefined,
102
+ attempted: target.attempted,
103
+ });
104
+ }
105
+ if (target.abs === undefined) {
106
+ complete = false;
107
+ continue;
108
+ }
109
+ merged = { ...merged, ...visit(target.abs, [...stack, abs]) };
110
+ }
111
+ return { ...merged, ...own };
112
+ };
113
+ const options = visit(join(root, entry), []);
114
+ return { entry, options, parsed, complete, files, links };
115
+ }
116
+ //# sourceMappingURL=tsconfig-chain.js.map
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Shapes shared by the toolchain grader. Same two-level model as the harness
3
+ * grader (`../harness/types.ts`): `structural` findings are wiring defects
4
+ * `tsc` and ESLint do not catch and fail a gate; `rubric` findings are the
5
+ * floor official TypeScript / typescript-eslint guidance sets and only ever
6
+ * warn.
7
+ */
8
+ import type { CheckTally, RuleLevel } from "../harness/types.js";
9
+ export type ToolchainCategory = "tsconfig" | "modules" | "eslint" | "testing" | "gates" | "deps";
10
+ export declare const TOOLCHAIN_CATEGORIES: readonly ToolchainCategory[];
11
+ export interface ToolchainFinding {
12
+ ruleId: string;
13
+ level: RuleLevel;
14
+ category: ToolchainCategory;
15
+ /** The file, package, or verify step the finding is about. */
16
+ subject: string;
17
+ message: string;
18
+ }
19
+ export interface ToolchainGrade {
20
+ findings: ToolchainFinding[];
21
+ structural: CheckTally;
22
+ /** Rubric tallies per category -- the absolute-quality measurement. */
23
+ rubric: Record<ToolchainCategory, CheckTally>;
24
+ /** `1 - failed/checked` over every rubric check; `1` when nothing was checked. */
25
+ rubricScore: number;
26
+ }
27
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,9 @@
1
+ export const TOOLCHAIN_CATEGORIES = [
2
+ "tsconfig",
3
+ "modules",
4
+ "eslint",
5
+ "testing",
6
+ "gates",
7
+ "deps",
8
+ ];
9
+ //# sourceMappingURL=types.js.map
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@monte3l/groundwork",
3
+ "version": "0.0.0",
4
+ "description": "Deterministic Phase A bootstrapper: writes the m3l-groundwork baseline (toolchain + Claude Code harness) into a new directory, or surveys an existing project read-only. No prompts, no network beyond the package install.",
5
+ "keywords": [
6
+ "typescript",
7
+ "claude-code",
8
+ "bootstrap",
9
+ "scaffold",
10
+ "cli"
11
+ ],
12
+ "homepage": "https://github.com/monte3l/m3l-groundwork#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/monte3l/m3l-groundwork/issues"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/monte3l/m3l-groundwork.git",
19
+ "directory": "packages/cli"
20
+ },
21
+ "license": "MIT",
22
+ "author": "Enrico Lionello",
23
+ "type": "module",
24
+ "engines": {
25
+ "node": ">=24"
26
+ },
27
+ "bin": {
28
+ "m3l-groundwork": "bin/m3l-groundwork.mjs"
29
+ },
30
+ "main": "./dist/main.js",
31
+ "types": "./dist/main.d.ts",
32
+ "exports": {
33
+ ".": {
34
+ "types": "./dist/main.d.ts",
35
+ "import": "./dist/main.js"
36
+ },
37
+ "./package.json": "./package.json"
38
+ },
39
+ "files": [
40
+ "dist",
41
+ "!dist/**/*.map",
42
+ "!dist/.tsbuildinfo",
43
+ "bin",
44
+ "templates",
45
+ "plugin"
46
+ ],
47
+ "publishConfig": {
48
+ "access": "public",
49
+ "provenance": true
50
+ },
51
+ "scripts": {
52
+ "prepack": "node scripts/vendor-assets.mjs vendor",
53
+ "postpack": "node scripts/vendor-assets.mjs clean"
54
+ },
55
+ "dependencies": {},
56
+ "devDependencies": {
57
+ "typescript": "^6.0.3"
58
+ }
59
+ }
@@ -0,0 +1,305 @@
1
+ ---
2
+ name: customize
3
+ description: >-
4
+ Tailors a project bootstrapped or adopted by m3l-groundwork: for a fresh
5
+ bootstrap, interviews the owner (project kind, runtime target, test
6
+ strictness, CI depth, which reviewer agents to keep) and applies
7
+ deterministic edits; for an adopted pre-existing project, first reconciles
8
+ the CLI's `.groundwork/` survey against the real repository, confirms what
9
+ to add, how to resolve conflicts, and which optional `templates/packs/`
10
+ pack(s) to install. Either way it then runs a live guidance pass over
11
+ official TypeScript and Anthropic sources to validate and refine the
12
+ result against current upstream recommendations. Use for /customize,
13
+ "tailor this project", "adopt this project", "set up this scaffold for my
14
+ project", or right after a fresh or adopted m3l-groundwork bootstrap.
15
+ ---
16
+
17
+ # customize
18
+
19
+ The baseline this project was bootstrapped or adopted with is universal and
20
+ frozen at publish time. This skill runs in three rounds, and the ordering is
21
+ the whole design:
22
+
23
+ - **Round 0 — the baseline.** Already in place for a fresh bootstrap; for an
24
+ adopted project, "the baseline" is instead whatever `.groundwork/` recorded
25
+ about the project's own existing files (see Step 0).
26
+ - **Round 1 — interview-driven tailoring.** Deterministic, from the answers
27
+ below. Prunes what the project doesn't need and selects what it does.
28
+ Still working from frozen knowledge.
29
+ - **Round 2 — guidance-driven refinement.** Two live sweeps over official
30
+ sources that compare, refine, and validate everything Rounds 0 and 1
31
+ produced against what upstream actually recommends **today**. This is the
32
+ most important round, because it is the only one whose knowledge is not
33
+ frozen.
34
+
35
+ ## Authority
36
+
37
+ Round 2's two sweeps each have authority over their **entire** domain, not a
38
+ narrow slice of it — this holds identically for a fresh bootstrap and an
39
+ adopted project; only the domain's _contents_ differ (the known baseline vs.
40
+ the project's real files):
41
+
42
+ - `typescript-guidance` (refresh mode) may amend every TypeScript facet —
43
+ `tsconfig.base.json`, `eslint.config.js`, `vitest.config.ts` (config
44
+ **and** the testing approach itself), packaging, the TypeScript-toolchain
45
+ entries in `package.json`, and the toolchain steps in
46
+ `.github/workflows/*.yml`.
47
+ - `harness-guidance` (refresh mode) may amend the whole `.claude/` surface —
48
+ `settings.json`, hooks, agents, skills, rules, and this `CLAUDE.md`.
49
+
50
+ The interview below scopes **priority, not authority**: it tells Round 2
51
+ which facets deserve the deepest dedicated research, never which facets it
52
+ may or may not touch.
53
+
54
+ ## Step 0 — Reconcile (adopt mode only)
55
+
56
+ 1. Look for `.groundwork/inventory.json`. **Absent → this is a fresh
57
+ bootstrap; skip straight to Step 1.** Everything below this step applies
58
+ only when it exists.
59
+ 2. **The deep read.** The CLI's survey is an index, not an interpretation —
60
+ it flagged what it found but could not parse (`needsReading: true` on
61
+ git-hook config, workflow files; anything in `survey.undetermined`) and
62
+ what it could only index, not summarize (`docs`). Read all of it for
63
+ real: the eslint config, the git-hook manager's actual stage commands,
64
+ the CI workflow job steps, `CLAUDE.md`, `CONTRIBUTING.md`, and any
65
+ docs/ADR files the survey indexed. Dispatch this as parallel read-only
66
+ `Explore` agents, one per discovery area (shape/toolchain, harness, docs),
67
+ so you aggregate their findings rather than reading everything yourself.
68
+ The harness agent also starts from `inventory.harnessGrade` (the report's
69
+ `## Harness grade` section): a deterministic, offline check of the
70
+ existing `.claude/` wiring. Its **wiring findings** (a hook registration
71
+ naming a missing file, a skill or agent with unreadable frontmatter, a
72
+ `CLAUDE.md` path that no longer exists) are facts to verify against the
73
+ real files, not verdicts to take on trust. Its **quality findings** are
74
+ advisory. `inventory.harnessConformance` counts how far the harness has
75
+ drifted from the baseline's — information only, since divergence from the
76
+ baseline is the point of adopting. An inventory with `schemaVersion` below
77
+ 3 carries neither field; skip this and continue.
78
+
79
+ The toolchain agent likewise starts from `inventory.toolchainGrade` (the
80
+ report's `## Toolchain grade` section): a deterministic, offline check of
81
+ the tsconfig chain, ESLint and vitest config, verify-step wiring, and
82
+ toolchain pins. It reads files and never runs them, and it reads
83
+ `eslint.config.js`/`vitest.config.ts` by pattern rather than by evaluating
84
+ them -- so a **wiring finding** (a build project that emits nowhere, a
85
+ verify step naming a script or file that does not exist, a `.node-version`
86
+ that contradicts `engines.node`) is a fact to verify against the real files,
87
+ and a **quality finding** (a missing strict flag, an option TypeScript has
88
+ deprecated, ESLint without type-aware linting, a coverage gate that is not
89
+ per-file) is advisory. Absence is never a finding: a project with no vitest
90
+ config simply has no coverage-gate line. `inventory.toolchainConformance`
91
+ counts drift from the baseline's toolchain files -- information only. An
92
+ inventory with `schemaVersion` below 4 carries neither field; skip this and
93
+ continue.
94
+
95
+ 3. **Write the findings back** into `.groundwork/adoption-report.md`,
96
+ replacing the CLI's index-level sections ("a `lefthook.yml` exists")
97
+ with semantic ones ("pre-push runs lint and typecheck; tests do not
98
+ gate").
99
+ 4. **Confirm.** Give a short summary in chat, then ask **one**
100
+ `AskUserQuestion` covering: (a) _did this miss anything about your
101
+ project?_ — the free-text option is the point of this question, not a
102
+ formality — (b) the conflict resolutions from the inventory's conflict
103
+ table, batched by facet (toolchain config, harness) rather than one
104
+ question per file (the harness facet's batch also carries any wiring
105
+ findings you confirmed in the deep read, offered as fixes to make, and the
106
+ toolchain facet's batch does the same for confirmed toolchain wiring findings) — and
107
+ (c) **which pack(s) to install**, from `inventory.packs`. For each pack, show its `budget`, its
108
+ `wiringObservations` (facts about how it would land — e.g. "no
109
+ `bin/lib/verify-steps.packs.json` found: no `bin/verify.mjs`-shaped gate
110
+ runner detected", or "`.claude/settings.json` already sets a top-level
111
+ `statusLine`"), and its `adoptNotes` verbatim; a pack whose gate
112
+ dependency the project doesn't have is still offered for its other
113
+ artifacts, with that limitation stated plainly rather than silently
114
+ dropped. This is index-level evidence from the CLI, not a kind-based
115
+ judgment — see Step 3's note on revisiting it once the interview confirms
116
+ the project's kind.
117
+ 5. **Record the confirmed decisions** to `.groundwork/adoption-decisions.json`
118
+ so a compacted or resumed session doesn't silently lose them and re-ask.
119
+
120
+ ## Step 1 — Interview
121
+
122
+ **Fresh bootstrap:** ask the following in **two** `AskUserQuestion` calls
123
+ (the tool caps a single call at four questions), each with a sensible
124
+ default marked "(Recommended)":
125
+
126
+ Call one (four questions):
127
+
128
+ 1. **Project kind** — library / CLI / frontend or web app / service.
129
+ 2. **Runtime target** — Node / browser / both.
130
+ 3. **Tests mandatory in the pre-push gate?** — yes (default; matches the
131
+ baseline) / warn only.
132
+ 4. **CI depth** — minimal / standard (default; matches the baseline) /
133
+ thorough.
134
+
135
+ Call two (one question):
136
+
137
+ 5. **Which baseline agents to keep** — multi-select over `Explore`,
138
+ `test-author`, `code-implementer`, `code-reviewer`,
139
+ `silent-failure-hunter` (all kept by default).
140
+
141
+ **Adopt mode:** ask the same five questions, but this becomes a
142
+ _confirmation_ round rather than a cold ask. Pre-select each answer from
143
+ Step 0's findings and **show the evidence alongside it** — "library — you
144
+ have an `exports` map and no `bin` field", not just a silent default. The
145
+ user confirms or corrects each one. This is why the CLI's survey deliberately
146
+ never names a `ProjectKind` itself (see its own `types.ts`): the inference
147
+ happens once, here, visibly, with its reasoning attached — not buried in an
148
+ offline heuristic no one reviews.
149
+
150
+ Packs are **not** re-asked here — Step 0.4 already collected that decision
151
+ (adopt mode) or the CLI already installed at bootstrap time via `--pack`
152
+ (fresh mode, nothing left to ask). Step 3 below is where a confirmed kind can
153
+ revise a pack decision made before the interview ran.
154
+
155
+ ## Step 2 — Plan facets (deterministic)
156
+
157
+ Read `kind-facet-map.ts`, alongside this file in the same skill
158
+ directory — a small, pure, unit-tested module (its canonical, tested source
159
+ lives in the m3l-groundwork repo at `packages/plugin/src/kind-facet-map.ts`;
160
+ this is a verbatim copy the bootstrapper placed here so the skill is
161
+ self-contained). Its `planFacets(answers)` function is the kind-to-facet
162
+ table: the same five answers always produce the same facet-emphasis plan
163
+ for both sweeps. You do not need to run it as code — it's short enough to
164
+ apply by inspection.
165
+
166
+ State the resulting plan in your response before proceeding — this is what
167
+ Round 2's two skill invocations will be told to emphasize.
168
+
169
+ ## Step 3 — Round 1: deterministic tailoring
170
+
171
+ **Fresh bootstrap** applies directly, no research needed:
172
+
173
+ - **Project kind ≠ library**: if `check:exports` (publint/attw) doesn't
174
+ apply to the chosen kind (CLI, frontend, service), remove the
175
+ `check:exports` step from `bin/lib/verify-steps.mjs` and the
176
+ corresponding `.github/workflows/ci.yml` line, and drop the `exports`
177
+ field from `package.json` in favor of a `bin` field (CLI) or leave `main`/
178
+ no public export map at all (service).
179
+ - **Runtime target = browser or both**: note that `tsconfig.base.json`'s
180
+ `lib` and `moduleResolution` will very likely need to change — but leave
181
+ the actual edit to Round 2's `typescript-guidance` sweep, which has full
182
+ authority over that file and access to current bundler-resolution
183
+ guidance you don't have without a live source.
184
+ - **Tests mandatory = warn only**: change the `test` lane in `lefthook.yml`
185
+ and `ci.yml` from a hard failure to a non-blocking report.
186
+ - **CI depth = minimal**: drop the `test` lane's coverage gate from CI
187
+ (still run locally); minimal keeps only format/lint/typecheck/build.
188
+ **CI depth = thorough**: note this for Round 2 — `harness-guidance` may
189
+ recommend additional current-best-practice lanes (e.g. a scheduled
190
+ dependency audit) beyond what the baseline ships.
191
+ - **Agents not kept**: delete their `.claude/agents/<name>.md` file. Never
192
+ delete `Explore`, `test-author`, or `code-implementer` even if unselected
193
+ — they're load-bearing for the hub-and-spoke loop `CLAUDE.md` documents.
194
+ - **Packs**: nothing to do here. A fresh bootstrap's packs were installed
195
+ (or not) by the CLI at `m3l-groundwork <dir> --pack <name>` invocation
196
+ time, before this skill ever ran — there is no fresh-mode install path in
197
+ `/customize` itself. To add a pack after the fact, re-run the CLI against
198
+ this now-non-empty directory (it auto-detects adopt mode) and run
199
+ `/customize` again; its Step 0 will offer the pack through the adopt path
200
+ below.
201
+
202
+ **Adopt mode** re-expresses each of the same five outcomes against whatever
203
+ the project actually has, instead of a named baseline path — "tests must not
204
+ hard-fail `pre-push`" is applied to _the gate the inventory found_ (jest in
205
+ CI, husky locally, whatever it is), not to `lefthook.yml`/`ci.yml` by name.
206
+ Concretely, adopt-mode Round 1 applies exactly three things, all already
207
+ confirmed in Step 0.4:
208
+
209
+ - The **approved additions** — files `templates/core` (at
210
+ `inventory.templateRoot`) would add that the project doesn't have and the
211
+ user approved adding.
212
+ - The **approved conflict resolutions** — for each divergent file the user
213
+ decided on, apply that decision (keep theirs / take groundwork's / merge
214
+ the named keys).
215
+ - The **approved packs** — installed from `.groundwork/packs/<name>/` (the
216
+ CLI's staged, self-contained copy — never `inventory.templateRoot`, which
217
+ may not exist by the time this runs). Before installing, call
218
+ `recommendPacks(answers)` from `pack-map.ts` (alongside this file, same
219
+ copy mechanism as `kind-facet-map.ts`) with the now-confirmed
220
+ `InterviewAnswers` and compare its verdict against Step 0.4's decision. For
221
+ both shipped packs this never disagrees (neither recommendation varies by
222
+ kind), but a future kind-scoped pack might — if it does, surface the
223
+ conflict rather than silently overriding the user's Step 0.4 answer,
224
+ mirroring Step 4's "the one exception" rule for guidance findings. To
225
+ install: copy `.groundwork/packs/<name>/files/` into the project (respecting
226
+ any approved per-file conflict decision the same way the baseline's own
227
+ additions are applied), then translate `pack.json`'s `wiring` by hand
228
+ against what Step 0.2's deep read already found — a `.claude/settings.json`
229
+ hook fragment merges the same way the baseline's own hook entries would;
230
+ `wiring.settingsTopLevel` is a set of top-level keys (e.g. `statusLine`)
231
+ planted whole, and only when the project doesn't already define that key —
232
+ when it does, show the existing value and ask, since the CLI's own
233
+ `mergeSettingsTopLevel` treats a differing value as a hard error and this
234
+ hand-applied path must not be laxer than the automated one (also check
235
+ `.claude/settings.local.json` and the user's `~/.claude/settings.json`,
236
+ either of which can shadow a project `statusLine`);
237
+ `wiring.verifySteps` becomes a step in whatever this project's real gate
238
+ runner is (a `package.json` script plus a line in its `lefthook.yml`/
239
+ `.husky/pre-push`/CI workflow, written by hand to match its actual shape)
240
+ — or, if the project has no such gate runner at all, install the pack's
241
+ other artifacts and state plainly in Step 6 that the gate was not wired,
242
+ rather than inventing a runner the project never asked for.
243
+
244
+ Nothing else is touched. A project file the user didn't approve a change to
245
+ stays exactly as it was.
246
+
247
+ Run `pnpm verify` (fresh) or the project's own equivalent (adopt) after
248
+ Round 1's edits to confirm the tailored result still passes before moving to
249
+ Round 2.
250
+
251
+ ## Step 4 — Round 2: the guidance pass
252
+
253
+ Invoke both guidance skills in **refresh mode**, in parallel:
254
+
255
+ ```
256
+ Skill(skill: "typescript-guidance", args: "mode: refresh")
257
+ Skill(skill: "harness-guidance", args: "mode: refresh")
258
+ ```
259
+
260
+ Each sweep reads its own tracker (`docs/research/typescript-refresh.md` /
261
+ `docs/research/harness-refresh.md`), fans out its five fixed facets — using
262
+ Step 2's plan to decide which facet gets the deepest attention this run,
263
+ not which facets it's allowed to touch — and enters plan mode with a
264
+ remediation plan if it finds drift.
265
+
266
+ **In adopt mode**, a sweep's domain is the project's real files, classified
267
+ by `domain-map.ts`'s `classifyPath` (the same module and glob lists that
268
+ guard the emitted baseline — broadened to cover common non-baseline
269
+ equivalents like `.eslintrc.*`/`jest.config.*`/`.husky/**`). A config file
270
+ that classifies as `uncovered` is a **reportable coverage gap**, exactly the
271
+ adopt-mode analogue of the structural test that guards `templates/core` —
272
+ name it in Step 6's report rather than silently skipping it.
273
+
274
+ **Applying findings.** A Round 2 finding carrying an allowlisted source URL
275
+ outranks both the baseline and Round 1, and should be applied. Name in your
276
+ final summary every place the findings disagreed with what Round 0/1
277
+ shipped — that disagreement list is the feature: it's the evidence the
278
+ baseline had gone stale.
279
+
280
+ **The one exception.** Where a finding would undo an **explicit interview
281
+ answer** from Step 1 — the user said tests must not gate `pre-push`,
282
+ guidance says they should — surface the conflict and leave the user's
283
+ answer standing. Guidance refines the _how_; the interview sets the _what_.
284
+ Record the conflict in the relevant tracker either way, so it isn't silently
285
+ rediscovered next sweep.
286
+
287
+ ## Step 5 — No network
288
+
289
+ If neither guidance skill can reach its sources (offline, sandboxed, no
290
+ `WebFetch`/`WebSearch` available): **skip Round 2 entirely, say so plainly,
291
+ write no tracker update, and leave Rounds 0 and 1 standing.** A tracker
292
+ stamped with a `last-verified` date and no real sweep behind it is worse
293
+ than an honest `unset` — the next sweep would trust a lie. Report exactly
294
+ which round the customization stopped at.
295
+
296
+ ## Step 6 — Report
297
+
298
+ One-line-per-item summary: the five interview answers, what Round 1 changed
299
+ deterministically, what Round 2's two sweeps found and applied (or "skipped
300
+ — no network"), and the current state of both trackers (`last-verified=` and
301
+ outstanding drift, if any). **In adopt mode**, add: what Step 0 found that
302
+ the CLI's report missed (if anything), which conflicts were resolved and
303
+ how, any domain-map coverage gap Step 4 surfaced, and **which packs were
304
+ installed and what each wired** (or, for a pack whose gate had no runner to
305
+ attach to, that it was skipped and why).