@oh-my-pi/pi-coding-agent 13.19.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 (202) hide show
  1. package/CHANGELOG.md +266 -1
  2. package/package.json +86 -20
  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 +91 -0
  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 +83 -125
  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 -5
  28. package/src/commit/agentic/index.ts +3 -4
  29. package/src/commit/agentic/tools/analyze-file.ts +3 -3
  30. package/src/commit/agentic/validation.ts +1 -1
  31. package/src/commit/analysis/conventional.ts +4 -4
  32. package/src/commit/analysis/summary.ts +3 -3
  33. package/src/commit/changelog/generate.ts +4 -4
  34. package/src/commit/map-reduce/map-phase.ts +4 -4
  35. package/src/commit/map-reduce/reduce-phase.ts +4 -4
  36. package/src/commit/pipeline.ts +3 -4
  37. package/src/config/prompt-templates.ts +44 -226
  38. package/src/config/resolve-config-value.ts +4 -2
  39. package/src/config/settings-schema.ts +54 -2
  40. package/src/config/settings.ts +25 -26
  41. package/src/dap/client.ts +674 -0
  42. package/src/dap/config.ts +150 -0
  43. package/src/dap/defaults.json +211 -0
  44. package/src/dap/index.ts +4 -0
  45. package/src/dap/session.ts +1255 -0
  46. package/src/dap/types.ts +600 -0
  47. package/src/debug/log-viewer.ts +3 -2
  48. package/src/discovery/builtin.ts +1 -2
  49. package/src/discovery/codex.ts +2 -2
  50. package/src/discovery/github.ts +2 -1
  51. package/src/discovery/helpers.ts +2 -2
  52. package/src/discovery/opencode.ts +2 -2
  53. package/src/edit/diff.ts +818 -0
  54. package/src/edit/index.ts +309 -0
  55. package/src/edit/line-hash.ts +67 -0
  56. package/src/edit/modes/chunk.ts +454 -0
  57. package/src/{patch → edit/modes}/hashline.ts +741 -361
  58. package/src/{patch/applicator.ts → edit/modes/patch.ts} +420 -117
  59. package/src/{patch/fuzzy.ts → edit/modes/replace.ts} +519 -197
  60. package/src/{patch → edit}/normalize.ts +97 -76
  61. package/src/{patch/shared.ts → edit/renderer.ts} +181 -108
  62. package/src/exec/bash-executor.ts +4 -2
  63. package/src/exec/idle-timeout-watchdog.ts +126 -0
  64. package/src/exec/non-interactive-env.ts +5 -0
  65. package/src/extensibility/custom-commands/bundled/ci-green/index.ts +2 -2
  66. package/src/extensibility/custom-commands/bundled/review/index.ts +2 -2
  67. package/src/extensibility/custom-commands/loader.ts +1 -2
  68. package/src/extensibility/custom-tools/loader.ts +34 -11
  69. package/src/extensibility/extensions/loader.ts +9 -4
  70. package/src/extensibility/extensions/runner.ts +24 -1
  71. package/src/extensibility/extensions/types.ts +1 -1
  72. package/src/extensibility/hooks/loader.ts +5 -6
  73. package/src/extensibility/hooks/types.ts +1 -1
  74. package/src/extensibility/plugins/doctor.ts +2 -1
  75. package/src/extensibility/slash-commands.ts +3 -7
  76. package/src/index.ts +2 -1
  77. package/src/internal-urls/docs-index.generated.ts +11 -11
  78. package/src/ipy/executor.ts +58 -17
  79. package/src/ipy/gateway-coordinator.ts +6 -4
  80. package/src/ipy/kernel.ts +45 -22
  81. package/src/ipy/runtime.ts +2 -2
  82. package/src/lsp/client.ts +7 -4
  83. package/src/lsp/clients/lsp-linter-client.ts +4 -4
  84. package/src/lsp/config.ts +2 -2
  85. package/src/lsp/defaults.json +688 -154
  86. package/src/lsp/index.ts +234 -45
  87. package/src/lsp/lspmux.ts +2 -2
  88. package/src/lsp/startup-events.ts +13 -0
  89. package/src/lsp/types.ts +12 -1
  90. package/src/lsp/utils.ts +8 -1
  91. package/src/main.ts +102 -46
  92. package/src/memories/index.ts +4 -5
  93. package/src/modes/acp/acp-agent.ts +563 -163
  94. package/src/modes/acp/acp-event-mapper.ts +9 -1
  95. package/src/modes/acp/acp-mode.ts +4 -2
  96. package/src/modes/components/agent-dashboard.ts +3 -4
  97. package/src/modes/components/diff.ts +6 -7
  98. package/src/modes/components/read-tool-group.ts +6 -12
  99. package/src/modes/components/settings-defs.ts +5 -0
  100. package/src/modes/components/tool-execution.ts +1 -1
  101. package/src/modes/components/welcome.ts +1 -1
  102. package/src/modes/controllers/btw-controller.ts +2 -2
  103. package/src/modes/controllers/command-controller.ts +3 -2
  104. package/src/modes/controllers/input-controller.ts +12 -8
  105. package/src/modes/index.ts +20 -2
  106. package/src/modes/interactive-mode.ts +94 -37
  107. package/src/modes/rpc/host-tools.ts +186 -0
  108. package/src/modes/rpc/rpc-client.ts +178 -13
  109. package/src/modes/rpc/rpc-mode.ts +73 -3
  110. package/src/modes/rpc/rpc-types.ts +53 -1
  111. package/src/modes/theme/theme.ts +80 -8
  112. package/src/modes/types.ts +2 -2
  113. package/src/prompts/system/system-prompt.md +2 -1
  114. package/src/prompts/tools/chunk-edit.md +219 -0
  115. package/src/prompts/tools/debug.md +43 -0
  116. package/src/prompts/tools/grep.md +3 -0
  117. package/src/prompts/tools/lsp.md +5 -5
  118. package/src/prompts/tools/read-chunk.md +17 -0
  119. package/src/prompts/tools/read.md +19 -5
  120. package/src/sdk.ts +190 -154
  121. package/src/secrets/obfuscator.ts +1 -1
  122. package/src/session/agent-session.ts +306 -256
  123. package/src/session/agent-storage.ts +12 -12
  124. package/src/session/compaction/branch-summarization.ts +3 -3
  125. package/src/session/compaction/compaction.ts +5 -6
  126. package/src/session/compaction/utils.ts +3 -3
  127. package/src/session/history-storage.ts +62 -19
  128. package/src/session/messages.ts +3 -3
  129. package/src/session/session-dump-format.ts +203 -0
  130. package/src/session/session-storage.ts +4 -2
  131. package/src/session/streaming-output.ts +1 -1
  132. package/src/session/tool-choice-queue.ts +213 -0
  133. package/src/slash-commands/builtin-registry.ts +56 -8
  134. package/src/ssh/connection-manager.ts +2 -2
  135. package/src/ssh/sshfs-mount.ts +5 -5
  136. package/src/stt/downloader.ts +4 -4
  137. package/src/stt/recorder.ts +4 -4
  138. package/src/stt/transcriber.ts +2 -2
  139. package/src/system-prompt.ts +21 -13
  140. package/src/task/agents.ts +5 -6
  141. package/src/task/commands.ts +2 -5
  142. package/src/task/executor.ts +4 -4
  143. package/src/task/index.ts +3 -4
  144. package/src/task/template.ts +2 -2
  145. package/src/task/worktree.ts +4 -4
  146. package/src/tools/ask.ts +2 -3
  147. package/src/tools/ast-edit.ts +7 -7
  148. package/src/tools/ast-grep.ts +7 -7
  149. package/src/tools/auto-generated-guard.ts +36 -41
  150. package/src/tools/await-tool.ts +2 -2
  151. package/src/tools/bash.ts +5 -23
  152. package/src/tools/browser.ts +4 -5
  153. package/src/tools/calculator.ts +2 -3
  154. package/src/tools/cancel-job.ts +2 -2
  155. package/src/tools/checkpoint.ts +3 -3
  156. package/src/tools/debug.ts +1007 -0
  157. package/src/tools/exit-plan-mode.ts +2 -3
  158. package/src/tools/fetch.ts +67 -3
  159. package/src/tools/find.ts +4 -5
  160. package/src/tools/fs-cache-invalidation.ts +5 -0
  161. package/src/tools/gemini-image.ts +13 -5
  162. package/src/tools/gh.ts +10 -11
  163. package/src/tools/grep.ts +57 -9
  164. package/src/tools/index.ts +44 -22
  165. package/src/tools/inspect-image.ts +4 -4
  166. package/src/tools/output-meta.ts +1 -1
  167. package/src/tools/python.ts +19 -6
  168. package/src/tools/read.ts +198 -67
  169. package/src/tools/render-mermaid.ts +2 -3
  170. package/src/tools/render-utils.ts +20 -6
  171. package/src/tools/renderers.ts +3 -1
  172. package/src/tools/report-tool-issue.ts +80 -0
  173. package/src/tools/resolve.ts +70 -39
  174. package/src/tools/search-tool-bm25.ts +2 -2
  175. package/src/tools/ssh.ts +2 -2
  176. package/src/tools/todo-write.ts +2 -2
  177. package/src/tools/tool-timeouts.ts +1 -0
  178. package/src/tools/write.ts +5 -6
  179. package/src/tui/tree-list.ts +3 -1
  180. package/src/utils/clipboard.ts +80 -0
  181. package/src/utils/commit-message-generator.ts +2 -3
  182. package/src/utils/edit-mode.ts +49 -0
  183. package/src/utils/file-display-mode.ts +6 -5
  184. package/src/utils/file-mentions.ts +8 -7
  185. package/src/utils/git.ts +4 -4
  186. package/src/utils/image-loading.ts +98 -0
  187. package/src/utils/title-generator.ts +2 -3
  188. package/src/utils/tools-manager.ts +6 -6
  189. package/src/web/scrapers/choosealicense.ts +1 -1
  190. package/src/web/search/index.ts +3 -3
  191. package/src/autoresearch/command-initialize.md +0 -34
  192. package/src/patch/diff.ts +0 -433
  193. package/src/patch/index.ts +0 -888
  194. package/src/patch/parser.ts +0 -532
  195. package/src/patch/types.ts +0 -292
  196. package/src/prompts/agents/oracle.md +0 -77
  197. package/src/tools/pending-action.ts +0 -49
  198. package/src/utils/child-process.ts +0 -88
  199. package/src/utils/frontmatter.ts +0 -117
  200. package/src/utils/image-input.ts +0 -274
  201. package/src/utils/mime.ts +0 -53
  202. package/src/utils/prompt-format.ts +0 -170
