opencode-discipline 0.1.2 → 0.1.4

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/agents/README.md CHANGED
@@ -7,7 +7,7 @@ This package ships seven agent definitions for disciplined plan-first execution.
7
7
  | `plan` | `anthropic/claude-opus-4-6` | primary | Runs the 4-wave planning workflow and coordinates accept/revise handoff |
8
8
  | `build` | `anthropic/claude-opus-4-6` | primary | Executes approved plans phase-by-phase with verification gates |
9
9
  | `oracle` | `openai/gpt-5.4` | subagent | Read-only architecture and tradeoff advisor |
10
- | `librarian` | `openai/gpt-4.1-mini` | subagent | Read-only research and documentation specialist |
10
+ | `librarian` | `openai/gpt-5.4-mini` | subagent | Read-only research and documentation specialist |
11
11
  | `reviewer` | `openai/gpt-5.4` | subagent | Read-only quality critic for plans and code |
12
12
  | `designer` | `anthropic/claude-sonnet-4-6` | subagent | Read-only UI/UX and accessibility advisor |
13
13
  | `deep` | `openai/gpt-5.3-codex` | subagent | Advanced implementation subagent for complex coding work |
@@ -0,0 +1,43 @@
1
+ ---
2
+ description: Fast codebase search. Finds files, patterns, and structure quickly. Cheap and parallel-friendly.
3
+ model: openai/gpt-5.4-mini
4
+ temperature: 0
5
+ mode: subagent
6
+ color: "#78909C"
7
+ tools:
8
+ read: true
9
+ write: false
10
+ edit: false
11
+ bash: true
12
+ glob: true
13
+ grep: true
14
+ task: false
15
+ permission:
16
+ bash:
17
+ "*": deny
18
+ "rtk *": allow
19
+ "find *": allow
20
+ "ls *": allow
21
+ "cat *": allow
22
+ "head *": allow
23
+ "tail *": allow
24
+ "wc *": allow
25
+ "git log *": allow
26
+ "git show *": allow
27
+ ---
28
+
29
+ You are a fast codebase explorer. Your job is to find files, patterns, and structure as quickly as possible. You never implement or modify anything.
30
+
31
+ ## How you work
32
+
33
+ 1. Start with project structure — `ls`, file tree, config files
34
+ 2. Use Glob for file patterns, Grep for content search
35
+ 3. Read only what's relevant — don't dump entire files
36
+ 4. Return concise findings with exact file paths and line numbers
37
+
38
+ ## Rules
39
+
40
+ - NEVER write or edit files
41
+ - Be fast — breadth first, then drill into relevant areas
42
+ - Return structured results the caller can act on immediately
43
+ - If you can't find what's needed, say so
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Research specialist. Gathers context from codebase, external docs, and reference implementations before work begins. Returns structured findings.
3
- model: openai/gpt-4.1-mini
3
+ model: openai/gpt-5.4-mini
4
4
  temperature: 0
5
5
  mode: subagent
6
6
  color: "#1D9E75"
package/agents/plan.md CHANGED
@@ -27,6 +27,12 @@ permission:
27
27
  "cargo check *": allow
28
28
  "cargo test --no-run *": allow
29
29
  "npm run type-check *": allow
30
+ "bun run type-check *": allow
31
+ "bun run type-check": allow
32
+ "bun test *": allow
33
+ "bun test": allow
34
+ "bun run build *": allow
35
+ "bun run build": allow
30
36
  "find *": allow
31
37
  "wc *": allow
32
38
  "mkdir *": allow
@@ -67,10 +73,10 @@ Before generating the plan, perform a Metis-style gap check. Ask yourself:
67
73
  4. **Ambiguity** — Are there terms or requirements that could be interpreted two ways?
68
74
  5. **Breaking changes** — Will this plan touch shared interfaces, public APIs, or migration paths?
69
75
 
70
- If any gaps are found:
71
- - Ask the user to clarify (1-2 questions max)
72
- - Delegate to @oracle if the gap involves an architectural tradeoff
73
- - Document the gap and resolution in the plan under "## Decisions"
76
+ Wave 2 policy (always required):
77
+ - Ask the user to clarify gaps when needed (1-2 questions max)
78
+ - Delegate to @oracle once in every Wave 2 pass, even for simple tasks
79
+ - Summarize oracle feedback and document the resolution in the plan under "## Decisions"
74
80
 
75
81
  ### Wave 3: Plan generation
76
82
 
@@ -148,10 +154,10 @@ For high-stakes plans (production changes, data migrations, auth refactors):
148
154
 
149
155
  ### Presenting the plan
150
156
 
151
- After writing:
157
+ After writing and completing Wave 4 review:
152
158
  1. Tell the user: "Plan written to `tasks/plans/{filename}.md`"
153
159
  2. Summarize in 3-5 sentences
154
- 3. Say: "Review and edit the plan, then switch to Build to execute. Or ask me to revise."
160
+ 3. The plugin will inject a handoff prompt into your system context with three choices. Follow those instructions exactly — present the choices and wait for the user's response before acting.
155
161
 
156
162
  ## Rules
