claude-dev-env 1.83.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.
Files changed (165) hide show
  1. package/_shared/pr-loop/audit-contract.md +24 -12
  2. package/_shared/pr-loop/scripts/_claude_permissions_common.py +16 -5
  3. package/_shared/pr-loop/scripts/code_rules_gate.py +2 -3
  4. package/_shared/pr-loop/scripts/reviews_disabled.py +2 -0
  5. package/_shared/pr-loop/scripts/tests/test__claude_permissions_common.py +212 -0
  6. package/_shared/pr-loop/scripts/tests/test_agent_config_carveout.py +18 -0
  7. package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +18 -0
  8. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +37 -0
  9. package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +14 -0
  10. package/agents/clean-coder.md +3 -0
  11. package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +1 -1
  12. package/bin/install.mjs +16 -8
  13. package/bin/install.test.mjs +17 -6
  14. package/commands/CLAUDE.md +0 -1
  15. package/docs/CODE_RULES.md +1 -1
  16. package/hooks/blocking/CLAUDE.md +0 -4
  17. package/hooks/blocking/code_rules_constants_config.py +5 -0
  18. package/hooks/blocking/code_rules_docstrings.py +116 -6
  19. package/hooks/blocking/code_rules_enforcer.py +4 -0
  20. package/hooks/blocking/code_rules_magic_values.py +5 -0
  21. package/hooks/blocking/code_rules_naming_collection.py +11 -6
  22. package/hooks/blocking/code_rules_shared.py +34 -0
  23. package/hooks/blocking/duplicate_rmtree_helper_blocker.py +4 -4
  24. package/hooks/blocking/test_code_rules_enforcer_docstring_prose_wall_illustration.py +197 -0
  25. package/hooks/blocking/test_code_rules_enforcer_docstring_runon_sentence.py +27 -0
  26. package/hooks/blocking/test_code_rules_enforcer_split_constants_config.py +33 -0
  27. package/hooks/blocking/test_code_rules_enforcer_split_shared.py +23 -0
  28. package/hooks/blocking/test_code_rules_magic_values.py +54 -0
  29. package/hooks/blocking/test_duplicate_rmtree_helper_blocker.py +0 -6
  30. package/hooks/blocking/test_pr_converge_bugteam_enforcer_state_tolerance.py +184 -0
  31. package/hooks/blocking/test_shared_stdin_adoption.py +5 -30
  32. package/hooks/hooks_constants/CLAUDE.md +0 -3
  33. package/hooks/hooks_constants/blocking_check_limits.py +3 -0
  34. package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -1
  35. package/hooks/hooks_constants/duplicate_rmtree_helper_blocker_constants.py +0 -1
  36. package/hooks/hooks_constants/post_tool_use_dispatcher_constants.py +0 -5
  37. package/hooks/validation/post_tool_use_dispatcher.py +1 -1
  38. package/hooks/validation/test_post_tool_use_dispatcher.py +51 -35
  39. package/hooks/workflow/CLAUDE.md +2 -8
  40. package/package.json +1 -1
  41. package/rules/CLAUDE.md +1 -1
  42. package/rules/plain-illustrative-docstrings.md +35 -1
  43. package/skills/CLAUDE.md +6 -1
  44. package/skills/autoconverge/SKILL.md +38 -57
  45. package/skills/autoconverge/reference/closing-report.md +6 -6
  46. package/skills/autoconverge/reference/convergence.md +17 -15
  47. package/skills/autoconverge/reference/gotchas.md +6 -3
  48. package/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +2 -9
  49. package/skills/autoconverge/workflow/converge.contract.test.mjs +87 -13
  50. package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +1 -1
  51. package/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +16 -9
  52. package/skills/autoconverge/workflow/converge.mjs +70 -73
  53. package/skills/autoconverge/workflow/render_report.py +7 -11
  54. package/skills/bugteam/CLAUDE.md +1 -1
  55. package/skills/bugteam/PROMPTS.md +7 -6
  56. package/skills/bugteam/SKILL.md +25 -72
  57. package/skills/bugteam/reference/CLAUDE.md +1 -3
  58. package/skills/bugteam/reference/README.md +1 -1
  59. package/skills/bugteam/reference/audit-and-teammates.md +1 -1
  60. package/skills/bugteam/reference/obstacles/fix-publish-summary.md +1 -1
  61. package/skills/bugteam/reference/team-setup.md +8 -7
  62. package/skills/bugteam/scripts/CLAUDE.md +0 -6
  63. package/skills/bugteam/scripts/README.md +0 -4
  64. package/skills/bugteam/scripts/bugteam_scripts_constants/CLAUDE.md +0 -1
  65. package/skills/code/SKILL.md +2 -0
  66. package/skills/copilot-review/CLAUDE.md +1 -1
  67. package/skills/copilot-review/SKILL.md +25 -23
  68. package/skills/findbugs/CLAUDE.md +2 -2
  69. package/skills/findbugs/SKILL.md +22 -83
  70. package/skills/fixbugs/SKILL.md +2 -4
  71. package/skills/log-audit/CLAUDE.md +20 -0
  72. package/skills/log-audit/SKILL.md +68 -0
  73. package/skills/log-audit/reference/CLAUDE.md +9 -0
  74. package/skills/log-audit/reference/charter.md +52 -0
  75. package/skills/log-audit/scripts/CLAUDE.md +27 -0
  76. package/skills/log-audit/scripts/cluster_recurrences.py +261 -0
  77. package/skills/log-audit/scripts/collect_log_window.py +199 -0
  78. package/skills/log-audit/scripts/log_audit_constants/CLAUDE.md +12 -0
  79. package/skills/log-audit/scripts/log_audit_constants/cluster_recurrences_constants.py +23 -0
  80. package/skills/log-audit/scripts/log_audit_constants/collect_log_window_constants.py +24 -0
  81. package/skills/log-audit/scripts/log_audit_constants/mine_copilot_findings_constants.py +49 -0
  82. package/skills/log-audit/scripts/mine_copilot_findings.py +302 -0
  83. package/skills/log-audit/scripts/test_cluster_recurrences.py +160 -0
  84. package/skills/log-audit/scripts/test_collect_log_window.py +111 -0
  85. package/skills/log-audit/scripts/test_mine_copilot_findings.py +126 -0
  86. package/skills/monitor-open-prs/SKILL.md +2 -2
  87. package/skills/post-audit-findings/SKILL.md +84 -0
  88. package/skills/pr-converge/CLAUDE.md +2 -0
  89. package/skills/pr-converge/SKILL.md +72 -59
  90. package/skills/pr-converge/reference/CLAUDE.md +1 -1
  91. package/skills/pr-converge/reference/convergence-gates.md +16 -19
  92. package/skills/pr-converge/reference/examples.md +5 -5
  93. package/skills/pr-converge/reference/fix-protocol.md +16 -43
  94. package/skills/pr-converge/reference/obstacles/fix-publish-summary.md +1 -1
  95. package/skills/pr-converge/reference/per-tick.md +24 -45
  96. package/skills/pr-converge/reference/state-schema.md +15 -0
  97. package/skills/pr-converge/scripts/README.md +3 -5
  98. package/skills/pr-fix-protocol/SKILL.md +70 -0
  99. package/skills/pr-loop-lifecycle/SKILL.md +73 -0
  100. package/skills/{bugteam → pr-loop-lifecycle}/reference/teardown-publish-permissions.md +22 -24
  101. package/skills/pr-scope-resolve/SKILL.md +48 -0
  102. package/skills/qbug/CLAUDE.md +4 -4
  103. package/skills/qbug/SKILL.md +46 -144
  104. package/skills/qbug/test_qbug_skill_audit_schema.py +2 -2
  105. package/skills/qbug/test_qbug_skill_post_fix_audit.py +1 -1
  106. package/skills/reviewer-gates/SKILL.md +96 -0
  107. package/skills/session-log/CLAUDE.md +7 -7
  108. package/skills/session-log/SKILL.md +27 -44
  109. package/skills/test_markdown_link_integrity.py +103 -0
  110. package/commands/doc-gist.md +0 -16
  111. package/hooks/blocking/_md_to_html_blocker_test_support.py +0 -65
  112. package/hooks/blocking/conftest.py +0 -30
  113. package/hooks/blocking/md_path_exemptions.py +0 -224
  114. package/hooks/blocking/md_to_html_blocker.py +0 -155
  115. package/hooks/blocking/test_md_to_html_blocker_exemptions.py +0 -434
  116. package/hooks/blocking/test_md_to_html_blocker_extensions.py +0 -157
  117. package/hooks/blocking/test_md_to_html_blocker_path_resolution.py +0 -336
  118. package/hooks/hooks_constants/doc_gist_auto_publish_constants.py +0 -18
  119. package/hooks/hooks_constants/html_companion_constants.py +0 -20
  120. package/hooks/hooks_constants/md_to_html_blocker_constants.py +0 -76
  121. package/hooks/hooks_constants/test_md_to_html_blocker_constants.py +0 -125
  122. package/hooks/workflow/doc_gist_auto_publish.py +0 -144
  123. package/hooks/workflow/md_to_html_companion.py +0 -358
  124. package/hooks/workflow/test_doc_gist_auto_publish.py +0 -117
  125. package/hooks/workflow/test_md_to_html_companion.py +0 -613
  126. package/skills/bugteam/reference/audit-contract.md +0 -163
  127. package/skills/bugteam/scripts/_bugteam_permissions_common.py +0 -455
  128. package/skills/bugteam/scripts/bugteam_scripts_constants/claude_permissions_common_constants.py +0 -69
  129. package/skills/bugteam/scripts/grant_project_claude_permissions.py +0 -280
  130. package/skills/bugteam/scripts/revoke_project_claude_permissions.py +0 -266
  131. package/skills/bugteam/scripts/test__bugteam_permissions_common.py +0 -160
  132. package/skills/bugteam/scripts/test_agent_config_carveout.py +0 -356
  133. package/skills/bugteam/scripts/test_bugteam_permissions_common.py +0 -140
  134. package/skills/doc-gist/CLAUDE.md +0 -25
  135. package/skills/doc-gist/SKILL.md +0 -97
  136. package/skills/doc-gist/references/CLAUDE.md +0 -9
  137. package/skills/doc-gist/references/examples/01-exploration-code-approaches.html +0 -453
  138. package/skills/doc-gist/references/examples/02-exploration-visual-designs.html +0 -515
  139. package/skills/doc-gist/references/examples/03-code-review-pr.html +0 -638
  140. package/skills/doc-gist/references/examples/04-code-understanding.html +0 -491
  141. package/skills/doc-gist/references/examples/05-design-system.html +0 -629
  142. package/skills/doc-gist/references/examples/06-component-variants.html +0 -605
  143. package/skills/doc-gist/references/examples/07-prototype-animation.html +0 -455
  144. package/skills/doc-gist/references/examples/08-prototype-interaction.html +0 -396
  145. package/skills/doc-gist/references/examples/09-slide-deck.html +0 -592
  146. package/skills/doc-gist/references/examples/10-svg-illustrations.html +0 -492
  147. package/skills/doc-gist/references/examples/11-status-report.html +0 -528
  148. package/skills/doc-gist/references/examples/12-incident-report.html +0 -596
  149. package/skills/doc-gist/references/examples/13-flowchart-diagram.html +0 -395
  150. package/skills/doc-gist/references/examples/14-research-feature-explainer.html +0 -381
  151. package/skills/doc-gist/references/examples/15-research-concept-explainer.html +0 -368
  152. package/skills/doc-gist/references/examples/16-implementation-plan.html +0 -702
  153. package/skills/doc-gist/references/examples/17-pr-writeup.html +0 -595
  154. package/skills/doc-gist/references/examples/18-editor-triage-board.html +0 -573
  155. package/skills/doc-gist/references/examples/19-editor-feature-flags.html +0 -663
  156. package/skills/doc-gist/references/examples/20-editor-prompt-tuner.html +0 -722
  157. package/skills/doc-gist/references/examples/21-decision-signoff.html +0 -546
  158. package/skills/doc-gist/references/examples/CLAUDE.md +0 -25
  159. package/skills/doc-gist/references/examples/README.md +0 -5
  160. package/skills/doc-gist/scripts/CLAUDE.md +0 -27
  161. package/skills/doc-gist/scripts/doc_gist_scripts_constants/CLAUDE.md +0 -10
  162. package/skills/doc-gist/scripts/doc_gist_scripts_constants/gist_upload_constants.py +0 -16
  163. package/skills/doc-gist/scripts/gist_upload.py +0 -177
  164. package/skills/doc-gist/scripts/test_gist_upload.py +0 -51
  165. /package/skills/{doc-gist/scripts/doc_gist_scripts_constants → log-audit/scripts/log_audit_constants}/__init__.py +0 -0
