@oh-my-pi/pi-coding-agent 1.341.0 → 2.1.1337

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 (158) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +1 -1
  3. package/examples/custom-tools/subagent/index.ts +1 -1
  4. package/package.json +10 -9
  5. package/src/bun-imports.d.ts +16 -0
  6. package/src/cli/args.ts +5 -6
  7. package/src/cli/file-processor.ts +3 -3
  8. package/src/cli/list-models.ts +2 -2
  9. package/src/cli/plugin-cli.ts +1 -1
  10. package/src/cli/session-picker.ts +2 -2
  11. package/src/cli/update-cli.ts +273 -0
  12. package/src/cli.ts +1 -1
  13. package/src/config.ts +23 -75
  14. package/src/core/agent-session.ts +158 -16
  15. package/src/core/auth-storage.ts +2 -3
  16. package/src/core/bash-executor.ts +50 -10
  17. package/src/core/compaction/branch-summarization.ts +5 -5
  18. package/src/core/compaction/compaction.ts +3 -3
  19. package/src/core/compaction/index.ts +3 -3
  20. package/src/core/custom-commands/bundled/review/index.ts +156 -0
  21. package/src/core/custom-commands/index.ts +15 -0
  22. package/src/core/custom-commands/loader.ts +232 -0
  23. package/src/core/custom-commands/types.ts +112 -0
  24. package/src/core/custom-tools/index.ts +3 -3
  25. package/src/core/custom-tools/loader.ts +10 -8
  26. package/src/core/custom-tools/types.ts +11 -6
  27. package/src/core/custom-tools/wrapper.ts +2 -1
  28. package/src/core/exec.ts +22 -12
  29. package/src/core/export-html/index.ts +38 -123
  30. package/src/core/export-html/template.css +0 -7
  31. package/src/core/export-html/template.html +3 -4
  32. package/src/core/export-html/template.macro.ts +24 -0
  33. package/src/core/file-mentions.ts +54 -0
  34. package/src/core/hooks/index.ts +5 -5
  35. package/src/core/hooks/loader.ts +21 -16
  36. package/src/core/hooks/runner.ts +6 -6
  37. package/src/core/hooks/tool-wrapper.ts +2 -2
  38. package/src/core/hooks/types.ts +12 -15
  39. package/src/core/index.ts +6 -6
  40. package/src/core/logger.ts +112 -0
  41. package/src/core/mcp/client.ts +3 -3
  42. package/src/core/mcp/config.ts +1 -1
  43. package/src/core/mcp/index.ts +12 -12
  44. package/src/core/mcp/loader.ts +2 -2
  45. package/src/core/mcp/manager.ts +6 -6
  46. package/src/core/mcp/tool-bridge.ts +3 -3
  47. package/src/core/mcp/transports/http.ts +1 -1
  48. package/src/core/mcp/transports/index.ts +2 -2
  49. package/src/core/mcp/transports/stdio.ts +1 -1
  50. package/src/core/messages.ts +22 -0
  51. package/src/core/model-registry.ts +2 -2
  52. package/src/core/model-resolver.ts +2 -2
  53. package/src/core/plugins/doctor.ts +1 -1
  54. package/src/core/plugins/index.ts +6 -6
  55. package/src/core/plugins/installer.ts +4 -4
  56. package/src/core/plugins/loader.ts +4 -9
  57. package/src/core/plugins/manager.ts +5 -5
  58. package/src/core/plugins/paths.ts +3 -3
  59. package/src/core/sdk.ts +77 -35
  60. package/src/core/session-manager.ts +6 -6
  61. package/src/core/settings-manager.ts +16 -3
  62. package/src/core/skills.ts +5 -5
  63. package/src/core/slash-commands.ts +60 -45
  64. package/src/core/system-prompt.ts +6 -6
  65. package/src/core/title-generator.ts +2 -2
  66. package/src/core/tools/bash.ts +32 -155
  67. package/src/core/tools/context.ts +2 -2
  68. package/src/core/tools/edit-diff.ts +3 -3
  69. package/src/core/tools/edit.ts +18 -5
  70. package/src/core/tools/exa/company.ts +3 -3
  71. package/src/core/tools/exa/index.ts +16 -17
  72. package/src/core/tools/exa/linkedin.ts +3 -3
  73. package/src/core/tools/exa/mcp-client.ts +9 -9
  74. package/src/core/tools/exa/render.ts +5 -5
  75. package/src/core/tools/exa/researcher.ts +3 -3
  76. package/src/core/tools/exa/search.ts +6 -5
  77. package/src/core/tools/exa/types.ts +5 -6
  78. package/src/core/tools/exa/websets.ts +3 -3
  79. package/src/core/tools/find.ts +3 -3
  80. package/src/core/tools/grep.ts +3 -3
  81. package/src/core/tools/index.ts +48 -34
  82. package/src/core/tools/ls.ts +4 -4
  83. package/src/core/tools/lsp/client.ts +161 -90
  84. package/src/core/tools/lsp/config.ts +1 -1
  85. package/src/core/tools/lsp/edits.ts +2 -2
  86. package/src/core/tools/lsp/index.ts +15 -13
  87. package/src/core/tools/lsp/render.ts +2 -2
  88. package/src/core/tools/lsp/rust-analyzer.ts +3 -3
  89. package/src/core/tools/lsp/utils.ts +1 -1
  90. package/src/core/tools/notebook.ts +1 -1
  91. package/src/core/tools/output.ts +175 -0
  92. package/src/core/tools/read.ts +7 -7
  93. package/src/core/tools/renderers.ts +92 -13
  94. package/src/core/tools/review.ts +268 -0
  95. package/src/core/tools/task/agents.ts +22 -38
  96. package/src/core/tools/task/bundled-agents/reviewer.md +52 -37
  97. package/src/core/tools/task/commands.ts +31 -10
  98. package/src/core/tools/task/discovery.ts +2 -2
  99. package/src/core/tools/task/executor.ts +145 -28
  100. package/src/core/tools/task/index.ts +78 -30
  101. package/src/core/tools/task/model-resolver.ts +30 -20
  102. package/src/core/tools/task/parallel.ts +1 -1
  103. package/src/core/tools/task/render.ts +219 -30
  104. package/src/core/tools/task/subprocess-tool-registry.ts +89 -0
  105. package/src/core/tools/task/types.ts +36 -2
  106. package/src/core/tools/web-fetch.ts +5 -3
  107. package/src/core/tools/web-search/auth.ts +1 -1
  108. package/src/core/tools/web-search/index.ts +17 -15
  109. package/src/core/tools/web-search/providers/anthropic.ts +2 -2
  110. package/src/core/tools/web-search/providers/exa.ts +3 -5
  111. package/src/core/tools/web-search/providers/perplexity.ts +1 -1
  112. package/src/core/tools/web-search/render.ts +3 -3
  113. package/src/core/tools/write.ts +4 -4
  114. package/src/index.ts +29 -18
  115. package/src/main.ts +50 -33
  116. package/src/migrations.ts +3 -3
  117. package/src/modes/index.ts +5 -5
  118. package/src/modes/interactive/components/armin.ts +1 -1
  119. package/src/modes/interactive/components/assistant-message.ts +1 -1
  120. package/src/modes/interactive/components/bash-execution.ts +4 -4
  121. package/src/modes/interactive/components/bordered-loader.ts +2 -2
  122. package/src/modes/interactive/components/branch-summary-message.ts +2 -2
  123. package/src/modes/interactive/components/compaction-summary-message.ts +2 -2
  124. package/src/modes/interactive/components/diff.ts +1 -1
  125. package/src/modes/interactive/components/dynamic-border.ts +1 -1
  126. package/src/modes/interactive/components/footer.ts +5 -5
  127. package/src/modes/interactive/components/hook-editor.ts +2 -2
  128. package/src/modes/interactive/components/hook-input.ts +2 -2
  129. package/src/modes/interactive/components/hook-message.ts +3 -3
  130. package/src/modes/interactive/components/hook-selector.ts +2 -2
  131. package/src/modes/interactive/components/model-selector.ts +281 -59
  132. package/src/modes/interactive/components/oauth-selector.ts +3 -3
  133. package/src/modes/interactive/components/plugin-settings.ts +4 -4
  134. package/src/modes/interactive/components/queue-mode-selector.ts +2 -2
  135. package/src/modes/interactive/components/session-selector.ts +4 -4
  136. package/src/modes/interactive/components/settings-defs.ts +1 -1
  137. package/src/modes/interactive/components/settings-selector.ts +5 -5
  138. package/src/modes/interactive/components/show-images-selector.ts +2 -2
  139. package/src/modes/interactive/components/theme-selector.ts +2 -2
  140. package/src/modes/interactive/components/thinking-selector.ts +2 -2
  141. package/src/modes/interactive/components/tool-execution.ts +26 -8
  142. package/src/modes/interactive/components/tree-selector.ts +3 -3
  143. package/src/modes/interactive/components/user-message-selector.ts +2 -2
  144. package/src/modes/interactive/components/user-message.ts +1 -1
  145. package/src/modes/interactive/components/welcome.ts +2 -2
  146. package/src/modes/interactive/interactive-mode.ts +86 -42
  147. package/src/modes/interactive/theme/theme.ts +15 -17
  148. package/src/modes/print-mode.ts +4 -3
  149. package/src/modes/rpc/rpc-client.ts +4 -4
  150. package/src/modes/rpc/rpc-mode.ts +22 -12
  151. package/src/modes/rpc/rpc-types.ts +3 -3
  152. package/src/utils/changelog.ts +2 -2
  153. package/src/utils/clipboard.ts +1 -1
  154. package/src/utils/shell-snapshot.ts +218 -0
  155. package/src/utils/shell.ts +93 -13
  156. package/src/utils/tools-manager.ts +1 -1
  157. package/examples/custom-tools/subagent/agents/reviewer.md +0 -35
  158. package/src/core/tools/exa/logger.ts +0 -56
