@@ -283,6 +283,8 @@ function buildLifecycleVisibleReplyUtils(ctx) {
283283 }
284284 const text = evidence . text
285285 state . visible . payloadObserved = true
286+ // Keep bounded sample for soft reminders / F-08 exemption (Stop path)
287+ state . visible . assistantTextSample = String ( text || '' ) . slice ( 0 , 12000 )
286288 // Accept both legacy and portable envelope precheck markers (W8).
287289 // PF-087: presence alone is not enough — require free-text PC0~PC7 column completeness.
288290 const entryCompleteness = analyzeEntryCheckCompleteness ( text , { mode : state . mode } )
@@ -432,8 +434,13 @@ function buildLifecycleVisibleReplyUtils(ctx) {
432434
433435 function buildClosureReminder ( state , eventName ) {
434436 const items = [ ]
437+ const modeL = String ( state . mode || '' ) . toLowerCase ( )
438+ const chatIdle = ( modeL === 'chat' || String ( state . workflow || '' ) . toLowerCase ( ) === 'chat' ) && ! state . mutated
435439 const precheckStatus = getPrecheckEvidenceStatus ( state )
436- if ( eventName === 'Stop' && precheckStatus === 'verified-missing' ) {
440+ // Pure chat without mutation: do not soft-spam entry-check (gate already allows).
441+ if ( eventName === 'Stop' && chatIdle ) {
442+ // still allow governance intake reminder below
443+ } else if ( eventName === 'Stop' && precheckStatus === 'verified-missing' ) {
437444 const completeness = state . visible ?. entryCheckCompleteness
438445 if ( completeness && completeness . claimed && ! completeness . complete ) {
439446 const detail = [
@@ -493,8 +500,20 @@ function buildLifecycleVisibleReplyUtils(ctx) {
493500 const missing = ( state . visible . artifactMissingItems || [ ] ) . join ( ', ' ) || 'semantic-artifact-items, visible-payload-unobserved'
494501 items . push ( `无法验证最终用户可见回复的产物交付(Stop/PreCompact 未提供可解析 assistant 内容;状态只能为 unverified;missingItems=${ missing } )` )
495502 }
496- if ( state . mutated && ! state . memoryTouched ) items . push ( '记忆文件尚未写入(S05:会话结束前必须写入)' )
497- if ( state . mutated && ! state . reportTouched ) items . push ( '报告文件尚未写入(chat 工作流豁免)' )
503+ // F-08: soft path mirrors stop-gate artifactGapsExempt when final text was observed
504+ let artifactExempt = false
505+ try {
506+ const { artifactGapsExempt } = require ( './lifecycle-stop-gate.cjs' )
507+ artifactExempt = artifactGapsExempt ( String ( state . visible ?. assistantTextSample || '' ) )
508+ } catch {
509+ artifactExempt = false
510+ }
511+ if ( state . mutated && ! state . memoryTouched && ! artifactExempt && ! chatIdle ) {
512+ items . push ( '记忆文件尚未写入(S05:会话结束前必须写入)' )
513+ }
514+ if ( state . mutated && ! state . reportTouched && ! artifactExempt && ! chatIdle ) {
515+ items . push ( '报告文件尚未写入(chat 工作流豁免;可用 报告:N/A + skipReason)' )
516+ }
498517 if ( eventName === 'Stop' && state . visible ?. workflowCompletion && ! state . visible . workflowCompletion . workflowComplete ) {
499518 items . push ( `workflow completion 未提交(phase=${ state . visible . workflowCompletion . completionPhase } ; firstBlocker=${ state . visible . workflowCompletion . firstBlocker || 'none' } )` )
500519 }
0 commit comments