@@ -1,22 +1,16 @@
1
1
  # hooks/workflow
2
2
 
3
- PostToolUse hooks that trigger side-effects after Claude writes a file. They do not block writes; they produce companion artifacts or publish content automatically.
3
+ PostToolUse hooks that run after Claude writes a file. They do not block the write; they run follow-up side effects reformatting the written file and resetting the investigation tracker.
4
4
 
5
5
  ## Key files
6
6
 
7
7
  | File | Event | What it does |
8
8
  |---|---|---|
9
9
  | `auto_formatter.py` | PostToolUse (Write/Edit) | Runs the project's auto-formatter (ruff, prettier, etc.) on the written file and sends a desktop notification when formatting changes are applied |
10
- | `doc_gist_auto_publish.py` | PostToolUse (Write on `.html` files) | When an `.html` file has the `<!-- @publish-as-gist -->` marker, uploads it as a secret GitHub Gist and prints the htmlpreview URL into the harness output |
11
- | `md_to_html_companion.py` | PostToolUse (Write/Edit on `.md` files) | Generates a styled `.html` companion file from the `.md` source with dark-mode styling |
12
10
  | `investigation_tracker_reset.py` | PostToolUse | Resets the investigation tracker state after a tool call |
13
11
  | `test_auto_formatter.py` | — | Tests for `auto_formatter.py` |