@@ -6,19 +6,20 @@
6
6
 
7
7
  import type { Component } from "@oh-my-pi/pi-tui";
8
8
  import { Text } from "@oh-my-pi/pi-tui";
9
- import type { Theme } from "../../modes/interactive/theme/theme.js";
10
- import type { RenderResultOptions } from "../custom-tools/types.js";
11
- import type { AskToolDetails } from "./ask.js";
12
- import type { FindToolDetails } from "./find.js";
13
- import type { GrepToolDetails } from "./grep.js";
14
- import type { LsToolDetails } from "./ls.js";
15
- import { renderCall as renderLspCall, renderResult as renderLspResult } from "./lsp/render.js";
16
- import type { LspToolDetails } from "./lsp/types.js";
17
- import type { NotebookToolDetails } from "./notebook.js";
18
- import { renderCall as renderTaskCall, renderResult as renderTaskResult } from "./task/render.js";
19
- import type { TaskToolDetails } from "./task/types.js";
20
- import { renderWebFetchCall, renderWebFetchResult, type WebFetchToolDetails } from "./web-fetch.js";
21
- import { renderWebSearchCall, renderWebSearchResult, type WebSearchRenderDetails } from "./web-search/render.js";
9
+ import type { Theme } from "../../modes/interactive/theme/theme";
10
+ import type { RenderResultOptions } from "../custom-tools/types";
11
+ import type { AskToolDetails } from "./ask";
12
+ import type { FindToolDetails } from "./find";
13
+ import type { GrepToolDetails } from "./grep";
14
+ import type { LsToolDetails } from "./ls";
15
+ import { renderCall as renderLspCall, renderResult as renderLspResult } from "./lsp/render";
16
+ import type { LspToolDetails } from "./lsp/types";
17
+ import type { NotebookToolDetails } from "./notebook";
18
+ import type { OutputToolDetails } from "./output";
19
+ import { renderCall as renderTaskCall, renderResult as renderTaskResult } from "./task/render";
20
+ import type { TaskToolDetails } from "./task/types";
21
+ import { renderWebFetchCall, renderWebFetchResult, type WebFetchToolDetails } from "./web-fetch";
22
+ import { renderWebSearchCall, renderWebSearchResult, type WebSearchRenderDetails } from "./web-search/render";
22
23
 
