agentic-flow-kit 1.7.16 → 1.8.1
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 +11 -7
- package/modules/hosts/claude/commands/wf-build.md +1 -1
- package/modules/hosts/claude/commands/wf-maintain.md +1 -1
- package/modules/hosts/claude/commands/wf-new-task.md +9 -8
- package/modules/hosts/claude/commands/wf-plan.md +1 -1
- package/modules/hosts/claude/commands/wf-review.md +2 -2
- package/modules/hosts/claude/commands/wf-test.md +1 -1
- package/modules/hosts/codex/commands/wf-build.md +1 -1
- package/modules/hosts/codex/commands/wf-maintain.md +1 -1
- package/modules/hosts/codex/commands/wf-new-task.md +9 -8
- package/modules/hosts/codex/commands/wf-plan.md +1 -1
- package/modules/hosts/codex/commands/wf-review.md +2 -2
- package/modules/hosts/codex/commands/wf-test.md +1 -1
- package/modules/hosts/cursor/commands/wf-build.md +1 -1
- package/modules/hosts/cursor/commands/wf-maintain.md +1 -1
- package/modules/hosts/cursor/commands/wf-new-task.md +9 -8
- package/modules/hosts/cursor/commands/wf-plan.md +1 -1
- package/modules/hosts/cursor/commands/wf-review.md +2 -2
- package/modules/hosts/cursor/commands/wf-test.md +1 -1
- package/modules/hosts/opencode/commands/wf-build.md +1 -1
- package/modules/hosts/opencode/commands/wf-maintain.md +1 -1
- package/modules/hosts/opencode/commands/wf-new-task.md +9 -8
- package/modules/hosts/opencode/commands/wf-plan.md +1 -1
- package/modules/hosts/opencode/commands/wf-review.md +2 -2
- package/modules/hosts/opencode/commands/wf-test.md +1 -1
- package/modules/hosts/trae/commands/wf-build.md +1 -1
- package/modules/hosts/trae/commands/wf-maintain.md +1 -1
- package/modules/hosts/trae/commands/wf-new-task.md +9 -8
- package/modules/hosts/trae/commands/wf-plan.md +1 -1
- package/modules/hosts/trae/commands/wf-review.md +2 -2
- package/modules/hosts/trae/commands/wf-test.md +1 -1
- package/package.json +1 -1
- package/src/cli.mjs +6 -2
- package/src/doctor.mjs +11 -2
- package/src/executor.mjs +25 -8
- package/src/executor.test.mjs +4 -1
- package/src/fresh-init.test.mjs +1 -1
- package/src/init.mjs +20 -3
- package/src/profiles.mjs +7 -3
- package/src/rule-budget.test.mjs +2 -2
- package/src/stack-profile.test.mjs +6 -0
- package/src/validate.mjs +6 -1
- package/src/validate.test.mjs +1 -1
- package/templates/.gitattributes +4 -1
- package/templates/AGENTS.md +2 -2
- package/templates/_agents/commands/build.md +1 -1
- package/templates/_agents/commands/maintain.md +1 -1
- package/templates/_agents/commands/new-task.md +9 -8
- package/templates/_agents/commands/plan.md +1 -1
- package/templates/_agents/commands/review.md +2 -2
- package/templates/_agents/commands/test.md +1 -1
- package/templates/_agents/engine-lock.json +130 -42
- package/templates/_agents/hooks/check-index-incremental.mjs +48 -0
- package/templates/_agents/hooks/check-pairing-incremental.mjs +66 -0
- package/templates/_agents/hooks/check-wiki-ledger.mjs +30 -0
- package/templates/_agents/lane-surfaces.txt +4 -1
- package/templates/_agents/level-hints.txt +1 -1
- package/templates/_agents/loop/papercut-debt/DESIGN.md +112 -0
- package/templates/_agents/mechanism-registry.md +27 -0
- package/templates/_agents/rule-budgets.txt +2 -2
- package/templates/_agents/scripts/agg-delegations.cjs +21 -3
- package/templates/_agents/scripts/agg-gate-stats.mjs +3 -3
- package/templates/_agents/scripts/agg-judgments.mjs +80 -0
- package/templates/_agents/scripts/agg-judgments.test.mjs +45 -0
- package/templates/_agents/scripts/archive-sweep.mjs +233 -0
- package/templates/_agents/scripts/archive-sweep.test.mjs +129 -0
- package/templates/_agents/scripts/check-attribution.mjs +37 -0
- package/templates/_agents/scripts/check-convergence.mjs +119 -0
- package/templates/_agents/scripts/check-engine-integrity.mjs +4 -1
- package/templates/_agents/scripts/check-funnel.mjs +56 -0
- package/templates/_agents/scripts/check-hygiene.mjs +5 -5
- package/templates/_agents/scripts/check-index-incremental.test.mjs +73 -0
- package/templates/_agents/scripts/check-lane-surface.mjs +2 -2
- package/templates/_agents/scripts/check-lane-surface.test.mjs +5 -5
- package/templates/_agents/scripts/check-ledger-invariant.mjs +403 -152
- package/templates/_agents/scripts/check-ledger-invariant.test.mjs +433 -3
- package/templates/_agents/scripts/check-loop-rev.test.mjs +8 -5
- package/templates/_agents/scripts/check-loop-unit.test.mjs +8 -0
- package/templates/_agents/scripts/check-loop.mjs +215 -34
- package/templates/_agents/scripts/check-loop.test.mjs +582 -49
- package/templates/_agents/scripts/check-prejudgment.mjs +64 -0
- package/templates/_agents/scripts/check-registry.mjs +45 -0
- package/templates/_agents/scripts/check-scope.mjs +96 -0
- package/templates/_agents/scripts/check-scope.test.mjs +96 -0
- package/templates/_agents/scripts/check-scripts-syntax.mjs +61 -0
- package/templates/_agents/scripts/check-scripts-syntax.test.mjs +72 -0
- package/templates/_agents/scripts/check-spec-verif.mjs +47 -0
- package/templates/_agents/scripts/compact-ledgers.mjs +3 -3
- package/templates/_agents/scripts/confirm-doc.mjs +114 -38
- package/templates/_agents/scripts/confirm-doc.test.mjs +180 -4
- package/templates/_agents/scripts/edit-face-check.mjs +5 -3
- package/templates/_agents/scripts/fill-intent.mjs +5 -3
- package/templates/_agents/scripts/fill-intent.test.mjs +19 -1
- package/templates/_agents/scripts/fill-plan.mjs +10 -6
- package/templates/_agents/scripts/fill-plan.test.mjs +19 -3
- package/templates/_agents/scripts/fill-spec.mjs +6 -3
- package/templates/_agents/scripts/fill-spec.test.mjs +12 -4
- package/templates/_agents/scripts/fixture-scripts.mjs +2 -2
- package/templates/_agents/scripts/fixture-scripts.test.mjs +6 -1
- package/templates/_agents/scripts/gate-checklist.mjs +6 -0
- package/templates/_agents/scripts/gate-seg.mjs +1 -1
- package/templates/_agents/scripts/gen-effective-policy.mjs +107 -0
- package/templates/_agents/scripts/gen-effective-policy.test.mjs +100 -0
- package/templates/_agents/scripts/gen-verify-evidence.mjs +107 -0
- package/templates/_agents/scripts/gen-verify-evidence.test.mjs +84 -0
- package/templates/_agents/scripts/gen-workflow-dashboard.mjs +8 -3
- package/templates/_agents/scripts/gen-workflow-dashboard.test.mjs +2 -1
- package/templates/_agents/scripts/gen-workflow-metrics.mjs +3 -3
- package/templates/_agents/scripts/git-core.mjs +45 -0
- package/templates/_agents/scripts/judgment-ledger.mjs +65 -0
- package/templates/_agents/scripts/judgment-ledger.test.mjs +129 -0
- package/templates/_agents/scripts/kb-search.mjs +19 -4
- package/templates/_agents/scripts/kb-search.test.mjs +33 -0
- package/templates/_agents/scripts/lane-surface-audit.mjs +143 -0
- package/templates/_agents/scripts/lane-surface-audit.test.mjs +93 -0
- package/templates/_agents/scripts/ledger-shards.mjs +307 -0
- package/templates/_agents/scripts/ledger-shards.test.mjs +339 -0
- package/templates/_agents/scripts/level-criteria.mjs +9 -10
- package/templates/_agents/scripts/level-criteria.test.mjs +7 -5
- package/templates/_agents/scripts/papercut-debt-controller.mjs +141 -0
- package/templates/_agents/scripts/papercut-debt-controller.test.mjs +127 -0
- package/templates/_agents/scripts/papercut-debt-sensor.mjs +170 -0
- package/templates/_agents/scripts/papercut-debt-sensor.test.mjs +146 -0
- package/templates/_agents/scripts/pipeline-run.mjs +6 -22
- package/templates/_agents/scripts/pipeline-run.test.mjs +22 -42
- package/templates/_agents/scripts/policy.mjs +28 -0
- package/templates/_agents/scripts/resolve-merge-conflicts.mjs +110 -0
- package/templates/_agents/scripts/resolve-merge-conflicts.test.mjs +87 -0
- package/templates/_agents/scripts/rewrite-file.mjs +133 -0
- package/templates/_agents/scripts/rewrite-file.test.mjs +148 -0
- package/templates/_agents/scripts/rule-budget.mjs +83 -0
- package/templates/_agents/scripts/run-tests.mjs +99 -0
- package/templates/_agents/scripts/run-tests.test.mjs +105 -0
- package/templates/_agents/scripts/solidify-task.test.mjs +1 -1
- package/templates/_agents/scripts/stage-gates.mjs +34 -12
- package/templates/_agents/scripts/trust-mode.mjs +4 -4
- package/templates/_agents/scripts/trust-mode.test.mjs +1 -1
- package/templates/_agents/scripts/verify.mjs +135 -58
- package/templates/_agents/scripts/verify.test.mjs +156 -47
- package/templates/_agents/scripts/wf-journal.mjs +8 -4
- package/templates/_agents/scripts/workflow-board-server.test.mjs +2 -2
- package/templates/_agents/scripts/workflow-enums.test.mjs +14 -5
- package/templates/_agents/scripts-test-exempt.txt +1 -0
- package/templates/_agents/skills/papercut-fix/SKILL.md +60 -0
- package/templates/_agents/skills/papercut-fix/references/response-template.md +81 -0
- package/templates/_agents/skills/review-method/SKILL.md +55 -0
- package/templates/_agents/workflow-enums.txt +8 -1
- package/templates/_githooks/pre-commit +110 -90
- package/templates/_githooks/pre-merge-commit +27 -23
- package/templates/_githooks/pre-push +4 -3
- package/templates/_github/workflows/kit-ci.yml +4 -3
- package/templates/workflow/README.md +14 -7
- package/templates/workflow/incidents/_TEMPLATE.md +4 -1
- package/templates/workflow/intents/_TEMPLATE.md +4 -2
- package/templates/workflow/papercuts.md +2 -0
- package/templates/workflow/plans/_TEMPLATE.md +10 -5
- package/templates/workflow/regression-checklist.md +1 -0
- package/templates/workflow/specs/_TEMPLATE.md +17 -1
- package/templates/_agents/hooks/check-pairing-incremental.sh +0 -43
- package/templates/_agents/hooks/check-wiki-ledger.sh +0 -18
- package/templates/_agents/scripts/rule-budget.sh +0 -74
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// papercut-debt-controller.test.mjs — 回路控制器 fixture(B10a 自源仓回灌随件测试;源仓无直测,
|
|
3
|
+
// kit 侧按「新增引擎脚本默认必须带同名 .test.mjs」立——排序键与 scope gate 防再同步漂移)
|
|
4
|
+
// 覆盖:排序键三序(命中次数降序 > 日期降序 > 原行序升序)/ scope gate(源码/数据库面拦下照列)/
|
|
5
|
+
// stale 与「同上」承接行转待人工定性 / debt=0 达成态 / 全员被拦警示态 / 口径单源(不复制判定正则)/
|
|
6
|
+
// CLI 退出码(0 正常含无目标;2 测量不可用)与 --json 形状
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import os from 'node:os';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { spawnSync } from 'node:child_process';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
|
|
13
|
+
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const CLI = path.join(SCRIPT_DIR, 'papercut-debt-controller.mjs');
|
|
15
|
+
|
|
16
|
+
let pass = 0;
|
|
17
|
+
let fail = 0;
|
|
18
|
+
const check = (desc, cond, detail = '') => {
|
|
19
|
+
if (cond) { pass++; console.log(`PASS ${desc}`); }
|
|
20
|
+
else { fail++; console.log(`FAIL ${desc}${detail ? '\n ' + detail : ''}`); }
|
|
21
|
+
};
|
|
22
|
+
const mkroot = () => {
|
|
23
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'papercut-ctrl-'));
|
|
24
|
+
// 位置列引用的文档面文件须真实存在——否则传感器 stale 判定(位置列路径全不存在)会把
|
|
25
|
+
// 条目转 needsHuman,排序键 / scope gate 场景测不到目标选取
|
|
26
|
+
for (const p of ['.agents/commands', '.agents/scripts']) {
|
|
27
|
+
fs.mkdirSync(path.join(root, ...p.split('/')), { recursive: true });
|
|
28
|
+
}
|
|
29
|
+
fs.writeFileSync(path.join(root, '.agents', 'commands', 'a.md'), '# a\n', 'utf8');
|
|
30
|
+
fs.writeFileSync(path.join(root, '.agents', 'commands', 'b.md'), '# b\n', 'utf8');
|
|
31
|
+
fs.writeFileSync(path.join(root, '.agents', 'commands', 'c.md'), '# c\n', 'utf8');
|
|
32
|
+
fs.mkdirSync(path.join(root, 'workflow'), { recursive: true });
|
|
33
|
+
return root;
|
|
34
|
+
};
|
|
35
|
+
const HEADER = '| 日期 | 位置 | 症状 | 拟修 |\n|------|------|------|------|\n';
|
|
36
|
+
const writeLedger = (root, text) => fs.writeFileSync(path.join(root, 'workflow', 'papercuts.md'), text, 'utf8');
|
|
37
|
+
const run = (root, ...extra) => spawnSync(process.execPath, [CLI, '--root', root, ...extra], { encoding: 'utf8' });
|
|
38
|
+
|
|
39
|
+
// ---- S1 排序键:命中次数降序 > 日期降序 > 原行序升序 ----
|
|
40
|
+
{
|
|
41
|
+
const root = mkroot();
|
|
42
|
+
writeLedger(root, HEADER
|
|
43
|
+
+ '| 2026-10-01 | `.agents/commands/a.md` | 症状早 | 拟修早 |\n' // 行序 1,日期早,hits=1
|
|
44
|
+
+ '| 2026-10-05 | `.agents/commands/b.md` | 症状新 | 拟修新 |\n' // 行序 2,日期新,hits=1 → 日期降序胜
|
|
45
|
+
+ '| 2026-10-03 | `.agents/commands/c.md` | 第 2 次命中:症状复发 | 拟修复发 |\n'); // hits=2 → 恒胜
|
|
46
|
+
const r = run(root, '--json');
|
|
47
|
+
const j = JSON.parse(r.stdout || '{}');
|
|
48
|
+
check('S1a exit 0', r.status === 0, `status=${r.status} stderr=${String(r.stderr).slice(0, 120)}`);
|
|
49
|
+
check('S1b 主排序键 = 命中次数降序', j.target?.location === '`.agents/commands/c.md`', JSON.stringify(j.target?.location));
|
|
50
|
+
check('S1c 平手时日期降序(近期优先)', j.queue?.[1]?.location === '`.agents/commands/b.md`', JSON.stringify(j.queue?.map((q) => q.location)));
|
|
51
|
+
check('S1d 再平手原行序升序', j.queue?.[2]?.location === '`.agents/commands/a.md`');
|
|
52
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ---- S2 scope gate:拟修触及源码/数据库面 → 拦下照列(不选为目标)----
|
|
56
|
+
{
|
|
57
|
+
const root = mkroot();
|
|
58
|
+
writeLedger(root, HEADER
|
|
59
|
+
+ '| 2026-10-01 | `.agents/commands/a.md` | 文档面症状 | 补一段说明 |\n'
|
|
60
|
+
+ '| 2026-10-02 | `backend/api/x.py` | 源码面症状 | 修校验逻辑 |\n'
|
|
61
|
+
+ '| 2026-10-03 | `tests/` | 测试面症状 | 补用例(涉及 DDL) |\n');
|
|
62
|
+
const r = run(root, '--json');
|
|
63
|
+
const j = JSON.parse(r.stdout || '{}');
|
|
64
|
+
check('S2a exit 0(含全部被拦态)', r.status === 0);
|
|
65
|
+
check('S2b scope gate 拦下 2 条(backend/ + tests/DDL)', j.skippedOutOfScope?.length === 2, JSON.stringify(j.skippedOutOfScope?.map((s) => s.location)));
|
|
66
|
+
check('S2c 在选目标只含文档面条目', j.target?.location === '`.agents/commands/a.md`', JSON.stringify(j.target?.location));
|
|
67
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ---- S3 stale 与「同上」承接行 → needsHuman(不作为自动目标)----
|
|
71
|
+
{
|
|
72
|
+
const root = mkroot();
|
|
73
|
+
writeLedger(root, HEADER
|
|
74
|
+
+ '| 2026-10-01 | `.agents/commands/a.md` | 文档面症状甲 | 拟修甲 |\n'
|
|
75
|
+
+ '| 2026-10-02 | `.agents/scripts/gone.mjs` | 路径已不存在 | 定性失效 |\n'
|
|
76
|
+
+ '| 2026-10-03 | 同上两处 | 承接上行 | 定性重定位 |\n');
|
|
77
|
+
const r = run(root, '--json');
|
|
78
|
+
const j = JSON.parse(r.stdout || '{}');
|
|
79
|
+
check('S3a stale 与「同上」行转待人工', j.needsHuman?.length === 2, JSON.stringify(j.needsHuman?.map((h) => h.location)));
|
|
80
|
+
check('S3b 待人工条目不进 queue', !j.queue?.some((q) => q.location === '.agents/scripts/gone.mjs' || q.location === '同上两处'));
|
|
81
|
+
check('S3c 文档面条目照常在选', j.target?.location === '`.agents/commands/a.md`');
|
|
82
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ---- S4 debt = 0:achieved 态(本轮无目标)----
|
|
86
|
+
{
|
|
87
|
+
const root = mkroot();
|
|
88
|
+
writeLedger(root, HEADER
|
|
89
|
+
+ '| 2026-10-01 | `.agents/commands/a.md` | 症状 | 拟修 ✅ 已升级修复(2026-10-02): 已改 |\n');
|
|
90
|
+
const r = run(root, '--json');
|
|
91
|
+
const j = JSON.parse(r.stdout || '{}');
|
|
92
|
+
check('S4a debt=0 → achieved true 且无目标', r.status === 0 && j.achieved === true && j.target === null && j.debt === 0,
|
|
93
|
+
JSON.stringify({ debt: j.debt, achieved: j.achieved }));
|
|
94
|
+
const r2 = run(root);
|
|
95
|
+
check('S4b 人读口径出「设定点已达成」', /设定点已达成/.test(r2.stdout));
|
|
96
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ---- S5 全员被 scope gate 拦 → 警示态、exit 0 ----
|
|
100
|
+
{
|
|
101
|
+
const root = mkroot();
|
|
102
|
+
writeLedger(root, HEADER + '| 2026-10-01 | `backend/api/x.py` | 源码面症状 | 修校验逻辑 |\n');
|
|
103
|
+
const r = run(root);
|
|
104
|
+
check('S5a 全员被拦:exit 0 + 需人工授权警示', r.status === 0 && /scope gate 拦下/.test(r.stdout) && /需人工决定是否单独授权/.test(r.stdout),
|
|
105
|
+
JSON.stringify({ status: r.status, stdout: String(r.stdout).slice(0, 200) }));
|
|
106
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ---- S6 测量不可用:exit 2(debt 未知)----
|
|
110
|
+
{
|
|
111
|
+
const root = mkroot();
|
|
112
|
+
const r = run(root, '--json');
|
|
113
|
+
const j = JSON.parse(r.stdout || '{}');
|
|
114
|
+
check('S6a 台账缺失 → exit 2 + error JSON', r.status === 2 && j.target === null && typeof j.error === 'string',
|
|
115
|
+
JSON.stringify({ status: r.status, error: j.error }));
|
|
116
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ---- S7 kit 适配钉子:口径单源(控制器不复制传感器判定正则)----
|
|
120
|
+
{
|
|
121
|
+
const src = fs.readFileSync(CLI, 'utf8');
|
|
122
|
+
check('S7a 控制器 import 传感器 scan(不复制判定面)', /import\s*\{\s*scan as sensorScan\s*\}\s*from\s*'\.\/papercut-debt-sensor\.mjs'/.test(src));
|
|
123
|
+
check('S7b 控制器不复制 ✅/⏸ 判定正则', !/'✅'|"✅"/.test(src.replace(/^\/\/.*$/gm, '')));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
console.log(`\n合计: PASS ${pass} / FAIL ${fail}`);
|
|
127
|
+
process.exit(fail ? 1 : 0);
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// papercut-debt-sensor.mjs — papercut 存量债传感器(2026-10-07 papercut-debt 回路 Phase D;B10a 自
|
|
3
|
+
// Shipyard.Material 回灌——判定面逐字对齐,出处沿革留痕在包源仓 git 历史与 CHANGELOG)
|
|
4
|
+
// 用途:测量设定点误差——workflow/papercuts.md 中未升级条目数(debt)。只读、零子进程、可重复。
|
|
5
|
+
// 口径单源:.agents/loop/papercut-debt/DESIGN.md §1/§2(改口径先改设计文件,本脚本只实现)。
|
|
6
|
+
// 未升级 = 台账数据行不含 ✅ 字面量。⏸ 暂缓 视为**未升级**(暂缓须带理由,无理由照算债)。
|
|
7
|
+
// 零子进程理由(实测留痕在包源仓 git 历史与 CHANGELOG):曾有托管 Node 环境 spawnSync 对任意 exe
|
|
8
|
+
// 返回 EBUSY,依赖 git 历史的传感器在该环境不可用(check-loop.mjs 即因此崩过)。故本脚本只读
|
|
9
|
+
// 文件系统——传感器不依赖子进程比依赖更稳,且免受环境回退影响。
|
|
10
|
+
// CRLF:台账检出为 CRLF(全局 core.autocrlf=true),按 split(/\r?\n/) 切行,不依赖行尾格式。
|
|
11
|
+
// 分片感知(B8 已落 ledger-shards):读侧走 readLedgerText(root,'papercuts')——主台账 ∪
|
|
12
|
+
// papercuts.d/*.md 自动合并,无分片时等价原读法,传感器零额外逻辑。
|
|
13
|
+
// 用法:node .agents/scripts/papercut-debt-sensor.mjs [--json] [--root <仓库根>]
|
|
14
|
+
// 退出码恒 0——测量失败不该阻断回路;失败时 stderr 出 [ERROR] 行且 debt 记为 null。
|
|
15
|
+
import fs from 'node:fs';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
18
|
+
import { readLedgerText } from './ledger-shards.mjs';
|
|
19
|
+
|
|
20
|
+
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
const argv = process.argv.slice(2);
|
|
22
|
+
const argOf = (k) => { const i = argv.indexOf(k); return i >= 0 && i + 1 < argv.length ? argv[i + 1] : ''; };
|
|
23
|
+
const asJson = argv.includes('--json');
|
|
24
|
+
const ROOT = path.resolve(argOf('--root') || path.join(SCRIPT_DIR, '..', '..'));
|
|
25
|
+
const LEDGER = path.join(ROOT, 'workflow', 'papercuts.md');
|
|
26
|
+
|
|
27
|
+
// 表格列:| 日期 | 位置 | 症状 | 拟修 | → 切分后 6 段(首尾空串)
|
|
28
|
+
const COLS = 4;
|
|
29
|
+
// 判定口径(单源,本文件):台账表格行中含 `✅` 者计为**已消债**(真修完)。
|
|
30
|
+
// `⏸ 暂缓(日期):理由` 者计为**已定性**——债已被人读过并判失效,不再驱动回路,
|
|
31
|
+
// 但与 ✅ 分开计数,避免"暂缓"冒充"修完"。`⏸ 暂缓` 无理由者仍计债(迫使人真读一遍)。
|
|
32
|
+
const DONE = '✅';
|
|
33
|
+
const DEFER = '⏸';
|
|
34
|
+
export const DEFER_REASONED_RE = /⏸\s*暂缓([^)]*)\s*:\s*\S/;
|
|
35
|
+
const DEFER_REASONED = DEFER_REASONED_RE;
|
|
36
|
+
// 「位置」列里抽仓库相对路径做存在性检查;只标 stale 不排除(台账记历史事实,路径改名/删除是常态)
|
|
37
|
+
const PATH_RE = /(?:^|[\s((`])([.a-zA-Z0-9_][\w./-]*\/[\w./-]+)(?=[\s))`::,,]|$)/g;
|
|
38
|
+
|
|
39
|
+
function parseRow(line) {
|
|
40
|
+
const parts = line.split('|');
|
|
41
|
+
// 单元格内可能含 | (代码片段)——多于 COLS+2 段时把中间段并回症状列
|
|
42
|
+
let cells;
|
|
43
|
+
if (parts.length > COLS + 2) {
|
|
44
|
+
const head = parts.slice(1, 3);
|
|
45
|
+
const tail = parts.slice(-(COLS - 2));
|
|
46
|
+
cells = [...head, parts.slice(3, parts.length - (COLS - 2)).join('|').trim(), ...tail];
|
|
47
|
+
} else {
|
|
48
|
+
cells = parts.slice(1, 1 + COLS).map((s) => s.trim());
|
|
49
|
+
}
|
|
50
|
+
while (cells.length < COLS) cells.push('');
|
|
51
|
+
return { date: cells[0], location: cells[1], symptom: cells[2], proposedFix: cells[3] };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function detectHitCount(text) {
|
|
55
|
+
// 台账约定:复发时在文本里写「第 2 次命中」「第 3 次命中」——控制器的主排序键
|
|
56
|
+
let max = 1;
|
|
57
|
+
for (const m of String(text).matchAll(/第\s*(\d+)\s*次命中/g)) {
|
|
58
|
+
const n = Number(m[1]);
|
|
59
|
+
if (Number.isFinite(n) && n > max) max = n;
|
|
60
|
+
}
|
|
61
|
+
return max;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function extractPaths(location) {
|
|
65
|
+
const out = new Set();
|
|
66
|
+
for (const m of String(location).matchAll(PATH_RE)) {
|
|
67
|
+
const p = m[1].replace(/[.,;:]+$/, '');
|
|
68
|
+
if (p.startsWith('.') || p.startsWith('wiki/') || p.startsWith('tmp/')) out.add(p);
|
|
69
|
+
}
|
|
70
|
+
return [...out];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// scan(root) 是控制器 import 的解析面——判定口径单源在此,控制器不复制任何判定正则。
|
|
74
|
+
// 「位置」列 [env] 前缀 = 环境约束类(DESIGN §1):不进债池(scan 不返回),单列 envPending。
|
|
75
|
+
export function scan(root = ROOT) {
|
|
76
|
+
return parseLedger(root).rows;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 环境约束挂起([env] 前缀行):仅列未 settled 的——写 ⏸ 前提消失 / ✅ 的 env 行已清,不占任何池。
|
|
80
|
+
export function envPending(root = ROOT) {
|
|
81
|
+
return parseLedger(root).envRows.filter((r) => !r.settled);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function parseLedger(root) {
|
|
85
|
+
const ledger = path.join(root, 'workflow', 'papercuts.md');
|
|
86
|
+
if (!fs.existsSync(ledger)) throw new Error(`台账不存在: ${ledger}`);
|
|
87
|
+
// 分片感知读(B8 ledger-shards):主台账 ∪ papercuts.d/*.md;无分片时等价原读法
|
|
88
|
+
const raw = readLedgerText(root, 'papercuts');
|
|
89
|
+
const lines = raw.split(/\r?\n/);
|
|
90
|
+
const rows = [];
|
|
91
|
+
const envRows = [];
|
|
92
|
+
for (let i = 0; i < lines.length; i++) {
|
|
93
|
+
const line = lines[i];
|
|
94
|
+
if (!/^\|\s*20\d\d-\d\d-\d\d/.test(line)) continue; // 只取数据行(表头/分隔行/说明行自然排除)
|
|
95
|
+
const c = parseRow(line);
|
|
96
|
+
const resolved = line.includes(DONE);
|
|
97
|
+
const deferred = line.includes(DEFER);
|
|
98
|
+
const deferredReasoned = DEFER_REASONED.test(line);
|
|
99
|
+
const paths = extractPaths(c.location);
|
|
100
|
+
const item = {
|
|
101
|
+
line: i + 1,
|
|
102
|
+
date: c.date,
|
|
103
|
+
location: c.location,
|
|
104
|
+
symptom: c.symptom,
|
|
105
|
+
proposedFix: c.proposedFix,
|
|
106
|
+
hits: detectHitCount(`${c.symptom} ${c.proposedFix}`),
|
|
107
|
+
resolved,
|
|
108
|
+
deferred,
|
|
109
|
+
deferredReasoned,
|
|
110
|
+
// 「已消债」= 真修完,或已带理由判失效。驱动回路的是这个口径,不是 resolved。
|
|
111
|
+
settled: resolved || deferredReasoned,
|
|
112
|
+
stale: paths.length > 0 && paths.every((p) => !fs.existsSync(path.join(root, p))),
|
|
113
|
+
paths,
|
|
114
|
+
};
|
|
115
|
+
(/^\s*`?\[env\]/.test(c.location) ? envRows : rows).push(item);
|
|
116
|
+
}
|
|
117
|
+
return { rows, envRows };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function main() {
|
|
121
|
+
let rows;
|
|
122
|
+
try {
|
|
123
|
+
rows = scan();
|
|
124
|
+
} catch (e) {
|
|
125
|
+
console.error(`[ERROR] papercut-debt-sensor: ${e.message}`);
|
|
126
|
+
if (asJson) console.log(JSON.stringify({ error: e.message, debt: null, total: null }, null, 2));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const resolved = rows.filter((r) => r.resolved);
|
|
130
|
+
const settled = rows.filter((r) => r.settled);
|
|
131
|
+
const unresolved = rows.filter((r) => !r.settled);
|
|
132
|
+
const stale = unresolved.filter((r) => r.stale);
|
|
133
|
+
const env = envPending();
|
|
134
|
+
|
|
135
|
+
if (asJson) {
|
|
136
|
+
console.log(JSON.stringify({
|
|
137
|
+
ledger: path.relative(ROOT, LEDGER).split(path.sep).join('/'),
|
|
138
|
+
total: rows.length + env.length,
|
|
139
|
+
resolved: resolved.length,
|
|
140
|
+
deferredReasoned: settled.filter((r) => r.deferredReasoned).length,
|
|
141
|
+
settled: settled.length,
|
|
142
|
+
debt: unresolved.length,
|
|
143
|
+
envPending: env.map(({ line, date, location, symptom }) => ({ line, date, location, symptom })),
|
|
144
|
+
stalePaths: stale.length,
|
|
145
|
+
unresolved: unresolved.map(({ resolved: _r, settled: _s, ...rest }) => rest),
|
|
146
|
+
}, null, 2));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
console.log(`papercut 存量债 —— ${path.relative(ROOT, LEDGER).split(path.sep).join('/')}`);
|
|
151
|
+
console.log(`条目 ${rows.length + env.length} · 已消债 ${settled.length}(✅ ${resolved.length} / ⏸带理由 ${settled.filter((r) => r.deferredReasoned).length}) · 未消债 ${unresolved.length}(设定点误差 = debt)`);
|
|
152
|
+
if (env.length) console.log(`环境约束挂起 ${env.length} 条([env] 前缀,不计 debt——环境变化后复测前提:消失则 ⏸,实为代码缺陷则去前缀转正)`);
|
|
153
|
+
if (unresolved.length === 0) {
|
|
154
|
+
console.log('✅ debt = 0,设定点已达成——无需再跑回路。');
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const noReason = unresolved.filter((r) => r.deferred && !r.deferredReasoned);
|
|
158
|
+
if (noReason.length) console.log(`其中 ⏸ 暂缓但无理由 ${noReason.length} 条(不算消债)`);
|
|
159
|
+
if (stale.length) console.log(`其中位置列路径已不存在 ${stale.length} 条(stale——台账记历史事实,不自动排除)`);
|
|
160
|
+
console.log('');
|
|
161
|
+
for (const r of unresolved) {
|
|
162
|
+
const flags = [r.hits > 1 ? `第${r.hits}次命中` : '', r.deferred ? '暂缓无理由' : '', r.stale ? 'stale' : ''].filter(Boolean).join(' · ');
|
|
163
|
+
console.log(` L${r.line} [${r.date}] ${r.location}${flags ? ` (${flags})` : ''}`);
|
|
164
|
+
}
|
|
165
|
+
console.log('');
|
|
166
|
+
console.log('下一步:node .agents/scripts/papercut-debt-controller.mjs # 选本轮目标(--dry-run 只看不改)');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 仅直接执行时跑(被控制器 / 测试 import 时不自跑、不打印——控制器只取 scan())
|
|
170
|
+
if (process.argv[1] && process.argv[1].replace(/\\/g, '/').endsWith('papercut-debt-sensor.mjs')) main();
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// papercut-debt-sensor.test.mjs — 存量债传感器 fixture(B10a 自源仓回灌随件测试;源仓无直测,
|
|
3
|
+
// kit 侧按「新增引擎脚本默认必须带同名 .test.mjs」立——判定面与传感器单源,防再同步漂移)
|
|
4
|
+
// 覆盖:三态计数(未升级 / ✅ 已消债 / ⏸ 暂缓无理由仍计债)/ ⏸带理由消债 / [env] 前缀单列 envPending
|
|
5
|
+
// 不入债池 / 命中次数抽取 / stale 只标不排 / 分片合并读(主台账 ∪ papercuts.d)/ CRLF 容忍 /
|
|
6
|
+
// 管道符并回症状列 / CLI 退出码恒 0(含台账缺失)与 --json 形状
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import os from 'node:os';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { spawnSync } from 'node:child_process';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
import { scan, envPending, DEFER_REASONED_RE } from './papercut-debt-sensor.mjs';
|
|
13
|
+
|
|
14
|
+
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const CLI = path.join(SCRIPT_DIR, 'papercut-debt-sensor.mjs');
|
|
16
|
+
|
|
17
|
+
let pass = 0;
|
|
18
|
+
let fail = 0;
|
|
19
|
+
const check = (desc, cond, detail = '') => {
|
|
20
|
+
if (cond) { pass++; console.log(`PASS ${desc}`); }
|
|
21
|
+
else { fail++; console.log(`FAIL ${desc}${detail ? '\n ' + detail : ''}`); }
|
|
22
|
+
};
|
|
23
|
+
const mkroot = () => {
|
|
24
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'papercut-sensor-'));
|
|
25
|
+
fs.mkdirSync(path.join(root, 'workflow'), { recursive: true });
|
|
26
|
+
return root;
|
|
27
|
+
};
|
|
28
|
+
const HEADER = '| 日期 | 位置 | 症状 | 拟修 |\n|------|------|------|------|\n';
|
|
29
|
+
const writeLedger = (root, text) => fs.writeFileSync(path.join(root, 'workflow', 'papercuts.md'), text, 'utf8');
|
|
30
|
+
|
|
31
|
+
// ---- S1 三态计数:未升级 / ✅ 已消债 / ⏸ 暂缓无理由仍计债;⏸带理由消债 ----
|
|
32
|
+
{
|
|
33
|
+
const root = mkroot();
|
|
34
|
+
writeLedger(root, HEADER
|
|
35
|
+
+ '| 2026-10-01 | `.agents/commands/build.md` | 症状甲 | 拟修甲 |\n'
|
|
36
|
+
+ '| 2026-10-02 | `.agents/commands/test.md` | 症状乙 | 拟修乙 ✅ 已升级修复(2026-10-03): 已改 |\n'
|
|
37
|
+
+ '| 2026-10-03 | `.agents/scripts/x.mjs` | 症状丙 | 拟修丙 ⏸ 暂缓无理由 |\n'
|
|
38
|
+
+ '| 2026-10-04 | `.agents/scripts/y.mjs` | 症状丁 | 拟修丁 ⏸ 暂缓(2026-10-05):前提已消失,判失效 |\n');
|
|
39
|
+
const rows = scan(root);
|
|
40
|
+
check('S1a 数据行 4 条全入池(env 无)', rows.length === 4, `len=${rows.length}`);
|
|
41
|
+
check('S1b 未升级计债', rows.filter((r) => !r.settled).length === 2, JSON.stringify(rows.map((r) => r.settled)));
|
|
42
|
+
check('S1c ✅ 判已消债', rows[1].resolved === true && rows[1].settled === true);
|
|
43
|
+
check('S1d ⏸ 无理由仍计债(deferred 但不 settled)', rows[2].deferred === true && rows[2].deferredReasoned === false && rows[2].settled === false);
|
|
44
|
+
check('S1e ⏸带理由判消债', rows[3].deferredReasoned === true && rows[3].settled === true);
|
|
45
|
+
check('S1f DEFER_REASONED_RE 形态:全角括号+全角冒号+非空理由', DEFER_REASONED_RE.test('⏸ 暂缓(2026-10-05):理由 X') && !DEFER_REASONED_RE.test('⏸ 暂缓无理由'));
|
|
46
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ---- S2 [env] 前缀:不进债池、单列 envPending(仅未 settled)----
|
|
50
|
+
{
|
|
51
|
+
const root = mkroot();
|
|
52
|
+
writeLedger(root, HEADER
|
|
53
|
+
+ '| 2026-10-01 | `[env] 无外网装不上依赖` | 症状戊 | 拟修戊 |\n'
|
|
54
|
+
+ '| 2026-10-02 | `[env] 本机时区漂移` | 症状己 | 拟修己 ⏸ 暂缓(2026-10-03):前提已消失 |\n'
|
|
55
|
+
+ '| 2026-10-03 | `.agents/scripts/z.mjs` | 症状庚 | 拟修庚 |\n');
|
|
56
|
+
const rows = scan(root);
|
|
57
|
+
const env = envPending(root);
|
|
58
|
+
check('S2a [env] 行不进债池', rows.length === 1 && rows[0].symptom === '症状庚', JSON.stringify(rows.map((r) => r.symptom)));
|
|
59
|
+
check('S2b envPending 只列未 settled 的 env 行', env.length === 1 && env[0].symptom === '症状戊', JSON.stringify(env.map((r) => r.symptom)));
|
|
60
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ---- S3 命中次数抽取(症状/拟修文本「第 N 次命中」,取最大)----
|
|
64
|
+
{
|
|
65
|
+
const root = mkroot();
|
|
66
|
+
writeLedger(root, HEADER
|
|
67
|
+
+ '| 2026-10-01 | `.agents/commands/build.md` | 第 2 次命中:症状辛 | 拟修辛 |\n'
|
|
68
|
+
+ '| 2026-10-02 | `.agents/commands/build.md` | 症状壬 | 拟修壬(第 3 次命中) |\n');
|
|
69
|
+
const rows = scan(root);
|
|
70
|
+
check('S3a 第 2 次命中抽出', rows[0].hits === 2, `hits=${rows[0].hits}`);
|
|
71
|
+
check('S3b 多处命中取最大且默认 1', rows[1].hits === 3, `hits=${rows[1].hits}`);
|
|
72
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ---- S4 stale 只标不排:位置列路径不存在 → stale true;存在 → false ----
|
|
76
|
+
{
|
|
77
|
+
const root = mkroot();
|
|
78
|
+
fs.mkdirSync(path.join(root, '.agents', 'scripts'), { recursive: true });
|
|
79
|
+
fs.writeFileSync(path.join(root, '.agents', 'scripts', 'real.mjs'), '// x\n', 'utf8');
|
|
80
|
+
writeLedger(root, HEADER
|
|
81
|
+
+ '| 2026-10-01 | `.agents/scripts/real.mjs` | 症状子 | 拟修子 |\n'
|
|
82
|
+
+ '| 2026-10-02 | `.agents/scripts/gone.mjs` | 症状丑 | 拟修丑 |\n');
|
|
83
|
+
const rows = scan(root);
|
|
84
|
+
check('S4a 路径存在 → stale false', rows[0].stale === false);
|
|
85
|
+
check('S4b 路径不存在 → stale true(仍留在 unresolved)', rows[1].stale === true && rows.length === 2);
|
|
86
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ---- S5 分片合并读:主台账 ∪ papercuts.d/*.md(B8 ledger-shards 读侧单源)----
|
|
90
|
+
{
|
|
91
|
+
const root = mkroot();
|
|
92
|
+
fs.mkdirSync(path.join(root, 'workflow', 'papercuts.d'), { recursive: true });
|
|
93
|
+
writeLedger(root, '# 命令/技能 Papercut 留痕\n\n' + HEADER
|
|
94
|
+
+ '| 2026-10-01 | `.agents/commands/build.md` | 主表症状 | 主表拟修 |\n');
|
|
95
|
+
fs.writeFileSync(path.join(root, 'workflow', 'papercuts.d', '2026-09.md'),
|
|
96
|
+
HEADER + '| 2026-09-01 | `.agents/commands/test.md` | 分片症状 | 分片拟修 |\n', 'utf8');
|
|
97
|
+
const rows = scan(root);
|
|
98
|
+
check('S5a 分片行并入扫描面', rows.length === 2, JSON.stringify(rows.map((r) => r.symptom)));
|
|
99
|
+
check('S5b 分片行判定照常(未升级计债)', rows.filter((r) => !r.settled).length === 2);
|
|
100
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ---- S6 CRLF 容忍 + 管道符并回症状列 ----
|
|
104
|
+
{
|
|
105
|
+
const root = mkroot();
|
|
106
|
+
writeLedger(root, HEADER.replace(/\n/g, '\r\n')
|
|
107
|
+
+ '| 2026-10-01 | `.agents/scripts/w.mjs` | 症状含管道 `a|b` 续 | 拟修寅 |\r\n');
|
|
108
|
+
const rows = scan(root);
|
|
109
|
+
check('S6a CRLF 台账照常解析', rows.length === 1, JSON.stringify(rows));
|
|
110
|
+
// 源仓口径钉死:并回分支只 trim 症状列(中间段),拟修列(尾部段)不 trim——逐字保留
|
|
111
|
+
check('S6b 单元格内管道符并回症状列(拟修列含原文即可)', rows[0].symptom.includes('a|b') && rows[0].proposedFix.includes('拟修寅'),
|
|
112
|
+
JSON.stringify({ symptom: rows[0].symptom, proposedFix: rows[0].proposedFix }));
|
|
113
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ---- S7 CLI:退出码恒 0(正常 / 台账缺失),--json 形状 ----
|
|
117
|
+
{
|
|
118
|
+
const root = mkroot();
|
|
119
|
+
writeLedger(root, HEADER + '| 2026-10-01 | `.agents/commands/build.md` | 症状卯 | 拟修卯 |\n');
|
|
120
|
+
const r1 = spawnSync(process.execPath, [CLI, '--json', '--root', root], { encoding: 'utf8' });
|
|
121
|
+
const j1 = JSON.parse(r1.stdout || '{}');
|
|
122
|
+
check('S7a 正常:exit 0 + debt 计数', r1.status === 0 && j1.debt === 1 && j1.total === 1 && j1.resolved === 0,
|
|
123
|
+
JSON.stringify({ status: r1.status, debt: j1.debt }));
|
|
124
|
+
check('S7b --json unresolved 带行号与命中数', j1.unresolved?.[0]?.line >= 1 && j1.unresolved?.[0]?.hits === 1, JSON.stringify(j1.unresolved));
|
|
125
|
+
const r2 = spawnSync(process.execPath, [CLI, '--root', root], { encoding: 'utf8' });
|
|
126
|
+
check('S7c 人读摘要 exit 0 且含 debt 行', r2.status === 0 && /未消债 1/.test(r2.stdout), String(r2.stdout).slice(0, 200));
|
|
127
|
+
|
|
128
|
+
const empty = mkroot();
|
|
129
|
+
const r3 = spawnSync(process.execPath, [CLI, '--json', '--root', empty], { encoding: 'utf8' });
|
|
130
|
+
const j3 = JSON.parse(r3.stdout || '{}');
|
|
131
|
+
check('S7d 台账缺失:exit 恒 0 + [ERROR] + debt null(测量失败不阻断回路)',
|
|
132
|
+
r3.status === 0 && j3.debt === null && /\[ERROR\] papercut-debt-sensor/.test(r3.stderr),
|
|
133
|
+
JSON.stringify({ status: r3.status, stderr: String(r3.stderr).slice(0, 120) }));
|
|
134
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
135
|
+
fs.rmSync(empty, { recursive: true, force: true });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ---- S8 kit 适配钉子:读侧必须走 ledger-shards(分片消费)——防再同步回退成裸读主台账 ----
|
|
139
|
+
{
|
|
140
|
+
const src = fs.readFileSync(CLI, 'utf8');
|
|
141
|
+
check('S8a 传感器经 readLedgerText 读台账(分片感知)', /readLedgerText\(root,\s*'papercuts'\)/.test(src));
|
|
142
|
+
check('S8b 零子进程约束:不 import child_process', !/child_process/.test(src));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
console.log(`\n合计: PASS ${pass} / FAIL ${fail}`);
|
|
146
|
+
process.exit(fail ? 1 : 0);
|
|
@@ -21,7 +21,7 @@ import path from 'node:path';
|
|
|
21
21
|
import { spawnSync } from 'node:child_process';
|
|
22
22
|
|
|
23
23
|
const KINDS = ['require', 'fix', 'verify', 'review', 'deploy'];
|
|
24
|
-
const LEVELS = ['
|
|
24
|
+
const LEVELS = ['L1', 'L2', 'L3']; // L0 已退役(2026-10-13 自源仓收敛系列 1 回灌)——级别三档,L1 = 一份 plan 即入口
|
|
25
25
|
const TERMINAL = ['done', 'superseded', 'cancelled', 'closed'];
|
|
26
26
|
const FIX_CAP = 3;
|
|
27
27
|
const GIT_NEVER_FLAGS = ['--no-verify', '-n']; // 不变量:任何 git 调用不得携带(防御性断言,正常路径不会拼出)
|
|
@@ -122,7 +122,7 @@ export function stageBar(run) {
|
|
|
122
122
|
: [['Plan', ['intent-fill', 'intent-draft', 'intent-confirm', 'incident-fill', 'incident-draft', 'incident-confirm', 'triage'].includes(s)],
|
|
123
123
|
['Design', ['spec-fill', 'spec-draft', 'spec-review', 'spec-confirm'].includes(s)],
|
|
124
124
|
['Build', ['plan-fill', 'plan-draft', 'plan-confirm', 'changes-confirm', 'implement', 'fix', 'changes-verify'].includes(s)],
|
|
125
|
-
['Test', ['verify', 'review', 'closeout', 'done-confirm', 'incident-fixed-confirm', 'incident-closed-confirm', 'direct-commit'
|
|
125
|
+
['Test', ['verify', 'review', 'closeout', 'done-confirm', 'incident-fixed-confirm', 'incident-closed-confirm', 'direct-commit'].includes(s) || s === 'done']];
|
|
126
126
|
const curIdx = seq.findIndex(([, cur]) => cur);
|
|
127
127
|
return seq.map(([name], i) => mk(name, i < curIdx ? 'past' : i === curIdx ? 'cur' : 'future')).join(' ');
|
|
128
128
|
}
|
|
@@ -342,7 +342,7 @@ function advance(ctx, run, opt) {
|
|
|
342
342
|
instructions: [
|
|
343
343
|
'1. 读 .agents/scripts/level-criteria.mjs 判据单源(L2_SURFACES 触达面闭集 / L1 默认档 / L3_SURFACES 结构面;new-task.md 只承载流程表,判据一律看单源;机器面可先跑 `flow-kit route --kind require` 取路径下限提示)',
|
|
344
344
|
`2. 对需求定级:「${run.requirement}」${run.hint ? `(预判参考:${run.hint})` : ''}`,
|
|
345
|
-
'3. 回填:next --triage "kind=require|fix|verify|review|deploy level=
|
|
345
|
+
'3. 回填:next --triage "kind=require|fix|verify|review|deploy level=L1-L3 module=<词表> topic=<英文kebab>"',
|
|
346
346
|
],
|
|
347
347
|
context: { docPath: '.agents/commands/new-task.md', kbHits: [], stderrTail: '', relatedDocs: ['workflow/INDEX.md'] },
|
|
348
348
|
constraints: ['判级就高不就低;勾红线必 L2+;module 取 .agents/workflow-modules.txt 词表'],
|
|
@@ -360,8 +360,7 @@ function advance(ctx, run, opt) {
|
|
|
360
360
|
run.docs = run.docs || { entry: null, spec: null, plan: null };
|
|
361
361
|
// 路由
|
|
362
362
|
run.stage = K() === 'verify' ? 'verify' : K() === 'review' ? 'review-only' : K() === 'deploy' ? 'deploy-check'
|
|
363
|
-
: K() === '
|
|
364
|
-
: K() === 'fix' ? 'incident-fill' : 'intent-fill';
|
|
363
|
+
: K() === 'fix' ? 'incident-fill' : 'intent-fill';
|
|
365
364
|
continue;
|
|
366
365
|
}
|
|
367
366
|
run.stage = 'intent-fill'; continue;
|
|
@@ -489,7 +488,7 @@ function advance(ctx, run, opt) {
|
|
|
489
488
|
const rel = `workflow/plans/${today()}-${topic()}.md`;
|
|
490
489
|
run.docs.plan = rel;
|
|
491
490
|
if (!docState(ctx, rel)) {
|
|
492
|
-
const r = runNode(ctx, run, 'fill-plan.mjs', ['--level', L()
|
|
491
|
+
const r = runNode(ctx, run, 'fill-plan.mjs', ['--level', L(), '--topic', topic(), '--output', rel]);
|
|
493
492
|
if (r.exit !== 0) stopGateFail(ctx, run, s, [`fill-plan exit ${r.exit}(G1 前置门或参数):${(r.stderr || r.stdout).split(/\r?\n/).slice(-3).join(' | ')}`]);
|
|
494
493
|
}
|
|
495
494
|
run.stage = 'plan-draft';
|
|
@@ -667,22 +666,7 @@ function advance(ctx, run, opt) {
|
|
|
667
666
|
stopConfirm(ctx, run, rest[0], `${label}——逐份原话,一次一份(余 ${rest.length} 份)`, true);
|
|
668
667
|
}
|
|
669
668
|
|
|
670
|
-
|
|
671
|
-
if (!opt.files) {
|
|
672
|
-
issueWO(ctx, run, wo(run, {
|
|
673
|
-
kind: 'implement', title: `L0 直改:${briefReq(run)}`, goal: 'docs/样式微调,无行为影响——直接改后提交',
|
|
674
|
-
instructions: ['1. 按需求直接修改目标文件', '2. 完成后回填:next --files "…"(脚本核对改动面并走 pre-commit 提交)'],
|
|
675
|
-
context: { docPath: '', kbHits: run.lastKbHits || [], stderrTail: '', relatedDocs: [] },
|
|
676
|
-
constraints: ['L0 语义:无行为影响;越此范围=判级错误,停机上报改判'],
|
|
677
|
-
acceptance: ['git 改动 ⊆ 回填清单', 'pre-commit 门通过'],
|
|
678
|
-
}), 'direct-edit');
|
|
679
|
-
}
|
|
680
|
-
const a = checkAuthorized(ctx, run, opt.files);
|
|
681
|
-
if (!a.ok) stopGateFail(ctx, run, s, a.problems);
|
|
682
|
-
const c = docsCommit(ctx, run, run.lastFiles, `docs(${run.triage.module}): ${briefReq(run)}(L0,pipeline-run ${run.runId})`);
|
|
683
|
-
if (!c.ok) stopGateFail(ctx, run, s, [c.msg]);
|
|
684
|
-
stopDone(ctx, run);
|
|
685
|
-
}
|
|
669
|
+
// direct-edit 分支随 L0 退役删除(2026-10-13 自源仓收敛系列 1 回灌)——最低档 L1 走 intent+plan 闭环。
|
|
686
670
|
|
|
687
671
|
if (s === 'verify-only') { run.stage = 'verify'; continue; }
|
|
688
672
|
if (s === 'review-only') {
|