@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/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 the plan agent and assessor both call plan-check/plan-dir.
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
- // Plan agent is read-only aside from writing under the plan dir — but
275
- // it does need to RESOLVE the plan dir via the `plan-dir` CLI
276
- // subcommand (returns an absolute path derived from the worktree's
277
- // repo-folder key; see src/plan-paths.ts and src/cli.ts). The object-
278
- // form denies bash broadly and re-allows only `bunx
279
- // @glrs-dev/harness-plugin-opencode plan-dir[...]`. No other bash invocation
280
- // is permitted, so the read-only-aside-from-plans invariant holds.
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
- "bunx @glrs-dev/harness-plugin-opencode plan-dir": "allow",
284
- "bunx @glrs-dev/harness-plugin-opencode plan-dir *": "allow",
285
- "glrs-oc plan-dir": "allow",
286
- "glrs-oc plan-dir *": "allow"
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 (resolve via `bunx @glrs-dev/harness-plugin-opencode plan-dir`).",
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.2.0" : "dev";
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,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
+ };