@oh-my-pi/pi-coding-agent 13.18.0 → 14.0.2

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 (235) hide show
  1. package/CHANGELOG.md +316 -1
  2. package/package.json +86 -24
  3. package/scripts/format-prompts.ts +2 -2
  4. package/src/autoresearch/apply-contract-to-state.ts +24 -0
  5. package/src/autoresearch/contract.ts +0 -44
  6. package/src/autoresearch/dashboard.ts +1 -2
  7. package/src/autoresearch/git.ts +116 -30
  8. package/src/autoresearch/helpers.ts +49 -0
  9. package/src/autoresearch/index.ts +28 -187
  10. package/src/autoresearch/prompt.md +26 -9
  11. package/src/autoresearch/state.ts +0 -6
  12. package/src/autoresearch/tools/init-experiment.ts +202 -117
  13. package/src/autoresearch/tools/log-experiment.ts +123 -178
  14. package/src/autoresearch/tools/run-experiment.ts +48 -10
  15. package/src/autoresearch/types.ts +2 -2
  16. package/src/capability/index.ts +4 -2
  17. package/src/cli/file-processor.ts +3 -3
  18. package/src/cli/grep-cli.ts +8 -8
  19. package/src/cli/grievances-cli.ts +78 -0
  20. package/src/cli/read-cli.ts +67 -0
  21. package/src/cli/setup-cli.ts +4 -4
  22. package/src/cli/update-cli.ts +3 -3
  23. package/src/cli.ts +2 -0
  24. package/src/commands/grep.ts +6 -1
  25. package/src/commands/grievances.ts +20 -0
  26. package/src/commands/read.ts +33 -0
  27. package/src/commit/agentic/agent.ts +5 -8
  28. package/src/commit/agentic/index.ts +22 -26
  29. package/src/commit/agentic/tools/analyze-file.ts +3 -3
  30. package/src/commit/agentic/tools/git-file-diff.ts +3 -6
  31. package/src/commit/agentic/tools/git-hunk.ts +3 -3
  32. package/src/commit/agentic/tools/git-overview.ts +6 -9
  33. package/src/commit/agentic/tools/index.ts +6 -8
  34. package/src/commit/agentic/tools/propose-commit.ts +4 -7
  35. package/src/commit/agentic/tools/recent-commits.ts +3 -3
  36. package/src/commit/agentic/tools/split-commit.ts +4 -4
  37. package/src/commit/agentic/validation.ts +1 -1
  38. package/src/commit/analysis/conventional.ts +4 -4
  39. package/src/commit/analysis/summary.ts +3 -3
  40. package/src/commit/changelog/generate.ts +4 -4
  41. package/src/commit/changelog/index.ts +5 -9
  42. package/src/commit/map-reduce/map-phase.ts +4 -4
  43. package/src/commit/map-reduce/reduce-phase.ts +4 -4
  44. package/src/commit/pipeline.ts +13 -16
  45. package/src/config/keybindings.ts +7 -6
  46. package/src/config/prompt-templates.ts +44 -226
  47. package/src/config/resolve-config-value.ts +4 -2
  48. package/src/config/settings-schema.ts +98 -2
  49. package/src/config/settings.ts +25 -26
  50. package/src/dap/client.ts +674 -0
  51. package/src/dap/config.ts +150 -0
  52. package/src/dap/defaults.json +211 -0
  53. package/src/dap/index.ts +4 -0
  54. package/src/dap/session.ts +1255 -0
  55. package/src/dap/types.ts +600 -0
  56. package/src/debug/log-viewer.ts +3 -2
  57. package/src/discovery/builtin.ts +1 -2
  58. package/src/discovery/codex.ts +2 -2
  59. package/src/discovery/github.ts +2 -1
  60. package/src/discovery/helpers.ts +2 -2
  61. package/src/discovery/opencode.ts +2 -2
  62. package/src/edit/diff.ts +818 -0
  63. package/src/edit/index.ts +309 -0
  64. package/src/edit/line-hash.ts +67 -0
  65. package/src/edit/modes/chunk.ts +454 -0
  66. package/src/{patch → edit/modes}/hashline.ts +741 -361
  67. package/src/{patch/applicator.ts → edit/modes/patch.ts} +420 -117
  68. package/src/{patch/fuzzy.ts → edit/modes/replace.ts} +519 -197
  69. package/src/{patch → edit}/normalize.ts +97 -76
  70. package/src/{patch/shared.ts → edit/renderer.ts} +181 -108
  71. package/src/exec/bash-executor.ts +4 -2
  72. package/src/exec/idle-timeout-watchdog.ts +126 -0
  73. package/src/exec/non-interactive-env.ts +5 -0
  74. package/src/extensibility/custom-commands/bundled/ci-green/index.ts +6 -18
  75. package/src/extensibility/custom-commands/bundled/review/index.ts +45 -43
  76. package/src/extensibility/custom-commands/loader.ts +1 -2
  77. package/src/extensibility/custom-tools/loader.ts +34 -11
  78. package/src/extensibility/custom-tools/types.ts +1 -1
  79. package/src/extensibility/extensions/loader.ts +9 -4
  80. package/src/extensibility/extensions/runner.ts +24 -1
  81. package/src/extensibility/extensions/types.ts +4 -2
  82. package/src/extensibility/hooks/loader.ts +5 -6
  83. package/src/extensibility/hooks/types.ts +2 -2
  84. package/src/extensibility/plugins/doctor.ts +2 -1
  85. package/src/extensibility/plugins/marketplace/fetcher.ts +2 -57
  86. package/src/extensibility/plugins/marketplace/source-resolver.ts +4 -4
  87. package/src/extensibility/slash-commands.ts +3 -7
  88. package/src/index.ts +3 -1
  89. package/src/internal-urls/docs-index.generated.ts +11 -11
  90. package/src/ipy/executor.ts +58 -17
  91. package/src/ipy/gateway-coordinator.ts +6 -4
  92. package/src/ipy/kernel.ts +45 -22
  93. package/src/ipy/runtime.ts +2 -2
  94. package/src/lsp/client.ts +7 -4
  95. package/src/lsp/clients/lsp-linter-client.ts +4 -4
  96. package/src/lsp/config.ts +2 -2
  97. package/src/lsp/defaults.json +688 -154
  98. package/src/lsp/index.ts +234 -45
  99. package/src/lsp/lspmux.ts +2 -2
  100. package/src/lsp/startup-events.ts +13 -0
  101. package/src/lsp/types.ts +12 -1
  102. package/src/lsp/utils.ts +8 -1
  103. package/src/main.ts +125 -47
  104. package/src/memories/index.ts +4 -5
  105. package/src/modes/acp/acp-agent.ts +563 -163
  106. package/src/modes/acp/acp-event-mapper.ts +9 -1
  107. package/src/modes/acp/acp-mode.ts +4 -2
  108. package/src/modes/components/agent-dashboard.ts +3 -4
  109. package/src/modes/components/diff.ts +6 -7
  110. package/src/modes/components/footer.ts +9 -29
  111. package/src/modes/components/hook-editor.ts +3 -3
  112. package/src/modes/components/hook-selector.ts +6 -1
  113. package/src/modes/components/read-tool-group.ts +6 -12
  114. package/src/modes/components/session-observer-overlay.ts +472 -0
  115. package/src/modes/components/settings-defs.ts +24 -0
  116. package/src/modes/components/status-line.ts +15 -61
  117. package/src/modes/components/tool-execution.ts +1 -1
  118. package/src/modes/components/welcome.ts +1 -1
  119. package/src/modes/controllers/btw-controller.ts +2 -2
  120. package/src/modes/controllers/command-controller.ts +4 -2
  121. package/src/modes/controllers/event-controller.ts +59 -2
  122. package/src/modes/controllers/extension-ui-controller.ts +1 -0
  123. package/src/modes/controllers/input-controller.ts +15 -8
  124. package/src/modes/controllers/selector-controller.ts +26 -0
  125. package/src/modes/index.ts +20 -2
  126. package/src/modes/interactive-mode.ts +278 -69
  127. package/src/modes/rpc/host-tools.ts +186 -0
  128. package/src/modes/rpc/rpc-client.ts +178 -13
  129. package/src/modes/rpc/rpc-mode.ts +73 -3
  130. package/src/modes/rpc/rpc-types.ts +53 -1
  131. package/src/modes/session-observer-registry.ts +146 -0
  132. package/src/modes/shared.ts +0 -42
  133. package/src/modes/theme/theme.ts +80 -8
  134. package/src/modes/types.ts +4 -2
  135. package/src/modes/utils/keybinding-matchers.ts +9 -0
  136. package/src/prompts/system/custom-system-prompt.md +5 -0
  137. package/src/prompts/system/system-prompt.md +8 -1
  138. package/src/prompts/tools/chunk-edit.md +219 -0
  139. package/src/prompts/tools/debug.md +43 -0
  140. package/src/prompts/tools/grep.md +3 -0
  141. package/src/prompts/tools/lsp.md +5 -5
  142. package/src/prompts/tools/read-chunk.md +17 -0
  143. package/src/prompts/tools/read.md +19 -5
  144. package/src/sdk.ts +216 -165
  145. package/src/secrets/index.ts +1 -1
  146. package/src/secrets/obfuscator.ts +25 -17
  147. package/src/session/agent-session.ts +381 -286
  148. package/src/session/agent-storage.ts +12 -12
  149. package/src/session/compaction/branch-summarization.ts +3 -3
  150. package/src/session/compaction/compaction.ts +5 -6
  151. package/src/session/compaction/utils.ts +3 -3
  152. package/src/session/history-storage.ts +62 -19
  153. package/src/session/messages.ts +3 -3
  154. package/src/session/session-dump-format.ts +203 -0
  155. package/src/session/session-manager.ts +15 -5
  156. package/src/session/session-storage.ts +4 -2
  157. package/src/session/streaming-output.ts +1 -1
  158. package/src/session/tool-choice-queue.ts +213 -0
  159. package/src/slash-commands/builtin-registry.ts +56 -8
  160. package/src/ssh/connection-manager.ts +2 -2
  161. package/src/ssh/sshfs-mount.ts +5 -5
  162. package/src/stt/downloader.ts +4 -4
  163. package/src/stt/recorder.ts +4 -4
  164. package/src/stt/transcriber.ts +2 -2
  165. package/src/system-prompt.ts +25 -13
  166. package/src/task/agents.ts +5 -6
  167. package/src/task/commands.ts +2 -5
  168. package/src/task/executor.ts +32 -4
  169. package/src/task/index.ts +91 -82
  170. package/src/task/template.ts +2 -2
  171. package/src/task/types.ts +25 -0
  172. package/src/task/worktree.ts +131 -149
  173. package/src/tools/ask.ts +2 -3
  174. package/src/tools/ast-edit.ts +7 -7
  175. package/src/tools/ast-grep.ts +7 -7
  176. package/src/tools/auto-generated-guard.ts +36 -41
  177. package/src/tools/await-tool.ts +2 -2
  178. package/src/tools/bash.ts +5 -23
  179. package/src/tools/browser.ts +4 -5
  180. package/src/tools/calculator.ts +2 -3
  181. package/src/tools/cancel-job.ts +2 -2
  182. package/src/tools/checkpoint.ts +3 -3
  183. package/src/tools/debug.ts +1007 -0
  184. package/src/tools/exit-plan-mode.ts +3 -3
  185. package/src/tools/fetch.ts +67 -3
  186. package/src/tools/find.ts +4 -5
  187. package/src/tools/fs-cache-invalidation.ts +5 -0
  188. package/src/tools/gemini-image.ts +13 -5
  189. package/src/tools/gh.ts +130 -308
  190. package/src/tools/grep.ts +57 -9
  191. package/src/tools/index.ts +44 -22
  192. package/src/tools/inspect-image.ts +4 -4
  193. package/src/tools/output-meta.ts +1 -1
  194. package/src/tools/python.ts +19 -6
  195. package/src/tools/read.ts +211 -146
  196. package/src/tools/render-mermaid.ts +2 -3
  197. package/src/tools/render-utils.ts +20 -6
  198. package/src/tools/renderers.ts +3 -1
  199. package/src/tools/report-tool-issue.ts +80 -0
  200. package/src/tools/resolve.ts +70 -39
  201. package/src/tools/search-tool-bm25.ts +2 -2
  202. package/src/tools/ssh.ts +2 -2
  203. package/src/tools/todo-write.ts +2 -2
  204. package/src/tools/tool-timeouts.ts +1 -0
  205. package/src/tools/write.ts +5 -6
  206. package/src/tui/tree-list.ts +3 -1
  207. package/src/utils/clipboard.ts +80 -0
  208. package/src/utils/commit-message-generator.ts +2 -3
  209. package/src/utils/edit-mode.ts +49 -0
  210. package/src/utils/external-editor.ts +11 -5
  211. package/src/utils/file-display-mode.ts +6 -5
  212. package/src/utils/file-mentions.ts +8 -7
  213. package/src/utils/git.ts +1400 -0
  214. package/src/utils/image-loading.ts +98 -0
  215. package/src/utils/title-generator.ts +2 -3
  216. package/src/utils/tools-manager.ts +6 -6
  217. package/src/web/scrapers/choosealicense.ts +1 -1
  218. package/src/web/search/index.ts +3 -3
  219. package/src/web/search/render.ts +6 -4
  220. package/src/autoresearch/command-initialize.md +0 -34
  221. package/src/commit/git/errors.ts +0 -9
  222. package/src/commit/git/index.ts +0 -210
  223. package/src/commit/git/operations.ts +0 -54
  224. package/src/patch/diff.ts +0 -433
  225. package/src/patch/index.ts +0 -888
  226. package/src/patch/parser.ts +0 -532
  227. package/src/patch/types.ts +0 -292
  228. package/src/prompts/agents/oracle.md +0 -77
  229. package/src/tools/gh-cli.ts +0 -125
  230. package/src/tools/pending-action.ts +0 -49
  231. package/src/utils/child-process.ts +0 -88
  232. package/src/utils/frontmatter.ts +0 -117
  233. package/src/utils/image-input.ts +0 -274
  234. package/src/utils/mime.ts +0 -53
  235. package/src/utils/prompt-format.ts +0 -170
