@oh-my-pi/pi-coding-agent 13.0.0 → 13.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [13.0.1] - 2026-02-22
6
+ ### Changed
7
+
8
+ - Simplified hashline edit schema to use unified `first`/`last` anchor fields instead of operation-specific field names (`tag`, `before`, `after`)
9
+ - Improved resilience of anchor resolution to degrade gracefully when anchors are missing or invalid, allowing edits to proceed with available anchors
10
+ - Updated hashline tool documentation to reflect new unified anchor syntax across all operations (replace, append, prepend, insert)
11
+
5
12
  ## [13.0.0] - 2026-02-22
6
13
  ### Added
7
14
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-coding-agent",
4
- "version": "13.0.0",
4
+ "version": "13.0.1",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://github.com/can1357/oh-my-pi",
7
7
  "author": "Can Boluk",
@@ -41,12 +41,12 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@mozilla/readability": "^0.6",
44
- "@oh-my-pi/omp-stats": "13.0.0",
45
- "@oh-my-pi/pi-agent-core": "13.0.0",
46
- "@oh-my-pi/pi-ai": "13.0.0",
47
- "@oh-my-pi/pi-natives": "13.0.0",
48
- "@oh-my-pi/pi-tui": "13.0.0",
49
- "@oh-my-pi/pi-utils": "13.0.0",
44
+ "@oh-my-pi/omp-stats": "13.0.1",
45
+ "@oh-my-pi/pi-agent-core": "13.0.1",
46
+ "@oh-my-pi/pi-ai": "13.0.1",
47
+ "@oh-my-pi/pi-natives": "13.0.1",
48
+ "@oh-my-pi/pi-tui": "13.0.1",
49
+ "@oh-my-pi/pi-utils": "13.0.1",
50
50
  "@sinclair/typebox": "^0.34",
51
51
  "@xterm/headless": "^6.0",
52
52
  "ajv": "^8.18",
@@ -168,24 +168,6 @@ export function stripNewLinePrefixes(lines: string[]): string[] {
168
168
  });
169
169
  }
170
170
 
