claude-dev-env 1.35.0 → 1.36.1

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 (115) hide show
  1. package/agents/clean-coder.md +109 -1
  2. package/bin/install.mjs +28 -8
  3. package/bin/install.test.mjs +9 -1
  4. package/docs/CODE_RULES.md +3 -0
  5. package/docs/agents-md-alignment-plan.md +123 -0
  6. package/hooks/blocking/code_rules_enforcer.py +451 -39
  7. package/hooks/blocking/es_exe_path_rewriter.py +10 -4
  8. package/hooks/blocking/test_code_rules_enforcer.py +182 -0
  9. package/hooks/blocking/test_code_rules_enforcer_banned_identifier.py +106 -0
  10. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +173 -0
  11. package/hooks/blocking/test_code_rules_enforcer_collection_prefix.py +191 -0
  12. package/hooks/blocking/test_code_rules_enforcer_constant_equality.py +40 -0
  13. package/hooks/blocking/test_code_rules_enforcer_hardcoded_user_path.py +291 -0
  14. package/hooks/blocking/test_code_rules_enforcer_loop_variable_naming.py +87 -3
  15. package/hooks/blocking/test_code_rules_enforcer_naming_pattern.py +49 -0
  16. package/hooks/blocking/test_code_rules_enforcer_sys_path_insert.py +157 -0
  17. package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +244 -0
  18. package/hooks/blocking/test_es_exe_path_rewriter.py +81 -3
  19. package/hooks/blocking/test_windows_rmtree_blocker.py +120 -8
  20. package/hooks/blocking/windows_rmtree_blocker.py +23 -6
  21. package/hooks/config/banned_identifiers_constants.py +24 -0
  22. package/hooks/config/hardcoded_user_path_constants.py +12 -0
  23. package/hooks/config/hook_log_extractor_constants.py +1 -1
  24. package/hooks/config/pre_tool_use_stdin.py +48 -0
  25. package/hooks/config/setup_project_paths_constants.py +4 -0
  26. package/hooks/config/stuttering_check_config.py +14 -0
  27. package/hooks/config/stuttering_import_binding_constants.py +11 -0
  28. package/hooks/config/sys_path_insert_constants.py +4 -0
  29. package/hooks/config/test_banned_identifiers_constants.py +48 -0
  30. package/hooks/config/test_hardcoded_user_path_constants.py +78 -0
  31. package/hooks/config/test_hook_log_extractor_constants.py +3 -3
  32. package/hooks/config/test_pre_tool_use_stdin.py +80 -0
  33. package/hooks/config/unused_module_import_constants.py +7 -0
  34. package/hooks/config/windows_rmtree_blocker_constants.py +3 -0
  35. package/hooks/diagnostic/hook_log_stop_wrapper.py +7 -4
  36. package/hooks/git-hooks/config.py +3 -3
  37. package/hooks/git-hooks/test_gate_utils.py +10 -10
  38. package/hooks/mypy.ini +2 -0
  39. package/package.json +1 -1
  40. package/rules/gh-paginate.md +125 -0
  41. package/skills/bugteam/CONSTRAINTS.md +12 -6
  42. package/skills/bugteam/SKILL.md +364 -154
  43. package/skills/bugteam/SKILL_EVALS.md +25 -23
  44. package/skills/bugteam/reference/README.md +2 -0
  45. package/skills/bugteam/reference/audit-and-teammates.md +2 -2
  46. package/skills/bugteam/reference/teardown-publish-permissions.md +1 -1
  47. package/skills/bugteam/reference/workflow-path-a-orchestrated-teams.md +113 -0
  48. package/skills/bugteam/reference/workflow-path-b-task-harness.md +48 -0
  49. package/skills/bugteam/scripts/reflow_skill_md.py +298 -0
  50. package/skills/bugteam/test_skill_additions.py +13 -4
  51. package/skills/bugteam/test_team_lifecycle.py +103 -0
  52. package/skills/findbugs/SKILL.md +3 -3
  53. package/skills/fixbugs/SKILL.md +4 -4
  54. package/skills/monitor-open-prs/SKILL.md +32 -2
  55. package/skills/monitor-open-prs/test_team_lifecycle.py +46 -0
  56. package/skills/pr-converge/SKILL.md +1206 -131
  57. package/skills/pr-converge/scripts/README.md +145 -0
  58. package/skills/pr-converge/scripts/caller-window-pid.ps1 +86 -0
  59. package/skills/pr-converge/scripts/check_pr_mergeability.py +79 -0
  60. package/skills/pr-converge/scripts/config/pr_converge_constants.py +65 -0
  61. package/skills/pr-converge/scripts/config/test_pr_converge_constants.py +176 -0
  62. package/skills/pr-converge/scripts/cursor-agents-continue-caller.cmd +9 -0
  63. package/skills/pr-converge/scripts/cursor-agents-continue-stop-others.ps1 +16 -0
  64. package/skills/pr-converge/scripts/cursor-agents-continue.ahk +172 -0
  65. package/skills/pr-converge/scripts/cursor-agents-continue.cmd +2 -0
  66. package/skills/pr-converge/scripts/evict_cached_config_modules.py +20 -0
  67. package/skills/pr-converge/scripts/fetch_bugbot_inline_comments.py +110 -0
  68. package/skills/pr-converge/scripts/fetch_bugbot_reviews.py +103 -0
  69. package/skills/pr-converge/scripts/fetch_copilot_inline_comments.py +112 -0
  70. package/skills/pr-converge/scripts/fetch_copilot_reviews.py +121 -0
  71. package/skills/pr-converge/scripts/mark_pr_ready.py +54 -0
  72. package/skills/pr-converge/scripts/open_followup_copilot_pr.py +136 -0
  73. package/skills/pr-converge/scripts/post-bugbot-run.helpers.ps1 +49 -0
  74. package/skills/pr-converge/scripts/post-bugbot-run.ps1 +33 -0
  75. package/skills/pr-converge/scripts/reflow_skill_md.py +288 -0
  76. package/skills/pr-converge/scripts/reply_to_inline_comment.py +84 -0
  77. package/skills/pr-converge/scripts/request_copilot_review.py +71 -0
  78. package/skills/pr-converge/scripts/resolve_pr_head.py +58 -0
  79. package/skills/pr-converge/scripts/review_field_helpers.py +43 -0
  80. package/skills/pr-converge/scripts/test_check_pr_mergeability.py +126 -0
  81. package/skills/pr-converge/scripts/test_evict_cached_config_modules.py +22 -0
  82. package/skills/pr-converge/scripts/test_fetch_bugbot_inline_comments.py +342 -0
  83. package/skills/pr-converge/scripts/test_fetch_bugbot_reviews.py +220 -0
  84. package/skills/pr-converge/scripts/test_fetch_copilot_inline_comments.py +372 -0
  85. package/skills/pr-converge/scripts/test_fetch_copilot_reviews.py +280 -0
  86. package/skills/pr-converge/scripts/test_mark_pr_ready.py +69 -0
  87. package/skills/pr-converge/scripts/test_open_followup_copilot_pr.py +236 -0
  88. package/skills/pr-converge/scripts/test_post_bugbot_run.py +195 -0
  89. package/skills/pr-converge/scripts/test_reply_to_inline_comment.py +159 -0
  90. package/skills/pr-converge/scripts/test_request_copilot_review.py +101 -0
  91. package/skills/pr-converge/scripts/test_resolve_pr_head.py +79 -0
  92. package/skills/pr-converge/scripts/test_review_field_helpers.py +80 -0
  93. package/skills/pr-converge/scripts/test_trigger_bugbot.py +139 -0
  94. package/skills/pr-converge/scripts/test_view_pr_context.py +111 -0
  95. package/skills/pr-converge/scripts/trigger_bugbot.py +77 -0
  96. package/skills/pr-converge/scripts/view_pr_context.py +47 -0
  97. package/skills/pr-converge/test_team_lifecycle.py +56 -0
  98. package/skills/pr-converge/workflows/ahk-auto-continue-loop.md +108 -0
  99. package/skills/pr-converge/workflows/schedule-wakeup-loop.md +37 -0
  100. package/skills/qbug/SKILL.md +4 -4
  101. package/skills/qbug/test_qbug_skill_post_fix_audit.py +2 -2
  102. package/skills/resume-review/SKILL.md +261 -0
  103. package/skills/bugteam/scripts/README.md +0 -58
  104. package/skills/bugteam/scripts/_claude_permissions_common.py +0 -219
  105. package/skills/bugteam/scripts/bugteam_code_rules_gate.py +0 -633
  106. package/skills/bugteam/scripts/bugteam_fix_hookspath.py +0 -260
  107. package/skills/bugteam/scripts/bugteam_preflight.py +0 -201
  108. package/skills/bugteam/scripts/config/bugteam_fix_hookspath_constants.py +0 -17
  109. package/skills/bugteam/scripts/grant_project_claude_permissions.py +0 -109
  110. package/skills/bugteam/scripts/revoke_project_claude_permissions.py +0 -135
  111. package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +0 -271
  112. package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +0 -267
  113. package/skills/bugteam/scripts/test_bugteam_preflight.py +0 -189
  114. package/skills/bugteam/scripts/test_claude_permissions_common.py +0 -44
  115. /package/skills/{bugteam → pr-converge}/scripts/config/__init__.py +0 -0