157
163
 
@@ -162,3 +168,4 @@ After writing:
162
168
  - Prefer small phases (2-4 items). If a phase has 5+ items, split it.
163
169
  - Interleave tests with implementation — never "Phase N: write all tests."
164
170
  - A good plan lets the Build agent one-shot the implementation. That's the bar.
171
+ - `advance_wave` and `accept_plan` are plugin tools you MUST call — they are not implementation actions. Calling them is part of your planning workflow, not a violation of read-only mode.
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ var __export = (target, all) => {
11
11
  };
12
12
 
13
13
  // src/index.ts
14
- import { accessSync, constants } from "fs";
14
+ import { accessSync, constants, existsSync as existsSync2 } from "fs";
15
15
  import { basename, relative, resolve } from "path";
16
16
 
17
17
  // node_modules/@opencode-ai/plugin/node_modules/zod/v4/classic/external.js
@@ -12665,7 +12665,7 @@ function buildWaveStateSystemBlock(wave, planName) {
12665
12665
  "",
12666
12666
  "### Wave rules:",
12667
12667
  "- Wave 1 (Interview): Ask clarifying questions. Delegate to @explore and @librarian. Do NOT write the plan yet.",
12668
- "- Wave 2 (Gap Analysis): Check for hidden intent, over-engineering, missing context, ambiguity, breaking changes. Do NOT write the plan yet.",
12668
+ "- Wave 2 (Gap Analysis): Check for hidden intent, over-engineering, missing context, ambiguity, breaking changes. MANDATORY: consult @oracle in this wave before advancing. Do NOT write the plan yet.",
12669
12669
  "- Wave 3 (Plan Generation): NOW write the plan to tasks/plans/{planName}.md using the structured template.",
12670
12670
  "- Wave 4 (Review): Self-review the plan. Delegate to @oracle for high-stakes decisions. Edit the plan if needed.",
12671
12671
  "",
@@ -12674,6 +12674,14 @@ function buildWaveStateSystemBlock(wave, planName) {
12674
12674
  ].join(`
12675
12675
  `);
12676
12676
  }
