@oh-my-pi/pi-coding-agent 13.9.2 → 13.9.4
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 +64 -0
- package/examples/sdk/02-custom-model.ts +2 -1
- package/package.json +7 -7
- package/src/cli/args.ts +10 -6
- package/src/cli/list-models.ts +2 -2
- package/src/commands/launch.ts +3 -3
- package/src/config/model-registry.ts +136 -38
- package/src/config/model-resolver.ts +47 -21
- package/src/config/settings-schema.ts +56 -2
- package/src/discovery/helpers.ts +3 -3
- package/src/extensibility/custom-tools/types.ts +2 -0
- package/src/extensibility/extensions/loader.ts +3 -2
- package/src/extensibility/extensions/types.ts +10 -7
- package/src/extensibility/hooks/types.ts +2 -0
- package/src/main.ts +5 -22
- package/src/memories/index.ts +7 -3
- package/src/modes/components/footer.ts +10 -8
- package/src/modes/components/model-selector.ts +33 -38
- package/src/modes/components/settings-defs.ts +32 -3
- package/src/modes/components/settings-selector.ts +16 -5
- package/src/modes/components/status-line/context-thresholds.ts +68 -0
- package/src/modes/components/status-line/segments.ts +11 -12
- package/src/modes/components/status-line.ts +2 -6
- package/src/modes/components/thinking-selector.ts +7 -7
- package/src/modes/components/tree-selector.ts +3 -2
- package/src/modes/controllers/command-controller.ts +11 -26
- package/src/modes/controllers/event-controller.ts +16 -3
- package/src/modes/controllers/input-controller.ts +4 -2
- package/src/modes/controllers/selector-controller.ts +5 -4
- package/src/modes/interactive-mode.ts +2 -2
- package/src/modes/rpc/rpc-client.ts +5 -10
- package/src/modes/rpc/rpc-types.ts +5 -5
- package/src/modes/theme/theme.ts +8 -3
- package/src/priority.json +1 -0
- package/src/prompts/system/auto-handoff-threshold-focus.md +1 -0
- package/src/prompts/system/system-prompt.md +18 -2
- package/src/prompts/tools/hashline.md +139 -83
- package/src/sdk.ts +24 -16
- package/src/session/agent-session.ts +261 -118
- package/src/session/agent-storage.ts +14 -14
- package/src/session/compaction/compaction.ts +500 -13
- package/src/session/messages.ts +12 -1
- package/src/session/session-manager.ts +77 -19
- package/src/slash-commands/builtin-registry.ts +48 -0
- package/src/task/agents.ts +3 -2
- package/src/task/executor.ts +2 -2
- package/src/task/types.ts +2 -1
- package/src/thinking.ts +87 -0
- package/src/tools/browser.ts +15 -6
- package/src/tools/fetch.ts +118 -100
- package/src/tools/index.ts +2 -1
- package/src/web/kagi.ts +62 -7
- package/src/web/search/providers/exa.ts +74 -3
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Handles TUI rendering and user interaction, delegating business logic to AgentSession.
|
|
4
4
|
*/
|
|
5
5
|
import * as path from "node:path";
|
|
6
|
-
import type
|
|
6
|
+
import { type Agent, type AgentMessage, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
7
7
|
import type { AssistantMessage, ImageContent, Message, Model, UsageReport } from "@oh-my-pi/pi-ai";
|
|
8
8
|
import type { Component, Loader, SlashCommand } from "@oh-my-pi/pi-tui";
|
|
9
9
|
import {
|
|
@@ -423,7 +423,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
423
423
|
} else if (this.isPythonMode) {
|
|
424
424
|
this.editor.borderColor = theme.getPythonModeBorderColor();
|
|
425
425
|
} else {
|
|
426
|
-
const level = this.session.thinkingLevel
|
|
426
|
+
const level = this.session.thinkingLevel ?? ThinkingLevel.Off;
|
|
427
427
|
this.editor.borderColor = theme.getThinkingBorderColor(level);
|
|
428
428
|
}
|
|
429
429
|
this.updateEditorTopBorder();
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Spawns the agent in RPC mode and provides a typed API for all operations.
|
|
5
5
|
*/
|
|
6
|
-
import type { AgentEvent, AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
7
|
-
import type { ImageContent,
|
|
6
|
+
import type { AgentEvent, AgentMessage, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
7
|
+
import type { Effort, ImageContent, Model } from "@oh-my-pi/pi-ai";
|
|
8
8
|
import { isRecord, ptree, readJsonl } from "@oh-my-pi/pi-utils";
|
|
9
9
|
import type { BashResult } from "../../exec/bash-executor";
|
|
10
10
|
import type { SessionStats } from "../../session/agent-session";
|
|
@@ -34,12 +34,7 @@ export interface RpcClientOptions {
|
|
|
34
34
|
args?: string[];
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
export
|
|
38
|
-
provider: string;
|
|
39
|
-
id: string;
|
|
40
|
-
contextWindow: number;
|
|
41
|
-
reasoning: boolean;
|
|
42
|
-
}
|
|
37
|
+
export type ModelInfo = Pick<Model, "provider" | "id" | "contextWindow" | "reasoning" | "thinking">;
|
|
43
38
|
|
|
44
39
|
export type RpcEventListener = (event: AgentEvent) => void;
|
|
45
40
|
|
|
@@ -284,7 +279,7 @@ export class RpcClient {
|
|
|
284
279
|
*/
|
|
285
280
|
async cycleModel(): Promise<{
|
|
286
281
|
model: { provider: string; id: string };
|
|
287
|
-
thinkingLevel: ThinkingLevel;
|
|
282
|
+
thinkingLevel: ThinkingLevel | undefined;
|
|
288
283
|
isScoped: boolean;
|
|
289
284
|
} | null> {
|
|
290
285
|
const response = await this.#send({ type: "cycle_model" });
|
|
@@ -309,7 +304,7 @@ export class RpcClient {
|
|
|
309
304
|
/**
|
|
310
305
|
* Cycle thinking level.
|
|
311
306
|
*/
|
|
312
|
-
async cycleThinkingLevel(): Promise<{ level:
|
|
307
|
+
async cycleThinkingLevel(): Promise<{ level: Effort } | null> {
|
|
313
308
|
const response = await this.#send({ type: "cycle_thinking_level" });
|
|
314
309
|
return this.#getData(response);
|
|
315
310
|
}
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Commands are sent as JSON lines on stdin.
|
|
5
5
|
* Responses and events are emitted as JSON lines on stdout.
|
|
6
6
|
*/
|
|
7
|
-
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
8
|
-
import type { ImageContent, Model
|
|
7
|
+
import type { AgentMessage, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
8
|
+
import type { Effort, ImageContent, Model } from "@oh-my-pi/pi-ai";
|
|
9
9
|
import type { BashResult } from "../../exec/bash-executor";
|
|
10
10
|
import type { SessionStats } from "../../session/agent-session";
|
|
11
11
|
import type { CompactionResult } from "../../session/compaction";
|
|
@@ -70,7 +70,7 @@ export type RpcCommand =
|
|
|
70
70
|
|
|
71
71
|
export interface RpcSessionState {
|
|
72
72
|
model?: Model;
|
|
73
|
-
thinkingLevel: ThinkingLevel;
|
|
73
|
+
thinkingLevel: ThinkingLevel | undefined;
|
|
74
74
|
isStreaming: boolean;
|
|
75
75
|
isCompacting: boolean;
|
|
76
76
|
steeringMode: "all" | "one-at-a-time";
|
|
@@ -114,7 +114,7 @@ export type RpcResponse =
|
|
|
114
114
|
type: "response";
|
|
115
115
|
command: "cycle_model";
|
|
116
116
|
success: true;
|
|
117
|
-
data: { model: Model; thinkingLevel: ThinkingLevel; isScoped: boolean } | null;
|
|
117
|
+
data: { model: Model; thinkingLevel: ThinkingLevel | undefined; isScoped: boolean } | null;
|
|
118
118
|
}
|
|
119
119
|
| {
|
|
120
120
|
id?: string;
|
|
@@ -131,7 +131,7 @@ export type RpcResponse =
|
|
|
131
131
|
type: "response";
|
|
132
132
|
command: "cycle_thinking_level";
|
|
133
133
|
success: true;
|
|
134
|
-
data: { level:
|
|
134
|
+
data: { level: Effort } | null;
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
// Queue modes
|
package/src/modes/theme/theme.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import type { ThinkingLevel } from "@oh-my-pi/pi-
|
|
3
|
+
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
4
|
+
import type { Effort } from "@oh-my-pi/pi-ai";
|
|
4
5
|
import {
|
|
5
6
|
detectMacOSAppearance,
|
|
6
7
|
type HighlightColors as NativeHighlightColors,
|
|
@@ -108,6 +109,7 @@ export type SymbolKey =
|
|
|
108
109
|
| "icon.warning"
|
|
109
110
|
| "icon.rewind"
|
|
110
111
|
| "icon.auto"
|
|
112
|
+
| "icon.fast"
|
|
111
113
|
| "icon.extensionSkill"
|
|
112
114
|
| "icon.extensionTool"
|
|
113
115
|
| "icon.extensionSlashCommand"
|
|
@@ -268,6 +270,7 @@ const UNICODE_SYMBOLS: SymbolMap = {
|
|
|
268
270
|
"icon.warning": "⚠",
|
|
269
271
|
"icon.rewind": "↶",
|
|
270
272
|
"icon.auto": "⟲",
|
|
273
|
+
"icon.fast": "⚡",
|
|
271
274
|
"icon.extensionSkill": "✦",
|
|
272
275
|
"icon.extensionTool": "🛠",
|
|
273
276
|
"icon.extensionSlashCommand": "⌘",
|
|
@@ -499,7 +502,7 @@ const NERD_SYMBOLS: SymbolMap = {
|
|
|
499
502
|
"icon.rewind": "\uf0e2",
|
|
500
503
|
// pick: | alt:
|
|
501
504
|
"icon.auto": "\u{f0068}",
|
|
502
|
-
|
|
505
|
+
"icon.fast": "\uf0e7",
|
|
503
506
|
"icon.extensionSkill": "\uf0eb",
|
|
504
507
|
// pick: | alt:
|
|
505
508
|
"icon.extensionTool": "\uf0ad",
|
|
@@ -680,6 +683,7 @@ const ASCII_SYMBOLS: SymbolMap = {
|
|
|
680
683
|
"icon.warning": "[!]",
|
|
681
684
|
"icon.rewind": "<-",
|
|
682
685
|
"icon.auto": "[A]",
|
|
686
|
+
"icon.fast": ">>",
|
|
683
687
|
"icon.extensionSkill": "SK",
|
|
684
688
|
"icon.extensionTool": "TL",
|
|
685
689
|
"icon.extensionSlashCommand": "/",
|
|
@@ -1220,7 +1224,7 @@ export class Theme {
|
|
|
1220
1224
|
return this.mode;
|
|
1221
1225
|
}
|
|
1222
1226
|
|
|
1223
|
-
getThinkingBorderColor(level: ThinkingLevel): (str: string) => string {
|
|
1227
|
+
getThinkingBorderColor(level: ThinkingLevel | Effort): (str: string) => string {
|
|
1224
1228
|
// Map thinking levels to dedicated theme colors
|
|
1225
1229
|
switch (level) {
|
|
1226
1230
|
case "off":
|
|
@@ -1381,6 +1385,7 @@ export class Theme {
|
|
|
1381
1385
|
warning: this.#symbols["icon.warning"],
|
|
1382
1386
|
rewind: this.#symbols["icon.rewind"],
|
|
1383
1387
|
auto: this.#symbols["icon.auto"],
|
|
1388
|
+
fast: this.#symbols["icon.fast"],
|
|
1384
1389
|
extensionSkill: this.#symbols["icon.extensionSkill"],
|
|
1385
1390
|
extensionTool: this.#symbols["icon.extensionTool"],
|
|
1386
1391
|
extensionSlashCommand: this.#symbols["icon.extensionSlashCommand"],
|
package/src/priority.json
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Threshold-triggered maintenance: preserve critical implementation state and immediate next actions.
|
|
@@ -186,7 +186,7 @@ Use the Task tool unless the change is:
|
|
|
186
186
|
- A direct answer or explanation with no code changes
|
|
187
187
|
- A command the user asked you to run yourself
|
|
188
188
|
|
|
189
|
-
For everything else — multi-file changes, refactors, new features, test additions, investigations — break the work into tasks and delegate. Err on the side of delegating
|
|
189
|
+
For everything else — multi-file changes, refactors, new features, test additions, investigations — break the work into tasks and delegate once the target design is settled. Err on the side of delegating after the architectural direction is fixed.
|
|
190
190
|
</eager-tasks>
|
|
191
191
|
{{/if}}
|
|
192
192
|
|
|
@@ -218,6 +218,18 @@ These are inviolable. Violation is system failure.
|
|
|
218
218
|
6. You **MUST NOT** ask for information obtainable from tools, repo context, or files. File referenced → you **MUST** locate and read it. Path implied → you **MUST** resolve it.
|
|
219
219
|
7. Full CUTOVER is **REQUIRED**. You **MUST** replace old usage everywhere you touch — no backwards-compat shims, no gradual migration, no "keeping both for now." The old way is dead; lingering instances **MUST** be treated as bugs.
|
|
220
220
|
|
|
221
|
+
# Design Integrity
|
|
222
|
+
- You **MUST** prefer a coherent final design over a minimally invasive patch.
|
|
223
|
+
- You **MUST NOT** preserve obsolete abstractions to reduce edit scope.
|
|
224
|
+
- Temporary bridges are **PROHIBITED** unless the user explicitly asks for a migration path.
|
|
225
|
+
- If a refactor introduces a new canonical abstraction, you **MUST** migrate consumers to it instead of wrapping it in compatibility helpers.
|
|
226
|
+
- Parallel APIs that express the same concept are a bug, not a convenience.
|
|
227
|
+
- Boolean compatibility helpers that collapse richer capability models are **PROHIBITED**.
|
|
228
|
+
- You **MUST NOT** collapse structured capability data into lossy booleans or convenience wrappers unless the domain is truly boolean.
|
|
229
|
+
- If a change removes a field, type, or API, all fixtures, tests, docs, and callsites using it **MUST** be updated in the same change.
|
|
230
|
+
- You **MUST** optimize for the next maintainer's edit, not for minimizing the current diff.
|
|
231
|
+
- "Works" is insufficient. The result **MUST** also be singular, obvious, and maintainable.
|
|
232
|
+
|
|
221
233
|
# Procedure
|
|
222
234
|
## 1. Scope
|
|
223
235
|
{{#if skills.length}}- If a skill matches the domain, you **MUST** read it before starting.{{/if}}
|
|
@@ -245,6 +257,8 @@ Justify sequential work; default parallel. Cannot articulate why B depends on A
|
|
|
245
257
|
- You **MUST** write idiomatic, simple, maintainable code. Complexity **MUST** earn its place.
|
|
246
258
|
- You **MUST** fix in the place the bug lives. You **MUST NOT** bandaid the problem within the caller.
|
|
247
259
|
- You **MUST** clean up unused code ruthlessly: dead parameters, unused helpers, orphaned types. You **MUST** delete them and update callers. Resulting code **MUST** be pristine.
|
|
260
|
+
- For every new abstraction, you **MUST** identify what becomes redundant: old helpers, fallback branches, compatibility adapters, duplicate tests, stale fixtures, and docs that describe removed behavior.
|
|
261
|
+
- You **MUST** delete or rewrite redundant code in the same change. Leaving obsolete code reachable, compilable, or tested is a failure of cutover.
|
|
248
262
|
- You **MUST NOT** leave breadcrumbs. When you delete or move code, you **MUST** remove it cleanly — no `// moved to X` comments, no `// relocated` markers, no re-exports from the old location. The old location **MUST** be removed without trace.
|
|
249
263
|
- You **MUST** fix from first principles. You **MUST NOT** apply bandaids. The root cause **MUST** be found and fixed at its source. A symptom suppressed is a bug deferred.
|
|
250
264
|
- When a tool call fails or returns unexpected output, you **MUST** read the full error and diagnose it.
|
|
@@ -297,8 +311,10 @@ Today is '{{date}}', and your work begins now. Get it right.
|
|
|
297
311
|
|
|
298
312
|
<critical>
|
|
299
313
|
- You **MUST** use the most specialized tool, **NEVER** `cat` if there's tool.bash, `rg/grep`:tool.grep, `find`:tool.find, `sed`:tool.edit…
|
|
300
|
-
- Every turn **MUST** advance the deliverable.
|
|
314
|
+
- Every turn **MUST** materially advance the deliverable.
|
|
301
315
|
- You **MUST** default to action. You **MUST NOT** ask for confirmation to continue work. If you hit an error, you **MUST** fix it. If you know the next step, you **MUST** take it. The user will intervene if needed.
|
|
316
|
+
- You **MUST NOT** make speculative edits before understanding the surrounding design.
|
|
317
|
+
- You **MUST** default to informed action. You **MUST NOT** ask for confirmation to continue work. If you hit an error, you **MUST** fix it. If you know the next step, you **MUST** take it. The user will intervene if needed.
|
|
302
318
|
- You **MUST NOT** ask when the answer may be obtained from available tools or repo context/files.
|
|
303
319
|
- You **MUST** verify the effect. When a task involves a behavioral change, you **MUST** confirm the change is observable before yielding: run the specific test, command, or scenario that covers your change.
|
|
304
320
|
</critical>
|
|
@@ -1,12 +1,31 @@
|
|
|
1
1
|
Applies precise, surgical file edits by referencing `LINE#ID` tags from `read` output. Each tag uniquely identifies a line, so edits remain stable even when lines shift.
|
|
2
2
|
|
|
3
|
+
<critical>
|
|
4
|
+
- Never anchor insertions on blank lines or lone closing delimiters like `}`, `]`, `)`, `};`, or `),` — they are mechanically valid tags but semantically unstable edit boundaries.
|
|
5
|
+
- For `append`/`prepend`, `lines` **MUST** contain only the newly introduced content. Do not re-emit surrounding braces, brackets, parentheses, or sibling declarations that already exist in the file.
|
|
6
|
+
- `append`/`prepend` are for self-contained new content only: sibling declarations, new object/list members, new test cases, or similar additions whose surrounding structure stays unchanged.
|
|
7
|
+
- When changing existing code near a block tail or closing delimiter, default to `replace` over the owned span instead of inserting around the boundary.
|
|
8
|
+
- When adding a sibling declaration, default to `prepend` on the next sibling declaration instead of `append` on the previous block's closing brace.
|
|
9
|
+
- If any inserted line is just a closing delimiter, stop and re-check the edit shape. A closing line is only valid when it belongs to newly introduced structure; if it belongs to surrounding existing structure, your edit should be a `replace` that consumes the old boundary.
|
|
10
|
+
</critical>
|
|
11
|
+
|
|
3
12
|
<workflow>
|
|
4
13
|
Follow these steps in order for every edit:
|
|
5
14
|
1. You **SHOULD** issue a `read` call before editing to get fresh `LINE#ID` tags. Editing without current tags causes mismatches because other edits or external changes may have shifted line numbers since your last read.
|
|
6
15
|
2. You **MUST** submit one `edit` call per file with all operations. Multiple calls to the same file require re-reading between each one (tags shift after each edit), so batching avoids wasted round-trips. Think your changes through before submitting.
|
|
7
|
-
3. You **MUST** pick the
|
|
16
|
+
3. You **MUST** pick the operation that matches the owning structure, not merely the smallest textual diff. Use the smallest operation only when it still cleanly owns the changed syntax. If a tiny edit would patch around a block tail, delimiter, or neighboring structural line, expand it to the semantically correct `replace` span instead.
|
|
8
17
|
</workflow>
|
|
9
18
|
|
|
19
|
+
<checklist>
|
|
20
|
+
Before choosing the payload, answer these questions in order:
|
|
21
|
+
1. **Am I replacing existing lines or inserting new ones?** If any existing line changes, use `replace` for the full changed span.
|
|
22
|
+
2. **What declaration or block owns this anchor line?** Prefer declaration/header lines over blank lines or delimiters.
|
|
23
|
+
3. **Am I inserting self-contained new content, or changing an existing block?** Use `append`/`prepend` only for self-contained additions. If surrounding code, indentation, or closers also change, use `replace`.
|
|
24
|
+
4. **Am I editing near a block tail or closing delimiter?** If yes, expand the edit to own that tail instead of patching just the last line or two.
|
|
25
|
+
5. **Does `lines` contain only new content?** For `append`/`prepend`, do not include existing closing braces or other surrounding syntax from the file.
|
|
26
|
+
6. **Would the replacement duplicate the line immediately after `end`?** If yes, extend the range to consume the old boundary.
|
|
27
|
+
</checklist>
|
|
28
|
+
|
|
10
29
|
<operations>
|
|
11
30
|
**`path`** — the path to the file to edit.
|
|
12
31
|
**`move`** — if set, move the file to the given path.
|
|
@@ -25,9 +44,12 @@ Tags are applied bottom-up: later edits (by position) are applied first, so earl
|
|
|
25
44
|
</operations>
|
|
26
45
|
|
|
27
46
|
<rules>
|
|
28
|
-
1. **Anchor on unique,
|
|
29
|
-
2. **Use `prepend`/`append` only
|
|
30
|
-
3. **
|
|
47
|
+
1. **Anchor on unique declaration or header lines, not delimiters.** Safe anchors are lines like `function beta() {`, `if (…) {`, `const value =`, or other unique structural headers. Blank lines and lone closers like `}` are never good insertion anchors.
|
|
48
|
+
2. **Use `prepend`/`append` only for self-contained additions whose surrounding structure stays unchanged.** If you are adding a sibling declaration, prefer `prepend` on the next sibling declaration instead of `append` on the previous block closer.
|
|
49
|
+
3. **If the change touches existing code near a block tail, use range `replace` over the owned span.** Do not patch just the final line(s) before a closing delimiter when the surrounding structure, indentation, or control flow is also changing.
|
|
50
|
+
4. **Match surrounding indentation for new lines.** When inserting via `prepend`/`append`, look at the anchor line and its neighbors in the `read` output. New `lines` entries **MUST** carry the same leading whitespace. If the context uses tabs at depth 1 (`\t`), your inserted declarations need `\t` and bodies need `\t\t`. Inserting at indent level 0 inside an indented block is always wrong.
|
|
51
|
+
5. **Consume the old closing boundary when your replacement emits one.** If the replacement's final line is a closing delimiter like `}`, `]`, or `)`, the `end` line **MUST** include the original matching closer that would otherwise remain in the file. Before submitting, compare the replacement's last line with the line immediately after `end`; if they would be the same boundary, extend the range so the old closer is removed.
|
|
52
|
+
6. **If you expect a second tiny cleanup edit for `}`, `};`, indentation, or a duplicated boundary, your first edit shape is wrong.** Expand the first `replace` so it owns the structural tail in one shot.
|
|
31
53
|
</rules>
|
|
32
54
|
|
|
33
55
|
<recovery>
|
|
@@ -36,17 +58,38 @@ Edits can fail in two ways. Here is exactly what to do for each:
|
|
|
36
58
|
2. **No-op (`identical`):** Your replacement is identical to the existing content — nothing changed. You **MUST NOT** resubmit the same edit. Re-read the target lines to understand what is actually there, then adjust your edit.
|
|
37
59
|
</recovery>
|
|
38
60
|
|
|
39
|
-
<
|
|
61
|
+
<examples>
|
|
62
|
+
All examples below reference the same file, `util.ts`:
|
|
40
63
|
```ts
|
|
41
|
-
{{hlinefull
|
|
64
|
+
{{hlinefull 1 "// @ts-ignore"}}
|
|
65
|
+
{{hlinefull 2 "const timeout = 5000;"}}
|
|
66
|
+
{{hlinefull 3 "const tag = \"DO NOT SHIP\";"}}
|
|
67
|
+
{{hlinefull 4 ""}}
|
|
68
|
+
{{hlinefull 5 "function alpha() {"}}
|
|
69
|
+
{{hlinefull 6 "\tlog();"}}
|
|
70
|
+
{{hlinefull 7 "}"}}
|
|
71
|
+
{{hlinefull 8 ""}}
|
|
72
|
+
{{hlinefull 9 "function beta() {"}}
|
|
73
|
+
{{hlinefull 10 "\t// TODO: remove after migration"}}
|
|
74
|
+
{{hlinefull 11 "\tlegacy();"}}
|
|
75
|
+
{{hlinefull 12 "\ttry {"}}
|
|
76
|
+
{{hlinefull 13 "\t\treturn parse(data);"}}
|
|
77
|
+
{{hlinefull 14 "\t} catch (err) {"}}
|
|
78
|
+
{{hlinefull 15 "\t\tconsole.error(err);"}}
|
|
79
|
+
{{hlinefull 16 "\t\treturn null;"}}
|
|
80
|
+
{{hlinefull 17 "\t}"}}
|
|
81
|
+
{{hlinefull 18 "}"}}
|
|
42
82
|
```
|
|
83
|
+
|
|
84
|
+
<example name="single-line replace">
|
|
85
|
+
Change the timeout from `5000` to `30_000`:
|
|
43
86
|
```
|
|
44
87
|
{
|
|
45
|
-
path: "
|
|
88
|
+
path: "util.ts",
|
|
46
89
|
edits: [{
|
|
47
90
|
op: "replace",
|
|
48
|
-
pos: {{hlineref
|
|
49
|
-
lines: ["
|
|
91
|
+
pos: {{hlineref 2 "const timeout = 5000;"}},
|
|
92
|
+
lines: ["const timeout = 30_000;"]
|
|
50
93
|
}]
|
|
51
94
|
}
|
|
52
95
|
```
|
|
@@ -56,22 +99,22 @@ Edits can fail in two ways. Here is exactly what to do for each:
|
|
|
56
99
|
Single line — `lines: null` deletes entirely:
|
|
57
100
|
```
|
|
58
101
|
{
|
|
59
|
-
path: "
|
|
102
|
+
path: "util.ts",
|
|
60
103
|
edits: [{
|
|
61
104
|
op: "replace",
|
|
62
|
-
pos: {{hlineref
|
|
105
|
+
pos: {{hlineref 1 "// @ts-ignore"}},
|
|
63
106
|
lines: null
|
|
64
107
|
}]
|
|
65
108
|
}
|
|
66
109
|
```
|
|
67
|
-
Range —
|
|
110
|
+
Range — remove the legacy block (lines 10–11):
|
|
68
111
|
```
|
|
69
112
|
{
|
|
70
|
-
path: "
|
|
113
|
+
path: "util.ts",
|
|
71
114
|
edits: [{
|
|
72
115
|
op: "replace",
|
|
73
|
-
pos: {{hlineref
|
|
74
|
-
end: {{hlineref
|
|
116
|
+
pos: {{hlineref 10 "\t// TODO: remove after migration"}},
|
|
117
|
+
end: {{hlineref 11 "\tlegacy();"}},
|
|
75
118
|
lines: null
|
|
76
119
|
}]
|
|
77
120
|
}
|
|
@@ -79,15 +122,13 @@ Range — add `end`:
|
|
|
79
122
|
</example>
|
|
80
123
|
|
|
81
124
|
<example name="clear text but keep the line break">
|
|
82
|
-
|
|
83
|
-
{{hlinefull 14 " placeholder: \"DO NOT SHIP\","}}
|
|
84
|
-
```
|
|
125
|
+
Blank out a line without removing it:
|
|
85
126
|
```
|
|
86
127
|
{
|
|
87
|
-
path: "
|
|
128
|
+
path: "util.ts",
|
|
88
129
|
edits: [{
|
|
89
130
|
op: "replace",
|
|
90
|
-
pos: {{hlineref
|
|
131
|
+
pos: {{hlineref 3 "const tag = \"DO NOT SHIP\";"}},
|
|
91
132
|
lines: [""]
|
|
92
133
|
}]
|
|
93
134
|
}
|
|
@@ -95,23 +136,52 @@ Range — add `end`:
|
|
|
95
136
|
</example>
|
|
96
137
|
|
|
97
138
|
<example name="rewrite a block">
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
{
|
|
101
|
-
|
|
102
|
-
{
|
|
139
|
+
Replace the catch body with smarter error handling:
|
|
140
|
+
```
|
|
141
|
+
{
|
|
142
|
+
path: "util.ts",
|
|
143
|
+
edits: [{
|
|
144
|
+
op: "replace",
|
|
145
|
+
pos: {{hlineref 15 "\t\tconsole.error(err);"}},
|
|
146
|
+
end: {{hlineref 17 "\t}"}},
|
|
147
|
+
lines: [
|
|
148
|
+
"\t\tif (isEnoent(err)) return null;",
|
|
149
|
+
"\t\tthrow err;",
|
|
150
|
+
"\t}"
|
|
151
|
+
]
|
|
152
|
+
}]
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
</example>
|
|
156
|
+
|
|
157
|
+
<example name="own the block tail instead of patching around it">
|
|
158
|
+
When changing the tail of an existing block, replace the owned span instead of appending just before the closer.
|
|
159
|
+
|
|
160
|
+
Bad — appending a new return before the existing closer leaves the old tail in place and often leads to a second cleanup edit:
|
|
161
|
+
```
|
|
162
|
+
{
|
|
163
|
+
path: "util.ts",
|
|
164
|
+
edits: [{
|
|
165
|
+
op: "append",
|
|
166
|
+
pos: {{hlineref 16 "\t\treturn null;"}},
|
|
167
|
+
lines: [
|
|
168
|
+
"\t\treturn fallback;"
|
|
169
|
+
]
|
|
170
|
+
}]
|
|
171
|
+
}
|
|
103
172
|
```
|
|
173
|
+
Good — replace the block tail so the new logic and the closing boundary are owned by one edit:
|
|
104
174
|
```
|
|
105
175
|
{
|
|
106
|
-
path: "
|
|
176
|
+
path: "util.ts",
|
|
107
177
|
edits: [{
|
|
108
178
|
op: "replace",
|
|
109
|
-
pos: {{hlineref
|
|
110
|
-
end: {{hlineref
|
|
179
|
+
pos: {{hlineref 15 "\t\tconsole.error(err);"}},
|
|
180
|
+
end: {{hlineref 17 "\t}"}},
|
|
111
181
|
lines: [
|
|
112
|
-
"
|
|
113
|
-
"
|
|
114
|
-
"
|
|
182
|
+
"\t\tif (isEnoent(err)) return null;",
|
|
183
|
+
"\t\treturn fallback;",
|
|
184
|
+
"\t}"
|
|
115
185
|
]
|
|
116
186
|
}]
|
|
117
187
|
}
|
|
@@ -119,40 +189,35 @@ Range — add `end`:
|
|
|
119
189
|
</example>
|
|
120
190
|
|
|
121
191
|
<example name="inclusive end avoids duplicate boundary">
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
{{hlinefull 71 " run();"}}
|
|
126
|
-
{{hlinefull 72 "}"}}
|
|
127
|
-
{{hlinefull 73 "after();"}}
|
|
128
|
-
```
|
|
129
|
-
Bad — `end` stops before `}` while `lines` already includes `}`:
|
|
192
|
+
Simplify `beta()` to a one-liner. `end` must include the original closing `}` when the replacement also ends with `}`.
|
|
193
|
+
|
|
194
|
+
Bad — `end` stops at line 17 (`\t}`), so the replacement adds `}` and the original function closer on line 18 survives. Result: two consecutive `}` lines.
|
|
130
195
|
```
|
|
131
196
|
{
|
|
132
|
-
path: "
|
|
197
|
+
path: "util.ts",
|
|
133
198
|
edits: [{
|
|
134
199
|
op: "replace",
|
|
135
|
-
pos: {{hlineref
|
|
136
|
-
end: {{hlineref
|
|
200
|
+
pos: {{hlineref 9 "function beta() {"}},
|
|
201
|
+
end: {{hlineref 17 "\t}"}},
|
|
137
202
|
lines: [
|
|
138
|
-
"
|
|
139
|
-
"
|
|
203
|
+
"function beta() {",
|
|
204
|
+
"\treturn parse(data);",
|
|
140
205
|
"}"
|
|
141
206
|
]
|
|
142
207
|
}]
|
|
143
208
|
}
|
|
144
209
|
```
|
|
145
|
-
Good — include
|
|
210
|
+
Good — include the function's own `}` on line 18 in the range, so the old closing boundary is consumed:
|
|
146
211
|
```
|
|
147
212
|
{
|
|
148
|
-
path: "
|
|
213
|
+
path: "util.ts",
|
|
149
214
|
edits: [{
|
|
150
215
|
op: "replace",
|
|
151
|
-
pos: {{hlineref
|
|
152
|
-
end: {{hlineref
|
|
216
|
+
pos: {{hlineref 9 "function beta() {"}},
|
|
217
|
+
end: {{hlineref 18 "}"}},
|
|
153
218
|
lines: [
|
|
154
|
-
"
|
|
155
|
-
"
|
|
219
|
+
"function beta() {",
|
|
220
|
+
"\treturn parse(data);",
|
|
156
221
|
"}"
|
|
157
222
|
]
|
|
158
223
|
}]
|
|
@@ -161,66 +226,54 @@ Good — include original `}` in the replaced range when replacement keeps `}`:
|
|
|
161
226
|
</example>
|
|
162
227
|
|
|
163
228
|
<example name="insert between sibling declarations">
|
|
164
|
-
|
|
165
|
-
{{hlinefull 44 "function x() {"}}
|
|
166
|
-
{{hlinefull 45 " runX();"}}
|
|
167
|
-
{{hlinefull 46 "}"}}
|
|
168
|
-
{{hlinefull 47 ""}}
|
|
169
|
-
{{hlinefull 48 "function y() {"}}
|
|
170
|
-
{{hlinefull 49 " runY();"}}
|
|
171
|
-
{{hlinefull 50 "}"}}
|
|
172
|
-
```
|
|
229
|
+
Add a `gamma()` function between `alpha()` and `beta()`:
|
|
173
230
|
```
|
|
174
231
|
{
|
|
175
|
-
path: "
|
|
232
|
+
path: "util.ts",
|
|
176
233
|
edits: [{
|
|
177
234
|
op: "prepend",
|
|
178
|
-
pos: {{hlineref
|
|
235
|
+
pos: {{hlineref 9 "function beta() {"}},
|
|
179
236
|
lines: [
|
|
180
|
-
"function
|
|
181
|
-
"
|
|
237
|
+
"function gamma() {",
|
|
238
|
+
"\tvalidate();",
|
|
182
239
|
"}",
|
|
183
240
|
""
|
|
184
241
|
]
|
|
185
242
|
}]
|
|
186
243
|
}
|
|
187
244
|
```
|
|
188
|
-
Use a trailing `""` to preserve the blank line between
|
|
245
|
+
Use a trailing `""` to preserve the blank line between sibling declarations.
|
|
189
246
|
</example>
|
|
190
247
|
|
|
191
|
-
<example name="
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
{{hlinefull 102 ""}}
|
|
196
|
-
{{hlinefull 103 "export function serialize(data: unknown): string {"}}
|
|
197
|
-
```
|
|
198
|
-
Bad — anchoring on the blank line (ambiguous, may shift):
|
|
248
|
+
<example name="avoid closer anchors">
|
|
249
|
+
When inserting a sibling declaration, do not anchor on the previous block's lone closing brace. Anchor on the next declaration instead.
|
|
250
|
+
|
|
251
|
+
Bad — appending after line 7 (`}`) happens to land in the gap today, but the anchor is still the previous function's closer rather than a stable declaration boundary:
|
|
199
252
|
```
|
|
200
253
|
{
|
|
201
|
-
path: "
|
|
254
|
+
path: "util.ts",
|
|
202
255
|
edits: [{
|
|
203
256
|
op: "append",
|
|
204
|
-
pos: {{hlineref
|
|
257
|
+
pos: {{hlineref 7 "}"}},
|
|
205
258
|
lines: [
|
|
206
|
-
"
|
|
207
|
-
"
|
|
208
|
-
"
|
|
209
|
-
""
|
|
259
|
+
"",
|
|
260
|
+
"function gamma() {",
|
|
261
|
+
"\tvalidate();",
|
|
262
|
+
"}"
|
|
210
263
|
]
|
|
211
264
|
}]
|
|
212
265
|
}
|
|
213
266
|
```
|
|
214
|
-
Good —
|
|
267
|
+
Good — prepend before the next declaration so the new sibling is anchored on a declaration header, not a block tail:
|
|
215
268
|
```
|
|
216
269
|
{
|
|
217
|
-
path: "
|
|
270
|
+
path: "util.ts",
|
|
218
271
|
edits: [{
|
|
219
272
|
op: "prepend",
|
|
220
|
-
pos: {{hlineref
|
|
273
|
+
pos: {{hlineref 9 "function beta() {"}},
|
|
221
274
|
lines: [
|
|
222
|
-
"function
|
|
223
|
-
"
|
|
275
|
+
"function gamma() {",
|
|
276
|
+
"\tvalidate();",
|
|
224
277
|
"}",
|
|
225
278
|
""
|
|
226
279
|
]
|
|
@@ -228,6 +281,7 @@ Good — anchor on the unique declaration line:
|
|
|
228
281
|
}
|
|
229
282
|
```
|
|
230
283
|
</example>
|
|
284
|
+
</examples>
|
|
231
285
|
|
|
232
286
|
<critical>
|
|
233
287
|
- Edit payload: `{ path, edits[] }`. Each entry: `op`, `lines`, optional `pos`/`end`. No extra keys.
|
|
@@ -235,4 +289,6 @@ Good — anchor on the unique declaration line:
|
|
|
235
289
|
- You **MUST** re-read the file after each edit call before issuing another on the same file. Tags shift after every edit, so reusing old tags produces mismatches.
|
|
236
290
|
- You **MUST NOT** use this tool to reformat, reindent, or adjust whitespace — run the project's formatter instead. If the only difference is whitespace, it is formatting; leave it alone.
|
|
237
291
|
- `lines` entries **MUST** be literal file content with indentation copied exactly from the `read` output. If the file uses tabs, use `\t` in JSON (a real tab character). Using `\\t` (backslash + t) writes the literal two-character string `\t` into the file.
|
|
292
|
+
- For `append`/`prepend`, `lines` **MUST NOT** repeat surrounding delimiters or existing sibling code. Insert only the new content.
|
|
293
|
+
- Before any range `replace`, you **MUST** check whether the replacement's last line duplicates the original line immediately after `end` (most often a closing `}`, `]`, or `)`). If it does, extend the range to consume that old boundary instead of leaving two closers behind.
|
|
238
294
|
</critical>
|