@glrs-dev/harness-plugin-opencode 2.1.0 → 2.3.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +133 -0
  2. package/README.md +42 -106
  3. package/SECURITY.md +1 -1
  4. package/dist/agents/prompts/build.md +34 -4
  5. package/dist/agents/prompts/build.open.md +18 -4
  6. package/dist/agents/prompts/code-reviewer-thorough.md +77 -0
  7. package/dist/agents/prompts/code-reviewer.md +80 -0
  8. package/dist/agents/prompts/code-reviewer.open.md +68 -0
  9. package/dist/agents/prompts/debriefer.md +55 -0
  10. package/dist/agents/prompts/gap-analyzer.md +2 -0
  11. package/dist/agents/prompts/plan-reviewer.md +5 -1
  12. package/dist/agents/prompts/plan.md +119 -10
  13. package/dist/agents/prompts/prime.md +149 -88
  14. package/dist/agents/prompts/research-auto.md +1 -1
  15. package/dist/agents/prompts/research-local.md +1 -1
  16. package/dist/agents/prompts/research-web.md +1 -1
  17. package/dist/agents/prompts/research.md +2 -0
  18. package/dist/agents/prompts/scoper.md +129 -0
  19. package/dist/agents/prompts/spec-reviewer.md +53 -0
  20. package/dist/agents/prompts/spec-reviewer.open.md +56 -0
  21. package/dist/agents/shared/index.ts +1 -0
  22. package/dist/agents/shared/ui-evaluation-ladder.md +50 -0
  23. package/dist/agents/shared/workflow-mechanics.md +5 -5
  24. package/dist/autopilot/prompt-template.md +104 -0
  25. package/dist/chunk-GCWHRUOK.js +259 -0
  26. package/dist/chunk-MJSMBY2Y.js +87 -0
  27. package/dist/chunk-NIFAVPNN.js +544 -0
  28. package/dist/{chunk-VJUETC6A.js → chunk-PDMXYZM4.js} +53 -1
  29. package/dist/cli.js +1596 -1964
  30. package/dist/commands/prompts/fresh.md +27 -24
  31. package/dist/commands/prompts/review.md +3 -3
  32. package/dist/commands/prompts/ship.md +2 -0
  33. package/dist/index.js +188 -633
  34. package/dist/loop-session-J35NILUZ.js +30 -0
  35. package/dist/opencode-server-KPCDFYAX.js +22 -0
  36. package/dist/plan-parser-TMHEKT22.js +6 -0
  37. package/dist/plan-session-7VS32P52.js +117 -0
  38. package/dist/scoper-S77SOK7X.js +326 -0
  39. package/dist/skills/adversarial-review-rubric/SKILL.md +47 -0
  40. package/dist/skills/code-quality/SKILL.md +1 -1
  41. package/dist/skills/root-cause-diagnosis/SKILL.md +24 -0
  42. package/dist/skills/spear-protocol/SKILL.md +167 -0
  43. package/package.json +3 -1
  44. package/dist/agents/prompts/pilot-assessor.md +0 -77
  45. package/dist/agents/prompts/pilot-builder.md +0 -40
  46. package/dist/agents/prompts/pilot-planner.md +0 -56
  47. package/dist/agents/prompts/pilot-scoper.md +0 -58
  48. package/dist/agents/prompts/qa-reviewer.md +0 -68
  49. package/dist/agents/prompts/qa-reviewer.open.md +0 -58
  50. package/dist/agents/prompts/qa-thorough.md +0 -63
  51. package/dist/bin/plan-check.sh +0 -255
  52. package/dist/chunk-6CZPRUMJ.js +0 -869
  53. package/dist/chunk-DZG4D3OH.js +0 -54
  54. package/dist/chunk-OYRKOEXK.js +0 -88
  55. package/dist/commands/prompts/autopilot.md +0 -96
  56. package/dist/install-6775ZBDG.js +0 -13
  57. package/dist/paths-WZ23ZQOV.js +0 -18
