claude-dev-env 1.74.0 → 1.75.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/hooks/blocking/CLAUDE.md +1 -0
- package/hooks/blocking/code_verifier_spawn_preflight_gate.py +2 -1
- package/hooks/blocking/duplicate_rmtree_helper_blocker.py +155 -0
- package/hooks/blocking/hedging_language_blocker.py +1 -13
- package/hooks/blocking/intent_only_ending_blocker.py +1 -15
- package/hooks/blocking/pre_tool_use_dispatcher.py +4 -5
- package/hooks/blocking/question_to_user_enforcer.py +1 -11
- package/hooks/blocking/session_handoff_blocker.py +1 -15
- package/hooks/blocking/test_code_verifier_spawn_preflight_gate.py +16 -0
- package/hooks/blocking/test_duplicate_rmtree_helper_blocker.py +328 -0
- package/hooks/blocking/test_hedging_language_blocker.py +6 -0
- package/hooks/blocking/test_intent_only_ending_blocker.py +5 -0
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +52 -5
- package/hooks/blocking/test_question_to_user_enforcer.py +6 -0
- package/hooks/blocking/test_session_handoff_blocker.py +6 -0
- package/hooks/hooks_constants/CLAUDE.md +4 -1
- package/hooks/hooks_constants/code_verifier_spawn_preflight_gate_constants.py +2 -1
- package/hooks/hooks_constants/duplicate_rmtree_helper_blocker_constants.py +27 -0
- package/hooks/hooks_constants/post_tool_use_dispatcher_constants.py +2 -0
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +8 -2
- package/hooks/hooks_constants/test_post_tool_use_dispatcher_constants.py +43 -0
- package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +99 -0
- package/hooks/hooks_constants/test_text_stripping.py +39 -0
- package/hooks/hooks_constants/text_stripping.py +36 -0
- package/hooks/validation/CLAUDE.md +1 -0
- package/hooks/validation/post_tool_use_dispatcher.py +2 -2
- package/hooks/validation/test_mypy_validator.py +1 -1
- package/hooks/validation/test_post_tool_use_dispatcher.py +6 -0
- package/hooks/workflow/auto_formatter.py +8 -5
- package/hooks/workflow/test_auto_formatter.py +33 -0
- package/package.json +1 -1
- package/rules/windows-filesystem-safe.md +2 -0
- package/skills/autoconverge/SKILL.md +6 -3
- package/skills/autoconverge/reference/stop-conditions.md +7 -0
- package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +5 -4
- package/skills/autoconverge/workflow/converge.contract.test.mjs +308 -132
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +16 -16
- package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +36 -44
- package/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +16 -24
- package/skills/autoconverge/workflow/converge.mjs +598 -606
- package/skills/autoconverge/workflow/convergence_summary.py +1 -1
- package/skills/autoconverge/workflow/render_report.py +2 -6
- package/skills/autoconverge/workflow/test_convergence_summary.py +17 -0
- package/skills/autoconverge/workflow/test_render_report.py +1 -0
|
@@ -129,12 +129,12 @@ test('a Copilot no-show after the poll cap returns a down result rather than a b
|
|
|
129
129
|
);
|
|
130
130
|
});
|
|
131
131
|
|
|
132
|
-
test('
|
|
133
|
-
const checkConvergenceBody = functionBody('
|
|
132
|
+
test('resumeConvergenceCheckAgent wires the --copilot-down flag from the copilotDown context', () => {
|
|
133
|
+
const checkConvergenceBody = functionBody('resumeConvergenceCheckAgent');
|
|
134
134
|
assert.match(
|
|
135
135
|
checkConvergenceBody,
|
|
136
|
-
/copilotDown \? ' --copilot-down' : ''/,
|
|
137
|
-
'expected
|
|
136
|
+
/context\.copilotDown \? ' --copilot-down' : ''/,
|
|
137
|
+
'expected resumeConvergenceCheckAgent to append --copilot-down when copilotDown is set',
|
|
138
138
|
);
|
|
139
139
|
assert.match(
|
|
140
140
|
checkConvergenceBody,
|
|
@@ -195,7 +195,7 @@ test('the standards-only Copilot sub-path resets copilotDown before FINALIZE', (
|
|
|
195
195
|
-1,
|
|
196
196
|
'expected the COPILOT phase to handle a standards-only Copilot fix outcome',
|
|
197
197
|
);
|
|
198
|
-
const standardsBranch = convergeSource.slice(standardsBranchStart, standardsBranchStart +
|
|
198
|
+
const standardsBranch = convergeSource.slice(standardsBranchStart, standardsBranchStart + 800);
|
|
199
199
|
const resetIndex = standardsBranch.indexOf('copilotDown = false');
|
|
200
200
|
const finalizeIndex = standardsBranch.indexOf("phase = 'FINALIZE'");
|
|
201
201
|
assert.notEqual(
|
|
@@ -234,32 +234,32 @@ test('the COPILOT phase recomputes copilotDown from each gate outcome via resolv
|
|
|
234
234
|
test('markReady receives copilotDown so it can opt the unflagged hook out of the Copilot gate', () => {
|
|
235
235
|
const finalizeStart = convergeSource.indexOf("if (phase === 'FINALIZE') {");
|
|
236
236
|
assert.notEqual(finalizeStart, -1, 'expected a FINALIZE phase block');
|
|
237
|
-
const markReadyCall = convergeSource.indexOf('
|
|
238
|
-
assert.notEqual(markReadyCall, -1, 'expected the FINALIZE phase to
|
|
239
|
-
const callSlice = convergeSource.slice(markReadyCall, markReadyCall +
|
|
237
|
+
const markReadyCall = convergeSource.indexOf("'mark-ready'", finalizeStart);
|
|
238
|
+
assert.notEqual(markReadyCall, -1, 'expected the FINALIZE phase to route mark-ready through the general-utility agent');
|
|
239
|
+
const callSlice = convergeSource.slice(markReadyCall - 20, markReadyCall + 60);
|
|
240
240
|
assert.match(
|
|
241
241
|
callSlice,
|
|
242
|
-
/
|
|
243
|
-
'expected
|
|
242
|
+
/copilotDown/,
|
|
243
|
+
'expected mark-ready context to include copilotDown so the agent can opt the unflagged hook out of the Copilot gate',
|
|
244
244
|
);
|
|
245
245
|
});
|
|
246
246
|
|
|
247
|
-
test('the
|
|
248
|
-
const markReadyBody = functionBody('
|
|
247
|
+
test('the mark-ready task in resumeGeneralUtilityAgent opts the unflagged convergence hook out of Copilot when copilotDown', () => {
|
|
248
|
+
const markReadyBody = functionBody('resumeGeneralUtilityAgent');
|
|
249
249
|
assert.match(
|
|
250
250
|
markReadyBody,
|
|
251
|
-
/copilotDown/,
|
|
252
|
-
'expected
|
|
251
|
+
/context\.copilotDown/,
|
|
252
|
+
'expected the mark-ready task to branch on copilotDown',
|
|
253
253
|
);
|
|
254
254
|
assert.match(
|
|
255
255
|
markReadyBody,
|
|
256
256
|
/CLAUDE_REVIEWS_DISABLED/,
|
|
257
|
-
'expected the
|
|
257
|
+
'expected the mark-ready prompt to set CLAUDE_REVIEWS_DISABLED so the unflagged hook re-derives the Copilot bypass',
|
|
258
258
|
);
|
|
259
259
|
assert.match(
|
|
260
260
|
markReadyBody,
|
|
261
261
|
/copilot/,
|
|
262
|
-
'expected the
|
|
262
|
+
'expected the mark-ready opt-out to name the copilot token',
|
|
263
263
|
);
|
|
264
264
|
});
|
|
265
265
|
|
|
@@ -99,30 +99,28 @@ test('FIX_RECOVERY_MAX_ATTEMPTS is declared and bounds the recovery loop at 2',
|
|
|
99
99
|
assert.match(constantLine('FIX_RECOVERY_MAX_ATTEMPTS'), /=\s*2\s*$/);
|
|
100
100
|
});
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
});
|
|
118
|
-
}
|
|
102
|
+
test('the commit path in resumeCodeEditorAgent separates an edit-requiring block from a transient failure', () => {
|
|
103
|
+
const commitBody = functionSource('resumeCodeEditorAgent');
|
|
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
|
+
});
|
|
119
117
|
|
|
120
|
-
test('
|
|
121
|
-
const recoverBody = functionSource('
|
|
118
|
+
test('the commit-recover task in resumeCodeEditorAgent is a clean-coder edit step bound to the blocker detail and leaves changes uncommitted', () => {
|
|
119
|
+
const recoverBody = functionSource('resumeCodeEditorAgent');
|
|
122
120
|
assert.match(recoverBody, /agentType:\s*'clean-coder'/, 'expected the fixer to use clean-coder');
|
|
123
121
|
assert.match(recoverBody, /schema:\s*EDIT_SCHEMA/, 'expected the fixer to reuse EDIT_SCHEMA');
|
|
124
|
-
assert.match(recoverBody, /
|
|
125
|
-
assert.match(recoverBody, /blockerDetail/, 'expected the fixer prompt to consume the blocker detail');
|
|
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');
|
|
126
124
|
assert.match(
|
|
127
125
|
recoverBody,
|
|
128
126
|
/only the (?:violation|finding|block)/i,
|
|
@@ -163,20 +161,17 @@ test('commitWithRecovery bounds the loop, re-verifies, and retries the commit on
|
|
|
163
161
|
);
|
|
164
162
|
});
|
|
165
163
|
|
|
166
|
-
test('applyFixes routes
|
|
164
|
+
test('applyFixes routes through spawnFixerAgent and fixerWithRecovery', () => {
|
|
167
165
|
const applyFixesBody = functionSource('applyFixes');
|
|
168
|
-
assert.match(applyFixesBody, /
|
|
169
|
-
assert.match(applyFixesBody, /
|
|
170
|
-
assert.match(applyFixesBody, /runVerify:\s*\(\)\s*=>\s*verifyFixesInWorkingTree\(/);
|
|
171
|
-
assert.match(applyFixesBody, /runRecoverEdit:[\s\S]*?recoverCommitBlockEdit\(/);
|
|
166
|
+
assert.match(applyFixesBody, /spawnFixerAgent\(/, 'expected applyFixes to call spawnFixerAgent');
|
|
167
|
+
assert.match(applyFixesBody, /fixerWithRecovery\(/, 'expected applyFixes to call fixerWithRecovery');
|
|
172
168
|
});
|
|
173
169
|
|
|
174
170
|
test('repairConvergence routes its commit through commitWithRecovery wired to the repair-path steps', () => {
|
|
175
171
|
const repairBody = functionSource('repairConvergence');
|
|
176
172
|
assert.match(repairBody, /commitWithRecovery\(/, 'expected repairConvergence to call commitWithRecovery');
|
|
177
|
-
assert.match(repairBody, /
|
|
178
|
-
assert.match(repairBody, /
|
|
179
|
-
assert.match(repairBody, /runRecoverEdit:[\s\S]*?recoverCommitBlockEdit\(/);
|
|
173
|
+
assert.match(repairBody, /resumeCodeEditorAgent\(/, 'expected repairConvergence to use resumeCodeEditorAgent');
|
|
174
|
+
assert.match(repairBody, /resumeVerifierAgent\(/, 'expected repairConvergence to use resumeVerifierAgent');
|
|
180
175
|
});
|
|
181
176
|
|
|
182
177
|
test('the round-loop fix-stalled blockers survive the recovery wiring', () => {
|
|
@@ -185,7 +180,8 @@ test('the round-loop fix-stalled blockers survive the recovery wiring', () => {
|
|
|
185
180
|
});
|
|
186
181
|
|
|
187
182
|
const verifyObjectionModule = new Function(
|
|
188
|
-
`${
|
|
183
|
+
`${functionSource('parseLastVerdictFence')}\n` +
|
|
184
|
+
`${constantLine('VERIFY_OBJECTION_FALLBACK')}\n` +
|
|
189
185
|
`${functionSource('renderVerifyObjectionLine')}\n` +
|
|
190
186
|
`${functionSource('extractVerifyObjection')}\n` +
|
|
191
187
|
'return { extractVerifyObjection, VERIFY_OBJECTION_FALLBACK };',
|
|
@@ -267,12 +263,12 @@ test('extractVerifyObjection falls back when no finding yields usable text', ()
|
|
|
267
263
|
assert.equal(extractVerifyObjection(transcript), VERIFY_OBJECTION_FALLBACK);
|
|
268
264
|
});
|
|
269
265
|
|
|
270
|
-
test('
|
|
271
|
-
const recoverBody = functionSource('
|
|
266
|
+
test('the verify-recover task in resumeCodeEditorAgent is a clean-coder edit step bound to the verifier objection and leaves changes uncommitted', () => {
|
|
267
|
+
const recoverBody = functionSource('resumeCodeEditorAgent');
|
|
272
268
|
assert.match(recoverBody, /agentType:\s*'clean-coder'/, 'expected the fixer to use clean-coder');
|
|
273
269
|
assert.match(recoverBody, /schema:\s*EDIT_SCHEMA/, 'expected the fixer to reuse EDIT_SCHEMA');
|
|
274
|
-
assert.match(recoverBody, /
|
|
275
|
-
assert.match(recoverBody, /objection/, 'expected the fixer prompt to consume the verifier objection');
|
|
270
|
+
assert.match(recoverBody, /VERIFY-RECOVERY fixer/, 'expected the verify-recovery prompt body');
|
|
271
|
+
assert.match(recoverBody, /context\.objection/, 'expected the fixer prompt to consume the verifier objection');
|
|
276
272
|
assert.match(
|
|
277
273
|
recoverBody,
|
|
278
274
|
/do not commit and do not push|Do NOT commit|leave .*uncommitted|uncommitted/i,
|
|
@@ -302,19 +298,15 @@ test('verifyWithRecovery bounds the loop, re-fixes on a failed verdict, and re-v
|
|
|
302
298
|
assert.ok(recoverEditIndex < reverifyIndex, 'expected order recover-edit -> re-verify, so a swap fails');
|
|
303
299
|
});
|
|
304
300
|
|
|
305
|
-
test('applyFixes routes
|
|
301
|
+
test('applyFixes routes through fixerWithRecovery which handles verify and commit', () => {
|
|
306
302
|
const applyFixesBody = functionSource('applyFixes');
|
|
307
|
-
assert.match(applyFixesBody, /
|
|
308
|
-
assert.match(applyFixesBody, /
|
|
309
|
-
assert.match(applyFixesBody, /runRecoverEdit:[\s\S]*?recoverVerifyFailEdit\(/);
|
|
310
|
-
const verifyIndex = applyFixesBody.search(/verifyWithRecovery\(/);
|
|
311
|
-
const commitIndex = applyFixesBody.search(/commitWithRecovery\(/);
|
|
312
|
-
assert.ok(verifyIndex < commitIndex, 'expected verify-recovery to precede commit-recovery');
|
|
303
|
+
assert.match(applyFixesBody, /fixerWithRecovery\(/, 'expected applyFixes to call fixerWithRecovery');
|
|
304
|
+
assert.match(applyFixesBody, /spawnFixerAgent\(/, 'expected applyFixes to call spawnFixerAgent');
|
|
313
305
|
});
|
|
314
306
|
|
|
315
|
-
test('repairConvergence routes its verify through verifyWithRecovery wired to
|
|
307
|
+
test('repairConvergence routes its verify through verifyWithRecovery wired to resume helpers', () => {
|
|
316
308
|
const repairBody = functionSource('repairConvergence');
|
|
317
309
|
assert.match(repairBody, /verifyWithRecovery\(/, 'expected repairConvergence to call verifyWithRecovery');
|
|
318
|
-
assert.match(repairBody, /
|
|
319
|
-
assert.match(repairBody, /
|
|
310
|
+
assert.match(repairBody, /resumeVerifierAgent\(/, 'expected repairConvergence to use resumeVerifierAgent for verify');
|
|
311
|
+
assert.match(repairBody, /resumeCodeEditorAgent\(/, 'expected repairConvergence to use resumeCodeEditorAgent for recover');
|
|
320
312
|
});
|
|
@@ -31,39 +31,36 @@ test('isMergeConflicting reports a conflict only when the check returned conflic
|
|
|
31
31
|
assert.equal(isMergeConflicting({ conflicting: false }), false);
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
-
test('
|
|
35
|
-
const
|
|
36
|
-
assert.match(
|
|
34
|
+
test('the git agent handles merge-conflict checks with shell-agnostic polling', () => {
|
|
35
|
+
const gitBody = functionBody('resumeGitAgent');
|
|
36
|
+
assert.match(gitBody, /mergeable/, 'expected the git agent to read the PR mergeable field');
|
|
37
37
|
assert.match(
|
|
38
|
-
|
|
38
|
+
gitBody,
|
|
39
39
|
/do not edit, commit, push, or rebase|read only/i,
|
|
40
|
-
'expected the
|
|
40
|
+
'expected the git agent merge check to be read-only',
|
|
41
41
|
);
|
|
42
|
-
assert.match(
|
|
43
|
-
assert.match(
|
|
44
|
-
assert.match(body, /null/, 'expected the probe to handle GitHub returning mergeable:null while it computes');
|
|
45
|
-
assert.match(body, /sleep 5|Start-Sleep/, 'expected a shell-agnostic poll delay');
|
|
42
|
+
assert.match(gitBody, /MERGE_CONFLICT_SCHEMA/, 'expected the git agent to return MERGE_CONFLICT_SCHEMA');
|
|
43
|
+
assert.match(gitBody, /sleep 5|Start-Sleep/, 'expected a shell-agnostic poll delay');
|
|
46
44
|
});
|
|
47
45
|
|
|
48
|
-
test('
|
|
49
|
-
const body = functionBody('
|
|
50
|
-
assert.match(body, /git rebase origin\/main/, 'expected the edit
|
|
46
|
+
test('resumeCodeEditorAgent conflict-edit path rebases onto origin/main and makes no push', () => {
|
|
47
|
+
const body = functionBody('resumeCodeEditorAgent');
|
|
48
|
+
assert.match(body, /git rebase origin\/main/, 'expected the edit path to rebase onto origin/main');
|
|
51
49
|
assert.match(
|
|
52
50
|
body,
|
|
53
51
|
/do not push|no push|not push/i,
|
|
54
|
-
'expected the edit
|
|
52
|
+
'expected the edit path to leave the push to the commit step',
|
|
55
53
|
);
|
|
56
|
-
assert.match(body, /agentType:\s*'clean-coder'/, 'expected the edit step to use clean-coder');
|
|
57
54
|
});
|
|
58
55
|
|
|
59
56
|
test('resolveMergeConflicts runs check -> edit -> verify -> commit and gates the push on the verdict', () => {
|
|
60
57
|
const body = functionBody('resolveMergeConflicts');
|
|
61
|
-
const checkIndex = body.indexOf(
|
|
62
|
-
const editIndex = body.indexOf('
|
|
63
|
-
const verifyIndex = body.indexOf('
|
|
64
|
-
const commitIndex = body.indexOf('
|
|
58
|
+
const checkIndex = body.indexOf("resumeGitAgent(gitAgentId, 'check-merge-conflicts'");
|
|
59
|
+
const editIndex = body.indexOf('spawnCodeEditorAgent(');
|
|
60
|
+
const verifyIndex = body.indexOf('spawnVerifierAgent(');
|
|
61
|
+
const commitIndex = body.indexOf('commitWithRecovery(');
|
|
65
62
|
assert.notEqual(checkIndex, -1, 'expected the conflict check to run');
|
|
66
|
-
assert.notEqual(editIndex, -1, 'expected the
|
|
63
|
+
assert.notEqual(editIndex, -1, 'expected the edit step to run');
|
|
67
64
|
assert.notEqual(verifyIndex, -1, 'expected the verify step to run');
|
|
68
65
|
assert.notEqual(commitIndex, -1, 'expected the commit step to run');
|
|
69
66
|
assert.ok(
|
|
@@ -71,11 +68,6 @@ test('resolveMergeConflicts runs check -> edit -> verify -> commit and gates the
|
|
|
71
68
|
'expected the order check -> edit -> verify -> commit',
|
|
72
69
|
);
|
|
73
70
|
assert.match(body, /verdictPassed\(/, 'expected the verifier verdict to gate the force-push');
|
|
74
|
-
assert.match(
|
|
75
|
-
body,
|
|
76
|
-
/commitRepairFixes\(head,\s*true\)/,
|
|
77
|
-
'expected the commit to force-with-lease (wasRebased=true) after a rebase',
|
|
78
|
-
);
|
|
79
71
|
});
|
|
80
72
|
|
|
81
73
|
test('resolveMergeConflicts rebases only when the check reports a conflict', () => {
|