@oh-my-pi/pi-coding-agent 14.1.0 → 14.1.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.
- package/CHANGELOG.md +79 -0
- package/package.json +8 -8
- package/src/async/job-manager.ts +43 -10
- package/src/commit/agentic/tools/analyze-file.ts +1 -2
- package/src/config/mcp-schema.json +1 -1
- package/src/config/model-equivalence.ts +1 -0
- package/src/config/model-registry.ts +63 -34
- package/src/config/model-resolver.ts +111 -15
- package/src/config/settings-schema.ts +4 -3
- package/src/config/settings.ts +1 -1
- package/src/cursor.ts +64 -23
- package/src/edit/index.ts +254 -89
- package/src/edit/modes/chunk.ts +336 -57
- package/src/edit/modes/hashline.ts +51 -26
- package/src/edit/modes/patch.ts +16 -10
- package/src/edit/modes/replace.ts +15 -7
- package/src/edit/renderer.ts +248 -94
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +6 -4
- package/src/extensibility/custom-tools/types.ts +0 -3
- package/src/extensibility/extensions/loader.ts +16 -0
- package/src/extensibility/extensions/runner.ts +2 -7
- package/src/extensibility/extensions/types.ts +8 -4
- package/src/internal-urls/docs-index.generated.ts +3 -3
- package/src/ipy/executor.ts +447 -52
- package/src/ipy/kernel.ts +39 -13
- package/src/lsp/client.ts +54 -0
- package/src/lsp/index.ts +8 -0
- package/src/lsp/types.ts +6 -0
- package/src/main.ts +0 -1
- package/src/modes/acp/acp-agent.ts +4 -1
- package/src/modes/components/bash-execution.ts +16 -4
- package/src/modes/components/status-line/presets.ts +17 -6
- package/src/modes/components/status-line/segments.ts +15 -0
- package/src/modes/components/status-line-segment-editor.ts +1 -0
- package/src/modes/components/status-line.ts +7 -1
- package/src/modes/components/tool-execution.ts +145 -75
- package/src/modes/controllers/command-controller.ts +24 -1
- package/src/modes/controllers/event-controller.ts +4 -1
- package/src/modes/controllers/extension-ui-controller.ts +28 -5
- package/src/modes/controllers/input-controller.ts +9 -3
- package/src/modes/controllers/selector-controller.ts +4 -1
- package/src/modes/interactive-mode.ts +19 -3
- package/src/modes/print-mode.ts +13 -4
- package/src/modes/prompt-action-autocomplete.ts +3 -5
- package/src/modes/rpc/rpc-mode.ts +8 -2
- package/src/modes/shared.ts +2 -2
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +1 -0
- package/src/prompts/tools/bash.md +2 -2
- package/src/prompts/tools/chunk-edit.md +191 -163
- package/src/prompts/tools/hashline.md +11 -11
- package/src/prompts/tools/patch.md +10 -5
- package/src/prompts/tools/{await.md → poll.md} +1 -1
- package/src/prompts/tools/read-chunk.md +3 -3
- package/src/prompts/tools/task.md +2 -2
- package/src/prompts/tools/vim.md +98 -0
- package/src/sdk.ts +754 -724
- package/src/session/agent-session.ts +164 -34
- package/src/session/session-manager.ts +50 -4
- package/src/slash-commands/builtin-registry.ts +17 -0
- package/src/task/executor.ts +4 -4
- package/src/task/index.ts +3 -5
- package/src/task/types.ts +2 -2
- package/src/tools/bash.ts +26 -8
- package/src/tools/find.ts +5 -2
- package/src/tools/grep.ts +77 -8
- package/src/tools/index.ts +48 -19
- package/src/tools/{await-tool.ts → poll-tool.ts} +36 -30
- package/src/tools/python.ts +293 -278
- package/src/tools/submit-result.ts +5 -2
- package/src/tools/todo-write.ts +8 -2
- package/src/tools/vim.ts +966 -0
- package/src/utils/edit-mode.ts +2 -1
- package/src/utils/session-color.ts +55 -0
- package/src/utils/title-generator.ts +15 -6
- package/src/vim/buffer.ts +309 -0
- package/src/vim/commands.ts +382 -0
- package/src/vim/engine.ts +2426 -0
- package/src/vim/parser.ts +151 -0
- package/src/vim/render.ts +252 -0
- package/src/vim/types.ts +197 -0
|
@@ -24,10 +24,10 @@ Executes bash command in shell session for terminal operations like git, bun, ca
|
|
|
24
24
|
{{/if}}
|
|
25
25
|
{{#if asyncEnabled}}
|
|
26
26
|
- Use `read jobs://` to inspect all background jobs and `read jobs://<job-id>` for detailed status/output when needed.
|
|
27
|
-
- When you need to wait for async results before continuing, call `
|
|
27
|
+
- When you need to wait for async results before continuing, call `poll` — it blocks until jobs complete. Do NOT poll `read jobs://` in a loop or yield and hope for delivery.
|
|
28
28
|
{{else}}
|
|
29
29
|
{{#if autoBackgroundEnabled}}
|
|
30
|
-
- If a command auto-backgrounds, use `read jobs://` to inspect jobs and `
|
|
30
|
+
- If a command auto-backgrounds, use `read jobs://` to inspect jobs and `poll` when you need to wait for completion instead of polling in a loop.
|
|
31
31
|
{{/if}}
|
|
32
32
|
{{/if}}
|
|
33
33
|
</instruction>
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
Edits files via syntax-aware chunks. Run `read(path="file.ts")` first.
|
|
1
|
+
Edits files via syntax-aware chunks. Run `read(path="file.ts")` first.
|
|
2
|
+
- `write` rewrites the entire targeted region — best for most edits.
|
|
3
|
+
- `replace` does surgical find-and-replace within a chunk — use when making small changes to a large chunk, or batching multiple substitutions.
|
|
4
|
+
- `insert` adds content before/after a chunk.
|
|
5
|
+
|
|
6
|
+
Call format: `{"edits": [{"path": "file:chunk#ID~", "write": "new body"}, …]}`
|
|
2
7
|
|
|
3
8
|
<rules>
|
|
4
|
-
- **MUST** `read` first. Never invent chunk paths or
|
|
5
|
-
- `
|
|
6
|
-
- insertions: `chunk`, `chunk~`, or `chunk^`
|
|
7
|
-
- replacements: `chunk#CRC`, `chunk#CRC~`, or `chunk#CRC^`
|
|
8
|
-
- Without a suffix it defaults to the entire chunk including leading trivia. `~` targets the body, `^` targets the head.
|
|
9
|
+
- **MUST** `read` first. Never invent chunk paths or IDs. Copy them from the latest `read` output or edit response.
|
|
10
|
+
- `path` format: `file:selector` — e.g. `src/app.ts:fn_foo#ABCD~`. Append `~` for body, `^` for head, or nothing for the whole chunk. Include `#ID` for `put`/`find`+`replace`/`delete`.
|
|
9
11
|
- If the exact chunk path is unclear, run `read(path="file", sel="?")` and copy a selector from that listing.
|
|
10
12
|
{{#if chunkAutoIndent}}
|
|
11
13
|
- Use `\t` for indentation in `content`. Write content at indent-level 0 — the tool re-indents it to match the chunk's position in the file. For example, to replace `~` of a method, write the body starting at column 0:
|
|
@@ -13,6 +15,10 @@ Edits files via syntax-aware chunks. Run `read(path="file.ts")` first. The edit
|
|
|
13
15
|
content: "if (x) {\n\treturn true;\n}"
|
|
14
16
|
```
|
|
15
17
|
The tool adds the correct base indent automatically. Never manually pad with the chunk's own indentation.
|
|
18
|
+
Multiple sibling body lines at the same level all start at column 0: `"print(a)\nprint(b)\nprint(c)\n"`. Only use `\t` when nesting deeper (e.g. `"if cond:\n\tinner\nouter\n"`).
|
|
19
|
+
**Common mistake** when replacing `~` of a function body: do NOT include the function's own indentation.
|
|
20
|
+
Wrong: `"if b == 0:\n\t\treturn None\n\treturn a / b\n"` — adds the function's base `\t` to every line.
|
|
21
|
+
Correct: `"if b == 0:\n\treturn None\nreturn a / b\n"` — `if` and `return a / b` at column 0, only `return None` gets `\t` for nesting.
|
|
16
22
|
{{else}}
|
|
17
23
|
- Match the file's literal tabs/spaces in `content`. Do not convert indentation to canonical `\t`.
|
|
18
24
|
- Write content at indent-level 0 relative to the target region. For example, to replace `~` of a method, write:
|
|
@@ -22,217 +28,239 @@ Edits files via syntax-aware chunks. Run `read(path="file.ts")` first. The edit
|
|
|
22
28
|
The tool adds the correct base indent automatically, then preserves the tabs/spaces you used inside the snippet. Never manually pad with the chunk's own indentation.
|
|
23
29
|
{{/if}}
|
|
24
30
|
- Region suffixes only apply to container chunks (classes, functions, impl blocks, sections). On leaf chunks (enum variants, fields, single statements, and compound statements like `if`/`for`/`while`/`match`/`try`), `~` and `^` silently fall back to whole-chunk replacement — prefer the unsuffixed form and always supply the complete replacement (condition + body, not just the body) to avoid dropping structural parts.
|
|
25
|
-
- `replace`
|
|
26
|
-
- **
|
|
31
|
+
- `put`, `find`+`replace`, and `delete` require the current ID. `prepend`/`append` do not.
|
|
32
|
+
- **IDs change after every edit.** The edit response always carries the new IDs — use those for the next call or run `read(path="file", sel="?")` to refresh. Never reuse an ID from before the latest edit.
|
|
27
33
|
</rules>
|
|
28
34
|
|
|
29
35
|
<critical>
|
|
30
|
-
You **MUST** use the narrowest region that covers your change.
|
|
36
|
+
You **MUST** use the narrowest region that covers your change. Putting without a region overwrites the **entire chunk including leading comments, decorators, and attributes** — omitting them from `content` deletes them.
|
|
31
37
|
|
|
32
|
-
**`
|
|
38
|
+
**`put` is total, not surgical.** The `content` you supply becomes the *complete* new content for the targeted region. Everything in the original region that you omit from `content` is deleted. Before using `put` on any chunk's `~`, verify the chunk does not contain children you intend to keep. If a chunk spans hundreds of lines and your change touches only a few, target a specific child chunk — not the parent.
|
|
33
39
|
|
|
34
|
-
**Group chunks (`stmts_*`, `imports_*`, `decls_*`) are containers.** They hold many sibling items (test functions, import statements, declarations).
|
|
40
|
+
**Group chunks (`stmts_*`, `imports_*`, `decls_*`) are containers.** They hold many sibling items (test functions, import statements, declarations). `put` on a group chunk's `~` overwrites **all** of its children. To edit one item inside a group, target that item's own chunk path. If no child chunk exists, use the specific child's chunk selector from `read` output — do not `put` the parent group.
|
|
35
41
|
</critical>
|
|
36
42
|
|
|
37
43
|
<regions>
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
#
|
|
42
|
-
fn foo(x: i32) { <-- signature + opening delimiter
|
|
43
|
-
body(); <-- body
|
|
44
|
-
} <-- closing delimiter
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
Append `~` to target the body, `^` to target the head (trivia + signature), or nothing for the whole chunk:
|
|
48
|
-
- `fn_foo#CRC~` — body only. **Use for most edits.** On leaf chunks, falls back to whole chunk.
|
|
49
|
-
- `fn_foo#CRC^` — head (decorators, attributes, doc comments, signature, opening delimiter).
|
|
50
|
-
- `fn_foo#CRC` — entire chunk including leading trivia.
|
|
44
|
+
In `read` output, lines marked `^` between the line number and `|` are **head** lines (doc comments, attributes/decorators, signature). Lines without `^` are **body** lines. Use this to decide which region to target:
|
|
45
|
+
- `fn_foo#ID~` — **body only (the default choice for most edits).** Head lines (`^`) are preserved automatically — doc comments, attributes, and signature stay untouched. On leaf chunks, falls back to whole chunk.
|
|
46
|
+
- `fn_foo#ID^` — head only (decorators, attributes, doc comments, signature, opening delimiter). Body stays untouched.
|
|
47
|
+
- `fn_foo#ID` — entire chunk including leading trivia. **You must include doc comments and attributes in `content`; omitting them deletes them.**
|
|
51
48
|
- `chunk~` + `append`/`prepend` inserts *inside* the container. `chunk` + `append`/`prepend` inserts *outside*.
|
|
52
49
|
|
|
53
|
-
**Note on leading trivia:** whether a decorator/doc comment belongs to `^` depends on the parser. In Rust and Python, attributes and decorators are attached to the function chunk, so `^` covers them. In TypeScript/JavaScript, a `@decorator` + `/** jsdoc */` block immediately above a method often surfaces as a **separate sibling chunk** (shown as `chunk#
|
|
50
|
+
**Note on leading trivia:** whether a decorator/doc comment belongs to `^` depends on the parser. In Rust and Python, attributes and decorators are attached to the function chunk, so `^` covers them. In TypeScript/JavaScript, a `@decorator` + `/** jsdoc */` block immediately above a method often surfaces as a **separate sibling chunk** (shown as `chunk#ID` in the `?` listing) rather than as part of the function's `^`. If you need to rewrite a decorator, check the `?` listing for a sibling `chunk#ID` directly above your target.
|
|
54
51
|
|
|
55
52
|
**Note on non-code formats:** for prose and data formats (markdown, YAML, JSON, fenced code blocks, frontmatter), `^` and `~` fall back to the whole chunk. Always replace the entire chunk and include any delimiter syntax (fence backticks, `---` frontmatter markers, list markers) in your `content` — omitting them deletes them. For markdown sections (`sect_*`), always use unsuffixed whole-chunk replace — `^` and `~` on section containers also fall back to whole-chunk replace. When editing fenced code blocks in markdown, use the exact whitespace from the file (read with `raw` first) — the tool preserves literal indentation inside fenced blocks, but any content you supply is written verbatim. To insert content after a markdown section heading, use `after` on the heading chunk (`sect_*.chunk` or `sect_*.chunk_1`) — not `before`/`prepend` on the section itself, which lands physically before the heading and gets absorbed by the preceding section on reparse.
|
|
56
53
|
</regions>
|
|
57
54
|
|
|
58
55
|
<ops>
|
|
59
|
-
|
|
56
|
+
Each edit entry has `path` (`file:selector`) plus **exactly one** operation field — `write`, `replace`, or `insert`. Never set more than one on the same entry.
|
|
57
|
+
|
|
58
|
+
|fields|path (selector part)|effect|
|
|
60
59
|
|---|---|---|
|
|
61
|
-
|`
|
|
62
|
-
|`
|
|
63
|
-
|`
|
|
64
|
-
|`
|
|
65
|
-
|`append`|`chunk`, `chunk~`, or `chunk^`|insert at the end inside the region|
|
|
60
|
+
|`write: "content"`|`file:chunk#ID`, `file:chunk#ID~`, or `file:chunk#ID^`|write complete new content to the region|
|
|
61
|
+
|`write: null`|`file:chunk#ID`|delete the chunk|
|
|
62
|
+
|`replace: {old, new}`|`file:chunk#ID`|find a literal substring in the chunk and replace it|
|
|
63
|
+
|`insert: {loc, body}`|`file:chunk` or `file:chunk~`|insert before/after the chunk (`loc`: `"prepend"` or `"append"`)|
|
|
66
64
|
</ops>
|
|
67
65
|
|
|
68
66
|
<examples>
|
|
69
|
-
Given this `read` output for `
|
|
70
|
-
```
|
|
71
|
-
| example.ts·34L·ts·#QBMH
|
|
72
|
-
|
|
|
73
|
-
| [<interface_Config#BWTR>]
|
|
74
|
-
1| interface Config {
|
|
75
|
-
| [<interface_Config.field_host#TTMN>]
|
|
76
|
-
2| host: string;
|
|
77
|
-
| [<interface_Config.field_port#QSMH>]
|
|
78
|
-
3| port: number;
|
|
79
|
-
| [<interface_Config.field_debug#JPRR>]
|
|
80
|
-
4| debug: boolean;
|
|
81
|
-
5| }
|
|
82
|
-
|
|
|
83
|
-
| [<class_Counter#HZHY>]
|
|
84
|
-
7| class Counter {
|
|
85
|
-
| [<class_Counter.field_value#QJBY>]
|
|
86
|
-
8| value: number = 0;
|
|
87
|
-
9|
|
|
88
|
-
| [<class_Counter.fn_increment#NQWY>]
|
|
89
|
-
10| increment(): void {
|
|
90
|
-
11| this.value += 1;
|
|
91
|
-
12| }
|
|
92
|
-
13|
|
|
93
|
-
| [<class_Counter.fn_decrement#PMBP>]
|
|
94
|
-
14| decrement(): void {
|
|
95
|
-
15| this.value -= 1;
|
|
96
|
-
16| }
|
|
97
|
-
17|
|
|
98
|
-
| [<class_Counter.fn_toString#ZQZP>]
|
|
99
|
-
18| toString(): string {
|
|
100
|
-
19| return `Counter(${this.value})`;
|
|
101
|
-
20| }
|
|
102
|
-
21| }
|
|
103
|
-
|
|
|
104
|
-
| [<enum_Status#HYQJ>]
|
|
105
|
-
23| enum Status {
|
|
106
|
-
| [<enum_Status.variant_Active#PQNS>]
|
|
107
|
-
24| Active = "ACTIVE",
|
|
108
|
-
| [<enum_Status.variant_Paused#HHNM>]
|
|
109
|
-
25| Paused = "PAUSED",
|
|
110
|
-
| [<enum_Status.variant_Stopped#NHTY>]
|
|
111
|
-
26| Stopped = "STOPPED",
|
|
112
|
-
27| }
|
|
113
|
-
|
|
|
114
|
-
| [<fn_createCounter#PQQY>]
|
|
115
|
-
29| function createCounter(initial: number): Counter {
|
|
116
|
-
30| const counter = new Counter();
|
|
117
|
-
31| counter.value = initial;
|
|
118
|
-
32| return counter;
|
|
119
|
-
33| }
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
**Replace a whole chunk** (rename a function):
|
|
123
|
-
~~~json
|
|
124
|
-
{{#if chunkAutoIndent}}
|
|
125
|
-
{ "sel": "fn_createCounter#PQQY", "op": "replace", "content": "function makeCounter(start: number): Counter {\n\tconst c = new Counter();\n\tc.value = start;\n\treturn c;\n}\n" }
|
|
126
|
-
{{else}}
|
|
127
|
-
{ "sel": "fn_createCounter#PQQY", "op": "replace", "content": "function makeCounter(start: number): Counter {\n const c = new Counter();\n c.value = start;\n return c;\n}\n" }
|
|
128
|
-
{{/if}}
|
|
129
|
-
~~~
|
|
130
|
-
Result — the entire chunk is rewritten:
|
|
67
|
+
Given this `read` output for `counter.rs`:
|
|
131
68
|
```
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
69
|
+
| counter.rs·62L·rust·#ZRPW
|
|
70
|
+
|
|
|
71
|
+
@imp#MNHH
|
|
72
|
+
1 |use std::fmt;
|
|
73
|
+
|
|
|
74
|
+
@struct_Counte#QTSX
|
|
75
|
+
3^|/// A simple counter that tracks a value and its history.
|
|
76
|
+
4^|#[derive(Debug, Clone)]
|
|
77
|
+
5^|pub struct Counter {
|
|
78
|
+
-@struct_Counte.field_value#MQTW
|
|
79
|
+
6 | /// The current value.
|
|
80
|
+
7 | value: i32,
|
|
81
|
+
-@struct_Counte.field_max#HJMQ
|
|
82
|
+
8 | /// Maximum allowed value.
|
|
83
|
+
9 | max: i32,
|
|
84
|
+
10 |}
|
|
85
|
+
|
|
|
86
|
+
@impl_Counte#VNPP
|
|
87
|
+
12^|impl Counter {
|
|
88
|
+
-@impl_Counte.fn_new#RWZV
|
|
89
|
+
13^| /// Creates a new counter starting at zero.
|
|
90
|
+
14^| pub fn new(max: i32) -> Self {
|
|
91
|
+
15 | Self { value: 0, max }
|
|
92
|
+
16 | }
|
|
93
|
+
17 |
|
|
94
|
+
-@impl_Counte.fn_increm#MNHV
|
|
95
|
+
18^| /// Increments the counter by one, clamping at max.
|
|
96
|
+
19^| pub fn increment(&mut self) {
|
|
97
|
+
20 | if self.value < self.max {
|
|
98
|
+
21 | self.value += 1;
|
|
99
|
+
22 | }
|
|
100
|
+
23 | }
|
|
101
|
+
24 |
|
|
102
|
+
-@impl_Counte.fn_decrem#TTWB
|
|
103
|
+
25^| /// Decrements the counter by one, clamping at zero.
|
|
104
|
+
26^| pub fn decrement(&mut self) {
|
|
105
|
+
27 | if self.value > 0 {
|
|
106
|
+
28 | self.value -= 1;
|
|
107
|
+
29 | }
|
|
108
|
+
30 | }
|
|
109
|
+
31 |
|
|
110
|
+
-@impl_Counte.fn_get#PTNT
|
|
111
|
+
32^| /// Returns the current value.
|
|
112
|
+
33^| pub fn get(&self) -> i32 {
|
|
113
|
+
34 | self.value
|
|
114
|
+
35 | }
|
|
115
|
+
36 |}
|
|
116
|
+
|
|
|
117
|
+
@impl_Displa#BNJH
|
|
118
|
+
38^|impl fmt::Display for Counter {
|
|
119
|
+
-@impl_Displa.fn_fmt#NKRN
|
|
120
|
+
39^| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
121
|
+
40 | write!(f, "Counter({}/{})", self.value, self.max)
|
|
122
|
+
41 | }
|
|
123
|
+
42 |}
|
|
124
|
+
|
|
|
125
|
+
@mod_tests#YWXM
|
|
126
|
+
44^|#[cfg(test)]
|
|
127
|
+
45^|mod tests {
|
|
128
|
+
-@mod_tests.chunk#VSMY
|
|
129
|
+
46 | use super::*;
|
|
130
|
+
47 |
|
|
131
|
+
-@mod_tests.fn_test_i#YXQZ
|
|
132
|
+
48^| #[test]
|
|
133
|
+
49^| fn test_increment() {
|
|
134
|
+
50 | let mut c = Counter::new(10);
|
|
135
|
+
51 | c.increment();
|
|
136
|
+
52 | assert_eq!(c.get(), 1);
|
|
137
|
+
53 | }
|
|
138
|
+
54 |
|
|
139
|
+
-@mod_tests.fn_test_d#XPBQ
|
|
140
|
+
55^| #[test]
|
|
141
|
+
56^| fn test_decrement_at_zero() {
|
|
142
|
+
57 | let mut c = Counter::new(10);
|
|
143
|
+
58 | c.decrement();
|
|
144
|
+
59 | assert_eq!(c.get(), 0);
|
|
145
|
+
60 | }
|
|
146
|
+
61 |}
|
|
137
147
|
```
|
|
138
148
|
|
|
139
|
-
**
|
|
149
|
+
**Understanding `^` markers in `read` output:** Lines marked with `^` between the line number and `|` (e.g. ` 3^|`) are **head** lines — doc comments, attributes, and the signature. Lines without `^` (e.g. ` 7 |`) are **body** lines. `~` replaces body lines only, keeping head lines intact.
|
|
150
|
+
|
|
151
|
+
**Put body** (`~` — the common case):
|
|
140
152
|
```
|
|
141
|
-
{ "
|
|
153
|
+
{ "path": "counter.rs:impl_Counte.fn_increm#MNHV~", "write": "self.value = (self.value + 1).min(self.max);\n" }
|
|
142
154
|
```
|
|
143
|
-
Result — only the body changes, signature and
|
|
155
|
+
Result — only the body (non-`^` lines) changes; the doc comment, signature, and closing `}` are all preserved:
|
|
144
156
|
```
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
157
|
+
/// Increments the counter by one, clamping at max.
|
|
158
|
+
pub fn increment(&mut self) {
|
|
159
|
+
self.value = (self.value + 1).min(self.max);
|
|
160
|
+
}
|
|
149
161
|
```
|
|
150
162
|
|
|
151
|
-
**
|
|
163
|
+
**Write whole chunk** (rewrite signature + doc comment + body):
|
|
152
164
|
```
|
|
153
|
-
{ "
|
|
165
|
+
{ "path": "counter.rs:impl_Counte.fn_increm#MNHV", "write": "/// Increments by the given step, clamping at max.\npub fn increment(&mut self, step: i32) {\n\tself.value = (self.value + step).min(self.max);\n}\n" }
|
|
154
166
|
```
|
|
155
|
-
Result —
|
|
167
|
+
Result — **everything** including the doc comment and signature is rewritten. You must include them; omitting them deletes them:
|
|
156
168
|
```
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
return counter;
|
|
162
|
-
}
|
|
169
|
+
/// Increments by the given step, clamping at max.
|
|
170
|
+
pub fn increment(&mut self, step: i32) {
|
|
171
|
+
self.value = (self.value + step).min(self.max);
|
|
172
|
+
}
|
|
163
173
|
```
|
|
164
174
|
|
|
165
|
-
**
|
|
175
|
+
**Write head** (`^` — attributes, doc comments, signature):
|
|
176
|
+
```
|
|
177
|
+
{ "path": "counter.rs:impl_Counte.fn_get#PTNT^", "write": "/// Returns the current counter value.\n#[inline]\npub fn get(&self) -> i32 {\n" }
|
|
166
178
|
```
|
|
167
|
-
|
|
179
|
+
Result — the head (all `^` lines + opening brace) changes, body untouched:
|
|
168
180
|
```
|
|
169
|
-
|
|
181
|
+
/// Returns the current counter value.
|
|
182
|
+
#[inline]
|
|
183
|
+
pub fn get(&self) -> i32 {
|
|
184
|
+
self.value
|
|
185
|
+
}
|
|
170
186
|
```
|
|
171
|
-
/** Factory function below. */
|
|
172
187
|
|
|
173
|
-
|
|
188
|
+
**Find and replace** (surgical edit within a chunk):
|
|
174
189
|
```
|
|
190
|
+
{ "path": "counter.rs:impl_Counte.fn_increm#MNHV", "replace": { "old": "self.value += 1;", "new": "self.value = (self.value + 1).min(self.max);" } }
|
|
191
|
+
```
|
|
192
|
+
Result — only the matched substring changes, everything else is preserved.
|
|
175
193
|
|
|
176
|
-
**Insert
|
|
177
|
-
|
|
178
|
-
{{
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
194
|
+
**Insert before a chunk** (`prepend`):
|
|
195
|
+
```
|
|
196
|
+
{ "path": "counter.rs:impl_Counte.fn_get", "insert": { "loc": "prepend", "body": "/// Resets the counter to zero.\npub fn reset(&mut self) {\n\tself.value = 0;\n}\n\n" } }
|
|
197
|
+
```
|
|
198
|
+
Result — a new method is inserted before `fn get`:
|
|
199
|
+
```
|
|
200
|
+
/// Resets the counter to zero.
|
|
201
|
+
pub fn reset(&mut self) {
|
|
202
|
+
self.value = 0;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/// Returns the current value.
|
|
206
|
+
pub fn get(&self) -> i32 {
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**Insert after a chunk** (`append`):
|
|
210
|
+
```
|
|
211
|
+
{ "path": "counter.rs:struct_Counte", "insert": { "loc": "append", "body": "\nimpl Default for Counter {\n\tfn default() -> Self {\n\t\tSelf { value: 0, max: 100 }\n\t}\n}\n" } }
|
|
212
|
+
```
|
|
213
|
+
Result — a new impl block appears after the struct:
|
|
185
214
|
```
|
|
186
|
-
enum Status {
|
|
187
|
-
Active = "ACTIVE",
|
|
188
|
-
Paused = "PAUSED",
|
|
189
|
-
Stopped = "STOPPED",
|
|
190
215
|
}
|
|
191
216
|
|
|
192
|
-
|
|
193
|
-
|
|
217
|
+
impl Default for Counter {
|
|
218
|
+
fn default() -> Self {
|
|
219
|
+
Self { value: 0, max: 100 }
|
|
220
|
+
}
|
|
194
221
|
}
|
|
195
222
|
|
|
196
|
-
|
|
223
|
+
impl Counter {
|
|
197
224
|
```
|
|
198
225
|
|
|
199
|
-
**
|
|
226
|
+
**Insert at start of container body** (`~` + `prepend`):
|
|
200
227
|
```
|
|
201
|
-
{ "
|
|
228
|
+
{ "path": "counter.rs:impl_Counte~", "insert": { "loc": "prepend", "body": "/// Creates a counter starting at the given value.\npub fn with_value(value: i32, max: i32) -> Self {\n\tSelf { value: value.min(max), max }\n}\n\n" } }
|
|
202
229
|
```
|
|
203
|
-
Result — a new
|
|
230
|
+
Result — a new method is added at the top of the impl body, before existing methods:
|
|
204
231
|
```
|
|
205
|
-
|
|
206
|
-
|
|
232
|
+
impl Counter {
|
|
233
|
+
/// Creates a counter starting at the given value.
|
|
234
|
+
pub fn with_value(value: i32, max: i32) -> Self {
|
|
235
|
+
Self { value: value.min(max), max }
|
|
236
|
+
}
|
|
207
237
|
|
|
208
|
-
|
|
238
|
+
/// Creates a new counter starting at zero.
|
|
239
|
+
pub fn new(max: i32) -> Self {
|
|
209
240
|
```
|
|
210
241
|
|
|
211
|
-
**
|
|
212
|
-
|
|
213
|
-
{{
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
{ "sel": "class_Counter~", "op": "append", "content": "\nreset(): void {\n this.value = 0;\n}\n" }
|
|
217
|
-
{{/if}}
|
|
218
|
-
~~~
|
|
219
|
-
Result — a new method is added at the end of the class body, before the closing `}`:
|
|
242
|
+
**Insert at end of container body** (`~` + `append`):
|
|
243
|
+
```
|
|
244
|
+
{ "path": "counter.rs:impl_Counte~", "insert": { "loc": "append", "body": "\n/// Returns true if the counter is at its maximum.\npub fn is_maxed(&self) -> bool {\n\tself.value >= self.max\n}\n" } }
|
|
245
|
+
```
|
|
246
|
+
Result — a new method is added at the end of the impl body, before the closing `}`:
|
|
220
247
|
```
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
248
|
+
pub fn get(&self) -> i32 {
|
|
249
|
+
self.value
|
|
250
|
+
}
|
|
224
251
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
252
|
+
/// Returns true if the counter is at its maximum.
|
|
253
|
+
pub fn is_maxed(&self) -> bool {
|
|
254
|
+
self.value >= self.max
|
|
255
|
+
}
|
|
228
256
|
}
|
|
229
257
|
```
|
|
230
258
|
|
|
231
|
-
**Delete a chunk
|
|
259
|
+
**Delete a chunk**:
|
|
232
260
|
```
|
|
233
|
-
{ "
|
|
261
|
+
{ "path": "counter.rs:impl_Counte.fn_decrem#TTWB", "write": null }
|
|
234
262
|
```
|
|
235
|
-
Result — the method
|
|
263
|
+
Result — the method (including its doc comment and signature) is removed.
|
|
236
264
|
- Indentation rules (important):
|
|
237
265
|
{{#if chunkAutoIndent}}
|
|
238
266
|
- Use `\t` for each indent level. The tool converts tabs to the file's actual style (2-space, 4-space, etc.).
|
|
@@ -240,12 +268,12 @@ Result — the method is removed from the class.
|
|
|
240
268
|
- Match the file's real indentation characters in your snippet. The tool preserves your literal tabs/spaces after adding the target region's base indent.
|
|
241
269
|
{{/if}}
|
|
242
270
|
- Do NOT include the chunk's base indentation — only indent relative to the region's opening level.
|
|
243
|
-
- For `~` of a function: write at column 0, and use `\t` for *relative* nesting. Flat body: `"return x;\n"`. Nested body: `"if (cond) {\n\treturn x;\n}\n"` — the `if` is at column 0, the `return` is one tab in,
|
|
244
|
-
- For `^`: write at the chunk's own depth. A class member's head uses `"
|
|
271
|
+
- For `~` of a function: write at column 0, and use `\t` for *relative* nesting. Flat body: `"return x;\n"`. Multiple sibling lines: `"print(a)\nprint(b)\nprint(c)\n"` — all at column 0, the tool adds the function's base indent. Nested body: `"if (cond) {\n\treturn x;\n}\n"` — the `if` is at column 0, the `return` is one tab in. Python example — to replace `~` of `def divide(a, b):`, write: `"if b == 0:\n\treturn None\nreturn a / b\n"` — the `if` and `return a / b` are at column 0, `return None` is one `\t` in.
|
|
272
|
+
- For `^`: write at the chunk's own depth. A class member's head uses `"/// doc\n#[attr]\npub fn start() {"`.
|
|
245
273
|
{{#if chunkAutoIndent}}
|
|
246
|
-
- For a top-level item: start at zero indent. Write `"
|
|
274
|
+
- For a top-level item: start at zero indent. Write `"fn foo() {\n\treturn 1;\n}\n"`.
|
|
247
275
|
{{else}}
|
|
248
|
-
- For a top-level item: start at zero indent. Write `"
|
|
276
|
+
- For a top-level item: start at zero indent. Write `"fn foo() {\n return 1;\n}\n"`.
|
|
249
277
|
{{/if}}
|
|
250
278
|
- The tool strips common leading indentation from your content as a safety net, so accidental over-indentation is corrected.
|
|
251
279
|
</examples>
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
Applies precise file edits using `LINE#ID` anchors from `read` output.
|
|
2
2
|
|
|
3
|
-
Read the file first. Copy anchors exactly from the latest `read` output.
|
|
3
|
+
Read the file first. Copy anchors exactly from the latest `read` output. After any successful edit, re-read before editing that file again.
|
|
4
4
|
|
|
5
5
|
<operations>
|
|
6
6
|
**Top level**
|
|
7
|
-
- `
|
|
8
|
-
- `move` — optional rename target
|
|
9
|
-
- `delete` — optional whole-file delete
|
|
10
|
-
- `edits` — array of `{ loc, content }` entries
|
|
7
|
+
- `edits` — array of edit entries
|
|
11
8
|
|
|
12
|
-
**Edit entry**: `{ loc, content }`
|
|
9
|
+
**Edit entry**: `{ path, loc, content }` or `{ path, delete: true }` or `{ path, move: "new/path" }`
|
|
10
|
+
- `path` — file path
|
|
13
11
|
- `loc` — where to apply the edit (see below)
|
|
14
12
|
- `content` — replacement/inserted lines (array of strings preferred, `null` to delete)
|
|
13
|
+
- `delete` — delete the file
|
|
14
|
+
- `move` — move/rename the file
|
|
15
15
|
|
|
16
16
|
**`loc` values**
|
|
17
17
|
- `"append"` / `"prepend"` — insert at end/start of file
|
|
@@ -46,8 +46,8 @@ All examples below reference the same file:
|
|
|
46
46
|
Replace only the catch body. Do not target the shared boundary line `} catch (err) {`.
|
|
47
47
|
```
|
|
48
48
|
{
|
|
49
|
-
path: "a.ts",
|
|
50
49
|
edits: [{
|
|
50
|
+
path: "a.ts",
|
|
51
51
|
loc: { range: { pos: {{href 15 "\t\tconsole.error(err);"}}, end: {{href 16 "\t\treturn null;"}} } },
|
|
52
52
|
content: [
|
|
53
53
|
"\t\tif (isEnoent(err)) return null;",
|
|
@@ -62,8 +62,8 @@ Replace only the catch body. Do not target the shared boundary line `} catch (er
|
|
|
62
62
|
Replace the entire body of `alpha`, including its closing `}`. `end` **MUST** be {{href 7 "}"}} because `content` includes `}`.
|
|
63
63
|
```
|
|
64
64
|
{
|
|
65
|
-
path: "a.ts",
|
|
66
65
|
edits: [{
|
|
66
|
+
path: "a.ts",
|
|
67
67
|
loc: { range: { pos: {{href 6 "\tlog();"}}, end: {{href 7 "}"}} } },
|
|
68
68
|
content: [
|
|
69
69
|
"\tvalidate();",
|
|
@@ -79,8 +79,8 @@ Replace the entire body of `alpha`, including its closing `}`. `end` **MUST** be
|
|
|
79
79
|
<example name="replace one line">
|
|
80
80
|
```
|
|
81
81
|
{
|
|
82
|
-
path: "a.ts",
|
|
83
82
|
edits: [{
|
|
83
|
+
path: "a.ts",
|
|
84
84
|
loc: { range: { pos: {{href 2 "const timeout = 5000;"}}, end: {{href 2 "const timeout = 5000;"}} } },
|
|
85
85
|
content: ["const timeout = 30_000;"]
|
|
86
86
|
}]
|
|
@@ -91,8 +91,8 @@ Replace the entire body of `alpha`, including its closing `}`. `end` **MUST** be
|
|
|
91
91
|
<example name="delete a range">
|
|
92
92
|
```
|
|
93
93
|
{
|
|
94
|
-
path: "a.ts",
|
|
95
94
|
edits: [{
|
|
95
|
+
path: "a.ts",
|
|
96
96
|
loc: { range: { pos: {{href 10 "\t// TODO: remove after migration"}}, end: {{href 11 "\tlegacy();"}} } },
|
|
97
97
|
content: null
|
|
98
98
|
}]
|
|
@@ -104,8 +104,8 @@ Replace the entire body of `alpha`, including its closing `}`. `end` **MUST** be
|
|
|
104
104
|
When adding a sibling declaration, prefer `prepend` on the next declaration.
|
|
105
105
|
```
|
|
106
106
|
{
|
|
107
|
-
path: "a.ts",
|
|
108
107
|
edits: [{
|
|
108
|
+
path: "a.ts",
|
|
109
109
|
loc: { prepend: {{href 9 "function beta() {"}} },
|
|
110
110
|
content: [
|
|
111
111
|
"function gamma() {",
|
|
@@ -18,7 +18,8 @@ When editing structured blocks (nested braces, tags, indented regions), include
|
|
|
18
18
|
|
|
19
19
|
<parameters>
|
|
20
20
|
```ts
|
|
21
|
-
|
|
21
|
+
// Input is { edits: Entry[] } where Entry is one of:
|
|
22
|
+
type Entry =
|
|
22
23
|
// Diff is one or more hunks in the same file.
|
|
23
24
|
// - Each hunk begins with "@@" (anchor optional).
|
|
24
25
|
// - Each hunk body only has lines starting with ' ' | '+' | '-'.
|
|
@@ -50,19 +51,23 @@ Returns success/failure; on failure, error message indicates:
|
|
|
50
51
|
</critical>
|
|
51
52
|
|
|
52
53
|
<example name="create">
|
|
53
|
-
edit {"path":"hello.txt","op":"create","diff":"Hello\n"}
|
|
54
|
+
edit {"edits":[{"path":"hello.txt","op":"create","diff":"Hello\n"}]}
|
|
54
55
|
</example>
|
|
55
56
|
|
|
56
57
|
<example name="update">
|
|
57
|
-
edit {"path":"src/app.py","op":"update","diff":"@@ def greet():\n def greet():\n-print('Hi')\n+print('Hello')\n"}
|
|
58
|
+
edit {"edits":[{"path":"src/app.py","op":"update","diff":"@@ def greet():\n def greet():\n-print('Hi')\n+print('Hello')\n"}]}
|
|
58
59
|
</example>
|
|
59
60
|
|
|
60
61
|
<example name="rename">
|
|
61
|
-
edit {"path":"src/app.py","op":"update","rename":"src/main.py","diff":"@@\n …\n"}
|
|
62
|
+
edit {"edits":[{"path":"src/app.py","op":"update","rename":"src/main.py","diff":"@@\n …\n"}]}
|
|
62
63
|
</example>
|
|
63
64
|
|
|
64
65
|
<example name="delete">
|
|
65
|
-
edit {"path":"obsolete.txt","op":"delete"}
|
|
66
|
+
edit {"edits":[{"path":"obsolete.txt","op":"delete"}]}
|
|
67
|
+
</example>
|
|
68
|
+
|
|
69
|
+
<example name="multi-file">
|
|
70
|
+
edit {"edits":[{"path":"src/types.ts","op":"update","diff":"@@\n-old\n+new\n"},{"path":"src/index.ts","op":"update","diff":"@@\n-old\n+new\n"}]}
|
|
66
71
|
</example>
|
|
67
72
|
|
|
68
73
|
<avoid>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Blocks until one or more background jobs complete, fail, or are cancelled.
|
|
2
2
|
|
|
3
|
-
You **MUST** use
|
|
3
|
+
You **MUST** use the `poll` tool instead of polling `read jobs://` in a loop when you need to wait for background task or bash results before continuing.
|
|
4
4
|
|
|
5
5
|
Returns the status and results of all watched jobs once at least one finishes.
|
|
@@ -5,8 +5,8 @@ Reads files using syntax-aware chunks.
|
|
|
5
5
|
- `sel` — optional selector: `class_Foo`, `class_Foo.fn_bar#ABCD~`, `?`, `L50`, `L50-L120`, or `raw`
|
|
6
6
|
- `timeout` — seconds, for URLs only
|
|
7
7
|
|
|
8
|
-
Each
|
|
9
|
-
If you need a canonical target list, run `read(path="file", sel="?")`. That listing shows chunk paths with
|
|
8
|
+
Each anchor `@full.chunk.path#CCCC` (with `-` prefixes for nesting depth) in the output identifies a chunk. Use `full.chunk.path#CCCC` as-is to read truncated chunks.
|
|
9
|
+
If you need a canonical target list, run `read(path="file", sel="?")`. That listing shows chunk paths with IDs.
|
|
10
10
|
Line numbers in the gutter are absolute file line numbers.
|
|
11
11
|
|
|
12
12
|
`L20` (single line, no explicit end) is shorthand for `L20` to end-of-file. Use `L20-L20` for a one-line window.
|
|
@@ -30,6 +30,6 @@ When used against a SQLite database (`.sqlite`, `.sqlite3`, `.db`, `.db3`), retu
|
|
|
30
30
|
</instruction>
|
|
31
31
|
|
|
32
32
|
<critical>
|
|
33
|
-
- **MUST** `read` before editing — never invent chunk names or
|
|
33
|
+
- **MUST** `read` before editing — never invent chunk names or IDs.
|
|
34
34
|
- Chunk names are truncated (e.g., `handleRequest` becomes `fn_handleRequ`). Always copy chunk paths from `read` or `?` output — never construct them from source identifiers.
|
|
35
35
|
</critical>
|
|
@@ -2,7 +2,7 @@ Launches subagents to parallelize workflows.
|
|
|
2
2
|
|
|
3
3
|
{{#if asyncEnabled}}
|
|
4
4
|
- Use `read jobs://` to inspect state; `read jobs://<job_id>` for detail.
|
|
5
|
-
- Use the `
|
|
5
|
+
- Use the `poll` tool to wait until completion. You **MUST NOT** poll `read jobs://` in a loop.
|
|
6
6
|
{{/if}}
|
|
7
7
|
|
|
8
8
|
Subagents lack your conversation history. Every decision, file content, and user requirement they need **MUST** be explicit in `context` or `assignment`.
|
|
@@ -13,7 +13,7 @@ Subagents lack your conversation history. Every decision, file content, and user
|
|
|
13
13
|
- `.description`: UI display only — subagent never sees it
|
|
14
14
|
- `.assignment`: Complete self-contained instructions. One-liners PROHIBITED; missing acceptance criteria = too vague.
|
|
15
15
|
- `context`: Shared background prepended to every assignment. Session-specific info only.
|
|
16
|
-
- `schema`: JTD schema for expected output. Format lives here — **MUST NOT** be duplicated in assignments.
|
|
16
|
+
- `schema`: JSON-encoded JTD schema for expected output. Format lives here — **MUST NOT** be duplicated in assignments.
|
|
17
17
|
- `tasks`: Tasks to execute in parallel.
|
|
18
18
|
- `isolated`: Run in isolated environment; returns patches. Use when tasks edit overlapping files.
|
|
19
19
|
</parameters>
|