@glrs-dev/cli 2.2.0 → 2.4.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 (65) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/{chunk-EM4MJBOD.js → chunk-2AZKRWC6.js} +4 -4
  3. package/dist/{chunk-UXBOTMDY.js → chunk-2P3ETOT2.js} +2 -2
  4. package/dist/chunk-2VMFXAJH.js +795 -0
  5. package/dist/chunk-5ZVUFNCP.js +140 -0
  6. package/dist/{chunk-W37UX3U2.js → chunk-6Y27RQQL.js} +2 -2
  7. package/dist/{chunk-RZWOWTKF.js → chunk-EKNRKZWR.js} +4 -4
  8. package/dist/{chunk-YGNDPKIW.js → chunk-HQUCVJ4G.js} +3 -1
  9. package/dist/{chunk-OABVEBWW.js → chunk-MBEVC327.js} +1 -1
  10. package/dist/{chunk-SB3MLROC.js → chunk-MCM47HH4.js} +8 -3
  11. package/dist/{chunk-F3AFRUT2.js → chunk-PTIO556V.js} +2 -2
  12. package/dist/{chunk-E2UNZIZT.js → chunk-R2WXQ54P.js} +1 -1
  13. package/dist/{chunk-I2KUXY3I.js → chunk-SMDIOB5B.js} +2 -2
  14. package/dist/{chunk-SPULDN7P.js → chunk-YY7EWHMA.js} +5 -3
  15. package/dist/cli.js +31 -20
  16. package/dist/commands/autopilot-interactive.d.ts +89 -0
  17. package/dist/commands/autopilot-interactive.js +248 -0
  18. package/dist/commands/autopilot-raw.d.ts +1 -0
  19. package/dist/commands/autopilot-raw.js +368 -0
  20. package/dist/commands/autopilot-tui.d.ts +7 -0
  21. package/dist/commands/autopilot-tui.js +7 -0
  22. package/dist/commands/autopilot.d.ts +39 -0
  23. package/dist/commands/autopilot.js +395 -0
  24. package/dist/commands/cleanup.js +3 -3
  25. package/dist/commands/create.js +4 -4
  26. package/dist/commands/dashboard.d.ts +3 -0
  27. package/dist/commands/dashboard.js +1549 -0
  28. package/dist/commands/debrief.d.ts +57 -0
  29. package/dist/commands/debrief.js +9 -0
  30. package/dist/commands/delete.js +3 -3
  31. package/dist/commands/go.js +2 -2
  32. package/dist/commands/list.js +3 -3
  33. package/dist/commands/loop.d.ts +42 -0
  34. package/dist/commands/loop.js +133 -0
  35. package/dist/commands/plan-picker.d.ts +15 -0
  36. package/dist/commands/plan-picker.js +76 -0
  37. package/dist/commands/scoper.d.ts +54 -0
  38. package/dist/commands/scoper.js +341 -0
  39. package/dist/commands/switch.js +3 -3
  40. package/dist/index.d.ts +2 -2
  41. package/dist/index.js +1 -1
  42. package/dist/lib/auto-update.js +1 -1
  43. package/dist/lib/config.d.ts +3 -2
  44. package/dist/lib/config.js +1 -1
  45. package/dist/lib/registry.d.ts +2 -0
  46. package/dist/lib/registry.js +1 -1
  47. package/dist/lib/worktree.js +3 -3
  48. package/dist/vendor/harness-opencode/dist/agents/prompts/build.md +16 -0
  49. package/dist/vendor/harness-opencode/dist/agents/prompts/code-reviewer-thorough.md +6 -7
  50. package/dist/vendor/harness-opencode/dist/agents/prompts/debriefer.md +55 -0
  51. package/dist/vendor/harness-opencode/dist/agents/prompts/plan-reviewer.md +2 -1
  52. package/dist/vendor/harness-opencode/dist/agents/prompts/plan.md +104 -7
  53. package/dist/vendor/harness-opencode/dist/agents/prompts/prime.md +4 -2
  54. package/dist/vendor/harness-opencode/dist/agents/prompts/scoper.md +129 -0
  55. package/dist/vendor/harness-opencode/dist/agents/prompts/spec-reviewer.md +0 -1
  56. package/dist/vendor/harness-opencode/dist/agents/prompts/spec-reviewer.open.md +0 -1
  57. package/dist/vendor/harness-opencode/dist/chunk-GILWWWMB.js +66 -0
  58. package/dist/vendor/harness-opencode/dist/cli.js +328 -687
  59. package/dist/vendor/harness-opencode/dist/index.js +123 -20
  60. package/dist/vendor/harness-opencode/dist/plugin-check-GJRD2OK6.js +14 -0
  61. package/dist/vendor/harness-opencode/dist/skills/spear-protocol/SKILL.md +2 -1
  62. package/dist/vendor/harness-opencode/package.json +1 -1
  63. package/package.json +10 -2
  64. package/dist/vendor/harness-opencode/dist/autopilot/prompt-template.md +0 -80
  65. package/dist/vendor/harness-opencode/dist/bin/plan-check.sh +0 -255
