@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,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A reader for the YAML subset Claude Code frontmatter actually uses --
|
|
3
|
+
* `SKILL.md`, agent, and rule files. Hand-rolled because this package has no
|
|
4
|
+
* runtime dependencies (see `jsonc.ts` for the same trade-off). It handles
|
|
5
|
+
* the scalar forms real harness files contain: plain, single/double quoted,
|
|
6
|
+
* block scalars (`>-`, `>`, `|`, `|-`), block lists, and flow lists.
|
|
7
|
+
*
|
|
8
|
+
* Deliberately NOT supported: nested mappings (a key whose value is an
|
|
9
|
+
* indented map, e.g. `mcpServers:` with inline server definitions) -- such a
|
|
10
|
+
* key is recorded with an empty string value rather than misparsed -- plus
|
|
11
|
+
* anchors, tags, and multi-document streams. Every string result is
|
|
12
|
+
* trimmed; a block scalar's trailing newline is not preserved.
|
|
13
|
+
*/
|
|
14
|
+
const KEY_LINE = /^([A-Za-z_][\w-]*):(?:[ \t]+(.*))?$/;
|
|
15
|
+
const BLOCK_SCALAR = /^([>|])(?:[+-]\d?|\d[+-]?)?$/;
|
|
16
|
+
/** Removes the smallest common indent from every non-blank line. */
|
|
17
|
+
function dedent(lines) {
|
|
18
|
+
const indents = lines
|
|
19
|
+
.filter((line) => line.trim() !== "")
|
|
20
|
+
.map((line) => /^[ \t]*/.exec(line)?.[0].length ?? 0);
|
|
21
|
+
const strip = indents.length === 0 ? 0 : Math.min(...indents);
|
|
22
|
+
return lines.map((line) => line.slice(strip));
|
|
23
|
+
}
|
|
24
|
+
function foldBlockScalar(lines) {
|
|
25
|
+
const paragraphs = [[]];
|
|
26
|
+
for (const line of lines) {
|
|
27
|
+
if (line.trim() === "") {
|
|
28
|
+
paragraphs.push([]);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
paragraphs[paragraphs.length - 1]?.push(line.trim());
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return paragraphs
|
|
35
|
+
.filter((paragraph) => paragraph.length > 0)
|
|
36
|
+
.map((paragraph) => paragraph.join(" "))
|
|
37
|
+
.join("\n");
|
|
38
|
+
}
|
|
39
|
+
/** Unquotes one YAML scalar; an unterminated quote yields `undefined`. */
|
|
40
|
+
function unquote(text) {
|
|
41
|
+
const trimmed = text.trim();
|
|
42
|
+
const quote = trimmed[0];
|
|
43
|
+
if (quote !== '"' && quote !== "'") {
|
|
44
|
+
return trimmed.replace(/\s+#.*$/, "");
|
|
45
|
+
}
|
|
46
|
+
let out = "";
|
|
47
|
+
for (let i = 1; i < trimmed.length; i++) {
|
|
48
|
+
const ch = trimmed[i];
|
|
49
|
+
if (quote === '"' && ch === "\\") {
|
|
50
|
+
const escaped = trimmed[i + 1];
|
|
51
|
+
out += escaped === "n" ? "\n" : escaped === "t" ? "\t" : (escaped ?? "");
|
|
52
|
+
i++;
|
|
53
|
+
}
|
|
54
|
+
else if (quote === "'" && ch === "'" && trimmed[i + 1] === "'") {
|
|
55
|
+
out += "'";
|
|
56
|
+
i++;
|
|
57
|
+
}
|
|
58
|
+
else if (ch === quote) {
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
out += ch ?? "";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
/** Splits a flow-list body on commas that sit outside quotes. */
|
|
68
|
+
function splitFlowList(inner) {
|
|
69
|
+
const items = [];
|
|
70
|
+
let current = "";
|
|
71
|
+
let quote;
|
|
72
|
+
for (const ch of inner) {
|
|
73
|
+
if (quote !== undefined) {
|
|
74
|
+
current += ch;
|
|
75
|
+
if (ch === quote)
|
|
76
|
+
quote = undefined;
|
|
77
|
+
}
|
|
78
|
+
else if (ch === '"' || ch === "'") {
|
|
79
|
+
quote = ch;
|
|
80
|
+
current += ch;
|
|
81
|
+
}
|
|
82
|
+
else if (ch === ",") {
|
|
83
|
+
items.push(current);
|
|
84
|
+
current = "";
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
current += ch;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
items.push(current);
|
|
91
|
+
return items.map((item) => item.trim()).filter((item) => item !== "");
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Parses the `---`-delimited frontmatter at the top of `content`. Returns a
|
|
95
|
+
* structured failure (never throws) when there is no block or it is never
|
|
96
|
+
* closed; lines it cannot interpret inside an otherwise valid block are
|
|
97
|
+
* reported in `problems`.
|
|
98
|
+
*/
|
|
99
|
+
export function parseFrontmatter(content) {
|
|
100
|
+
const lines = content.replace(/\r\n/g, "\n").split("\n");
|
|
101
|
+
if (lines[0]?.trimEnd() !== "---") {
|
|
102
|
+
return {
|
|
103
|
+
ok: false,
|
|
104
|
+
error: "no frontmatter block (the file must start with `---`)",
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
const end = lines.findIndex((line, index) => index > 0 && line.trimEnd() === "---");
|
|
108
|
+
if (end === -1) {
|
|
109
|
+
return {
|
|
110
|
+
ok: false,
|
|
111
|
+
error: "frontmatter block is never closed with a `---` line",
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const block = lines.slice(1, end);
|
|
115
|
+
const body = lines.slice(end + 1).join("\n");
|
|
116
|
+
const fields = new Map();
|
|
117
|
+
const problems = [];
|
|
118
|
+
let i = 0;
|
|
119
|
+
while (i < block.length) {
|
|
120
|
+
const line = block[i] ?? "";
|
|
121
|
+
if (line.trim() === "" || line.trimStart().startsWith("#")) {
|
|
122
|
+
i++;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const match = KEY_LINE.exec(line);
|
|
126
|
+
if (match?.[1] === undefined) {
|
|
127
|
+
problems.push(`unparseable frontmatter line: ${line.trim()}`);
|
|
128
|
+
i++;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
const key = match[1];
|
|
132
|
+
const rest = (match[2] ?? "").trim();
|
|
133
|
+
const continuation = [];
|
|
134
|
+
let j = i + 1;
|
|
135
|
+
while (j < block.length) {
|
|
136
|
+
const next = block[j] ?? "";
|
|
137
|
+
if (next.trim() === "" || /^\s/.test(next) || /^-(\s|$)/.test(next)) {
|
|
138
|
+
continuation.push(next);
|
|
139
|
+
j++;
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
i = j;
|
|
146
|
+
if (fields.has(key)) {
|
|
147
|
+
problems.push(`duplicate frontmatter key: ${key}`);
|
|
148
|
+
}
|
|
149
|
+
const blockScalar = BLOCK_SCALAR.exec(rest);
|
|
150
|
+
if (blockScalar !== null) {
|
|
151
|
+
const dedented = dedent(continuation);
|
|
152
|
+
const text = blockScalar[1] === ">"
|
|
153
|
+
? foldBlockScalar(dedented)
|
|
154
|
+
: dedented.join("\n");
|
|
155
|
+
fields.set(key, text.trim());
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (rest === "") {
|
|
159
|
+
const items = continuation
|
|
160
|
+
.filter((entry) => /^\s*-(\s|$)/.test(entry))
|
|
161
|
+
.map((entry) => unquote(entry.replace(/^\s*-\s*/, "")) ?? "")
|
|
162
|
+
.filter((item) => item !== "");
|
|
163
|
+
fields.set(key, items.length > 0 ? items : "");
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
const joined = [rest, ...continuation.map((entry) => entry.trim())]
|
|
167
|
+
.filter((part) => part !== "")
|
|
168
|
+
.join(" ");
|
|
169
|
+
if (rest.startsWith("[")) {
|
|
170
|
+
if (!joined.trimEnd().endsWith("]")) {
|
|
171
|
+
problems.push(`unterminated flow list for key: ${key}`);
|
|
172
|
+
fields.set(key, "");
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
const inner = joined.trim().slice(1, -1);
|
|
176
|
+
fields.set(key, splitFlowList(inner).map((item) => unquote(item) ?? item));
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const scalar = unquote(joined);
|
|
180
|
+
if (scalar === undefined) {
|
|
181
|
+
problems.push(`unterminated quoted string for key: ${key}`);
|
|
182
|
+
fields.set(key, "");
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
fields.set(key, scalar);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return { ok: true, fields, body, problems };
|
|
189
|
+
}
|
|
190
|
+
/** A field as one string -- a list is joined with `, `. `undefined` when absent. */
|
|
191
|
+
export function fieldText(fields, key) {
|
|
192
|
+
const value = fields.get(key);
|
|
193
|
+
return Array.isArray(value) ? value.join(", ") : value;
|
|
194
|
+
}
|
|
195
|
+
/** A field as a list -- a non-empty scalar becomes a one-item list. `undefined` when absent. */
|
|
196
|
+
export function fieldList(fields, key) {
|
|
197
|
+
const value = fields.get(key);
|
|
198
|
+
if (value === undefined)
|
|
199
|
+
return undefined;
|
|
200
|
+
if (Array.isArray(value))
|
|
201
|
+
return value;
|
|
202
|
+
return value === "" ? [] : [value];
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=frontmatter.js.map
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grades a project's Claude Code harness. `gradeHarness` reads the `.claude/`
|
|
3
|
+
* tree and `CLAUDE.md` once into a `HarnessSnapshot`, then runs every rule in
|
|
4
|
+
* `rules.ts` over it. Offline and read-only -- safe to point at an adopted
|
|
5
|
+
* project.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { readJsoncFile } from "../jsonc.js";
|
|
10
|
+
import { walkBounded } from "../survey/fs-walk.js";
|
|
11
|
+
import { RULES } from "./rules.js";
|
|
12
|
+
import { HARNESS_CATEGORIES } from "./types.js";
|
|
13
|
+
const PROJECT_WALK_DEPTH = 8;
|
|
14
|
+
function readText(path) {
|
|
15
|
+
try {
|
|
16
|
+
return readFileSync(path, "utf8");
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function readSettings(path) {
|
|
23
|
+
if (!existsSync(path)) {
|
|
24
|
+
return { present: false, error: undefined, parsed: undefined };
|
|
25
|
+
}
|
|
26
|
+
const result = readJsoncFile(path);
|
|
27
|
+
return result.ok
|
|
28
|
+
? { present: true, error: undefined, parsed: result.value }
|
|
29
|
+
: { present: true, error: result.error, parsed: undefined };
|
|
30
|
+
}
|
|
31
|
+
function loadSnapshot(root) {
|
|
32
|
+
const entries = walkBounded(root, PROJECT_WALK_DEPTH);
|
|
33
|
+
const claudeEntries = entries.filter((entry) => entry.relPath.startsWith(".claude/"));
|
|
34
|
+
const readEach = (pattern, strip) => new Map(claudeEntries
|
|
35
|
+
.filter((entry) => !entry.isDirectory && pattern.test(entry.relPath))
|
|
36
|
+
.map((entry) => [
|
|
37
|
+
entry.relPath.slice(strip.length),
|
|
38
|
+
readText(entry.path) ?? "",
|
|
39
|
+
]));
|
|
40
|
+
const skills = claudeEntries
|
|
41
|
+
.filter((entry) => entry.isDirectory && /^\.claude\/skills\/[^/]+$/.test(entry.relPath))
|
|
42
|
+
.map((dir) => {
|
|
43
|
+
const prefix = `${dir.relPath}/`;
|
|
44
|
+
const files = claudeEntries
|
|
45
|
+
.filter((entry) => !entry.isDirectory && entry.relPath.startsWith(prefix))
|
|
46
|
+
.map((entry) => entry.relPath.slice(prefix.length));
|
|
47
|
+
return {
|
|
48
|
+
name: dir.relPath.slice(".claude/skills/".length),
|
|
49
|
+
skillMd: files.includes("SKILL.md")
|
|
50
|
+
? readText(join(dir.path, "SKILL.md"))
|
|
51
|
+
: undefined,
|
|
52
|
+
files,
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
const settingsLocalResult = readJsoncFile(join(root, ".claude", "settings.local.json"));
|
|
56
|
+
return {
|
|
57
|
+
settings: readSettings(join(root, ".claude", "settings.json")),
|
|
58
|
+
settingsLocal: settingsLocalResult.ok
|
|
59
|
+
? settingsLocalResult.value
|
|
60
|
+
: undefined,
|
|
61
|
+
hooks: readEach(/^\.claude\/hooks\/[^/]+$/, ".claude/hooks/"),
|
|
62
|
+
agents: readEach(/^\.claude\/agents\/[^/]+\.md$/, ".claude/agents/"),
|
|
63
|
+
skills,
|
|
64
|
+
rules: readEach(/^\.claude\/rules\/[^/]+\.md$/, ".claude/rules/"),
|
|
65
|
+
claudeMd: readText(join(root, "CLAUDE.md")),
|
|
66
|
+
claudeTree: new Set(claudeEntries.map((entry) => entry.relPath)),
|
|
67
|
+
projectFiles: entries
|
|
68
|
+
.filter((entry) => !entry.isDirectory)
|
|
69
|
+
.map((entry) => entry.relPath),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function emptyTally() {
|
|
73
|
+
return { checked: 0, failed: 0 };
|
|
74
|
+
}
|
|
75
|
+
/** Runs every rule over the harness rooted at `rootDir` and tallies the result. */
|
|
76
|
+
export function gradeHarness(rootDir) {
|
|
77
|
+
const snapshot = loadSnapshot(rootDir);
|
|
78
|
+
const findings = [];
|
|
79
|
+
const structural = emptyTally();
|
|
80
|
+
const rubric = Object.fromEntries(HARNESS_CATEGORIES.map((category) => [category, emptyTally()]));
|
|
81
|
+
for (const rule of RULES) {
|
|
82
|
+
const result = rule.check(snapshot);
|
|
83
|
+
const tally = rule.level === "structural" ? structural : rubric[rule.category];
|
|
84
|
+
tally.checked += result.checked;
|
|
85
|
+
tally.failed += result.failures.length;
|
|
86
|
+
for (const failure of result.failures) {
|
|
87
|
+
findings.push({
|
|
88
|
+
ruleId: rule.id,
|
|
89
|
+
level: rule.level,
|
|
90
|
+
category: rule.category,
|
|
91
|
+
subject: failure.subject,
|
|
92
|
+
message: failure.message,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const rubricChecked = Object.values(rubric).reduce((sum, tally) => sum + tally.checked, 0);
|
|
97
|
+
const rubricFailed = Object.values(rubric).reduce((sum, tally) => sum + tally.failed, 0);
|
|
98
|
+
return {
|
|
99
|
+
findings,
|
|
100
|
+
structural,
|
|
101
|
+
rubric,
|
|
102
|
+
rubricScore: rubricChecked === 0 ? 1 : 1 - rubricFailed / rubricChecked,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=grade.js.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { HarnessCategory, RuleLevel } from "./types.js";
|
|
2
|
+
export interface SkillSnapshot {
|
|
3
|
+
/** The skill's directory name. */
|
|
4
|
+
name: string;
|
|
5
|
+
/** `SKILL.md`'s text, or `undefined` when the file is missing. */
|
|
6
|
+
skillMd: string | undefined;
|
|
7
|
+
/** Every file under the skill directory, relative to it. */
|
|
8
|
+
files: string[];
|
|
9
|
+
}
|
|
10
|
+
export interface HarnessSnapshot {
|
|
11
|
+
settings: {
|
|
12
|
+
present: boolean;
|
|
13
|
+
error: string | undefined;
|
|
14
|
+
parsed: unknown;
|
|
15
|
+
};
|
|
16
|
+
/** `.claude/settings.local.json`, parsed, or `undefined` when absent/unparseable. */
|
|
17
|
+
settingsLocal: unknown;
|
|
18
|
+
/** Hook filename to source text. */
|
|
19
|
+
hooks: Map<string, string>;
|
|
20
|
+
/** Agent filename (with `.md`) to text. */
|
|
21
|
+
agents: Map<string, string>;
|
|
22
|
+
skills: SkillSnapshot[];
|
|
23
|
+
/** Rule filename (with `.md`) to text. */
|
|
24
|
+
rules: Map<string, string>;
|
|
25
|
+
claudeMd: string | undefined;
|
|
26
|
+
/** Every file and directory under `.claude/`, as project-relative paths. */
|
|
27
|
+
claudeTree: Set<string>;
|
|
28
|
+
/** Every project file (bounded walk), as project-relative paths. */
|
|
29
|
+
projectFiles: string[];
|
|
30
|
+
}
|
|
31
|
+
interface RuleFailure {
|
|
32
|
+
subject: string;
|
|
33
|
+
message: string;
|
|
34
|
+
}
|
|
35
|
+
interface RuleResult {
|
|
36
|
+
/** How many subjects the rule examined -- the denominator of the rubric score. */
|
|
37
|
+
checked: number;
|
|
38
|
+
failures: RuleFailure[];
|
|
39
|
+
}
|
|
40
|
+
export interface HarnessRule {
|
|
41
|
+
id: string;
|
|
42
|
+
level: RuleLevel;
|
|
43
|
+
category: HarnessCategory;
|
|
44
|
+
check: (snapshot: HarnessSnapshot) => RuleResult;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Model ids and aliases considered current. Bump alongside the
|
|
48
|
+
* `harness-guidance` refresh sweep; `templates/core/bin/lib/harness-rules.mjs`
|
|
49
|
+
* carries the same list and the parity test keeps the two equal.
|
|
50
|
+
*/
|
|
51
|
+
export declare const CURRENT_MODELS: readonly string[];
|
|
52
|
+
/** Every rule, structural first. Order is the order findings are reported in. */
|
|
53
|
+
export declare const RULES: readonly HarnessRule[];
|
|
54
|
+
export {};
|
|
55
|
+
//# sourceMappingURL=rules.d.ts.map
|