claude-dev-env 1.92.1 → 1.93.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/CLAUDE.md +6 -2
- package/_shared/CLAUDE.md +2 -1
- package/_shared/advisor/CLAUDE.md +21 -0
- package/_shared/advisor/advisor-protocol.md +142 -0
- package/_shared/advisor/scripts/config/advisor_scripts_constants/__init__.py +1 -0
- package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +86 -0
- package/_shared/advisor/scripts/model_tier_run_validator.py +246 -0
- package/_shared/advisor/scripts/pyproject.toml +3 -0
- package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +302 -0
- package/_shared/advisor/scripts/tests/test_tier_model_ids.py +180 -0
- package/_shared/advisor/scripts/tier_model_ids.py +164 -0
- package/_shared/pr-loop/CLAUDE.md +1 -0
- package/_shared/pr-loop/precatch-rubric.md +65 -0
- package/_shared/pr-loop/scripts/CLAUDE.md +1 -1
- package/_shared/pr-loop/scripts/code_rules_gate.py +137 -5
- package/_shared/pr-loop/scripts/post_audit_thread.py +1 -1
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -1
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +16 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/reviews_disabled_constants.py +2 -1
- package/_shared/pr-loop/scripts/reviewer_availability.py +42 -12
- package/_shared/pr-loop/scripts/reviews_disabled.py +65 -21
- package/_shared/pr-loop/scripts/terminology_sweep.py +69 -21
- package/_shared/pr-loop/scripts/tests/CLAUDE.md +1 -1
- package/_shared/pr-loop/scripts/tests/fixtures/{copilot_internal_user_jonecho.json → copilot_internal_user_example.json} +1 -1
- package/_shared/pr-loop/scripts/tests/test_agent_config_carveout.py +1 -1
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +146 -2
- package/_shared/pr-loop/scripts/tests/test_copilot_quota.py +8 -8
- package/_shared/pr-loop/scripts/tests/test_reviewer_availability.py +18 -5
- package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +56 -5
- package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +104 -17
- package/agents/CLAUDE.md +3 -2
- package/agents/clasp-deployment-orchestrator.md +2 -2
- package/agents/code-advisor.md +7 -5
- package/agents/code-verifier.md +6 -2
- package/agents/session-advisor.md +27 -0
- package/audit-rubrics/category_rubrics/category-j-code-rules-compliance.md +1 -1
- package/audit-rubrics/category_rubrics/category-k-codebase-conflicts.md +1 -1
- package/audit-rubrics/prompts/category-a-api-contracts.md +2 -2
- package/audit-rubrics/prompts/category-b-selector-engine-compat.md +2 -2
- package/audit-rubrics/prompts/category-c-resource-cleanup.md +2 -2
- package/audit-rubrics/prompts/category-d-scoping-and-ordering.md +2 -2
- package/audit-rubrics/prompts/category-e-dead-code.md +2 -2
- package/audit-rubrics/prompts/category-f-silent-failures.md +2 -2
- package/audit-rubrics/prompts/category-g-bounds-and-overflow.md +2 -2
- package/audit-rubrics/prompts/category-h-security-boundaries.md +2 -2
- package/audit-rubrics/prompts/category-i-concurrency.md +2 -2
- package/audit-rubrics/prompts/category-j-code-rules-compliance.md +2 -2
- package/audit-rubrics/prompts/category-k-codebase-conflicts.md +2 -2
- package/audit-rubrics/prompts/category-l-behavior-equivalence.md +2 -2
- package/audit-rubrics/prompts/category-m-producer-consumer-cardinality.md +2 -2
- package/audit-rubrics/prompts/category-n-test-name-scenario-verifier.md +2 -2
- package/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md +2 -2
- package/audit-rubrics/prompts/category-p-name-vs-behavior-contract.md +2 -2
- package/bin/CLAUDE.md +1 -0
- package/bin/expand_home_directory_tokens.mjs +75 -0
- package/bin/install.mjs +21 -7
- package/bin/install.test.mjs +220 -10
- package/hooks/CLAUDE.md +1 -1
- package/hooks/blocking/CLAUDE.md +4 -1
- package/hooks/blocking/_path_setup.py +13 -0
- package/hooks/blocking/bash_pre_tool_use_dispatcher.py +281 -0
- package/hooks/blocking/code_rules_dead_module_constant.py +88 -16
- package/hooks/blocking/code_rules_enforcer.py +6 -0
- package/hooks/blocking/code_rules_imports_logging.py +34 -1
- package/hooks/blocking/code_rules_paths_syspath.py +1 -1
- package/hooks/blocking/code_rules_test_layout.py +272 -0
- package/hooks/blocking/nas_ssh_binary_enforcer.py +22 -12
- package/hooks/blocking/pii_prevention_blocker.py +781 -0
- package/hooks/blocking/pii_scanner.py +340 -0
- package/hooks/blocking/pre_tool_use_dispatcher.py +3 -3
- package/hooks/blocking/stop_dispatcher.py +84 -0
- package/hooks/blocking/test__path_setup.py +39 -0
- package/hooks/blocking/test_bash_dispatcher_interpreter_starts.py +61 -0
- package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +294 -0
- package/hooks/blocking/test_code_rules_enforcer_cross_skill_duplicate.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_dead_module_constant.py +51 -0
- package/hooks/blocking/test_code_rules_enforcer_dead_module_constant_read_cap.py +103 -0
- package/hooks/blocking/test_code_rules_enforcer_dot_test_pattern.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_file_global_constants.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_hardcoded_user_path.py +11 -11
- package/hooks/blocking/test_code_rules_enforcer_naive_datetime.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_same_file_inline_duplicate.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_test_layout.py +111 -0
- package/hooks/blocking/test_code_rules_enforcer_type_checking_scope.py +76 -0
- package/hooks/blocking/test_destructive_command_blocker.py +29 -9
- package/hooks/blocking/test_gh_body_arg_blocker.py +3 -3
- package/hooks/blocking/test_nas_ssh_binary_enforcer.py +96 -39
- package/hooks/blocking/test_pii_prevention_blocker.py +905 -0
- package/hooks/blocking/test_pii_prevention_windows_git_path.py +41 -0
- package/hooks/blocking/test_pii_scanner.py +165 -0
- package/hooks/blocking/test_pr_description_enforcer_pr_number.py +1 -1
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +14 -13
- package/hooks/blocking/test_stop_dispatcher.py +187 -0
- package/hooks/blocking/test_verdict_directory_write_blocker.py +1 -1
- package/hooks/blocking/test_volatile_path_in_post_blocker.py +3 -3
- package/hooks/blocking/volatile_path_in_post_blocker.py +54 -5
- package/hooks/diagnostic/migrations/README.md +25 -26
- package/hooks/diagnostic/test_hook_log_extractor.py +10 -10
- package/hooks/hooks.json +10 -120
- package/hooks/hooks_constants/CLAUDE.md +12 -3
- package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +78 -0
- package/hooks/hooks_constants/dead_module_constant_constants.py +1 -0
- package/hooks/hooks_constants/hosted_hook_runner.py +73 -0
- package/hooks/hooks_constants/local_identity.py +182 -0
- package/hooks/hooks_constants/nas_ssh_binary_enforcer_constants.py +4 -37
- package/hooks/hooks_constants/pii_prevention_constants.py +299 -0
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +4 -0
- package/hooks/hooks_constants/python_style_checks_constants.py +15 -0
- package/hooks/hooks_constants/stop_dispatcher_constants.py +26 -0
- package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +87 -0
- package/hooks/hooks_constants/test_hardcoded_user_path_constants.py +2 -2
- package/hooks/hooks_constants/test_hosted_hook_runner.py +101 -0
- package/hooks/hooks_constants/test_layout_constants.py +28 -0
- package/hooks/hooks_constants/test_local_identity.py +88 -0
- package/hooks/hooks_constants/test_session_env_cleanup_constants.py +1 -1
- package/hooks/hooks_constants/test_stop_dispatcher_constants.py +32 -0
- package/hooks/validators/README.md +5 -6
- package/hooks/validators/__init__.py +2 -2
- package/hooks/validators/python_style_checks.py +298 -243
- package/hooks/validators/run_all_validators.py +6 -0
- package/hooks/validators/test_python_style_checks.py +278 -163
- package/package.json +2 -2
- package/rules/CLAUDE.md +2 -1
- package/rules/bdd.md +1 -1
- package/rules/nas-ssh-invocation.md +6 -4
- package/rules/no-justification-noise.md +61 -0
- package/scripts/test_setup_project_paths.py +1 -1
- package/skills/CLAUDE.md +6 -3
- package/skills/_shared/pr-loop/scripts/_path_resolver.py +2 -2
- package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +1 -1
- package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +1 -1
- package/skills/_shared/pr-loop/scripts/test__path_resolver.py +42 -3
- package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +11 -11
- package/skills/_shared/pr-loop/scripts/test_build_fix_prompt.py +26 -3
- package/skills/_shared/pr-loop/scripts/test_preflight_worktree.py +49 -25
- package/skills/autoconverge/CLAUDE.md +3 -3
- package/skills/autoconverge/SKILL.md +113 -35
- package/skills/autoconverge/reference/CLAUDE.md +2 -2
- package/skills/autoconverge/reference/convergence.md +44 -22
- package/skills/autoconverge/reference/gotchas.md +11 -0
- package/skills/autoconverge/reference/stop-conditions.md +23 -13
- package/skills/autoconverge/workflow/CLAUDE.md +2 -1
- package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +15 -19
- package/skills/autoconverge/workflow/converge.contract.test.mjs +43 -42
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +141 -16
- package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +2 -2
- package/skills/autoconverge/workflow/converge.mjs +329 -68
- package/skills/autoconverge/workflow/converge.path-aware.test.mjs +1 -1
- package/skills/autoconverge/workflow/converge.precatch.test.mjs +152 -0
- package/skills/autoconverge/workflow/converge.run-input.test.mjs +3 -3
- package/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +15 -15
- package/skills/autoconverge/workflow/test_convergence_summary.py +6 -6
- package/skills/bdd-protocol/SKILL.md +4 -5
- package/skills/bdd-protocol/references/anti-patterns.md +1 -1
- package/skills/bugteam/SKILL.md +8 -0
- package/skills/bugteam/reference/copilot-gap-analysis.md +20 -20
- package/skills/bugteam/reference/obstacles/audit-walk-categories.md +1 -1
- package/skills/bugteam/reference/team-setup.md +7 -5
- package/skills/bugteam/scripts/reflow_skill_md.py +1 -1
- package/skills/copilot-finding-triage/SKILL.md +124 -0
- package/skills/copilot-finding-triage/reference/tier-rubric.md +49 -0
- package/skills/copilot-finding-triage/scripts/conftest.py +8 -0
- package/skills/copilot-finding-triage/scripts/copilot_finding_triage_constants/__init__.py +0 -0
- package/skills/copilot-finding-triage/scripts/copilot_finding_triage_constants/config/__init__.py +0 -0
- package/skills/copilot-finding-triage/scripts/copilot_finding_triage_constants/config/notify_ntfy_constants.py +32 -0
- package/skills/copilot-finding-triage/scripts/notify_ntfy.py +192 -0
- package/skills/copilot-finding-triage/scripts/test_notify_ntfy.py +141 -0
- package/skills/copilot-finding-triage/templates/notification.md +34 -0
- package/skills/copilot-review/SKILL.md +8 -6
- package/skills/findbugs/SKILL.md +4 -0
- package/skills/fixbugs/SKILL.md +8 -7
- package/skills/gotcha/CLAUDE.md +2 -2
- package/skills/gotcha/SKILL.md +4 -4
- package/skills/log-audit/SKILL.md +4 -6
- package/skills/monitor-open-prs/CLAUDE.md +1 -1
- package/skills/monitor-open-prs/SKILL.md +6 -1
- package/skills/orchestrator/SKILL.md +74 -78
- package/skills/orchestrator-refresh/SKILL.md +24 -12
- package/skills/post-audit-findings/SKILL.md +5 -9
- package/skills/pr-consistency-audit/SKILL.md +5 -1
- package/skills/pr-converge/CLAUDE.md +1 -1
- package/skills/pr-converge/SKILL.md +86 -47
- package/skills/pr-converge/reference/CLAUDE.md +1 -1
- package/skills/pr-converge/reference/convergence-gates.md +20 -17
- package/skills/pr-converge/reference/examples.md +63 -47
- package/skills/pr-converge/reference/fix-protocol.md +15 -15
- package/skills/pr-converge/reference/ground-rules.md +11 -7
- package/skills/pr-converge/reference/multi-pr-orchestration.md +6 -5
- package/skills/pr-converge/reference/per-tick.md +129 -107
- package/skills/pr-converge/reference/state-schema.md +15 -10
- package/skills/pr-converge/scripts/check_convergence.py +5 -4
- package/skills/pr-converge/scripts/test_check_convergence.py +6 -4
- package/skills/pr-fix-protocol/SKILL.md +3 -8
- package/skills/pr-loop-cloud-transport/SKILL.md +102 -0
- package/skills/pr-loop-cloud-transport/reference/identity-and-hooks.md +40 -0
- package/skills/pr-loop-cloud-transport/reference/substitution-matrix.md +48 -0
- package/skills/pr-loop-lifecycle/SKILL.md +5 -9
- package/skills/pr-scope-resolve/SKILL.md +3 -6
- package/skills/privacy-hygiene/SKILL.md +114 -0
- package/skills/qbug/SKILL.md +8 -8
- package/skills/rebase/SKILL.md +5 -1
- package/skills/refine/SKILL.md +4 -5
- package/skills/reviewer-gates/SKILL.md +7 -11
- package/skills/session-log/SKILL.md +4 -1
- package/skills/skill-builder/SKILL.md +3 -6
- package/skills/structure-prompt/SKILL.md +4 -5
- package/skills/team-advisor/SKILL.md +56 -0
- package/skills/test_markdown_link_integrity.py +10 -6
- package/skills/update/SKILL.md +5 -1
- package/skills/usage-pause/SKILL.md +14 -5
- package/skills/usage-pause/scripts/resolve_usage_window.py +83 -5
- package/skills/usage-pause/scripts/test_resolve_usage_window.py +185 -17
- package/skills/usage-pause/scripts/usage_pause_constants/resolve_usage_window_constants.py +4 -3
- package/skills/verified-build/SKILL.md +4 -9
|
@@ -119,37 +119,21 @@ test('the reviewer-availability probe rides the merged preflight-git spawn, not
|
|
|
119
119
|
);
|
|
120
120
|
});
|
|
121
121
|
|
|
122
|
-
test('the
|
|
122
|
+
test('the round parallel array holds exactly the three internal lenses in order and no Bugbot slot', () => {
|
|
123
123
|
const parallelLensIndex = convergeSource.indexOf('const lenses = await parallel(');
|
|
124
124
|
assert.notEqual(parallelLensIndex, -1, 'expected the parallel lens block to exist');
|
|
125
|
-
const isBugbotDownPreSpawnIndex = convergeSource.indexOf('const isBugbotDownPreSpawn = resolveReviewerDown(');
|
|
126
|
-
assert.notEqual(isBugbotDownPreSpawnIndex, -1, 'expected a pre-spawn Bugbot-down decision computed before the lens array');
|
|
127
|
-
assert.ok(
|
|
128
|
-
isBugbotDownPreSpawnIndex < parallelLensIndex,
|
|
129
|
-
'expected the pre-spawn Bugbot-down decision to be computed before the parallel lens array',
|
|
130
|
-
);
|
|
131
125
|
const lensArrayEnd = convergeSource.indexOf('])', parallelLensIndex);
|
|
132
126
|
const lensArray = convergeSource.slice(parallelLensIndex, lensArrayEnd);
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
test('the post-round bugbotDown verdict treats a dead Bugbot lens as down, not fail-open', () => {
|
|
141
|
-
const parallelLensIndex = convergeSource.indexOf('const lenses = await parallel(');
|
|
142
|
-
assert.notEqual(parallelLensIndex, -1, 'expected the parallel lens block to exist');
|
|
143
|
-
const lensArrayEnd = convergeSource.indexOf('])', parallelLensIndex);
|
|
144
|
-
const bugbotDownAssignIndex = convergeSource.indexOf('bugbotDown = ', lensArrayEnd);
|
|
145
|
-
assert.notEqual(bugbotDownAssignIndex, -1, 'expected the post-round bugbotDown assignment after the lens array');
|
|
146
|
-
const bugbotDownAssignLineEnd = convergeSource.indexOf('\n', bugbotDownAssignIndex);
|
|
147
|
-
const bugbotDownAssignLine = convergeSource.slice(bugbotDownAssignIndex, bugbotDownAssignLineEnd);
|
|
148
|
-
assert.match(
|
|
149
|
-
bugbotDownAssignLine,
|
|
150
|
-
/bugbotDown = lenses\[0\] == null \? true : resolveReviewerDown\(lenses\[0\], input\.bugbotDisabled \|\| false\)/,
|
|
151
|
-
'expected a dead (null) Bugbot lens result to report bugbotDown:true — resolveReviewerDown alone is fail-open on null, which would wrongly report bugbotDown:false for a lens agent that actually spawned and died, leaving check_convergence.py to look for a bugbot review that was never posted',
|
|
127
|
+
const codeReviewIndex = lensArray.indexOf('runCodeReviewLens(head, reviewerAvailability)');
|
|
128
|
+
const auditIndex = lensArray.indexOf('runAuditLens(head, reviewerAvailability)');
|
|
129
|
+
const selfReviewIndex = lensArray.indexOf('runSelfReviewLens(head, reviewerAvailability)');
|
|
130
|
+
assert.ok(codeReviewIndex !== -1 && auditIndex !== -1 && selfReviewIndex !== -1, 'expected the three internal lenses in the parallel array');
|
|
131
|
+
assert.ok(
|
|
132
|
+
codeReviewIndex < auditIndex && auditIndex < selfReviewIndex,
|
|
133
|
+
'expected the code-review, bug-audit, self-review order to match LENS_NAMES',
|
|
152
134
|
);
|
|
135
|
+
assert.doesNotMatch(lensArray, /runBugbotLens/, 'expected Bugbot to leave the per-round parallel and become a terminal gate');
|
|
136
|
+
assert.doesNotMatch(convergeSource, /isBugbotDownPreSpawn/, 'expected the pre-spawn Bugbot-down decision to be gone from the round');
|
|
153
137
|
});
|
|
154
138
|
|
|
155
139
|
test('both reviewer trigger commands carry the CLAUDE_REVIEWER_GATE=autoconverge sentinel', () => {
|
|
@@ -324,8 +308,8 @@ test('each fix push, each lens-retry, and the convergence repair invalidate the
|
|
|
324
308
|
const invalidationMatches = convergeSource.match(/^ +head = null$/gm) || [];
|
|
325
309
|
assert.equal(
|
|
326
310
|
invalidationMatches.length,
|
|
327
|
-
|
|
328
|
-
'expected head invalidation after the CONVERGE fix push, the COPILOT fix push, the convergence repair, the all-lenses-dead retry, the not-clean-no-findings retry, and the all-clean no-lens-ran clean-audit refusal',
|
|
311
|
+
8,
|
|
312
|
+
'expected head invalidation after the static-sweep fix push, the CONVERGE fix push, the terminal-Bugbot fix push, the COPILOT fix push, the convergence repair, the all-lenses-dead retry, the not-clean-no-findings retry, and the all-clean no-lens-ran clean-audit refusal',
|
|
329
313
|
);
|
|
330
314
|
});
|
|
331
315
|
|
|
@@ -625,12 +609,12 @@ test('the standards-deferral surfaces disclose the hardening-PR state unconditio
|
|
|
625
609
|
);
|
|
626
610
|
});
|
|
627
611
|
|
|
628
|
-
test('
|
|
612
|
+
test('every standards-deferral call site builds standardsNote from the spawnStandardsFollowUp outcome', () => {
|
|
629
613
|
const callSiteUses = convergeSource.match(/standardsNote = standardsDeferralNote\(/g) || [];
|
|
630
614
|
assert.equal(
|
|
631
615
|
callSiteUses.length,
|
|
632
|
-
|
|
633
|
-
'expected
|
|
616
|
+
3,
|
|
617
|
+
'expected the converge-round, terminal-Bugbot, and Copilot standards call sites to each build standardsNote via standardsDeferralNote(...)',
|
|
634
618
|
);
|
|
635
619
|
assert.doesNotMatch(
|
|
636
620
|
convergeSource,
|
|
@@ -741,8 +725,8 @@ for (const [helperName, taskName] of editStepTasks) {
|
|
|
741
725
|
}
|
|
742
726
|
|
|
743
727
|
function preambleText() {
|
|
744
|
-
const preambleStart = convergeSource.indexOf('const
|
|
745
|
-
assert.notEqual(preambleStart, -1, 'expected
|
|
728
|
+
const preambleStart = convergeSource.indexOf('const HEADLESS_EDIT_PREAMBLE =');
|
|
729
|
+
assert.notEqual(preambleStart, -1, 'expected HEADLESS_EDIT_PREAMBLE to exist');
|
|
746
730
|
const preambleEnd = convergeSource.indexOf('\n\nlet ', preambleStart);
|
|
747
731
|
return convergeSource.slice(preambleStart, preambleEnd === -1 ? undefined : preambleEnd);
|
|
748
732
|
}
|
|
@@ -850,15 +834,24 @@ test('SKILL.md attributes the known-temp-var resolution to the cwd-scoped auto-a
|
|
|
850
834
|
);
|
|
851
835
|
});
|
|
852
836
|
|
|
853
|
-
test('convergeAgent prepends
|
|
837
|
+
test('convergeAgent prepends HEADLESS_EDIT_PREAMBLE and the worktree directive to every prompt', () => {
|
|
854
838
|
const convergeAgentBody = lensPromptBody('convergeAgent');
|
|
855
839
|
assert.match(
|
|
856
840
|
convergeAgentBody,
|
|
857
|
-
/
|
|
841
|
+
/HEADLESS_EDIT_PREAMBLE.*worktreeDirective/,
|
|
858
842
|
'expected convergeAgent to prepend both preamble and worktree directive',
|
|
859
843
|
);
|
|
860
844
|
});
|
|
861
845
|
|
|
846
|
+
test('convergeReadOnlyAgent prepends HEADLESS_READONLY_PREAMBLE and the worktree directive to every read-only prompt', () => {
|
|
847
|
+
const readOnlyAgentBody = lensPromptBody('convergeReadOnlyAgent');
|
|
848
|
+
assert.match(
|
|
849
|
+
readOnlyAgentBody,
|
|
850
|
+
/HEADLESS_READONLY_PREAMBLE.*worktreeDirective/,
|
|
851
|
+
'expected convergeReadOnlyAgent to prepend the trimmed read-only preamble and the worktree directive',
|
|
852
|
+
);
|
|
853
|
+
});
|
|
854
|
+
|
|
862
855
|
const taskDispatchers = [
|
|
863
856
|
{ name: 'runGitTask', isAsync: false },
|
|
864
857
|
{ name: 'runFixerTask', isAsync: false },
|
|
@@ -986,8 +979,8 @@ test('extractVerifyObjection calls parseLastVerdictFence', () => {
|
|
|
986
979
|
});
|
|
987
980
|
|
|
988
981
|
test('the headless preamble routes waits through the Monitor tool and forbids ending a turn to await work', () => {
|
|
989
|
-
const preambleStart = convergeSource.indexOf('const
|
|
990
|
-
assert.notEqual(preambleStart, -1, 'expected a
|
|
982
|
+
const preambleStart = convergeSource.indexOf('const HEADLESS_EDIT_PREAMBLE =');
|
|
983
|
+
assert.notEqual(preambleStart, -1, 'expected a HEADLESS_EDIT_PREAMBLE definition');
|
|
991
984
|
const preambleEnd = convergeSource.indexOf('\n\nlet activeRepoPath', preambleStart);
|
|
992
985
|
assert.notEqual(preambleEnd, -1, 'expected the preamble to end before activeRepoPath');
|
|
993
986
|
const preamble = convergeSource.slice(preambleStart, preambleEnd);
|
|
@@ -998,7 +991,7 @@ test('the headless preamble routes waits through the Monitor tool and forbids en
|
|
|
998
991
|
});
|
|
999
992
|
|
|
1000
993
|
test('the preamble describes the Monitor wait by its real contract: a bounded until-loop consuming notifications as they arrive, rather than an in-turn synchronous return', () => {
|
|
1001
|
-
const blockStart = convergeSource.indexOf('const
|
|
994
|
+
const blockStart = convergeSource.indexOf('const HEADLESS_EDIT_PREAMBLE =');
|
|
1002
995
|
assert.ok(blockStart !== -1, 'expected a wait-safety preamble to exist');
|
|
1003
996
|
const blockEnd = convergeSource.indexOf('\n\nlet activeRepoPath', blockStart);
|
|
1004
997
|
assert.ok(blockEnd !== -1, 'expected the preamble to end before activeRepoPath');
|
|
@@ -1129,8 +1122,8 @@ test('every convergeAgent spawn options object carries a model and effort tier',
|
|
|
1129
1122
|
}
|
|
1130
1123
|
});
|
|
1131
1124
|
|
|
1132
|
-
test('the
|
|
1133
|
-
for (const label of ['lens:bugbot', 'lens:code-review', 'lens:bug-audit', 'lens:reuse']) {
|
|
1125
|
+
test('the semantic review lenses and the terminal Bugbot gate run on the opusMedium tier', () => {
|
|
1126
|
+
for (const label of ['lens:bugbot', 'lens:code-review', 'lens:bug-audit', 'lens:self-review', 'lens:reuse']) {
|
|
1134
1127
|
assert.match(
|
|
1135
1128
|
optionsLineForLabel(label),
|
|
1136
1129
|
/\.\.\.TIERS\.opusMedium/,
|
|
@@ -1139,6 +1132,14 @@ test('the four review lenses run on the opusMedium tier', () => {
|
|
|
1139
1132
|
}
|
|
1140
1133
|
});
|
|
1141
1134
|
|
|
1135
|
+
test('the deterministic static sweep runs on the cheaper sonnetMedium tier', () => {
|
|
1136
|
+
assert.match(
|
|
1137
|
+
optionsLineForLabel('lens:static-sweep'),
|
|
1138
|
+
/\.\.\.TIERS\.sonnetMedium/,
|
|
1139
|
+
'expected the deterministic static sweep to run on the sonnet tier, not opus',
|
|
1140
|
+
);
|
|
1141
|
+
});
|
|
1142
|
+
|
|
1142
1143
|
test('the copilot gate runs on the haikuLow tier', () => {
|
|
1143
1144
|
assert.match(optionsLineForLabel('copilot-gate'), /\.\.\.TIERS\.haikuLow/);
|
|
1144
1145
|
});
|
|
@@ -1161,8 +1162,8 @@ test('the preflight-git task returns the changed-file list and diffstat for the
|
|
|
1161
1162
|
assert.match(schema, /required:[^\n]*'changedFiles'[^\n]*'diffstat'/);
|
|
1162
1163
|
});
|
|
1163
1164
|
|
|
1164
|
-
test('each per-round lens
|
|
1165
|
-
for (const builder of ['
|
|
1165
|
+
test('each per-round lens, the static sweep, the reuse lens, and the terminal Bugbot gate inject the preflight changed-file context', () => {
|
|
1166
|
+
for (const builder of ['runStaticSweep', 'runCodeReviewLens', 'runAuditLens', 'runSelfReviewLens', 'runReuseAuditPass', 'runBugbotLens']) {
|
|
1166
1167
|
assert.match(
|
|
1167
1168
|
lensPromptBody(builder),
|
|
1168
1169
|
/renderLensDiffContext\(preflightResult\)/,
|
|
@@ -16,12 +16,36 @@ function functionBody(functionName) {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
const productionModule = new Function(
|
|
19
|
-
`${functionBody('
|
|
19
|
+
`${functionBody('classifyReviewerGateOutcome')}\n` +
|
|
20
|
+
`${functionBody('classifyCopilotOutcome')}\n` +
|
|
21
|
+
`${functionBody('hasCodeConcernFinding')}\n` +
|
|
22
|
+
`${functionBody('buildUserReview')}\n` +
|
|
20
23
|
`${functionBody('resolveCopilotDown')}\n` +
|
|
21
24
|
`${functionBody('resolveReviewerDown')}\n` +
|
|
22
|
-
'return { classifyCopilotOutcome, resolveCopilotDown, resolveReviewerDown };',
|
|
25
|
+
'return { classifyReviewerGateOutcome, classifyCopilotOutcome, hasCodeConcernFinding, buildUserReview, resolveCopilotDown, resolveReviewerDown };',
|
|
23
26
|
)();
|
|
24
|
-
const {
|
|
27
|
+
const {
|
|
28
|
+
classifyReviewerGateOutcome,
|
|
29
|
+
classifyCopilotOutcome,
|
|
30
|
+
hasCodeConcernFinding,
|
|
31
|
+
buildUserReview,
|
|
32
|
+
resolveCopilotDown,
|
|
33
|
+
resolveReviewerDown,
|
|
34
|
+
} = productionModule;
|
|
35
|
+
|
|
36
|
+
function copilotFinding(overrides) {
|
|
37
|
+
return {
|
|
38
|
+
file: 'a.py',
|
|
39
|
+
line: 1,
|
|
40
|
+
severity: 'P1',
|
|
41
|
+
category: 'bug',
|
|
42
|
+
tier: 'self-healing',
|
|
43
|
+
title: 't',
|
|
44
|
+
detail: 'd',
|
|
45
|
+
replyToCommentId: null,
|
|
46
|
+
...overrides,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
25
49
|
|
|
26
50
|
function copilotResult(overrides) {
|
|
27
51
|
return {
|
|
@@ -34,26 +58,26 @@ function copilotResult(overrides) {
|
|
|
34
58
|
}
|
|
35
59
|
|
|
36
60
|
test('an out-of-usage Copilot result (down) routes to the down kind', () => {
|
|
37
|
-
const outcome =
|
|
61
|
+
const outcome = classifyReviewerGateOutcome(copilotResult({ clean: true, down: true }));
|
|
38
62
|
assert.equal(outcome.kind, 'down');
|
|
39
63
|
});
|
|
40
64
|
|
|
41
65
|
test('a down Copilot result routes to down even when clean is false', () => {
|
|
42
|
-
const outcome =
|
|
66
|
+
const outcome = classifyReviewerGateOutcome(copilotResult({ clean: false, down: true }));
|
|
43
67
|
assert.equal(outcome.kind, 'down');
|
|
44
68
|
});
|
|
45
69
|
|
|
46
70
|
test('a dead Copilot gate agent retries rather than passing', () => {
|
|
47
|
-
assert.equal(
|
|
71
|
+
assert.equal(classifyReviewerGateOutcome(null).kind, 'retry');
|
|
48
72
|
});
|
|
49
73
|
|
|
50
74
|
test('a reachable Copilot gate with no findings and no clean verdict retries', () => {
|
|
51
|
-
const outcome =
|
|
75
|
+
const outcome = classifyReviewerGateOutcome(copilotResult({ clean: false, down: false }));
|
|
52
76
|
assert.equal(outcome.kind, 'retry');
|
|
53
77
|
});
|
|
54
78
|
|
|
55
79
|
test('Copilot findings route to a fix when Copilot is reachable and not down', () => {
|
|
56
|
-
const outcome =
|
|
80
|
+
const outcome = classifyReviewerGateOutcome(
|
|
57
81
|
copilotResult({
|
|
58
82
|
findings: [
|
|
59
83
|
{
|
|
@@ -71,6 +95,61 @@ test('Copilot findings route to a fix when Copilot is reachable and not down', (
|
|
|
71
95
|
assert.equal(outcome.kind, 'fix');
|
|
72
96
|
});
|
|
73
97
|
|
|
98
|
+
test('self-healing-only Copilot findings route to a fix when Copilot is reachable and not down', () => {
|
|
99
|
+
const outcome = classifyCopilotOutcome(
|
|
100
|
+
copilotResult({
|
|
101
|
+
findings: [copilotFinding({ tier: 'self-healing' })],
|
|
102
|
+
}),
|
|
103
|
+
);
|
|
104
|
+
assert.equal(outcome.kind, 'fix');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('a code-concern Copilot finding routes to user-review rather than an auto-fix', () => {
|
|
108
|
+
const outcome = classifyCopilotOutcome(
|
|
109
|
+
copilotResult({
|
|
110
|
+
reviewUrl: 'https://github.com/o/r/pull/1#pullrequestreview-9',
|
|
111
|
+
findings: [copilotFinding({ tier: 'code-concern', severity: 'P0' })],
|
|
112
|
+
}),
|
|
113
|
+
);
|
|
114
|
+
assert.equal(outcome.kind, 'user-review');
|
|
115
|
+
assert.equal(outcome.findings.length, 1);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('a mixed round with one code-concern finding routes the whole round to user-review', () => {
|
|
119
|
+
const outcome = classifyCopilotOutcome(
|
|
120
|
+
copilotResult({
|
|
121
|
+
findings: [
|
|
122
|
+
copilotFinding({ tier: 'self-healing' }),
|
|
123
|
+
copilotFinding({ tier: 'code-concern', file: 'b.py', line: 2 }),
|
|
124
|
+
],
|
|
125
|
+
}),
|
|
126
|
+
);
|
|
127
|
+
assert.equal(outcome.kind, 'user-review');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('hasCodeConcernFinding treats a missing or unexpected tier as a code concern', () => {
|
|
131
|
+
assert.equal(hasCodeConcernFinding([copilotFinding({ tier: 'self-healing' })]), false);
|
|
132
|
+
assert.equal(hasCodeConcernFinding([copilotFinding({ tier: 'code-concern' })]), true);
|
|
133
|
+
assert.equal(hasCodeConcernFinding([{ file: 'a.py', line: 1 }]), true);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('buildUserReview carries the review URL and pares findings to the triage fields', () => {
|
|
137
|
+
const reviewUrl = 'https://github.com/o/r/pull/1#pullrequestreview-9';
|
|
138
|
+
const userReview = buildUserReview(
|
|
139
|
+
copilotResult({ reviewUrl }),
|
|
140
|
+
[copilotFinding({ tier: 'code-concern', severity: 'P0', title: 'unsafe eval', detail: 'ignored' })],
|
|
141
|
+
);
|
|
142
|
+
assert.equal(userReview.reviewUrl, reviewUrl);
|
|
143
|
+
assert.deepEqual(userReview.findings, [
|
|
144
|
+
{ file: 'a.py', line: 1, severity: 'P0', tier: 'code-concern', title: 'unsafe eval' },
|
|
145
|
+
]);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('buildUserReview defaults a missing review URL to an empty string', () => {
|
|
149
|
+
const userReview = buildUserReview(copilotResult({}), [copilotFinding({ tier: 'code-concern' })]);
|
|
150
|
+
assert.equal(userReview.reviewUrl, '');
|
|
151
|
+
});
|
|
152
|
+
|
|
74
153
|
test('COPILOT_SCHEMA carries a required down field', () => {
|
|
75
154
|
const schemaStart = convergeSource.indexOf('const COPILOT_SCHEMA =');
|
|
76
155
|
const schemaEnd = convergeSource.indexOf('const REVIEWER_AVAILABILITY_SCHEMA =');
|
|
@@ -85,6 +164,52 @@ test('COPILOT_SCHEMA carries a required down field', () => {
|
|
|
85
164
|
);
|
|
86
165
|
});
|
|
87
166
|
|
|
167
|
+
test('the tier enum constant names both routing tiers', () => {
|
|
168
|
+
const tiersMatch = convergeSource.match(/const COPILOT_FINDING_TIERS = \[([^\]]*)\]/);
|
|
169
|
+
assert.notEqual(tiersMatch, null, 'expected a COPILOT_FINDING_TIERS constant');
|
|
170
|
+
assert.match(tiersMatch[1], /'self-healing'/, 'expected the tier enum to name self-healing');
|
|
171
|
+
assert.match(tiersMatch[1], /'code-concern'/, 'expected the tier enum to name code-concern');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('COPILOT_SCHEMA findings carry a required tier enum drawn from the tier constant', () => {
|
|
175
|
+
const schemaStart = convergeSource.indexOf('const COPILOT_FINDINGS_SCHEMA =');
|
|
176
|
+
const schemaEnd = convergeSource.indexOf('const COPILOT_SCHEMA =');
|
|
177
|
+
assert.notEqual(schemaStart, -1, 'expected COPILOT_FINDINGS_SCHEMA to exist');
|
|
178
|
+
const schemaSource = convergeSource.slice(schemaStart, schemaEnd);
|
|
179
|
+
assert.match(schemaSource, /tier:\s*\{/, 'expected a tier property on the Copilot finding schema');
|
|
180
|
+
assert.match(schemaSource, /enum:\s*COPILOT_FINDING_TIERS/, 'expected the tier enum to draw from the tier constant');
|
|
181
|
+
assert.match(schemaSource, /required:\s*\[[^\]]*'tier'[^\]]*\]/, 'expected tier to be a required finding field');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test('COPILOT_SCHEMA carries a reviewUrl the user-review payload links to', () => {
|
|
185
|
+
const schemaStart = convergeSource.indexOf('const COPILOT_SCHEMA =');
|
|
186
|
+
const schemaEnd = convergeSource.indexOf('const REVIEWER_AVAILABILITY_SCHEMA =');
|
|
187
|
+
const schemaSource = convergeSource.slice(schemaStart, schemaEnd);
|
|
188
|
+
assert.match(schemaSource, /reviewUrl:\s*\{\s*type:\s*'string'/, 'expected a reviewUrl string field on COPILOT_SCHEMA');
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test('the Copilot gate prompt tiers each finding and returns the review URL', () => {
|
|
192
|
+
const copilotPrompt = functionBody('runCopilotGate');
|
|
193
|
+
assert.match(copilotPrompt, /tier 'self-healing'/, 'expected the prompt to define the self-healing tier');
|
|
194
|
+
assert.match(copilotPrompt, /tier 'code-concern'/, 'expected the prompt to define the code-concern tier');
|
|
195
|
+
assert.match(copilotPrompt, /in doubt/i, 'expected the prompt to default to code-concern in doubt');
|
|
196
|
+
assert.match(copilotPrompt, /reviewUrl set to the Copilot review html_url/, 'expected the findings branch to return the review URL');
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test('the COPILOT phase returns blocker user-review with the findings carried through on a code-concern outcome', () => {
|
|
200
|
+
const copilotPhaseStart = convergeSource.indexOf("if (phase === 'COPILOT') {");
|
|
201
|
+
const finalizePhaseStart = convergeSource.indexOf("if (phase === 'FINALIZE') {", copilotPhaseStart);
|
|
202
|
+
const copilotPhase = convergeSource.slice(copilotPhaseStart, finalizePhaseStart);
|
|
203
|
+
const userReviewBranchStart = copilotPhase.indexOf("copilotOutcome.kind === 'user-review'");
|
|
204
|
+
assert.notEqual(userReviewBranchStart, -1, 'expected the COPILOT phase to handle a user-review outcome');
|
|
205
|
+
const userReviewBranch = copilotPhase.slice(userReviewBranchStart, userReviewBranchStart + 500);
|
|
206
|
+
assert.match(userReviewBranch, /blocker:\s*'user-review'/, 'expected the branch to return blocker user-review');
|
|
207
|
+
assert.match(userReviewBranch, /converged:\s*false/, 'expected the branch to return converged false');
|
|
208
|
+
assert.match(userReviewBranch, /userReview:\s*buildUserReview\(copilot, copilotOutcome\.findings\)/, 'expected the branch to carry the user-review payload');
|
|
209
|
+
const autoFixIndex = userReviewBranch.indexOf('applyFixes');
|
|
210
|
+
assert.equal(autoFixIndex, -1, 'expected the user-review branch to not auto-fix the code-concern findings');
|
|
211
|
+
});
|
|
212
|
+
|
|
88
213
|
test('the Copilot gate prompt detects an out-of-usage notice and returns a down result', () => {
|
|
89
214
|
const copilotPrompt = functionBody('runCopilotGate');
|
|
90
215
|
assert.match(
|
|
@@ -412,12 +537,12 @@ test('openStandardsFollowUpOnce gates spawnStandardsFollowUp behind the run-once
|
|
|
412
537
|
);
|
|
413
538
|
});
|
|
414
539
|
|
|
415
|
-
test('
|
|
540
|
+
test('every standards-deferral call site routes the create through openStandardsFollowUpOnce', () => {
|
|
416
541
|
const onceCalls = convergeSource.match(/await openStandardsFollowUpOnce\(/g) || [];
|
|
417
542
|
assert.equal(
|
|
418
543
|
onceCalls.length,
|
|
419
|
-
|
|
420
|
-
'expected the converge-round and
|
|
544
|
+
3,
|
|
545
|
+
'expected the converge-round, terminal-Bugbot, and Copilot standards call sites to all defer to openStandardsFollowUpOnce',
|
|
421
546
|
);
|
|
422
547
|
const directCreates = convergeSource.match(/await spawnStandardsFollowUp\(/g) || [];
|
|
423
548
|
assert.equal(
|
|
@@ -497,8 +622,8 @@ function loadParseDeferredPr() {
|
|
|
497
622
|
test('parseDeferredPr parses a full canonical hardening PR URL into its coordinates', () => {
|
|
498
623
|
const parseDeferredPr = loadParseDeferredPr();
|
|
499
624
|
assert.deepEqual(
|
|
500
|
-
parseDeferredPr('https://github.com/jl-cmd/claude-
|
|
501
|
-
{ owner: 'jl-cmd', repo: 'claude-
|
|
625
|
+
parseDeferredPr('https://github.com/jl-cmd/claude-dev-env/pull/824'),
|
|
626
|
+
{ owner: 'jl-cmd', repo: 'claude-dev-env', prNumber: 824 },
|
|
502
627
|
);
|
|
503
628
|
});
|
|
504
629
|
|
|
@@ -679,7 +804,7 @@ test('a hardening-commit that opens no PR (empty hardeningPrUrl) leaves the run-
|
|
|
679
804
|
test('a later standards-only round resolves its own review threads after the follow-up issue was already filed', async () => {
|
|
680
805
|
const recordedCalls = [];
|
|
681
806
|
const issueFiledNoHardening = {
|
|
682
|
-
issueUrl: 'https://github.com/jl-cmd/claude-
|
|
807
|
+
issueUrl: 'https://github.com/jl-cmd/claude-dev-env/issues/900',
|
|
683
808
|
hardeningEdited: false,
|
|
684
809
|
hardeningRepoPath: '',
|
|
685
810
|
hardeningBranch: '',
|
|
@@ -701,7 +826,7 @@ test('a later standards-only round resolves its own review threads after the fol
|
|
|
701
826
|
);
|
|
702
827
|
assert.equal(
|
|
703
828
|
resolveCalls[0].context.issueUrl,
|
|
704
|
-
'https://github.com/jl-cmd/claude-
|
|
829
|
+
'https://github.com/jl-cmd/claude-dev-env/issues/900',
|
|
705
830
|
'expected the resolve step to reference the already-filed follow-up issue in its inline reply',
|
|
706
831
|
);
|
|
707
832
|
});
|
|
@@ -709,7 +834,7 @@ test('a later standards-only round resolves its own review threads after the fol
|
|
|
709
834
|
test('a reuse-path standards round carrying no review threads spawns no thread-resolution agent', async () => {
|
|
710
835
|
const recordedCalls = [];
|
|
711
836
|
const issueFiledNoHardening = {
|
|
712
|
-
issueUrl: 'https://github.com/jl-cmd/claude-
|
|
837
|
+
issueUrl: 'https://github.com/jl-cmd/claude-dev-env/issues/901',
|
|
713
838
|
hardeningEdited: false,
|
|
714
839
|
hardeningRepoPath: '',
|
|
715
840
|
hardeningBranch: '',
|
|
@@ -138,8 +138,8 @@ test('commitWithRecovery bounds the loop, re-verifies, and retries the commit on
|
|
|
138
138
|
assert.match(recoveryBody, /commitNeedsCodeRecovery\(/, 'expected the loop guard to call commitNeedsCodeRecovery');
|
|
139
139
|
assert.match(
|
|
140
140
|
recoveryBody,
|
|
141
|
-
/attempt\s*<\s*
|
|
142
|
-
'expected the loop to be bounded by
|
|
141
|
+
/attempt\s*<\s*COMMIT_RECOVERY_MAX_ATTEMPTS/,
|
|
142
|
+
'expected the commit-recovery loop to be bounded by the dedicated COMMIT_RECOVERY_MAX_ATTEMPTS cap of one',
|
|
143
143
|
);
|
|
144
144
|
assert.match(recoveryBody, /runRecoverEdit\(/, 'expected the loop to spawn the recover-edit fixer');
|
|
145
145
|
assert.match(recoveryBody, /runVerify\(/, 'expected the loop to re-verify after the fixer edit');
|