@@ -0,0 +1,98 @@
1
+ import * as fs from "node:fs/promises";
2
+ import type { ImageContent } from "@oh-my-pi/pi-ai";
3
+ import { formatBytes, readImageMetadata, SUPPORTED_IMAGE_MIME_TYPES } from "@oh-my-pi/pi-utils";
4
+ import { resolveReadPath } from "../tools/path-utils";
5
+ import { convertToPng } from "./image-convert";
6
+ import { formatDimensionNote, resizeImage } from "./image-resize";
7
+
8
+ export const MAX_IMAGE_INPUT_BYTES = 20 * 1024 * 1024;
9
+ export const SUPPORTED_INPUT_IMAGE_MIME_TYPES = SUPPORTED_IMAGE_MIME_TYPES;
10
+
11
+ export interface LoadImageInputOptions {
12
+ path: string;
13
+ cwd: string;
14
+ autoResize: boolean;
15
+ maxBytes?: number;
16
+ resolvedPath?: string;
17
+ detectedMimeType?: string;
18
+ }
19
+
20
+ export interface LoadedImageInput {
21
+ resolvedPath: string;
22
+ mimeType: string;
23
+ data: string;
24
+ textNote: string;
25
+ dimensionNote?: string;
26
+ bytes: number;
27
+ }
28
+
29
+ export class ImageInputTooLargeError extends Error {
30
+ readonly bytes: number;
31
+ readonly maxBytes: number;
32
+
33
+ constructor(bytes: number, maxBytes: number) {
34
+ super(`Image file too large: ${formatBytes(bytes)} exceeds ${formatBytes(maxBytes)} limit.`);
35
+ this.name = "ImageInputTooLargeError";
36
+ this.bytes = bytes;
37
+ this.maxBytes = maxBytes;
38
+ }
39
+ }
40
+
41
+ export async function ensureSupportedImageInput(image: ImageContent): Promise<ImageContent | null> {
42
+ if (SUPPORTED_INPUT_IMAGE_MIME_TYPES.has(image.mimeType)) {
43
+ return image;
44
+ }
45
+ const converted = await convertToPng(image.data, image.mimeType);
46
+ return converted ? { type: "image", data: converted.data, mimeType: converted.mimeType } : null;
47
+ }
48
+
49
+ export async function loadImageInput(options: LoadImageInputOptions): Promise<LoadedImageInput | null> {
50
+ const maxBytes = options.maxBytes ?? MAX_IMAGE_INPUT_BYTES;
51
+ const resolvedPath = options.resolvedPath ?? resolveReadPath(options.path, options.cwd);
52
+ const metadata = options.detectedMimeType
53
+ ? { mimeType: options.detectedMimeType }
54
+ : await readImageMetadata(resolvedPath);
55
+ const mimeType = metadata?.mimeType;
56
+ if (!mimeType) return null;
57
+
58
+ const stat = await Bun.file(resolvedPath).stat();
59
+ if (stat.size > maxBytes) {
60
+ throw new ImageInputTooLargeError(stat.size, maxBytes);
61
+ }
62
+
63
+ const inputBuffer = await fs.readFile(resolvedPath);
64
+ if (inputBuffer.byteLength > maxBytes) {
65
+ throw new ImageInputTooLargeError(inputBuffer.byteLength, maxBytes);
66
+ }
67
+
68
+ let outputData = Buffer.from(inputBuffer).toBase64();
69
+ let outputMimeType = mimeType;
70
+ let outputBytes = inputBuffer.byteLength;
71
+ let dimensionNote: string | undefined;
72
+
73
+ if (options.autoResize) {
74
+ try {
75
+ const resized = await resizeImage({ type: "image", data: outputData, mimeType });
76
+ outputData = resized.data;
77
+ outputMimeType = resized.mimeType;
78
+ outputBytes = resized.buffer.byteLength;
79
+ dimensionNote = formatDimensionNote(resized);
80
+ } catch {
81
+ // keep original image when resize fails
82
+ }
83
+ }
84
+
85
+ let textNote = `Read image file [${outputMimeType}]`;
86
+ if (dimensionNote) {
87
+ textNote += `\n${dimensionNote}`;
88
+ }
89
+
90
+ return {
91
+ resolvedPath,
92
+ mimeType: outputMimeType,
93
+ data: outputData,
94
+ textNote,
95
+ dimensionNote,
96
+ bytes: outputBytes,
97
+ };
98
+ }
@@ -5,15 +5,14 @@ import * as path from "node:path";
5
5
  import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
