@oh-my-pi/pi-coding-agent 14.5.14 → 14.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +49 -0
- package/package.json +7 -7
- package/src/autoresearch/command-resume.md +5 -8
- package/src/autoresearch/git.ts +41 -51
- package/src/autoresearch/helpers.ts +43 -359
- package/src/autoresearch/index.ts +281 -273
- package/src/autoresearch/prompt-setup.md +43 -0
- package/src/autoresearch/prompt.md +52 -193
- package/src/autoresearch/resume-message.md +2 -8
- package/src/autoresearch/state.ts +59 -166
- package/src/autoresearch/storage.ts +687 -0
- package/src/autoresearch/tools/init-experiment.ts +201 -290
- package/src/autoresearch/tools/log-experiment.ts +304 -517
- package/src/autoresearch/tools/run-experiment.ts +117 -296
- package/src/autoresearch/tools/update-notes.ts +116 -0
- package/src/autoresearch/types.ts +16 -66
- package/src/cli/list-models.ts +66 -0
- package/src/config/settings-schema.ts +1 -1
- package/src/config/settings.ts +20 -1
- package/src/cursor.ts +1 -1
- package/src/edit/index.ts +9 -31
- package/src/edit/line-hash.ts +70 -43
- package/src/edit/modes/hashline.lark +26 -0
- package/src/edit/modes/hashline.ts +898 -1099
- package/src/edit/modes/patch.ts +0 -7
- package/src/edit/modes/replace.ts +0 -4
- package/src/edit/renderer.ts +22 -20
- package/src/edit/streaming.ts +8 -28
- package/src/eval/eval.lark +24 -30
- package/src/eval/js/context-manager.ts +5 -162
- package/src/eval/js/prelude.txt +0 -12
- package/src/eval/parse.ts +129 -129
- package/src/eval/py/prelude.py +1 -219
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +2 -2
- package/src/internal-urls/docs-index.generated.ts +2 -2
- package/src/main.ts +18 -3
- package/src/modes/components/session-observer-overlay.ts +5 -2
- package/src/modes/components/status-line/segments.ts +1 -1
- package/src/modes/components/status-line.ts +3 -5
- package/src/modes/components/tree-selector.ts +4 -5
- package/src/modes/components/welcome.ts +11 -1
- package/src/modes/controllers/command-controller.ts +2 -6
- package/src/modes/controllers/event-controller.ts +7 -5
- package/src/modes/controllers/extension-ui-controller.ts +3 -15
- package/src/modes/controllers/input-controller.ts +0 -1
- package/src/modes/controllers/selector-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +5 -7
- package/src/prompts/system/system-prompt.md +14 -38
- package/src/prompts/tools/ast-edit.md +8 -8
- package/src/prompts/tools/ast-grep.md +10 -10
- package/src/prompts/tools/eval.md +13 -31
- package/src/prompts/tools/find.md +2 -1
- package/src/prompts/tools/hashline.md +66 -57
- package/src/prompts/tools/search.md +2 -2
- package/src/session/agent-session.ts +1 -1
- package/src/session/session-manager.ts +17 -13
- package/src/tools/ast-edit.ts +141 -44
- package/src/tools/ast-grep.ts +112 -36
- package/src/tools/eval.ts +2 -53
- package/src/tools/find.ts +16 -15
- package/src/tools/gh-renderer.ts +184 -59
- package/src/tools/path-utils.ts +36 -196
- package/src/tools/search.ts +56 -35
- package/src/utils/edit-mode.ts +2 -11
- package/src/utils/file-display-mode.ts +1 -1
- package/src/utils/git.ts +59 -24
- package/src/utils/session-color.ts +0 -12
- package/src/utils/title-generator.ts +22 -38
- package/src/autoresearch/apply-contract-to-state.ts +0 -24
- package/src/autoresearch/contract.ts +0 -288
- package/src/edit/modes/atom.lark +0 -29
- package/src/edit/modes/atom.ts +0 -1773
- package/src/prompts/tools/atom.md +0 -150
package/src/main.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { invalidate as invalidateFsCache } from "./capability/fs";
|
|
|
17
17
|
import type { Args } from "./cli/args";
|
|
18
18
|
import { processFileArguments } from "./cli/file-processor";
|
|
19
19
|
import { buildInitialMessage } from "./cli/initial-message";
|
|
20
|
-
import {
|
|
20
|
+
import { runListModelsCommand } from "./cli/list-models";
|
|
21
21
|
import { selectSession } from "./cli/session-picker";
|
|
22
22
|
import { findConfigFile } from "./config";
|
|
23
23
|
import { ModelRegistry, ModelsConfigFile } from "./config/model-registry";
|
|
@@ -604,10 +604,25 @@ export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise<v
|
|
|
604
604
|
}
|
|
605
605
|
|
|
606
606
|
if (parsedArgs.listModels !== undefined) {
|
|
607
|
-
await logger.time("settings:init:list-models", Settings.init, {
|
|
607
|
+
const settingsInstance = await logger.time("settings:init:list-models", Settings.init, {
|
|
608
|
+
cwd: getProjectDir(),
|
|
609
|
+
});
|
|
608
610
|
await modelRegistry.refresh("online");
|
|
611
|
+
const cliExtensionPaths = parsedArgs.noExtensions
|
|
612
|
+
? []
|
|
613
|
+
: [...(parsedArgs.extensions ?? []), ...(parsedArgs.hooks ?? [])];
|
|
614
|
+
const settingsExtensions = settingsInstance.get("extensions") ?? [];
|
|
615
|
+
const disabledExtensionIds = settingsInstance.get("disabledExtensions") ?? [];
|
|
609
616
|
const searchPattern = typeof parsedArgs.listModels === "string" ? parsedArgs.listModels : undefined;
|
|
610
|
-
await
|
|
617
|
+
await runListModelsCommand({
|
|
618
|
+
modelRegistry,
|
|
619
|
+
cwd: getProjectDir(),
|
|
620
|
+
additionalExtensionPaths: cliExtensionPaths,
|
|
621
|
+
settingsExtensions,
|
|
622
|
+
disabledExtensionIds,
|
|
623
|
+
disableExtensionDiscovery: Boolean(parsedArgs.noExtensions),
|
|
624
|
+
searchPattern,
|
|
625
|
+
});
|
|
611
626
|
process.exit(0);
|
|
612
627
|
}
|
|
613
628
|
|
|
@@ -519,11 +519,14 @@ export class SessionObserverOverlayComponent extends Container {
|
|
|
519
519
|
case "edit":
|
|
520
520
|
return args.path ? `path: ${args.path}` : "";
|
|
521
521
|
case "search":
|
|
522
|
-
return [
|
|
522
|
+
return [
|
|
523
|
+
args.pattern ? `pattern: ${args.pattern}` : "",
|
|
524
|
+
Array.isArray(args.paths) ? `paths: ${args.paths.join(", ")}` : "",
|
|
525
|
+
]
|
|
523
526
|
.filter(Boolean)
|
|
524
527
|
.join(", ");
|
|
525
528
|
case "find":
|
|
526
|
-
return args.
|
|
529
|
+
return Array.isArray(args.paths) ? `paths: ${args.paths.join(", ")}` : "";
|
|
527
530
|
case "bash": {
|
|
528
531
|
const cmd = args.command;
|
|
529
532
|
return typeof cmd === "string" ? replaceTabs(cmd) : "";
|
|
@@ -366,7 +366,7 @@ const sessionNameSegment: StatusLineSegment = {
|
|
|
366
366
|
id: "session_name",
|
|
367
367
|
render(ctx) {
|
|
368
368
|
const sessionManager = ctx.session.sessionManager;
|
|
369
|
-
const name = sessionManager?.
|
|
369
|
+
const name = sessionManager?.getSessionName();
|
|
370
370
|
if (!name) return { content: "", visible: false };
|
|
371
371
|
|
|
372
372
|
const ansi = getSessionAccentAnsi(getSessionAccentHex(name)) ?? theme.getFgAnsi("accent");
|
|
@@ -9,7 +9,7 @@ import { theme } from "../../modes/theme/theme";
|
|
|
9
9
|
import type { AgentSession } from "../../session/agent-session";
|
|
10
10
|
import { calculatePromptTokens } from "../../session/compaction/compaction";
|
|
11
11
|
import * as git from "../../utils/git";
|
|
12
|
-
import { getSessionAccentAnsi,
|
|
12
|
+
import { getSessionAccentAnsi, getSessionAccentHex } from "../../utils/session-color";
|
|
13
13
|
import { sanitizeStatusText } from "../shared";
|
|
14
14
|
import {
|
|
15
15
|
canReuseCachedPr,
|
|
@@ -514,10 +514,8 @@ export class StatusLineComponent implements Component {
|
|
|
514
514
|
leftWidth = groupWidth(left, leftCapWidth, leftSepWidth);
|
|
515
515
|
rightWidth = groupWidth(right, rightCapWidth, rightSepWidth);
|
|
516
516
|
const gapWidth = Math.max(1, topFillWidth - leftWidth - rightWidth);
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
this.session.sessionManager?.titleSource,
|
|
520
|
-
);
|
|
517
|
+
const sessionName = this.session.sessionManager?.getSessionName();
|
|
518
|
+
const accentHex = sessionName ? getSessionAccentHex(sessionName) : undefined;
|
|
521
519
|
const gapColor = getSessionAccentAnsi(accentHex) ?? theme.getFgAnsi("border");
|
|
522
520
|
const gapFill = `${gapColor}${theme.boxRound.horizontal.repeat(gapWidth)}\x1b[39m`;
|
|
523
521
|
return leftGroup + gapFill + rightGroup;
|
|
@@ -667,13 +667,12 @@ class TreeList implements Component {
|
|
|
667
667
|
}
|
|
668
668
|
case "search": {
|
|
669
669
|
const pattern = String(args.pattern || "");
|
|
670
|
-
const
|
|
671
|
-
return `[search: /${pattern}/ in ${
|
|
670
|
+
const paths = Array.isArray(args.paths) ? args.paths.join(", ") : String(args.path || ".");
|
|
671
|
+
return `[search: /${pattern}/ in ${shortenPath(paths)}]`;
|
|
672
672
|
}
|
|
673
673
|
case "find": {
|
|
674
|
-
const
|
|
675
|
-
|
|
676
|
-
return `[find: ${pattern} in ${path}]`;
|
|
674
|
+
const paths = Array.isArray(args.paths) ? args.paths.join(", ") : String(args.pattern || ".");
|
|
675
|
+
return `[find: ${shortenPath(paths)}]`;
|
|
677
676
|
}
|
|
678
677
|
case "ls": {
|
|
679
678
|
const path = shortenPath(String(args.path || "."));
|
|
@@ -94,9 +94,19 @@ export class WelcomeComponent implements Component {
|
|
|
94
94
|
if (this.recentSessions.length === 0) {
|
|
95
95
|
sessionLines.push(` ${theme.fg("dim", "No recent sessions")}`);
|
|
96
96
|
} else {
|
|
97
|
+
// Reserve width for the bullet prefix (" • ") and the trailing " (timeAgo)"
|
|
98
|
+
// so the relative time is never the part that gets truncated. The name
|
|
99
|
+
// absorbs whatever space is left.
|
|
100
|
+
const bulletPrefix = ` ${theme.md.bullet} `;
|
|
101
|
+
const prefixWidth = visibleWidth(bulletPrefix);
|
|
97
102
|
for (const session of this.recentSessions.slice(0, 3)) {
|
|
103
|
+
const timeSuffixRaw = ` (${session.timeAgo})`;
|
|
104
|
+
const timeWidth = visibleWidth(timeSuffixRaw);
|
|
105
|
+
const nameBudget = Math.max(1, rightCol - prefixWidth - timeWidth);
|
|
106
|
+
const nameVis = visibleWidth(session.name);
|
|
107
|
+
const name = nameVis > nameBudget ? truncateToWidth(session.name, nameBudget) : session.name;
|
|
98
108
|
sessionLines.push(
|
|
99
|
-
|
|
109
|
+
`${theme.fg("dim", bulletPrefix)}${theme.fg("muted", name)}${theme.fg("dim", timeSuffixRaw)}`,
|
|
100
110
|
);
|
|
101
111
|
}
|
|
102
112
|
}
|
|
@@ -626,11 +626,7 @@ export class CommandController {
|
|
|
626
626
|
}
|
|
627
627
|
if (!(await this.ctx.session.newSession(options))) return;
|
|
628
628
|
this.ctx.resetObserverRegistry();
|
|
629
|
-
setSessionTerminalTitle(
|
|
630
|
-
this.ctx.sessionManager.getSessionName(),
|
|
631
|
-
this.ctx.sessionManager.getCwd(),
|
|
632
|
-
this.ctx.sessionManager.titleSource,
|
|
633
|
-
);
|
|
629
|
+
setSessionTerminalTitle(this.ctx.sessionManager.getSessionName(), this.ctx.sessionManager.getCwd());
|
|
634
630
|
|
|
635
631
|
this.ctx.statusLine.invalidate();
|
|
636
632
|
this.ctx.statusLine.setSessionStartTime(Date.now());
|
|
@@ -747,7 +743,7 @@ export class CommandController {
|
|
|
747
743
|
return;
|
|
748
744
|
}
|
|
749
745
|
const name = this.ctx.sessionManager.getSessionName()!;
|
|
750
|
-
setSessionTerminalTitle(name, this.ctx.sessionManager.getCwd()
|
|
746
|
+
setSessionTerminalTitle(name, this.ctx.sessionManager.getCwd());
|
|
751
747
|
this.ctx.statusLine.invalidate();
|
|
752
748
|
this.ctx.updateEditorBorderColor();
|
|
753
749
|
this.ctx.showStatus(`Session renamed to "${name}".`);
|
|
@@ -110,8 +110,11 @@ export class EventController {
|
|
|
110
110
|
assistantComponent.setToolResultImages(toolCallId, images);
|
|
111
111
|
return true;
|
|
112
112
|
}
|
|
113
|
-
#updateWorkingMessageFromIntent(intent:
|
|
114
|
-
|
|
113
|
+
#updateWorkingMessageFromIntent(intent: unknown): void {
|
|
114
|
+
// Streamed JSON can deliver non-string `_i` (object, number, boolean) before
|
|
115
|
+
// schema validation; `?.` only guards null/undefined, so guard the type too.
|
|
116
|
+
if (typeof intent !== "string") return;
|
|
117
|
+
const trimmed = intent.trim();
|
|
115
118
|
if (!trimmed || trimmed === this.#lastIntent) return;
|
|
116
119
|
this.#lastIntent = trimmed;
|
|
117
120
|
this.ctx.setWorkingMessage(`${trimmed} (esc to interrupt)`);
|
|
@@ -288,7 +291,7 @@ export class EventController {
|
|
|
288
291
|
const args = content.arguments;
|
|
289
292
|
if (!args || typeof args !== "object") continue;
|
|
290
293
|
if (INTENT_FIELD in args) {
|
|
291
|
-
this.#updateWorkingMessageFromIntent(args[INTENT_FIELD]
|
|
294
|
+
this.#updateWorkingMessageFromIntent(args[INTENT_FIELD]);
|
|
292
295
|
continue;
|
|
293
296
|
}
|
|
294
297
|
const tool = this.ctx.session.getToolByName(content.name);
|
|
@@ -678,8 +681,7 @@ export class EventController {
|
|
|
678
681
|
if (this.ctx.isBackgrounded === false) return;
|
|
679
682
|
const notify = settings.get("completion.notify");
|
|
680
683
|
if (notify === "off") return;
|
|
681
|
-
const title =
|
|
682
|
-
this.ctx.sessionManager.titleSource === "auto" ? undefined : this.ctx.sessionManager.getSessionName();
|
|
684
|
+
const title = this.ctx.sessionManager.getSessionName();
|
|
683
685
|
const message = title ? `${title}: Complete` : "Complete";
|
|
684
686
|
TERMINAL.sendNotification(message);
|
|
685
687
|
}
|
|
@@ -150,11 +150,7 @@ export class ExtensionUiController {
|
|
|
150
150
|
if (!success) {
|
|
151
151
|
return { cancelled: true };
|
|
152
152
|
}
|
|
153
|
-
setSessionTerminalTitle(
|
|
154
|
-
this.ctx.sessionManager.getSessionName(),
|
|
155
|
-
this.ctx.sessionManager.getCwd(),
|
|
156
|
-
this.ctx.sessionManager.titleSource,
|
|
157
|
-
);
|
|
153
|
+
setSessionTerminalTitle(this.ctx.sessionManager.getSessionName(), this.ctx.sessionManager.getCwd());
|
|
158
154
|
|
|
159
155
|
// Call setup callback if provided
|
|
160
156
|
if (options?.setup) {
|
|
@@ -223,11 +219,7 @@ export class ExtensionUiController {
|
|
|
223
219
|
if (!result) {
|
|
224
220
|
return { cancelled: true };
|
|
225
221
|
}
|
|
226
|
-
setSessionTerminalTitle(
|
|
227
|
-
this.ctx.sessionManager.getSessionName(),
|
|
228
|
-
this.ctx.sessionManager.getCwd(),
|
|
229
|
-
this.ctx.sessionManager.titleSource,
|
|
230
|
-
);
|
|
222
|
+
setSessionTerminalTitle(this.ctx.sessionManager.getSessionName(), this.ctx.sessionManager.getCwd());
|
|
231
223
|
this.ctx.chatContainer.clear();
|
|
232
224
|
this.ctx.renderInitialMessages();
|
|
233
225
|
await this.ctx.reloadTodos();
|
|
@@ -875,11 +867,7 @@ export class ExtensionUiController {
|
|
|
875
867
|
|
|
876
868
|
async #updateSessionName(name: string): Promise<void> {
|
|
877
869
|
await this.ctx.sessionManager.setSessionName(name, "user");
|
|
878
|
-
setSessionTerminalTitle(
|
|
879
|
-
this.ctx.sessionManager.getSessionName(),
|
|
880
|
-
this.ctx.sessionManager.getCwd(),
|
|
881
|
-
this.ctx.sessionManager.titleSource,
|
|
882
|
-
);
|
|
870
|
+
setSessionTerminalTitle(this.ctx.sessionManager.getSessionName(), this.ctx.sessionManager.getCwd());
|
|
883
871
|
}
|
|
884
872
|
|
|
885
873
|
#sendExtensionUserMessage: SendUserMessageHandler = (content, options) => {
|
|
@@ -750,7 +750,7 @@ export class SelectorController {
|
|
|
750
750
|
getCwd: () => string;
|
|
751
751
|
titleSource?: "auto" | "user" | undefined;
|
|
752
752
|
};
|
|
753
|
-
setSessionTerminalTitle(sessionManager.getSessionName?.(), sessionManager.getCwd()
|
|
753
|
+
setSessionTerminalTitle(sessionManager.getSessionName?.(), sessionManager.getCwd());
|
|
754
754
|
}
|
|
755
755
|
|
|
756
756
|
async #detachActiveSessionBeforeDeletion(sessionPath: string): Promise<boolean> {
|
|
@@ -51,7 +51,7 @@ import { normalizeLocalScheme } from "../tools/path-utils";
|
|
|
51
51
|
import { formatPhaseDisplayName } from "../tools/todo-write";
|
|
52
52
|
import type { EventBus } from "../utils/event-bus";
|
|
53
53
|
import { getEditorCommand, openInEditor } from "../utils/external-editor";
|
|
54
|
-
import { getSessionAccentAnsi,
|
|
54
|
+
import { getSessionAccentAnsi, getSessionAccentHex } from "../utils/session-color";
|
|
55
55
|
import { popTerminalTitle, pushTerminalTitle, setSessionTerminalTitle } from "../utils/title-generator";
|
|
56
56
|
import type { AssistantMessageComponent } from "./components/assistant-message";
|
|
57
57
|
import type { BashExecutionComponent } from "./components/bash-execution";
|
|
@@ -275,6 +275,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
275
275
|
this.#syncEditorMaxHeight();
|
|
276
276
|
this.#resizeHandler = () => {
|
|
277
277
|
this.#syncEditorMaxHeight();
|
|
278
|
+
this.updateEditorTopBorder();
|
|
278
279
|
};
|
|
279
280
|
process.stdout.on("resize", this.#resizeHandler);
|
|
280
281
|
try {
|
|
@@ -431,11 +432,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
431
432
|
// Start the UI
|
|
432
433
|
this.ui.start();
|
|
433
434
|
pushTerminalTitle();
|
|
434
|
-
setSessionTerminalTitle(
|
|
435
|
-
this.sessionManager.getSessionName(),
|
|
436
|
-
this.sessionManager.getCwd(),
|
|
437
|
-
this.sessionManager.titleSource,
|
|
438
|
-
);
|
|
435
|
+
setSessionTerminalTitle(this.sessionManager.getSessionName(), this.sessionManager.getCwd());
|
|
439
436
|
this.updateEditorBorderColor();
|
|
440
437
|
this.#syncEditorMaxHeight();
|
|
441
438
|
this.isInitialized = true;
|
|
@@ -647,7 +644,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
647
644
|
} else if (this.isPythonMode) {
|
|
648
645
|
this.editor.borderColor = theme.getPythonModeBorderColor();
|
|
649
646
|
} else {
|
|
650
|
-
const
|
|
647
|
+
const sessionName = this.sessionManager.getSessionName();
|
|
648
|
+
const hex = sessionName ? getSessionAccentHex(sessionName) : undefined;
|
|
651
649
|
const ansi = getSessionAccentAnsi(hex);
|
|
652
650
|
if (ansi) {
|
|
653
651
|
this.editor.borderColor = (str: string) => `${ansi}${str}\x1b[39m`;
|
|
@@ -77,23 +77,22 @@ If any check fails, continue or mark [blocked]. Do **NOT** reframe partial work
|
|
|
77
77
|
- Correctness first, brevity second, politeness third.
|
|
78
78
|
- Prefer concise, information-dense writing.
|
|
79
79
|
- Avoid repeating the user's request or narrating routine tool calls.
|
|
80
|
+
- Prefer tool output over prose explanation — tool results communicate directly; narration adds noise, not signal.
|
|
80
81
|
- Do not give time estimates or predictions.
|
|
81
82
|
- Do not emit closing summaries, recap paragraphs, or "what I did" wrap-ups. Final messages state the result and any blockers; the trace already shows the work.
|
|
82
83
|
</communication>
|
|
83
84
|
|
|
84
85
|
<output-contract>
|
|
85
|
-
- Brief preambles are allowed when they improve orientation, but they **MUST** stay short and **MUST NOT** be treated as completion.
|
|
86
86
|
- A phase boundary, todo flip, or completed sub-step is **NOT** a yield point. Continue directly to the next step in the same turn — do **NOT** stop to summarize, ask for acknowledgement, or wait for the user to say "go".
|
|
87
87
|
- Yield only when (a) the whole deliverable is complete, (b) you are [blocked], or (c) the user asked a question that requires their input.
|
|
88
88
|
- Claims about code, tools, tests, docs, or external sources **MUST** be grounded in what was actually observed.
|
|
89
|
-
-
|
|
89
|
+
- Persist on hard problems; do **NOT** punt half-solved work back
|
|
90
90
|
- Be brief in prose, not in evidence, verification, or blocking details.
|
|
91
91
|
</output-contract>
|
|
92
92
|
|
|
93
93
|
<default-follow-through>
|
|
94
94
|
- If the user's intent is clear and the next step is low-risk, proceed without asking.
|
|
95
95
|
- Ask only when the next step is irreversible, has external side effects, or requires a missing choice that materially changes the outcome.
|
|
96
|
-
- If you proceed, state what you did, what you verified, and what remains optional.
|
|
97
96
|
</default-follow-through>
|
|
98
97
|
|
|
99
98
|
<behavior>
|
|
@@ -138,20 +137,6 @@ Edge cases you ignored: pages at 3am.
|
|
|
138
137
|
- Tests you did not write are bugs shipped; edge cases you ignored are pages at 3am. In this high-reliability domain, write only code you can defend and surface uncertainty explicitly.
|
|
139
138
|
</principles>
|
|
140
139
|
|
|
141
|
-
<design-checklist>
|
|
142
|
-
Before writing or refactoring, verify:
|
|
143
|
-
- Caller expectations are explicit
|
|
144
|
-
- Failure modes surface the truth rather than plausible lies
|
|
145
|
-
- Interfaces preserve distinctions the domain already knows
|
|
146
|
-
- Existing repository patterns were considered before introducing new ones
|
|
147
|
-
- The simpler design has been considered
|
|
148
|
-
- Compiling is not correctness: verify behavior under the conditions that actually occur, including the failure modes
|
|
149
|
-
- Adversarial caller: what does a malicious caller do? what would a tired maintainer misunderstand?
|
|
150
|
-
- Cost named: before choosing the easy path, name what it costs (duplicated pattern across N files, unbounded resource use, escape hatch through the type system)
|
|
151
|
-
- Inhabit the call site: read your own change as someone who has never seen the implementation — does the interface reflect what happened? is any input silently discarded?
|
|
152
|
-
- Persist on hard problems; do **NOT** punt half-solved work back
|
|
153
|
-
</design-checklist>
|
|
154
|
-
|
|
155
140
|
{{SECTION_SEPARATOR "Environment"}}
|
|
156
141
|
|
|
157
142
|
You operate inside the Oh My Pi coding harness. Given a task, you **MUST** complete it using the tools available to you.
|
|
@@ -288,6 +273,7 @@ Don't open a file hoping. Hope is not a strategy.
|
|
|
288
273
|
{{#has tools "find"}}- Use `{{toolRefs.find}}` to map structure.{{/has}}
|
|
289
274
|
{{#has tools "read"}}- Use `{{toolRefs.read}}` with offset or limit rather than whole-file reads when practical.{{/has}}
|
|
290
275
|
{{#has tools "task"}}- Use `{{toolRefs.task}}` for investigate+edit when available.{{/has}}
|
|
276
|
+
- Load into context only what is necessary. Do not read files you do not need; do not fetch sections beyond what the task requires.
|
|
291
277
|
<tool-persistence>
|
|
292
278
|
- Use tools whenever they materially improve correctness, completeness, or grounding.
|
|
293
279
|
- Do not stop at the first plausible answer if another tool call would materially reduce uncertainty.
|
|
@@ -322,15 +308,6 @@ These are inviolable.
|
|
|
322
308
|
- If something is blocked, label it [blocked], say exactly what is missing, and distinguish it from work that is complete.
|
|
323
309
|
</completeness-contract>
|
|
324
310
|
|
|
325
|
-
# Design Integrity
|
|
326
|
-
|
|
327
|
-
Design integrity means the code tells the truth about what the system currently is — not what it used to be, not what was convenient to patch. Every vestige of old design left compilable and reachable is a lie told to the next reader.
|
|
328
|
-
- **The unit of change is the design decision, not the feature.** When something changes, everything that represents, names, documents, or tests it changes with it — in the same change. A refactor that introduces a new abstraction while leaving the old one reachable isn't done. A feature that requires a compatibility wrapper to land isn't done. The work is complete when the design is coherent, not when the tests pass.
|
|
329
|
-
- **One concept, one representation.** Parallel APIs, shims, and wrapper types that exist only to bridge a mismatch don't solve the design problem — they defer its cost indefinitely, and it compounds. Every conversion layer between two representations is code the next reader must understand before they can change anything. Pick one representation, migrate everything to it, delete the other.
|
|
330
|
-
- **Abstractions must cover their domain completely.** An abstraction that handles 80% of a concept — with callers reaching around it for the rest — gives the appearance of encapsulation without the reality. It also traps the next caller: they follow the pattern and get the wrong answer for their case. If callers routinely work around an abstraction, its boundary is wrong. Fix the boundary.
|
|
331
|
-
- **Types must preserve what the domain knows.** Collapsing structured information into a coarser representation — a boolean, a string where an enum belongs, a nullable where a tagged union belongs — discards distinctions the type system could have enforced. Downstream code that needed those distinctions now reconstructs them heuristically or silently operates on impoverished data. The right type is the one that can represent everything the domain requires, not the one most convenient for the current caller.
|
|
332
|
-
- **Optimize for the next edit, not the current diff.** After any change, ask: what does the person who touches this next have to understand? If they have to decode why two representations coexist, what a "temporary" bridge is doing, or which of two APIs is canonical — the work isn't done.
|
|
333
|
-
|
|
334
311
|
# Procedure
|
|
335
312
|
## 1. Scope
|
|
336
313
|
{{#if skills.length}}- You **MUST** read skills that match the task domain before starting.{{/if}}
|
|
@@ -353,6 +330,7 @@ Design integrity means the code tells the truth about what the system currently
|
|
|
353
330
|
> a. Semantic edits to files that don't import each other or share types being changed
|
|
354
331
|
> b. Investigating multiple subsystems
|
|
355
332
|
> c. Work that decomposes into independent pieces wired together at the end
|
|
333
|
+
- Multiple edits to different sections of the same file are independent — stable hash anchors make them safe to batch. Issue them in one response rather than sequentially.
|
|
356
334
|
- When a plan feels too large for a single turn, parallelize aggressively — do **NOT** abandon phases, silently drop them, or narrate scope cuts. Scope pressure is a signal to delegate, not to shrink the work.
|
|
357
335
|
{{/has}}
|
|
358
336
|
- Justify sequential work; default parallel. If you cannot articulate why B depends on A, it doesn't.
|
|
@@ -362,18 +340,16 @@ Design integrity means the code tells the truth about what the system currently
|
|
|
362
340
|
- Marking a todo done is a transition, not a stop: in the same turn, start the next pending todo. Acceptable inter-phase text is one short line ("phase 1 done, starting phase 2") — not a recap, not a question.
|
|
363
341
|
|
|
364
342
|
## 5. While working
|
|
365
|
-
|
|
366
|
-
-
|
|
367
|
-
-
|
|
368
|
-
-
|
|
369
|
-
-
|
|
370
|
-
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
- If others may be editing concurrently, re-read changed files and adapt.
|
|
376
|
-
- If blocked, exhaust tools and context first.
|
|
343
|
+
Focus on clarity and correctness. Make code easy to understand now and in the future.
|
|
344
|
+
- Fix problems at their source, not at their symptoms.
|
|
345
|
+
- Remove obsolete or unused code — no leftover comments, aliases, or re-exports.
|
|
346
|
+
- Prefer updating existing files over creating new ones, unless a new file is necessary.
|
|
347
|
+
- After editing, review from a user's perspective. Make sure your changes are clear and the interface matches behavior.
|
|
348
|
+
- If a tool fails or a file changes, re-read before acting.
|
|
349
|
+
{{#has tools "ask"}}- Ask before running destructive commands or deleting code you did not write.{{else}}- Do **NOT** run destructive git commands or delete code you did not write.{{/has}}
|
|
350
|
+
{{#has tools "web_search"}}- If unsure, search for more information instead of guessing.{{/has}}
|
|
351
|
+
- Adapt to concurrent edits by re-reading changed files.
|
|
352
|
+
- Use all available tools and context before declaring a blocker.
|
|
377
353
|
|
|
378
354
|
## 6. Verification
|
|
379
355
|
- Test rigorously. Prefer unit or end-to-end tests, you **MUST NOT** rely on mocks.
|
|
@@ -2,8 +2,8 @@ Performs structural AST-aware rewrites via native ast-grep.
|
|
|
2
2
|
|
|
3
3
|
<instruction>
|
|
4
4
|
- Use for codemods and structural rewrites where plain text replace is unsafe
|
|
5
|
-
- `
|
|
6
|
-
- Language is inferred from `
|
|
5
|
+
- `paths` is required and accepts an array of files, directories, globs, or internal URLs
|
|
6
|
+
- Language is inferred from `paths`; narrow each call to one language for deterministic rewrites
|
|
7
7
|
- Metavariables captured in `pat` (`$A`, `$$$ARGS`) are substituted into that entry's `out` template
|
|
8
8
|
- **Patterns match AST structure, not text.** `$NAME` = one node (captured); `$_` = one without binding; `$$$NAME` = zero-or-more (lazy — stops at next matchable element); `$$$` = zero-or-more without binding. Use `$$$NAME`, **NOT** `$$NAME` — the two-dollar form is invalid. Metavariable names are UPPERCASE and **MUST** be the whole AST node — partial text like `prefix$VAR` or `"hello $NAME"` does NOT work
|
|
9
9
|
- When the same metavariable appears twice, both occurrences **MUST** match identical code (`$A == $A` matches `x == x`, not `x == y`)
|
|
@@ -20,17 +20,17 @@ Performs structural AST-aware rewrites via native ast-grep.
|
|
|
20
20
|
|
|
21
21
|
<examples>
|
|
22
22
|
# Rename a call site across TypeScript files
|
|
23
|
-
`{"ops":[{"pat":"oldApi($$$ARGS)","out":"newApi($$$ARGS)"}],"
|
|
23
|
+
`{"ops":[{"pat":"oldApi($$$ARGS)","out":"newApi($$$ARGS)"}],"paths":["src/**/*.ts"]}`
|
|
24
24
|
# Delete matching calls
|
|
25
|
-
`{"ops":[{"pat":"console.log($$$ARGS)","out":""}],"
|
|
25
|
+
`{"ops":[{"pat":"console.log($$$ARGS)","out":""}],"paths":["src/**/*.ts"]}`
|
|
26
26
|
# Rewrite import source path
|
|
27
|
-
`{"ops":[{"pat":"import { $$$IMPORTS } from \"old-package\"","out":"import { $$$IMPORTS } from \"new-package\""}],"
|
|
27
|
+
`{"ops":[{"pat":"import { $$$IMPORTS } from \"old-package\"","out":"import { $$$IMPORTS } from \"new-package\""}],"paths":["src/**/*.ts"]}`
|
|
28
28
|
# Modernize to optional chaining (same metavariable enforces identity)
|
|
29
|
-
`{"ops":[{"pat":"$A && $A()","out":"$A?.()"}],"
|
|
29
|
+
`{"ops":[{"pat":"$A && $A()","out":"$A?.()"}],"paths":["src/**/*.ts"]}`
|
|
30
30
|
# Swap two arguments using captures
|
|
31
|
-
`{"ops":[{"pat":"assertEqual($A, $B)","out":"assertEqual($B, $A)"}],"
|
|
31
|
+
`{"ops":[{"pat":"assertEqual($A, $B)","out":"assertEqual($B, $A)"}],"paths":["tests/**/*.ts"]}`
|
|
32
32
|
# Python — convert print calls to logging
|
|
33
|
-
`{"ops":[{"pat":"print($$$ARGS)","out":"logger.info($$$ARGS)"}],"
|
|
33
|
+
`{"ops":[{"pat":"print($$$ARGS)","out":"logger.info($$$ARGS)"}],"paths":["src/**/*.py"]}`
|
|
34
34
|
</examples>
|
|
35
35
|
|
|
36
36
|
<critical>
|
|
@@ -2,8 +2,8 @@ Performs structural code search using AST matching via native ast-grep.
|
|
|
2
2
|
|
|
3
3
|
<instruction>
|
|
4
4
|
- Use when syntax shape matters more than raw text (calls, declarations, specific language constructs)
|
|
5
|
-
- `
|
|
6
|
-
- Language is inferred from `
|
|
5
|
+
- `paths` is required and accepts an array of files, directories, globs, or internal URLs
|
|
6
|
+
- Language is inferred from `paths`; narrow each call to one language when mixed-language trees could cause parse noise
|
|
7
7
|
- `pat` is a single AST pattern. Run separate calls for distinct unrelated patterns
|
|
8
8
|
- **Patterns match AST structure, not text** — whitespace/formatting is ignored
|
|
9
9
|
- `$NAME` captures one node; `$_` matches one without binding; `$$$NAME` captures zero-or-more (lazy — stops at next matchable element); `$$$` matches zero-or-more without binding. Use `$$$NAME`, **NOT** `$$NAME` — the two-dollar form is invalid and produces a parse error
|
|
@@ -13,7 +13,7 @@ Performs structural code search using AST matching via native ast-grep.
|
|
|
13
13
|
- C++ qualified calls used as expression statements need the statement semicolon in the pattern: use `ns::doThing($ARG);`, `$CALLEE($ARG);`, or wrap a statement snippet. Without `;`, tree-sitter-cpp may parse `ns::doThing($ARG)` as declaration-like syntax and return no matches
|
|
14
14
|
- For TS declarations/methods, tolerate unknown annotations: `async function $NAME($$$ARGS): $_ { $$$BODY }` or `class $_ { method($ARG: $_): $_ { $$$BODY } }`
|
|
15
15
|
- Declaration forms are structurally distinct — top-level `function foo`, class method `foo()`, and `const foo = () => {}` are different AST shapes; search the right form before concluding absence
|
|
16
|
-
- Loosest existence check: `pat: "executeBash"` with
|
|
16
|
+
- Loosest existence check: `pat: "executeBash"` with narrow `paths`
|
|
17
17
|
</instruction>
|
|
18
18
|
|
|
19
19
|
<output>
|
|
@@ -24,19 +24,19 @@ Performs structural code search using AST matching via native ast-grep.
|
|
|
24
24
|
|
|
25
25
|
<examples>
|
|
26
26
|
# Search TypeScript files under src
|
|
27
|
-
`{"pat":"console.log($$$)","
|
|
27
|
+
`{"pat":"console.log($$$)","paths":["src/**/*.ts"]}`
|
|
28
28
|
# Named imports from a specific package
|
|
29
|
-
`{"pat":"import { $$$IMPORTS } from \"react\"","
|
|
29
|
+
`{"pat":"import { $$$IMPORTS } from \"react\"","paths":["src/**/*.ts"]}`
|
|
30
30
|
# Arrow functions assigned to a const
|
|
31
|
-
`{"pat":"const $NAME = ($$$ARGS) => $BODY","
|
|
31
|
+
`{"pat":"const $NAME = ($$$ARGS) => $BODY","paths":["src/utils/**/*.ts"]}`
|
|
32
32
|
# Method call on any object, ignoring method name with `$_`
|
|
33
|
-
`{"pat":"logger.$_($$$ARGS)","
|
|
33
|
+
`{"pat":"logger.$_($$$ARGS)","paths":["src/**/*.ts"]}`
|
|
34
34
|
# Loosest existence check for a symbol in one file
|
|
35
|
-
`{"pat":"processItems","
|
|
35
|
+
`{"pat":"processItems","paths":["src/worker.ts"]}`
|
|
36
36
|
</examples>
|
|
37
37
|
|
|
38
38
|
<critical>
|
|
39
|
-
- Avoid repo-root scans — narrow `
|
|
40
|
-
- Parse issues are query failure, not evidence of absence: repair the pattern or tighten `
|
|
39
|
+
- Avoid repo-root scans — narrow `paths` first
|
|
40
|
+
- Parse issues are query failure, not evidence of absence: repair the pattern or tighten `paths` before concluding "no matches"
|
|
41
41
|
- For broad/open-ended exploration across subsystems, use Task tool with explore subagent first
|
|
42
42
|
</critical>
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
Run code in a persistent kernel, using a series of codeblocks acting as cells.
|
|
2
2
|
|
|
3
3
|
<instruction>
|
|
4
|
-
Each cell is
|
|
4
|
+
Each cell is introduced by a header line of the form:
|
|
5
5
|
|
|
6
6
|
```
|
|
7
|
-
|
|
7
|
+
===== <info> =====
|
|
8
8
|
```
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
|
|
10
|
+
where each side is at least 5 equal signs. Everything between one header and the next (or end of input) is the cell's code, verbatim. The info is space-separated tokens, all optional, in any order:
|
|
11
|
+
- **Language**: {{#if py}}`py` for Python{{/if}}{{#ifAll py js}}, {{/ifAll}}{{#if js}}`js` / `ts` for JavaScript{{/if}}.{{#ifAll py js}} Omitted → inherit the previous cell's language (the first cell defaults to Python, falling back to JavaScript when Python is unavailable).{{else}} Omitted → inherit the previous cell's language.{{/ifAll}}
|
|
12
|
+
- **Title shorthand**: `py:"…"`, `js:"…"`, `ts:"…"` set the language and the cell title together.
|
|
11
13
|
- **Attributes**:
|
|
12
|
-
- `id
|
|
13
|
-
- `t
|
|
14
|
-
- `rst
|
|
14
|
+
- `id:"…"` — cell title (when language is unchanged or already set).
|
|
15
|
+
- `t:<duration>` — per-cell timeout. Duration is digits with optional `ms` / `s` / `m` units (e.g. `t:500ms`, `t:15s`, `t:2m`). Default 30s.
|
|
16
|
+
- `rst` — wipe **this cell's own language kernel** before running.{{#ifAll py js}} Other languages are untouched.{{/ifAll}}
|
|
15
17
|
|
|
16
18
|
**Work incrementally:** one logical step per cell (imports, define, test, use). Pass multiple small cells in one call. Define small reusable functions you can debug individually. You **MUST** put workflow explanations in the assistant message or cell title — never inside cell code.
|
|
17
19
|
|
|
@@ -31,18 +33,6 @@ write(path, content) → str
|
|
|
31
33
|
Write content to a file (creates parent directories). Returns the resolved path.
|
|
32
34
|
append(path, content) → str
|
|
33
35
|
Append content to a file. Returns the resolved path.
|
|
34
|
-
stat(path) → {path, size, is_file, is_dir, mtime}
|
|
35
|
-
File or directory metadata. mtime is an ISO-8601 string.
|
|
36
|
-
find(pattern, path?=".", type?="file", limit?=1000, hidden?=False, sort_by_mtime?=False, maxdepth?=None, mindepth?=None) → list[path]
|
|
37
|
-
Recursive glob find. Respects .gitignore.
|
|
38
|
-
glob(pattern, path?=".", hidden?=False) → list[path]
|
|
39
|
-
Non-recursive glob. Use find() for recursive walks. Respects .gitignore.
|
|
40
|
-
grep(pattern, path, ignore_case?=False, literal?=False, context?=0) → list[{line, text}]
|
|
41
|
-
Search a single file.
|
|
42
|
-
rgrep(pattern, path?=".", glob_pattern?="*", ignore_case?=False, literal?=False, limit?=100, hidden?=False) → list[{file, line, text}]
|
|
43
|
-
Search recursively across files. Respects .gitignore.
|
|
44
|
-
sed(path, pattern, repl, flags?=0) → int
|
|
45
|
-
Regex replace in a file (like sed -i). Returns replacement count.
|
|
46
36
|
tree(path?=".", max_depth?=3, show_hidden?=False) → str
|
|
47
37
|
Render a directory tree.
|
|
48
38
|
diff(a, b) → str
|
|
@@ -63,30 +53,22 @@ Cells render like a Jupyter notebook. Pass any value to `display(value)`; non-pr
|
|
|
63
53
|
</output>
|
|
64
54
|
|
|
65
55
|
<caution>
|
|
66
|
-
- In session mode, use `rst
|
|
56
|
+
- In session mode, use `rst` on a cell to wipe its language's kernel before running.{{#ifAll py js}} Reset is per-language: a python cell's `rst` does not touch the JavaScript kernel and vice versa.{{/ifAll}}
|
|
67
57
|
{{#if js}}- **js**: the VM exposes a selective `process` subset, Web APIs, `Buffer`, `fs/promises`.
|
|
68
58
|
{{/if}}</caution>
|
|
69
59
|
|
|
70
60
|
<example>
|
|
71
|
-
{{#if py}}
|
|
61
|
+
{{#if py}}===== py:"imports" t:10s =====
|
|
72
62
|
import json
|
|
73
63
|
from pathlib import Path
|
|
74
|
-
```
|
|
75
64
|
|
|
76
|
-
|
|
65
|
+
===== py:"load config" =====
|
|
77
66
|
data = json.loads(read('package.json'))
|
|
78
67
|
display(data)
|
|
79
|
-
```
|
|
80
68
|
{{/if}}{{#ifAll py js}}
|
|
81
|
-
|
|
82
|
-
{{/ifAll}}{{#if js}}```js id="js summary" rst=true
|
|
69
|
+
{{/ifAll}}{{#if js}}===== js:"js summary" rst =====
|
|
83
70
|
const data = JSON.parse(await read('package.json'));
|
|
84
71
|
display(data);
|
|
85
72
|
return data.name;
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
```
|
|
89
|
-
return 'still JavaScript';
|
|
90
|
-
```
|
|
91
73
|
{{/if}}
|
|
92
74
|
</example>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
Finds files using fast pattern matching that works with any codebase size.
|
|
2
2
|
|
|
3
3
|
<instruction>
|
|
4
|
+
- `paths` is required and accepts an array of globs, files, or directories
|
|
4
5
|
- You **SHOULD** perform multiple searches in parallel when potentially useful
|
|
5
6
|
</instruction>
|
|
6
7
|
|
|
@@ -10,7 +11,7 @@ Matching file paths sorted by modification time (most recent first). Truncated a
|
|
|
10
11
|
|
|
11
12
|
<examples>
|
|
12
13
|
# Find files
|
|
13
|
-
`{"
|
|
14
|
+
`{"paths": ["src/**/*.ts"], "limit": 1000}`
|
|
14
15
|
</examples>
|
|
15
16
|
|
|
16
17
|
<avoid>
|