Skip to content

Commit 68d96bc

Browse files
committed
feat(hooks): enforce ecr-missing on strong completion claims
Add classifyEcrClosure for N6 ECR evidence, Stop gap ecr-missing, tests on stop-gate and control-plane chains, and report-dev note that test green is not ECR. Closes ECR completion-claim forced-closure fix implementation.
1 parent 8910712 commit 68d96bc

8 files changed

Lines changed: 189 additions & 4 deletions

File tree

changelogs/unreleased.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
66
## 当前未发布实现候选
77

8+
- **ECR 完成宣称强制闭环(ecr-missing)**`classifyEcrClosure`;强完成/关需求无 ECR/DoD 证据 → Stop gap `ecr-missing``npm run test:ecr-closure` 挂 stop-gate 与 control-plane;report-dev 标明测试绿≠ECR。需求:`ECR与完成宣称强制闭环修复`
89
- **Agent 交付诚实门(delivery-honesty)**`classifyDeliveryHonesty` → gap `stage-report-missing` / `progress-overclaim`;Stop 接线;PreTool 有 04 无 05 时禁控制面写;`npm run test:delivery-honesty` 挂 stop-gate 与 control-plane;进度模板 DeliveryHonestyMatrix。需求:`Agent交付诚实与过程产物强制修复`
910
- **维护者站点×完整功能清单对账门(docs-surface-inventory)**:新增 `scripts/lib/docs-surface-inventory.js` + `npm run test:docs-surface-inventory`;校验 Skills84/plugin 双向、workflows×8(含 other)、MCP15、Hooks5/runtime、prompts30、instructions15、website≥156、process-enforcement 文件与 script;挂入 `test:control-plane` 与 validation-manifest 节点 `docs-surface-inventory` / `process-enforcement-e2e`(fast/full)。public-text-surfaces 扩展 guide/index 与 host-parity-grok。intro 增加能力证据三列表。
1011
- **全宿主流程强制 + 产物路径准确(process-enforcement)**:新增 `scripts/lib/process-enforcement.js`(MutationCpGate 受保护路径 hard-deny、ArtifactPathGate、复审清单/过程包齐套判定)与 `host-enforcement-matrix.js`(五宿主策略真相源);`lifecycle.cjs` PreTool 接线;`lifecycle-stop-gate.cjs` 追加 `review-checklist-missing` / `process-artifact-incomplete` / `progress-artifact-missing``npm run test:process-enforcement-e2e` 并入 `test:stop-gate``test:control-plane`。文档:`02-output-paths` ArtifactPathGate 行、README 流程强制条;配套 PI-REQ-ASSESS 需求模板评估字段。

