supipowers 0.3.0 → 0.5.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/package.json +1 -1
- package/skills/fix-pr/SKILL.md +99 -0
- package/skills/qa-strategy/SKILL.md +103 -21
- package/src/commands/fix-pr.ts +324 -0
- package/src/commands/qa.ts +232 -148
- package/src/commands/supi.ts +2 -1
- package/src/config/defaults.ts +1 -0
- package/src/config/schema.ts +1 -0
- package/src/fix-pr/config.ts +36 -0
- package/src/fix-pr/prompt-builder.ts +201 -0
- package/src/fix-pr/scripts/diff-comments.sh +33 -0
- package/src/fix-pr/scripts/fetch-pr-comments.sh +25 -0
- package/src/fix-pr/scripts/trigger-review.sh +36 -0
- package/src/fix-pr/scripts/wait-and-check.sh +37 -0
- package/src/fix-pr/types.ts +71 -0
- package/src/index.ts +2 -0
- package/src/qa/config.ts +43 -0
- package/src/qa/matrix.ts +84 -0
- package/src/qa/prompt-builder.ts +212 -0
- package/src/qa/scripts/detect-app-type.sh +68 -0
- package/src/qa/scripts/discover-routes.sh +143 -0
- package/src/qa/scripts/ensure-playwright.sh +38 -0
- package/src/qa/scripts/run-e2e-tests.sh +99 -0
- package/src/qa/scripts/start-dev-server.sh +46 -0
- package/src/qa/scripts/stop-dev-server.sh +36 -0
- package/src/qa/session.ts +39 -55
- package/src/qa/types.ts +97 -0
- package/src/storage/fix-pr-sessions.ts +59 -0
- package/src/storage/qa-sessions.ts +9 -9
- package/src/types.ts +1 -70
- package/src/qa/detector.ts +0 -61
- package/src/qa/phases/discovery.ts +0 -34
- package/src/qa/phases/execution.ts +0 -65
- package/src/qa/phases/matrix.ts +0 -41
- package/src/qa/phases/reporting.ts +0 -71
- package/src/qa/report.ts +0 -22
- package/src/qa/runner.ts +0 -46
package/package.json
CHANGED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fix-pr
|
|
3
|
+
description: Critically assess PR review comments — verify, investigate ripple effects, then fix or reject with evidence
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# PR Review Comment Assessment
|
|
7
|
+
|
|
8
|
+
## Core Principle
|
|
9
|
+
|
|
10
|
+
Review comments are suggestions to evaluate, not orders to follow.
|
|
11
|
+
Assess each one critically before acting. The reviewer may lack context you have.
|
|
12
|
+
|
|
13
|
+
## Assessment Framework
|
|
14
|
+
|
|
15
|
+
### For Each Comment, Answer:
|
|
16
|
+
|
|
17
|
+
1. **Is this valid?** Read the actual code being commented on. Does the concern apply?
|
|
18
|
+
2. **Is this important?** Bug fix vs style preference vs premature optimization.
|
|
19
|
+
3. **What breaks if we change this?** Trace callers, check tests, find ripple effects.
|
|
20
|
+
4. **Does the reviewer have full context?** They often review diffs, not the full picture.
|
|
21
|
+
5. **Is this YAGNI?** "You should also handle X" — but does X actually occur?
|
|
22
|
+
|
|
23
|
+
### Verdict Categories
|
|
24
|
+
|
|
25
|
+
- **ACCEPT**: Valid concern, should fix. Evidence: the code has the problem described.
|
|
26
|
+
- **REJECT**: Invalid, unnecessary, or would cause harm. Evidence: why this doesn't apply.
|
|
27
|
+
- **INVESTIGATE**: Need to check more before deciding. List what to check.
|
|
28
|
+
|
|
29
|
+
### Investigation Protocol
|
|
30
|
+
|
|
31
|
+
When INVESTIGATE:
|
|
32
|
+
1. Read the file(s) mentioned in full (not just the diff)
|
|
33
|
+
2. Search for usages of the symbol/pattern being discussed
|
|
34
|
+
3. Check test coverage for the area
|
|
35
|
+
4. Look at git blame — why is the code written this way?
|
|
36
|
+
5. Then decide ACCEPT or REJECT with evidence
|
|
37
|
+
|
|
38
|
+
## Ripple Effect Analysis
|
|
39
|
+
|
|
40
|
+
Before accepting any change:
|
|
41
|
+
1. **Who calls this?** Search for usages of the function/method/class
|
|
42
|
+
2. **Who depends on this behavior?** Check tests that assert current behavior
|
|
43
|
+
3. **What imports this?** Follow the dependency graph
|
|
44
|
+
4. **Is this a public API?** Changes to public interfaces affect consumers
|
|
45
|
+
|
|
46
|
+
If ripple effects are significant, note them in the plan so the fixer handles them.
|
|
47
|
+
|
|
48
|
+
## Grouping Strategy
|
|
49
|
+
|
|
50
|
+
Group comments that:
|
|
51
|
+
- Touch the same file
|
|
52
|
+
- Touch tightly coupled files (caller/callee, type/implementation)
|
|
53
|
+
- Relate to the same logical concern (e.g., "error handling in module X")
|
|
54
|
+
|
|
55
|
+
Keep separate:
|
|
56
|
+
- Comments on unrelated files/areas
|
|
57
|
+
- Cosmetic vs functional changes
|
|
58
|
+
- Independent features or concerns
|
|
59
|
+
|
|
60
|
+
## Comment Reply Guidelines
|
|
61
|
+
|
|
62
|
+
### For ACCEPT:
|
|
63
|
+
- "Fixed. [description of change]."
|
|
64
|
+
- "Fixed in [file]. Also updated [related file] to maintain consistency."
|
|
65
|
+
|
|
66
|
+
### For REJECT:
|
|
67
|
+
- "Investigated — [reason this doesn't apply]. The current implementation [explanation]."
|
|
68
|
+
- "This is intentional: [reason]. Changing it would [consequence]."
|
|
69
|
+
|
|
70
|
+
### For grouped fixes:
|
|
71
|
+
- "Addressed these comments together in [commit]. Changes: [bullet list]."
|
|
72
|
+
|
|
73
|
+
**Never use performative agreement.** No "Great catch!", "You're absolutely right!", etc.
|
|
74
|
+
Technical acknowledgment only.
|
|
75
|
+
|
|
76
|
+
## Common Reviewer Mistakes to Watch For
|
|
77
|
+
|
|
78
|
+
| Pattern | Reality |
|
|
79
|
+
|---------|---------|
|
|
80
|
+
| Suggesting abstraction for code used once | YAGNI — one usage doesn't need a helper |
|
|
81
|
+
| Requesting error handling for impossible states | Trust internal code; only validate at boundaries |
|
|
82
|
+
| Style preferences disguised as correctness | If it works and is readable, style is preference |
|
|
83
|
+
| Suggesting patterns from a different language | Follow THIS codebase's patterns |
|
|
84
|
+
| Not seeing the full file (diff-only context) | They may miss why code is structured this way |
|
|
85
|
+
| "This could be a security issue" without specifics | Ask for the specific attack vector |
|
|
86
|
+
| "Add tests for X" when X is already tested | Check before accepting |
|
|
87
|
+
|
|
88
|
+
## Decision Record
|
|
89
|
+
|
|
90
|
+
For each comment, record:
|
|
91
|
+
```
|
|
92
|
+
Comment #ID by @user on file:line
|
|
93
|
+
Verdict: ACCEPT | REJECT | INVESTIGATE
|
|
94
|
+
Reasoning: [1-2 sentences]
|
|
95
|
+
Ripple effects: [list or "none"]
|
|
96
|
+
Group: [group-id]
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
This record serves as the basis for reply content and fix planning.
|
|
@@ -1,32 +1,114 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: qa-strategy
|
|
3
|
-
description:
|
|
3
|
+
description: E2E product testing strategy using Playwright — flow-based, autonomous, close to human interaction
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# E2E Product Testing Strategy
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## Core Principle
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
2. **Integration tests**: Test component interactions
|
|
12
|
-
3. **E2E tests**: Test user-facing flows end-to-end
|
|
10
|
+
Test the product the way a user uses it. Every test simulates a real user flow — navigating, clicking, filling forms, waiting for responses. If a human wouldn't do it, don't test it here.
|
|
13
11
|
|
|
14
|
-
|
|
12
|
+
**This is NOT for unit or integration tests.** This pipeline tests complete user journeys through the running application.
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
- New API endpoint → integration test
|
|
18
|
-
- New user flow → E2E test
|
|
19
|
-
- Bug fix → regression test at the appropriate level
|
|
14
|
+
## Flow Discovery
|
|
20
15
|
|
|
21
|
-
|
|
16
|
+
Before writing tests, understand what the product does:
|
|
22
17
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
18
|
+
1. **Scan routes and pages** — every URL a user can visit is a potential flow entry point
|
|
19
|
+
2. **Identify forms** — login, signup, search, create, edit — these are high-value interaction points
|
|
20
|
+
3. **Map navigation** — how does a user get from page A to page B? What's the happy path?
|
|
21
|
+
4. **Find auth boundaries** — what's public vs protected? Test both sides
|
|
22
|
+
5. **Check CRUD operations** — can you create, read, update, delete the core entities?
|
|
28
23
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
24
|
+
## Flow Prioritization
|
|
25
|
+
|
|
26
|
+
| Priority | Description | Examples |
|
|
27
|
+
|----------|-------------|---------|
|
|
28
|
+
| **Critical** | Revenue or access blocking | Login, checkout, payment |
|
|
29
|
+
| **High** | Core product value | Create/edit main entities, dashboard |
|
|
30
|
+
| **Medium** | Secondary features | Settings, profile, search |
|
|
31
|
+
| **Low** | Nice-to-have | Theme toggle, tooltips |
|
|
32
|
+
|
|
33
|
+
Test critical and high flows first. Skip low flows if hitting the token budget.
|
|
34
|
+
|
|
35
|
+
## Playwright Best Practices
|
|
36
|
+
|
|
37
|
+
### Locators (prefer resilient selectors)
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
// GOOD — role-based, resilient to styling changes
|
|
41
|
+
page.getByRole('button', { name: 'Submit' })
|
|
42
|
+
page.getByLabel('Email')
|
|
43
|
+
page.getByText('Welcome back')
|
|
44
|
+
page.getByTestId('user-avatar')
|
|
45
|
+
|
|
46
|
+
// BAD — fragile, breaks on refactoring
|
|
47
|
+
page.locator('.btn-primary')
|
|
48
|
+
page.locator('#submit-btn')
|
|
49
|
+
page.locator('div > form > button:nth-child(2)')
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Assertions
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
// Wait for navigation
|
|
56
|
+
await expect(page).toHaveURL('/dashboard');
|
|
57
|
+
|
|
58
|
+
// Wait for element visibility
|
|
59
|
+
await expect(page.getByText('Success')).toBeVisible();
|
|
60
|
+
|
|
61
|
+
// Wait for element to disappear (loading states)
|
|
62
|
+
await expect(page.getByText('Loading...')).not.toBeVisible();
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Waiting
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
// GOOD — wait for specific condition
|
|
69
|
+
await page.waitForResponse(resp => resp.url().includes('/api/users'));
|
|
70
|
+
await page.waitForLoadState('networkidle');
|
|
71
|
+
|
|
72
|
+
// BAD — arbitrary delays
|
|
73
|
+
await page.waitForTimeout(3000);
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Test Structure
|
|
77
|
+
|
|
78
|
+
One flow per file. Each test in the flow tests a step or variant:
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
test.describe('Checkout flow', () => {
|
|
82
|
+
test('adds item to cart', async ({ page }) => { ... });
|
|
83
|
+
test('fills shipping info', async ({ page }) => { ... });
|
|
84
|
+
test('completes payment', async ({ page }) => { ... });
|
|
85
|
+
test('shows confirmation', async ({ page }) => { ... });
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## What Makes a Good E2E Test
|
|
90
|
+
|
|
91
|
+
| Quality | Good | Bad |
|
|
92
|
+
|---------|------|-----|
|
|
93
|
+
| **User-centric** | Tests what a user would do | Tests implementation details |
|
|
94
|
+
| **Independent** | Each test can run alone | Tests depend on previous test state |
|
|
95
|
+
| **Resilient** | Uses role/label selectors | Uses CSS classes or DOM structure |
|
|
96
|
+
| **Fast-failing** | Fails clearly on the broken step | Fails on a timeout with no context |
|
|
97
|
+
| **Readable** | Test name describes the user action | Test name is a technical description |
|
|
98
|
+
|
|
99
|
+
## Common Pitfalls
|
|
100
|
+
|
|
101
|
+
1. **Testing internal state** — don't check Redux store, localStorage, or cookies directly. Test what the user sees.
|
|
102
|
+
2. **Flaky waits** — use `waitForResponse` or `waitForSelector`, never `waitForTimeout`.
|
|
103
|
+
3. **Shared state** — each test should set up its own state. Don't rely on test execution order.
|
|
104
|
+
4. **Over-testing** — one flow per critical path. Don't test every permutation of a form.
|
|
105
|
+
5. **Ignoring error states** — test what happens when the API returns an error, the network is slow, or the user enters invalid data.
|
|
106
|
+
|
|
107
|
+
## Regression Analysis
|
|
108
|
+
|
|
109
|
+
When a previously-passing test fails:
|
|
110
|
+
|
|
111
|
+
1. **Read the error** — what element wasn't found? What URL didn't match?
|
|
112
|
+
2. **Check if the app changed** — did a route move? Did a button get renamed?
|
|
113
|
+
3. **Distinguish bug from change** — if the app intentionally changed, the test needs updating. If not, it's a regression.
|
|
114
|
+
4. **Record the finding** — update the flow matrix with the new status and reasoning.
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { loadFixPrConfig, saveFixPrConfig, DEFAULT_FIX_PR_CONFIG } from "../fix-pr/config.js";
|
|
5
|
+
import { buildFixPrOrchestratorPrompt } from "../fix-pr/prompt-builder.js";
|
|
6
|
+
import type { FixPrConfig, ReviewerType, CommentReplyPolicy } from "../fix-pr/types.js";
|
|
7
|
+
import {
|
|
8
|
+
generateFixPrSessionId,
|
|
9
|
+
createFixPrSession,
|
|
10
|
+
findActiveFixPrSession,
|
|
11
|
+
getSessionDir,
|
|
12
|
+
} from "../storage/fix-pr-sessions.js";
|
|
13
|
+
import { notifyInfo, notifyError, notifyWarning } from "../notifications/renderer.js";
|
|
14
|
+
|
|
15
|
+
function getScriptsDir(): string {
|
|
16
|
+
return path.join(path.dirname(new URL(import.meta.url).pathname), "..", "fix-pr", "scripts");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function findSkillPath(skillName: string): string | null {
|
|
20
|
+
const candidates = [
|
|
21
|
+
path.join(process.cwd(), "skills", skillName, "SKILL.md"),
|
|
22
|
+
path.join(path.dirname(new URL(import.meta.url).pathname), "..", "..", "skills", skillName, "SKILL.md"),
|
|
23
|
+
];
|
|
24
|
+
for (const p of candidates) {
|
|
25
|
+
if (fs.existsSync(p)) return p;
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function registerFixPrCommand(pi: ExtensionAPI): void {
|
|
31
|
+
pi.registerCommand("supi:fix-pr", {
|
|
32
|
+
description: "Fix PR review comments with token-optimized agent orchestration",
|
|
33
|
+
async handler(args, ctx) {
|
|
34
|
+
// ── Step 1: Detect PR ──────────────────────────────────────────
|
|
35
|
+
let prNumber: number | null = null;
|
|
36
|
+
let repo: string | null = null;
|
|
37
|
+
|
|
38
|
+
// Try to parse from args
|
|
39
|
+
const argTrimmed = args?.trim().replace("#", "") || "";
|
|
40
|
+
if (/^\d+$/.test(argTrimmed)) {
|
|
41
|
+
prNumber = parseInt(argTrimmed, 10);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Detect repo
|
|
45
|
+
try {
|
|
46
|
+
const repoResult = await pi.exec("gh", ["repo", "view", "--json", "nameWithOwner", "-q", ".nameWithOwner"], { cwd: ctx.cwd });
|
|
47
|
+
if (repoResult.code === 0) repo = repoResult.stdout.trim();
|
|
48
|
+
} catch { /* ignore */ }
|
|
49
|
+
|
|
50
|
+
if (!repo) {
|
|
51
|
+
notifyError(ctx, "Could not detect repository", "Run from a git repo with gh CLI configured");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Detect PR number from current branch if not provided
|
|
56
|
+
if (!prNumber) {
|
|
57
|
+
try {
|
|
58
|
+
const prResult = await pi.exec("gh", ["pr", "view", "--json", "number", "-q", ".number"], { cwd: ctx.cwd });
|
|
59
|
+
if (prResult.code === 0) prNumber = parseInt(prResult.stdout.trim(), 10);
|
|
60
|
+
} catch { /* ignore */ }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!prNumber) {
|
|
64
|
+
notifyError(ctx, "No PR found", "Provide PR number as argument or run from a PR branch");
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ── Step 2: Load or create config ──────────────────────────────
|
|
69
|
+
let config = loadFixPrConfig(ctx.cwd);
|
|
70
|
+
|
|
71
|
+
if (!config && ctx.hasUI) {
|
|
72
|
+
config = await runSetupWizard(ctx);
|
|
73
|
+
if (!config) return; // user cancelled
|
|
74
|
+
saveFixPrConfig(ctx.cwd, config);
|
|
75
|
+
ctx.ui.notify("Fix-PR config saved to .omp/supipowers/fix-pr.json", "info");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!config) {
|
|
79
|
+
notifyError(ctx, "No fix-pr config", "Run interactively first to set up configuration");
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ── Step 3: Session handling ───────────────────────────────────
|
|
84
|
+
let activeSession = findActiveFixPrSession(ctx.cwd);
|
|
85
|
+
|
|
86
|
+
if (activeSession && ctx.hasUI) {
|
|
87
|
+
const choice = await ctx.ui.select(
|
|
88
|
+
"Fix-PR Session",
|
|
89
|
+
[
|
|
90
|
+
`Resume ${activeSession.id} (iteration ${activeSession.iteration}, PR #${activeSession.prNumber})`,
|
|
91
|
+
"Start new session",
|
|
92
|
+
],
|
|
93
|
+
{ helpText: "Select session · Esc to cancel" },
|
|
94
|
+
);
|
|
95
|
+
if (!choice) return;
|
|
96
|
+
if (choice.startsWith("Start new")) activeSession = null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const ledger = activeSession ?? {
|
|
100
|
+
id: generateFixPrSessionId(),
|
|
101
|
+
createdAt: new Date().toISOString(),
|
|
102
|
+
updatedAt: new Date().toISOString(),
|
|
103
|
+
prNumber,
|
|
104
|
+
repo,
|
|
105
|
+
status: "running" as const,
|
|
106
|
+
iteration: 0,
|
|
107
|
+
config,
|
|
108
|
+
commentsProcessed: [],
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
if (!activeSession) {
|
|
112
|
+
createFixPrSession(ctx.cwd, ledger);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ── Step 4: Fetch initial comments ─────────────────────────────
|
|
116
|
+
const sessionDir = getSessionDir(ctx.cwd, ledger.id);
|
|
117
|
+
const scriptsDir = getScriptsDir();
|
|
118
|
+
const snapshotPath = path.join(sessionDir, "snapshots", `comments-${ledger.iteration}.jsonl`);
|
|
119
|
+
|
|
120
|
+
const fetchResult = await pi.exec("bash", [
|
|
121
|
+
path.join(scriptsDir, "fetch-pr-comments.sh"),
|
|
122
|
+
repo,
|
|
123
|
+
String(prNumber),
|
|
124
|
+
snapshotPath,
|
|
125
|
+
], { cwd: ctx.cwd });
|
|
126
|
+
|
|
127
|
+
if (fetchResult.code !== 0) {
|
|
128
|
+
notifyError(ctx, "Failed to fetch PR comments", fetchResult.stderr);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Read the snapshot
|
|
133
|
+
let comments = "";
|
|
134
|
+
try {
|
|
135
|
+
comments = fs.readFileSync(snapshotPath, "utf-8").trim();
|
|
136
|
+
} catch {
|
|
137
|
+
notifyWarning(ctx, "No comments found", "PR has no review comments to process");
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (!comments) {
|
|
142
|
+
notifyInfo(ctx, "No comments to process", "PR has no review comments");
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const commentCount = comments.split("\n").length;
|
|
147
|
+
|
|
148
|
+
// ── Step 5: Load skill ─────────────────────────────────────────
|
|
149
|
+
let skillContent = "";
|
|
150
|
+
const skillPath = findSkillPath("fix-pr");
|
|
151
|
+
if (skillPath) {
|
|
152
|
+
try {
|
|
153
|
+
skillContent = fs.readFileSync(skillPath, "utf-8");
|
|
154
|
+
} catch { /* proceed without */ }
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ── Step 6: Build and send prompt ──────────────────────────────
|
|
158
|
+
const prompt = buildFixPrOrchestratorPrompt({
|
|
159
|
+
prNumber,
|
|
160
|
+
repo,
|
|
161
|
+
comments,
|
|
162
|
+
sessionDir,
|
|
163
|
+
scriptsDir,
|
|
164
|
+
config,
|
|
165
|
+
iteration: ledger.iteration,
|
|
166
|
+
skillContent,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
pi.sendMessage(
|
|
170
|
+
{
|
|
171
|
+
customType: "supi-fix-pr",
|
|
172
|
+
content: [{ type: "text", text: prompt }],
|
|
173
|
+
display: "none",
|
|
174
|
+
},
|
|
175
|
+
{ deliverAs: "steer" },
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
notifyInfo(ctx, `Fix-PR started: PR #${prNumber}`, `${commentCount} comments to assess | session ${ledger.id}`);
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ── Setup Wizard ───────────────────────────────────────────────────────
|
|
184
|
+
|
|
185
|
+
const REVIEWER_OPTIONS = [
|
|
186
|
+
"CodeRabbit",
|
|
187
|
+
"GitHub Copilot",
|
|
188
|
+
"Gemini Code Review",
|
|
189
|
+
"None",
|
|
190
|
+
];
|
|
191
|
+
|
|
192
|
+
const REVIEWER_DEFAULTS: Record<string, string> = {
|
|
193
|
+
"CodeRabbit": "/review",
|
|
194
|
+
"GitHub Copilot": "@copilot review",
|
|
195
|
+
"Gemini Code Review": "/gemini review",
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const POLICY_OPTIONS = [
|
|
199
|
+
"Answer all comments",
|
|
200
|
+
"Only answer wrong/unnecessary ones (recommended)",
|
|
201
|
+
"Don't answer, just fix",
|
|
202
|
+
];
|
|
203
|
+
|
|
204
|
+
const DELAY_OPTIONS = [
|
|
205
|
+
"60 seconds",
|
|
206
|
+
"120 seconds",
|
|
207
|
+
"180 seconds (recommended)",
|
|
208
|
+
"300 seconds",
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
const ITERATION_OPTIONS = [
|
|
212
|
+
"1",
|
|
213
|
+
"2",
|
|
214
|
+
"3 (recommended)",
|
|
215
|
+
"5",
|
|
216
|
+
];
|
|
217
|
+
|
|
218
|
+
const MODEL_TIER_OPTIONS = [
|
|
219
|
+
"high — thorough reasoning, more tokens",
|
|
220
|
+
"low — fast execution, fewer tokens",
|
|
221
|
+
];
|
|
222
|
+
|
|
223
|
+
async function runSetupWizard(ctx: any): Promise<FixPrConfig | null> {
|
|
224
|
+
// 1. Automated reviewer
|
|
225
|
+
const reviewerChoice = await ctx.ui.select(
|
|
226
|
+
"Automated PR reviewer",
|
|
227
|
+
REVIEWER_OPTIONS,
|
|
228
|
+
{ helpText: "Select your automated reviewer, if any" },
|
|
229
|
+
);
|
|
230
|
+
if (!reviewerChoice) return null;
|
|
231
|
+
|
|
232
|
+
let reviewerType: ReviewerType = "none";
|
|
233
|
+
let triggerMethod: string | null = null;
|
|
234
|
+
|
|
235
|
+
if (reviewerChoice !== "None") {
|
|
236
|
+
reviewerType = reviewerChoice.toLowerCase().replace(/ /g, "").replace("github", "") as ReviewerType;
|
|
237
|
+
// Normalize to our type names
|
|
238
|
+
if (reviewerChoice === "CodeRabbit") reviewerType = "coderabbit";
|
|
239
|
+
else if (reviewerChoice === "GitHub Copilot") reviewerType = "copilot";
|
|
240
|
+
else if (reviewerChoice === "Gemini Code Review") reviewerType = "gemini";
|
|
241
|
+
|
|
242
|
+
const defaultTrigger = REVIEWER_DEFAULTS[reviewerChoice] || "";
|
|
243
|
+
triggerMethod = await ctx.ui.input(
|
|
244
|
+
"How to trigger re-review?",
|
|
245
|
+
defaultTrigger,
|
|
246
|
+
{ helpText: `Default for ${reviewerChoice}: ${defaultTrigger}` },
|
|
247
|
+
);
|
|
248
|
+
if (triggerMethod === undefined) return null;
|
|
249
|
+
if (!triggerMethod) triggerMethod = defaultTrigger;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// 2. Comment reply policy
|
|
253
|
+
const policyChoice = await ctx.ui.select(
|
|
254
|
+
"Comment reply policy",
|
|
255
|
+
POLICY_OPTIONS,
|
|
256
|
+
{ helpText: "How should we handle replying to comments?" },
|
|
257
|
+
);
|
|
258
|
+
if (!policyChoice) return null;
|
|
259
|
+
|
|
260
|
+
let commentPolicy: CommentReplyPolicy = "answer-selective";
|
|
261
|
+
if (policyChoice.startsWith("Answer all")) commentPolicy = "answer-all";
|
|
262
|
+
else if (policyChoice.startsWith("Don't")) commentPolicy = "no-answer";
|
|
263
|
+
|
|
264
|
+
// 3. Loop timing
|
|
265
|
+
const delayChoice = await ctx.ui.select(
|
|
266
|
+
"Delay between review checks",
|
|
267
|
+
DELAY_OPTIONS,
|
|
268
|
+
{ helpText: "How long to wait for reviewer after pushing changes" },
|
|
269
|
+
);
|
|
270
|
+
if (!delayChoice) return null;
|
|
271
|
+
const delaySeconds = parseInt(delayChoice, 10);
|
|
272
|
+
|
|
273
|
+
const iterChoice = await ctx.ui.select(
|
|
274
|
+
"Max review iterations",
|
|
275
|
+
ITERATION_OPTIONS,
|
|
276
|
+
{ helpText: "Maximum fix-check-fix cycles" },
|
|
277
|
+
);
|
|
278
|
+
if (!iterChoice) return null;
|
|
279
|
+
const maxIterations = parseInt(iterChoice, 10);
|
|
280
|
+
|
|
281
|
+
// 4. Model preferences
|
|
282
|
+
const orchestratorTier = await ctx.ui.select(
|
|
283
|
+
"Orchestrator model tier (assessment & grouping)",
|
|
284
|
+
MODEL_TIER_OPTIONS,
|
|
285
|
+
{ helpText: "Higher tier = more thorough analysis" },
|
|
286
|
+
);
|
|
287
|
+
if (!orchestratorTier) return null;
|
|
288
|
+
|
|
289
|
+
const plannerTier = await ctx.ui.select(
|
|
290
|
+
"Planner model tier (fix planning)",
|
|
291
|
+
MODEL_TIER_OPTIONS,
|
|
292
|
+
{ helpText: "Higher tier = more detailed plans" },
|
|
293
|
+
);
|
|
294
|
+
if (!plannerTier) return null;
|
|
295
|
+
|
|
296
|
+
const fixerTier = await ctx.ui.select(
|
|
297
|
+
"Fixer model tier (code changes)",
|
|
298
|
+
MODEL_TIER_OPTIONS,
|
|
299
|
+
{ helpText: "Lower tier usually sufficient for execution" },
|
|
300
|
+
);
|
|
301
|
+
if (!fixerTier) return null;
|
|
302
|
+
|
|
303
|
+
const config: FixPrConfig = {
|
|
304
|
+
reviewer: { type: reviewerType, triggerMethod },
|
|
305
|
+
commentPolicy,
|
|
306
|
+
loop: { delaySeconds, maxIterations },
|
|
307
|
+
models: {
|
|
308
|
+
orchestrator: {
|
|
309
|
+
...DEFAULT_FIX_PR_CONFIG.models.orchestrator,
|
|
310
|
+
tier: orchestratorTier.startsWith("high") ? "high" : "low",
|
|
311
|
+
},
|
|
312
|
+
planner: {
|
|
313
|
+
...DEFAULT_FIX_PR_CONFIG.models.planner,
|
|
314
|
+
tier: plannerTier.startsWith("high") ? "high" : "low",
|
|
315
|
+
},
|
|
316
|
+
fixer: {
|
|
317
|
+
...DEFAULT_FIX_PR_CONFIG.models.fixer,
|
|
318
|
+
tier: fixerTier.startsWith("high") ? "high" : "low",
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
return config;
|
|
324
|
+
}
|