12677
+ function buildWave2OraclePrompt() {
12678
+ return [
12679
+ "## MANDATORY: Wave 2 Oracle Check",
12680
+ "Before you can advance to Wave 3, delegate to `@oracle` once for a gap-analysis sanity check.",
12681
+ 'Use the Task tool with `subagent_type: "oracle"` and summarize the result in your analysis.'
12682
+ ].join(`
12683
+ `);
12684
+ }
12677
12685
  function buildPlanReadOnlyReminder() {
12678
12686
  return [
12679
12687
  "## CRITICAL: Plan mode ACTIVE - you are in READ-ONLY phase...",
@@ -12684,6 +12692,31 @@ function buildPlanReadOnlyReminder() {
12684
12692
  ].join(`
12685
12693
  `);
12686
12694
  }
12695
+ function buildPlanHandoffPrompt(planName) {
12696
+ const planPath = `tasks/plans/${planName}.md`;
12697
+ return [
12698
+ "## MANDATORY: Plan Handoff \u2014 Present Choices Now",
12699
+ "",
12700
+ `The plan file \`${planPath}\` has been written and Wave 4 review is complete.`,
12701
+ "You MUST present the following choices to the user. Do NOT skip this step.",
12702
+ "Do NOT say 'switch to Build' or any other freeform handoff text.",
12703
+ "",
12704
+ "Present this EXACTLY:",
12705
+ "",
12706
+ "---",
12707
+ "**Choose one:**",
12708
+ `1. **Accept plan & start work** \u2014 I'll call \`accept_plan\` to create a new Build session and start implementing \`${planPath}\` immediately.`,
12709
+ "2. **Start work later** \u2014 The plan is saved. You can start a Build session whenever you're ready.",
12710
+ "3. **I have modifications to make** \u2014 Stay in review mode so you can request changes.",
12711
+ "---",
12712
+ "",
12713
+ "Then wait for the user's response before taking any action.",
12714
+ '- If the user picks option 1: call `accept_plan(action="accept")`',
12715
+ "- If the user picks option 2: do nothing, confirm the plan is saved",
12716
+ '- If the user picks option 3: call `accept_plan(action="revise")`, ask what to change, and after revisions present these choices again'
12717
+ ].join(`
12718
+ `);
12719
+ }
12687
12720
  function buildCompactionContext(state) {
12688
12721
  const acceptedLine = state.acceptedAt ? `- Accepted timestamp: ${state.acceptedAt}${state.acceptedBySessionID ? ` (build session ${state.acceptedBySessionID})` : ""}` : "- accept_plan controls handoff to Build; if accepted, preserve build session id and accepted timestamp.";
12689
12722
  return [
@@ -12701,20 +12734,51 @@ function buildCompactionContext(state) {
12701
12734
  ].join(`
12702
12735
  `);
12703
12736
  }
12704
- function buildTodoSeedContent(planName) {
12705
- return `Plan kickoff: ${planName}`;
12737
+ function buildPlanningTodos(planName) {
12738
+ const planPath = `tasks/plans/${planName}.md`;
12739
+ return [
12740
+ {
12741
+ content: "Wave 1: Interview - clarify requirements, scope, constraints, and success criteria",
12742
+ status: "in_progress",
12743
+ priority: "high"
12744
+ },
12745
+ {
12746
+ content: "Wave 2: Gap Analysis - surface hidden intent, ambiguity, risks, and breaking changes",
12747
+ status: "pending",
12748
+ priority: "high"
12749
+ },
12750
+ {
12751
+ content: `Wave 3: Plan Generation - write the structured plan file at ${planPath}`,
12752
+ status: "pending",
12753
+ priority: "high"
12754
+ },
12755
+ {
12756
+ content: "Wave 4: Review - self-review clarity, verification, scope, and implementation readiness",
12757
+ status: "pending",
12758
+ priority: "high"
12759
+ },
12760
+ {
12761
+ content: "Step 5: Ask user to confirm - choose one: Accept plan & start work / Start work later / I have modifications to make",
12762
+ status: "pending",
12763
+ priority: "high"
12764
+ }
12765
+ ];
12766
+ }
12767
+ function normalizeTodoContent(content) {
12768
+ return content.toLowerCase().replace(/\s+/g, " ").trim();
12706
12769
  }
12707
12770
  function buildTodoSeedInstruction(planName, retry) {
12708
- const content = buildTodoSeedContent(planName);
12771
+ const todoItems = buildPlanningTodos(planName);
12709
12772
  const title = retry ? "## Discipline Plugin \u2014 Todo Seed Retry" : "## Discipline Plugin \u2014 Todo Seed";
12710
- const intro = retry ? "The plan kickoff todo is still missing in the right panel." : "Before continuing, create the kickoff todo so progress is visible in the right panel.";
12773
+ const intro = retry ? "The planning checklist is still missing in the right panel." : "Before continuing, create the planning checklist in the right panel.";
12774
+ const todoLines = todoItems.map((todo, index) => {
12775
+ return `${index + 1}. content: \`${todo.content}\` | status: \`${todo.status}\` | priority: \`${todo.priority}\``;
12776
+ });
12711
12777
  return [
12712
12778
  title,
12713
12779
  intro,
12714
- "Call `todowrite` now with this item:",
12715
- `- content: \`${content}\``,
12716
- "- status: `in_progress`",
12717
- "- priority: `high`"
12780
+ "Call `todowrite` now with these five items:",
12781
+ ...todoLines
12718
12782
  ].join(`
12719
12783
  `);
12720
12784
  }
@@ -12732,9 +12796,12 @@ function extractTodos(response) {
12732
12796
  }
12733
12797
  return [];
12734
12798
  }
12735
- function hasPlanKickoffTodo(todos, planName) {
12736
- const expected = buildTodoSeedContent(planName).toLowerCase();
12737
- return todos.some((todo) => todo.content.toLowerCase().includes(expected));
12799
+ function hasPlanningTodoChecklist(todos, planName) {
12800
+ const normalizedTodos = todos.map((todo) => normalizeTodoContent(todo.content));
12801
+ const expectedContents = buildPlanningTodos(planName).map((todo) => normalizeTodoContent(todo.content));
12802
+ return expectedContents.every((expected) => {
12803
+ return normalizedTodos.some((content) => content.includes(expected));
12804
+ });
12738
12805
  }
12739
12806
  async function readSessionTodos(client, directory, sessionID) {
12740
12807
  const sessionApi = client.session;
@@ -12771,6 +12838,15 @@ async function handleSystemTransform(ctx, input, output) {
12771
12838
  if (currentAgent === "plan" && state.wave < 3) {
12772
12839
  output.system.push(buildPlanReadOnlyReminder());
12773
12840
  }
12841
+ if (currentAgent === "plan" && state.wave === 2 && !state.accepted) {
12842
+ output.system.push(buildWave2OraclePrompt());
12843
+ }
12844
+ if (currentAgent === "plan" && state.wave === 4 && !state.accepted) {
12845
+ const planFilePath = resolve(ctx.worktree, `tasks/plans/${state.planName}.md`);
12846
+ if (existsSync2(planFilePath)) {
12847
+ output.system.push(buildPlanHandoffPrompt(state.planName));
12848
+ }
12849
+ }
12774
12850
  if (sessionID && currentAgent === "plan" && !state.accepted) {
12775
12851
  const nudgeState = ctx.todoNudges.get(sessionID) ?? {
12776
12852
  prompted: false,
@@ -12778,7 +12854,7 @@ async function handleSystemTransform(ctx, input, output) {
12778
12854
  seeded: false
12779
12855
  };
12780
12856
  const todos = await readSessionTodos(ctx.client, ctx.directory, sessionID);
12781
- const hasSeededTodo = todos !== undefined && hasPlanKickoffTodo(todos, state.planName);
12857
+ const hasSeededTodo = todos !== undefined && hasPlanningTodoChecklist(todos, state.planName);
12782
12858
  if (hasSeededTodo) {
12783
12859
  nudgeState.seeded = true;
12784
12860
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-discipline",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",