claude-dev-env 1.60.0 → 1.61.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 +4 -0
- package/audit-rubrics/category_rubrics/category-f-silent-failures.md +1 -1
- package/audit-rubrics/prompts/category-e-dead-code.md +17 -4
- package/audit-rubrics/prompts/category-f-silent-failures.md +1 -0
- package/docs/CODE_RULES.md +2 -2
- package/hooks/blocking/code_rules_annotations_length.py +189 -10
- package/hooks/blocking/code_rules_enforcer.py +8 -0
- package/hooks/blocking/code_rules_orphan_css_class.py +196 -0
- package/hooks/blocking/config/verified_commit_constants.py +14 -2
- package/hooks/blocking/destructive_command_blocker.py +483 -61
- package/hooks/blocking/test_code_rules_enforcer_annotations.py +240 -0
- package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +1 -0
- package/hooks/blocking/test_code_rules_enforcer_dispatch_wiring.py +82 -0
- package/hooks/blocking/test_code_rules_enforcer_orphan_css_class.py +196 -0
- package/hooks/blocking/test_destructive_command_blocker.py +213 -0
- package/hooks/blocking/test_verification_verdict_store.py +212 -0
- package/hooks/blocking/test_verified_commit_gate.py +127 -0
- package/hooks/blocking/test_verifier_verdict_minter.py +74 -95
- package/hooks/blocking/verification_verdict_store.py +240 -0
- package/hooks/blocking/verified_commit_gate.py +20 -8
- package/hooks/blocking/verifier_verdict_minter.py +46 -124
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +6 -0
- package/hooks/hooks_constants/destructive_command_segment_constants.py +15 -0
- package/hooks/hooks_constants/orphan_css_class_constants.py +40 -0
- package/hooks/validation/mypy_validator.py +59 -7
- package/hooks/validation/test_mypy_validator.py +94 -0
- package/package.json +1 -1
- package/rules/orphan-css-class.md +23 -0
- package/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +0 -1
- package/skills/autoconverge/workflow/converge.contract.test.mjs +202 -13
- package/skills/autoconverge/workflow/converge.mjs +392 -51
- package/skills/autoconverge/workflow/test_render_report.py +30 -0
|
@@ -14,8 +14,9 @@ const gotchasSource = readFileSync(
|
|
|
14
14
|
function lensPromptBody(builderName) {
|
|
15
15
|
const builderStart = convergeSource.indexOf(`function ${builderName}(`);
|
|
16
16
|
assert.notEqual(builderStart, -1, `expected ${builderName} to exist`);
|
|
17
|
-
const
|
|
18
|
-
const builderEnd =
|
|
17
|
+
const nextBuilderMatch = /\n(?:async )?function /.exec(convergeSource.slice(builderStart + 1));
|
|
18
|
+
const builderEnd =
|
|
19
|
+
nextBuilderMatch === null ? convergeSource.length : builderStart + 1 + nextBuilderMatch.index;
|
|
19
20
|
return convergeSource.slice(builderStart, builderEnd);
|
|
20
21
|
}
|
|
21
22
|
|
|
@@ -67,8 +68,8 @@ test('gotchas doc states parallel lenses must avoid concurrent git operations',
|
|
|
67
68
|
assert.match(gotchasSource, /fetch.*once.*before/i);
|
|
68
69
|
});
|
|
69
70
|
|
|
70
|
-
test('repair-convergence filters unresolved threads to bot authors and skips human threads', () => {
|
|
71
|
-
const repairPrompt = lensPromptBody('
|
|
71
|
+
test('repair-convergence edit step filters unresolved threads to bot authors and skips human threads', () => {
|
|
72
|
+
const repairPrompt = lensPromptBody('repairConvergenceEdit');
|
|
72
73
|
assert.match(
|
|
73
74
|
repairPrompt,
|
|
74
75
|
/cursor.*claude.*copilot|copilot.*cursor.*claude|claude.*cursor.*copilot/is,
|
|
@@ -81,8 +82,8 @@ test('repair-convergence filters unresolved threads to bot authors and skips hum
|
|
|
81
82
|
);
|
|
82
83
|
});
|
|
83
84
|
|
|
84
|
-
test('repair-convergence no longer instructs resolving every unresolved thread without an author filter', () => {
|
|
85
|
-
const repairPrompt = lensPromptBody('
|
|
85
|
+
test('repair-convergence edit step no longer instructs resolving every unresolved thread without an author filter', () => {
|
|
86
|
+
const repairPrompt = lensPromptBody('repairConvergenceEdit');
|
|
86
87
|
assert.doesNotMatch(
|
|
87
88
|
repairPrompt,
|
|
88
89
|
/fetch every thread where isResolved is false/,
|
|
@@ -182,25 +183,213 @@ test('the CONVERGE branch re-resolves HEAD from GitHub on every entry', () => {
|
|
|
182
183
|
assert.notEqual(resolveHeadIndex, -1, 'expected CONVERGE to re-resolve HEAD via resolveHead()');
|
|
183
184
|
});
|
|
184
185
|
|
|
185
|
-
test('fix prompt resolves threads by PRRT thread node id looked up from the comment databaseId', () => {
|
|
186
|
-
const
|
|
187
|
-
assert.match(
|
|
186
|
+
test('fix edit prompt resolves threads by PRRT thread node id looked up from the comment databaseId', () => {
|
|
187
|
+
const editPrompt = lensPromptBody('applyFixesEdit');
|
|
188
|
+
assert.match(editPrompt, /PRRT/, 'expected the thread node id form (PRRT_...) to be named');
|
|
188
189
|
assert.match(
|
|
189
|
-
|
|
190
|
+
editPrompt,
|
|
190
191
|
/databaseId/,
|
|
191
192
|
'expected the GraphQL lookup matching comment databaseId to be named',
|
|
192
193
|
);
|
|
193
194
|
assert.match(
|
|
194
|
-
|
|
195
|
+
editPrompt,
|
|
195
196
|
/not the numeric comment id/,
|
|
196
197
|
'expected an explicit guard against passing the numeric comment id to resolve_thread',
|
|
197
198
|
);
|
|
198
199
|
});
|
|
199
200
|
|
|
200
|
-
test('fix prompt does not pass the numeric comment id straight to resolve_thread', () => {
|
|
201
|
+
test('fix edit prompt does not pass the numeric comment id straight to resolve_thread', () => {
|
|
201
202
|
assert.doesNotMatch(
|
|
202
|
-
lensPromptBody('
|
|
203
|
+
lensPromptBody('applyFixesEdit'),
|
|
203
204
|
/then resolve that thread \(use the github MCP pull_request_review_write/,
|
|
204
205
|
'resolve_thread and resolveReviewThread require a PRRT_... thread node id, not the comment id',
|
|
205
206
|
);
|
|
206
207
|
});
|
|
208
|
+
|
|
209
|
+
test('the fix flow spawns a code-verifier step between the edit step and the commit step', () => {
|
|
210
|
+
const applyFixesBody = lensPromptBody('applyFixes');
|
|
211
|
+
const editIndex = applyFixesBody.indexOf('applyFixesEdit(');
|
|
212
|
+
const verifyIndex = applyFixesBody.indexOf('verifyFixesInWorkingTree(');
|
|
213
|
+
const commitIndex = applyFixesBody.indexOf('commitVerifiedFixes(');
|
|
214
|
+
assert.notEqual(editIndex, -1, 'expected applyFixes to call the edit step');
|
|
215
|
+
assert.notEqual(verifyIndex, -1, 'expected applyFixes to call the verify step');
|
|
216
|
+
assert.notEqual(commitIndex, -1, 'expected applyFixes to call the commit step');
|
|
217
|
+
assert.ok(
|
|
218
|
+
editIndex < verifyIndex && verifyIndex < commitIndex,
|
|
219
|
+
'expected the order edit -> verify -> commit so the verifier verdict binds the fixed working tree',
|
|
220
|
+
);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
function constantBody(constantName) {
|
|
224
|
+
const constantStart = convergeSource.indexOf(`const ${constantName} =`);
|
|
225
|
+
assert.notEqual(constantStart, -1, `expected ${constantName} to exist`);
|
|
226
|
+
const nextConstantStart = convergeSource.indexOf('\nconst ', constantStart + 1);
|
|
227
|
+
const constantEnd = nextConstantStart === -1 ? convergeSource.length : nextConstantStart;
|
|
228
|
+
return convergeSource.slice(constantStart, constantEnd);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
test('the shared verdict-fence steps name the binding-hash command and the verdict fence', () => {
|
|
232
|
+
const fenceSteps = constantBody('VERDICT_FENCE_STEPS');
|
|
233
|
+
assert.match(fenceSteps, /--manifest-hash/, 'expected the binding-hash command to be named');
|
|
234
|
+
assert.match(
|
|
235
|
+
fenceSteps,
|
|
236
|
+
/verification_verdict_store\.py/,
|
|
237
|
+
'expected the verdict-store script that computes the binding hash to be named',
|
|
238
|
+
);
|
|
239
|
+
assert.match(fenceSteps, /```verdict/, 'expected the verdict fence to be specified');
|
|
240
|
+
assert.match(fenceSteps, /manifest_sha256/, 'expected the verdict fence to carry manifest_sha256');
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test('every verify step reuses the shared verdict-fence steps, uses code-verifier, and forbids edits', () => {
|
|
244
|
+
for (const verifyFunctionName of [
|
|
245
|
+
'verifyFixesInWorkingTree',
|
|
246
|
+
'verifyRepairChanges',
|
|
247
|
+
'verifyHardeningChanges',
|
|
248
|
+
]) {
|
|
249
|
+
const verifyBody = lensPromptBody(verifyFunctionName);
|
|
250
|
+
assert.match(
|
|
251
|
+
verifyBody,
|
|
252
|
+
/VERDICT_FENCE_STEPS/,
|
|
253
|
+
`expected ${verifyFunctionName} to reuse the shared VERDICT_FENCE_STEPS`,
|
|
254
|
+
);
|
|
255
|
+
assert.match(
|
|
256
|
+
verifyBody,
|
|
257
|
+
/agentType:\s*'code-verifier'/,
|
|
258
|
+
`expected ${verifyFunctionName} to spawn the code-verifier agent type`,
|
|
259
|
+
);
|
|
260
|
+
assert.doesNotMatch(
|
|
261
|
+
verifyBody,
|
|
262
|
+
/schema:/,
|
|
263
|
+
`expected ${verifyFunctionName} to pass no schema so its verdict fence stays as assistant text`,
|
|
264
|
+
);
|
|
265
|
+
assert.match(
|
|
266
|
+
verifyBody,
|
|
267
|
+
/do no edits|make no edits|not edit|no file edits/i,
|
|
268
|
+
`expected ${verifyFunctionName} to be told to make no edits`,
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test('the commit step is instructed to make no further file edits', () => {
|
|
274
|
+
const commitBody = lensPromptBody('commitVerifiedFixes');
|
|
275
|
+
assert.match(
|
|
276
|
+
commitBody,
|
|
277
|
+
/no (?:further |additional )?(?:file )?edits|do not edit|make no edits/i,
|
|
278
|
+
'expected the commit step to forbid further edits so the verified surface stays bound',
|
|
279
|
+
);
|
|
280
|
+
assert.match(
|
|
281
|
+
commitBody,
|
|
282
|
+
/agentType:\s*'clean-coder'/,
|
|
283
|
+
'expected the commit step to use clean-coder',
|
|
284
|
+
);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test('the repair flow spawns a code-verifier step between the edit step and the commit step', () => {
|
|
288
|
+
const repairBody = lensPromptBody('repairConvergence');
|
|
289
|
+
const editIndex = repairBody.indexOf('repairConvergenceEdit(');
|
|
290
|
+
const verifyIndex = repairBody.indexOf('verifyRepairChanges(');
|
|
291
|
+
const commitIndex = repairBody.indexOf('commitRepairFixes(');
|
|
292
|
+
assert.notEqual(editIndex, -1, 'expected repairConvergence to call the edit step');
|
|
293
|
+
assert.notEqual(verifyIndex, -1, 'expected repairConvergence to call the verify step');
|
|
294
|
+
assert.notEqual(commitIndex, -1, 'expected repairConvergence to call the commit step');
|
|
295
|
+
assert.ok(
|
|
296
|
+
editIndex < verifyIndex && verifyIndex < commitIndex,
|
|
297
|
+
'expected edit -> verify -> commit so the verifier verdict binds the repaired working tree',
|
|
298
|
+
);
|
|
299
|
+
assert.match(
|
|
300
|
+
repairBody,
|
|
301
|
+
/verdictPassed\(/,
|
|
302
|
+
'expected the verify verdict to gate the repair commit step',
|
|
303
|
+
);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test('the standards-deferral flow spawns a code-verifier step between the edit step and the commit step', () => {
|
|
307
|
+
const standardsBody = lensPromptBody('spawnStandardsFollowUp');
|
|
308
|
+
const editIndex = standardsBody.indexOf('standardsFollowUpEdit(');
|
|
309
|
+
const verifyIndex = standardsBody.indexOf('verifyHardeningChanges(');
|
|
310
|
+
const commitIndex = standardsBody.indexOf('commitHardeningPr(');
|
|
311
|
+
assert.notEqual(editIndex, -1, 'expected spawnStandardsFollowUp to call the edit step');
|
|
312
|
+
assert.notEqual(verifyIndex, -1, 'expected spawnStandardsFollowUp to call the verify step');
|
|
313
|
+
assert.notEqual(commitIndex, -1, 'expected spawnStandardsFollowUp to call the commit step');
|
|
314
|
+
assert.ok(
|
|
315
|
+
editIndex < verifyIndex && verifyIndex < commitIndex,
|
|
316
|
+
'expected edit -> verify -> commit so the verifier verdict binds the hardening working tree',
|
|
317
|
+
);
|
|
318
|
+
assert.match(
|
|
319
|
+
standardsBody,
|
|
320
|
+
/verdictPassed\(/,
|
|
321
|
+
'expected the verify verdict to gate the hardening commit step',
|
|
322
|
+
);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
test('the repair and hardening commit steps forbid further edits and use clean-coder', () => {
|
|
326
|
+
for (const commitFunctionName of ['commitRepairFixes', 'commitHardeningPr']) {
|
|
327
|
+
const commitBody = lensPromptBody(commitFunctionName);
|
|
328
|
+
assert.match(
|
|
329
|
+
commitBody,
|
|
330
|
+
/no (?:further |additional )?(?:file )?edits|do not edit|make no edits/i,
|
|
331
|
+
`expected ${commitFunctionName} to forbid further edits so the verified surface stays bound`,
|
|
332
|
+
);
|
|
333
|
+
assert.match(
|
|
334
|
+
commitBody,
|
|
335
|
+
/agentType:\s*'clean-coder'/,
|
|
336
|
+
`expected ${commitFunctionName} to use clean-coder`,
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test('the standards-deferral edit step stages the hardening change without committing', () => {
|
|
342
|
+
const editBody = lensPromptBody('standardsFollowUpEdit');
|
|
343
|
+
assert.match(
|
|
344
|
+
editBody,
|
|
345
|
+
/do not commit and do not push|NO commit and NO push|Do NOT commit/i,
|
|
346
|
+
'expected the standards edit step to leave the hardening change uncommitted',
|
|
347
|
+
);
|
|
348
|
+
assert.match(
|
|
349
|
+
editBody,
|
|
350
|
+
/agentType:\s*'clean-coder'/,
|
|
351
|
+
'expected the standards edit step to use clean-coder',
|
|
352
|
+
);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
test('spawnStandardsFollowUp reports whether a hardening PR opened on every path', () => {
|
|
356
|
+
const body = lensPromptBody('spawnStandardsFollowUp');
|
|
357
|
+
const falseReturns = body.match(/hardeningPrOpened:\s*false/g) || [];
|
|
358
|
+
assert.ok(
|
|
359
|
+
falseReturns.length >= 2,
|
|
360
|
+
'expected both skip paths (no hardening staged, verify failed) to return hardeningPrOpened:false',
|
|
361
|
+
);
|
|
362
|
+
assert.match(
|
|
363
|
+
body,
|
|
364
|
+
/hardeningPrOpened:\s*true/,
|
|
365
|
+
'expected the commit path to return hardeningPrOpened:true',
|
|
366
|
+
);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
test('the standards-deferral note names the hardening PR only when one opened', () => {
|
|
370
|
+
const noteBody = lensPromptBody('standardsDeferralNote');
|
|
371
|
+
assert.match(
|
|
372
|
+
noteBody,
|
|
373
|
+
/environment-hardening PR/,
|
|
374
|
+
'expected the opened-PR branch to name the hardening PR',
|
|
375
|
+
);
|
|
376
|
+
assert.match(
|
|
377
|
+
noteBody,
|
|
378
|
+
/no environment-hardening PR/i,
|
|
379
|
+
'expected the skip branch to state no hardening PR was opened',
|
|
380
|
+
);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
test('both standards-deferral call sites build standardsNote from the spawnStandardsFollowUp outcome', () => {
|
|
384
|
+
const callSiteUses = convergeSource.match(/standardsNote = standardsDeferralNote\(/g) || [];
|
|
385
|
+
assert.equal(
|
|
386
|
+
callSiteUses.length,
|
|
387
|
+
2,
|
|
388
|
+
'expected both standards-deferral call sites to build standardsNote via standardsDeferralNote(...)',
|
|
389
|
+
);
|
|
390
|
+
assert.doesNotMatch(
|
|
391
|
+
convergeSource,
|
|
392
|
+
/standardsNote = `\$\{[^}]+\} code-standard finding\(s\) deferred to a follow-up fix issue plus an environment-hardening PR/,
|
|
393
|
+
'expected no unconditional hardening-PR claim in standardsNote',
|
|
394
|
+
);
|
|
395
|
+
});
|