claude-dev-env 2.8.0 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +7 -1
- package/agents/clean-coder.md +9 -19
- package/agents/test_agent_frontmatter.py +26 -0
- package/docs/CODE_RULES.md +4 -2
- package/docs/references/CLAUDE.md +2 -2
- package/docs/references/advisor-tool.md +44 -6
- package/docs/references/team-advisor-skill.md +14 -8
- package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -0
- package/output-styles/CLAUDE.md +17 -0
- package/output-styles/caveman-agent.md +37 -0
- package/package.json +2 -1
- package/rules/code-standards.md +33 -7
- package/rules/eli11-replies.md +1 -1
- package/scripts/CLAUDE.md +2 -2
- package/scripts/dev_env_scripts_constants/CLAUDE.md +1 -1
- package/scripts/dev_env_scripts_constants/grok_run_ledger_constants.py +50 -0
- package/scripts/dev_env_scripts_constants/grok_worker_constants.py +104 -0
- package/scripts/grok_patch_artifacts.py +123 -0
- package/scripts/grok_run_ledger.py +318 -0
- package/scripts/spawn_grok_batch.py +553 -9
- package/scripts/test_grok_patch_artifacts.py +82 -0
- package/scripts/test_grok_run_ledger.py +116 -0
- package/scripts/test_spawn_grok_batch.py +295 -0
- package/skills/CLAUDE.md +4 -2
- package/skills/_shared/CLAUDE.md +37 -4
- package/skills/_shared/advisor/CLAUDE.md +9 -0
- package/skills/_shared/advisor/advisor-protocol.md +5 -0
- package/skills/_shared/advisor/scripts/README.md +9 -0
- package/skills/_shared/end-of-run-gotcha-recommendations.md +156 -0
- package/skills/_shared/pr-loop/CLAUDE.md +18 -1
- package/skills/_shared/pr-loop/audit-contract.md +5 -0
- package/skills/_shared/pr-loop/audit-reply-template.md +5 -0
- package/skills/_shared/pr-loop/code-rules-gate.md +5 -0
- package/skills/_shared/pr-loop/fix-protocol.md +5 -0
- package/skills/_shared/pr-loop/gh-payloads.md +5 -0
- package/skills/_shared/pr-loop/post-audit-thread-contract.md +5 -0
- package/skills/_shared/pr-loop/precatch-rubric.md +5 -0
- package/skills/_shared/pr-loop/scripts/CLAUDE.md +8 -1
- package/skills/_shared/pr-loop/scripts/RUNTIME_SCRIPTS.md +29 -0
- package/skills/_shared/pr-loop/state-schema.md +5 -0
- package/skills/_shared/pr-loop/worker-spawn.md +5 -0
- package/skills/e-code-review/SKILL.md +6 -1
- package/skills/e-code-review/reference/runner-selection.md +40 -0
- package/skills/e-code-review/scripts/e_code_review_scripts_constants/__init__.py +1 -0
- package/skills/e-code-review/scripts/e_code_review_scripts_constants/grok_code_review_constants.py +55 -0
- package/skills/e-code-review/scripts/grok_code_review.py +221 -0
- package/skills/e-code-review/scripts/test_grok_code_review.py +212 -0
- package/skills/grok-spawn/SKILL.md +5 -0
- package/skills/orchestrator/SKILL.md +5 -0
- package/skills/task-build/reference/tool-routing.md +3 -0
- package/skills/team-advisor/SKILL.md +23 -44
- package/system-prompts/software-engineer.xml +6 -3
- package/skills/test_markdown_link_integrity.py +0 -107
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# End-of-run gotcha recommendations
|
|
2
|
+
|
|
3
|
+
**Shared close-out** for every skill.
|
|
4
|
+
|
|
5
|
+
After the deliverable, recommend pasteable gotchas from issues **this run** hit.
|
|
6
|
+
|
|
7
|
+
## When
|
|
8
|
+
|
|
9
|
+
- **With issues:** end of work, after the deliverable.
|
|
10
|
+
- **Clean run:** end with the deliverable only.
|
|
11
|
+
|
|
12
|
+
## What counts
|
|
13
|
+
|
|
14
|
+
Keep only items that **bit this run**:
|
|
15
|
+
|
|
16
|
+
- Wrong **path, file, or config**
|
|
17
|
+
- **Command / CLI** failed or needed different flags
|
|
18
|
+
- **Auth, permission, or environment** blocked a step
|
|
19
|
+
- **Data shape** mismatch (empty set, wrong schema, bad field)
|
|
20
|
+
- **Tool, UI, or selector** missed the target
|
|
21
|
+
- **Timing** needed a retry or longer wait
|
|
22
|
+
- **Dependency or install** gap or wrong version
|
|
23
|
+
- **Output format** wrong for the next step
|
|
24
|
+
- A **workaround** required to finish
|
|
25
|
+
|
|
26
|
+
## What to produce
|
|
27
|
+
|
|
28
|
+
**Order (hard):**
|
|
29
|
+
|
|
30
|
+
1. Show findings in chat; categorize as P0-P3.
|
|
31
|
+
2. Then `AskUserQuestion`.
|
|
32
|
+
3. On confirm only, file via `issue-tracker`.
|
|
33
|
+
|
|
34
|
+
### Cold-reader frame
|
|
35
|
+
|
|
36
|
+
Assume zero context.
|
|
37
|
+
|
|
38
|
+
**Exactly two setup sentences**, each on its **own rendered line**:
|
|
39
|
+
|
|
40
|
+
1. What we were doing.
|
|
41
|
+
2. Why these notes matter.
|
|
42
|
+
|
|
43
|
+
### 1. Chat findings (always first)
|
|
44
|
+
|
|
45
|
+
#### Sentence law (HARD)
|
|
46
|
+
|
|
47
|
+
| Rule | Required |
|
|
48
|
+
|---|---|
|
|
49
|
+
| **One sentence per rendered line** | Exactly one terminal `.` `?` or `!` on that line |
|
|
50
|
+
| **No collapsed pairs** | A single markdown newline is **not** a line break — it joins sentences |
|
|
51
|
+
| **Force the break** | Blank line between every prose sentence, **or** one list item per sentence |
|
|
52
|
+
| **No clause glue** | No em-dash or semicolon joining two clauses |
|
|
53
|
+
| **One idea** | If you can split it, split it |
|
|
54
|
+
| **Heavy detail** | Prefer an **ASCII infographic** over a stack of sentences |
|
|
55
|
+
|
|
56
|
+
**Self-check before send:** paste the chat into a viewer that collapses soft breaks.
|
|
57
|
+
|
|
58
|
+
If two periods appear on one visual line, **split or diagram**.
|
|
59
|
+
|
|
60
|
+
#### Per gotcha (HARD shape)
|
|
61
|
+
|
|
62
|
+
| Part | Form |
|
|
63
|
+
|---|---|
|
|
64
|
+
| **Title** | Heading fragment only |
|
|
65
|
+
| **Broke** | One sentence alone on its rendered line |
|
|
66
|
+
| **Fix** | One sentence alone on its rendered line |
|
|
67
|
+
| **ASCII** | Required for path, flag, or before/after |
|
|
68
|
+
|
|
69
|
+
**Cap: two prose sentences per gotcha.**
|
|
70
|
+
|
|
71
|
+
```markdown
|
|
72
|
+
## Session close-out
|
|
73
|
+
|
|
74
|
+
We built phone-theme prompts from a project style catalog.
|
|
75
|
+
|
|
76
|
+
Keep these traps for the next run.
|
|
77
|
+
|
|
78
|
+
## Gotcha recommendations
|
|
79
|
+
|
|
80
|
+
### Wrong catalog folder
|
|
81
|
+
|
|
82
|
+
The style picker failed outside the project.
|
|
83
|
+
|
|
84
|
+
Run from the project root with data/midjourney_sref_catalog.json.
|
|
85
|
+
|
|
86
|
+
```text
|
|
87
|
+
wrong folder --> fail
|
|
88
|
+
project root --> data/midjourney_sref_catalog.json
|
|
89
|
+
```
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Wrong (soft break joins sentences into one line):**
|
|
93
|
+
|
|
94
|
+
```markdown
|
|
95
|
+
The style picker failed outside the project.
|
|
96
|
+
Run from the project root with data/midjourney_sref_catalog.json.
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Renders as one line with **two** sentences.
|
|
100
|
+
|
|
101
|
+
**Right (blank line forces two lines):**
|
|
102
|
+
|
|
103
|
+
```markdown
|
|
104
|
+
The style picker failed outside the project.
|
|
105
|
+
|
|
106
|
+
Run from the project root with data/midjourney_sref_catalog.json.
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Right (list items):**
|
|
110
|
+
|
|
111
|
+
```markdown
|
|
112
|
+
- The style picker failed outside the project.
|
|
113
|
+
- Run from the project root with data/midjourney_sref_catalog.json.
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### 2. Issue offer (`AskUserQuestion`)
|
|
117
|
+
|
|
118
|
+
Only after the chat block.
|
|
119
|
+
|
|
120
|
+
| Field | Limit |
|
|
121
|
+
|---|---|
|
|
122
|
+
| **Question** | One short sentence |
|
|
123
|
+
| **Header** | `File issues` |
|
|
124
|
+
| **label** | Plain name |
|
|
125
|
+
| **description** | One short sentence |
|
|
126
|
+
|
|
127
|
+
Detail and ASCII stay in chat.
|
|
128
|
+
|
|
129
|
+
**On confirm:** file via `issue-tracker`.
|
|
130
|
+
|
|
131
|
+
Cold-reader issue body.
|
|
132
|
+
|
|
133
|
+
Clean run: skip.
|
|
134
|
+
|
|
135
|
+
## Built-in skill gotcha
|
|
136
|
+
|
|
137
|
+
**Two sentences on one rendered line.**
|
|
138
|
+
|
|
139
|
+
Markdown soft-breaks join prose.
|
|
140
|
+
|
|
141
|
+
Review the close-out before send.
|
|
142
|
+
|
|
143
|
+
Split with a blank line or a list item.
|
|
144
|
+
|
|
145
|
+
Or move the detail into an ASCII infographic.
|
|
146
|
+
|
|
147
|
+
## Rules
|
|
148
|
+
|
|
149
|
+
- Cold-reader first.
|
|
150
|
+
- Chat first, then ask.
|
|
151
|
+
- One sentence per **rendered** line.
|
|
152
|
+
- Blank line or list item between prose sentences.
|
|
153
|
+
- Two prose sentences per gotcha max.
|
|
154
|
+
- ASCII for path, flag, or before/after.
|
|
155
|
+
- File only on confirm.
|
|
156
|
+
- Lived issues only.
|
|
@@ -12,7 +12,24 @@ those skills invoke during each loop tick.
|
|
|
12
12
|
| `prompts/` | XML agent prompt templates. |
|
|
13
13
|
| `scripts/` | Python scripts for loop state management, prompt building, outcome recording, path resolution, pacer selection, and preflight checks. |
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## Canonical-path stubs (`~/.claude/_shared/pr-loop/`)
|
|
16
|
+
|
|
17
|
+
Open a stub, then load the `@` target:
|
|
18
|
+
|
|
19
|
+
| Stub here | Load |
|
|
20
|
+
|---|---|
|
|
21
|
+
| `audit-contract.md` | `@~/.claude/_shared/pr-loop/audit-contract.md` |
|
|
22
|
+
| `audit-reply-template.md` | `@~/.claude/_shared/pr-loop/audit-reply-template.md` |
|
|
23
|
+
| `code-rules-gate.md` | `@~/.claude/_shared/pr-loop/code-rules-gate.md` |
|
|
24
|
+
| `fix-protocol.md` | `@~/.claude/_shared/pr-loop/fix-protocol.md` |
|
|
25
|
+
| `gh-payloads.md` | `@~/.claude/_shared/pr-loop/gh-payloads.md` |
|
|
26
|
+
| `post-audit-thread-contract.md` | `@~/.claude/_shared/pr-loop/post-audit-thread-contract.md` |
|
|
27
|
+
| `precatch-rubric.md` | `@~/.claude/_shared/pr-loop/precatch-rubric.md` |
|
|
28
|
+
| `state-schema.md` | `@~/.claude/_shared/pr-loop/state-schema.md` |
|
|
29
|
+
| `worker-spawn.md` | `@~/.claude/_shared/pr-loop/worker-spawn.md` |
|
|
30
|
+
| `scripts/RUNTIME_SCRIPTS.md` | `@~/.claude/_shared/pr-loop/scripts/` |
|
|
31
|
+
|
|
32
|
+
## Key files (live in this tree)
|
|
16
33
|
|
|
17
34
|
| File | Role |
|
|
18
35
|
|---|---|
|
|
@@ -2,7 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
Python scripts that run the PR audit-fix loop at runtime. Both `bugteam` and `pr-converge` invoke these scripts during each loop tick.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Two script homes
|
|
6
|
+
|
|
7
|
+
| Home | Path |
|
|
8
|
+
|---|---|
|
|
9
|
+
| **Skill-local** (this folder) | Converge helpers: `build_*_prompt.py`, `init_loop_state.py`, `portable_converge_driver.py`, … |
|
|
10
|
+
| **Runtime** (top-level shared) | Gate / preflight / review helpers — see `RUNTIME_SCRIPTS.md` → `@~/.claude/_shared/pr-loop/scripts/` |
|
|
11
|
+
|
|
12
|
+
## Key files (skill-local)
|
|
6
13
|
|
|
7
14
|
| File | Role |
|
|
8
15
|
|---|---|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# PR-loop runtime scripts
|
|
2
|
+
|
|
3
|
+
**Canonical home:**
|
|
4
|
+
|
|
5
|
+
@~/.claude/_shared/pr-loop/scripts/code_rules_gate.py
|
|
6
|
+
|
|
7
|
+
@~/.claude/_shared/pr-loop/scripts/preflight.py
|
|
8
|
+
|
|
9
|
+
@~/.claude/_shared/pr-loop/scripts/post_audit_thread.py
|
|
10
|
+
|
|
11
|
+
@~/.claude/_shared/pr-loop/scripts/post_audit_review.py
|
|
12
|
+
|
|
13
|
+
@~/.claude/_shared/pr-loop/scripts/gh_util.py
|
|
14
|
+
|
|
15
|
+
@~/.claude/_shared/pr-loop/scripts/reviews_disabled.py
|
|
16
|
+
|
|
17
|
+
@~/.claude/_shared/pr-loop/scripts/reviewer_availability.py
|
|
18
|
+
|
|
19
|
+
@~/.claude/_shared/pr-loop/scripts/copilot_quota.py
|
|
20
|
+
|
|
21
|
+
@~/.claude/_shared/pr-loop/scripts/grant_project_claude_permissions.py
|
|
22
|
+
|
|
23
|
+
@~/.claude/_shared/pr-loop/scripts/revoke_project_claude_permissions.py
|
|
24
|
+
|
|
25
|
+
@~/.claude/_shared/pr-loop/scripts/verify_review.py
|
|
26
|
+
|
|
27
|
+
@~/.claude/_shared/pr-loop/scripts/README.md
|
|
28
|
+
|
|
29
|
+
Skill-local converge helpers (`build_*_prompt.py`, `init_loop_state.py`, and siblings) live in this `scripts/` folder. Runtime gate, preflight, and review helpers live under `~/.claude/_shared/pr-loop/scripts/`.
|
|
@@ -46,8 +46,13 @@ Triggers: `/e-code-review <level> [--fix] [loop]`. `<level>` is `low`, `medium`,
|
|
|
46
46
|
| `reference/xhigh.md` | xhigh review procedure — 10 angles, 1-vote verify, gap sweep |
|
|
47
47
|
| `reference/fix.md` | Fix application, code-rules gate, skip logging, outcome reporting |
|
|
48
48
|
| `reference/loop.md` | Repeat review/fix rounds until clean |
|
|
49
|
+
| `scripts/grok_code_review.py` | Grok medium-review discovery and verification |
|
|
50
|
+
| `scripts/test_grok_code_review.py` | Behavioral tests for the Grok medium-review module |
|
|
51
|
+
| `scripts/e_code_review_scripts_constants/` | Skill-local constants (unique package name; avoids bare `config` import shadow) |
|
|
52
|
+
| `reference/runner-selection.md` | Runner selection map |
|
|
49
53
|
|
|
50
54
|
## Folder map
|
|
51
55
|
|
|
52
56
|
- `SKILL.md` — route and dispatch.
|
|
53
|
-
- `reference/` —
|
|
57
|
+
- `reference/` — level procedures, fix/loop, runner selection.
|
|
58
|
+
- `scripts/` — medium-review module, tests, `e_code_review_scripts_constants/`.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Runner selection (medium review)
|
|
2
|
+
|
|
3
|
+
How `/e-code-review medium` (and loop) should pick a **harness runner** for
|
|
4
|
+
finder/verify work. This file is the map; `SKILL.md` only points here.
|
|
5
|
+
|
|
6
|
+
## What lives here
|
|
7
|
+
|
|
8
|
+
| Topic | Content |
|
|
9
|
+
|---|---|
|
|
10
|
+
| Goal | One skill surface; many possible runners (Grok, Codex, Claude headless, …) |
|
|
11
|
+
| Today | `scripts/grok_code_review.py` is the Grok medium orchestration module |
|
|
12
|
+
| Target | A thin selector chooses a runner by availability and task flags |
|
|
13
|
+
| Non-goals | Reimplementing every harness inside this skill |
|
|
14
|
+
|
|
15
|
+
## Today
|
|
16
|
+
|
|
17
|
+
- Medium procedure: `reference/medium.md` (angles, verify, output fields).
|
|
18
|
+
- Grok orchestration API: `scripts/grok_code_review.py` (discovery, dedupe,
|
|
19
|
+
retain, head-drift).
|
|
20
|
+
- Constants: `scripts/e_code_review_scripts_constants/`.
|
|
21
|
+
|
|
22
|
+
## Target shape (tracked separately)
|
|
23
|
+
|
|
24
|
+
1. **Runner protocol** — shared inputs (diff base, head, angles) and outputs
|
|
25
|
+
(candidates, verdicts, severities).
|
|
26
|
+
2. **Detection** — which runners are installed/configured (PATH, skills,
|
|
27
|
+
account chain).
|
|
28
|
+
3. **Selection** — pick one runner without baking a single vendor into
|
|
29
|
+
`SKILL.md` process steps.
|
|
30
|
+
4. **Adapters** — thin wrappers: Grok (current module), Codex, Claude, …
|
|
31
|
+
|
|
32
|
+
Open issue for implementation work should link this file and keep PRs small
|
|
33
|
+
(Google small-CL practice: one concern per PR).
|
|
34
|
+
|
|
35
|
+
## Related always-on docs
|
|
36
|
+
|
|
37
|
+
- Skill hub: `../SKILL.md` (levels, fix, loop — not runner internals).
|
|
38
|
+
- Medium procedure: `medium.md`.
|
|
39
|
+
- Shared worker spawn (when applicable):
|
|
40
|
+
`packages/claude-dev-env/_shared/pr-loop/worker-spawn.md` in the package tree.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Constants package for e-code-review scripts."""
|
package/skills/e-code-review/scripts/e_code_review_scripts_constants/grok_code_review_constants.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Named constants for Grok medium-review discovery and verification."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
MEDIUM_REVIEW_SCHEMA_VERSION: str = "1.0.0"
|
|
6
|
+
"""Schema version for medium-review run documents."""
|
|
7
|
+
|
|
8
|
+
MEDIUM_REVIEW_FINDER_COUNT: int = 8
|
|
9
|
+
"""Exactly eight finder angles execute per medium review head."""
|
|
10
|
+
|
|
11
|
+
ALL_MEDIUM_FINDER_ANGLES: tuple[str, ...] = (
|
|
12
|
+
"correctness",
|
|
13
|
+
"security",
|
|
14
|
+
"code_rules",
|
|
15
|
+
"tests",
|
|
16
|
+
"docs",
|
|
17
|
+
"concurrency",
|
|
18
|
+
"api_contracts",
|
|
19
|
+
"regressions",
|
|
20
|
+
)
|
|
21
|
+
"""Named finder angles for one medium-review discovery batch."""
|
|
22
|
+
|
|
23
|
+
VERDICT_CONFIRMED: str = "CONFIRMED"
|
|
24
|
+
"""Verification retained a concrete failure scenario."""
|
|
25
|
+
|
|
26
|
+
VERDICT_PLAUSIBLE: str = "PLAUSIBLE"
|
|
27
|
+
"""Verification could not refute the finding."""
|
|
28
|
+
|
|
29
|
+
VERDICT_REFUTED: str = "REFUTED"
|
|
30
|
+
"""Verification rejected the finding."""
|
|
31
|
+
|
|
32
|
+
ALL_VERIFICATION_VERDICTS: frozenset[str] = frozenset(
|
|
33
|
+
{VERDICT_CONFIRMED, VERDICT_PLAUSIBLE, VERDICT_REFUTED}
|
|
34
|
+
)
|
|
35
|
+
"""Legal verification verdict tokens."""
|
|
36
|
+
|
|
37
|
+
SEVERITY_BLOCKER: str = "blocker"
|
|
38
|
+
SEVERITY_HIGH: str = "high"
|
|
39
|
+
SEVERITY_MEDIUM: str = "medium"
|
|
40
|
+
SEVERITY_LOW: str = "low"
|
|
41
|
+
SEVERITY_NIT: str = "nit"
|
|
42
|
+
|
|
43
|
+
ALL_SEVERITIES: frozenset[str] = frozenset(
|
|
44
|
+
{
|
|
45
|
+
SEVERITY_BLOCKER,
|
|
46
|
+
SEVERITY_HIGH,
|
|
47
|
+
SEVERITY_MEDIUM,
|
|
48
|
+
SEVERITY_LOW,
|
|
49
|
+
SEVERITY_NIT,
|
|
50
|
+
}
|
|
51
|
+
)
|
|
52
|
+
"""Legal severity tokens on retained findings."""
|
|
53
|
+
|
|
54
|
+
UTF8_ENCODING: str = "utf-8"
|
|
55
|
+
"""Text encoding for review artifacts."""
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Grok medium-review discovery and verification orchestration.
|
|
3
|
+
|
|
4
|
+
Dispatches exactly eight named finder angles at one target head, binds each
|
|
5
|
+
finder to an isolated identity (worktree path, leader socket, advisor session),
|
|
6
|
+
deduplicates candidates, verifies them, and retains CONFIRMED plus unresolved
|
|
7
|
+
PLAUSIBLE findings with severity. Head drift rejects the whole batch.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
|
|
14
|
+
from e_code_review_scripts_constants.grok_code_review_constants import (
|
|
15
|
+
ALL_MEDIUM_FINDER_ANGLES,
|
|
16
|
+
ALL_SEVERITIES,
|
|
17
|
+
ALL_VERIFICATION_VERDICTS,
|
|
18
|
+
MEDIUM_REVIEW_FINDER_COUNT,
|
|
19
|
+
MEDIUM_REVIEW_SCHEMA_VERSION,
|
|
20
|
+
VERDICT_CONFIRMED,
|
|
21
|
+
VERDICT_PLAUSIBLE,
|
|
22
|
+
VERDICT_REFUTED,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class FinderCandidate:
|
|
28
|
+
"""One candidate finding from a discovery angle."""
|
|
29
|
+
|
|
30
|
+
angle: str
|
|
31
|
+
file_path: str
|
|
32
|
+
line_number: int
|
|
33
|
+
mechanism: str
|
|
34
|
+
scenario: str
|
|
35
|
+
worktree_path: str
|
|
36
|
+
leader_socket: str
|
|
37
|
+
advisor_session_id: str
|
|
38
|
+
reviewed_head: str
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True)
|
|
42
|
+
class VerifiedFinding:
|
|
43
|
+
"""A retained finding after verification."""
|
|
44
|
+
|
|
45
|
+
file_path: str
|
|
46
|
+
line_number: int
|
|
47
|
+
mechanism: str
|
|
48
|
+
scenario: str
|
|
49
|
+
verdict: str
|
|
50
|
+
severity: str
|
|
51
|
+
reviewed_head: str
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class MediumReviewBatch:
|
|
56
|
+
"""Result of one medium-review run at a single head."""
|
|
57
|
+
|
|
58
|
+
schema_version: str = MEDIUM_REVIEW_SCHEMA_VERSION
|
|
59
|
+
target_head: str = ""
|
|
60
|
+
diff_base: str = ""
|
|
61
|
+
all_finder_angles: tuple[str, ...] = ALL_MEDIUM_FINDER_ANGLES
|
|
62
|
+
all_retained_findings: list[VerifiedFinding] = field(default_factory=list)
|
|
63
|
+
is_rejected: bool = False
|
|
64
|
+
rejection_reason: str | None = None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def require_exact_finder_set(all_angles: tuple[str, ...] | list[str]) -> None:
|
|
68
|
+
"""Require exactly the eight named finder angles.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
all_angles: Angle names present in a run.
|
|
72
|
+
|
|
73
|
+
Raises:
|
|
74
|
+
ValueError: When the set is not exactly the eight angles.
|
|
75
|
+
"""
|
|
76
|
+
angle_set = set(all_angles)
|
|
77
|
+
expected = set(ALL_MEDIUM_FINDER_ANGLES)
|
|
78
|
+
if angle_set != expected or len(all_angles) != MEDIUM_REVIEW_FINDER_COUNT:
|
|
79
|
+
raise ValueError(
|
|
80
|
+
f"finder angles must be exactly {MEDIUM_REVIEW_FINDER_COUNT}: "
|
|
81
|
+
f"got {sorted(angle_set)}"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def deduplicate_candidates(
|
|
86
|
+
all_candidates: list[FinderCandidate],
|
|
87
|
+
) -> list[FinderCandidate]:
|
|
88
|
+
"""Keep the most concrete scenario per file/line/mechanism.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
all_candidates: Raw finder outputs.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Deduplicated candidates preserving the longest scenario text.
|
|
95
|
+
"""
|
|
96
|
+
best_by_key: dict[tuple[str, int, str], FinderCandidate] = {}
|
|
97
|
+
for each_candidate in all_candidates:
|
|
98
|
+
key = (
|
|
99
|
+
each_candidate.file_path,
|
|
100
|
+
each_candidate.line_number,
|
|
101
|
+
each_candidate.mechanism,
|
|
102
|
+
)
|
|
103
|
+
existing = best_by_key.get(key)
|
|
104
|
+
if existing is None or len(each_candidate.scenario) > len(existing.scenario):
|
|
105
|
+
best_by_key[key] = each_candidate
|
|
106
|
+
return list(best_by_key.values())
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def retain_verified_findings(
|
|
110
|
+
*,
|
|
111
|
+
all_candidates: list[FinderCandidate],
|
|
112
|
+
verdict_by_key: dict[tuple[str, int, str], str],
|
|
113
|
+
severity_by_key: dict[tuple[str, int, str], str],
|
|
114
|
+
) -> list[VerifiedFinding]:
|
|
115
|
+
"""Retain CONFIRMED and unresolved PLAUSIBLE findings with severity.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
all_candidates: Deduplicated candidates.
|
|
119
|
+
verdict_by_key: Verification verdict per file/line/mechanism.
|
|
120
|
+
severity_by_key: Severity per file/line/mechanism.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Retained findings.
|
|
124
|
+
|
|
125
|
+
Raises:
|
|
126
|
+
ValueError: When a retained finding lacks a legal severity or verdict.
|
|
127
|
+
"""
|
|
128
|
+
all_retained: list[VerifiedFinding] = []
|
|
129
|
+
for each_candidate in all_candidates:
|
|
130
|
+
key = (
|
|
131
|
+
each_candidate.file_path,
|
|
132
|
+
each_candidate.line_number,
|
|
133
|
+
each_candidate.mechanism,
|
|
134
|
+
)
|
|
135
|
+
verdict = verdict_by_key.get(key)
|
|
136
|
+
if verdict not in ALL_VERIFICATION_VERDICTS:
|
|
137
|
+
raise ValueError(f"missing or illegal verdict for {key}")
|
|
138
|
+
if verdict == VERDICT_REFUTED:
|
|
139
|
+
continue
|
|
140
|
+
if verdict not in {VERDICT_CONFIRMED, VERDICT_PLAUSIBLE}:
|
|
141
|
+
continue
|
|
142
|
+
severity = severity_by_key.get(key)
|
|
143
|
+
if severity not in ALL_SEVERITIES:
|
|
144
|
+
raise ValueError(f"missing or illegal severity for {key}")
|
|
145
|
+
all_retained.append(
|
|
146
|
+
VerifiedFinding(
|
|
147
|
+
file_path=each_candidate.file_path,
|
|
148
|
+
line_number=each_candidate.line_number,
|
|
149
|
+
mechanism=each_candidate.mechanism,
|
|
150
|
+
scenario=each_candidate.scenario,
|
|
151
|
+
verdict=verdict,
|
|
152
|
+
severity=severity,
|
|
153
|
+
reviewed_head=each_candidate.reviewed_head,
|
|
154
|
+
)
|
|
155
|
+
)
|
|
156
|
+
return all_retained
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def run_medium_review(
|
|
160
|
+
*,
|
|
161
|
+
target_head: str,
|
|
162
|
+
diff_base: str,
|
|
163
|
+
all_finder_candidates: list[FinderCandidate],
|
|
164
|
+
verdict_by_key: dict[tuple[str, int, str], str],
|
|
165
|
+
severity_by_key: dict[tuple[str, int, str], str],
|
|
166
|
+
live_head: str,
|
|
167
|
+
is_any_advisor_blocked: bool = False,
|
|
168
|
+
) -> MediumReviewBatch:
|
|
169
|
+
"""Run discovery and verification for one medium-review head.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
target_head: Reviewed head OID.
|
|
173
|
+
diff_base: Diff base OID.
|
|
174
|
+
all_finder_candidates: Outputs from the eight finder angles.
|
|
175
|
+
verdict_by_key: Verification results keyed by file/line/mechanism.
|
|
176
|
+
severity_by_key: Severities for retained findings.
|
|
177
|
+
live_head: Live head OID; must match target_head.
|
|
178
|
+
is_any_advisor_blocked: True when any finder ended advisor_blocked.
|
|
179
|
+
|
|
180
|
+
Raises:
|
|
181
|
+
ValueError: When finder angles are incomplete or duplicated.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
The medium-review batch (possibly rejected).
|
|
185
|
+
"""
|
|
186
|
+
batch = MediumReviewBatch(target_head=target_head, diff_base=diff_base)
|
|
187
|
+
if is_any_advisor_blocked:
|
|
188
|
+
batch.is_rejected = True
|
|
189
|
+
batch.rejection_reason = "advisor_blocked"
|
|
190
|
+
return batch
|
|
191
|
+
if live_head != target_head:
|
|
192
|
+
batch.is_rejected = True
|
|
193
|
+
batch.rejection_reason = "head_drift"
|
|
194
|
+
return batch
|
|
195
|
+
for each_candidate in all_finder_candidates:
|
|
196
|
+
if each_candidate.reviewed_head != target_head:
|
|
197
|
+
batch.is_rejected = True
|
|
198
|
+
batch.rejection_reason = "finder_head_mismatch"
|
|
199
|
+
return batch
|
|
200
|
+
all_angles = [each.angle for each in all_finder_candidates]
|
|
201
|
+
all_sockets = {each.leader_socket for each in all_finder_candidates}
|
|
202
|
+
all_sessions = {each.advisor_session_id for each in all_finder_candidates}
|
|
203
|
+
all_worktrees = {each.worktree_path for each in all_finder_candidates}
|
|
204
|
+
if (
|
|
205
|
+
len(all_sockets) != len(all_finder_candidates)
|
|
206
|
+
or len(all_sessions) != len(all_finder_candidates)
|
|
207
|
+
or len(all_worktrees) != len(all_finder_candidates)
|
|
208
|
+
):
|
|
209
|
+
batch.is_rejected = True
|
|
210
|
+
batch.rejection_reason = "non_unique_finder_identity"
|
|
211
|
+
return batch
|
|
212
|
+
require_exact_finder_set(tuple(sorted(set(all_angles))))
|
|
213
|
+
if len(set(all_angles)) != MEDIUM_REVIEW_FINDER_COUNT:
|
|
214
|
+
raise ValueError("each finder angle must run exactly once")
|
|
215
|
+
deduped = deduplicate_candidates(all_finder_candidates)
|
|
216
|
+
batch.all_retained_findings = retain_verified_findings(
|
|
217
|
+
all_candidates=deduped,
|
|
218
|
+
verdict_by_key=verdict_by_key,
|
|
219
|
+
severity_by_key=severity_by_key,
|
|
220
|
+
)
|
|
221
|
+
return batch
|