@oh-my-pi/pi-coding-agent 14.3.0 → 14.4.1

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 (120) hide show
  1. package/CHANGELOG.md +98 -1
  2. package/package.json +7 -7
  3. package/src/autoresearch/prompt.md +1 -1
  4. package/src/commit/agentic/prompts/analyze-file.md +1 -1
  5. package/src/config/model-registry.ts +67 -15
  6. package/src/config/prompt-templates.ts +5 -5
  7. package/src/config/settings-schema.ts +4 -4
  8. package/src/cursor.ts +3 -8
  9. package/src/discovery/helpers.ts +3 -3
  10. package/src/edit/diff.ts +50 -47
  11. package/src/edit/index.ts +86 -57
  12. package/src/edit/line-hash.ts +743 -24
  13. package/src/edit/modes/apply-patch.ts +0 -9
  14. package/src/edit/modes/atom.ts +893 -0
  15. package/src/edit/modes/chunk.ts +14 -24
  16. package/src/edit/modes/hashline.ts +193 -146
  17. package/src/edit/modes/patch.ts +5 -9
  18. package/src/edit/modes/replace.ts +6 -11
  19. package/src/edit/renderer.ts +14 -10
  20. package/src/edit/streaming.ts +50 -16
  21. package/src/exec/bash-executor.ts +2 -4
  22. package/src/export/html/template.generated.ts +1 -1
  23. package/src/export/html/template.js +4 -12
  24. package/src/extensibility/custom-tools/types.ts +2 -0
  25. package/src/extensibility/custom-tools/wrapper.ts +2 -1
  26. package/src/internal-urls/docs-index.generated.ts +2 -2
  27. package/src/lsp/defaults.json +142 -652
  28. package/src/lsp/index.ts +1 -1
  29. package/src/mcp/render.ts +1 -8
  30. package/src/modes/components/assistant-message.ts +4 -0
  31. package/src/modes/components/diff.ts +23 -14
  32. package/src/modes/components/footer.ts +21 -16
  33. package/src/modes/components/session-selector.ts +3 -3
  34. package/src/modes/components/settings-defs.ts +6 -1
  35. package/src/modes/components/todo-reminder.ts +1 -8
  36. package/src/modes/components/tool-execution.ts +1 -4
  37. package/src/modes/controllers/selector-controller.ts +1 -1
  38. package/src/modes/print-mode.ts +8 -0
  39. package/src/prompts/agents/librarian.md +1 -1
  40. package/src/prompts/agents/reviewer.md +4 -4
  41. package/src/prompts/ci-green-request.md +1 -1
  42. package/src/prompts/review-request.md +1 -1
  43. package/src/prompts/system/subagent-system-prompt.md +3 -3
  44. package/src/prompts/system/subagent-yield-reminder.md +11 -0
  45. package/src/prompts/system/system-prompt.md +3 -0
  46. package/src/prompts/tools/ask.md +3 -2
  47. package/src/prompts/tools/ast-edit.md +16 -20
  48. package/src/prompts/tools/ast-grep.md +19 -24
  49. package/src/prompts/tools/atom.md +87 -0
  50. package/src/prompts/tools/chunk-edit.md +37 -161
  51. package/src/prompts/tools/debug.md +4 -5
  52. package/src/prompts/tools/exit-plan-mode.md +4 -5
  53. package/src/prompts/tools/find.md +4 -8
  54. package/src/prompts/tools/github.md +18 -0
  55. package/src/prompts/tools/grep.md +4 -5
  56. package/src/prompts/tools/hashline.md +22 -89
  57. package/src/prompts/tools/{gemini-image.md → image-gen.md} +1 -1
  58. package/src/prompts/tools/inspect-image.md +6 -6
  59. package/src/prompts/tools/lsp.md +1 -1
  60. package/src/prompts/tools/patch.md +12 -19
  61. package/src/prompts/tools/python.md +3 -2
  62. package/src/prompts/tools/read-chunk.md +2 -3
  63. package/src/prompts/tools/read.md +2 -2
  64. package/src/prompts/tools/ssh.md +8 -17
  65. package/src/prompts/tools/todo-write.md +54 -41
  66. package/src/sdk.ts +14 -9
  67. package/src/session/agent-session.ts +25 -2
  68. package/src/session/session-manager.ts +4 -1
  69. package/src/task/executor.ts +43 -48
  70. package/src/task/render.ts +11 -13
  71. package/src/tools/ask.ts +7 -7
  72. package/src/tools/ast-edit.ts +45 -41
  73. package/src/tools/ast-grep.ts +77 -85
  74. package/src/tools/bash.ts +8 -9
  75. package/src/tools/browser.ts +32 -30
  76. package/src/tools/calculator.ts +4 -4
  77. package/src/tools/cancel-job.ts +1 -1
  78. package/src/tools/checkpoint.ts +2 -2
  79. package/src/tools/debug.ts +41 -37
  80. package/src/tools/exit-plan-mode.ts +1 -1
  81. package/src/tools/find.ts +4 -4
  82. package/src/tools/gh-renderer.ts +12 -4
  83. package/src/tools/gh.ts +509 -697
  84. package/src/tools/grep.ts +116 -131
  85. package/src/tools/{gemini-image.ts → image-gen.ts} +459 -60
  86. package/src/tools/index.ts +14 -32
  87. package/src/tools/inspect-image.ts +3 -3
  88. package/src/tools/json-tree.ts +114 -114
  89. package/src/tools/match-line-format.ts +8 -7
  90. package/src/tools/notebook.ts +8 -7
  91. package/src/tools/poll-tool.ts +2 -1
  92. package/src/tools/python.ts +9 -23
  93. package/src/tools/read.ts +32 -25
  94. package/src/tools/render-mermaid.ts +1 -1
  95. package/src/tools/render-utils.ts +18 -0
  96. package/src/tools/renderers.ts +2 -2
  97. package/src/tools/report-tool-issue.ts +3 -2
  98. package/src/tools/resolve.ts +1 -1
  99. package/src/tools/review.ts +12 -10
  100. package/src/tools/search-tool-bm25.ts +2 -4
  101. package/src/tools/ssh.ts +4 -4
  102. package/src/tools/todo-write.ts +172 -147
  103. package/src/tools/vim.ts +14 -15
  104. package/src/tools/write.ts +4 -4
  105. package/src/tools/{submit-result.ts → yield.ts} +11 -13
  106. package/src/utils/edit-mode.ts +2 -1
  107. package/src/utils/file-display-mode.ts +10 -5
  108. package/src/utils/git.ts +9 -5
  109. package/src/utils/shell-snapshot.ts +2 -3
  110. package/src/vim/render.ts +4 -4
  111. package/src/prompts/system/subagent-submit-reminder.md +0 -11
  112. package/src/prompts/tools/gh-issue-view.md +0 -11
  113. package/src/prompts/tools/gh-pr-checkout.md +0 -12
  114. package/src/prompts/tools/gh-pr-diff.md +0 -12
  115. package/src/prompts/tools/gh-pr-push.md +0 -12
  116. package/src/prompts/tools/gh-pr-view.md +0 -11
  117. package/src/prompts/tools/gh-repo-view.md +0 -11
  118. package/src/prompts/tools/gh-run-watch.md +0 -12
  119. package/src/prompts/tools/gh-search-issues.md +0 -11
  120. package/src/prompts/tools/gh-search-prs.md +0 -11
