claude-dev-env 1.82.0 → 1.84.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 +16 -13
- package/_shared/pr-loop/audit-contract.md +24 -12
- package/_shared/pr-loop/scripts/CLAUDE.md +1 -0
- package/_shared/pr-loop/scripts/README.md +1 -0
- package/_shared/pr-loop/scripts/_claude_permissions_common.py +16 -5
- package/_shared/pr-loop/scripts/code_rules_gate.py +2 -3
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/reviewer_availability_constants.py +12 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +0 -2
- package/_shared/pr-loop/scripts/reviewer_availability.py +182 -0
- package/_shared/pr-loop/scripts/reviews_disabled.py +2 -0
- package/_shared/pr-loop/scripts/terminology_sweep.py +9 -33
- package/_shared/pr-loop/scripts/tests/CLAUDE.md +2 -0
- package/_shared/pr-loop/scripts/tests/test__claude_permissions_common.py +212 -0
- package/_shared/pr-loop/scripts/tests/test_agent_config_carveout.py +18 -0
- package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +18 -0
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +37 -0
- package/_shared/pr-loop/scripts/tests/test_reviewer_availability.py +159 -0
- package/_shared/pr-loop/scripts/tests/test_reviewer_availability_constants.py +36 -0
- package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +14 -0
- package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +14 -4
- package/agents/clean-coder.md +3 -0
- package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +1 -1
- package/bin/install.mjs +16 -8
- package/bin/install.test.mjs +17 -6
- package/commands/CLAUDE.md +0 -1
- package/docs/CODE_RULES.md +1 -1
- package/hooks/blocking/CLAUDE.md +2 -4
- package/hooks/blocking/code_rules_constants_config.py +164 -1
- package/hooks/blocking/code_rules_docstrings.py +428 -15
- package/hooks/blocking/code_rules_enforcer.py +33 -0
- package/hooks/blocking/code_rules_imports_logging.py +867 -1
- package/hooks/blocking/code_rules_magic_values.py +5 -0
- package/hooks/blocking/code_rules_naming_collection.py +152 -6
- package/hooks/blocking/code_rules_shared.py +34 -0
- package/hooks/blocking/code_rules_string_magic.py +68 -0
- package/hooks/blocking/duplicate_rmtree_helper_blocker.py +4 -4
- package/hooks/blocking/pre_tool_use_dispatcher.py +3 -3
- package/hooks/blocking/reviewer_spawn_gate.py +182 -0
- package/hooks/blocking/stale_comment_reference_blocker.py +267 -0
- package/hooks/blocking/state_description_blocker.py +96 -5
- package/hooks/blocking/test_code_rules_config_duplicate_path_anchor.py +132 -0
- package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +2 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_delegation_summary.py +385 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_prose_wall_illustration.py +197 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_runon_sentence.py +27 -0
- package/hooks/blocking/test_code_rules_enforcer_join_separator_magic.py +67 -0
- package/hooks/blocking/test_code_rules_enforcer_module_docstring_roster.py +40 -0
- package/hooks/blocking/test_code_rules_enforcer_naive_datetime.py +213 -0
- package/hooks/blocking/test_code_rules_enforcer_referenced_underscore_loop.py +169 -0
- package/hooks/blocking/test_code_rules_enforcer_split_constants_config.py +33 -0
- package/hooks/blocking/test_code_rules_enforcer_split_shared.py +23 -0
- package/hooks/blocking/test_code_rules_js_bare_flag_return_directive.py +266 -0
- package/hooks/blocking/test_code_rules_js_sibling_return_object_key_drift.py +490 -0
- package/hooks/blocking/test_code_rules_logging_adjacent_literals.py +171 -0
- package/hooks/blocking/test_code_rules_magic_values.py +54 -0
- package/hooks/blocking/test_duplicate_rmtree_helper_blocker.py +0 -6
- package/hooks/blocking/test_pr_converge_bugteam_enforcer_state_tolerance.py +184 -0
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +9 -3
- package/hooks/blocking/test_reviewer_spawn_gate.py +230 -0
- package/hooks/blocking/test_shared_stdin_adoption.py +5 -30
- package/hooks/blocking/test_stale_comment_reference_blocker.py +236 -0
- package/hooks/blocking/test_state_description_blocker.py +135 -0
- package/hooks/hooks.json +5 -0
- package/hooks/hooks_constants/CLAUDE.md +3 -4
- package/hooks/hooks_constants/blocking_check_limits.py +46 -0
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +41 -0
- package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -1
- package/hooks/hooks_constants/duplicate_rmtree_helper_blocker_constants.py +0 -1
- package/hooks/hooks_constants/post_tool_use_dispatcher_constants.py +0 -5
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +4 -0
- package/hooks/hooks_constants/reviewer_spawn_gate_constants.py +41 -0
- package/hooks/hooks_constants/stale_comment_reference_blocker_constants.py +76 -0
- package/hooks/hooks_constants/state_description_blocker_constants.py +8 -0
- package/hooks/validation/post_tool_use_dispatcher.py +1 -1
- package/hooks/validation/test_post_tool_use_dispatcher.py +51 -35
- package/hooks/workflow/CLAUDE.md +2 -8
- package/package.json +1 -1
- package/rules/CLAUDE.md +5 -2
- package/rules/claude-md-orphan-file.md +5 -0
- package/rules/docstring-prose-matches-implementation.md +10 -1
- package/rules/env-var-table-code-drift.md +5 -0
- package/rules/es-exe-file-search.md +17 -0
- package/rules/no-historical-clutter.md +12 -1
- package/rules/orphan-css-class.md +5 -0
- package/rules/package-inventory-stale-entry.md +10 -0
- package/rules/paired-test-coverage.md +5 -0
- package/rules/plain-illustrative-docstrings.md +40 -1
- package/rules/verify-before-asking.md +7 -0
- package/rules/verify-runtime-state.md +40 -0
- package/rules/windows-filesystem-safe.md +8 -0
- package/rules/workers-done-before-complete.md +33 -0
- package/rules/workflow-substitution-slots.md +5 -0
- package/skills/CLAUDE.md +7 -2
- package/skills/autoconverge/SKILL.md +48 -61
- package/skills/autoconverge/reference/closing-report.md +6 -6
- package/skills/autoconverge/reference/convergence.md +17 -15
- package/skills/autoconverge/reference/gotchas.md +6 -3
- package/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +2 -9
- package/skills/autoconverge/workflow/converge.contract.test.mjs +153 -10
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +55 -19
- package/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +16 -9
- package/skills/autoconverge/workflow/converge.mjs +167 -106
- package/skills/autoconverge/workflow/render_report.py +7 -11
- package/skills/bugteam/CLAUDE.md +1 -1
- package/skills/bugteam/PROMPTS.md +7 -6
- package/skills/bugteam/SKILL.md +25 -72
- package/skills/bugteam/reference/CLAUDE.md +1 -3
- package/skills/bugteam/reference/README.md +1 -1
- package/skills/bugteam/reference/audit-and-teammates.md +1 -1
- package/skills/bugteam/reference/obstacles/fix-publish-summary.md +1 -1
- package/skills/bugteam/reference/team-setup.md +8 -7
- package/skills/bugteam/scripts/CLAUDE.md +0 -6
- package/skills/bugteam/scripts/README.md +0 -4
- package/skills/bugteam/scripts/bugteam_scripts_constants/CLAUDE.md +0 -1
- package/skills/code/SKILL.md +2 -0
- package/skills/copilot-review/CLAUDE.md +1 -1
- package/skills/copilot-review/SKILL.md +25 -23
- package/skills/everything-search/SKILL.md +5 -0
- package/skills/findbugs/CLAUDE.md +2 -2
- package/skills/findbugs/SKILL.md +22 -83
- package/skills/fixbugs/SKILL.md +2 -4
- package/skills/log-audit/CLAUDE.md +20 -0
- package/skills/log-audit/SKILL.md +68 -0
- package/skills/log-audit/reference/CLAUDE.md +9 -0
- package/skills/log-audit/reference/charter.md +52 -0
- package/skills/log-audit/scripts/CLAUDE.md +27 -0
- package/skills/log-audit/scripts/cluster_recurrences.py +261 -0
- package/skills/log-audit/scripts/collect_log_window.py +199 -0
- package/skills/log-audit/scripts/log_audit_constants/CLAUDE.md +12 -0
- package/skills/log-audit/scripts/log_audit_constants/cluster_recurrences_constants.py +23 -0
- package/skills/log-audit/scripts/log_audit_constants/collect_log_window_constants.py +24 -0
- package/skills/log-audit/scripts/log_audit_constants/mine_copilot_findings_constants.py +49 -0
- package/skills/log-audit/scripts/mine_copilot_findings.py +302 -0
- package/skills/log-audit/scripts/test_cluster_recurrences.py +160 -0
- package/skills/log-audit/scripts/test_collect_log_window.py +111 -0
- package/skills/log-audit/scripts/test_mine_copilot_findings.py +126 -0
- package/skills/monitor-open-prs/SKILL.md +2 -2
- package/skills/post-audit-findings/SKILL.md +84 -0
- package/skills/pr-converge/CLAUDE.md +2 -0
- package/skills/pr-converge/SKILL.md +72 -59
- package/skills/pr-converge/reference/CLAUDE.md +1 -1
- package/skills/pr-converge/reference/convergence-gates.md +16 -19
- package/skills/pr-converge/reference/examples.md +5 -5
- package/skills/pr-converge/reference/fix-protocol.md +16 -43
- package/skills/pr-converge/reference/obstacles/fix-publish-summary.md +1 -1
- package/skills/pr-converge/reference/per-tick.md +24 -45
- package/skills/pr-converge/reference/state-schema.md +15 -0
- package/skills/pr-converge/scripts/README.md +3 -5
- package/skills/pr-fix-protocol/SKILL.md +70 -0
- package/skills/pr-loop-lifecycle/SKILL.md +73 -0
- package/skills/{bugteam → pr-loop-lifecycle}/reference/teardown-publish-permissions.md +22 -24
- package/skills/pr-scope-resolve/SKILL.md +48 -0
- package/skills/qbug/CLAUDE.md +4 -4
- package/skills/qbug/SKILL.md +46 -144
- package/skills/qbug/test_qbug_skill_audit_schema.py +2 -2
- package/skills/qbug/test_qbug_skill_post_fix_audit.py +1 -1
- package/skills/reviewer-gates/SKILL.md +96 -0
- package/skills/session-log/CLAUDE.md +7 -7
- package/skills/session-log/SKILL.md +27 -44
- package/skills/test_markdown_link_integrity.py +103 -0
- package/commands/doc-gist.md +0 -16
- package/hooks/blocking/_md_to_html_blocker_test_support.py +0 -65
- package/hooks/blocking/conftest.py +0 -30
- package/hooks/blocking/md_path_exemptions.py +0 -224
- package/hooks/blocking/md_to_html_blocker.py +0 -155
- package/hooks/blocking/test_md_to_html_blocker_exemptions.py +0 -434
- package/hooks/blocking/test_md_to_html_blocker_extensions.py +0 -157
- package/hooks/blocking/test_md_to_html_blocker_path_resolution.py +0 -336
- package/hooks/hooks_constants/doc_gist_auto_publish_constants.py +0 -18
- package/hooks/hooks_constants/html_companion_constants.py +0 -20
- package/hooks/hooks_constants/md_to_html_blocker_constants.py +0 -76
- package/hooks/hooks_constants/test_md_to_html_blocker_constants.py +0 -125
- package/hooks/workflow/doc_gist_auto_publish.py +0 -144
- package/hooks/workflow/md_to_html_companion.py +0 -358
- package/hooks/workflow/test_doc_gist_auto_publish.py +0 -117
- package/hooks/workflow/test_md_to_html_companion.py +0 -613
- package/skills/bugteam/reference/audit-contract.md +0 -163
- package/skills/bugteam/scripts/_bugteam_permissions_common.py +0 -455
- package/skills/bugteam/scripts/bugteam_scripts_constants/claude_permissions_common_constants.py +0 -69
- package/skills/bugteam/scripts/grant_project_claude_permissions.py +0 -280
- package/skills/bugteam/scripts/revoke_project_claude_permissions.py +0 -266
- package/skills/bugteam/scripts/test__bugteam_permissions_common.py +0 -160
- package/skills/bugteam/scripts/test_agent_config_carveout.py +0 -356
- package/skills/bugteam/scripts/test_bugteam_permissions_common.py +0 -140
- package/skills/doc-gist/CLAUDE.md +0 -25
- package/skills/doc-gist/SKILL.md +0 -97
- package/skills/doc-gist/references/CLAUDE.md +0 -9
- package/skills/doc-gist/references/examples/01-exploration-code-approaches.html +0 -453
- package/skills/doc-gist/references/examples/02-exploration-visual-designs.html +0 -515
- package/skills/doc-gist/references/examples/03-code-review-pr.html +0 -638
- package/skills/doc-gist/references/examples/04-code-understanding.html +0 -491
- package/skills/doc-gist/references/examples/05-design-system.html +0 -629
- package/skills/doc-gist/references/examples/06-component-variants.html +0 -605
- package/skills/doc-gist/references/examples/07-prototype-animation.html +0 -455
- package/skills/doc-gist/references/examples/08-prototype-interaction.html +0 -396
- package/skills/doc-gist/references/examples/09-slide-deck.html +0 -592
- package/skills/doc-gist/references/examples/10-svg-illustrations.html +0 -492
- package/skills/doc-gist/references/examples/11-status-report.html +0 -528
- package/skills/doc-gist/references/examples/12-incident-report.html +0 -596
- package/skills/doc-gist/references/examples/13-flowchart-diagram.html +0 -395
- package/skills/doc-gist/references/examples/14-research-feature-explainer.html +0 -381
- package/skills/doc-gist/references/examples/15-research-concept-explainer.html +0 -368
- package/skills/doc-gist/references/examples/16-implementation-plan.html +0 -702
- package/skills/doc-gist/references/examples/17-pr-writeup.html +0 -595
- package/skills/doc-gist/references/examples/18-editor-triage-board.html +0 -573
- package/skills/doc-gist/references/examples/19-editor-feature-flags.html +0 -663
- package/skills/doc-gist/references/examples/20-editor-prompt-tuner.html +0 -722
- package/skills/doc-gist/references/examples/21-decision-signoff.html +0 -546
- package/skills/doc-gist/references/examples/CLAUDE.md +0 -25
- package/skills/doc-gist/references/examples/README.md +0 -5
- package/skills/doc-gist/scripts/CLAUDE.md +0 -27
- package/skills/doc-gist/scripts/doc_gist_scripts_constants/CLAUDE.md +0 -10
- package/skills/doc-gist/scripts/doc_gist_scripts_constants/gist_upload_constants.py +0 -16
- package/skills/doc-gist/scripts/gist_upload.py +0 -177
- package/skills/doc-gist/scripts/test_gist_upload.py +0 -51
- /package/skills/{doc-gist/scripts/doc_gist_scripts_constants → log-audit/scripts/log_audit_constants}/__init__.py +0 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Verify Runtime State
|
|
2
|
+
|
|
3
|
+
**When this applies:** Before stating that a component is fine, healthy, not at fault, or working — during debugging, triage, or any judgment about whether something runs.
|
|
4
|
+
|
|
5
|
+
## Rule
|
|
6
|
+
|
|
7
|
+
A verdict that a component is fine or not the cause rests on live evidence gathered this session: a process list, a port probe, a log tail, an HTTP status code, or a fresh repro. Reading the code, recalling how the component behaved earlier, or trusting a prior session's finding does not settle whether it runs right now. Code shows what should happen; only a live probe shows what does.
|
|
8
|
+
|
|
9
|
+
Gather the probe before you write the verdict. When the probe contradicts the code (the code looks right but the port refuses the connection), report the live result and treat the component as suspect.
|
|
10
|
+
|
|
11
|
+
## Grounding checklist
|
|
12
|
+
|
|
13
|
+
Before stating a runtime claim, gather the matching live signal:
|
|
14
|
+
|
|
15
|
+
| Claim | Grounding probe |
|
|
16
|
+
|---|---|
|
|
17
|
+
| The service is healthy | Hit its health endpoint and read the status code. |
|
|
18
|
+
| The config is in effect | Print the loaded config at runtime and read the value. |
|
|
19
|
+
| The server is up | Probe the port; a refused connection means it is down. |
|
|
20
|
+
| The process is running | List processes and match the name or PID. |
|
|
21
|
+
| The change took effect | Drive the flow and watch the new behavior. |
|
|
22
|
+
| The dependency is reachable | Send one real request and read the response. |
|
|
23
|
+
|
|
24
|
+
Only after a live signal backs the claim do you state it.
|
|
25
|
+
|
|
26
|
+
## Examples
|
|
27
|
+
|
|
28
|
+
**Wrong:** "The search server code looks correct, so it is not the problem."
|
|
29
|
+
**Right:** Probe port 54321; report "connection refused — the server is down."
|
|
30
|
+
|
|
31
|
+
**Wrong:** "This function handles the retry, so the request must be going through."
|
|
32
|
+
**Right:** Tail the request log and confirm the retry fired, or report that no retry line appears.
|
|
33
|
+
|
|
34
|
+
**Wrong:** "The config sets the timeout to 30 seconds, so the timeout is fine."
|
|
35
|
+
**Right:** Print the loaded config at runtime and report the value the process actually holds.
|
|
36
|
+
|
|
37
|
+
## Relationship to other rules
|
|
38
|
+
|
|
39
|
+
- **`verify-before-asking`** answers questions with a tool before asking the user. This rule extends that to runtime verdicts: gather the live probe before you conclude, not just before you ask.
|
|
40
|
+
- **`long-horizon-autonomy`** requires every progress claim to rest on a tool result from this session. A runtime verdict is a progress claim; this rule names the probes that back it.
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.py"
|
|
4
|
+
- "**/*.mjs"
|
|
5
|
+
- "**/*.js"
|
|
6
|
+
- "**/*.ts"
|
|
7
|
+
---
|
|
8
|
+
|
|
1
9
|
# Windows Filesystem Safety
|
|
2
10
|
|
|
3
11
|
Never call `shutil.rmtree` with `ignore_errors=True` — Windows `ReadOnly` files (e.g. `.git/objects/pack/`) raise `PermissionError`, the flag swallows it, and the tree silently stays on disk. Use an `onexc` (Python >= 3.12) / `onerror` handler that runs `os.chmod(target_path, stat.S_IWRITE)` then retries the removal function the failure interrupted.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Workers Done Before Complete
|
|
2
|
+
|
|
3
|
+
**When this applies:** Before marking any task `completed` when the task spawned workers — subagents, workflow agents, or background shells.
|
|
4
|
+
|
|
5
|
+
## Rule
|
|
6
|
+
|
|
7
|
+
A task reaches `completed` only when two things hold: every worker it spawned has finished, and each worker's result is merged into run state — `state.json`, `pr-converge-state.json`, the task list, or whatever record the task keeps. A worker that still runs, or one that finished but whose output never landed in run state, leaves the task open.
|
|
8
|
+
|
|
9
|
+
List the live workers before you mark the task complete. When a worker is dead or hung, that is a finding to record and report, not a result to drop in silence. A step that waits on workers ends its turn `in_progress` with a wakeup scheduled, so the run picks the workers back up rather than closing the task without them.
|
|
10
|
+
|
|
11
|
+
## Checklist before marking complete
|
|
12
|
+
|
|
13
|
+
| Check | Action |
|
|
14
|
+
|---|---|
|
|
15
|
+
| Are any spawned workers still running? | List them; if yes, stay `in_progress` and schedule a wakeup. |
|
|
16
|
+
| Did every finished worker return a result? | Read each result; a dead or hung worker is a finding to report. |
|
|
17
|
+
| Is each result merged into run state? | Write it to `state.json` / the task list before closing. |
|
|
18
|
+
| Does the task's own goal now hold? | Confirm against the merged state, not a worker's self-report. |
|
|
19
|
+
|
|
20
|
+
Mark `completed` only when every row passes.
|
|
21
|
+
|
|
22
|
+
## Examples
|
|
23
|
+
|
|
24
|
+
**Wrong:** Marking the audit task complete while two bugteam workers still run in the background.
|
|
25
|
+
**Right:** List the workers, see two still running, keep the task `in_progress`, and schedule a wakeup to collect them.
|
|
26
|
+
|
|
27
|
+
**Wrong:** A worker crashes; the task closes as complete because the other workers finished.
|
|
28
|
+
**Right:** Record the crashed worker as a finding, report it, and hold the task open until its work is covered.
|
|
29
|
+
|
|
30
|
+
## Relationship to other rules
|
|
31
|
+
|
|
32
|
+
- **`long-horizon-autonomy`** covers acting on what you have and not ending a turn on a promise. This rule names the specific completion gate: workers finished and their results merged.
|
|
33
|
+
- **`skills/pr-converge/reference/state-schema.md`** defines the run-state records this rule requires a worker's result to land in before the task closes.
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.workflow.js"
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# Workflow Substitution Slot Rule
|
|
2
7
|
|
|
3
8
|
In a `.workflow.js` agent-prompt template, every per-call or per-iteration value an agent must fill in is marked with the angle-bracket convention — `<plate.svg>`, `<object.svg>`, `<glow_hex>`, `cand_<i>`. A bare token such as `cand_i` reads as a fixed literal, so an agent can create one literal directory named `cand_i` and overwrite it across every iteration of a loop, collapsing an N-iteration gate into a single run.
|
package/skills/CLAUDE.md
CHANGED
|
@@ -35,6 +35,11 @@ Skills install to `~/.claude/skills/<skill-name>/` via `packages/claude-dev-env/
|
|
|
35
35
|
- `pr-consistency-audit` — cross-file consistency check on a PR diff
|
|
36
36
|
- `copilot-review` — requests and polls a GitHub Copilot review
|
|
37
37
|
- `findbugs` / `fixbugs` — find bugs then fix them in separate passes
|
|
38
|
+
- `reviewer-gates` — availability gates for external reviewers (opt-out parse, Copilot quota, Bugbot trigger/detect)
|
|
39
|
+
- `pr-scope-resolve` — one resolution ladder for a PR-loop skill's audit/fix target
|
|
40
|
+
- `pr-fix-protocol` — fix, reply, and resolve reviewer findings; the unresolved-thread sweep
|
|
41
|
+
- `post-audit-findings` — publishes an audit pass as one GitHub PR review
|
|
42
|
+
- `pr-loop-lifecycle` — opens and closes a PR-loop run (grant, teardown, PR description, revoke, report)
|
|
38
43
|
- `code` — strict-mode code generation session
|
|
39
44
|
|
|
40
45
|
**Research and discovery**
|
|
@@ -42,7 +47,7 @@ Skills install to `~/.claude/skills/<skill-name>/` via `packages/claude-dev-env/
|
|
|
42
47
|
- `research-mode` — activates anti-hallucination discipline for a session
|
|
43
48
|
- `recall` — retrieves facts from memory files
|
|
44
49
|
- `remember` — saves a decision, gotcha, or architectural choice to the Obsidian vault
|
|
45
|
-
- `everything-search` — file-system search via the Everything
|
|
50
|
+
- `everything-search` — file-system search via the Everything `es.exe` CLI
|
|
46
51
|
- `caveman` — trims noise from a draft artifact
|
|
47
52
|
|
|
48
53
|
**Session and workflow management**
|
|
@@ -62,5 +67,5 @@ Skills install to `~/.claude/skills/<skill-name>/` via `packages/claude-dev-env/
|
|
|
62
67
|
- `pre-compact` — compact-safe session handoff
|
|
63
68
|
- `qbug` — required baseline PR audit; one clean-coder subagent loops audit → fix → commit → push until clean or stuck
|
|
64
69
|
- `skill-builder` — complete skill-building lifecycle
|
|
65
|
-
- `doc-gist` — uploads an HTML file as a secret gist
|
|
66
70
|
- `auditing-claude-config` — audits a Claude Code setup for context-budget waste and produces a migration table with savings
|
|
71
|
+
- `log-audit` — background agent that audits this repo's own logs for recurring errors and timing regressions and files grouped fixes
|
|
@@ -83,31 +83,16 @@ PR's owner.
|
|
|
83
83
|
example, `claude-code-config` while the PR lives in `llm-settings`) or in no
|
|
84
84
|
git checkout at all cannot continue.
|
|
85
85
|
|
|
86
|
-
4. **Grant project permissions.**
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
Continue once the grant lands. A user who wants future runs to skip this
|
|
97
|
-
prompt can add a standing Bash permission allow-rule for that script in their
|
|
98
|
-
settings.
|
|
99
|
-
|
|
100
|
-
5. **Copilot quota pre-check.** Before the `Workflow` call, run once:
|
|
101
|
-
`python "$HOME/.claude/_shared/pr-loop/scripts/copilot_quota.py"`
|
|
102
|
-
It reads the account's remaining Copilot premium-request quota via
|
|
103
|
-
`gh api copilot_internal/user` and prints one line — log that line. Exit 0
|
|
104
|
-
means Copilot has quota to run, so pass `copilotDisabled: false`. Any non-zero
|
|
105
|
-
exit means skip Copilot for this run — the account is out of quota, the quota
|
|
106
|
-
API or account access is down, or no account is set — so pass
|
|
107
|
-
`copilotDisabled: true`; the workflow then skips the Copilot gate with no agent
|
|
108
|
-
spawned. The account comes from the `COPILOT_QUOTA_ACCOUNT` environment
|
|
109
|
-
variable or a git-ignored `.env` file, and the no-account line names the exact
|
|
110
|
-
`.env` path and key to set.
|
|
86
|
+
4. **Grant project permissions.** Apply the `pr-loop-lifecycle` skill's Open
|
|
87
|
+
section (`../pr-loop-lifecycle/SKILL.md`) — the grant command
|
|
88
|
+
(`grant_project_claude_permissions.py`) and the auto-mode `AskUserQuestion`
|
|
89
|
+
escalation for a blocked grant both live there.
|
|
90
|
+
|
|
91
|
+
5. **Copilot quota pre-check.** Before the `Workflow` call, apply the
|
|
92
|
+
`reviewer-gates` skill's Copilot quota gate (`../reviewer-gates/SKILL.md`)
|
|
93
|
+
once. Exit 0 maps to `copilotDisabled: false` in the Workflow call; any
|
|
94
|
+
non-zero exit maps to `copilotDisabled: true`, and the workflow then skips
|
|
95
|
+
the Copilot gate with no agent spawned.
|
|
111
96
|
|
|
112
97
|
## Run the workflow
|
|
113
98
|
|
|
@@ -133,7 +118,9 @@ on completion. Watch live progress with `/workflows`.
|
|
|
133
118
|
The workflow returns
|
|
134
119
|
`{ converged, rounds, finalSha, blocker, standardsNote, copilotNote, reuseNote, deferredPrs }`.
|
|
135
120
|
`deferredPrs` is the list of draft environment-hardening PRs the standards-deferral
|
|
136
|
-
path opened this run, each as `{ owner, repo, prNumber
|
|
121
|
+
path opened this run, each as `{ owner, repo, prNumber, copilotDisabled, bugbotDisabled }`.
|
|
122
|
+
The two flags carry this run's Copilot and Bugbot availability, so the next generation
|
|
123
|
+
skips a reviewer that is down or out of quota without re-probing. This list is the seed the
|
|
137
124
|
[self-closing loop](#self-closing-loop-converge-the-deferred-prs) converges next.
|
|
138
125
|
|
|
139
126
|
## Budget-aware round boundaries
|
|
@@ -150,10 +137,10 @@ round records nothing resumable and replays dirty.
|
|
|
150
137
|
## Teardown (on workflow completion)
|
|
151
138
|
|
|
152
139
|
1. **When `converged` is true — build and publish the closing report.**
|
|
153
|
-
Skip this entire step (report,
|
|
154
|
-
returned a non-null `blocker`. Per-round live-dashboard refresh is out of
|
|
155
|
-
here; this step builds the one-shot closing report and the seam (marker
|
|
156
|
-
|
|
140
|
+
Skip this entire step (report, artifact publish, comment, Chrome open) when the
|
|
141
|
+
workflow returned a non-null `blocker`. Per-round live-dashboard refresh is out of
|
|
142
|
+
scope here; this step builds the one-shot closing report and the seam (marker
|
|
143
|
+
comment + artifact URL) a future live-dashboard reuses.
|
|
157
144
|
|
|
158
145
|
a. **Resolve a seed journal path.** Glob
|
|
159
146
|
`~/.claude/projects/**/workflows/wf_<runId>.json` (where `runId` is the run id
|
|
@@ -192,21 +179,16 @@ round records nothing resumable and replays dirty.
|
|
|
192
179
|
Use the `combinedJournal`, `finalSha`, and `roundCount` from step b. Capture the
|
|
193
180
|
output path from stdout.
|
|
194
181
|
|
|
195
|
-
e. **Publish
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
--no-open \
|
|
201
|
-
--description "autoconverge report PR #<n>"
|
|
202
|
-
```
|
|
203
|
-
Capture the htmlpreview URL from stdout. The gist is secret by default; pass
|
|
204
|
-
no public flag.
|
|
182
|
+
e. **Publish via the Artifact tool.** Load the `artifact-design` skill first, then
|
|
183
|
+
call `Artifact` with `file_path` set to the report path from step d, `favicon`
|
|
184
|
+
set to the fixed autoconverge favicon `✅` (keep this favicon stable across
|
|
185
|
+
every autoconverge report — never swap it per run), and `description` set to
|
|
186
|
+
a one-sentence subtitle naming the PR. Capture the returned URL.
|
|
205
187
|
|
|
206
188
|
f. **Post one idempotent PR comment.** List the PR's issue comments; if one
|
|
207
189
|
carries the marker `<!-- autoconverge-report -->`, edit it in place, otherwise
|
|
208
190
|
create a new one. The body begins with `<!-- autoconverge-report -->`, then
|
|
209
|
-
the
|
|
191
|
+
the artifact URL, then a plain-language summary that mirrors the report:
|
|
210
192
|
lead with the one-sentence `verdictLine`; then the plain Problem and Fix
|
|
211
193
|
sentences (`prProblem`, `prFix`); then the issue-class list — one bullet per
|
|
212
194
|
class as `plainName (×count, status)`. Place the raw finding list as
|
|
@@ -217,21 +199,21 @@ round records nothing resumable and replays dirty.
|
|
|
217
199
|
GitHub MCP `add_issue_comment` tool (body as a structured parameter, no
|
|
218
200
|
`--body` flag).
|
|
219
201
|
|
|
220
|
-
g. **Open the report in Chrome.**
|
|
202
|
+
g. **Open the published report in Chrome.** Use the artifact URL captured in
|
|
203
|
+
step e.
|
|
221
204
|
```
|
|
222
|
-
Start-Process chrome -ArgumentList '--new-window', '<
|
|
205
|
+
Start-Process chrome -ArgumentList '--new-window', '<artifact URL>'
|
|
223
206
|
```
|
|
224
207
|
Tolerate a missing Chrome without aborting the rest of teardown.
|
|
225
208
|
|
|
226
|
-
2. **
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
`python "$HOME/.claude/skills/bugteam/scripts/revoke_project_claude_permissions.py"`
|
|
209
|
+
2. **Close the run.** Apply the `pr-loop-lifecycle` skill's Close section
|
|
210
|
+
(`../pr-loop-lifecycle/SKILL.md`): when `converged` is true, rewrite the PR
|
|
211
|
+
description and clean the working tree — see
|
|
212
|
+
[`pr-loop-lifecycle/reference/teardown-publish-permissions.md` § Clean working tree and § Publish the final PR description](../pr-loop-lifecycle/reference/teardown-publish-permissions.md);
|
|
213
|
+
the workflow already marked the PR ready. The permission revoke always runs,
|
|
214
|
+
including on a blocker exit.
|
|
233
215
|
|
|
234
|
-
|
|
216
|
+
3. **Print the final report:**
|
|
235
217
|
|
|
236
218
|
```
|
|
237
219
|
/autoconverge exit: <converged | blocked>
|
|
@@ -302,13 +284,14 @@ matches.
|
|
|
302
284
|
|
|
303
285
|
- **Converge:** `parallel([Bugbot lens, code-review lens, bug-audit lens])` on
|
|
304
286
|
the current HEAD, full `origin/main...HEAD` diff. Dedup findings; one
|
|
305
|
-
`clean-coder` applies
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
287
|
+
`clean-coder` applies the round's fixes per the `pr-fix-protocol` skill
|
|
288
|
+
(`../pr-fix-protocol/SKILL.md`) — fix, reply, resolve — landing every fix in
|
|
289
|
+
one commit per round, which the workflow journal records; re-verify next
|
|
290
|
+
round on the new HEAD. Every edit step ends with a pre-commit gate check:
|
|
291
|
+
before its turn ends, the fixer dry-runs the CODE_RULES commit gate
|
|
292
|
+
(`code_rules_gate.py --staged`) and keeps fixing until that gate would accept
|
|
293
|
+
the commit — it makes no commit itself. When all three are clean on a stable
|
|
294
|
+
HEAD, post the CLEAN bugteam audit artifact.
|
|
312
295
|
A round whose findings are ALL code-standard violations (pure CODE_RULES/style,
|
|
313
296
|
no behavioral impact) passes for convergence purposes: the workflow files a
|
|
314
297
|
follow-up issue listing the findings, opens a draft environment-hardening PR
|
|
@@ -435,8 +418,8 @@ so there is nothing to converge. Report that and stop.
|
|
|
435
418
|
|
|
436
419
|
### Each generation
|
|
437
420
|
|
|
438
|
-
Given a non-empty list of deferred PRs `{ owner, repo, prNumber }`
|
|
439
|
-
may span more than one repository — a hardening PR lands in whichever repo owns
|
|
421
|
+
Given a non-empty list of deferred PRs `{ owner, repo, prNumber, copilotDisabled, bugbotDisabled }`
|
|
422
|
+
(a generation may span more than one repository — a hardening PR lands in whichever repo owns
|
|
440
423
|
the surface that blocks the deferred class, so `JonEcho/llm-settings` for hooks
|
|
441
424
|
and `jl-cmd/claude-code-config` for rules and skills both appear):
|
|
442
425
|
|
|
@@ -449,7 +432,11 @@ and `jl-cmd/claude-code-config` for rules and skills both appear):
|
|
|
449
432
|
Grant project permissions once per repository the generation spans.
|
|
450
433
|
2. **Converge the generation.** Launch `workflow/converge_multi.mjs` with one
|
|
451
434
|
entry per checked-out deferred PR, exactly as the
|
|
452
|
-
[multi-PR launch](#launch-the-multi-pr-workflow) describes.
|
|
435
|
+
[multi-PR launch](#launch-the-multi-pr-workflow) describes. Each child run
|
|
436
|
+
re-checks Copilot and Bugbot availability every round through the workflow's own
|
|
437
|
+
pre-spawn probe, so a reviewer that is down or out of quota is never spawned in any
|
|
438
|
+
generation; the `copilotDisabled`/`bugbotDisabled` flags each deferred PR carries
|
|
439
|
+
seed that check for the first round.
|
|
453
440
|
3. **Tear down.** Run the [multi-PR teardown](#multi-pr-teardown-on-workflow-completion)
|
|
454
441
|
over the generation's `results`, and revoke project permissions once per
|
|
455
442
|
repository.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Closing Report
|
|
2
2
|
|
|
3
|
-
When an autoconverge run converges (the workflow returns `converged: true`), the main session generates a convergence insights HTML report and publishes it
|
|
3
|
+
When an autoconverge run converges (the workflow returns `converged: true`), the main session generates a convergence insights HTML report and publishes it with the `Artifact` tool, with an idempotent PR comment linking to it.
|
|
4
4
|
|
|
5
5
|
## Data source
|
|
6
6
|
|
|
@@ -73,14 +73,14 @@ Counting is deterministic: `generated_date` comes from the journal `timestamp`,
|
|
|
73
73
|
|
|
74
74
|
After rendering, the main session:
|
|
75
75
|
|
|
76
|
-
1. **
|
|
77
|
-
2. **Posts one idempotent PR comment** marked with `<!-- autoconverge-report -->`. If a comment with that marker already exists on the PR, it is edited in place; otherwise a new comment is created. The body leads with the
|
|
78
|
-
3. **Opens the report** with `Start-Process chrome -ArgumentList '--new-window', '<
|
|
76
|
+
1. **Publishes the HTML with the Artifact tool.** Loads the `artifact-design` skill first, then calls `Artifact` with `file_path` set to the rendered report, `favicon` set to the fixed autoconverge favicon `✅` (kept stable across every autoconverge report), and `description` naming the PR. Captures the returned URL.
|
|
77
|
+
2. **Posts one idempotent PR comment** marked with `<!-- autoconverge-report -->`. If a comment with that marker already exists on the PR, it is edited in place; otherwise a new comment is created. The body leads with the artifact URL, then the one-sentence `verdictLine`, then the plain Problem and Fix sentences (`prProblem`, `prFix`), then the issue-class list — one bullet per class as `plainName (×count, status)` — and closes with the full finding list (`file:line — P# — title`) inside a collapsed `<details>` block. Write the body to a BOM-free temp file and pass `--body-file` to `gh issue comment` (never `--body`), or use the GitHub MCP `add_issue_comment` tool.
|
|
78
|
+
3. **Opens the published report** with `Start-Process chrome -ArgumentList '--new-window', '<artifact URL>'`, using the artifact URL captured in step 1. A missing Chrome does not abort teardown.
|
|
79
79
|
|
|
80
80
|
## Live-dashboard seam
|
|
81
81
|
|
|
82
|
-
The marker comment and
|
|
82
|
+
The marker comment and artifact together form a seam for future per-round dashboard refreshes: a live-dashboard step re-renders with the same `render_report_html` function (pure, no side effects), calls `Artifact` again on the same `file_path` to redeploy to the same URL, and edits the same marker comment. That per-round refresh path is out of scope here; this document describes the one-shot closing report only.
|
|
83
83
|
|
|
84
84
|
## Scope
|
|
85
85
|
|
|
86
|
-
The closing report runs only when `converged === true`. On a blocker exit (`blocker: "budget"` or similar), the report,
|
|
86
|
+
The closing report runs only when `converged === true`. On a blocker exit (`blocker: "budget"` or similar), the report, artifact publish, comment, and Chrome open are all skipped.
|
|
@@ -55,24 +55,25 @@ tracks CONVERGE passes only and is never the cap.
|
|
|
55
55
|
- **Code-review lens** — a correctness-focused review pass (`code-quality-agent`)
|
|
56
56
|
that reports findings without editing.
|
|
57
57
|
- **Bug-audit lens** — the bug-audit (`code-quality-agent`) applying the
|
|
58
|
-
shared A–P rubric from `
|
|
58
|
+
shared A–P rubric from `_shared/pr-loop/audit-contract.md`, reporting
|
|
59
59
|
findings without editing.
|
|
60
60
|
3. Dedup findings across the three lenses by file, line, and title. A collision
|
|
61
61
|
keeps the most severe duplicate's severity (P0 > P1 > P2), unions the detail
|
|
62
62
|
text, and collects every distinct bot thread id so the fix lens resolves all
|
|
63
63
|
colliding threads.
|
|
64
|
-
4. **Any findings** → one `clean-coder` applies every fix
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
4. **Any findings** → one `clean-coder` applies every fix per the
|
|
65
|
+
`pr-fix-protocol` skill (`../../pr-fix-protocol/SKILL.md`): a single
|
|
66
|
+
test-first commit, a push, then a reply and resolve on each finding that
|
|
67
|
+
carries a GitHub review thread. Before its turn ends, the edit step dry-runs
|
|
68
|
+
the CODE_RULES commit gate (`code_rules_gate.py --staged`) over its staged
|
|
68
69
|
changes and keeps fixing until that gate would accept the commit, so the
|
|
69
|
-
later commit step never hits a gate rejection. A round progresses when the
|
|
70
|
-
moves HEAD, or when every finding was already
|
|
71
|
-
needed yet each finding thread is still
|
|
72
|
-
`resolvedWithoutCommit` and the run
|
|
73
|
-
round whose fix lens reports neither
|
|
74
|
-
thread-resolution ends the run with a
|
|
75
|
-
re-verifies on the current HEAD.
|
|
70
|
+
later commit step never hits a gate rejection. A round progresses when the
|
|
71
|
+
fix lens lands a push that moves HEAD, or when every finding was already
|
|
72
|
+
addressed so no code change is needed yet each finding thread is still
|
|
73
|
+
resolved (the fix lens reports `resolvedWithoutCommit` and the run
|
|
74
|
+
re-converges on the unchanged HEAD). A round whose fix lens reports neither
|
|
75
|
+
a moved-HEAD push nor a full thread-resolution ends the run with a
|
|
76
|
+
fix-stalled blocker. The next round re-verifies on the current HEAD.
|
|
76
77
|
5. **Zero findings on a stable HEAD** → post the CLEAN bugteam audit artifact
|
|
77
78
|
for that HEAD, then move to the Copilot gate.
|
|
78
79
|
|
|
@@ -112,9 +113,10 @@ the current HEAD:
|
|
|
112
113
|
4. The Copilot review on HEAD is clean or approved (bypassed when Copilot is down
|
|
113
114
|
or out of quota this run).
|
|
114
115
|
5. Zero unresolved bot review threads anywhere on the PR — counting Cursor,
|
|
115
|
-
Claude, and Copilot authored threads
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
Claude, and Copilot authored threads. The filter is purely
|
|
117
|
+
`isResolved == false`; `isOutdated` is informational, and the fix lens
|
|
118
|
+
verifies each outdated thread against current HEAD like any other (the
|
|
119
|
+
`pr-fix-protocol` skill's unresolved-thread sweep).
|
|
118
120
|
6. The PR is mergeable (`mergeable` true and `mergeable_state` clean).
|
|
119
121
|
7. No requested reviewers are still pending (bypassed when Copilot is down or out
|
|
120
122
|
of quota this run).
|
|
@@ -21,9 +21,12 @@ fails in a new way.
|
|
|
21
21
|
- **Fetch origin/main once before the parallel lenses.** The code-review and
|
|
22
22
|
bug-audit lenses both diff against `origin/main`. Concurrent `git fetch` calls
|
|
23
23
|
contend on the worktree `.git` lock and fail intermittently, so the workflow
|
|
24
|
-
runs a single serial `git fetch origin main`
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
runs a single serial `git fetch origin main` inside the merged `preflight-git`
|
|
25
|
+
step — the one git-utility agent that also resolves the PR HEAD SHA and probes
|
|
26
|
+
mergeability — and the parallel lenses run no git fetch of their own; they
|
|
27
|
+
diff against the already-current ref. The workflow threads the resolved HEAD
|
|
28
|
+
through the rounds and re-runs `preflight-git` only after a push or rebase
|
|
29
|
+
invalidates it, so a round on an unchanged HEAD spawns no git agent at all.
|
|
27
30
|
|
|
28
31
|
- **The CLEAN bugteam artifact is HEAD-specific.** `check_convergence.py` reads
|
|
29
32
|
the bugteam review on the current HEAD. Any push moves HEAD and invalidates a
|
package/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py
CHANGED
|
@@ -154,16 +154,9 @@ Write so a non-programmer understands every line. The reader has never seen the
|
|
|
154
154
|
|
|
155
155
|
Return strictly a JSON object with keys prProblem, prFix, problemScenes, fixScenes, verdictLine, and issueClasses."""
|
|
156
156
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
HTML_HEAD_TEMPLATE = """\
|
|
160
|
-
<head>
|
|
161
|
-
<meta charset="utf-8">
|
|
162
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
157
|
+
HTML_TITLE_AND_STYLE_TEMPLATE = """\
|
|
163
158
|
<title>PR #{pr_number} Convergence Summary</title>
|
|
164
|
-
|
|
165
|
-
{style_block}
|
|
166
|
-
</head>"""
|
|
159
|
+
{style_block}"""
|
|
167
160
|
|
|
168
161
|
HTML_STYLE_BLOCK = """\
|
|
169
162
|
<style>
|
|
@@ -44,15 +44,95 @@ test('bug-audit lens prompt no longer instructs a per-lens git fetch', () => {
|
|
|
44
44
|
assert.doesNotMatch(lensPromptBody('runAuditLens'), /git fetch origin main/);
|
|
45
45
|
});
|
|
46
46
|
|
|
47
|
-
test('
|
|
48
|
-
assert.ok(convergeSource.includes("runGitTask('
|
|
49
|
-
const
|
|
47
|
+
test('the merged preflight-git task fetches origin/main once before the parallel lenses', () => {
|
|
48
|
+
assert.ok(convergeSource.includes("runGitTask('preflight-git')"));
|
|
49
|
+
const preflightCallIndex = convergeSource.indexOf("runGitTask('preflight-git')");
|
|
50
50
|
const parallelLensIndex = convergeSource.indexOf('const lenses = await parallel(');
|
|
51
|
-
assert.notEqual(
|
|
51
|
+
assert.notEqual(preflightCallIndex, -1, 'expected the merged preflight to be invoked');
|
|
52
52
|
assert.notEqual(parallelLensIndex, -1, 'expected the parallel lens block to exist');
|
|
53
53
|
assert.ok(
|
|
54
|
-
|
|
55
|
-
'expected the
|
|
54
|
+
preflightCallIndex < parallelLensIndex,
|
|
55
|
+
'expected the merged preflight to run before the parallel lenses spawn',
|
|
56
|
+
);
|
|
57
|
+
const gitTaskBody = functionSource('runGitTask');
|
|
58
|
+
assert.match(gitTaskBody, /git fetch origin main/, 'expected the merged task to carry the base-ref fetch');
|
|
59
|
+
assert.match(gitTaskBody, /--jq \.head\.sha/, 'expected the merged task to resolve the PR HEAD SHA');
|
|
60
|
+
assert.match(gitTaskBody, /PREFLIGHT_GIT_SCHEMA/, 'expected the merged task to return the {sha, conflicting, fetched} schema');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('the merged preflight-git agent runs on haiku at low effort', () => {
|
|
64
|
+
const gitTaskBody = functionSource('runGitTask');
|
|
65
|
+
assert.match(gitTaskBody, /model: 'haiku'/, 'expected the git-utility agent to run on the cheapest model');
|
|
66
|
+
assert.match(gitTaskBody, /effort: 'low'/, 'expected the git-utility agent to run at low effort');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('the CONVERGE round spawns a single shared reviewer-availability probe before the parallel lenses', () => {
|
|
70
|
+
const convergeBranchStart = convergeSource.indexOf("if (phase === 'CONVERGE')");
|
|
71
|
+
const refreshCallIndex = convergeSource.indexOf("runGitTask('preflight-git')", convergeBranchStart);
|
|
72
|
+
const probeCallIndex = convergeSource.indexOf('reviewerAvailability = await runReviewerAvailabilityCheck()');
|
|
73
|
+
const parallelLensIndex = convergeSource.indexOf('const lenses = await parallel(');
|
|
74
|
+
assert.notEqual(probeCallIndex, -1, 'expected the CONVERGE round to spawn the shared reviewer-availability probe');
|
|
75
|
+
assert.ok(
|
|
76
|
+
refreshCallIndex < probeCallIndex && probeCallIndex < parallelLensIndex,
|
|
77
|
+
'expected the probe to run after the round head refresh and before the parallel lenses spawn',
|
|
78
|
+
);
|
|
79
|
+
const probeBody = functionSource('runReviewerAvailabilityCheck');
|
|
80
|
+
assert.match(probeBody, /reviewer_availability\.py/);
|
|
81
|
+
assert.match(probeBody, /--reviewer copilot/);
|
|
82
|
+
assert.match(probeBody, /--reviewer bugbot/);
|
|
83
|
+
assert.match(probeBody, /schema:\s*REVIEWER_AVAILABILITY_SCHEMA/);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('the Bugbot lens is not spawned pre-spawn when the shared gate reports Bugbot down', () => {
|
|
87
|
+
const parallelLensIndex = convergeSource.indexOf('const lenses = await parallel(');
|
|
88
|
+
assert.notEqual(parallelLensIndex, -1, 'expected the parallel lens block to exist');
|
|
89
|
+
const isBugbotDownPreSpawnIndex = convergeSource.indexOf('const isBugbotDownPreSpawn = resolveReviewerDown(');
|
|
90
|
+
assert.notEqual(isBugbotDownPreSpawnIndex, -1, 'expected a pre-spawn Bugbot-down decision computed before the lens array');
|
|
91
|
+
assert.ok(
|
|
92
|
+
isBugbotDownPreSpawnIndex < parallelLensIndex,
|
|
93
|
+
'expected the pre-spawn Bugbot-down decision to be computed before the parallel lens array',
|
|
94
|
+
);
|
|
95
|
+
const lensArrayEnd = convergeSource.indexOf('])', parallelLensIndex);
|
|
96
|
+
const lensArray = convergeSource.slice(parallelLensIndex, lensArrayEnd);
|
|
97
|
+
assert.match(
|
|
98
|
+
lensArray,
|
|
99
|
+
/isBugbotDownPreSpawn \? Promise\.resolve\(\{ sha: head, clean: true, down: true, findings: \[\] \}\) : runBugbotLens\(head\)/,
|
|
100
|
+
'expected the Bugbot lens slot to substitute a resolved down placeholder instead of spawning runBugbotLens when isBugbotDownPreSpawn is true',
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('the post-round bugbotDown verdict treats a dead Bugbot lens as down, not fail-open', () => {
|
|
105
|
+
const parallelLensIndex = convergeSource.indexOf('const lenses = await parallel(');
|
|
106
|
+
assert.notEqual(parallelLensIndex, -1, 'expected the parallel lens block to exist');
|
|
107
|
+
const lensArrayEnd = convergeSource.indexOf('])', parallelLensIndex);
|
|
108
|
+
const bugbotDownAssignIndex = convergeSource.indexOf('bugbotDown = ', lensArrayEnd);
|
|
109
|
+
assert.notEqual(bugbotDownAssignIndex, -1, 'expected the post-round bugbotDown assignment after the lens array');
|
|
110
|
+
const bugbotDownAssignLineEnd = convergeSource.indexOf('\n', bugbotDownAssignIndex);
|
|
111
|
+
const bugbotDownAssignLine = convergeSource.slice(bugbotDownAssignIndex, bugbotDownAssignLineEnd);
|
|
112
|
+
assert.match(
|
|
113
|
+
bugbotDownAssignLine,
|
|
114
|
+
/bugbotDown = lenses\[0\] == null \? true : resolveReviewerDown\(lenses\[0\], input\.bugbotDisabled \|\| false\)/,
|
|
115
|
+
'expected a dead (null) Bugbot lens result to report bugbotDown:true — resolveReviewerDown alone is fail-open on null, which would wrongly report bugbotDown:false for a lens agent that actually spawned and died, leaving check_convergence.py to look for a bugbot review that was never posted',
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('both reviewer trigger commands carry the CLAUDE_REVIEWER_GATE=autoconverge sentinel', () => {
|
|
120
|
+
assert.match(
|
|
121
|
+
convergeSource,
|
|
122
|
+
/const REVIEWER_GATE_SENTINEL = 'CLAUDE_REVIEWER_GATE=autoconverge '/,
|
|
123
|
+
'expected the shared sentinel constant to exist',
|
|
124
|
+
);
|
|
125
|
+
const copilotPrompt = lensPromptBody('runCopilotGate');
|
|
126
|
+
assert.match(
|
|
127
|
+
copilotPrompt,
|
|
128
|
+
/\$\{REVIEWER_GATE_SENTINEL\}gh api --method POST[^\n]*requested_reviewers/,
|
|
129
|
+
'expected the Copilot requested_reviewers trigger to carry the sentinel prefix',
|
|
130
|
+
);
|
|
131
|
+
const bugbotPrompt = lensPromptBody('runBugbotLens');
|
|
132
|
+
assert.match(
|
|
133
|
+
bugbotPrompt,
|
|
134
|
+
/\$\{REVIEWER_GATE_SENTINEL\}python "\$\{CONFIG\.sharedScripts\}\/post_fix_reply\.py"[^\n]*--body "bugbot run"/,
|
|
135
|
+
'expected the Bugbot rerun-comment trigger to carry the sentinel prefix',
|
|
56
136
|
);
|
|
57
137
|
});
|
|
58
138
|
|
|
@@ -178,11 +258,74 @@ test('the COPILOT fix branch does not re-assign head from the fix before re-conv
|
|
|
178
258
|
);
|
|
179
259
|
});
|
|
180
260
|
|
|
181
|
-
test('the CONVERGE branch
|
|
261
|
+
test('the CONVERGE branch refreshes HEAD via the merged preflight-git task only when the threaded head is invalidated', () => {
|
|
182
262
|
const convergeBranchStart = convergeSource.indexOf("if (phase === 'CONVERGE')");
|
|
183
263
|
assert.notEqual(convergeBranchStart, -1, 'expected the CONVERGE branch to exist');
|
|
184
|
-
const
|
|
185
|
-
|
|
264
|
+
const invalidGuardIndex = convergeSource.indexOf('if (!isResolvedHeadUsable(head))', convergeBranchStart);
|
|
265
|
+
const headRefreshCallIndex = convergeSource.indexOf("runGitTask('preflight-git')", convergeBranchStart);
|
|
266
|
+
assert.notEqual(invalidGuardIndex, -1, 'expected CONVERGE to gate the refresh on an invalidated head');
|
|
267
|
+
assert.notEqual(headRefreshCallIndex, -1, 'expected CONVERGE to refresh HEAD via the merged preflight-git task');
|
|
268
|
+
assert.ok(
|
|
269
|
+
invalidGuardIndex < headRefreshCallIndex,
|
|
270
|
+
'expected the invalidated-head guard to precede the merged refresh so a valid threaded head spawns no git agent',
|
|
271
|
+
);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test('each fix push, each lens-retry, and the convergence repair invalidate the threaded head so the next CONVERGE entry refreshes it', () => {
|
|
275
|
+
const invalidationMatches = convergeSource.match(/^ +head = null$/gm) || [];
|
|
276
|
+
assert.equal(
|
|
277
|
+
invalidationMatches.length,
|
|
278
|
+
5,
|
|
279
|
+
'expected head invalidation after the CONVERGE fix push, the COPILOT fix push, the convergence repair, the all-lenses-dead retry, and the not-clean-no-findings retry',
|
|
280
|
+
);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
function convergeRetryBranch(guardMarker) {
|
|
284
|
+
const guardIndex = convergeSource.indexOf(guardMarker);
|
|
285
|
+
assert.notEqual(guardIndex, -1, `expected the ${guardMarker} retry branch to exist`);
|
|
286
|
+
const continueIndex = convergeSource.indexOf('continue', guardIndex);
|
|
287
|
+
assert.notEqual(continueIndex, -1, `expected a continue statement to close the ${guardMarker} branch`);
|
|
288
|
+
return convergeSource.slice(guardIndex, continueIndex + 'continue'.length);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
test('the all-lenses-dead retry invalidates the threaded head so the next round re-fetches origin/main before spawning lenses', () => {
|
|
292
|
+
const branch = convergeRetryBranch('if (roundOutcome.allLensesDead) {');
|
|
293
|
+
const headNullIndex = branch.indexOf('head = null');
|
|
294
|
+
assert.notEqual(
|
|
295
|
+
headNullIndex,
|
|
296
|
+
-1,
|
|
297
|
+
'expected the all-lenses-dead retry to null head so the next round re-enters preflight-git — otherwise the lenses are told origin/main was fetched this round when no fetch ran',
|
|
298
|
+
);
|
|
299
|
+
assert.ok(
|
|
300
|
+
headNullIndex < branch.indexOf('continue'),
|
|
301
|
+
'expected head to be invalidated before the retry continues to the next round',
|
|
302
|
+
);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
test('the not-clean-no-findings retry invalidates the threaded head so the next round re-fetches origin/main before spawning lenses', () => {
|
|
306
|
+
const branch = convergeRetryBranch('if (!roundOutcome.roundClean) {');
|
|
307
|
+
const headNullIndex = branch.indexOf('head = null');
|
|
308
|
+
assert.notEqual(
|
|
309
|
+
headNullIndex,
|
|
310
|
+
-1,
|
|
311
|
+
'expected the not-clean-no-findings retry to null head so the next round re-enters preflight-git — otherwise the lenses are told origin/main was fetched this round when no fetch ran',
|
|
312
|
+
);
|
|
313
|
+
assert.ok(
|
|
314
|
+
headNullIndex < branch.indexOf('continue'),
|
|
315
|
+
'expected head to be invalidated before the retry continues to the next round',
|
|
316
|
+
);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
test('the preflight consumes the head returned by resolveMergeConflicts without an immediate re-resolve', () => {
|
|
320
|
+
const consumedHeadIndex = convergeSource.indexOf('head = await resolveMergeConflicts(');
|
|
321
|
+
assert.notEqual(consumedHeadIndex, -1, 'expected the post-rebase head from resolveMergeConflicts to be captured');
|
|
322
|
+
const whileLoopIndex = convergeSource.indexOf('while (iterations < CONFIG.maxIterations)');
|
|
323
|
+
const betweenPreflightAndLoop = convergeSource.slice(consumedHeadIndex, whileLoopIndex);
|
|
324
|
+
assert.doesNotMatch(
|
|
325
|
+
betweenPreflightAndLoop,
|
|
326
|
+
/runGitTask\(/,
|
|
327
|
+
'expected the reuse pass to reuse the threaded head with no git-utility agent spawn',
|
|
328
|
+
);
|
|
186
329
|
});
|
|
187
330
|
|
|
188
331
|
test('fix edit prompt resolves threads by PRRT thread node id looked up from the comment databaseId', () => {
|
|
@@ -484,7 +627,7 @@ test('the reuse pass applies its findings through applyFixes, not the standards-
|
|
|
484
627
|
const reuseBlock = convergeSource.slice(reuseCallIndex, loopIndex);
|
|
485
628
|
assert.match(
|
|
486
629
|
reuseBlock,
|
|
487
|
-
/applyFixes\(
|
|
630
|
+
/applyFixes\(head, reuseFindings, 'reuse-pass'\)/,
|
|
488
631
|
'expected the reuse pass to apply its findings via applyFixes',
|
|
489
632
|
);
|
|
490
633
|
assert.doesNotMatch(
|