@@ -1,292 +0,0 @@
1
- /**
2
- * Shared types for the edit tool module.
3
- */
4
-
5
- // ═══════════════════════════════════════════════════════════════════════════
6
- // File System Abstraction
7
- // ═══════════════════════════════════════════════════════════════════════════
8
-
9
- /** Abstraction for file system operations to support LSP writethrough */
10
- export interface FileSystem {
11
- exists(path: string): Promise<boolean>;
12
- read(path: string): Promise<string>;
13
- readBinary?: (path: string) => Promise<Uint8Array>;
14
- write(path: string, content: string): Promise<void>;
15
- delete(path: string): Promise<void>;
16
- mkdir(path: string): Promise<void>;
17
- }
18
-
19
- // ═══════════════════════════════════════════════════════════════════════════
20
- // Fuzzy Matching Types
21
- // ═══════════════════════════════════════════════════════════════════════════
22
-
23
- /** Result of a fuzzy match operation */
24
- export interface FuzzyMatch {
25
- /** The actual text that was matched */
26
- actualText: string;
27
- /** Character index where the match starts */
28
- startIndex: number;
29
- /** Line number where the match starts (1-indexed) */
30
- startLine: number;
31
- /** Confidence score (0-1, where 1 is exact match) */
32
- confidence: number;
33
- }
34
-
35
- /** Outcome of attempting to find a match */
36
- export interface MatchOutcome {
37
- /** The match if found with sufficient confidence */
38
- match?: FuzzyMatch;
39
- /** The closest match found (may be below threshold) */
40
- closest?: FuzzyMatch;
41
- /** Number of occurrences if multiple exact matches found */
42
- occurrences?: number;
43
- /** Line numbers where occurrences were found (1-indexed) */
44
- occurrenceLines?: number[];
45
- /** Preview snippets for each occurrence (up to 5) */
46
- occurrencePreviews?: string[];
47
- /** Number of fuzzy matches above threshold */
48
- fuzzyMatches?: number;
49
- /** True when a dominant fuzzy match was accepted despite multiple candidates */
50
- dominantFuzzy?: boolean;
51
- }
52
-
53
- /** Result of a sequence search */
54
- export type SequenceMatchStrategy =
55
- | "exact"
56
- | "trim-trailing"
57
- | "trim"
58
- | "comment-prefix"
59
- | "unicode"
60
- | "prefix"
61
- | "substring"
62
- | "fuzzy"
63
- | "fuzzy-dominant"
64
- | "character";
65
-
66
- export interface SequenceSearchResult {
67
- /** Starting line index of the match (0-indexed) */
68
- index: number | undefined;
69
- /** Confidence score (1.0 for exact match, lower for fuzzy) */
70
- confidence: number;
71
- /** Number of matches at the same confidence level (for ambiguity detection) */
72
- matchCount?: number;
73
- /** Sample of matching indices (0-indexed, up to a small limit) */
74
- matchIndices?: number[];
75
- /** Matching strategy used */
76
- strategy?: SequenceMatchStrategy;
77
- }
78
-
79
- /** Result of a context line search */
80
- export type ContextMatchStrategy = "exact" | "trim" | "unicode" | "prefix" | "substring" | "fuzzy";
81
-
82
- export interface ContextLineResult {
83
- /** Index of the matching line (0-indexed) */
84
- index: number | undefined;
85
- /** Confidence score (1.0 for exact match, lower for fuzzy) */
86
- confidence: number;
87
- /** Number of matches at the same confidence level (for ambiguity detection) */
88
- matchCount?: number;
89
- /** Sample of matching indices (0-indexed, up to a small limit) */
90
- matchIndices?: number[];
91
- /** Matching strategy used */
92
- strategy?: ContextMatchStrategy;
93
- }
94
-
95
- // ═══════════════════════════════════════════════════════════════════════════
96
- // Patch Types
97
- // ═══════════════════════════════════════════════════════════════════════════
98
-
99
- export type Operation = "create" | "delete" | "update";
100
-
101
- /** Input for a patch operation */
102
- export interface PatchInput {
103
- /** File path (relative or absolute) */
104
- path: string;
105
- /** Operation type */
106
- op: Operation;
107
- /** New path for rename (update only) */
108
- rename?: string;
109
- /** File content (create) or diff hunks (update) */
110
- diff?: string;
111
- }
112
-
113
- /** Normalized patch input used internally by the applicator. */
114
- export interface NormalizedPatchInput {
115
- path: string;
116
- op: Operation;
117
- rename?: string;
118
- diff?: string;
119
- }
120
-
121
- export function normalizePatchInput(input: PatchInput): NormalizedPatchInput {
122
- return {
123
- path: input.path,
124
- op: input.op ?? "update",
125
- rename: input.rename,
126
- diff: input.diff,
127
- };
128
- }
129
-
130
- /** A single hunk/chunk in a diff */
131
- export interface DiffHunk {
132
- /** Context line to narrow down position (e.g., class/method definition) */
133
- changeContext?: string;
134
- /** 1-based line hint from unified diff headers (old file) */
135
- oldStartLine?: number;
136
- /** 1-based line hint from unified diff headers (new file) */
137
- newStartLine?: number;
138
- /** True if the hunk contains context lines (space-prefixed) */
139
- hasContextLines: boolean;
140
- /** Lines to be replaced (old content) */
141
- oldLines: string[];
142
- /** Lines to replace with (new content) */
143
- newLines: string[];
144
- /** If true, oldLines must occur at end of file */
145
- isEndOfFile: boolean;
146
- }
147
-
148
- /** Describes a change made to a file */
149
- export interface FileChange {
150
- type: Operation;
151
- path: string;
152
- newPath?: string;
153
- oldContent?: string;
154
- newContent?: string;
155
- }
156
-
157
- /** Result of applying a patch */
158
- export interface ApplyPatchResult {
159
- change: FileChange;
160
- warnings?: string[];
161
- }
162
-
163
- /** Options for applying a patch */
164
- export interface ApplyPatchOptions {
165
- /** Working directory for resolving relative paths */
166
- cwd: string;
167
- /** Dry run - compute changes without writing */
168
- dryRun?: boolean;
169
- /** Similarity threshold for fuzzy matching */
170
- fuzzyThreshold?: number;
171
- /** Allow fuzzy/partial matching when applying hunks */
172
- allowFuzzy?: boolean;
173
- /** File system abstraction (defaults to Bun-based implementation) */
174
- fs?: FileSystem;
175
- }
176
-
177
- // ═══════════════════════════════════════════════════════════════════════════
178
- // Diff Generation Types
179
- // ═══════════════════════════════════════════════════════════════════════════
180
-
181
- /** Result of generating a diff */
182
- export interface DiffResult {
183
- /** The unified diff string */
184
- diff: string;
185
- /** Line number of the first change in the new file */
186
- firstChangedLine: number | undefined;
187
- }
188
-
189
- /** Error from diff computation */
190
- export interface DiffError {
191
- error: string;
192
- }
193
-
194
- // ═══════════════════════════════════════════════════════════════════════════
195
- // Hashline Types
196
- // ═══════════════════════════════════════════════════════════════════════════
197
-
198
- /**
199
- * Hashline edit operation/input types are schema-derived in `patch/index.ts`
200
- * via `Static<typeof hashlineEditItemSchema>` and `Static<typeof hashlineEditSchema>`.
201
- */
202
-
203
- /** A single hash mismatch found during validation */
204
- export interface HashMismatch {
205
- /** 1-indexed line number */
206
- line: number;
207
- /** Hash the caller provided */
208
- expected: string;
209
- /** Hash computed from the current file content */
210
- actual: string;
211
- }
212
-
213
- // ═══════════════════════════════════════════════════════════════════════════
214
- // Error Classes
215
- // ═══════════════════════════════════════════════════════════════════════════
216
-
217
- export class ParseError extends Error {
218
- constructor(
219
- message: string,
220
- public readonly lineNumber?: number,
221
- ) {
222
- super(lineNumber !== undefined ? `Line ${lineNumber}: ${message}` : message);
223
- this.name = "ParseError";
224
- }
225
- }
226
-
227
- export class ApplyPatchError extends Error {
228
- constructor(message: string) {
229
- super(message);
230
- this.name = "ApplyPatchError";
231
- }
232
- }
233
-
234
- export class EditMatchError extends Error {
235
- constructor(
236
- public readonly path: string,
237
- public readonly searchText: string,
238
- public readonly closest: FuzzyMatch | undefined,
239
- public readonly options: { allowFuzzy: boolean; threshold: number; fuzzyMatches?: number },
240
- ) {
241
- super(EditMatchError.formatMessage(path, searchText, closest, options));
242
- this.name = "EditMatchError";
243
- }
244
-
245
- static formatMessage(
246
- path: string,
247
- searchText: string,
248
- closest: FuzzyMatch | undefined,
249
- options: { allowFuzzy: boolean; threshold: number; fuzzyMatches?: number },
250
- ): string {
251
- if (!closest) {
252
- return options.allowFuzzy
253
- ? `Could not find a close enough match in ${path}.`
254
- : `Could not find the exact text in ${path}. The old text must match exactly including all whitespace and newlines.`;
255
- }
256
-
257
- const similarity = Math.round(closest.confidence * 100);
258
- const searchLines = searchText.split("\n");
259
- const actualLines = closest.actualText.split("\n");
260
- const { oldLine, newLine } = findFirstDifferentLine(searchLines, actualLines);
261
- const thresholdPercent = Math.round(options.threshold * 100);
262
-
263
- const hint = options.allowFuzzy
264
- ? options.fuzzyMatches && options.fuzzyMatches > 1
265
- ? `Found ${options.fuzzyMatches} high-confidence matches. Provide more context to make it unique.`
266
- : `Closest match was below the ${thresholdPercent}% similarity threshold.`
267
- : "Fuzzy matching is disabled. Enable 'Edit fuzzy match' in settings to accept high-confidence matches.";
268
-
269
- return [
270
- options.allowFuzzy
271
- ? `Could not find a close enough match in ${path}.`
272
- : `Could not find the exact text in ${path}.`,
273
- ``,
274
- `Closest match (${similarity}% similar) at line ${closest.startLine}:`,
275
- ` - ${oldLine}`,
276
- ` + ${newLine}`,
277
- hint,
278
- ].join("\n");
279
- }
280
- }
281
-
282
- function findFirstDifferentLine(oldLines: string[], newLines: string[]): { oldLine: string; newLine: string } {
283
- const max = Math.max(oldLines.length, newLines.length);
284
- for (let i = 0; i < max; i++) {
285
- const oldLine = oldLines[i] ?? "";
286
- const newLine = newLines[i] ?? "";
287
- if (oldLine !== newLine) {
288
- return { oldLine, newLine };
289
- }
290
- }
291
- return { oldLine: oldLines[0] ?? "", newLine: newLines[0] ?? "" };
292
- }
@@ -1,77 +0,0 @@
1
- ---
2
- name: oracle
3
- description: Deep reasoning advisor for debugging dead ends, architecture decisions, and second opinions. Read-only.
4
- tools: read, grep, find, bash, lsp, web_search, ast_grep
5
- spawns: explore
6
- model: pi/slow
7
- thinking-level: high
8
- blocking: true
9
- ---
10
-
11
- You are a senior diagnostician and strategic technical advisor. You receive problems other agents are stuck on — doom loops, mysterious failures, architectural tradeoffs, subtle bugs — and return clear, actionable analysis.
12
-
13
- You diagnose, explain, and recommend. You do not implement. Others act on your findings.
14
-
15
- <critical>
16
- You **MUST** operate as read-only. You **MUST NOT** write, edit, or modify files, nor execute any state-changing commands.
17
- </critical>
18
-
19
- <directives>
20
- - You **MUST** reason from first principles. The caller already tried the obvious.
21
- - You **MUST** use tools to verify claims. You **MUST NOT** speculate about code behavior — read it.
22
- - You **MUST** identify root causes, not symptoms. If the caller says "X is broken", determine *why* X is broken.
23
- - You **MUST** surface hidden assumptions — in the code, in the caller's framing, in the environment.
24
- - You **SHOULD** consider at least two hypotheses before converging on one.
25
- - You **SHOULD** invoke tools in parallel when investigating multiple hypotheses.
26
- - When the problem is architectural, you **MUST** weigh tradeoffs explicitly: what does each option cost, what does it buy, what does it foreclose.
27
- </directives>
28
-
29
- <decision-framework>
30
- Apply pragmatic minimalism:
31
- - **Bias toward simplicity**: The right solution is the least complex one that fulfills actual requirements. Resist hypothetical future needs.
32
- - **Leverage what exists**: Favor modifications to current code and established patterns over introducing new components. New dependencies or infrastructure require explicit justification.
33
- - **One clear path**: Present a single primary recommendation. Mention alternatives only when they offer substantially different tradeoffs worth considering.
34
- - **Match depth to complexity**: Quick questions get quick answers. Reserve thorough analysis for genuinely complex problems.
35
- - **Signal the investment**: Tag recommendations with estimated effort — Quick (<1h), Short (1-4h), Medium (1-2d), Large (3d+).
36
- </decision-framework>
37
-
38
- <procedure>
39
- 1. Read the problem statement carefully. Identify what was already tried and why it failed.
40
- 2. Form 2-3 hypotheses for the root cause.
41
- 3. Use tools to gather evidence — read relevant code, trace data flow, check types, grep for related patterns. Parallelize independent reads.
42
- 4. Eliminate hypotheses based on evidence. Narrow to the most likely cause.
43
- 5. If the problem is a decision (not a bug), lay out options with concrete tradeoffs.
44
- 6. Deliver a clear verdict with supporting evidence.
45
- </procedure>
46
-
47
- <output>
48
- Structure your response in tiers:
49
-
50
- **Always include:**
51
- - **Diagnosis**: What is actually wrong, or what the real tradeoff is. 2-3 sentences.
52
- - **Evidence**: Specific file paths, line numbers, code excerpts that support your conclusion.
53
- - **Recommendation**: What to do about it — concrete, actionable, with enough detail that an implementing agent can act without re-investigating. Numbered steps, each 1-2 sentences.
54
-
55
- **Include when relevant:**
56
- - **Caveats**: Anything you are not confident about. Uncertainty **MUST** be stated, not hidden.
57
- - **Risks**: Edge cases, failure modes, or mitigation strategies.
58
-
59
- **Only when genuinely applicable:**
60
- - **Escalation triggers**: Conditions that would justify a more complex solution.
61
- - **Alternative sketch**: High-level outline of an alternative path (not a full design).
62
-
63
- You **MUST NOT** pad with meta-commentary. Dense and useful beats long and thorough.
64
- </output>
65
-
66
- <scope-discipline>
67
- - Recommend ONLY what was asked. No unsolicited improvements.
68
- - If you notice other issues, list at most 2 as "Optional future considerations" at the end.
69
- - You **MUST NOT** expand the problem surface beyond the original request.
70
- - Exhaust provided context before reaching for tools. External lookups fill genuine gaps, not curiosity.
71
- </scope-discipline>
72
-
73
- <critical>
74
- You **MUST** keep going until you have a clear answer or have exhausted available evidence.
75
- Before finalizing: re-scan for unstated assumptions, verify claims are grounded in code not invented, check for overly strong language not justified by evidence.
76
- This matters. The caller is stuck. Get it right.
77
- </critical>
@@ -1,49 +0,0 @@
1
- import type { AgentToolResult } from "@oh-my-pi/pi-agent-core";
2
-
3
- export interface PendingAction {
4
- label: string;
5
- sourceToolName: string;
6
- apply(reason: string): Promise<AgentToolResult<unknown>>;
7
- reject?(reason: string): Promise<AgentToolResult<unknown> | undefined>;
8
- details?: unknown;
9
- }
10
-
11
- export class PendingActionStore {
12
- #actions: PendingAction[] = [];
13
- #pushListeners = new Set<(action: PendingAction, count: number) => void>();
14
-
15
- push(action: PendingAction): void {
16
- this.#actions.push(action);
17
- const count = this.#actions.length;
18
- for (const listener of this.#pushListeners) {
19
- listener(action, count);
20
- }
21
- }
22
-
23
- peek(): PendingAction | null {
24
- return this.#actions.at(-1) ?? null;
25
- }
26
-
27
- pop(): PendingAction | null {
28
- return this.#actions.pop() ?? null;
29
- }
30
-
31
- subscribePush(listener: (action: PendingAction, count: number) => void): () => void {
32
- this.#pushListeners.add(listener);
33
- return () => {
34
- this.#pushListeners.delete(listener);
35
- };
36
- }
37
-
38
- clear(): void {
39
- this.#actions = [];
40
- }
41
-
42
- get count(): number {
43
- return this.#actions.length;
44
- }
45
-
46
- get hasPending(): boolean {
47
- return this.#actions.length > 0;
48
- }
49
- }
@@ -1,88 +0,0 @@
1
- import type { ChildProcess } from "node:child_process";
2
-
3
- const EXIT_STDIO_GRACE_MS = 100;
4
-
5
- /**
6
- * Wait for a child process to terminate without hanging on inherited stdio handles.
7
- *
8
- * Daemonized descendants can inherit the child's stdout/stderr pipe handles. In that
9
- * case the child emits `exit`, but `close` can hang forever even though the original
10
- * process is already gone. We wait briefly for stdio to end, then forcibly stop
11
- * tracking the inherited handles.
12
- */
13
- export function waitForChildProcess(child: ChildProcess): Promise<number | null> {
14
- const { promise, resolve, reject } = Promise.withResolvers<number | null>();
15
-
16
- let settled = false;
17
- let exited = false;
18
- let exitCode: number | null = null;
19
- let postExitTimer: NodeJS.Timeout | undefined;
20
- let stdoutEnded = child.stdout === null;
21
- let stderrEnded = child.stderr === null;
22
-
23
- const cleanup = () => {
24
- if (postExitTimer) {
25
- clearTimeout(postExitTimer);
26
- postExitTimer = undefined;
27
- }
28
- child.removeListener("error", onError);
29
- child.removeListener("exit", onExit);
30
- child.removeListener("close", onClose);
31
- child.stdout?.removeListener("end", onStdoutEnd);
32
- child.stderr?.removeListener("end", onStderrEnd);
33
- };
34
-
35
- const finalize = (code: number | null) => {
36
- if (settled) return;
37
- settled = true;
38
- cleanup();
39
- child.stdout?.destroy();
40
- child.stderr?.destroy();
41
- resolve(code);
42
- };
43
-
44
- const maybeFinalizeAfterExit = () => {
45
- if (!exited || settled) return;
46
- if (stdoutEnded && stderrEnded) {
47
- finalize(exitCode);
48
- }
49
- };
50
-
51
- const onStdoutEnd = () => {
52
- stdoutEnded = true;
53
- maybeFinalizeAfterExit();
54
- };
55
-
56
- const onStderrEnd = () => {
57
- stderrEnded = true;
58
- maybeFinalizeAfterExit();
59
- };
60
-
61
- const onError = (err: Error) => {
62
- if (settled) return;
63
- settled = true;
64
- cleanup();
65
- reject(err);
66
- };
67
-
68
- const onExit = (code: number | null) => {
69
- exited = true;
70
- exitCode = code;
71
- maybeFinalizeAfterExit();
72
- if (!settled) {
73
- postExitTimer = setTimeout(() => finalize(code), EXIT_STDIO_GRACE_MS);
74
- }
75
- };
76
-
77
- const onClose = (code: number | null) => {
78
- finalize(code);
79
- };
80
-
81
- child.stdout?.once("end", onStdoutEnd);
82
- child.stderr?.once("end", onStderrEnd);
83
- child.once("error", onError);
84
- child.once("exit", onExit);
85
- child.once("close", onClose);
86
-
87
- return promise;
88
- }
@@ -1,117 +0,0 @@
1
- import { logger, truncate } from "@oh-my-pi/pi-utils";
2
- import { YAML } from "bun";
3
-
4
- function stripHtmlComments(content: string): string {
5
- return content.replace(/<!--[\s\S]*?-->/g, "");
6
- }
7
-
8
- /** Convert kebab-case to camelCase (e.g. "thinking-level" -> "thinkingLevel") */
9
- function kebabToCamel(key: string): string {
10
- return key.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
11
- }
12
-
13
- /** Recursively normalize object keys from kebab-case to camelCase */
14
- function normalizeKeys<T>(obj: T): T {
15
- if (obj === null || typeof obj !== "object") {
16
- return obj;
17
- }
18
- if (Array.isArray(obj)) {
19
- return obj.map(normalizeKeys) as T;
20
- }
21
- const result: Record<string, unknown> = {};
22
- for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {
23
- const normalizedKey = kebabToCamel(key);
24
- result[normalizedKey] = normalizeKeys(value);
25
- }
26
- return result as T;
27
- }
28
-
29
- export class FrontmatterError extends Error {
30
- constructor(
31
- error: Error,
32
- readonly source?: unknown,
33
- ) {
34
- super(`Failed to parse YAML frontmatter (${source}): ${error.message}`, { cause: error });
35
- this.name = "FrontmatterError";
36
- }
37
-
38
- toString(): string {
39
- // Format the error with stack and detail, including the error message, stack, and source if present
40
- const details: string[] = [this.message];
41
- if (this.source !== undefined) {
42
- details.push(`Source: ${JSON.stringify(this.source)}`);
43
- }
44
- if (this.cause && typeof this.cause === "object" && "stack" in this.cause && this.cause.stack) {
45
- details.push(`Stack:\n${this.cause.stack}`);
46
- } else if (this.stack) {
47
- details.push(`Stack:\n${this.stack}`);
48
- }
49
- return details.join("\n\n");
50
- }
51
- }
52
-
53
- export interface FrontmatterOptions {
54
- /** Source of the content (alias: source) */
55
- location?: unknown;
56
- /** Source of the content (alias for location) */
57
- source?: unknown;
58
- /** Fallback frontmatter values */
59
- fallback?: Record<string, unknown>;
60
- /** Normalize the content */
61
- normalize?: boolean;
62
- /** Level of error handling */
63
- level?: "off" | "warn" | "fatal";
64
- }
65
-
66
- /**
67
- * Parse YAML frontmatter from markdown content
68
- * Returns { frontmatter, body } where body has frontmatter stripped
69
- */
70
- export function parseFrontmatter(
71
- content: string,
72
- options?: FrontmatterOptions,
73
- ): { frontmatter: Record<string, unknown>; body: string } {
74
- const { location, source, fallback, normalize = true, level = "warn" } = options ?? {};
75
- const loc = location ?? source;
76
- const frontmatter: Record<string, unknown> = { ...fallback };
77
-
78
- const normalized = normalize ? stripHtmlComments(content.replace(/\r\n/g, "\n").replace(/\r/g, "\n")) : content;
79
- if (!normalized.startsWith("---")) {
80
- return { frontmatter, body: normalized };
81
- }
82
-
83
- const endIndex = normalized.indexOf("\n---", 3);
84
- if (endIndex === -1) {
85
- return { frontmatter, body: normalized };
86
- }
87
-
88
- const metadata = normalized.slice(4, endIndex);
89
- const body = normalized.slice(endIndex + 4).trim();
90
-
91
- try {
92
- // Replace tabs with spaces for YAML compatibility, use failsafe mode for robustness
93
- const loaded = YAML.parse(metadata.replaceAll("\t", " ")) as Record<string, unknown> | null;
94
- return { frontmatter: normalizeKeys({ ...frontmatter, ...loaded }), body };
95
- } catch (error) {
96
- const err = new FrontmatterError(
97
- error instanceof Error ? error : new Error(`YAML: ${error}`),
98
- loc ?? `Inline '${truncate(content, 64)}'`,
99
- );
100
- if (level === "warn" || level === "fatal") {
101
- logger.warn("Failed to parse YAML frontmatter", { err: err.toString() });
102
- }
103
- if (level === "fatal") {
104
- throw err;
105
- }
106
-
107
- // Simple YAML parsing - just key: value pairs
108
- for (const line of metadata.split("\n")) {
109
- const match = line.match(/^([\w-]+):\s*(.*)$/);
110
- if (match) {
111
- frontmatter[match[1]] = match[2].trim();
112
- }
113
- }
114
-
115
- return { frontmatter: normalizeKeys(frontmatter) as Record<string, unknown>, body };
116
- }
117
- }