@@ -14,23 +14,23 @@ Evals are split into two layers. Both layers run against the same trace but carr
14
14
 
15
15
  ## Ironclad invariants (Layer A, apply to every eval)
16
16
 
17
- Each invariant cites the normative section or companion file it derives from.
17
+ Each invariant cites the normative section or companion file it derives from. **Path A vs Path B:** `SKILL.md` **Path routing** splits harnesses. Invariants **I-1, I-3, I-4, I-7, I-9, I-11 (teammate shutdown → `TeamDelete` prefix), I-13** apply to **Path A only**. **Path B** substitutes per [`reference/workflow-path-b-task-harness.md`](reference/workflow-path-b-task-harness.md): no `TeamCreate` / `TeamDelete`; parallel auditors use parallel **`Task`** calls; **I-12 Path B** — the **lead** runs Step 2.5 `gh api` posts. **I-2, I-5, I-6, I-8, I-10** apply to **both** paths (revoke once; fresh spawn per loop; `model="opus"` on audit/fix workers; cap; read outcome XML).
18
18
 
19
19
  | # | Invariant | Citation |
20
20
  |---|---|---|
21
- | I-1 | `Bash` invoking `scripts/grant_project_claude_permissions.py` precedes every `TeamCreate`. | `SKILL.md` § Step 0 |
22
- | I-2 | `Bash` invoking `scripts/revoke_project_claude_permissions.py` runs exactly once per invocation, after the last `TeamDelete`, on every exit path (converged, stuck, cap reached, error). | `SKILL.md` § Step 5 |
23
- | I-3 | Exactly one `TeamCreate` and exactly one `TeamDelete` per invocation. | `SKILL.md` § Step 2; § Step 4 |
24
- | I-4 | Before `TeamDelete`, no teammate remains active without cleanup: either the teammate self-terminated after `Agent` returned, or the lead sent a matching `SendMessage(..., shutdown_request)` (including parallel-auditor shutdowns). No orphaned teammates when `TeamDelete` runs. | `SKILL.md` § AUDIT action (**Shutdown**); § FIX action (**Shutdown**); § Step 4 |
25
- | I-5 | `Agent` calls are fresh per loop the same `name` is never reused across loops without an intervening shutdown. | `CONSTRAINTS.md` — **Fresh teammate per loop** |
26
- | I-6 | Both audit and fix `Agent` calls pass `model="opus"` (resolves to Opus 4.7 via the Anthropic API alias; effort remains the Claude Code/model-config default `xhigh`). | `SKILL.md` § Step 2 (**Roles**); `CONSTRAINTS.md` — **Opus 4.7 at xhigh effort for both teammates** |
27
- | I-7 | `TeamDelete()` is called with no arguments. | TeamDelete schema: no required params, no properties |
21
+ | I-1 | **Path A:** `Bash` invoking `scripts/grant_project_claude_permissions.py` precedes every `TeamCreate`. **Path B:** grant precedes first audit **`Task`**. | `SKILL.md` § Step 0; § Path routing |
22
+ | I-2 | `Bash` invoking `scripts/revoke_project_claude_permissions.py` runs exactly once per invocation on every exit path, **after** teardown that applies to that path (`TeamDelete` only on Path A). | `SKILL.md` § Step 5 |
23
+ | I-3 | **Path A:** Exactly one `TeamCreate` and exactly one `TeamDelete` per invocation. **Path B:** zero `TeamCreate` / `TeamDelete`. | `SKILL.md` § Step 2; § Step 4 |
24
+ | I-4 | **Path A:** Before `TeamDelete`, no teammate remains active without cleanup (self-terminated `Agent` or `SendMessage` shutdown). | `SKILL.md` § AUDIT/FIX shutdown; § Step 4 |
25
+ | I-5 | **Path A:** `Agent` calls are fresh per loop. **Path B:** `Task` calls for audit/fix are fresh per loop (same `name` discipline where the host exposes naming). | `CONSTRAINTS.md` — **Fresh teammate per loop**; deltas **Clean-room note** |
26
+ | I-6 | Both paths: audit and fix worker spawns pass `model="opus"` on `Agent` **or** `Task` as documented in `SKILL.md` § AUDIT/FIX. | `SKILL.md` § Step 2 (**Roles**); `CONSTRAINTS.md` — **Opus 4.7 at xhigh effort for both teammates** |
27
+ | I-7 | **Path A:** `TeamDelete()` is called with no arguments. **Path B:** omit. | `SKILL.md` § Step 4 |
28
28
  | I-8 | Loop count ≤ 10 audits. 11th audit never fires. | `SKILL.md` YAML `description` (10-loop cap); § Step 3 (**Pre-audit** / **FIX** increment rules) |