23
24
  // Tree drawing characters
24
25
  const TREE_MID = "├─";
@@ -416,6 +417,83 @@ const lspRenderer: ToolRenderer<LspArgs, LspToolDetails> = {
416
417
  renderResult: renderLspResult,
417
418
  };
418
419
 
420
+ // ============================================================================
421
+ // Output Renderer
422
+ // ============================================================================
423
+
424
+ interface OutputArgs {
425
+ ids: string[];
426
+ format?: "raw" | "json" | "stripped";
427
+ }
428
+
429
+ /** Format byte count for display */
430
+ function formatBytes(bytes: number): string {
431
+ if (bytes < 1024) return `${bytes}B`;
432
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}K`;
433
+ return `${(bytes / (1024 * 1024)).toFixed(1)}M`;
434
+ }
435
+
436
+ const outputRenderer: ToolRenderer<OutputArgs, OutputToolDetails> = {
437
+ renderCall(args, theme) {
438
+ const ids = args.ids?.join(", ") ?? "?";
439
+ const label = theme.fg("toolTitle", theme.bold("output"));
440
+ const format = args.format && args.format !== "raw" ? theme.fg("muted", ` (${args.format})`) : "";
441
+ return new Text(`${label} ${theme.fg("dim", ids)}${format}`, 0, 0);
442
+ },
443
+
444
+ renderResult(result, { expanded }, theme) {
445
+ const details = result.details;
446
+
447
+ // Error case: some IDs not found
448
+ if (details?.notFound?.length) {
449
+ let text = `${theme.fg("error", ICON_ERROR)} Not found: ${details.notFound.join(", ")}`;
450
+ if (details.availableIds?.length) {
451
+ text += `\n${theme.fg("dim", "Available:")} ${details.availableIds.join(", ")}`;
452
+ } else {
453
+ text += `\n${theme.fg("dim", "No outputs available in current session")}`;
454
+ }
455
+ return new Text(text, 0, 0);
456
+ }
457
+
458
+ const outputs = details?.outputs ?? [];
459
+
460
+ // No session case
461
+ if (outputs.length === 0) {
462
+ const textContent = result.content?.find((c: any) => c.type === "text")?.text;
463
+ return new Text(
464
+ `${theme.fg("warning", ICON_WARNING)} ${theme.fg("muted", textContent || "No outputs")}`,
465
+ 0,
466
+ 0,
467
+ );
468
+ }
469
+
470
+ // Success: single output
471
+ if (outputs.length === 1) {
472
+ const o = outputs[0];
473
+ const summary = `read ${o.id}.out.md (${o.lineCount} lines, ${formatBytes(o.charCount)})`;
474
+ return new Text(`${theme.fg("success", ICON_SUCCESS)} ${theme.fg("dim", summary)}`, 0, 0);
475
+ }
476
+
477
+ // Success: multiple outputs (tree display)
478
+ const expandHint = expanded ? "" : theme.fg("dim", " (Ctrl+O to expand)");
479
+ let text = `${theme.fg("success", ICON_SUCCESS)} ${theme.fg("dim", `read ${outputs.length} outputs`)}${expandHint}`;
480
+
481
+ const maxOutputs = expanded ? outputs.length : Math.min(outputs.length, 5);
482
+ for (let i = 0; i < maxOutputs; i++) {
483
+ const o = outputs[i];
484
+ const isLast = i === maxOutputs - 1 && (expanded || outputs.length <= 5);
485
+ const branch = isLast ? TREE_END : TREE_MID;
486
+ text += `\n ${theme.fg("dim", branch)} ${theme.fg("accent", o.id)} ${theme.fg("dim", `(${o.lineCount} lines)`)}`;
487
+ }
488
+
489
+ if (!expanded && outputs.length > 5) {
490
+ text += `\n ${theme.fg("dim", TREE_END)} ${theme.fg("muted", `… ${outputs.length - 5} more outputs`)}`;
491
+ }
492
+
493
+ return new Text(text, 0, 0);
494
+ },
495
+ };
496
+
419
497
  // ============================================================================
420
498
  // Task Renderer
421
499
  // ============================================================================
@@ -534,6 +612,7 @@ export const toolRenderers: Record<
534
612
  notebook: notebookRenderer,
535
613
  ls: lsRenderer,
536
614
  lsp: lspRenderer,
615
+ output: outputRenderer,
537
616
  task: taskRenderer,
538
617
  web_fetch: webFetchRenderer,
539
618
  web_search: webSearchRenderer,
@@ -0,0 +1,268 @@
1
+ /**
2
+ * Review tools - report_finding and submit_review
3
+ *
4
+ * Used by the reviewer agent to report findings in a structured way.
5
+ * Both tools are hidden by default - only enabled when explicitly listed in agent's tools.
6
+ */
7
+
8
+ import type { AgentTool } from "@oh-my-pi/pi-agent-core";
9
+ import type { Component } from "@oh-my-pi/pi-tui";
10
+ import { Container, Spacer, Text } from "@oh-my-pi/pi-tui";
11
+ import { Type } from "@sinclair/typebox";
12
+ import type { Theme } from "../../modes/interactive/theme/theme";
13
+
14
+ const PRIORITY_LABELS: Record<number, string> = {
15
+ 0: "P0",
16
+ 1: "P1",
17
+ 2: "P2",
18
+ 3: "P3",
19
+ };
20
+
21
+ const _PRIORITY_DESCRIPTIONS: Record<number, string> = {
22
+ 0: "Drop everything to fix. Blocking release, operations, or major usage.",
23
+ 1: "Urgent. Should be addressed in the next cycle.",
24
+ 2: "Normal. To be fixed eventually.",
25
+ 3: "Low. Nice to have.",
26
+ };
27
+
28
+ // report_finding schema
29
+ const ReportFindingParams = Type.Object({
30
+ title: Type.String({
31
+ description: "≤80 chars, imperative, prefixed with [P0-P3]. E.g., '[P1] Un-padding slices along wrong dimension'",
32
+ }),
33
+ body: Type.String({
34
+ description: "Markdown explaining why this is a problem. One paragraph max.",
35
+ }),
36
+ priority: Type.Union([Type.Literal(0), Type.Literal(1), Type.Literal(2), Type.Literal(3)], {
37
+ description: "0=P0 (critical), 1=P1 (urgent), 2=P2 (normal), 3=P3 (low)",
38
+ }),
39
+ confidence: Type.Number({
40
+ minimum: 0,
41
+ maximum: 1,
42
+ description: "Confidence score 0.0-1.0",
43
+ }),
44
+ file_path: Type.String({ description: "Absolute path to the file" }),
45
+ line_start: Type.Number({ description: "Start line of the issue" }),
46
+ line_end: Type.Number({ description: "End line of the issue" }),
47
+ });
48
+
49
+ interface ReportFindingDetails {
50
+ title: string;
51
+ body: string;
52
+ priority: number;
53
+ confidence: number;
54
+ file_path: string;
55
+ line_start: number;
56
+ line_end: number;
57
+ }
58
+
59
+ export const reportFindingTool: AgentTool<typeof ReportFindingParams, ReportFindingDetails, Theme> = {
60
+ name: "report_finding",
61
+ label: "Report Finding",
62
+ description: "Report a code review finding. Use this for each issue found. Call submit_review when done.",
63
+ parameters: ReportFindingParams,
64
+ hidden: true,
65
+
66
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
67
+ const { title, body, priority, confidence, file_path, line_start, line_end } = params;
68
+ const location = `${file_path}:${line_start}${line_end !== line_start ? `-${line_end}` : ""}`;
69
+
70
+ return {
71
+ content: [
72
+ {
73
+ type: "text",
74
+ text: `Finding recorded: ${PRIORITY_LABELS[priority]} ${title}\nLocation: ${location}\nConfidence: ${(confidence * 100).toFixed(0)}%`,
75
+ },
76
+ ],
77
+ details: { title, body, priority, confidence, file_path, line_start, line_end },
78
+ };
79
+ },
80
+
81
+ renderCall(args, theme): Component {
82
+ const priority = PRIORITY_LABELS[args.priority as number] ?? "P?";
83
+ const color = args.priority === 0 ? "error" : args.priority === 1 ? "warning" : "muted";
84
+ const titleText = String(args.title).replace(/^\[P\d\]\s*/, "");
85
+ return new Text(
86
+ `${theme.fg("toolTitle", theme.bold("report_finding "))}${theme.fg(color, `[${priority}]`)} ${theme.fg("dim", titleText)}`,
87
+ 0,
88
+ 0,
89
+ );
90
+ },
91
+
92
+ renderResult(result, _options, theme): Component {
93
+ const { details } = result;
94
+ if (!details) {
95
+ const text = result.content[0];
96
+ return new Text(text?.type === "text" ? text.text : "", 0, 0);
97
+ }
98
+
99
+ const priority = PRIORITY_LABELS[details.priority] ?? "P?";
100
+ const color = details.priority === 0 ? "error" : details.priority === 1 ? "warning" : "muted";
101
+ const location = `${details.file_path}:${details.line_start}${details.line_end !== details.line_start ? `-${details.line_end}` : ""}`;
102
+
103
+ return new Text(
104
+ `${theme.fg("success", "✓")} ${theme.fg(color, `[${priority}]`)} ${theme.fg("dim", location)}`,
105
+ 0,
106
+ 0,
107
+ );
108
+ },
109
+ };
110
+
111
+ // submit_review schema
112
+ const SubmitReviewParams = Type.Object({
113
+ overall_correctness: Type.Union([Type.Literal("correct"), Type.Literal("incorrect")], {
114
+ description: "Whether the patch is correct (no bugs, tests won't break)",
115
+ }),
116
+ explanation: Type.String({
117
+ description: "1-3 sentence explanation justifying the verdict",
118
+ }),
119
+ confidence: Type.Number({
120
+ minimum: 0,
121
+ maximum: 1,
122
+ description: "Overall confidence score 0.0-1.0",
123
+ }),
124
+ });
125
+
126
+ interface SubmitReviewDetails {
127
+ overall_correctness: "correct" | "incorrect";
128
+ explanation: string;
129
+ confidence: number;
130
+ }
131
+
132
+ export const submitReviewTool: AgentTool<typeof SubmitReviewParams, SubmitReviewDetails, Theme> = {
133
+ name: "submit_review",
134
+ label: "Submit Review",
135
+ description: "Submit the final review verdict. Call this after all findings have been reported.",
136
+ parameters: SubmitReviewParams,
137
+ hidden: true,
138
+
139
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
140
+ const { overall_correctness, explanation, confidence } = params;
141
+
142
+ let summary = `## Review Summary\n\n`;
143
+ summary += `**Verdict:** ${overall_correctness === "correct" ? "✓ Patch is correct" : "✗ Patch is incorrect"}\n`;
144
+ summary += `**Confidence:** ${(confidence * 100).toFixed(0)}%\n\n`;
145
+ summary += explanation;
146
+
147
+ return {
148
+ content: [{ type: "text", text: summary }],
149
+ details: { overall_correctness, explanation, confidence },
150
+ };
151
+ },
152
+
153
+ renderCall(args, theme): Component {
154
+ const verdict = args.overall_correctness === "correct" ? "correct" : "incorrect";
155
+ const color = args.overall_correctness === "correct" ? "success" : "error";
156
+ return new Text(
157
+ `${theme.fg("toolTitle", theme.bold("submit_review "))}${theme.fg(color, verdict)} ${theme.fg("dim", `(${((args.confidence as number) * 100).toFixed(0)}%)`)}`,
158
+ 0,
159
+ 0,
160
+ );
161
+ },
162
+
163
+ renderResult(result, { expanded }, theme): Component {
164
+ const { details } = result;
165
+ if (!details) {
166
+ const text = result.content[0];
167
+ return new Text(text?.type === "text" ? text.text : "", 0, 0);
168
+ }
169
+
170
+ const container = new Container();
171
+ const verdictColor = details.overall_correctness === "correct" ? "success" : "error";
172
+ const verdictIcon = details.overall_correctness === "correct" ? "✓" : "✗";
173
+
174
+ container.addChild(
175
+ new Text(
176
+ `${theme.fg(verdictColor, verdictIcon)} Patch is ${theme.fg(verdictColor, details.overall_correctness)} ${theme.fg("dim", `(${(details.confidence * 100).toFixed(0)}% confidence)`)}`,
177
+ 0,
178
+ 0,
179
+ ),
180
+ );
181
+
182
+ if (expanded) {
183
+ container.addChild(new Spacer(1));
184
+ container.addChild(new Text(theme.fg("dim", details.explanation), 0, 0));
185
+ }
186
+
187
+ return container;
188
+ },
189
+ };
190
+
191
+ export function createReportFindingTool(): AgentTool<typeof ReportFindingParams, ReportFindingDetails, Theme> {
192
+ return reportFindingTool;
193
+ }
194
+
195
+ export function createSubmitReviewTool(): AgentTool<typeof SubmitReviewParams, SubmitReviewDetails, Theme> {
196
+ return submitReviewTool;
197
+ }
198
+
199
+ // Re-export types for external use
200
+ export type { ReportFindingDetails, SubmitReviewDetails };
201
+
202
+ // ─────────────────────────────────────────────────────────────────────────────
203
+ // Subprocess tool handlers - registered for extraction/rendering in task tool
204
+ // ─────────────────────────────────────────────────────────────────────────────
205
+
206
+ import path from "node:path";
207
+ import { subprocessToolRegistry } from "./task/subprocess-tool-registry";
208
+
209
+ // Register report_finding handler
210
+ subprocessToolRegistry.register<ReportFindingDetails>("report_finding", {
211
+ extractData: (event) => event.result?.details as ReportFindingDetails | undefined,
212
+
213
+ renderInline: (data, theme) => {
214
+ const priority = PRIORITY_LABELS[data.priority] ?? "P?";
215
+ const color = data.priority === 0 ? "error" : data.priority === 1 ? "warning" : "muted";
216
+ const titleText = data.title.replace(/^\[P\d\]\s*/, "");
217
+ const loc = `${path.basename(data.file_path)}:${data.line_start}`;
218
+ return new Text(`${theme.fg(color, `[${priority}]`)} ${titleText} ${theme.fg("dim", loc)}`, 0, 0);
219
+ },
220
+
221
+ renderFinal: (allData, theme, expanded) => {
222
+ const container = new Container();
223
+ const displayCount = expanded ? allData.length : Math.min(3, allData.length);
224
+
225
+ for (let i = 0; i < displayCount; i++) {
226
+ const data = allData[i];
227
+ const priority = PRIORITY_LABELS[data.priority] ?? "P?";
228
+ const color = data.priority === 0 ? "error" : data.priority === 1 ? "warning" : "muted";
229
+ const titleText = data.title.replace(/^\[P\d\]\s*/, "");
230
+ const loc = `${path.basename(data.file_path)}:${data.line_start}`;
231
+
232
+ container.addChild(
233
+ new Text(` ${theme.fg(color, `[${priority}]`)} ${titleText} ${theme.fg("dim", loc)}`, 0, 0),
234
+ );
235
+
236
+ if (expanded && data.body) {
237
+ container.addChild(new Text(` ${theme.fg("dim", data.body)}`, 0, 0));
238
+ }
239
+ }
240
+
241
+ if (allData.length > displayCount) {
242
+ container.addChild(new Text(theme.fg("dim", ` ... ${allData.length - displayCount} more findings`), 0, 0));
243
+ }
244
+
245
+ return container;
246
+ },
247
+ });
248
+
249
+ // Register submit_review handler
250
+ subprocessToolRegistry.register<SubmitReviewDetails>("submit_review", {
251
+ extractData: (event) => event.result?.details as SubmitReviewDetails | undefined,
252
+
253
+ // Terminate subprocess after review is submitted
254
+ shouldTerminate: () => true,
255
+
256
+ renderInline: (data, theme) => {
257
+ const verdictColor = data.overall_correctness === "correct" ? "success" : "error";
258
+ const verdictIcon = data.overall_correctness === "correct" ? "✓" : "✗";
259
+ return new Text(
260
+ `${theme.fg(verdictColor, verdictIcon)} Review: ${theme.fg(verdictColor, data.overall_correctness)} (${(data.confidence * 100).toFixed(0)}%)`,
261
+ 0,
262
+ 0,
263
+ );
264
+ },
265
+
266
+ // Note: renderFinal is NOT used for submit_review - we use the combined
267
+ // renderReviewResult in render.ts to show verdict + findings together
268
+ });
@@ -1,17 +1,24 @@
1
1
  /**
2
2
  * Bundled agent definitions.
3
3
  *
4
- * Agents are loaded from .md files in the bundled-agents directory.
5
- * These serve as defaults when no user/project agents are discovered.
4
+ * Agents are embedded at build time via Bun's import with { type: "text" }.
6
5
  */
