@oh-my-pi/pi-coding-agent 9.3.1 → 9.6.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 (88) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/examples/hooks/snake.ts +5 -5
  3. package/package.json +9 -8
  4. package/src/capability/index.ts +7 -9
  5. package/src/cli/config-cli.ts +86 -73
  6. package/src/cli/update-cli.ts +45 -3
  7. package/src/commit/agentic/agent.ts +4 -4
  8. package/src/commit/agentic/index.ts +6 -5
  9. package/src/commit/agentic/tools/analyze-file.ts +5 -7
  10. package/src/commit/agentic/tools/index.ts +3 -3
  11. package/src/commit/model-selection.ts +13 -17
  12. package/src/commit/pipeline.ts +5 -5
  13. package/src/config/model-registry.ts +7 -0
  14. package/src/config/settings-schema.ts +836 -0
  15. package/src/config/settings.ts +702 -0
  16. package/src/discovery/helpers.ts +55 -11
  17. package/src/exa/index.ts +1 -1
  18. package/src/exec/bash-executor.ts +13 -13
  19. package/src/exec/shell-session.ts +15 -3
  20. package/src/export/ttsr.ts +1 -1
  21. package/src/extensibility/skills.ts +40 -9
  22. package/src/index.ts +2 -10
  23. package/src/ipy/gateway-coordinator.ts +5 -159
  24. package/src/ipy/kernel.ts +6 -171
  25. package/src/ipy/runtime.ts +198 -0
  26. package/src/lsp/client.ts +14 -1
  27. package/src/lsp/defaults.json +0 -6
  28. package/src/lsp/index.ts +1 -1
  29. package/src/lsp/types.ts +2 -0
  30. package/src/main.ts +26 -48
  31. package/src/modes/components/armin.ts +7 -7
  32. package/src/modes/components/extensions/extension-dashboard.ts +33 -13
  33. package/src/modes/components/extensions/extension-list.ts +2 -2
  34. package/src/modes/components/footer.ts +5 -5
  35. package/src/modes/components/history-search.ts +2 -1
  36. package/src/modes/components/hook-selector.ts +2 -2
  37. package/src/modes/components/index.ts +1 -1
  38. package/src/modes/components/model-selector.ts +7 -7
  39. package/src/modes/components/session-selector.ts +2 -1
  40. package/src/modes/components/settings-defs.ts +210 -915
  41. package/src/modes/components/settings-selector.ts +80 -106
  42. package/src/modes/components/status-line/types.ts +2 -8
  43. package/src/modes/components/status-line-segment-editor.ts +4 -4
  44. package/src/modes/components/status-line.ts +28 -5
  45. package/src/modes/components/welcome.ts +3 -3
  46. package/src/modes/controllers/command-controller.ts +2 -2
  47. package/src/modes/controllers/event-controller.ts +9 -8
  48. package/src/modes/controllers/input-controller.ts +19 -15
  49. package/src/modes/controllers/selector-controller.ts +30 -14
  50. package/src/modes/interactive-mode.ts +10 -10
  51. package/src/modes/rpc/rpc-mode.ts +10 -0
  52. package/src/modes/rpc/rpc-types.ts +3 -0
  53. package/src/modes/types.ts +2 -2
  54. package/src/modes/utils/ui-helpers.ts +4 -3
  55. package/src/patch/index.ts +7 -7
  56. package/src/patch/normalize.ts +3 -1
  57. package/src/prompts/system/plan-mode-active.md +5 -4
  58. package/src/prompts/system/system-prompt.md +0 -1
  59. package/src/prompts/tools/bash.md +12 -2
  60. package/src/prompts/tools/task.md +180 -73
  61. package/src/sdk.ts +38 -61
  62. package/src/session/agent-session.ts +66 -55
  63. package/src/session/agent-storage.ts +1 -1
  64. package/src/session/session-manager.ts +10 -10
  65. package/src/system-prompt.ts +2 -2
  66. package/src/task/executor.ts +9 -9
  67. package/src/task/index.ts +2 -2
  68. package/src/tools/ask.ts +5 -6
  69. package/src/tools/bash-interceptor.ts +39 -1
  70. package/src/tools/bash-normalize.ts +126 -0
  71. package/src/tools/bash.ts +31 -5
  72. package/src/tools/find.ts +51 -33
  73. package/src/tools/gemini-image.ts +7 -8
  74. package/src/tools/index.ts +5 -23
  75. package/src/tools/plan-mode-guard.ts +1 -6
  76. package/src/tools/python.ts +29 -4
  77. package/src/tools/read.ts +2 -2
  78. package/src/tools/write.ts +2 -2
  79. package/src/tui/output-block.ts +2 -2
  80. package/src/tui/utils.ts +2 -2
  81. package/src/utils/ignore-files.ts +119 -0
  82. package/src/web/search/auth.ts +6 -58
  83. package/src/web/search/index.ts +2 -6
  84. package/src/web/search/providers/anthropic.ts +6 -6
  85. package/src/web/search/providers/exa.ts +2 -62
  86. package/src/web/search/providers/perplexity.ts +7 -53
  87. package/examples/sdk/10-settings.ts +0 -37
  88. package/src/config/settings-manager.ts +0 -2015
