@oh-my-pi/pi-coding-agent 14.4.0 → 14.4.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 +14 -0
- package/package.json +7 -7
- package/src/config/prompt-templates.ts +1 -1
- package/src/config/settings-schema.ts +1 -1
- package/src/edit/line-hash.ts +13 -10
- package/src/edit/modes/atom.ts +264 -29
- package/src/edit/modes/hashline.ts +17 -22
- package/src/lsp/defaults.json +142 -652
- package/src/modes/components/session-selector.ts +3 -3
- package/src/prompts/tools/ast-edit.md +1 -1
- package/src/prompts/tools/ast-grep.md +1 -0
- package/src/prompts/tools/atom.md +29 -38
- package/src/prompts/tools/grep.md +2 -2
- package/src/prompts/tools/read.md +1 -1
- package/src/session/session-manager.ts +4 -1
- package/src/tools/grep.ts +2 -2
- package/src/tools/match-line-format.ts +3 -3
- package/src/tools/read.ts +1 -5
- package/src/tools/write.ts +2 -2
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
truncateToWidth,
|
|
11
11
|
visibleWidth,
|
|
12
12
|
} from "@oh-my-pi/pi-tui";
|
|
13
|
+
import { formatBytes } from "@oh-my-pi/pi-utils";
|
|
13
14
|
import { theme } from "../../modes/theme/theme";
|
|
14
15
|
import { matchesAppInterrupt } from "../../modes/utils/keybinding-matchers";
|
|
15
16
|
import type { SessionInfo } from "../../session/session-manager";
|
|
@@ -157,10 +158,9 @@ class SessionList implements Component {
|
|
|
157
158
|
lines.push(messageLine);
|
|
158
159
|
}
|
|
159
160
|
|
|
160
|
-
// Metadata line: date +
|
|
161
|
+
// Metadata line: date + file size
|
|
161
162
|
const modified = formatDate(session.modified);
|
|
162
|
-
const
|
|
163
|
-
const metadata = ` ${modified} ${theme.sep.dot} ${msgCount}`;
|
|
163
|
+
const metadata = ` ${modified} ${theme.sep.dot} ${formatBytes(session.size)}`;
|
|
164
164
|
const metadataLine = theme.fg("dim", truncateToWidth(metadata, width));
|
|
165
165
|
|
|
166
166
|
lines.push(metadataLine);
|
|
@@ -14,7 +14,7 @@ Performs structural AST-aware rewrites via native ast-grep.
|
|
|
14
14
|
</instruction>
|
|
15
15
|
|
|
16
16
|
<output>
|
|
17
|
-
- Replacement summary, per-file replacement counts, and change diffs
|
|
17
|
+
- Replacement summary, per-file replacement counts, and change diffs as `-LINE+ID|before` / `+LINE+ID|after` lines
|
|
18
18
|
- Parse issues when files cannot be processed
|
|
19
19
|
</output>
|
|
20
20
|
|
|
@@ -18,6 +18,7 @@ Performs structural code search using AST matching via native ast-grep.
|
|
|
18
18
|
|
|
19
19
|
<output>
|
|
20
20
|
- Grouped matches with file path, byte range, line/column ranges, metavariable captures
|
|
21
|
+
- Match lines are anchor-prefixed: `LINE+ID>content` for the matched line and `LINE+ID:content` for surrounding context
|
|
21
22
|
- Summary counts (`totalMatches`, `filesWithMatches`, `filesSearched`) and parse issues when present
|
|
22
23
|
</output>
|
|
23
24
|
|
|
@@ -15,6 +15,10 @@ Verbs:
|
|
|
15
15
|
- `set: ["…"]` — replace the anchor line
|
|
16
16
|
- `pre: ["…"]` — insert before the anchor line (or at BOF when `loc:"^"`)
|
|
17
17
|
- `post: ["…"]` — insert after the anchor line (or at EOF when `loc:"$"`)
|
|
18
|
+
- `sed: "s/foo/bar/"` — sed-style substitution applied to the anchor line. **Prefer this over `set` for token-level changes**
|
|
19
|
+
Flags: `g` (all occurrences), `i` (case-insensitive), `F` (literal/fixed-string, no regex).
|
|
20
|
+
Delimiter is whatever character follows `s`.
|
|
21
|
+
You **MUST** keep the pattern as short as possible.
|
|
18
22
|
|
|
19
23
|
Combination rules:
|
|
20
24
|
- On a single-anchor `loc`, you may combine `pre`, `set`, and `post` in the same entry.
|
|
@@ -26,60 +30,45 @@ Combination rules:
|
|
|
26
30
|
All examples below reference the same file:
|
|
27
31
|
|
|
28
32
|
```ts title="a.ts"
|
|
29
|
-
{{hline
|
|
30
|
-
{{hline
|
|
31
|
-
{{hline
|
|
32
|
-
{{hline
|
|
33
|
-
{{hline
|
|
34
|
-
{{hline
|
|
35
|
-
{{hline
|
|
36
|
-
{{hline
|
|
37
|
-
{{hline 9 "function beta(x) {"}}
|
|
38
|
-
{{hline 10 "\tif (x) {"}}
|
|
39
|
-
{{hline 11 "\t\treturn parse(data);"}}
|
|
40
|
-
{{hline 12 "\t}"}}
|
|
41
|
-
{{hline 13 "\treturn null;"}}
|
|
42
|
-
{{hline 14 "}"}}
|
|
33
|
+
{{hline 1 "const tag = \"BAD\";"}}
|
|
34
|
+
{{hline 2 ""}}
|
|
35
|
+
{{hline 3 "function beta(x) {"}}
|
|
36
|
+
{{hline 4 "\tif (x) {"}}
|
|
37
|
+
{{hline 5 "\t\treturn parse(data) || fallback;"}}
|
|
38
|
+
{{hline 6 "\t}"}}
|
|
39
|
+
{{hline 7 "\treturn null;"}}
|
|
40
|
+
{{hline 8 "}"}}
|
|
43
41
|
```
|
|
44
42
|
|
|
45
|
-
#
|
|
46
|
-
|
|
47
|
-
`{path:"a.ts",edits:[{loc:{{href 4 "const fallback = group.targetFramework || 'All Frameworks';"}},set:["const fallback = group.targetFramework ?? 'All Frameworks';"]}]}`
|
|
48
|
-
|
|
49
|
-
# Flip a literal by replacing the line
|
|
50
|
-
Original line 2: `const timeout = 5000;`
|
|
51
|
-
`{path:"a.ts",edits:[{loc:{{href 2 "const timeout = 5000;"}},set:["const timeout = 30_000;"]}]}`
|
|
52
|
-
|
|
53
|
-
# Negate a condition by replacing the line
|
|
54
|
-
Original line 10: `\tif (x) {`
|
|
55
|
-
`{path:"a.ts",edits:[{loc:{{href 10 "\tif (x) {"}},set:["\tif (!x) {"]}]}`
|
|
43
|
+
# Replace a line with `set`
|
|
44
|
+
`{path:"a.ts",edits:[{loc:{{href 1 "const tag = \"BAD\";"}},set:["const tag = \"OK\";"]}]}`
|
|
56
45
|
|
|
57
46
|
# Combine `pre` + `set` + `post` in one entry
|
|
58
|
-
`{path:"a.ts",edits:[{loc:{{href
|
|
59
|
-
|
|
60
|
-
# Replace one whole line with `set`
|
|
61
|
-
Use `set` to replace the full anchored line, preserving any unchanged surrounding lines yourself.
|
|
62
|
-
`{path:"a.ts",edits:[{loc:{{href 3 "const tag = \"DO NOT SHIP\";"}},set:["const tag = \"OK\";"]}]}`
|
|
63
|
-
|
|
64
|
-
# Replace multiple non-adjacent lines
|
|
65
|
-
`{path:"a.ts",edits:[{loc:{{href 11 "\t\treturn parse(data);"}},set:["\t\treturn parse(data) ?? fallback;"]},{loc:{{href 13 "\treturn null;"}},set:["\treturn fallback;"]}]}`
|
|
47
|
+
`{path:"a.ts",edits:[{loc:{{href 4 "\tif (x) {"}},pre:["\tvalidate();"],set:["\tif (!x) {"],post:["\t\tlog();"]}]}`
|
|
66
48
|
|
|
67
49
|
# Delete a line with `set: []`
|
|
68
|
-
`{path:"a.ts",edits:[{loc:{{href
|
|
50
|
+
`{path:"a.ts",edits:[{loc:{{href 7 "\treturn null;"}},set:[]}]}`
|
|
69
51
|
|
|
70
52
|
# Preserve a blank line with `set:[""]`
|
|
71
|
-
`{path:"a.ts",edits:[{loc:{{href
|
|
53
|
+
`{path:"a.ts",edits:[{loc:{{href 2 ""}},set:[""]}]}`
|
|
72
54
|
|
|
73
55
|
# Insert before / after a line
|
|
74
|
-
`{path:"a.ts",edits:[{loc:{{href
|
|
75
|
-
|
|
56
|
+
`{path:"a.ts",edits:[{loc:{{href 3 "function beta(x) {"}},pre:["function gamma() {","\tvalidate();","}",""]}]}`
|
|
57
|
+
|
|
58
|
+
# Substitute one token with `sed` (regex) — preferred for token-level edits
|
|
59
|
+
Use the smallest pattern that uniquely identifies the change.
|
|
60
|
+
`{path:"a.ts",edits:[{loc:{{href 5 "\t\treturn parse(data) || fallback;"}},sed:"s/\\|\\|/??/"}]}`
|
|
61
|
+
|
|
62
|
+
# Substitute every occurrence with `sed` (literal/fixed-string)
|
|
63
|
+
Use the `F` flag to disable regex; the delimiter can be any non-alphanumeric char.
|
|
64
|
+
`{path:"a.ts",edits:[{loc:{{href 5 "\t\treturn parse(data) || fallback;"}},sed:"s|data|input|gF"}]}`
|
|
76
65
|
|
|
77
66
|
# Prepend / append at file edges
|
|
78
67
|
`{path:"a.ts",edits:[{loc:"^",pre:["// Copyright (c) 2026",""]}]}`
|
|
79
68
|
`{path:"a.ts",edits:[{loc:"$",post:["","export const VERSION = \"1.0.0\";"]}]}`
|
|
80
69
|
|
|
81
70
|
# Cross-file override inside `loc`
|
|
82
|
-
`{path:"a.ts",edits:[{loc:"b.ts:{{href
|
|
71
|
+
`{path:"a.ts",edits:[{loc:"b.ts:{{href 1 "const tag = \"BAD\";"}}",set:["const tag = \"OK\";"]}]}`
|
|
83
72
|
</examples>
|
|
84
73
|
|
|
85
74
|
<critical>
|
|
@@ -91,6 +80,8 @@ Use `set` to replace the full anchored line, preserving any unchanged surroundin
|
|
|
91
80
|
- `set: []` deletes the anchored line. `set:[""]` preserves a blank line.
|
|
92
81
|
- Within a single request you may submit edits in any order — the runtime applies them bottom-up so they don't shift each other. After any request that mutates a file, anchors below the mutation are stale on disk; re-read before issuing more edits to that file.
|
|
93
82
|
- `set` operations target the current file content only. Do not try to reference old line text after the file has changed.
|
|
83
|
+
- For token-level edits, prefer `sed` over `set`. The `loc` anchor already pins the line — repeating the entire line in a `set` array invites hallucinated content. Use the smallest `sed` pattern that uniquely identifies the change on that line; do not pad it with surrounding text just to feel safe.
|
|
84
|
+
- When you do use `set`, re-read the anchored line first and copy it verbatim, changing only the required token(s). Anchor identity does not verify line content, so a hallucinated replacement will silently corrupt the file.
|
|
94
85
|
- Text content must be literal file content with matching indentation. If the file uses tabs, use real tabs.
|
|
95
86
|
- You **MUST NOT** use this tool to reformat or clean up unrelated code.
|
|
96
87
|
</critical>
|
|
@@ -8,14 +8,14 @@ Searches files using powerful regex matching.
|
|
|
8
8
|
|
|
9
9
|
<output>
|
|
10
10
|
{{#if IS_HASHLINE_MODE}}
|
|
11
|
-
- Text output is anchor-prefixed: `123th
|
|
11
|
+
- Text output is anchor-prefixed: `123th>content` (match) or `123th:content` (context). The 2-letter ID is a content fingerprint.
|
|
12
12
|
{{else}}
|
|
13
13
|
{{#if IS_LINE_NUMBER_MODE}}
|
|
14
14
|
- Text output is line-number-prefixed
|
|
15
15
|
{{/if}}
|
|
16
16
|
{{/if}}
|
|
17
17
|
{{#if IS_CHUNK_MODE}}
|
|
18
|
-
- Text output is chunk-path-prefixed: `path:sel>
|
|
18
|
+
- Text output is chunk-path-prefixed: `path:sel>123|content`
|
|
19
19
|
{{/if}}
|
|
20
20
|
</output>
|
|
21
21
|
|
|
@@ -24,7 +24,7 @@ Max {{DEFAULT_MAX_LINES}} lines per call.
|
|
|
24
24
|
|
|
25
25
|
# Filesystem
|
|
26
26
|
{{#if IS_HASHLINE_MODE}}
|
|
27
|
-
- Reading from FS returns lines prefixed with anchors: `41th
|
|
27
|
+
- Reading from FS returns lines prefixed with anchors: `41th|def alpha():` (line number, 2-letter ID, pipe, then content)
|
|
28
28
|
{{else}}
|
|
29
29
|
{{#if IS_LINE_NUMBER_MODE}}
|
|
30
30
|
- Reading from FS returns lines prefixed with line numbers: `41:def alpha():`
|
|
@@ -259,6 +259,8 @@ export interface SessionInfo {
|
|
|
259
259
|
created: Date;
|
|
260
260
|
modified: Date;
|
|
261
261
|
messageCount: number;
|
|
262
|
+
/** File size in bytes on disk; used for compact list rendering. */
|
|
263
|
+
size: number;
|
|
262
264
|
firstMessage: string;
|
|
263
265
|
allMessagesText: string;
|
|
264
266
|
}
|
|
@@ -1264,7 +1266,7 @@ function extractTextFromContent(content: Message["content"]): string {
|
|
|
1264
1266
|
.join(" ");
|
|
1265
1267
|
}
|
|
1266
1268
|
|
|
1267
|
-
const SESSION_LIST_PREFIX_BYTES =
|
|
1269
|
+
const SESSION_LIST_PREFIX_BYTES = 4096;
|
|
1268
1270
|
const SESSION_LIST_PARALLEL_THRESHOLD = 64;
|
|
1269
1271
|
const SESSION_LIST_MAX_WORKERS = 16;
|
|
1270
1272
|
const sessionListPrefixDecoder = new TextDecoder("utf-8", { fatal: false });
|
|
@@ -1466,6 +1468,7 @@ async function collectSessionFromFile(
|
|
|
1466
1468
|
created: new Date(header.timestamp ?? ""),
|
|
1467
1469
|
modified: stats.mtime,
|
|
1468
1470
|
messageCount,
|
|
1471
|
+
size: stats.size,
|
|
1469
1472
|
firstMessage: firstMessage || "(no messages)",
|
|
1470
1473
|
allMessagesText: allMessages.length > 0 ? allMessages.join(" ") : firstMessage,
|
|
1471
1474
|
};
|
package/src/tools/grep.ts
CHANGED
|
@@ -64,7 +64,7 @@ export interface GrepToolDetails {
|
|
|
64
64
|
truncated?: boolean;
|
|
65
65
|
error?: string;
|
|
66
66
|
/** Pre-formatted text for the user-visible TUI render. Mirrors the model-facing
|
|
67
|
-
* `result.text` lines but uses a `│` gutter and `*` to mark match lines (vs
|
|
67
|
+
* `result.text` lines but uses a `│` gutter and `*` to mark match lines (vs space for
|
|
68
68
|
* context). The TUI uses this directly so it never parses model-facing hashline anchors. */
|
|
69
69
|
displayContent?: string;
|
|
70
70
|
}
|
|
@@ -502,7 +502,7 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
502
502
|
}
|
|
503
503
|
}
|
|
504
504
|
if (hasContextLines && outputLines.length > 0) {
|
|
505
|
-
outputLines.unshift("[grep] match lines use '
|
|
505
|
+
outputLines.unshift("[grep] match lines use '>'; context lines use ':'.");
|
|
506
506
|
}
|
|
507
507
|
if (matchLimitReached || result.limitReached) {
|
|
508
508
|
outputLines.push("", limitMessage);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { computeLineHash
|
|
1
|
+
import { computeLineHash } from "../edit/line-hash";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Format a single line of match output for grep/ast-grep style results.
|
|
5
5
|
*
|
|
6
|
-
* Match lines use
|
|
6
|
+
* Match lines use `>` as the anchor/content separator; context lines use `:`.
|
|
7
7
|
* In hashline mode the anchor is `LINE+ID` (no `#`); in plain mode it is
|
|
8
8
|
* just the line number. Line numbers are never padded.
|
|
9
9
|
*/
|
|
@@ -13,7 +13,7 @@ export function formatMatchLine(
|
|
|
13
13
|
isMatch: boolean,
|
|
14
14
|
options: { useHashLines: boolean },
|
|
15
15
|
): string {
|
|
16
|
-
const separator = isMatch ?
|
|
16
|
+
const separator = isMatch ? ">" : ":";
|
|
17
17
|
if (options.useHashLines) {
|
|
18
18
|
return `${lineNumber}${computeLineHash(lineNumber, line)}${separator}${line}`;
|
|
19
19
|
}
|
package/src/tools/read.ts
CHANGED
|
@@ -92,17 +92,13 @@ function prependLineNumbers(text: string, startNum: number): string {
|
|
|
92
92
|
return textLines.map((line, i) => `${startNum + i}|${line}`).join("\n");
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
function prependHashLines(text: string, startNum: number): string {
|
|
96
|
-
return formatHashLines(text, startNum);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
95
|
function formatTextWithMode(
|
|
100
96
|
text: string,
|
|
101
97
|
startNum: number,
|
|
102
98
|
shouldAddHashLines: boolean,
|
|
103
99
|
shouldAddLineNumbers: boolean,
|
|
104
100
|
): string {
|
|
105
|
-
if (shouldAddHashLines) return
|
|
101
|
+
if (shouldAddHashLines) return formatHashLines(text, startNum);
|
|
106
102
|
if (shouldAddLineNumbers) return prependLineNumbers(text, startNum);
|
|
107
103
|
return text;
|
|
108
104
|
}
|
package/src/tools/write.ts
CHANGED
|
@@ -59,7 +59,7 @@ export interface WriteToolDetails {
|
|
|
59
59
|
/**
|
|
60
60
|
* Strip hashline display prefixes from write content.
|
|
61
61
|
*
|
|
62
|
-
* Only active when hashline edit mode is enabled — the model sees `LINE+ID
|
|
62
|
+
* Only active when hashline edit mode is enabled — the model sees `LINE+ID|`
|
|
63
63
|
* prefixes in read output and sometimes copies them into write content.
|
|
64
64
|
*/
|
|
65
65
|
function stripWriteContent(session: ToolSession, content: string): { text: string; stripped: boolean } {
|
|
@@ -418,7 +418,7 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
418
418
|
context?: AgentToolContext,
|
|
419
419
|
): Promise<AgentToolResult<WriteToolDetails>> {
|
|
420
420
|
return untilAborted(signal, async () => {
|
|
421
|
-
// Strip hashline display prefixes (LINE+ID
|
|
421
|
+
// Strip hashline display prefixes (LINE+ID|) if the model copied them from read output
|
|
422
422
|
const { text: cleanContent, stripped } = stripWriteContent(this.session, content);
|
|
423
423
|
const resolvedArchivePath = await this.#resolveArchiveWritePath(path);
|
|
424
424
|
if (resolvedArchivePath) {
|