@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,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grades a project's TypeScript toolchain. `gradeToolchain` reads the tsconfig
|
|
3
|
+
* chains, ESLint and vitest configs, `package.json`, and the verify-step
|
|
4
|
+
* wiring once into a `ToolchainSnapshot`, then runs every rule in `rules.ts`
|
|
5
|
+
* over it. Offline and read-only -- it reads project files and never runs
|
|
6
|
+
* them, so it is safe to point at an adopted project.
|
|
7
|
+
*/
|
|
8
|
+
import { readFileSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { readJsoncFile, stripJsoncNoise } from "../jsonc.js";
|
|
11
|
+
import { walkBounded } from "../survey/fs-walk.js";
|
|
12
|
+
import { RULES } from "./rules.js";
|
|
13
|
+
import { loadTsconfigChain } from "./tsconfig-chain.js";
|
|
14
|
+
import { TOOLCHAIN_CATEGORIES } from "./types.js";
|
|
15
|
+
const PROJECT_WALK_DEPTH = 6;
|
|
16
|
+
const FLAT_ESLINT = [
|
|
17
|
+
"eslint.config.js",
|
|
18
|
+
"eslint.config.mjs",
|
|
19
|
+
"eslint.config.cjs",
|
|
20
|
+
"eslint.config.ts",
|
|
21
|
+
"eslint.config.mts",
|
|
22
|
+
"eslint.config.cts",
|
|
23
|
+
];
|
|
24
|
+
const LEGACY_ESLINT = /^\.eslintrc(\.(js|cjs|json|ya?ml))?$/;
|
|
25
|
+
const VITEST_CONFIGS = [
|
|
26
|
+
"vitest.config.ts",
|
|
27
|
+
"vitest.config.mts",
|
|
28
|
+
"vitest.config.cts",
|
|
29
|
+
"vitest.config.js",
|
|
30
|
+
"vitest.config.mjs",
|
|
31
|
+
"vitest.config.cjs",
|
|
32
|
+
];
|
|
33
|
+
const STEPS_PATH = "bin/lib/verify-steps.mjs";
|
|
34
|
+
const PACKS_PATH = "bin/lib/verify-steps.packs.json";
|
|
35
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
36
|
+
/** File text exactly as written, or `undefined` when unreadable. */
|
|
37
|
+
function readRaw(path) {
|
|
38
|
+
try {
|
|
39
|
+
return readFileSync(path, "utf8");
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** File text with comments stripped, or `undefined` when unreadable. */
|
|
46
|
+
function readSource(path) {
|
|
47
|
+
try {
|
|
48
|
+
return stripJsoncNoise(readFileSync(path, "utf8"));
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Reads `verify-steps.mjs` as data: the `GROUPS` list and every `{ id, group, cmd }` literal. */
|
|
55
|
+
function scrapeGateSteps(text) {
|
|
56
|
+
const groupList = /\bGROUPS\s*=\s*\[([^\]]*)\]/.exec(text);
|
|
57
|
+
const groups = groupList === null
|
|
58
|
+
? []
|
|
59
|
+
: [...(groupList[1] ?? "").matchAll(/"([^"]+)"/g)].map((m) => m[1] ?? "");
|
|
60
|
+
const steps = [];
|
|
61
|
+
for (const match of text.matchAll(/\bid:\s*"([^"]+)"([^{}]*?)\bcmd:\s*\[([^\]]*)\]/g)) {
|
|
62
|
+
const group = /\bgroup:\s*"([^"]+)"/.exec(match[2] ?? "");
|
|
63
|
+
steps.push({
|
|
64
|
+
id: match[1] ?? "",
|
|
65
|
+
group: group?.[1],
|
|
66
|
+
cmd: [...(match[3] ?? "").matchAll(/"([^"]*)"/g)].map((m) => m[1] ?? ""),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return { groups, steps };
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Reads every `verify.mjs` invocation out of one YAML surface as data: the
|
|
73
|
+
* `--group`/`--step` each names. YAML is scraped, never parsed. An invocation
|
|
74
|
+
* naming neither (a matrix, or a bare full run) marks the surface `dynamic`.
|
|
75
|
+
*/
|
|
76
|
+
function scrapeLaneInvocations(text) {
|
|
77
|
+
const groups = [];
|
|
78
|
+
const steps = [];
|
|
79
|
+
let dynamic = false;
|
|
80
|
+
let seen = false;
|
|
81
|
+
for (const raw of text.split("\n")) {
|
|
82
|
+
const line = raw.replace(/(^|\s)#.*$/, "");
|
|
83
|
+
const at = line.indexOf("verify.mjs");
|
|
84
|
+
if (at === -1)
|
|
85
|
+
continue;
|
|
86
|
+
seen = true;
|
|
87
|
+
const rest = line.slice(at);
|
|
88
|
+
const group = /--group[ =]+([A-Za-z][\w-]*)/.exec(rest);
|
|
89
|
+
const step = /--step[ =]+([A-Za-z][\w-]*)/.exec(rest);
|
|
90
|
+
if (group?.[1] !== undefined)
|
|
91
|
+
groups.push(group[1]);
|
|
92
|
+
else if (step?.[1] !== undefined)
|
|
93
|
+
steps.push(step[1]);
|
|
94
|
+
else
|
|
95
|
+
dynamic = true;
|
|
96
|
+
}
|
|
97
|
+
return { seen, groups, steps, dynamic };
|
|
98
|
+
}
|
|
99
|
+
function loadSnapshot(root) {
|
|
100
|
+
const entries = walkBounded(root, PROJECT_WALK_DEPTH);
|
|
101
|
+
const projectFiles = new Set(entries.filter((e) => !e.isDirectory).map((e) => e.relPath));
|
|
102
|
+
const rootFiles = [...projectFiles].filter((p) => !p.includes("/"));
|
|
103
|
+
const packageRead = readJsoncFile(join(root, "package.json"));
|
|
104
|
+
const packageJson = packageRead.ok && isRecord(packageRead.value)
|
|
105
|
+
? packageRead.value
|
|
106
|
+
: undefined;
|
|
107
|
+
const scripts = {};
|
|
108
|
+
const rawScripts = packageJson?.["scripts"];
|
|
109
|
+
if (isRecord(rawScripts)) {
|
|
110
|
+
for (const [name, cmd] of Object.entries(rawScripts)) {
|
|
111
|
+
if (typeof cmd === "string")
|
|
112
|
+
scripts[name] = cmd;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const tsconfigFiles = new Map();
|
|
116
|
+
const tsconfigLinks = [];
|
|
117
|
+
const chains = rootFiles
|
|
118
|
+
.filter((name) => /^tsconfig(\..+)?\.json$/.test(name))
|
|
119
|
+
.sort()
|
|
120
|
+
.map((name) => loadTsconfigChain(root, name));
|
|
121
|
+
for (const chain of chains) {
|
|
122
|
+
for (const file of chain.files) {
|
|
123
|
+
// A node_modules base is read for its options, never graded as ours.
|
|
124
|
+
if (file.rel.startsWith("node_modules/") || file.rel.startsWith("..")) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (!tsconfigFiles.has(file.rel)) {
|
|
128
|
+
tsconfigFiles.set(file.rel, {
|
|
129
|
+
rel: file.rel,
|
|
130
|
+
error: file.error,
|
|
131
|
+
options: file.options,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
for (const link of chain.links) {
|
|
136
|
+
if (!tsconfigLinks.some((l) => l.from === link.from && l.specifier === link.specifier)) {
|
|
137
|
+
tsconfigLinks.push(link);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const flatFile = FLAT_ESLINT.find((name) => projectFiles.has(name));
|
|
142
|
+
const vitestFile = VITEST_CONFIGS.find((name) => projectFiles.has(name));
|
|
143
|
+
const stepsText = projectFiles.has(STEPS_PATH)
|
|
144
|
+
? readSource(join(root, STEPS_PATH))
|
|
145
|
+
: undefined;
|
|
146
|
+
const packsRead = projectFiles.has(PACKS_PATH)
|
|
147
|
+
? readJsoncFile(join(root, PACKS_PATH))
|
|
148
|
+
: undefined;
|
|
149
|
+
const nodeVersionText = projectFiles.has(".node-version")
|
|
150
|
+
? readSource(join(root, ".node-version"))
|
|
151
|
+
: undefined;
|
|
152
|
+
const lanes = [];
|
|
153
|
+
const laneSurfaces = [
|
|
154
|
+
[
|
|
155
|
+
rootFiles.find((name) => /^lefthook\.ya?ml$/.test(name)) ??
|
|
156
|
+
"lefthook.yml",
|
|
157
|
+
rootFiles.filter((name) => /^lefthook\.ya?ml$/.test(name)),
|
|
158
|
+
],
|
|
159
|
+
[
|
|
160
|
+
".github/workflows",
|
|
161
|
+
[...projectFiles].filter((p) => /^\.github\/workflows\/[^/]+\.ya?ml$/.test(p)),
|
|
162
|
+
],
|
|
163
|
+
];
|
|
164
|
+
for (const [surface, paths] of laneSurfaces) {
|
|
165
|
+
const text = paths.map((p) => readRaw(join(root, p)) ?? "").join("\n");
|
|
166
|
+
const lane = scrapeLaneInvocations(text);
|
|
167
|
+
// A surface that never runs verify.mjs is not judged: absence is no defect.
|
|
168
|
+
if (lane.seen) {
|
|
169
|
+
lanes.push({
|
|
170
|
+
surface,
|
|
171
|
+
groups: lane.groups,
|
|
172
|
+
steps: lane.steps,
|
|
173
|
+
dynamic: lane.dynamic,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
packageJson,
|
|
179
|
+
scripts,
|
|
180
|
+
nodeVersion: nodeVersionText?.trim(),
|
|
181
|
+
tsconfigFiles,
|
|
182
|
+
tsconfigLinks,
|
|
183
|
+
chains,
|
|
184
|
+
eslint: {
|
|
185
|
+
flatFile,
|
|
186
|
+
source: flatFile === undefined ? undefined : readSource(join(root, flatFile)),
|
|
187
|
+
legacy: rootFiles.filter((name) => LEGACY_ESLINT.test(name)),
|
|
188
|
+
},
|
|
189
|
+
vitest: {
|
|
190
|
+
file: vitestFile,
|
|
191
|
+
source: vitestFile === undefined
|
|
192
|
+
? undefined
|
|
193
|
+
: readSource(join(root, vitestFile)),
|
|
194
|
+
},
|
|
195
|
+
gates: {
|
|
196
|
+
stepsFile: stepsText === undefined ? undefined : STEPS_PATH,
|
|
197
|
+
...(stepsText === undefined
|
|
198
|
+
? { groups: [], steps: [] }
|
|
199
|
+
: scrapeGateSteps(stepsText)),
|
|
200
|
+
packs: packsRead === undefined
|
|
201
|
+
? undefined
|
|
202
|
+
: {
|
|
203
|
+
path: PACKS_PATH,
|
|
204
|
+
error: packsRead.ok ? undefined : packsRead.error,
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
lanes,
|
|
208
|
+
projectFiles,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
/** Runs every rule over the project rooted at `rootDir` and tallies the result. */
|
|
212
|
+
export function gradeToolchain(rootDir) {
|
|
213
|
+
const snapshot = loadSnapshot(rootDir);
|
|
214
|
+
const findings = [];
|
|
215
|
+
const structural = { checked: 0, failed: 0 };
|
|
216
|
+
const rubric = Object.fromEntries(TOOLCHAIN_CATEGORIES.map((category) => [
|
|
217
|
+
category,
|
|
218
|
+
{ checked: 0, failed: 0 },
|
|
219
|
+
]));
|
|
220
|
+
for (const rule of RULES) {
|
|
221
|
+
const result = rule.check(snapshot);
|
|
222
|
+
const tally = rule.level === "structural" ? structural : rubric[rule.category];
|
|
223
|
+
tally.checked += result.checked;
|
|
224
|
+
tally.failed += result.failures.length;
|
|
225
|
+
for (const failure of result.failures) {
|
|
226
|
+
findings.push({
|
|
227
|
+
ruleId: rule.id,
|
|
228
|
+
level: rule.level,
|
|
229
|
+
category: rule.category,
|
|
230
|
+
subject: failure.subject,
|
|
231
|
+
message: failure.message,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
const rubricChecked = Object.values(rubric).reduce((sum, tally) => sum + tally.checked, 0);
|
|
236
|
+
const rubricFailed = Object.values(rubric).reduce((sum, tally) => sum + tally.failed, 0);
|
|
237
|
+
return {
|
|
238
|
+
findings,
|
|
239
|
+
structural,
|
|
240
|
+
rubric,
|
|
241
|
+
rubricScore: rubricChecked === 0 ? 1 : 1 - rubricFailed / rubricChecked,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
//# sourceMappingURL=grade.js.map
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The toolchain grader's rules. Each rule is a pure function over a
|
|
3
|
+
* `ToolchainSnapshot` read once by `grade.ts`, so none of them touches the
|
|
4
|
+
* filesystem. Structural rules catch wiring defects `tsc` and ESLint do not --
|
|
5
|
+
* a build project that emits nowhere, a verify step naming a script that does
|
|
6
|
+
* not exist -- and fail a gate. Rubric rules encode the floor official
|
|
7
|
+
* TypeScript / typescript-eslint guidance sets and only ever warn.
|
|
8
|
+
*
|
|
9
|
+
* `eslint.config.js`, `vitest.config.ts` and `verify-steps.mjs` are executable
|
|
10
|
+
* JavaScript, so their rules are regex scrapes over comment-stripped source,
|
|
11
|
+
* never evaluation. A scrape that finds the file but cannot tell the answer
|
|
12
|
+
* returns `{ checked: 0 }` rather than a failure: absence, or a shape this
|
|
13
|
+
* module cannot read, is never a defect.
|
|
14
|
+
*
|
|
15
|
+
* `templates/core/bin/lib/toolchain-rules.mjs` carries the same rules for the
|
|
16
|
+
* emitted gate; `tests/toolchain/toolchain-parity.test.ts` runs both over the
|
|
17
|
+
* real baseline and asserts identical findings. Change a rule in one, change
|
|
18
|
+
* the other in the same commit.
|
|
19
|
+
*/
|
|
20
|
+
import type { RuleLevel } from "../harness/types.js";
|
|
21
|
+
import type { ChainLink, TsconfigChain } from "./tsconfig-chain.js";
|
|
22
|
+
import type { ToolchainCategory } from "./types.js";
|
|
23
|
+
/** A tsconfig file the project itself owns (a `node_modules` base is not graded as ours). */
|
|
24
|
+
export interface OwnTsconfigFile {
|
|
25
|
+
rel: string;
|
|
26
|
+
error: string | undefined;
|
|
27
|
+
options: Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
export interface GateStep {
|
|
30
|
+
id: string;
|
|
31
|
+
group: string | undefined;
|
|
32
|
+
cmd: string[];
|
|
33
|
+
}
|
|
34
|
+
/** The `verify.mjs` invocations one YAML surface (lefthook, CI workflows) makes, scraped as data. */
|
|
35
|
+
export interface LaneSurface {
|
|
36
|
+
surface: string;
|
|
37
|
+
groups: string[];
|
|
38
|
+
steps: string[];
|
|
39
|
+
/** An invocation named no static group or step (a matrix, or a bare full run). */
|
|
40
|
+
dynamic: boolean;
|
|
41
|
+
}
|
|
42
|
+
export interface ToolchainSnapshot {
|
|
43
|
+
/** Parsed `package.json`, or `undefined` when absent or unparseable. */
|
|
44
|
+
packageJson: Record<string, unknown> | undefined;
|
|
45
|
+
/** `package.json` `scripts`, string values only. */
|
|
46
|
+
scripts: Record<string, string>;
|
|
47
|
+
/** Trimmed `.node-version`, or `undefined` when absent. */
|
|
48
|
+
nodeVersion: string | undefined;
|
|
49
|
+
tsconfigFiles: Map<string, OwnTsconfigFile>;
|
|
50
|
+
tsconfigLinks: ChainLink[];
|
|
51
|
+
/** One chain per root-level `tsconfig*.json`. */
|
|
52
|
+
chains: TsconfigChain[];
|
|
53
|
+
eslint: {
|
|
54
|
+
flatFile: string | undefined;
|
|
55
|
+
/** Comment-stripped source. Rules regex-scrape it; nothing evaluates it. */
|
|
56
|
+
source: string | undefined;
|
|
57
|
+
legacy: string[];
|
|
58
|
+
};
|
|
59
|
+
vitest: {
|
|
60
|
+
file: string | undefined;
|
|
61
|
+
source: string | undefined;
|
|
62
|
+
};
|
|
63
|
+
gates: {
|
|
64
|
+
stepsFile: string | undefined;
|
|
65
|
+
groups: string[];
|
|
66
|
+
steps: GateStep[];
|
|
67
|
+
packs: {
|
|
68
|
+
path: string;
|
|
69
|
+
error: string | undefined;
|
|
70
|
+
} | undefined;
|
|
71
|
+
};
|
|
72
|
+
/** Surfaces that run `verify.mjs`; one that never does is absent, not failed. */
|
|
73
|
+
lanes: LaneSurface[];
|
|
74
|
+
/** Every project file (bounded walk), project-relative. */
|
|
75
|
+
projectFiles: Set<string>;
|
|
76
|
+
}
|
|
77
|
+
interface RuleFailure {
|
|
78
|
+
subject: string;
|
|
79
|
+
message: string;
|
|
80
|
+
}
|
|
81
|
+
interface RuleResult {
|
|
82
|
+
/** How many subjects the rule examined -- the denominator of the rubric score. */
|
|
83
|
+
checked: number;
|
|
84
|
+
failures: RuleFailure[];
|
|
85
|
+
}
|
|
86
|
+
export interface ToolchainRule {
|
|
87
|
+
id: string;
|
|
88
|
+
level: RuleLevel;
|
|
89
|
+
category: ToolchainCategory;
|
|
90
|
+
check: (snapshot: ToolchainSnapshot) => RuleResult;
|
|
91
|
+
}
|
|
92
|
+
interface LegacyOption {
|
|
93
|
+
option: string;
|
|
94
|
+
label: string;
|
|
95
|
+
deprecatedIn: number;
|
|
96
|
+
removedIn: number;
|
|
97
|
+
matches: (value: unknown) => boolean;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Options TypeScript 6.0 deprecated. `removedIn` is the major that drops them
|
|
101
|
+
* (`outFile` and `moduleResolution: classic` already went in 6.0).
|
|
102
|
+
* https://www.typescriptlang.org/docs/handbook/release-notes/typescript-6-0.html
|
|
103
|
+
*/
|
|
104
|
+
export declare const LEGACY_OPTIONS: readonly LegacyOption[];
|
|
105
|
+
/**
|
|
106
|
+
* The strict-family flags the baseline enables, plus `skipLibCheck`. Three
|
|
107
|
+
* omissions are deliberate and must not be "completed": `isolatedDeclarations`
|
|
108
|
+
* (the build project sets it alone; the tooling project includes tests/, which
|
|
109
|
+
* it does not tolerate), `noUnusedLocals`/`noUnusedParameters`
|
|
110
|
+
* (`@typescript-eslint/no-unused-vars` covers both with an `^_` escape hatch
|
|
111
|
+
* tsc's flags lack), and `forceConsistentCasingInFileNames` (defaults on).
|
|
112
|
+
* `allowUnreachableCode` is graded separately -- it must be `false`.
|
|
113
|
+
*/
|
|
114
|
+
export declare const STRICT_FLAGS: readonly string[];
|
|
115
|
+
/** Every rule, structural first. Order is the order findings are reported in. */
|
|
116
|
+
export declare const RULES: readonly ToolchainRule[];
|
|
117
|
+
export {};
|
|
118
|
+
//# sourceMappingURL=rules.d.ts.map
|