@erzhe/harness-kit 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/harness-kit.cjs +49 -13
- package/package.json +9 -10
package/dist/harness-kit.cjs
CHANGED
|
@@ -16619,9 +16619,24 @@ function loadManifest(repo) {
|
|
|
16619
16619
|
}
|
|
16620
16620
|
function validateManifest(m) {
|
|
16621
16621
|
const issues = [];
|
|
16622
|
+
function checkStrArr(arr, prefix) {
|
|
16623
|
+
if (!arr) return;
|
|
16624
|
+
for (let i = 0; i < arr.length; i++) {
|
|
16625
|
+
if (typeof arr[i] !== "string") {
|
|
16626
|
+
issues.push({
|
|
16627
|
+
level: "error",
|
|
16628
|
+
msg: `${prefix}[${i}] \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\uFF08\u5F53\u524D\u662F ${typeof arr[i]}\uFF09`
|
|
16629
|
+
});
|
|
16630
|
+
}
|
|
16631
|
+
}
|
|
16632
|
+
}
|
|
16622
16633
|
if (!m.spec) issues.push({ level: "error", msg: "\u7F3A\u5C11 spec \u5B57\u6BB5" });
|
|
16623
16634
|
if (!m.identity?.name) issues.push({ level: "error", msg: "identity.name \u5FC5\u586B" });
|
|
16624
16635
|
if (!m.identity?.summary) issues.push({ level: "warn", msg: "identity.summary \u5EFA\u8BAE\u586B\u5199" });
|
|
16636
|
+
checkStrArr(m.identity?.scope_in, "identity.scope_in");
|
|
16637
|
+
checkStrArr(m.identity?.scope_out, "identity.scope_out");
|
|
16638
|
+
checkStrArr(m.identity?.upstream, "identity.upstream");
|
|
16639
|
+
checkStrArr(m.identity?.downstream, "identity.downstream");
|
|
16625
16640
|
for (const [verb, cap] of Object.entries(m.capabilities ?? {})) {
|
|
16626
16641
|
if (!cap?.run) issues.push({ level: "error", msg: `capabilities.${verb}.run \u5FC5\u586B` });
|
|
16627
16642
|
}
|
|
@@ -16636,11 +16651,23 @@ function validateManifest(m) {
|
|
|
16636
16651
|
if (!inv.enforcement && !inv.check && !inv.manual) {
|
|
16637
16652
|
issues.push({ level: "warn", msg: `invariant ${inv.id} \u65E2\u65E0 enforcement/check \u4E5F\u672A\u6807 manual` });
|
|
16638
16653
|
}
|
|
16654
|
+
const pfx = `invariant "${inv.id}" \u7684 enforcement`;
|
|
16655
|
+
checkStrArr(inv.enforcement?.forbid_pattern, `${pfx}.forbid_pattern`);
|
|
16656
|
+
checkStrArr(inv.enforcement?.forbid_import, `${pfx}.forbid_import`);
|
|
16657
|
+
checkStrArr(inv.enforcement?.require_pattern, `${pfx}.require_pattern`);
|
|
16658
|
+
checkStrArr(inv.enforcement?.path_glob, `${pfx}.path_glob`);
|
|
16659
|
+
}
|
|
16660
|
+
for (const k of m.knowledge ?? []) {
|
|
16661
|
+
checkStrArr(k.binds, `knowledge "${k.path}" \u7684 binds`);
|
|
16639
16662
|
}
|
|
16640
16663
|
const capVerbs = new Set(Object.keys(m.capabilities ?? {}));
|
|
16641
16664
|
for (const r of m.routing ?? []) {
|
|
16642
16665
|
if (!r.when) issues.push({ level: "error", msg: "\u5B58\u5728\u7F3A\u5C11 when \u7684 routing \u6761\u76EE" });
|
|
16666
|
+
for (const field of ["read", "entry", "dont_assume", "verify"]) {
|
|
16667
|
+
checkStrArr(r[field], `routing "${r.when}" \u7684 ${field}`);
|
|
16668
|
+
}
|
|
16643
16669
|
for (const v of r.verify ?? []) {
|
|
16670
|
+
if (typeof v !== "string") continue;
|
|
16644
16671
|
if (!v.includes(" ") && !capVerbs.has(v))
|
|
16645
16672
|
issues.push({ level: "warn", msg: `routing "${r.when}" \u7684 verify \u5F15\u7528\u4E86\u672A\u58F0\u660E\u7684 capability: ${v}` });
|
|
16646
16673
|
}
|
|
@@ -16655,11 +16682,20 @@ function validateManifest(m) {
|
|
|
16655
16682
|
modSeen.add(mod.name);
|
|
16656
16683
|
if (!mod.entry?.length)
|
|
16657
16684
|
issues.push({ level: "warn", msg: `module ${mod.name} \u672A\u58F0\u660E entry\uFF08\u65E0\u6CD5\u505A\u65B0\u9C9C\u5EA6\u7ED1\u5B9A\uFF09` });
|
|
16685
|
+
for (const field of ["entry", "upstream", "downstream", "must_know", "pitfalls"]) {
|
|
16686
|
+
checkStrArr(mod[field], `module "${mod.name}" \u7684 ${field}`);
|
|
16687
|
+
}
|
|
16658
16688
|
}
|
|
16659
16689
|
return issues;
|
|
16660
16690
|
}
|
|
16661
16691
|
|
|
16662
16692
|
// src/render.ts
|
|
16693
|
+
function safeStr(v) {
|
|
16694
|
+
if (typeof v === "string") return v;
|
|
16695
|
+
if (v == null) return "";
|
|
16696
|
+
if (typeof v === "number" || typeof v === "boolean") return String(v);
|
|
16697
|
+
return JSON.stringify(v);
|
|
16698
|
+
}
|
|
16663
16699
|
function renderTargets(m) {
|
|
16664
16700
|
const targets = [
|
|
16665
16701
|
["AGENTS.md", renderAgentsMd(m)],
|
|
@@ -16699,15 +16735,15 @@ function renderAgentsMd(m) {
|
|
|
16699
16735
|
L.push("");
|
|
16700
16736
|
if (id.scope_in?.length || id.scope_out?.length) {
|
|
16701
16737
|
L.push("## Scope");
|
|
16702
|
-
if (id.scope_in?.length) L.push("", "In scope:", ...id.scope_in.map((s) => `- ${s}`));
|
|
16738
|
+
if (id.scope_in?.length) L.push("", "In scope:", ...id.scope_in.map((s) => `- ${safeStr(s)}`));
|
|
16703
16739
|
if (id.scope_out?.length)
|
|
16704
|
-
L.push("", "Out of scope (do NOT modify here):", ...id.scope_out.map((s) => `- ${s}`));
|
|
16740
|
+
L.push("", "Out of scope (do NOT modify here):", ...id.scope_out.map((s) => `- ${safeStr(s)}`));
|
|
16705
16741
|
L.push("");
|
|
16706
16742
|
}
|
|
16707
16743
|
if (id.upstream?.length || id.downstream?.length) {
|
|
16708
16744
|
L.push("## Dependencies");
|
|
16709
|
-
if (id.upstream?.length) L.push("", `Upstream: ${id.upstream.join(", ")}`);
|
|
16710
|
-
if (id.downstream?.length) L.push("", `Downstream: ${id.downstream.join(", ")}`);
|
|
16745
|
+
if (id.upstream?.length) L.push("", `Upstream: ${id.upstream.map(safeStr).join(", ")}`);
|
|
16746
|
+
if (id.downstream?.length) L.push("", `Downstream: ${id.downstream.map(safeStr).join(", ")}`);
|
|
16711
16747
|
L.push("");
|
|
16712
16748
|
}
|
|
16713
16749
|
const caps = Object.entries(m.capabilities ?? {});
|
|
@@ -16759,10 +16795,10 @@ function renderRoutingMd(m) {
|
|
|
16759
16795
|
L.push("Find the change-type that matches your task and follow that row before editing.", "");
|
|
16760
16796
|
for (const r of m.routing ?? []) {
|
|
16761
16797
|
L.push(`## ${r.when}`);
|
|
16762
|
-
if (r.read?.length) L.push(`- Read first: ${r.read.map((s) => `\`${s}\``).join(", ")}`);
|
|
16763
|
-
if (r.entry?.length) L.push(`- Entry points: ${r.entry.map((s) => `\`${s}\``).join(", ")}`);
|
|
16764
|
-
if (r.dont_assume?.length) for (const d of r.dont_assume) L.push(`- Do NOT assume: ${d}`);
|
|
16765
|
-
if (r.verify?.length) L.push(`- Minimum verification: ${r.verify.map((s) => `\`${s}\``).join(", ")}`);
|
|
16798
|
+
if (r.read?.length) L.push(`- Read first: ${r.read.map((s) => `\`${safeStr(s)}\``).join(", ")}`);
|
|
16799
|
+
if (r.entry?.length) L.push(`- Entry points: ${r.entry.map((s) => `\`${safeStr(s)}\``).join(", ")}`);
|
|
16800
|
+
if (r.dont_assume?.length) for (const d of r.dont_assume) L.push(`- Do NOT assume: ${safeStr(d)}`);
|
|
16801
|
+
if (r.verify?.length) L.push(`- Minimum verification: ${r.verify.map((s) => `\`${safeStr(s)}\``).join(", ")}`);
|
|
16766
16802
|
L.push("");
|
|
16767
16803
|
}
|
|
16768
16804
|
return L.join("\n");
|
|
@@ -16773,11 +16809,11 @@ function renderModulesMd(m) {
|
|
|
16773
16809
|
L.push("# Module map", "");
|
|
16774
16810
|
for (const mod of m.modules ?? []) {
|
|
16775
16811
|
L.push(`## ${mod.name} \u2014 ${mod.role}`);
|
|
16776
|
-
if (mod.entry?.length) L.push(`- Entry: ${mod.entry.map((s) => `\`${s}\``).join(", ")}`);
|
|
16777
|
-
if (mod.upstream?.length) L.push(`- Upstream: ${mod.upstream.join(", ")}`);
|
|
16778
|
-
if (mod.downstream?.length) L.push(`- Downstream: ${mod.downstream.join(", ")}`);
|
|
16779
|
-
if (mod.must_know?.length) for (const k of mod.must_know) L.push(`- Must know: ${k}`);
|
|
16780
|
-
if (mod.pitfalls?.length) for (const p of mod.pitfalls) L.push(`- Pitfall: ${p}`);
|
|
16812
|
+
if (mod.entry?.length) L.push(`- Entry: ${mod.entry.map((s) => `\`${safeStr(s)}\``).join(", ")}`);
|
|
16813
|
+
if (mod.upstream?.length) L.push(`- Upstream: ${mod.upstream.map(safeStr).join(", ")}`);
|
|
16814
|
+
if (mod.downstream?.length) L.push(`- Downstream: ${mod.downstream.map(safeStr).join(", ")}`);
|
|
16815
|
+
if (mod.must_know?.length) for (const k of mod.must_know) L.push(`- Must know: ${safeStr(k)}`);
|
|
16816
|
+
if (mod.pitfalls?.length) for (const p of mod.pitfalls) L.push(`- Pitfall: ${safeStr(p)}`);
|
|
16781
16817
|
L.push("");
|
|
16782
16818
|
}
|
|
16783
16819
|
return L.join("\n");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@erzhe/harness-kit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AI-friendly repo harness: a manifest.yaml single source of truth that generates AGENTS.md / CLAUDE.md / Cursor rules and enforces invariants, contracts and drift via `harness-kit verify`.",
|
|
6
6
|
"keywords": [
|
|
@@ -39,14 +39,6 @@
|
|
|
39
39
|
"access": "public",
|
|
40
40
|
"registry": "https://registry.npmjs.org/"
|
|
41
41
|
},
|
|
42
|
-
"scripts": {
|
|
43
|
-
"harness": "tsx src/cli.ts",
|
|
44
|
-
"build": "esbuild src/cli.ts --bundle --platform=node --format=cjs --target=node18 --log-override:empty-import-meta=silent --outfile=dist/harness-kit.cjs && chmod +x dist/harness-kit.cjs",
|
|
45
|
-
"typecheck": "tsc --noEmit",
|
|
46
|
-
"test": "node --import tsx --test test/*.test.ts",
|
|
47
|
-
"test:repos": "bash scripts/regression.sh",
|
|
48
|
-
"prepare": "pnpm build"
|
|
49
|
-
},
|
|
50
42
|
"dependencies": {
|
|
51
43
|
"commander": "^15.0.0",
|
|
52
44
|
"fast-glob": "^3.3.3",
|
|
@@ -57,5 +49,12 @@
|
|
|
57
49
|
"esbuild": "^0.28.1",
|
|
58
50
|
"tsx": "^4.22.5",
|
|
59
51
|
"typescript": "^6.0.3"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"harness": "tsx src/cli.ts",
|
|
55
|
+
"build": "esbuild src/cli.ts --bundle --platform=node --format=cjs --target=node18 --log-override:empty-import-meta=silent --outfile=dist/harness-kit.cjs && chmod +x dist/harness-kit.cjs",
|
|
56
|
+
"typecheck": "tsc --noEmit",
|
|
57
|
+
"test": "node --import tsx --test test/*.test.ts",
|
|
58
|
+
"test:repos": "bash scripts/regression.sh"
|
|
60
59
|
}
|
|
61
|
-
}
|
|
60
|
+
}
|