@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
package/dist/index.js CHANGED
@@ -1,12 +1,10 @@
1
- import {
2
- formatModelOverrideWarning,
3
- validateModelOverride
4
- } from "./chunk-DZG4D3OH.js";
5
1
  import {
6
2
  PACKAGE_NAME,
3
+ formatModelOverrideWarning,
7
4
  readOurPackageVersion,
8
- refreshPluginCache
9
- } from "./chunk-VJUETC6A.js";
5
+ refreshPluginCache,
6
+ validateModelOverride
7
+ } from "./chunk-PDMXYZM4.js";
10
8
 
11
9
  // src/config-hook.ts
12
10
  import * as fs from "fs";
@@ -36,6 +34,7 @@ function readMd(name) {
36
34
  throw new Error(`Could not find shared file: ${name}`);
37
35
  }
38
36
  var WORKFLOW_MECHANICS_RULE = readMd("workflow-mechanics.md");
37
+ var UI_EVALUATION_LADDER = readMd("ui-evaluation-ladder.md");
39
38
 
40
39
  // src/agents/index.ts
41
40
  import { readFileSync as readFileSync2 } from "fs";
@@ -60,12 +59,15 @@ function readPrompt(name) {
60
59
  throw new Error(`Could not find prompt file: ${name}`);
61
60
  }
62
61
  var primePrompt = readPrompt("prime.md");
62
+ var scoperPrompt = readPrompt("scoper.md");
63
63
  var planPrompt = readPrompt("plan.md");
64
64
  var buildPrompt = readPrompt("build.md");
65
65
  var buildOpenPrompt = readPrompt("build.open.md");
66
- var qaReviewerPrompt = readPrompt("qa-reviewer.md");
67
- var qaReviewerOpenPrompt = readPrompt("qa-reviewer.open.md");
68
- var qaThoroughPrompt = readPrompt("qa-thorough.md");
66
+ var specReviewerPrompt = readPrompt("spec-reviewer.md");
67
+ var specReviewerOpenPrompt = readPrompt("spec-reviewer.open.md");
68
+ var codeReviewerPrompt = readPrompt("code-reviewer.md");
69
+ var codeReviewerOpenPrompt = readPrompt("code-reviewer.open.md");
70
+ var codeReviewerThoroughPrompt = readPrompt("code-reviewer-thorough.md");
69
71
  var planReviewerPrompt = readPrompt("plan-reviewer.md");
70
72
  var codeSearcherPrompt = readPrompt("code-searcher.md");
71
73
  var gapAnalyzerPrompt = readPrompt("gap-analyzer.md");
@@ -73,18 +75,15 @@ var architectureAdvisorPrompt = readPrompt("architecture-advisor.md");
73
75
  var docsMaintainerPrompt = readPrompt("docs-maintainer.md");
74
76
  var libReaderPrompt = readPrompt("lib-reader.md");
75
77
  var agentsMdWriterPrompt = readPrompt("agents-md-writer.md");
76
- var pilotScopePrompt = readPrompt("pilot-scoper.md");
77
- var pilotPlannerPrompt = readPrompt("pilot-planner.md");
78
- var pilotBuilderPrompt = readPrompt("pilot-builder.md");
79
- var pilotAssessorPrompt = readPrompt("pilot-assessor.md");
80
78
  var researchPrompt = readPrompt("research.md");
81
79
  var researchWebPrompt = readPrompt("research-web.md");
82
80
  var researchLocalPrompt = readPrompt("research-local.md");
83
81
  var researchAutoPrompt = readPrompt("research-auto.md");
82
+ var debrieferPrompt = readPrompt("debriefer.md");
84
83
  var EXECUTOR_VARIANT_AGENTS = {
85
84
  build: { reasoning: buildPrompt, strict: buildOpenPrompt },
86
- "qa-reviewer": { reasoning: qaReviewerPrompt, strict: qaReviewerOpenPrompt },
87
- "pilot-builder": { reasoning: pilotBuilderPrompt, strict: pilotBuilderPrompt }
85
+ "spec-reviewer": { reasoning: specReviewerPrompt, strict: specReviewerOpenPrompt },
86
+ "code-reviewer": { reasoning: codeReviewerPrompt, strict: codeReviewerOpenPrompt }
88
87
  };