@@ -3,6 +3,7 @@ import type { OAuthProvider } from "@oh-my-pi/pi-ai";
3
3
  import type { Component } from "@oh-my-pi/pi-tui";
4
4
  import { Input, Loader, Spacer, Text } from "@oh-my-pi/pi-tui";
5
5
  import { getAgentDbPath } from "../../config";
6
+ import { settings } from "../../config/settings";
6
7
  import { DebugSelectorComponent } from "../../debug";
7
8
  import { disableProvider, enableProvider } from "../../discovery";
8
9
  import { AssistantMessageComponent } from "../../modes/components/assistant-message";
@@ -51,7 +52,6 @@ export class SelectorController {
51
52
  getAvailableThemes().then(availableThemes => {
52
53
  this.showSelector(done => {
53
54
  const selector = new SettingsSelectorComponent(
54
- this.ctx.settingsManager,
55
55
  {
56
56
  availableThinkingLevels: this.ctx.session.getAvailableThinkingLevels(),
57
57
  thinkingLevel: this.ctx.session.thinkingLevel,
@@ -68,10 +68,16 @@ export class SelectorController {
68
68
  }
69
69
  });
70
70
  },
71
- onStatusLinePreview: settings => {
71
+ onStatusLinePreview: previewSettings => {
72
72
  // Update status line with preview settings
73
- const currentSettings = this.ctx.settingsManager.getStatusLineSettings();
74
- this.ctx.statusLine.updateSettings({ ...currentSettings, ...settings });
73
+ this.ctx.statusLine.updateSettings({
74
+ preset: settings.get("statusLine.preset"),
75
+ leftSegments: settings.get("statusLine.leftSegments"),
76
+ rightSegments: settings.get("statusLine.rightSegments"),
77
+ separator: settings.get("statusLine.separator"),
78
+ showHookStatus: settings.get("statusLine.showHookStatus"),
79
+ ...previewSettings,
80
+ });
75
81
  this.ctx.updateEditorTopBorder();
76
82
  this.ctx.ui.requestRender();
77
83
  },
@@ -86,7 +92,13 @@ export class SelectorController {
86
92
  onCancel: () => {
87
93
  done();
88
94
  // Restore status line to saved settings
89
- this.ctx.statusLine.updateSettings(this.ctx.settingsManager.getStatusLineSettings());
95
+ this.ctx.statusLine.updateSettings({
96
+ preset: settings.get("statusLine.preset"),
97
+ leftSegments: settings.get("statusLine.leftSegments"),
98
+ rightSegments: settings.get("statusLine.rightSegments"),
99
+ separator: settings.get("statusLine.separator"),
100
+ showHookStatus: settings.get("statusLine.showHookStatus"),
101
+ });
90
102
  this.ctx.updateEditorTopBorder();
91
103
  this.ctx.ui.requestRender();
92
104
  },
@@ -123,11 +135,7 @@ export class SelectorController {
123
135
  * Replaces /status with a unified view of all providers and extensions.
124
136
  */
125
137
  async showExtensionsDashboard(): Promise<void> {
126
- const dashboard = await ExtensionDashboard.create(
127
- process.cwd(),
128
- this.ctx.settingsManager,
129
- this.ctx.ui.terminal.rows,
130
- );
138
+ const dashboard = await ExtensionDashboard.create(process.cwd(), this.ctx.settings, this.ctx.ui.terminal.rows);
131
139
  this.showSelector(done => {
132
140
  dashboard.onClose = () => {
133
141
  done();
@@ -142,7 +150,7 @@ export class SelectorController {
142
150
  * Most settings are saved directly via SettingsManager in the definitions.
143
151
  * This handles side effects and session-specific settings.
144
152
  */
145
- handleSettingChange(id: string, value: string | boolean): void {
153
+ handleSettingChange(id: string, value: unknown): void {
146
154
  // Discovery provider toggles
147
155
  if (id.startsWith("discovery.")) {
148
156
  const providerId = id.replace("discovery.", "");
@@ -232,7 +240,15 @@ export class SelectorController {
232
240
  case "statusLineGitShowUntracked":
233
241
  case "statusLineTimeFormat":
234
242
  case "statusLineTimeShowSeconds": {
235
- this.ctx.statusLine.updateSettings(this.ctx.settingsManager.getStatusLineSettings());
243
+ const statusLineSettings = {
244
+ preset: settings.get("statusLine.preset"),
245
+ leftSegments: settings.get("statusLine.leftSegments"),
246
+ rightSegments: settings.get("statusLine.rightSegments"),
247
+ separator: settings.get("statusLine.separator"),
248
+ showHookStatus: settings.get("statusLine.showHookStatus"),
249
+ segmentOptions: settings.get("statusLine.segmentOptions"),
250
+ };
251
+ this.ctx.statusLine.updateSettings(statusLineSettings);
236
252
  this.ctx.updateEditorTopBorder();
237
253
  this.ctx.ui.requestRender();
238
254
  break;
@@ -256,7 +272,7 @@ export class SelectorController {
256
272
  const selector = new ModelSelectorComponent(
257
273
  this.ctx.ui,
258
274
  this.ctx.session.model,
259
- this.ctx.settingsManager,
275
+ this.ctx.settings,
260
276
  this.ctx.session.modelRegistry,
261
277
  this.ctx.session.scopedModels,
262
278
  async (model, role) => {
@@ -369,7 +385,7 @@ export class SelectorController {
369
385
  let wantsSummary = false;
370
386
  let customInstructions: string | undefined;
371
387
 
372
- const branchSummariesEnabled = this.ctx.settingsManager.getBranchSummaryEnabled();
388
+ const branchSummariesEnabled = settings.get("branchSummary.enabled");
373
389
 
374
390
  while (branchSummariesEnabled) {
375
391
  const summaryChoice = await this.ctx.showHookSelector("Summarize branch?", [
@@ -19,7 +19,7 @@ import { isEnoent, logger, postmortem } from "@oh-my-pi/pi-utils";
19
19
  import chalk from "chalk";
20
20
  import { KeybindingsManager } from "../config/keybindings";
21
21
  import { renderPromptTemplate } from "../config/prompt-templates";
22
- import type { SettingsManager } from "../config/settings-manager";
22
+ import { type Settings, settings } from "../config/settings";
23
23
  import type { ExtensionUIContext, ExtensionUIDialogOptions } from "../extensibility/extensions";
24
24
  import type { CompactOptions } from "../extensibility/extensions/types";
25
25
  import { loadSlashCommands } from "../extensibility/slash-commands";
@@ -72,7 +72,7 @@ export interface InteractiveModeOptions {
72
72
  export class InteractiveMode implements InteractiveModeContext {
73
73
  public session: AgentSession;
74
74
  public sessionManager: SessionManager;
75
- public settingsManager: SettingsManager;
75
+ public settings: Settings;
76
76
  public keybindings: KeybindingsManager;
77
77
  public agent: AgentSession["agent"];
78
78
  public historyStorage?: HistoryStorage;
@@ -159,7 +159,7 @@ export class InteractiveMode implements InteractiveModeContext {
159
159
  ) {
160
160
  this.session = session;
161
161
  this.sessionManager = session.sessionManager;
162
- this.settingsManager = session.settingsManager;
162
+ this.settings = session.settings;
163
163
  this.keybindings = KeybindingsManager.inMemory();
164
164
  this.agent = session.agent;
165
165
  this.version = version;
@@ -168,7 +168,7 @@ export class InteractiveMode implements InteractiveModeContext {
168
168
  this.lspServers = lspServers;
169
169
  this.mcpManager = mcpManager;
170
170
 
171
- this.ui = new TUI(new ProcessTerminal(), this.settingsManager.getShowHardwareCursor());
171
+ this.ui = new TUI(new ProcessTerminal(), settings.get("showHardwareCursor"));
172
172
  setMermaidRenderCallback(() => this.ui.requestRender());
173
173
  this.chatContainer = new Container();
174
174
  this.pendingMessagesContainer = new Container();
@@ -193,7 +193,7 @@ export class InteractiveMode implements InteractiveModeContext {
193
193
  this.statusLine = new StatusLineComponent(session);
194
194
  this.statusLine.setAutoCompactEnabled(session.autoCompactionEnabled);
195
195
 
196
- this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
196
+ this.hideThinkingBlock = settings.get("hideThinkingBlock");
197
197
 
198
198
  // Define slash commands for autocomplete
199
199
  const slashCommands: SlashCommand[] = [
@@ -240,7 +240,7 @@ export class InteractiveMode implements InteractiveModeContext {
240
240
 
241
241
  // Build skill commands from session.skills (if enabled)
242
242
  const skillCommandList: SlashCommand[] = [];
243
- if (this.settingsManager.getEnableSkillCommands?.()) {
243
+ if (settings.get("skills.enableSkillCommands")) {
244
244
  for (const skill of this.session.skills) {
245
245
  const commandName = `skill:${skill.name}`;
246
246
  this.skillCommands.set(commandName, skill.filePath);
@@ -300,7 +300,7 @@ export class InteractiveMode implements InteractiveModeContext {
300
300
  fileTypes: s.fileTypes,
301
301
  })) ?? [];
302
302
 
303
- const startupQuiet = this.settingsManager.getStartupQuiet();
303
+ const startupQuiet = settings.get("startup.quiet");
304
304
 
305
305
  if (!startupQuiet) {
306
306
  // Add welcome header
@@ -314,7 +314,7 @@ export class InteractiveMode implements InteractiveModeContext {
314
314
  // Add changelog if provided
315
315
  if (this.changelogMarkdown) {
316
316
  this.ui.addChild(new DynamicBorder());
317
- if (this.settingsManager.getCollapseChangelog()) {
317
+ if (settings.get("collapseChangelog")) {
318
318
  const versionMatch = this.changelogMarkdown.match(/##\s+\[?(\d+\.\d+\.\d+)\]?/);
319
319
  const latestVersion = versionMatch ? versionMatch[1] : this.version;
320
320
  const condensedText = `Updated to v${latestVersion}. Use ${theme.bold("/changelog")} to view full changelog.`;
@@ -330,7 +330,7 @@ export class InteractiveMode implements InteractiveModeContext {
330
330
  }
331
331
 
332
332
  // Set terminal title if session already has one (resumed session)
333
- const existingTitle = this.sessionManager.getSessionTitle();
333
+ const existingTitle = this.sessionManager.getSessionName();
334
334
  if (existingTitle) {
335
335
  setTerminalTitle(`pi: ${existingTitle}`);
336
336
  }
@@ -494,7 +494,7 @@ export class InteractiveMode implements InteractiveModeContext {
494
494
  private resolvePlanFilePath(planFilePath: string): string {
495
495
  if (planFilePath.startsWith("plan://")) {
496
496
  return resolvePlanUrlToPath(planFilePath, {
497
- getPlansDirectory: this.settingsManager.getPlansDirectory.bind(this.settingsManager),
497
+ getPlansDirectory: () => this.settings.getPlansDirectory(),
498
498
  cwd: this.sessionManager.getCwd(),
499
499
  });
500
500
  }
@@ -448,6 +448,7 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
448
448
  interruptMode: session.interruptMode,
449
449
  sessionFile: session.sessionFile,
450
450
  sessionId: session.sessionId,
451
+ sessionName: session.sessionName,
451
452
  autoCompactionEnabled: session.autoCompactionEnabled,
452
453
  messageCount: session.messages.length,
453
454
  queuedMessageCount: session.queuedMessageCount,
@@ -594,6 +595,15 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
594
595
  return success(id, "get_last_assistant_text", { text });
595
596
  }
596
597
 
598
+ case "set_session_name": {
599
+ const name = command.name.trim();
600
+ if (!name) {
601
+ return error(id, "set_session_name", "Session name cannot be empty");
602
+ }
603
+ session.setSessionName(name);
604
+ return success(id, "set_session_name");
605
+ }
606
+
597
607
  // =================================================================
598
608
  // Messages
599
609
  // =================================================================
@@ -58,6 +58,7 @@ export type RpcCommand =
58
58
  | { id?: string; type: "branch"; entryId: string }
59
59
  | { id?: string; type: "get_branch_messages" }
60
60
  | { id?: string; type: "get_last_assistant_text" }
61
+ | { id?: string; type: "set_session_name"; name: string }
61
62
 
62
63
  // Messages
63
64
  | { id?: string; type: "get_messages" };
@@ -76,6 +77,7 @@ export interface RpcSessionState {
76
77
  interruptMode: "immediate" | "wait";
77
78
  sessionFile?: string;
78
79
  sessionId: string;
80
+ sessionName?: string;
79
81
  autoCompactionEnabled: boolean;
80
82
  messageCount: number;
81
83
  queuedMessageCount: number;
@@ -166,6 +168,7 @@ export type RpcResponse =
166
168
  success: true;
167
169
  data: { text: string | null };
168
170
  }
171
+ | { id?: string; type: "response"; command: "set_session_name"; success: true }
169
172
 
170
173
  // Messages
171
174
  | { id?: string; type: "response"; command: "get_messages"; success: true; data: { messages: AgentMessage[] } }
@@ -2,7 +2,7 @@ import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
2
2
  import type { AssistantMessage, ImageContent, Message, UsageReport } from "@oh-my-pi/pi-ai";
3
3
  import type { Component, Container, Loader, Spacer, Text, TUI } from "@oh-my-pi/pi-tui";
4
4
  import type { KeybindingsManager } from "../config/keybindings";
5
- import type { SettingsManager } from "../config/settings-manager";
5
+ import type { Settings } from "../config/settings";
6
6
  import type { ExtensionUIContext, ExtensionUIDialogOptions } from "../extensibility/extensions";
7
7
  import type { CompactOptions } from "../extensibility/extensions/types";
8
8
  import type { MCPManager } from "../mcp";
@@ -46,7 +46,7 @@ export interface InteractiveModeContext {
46
46
  // Session access
47
47
  session: AgentSession;
48
48
  sessionManager: SessionManager;
49
- settingsManager: SettingsManager;
49
+ settings: Settings;
50
50
  keybindings: KeybindingsManager;
51
51
  agent: AgentSession["agent"];
52
52
  historyStorage?: HistoryStorage;
@@ -1,6 +1,7 @@
1
1
  import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
2
2
  import type { AssistantMessage, Message } from "@oh-my-pi/pi-ai";
3
3
  import { Spacer, Text, TruncatedText } from "@oh-my-pi/pi-tui";
4
+ import { settings } from "../../config/settings";
4
5
  import { AssistantMessageComponent } from "../../modes/components/assistant-message";
5
6
  import { BashExecutionComponent } from "../../modes/components/bash-execution";
6
7
  import { BranchSummaryMessageComponent } from "../../modes/components/branch-summary-message";
@@ -224,9 +225,9 @@ export class UiHelpers {
224
225
  content.name,
225
226
  content.arguments,
226
227
  {
227
- showImages: this.ctx.settingsManager.getShowImages(),
228
- editFuzzyThreshold: this.ctx.settingsManager.getEditFuzzyThreshold(),
229
- editAllowFuzzy: this.ctx.settingsManager.getEditFuzzyMatch(),
228
+ showImages: settings.get("terminal.showImages"),
229
+ editFuzzyThreshold: settings.get("edit.fuzzyThreshold"),
230
+ editAllowFuzzy: settings.get("edit.fuzzyMatch"),
230
231
  },
231
232
  tool,
232
233
  this.ctx.ui,
@@ -26,7 +26,7 @@ import { outputMeta } from "../tools/output-meta";
26
26
  import { enforcePlanModeWrite, resolvePlanPath } from "../tools/plan-mode-guard";
27
27
  import { applyPatch } from "./applicator";
28
28
  import { generateDiffString, generateUnifiedDiffString, replaceText } from "./diff";
29
- import { DEFAULT_FUZZY_THRESHOLD, findMatch } from "./fuzzy";
29
+ import { findMatch } from "./fuzzy";
30
30
  import { detectLineEnding, normalizeToLF, restoreLineEndings, stripBom } from "./normalize";
31
31
  import { buildNormativeUpdateInput } from "./normative";
32
32
  import { type EditToolDetails, getLspBatchRequest } from "./shared";
@@ -233,14 +233,14 @@ export class EditTool implements AgentTool<TInput> {
233
233
  this.allowFuzzy = false;
234
234
  break;
235
235
  case "auto":
236
- this.allowFuzzy = session.settings?.getEditFuzzyMatch() ?? true;
236
+ this.allowFuzzy = session.settings.get("edit.fuzzyMatch");
237
237
  break;
238
238
  default:
239
239
  throw new Error(`Invalid OMP_EDIT_FUZZY: ${editFuzzy}`);
240
240
  }
241
241
  switch (editFuzzyThreshold) {
242
242
  case "auto":
243
- this.fuzzyThreshold = session.settings?.getEditFuzzyThreshold?.() ?? DEFAULT_FUZZY_THRESHOLD;
243
+ this.fuzzyThreshold = session.settings.get("edit.fuzzyThreshold");
244
244
  break;
245
245
  default:
246
246
  this.fuzzyThreshold = parseFloat(editFuzzyThreshold);
@@ -251,8 +251,8 @@ export class EditTool implements AgentTool<TInput> {
251
251
  }
252
252
 
253
253
  const enableLsp = session.enableLsp ?? true;
254
- const enableDiagnostics = enableLsp ? (session.settings?.getLspDiagnosticsOnEdit() ?? false) : false;
255
- const enableFormat = enableLsp ? (session.settings?.getLspFormatOnWrite() ?? true) : false;
254
+ const enableDiagnostics = enableLsp && session.settings.get("lsp.diagnosticsOnEdit");
255
+ const enableFormat = enableLsp && session.settings.get("lsp.formatOnWrite");
256
256
  this.writethrough = enableLsp
257
257
  ? createLspWritethrough(session.cwd, { enableFormat, enableDiagnostics })
258
258
  : writethroughNoop;
@@ -268,11 +268,11 @@ export class EditTool implements AgentTool<TInput> {
268
268
 
269
269
  // Auto mode: check model-specific settings
270
270
  const activeModel = this.session.getActiveModelString?.();
271
- const modelVariant = this.session.settings?.getEditVariantForModel?.(activeModel);
271
+ const modelVariant = this.session.settings.getEditVariantForModel(activeModel);
272
272
  if (modelVariant === "replace") return false;
273
273
  if (modelVariant === "patch") return true;
274
274
 
275
- return this.session.settings?.getEditPatchMode?.() ?? true;
275
+ return this.session.settings.get("edit.patchMode");
276
276
  }
277
277
 
278
278
  /**
@@ -4,6 +4,8 @@
4
4
  * Handles line endings, BOM, whitespace, and Unicode normalization.
5
5
  */
6
6
 
7
+ import { padding } from "@oh-my-pi/pi-tui";
8
+
7
9
  // ═══════════════════════════════════════════════════════════════════════════
8
10
  // Line Ending Utilities
9
11
  // ═══════════════════════════════════════════════════════════════════════════
@@ -194,7 +196,7 @@ export function convertLeadingTabsToSpaces(text: string, spacesPerTab: number):
194
196
  if (trimmed.length === 0) return line;
195
197
  const leading = getLeadingWhitespace(line);
196
198
  if (!leading.includes("\t") || leading.includes(" ")) return line;
197
- const converted = " ".repeat(leading.length * spacesPerTab);
199
+ const converted = padding(leading.length * spacesPerTab);
198
200
  return converted + trimmed;
199
201
  })
200
202
  .join("\n");
@@ -18,6 +18,7 @@ Create your plan at `{{planFilePath}}`.
18
18
  {{/if}}
19
19
 
20
20
  The plan file is the ONLY file you may write or edit.
21
+ Use `{{editToolName}}` for incremental updates; use `{{writeToolName}}` only when creating or fully replacing the plan.
21
22
 
22
23
  <important>
23
24
  Plan execution runs in a fresh context (session cleared). Make the plan file self-contained: include any requirements, decisions, key findings, and remaining todos needed to continue without prior session history.
@@ -55,8 +56,8 @@ Use `ask` to clarify:
55
56
  - Preferences for UI/UX, performance, edge cases
56
57
 
57
58
  Batch questions. Do not ask what you can answer by exploring.
58
- ### 3. Write Incrementally
59
- Update the plan file as you learn. Do not wait until the end.
59
+ ### 3. Update Incrementally
60
+ Use `{{editToolName}}` to update the plan file as you learn. Do not wait until the end.
60
61
  ### 4. Calibrate
61
62
  - Large unspecified task → multiple interview rounds
62
63
  - Smaller task → fewer or no questions
@@ -86,8 +87,8 @@ Draft approach based on exploration. Consider trade-offs briefly, then choose.
86
87
  ### Phase 3: Review
87
88
  Read critical files. Verify plan matches original request. Use `ask` to clarify remaining questions.
88
89
 
89
- ### Phase 4: Write Plan
90
- Write to `{{planFilePath}}`:
90
+ ### Phase 4: Update Plan
91
+ Update `{{planFilePath}}` (use `{{editToolName}}` for changes, `{{writeToolName}}` only if creating from scratch):
91
92
  - Recommended approach only
92
93
  - Paths of critical files to modify
93
94
  - Verification section
@@ -330,7 +330,6 @@ Write what you can defend.
330
330
  {{#if isCoordinator}}
331
331
  {{#has tools "task"}}
332
332
  <critical id="coordinator">
333
- As the coordinator, **ALWAYS prefer** delegation via Task tool for all SUBSTANTIAL work.
334
333
  Your context window is limited—especially the output. Work in discrete steps and run each step using Task tool. Avoid putting substantial work in the main context when possible. Run multiple tasks in parallel whenever possible.
335
334
 
336
335
  ## Triggers requiring Task tool
@@ -12,7 +12,7 @@ Executes a bash command in a shell session for terminal operations like git, bun
12
12
 
13
13
  <output>
14
14
  Returns stdout, stderr, and exit code from command execution.
15
- - Output truncated after 50KB or 2000 lines (whichever comes first); use `| head -n 50` for large output
15
+ - Output truncated after 50KB or 2000 lines (whichever comes first); use `head` parameter to limit output
16
16
  - If output is truncated, full output is stored under $ARTIFACTS and referenced as `artifact://<id>` in metadata
17
17
  - Exit codes shown on non-zero exit; stderr captured
18
18
  </output>
@@ -24,4 +24,14 @@ Do NOT use Bash for these operations—specialized tools exist:
24
24
  - Finding files by pattern → Find tool
25
25
  - Content-addressed edits → Edit tool
26
26
  - Writing new files → Write tool
27
- </critical>
27
+ </critical>
28
+
29
+ <avoid>
30
+ Do NOT pipe through `head` or `tail`—use the `head` and `tail` parameters instead:
31
+ - `command | head -n 50` → use `head: 50` parameter
32
+ - `command | tail -n 100` → use `tail: 100` parameter
33
+
34
+ The pipe pattern breaks streaming output and prevents artifact storage.
35
+
36
+ Do NOT use `2>&1`—stdout and stderr are already merged.
37
+ </avoid>