package/src/lsp/index.ts CHANGED
@@ -1115,11 +1115,11 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
1115
1115
  readonly label = "LSP";
1116
1116
  readonly description: string;
1117
1117
  readonly parameters = lspSchema;
1118
- readonly strict = true;
1119
1118
  readonly renderCall = renderCall;
1120
1119
  readonly renderResult = renderResult;
1121
1120
  readonly mergeCallAndResult = true;
1122
1121
  readonly inline = true;
1122
+ readonly strict = true;
1123
1123
 
1124
1124
  constructor(private readonly session: ToolSession) {
1125
1125
  this.description = prompt.render(lspDescription);
package/src/mcp/render.ts CHANGED
@@ -17,7 +17,6 @@ import {
17
17
  JSON_TREE_SCALAR_LEN_COLLAPSED,
18
18
  JSON_TREE_SCALAR_LEN_EXPANDED,
19
19
  renderJsonTreeLines,
20
- stripInternalArgs,
21
20
  } from "../tools/json-tree";
22
21
  import { formatExpandHint, truncateToWidth } from "../tools/render-utils";
23
22
  import { renderStatusLine } from "../tui";
@@ -58,13 +57,7 @@ export function renderMCPResult(
58
57
  lines.push(`${theme.fg("dim", "Args")}`);
59
58
  const maxDepth = JSON_TREE_MAX_DEPTH_EXPANDED;
60
59
  const maxLines = JSON_TREE_MAX_LINES_EXPANDED;
61
- const tree = renderJsonTreeLines(
62
- stripInternalArgs(args),
63
- theme,
64
- maxDepth,
65
- maxLines,
66
- JSON_TREE_SCALAR_LEN_EXPANDED,
67
- );
60
+ const tree = renderJsonTreeLines(args, theme, maxDepth, maxLines, JSON_TREE_SCALAR_LEN_EXPANDED);
68
61
  for (const line of tree.lines) {
69
62
  lines.push(line);
70
63
  }
@@ -155,6 +155,10 @@ export class AssistantMessageComponent extends Container {
155
155
  this.#contentContainer.addChild(new Text(theme.fg("error", `Error: ${errorMsg}`), 1, 0));
156
156
  }
157
157
  }
158
+ if (message.errorMessage && message.stopReason !== "aborted" && message.stopReason !== "error") {
159
+ this.#contentContainer.addChild(new Spacer(1));
160
+ this.#contentContainer.addChild(new Text(theme.fg("error", `Error: ${message.errorMessage}`), 1, 0));
161
+ }
158
162
 
159
163
  // Token usage metadata
160
164
  if (settings.get("display.showTokenUsage") && this.#usageInfo) {
@@ -1,7 +1,7 @@
1
1
  import { getIndentation } from "@oh-my-pi/pi-utils";
2
2
  import * as Diff from "diff";
3
3
  import { theme } from "../../modes/theme/theme";
4
- import { replaceTabs } from "../../tools/render-utils";
4
+ import { type CodeFrameMarker, formatCodeFrameLine, replaceTabs } from "../../tools/render-utils";
5
5
 
6
6
  /** SGR dim on / normal intensity — additive, preserves fg/bg colors. */
7
7
  const DIM = "\x1b[2m";
@@ -37,14 +37,14 @@ function visualizeIndent(text: string, filePath?: string): string {
37
37
  * Parse diff line to extract prefix, line number, and content.
38
38
  * Supported formats: "+123|content" (canonical) and "+123 content" (legacy).
39
39
  */
40
- function parseDiffLine(line: string): { prefix: string; lineNum: string; content: string } | null {
40
+ function parseDiffLine(line: string): { prefix: CodeFrameMarker; lineNum: string; content: string } | null {
41
41
  const canonical = line.match(/^([+-\s])(\s*\d+)\|(.*)$/);
42
42
  if (canonical) {
43
- return { prefix: canonical[1], lineNum: canonical[2], content: canonical[3] };
43
+ return { prefix: canonical[1] as CodeFrameMarker, lineNum: canonical[2] ?? "", content: canonical[3] ?? "" };
44
44
  }
45
45
  const legacy = line.match(/^([+-\s])(?:(\s*\d+)\s)?(.*)$/);
46
46
  if (!legacy) return null;
47
- return { prefix: legacy[1], lineNum: legacy[2] ?? "", content: legacy[3] };
47
+ return { prefix: legacy[1] as CodeFrameMarker, lineNum: legacy[2] ?? "", content: legacy[3] ?? "" };
48
48
  }
49
49
 
50
50
  /**
@@ -108,12 +108,27 @@ export interface RenderDiffOptions {
108
108
  export function renderDiff(diffText: string, options: RenderDiffOptions = {}): string {
109
109
  const lines = diffText.split("\n");
110
110
  const result: string[] = [];
111
-
112
- const formatLine = (prefix: string, lineNum: string, content: string): string => {
111
+ const parsedLines = lines.map(parseDiffLine);
112
+ const lineNumberWidth = parsedLines.reduce((width, parsed) => {
113
+ const lineNumber = parsed?.lineNum.trim() ?? "";
114
+ return Math.max(width, lineNumber.length);
115
+ }, 0);
116
+
117
+ // Track the line number rendered on the previous emitted line so we can
118
+ // blank out duplicate gutters. Two cases trigger this:
119
+ // 1. Single-line replacement (`-N` followed by `+N`) — the `+N` repeats `N`.
120
+ // 2. Insertion followed by context (`+N` then ` N` if producer used oldLine).
121
+ let prevLineNum = "";
122
+
123
+ const formatLine = (prefix: CodeFrameMarker, lineNum: string, content: string): string => {
113
124
  if (lineNum.trim().length === 0) {
125
+ prevLineNum = "";
114
126
  return `${prefix}${content}`;
115
127
  }
116
- return `${prefix}${lineNum}|${content}`;
128
+ const trimmed = lineNum.trim();
129
+ const displayNum = trimmed === prevLineNum ? "" : trimmed;
130
+ prevLineNum = trimmed;
131
+ return formatCodeFrameLine(prefix, displayNum, content, lineNumberWidth);
117
132
  };
118
133
 
119
134
  let i = 0;
@@ -122,13 +137,13 @@ export function renderDiff(diffText: string, options: RenderDiffOptions = {}): s
122
137
  const parsed = parseDiffLine(line);
123
138
 
124
139
  if (!parsed) {
140
+ prevLineNum = "";
125
141
  result.push(theme.fg("toolDiffContext", line));
126
142
  i++;
127
143
  continue;
128
144
  }
129
145
 
130
146
  if (parsed.prefix === "-") {
131
- // Collect consecutive removed lines
132
147
  const removedLines: { lineNum: string; content: string }[] = [];
133
148
  while (i < lines.length) {
134
149
  const p = parseDiffLine(lines[i]);
@@ -137,7 +152,6 @@ export function renderDiff(diffText: string, options: RenderDiffOptions = {}): s
137
152
  i++;
138
153
  }
139
154
 
140
- // Collect consecutive added lines
141
155
  const addedLines: { lineNum: string; content: string }[] = [];
142
156
  while (i < lines.length) {
143
157
  const p = parseDiffLine(lines[i]);
@@ -146,8 +160,6 @@ export function renderDiff(diffText: string, options: RenderDiffOptions = {}): s
146
160
  i++;
147
161
  }
148
162
 
149
- // Only do intra-line diffing when there's exactly one removed and one added line
150
- // (indicating a single line modification). Otherwise, show lines as-is.
151
163
  if (removedLines.length === 1 && addedLines.length === 1) {
152
164
  const removed = removedLines[0];
153
165
  const added = addedLines[0];
@@ -167,7 +179,6 @@ export function renderDiff(diffText: string, options: RenderDiffOptions = {}): s
167
179
  theme.fg("toolDiffAdded", formatLine("+", added.lineNum, visualizeIndent(addedLine, options.filePath))),
168
180
  );
169
181
  } else {
170
- // Show all removed lines first, then all added lines
171
182
  for (const removed of removedLines) {
172
183
  result.push(
173
184
  theme.fg(
@@ -186,7 +197,6 @@ export function renderDiff(diffText: string, options: RenderDiffOptions = {}): s
186
197
  }
187
198
  }
188
199
  } else if (parsed.prefix === "+") {
189
- // Standalone added line
190
200
  result.push(
191
201
  theme.fg(
192
202
  "toolDiffAdded",
@@ -195,7 +205,6 @@ export function renderDiff(diffText: string, options: RenderDiffOptions = {}): s
195
205
  );
196
206
  i++;
197
207
  } else {
198
- // Context line
199
208
  result.push(
200
209
  theme.fg(
201
210
  "toolDiffContext",
@@ -56,22 +56,27 @@ export class FooterComponent implements Component {
56
56
  this.#gitWatcher = null;
57
57
  }
58
58
 
59
- git.head.resolve(getProjectDir()).then(head => {
60
- if (!head) {
61
- return;
62
- }
63
-
64
- try {
65
- this.#gitWatcher = fs.watch(head.headPath, () => {
66
- this.#cachedBranch = undefined; // Invalidate cache
67
- if (this.#onBranchChange) {
68
- this.#onBranchChange();
69
- }
70
- });
71
- } catch {
72
- // Silently fail if we can't watch
73
- }
74
- });
59
+ void git.head
60
+ .resolve(getProjectDir())
61
+ .then(head => {
62
+ if (!head) {
63
+ return;
64
+ }
65
+
66
+ try {
67
+ this.#gitWatcher = fs.watch(head.headPath, () => {
68
+ this.#cachedBranch = undefined; // Invalidate cache
69
+ if (this.#onBranchChange) {
70
+ this.#onBranchChange();
71
+ }
72
+ });
73
+ } catch {
74
+ // Silently fail if we can't watch
75
+ }
76
+ })
77
+ .catch(() => {
78
+ this.#cachedBranch = null;
79
+ });
75
80
  }
76
81
 
77
82
  /**
@@ -10,6 +10,7 @@ import {
10
10
  truncateToWidth,
11
11
  visibleWidth,
12
12
  } from "@oh-my-pi/pi-tui";
13
+ import { formatBytes } from "@oh-my-pi/pi-utils";
13
14
  import { theme } from "../../modes/theme/theme";
14
15
  import { matchesAppInterrupt } from "../../modes/utils/keybinding-matchers";
15
16
  import type { SessionInfo } from "../../session/session-manager";
@@ -157,10 +158,9 @@ class SessionList implements Component {
157
158
  lines.push(messageLine);
158
159
  }
159
160
 
160
- // Metadata line: date + message count
161
+ // Metadata line: date + file size
161
162
  const modified = formatDate(session.modified);
162
- const msgCount = `${session.messageCount} message${session.messageCount !== 1 ? "s" : ""}`;
163
- const metadata = ` ${modified} ${theme.sep.dot} ${msgCount}`;
163
+ const metadata = ` ${modified} ${theme.sep.dot} ${formatBytes(session.size)}`;
164
164
  const metadataLine = theme.fg("dim", truncateToWidth(metadata, width));
165
165
 
166
166
  lines.push(metadataLine);
@@ -355,7 +355,12 @@ const OPTION_PROVIDERS: Partial<Record<SettingPath, OptionProvider>> = {
355
355
  { value: "parallel", label: "Parallel", description: "Requires PARALLEL_API_KEY" },
356
356
  ],
357
357
  "providers.image": [
358
- { value: "auto", label: "Auto", description: "Priority: OpenRouter > Gemini" },
358
+ {
359
+ value: "auto",
360
+ label: "Auto",
361
+ description: "Priority: GPT model image tool > Antigravity > OpenRouter > Gemini",
362
+ },
363
+ { value: "openai", label: "OpenAI", description: "Uses the active GPT Responses/Codex model" },
359
364
  { value: "gemini", label: "Gemini", description: "Requires GEMINI_API_KEY" },
360
365
  { value: "openrouter", label: "OpenRouter", description: "Requires OPENROUTER_API_KEY" },
361
366
  ],
@@ -34,14 +34,7 @@ export class TodoReminderComponent extends Container {
34
34
  this.#box.addChild(new Text(header, 0, 0));
35
35
  this.#box.addChild(new Spacer(1));
36
36
 
37
- const todoList = this.todos
38
- .map(t => {
39
- const line = ` ${theme.checkbox.unchecked} ${t.content}`;
40
- if (!t.details) return line;
41
- const detailLines = t.details.split("\n").map(l => ` ${l}`);
42
- return [line, ...detailLines].join("\n");
43
- })
44
- .join("\n");
37
+ const todoList = this.todos.map(todo => ` ${theme.checkbox.unchecked} ${todo.content}`).join("\n");
45
38
  this.#box.addChild(new Text(theme.italic(todoList), 0, 0));
46
39
  }
47
40
  }
@@ -27,7 +27,6 @@ import {
27
27
  JSON_TREE_SCALAR_LEN_COLLAPSED,
28
28
  JSON_TREE_SCALAR_LEN_EXPANDED,
29
29
  renderJsonTreeLines,
30
- stripInternalArgs,
31
30
  } from "../../tools/json-tree";
32
31
  import { PYTHON_DEFAULT_PREVIEW_LINES } from "../../tools/python";
33
32
  import { formatExpandHint, replaceTabs, resolveImageOptions, truncateToWidth } from "../../tools/render-utils";
@@ -743,9 +742,7 @@ export class ToolExecutionComponent extends Container {
743
742
  lines.push("");
744
743
  lines.push(theme.fg("dim", "Args"));
745
744
  const tree = renderJsonTreeLines(
746
- this.#args && typeof this.#args === "object" && !Array.isArray(this.#args)
747
- ? stripInternalArgs(this.#args as Record<string, unknown>)
748
- : this.#args,
745
+ this.#args,
749
746
  theme,
750
747
  JSON_TREE_MAX_DEPTH_EXPANDED,
751
748
  JSON_TREE_MAX_LINES_EXPANDED,
@@ -362,7 +362,7 @@ export class SelectorController {
362
362
  }
363
363
  break;
364
364
  case "providers.image":
365
- if (value === "auto" || value === "gemini" || value === "openrouter") {
365
+ if (value === "auto" || value === "openai" || value === "gemini" || value === "openrouter") {
366
366
  setPreferredImageProvider(value);
367
367
  }
368
368
  break;
@@ -161,6 +161,14 @@ export async function runPrintMode(session: AgentSession, options: PrintModeOpti
161
161
  }
162
162
  }
163
163
 
164
+ if (
165
+ assistantMsg.errorMessage &&
166
+ assistantMsg.stopReason !== "error" &&
167
+ assistantMsg.stopReason !== "aborted"
168
+ ) {
169
+ process.stderr.write(`${sanitizeText(assistantMsg.errorMessage)}\n`);
170
+ }
171
+
164
172
  // Output text content
165
173
  for (const content of assistantMsg.content) {
166
174
  if (content.type === "text") {
@@ -98,7 +98,7 @@ Before acting, determine what kind of question this is:
98
98
  - For API signatures: copy verbatim from source. You **MUST NOT** paraphrase or reconstruct from memory.
99
99
 
100
100
  ## 5. Report
101
- - Call `submit_result` with structured findings.
101
+ - Call `yield` with structured findings.
102
102
  - Every `sources` entry **MUST** include a verbatim excerpt.
103
103
  - The `api` array **MUST** contain exact signatures copied from source.
104
104
  - Clean up cloned repos: `rm -rf /tmp/librarian-*`.
@@ -44,7 +44,7 @@ output:
44
44
  type: number
45
45
  file_path:
46
46
  metadata:
47
- description: Absolute path to affected file
47
+ description: Path to affected file
48
48
  type: string
49
49
  line_start:
50
50
  metadata:
@@ -63,7 +63,7 @@ Your goal is to identify bugs the author would want fixed before merge.
63
63
  1. Run `git diff` (or `gh pr diff <number>`) to view patch
64
64
  2. Read modified files for full context
65
65
  3. Call `report_finding` per issue
66
- 4. Call `submit_result` with verdict
66
+ 4. Call `yield` with verdict
67
67
 
68
68
  Bash is read-only: `git diff`, `git log`, `git show`, `gh pr diff`. You **MUST NOT** make file edits or trigger builds.
69
69
  </procedure>
@@ -108,10 +108,10 @@ Each `report_finding` requires:
108
108
  - `body`: One paragraph
109
109
  - `priority`: 0-3
110
110
  - `confidence`: 0.0-1.0
111
- - `file_path`: Absolute path
111
+ - `file_path`: Path to affected file
112
112
  - `line_start`, `line_end`: Range ≤10 lines, must overlap diff
113
113
 
114
- Final `submit_result` call (payload under `result.data`):
114
+ Final `yield` call (payload under `result.data`):
115
115
  - `result.data.overall_correctness`: "correct" (no bugs/blockers) or "incorrect"
116
116
  - `result.data.explanation`: Plain text, 1-3 sentences summarizing verdict. Don't repeat findings (captured via `report_finding`).
117
117
  - `result.data.confidence`: 0.0-1.0
@@ -4,7 +4,7 @@ Do not stop after a single fix attempt.
4
4
  </critical>
5
5
 
6
6
  <instruction>
7
- - Prefer `gh_run_watch` with no arguments if that tool is available.
7
+ - Prefer the `github` tool with `op: run_watch` and no other arguments if that tool is available.
8
8
  - Otherwise use `gh` cli.
9
9
  - Use the workflow runs for the current HEAD commit as the source of truth after each push.
10
10
  </instruction>
@@ -40,7 +40,7 @@ Reviewer **MUST**:
40
40
  2. {{#if skipDiff}}**MUST** run `git diff`/`git show` for assigned files{{else}}**MUST** use diff hunks below (**MUST NOT** re-run git diff){{/if}}
41
41
  3. **MAY** read full file context as needed via `read`
42
42
  4. Call `report_finding` per issue
43
- 5. Call `submit_result` with verdict when done
43
+ 5. Call `yield` with verdict when done
44
44
 
45
45
  {{#if skipDiff}}
46
46
  ### Diff Previews
@@ -15,9 +15,9 @@ If you need additional information, you can find your conversation with the user
15
15
  {{/if}}
16
16
 
17
17
  {{SECTION_SEPARATOR "Closure"}}
18
- No TODO tracking, no progress updates. Execute, call `submit_result`, done.
18
+ No TODO tracking, no progress updates. Execute, call `yield`, done.
19
19
 
20
- When finished, you **MUST** call `submit_result` exactly once. This is like writing to a ticket, provide what is required, and close it.
20
+ When finished, you **MUST** call `yield` exactly once. This is like writing to a ticket, provide what is required, and close it.
21
21
 
22
22
  This is your only way to return a result. You **MUST NOT** put JSON in plain text, and you **MUST NOT** substitute a text summary for the structured `result.data` parameter.
23
23
 
@@ -29,7 +29,7 @@ Your result **MUST** match this TypeScript interface:
29
29
  {{/if}}
30
30
 
31
31
  {{SECTION_SEPARATOR "Giving Up"}}
32
- Giving up is a last resort. If truly blocked, you **MUST** call `submit_result` exactly once with `result.error` describing what you tried and the exact blocker.
32
+ Giving up is a last resort. If truly blocked, you **MUST** call `yield` exactly once with `result.error` describing what you tried and the exact blocker.
33
33
  You **MUST NOT** give up due to uncertainty, missing information obtainable via tools or repo context, or needing a design decision you can derive yourself.
34
34
 
35
35
  You **MUST** keep going until this ticket is closed. This matters.
@@ -0,0 +1,11 @@
1
+ <system-reminder>
2
+ You stopped without calling yield. This is reminder {{retryCount}} of {{maxRetries}}.
3
+
4
+ You **MUST** call yield as your only action now. Choose one:
5
+ - If task is complete: call yield with your result in `result.data`
6
+ - If task failed: call yield with `result.error` describing what happened
7
+
8
+ You **MUST NOT** give up if you can still complete the task through exploration (using available tools or repo context). If you submit an error, you **MUST** include what you tried and the exact blocker.
9
+
10
+ You **MUST NOT** output text without a tool call. You **MUST** call yield to finish.
11
+ </system-reminder>
@@ -194,6 +194,9 @@ You **MUST NOT** use Python or Bash when a specialized tool exists.
194
194
  {{#has tools "edit"}}- Use `edit` for surgical text changes, not `sed`.{{/has}}
195
195
  {{/ifAny}}
196
196
 
197
+ ### Paths
198
+ - For tools that take a `path` (or path-like field), prefer cwd-relative paths for files inside the cwd. Use absolute paths only when targeting files outside the cwd or when expanding `~`.
199
+
197
200
  {{#has tools "lsp"}}
198
201
  ### LSP guidance
199
202
  Use semantic tools for semantic questions:
@@ -21,8 +21,9 @@ Asks user when you need clarification or input during task execution.
21
21
  - **Do NOT include "Other" option** — UI automatically adds "Other (type your own)" to every question.
22
22
  </critical>
23
23
 
24
- <example name="single">
24
+ <examples>
25
+ # Single question
25
26
  question: "Which authentication method should this API use?"
26
27
  options: [{"label": "JWT"}, {"label": "OAuth2"}, {"label": "Session cookies"}]
27
28
  recommended: 0
28
- </example>
29
+ </examples>
@@ -2,39 +2,35 @@ Performs structural AST-aware rewrites via native ast-grep.
2
2
 
3
3
  <instruction>
4
4
  - Use for codemods and structural rewrites where plain text replace is unsafe
5
- - `path` accepts a comma-separated list in addition to file/dir/glob
6
- - Set `lang` explicitly in mixed-language trees for deterministic rewrites
5
+ - `path` is required and accepts a file, directory, glob, comma-separated path list, or internal URL
6
+ - Language is inferred from `path`; narrow `path` to one language for deterministic rewrites
7
7
  - Metavariables captured in `pat` (`$A`, `$$$ARGS`) are substituted into that entry's `out` template
8
8
  - **Patterns match AST structure, not text.** `$NAME` = one node (captured); `$_` = one without binding; `$$$NAME` = zero-or-more (lazy — stops at next matchable element); `$$$` = zero-or-more without binding. Use `$$$NAME`, **NOT** `$$NAME` — the two-dollar form is invalid. Metavariable names are UPPERCASE and **MUST** be the whole AST node — partial text like `prefix$VAR` or `"hello $NAME"` does NOT work
9
9
  - When the same metavariable appears twice, both occurrences **MUST** match identical code (`$A == $A` matches `x == x`, not `x == y`)
10
- - Rewrite patterns **MUST** parse as a single valid AST node. For method fragments or body snippets that don't parse standalone, wrap in context (e.g. `class $_ { … }`) and set `sel` to target the inner node — match and replacement target the selected node, not the wrapper. If ast-grep reports `Multiple AST nodes are detected`, wrap and use `sel`
10
+ - Rewrite patterns **MUST** parse as a single valid AST node. For method fragments or body snippets that don't parse standalone, wrap in context (e.g. `class $_ { … }`)
11
11
  - For TS declarations/methods, tolerate unknown annotations: `async function $NAME($$$ARGS): $_ { $$$BODY }` or `class $_ { method($ARG: $_): $_ { $$$BODY } }`
12
12
  - Delete matched code with empty `out`: `{"pat":"console.log($$$)","out":""}`
13
13
  - Each rewrite is a 1:1 structural substitution — cannot split one capture across multiple nodes or merge multiple captures into one
14
14
  </instruction>
15
15
 
16
16
  <output>
17
- - Replacement summary, per-file replacement counts, and change diffs
17
+ - Replacement summary, per-file replacement counts, and change diffs as `-LINE+ID|before` / `+LINE+ID|after` lines
18
18
  - Parse issues when files cannot be processed
19
19
  </output>
20
20
 
21
21
  <examples>
22
- - Rename a call site across a directory:
23
- `{"ops":[{"pat":"oldApi($$$ARGS)","out":"newApi($$$ARGS)"}],"lang":"typescript","path":"src/"}`
24
- - Delete matching calls (empty `out` removes the node):
25
- `{"ops":[{"pat":"console.log($$$ARGS)","out":""}],"lang":"typescript","path":"src/"}`
26
- - Rewrite import source path:
27
- `{"ops":[{"pat":"import { $$$IMPORTS } from \"old-package\"","out":"import { $$$IMPORTS } from \"new-package\""}],"lang":"typescript","path":"src/"}`
28
- - Modernize to optional chaining (same metavariable enforces identity):
29
- `{"ops":[{"pat":"$A && $A()","out":"$A?.()"}],"lang":"typescript","path":"src/"}`
30
- - Swap two arguments using captures:
31
- `{"ops":[{"pat":"assertEqual($A, $B)","out":"assertEqual($B, $A)"}],"lang":"typescript","path":"tests/"}`
32
- - Rename a function declaration while tolerating any return type annotation:
33
- `{"ops":[{"pat":"async function fetchData($$$ARGS): $_ { $$$BODY }","out":"async function loadData($$$ARGS): $_ { $$$BODY }"}],"sel":"function_declaration","lang":"typescript","path":"src/api.ts"}`
34
- - Rewrite a method body fragment by wrapping in parseable context and selecting the method:
35
- `{"ops":[{"pat":"class $_ { async execute($INPUT: $_) { $$$BEFORE; const $PARSED = $_.parse($INPUT); $$$AFTER } }","out":"class $_ { async execute($INPUT: $_) { $$$BEFORE; const $PARSED = $SCHEMA.parse($INPUT); $$$AFTER } }"}],"sel":"method_definition","lang":"typescript","path":"src/tools/todo.ts"}`
36
- - Python — convert print calls to logging:
37
- `{"ops":[{"pat":"print($$$ARGS)","out":"logger.info($$$ARGS)"}],"lang":"python","path":"src/"}`
22
+ # Rename a call site across TypeScript files
23
+ `{"ops":[{"pat":"oldApi($$$ARGS)","out":"newApi($$$ARGS)"}],"path":"src/**/*.ts"}`
24
+ # Delete matching calls
25
+ `{"ops":[{"pat":"console.log($$$ARGS)","out":""}],"path":"src/**/*.ts"}`
26
+ # Rewrite import source path
27
+ `{"ops":[{"pat":"import { $$$IMPORTS } from \"old-package\"","out":"import { $$$IMPORTS } from \"new-package\""}],"path":"src/**/*.ts"}`
28
+ # Modernize to optional chaining (same metavariable enforces identity)
29
+ `{"ops":[{"pat":"$A && $A()","out":"$A?.()"}],"path":"src/**/*.ts"}`
30
+ # Swap two arguments using captures
31
+ `{"ops":[{"pat":"assertEqual($A, $B)","out":"assertEqual($B, $A)"}],"path":"tests/**/*.ts"}`
32
+ # Python convert print calls to logging
33
+ `{"ops":[{"pat":"print($$$ARGS)","out":"logger.info($$$ARGS)"}],"path":"src/**/*.py"}`
38
34
  </examples>
39
35
 
40
36
  <critical>
@@ -2,46 +2,41 @@ Performs structural code search using AST matching via native ast-grep.
2
2
 
3
3
  <instruction>
4
4
  - Use when syntax shape matters more than raw text (calls, declarations, specific language constructs)
5
- - `path` accepts a comma-separated list in addition to file/dir/glob
6
- - Set `lang` explicitly in mixed-language trees to avoid parse noise from non-source files
7
- - Multiple patterns in `pat` run in one native pass, merged, then `offset`/`limit` applied
5
+ - `path` is required and accepts a file, directory, glob, comma-separated path list, or internal URL
6
+ - Language is inferred from `path`; narrow `path` to one language when mixed-language trees could cause parse noise
7
+ - `pat` is a single AST pattern. Run separate calls for distinct unrelated patterns
8
8
  - **Patterns match AST structure, not text** — whitespace/formatting is ignored
9
9
  - `$NAME` captures one node; `$_` matches one without binding; `$$$NAME` captures zero-or-more (lazy — stops at next matchable element); `$$$` matches zero-or-more without binding. Use `$$$NAME`, **NOT** `$$NAME` — the two-dollar form is invalid and produces a parse error
10
10
  - Metavariable names are UPPERCASE and must be the whole AST node — partial-text like `prefix$VAR`, `"hello $NAME"`, or `a $OP b` does NOT work; match the whole node instead
11
11
  - When the same metavariable appears twice, both occurrences **MUST** match identical code (`$A == $A` matches `x == x`, not `x == y`)
12
- - Patterns **MUST** parse as a single valid AST node for the target language. For method fragments or body snippets that don't parse standalone, wrap in valid context (e.g. `class $_ { … }`) and set `sel` to target the inner node — results return for the selected node, not the outer wrapper. If ast-grep reports `Multiple AST nodes are detected`, the pattern isn't a single parseable node — wrap and use `sel`
13
- - C++ qualified calls used as expression statements need the statement semicolon in the pattern: use `ns::doThing($ARG);`, `$CALLEE($ARG)`, or wrap a statement snippet and select `call_expression`. Without `;`, tree-sitter-cpp may parse `ns::doThing($ARG)` as declaration-like syntax and return no matches
12
+ - Patterns **MUST** parse as a single valid AST node for the inferred target language. For method fragments or body snippets that don't parse standalone, wrap in valid context (e.g. `class $_ { … }`)
13
+ - C++ qualified calls used as expression statements need the statement semicolon in the pattern: use `ns::doThing($ARG);`, `$CALLEE($ARG);`, or wrap a statement snippet. Without `;`, tree-sitter-cpp may parse `ns::doThing($ARG)` as declaration-like syntax and return no matches
14
14
  - For TS declarations/methods, tolerate unknown annotations: `async function $NAME($$$ARGS): $_ { $$$BODY }` or `class $_ { method($ARG: $_): $_ { $$$BODY } }`
15
15
  - Declaration forms are structurally distinct — top-level `function foo`, class method `foo()`, and `const foo = () => {}` are different AST shapes; search the right form before concluding absence
16
- - Loosest existence check: `pat: ["executeBash"]` with `sel: "identifier"`
16
+ - Loosest existence check: `pat: "executeBash"` with a narrow `path`
17
17
  </instruction>
18
18
 
19
19
  <output>
20
20
  - Grouped matches with file path, byte range, line/column ranges, metavariable captures
21
+ - Match lines are anchor-prefixed: `LINE+ID>content` for the matched line and `LINE+ID:content` for surrounding context
21
22
  - Summary counts (`totalMatches`, `filesWithMatches`, `filesSearched`) and parse issues when present
22
23
  </output>
23
24
 
24
25
  <examples>
25
- - Multi-pattern scoped search:
26
- `{"pat":["console.log($$$)","console.error($$$)"],"lang":"typescript","path":"src/"}`
27
- - Named imports from a specific package (quoted string inside pattern):
28
- `{"pat":["import { $$$IMPORTS } from \"react\""],"lang":"typescript","path":"src/"}`
29
- - Arrow functions assigned to a const (distinct AST from function declarations):
30
- `{"pat":["const $NAME = ($$$ARGS) => $BODY"],"lang":"typescript","path":"src/utils/"}`
31
- - Method call on any object, ignoring method name with `$_`:
32
- `{"pat":["logger.$_($$$ARGS)"],"lang":"typescript","path":"src/"}`
33
- - Contextual pattern with selector match the identifier `foo`, not the whole call:
34
- `{"pat":["foo()"],"sel":"identifier","lang":"typescript","path":"src/utils.ts"}`
35
- - Match a function declaration while tolerating any return type annotation (`sel` targets the inner node):
36
- `{"pat":["async function processItems($$$ARGS): $_ { $$$BODY }"],"sel":"function_declaration","lang":"typescript","path":"src/worker.ts"}`
37
- - Match a method body fragment by wrapping in parseable context and selecting the method:
38
- `{"pat":["class $_ { async execute($INPUT: $_) { $$$BEFORE; const $PARSED = $_.parse($INPUT); $$$AFTER } }"],"sel":"method_definition","lang":"typescript","path":"src/tools/todo.ts"}`
39
- - Loosest existence check for a symbol in one file:
40
- `{"pat":["processItems"],"sel":"identifier","lang":"typescript","path":"src/worker.ts"}`
26
+ # Search TypeScript files under src
27
+ `{"pat":"console.log($$$)","path":"src/**/*.ts"}`
28
+ # Named imports from a specific package
29
+ `{"pat":"import { $$$IMPORTS } from \"react\"","path":"src/**/*.ts"}`
30
+ # Arrow functions assigned to a const
31
+ `{"pat":"const $NAME = ($$$ARGS) => $BODY","path":"src/utils/**/*.ts"}`
32
+ # Method call on any object, ignoring method name with `$_`
33
+ `{"pat":"logger.$_($$$ARGS)","path":"src/**/*.ts"}`
34
+ # Loosest existence check for a symbol in one file
35
+ `{"pat":"processItems","path":"src/worker.ts"}`
41
36
  </examples>
42
37
 
43
38
  <critical>
44
- - Avoid repo-root AST scans when the target is language-specific — narrow `path` first
45
- - Parse issues are query failure, not evidence of absence: repair the pattern or tighten `path`/`glob`/`lang` before concluding "no matches"
39
+ - Avoid repo-root scans — narrow `path` first
40
+ - Parse issues are query failure, not evidence of absence: repair the pattern or tighten `path` before concluding "no matches"
46
41
  - For broad/open-ended exploration across subsystems, use Task tool with explore subagent first
47
42
  </critical>