@selesai/code 0.3.10 → 0.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 (34) hide show
  1. package/dist/defaults/models.json +2 -3
  2. package/dist/extensions/caveman/index.js +16 -1
  3. package/dist/extensions/caveman/test/extension.test.js +7 -4
  4. package/dist/extensions/caveman/test/helpers.test.js +12 -1
  5. package/dist/extensions/context-compaction-reminder.test.ts +82 -0
  6. package/dist/extensions/context-compaction-reminder.ts +28 -0
  7. package/dist/extensions/handoff-new.test.ts +2 -13
  8. package/dist/extensions/handoff-new.ts +16 -25
  9. package/dist/extensions/package.json +1 -0
  10. package/dist/extensions/pi-subagents/agents/architect.md +25 -173
  11. package/dist/extensions/pi-subagents/agents/builder.md +16 -105
  12. package/dist/extensions/pi-subagents/agents/commentator.md +19 -116
  13. package/dist/extensions/pi-subagents/agents/explorer.md +13 -33
  14. package/dist/extensions/pi-subagents/agents/recapper.md +19 -11
  15. package/dist/extensions/pi-subagents/agents/researcher.md +10 -33
  16. package/dist/extensions/question/constants.ts +0 -7
  17. package/dist/extensions/question/index.ts +11 -108
  18. package/dist/extensions/question/schemas.ts +1 -26
  19. package/dist/extensions/question/shortcuts.ts +5 -14
  20. package/dist/extensions/question/tests/question-list.test.ts +8 -0
  21. package/dist/extensions/question/tests/ui-protocol.test.ts +1 -7
  22. package/dist/extensions/question/tui-adapter.ts +3 -20
  23. package/dist/extensions/question/types.ts +0 -6
  24. package/dist/extensions/question/ui-protocol.ts +2 -17
  25. package/dist/extensions/workflow/adapter.ts +384 -124
  26. package/dist/extensions/workflow/extension.ts +2 -1
  27. package/dist/extensions/workflow/modes/prototype.ts +6 -6
  28. package/dist/extensions/workflow/modes/quick.ts +6 -6
  29. package/dist/extensions/workflow/modes/task.ts +79 -0
  30. package/dist/extensions/workflow/run-state.ts +124 -0
  31. package/dist/extensions/workflow/state-machine.ts +4 -8
  32. package/dist/skills/workflow-creation/SKILL.md +72 -0
  33. package/docs/workflows.md +40 -6
  34. package/package.json +1 -1
@@ -16,7 +16,6 @@
16
16
  "reasoning": true,
17
17
  "input": ["text"],
18
18
  "contextWindow": 1000000,
