agentic-flow-kit 1.0.2 → 1.1.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 +1 -1
- package/package.json +1 -1
- package/src/add-host.mjs +3 -2
- package/src/cli.mjs +1 -1
- package/src/init.mjs +2 -1
- package/src/profiles.mjs +6 -2
- package/src/sync.test.mjs +3 -1
- package/templates/_agents/scripts/check-loop.mjs +45 -9
- package/templates/_agents/scripts/check-loop.test.mjs +70 -15
package/README.md
CHANGED
|
@@ -54,7 +54,7 @@ npx agentic-flow-kit add-gate dotnet-ca
|
|
|
54
54
|
| 选项 | 说明 |
|
|
55
55
|
|------|------|
|
|
56
56
|
| `--stack` | `dotnet` / `node` / `python` / `go` / `none`(默认 none)——**门禁配置三处**:commit-check 条件编译检查(暂存对应扩展名时 commit 前自动构建)+ **质量检测**(`checks`:lint / 类型检查 / vet 等秒级确定性检查,存在对应配置文件(tsconfig / eslint / ruff / go.mod 等)才启用,没有则自动跳过;测试不放提交门——关单在 test.md 阶段门)、settings.json 自检验命令权限、AGENTS.md「项目适配区」命令预填 |
|
|
57
|
-
| `--hosts` | `zcode` / `opencode` / `trae` / `omp` / `claude` / `cursor` / `codex` 逗号多选(默认 zcode
|
|
57
|
+
| `--hosts` | `zcode` / `opencode` / `trae` / `omp` / `claude` / `cursor` / `codex` 逗号多选(默认 zcode)。七个宿主一律入库(2026-10-05 起 zcode/omp 撤销 localOnly——换宿主不丢文件、CI 克隆面与本仓一致);zcode 的会话级液态草稿(`.zcode/drafts/`、`.zcode/plans/`)是本机态、不该入库——init 不再自动写这条规则,请自行加进 `.gitignore`(本仓实例见根 `.gitignore`)。Claude Code 加载 `.claude/commands` 与 `.claude/agents`;Cursor 加载 `.cursor/commands`;Codex 读仓库根 `AGENTS.md` 与 `.codex/skills/`,`.codex/commands` 是同形薄转发 |
|
|
58
58
|
| `--board-port` | workflow 看板端口(默认 8933) |
|
|
59
59
|
| `--dir` | 目标项目根(默认当前目录) |
|
|
60
60
|
| `--force` | 覆盖已存在的同名文件(默认保守跳过) |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentic-flow-kit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "AI-Native 闭环工作流 + wiki 知识层脚手架——flow-kit init 一条命令为新项目装好 .agents 工作流引擎(阶段命令/角色契约/门禁钩子/kb 检索/看板/规则面预算)+ workflow 文档闭环 + wiki 知识库,装完自包含、零运行时依赖",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
package/src/add-host.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// flow-kit add-host <宿主>:init 后补装宿主适配层。
|
|
2
2
|
// 渲染 modules/hosts/<h> → HOSTS[h].dir → managed 台账按 rel 去重补记 → options.hosts 补记
|
|
3
|
-
// → localOnly 宿主(
|
|
3
|
+
// → localOnly 宿主(2026-10-05 起暂无命中,见 profiles.mjs#HOSTS.localOnly).gitignore 追加 → doctor
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { renderTree } from './render.mjs';
|
|
@@ -51,7 +51,8 @@ export function addHost(args, pkgRoot) {
|
|
|
51
51
|
if (!already) kit.options.hosts = [...(kit.options.hosts || []), host];
|
|
52
52
|
fs.writeFileSync(kitPath, `${JSON.stringify(kit, null, 2)}\n`);
|
|
53
53
|
|
|
54
|
-
// localOnly 宿主进 .gitignore
|
|
54
|
+
// localOnly 宿主进 .gitignore(缺才补)。2026-10-05 起七个宿主一律 localOnly:false,该分支暂无宿主命中;
|
|
55
|
+
// 保留是为了未来重开「本机专属宿主」档位时不必改结构——判定单源在 profiles.mjs#HOSTS.localOnly。
|
|
55
56
|
if (HOSTS[host].localOnly) {
|
|
56
57
|
const giPath = path.join(target, '.gitignore');
|
|
57
58
|
const line = `${HOSTS[host].dir}/`;
|
package/src/cli.mjs
CHANGED
|
@@ -46,7 +46,7 @@ init 选项(全部可选,均有默认值):
|
|
|
46
46
|
条件编译检查(builds)与质量检测(checks:lint/类型/vet 等秒级确定性检查,
|
|
47
47
|
有对应配置文件才启用)、settings.json 自检验命令权限(allow)、
|
|
48
48
|
AGENTS.md「项目适配区」命令预填
|
|
49
|
-
--hosts <宿主列表> 逗号分隔:zcode,opencode,trae,omp,claude,cursor,codex(默认 zcode
|
|
49
|
+
--hosts <宿主列表> 逗号分隔:zcode,opencode,trae,omp,claude,cursor,codex(默认 zcode;七个宿主一律入库,不自动进 .gitignore)
|
|
50
50
|
--board-port <端口> workflow 看板端口(默认 8933)
|
|
51
51
|
--dir <目录> 目标项目根(默认当前目录)
|
|
52
52
|
--force 覆盖已存在的同名文件(默认保守跳过)
|
package/src/init.mjs
CHANGED
|
@@ -242,7 +242,8 @@ export async function init(args, pkgRoot) {
|
|
|
242
242
|
ownedGenerated.push({ rel, sha256: sha256(Buffer.from(content, 'utf8')) });
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
-
// 4) .gitignore
|
|
245
|
+
// 4) .gitignore 追加(缺哪条补哪条)。2026-10-05 起七个宿主一律 localOnly:false,
|
|
246
|
+
// 宿主目录不再自动进 .gitignore——实际只补 .agents/cache/ 这条运行时缓存。
|
|
246
247
|
const giPath = path.join(target, '.gitignore');
|
|
247
248
|
const giNeed = ['.agents/cache/', ...(hosts.filter((h) => HOSTS[h].localOnly).map((h) => `${HOSTS[h].dir}/`))];
|
|
248
249
|
let giText = fs.existsSync(giPath) ? fs.readFileSync(giPath, 'utf8') : '';
|
package/src/profiles.mjs
CHANGED
|
@@ -6,10 +6,14 @@
|
|
|
6
6
|
// pkg:scripts.build 表示 package.json 里该脚本为非空字符串。没有对应配置的项目自动跳过,
|
|
7
7
|
// 不 fail-closed 误拦。none = 不配置(项目日后自填)。
|
|
8
8
|
export const HOSTS = {
|
|
9
|
-
zcode
|
|
9
|
+
// 2026-10-05 宿主本地化撤销:zcode / omp 原 localOnly:true(init 与 add-host 会把 .zcode/、.omp/
|
|
10
|
+
// 整目录写进装户 .gitignore)。改为 false 后七个宿主一律入库,换宿主不丢文件、CI 克隆面与本仓一致。
|
|
11
|
+
// 代价是 zcode 的会话级液态草稿(.zcode/drafts/、.zcode/plans/)改由装户自己加 .gitignore 规则;
|
|
12
|
+
// 本仓 .gitignore 净效果保留这两行草稿规则(实例见根 .gitignore),.zcode/agents/*.md 保持 tracked(2026-09-27 gate-coverage 独立复核确立)。
|
|
13
|
+
zcode: { dir: '.zcode', localOnly: false },
|
|
10
14
|
opencode: { dir: '.opencode', localOnly: false, commandPrefix: 'wf-' },
|
|
11
15
|
trae: { dir: '.trae', localOnly: false, commandPrefix: 'wf-' },
|
|
12
|
-
omp: { dir: '.omp', localOnly:
|
|
16
|
+
omp: { dir: '.omp', localOnly: false },
|
|
13
17
|
// 2026-09-29 店面:序号仍把 zcode 放第一(交互菜单 1–4 不变)。claude / cursor 的 commands 由宿主自动加载;
|
|
14
18
|
// codex 自动读仓库根 AGENTS.md 与 .codex/skills/,commands 与 agents 是同形薄转发,供显式引用。
|
|
15
19
|
claude: { dir: '.claude', localOnly: false, commandPrefix: 'wf-' },
|
package/src/sync.test.mjs
CHANGED
|
@@ -18,6 +18,8 @@ function check(name, cond, detail = '') {
|
|
|
18
18
|
}
|
|
19
19
|
const W = (p, content) => { fs.mkdirSync(path.dirname(p), { recursive: true }); fs.writeFileSync(p, content); };
|
|
20
20
|
const R = (p) => fs.readFileSync(p, 'utf8');
|
|
21
|
+
// 可选读:装户端在无 localOnly 宿主时不再创建 .gitignore,断言「不追加」须容忍文件不存在
|
|
22
|
+
const ROpt = (p) => (fs.existsSync(p) ? fs.readFileSync(p, 'utf8') : '');
|
|
21
23
|
|
|
22
24
|
// ---- fixture 包根(v2 形态:chg/host 变更、new 新增、gone 已删)----
|
|
23
25
|
function mkFixture() {
|
|
@@ -162,7 +164,7 @@ else if (cmd === 'add-gate') addGate(rest, root);
|
|
|
162
164
|
const kit = JSON.parse(R(path.join(t, '.agents/kit.json')));
|
|
163
165
|
check('S7 managed 台账补记(项目根相对路径)', kit.managed.some((f) => f.rel === '.zcode/h.txt'), JSON.stringify(kit.managed));
|
|
164
166
|
check('S7 options.hosts 补记', Array.isArray(kit.options.hosts) && kit.options.hosts.includes('zcode'));
|
|
165
|
-
check('S7 localOnly
|
|
167
|
+
check('S7 宿主目录不再进 .gitignore(localOnly 已撤销,zcode 与其余宿主同策)', !ROpt(path.join(t, '.gitignore')).includes('.zcode/'), ROpt(path.join(t, '.gitignore')));
|
|
166
168
|
const r2 = runCmd('add-host', fx, t, ['zcode']);
|
|
167
169
|
check('S7 重复装未 --force → 报错退出', r2.status === 1 && (r2.stderr || '').includes('--force'), r2.stderr);
|
|
168
170
|
}
|
|
@@ -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
|
-
|
|
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 =
|
|
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 = false;
|
|
221
246
|
const fileLinesCache = new Map();
|
|
222
247
|
function linesOf(file) {
|
|
223
248
|
if (!fileLinesCache.has(file)) {
|
|
224
|
-
|
|
225
|
-
|
|
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) {
|
|
255
|
+
fsReadWarned = true;
|
|
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
|
}
|
|
@@ -504,7 +538,7 @@ for (const inc of docFiles('incidents')) {
|
|
|
504
538
|
}
|
|
505
539
|
}
|
|
506
540
|
const zc = `.zcode/agents/${role}.md`;
|
|
507
|
-
if (fs.existsSync(path.join(ROOT, zc))) { //
|
|
541
|
+
if (fs.existsSync(path.join(ROOT, zc))) { // .zcode/agents/ 现为入库件(2026-10-05 起宿主一律入库);缺失只说明宿主未装,不告警,存在时校验一致性
|
|
508
542
|
if (!new RegExp(`^name:\\s*["']?${role}["']?\\s*$`, 'm').test(textOf(zc).join('\n'))) warnings.push(`- [WARN Adapter 无效] ZCode ${role} 的 name 与文件名不一致:${zc}`);
|
|
509
543
|
if (!textOf(zc).some((l) => l.includes(rolePath))) warnings.push(`- [WARN Adapter 断线] ZCode ${role} 未引用公共角色:${rolePath}`);
|
|
510
544
|
}
|
|
@@ -578,7 +612,9 @@ function verifyEvidenceTruth(evidenceStr, planBase, root) {
|
|
|
578
612
|
if (!shaMatch || /^\d+$/.test(shaMatch[1])) return { ok: true, type: 'text' };
|
|
579
613
|
|
|
580
614
|
const sha = shaMatch[1];
|
|
581
|
-
|
|
615
|
+
// 直连走 spawnGit(与 gitOut 同一 loud+retry 通道,注释互引):spawn 异常不再被静默算作业务失败;
|
|
616
|
+
// 重试后仍异常按下方 status!==0 判定(fail-closed 保持——本地核验不了就按伪造拦)
|
|
617
|
+
const revParse = spawnGit(['rev-parse', sha]);
|
|
582
618
|
if (revParse.status !== 0) {
|
|
583
619
|
// 区分本地伪造与外部仓库引用:
|
|
584
620
|
// 剔除 SHA 与泛指词后仍有其他内容(如项目名、路径描述)→ 视为外部仓库引用,不硬拦(本地无法核验他仓哈希);
|
|
@@ -607,7 +643,7 @@ function verifyEvidenceTruth(evidenceStr, planBase, root) {
|
|
|
607
643
|
}
|
|
608
644
|
}
|
|
609
645
|
|
|
610
|
-
const show =
|
|
646
|
+
const show = spawnGit(['show', '--name-only', sha]);
|
|
611
647
|
if (show.status !== 0) return { ok: false, type: 'error', msg: `无法读取提交 ${sha} 的文件列表` };
|
|
612
648
|
|
|
613
649
|
const changedFiles = show.stdout.split(/\r?\n/).filter(Boolean);
|
|
@@ -617,7 +653,7 @@ function verifyEvidenceTruth(evidenceStr, planBase, root) {
|
|
|
617
653
|
// 过程证据(2026-10-03 check-evidence-process):执行器驱动的提交可作验收证据——提交信息带
|
|
618
654
|
// 「pipeline-run <runId>」标记(docsCommit 四种格式共有段,runId 严格形态 日期8-时间4-主题-尾;
|
|
619
655
|
// 与 run 事件流同源,git 历史留痕可对质)。只救 irrelevant:forged(SHA 不存在)在前已判,其余判据不动。
|
|
620
|
-
const subj =
|
|
656
|
+
const subj = spawnGit(['log', '-1', '--format=%s', sha]);
|
|
621
657
|
if (subj.status === 0 && /pipeline-run [0-9]{8}-[0-9]{4}-[A-Za-z0-9-]+/.test(subj.stdout)) {
|
|
622
658
|
return { ok: true, type: 'process' };
|
|
623
659
|
}
|
|
@@ -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
|
|
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
|
-
|
|
110
|
+
gitRetry(['init', '-q'], root);
|
|
88
111
|
};
|
|
89
112
|
const gitCommitAll = (root, msg) => {
|
|
90
|
-
|
|
91
|
-
|
|
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)
|
|
@@ -253,7 +275,7 @@ const mkConfirmedDoc = (root, rel, fmBody) => {
|
|
|
253
275
|
rmfix(T);
|
|
254
276
|
}
|
|
255
277
|
|
|
256
|
-
// ---- 场景 15:ZCode Adapter 缺失不告警(
|
|
278
|
+
// ---- 场景 15:ZCode Adapter 缺失不告警(宿主未装),存在且断线仍告警 ----
|
|
257
279
|
{
|
|
258
280
|
const T = mkfix();
|
|
259
281
|
w(T, 'workflow/intents/2026-09-12-o.md', INTENT('o', '状态: done\n级别: L1\n日期: 2026-09-12', '\n## 验收标准(可测试)\n- [x] 用例通过(证据:全绿)\n'));
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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,41 @@ 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
|
+
}
|
|
702
757
|
// ---- 场景 45:检查 2——真未填占位(标题 <主题>,无 .md)→ 仍报模板未填 ----
|
|
703
758
|
{
|
|
704
759
|
const T = mkfix();
|