6
6
  import type { Api, Model } from "@oh-my-pi/pi-ai";
7
7
  import { completeSimple } from "@oh-my-pi/pi-ai";
8
- import { logger } from "@oh-my-pi/pi-utils";
8
+ import { logger, prompt } from "@oh-my-pi/pi-utils";
9
9
  import type { ModelRegistry } from "../config/model-registry";
10
10
  import { resolveRoleSelection } from "../config/model-resolver";
11
- import { renderPromptTemplate } from "../config/prompt-templates";
12
11
  import type { Settings } from "../config/settings";
13
12
  import titleSystemPrompt from "../prompts/system/title-system.md" with { type: "text" };
14
13
  import { toReasoningEffort } from "../thinking";
15
14
 
16
- const TITLE_SYSTEM_PROMPT = renderPromptTemplate(titleSystemPrompt);
15
+ const TITLE_SYSTEM_PROMPT = prompt.render(titleSystemPrompt);
17
16
 
18
17
  const DEFAULT_TERMINAL_TITLE = "π";
19
18
  const TERMINAL_TITLE_CONTROL_CHARS = /[\u0000-\u001f\u007f-\u009f]/g;
@@ -1,7 +1,7 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as os from "node:os";
3
3
  import * as path from "node:path";
4
- import { APP_NAME, getToolsDir, logger, ptree, TempDir } from "@oh-my-pi/pi-utils";
4
+ import { $which, APP_NAME, getToolsDir, logger, ptree, TempDir } from "@oh-my-pi/pi-utils";
5
5
 