7
6
 
8
- import * as fs from "node:fs";
9
- import * as path from "node:path";
10
- import { fileURLToPath } from "node:url";
11
- import type { AgentDefinition, AgentSource } from "./types.js";
12
-
13
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
- const BUNDLED_AGENTS_DIR = path.join(__dirname, "bundled-agents");
7
+ // Embed agent markdown files at build time
8
+ import browserMd from "./bundled-agents/browser.md" with { type: "text" };
9
+ import exploreMd from "./bundled-agents/explore.md" with { type: "text" };
10
+ import planMd from "./bundled-agents/plan.md" with { type: "text" };
11
+ import reviewerMd from "./bundled-agents/reviewer.md" with { type: "text" };
12
+ import taskMd from "./bundled-agents/task.md" with { type: "text" };
13
+ import type { AgentDefinition, AgentSource } from "./types";
14
+
15
+ const EMBEDDED_AGENTS: { name: string; content: string }[] = [
16
+ { name: "browser.md", content: browserMd },
17
+ { name: "explore.md", content: exploreMd },
18
+ { name: "plan.md", content: planMd },
19
+ { name: "reviewer.md", content: reviewerMd },
20
+ { name: "task.md", content: taskMd },
21
+ ];
15
22
 
16
23
  /**
17
24
  * Parse YAML frontmatter from markdown content.
@@ -47,16 +54,9 @@ function parseFrontmatter(content: string): { frontmatter: Record<string, string
47
54
  }
48
55
 
49
56
  /**
50
- * Load a single agent from a markdown file.
57
+ * Parse an agent from embedded content.
51
58
  */
