opencode-discipline 0.1.3 → 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 +1 -1
- package/agents/explore.md +1 -1
- package/agents/librarian.md +1 -1
- package/agents/plan.md +5 -8
- package/dist/index.js +90 -14
- package/package.json +1 -1
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
|
|
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 |
|
package/agents/explore.md
CHANGED
package/agents/librarian.md
CHANGED
|
@@ -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
|
|
3
|
+
model: openai/gpt-5.4-mini
|
|
4
4
|
temperature: 0
|
|
5
5
|
mode: subagent
|
|
6
6
|
color: "#1D9E75"
|
package/agents/plan.md
CHANGED
|
@@ -73,10 +73,10 @@ Before generating the plan, perform a Metis-style gap check. Ask yourself:
|
|
|
73
73
|
4. **Ambiguity** — Are there terms or requirements that could be interpreted two ways?
|
|
74
74
|
5. **Breaking changes** — Will this plan touch shared interfaces, public APIs, or migration paths?
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
- Ask the user to clarify (1-2 questions max)
|
|
78
|
-
- Delegate to @oracle
|
|
79
|
-
-
|
|
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"
|
|
80
80
|
|
|
81
81
|
### Wave 3: Plan generation
|
|
82
82
|
|
|
@@ -157,10 +157,7 @@ For high-stakes plans (production changes, data migrations, auth refactors):
|
|
|
157
157
|
After writing and completing Wave 4 review:
|
|
158
158
|
1. Tell the user: "Plan written to `tasks/plans/{filename}.md`"
|
|
159
159
|
2. Summarize in 3-5 sentences
|
|
160
|
-
3.
|
|
161
|
-
- **Accept plan & start work** — Call `accept_plan(action="accept")` to create a new Build session with the plan registered. The Build agent will start implementing immediately.
|
|
162
|
-
- **Start work later** — Do nothing. The plan is saved and the user can start a Build session manually whenever they're ready.
|
|
163
|
-
- **I have modifications to make** — Call `accept_plan(action="revise")` and stay in Wave 4 to incorporate the user's feedback. After revisions, present these three choices again.
|
|
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.
|
|
164
161
|
|
|
165
162
|
## Rules
|
|
166
163
|
|
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
|
|
12705
|
-
|
|
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
|
|
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
|
|
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
|
|
12715
|
-
|
|
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
|
|
12736
|
-
const
|
|
12737
|
-
|
|
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 &&
|
|
12857
|
+
const hasSeededTodo = todos !== undefined && hasPlanningTodoChecklist(todos, state.planName);
|
|
12782
12858
|
if (hasSeededTodo) {
|
|
12783
12859
|
nudgeState.seeded = true;
|
|
12784
12860
|
}
|