@@ -60,6 +60,13 @@ const {
6060 evaluateStopCompletionGate,
6161 extractLastAssistantMessage
6262} = require ( './lifecycle-stop-gate.cjs' )
63+ const {
64+ shouldHardDenyCpMutation,
65+ classifyPathsForArtifacts,
66+ isStrictProtectedPath,
67+ simpleTaskForbidsPath,
68+ ERROR_CODES : PROCESS_ENFORCEMENT_CODES
69+ } = require ( '../../scripts/lib/process-enforcement.js' )
6370
6471const CONTEXT_ROOT = process . cwd ( )
6572const PAYLOAD_PREVIEW_LIMIT = 160
@@ -72,9 +79,8 @@ const CP2_FILE = '02-技术方案.md'
7279const CP3_FILE = '04-实施计划.md'
7380const CP3_RUNTIME_FILE_THRESHOLD = 5
7481// Dual-Track Closure (PI-154 / PF-171): control-plane source paths that require a bound task+CP when mutated.
75- // Matches package source under repo root (scripts/hooks/skills, host-projections, package.json, host-parity docs).
76- // R10 (20260726-grok-stop-enforcement-honesty): extended host-projections + website host-parity intro.
77- const CONTROL_PLANE_SOURCE_RE = / (?: ^ | [ / \\ ] ) (?: s c r i p t s | h o o k s | i n s t r u c t i o n s | h o s t - p r o j e c t i o n s ) (?: [ / \\ ] | $ ) | (?: ^ | [ / \\ ] ) p a c k a g e \. j s o n $ | (?: ^ | [ / \\ ] ) s k i l l s [ / \\ ] | (?: ^ | [ / \\ ] ) w e b s i t e [ / \\ ] d o c s [ / \\ ] i n t r o [ / \\ ] h o s t - p a r i t y / i
82+ // PF-process-enforcement: full website/docs + skills/mcp/prompts (aligned with process-enforcement STRICT_PROTECTED).
83+ const CONTROL_PLANE_SOURCE_RE = / (?: ^ | [ / \\ ] ) (?: s c r i p t s | h o o k s | i n s t r u c t i o n s | h o s t - p r o j e c t i o n s | m c p | p r o m p t s | a g e n t s ) (?: [ / \\ ] | $ ) | (?: ^ | [ / \\ ] ) p a c k a g e \. j s o n $ | (?: ^ | [ / \\ ] ) p l u g i n \. j s o n $ | (?: ^ | [ / \\ ] ) s k i l l s [ / \\ ] | (?: ^ | [ / \\ ] ) w e b s i t e [ / \\ ] d o c s (?: [ / \\ ] | $ ) / i
7884const EXECUTION_MODE = { CONFIRM : 'confirm' , AUTO : 'auto' }
7985const ENFORCEMENT_MODE = ( ( ) => {
8086 const mode = String ( process . env . DEVCODEX_HOOK_ENFORCEMENT || 'safety-only' ) . trim ( ) . toLowerCase ( )
@@ -933,7 +939,7 @@ function checkCpGate(payload, state) {
933939}
934940
935941// Source file extensions that indicate code/config being written
936- const SOURCE_EXT_RE = / \. ( j s | t s | t s x | j s x | m j s | c j s | p y | g o | r s | j a v a | c s | r b | p h p | c | c p p | h | s w i f t | k t | v u e | s v e l t e | c s s | s c s s | l e s s | h t m l | s q l | s h | b a s h | z s h | p s 1 | p s m 1 | j s o n | y a m l | y m l | t o m l | i n i | x m l | e n v ) $ / i
942+ const SOURCE_EXT_RE = / \. ( j s | t s | t s x | j s x | m j s | c j s | p y | g o | r s | j a v a | c s | r b | p h p | c | c p p | h | s w i f t | k t | v u e | s v e l t e | c s s | s c s s | l e s s | h t m l | s q l | s h | b a s h | z s h | p s 1 | p s m 1 | j s o n | y a m l | y m l | t o m l | i n i | x m l | e n v | m d | m d x ) $ / i
937943// F-001/F-037: only governance deployment paths and the active .devcodex namespace are exempt.
938944// This prevents workspace-namespace projects from treating project/.devcodex/.tmp as managed state.
939945const DEVCODEX_DEPLOYMENT_PATH_RE = / ^ (?: \. c l a u d e | \. g i t h u b ) \/ (?: i n s t r u c t i o n s | s k i l l s | h o o k s | a g e n t s | p r o m p t s | s e t t i n g s \. j s o n | s e t t i n g s \. l o c a l \. j s o n | d a t a ) (?: \/ | $ ) | ^ A G E N T S \. m d $ | ^ \. a g e n t s \/ s k i l l s (?: \/ | $ ) | ^ \. c o d e x \/ (?: h o o k s \. j s o n | h o o k s ) (?: \/ | $ ) | ^ c o d e x \/ (?: h o o k s \. j s o n | h o o k s ) (?: \/ | $ ) /
@@ -1458,13 +1464,54 @@ async function main() {
14581464 return
14591465 }
14601466
1467+ // 2.5 ArtifactPathGate — requirements/02|04 slot semantics (always hard when invalid)
1468+ if ( isSourceCodeMutation ( payload , platform , state ) || isProductArtifactMutation ( payload , platform ) ) {
1469+ const toolPaths = extractToolPaths ( payload )
1470+ const art = classifyPathsForArtifacts ( toolPaths )
1471+ if ( ! art . ok ) {
1472+ state . lastReason = art . code || 'ARTIFACT_PATH_INVALID'
1473+ saveState ( state )
1474+ writeStdout ( buildInterceptionOutput (
1475+ state , platform , eventName , INTERCEPTION_ACTION . REQUIRE_COMPLETION , art . code || 'ARTIFACT_PATH_INVALID' ,
1476+ `Artifact path denied: ${ art . code } ` ,
1477+ art . message || 'Illegal requirements artifact path.' ,
1478+ 'Place analysis reports under reports/analysis/…; reserve 02- for technical design.'
1479+ ) )
1480+ return
1481+ }
1482+ }
1483+
1484+ // 2.6 SimpleTask path forbid — website/docs + control-plane protected paths (D2)
1485+ if (
1486+ ( state . simpleTaskFastPath === true || state . taskPathMode === 'simple' ) &&
1487+ isSourceCodeMutation ( payload , platform , state )
1488+ ) {
1489+ const toolPaths = extractToolPaths ( payload )
1490+ const forbidden = toolPaths . find ( p => simpleTaskForbidsPath ( p ) )
1491+ if ( forbidden ) {
1492+ state . lastReason = PROCESS_ENFORCEMENT_CODES . SIMPLE_TASK_PATH_FORBIDDEN
1493+ saveState ( state )
1494+ writeStdout ( buildInterceptionOutput (
1495+ state , platform , eventName , INTERCEPTION_ACTION . REQUIRE_COMPLETION ,
1496+ PROCESS_ENFORCEMENT_CODES . SIMPLE_TASK_PATH_FORBIDDEN ,
1497+ 'SimpleTask path forbidden' ,
1498+ `SimpleTaskFastPath may not mutate protected path: ${ forbidden } ` ,
1499+ 'Upgrade to full CP task, or keep changes outside website/docs and control-plane sources.'
1500+ ) )
1501+ return
1502+ }
1503+ }
1504+
14611505 // 3. CP gate — block source code mutations until checkpoints confirmed
1462- // payload-aware: when tool paths target specific requirement dirs, only that requirement's CP is checked
1463- // R10: reuse checkCpGate; safety-only allows write but Honesty discloses cp2-unconfirmed-write
1506+ // PF-process-enforcement: hard-deny for strict-protected paths even under safety- only (D1)
1507+ // Non-protected paths: legacy safety-only warning + Honesty cp2-unconfirmed-write
14641508 const gate = checkCpGate ( payload , state )
14651509 if ( gate && isSourceCodeMutation ( payload , platform , state ) ) {
1466- state . lastReason = `cp-gate-${ gate . phase } `
1467- if ( ! isStrictEnforcement ( ) && ( gate . phase === 'CP2' || gate . code === 'cp-gate-orphan-control-plane' ) ) {
1510+ const toolPaths = extractToolPaths ( payload )
1511+ const hard = shouldHardDenyCpMutation ( gate , toolPaths , { strictEnv : isStrictEnforcement ( ) } )
1512+ const useHardDeny = hard . hardDeny === true
1513+ state . lastReason = `cp-gate-${ gate . phase } ${ useHardDeny ? '-hard' : '-warn' } `
1514+ if ( ! useHardDeny && ( gate . phase === 'CP2' || gate . code === 'cp-gate-orphan-control-plane' ) ) {
14681515 const honesty = state . enforcementHonesty && typeof state . enforcementHonesty === 'object'
14691516 ? { ...state . enforcementHonesty }
14701517 : { }
@@ -1479,8 +1526,21 @@ async function main() {
14791526 }
14801527 state . enforcementHonesty = honesty
14811528 }
1529+ if ( useHardDeny ) {
1530+ const honesty = state . enforcementHonesty && typeof state . enforcementHonesty === 'object'
1531+ ? { ...state . enforcementHonesty }
1532+ : { }
1533+ honesty . thisTurn = {
1534+ ...( honesty . thisTurn || { } ) ,
1535+ preToolHardDeny : true ,
1536+ preToolSafetyOnlyAllow : false ,
1537+ cpGatePhase : gate . phase ,
1538+ processEnforcementReason : hard . reason
1539+ }
1540+ state . enforcementHonesty = honesty
1541+ }
14821542 saveState ( state )
1483- writeStdout ( isStrictEnforcement ( )
1543+ writeStdout ( useHardDeny
14841544 ? buildCpDenyOutput ( state , platform , eventName , gate , getToolName ( payload ) || 'tool' )
14851545 : buildCpWarningOutput ( state , platform , eventName , gate , getToolName ( payload ) || 'tool' ) )
14861546 return
0 commit comments