14
- | `test_doc_gist_auto_publish.py` | — | Tests for `doc_gist_auto_publish.py` |
15
- | `test_md_to_html_companion.py` | — | Tests for `md_to_html_companion.py` |
16
12
 
17
13
  ## Conventions
18
14
 
19
- - All three main hooks exit 0 even on failure — they log warnings to stderr but do not break Claude's flow.
20
- - `doc_gist_auto_publish.py` checks for the `<!-- @publish-as-gist -->` marker before doing any work; HTML files without it are ignored.
21
- - Constants (marker text, blocked URL schemes, gist upload script path) live in `hooks_constants/doc_gist_auto_publish_constants.py` and `hooks_constants/html_companion_constants.py`.
15
+ - `auto_formatter.py` exits 0 even on failure — it logs warnings to stderr but does not break Claude's flow.
22
16
  - Tests run with `python -m pytest workflow/test_<name>.py`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-dev-env",
3
- "version": "1.83.0",
3
+ "version": "1.84.0",
4
4
  "description": "Claude Code development standards — rules, hooks, agents, commands, and skills",
5
5
  "type": "module",
6
6
  "bin": {
package/rules/CLAUDE.md CHANGED
@@ -33,7 +33,7 @@ Rule files installed into `~/.claude/rules/` by `bin/install.mjs`. Claude Code l
33
33
  | `package-inventory-stale-entry.md` | A new production code file added to a directory carries an entry in that directory's `README.md`/`CLAUDE.md` file inventory |
34
34
  | `paired-test-coverage.md` | A public function omitted by a module's established paired test suite must get a behavioral test |
35
35
  | `parallel-tools.md` | Make all independent tool calls in a single response |
36
- | `plain-illustrative-docstrings.md` | Public docstring narrative reads plainly and paints a concrete scene a general developer follows on first read; a run-on backstop hook plus Category O9 audit enforce it |
36
+ | `plain-illustrative-docstrings.md` | Public docstring narrative reads plainly and shows behavior with a diagram block (a `::` example or a doctest), painting a concrete scene a general developer follows on first read; a run-on backstop hook, a prose-wall backstop hook, and Category O9 audit enforce it |
37
37
  | `plain-language.md` | Everyday words, short active sentences, lead with the answer |
38
38
  | `prompt-workflow-context-controls.md` | Keep prompt-workflow instruction layers small and stable; load heavy skills on demand |
39
39
  | `research-mode.md` | Three anti-hallucination constraints: say "I don't know", verify with citations, quote for factual grounding |
@@ -19,6 +19,39 @@ Two shapes break the standard. Hold the prose clear of both:
19
19
  - **No machinery nouns stacked into a wall.** A sentence that chains abstract machinery terms (`the SIGINT install/restore/installability check, the atexit terminal-record registration, and the interrupted-run finalizer`) names parts without painting a scene. Name what the reader sees and why it matters.
20
20
  - **No defining by negation.** Prose that explains a thing by what it is not (`the non-promoter-specific machinery`) leaves the reader without a picture. Say what the thing is.
21
21
 
22
+ ## Shape: a summary line, then a diagram
23
+
24
+ The clearest way to be illustrative is to show a worked example, not describe one. A docstring that lands well reads in four parts:
25
+
26
+ 1. **One summary line** that says what the code does.
27
+ 2. **A diagram block** that carries the explanation by sight — a reStructuredText literal block (a line ending in `::`, then an indented example) or a doctest (`>>>`). Give the concrete input, mark the outcome, and add `ok:` / `flag:` contrast lines where a pass-and-fail pair makes the point. Keep the diagram clear of a bare number or an ALL-CAPS `NAME = value` line, which read as a magic value or a stray constant to a line-based lint pass.
28
+ 3. **A couple of short narrative lines** after the block — two or three at most.
29
+ 4. **The Google `Args:` / `Returns:` sections.**
30
+
31
+ Keep the wording neutral in the diagram and the prose: two names "contradict" or "clash", two names "agree". Skip words that pass judgment.
32
+
33
+ Canonical example:
34
+
35
+ ```
36
+ Flag a boolean assignment whose target and callee assert opposite polarity.
37
+
38
+ ::
39
+
40
+ is_inside_allowed = _point_hits_any_forbidden(...)
41
+ ^^^^^^^ ^^^^^^^^^
42
+ allowed vs. forbidden ⚠ the two names clash
43
+ ok: is_inside_allowed = _point_inside_allowed_region(...)
44
+ flag: is_inside_allowed = _point_hits_any_forbidden(...)
45
+
46
+ The target token and the callee token contradict each other, so the reader
47
+ cannot tell which name states the truth. Rename the callee to a neutral form
48
+ the two names agree on at every call site.
49
+ ```
50
+
51
+ The live version of this docstring sits on `check_polarity_name_contradiction` in `packages/claude-dev-env/hooks/blocking/code_rules_naming_collection.py`.
52
+
53
+ A short narrative with no diagram is fine when a couple of plain sentences carry the whole picture. The diagram earns its place once the explanation grows past what two or three lines hold — the moment a wall of prose starts to form.
54
+
22
55
  ## What to check before you write the docstring
23
56
 
24
57
  Read the narrative back as a stranger would:
@@ -53,7 +86,8 @@ one — and you're debugging blind.
53
86
 
54
87
  Two surfaces carry this standard:
55
88
 
56
- - **Hook (the run-on backstop).** `check_docstring_runon_sentence` in `packages/claude-dev-env/hooks/blocking/code_rules_docstrings.py` flags the one mechanical mark of a wall: a single narrative sentence that is both over the word limit and joined by an em-dash or a semicolon. A hook cannot judge whether prose paints a picture, so it catches only this structural mark.
89
+ - **Hook (the run-on backstop).** `check_docstring_runon_sentence` in `packages/claude-dev-env/hooks/blocking/code_rules_docstrings.py` flags the one mechanical mark of a wall: a single narrative sentence that is both over the word limit and joined by an em-dash or a semicolon. A hook cannot judge whether prose paints a picture, so it catches only this structural mark. It reads the narrative through a shared partition that sets aside any `::` literal block and any doctest, so a diagram's own arrows and dashes never count against the sentence.
90
+ - **Hook (the prose-wall backstop).** `check_docstring_prose_wall_without_illustration` in the same module flags a narrative that runs more than six prose lines with no diagram block. It marks the wall so the writer shows the behavior with a `::` example or a doctest and trims the prose to a few short lines. It cannot judge whether the diagram illustrates well; that stays with the audit lane.
57
91
  - **Audit (the judgment lane).** Category O sub-bucket O9 in `packages/claude-dev-env/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md` carries the illustrative-and-brief judgment the hook cannot. The audit teammate reads each changed docstring's narrative and asks whether a general developer follows it on the first read.
58
92
 
59
93
  ## Why
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**
@@ -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
- `python "$HOME/.claude/skills/bugteam/scripts/grant_project_claude_permissions.py"`
88
-
89
- In auto-mode the classifier blocks this grant as an unrequested change to the
90
- permission allowlist: the `/autoconverge` invocation alone does not meet its
91
- bar for an explicitly requested permission change. When it is blocked, keep
92
- the run alive surface the grant to the user through `AskUserQuestion` with
93
- the exact command and ask them to approve it or run it themselves with the `!`
94
- prefix:
95
- `! python "$HOME/.claude/skills/bugteam/scripts/grant_project_claude_permissions.py"`.
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
 
@@ -152,10 +137,10 @@ round records nothing resumable and replays dirty.
152
137
  ## Teardown (on workflow completion)
153
138
 
154
139
  1. **When `converged` is true — build and publish the closing report.**
155
- Skip this entire step (report, gist, comment, Chrome open) when the workflow
156
- returned a non-null `blocker`. Per-round live-dashboard refresh is out of scope
157
- here; this step builds the one-shot closing report and the seam (marker comment +
158
- gist URL) a future live-dashboard reuses.
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.
159
144
 
160
145
  a. **Resolve a seed journal path.** Glob
161
146
  `~/.claude/projects/**/workflows/wf_<runId>.json` (where `runId` is the run id
@@ -194,21 +179,16 @@ round records nothing resumable and replays dirty.
194
179
  Use the `combinedJournal`, `finalSha`, and `roundCount` from step b. Capture the
195
180
  output path from stdout.
196
181
 
197
- e. **Publish as a secret gist** by reusing `doc-gist` (do not reimplement gist
198
- creation):
199
- ```
200
- python "$HOME/.claude/skills/doc-gist/scripts/gist_upload.py" \
201
- --input "<html path>" \
202
- --no-open \
203
- --description "autoconverge report PR #<n>"
204
- ```
205
- Capture the htmlpreview URL from stdout. The gist is secret by default; pass
206
- 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.
207
187
 
208
188
  f. **Post one idempotent PR comment.** List the PR's issue comments; if one
209
189
  carries the marker `<!-- autoconverge-report -->`, edit it in place, otherwise
210
190
  create a new one. The body begins with `<!-- autoconverge-report -->`, then
211
- the htmlpreview link, then a plain-language summary that mirrors the report:
191
+ the artifact URL, then a plain-language summary that mirrors the report:
212
192
  lead with the one-sentence `verdictLine`; then the plain Problem and Fix
213
193
  sentences (`prProblem`, `prFix`); then the issue-class list — one bullet per
214
194
  class as `plainName (×count, status)`. Place the raw finding list as
@@ -219,21 +199,21 @@ round records nothing resumable and replays dirty.
219
199
  GitHub MCP `add_issue_comment` tool (body as a structured parameter, no
220
200
  `--body` flag).
221
201
 
222
- g. **Open the report in Chrome.**
202
+ g. **Open the published report in Chrome.** Use the artifact URL captured in
203
+ step e.
223
204
  ```
224
- Start-Process chrome -ArgumentList '--new-window', '<report path>'
205
+ Start-Process chrome -ArgumentList '--new-window', '<artifact URL>'
225
206
  ```
226
207
  Tolerate a missing Chrome without aborting the rest of teardown.
227
208
 
228
- 2. **When `converged` is true:** rewrite the PR description and clean the
229
- working tree see
230
- [`bugteam/reference/teardown-publish-permissions.md` § Step 4 and § Step 4.5](../bugteam/reference/teardown-publish-permissions.md).
231
- The workflow already marked the PR ready.
232
-
233
- 3. **Always revoke project permissions** (including on a blocker exit):
234
- `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.
235
215
 
236
- 4. **Print the final report:**
216
+ 3. **Print the final report:**
237
217
 
238
218
  ```
239
219
  /autoconverge exit: <converged | blocked>
@@ -304,13 +284,14 @@ matches.
304
284
 
305
285
  - **Converge:** `parallel([Bugbot lens, code-review lens, bug-audit lens])` on
306
286
  the current HEAD, full `origin/main...HEAD` diff. Dedup findings; one
307
- `clean-coder` applies all fixes in a single commit, pushes, replies to and
308
- resolves any bot threads; re-verify next round on the new HEAD. Every edit
309
- step ends with a pre-commit gate check: before its turn ends, the fixer
310
- dry-runs the CODE_RULES commit gate (`code_rules_gate.py --staged`) and keeps
311
- fixing until that gate would accept the commit it makes no commit itself.
312
- When all three are clean on a stable HEAD, post the CLEAN bugteam audit
313
- artifact.
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.
314
295
  A round whose findings are ALL code-standard violations (pure CODE_RULES/style,
315
296
  no behavioral impact) passes for convergence purposes: the workflow files a
316
297
  follow-up issue listing the findings, opens a draft environment-hardening PR
@@ -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 as a secret gist with an idempotent PR comment linking to 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. **Uploads the HTML as a secret gist** using `doc-gist/scripts/gist_upload.py --no-open`. Captures the htmlpreview URL from stdout.
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 gist 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 report** with `Start-Process chrome -ArgumentList '--new-window', '<report path>'`. A missing Chrome does not abort teardown.
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 gist 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), runs `gh gist edit` on the same gist, 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.
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, gist, comment, and Chrome open are all skipped.
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 `bugteam/reference/audit-contract.md`, reporting
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 in a single test-first
65
- commit, pushes, then replies to and resolves each finding that carries a
66
- GitHub review thread. Before its turn ends, the edit step dry-runs the
67
- CODE_RULES commit gate (`code_rules_gate.py --staged`) over its staged
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 fix lens lands a push that
70
- moves HEAD, or when every finding was already addressed so no code change is
71
- needed yet each finding thread is still resolved (the fix lens reports
72
- `resolvedWithoutCommit` and the run re-converges on the unchanged HEAD). A
73
- round whose fix lens reports neither a moved-HEAD push nor a full
74
- thread-resolution ends the run with a fix-stalled blocker. The next round
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 where `isResolved` is false (`isOutdated`
116
- threads are excluded by the gate, but the fix lens still verifies and resolves
117
- them during the round).
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` (the `prefetch-main` step) at the
25
- start of each round and the parallel lenses run no git fetch of their own —
26
- they diff against the already-current ref.
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
@@ -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
- HTML_DOCTYPE = "<!DOCTYPE html>"
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
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
165
- {style_block}
166
- </head>"""
159
+ {style_block}"""
167
160
 
168
161
  HTML_STYLE_BLOCK = """\
169
162
  <style>
@@ -44,26 +44,37 @@ 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('a single round-level prefetch step fetches origin/main before the parallel lenses', () => {
48
- assert.ok(convergeSource.includes("runGitTask('prefetch-main')"));
49
- const prefetchCallIndex = convergeSource.indexOf("runGitTask('prefetch-main')");
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(prefetchCallIndex, -1, 'expected prefetch to be invoked');
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
- prefetchCallIndex < parallelLensIndex,
55
- 'expected the round prefetch to run before the parallel lenses spawn',
54
+ preflightCallIndex < parallelLensIndex,
55
+ 'expected the merged preflight to run before the parallel lenses spawn',
56
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');
57
67
  });
58
68
 
59
69
  test('the CONVERGE round spawns a single shared reviewer-availability probe before the parallel lenses', () => {
60
- const prefetchCallIndex = convergeSource.indexOf("runGitTask('prefetch-main')");
70
+ const convergeBranchStart = convergeSource.indexOf("if (phase === 'CONVERGE')");
71
+ const refreshCallIndex = convergeSource.indexOf("runGitTask('preflight-git')", convergeBranchStart);
61
72
  const probeCallIndex = convergeSource.indexOf('reviewerAvailability = await runReviewerAvailabilityCheck()');
62
73
  const parallelLensIndex = convergeSource.indexOf('const lenses = await parallel(');
63
74
  assert.notEqual(probeCallIndex, -1, 'expected the CONVERGE round to spawn the shared reviewer-availability probe');
64
75
  assert.ok(
65
- prefetchCallIndex < probeCallIndex && probeCallIndex < parallelLensIndex,
66
- 'expected the probe to run after the round prefetch and before the parallel lenses spawn',
76
+ refreshCallIndex < probeCallIndex && probeCallIndex < parallelLensIndex,
77
+ 'expected the probe to run after the round head refresh and before the parallel lenses spawn',
67
78
  );
68
79
  const probeBody = functionSource('runReviewerAvailabilityCheck');
69
80
  assert.match(probeBody, /reviewer_availability\.py/);
@@ -247,11 +258,74 @@ test('the COPILOT fix branch does not re-assign head from the fix before re-conv
247
258
  );
248
259
  });
249
260
 
250
- test('the CONVERGE branch re-resolves HEAD from GitHub on every entry', () => {
261
+ test('the CONVERGE branch refreshes HEAD via the merged preflight-git task only when the threaded head is invalidated', () => {
251
262
  const convergeBranchStart = convergeSource.indexOf("if (phase === 'CONVERGE')");
252
263
  assert.notEqual(convergeBranchStart, -1, 'expected the CONVERGE branch to exist');
253
- const headResolveCallIndex = convergeSource.indexOf("runGitTask('resolve-head')", convergeBranchStart);
254
- assert.notEqual(headResolveCallIndex, -1, 'expected CONVERGE to re-resolve HEAD via runGitTask');
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
+ );
255
329
  });
256
330
 
257
331
  test('fix edit prompt resolves threads by PRRT thread node id looked up from the comment databaseId', () => {
@@ -553,7 +627,7 @@ test('the reuse pass applies its findings through applyFixes, not the standards-
553
627
  const reuseBlock = convergeSource.slice(reuseCallIndex, loopIndex);
554
628
  assert.match(
555
629
  reuseBlock,
556
- /applyFixes\(reuseHead, reuseFindings, 'reuse-pass'\)/,
630
+ /applyFixes\(head, reuseFindings, 'reuse-pass'\)/,
557
631
  'expected the reuse pass to apply its findings via applyFixes',
558
632
  );
559
633
  assert.doesNotMatch(
@@ -73,7 +73,7 @@ test('Copilot findings route to a fix when Copilot is reachable and not down', (
73
73
 
74
74
  test('COPILOT_SCHEMA carries a required down field', () => {
75
75
  const schemaStart = convergeSource.indexOf('const COPILOT_SCHEMA =');
76
- const schemaEnd = convergeSource.indexOf('const HEAD_SCHEMA =');
76
+ const schemaEnd = convergeSource.indexOf('const REVIEWER_AVAILABILITY_SCHEMA =');
77
77
  assert.notEqual(schemaStart, -1, 'expected COPILOT_SCHEMA to exist');
78
78
  const schemaSource = convergeSource.slice(schemaStart, schemaEnd);
79
79
  assert.match(schemaSource, /down:\s*\{\s*type:\s*'boolean'/);
@@ -39,7 +39,7 @@ test('the git agent handles merge-conflict checks with a Monitor-based poll', ()
39
39
  /do not edit, commit, push, or rebase|read only/i,
40
40
  'expected the git agent merge check to be read-only',
41
41
  );
42
- assert.match(gitBody, /MERGE_CONFLICT_SCHEMA/, 'expected the git agent to return MERGE_CONFLICT_SCHEMA');
42
+ assert.match(gitBody, /PREFLIGHT_GIT_SCHEMA/, 'expected the git agent to return PREFLIGHT_GIT_SCHEMA');
43
43
  assert.match(gitBody, /Monitor tool/, 'expected a Monitor-based poll delay, not a foreground sleep');
44
44
  });
45
45
 
@@ -53,31 +53,38 @@ test('runCodeEditorTask conflict-edit path rebases onto origin/main and makes no
53
53
  );
54
54
  });
55
55
 
56
- test('resolveMergeConflicts runs check -> edit -> verify -> commit and gates the push on the verdict', () => {
56
+ test('resolveMergeConflicts runs edit -> verify -> commit and gates the push on the verdict', () => {
57
57
  const body = functionBody('resolveMergeConflicts');
58
- const checkIndex = body.indexOf("runGitTask('check-merge-conflicts'");
59
58
  const editIndex = body.indexOf("runCodeEditorTask('conflict-edit'");
60
59
  const verifyIndex = body.indexOf('runVerifierTask(');
61
60
  const commitIndex = body.indexOf('commitWithRecovery(');
62
- assert.notEqual(checkIndex, -1, 'expected the conflict check to run');
63
61
  assert.notEqual(editIndex, -1, 'expected the edit step to run');
64
62
  assert.notEqual(verifyIndex, -1, 'expected the verify step to run');
65
63
  assert.notEqual(commitIndex, -1, 'expected the commit step to run');
66
64
  assert.ok(
67
- checkIndex < editIndex && editIndex < verifyIndex && verifyIndex < commitIndex,
68
- 'expected the order check -> edit -> verify -> commit',
65
+ editIndex < verifyIndex && verifyIndex < commitIndex,
66
+ 'expected the order edit -> verify -> commit',
69
67
  );
70
68
  assert.match(body, /verdictPassed\(/, 'expected the verifier verdict to gate the force-push');
69
+ assert.doesNotMatch(
70
+ body,
71
+ /runGitTask\(/,
72
+ 'expected the conflict decision to come from the merged preflight-git probe, with no mergeability agent spawned here',
73
+ );
71
74
  });
72
75
 
73
- test('resolveMergeConflicts rebases only when the check reports a conflict', () => {
76
+ test('resolveMergeConflicts rebases only when the caller-supplied preflight decision reports a conflict', () => {
74
77
  const body = functionBody('resolveMergeConflicts');
75
- assert.match(body, /isMergeConflicting\(/, 'expected the orchestrator to branch on the conflict decision');
76
78
  assert.match(
77
79
  body,
78
- /if \(!isMergeConflicting\([^)]*\)\) return head/,
80
+ /if \(!isConflicting\) return head/,
79
81
  'expected a clean PR to return the unchanged HEAD without rebasing',
80
82
  );
83
+ assert.match(
84
+ convergeSource,
85
+ /resolveMergeConflicts\(preflight\.sha, isMergeConflicting\(preflight\)\)/,
86
+ 'expected the call site to derive the conflict decision from the merged preflight-git result',
87
+ );
81
88
  });
82
89
 
83
90
  test('the merge-conflict pre-flight runs once before the round loop, ahead of the parallel bug-check lenses', () => {