@erzhe/harness-kit 0.1.2 → 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/README.md +20 -4
- package/SPEC-v0.md +0 -2
- package/dist/harness-kit.cjs +53 -30
- package/package.json +9 -10
- package/skills/erzhe-harness-init/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> 给 Agent 发一句话,让它帮你把仓库变成 AI 友好的。
|
|
4
4
|
|
|
5
|
-
harness-kit 把仓库的工程知识(这是什么、怎么跑、什么不能破、改东西看哪)沉淀成一份 `.agents/manifest.yaml`,然后自动生成各家 Agent 的入口文件(`AGENTS.md` / `CLAUDE.md
|
|
5
|
+
harness-kit 把仓库的工程知识(这是什么、怎么跑、什么不能破、改东西看哪)沉淀成一份 `.agents/manifest.yaml`,然后自动生成各家 Agent 的入口文件(`AGENTS.md` / `CLAUDE.md`),并用门禁保证它们始终一致。
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -20,6 +20,7 @@ Onboard 本仓库到 harness-kit:跑 npx -y @erzhe/harness-kit@latest onboard
|
|
|
20
20
|
2. 扫描你的仓库(读 README、package.json、目录结构…)
|
|
21
21
|
3. 逐块跟你确认着填 `.agents/manifest.yaml`
|
|
22
22
|
4. 跑 `sync` / `doctor` / `verify` 直到全绿
|
|
23
|
+
5. 装上 git 钩子(`install-hooks`):之后 pre-commit 自动 sync、pre-push verify 拦漂移,维护不用你记命令
|
|
23
24
|
|
|
24
25
|
全程走 `npx`,不往机器上装任何全局东西;你一发新版,所有人下次执行就用上了。
|
|
25
26
|
|
|
@@ -31,7 +32,6 @@ Onboard 本仓库到 harness-kit:跑 npx -y @erzhe/harness-kit@latest onboard
|
|
|
31
32
|
你的仓库/
|
|
32
33
|
AGENTS.md ← [生成] 跨工具入口,Agent 每次会话必读
|
|
33
34
|
CLAUDE.md ← [生成] Claude Code 入口
|
|
34
|
-
.cursor/rules/ ← [生成] Cursor 规则
|
|
35
35
|
.agents/
|
|
36
36
|
manifest.yaml ← 你维护的唯一真相源
|
|
37
37
|
knowledge/ ← Agent 从代码推不出来的知识(领域、约定、决策)
|
|
@@ -45,13 +45,29 @@ Onboard 本仓库到 harness-kit:跑 npx -y @erzhe/harness-kit@latest onboard
|
|
|
45
45
|
|
|
46
46
|
---
|
|
47
47
|
|
|
48
|
+
## 接入之后:自动防腐烂,人只 review
|
|
49
|
+
|
|
50
|
+
harness-kit 的长期价值不在首次接入,而在**代码演进时上下文不腐烂**——而且这件事不该靠人记命令:
|
|
51
|
+
|
|
52
|
+
- **装了钩子就无感**:`install-hooks` 之后,pre-commit 自动重生成入口文件、pre-push 跑 `verify`,上下文和代码对不上就红灯拦下。
|
|
53
|
+
- **漂移了让 Agent 自愈**:知识过期 / 接口契约变了,把仓库丢给 Agent 说一句 `onboard`,它会照 skill 自动考古、改 manifest/knowledge、有据才 accept 契约,最后**产出一份变更清单**。
|
|
54
|
+
- **你只做一个动作**:审查那份 diff,批准或打回。生产和更新都交给 Agent,人只 review。
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
代码演进 → [hook/CI] verify 红灯,指出哪块过期/哪个契约变了
|
|
58
|
+
→ [Agent] 自动修 + 产出变更清单
|
|
59
|
+
→ [你] review:批准 or 打回 ← 唯一的人工动作
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
48
64
|
## 命令
|
|
49
65
|
|
|
50
66
|
| 命令 | 作用 |
|
|
51
67
|
| --- | --- |
|
|
52
68
|
| `harness-kit onboard` | 打印 onboard skill 给 Agent(配合 npx,永远最新、零安装) |
|
|
53
69
|
| `harness-kit init` | 铺 `.agents/` 骨架 + 空白 manifest |
|
|
54
|
-
| `harness-kit sync` | manifest → 生成 AGENTS.md / CLAUDE.md /
|
|
70
|
+
| `harness-kit sync` | manifest → 生成 AGENTS.md / CLAUDE.md / routing / modules |
|
|
55
71
|
| `harness-kit doctor` | 体检:完整性 / 路径引用 / 漂移 / 新鲜度 / 体量预算 |
|
|
56
72
|
| `harness-kit verify` | 门禁:跑不变量 + 契约 + 漂移,列出 GAPS,失败非 0 退出 |
|
|
57
73
|
| `harness-kit accept-contract` | 有意变更接口后,记录新的契约指纹为基线 |
|
|
@@ -82,7 +98,7 @@ harness-kit 把这些沉淀进 manifest,再确定性地生成与校验。
|
|
|
82
98
|
│
|
|
83
99
|
harness-kit sync (确定性生成,勿手改产物)
|
|
84
100
|
▼
|
|
85
|
-
AGENTS.md / CLAUDE.md / .
|
|
101
|
+
AGENTS.md / CLAUDE.md / .agents/routing.md / .agents/modules.md
|
|
86
102
|
│
|
|
87
103
|
harness-kit verify (门禁:不变量 + 契约 + 漂移)
|
|
88
104
|
▼
|
package/SPEC-v0.md
CHANGED
|
@@ -29,7 +29,6 @@
|
|
|
29
29
|
<repo>/
|
|
30
30
|
AGENTS.md # [生成] 跨工具入口,<100 行,指路
|
|
31
31
|
CLAUDE.md # [生成] 首行 @AGENTS.md
|
|
32
|
-
.cursor/rules/00-follow-agents.mdc # [生成] 薄适配器
|
|
33
32
|
.agents/ # canonical 命名空间(唯一手写源)
|
|
34
33
|
manifest.yaml # 脊椎(本规范核心,见 §3)
|
|
35
34
|
knowledge/ # 手写叙述:domain / conventions / journal
|
|
@@ -142,7 +141,6 @@ playbooks:
|
|
|
142
141
|
generate:
|
|
143
142
|
- to: "AGENTS.md" render: "identity+capabilities+environment+invariants"
|
|
144
143
|
- to: "CLAUDE.md" content: "@AGENTS.md"
|
|
145
|
-
- to: ".cursor/rules/00-follow-agents.mdc" template: "adapter"
|
|
146
144
|
```
|
|
147
145
|
|
|
148
146
|
**保留动词说明**:`capabilities` 的保留动词全部**可选**——它是一套"共享词汇表",仓库只声明适用的子集(库通常没有 `run`;前端 `run` 是 dev server 且 `background: true`)。Agent 冷启动时能预期这些词的语义。
|
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,16 +16682,24 @@ 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)],
|
|
16666
|
-
["CLAUDE.md", renderClaudeMd()]
|
|
16667
|
-
[".cursor/rules/00-follow-agents.mdc", renderCursorRule()]
|
|
16702
|
+
["CLAUDE.md", renderClaudeMd()]
|
|
16668
16703
|
];
|
|
16669
16704
|
if (m.routing?.length) targets.push([".agents/routing.md", renderRoutingMd(m)]);
|
|
16670
16705
|
if (m.modules?.length) targets.push([".agents/modules.md", renderModulesMd(m)]);
|
|
@@ -16700,15 +16735,15 @@ function renderAgentsMd(m) {
|
|
|
16700
16735
|
L.push("");
|
|
16701
16736
|
if (id.scope_in?.length || id.scope_out?.length) {
|
|
16702
16737
|
L.push("## Scope");
|
|
16703
|
-
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)}`));
|
|
16704
16739
|
if (id.scope_out?.length)
|
|
16705
|
-
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)}`));
|
|
16706
16741
|
L.push("");
|
|
16707
16742
|
}
|
|
16708
16743
|
if (id.upstream?.length || id.downstream?.length) {
|
|
16709
16744
|
L.push("## Dependencies");
|
|
16710
|
-
if (id.upstream?.length) L.push("", `Upstream: ${id.upstream.join(", ")}`);
|
|
16711
|
-
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(", ")}`);
|
|
16712
16747
|
L.push("");
|
|
16713
16748
|
}
|
|
16714
16749
|
const caps = Object.entries(m.capabilities ?? {});
|
|
@@ -16760,10 +16795,10 @@ function renderRoutingMd(m) {
|
|
|
16760
16795
|
L.push("Find the change-type that matches your task and follow that row before editing.", "");
|
|
16761
16796
|
for (const r of m.routing ?? []) {
|
|
16762
16797
|
L.push(`## ${r.when}`);
|
|
16763
|
-
if (r.read?.length) L.push(`- Read first: ${r.read.map((s) => `\`${s}\``).join(", ")}`);
|
|
16764
|
-
if (r.entry?.length) L.push(`- Entry points: ${r.entry.map((s) => `\`${s}\``).join(", ")}`);
|
|
16765
|
-
if (r.dont_assume?.length) for (const d of r.dont_assume) L.push(`- Do NOT assume: ${d}`);
|
|
16766
|
-
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(", ")}`);
|
|
16767
16802
|
L.push("");
|
|
16768
16803
|
}
|
|
16769
16804
|
return L.join("\n");
|
|
@@ -16774,11 +16809,11 @@ function renderModulesMd(m) {
|
|
|
16774
16809
|
L.push("# Module map", "");
|
|
16775
16810
|
for (const mod of m.modules ?? []) {
|
|
16776
16811
|
L.push(`## ${mod.name} \u2014 ${mod.role}`);
|
|
16777
|
-
if (mod.entry?.length) L.push(`- Entry: ${mod.entry.map((s) => `\`${s}\``).join(", ")}`);
|
|
16778
|
-
if (mod.upstream?.length) L.push(`- Upstream: ${mod.upstream.join(", ")}`);
|
|
16779
|
-
if (mod.downstream?.length) L.push(`- Downstream: ${mod.downstream.join(", ")}`);
|
|
16780
|
-
if (mod.must_know?.length) for (const k of mod.must_know) L.push(`- Must know: ${k}`);
|
|
16781
|
-
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)}`);
|
|
16782
16817
|
L.push("");
|
|
16783
16818
|
}
|
|
16784
16819
|
return L.join("\n");
|
|
@@ -16789,18 +16824,6 @@ function renderClaudeMd() {
|
|
|
16789
16824
|
${GEN_HEADER()}
|
|
16790
16825
|
`;
|
|
16791
16826
|
}
|
|
16792
|
-
function renderCursorRule() {
|
|
16793
|
-
return `---
|
|
16794
|
-
description: Follow the repo AGENTS.md (generated from .agents/manifest.yaml)
|
|
16795
|
-
alwaysApply: true
|
|
16796
|
-
---
|
|
16797
|
-
${GEN_HEADER()}
|
|
16798
|
-
|
|
16799
|
-
Read \`AGENTS.md\` at the repo root for project scope, commands, environment,
|
|
16800
|
-
contracts, and invariants. It is the single source of truth (generated).
|
|
16801
|
-
Do not edit generated files; edit \`.agents/manifest.yaml\` then run \`harness-kit sync\`.
|
|
16802
|
-
`;
|
|
16803
|
-
}
|
|
16804
16827
|
|
|
16805
16828
|
// src/state.ts
|
|
16806
16829
|
var import_node_path5 = require("node:path");
|
|
@@ -17184,7 +17207,7 @@ if ! $HK sync --repo . >/dev/null; then
|
|
|
17184
17207
|
echo "harness-kit: sync failed (run \\\`$HK sync\\\` to see why)" >&2
|
|
17185
17208
|
exit 1
|
|
17186
17209
|
fi
|
|
17187
|
-
git add AGENTS.md CLAUDE.md .
|
|
17210
|
+
git add AGENTS.md CLAUDE.md .agents >/dev/null 2>&1 || true
|
|
17188
17211
|
exit 0
|
|
17189
17212
|
`;
|
|
17190
17213
|
}
|
|
@@ -17286,10 +17309,10 @@ function guard(fn) {
|
|
|
17286
17309
|
}
|
|
17287
17310
|
}
|
|
17288
17311
|
var program2 = new Command();
|
|
17289
|
-
program2.name("harness-kit").description("AI-friendly repo harness").version("0.1.
|
|
17312
|
+
program2.name("harness-kit").description("AI-friendly repo harness").version("0.1.3");
|
|
17290
17313
|
var repoOf = (o) => (0, import_node_path13.resolve)(o.repo);
|
|
17291
17314
|
program2.command("init").description("scaffold .agents/ skeleton + starter manifest").option("-C, --repo <dir>", "target repo dir", process.cwd()).option("--name <name>", "project name", "my-project").option("--force", "overwrite existing manifest", false).action((o) => guard(() => initCmd(repoOf(o), o.name, o.force)));
|
|
17292
|
-
program2.command("sync").description("generate tool files (AGENTS.md, CLAUDE.md
|
|
17315
|
+
program2.command("sync").description("generate tool files (AGENTS.md, CLAUDE.md) from manifest").option("-C, --repo <dir>", "target repo dir", process.cwd()).action((o) => guard(() => syncCmd(repoOf(o))));
|
|
17293
17316
|
program2.command("doctor").description("dev-time health check: completeness, drift, freshness, tech debt").option("-C, --repo <dir>", "target repo dir", process.cwd()).action((o) => guard(() => doctorCmd(repoOf(o))));
|
|
17294
17317
|
program2.command("verify").description("CI gate: run enforceable invariants + contracts + drift; nonzero on failure").option("-C, --repo <dir>", "target repo dir", process.cwd()).action((o) => guard(() => verifyCmd(repoOf(o))));
|
|
17295
17318
|
program2.command("accept-contract").description("record current contract fingerprint(s) as the accepted baseline (after an intended change)").option("-C, --repo <dir>", "target repo dir", process.cwd()).option("--id <id>", "only this contract (default: all with a snapshot command)").action((o) => guard(() => acceptContractCmd(repoOf(o), o.id)));
|
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
|
+
}
|
|
@@ -49,7 +49,7 @@ harness-kit init --repo .
|
|
|
49
49
|
## 3. 生成 + 验证到绿
|
|
50
50
|
|
|
51
51
|
```
|
|
52
|
-
harness-kit sync # manifest -> AGENTS.md / CLAUDE.md /
|
|
52
|
+
harness-kit sync # manifest -> AGENTS.md / CLAUDE.md / routing.md / modules.md
|
|
53
53
|
harness-kit doctor # 补全度 / 引用路径 / 漂移 / 新鲜度 / 体量预算;按报告修
|
|
54
54
|
harness-kit verify # 门禁:跑不变量 + 契约 + 漂移,并列出 GAPS
|
|
55
55
|
```
|