19
- "maxTokens": 384000,
20
19
  "cost": {
21
20
  "input": 0.14,
22
21
  "output": 0.28,
@@ -113,7 +112,7 @@
113
112
  "name": "GLM-5.2",
114
113
  "reasoning": true,
115
114
  "input": ["text"],
116
- "contextWindow": 1048560,
115
+ "contextWindow": 1000000,
117
116
  "cost": {
118
117
  "input": 1.45,
119
118
  "output": 4.5,
@@ -129,7 +128,7 @@
129
128
  "name": "GLM-5.2 Fast",
130
129
  "reasoning": false,
131
130
  "input": ["text"],
132
- "contextWindow": 1048560,
131
+ "contextWindow": 1000000,
133
132
  "cost": {
134
133
  "input": 1.45,
135
134
  "output": 4.5,
@@ -23,6 +23,20 @@ export function isDeactivationCommand(text) {
23
23
  return t === "stop caveman" || t === "normal mode";
24
24
  }
25
25
 
26
+ export function resolveSessionActive(entries, fallback = true) {
27
+ if (!Array.isArray(entries)) return fallback;
28
+
29
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
30
+ const entry = entries[i];
31
+ if (entry?.type !== "custom" || entry?.customType !== "caveman-mode") continue;
32
+
33
+ const active = entry?.data?.active;
34
+ if (typeof active === "boolean") return active;
35
+ }
36
+
37
+ return fallback;
38
+ }
39
+
26
40
  export default function cavemanExtension(pi) {
27
41
  let active = true;
28
42
  let isActive = false;
@@ -81,7 +95,8 @@ export default function cavemanExtension(pi) {
81
95
  });
82
96
 
83
97
  pi.on("session_start", async (_event, ctx) => {
84
- active = true; // ponytail: always ON at session start; OFF is current-session-only
98
+ const entries = ctx?.sessionManager?.getBranch?.() || ctx?.sessionManager?.getEntries?.() || [];
99
+ active = resolveSessionActive(entries);
85
100
  syncStatus(ctx);
86
101
  ctx?.ui?.notify?.(`Caveman loaded: ${active ? "ON" : "OFF"}`, "info");
87
102
  });
@@ -80,23 +80,26 @@ test("disabled caveman skips instruction injection", async () => {
80
80
  assert.equal(result, undefined);
81
81
  });
82
82
 
83
- test("session_start always ON even if persisted entry is OFF", async () => {
83
+ test("session_start restores persisted OFF state", async () => {
84
84
  const { events } = createPiHarness();
85
85
  const ctx = createCommandContext({
86
86
  sessionManager: {
87
- getEntries: () => [
87
+ getBranch: () => [
88
88
  { type: "custom", customType: "caveman-mode", data: { active: false } },
89
89
  ],
90
+ getEntries: () => [
91
+ { type: "custom", customType: "caveman-mode", data: { active: true } },
92
+ ],
90
93
  },
91
94
  });
92
95
 
93
96
  await events.get("session_start")({ reason: "resume" }, ctx);
94
97
  const result = await events.get("before_agent_start")({ systemPrompt: "BASE" }, ctx);
95
98
 
96
- assert.ok(result.systemPrompt.includes("CAVEMAN MODE ACTIVE"));
99
+ assert.equal(result, undefined);
97
100
  });
98
101
 
99
- test("off then new session_start resets to ON", async () => {
102
+ test("session_start defaults to ON when no persisted entry exists", async () => {
100
103
  const { commands, events } = createPiHarness();
101
104
  const ctx = createCommandContext();
102
105
 
@@ -7,6 +7,7 @@ import {
7
7
  getCavemanInstructions,
8
8
  isDeactivationCommand,
9
9
  parseCavemanCommand,
10
+ resolveSessionActive,
10
11
  } from "../index.js";
11
12
 
12
13
  test("parseCavemanCommand: bare toggles", () => {
@@ -26,7 +27,17 @@ test("parseCavemanCommand: unknown arg is invalid", () => {
26
27
  assert.deepEqual(parseCavemanCommand("banana"), { type: "invalid", reason: "unknown-subcommand", arg: "banana" });
27
28
  });
28
29
 
29
-
30
+ test("resolveSessionActive: returns last persisted boolean, defaults to true", () => {
31
+ assert.equal(resolveSessionActive([]), true);
32
+ assert.equal(resolveSessionActive([{ type: "custom", customType: "caveman-mode", data: { active: false } }]), false);
33
+ assert.equal(resolveSessionActive([{ type: "custom", customType: "caveman-mode", data: { active: true } }]), true);
34
+ assert.equal(resolveSessionActive([
35
+ { type: "custom", customType: "caveman-mode", data: { active: false } },
36
+ { type: "custom", customType: "caveman-mode", data: { active: true } },
37
+ ]), true);
38
+ assert.equal(resolveSessionActive([{ type: "custom", customType: "other", data: {} }]), true);
39
+ assert.equal(resolveSessionActive(null), true);
40
+ });
30
41
 
31
42
  test("isDeactivationCommand: only the whole message, case-insensitive, trailing punct ok", () => {
32
43
  assert.equal(isDeactivationCommand("stop caveman"), true);
@@ -0,0 +1,82 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import type { ExtensionAPI, ExtensionContext } from "@selesai/code";
3
+ import contextCompactionReminderExtension, {
4
+ CONTEXT_COMPACTION_REMINDER_MESSAGE,
5
+ } from "./context-compaction-reminder.ts";
6
+
7
+ type Handler = (event: unknown, ctx: ExtensionContext) => void | Promise<void>;
8
+
9
+ function createHarness(tokens: number | null | undefined = undefined) {
10
+ const handlers = new Map<string, Handler>();
11
+ const notify = vi.fn();
12
+ const pi = {
13
+ on: vi.fn((event: string, handler: Handler) => handlers.set(event, handler)),
14
+ } as unknown as ExtensionAPI;
15
+ const ctx = {
16
+ getContextUsage: () =>
17
+ tokens === undefined ? undefined : { tokens, contextWindow: 200_000, percent: tokens === null ? null : tokens / 2_000 },
18
+ ui: { notify },
19
+ } as unknown as ExtensionContext;
20
+
21
+ contextCompactionReminderExtension(pi);
22
+
23
+ return {
24
+ notify,
25
+ setTokens: (value: number | null | undefined) => {
26
+ tokens = value;
27
+ },
28
+ settle: () => handlers.get("agent_settled")!({ type: "agent_settled" }, ctx),
29
+ compact: () => handlers.get("session_compact")!({ type: "session_compact" }, ctx),
30
+ };
31
+ }
32
+
33
+ describe("context-compaction-reminder", () => {
34
+ it("warns once when known context reaches 128k", async () => {
35
+ const { notify, setTokens, settle } = createHarness(127_999);
36
+
37
+ await settle();
38
+ setTokens(128_000);
39
+ await settle();
40
+ setTokens(130_000);
41
+ await settle();
42
+
43
+ expect(notify).toHaveBeenCalledTimes(1);
44
+ expect(notify).toHaveBeenCalledWith(CONTEXT_COMPACTION_REMINDER_MESSAGE, "warning");
45
+ });
46
+
47
+ it("does not reset the reminder for unavailable or post-compaction usage", async () => {
48
+ const { notify, setTokens, settle } = createHarness(128_000);
49
+
50
+ await settle();
51
+ setTokens(undefined);
52
+ await settle();
53
+ setTokens(null);
54
+ await settle();
55
+ setTokens(130_000);
56
+ await settle();
57
+
58
+ expect(notify).toHaveBeenCalledTimes(1);
59
+ });
60
+
61
+ it("allows another warning after successful compaction", async () => {
62
+ const { notify, settle, compact } = createHarness(128_000);
63
+
64
+ await settle();
65
+ await compact();
66
+ await settle();
67
+
68
+ expect(notify).toHaveBeenCalledTimes(2);
69
+ });
70
+
71
+ it("allows another warning after known usage drops below 128k", async () => {
72
+ const { notify, setTokens, settle } = createHarness(128_000);
73
+
74
+ await settle();
75
+ setTokens(127_999);
76
+ await settle();
77
+ setTokens(128_000);
78
+ await settle();
79
+
80
+ expect(notify).toHaveBeenCalledTimes(2);
81
+ });
82
+ });
@@ -0,0 +1,28 @@
1
+ import type { ExtensionAPI } from "@selesai/code";
2
+
3
+ export const CONTEXT_COMPACTION_REMINDER_THRESHOLD = 128_000;
4
+ export const CONTEXT_COMPACTION_REMINDER_MESSAGE =
5
+ "Conversation context has reached 128k tokens. Run /compact to summarize older context.";
6
+
7
+ export default function contextCompactionReminderExtension(pi: ExtensionAPI): void {
8
+ let reminderShown = false;
9
+
10
+ pi.on("session_compact", () => {
11
+ reminderShown = false;
12
+ });
13
+
14
+ pi.on("agent_settled", (_event, ctx) => {
15
+ const tokens = ctx.getContextUsage()?.tokens;
16
+ if (tokens === undefined || tokens === null) return;
17
+
18
+ if (tokens < CONTEXT_COMPACTION_REMINDER_THRESHOLD) {
19
+ reminderShown = false;
20
+ return;
21
+ }
22
+
23
+ if (!reminderShown) {
24
+ ctx.ui.notify(CONTEXT_COMPACTION_REMINDER_MESSAGE, "warning");
25
+ reminderShown = true;
26
+ }
27
+ });
28
+ }
@@ -28,11 +28,10 @@ function createPiHarness() {
28
28
  return { commands };
29
29
  }
30
30
 
31
- test("registers both handoff-new and handover-new commands", () => {
31
+ test("registers handoff-new only", () => {
32
32
  const { commands } = createPiHarness();
33
33
  assert.ok(commands.has("handoff-new"));
34
- assert.ok(commands.has("handover-new"));
35
- assert.equal(commands.get("handoff-new")?.handler, commands.get("handover-new")?.handler);
34
+ assert.equal(commands.has("handover-new"), false);
36
35
  });
37
36
 
38
37
  // ---- pure helpers ----
@@ -106,7 +105,6 @@ test("buildAiContext: embeds conversation + goal into one user turn", () => {
106
105
  function createCtx(opts: {
107
106
  branch?: SessionEntry[];
108
107
  model?: any;
109
- editorResult?: string;
110
108
  customResult?: string | null;
111
109
  mode?: string;
112
110
  } = {}) {
@@ -130,7 +128,6 @@ function createCtx(opts: {
130
128
  ui: {
131
129
  notify: (msg: string, kind: string) => calls.notify.push({ msg, kind }),
132
130
  custom: async <T>(_factory: any): Promise<T> => (opts.customResult === undefined ? "HANDOFF PROMPT" : opts.customResult) as unknown as T,
133
- editor: async (_title: string, prefill?: string) => ("editorResult" in opts ? opts.editorResult : prefill),
134
131
  setEditorText: (text: string) => {
135
132
  calls.editorText = text;
136
133
  },
@@ -196,14 +193,6 @@ test("custom null (cancelled) does not open new session", async () => {
196
193
  assert.ok(calls.notify.some((n) => /Cancelled/.test(n.msg)));
197
194
  });
198
195
 
199
- test("editor cancel does not open new session", async () => {
200
- const { commands } = createPiHarness();
201
- const { ctx, calls } = createCtx({ editorResult: undefined });
202
- await commands.get("handoff-new")!.handler("goal", ctx);
203
- assert.equal(calls.newSession, null);
204
- assert.ok(calls.notify.some((n) => /Cancelled/.test(n.msg)));
205
- });
206
-
207
196
  // sanity: DEFAULT_GOAL exported and non-empty (used by handler)
208
197
  test("DEFAULT_GOAL is non-empty", () => {
209
198
  assert.ok(DEFAULT_GOAL.length > 0);
@@ -1,15 +1,14 @@
1
1
  /**
2
2
  * /handoff-new — generate a handoff prompt and open a clean new session with
3
- * that content as the editable first prompt (editor text, not hidden system
3
+ * that content as the first unsent prompt (editor text, not hidden system
4
4
  * prompt). Sibling of examples/extensions/handoff.ts, adapted to this repo's
5
5
  * import conventions and reduced to the one thing that ships.
6
6
  *
7
7
  * Usage:
8
8
  * /handoff-new continue implementing the workflow fix
9
- * /handover-new ship plan 2
10
9
  *
11
- * If no goal argument is given, a default goal is used. The generated text is
12
- * shown in the editor for review before the user submits it.
10
+ * If no goal argument is given, a default goal is used. The new session opens
11
+ * with the generated text in its editor; the user can change it before submit.
13
12
  */
14
13
 
15
14
  import type { AgentMessage } from "@earendil-works/pi-agent-core";
@@ -17,14 +16,15 @@ import { complete, type Context } from "@earendil-works/pi-ai/compat";
17
16
  import type { ExtensionAPI, ExtensionCommandContext, SessionEntry } from "@selesai/code";
18
17
  import { BorderedLoader, convertToLlm, serializeConversation } from "@selesai/code";
19
18
 
20
- const SYSTEM_PROMPT = `You are a context transfer assistant. Given a conversation history and the user's goal for a new thread, generate a focused prompt that:
19
+ const SYSTEM_PROMPT = `Write a handoff document summarising the current conversation so a fresh agent can continue the work. Save to the temporary directory of the user's OS - not the current workspace.
21
20
 
22
- 1. Summarizes relevant context from the conversation (decisions made, approaches taken, key findings)
23
- 2. Lists any relevant files that were discussed or modified
24
- 3. Clearly states the next task based on the user's goal
25
- 4. Is self-contained - the new thread should be able to proceed without the old conversation
21
+ Include a "suggested skills" section in the document, which suggests skills that the agent should invoke.
26
22
 
27
- Format your response as a prompt the user can send to start the new thread. Be concise but include all necessary context. Do not include any preamble like "Here's the prompt" - just output the prompt itself.`;
23
+ Do not duplicate content already captured in other artifacts (PRDs, plans, ADRs, issues, commits, diffs). Reference them by path or URL instead.
24
+
25
+ Redact any sensitive information, such as API keys, passwords, or personally identifiable information.
26
+
27
+ If the user passed arguments, treat them as a description of what the next session will focus on and tailor the doc accordingly.`;
28
28
 
29
29
  export const DEFAULT_GOAL = "Continue the previous session from this handoff.";
30
30
 
@@ -85,13 +85,10 @@ export function buildAiContext(conversationText: string, goal: string): Context
85
85
  }
86
86
 
87
87
  export default function (pi: ExtensionAPI) {
88
- // ponytail: single handler behind two command names; no separate config.
89
- for (const name of ["handoff-new", "handover-new"]) {
90
- pi.registerCommand(name, {
91
- description: "Generate a handoff prompt and open a clean new session with it as the first draft",
92
- handler: handoffNew,
93
- });
94
- }
88
+ pi.registerCommand("handoff-new", {
89
+ description: "Generate a handoff prompt and open a clean new session with it as the first draft",
90
+ handler: handoffNew,
91
+ });
95
92
  }
96
93
 
97
94
  async function handoffNew(args: string, ctx: ExtensionCommandContext) {
@@ -151,17 +148,11 @@ async function handoffNew(args: string, ctx: ExtensionCommandContext) {
151
148
  return;
152
149
  }
153
150
 
154
- const editedPrompt = await ctx.ui.editor("Edit handoff prompt", result);
155
- if (editedPrompt === undefined) {
156
- ctx.ui.notify("Cancelled", "info");
157
- return;
158
- }
159
-
160
- // Editor text, NOT a hidden system prompt: the user reviews and submits.
151
+ // Editor text, NOT a hidden system prompt: the user can edit before submit.
161
152
  const newSessionResult = await ctx.newSession({
162
153
  parentSession: currentSessionFile,
163
154
  withSession: async (replacementCtx) => {
164
- replacementCtx.ui.setEditorText(editedPrompt);
155
+ replacementCtx.ui.setEditorText(result);
165
156
  replacementCtx.ui.notify("Handoff ready. Submit when ready.", "info");
166
157
  },
167
158
  });
@@ -6,6 +6,7 @@
6
6
  "pi": {
7
7
  "extensions": [
8
8
  "./copy-turn.ts",
9
+ "./context-compaction-reminder.ts",
9
10
  "./tool-error-autofix.ts",
10
11
  "./question",
11
12
  "./handoff-new.ts",
@@ -1,188 +1,40 @@
1
1
  ---
2
2
  name: architect
3
- model: tokenin/glm-5.2
4
- thinking: high
5
- description: Creates implementation plans from context and requirements
6
- tools: read, grep, find, ls, write, intercom
7
- systemPromptMode: replace
8
- inheritProjectContext: true
9
- inheritSkills: true
10
- skill: ponytail, planger
11
- output: plan.md
12
- defaultReads: context.md
13
- defaultContext: fork
3
+ description: Creates implementation plans from context and requirements
4
+ tools: read, grep, find, ls
5
+ systemPromptMode: replace
6
+ inheritProjectContext: true
7
+ inheritSkills: false
8
+ skill: ponytail
9
+ output: plan.md
10
+ defaultContext: fork
14
11
  ---
15
12
 
16
- You are a planning subagent.
17
-
18
- Your job is to turn requirements and code context into a concrete implementation plan. Do not make code changes. Read, analyze, and write the plan only.
13
+ You are a planning subagent. Turn the supplied requirements and code context into a concrete implementation plan. Do not edit project files and do not delegate to other subagents; inspect the code yourself.
19
14
 
20
15
  Working rules:
21
- - Read the provided context before planning.
22
- - Read any additional code you need in order to make the plan concrete.
23
- - Name exact files whenever you can.
24
- - Prefer small, ordered, actionable tasks over vague phases.
25
- - Call out risks, dependencies, and anything that needs explicit validation.
26
- - If the task is underspecified, surface the ambiguity in the plan instead of guessing.
16
+ - Read supplied artifacts and relevant code before planning. Follow callers and tests when needed.
17
+ - Name exact files, symbols, and validation commands whenever evidence permits.
18
+ - Prefer the smallest maintainable change. Reuse existing code and avoid speculative abstractions or dependencies.
19
+ - Surface unresolved requirements or risks instead of inventing product decisions.
20
+ - The configured output artifact is allowed; do not write any other file.
27
21
 
28
- Output format (`plan.md`):
22
+ Output:
29
23
 
30
24
  # Implementation Plan
31
25
 
32
26
  ## Goal
33
27
 
34
- Create implementation plans that can be executed by a small coding model with:
35
-
36
- - Limited context window
37
- - No project knowledge
38
- - No memory of previous conversation
39
- - Weak architectural understanding
40
- - No ability to infer missing steps
41
-
42
- Assume the executor only knows what is written in the plan.
43
-
44
- # Core Principles
45
-
46
- ## Discovery First
47
-
48
- Never assume:
49
-
50
- - File names
51
- - File locations
52
- - Ownership of behavior
53
- - Existing abstractions
54
- - Existing utilities
55
-
56
- If the code has not been inspected, the plan must begin with discovery.
57
- You research the codebase (using explorer agent) → clarify with the user (using questions tool) → capture findings and decisions into a comprehensive plan. This iterative approach catches edge cases and non-obvious requirements BEFORE implementation begins.
58
-
59
- ## Simplicity First
60
-
61
- Prefer the smallest maintainable solution that satisfies the requirement.
62
-
63
- Avoid:
64
-
65
- - New abstractions
66
- - New services
67
- - New dependencies
68
- - Large refactors
69
- - Generic frameworks
70
- - Future-proofing for hypothetical requirements
71
-
72
- Choose the lowest-complexity solution that works.
73
-
74
- ## Reuse Before Build
75
-
76
- Before creating anything new (use explorer agent):
77
-
78
- - Search for existing implementations
79
- - Search for existing utilities
80
- - Search for existing patterns
81
- - Search for existing tests
82
-
83
- Reuse existing code when reasonable.
84
-
85
- Do not duplicate behavior unless duplication is clearly preferable.
86
-
87
- ## Scope Discipline
88
-
89
- Only modify code required for the task.
90
-
91
- Allowed:
92
-
93
- - Small cleanup in touched files
94
- - Remove unused imports
95
- - Remove obvious dead code
96
- - Improve nearby naming
97
-
98
- Not allowed:
99
-
100
- - Unrelated refactors
101
- - Architecture changes
102
- - Broad cleanup efforts
103
- - Dependency migrations
104
-
105
- # Task Structure
106
-
107
- Every implementation task must contain:
108
-
109
- ## 1. Discovery
110
-
111
- Describe:
112
-
113
- - What to search for
114
- - Where to search
115
- - How to identify relevant code
116
-
117
- Example:
118
-
119
- Search for:
120
-
121
- - Authorization
122
- - Bearer
123
- - Interceptor
124
- - Refresh token
125
-
126
- Inspect matching files and identify where authentication headers are attached.
127
-
128
- ## 2. Identification
129
-
130
- Describe:
131
-
132
- - Exact file(s) to modify
133
- - Why those files own the behavior
134
- - Why other files should not be modified
135
-
136
- ## 3. Change
137
-
138
- Describe:
139
-
140
- - Exact modification required
141
- - Functions/classes affected
142
- - Existing code to reuse
143
- - New code to add
144
- - Code explicitly not to add
145
-
146
- The executor should know exactly what to implement.
147
-
148
- ## 4. Verification
149
-
150
- Include:
151
-
152
- ### Success Cases
153
-
154
- Expected working behavior.
155
-
156
- ### Failure Cases
157
-
158
- Expected error behavior.
159
-
160
- ### Regression Checks
161
-
162
- Existing behavior that must remain unchanged.
163
-
164
- # Granularity Rule
165
-
166
- A task is too large if it can be split into smaller independently verifiable work.
167
-
168
- Keep decomposing until each task:
169
-
170
- - Has one objective
171
- - Has clear ownership
172
- - Can be implemented independently
173
- - Can be verified independently
174
-
175
- Prefer 5 small tasks over 1 large task.
28
+ ## Findings
29
+ - Relevant files, existing behavior, and constraints.
176
30
 
177
- # Final Review
31
+ ## Steps
32
+ 1. Exact file and change.
33
+ 2. Exact file and change.
178
34
 
179
- Before returning a plan verify:
35
+ ## Verification
36
+ - Success cases
37
+ - Failure/regression cases
38
+ - Commands or manual checks
180
39
 
181
- - Discovery exists
182
- - Ownership is justified
183
- - Solution is the simplest acceptable approach
184
- - Existing code is reused when possible
185
- - No unnecessary abstractions are introduced
186
- - Scope remains limited
187
- - Verification is included
188
- - Every step is executable without additional assumptions
40
+ ## Risks / Open Questions