@@ -0,0 +1,30 @@
1
+ import {
2
+ runRalphLoop
3
+ } from "./chunk-NIFAVPNN.js";
4
+ import "./chunk-MJSMBY2Y.js";
5
+ import "./chunk-GCWHRUOK.js";
6
+
7
+ // src/autopilot/loop-session.ts
8
+ import * as fs from "fs";
9
+ import * as path from "path";
10
+ async function runLoopSession(opts) {
11
+ const _runRalphLoop = opts._deps?.runRalphLoop ?? runRalphLoop;
12
+ const isDirectory = opts._deps?.isDirectory ? opts._deps.isDirectory(opts.planPath) : (() => {
13
+ try {
14
+ return fs.statSync(opts.planPath).isDirectory();
15
+ } catch {
16
+ return false;
17
+ }
18
+ })();
19
+ let prompt;
20
+ if (isDirectory) {
21
+ const mainMd = path.join(opts.planPath, "main.md");
22
+ prompt = `Work the plan at ${mainMd}. Find the first unchecked phase in ## Phases and complete all its items. Continue until all phases are checked and all main.md items are checked. Mark items done as they complete.`;
23
+ } else {
24
+ prompt = `Work the plan at ${opts.planPath}. Complete all items in ## Acceptance criteria. Mark items done as they complete.`;
25
+ }
26
+ return _runRalphLoop({ prompt, cwd: opts.cwd });
27
+ }
28
+ export {
29
+ runLoopSession
30
+ };
@@ -0,0 +1,22 @@
1
+ import {
2
+ DEFAULT_STARTUP_TIMEOUT_MS,
3
+ createSession,
4
+ execFileP,
5
+ getLastAssistantMessage,
6
+ getSessionCost,
7
+ selfTest,
8
+ sendAndWait,
9
+ startServer,
10
+ waitForIdle
11
+ } from "./chunk-GCWHRUOK.js";
12
+ export {
13
+ DEFAULT_STARTUP_TIMEOUT_MS,
14
+ createSession,
15
+ execFileP,
16
+ getLastAssistantMessage,
17
+ getSessionCost,
18
+ selfTest,
19
+ sendAndWait,
20
+ startServer,
21
+ waitForIdle
22
+ };
@@ -0,0 +1,6 @@
1
+ import {
2
+ parsePlanState
3
+ } from "./chunk-MJSMBY2Y.js";
4
+ export {
5
+ parsePlanState
6
+ };
@@ -0,0 +1,117 @@
1
+ import {
2
+ createSession,
3
+ sendAndWait,
4
+ startServer
5
+ } from "./chunk-GCWHRUOK.js";
6
+
7
+ // src/autopilot/plan-session.ts
8
+ import * as fs from "fs";
9
+ import * as path from "path";
10
+ var DEFAULT_PLAN_TIMEOUT_MS = 10 * 60 * 1e3;
11
+ async function runPlanSession(opts) {
12
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_PLAN_TIMEOUT_MS;
13
+ const _startServer = opts._deps?.startServer ?? startServer;
14
+ const _createSession = opts._deps?.createSession ?? createSession;
15
+ const _sendAndWait = opts._deps?.sendAndWait ?? sendAndWait;
16
+ const _existsSync = opts._deps?.existsSync ?? fs.existsSync;
17
+ const server = await _startServer({ cwd: opts.planDir });
18
+ try {
19
+ const sessionId = await _createSession(server.client, {
20
+ cwd: opts.planDir,
21
+ agentName: "plan"
22
+ });
23
+ const multiFileDir = path.join(opts.planDir, opts.slug);
24
+ const multiFileMain = path.join(multiFileDir, "main.md");
25
+ const singleFilePlan = path.join(opts.planDir, `${opts.slug}.md`);
26
+ const prompt = `Read the scope at ${opts.scopePath} and produce a plan. Use slug ${opts.slug} for the plan file(s). If the scope warrants multiple phases, produce a multi-file plan at ${multiFileDir}/main.md + phase_N.md files. Otherwise produce a single-file plan at ${singleFilePlan}.`;
27
+ const result = await _sendAndWait(server.client, {
28
+ sessionId,
29
+ message: prompt,
30
+ agentName: "plan",
31
+ stallMs: timeoutMs,
32
+ autoRejectPermissions: true,
33
+ serverUrl: server.url
34
+ });
35
+ if (result.kind === "abort") {
36
+ throw new Error(
37
+ `Plan session aborted (timeout after ${timeoutMs}ms).`
38
+ );
39
+ }
40
+ if (result.kind === "stall") {
41
+ throw new Error(
42
+ `Plan session stalled for ${result.stallMs}ms with no idle signal.`
43
+ );
44
+ }
45
+ if (result.kind === "error") {
46
+ throw new Error(`Plan session error: ${result.message}`);
47
+ }
48
+ if (result.kind === "question_rejected") {
49
+ process.stderr.write(
50
+ `
51
+ \u26A0 @plan tried to ask a question (rejected). Checking if plan was written anyway...
52
+ `
53
+ );
54
+ }
55
+ if (_existsSync(multiFileMain)) {
56
+ return { planPath: multiFileDir };
57
+ }
58
+ if (_existsSync(singleFilePlan)) {
59
+ return { planPath: singleFilePlan };
60
+ }
61
+ process.stderr.write(
62
+ `
63
+ \u26A0 @plan didn't write a plan file. Re-sending with explicit instructions...
64
+ `
65
+ );
66
+ const retryPrompt = `You did not write a plan file. Write the plan NOW. Read the scope at ${opts.scopePath}. Write the plan to ${singleFilePlan} (single-file) or ${multiFileDir}/main.md (multi-file). Do NOT ask questions. Just write the plan.`;
67
+ const retryResult = await _sendAndWait(server.client, {
68
+ sessionId,
69
+ message: retryPrompt,
70
+ agentName: "plan",
71
+ stallMs: timeoutMs,
72
+ autoRejectPermissions: true,
73
+ serverUrl: server.url
74
+ });
75
+ if (retryResult.kind !== "idle" && retryResult.kind !== "question_rejected") {
76
+ throw new Error(`@plan retry failed: ${retryResult.kind}`);
77
+ }
78
+ if (_existsSync(multiFileMain)) {
79
+ return { planPath: multiFileDir };
80
+ }
81
+ if (_existsSync(singleFilePlan)) {
82
+ return { planPath: singleFilePlan };
83
+ }
84
+ process.stderr.write(
85
+ `
86
+ \u26A0 @plan still didn't write a plan. Constructing minimal plan from scope.
87
+ `
88
+ );
89
+ const scopeContent = fs.existsSync(opts.scopePath) ? fs.readFileSync(opts.scopePath, "utf-8") : `# Plan
90
+
91
+ Scope file not found at ${opts.scopePath}.`;
92
+ const minimalPlan = [
93
+ `# Plan (auto-generated from scope)`,
94
+ "",
95
+ "This plan was auto-generated because @plan did not produce a plan file.",
96
+ "Review and refine before executing.",
97
+ "",
98
+ scopeContent,
99
+ "",
100
+ "## Acceptance criteria",
101
+ "",
102
+ "- [ ] Review and refine this auto-generated plan",
103
+ "",
104
+ "## File-level changes",
105
+ "",
106
+ "- To be determined after plan review."
107
+ ].join("\n");
108
+ fs.mkdirSync(path.dirname(singleFilePlan), { recursive: true });
109
+ fs.writeFileSync(singleFilePlan, minimalPlan);
110
+ return { planPath: singleFilePlan };
111
+ } finally {
112
+ await server.shutdown();
113
+ }
114
+ }
115
+ export {
116
+ runPlanSession
117
+ };
@@ -0,0 +1,326 @@
1
+ import {
2
+ createSession,
3
+ getLastAssistantMessage,
4
+ sendAndWait,
5
+ startServer
6
+ } from "./chunk-GCWHRUOK.js";
7
+
8
+ // src/autopilot/scoper.ts
9
+ import * as fs from "fs";
10
+ import * as path from "path";
11
+ var ANSI_RESET = "\x1B[0m\x1B[2K\r";
12
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
13
+ function createSpinner() {
14
+ let timer = null;
15
+ let frame = 0;
16
+ return {
17
+ start(label = "Thinking") {
18
+ if (timer) return;
19
+ frame = 0;
20
+ const isTTY = process.stderr.isTTY ?? false;
21
+ if (!isTTY) return;
22
+ timer = setInterval(() => {
23
+ const f = SPINNER_FRAMES[frame % SPINNER_FRAMES.length];
24
+ process.stderr.write(`\x1B[2K\r\x1B[36m${f}\x1B[0m ${label}...`);
25
+ frame++;
26
+ }, 80);
27
+ },
28
+ stop() {
29
+ if (!timer) return;
30
+ clearInterval(timer);
31
+ timer = null;
32
+ process.stderr.write("\x1B[2K\r");
33
+ }
34
+ };
35
+ }
36
+ function parseQuestion(response) {
37
+ const match = response.match(/^([^\n]{1,199}\?)\s*$/);
38
+ return match ? match[1] ?? null : null;
39
+ }
40
+ function parseScopeSummary(response) {
41
+ const match = response.match(/^SCOPE_SUMMARY:\s*\n?([\s\S]+)$/);
42
+ return match ? match[1]?.trim() ?? null : null;
43
+ }
44
+ function extractScopeCompletePath(output) {
45
+ const lines = output.split("\n");
46
+ let lastMatch = null;
47
+ for (const line of lines) {
48
+ const trimmed = line.trim();
49
+ if (trimmed.startsWith("SCOPE_COMPLETE:")) {
50
+ const rest = trimmed.slice("SCOPE_COMPLETE:".length).trim();
51
+ if (rest.length > 0) {
52
+ lastMatch = rest;
53
+ }
54
+ }
55
+ }
56
+ return lastMatch;
57
+ }
58
+ var DEFAULT_SCOPER_TIMEOUT_MS = 5 * 60 * 1e3;
59
+ var MAX_QUESTIONS = 8;
60
+ var FORCED_FINALIZE_MESSAGE = "You have asked enough questions. Present a SCOPE_SUMMARY for user approval, then write scope.md and emit SCOPE_COMPLETE.";
61
+ var PARSE_RETRY_REMINDER = "Your last response did not follow the strict contract. Respond with EXACTLY one of: (a) a single question (\u2264200 chars, ending with '?'), (b) a scope summary starting with 'SCOPE_SUMMARY:', or (c) the sentinel 'SCOPE_COMPLETE: <absolute-path>'. Nothing else.";
62
+ async function runScoperSession(opts) {
63
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_SCOPER_TIMEOUT_MS;
64
+ const _startServer = opts._deps?.startServer ?? startServer;
65
+ const _createSession = opts._deps?.createSession ?? createSession;
66
+ const _sendAndWait = opts._deps?.sendAndWait ?? sendAndWait;
67
+ const _getLastAssistantMessage = opts._deps?.getLastAssistantMessage ?? getLastAssistantMessage;
68
+ const _existsSync = opts._deps?.existsSync ?? fs.existsSync;
69
+ const _promptUser = opts._deps?.promptUser ?? (async (question) => {
70
+ const { input } = await import("@inquirer/prompts");
71
+ return input({ message: question });
72
+ });
73
+ const server = await _startServer({ cwd: opts.planDir });
74
+ const spinner = createSpinner();
75
+ try {
76
+ const sessionId = await _createSession(server.client, {
77
+ cwd: opts.planDir,
78
+ agentName: "scoper"
79
+ });
80
+ const initialPrompt = [
81
+ "You are running in an inquirer-driven wizard. Follow the strict response contract:",
82
+ "- Every response must be EXACTLY one of:",
83
+ " (a) A single question (\u2264200 chars, ending with '?')",
84
+ " (b) A scope summary starting with 'SCOPE_SUMMARY:' for user approval",
85
+ " (c) The sentinel 'SCOPE_COMPLETE: <absolute-path>'",
86
+ "- Do NOT call the question tool. Emit questions as plain assistant text.",
87
+ "- Start with first-principles questions (WHAT and WHY), not implementation details.",
88
+ "",
89
+ `The user wants to build: ${opts.initialGoal}`,
90
+ "",
91
+ "Begin by asking your first clarifying question about the problem being solved."
92
+ ].join("\n");
93
+ spinner.start("Scoper is thinking");
94
+ const firstResult = await _sendAndWait(server.client, {
95
+ sessionId,
96
+ message: initialPrompt,
97
+ agentName: "scoper",
98
+ stallMs: timeoutMs,
99
+ autoRejectPermissions: true
100
+ });
101
+ if (firstResult.kind === "abort") {
102
+ throw new Error(`Scoper session aborted (timeout after ${timeoutMs}ms).`);
103
+ }
104
+ if (firstResult.kind === "stall") {
105
+ throw new Error(
106
+ `Scoper session stalled for ${firstResult.stallMs}ms with no idle signal.`
107
+ );
108
+ }
109
+ if (firstResult.kind === "error") {
110
+ throw new Error(`Scoper session error: ${firstResult.message}`);
111
+ }
112
+ let questionsAsked = 0;
113
+ let parseRetryPending = false;
114
+ while (true) {
115
+ const lastMessage = await _getLastAssistantMessage(
116
+ server.client,
117
+ sessionId
118
+ );
119
+ const scopePath = extractScopeCompletePath(lastMessage);
120
+ if (scopePath) {
121
+ spinner.stop();
122
+ if (!_existsSync(scopePath)) {
123
+ throw new Error(
124
+ `Scoper emitted SCOPE_COMPLETE but scope.md does not exist at: ${scopePath}`
125
+ );
126
+ }
127
+ return { scopePath };
128
+ }
129
+ if (questionsAsked >= MAX_QUESTIONS) {
130
+ spinner.start("Finalizing scope");
131
+ const finalResult = await _sendAndWait(server.client, {
132
+ sessionId,
133
+ message: FORCED_FINALIZE_MESSAGE,
134
+ stallMs: timeoutMs,
135
+ autoRejectPermissions: true
136
+ });
137
+ if (finalResult.kind !== "idle") {
138
+ throw new Error(
139
+ `Scoper session failed during forced finalize: ${finalResult.kind}`
140
+ );
141
+ }
142
+ const finalMessage = await _getLastAssistantMessage(
143
+ server.client,
144
+ sessionId
145
+ );
146
+ const finalScopePath = extractScopeCompletePath(finalMessage);
147
+ if (finalScopePath) {
148
+ spinner.stop();
149
+ if (!_existsSync(finalScopePath)) {
150
+ throw new Error(
151
+ `Scoper emitted SCOPE_COMPLETE after forced finalize but scope.md does not exist at: ${finalScopePath}`
152
+ );
153
+ }
154
+ return { scopePath: finalScopePath };
155
+ }
156
+ const expectedScopePath = path.join(opts.planDir, opts.slug, "scope.md");
157
+ if (_existsSync(expectedScopePath)) {
158
+ spinner.stop();
159
+ process.stderr.write(
160
+ `
161
+ \u26A0 Scoper didn't emit sentinel, but scope.md exists at ${expectedScopePath}. Using it.
162
+
163
+ `
164
+ );
165
+ return { scopePath: expectedScopePath };
166
+ }
167
+ spinner.stop();
168
+ process.stderr.write(
169
+ `
170
+ \u26A0 Scoper didn't write scope.md. Constructing from conversation.
171
+
172
+ `
173
+ );
174
+ const scopeDir = path.join(opts.planDir, opts.slug);
175
+ if (!_existsSync(scopeDir)) {
176
+ fs.mkdirSync(scopeDir, { recursive: true });
177
+ }
178
+ const constructedScope = [
179
+ `# ${opts.initialGoal}`,
180
+ "",
181
+ "## Goal",
182
+ "",
183
+ opts.initialGoal,
184
+ "",
185
+ "## Scoper conversation summary",
186
+ "",
187
+ "The scoper agent asked 8 questions and the user provided answers,",
188
+ "but the agent did not produce a formal scope.md. The last agent",
189
+ "response is included below for the plan agent to work from.",
190
+ "",
191
+ "### Last agent response",
192
+ "",
193
+ finalMessage || "(no response captured)",
194
+ "",
195
+ "## Acceptance criteria",
196
+ "",
197
+ "- To be determined by the plan agent based on the conversation above.",
198
+ "",
199
+ "## Constraints",
200
+ "",
201
+ "- To be determined by the plan agent.",
202
+ "",
203
+ "## Out of scope",
204
+ "",
205
+ "- To be determined by the plan agent.",
206
+ "",
207
+ "## Open questions for the plan agent",
208
+ "",
209
+ "- The scoper did not complete formally. Review the conversation summary above and fill in the missing sections."
210
+ ].join("\n");
211
+ const constructedPath = path.join(scopeDir, "scope.md");
212
+ fs.writeFileSync(constructedPath, constructedScope);
213
+ return { scopePath: constructedPath };
214
+ }
215
+ const summary = parseScopeSummary(lastMessage);
216
+ if (summary) {
217
+ spinner.stop();
218
+ parseRetryPending = false;
219
+ process.stderr.write(ANSI_RESET);
220
+ process.stderr.write(`
221
+ \x1B[1m\u{1F4CB} Scope summary:\x1B[0m
222
+
223
+ ${summary}
224
+
225
+ `);
226
+ const approval = await _promptUser("Approve this scope? (yes / or describe what to change)");
227
+ if (approval.toLowerCase().startsWith("yes") || approval.toLowerCase() === "y" || approval.toLowerCase() === "approve") {
228
+ spinner.start("Writing scope.md");
229
+ const writeResult = await _sendAndWait(server.client, {
230
+ sessionId,
231
+ message: "The user approved the scope. Write scope.md now and emit SCOPE_COMPLETE.",
232
+ stallMs: timeoutMs,
233
+ autoRejectPermissions: true
234
+ });
235
+ if (writeResult.kind !== "idle") {
236
+ throw new Error(
237
+ `Scoper session failed after scope approval: ${writeResult.kind}`
238
+ );
239
+ }
240
+ continue;
241
+ } else {
242
+ spinner.start("Scoper is revising");
243
+ const reviseResult = await _sendAndWait(server.client, {
244
+ sessionId,
245
+ message: approval,
246
+ stallMs: timeoutMs,
247
+ autoRejectPermissions: true
248
+ });
249
+ if (reviseResult.kind !== "idle") {
250
+ throw new Error(
251
+ `Scoper session failed during revision: ${reviseResult.kind}`
252
+ );
253
+ }
254
+ continue;
255
+ }
256
+ }
257
+ const question = parseQuestion(lastMessage);
258
+ if (question) {
259
+ parseRetryPending = false;
260
+ spinner.stop();
261
+ process.stderr.write(ANSI_RESET);
262
+ questionsAsked++;
263
+ const userAnswer = await _promptUser(question);
264
+ spinner.start("Scoper is thinking");
265
+ const nextResult = await _sendAndWait(server.client, {
266
+ sessionId,
267
+ message: userAnswer,
268
+ stallMs: timeoutMs,
269
+ autoRejectPermissions: true
270
+ });
271
+ if (nextResult.kind === "abort") {
272
+ throw new Error(
273
+ `Scoper session aborted (timeout after ${timeoutMs}ms).`
274
+ );
275
+ }
276
+ if (nextResult.kind === "stall") {
277
+ throw new Error(
278
+ `Scoper session stalled for ${nextResult.stallMs}ms with no idle signal.`
279
+ );
280
+ }
281
+ if (nextResult.kind === "error") {
282
+ throw new Error(`Scoper session error: ${nextResult.message}`);
283
+ }
284
+ continue;
285
+ }
286
+ if (parseRetryPending) {
287
+ spinner.stop();
288
+ throw new Error(
289
+ `Scoper response did not follow the strict contract after retry. Last response: ${lastMessage.slice(0, 200)}`
290
+ );
291
+ }
292
+ parseRetryPending = true;
293
+ spinner.start("Retrying");
294
+ const retryResult = await _sendAndWait(server.client, {
295
+ sessionId,
296
+ message: PARSE_RETRY_REMINDER,
297
+ stallMs: timeoutMs,
298
+ autoRejectPermissions: true
299
+ });
300
+ if (retryResult.kind === "abort") {
301
+ throw new Error(
302
+ `Scoper session aborted during parse retry (timeout after ${timeoutMs}ms).`
303
+ );
304
+ }
305
+ if (retryResult.kind === "stall") {
306
+ throw new Error(
307
+ `Scoper session stalled during parse retry for ${retryResult.stallMs}ms.`
308
+ );
309
+ }
310
+ if (retryResult.kind === "error") {
311
+ throw new Error(
312
+ `Scoper session error during parse retry: ${retryResult.message}`
313
+ );
314
+ }
315
+ }
316
+ } finally {
317
+ spinner.stop();
318
+ await server.shutdown();
319
+ }
320
+ }
321
+ export {
322
+ extractScopeCompletePath,
323
+ parseQuestion,
324
+ parseScopeSummary,
325
+ runScoperSession
326
+ };
@@ -0,0 +1,47 @@
1
+ ---
2
+ name: adversarial-review-rubric
3
+ description: Use when reviewing a diff or PR against a plan or acceptance criteria.
4
+ ---
5
+
6
+ # Adversarial review rubric
7
+
8
+ ## MECE rubric (five dimensions)
9
+
10
+ Every review evaluates five dimensions — every dimension must pass for `[PASS]` or `[PASS_SPEC]`:
11
+
12
+ 1. **Correctness** — Does the code do what the plan says? Are acceptance criteria met?
13
+ 2. **Completeness** — Are all plan items implemented? Are edge cases handled?
14
+ 3. **Consistency** — Does the code follow existing patterns? Are naming/types consistent?
15
+ 4. **Safety** — Are there security, data-loss, or deployment risks?
16
+ 5. **Scope** — Does the diff stay within the plan's `## File-level changes`? No unplanned additions?
17
+
18
+ ## Progressive strictness
19
+
20
+ Strictness increases across Assess iterations within a session:
21
+
22
+ - **Level 1/3 (first Assess):** Standard review. Trust-recent-green applies. Focus on correctness and scope.
23
+ - **Level 2/3 (second Assess, after FIX-INLINE loop):** Elevated scrutiny. Re-run tests unconditionally. Check all five MECE dimensions explicitly.
24
+ - **Level 3/3 (third Assess, after LOOP-TO-PLAN):** Maximum strictness. Treat as a fresh review. Escalate to `@code-reviewer-thorough` regardless of diff size.
25
+
26
+ ## Red CI blocks merge
27
+
28
+ **Red CI blocks merge.** Any red output from typecheck, test, or lint is a FAIL regardless of whether the failure appears pre-existing. Pre-existing status does not exempt a failure from blocking merge. There is no deferral path.
29
+
30
+ ## Unevidenced pre-existing claim rejection
31
+
32
+ **Unevidenced pre-existing claims are rejected.** A claim that a failure is "pre-existing" or "unrelated" is only valid with ALL THREE of:
33
+
34
+ - (a) a specific commit SHA showing the failure pre-dates this branch,
35
+ - (b) `git log` output confirming the commit,
36
+ - (c) merge-base reproduction confirming the failure exists on the merge-base.
37
+
38
+ Without all three, treat the claim as unevidenced and fail the review.
39
+
40
+ ## Return tokens
41
+
42
+ Return tokens are agent-role contracts and stay in each agent's own prompt. For reference:
43
+
44
+ - `@spec-reviewer` uses: `[PASS_SPEC]` or `[FAIL_SPEC: <summary>]`
45
+ - `@code-reviewer` and `@code-reviewer-thorough` use: `[PASS]`, `[LOOP-TO-PLAN: <summary>]`, or `[FIX-INLINE: <summary>]`
46
+
47
+ Use the tokens appropriate to your role as defined in your own prompt.
@@ -26,7 +26,7 @@ Each rule file applies all four principles through the lens of a specific pipeli
26
26
 
