claude-dev-env 2.7.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_shared/CLAUDE.md +1 -0
- package/_shared/advisor/advisor-protocol.md +19 -9
- package/_shared/pr-loop/audit-contract.md +4 -4
- package/_shared/pr-loop/precatch-rubric.md +2 -2
- package/_shared/pr-loop/worker-spawn.md +3 -1
- package/_shared/process-tree/CLAUDE.md +41 -0
- package/_shared/process-tree/scripts/config/process_tree_scripts_constants/__init__.py +1 -0
- package/_shared/process-tree/scripts/config/process_tree_scripts_constants/process_tree_kill_constants.py +27 -0
- package/_shared/process-tree/scripts/process_tree_kill.py +141 -0
- package/_shared/process-tree/scripts/pyproject.toml +16 -0
- package/_shared/process-tree/scripts/test_process_tree_kill.py +278 -0
- package/agents/code-quality-agent.md +6 -5
- package/agents/deep-research.md +7 -24
- package/agents/docs-agent.md +1 -27
- package/agents/issue-tracker.md +1 -7
- package/agents/skill-writer-agent.md +1 -2
- package/agents/test_agent_frontmatter.py +309 -12
- package/hooks/blocking/CLAUDE.md +2 -0
- package/hooks/blocking/fable_spawn_gate.py +187 -0
- package/hooks/blocking/piped_pytest_blocker.py +1223 -0
- package/hooks/blocking/plain_language_blocker.py +287 -15
- package/hooks/blocking/test_fable_spawn_gate.py +374 -0
- package/hooks/blocking/test_piped_pytest_blocker.py +587 -0
- package/hooks/blocking/test_plain_language_blocker.py +277 -2
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +34 -2
- package/hooks/git-hooks/CLAUDE.md +2 -2
- package/hooks/git-hooks/git_hooks_constants/__init__.py +28 -0
- package/hooks/git-hooks/pre_push.py +343 -54
- package/hooks/git-hooks/test_pre_push.py +852 -6
- package/hooks/hooks.json +9 -19
- package/hooks/hooks_constants/CLAUDE.md +2 -0
- package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +1 -0
- package/hooks/hooks_constants/fable_spawn_gate_constants.py +62 -0
- package/hooks/hooks_constants/piped_pytest_blocker_constants.py +360 -0
- package/hooks/hooks_constants/plain_language_blocker_constants.py +64 -1
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +10 -0
- package/hooks/hooks_constants/shell_command_segments.py +1 -1
- package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +1 -0
- package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +27 -0
- package/hooks/hooks_constants/test_prose_metrics_parity.py +124 -0
- package/package.json +1 -1
- package/rules/CLAUDE.md +1 -0
- package/rules/ask-user-question-required.md +26 -0
- package/rules/claims-as-quotes.md +65 -0
- package/scripts/CLAUDE.md +4 -4
- package/scripts/_code_review_test_support.py +6 -0
- package/scripts/check.ps1 +18 -5
- package/scripts/claude_chain_runner.py +203 -31
- package/scripts/codec_forwarding_test_support.py +2 -0
- package/scripts/dev_env_scripts_constants/CLAUDE.md +4 -4
- package/scripts/dev_env_scripts_constants/claude_chain_constants.py +38 -0
- package/scripts/dev_env_scripts_constants/code_review_constants.py +403 -2
- package/scripts/dev_env_scripts_constants/grok_worker_constants.py +83 -13
- package/scripts/grok_headless_runner.py +148 -18
- package/scripts/resolve_worker_spawn.py +56 -10
- package/scripts/spawn_grok_batch.py +81 -23
- package/scripts/test_claude_chain_runner.py +358 -0
- package/scripts/test_grok_headless_runner.py +547 -10
- package/scripts/test_invoke_code_review.py +298 -0
- package/scripts/test_resolve_worker_spawn.py +185 -15
- package/scripts/test_spawn_grok_batch.py +326 -22
- package/scripts/tests/CLAUDE.md +1 -0
- package/scripts/tests/test_grok_worker_constants.py +59 -0
- package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +46 -0
- package/skills/autoconverge/workflow/converge.contract.test.mjs +133 -8
- package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +107 -1
- package/skills/autoconverge/workflow/converge.mjs +113 -31
- package/skills/codex-review/scripts/codex_review_scripts_constants/codex_usage_probe_constants.py +0 -4
- package/skills/codex-review/scripts/codex_usage_probe.py +20 -33
- package/skills/codex-review/scripts/run_codex_review.py +16 -64
- package/skills/codex-review/scripts/test_codex_usage_probe.py +46 -41
- package/skills/codex-review/scripts/test_run_codex_review.py +1 -33
- package/skills/e-code-review/SKILL.md +9 -8
- package/skills/e-code-review/reference/fix.md +29 -7
- package/skills/e-code-review/reference/loop.md +230 -14
- package/skills/e-code-review/reference/low.md +33 -15
- package/skills/e-code-review/reference/medium.md +55 -21
- package/skills/e-code-review/reference/xhigh.md +30 -12
- package/skills/fresh-branch/CLAUDE.md +5 -5
- package/skills/fresh-branch/SKILL.md +14 -6
- package/skills/fresh-branch/scripts/create_fresh_branch.py +122 -39
- package/skills/fresh-branch/scripts/fresh_branch_scripts_constants/fresh_branch_cli_constants.py +10 -3
- package/skills/fresh-branch/scripts/test_create_fresh_branch.py +251 -0
- package/skills/grok-spawn/SKILL.md +10 -3
- package/skills/grok-spawn/reference/flag-profiles.md +3 -1
- package/skills/orchestrator/SKILL.md +4 -1
- package/skills/orchestrator-refresh/SKILL.md +5 -1
- package/skills/team-advisor/SKILL.md +4 -1
|
@@ -411,8 +411,13 @@ test('the shared verdict-fence builder names the binding-hash command and the ve
|
|
|
411
411
|
/verification_verdict_store\.py/,
|
|
412
412
|
'expected the verdict-store script that computes the binding hash to be named',
|
|
413
413
|
);
|
|
414
|
-
assert.match(fenceBuilder,
|
|
415
|
-
|
|
414
|
+
assert.match(fenceBuilder, /buildVerdictFenceTail\(/, 'expected the fence builder to close with the shared fence tail');
|
|
415
|
+
const renderedFenceTail = loadVerdictFenceTailBuilder()('');
|
|
416
|
+
assert.match(renderedFenceTail, /```verdict/, 'expected the verdict fence to be specified');
|
|
417
|
+
assert.ok(
|
|
418
|
+
renderedFenceTail.includes(VERDICT_FENCE_JSON_LINE),
|
|
419
|
+
'expected the verdict fence to carry manifest_sha256',
|
|
420
|
+
);
|
|
416
421
|
assert.match(
|
|
417
422
|
fenceBuilder,
|
|
418
423
|
/gh pr view/,
|
|
@@ -425,6 +430,100 @@ test('the shared verdict-fence builder names the binding-hash command and the ve
|
|
|
425
430
|
);
|
|
426
431
|
});
|
|
427
432
|
|
|
433
|
+
test('the incomplete-verdict contract sentence is written once and used by both verify paths', () => {
|
|
434
|
+
const sentenceOccurrences = convergeSource.split(
|
|
435
|
+
'name that check in prose directly above the fence rather than in findings',
|
|
436
|
+
).length - 1;
|
|
437
|
+
assert.equal(
|
|
438
|
+
sentenceOccurrences,
|
|
439
|
+
1,
|
|
440
|
+
'expected the incomplete-verdict contract sentence to be written exactly once so the two verify paths cannot drift',
|
|
441
|
+
);
|
|
442
|
+
assert.match(
|
|
443
|
+
lensPromptBody('buildVerdictFenceTail'),
|
|
444
|
+
/VERDICT_FENCE_CONTRACT_SENTENCE/,
|
|
445
|
+
'expected the shared fence tail to use the one contract sentence',
|
|
446
|
+
);
|
|
447
|
+
assert.match(
|
|
448
|
+
lensPromptBody('buildVerdictFenceSteps'),
|
|
449
|
+
/buildVerdictFenceTail\(/,
|
|
450
|
+
'expected the gh-lookup fence builder to reach the contract sentence through the shared fence tail',
|
|
451
|
+
);
|
|
452
|
+
assert.match(
|
|
453
|
+
lensPromptBody('runVerifierTask'),
|
|
454
|
+
/buildVerdictFenceTail\(/,
|
|
455
|
+
'expected the hardening-verify prompt to reach the contract sentence through the shared fence tail',
|
|
456
|
+
);
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
const VERDICT_FENCE_JSON_LINE = '{"all_pass": true, "findings": [], "manifest_sha256": "<that hash>"}';
|
|
460
|
+
const VERDICT_FENCE_LEAD_IN = 'END your message with a fenced verdict block exactly in this shape';
|
|
461
|
+
|
|
462
|
+
function countOccurrences(haystack, needle) {
|
|
463
|
+
return haystack.split(needle).length - 1;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function loadVerdictFenceTailBuilder() {
|
|
467
|
+
const sentenceMatch = /const VERDICT_FENCE_CONTRACT_SENTENCE =\s*\n?\s*'[^']*'/.exec(convergeSource);
|
|
468
|
+
assert.notEqual(sentenceMatch, null, 'expected VERDICT_FENCE_CONTRACT_SENTENCE to be declared as one string literal');
|
|
469
|
+
const builderSource = lensPromptBody('buildVerdictFenceTail');
|
|
470
|
+
return new Function(
|
|
471
|
+
`${sentenceMatch[0]}\n${builderSource}\nreturn buildVerdictFenceTail;`,
|
|
472
|
+
)();
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
test('the verdict fence recipe is written once — no verify path re-inlines its own copy', () => {
|
|
476
|
+
assert.equal(
|
|
477
|
+
countOccurrences(convergeSource, VERDICT_FENCE_JSON_LINE),
|
|
478
|
+
1,
|
|
479
|
+
'expected the verdict fence JSON skeleton to be written exactly once so no verify path can drift from the shared recipe',
|
|
480
|
+
);
|
|
481
|
+
assert.equal(
|
|
482
|
+
countOccurrences(convergeSource, VERDICT_FENCE_LEAD_IN),
|
|
483
|
+
1,
|
|
484
|
+
'expected the fence lead-in sentence to be written exactly once so no verify path can drift from the shared recipe',
|
|
485
|
+
);
|
|
486
|
+
assert.match(
|
|
487
|
+
lensPromptBody('buildVerdictFenceSteps'),
|
|
488
|
+
/buildVerdictFenceTail\(/,
|
|
489
|
+
'expected the gh-lookup binding path to render its fence through the shared tail builder',
|
|
490
|
+
);
|
|
491
|
+
assert.match(
|
|
492
|
+
lensPromptBody('runVerifierTask'),
|
|
493
|
+
/buildVerdictFenceTail\(/,
|
|
494
|
+
'expected the hardening-verify path to render its fence through the shared tail builder',
|
|
495
|
+
);
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
test('the shared fence tail renders the same fence JSON and contract sentence at every indent', () => {
|
|
499
|
+
const buildVerdictFenceTail = loadVerdictFenceTailBuilder();
|
|
500
|
+
const topLevelTail = buildVerdictFenceTail('');
|
|
501
|
+
const nestedTail = buildVerdictFenceTail(' ');
|
|
502
|
+
for (const [siteName, renderedTail] of [['top-level', topLevelTail], ['nested', nestedTail]]) {
|
|
503
|
+
assert.ok(
|
|
504
|
+
renderedTail.includes(VERDICT_FENCE_JSON_LINE),
|
|
505
|
+
`expected the ${siteName} fence tail to carry the verdict JSON skeleton`,
|
|
506
|
+
);
|
|
507
|
+
assert.ok(
|
|
508
|
+
renderedTail.includes(VERDICT_FENCE_LEAD_IN),
|
|
509
|
+
`expected the ${siteName} fence tail to carry the fence lead-in sentence`,
|
|
510
|
+
);
|
|
511
|
+
assert.ok(
|
|
512
|
+
renderedTail.includes('```verdict'),
|
|
513
|
+
`expected the ${siteName} fence tail to open a verdict fence`,
|
|
514
|
+
);
|
|
515
|
+
assert.ok(
|
|
516
|
+
renderedTail.includes('name that check in prose directly above the fence rather than in findings'),
|
|
517
|
+
`expected the ${siteName} fence tail to carry the one incomplete-verdict contract sentence`,
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
assert.equal(
|
|
521
|
+
nestedTail,
|
|
522
|
+
topLevelTail.split('\n').map((eachLine) => ` ${eachLine}`).join('\n'),
|
|
523
|
+
'expected indent to be the only difference between the two rendered fence tails',
|
|
524
|
+
);
|
|
525
|
+
});
|
|
526
|
+
|
|
428
527
|
test('the verdict-fence binding does not self-resolve a cwd via git rev-parse for the manifest hash', () => {
|
|
429
528
|
const fenceBuilder = lensPromptBody('buildVerdictFenceSteps');
|
|
430
529
|
assert.doesNotMatch(
|
|
@@ -434,7 +533,7 @@ test('the verdict-fence binding does not self-resolve a cwd via git rev-parse fo
|
|
|
434
533
|
);
|
|
435
534
|
});
|
|
436
535
|
|
|
437
|
-
test('every verify step calls buildVerdictFenceSteps, uses code-verifier, and forbids
|
|
536
|
+
test('every verify step calls buildVerdictFenceSteps, uses code-verifier, and forbids editing the tree under verification', () => {
|
|
438
537
|
for (const verifyFunctionName of ['runVerifierTask']) {
|
|
439
538
|
const verifyBody = lensPromptBody(verifyFunctionName);
|
|
440
539
|
assert.match(
|
|
@@ -454,8 +553,8 @@ test('every verify step calls buildVerdictFenceSteps, uses code-verifier, and fo
|
|
|
454
553
|
);
|
|
455
554
|
assert.match(
|
|
456
555
|
verifyBody,
|
|
457
|
-
/
|
|
458
|
-
`expected ${verifyFunctionName} to
|
|
556
|
+
/(?:no|not|never)[^.\n]{0,30}edit[^.\n]{0,40}tree[^.\n]{0,30}(?:under|being)\s+verif/i,
|
|
557
|
+
`expected ${verifyFunctionName} to forbid editing the tree under verification (a deliberate break off that tree stays allowed)`,
|
|
459
558
|
);
|
|
460
559
|
}
|
|
461
560
|
});
|
|
@@ -467,7 +566,7 @@ test('runFixerTask never verifies — verification belongs to the separate verif
|
|
|
467
566
|
assert.match(fixerBody, /agentType:\s*'clean-coder'/, 'expected the fixer to use clean-coder for its commit and recovery edits');
|
|
468
567
|
});
|
|
469
568
|
|
|
470
|
-
test('runVerifierTask uses --manifest-hash-for-branch with the hardening branch and forbids
|
|
569
|
+
test('runVerifierTask uses --manifest-hash-for-branch with the hardening branch and forbids editing the tree under verification', () => {
|
|
471
570
|
const verifyBody = lensPromptBody('runVerifierTask');
|
|
472
571
|
assert.match(
|
|
473
572
|
verifyBody,
|
|
@@ -476,8 +575,8 @@ test('runVerifierTask uses --manifest-hash-for-branch with the hardening branch
|
|
|
476
575
|
);
|
|
477
576
|
assert.match(
|
|
478
577
|
verifyBody,
|
|
479
|
-
/
|
|
480
|
-
'expected the verifier to
|
|
578
|
+
/(?:no|not|never)[^.\n]{0,30}edit[^.\n]{0,40}tree[^.\n]{0,30}(?:under|being)\s+verif/i,
|
|
579
|
+
'expected the verifier to forbid editing the tree under verification (a deliberate break off that tree stays allowed)',
|
|
481
580
|
);
|
|
482
581
|
});
|
|
483
582
|
|
|
@@ -901,6 +1000,28 @@ test('convergeReadOnlyAgent prepends HEADLESS_READONLY_PREAMBLE and the worktree
|
|
|
901
1000
|
);
|
|
902
1001
|
});
|
|
903
1002
|
|
|
1003
|
+
test('the read-only destructive pointer scopes its no-edit clause to the tree it reads', () => {
|
|
1004
|
+
const destructivePointer = convergeSource
|
|
1005
|
+
.split('\n')
|
|
1006
|
+
.find((eachLine) => eachLine.includes('Never run a destructive command'));
|
|
1007
|
+
assert.ok(destructivePointer, 'expected the read-only destructive pointer to be declared');
|
|
1008
|
+
assert.match(
|
|
1009
|
+
destructivePointer,
|
|
1010
|
+
/Never run a destructive command/,
|
|
1011
|
+
'expected the destructive-command prohibition to stay absolute',
|
|
1012
|
+
);
|
|
1013
|
+
assert.match(
|
|
1014
|
+
destructivePointer,
|
|
1015
|
+
/no edit to the tree it reads/,
|
|
1016
|
+
'expected the no-edit clause to be scoped to the tree under verification',
|
|
1017
|
+
);
|
|
1018
|
+
assert.doesNotMatch(
|
|
1019
|
+
destructivePointer,
|
|
1020
|
+
/edits nothing/,
|
|
1021
|
+
'expected no blanket edits-nothing wording, which forbids the deliberate break off that tree',
|
|
1022
|
+
);
|
|
1023
|
+
});
|
|
1024
|
+
|
|
904
1025
|
const taskDispatchers = [
|
|
905
1026
|
{ name: 'runGitTask', isAsync: false },
|
|
906
1027
|
{ name: 'runFixerTask', isAsync: false },
|
|
@@ -984,6 +1105,7 @@ test('the whole priming spawn-agent family is removed — every dispatcher spawn
|
|
|
984
1105
|
|
|
985
1106
|
test('parseLastVerdictFence returns non-null for a verdict fence with valid JSON', () => {
|
|
986
1107
|
const parseModule = new Function(
|
|
1108
|
+
`${functionSource('findLastVerdictFence')}\n` +
|
|
987
1109
|
`${functionSource('parseLastVerdictFence')}\n` +
|
|
988
1110
|
'return { parseLastVerdictFence };',
|
|
989
1111
|
)();
|
|
@@ -994,6 +1116,7 @@ test('parseLastVerdictFence returns non-null for a verdict fence with valid JSON
|
|
|
994
1116
|
|
|
995
1117
|
test('parseLastVerdictFence returns null for non-string input', () => {
|
|
996
1118
|
const parseModule = new Function(
|
|
1119
|
+
`${functionSource('findLastVerdictFence')}\n` +
|
|
997
1120
|
`${functionSource('parseLastVerdictFence')}\n` +
|
|
998
1121
|
'return { parseLastVerdictFence };',
|
|
999
1122
|
)();
|
|
@@ -1003,6 +1126,7 @@ test('parseLastVerdictFence returns null for non-string input', () => {
|
|
|
1003
1126
|
|
|
1004
1127
|
test('parseLastVerdictFence returns null when no verdict fence is present', () => {
|
|
1005
1128
|
const parseModule = new Function(
|
|
1129
|
+
`${functionSource('findLastVerdictFence')}\n` +
|
|
1006
1130
|
`${functionSource('parseLastVerdictFence')}\n` +
|
|
1007
1131
|
'return { parseLastVerdictFence };',
|
|
1008
1132
|
)();
|
|
@@ -1011,6 +1135,7 @@ test('parseLastVerdictFence returns null when no verdict fence is present', () =
|
|
|
1011
1135
|
|
|
1012
1136
|
test('parseLastVerdictFence returns null for malformed JSON in the fence', () => {
|
|
1013
1137
|
const parseModule = new Function(
|
|
1138
|
+
`${functionSource('findLastVerdictFence')}\n` +
|
|
1014
1139
|
`${functionSource('parseLastVerdictFence')}\n` +
|
|
1015
1140
|
'return { parseLastVerdictFence };',
|
|
1016
1141
|
)();
|
|
@@ -255,9 +255,11 @@ test('the round-loop fix-stalled blockers survive the recovery wiring', () => {
|
|
|
255
255
|
});
|
|
256
256
|
|
|
257
257
|
const verifyObjectionModule = new Function(
|
|
258
|
-
`${functionSource('
|
|
258
|
+
`${functionSource('findLastVerdictFence')}\n` +
|
|
259
|
+
`${functionSource('parseLastVerdictFence')}\n` +
|
|
259
260
|
`${constantLine('VERIFY_OBJECTION_FALLBACK')}\n` +
|
|
260
261
|
`${functionSource('renderVerifyObjectionLine')}\n` +
|
|
262
|
+
`${functionSource('extractPreFenceProse')}\n` +
|
|
261
263
|
`${functionSource('extractVerifyObjection')}\n` +
|
|
262
264
|
'return { extractVerifyObjection, VERIFY_OBJECTION_FALLBACK };',
|
|
263
265
|
)();
|
|
@@ -338,6 +340,110 @@ test('extractVerifyObjection falls back when no finding yields usable text', ()
|
|
|
338
340
|
assert.equal(extractVerifyObjection(transcript), VERIFY_OBJECTION_FALLBACK);
|
|
339
341
|
});
|
|
340
342
|
|
|
343
|
+
test('the prose reader anchors on the last CLOSED fence, not a stray unterminated marker', () => {
|
|
344
|
+
const transcript =
|
|
345
|
+
'check X never showed red\n\n' +
|
|
346
|
+
'```verdict\n{"all_pass": false, "findings": []}\n```\n\n' +
|
|
347
|
+
'and here I started to restate it\n\n' +
|
|
348
|
+
'```verdict';
|
|
349
|
+
const objection = extractVerifyObjection(transcript);
|
|
350
|
+
assert.equal(objection, 'check X never showed red');
|
|
351
|
+
assert.doesNotMatch(objection, /all_pass/, 'expected the verdict body never to be read back as prose');
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
test('both fence readers locate the fence through the one shared locator', () => {
|
|
355
|
+
assert.match(
|
|
356
|
+
functionSource('parseLastVerdictFence'),
|
|
357
|
+
/findLastVerdictFence\(/,
|
|
358
|
+
'expected the verdict parser to locate its fence through findLastVerdictFence',
|
|
359
|
+
);
|
|
360
|
+
assert.match(
|
|
361
|
+
functionSource('extractPreFenceProse'),
|
|
362
|
+
/findLastVerdictFence\(/,
|
|
363
|
+
'expected the prose reader to locate its fence through findLastVerdictFence, not a second search',
|
|
364
|
+
);
|
|
365
|
+
assert.doesNotMatch(
|
|
366
|
+
functionSource('extractPreFenceProse'),
|
|
367
|
+
/lastIndexOf\(/,
|
|
368
|
+
'expected no second fence-search procedure in the prose reader to drift from the parser',
|
|
369
|
+
);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
test('the prose reader and the verdict parser land on the same fence when a later marker carries no newline', () => {
|
|
373
|
+
const transcript =
|
|
374
|
+
'check X never showed red\n\n' +
|
|
375
|
+
'```verdict\n{"all_pass": false, "findings": []}\n```\n\n' +
|
|
376
|
+
'```verdict {"all_pass": true, "findings": []}```';
|
|
377
|
+
const objection = extractVerifyObjection(transcript);
|
|
378
|
+
assert.doesNotMatch(
|
|
379
|
+
objection,
|
|
380
|
+
/all_pass/,
|
|
381
|
+
'expected the prose reader to anchor on the fence the parser read, never to slice a verdict body in as prose',
|
|
382
|
+
);
|
|
383
|
+
assert.equal(objection, 'check X never showed red');
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
test('the prose reader skips a scaffolding-only paragraph above the fence', () => {
|
|
387
|
+
const transcript =
|
|
388
|
+
'check X never showed red\n\n' + '## Verdict\n\n' + '```verdict\n{"all_pass": false, "findings": []}\n```';
|
|
389
|
+
assert.equal(extractVerifyObjection(transcript), 'check X never showed red');
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
const verifyRecoveryPromptModule = new Function(
|
|
393
|
+
`${constantLine('VERIFY_OBJECTION_FALLBACK')}\n` +
|
|
394
|
+
'const prCoordinates = "owner/repo#1";\n' +
|
|
395
|
+
'const PRE_COMMIT_GATE_STEP = "";\n' +
|
|
396
|
+
'const EDIT_SCHEMA = {};\n' +
|
|
397
|
+
'const TIERS = { sonnetMedium: {} };\n' +
|
|
398
|
+
'const convergeAgent = (spawnPrompt) => spawnPrompt;\n' +
|
|
399
|
+
`${functionSource('runCodeEditorTask')}\n` +
|
|
400
|
+
'return { runCodeEditorTask };',
|
|
401
|
+
)();
|
|
402
|
+
|
|
403
|
+
const { runCodeEditorTask: buildCodeEditorPrompt } = verifyRecoveryPromptModule;
|
|
404
|
+
|
|
405
|
+
function buildVerifyRecoveryPrompt(verifyTranscript) {
|
|
406
|
+
return buildCodeEditorPrompt('verify-recover', {
|
|
407
|
+
objection: extractVerifyObjection(verifyTranscript),
|
|
408
|
+
head: 'deadbeefcafe',
|
|
409
|
+
sourceLabel: 'round-1 lens findings',
|
|
410
|
+
attempt: 1,
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const INCOMPLETE_CHECK_SENTENCE =
|
|
415
|
+
'This verdict is incomplete: the deliberate break for check_docstring_runon_sentence never showed red, so that gate is unproven rather than failed.';
|
|
416
|
+
|
|
417
|
+
test('an incomplete verdict carries its named check into the fixer prompt instead of the fallback', () => {
|
|
418
|
+
const transcript =
|
|
419
|
+
'I ran the named gates and read the diff against the task text.\n\n' +
|
|
420
|
+
`${INCOMPLETE_CHECK_SENTENCE}\n\n` +
|
|
421
|
+
'```verdict\n{"all_pass": false, "findings": [], "manifest_sha256": "0f1e2d"}\n```';
|
|
422
|
+
const fixerPrompt = buildVerifyRecoveryPrompt(transcript);
|
|
423
|
+
assert.match(fixerPrompt, /VERIFY-RECOVERY fixer/, 'expected the verify-recovery fixer prompt');
|
|
424
|
+
assert.ok(
|
|
425
|
+
fixerPrompt.includes(INCOMPLETE_CHECK_SENTENCE),
|
|
426
|
+
`expected the fixer prompt to carry the incomplete-check sentence, got:\n${fixerPrompt.slice(0, 600)}`,
|
|
427
|
+
);
|
|
428
|
+
assert.doesNotMatch(
|
|
429
|
+
fixerPrompt,
|
|
430
|
+
/without a parseable verdict/,
|
|
431
|
+
'expected the incomplete verdict never to reach the fixer as VERIFY_OBJECTION_FALLBACK',
|
|
432
|
+
);
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
test('a genuine code defect still reaches the fixer prompt from findings, unchanged by pre-fence prose', () => {
|
|
436
|
+
const transcript =
|
|
437
|
+
`${INCOMPLETE_CHECK_SENTENCE}\n\n` +
|
|
438
|
+
'```verdict\n{"all_pass": false, "findings": [{"check": "Finding 1", "detail": "boundary still over-blocks"}]}\n```';
|
|
439
|
+
const fixerPrompt = buildVerifyRecoveryPrompt(transcript);
|
|
440
|
+
assert.match(fixerPrompt, /1\. Finding 1 — boundary still over-blocks/);
|
|
441
|
+
assert.ok(
|
|
442
|
+
!fixerPrompt.includes(INCOMPLETE_CHECK_SENTENCE),
|
|
443
|
+
'expected findings to keep owning a verdict that names a code defect',
|
|
444
|
+
);
|
|
445
|
+
});
|
|
446
|
+
|
|
341
447
|
test('the verify-recover task in runCodeEditorTask is a clean-coder edit step bound to the verifier objection and leaves changes uncommitted', () => {
|
|
342
448
|
const recoverBody = functionSource('runCodeEditorTask');
|
|
343
449
|
assert.match(recoverBody, /agentType:\s*'clean-coder'/, 'expected the fixer to use clean-coder');
|
|
@@ -68,13 +68,15 @@ const HEADLESS_EDIT_PREAMBLE =
|
|
|
68
68
|
'- When your run was given a result schema, your final action is always the StructuredOutput call. If the poll budget is spent before the awaited signal arrives, call StructuredOutput with the whole time-out result the step documents — for the Copilot gate, the full down result {sha, clean:false, down:true, findings:[]}, never a bare down flag — rather than ending the turn without a result.\n\n'
|
|
69
69
|
|
|
70
70
|
const HEADLESS_READONLY_DESTRUCTIVE_POINTER =
|
|
71
|
-
'- Never run a destructive command (rm -rf, git reset --hard, dd, mkfs, chmod -R, a fork bomb) and never place its literal text in a Bash command: this step
|
|
71
|
+
'- Never run a destructive command (rm -rf, git reset --hard, dd, mkfs, chmod -R, a fork bomb) and never place its literal text in a Bash command: this step makes no edit to the tree it reads, so it needs no destructive command. If a step seems to require one, report it as a blocker rather than running it.\n'
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
74
|
* The read-only preamble a review, verify, or utility agent receives: the full
|
|
75
|
-
* edit preamble with the rm-shape-rules bullet dropped, since an agent that
|
|
76
|
-
*
|
|
77
|
-
*
|
|
75
|
+
* edit preamble with the rm-shape-rules bullet dropped, since an agent that makes
|
|
76
|
+
* no edit in the tree it reads never runs rm against that tree, and the one file
|
|
77
|
+
* it may touch off that tree — a deliberate break at a break site outside it —
|
|
78
|
+
* is an edit rather than a delete, so the shape rules add no value to its
|
|
79
|
+
* prompt. The one-line destructive pointer keeps the escape-hatch guidance in view. The
|
|
78
80
|
* derivation reads the single rm-shape bullet out of the edit preamble and swaps
|
|
79
81
|
* the pointer in, so the two preambles share every other clause from one source.
|
|
80
82
|
*/
|
|
@@ -430,7 +432,7 @@ function runVerifierTask(task, context) {
|
|
|
430
432
|
if (task === 'fix-verify') {
|
|
431
433
|
const findingsBlock = renderFindingsBlock(context.findings)
|
|
432
434
|
return convergeReadOnlyAgent(
|
|
433
|
-
`You are the VERIFY step for ${context.findings.length} finding(s) (${context.sourceLabel}) on ${prCoordinates}, HEAD ${context.head}. The edit step left fixes in the working tree, uncommitted.
|
|
435
|
+
`You are the VERIFY step for ${context.findings.length} finding(s) (${context.sourceLabel}) on ${prCoordinates}, HEAD ${context.head}. The edit step left fixes in the working tree, uncommitted. Make NO edit to the tree under verification — verification only; any edit inside that tree invalidates the verdict you are about to emit.\n\n` +
|
|
434
436
|
`Findings the working-tree fixes must address:\n${findingsBlock}\n\n` +
|
|
435
437
|
`Steps:\n` +
|
|
436
438
|
`1. Resolve the worktree repo root for running tests: REPO=$(git rev-parse --show-toplevel).\n` +
|
|
@@ -444,7 +446,7 @@ function runVerifierTask(task, context) {
|
|
|
444
446
|
? context.failures.map((each, position) => `${position + 1}. ${each}`).join('\n')
|
|
445
447
|
: 'none reported'
|
|
446
448
|
return convergeReadOnlyAgent(
|
|
447
|
-
`You are the VERIFY step for the convergence repair on ${prCoordinates}, HEAD ${context.head}. The edit step left its repair in the working tree (a bot-thread fix uncommitted, and/or a rebase onto origin/main), unpushed.
|
|
449
|
+
`You are the VERIFY step for the convergence repair on ${prCoordinates}, HEAD ${context.head}. The edit step left its repair in the working tree (a bot-thread fix uncommitted, and/or a rebase onto origin/main), unpushed. Make NO edit to the tree under verification — verification only; any edit inside that tree invalidates the verdict you are about to emit.\n\n` +
|
|
448
450
|
`Concerns the working-tree repair must resolve (the gates the convergence check flagged):\n${failureBlock}\n\n` +
|
|
449
451
|
`Steps:\n` +
|
|
450
452
|
`1. Resolve the worktree repo root for running tests: REPO=$(git rev-parse --show-toplevel).\n` +
|
|
@@ -454,7 +456,7 @@ function runVerifierTask(task, context) {
|
|
|
454
456
|
)
|
|
455
457
|
}
|
|
456
458
|
return convergeReadOnlyAgent(
|
|
457
|
-
`You are the VERIFY step for an environment-hardening change (${context.sourceLabel}) staged in the working tree of ${context.hardeningRepoPath}. The edit step left the hooks/rules edits uncommitted there.
|
|
459
|
+
`You are the VERIFY step for an environment-hardening change (${context.sourceLabel}) staged in the working tree of ${context.hardeningRepoPath}. The edit step left the hooks/rules edits uncommitted there. Make NO edit to the tree under verification — verification only; any edit inside that tree invalidates the verdict you are about to emit.\n\n` +
|
|
458
460
|
`Concern the working-tree change must resolve: the edited hooks/rules block the code-standard violation classes from the deferred round at Write/Edit time, and a hook change carries a passing test per CODE_RULES.\n\n` +
|
|
459
461
|
`Steps:\n` +
|
|
460
462
|
`1. cd into ${context.hardeningRepoPath}, then resolve its repo root: REPO=$(git rev-parse --show-toplevel).\n` +
|
|
@@ -464,11 +466,7 @@ function runVerifierTask(task, context) {
|
|
|
464
466
|
` Run exactly:\n` +
|
|
465
467
|
` "C:\\Python313\\python.exe" "<REPO>/packages/claude-dev-env/hooks/blocking/verification_verdict_store.py" --manifest-hash-for-branch "${context.hardeningBranch}"\n` +
|
|
466
468
|
` (substitute the REPO path you resolved for the script path). That prints a single 64-char hex hash on stdout — capture it.\n` +
|
|
467
|
-
|
|
468
|
-
" ```verdict\n" +
|
|
469
|
-
` {"all_pass": true, "findings": [], "manifest_sha256": "<that hash>"}\n` +
|
|
470
|
-
" ```\n" +
|
|
471
|
-
` When verification fails, set all_pass to false and list the unresolved concerns in findings; still include the manifest_sha256. The verdict fence must be the last thing in your message.`,
|
|
469
|
+
buildVerdictFenceTail(' '),
|
|
472
470
|
{ label, phase: 'Converge', agentType: 'code-verifier', ...TIERS.sonnetMedium },
|
|
473
471
|
)
|
|
474
472
|
}
|
|
@@ -847,6 +845,29 @@ const HARDENING_COMMIT_SCHEMA = {
|
|
|
847
845
|
required: ['hardeningPrUrl', 'summary'],
|
|
848
846
|
}
|
|
849
847
|
|
|
848
|
+
const VERDICT_FENCE_CONTRACT_SENTENCE =
|
|
849
|
+
'Set all_pass to false when verification fails, and list every code defect you found in findings. When the verdict is incomplete because a check it rests on never showed red, set all_pass to false and name that check in prose directly above the fence rather than in findings. Always include the manifest_sha256. The verdict fence must be the last thing in your message.'
|
|
850
|
+
|
|
851
|
+
/**
|
|
852
|
+
* Build the closing fence recipe every verify prompt ends with: the lead-in
|
|
853
|
+
* sentence, the ```verdict block shape carrying the binding hash, and the
|
|
854
|
+
* contract sentence. Each verify path resolves the binding hash its own way
|
|
855
|
+
* and sits at its own nesting depth, so the caller passes the indent its
|
|
856
|
+
* surrounding prose uses; the fence body itself sits one level deeper.
|
|
857
|
+
* @param {string} indent leading whitespace the caller's prose sits at
|
|
858
|
+
* @returns {string} the fence recipe tail for a verify prompt
|
|
859
|
+
*/
|
|
860
|
+
function buildVerdictFenceTail(indent) {
|
|
861
|
+
const fenceIndent = `${indent} `
|
|
862
|
+
return (
|
|
863
|
+
`${indent}Then END your message with a fenced verdict block exactly in this shape, on its own, carrying that hash:\n` +
|
|
864
|
+
`${fenceIndent}` + "```verdict\n" +
|
|
865
|
+
`${fenceIndent}{"all_pass": true, "findings": [], "manifest_sha256": "<that hash>"}\n` +
|
|
866
|
+
`${fenceIndent}` + "```\n" +
|
|
867
|
+
`${fenceIndent}${VERDICT_FENCE_CONTRACT_SENTENCE}`
|
|
868
|
+
)
|
|
869
|
+
}
|
|
870
|
+
|
|
850
871
|
/**
|
|
851
872
|
* Build the verdict-fence step instructions for a verify agent, binding the
|
|
852
873
|
* surface hash by branch name rather than by a self-resolved cwd. Resolving
|
|
@@ -867,11 +888,7 @@ function buildVerdictFenceSteps(prOwner, prRepo, prNumber) {
|
|
|
867
888
|
` b. Run exactly:\n` +
|
|
868
889
|
` "C:\\Python313\\python.exe" "<REPO>/packages/claude-dev-env/hooks/blocking/verification_verdict_store.py" --manifest-hash-for-branch "<that branch>"\n` +
|
|
869
890
|
` (substitute the REPO path you resolved for the script path, and the branch name for <that branch>). That prints a single 64-char hex hash on stdout — capture it.\n` +
|
|
870
|
-
|
|
871
|
-
" ```verdict\n" +
|
|
872
|
-
` {"all_pass": true, "findings": [], "manifest_sha256": "<that hash>"}\n` +
|
|
873
|
-
" ```\n" +
|
|
874
|
-
` When verification fails, set all_pass to false and list the unresolved concerns in findings; still include the manifest_sha256. The verdict fence must be the last thing in your message.`
|
|
891
|
+
buildVerdictFenceTail('')
|
|
875
892
|
)
|
|
876
893
|
}
|
|
877
894
|
|
|
@@ -1280,23 +1297,45 @@ function normalizeShaForComparison(sha) {
|
|
|
1280
1297
|
}
|
|
1281
1298
|
|
|
1282
1299
|
/**
|
|
1283
|
-
*
|
|
1284
|
-
*
|
|
1285
|
-
*
|
|
1300
|
+
* Locate the LAST ```verdict ...``` fence in a transcript: the marker, optional
|
|
1301
|
+
* whitespace, a required newline, then a body closed by ```. This is the ONE
|
|
1302
|
+
* definition of "the verdict fence" — the verdict parser and the pre-fence prose
|
|
1303
|
+
* reader both anchor here, so they cannot land on different fences.
|
|
1304
|
+
*
|
|
1305
|
+
* ::
|
|
1306
|
+
*
|
|
1307
|
+
* ok: 'note\n\n```verdict\n{}\n```' -> body '{}\n' starting at index 6
|
|
1308
|
+
* flag: 'note\n\n```verdict {}```' -> null (no newline after the marker)
|
|
1309
|
+
*
|
|
1310
|
+
* A marker with no newline after it is not a fence, so a later one cannot pull
|
|
1311
|
+
* either reader off the fence the other reads.
|
|
1312
|
+
*
|
|
1286
1313
|
* @param {string|null|undefined} transcript the agent transcript text
|
|
1287
|
-
* @returns {
|
|
1314
|
+
* @returns {{bodyText: string, startIndex: number}|null} the last fence, or null when there is none
|
|
1288
1315
|
*/
|
|
1289
|
-
function
|
|
1316
|
+
function findLastVerdictFence(transcript) {
|
|
1290
1317
|
if (typeof transcript !== 'string') return null
|
|
1291
1318
|
const fencePattern = /```verdict\s*\n([\s\S]*?)```/g
|
|
1292
|
-
let
|
|
1319
|
+
let lastFence = null
|
|
1293
1320
|
let eachMatch
|
|
1294
1321
|
while ((eachMatch = fencePattern.exec(transcript)) !== null) {
|
|
1295
|
-
|
|
1322
|
+
lastFence = { bodyText: eachMatch[1], startIndex: eachMatch.index }
|
|
1296
1323
|
}
|
|
1297
|
-
|
|
1324
|
+
return lastFence
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
/**
|
|
1328
|
+
* Parse the LAST ```verdict ...``` fenced JSON block from a transcript.
|
|
1329
|
+
* Guards against non-string input, takes the fence findLastVerdictFence locates,
|
|
1330
|
+
* parses the JSON, and returns the object or null on any failure.
|
|
1331
|
+
* @param {string|null|undefined} transcript the agent transcript text
|
|
1332
|
+
* @returns {object|null} the parsed verdict object, or null when absent or malformed
|
|
1333
|
+
*/
|
|
1334
|
+
function parseLastVerdictFence(transcript) {
|
|
1335
|
+
const lastFence = findLastVerdictFence(transcript)
|
|
1336
|
+
if (lastFence === null) return null
|
|
1298
1337
|
try {
|
|
1299
|
-
return JSON.parse(
|
|
1338
|
+
return JSON.parse(lastFence.bodyText)
|
|
1300
1339
|
} catch {
|
|
1301
1340
|
return null
|
|
1302
1341
|
}
|
|
@@ -1343,14 +1382,57 @@ function renderVerifyObjectionLine(eachFinding) {
|
|
|
1343
1382
|
return stringifiedFinding === '{}' ? null : stringifiedFinding
|
|
1344
1383
|
}
|
|
1345
1384
|
|
|
1385
|
+
/**
|
|
1386
|
+
* Read the prose paragraph the verifier wrote directly above its last verdict
|
|
1387
|
+
* fence. A verdict that is incomplete — a check it rests on never showed red —
|
|
1388
|
+
* is not a code defect, so the fence contract puts that reason here rather than
|
|
1389
|
+
* in findings; this reader is how the reason survives into the re-fix step.
|
|
1390
|
+
*
|
|
1391
|
+
* Anchors through findLastVerdictFence, the single locator parseLastVerdictFence
|
|
1392
|
+
* reads its JSON from, so both readers see the same fence by construction rather
|
|
1393
|
+
* than by two searches that happen to agree: a stray marker after the real fence
|
|
1394
|
+
* — unterminated, or closed but carrying no newline after the marker — moves
|
|
1395
|
+
* neither reader. A candidate paragraph carrying only markdown scaffolding (a
|
|
1396
|
+
* heading, a fence delimiter) is skipped, since handing the fixer a heading is
|
|
1397
|
+
* worse than the generic fallback the caller keeps.
|
|
1398
|
+
*
|
|
1399
|
+
* ::
|
|
1400
|
+
*
|
|
1401
|
+
* 'ran the gates\n\ncheck X never showed red\n\n```verdict\n{}\n```'
|
|
1402
|
+
* -> 'check X never showed red'
|
|
1403
|
+
* '```verdict\n{}\n```' -> null
|
|
1404
|
+
*
|
|
1405
|
+
* @param {string|null|undefined} verifyTranscript the verifier transcript text
|
|
1406
|
+
* @returns {string|null} the last prose paragraph above the final fence, or null when there is none
|
|
1407
|
+
*/
|
|
1408
|
+
function extractPreFenceProse(verifyTranscript) {
|
|
1409
|
+
const lastFence = findLastVerdictFence(verifyTranscript)
|
|
1410
|
+
if (lastFence === null) return null
|
|
1411
|
+
const proseParagraphs = verifyTranscript
|
|
1412
|
+
.slice(0, lastFence.startIndex)
|
|
1413
|
+
.split(/\n\s*\n/)
|
|
1414
|
+
.map((eachParagraph) => eachParagraph.trim())
|
|
1415
|
+
.filter((eachParagraph) =>
|
|
1416
|
+
eachParagraph
|
|
1417
|
+
.split('\n')
|
|
1418
|
+
.some((eachLine) => {
|
|
1419
|
+
const trimmedLine = eachLine.trim()
|
|
1420
|
+
return trimmedLine.length > 0 && !trimmedLine.startsWith('#') && !trimmedLine.startsWith('```') && /[A-Za-z]/.test(trimmedLine)
|
|
1421
|
+
}),
|
|
1422
|
+
)
|
|
1423
|
+
return proseParagraphs.length === 0 ? null : proseParagraphs[proseParagraphs.length - 1]
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1346
1426
|
/**
|
|
1347
1427
|
* Pull the verifier's stated objections out of a failed verify transcript so the
|
|
1348
1428
|
* re-fix step knows what the verdict rejected. Reads the last fenced verdict JSON
|
|
1349
1429
|
* (the same block verdictPassed reads) and renders each finding through
|
|
1350
|
-
* renderVerifyObjectionLine into a numbered list.
|
|
1351
|
-
*
|
|
1352
|
-
*
|
|
1353
|
-
*
|
|
1430
|
+
* renderVerifyObjectionLine into a numbered list. When findings yields no usable
|
|
1431
|
+
* line the verdict is an incomplete one rather than a code-defect one, so the
|
|
1432
|
+
* prose paragraph above the fence — where the fence contract puts the unshown-red
|
|
1433
|
+
* check — carries the objection instead. A missing fence, a parse failure, or an
|
|
1434
|
+
* empty findings list with no prose above the fence falls back to a generic
|
|
1435
|
+
* re-read instruction, so the re-fix step always receives actionable text.
|
|
1354
1436
|
* @param {string|null|undefined} verifyTranscript the failed verifier transcript text
|
|
1355
1437
|
* @returns {string} a human-readable block of the verifier's objections
|
|
1356
1438
|
*/
|
|
@@ -1361,7 +1443,7 @@ function extractVerifyObjection(verifyTranscript) {
|
|
|
1361
1443
|
const renderedObjections = allObjections
|
|
1362
1444
|
.map((eachFinding) => renderVerifyObjectionLine(eachFinding))
|
|
1363
1445
|
.filter((eachLine) => eachLine !== null)
|
|
1364
|
-
if (renderedObjections.length === 0) return VERIFY_OBJECTION_FALLBACK
|
|
1446
|
+
if (renderedObjections.length === 0) return extractPreFenceProse(verifyTranscript) || VERIFY_OBJECTION_FALLBACK
|
|
1365
1447
|
return renderedObjections.map((eachLine, position) => `${position + 1}. ${eachLine}`).join('\n')
|
|
1366
1448
|
}
|
|
1367
1449
|
|
package/skills/codex-review/scripts/codex_review_scripts_constants/codex_usage_probe_constants.py
CHANGED
|
@@ -22,10 +22,6 @@ WINDOWS_OS_NAME = "nt"
|
|
|
22
22
|
WINDOWS_COMMAND_SHELL = "cmd"
|
|
23
23
|
WINDOWS_COMMAND_SHELL_RUN_FLAG = "/c"
|
|
24
24
|
ALL_WINDOWS_SCRIPT_SUFFIXES = (".cmd", ".bat")
|
|
25
|
-
WINDOWS_TASKKILL_COMMAND = "taskkill"
|
|
26
|
-
WINDOWS_TASKKILL_FORCE_FLAG = "/F"
|
|
27
|
-
WINDOWS_TASKKILL_TREE_FLAG = "/T"
|
|
28
|
-
WINDOWS_TASKKILL_PID_FLAG = "/PID"
|
|
29
25
|
|
|
30
26
|
JSONRPC_VERSION = "2.0"
|
|
31
27
|
JSONRPC_KEY_ID = "id"
|