@oh-my-pi/pi-coding-agent 14.0.5 → 14.1.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.
Files changed (101) hide show
  1. package/CHANGELOG.md +120 -0
  2. package/package.json +8 -8
  3. package/src/async/index.ts +1 -0
  4. package/src/async/job-manager.ts +43 -10
  5. package/src/async/support.ts +5 -0
  6. package/src/cli/list-models.ts +96 -57
  7. package/src/commit/agentic/tools/analyze-file.ts +1 -2
  8. package/src/commit/model-selection.ts +16 -13
  9. package/src/config/mcp-schema.json +1 -1
  10. package/src/config/model-equivalence.ts +675 -0
  11. package/src/config/model-registry.ts +242 -45
  12. package/src/config/model-resolver.ts +282 -65
  13. package/src/config/settings-schema.ts +27 -3
  14. package/src/config/settings.ts +1 -1
  15. package/src/cursor.ts +64 -23
  16. package/src/edit/index.ts +254 -89
  17. package/src/edit/modes/chunk.ts +336 -57
  18. package/src/edit/modes/hashline.ts +51 -26
  19. package/src/edit/modes/patch.ts +16 -10
  20. package/src/edit/modes/replace.ts +15 -7
  21. package/src/edit/renderer.ts +248 -94
  22. package/src/export/html/template.css +82 -0
  23. package/src/export/html/template.generated.ts +1 -1
  24. package/src/export/html/template.js +614 -97
  25. package/src/extensibility/custom-tools/types.ts +0 -3
  26. package/src/extensibility/extensions/loader.ts +16 -0
  27. package/src/extensibility/extensions/runner.ts +2 -7
  28. package/src/extensibility/extensions/types.ts +8 -4
  29. package/src/internal-urls/docs-index.generated.ts +4 -4
  30. package/src/internal-urls/jobs-protocol.ts +2 -1
  31. package/src/ipy/executor.ts +447 -52
  32. package/src/ipy/kernel.ts +39 -13
  33. package/src/lsp/client.ts +55 -1
  34. package/src/lsp/index.ts +8 -0
  35. package/src/lsp/types.ts +6 -0
  36. package/src/main.ts +6 -2
  37. package/src/memories/index.ts +7 -6
  38. package/src/modes/acp/acp-agent.ts +4 -1
  39. package/src/modes/components/bash-execution.ts +16 -4
  40. package/src/modes/components/model-selector.ts +221 -64
  41. package/src/modes/components/status-line/presets.ts +17 -6
  42. package/src/modes/components/status-line/segments.ts +15 -0
  43. package/src/modes/components/status-line-segment-editor.ts +1 -0
  44. package/src/modes/components/status-line.ts +7 -1
  45. package/src/modes/components/tool-execution.ts +145 -75
  46. package/src/modes/controllers/command-controller.ts +42 -1
  47. package/src/modes/controllers/event-controller.ts +4 -1
  48. package/src/modes/controllers/extension-ui-controller.ts +28 -5
  49. package/src/modes/controllers/input-controller.ts +9 -3
  50. package/src/modes/controllers/selector-controller.ts +17 -6
  51. package/src/modes/interactive-mode.ts +19 -3
  52. package/src/modes/print-mode.ts +13 -4
  53. package/src/modes/prompt-action-autocomplete.ts +3 -5
  54. package/src/modes/rpc/rpc-mode.ts +8 -2
  55. package/src/modes/shared.ts +2 -2
  56. package/src/modes/types.ts +1 -0
  57. package/src/modes/utils/ui-helpers.ts +1 -0
  58. package/src/prompts/system/system-prompt.md +5 -1
  59. package/src/prompts/tools/bash.md +16 -1
  60. package/src/prompts/tools/cancel-job.md +1 -1
  61. package/src/prompts/tools/chunk-edit.md +191 -163
  62. package/src/prompts/tools/hashline.md +11 -11
  63. package/src/prompts/tools/patch.md +10 -5
  64. package/src/prompts/tools/{await.md → poll.md} +1 -1
  65. package/src/prompts/tools/read-chunk.md +12 -3
  66. package/src/prompts/tools/read.md +9 -0
  67. package/src/prompts/tools/task.md +2 -2
  68. package/src/prompts/tools/vim.md +98 -0
  69. package/src/prompts/tools/write.md +1 -0
  70. package/src/sdk.ts +758 -725
  71. package/src/session/agent-session.ts +187 -40
  72. package/src/session/session-manager.ts +50 -4
  73. package/src/slash-commands/builtin-registry.ts +17 -0
  74. package/src/task/executor.ts +9 -5
  75. package/src/task/index.ts +3 -5
  76. package/src/task/types.ts +2 -2
  77. package/src/tools/bash.ts +240 -57
  78. package/src/tools/cancel-job.ts +2 -1
  79. package/src/tools/find.ts +5 -2
  80. package/src/tools/grep.ts +77 -8
  81. package/src/tools/index.ts +48 -19
  82. package/src/tools/inspect-image.ts +1 -1
  83. package/src/tools/{await-tool.ts → poll-tool.ts} +38 -31
  84. package/src/tools/python.ts +293 -278
  85. package/src/tools/read.ts +218 -1
  86. package/src/tools/sqlite-reader.ts +623 -0
  87. package/src/tools/submit-result.ts +5 -2
  88. package/src/tools/todo-write.ts +8 -2
  89. package/src/tools/vim.ts +966 -0
  90. package/src/tools/write.ts +187 -1
  91. package/src/utils/commit-message-generator.ts +1 -0
  92. package/src/utils/edit-mode.ts +2 -1
  93. package/src/utils/git.ts +24 -1
  94. package/src/utils/session-color.ts +55 -0
  95. package/src/utils/title-generator.ts +16 -7
  96. package/src/vim/buffer.ts +309 -0
  97. package/src/vim/commands.ts +382 -0
  98. package/src/vim/engine.ts +2426 -0
  99. package/src/vim/parser.ts +151 -0
  100. package/src/vim/render.ts +252 -0
  101. package/src/vim/types.ts +197 -0
