@@ -1421,6 +1421,66 @@ test('TC-N-OVERRIDE-BLOCK: --n 3 requires 2 calls; 1 call blocks despite config
14211421 }
14221422} ) ;
14231423
1424+ // ── TC-PARSE-N: parseQuorumSizeFlag edge cases via integration ─────────────────
1425+ //
1426+ // These tests exercise parseQuorumSizeFlag indirectly through the hook's --n flag
1427+ // handling in the prompt text (GUARD 6 solo mode bypass, N-OVERRIDE path).
1428+
1429+ // TC-PARSE-N-EDGE: --n=5 (equals sign instead of space) is NOT recognized
1430+ // The parseQuorumSizeFlag regex is /\s+--n\s+(\d+)/ — requires whitespace before --n
1431+ // If a user types --n=5 (with equals), the flag is not detected and solo mode
1432+ // is NOT triggered. This tests that --n=5 does NOT trigger --n 1 solo bypass.
1433+ test ( 'TC-PARSE-N-EDGE: --n=5 (equals sign) is NOT recognized as solo mode trigger' , ( ) => {
1434+ const tmpFile = writeTempTranscript ( [
1435+ userLine ( '/qnf:plan-phase 1 --n=5' , 'human-eq' ) ,
1436+ assistantLine ( [ bashCommitBlock ( 'node /path/nf-tools.cjs commit "docs: plan" --files 04-01-PLAN.md' ) ] , 'assistant-commit' ) ,
1437+ assistantLine ( [ { type : 'text' , text : 'Plan with equals syntax.' } ] , 'assistant-final' ) ,
1438+ ] ) ;
1439+ try {
1440+ const { stdout, exitCode } = runHook ( {
1441+ stop_hook_active : false ,
1442+ hook_event_name : 'Stop' ,
1443+ transcript_path : tmpFile ,
1444+ last_assistant_message : 'Plan with equals syntax.' ,
1445+ } ) ;
1446+ // --n=5 is not recognized, so solo mode (--n 1) is NOT triggered
1447+ // The prompt contains a quorum command + decision turn + no quorum calls
1448+ // → should block (not pass silently)
1449+ assert . strictEqual ( exitCode , 0 , 'exit code must be 0 even when blocking' ) ;
1450+ assert . ok ( stdout . length > 0 , 'stdout must contain block decision — --n=5 not recognized as --n flag' ) ;
1451+ const parsed = JSON . parse ( stdout ) ;
1452+ assert . strictEqual ( parsed . decision , 'block' , 'decision must be block — --n=5 does not trigger solo bypass' ) ;
1453+ } finally {
1454+ fs . unlinkSync ( tmpFile ) ;
1455+ }
1456+ } ) ;
1457+
1458+ // TC-PARSE-N-ZERO: --n 0 should NOT trigger solo mode (n must be >= 1)
1459+ // GUARD 6 solo mode requires quorumSizeOverride === 1. --n 0 → n=0 → null (fails n>=1 check)
1460+ test ( 'TC-PARSE-N-ZERO: --n 0 is invalid and does NOT trigger solo mode bypass' , ( ) => {
1461+ const tmpFile = writeTempTranscript ( [
1462+ userLine ( '/qnf:plan-phase 1 --n 0' , 'human-zero' ) ,
1463+ assistantLine ( [ bashCommitBlock ( 'node /path/nf-tools.cjs commit "docs: plan" --files 04-01-PLAN.md' ) ] , 'assistant-commit' ) ,
1464+ assistantLine ( [ { type : 'text' , text : 'Plan with --n 0.' } ] , 'assistant-final' ) ,
1465+ ] ) ;
1466+ try {
1467+ const { stdout, exitCode } = runHook ( {
1468+ stop_hook_active : false ,
1469+ hook_event_name : 'Stop' ,
1470+ transcript_path : tmpFile ,
1471+ last_assistant_message : 'Plan with --n 0.' ,
1472+ } ) ;
1473+ // --n 0 → parseQuorumSizeFlag returns null (n=0 fails n>=1 check)
1474+ // Solo mode not triggered, and no quorum calls made → should block
1475+ assert . strictEqual ( exitCode , 0 , 'exit code must be 0 even when blocking' ) ;
1476+ assert . ok ( stdout . length > 0 , 'stdout must contain block decision — --n 0 is invalid' ) ;
1477+ const parsed = JSON . parse ( stdout ) ;
1478+ assert . strictEqual ( parsed . decision , 'block' , 'decision must be block — --n 0 is not valid solo mode' ) ;
1479+ } finally {
1480+ fs . unlinkSync ( tmpFile ) ;
1481+ }
1482+ } ) ;
1483+
14241484// ── Profile Guard Tests ─────────────────────────────────────────────────────
14251485
14261486// TC-PROFILE-MINIMAL-EXIT: hook_profile=minimal → nf-stop exits 0 with no output (profile guard)
0 commit comments