@mechanai/deepreview 1.0.1 → 2.0.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/.opencode/plugins/deepreview.ts +45 -0
- package/README.md +48 -100
- package/agents/deepreview-review-formatter.md +66 -0
- package/commands/_deepreview-pipeline.md +57 -0
- package/commands/deepreview-pr-review.md +61 -0
- package/package.json +30 -5
- package/src/diff-classifier.test.ts +45 -0
- package/src/diff-classifier.ts +63 -0
- package/src/graphql.ts +183 -0
- package/src/parse-threads.test.ts +58 -0
- package/src/parse-threads.ts +117 -0
- package/src/post-review.test.ts +52 -0
- package/src/post-review.ts +325 -0
- package/src/review-api.ts +253 -0
- package/src/review-helpers.ts +65 -0
- package/src/cli.js +0 -103
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type Plugin, type PluginInput, tool } from "@opencode-ai/plugin";
|
|
2
|
+
import { postReview } from "@mechanai/deepreview/api";
|
|
3
|
+
|
|
4
|
+
// oxlint-disable-next-line require-await -- Why: Plugin type signature requires async but this plugin has no async initialization
|
|
5
|
+
export const server: Plugin = async (_input: PluginInput) => {
|
|
6
|
+
return {
|
|
7
|
+
tool: {
|
|
8
|
+
"deepreview-post-review": tool({
|
|
9
|
+
description:
|
|
10
|
+
"Post a GitHub PR review from a threads.md file. " +
|
|
11
|
+
"Parses findings, classifies them into line-level/file-level/review-body " +
|
|
12
|
+
"tiers based on the PR diff, and submits via GitHub GraphQL API. " +
|
|
13
|
+
"Returns a summary of what was posted.",
|
|
14
|
+
args: {
|
|
15
|
+
threads_path: tool.schema
|
|
16
|
+
.string()
|
|
17
|
+
.describe("Relative path to the threads.md file (from workspace root)"),
|
|
18
|
+
pr_number: tool.schema.number().int().positive().describe("Pull request number"),
|
|
19
|
+
dry_run: tool.schema
|
|
20
|
+
.boolean()
|
|
21
|
+
.optional()
|
|
22
|
+
.describe("Print what would be posted without submitting"),
|
|
23
|
+
skip_ids: tool.schema
|
|
24
|
+
.array(tool.schema.string())
|
|
25
|
+
.optional()
|
|
26
|
+
.describe("Finding IDs to skip (for retrying partial failures)"),
|
|
27
|
+
},
|
|
28
|
+
async execute(args, context) {
|
|
29
|
+
try {
|
|
30
|
+
const result = await postReview({
|
|
31
|
+
threadsPath: args.threads_path,
|
|
32
|
+
prNumber: args.pr_number,
|
|
33
|
+
dryRun: args.dry_run ?? false,
|
|
34
|
+
skipIds: args.skip_ids,
|
|
35
|
+
cwd: context.directory,
|
|
36
|
+
});
|
|
37
|
+
return result.summary;
|
|
38
|
+
} catch (err) {
|
|
39
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
}),
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
};
|
package/README.md
CHANGED
|
@@ -4,132 +4,80 @@ Multi-agent parallel code/spec review for [OpenCode](https://opencode.ai). Spawn
|
|
|
4
4
|
review agents, cross-validates findings, synthesizes results, and produces an actionable
|
|
5
5
|
implementation plan.
|
|
6
6
|
|
|
7
|
-
## How it works
|
|
8
|
-
|
|
9
|
-
### Code review (`/deepreview`)
|
|
10
|
-
|
|
11
|
-
```
|
|
12
|
-
Stage 1: 5 parallel reviewers (correctness, security, architecture, docs, compatibility)
|
|
13
|
-
Stage 2: 5 parallel cross-validators (try to disprove each finding)
|
|
14
|
-
Stage 3: Synthesizer (deduplicate, rank, produce unified report)
|
|
15
|
-
Stage 4: Planner (write exact code fixes)
|
|
16
|
-
Stage 5: Applier (apply fixes — user-gated)
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
### Spec/plan review (`/deepreview-spec`)
|
|
20
|
-
|
|
21
|
-
```
|
|
22
|
-
Stage 1: 5 parallel reviewers (completeness, consistency, feasibility, docs, architecture)
|
|
23
|
-
Stage 2: 5 parallel cross-validators (try to disprove each finding)
|
|
24
|
-
Stage 3: Synthesizer (deduplicate, rank, produce unified report)
|
|
25
|
-
Stage 4: Planner (write spec/plan fixes, not code fixes)
|
|
26
|
-
Stage 5: Applier (apply fixes — user-gated)
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
All communication between stages happens via files on disk. The orchestrator never reads
|
|
30
|
-
review content into its own context, keeping token usage minimal.
|
|
31
|
-
|
|
32
7
|
## Install
|
|
33
8
|
|
|
34
|
-
|
|
35
|
-
npx @mechanai/deepreview install
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
This copies agent and command files into `~/.config/opencode/` and adds `.ai/deepreview/`
|
|
39
|
-
to the local `.gitignore` (where review output is written). Run it again after updating
|
|
40
|
-
the package to sync changes.
|
|
9
|
+
Add to your `opencode.json` (project-level or global):
|
|
41
10
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
11
|
+
```jsonc
|
|
12
|
+
{
|
|
13
|
+
"plugin": ["@mechanai/deepreview"],
|
|
14
|
+
}
|
|
46
15
|
```
|
|
47
16
|
|
|
48
|
-
|
|
17
|
+
OpenCode installs the package automatically at startup.
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
49
20
|
|
|
50
|
-
```bash
|
|
51
|
-
npx @mechanai/deepreview uninstall
|
|
52
21
|
```
|
|
22
|
+
/deepreview # Review current branch vs main
|
|
23
|
+
/deepreview 123 # Review PR #123
|
|
24
|
+
/deepreview file1.ts file2.ts # Review specific files
|
|
53
25
|
|
|
54
|
-
|
|
26
|
+
/deepreview-loop # Review + fix loop (repeats until clean or 5 iterations)
|
|
27
|
+
/deepreview-loop 123 # Same, targeting a PR
|
|
55
28
|
|
|
56
|
-
|
|
29
|
+
/deepreview-pr-review 123 # Review PR and post findings as a pending GitHub review
|
|
57
30
|
|
|
31
|
+
/deepreview-spec spec.md # Spec-focused review (completeness, consistency, feasibility)
|
|
32
|
+
/deepreview-spec-loop spec.md # Spec review + fix loop
|
|
58
33
|
```
|
|
59
|
-
/deepreview # Review current branch vs main
|
|
60
|
-
/deepreview 123 # Review PR #123
|
|
61
|
-
/deepreview path/to/spec.md # Review a spec or plan
|
|
62
|
-
/deepreview doc1.md doc2.md # Review multiple files
|
|
63
34
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
35
|
+
All commands accept a branch diff, PR number, or file path(s). The `-loop` variants
|
|
36
|
+
apply fixes automatically and re-review until no findings remain. Pauses on plateaus
|
|
37
|
+
(same finding persists across iterations).
|
|
67
38
|
|
|
68
|
-
|
|
69
|
-
/deepreview-spec a.md b.md # Review multiple spec/plan files
|
|
39
|
+
## Pipeline
|
|
70
40
|
|
|
71
|
-
|
|
72
|
-
|
|
41
|
+
```mermaid
|
|
42
|
+
graph LR
|
|
43
|
+
A[5 Reviewers] --> B[5 Validators]
|
|
44
|
+
B --> C[Synthesizer]
|
|
45
|
+
C --> D[Planner]
|
|
46
|
+
D --> E[Applier]
|
|
73
47
|
```
|
|
74
48
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
extendable). Pauses on decision deadlocks (same finding persists across iterations).
|
|
49
|
+
Stages communicate via files on disk — the orchestrator never reads review content into
|
|
50
|
+
its own context, keeping token usage minimal.
|
|
78
51
|
|
|
79
|
-
|
|
80
|
-
fixes) each iteration. Includes plateau detection to stop when findings oscillate rather
|
|
81
|
-
than converge.
|
|
52
|
+
### Review agents
|
|
82
53
|
|
|
83
|
-
|
|
84
|
-
|
|
54
|
+
| Agent | Code review | Spec review |
|
|
55
|
+
| --------------------------- | -------------------------------------- | -------------------------------------------- |
|
|
56
|
+
| correctness / completeness | Logic bugs, edge cases, error handling | Gaps, missing edge cases, undefined behavior |
|
|
57
|
+
| security / consistency | Vulnerabilities, performance | Contradictions, name mismatches, type drift |
|
|
58
|
+
| architecture | Patterns, coupling, complexity | Patterns, coupling, complexity |
|
|
59
|
+
| docs | Comment quality, stale claims | Comment quality, stale claims |
|
|
60
|
+
| compatibility / feasibility | Breaking changes, API contracts | Implicit dependencies, can it be built |
|
|
85
61
|
|
|
86
62
|
## Requirements
|
|
87
63
|
|
|
88
64
|
- [OpenCode](https://opencode.ai)
|
|
89
|
-
- `git`
|
|
90
|
-
- `gh` CLI (only
|
|
65
|
+
- `git`
|
|
66
|
+
- `gh` CLI (only for PR commands)
|
|
91
67
|
|
|
92
|
-
|
|
68
|
+
> [!NOTE]
|
|
69
|
+
> If upgrading from the old `npx @anthropic/deepreview install` workflow, remove
|
|
70
|
+
> `~/.config/opencode/agents/deepreview*` files — they are no longer used.
|
|
93
71
|
|
|
94
|
-
|
|
72
|
+
## Development
|
|
95
73
|
|
|
96
|
-
|
|
97
|
-
| ------------- | ----------------------------------------------------- |
|
|
98
|
-
| correctness | Logic bugs, edge cases, error handling, missing tests |
|
|
99
|
-
| security | Vulnerabilities, auth issues, performance bottlenecks |
|
|
100
|
-
| architecture | Patterns, coupling, abstractions, complexity |
|
|
101
|
-
| docs | Comment quality, stale claims, duplicate content |
|
|
102
|
-
| compatibility | Breaking changes, API contract violations |
|
|
74
|
+
This project uses [Bun](https://bun.sh/) as its runtime and package manager.
|
|
103
75
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
| spec-consistency | Contradictions, name mismatches, type drift |
|
|
110
|
-
| spec-feasibility | Can it be built, implicit dependencies, complexity |
|
|
111
|
-
| docs | Comment quality, stale claims, duplicate content |
|
|
112
|
-
| architecture | Patterns, coupling, abstractions, complexity |
|
|
113
|
-
|
|
114
|
-
## Output
|
|
115
|
-
|
|
116
|
-
All review artifacts are saved to `.ai/deepreview/<branch-or-PR>-<date>/`:
|
|
117
|
-
|
|
118
|
-
```
|
|
119
|
-
.ai/deepreview/feature-xyz-2025-05-10/
|
|
120
|
-
├── diff.txt
|
|
121
|
-
├── review-correctness.md
|
|
122
|
-
├── review-security.md
|
|
123
|
-
├── review-architecture.md
|
|
124
|
-
├── review-docs.md
|
|
125
|
-
├── review-compatibility.md
|
|
126
|
-
├── validated-correctness.md
|
|
127
|
-
├── validated-security.md
|
|
128
|
-
├── validated-architecture.md
|
|
129
|
-
├── validated-docs.md
|
|
130
|
-
├── validated-compatibility.md
|
|
131
|
-
├── synthesis.md
|
|
132
|
-
└── implementation-plan.md
|
|
76
|
+
```bash
|
|
77
|
+
bun install
|
|
78
|
+
mise run test
|
|
79
|
+
mise run lint
|
|
80
|
+
mise run fmt
|
|
133
81
|
```
|
|
134
82
|
|
|
135
83
|
## License
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Formats review synthesis into individual thread findings for GitHub PR review posting. Part of the deepreview pipeline."
|
|
3
|
+
mode: subagent
|
|
4
|
+
temperature: 0.1
|
|
5
|
+
permission:
|
|
6
|
+
edit: allow
|
|
7
|
+
bash: deny
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
You are a formatter that converts a code review synthesis into individual, postable comment threads for a GitHub PR review.
|
|
11
|
+
|
|
12
|
+
## Input
|
|
13
|
+
|
|
14
|
+
You will receive:
|
|
15
|
+
|
|
16
|
+
1. A path to `synthesis.md` — the unified review synthesis
|
|
17
|
+
2. A path to `input.txt` — the PR diff
|
|
18
|
+
3. The PR head commit SHA — provided inline in the prompt text
|
|
19
|
+
|
|
20
|
+
Read both files.
|
|
21
|
+
|
|
22
|
+
## Process
|
|
23
|
+
|
|
24
|
+
1. Read the synthesis and identify every individual finding (each bullet or paragraph that describes a distinct issue)
|
|
25
|
+
2. For each finding, determine:
|
|
26
|
+
- `path`: the file path (relative to repo root) the finding refers to
|
|
27
|
+
- `line`: the specific line number (new-side of diff). If the synthesis gives a range, use the end line.
|
|
28
|
+
- `startLine`: if the finding spans multiple lines, use the start of the range. Omit if single-line.
|
|
29
|
+
3. Read `input.txt` (the diff) to:
|
|
30
|
+
- Verify line references are correct
|
|
31
|
+
- Generate ` ```suggestion ` blocks where a concrete fix is obvious and fits within the diff
|
|
32
|
+
4. Write each finding as a document in the output file
|
|
33
|
+
|
|
34
|
+
## Output format
|
|
35
|
+
|
|
36
|
+
Write to the output path provided. Use this exact format — one document per finding, separated by `---`:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
---
|
|
40
|
+
path: <file path>
|
|
41
|
+
startLine: <start line, omit if single-line>
|
|
42
|
+
line: <line number>
|
|
43
|
+
---
|
|
44
|
+
<markdown body of the comment>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Content rules
|
|
48
|
+
|
|
49
|
+
- One finding per document. Never bundle multiple issues.
|
|
50
|
+
- No stats, severity counts, or framing ("3 critical issues found")
|
|
51
|
+
- No references to local file paths, session directories, AI tooling, or the deepreview pipeline
|
|
52
|
+
- Use permalinks for code references: `https://github.com/OWNER/REPO/blob/$PR_HEAD_SHA/<path>#L<line>`
|
|
53
|
+
- Get OWNER/REPO from the diff header or from `input.txt` context
|
|
54
|
+
- Use ` ```suggestion ` blocks where a concrete fix is obvious
|
|
55
|
+
- American English. Succinct. No filler.
|
|
56
|
+
- Do NOT classify findings into tiers — the posting pipeline handles placement
|
|
57
|
+
|
|
58
|
+
## Line number rules
|
|
59
|
+
|
|
60
|
+
- All line numbers refer to the NEW (right) side of the diff
|
|
61
|
+
- If the synthesis says "line 42" but looking at the diff that corresponds to a different new-side line, use the correct new-side line
|
|
62
|
+
- If you cannot determine an exact line, use the first line of the relevant function/block
|
|
63
|
+
|
|
64
|
+
## Response contract
|
|
65
|
+
|
|
66
|
+
After writing the threads file, your ONLY response must be the absolute path to your output file and a count line (e.g., "12 threads written"). Do not summarize findings.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
internal: true
|
|
3
|
+
description: "Shared pipeline stages for deepreview commands (review → validate → synthesize)"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Shared Pipeline: Review → Validate → Synthesize
|
|
7
|
+
|
|
8
|
+
This template defines 3 stages used by deepreview orchestrator commands. The orchestrator must set SESSION_DIR and INPUT_DESCRIPTION before invoking these stages.
|
|
9
|
+
|
|
10
|
+
## STAGE 1: INITIAL REVIEW (one task per review perspective)
|
|
11
|
+
|
|
12
|
+
Dispatch ALL of these Task tool calls simultaneously in a single message:
|
|
13
|
+
|
|
14
|
+
Task — Use the Task tool with subagent_type="deepreview-correctness":
|
|
15
|
+
"You are reviewing $INPUT_DESCRIPTION. Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-correctness.md."
|
|
16
|
+
|
|
17
|
+
Task — Use the Task tool with subagent_type="deepreview-security":
|
|
18
|
+
"You are reviewing $INPUT_DESCRIPTION. Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-security.md."
|
|
19
|
+
|
|
20
|
+
Task — Use the Task tool with subagent_type="deepreview-architecture":
|
|
21
|
+
"You are reviewing $INPUT_DESCRIPTION. Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-architecture.md."
|
|
22
|
+
|
|
23
|
+
Task — Use the Task tool with subagent_type="deepreview-docs":
|
|
24
|
+
"You are reviewing $INPUT_DESCRIPTION. Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-docs.md."
|
|
25
|
+
|
|
26
|
+
Task — Use the Task tool with subagent_type="deepreview-compatibility":
|
|
27
|
+
"You are reviewing $INPUT_DESCRIPTION. Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-compatibility.md."
|
|
28
|
+
|
|
29
|
+
Wait for all 5 to return. Record which succeeded and which failed.
|
|
30
|
+
|
|
31
|
+
## STAGE 2: CROSS-VALIDATION (5 parallel tasks)
|
|
32
|
+
|
|
33
|
+
Only proceed with reviews that exist. For each validator below, replace $REVIEW_FILE_LIST with ONLY the review files that were successfully created in Stage 1. If a review file failed, omit it from the list entirely. Dispatch validators simultaneously:
|
|
34
|
+
|
|
35
|
+
Task — Use the Task tool with subagent_type="deepreview-validator":
|
|
36
|
+
"Your perspective: correctness. Read all available review files at: $REVIEW_FILE_LIST. Also read the original input at $SESSION_DIR/input.txt for context. Write your validated review to $SESSION_DIR/validated-correctness.md."
|
|
37
|
+
|
|
38
|
+
Task — Use the Task tool with subagent_type="deepreview-validator":
|
|
39
|
+
"Your perspective: security. Read all available review files at: $REVIEW_FILE_LIST. Also read the original input at $SESSION_DIR/input.txt for context. Write your validated review to $SESSION_DIR/validated-security.md."
|
|
40
|
+
|
|
41
|
+
Task — Use the Task tool with subagent_type="deepreview-validator":
|
|
42
|
+
"Your perspective: architecture. Read all available review files at: $REVIEW_FILE_LIST. Also read the original input at $SESSION_DIR/input.txt for context. Write your validated review to $SESSION_DIR/validated-architecture.md."
|
|
43
|
+
|
|
44
|
+
Task — Use the Task tool with subagent_type="deepreview-validator":
|
|
45
|
+
"Your perspective: docs. Read all available review files at: $REVIEW_FILE_LIST. Also read the original input at $SESSION_DIR/input.txt for context. Write your validated review to $SESSION_DIR/validated-docs.md."
|
|
46
|
+
|
|
47
|
+
Task — Use the Task tool with subagent_type="deepreview-validator":
|
|
48
|
+
"Your perspective: compatibility. Read all available review files at: $REVIEW_FILE_LIST. Also read the original input at $SESSION_DIR/input.txt for context. Write your validated review to $SESSION_DIR/validated-compatibility.md."
|
|
49
|
+
|
|
50
|
+
Wait for all 5 to return.
|
|
51
|
+
|
|
52
|
+
## STAGE 3: SYNTHESIS (1 task)
|
|
53
|
+
|
|
54
|
+
Task — Use the Task tool with subagent_type="deepreview-synthesizer":
|
|
55
|
+
"Read the validated reviews at: $SESSION_DIR/validated-correctness.md, $SESSION_DIR/validated-security.md, $SESSION_DIR/validated-architecture.md, $SESSION_DIR/validated-docs.md, $SESSION_DIR/validated-compatibility.md (skip any that don't exist). Write the synthesis to $SESSION_DIR/synthesis.md."
|
|
56
|
+
|
|
57
|
+
Record the stats line from its return.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Multi-agent parallel code review posted as a pending GitHub PR review"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
You are an orchestrator for a multi-agent code review pipeline that posts findings as a pending GitHub PR review. Follow these steps EXACTLY. Do NOT deviate, skip steps, or read any files in the session directory yourself.
|
|
6
|
+
|
|
7
|
+
STEP 1: VALIDATE INPUT
|
|
8
|
+
$ARGUMENTS must be a PR number (integer). If it is not a number, tell the user "Usage: /deepreview-pr-review <PR_NUMBER>" and STOP.
|
|
9
|
+
|
|
10
|
+
Set SESSION_DIR=".ai/deepreview/$ARGUMENTS-review-$(date +%Y-%m-%d-%H%M%S)"
|
|
11
|
+
Create the directory with `mkdir -p $SESSION_DIR`
|
|
12
|
+
Set INPUT_DESCRIPTION="a PR diff (code changes)"
|
|
13
|
+
|
|
14
|
+
STEP 2: PREPARE INPUT
|
|
15
|
+
Run `gh pr diff $ARGUMENTS > $SESSION_DIR/input.txt`
|
|
16
|
+
Check if input.txt is empty (0 bytes). If empty, tell the user "Nothing to review — PR has no diff." and STOP.
|
|
17
|
+
|
|
18
|
+
Get and store the PR head SHA:
|
|
19
|
+
Run `gh pr view $ARGUMENTS --json headRefOid --jq .headRefOid` and save the output as PR_HEAD_SHA.
|
|
20
|
+
|
|
21
|
+
## Stage 1-3: Shared Pipeline
|
|
22
|
+
|
|
23
|
+
Follow the shared pipeline at `commands/_deepreview-pipeline.md` with:
|
|
24
|
+
|
|
25
|
+
- INPUT: `$SESSION_DIR/input.txt`
|
|
26
|
+
- OUTPUT: `$SESSION_DIR/synthesis.md`
|
|
27
|
+
|
|
28
|
+
If stats show 0 critical, 0 warnings, 0 suggestions, tell the user "No findings to post. PR looks good!" and STOP.
|
|
29
|
+
|
|
30
|
+
STEP 3: FORMAT THREADS (1 task)
|
|
31
|
+
|
|
32
|
+
Get the repo owner/name:
|
|
33
|
+
Run `gh repo view --json owner,name --jq '.owner.login + "/" + .name'` and save as OWNER_REPO.
|
|
34
|
+
|
|
35
|
+
Task — Use the Task tool with subagent_type="deepreview-review-formatter":
|
|
36
|
+
"Read the synthesis at $SESSION_DIR/synthesis.md and the diff at $SESSION_DIR/input.txt. The PR is $OWNER_REPO#$ARGUMENTS, head SHA is $PR_HEAD_SHA. Write the formatted threads to $SESSION_DIR/threads.md."
|
|
37
|
+
|
|
38
|
+
Wait for it to return.
|
|
39
|
+
|
|
40
|
+
STEP 4: POST REVIEW
|
|
41
|
+
Use the `deepreview-post-review` tool:
|
|
42
|
+
|
|
43
|
+
- `threads_path`: The absolute path to `$SESSION_DIR/threads.md`
|
|
44
|
+
- `pr_number`: $ARGUMENTS (the PR number)
|
|
45
|
+
|
|
46
|
+
STEP 5: PRESENT RESULTS
|
|
47
|
+
Show the user:
|
|
48
|
+
|
|
49
|
+
- Session directory: $SESSION_DIR/
|
|
50
|
+
- Which reviewers completed (and any that failed)
|
|
51
|
+
- Stats from synthesis (the stats line from Stage 3)
|
|
52
|
+
- Output from the posting script (how many threads posted, any demotions)
|
|
53
|
+
- Remind: "The review is PENDING. Submit it via the GitHub UI when ready."
|
|
54
|
+
|
|
55
|
+
IMPORTANT RULES:
|
|
56
|
+
|
|
57
|
+
- Do NOT read any files in $SESSION_DIR yourself. Ever.
|
|
58
|
+
- Use ONLY the file paths and stats/summary lines returned by subagents.
|
|
59
|
+
- If a subagent fails, note which one failed and continue with what you have.
|
|
60
|
+
- If all 5 reviewers fail in Stage 1, tell the user and STOP.
|
|
61
|
+
- Do NOT submit the review. It stays pending.
|
package/package.json
CHANGED
|
@@ -1,24 +1,49 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mechanai/deepreview",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Multi-agent parallel code/spec review for OpenCode",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "https://github.com/mechanai/deepreview"
|
|
9
9
|
},
|
|
10
|
-
"bin": "./src/cli.js",
|
|
11
10
|
"files": [
|
|
12
11
|
"src/",
|
|
13
12
|
"agents/",
|
|
14
|
-
"commands/"
|
|
13
|
+
"commands/",
|
|
14
|
+
".opencode/"
|
|
15
15
|
],
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "./.opencode/plugins/deepreview.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": "./.opencode/plugins/deepreview.ts",
|
|
20
|
+
"./api": "./src/post-review.ts"
|
|
21
|
+
},
|
|
16
22
|
"publishConfig": {
|
|
17
23
|
"access": "public"
|
|
18
24
|
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"gray-matter": "4.0.3",
|
|
27
|
+
"js-yaml": "4.1.0",
|
|
28
|
+
"parse-diff": "0.11.1"
|
|
29
|
+
},
|
|
19
30
|
"devDependencies": {
|
|
31
|
+
"@opencode-ai/plugin": "1.4.7",
|
|
32
|
+
"@types/js-yaml": "^4.0.9",
|
|
33
|
+
"bun-types": "^1.3.14",
|
|
20
34
|
"oxfmt": "0.52.0",
|
|
21
|
-
"oxlint": "1.67.0"
|
|
35
|
+
"oxlint": "1.67.0",
|
|
36
|
+
"oxlint-tsgolint": "^0.23.0"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"@opencode-ai/plugin": ">=1.4.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependenciesMeta": {
|
|
42
|
+
"@opencode-ai/plugin": {
|
|
43
|
+
"optional": true
|
|
44
|
+
}
|
|
22
45
|
},
|
|
23
|
-
"
|
|
46
|
+
"engines": {
|
|
47
|
+
"bun": ">=1.2.0"
|
|
48
|
+
}
|
|
24
49
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, it } from "bun:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { classifyFindings } from "./diff-classifier.ts";
|
|
4
|
+
|
|
5
|
+
const SAMPLE_DIFF = [
|
|
6
|
+
"diff --git a/pkg/server/handler.go b/pkg/server/handler.go",
|
|
7
|
+
"index abc1234..def5678 100644",
|
|
8
|
+
"--- a/pkg/server/handler.go",
|
|
9
|
+
"+++ b/pkg/server/handler.go",
|
|
10
|
+
"@@ -40,6 +40,10 @@ func Handle() {",
|
|
11
|
+
" existing()",
|
|
12
|
+
"+ newLine1()",
|
|
13
|
+
"+ newLine2()",
|
|
14
|
+
"+ newLine3()",
|
|
15
|
+
"+ newLine4()",
|
|
16
|
+
" more()",
|
|
17
|
+
"diff --git a/README.md b/README.md",
|
|
18
|
+
"index 111..222 100644",
|
|
19
|
+
"--- a/README.md",
|
|
20
|
+
"+++ b/README.md",
|
|
21
|
+
"@@ -1,3 +1,4 @@",
|
|
22
|
+
" # Title",
|
|
23
|
+
"+New line",
|
|
24
|
+
" Rest",
|
|
25
|
+
].join("\n");
|
|
26
|
+
|
|
27
|
+
describe("classifyFindings", () => {
|
|
28
|
+
it("tier 1: line within a diff hunk", () => {
|
|
29
|
+
const findings = [{ path: "pkg/server/handler.go", line: 42, body: "issue" }];
|
|
30
|
+
const result = classifyFindings(findings, SAMPLE_DIFF);
|
|
31
|
+
assert.equal(result[0].tier, 1);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("tier 2: file in diff but line not in any hunk", () => {
|
|
35
|
+
const findings = [{ path: "pkg/server/handler.go", line: 200, body: "issue" }];
|
|
36
|
+
const result = classifyFindings(findings, SAMPLE_DIFF);
|
|
37
|
+
assert.equal(result[0].tier, 2);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("tier 3: file not in diff at all", () => {
|
|
41
|
+
const findings = [{ path: "internal/other.go", line: 10, body: "issue" }];
|
|
42
|
+
const result = classifyFindings(findings, SAMPLE_DIFF);
|
|
43
|
+
assert.equal(result[0].tier, 3);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import parseDiff from "parse-diff";
|
|
2
|
+
|
|
3
|
+
export interface Finding {
|
|
4
|
+
path: string;
|
|
5
|
+
line: number;
|
|
6
|
+
startLine?: number;
|
|
7
|
+
body: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ClassifiedFinding extends Finding {
|
|
11
|
+
tier: 1 | 2 | 3;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface FileHunks {
|
|
15
|
+
hunks: { newStart: number; newLines: number }[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Classify findings into placement tiers based on the PR diff.
|
|
20
|
+
*
|
|
21
|
+
* Tier 1: line-level (line within a diff hunk's new-side range)
|
|
22
|
+
* Tier 2: file-level (file in diff but line not in any hunk)
|
|
23
|
+
* Tier 3: review body (file not in diff)
|
|
24
|
+
*/
|
|
25
|
+
export function classifyFindings(findings: Finding[], diffText: string): ClassifiedFinding[] {
|
|
26
|
+
const parsed = parseDiff(diffText);
|
|
27
|
+
const fileMap = buildFileMap(parsed);
|
|
28
|
+
|
|
29
|
+
return findings.map((finding) => {
|
|
30
|
+
const file = fileMap.get(finding.path);
|
|
31
|
+
if (!file) {
|
|
32
|
+
return { ...finding, tier: 3 as const };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const lineInHunk = file.hunks.some((hunk) => {
|
|
36
|
+
return finding.line >= hunk.newStart && finding.line < hunk.newStart + hunk.newLines;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (lineInHunk) {
|
|
40
|
+
return { ...finding, tier: 1 as const };
|
|
41
|
+
}
|
|
42
|
+
return { ...finding, tier: 2 as const };
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Build a map of file path → {hunks: [{newStart, newLines}]}
|
|
48
|
+
*/
|
|
49
|
+
function buildFileMap(parsedDiff: parseDiff.File[]): Map<string, FileHunks> {
|
|
50
|
+
const map = new Map<string, FileHunks>();
|
|
51
|
+
for (const file of parsedDiff) {
|
|
52
|
+
const filePath = file.to === "/dev/null" ? file.from : file.to;
|
|
53
|
+
if (filePath === undefined || filePath === "") continue;
|
|
54
|
+
|
|
55
|
+
const hunks = file.chunks.map((chunk) => ({
|
|
56
|
+
newStart: chunk.newStart,
|
|
57
|
+
newLines: chunk.newLines,
|
|
58
|
+
}));
|
|
59
|
+
|
|
60
|
+
map.set(filePath, { hunks });
|
|
61
|
+
}
|
|
62
|
+
return map;
|
|
63
|
+
}
|