@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.
- package/README.md +23 -0
- package/bin/m3l-groundwork.mjs +10 -0
- package/dist/assets.d.ts +20 -0
- package/dist/assets.js +79 -0
- package/dist/caps.d.ts +25 -0
- package/dist/caps.js +69 -0
- package/dist/conflicts.d.ts +12 -0
- package/dist/conflicts.js +77 -0
- package/dist/emit.d.ts +7 -0
- package/dist/emit.js +42 -0
- package/dist/git.d.ts +3 -0
- package/dist/git.js +9 -0
- package/dist/harness/conformance.d.ts +20 -0
- package/dist/harness/conformance.js +18 -0
- package/dist/harness/frontmatter.d.ts +38 -0
- package/dist/harness/frontmatter.js +204 -0
- package/dist/harness/grade.d.ts +4 -0
- package/dist/harness/grade.js +105 -0
- package/dist/harness/rules.d.ts +55 -0
- package/dist/harness/rules.js +580 -0
- package/dist/harness/types.d.ts +32 -0
- package/dist/harness/types.js +9 -0
- package/dist/inventory.d.ts +63 -0
- package/dist/inventory.js +66 -0
- package/dist/jsonc.d.ts +14 -0
- package/dist/jsonc.js +83 -0
- package/dist/main.d.ts +24 -0
- package/dist/main.js +297 -0
- package/dist/merge-json.d.ts +74 -0
- package/dist/merge-json.js +135 -0
- package/dist/mode.d.ts +19 -0
- package/dist/mode.js +53 -0
- package/dist/packs.d.ts +61 -0
- package/dist/packs.js +186 -0
- package/dist/plugin.d.ts +23 -0
- package/dist/plugin.js +79 -0
- package/dist/report.d.ts +4 -0
- package/dist/report.js +323 -0
- package/dist/survey/fs-walk.d.ts +14 -0
- package/dist/survey/fs-walk.js +60 -0
- package/dist/survey/survey-docs.d.ts +4 -0
- package/dist/survey/survey-docs.js +69 -0
- package/dist/survey/survey-harness.d.ts +4 -0
- package/dist/survey/survey-harness.js +121 -0
- package/dist/survey/survey-shape.d.ts +4 -0
- package/dist/survey/survey-shape.js +182 -0
- package/dist/survey/survey-toolchain.d.ts +4 -0
- package/dist/survey/survey-toolchain.js +217 -0
- package/dist/survey/survey.d.ts +5 -0
- package/dist/survey/survey.js +21 -0
- package/dist/survey/types.d.ts +117 -0
- package/dist/survey/types.js +8 -0
- package/dist/tokens.d.ts +13 -0
- package/dist/tokens.js +13 -0
- package/dist/toolchain/conformance.d.ts +20 -0
- package/dist/toolchain/conformance.js +30 -0
- package/dist/toolchain/grade.d.ts +4 -0
- package/dist/toolchain/grade.js +244 -0
- package/dist/toolchain/rules.d.ts +118 -0
- package/dist/toolchain/rules.js +706 -0
- package/dist/toolchain/tsconfig-chain.d.ts +36 -0
- package/dist/toolchain/tsconfig-chain.js +116 -0
- package/dist/toolchain/types.d.ts +27 -0
- package/dist/toolchain/types.js +9 -0
- package/package.json +59 -0
- package/plugin/skills/customize/SKILL.md +305 -0
- package/plugin/src/domain-map.ts +134 -0
- package/plugin/src/index.ts +4 -0
- package/plugin/src/kind-facet-map.ts +174 -0
- package/plugin/src/pack-map.ts +65 -0
- package/templates/core/.claude/agents/Explore.md +43 -0
- package/templates/core/.claude/agents/code-implementer.md +258 -0
- package/templates/core/.claude/agents/code-reviewer.md +163 -0
- package/templates/core/.claude/agents/silent-failure-hunter.md +191 -0
- package/templates/core/.claude/agents/test-author.md +211 -0
- package/templates/core/.claude/hooks/guard-branch-isolation.mjs +123 -0
- package/templates/core/.claude/hooks/guard-double-background.mjs +113 -0
- package/templates/core/.claude/hooks/guard-git-push-signed.mjs +90 -0
- package/templates/core/.claude/hooks/guard-hub-src-writes.mjs +88 -0
- package/templates/core/.claude/hooks/guard-js-extension.mjs +66 -0
- package/templates/core/.claude/hooks/guard-no-commonjs.mjs +105 -0
- package/templates/core/.claude/hooks/guard-protected-paths.mjs +45 -0
- package/templates/core/.claude/hooks/guard-secret-writes.mjs +183 -0
- package/templates/core/.claude/hooks/inject-decision-gate.mjs +119 -0
- package/templates/core/.claude/hooks/post-edit-verify.mjs +150 -0
- package/templates/core/.claude/rules/agent-dispatch.md +121 -0
- package/templates/core/.claude/rules/refactoring.md +52 -0
- package/templates/core/.claude/rules/src.md +114 -0
- package/templates/core/.claude/rules/tests.md +129 -0
- package/templates/core/.claude/settings.json +111 -0
- package/templates/core/.claude/skills/creating-prs/SKILL.md +132 -0
- package/templates/core/.claude/skills/finishing-work/SKILL.md +117 -0
- package/templates/core/.claude/skills/harness-guidance/SKILL.md +140 -0
- package/templates/core/.claude/skills/harness-guidance/references/official-sources.md +58 -0
- package/templates/core/.claude/skills/starting-work/SKILL.md +94 -0
- package/templates/core/.claude/skills/triaging-ci/SKILL.md +111 -0
- package/templates/core/.claude/skills/typescript-guidance/SKILL.md +143 -0
- package/templates/core/.claude/skills/typescript-guidance/references/typescript-sources.md +102 -0
- package/templates/core/.claude/skills/writing-commits/SKILL.md +248 -0
- package/templates/core/.github/workflows/ci.yml +123 -0
- package/templates/core/.github/workflows/dependency-review.yml +26 -0
- package/templates/core/.github/workflows/security-audit.yml +54 -0
- package/templates/core/.node-version +1 -0
- package/templates/core/.prettierignore +5 -0
- package/templates/core/.prettierrc.json +4 -0
- package/templates/core/CLAUDE.md +127 -0
- package/templates/core/README.md +24 -0
- package/templates/core/_gitignore +19 -0
- package/templates/core/_npmrc +1 -0
- package/templates/core/bin/check-exports.mjs +92 -0
- package/templates/core/bin/check-harness.mjs +27 -0
- package/templates/core/bin/check-node-version.mjs +51 -0
- package/templates/core/bin/check-toolchain.mjs +20 -0
- package/templates/core/bin/lib/agent-roster.mjs +8 -0
- package/templates/core/bin/lib/frontmatter.mjs +210 -0
- package/templates/core/bin/lib/harness-rules.mjs +916 -0
- package/templates/core/bin/lib/protected-paths.mjs +23 -0
- package/templates/core/bin/lib/report.mjs +56 -0
- package/templates/core/bin/lib/signed-range.mjs +178 -0
- package/templates/core/bin/lib/toolchain-rules.mjs +1264 -0
- package/templates/core/bin/lib/verify-steps.mjs +131 -0
- package/templates/core/bin/lib/verify-steps.packs.json +1 -0
- package/templates/core/bin/lint-commit.mjs +50 -0
- package/templates/core/bin/strip-claude-trailers.mjs +25 -0
- package/templates/core/bin/verify.mjs +64 -0
- package/templates/core/commitlint.config.js +11 -0
- package/templates/core/docs/research/harness-refresh.md +27 -0
- package/templates/core/docs/research/typescript-refresh.md +32 -0
- package/templates/core/eslint.config.js +105 -0
- package/templates/core/knip.json +6 -0
- package/templates/core/lefthook.yml +39 -0
- package/templates/core/package.json +58 -0
- package/templates/core/pnpm-workspace.yaml +13 -0
- package/templates/core/src/index.ts +12 -0
- package/templates/core/tests/index.test.ts +8 -0
- package/templates/core/tsconfig.base.json +36 -0
- package/templates/core/tsconfig.build.json +10 -0
- package/templates/core/tsconfig.json +11 -0
- package/templates/core/vitest.config.ts +32 -0
- package/templates/packs/README.md +81 -0
- package/templates/packs/harness-extras/files/.claude/agents/type-design-analyzer.md +188 -0
- package/templates/packs/harness-extras/files/.claude/hooks/guard-readonly-bash.mjs +324 -0
- package/templates/packs/harness-extras/files/.claude/hooks/reinject-compact-handoff.mjs +197 -0
- package/templates/packs/harness-extras/files/.claude/hooks/write-compact-handoff.mjs +180 -0
- package/templates/packs/harness-extras/files/bin/check-file-budget.mjs +407 -0
- package/templates/packs/harness-extras/files/bin/file-budget-baseline.json +1 -0
- package/templates/packs/harness-extras/pack.json +65 -0
- package/templates/packs/statusline/files/.claude/hooks/statusline-layout.mjs +365 -0
- package/templates/packs/statusline/files/.claude/hooks/statusline.mjs +996 -0
- package/templates/packs/statusline/files/.claude/hooks/subagent-statusline.mjs +203 -0
- 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
|
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).
|