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,64 @@
|
|
|
1
|
+
// check-prejudgment.mjs — 检查 25:路由预判一致性 [warning](2026-10-13 B6b-2 自 Shipyard.Material
|
|
2
|
+
// check-loop-sections/s25-prejudgment.mjs 回灌;段体判据逐字对齐,出处沿革留痕在包源仓 git 历史与
|
|
3
|
+
// CHANGELOG——装户径不引实例路径)
|
|
4
|
+
// 背景(prejudgment-ledger):类型误路由(修复被当新需求 → 跳过 incident 立项与 kb-search 历史检索)
|
|
5
|
+
// 是路由自动识别的静默盲区;级别判低已有「红线判低」hard 与泳道门兜底,但「入口预判 vs 实际落位」的
|
|
6
|
+
// 偏离本身不可见。本段只做 advisory 比对:frontmatter「预判: kind=<kind> level=<L1|L2|L3>」在场才检——
|
|
7
|
+
// kind 与入口类型不符 / level 与「级别」字段不符 / 词表非法 → WARN,不阻断。
|
|
8
|
+
// kind 语义源 = pipeline-run.mjs KINDS(require/fix/verify/review/deploy);类型只对 intents(≡require)/
|
|
9
|
+
// incidents(≡fix) 执法——plans/specs 是两道共用件不判类型、只比对级别。
|
|
10
|
+
// 缺字段零告警(存量宽容不回填,不做强制前置)——灰度数据经 judgments.jsonl(WARN 预判* 计数)
|
|
11
|
+
// 月度对账后再议升格,退役/升格阈值由 mechanism-registry §25 行承载。
|
|
12
|
+
// kit 适配(源仓→kit 逐条):无判据改动。kind 词表沿源仓 s25 等价落——pipeline-run.mjs 的 KINDS 为
|
|
13
|
+
// 模块内常量、无导出(两侧同状),故本文件按源仓同款内联声明 + 注释互引(改任一侧须同步,见下)。
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
|
|
16
|
+
export function run(ctx) {
|
|
17
|
+
const { ROOT, WF, DOC_DIRS, ENUMS, inSet, fmGet, isTracked, readdirOrNull, warnings } = ctx;
|
|
18
|
+
// --- 25. 路由预判一致性 [warning] ---
|
|
19
|
+
// kind 词表单源 = pipeline-run.mjs KINDS(该处为模块内常量、未导出——源仓 s25 同款内联等价落);
|
|
20
|
+
// 两侧各一份靠本注释互引,改词表须同步(pipeline-run 校验文案 `kind 须为 …` 同步)。
|
|
21
|
+
const KINDS = ['require', 'fix', 'verify', 'review', 'deploy'];
|
|
22
|
+
const rankOf = (lv) => Number(String(lv).replace(/^L/, '')) || 0;
|
|
23
|
+
for (const sub of DOC_DIRS) {
|
|
24
|
+
const dir = path.join(ROOT, WF, sub);
|
|
25
|
+
for (const f of readdirOrNull(dir) || []) {
|
|
26
|
+
if (!f.endsWith('.md') || f === '_TEMPLATE.md') continue;
|
|
27
|
+
const abs = path.join(dir, f);
|
|
28
|
+
if (!isTracked(abs)) continue;
|
|
29
|
+
const pj = fmGet(abs, '预判');
|
|
30
|
+
if (!pj) continue;
|
|
31
|
+
const rel = `${WF}/${sub}/${f}`;
|
|
32
|
+
// 终态文档(2026-10-15):形态/词表类告警跳过——终态受检查 15 内容绑定不可修订,此类 WARN 永不可
|
|
33
|
+
// 消(「不可消除噪声」红线);类型/级别比对保留——那是路由审计的本体信号,灰度对账照常采集。
|
|
34
|
+
// (终态并集 = doc 终态 done/superseded/cancelled + incident 终态 fixed/closed——两侧枚举无单一键
|
|
35
|
+
// 覆盖该并集,故与源仓 s25 同款内联声明。)
|
|
36
|
+
const terminalDoc = ['done', 'superseded', 'cancelled', 'fixed', 'closed'].includes(fmGet(abs, '状态'));
|
|
37
|
+
const kv = {};
|
|
38
|
+
for (const m of pj.matchAll(/(kind|level)=([^\s]+)/g)) kv[m[1]] = m[2];
|
|
39
|
+
if (!Object.keys(kv).length) {
|
|
40
|
+
if (!terminalDoc) warnings.push(`- [WARN 预判字段不可解析] 须形如「预判: kind=<kind> level=<L1|L2|L3>」:${rel}`);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (kv.kind && !KINDS.includes(kv.kind) && !terminalDoc) {
|
|
44
|
+
warnings.push(`- [WARN 预判字段不可解析] kind=${kv.kind} 不在词表(${KINDS.join('/')}):${rel}`);
|
|
45
|
+
}
|
|
46
|
+
if (kv.level && !inSet(kv.level, ENUMS['level.all']) && !terminalDoc) {
|
|
47
|
+
warnings.push(`- [WARN 预判字段不可解析] level=${kv.level} 不在枚举 level.all:${rel}`);
|
|
48
|
+
}
|
|
49
|
+
// 类型比对:intents ≡ require、incidents ≡ fix(plans/specs 两道共用,不判类型)
|
|
50
|
+
const actualKind = sub === 'incidents' ? 'fix' : sub === 'intents' ? 'require' : null;
|
|
51
|
+
if (kv.kind && actualKind && kv.kind !== actualKind) {
|
|
52
|
+
warnings.push(`- [WARN 预判类型不符] 预判 kind=${kv.kind} 但落位 ${sub}(实际 kind=${actualKind})——类型误路由会跳过对应道的检索与立档,确认是否有意:${rel}`);
|
|
53
|
+
}
|
|
54
|
+
// 级别比对:与 frontmatter「级别」字段
|
|
55
|
+
if (kv.level) {
|
|
56
|
+
const lv = fmGet(abs, '级别');
|
|
57
|
+
if (lv && lv !== kv.level) {
|
|
58
|
+
const hint = rankOf(lv) < rankOf(kv.level) ? '(判低信号——落位低于预判,复核两问判据)' : '(判高——流程偏重,无害)';
|
|
59
|
+
warnings.push(`- [WARN 预判级别不符] 预判 ${kv.level} 落位 ${lv}${hint}:${rel}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// check-registry.mjs — 检查 22:机制使用量台账 [warning](2026-10-13 B6b-2 自 Shipyard.Material
|
|
2
|
+
// check-loop-sections/s22-registry.mjs 回灌;段体判据逐字对齐,出处沿革留痕在包源仓 git 历史与
|
|
3
|
+
// CHANGELOG——装户径不引实例路径)
|
|
4
|
+
// 背景:门禁只增不减(每道门都有事故背书,但无退役机制)——本检查给「低频机制退役」装强制处置环。
|
|
5
|
+
// .agents/mechanism-registry.md 在场才启用(历史 clone / 装户缺文件 = 跳过,沿 lane-surfaces.txt
|
|
6
|
+
// 「缺失即跳过」惯例,不制造一次性噪声)。表格式 markdown 管道表,列序固定:
|
|
7
|
+
// | 机制 | 门禁位置 | 触发统计口径 | 退役阈值 | 最近触发 | 状态 | 处置期限 |
|
|
8
|
+
// 判据:状态 ∈ {现役,待退役,已退役};「待退役」必须填处置期限(日期或批次号)——台账的目的不是记录
|
|
9
|
+
// 而是强制处置:没有时间表的退役意向等于永不变退役。触发计数本身半自动(口径列写明统计方法),
|
|
10
|
+
// 不进机器门——先有账,再谈自动化。纯 WARN advisory,永不 hard(机制未登记/只有示例行都只出账不拦)。
|
|
11
|
+
// kit 适配(源仓→kit 逐条):台账路径平铺 .agents/config/ → .agents/(kit 装户布局无 config/ 层)。
|
|
12
|
+
import fs from 'node:fs';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
|
|
15
|
+
export function run(ctx) {
|
|
16
|
+
const { ROOT, linesOf, warnings } = ctx;
|
|
17
|
+
// --- 22. 机制使用量台账(mechanism-registry;复杂度棘轮的出口)---
|
|
18
|
+
{
|
|
19
|
+
const regPath = path.join(ROOT, '.agents', 'mechanism-registry.md');
|
|
20
|
+
if (fs.existsSync(regPath)) {
|
|
21
|
+
const dataRows = (linesOf(regPath) || [])
|
|
22
|
+
.filter((l) => /^\|/.test(l))
|
|
23
|
+
.filter((l) => !/^\|[\s:|-]+\|?\s*$/.test(l)) // 分隔行
|
|
24
|
+
.filter((l) => (l.split('|')[1] || '').trim() !== '机制'); // 表头行
|
|
25
|
+
if (!dataRows.length) {
|
|
26
|
+
warnings.push('- [WARN 机制台账] 无数据行——表格式:| 机制 | 门禁位置 | 触发统计口径 | 退役阈值 | 最近触发 | 状态 | 处置期限 |');
|
|
27
|
+
}
|
|
28
|
+
for (const l of dataRows) {
|
|
29
|
+
const cells = l.split('|').slice(1, -1).map((c) => c.trim());
|
|
30
|
+
if (cells.length !== 7) {
|
|
31
|
+
warnings.push(`- [WARN 机制台账] 行列数 ${cells.length} ≠ 7(机制|门禁位置|触发统计口径|退役阈值|最近触发|状态|处置期限):${cells[0] || l.slice(0, 30)}`);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const [name, , , , , status, deadline] = cells;
|
|
35
|
+
if (!['现役', '待退役', '已退役'].includes(status)) {
|
|
36
|
+
warnings.push(`- [WARN 机制台账] 「${name}」状态非法(须 现役/待退役/已退役):${status}`);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (status === '待退役' && !deadline) {
|
|
40
|
+
warnings.push(`- [WARN 机制台账] 「${name}」待退役但缺处置期限——补时间表,或改回现役并在阈值列写保留理由`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// check-scope.mjs — plan 改动面 ↔ git 变更双向对账(advisory 工具;2026-10-13 自 Shipyard.Material
|
|
3
|
+
// 参考实现回灌,源仓沿革留痕在包源仓 git 历史与 CHANGELOG——装户径不引实例路径)
|
|
4
|
+
// 用途:解析 plan「改动面 / 任务拆解 / 改动方案」节声明的文件清单,与 git 变更集对账——
|
|
5
|
+
// 越界件(改了未声明)= 范围蔓延信号;未落件(声明未改)= plan 滞后或改名信号。check-loop 检查 8
|
|
6
|
+
// 的证据对账只做单向交集(提交须触及至少一个声明文件),夹带 plan 外文件不报——本件补越界方向。
|
|
7
|
+
// 语义与红线:只读裁决(判红绿,不改任何状态——命名规约 check- 前缀);默认 advisory(exit 0 出报告),
|
|
8
|
+
// --strict 时越界件非空 exit 1(供门禁化调用方使用);plan 不可读 exit 2(fail-loud,不静默放行)。
|
|
9
|
+
// 文件匹配沿 check-loop 检查 8 宽松规则(f===df / 后缀 / 双向 includes)——声明粒度粗(写目录或部分路径)
|
|
10
|
+
// 不误报越界;白名单豁免:workflow/ 台账(intents/specs/plans/incidents/INDEX)、.agents/confirmations.jsonl、
|
|
11
|
+
// .agents/confirmations.d/ 分片、package-lock.json——闭环随行件不算越界。
|
|
12
|
+
// 测试:node .agents/scripts/check-scope.test.mjs(tmp git 仓 fixture:越界 / 未落 / 白名单 / strict / 无 plan)
|
|
13
|
+
// 用法:node .agents/scripts/check-scope.mjs workflow/plans/<name>.md [--base <sha>|--staged] [--strict] [--root <dir>]
|
|
14
|
+
// 默认比对工作区(含未跟踪,git status --porcelain 口径);--base <sha> 比对 sha..HEAD 已提交变更;
|
|
15
|
+
// --staged 仅比对暂存区。
|
|
16
|
+
import fs from 'node:fs';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import { spawnSync } from 'node:child_process';
|
|
19
|
+
|
|
20
|
+
const args = process.argv.slice(2);
|
|
21
|
+
let planRel = '', base = '', staged = false, strict = false, rootArg = '';
|
|
22
|
+
for (let i = 0; i < args.length; i++) {
|
|
23
|
+
const a = args[i];
|
|
24
|
+
if (a === '--base') base = args[++i] || '';
|
|
25
|
+
else if (a === '--staged') staged = true;
|
|
26
|
+
else if (a === '--strict') strict = true;
|
|
27
|
+
else if (a === '--root') rootArg = args[++i] || '';
|
|
28
|
+
else if (!planRel && !a.startsWith('--')) planRel = a;
|
|
29
|
+
}
|
|
30
|
+
const ROOT = path.resolve(rootArg || process.cwd());
|
|
31
|
+
if (!planRel) { console.error('用法: node .agents/scripts/check-scope.mjs workflow/plans/<name>.md [--base <sha>|--staged] [--strict] [--root <dir>]'); process.exit(2); }
|
|
32
|
+
const planAbs = path.isAbsolute(planRel) ? planRel : path.join(ROOT, planRel);
|
|
33
|
+
if (!fs.existsSync(planAbs)) { console.error(`plan 不可读: ${planRel}`); process.exit(2); }
|
|
34
|
+
|
|
35
|
+
// 声明文件提取——与 check-loop 检查 8 同节同正则(改动面|任务拆解|改动方案),另加扩展名白名单
|
|
36
|
+
// (过滤 v1.2.2 之类版本号伪 token:无目录且扩展名不在清单内则弃)
|
|
37
|
+
const KNOWN_EXT = new Set(['.md', '.mjs', '.js', '.ts', '.json', '.txt', '.cs', '.csproj', '.sln', '.vue', '.ps1', '.sh', '.yml', '.yaml', '.sql', '.cjs']);
|
|
38
|
+
const declared = [];
|
|
39
|
+
{
|
|
40
|
+
const lines = fs.readFileSync(planAbs, 'utf8').split(/\r?\n/);
|
|
41
|
+
let inSec = false;
|
|
42
|
+
for (const line of lines) {
|
|
43
|
+
if (/^##\s+(改动面|任务拆解|改动方案)/.test(line)) { inSec = true; continue; }
|
|
44
|
+
if (inSec && /^##\s+/.test(line)) { inSec = false; continue; }
|
|
45
|
+
if (!inSec) continue;
|
|
46
|
+
const m = line.match(/([a-zA-Z0-9._/-]+\.[a-zA-Z0-9]+)/);
|
|
47
|
+
if (!m) continue;
|
|
48
|
+
const tok = m[1].replace(/\\/g, '/');
|
|
49
|
+
const ext = tok.slice(tok.lastIndexOf('.'));
|
|
50
|
+
if (tok.includes('/') || KNOWN_EXT.has(ext.toLowerCase())) declared.push(tok);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const git = (gitArgs, opts = {}) => spawnSync('git', gitArgs, { cwd: ROOT, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024, ...opts });
|
|
55
|
+
let changed = [];
|
|
56
|
+
if (base) {
|
|
57
|
+
const r = git(['diff', '--name-only', `${base}..HEAD`]);
|
|
58
|
+
if (r.status !== 0) { console.error(`git diff 失败: ${r.stderr}`); process.exit(2); }
|
|
59
|
+
changed = r.stdout.split(/\r?\n/).filter(Boolean);
|
|
60
|
+
} else if (staged) {
|
|
61
|
+
const r = git(['diff', '--name-only', '--cached']);
|
|
62
|
+
if (r.status !== 0) { console.error(`git diff 失败: ${r.stderr}`); process.exit(2); }
|
|
63
|
+
changed = r.stdout.split(/\r?\n/).filter(Boolean);
|
|
64
|
+
} else {
|
|
65
|
+
// 工作区口径:status --porcelain -uall(含未跟踪且逐文件展开——默认折叠成 `?? dir/` 会把整个新目录误判越界)
|
|
66
|
+
const r = git(['status', '--porcelain', '-uall']);
|
|
67
|
+
if (r.status !== 0) { console.error(`git status 失败: ${r.stderr}`); process.exit(2); }
|
|
68
|
+
changed = r.stdout.split(/\r?\n/).filter(Boolean).map((l) => {
|
|
69
|
+
const body = l.slice(3);
|
|
70
|
+
const ren = body.match(/.* -> (.*)$/); // 重命名取新路径
|
|
71
|
+
return (ren ? ren[1] : body).trim().replace(/^"(.*)"$/, '$1');
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const WHITELIST_RE = /^(?:workflow\/(?:intents|specs|plans|incidents)\/|workflow\/INDEX\.md$|\.agents\/confirmations\.jsonl$|\.agents\/confirmations\.d\/|package-lock\.json$)/;
|
|
76
|
+
const norm = (p) => p.replace(/\\/g, '/');
|
|
77
|
+
const matches = (f, df) => f === df || f.endsWith(`/${df}`) || f.includes(df) || df.includes(f);
|
|
78
|
+
|
|
79
|
+
const outOfScope = changed.filter((f) => !WHITELIST_RE.test(norm(f)) && !declared.some((df) => matches(norm(f), df)));
|
|
80
|
+
const notLanded = declared.filter((df) => !changed.some((f) => matches(norm(f), df)));
|
|
81
|
+
|
|
82
|
+
console.log(`▶ check-scope(plan 改动面 ↔ git 变更对账): ${planRel}`);
|
|
83
|
+
console.log(` 声明 ${declared.length} | 变更 ${changed.length}${base ? `(${base}..HEAD)` : staged ? '(staged)' : '(工作区)'}`);
|
|
84
|
+
if (outOfScope.length) {
|
|
85
|
+
console.log(' 越界件(改了未声明):');
|
|
86
|
+
outOfScope.forEach((f) => console.log(` - ${f}`));
|
|
87
|
+
}
|
|
88
|
+
if (notLanded.length) {
|
|
89
|
+
console.log(' 未落件(声明未变更):');
|
|
90
|
+
notLanded.forEach((f) => console.log(` - ${f}`));
|
|
91
|
+
}
|
|
92
|
+
if (!outOfScope.length && !notLanded.length) console.log(' 双向对齐 ✅');
|
|
93
|
+
else if (!strict) console.log(' (advisory 报告——--strict 时越界件即 fail)');
|
|
94
|
+
|
|
95
|
+
if (strict && outOfScope.length) process.exit(1);
|
|
96
|
+
process.exit(0);
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// check-scope.test.mjs — tmp git 仓 fixture(2026-10-13 自 Shipyard.Material 参考实现回灌)
|
|
2
|
+
// 覆盖:越界检出 / 未落检出 / 白名单豁免 / --strict 退出码 / 无 plan exit 2 / 版本号 token 过滤
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { spawnSync } from 'node:child_process';
|
|
7
|
+
|
|
8
|
+
const CLI = path.join(path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, '$1')), 'check-scope.mjs');
|
|
9
|
+
let pass = 0, fail = 0;
|
|
10
|
+
const check = (name, cond, detail = '') => {
|
|
11
|
+
if (cond) { pass++; console.log(`PASS ${name}`); }
|
|
12
|
+
else { fail++; console.log(`FAIL ${name}${detail ? ` —— ${detail}` : ''}`); }
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function mkRepo() {
|
|
16
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'check-scope-'));
|
|
17
|
+
const g = (...a) => spawnSync('git', a, { cwd: root, encoding: 'utf8' });
|
|
18
|
+
g('init', '-q');
|
|
19
|
+
g('config', 'user.email', 't@t');
|
|
20
|
+
g('config', 'user.name', 't');
|
|
21
|
+
fs.writeFileSync(path.join(root, 'src-a.md'), '# A\n');
|
|
22
|
+
g('add', '.');
|
|
23
|
+
g('commit', '-qm', 'init');
|
|
24
|
+
return { root, g };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const run = (root, planArgs = []) => spawnSync(process.execPath, [CLI, ...planArgs, '--root', root], { cwd: root, encoding: 'utf8' });
|
|
28
|
+
|
|
29
|
+
// S1 越界检出(工作区默认口径,含未跟踪)
|
|
30
|
+
{
|
|
31
|
+
const { root } = mkRepo();
|
|
32
|
+
fs.mkdirSync(path.join(root, 'workflow', 'plans'), { recursive: true });
|
|
33
|
+
fs.writeFileSync(path.join(root, 'workflow', 'plans', 'p1.md'), '## 改动面\n- src-a.md:改\n- src-new.md:新建\n');
|
|
34
|
+
fs.writeFileSync(path.join(root, 'src-b.md'), '# B 越界\n'); // 未跟踪新文件,未声明
|
|
35
|
+
const r = run(root, ['workflow/plans/p1.md']);
|
|
36
|
+
check('S1 越界件检出(未跟踪 src-b.md)', r.status === 0 && /越界件/.test(r.stdout) && /src-b\.md/.test(r.stdout), JSON.stringify({ status: r.status, out: r.stdout.slice(0, 300) }));
|
|
37
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// S2 未落检出(声明未变更)
|
|
41
|
+
{
|
|
42
|
+
const { root } = mkRepo();
|
|
43
|
+
fs.mkdirSync(path.join(root, 'workflow', 'plans'), { recursive: true });
|
|
44
|
+
fs.writeFileSync(path.join(root, 'workflow', 'plans', 'p2.md'), '## 改动面\n- src-ghost.md:计划改但没改\n');
|
|
45
|
+
const r = run(root, ['workflow/plans/p2.md']);
|
|
46
|
+
check('S2 未落件检出(src-ghost.md)', r.status === 0 && /未落件/.test(r.stdout) && /src-ghost\.md/.test(r.stdout), JSON.stringify({ status: r.status, out: r.stdout.slice(0, 300) }));
|
|
47
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// S3 白名单豁免(workflow 台账随行件不算越界)
|
|
51
|
+
{
|
|
52
|
+
const { root } = mkRepo();
|
|
53
|
+
fs.mkdirSync(path.join(root, 'workflow', 'intents'), { recursive: true });
|
|
54
|
+
fs.mkdirSync(path.join(root, 'workflow', 'plans'), { recursive: true });
|
|
55
|
+
fs.writeFileSync(path.join(root, 'workflow', 'plans', 'p3.md'), '## 改动面\n- src-a.md:改\n');
|
|
56
|
+
fs.writeFileSync(path.join(root, 'src-a.md'), '# A2\n');
|
|
57
|
+
fs.writeFileSync(path.join(root, 'workflow', 'intents', '2026-10-09-x.md'), '---\n状态: draft\n---\n');
|
|
58
|
+
const r = run(root, ['workflow/plans/p3.md']);
|
|
59
|
+
check('S3 台账随行件豁免(workflow/intents 不算越界)', r.status === 0 && !/workflow[\\/]intents/.test(r.stdout.split('未落件')[0]), JSON.stringify({ status: r.status, out: r.stdout.slice(0, 300) }));
|
|
60
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// S4 --strict 越界 exit 1;无越界 exit 0
|
|
64
|
+
{
|
|
65
|
+
const { root } = mkRepo();
|
|
66
|
+
fs.mkdirSync(path.join(root, 'workflow', 'plans'), { recursive: true });
|
|
67
|
+
fs.writeFileSync(path.join(root, 'workflow', 'plans', 'p4.md'), '## 改动面\n- src-a.md:改\n');
|
|
68
|
+
fs.writeFileSync(path.join(root, 'src-a.md'), '# A2\n');
|
|
69
|
+
check('S4a 无越界 --strict exit 0', run(root, ['workflow/plans/p4.md', '--strict']).status === 0);
|
|
70
|
+
fs.writeFileSync(path.join(root, 'src-rogue.md'), '# rogue\n');
|
|
71
|
+
check('S4b 越界 --strict exit 1', run(root, ['workflow/plans/p4.md', '--strict']).status === 1);
|
|
72
|
+
check('S4c 越界无 --strict exit 0(advisory)', run(root, ['workflow/plans/p4.md']).status === 0);
|
|
73
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// S5 无 plan exit 2(fail-loud)
|
|
77
|
+
{
|
|
78
|
+
const { root } = mkRepo();
|
|
79
|
+
const r = run(root, ['workflow/plans/nope.md']);
|
|
80
|
+
check('S5 plan 不存在 exit 2', r.status === 2, JSON.stringify({ status: r.status }));
|
|
81
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// S6 版本号伪 token 过滤(v1.2.2 不入声明清单)
|
|
85
|
+
{
|
|
86
|
+
const { root } = mkRepo();
|
|
87
|
+
fs.mkdirSync(path.join(root, 'workflow', 'plans'), { recursive: true });
|
|
88
|
+
fs.writeFileSync(path.join(root, 'workflow', 'plans', 'p6.md'), '## 改动面\n- src-a.md:升级到 v1.2.2\n');
|
|
89
|
+
fs.writeFileSync(path.join(root, 'src-a.md'), '# A2\n');
|
|
90
|
+
const r = run(root, ['workflow/plans/p6.md']);
|
|
91
|
+
check('S6 版本号 token 不产生未落噪声', r.status === 0 && !/1\.2\.2/.test(r.stdout), JSON.stringify({ out: r.stdout.slice(0, 300) }));
|
|
92
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
console.log(`\n合计: PASS ${pass} / FAIL ${fail}`);
|
|
96
|
+
process.exit(fail ? 1 : 0);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// check-scripts-syntax.mjs — 引擎脚本语法自检(node --check 全量扫描)
|
|
3
|
+
// 用途:守护者受守护——对 .agents/scripts 与 .agents/hooks 下全部 .mjs/.cjs 逐个 node --check,
|
|
4
|
+
// 任何文件不可解析即判红(先例教训:verify.mjs:31 SyntaxError 带病进 CI,全链门静默失效)。
|
|
5
|
+
// 语义与红线:只读裁决,不改任何状态;检查目录缺失或可检文件数为 0 一律判红(fail-closed,
|
|
6
|
+
// 防「改名/删除脚本即去门」);不 import 任何引擎件,零依赖可独立调度。
|
|
7
|
+
// 测试:node .agents/scripts/check-scripts-syntax.test.mjs
|
|
8
|
+
// 用法:node .agents/scripts/check-scripts-syntax.mjs [目录...](相对仓库根;缺省 .agents/scripts .agents/hooks;
|
|
9
|
+
// CHECK_LOOP_ROOT 可覆写仓库根,与 check-loop/verify 同口径)
|
|
10
|
+
// 沿革:2026-10-09 guardian-selfcheck(审计 P0-1 立项,verify.mjs GATES 首门消费)。
|
|
11
|
+
|
|
12
|
+
import fs from 'node:fs';
|
|
13
|
+
import { spawnSync } from 'node:child_process';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
|
|
17
|
+
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const ROOT = process.env.CHECK_LOOP_ROOT || path.resolve(SCRIPT_DIR, '..', '..');
|
|
19
|
+
const DEFAULT_DIRS = ['.agents/scripts', '.agents/hooks'];
|
|
20
|
+
|
|
21
|
+
const dirs = process.argv.slice(2);
|
|
22
|
+
const targets = dirs.length ? dirs : DEFAULT_DIRS;
|
|
23
|
+
|
|
24
|
+
let total = 0;
|
|
25
|
+
const reds = [];
|
|
26
|
+
const missing = [];
|
|
27
|
+
for (const d of targets) {
|
|
28
|
+
const abs = path.resolve(ROOT, d);
|
|
29
|
+
let names;
|
|
30
|
+
try {
|
|
31
|
+
if (!fs.statSync(abs).isDirectory()) { missing.push(d); continue; }
|
|
32
|
+
names = fs.readdirSync(abs);
|
|
33
|
+
} catch {
|
|
34
|
+
missing.push(d);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
for (const f of names.filter((n) => /\.(mjs|cjs)$/.test(n)).sort()) {
|
|
38
|
+
total++;
|
|
39
|
+
const r = spawnSync(process.execPath, ['--check', path.join(abs, f)], { encoding: 'utf8' });
|
|
40
|
+
if (r.status !== 0) {
|
|
41
|
+
const first = (r.stderr || '').split(/\r?\n/).find((l) => l.trim()) || `exit ${r.status}`;
|
|
42
|
+
reds.push(`[syntax] ❌ ${d}/${f}: ${first}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (missing.length) {
|
|
48
|
+
console.error(`[syntax] ❌ 检查目标目录缺失:${missing.join('、')}(fail-closed——脚本被改名/删除即去门)`);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
if (total === 0) {
|
|
52
|
+
console.error('[syntax] ❌ 可检文件数为 0(目录为空或参数指错,fail-closed)');
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
if (reds.length) {
|
|
56
|
+
for (const line of reds) console.error(line);
|
|
57
|
+
console.error(`[syntax] 共 ${total} 个引擎脚本,${reds.length} 个不可解析`);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
console.log(`[syntax] ✅ ${total} 个引擎脚本全部可解析(node --check)`);
|
|
61
|
+
process.exit(0);
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// check-scripts-syntax.mjs 的夹具测试(CHECK_LOOP_ROOT 注入临时根 + 显式目录参数,不触真实 .agents)
|
|
3
|
+
// 断言:①全绿目录 exit 0 且报数;②混入语法坏件 exit 1 且点名坏文件;③目录缺失 exit 1(fail-closed);
|
|
4
|
+
// ④空目录(可检数 0)exit 1(防「清空目录即去门」);⑤非 .mjs/.cjs 扩展名不入扫描。
|
|
5
|
+
// 用法:node .agents/scripts/check-scripts-syntax.test.mjs
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import os from 'node:os';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { spawnSync } from 'node:child_process';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
|
|
12
|
+
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const CHECKER = path.join(SCRIPT_DIR, 'check-scripts-syntax.mjs');
|
|
14
|
+
|
|
15
|
+
let pass = 0;
|
|
16
|
+
let fail = 0;
|
|
17
|
+
const check = (desc, cond, detail = '') => {
|
|
18
|
+
if (cond) { pass++; console.log(`PASS ${desc}`); }
|
|
19
|
+
else { fail++; console.log(`FAIL ${desc}${detail ? `\n${detail}` : ''}`); }
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const mkroot = () => fs.mkdtempSync(path.join(os.tmpdir(), 'syn-fix-'));
|
|
23
|
+
const run = (root, argv) => spawnSync(process.execPath, [CHECKER, ...argv], {
|
|
24
|
+
encoding: 'utf8',
|
|
25
|
+
env: { ...process.env, CHECK_LOOP_ROOT: root },
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// ---- 场景 1:全绿目录 → exit 0 且报数 2(txt 不入扫描) ----
|
|
29
|
+
{
|
|
30
|
+
const root = mkroot();
|
|
31
|
+
fs.mkdirSync(path.join(root, 'sd'), { recursive: true });
|
|
32
|
+
fs.writeFileSync(path.join(root, 'sd', 'good.mjs'), 'const a = 1;\n', 'utf8');
|
|
33
|
+
fs.writeFileSync(path.join(root, 'sd', 'good.cjs'), 'module.exports = 1;\n', 'utf8');
|
|
34
|
+
fs.writeFileSync(path.join(root, 'sd', 'note.txt'), 'not scanned\n', 'utf8');
|
|
35
|
+
const r = run(root, ['sd']);
|
|
36
|
+
check('场景 1:全绿 → exit 0', r.status === 0, `exit=${r.status}\n${r.stdout}\n${r.stderr}`);
|
|
37
|
+
check('场景 1:报数 2 且 txt 被排除', r.stdout.includes('✅ 2 个引擎脚本'), r.stdout);
|
|
38
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ---- 场景 2:语法坏件 → exit 1 且点名 ----
|
|
42
|
+
{
|
|
43
|
+
const root = mkroot();
|
|
44
|
+
fs.mkdirSync(path.join(root, 'sd'), { recursive: true });
|
|
45
|
+
fs.writeFileSync(path.join(root, 'sd', 'good.mjs'), 'const a = 1;\n', 'utf8');
|
|
46
|
+
fs.writeFileSync(path.join(root, 'sd', 'broken.mjs'), 'if (true) entry.x process.env.Y;\n', 'utf8');
|
|
47
|
+
const r = run(root, ['sd']);
|
|
48
|
+
check('场景 2:混入坏件 → exit 1', r.status === 1, `exit=${r.status}`);
|
|
49
|
+
check('场景 2:stderr 点名坏文件', r.stderr.includes('sd/broken.mjs') && r.stderr.includes('1 个不可解析'), r.stderr);
|
|
50
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ---- 场景 3:目录缺失 → exit 1(fail-closed) ----
|
|
54
|
+
{
|
|
55
|
+
const root = mkroot();
|
|
56
|
+
const r = run(root, ['no-such-dir']);
|
|
57
|
+
check('场景 3:目录缺失 → exit 1 且提示 fail-closed', r.status === 1 && r.stderr.includes('检查目标目录缺失'), `exit=${r.status}\n${r.stderr}`);
|
|
58
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ---- 场景 4:空目录 → exit 1(可检数 0 拒放行) ----
|
|
62
|
+
{
|
|
63
|
+
const root = mkroot();
|
|
64
|
+
fs.mkdirSync(path.join(root, 'sd'), { recursive: true });
|
|
65
|
+
const r = run(root, ['sd']);
|
|
66
|
+
check('场景 4:空目录 → exit 1 且提示数为 0', r.status === 1 && r.stderr.includes('可检文件数为 0'), `exit=${r.status}\n${r.stderr}`);
|
|
67
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
console.log(`\n合计: PASS ${pass} / FAIL ${fail}`);
|
|
71
|
+
if (fail) process.exit(1);
|
|
72
|
+
process.exit(0);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// check-spec-verif.mjs — 检查 21:spec 验证落地核查 [warning](2026-10-13 B6b-2 自 Shipyard.Material
|
|
2
|
+
// check-loop-sections/s21-spec-verif.mjs 回灌;段体判据逐字对齐,出处沿革留痕在包源仓 git 历史与
|
|
3
|
+
// CHANGELOG——装户径不引实例路径)
|
|
4
|
+
// review P1「spec 承诺落地:spec §测试用例须真落到测试」的机器面:L2/L3 done spec 且建档日 ≥ 锚
|
|
5
|
+
// (policy v9 specVerifSince;v1–v8 缺键 → 本段整体跳过),其「测试用例 / 验证计划 / 验证方式」节
|
|
6
|
+
// 无测试文件或验证命令引用 → WARN(advisory——spec 可能以 UI 实测等人工可复跑方式验证,不做 hard)。
|
|
7
|
+
// 缺键(v1–v8)→ 整体跳过;存量(< 锚)豁免。
|
|
8
|
+
// 模块形态(kit check-hygiene / check-stage-index 先例的 run(ctx)):判定语义单源在本文件,ctx 只传
|
|
9
|
+
// check-loop.mjs 既有共享面(helper 单源在那边,复刻即造口径分叉);warn 直接进 ctx.warnings,
|
|
10
|
+
// 输出序 = check-loop 调用序(稳定输出契约)。
|
|
11
|
+
// kit 适配(源仓→kit 逐条):证据正则剔除源仓实例路径面(backend/…Tests.cs、frontend/__tests__、
|
|
12
|
+
// verify-ui)——kit 口径保留通用形态(.test.mjs / 回归清单 / 测试与门命令),见 VERIF_EVIDENCE_RE 注。
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
|
|
15
|
+
export function run(ctx) {
|
|
16
|
+
const { docFiles, fmGet, linesOf, addedDateOf, kitPolicy, warnings } = ctx;
|
|
17
|
+
// --- 21. spec 验证落地核查 [warning](锚 specVerifSince)---
|
|
18
|
+
{
|
|
19
|
+
const anchor = kitPolicy.specVerifSince;
|
|
20
|
+
if (anchor) {
|
|
21
|
+
const VERIF_SEC_RE = /^##\s+.*(测试用例|验证计划|验证方式|测试与验证)/;
|
|
22
|
+
// 证据引用形态(宽匹配——门的是「验证节里有可复跑资产的身影」,不猜具体测试名):
|
|
23
|
+
// 引擎 .test.mjs / 回归清单(kit 模板 workflow/regression-checklist.md)/ 冒烟与测试命令
|
|
24
|
+
// (npm test、dotnet test、run-tests、check-scope、verify.mjs)。源仓实例路径面
|
|
25
|
+
// (backend/…、\w+Tests.cs、frontend/…、verify-ui)按 instance-ref 门修法剔除,判据方向不变。
|
|
26
|
+
const VERIF_EVIDENCE_RE = /\.test\.mjs|workflow\/regression-checklist\.md|regression-smoke|npm\s+test|dotnet\s+test|run-tests|check-scope|verify\.mjs/;
|
|
27
|
+
for (const spec of docFiles('specs')) {
|
|
28
|
+
const st = fmGet(spec, '状态');
|
|
29
|
+
const lvl = fmGet(spec, '级别');
|
|
30
|
+
if (st !== 'done' || !['L2', 'L3'].includes(lvl)) continue;
|
|
31
|
+
const add = addedDateOf(spec);
|
|
32
|
+
if (!add || add < anchor) continue;
|
|
33
|
+
const lines = linesOf(spec) || [];
|
|
34
|
+
const secLines = [];
|
|
35
|
+
let inSec = false;
|
|
36
|
+
for (const l of lines) {
|
|
37
|
+
if (VERIF_SEC_RE.test(l)) { inSec = true; continue; }
|
|
38
|
+
if (inSec && /^##\s+/.test(l)) { inSec = false; continue; }
|
|
39
|
+
if (inSec) secLines.push(l);
|
|
40
|
+
}
|
|
41
|
+
if (secLines.length && !secLines.some((l) => VERIF_EVIDENCE_RE.test(l))) {
|
|
42
|
+
warnings.push(`- [WARN spec 验证落地] ${path.basename(spec)} 验证节无测试文件/验证命令引用——review P1「spec 承诺落地」要求落到可复跑验证资产`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// compact-ledgers.mjs — 台账分片归并器(2026-10-12 ledger-shard-write
|
|
2
|
+
// compact-ledgers.mjs — 台账分片归并器(2026-10-12 ledger-shard-write 配套;2026-10-13 ledger-shard-default 起为默认分片的收敛动作)
|
|
3
3
|
// 用途:里程碑节点或发布前,把 `.agents/confirmations.d/*.jsonl` 的分片确认行并回主台账
|
|
4
|
-
// `.agents/confirmations.jsonl`
|
|
5
|
-
//
|
|
4
|
+
// `.agents/confirmations.jsonl` 并清理分片。分片已是**默认**写入口径(confirm-doc 不带 --no-shard 即写分片),
|
|
5
|
+
// 长期不归并会让台账面无限碎片化——本脚本就是可选的收敛动作(跨分支合并另由 union driver 承担,归并非必需)。
|
|
6
6
|
//
|
|
7
7
|
// 保持 append-only 前缀不变量:主台账既有行**严格保序不动**,分片新行只追加到末尾——这正是
|
|
8
8
|
// check-ledger-invariant 的「旧 blob 是新 blob 的逐字节前缀」所要求的形态。绝不做重排/去重改写。
|