claude-dev-env 2.12.0 → 2.13.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/pr-loop/scripts/code_rules_gate.py +2 -3
- package/_shared/pr-loop/scripts/test_code_rules_gate.py +0 -26
- package/agents/CLAUDE.md +1 -2
- package/agents/pr-description-writer.md +11 -81
- package/bin/install.mjs +48 -9
- package/bin/install.prune.test.mjs +26 -0
- package/bin/install.settings-defaults.test.mjs +60 -33
- package/bin/install.test.mjs +2 -4
- package/commands/sr-loop.md +9 -5
- package/docs/CLAUDE.md +0 -1
- package/docs/references/CLAUDE.md +0 -1
- package/hooks/CLAUDE.md +1 -1
- package/hooks/blocking/CLAUDE.md +2 -27
- package/hooks/blocking/config/CLAUDE.md +1 -13
- package/hooks/blocking/conventional_pr_title_gate.py +1 -2
- package/hooks/blocking/send_user_file_open_locally_blocker.py +1 -1
- package/hooks/blocking/stop_dispatcher.py +4 -4
- package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +0 -3
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +6 -7
- package/hooks/blocking/test_send_user_file_open_locally_blocker.py +4 -3
- package/hooks/blocking/test_shared_stdin_adoption.py +0 -2
- package/hooks/diagnostic/CLAUDE.md +3 -3
- package/hooks/diagnostic/hook_log_extractor.py +2 -36
- package/hooks/diagnostic/hook_log_stop_wrapper.py +6 -155
- package/hooks/diagnostic/test_hook_log_extractor.py +8 -21
- package/hooks/diagnostic/test_hook_log_stop_wrapper.py +3 -331
- package/hooks/git-hooks/git_hooks_constants/__init__.py +0 -6
- package/hooks/git-hooks/pre_push.py +3 -85
- package/hooks/git-hooks/test_pre_push.py +0 -130
- package/hooks/hooks.json +0 -35
- package/hooks/hooks_constants/CLAUDE.md +0 -9
- package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +0 -11
- package/hooks/hooks_constants/convergence_branch_constants.py +0 -1
- package/hooks/hooks_constants/fable_spawn_gate_constants.py +3 -4
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +0 -8
- package/hooks/hooks_constants/send_user_file_open_locally_blocker_constants.py +1 -6
- package/hooks/hooks_constants/stop_dispatcher_constants.py +0 -1
- package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +1 -26
- package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +0 -5
- package/hooks/hooks_constants/test_stop_dispatcher_constants.py +0 -1
- package/hooks/lifecycle/CLAUDE.md +1 -3
- package/package.json +1 -1
- package/rules/CLAUDE.md +0 -2
- package/rules/gh-cli-conventions.md +0 -1
- package/rules/git-workflow.md +1 -3
- package/scripts/CLAUDE.md +0 -1
- package/scripts/_code_review_test_support.py +0 -95
- package/scripts/dev_env_scripts_constants/code_review_constants.py +9 -98
- package/scripts/dev_env_scripts_constants/test_code_review_constants.py +1 -44
- package/scripts/invoke_code_review.py +8 -451
- package/scripts/test_invoke_code_review.py +5 -61
- package/scripts/test_invoke_code_review_cli.py +1 -45
- package/scripts/test_invoke_code_review_contract.py +1 -118
- package/settings.json +1 -10
- package/skills/CLAUDE.md +8 -0
- package/skills/autoconverge/reference/CLAUDE.md +1 -1
- package/skills/autoconverge/reference/convergence.md +7 -7
- package/skills/autoconverge/reference/gotchas.md +3 -10
- package/skills/autoconverge/workflow/converge.contract.test.mjs +53 -1388
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +4 -3
- package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +33 -493
- package/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +15 -97
- package/skills/autoconverge/workflow/converge.mjs +201 -238
- package/skills/autoconverge/workflow/converge.precatch.test.mjs +1 -1
- package/skills/bugteam/CONSTRAINTS.md +1 -1
- package/skills/bugteam/SKILL.md +9 -0
- package/skills/bugteam/reference/audit-and-teammates.md +10 -6
- package/skills/bugteam/test_skill_additions.py +9 -0
- package/skills/comments/SKILL.md +65 -0
- package/skills/descriptions/SKILL.md +51 -0
- package/skills/emergencies/SKILL.md +42 -0
- package/skills/imagegen/SKILL.md +21 -0
- package/skills/imagegen/scripts/config/__init__.py +1 -0
- package/skills/imagegen/scripts/config/constants.py +28 -0
- package/skills/imagegen/scripts/imagegen.py +45 -0
- package/skills/imagegen/scripts/imagegen_core.py +528 -0
- package/skills/imagegen/scripts/test_imagegen.py +204 -0
- package/skills/orchestrator/SKILL.md +2 -4
- package/skills/plan-to-pr/SKILL.md +6 -7
- package/skills/plan-to-pr/reference/final-validation-tasks.md +1 -1
- package/skills/plan-to-pr/reference/process-inventory.md +2 -2
- package/skills/plan-to-pr/reference/review-loop.md +2 -2
- package/skills/plan-to-pr/reference/run-record.schema.json +1 -1
- package/skills/plan-to-pr/reference/task-seeds.md +1 -1
- package/skills/plan-to-pr/reference/task-ticket.md +3 -4
- package/skills/plan-to-pr/scripts/config/constants.py +1 -2
- package/skills/plan-to-pr/scripts/test_validate_protocol.py +1 -1
- package/skills/plan-to-pr/test_skill_contract.py +2 -2
- package/skills/pr-converge/SKILL.md +13 -16
- package/skills/pr-converge/reference/progress-checklist.md +6 -5
- package/skills/pr-converge/reference/state-schema.md +1 -27
- package/skills/pr-loop-cloud-transport/SKILL.md +1 -1
- package/skills/pr-loop-cloud-transport/reference/identity-and-hooks.md +3 -3
- package/skills/pr-loop-lifecycle/SKILL.md +1 -1
- package/skills/pr-loop-lifecycle/reference/teardown-publish-permissions.md +1 -1
- package/skills/prototype/SKILL.md +4 -4
- package/skills/prototype/reference/honest-limitations.md +4 -4
- package/skills/prototype/reference/promotion-tasks.md +2 -2
- package/skills/prototype/workflows/promotion.md +4 -6
- package/skills/reviews/SKILL.md +85 -0
- package/skills/small-cl/SKILL.md +52 -0
- package/agents/code-verifier.md +0 -74
- package/docs/PR_DESCRIPTION_GUIDE.md +0 -157
- package/docs/references/code-review-enforcement.md +0 -132
- package/hooks/blocking/code_review_enforcement_config_bootstrap.py +0 -53
- package/hooks/blocking/code_review_gate_deny.py +0 -74
- package/hooks/blocking/code_review_pr_create_gate.py +0 -198
- package/hooks/blocking/code_review_push_gate.py +0 -145
- package/hooks/blocking/code_review_stamp_directory_write_blocker.py +0 -348
- package/hooks/blocking/code_review_stamp_store.py +0 -233
- package/hooks/blocking/code_review_stamp_write_blocker_parts/__init__.py +0 -7
- package/hooks/blocking/code_review_stamp_write_blocker_parts/conftest.py +0 -15
- package/hooks/blocking/code_review_stamp_write_blocker_parts/obfuscated_stamp_path_reference.py +0 -212
- package/hooks/blocking/code_review_stamp_write_blocker_parts/split_directory_change_into_stamp.py +0 -138
- package/hooks/blocking/code_review_stamp_write_blocker_parts/test_obfuscated_stamp_path_reference.py +0 -49
- package/hooks/blocking/code_review_stamp_write_blocker_parts/test_split_directory_change_into_stamp.py +0 -38
- package/hooks/blocking/code_verifier_spawn_preflight_gate.py +0 -483
- package/hooks/blocking/config/code_review_enforcement_constants.py +0 -143
- package/hooks/blocking/config/test_code_review_enforcement_constants.py +0 -166
- package/hooks/blocking/config/verified_commit_constants.py +0 -160
- package/hooks/blocking/config/verified_commit_context_constants.py +0 -21
- package/hooks/blocking/config/verified_commit_gate_output_constants.py +0 -14
- package/hooks/blocking/conftest.py +0 -123
- package/hooks/blocking/convergence_gate_blocker.py +0 -310
- package/hooks/blocking/pr_converge_bugteam_enforcer.py +0 -170
- package/hooks/blocking/pr_description_body_audit.py +0 -148
- package/hooks/blocking/pr_description_command_parser.py +0 -237
- package/hooks/blocking/pr_description_enforcer.py +0 -227
- package/hooks/blocking/pr_description_pr_number.py +0 -155
- package/hooks/blocking/pr_description_proof_of_work.py +0 -385
- package/hooks/blocking/pr_description_readability.py +0 -364
- package/hooks/blocking/reviewer_spawn_gate.py +0 -182
- package/hooks/blocking/test_code_review_enforcement_config_bootstrap.py +0 -62
- package/hooks/blocking/test_code_review_gate_deny.py +0 -54
- package/hooks/blocking/test_code_review_pr_create_gate.py +0 -199
- package/hooks/blocking/test_code_review_push_gate.py +0 -205
- package/hooks/blocking/test_code_review_stamp_directory_write_blocker.py +0 -199
- package/hooks/blocking/test_code_review_stamp_store.py +0 -205
- package/hooks/blocking/test_code_verifier_spawn_preflight_gate.py +0 -710
- package/hooks/blocking/test_code_verifier_tools_contract.py +0 -28
- package/hooks/blocking/test_convergence_gate_blocker.py +0 -428
- package/hooks/blocking/test_pr_converge_bugteam_enforcer.py +0 -311
- package/hooks/blocking/test_pr_converge_bugteam_enforcer_state_tolerance.py +0 -184
- package/hooks/blocking/test_pr_description_enforcer.py +0 -221
- package/hooks/blocking/test_pr_description_enforcer_body_audit.py +0 -247
- package/hooks/blocking/test_pr_description_enforcer_body_rules.py +0 -493
- package/hooks/blocking/test_pr_description_enforcer_command_parser.py +0 -366
- package/hooks/blocking/test_pr_description_enforcer_pr_number.py +0 -159
- package/hooks/blocking/test_pr_description_enforcer_proof_gate.py +0 -175
- package/hooks/blocking/test_pr_description_enforcer_readability.py +0 -443
- package/hooks/blocking/test_pr_description_proof_of_work.py +0 -313
- package/hooks/blocking/test_reviewer_spawn_gate.py +0 -230
- package/hooks/blocking/test_verdict_directory_write_blocker.py +0 -804
- package/hooks/blocking/test_verification_verdict_store.py +0 -974
- package/hooks/blocking/test_verified_commit_config_bootstrap.py +0 -67
- package/hooks/blocking/test_verified_commit_docs_delta.py +0 -176
- package/hooks/blocking/test_verified_commit_gate.py +0 -581
- package/hooks/blocking/test_verified_commit_gate_additional_context.py +0 -134
- package/hooks/blocking/test_verified_commit_message_accuracy_blocker.py +0 -131
- package/hooks/blocking/test_verifier_verdict_minter.py +0 -299
- package/hooks/blocking/tests/test_verified_commit_gate.py +0 -41
- package/hooks/blocking/verdict_directory_write_blocker.py +0 -687
- package/hooks/blocking/verification_verdict_store.py +0 -1039
- package/hooks/blocking/verified_commit_config_bootstrap.py +0 -63
- package/hooks/blocking/verified_commit_gate.py +0 -113
- package/hooks/blocking/verified_commit_gate_parts/CLAUDE.md +0 -28
- package/hooks/blocking/verified_commit_gate_parts/__init__.py +0 -1
- package/hooks/blocking/verified_commit_gate_parts/command_tokenization.py +0 -174
- package/hooks/blocking/verified_commit_gate_parts/deny_payload.py +0 -53
- package/hooks/blocking/verified_commit_gate_parts/deny_reason.py +0 -80
- package/hooks/blocking/verified_commit_gate_parts/directory_resolution.py +0 -170
- package/hooks/blocking/verified_commit_gate_parts/gated_invocations.py +0 -217
- package/hooks/blocking/verified_commit_gate_parts/tests/conftest.py +0 -10
- package/hooks/blocking/verified_commit_gate_parts/tests/test_command_tokenization.py +0 -94
- package/hooks/blocking/verified_commit_gate_parts/tests/test_deny_payload.py +0 -17
- package/hooks/blocking/verified_commit_gate_parts/tests/test_deny_reason.py +0 -38
- package/hooks/blocking/verified_commit_gate_parts/tests/test_directory_resolution.py +0 -71
- package/hooks/blocking/verified_commit_gate_parts/tests/test_gated_invocations.py +0 -96
- package/hooks/blocking/verified_commit_message_accuracy_blocker.py +0 -167
- package/hooks/blocking/verifier_verdict_minter.py +0 -280
- package/hooks/hooks_constants/code_verifier_spawn_preflight_gate_constants.py +0 -64
- package/hooks/hooks_constants/convergence_gate_blocker_constants.py +0 -53
- package/hooks/hooks_constants/pr_converge_bugteam_enforcer_constants.py +0 -55
- package/hooks/hooks_constants/pr_converge_bugteam_enforcer_state.py +0 -67
- package/hooks/hooks_constants/pr_description_enforcer_constants.py +0 -167
- package/hooks/hooks_constants/pr_description_proof_of_work_constants.py +0 -107
- package/hooks/hooks_constants/reviewer_spawn_gate_constants.py +0 -41
- package/hooks/hooks_constants/test_pr_description_enforcer_constants.py +0 -292
- package/hooks/lifecycle/pr_converge_bugteam_skill_tracker.py +0 -198
- package/hooks/lifecycle/test_pr_converge_bugteam_skill_tracker.py +0 -283
- package/rules/proof-of-work-pr-comments.md +0 -22
- package/rules/verified-commit-gate-skip.md +0 -4
- package/scripts/Show-Asset.ps1 +0 -106
|
@@ -720,12 +720,14 @@ function loadStandardsFollowUpRuntime(recordedCalls, standardsEditResult, harden
|
|
|
720
720
|
" if (taskName === 'hardening-commit') return hardeningCommitResult;\n" +
|
|
721
721
|
' return {};\n' +
|
|
722
722
|
'}\n' +
|
|
723
|
-
'async function
|
|
723
|
+
'async function runReviewTask() {\n' +
|
|
724
724
|
' return { passed: true };\n' +
|
|
725
725
|
'}\n' +
|
|
726
|
-
'function
|
|
726
|
+
'function reviewPassed() {\n' +
|
|
727
727
|
' return true;\n' +
|
|
728
728
|
'}\n' +
|
|
729
|
+
"async function captureReviewedSurfaceHash() { return 'a'.repeat(64); }\n" +
|
|
730
|
+
'async function rebindReviewedSurface({ reviewedSurfaceHash }) { return reviewedSurfaceHash; }\n' +
|
|
729
731
|
'function log() {}\n' +
|
|
730
732
|
`${convergeSource.match(/const GITHUB_ISSUE_URL_PATTERN = .+/)[0]}\n` +
|
|
731
733
|
`${extractCallableSource('canonicalizeIssueUrl')}\n` +
|
|
@@ -1005,4 +1007,3 @@ test('resolveReviewerDown reports available (fail-open) when the probe entry is
|
|
|
1005
1007
|
test('resolveReviewerDown reports available when the probe entry explicitly reports available', () => {
|
|
1006
1008
|
assert.equal(resolveReviewerDown({ down: false, reason: 'available' }, false), false);
|
|
1007
1009
|
});
|
|
1008
|
-
|
|
@@ -1,493 +1,33 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
assert.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
function
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
assert.
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
assert.
|
|
30
|
-
|
|
31
|
-
assert.
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const pureModule = new Function(
|
|
36
|
-
`${functionSource('commitNeedsCodeRecovery')}\n` + 'return { commitNeedsCodeRecovery };',
|
|
37
|
-
)();
|
|
38
|
-
|
|
39
|
-
const { commitNeedsCodeRecovery } = pureModule;
|
|
40
|
-
|
|
41
|
-
test('a dead commit agent (null result) does not need code recovery', () => {
|
|
42
|
-
assert.equal(commitNeedsCodeRecovery(null), false);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
test('a pushed commit does not need code recovery even with the flag and detail set', () => {
|
|
46
|
-
assert.equal(
|
|
47
|
-
commitNeedsCodeRecovery({ pushed: true, blockedNeedingEdit: true, blockerDetail: 'CODE_RULES' }),
|
|
48
|
-
false,
|
|
49
|
-
);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test('a transient failure (flag false, empty detail) does not need code recovery', () => {
|
|
53
|
-
assert.equal(
|
|
54
|
-
commitNeedsCodeRecovery({ pushed: false, blockedNeedingEdit: false, blockerDetail: '' }),
|
|
55
|
-
false,
|
|
56
|
-
);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test('a code-edit block (flag true, concrete detail) needs code recovery', () => {
|
|
60
|
-
assert.equal(
|
|
61
|
-
commitNeedsCodeRecovery({
|
|
62
|
-
pushed: false,
|
|
63
|
-
blockedNeedingEdit: true,
|
|
64
|
-
blockerDetail: 'BLOCKED [code-rules]: collection param needs all_ prefix',
|
|
65
|
-
}),
|
|
66
|
-
true,
|
|
67
|
-
);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
test('a flagged block with an empty detail does not need code recovery', () => {
|
|
71
|
-
assert.equal(
|
|
72
|
-
commitNeedsCodeRecovery({ pushed: false, blockedNeedingEdit: true, blockerDetail: '' }),
|
|
73
|
-
false,
|
|
74
|
-
);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test('a detail without the flag does not need code recovery', () => {
|
|
78
|
-
assert.equal(
|
|
79
|
-
commitNeedsCodeRecovery({ pushed: false, blockedNeedingEdit: false, blockerDetail: 'some text' }),
|
|
80
|
-
false,
|
|
81
|
-
);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
test('FIX_SCHEMA declares blockedNeedingEdit and blockerDetail as properties', () => {
|
|
85
|
-
const fixSchema = schemaSource('FIX_SCHEMA', 'EDIT_SCHEMA');
|
|
86
|
-
assert.match(fixSchema, /blockedNeedingEdit:\s*\{[\s\S]*?type:\s*'boolean'/);
|
|
87
|
-
assert.match(fixSchema, /blockerDetail:\s*\{[\s\S]*?type:\s*'string'/);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test('FIX_SCHEMA requires blockedNeedingEdit and blockerDetail', () => {
|
|
91
|
-
const fixSchema = schemaSource('FIX_SCHEMA', 'EDIT_SCHEMA');
|
|
92
|
-
const requiredMatch = /required:\s*\[([^\]]*)\]/.exec(fixSchema);
|
|
93
|
-
assert.ok(requiredMatch, 'expected FIX_SCHEMA to carry a required array');
|
|
94
|
-
assert.match(requiredMatch[1], /blockedNeedingEdit/);
|
|
95
|
-
assert.match(requiredMatch[1], /blockerDetail/);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test('FIX_RECOVERY_MAX_ATTEMPTS is declared and bounds the recovery loop at 2', () => {
|
|
99
|
-
assert.match(constantLine('FIX_RECOVERY_MAX_ATTEMPTS'), /=\s*2\s*$/);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
test('the commit path in runCodeEditorTask separates an edit-requiring block from a transient failure', () => {
|
|
103
|
-
const commitBody = functionSource('runCodeEditorTask');
|
|
104
|
-
assert.match(commitBody, /blockedNeedingEdit/, 'expected the edit-block flag to be set in the prompt');
|
|
105
|
-
assert.match(commitBody, /blockerDetail/, 'expected the verbatim blocker detail to be requested');
|
|
106
|
-
assert.match(
|
|
107
|
-
commitBody,
|
|
108
|
-
/code_rules_gate|CODE_RULES/,
|
|
109
|
-
'expected the commit prompt to name the CODE_RULES commit gate as an edit-requiring block',
|
|
110
|
-
);
|
|
111
|
-
assert.match(
|
|
112
|
-
commitBody,
|
|
113
|
-
/transient/i,
|
|
114
|
-
'expected the commit prompt to name the transient (non-code) failure case',
|
|
115
|
-
);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
test('the commit-recover task in runCodeEditorTask is a clean-coder edit step bound to the blocker detail and leaves changes uncommitted', () => {
|
|
119
|
-
const recoverBody = functionSource('runCodeEditorTask');
|
|
120
|
-
assert.match(recoverBody, /agentType:\s*'clean-coder'/, 'expected the fixer to use clean-coder');
|
|
121
|
-
assert.match(recoverBody, /schema:\s*EDIT_SCHEMA/, 'expected the fixer to reuse EDIT_SCHEMA');
|
|
122
|
-
assert.match(recoverBody, /task === 'commit-recover'/, 'expected the commit-recover task branch');
|
|
123
|
-
assert.match(recoverBody, /context\.blockerDetail/, 'expected the fixer prompt to consume the blocker detail');
|
|
124
|
-
assert.match(
|
|
125
|
-
recoverBody,
|
|
126
|
-
/only the (?:violation|finding|block)/i,
|
|
127
|
-
'expected the fixer to be scoped to only the blocking violation',
|
|
128
|
-
);
|
|
129
|
-
assert.match(
|
|
130
|
-
recoverBody,
|
|
131
|
-
/do not commit and do not push|NO commit and NO push|Do NOT commit|leave .*uncommitted|uncommitted/i,
|
|
132
|
-
'expected the fixer to leave its fix uncommitted for the re-verify and retry commit',
|
|
133
|
-
);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
test('commitWithRecovery bounds the loop, re-verifies, and retries the commit on a code block', () => {
|
|
137
|
-
const recoveryBody = functionSource('commitWithRecovery');
|
|
138
|
-
assert.match(recoveryBody, /commitNeedsCodeRecovery\(/, 'expected the loop guard to call commitNeedsCodeRecovery');
|
|
139
|
-
assert.match(
|
|
140
|
-
recoveryBody,
|
|
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
|
-
);
|
|
144
|
-
assert.match(recoveryBody, /runRecoverEdit\(/, 'expected the loop to spawn the recover-edit fixer');
|
|
145
|
-
assert.match(recoveryBody, /runVerify\(/, 'expected the loop to re-verify after the fixer edit');
|
|
146
|
-
assert.match(recoveryBody, /verdictPassed\(/, 'expected a fresh verdict to gate the retry commit');
|
|
147
|
-
assert.match(recoveryBody, /runCommit\(/, 'expected the loop to retry the commit');
|
|
148
|
-
const editGuardIndex = recoveryBody.search(/edited\s*!==\s*true/);
|
|
149
|
-
const verifyGateIndex = recoveryBody.search(/verdictPassed\(/);
|
|
150
|
-
assert.notEqual(editGuardIndex, -1, 'expected an early break when the fixer made no edit');
|
|
151
|
-
assert.ok(
|
|
152
|
-
editGuardIndex < verifyGateIndex,
|
|
153
|
-
'expected the no-edit break to precede the re-verify gate',
|
|
154
|
-
);
|
|
155
|
-
const recoverEditIndex = recoveryBody.search(/runRecoverEdit\(/);
|
|
156
|
-
const reverifyIndex = recoveryBody.search(/runVerify\(/);
|
|
157
|
-
const retryCommitIndex = recoveryBody.lastIndexOf('runCommit(');
|
|
158
|
-
assert.ok(
|
|
159
|
-
recoverEditIndex < reverifyIndex && reverifyIndex < retryCommitIndex,
|
|
160
|
-
'expected order recover-edit -> re-verify -> retry commit, so a verify/commit swap fails',
|
|
161
|
-
);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
test('applyFixes routes through the fix-edit task and fixerWithRecovery', () => {
|
|
165
|
-
const applyFixesBody = functionSource('applyFixes');
|
|
166
|
-
assert.match(applyFixesBody, /runCodeEditorTask\('fix-edit'/, "expected applyFixes to call runCodeEditorTask('fix-edit')");
|
|
167
|
-
assert.match(applyFixesBody, /fixerWithRecovery\(/, 'expected applyFixes to call fixerWithRecovery');
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
test('applyFixes delegates to fixerWithRecovery, which grades fixes with a separate verifier from the fixer', () => {
|
|
171
|
-
const applyFixesBody = functionSource('applyFixes');
|
|
172
|
-
assert.match(
|
|
173
|
-
applyFixesBody,
|
|
174
|
-
/fixerWithRecovery\(head, findings, sourceLabel\)/,
|
|
175
|
-
'expected applyFixes to delegate the verify and commit flow to fixerWithRecovery',
|
|
176
|
-
);
|
|
177
|
-
const recoveryBody = functionSource('fixerWithRecovery');
|
|
178
|
-
assert.match(
|
|
179
|
-
recoveryBody,
|
|
180
|
-
/runVerifierTask\('fix-verify'/,
|
|
181
|
-
'expected fixerWithRecovery to grade the fixes with the code-verifier task so the verdict is independent of the fixer',
|
|
182
|
-
);
|
|
183
|
-
assert.match(
|
|
184
|
-
recoveryBody,
|
|
185
|
-
/runFixerTask\('commit'/,
|
|
186
|
-
'expected fixerWithRecovery to commit through the clean-coder fixer task',
|
|
187
|
-
);
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
test('fixerWithRecovery runs verify via runVerifierTask and edits/commits via runFixerTask', () => {
|
|
191
|
-
const recoveryBody = functionSource('fixerWithRecovery');
|
|
192
|
-
assert.match(
|
|
193
|
-
recoveryBody,
|
|
194
|
-
/runVerifierTask\('fix-verify'/,
|
|
195
|
-
'expected fixerWithRecovery to run the verify step through the separate verifier task',
|
|
196
|
-
);
|
|
197
|
-
assert.match(
|
|
198
|
-
recoveryBody,
|
|
199
|
-
/runFixerTask\('commit'/,
|
|
200
|
-
'expected fixerWithRecovery to run the commit step through the fixer task',
|
|
201
|
-
);
|
|
202
|
-
assert.doesNotMatch(
|
|
203
|
-
recoveryBody,
|
|
204
|
-
/runFixerTask\('(?:verify-commit|fix-verify)'/,
|
|
205
|
-
'expected the verify step never to route through the fixer task — the verifier grades a different agent than the one that edits',
|
|
206
|
-
);
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
test('the fix-path verify routes through a different helper and agentType than the fix-path edits, mirroring the repair path', () => {
|
|
210
|
-
const recoveryBody = functionSource('fixerWithRecovery');
|
|
211
|
-
const verifyHelperMatch = /(\w+)\('fix-verify'/.exec(recoveryBody);
|
|
212
|
-
const editHelperMatch = /(\w+)\('commit'/.exec(recoveryBody);
|
|
213
|
-
assert.ok(verifyHelperMatch, 'expected a verify call naming its task helper');
|
|
214
|
-
assert.ok(editHelperMatch, 'expected an edit/commit call naming its task helper');
|
|
215
|
-
assert.equal(verifyHelperMatch[1], 'runVerifierTask', 'expected verify to route through runVerifierTask (code-verifier)');
|
|
216
|
-
assert.equal(editHelperMatch[1], 'runFixerTask', 'expected commit to route through runFixerTask (clean-coder)');
|
|
217
|
-
assert.notEqual(
|
|
218
|
-
verifyHelperMatch[1],
|
|
219
|
-
editHelperMatch[1],
|
|
220
|
-
'expected the verify step to route through a different helper than the edit/commit step',
|
|
221
|
-
);
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
test('the verifier carries a fix-verify task so the fix path verdict comes from the verifier', () => {
|
|
225
|
-
const verifierBody = functionSource('runVerifierTask');
|
|
226
|
-
assert.match(verifierBody, /task === 'fix-verify'/, 'expected runVerifierTask to handle the fix-verify task');
|
|
227
|
-
assert.match(verifierBody, /buildVerdictFenceSteps\(/, 'expected the fix-verify task to emit a verdict fence');
|
|
228
|
-
assert.match(verifierBody, /agentType:\s*'code-verifier'/, 'expected the fix-verify task to run as code-verifier');
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
test('runFixerTask no longer verifies its own edits — no code-verifier verify-commit task remains', () => {
|
|
232
|
-
const fixerBody = functionSource('runFixerTask');
|
|
233
|
-
assert.doesNotMatch(
|
|
234
|
-
fixerBody,
|
|
235
|
-
/task === 'verify-commit'/,
|
|
236
|
-
'expected the fixer to no longer carry the verify-commit task that graded its own edits',
|
|
237
|
-
);
|
|
238
|
-
assert.doesNotMatch(
|
|
239
|
-
fixerBody,
|
|
240
|
-
/agentType:\s*'code-verifier'/,
|
|
241
|
-
'expected the fixer to be clean-coder only — a separate verifier grades the working tree',
|
|
242
|
-
);
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
test('repairConvergence routes its commit through commitWithRecovery wired to the repair-path steps', () => {
|
|
246
|
-
const repairBody = functionSource('repairConvergence');
|
|
247
|
-
assert.match(repairBody, /commitWithRecovery\(/, 'expected repairConvergence to call commitWithRecovery');
|
|
248
|
-
assert.match(repairBody, /runCodeEditorTask\(/, 'expected repairConvergence to use runCodeEditorTask');
|
|
249
|
-
assert.match(repairBody, /runVerifierTask\(/, 'expected repairConvergence to use runVerifierTask');
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
test('the round-loop fix-stalled blockers survive the recovery wiring', () => {
|
|
253
|
-
assert.match(convergeSource, /fix lens landed no push for/);
|
|
254
|
-
assert.match(convergeSource, /copilot fix lens landed no push for/);
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
const verifyObjectionModule = new Function(
|
|
258
|
-
`${functionSource('findLastVerdictFence')}\n` +
|
|
259
|
-
`${functionSource('parseLastVerdictFence')}\n` +
|
|
260
|
-
`${constantLine('VERIFY_OBJECTION_FALLBACK')}\n` +
|
|
261
|
-
`${functionSource('renderVerifyObjectionLine')}\n` +
|
|
262
|
-
`${functionSource('extractPreFenceProse')}\n` +
|
|
263
|
-
`${functionSource('extractVerifyObjection')}\n` +
|
|
264
|
-
'return { extractVerifyObjection, VERIFY_OBJECTION_FALLBACK };',
|
|
265
|
-
)();
|
|
266
|
-
|
|
267
|
-
const { extractVerifyObjection, VERIFY_OBJECTION_FALLBACK } = verifyObjectionModule;
|
|
268
|
-
|
|
269
|
-
test('extractVerifyObjection falls back for a non-string transcript', () => {
|
|
270
|
-
assert.equal(extractVerifyObjection(null), VERIFY_OBJECTION_FALLBACK);
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
test('extractVerifyObjection falls back when no verdict fence is present', () => {
|
|
274
|
-
assert.equal(
|
|
275
|
-
extractVerifyObjection('the verifier wrote prose with no verdict fence'),
|
|
276
|
-
VERIFY_OBJECTION_FALLBACK,
|
|
277
|
-
);
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
test('extractVerifyObjection falls back when the verdict fence carries no findings', () => {
|
|
281
|
-
const transcript = '```verdict\n{"all_pass": false, "findings": []}\n```';
|
|
282
|
-
assert.equal(extractVerifyObjection(transcript), VERIFY_OBJECTION_FALLBACK);
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
test('extractVerifyObjection renders each verdict finding as check then detail', () => {
|
|
286
|
-
const transcript =
|
|
287
|
-
'```verdict\n{"all_pass": false, "findings": [{"check": "Finding 1", "detail": "still over-blocks"}, {"check": "Finding 2", "detail": "boundary unchecked"}]}\n```';
|
|
288
|
-
const objection = extractVerifyObjection(transcript);
|
|
289
|
-
assert.match(objection, /1\. Finding 1 — still over-blocks/);
|
|
290
|
-
assert.match(objection, /2\. Finding 2 — boundary unchecked/);
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
test('extractVerifyObjection reads the LAST verdict fence', () => {
|
|
294
|
-
const transcript =
|
|
295
|
-
'```verdict\n{"all_pass": false, "findings": [{"check": "stale", "detail": "old"}]}\n```\nretry\n```verdict\n{"all_pass": false, "findings": [{"check": "fresh", "detail": "new"}]}\n```';
|
|
296
|
-
const objection = extractVerifyObjection(transcript);
|
|
297
|
-
assert.match(objection, /fresh — new/);
|
|
298
|
-
assert.doesNotMatch(objection, /stale/);
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
test('extractVerifyObjection renders bare string findings as their text', () => {
|
|
302
|
-
const transcript =
|
|
303
|
-
'```verdict\n{"all_pass": false, "findings": ["boundary still over-blocks", "missing test for empty input"]}\n```';
|
|
304
|
-
const objection = extractVerifyObjection(transcript);
|
|
305
|
-
assert.match(objection, /1\. boundary still over-blocks/);
|
|
306
|
-
assert.match(objection, /2\. missing test for empty input/);
|
|
307
|
-
assert.doesNotMatch(objection, /unnamed check/);
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
test('extractVerifyObjection renders alternate-keyed objects (title, message, description, issue)', () => {
|
|
311
|
-
const transcript =
|
|
312
|
-
'```verdict\n{"all_pass": false, "findings": [{"title": "over-blocks", "detail": "boundary unchecked"}, {"message": "regex too broad"}, {"description": "no fallback path"}, {"issue": "stale fixture"}]}\n```';
|
|
313
|
-
const objection = extractVerifyObjection(transcript);
|
|
314
|
-
assert.match(objection, /over-blocks — boundary unchecked/);
|
|
315
|
-
assert.match(objection, /regex too broad/);
|
|
316
|
-
assert.match(objection, /no fallback path/);
|
|
317
|
-
assert.match(objection, /stale fixture/);
|
|
318
|
-
assert.doesNotMatch(objection, /unnamed check/);
|
|
319
|
-
assert.doesNotMatch(objection, /no detail/);
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
test('extractVerifyObjection renders mixed string and object findings', () => {
|
|
323
|
-
const transcript =
|
|
324
|
-
'```verdict\n{"all_pass": false, "findings": ["plain concern", {"check": "named", "detail": "explained"}]}\n```';
|
|
325
|
-
const objection = extractVerifyObjection(transcript);
|
|
326
|
-
assert.match(objection, /1\. plain concern/);
|
|
327
|
-
assert.match(objection, /2\. named — explained/);
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
test('extractVerifyObjection stringifies an object whose keys it does not recognize', () => {
|
|
331
|
-
const transcript = '```verdict\n{"all_pass": false, "findings": [{"severity": "P1", "line": 42}]}\n```';
|
|
332
|
-
const objection = extractVerifyObjection(transcript);
|
|
333
|
-
assert.match(objection, /severity/);
|
|
334
|
-
assert.match(objection, /42/);
|
|
335
|
-
assert.doesNotMatch(objection, /unnamed check/);
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
test('extractVerifyObjection falls back when no finding yields usable text', () => {
|
|
339
|
-
const transcript = '```verdict\n{"all_pass": false, "findings": [null, {}, ""]}\n```';
|
|
340
|
-
assert.equal(extractVerifyObjection(transcript), VERIFY_OBJECTION_FALLBACK);
|
|
341
|
-
});
|
|
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
|
-
|
|
447
|
-
test('the verify-recover task in runCodeEditorTask is a clean-coder edit step bound to the verifier objection and leaves changes uncommitted', () => {
|
|
448
|
-
const recoverBody = functionSource('runCodeEditorTask');
|
|
449
|
-
assert.match(recoverBody, /agentType:\s*'clean-coder'/, 'expected the fixer to use clean-coder');
|
|
450
|
-
assert.match(recoverBody, /schema:\s*EDIT_SCHEMA/, 'expected the fixer to reuse EDIT_SCHEMA');
|
|
451
|
-
assert.match(recoverBody, /VERIFY-RECOVERY fixer/, 'expected the verify-recovery prompt body');
|
|
452
|
-
assert.match(recoverBody, /context\.objection/, 'expected the fixer prompt to consume the verifier objection');
|
|
453
|
-
assert.match(
|
|
454
|
-
recoverBody,
|
|
455
|
-
/do not commit and do not push|Do NOT commit|leave .*uncommitted|uncommitted/i,
|
|
456
|
-
'expected the fixer to leave its fix uncommitted for the re-verify and retry commit',
|
|
457
|
-
);
|
|
458
|
-
});
|
|
459
|
-
|
|
460
|
-
test('verifyWithRecovery bounds the loop, re-fixes on a failed verdict, and re-verifies', () => {
|
|
461
|
-
const recoveryBody = functionSource('verifyWithRecovery');
|
|
462
|
-
assert.match(recoveryBody, /verdictPassed\(/, 'expected the loop guard to call verdictPassed');
|
|
463
|
-
assert.match(
|
|
464
|
-
recoveryBody,
|
|
465
|
-
/attempt\s*<\s*FIX_RECOVERY_MAX_ATTEMPTS/,
|
|
466
|
-
'expected the loop to be bounded by FIX_RECOVERY_MAX_ATTEMPTS',
|
|
467
|
-
);
|
|
468
|
-
assert.match(recoveryBody, /runRecoverEdit\(/, 'expected the loop to spawn the verify-recovery fixer');
|
|
469
|
-
assert.match(recoveryBody, /runVerify\(/, 'expected the loop to re-verify after the fixer edit');
|
|
470
|
-
assert.match(
|
|
471
|
-
recoveryBody,
|
|
472
|
-
/extractVerifyObjection\(/,
|
|
473
|
-
'expected the loop to feed the fixer the verifier objection',
|
|
474
|
-
);
|
|
475
|
-
const editGuardIndex = recoveryBody.search(/edited\s*!==\s*true/);
|
|
476
|
-
assert.notEqual(editGuardIndex, -1, 'expected an early break when the fixer made no edit');
|
|
477
|
-
const recoverEditIndex = recoveryBody.search(/runRecoverEdit\(/);
|
|
478
|
-
const reverifyIndex = recoveryBody.lastIndexOf('runVerify(');
|
|
479
|
-
assert.ok(recoverEditIndex < reverifyIndex, 'expected order recover-edit -> re-verify, so a swap fails');
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
test('applyFixes routes through fixerWithRecovery which handles verify and commit', () => {
|
|
483
|
-
const applyFixesBody = functionSource('applyFixes');
|
|
484
|
-
assert.match(applyFixesBody, /fixerWithRecovery\(/, 'expected applyFixes to call fixerWithRecovery');
|
|
485
|
-
assert.match(applyFixesBody, /runCodeEditorTask\('fix-edit'/, "expected applyFixes to call runCodeEditorTask('fix-edit')");
|
|
486
|
-
});
|
|
487
|
-
|
|
488
|
-
test('repairConvergence routes its verify through verifyWithRecovery wired to the task helpers', () => {
|
|
489
|
-
const repairBody = functionSource('repairConvergence');
|
|
490
|
-
assert.match(repairBody, /verifyWithRecovery\(/, 'expected repairConvergence to call verifyWithRecovery');
|
|
491
|
-
assert.match(repairBody, /runVerifierTask\(/, 'expected repairConvergence to use runVerifierTask for verify');
|
|
492
|
-
assert.match(repairBody, /runCodeEditorTask\(/, 'expected repairConvergence to use runCodeEditorTask for recover');
|
|
493
|
-
});
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { readFileSync } from 'node:fs'
|
|
3
|
+
import test from 'node:test'
|
|
4
|
+
|
|
5
|
+
const workflowSource = readFileSync(new URL('./converge.mjs', import.meta.url), 'utf8')
|
|
6
|
+
|
|
7
|
+
test('review results gate recovery commits', () => {
|
|
8
|
+
const recoveryStart = workflowSource.indexOf('async function commitWithRecovery')
|
|
9
|
+
const recoveryEnd = workflowSource.indexOf('async function verifyWithRecovery', recoveryStart)
|
|
10
|
+
const recoverySource = workflowSource.slice(recoveryStart, recoveryEnd)
|
|
11
|
+
assert.match(recoverySource, /commitNeedsCodeRecovery/)
|
|
12
|
+
assert.match(recoverySource, /runRecoverEdit/)
|
|
13
|
+
assert.match(recoverySource, /reviewPassed\(verifyTranscript\)/)
|
|
14
|
+
assert.match(recoverySource, /rebindReviewedSurface/)
|
|
15
|
+
assert.match(recoverySource, /captureReviewedSurface/)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('failed review findings route back to the bounded repair loop', () => {
|
|
19
|
+
const recoveryStart = workflowSource.indexOf('async function verifyWithRecovery')
|
|
20
|
+
const recoveryEnd = workflowSource.indexOf('async function applyFixes', recoveryStart)
|
|
21
|
+
const recoverySource = workflowSource.slice(recoveryStart, recoveryEnd)
|
|
22
|
+
assert.match(recoverySource, /FIX_RECOVERY_MAX_ATTEMPTS/)
|
|
23
|
+
assert.match(recoverySource, /extractVerifyObjection/)
|
|
24
|
+
assert.match(recoverySource, /reviewPassed\(verifyTranscript\)/)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test('commit paths bind a deterministic reviewed working-tree surface', () => {
|
|
28
|
+
assert.match(workflowSource, /function runWorkingTreeSurfaceTask\(context\)/)
|
|
29
|
+
assert.match(workflowSource, /git diff --no-ext-diff --binary HEAD/)
|
|
30
|
+
assert.match(workflowSource, /git ls-files --others --exclude-standard -z/)
|
|
31
|
+
assert.match(workflowSource, /async function rebindReviewedSurface/)
|
|
32
|
+
assert.match(workflowSource, /stableReviewedSurfaceHash/)
|
|
33
|
+
})
|
|
@@ -1,97 +1,15 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
assert.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return convergeSource.slice(functionStart, functionEnd);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const helperModule = new Function(
|
|
20
|
-
`${functionBody('isMergeConflicting')}\nreturn { isMergeConflicting };`,
|
|
21
|
-
)();
|
|
22
|
-
const { isMergeConflicting } = helperModule;
|
|
23
|
-
|
|
24
|
-
test('isMergeConflicting treats a dead check agent (null/undefined) as not conflicting', () => {
|
|
25
|
-
assert.equal(isMergeConflicting(null), false);
|
|
26
|
-
assert.equal(isMergeConflicting(undefined), false);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test('isMergeConflicting reports a conflict only when the check returned conflicting:true', () => {
|
|
30
|
-
assert.equal(isMergeConflicting({ conflicting: true }), true);
|
|
31
|
-
assert.equal(isMergeConflicting({ conflicting: false }), false);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test('the git agent handles merge-conflict checks with a Monitor-based poll', () => {
|
|
35
|
-
const gitBody = functionBody('runGitTask');
|
|
36
|
-
assert.match(gitBody, /mergeable/, 'expected the git agent to read the PR mergeable field');
|
|
37
|
-
assert.match(
|
|
38
|
-
gitBody,
|
|
39
|
-
/do not edit, commit, push, or rebase|read only/i,
|
|
40
|
-
'expected the git agent merge check to be read-only',
|
|
41
|
-
);
|
|
42
|
-
assert.match(gitBody, /PREFLIGHT_GIT_SCHEMA/, 'expected the git agent to return PREFLIGHT_GIT_SCHEMA');
|
|
43
|
-
assert.match(gitBody, /Monitor tool/, 'expected a Monitor-based poll delay, not a foreground sleep');
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test('runCodeEditorTask conflict-edit path rebases onto origin/main and makes no push', () => {
|
|
47
|
-
const body = functionBody('runCodeEditorTask');
|
|
48
|
-
assert.match(body, /git rebase origin\/main/, 'expected the edit path to rebase onto origin/main');
|
|
49
|
-
assert.match(
|
|
50
|
-
body,
|
|
51
|
-
/do not push|no push|not push/i,
|
|
52
|
-
'expected the edit path to leave the push to the commit step',
|
|
53
|
-
);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test('resolveMergeConflicts runs edit -> verify -> commit and gates the push on the verdict', () => {
|
|
57
|
-
const body = functionBody('resolveMergeConflicts');
|
|
58
|
-
const editIndex = body.indexOf("runCodeEditorTask('conflict-edit'");
|
|
59
|
-
const verifyIndex = body.indexOf('runVerifierTask(');
|
|
60
|
-
const commitIndex = body.indexOf('commitWithRecovery(');
|
|
61
|
-
assert.notEqual(editIndex, -1, 'expected the edit step to run');
|
|
62
|
-
assert.notEqual(verifyIndex, -1, 'expected the verify step to run');
|
|
63
|
-
assert.notEqual(commitIndex, -1, 'expected the commit step to run');
|
|
64
|
-
assert.ok(
|
|
65
|
-
editIndex < verifyIndex && verifyIndex < commitIndex,
|
|
66
|
-
'expected the order edit -> verify -> commit',
|
|
67
|
-
);
|
|
68
|
-
assert.match(body, /verdictPassed\(/, 'expected the verifier verdict to gate the force-push');
|
|
69
|
-
assert.doesNotMatch(
|
|
70
|
-
body,
|
|
71
|
-
/runGitTask\(/,
|
|
72
|
-
'expected the conflict decision to come from the merged preflight-git probe, with no mergeability agent spawned here',
|
|
73
|
-
);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test('resolveMergeConflicts rebases only when the caller-supplied preflight decision reports a conflict', () => {
|
|
77
|
-
const body = functionBody('resolveMergeConflicts');
|
|
78
|
-
assert.match(
|
|
79
|
-
body,
|
|
80
|
-
/if \(!isConflicting\) return head/,
|
|
81
|
-
'expected a clean PR to return the unchanged HEAD without rebasing',
|
|
82
|
-
);
|
|
83
|
-
assert.match(
|
|
84
|
-
convergeSource,
|
|
85
|
-
/resolveMergeConflicts\(preflight\.sha, isMergeConflicting\(preflight\)\)/,
|
|
86
|
-
'expected the call site to derive the conflict decision from the merged preflight-git result',
|
|
87
|
-
);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test('the merge-conflict pre-flight runs once before the round loop, ahead of the parallel bug-check lenses', () => {
|
|
91
|
-
const preflightCall = convergeSource.indexOf('await resolveMergeConflicts(');
|
|
92
|
-
const whileLoop = convergeSource.indexOf('while (iterations < CONFIG.maxIterations)');
|
|
93
|
-
const firstLens = convergeSource.indexOf('const lenses = await parallel(');
|
|
94
|
-
assert.notEqual(preflightCall, -1, 'expected the pre-flight resolveMergeConflicts call site');
|
|
95
|
-
assert.ok(preflightCall < whileLoop, 'expected the pre-flight to run before the round loop');
|
|
96
|
-
assert.ok(preflightCall < firstLens, 'expected the pre-flight to run before the first bug-check lenses');
|
|
97
|
-
});
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { readFileSync } from 'node:fs'
|
|
3
|
+
import test from 'node:test'
|
|
4
|
+
|
|
5
|
+
const workflowSource = readFileSync(new URL('./converge.mjs', import.meta.url), 'utf8')
|
|
6
|
+
|
|
7
|
+
test('merge-conflict repair keeps the rebase, review, and force-push sequence', () => {
|
|
8
|
+
const conflictStart = workflowSource.indexOf('async function resolveMergeConflicts')
|
|
9
|
+
const conflictEnd = workflowSource.indexOf('function isStandardsOnlyRound', conflictStart)
|
|
10
|
+
const conflictSource = workflowSource.slice(conflictStart, conflictEnd)
|
|
11
|
+
assert.match(conflictSource, /runCodeEditorTask\('conflict-edit'/)
|
|
12
|
+
assert.match(conflictSource, /runReviewTask\('repair-verify'/)
|
|
13
|
+
assert.match(conflictSource, /reviewPassed\(verifyTranscript\)/)
|
|
14
|
+
assert.match(conflictSource, /commitWithRecovery/)
|
|
15
|
+
})
|