@mechanai/deepreview 2.0.1 → 2.1.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/agents/deepreview-applier.md +6 -2
- package/.opencode/agents/deepreview-architecture.md +7 -1
- package/.opencode/agents/deepreview-compatibility.md +6 -0
- package/.opencode/agents/deepreview-correctness.md +7 -2
- package/.opencode/agents/deepreview-docs.md +6 -0
- package/.opencode/agents/deepreview-security.md +6 -0
- package/.opencode/agents/deepreview-spec-completeness.md +6 -0
- package/.opencode/agents/deepreview-spec-consistency.md +6 -0
- package/.opencode/agents/deepreview-spec-feasibility.md +6 -0
- package/.opencode/commands/deepreview-loop.md +59 -10
- package/.opencode/commands/deepreview-spec-loop.md +58 -12
- package/.opencode/commands/deepreview-spec.md +13 -6
- package/.opencode/commands/deepreview.md +12 -5
- package/README.md +30 -9
- package/package.json +7 -3
- package/src/setup.test.ts +159 -0
- package/src/setup.ts +151 -0
|
@@ -21,8 +21,12 @@ For each fix in the plan, in the order specified by the "Order of Operations" se
|
|
|
21
21
|
|
|
22
22
|
1. Read the current file at the referenced location
|
|
23
23
|
2. Apply the code change exactly as specified in the plan
|
|
24
|
-
3.
|
|
25
|
-
|
|
24
|
+
3. **Globalize check:** After applying, check whether other files _listed in input.txt or the plan_ have the same pattern. If so, apply the equivalent fix there too. Do NOT search the broader codebase. Common cases:
|
|
25
|
+
- A loop command fix that applies to the other loop command (code-loop ↔ spec-loop)
|
|
26
|
+
- A prompt/contract change affecting multiple agent files
|
|
27
|
+
- A variable rename or policy change referenced in multiple files
|
|
28
|
+
4. Run `git diff <file>` to verify the edit looks correct
|
|
29
|
+
5. Note what was changed (file path + one-line description)
|
|
26
30
|
|
|
27
31
|
If a fix cannot be applied (file doesn't exist, code doesn't match what was expected), skip it and note the failure.
|
|
28
32
|
|
|
@@ -15,7 +15,13 @@ You are a principal engineer conducting a focused code review. Your scope is arc
|
|
|
15
15
|
|
|
16
16
|
## Input
|
|
17
17
|
|
|
18
|
-
You will receive a path to an input file. This may be a diff, a spec, a plan, or concatenated file contents. Read it with the Read tool and adapt your review to the content type.
|
|
18
|
+
You will receive a path to an input file. This may be a diff, a spec, a plan, or concatenated file contents. Read it with the Read tool and adapt your review to the content type. Read surrounding files referenced in the diff to understand existing patterns (max 10 files).
|
|
19
|
+
|
|
20
|
+
## Prior Context (if provided)
|
|
21
|
+
|
|
22
|
+
Your prompt may include sections titled "Design Decisions", "Prior Findings", and "Covered Regions". Rules: do NOT flag design decisions as issues; do NOT re-report prior findings; prioritize uncovered regions but you may still report _new_ issues in covered regions.
|
|
23
|
+
|
|
24
|
+
Your prompt may also begin with framing directives (e.g., novelty-seeking instructions). Follow those directives in addition to the rules above.
|
|
19
25
|
|
|
20
26
|
## Review checklist
|
|
21
27
|
|
|
@@ -17,6 +17,12 @@ You are a senior engineer focused on backwards compatibility. Your scope is brea
|
|
|
17
17
|
|
|
18
18
|
You will receive a path to an input file. This may be a diff, a spec, a plan, or concatenated file contents. Read it with the Read tool and adapt your review to the content type.
|
|
19
19
|
|
|
20
|
+
## Prior Context (if provided)
|
|
21
|
+
|
|
22
|
+
Your prompt may include sections titled "Design Decisions", "Prior Findings", and "Covered Regions". Rules: do NOT flag design decisions as issues; do NOT re-report prior findings; prioritize uncovered regions but you may still report _new_ issues in covered regions.
|
|
23
|
+
|
|
24
|
+
Your prompt may also begin with framing directives (e.g., novelty-seeking instructions). Follow those directives in addition to the rules above.
|
|
25
|
+
|
|
20
26
|
## Review checklist
|
|
21
27
|
|
|
22
28
|
- Removed or renamed public exports, functions, classes, or methods
|
|
@@ -17,14 +17,19 @@ You are a senior engineer conducting a focused code review. Your scope is correc
|
|
|
17
17
|
|
|
18
18
|
You will receive a path to an input file. This may be a diff, a spec, a plan, or concatenated file contents. Read it with the Read tool and adapt your review to the content type.
|
|
19
19
|
|
|
20
|
+
## Prior Context (if provided)
|
|
21
|
+
|
|
22
|
+
Your prompt may include sections titled "Design Decisions", "Prior Findings", and "Covered Regions". Rules: do NOT flag design decisions as issues; do NOT re-report prior findings; prioritize uncovered regions but you may still report _new_ issues in covered regions.
|
|
23
|
+
|
|
24
|
+
Your prompt may also begin with framing directives (e.g., novelty-seeking instructions). Follow those directives in addition to the rules above.
|
|
25
|
+
|
|
20
26
|
## Review checklist
|
|
21
27
|
|
|
22
28
|
- Logic errors and off-by-one mistakes
|
|
23
29
|
- Unhandled edge cases and null/undefined paths
|
|
24
30
|
- Incorrect assumptions about input or state
|
|
25
31
|
- Race conditions or async handling issues
|
|
26
|
-
- Functions that can fail silently
|
|
27
|
-
- Errors swallowed without logging or re-raising
|
|
32
|
+
- Functions that can fail silently (errors swallowed, not logged or re-raised)
|
|
28
33
|
- Missing error propagation (errors caught but not communicated to callers)
|
|
29
34
|
- Partial failure leaving system in inconsistent state
|
|
30
35
|
- Missing retry/backoff for transient failures
|
|
@@ -17,6 +17,12 @@ You are a technical writing expert conducting a focused code review. Your scope
|
|
|
17
17
|
|
|
18
18
|
You will receive a path to an input file. This may be a diff, a spec, a plan, or concatenated file contents. Read it with the Read tool and adapt your review to the content type.
|
|
19
19
|
|
|
20
|
+
## Prior Context (if provided)
|
|
21
|
+
|
|
22
|
+
Your prompt may include sections titled "Design Decisions", "Prior Findings", and "Covered Regions". Rules: do NOT flag design decisions as issues; do NOT re-report prior findings; prioritize uncovered regions but you may still report _new_ issues in covered regions.
|
|
23
|
+
|
|
24
|
+
Your prompt may also begin with framing directives (e.g., novelty-seeking instructions). Follow those directives in addition to the rules above.
|
|
25
|
+
|
|
20
26
|
## Review checklist
|
|
21
27
|
|
|
22
28
|
- **Succinctness:** Comments or docs that are verbose, rambling, or use 3 sentences where 1 would do
|
|
@@ -17,6 +17,12 @@ You are a senior security and performance engineer conducting a focused code rev
|
|
|
17
17
|
|
|
18
18
|
You will receive a path to an input file. This may be a diff, a spec, a plan, or concatenated file contents. Read it with the Read tool and adapt your review to the content type.
|
|
19
19
|
|
|
20
|
+
## Prior Context (if provided)
|
|
21
|
+
|
|
22
|
+
Your prompt may include sections titled "Design Decisions", "Prior Findings", and "Covered Regions". Rules: do NOT flag design decisions as issues; do NOT re-report prior findings; prioritize uncovered regions but you may still report _new_ issues in covered regions.
|
|
23
|
+
|
|
24
|
+
Your prompt may also begin with framing directives (e.g., novelty-seeking instructions). Follow those directives in addition to the rules above.
|
|
25
|
+
|
|
20
26
|
## Review checklist
|
|
21
27
|
|
|
22
28
|
- Injection vulnerabilities (SQL, command, XSS, etc.)
|
|
@@ -17,6 +17,12 @@ You are a senior engineer reviewing a specification or implementation plan for c
|
|
|
17
17
|
|
|
18
18
|
You will receive a path to a spec or plan file. Read it with the Read tool.
|
|
19
19
|
|
|
20
|
+
## Prior Context (if provided)
|
|
21
|
+
|
|
22
|
+
Your prompt may include sections titled "Design Decisions", "Prior Findings", and "Covered Regions". Rules: do NOT flag design decisions as issues; do NOT re-report prior findings; prioritize uncovered regions but you may still report _new_ issues in covered regions.
|
|
23
|
+
|
|
24
|
+
Your prompt may also begin with framing directives (e.g., novelty-seeking instructions). Follow those directives in addition to the rules above.
|
|
25
|
+
|
|
20
26
|
## Review checklist
|
|
21
27
|
|
|
22
28
|
- Missing error cases — what happens when things go wrong?
|
|
@@ -17,6 +17,12 @@ You are a senior engineer reviewing a specification or implementation plan for i
|
|
|
17
17
|
|
|
18
18
|
You will receive a path to a spec or plan file. Read it with the Read tool.
|
|
19
19
|
|
|
20
|
+
## Prior Context (if provided)
|
|
21
|
+
|
|
22
|
+
Your prompt may include sections titled "Design Decisions", "Prior Findings", and "Covered Regions". Rules: do NOT flag design decisions as issues; do NOT re-report prior findings; prioritize uncovered regions but you may still report _new_ issues in covered regions.
|
|
23
|
+
|
|
24
|
+
Your prompt may also begin with framing directives (e.g., novelty-seeking instructions). Follow those directives in addition to the rules above.
|
|
25
|
+
|
|
20
26
|
## Review checklist
|
|
21
27
|
|
|
22
28
|
- **Contradictions:** Does section A say one thing and section B say the opposite?
|
|
@@ -17,6 +17,12 @@ You are a principal engineer reviewing a specification or implementation plan fo
|
|
|
17
17
|
|
|
18
18
|
You will receive a path to a spec or plan file. Read it with the Read tool. If it references existing code, read the relevant files to assess feasibility against the current codebase.
|
|
19
19
|
|
|
20
|
+
## Prior Context (if provided)
|
|
21
|
+
|
|
22
|
+
Your prompt may include sections titled "Design Decisions", "Prior Findings", and "Covered Regions". Rules: do NOT flag design decisions as issues; do NOT re-report prior findings; prioritize uncovered regions but you may still report _new_ issues in covered regions.
|
|
23
|
+
|
|
24
|
+
Your prompt may also begin with framing directives (e.g., novelty-seeking instructions). Follow those directives in addition to the rules above.
|
|
25
|
+
|
|
20
26
|
## Review checklist
|
|
21
27
|
|
|
22
28
|
- **Impossible requirements:** Does the spec ask for something that can't be done with the specified tools/approach?
|
|
@@ -7,19 +7,27 @@ You are an orchestrator that runs deepreview repeatedly until the code is clean.
|
|
|
7
7
|
STEP 1: DETERMINE INPUT MODE
|
|
8
8
|
Parse "$ARGUMENTS" the same way as /deepreview:
|
|
9
9
|
|
|
10
|
+
- If it starts with `--context <path>`, extract CONTEXT_FILE=<path> and remove it from $ARGUMENTS before parsing the rest.
|
|
11
|
+
- Validate CONTEXT_FILE: it must be a relative path (no leading `/`), must not contain `..`, must exist on disk, and must be a regular file (not a directory or symlink to outside the project), and must be under 50KB. If validation fails, tell the user the error and STOP.
|
|
10
12
|
- If it is a number → MODE=pr, TARGET="$ARGUMENTS"
|
|
11
13
|
- If it is a file path or multiple file paths → MODE=files, TARGET="$ARGUMENTS"
|
|
12
14
|
- If it is empty → MODE=branch, TARGET=""
|
|
13
15
|
|
|
14
16
|
Set ITERATION=1
|
|
17
|
+
Set PRIOR_CONTEXT="" (empty — built up across iterations)
|
|
18
|
+
Set ALL_SESSION_DIRS=[] (list of all session directories used, in order)
|
|
19
|
+
|
|
20
|
+
If CONTEXT_FILE exists, read its contents and set PRIOR_CONTEXT to:
|
|
21
|
+
"## Design Decisions (intentional — do not flag)\nThe following are deliberate design choices. Do NOT flag these as issues or suggest alternatives.\n`\n" + contents of CONTEXT_FILE + "\n`\n"
|
|
15
22
|
|
|
16
23
|
STEP 2: RUN INITIAL DEEPREVIEW (full pipeline with cross-validation)
|
|
17
24
|
Run the full deepreview pipeline (Stages 1-5 from the deepreview command):
|
|
18
25
|
|
|
19
26
|
- Determine SESSION_DIR and write input.txt
|
|
20
|
-
-
|
|
27
|
+
- Append SESSION_DIR to ALL_SESSION_DIRS
|
|
28
|
+
- Stage 1: 5 parallel reviewers — prepend PRIOR_CONTEXT (if non-empty) to each reviewer's prompt as "${PRIOR_CONTEXT}You are reviewing ... Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-{perspective}.md."
|
|
21
29
|
- Stage 2: 5 parallel validators (cross-validation)
|
|
22
|
-
- Stage 3: Synthesizer
|
|
30
|
+
- Note: validators do NOT receive PRIOR_CONTEXT. This is intentional — validators independently verify reviewer claims without being influenced by design context.- Stage 3: Synthesizer
|
|
23
31
|
- Stage 4: Implementation planner
|
|
24
32
|
|
|
25
33
|
Record the stats from the synthesis return: count of critical, warning, and suggestion findings.
|
|
@@ -50,6 +58,7 @@ If ITERATION > 5:
|
|
|
50
58
|
|
|
51
59
|
Create new session directory: SESSION_DIR=".ai/deepreview/loop-iter$ITERATION-$(date +%Y-%m-%d-%H%M%S)"
|
|
52
60
|
Run `mkdir -p $SESSION_DIR`
|
|
61
|
+
Append SESSION_DIR to ALL_SESSION_DIRS
|
|
53
62
|
|
|
54
63
|
Prepare fresh input:
|
|
55
64
|
|
|
@@ -58,27 +67,65 @@ Prepare fresh input:
|
|
|
58
67
|
|
|
59
68
|
Check if input.txt is empty. If empty, tell user "Nothing to review — all changes resolved." and STOP.
|
|
60
69
|
|
|
70
|
+
STEP 5a: BUILD PRIOR CONTEXT
|
|
71
|
+
Accumulate findings from ALL previous iterations into PRIOR_CONTEXT so no finding is re-reported.
|
|
72
|
+
|
|
73
|
+
To build this, dispatch a helper task that reads ALL previous syntheses:
|
|
74
|
+
NOTE: Interpolate the actual directory paths from ALL_SESSION_DIRS into this task string — the subagent cannot access your variables.
|
|
75
|
+
Task — Use the Task tool with subagent_type="general":
|
|
76
|
+
"Read the synthesis files from these directories: [LIST EACH PATH FROM ALL_SESSION_DIRS EXCLUDING CURRENT]. If any synthesis file does not exist, skip it. Extract ALL findings across them as a deduplicated Markdown list in this exact format:
|
|
77
|
+
|
|
78
|
+
## Prior Findings (already reported — do not re-report or verify)
|
|
79
|
+
|
|
80
|
+
- [Short Issue Title] ([category]) — [file:line]
|
|
81
|
+
|
|
82
|
+
## Covered Regions (already examined — prioritize elsewhere)
|
|
83
|
+
|
|
84
|
+
- [file:line-range] (pad each finding's file:line by 20 lines in each direction)
|
|
85
|
+
|
|
86
|
+
Deduplicate findings that appear in multiple syntheses. Return ONLY these two sections, nothing else."
|
|
87
|
+
|
|
88
|
+
Set PRIOR_CONTEXT to the returned text. Validate that it contains "## Prior Findings" — if not, warn the user ("Helper returned malformed prior context — proceeding without deduplication") and set PRIOR_CONTEXT="". If CONTEXT_FILE exists, prepend:
|
|
89
|
+
"## Design Decisions (intentional — do not flag)\nThe following are deliberate design choices. Do NOT flag these as issues or suggest alternatives.\n`\n" + contents of CONTEXT_FILE + "\n`\n\n"
|
|
90
|
+
|
|
61
91
|
NOW RUN A LIGHTWEIGHT REVIEW (Stages 1, 3, 4 only — NO cross-validation):
|
|
62
92
|
|
|
63
|
-
The key difference: iteration 2+ skips cross-validation
|
|
93
|
+
The key difference: iteration 2+ skips cross-validation. This prevents validators from filtering out new issues introduced by fixes.
|
|
64
94
|
|
|
65
95
|
Stage 1 — DISPATCH 5 PARALLEL REVIEWERS:
|
|
66
|
-
Each reviewer prompt MUST include
|
|
96
|
+
Each reviewer prompt MUST include PRIOR_CONTEXT and the novelty-seeking framing below.
|
|
97
|
+
|
|
98
|
+
The REVIEWER_PREAMBLE for all iter2+ reviewers is:
|
|
99
|
+
"Your goal is to find issues that PREVIOUS reviewers missed. Do NOT re-report, verify, or comment on prior findings.
|
|
100
|
+
|
|
101
|
+
$PRIOR_CONTEXT
|
|
102
|
+
|
|
103
|
+
Find genuinely new issues. You may find different issues in covered regions, but prioritize areas not yet examined."
|
|
67
104
|
|
|
68
105
|
Task 1 — Use the Task tool with subagent_type="deepreview-correctness":
|
|
69
|
-
"
|
|
106
|
+
"$REVIEWER_PREAMBLE
|
|
107
|
+
|
|
108
|
+
Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-correctness.md."
|
|
70
109
|
|
|
71
110
|
Task 2 — Use the Task tool with subagent_type="deepreview-security":
|
|
72
|
-
"
|
|
111
|
+
"$REVIEWER_PREAMBLE
|
|
112
|
+
|
|
113
|
+
Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-security.md."
|
|
73
114
|
|
|
74
115
|
Task 3 — Use the Task tool with subagent_type="deepreview-architecture":
|
|
75
|
-
"
|
|
116
|
+
"$REVIEWER_PREAMBLE
|
|
117
|
+
|
|
118
|
+
Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-architecture.md."
|
|
76
119
|
|
|
77
120
|
Task 4 — Use the Task tool with subagent_type="deepreview-docs":
|
|
78
|
-
"
|
|
121
|
+
"$REVIEWER_PREAMBLE
|
|
122
|
+
|
|
123
|
+
Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-docs.md."
|
|
79
124
|
|
|
80
125
|
Task 5 — Use the Task tool with subagent_type="deepreview-compatibility":
|
|
81
|
-
"
|
|
126
|
+
"$REVIEWER_PREAMBLE
|
|
127
|
+
|
|
128
|
+
Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-compatibility.md."
|
|
82
129
|
|
|
83
130
|
Wait for all 5. Record which succeeded.
|
|
84
131
|
|
|
@@ -113,5 +160,7 @@ IMPORTANT RULES:
|
|
|
113
160
|
- Apply ALL findings (critical, warning, AND suggestion) — the goal is a clean review.
|
|
114
161
|
- Do NOT ask the user for permission to apply fixes. Apply automatically.
|
|
115
162
|
- DO ask the user if iteration limit is hit or deadlock is detected.
|
|
116
|
-
- Iteration 2+ MUST skip cross-validation
|
|
163
|
+
- Iteration 2+ MUST skip cross-validation, MUST include PRIOR_CONTEXT, and MUST use novelty-seeking framing.
|
|
164
|
+
- Iteration 2+ MUST NOT tell reviewers to "verify" or "check status of" prior findings.
|
|
117
165
|
- Each iteration uses a NEW session directory — never reuse a previous one.
|
|
166
|
+
- If --context file is provided, include its contents under "Design Decisions" in PRIOR_CONTEXT for ALL iterations (including iter1).
|
|
@@ -6,15 +6,21 @@ You are an orchestrator that runs deepreview-spec repeatedly until the spec/plan
|
|
|
6
6
|
|
|
7
7
|
STEP 1: DETERMINE INPUT
|
|
8
8
|
|
|
9
|
-
- If "$ARGUMENTS"
|
|
9
|
+
- If "$ARGUMENTS" starts with `--context <path>`, extract CONTEXT_FILE=<path> and remove it from $ARGUMENTS before parsing the rest.
|
|
10
|
+
- Validate CONTEXT_FILE: it must be a relative path (no leading `/`), must not contain `..`, must exist on disk, and must be a regular file (not a directory or symlink to outside the project), and must be under 50KB. If validation fails, tell the user the error and STOP.
|
|
11
|
+
- If remaining "$ARGUMENTS" is empty, tell the user "Usage: /deepreview-spec-loop [--context <file>] <file1> [file2 ...]" and STOP.
|
|
10
12
|
- Set FILES="$ARGUMENTS"
|
|
11
13
|
- Set ITERATION=1
|
|
14
|
+
- Set PRIOR_CONTEXT="" (empty — built up across iterations)
|
|
15
|
+
- Set ALL_SESSION_DIRS=[] (list of all session directories used, in order)
|
|
16
|
+
- If CONTEXT_FILE exists, set PRIOR_CONTEXT="## Design Decisions (intentional — do not flag)\nThe following are deliberate design choices. Do NOT flag these as issues or suggest alternatives.\n`\n" + contents of CONTEXT_FILE + "\n`\n\n"
|
|
12
17
|
|
|
13
18
|
STEP 2: RUN INITIAL DEEPREVIEW-SPEC (full pipeline with cross-validation)
|
|
14
19
|
Run the full deepreview-spec pipeline (Stages 1-5 from the deepreview-spec command):
|
|
15
20
|
|
|
16
21
|
- Determine SESSION_DIR=".ai/deepreview/spec-loop-iter1-$(date +%Y-%m-%d-%H%M%S)" and write input.txt
|
|
17
|
-
-
|
|
22
|
+
- Append SESSION_DIR to ALL_SESSION_DIRS
|
|
23
|
+
- Stage 1: 5 parallel reviewers (completeness, consistency, feasibility, docs, architecture) — prepend PRIOR_CONTEXT (if non-empty) to each reviewer's prompt as "${PRIOR_CONTEXT}You are reviewing ... Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-{perspective}.md."
|
|
18
24
|
- Stage 2: 5 parallel validators (cross-validation)
|
|
19
25
|
- Stage 3: Synthesizer
|
|
20
26
|
- Stage 4: Implementation planner (spec changes, not code changes)
|
|
@@ -53,41 +59,78 @@ If ITERATION > 7:
|
|
|
53
59
|
|
|
54
60
|
Create new session directory: SESSION_DIR=".ai/deepreview/spec-loop-iter$ITERATION-$(date +%Y-%m-%d-%H%M%S)"
|
|
55
61
|
Run `mkdir -p $SESSION_DIR`
|
|
62
|
+
Append SESSION_DIR to ALL_SESSION_DIRS
|
|
56
63
|
|
|
57
64
|
Re-read the same files into `$SESSION_DIR/input.txt`:
|
|
58
65
|
`for f in $FILES; do echo "=== $f ===" >> $SESSION_DIR/input.txt; cat "$f" >> $SESSION_DIR/input.txt; echo >> $SESSION_DIR/input.txt; done`
|
|
59
66
|
|
|
60
67
|
Check if input.txt is empty. If empty, tell user "Nothing to review — files are empty." and STOP.
|
|
61
68
|
|
|
69
|
+
BUILD PRIOR CONTEXT FOR THIS ITERATION:
|
|
70
|
+
Dispatch a helper task to extract findings from ALL previous syntheses:
|
|
71
|
+
Task — Use the Task tool with subagent_type="general":
|
|
72
|
+
"Read the synthesis files from ALL completed iterations ([LIST EACH PATH FROM ALL_SESSION_DIRS EXCLUDING CURRENT]). If any synthesis file does not exist, skip it. Extract ALL findings across them as a deduplicated Markdown list in this exact format:
|
|
73
|
+
|
|
74
|
+
## Prior Findings (already reported — do not re-report or verify)
|
|
75
|
+
|
|
76
|
+
- [Short Issue Title] ([category]) — [file:line or section reference]
|
|
77
|
+
|
|
78
|
+
## Covered Regions (already examined — prioritize elsewhere)
|
|
79
|
+
|
|
80
|
+
- [file or section references, padded generously around each finding location]
|
|
81
|
+
|
|
82
|
+
Deduplicate findings that appear in multiple syntheses. Return ONLY these two sections, nothing else."
|
|
83
|
+
|
|
84
|
+
Set PRIOR_CONTEXT to the returned text. If CONTEXT_FILE exists, prepend:
|
|
85
|
+
"## Design Decisions (intentional — do not flag)\nThe following are deliberate design choices. Do NOT flag these as issues or suggest alternatives.\n`\n" + contents of CONTEXT_FILE + "\n`\n\n"
|
|
86
|
+
|
|
87
|
+
The REVIEWER_PREAMBLE for all iter2+ reviewers is:
|
|
88
|
+
"Your goal is to find issues that PREVIOUS reviewers missed. Do NOT re-report, verify, or comment on prior findings.
|
|
89
|
+
|
|
90
|
+
$PRIOR_CONTEXT
|
|
91
|
+
|
|
92
|
+
Find genuinely new issues. You may find different issues in covered regions, but prioritize areas not yet examined. Focus ONLY on objective issues — do NOT flag stylistic preferences."
|
|
93
|
+
|
|
94
|
+
<!-- The objectivity constraint is spec-loop-specific because specs lack objective correctness criteria, making subjective drift more likely in iterative passes. -->
|
|
95
|
+
|
|
62
96
|
NOW RUN A FULL REVIEW (with cross-validation):
|
|
63
97
|
|
|
64
|
-
Unlike the code loop, spec review iterations ALWAYS include cross-validation.
|
|
65
|
-
|
|
66
|
-
than converge, producing more findings each iteration instead of fewer.
|
|
98
|
+
Unlike the code loop, spec review iterations ALWAYS include cross-validation. Without validators
|
|
99
|
+
filtering subjective opinions, reviewers diverge rather than converge.
|
|
67
100
|
|
|
68
101
|
Stage 1 — DISPATCH 5 PARALLEL REVIEWERS:
|
|
69
|
-
Each reviewer prompt MUST include: "This is a fresh review. You have no prior context about this spec. Review it as if seeing it for the first time. Do not assume anything is correct just because it looks intentional. Focus ONLY on objective issues (contradictions, gaps, impossibilities) — do NOT flag stylistic preferences or reorganization suggestions."
|
|
70
102
|
|
|
71
103
|
Task 1 — Use the Task tool with subagent_type="deepreview-spec-completeness":
|
|
72
|
-
"
|
|
104
|
+
"$REVIEWER_PREAMBLE
|
|
105
|
+
|
|
106
|
+
Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-completeness.md."
|
|
73
107
|
|
|
74
108
|
Task 2 — Use the Task tool with subagent_type="deepreview-spec-consistency":
|
|
75
|
-
"
|
|
109
|
+
"$REVIEWER_PREAMBLE
|
|
110
|
+
|
|
111
|
+
Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-consistency.md."
|
|
76
112
|
|
|
77
113
|
Task 3 — Use the Task tool with subagent_type="deepreview-spec-feasibility":
|
|
78
|
-
"
|
|
114
|
+
"$REVIEWER_PREAMBLE
|
|
115
|
+
|
|
116
|
+
Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-feasibility.md."
|
|
79
117
|
|
|
80
118
|
Task 4 — Use the Task tool with subagent_type="deepreview-docs":
|
|
81
|
-
"
|
|
119
|
+
"$REVIEWER_PREAMBLE
|
|
120
|
+
|
|
121
|
+
Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-docs.md."
|
|
82
122
|
|
|
83
123
|
Task 5 — Use the Task tool with subagent_type="deepreview-architecture":
|
|
84
|
-
"
|
|
124
|
+
"$REVIEWER_PREAMBLE
|
|
125
|
+
|
|
126
|
+
Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-architecture.md."
|
|
85
127
|
|
|
86
128
|
Wait for all 5. Record which succeeded.
|
|
87
129
|
|
|
88
130
|
Stage 2 — DISPATCH 5 PARALLEL VALIDATORS (cross-validation):
|
|
89
131
|
Task 6-10 — Use the Task tool with subagent_type="deepreview-validator" (5 times, one per perspective):
|
|
90
132
|
Each validator reads ALL review files and writes to $SESSION_DIR/validated-{completeness,consistency,feasibility,docs,architecture}.md.
|
|
133
|
+
Note: Validators intentionally do NOT receive PRIOR_CONTEXT. They filter on objective merit only — whether findings are technically valid and actionable. The novelty filter is applied at the reviewer level.
|
|
91
134
|
|
|
92
135
|
Wait for all 5.
|
|
93
136
|
|
|
@@ -108,7 +151,7 @@ Go to STEP 3.
|
|
|
108
151
|
STEP 6: DIVERGENCE AND DEADLOCK DETECTION
|
|
109
152
|
Track finding counts across iterations. Detect TWO failure modes:
|
|
110
153
|
|
|
111
|
-
A) DIVERGENCE: If total findings
|
|
154
|
+
A) DIVERGENCE: If total findings INCREASE from one iteration to the next:
|
|
112
155
|
|
|
113
156
|
- Tell the user: "Divergence detected: findings increased from N to M. The review is not converging — fixes are introducing new issues or reviewers are finding new stylistic concerns."
|
|
114
157
|
- Show the iteration-over-iteration stats.
|
|
@@ -130,4 +173,7 @@ IMPORTANT RULES:
|
|
|
130
173
|
- Do NOT ask the user for permission to apply fixes. Apply automatically.
|
|
131
174
|
- DO ask the user if iteration limit is hit, divergence is detected, or deadlock is detected.
|
|
132
175
|
- ALL iterations include cross-validation (unlike the code loop).
|
|
176
|
+
- Iteration 2+ MUST include PRIOR_CONTEXT and novelty-seeking framing.
|
|
177
|
+
- Iteration 2+ MUST NOT tell reviewers to "verify" or "check status of" prior findings.
|
|
133
178
|
- Each iteration uses a NEW session directory — never reuse a previous one.
|
|
179
|
+
- If --context file is provided, include its contents under "Design Decisions" in PRIOR_CONTEXT for ALL iterations (including iter1).
|
|
@@ -6,12 +6,14 @@ You are an orchestrator for a multi-agent spec/plan review pipeline. Follow thes
|
|
|
6
6
|
|
|
7
7
|
STEP 1: DETERMINE SESSION DIRECTORY
|
|
8
8
|
|
|
9
|
+
- If "$ARGUMENTS" starts with `--context <path>`, extract CONTEXT_FILE=<path> and remove it from $ARGUMENTS before parsing the rest.
|
|
10
|
+
- Validate CONTEXT_FILE: it must be a relative path (no leading `/`), must not contain `..`, must exist on disk, and must be a regular file (not a directory or symlink to outside the project), and must be under 50KB. If validation fails, tell the user the error and STOP.
|
|
9
11
|
- Set SESSION_DIR=".ai/deepreview/spec-$(date +%Y-%m-%d-%H%M%S)"
|
|
10
12
|
- Create the directory with `mkdir -p $SESSION_DIR`
|
|
11
13
|
|
|
12
14
|
STEP 2: PREPARE INPUT
|
|
13
15
|
|
|
14
|
-
- If "$ARGUMENTS" is empty, tell the user "Usage: /deepreview-spec <file1
|
|
16
|
+
- If remaining "$ARGUMENTS" is empty, tell the user "Usage: /deepreview-spec [--context <file>] <file1> [file2 ...]" and STOP.
|
|
15
17
|
- Concatenate all specified files into $SESSION_DIR/input.txt with headers:
|
|
16
18
|
For each file, write a header line "=== <filename> ===" followed by the file contents.
|
|
17
19
|
Use: `for f in $ARGUMENTS; do echo "=== $f ===" >> $SESSION_DIR/input.txt; cat "$f" >> $SESSION_DIR/input.txt; echo >> $SESSION_DIR/input.txt; done`
|
|
@@ -19,23 +21,28 @@ STEP 2: PREPARE INPUT
|
|
|
19
21
|
|
|
20
22
|
Set INPUT_DESCRIPTION="the following spec/plan files: $ARGUMENTS"
|
|
21
23
|
|
|
24
|
+
If CONTEXT_FILE exists, set DESIGN_CONTEXT to the contents of that file. Build a CONTEXT_PREAMBLE:
|
|
25
|
+
"## Design Decisions (intentional — do not flag)\nThe following are deliberate design choices. Do NOT flag these as issues or suggest alternatives.\n`\n$DESIGN_CONTEXT\n`\n\n"
|
|
26
|
+
|
|
27
|
+
If CONTEXT_FILE does not exist, set CONTEXT_PREAMBLE="" (empty string).
|
|
28
|
+
|
|
22
29
|
STEP 3: DISPATCH STAGE 1 — INITIAL REVIEW (5 parallel tasks)
|
|
23
30
|
Dispatch ALL FIVE of these Task tool calls simultaneously in a single message:
|
|
24
31
|
|
|
25
32
|
Task 1 — Use the Task tool with subagent_type="deepreview-spec-completeness":
|
|
26
|
-
"You are reviewing $INPUT_DESCRIPTION. Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-completeness.md."
|
|
33
|
+
"${CONTEXT_PREAMBLE}You are reviewing $INPUT_DESCRIPTION. Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-completeness.md."
|
|
27
34
|
|
|
28
35
|
Task 2 — Use the Task tool with subagent_type="deepreview-spec-consistency":
|
|
29
|
-
"You are reviewing $INPUT_DESCRIPTION. Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-consistency.md."
|
|
36
|
+
"${CONTEXT_PREAMBLE}You are reviewing $INPUT_DESCRIPTION. Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-consistency.md."
|
|
30
37
|
|
|
31
38
|
Task 3 — Use the Task tool with subagent_type="deepreview-spec-feasibility":
|
|
32
|
-
"You are reviewing $INPUT_DESCRIPTION. Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-feasibility.md."
|
|
39
|
+
"${CONTEXT_PREAMBLE}You are reviewing $INPUT_DESCRIPTION. Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-feasibility.md."
|
|
33
40
|
|
|
34
41
|
Task 4 — Use the Task tool with subagent_type="deepreview-docs":
|
|
35
|
-
"You are reviewing $INPUT_DESCRIPTION. Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-docs.md."
|
|
42
|
+
"${CONTEXT_PREAMBLE}You are reviewing $INPUT_DESCRIPTION. Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-docs.md."
|
|
36
43
|
|
|
37
44
|
Task 5 — Use the Task tool with subagent_type="deepreview-architecture":
|
|
38
|
-
"You are reviewing $INPUT_DESCRIPTION. Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-architecture.md."
|
|
45
|
+
"${CONTEXT_PREAMBLE}You are reviewing $INPUT_DESCRIPTION. Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-architecture.md."
|
|
39
46
|
|
|
40
47
|
Wait for all 5 to return. Record which succeeded and which failed.
|
|
41
48
|
|
|
@@ -7,6 +7,8 @@ You are an orchestrator for a multi-agent code review pipeline. Follow these ste
|
|
|
7
7
|
STEP 1: DETERMINE INPUT MODE AND SESSION DIRECTORY
|
|
8
8
|
Classify "$ARGUMENTS":
|
|
9
9
|
|
|
10
|
+
- If it starts with `--context <path>`, extract CONTEXT_FILE=<path> and remove it from $ARGUMENTS before parsing the rest.
|
|
11
|
+
- Validate CONTEXT_FILE: it must be a relative path (no leading `/`), must not contain `..`, must exist on disk, and must be a regular file (not a directory or symlink to outside the project), and must be under 50KB. If validation fails, tell the user the error and STOP.
|
|
10
12
|
- If it is a number → MODE=pr
|
|
11
13
|
- If it is a file path (ends in .md, .txt, .yaml, .json, or file exists on disk) → MODE=files
|
|
12
14
|
- If it is multiple space-separated file paths → MODE=files
|
|
@@ -36,23 +38,28 @@ Set INPUT_DESCRIPTION based on mode:
|
|
|
36
38
|
- MODE=branch: "a branch diff against main"
|
|
37
39
|
- MODE=files: "the following files: <list of filenames>"
|
|
38
40
|
|
|
41
|
+
If CONTEXT_FILE exists, set DESIGN_CONTEXT to the contents of that file. Build a CONTEXT_PREAMBLE:
|
|
42
|
+
"## Design Decisions (intentional — do not flag)\nThe following are deliberate design choices. Do NOT flag these as issues or suggest alternatives.\n`\n$DESIGN_CONTEXT\n`\n\n"
|
|
43
|
+
|
|
44
|
+
If CONTEXT_FILE does not exist, set CONTEXT_PREAMBLE="" (empty string).
|
|
45
|
+
|
|
39
46
|
STEP 3: DISPATCH STAGE 1 — INITIAL REVIEW (5 parallel tasks)
|
|
40
47
|
Dispatch ALL FIVE of these Task tool calls simultaneously in a single message:
|
|
41
48
|
|
|
42
49
|
Task 1 — Use the Task tool with subagent_type="deepreview-correctness":
|
|
43
|
-
"You are reviewing $INPUT_DESCRIPTION. Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-correctness.md."
|
|
50
|
+
"${CONTEXT_PREAMBLE}You are reviewing $INPUT_DESCRIPTION. Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-correctness.md."
|
|
44
51
|
|
|
45
52
|
Task 2 — Use the Task tool with subagent_type="deepreview-security":
|
|
46
|
-
"You are reviewing $INPUT_DESCRIPTION. Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-security.md."
|
|
53
|
+
"${CONTEXT_PREAMBLE}You are reviewing $INPUT_DESCRIPTION. Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-security.md."
|
|
47
54
|
|
|
48
55
|
Task 3 — Use the Task tool with subagent_type="deepreview-architecture":
|
|
49
|
-
"You are reviewing $INPUT_DESCRIPTION. Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-architecture.md."
|
|
56
|
+
"${CONTEXT_PREAMBLE}You are reviewing $INPUT_DESCRIPTION. Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-architecture.md."
|
|
50
57
|
|
|
51
58
|
Task 4 — Use the Task tool with subagent_type="deepreview-docs":
|
|
52
|
-
"You are reviewing $INPUT_DESCRIPTION. Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-docs.md."
|
|
59
|
+
"${CONTEXT_PREAMBLE}You are reviewing $INPUT_DESCRIPTION. Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-docs.md."
|
|
53
60
|
|
|
54
61
|
Task 5 — Use the Task tool with subagent_type="deepreview-compatibility":
|
|
55
|
-
"You are reviewing $INPUT_DESCRIPTION. Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-compatibility.md."
|
|
62
|
+
"${CONTEXT_PREAMBLE}You are reviewing $INPUT_DESCRIPTION. Read the content at $SESSION_DIR/input.txt. Write your review to $SESSION_DIR/review-compatibility.md."
|
|
56
63
|
|
|
57
64
|
Wait for all 5 to return. Record which succeeded and which failed.
|
|
58
65
|
|
package/README.md
CHANGED
|
@@ -6,15 +6,28 @@ implementation plan.
|
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Run the setup script:
|
|
10
10
|
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
```bash
|
|
12
|
+
bunx @mechanai/deepreview@latest/setup # Global install (~/.config/opencode/)
|
|
13
|
+
bunx @mechanai/deepreview@latest/setup --local # Project-level install (.opencode/)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Or with Node.js (v22+):
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npx @mechanai/deepreview@latest/setup
|
|
20
|
+
npx @mechanai/deepreview@latest/setup --local
|
|
15
21
|
```
|
|
16
22
|
|
|
17
|
-
|
|
23
|
+
This will:
|
|
24
|
+
|
|
25
|
+
1. Add `@mechanai/deepreview` to the `plugin` array in your `opencode.json` (creates the file if needed)
|
|
26
|
+
2. Symlink agents and commands into the appropriate config directory
|
|
27
|
+
|
|
28
|
+
> [!NOTE]
|
|
29
|
+
> The symlinks are needed because OpenCode does not yet auto-discover
|
|
30
|
+
> agents and commands from installed plugin packages.
|
|
18
31
|
|
|
19
32
|
## Usage
|
|
20
33
|
|
|
@@ -22,14 +35,18 @@ OpenCode installs the package automatically at startup.
|
|
|
22
35
|
/deepreview # Review current branch vs main
|
|
23
36
|
/deepreview 123 # Review PR #123
|
|
24
37
|
/deepreview file1.ts file2.ts # Review specific files
|
|
38
|
+
/deepreview --context decisions.md # Review with design context (suppresses known decisions)
|
|
25
39
|
|
|
26
40
|
/deepreview-loop # Review + fix loop (repeats until clean or 5 iterations)
|
|
27
41
|
/deepreview-loop 123 # Same, targeting a PR
|
|
42
|
+
/deepreview-loop --context decisions.md # Loop with design context
|
|
43
|
+
/deepreview-spec-loop --context decisions.md spec.md # Spec loop with design context
|
|
28
44
|
|
|
29
45
|
/deepreview-pr-review 123 # Review PR and post findings as a pending GitHub review
|
|
30
46
|
|
|
31
|
-
/deepreview-spec spec.md
|
|
32
|
-
/deepreview-spec
|
|
47
|
+
/deepreview-spec spec.md # Spec-focused review (completeness, consistency, feasibility)
|
|
48
|
+
/deepreview-spec --context decisions.md spec.md # Spec review with design context
|
|
49
|
+
/deepreview-spec-loop spec.md # Spec review + fix loop
|
|
33
50
|
```
|
|
34
51
|
|
|
35
52
|
All commands accept a branch diff, PR number, or file path(s). The `-loop` variants
|
|
@@ -62,12 +79,16 @@ its own context, keeping token usage minimal.
|
|
|
62
79
|
## Requirements
|
|
63
80
|
|
|
64
81
|
- [OpenCode](https://opencode.ai)
|
|
82
|
+
- [Bun](https://bun.sh/) >= 1.2 or [Node.js](https://nodejs.org/) >= 22
|
|
65
83
|
- `git`
|
|
66
84
|
- `gh` CLI (only for PR commands)
|
|
67
85
|
|
|
68
86
|
> [!NOTE]
|
|
69
87
|
> If upgrading from the old `npx @anthropic/deepreview install` workflow, remove
|
|
70
|
-
>
|
|
88
|
+
> the old copied files first (`rm ~/.config/opencode/agents/deepreview*
|
|
89
|
+
~/.config/opencode/commands/deepreview*`), then run the setup script above.
|
|
90
|
+
> The setup script uses symlinks instead of copies, so future upgrades only
|
|
91
|
+
> require re-running the script.
|
|
71
92
|
|
|
72
93
|
## Development
|
|
73
94
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mechanai/deepreview",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Multi-agent parallel code/spec review for OpenCode",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -9,13 +9,15 @@
|
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"src/",
|
|
12
|
+
"dist/",
|
|
12
13
|
".opencode/"
|
|
13
14
|
],
|
|
14
15
|
"type": "module",
|
|
15
16
|
"main": "./.opencode/plugins/deepreview.ts",
|
|
16
17
|
"exports": {
|
|
17
18
|
".": "./.opencode/plugins/deepreview.ts",
|
|
18
|
-
"./api": "./src/post-review.ts"
|
|
19
|
+
"./api": "./src/post-review.ts",
|
|
20
|
+
"./setup": "./dist/setup.mjs"
|
|
19
21
|
},
|
|
20
22
|
"publishConfig": {
|
|
21
23
|
"access": "public"
|
|
@@ -23,6 +25,7 @@
|
|
|
23
25
|
"dependencies": {
|
|
24
26
|
"gray-matter": "4.0.3",
|
|
25
27
|
"js-yaml": "4.1.0",
|
|
28
|
+
"jsonc-parser": "3.3.1",
|
|
26
29
|
"parse-diff": "0.11.1"
|
|
27
30
|
},
|
|
28
31
|
"devDependencies": {
|
|
@@ -42,6 +45,7 @@
|
|
|
42
45
|
}
|
|
43
46
|
},
|
|
44
47
|
"engines": {
|
|
45
|
-
"bun": ">=1.2.0"
|
|
48
|
+
"bun": ">=1.2.0",
|
|
49
|
+
"node": ">=22.0.0"
|
|
46
50
|
}
|
|
47
51
|
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { describe, expect, test, beforeEach, afterEach } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
existsSync,
|
|
4
|
+
lstatSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
symlinkSync,
|
|
8
|
+
writeFileSync,
|
|
9
|
+
} from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import os from "node:os";
|
|
12
|
+
|
|
13
|
+
const setupScript = path.resolve(import.meta.dirname, "setup.ts");
|
|
14
|
+
|
|
15
|
+
function makeTempDir(): string {
|
|
16
|
+
const dir = path.join(
|
|
17
|
+
os.tmpdir(),
|
|
18
|
+
`deepreview-setup-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
19
|
+
);
|
|
20
|
+
mkdirSync(dir, { recursive: true });
|
|
21
|
+
return dir;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function run(cwd: string, args: string[] = []) {
|
|
25
|
+
return Bun.spawnSync(["bun", "run", setupScript, ...args], {
|
|
26
|
+
cwd,
|
|
27
|
+
env: { ...process.env, XDG_CONFIG_HOME: cwd },
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function readConfig(filePath: string): { plugin?: unknown } {
|
|
32
|
+
const parsed: unknown = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
33
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
34
|
+
throw new Error("config is not an object");
|
|
35
|
+
}
|
|
36
|
+
return parsed as { plugin?: unknown };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe("setup script - config", () => {
|
|
40
|
+
let tempDir: string;
|
|
41
|
+
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
tempDir = makeTempDir();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
afterEach(async () => {
|
|
47
|
+
await Bun.$`rm -rf ${tempDir}`;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("creates opencode.json with plugin when no config exists (global)", () => {
|
|
51
|
+
const result = run(tempDir);
|
|
52
|
+
expect(result.exitCode).toBe(0);
|
|
53
|
+
|
|
54
|
+
const configPath = path.join(tempDir, "opencode", "opencode.json");
|
|
55
|
+
expect(existsSync(configPath)).toBe(true);
|
|
56
|
+
|
|
57
|
+
const config = readConfig(configPath);
|
|
58
|
+
expect(config.plugin).toContain("@mechanai/deepreview");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("--local installs into .opencode/ in cwd", () => {
|
|
62
|
+
const result = run(tempDir, ["--local"]);
|
|
63
|
+
expect(result.exitCode).toBe(0);
|
|
64
|
+
|
|
65
|
+
const configPath = path.join(tempDir, "opencode.json");
|
|
66
|
+
expect(existsSync(configPath)).toBe(true);
|
|
67
|
+
|
|
68
|
+
const agentsDir = path.join(tempDir, ".opencode", "agents");
|
|
69
|
+
expect(existsSync(path.join(agentsDir, "deepreview-synthesizer.md"))).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("adds plugin to existing config without duplicating", () => {
|
|
73
|
+
const configPath = path.join(tempDir, "opencode");
|
|
74
|
+
mkdirSync(configPath, { recursive: true });
|
|
75
|
+
writeFileSync(
|
|
76
|
+
path.join(configPath, "opencode.json"),
|
|
77
|
+
JSON.stringify({ plugin: ["other-plugin"] }, null, 2),
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
run(tempDir);
|
|
81
|
+
const config = readConfig(path.join(configPath, "opencode.json"));
|
|
82
|
+
expect(config.plugin).toEqual(["other-plugin", "@mechanai/deepreview"]);
|
|
83
|
+
|
|
84
|
+
// Run again — should not duplicate
|
|
85
|
+
run(tempDir);
|
|
86
|
+
const config2 = readConfig(path.join(configPath, "opencode.json"));
|
|
87
|
+
expect(config2.plugin).toEqual(["other-plugin", "@mechanai/deepreview"]);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("preserves JSONC comments", () => {
|
|
91
|
+
const configDir = path.join(tempDir, "opencode");
|
|
92
|
+
mkdirSync(configDir, { recursive: true });
|
|
93
|
+
const content = '{\n // My comment\n "provider": {}\n}\n';
|
|
94
|
+
writeFileSync(path.join(configDir, "opencode.jsonc"), content);
|
|
95
|
+
|
|
96
|
+
run(tempDir);
|
|
97
|
+
const result = readFileSync(path.join(configDir, "opencode.jsonc"), "utf-8");
|
|
98
|
+
expect(result).toContain("// My comment");
|
|
99
|
+
expect(result).toContain("@mechanai/deepreview");
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe("setup script - symlinks", () => {
|
|
104
|
+
let tempDir: string;
|
|
105
|
+
|
|
106
|
+
beforeEach(() => {
|
|
107
|
+
tempDir = makeTempDir();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
afterEach(async () => {
|
|
111
|
+
await Bun.$`rm -rf ${tempDir}`;
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("creates symlinks for agents and commands (global)", () => {
|
|
115
|
+
const result = run(tempDir);
|
|
116
|
+
expect(result.exitCode).toBe(0);
|
|
117
|
+
|
|
118
|
+
const agentsDir = path.join(tempDir, "opencode", "agents");
|
|
119
|
+
const synthesizer = path.join(agentsDir, "deepreview-synthesizer.md");
|
|
120
|
+
expect(existsSync(synthesizer)).toBe(true);
|
|
121
|
+
expect(lstatSync(synthesizer).isSymbolicLink()).toBe(true);
|
|
122
|
+
|
|
123
|
+
const commandsDir = path.join(tempDir, "opencode", "commands");
|
|
124
|
+
const mainCmd = path.join(commandsDir, "deepreview.md");
|
|
125
|
+
expect(existsSync(mainCmd)).toBe(true);
|
|
126
|
+
expect(lstatSync(mainCmd).isSymbolicLink()).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("removes stale symlinks on upgrade", () => {
|
|
130
|
+
const agentsDir = path.join(tempDir, "opencode", "agents");
|
|
131
|
+
mkdirSync(agentsDir, { recursive: true });
|
|
132
|
+
|
|
133
|
+
// Create a fake stale symlink that contains "deepreview" but doesn't exist in package
|
|
134
|
+
symlinkSync("/nonexistent", path.join(agentsDir, "deepreview-old-agent.md"));
|
|
135
|
+
|
|
136
|
+
run(tempDir);
|
|
137
|
+
|
|
138
|
+
// Stale symlink should be removed
|
|
139
|
+
expect(existsSync(path.join(agentsDir, "deepreview-old-agent.md"))).toBe(false);
|
|
140
|
+
// But real agents should exist
|
|
141
|
+
expect(existsSync(path.join(agentsDir, "deepreview-synthesizer.md"))).toBe(true);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("handles dangling symlinks at dest gracefully", () => {
|
|
145
|
+
const agentsDir = path.join(tempDir, "opencode", "agents");
|
|
146
|
+
mkdirSync(agentsDir, { recursive: true });
|
|
147
|
+
|
|
148
|
+
// Create a dangling symlink where a real agent should go
|
|
149
|
+
symlinkSync("/nonexistent", path.join(agentsDir, "deepreview-synthesizer.md"));
|
|
150
|
+
|
|
151
|
+
const result = run(tempDir);
|
|
152
|
+
expect(result.exitCode).toBe(0);
|
|
153
|
+
|
|
154
|
+
// Should have replaced the dangling symlink
|
|
155
|
+
const dest = path.join(agentsDir, "deepreview-synthesizer.md");
|
|
156
|
+
expect(lstatSync(dest).isSymbolicLink()).toBe(true);
|
|
157
|
+
expect(existsSync(dest)).toBe(true);
|
|
158
|
+
});
|
|
159
|
+
});
|
package/src/setup.ts
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Setup script for @mechanai/deepreview.
|
|
3
|
+
*
|
|
4
|
+
* Ensures the plugin is registered in opencode.json and symlinks agents/commands
|
|
5
|
+
* into the OpenCode config directory.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* bunx @mechanai/deepreview/setup # Install globally (~/.config/opencode/)
|
|
9
|
+
* bunx @mechanai/deepreview/setup --local # Install into current project (.opencode/)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
existsSync,
|
|
14
|
+
lstatSync,
|
|
15
|
+
mkdirSync,
|
|
16
|
+
readdirSync,
|
|
17
|
+
symlinkSync,
|
|
18
|
+
unlinkSync,
|
|
19
|
+
readFileSync,
|
|
20
|
+
writeFileSync,
|
|
21
|
+
} from "node:fs";
|
|
22
|
+
import path from "node:path";
|
|
23
|
+
import os from "node:os";
|
|
24
|
+
import { applyEdits, modify, parse as parseJsonc } from "jsonc-parser";
|
|
25
|
+
|
|
26
|
+
const PACKAGE_NAME = "@mechanai/deepreview";
|
|
27
|
+
const local = process.argv.includes("--local");
|
|
28
|
+
const cwd = process.cwd();
|
|
29
|
+
|
|
30
|
+
const globalConfigDir = path.join(
|
|
31
|
+
process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config"),
|
|
32
|
+
"opencode",
|
|
33
|
+
);
|
|
34
|
+
const targetDir = local ? path.join(cwd, ".opencode") : globalConfigDir;
|
|
35
|
+
|
|
36
|
+
// Resolve the package directory (where this script lives)
|
|
37
|
+
const packageDir = path.resolve(import.meta.dirname, "..");
|
|
38
|
+
const packageOpencode = path.join(packageDir, ".opencode");
|
|
39
|
+
|
|
40
|
+
function ensurePluginInConfig() {
|
|
41
|
+
const configFiles = ["opencode.jsonc", "opencode.json"];
|
|
42
|
+
const searchDir = local ? cwd : globalConfigDir;
|
|
43
|
+
let configPath: string | undefined;
|
|
44
|
+
|
|
45
|
+
for (const file of configFiles) {
|
|
46
|
+
const candidate = path.join(searchDir, file);
|
|
47
|
+
if (existsSync(candidate)) {
|
|
48
|
+
configPath = candidate;
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (configPath === undefined) {
|
|
54
|
+
configPath = path.join(searchDir, "opencode.json");
|
|
55
|
+
mkdirSync(searchDir, { recursive: true });
|
|
56
|
+
writeFileSync(configPath, JSON.stringify({ plugin: [PACKAGE_NAME] }, null, 2) + "\n");
|
|
57
|
+
console.log(`Created ${configPath} with plugin entry.`);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
62
|
+
|
|
63
|
+
// Check if plugin is already registered
|
|
64
|
+
let config: Record<string, unknown>;
|
|
65
|
+
try {
|
|
66
|
+
const parsed: unknown = parseJsonc(raw);
|
|
67
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
68
|
+
throw new Error("not an object");
|
|
69
|
+
}
|
|
70
|
+
// oxlint-disable-next-line no-unsafe-type-assertion -- Why: validated above with type guards
|
|
71
|
+
config = parsed as Record<string, unknown>;
|
|
72
|
+
} catch {
|
|
73
|
+
console.error(`Could not parse ${configPath}. Add the plugin manually:`);
|
|
74
|
+
console.error(` "plugin": ["${PACKAGE_NAME}"]`);
|
|
75
|
+
process.exitCode = 1;
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const pluginArray = Array.isArray(config.plugin) ? config.plugin : [];
|
|
80
|
+
const plugins = pluginArray.filter((p): p is string => typeof p === "string");
|
|
81
|
+
if (plugins.includes(PACKAGE_NAME)) {
|
|
82
|
+
console.log(`Plugin already registered in ${configPath}.`);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Use jsonc-parser to insert into the plugin array without stripping comments
|
|
87
|
+
const formatting = { formattingOptions: { insertSpaces: true, tabSize: 2 } };
|
|
88
|
+
const edits = Array.isArray(config.plugin)
|
|
89
|
+
? modify(raw, ["plugin", pluginArray.length], PACKAGE_NAME, formatting)
|
|
90
|
+
: modify(raw, ["plugin"], [PACKAGE_NAME], formatting);
|
|
91
|
+
|
|
92
|
+
writeFileSync(configPath, applyEdits(raw, edits));
|
|
93
|
+
console.log(`Added "${PACKAGE_NAME}" to plugin array in ${configPath}.`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function symlinkDirectory(kind: "agents" | "commands") {
|
|
97
|
+
const sourceDir = path.join(packageOpencode, kind);
|
|
98
|
+
if (!existsSync(sourceDir)) return;
|
|
99
|
+
|
|
100
|
+
const destDir = path.join(targetDir, kind);
|
|
101
|
+
mkdirSync(destDir, { recursive: true });
|
|
102
|
+
|
|
103
|
+
const sourceFiles = new Set(readdirSync(sourceDir).filter((f) => f.endsWith(".md")));
|
|
104
|
+
let created = 0;
|
|
105
|
+
|
|
106
|
+
// Remove stale deepreview symlinks that no longer exist in the package
|
|
107
|
+
for (const file of readdirSync(destDir)) {
|
|
108
|
+
if (!file.startsWith("deepreview-") && !file.startsWith("_deepreview-")) continue;
|
|
109
|
+
const dest = path.join(destDir, file);
|
|
110
|
+
try {
|
|
111
|
+
if (lstatSync(dest).isSymbolicLink() && !sourceFiles.has(file)) {
|
|
112
|
+
unlinkSync(dest);
|
|
113
|
+
}
|
|
114
|
+
} catch (err: unknown) {
|
|
115
|
+
if (err instanceof Error && !("code" in err && err.code === "ENOENT")) {
|
|
116
|
+
console.warn(`Could not check ${dest}: ${err.message}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
for (const file of sourceFiles) {
|
|
122
|
+
const dest = path.join(destDir, file);
|
|
123
|
+
const source = path.relative(destDir, path.join(sourceDir, file));
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
// Use lstatSync to detect both regular files and dangling symlinks
|
|
127
|
+
const stat = lstatSync(dest);
|
|
128
|
+
if (!stat.isSymbolicLink()) {
|
|
129
|
+
console.warn(`Skipping ${dest}: not a symlink (would overwrite regular file)`);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
unlinkSync(dest);
|
|
133
|
+
} catch (err: unknown) {
|
|
134
|
+
if (err instanceof Error && "code" in err && err.code !== "ENOENT") {
|
|
135
|
+
throw err;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
symlinkSync(source, dest);
|
|
139
|
+
created++;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const label = local ? `.opencode/${kind}/` : path.join(targetDir, kind) + "/";
|
|
143
|
+
console.log(`Linked ${created} ${kind} into ${label}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Run
|
|
147
|
+
ensurePluginInConfig();
|
|
148
|
+
symlinkDirectory("agents");
|
|
149
|
+
symlinkDirectory("commands");
|
|
150
|
+
const scope = local ? "project" : "global";
|
|
151
|
+
console.log(`Done (${scope}). Run opencode to use /deepreview commands.`);
|