29
- | I-9 | From loop 4 onward without convergence, the audit phase emits three parallel `Agent` calls in a single assistant message with names `bugfind-loop-<N>-a/b/c`. | `SKILL.md` § AUDIT action (**Parallel auditors**); `reference/audit-and-teammates.md` § **Parallel auditors** |
29
+ | I-9 | **Path A:** From loop 4 onward without convergence, three parallel `Agent` calls in one message. **Path B:** three parallel **`Task`** calls. | `SKILL.md` § AUDIT action (**Parallel auditors**); `reference/workflow-path-b-task-harness.md` |
30
30
  | I-10 | Lead reads `.bugteam-loop-<N>.outcomes.xml` with the `Read` tool after each audit, before the next action. | `SKILL.md` § AUDIT action |
31
- | I-11 | On exit of any kind, ordering is: teammate shutdowns → `TeamDelete` → temp-dir cleanup → Step 4.5 PR rewrite → revoke. | `SKILL.md` § Step 4; § Step 4.5; § Step 5; `reference/teardown-publish-permissions.md` § **Step 4** / **Step 4.5** / **Step 5** |
32
- | I-12 | Lead never posts PR review comments, finding comments, or fix replies. The only lead-side PR mutation is the final `gh pr edit --body-file` in Step 4.5. | `CONSTRAINTS.md` — **Teammates own audit/fix comment posting**; **Lead owns the final PR description rewrite only** |
33
- | I-13 | Only the orchestrator (lead session) invokes `TeamCreate`. Every teammate `Agent(...)` call passes `team_name=<lead_team_name>`. No teammate ever calls `TeamCreate`. When supplementary work arises mid-cycle (parallel auditors, adjacent-file audits, infrastructure fixes), the lead spawns additional teammates into the existing team rather than creating a second team. | `CONSTRAINTS.md` — **Orchestrator-only `TeamCreate`** (runtime error quoted there) |
31
+ | I-11 | **Path A:** `git worktree remove` each PR teammate shutdowns → `TeamDelete` → `rmtree` `<team_temp_dir>` → Step 4.5 → revoke. **Path B:** `git worktree remove` each PR (omit shutdown / `TeamDelete`) `rmtree` → Step 4.5 → revoke. | `SKILL.md` § Step 4; § Step 4.5; § Step 5; `reference/workflow-path-a-orchestrated-teams.md` § Step 4; `reference/workflow-path-b-task-harness.md` § Step 4 |
32
+ | I-12 | **Path A:** Lead never posts PR review / finding / fix replies except Step 4.5 body. **Path B:** Lead performs Step 2.5 posts per deltas; Step 4.5 unchanged. | `CONSTRAINTS.md` — **Audit/fix comment posting** |
33
+ | I-13 | **Path A:** Only the lead invokes `TeamCreate`; every teammate `Agent(..., team_name=...)`. **Path B:** no `TeamCreate`; `Task` spawns omit `team_name`. | `CONSTRAINTS.md` — **Path A — orchestrator-only `TeamCreate`**; `reference/workflow-path-b-task-harness.md` |
34
34
 
35
35
  Any eval failing one or more Layer A invariants fails the run.
36
36
 
@@ -46,23 +46,25 @@ The harness does not yet exist; this document defines its contract.
46
46
 
47
47
  ---
48
48
 
49
- ## Eval 1 — Refusal: agent teams not enabled
49
+ ## Eval 1 — Path B: agent teams env unset (Task harness, not a refusal)
50
50
 
51
51
  **Scenario.** `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS` is unset in both `claude config` and `~/.claude/settings.json`.
52
52
 
53
53
  **Trigger.** `/bugteam`
54
54
 
55
- **Layer A invariants.** None fire downstream this is a pre-cycle refusal.
55
+ **Layer A invariants.** Path B subset (I-2, I-5, I-6, I-8, I-10; I-1/I-3/I-4/I-7/I-9/I-11/I-13 N/A or Path-B-shaped).
56
56
 
57
- **Layer B predicted trace.**
57
+ **Layer B predicted trace (Path B smoke).**
58
58
  1. `Bash("claude config get env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS")` → empty.
59
- 2. `Read("~/.claude/settings.json")` settings without the env var.
60
- 3. No grant script, no `TeamCreate`, no `Agent`.
59
+ 2. `Bash("python .../grant_project_claude_permissions.py")` runs (Step 0).
60
+ 3. **No** `TeamCreate`.
61
+ 4. At least one `Task(subagent_type="code-quality-agent", ...)` or host-equivalent for AUDIT; FIX rounds use the host-appropriate FIX `Task` from `workflow-path-b-task-harness.md` § **FIX spawn** (`clean-coder` subtype on Claude Code when accepted; `generalPurpose` + clean-coder **Read** preamble on Cursor when the enum rejects `clean-coder`).
62
+ 5. `Bash("python .../revoke_project_claude_permissions.py")` on exit.
61
63
 
62
64
  **Pass criteria.**
63
- - Final assistant message contains the exact string `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 not set. /bugteam requires the agent teams feature.`.
64
- - Zero `TeamCreate`, `Agent`, `SendMessage`, `TeamDelete` calls.
65
- - Zero invocations of the grant or revoke scripts.
65
+ - **No** refusal string about missing agent teams.
66
+ - Zero `TeamCreate`, zero `TeamDelete`, zero teammate `SendMessage` shutdowns.
67
+ - Non-zero `Task` (or `Agent` without `team_name` only if the host maps Path B that way) carrying **`code-quality-agent`** / **fix worker under the clean-coder contract** (subtype `clean-coder` where accepted, else `generalPurpose` + `clean-coder.md` Read per `workflow-path-b-task-harness.md`).
66
68
 
67
69
  ---
68
70
 
@@ -95,11 +97,11 @@ The harness does not yet exist; this document defines its contract.
95
97
 
96
98
  ---
97
99
 