6
6
  const TOOLS_DIR = getToolsDir();
7
7
  const TOOL_DOWNLOAD_TIMEOUT_MS = 120_000;
@@ -96,7 +96,7 @@ export function getToolPath(tool: ToolName): string | null {
96
96
  // Check Python tools first
97
97
  const pythonConfig = PYTHON_TOOLS[tool];
98
98
  if (pythonConfig) {
99
- return Bun.which(pythonConfig.binaryName);
99
+ return $which(pythonConfig.binaryName);
100
100
  }
101
101
 
102
102
  const config = TOOLS[tool];
@@ -109,7 +109,7 @@ export function getToolPath(tool: ToolName): string | null {
109
109
  }
110
110
 
111
111
  // Check system PATH
112
- return Bun.which(config.binaryName);
112
+ return $which(config.binaryName);
113
113
  }
114
114
 
115
115
  // Fetch latest release version from GitHub
@@ -249,7 +249,7 @@ async function downloadTool(tool: ToolName, signal?: AbortSignal): Promise<strin
249
249
  // Install a Python package via uv (preferred) or pip
250
250
  async function installPythonPackage(pkg: string, signal?: AbortSignal): Promise<boolean> {
251
251
  // Try uv first (faster, better isolation)
252
- const uv = Bun.which("uv");
252
+ const uv = $which("uv");
253
253
  if (uv) {
254
254
  const result = await ptree.exec(["uv", "tool", "install", pkg], {
255
255
  signal,
@@ -261,7 +261,7 @@ async function installPythonPackage(pkg: string, signal?: AbortSignal): Promise<
261
261
  }
262
262
 
263
263
  // Fall back to pip
264
- const pip = Bun.which("pip3") || Bun.which("pip");
264
+ const pip = $which("pip3") || $which("pip");
265
265
  if (pip) {
266
266
  const result = await ptree.exec(["pip", "install", "--user", pkg], {
267
267
  signal,
@@ -316,7 +316,7 @@ export async function ensureTool(tool: ToolName, silentOrOptions?: EnsureToolOpt
316
316
  const success = await installPythonPackage(pythonConfig.package, signal);
317
317
  if (success) {
318
318
  // Re-check for the command after installation
319
- const path = Bun.which(pythonConfig.binaryName);
319
+ const path = $which(pythonConfig.binaryName);
320
320
  if (path) {
321
321
  if (!silent) {
322
322
  logger.debug(`${pythonConfig.name} installed successfully`);
@@ -1,4 +1,4 @@
1
- import { parseFrontmatter } from "../../utils/frontmatter";
1
+ import { parseFrontmatter } from "@oh-my-pi/pi-utils";
2
2
  import type { RenderResult, SpecialHandler } from "./types";
3
3
  import { buildResult, loadPage } from "./types";
4
4
  import { asString } from "./utils";
@@ -7,8 +7,8 @@
7
7
  */
8
8
  import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
9
9
  import { StringEnum } from "@oh-my-pi/pi-ai";
10
+ import { prompt } from "@oh-my-pi/pi-utils";
10
11
  import { Type } from "@sinclair/typebox";
11
- import { renderPromptTemplate } from "../../config/prompt-templates";
12
12
  import type { CustomTool, CustomToolContext, RenderResultOptions } from "../../extensibility/custom-tools/types";
13
13
  import type { Theme } from "../../modes/theme/theme";
14
14
  import webSearchSystemPrompt from "../../prompts/system/web-search.md" with { type: "text" };
@@ -213,7 +213,7 @@ export class SearchTool implements AgentTool<typeof webSearchSchema, SearchRende
213
213
  readonly strict = true;
214
214
 
215
215
  constructor(_session: ToolSession) {
216
- this.description = renderPromptTemplate(webSearchDescription);
216
+ this.description = prompt.render(webSearchDescription);
217
217
  }
218
218
 
219
219
  async execute(
@@ -231,7 +231,7 @@ export class SearchTool implements AgentTool<typeof webSearchSchema, SearchRende
231
231
  export const webSearchCustomTool: CustomTool<typeof webSearchSchema, SearchRenderDetails> = {
232
232
  name: "web_search",
233
233
  label: "Web Search",
234
- description: renderPromptTemplate(webSearchDescription),
234
+ description: prompt.render(webSearchDescription),
235
235
  parameters: webSearchSchema,
236
236
 
237
237
  async execute(
@@ -188,7 +188,7 @@ export function renderSearchResult(
188
188
  : typeof src.url === "string" && src.url.trim()
189
189
  ? src.url
190
190
  : "Untitled";
191
- const title = truncateToWidth(titleText, 70);
191
+ const title = truncateToWidth(titleText, MAX_SNIPPET_LINE_LEN);
192
192
  const url = typeof src.url === "string" ? src.url : "";
193
193
  const domain = url ? getDomain(url) : "";
194
194
  const age =
@@ -196,11 +196,13 @@ export function renderSearchResult(
196
196
  const metaParts: string[] = [];
197
197
  if (domain) metaParts.push(theme.fg("dim", `(${domain})`));
198
198
  if (typeof src.author === "string" && src.author.trim())
199
- metaParts.push(theme.fg("muted", src.author));
199
+ metaParts.push(theme.fg("muted", truncateToWidth(src.author.trim(), 40)));
200
200
  if (age) metaParts.push(theme.fg("muted", age));
201
201
  const metaSep = theme.fg("dim", theme.sep.dot);
202
202
  const metaSuffix = metaParts.length > 0 ? ` ${metaParts.join(metaSep)}` : "";
203
- const srcLines: string[] = [`${theme.fg("accent", title)}${metaSuffix}`];
203
+ const srcLines: string[] = [
204
+ truncateToWidth(`${theme.fg("accent", title)}${metaSuffix}`, MAX_SNIPPET_LINE_LEN),
205
+ ];
204
206
  const snippetText = typeof src.snippet === "string" ? src.snippet : "";
205
207
  if (snippetText.trim()) {
206
208
  const snippetLines = getPreviewLines(snippetText, MAX_SNIPPET_LINES, MAX_SNIPPET_LINE_LEN);
@@ -208,7 +210,7 @@ export function renderSearchResult(
208
210
  srcLines.push(theme.fg("muted", `${theme.format.dash} ${snippetLine}`));
209
211
  }
210
212
  }
211
- if (url) srcLines.push(theme.fg("mdLinkUrl", url));
213
+ if (url) srcLines.push(theme.fg("mdLinkUrl", truncateToWidth(url, MAX_SNIPPET_LINE_LEN)));
212
214
  return srcLines;
213
215
  },
214
216
  },
@@ -1,34 +0,0 @@
1
- Set up autoresearch for this intent:
2
-
3
- {{intent}}
4
-
5
- {{branch_status_line}}
6
-
7
- Collected setup:
8
-
9
- - benchmark command: `{{benchmark_command}}`
10
- - primary metric: `{{metric_name}}`
11
- - metric unit: `{{metric_unit}}`
12
- - direction: `{{direction}}`
13
- - tradeoff metrics:
14
- {{{secondary_metrics_block}}}
15
- - files in scope:
16
- {{{scope_paths_block}}}
17
- - off limits:
18
- {{{off_limits_block}}}
19
- - constraints:
20
- {{{constraints_block}}}
21
-
22
- Explain briefly what autoresearch will do in this repository, then initialize the workspace.
23
-
24
- Your first actions:
25
- - write `autoresearch.md`
26
- - record the collected benchmark command, primary metric, metric unit, direction, tradeoff metrics, scope, off-limits list, and constraints in `autoresearch.md`
27
- - add a short preflight section in `autoresearch.md` covering prerequisites, one-time setup, and the comparability invariant that must stay fixed across runs
28
- - explicitly mark the ground-truth evaluator, fixed datasets, and other measurement-critical files as off-limits or hard constraints when they define the benchmark contract
29
- - write or update `autoresearch.program.md` when you learn durable heuristics, failure patterns, or repo-specific strategy that future resume turns should inherit
30
- - define the benchmark entrypoint in `autoresearch.sh`
31
- - optionally add `autoresearch.checks.sh` if correctness or quality needs a hard gate
32
- - run `init_experiment` with the exact collected benchmark command, metric definition, scope paths, off-limits list, and constraints
33
- - run and log the baseline
34
- - keep iterating until interrupted or until the configured iteration cap is reached
@@ -1,9 +0,0 @@
1
- export class GitError extends Error {
2
- constructor(
3
- readonly command: string,
4
- readonly stderr: string,
5
- ) {
6
- super(`${command} failed: ${stderr || "unknown error"}`);
7
- this.name = "GitError";
8
- }
9
- }
@@ -1,210 +0,0 @@
1
- import * as fs from "node:fs/promises";
2
- import * as os from "node:os";
3
- import * as path from "node:path";
4
- import { logger, Snowflake } from "@oh-my-pi/pi-utils";
5
- import type { FileDiff, FileHunks, NumstatEntry } from "../../commit/types";
6
- import { parseDiffHunks, parseFileDiffs, parseFileHunks, parseNumstat } from "./diff";
7
- import { GitError } from "./errors";
8
- import { commit, push, resetStaging, runGitCommand, stageFiles } from "./operations";
9
-
10
- export type HunkSelection = {
11
- path: string;
12
- hunks: { type: "all" } | { type: "indices"; indices: number[] } | { type: "lines"; start: number; end: number };
13
- };
14
-
15
- export class ControlledGit {
16
- constructor(private readonly cwd: string) {}
17
-
18
- async getDiff(staged: boolean): Promise<string> {
19
- const args = staged ? ["diff", "--cached"] : ["diff"];
20
- const result = await runGitCommand(this.cwd, args);
21
- this.#ensureSuccess(result, "git diff");
22
- return result.stdout;
23
- }
24
-
25
- async getDiffForFiles(files: string[], staged = true): Promise<string> {
26
- const args = staged ? ["diff", "--cached", "--", ...files] : ["diff", "--", ...files];
27
- const result = await runGitCommand(this.cwd, args);
28
- this.#ensureSuccess(result, "git diff (files)");
29
- return result.stdout;
30
- }
31
-
32
- async getChangedFiles(staged: boolean): Promise<string[]> {
33
- const args = staged ? ["diff", "--cached", "--name-only"] : ["diff", "--name-only"];
34
- const result = await runGitCommand(this.cwd, args);
35
- this.#ensureSuccess(result, "git diff --name-only");
36
- return result.stdout
37
- .split("\n")
38
- .map(line => line.trim())
39
- .filter(Boolean);
40
- }
41
-
42
- async getStat(staged: boolean): Promise<string> {
43
- const args = staged ? ["diff", "--cached", "--stat"] : ["diff", "--stat"];
44
- const result = await runGitCommand(this.cwd, args);
45
- this.#ensureSuccess(result, "git diff --stat");
46
- return result.stdout;
47
- }
48
-
49
- async getStatForFiles(files: string[], staged = true): Promise<string> {
50
- const args = staged ? ["diff", "--cached", "--stat", "--", ...files] : ["diff", "--stat", "--", ...files];
51
- const result = await runGitCommand(this.cwd, args);
52
- this.#ensureSuccess(result, "git diff --stat (files)");
53
- return result.stdout;
54
- }
55
-
56
- async getNumstat(staged: boolean): Promise<NumstatEntry[]> {
57
- const args = staged ? ["diff", "--cached", "--numstat"] : ["diff", "--numstat"];
58
- const result = await runGitCommand(this.cwd, args);
59
- this.#ensureSuccess(result, "git diff --numstat");
60
- return parseNumstat(result.stdout);
61
- }
62
-
63
- async getRecentCommits(count: number): Promise<string[]> {
64
- const result = await runGitCommand(this.cwd, ["log", `-n${count}`, "--pretty=format:%s"]);
65
- this.#ensureSuccess(result, "git log");
66
- return result.stdout
67
- .split("\n")
68
- .map(line => line.trim())
69
- .filter(Boolean);
70
- }
71
-
72
- async getStagedFiles(): Promise<string[]> {
73
- const result = await runGitCommand(this.cwd, ["diff", "--cached", "--name-only"]);
74
- this.#ensureSuccess(result, "git diff --cached --name-only");
75
- return result.stdout
76
- .split("\n")
77
- .map(line => line.trim())
78
- .filter(Boolean);
79
- }
80
-
81
- async getUntrackedFiles(): Promise<string[]> {
82
- const result = await runGitCommand(this.cwd, ["ls-files", "--others", "--exclude-standard"]);
83
- this.#ensureSuccess(result, "git ls-files --others --exclude-standard");
84
- return result.stdout
85
- .split("\n")
86
- .map(line => line.trim())
87
- .filter(Boolean);
88
- }
89
-
90
- async stageAll(): Promise<void> {
91
- const result = await stageFiles(this.cwd, []);
92
- this.#ensureSuccess(result, "git add -A");
93
- }
94
-
95
- async stageFiles(files: string[]): Promise<void> {
96
- const result = await stageFiles(this.cwd, files);
97
- this.#ensureSuccess(result, "git add");
98
- }
99
-
100
- async stageHunks(selections: HunkSelection[]): Promise<void> {
101
- if (selections.length === 0) return;
102
- const diff = await this.getDiff(false);
103
- const fileDiffs = parseFileDiffs(diff);
104
- const fileDiffMap = new Map(fileDiffs.map(entry => [entry.filename, entry]));
105
- const patchParts: string[] = [];
106
- for (const selection of selections) {
107
- const fileDiff = fileDiffMap.get(selection.path);
108
- if (!fileDiff) {
109
- throw new GitError("git apply --cached", `No diff found for ${selection.path}`);
110
- }
111
- if (fileDiff.isBinary) {
112
- if (selection.hunks.type !== "all") {
113
- throw new GitError("git apply --cached", `Cannot select hunks for binary file ${selection.path}`);
114
- }
115
- patchParts.push(fileDiff.content);
116
- continue;
117
- }
118
-
119
- if (selection.hunks.type === "all") {
120
- patchParts.push(fileDiff.content);
121
- continue;
122
- }
123
-
124
- const fileHunks = parseFileHunks(fileDiff);
125
- const selectedHunks = selectHunks(fileHunks, selection.hunks);
126
- if (selectedHunks.length === 0) {
127
- throw new GitError("git apply --cached", `No hunks selected for ${selection.path}`);
128
- }
129
- const header = extractFileHeader(fileDiff.content);
130
- const filePatch = [header, ...selectedHunks.map(hunk => hunk.content)].join("\n");
131
- patchParts.push(filePatch);
132
- }
133
-
134
- const patch = joinPatch(patchParts);
135
- if (!patch.trim()) return;
136
- const tempPath = path.join(os.tmpdir(), `omp-hunks-${Snowflake.next()}.patch`);
137
- try {
138
- await Bun.write(tempPath, patch);
139
- const result = await runGitCommand(this.cwd, ["apply", "--cached", "--binary", tempPath]);
140
- this.#ensureSuccess(result, "git apply --cached");
141
- } finally {
142
- await fs.rm(tempPath, { force: true });
143
- }
144
- }
145
-
146
- async resetStaging(files: string[] = []): Promise<void> {
147
- const result = await resetStaging(this.cwd, files);
148
- this.#ensureSuccess(result, "git reset");
149
- }
150
-
151
- async commit(message: string): Promise<void> {
152
- const result = await commit(this.cwd, message);
153
- this.#ensureSuccess(result, "git commit");
154
- }
155
-
156
- async push(): Promise<void> {
157
- const result = await push(this.cwd);
158
- this.#ensureSuccess(result, "git push");
159
- }
160
-
161
- parseDiffFiles(diff: string): FileDiff[] {
162
- return parseFileDiffs(diff);
163
- }
164
-
165
- parseDiffHunks(diff: string): FileHunks[] {
166
- return parseDiffHunks(diff);
167
- }
168
-
169
- async getHunks(files: string[], staged = true): Promise<FileHunks[]> {
170
- const diff = await this.getDiffForFiles(files, staged);
171
- return this.parseDiffHunks(diff);
172
- }
173
-
174
- #ensureSuccess(result: { exitCode: number; stderr: string }, label: string): void {
175
- if (result.exitCode !== 0) {
176
- logger.error("commit git command failed", { label, stderr: result.stderr });
177
- throw new GitError(label, result.stderr);
178
- }
179
- }
180
- }
181
-
182
- function extractFileHeader(diff: string): string {
183
- const lines = diff.split("\n");
184
- const headerLines: string[] = [];
185
- for (const line of lines) {
186
- if (line.startsWith("@@")) break;
187
- headerLines.push(line);
188
- }
189
- return headerLines.join("\n");
190
- }
191
-
192
- export function joinPatch(parts: string[]): string {
193
- return `${parts
194
- .map(part => (part.endsWith("\n") ? part : `${part}\n`))
195
- .join("\n")
196
- .replace(/\n+$/, "")}\n`;
197
- }
198
-
199
- function selectHunks(file: FileHunks, selector: HunkSelection["hunks"]): FileHunks["hunks"] {
200
- if (selector.type === "indices") {
201
- const wanted = new Set(selector.indices.map(value => Math.max(1, Math.floor(value))));
202
- return file.hunks.filter(hunk => wanted.has(hunk.index + 1));
203
- }
204
- if (selector.type === "lines") {
205
- const start = Math.floor(selector.start);
206
- const end = Math.floor(selector.end);
207
- return file.hunks.filter(hunk => hunk.newStart <= end && hunk.newStart + hunk.newLines - 1 >= start);
208
- }
209
- return file.hunks;
210
- }
@@ -1,54 +0,0 @@
1
- import { $ } from "bun";
2
-
3
- interface GitResult {
4
- exitCode: number;
5
- stdout: string;
6
- stderr: string;
7
- }
8
-
9
- export async function runGitCommand(cwd: string, args: string[]): Promise<GitResult> {
10
- const result = await $`git ${args}`.cwd(cwd).quiet().nothrow();
11
- const stdout = result.text();
12
- const stderr = result.stderr?.toString() ?? "";
13
- return {
14
- exitCode: result.exitCode ?? 0,
15
- stdout,
16
- stderr,
17
- };
18
- }
19
-
20
- export async function stageFiles(cwd: string, files: string[]): Promise<GitResult> {
21
- const args = files.length === 0 ? ["add", "-A"] : ["add", "--", ...files];
22
- return runGitCommand(cwd, args);
23
- }
24
-
25
- export async function resetStaging(cwd: string, files: string[]): Promise<GitResult> {
26
- const args = files.length === 0 ? ["reset"] : ["reset", "--", ...files];
27
- return runGitCommand(cwd, args);
28
- }
29
-
30
- export async function push(cwd: string): Promise<GitResult> {
31
- return runGitCommand(cwd, ["push"]);
32
- }
33
-
34
- export async function commit(cwd: string, message: string): Promise<GitResult> {
35
- const child = Bun.spawn(["git", "commit", "-F", "-"], {
36
- cwd,
37
- stdin: Buffer.from(message),
38
- stdout: "pipe",
39
- stderr: "pipe",
40
- windowsHide: true,
41
- });
42
-
43
- const [stdout, stderr, exitCode] = await Promise.all([
44
- new Response(child.stdout).text(),
45
- new Response(child.stderr).text(),
46
- child.exited,
47
- ]);
48
-
49
- return {
50
- exitCode: exitCode ?? 0,
51
- stdout: stdout.trim(),
52
- stderr: stderr.trim(),
53
- };
54
- }