@@ -1,11 +1,13 @@
1
- Edits files via syntax-aware chunks. Run `read(path="file.ts")` first. The edit selector is a chunk path, optionally qualified with a region.
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 CRCs. Copy them from the latest `read` output or edit response.
5
- - `sel` format:
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` requires the current CRC. Insertions do not.
26
- - **CRCs change after every edit.** The edit response always carries the new CRCs — use those for the next call or run `read(path="file", sel="?")` to refresh. Never reuse a CRC from before the latest edit.
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. Replacing without a region replaces the **entire chunk including leading comments, decorators, and attributes** — omitting them from `content` deletes them.
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
- **`replace` 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 replacing `~` on any chunk, 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.
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). Replacing `~` on a group chunk replaces **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 replace the parent group.
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
- Given a chunk like:
39
- ```
40
- /// doc comment <-- leading trivia
41
- #[attr] <-- leading trivia
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#CRC` 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#CRC` directly above your target.
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
- |op|sel|effect|
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
- |`replace`|`chunk#CRC`, `chunk#CRC~`, or `chunk#CRC^`|rewrite the addressed region|
62
- |`before`|`chunk`, `chunk~`, or `chunk^`|insert before the region span|
63
- |`after`|`chunk`, `chunk~`, or `chunk^`|insert after the region span|
64
- |`prepend`|`chunk`, `chunk~`, or `chunk^`|insert at the start inside the region|
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 `example.ts`:
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
- function makeCounter(start: number): Counter {
133
- const c = new Counter();
134
- c.value = start;
135
- return c;
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
- **Replace a method body** (`~`):
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
- { "sel": "class_Counter.fn_increment#NQWY~", "op": "replace", "content": "this.value += 1;\nconsole.log('incremented to', this.value);\n" }
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 braces are kept:
155
+ Result — only the body (non-`^` lines) changes; the doc comment, signature, and closing `}` are all preserved:
144
156
  ```
145
- increment(): void {
146
- this.value += 1;
147
- console.log('incremented to', this.value);
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
- **Replace a function header** (`^` signature and doc comment):
163
+ **Write whole chunk** (rewrite signature + doc comment + body):
152
164
  ```
153
- { "sel": "fn_createCounter#PQQY^", "op": "replace", "content": "/** Creates a counter with the given start value. */\nfunction createCounter(initial: number, label?: string): Counter {\n" }
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 — adds a doc comment and updates the signature, body untouched:
167
+ Result — **everything** including the doc comment and signature is rewritten. You must include them; omitting them deletes them:
156
168
  ```
157
- /** Creates a counter with the given start value. */
158
- function createCounter(initial: number, label?: string): Counter {
159
- const counter = new Counter();
160
- counter.value = initial;
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
- **Insert before a chunk** (`before`):
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
- { "sel": "fn_createCounter", "op": "before", "content": "/** Factory function below. */\n" }
179
+ Result the head (all `^` lines + opening brace) changes, body untouched:
168
180
  ```
169
- Result a comment is inserted before the function:
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
- function createCounter(initial: number): Counter {
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 after a chunk** (`after`):
177
- ~~~json
178
- {{#if chunkAutoIndent}}
179
- { "sel": "enum_Status", "op": "after", "content": "\nfunction isActive(s: Status): boolean {\n\treturn s === Status.Active;\n}\n" }
180
- {{else}}
181
- { "sel": "enum_Status", "op": "after", "content": "\nfunction isActive(s: Status): boolean {\n return s === Status.Active;\n}\n" }
182
- {{/if}}
183
- ~~~
184
- Result a new function appears after the enum:
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
- function isActive(s: Status): boolean {
193
- return s === Status.Active;
217
+ impl Default for Counter {
218
+ fn default() -> Self {
219
+ Self { value: 0, max: 100 }
220
+ }
194
221
  }
195
222
 
196
- function createCounter(initial: number): Counter {
223
+ impl Counter {
197
224
  ```
198
225
 
199
- **Prepend inside a container** (`~` + `prepend`):
226
+ **Insert at start of container body** (`~` + `prepend`):
200
227
  ```
201
- { "sel": "class_Counter~", "op": "prepend", "content": "label: string = 'default';\n\n" }
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 field is added at the top of the class body, before existing members:
230
+ Result — a new method is added at the top of the impl body, before existing methods:
204
231
  ```
205
- class Counter {
206
- label: string = 'default';
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
- value: number = 0;
238
+ /// Creates a new counter starting at zero.
239
+ pub fn new(max: i32) -> Self {
209
240
  ```
210
241
 
211
- **Append inside a container** (`~` + `append`):
212
- ~~~json
213
- {{#if chunkAutoIndent}}
214
- { "sel": "class_Counter~", "op": "append", "content": "\nreset(): void {\n\tthis.value = 0;\n}\n" }
215
- {{else}}
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
- toString(): string {
222
- return `Counter(${this.value})`;
223
- }
248
+ pub fn get(&self) -> i32 {
249
+ self.value
250
+ }
224
251
 
225
- reset(): void {
226
- this.value = 0;
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** (`replace` with empty content):
259
+ **Delete a chunk**:
232
260
  ```
233
- { "sel": "class_Counter.fn_toString#ZQZP", "op": "replace", "content": "" }
261
+ { "path": "counter.rs:impl_Counte.fn_decrem#TTWB", "write": null }
234
262
  ```
235
- Result — the method is removed from the class.
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, and the tool adds the method's base indent to both.
244
- - For `^`: write at the chunk's own depth. A class member's head uses `"/** doc */\nstart(): void {"`.
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 `"function foo() {\n\treturn 1;\n}\n"`.
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 `"function foo() {\n return 1;\n}\n"`.
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. In one `edit` call, batch all edits for one file. After any successful edit, re-read before editing that file again.
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
- - `path` — file path
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
- type T =
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 this instead of polling `read jobs://` in a loop when you need to wait for background task or bash results before continuing.
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 opening anchor `[< full.chunk.path#CCCC ]` in the default 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 CRCs.
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.
@@ -18,9 +18,18 @@ Chunk reads preserve literal leading tabs/spaces from the file. When editing, ke
18
18
  {{/if}}
19
19
 
20
20
  Chunk trees: JS, TS, TSX, Python, Rust, Go. Others use blank-line fallback.
21
+
22
+ # SQLite Databases
23
+ When used against a SQLite database (`.sqlite`, `.sqlite3`, `.db`, `.db3`), returns structured database content.
24
+ - `file.db` — list tables with row counts
25
+ - `file.db:table` — table schema + sample rows
26
+ - `file.db:table:key` — single row by primary key
27
+ - `file.db:table?limit=50&offset=100` — paginated rows
28
+ - `file.db:table?where=status='active'&order=created:desc` — filtered rows
29
+ - `file.db?q=SELECT …` — read-only SELECT query
21
30
  </instruction>
22
31
 
23
32
  <critical>
24
- - **MUST** `read` before editing — never invent chunk names or CRCs.
33
+ - **MUST** `read` before editing — never invent chunk names or IDs.
25
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.
26
35
  </critical>
@@ -38,6 +38,15 @@ When used against a directory, or an archive root, the tool will return a list o
38
38
  - Formats: `.tar`, `.tar.gz`, `.tgz`, and `.zip`.
39
39
  - Use `archive.ext:path/inside/archive` to read or list archive contents
40
40
 
41
+ # SQLite Databases
42
+ When used against a SQLite database (`.sqlite`, `.sqlite3`, `.db`, `.db3`), returns structured database content.
43
+ - `file.db` — list tables with row counts
44
+ - `file.db:table` — table schema + sample rows
45
+ - `file.db:table:key` — single row by primary key
46
+ - `file.db:table?limit=50&offset=100` — paginated rows
47
+ - `file.db:table?where=status='active'&order=created:desc` — filtered rows
48
+ - `file.db?q=SELECT …` — read-only SELECT query
49
+
41
50
  # URLs
42
51
  - Extract information from web pages, GitHub issues/PRs, Stack Overflow, Wikipedia, Reddit, NPM, arXiv, technical blogs, RSS/Atom feeds, JSON endpoints
43
52
  - `sel="raw"` for untouched HTML or debugging
@@ -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 `await` tool to wait until completion. You **MUST NOT** poll `read jobs://` in a loop.
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>