claude-dev-env 1.78.0 → 1.80.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/CLAUDE.md +1 -0
- package/_shared/pr-loop/scripts/copilot_quota.py +360 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/copilot_quota_constants.py +24 -0
- package/_shared/pr-loop/scripts/tests/CLAUDE.md +7 -0
- package/_shared/pr-loop/scripts/tests/fixtures/copilot_internal_user_jonecho.json +76 -0
- package/_shared/pr-loop/scripts/tests/test_copilot_quota.py +242 -0
- package/_shared/pr-loop/scripts/tests/test_copilot_quota_constants.py +63 -0
- package/audit-rubrics/category_rubrics/category-k-codebase-conflicts.md +1 -0
- package/bin/install.mjs +1 -0
- package/bin/install.test.mjs +3 -2
- package/hooks/blocking/CLAUDE.md +3 -2
- package/hooks/blocking/code_rules_docstrings.py +609 -16
- package/hooks/blocking/code_rules_enforcer.py +41 -0
- package/hooks/blocking/code_rules_imports_logging.py +136 -1
- package/hooks/blocking/code_rules_naming_collection.py +76 -1
- package/hooks/blocking/code_rules_paired_test.py +240 -22
- package/hooks/blocking/code_rules_test_assertions.py +159 -1
- package/hooks/blocking/code_rules_unused_imports.py +7 -63
- package/hooks/blocking/env_var_table_code_drift_blocker.py +475 -0
- package/hooks/blocking/package_inventory_stale_blocker.py +54 -15
- package/hooks/blocking/test_code_rules_enforcer_docstring_field_runmode_outcome.py +129 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_length_constant_superlative.py +198 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_no_network.py +115 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_unreferenced_param.py +160 -0
- package/hooks/blocking/test_code_rules_enforcer_js_returns_object.py +72 -0
- package/hooks/blocking/test_code_rules_enforcer_module_docstring_data_schema_scope.py +82 -0
- package/hooks/blocking/test_code_rules_enforcer_paired_test.py +190 -0
- package/hooks/blocking/test_code_rules_enforcer_polarity_name_contradiction.py +76 -0
- package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +96 -15
- package/hooks/blocking/test_code_rules_enforcer_vacuous_cleanup_assertion.py +132 -0
- package/hooks/blocking/test_code_rules_js_returns_object_schemaless.py +167 -0
- package/hooks/blocking/test_env_var_table_code_drift_blocker.py +94 -0
- package/hooks/blocking/test_package_inventory_stale_blocker.py +46 -0
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +7 -7
- package/hooks/hooks_constants/CLAUDE.md +1 -0
- package/hooks/hooks_constants/blocking_check_limits.py +79 -0
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +28 -0
- package/hooks/hooks_constants/env_var_table_code_drift_constants.py +64 -0
- package/hooks/hooks_constants/package_inventory_stale_blocker_constants.py +20 -9
- package/hooks/hooks_constants/paired_test_coverage_constants.py +11 -3
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +4 -0
- package/hooks/hooks_constants/test_code_rules_enforcer_constants.py +93 -0
- package/hooks/hooks_constants/unused_module_import_constants.py +0 -1
- package/package.json +1 -1
- package/rules/CLAUDE.md +1 -0
- package/rules/docstring-prose-matches-implementation.md +57 -54
- package/rules/env-var-table-code-drift.md +24 -0
- package/rules/package-inventory-stale-entry.md +4 -4
- package/rules/paired-test-coverage.md +12 -5
- package/skills/autoconverge/SKILL.md +26 -5
- package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +4 -4
- package/skills/autoconverge/workflow/converge.contract.test.mjs +56 -120
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +45 -5
- package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +50 -46
- package/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +6 -6
- package/skills/autoconverge/workflow/converge.mjs +110 -228
- package/skills/autoconverge/workflow/converge.run-input.test.mjs +11 -0
- package/skills/autoconverge/workflow/converge_multi.mjs +23 -7
- package/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +28 -2
- package/skills/pr-converge/SKILL.md +28 -2
- package/skills/pr-converge/reference/convergence-gates.md +13 -1
- package/skills/pr-converge/reference/state-schema.md +11 -0
|
@@ -79,3 +79,14 @@ test('the top-level run guards an unusable input into a structured blocker befor
|
|
|
79
79
|
assert.match(guardBlock, /converged: false/);
|
|
80
80
|
assert.match(guardBlock, /return/);
|
|
81
81
|
});
|
|
82
|
+
|
|
83
|
+
test('classifyRunInput passes copilotDisabled through alongside the coordinates', () => {
|
|
84
|
+
const classified = classifyRunInput({ ...VALID_COORDINATES, copilotDisabled: true });
|
|
85
|
+
assert.equal(classified.blocker, null);
|
|
86
|
+
assert.equal(classified.input.copilotDisabled, true);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('classifyRunInput leaves copilotDisabled undefined when the input omits it', () => {
|
|
90
|
+
const classified = classifyRunInput(VALID_COORDINATES);
|
|
91
|
+
assert.equal(classified.input.copilotDisabled, undefined);
|
|
92
|
+
});
|
|
@@ -102,6 +102,28 @@ function classifyMultiInput(rawArgs) {
|
|
|
102
102
|
return { input: candidate, blocker: null }
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Build the converge.mjs child-run args for one validated PR entry.
|
|
107
|
+
*
|
|
108
|
+
* Every per-run opt-out the child converge.mjs reads is forwarded here so the
|
|
109
|
+
* fan-out honors it: bugbotDisabled skips the Bugbot phase, and copilotDisabled
|
|
110
|
+
* short-circuits the Copilot quota gate when the account is out of premium
|
|
111
|
+
* requests. An entry that omits an opt-out defaults it to false so the child
|
|
112
|
+
* runs that phase.
|
|
113
|
+
* @param {object} prEntry one validated element of the args.prs array
|
|
114
|
+
* @returns {object} the args object passed to the converge.mjs child run
|
|
115
|
+
*/
|
|
116
|
+
function childRunInput(prEntry) {
|
|
117
|
+
return {
|
|
118
|
+
owner: prEntry.owner,
|
|
119
|
+
repo: prEntry.repo,
|
|
120
|
+
prNumber: prEntry.prNumber,
|
|
121
|
+
repoPath: prEntry.repoPath,
|
|
122
|
+
bugbotDisabled: Boolean(prEntry.bugbotDisabled),
|
|
123
|
+
copilotDisabled: Boolean(prEntry.copilotDisabled),
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
105
127
|
const multiInput = classifyMultiInput(args)
|
|
106
128
|
if (multiInput.blocker) {
|
|
107
129
|
return { converged: false, prCount: 0, convergedCount: 0, results: [], blocker: multiInput.blocker }
|
|
@@ -115,13 +137,7 @@ const childResults = await parallel(
|
|
|
115
137
|
input.prs.map((eachPr) => async () => {
|
|
116
138
|
const childOutcome = await workflow(
|
|
117
139
|
{ scriptPath: input.convergeScriptPath },
|
|
118
|
-
|
|
119
|
-
owner: eachPr.owner,
|
|
120
|
-
repo: eachPr.repo,
|
|
121
|
-
prNumber: eachPr.prNumber,
|
|
122
|
-
repoPath: eachPr.repoPath,
|
|
123
|
-
bugbotDisabled: Boolean(eachPr.bugbotDisabled),
|
|
124
|
-
},
|
|
140
|
+
childRunInput(eachPr),
|
|
125
141
|
)
|
|
126
142
|
return {
|
|
127
143
|
owner: eachPr.owner,
|
|
@@ -17,9 +17,9 @@ function sourceSliceBetween(startNeedle, endNeedle) {
|
|
|
17
17
|
|
|
18
18
|
const productionModule = new Function(
|
|
19
19
|
`${sourceSliceBetween('function normalizeMultiInput(', '\nconst multiInput =')}\n` +
|
|
20
|
-
'return { normalizeMultiInput, isUsablePrEntry, classifyMultiInput };',
|
|
20
|
+
'return { normalizeMultiInput, isUsablePrEntry, classifyMultiInput, childRunInput };',
|
|
21
21
|
)();
|
|
22
|
-
const { normalizeMultiInput, classifyMultiInput } = productionModule;
|
|
22
|
+
const { normalizeMultiInput, classifyMultiInput, childRunInput } = productionModule;
|
|
23
23
|
|
|
24
24
|
const SCRIPT_PATH = '/abs/skills/autoconverge/workflow/converge.mjs';
|
|
25
25
|
|
|
@@ -98,3 +98,29 @@ test('a null payload is blocked', () => {
|
|
|
98
98
|
assert.equal(classified.input, null);
|
|
99
99
|
assert.match(classified.blocker, /did not parse/);
|
|
100
100
|
});
|
|
101
|
+
|
|
102
|
+
test('childRunInput forwards the PR coordinates to the child run', () => {
|
|
103
|
+
const childArgs = childRunInput(validEntry(398));
|
|
104
|
+
assert.equal(childArgs.owner, 'JonEcho');
|
|
105
|
+
assert.equal(childArgs.repo, 'python-automation');
|
|
106
|
+
assert.equal(childArgs.prNumber, 398);
|
|
107
|
+
assert.equal(childArgs.repoPath, '/worktrees/pr-398');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('childRunInput forwards copilotDisabled true when the entry opts out', () => {
|
|
111
|
+
const optedOutEntry = { ...validEntry(398), copilotDisabled: true };
|
|
112
|
+
assert.equal(childRunInput(optedOutEntry).copilotDisabled, true);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('childRunInput defaults copilotDisabled to false when the entry omits it', () => {
|
|
116
|
+
assert.equal(childRunInput(validEntry(398)).copilotDisabled, false);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('childRunInput forwards bugbotDisabled true when the entry opts out', () => {
|
|
120
|
+
const optedOutEntry = { ...validEntry(398), bugbotDisabled: true };
|
|
121
|
+
assert.equal(childRunInput(optedOutEntry).bugbotDisabled, true);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test('childRunInput defaults bugbotDisabled to false when the entry omits it', () => {
|
|
125
|
+
assert.equal(childRunInput(validEntry(398)).bugbotDisabled, false);
|
|
126
|
+
});
|
|
@@ -43,6 +43,26 @@ working directory routes into the PR's repo for local work and returns to
|
|
|
43
43
|
the session worktree before teardown. See
|
|
44
44
|
[`reference/per-tick.md` § Step 1.5](reference/per-tick.md).
|
|
45
45
|
|
|
46
|
+
## Copilot quota pre-check (start of run)
|
|
47
|
+
|
|
48
|
+
On the first tick, before Step 4, run the Copilot quota pre-check once:
|
|
49
|
+
`python "$HOME/.claude/_shared/pr-loop/scripts/copilot_quota.py"`. It reads the
|
|
50
|
+
account's remaining Copilot premium-request quota via `gh api
|
|
51
|
+
copilot_internal/user` and prints one line — log that line. Exit 0 means Copilot
|
|
52
|
+
has quota, so leave `copilot_down` false. Any non-zero exit means skip Copilot
|
|
53
|
+
for the whole run — the account is out of quota, the quota API or account access
|
|
54
|
+
is down, or no account is set — so set `copilot_down = true` in
|
|
55
|
+
`pr-converge-state.json`. The account comes from the `COPILOT_QUOTA_ACCOUNT`
|
|
56
|
+
environment variable or a git-ignored `.env` file, and the no-account line names
|
|
57
|
+
the exact `.env` path and key to set.
|
|
58
|
+
|
|
59
|
+
Run the pre-check once, not per tick. Every tick reads `copilot_down` from
|
|
60
|
+
state. When `copilot_down` is true, skip the Copilot gate outright — no fetch,
|
|
61
|
+
no request, no poll, no agent — and export `CLAUDE_REVIEWS_DISABLED="copilot"`
|
|
62
|
+
in that tick's shell before the convergence check, so `check_convergence.py`
|
|
63
|
+
bypasses the Copilot review gate and the pending-requested-reviews gate and the
|
|
64
|
+
run still marks ready on the remaining signals.
|
|
65
|
+
|
|
46
66
|
## Budget-aware tick boundaries
|
|
47
67
|
|
|
48
68
|
Before starting any tick, estimate whether the remaining session/usage
|
|
@@ -73,8 +93,8 @@ so the next tick resumes with accurate state.
|
|
|
73
93
|
|
|
74
94
|
Fields: `phase`, `tick_count`, `bugbot_clean_at`, `code_review_clean_at`,
|
|
75
95
|
`bugteam_clean_at`, `copilot_clean_at`, `current_head`,
|
|
76
|
-
`bugbot_acknowledged_at`, `bugbot_down`, `
|
|
77
|
-
`bugteam_skill_invoked_at_tick`.
|
|
96
|
+
`bugbot_acknowledged_at`, `bugbot_down`, `copilot_down`,
|
|
97
|
+
`bugteam_skill_invoked_at_head`, `bugteam_skill_invoked_at_tick`.
|
|
78
98
|
|
|
79
99
|
## Gotchas
|
|
80
100
|
|
|
@@ -300,6 +320,7 @@ round as converged. This rule holds every tick, every loop, every PR.
|
|
|
300
320
|
- [ ] Any unresolved? → For each: verify concern against current HEAD;
|
|
301
321
|
if still applies → Fix (spawn `clean-coder`) → reply → resolve;
|
|
302
322
|
if no longer applies → reply-with-note → resolve. Push if any code changed → return to Step 4
|
|
323
|
+
- [ ] When `copilot_down == true` (start-of-run quota pre-check), skip the Copilot fetch below — no request, no poll, no agent — and continue to gate (b); the Copilot gate is bypassed for the whole run.
|
|
303
324
|
- [ ] Fetch Copilot review on `current_head` (top-level review state — uses get_reviews, identifies by reviewer):
|
|
304
325
|
```
|
|
305
326
|
python ~/.claude/skills/pr-converge/scripts/fetch_copilot_reviews.py --owner <O> --repo <R> --pr-number <N>
|
|
@@ -316,6 +337,7 @@ round as converged. This rule holds every tick, every loop, every PR.
|
|
|
316
337
|
- [ ] not mergeable → rebase → push → return to Step 1
|
|
317
338
|
|
|
318
339
|
**(c) Request Copilot review**
|
|
340
|
+
- [ ] When `copilot_down == true`, skip the request and do not enter COPILOT_WAIT — continue to gate (d); the run marks ready on the remaining signals.
|
|
319
341
|
- [ ] Check for pending Copilot review:
|
|
320
342
|
```
|
|
321
343
|
python ~/.claude/skills/pr-converge/scripts/check_pending_reviews.py --owner <O> --repo <R> --pr-number <N>
|
|
@@ -341,6 +363,7 @@ round as converged. This rule holds every tick, every loop, every PR.
|
|
|
341
363
|
Push if code changed → return to Step 4
|
|
342
364
|
|
|
343
365
|
**(e) Mark ready**
|
|
366
|
+
- [ ] When `copilot_down == true`, export `CLAUDE_REVIEWS_DISABLED="copilot"` in this tick's shell before the check below, so it bypasses the Copilot review gate and the pending-requested-reviews gate.
|
|
344
367
|
- [ ] Run automated convergence check:
|
|
345
368
|
```
|
|
346
369
|
python $HOME/.claude/skills/pr-converge/scripts/check_convergence.py \
|
|
@@ -353,6 +376,9 @@ round as converged. This rule holds every tick, every loop, every PR.
|
|
|
353
376
|
- [ ] **Step 7a: COPILOT_WAIT — fetch Copilot, decide**
|
|
354
377
|
See: [`reference/per-tick.md` § Step 2 COPILOT_WAIT](reference/per-tick.md)
|
|
355
378
|
|
|
379
|
+
This step does not run when `copilot_down == true`: gate (c) skips the
|
|
380
|
+
Copilot request, so the loop never enters COPILOT_WAIT.
|
|
381
|
+
|
|
356
382
|
Fetch Copilot reviews + inline comments on `current_head`.
|
|
357
383
|
|
|
358
384
|
- [ ] **clean (no findings)** →
|
|
@@ -25,6 +25,12 @@ python ~/.claude/skills/pr-converge/scripts/fetch_copilot_inline_comments.py --o
|
|
|
25
25
|
→ unaddressed inline threads on the latest Copilot review at current_head
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
+
When `copilot_down == true` (start-of-run quota pre-check), skip this gate
|
|
29
|
+
entirely — no Copilot fetch, no request, no poll, no agent. Record evidence
|
|
30
|
+
"Copilot bypassed (quota pre-check non-zero) at <SHA>" and continue to gate (b); the bypass
|
|
31
|
+
holds for the whole run and the quota API is not re-queried per tick. Otherwise
|
|
32
|
+
decide among the four branches below.
|
|
33
|
+
|
|
28
34
|
Decide (four branches; match first whose predicate holds):
|
|
29
35
|
|
|
30
36
|
- **`classification == "dirty"` with non-empty inline comments matching
|
|
@@ -117,6 +123,12 @@ Persist `mergeable_state` into `merge_state_status`. Decide:
|
|
|
117
123
|
|
|
118
124
|
## (d) Post-convergence Copilot review request
|
|
119
125
|
|
|
126
|
+
When `copilot_down == true` (start-of-run quota pre-check), skip this gate: do
|
|
127
|
+
not request a Copilot review and do not enter `COPILOT_WAIT`. Export
|
|
128
|
+
`CLAUDE_REVIEWS_DISABLED="copilot"` before gate (f)'s convergence check so
|
|
129
|
+
`check_convergence.py` bypasses the Copilot review and pending-review gates.
|
|
130
|
+
Continue to gate (e).
|
|
131
|
+
|
|
120
132
|
Once gates (a), (b), and (c) all pass (Copilot clean at `current_head` *or* no
|
|
121
133
|
Copilot review yet, AND Claude clean or absent at `current_head`, AND
|
|
122
134
|
`mergeable_state == "clean"`), request Copilot review:
|
|
@@ -190,7 +202,7 @@ evidence from gates (a)–(e) above. All seven must be confirmed:
|
|
|
190
202
|
|
|
191
203
|
- [ ] `bugbot_clean_at == current_head` (from per-tick.md Step 2 BUGBOT §c)
|
|
192
204
|
- [ ] bugteam `convergence (zero findings)` at `current_head` (from per-tick.md Step 2 BUGTEAM §d)
|
|
193
|
-
- [ ] `copilot_clean_at == current_head` (from gate (a) or gate (d))
|
|
205
|
+
- [ ] `copilot_clean_at == current_head` (from gate (a) or gate (d)), or the Copilot gate bypassed for the run via `copilot_down`
|
|
194
206
|
- [ ] Claude `APPROVED` or absent at `current_head` (from gate (b))
|
|
195
207
|
- [ ] `mergeable_state == "clean"` AND `mergeable == true` (from gate (c))
|
|
196
208
|
- [ ] Zero unresolved review threads anywhere on the PR (from gate (e))
|
|
@@ -22,6 +22,17 @@ live ONLY in the single-PR `$CLAUDE_JOB_DIR/pr-converge-state.json` file
|
|
|
22
22
|
with no Copilot review surfaced at `current_head`. Escalate as hard blocker
|
|
23
23
|
at `>= 3`. Reset to `0` when a Copilot review surfaces at `current_head`
|
|
24
24
|
(APPROVED or dirty) or on any non-COPILOT_WAIT branch.
|
|
25
|
+
- `copilot_down`: boolean, init `false`. Set `true` at the start of the run
|
|
26
|
+
when the Copilot quota pre-check
|
|
27
|
+
(`_shared/pr-loop/scripts/copilot_quota.py`) exits non-zero — the account is
|
|
28
|
+
out of premium-request quota, the quota API or account access is down, or no
|
|
29
|
+
account is set. Read once from the start-of-run pre-check, not re-queried per
|
|
30
|
+
tick. While `true`, every tick skips the Copilot gate outright (no fetch, no
|
|
31
|
+
request, no poll, no agent) and exports `CLAUDE_REVIEWS_DISABLED="copilot"`
|
|
32
|
+
before the convergence check, so `check_convergence.py` bypasses the Copilot
|
|
33
|
+
review gate and the pending-requested-reviews gate and the run marks ready on
|
|
34
|
+
the remaining signals. Unlike `bugbot_down`, it is not reset on push — a
|
|
35
|
+
quota outage holds for the whole run.
|
|
25
36
|
- `inline_lag_streak`: integer, init `0`. Consecutive ticks where review
|
|
26
37
|
body shows findings against `current_head` but inline API returns zero
|
|
27
38
|
matching. Reset to `0` on any other branch outcome.
|