@glrs-dev/harness-plugin-opencode 2.2.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.
- package/CHANGELOG.md +63 -0
- package/README.md +7 -6
- package/SECURITY.md +1 -1
- package/dist/agents/prompts/build.md +16 -0
- package/dist/agents/prompts/code-reviewer-thorough.md +6 -7
- package/dist/agents/prompts/debriefer.md +55 -0
- package/dist/agents/prompts/plan-reviewer.md +2 -1
- package/dist/agents/prompts/plan.md +97 -7
- package/dist/agents/prompts/prime.md +4 -2
- package/dist/agents/prompts/scoper.md +129 -0
- package/dist/agents/prompts/spec-reviewer.md +0 -1
- package/dist/agents/prompts/spec-reviewer.open.md +0 -1
- package/dist/autopilot/prompt-template.md +69 -45
- package/dist/chunk-GCWHRUOK.js +259 -0
- package/dist/chunk-MJSMBY2Y.js +87 -0
- package/dist/chunk-NIFAVPNN.js +544 -0
- package/dist/cli.js +448 -503
- package/dist/index.js +90 -14
- package/dist/loop-session-J35NILUZ.js +30 -0
- package/dist/opencode-server-KPCDFYAX.js +22 -0
- package/dist/plan-parser-TMHEKT22.js +6 -0
- package/dist/plan-session-7VS32P52.js +117 -0
- package/dist/scoper-S77SOK7X.js +326 -0
- package/dist/skills/spear-protocol/SKILL.md +2 -1
- package/package.json +3 -1
- package/dist/bin/plan-check.sh +0 -255
package/dist/index.js
CHANGED
|
@@ -59,6 +59,7 @@ function readPrompt(name) {
|
|
|
59
59
|
throw new Error(`Could not find prompt file: ${name}`);
|
|
60
60
|
}
|
|
61
61
|
var primePrompt = readPrompt("prime.md");
|
|
62
|
+
var scoperPrompt = readPrompt("scoper.md");
|
|
62
63
|
var planPrompt = readPrompt("plan.md");
|
|
63
64
|
var buildPrompt = readPrompt("build.md");
|
|
64
65
|
var buildOpenPrompt = readPrompt("build.open.md");
|
|
@@ -78,6 +79,7 @@ var researchPrompt = readPrompt("research.md");
|
|
|
78
79
|
var researchWebPrompt = readPrompt("research-web.md");
|
|
79
80
|
var researchLocalPrompt = readPrompt("research-local.md");
|
|
80
81
|
var researchAutoPrompt = readPrompt("research-auto.md");
|
|
82
|
+
var debrieferPrompt = readPrompt("debriefer.md");
|
|
81
83
|
var EXECUTOR_VARIANT_AGENTS = {
|
|
82
84
|
build: { reasoning: buildPrompt, strict: buildOpenPrompt },
|
|
83
85
|
"spec-reviewer": { reasoning: specReviewerPrompt, strict: specReviewerOpenPrompt },
|
|
@@ -214,7 +216,7 @@ var CORE_BASH_ALLOW_LIST = {
|
|
|
214
216
|
"eslint *": "allow",
|
|
215
217
|
"prettier *": "allow",
|
|
216
218
|
"biome *": "allow",
|
|
217
|
-
// Our own CLI
|
|
219
|
+
// Our own CLI (install, doctor, autopilot, etc.) — reviewer/build invocations.
|
|
218
220
|
"bunx @glrs-dev/harness-plugin-opencode *": "allow",
|
|
219
221
|
"glrs-oc *": "allow",
|
|
220
222
|
// GitHub CLI — read-only gh calls are fine; destructive `gh pr merge`
|
|
@@ -269,21 +271,33 @@ var PRIME_PERMISSIONS = {
|
|
|
269
271
|
playwright: "allow",
|
|
270
272
|
linear: "allow"
|
|
271
273
|
};
|
|
274
|
+
var SCOPER_PERMISSIONS = {
|
|
275
|
+
...PRIME_PERMISSIONS
|
|
276
|
+
};
|
|
277
|
+
var SCOPER_DISABLED_TOOLS = {
|
|
278
|
+
question: false
|
|
279
|
+
};
|
|
280
|
+
var AUTOPILOT_PRIME_DISABLED_TOOLS = {
|
|
281
|
+
question: false
|
|
282
|
+
};
|
|
272
283
|
var PLAN_PERMISSIONS = {
|
|
273
284
|
edit: "allow",
|
|
274
|
-
|
|
275
|
-
//
|
|
276
|
-
//
|
|
277
|
-
//
|
|
278
|
-
//
|
|
279
|
-
//
|
|
280
|
-
//
|
|
285
|
+
write: "allow",
|
|
286
|
+
// Plan agent is read-only aside from writing under the plan dir. It
|
|
287
|
+
// resolves the plan dir inline (see src/agents/prompts/plan.md
|
|
288
|
+
// `## 4. Write the plan`): `$HOME/.glorious/opencode/<repo-folder>/plans/`,
|
|
289
|
+
// where `<repo-folder>` comes from
|
|
290
|
+
// `basename(dirname(git rev-parse --git-common-dir))`. The object-form
|
|
291
|
+
// denies bash broadly and re-allows only the four commands that snippet
|
|
292
|
+
// needs. Everything else remains denied, preserving the "plan writes only
|
|
293
|
+
// plan files" invariant (the write-scope constraint is prompt-enforced,
|
|
294
|
+
// not permission-enforced).
|
|
281
295
|
bash: {
|
|
282
296
|
"*": "deny",
|
|
283
|
-
"
|
|
284
|
-
"
|
|
285
|
-
"
|
|
286
|
-
"
|
|
297
|
+
"git rev-parse --git-common-dir": "allow",
|
|
298
|
+
"basename *": "allow",
|
|
299
|
+
"dirname *": "allow",
|
|
300
|
+
"mkdir -p *": "allow"
|
|
287
301
|
},
|
|
288
302
|
webfetch: "allow",
|
|
289
303
|
ast_grep: "deny",
|
|
@@ -500,8 +514,39 @@ var RESEARCH_PERMISSIONS = {
|
|
|
500
514
|
playwright: "allow",
|
|
501
515
|
linear: "allow"
|
|
502
516
|
};
|
|
517
|
+
var DEBRIEFER_PERMISSIONS = {
|
|
518
|
+
edit: "deny",
|
|
519
|
+
bash: {
|
|
520
|
+
"*": "deny",
|
|
521
|
+
"git log *": "allow",
|
|
522
|
+
"git diff *": "allow",
|
|
523
|
+
"git show *": "allow",
|
|
524
|
+
"git status *": "allow",
|
|
525
|
+
"git rev-parse *": "allow",
|
|
526
|
+
"git branch *": "allow",
|
|
527
|
+
"cat *": "allow",
|
|
528
|
+
"head *": "allow",
|
|
529
|
+
"tail *": "allow",
|
|
530
|
+
"ls *": "allow",
|
|
531
|
+
"wc *": "allow"
|
|
532
|
+
},
|
|
533
|
+
webfetch: "deny",
|
|
534
|
+
ast_grep: "deny",
|
|
535
|
+
tsc_check: "deny",
|
|
536
|
+
eslint_check: "deny",
|
|
537
|
+
todo_scan: "deny",
|
|
538
|
+
comment_check: "deny",
|
|
539
|
+
question: "deny",
|
|
540
|
+
serena: "deny",
|
|
541
|
+
memory: "deny",
|
|
542
|
+
git: "allow",
|
|
543
|
+
playwright: "deny",
|
|
544
|
+
linear: "deny"
|
|
545
|
+
};
|
|
503
546
|
var AGENT_TIERS = {
|
|
504
547
|
prime: "deep",
|
|
548
|
+
scoper: "deep",
|
|
549
|
+
"autopilot-prime": "deep",
|
|
505
550
|
plan: "deep",
|
|
506
551
|
"architecture-advisor": "deep",
|
|
507
552
|
"plan-reviewer": "deep",
|
|
@@ -517,6 +562,7 @@ var AGENT_TIERS = {
|
|
|
517
562
|
"docs-maintainer": "mid",
|
|
518
563
|
"lib-reader": "mid",
|
|
519
564
|
"agents-md-writer": "mid",
|
|
565
|
+
debriefer: "mid",
|
|
520
566
|
"code-searcher": "fast"
|
|
521
567
|
};
|
|
522
568
|
function createAgents() {
|
|
@@ -529,11 +575,31 @@ function createAgents() {
|
|
|
529
575
|
temperature: 0.2,
|
|
530
576
|
permission: PRIME_PERMISSIONS
|
|
531
577
|
}),
|
|
578
|
+
scoper: agentFromPrompt(scoperPrompt, {
|
|
579
|
+
description: "Interactive scoping agent. Runs an inquirer-driven wizard loop \u2014 asks short questions via assistant text, collects answers via inquirer, then writes scope.md. Use at the start of a new feature to align on intent, constraints, and acceptance criteria before planning.",
|
|
580
|
+
mode: "primary",
|
|
581
|
+
model: "anthropic/claude-opus-4-7",
|
|
582
|
+
temperature: 0.3,
|
|
583
|
+
permission: SCOPER_PERMISSIONS,
|
|
584
|
+
tools: SCOPER_DISABLED_TOOLS
|
|
585
|
+
}),
|
|
586
|
+
"autopilot-prime": agentFromPrompt(primePrompt, {
|
|
587
|
+
description: "PRIME for unattended autopilot sessions. Identical to `prime` except the `question` tool is disabled \u2014 autopilot has no user to answer interactive prompts, and a blocking question deadlocks the session. Not user-selectable; invoked by the Ralph loop.",
|
|
588
|
+
mode: "subagent",
|
|
589
|
+
model: "anthropic/claude-opus-4-7",
|
|
590
|
+
temperature: 0.2,
|
|
591
|
+
permission: PRIME_PERMISSIONS,
|
|
592
|
+
tools: AUTOPILOT_PRIME_DISABLED_TOOLS
|
|
593
|
+
}),
|
|
532
594
|
plan: agentFromPrompt(planPrompt, {
|
|
533
|
-
description: "Interactive planner. Orchestrates gap analysis and adversarial review. Produces a written plan in the repo-shared plan directory (
|
|
595
|
+
description: "Interactive planner. Orchestrates gap analysis and adversarial review. Produces a written plan in the repo-shared plan directory (`~/.glorious/opencode/<repo-folder>/plans/`, resolved inline via `git rev-parse --git-common-dir`).",
|
|
534
596
|
mode: "all",
|
|
535
597
|
model: "anthropic/claude-opus-4-7",
|
|
536
598
|
temperature: 0.3,
|
|
599
|
+
// @plan dispatches @gap-analyzer, @code-searcher, and @plan-reviewer
|
|
600
|
+
// as subagents. OpenCode strips the `task` tool from subagent contexts
|
|
601
|
+
// by default; explicit opt-in re-enables it.
|
|
602
|
+
tools: { task: true },
|
|
537
603
|
permission: PLAN_PERMISSIONS
|
|
538
604
|
}),
|
|
539
605
|
build: agentFromPrompt(buildPrompt, {
|
|
@@ -580,6 +646,8 @@ function createAgents() {
|
|
|
580
646
|
mode: "all",
|
|
581
647
|
model: "anthropic/claude-opus-4-7",
|
|
582
648
|
temperature: 0.3,
|
|
649
|
+
// @research dispatches @research-web, @research-local, @research-auto.
|
|
650
|
+
tools: { task: true },
|
|
583
651
|
permission: RESEARCH_PERMISSIONS
|
|
584
652
|
}),
|
|
585
653
|
// Research subagents — thin shims that load the bundled skills.
|
|
@@ -591,6 +659,8 @@ function createAgents() {
|
|
|
591
659
|
mode: "subagent",
|
|
592
660
|
model: "anthropic/claude-opus-4-7",
|
|
593
661
|
temperature: 0.3,
|
|
662
|
+
// @research-web dispatches its own parallel workstream agents.
|
|
663
|
+
tools: { task: true },
|
|
594
664
|
permission: RESEARCH_PERMISSIONS
|
|
595
665
|
}),
|
|
596
666
|
"research-local": agentFromPrompt(researchLocalPrompt, {
|
|
@@ -598,6 +668,8 @@ function createAgents() {
|
|
|
598
668
|
mode: "subagent",
|
|
599
669
|
model: "anthropic/claude-opus-4-7",
|
|
600
670
|
temperature: 0.3,
|
|
671
|
+
// @research-local dispatches parallel Explore subagents.
|
|
672
|
+
tools: { task: true },
|
|
601
673
|
permission: RESEARCH_PERMISSIONS
|
|
602
674
|
}),
|
|
603
675
|
"research-auto": agentFromPrompt(researchAutoPrompt, {
|
|
@@ -606,6 +678,10 @@ function createAgents() {
|
|
|
606
678
|
model: "anthropic/claude-opus-4-7",
|
|
607
679
|
temperature: 0.3,
|
|
608
680
|
permission: RESEARCH_PERMISSIONS
|
|
681
|
+
}),
|
|
682
|
+
// Debriefer — post-run summary agent for the autopilot CLI
|
|
683
|
+
debriefer: agentFromPrompt(debrieferPrompt, {
|
|
684
|
+
permission: DEBRIEFER_PERMISSIONS
|
|
609
685
|
})
|
|
610
686
|
};
|
|
611
687
|
}
|
|
@@ -2025,7 +2101,7 @@ import { join as join9 } from "path";
|
|
|
2025
2101
|
var APP_KEY = "A-US-3617699429";
|
|
2026
2102
|
var ENDPOINT = "https://us.aptabase.com/api/v0/event";
|
|
2027
2103
|
var PKG_NAME = "@glrs-dev/harness-plugin-opencode";
|
|
2028
|
-
var PKG_VERSION = true ? "2.
|
|
2104
|
+
var PKG_VERSION = true ? "2.3.0" : "dev";
|
|
2029
2105
|
var DISABLED = process.env.HARNESS_OPENCODE_TELEMETRY === "0" || process.env.HARNESS_OPENCODE_TELEMETRY === "false" || process.env.DO_NOT_TRACK === "1" || process.env.CI === "true";
|
|
2030
2106
|
var SESSION_ID = randomUUID();
|
|
2031
2107
|
function getInstallId() {
|
|
@@ -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,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
|
+
};
|