171
- const hashlineReplaceContentFormat = (kind: string) =>
172
- Type.Union([
173
- Type.Null(),
174
- Type.Array(Type.String(), { description: `${kind} lines` }),
175
- Type.String({ description: `${kind} line` }),
176
- ]);
177
-
178
- const hashlineInsertContentFormat = (kind: string) =>
179
- Type.Union([
180
- Type.Array(Type.String(), { description: `${kind} lines`, minItems: 1 }),
181
- Type.String({ description: `${kind} line`, minLength: 1 }),
182
- ]);
183
-
184
- const hashlineTagFormat = (what: string) =>
185
- Type.String({
186
- description: `Tag identifying the ${what} in "LINE#ID" format`,
187
- });
188
-
189
171
  export function hashlineParseContent(edit: string | string[] | null): string[] {
190
172
  if (edit === null) return [];
191
173
  if (Array.isArray(edit)) return edit;
@@ -194,61 +176,23 @@ export function hashlineParseContent(edit: string | string[] | null): string[] {
194
176
  if (lines[lines.length - 1].trim() === "") return lines.slice(0, -1);
195
177
  return lines;
196
178
  }
197
- const hashlineReplaceTagEditSchema = Type.Object(
198
- {
199
- op: Type.Literal("replace"),
200
- tag: hashlineTagFormat("line being replaced"),
201
- content: hashlineReplaceContentFormat("Replacement"),
202
- },
203
- { additionalProperties: false },
204
- );
205
179
 
206
- const hashlineAppendEditSchema = Type.Object(
180
+ const hashlineEditSpecSchema = Type.Object(
207
181
  {
208
- op: Type.Literal("append"),
209
- after: Type.Optional(hashlineTagFormat("line after which to append")),
210
- content: hashlineInsertContentFormat("Appended"),
211
- },
212
- { additionalProperties: false },
213
- );
214
-
215
- const hashlinePrependEditSchema = Type.Object(
216
- {
217
- op: Type.Literal("prepend"),
218
- before: Type.Optional(hashlineTagFormat("line before which to prepend")),
219
- content: hashlineInsertContentFormat("Prepended"),
220
- },
221
- { additionalProperties: false },
222
- );
223
-
224
- const hashlineReplaceRangeEditSchema = Type.Object(
225
- {
226
- op: Type.Literal("replace"),
227
- first: hashlineTagFormat("first line"),
228
- last: hashlineTagFormat("last line"),
229
- content: hashlineReplaceContentFormat("Replacement"),
230
- },
231
- { additionalProperties: false },
232
- );
233
-
234
- const hashlineInsertEditSchema = Type.Object(
235
- {
236
- op: Type.Literal("insert"),
237
- before: Type.Optional(hashlineTagFormat("line before which to insert")),
238
- after: Type.Optional(hashlineTagFormat("line after which to insert")),
239
- content: hashlineInsertContentFormat("Inserted"),
182
+ op: StringEnum(["replace", "append", "prepend", "insert"], {
183
+ description: "Operation type",
184
+ }),
185
+ first: Type.Optional(Type.String({ description: 'First/start anchor tag in "LINE#ID" format' })),
186
+ last: Type.Optional(Type.String({ description: 'Last/end anchor tag in "LINE#ID" format' })),
187
+ content: Type.Union([
188
+ Type.Null(),
189
+ Type.Array(Type.String(), { description: "Content lines" }),
190
+ Type.String({ description: "Content line" }),
191
+ ]),
240
192
  },
241
193
  { additionalProperties: false },
242
194
  );
243
195
 
244
- const hashlineEditSpecSchema = Type.Union([
245
- hashlineReplaceTagEditSchema,
246
- hashlineReplaceRangeEditSchema,
247
- hashlineAppendEditSchema,
248
- hashlinePrependEditSchema,
249
- hashlineInsertEditSchema,
250
- ]);
251
-
252
196
  const hashlineEditSchema = Type.Object(
253
197
  {
254
198
  path: Type.String({ description: "File path (relative or absolute)" }),
@@ -265,6 +209,88 @@ const hashlineEditSchema = Type.Object(
265
209
  export type HashlineToolEdit = Static<typeof hashlineEditSpecSchema>;
266
210
  export type HashlineParams = Static<typeof hashlineEditSchema>;
267
211
 
212
+ // ═══════════════════════════════════════════════════════════════════════════
213
+ // Resilient anchor resolution
214
+ // ═══════════════════════════════════════════════════════════════════════════
215
+
216
+ /**
217
+ * Map flat tool-schema edits (first/last) into typed HashlineEdit objects.
218
+ *
219
+ * Resilient: as long as at least one anchor exists, we execute.
220
+ * - replace + first only → single-line replace (tag = first)
221
+ * - replace + first + last → range replace
222
+ * - append + first or last → append after that anchor
223
+ * - prepend + first or last → prepend before that anchor
224
+ * - insert + first + last → insert between them
225
+ * - insert + one anchor → degrade to append/prepend
226
+ * - no anchors → file-level append/prepend (only for those ops)
227
+ *
228
+ * Unknown ops default to "replace".
229
+ */
230
+ function resolveEditAnchors(edits: HashlineToolEdit[]): HashlineEdit[] {
231
+ const result: HashlineEdit[] = [];
232
+ for (const edit of edits) {
233
+ const content = hashlineParseContent(edit.content);
234
+ const first = edit.first ? tryParseTag(edit.first) : undefined;
235
+ const last = edit.last ? tryParseTag(edit.last) : undefined;
236
+
237
+ // Normalize op — default unknown values to "replace"
238
+ const op = edit.op === "append" || edit.op === "prepend" || edit.op === "insert" ? edit.op : "replace";
239
+
240
+ switch (op) {
241
+ case "replace": {
242
+ if (first && last) {
243
+ result.push({ op: "replace", first, last, content });
244
+ } else if (first) {
245
+ result.push({ op: "replace", tag: first, content });
246
+ } else if (last) {
247
+ result.push({ op: "replace", tag: last, content });
248
+ } else {
249
+ throw new Error("Replace requires at least one anchor (first or last).");
250
+ }
251
+ break;
252
+ }
253
+ case "append": {
254
+ // Prefer first as the "after" anchor; fall back to last
255
+ const anchor = first ?? last;
256
+ result.push({ op: "append", ...(anchor ? { after: anchor } : {}), content });
257
+ break;
258
+ }
259
+ case "prepend": {
260
+ // Prefer last as the "before" anchor; fall back to first
261
+ const anchor = last ?? first;
262
+ result.push({ op: "prepend", ...(anchor ? { before: anchor } : {}), content });
263
+ break;
264
+ }
265
+ case "insert": {
266
+ if (first && last) {
267
+ result.push({ op: "insert", after: first, before: last, content });
268
+ } else if (first) {
269
+ // Degrade: insert after first
270
+ result.push({ op: "append", after: first, content });
271
+ } else if (last) {
272
+ // Degrade: insert before last
273
+ result.push({ op: "prepend", before: last, content });
274
+ } else {
275
+ // No anchors — append to end
276
+ result.push({ op: "append", content });
277
+ }
278
+ break;
279
+ }
280
+ }
281
+ }
282
+ return result;
283
+ }
284
+
285
+ /** Parse a tag, returning undefined instead of throwing on garbage. */
286
+ function tryParseTag(raw: string): LineTag | undefined {
287
+ try {
288
+ return parseTag(raw);
289
+ } catch {
290
+ return undefined;
291
+ }
292
+ }
293
+
268
294
  // ═══════════════════════════════════════════════════════════════════════════
269
295
  // LSP FileSystem for patch mode
270
296
  // ═══════════════════════════════════════════════════════════════════════════
@@ -522,24 +548,15 @@ export class EditTool implements AgentTool<TInput> {
522
548
  if (!(await file.exists())) {
523
549
  const content: string[] = [];
524
550
  for (const edit of edits) {
525
- switch (edit.op) {
526
- case "append": {
527
- if (edit.after) {
528
- throw new Error(`File not found: ${path}`);
529
- }
530
- content.push(...hashlineParseContent(edit.content));
531
- break;
532
- }
533
- case "prepend": {
534
- if (edit.before) {
535
- throw new Error(`File not found: ${path}`);
536
- }
551
+ // For file creation, only anchorless appends/prepends are valid
552
+ if ((edit.op === "append" || edit.op === "prepend") && !edit.first && !edit.last) {
553
+ if (edit.op === "prepend") {
537
554
  content.unshift(...hashlineParseContent(edit.content));
538
- break;
539
- }
540
- default: {
541
- throw new Error(`File not found: ${path}`);
555
+ } else {
556
+ content.push(...hashlineParseContent(edit.content));
542
557
  }
558
+ } else {
559
+ throw new Error(`File not found: ${path}`);
543
560
  }
544
561
  }
545
562
  await file.write(content.join("\n"));
@@ -553,74 +570,7 @@ export class EditTool implements AgentTool<TInput> {
553
570
  };
554
571
  }
555
572
 
556
- const anchorEdits: HashlineEdit[] = [];
557
- for (const edit of edits) {
558
- switch (edit.op) {
559
- case "replace": {
560
- if ("tag" in edit) {
561
- anchorEdits.push({
562
- op: "replace",
563
- tag: parseTag(edit.tag),
564
- content: hashlineParseContent(edit.content),
565
- });
566
- } else {
567
- anchorEdits.push({
568
- op: "replace",
569
- first: parseTag(edit.first),
570
- last: parseTag(edit.last),
571
- content: hashlineParseContent(edit.content),
572
- });
573
- }
574
- break;
575
- }
576
- case "append": {
577
- const { after, content } = edit;
578
- anchorEdits.push({
579
- op: "append",
580
- ...(after ? { after: parseTag(after) } : {}),
581
- content: hashlineParseContent(content),
582
- });
583
- break;
584
- }
585
- case "prepend": {
586
- const { before, content } = edit;
587
- anchorEdits.push({
588
- op: "prepend",
589
- ...(before ? { before: parseTag(before) } : {}),
590
- content: hashlineParseContent(content),
591
- });
592
- break;
593
- }
594
- case "insert": {
595
- const { before, after, content } = edit;
596
- if (before && !after) {
597
- anchorEdits.push({
598
- op: "prepend",
599
- before: parseTag(before),
600
- content: hashlineParseContent(content),
601
- });
602
- } else if (after && !before) {
603
- anchorEdits.push({
604
- op: "append",
605
- after: parseTag(after),
606
- content: hashlineParseContent(content),
607
- });
608
- } else if (before && after) {
609
- anchorEdits.push({
610
- op: "insert",
611
- before: parseTag(before),
612
- after: parseTag(after),
613
- content: hashlineParseContent(content),
614
- });
615
- } else {
616
- throw new Error(`Insert must have both before and after tags.`);
617
- }
618
- break;
619
- }
620
- default:
621
- throw new Error(`Invalid edit operation: ${JSON.stringify(edit)}`);
622
- }
623
- }
573
+ const anchorEdits = resolveEditAnchors(edits);
624
574
 
625
575
  const rawContent = await file.text();
626
576
  const { bom, text: content } = stripBom(rawContent);
@@ -86,12 +86,12 @@ interface EditRenderArgs {
86
86
  edits?: HashlineEditPreview[];
87
87
  }
88
88
 
89
- type HashlineEditPreview =
90
- | { op: "replace"; tag: string; content: string[] }
91
- | { op: "replace"; first: string; last: string; content: string[] }
92
- | { op: "append"; after?: string; content: string[] }
93
- | { op: "prepend"; before?: string; content: string[] }
94
- | { op: "insert"; before: string; after: string; content: string[] };
89
+ type HashlineEditPreview = {
90
+ op: string;
91
+ first?: string;
92
+ last?: string;
93
+ content: string | string[] | null;
94
+ };
95
95
 
96
96
  /** Extended context for edit tool rendering */
97
97
  export interface EditRenderContext {
@@ -166,26 +166,18 @@ function formatStreamingHashlineEdits(edits: unknown[], uiTheme: Theme): string
166
166
 
167
167
  const contentLines = Array.isArray(editRecord.content) ? (editRecord.content as string[]).join("\n") : "";
168
168
 
169
- // replace with tag (single line)
170
- if ("tag" in editRecord && !("first" in editRecord)) {
171
- const tag = typeof editRecord.tag === "string" ? editRecord.tag : "…";
172
- return { srcLabel: `• line ${tag}`, dst: contentLines };
173
- }
174
- // replace with first..last (range)
175
- if ("first" in editRecord || "last" in editRecord) {
176
- const first = typeof editRecord.first === "string" ? editRecord.first : "…";
177
- const last = typeof editRecord.last === "string" ? editRecord.last : "…";
178
- return { srcLabel: `• range ${first}..${last}`, dst: contentLines };
169
+ const op = typeof editRecord.op === "string" ? editRecord.op : "?";
170
+ const first = typeof editRecord.first === "string" ? editRecord.first : undefined;
171
+ const last = typeof editRecord.last === "string" ? editRecord.last : undefined;
172
+
173
+ if (first && last && first !== last) {
174
+ return { srcLabel: `\u2022 ${op} ${first}..${last}`, dst: contentLines };
179
175
  }
180
- // append/prepend/insert
181
- if ("before" in editRecord || "after" in editRecord) {
182
- const after = typeof editRecord.after === "string" ? editRecord.after : undefined;
183
- const before = typeof editRecord.before === "string" ? editRecord.before : undefined;
184
- const refs = [after, before].filter(Boolean).join("..") || "…";
185
- return { srcLabel: `• insert ${refs}`, dst: contentLines };
176
+ const anchor = first ?? last;
177
+ if (anchor) {
178
+ return { srcLabel: `\u2022 ${op} ${anchor}`, dst: contentLines };
186
179
  }
187
-
188
- return { srcLabel: "• (incomplete edit)", dst: "" };
180
+ return { srcLabel: `\u2022 ${op} (file-level)`, dst: contentLines };
189
181
  }
190
182
  }
191
183
  function formatMetadataLine(lineCount: number | null, language: string | undefined, uiTheme: Theme): string {
@@ -5,5 +5,5 @@ Rules:
5
5
  - MUST describe the changes made, not the process
6
6
  - MUST NOT mention running tests, builds, or other validation steps
7
7
  - MUST NOT explain what the user asked for
8
- - MUST write in first person (I added..., I fixed...)
8
+ - MUST write in first person (I added…, I fixed)
9
9
  - MUST NOT ask questions
@@ -33,17 +33,17 @@ When a user describes what they want an agent to do, you will:
33
33
  <function call omitted for brevity only for this example>
34
34
  <commentary>
35
35
  Since a significant piece of code was written, use the {{TASK_TOOL_NAME}} tool to launch the test-runner agent to run the tests.
36
- </commentary>
36
+ </commentary>
37
37
  assistant: "Now let me use the test-runner agent to run the tests"
38
- </example>
38
+ </example>
39
39
  - <example>
40
40
  Context: User is creating an agent to respond to the word "hello" with a friendly jok.
41
41
  user: "Hello"
42
42
  assistant: "I'm going to use the {{TASK_TOOL_NAME}} tool to launch the greeting-responder agent to respond with a friendly joke"
43
43
  <commentary>
44
44
  Since the user is greeting, use the greeting-responder agent to respond with a friendly joke.
45
- </commentary>
46
- </example>
45
+ </commentary>
46
+ </example>
47
47
  - If the user mentioned or implied that the agent should be used proactively, you SHOULD include examples of this.
48
48
  - NOTE: You MUST ensure that in the examples, you are making the assistant use the Agent tool and MUST NOT simply respond directly to the task.
49
49
 
@@ -12,14 +12,15 @@ Apply precise file edits using `LINE#ID` tags, anchoring to the file content.
12
12
  </workflow>
13
13
 
14
14
  <operations>
15
+ Every edit has `op`, `first` (start anchor), `last` (end anchor), and `content`. All anchors use `"LINE#ID"` format.
15
16
  - **Line or range replace/delete**
16
- - `{ op: "replace", tag: "N#ID", content: […] }`
17
- - `{ op: "replace", first: "N#ID", last: "N#ID", content: […] }`
17
+ - `{ op: "replace", first: "N#ID", content: […] }` (single line)
18
+ - `{ op: "replace", first: "N#ID", last: "N#ID", content: […] }` (range)
18
19
  - Use for swaps, block rewrites, or deleting a full span (`content: null`).
19
20
  - **Insert** (new content)
20
- - `{ op: "prepend", before: "N#ID", content: […] }` or `{ op: "prepend", content: […] }` (no `before` = insert at beginning of file)
21
- - `{ op: "append", after: "N#ID", content: […] }` or `{ op: "append", content: […] }` (no `after` = insert at end of file)
22
- - `{ op: "insert", after: "N#ID", before: "N#ID", content: […] }` (between adjacent anchors; safest for blocks)
21
+ - `{ op: "prepend", last: "N#ID", content: […] }` or `{ op: "prepend", content: […] }` (no anchor = insert at beginning of file)
22
+ - `{ op: "append", first: "N#ID", content: […] }` or `{ op: "append", content: […] }` (no anchor = insert at end of file)
23
+ - `{ op: "insert", first: "N#ID", last: "N#ID", content: […] }` (between adjacent anchors; safest for blocks)
23
24
  - **File-level controls**
24
25
  - `{ delete: true, edits: [] }` deletes the file (cannot be combined with `rename`).
25
26
  - `{ rename: "new/path.ts", edits: […] }` writes result to new path and removes old path.
@@ -62,7 +63,7 @@ Apply precise file edits using `LINE#ID` tags, anchoring to the file content.
62
63
  ```
63
64
  ```
64
65
  op: "replace"
65
- tag: "{{hlineref 23 " const timeout: number = 5000;"}}"
66
+ first: "{{hlineref 23 " const timeout: number = 5000;"}}"
66
67
  content: [" const timeout: number = 30_000;"]
67
68
  ```
68
69
  </example>
@@ -74,7 +75,7 @@ content: [" const timeout: number = 30_000;"]
74
75
  ```
75
76
  ```
76
77
  op: "replace"
77
- tag: "{{hlineref 7 "// @ts-ignore"}}"
78
+ first: "{{hlineref 7 "// @ts-ignore"}}"
78
79
  content: null
79
80
  ```
80
81
  </example>
@@ -85,7 +86,7 @@ content: null
85
86
  ```
86
87
  ```
87
88
  op: "replace"
88
- tag: "{{hlineref 14 " placeholder: \"DO NOT SHIP\","}}"
89
+ first: "{{hlineref 14 " placeholder: \"DO NOT SHIP\","}}"
89
90
  content: [""]
90
91
  ```
91
92
  </example>
@@ -127,10 +128,10 @@ content: null
127
128
  ```
128
129
  ```
129
130
  op: "prepend"
130
- before: "{{hlineref 1 "import * as fs from \"node:fs/promises\";"}}"
131
+ last: "{{hlineref 1 "import * as fs from \"node:fs/promises\";"}}"
131
132
  content: ["import * as os from \"node:os\";"]
132
133
  ```
133
- Use `before` for anchored insertion before a specific line. Omit `before` to prepend at BOF.
134
+ Use `last` for anchored insertion before a specific line. Omit anchor to prepend at BOF.
134
135
  </example>
135
136
 
136
137
  <example name="append at end of file">
@@ -139,10 +140,10 @@ Use `before` for anchored insertion before a specific line. Omit `before` to pre
139
140
  ```
140
141
  ```
141
142
  op: "append"
142
- after: "{{hlineref 260 "export { serialize, deserialize };"}}"
143
+ first: "{{hlineref 260 "export { serialize, deserialize };"}}"
143
144
  content: ["export { validate };"]
144
145
  ```
145
- Use `after` for anchored insertion after a specific line. Omit `after` to append at EOF.
146
+ Use `first` for anchored insertion after a specific line. Omit anchor to append at EOF.
146
147
  </example>
147
148
 
148
149
  <example name="add an entry between known siblings">
@@ -152,8 +153,8 @@ Use `after` for anchored insertion after a specific line. Omit `after` to append
152
153
  ```
153
154
  ```
154
155
  op: "insert"
155
- after: "{{hlineref 44 " \"build\": \"bun run compile\","}}"
156
- before: "{{hlineref 45 " \"test\": \"bun test\""}}"
156
+ first: "{{hlineref 44 " \"build\": \"bun run compile\","}}"
157
+ last: "{{hlineref 45 " \"test\": \"bun test\""}}"
157
158
  content: [" \"lint\": \"biome check\","]
158
159
  ```
159
160
  Dual anchors pin the insert to exactly one gap, preventing drift from edits elsewhere in the file. **Always prefer dual anchors when both boundaries are content lines.**
@@ -168,10 +169,10 @@ Dual anchors pin the insert to exactly one gap, preventing drift from edits else
168
169
  ```
169
170
  ```
170
171
  op: "insert"
171
- before: "{{hlineref 103 "export function serialize(data: unknown): string {"}}"
172
+ last: "{{hlineref 103 "export function serialize(data: unknown): string {"}}"
172
173
  content: ["function validate(data: unknown): boolean {", " return data != null && typeof data === \"object\";", "}", ""]
173
174
  ```
174
- The trailing `""` in `content` preserves the blank-line separator. **Anchor to the structural line (`export function ...`), not the blank line above it** — blank lines are ambiguous and may be added or removed by other edits.
175
+ The trailing `""` in `content` preserves the blank-line separator. **Anchor to the structural line (`export function …`), not the blank line above it** — blank lines are ambiguous and may be added or removed by other edits.
175
176
  </example>
176
177
 
177
178
  <example name="file delete">
@@ -192,14 +193,14 @@ edits: […]
192
193
  <example name="anti-pattern: anchoring to whitespace">
193
194
  Bad — tags to a blank line; fragile if blank lines shift:
194
195
  ```
195
- after: "{{hlineref 102 ""}}"
196
+ first: "{{hlineref 102 ""}}"
196
197
  content: ["function validate() {", …, "}"]
197
198
  ```
198
199
 
199
200
  Good — anchors to the structural target:
200
201
 
201
202
  ```
202
- before: "{{hlineref 103 "export function serialize(data: unknown): string {"}}"
203
+ last: "{{hlineref 103 "export function serialize(data: unknown): string {"}}"
203
204
  content: ["function validate() {", …, "}"]
204
205
  ```
205
206
  </example>
@@ -207,7 +208,7 @@ content: ["function validate() {", …, "}"]
207
208
  <critical>
208
209
  You MUST ensure:
209
210
  - Payload shape is `{ "path": string, "edits": [operation, …], "delete"?: boolean, "rename"?: string }`
210
- - Every edit MUST match exactly one variant
211
+ - Each edit has `op`, optional `first`/`last` anchors, and `content`
211
212
  - Every tag MUST be copied EXACTLY from a tool result as `N#ID`
212
213
  - Scope MUST be minimal and formatting MUST be preserved except targeted token changes
213
214
  </critical>
@@ -60,7 +60,7 @@ edit {"path":"src/app.py","op":"update","diff":"@@ def greet():\n def greet():\n
60
60
  </example>
61
61
 
62
62
  <example name="rename">
63
- edit {"path":"src/app.py","op":"update","rename":"src/main.py","diff":"@@\n ...\n"}
63
+ edit {"path":"src/app.py","op":"update","rename":"src/main.py","diff":"@@\n …\n"}
64
64
  </example>
65
65
 
66
66
  <example name="delete">
@@ -36,8 +36,8 @@ All helpers auto-print results and return values for chaining.
36
36
  <output>
37
37
  User sees output like Jupyter notebook; rich displays render fully:
38
38
  - `display(JSON(data))` → interactive JSON tree
39
- - `display(HTML(...))` → rendered HTML
40
- - `display(Markdown(...))` → formatted markdown
39
+ - `display(HTML())` → rendered HTML
40
+ - `display(Markdown())` → formatted markdown
41
41
  - `plt.show()` → inline figures
42
42
  **You will see object repr** (e.g., `<IPython.core.display.JSON object>`). Trust `display()`; you MUST NOT assume user sees only repr.
43
43
  </output>
@@ -24,7 +24,7 @@ For position-addressed or pattern-addressed changes, bash more efficient:
24
24
 
25
25
  |Operation|Command|
26
26
  |---|---|
27
- |Append to file|`cat >> file <<'EOF'`...`EOF`|
27
+ |Append to file|`cat >> file <<'EOF'`…`EOF`|
28
28
  |Prepend to file|`{ cat - file; } <<'EOF' > tmp && mv tmp file`|
29
29
  |Delete lines N-M|`sed -i 'N,Md' file`|
30
30
  |Insert after line N|`sed -i 'Na\text' file`|
@@ -19,7 +19,6 @@ Subagents have no access to your conversation history. They don't know:
19
19
  Subagents CAN grep the parent conversation file for supplementary details.
20
20
 
21
21
  For large intermediate outputs (long traces, JSON payloads, temporary analysis snapshots), you SHOULD write them to `local://<path>` and pass the path in task context instead of inlining bulky text.
22
- ---
23
22
 
24
23
  ## Parameters
25
24
 
@@ -128,9 +127,9 @@ Use structure every assignment:
128
127
  - Patterns/APIs to use; reference files if applicable
129
128
 
130
129
  ## Edge Cases / Don't Break
131
- - Tricky case 1: ...
132
- - Tricky case 2: ...
133
- - Existing behavior must survive: ...
130
+ - Tricky case 1:
131
+ - Tricky case 2:
132
+ - Existing behavior must survive:
134
133
 
135
134
  ## Acceptance (task-local)
136
135
  - Expected behavior or observable result
@@ -145,11 +144,11 @@ Use structure every assignment:
145
144
  - "Migrate to N-API."
146
145
  - "Fix the bug in streaming."
147
146
  - "Update all constructors in `src/**/*.ts`."
148
- **Vague context** — forces agent invent conventions:
147
+ **Vague context** — forces agent invent conventions:
149
148
  - "Use existing patterns."
150
149
  - "Follow conventions."
151
150
  - "No WASM."
152
- **Redundant context** — wastes tokens repeating what subagents already have:
151
+ **Redundant context** — wastes tokens repeating what subagents already have:
153
152
  - Restating AGENTS.md rules (coding style, import conventions, formatting commands, logger usage, etc.)
154
153
  - Repeating project constraints from context files
155
154
  - Listing tool/framework preferences already documented in the repo
@@ -192,9 +191,9 @@ First style wastes your time, brittle if code shifts. Second gives agent room to
192
191
  <example type="bad" label="Duplicated context inflates tokens">
193
192
  <tasks>
194
193
  <task name="Grep">
195
- <description>Port grep module from WASM to N-API...</description>
196
- <assignment>Port grep module from WASM to N-API... (same blob repeated)</assignment>
197
- </task>
194
+ <description>Port grep module from WASM to N-API…</description>
195
+ <assignment>Port grep module from WASM to N-API (same blob repeated)</assignment>
196
+ </task>
198
197
  </tasks>
199
198
  </example>
200
199
 
@@ -210,7 +209,7 @@ Do not touch TS bindings or downstream consumers — separate phase.
210
209
  - MUST use `#[napi]` attribute macro on all exports
211
210
  - MUST return `napi::Result<T>` for fallible ops; never panic
212
211
  - MUST use `spawn_blocking` for filesystem I/O or >1ms work
213
- ...
212
+
214
213
 
215
214
  ## Acceptance (global)
216
215
  - Caller verifies after all tasks: `cargo test -p pi-natives` and `cargo build -p pi-natives` with no warnings
@@ -232,17 +231,17 @@ Do not touch TS bindings or downstream consumers — separate phase.
232
231
 
233
232
  ## Acceptance (task-local)
234
233
  - Three functions exported with correct signatures (caller verifies build after all tasks)
235
- </assignment>
236
- </task>
234
+ </assignment>
235
+ </task>
237
236
 
238
237
  <task name="PortHighlight">
239
238
  <description>Port highlight module to N-API</description>
240
239
  <assignment>
241
240
  ## Target
242
241
  - Files: `src/highlight.rs`, `src/lib.rs` (registration only)
243
- ...
244
- </assignment>
245
- </task>
242
+
243
+ </assignment>
244
+ </task>
246
245
  </tasks>
247
246
  </example>
248
247
  ---
@@ -254,7 +253,7 @@ Each task MUST have small, well-defined scope — **at most 3–5 files**.
254
253
  - File paths use globs (`src/**/*.ts`) instead of explicit names
255
254
  - Assignment says "update all" / "migrate everything" / "refactor across"
256
255
  - Scope covers entire package or directory tree
257
- **Fix:** You MUST enumerate files first (grep/glob discovery), then fan out one task per file or small cluster.
256
+ **Fix:** You MUST enumerate files first (grep/glob discovery), then fan out one task per file or small cluster.
258
257
  ---
259
258
 
260
259
  ## Parallelization