agentic-flow-kit 1.1.0 → 1.1.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-flow-kit",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "AI-Native 闭环工作流 + wiki 知识层脚手架——flow-kit init 一条命令为新项目装好 .agents 工作流引擎(阶段命令/角色契约/门禁钩子/kb 检索/看板/规则面预算)+ workflow 文档闭环 + wiki 知识库,装完自包含、零运行时依赖",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -117,7 +117,8 @@ import { laneOfEntry } from './stage-gates.mjs';
117
117
  import { MARK_RE, approvedTraceHit } from './stage-gates.mjs';
118
118
 
119
119
  const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
120
- const GIT = process.platform === 'win32' ? 'git.exe' : 'git';
120
+ // CHECK_LOOP_GIT:测试注入钩子(指向不存在/不可执行路径可稳定触发 spawn 异常);未设置时与原行为逐字节一致(2026-10-05-gitout-fail-open)
121
+ const GIT = process.env.CHECK_LOOP_GIT || (process.platform === 'win32' ? 'git.exe' : 'git');
121
122
 
122
123
  function takeOpts() {
123
124
  const args = process.argv.slice(2);
@@ -210,19 +211,52 @@ const inSet = (v, arr) => arr.includes(v);
210
211
  const stOkDoc = (st) => inSet(st, ENUMS['doc.status.confirmed']);
211
212
 
212
213
  // git 调用(参数数组形式不走 shell——Windows cmd 不认单引号;git.exe 显式解析沿 doctor 先例)
214
+ // 基础设施异常与业务失败分流(2026-10-05-gitout-fail-open):spawn 异常响亮出账(进程级去重恰一条,
215
+ // 沿检查 16 装户「载入失败响亮出账」先例)+ 瞬时类错误码单次重试;「非 git 仓」(status≠0)仍是
216
+ // 合法装户语义、静默走降级。intentional-simple: 重试后仍异常继续走原降级/拦截语义(连续瞬时异常
217
+ // 残余面小且有账可查);升级路径 = 探测门(:720/:796)改三态拆分
218
+ let gitInfraWarned = false;
219
+ function warnGitInfra(args, code) {
220
+ if (gitInfraWarned) return;
221
+ gitInfraWarned = true;
222
+ console.error(`check-loop: git 探测异常 ${args[0]}(${code})——按非 git 仓降级,检查面可能收窄`);
223
+ }
224
+ const GIT_TRANSIENT = new Set(['EAGAIN', 'EPERM', 'EMFILE', 'EINTR']);
225
+ function spawnGit(args) {
226
+ const opts = { cwd: ROOT, encoding: 'utf8', maxBuffer: 32 * 1024 * 1024 };
227
+ let r = spawnSync(GIT, args, opts);
228
+ if (r.error) {
229
+ // 出账在重试终败后(与 linesOf 对称——瞬时重试恢复成功不打扰);非瞬时类直接出账
230
+ if (GIT_TRANSIENT.has(r.error.code)) r = spawnSync(GIT, args, opts);
231
+ if (r.error) warnGitInfra(args, r.error.code || r.error.message);
232
+ }
233
+ return r;
234
+ }
213
235
  function gitOut(args, { ok = () => true } = {}) {
214
- const r = spawnSync(GIT, args,
215
- { cwd: ROOT, encoding: 'utf8', maxBuffer: 32 * 1024 * 1024 });
236
+ const r = spawnGit(args);
216
237
  if (r.error || r.status !== 0 || !ok(r)) return null;
217
238
  return r.stdout || '';
218
239
  }
219
240
 
220
241
  // ---- frontmatter 受限子集读取(fm_get 语义:首行 --- 进入、下一 --- 闭合、键在行首、首个命中、值两侧去空白)----
242
+ // fs 读取同走 fail-loud(2026-10-05-gitout-fail-open 实现期诊断追加,与 spawnGit 同一故障类):
243
+ // readFileSync 瞬时异常(宿主资源压力 EMFILE/共享冲突类)重试一次,仍失败响亮出账(进程级去重)
244
+ // 后按 null 降级——调用方按空文档跳过该件,但异常本身不再静默
245
+ let fsReadWarned = new Set();
221
246
  const fileLinesCache = new Map();
222
247
  function linesOf(file) {
223
248
  if (!fileLinesCache.has(file)) {
224
- try { fileLinesCache.set(file, fs.readFileSync(file, 'utf8').split(/\r?\n/)); }
225
- catch { fileLinesCache.set(file, null); }
249
+ let lines = null;
250
+ try {
251
+ lines = fs.readFileSync(file, 'utf8').split(/\r?\n/);
252
+ } catch (e) {
253
+ try { lines = fs.readFileSync(file, 'utf8').split(/\r?\n/); } catch { lines = null; }
254
+ if (!lines && !fsReadWarned.has(file)) {
255
+ fsReadWarned.add(file);
256
+ console.error(`check-loop: 文档读取异常 ${path.basename(file)}(${e.code || e.message})——按空文档降级,检查面可能收窄`);
257
+ }
258
+ }
259
+ fileLinesCache.set(file, lines);
226
260
  }
227
261
  return fileLinesCache.get(file);
228
262
  }
@@ -571,28 +605,54 @@ const addedDateOf = (absDoc) => {
571
605
  return iso ? iso.slice(0, 10) : '';
572
606
  };
573
607
 
608
+ // 豁免/放行分支出账(fail-loud 口径——「裁决消失」不可归因的教训:豁免路径零痕迹;
609
+ // 2026-10-05-check8-digit-sha-misfire):按 type 进程级去重,每类至多一条;stderr 不改 stdout 协议
610
+ const evidenceExemptWarned = new Set();
611
+ function warnEvidenceExempt(type, base) {
612
+ if (evidenceExemptWarned.has(type)) return;
613
+ evidenceExemptWarned.add(type);
614
+ console.error(`check-loop: 证据豁免 ${type} ${base}——该文档的验收证据未做真相核验`);
615
+ }
616
+
574
617
  // 证据真相校验:检查证据字符串是否包含合法的 commit SHA,且该 commit 触及了 plan 声明的文件
575
618
  function verifyEvidenceTruth(evidenceStr, planBase, root) {
576
619
  const shaMatch = evidenceStr.match(/\b([a-f0-9]{7,40})\b/i);
577
- // 排除纯数字串(时间戳/ID),必须是包含字母的 hex 串才疑似 SHA
578
- if (!shaMatch || /^\d+$/.test(shaMatch[1])) return { ok: true, type: 'text' };
620
+ if (!shaMatch) {
621
+ warnEvidenceExempt('text', planBase);
622
+ return { ok: true, type: 'text' };
623
+ }
579
624
 
580
625
  const sha = shaMatch[1];
581
- const revParse = spawnSync(GIT, ['rev-parse', sha], { cwd: ROOT, encoding: 'utf8' });
626
+ // 直连走 spawnGit(与 gitOut 同一 loud+retry 通道,注释互引):spawn 异常不再被静默算作业务失败;
627
+ // 重试后仍异常按下方 status!==0 判定(fail-closed 保持——本地核验不了就按伪造拦)
628
+ const revParse = spawnGit(['rev-parse', sha]);
582
629
  if (revParse.status !== 0) {
630
+ // 纯数字串且解析失败 → 时间戳/ID 维持文本豁免(2026-10-05-check8-digit-sha-misfire:形态守卫
631
+ // 曾把全数字短 SHA≈4.4%/夹具 误分类为文本致核验静默跳过——改由 rev-parse 实证分流)。
632
+ // 纪律:文本启发式须可被实证兜底,禁以字符形态抢先分类。
633
+ if (/^\d+$/.test(sha)) {
634
+ warnEvidenceExempt('text', planBase);
635
+ return { ok: true, type: 'text' };
636
+ }
583
637
  // 区分本地伪造与外部仓库引用:
584
638
  // 剔除 SHA 与泛指词后仍有其他内容(如项目名、路径描述)→ 视为外部仓库引用,不硬拦(本地无法核验他仓哈希);
585
639
  // 剔除后为空(如纯「commit a1b2c3d」「提交 a1b2c3d」)→ 视为本地引用伪造,硬拦。
586
640
  const residue = evidenceStr.replace(sha, '')
587
641
  .replace(/证据[::]|commit|提交|[()()。—-]/gi, '')
588
642
  .trim();
589
- if (residue.length > 0) return { ok: true, type: 'external' };
643
+ if (residue.length > 0) {
644
+ warnEvidenceExempt('external', planBase);
645
+ return { ok: true, type: 'external' };
646
+ }
590
647
  return { ok: false, type: 'forged', msg: `提交 ${sha} 不存在` };
591
648
  }
592
649
 
593
- // 语义核验:该 commit 必须触及 plan 中声明的任何一个文件
650
+ // 解析成功(含纯数字短 SHA 命中提交/ref 的情形)→ 真相核验
594
651
  const planPath = path.join(ROOT, WF, 'plans', planBase + '.md');
595
- if (!fs.existsSync(planPath)) return { ok: true, type: 'no-plan' }; // 缺 plan 走 Check 1 拦截
652
+ if (!fs.existsSync(planPath)) {
653
+ warnEvidenceExempt('no-plan', planBase);
654
+ return { ok: true, type: 'no-plan' }; // 缺 plan 走 Check 1 拦截
655
+ }
596
656
 
597
657
  const planLines = linesOf(planPath) || [];
598
658
  const declaredFiles = [];
@@ -607,7 +667,7 @@ function verifyEvidenceTruth(evidenceStr, planBase, root) {
607
667
  }
608
668
  }
609
669
 
610
- const show = spawnSync(GIT, ['show', '--name-only', sha], { cwd: ROOT, encoding: 'utf8' });
670
+ const show = spawnGit(['show', '--name-only', sha]);
611
671
  if (show.status !== 0) return { ok: false, type: 'error', msg: `无法读取提交 ${sha} 的文件列表` };
612
672
 
613
673
  const changedFiles = show.stdout.split(/\r?\n/).filter(Boolean);
@@ -617,8 +677,9 @@ function verifyEvidenceTruth(evidenceStr, planBase, root) {
617
677
  // 过程证据(2026-10-03 check-evidence-process):执行器驱动的提交可作验收证据——提交信息带
618
678
  // 「pipeline-run <runId>」标记(docsCommit 四种格式共有段,runId 严格形态 日期8-时间4-主题-尾;
619
679
  // 与 run 事件流同源,git 历史留痕可对质)。只救 irrelevant:forged(SHA 不存在)在前已判,其余判据不动。
620
- const subj = spawnSync(GIT, ['log', '-1', '--format=%s', sha], { cwd: ROOT, encoding: 'utf8' });
680
+ const subj = spawnGit(['log', '-1', '--format=%s', sha]);
621
681
  if (subj.status === 0 && /pipeline-run [0-9]{8}-[0-9]{4}-[A-Za-z0-9-]+/.test(subj.stdout)) {
682
+ warnEvidenceExempt('process', planBase);
622
683
  return { ok: true, type: 'process' };
623
684
  }
624
685
  return { ok: false, type: 'irrelevant', msg: `提交 ${sha} 未触及 plan 声明的任何文件` };
@@ -68,9 +68,12 @@ ${refs ? ' - 是 → ../intents/2026-09-12-ghost.md\n' : ' - 否 → 理由:
68
68
  `;
69
69
 
70
70
  // run:默认 CHECK_LOOP_ROOT 注入(fixture 模式);{git:true} 时 cwd 注入(真 git 模式,测 tracked 过滤);
71
- // opts.args 追加 CLI 参数(--hardening 等加固门场景用,2026-09-30 hybrid-governance-explore-hardening)
71
+ // opts.args 追加 CLI 参数(--hardening 等加固门场景用,2026-09-30 hybrid-governance-explore-hardening);
72
+ // opts.env 增量覆盖子进程 env(CHECK_LOOP_GIT 替身注入用,2026-10-05-gitout-fail-open)
72
73
  const run = (root, opts = {}) => spawnSync(process.execPath, [CHECK_LOOP, ...(opts.args || [])], {
73
- ...(opts.git ? { cwd: root } : { cwd: ROOT_CWD, env: { ...process.env, CHECK_LOOP_ROOT: root } }),
74
+ ...(opts.git
75
+ ? { cwd: root, ...(opts.env ? { env: { ...process.env, ...opts.env } } : {}) }
76
+ : { cwd: ROOT_CWD, env: { ...process.env, ...(opts.env || {}), CHECK_LOOP_ROOT: root } }),
74
77
  encoding: 'utf8',
75
78
  });
76
79
  const ROOT_CWD = process.cwd();
@@ -83,13 +86,32 @@ const expectOk = (desc, root) => {
83
86
  const r = run(root);
84
87
  check(desc, r.status === 0, `exit=${r.status}\n${outOf(r)}`);
85
88
  };
89
+ // 夹具 git 调用防瞬时失败(2026-10-05-gitout-fail-open 夹具侧加固):宿主/CI 资源压力下 spawnSync 可能
90
+ // 瞬时 `r.error`,git 侧也可能瞬时 status≠0(如 index.lock 争用)——对夹具 setup 调用(init/add/commit/
91
+ // rev-parse)两者都永不合法:重试一次,持续失败抛错响亮失败。绝不静默产出空 sha / 缺提交的假夹具
92
+ // (那会让证据场景拿到「无裁决」的错误结果——CI run 37252874044 flake 的实际根因侧,诊断详见同名 incident)
93
+ function gitRetry(args, root) {
94
+ const opts = { cwd: root, encoding: 'utf8' };
95
+ let r = spawnSync(GIT, args, opts);
96
+ if (r.error || r.status !== 0) {
97
+ r = spawnSync(GIT, args, opts);
98
+ if (r.error || r.status !== 0) {
99
+ throw new Error(`fixture git 瞬时失败(重试后仍 ${r.error ? (r.error.code || r.error.message) : `exit ${r.status}`}): git ${args.join(' ')}\n${(r.stderr || '').slice(0, 300)}`);
100
+ }
101
+ }
102
+ return r;
103
+ }
104
+ const shortSha = (root, args = ['rev-parse', '--short', 'HEAD']) => {
105
+ const out = gitRetry(args, root).stdout.trim();
106
+ if (!out) throw new Error(`fixture git 空输出(假夹具防线): git ${args.join(' ')}`);
107
+ return out;
108
+ };
86
109
  const gitInit = (root) => {
87
- spawnSync(process.platform === 'win32' ? 'git.exe' : 'git', ['init', '-q'], { cwd: root });
110
+ gitRetry(['init', '-q'], root);
88
111
  };
89
112
  const gitCommitAll = (root, msg) => {
90
- const G = process.platform === 'win32' ? 'git.exe' : 'git';
91
- spawnSync(G, ['add', '-A'], { cwd: root });
92
- spawnSync(G, ['-c', 'user.email=t@t', '-c', 'user.name=t', 'commit', '-qm', msg], { cwd: root });
113
+ gitRetry(['add', '-A'], root);
114
+ gitRetry(['-c', 'user.email=t@t', '-c', 'user.name=t', 'commit', '-qm', msg], root);
93
115
  };
94
116
  const rmfix = (root) => fs.rmSync(root, { recursive: true, force: true });
95
117
  // writeLedger:fixture 台账辅助(检查 15 配对用;行 schema 同 confirm-doc.mjs appendLedger)
@@ -435,12 +457,12 @@ const mkConfirmedDoc = (root, rel, fmBody) => {
435
457
  fs.copyFileSync(path.join(SCRIPT_DIR, '..', 'rule-budgets.txt'), path.join(T, '.agents', 'rule-budgets.txt'));
436
458
  gitInit(T);
437
459
  w(T, 'AGENTS.md', 'x'.repeat(8000));
438
- spawnSync(process.platform === 'win32' ? 'git.exe' : 'git', ['add', 'AGENTS.md'], { cwd: T });
460
+ gitRetry(['add', 'AGENTS.md'], T);
439
461
  let r = spawnSync('sh', ['.agents/scripts/rule-budget.sh', '--staged'], { cwd: T, encoding: 'utf8' });
440
462
  check('rule-budget --staged:暂存超限 → exit 1(硬拦路径)',
441
463
  r.status === 1 && `${r.stdout}${r.stderr}`.includes('常驻面超限:AGENTS.md'), `${r.stdout}${r.stderr}`);
442
464
  w(T, 'AGENTS.md', '# 小体积\n');
443
- spawnSync(process.platform === 'win32' ? 'git.exe' : 'git', ['add', 'AGENTS.md'], { cwd: T });
465
+ gitRetry(['add', 'AGENTS.md'], T);
444
466
  r = spawnSync('sh', ['.agents/scripts/rule-budget.sh', '--staged'], { cwd: T, encoding: 'utf8' });
445
467
  check('rule-budget --staged:合规 → exit 0(不误拦)', r.status === 0, `${r.stdout}${r.stderr}`);
446
468
  rmfix(T);
@@ -582,7 +604,7 @@ const evidenceFixture = (slug, declared, createDeclared) => {
582
604
  w(T, `workflow/intents/2026-10-04-${slug}.md`, INTENT(slug, '状态: done\n级别: L1\n日期: 2026-10-04',
583
605
  '\n## 验收标准(可测试)\n- [x] 用例通过(证据:commit PLACEHOLDER)\n'));
584
606
  gitCommitAll(T, `feat: ${slug} fixture`);
585
- const sha = spawnSync(GIT, ['rev-parse', 'HEAD'], { cwd: T, encoding: 'utf8' }).stdout.trim().slice(0, 7);
607
+ const sha = shortSha(T, ['rev-parse', 'HEAD']).slice(0, 7);
586
608
  w(T, `workflow/intents/2026-10-04-${slug}.md`, INTENT(slug, '状态: done\n级别: L1\n日期: 2026-10-04',
587
609
  `\n## 验收标准(可测试)\n- [x] 用例通过(证据:commit ${sha})\n`));
588
610
  gitCommitAll(T, `docs: ${slug} evidence`);
@@ -620,7 +642,7 @@ const evidenceFixture = (slug, declared, createDeclared) => {
620
642
  fs.mkdirSync(path.join(T, 'src'), { recursive: true });
621
643
  w(T, 'src/app.mjs', '// fixture\n');
622
644
  gitCommitAll(T, 'feat: sh1 实现(无执行器标记)');
623
- const sha = spawnSync(GIT, ['rev-parse', '--short', 'HEAD'], { cwd: T, encoding: 'utf8' }).stdout.trim();
645
+ const sha = shortSha(T);
624
646
  w(T, 'workflow/intents/2026-09-26-sh1.md', INTENT('sh1', '状态: done\n级别: L1\n日期: 2026-09-26', `\n## 验收标准(可测试)\n- [x] 用例通过(证据:${sha})\n`));
625
647
  expectOk('证据 SHA 触及声明文件(无执行器标记)→ type=sha 校验通过 exit 0', T);
626
648
  rmfix(T);
@@ -672,8 +694,7 @@ const evidenceFixture = (slug, declared, createDeclared) => {
672
694
  fs.mkdirSync(path.join(T, 'notes'), { recursive: true });
673
695
  w(T, 'notes/other.md', 'x\n');
674
696
  gitCommitAll(T, 'docs(x): 演练穿越(pipeline-run 20260926-0900-pe1-abc1)');
675
- const G = process.platform === 'win32' ? 'git.exe' : 'git';
676
- const sha = spawnSync(G, ['rev-parse', '--short', 'HEAD'], { cwd: T, encoding: 'utf8' }).stdout.trim();
697
+ const sha = shortSha(T);
677
698
  w(T, 'workflow/intents/2026-09-26-pe1.md', INTENT('pe1', '状态: done\n级别: L1\n日期: 2026-09-26', `\n## 验收标准(可测试)\n- [x] 演练穿越(证据:${sha})\n`));
678
699
  expectOk('过程证据:提交信息带执行器标记(改动面外)→ 放行 exit 0', T);
679
700
  rmfix(T);
@@ -685,8 +706,7 @@ const evidenceFixture = (slug, declared, createDeclared) => {
685
706
  fs.mkdirSync(path.join(T, 'notes'), { recursive: true });
686
707
  w(T, 'notes/other.md', 'x\n');
687
708
  gitCommitAll(T, 'docs(x): 演练穿越(L0,无标记提交)');
688
- const G = process.platform === 'win32' ? 'git.exe' : 'git';
689
- const sha = spawnSync(G, ['rev-parse', '--short', 'HEAD'], { cwd: T, encoding: 'utf8' }).stdout.trim();
709
+ const sha = shortSha(T);
690
710
  w(T, 'workflow/intents/2026-09-26-pe2.md', INTENT('pe2', '状态: done\n级别: L1\n日期: 2026-09-26', `\n## 验收标准(可测试)\n- [x] 演练穿越(证据:${sha})\n`));
691
711
  expectHard('过程证据:无标记无关提交 → 仍拦 证据无关', T, '证据无关');
692
712
  rmfix(T);
@@ -699,6 +719,68 @@ const evidenceFixture = (slug, declared, createDeclared) => {
699
719
  expectHard('过程证据:SHA 不存在 → forged 仍拦(防伪线不放松)', T, '证据伪造');
700
720
  rmfix(T);
701
721
  }
722
+ // ---- 场景 5v:git 基础设施异常 fail-loud(2026-10-05-gitout-fail-open)----
723
+ // CHECK_LOOP_GIT 指向不存在路径 → spawnSync 跨平台稳定 r.error(ENOENT)→ 响亮出账恰一条(进程去重)
724
+ // + 证据核验降级(exit 语义不变、无业务裁决误报);正常 git 零出账(防误报负例)
725
+ // 注(L2 复核 P2-2):出账断言不构成 :581 接入 spawnGit 的独立钉死——探测门与 :581 共用去重 flag,
726
+ // 变异(:581 改回裸 spawnSync)三断言仍绿;:581 接线由代码走读 + CI 覆盖,瞬时重试路径静态替身不可测
727
+ {
728
+ const T = mkfix();
729
+ gitInit(T);
730
+ w(T, 'workflow/plans/2026-09-26-pe4.md', PLAN('pe4', '状态: done\n级别: L1\n模块: material', '\n## 改动方案\n\n- src/app.mjs:实现功能\n'));
731
+ fs.mkdirSync(path.join(T, 'notes'), { recursive: true });
732
+ w(T, 'notes/other.md', 'x\n');
733
+ gitCommitAll(T, 'docs(x): 演练穿越(L0,无标记提交)');
734
+ const sha = shortSha(T);
735
+ w(T, 'workflow/intents/2026-09-26-pe4.md', INTENT('pe4', '状态: done\n级别: L1\n日期: 2026-09-26', `\n## 验收标准(可测试)\n- [x] 演练穿越(证据:${sha})\n`));
736
+ const r = run(T, { env: { CHECK_LOOP_GIT: 'definitely-not-git-xyz-2026' } });
737
+ const hits = outOf(r).split('git 探测异常').length - 1;
738
+ check('git 基础设施异常:响亮出账恰一条(进程去重)', hits === 1, `hits=${hits}\n${outOf(r)}`);
739
+ check('git 基础设施异常:持续异常仍 fail-closed(伪造拦不放松,防静默降级)', r.status === 1 && outOf(r).includes('证据伪造'), `exit=${r.status}\n${outOf(r)}`);
740
+ check('git 基础设施异常:基础设施异常不冒充业务裁决(无证据无关误报)', !outOf(r).includes('证据无关'), outOf(r));
741
+ rmfix(T);
742
+ }
743
+ {
744
+ const T = mkfix();
745
+ gitInit(T);
746
+ w(T, 'workflow/plans/2026-09-26-pe5.md', PLAN('pe5', '状态: done\n级别: L1\n模块: material', '\n## 改动方案\n\n- src/app.mjs:实现功能\n'));
747
+ fs.mkdirSync(path.join(T, 'notes'), { recursive: true });
748
+ w(T, 'notes/other.md', 'x\n');
749
+ gitCommitAll(T, 'docs(x): 演练穿越(L0,无标记提交)');
750
+ const sha = shortSha(T);
751
+ w(T, 'workflow/intents/2026-09-26-pe5.md', INTENT('pe5', '状态: done\n级别: L1\n日期: 2026-09-26', `\n## 验收标准(可测试)\n- [x] 演练穿越(证据:${sha})\n`));
752
+ const r = run(T);
753
+ check('git 基础设施异常:正常 git 零出账(防误报负例)', !outOf(r).includes('git 探测异常'), outOf(r));
754
+ check('git 基础设施异常:正常 git 行为不变(无标记无关提交仍拦证据无关)', r.status === 1 && outOf(r).includes('证据无关'), `exit=${r.status}\n${outOf(r)}`);
755
+ rmfix(T);
756
+ }
757
+ // ---- 场景 5u:检查 8 全数字证据串——rev-parse 实证分流(2026-10-05-check8-digit-sha-misfire)----
758
+ // 纯数字证据串曾由 /^\d+$/ 形态守卫抢先 text 豁免(全数字短 SHA≈4.4%/夹具 静默漏检);改为实证分流:
759
+ // 能解析成提交(含同名 ref/tag)→ 走真相核验;解析失败 → 维持 text 豁免。构造性钉子:git tag 数字名,不依赖哈希运气
760
+ {
761
+ const T = mkfix();
762
+ gitInit(T);
763
+ w(T, 'workflow/plans/2026-10-04-ev5.md', PLAN('ev5', '状态: done\n级别: L1', '\n## 改动方案\n- src/ghost.js:声明文件(实际不存在,证据必不触及)\n'));
764
+ w(T, 'workflow/intents/2026-10-04-ev5.md', INTENT('ev5', '状态: done\n级别: L1\n日期: 2026-10-04', '\n## 验收标准(可测试)\n- [x] 用例通过(证据:2620913)\n'));
765
+ gitCommitAll(T, 'feat: ev5 fixture');
766
+ gitRetry(['tag', '2620913', 'HEAD'], T);
767
+ const r = run(T);
768
+ check('全数字证据串可解析成提交(同名 ref)→ 按真相核验报证据无关(数字形态不豁免)',
769
+ outOf(r).includes('证据无关'), `exit=${r.status}\n${outOf(r)}`);
770
+ rmfix(T);
771
+ }
772
+ {
773
+ const T = mkfix();
774
+ gitInit(T);
775
+ w(T, 'workflow/plans/2026-10-04-ev6.md', PLAN('ev6', '状态: done\n级别: L1', '\n## 改动方案\n- src/ghost.js:声明文件(实际不存在,证据必不触及)\n'));
776
+ w(T, 'workflow/intents/2026-10-04-ev6.md', INTENT('ev6', '状态: done\n级别: L1\n日期: 2026-10-04', '\n## 验收标准(可测试)\n- [x] 用例通过(证据:20260926)\n'));
777
+ gitCommitAll(T, 'feat: ev6 fixture');
778
+ const r = run(T);
779
+ check('纯数字时间戳证据(无同名 ref)→ 维持文本豁免不误报',
780
+ !outOf(r).includes('证据无关') && !outOf(r).includes('证据伪造'), `exit=${r.status}\n${outOf(r)}`);
781
+ rmfix(T);
782
+ }
783
+
702
784
  // ---- 场景 45:检查 2——真未填占位(标题 <主题>,无 .md)→ 仍报模板未填 ----
703
785
  {
704
786
  const T = mkfix();