89
88
  function getStrictPrompt(agentName) {
90
89
  const variants = EXECUTOR_VARIANT_AGENTS[agentName];
@@ -135,10 +134,13 @@ function parseFrontmatter(md) {
135
134
  function injectWorkflowMechanics(prompt) {
136
135
  return prompt.replace("{WORKFLOW_MECHANICS_RULE}", WORKFLOW_MECHANICS_RULE);
137
136
  }
137
+ function injectUIEvaluationLadder(prompt) {
138
+ return prompt.replace("{UI_EVALUATION_LADDER}", UI_EVALUATION_LADDER);
139
+ }
138
140
  function agentFromPrompt(raw, overrides = {}) {
139
141
  const fm = parseFrontmatter(raw);
140
142
  const body = stripFrontmatter(raw);
141
- const prompt = injectWorkflowMechanics(body);
143
+ const prompt = injectUIEvaluationLadder(injectWorkflowMechanics(body));
142
144
  const base = {
143
145
  description: fm["description"] ?? "",
144
146
  mode: fm["mode"] ?? "subagent",
@@ -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 qa-reviewer 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",
@@ -295,7 +309,7 @@ var PLAN_PERMISSIONS = {
295
309
  serena: "allow",
296
310
  memory: "allow",
297
311
  git: "allow",
298
- playwright: "deny",
312
+ playwright: "allow",
299
313
  linear: "allow"
300
314
  };
301
315
  var BUILD_PERMISSIONS = {
@@ -323,15 +337,28 @@ var BUILD_PERMISSIONS = {
323
337
  playwright: "allow",
324
338
  linear: "allow"
325
339
  };
326
- var QA_REVIEWER_PERMISSIONS = {
340
+ var SPEC_REVIEWER_PERMISSIONS = {
341
+ edit: "deny",
342
+ bash: {
343
+ "*": "allow",
344
+ ...CORE_BASH_ALLOW_LIST,
345
+ ...CORE_DESTRUCTIVE_BASH_DENIES
346
+ },
347
+ webfetch: "deny",
348
+ ast_grep: "allow",
349
+ tsc_check: "allow",
350
+ eslint_check: "allow",
351
+ todo_scan: "allow",
352
+ comment_check: "allow",
353
+ question: "allow",
354
+ serena: "allow",
355
+ memory: "deny",
356
+ git: "allow",
357
+ playwright: "allow",
358
+ linear: "deny"
359
+ };
360
+ var CODE_REVIEWER_PERMISSIONS = {
327
361
  edit: "deny",
328
- // Object-form bash: the scalar `"allow"` shape loses to OpenCode's
329
- // upstream subagent-default `{bash, *, ask}` via last-match-wins (see
330
- // the root-cause comment near PRIME_PERMISSIONS). Enumerated
331
- // specific patterns in CORE_BASH_ALLOW_LIST sort AFTER the upstream
332
- // wildcard ask and win for the commands they match. `"*": "allow"`
333
- // is kept as a backstop but may still lose to the upstream rule for
334
- // commands not in the enumerated list; those are the known blind spot.
335
362
  bash: {
336
363
  "*": "allow",
337
364
  ...CORE_BASH_ALLOW_LIST,
@@ -350,12 +377,8 @@ var QA_REVIEWER_PERMISSIONS = {
350
377
  playwright: "allow",
351
378
  linear: "deny"
352
379
  };
353
- var QA_THOROUGH_PERMISSIONS = {
380
+ var CODE_REVIEWER_THOROUGH_PERMISSIONS = {
354
381
  edit: "deny",
355
- // Same object-form as QA_REVIEWER_PERMISSIONS — see the shape rationale
356
- // there. qa-thorough re-runs the full suite unconditionally (per its
357
- // prompt), so it touches the same command surface as qa-reviewer and
358
- // needs the identical bash allow-list.
359
382
  bash: {
360
383
  "*": "allow",
361
384
  ...CORE_BASH_ALLOW_LIST,
@@ -403,7 +426,7 @@ var GAP_ANALYZER_PERMISSIONS = {
403
426
  serena: "allow",
404
427
  memory: "allow",
405
428
  git: "deny",
406
- playwright: "deny",
429
+ playwright: "allow",
407
430
  linear: "allow"
408
431
  };
409
432
  var CODE_SEARCHER_PERMISSIONS = {
@@ -488,151 +511,59 @@ var RESEARCH_PERMISSIONS = {
488
511
  serena: "allow",
489
512
  memory: "allow",
490
513
  git: "allow",
491
- playwright: "deny",
514
+ playwright: "allow",
492
515
  linear: "allow"
493
516
  };
494
- var AGENT_TIERS = {
495
- prime: "deep",
496
- plan: "deep",
497
- "qa-thorough": "deep",
498
- "architecture-advisor": "deep",
499
- "plan-reviewer": "deep",
500
- "gap-analyzer": "deep",
501
- research: "deep",
502
- "research-web": "deep",
503
- "research-local": "deep",
504
- "research-auto": "deep",
505
- // Pilot v2 agents
506
- "pilot-scoper": "mid",
507
- "pilot-planner": "mid",
508
- "pilot-builder": "mid-execute",
509
- "pilot-assessor": "mid",
510
- build: "mid-execute",
511
- "qa-reviewer": "mid-execute",
512
- "docs-maintainer": "mid",
513
- "lib-reader": "mid",
514
- "agents-md-writer": "mid",
515
- "code-searcher": "fast"
516
- };
517
- var PILOT_SCOPER_PERMISSIONS = {
517
+ var DEBRIEFER_PERMISSIONS = {
518
518
  edit: "deny",
519
519
  bash: {
520
520
  "*": "deny",
521
- "ls *": "allow",
522
- "cat *": "allow",
523
- "head *": "allow",
524
- "tail *": "allow",
525
- "wc *": "allow",
526
- "grep *": "allow",
527
- "rg *": "allow",
528
- "find *": "allow",
529
- "git status *": "allow",
530
521
  "git log *": "allow",
531
522
  "git diff *": "allow",
532
523
  "git show *": "allow",
524
+ "git status *": "allow",
525
+ "git rev-parse *": "allow",
533
526
  "git branch *": "allow",
534
- "git rev-parse *": "allow"
535
- },
536
- webfetch: "deny",
537
- ast_grep: "allow",
538
- tsc_check: "deny",
539
- eslint_check: "deny",
540
- todo_scan: "allow",
541
- comment_check: "allow",
542
- question: "allow",
543
- serena: "allow",
544
- memory: "deny",
545
- git: "allow",
546
- playwright: "deny",
547
- linear: "deny"
548
- };
549
- var PILOT_PLANNER_PERMISSIONS = {
550
- edit: "allow",
551
- bash: {
552
- "*": "deny",
553
- "ls *": "allow",
554
527
  "cat *": "allow",
555
528
  "head *": "allow",
556
529
  "tail *": "allow",
557
- "wc *": "allow",
558
- "grep *": "allow",
559
- "rg *": "allow",
560
- "find *": "allow",
561
- "git status *": "allow",
562
- "git log *": "allow",
563
- "git diff *": "allow",
564
- "git show *": "allow",
565
- "git branch *": "allow",
566
- "git rev-parse *": "allow"
530
+ "ls *": "allow",
531
+ "wc *": "allow"
567
532
  },
568
533
  webfetch: "deny",
569
- ast_grep: "allow",
534
+ ast_grep: "deny",
570
535
  tsc_check: "deny",
571
536
  eslint_check: "deny",
572
- todo_scan: "allow",
573
- comment_check: "allow",
574
- question: "deny",
575
- serena: "allow",
576
- memory: "deny",
577
- git: "allow",
578
- playwright: "deny",
579
- linear: "deny"
580
- };
581
- var PILOT_BUILDER_PERMISSIONS = {
582
- edit: "allow",
583
- bash: {
584
- "*": "allow",
585
- ...CORE_BASH_ALLOW_LIST,
586
- ...CORE_DESTRUCTIVE_BASH_DENIES,
587
- "git commit*": "deny",
588
- "git push*": "deny",
589
- "git tag*": "deny",
590
- "git checkout *": "deny",
591
- "git switch *": "deny",
592
- "git branch *": "deny",
593
- "git restore --source*": "deny",
594
- "git reset *": "deny",
595
- "gh pr *": "deny",
596
- "gh release *": "deny"
597
- },
598
- webfetch: "allow",
599
- ast_grep: "allow",
600
- tsc_check: "allow",
601
- eslint_check: "allow",
602
- todo_scan: "allow",
603
- comment_check: "allow",
537
+ todo_scan: "deny",
538
+ comment_check: "deny",
604
539
  question: "deny",
605
- serena: "allow",
540
+ serena: "deny",
606
541
  memory: "deny",
607
542
  git: "allow",
608
543
  playwright: "deny",
609
544
  linear: "deny"
610
545
  };
611
- var PILOT_ASSESSOR_PERMISSIONS = {
612
- edit: "allow",
613
- bash: {
614
- "*": "allow",
615
- ...CORE_BASH_ALLOW_LIST,
616
- ...CORE_DESTRUCTIVE_BASH_DENIES,
617
- "git commit*": "deny",
618
- "git push*": "deny",
619
- "git checkout *": "deny",
620
- "git switch *": "deny",
621
- "git reset *": "deny",
622
- "gh pr *": "deny"
623
- },
624
- webfetch: "deny",
625
- ast_grep: "allow",
626
- tsc_check: "allow",
627
- eslint_check: "allow",
628
- todo_scan: "allow",
629
- comment_check: "allow",
630
- question: "deny",
631
- serena: "allow",
632
- memory: "deny",
633
- git: "allow",
634
- playwright: "allow",
635
- linear: "deny"
546
+ var AGENT_TIERS = {
547
+ prime: "deep",
548
+ scoper: "deep",
549
+ "autopilot-prime": "deep",
550
+ plan: "deep",
551
+ "architecture-advisor": "deep",
552
+ "plan-reviewer": "deep",
553
+ "gap-analyzer": "deep",
554
+ research: "deep",
555
+ "research-web": "deep",
556
+ "research-local": "deep",
557
+ "research-auto": "deep",
558
+ build: "mid-execute",
559
+ "spec-reviewer": "mid-execute",
560
+ "code-reviewer": "mid-execute",
561
+ "code-reviewer-thorough": "deep",
562
+ "docs-maintainer": "mid",
563
+ "lib-reader": "mid",
564
+ "agents-md-writer": "mid",
565
+ debriefer: "mid",
566
+ "code-searcher": "fast"
636
567
  };
637
568
  function createAgents() {
638
569
  return {
@@ -644,11 +575,31 @@ function createAgents() {
644
575
  temperature: 0.2,
645
576
  permission: PRIME_PERMISSIONS
646
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
+ }),
647
594
  plan: agentFromPrompt(planPrompt, {
648
- 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`).",
649
596
  mode: "all",
650
597
  model: "anthropic/claude-opus-4-7",
651
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 },
652
603
  permission: PLAN_PERMISSIONS
653
604
  }),
654
605
  build: agentFromPrompt(buildPrompt, {
@@ -661,11 +612,14 @@ function createAgents() {
661
612
  // Subagents — model/mode/description from frontmatter, permissions
662
613
  // via overrides (see permission blocks above). docs-maintainer has no
663
614
  // frontmatter permission declaration and keeps that behavior.
664
- "qa-reviewer": agentFromPrompt(qaReviewerPrompt, {
665
- permission: QA_REVIEWER_PERMISSIONS
615
+ "spec-reviewer": agentFromPrompt(specReviewerPrompt, {
616
+ permission: SPEC_REVIEWER_PERMISSIONS
666
617
  }),
667
- "qa-thorough": agentFromPrompt(qaThoroughPrompt, {
668
- permission: QA_THOROUGH_PERMISSIONS
618
+ "code-reviewer": agentFromPrompt(codeReviewerPrompt, {
619
+ permission: CODE_REVIEWER_PERMISSIONS
620
+ }),
621
+ "code-reviewer-thorough": agentFromPrompt(codeReviewerThoroughPrompt, {
622
+ permission: CODE_REVIEWER_THOROUGH_PERMISSIONS
669
623
  }),
670
624
  "plan-reviewer": agentFromPrompt(planReviewerPrompt, {
671
625
  permission: PLAN_REVIEWER_PERMISSIONS
@@ -692,58 +646,42 @@ function createAgents() {
692
646
  mode: "all",
693
647
  model: "anthropic/claude-opus-4-7",
694
648
  temperature: 0.3,
649
+ // @research dispatches @research-web, @research-local, @research-auto.
650
+ tools: { task: true },
695
651
  permission: RESEARCH_PERMISSIONS
696
652
  }),
697
- // Research subagents — thin shims that load the bundled skills
653
+ // Research subagents — thin shims that load the bundled skills.
654
+ // mode: "subagent" — these are internal implementation details of
655
+ // @research's orchestration; users should invoke @research (mode:all)
656
+ // as the primary entry point, not these directly.
698
657
  "research-web": agentFromPrompt(researchWebPrompt, {
699
658
  description: "Research orchestrator subagent \u2014 Multi-agent web research orchestrator. Decomposes a research question into parallel agent workstreams, launches them, monitors progress, and synthesizes results. Use when user says 'research this topic', 'I need to understand', 'deep dive into', 'investigate the market for', 'what do we know about'. Provide the research topic and context.",
700
- mode: "all",
659
+ mode: "subagent",
701
660
  model: "anthropic/claude-opus-4-7",
702
661
  temperature: 0.3,
662
+ // @research-web dispatches its own parallel workstream agents.
663
+ tools: { task: true },
703
664
  permission: RESEARCH_PERMISSIONS
704
665
  }),
705
666
  "research-local": agentFromPrompt(researchLocalPrompt, {
706
667
  description: "Research orchestrator subagent \u2014 Deep codebase research using parallel Explore subagents. Decomposes a question about the local codebase into research tasks, launches parallel explorations, reviews for gaps, iterates, and synthesizes findings with specific file paths and line numbers. Use when user says 'how does X work in this codebase', 'where is Y implemented', 'trace the data flow for Z', 'what patterns does this repo use', 'explain the architecture of'. Provide the research topic as arguments.",
707
- mode: "all",
668
+ mode: "subagent",
708
669
  model: "anthropic/claude-opus-4-7",
709
670
  temperature: 0.3,
671
+ // @research-local dispatches parallel Explore subagents.
672
+ tools: { task: true },
710
673
  permission: RESEARCH_PERMISSIONS
711
674
  }),
712
675
  "research-auto": agentFromPrompt(researchAutoPrompt, {
713
676
  description: "Research orchestrator subagent \u2014 Autonomous experimentation skill. Agent interviews the user, sets up a lab, then explores freely (think, test, reflect) until stopped or a target is hit. Works for any domain where you can measure or evaluate a result. Use when user says 'optimize this', 'experiment with', 'find the best approach', 'iterate on', 'research mode'. Do NOT use for binary validation tests (use /spec-lab instead). Based on ResearcherSkill v1.4.4 by krzysztofdudek.",
714
- mode: "all",
677
+ mode: "subagent",
715
678
  model: "anthropic/claude-opus-4-7",
716
679
  temperature: 0.3,
717
680
  permission: RESEARCH_PERMISSIONS
718
681
  }),
719
- // Pilot v2 agents (SPEAR-based autonomous execution)
720
- "pilot-scoper": agentFromPrompt(pilotScopePrompt, {
721
- description: "Pilot v2 scoping agent. Interviews the user to understand their goal, explores the codebase, and produces a scope.json artifact with framing and acceptance criteria.",
722
- mode: "subagent",
723
- model: "anthropic/claude-sonnet-4-6",
724
- temperature: 0.3,
725
- permission: PILOT_SCOPER_PERMISSIONS
726
- }),
727
- "pilot-planner": agentFromPrompt(pilotPlannerPrompt, {
728
- description: "Pilot v2 planning agent. Reads scope.json, surveys the codebase, and produces a plan.json with an ordered task list.",
729
- mode: "subagent",
730
- model: "anthropic/claude-sonnet-4-6",
731
- temperature: 0.2,
732
- permission: PILOT_PLANNER_PERMISSIONS
733
- }),
734
- "pilot-builder": agentFromPrompt(pilotBuilderPrompt, {
735
- description: "Pilot v2 builder agent. Executes a single task from the plan. Makes code changes, runs verify commands, and signals completion.",
736
- mode: "subagent",
737
- model: "anthropic/claude-sonnet-4-6",
738
- temperature: 0.1,
739
- permission: PILOT_BUILDER_PERMISSIONS
740
- }),
741
- "pilot-assessor": agentFromPrompt(pilotAssessorPrompt, {
742
- description: "Pilot v2 assessor agent. Evaluates completed work against acceptance criteria, runs deployment-risk reflection, and produces an assessment report.",
743
- mode: "subagent",
744
- model: "anthropic/claude-sonnet-4-6",
745
- temperature: 0.2,
746
- permission: PILOT_ASSESSOR_PERMISSIONS
682
+ // Debriefer post-run summary agent for the autopilot CLI
683
+ debriefer: agentFromPrompt(debrieferPrompt, {
684
+ permission: DEBRIEFER_PERMISSIONS
747
685
  })
748
686
  };
749
687
  }
@@ -770,7 +708,6 @@ function readPrompt2(name) {
770
708
  }
771
709
  throw new Error(`Could not find command prompt: ${name}`);
772
710
  }
773
- var autopilotPrompt = readPrompt2("autopilot.md");
774
711
  var shipPrompt = readPrompt2("ship.md");
775
712
  var reviewPrompt = readPrompt2("review.md");
776
713
  var initDeepPrompt = readPrompt2("init-deep.md");
@@ -779,10 +716,6 @@ var freshPrompt = readPrompt2("fresh.md");
779
716
  var costsPrompt = readPrompt2("costs.md");
780
717
  function createCommands() {
781
718
  return {
782
- autopilot: {
783
- template: autopilotPrompt,
784
- description: "Self-driving run. Pass a ticket ref (any tracker), a task description, or a question."
785
- },
786
719
  ship: {
787
720
  template: shipPrompt,
788
721
  description: "Finalize, commit, push, and open a PR/MR. Human-gated at each step."
@@ -927,7 +860,7 @@ function resolveHarnessModels(agents, config, pluginOptions) {
927
860
  }
928
861
  }
929
862
  if (midExecuteConfigured) {
930
- const EXECUTOR_AGENTS = ["build", "qa-reviewer", "pilot-builder"];
863
+ const EXECUTOR_AGENTS = ["build", "spec-reviewer", "code-reviewer"];
931
864
  for (const agentName of EXECUTOR_AGENTS) {
932
865
  const agentCfg = agents[agentName];
933
866
  if (!agentCfg) continue;
@@ -1492,311 +1425,8 @@ function loadDotenv(directory) {
1492
1425
  return { filesLoaded, varsSet };
1493
1426
  }
1494
1427
 
1495
- // src/plugins/autopilot.ts
1496
- import { execFile as execFileCb } from "child_process";
1497
- import * as fs3 from "fs/promises";
1498
- import * as path3 from "path";
1499
- import { promisify as promisify6 } from "util";
1500
- var STATE_PATH = ".agent/autopilot-state.json";
1501
- var KILL_SWITCH_PATH = ".agent/autopilot-disable";
1502
- var MAX_ITERATIONS = 20;
1503
- var TARGET_AGENTS = /* @__PURE__ */ new Set(["build", "prime"]);
1504
- var MESSAGE_LIMIT = 40;
1505
- var NUDGE_DEBOUNCE_MS = 3e4;
1506
- var PR_CACHE_MS = 5 * 60 * 1e3;
1507
- var MAX_CONSECUTIVE_STOPS = 2;
1508
- var UMBRELLA_MIN_BYTES = 5e4;
1509
- var UMBRELLA_MIN_LINEAR_IDS = 3;
1510
- var AUTOPILOT_MARKER_RE = /(^|\s)\/autopilot(\s|$)|AUTOPILOT mode/;
1511
- var OPT_OUT_RE = /<!--\s*autopilot:\s*(skip|false)\s*-->/i;
1512
- var UMBRELLA_SECTION_RE = /^##\s+(Chunks|Milestones|Workstreams)\b/m;
1513
- var LINEAR_ID_RE = /\b[A-Z]{2,10}-\d+\b/g;
1514
- var MEASUREMENT_GATE_RE = /\b(7-day|production window|post-deploy|post-launch|SLO|success rate reaches|after deploy|bake time)\b/i;
1515
- var STOP_REPORT_RE = /^STOP[:.\s—]/m;
1516
- var NUDGE_TEXT = "[autopilot] Session idled with unchecked acceptance criteria. Re-read the plan, do the most important unchecked item, check its box when done, then move to the next. When all boxes are `[x]`, print the Phase 5 handoff and stop \u2014 the user runs `/ship` manually.";
1517
- var MAX_ITERATIONS_TEXT = `[autopilot] Stopped: hit max iterations (${MAX_ITERATIONS}). Either the work is complete or the loop is stuck. Review and resume manually; a new \`/autopilot\` session will re-enable nudges.`;
1518
- async function readState(dir) {
1519
- try {
1520
- const raw = await fs3.readFile(path3.join(dir, STATE_PATH), "utf8");
1521
- const parsed = JSON.parse(raw);
1522
- return { sessions: parsed.sessions ?? {} };
1523
- } catch {
1524
- return { sessions: {} };
1525
- }
1526
- }
1527
- async function writeState(dir, state) {
1528
- const p = path3.join(dir, STATE_PATH);
1529
- await fs3.mkdir(path3.dirname(p), { recursive: true });
1530
- await fs3.writeFile(p, JSON.stringify(state, null, 2) + "\n", "utf8");
1531
- }
1532
- function userText(msg) {
1533
- if (msg.info?.role !== "user") return "";
1534
- const parts = msg.parts ?? [];
1535
- return parts.filter((p) => p.type === "text" && typeof p.text === "string").map((p) => p.text).join("\n");
1536
- }
1537
- function latestUserAgent(messages) {
1538
- for (let i = messages.length - 1; i >= 0; i--) {
1539
- const info = messages[i].info;
1540
- if (info?.role === "user" && typeof info.agent === "string") {
1541
- return info.agent;
1542
- }
1543
- }
1544
- return void 0;
1545
- }
1546
- function latestAssistantText(messages) {
1547
- for (let i = messages.length - 1; i >= 0; i--) {
1548
- const msg = messages[i];
1549
- if (msg.info?.role !== "assistant") continue;
1550
- const parts = msg.parts ?? [];
1551
- return parts.filter((p) => p.type === "text" && typeof p.text === "string").map((p) => p.text).join("\n");
1552
- }
1553
- return "";
1554
- }
1555
- function detectActivation(messages) {
1556
- for (const msg of messages) {
1557
- if (msg.info?.role !== "user") continue;
1558
- return AUTOPILOT_MARKER_RE.test(userText(msg));
1559
- }
1560
- return false;
1561
- }
1562
- var PLAN_PATH_RE = /(?:\.agent\/plans\/[\w-]+\.md|(?:\/[^\s`"']*)?\/[\w.-]+\/plans\/[\w-]+\.md)/;
1563
- function findPlanPath(messages) {
1564
- for (let i = messages.length - 1; i >= 0; i--) {
1565
- const parts = messages[i].parts ?? [];
1566
- for (const part of parts) {
1567
- if (part.type === "text" && typeof part.text === "string") {
1568
- const m = part.text.match(PLAN_PATH_RE);
1569
- if (m) return m[0];
1570
- }
1571
- }
1572
- }
1573
- return null;
1574
- }
1575
- function countUnchecked(planContent) {
1576
- const section = /## Acceptance criteria([\s\S]*?)(?=\n##|$)/.exec(planContent);
1577
- if (!section) return 0;
1578
- const matches = section[1].match(/^- \[ \]/gm);
1579
- return matches?.length ?? 0;
1580
- }
1581
- function classifyPlan(content) {
1582
- if (OPT_OUT_RE.test(content)) return "opted-out";
1583
- if (UMBRELLA_SECTION_RE.test(content)) return "umbrella";
1584
- if (content.length > UMBRELLA_MIN_BYTES) return "umbrella";
1585
- const linearIds = content.match(LINEAR_ID_RE) ?? [];
1586
- const unique = new Set(linearIds);
1587
- if (unique.size >= UMBRELLA_MIN_LINEAR_IDS) return "umbrella";
1588
- const acSection = /## Acceptance criteria([\s\S]*?)(?=\n##|$)/.exec(content);
1589
- if (acSection && MEASUREMENT_GATE_RE.test(acSection[1])) {
1590
- return "measurement-gated";
1591
- }
1592
- return "unit";
1593
- }
1594
- function planGoalLinearId(content) {
1595
- const goal = /## Goal([\s\S]*?)(?=\n##|$)/.exec(content);
1596
- if (!goal) return null;
1597
- const m = goal[1].match(LINEAR_ID_RE);
1598
- return m ? m[0] : null;
1599
- }
1600
- function detectStopReport(assistantText) {
1601
- if (!assistantText) return false;
1602
- return STOP_REPORT_RE.test(assistantText);
1603
- }
1604
- var execFile6 = promisify6(execFileCb);
1605
- async function currentBranch(dir) {
1606
- try {
1607
- const { stdout } = await execFile6(
1608
- "git",
1609
- ["-C", dir, "branch", "--show-current"],
1610
- { timeout: 2e3 }
1611
- );
1612
- const branch = stdout.trim();
1613
- return branch.length > 0 ? branch : null;
1614
- } catch {
1615
- return null;
1616
- }
1617
- }
1618
- async function pullRequestState(dir) {
1619
- try {
1620
- const { stdout } = await execFile6(
1621
- "gh",
1622
- ["pr", "view", "--json", "state", "--jq", ".state"],
1623
- { cwd: dir, timeout: 5e3 }
1624
- );
1625
- const state = stdout.trim();
1626
- return state.length > 0 ? state : null;
1627
- } catch {
1628
- return null;
1629
- }
1630
- }
1631
- async function killSwitchEngaged(dir) {
1632
- try {
1633
- await fs3.access(path3.join(dir, KILL_SWITCH_PATH));
1634
- return true;
1635
- } catch {
1636
- return false;
1637
- }
1638
- }
1639
- async function sendNudge(client, sessionID, sessState, text, now = Date.now()) {
1640
- if (sessState.lastNudgeAt !== void 0 && now - sessState.lastNudgeAt < NUDGE_DEBOUNCE_MS) {
1641
- return false;
1642
- }
1643
- await client.session.promptAsync({
1644
- path: { id: sessionID },
1645
- body: { parts: [{ type: "text", text }] }
1646
- });
1647
- sessState.lastNudgeAt = now;
1648
- return true;
1649
- }
1650
- var plugin = async ({ client, directory }) => {
1651
- return {
1652
- event: async ({ event }) => {
1653
- if (event.type !== "session.idle") return;
1654
- const sessionID = event.properties.sessionID;
1655
- const msgsResp = await client.session.messages({
1656
- path: { id: sessionID },
1657
- query: { limit: MESSAGE_LIMIT }
1658
- });
1659
- const messages = msgsResp.data ?? [];
1660
- const agent = latestUserAgent(messages);
1661
- if (!agent || !TARGET_AGENTS.has(agent)) return;
1662
- const state = await readState(directory);
1663
- const sessState = state.sessions[sessionID] ?? {
1664
- iterations: 0
1665
- };
1666
- if (sessState.stopped) return;
1667
- if (!sessState.enabled) {
1668
- if (!detectActivation(messages)) return;
1669
- sessState.enabled = true;
1670
- }
1671
- if (await killSwitchEngaged(directory)) {
1672
- state.sessions[sessionID] = {
1673
- ...sessState,
1674
- stopped: true,
1675
- stopReason: "kill-switch"
1676
- };
1677
- await writeState(directory, state);
1678
- return;
1679
- }
1680
- if (sessState.iterations >= MAX_ITERATIONS) {
1681
- await sendNudge(client, sessionID, sessState, MAX_ITERATIONS_TEXT);
1682
- state.sessions[sessionID] = {
1683
- ...sessState,
1684
- stopped: true,
1685
- stopReason: "max-iterations"
1686
- };
1687
- await writeState(directory, state);
1688
- return;
1689
- }
1690
- const planPath = findPlanPath(messages);
1691
- if (!planPath) return;
1692
- const resolvedPlanPath = path3.isAbsolute(planPath) ? planPath : path3.join(directory, planPath);
1693
- let planContent;
1694
- try {
1695
- planContent = await fs3.readFile(resolvedPlanPath, "utf8");
1696
- } catch {
1697
- return;
1698
- }
1699
- const shape = classifyPlan(planContent);
1700
- if (shape !== "unit") {
1701
- state.sessions[sessionID] = {
1702
- ...sessState,
1703
- stopped: true,
1704
- stopReason: `plan-shape:${shape}`
1705
- };
1706
- await writeState(directory, state);
1707
- return;
1708
- }
1709
- const planLinearId = planGoalLinearId(planContent);
1710
- if (planLinearId) {
1711
- const branch = await currentBranch(directory);
1712
- if (branch && !branch.toLowerCase().includes(planLinearId.toLowerCase())) {
1713
- state.sessions[sessionID] = {
1714
- ...sessState,
1715
- stopped: true,
1716
- stopReason: "branch-mismatch"
1717
- };
1718
- await writeState(directory, state);
1719
- return;
1720
- }
1721
- }
1722
- const now = Date.now();
1723
- let prState = sessState.prState;
1724
- const prExpired = sessState.prCheckedAt === void 0 || now - sessState.prCheckedAt > PR_CACHE_MS;
1725
- if (prExpired) {
1726
- const fetched = await pullRequestState(directory);
1727
- prState = fetched ?? "none";
1728
- sessState.prState = prState;
1729
- sessState.prCheckedAt = now;
1730
- }
1731
- if (prState === "MERGED") {
1732
- state.sessions[sessionID] = {
1733
- ...sessState,
1734
- stopped: true,
1735
- stopReason: "pr-merged"
1736
- };
1737
- await writeState(directory, state);
1738
- return;
1739
- }
1740
- const unchecked = countUnchecked(planContent);
1741
- if (unchecked === 0) {
1742
- state.sessions[sessionID] = {
1743
- ...sessState,
1744
- consecutiveStops: 0,
1745
- lastUncheckedCount: 0
1746
- };
1747
- await writeState(directory, state);
1748
- return;
1749
- }
1750
- const lastUnchecked = sessState.lastUncheckedCount;
1751
- const madeProgress = lastUnchecked !== void 0 && unchecked < lastUnchecked;
1752
- const stopReported = detectStopReport(latestAssistantText(messages));
1753
- let consecutiveStops = sessState.consecutiveStops ?? 0;
1754
- if (madeProgress) {
1755
- consecutiveStops = 0;
1756
- } else if (stopReported) {
1757
- consecutiveStops += 1;
1758
- } else {
1759
- }
1760
- sessState.consecutiveStops = consecutiveStops;
1761
- sessState.lastUncheckedCount = unchecked;
1762
- if (consecutiveStops >= MAX_CONSECUTIVE_STOPS) {
1763
- state.sessions[sessionID] = {
1764
- ...sessState,
1765
- stopped: true,
1766
- stopReason: "agent-stop-report"
1767
- };
1768
- await writeState(directory, state);
1769
- return;
1770
- }
1771
- const sent = await sendNudge(client, sessionID, sessState, NUDGE_TEXT);
1772
- if (sent) {
1773
- state.sessions[sessionID] = {
1774
- ...sessState,
1775
- iterations: sessState.iterations + 1
1776
- };
1777
- await writeState(directory, state);
1778
- } else {
1779
- state.sessions[sessionID] = { ...sessState };
1780
- await writeState(directory, state);
1781
- }
1782
- },
1783
- "chat.message": async ({ sessionID, agent }) => {
1784
- if (!agent || !TARGET_AGENTS.has(agent)) return;
1785
- const state = await readState(directory);
1786
- const existing = state.sessions[sessionID];
1787
- if (!existing?.enabled) return;
1788
- state.sessions[sessionID] = {
1789
- ...existing,
1790
- iterations: 0
1791
- };
1792
- await writeState(directory, state);
1793
- }
1794
- };
1795
- };
1796
- var autopilot_default = plugin;
1797
-
1798
1428
  // src/plugins/notify.ts
1799
- var plugin2 = async ({ $, client }) => {
1429
+ var plugin = async ({ $, client }) => {
1800
1430
  async function notify(title, message) {
1801
1431
  if (process.platform === "darwin") {
1802
1432
  const esc = (s) => s.replace(/"/g, '\\"');
@@ -1819,11 +1449,11 @@ var plugin2 = async ({ $, client }) => {
1819
1449
  }
1820
1450
  };
1821
1451
  };
1822
- var notify_default = plugin2;
1452
+ var notify_default = plugin;
1823
1453
 
1824
1454
  // src/plugins/cost-tracker.ts
1825
- import * as fs4 from "fs/promises";
1826
- import * as path4 from "path";
1455
+ import * as fs3 from "fs/promises";
1456
+ import * as path3 from "path";
1827
1457
  import * as os2 from "os";
1828
1458
  var MAX_LINE_BYTES = 2048;
1829
1459
  var ROLLUP_DEBOUNCE_MS = 5e3;
@@ -1831,11 +1461,11 @@ function resolveDataDir() {
1831
1461
  const override = process.env.GLORIOUS_COST_TRACKER_DIR;
1832
1462
  if (override) {
1833
1463
  if (override.startsWith("~")) {
1834
- return path4.join(os2.homedir(), override.slice(1));
1464
+ return path3.join(os2.homedir(), override.slice(1));
1835
1465
  }
1836
1466
  return override;
1837
1467
  }
1838
- return path4.join(os2.homedir(), ".glorious", "opencode");
1468
+ return path3.join(os2.homedir(), ".glorious", "opencode");
1839
1469
  }
1840
1470
  function zeroTokens() {
1841
1471
  return {
@@ -1902,13 +1532,13 @@ function emptyRollup() {
1902
1532
  byProvider: {}
1903
1533
  };
1904
1534
  }
1905
- var plugin3 = async () => {
1535
+ var plugin2 = async () => {
1906
1536
  if (process.env.GLORIOUS_COST_TRACKER === "0") {
1907
1537
  return {};
1908
1538
  }
1909
1539
  const dataDir = resolveDataDir();
1910
- const jsonlPath = path4.join(dataDir, "costs.jsonl");
1911
- const rollupPath = path4.join(dataDir, "costs.json");
1540
+ const jsonlPath = path3.join(dataDir, "costs.jsonl");
1541
+ const rollupPath = path3.join(dataDir, "costs.json");
1912
1542
  const lastSeen = /* @__PURE__ */ new Map();
1913
1543
  const messageMeta = /* @__PURE__ */ new Map();
1914
1544
  const rollup = emptyRollup();
@@ -1924,7 +1554,7 @@ var plugin3 = async () => {
1924
1554
  async function ensureDir() {
1925
1555
  if (disabled) return false;
1926
1556
  try {
1927
- await fs4.mkdir(dataDir, { recursive: true });
1557
+ await fs3.mkdir(dataDir, { recursive: true });
1928
1558
  return true;
1929
1559
  } catch (err) {
1930
1560
  warnOnce("mkdir", err);
@@ -1973,13 +1603,13 @@ var plugin3 = async () => {
1973
1603
  if (!await ensureDir()) return;
1974
1604
  const tmp = `${rollupPath}.tmp.${process.pid}.${Math.random().toString(36).slice(2, 10)}`;
1975
1605
  try {
1976
- await fs4.writeFile(tmp, JSON.stringify(rollup, null, 2) + "\n", "utf8");
1977
- await fs4.rename(tmp, rollupPath);
1606
+ await fs3.writeFile(tmp, JSON.stringify(rollup, null, 2) + "\n", "utf8");
1607
+ await fs3.rename(tmp, rollupPath);
1978
1608
  lastRollupWriteAt = now;
1979
1609
  } catch (err) {
1980
1610
  warnOnce("rollup-write", err);
1981
1611
  try {
1982
- await fs4.unlink(tmp);
1612
+ await fs3.unlink(tmp);
1983
1613
  } catch {
1984
1614
  }
1985
1615
  }
@@ -1999,14 +1629,14 @@ var plugin3 = async () => {
1999
1629
  return;
2000
1630
  }
2001
1631
  try {
2002
- await fs4.appendFile(jsonlPath, text, "utf8");
1632
+ await fs3.appendFile(jsonlPath, text, "utf8");
2003
1633
  } catch (err) {
2004
1634
  warnOnce("jsonl-append", err);
2005
1635
  }
2006
1636
  }
2007
1637
  async function warmUp() {
2008
1638
  try {
2009
- const raw = await fs4.readFile(jsonlPath, "utf8");
1639
+ const raw = await fs3.readFile(jsonlPath, "utf8");
2010
1640
  const byMsg = /* @__PURE__ */ new Map();
2011
1641
  for (const rawLine of raw.split("\n")) {
2012
1642
  if (!rawLine) continue;
@@ -2140,76 +1770,16 @@ var plugin3 = async () => {
2140
1770
  }
2141
1771
  };
2142
1772
  };
2143
- var cost_tracker_default = plugin3;
2144
-
2145
- // src/plugins/pilot-plugin.ts
2146
- var PILOT_TITLE_PREFIX = "pilot/";
2147
- var BUILDER_DENIED_PATTERNS = [
2148
- /^git\s+commit/,
2149
- /^git\s+push/,
2150
- /^git\s+tag/,
2151
- /^git\s+checkout\s/,
2152
- /^git\s+switch\s/,
2153
- /^git\s+branch\s/,
2154
- /^git\s+restore\s+--source/,
2155
- /^git\s+reset\s/,
2156
- /^gh\s+pr\s/,
2157
- /^gh\s+release\s/
2158
- ];
2159
- var sessionCache = /* @__PURE__ */ new Map();
2160
- async function getSessionPhase(client, sessionId) {
2161
- if (sessionCache.has(sessionId)) {
2162
- return sessionCache.get(sessionId);
2163
- }
2164
- try {
2165
- const session = await client.session.get({ sessionID: sessionId });
2166
- const title = session.title ?? "";
2167
- if (!title.startsWith(PILOT_TITLE_PREFIX)) return null;
2168
- const parts = title.slice(PILOT_TITLE_PREFIX.length).split("/");
2169
- if (parts.length < 2) return null;
2170
- const [workflowId, phase] = parts;
2171
- const result = { phase, workflowId };
2172
- sessionCache.set(sessionId, result);
2173
- return result;
2174
- } catch {
2175
- return null;
2176
- }
2177
- }
2178
- var pilotPlugin = async (input) => {
2179
- return {
2180
- "tool.execute.before": async (toolInput, _output) => {
2181
- const sessionId = toolInput.sessionID;
2182
- if (!sessionId) return;
2183
- const sessionInfo = await getSessionPhase(input.client, sessionId);
2184
- if (!sessionInfo) return;
2185
- const { phase } = sessionInfo;
2186
- const toolName = toolInput.tool ?? "";
2187
- const args = toolInput.args ?? {};
2188
- if (phase === "execute") {
2189
- if (toolName === "bash") {
2190
- const cmd = String(args["command"] ?? "").trim();
2191
- for (const pattern of BUILDER_DENIED_PATTERNS) {
2192
- if (pattern.test(cmd)) {
2193
- throw new Error(
2194
- `pilot-builder: "${cmd}" is not allowed. The orchestrator handles commits and pushes after verify passes.`
2195
- );
2196
- }
2197
- }
2198
- }
2199
- }
2200
- }
2201
- };
2202
- };
2203
- var pilot_plugin_default = pilotPlugin;
1773
+ var cost_tracker_default = plugin2;
2204
1774
 
2205
1775
  // src/plugins/tool-hooks.ts
2206
1776
  import * as crypto from "crypto";
2207
- import * as fs5 from "fs";
2208
- import * as path5 from "path";
1777
+ import * as fs4 from "fs";
1778
+ import * as path4 from "path";
2209
1779
  import * as os3 from "os";
2210
- import { execFile as execFileCb2 } from "child_process";
2211
- import { promisify as promisify7 } from "util";
2212
- var exec6 = promisify7(execFileCb2);
1780
+ import { execFile as execFileCb } from "child_process";
1781
+ import { promisify as promisify6 } from "util";
1782
+ var exec6 = promisify6(execFileCb);
2213
1783
  var EDIT_TOOLS = /* @__PURE__ */ new Set(["edit", "write", "patch", "multiedit"]);
2214
1784
  var TS_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
2215
1785
  var DEFAULT_BACKPRESSURE_THRESHOLD = 6e3;
@@ -2292,8 +1862,8 @@ function resolveConfig(config, pluginOptions) {
2292
1862
  };
2293
1863
  }
2294
1864
  function getToolOutputDir() {
2295
- const stateHome = process.env["XDG_STATE_HOME"] || path5.join(os3.homedir(), ".local", "state");
2296
- return path5.join(stateHome, "harness-opencode", "tool-output");
1865
+ const stateHome = process.env["XDG_STATE_HOME"] || path4.join(os3.homedir(), ".local", "state");
1866
+ return path4.join(stateHome, "harness-opencode", "tool-output");
2297
1867
  }
2298
1868
  function hashContent(content) {
2299
1869
  return crypto.createHash("sha256").update(content).digest("hex").slice(0, 16);
@@ -2326,9 +1896,9 @@ async function resolveSessionDir(client, sess, sessionID) {
2326
1896
  }
2327
1897
  function isUnderToolOutputDir(filePath) {
2328
1898
  try {
2329
- const abs = path5.resolve(filePath);
2330
- const spillDir = path5.resolve(getToolOutputDir());
2331
- return abs === spillDir || abs.startsWith(spillDir + path5.sep);
1899
+ const abs = path4.resolve(filePath);
1900
+ const spillDir = path4.resolve(getToolOutputDir());
1901
+ return abs === spillDir || abs.startsWith(spillDir + path4.sep);
2332
1902
  } catch {
2333
1903
  return false;
2334
1904
  }
@@ -2362,9 +1932,9 @@ function applyBackpressure(cfg, toolName, callID, output, args) {
2362
1932
  let diskPath = null;
2363
1933
  try {
2364
1934
  const dir = getToolOutputDir();
2365
- fs5.mkdirSync(dir, { recursive: true });
2366
- diskPath = path5.join(dir, `${callID}.txt`);
2367
- fs5.writeFileSync(diskPath, text);
1935
+ fs4.mkdirSync(dir, { recursive: true });
1936
+ diskPath = path4.join(dir, `${callID}.txt`);
1937
+ fs4.writeFileSync(diskPath, text);
2368
1938
  } catch {
2369
1939
  }
2370
1940
  const pathNote = diskPath ? ` Full output saved to: ${diskPath}` : "";
@@ -2410,7 +1980,7 @@ ${tail}`;
2410
1980
  }
2411
1981
  async function runPostEditVerify(cfg, client, sess, sessionID, filePath, output) {
2412
1982
  if (!cfg.enabled) return;
2413
- const ext = path5.extname(filePath).toLowerCase();
1983
+ const ext = path4.extname(filePath).toLowerCase();
2414
1984
  if (!TS_EXTENSIONS.has(ext)) return;
2415
1985
  const now = Date.now();
2416
1986
  if (now - sess.lastVerifyTs < 2e3) return;
@@ -2443,17 +2013,17 @@ ${String(stderr)}`;
2443
2013
  }
2444
2014
  if (!raw.trim()) return;
2445
2015
  const errors = parseTscOutput(raw);
2446
- const normPath = path5.resolve(cwd, filePath);
2016
+ const normPath = path4.resolve(cwd, filePath);
2447
2017
  const fileErrors = errors.filter((e) => {
2448
- const errPath = path5.isAbsolute(e.file) ? e.file : path5.resolve(cwd, e.file);
2449
- return path5.normalize(errPath) === path5.normalize(normPath);
2018
+ const errPath = path4.isAbsolute(e.file) ? e.file : path4.resolve(cwd, e.file);
2019
+ return path4.normalize(errPath) === path4.normalize(normPath);
2450
2020
  });
2451
2021
  if (fileErrors.length === 0) return;
2452
2022
  const { rows } = dedupeAndCap(fileErrors, VERIFY_MAX_ERRORS);
2453
2023
  const lines = rows.map(formatRow);
2454
2024
  output.output += `
2455
2025
 
2456
- --- POST-EDIT DIAGNOSTICS (${fileErrors.length} error${fileErrors.length !== 1 ? "s" : ""} in ${path5.basename(filePath)}) ---
2026
+ --- POST-EDIT DIAGNOSTICS (${fileErrors.length} error${fileErrors.length !== 1 ? "s" : ""} in ${path4.basename(filePath)}) ---
2457
2027
  ` + lines.join("\n") + `
2458
2028
  --- Fix these before proceeding ---`;
2459
2029
  } catch {
@@ -2467,7 +2037,7 @@ function checkEditLoop(cfg, sess, filePath, output) {
2467
2037
  output.output += `
2468
2038
 
2469
2039
  --- LOOP WARNING ---
2470
- You've edited ${path5.basename(filePath)} ${count} times this session. Consider reconsidering your approach \u2014 are you stuck in a loop? Step back and think about whether a different strategy would be more effective.
2040
+ You've edited ${path4.basename(filePath)} ${count} times this session. Consider reconsidering your approach \u2014 are you stuck in a loop? Step back and think about whether a different strategy would be more effective.
2471
2041
  ---`;
2472
2042
  }
2473
2043
  }
@@ -2485,7 +2055,7 @@ function checkReadDedup(cfg, sess, filePath, output) {
2485
2055
  }
2486
2056
  var pluginConfig = null;
2487
2057
  var storedPluginOptions;
2488
- var plugin4 = async ({ client }, options) => {
2058
+ var plugin3 = async ({ client }, options) => {
2489
2059
  storedPluginOptions = options;
2490
2060
  return {
2491
2061
  config: async (config) => {
@@ -2518,7 +2088,7 @@ var plugin4 = async ({ client }, options) => {
2518
2088
  }
2519
2089
  };
2520
2090
  };
2521
- var tool_hooks_default = plugin4;
2091
+ var tool_hooks_default = plugin3;
2522
2092
 
2523
2093
  // src/plugins/telemetry.ts
2524
2094
  import { extname as extname2 } from "path";
@@ -2527,16 +2097,16 @@ import { extname as extname2 } from "path";
2527
2097
  import { createHash as createHash2, randomUUID } from "crypto";
2528
2098
  import { homedir as homedir4 } from "os";
2529
2099
  import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync } from "fs";
2530
- import { join as join10 } from "path";
2100
+ import { join as join9 } from "path";
2531
2101
  var APP_KEY = "A-US-3617699429";
2532
2102
  var ENDPOINT = "https://us.aptabase.com/api/v0/event";
2533
2103
  var PKG_NAME = "@glrs-dev/harness-plugin-opencode";
2534
- var PKG_VERSION = true ? "2.1.0" : "dev";
2104
+ var PKG_VERSION = true ? "2.3.0" : "dev";
2535
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";
2536
2106
  var SESSION_ID = randomUUID();
2537
2107
  function getInstallId() {
2538
- const dir = join10(homedir4(), ".config", "harness-opencode");
2539
- const file = join10(dir, "install-id");
2108
+ const dir = join9(homedir4(), ".config", "harness-opencode");
2109
+ const file = join9(dir, "install-id");
2540
2110
  try {
2541
2111
  if (existsSync(file)) return readFileSync5(file, "utf8").trim();
2542
2112
  mkdirSync3(dir, { recursive: true });
@@ -2603,7 +2173,7 @@ function track(eventName, props = {}) {
2603
2173
  }
2604
2174
 
2605
2175
  // src/plugins/telemetry.ts
2606
- var plugin5 = async () => {
2176
+ var plugin4 = async () => {
2607
2177
  if (DISABLED) {
2608
2178
  return {};
2609
2179
  }
@@ -2665,7 +2235,7 @@ var plugin5 = async () => {
2665
2235
  }
2666
2236
  };
2667
2237
  };
2668
- var telemetry_default = plugin5;
2238
+ var telemetry_default = plugin4;
2669
2239
 
2670
2240
  // src/index.ts
2671
2241
  var BUNDLED_VERSION = readOurPackageVersion(import.meta.url);
@@ -2710,22 +2280,19 @@ async function checkForUpdate(client) {
2710
2280
  } catch {
2711
2281
  }
2712
2282
  }
2713
- var plugin6 = async (input, options) => {
2283
+ var plugin5 = async (input, options) => {
2714
2284
  const pluginOptions = options ?? {};
2715
2285
  loadDotenv(input.directory);
2716
2286
  checkForUpdate(input.client).catch(() => {
2717
2287
  });
2718
- const autopilotHooks = await autopilot_default(input);
2719
2288
  const notifyHooks = await notify_default(input);
2720
2289
  const costTrackerHooks = await cost_tracker_default(input);
2721
- const pilotHooks = await pilot_plugin_default(input);
2722
2290
  const toolHooks = await tool_hooks_default(input, pluginOptions);
2723
2291
  const telemetryHooks = await telemetry_default(input);
2724
2292
  const hooks = {
2725
2293
  // Config hook: register agents, commands, MCPs, skills
2726
2294
  config: async (config) => {
2727
2295
  applyConfig(config, pluginOptions);
2728
- if (autopilotHooks.config) await autopilotHooks.config(config);
2729
2296
  if (notifyHooks.config) await notifyHooks.config(config);
2730
2297
  if (costTrackerHooks.config) await costTrackerHooks.config(config);
2731
2298
  if (toolHooks.config) await toolHooks.config(config);
@@ -2734,27 +2301,15 @@ var plugin6 = async (input, options) => {
2734
2301
  tool: createTools(),
2735
2302
  // Event handlers from sub-plugins
2736
2303
  event: async (input2) => {
2737
- if (autopilotHooks.event) await autopilotHooks.event(input2);
2738
2304
  if (notifyHooks.event) await notifyHooks.event(input2);
2739
2305
  if (costTrackerHooks.event) await costTrackerHooks.event(input2);
2740
2306
  if (telemetryHooks.event) await telemetryHooks.event(input2);
2741
2307
  }
2742
2308
  };
2743
- if (autopilotHooks["chat.params"] !== void 0) {
2744
- hooks["chat.params"] = autopilotHooks["chat.params"];
2745
- }
2746
- if (autopilotHooks["chat.message"] !== void 0) {
2747
- hooks["chat.message"] = autopilotHooks["chat.message"];
2748
- }
2749
- if (autopilotHooks["experimental.session.compacting"] !== void 0) {
2750
- hooks["experimental.session.compacting"] = autopilotHooks["experimental.session.compacting"];
2751
- }
2752
2309
  const hasTelemetryBefore = telemetryHooks["tool.execute.before"] !== void 0;
2753
- const hasPilotBefore = pilotHooks["tool.execute.before"] !== void 0;
2754
- if (hasTelemetryBefore || hasPilotBefore) {
2310
+ if (hasTelemetryBefore) {
2755
2311
  hooks["tool.execute.before"] = async (input2, output) => {
2756
2312
  if (hasTelemetryBefore) await telemetryHooks["tool.execute.before"](input2, output);
2757
- if (hasPilotBefore) await pilotHooks["tool.execute.before"](input2, output);
2758
2313
  };
2759
2314
  }
2760
2315
  const hasToolHooksAfter = toolHooks["tool.execute.after"] !== void 0;
@@ -2767,7 +2322,7 @@ var plugin6 = async (input, options) => {
2767
2322
  }
2768
2323
  return hooks;
2769
2324
  };
2770
- var src_default = plugin6;
2325
+ var src_default = plugin5;
2771
2326
  export {
2772
2327
  src_default as default
2773
2328
  };