claude-dev-env 1.52.1 → 1.54.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/agents/clean-coder.md +1 -1
- package/package.json +2 -2
- package/skills/autoconverge/SKILL.md +112 -0
- package/skills/autoconverge/reference/convergence.md +84 -0
- package/skills/autoconverge/reference/gotchas.md +47 -0
- package/skills/autoconverge/reference/stop-conditions.md +59 -0
- package/skills/autoconverge/workflow/converge.contract.test.mjs +177 -0
- package/skills/autoconverge/workflow/converge.fix-progress.test.mjs +107 -0
- package/skills/autoconverge/workflow/converge.mjs +742 -0
- package/skills/autoconverge/workflow/converge.run-input.test.mjs +81 -0
- package/skills/bugteam/SKILL.md +1 -1
- package/skills/bugteam/reference/audit-and-teammates.md +1 -1
- package/skills/bugteam/reference/team-setup.md +1 -1
- package/skills/qbug/SKILL.md +1 -1
- package/skills/qbug/test_qbug_skill_audit_schema.py +3 -3
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import { strict as assert } from 'node:assert';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { dirname, join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
const workflowDirectory = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const convergeSource = readFileSync(join(workflowDirectory, 'converge.mjs'), 'utf8');
|
|
9
|
+
|
|
10
|
+
function sourceSliceBetween(startNeedle, endNeedle) {
|
|
11
|
+
const sliceStart = convergeSource.indexOf(startNeedle);
|
|
12
|
+
assert.notEqual(sliceStart, -1, `expected ${startNeedle} to exist`);
|
|
13
|
+
const sliceEnd = convergeSource.indexOf(endNeedle, sliceStart + startNeedle.length);
|
|
14
|
+
assert.notEqual(sliceEnd, -1, `expected ${endNeedle} to exist after ${startNeedle}`);
|
|
15
|
+
return convergeSource.slice(sliceStart, sliceEnd);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const productionModule = new Function(
|
|
19
|
+
`${sourceSliceBetween('function normalizeRunInput(', '\nconst runInput =')}\n` +
|
|
20
|
+
'return { normalizeRunInput, classifyRunInput };',
|
|
21
|
+
)();
|
|
22
|
+
|
|
23
|
+
const { normalizeRunInput, classifyRunInput } = productionModule;
|
|
24
|
+
|
|
25
|
+
const VALID_COORDINATES = { owner: 'jl-cmd', repo: 'claude-code-config', prNumber: 543 };
|
|
26
|
+
|
|
27
|
+
test('an object payload passes through unchanged', () => {
|
|
28
|
+
assert.deepEqual(normalizeRunInput(VALID_COORDINATES), VALID_COORDINATES);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('a JSON-encoded string payload is parsed into coordinates', () => {
|
|
32
|
+
assert.deepEqual(normalizeRunInput(JSON.stringify(VALID_COORDINATES)), VALID_COORDINATES);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('a non-JSON string returns null rather than throwing', () => {
|
|
36
|
+
assert.equal(normalizeRunInput('not json at all'), null);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('an empty string returns null rather than throwing', () => {
|
|
40
|
+
assert.equal(normalizeRunInput(''), null);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('classifyRunInput accepts coordinates carrying owner, repo, and prNumber', () => {
|
|
44
|
+
const classified = classifyRunInput(VALID_COORDINATES);
|
|
45
|
+
assert.deepEqual(classified.input, VALID_COORDINATES);
|
|
46
|
+
assert.equal(classified.blocker, null);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('classifyRunInput blocks a failed parse with a structured blocker and no input', () => {
|
|
50
|
+
const classified = classifyRunInput('not json at all');
|
|
51
|
+
assert.equal(classified.input, null);
|
|
52
|
+
assert.match(classified.blocker, /coordinates/i);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('classifyRunInput blocks a null payload with a structured blocker', () => {
|
|
56
|
+
const classified = classifyRunInput(null);
|
|
57
|
+
assert.equal(classified.input, null);
|
|
58
|
+
assert.match(classified.blocker, /coordinates/i);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('classifyRunInput blocks a payload missing owner', () => {
|
|
62
|
+
const classified = classifyRunInput({ repo: 'claude-code-config', prNumber: 543 });
|
|
63
|
+
assert.equal(classified.input, null);
|
|
64
|
+
assert.match(classified.blocker, /coordinates/i);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('classifyRunInput blocks a payload missing prNumber', () => {
|
|
68
|
+
const classified = classifyRunInput({ owner: 'jl-cmd', repo: 'claude-code-config' });
|
|
69
|
+
assert.equal(classified.input, null);
|
|
70
|
+
assert.match(classified.blocker, /coordinates/i);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('the top-level run guards an unusable input into a structured blocker before reading input.owner', () => {
|
|
74
|
+
const guardBlock = convergeSource.slice(
|
|
75
|
+
convergeSource.indexOf('const runInput = classifyRunInput('),
|
|
76
|
+
convergeSource.indexOf('const prCoordinates ='),
|
|
77
|
+
);
|
|
78
|
+
assert.match(guardBlock, /runInput\.blocker/);
|
|
79
|
+
assert.match(guardBlock, /converged: false/);
|
|
80
|
+
assert.match(guardBlock, /return/);
|
|
81
|
+
});
|
package/skills/bugteam/SKILL.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: bugteam
|
|
3
3
|
description: >-
|
|
4
4
|
Open pull request audit–fix until convergence: CODE_RULES gate, clean-room
|
|
5
|
-
audit (`code-quality-agent`, opus) and fix (`clean-coder`,
|
|
5
|
+
audit (`code-quality-agent`, opus) and fix (`clean-coder`, fable), per-loop
|
|
6
6
|
GitHub reviews, 20-audit cap; grant then revoke `.claude/**`. Spawns
|
|
7
7
|
background subagents (`Agent(..., run_in_background=true)`). Triggers: '/bugteam', 'run
|
|
8
8
|
the bug team', 'auto-fix the PR until clean', 'loop audit and fix'.
|
|
@@ -110,7 +110,7 @@ truth.
|
|
|
110
110
|
|
|
111
111
|
- **Subagent roles (spawned per loop, not at invocation start):**
|
|
112
112
|
- `bugfind` — `code-quality-agent`, model opus (Opus 4.7 at default xhigh effort)
|
|
113
|
-
- `bugfix` — `clean-coder`, model
|
|
113
|
+
- `bugfix` — `clean-coder`, model fable
|
|
114
114
|
|
|
115
115
|
### Loop state block
|
|
116
116
|
|
package/skills/qbug/SKILL.md
CHANGED
|
@@ -107,7 +107,7 @@ Then call `Agent` twice in the same message — the primary clean-coder and the
|
|
|
107
107
|
```
|
|
108
108
|
Agent(
|
|
109
109
|
subagent_type="clean-coder",
|
|
110
|
-
model="
|
|
110
|
+
model="fable",
|
|
111
111
|
description="qbug primary audit/fix cycle for PR <number>",
|
|
112
112
|
prompt="<filled cycle XML; see § Subagent cycle prompt>",
|
|
113
113
|
run_in_background=False
|
|
@@ -113,10 +113,10 @@ def test_contract_should_require_files_opened_in_proof_of_absence() -> None:
|
|
|
113
113
|
)
|
|
114
114
|
|
|
115
115
|
|
|
116
|
-
def
|
|
116
|
+
def test_step2_spawn_should_include_model_fable_parameter() -> None:
|
|
117
117
|
skill_text = _load_skill_text()
|
|
118
|
-
assert 'model="
|
|
119
|
-
"Step 2 Agent() spawn template must include model=\"
|
|
118
|
+
assert 'model="fable"' in skill_text, (
|
|
119
|
+
"Step 2 Agent() spawn template must include model=\"fable\" for the primary subagent"
|
|
120
120
|
)
|
|
121
121
|
|
|
122
122
|
|