98
- ## Eval 5 — Happy path: converges in 2 loops
100
+ ## Eval 5 — Happy path: converges in 2 loops (Path A fixture)
99
101
 
100
- **Scenario.** PR #42 contains three P1 bugs all addressable by the mock fix teammate. Loop 1 audit returns 3 findings; loop 1 fix commits cleanly; loop 2 audit returns zero findings.
102
+ **Scenario.** PR #42 contains three P1 bugs all addressable by the mock fix teammate. Loop 1 audit returns 3 findings; loop 1 fix commits cleanly; loop 2 audit returns zero findings. **`CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`** (Path A — `TeamCreate` + teammate `Agent`).
101
103
 
102
- **Layer A invariants.** I-1, I-2, I-3, I-4, I-5, I-6, I-7, I-10, I-11, I-12.
104
+ **Layer A invariants.** Path A: I-1, I-2, I-3, I-4, I-5, I-6, I-7, I-10, I-11, I-12, I-13.
103
105
 
104
106
  **Layer B predicted trace.**
105
107
 
@@ -4,6 +4,8 @@ Expanded material that used to live inline in `SKILL.md`. Load a file when the o
4
4
 
5
5
  | File | Domain |
6
6
  |------|--------|
7
+ | [`workflow-path-a-orchestrated-teams.md`](workflow-path-a-orchestrated-teams.md) | **Path A only** — `TeamCreate`, `Agent` + `team_name`, `SendMessage`, `TeamDelete`, who posts Step 2.5 |
8
+ | [`workflow-path-b-task-harness.md`](workflow-path-b-task-harness.md) | **Path B only** — `Task` harness (no `TeamCreate` / `TeamDelete`, lead Step 2.5 `gh api`, Step 4 omissions) |
7
9
  | [`design-rationale.md`](design-rationale.md) | Why agent teams (clean-room), table-of-contents habit, when `/bugteam` applies, refusal reasons |
8
10
  | [`team-setup.md`](team-setup.md) | Permissions grant (`CLAUDE_SKILL_DIR`), PR scope, `TeamCreate`, team name / sanitization / temp dir / roles / loop state |
9
11
  | [`github-pr-reviews.md`](github-pr-reviews.md) | Per-loop reviews, `jq` + `gh api` payloads, anchors, fallbacks, REST endpoints |
@@ -16,10 +16,10 @@ Repeat until an exit condition fires.
16
16
  1. From the repository root, run the gate script (align `--base` with the PR base branch from Step 1, e.g. `origin/main` or `origin/develop`):
17
17
 
18
18
  ```bash
19
- python "${CLAUDE_SKILL_DIR}/scripts/bugteam_code_rules_gate.py" --base origin/<baseRefName>
19
+ python "${CLAUDE_SKILL_DIR}/../../_shared/pr-loop/scripts/code_rules_gate.py" --base origin/<baseRefName>
20
20
  ```
21
21
 
22
- `git merge-base` + `git diff --name-only` live inside the script; see [`../scripts/README.md`](../scripts/README.md). The lead runs this (not a teammate).
22
+ `git merge-base` + `git diff --name-only` live inside the script; see [`../../../_shared/pr-loop/scripts/README.md`](../../../_shared/pr-loop/scripts/README.md) for what lives under this directory, and [`../../../_shared/pr-loop/code-rules-gate.md`](../../../_shared/pr-loop/code-rules-gate.md) for gate-only merge-base / invocation semantics. The lead runs this (not a teammate).
23
23
 
24
24
  2. If exit code **0** → continue to step 2.5 (AUDIT spawn) below.
25
25
  3. If exit code **non-zero** → spawn a new **clean-coder** teammate — **standards-fix pass** — with instructions: read the script’s stderr, edit the repo until a **re-run** of the **same** gate command exits **0**, then one commit, `git push`, shutdown. Repeat standards-fix spawns until the gate exits **0** or **5** failed gate rounds (each round = one teammate session after a non-zero gate). If still non-zero after 5 rounds → exit reason = `error: code rules gate failed pre-audit`.
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Utility scripts (progressive disclosure)
4
4
 