52
- function loadAgentFromFile(filePath: string, source: AgentSource): AgentDefinition | null {
53
- let content: string;
54
- try {
55
- content = fs.readFileSync(filePath, "utf-8");
56
- } catch {
57
- return null;
58
- }
59
-
59
+ function parseAgent(fileName: string, content: string, source: AgentSource): AgentDefinition | null {
60
60
  const { frontmatter, body } = parseFrontmatter(content);
61
61
 
62
62
  if (!frontmatter.name || !frontmatter.description) {
@@ -79,7 +79,7 @@ function loadAgentFromFile(filePath: string, source: AgentSource): AgentDefiniti
79
79
  recursive,
80
80
  systemPrompt: body,
81
81
  source,
82
- filePath,
82
+ filePath: `embedded:${fileName}`,
83
83
  };
84
84
  }
85
85
 
@@ -87,7 +87,7 @@ function loadAgentFromFile(filePath: string, source: AgentSource): AgentDefiniti
87
87
  let bundledAgentsCache: AgentDefinition[] | null = null;
88
88
 
89
89
  /**
90
- * Load all bundled agents from the bundled-agents directory.
90
+ * Load all bundled agents from embedded content.
91
91
  * Results are cached after first load.
92
92
  */
93
93
  export function loadBundledAgents(): AgentDefinition[] {
@@ -97,24 +97,8 @@ export function loadBundledAgents(): AgentDefinition[] {
97
97
 
98
98
  const agents: AgentDefinition[] = [];
99
99
 
100
- if (!fs.existsSync(BUNDLED_AGENTS_DIR)) {
101
- bundledAgentsCache = agents;
102
- return agents;
103
- }
104
-
105
- let entries: fs.Dirent[];
106
- try {
107
- entries = fs.readdirSync(BUNDLED_AGENTS_DIR, { withFileTypes: true });
108
- } catch {
109
- bundledAgentsCache = agents;
110
- return agents;
111
- }
112
-
113
- for (const entry of entries) {
114
- if (!entry.name.endsWith(".md")) continue;
115
-
116
- const filePath = path.join(BUNDLED_AGENTS_DIR, entry.name);
117
- const agent = loadAgentFromFile(filePath, "bundled");
100
+ for (const { name, content } of EMBEDDED_AGENTS) {
101
+ const agent = parseAgent(name, content, "bundled");
118
102
  if (agent) {
119
103
  agents.push(agent);
120
104
  }
@@ -1,59 +1,74 @@
1
1
  ---
2
2
  name: reviewer
3
- description: Expert code reviewer for PRs and implementation changes
4
- tools: read, grep, glob, ls, bash
3
+ description: Code review specialist for quality and security analysis
4
+ tools: read, grep, find, ls, bash, task, report_finding, submit_review
5
5
  model: pi/slow, gpt-5.2-codex, gpt-5.2, codex, gpt
6
6
  ---
7
7
 
8
- You are an expert code reviewer. Analyze code changes and provide thorough reviews.
8
+ You are acting as a reviewer for a proposed code change made by another engineer.
9
9
 
10
- ## For PR Reviews
10
+ Bash is for read-only commands only: `git diff`, `git log`, `git show`, `gh pr diff`. Do NOT modify files or run builds.
11
11
 
12
- 1. If no PR number provided, run `gh pr list` to show open PRs
13
- 2. If PR number provided:
14
- - `gh pr view <number>` to get PR details
15
- - `gh pr diff <number>` to get the diff
16
- 3. Analyze changes and provide review
12
+ # Review Strategy
17
13
 
18
- ## For Implementation Reviews
14
+ 1. Run `git diff` (or `gh pr diff <number>`) to see the changes
15
+ 2. Read the modified files for full context
16
+ 3. For large changes spanning multiple files/modules, use `task` with `explore` agents in parallel to gather context faster
17
+ 4. Analyze for bugs, security issues, and code quality problems
18
+ 5. Use `report_finding` for each issue found
19
+ 6. Use `submit_review` to provide final verdict
19
20
 
20
- When reviewing implementation output from another agent:
21
+ # Parallelization
21
22
 
22
- 1. Read the files that were changed
23
- 2. Understand the context and requirements
24
- 3. Analyze the implementation quality
23
+ For reviews touching many files, spawn `explore` agents to research in parallel:
24
+ - Each agent can investigate a different module or concern
25
+ - Example: one explores test coverage, another checks related implementations
26
+ - Gather their findings, then synthesize into your review
25
27
 
26
- ## Review Focus
28
+ # What to Flag
27
29
 
28
- - **Correctness**: Does the code do what it's supposed to?
29
- - **Project Conventions**: Does it follow existing patterns?
30
- - **Performance**: Any performance implications?
31
- - **Test Coverage**: Are changes adequately tested?
32
- - **Security**: Any security considerations?
33
- - **Edge Cases**: Are edge cases handled?
30
+ Only flag issues where ALL of these apply:
34
31
 
35
- ## Output Format
32
+ 1. It meaningfully impacts the accuracy, performance, security, or maintainability of the code
33
+ 2. The bug is discrete and actionable (not a general issue or combination of multiple issues)
34
+ 3. Fixing it doesn't demand rigor not present elsewhere in the codebase
35
+ 4. The bug was introduced in this commit (don't flag pre-existing bugs)
36
+ 5. The author would likely fix the issue if made aware of it
37
+ 6. The bug doesn't rely on unstated assumptions about the codebase or author's intent
38
+ 7. You can identify specific code that is provably affected (speculation is not enough)
39
+ 8. The issue is clearly not an intentional change by the author
36
40
 
37
- ### Overview
41
+ # Priority Levels
38
42
 
39
- What the changes do.
43
+ - **P0**: Drop everything to fix. Blocking release, operations, or major usage. Only use for universal issues that do not depend on assumptions about inputs.
44
+ - **P1**: Urgent. Should be addressed in the next cycle.
45
+ - **P2**: Normal. To be fixed eventually.
46
+ - **P3**: Low. Nice to have.
40
47
 
41
- ### Strengths
48
+ # Comment Guidelines
42
49
 
43
- What's done well.
50
+ 1. Be clear about WHY the issue is a bug
51
+ 2. Communicate severity appropriately - don't overstate
52
+ 3. Keep body to one paragraph max
53
+ 4. Code snippets should be ≤3 lines, wrapped in markdown code tags
54
+ 5. Clearly state what conditions are necessary for the bug to arise
55
+ 6. Tone: matter-of-fact, not accusatory or overly positive
56
+ 7. Write so the author can immediately grasp the idea without close reading
57
+ 8. Avoid flattery and phrases like "Great job...", "Thanks for..."
44
58
 
45
- ### Issues
59
+ # CRITICAL
46
60
 
47
- Problems that should be fixed (with file:line references).
61
+ You MUST call `submit_review` before ending your response, even if you found no issues.
62
+ The review is only considered complete when `submit_review` is called.
63
+ Failure to call `submit_review` means the review was not submitted.
48
64
 
49
- ### Suggestions
65
+ # Output
50
66
 
51
- Improvements to consider (optional, not blocking).
67
+ - Use `report_finding` for each issue. Continue until you've listed every qualifying finding.
68
+ - If there is no finding that a person would definitely want to fix, prefer outputting no findings.
69
+ - Ignore trivial style unless it obscures meaning or violates documented standards.
70
+ - Use `submit_review` at the end with your overall verdict:
71
+ - **correct**: Existing code and tests will not break, patch is free of bugs and blocking issues
72
+ - **incorrect**: Has bugs or blocking issues that must be addressed
52
73
 
53
- ### Verdict
54
-
55
- - ✅ **Approve**: Ready to merge/complete
56
- - 🔄 **Request Changes**: Issues must be addressed
57
- - 💬 **Comment**: Minor suggestions, can proceed
58
-
59
- Keep reviews concise but thorough. Focus on substance over style nitpicks.
74
+ Ignore non-blocking issues (style, formatting, typos, documentation, nits) when determining correctness.
@@ -1,17 +1,23 @@
1
1
  /**
2
2
  * Workflow commands for orchestrating multi-agent workflows.
3
3
  *
4
- * Commands are loaded from .md files with YAML frontmatter.
5
- * They define multi-step workflows that chain agent outputs.
4
+ * Commands are embedded at build time via Bun's import with { type: "text" }.
6
5
  */
7
6
 
8
7
  import * as fs from "node:fs";
9
8
  import * as os from "node:os";
10
9
  import * as path from "node:path";
11
- import { fileURLToPath } from "node:url";
12
10
 
13
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
- const BUNDLED_COMMANDS_DIR = path.join(__dirname, "bundled-commands");
11
+ // Embed command markdown files at build time
12
+ import architectPlanMd from "./bundled-commands/architect-plan.md" with { type: "text" };
13
+ import implementMd from "./bundled-commands/implement.md" with { type: "text" };
14
+ import implementWithCriticMd from "./bundled-commands/implement-with-critic.md" with { type: "text" };
15
+
16
+ const EMBEDDED_COMMANDS: { name: string; content: string }[] = [
17
+ { name: "architect-plan.md", content: architectPlanMd },
18
+ { name: "implement-with-critic.md", content: implementWithCriticMd },
19
+ { name: "implement.md", content: implementMd },
20
+ ];
15
21
 
16
22
  /** Workflow command definition */
17
23
  export interface WorkflowCommand {
@@ -56,9 +62,9 @@ function parseFrontmatter(content: string): { frontmatter: Record<string, string
56
62
  }
57
63
 
58
64
  /**
59
- * Load commands from a directory.
65
+ * Load commands from a directory (for user/project commands).
60
66
  */
61
- function loadCommandsFromDir(dir: string, source: "bundled" | "user" | "project"): WorkflowCommand[] {
67
+ function loadCommandsFromDir(dir: string, source: "user" | "project"): WorkflowCommand[] {
62
68
  const commands: WorkflowCommand[] = [];
63
69
 
64
70
  if (!fs.existsSync(dir)) {
@@ -137,15 +143,30 @@ function findNearestDir(cwd: string, relPath: string): string | null {
137
143
  let bundledCommandsCache: WorkflowCommand[] | null = null;
138
144
 
139
145
  /**
140
- * Load all bundled commands.
146
+ * Load all bundled commands from embedded content.
141
147
  */
142
148
  export function loadBundledCommands(): WorkflowCommand[] {
143
149
  if (bundledCommandsCache !== null) {
144
150
  return bundledCommandsCache;
145
151
  }
146
152
 
147
- bundledCommandsCache = loadCommandsFromDir(BUNDLED_COMMANDS_DIR, "bundled");
148
- return bundledCommandsCache;
153
+ const commands: WorkflowCommand[] = [];
154
+
155
+ for (const { name, content } of EMBEDDED_COMMANDS) {
156
+ const { frontmatter, body } = parseFrontmatter(content);
157
+ const cmdName = name.replace(/\.md$/, "");
158
+
159
+ commands.push({
160
+ name: cmdName,
161
+ description: frontmatter.description || "",
162
+ instructions: body,
163
+ source: "bundled",
164
+ filePath: `embedded:${name}`,
165
+ });
166
+ }
167
+
168
+ bundledCommandsCache = commands;
169
+ return commands;
149
170
  }
150
171
 
151
172
  /**