33/**
44 * Stop completion / process gate (Grok Stop hard-continue + process gaps).
55 * Spec: requirements/20260726-grok-stop-enforcement-honesty/02-技术方案.md §3 / §9b
6+ * R11 processGaps (canonical):
7+ * entry-check-missing | completion-check-missing | final-validation-summary |
8+ * report-missing | memory-missing | pr1-skipped | cp2-unconfirmed-write |
9+ * stop-continuation-exhausted
610 */
711
812const path = require ( 'path' )
1519 analyzeFinalValidationSummarySample = ( ) => ( { classification : 'not-claimed' , status : 'not-claimed' } )
1620}
1721
18- function extractLastAssistantMessage ( payload ) {
22+ function extractLastAssistantMessage ( payload ) {
1923 if ( ! payload || typeof payload !== 'object' ) return ''
2024 const direct = payload . lastAssistantMessage || payload . last_assistant_message || payload . assistantMessage
2125 if ( typeof direct === 'string' && direct . trim ( ) ) return direct
@@ -26,27 +30,43 @@ function extractLastAssistantMessage(payload) {
2630 return ''
2731}
2832
29- function completionClaimed ( text ) {
33+ function completionClaimed ( text ) {
3034 return / 完 成 检 查 | c o m p l e t i o n - c h e c k | C o m p l e t i o n E v i d e n c e G a t e | 已 完 成 | 已 收 口 | 宣 告 完 成 | 任 务 完 成 | F i n a l V a l i d a t i o n S u m m a r y | 全 绿 | S C 1 5 / i. test ( text || '' )
3135}
3236
33- function askingCp2Confirm ( text ) {
34- return / 确 认 \s * C P 2 | C P 2 \s * 确 认 | 请 确 认 (?: 技 术 方 案 | 方 案 ) | 确 认 (?: 本 | 该 ) ? 技 术 方 案 | 确 认 \s * C P 2 / i. test ( text || '' )
37+ function askingCp2Confirm ( text ) {
38+ // F-05: cover confirm-request presentation, not only "确认 CP2" shorthand
39+ return / 确 认 \s * C P 2 | C P 2 \s * 确 认 | 请 确 认 (?: 技 术 方 案 | 方 案 ) | 确 认 (?: 本 | 该 ) ? 技 术 方 案 | 确 认 \s * C P 2 | 技 术 方 案 \s * 待 确 认 | 请 求 确 认 \s * C P 2 | C P 2 \s * ( ? 待 确 认 ) ? / i. test ( text || '' )
3540}
3641
37- function hasEntryCheck ( text ) {
38- return / # # # \s * D e v C o d e x \s * · \s * 入 口 检 查 | P C 0 \s * [ | : : ] | P C 0 ~ P C 7 | 入 口 检 查 块 / i. test ( text || '' )
42+ function hasEntryCheck ( text ) {
43+ return / # # # \s * D e v C o d e x \s * · \s * 入 口 检 查 | P C 0 \s * [ | : : ] | P C 0 ~ P C 7 | 入 口 检 查 块 | D e v C o d e x V i s i b l e E n v e l o p e V 1 \s * · \s * e n t r y - c h e c k / i. test ( text || '' )
3944}
4045
41- function findActiveTaskRoot ( state ) {
46+ /** F-14/F-16: standard completion-check block present */
47+ function hasCompletionCheck ( text ) {
48+ return (
49+ / # # # \s * D e v C o d e x \s * · \s * 完 成 检 查 / i. test ( text || '' ) ||
50+ / D e v C o d e x V i s i b l e E n v e l o p e V 1 \s * · \s * c o m p l e t i o n - c h e c k / i. test ( text || '' ) ||
51+ / 🛡 ️ \s * D E V \s * 模 式 \s * \| \s * 合 规 检 查 / i. test ( text || '' )
52+ )
53+ }
54+
55+ /**
56+ * F-08: skip report/memory hard gaps when explicit N/A or simple/probe skipReason.
57+ */
58+ function artifactGapsExempt ( text ) {
59+ return / S i m p l e T a s k F a s t P a t h | 报 告 \s * [ : : ] \s * N \/ A | r e p o r t \s * [ : : = ] \s * N \/ A | 记 忆 \s * [ : : ] \s * N \/ A | m e m o r y \s * [ : : = ] \s * N \/ A | s k i p R e a s o n \s * [ : : = ] \s * (?: s i m p l e - t a s k | s i m p l e _ t a s k | p r o b e | t e m p | t m p ) | 产 物 \s * N \/ A \s * \+ \s * s k i p R e a s o n / i. test ( text || '' )
60+ }
61+
62+ function findActiveTaskRoot ( state ) {
4263 try {
4364 const root = state ?. activeNamespaceRoot || state ?. workspaceRoot || process . cwd ( )
4465 const req = path . join ( root , 'requirements' )
4566 if ( ! fs . existsSync ( req ) ) return null
4667 const names = fs . readdirSync ( req , { withFileTypes : true } )
4768 . filter ( d => d . isDirectory ( ) && ! d . name . startsWith ( '.' ) )
4869 . map ( d => d . name )
49- // Prefer most recently modified task with 02-技术方案
5070 let best = null
5171 let bestM = 0
5272 for ( const name of names ) {
@@ -65,15 +85,29 @@ function findActiveTaskRoot(state) {
6585 }
6686}
6787
68- function pr1EvidenceOk ( taskRoot ) {
88+ /**
89+ * F-04: PR-1 pass evidence must be strong (zero-blocker / explicit pass row).
90+ * Rejects loose "某某通过" alone.
91+ */
92+ function pr1EvidenceOk ( taskRoot ) {
6993 if ( ! taskRoot || ! fs . existsSync ( taskRoot ) ) return false
7094 try {
7195 const files = fs . readdirSync ( taskRoot )
72- const review = files . find ( f => / ^ 0 3 - .* 方 案 复 审 / i. test ( f ) || / ^ 0 4 - .* 方 案 复 审 / i. test ( f ) || / 方 案 复 审 / i. test ( f ) )
96+ const review = files . find ( f =>
97+ / ^ 0 3 - .* 方 案 复 审 / i. test ( f ) ||
98+ / ^ 0 4 - .* 方 案 复 审 / i. test ( f ) ||
99+ / 方 案 复 审 / i. test ( f )
100+ )
73101 if ( review ) {
74102 const body = fs . readFileSync ( path . join ( taskRoot , review ) , 'utf8' )
75- if ( / P R - 1 / i. test ( body ) && ( / z e r o \s * b l o c k e r | o p e n b l o c k e r \s * = \s * 0 | ✅ \s * 通 过 | 通 过 / i. test ( body ) ) ) return true
76- if ( / P R - 1 / i. test ( body ) && / 通 过 / . test ( body ) && ! / 不 通 过 | 阻 断 .* P R - 1 / i. test ( body ) ) return true
103+ if ( ! / P R - 1 / i. test ( body ) ) return false
104+ // Strong pass signals only
105+ if ( / o p e n \s * b l o c k e r \s * = \s * 0 | z e r o \s * b l o c k e r | b l o c k e r s ? \s * = \s * 0 / i. test ( body ) ) return true
106+ if ( / P R - 1 [ ^ \n ] { 0 , 40 } (?: ✅ \s * 通 过 | 通 过 \s * ✅ | = \s * p a s s | : \s * p a s s ) / i. test ( body ) ) return true
107+ if ( / 阶 段 一 [ ^ \n ] { 0 , 30 } P R - 1 [ ^ \n ] { 0 , 30 } ✅ / i. test ( body ) ) return true
108+ if ( / P R - 1 \s * [ | : : ] \s * ✅ / i. test ( body ) ) return true
109+ // Explicit fail
110+ if ( / P R - 1 [ ^ \n ] { 0 , 40 } (?: 不 通 过 | 阻 断 | f a i l | ❌ ) / i. test ( body ) ) return false
77111 }
78112 const sessions = path . join ( taskRoot , '.memory' , 'sessions.md' )
79113 if ( fs . existsSync ( sessions ) ) {
@@ -86,15 +120,15 @@ function pr1EvidenceOk(taskRoot) {
86120 return false
87121}
88122
89- function hasTechDesign ( taskRoot ) {
123+ function hasTechDesign ( taskRoot ) {
90124 if ( ! taskRoot ) return false
91125 return fs . existsSync ( path . join ( taskRoot , '02-技术方案.md' ) )
92126}
93127
94128/**
95129 * @returns {{ decision: 'allow'|'block'|'unverified', gaps: string[], reason: string, honesty: object } }
96130 */
97- function evaluateStopCompletionGate ( input = { } ) {
131+ function evaluateStopCompletionGate ( input = { } ) {
98132 const {
99133 mode = '' ,
100134 workflow = '' ,
@@ -116,7 +150,8 @@ function evaluateStopCompletionGate(input = {}) {
116150 }
117151
118152 const wf = String ( workflow || '' ) . toLowerCase ( )
119- if ( ( wf === 'chat' || mode === 'chat' ) && ! mutated ) {
153+ const modeL = String ( mode || '' ) . toLowerCase ( )
154+ if ( ( wf === 'chat' || modeL === 'chat' ) && ! mutated ) {
120155 return { decision : 'allow' , gaps : [ ] , reason : '' , honesty }
121156 }
122157
@@ -143,35 +178,50 @@ function evaluateStopCompletionGate(input = {}) {
143178 }
144179
145180 const gaps = [ ]
146- const nonChatWork = [ 'dev' , 'fix' , 'self-fix' ] . includes ( String ( mode ) . toLowerCase ( ) )
181+ const nonChatWork = [ 'dev' , 'fix' , 'self-fix' ] . includes ( modeL )
147182 || [ 'dev' , 'fix' , 'self-fix' ] . includes ( wf )
148183 || mutated
149184 || reportTouched
150185
151186 if ( nonChatWork ) {
152- if ( ! hasEntryCheck ( text ) ) gaps . push ( 'entry-check' )
187+ // F-07: R11 canonical names
188+ if ( ! hasEntryCheck ( text ) ) gaps . push ( 'entry-check-missing' )
189+
190+ // F-16: completion-check block required for non-chat work that claims complete or mutates under dev/fix
191+ const needCompletionBlock = completionClaimed ( text )
192+ || reportTouched
193+ || [ 'dev' , 'fix' , 'self-fix' ] . includes ( modeL )
194+ || [ 'dev' , 'fix' , 'self-fix' ] . includes ( wf )
195+ || mutated
196+ if ( needCompletionBlock && ! hasCompletionCheck ( text ) ) {
197+ gaps . push ( 'completion-check-missing' )
198+ }
199+
153200 const summary = analyzeFinalValidationSummarySample ( text )
154201 const classif = summary . classification || summary . status || 'not-claimed'
155202 if (
156203 ( completionClaimed ( text ) || reportTouched || mutated )
157204 && ( classif === 'not-claimed' || classif === 'thin-green-summary' || classif === 'report-link-only' || summary . status === 'verified-missing' )
158205 ) {
159206 if ( classif === 'not-claimed' && ! completionClaimed ( text ) && ! reportTouched && mutated ) {
160- // mutated but no completion claim: still require FVS only if completion-ish; Q1 focuses entry-check
207+ // mutated without completion claim: entry + completion-check cover Q1; FVS optional until claim
161208 } else if ( completionClaimed ( text ) || reportTouched ) {
162209 gaps . push ( 'final-validation-summary' )
163- } else if ( mutated && summary . status === 'verified-missing' ) {
210+ } else if ( mutated && summary . status === 'verified-missing' && hasCompletionCheck ( text ) ) {
164211 gaps . push ( 'final-validation-summary' )
165212 }
166213 }
167- // Stronger: if completion claimed and thin/not claimed
168214 if ( completionClaimed ( text ) && ( classif === 'not-claimed' || classif === 'thin-green-summary' || summary . status === 'verified-missing' ) ) {
169215 if ( ! gaps . includes ( 'final-validation-summary' ) ) gaps . push ( 'final-validation-summary' )
170216 }
171- if ( mutated && ! reportTouched && wf !== 'chat' ) gaps . push ( 'report' )
172- if ( mutated && ! memoryTouched && wf !== 'chat' ) gaps . push ( 'memory' )
217+
218+ // F-08: report/memory with explicit N/A / SimpleTask / probe exemption
219+ const exempt = artifactGapsExempt ( text )
220+ if ( mutated && ! reportTouched && wf !== 'chat' && ! exempt ) gaps . push ( 'report-missing' )
221+ if ( mutated && ! memoryTouched && wf !== 'chat' && ! exempt ) gaps . push ( 'memory-missing' )
173222 }
174223
224+ // F-05 / R9: CP2 confirm request without PR-1 strong evidence
175225 const taskRoot = input . taskRoot || findActiveTaskRoot ( input . state )
176226 if ( askingCp2Confirm ( text ) && hasTechDesign ( taskRoot ) && ! pr1EvidenceOk ( taskRoot ) ) {
177227 gaps . push ( 'pr1-skipped' )
@@ -198,5 +248,7 @@ module.exports = {
198248 pr1EvidenceOk,
199249 completionClaimed,
200250 hasEntryCheck,
251+ hasCompletionCheck,
252+ artifactGapsExempt,
201253 findActiveTaskRoot
202254}
0 commit comments