agentic-flow-kit 1.7.15 → 1.8.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 +50 -8
- package/modules/hosts/claude/commands/wf-build.md +1 -1
- package/modules/hosts/claude/commands/wf-design.md +5 -4
- package/modules/hosts/claude/commands/wf-maintain.md +1 -1
- package/modules/hosts/claude/commands/wf-new-task.md +47 -25
- 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-sync-hosts.md +7 -13
- package/modules/hosts/claude/commands/wf-test.md +2 -2
- package/modules/hosts/codex/commands/wf-build.md +1 -1
- package/modules/hosts/codex/commands/wf-design.md +5 -4
- package/modules/hosts/codex/commands/wf-maintain.md +1 -1
- package/modules/hosts/codex/commands/wf-new-task.md +47 -25
- 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-sync-hosts.md +7 -13
- package/modules/hosts/codex/commands/wf-test.md +2 -2
- package/modules/hosts/cursor/commands/wf-build.md +1 -1
- package/modules/hosts/cursor/commands/wf-design.md +5 -4
- package/modules/hosts/cursor/commands/wf-maintain.md +1 -1
- package/modules/hosts/cursor/commands/wf-new-task.md +47 -25
- 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-sync-hosts.md +7 -13
- package/modules/hosts/cursor/commands/wf-test.md +2 -2
- package/modules/hosts/opencode/commands/wf-build.md +1 -1
- package/modules/hosts/opencode/commands/wf-design.md +5 -4
- package/modules/hosts/opencode/commands/wf-maintain.md +1 -1
- package/modules/hosts/opencode/commands/wf-new-task.md +47 -25
- 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-sync-hosts.md +7 -13
- package/modules/hosts/opencode/commands/wf-test.md +2 -2
- package/modules/hosts/trae/commands/wf-build.md +1 -1
- package/modules/hosts/trae/commands/wf-design.md +5 -4
- package/modules/hosts/trae/commands/wf-maintain.md +1 -1
- package/modules/hosts/trae/commands/wf-new-task.md +47 -25
- 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-sync-hosts.md +7 -13
- package/modules/hosts/trae/commands/wf-test.md +2 -2
- package/package.json +1 -1
- package/src/cli.mjs +47 -4
- package/src/doctor.mjs +48 -3
- package/src/executor.mjs +262 -0
- package/src/executor.test.mjs +295 -0
- package/src/fresh-init.test.mjs +1 -1
- package/src/init.mjs +27 -3
- package/src/profiles.mjs +10 -3
- package/src/render.mjs +3 -3
- package/src/rule-budget.test.mjs +2 -2
- package/src/stack-profile.test.mjs +6 -0
- package/src/sync-hosts.mjs +37 -43
- package/src/sync-hosts.test.mjs +67 -56
- package/src/validate.mjs +6 -1
- package/src/validate.test.mjs +1 -1
- package/templates/.gitattributes +12 -1
- package/templates/AGENTS.md +3 -3
- package/templates/_agents/commands/build.md +1 -1
- package/templates/_agents/commands/design.md +5 -4
- package/templates/_agents/commands/maintain.md +1 -1
- package/templates/_agents/commands/new-task.md +47 -25
- package/templates/_agents/commands/plan.md +1 -1
- package/templates/_agents/commands/review.md +2 -2
- package/templates/_agents/commands/sync-hosts.md +7 -13
- package/templates/_agents/commands/test.md +2 -2
- package/templates/_agents/control-surfaces.txt +1 -1
- package/templates/_agents/engine-lock.json +334 -0
- 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 +5 -2
- package/templates/_agents/level-hints.txt +30 -0
- 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-delegations.test.mjs +5 -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 +59 -127
- package/templates/_agents/scripts/check-engine-integrity.test.mjs +33 -45
- 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 +74 -25
- package/templates/_agents/scripts/check-lane-surface.test.mjs +71 -5
- package/templates/_agents/scripts/check-ledger-invariant.mjs +427 -78
- package/templates/_agents/scripts/check-ledger-invariant.test.mjs +438 -4
- package/templates/_agents/scripts/check-loop-rev.test.mjs +13 -8
- package/templates/_agents/scripts/check-loop-unit.test.mjs +8 -0
- package/templates/_agents/scripts/check-loop.mjs +264 -75
- package/templates/_agents/scripts/check-loop.test.mjs +587 -61
- package/templates/_agents/scripts/check-metric-claims.mjs +4 -2
- package/templates/_agents/scripts/check-prejudgment.mjs +59 -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 +97 -0
- package/templates/_agents/scripts/compact-ledgers.test.mjs +162 -0
- package/templates/_agents/scripts/confirm-doc.mjs +136 -31
- package/templates/_agents/scripts/confirm-doc.test.mjs +180 -4
- package/templates/_agents/scripts/doctor.test.mjs +44 -1
- package/templates/_agents/scripts/edit-face-check.mjs +5 -3
- package/templates/_agents/scripts/edit-face-check.test.mjs +5 -4
- package/templates/_agents/scripts/fill-intent.mjs +7 -5
- 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 +153 -0
- package/templates/_agents/scripts/fixture-scripts.test.mjs +112 -0
- package/templates/_agents/scripts/gate-checklist.mjs +10 -9
- package/templates/_agents/scripts/gate-checklist.test.mjs +2 -11
- 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 +16 -14
- package/templates/_agents/scripts/gen-workflow-dashboard.test.mjs +2 -1
- package/templates/_agents/scripts/gen-workflow-metrics.mjs +12 -36
- package/templates/_agents/scripts/git-core.mjs +45 -0
- package/templates/_agents/scripts/git-merge-jsonl.mjs +63 -0
- package/templates/_agents/scripts/git-merge-jsonl.test.mjs +125 -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-doc-chain.test.mjs +166 -0
- package/templates/_agents/scripts/ledger-loader.mjs +108 -0
- package/templates/_agents/scripts/ledger-loader.test.mjs +131 -0
- package/templates/_agents/scripts/ledger-prune.mjs +96 -0
- package/templates/_agents/scripts/ledger-prune.test.mjs +120 -0
- package/templates/_agents/scripts/ledger-shards.mjs +307 -0
- package/templates/_agents/scripts/ledger-shards.test.mjs +339 -0
- package/templates/_agents/scripts/ledger-ts-monotonic.test.mjs +135 -0
- package/templates/_agents/scripts/level-criteria.mjs +231 -0
- package/templates/_agents/scripts/level-criteria.test.mjs +281 -0
- 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 +10 -24
- package/templates/_agents/scripts/pipeline-run.test.mjs +22 -42
- package/templates/_agents/scripts/policy.mjs +75 -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 +38 -21
- 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/wf-journal.test.mjs +1 -1
- package/templates/_agents/scripts/wiki-search.test.mjs +5 -6
- 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/workflows-check.mjs +1 -1
- 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/workflow-enums.txt +8 -1
- package/templates/_agents/workflows/pipeline-closing.md +4 -5
- package/templates/_githooks/pre-commit +109 -120
- package/templates/_githooks/pre-merge-commit +27 -23
- package/templates/_githooks/pre-push +7 -6
- package/templates/_github/workflows/kit-ci.yml +4 -3
- package/templates/workflow/README.md +23 -5
- 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/modules/hosts/claude/commands/wf-source-sync-check.md +0 -89
- package/modules/hosts/codex/commands/wf-source-sync-check.md +0 -89
- package/modules/hosts/cursor/commands/wf-source-sync-check.md +0 -89
- package/modules/hosts/opencode/commands/wf-source-sync-check.md +0 -89
- package/modules/hosts/trae/commands/wf-source-sync-check.md +0 -90
- package/templates/_agents/commands/source-sync-check.md +0 -103
- package/templates/_agents/hooks/check-pairing-incremental.sh +0 -43
- package/templates/_agents/hooks/check-wiki-ledger.sh +0 -18
- package/templates/_agents/scripts/check-ledger.mjs +0 -109
- package/templates/_agents/scripts/check-ledger.test.mjs +0 -199
- package/templates/_agents/scripts/rule-budget.sh +0 -74
- package/templates/_agents/scripts/source-sync-check.mjs +0 -156
- package/templates/_agents/scripts/source-sync-check.test.mjs +0 -286
- package/templates/_agents/workflows/source-sync-repair.md +0 -30
|
@@ -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
|
}
|
|
@@ -271,7 +271,9 @@ function changedFiles(ctx, run) {
|
|
|
271
271
|
}
|
|
272
272
|
|
|
273
273
|
// 脚本自身管理的运行态路径——confirm/gate/gen-index 副产物,不属宿主越权面(沙箱演练实证,2026-10-02)
|
|
274
|
-
|
|
274
|
+
// confirmations.d/ 亦为运行态副产物(2026-10-12 ledger-shard-write 的 `--shard` 写入面)——
|
|
275
|
+
// 不加会让 --shard 的确认行被沙箱越权检查误判为「宿主改了台账」。
|
|
276
|
+
const RUNTIME_OWNED = ['.agents/cache/', '.agents/confirmations.jsonl', '.agents/confirmations.d/', 'workflow/INDEX.md'];
|
|
275
277
|
|
|
276
278
|
function checkAuthorized(ctx, run, filesCsv) {
|
|
277
279
|
const listed = String(filesCsv || '').split(/[,,]/).map((s) => s.trim().replace(/\\/g, '/')).filter(Boolean);
|
|
@@ -338,9 +340,9 @@ function advance(ctx, run, opt) {
|
|
|
338
340
|
issueWO(ctx, run, wo(run, {
|
|
339
341
|
kind: 'triage', title: '判级与分类', goal: '读需求与 new-task.md 判级闭集,输出 kind/level/module/topic',
|
|
340
342
|
instructions: [
|
|
341
|
-
'1. 读 .agents/
|
|
343
|
+
'1. 读 .agents/scripts/level-criteria.mjs 判据单源(L2_SURFACES 触达面闭集 / L1 默认档 / L3_SURFACES 结构面;new-task.md 只承载流程表,判据一律看单源;机器面可先跑 `flow-kit route --kind require` 取路径下限提示)',
|
|
342
344
|
`2. 对需求定级:「${run.requirement}」${run.hint ? `(预判参考:${run.hint})` : ''}`,
|
|
343
|
-
'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>"',
|
|
344
346
|
],
|
|
345
347
|
context: { docPath: '.agents/commands/new-task.md', kbHits: [], stderrTail: '', relatedDocs: ['workflow/INDEX.md'] },
|
|
346
348
|
constraints: ['判级就高不就低;勾红线必 L2+;module 取 .agents/workflow-modules.txt 词表'],
|
|
@@ -358,8 +360,7 @@ function advance(ctx, run, opt) {
|
|
|
358
360
|
run.docs = run.docs || { entry: null, spec: null, plan: null };
|
|
359
361
|
// 路由
|
|
360
362
|
run.stage = K() === 'verify' ? 'verify' : K() === 'review' ? 'review-only' : K() === 'deploy' ? 'deploy-check'
|
|
361
|
-
: K() === '
|
|
362
|
-
: K() === 'fix' ? 'incident-fill' : 'intent-fill';
|
|
363
|
+
: K() === 'fix' ? 'incident-fill' : 'intent-fill';
|
|
363
364
|
continue;
|
|
364
365
|
}
|
|
365
366
|
run.stage = 'intent-fill'; continue;
|
|
@@ -487,7 +488,7 @@ function advance(ctx, run, opt) {
|
|
|
487
488
|
const rel = `workflow/plans/${today()}-${topic()}.md`;
|
|
488
489
|
run.docs.plan = rel;
|
|
489
490
|
if (!docState(ctx, rel)) {
|
|
490
|
-
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]);
|
|
491
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(' | ')}`]);
|
|
492
493
|
}
|
|
493
494
|
run.stage = 'plan-draft';
|
|
@@ -665,22 +666,7 @@ function advance(ctx, run, opt) {
|
|
|
665
666
|
stopConfirm(ctx, run, rest[0], `${label}——逐份原话,一次一份(余 ${rest.length} 份)`, true);
|
|
666
667
|
}
|
|
667
668
|
|
|
668
|
-
|
|
669
|
-
if (!opt.files) {
|
|
670
|
-
issueWO(ctx, run, wo(run, {
|
|
671
|
-
kind: 'implement', title: `L0 直改:${briefReq(run)}`, goal: 'docs/样式微调,无行为影响——直接改后提交',
|
|
672
|
-
instructions: ['1. 按需求直接修改目标文件', '2. 完成后回填:next --files "…"(脚本核对改动面并走 pre-commit 提交)'],
|
|
673
|
-
context: { docPath: '', kbHits: run.lastKbHits || [], stderrTail: '', relatedDocs: [] },
|
|
674
|
-
constraints: ['L0 语义:无行为影响;越此范围=判级错误,停机上报改判'],
|
|
675
|
-
acceptance: ['git 改动 ⊆ 回填清单', 'pre-commit 门通过'],
|
|
676
|
-
}), 'direct-edit');
|
|
677
|
-
}
|
|
678
|
-
const a = checkAuthorized(ctx, run, opt.files);
|
|
679
|
-
if (!a.ok) stopGateFail(ctx, run, s, a.problems);
|
|
680
|
-
const c = docsCommit(ctx, run, run.lastFiles, `docs(${run.triage.module}): ${briefReq(run)}(L0,pipeline-run ${run.runId})`);
|
|
681
|
-
if (!c.ok) stopGateFail(ctx, run, s, [c.msg]);
|
|
682
|
-
stopDone(ctx, run);
|
|
683
|
-
}
|
|
669
|
+
// direct-edit 分支随 L0 退役删除(2026-10-13 自源仓收敛系列 1 回灌)——最低档 L1 走 intent+plan 闭环。
|
|
684
670
|
|
|
685
671
|
if (s === 'verify-only') { run.stage = 'verify'; continue; }
|
|
686
672
|
if (s === 'review-only') {
|
|
@@ -47,7 +47,7 @@ console.log('✅ intent 草稿:' + out);
|
|
|
47
47
|
fs.writeFileSync(out, body);
|
|
48
48
|
console.log('✅ spec 草稿:' + out);
|
|
49
49
|
`);
|
|
50
|
-
fake('fill-plan.mjs', FILL_HEAD + `const body = '---\\n状态: draft\\n级别: ' +
|
|
50
|
+
fake('fill-plan.mjs', FILL_HEAD + `const body = '---\\n状态: draft\\n级别: ' + lv + '\\n模块: pipeline\\n---\\n# PLAN — ' + t + '\\n\\n## 改动方案\\n\\n- src/app.mjs:实现功能\\n\\n## 约束与风险\\n\\n- 风险低\\n\\n## 任务拆解\\n\\n1. 实现\\n\\n## 执行顺序\\n\\n1\\n\\n## 验证计划\\n\\n- 静态门:npm test\\n\\n## 确认与复核\\n- 待\\n';
|
|
51
51
|
fs.writeFileSync(out, body);
|
|
52
52
|
console.log('✅ plan 草稿:' + out);
|
|
53
53
|
`);
|
|
@@ -289,22 +289,13 @@ test('修复环 cap 3 → 超限 gate-fail exit 2', () => {
|
|
|
289
289
|
assert.match(out(p), /修复环超限/);
|
|
290
290
|
});
|
|
291
291
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
prun(f, ['start', '改一处文案']);
|
|
295
|
-
prun(f, ['next', '--triage', 'kind=require level=L0 module=wiki topic=copy1']);
|
|
296
|
-
const pw = prun(f, ['next']); // 无 --files:重发工单
|
|
297
|
-
assert.match(out(pw), /PIPELINE-STOP work-order/);
|
|
298
|
-
const p = prun(f, ['next', '--files', 'README.md'], { FIXTURE_GIT_STATUS: ' M README.md' });
|
|
299
|
-
assert.equal(p.status, 0, out(p));
|
|
300
|
-
assert.match(out(p), /PIPELINE-STOP done/);
|
|
301
|
-
assert.match(fs.readFileSync(f.gitLog, 'utf8'), /commit -m docs\(wiki\): 改一处文案/);
|
|
302
|
-
});
|
|
292
|
+
// (2026-10-13 L0 退役,自源仓收敛系列 1 回灌:原「L0 路径:直改 → 提交 → done」场景随 direct-edit
|
|
293
|
+
// 分支删除——最低档 L1 走 intent+plan 闭环,无直改通道。)
|
|
303
294
|
|
|
304
295
|
test('verify-only 短路径直达 done', () => {
|
|
305
296
|
const f = buildFixture();
|
|
306
297
|
prun(f, ['start', '跑下 verify']);
|
|
307
|
-
const p = prun(f, ['next', '--triage', 'kind=verify level=
|
|
298
|
+
const p = prun(f, ['next', '--triage', 'kind=verify level=L1 module=pipeline topic=v1']);
|
|
308
299
|
assert.equal(p.status, 0, out(p));
|
|
309
300
|
assert.match(out(p), /PIPELINE-STOP done/);
|
|
310
301
|
assert.ok(runFile(f, latestId(f)).events.some((e) => e.type === 'gate' && e.cmd.includes('verify.mjs') && e.exit === 0));
|
|
@@ -313,7 +304,7 @@ test('verify-only 短路径直达 done', () => {
|
|
|
313
304
|
test('review-only:工单 → --done 收尾可达(复核 P1-1 回归)', () => {
|
|
314
305
|
const f = buildFixture();
|
|
315
306
|
prun(f, ['start', '评审一下']);
|
|
316
|
-
const pw = prun(f, ['next', '--triage', 'kind=review level=
|
|
307
|
+
const pw = prun(f, ['next', '--triage', 'kind=review level=L1 module=pipeline topic=rv1']);
|
|
317
308
|
assert.match(out(pw), /PIPELINE-STOP work-order/);
|
|
318
309
|
const pd = prun(f, ['next', '--done']);
|
|
319
310
|
assert.equal(pd.status, 0, out(pd));
|
|
@@ -332,7 +323,7 @@ test('deploy 路径:三件 done → prep 工单 → 机器校验 → 授权
|
|
|
332
323
|
const f = buildFixture();
|
|
333
324
|
seedDeployDocs(f, { intentState: 'done', planState: 'done' });
|
|
334
325
|
prun(f, ['start', '可以上了']);
|
|
335
|
-
const pw = prun(f, ['next', '--triage', 'kind=deploy level=
|
|
326
|
+
const pw = prun(f, ['next', '--triage', 'kind=deploy level=L1 module=pipeline topic=dep1']);
|
|
336
327
|
assert.equal(pw.status, 0, out(pw));
|
|
337
328
|
assert.match(out(pw), /deploy-prep/);
|
|
338
329
|
assert.match(out(pw), /PIPELINE-STOP work-order/);
|
|
@@ -350,7 +341,7 @@ test('deploy 三件缺口 → gate-fail 列缺口 exit 2(P1-2)', () => {
|
|
|
350
341
|
const f = buildFixture();
|
|
351
342
|
seedDeployDocs(f, { intentState: 'done', planState: 'approved' }); // plan 未关单
|
|
352
343
|
prun(f, ['start', '可以上了']);
|
|
353
|
-
prun(f, ['next', '--triage', 'kind=deploy level=
|
|
344
|
+
prun(f, ['next', '--triage', 'kind=deploy level=L1 module=pipeline topic=dep1']);
|
|
354
345
|
const p = prun(f, ['next']);
|
|
355
346
|
assert.equal(p.status, 2);
|
|
356
347
|
assert.match(out(p), /须 done/);
|
|
@@ -362,7 +353,7 @@ test('deploy 边界:incident closed 但 plan 缺失 → gate-fail(复核 P2-
|
|
|
362
353
|
fs.writeFileSync(path.join(f.root, 'workflow', 'incidents', '2026-10-02-incd.md'),
|
|
363
354
|
'---\n状态: closed\n发现: 2026-10-02\n模块: pipeline\n---\n# INCIDENT — incd\n\n## 时间线\n\n- x\n\n## 根因\n\nx\n\n## 复盘三件套\n\nx\n');
|
|
364
355
|
prun(f, ['start', '可以上了']);
|
|
365
|
-
prun(f, ['next', '--triage', 'kind=deploy level=
|
|
356
|
+
prun(f, ['next', '--triage', 'kind=deploy level=L1 module=pipeline topic=incd']);
|
|
366
357
|
const p = prun(f, ['next']);
|
|
367
358
|
assert.equal(p.status, 2);
|
|
368
359
|
assert.match(out(p), /plans\/2026-10-02-incd\.md(缺失——须 done)/);
|
|
@@ -372,32 +363,26 @@ test('deploy 边界:topic 子串不再误匹配(复核 P2-C 收紧回归)'
|
|
|
372
363
|
const f = buildFixture();
|
|
373
364
|
seedDeployDocs(f, { intentState: 'done', planState: 'done', topic: 'dep1' });
|
|
374
365
|
prun(f, ['start', '可以上了']);
|
|
375
|
-
const p = prun(f, ['next', '--triage', 'kind=deploy level=
|
|
366
|
+
const p = prun(f, ['next', '--triage', 'kind=deploy level=L1 module=pipeline topic=dep']); // 旧版 includes 会误命中 dep1
|
|
376
367
|
assert.equal(p.status, 2);
|
|
377
368
|
assert.match(out(p), /找不到可上线的对象/);
|
|
378
369
|
});
|
|
379
370
|
|
|
380
371
|
// P2-4 A′:提交命中 managed 面的处理(装户降级 WARN / 包源自愈 sync)
|
|
372
|
+
// (2026-10-13 L0 退役:原用例经 direct-edit 直改通道驱动;该通道随级别退役删除,改由 L1 闭环的
|
|
373
|
+
// workflow 文档提交(plan-confirm 批次一 docsCommit)驱动同一 managedFaceCheck 判定面。)
|
|
381
374
|
test('P2-4 A′:命中台账件且无 flow-kit → 装户降级 WARN + 台账随提交(复核回归)', () => {
|
|
382
375
|
const f = buildFixture();
|
|
383
|
-
fs.
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
editDoc(f, '.agents/notes/runtime-env.md', (t) => t + '\n- 演练追加一行\n');
|
|
389
|
-
const p = prun(f, ['next', '--files', '.agents/notes/runtime-env.md'], { FIXTURE_GIT_STATUS: ' M .agents/notes/runtime-env.md' });
|
|
390
|
-
assert.equal(p.status, 0, out(p));
|
|
391
|
-
assert.match(out(p), /⚠️ 触及 managed 面/);
|
|
392
|
-
const addLine = fs.readFileSync(f.gitLog, 'utf8').split('\n').find((l) => l.startsWith('add '));
|
|
393
|
-
assert.equal(addLine, 'add .agents/notes/runtime-env.md', '装户降级只 WARN:add 面不含 kit.json(复核 P2-D 断言改实)');
|
|
376
|
+
fs.writeFileSync(path.join(f.root, '.agents', 'kit.json'), JSON.stringify({ managed: [{ rel: 'workflow/intents/2026-10-02-mng1.md', sha256: 'x' }] }));
|
|
377
|
+
driveToImplement(f, 'L1', 'mng1');
|
|
378
|
+
const run = runFile(f, latestId(f));
|
|
379
|
+
assert.ok(run.events.some((e) => e.type === 'warn' && /触及 managed 面/.test(e.note)), '装户环境降级 WARN 出账');
|
|
380
|
+
assert.doesNotMatch(fs.readFileSync(f.gitLog, 'utf8'), /add .*\.agents\/kit\.json/, '装户降级只 WARN:add 面不含 kit.json(复核 P2-D 断言改实)');
|
|
394
381
|
});
|
|
395
382
|
|
|
396
383
|
test('P2-4 A′:包源环境自动 sync——flow-kit 假件被调 + 产物入提交(复核回归)', () => {
|
|
397
384
|
const f = buildFixture();
|
|
398
|
-
fs.
|
|
399
|
-
fs.writeFileSync(path.join(f.root, '.agents', 'notes', 'runtime-env.md'), 'notes\n');
|
|
400
|
-
fs.writeFileSync(path.join(f.root, '.agents', 'kit.json'), JSON.stringify({ managed: [{ rel: '.agents/notes/runtime-env.md', sha256: 'x' }] }));
|
|
385
|
+
fs.writeFileSync(path.join(f.root, '.agents', 'kit.json'), JSON.stringify({ managed: [{ rel: 'workflow/intents/2026-10-02-mng2.md', sha256: 'x' }] }));
|
|
401
386
|
fs.mkdirSync(path.join(f.root, 'bin'), { recursive: true });
|
|
402
387
|
const syncLog = path.join(f.root, 'flowkit-sync.log');
|
|
403
388
|
fs.writeFileSync(path.join(f.root, 'bin', 'flow-kit.mjs'), `#!/usr/bin/env node
|
|
@@ -408,11 +393,7 @@ const kit = JSON.parse(fs.readFileSync(kp, 'utf8'));
|
|
|
408
393
|
kit.managed[0].sha256 = 'refreshed';
|
|
409
394
|
fs.writeFileSync(kp, JSON.stringify(kit));
|
|
410
395
|
`);
|
|
411
|
-
|
|
412
|
-
prun(f, ['next', '--triage', 'kind=require level=L0 module=wiki topic=mng2']);
|
|
413
|
-
editDoc(f, '.agents/notes/runtime-env.md', (t) => t + '\n- 演练追加一行\n');
|
|
414
|
-
const p = prun(f, ['next', '--files', '.agents/notes/runtime-env.md'], { FIXTURE_GIT_STATUS: ' M .agents/notes/runtime-env.md' });
|
|
415
|
-
assert.equal(p.status, 0, out(p));
|
|
396
|
+
driveToImplement(f, 'L1', 'mng2');
|
|
416
397
|
assert.match(fs.readFileSync(syncLog, 'utf8'), /sync-called/);
|
|
417
398
|
const run = runFile(f, latestId(f));
|
|
418
399
|
assert.ok(run.events.some((e) => e.type === 'gate' && e.cmd.includes('flow-kit.mjs sync') && e.exit === 0));
|
|
@@ -423,11 +404,10 @@ fs.writeFileSync(kp, JSON.stringify(kit));
|
|
|
423
404
|
test('P2-4 A′:未命中台账面 → 不触发 sync/告警', () => {
|
|
424
405
|
const f = buildFixture();
|
|
425
406
|
fs.writeFileSync(path.join(f.root, '.agents', 'kit.json'), JSON.stringify({ managed: [{ rel: '.agents/notes/runtime-env.md', sha256: 'x' }] }));
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
assert.
|
|
430
|
-
assert.doesNotMatch(out(p), /managed 面/);
|
|
407
|
+
driveToImplement(f, 'L1', 'mng3');
|
|
408
|
+
const run = runFile(f, latestId(f));
|
|
409
|
+
assert.ok(!run.events.some((e) => e.type === 'warn' && /managed 面/.test(e.note)), '未命中台账面不出告警');
|
|
410
|
+
assert.ok(!run.events.some((e) => e.type === 'gate' && String(e.cmd).includes('flow-kit.mjs sync')), '未命中台账面不触发 sync');
|
|
431
411
|
});
|
|
432
412
|
|
|
433
413
|
test('rename 行 old+new 双收录防逃逸(复核 P2-2 回归)', () => {
|
|
@@ -119,8 +119,64 @@ export const POLICIES = {
|
|
|
119
119
|
// 消费方:check-loop 检查 15 的 scope 子项(台账行 ts ≥ 本值才判)。
|
|
120
120
|
scopeSince: '2026-10-09T11:24:43Z',
|
|
121
121
|
},
|
|
122
|
+
// 版本 8 = 版本 7 的全部键 + ledgerDocChainSince(台账哈希链的**链域**从「文件物理序」改为
|
|
123
|
+
// 「按 doc 的逻辑序」,2026-10-12 ledger-doc-chain)。
|
|
124
|
+
// 为什么改:原链 prevHash = 物理上一行的 hash,隐含「台账是单写者顺序追加」的前提。三种并发形态
|
|
125
|
+
// 全部破坏它——① 两进程并发 append 读到同一末行 → 同 prevHash 分叉;② git merge 并集(见
|
|
126
|
+
// git-merge-jsonl.mjs)后 B 侧行被追加到 A 尾部之后 → A 侧链被劈开;③ 分片归并 compact 把分片行
|
|
127
|
+
// 插进主台账中部 → 断链。且这三者都是**正常协作**,不是篡改——让正常协作长期撞 hard 门,门禁就会被
|
|
128
|
+
// 绕过(--no-verify),治理失效。
|
|
129
|
+
// 为什么按 doc 分域:台账行的天然主键是 doc(每行 = 某份文档的一次确认)。**同一 doc 内的先后有语义,
|
|
130
|
+
// doc 之间的先后没有语义**。把链的域对齐语义域是对既有判据的修正,不是削弱——就地改行、删行、
|
|
131
|
+
// 插行三类篡改在 doc 链下一律仍被抓住(见 check-loop 检查 15)。
|
|
132
|
+
// 本版本只加一个开关键:**缺键(v1-v7)时写入侧与校验侧都走旧物理链口径**,存量台账零变化、零新增告警。
|
|
133
|
+
// 粒度取「时刻」而非日期(同 scopeSince / verifyDocSince 的教训):本日已有一批旧口径行写入,
|
|
134
|
+
// 日期粒度会把它们纳入新受审范围、产出不可消退告警。
|
|
135
|
+
// 消费方:confirm-doc appendLedger(写入侧取前驱)+ check-loop 检查 15(校验侧按行分域)。
|
|
136
|
+
8: {
|
|
137
|
+
moduleSince: '2026-09-22',
|
|
138
|
+
check14Since: '2026-09-26',
|
|
139
|
+
confirmDocsEffective: '2026-09-27',
|
|
140
|
+
confirmIncidentsEffective: '2026-09-28',
|
|
141
|
+
bindingTs: '2026-09-28',
|
|
142
|
+
check17UnclosedAfter: '0.8.0',
|
|
143
|
+
stageGateSince: '2026-09-29',
|
|
144
|
+
riskLevelSince: '2026-10-01',
|
|
145
|
+
verifySince: '2026-10-07',
|
|
146
|
+
verifyDocSince: '2026-10-08T09:37:35Z',
|
|
147
|
+
scopeSince: '2026-10-09T11:24:43Z',
|
|
148
|
+
ledgerDocChainSince: '2026-10-12T07:20:00Z',
|
|
149
|
+
},
|
|
122
150
|
};
|
|
123
151
|
|
|
152
|
+
// ---- 版本 9–12(2026-10-13 B6b-1,自源仓 policy v9–v12 对齐回灌)----
|
|
153
|
+
// 构造式沿源仓 2026-10-10 收敛系列 6 的 merge 链(基线 + 显式差异):kit v1–v8 键史原样保留不动,
|
|
154
|
+
// 新版本在 kit v8 之上做超集追加,消除「全量手抄日期键、漏抄即静默改语义」的脆弱点(v2 注释自警同源)。
|
|
155
|
+
// 键名与源仓逐字一致;kit 无源仓 v6–v8 的 check14HardSince / verifyHardSince——那两个键的消费段
|
|
156
|
+
// (检查 14 hard 档 / 凭证 hard 档)kit 仍为恒 advisory 面,未落地不引入。incidentRegrSince 随 B6b-2
|
|
157
|
+
//(§21–26 同批)落地并激活其消费段 §3x 防复发证据门。各锚日期 = kit 落地日口径:
|
|
158
|
+
// 发布当日(2026-10-13)= specVerifSince / attributionSince / convergenceSince(advisory 档沿源仓
|
|
159
|
+
// convergenceSince「发布当日」先例——当日起、存量不追溯);
|
|
160
|
+
// 发布次日(2026-10-14)= convergenceHardSince / reviewerSince / incidentRegrSince(沿源仓
|
|
161
|
+
// check14HardSince「发布次日」先例——当日在途件不追溯、维持 advisory 留痕,锚起新违例才升格)。
|
|
162
|
+
// 缺键(v1–v8)→ 对应消费段整体跳过(存量零噪声)。抄全键仍是铁律。
|
|
163
|
+
const merge = (base, over) => ({ ...base, ...over });
|
|
164
|
+
const V9 = merge(POLICIES[8], {
|
|
165
|
+
incidentRegrSince: '2026-10-14', // 检查 3x incident 防复发验证证据门生效锚(B6b-2 §3x 消费,hard 档「发布次日」口径——在途 fixed 件不追溯);v1–v8 无此键 → 该段整体跳过
|
|
166
|
+
specVerifSince: '2026-10-13', // 检查 21 spec 验证落地核查生效锚(B6b-2 §21 消费;kit 落地 2026-10-13);v1–v8 无此键 → 该检查整体跳过
|
|
167
|
+
attributionSince: '2026-10-13', // 检查 24 incident 归因字段生效锚(B6b-2 §24 消费;kit 落地 2026-10-13);v1–v8 无此键 → 该检查整体跳过
|
|
168
|
+
});
|
|
169
|
+
const V10 = merge(V9, {
|
|
170
|
+
convergenceSince: '2026-10-13', // 检查 23 判据级收敛对账生效锚(B6b-2 §23 advisory 档消费;kit 落地 2026-10-13);v1–v9 无此键 → 该检查整体跳过
|
|
171
|
+
});
|
|
172
|
+
const V11 = merge(V10, {
|
|
173
|
+
convergenceHardSince: '2026-10-14', // 检查 23 证据无效 hard 锚(B6b-2 §23 hard 档消费;kit 落地 2026-10-14);v1–v10 无此键 → 维持 warning
|
|
174
|
+
});
|
|
175
|
+
const V12 = merge(V11, {
|
|
176
|
+
reviewerSince: '2026-10-14', // 检查 1 独立复核上下文标识生效锚(B6b-2 检查 1 扩展消费;kit 落地 2026-10-14);v1–v11 无此键 → 该子检查整体跳过
|
|
177
|
+
});
|
|
178
|
+
Object.assign(POLICIES, { 9: V9, 10: V10, 11: V11, 12: V12 });
|
|
179
|
+
|
|
124
180
|
export function loadKitPolicy(root) {
|
|
125
181
|
let raw = null;
|
|
126
182
|
try {
|
|
@@ -180,6 +236,25 @@ export function ledgerChainHash(row, prevHash) {
|
|
|
180
236
|
return createHash('sha256').update((prevHash || '') + JSON.stringify(row)).digest('hex');
|
|
181
237
|
}
|
|
182
238
|
|
|
239
|
+
// ---- 台账 doc 链前驱(2026-10-12 ledger-doc-chain)----
|
|
240
|
+
// 单源:confirm-doc appendLedger(写入侧取前驱)与 check-loop 检查 15(校验侧判接续)共用同一语义。
|
|
241
|
+
// ledgerDocChainActive(policy):锚存在且为合法 ISO 时刻 → 新口径生效;缺键(v1-v7)→ 旧物理链。
|
|
242
|
+
// ledgerDocChainPrev(rows, doc):rows 为物理序的已解析台账行,返回该 doc **最后一个带 hash 行**的
|
|
243
|
+
// hash,无则 ''(链段重启)。取不到带 hash 的行时返回 '',与旧口径「末行无 hash → ''」同向。
|
|
244
|
+
// 为什么不做去重/合并:去重是**口径变更**(会改漏斗计数),属分片功能同批生效,不在本步。
|
|
245
|
+
export function ledgerDocChainActive(policy) {
|
|
246
|
+
const s = policy && policy.ledgerDocChainSince;
|
|
247
|
+
return typeof s === 'string' && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/.test(s);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function ledgerDocChainPrev(rows, doc) {
|
|
251
|
+
for (let i = (rows && rows.length ? rows.length : 0) - 1; i >= 0; i--) {
|
|
252
|
+
const r = rows[i];
|
|
253
|
+
if (r && r.doc === doc && typeof r.hash === 'string') return r.hash;
|
|
254
|
+
}
|
|
255
|
+
return '';
|
|
256
|
+
}
|
|
257
|
+
|
|
183
258
|
// ---- 台账 scope(机器派生改动面,2026-10-09 审查 A1 干净版)----
|
|
184
259
|
// scopeDigest(files) = {files, sha256}:files 归一(反斜杠→posix)、去重、排序;sha256 = files.join('\n') 的摘要。
|
|
185
260
|
// **单源**(confirm-doc 落账侧构造与 check-loop 检查 15 校验侧重算共用——防两处归一/排序口径漂移,
|