@@ -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,40 @@ 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",
550
+ "autopilot-fast": "autopilot-execute",
505
551
  plan: "deep",
506
552
  "architecture-advisor": "deep",
507
553
  "plan-reviewer": "deep",
@@ -517,6 +563,7 @@ var AGENT_TIERS = {
517
563
  "docs-maintainer": "mid",
518
564
  "lib-reader": "mid",
519
565
  "agents-md-writer": "mid",
566
+ debriefer: "mid",
520
567
  "code-searcher": "fast"
521
568
  };
522
569
  function createAgents() {
@@ -529,11 +576,45 @@ function createAgents() {
529
576
  temperature: 0.2,
530
577
  permission: PRIME_PERMISSIONS
531
578
  }),
579
+ scoper: agentFromPrompt(scoperPrompt, {
580
+ 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.",
581
+ mode: "primary",
582
+ model: "anthropic/claude-opus-4-7",
583
+ temperature: 0.3,
584
+ permission: SCOPER_PERMISSIONS,
585
+ tools: SCOPER_DISABLED_TOOLS
586
+ }),
587
+ "autopilot-prime": agentFromPrompt(primePrompt, {
588
+ 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.",
589
+ mode: "subagent",
590
+ model: "anthropic/claude-opus-4-7",
591
+ temperature: 0.2,
592
+ permission: {
593
+ ...PRIME_PERMISSIONS,
594
+ question: "deny"
595
+ },
596
+ tools: AUTOPILOT_PRIME_DISABLED_TOOLS
597
+ }),
598
+ "autopilot-fast": agentFromPrompt(primePrompt, {
599
+ description: "Fast executor for autopilot sessions. Same prompt as autopilot-prime but runs on the mid-execute tier (Kimi 2.5/2.6, GLM-5). Used when --fast is passed. Plans must be enriched with mirror refs and code pointers before using this agent.",
600
+ mode: "subagent",
601
+ model: "anthropic/claude-sonnet-4-6",
602
+ temperature: 0.1,
603
+ permission: {
604
+ ...PRIME_PERMISSIONS,
605
+ question: "deny"
606
+ },
607
+ tools: AUTOPILOT_PRIME_DISABLED_TOOLS
608
+ }),
532
609
  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`).",
610
+ 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
611
  mode: "all",
535
612
  model: "anthropic/claude-opus-4-7",
536
613
  temperature: 0.3,
614
+ // @plan dispatches @gap-analyzer, @code-searcher, and @plan-reviewer
615
+ // as subagents. OpenCode strips the `task` tool from subagent contexts
616
+ // by default; explicit opt-in re-enables it.
617
+ tools: { task: true },
537
618
  permission: PLAN_PERMISSIONS
538
619
  }),
539
620
  build: agentFromPrompt(buildPrompt, {
@@ -580,6 +661,8 @@ function createAgents() {
580
661
  mode: "all",
581
662
  model: "anthropic/claude-opus-4-7",
582
663
  temperature: 0.3,
664
+ // @research dispatches @research-web, @research-local, @research-auto.
665
+ tools: { task: true },
583
666
  permission: RESEARCH_PERMISSIONS
584
667
  }),
585
668
  // Research subagents — thin shims that load the bundled skills.
@@ -591,6 +674,8 @@ function createAgents() {
591
674
  mode: "subagent",
592
675
  model: "anthropic/claude-opus-4-7",
593
676
  temperature: 0.3,
677
+ // @research-web dispatches its own parallel workstream agents.
678
+ tools: { task: true },
594
679
  permission: RESEARCH_PERMISSIONS
595
680
  }),
596
681
  "research-local": agentFromPrompt(researchLocalPrompt, {
@@ -598,6 +683,8 @@ function createAgents() {
598
683
  mode: "subagent",
599
684
  model: "anthropic/claude-opus-4-7",
600
685
  temperature: 0.3,
686
+ // @research-local dispatches parallel Explore subagents.
687
+ tools: { task: true },
601
688
  permission: RESEARCH_PERMISSIONS
602
689
  }),
603
690
  "research-auto": agentFromPrompt(researchAutoPrompt, {
@@ -606,6 +693,10 @@ function createAgents() {
606
693
  model: "anthropic/claude-opus-4-7",
607
694
  temperature: 0.3,
608
695
  permission: RESEARCH_PERMISSIONS
696
+ }),
697
+ // Debriefer — post-run summary agent for the autopilot CLI
698
+ debriefer: agentFromPrompt(debrieferPrompt, {
699
+ permission: DEBRIEFER_PERMISSIONS
609
700
  })
610
701
  };
611
702
  }
@@ -773,6 +864,9 @@ function resolveHarnessModels(agents, config, pluginOptions) {
773
864
  const tier = AGENT_TIERS[agentName];
774
865
  if (tier) {
775
866
  let perTier = modelsConfig[tier];
867
+ if (tier === "autopilot-execute" && perTier === void 0) {
868
+ perTier = modelsConfig["mid-execute"] ?? modelsConfig["mid"];
869
+ }
776
870
  if (tier === "mid-execute" && perTier === void 0) {
777
871
  perTier = modelsConfig["mid"];
778
872
  }
@@ -1366,8 +1460,14 @@ var plugin = async ({ $, client }) => {
1366
1460
  }
1367
1461
  }
1368
1462
  return {
1369
- // Notify when a permission prompt fires (replaces the old permission.asked event)
1463
+ // Notify when a permission prompt fires (replaces the old permission.asked event).
1464
+ // In headless autopilot mode, auto-deny question permissions at the plugin level
1465
+ // so they never reach the event stream — prevents wasted loop iterations.
1370
1466
  "permission.ask": async (input, output) => {
1467
+ if (process.env["GLRS_AUTOPILOT_HEADLESS"] === "1" && (input?.type === "question" || input?.title === "")) {
1468
+ output.status = "deny";
1469
+ return;
1470
+ }
1371
1471
  const tool6 = input?.tool ?? input?.title ?? "a tool";
1372
1472
  await notify("opencode permission required", `Approval needed for ${tool6}.`);
1373
1473
  }
@@ -2025,7 +2125,7 @@ import { join as join9 } from "path";
2025
2125
  var APP_KEY = "A-US-3617699429";
2026
2126
  var ENDPOINT = "https://us.aptabase.com/api/v0/event";
2027
2127
  var PKG_NAME = "@glrs-dev/harness-plugin-opencode";
2028
- var PKG_VERSION = true ? "2.2.0" : "dev";
2128
+ var PKG_VERSION = true ? "2.4.0" : "dev";
2029
2129
  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
2130
  var SESSION_ID = randomUUID();
2031
2131
  function getInstallId() {
@@ -2231,11 +2331,14 @@ var plugin5 = async (input, options) => {
2231
2331
  }
2232
2332
  };
2233
2333
  const hasTelemetryBefore = telemetryHooks["tool.execute.before"] !== void 0;
2234
- if (hasTelemetryBefore) {
2235
- hooks["tool.execute.before"] = async (input2, output) => {
2236
- if (hasTelemetryBefore) await telemetryHooks["tool.execute.before"](input2, output);
2237
- };
2238
- }
2334
+ hooks["tool.execute.before"] = async (input2, output) => {
2335
+ if (process.env["GLRS_AUTOPILOT_HEADLESS"] === "1" && input2.tool === "question") {
2336
+ throw new Error(
2337
+ "The question tool is not available in autopilot mode. Pick a sensible default and continue without asking the user."
2338
+ );
2339
+ }
2340
+ if (hasTelemetryBefore) await telemetryHooks["tool.execute.before"](input2, output);
2341
+ };
2239
2342
  const hasToolHooksAfter = toolHooks["tool.execute.after"] !== void 0;
2240
2343
  const hasTelemetryAfter = telemetryHooks["tool.execute.after"] !== void 0;
2241
2344
  if (hasToolHooksAfter || hasTelemetryAfter) {
@@ -0,0 +1,14 @@
1
+ import {
2
+ getOpencodeConfigPath,
3
+ isPluginInstalled,
4
+ promptChoice,
5
+ promptMulti,
6
+ promptYesNo
7
+ } from "./chunk-GILWWWMB.js";
8
+ export {
9
+ getOpencodeConfigPath,
10
+ isPluginInstalled,
11
+ promptChoice,
12
+ promptMulti,
13
+ promptYesNo
14
+ };
@@ -14,7 +14,8 @@ Before Scope, run this probe inline (no subagent) — sessions typically start i
14
14
  1. `pwd` — confirm working directory.
15
15
  2. `git status --short` — see uncommitted work.
16
16
  3. `git log --oneline -5` — recent history.
17
- 4. `PLAN_DIR="$(bunx @glrs-dev/harness-plugin-opencode plan-dir 2>/dev/null)" && ls "$PLAN_DIR" 2>/dev/null | tail -5` — plans for this repo.
17
+ 4. Resolve the plan dir and list recent plans:
18
+ `PLAN_BASE="${GLORIOUS_PLAN_DIR:-$HOME/.glorious/opencode}" && GIT_COMMON="$(git rev-parse --git-common-dir 2>/dev/null)" && [ -n "$GIT_COMMON" ] && [[ "$GIT_COMMON" != /* ]] && GIT_COMMON="$PWD/$GIT_COMMON"; REPO_FOLDER="$(basename "$(dirname "$GIT_COMMON")" 2>/dev/null)" && [ -n "$REPO_FOLDER" ] && [ "$REPO_FOLDER" != "." ] && ls "$PLAN_BASE/$REPO_FOLDER/plans" 2>/dev/null | tail -5` — plans for this repo.
18
19
 
19
20
  For each plan found, read it and count unchecked acceptance items. Classify as **stale** (ignore) only if `git merge-base --is-ancestor HEAD origin/main` (fallback `origin/master`) exits 0. If classification fails, treat as active.
20
21
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glrs-dev/harness-plugin-opencode",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glrs-dev/cli",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "description": "Unified CLI for the @glrs-dev ecosystem — OpenCode agent harness dispatch + worktree management.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -38,15 +38,22 @@
38
38
  "scripts": {
39
39
  "build": "bun scripts/build.ts",
40
40
  "typecheck": "tsc --noEmit",
41
- "test": "bun test src/",
41
+ "test": "bun test src/ test/",
42
42
  "lint": "echo 'no linter configured yet'"
43
43
  },
44
44
  "dependencies": {
45
+ "@glrs-dev/autopilot": "workspace:*",
46
+ "@glrs-dev/adapter-opencode": "workspace:*",
47
+ "@inkjs/ui": "^2.0.0",
45
48
  "@inquirer/prompts": "^8.4.2",
46
49
  "@opencode-ai/plugin": "^1.14",
47
50
  "@opencode-ai/sdk": "^1.14",
48
51
  "cmd-ts": "^0.15.0",
52
+ "ink": "^5.0.0",
49
53
  "picomatch": "^4.0.4",
54
+ "pino": "^10.3.1",
55
+ "pino-pretty": "^13.1.3",
56
+ "react": "^18.3.1",
50
57
  "ulid": "^3.0.2",
51
58
  "yaml": "^2.8.3",
52
59
  "zod": "4.1.8"
@@ -55,6 +62,7 @@
55
62
  "@glrs-dev/harness-plugin-opencode": "workspace:*",
56
63
  "@types/bun": "latest",
57
64
  "@types/node": "^22",
65
+ "@types/react": "^18.3.0",
58
66
  "tsup": "^8",
59
67
  "typescript": "^5"
60
68
  }
@@ -1,80 +0,0 @@
1
- ---
2
- description: Self-driving PRIME run. Accepts an issue-tracker reference, a free-form task description, or a question.
3
- ---
4
-
5
- You are running in autopilot mode. The user invoked `/autopilot` to hand off a task for lights-out execution. Work through the normal SPEAR workflow until all work described in the prompt is complete, then emit `<autopilot-done>` as the **first token** of your final message.
6
-
7
- **Sentinel contract.** When ALL work described in the user's prompt is genuinely complete (plan executed, Resolve stage done, PR open), emit `<autopilot-done>` at the very start of your final message. The Ralph loop driver watches for this tag to know when to stop. Do NOT emit `<autopilot-done>` prematurely — only when you have truly finished everything the prompt asked for.
8
-
9
- **Single-shot TUI path.** When invoked from the TUI (not the CLI driver), there is no external loop watching for the sentinel. Run the SPEAR workflow once to completion. The sentinel is harmless in this context — emit it anyway so the output is consistent.
10
-
11
- **Kill switch.** If `.agent/autopilot-disable` exists in the worktree, the CLI driver will have already stopped before sending this prompt. No action needed from you.
12
-
13
- The user wants autopilot to process: $ARGUMENTS
14
-
15
- ## 0. Workflow-mechanics: decide before anything else
16
-
17
- Before classifying the argument, apply the workflow-mechanics heuristic from `prime.md` § `# Workflow-mechanics decisions`. Autopilot is lights-out: the rule fires automatically and silently (single line of chat, no `question` tool). Never ask the user whether to open a fresh worktree, switch branches, or stack on current — the heuristic decides.
18
-
19
- Abort paths (dirty tree on default branch; dirty tree on feature branch with unrelated work) mean STOP and report the one-sentence reason. The user resolves and re-runs.
20
-
21
- If you auto-invoke `/fresh`, do NOT pass `--clean`. Cleanup stays user-triggered.
22
-
23
- ## 1. Classify the argument
24
-
25
- Pick ONE of these paths:
26
-
27
- - **Issue-tracker reference** (single issue) — match any of:
28
- - `<PROJECT>-<NUMBER>` where PROJECT is 2–10 uppercase letters (e.g. `ENG-1234`, `GEN-1114`) — Linear, Jira, YouTrack, Shortcut, etc.
29
- - `#<NUMBER>` alone (e.g. `#1234`) — GitHub shorthand
30
- - A URL to a recognized tracker (`github.com/.../issues/123`, `linear.app/.../issue/...`, `*.atlassian.net/browse/...`)
31
- - **Free-form task description** — any natural-language request that isn't a recognized issue ref
32
- - **Question** — starts with what/why/how/when/where/which/who, or ends with `?`
33
-
34
- ## 2. Fetch issue content (only if step 1 returned an issue ref)
35
-
36
- Probe in order, stop at the first that returns real content:
37
-
38
- 1. **Linear MCP** — if configured and the arg matches `<PROJECT>-<NUMBER>` shape OR is a `linear.app` URL: `linear_get_issue`.
39
- 2. **GitHub MCP** — if configured OR the arg is a `github.com/.../issues/...` URL OR is `#<NUMBER>` and `gh` CLI is available.
40
- 3. **Jira / Atlassian MCP** — if configured and the arg matches `<PROJECT>-<NUMBER>` OR is an `*.atlassian.net` URL.
41
-
42
- If no probe resolves, report once: *"I see a ref that looks like a ticket (`<arg>`), but no issue-tracker MCP is configured. Treating as free-form — paste the issue body if you want me to ground in it."* Then proceed as free-form.
43
-
44
- Treat the fetched issue's title + description + acceptance criteria as the intent baseline. Map to the plan's `## Acceptance criteria` 1:1, in order. Do not invent entries.
45
-
46
- ## 3. Run the PRIME arc
47
-
48
- Run the normal SPEAR workflow from `prime.md`. Key adaptations for autopilot mode:
49
-
50
- - **Scope.** Already classified; skip redundant classification. Announce the frame as `→ Frame:` and proceed — do NOT use the `question` tool to confirm. The user is walked away.
51
- - **Plan.** Delegate to `@plan`. For ref-originated requests, cite the issue ID in the plan's `## Goal`. The plan's `## Acceptance criteria` maps 1:1 to the ticket's Changes / Definition of Done list.
52
- - **Execute.** Delegate to `@build`. `@build` executes file-by-file and returns a summary; PRIME relays progress. Acceptance boxes get checked during `@build`'s execution.
53
- - **Assess.** Full suite pass + `@spec-reviewer` → `@code-reviewer` → iterate to `[PASS]`. No sentinel tokens during intermediate steps.
54
- - **Resolve.** Complete the Resolve stage: push branch, open PR via `gh pr create`, print PR URL.
55
- - **Multi-issue workflows.** If the prompt describes multiple issues, use `/fresh` between issues to isolate each on its own branch. Complete each issue's full SPEAR arc (including Resolve) before moving to the next.
56
-
57
- ## 4. Guardrails
58
-
59
- - **Never ask scoping questions.** The issue's acceptance list IS the authoritative scope. If you're tempted to ask whether to include X, the answer is: if the ticket didn't ask for it, don't include it. The `question` tool is forbidden in autopilot mode except for one narrow case: an architectural fork that blocks all progress AFTER codebase inspection, `@gap-analyzer` consultation, and precedent search (`git log`) have ALL failed to determine a default.
60
- - **Precedent defaults.** For helper-file location, naming, logging verbosity, error-wrapper style: search `git log` for a recent similar PR and mirror its structure. Cite the precedent commit in `## Constraints`.
61
- - **Plan-revision budget.** After `@plan-reviewer` returns `[REJECT]`: 1st REJECT → fix listed issues, resubmit. 2nd REJECT → narrow scope (move disputed items to `## Out of scope`). 3rd REJECT → escalate to `@architecture-advisor`.
62
- - **Resolve auto-ships.** When Assess returns `[PASS]`, complete the Resolve stage: push branch, open PR via `gh pr create`, print the PR URL, then stop. Do NOT re-invoke `/ship` — Resolve already did the work. `/ship` exists only as a manual resume path for interrupted sessions.
63
- - **Hard rules from Resolve still apply.** Never `--force`-push, never push to `main`/`master`, never `--no-verify`, never merge the PR yourself. Resolve only pushes the feature branch and opens the PR; the human gate is PR review and merge.
64
- - **Circular failure.** If the same test fails after the same fix twice, delegate to `@architecture-advisor` before a third attempt.
65
- - **STOP when stuck, don't churn.** If the plan is structurally wrong for this session (wrong branch, un-tickable AC, missing upstream work), emit a single line starting with `STOP:` followed by the specific reason. Do not re-attempt.
66
-
67
- ## 5. Completion
68
-
69
- When ALL work described in the prompt is complete (every issue resolved, every PR open), emit `<autopilot-done>` as the first token of your final message, followed by a brief summary:
70
-
71
- ```
72
- <autopilot-done>
73
-
74
- Done. <One-sentence summary of what was built.>
75
- PR(s): <url(s)>
76
- ```
77
-
78
- If Resolve failed or was interrupted, report the failure and the resume command: `/ship <plan-path>`.
79
-
80
- If you stopped early due to a structural block, emit `STOP: <reason>` instead of `<autopilot-done>`.
@@ -1,255 +0,0 @@
1
- #!/usr/bin/env bash
2
- # plan-check.sh — parse a plan file's plan-state fence and report on it.
3
- #
4
- # Modes:
5
- # plan-check.sh <path> Prints a summary line then one line per item:
6
- # `total=N done=M pending=K invalid=I`
7
- # `STATUS ID VERIFY` (one per item)
8
- #
9
- # plan-check.sh --run <path> Prints the verify command of each PENDING
10
- # item on stdout, one per line, raw. The
11
- # caller is responsible for executing them.
12
- # This script NEVER executes verify commands
13
- # itself — that would bypass the caller's
14
- # bash-permission scope.
15
- #
16
- # plan-check.sh --check <path>
17
- # Structural validation only. Exits 1 if any
18
- # fence item is missing a required field.
19
- #
20
- # Fence format, inside `## Acceptance criteria`:
21
- #
22
- # ```plan-state
23
- # - [ ] id: a1
24
- # intent: Prose description of business intent (one line).
25
- # tests:
26
- # - path/to/test.sh::"some test name"
27
- # - path/to/other.ts::"another test"
28
- # verify: bash path/to/test.sh
29
- #
30
- # - [x] id: a2
31
- # ...
32
- # ```
33
- #
34
- # Backward compat: a plan without a ```plan-state fence emits the line
35
- # `legacy` and exits 0 — callers treat it as "old format, fall back".
36
- #
37
- # Portability: POSIX bash + awk + grep only. No sed -i.
38
-
39
- set -eu
40
-
41
- MODE=""
42
- PLAN_PATH=""
43
-
44
- case "${1:-}" in
45
- --run) MODE=run; PLAN_PATH="${2:-}" ;;
46
- --check) MODE=check; PLAN_PATH="${2:-}" ;;
47
- -h|--help|"")
48
- sed -n '2,34p' "$0"
49
- exit 0
50
- ;;
51
- *) MODE=summary; PLAN_PATH="${1:-}" ;;
52
- esac
53
-
54
- if [[ -z "${PLAN_PATH:-}" ]]; then
55
- echo "plan-check.sh: missing plan path" >&2
56
- exit 2
57
- fi
58
-
59
- if [[ ! -f "$PLAN_PATH" ]]; then
60
- echo "plan-check.sh: file not found: $PLAN_PATH" >&2
61
- exit 2
62
- fi
63
-
64
- # Extract the plan-state fence body into a temp file. awk state machine:
65
- # enter `## Acceptance criteria`, enter ``` plan-state, exit on next ```.
66
- FENCE_BODY="$(awk '
67
- /^## Acceptance criteria/ { in_ac = 1; next }
68
- /^## / && in_ac && !in_fence { in_ac = 0 }
69
- in_ac && /^```plan-state[[:space:]]*$/ { in_fence = 1; next }
70
- in_fence && /^```[[:space:]]*$/ { in_fence = 0; next }
71
- in_fence { print }
72
- ' "$PLAN_PATH")"
73
-
74
- if [[ -z "$FENCE_BODY" ]]; then
75
- # No fence found — legacy plan. Report and exit cleanly.
76
- if [[ "$MODE" == "summary" ]]; then
77
- echo "legacy (no plan-state fence)"
78
- fi
79
- # --run on a legacy plan emits nothing (no commands to run).
80
- # --check on a legacy plan succeeds (we're accepting legacy plans).
81
- exit 0
82
- fi
83
-
84
- # Parse items. awk state machine:
85
- # - A line `- [ ] id: ID` or `- [x] id: ID` starts a new item.
86
- # - While inside an item, indented keys `intent:`, `tests:`, `verify:` set
87
- # fields. Under `tests:`, subsequent ` - ...` lines extend the list
88
- # until the next key or the next item.
89
- # - Items are separated by one or more blank lines OR by the next `- [`.
90
- #
91
- # We emit a tab-delimited record per item:
92
- # STATUS<TAB>ID<TAB>INTENT<TAB>TESTS<TAB>VERIFY
93
- # TESTS is a `|`-delimited list. Missing fields are the empty string.
94
- PARSED="$(echo "$FENCE_BODY" | awk '
95
- function flush() {
96
- if (cur_id != "") {
97
- # Trim trailing/leading whitespace on each field
98
- gsub(/^[[:space:]]+|[[:space:]]+$/, "", cur_intent)
99
- gsub(/^[[:space:]]+|[[:space:]]+$/, "", cur_verify)
100
- gsub(/^\||\|$/, "", cur_tests)
101
- printf "%s\t%s\t%s\t%s\t%s\n", cur_status, cur_id, cur_intent, cur_tests, cur_verify
102
- }
103
- cur_status = ""; cur_id = ""; cur_intent = ""; cur_tests = ""; cur_verify = ""
104
- in_tests = 0
105
- }
106
-
107
- /^-[[:space:]]+\[[[:space:]xX[:space:]]\][[:space:]]+id:/ {
108
- flush()
109
- status = $0
110
- sub(/^-[[:space:]]+\[[[:space:]]*/, "", status)
111
- sub(/\].*$/, "", status)
112
- # status is either empty/space (" ") -> pending, or "x"/"X" -> done
113
- if (status ~ /[xX]/) cur_status = "done"; else cur_status = "pending"
114
- # Capture id
115
- id_part = $0
116
- sub(/^.*id:[[:space:]]*/, "", id_part)
117
- cur_id = id_part
118
- next
119
- }
120
-
121
- /^[[:space:]]*intent:/ {
122
- field = $0
123
- sub(/^[[:space:]]*intent:[[:space:]]*/, "", field)
124
- cur_intent = field
125
- in_tests = 0
126
- next
127
- }
128
-
129
- /^[[:space:]]*intent\b/ {
130
- # already handled
131
- next
132
- }
133
-
134
- /^[[:space:]]*tests:/ {
135
- in_tests = 1
136
- next
137
- }
138
-
139
- /^[[:space:]]*verify:/ {
140
- field = $0
141
- sub(/^[[:space:]]*verify:[[:space:]]*/, "", field)
142
- cur_verify = field
143
- in_tests = 0
144
- next
145
- }
146
-
147
- # Continuation lines inside tests: list
148
- in_tests && /^[[:space:]]+-[[:space:]]/ {
149
- line = $0
150
- sub(/^[[:space:]]+-[[:space:]]+/, "", line)
151
- if (cur_tests == "") cur_tests = line
152
- else cur_tests = cur_tests "|" line
153
- next
154
- }
155
-
156
- # Continuation line for intent (indented without `-`, after intent is set
157
- # and before another key). Append with a space separator.
158
- !in_tests && /^[[:space:]]{4,}[^-[:space:]]/ && cur_id != "" && cur_intent != "" && cur_verify == "" {
159
- line = $0
160
- sub(/^[[:space:]]+/, "", line)
161
- cur_intent = cur_intent " " line
162
- next
163
- }
164
-
165
- END { flush() }
166
- ' 2>&1)"
167
-
168
- # If PARSED contains awk errors, surface them as invalid.
169
- if echo "$PARSED" | grep -q '^awk:'; then
170
- echo "plan-check.sh: parser error" >&2
171
- echo "$PARSED" >&2
172
- exit 3
173
- fi
174
-
175
- # Count totals.
176
- total=0
177
- done_count=0
178
- pending_count=0
179
- invalid_count=0
180
- invalid_reasons=()
181
-
182
- while IFS=$'\t' read -r status id intent tests verify; do
183
- [[ -z "$status" ]] && continue
184
- total=$((total + 1))
185
- if [[ -z "$id" ]]; then
186
- invalid_count=$((invalid_count + 1))
187
- invalid_reasons+=("missing id")
188
- continue
189
- fi
190
- if [[ -z "$intent" ]]; then
191
- invalid_count=$((invalid_count + 1))
192
- invalid_reasons+=("$id: missing intent")
193
- continue
194
- fi
195
- if [[ -z "$tests" ]]; then
196
- invalid_count=$((invalid_count + 1))
197
- invalid_reasons+=("$id: missing tests")
198
- continue
199
- fi
200
- if [[ -z "$verify" ]]; then
201
- invalid_count=$((invalid_count + 1))
202
- invalid_reasons+=("$id: missing verify")
203
- continue
204
- fi
205
- if [[ "$status" == "done" ]]; then
206
- done_count=$((done_count + 1))
207
- else
208
- pending_count=$((pending_count + 1))
209
- fi
210
- done <<< "$PARSED"
211
-
212
- case "$MODE" in
213
- summary)
214
- printf 'total=%d done=%d pending=%d invalid=%d\n' \
215
- "$total" "$done_count" "$pending_count" "$invalid_count"
216
- while IFS=$'\t' read -r status id intent tests verify; do
217
- [[ -z "$status" ]] && continue
218
- # For the summary-per-item line, prefer displaying the verify
219
- # command (truncated) so the reader sees what gates each item.
220
- v="${verify:0:60}"
221
- if [[ -n "$verify" && ${#verify} -gt 60 ]]; then v="${v}…"; fi
222
- printf '%s %s %s\n' "$status" "$id" "$v"
223
- done <<< "$PARSED"
224
- if [[ "$invalid_count" -gt 0 ]]; then
225
- echo "invalid:"
226
- for r in "${invalid_reasons[@]}"; do
227
- echo " $r"
228
- done
229
- fi
230
- ;;
231
-
232
- run)
233
- # Emit verify command per PENDING item, one per line. Skip done items,
234
- # skip invalid items. Caller executes via their own bash permission.
235
- while IFS=$'\t' read -r status id intent tests verify; do
236
- [[ -z "$status" ]] && continue
237
- [[ "$status" == "done" ]] && continue
238
- [[ -z "$verify" ]] && continue
239
- [[ -z "$intent" || -z "$tests" ]] && continue
240
- echo "$verify"
241
- done <<< "$PARSED"
242
- ;;
243
-
244
- check)
245
- # Structural validation. Exit 1 if anything invalid.
246
- if [[ "$invalid_count" -gt 0 ]]; then
247
- echo "plan-check: $invalid_count invalid item(s):" >&2
248
- for r in "${invalid_reasons[@]}"; do
249
- echo " $r" >&2
250
- done
251
- exit 1
252
- fi
253
- printf 'ok: %d item(s) pass structural validation\n' "$total"
254
- ;;
255
- esac