agentic-flow-kit 1.7.0 → 1.7.15
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 +40 -6
- package/modules/gates/README.md +1 -0
- package/modules/gates/test-protect/README.md +23 -0
- package/modules/gates/test-protect/check-test-protect.mjs +64 -0
- package/modules/gates/test-protect/local-pre-commit.line +1 -0
- package/modules/hosts/README.md +57 -0
- package/modules/hosts/claude/commands/wf-build.md +5 -24
- package/modules/hosts/claude/commands/wf-deploy.md +1 -0
- package/modules/hosts/claude/commands/wf-design.md +2 -2
- package/modules/hosts/claude/commands/wf-gate-checklist.md +1 -3
- package/modules/hosts/claude/commands/wf-plan.md +1 -1
- package/modules/hosts/claude/commands/wf-source-sync-check.md +1 -3
- package/modules/hosts/claude/commands/wf-sync-hosts.md +1 -3
- package/modules/hosts/claude/commands/wf-test.md +5 -4
- package/modules/hosts/codex/commands/wf-build.md +5 -24
- package/modules/hosts/codex/commands/wf-deploy.md +1 -0
- package/modules/hosts/codex/commands/wf-design.md +2 -2
- package/modules/hosts/codex/commands/wf-gate-checklist.md +1 -3
- package/modules/hosts/codex/commands/wf-plan.md +1 -1
- package/modules/hosts/codex/commands/wf-source-sync-check.md +1 -3
- package/modules/hosts/codex/commands/wf-sync-hosts.md +1 -3
- package/modules/hosts/codex/commands/wf-test.md +5 -4
- package/modules/hosts/cursor/commands/wf-build.md +5 -24
- package/modules/hosts/cursor/commands/wf-deploy.md +1 -0
- package/modules/hosts/cursor/commands/wf-design.md +2 -2
- package/modules/hosts/cursor/commands/wf-gate-checklist.md +1 -3
- package/modules/hosts/cursor/commands/wf-plan.md +1 -1
- package/modules/hosts/cursor/commands/wf-source-sync-check.md +1 -3
- package/modules/hosts/cursor/commands/wf-sync-hosts.md +1 -3
- package/modules/hosts/cursor/commands/wf-test.md +5 -4
- package/modules/hosts/opencode/commands/wf-build.md +5 -24
- package/modules/hosts/opencode/commands/wf-deploy.md +1 -0
- package/modules/hosts/opencode/commands/wf-design.md +2 -2
- package/modules/hosts/opencode/commands/wf-gate-checklist.md +1 -3
- package/modules/hosts/opencode/commands/wf-plan.md +1 -1
- package/modules/hosts/opencode/commands/wf-source-sync-check.md +1 -3
- package/modules/hosts/opencode/commands/wf-sync-hosts.md +1 -3
- package/modules/hosts/opencode/commands/wf-test.md +5 -4
- package/modules/hosts/trae/commands/wf-build.md +5 -24
- package/modules/hosts/trae/commands/wf-deploy.md +1 -0
- package/modules/hosts/trae/commands/wf-design.md +2 -2
- package/modules/hosts/trae/commands/wf-gate-checklist.md +1 -3
- package/modules/hosts/trae/commands/wf-plan.md +1 -1
- package/modules/hosts/trae/commands/wf-source-sync-check.md +1 -3
- package/modules/hosts/trae/commands/wf-sync-hosts.md +1 -3
- package/modules/hosts/trae/commands/wf-test.md +5 -4
- package/package.json +2 -2
- package/src/add-gate.mjs +25 -1
- package/src/add-gate.test.mjs +103 -0
- package/src/cli.mjs +17 -2
- package/src/doctor.mjs +99 -11
- package/src/gates.test.mjs +17 -0
- package/src/init.mjs +7 -1
- package/src/init.test.mjs +19 -0
- package/src/instance-ref.test.mjs +62 -0
- package/src/jscpd-drift.test.mjs +144 -0
- package/src/render.mjs +5 -0
- package/src/rule-budget.test.mjs +103 -0
- package/src/sync-hosts.mjs +4 -2
- package/src/trae-hooks.test.mjs +15 -3
- package/src/validate.mjs +72 -0
- package/src/validate.test.mjs +95 -0
- package/templates/AGENTS.md +1 -0
- package/templates/_agents/commands/build.md +5 -24
- package/templates/_agents/commands/deploy.md +1 -0
- package/templates/_agents/commands/design.md +2 -2
- package/templates/_agents/commands/gate-checklist.md +1 -3
- package/templates/_agents/commands/plan.md +1 -1
- package/templates/_agents/commands/source-sync-check.md +1 -3
- package/templates/_agents/commands/sync-hosts.md +1 -3
- package/templates/_agents/commands/test.md +5 -4
- package/templates/_agents/control-surfaces.txt +53 -0
- package/templates/_agents/scripts/agg-delegations.cjs +51 -6
- package/templates/_agents/scripts/agg-delegations.test.mjs +79 -1
- package/templates/_agents/scripts/check-engine-integrity.mjs +288 -0
- package/templates/_agents/scripts/check-engine-integrity.test.mjs +227 -0
- package/templates/_agents/scripts/check-lane-surface.mjs +7 -2
- package/templates/_agents/scripts/check-ledger.test.mjs +2 -0
- package/templates/_agents/scripts/check-loop.mjs +206 -4
- package/templates/_agents/scripts/check-loop.test.mjs +312 -2
- package/templates/_agents/scripts/confirm-doc.mjs +14 -1
- package/templates/_agents/scripts/confirm-doc.test.mjs +59 -0
- package/templates/_agents/scripts/doctor.test.mjs +251 -1
- package/templates/_agents/scripts/draft-sediment.mjs +178 -0
- package/templates/_agents/scripts/draft-sediment.test.mjs +272 -0
- package/templates/_agents/scripts/gate-checklist.mjs +10 -2
- package/templates/_agents/scripts/gate-checklist.test.mjs +7 -1
- package/templates/_agents/scripts/gen-wiki-board.mjs +61 -6
- package/templates/_agents/scripts/gen-wiki-board.test.mjs +74 -1
- package/templates/_agents/scripts/gen-workflow-dashboard.mjs +2 -2
- package/templates/_agents/scripts/gen-workflow-metrics.mjs +11 -5
- package/templates/_agents/scripts/kb-search.mjs +87 -12
- package/templates/_agents/scripts/kb-search.test.mjs +83 -0
- package/templates/_agents/scripts/pipeline-run.mjs +1 -1
- package/templates/_agents/scripts/solidify-task.mjs +1 -1
- package/templates/_agents/scripts/source-sync-check.test.mjs +9 -4
- package/templates/_agents/scripts/stage-gates.mjs +48 -1
- package/templates/_agents/scripts/stage-gates.test.mjs +105 -0
- package/templates/_agents/scripts/verify-wiki-consistency.mjs +34 -0
- package/templates/_agents/scripts/verify-wiki-consistency.test.mjs +44 -2
- package/templates/_agents/scripts/verify.mjs +1 -1
- package/templates/_agents/scripts-test-exempt.txt +3 -1
- package/templates/_agents/skills/wiki/SKILL.md +5 -3
- package/templates/wiki/INDEX.md +44 -1
- package/templates/wiki//347/237/245/350/257/206/346/262/211/346/267/200/346/200/273/350/247/210.html +22 -1
- package/templates/workflow/delegations.md +5 -4
- package/templates/workflow/incidents/_TEMPLATE.md +2 -0
- package/templates/workflow/intents/_TEMPLATE.md +3 -0
- package/templates/workflow/plans/_TEMPLATE.md +4 -0
- package/templates/workflow/specs/_TEMPLATE.md +23 -0
|
@@ -13,7 +13,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
13
13
|
import { computeFingerprint } from './confirm-doc.mjs';
|
|
14
14
|
import { POLICIES, loadKitPolicy } from './policy.mjs';
|
|
15
15
|
import { entryConfirmed, laneOfEntry, laneOfDoc } from './stage-gates.mjs';
|
|
16
|
-
import { parseAddedDatesLog, addedDatesWithCache } from './check-loop.mjs';
|
|
16
|
+
import { parseAddedDatesLog, addedDatesWithCache, scenarioIdsOf, extractThenByScenario, sectionHasBody, sedimentLandingIsShell } from './check-loop.mjs';
|
|
17
17
|
|
|
18
18
|
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
19
19
|
const CHECK_LOOP = path.join(SCRIPT_DIR, 'check-loop.mjs');
|
|
@@ -3023,5 +3023,315 @@ w(T, 'workflow/intents/2026-09-12-deep.md', INTENT('deep', '状态: draft\n级
|
|
|
3023
3023
|
fs.rmSync(cacheP, { force: true });
|
|
3024
3024
|
}
|
|
3025
3025
|
|
|
3026
|
+
// ---- 场景 23:场景覆盖子判据(2026-10-10 scenario-coverage,灰度 warning)----
|
|
3027
|
+
// scenarioIdsOf 纯函数 + 端到端(spec 有「验收场景」节 → done intent 须逐场景引用;缺 = warning 不阻断)
|
|
3028
|
+
{
|
|
3029
|
+
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'scen-ids-'));
|
|
3030
|
+
fs.writeFileSync(path.join(d, 's1.md'),
|
|
3031
|
+
'---\n状态: draft\n---\n# SPEC\n## 功能行为\n### S0 不该被提取(节外)\n## 验收场景(可选)\n### S1 折扣\n- GIVEN x\n- WHEN y\n- THEN z\n### S2 退款\n- GIVEN a\n## 数据流\n### S3 也不提取(节外)\n');
|
|
3032
|
+
const ids = scenarioIdsOf(path.join(d, 's1.md'));
|
|
3033
|
+
check('场景 24a scenarioIdsOf:只提「验收场景」节内 ID,按序去重', ids.join(',') === 'S1,S2', JSON.stringify(ids));
|
|
3034
|
+
fs.writeFileSync(path.join(d, 's2.md'), '---\n状态: draft\n---\n# SPEC\n## 功能行为\n无场景节\n');
|
|
3035
|
+
check('场景 24b scenarioIdsOf:无「验收场景」节 → 空数组', scenarioIdsOf(path.join(d, 's2.md')).length === 0);
|
|
3036
|
+
check('场景 24c scenarioIdsOf:文件不可读 → 空数组(fail-open)', scenarioIdsOf(path.join(d, 'nope.md')).length === 0);
|
|
3037
|
+
fs.rmSync(d, { recursive: true, force: true });
|
|
3038
|
+
}
|
|
3039
|
+
{
|
|
3040
|
+
// 端到端:spec 两场景 + intent 全引用 → exit 0 且无「场景未覆盖」warning
|
|
3041
|
+
const T = mkfix();
|
|
3042
|
+
w(T, 'workflow/specs/2026-09-12-sc.md', '---\n状态: approved\n级别: L1\n---\n# SPEC — sc\n## 验收场景(可选)\n### S1 折扣\n- GIVEN x\n- WHEN y\n- THEN z\n### S2 退款\n- GIVEN a\n');
|
|
3043
|
+
w(T, 'workflow/intents/2026-09-12-sc.md', INTENT('sc', '状态: done\n级别: L1\n日期: 2026-09-12',
|
|
3044
|
+
'\n## 验收标准(可测试)\n- [x] 场景:S1 折扣生效(证据:冒烟 checkout.json)\n- [x] 场景:S2 退款到账(证据:refund.test.js)\n'));
|
|
3045
|
+
w(T, 'workflow/plans/2026-09-12-sc.md', PLAN('sc', '状态: done\n级别: L1'));
|
|
3046
|
+
const r = run(T);
|
|
3047
|
+
check('场景 24d spec 有场景且 intent 全引用 → exit 0 无告警', r.status === 0 && !outOf(r).includes('场景未覆盖'), `exit=${r.status}\n${outOf(r).slice(0, 400)}`);
|
|
3048
|
+
}
|
|
3049
|
+
{
|
|
3050
|
+
// 端到端:缺 S2 引用 → warning(灰度,不阻断:exit 仍 0)
|
|
3051
|
+
const T = mkfix();
|
|
3052
|
+
w(T, 'workflow/specs/2026-09-12-sc.md', '---\n状态: approved\n级别: L1\n---\n# SPEC — sc\n## 验收场景(可选)\n### S1 折扣\n- GIVEN x\n### S2 退款\n- GIVEN a\n');
|
|
3053
|
+
w(T, 'workflow/intents/2026-09-12-sc.md', INTENT('sc', '状态: done\n级别: L1\n日期: 2026-09-12',
|
|
3054
|
+
'\n## 验收标准(可测试)\n- [x] 场景:S1 折扣生效(证据:冒烟 checkout.json)\n'));
|
|
3055
|
+
w(T, 'workflow/plans/2026-09-12-sc.md', PLAN('sc', '状态: done\n级别: L1'));
|
|
3056
|
+
const r = run(T);
|
|
3057
|
+
check('场景 24e 缺场景引用 → warning 且不阻断(exit 0)',
|
|
3058
|
+
r.status === 0 && outOf(r).includes('场景未覆盖') && outOf(r).includes('缺 S2'), `exit=${r.status}\n${outOf(r).slice(0, 500)}`);
|
|
3059
|
+
}
|
|
3060
|
+
{
|
|
3061
|
+
// 端到端:spec 无场景节 → 零变化(不出现「场景未覆盖」)
|
|
3062
|
+
const T = mkfix();
|
|
3063
|
+
w(T, 'workflow/specs/2026-09-12-sc.md', '---\n状态: approved\n级别: L1\n---\n# SPEC — sc\n## 功能行为\n普通 spec\n');
|
|
3064
|
+
w(T, 'workflow/intents/2026-09-12-sc.md', INTENT('sc', '状态: done\n级别: L1\n日期: 2026-09-12',
|
|
3065
|
+
'\n## 验收标准(可测试)\n- [x] 用例通过(证据:jest 全绿)\n'));
|
|
3066
|
+
w(T, 'workflow/plans/2026-09-12-sc.md', PLAN('sc', '状态: done\n级别: L1'));
|
|
3067
|
+
const r = run(T);
|
|
3068
|
+
check('场景 24f spec 无场景节 → 零变化(无场景告警,exit 0)', r.status === 0 && !outOf(r).includes('场景未覆盖'), `exit=${r.status}\n${outOf(r).slice(0, 400)}`);
|
|
3069
|
+
}
|
|
3070
|
+
|
|
3071
|
+
// ---- 场景 25:沉淀追踪子判据(2026-10-10 sediment-tracking,灰度 warning)----
|
|
3072
|
+
// sectionHasBody 纯函数 + 端到端(done intent 含影响面/触达红线 且 frontmatter 无「沉淀」→ WARN 未沉淀;
|
|
3073
|
+
// incident closed 且三件套齐全 → 同款;「沉淀: 无」豁免;路径断档 → WARN 沉淀断档)
|
|
3074
|
+
{
|
|
3075
|
+
check('场景 25a sectionHasBody:标题后有实质行 → true',
|
|
3076
|
+
sectionHasBody(['## 影响面', '- 页面 A', ''], '影响面') === true);
|
|
3077
|
+
check('场景 25b sectionHasBody:光标题(模板未填)→ false',
|
|
3078
|
+
sectionHasBody(['## 影响面', '<页面 / 接口 / 数据范围>', ''], '影响面') === false);
|
|
3079
|
+
check('场景 25c sectionHasBody:无该节 → false',
|
|
3080
|
+
sectionHasBody(['## 目标', '做 X'], '影响面') === false);
|
|
3081
|
+
check('场景 25d sectionHasBody:前缀匹配 + 节外有同名行不算',
|
|
3082
|
+
sectionHasBody(['## 目标', '提影响面', '## 影响面与边界', '- 内容'], '影响面') === true);
|
|
3083
|
+
}
|
|
3084
|
+
{
|
|
3085
|
+
// intent 侧:done + 影响面有内容 + 无沉淀字段 → WARN 未沉淀(不阻断)
|
|
3086
|
+
const T = mkfix();
|
|
3087
|
+
w(T, 'workflow/intents/2026-09-12-sed.md', INTENT('sed', '状态: done\n级别: L1\n日期: 2026-09-12',
|
|
3088
|
+
'\n## 验收标准(可测试)\n- [x] 用例通过(证据:jest 全绿)\n## 影响面\n- 页面 A / 接口 B\n'));
|
|
3089
|
+
w(T, 'workflow/plans/2026-09-12-sed.md', PLAN('sed', '状态: done\n级别: L1'));
|
|
3090
|
+
const r = run(T);
|
|
3091
|
+
check('场景 25e done intent 高价值无沉淀 → WARN 未沉淀且不阻断',
|
|
3092
|
+
r.status === 0 && outOf(r).includes('未沉淀') && outOf(r).includes('2026-09-12-sed.md'), `exit=${r.status}\n${outOf(r).slice(0, 500)}`);
|
|
3093
|
+
}
|
|
3094
|
+
{
|
|
3095
|
+
// 「沉淀: 无」显式豁免 → 无未沉淀告警
|
|
3096
|
+
const T = mkfix();
|
|
3097
|
+
w(T, 'workflow/intents/2026-09-12-sed.md', INTENT('sed', '状态: done\n级别: L1\n日期: 2026-09-12\n沉淀: 无',
|
|
3098
|
+
'\n## 验收标准(可测试)\n- [x] 用例通过(证据:jest 全绿)\n## 影响面\n- 页面 A\n'));
|
|
3099
|
+
w(T, 'workflow/plans/2026-09-12-sed.md', PLAN('sed', '状态: done\n级别: L1'));
|
|
3100
|
+
const r = run(T);
|
|
3101
|
+
check('场景 25f 「沉淀: 无」显式豁免 → 无未沉淀告警',
|
|
3102
|
+
r.status === 0 && !outOf(r).includes('未沉淀'), `exit=${r.status}\n${outOf(r).slice(0, 500)}`);
|
|
3103
|
+
}
|
|
3104
|
+
{
|
|
3105
|
+
// 沉淀字段指向不存在的文件 → WARN 沉淀断档
|
|
3106
|
+
const T = mkfix();
|
|
3107
|
+
w(T, 'workflow/intents/2026-09-12-sed.md', INTENT('sed', '状态: done\n级别: L1\n日期: 2026-09-12\n沉淀: wiki/项目规范/不存在.md',
|
|
3108
|
+
'\n## 验收标准(可测试)\n- [x] 用例通过(证据:jest 全绿)\n## 影响面\n- 页面 A\n'));
|
|
3109
|
+
w(T, 'workflow/plans/2026-09-12-sed.md', PLAN('sed', '状态: done\n级别: L1'));
|
|
3110
|
+
const r = run(T);
|
|
3111
|
+
check('场景 25g 沉淀字段指向缺失文件 → WARN 沉淀断档',
|
|
3112
|
+
r.status === 0 && outOf(r).includes('沉淀断档') && !outOf(r).includes('未沉淀'), `exit=${r.status}\n${outOf(r).slice(0, 500)}`);
|
|
3113
|
+
}
|
|
3114
|
+
{
|
|
3115
|
+
// intent 无影响面/触达红线 → 零变化(不出现未沉淀告警)
|
|
3116
|
+
const T = mkfix();
|
|
3117
|
+
w(T, 'workflow/intents/2026-09-12-sed.md', INTENT('sed', '状态: done\n级别: L1\n日期: 2026-09-12',
|
|
3118
|
+
'\n## 验收标准(可测试)\n- [x] 用例通过(证据:jest 全绿)\n'));
|
|
3119
|
+
w(T, 'workflow/plans/2026-09-12-sed.md', PLAN('sed', '状态: done\n级别: L1'));
|
|
3120
|
+
const r = run(T);
|
|
3121
|
+
check('场景 25h intent 无高价值节 → 零变化', r.status === 0 && !outOf(r).includes('未沉淀'), `exit=${r.status}\n${outOf(r).slice(0, 400)}`);
|
|
3122
|
+
}
|
|
3123
|
+
{
|
|
3124
|
+
// incident 侧:closed 且三件套齐全 且无沉淀 → WARN 未沉淀
|
|
3125
|
+
const T = mkfix();
|
|
3126
|
+
w(T, 'workflow/incidents/2026-09-12-inc.md',
|
|
3127
|
+
`---\n状态: closed\n级别: L1\n发现: 2026-09-12\n---\n# INCIDENT — inc\n## 时间线\n- 用户确认:草稿过目通过(2026-09-12)\n## 复盘三件套(缺一不可)\n\n${三件套(false)}`);
|
|
3128
|
+
w(T, 'workflow/plans/2026-09-12-inc.md', PLAN('inc', '状态: done\n级别: L1'));
|
|
3129
|
+
const r = run(T);
|
|
3130
|
+
check('场景 25i closed incident 三件套齐全无沉淀 → WARN 未沉淀',
|
|
3131
|
+
r.status === 0 && outOf(r).includes('未沉淀') && outOf(r).includes('2026-09-12-inc.md'), `exit=${r.status}\n${outOf(r).slice(0, 500)}`);
|
|
3132
|
+
}
|
|
3133
|
+
{
|
|
3134
|
+
// incident 三件套不全 → 沉淀子判据不触发(不出未沉淀,只出三件套不全)
|
|
3135
|
+
const T = mkfix();
|
|
3136
|
+
w(T, 'workflow/incidents/2026-09-12-inc.md',
|
|
3137
|
+
`---\n状态: closed\n级别: L1\n发现: 2026-09-12\n---\n# INCIDENT — inc\n## 时间线\n- 用户确认:草稿过目通过(2026-09-12)\n## 复盘三件套(缺一不可)\n\n1. 结构性修复\n - 修复 commit:abc1234\n - 是否需要新 intent:\n - 否 → 理由:单点修复\n`);
|
|
3138
|
+
w(T, 'workflow/plans/2026-09-12-inc.md', PLAN('inc', '状态: done\n级别: L1'));
|
|
3139
|
+
const r = run(T);
|
|
3140
|
+
check('场景 25j incident 三件套不全 → 只告三件套不全,不出未沉淀',
|
|
3141
|
+
r.status === 0 && outOf(r).includes('三件套不全') && !outOf(r).includes('未沉淀'), `exit=${r.status}\n${outOf(r).slice(0, 500)}`);
|
|
3142
|
+
}
|
|
3143
|
+
|
|
3144
|
+
// ---- 场景 25 续:落点真实性(2026-10-11 landing-reality,P1-3,灰度 warning)----
|
|
3145
|
+
// sedimentLandingIsShell 纯函数 + 端到端(落点只有标题/占位 → WARN 落点空壳;带「来源:」frontmatter
|
|
3146
|
+
// 或 ≥3 行非占位正文 → 不出;不存在 → 仍 WARN 沉淀断档(回归);指向目录(读取失败)→ 空壳并注明读取失败)
|
|
3147
|
+
{
|
|
3148
|
+
check('场景 25k sedimentLandingIsShell:只有标题+占位(空壳)→ true',
|
|
3149
|
+
sedimentLandingIsShell('---\n标题: 某主题\n---\n# 某主题\n<状态:待整理>\n') === true);
|
|
3150
|
+
check('场景 25l sedimentLandingIsShell:frontmatter 有「来源:」→ false',
|
|
3151
|
+
sedimentLandingIsShell('---\n来源: workflow/incidents/2026-09-12-inc.md\n---\n# 某主题\n') === false);
|
|
3152
|
+
check('场景 25m sedimentLandingIsShell:非占位正文 ≥3 行 → false',
|
|
3153
|
+
sedimentLandingIsShell('# 某主题\n- 要点一\n- 要点二\n- 要点三\n') === false);
|
|
3154
|
+
check('场景 25n sedimentLandingIsShell:非占位正文仅 2 行 → true',
|
|
3155
|
+
sedimentLandingIsShell('# 某主题\n- 要点一\n- 要点二\n') === true);
|
|
3156
|
+
check('场景 25o sedimentLandingIsShell:读取失败(null)→ true',
|
|
3157
|
+
sedimentLandingIsShell(null) === true);
|
|
3158
|
+
}
|
|
3159
|
+
{
|
|
3160
|
+
// incident 侧:closed + 三件套齐全 + 沉淀指向只有标题/占位的文件 → WARN 落点空壳(不阻断)
|
|
3161
|
+
const T = mkfix();
|
|
3162
|
+
w(T, 'workflow/incidents/2026-09-12-inc.md',
|
|
3163
|
+
`---\n状态: closed\n级别: L1\n发现: 2026-09-12\n沉淀: wiki/项目规范/shell.md\n---\n# INCIDENT — inc\n## 时间线\n- 用户确认:草稿过目通过(2026-09-12)\n## 复盘三件套(缺一不可)\n\n${三件套(false)}`);
|
|
3164
|
+
w(T, 'workflow/plans/2026-09-12-inc.md', PLAN('inc', '状态: done\n级别: L1'));
|
|
3165
|
+
fs.mkdirSync(path.join(T, 'wiki', '项目规范'), { recursive: true });
|
|
3166
|
+
w(T, 'wiki/项目规范/shell.md', '---\n标题: 某主题沉淀\n---\n# 某主题沉淀\n<状态:待整理>\n');
|
|
3167
|
+
const r = run(T);
|
|
3168
|
+
check('场景 25p incident 沉淀落点为标题/占位空壳 → WARN 落点空壳',
|
|
3169
|
+
r.status === 0 && outOf(r).includes('落点空壳') && outOf(r).includes('2026-09-12-inc.md')
|
|
3170
|
+
&& !outOf(r).includes('沉淀断档') && !outOf(r).includes('未沉淀'), `exit=${r.status}\n${outOf(r).slice(0, 500)}`);
|
|
3171
|
+
}
|
|
3172
|
+
{
|
|
3173
|
+
// intent 侧对称:done + 影响面有内容 + 沉淀指向空壳文件 → WARN 落点空壳(不阻断)
|
|
3174
|
+
const T = mkfix();
|
|
3175
|
+
w(T, 'workflow/intents/2026-09-12-sed.md', INTENT('sed', '状态: done\n级别: L1\n日期: 2026-09-12\n沉淀: wiki/项目规范/shell.md',
|
|
3176
|
+
'\n## 验收标准(可测试)\n- [x] 用例通过(证据:jest 全绿)\n## 影响面\n- 页面 A\n'));
|
|
3177
|
+
w(T, 'workflow/plans/2026-09-12-sed.md', PLAN('sed', '状态: done\n级别: L1'));
|
|
3178
|
+
fs.mkdirSync(path.join(T, 'wiki', '项目规范'), { recursive: true });
|
|
3179
|
+
w(T, 'wiki/项目规范/shell.md', '# 某主题沉淀\n<待整理>\n');
|
|
3180
|
+
const r = run(T);
|
|
3181
|
+
check('场景 25q intent 沉淀落点为标题/占位空壳 → WARN 落点空壳',
|
|
3182
|
+
r.status === 0 && outOf(r).includes('落点空壳') && outOf(r).includes('2026-09-12-sed.md')
|
|
3183
|
+
&& !outOf(r).includes('沉淀断档') && !outOf(r).includes('未沉淀'), `exit=${r.status}\n${outOf(r).slice(0, 500)}`);
|
|
3184
|
+
}
|
|
3185
|
+
{
|
|
3186
|
+
// 落点带「来源:」frontmatter → 非空壳(无落点空壳告警)
|
|
3187
|
+
const T = mkfix();
|
|
3188
|
+
w(T, 'workflow/intents/2026-09-12-sed.md', INTENT('sed', '状态: done\n级别: L1\n日期: 2026-09-12\n沉淀: wiki/项目规范/real.md',
|
|
3189
|
+
'\n## 验收标准(可测试)\n- [x] 用例通过(证据:jest 全绿)\n## 影响面\n- 页面 A\n'));
|
|
3190
|
+
w(T, 'workflow/plans/2026-09-12-sed.md', PLAN('sed', '状态: done\n级别: L1'));
|
|
3191
|
+
fs.mkdirSync(path.join(T, 'wiki', '项目规范'), { recursive: true });
|
|
3192
|
+
w(T, 'wiki/项目规范/real.md', '---\n来源: workflow/incidents/2026-09-12-inc.md\n---\n# 某主题沉淀\n');
|
|
3193
|
+
const r = run(T);
|
|
3194
|
+
check('场景 25r 沉淀落点带来源 frontmatter → 不出落点空壳',
|
|
3195
|
+
r.status === 0 && !outOf(r).includes('落点空壳') && !outOf(r).includes('沉淀断档'), `exit=${r.status}\n${outOf(r).slice(0, 500)}`);
|
|
3196
|
+
}
|
|
3197
|
+
{
|
|
3198
|
+
// 落点有 ≥3 行非占位正文 → 非空壳(incident 侧非空壳路径)
|
|
3199
|
+
const T = mkfix();
|
|
3200
|
+
w(T, 'workflow/incidents/2026-09-12-inc.md',
|
|
3201
|
+
`---\n状态: closed\n级别: L1\n发现: 2026-09-12\n沉淀: wiki/项目规范/real.md\n---\n# INCIDENT — inc\n## 时间线\n- 用户确认:草稿过目通过(2026-09-12)\n## 复盘三件套(缺一不可)\n\n${三件套(false)}`);
|
|
3202
|
+
w(T, 'workflow/plans/2026-09-12-inc.md', PLAN('inc', '状态: done\n级别: L1'));
|
|
3203
|
+
fs.mkdirSync(path.join(T, 'wiki', '项目规范'), { recursive: true });
|
|
3204
|
+
w(T, 'wiki/项目规范/real.md', '---\n标题: 某主题沉淀\n---\n# 某主题沉淀\n- 要点一\n- 要点二\n- 要点三\n');
|
|
3205
|
+
const r = run(T);
|
|
3206
|
+
check('场景 25s 沉淀落点有 ≥3 行非占位正文 → 不出落点空壳',
|
|
3207
|
+
r.status === 0 && !outOf(r).includes('落点空壳') && !outOf(r).includes('沉淀断档'), `exit=${r.status}\n${outOf(r).slice(0, 500)}`);
|
|
3208
|
+
}
|
|
3209
|
+
{
|
|
3210
|
+
// 回归:沉淀指向不存在的文件 → 仍是 WARN 沉淀断档(incident 侧;intent 侧由既有 25g 锚定)
|
|
3211
|
+
const T = mkfix();
|
|
3212
|
+
w(T, 'workflow/incidents/2026-09-12-inc.md',
|
|
3213
|
+
`---\n状态: closed\n级别: L1\n发现: 2026-09-12\n沉淀: wiki/项目规范/不存在.md\n---\n# INCIDENT — inc\n## 时间线\n- 用户确认:草稿过目通过(2026-09-12)\n## 复盘三件套(缺一不可)\n\n${三件套(false)}`);
|
|
3214
|
+
w(T, 'workflow/plans/2026-09-12-inc.md', PLAN('inc', '状态: done\n级别: L1'));
|
|
3215
|
+
const r = run(T);
|
|
3216
|
+
check('场景 25t 沉淀指向缺失文件 → 仍 WARN 沉淀断档(不出落点空壳)',
|
|
3217
|
+
r.status === 0 && outOf(r).includes('沉淀断档') && !outOf(r).includes('落点空壳'), `exit=${r.status}\n${outOf(r).slice(0, 500)}`);
|
|
3218
|
+
}
|
|
3219
|
+
{
|
|
3220
|
+
// 落点指向目录(readFileSync 抛异常)→ 按空壳出账并在文案注明读取失败
|
|
3221
|
+
const T = mkfix();
|
|
3222
|
+
w(T, 'workflow/incidents/2026-09-12-inc.md',
|
|
3223
|
+
`---\n状态: closed\n级别: L1\n发现: 2026-09-12\n沉淀: wiki/项目规范\n---\n# INCIDENT — inc\n## 时间线\n- 用户确认:草稿过目通过(2026-09-12)\n## 复盘三件套(缺一不可)\n\n${三件套(false)}`);
|
|
3224
|
+
w(T, 'workflow/plans/2026-09-12-inc.md', PLAN('inc', '状态: done\n级别: L1'));
|
|
3225
|
+
fs.mkdirSync(path.join(T, 'wiki', '项目规范'), { recursive: true });
|
|
3226
|
+
const r = run(T);
|
|
3227
|
+
check('场景 25u 沉淀落点读取失败(目录)→ WARN 落点空壳并注明读取失败',
|
|
3228
|
+
r.status === 0 && outOf(r).includes('落点空壳') && outOf(r).includes('读取失败'), `exit=${r.status}\n${outOf(r).slice(0, 500)}`);
|
|
3229
|
+
}
|
|
3230
|
+
|
|
3231
|
+
// ---- 场景 92:THEN 断言抽取(2026-10-11 then-assertion,P2-2,灰度 warning)----
|
|
3232
|
+
// extractThenByScenario 纯函数 + 端到端(声明场景缺 THEN 行 = 空壳场景 → WARN 场景无断言,聚合一条;
|
|
3233
|
+
// 验收场景节本身可选——节不存在零变化)
|
|
3234
|
+
{
|
|
3235
|
+
const spec = [
|
|
3236
|
+
'## 验收场景(可选)',
|
|
3237
|
+
'<说明行,不属于任何场景块>',
|
|
3238
|
+
'### S1 折扣',
|
|
3239
|
+
'- GIVEN 购物车有商品 THEN 不误抽(行中段 THEN 字样不算断言)',
|
|
3240
|
+
'- WHEN 结算',
|
|
3241
|
+
'- THEN 折扣金额入账',
|
|
3242
|
+
' - THEN 重复结算幂等(缩进变体也认)',
|
|
3243
|
+
'### S2 退款',
|
|
3244
|
+
'THEN: 退款单生成(无连字符带冒号也认)',
|
|
3245
|
+
'THEN 审计流水落库(无连字符空格也认)',
|
|
3246
|
+
].join('\n');
|
|
3247
|
+
const m = extractThenByScenario(spec);
|
|
3248
|
+
check('场景 92a extractThenByScenario:- THEN / 缩进 / THEN: / THEN 变体均抽取',
|
|
3249
|
+
m.get('S1').length === 2 && m.get('S2').length === 2
|
|
3250
|
+
&& m.get('S1')[0] === '- THEN 折扣金额入账' && m.get('S2')[0] === 'THEN: 退款单生成(无连字符带冒号也认)',
|
|
3251
|
+
JSON.stringify([...m]));
|
|
3252
|
+
check('场景 92b extractThenByScenario:GIVEN 行中段 THEN 字样不误抽',
|
|
3253
|
+
!m.get('S1').some((s) => s.includes('GIVEN')), JSON.stringify(m.get('S1')));
|
|
3254
|
+
check('场景 92c extractThenByScenario:无验收场景节 → 空 Map(节外 THEN 不认)',
|
|
3255
|
+
extractThenByScenario('# SPEC\n## 功能行为\n- THEN 节外不认\n').size === 0);
|
|
3256
|
+
check('场景 92d extractThenByScenario:非字符串(读取失败)→ 空 Map(不可判定不误报)',
|
|
3257
|
+
extractThenByScenario(null).size === 0);
|
|
3258
|
+
}
|
|
3259
|
+
{
|
|
3260
|
+
// 端到端:全场景带 THEN → 无「场景无断言」
|
|
3261
|
+
const T = mkfix();
|
|
3262
|
+
w(T, 'workflow/specs/2026-09-12-thn.md', '---\n状态: approved\n级别: L1\n---\n# SPEC — thn\n## 验收场景(可选)\n### S1 折扣\n- GIVEN x\n- WHEN y\n- THEN z\n### S2 退款\n- GIVEN a\n- THEN b\n');
|
|
3263
|
+
w(T, 'workflow/intents/2026-09-12-thn.md', INTENT('thn', '状态: done\n级别: L1\n日期: 2026-09-12',
|
|
3264
|
+
'\n## 验收标准(可测试)\n- [x] 场景:S1 折扣生效(证据:checkout.json)\n- [x] 场景:S2 退款到账(证据:refund.test.js)\n'));
|
|
3265
|
+
w(T, 'workflow/plans/2026-09-12-thn.md', PLAN('thn', '状态: done\n级别: L1'));
|
|
3266
|
+
const r = run(T);
|
|
3267
|
+
check('场景 92e 全场景带 THEN → 无场景无断言告警(exit 0)',
|
|
3268
|
+
r.status === 0 && !outOf(r).includes('场景无断言'), `exit=${r.status}\n${outOf(r).slice(0, 500)}`);
|
|
3269
|
+
}
|
|
3270
|
+
{
|
|
3271
|
+
// 端到端:S2/S3 缺 THEN → 聚合一条 WARN,S1(有断言)不在列
|
|
3272
|
+
const T = mkfix();
|
|
3273
|
+
w(T, 'workflow/specs/2026-09-12-thn.md', '---\n状态: approved\n级别: L1\n---\n# SPEC — thn\n## 验收场景(可选)\n### S1 折扣\n- GIVEN x\n- THEN z\n### S2 退款\n- GIVEN a\n### S3 对账\n- WHEN c\n');
|
|
3274
|
+
w(T, 'workflow/intents/2026-09-12-thn.md', INTENT('thn', '状态: done\n级别: L1\n日期: 2026-09-12',
|
|
3275
|
+
'\n## 验收标准(可测试)\n- [x] 场景:S1(证据:a.log)\n- [x] 场景:S2(证据:b.log)\n- [x] 场景:S3(证据:c.log)\n'));
|
|
3276
|
+
w(T, 'workflow/plans/2026-09-12-thn.md', PLAN('thn', '状态: done\n级别: L1'));
|
|
3277
|
+
const r = run(T);
|
|
3278
|
+
check('场景 92f 场景缺 THEN → 聚合 WARN 且只列缺断言的 ID(S2、S3)',
|
|
3279
|
+
r.status === 0 && outOf(r).includes('场景无断言') && outOf(r).includes('缺 THEN 断言:S2、S3'),
|
|
3280
|
+
`exit=${r.status}\n${outOf(r).slice(0, 500)}`);
|
|
3281
|
+
}
|
|
3282
|
+
{
|
|
3283
|
+
// 端到端:spec 无验收场景节 → THEN 对账不适用(零变化)
|
|
3284
|
+
const T = mkfix();
|
|
3285
|
+
w(T, 'workflow/specs/2026-09-12-thn.md', '---\n状态: approved\n级别: L1\n---\n# SPEC — thn\n## 功能行为\n普通 spec,无场景节\n');
|
|
3286
|
+
w(T, 'workflow/intents/2026-09-12-thn.md', INTENT('thn', '状态: done\n级别: L1\n日期: 2026-09-12',
|
|
3287
|
+
'\n## 验收标准(可测试)\n- [x] 用例通过(证据:jest 全绿)\n'));
|
|
3288
|
+
w(T, 'workflow/plans/2026-09-12-thn.md', PLAN('thn', '状态: done\n级别: L1'));
|
|
3289
|
+
const r = run(T);
|
|
3290
|
+
check('场景 92g spec 无验收场景节 → 零场景告警(exit 0)',
|
|
3291
|
+
r.status === 0 && !outOf(r).includes('场景无断言') && !outOf(r).includes('场景引用断链'),
|
|
3292
|
+
`exit=${r.status}\n${outOf(r).slice(0, 400)}`);
|
|
3293
|
+
}
|
|
3294
|
+
|
|
3295
|
+
// ---- 场景 93:引用断链反向对账(2026-10-11 dangling-ref,P2-1,灰度 warning)----
|
|
3296
|
+
// 端到端(与「场景未覆盖」共用同一次解析):验收标准引用 spec 未声明的场景 ID → WARN 场景引用断链
|
|
3297
|
+
{
|
|
3298
|
+
// 引用 S1(已声明且覆盖)+ S9(未声明)→ 只出断链,不出未覆盖
|
|
3299
|
+
const T = mkfix();
|
|
3300
|
+
w(T, 'workflow/specs/2026-09-12-dr.md', '---\n状态: approved\n级别: L1\n---\n# SPEC — dr\n## 验收场景(可选)\n### S1 折扣\n- GIVEN x\n- THEN z\n');
|
|
3301
|
+
w(T, 'workflow/intents/2026-09-12-dr.md', INTENT('dr', '状态: done\n级别: L1\n日期: 2026-09-12',
|
|
3302
|
+
'\n## 验收标准(可测试)\n- [x] 场景:S1(证据:a.log)\n- [x] 场景:S9 幻影场景(证据:ghost.log)\n'));
|
|
3303
|
+
w(T, 'workflow/plans/2026-09-12-dr.md', PLAN('dr', '状态: done\n级别: L1'));
|
|
3304
|
+
const r = run(T);
|
|
3305
|
+
check('场景 93a 引用 spec 未声明的 S9 → WARN 场景引用断链(不阻断)',
|
|
3306
|
+
r.status === 0 && outOf(r).includes('场景引用断链') && outOf(r).includes('S9'),
|
|
3307
|
+
`exit=${r.status}\n${outOf(r).slice(0, 500)}`);
|
|
3308
|
+
check('场景 93b 已覆盖的 S1 不触发未覆盖(两方向互不误伤)',
|
|
3309
|
+
!outOf(r).includes('场景未覆盖'), outOf(r).slice(0, 400));
|
|
3310
|
+
}
|
|
3311
|
+
{
|
|
3312
|
+
// 引用与声明完全一致 → 无断链
|
|
3313
|
+
const T = mkfix();
|
|
3314
|
+
w(T, 'workflow/specs/2026-09-12-dr.md', '---\n状态: approved\n级别: L1\n---\n# SPEC — dr\n## 验收场景(可选)\n### S1 折扣\n- GIVEN x\n- THEN z\n### S2 退款\n- GIVEN a\n- THEN b\n');
|
|
3315
|
+
w(T, 'workflow/intents/2026-09-12-dr.md', INTENT('dr', '状态: done\n级别: L1\n日期: 2026-09-12',
|
|
3316
|
+
'\n## 验收标准(可测试)\n- [x] 场景:S1(证据:a.log)\n- [x] 场景:S2(证据:b.log)\n'));
|
|
3317
|
+
w(T, 'workflow/plans/2026-09-12-dr.md', PLAN('dr', '状态: done\n级别: L1'));
|
|
3318
|
+
const r = run(T);
|
|
3319
|
+
check('场景 93c 引用与声明一致 → 无断链无未覆盖(exit 0)',
|
|
3320
|
+
r.status === 0 && !outOf(r).includes('场景引用断链') && !outOf(r).includes('场景未覆盖'),
|
|
3321
|
+
`exit=${r.status}\n${outOf(r).slice(0, 400)}`);
|
|
3322
|
+
}
|
|
3323
|
+
{
|
|
3324
|
+
// spec 无场景节但 intent 引用 S1 → 同样算断链(引用了不存在的场景)
|
|
3325
|
+
const T = mkfix();
|
|
3326
|
+
w(T, 'workflow/specs/2026-09-12-dr.md', '---\n状态: approved\n级别: L1\n---\n# SPEC — dr\n## 功能行为\n普通 spec,无场景节\n');
|
|
3327
|
+
w(T, 'workflow/intents/2026-09-12-dr.md', INTENT('dr', '状态: done\n级别: L1\n日期: 2026-09-12',
|
|
3328
|
+
'\n## 验收标准(可测试)\n- [x] 场景:S1(证据:a.log)\n'));
|
|
3329
|
+
w(T, 'workflow/plans/2026-09-12-dr.md', PLAN('dr', '状态: done\n级别: L1'));
|
|
3330
|
+
const r = run(T);
|
|
3331
|
+
check('场景 93d spec 无场景节且引用 S1 → 断链 WARN(exit 0)',
|
|
3332
|
+
r.status === 0 && outOf(r).includes('场景引用断链') && outOf(r).includes('S1'),
|
|
3333
|
+
`exit=${r.status}\n${outOf(r).slice(0, 500)}`);
|
|
3334
|
+
}
|
|
3335
|
+
|
|
3026
3336
|
console.log(`\n合计: PASS ${pass} / FAIL ${fail}`);
|
|
3027
|
-
process.exit(fail ? 1 : 0);
|
|
3337
|
+
process.exit(fail ? 1 : 0);
|
|
@@ -39,7 +39,7 @@ import path from 'node:path';
|
|
|
39
39
|
import readline from 'node:readline';
|
|
40
40
|
import { createHash, randomBytes } from 'node:crypto';
|
|
41
41
|
import { spawnSync } from 'node:child_process';
|
|
42
|
-
import { confirmGateFor, doneGateFor, laneOfEntry, laneOfDoc } from './stage-gates.mjs';
|
|
42
|
+
import { confirmGateFor, clarifyGateFor, doneGateFor, laneOfEntry, laneOfDoc } from './stage-gates.mjs';
|
|
43
43
|
import { loadKitPolicy, hasFreshVerifyLine, ledgerChainHash, scopeDigest } from './policy.mjs';
|
|
44
44
|
|
|
45
45
|
// hasFreshVerify(root, doc):读 <root>/.agents/verifications.jsonl 判 24h 窗口绿行(判定单源
|
|
@@ -358,6 +358,19 @@ if (isMain) {
|
|
|
358
358
|
refused++;
|
|
359
359
|
continue;
|
|
360
360
|
}
|
|
361
|
+
// 待澄清项门(2026-10-12 needs-clarification):起草期挂「待澄清」标记**不阻断**(零阻塞),
|
|
362
|
+
// 但 draft→approved 确认时若仍挂着未澄清项则拒绝——确认即「认可这份文档」,文档里还写着
|
|
363
|
+
// 「这里我不知道」,确认就没有意义。与上面的前置门分开出账:拒绝原因必须指向真正的原因
|
|
364
|
+
// (合并会让作者去查「前置」,而前置其实是好的)。
|
|
365
|
+
// ⚠️ 文案锚点硬约束:本段禁止并入上方 gate 分支的输出契约(既有 confirm-doc.test.mjs 断言
|
|
366
|
+
// 锚定「未过逐阶段前置门(stage-gates)」子串),故单列独立分支。
|
|
367
|
+
const cl = clarifyGateFor(abs);
|
|
368
|
+
if (!cl.ok) {
|
|
369
|
+
console.error(`❌ ${doc} 含未澄清项(needs-clarification):${cl.reason}`);
|
|
370
|
+
console.error(` ${cl.hint}(本份未落账、未写盘)`);
|
|
371
|
+
refused++;
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
361
374
|
}
|
|
362
375
|
// done 前置门(2026-09-30 confirm-gate-approved-history):→done 须 git 历史已留 approved 态
|
|
363
376
|
// (先 done 后提交 / 两跳同批提交 → 拒绝;防「确认态缺失」缺陷复发;判据单源见 stage-gates.doneGateFor)。
|
|
@@ -386,6 +386,65 @@ const readLedgerOf = (root) => {
|
|
|
386
386
|
fs.rmSync(root, { recursive: true, force: true });
|
|
387
387
|
}
|
|
388
388
|
|
|
389
|
+
// ---- S19b【待澄清门】spec 挂未澄清项 → 拒绝落账(2026-10-12 needs-clarification)----
|
|
390
|
+
// 夹具沿 S23 的正路入口(台账行 + approved),确保**前置门已过**——否则拒绝原因会指向前置而非待澄清,
|
|
391
|
+
// 用例就测不出新门。断言拒绝文案点名「needs-clarification」且**不含**前置门文案(两门分开出账)。
|
|
392
|
+
{
|
|
393
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'confirm-cl1-'));
|
|
394
|
+
for (const sub of ['intents', 'specs']) fs.mkdirSync(path.join(root, 'workflow', sub), { recursive: true });
|
|
395
|
+
fs.mkdirSync(path.join(root, '.agents'), { recursive: true });
|
|
396
|
+
const fp = 'c'.repeat(64);
|
|
397
|
+
fs.writeFileSync(path.join(root, 'workflow', 'intents', '2026-10-12-n.md'), `---\n状态: approved\n级别: L2\n日期: 2026-10-12\n确认指纹: ${fp.slice(0, 16)}\n---\n# I\n`);
|
|
398
|
+
fs.writeFileSync(path.join(root, '.agents', 'confirmations.jsonl'), JSON.stringify({ ts: '2026-10-12T02:00:00.000Z', doc: 'workflow/intents/2026-10-12-n.md', stage: 'approved', fingerprint: fp, prev: 'draft', source: 'chat-delegated', batch: 'n1', seq: 1, of: 1 }) + '\n');
|
|
399
|
+
const specP = path.join(root, 'workflow', 'specs', '2026-10-12-n.md');
|
|
400
|
+
fs.writeFileSync(specP, '---\n状态: draft\n级别: L2\n---\n# S\n\n## 功能行为\n- 待澄清:错误率的判据是 5xx 占比还是全量?\n');
|
|
401
|
+
const before = fs.readFileSync(specP, 'utf8');
|
|
402
|
+
const r = spawnSync(process.execPath, [CLI, 'workflow/specs/2026-10-12-n.md', '--delegated', '可以'], { cwd: root, encoding: 'utf8' });
|
|
403
|
+
const after = fs.readFileSync(specP, 'utf8');
|
|
404
|
+
const led = readLedgerOf(root);
|
|
405
|
+
check('S19b 待澄清门:挂未澄清项 → exit 2 + 点名行号与内容 + 未落盘 + 零新增台账',
|
|
406
|
+
r.status === 2 && /needs-clarification/.test(r.stderr) && /第8行/.test(r.stderr) && /5xx 占比还是全量/.test(r.stderr)
|
|
407
|
+
&& after === before && led.length === 1,
|
|
408
|
+
JSON.stringify({ status: r.status, stderr: r.stderr, ledger: led.length }));
|
|
409
|
+
check('S19b 待澄清门:拒绝原因不误指向前置门(两门分开出账,否则作者查错地方)',
|
|
410
|
+
!/逐阶段前置门/.test(r.stderr), r.stderr);
|
|
411
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// ---- S19c【待澄清门】解决后(行首改「已澄清」)→ 放行;纯删行亦可 ----
|
|
415
|
+
{
|
|
416
|
+
const mkClarified = (body) => {
|
|
417
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'confirm-cl2-'));
|
|
418
|
+
for (const sub of ['intents', 'specs']) fs.mkdirSync(path.join(root, 'workflow', sub), { recursive: true });
|
|
419
|
+
fs.mkdirSync(path.join(root, '.agents'), { recursive: true });
|
|
420
|
+
const fp = 'd'.repeat(64);
|
|
421
|
+
fs.writeFileSync(path.join(root, 'workflow', 'intents', '2026-10-12-o.md'), `---\n状态: approved\n级别: L2\n日期: 2026-10-12\n确认指纹: ${fp.slice(0, 16)}\n---\n# I\n`);
|
|
422
|
+
fs.writeFileSync(path.join(root, '.agents', 'confirmations.jsonl'), JSON.stringify({ ts: '2026-10-12T02:00:00.000Z', doc: 'workflow/intents/2026-10-12-o.md', stage: 'approved', fingerprint: fp, prev: 'draft', source: 'chat-delegated', batch: 'o1', seq: 1, of: 1 }) + '\n');
|
|
423
|
+
fs.writeFileSync(path.join(root, 'workflow', 'specs', '2026-10-12-o.md'), body);
|
|
424
|
+
return root;
|
|
425
|
+
};
|
|
426
|
+
for (const [name, body] of [
|
|
427
|
+
['改前缀为「已澄清」(保留行、留痕曾不清楚)', '---\n状态: draft\n级别: L2\n---\n# S\n\n- 已澄清:错误率判据 = 5xx 占比(与用户 10-12 对齐)\n'],
|
|
428
|
+
['直接删行', '---\n状态: draft\n级别: L2\n---\n# S\n\n## 功能行为\n- 错误率判据 = 5xx 占比\n'],
|
|
429
|
+
]) {
|
|
430
|
+
const root = mkClarified(body);
|
|
431
|
+
const r = spawnSync(process.execPath, [CLI, 'workflow/specs/2026-10-12-o.md', '--delegated', '可以'], { cwd: root, encoding: 'utf8' });
|
|
432
|
+
const after = fs.readFileSync(path.join(root, 'workflow', 'specs', '2026-10-12-o.md'), 'utf8');
|
|
433
|
+
check(`S19c 待澄清门:${name} → 放行落态`, r.status === 0 && after.includes('状态: approved'),
|
|
434
|
+
JSON.stringify({ status: r.status, stderr: r.stderr }));
|
|
435
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
436
|
+
}
|
|
437
|
+
// intents 不受本门约束(歧义标记的落点是 spec/plan 的设计判据;intent 是需求单,澄清天然发生在此处)
|
|
438
|
+
const root3 = fs.mkdtempSync(path.join(os.tmpdir(), 'confirm-cl3-'));
|
|
439
|
+
fs.mkdirSync(path.join(root3, 'workflow', 'intents'), { recursive: true });
|
|
440
|
+
fs.writeFileSync(path.join(root3, 'workflow', 'intents', '2026-10-12-p.md'), '---\n状态: draft\n级别: L1\n---\n# I\n\n- 待澄清:范围含不含移动端?\n');
|
|
441
|
+
const r3 = spawnSync(process.execPath, [CLI, 'workflow/intents/2026-10-12-p.md', '--delegated', '可以'], { cwd: root3, encoding: 'utf8' });
|
|
442
|
+
check('S19c 待澄清门:intent 不受本门约束(澄清天然发生在需求单处)',
|
|
443
|
+
r3.status === 0 && fs.readFileSync(path.join(root3, 'workflow', 'intents', '2026-10-12-p.md'), 'utf8').includes('状态: approved'),
|
|
444
|
+
JSON.stringify({ status: r3.status, stderr: r3.stderr }));
|
|
445
|
+
fs.rmSync(root3, { recursive: true, force: true });
|
|
446
|
+
}
|
|
447
|
+
|
|
389
448
|
// ---- S20【逐阶段门】入口存在但未确认(draft)→ 拒绝 ----
|
|
390
449
|
{
|
|
391
450
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'confirm-sg2-'));
|