hooks/_runtime/lifecycle-stop-gate.cjs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,19 @@ try {
2222
let classifyReviewChecklistCompletion
2323
let classifyProcessArtifactCompleteness
2424
let classifyDeliveryHonesty
25+
let classifyEcrClosure
2526
try {
2627
;({
2728
classifyReviewChecklistCompletion,
2829
classifyProcessArtifactCompleteness,
29-
classifyDeliveryHonesty
30+
classifyDeliveryHonesty,
31+
classifyEcrClosure
3032
} = require('../../scripts/lib/process-enforcement.js'))
3133
} catch {
3234
classifyReviewChecklistCompletion = () => ({ ok: true, code: null, gap: null })
3335
classifyProcessArtifactCompleteness = () => ({ ok: true, code: null, gap: null, missing: [] })
3436
classifyDeliveryHonesty = () => ({ ok: true, gaps: [], code: null, gap: null })
37+
classifyEcrClosure = () => ({ ok: true, code: null, gap: null })
3538
}
3639

3740
function extractLastAssistantMessage (payload) {
@@ -286,6 +289,18 @@ function evaluateStopCompletionGate (input = {}) {
286289
}
287290
}
288291

292+
// ECR / N6 execution-closure evidence (ecr-missing)
293+
if (typeof classifyEcrClosure === 'function') {
294+
const ecr = classifyEcrClosure({
295+
text,
296+
mode: modeL || mode,
297+
workflow: wf || workflow
298+
})
299+
if (!ecr.ok && ecr.gap && !gaps.includes(ecr.gap)) {
300+
gaps.push(ecr.gap)
301+
}
302+
}
303+
289304
const uniqueGaps = [...new Set(gaps)]
290305
honesty.processGaps = uniqueGaps
291306

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888
"test:package-release": "node scripts/run-validation.js --route package-release",
8989
"test:control-plane-module-contracts": "node scripts/test-control-plane-module-contracts.js",
9090
"test:closure-evidence": "node scripts/test-closure-evidence-controls.js",
91-
"test:control-plane": "npm run test:probe-registry && npm run test:cli-command-registry && npm run test:cli-json-contract && npm run test:governance-status-summary && npm run test:always-on-governance && npm run test:intent-consistency && npm run test:local-probe && npm run test:context-read && npm run test:context-binding && npm run test:context-read-controls && npm run test:tenant-selection && npm run test:workflow-capabilities && npm run test:api-verification-contracts && npm run test:git-index-isolation && npm run test:control-plane-contracts && npm run test:markdown-structure && npm run test:control-plane-module-contracts && npm run test:consumer-evolution-controls && npm run test:residual-absorption-controls && npm run test:brand-visual-quality && npm run test:turn-liveness && npm run test:closure-evidence && npm run test:workflow-completion && npm run test:public-text-integrity && npm run test:external-review-claim && npm run test:candidate-review-bundle && npm run test:capability-surface-decision && npm run test:host-capability-routing && npm run test:requirement-parallel-orchestration && npm run test:evidence-freshness && npm run test:optimization-backlog-evidence && npm run test:discipline-execution && npm run test:skill-resolve && npm run test:process-enforcement-e2e && npm run test:docs-surface-inventory && npm run test:delivery-honesty",
91+
"test:control-plane": "npm run test:probe-registry && npm run test:cli-command-registry && npm run test:cli-json-contract && npm run test:governance-status-summary && npm run test:always-on-governance && npm run test:intent-consistency && npm run test:local-probe && npm run test:context-read && npm run test:context-binding && npm run test:context-read-controls && npm run test:tenant-selection && npm run test:workflow-capabilities && npm run test:api-verification-contracts && npm run test:git-index-isolation && npm run test:control-plane-contracts && npm run test:markdown-structure && npm run test:control-plane-module-contracts && npm run test:consumer-evolution-controls && npm run test:residual-absorption-controls && npm run test:brand-visual-quality && npm run test:turn-liveness && npm run test:closure-evidence && npm run test:workflow-completion && npm run test:public-text-integrity && npm run test:external-review-claim && npm run test:candidate-review-bundle && npm run test:capability-surface-decision && npm run test:host-capability-routing && npm run test:requirement-parallel-orchestration && npm run test:evidence-freshness && npm run test:optimization-backlog-evidence && npm run test:discipline-execution && npm run test:skill-resolve && npm run test:process-enforcement-e2e && npm run test:docs-surface-inventory && npm run test:delivery-honesty && npm run test:ecr-closure",
9292
"test:tenant-selection": "node scripts/test-tenant-selection.js",
9393
"test:workflow-capabilities": "node scripts/test-workflow-capabilities.js",
9494
"test:api-verification-contracts": "node scripts/test-api-verification-contracts.js",
@@ -113,10 +113,11 @@
113113
"prepublishOnly": "npm run test:all:with-audit",
114114
"test:executable-absorption-gates": "node scripts/test-executable-absorption-gates.js",
115115
"test:skill-resolve": "node scripts/test-skill-resolve.js && node scripts/test-skill-resolution-hook-paths.js && node scripts/test-workspace-skill-auto-match.js",
116-
"test:stop-gate": "node scripts/test-lifecycle-stop-gate.js && node scripts/test-r10-control-plane-honesty.js && node scripts/replay-stop-gate-hook-matrix.js && node scripts/test-process-enforcement-e2e.js && node scripts/test-delivery-honesty.js",
116+
"test:stop-gate": "node scripts/test-lifecycle-stop-gate.js && node scripts/test-r10-control-plane-honesty.js && node scripts/replay-stop-gate-hook-matrix.js && node scripts/test-process-enforcement-e2e.js && node scripts/test-delivery-honesty.js && node scripts/test-ecr-closure.js",
117117
"test:process-enforcement-e2e": "node scripts/test-process-enforcement-e2e.js",
118118
"test:docs-surface-inventory": "node scripts/test-docs-surface-inventory.js",
119119
"test:delivery-honesty": "node scripts/test-delivery-honesty.js",
120+
"test:ecr-closure": "node scripts/test-ecr-closure.js",
120121
"test:skill-automatch-accept": "node scripts/replay-skill-automatch-accept.cjs"
121122
},
122123
"keywords": [
@@ -233,6 +234,7 @@
233234
"scripts/test-process-enforcement-e2e.js",
234235
"scripts/test-docs-surface-inventory.js",
235236
"scripts/test-delivery-honesty.js",
237+
"scripts/test-ecr-closure.js",
236238
"scripts/lib/docs-surface-inventory.js",
237239
"scripts/lib/process-enforcement.js",
238240
"scripts/lib/host-enforcement-matrix.js",

prompts/implementation-progress.prompt.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ applyTo: .devcodex/**/{requirements,bugs}/**; .devcodex/**/fix/**
4949
| progressHonest | 是(每行有证据)/ 否 |
5050
| checklistVisible | 03-复审清单 或 review-checklists 路径 |
5151
| validationEvidence | 命令 + exitCode |
52+
| ecrStatus | pending / done + ECR 报告路径(测试绿≠ECR) |
5253

5354
## 进度总览
5455

prompts/report-dev.prompt.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ applyTo: .devcodex/**/reports/requirements/**
77

88
> **路径**: 优先 `.devcodex/**/requirements/<需求>/reports/<agent>/YYYYMMDD/NN--<name>.md`;无任务上下文时回退到 `.devcodex/**/reports/requirements/<agent>/YYYYMMDD/NN--<name>.md`
99
> **触发**: dev 工作流完成后,由 `report/SKILL.md` 驱动生成
10+
> ⚠️ **测试绿 ≠ ECR**`npm test` / 探针 exit 0 不能代替 **N6 ECR 执行闭环复审**。强完成/关需求话术须含 ECR 段 + DoD 对账表,否则 Stop gap `ecr-missing`
1011
> **共享基模**: `skills/report/report-schema.json` 的 baseFields + dev overlay;治理结果按 `gateGroup / result / evidence / skipReason` 记录
1112
> **字段约束**: 每条遗留问题/建议必须附五项验证(合理性 + 可实施性 + 收益 + 验证状态 + 影响范围),详见 [`17-compliance.instructions.md`](../instructions/17-compliance.instructions.md) §1 输出验证
1213
---

scripts/lib/process-enforcement.js

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ const ERROR_CODES = Object.freeze({
2121
/** Completion claim without discoverable stage report path */
2222
STAGE_REPORT_MISSING: 'stage-report-missing',
2323
/** Over-claim progress without validation evidence rows */
24-
PROGRESS_OVERCLAIM: 'progress-overclaim'
24+
PROGRESS_OVERCLAIM: 'progress-overclaim',
25+
/** Strong completion without ECR / execution-closure review evidence */
26+
ECR_MISSING: 'ecr-missing'
2527
})
2628

2729
/** Paths that use hard-deny for unconfirmed CP even under default safety-only (D1). */
@@ -300,6 +302,51 @@ function classifyDeliveryHonesty (input = {}) {
300302
return { ok: true, gaps: [], code: null, gap: null }
301303
}
302304

305+
/**
306+
* ECR / N6 execution-closure evidence for strong completion claims.
307+
* @param {{ completionClaimed?: boolean, text?: string, mode?: string, workflow?: string }} input
308+
*/
309+
function classifyEcrClosure (input = {}) {
310+
const text = String(input.text || '')
311+
const mode = String(input.mode || '').toLowerCase()
312+
const wf = String(input.workflow || mode || '').toLowerCase()
313+
const strongClaim =
314+
input.completionClaimed === true ||
315+
/||all work is complete|||.*|DoD.*||/i.test(
316+
text
317+
)
318+
319+
if (!strongClaim) return { ok: true, code: null, gap: null }
320+
if (mode === 'chat' || wf === 'chat') return { ok: true, code: null, gap: null }
321+
if (isDeliveryHonestyExempt(text)) return { ok: true, code: null, gap: null }
322+
323+
// Explicit N/A with whitelist skipReason
324+
if (/ECR\s*[:]\s*N\/A/i.test(text) && /skipReason\s*[=:]\s*(chat|simple-task|simple_task|probe)/i.test(text)) {
325+
return { ok: true, code: null, gap: null }
326+
}
327+
328+
const hasEcrHeading = /ECR||Execution\s*Closure\s*Review/i.test(text)
329+
const hasDodMatrix =
330+
/DoD|ECR-[1-7]||ECR\s*|/i.test(text) && /\|/.test(text)
331+
const hasEcrReportPath =
332+
/reports[/\\][^\s)\]`]+ECR[^\s)\]`]*\.md|ECR[^\n]{0,40}\.md||/i.test(text)
333+
334+
if (hasEcrHeading && hasDodMatrix) return { ok: true, code: null, gap: null }
335+
if (hasEcrReportPath && (hasEcrHeading || hasDodMatrix || /DoD\s*/i.test(text))) {
336+
return { ok: true, code: null, gap: null }
337+
}
338+
// Path alone to a report named ECR is enough if strong claim already requires care
339+
if (/reports[/\\][^\s)\]`]*ECR[^\s)\]`]*\.md/i.test(text)) {
340+
return { ok: true, code: null, gap: null }
341+
}
342+
343+
return {
344+
ok: false,
345+
code: ERROR_CODES.ECR_MISSING,
346+
gap: ERROR_CODES.ECR_MISSING
347+
}
348+
}
349+
303350
/**
304351
* Requirement-dir review checklist discoverability (C).
305352
* @param {{ taskRoot?: string|null, fs?: { readdirSync?: Function }, text?: string }} input
@@ -336,6 +383,7 @@ module.exports = {
336383
classifyReviewChecklistCompletion,
337384
classifyProcessArtifactCompleteness,
338385
classifyDeliveryHonesty,
386+
classifyEcrClosure,
339387
classifyReviewChecklistDiscoverability,
340388
simpleTaskForbidsPath,
341389
normalizePath,

scripts/replay-stop-gate-hook-matrix.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ const COMPLETE_BODY = [
6363
'`DevCodexVisibleEnvelopeV1 · completion-check · PASS · ' + 'c'.repeat(64) + '`',
6464
'',
6565
'阶段报告: reports/requirements/grok/20260723/01--stop-gate-replay.md',
66+
'## ECR 执行闭环复审',
67+
'| DoD | 结果 |',
68+
'| D1 | ✅ |',
69+
'| ECR-1 | ✅ |',
6670
'工作已完成并收口。'
6771
].join('\n')
6872

scripts/test-ecr-closure.js

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
'use strict'
2+
3+
/**
4+
* T-ECR: ecr-missing classifier + stop-gate integration samples.
5+
*/
6+
7+
const assert = require('assert')
8+
const {
9+
classifyEcrClosure,
10+
classifyDeliveryHonesty,
11+
ERROR_CODES
12+
} = require('./lib/process-enforcement.js')
13+
const { evaluateStopCompletionGate } = require('../hooks/_runtime/lifecycle-stop-gate.cjs')
14+
15+
// T-ECR-01
16+
{
17+
const r = classifyEcrClosure({
18+
mode: 'dev',
19+
text: '控制面任务已完成 可关闭需求'
20+
})
21+
assert.strictEqual(r.ok, false)
22+
assert.strictEqual(r.gap, ERROR_CODES.ECR_MISSING)
23+
}
24+
25+
// T-ECR-02: green table without ECR
26+
{
27+
const r = classifyEcrClosure({
28+
mode: 'dev',
29+
text: [
30+
'任务完成 可关闭需求',
31+
'| 权威 | npm run test:x | exitCode 0 |',
32+
'全绿'
33+
].join('\n')
34+
})
35+
assert.strictEqual(r.ok, false)
36+
assert.strictEqual(r.gap, ERROR_CODES.ECR_MISSING)
37+
}
38+
39+
// T-ECR-03: ECR + DoD matrix
40+
{
41+
const r = classifyEcrClosure({
42+
mode: 'dev',
43+
text: [
44+
'任务已完成 可关闭需求',
45+
'reports/requirements/grok/20260727/01--ECR-闭环报告.md',
46+
'## ECR 执行闭环复审',
47+
'| DoD | 结果 |',
48+
'| D1 | ✅ |',
49+
'| ECR-1 | ✅ |'
50+
].join('\n')
51+
})
52+
assert.strictEqual(r.ok, true, JSON.stringify(r))
53+
}
54+
55+
// T-ECR-04: chat
56+
{
57+
const r = classifyEcrClosure({
58+
mode: 'chat',
59+
text: '任务完成了 可关闭需求'
60+
})
61+
assert.strictEqual(r.ok, true)
62+
}
63+
64+
// Golden negatives
65+
const bad = [
66+
'需求已完成 可关闭需求',
67+
'dev 已完成 宣告完成',
68+
'DoD 闭环 只差验收',
69+
'本需求闭环 已完成',
70+
'控制面 任务完成 全绿',
71+
'Fix 完成 可关闭需求',
72+
'实施已完成 需求已完成',
73+
'ECR 没写但 可关闭需求'
74+
]
75+
let hits = 0
76+
for (const t of bad) {
77+
const r = classifyEcrClosure({ mode: 'dev', text: t })
78+
if (!r.ok) hits++
79+
}
80+
assert.ok(hits >= 8, `hits=${hits}`)
81+
82+
// Stop integration
83+
{
84+
const r = evaluateStopCompletionGate({
85+
mode: 'dev',
86+
workflow: 'dev',
87+
mutated: true,
88+
reportTouched: true,
89+
memoryTouched: true,
90+
lastAssistantMessage: [
91+
'### DevCodex · 入口检查',
92+
'PC0 | ok',
93+
'### DevCodex · 完成检查',
94+
'| 类型 | 命令 | exitCode |',
95+
'| 权威 | `npm run test:x` | exitCode 0 |',
96+
'reports/x/01--stage.md',
97+
'任务已完成 可关闭需求'
98+
].join('\n')
99+
})
100+
assert.strictEqual(r.decision, 'block')
101+
assert.ok(r.gaps.includes('ecr-missing'), `gaps=${r.gaps.join(',')}`)
102+
}
103+
104+
// Regression: delivery honesty still callable
105+
{
106+
const r = classifyDeliveryHonesty({
107+
mode: 'dev',
108+
text: '控制面已完成 可关闭需求'
109+
})
110+
assert.strictEqual(r.ok, false)
111+
}
112+
113+
console.log('test-ecr-closure: T-ECR samples passed')

0 commit comments

Comments
 (0)