claude-dev-env 1.26.3 → 1.26.5
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/hooks/blocking/hedging_language_blocker.py +18 -9
- package/hooks/blocking/test_hedging_language_blocker.py +135 -0
- package/hooks/blocking/test_pr_description_enforcer.py +4 -1
- package/hooks/config/__init__.py +1 -0
- package/hooks/config/messages.py +4 -0
- package/package.json +1 -1
- package/skills/bugteam/CONSTRAINTS.md +1 -0
- package/skills/bugteam/PROMPTS.md +13 -5
- package/skills/bugteam/SKILL.md +28 -19
|
@@ -11,6 +11,9 @@ import os
|
|
|
11
11
|
import re
|
|
12
12
|
import sys
|
|
13
13
|
|
|
14
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "config"))
|
|
15
|
+
from messages import USER_FACING_NOTICE
|
|
16
|
+
|
|
14
17
|
PLUGIN_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
15
18
|
|
|
16
19
|
RESEARCH_MODE_SKILL_SEARCH_PATHS = [
|
|
@@ -99,14 +102,19 @@ def main() -> None:
|
|
|
99
102
|
|
|
100
103
|
formatted_term_list = ", ".join(f'"{term}"' for term in found_hedging_terms)
|
|
101
104
|
|
|
102
|
-
|
|
105
|
+
resolved_skill_path: str | None = None
|
|
103
106
|
for each_skill_path in RESEARCH_MODE_SKILL_SEARCH_PATHS:
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
if os.path.exists(each_skill_path):
|
|
108
|
+
resolved_skill_path = each_skill_path
|
|
109
|
+
break
|
|
110
|
+
|
|
111
|
+
if resolved_skill_path is not None:
|
|
112
|
+
skill_reference = f"under the research-mode constraints defined in:\n\n{resolved_skill_path}"
|
|
113
|
+
else:
|
|
114
|
+
skill_reference = (
|
|
115
|
+
"under research-mode constraints "
|
|
116
|
+
"(no research-mode skill installed; verify with sources or reply 'I don't know')"
|
|
117
|
+
)
|
|
110
118
|
|
|
111
119
|
block_response = {
|
|
112
120
|
"decision": "block",
|
|
@@ -114,12 +122,13 @@ def main() -> None:
|
|
|
114
122
|
f"ANTI-HALLUCINATION GUARDRAIL: Your response contains hedging language: "
|
|
115
123
|
f"{formatted_term_list}. "
|
|
116
124
|
f"These words signal unverified claims. You MUST rewrite your response "
|
|
117
|
-
f"
|
|
118
|
-
f"{research_mode_content}\n\n"
|
|
125
|
+
f"{skill_reference}\n\n"
|
|
119
126
|
f"Do NOT simply remove the hedging word and keep the unverified claim. "
|
|
120
127
|
f"Either VERIFY it with a source or replace it with 'I don't know'.\n\n"
|
|
121
128
|
f"You MUST re-output the complete, revised response with the corrections applied."
|
|
122
129
|
),
|
|
130
|
+
"systemMessage": USER_FACING_NOTICE,
|
|
131
|
+
"suppressOutput": True,
|
|
123
132
|
}
|
|
124
133
|
|
|
125
134
|
print(json.dumps(block_response))
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Tests for hedging_language_blocker hook response shape."""
|
|
2
|
+
|
|
3
|
+
import importlib.util
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
import tempfile
|
|
9
|
+
|
|
10
|
+
HOOK_SCRIPT_PATH = os.path.join(os.path.dirname(__file__), "hedging_language_blocker.py")
|
|
11
|
+
_HOOKS_DIR = os.path.dirname(HOOK_SCRIPT_PATH)
|
|
12
|
+
_CONFIG_DIR = os.path.join(_HOOKS_DIR, "..", "config")
|
|
13
|
+
if _HOOKS_DIR not in sys.path:
|
|
14
|
+
sys.path.insert(0, _HOOKS_DIR)
|
|
15
|
+
if _CONFIG_DIR not in sys.path:
|
|
16
|
+
sys.path.insert(0, _CONFIG_DIR)
|
|
17
|
+
import hedging_language_blocker
|
|
18
|
+
from messages import USER_FACING_NOTICE
|
|
19
|
+
|
|
20
|
+
RESEARCH_MODE_SKILL_BODY_MARKER = "Three anti-hallucination constraints are ALWAYS active."
|
|
21
|
+
HEDGING_MESSAGE = "This is likely correct."
|
|
22
|
+
CLEAN_MESSAGE = "This is verified by the source document."
|
|
23
|
+
EMPTY_MESSAGE = ""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def run_hook_with_message(assistant_message: str) -> subprocess.CompletedProcess:
|
|
27
|
+
hook_input_payload = json.dumps({"last_assistant_message": assistant_message})
|
|
28
|
+
return subprocess.run(
|
|
29
|
+
[sys.executable, HOOK_SCRIPT_PATH],
|
|
30
|
+
input=hook_input_payload,
|
|
31
|
+
capture_output=True,
|
|
32
|
+
text=True,
|
|
33
|
+
check=False,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def run_hook_with_patched_search_paths(
|
|
38
|
+
assistant_message: str,
|
|
39
|
+
search_paths: list[str],
|
|
40
|
+
) -> subprocess.CompletedProcess:
|
|
41
|
+
"""Run the hook with RESEARCH_MODE_SKILL_SEARCH_PATHS overridden via a wrapper script."""
|
|
42
|
+
wrapper_script = (
|
|
43
|
+
"import sys, json, os\n"
|
|
44
|
+
f"sys.path.insert(0, {repr(os.path.dirname(HOOK_SCRIPT_PATH))})\n"
|
|
45
|
+
"import hedging_language_blocker as blocker\n"
|
|
46
|
+
f"blocker.RESEARCH_MODE_SKILL_SEARCH_PATHS = {repr(search_paths)}\n"
|
|
47
|
+
"blocker.main()\n"
|
|
48
|
+
)
|
|
49
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as wrapper_file:
|
|
50
|
+
wrapper_file.write(wrapper_script)
|
|
51
|
+
wrapper_file_path = wrapper_file.name
|
|
52
|
+
|
|
53
|
+
hook_input_payload = json.dumps({"last_assistant_message": assistant_message})
|
|
54
|
+
try:
|
|
55
|
+
completed_process = subprocess.run(
|
|
56
|
+
[sys.executable, wrapper_file_path],
|
|
57
|
+
input=hook_input_payload,
|
|
58
|
+
capture_output=True,
|
|
59
|
+
text=True,
|
|
60
|
+
check=False,
|
|
61
|
+
)
|
|
62
|
+
finally:
|
|
63
|
+
os.unlink(wrapper_file_path)
|
|
64
|
+
return completed_process
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_user_facing_notice_importable_from_config_messages():
|
|
68
|
+
config_messages_path = os.path.join(_CONFIG_DIR, "messages.py")
|
|
69
|
+
specification = importlib.util.spec_from_file_location("messages", config_messages_path)
|
|
70
|
+
module = importlib.util.module_from_spec(specification)
|
|
71
|
+
specification.loader.exec_module(module)
|
|
72
|
+
|
|
73
|
+
assert module.USER_FACING_NOTICE == USER_FACING_NOTICE
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_hedging_message_emits_block_with_short_user_notice():
|
|
77
|
+
completed_process = run_hook_with_message(HEDGING_MESSAGE)
|
|
78
|
+
|
|
79
|
+
assert completed_process.returncode == 0
|
|
80
|
+
parsed_response = json.loads(completed_process.stdout)
|
|
81
|
+
|
|
82
|
+
assert parsed_response["decision"] == "block"
|
|
83
|
+
assert parsed_response["systemMessage"] == USER_FACING_NOTICE
|
|
84
|
+
assert parsed_response["suppressOutput"] is True
|
|
85
|
+
assert "likely" in parsed_response["reason"]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_hedging_reason_contains_not_installed_notice_when_skill_absent():
|
|
89
|
+
completed_process = run_hook_with_patched_search_paths(
|
|
90
|
+
HEDGING_MESSAGE,
|
|
91
|
+
["/nonexistent/path/one/SKILL.md", "/nonexistent/path/two/SKILL.md"],
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
assert completed_process.returncode == 0
|
|
95
|
+
parsed_response = json.loads(completed_process.stdout)
|
|
96
|
+
|
|
97
|
+
assert parsed_response["decision"] == "block"
|
|
98
|
+
assert "no research-mode skill installed" in parsed_response["reason"]
|
|
99
|
+
assert "verify with sources or reply" in parsed_response["reason"]
|
|
100
|
+
assert "SKILL.md" not in parsed_response["reason"]
|
|
101
|
+
assert RESEARCH_MODE_SKILL_BODY_MARKER not in parsed_response["reason"]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_hedging_reason_contains_skill_path_when_skill_present():
|
|
105
|
+
with tempfile.TemporaryDirectory() as skill_dir:
|
|
106
|
+
skill_file_path = os.path.join(skill_dir, "SKILL.md")
|
|
107
|
+
with open(skill_file_path, "w") as skill_file:
|
|
108
|
+
skill_file.write("# Research Mode Skill\n")
|
|
109
|
+
|
|
110
|
+
completed_process = run_hook_with_patched_search_paths(
|
|
111
|
+
HEDGING_MESSAGE,
|
|
112
|
+
["/nonexistent/path/SKILL.md", skill_file_path],
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
assert completed_process.returncode == 0
|
|
116
|
+
parsed_response = json.loads(completed_process.stdout)
|
|
117
|
+
|
|
118
|
+
assert parsed_response["decision"] == "block"
|
|
119
|
+
assert "SKILL.md" in parsed_response["reason"]
|
|
120
|
+
assert "no research-mode skill installed" not in parsed_response["reason"]
|
|
121
|
+
assert RESEARCH_MODE_SKILL_BODY_MARKER not in parsed_response["reason"]
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def test_clean_message_passes_through_with_no_output():
|
|
125
|
+
completed_process = run_hook_with_message(CLEAN_MESSAGE)
|
|
126
|
+
|
|
127
|
+
assert completed_process.returncode == 0
|
|
128
|
+
assert completed_process.stdout == ""
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def test_empty_message_passes_through_with_no_output():
|
|
132
|
+
completed_process = run_hook_with_message(EMPTY_MESSAGE)
|
|
133
|
+
|
|
134
|
+
assert completed_process.returncode == 0
|
|
135
|
+
assert completed_process.stdout == ""
|
|
@@ -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
|
-
|
|
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):
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# pragma: no-tdd-gate
|
package/package.json
CHANGED
|
@@ -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
|
|
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
|
|
76
|
-
|
|
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
|
|
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
|
|
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>
|
package/skills/bugteam/SKILL.md
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 <
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|