claude-dev-env 1.26.2 → 1.26.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/install.mjs CHANGED
@@ -11,6 +11,7 @@ const CLAUDE_HOME = join(homedir(), '.claude');
11
11
  const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
12
12
  const MANIFEST_FILE = join(CLAUDE_HOME, '.claude-dev-env-manifest.json');
13
13
  const PACKAGE_NAME = 'claude-dev-env';
14
+ const PACKAGE_VERSION = JSON.parse(readFileSync(join(PACKAGE_ROOT, 'package.json'), 'utf8')).version;
14
15
  const packageRequire = createRequire(import.meta.url);
15
16
 
16
17
  const CONTENT_DIRECTORIES = ['rules', 'docs', 'commands', 'agents', 'system-prompts', 'scripts'];
@@ -218,7 +219,7 @@ function mergeHooks(hooksSourceRoot, pythonCommand) {
218
219
  }
219
220
 
220
221
  function writeManifest(installedFiles) {
221
- const manifest = { package: PACKAGE_NAME, version: '1.0.0', installedAt: new Date().toISOString(), files: installedFiles };
222
+ const manifest = { package: PACKAGE_NAME, version: PACKAGE_VERSION, installedAt: new Date().toISOString(), files: installedFiles };
222
223
  writeFileSync(MANIFEST_FILE, JSON.stringify(manifest, null, 2) + '\n');
223
224
  }
224
225
 
@@ -219,7 +219,10 @@ def test_read_body_file_rejects_relative_path_traversal(tmp_path) -> None:
219
219
  import os, pytest
220
220
  sentinel_file = tmp_path / 'secret.txt'
221
221
  sentinel_file.write_text('secret')
222
- rel_path = os.path.relpath(str(sentinel_file))
222
+ try:
223
+ rel_path = os.path.relpath(str(sentinel_file))
224
+ except ValueError:
225
+ pytest.skip('tmp_path on different drive than cwd; relpath undefined on Windows')
223
226
  if '..' not in rel_path:
224
227
  pytest.skip('file is under cwd, not a traversal case')
225
228
  with pytest.raises(m.PathTraversalError):
package/hooks/hooks.json CHANGED
@@ -25,11 +25,6 @@
25
25
  "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/sensitive_file_protector.py",
26
26
  "timeout": 10
27
27
  },
28
- {
29
- "type": "command",
30
- "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/pyautogui_scroll_blocker.py",
31
- "timeout": 10
32
- },
33
28
  {
34
29
  "type": "command",
35
30
  "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/validation/hook_format_validator.py",
@@ -49,11 +44,6 @@
49
44
  "type": "command",
50
45
  "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/tdd_enforcer.py",
51
46
  "timeout": 10
52
- },
53
- {
54
- "type": "command",
55
- "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/validation/code_style_validator.py",
56
- "timeout": 15
57
47
  }
58
48
  ]
59
49
  },
@@ -102,16 +92,6 @@
102
92
  }
103
93
  ]
104
94
  },
105
- {
106
- "matcher": "Task|Agent",
107
- "hooks": [
108
- {
109
- "type": "command",
110
- "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/blocking/parallel_task_blocker.py",
111
- "timeout": 10
112
- }
113
- ]
114
- },
115
95
  {
116
96
  "matcher": "AskUserQuestion",
117
97
  "hooks": [
@@ -123,23 +103,6 @@
123
103
  ]
124
104
  }
125
105
  ],