5
- Fragile or repeatable shell sequences belong in `scripts/`. Anthropic: [Progressive disclosure patterns](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices#progressive-disclosure-patterns) — utility scripts are **executed**, not loaded into context as primary reading. Details: [`../scripts/README.md`](../scripts/README.md).
5
+ Fragile or repeatable shell sequences belong in `_shared/pr-loop/scripts/`. Anthropic: [Progressive disclosure patterns](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices#progressive-disclosure-patterns) — utility scripts are **executed**, not loaded into context as primary reading. Script inventory and entry points: [`../../../_shared/pr-loop/scripts/README.md`](../../../_shared/pr-loop/scripts/README.md). Gate-only merge-base / diff semantics: [`../../../_shared/pr-loop/code-rules-gate.md`](../../../_shared/pr-loop/code-rules-gate.md).
6
6
 
7
7
  ### Pre-flight (recommended before Step 0)
8
8
 
@@ -0,0 +1,113 @@
1
+ # Bugteam — Path A workflow (orchestrated teams)
2
+
3
+ Load when bugteam `SKILL.md` **Path routing** selects **Path A** (`CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS` equals **`1`** after trim). Execute **after** shared `SKILL.md` steps through **Step 2 loop state**; then use this file for **harness-only** steps. Shared gate scripts, Step 3 cycle numbering, Step 2.5 `jq`/`gh` payload shapes, `PROMPTS.md`, and outcome XML rules remain in **`SKILL.md`**.
4
+
5
+ ## Step 2 harness — lifecycle, `TeamCreate`, and team metadata
6
+
7
+ **This session is the lead.** Step 2 first resolves the team lifecycle from the [Team lifecycle](../SKILL.md#team-lifecycle-path-a-only) table in `SKILL.md`, then either creates or attaches to a team.
8
+
9
+ **Lifecycle resolution (read once, then apply):**
10
+
11
+ ```python
12
+ import os
13
+ import re
14
+
15
+ mode = os.environ.get("BUGTEAM_TEAM_LIFECYCLE", "auto").strip().lower() or "auto"
16
+ attached_team_name = os.environ.get("BUGTEAM_TEAM_NAME", "").strip()
17
+
18
+ if mode == "attach" and not attached_team_name:
19
+ raise RuntimeError(
20
+ "BUGTEAM_TEAM_LIFECYCLE=attach requires BUGTEAM_TEAM_NAME to name an existing team."
21
+ )
22
+ ```
23
+
24
+ The mode then drives Step 2's `TeamCreate` decision:
25
+
26
+ - **`mode == "attach"`** — skip `TeamCreate` entirely. Set `team_name = attached_team_name`, `team_owned = false`. Continue to teammate spawns using that `team_name`.
27
+
28
+ - **`mode == "owned"`** — call `TeamCreate(team_name=<computed_team_name>, ...)` (form below). On the runtime error matching `Already leading team "(.+)"\.` → **fail** with `Already leading team <existing>; rerun with BUGTEAM_TEAM_LIFECYCLE=attach BUGTEAM_TEAM_NAME=<existing>`. On success, set `team_owned = true`.
29
+
30
+ - **`mode == "auto"`** (default) — call `TeamCreate(team_name=<computed_team_name>, ...)`. On the same runtime error, parse the existing team name with the regex `r'Already leading team "([^"]+)"'`, set `team_name = <existing>`, `team_owned = false`, and continue without retrying `TeamCreate`. On success, set `team_owned = true`. Both branches honor §Team name below.
31
+
32
+ **`TeamCreate` call shape (only when `mode != "attach"` and the auto branch did not already attach):**
33
+
34
+ ```
35
+ TeamCreate(
36
+ team_name="<computed_team_name>",
37
+ description="Bugteam audit/fix loop for PR <number> (<owner>/<repo>)",
38
+ agent_type="team-lead"
39
+ )
40
+ ```
41
+
42
+ **Team name:** For a single-PR invocation use `bugteam-pr-<number>-<YYYYMMDDHHMMSS>`. For a multi-PR invocation use `bugteam-<YYYYMMDDHHMMSS>`. The timestamp is captured once at team-creation time. Apply the no-PR fallback (`bugteam-<sanitized-head>-<YYYYMMDDHHMMSS>`) only when no PR resolves at all. `TeamCreate` implements natural-language team creation ([`sources.md`](../sources.md) § Team creation in natural language).
43
+
44
+ **Sanitize head branch (no-PR only):** replace characters outside `[A-Za-z0-9._-]` with `-` (e.g. `feat/foo*bar` → `feat-foo-bar`). Apply once; reuse everywhere below.
45
+
46
+ **`<team_temp_dir>`:** `Path(tempfile.gettempdir()) / team_name` (lead resolves once to an absolute path; every shell gets that literal string).
47
+
48
+ **Roles (spawned per loop, not here):** bugfind → `code-quality-agent` opus (4.7) at xhigh effort; bugfix → `clean-coder` opus (4.7) at xhigh effort. `model="opus"` resolves to Opus 4.7 on the Anthropic API and runs at the model's default `xhigh` effort level — see [`CONSTRAINTS.md`](../CONSTRAINTS.md) § **Opus 4.7 at xhigh effort for both teammates** for rationale. **Display:** inherit `teammateMode` from `~/.claude.json`. Reference subagent types by name when spawning teammates ([`sources.md`](../sources.md) § Referencing subagent types when spawning teammates).
49
+
50
+ **Optional Groq-backed FIX path (explicit opt-in only):** when the user explicitly sets `BUGTEAM_FIX_IMPLEMENTER=groq-coder` before invocation, spawn the FIX teammate with `subagent_type="groq-coder"`. Before Step 3, `groq_bugteam.py` loads `packages/claude-dev-env/.env` when that file exists (gitignored; start from `packages/claude-dev-env/.env.example`). If `GROQ_API_KEY` is still unset after that load, stop and prompt the user to create `packages/claude-dev-env/.env` from the example path above—do not continue the Groq path without a key. Any other `BUGTEAM_FIX_IMPLEMENTER` value (or unset) keeps `clean-coder` on Opus. The FIX spawn XML in [`PROMPTS.md`](../PROMPTS.md) is identical for both implementers.
51
+
52
+ **`--bugbot-retrigger` flag:** when present on the `/bugteam` invocation, after every successful FIX push in Step 3, post an additional `bugbot run` issue comment via the Step 2.5 issue-comments fallback endpoint (`POST .../issues/{issue}/comments`) to re-trigger Cursor's bugbot on the new commit. Omit when the flag is absent.
53
+
54
+ ## Step 2.5 — who posts (Path A)
55
+
56
+ **Bugfind** posts one `POST .../pulls/<n>/reviews` per loop after audit. **Bugfix** posts `.../comments/<id>/replies` after push. The **lead’s** only PR write before Step 4.5 is unchanged from `SKILL.md` (Step 4.5 body edit).
57
+
58
+ ## AUDIT spawn (Path A)
59
+
60
+ After shared setup in `SKILL.md` (`mkdir`, `gh pr diff`):
61
+
62
+ ```
63
+ Agent(
64
+ subagent_type="code-quality-agent",
65
+ name="bugfind-pr<N>-loop<L>",
66
+ team_name="<team_name>",
67
+ model="opus",
68
+ description="Bugfind audit PR <N> loop <L>",
69
+ prompt="<audit XML; see PROMPTS.md>"
70
+ )
71
+ ```
72
+
73
+ Fresh `Agent` each loop; teammate context excludes lead history ([`sources.md`](../sources.md) § Teammate context isolation). [`PROMPTS.md`](../PROMPTS.md): XML + outcome schema. Lead reads `.bugteam-pr<N>-loop<L>.outcomes.xml`, fills `loop_comment_index` per `SKILL.md`.
74
+
75
+ **Shutdown:** If `Agent` returned and the teammate already ended, skip. Otherwise:
76
+
77
+ ```
78
+ SendMessage(
79
+ to="bugfind-pr<N>-loop<L>",
80
+ message={"type": "shutdown_request", "reason": "audit PR <N> loop <L> complete; outcome XML captured"}
81
+ )
82
+ ```
83
+
84
+ `approve: false` → `error: bugfind teammate refused shutdown` → Step 4 then 5 per `SKILL.md`.
85
+
86
+ **Parallel auditors (`loop_count >= 4`):** after three full audit/fix rounds without convergence, issue three `Agent` calls in one assistant message with `team_name="<team_name>"` per `SKILL.md` § parallel variant naming (`-a` / `-b` / `-c`). Shutdown: parallel `SendMessage` to `b` and `c`, then `a`.
87
+
88
+ ## FIX spawn (Path A)
89
+
90
+ ```
91
+ Agent(
92
+ subagent_type="clean-coder",
93
+ name="bugfix-pr<N>-loop<L>",
94
+ team_name="<team_name>",
95
+ model="opus",
96
+ description="Bugfix PR <N> loop <L>",
97
+ prompt="<fix XML; see PROMPTS.md>"
98
+ )
99
+ ```
100
+
101
+ **Shutdown:** same pattern as AUDIT; `SendMessage(to="bugfix-pr<N>-loop<L>", message={"type": "shutdown_request", "reason": "fix PR <N> loop <L> complete; commit <sha7> pushed"})`. `approve: false` → `error: bugfix teammate refused shutdown` → Step 4 then 5.
102
+
103
+ Verify and outcome handling: unchanged from `SKILL.md` § FIX action (**Verify**, stuck message, [`PROMPTS.md`](../PROMPTS.md)).
104
+
105
+ ## Step 4 harness — after worktree remove, before shared `rmtree`
106
+
107
+ Run **after** `SKILL.md` § Step 4 step 1 (`git worktree remove` for each PR).
108
+
109
+ 1. For each live teammate **spawned by this invocation**: `SendMessage(to="<name>", message={"type": "shutdown_request", "reason": "bugteam cycle ending"})`. `approve: false` on cleanup → log and continue. Teammates that pre-existed an attached team (mode `attach` or `auto` after the attach branch) are owned by the orchestrator, not this invocation — leave them alone.
110
+
111
+ 2. `TeamDelete()` — **only when `team_owned=true`** (set in Step 2 lifecycle resolution). When `team_owned=false`, **skip `TeamDelete`** and log `cleanup: skipped TeamDelete (team not owned by this invocation; lifecycle=<mode>)`. The orchestrator that originally created the team owns teardown — see [Team lifecycle](../SKILL.md#team-lifecycle-path-a-only).
112
+
113
+ Then continue with `SKILL.md` § Step 4 step 3 (`<team_temp_dir>` cleanup, also gated on `team_owned`).
@@ -0,0 +1,48 @@
1
+ # Bugteam — Path B workflow (Task harness)
2
+
3
+ Load when bugteam `SKILL.md` **Path routing** selects **Path B** (`CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS` is not exactly **`1`** after trim — typical Cursor IDE). Execute **after** shared `SKILL.md` steps through **Step 2 loop state**. **Do not** run `TeamCreate` or `TeamDelete`. Harness substitutions vs Path A:
4
+
5
+ | Path A | Path B |
6
+ | --- | --- |
7
+ | `TeamCreate(...)` | **Omit.** |
8
+ | `Agent(..., team_name=..., ...)` | **`Task`** (or host-equivalent) with the same `model` and prompt contracts as Path A — **omit** `team_name`. `subagent_type` follows this file's **AUDIT** / **FIX spawn** sections (Claude Code: `code-quality-agent` / `clean-coder`; Cursor: `code-quality-agent` / `generalPurpose` + mandatory `clean-coder.md` **Read** on FIX when the enum rejects `clean-coder`). |
9
+ | `SendMessage` shutdown | **Omit.** Await Task completion. |
10
+ | `TeamDelete()` | **Omit.** |
11
+ | Three parallel `Agent` (`loop_count >= 4`) | Three parallel **`Task`** with `subagent_type="code-quality-agent"`; merge outcomes in the lead like Path A `-a`/`-b`/`-c`. |
12
+
13
+ ## Step 2 harness — Path B
14
+
15
+ No `TeamCreate`. After shared `SKILL.md` **Step 1** completes (PR scope **and** `<team_temp_dir>/pr-<N>/` per Step 1 items 1–3 there), use the same `team_name` string only as a **logical label** for paths under that `<team_temp_dir>`; do not pass `team_name` into spawns.
16
+
17
+ **`--bugbot-retrigger` flag:** same as Path A [`workflow-path-a-orchestrated-teams.md`](workflow-path-a-orchestrated-teams.md) § Step 2 harness — the **lead** posts the issue comment after each successful FIX push when the flag is present.
18
+
19
+ ## Step 2.5 — who posts (Path B)
20
+
21
+ The **lead** runs the **same** `gh api` / `jq` sequences as `SKILL.md` **Step 2.5** after reading each **`Task`** handoff / outcome XML — same JSON shapes and anchor rules; only **who executes the shell** changes.
22
+
23
+ ## AUDIT spawn (Path B)
24
+
25
+ After shared setup in `SKILL.md` (`mkdir`, `gh pr diff`):
26
+
27
+ `Task` with `subagent_type="code-quality-agent"`, **omit** `team_name`, same `model`, `description`, and `prompt="<audit XML; see PROMPTS.md>"` as Path A [`workflow-path-a-orchestrated-teams.md`](workflow-path-a-orchestrated-teams.md) § AUDIT spawn.
28
+
29
+ Fresh **Task** each loop (clean-room intent: do not reuse prior Task transcript as audit input). Lead reads `.bugteam-pr<N>-loop<L>.outcomes.xml`, fills `loop_comment_index` per `SKILL.md`.
30
+
31
+ **Parallel auditors (`loop_count >= 4`):** three **`Task`** calls with `subagent_type="code-quality-agent"` in one assistant message; merge outcomes in the lead exactly as Path A documents for variants `-a`/`-b`/`-c`. Await all three Tasks — no `SendMessage`.
32
+
33
+ ## FIX spawn (Path B)
34
+
35
+ **Hosts that accept `clean-coder` as a `Task` subtype (typical Claude Code):** `Task` with `subagent_type="clean-coder"` (or `subagent_type="groq-coder"` when `BUGTEAM_FIX_IMPLEMENTER=groq-coder` per Path A optional Groq rules), **omit** `team_name`, same fields otherwise as Path A [`workflow-path-a-orchestrated-teams.md`](workflow-path-a-orchestrated-teams.md) § FIX spawn. Await Task completion — no `SendMessage`.
36
+
37
+ **Cursor and other hosts with a fixed `Task` enum (no `clean-coder` value):** use `Task` with `subagent_type: "generalPurpose"` and put the **same** FIX obligations from Path A into the **`prompt`**, after a mandatory first step to **Read** the clean-coder agent markdown: macOS/Linux `$HOME/.claude/agents/clean-coder.md`, Windows `%USERPROFILE%\.claude\agents\clean-coder.md`. State that file is binding for naming, TDD when behavior changes, hooks, one commit, and scope. Do **not** use a bare `generalPurpose` prompt without that Read. Same bundle as [`../../pr-converge/SKILL.md`](../../pr-converge/SKILL.md) Fix protocol **Implement**. If `Task` cannot run, stop and notify the user.
38
+
39
+ Verify and outcome handling: unchanged from `SKILL.md` § FIX action.
40
+ ## Step 4 harness — after worktree remove, before shared `rmtree`
41
+
42
+ Run **after** `SKILL.md` § Step 4 step 1 (`git worktree remove` for each PR).
43
+
44
+ **Omit** teammate `SendMessage` rounds and **`TeamDelete()`**. Then continue with `SKILL.md` § Step 4 step 3 (shared `rmtree` on `<team_temp_dir>`).
45
+
46
+ ## Clean-room note
47
+
48
+ Path B approximates Path A isolation by spawning a **new** Task per AUDIT (and per FIX) with the same prompt contract as Path A, without reusing prior Task context as audit input.
@@ -0,0 +1,298 @@
1
+ """Reflow packages/claude-dev-env/skills/bugteam/SKILL.md to 80 columns.
2
+
3
+ Merge soft line breaks outside fenced blocks (space join; URL path fragments
4
+ joined without a space only inside unfinished markdown link targets), then
5
+ wrap with textwrap. Preserves fenced blocks verbatim.
6
+
7
+ Same algorithm as ``packages/claude-dev-env/skills/pr-converge/scripts/reflow_skill_md.py``
8
+ from https://github.com/jl-cmd/claude-code-config/pull/349 (branch
9
+ ``cursor/pr-converge-skill-line-wrap-ecd1``); ``SKILL_PATH`` points at bugteam
10
+ ``SKILL.md``. Link reference definitions (``[id]: url``) are treated as logical
11
+ line starts so they are not merged with prior paragraphs.
12
+
13
+ Run: python3 packages/claude-dev-env/skills/bugteam/scripts/reflow_skill_md.py
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import re
19
+ import textwrap
20
+ from pathlib import Path
21
+
22
+ MAX_WIDTH = 80
23
+ SKILL_PATH = Path(__file__).resolve().parent.parent / "SKILL.md"
24
+
25
+ ORDERED_RE = re.compile(r"^(\s*)(\d+\.\s)(.*)$")
26
+ BULLET_RE = re.compile(r"^(\s*)([-*]\s)(.*)$")
27
+ UNFINISHED_MD_LINK_TARGET = re.compile(r"\]\([^)]*$")
28
+
29
+
30
+ def wrap_paragraph_plain(text: str) -> list[str]:
31
+ collapsed = " ".join(text.split())
32
+ if not collapsed:
33
+ return []
34
+ return textwrap.fill(
35
+ collapsed,
36
+ width=MAX_WIDTH,
37
+ break_long_words=False,
38
+ break_on_hyphens=False,
39
+ ).splitlines()
40
+
41
+
42
+ def wrap_list_item(lead_ws: str, marker: str, body: str) -> list[str]:
43
+ collapsed = " ".join(body.split())
44
+ if not collapsed:
45
+ return [lead_ws + marker.rstrip()]
46
+ prefix = lead_ws + marker
47
+ subsequent = lead_ws + (" " * len(marker))
48
+ return textwrap.fill(
49
+ collapsed,
50
+ width=MAX_WIDTH,
51
+ initial_indent=prefix,
52
+ subsequent_indent=subsequent,
53
+ break_long_words=False,
54
+ break_on_hyphens=False,
55
+ ).splitlines()
56
+
57
+
58
+ def reflow_yaml_description_block(lines: list[str], body_start: int) -> tuple[list[str], int]:
59
+ body_parts: list[str] = []
60
+ index = body_start
61
+ while index < len(lines):
62
+ line = lines[index]
63
+ if line.strip() == "---":
64
+ index += 1
65
+ break
66
+ stripped = line.lstrip()
67
+ if stripped:
68
+ body_parts.append(stripped)
69
+ index += 1
70
+ merged = " ".join(body_parts)
71
+ wrapped = textwrap.fill(
72
+ merged,
73
+ width=MAX_WIDTH,
74
+ initial_indent=" ",
75
+ subsequent_indent=" ",
76
+ break_long_words=False,
77
+ break_on_hyphens=False,
78
+ )
79
+ return wrapped.splitlines(), index
80
+
81
+
82
+ def is_table_line(line: str) -> bool:
83
+ return line.lstrip().startswith("|")
84
+
85
+
86
+ def is_link_reference_definition(stripped: str) -> bool:
87
+ return bool(re.match(r"^\[[^\]]+\]:\s+\S", stripped))
88
+
89
+
90
+ def is_new_logical_line(stripped: str) -> bool:
91
+ if not stripped:
92
+ return False
93
+ if stripped.startswith("```"):
94
+ return True
95
+ if stripped.startswith("#"):
96
+ return True
97
+ if stripped == "---":
98
+ return True
99
+ if is_table_line(stripped):
100
+ return True
101
+ if stripped.startswith("<example>") or stripped.startswith("</example>"):
102
+ return True
103
+ if is_link_reference_definition(stripped):
104
+ return True
105
+ if ORDERED_RE.match(stripped) or BULLET_RE.match(stripped):
106
+ return True
107
+ return False
108
+
109
+
110
+ def merge_without_space(buffer: str, continuation: str) -> bool:
111
+ """Join without space only for split markdown link URL paths."""
112
+ base = buffer.rstrip()
113
+ stripped = continuation.lstrip()
114
+ if not base or not stripped:
115
+ return False
116
+ if stripped.startswith("/") and UNFINISHED_MD_LINK_TARGET.search(base):
117
+ return True
118
+ return False
119
+
120
+
121
+ def merge_soft_breaks(lines: list[str]) -> list[str]:
122
+ output: list[str] = []
123
+ index = 0
124
+ in_fence = False
125
+ while index < len(lines):
126
+ raw = lines[index]
127
+ line = raw.rstrip("\n")
128
+ if line.lstrip().startswith("```"):
129
+ in_fence = not in_fence
130
+ output.append(line)
131
+ index += 1
132
+ continue
133
+ if in_fence:
134
+ output.append(line)
135
+ index += 1
136
+ continue
137
+ if line.strip() == "":
138
+ output.append(line)
139
+ index += 1
140
+ continue
141
+ buffer_line = line
142
+ index += 1
143
+ while index < len(lines):
144
+ next_raw = lines[index].rstrip("\n")
145
+ if next_raw.strip() == "":
146
+ break
147
+ if next_raw.lstrip().startswith("```"):
148
+ break
149
+ stripped_next = next_raw.lstrip()
150
+ if is_new_logical_line(stripped_next):
151
+ break
152
+ if merge_without_space(buffer_line, stripped_next):
153
+ buffer_line = buffer_line.rstrip() + stripped_next
154
+ else:
155
+ buffer_line = f"{buffer_line.rstrip()} {stripped_next}"
156
+ index += 1
157
+ output.append(buffer_line)
158
+ return output
159
+
160
+
161
+ def reflow_merged_line(line: str) -> list[str]:
162
+ stripped = line.strip()
163
+ if stripped == "":
164
+ return [""]
165
+ if stripped.startswith("```"):
166
+ return [line]
167
+ if stripped.startswith("#"):
168
+ if len(stripped) <= MAX_WIDTH:
169
+ return [stripped]
170
+ title = stripped.lstrip("#").strip()
171
+ level = len(stripped) - len(stripped.lstrip("#"))
172
+ prefix = "#" * level + " "
173
+ return textwrap.fill(
174
+ title,
175
+ width=MAX_WIDTH,
176
+ initial_indent=prefix,
177
+ subsequent_indent=prefix,
178
+ break_long_words=False,
179
+ break_on_hyphens=False,
180
+ ).splitlines()
181
+ if stripped == "---":
182
+ return ["---"]
183
+ if is_link_reference_definition(stripped):
184
+ return [stripped]
185
+ if is_table_line(stripped):
186
+ return [stripped]
187
+ if stripped.startswith("</example>"):
188
+ return [stripped]
189
+ if stripped.startswith("<example>"):
190
+ inner = stripped[len("<example>") :].strip()
191
+ if not inner:
192
+ return ["<example>"]
193
+ tag = "<example> "
194
+ subsequent = " " * len(tag)
195
+ return textwrap.fill(
196
+ " ".join(inner.split()),
197
+ width=MAX_WIDTH,
198
+ initial_indent=tag,
199
+ subsequent_indent=subsequent,
200
+ break_long_words=False,
201
+ break_on_hyphens=False,
202
+ ).splitlines()
203
+
204
+ ordered = ORDERED_RE.match(line)
205
+ if ordered:
206
+ return wrap_list_item(ordered.group(1), ordered.group(2), ordered.group(3))
207
+
208
+ bullet = BULLET_RE.match(line)
209
+ if bullet:
210
+ return wrap_list_item(bullet.group(1), bullet.group(2), bullet.group(3))
211
+
212
+ return wrap_paragraph_plain(stripped)
213
+
214
+
215
+ def reflow_markdown_body(lines: list[str]) -> list[str]:
216
+ merged = merge_soft_breaks(lines)
217
+ output: list[str] = []
218
+ for each_line in merged:
219
+ if each_line.strip() == "":
220
+ output.append("")
221
+ continue
222
+ output.extend(reflow_merged_line(each_line))
223
+ return output
224
+
225
+
226
+ def wrap_long_bash_fence_lines(lines: list[str]) -> list[str]:
227
+ """Hard-wrap only ```bash fence bodies that still exceed MAX_WIDTH."""
228
+ output: list[str] = []
229
+ in_bash_fence = False
230
+ for line in lines:
231
+ stripped = line.lstrip()
232
+ if stripped.startswith("```"):
233
+ if not in_bash_fence:
234
+ lang = stripped[3:].strip().lower()
235
+ in_bash_fence = lang == "bash"
236
+ else:
237
+ in_bash_fence = False
238
+ output.append(line)
239
+ continue
240
+ if in_bash_fence and len(line) > MAX_WIDTH:
241
+ indent_len = len(line) - len(line.lstrip())
242
+ indent = line[:indent_len]
243
+ content = line.lstrip()
244
+ wrapped_segments: list[str] = []
245
+ rest = content
246
+ while len(rest) > MAX_WIDTH - len(indent):
247
+ room = MAX_WIDTH - len(indent) - 2
248
+ window = rest[:room]
249
+ break_at = window.rfind(" ")
250
+ if break_at <= 0:
251
+ break_at = room
252
+ piece = rest[:break_at].rstrip()
253
+ rest = rest[break_at:].lstrip()
254
+ wrapped_segments.append(indent + piece + " \\")
255
+ if rest:
256
+ wrapped_segments.append(indent + (" " if wrapped_segments else "") + rest)
257
+ output.extend(wrapped_segments)
258
+ else:
259
+ output.append(line)
260
+ return output
261
+
262
+
263
+ def main() -> None:
264
+ raw = SKILL_PATH.read_text(encoding="utf-8")
265
+ lines = raw.splitlines()
266
+ if not lines or lines[0].strip() != "---":
267
+ raise SystemExit("expected YAML front matter starting with ---")
268
+
269
+ out: list[str] = ["---"]
270
+ index = 1
271
+ while index < len(lines):
272
+ line = lines[index]
273
+ if line.startswith("description: >-"):
274
+ out.append(line)
275
+ index += 1
276
+ desc_lines, index = reflow_yaml_description_block(lines, index)
277
+ out.extend(desc_lines)
278
+ out.append("---")
279
+ break
280
+ out.append(line)
281
+ index += 1
282
+
283
+ body = reflow_markdown_body(lines[index:])
284
+ body = wrap_long_bash_fence_lines(body)
285
+
286
+ text = "\n".join(out + body) + "\n"
287
+ SKILL_PATH.write_text(text, encoding="utf-8", newline="\n")
288
+
289
+ all_lines = text.splitlines()
290
+ long_rows = [(i, len(ln)) for i, ln in enumerate(all_lines, 1) if len(ln) > MAX_WIDTH]
291
+ print("SKILL.md reflowed; lines:", len(all_lines))
292
+ print("lines longer than %d: %d" % (MAX_WIDTH, len(long_rows)))
293
+ if long_rows[:20]:
294
+ print("first long:", long_rows[:20])
295
+
296
+
297
+ if __name__ == "__main__":
298
+ main()
@@ -10,9 +10,16 @@ def _read_skill_text() -> str:
10
10
  return skill_path.read_text(encoding="utf-8")
11
11
 
12
12
 
13
+ def _read_path_a_workflow_text() -> str:
14
+ workflow_path = (
15
+ pathlib.Path(__file__).parent / "reference" / "workflow-path-a-orchestrated-teams.md"
16
+ )
17
+ return workflow_path.read_text(encoding="utf-8")
18
+
19
+
13
20
  def test_skill_references_fix_implementer_env_var():
14
- skill_text = _read_skill_text()
15
- assert "BUGTEAM_FIX_IMPLEMENTER" in skill_text
21
+ workflow_text = _read_path_a_workflow_text()
22
+ assert "BUGTEAM_FIX_IMPLEMENTER" in workflow_text
16
23
 
17
24
 
18
25
  def test_skill_names_default_implementer_subagent_type():
@@ -21,10 +28,12 @@ def test_skill_names_default_implementer_subagent_type():
21
28
 
22
29
 
23
30
  def test_skill_names_optional_groq_implementer_subagent_type():
24
- skill_text = _read_skill_text()
25
- assert "groq-coder" in skill_text
31
+ workflow_text = _read_path_a_workflow_text()
32
+ assert "groq-coder" in workflow_text
26
33
 
27
34
 
28
35
  def test_skill_documents_bugbot_retrigger_flag():
29
36
  skill_text = _read_skill_text()
37
+ workflow_text = _read_path_a_workflow_text()
30
38
  assert "--bugbot-retrigger" in skill_text
39
+ assert "--bugbot-retrigger" in workflow_text