27
27
  3. [`rules/building.md`](rules/building.md) — For `@build`. Enforce surgical changes. Verify names before using them. Flag unplanned edits. Write failure-path tests before happy-path code.
28
28
 
29
- 4. [`rules/review.md`](rules/review.md) — For `@qa-reviewer` and `@qa-thorough`. Verify failure-path coverage in the diff. Grep-confirm cross-boundary string literals. Reject diffs with unplanned scope.
29
+ 4. [`rules/review.md`](rules/review.md) — For `@spec-reviewer`, `@code-reviewer`, and `@code-reviewer-thorough`. Verify failure-path coverage in the diff. Grep-confirm cross-boundary string literals. Reject diffs with unplanned scope.
30
30
 
31
31
  ## When to load this skill
32
32
 
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: root-cause-diagnosis
3
+ description: Use when a test, lint, or typecheck failure appears unexpectedly — before assuming it's pre-existing or unrelated.
4
+ ---
5
+
6
+ # Root-cause diagnosis protocol
7
+
8
+ When any test, lint, or typecheck fails during execution, run this protocol before concluding anything about the failure's origin:
9
+
10
+ 1. **Reproduce on merge-base.** Run `git stash && git merge-base HEAD origin/main` (fallback `origin/master`), check out the merge-base, run the failing command, then restore: `git checkout -` and `git stash pop`. If the failure reproduces on the merge-base, it pre-dates this branch — but it still blocks merge.
11
+ 2. **git blame the failing line.** Run `git blame <file> -L <line>,<line>` to identify the commit that introduced the failure. If the commit is on this branch, you introduced it — fix it. If the commit pre-dates this branch, it is pre-existing — but it still blocks merge.
12
+ 3. **Scope check.** If fixing the pre-existing failure would require touching >~5 files outside the plan's `## File-level changes`, STOP with a reorganization proposal. Do NOT defer or log-and-continue.
13
+
14
+ **Exception (TDD-RED state):** Tests written in this session under the TDD order (RED → GREEN) are EXPECTED to fail before their corresponding implementation step. The diagnosis protocol fires on UNEXPECTED failures — tests or lints that were green before your session and are now red, or tests from previous sessions that have started failing. A test you just wrote that has never been green is not an unexpected failure.
15
+
16
+ ## Root-cause rationalization table
17
+
18
+ | Excuse | Reality |
19
+ |---|---|
20
+ | "This test was probably already failing before my change" | "Probably" is not evidence. Run the merge-base reproduction. |
21
+ | "Likely pre-existing — unrelated to my diff" | "Likely" is not evidence. Run `git blame` and show the commit SHA. |
22
+ | "This failure is in a different module, not my concern" | Red CI blocks merge regardless of which module owns the failure. |
23
+ | "I'll log it to Open questions and move on" | There is no deferral path. Fix it or STOP with a reorganization proposal. |
24
+ | "The test is flaky — it passes sometimes" | Flaky tests still block merge. Either fix the flakiness or STOP. |