126
- "UserPromptSubmit": [
127
- {
128
- "matcher": "",
129
- "hooks": [
130
- {
131
- "type": "command",
132
- "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/session/bulk_edit_reminder.py",
133
- "timeout": 15
134
- },
135
- {
136
- "type": "command",
137
- "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/session/code_rules_reminder.py",
138
- "timeout": 15
139
- }
140
- ]
141
- }
142
- ],
143
106
  "SessionStart": [
144
107
  {
145
108
  "matcher": "",
@@ -202,11 +165,6 @@
202
165
  "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/validation/mypy_validator.py",
203
166
  "timeout": 30
204
167
  },
205
- {
206
- "type": "command",
207
- "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/validation/e2e-test-validator.py",
208
- "timeout": 15
209
- },
210
168
  {
211
169
  "type": "command",
212
170
  "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/workflow/auto_formatter.py",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-dev-env",
3
- "version": "1.26.2",
3
+ "version": "1.26.4",
4
4
  "description": "Claude Code development standards — rules, hooks, agents, commands, and skills",
5
5
  "type": "module",
6
6
  "bin": {
@@ -4,6 +4,7 @@
4
4
 
5
5
  - **Agent teams required, not parallel subagents.** The skill MUST use Claude Code's agent teams feature (`CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`). Spawning `code-quality-agent` and `clean-coder` as parallel subagents from the lead's context = fail; the clean-room property requires independent teammate sessions.
6
6
  - **Orchestrator-only `TeamCreate`.** Only the lead session (this session, when `/bugteam` is invoked) calls `TeamCreate`. Teammates never call `TeamCreate` — if a teammate's spawn prompt instructs it to, that is a skill defect. When additional parallel work is needed (e.g., parallel auditors from loop 4 onward, supplementary audit of adjacent files), the lead spawns additional teammates into the EXISTING team by passing the current `team_name` to every `Agent(...)` call. Multiple teammate "sets" live inside one team under one orchestrator. The runtime enforces this: `TeamCreate` called while the session already leads a team returns the error `Already leading team "<name>". A leader can only manage one team at a time. Use TeamDelete to end the current team before creating a new one.` — direct quote from the runtime's response when this invariant is violated.
7
+ - **One team per invocation, multi-PR supported.** All PRs in a single /bugteam invocation share one team created by the orchestrator. Per-PR identity lives in the teammate name prefix (`bugfind-pr<N>-loop<L>` / `bugfix-pr<N>-loop<L>`) and the `<team_temp_dir>/pr-<N>/` subfolder containing that PR's git worktree, diff patches, and outcome XML files.
7
8
  - **Grant before any spawn, revoke before any return.** Step 0 grants project `.claude/**` permissions; Step 5 revokes. Both are mandatory. Revoke runs on every exit path including error, cap-reached, and stuck.
8
9
  - **Fresh teammate per loop.** Both bugfind and bugfix are spawned new each loop and shut down after their action. Reusing a teammate across loops accumulates context inside that teammate's window — defeats clean-room.
9
10
  - **One up-front confirmation = whole cycle.** The `/bugteam` invocation authorizes the entire cycle; every subsequent decision runs on that single authorization.
@@ -11,10 +11,14 @@ Keep the spawn prompt self-contained: reference only the PR scope, audit rubric,
11
11
  <base_branch>base ref</base_branch>
12
12
  <pr_url>full URL</pr_url>
13
13
  <loop>N</loop>
14
+ <pr_number>N</pr_number>
15
+ <worktree_path>absolute path from Step 1 per-PR workspace</worktree_path>
14
16
  </context>
15
17
 
18
+ cd into `<worktree_path>` before any git, gh, or file operation.
19
+
16
20
  <scope>
17
- <diff_path>Absolute path to the loop-N patch file under team_temp_dir from Step 2 (same path as gh pr diff redirect in AUDIT)</diff_path>
21
+ <diff_path>Absolute path to the per-PR patch file: <team_temp_dir>/pr-<N>/loop-<L>.patch (same path as gh pr diff redirect in AUDIT)</diff_path>
18
22
  <scope_rule>Audit only lines added or modified in the diff. Pre-existing code on untouched lines is out of scope.</scope_rule>
19
23
  </scope>
20
24
 
@@ -72,8 +76,8 @@ Keep the spawn prompt self-contained: reference only the PR scope, audit rubric,
72
76
  </comment_posting>
73
77
 
74
78
  <output_format>
75
- Write the outcome XML below to .bugteam-loop-N.outcomes.xml in the
76
- working directory. Return only that path on stdout. The schema:
79
+ Write the outcome XML below to .bugteam-pr<N>-loop<L>.outcomes.xml inside
80
+ the PR's worktree directory (<worktree_path>). Return only that path on stdout. The schema:
77
81
  </output_format>
78
82
  ```
79
83
 
@@ -100,7 +104,7 @@ Keep the spawn prompt self-contained: reference only the PR scope, audit rubric,
100
104
  </bugteam_audit>
101
105
  ```
102
106
 
103
- After the teammate writes the XML and returns, the lead reads `.bugteam-loop-<N>.outcomes.xml` with the `Read` tool, parses it, and populates `loop_comment_index` from `<finding>` elements.
107
+ After the teammate writes the XML and returns, the lead reads `.bugteam-pr<N>-loop<L>.outcomes.xml` from the PR's worktree directory with the `Read` tool, parses it, and populates `loop_comment_index` from `<finding>` elements.
104
108
 
105
109
  ## FIX spawn-prompt XML (bugfix teammate)
106
110
 
@@ -111,8 +115,12 @@ After the teammate writes the XML and returns, the lead reads `.bugteam-loop-<N>
111
115
  <base_branch>base</base_branch>
112
116
  <pr_url>url</pr_url>
113
117
  <loop>N</loop>
118
+ <pr_number>N</pr_number>
119
+ <worktree_path>absolute path from Step 1 per-PR workspace</worktree_path>
114
120
  </context>
115
121
 
122
+ cd into `<worktree_path>` before any git, gh, or file operation.
123
+
116
124
  <bugs_to_fix>
117
125
  [for each P0/P1/P2 finding from last_findings:]
118
126
  <bug
@@ -144,7 +152,7 @@ After the teammate writes the XML and returns, the lead reads `.bugteam-loop-<N>
144
152
  - "Could not address this loop: <one-line reason>" if you skipped or failed it
145
153
  - "Hook blocked the fix commit: <one-line summary>" if the commit was hook-blocked
146
154
  Use the Fix reply CLI shape from Step 2.5 (`jq -Rs | gh api .../comments/<id>/replies --input -`). Write every reply body to a temp file first.
147
- 7. Write `.bugteam-loop-<N>.outcomes.xml` (schema below) and return its path.
155
+ 7. Write `.bugteam-pr<N>-loop<L>.outcomes.xml` inside `<worktree_path>` (schema below) and return its path.
148
156
  </execution>
149
157
 
150
158
  <outcome_xml_schema>
@@ -51,6 +51,7 @@ Refusals — first match wins; respond with the quoted line exactly and stop:
51
51
  - **No PR or upstream diff.** `No PR or upstream diff. /bugteam needs a target.`
52
52
  - **Dirty tree.** `Uncommitted changes detected. Stash, commit, or revert before /bugteam.`
53
53
  - **Missing subagents.** Before Step 0, confirm `code-quality-agent` and `clean-coder` exist. Else: `Required subagent type <name> not installed. /bugteam needs both code-quality-agent and clean-coder available.`
54
+ - **Lead role must be held by the orchestrator.** Run /bugteam in the session that received the user's command. The orchestrator session calls TeamCreate directly. Runtime confirms a single lead per team: `Already leading team "<name>". A leader can only manage one team at a time.`
54
55
 
55
56
  ## Utility scripts
56
57
 
@@ -89,17 +90,23 @@ python "${CLAUDE_SKILL_DIR}/scripts/grant_project_claude_permissions.py"
89
90
 
90
91
  ### Step 1: Resolve PR scope (once)
91
92
 
92
- Same as `/findbugs`:
93
-
94
- 1. `gh pr view --json number,baseRefName,headRefName,url`
95
- 2. Else `git merge-base HEAD origin/<default>` then `git diff <merge-base>...HEAD`
96
- 3. Else refuse above.
93
+ Accept one or more PR numbers from the invocation. For each PR, run `gh pr view --json number,baseRefName,headRefName,url` (falling back to the merge-base diff path when no PR exists). Capture `all_prs = [{number, owner, repo, baseRef, headRef, url}, ...]`. A single-PR invocation produces a one-element list and follows the same downstream rules.
97
94
 
98
95
  Keep: owner/repo, branches, PR number, URL — for all loops.
99
96
 
97
+ #### Per-PR workspace
98
+
99
+ For each PR in all_prs:
100
+
101
+ 1. Create `<team_temp_dir>/pr-<N>/`.
102
+ 2. Run `git worktree add "<team_temp_dir>/pr-<N>/worktree" origin/<headRef>`.
103
+ 3. Record the absolute worktree path alongside the PR's other fields.
104
+
105
+ Teammates spawned for a PR operate inside that PR's worktree. Step 4 teardown runs `git worktree remove "<team_temp_dir>/pr-<N>/worktree"` for each PR before `TeamDelete`.
106
+
100
107
  ### Step 2: Create the agent team
101
108
 
102
- Lead calls `TeamCreate`:
109
+ **This session is the lead.** The orchestrator calls `TeamCreate` directly:
103
110
 
104
111
  ```
105
112
  TeamCreate(
@@ -109,7 +116,7 @@ TeamCreate(
109
116
  )
110
117
  ```
111
118
 
112
- **Team name:** `bugteam-pr-<number>-<YYYYMMDDHHMMSS>` or `bugteam-<sanitized-head>-<YYYYMMDDHHMMSS>` if no PR. Timestamp avoids collisions. `TeamCreate` implements natural-language team creation ([`sources.md`](sources.md) § Team creation in natural language).
119
+ **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).
113
120
 
114
121
  **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.
115
122
 
@@ -186,7 +193,9 @@ jq -n \
186
193
 
187
194
  ### Step 3: The cycle
188
195
 
189
- Repeat until exit. **Gate:** `validate_content` / `hooks/blocking/code_rules_enforcer.py` on PR-scoped files before every AUDIT (`bugteam_code_rules_gate.py`). Lead runs gate; clean-coder clears failures; then bugfind audits.
196
+ Run the AUDIT-FIX cycle for each PR in all_prs, reusing the same team across PRs. The 10-loop cap applies per PR. Exit reasons (converged, cap reached, stuck, error) are tracked per PR; the final report lists one outcome line per PR.
197
+
198
+ **Gate:** `validate_content` / `hooks/blocking/code_rules_enforcer.py` on PR-scoped files before every AUDIT (`bugteam_code_rules_gate.py`). Lead runs gate; clean-coder clears failures; then bugfind audits.
190
199
 
191
200
  1. From `last_action` / `last_findings`:
192
201
  - `last_action == "audited"` and `last_findings.total == 0` → exit `converged`
@@ -213,29 +222,29 @@ First pass: pre-audit → AUDIT. After a FIX, the next pass runs pre-audit again
213
222
  ### AUDIT action
214
223
 
215
224
  ```bash
216
- mkdir -p "<team_temp_dir>"
217
- gh pr diff <number> -R <owner>/<repo> > "<team_temp_dir>/loop-<N>.patch"
225
+ mkdir -p "<team_temp_dir>/pr-<N>"
226
+ gh pr diff <N> -R <owner>/<repo> > "<team_temp_dir>/pr-<N>/loop-<L>.patch"
218
227
  ```
219
228
 
220
229
  ```
221
230
  Agent(
222
231
  subagent_type="code-quality-agent",
223
- name="bugfind",
232
+ name="bugfind-pr<N>-loop<L>",
224
233
  team_name="<team_name>",
225
234
  model="sonnet",
226
- description="Bugfind audit loop <N>",
235
+ description="Bugfind audit PR <N> loop <L>",
227
236
  prompt="<audit XML; see PROMPTS.md>"
228
237
  )
229
238
  ```
230
239
 
231
- 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-loop-<N>.outcomes.xml`, fills `loop_comment_index`.
240
+ 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`.
232
241
 
233
242
  **Shutdown:** If `Agent` returned and the teammate already ended, skip. Otherwise:
234
243
 
235
244
  ```
236
245
  SendMessage(
237
- to="bugfind",
238
- message={"type": "shutdown_request", "reason": "audit loop <N> complete; outcome XML captured"}
246
+ to="bugfind-pr<N>-loop<L>",
247
+ message={"type": "shutdown_request", "reason": "audit PR <N> loop <L> complete; outcome XML captured"}
239
248
  )
240
249
  ```
241
250
 
@@ -243,24 +252,24 @@ SendMessage(
243
252
 
244
253
  `last_action = "audited"`; append audit line to `audit_log`.
245
254
 
246
- **Parallel auditors (`loop_count >= 4`):** gate passes immediately before; after three full audit/fix rounds without convergence, issue three `Agent` calls in one assistant message (parallel). `-a` posts the review and merges outcomes from `-b`/`-c` (read `.bugteam-loop-<N>.outcomes.xml` plus `<team_temp_dir>/loop-<N>-b.outcomes.xml` and `...-c...`); merge key `(file, line, category_letter)`; re-id `loopN-K`. `-b`/`-c` write sibling XML only; prompts must pass literal absolute sibling paths. Shutdown: parallel `SendMessage` to `b` and `c`, then `a`.
255
+ **Parallel auditors (`loop_count >= 4`):** gate passes immediately before; after three full audit/fix rounds without convergence, issue three `Agent` calls in one assistant message (parallel). `-a` posts the review and merges outcomes from `-b`/`-c` (read `.bugteam-pr<N>-loop<L>.outcomes.xml` plus `<team_temp_dir>/pr-<N>/loop-<L>-b.outcomes.xml` and `...-c...`); merge key `(file, line, category_letter)`; re-id `loopN-K`. `-b`/`-c` write sibling XML only; prompts must pass literal absolute sibling paths. Shutdown: parallel `SendMessage` to `b` and `c`, then `a`.
247
256
 
248
257
  ### FIX action
249
258
 
250
259
  ```
251
260
  Agent(
252
261
  subagent_type="clean-coder",
253
- name="bugfix",
262
+ name="bugfix-pr<N>-loop<L>",
254
263
  team_name="<team_name>",
255
264
  model="sonnet",
256
- description="Bugfix loop <N>",
265
+ description="Bugfix PR <N> loop <L>",
257
266
  prompt="<fix XML; see PROMPTS.md>"
258
267
  )
259
268
  ```
260
269
 
261
270
  Pass finding comment URLs/ids from `loop_comment_index` in XML. Replies: `Fixed in <sha>` or `Could not address this loop: <reason>`.
262
271
 
263
- **Shutdown:** same as bugfind; else `SendMessage(to="bugfix", message={"type": "shutdown_request", "reason": "fix loop <N> complete; commit <sha7> pushed"})`. `approve: false` → `error: bugfix teammate refused shutdown` → Step 4 then 5.
272
+ **Shutdown:** same as bugfind; else `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.
264
273
 
265
274
  [`PROMPTS.md`](PROMPTS.md): fix XML + schema. Verify: `git rev-parse HEAD` advanced; `git fetch origin <branch> && git rev-parse origin/<branch>` matches `HEAD`. Unchanged HEAD → `stuck — bugfix teammate could not address findings`.
266
275