@oh-my-pi/pi-coding-agent 14.4.0 → 14.4.3

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 (67) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/package.json +7 -7
  3. package/src/cli.ts +0 -1
  4. package/src/config/prompt-templates.ts +1 -31
  5. package/src/config/settings-schema.ts +27 -37
  6. package/src/config/settings.ts +1 -1
  7. package/src/edit/index.ts +1 -53
  8. package/src/edit/line-hash.ts +13 -63
  9. package/src/edit/modes/atom.ts +334 -64
  10. package/src/edit/modes/hashline.ts +19 -26
  11. package/src/edit/renderer.ts +6 -8
  12. package/src/edit/streaming.ts +90 -114
  13. package/src/export/html/template.generated.ts +1 -1
  14. package/src/export/html/template.js +10 -15
  15. package/src/internal-urls/docs-index.generated.ts +1 -2
  16. package/src/lsp/defaults.json +142 -652
  17. package/src/modes/components/session-selector.ts +3 -3
  18. package/src/modes/components/settings-defs.ts +0 -5
  19. package/src/modes/components/tool-execution.ts +2 -5
  20. package/src/modes/controllers/btw-controller.ts +17 -105
  21. package/src/modes/controllers/todo-command-controller.ts +537 -0
  22. package/src/modes/interactive-mode.ts +35 -9
  23. package/src/modes/types.ts +2 -0
  24. package/src/modes/utils/ui-helpers.ts +17 -0
  25. package/src/prompts/system/irc-incoming.md +8 -0
  26. package/src/prompts/system/subagent-system-prompt.md +8 -0
  27. package/src/prompts/tools/ast-edit.md +1 -1
  28. package/src/prompts/tools/ast-grep.md +1 -0
  29. package/src/prompts/tools/atom.md +55 -53
  30. package/src/prompts/tools/bash.md +2 -2
  31. package/src/prompts/tools/grep.md +2 -5
  32. package/src/prompts/tools/irc.md +49 -0
  33. package/src/prompts/tools/job.md +11 -0
  34. package/src/prompts/tools/read.md +12 -13
  35. package/src/prompts/tools/task.md +1 -1
  36. package/src/prompts/tools/todo-write.md +14 -5
  37. package/src/registry/agent-registry.ts +139 -0
  38. package/src/sdk.ts +35 -0
  39. package/src/session/agent-session.ts +217 -5
  40. package/src/session/session-manager.ts +4 -1
  41. package/src/session/streaming-output.ts +1 -1
  42. package/src/slash-commands/builtin-registry.ts +24 -0
  43. package/src/task/executor.ts +14 -0
  44. package/src/tools/bash.ts +1 -1
  45. package/src/tools/fetch.ts +18 -6
  46. package/src/tools/fs-cache-invalidation.ts +0 -5
  47. package/src/tools/grep.ts +5 -125
  48. package/src/tools/index.ts +12 -6
  49. package/src/tools/irc.ts +258 -0
  50. package/src/tools/job.ts +489 -0
  51. package/src/tools/match-line-format.ts +8 -7
  52. package/src/tools/output-meta.ts +1 -1
  53. package/src/tools/read.ts +37 -131
  54. package/src/tools/renderers.ts +2 -0
  55. package/src/tools/todo-write.ts +243 -12
  56. package/src/tools/write.ts +2 -2
  57. package/src/utils/edit-mode.ts +1 -2
  58. package/src/utils/file-display-mode.ts +0 -3
  59. package/src/cli/read-cli.ts +0 -67
  60. package/src/commands/read.ts +0 -33
  61. package/src/edit/modes/chunk.ts +0 -832
  62. package/src/prompts/tools/cancel-job.md +0 -5
  63. package/src/prompts/tools/chunk-edit.md +0 -158
  64. package/src/prompts/tools/poll.md +0 -5
  65. package/src/prompts/tools/read-chunk.md +0 -73
  66. package/src/tools/cancel-job.ts +0 -95
  67. package/src/tools/poll-tool.ts +0 -173
package/CHANGELOG.md CHANGED
@@ -1,19 +1,89 @@
1
1
  # Changelog
2
2
 
3
3
  ## [Unreleased]
4
+
5
+ ## [14.4.3] - 2026-04-26
6
+ ### Added
7
+
8
+ - Added `irc` tool for agent-to-agent messaging with `list` and `send` operations, including optional broadcast to `all` and optional suppression of reply waits
9
+ - Added `irc.enabled` tool setting (default `true`) to toggle agent-to-agent messaging
10
+ - Added live in-chat rendering of IRC incoming and auto-reply messages so peer messages now appear in the session transcript
11
+
12
+ ### Changed
13
+
14
+ - Changed peer-aware prompts for subagents to include currently live agent peers and IRC usage guidance when available
15
+ - Changed the `/btw` helper to use a session-side ephemeral turn path that preserves streaming-context handling and updates the existing request handling behavior
16
+ - Merged the `poll` and `cancel_job` tools into a single `job` tool that accepts `poll` and `cancel` arrays; the renamed tool reuses the richer poll renderer for both polling and cancellation calls
17
+
18
+ ### Fixed
19
+
20
+ - Fixed IRC messaging to use a background ephemeral turn path so a recipient can reply even while its main loop is busy
21
+ - Fixed `/btw` handling of empty prompts and missing model configuration by rejecting invalid requests before starting a stream
22
+ - Fixed `/btw` request replacement so issuing a new query cleanly aborts the previous active request
23
+
24
+ ## [14.4.2] - 2026-04-26
25
+ ### Breaking Changes
26
+
27
+ - Changed `/todo append` from JSON payload input to `/todo append [<phase>] <task...>` with optional quoted tokens and automatic phase creation
28
+
29
+ ### Added
30
+
31
+ - Added `note` to todo-write operations so you can append follow-up text notes to a task via `op: "note"` and `text`
32
+ - Added markdown note-block support to `/todo export` and `/todo import` so task notes are written as blockquote lines and reloaded with the todo list
33
+ - Added `/todo export <path>` to write the current todo list as Markdown to a file, defaulting to `TODO.md` when no path is provided
34
+ - Added `/todo import <path>` to replace the current todo list from a Markdown file, defaulting to `TODO.md` when no path is provided
35
+ - Added live poll progress updates so the UI now emits intermediate job state while waiting for jobs to finish
36
+ - Added a dedicated TUI renderer for the `poll` tool that displays job status, counts, duration, and result/error previews
37
+ - Added a `/todo` slash command to view and modify todos with `edit`, `copy`, `start`, `done`, `drop`, `rm`, `append`, and `replace` operations
38
+ - Added `/todo edit` to open the current todo list in `$EDITOR` as Markdown and sync the edited checklist back into the session
39
+ - Added `/todo copy` to copy the rendered Markdown todo list to the clipboard
40
+
41
+ ### Changed
42
+
43
+ - Changed todo list rendering and summaries to show a `+N` note marker on task lines and display attached notes for tasks in progress
44
+ - Changed `/todo start`, `/todo done`, `/todo drop`, and `/todo rm` to resolve task/phase targets by fuzzy id/name matching instead of strict identifiers
45
+ - Removed `/todo replace` from supported slash commands
46
+ - Changed todo list restoration to include user todo-edit custom session entries so slash-command and editor-based todo updates persist after reload
47
+ - Restored sensible defaults for `grep.contextBefore` (1) and `grep.contextAfter` (3) so grep matches show context lines by default after the `pre`/`post` parameters were folded into settings
48
+
49
+ ### Removed
50
+
51
+ - Removed the `chunk` edit mode, chunk-aware `read` selectors, chunk-aware `grep` rendering, and the `omp read` chunk CLI subcommand
52
+ - Removed the `read.prosechunks`, `read.explorechunks`, and `read.anchorstyle` settings
53
+ - Removed the underlying `chunk` native module and AST-based chunk schema generation from `pi-natives`
54
+
55
+ ### Fixed
56
+
57
+ - Fixed poll final output to reflect live job data from the async job manager, improving status and result visibility
58
+ - Fixed job duration and output reporting to use current job snapshots instead of initial poll input metadata
59
+ - Fixed `poll` wait duration parsing to fall back to `30s` when the provided value is an empty string
60
+
61
+ ## [14.4.1] - 2026-04-26
4
62
  ### Breaking Changes
5
63
 
6
64
  - Replaced the legacy `gh_repo_view`, `gh_issue_view`, `gh_pr_view`, `gh_pr_diff`, `gh_pr_checkout`, `gh_pr_push`, `gh_run_watch`, `gh_search_issues`, and `gh_search_prs` tool names with only `github`, which requires updating existing callers that invoked the old `gh_*` tools
7
65
 
8
66
  ### Added
9
67
 
68
+ - Added a `sed` verb to the `atom` edit tool for line-local substitutions using sed-style syntax (`s/pattern/replacement/`) with `g`, `i`, and `F` flags and model-tolerant delimiter choices
10
69
  - Added the unified `github` tool with op-based dispatch for repository, issue, pull request, search, checkout, push, and Actions watch workflows
11
70
  - Added `op` routing so callers can select `repo_view`, `issue_view`, `pr_view`, `pr_diff`, `pr_checkout`, `pr_push`, `search_issues`, `search_prs`, or `run_watch` through a single tool entry point
12
71
 
13
72
  ### Changed
14
73
 
74
+ - Changed hashline-based read and match output formatting to use `LINE+ID|content` as the anchor/content separator, and updated match/context markers to `>` for matches and `:` for context
15
75
  - Updated GitHub CLI render output to show `GitHub <op>` for tool calls dispatched through `github` operations
16
76
 
77
+ ### Removed
78
+
79
+ - Removed the built-in `taplo` Language Server entry from default LSP settings, so TOML files no longer have default TOML server startup
80
+
81
+ ### Fixed
82
+
83
+ - Fixed `atom` `loc` parsing so path-qualified anchors like `path:263ti| ...` and single-anchor locs containing hyphens no longer mis-parse as ranges
84
+ - Fixed hashline anchor handling in `atom` edits so a provided content hint after the anchor (`|` or `:` suffix) can rebond a stale hash to the intended line
85
+ - Fixed `atom` `sed` execution to tolerate common model-emitted forms such as `/pat/rep/`, and to apply safe literal fallbacks for regex failures or metacharacter-heavy patterns while still erroring when no match is possible
86
+
17
87
  ## [14.4.0] - 2026-04-26
18
88
 
19
89
  ### Breaking Changes
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": "14.4.0",
4
+ "version": "14.4.3",
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",
@@ -46,12 +46,12 @@
46
46
  "dependencies": {
47
47
  "@agentclientprotocol/sdk": "0.20.0",
48
48
  "@mozilla/readability": "^0.6.0",
49
- "@oh-my-pi/omp-stats": "14.4.0",
50
- "@oh-my-pi/pi-agent-core": "14.4.0",
51
- "@oh-my-pi/pi-ai": "14.4.0",
52
- "@oh-my-pi/pi-natives": "14.4.0",
53
- "@oh-my-pi/pi-tui": "14.4.0",
54
- "@oh-my-pi/pi-utils": "14.4.0",
49
+ "@oh-my-pi/omp-stats": "14.4.3",
50
+ "@oh-my-pi/pi-agent-core": "14.4.3",
51
+ "@oh-my-pi/pi-ai": "14.4.3",
52
+ "@oh-my-pi/pi-natives": "14.4.3",
53
+ "@oh-my-pi/pi-tui": "14.4.3",
54
+ "@oh-my-pi/pi-utils": "14.4.3",
55
55
  "@sinclair/typebox": "^0.34.49",
56
56
  "@xterm/headless": "^6.0.0",
57
57
  "ajv": "^8.20.0",
package/src/cli.ts CHANGED
@@ -49,7 +49,6 @@ const commands: CommandEntry[] = [
49
49
  { name: "config", load: () => import("./commands/config").then(m => m.default) },
50
50
  { name: "grep", load: () => import("./commands/grep").then(m => m.default) },
51
51
  { name: "grievances", load: () => import("./commands/grievances").then(m => m.default) },
52
- { name: "read", load: () => import("./commands/read").then(m => m.default) },
53
52
  { name: "jupyter", load: () => import("./commands/jupyter").then(m => m.default) },
54
53
  { name: "plugin", load: () => import("./commands/plugin").then(m => m.default) },
55
54
  { name: "setup", load: () => import("./commands/setup").then(m => m.default) },
@@ -1,6 +1,5 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
- import { type ChunkAnchorStyle, formatAnchor } from "@oh-my-pi/pi-natives";
4
3
  import {
5
4
  getProjectDir,
6
5
  getProjectPromptsDir,
@@ -55,42 +54,13 @@ prompt.registerHelper("href", (lineNum: unknown, content: unknown): string => {
55
54
 
56
55
  /**
57
56
  * {{hline lineNum "content"}} — format a full read-style line with prefix.
58
- * Returns `"lineNumBIGRAM:content"` (colon between anchor and content).
57
+ * Returns `"lineNumBIGRAM|content"` (pipe between anchor and content).
59
58
  */
60
59
  prompt.registerHelper("hline", (lineNum: unknown, content: unknown): string => {
61
60
  const { ref, text } = formatHashlineRef(lineNum, content);
62
61
  return `${ref}${HASHLINE_CONTENT_SEPARATOR}${text}`;
63
62
  });
64
63
 
65
- /**
66
- * {{anchor name checksum}} — render a branch anchor tag using the current anchor style.
67
- * Style is resolved from the template context (`anchorStyle`) or defaults to "full".
68
- */
69
- prompt.registerHelper("anchor", function (this: prompt.TemplateContext, name: string, checksum: string): string {
70
- const style = (this.anchorStyle as ChunkAnchorStyle) ?? "full";
71
- return formatAnchor(name, checksum, style);
72
- });
73
-
74
- /**
75
- * {{sel "parent_Name.child_Name"}} — render a chunk path for `sel` fields in examples.
76
- * In `full` style the path is returned as-is (`class_Server.fn_start`).
77
- * In `kind` style each segment is trimmed to its kind prefix (`class.fn`).
78
- * In `bare` style the path is omitted (the model uses only `crc` to identify chunks).
79
- */
80
- prompt.registerHelper("sel", function (this: prompt.TemplateContext, chunkPath: string): string {
81
- const style = (this.anchorStyle as ChunkAnchorStyle) ?? "full";
82
- if (style === "full") return chunkPath;
83
- if (style === "bare") return "";
84
- // kind: trim each segment to its kind prefix (before the first `_`)
85
- return chunkPath
86
- .split(".")
87
- .map(seg => {
88
- const idx = seg.indexOf("_");
89
- return idx === -1 ? seg : seg.slice(0, idx);
90
- })
91
- .join(".");
92
- });
93
-
94
64
  const INLINE_ARG_SHELL_PATTERN = /\$(?:ARGUMENTS|@(?:\[\d+(?::\d*)?\])?|\d+)/;
95
65
  const INLINE_ARG_TEMPLATE_PATTERN = /\{\{[\s\S]*?(?:\b(?:arguments|ARGUMENTS|args)\b|\barg\s+[^}]+)[\s\S]*?\}\}/;
96
66
 
@@ -955,12 +955,12 @@ export const SETTINGS_SCHEMA = {
955
955
  // Edit tool
956
956
  "edit.mode": {
957
957
  type: "enum",
958
- values: ["replace", "patch", "hashline", "chunk", "vim", "apply_patch", "atom"] as const,
958
+ values: ["replace", "patch", "hashline", "vim", "apply_patch", "atom"] as const,
959
959
  default: "hashline",
960
960
  ui: {
961
961
  tab: "editing",
962
962
  label: "Edit Mode",
963
- description: "Select the edit tool variant (replace, patch, hashline, chunk, vim, or apply_patch)",
963
+ description: "Select the edit tool variant (replace, patch, hashline, vim, or apply_patch)",
964
964
  },
965
965
  },
966
966
 
@@ -1021,7 +1021,7 @@ export const SETTINGS_SCHEMA = {
1021
1021
  ui: {
1022
1022
  tab: "editing",
1023
1023
  label: "Hash Lines",
1024
- description: "Include line hashes in read output for hashline edit mode (LINE+ID\\tcontent)",
1024
+ description: "Include line hashes in read output for hashline edit mode (LINE+ID|content)",
1025
1025
  },
1026
1026
  },
1027
1027
 
@@ -1046,38 +1046,6 @@ export const SETTINGS_SCHEMA = {
1046
1046
  },
1047
1047
  },
1048
1048
 
1049
- "read.prosechunks": {
1050
- type: "boolean",
1051
- default: false,
1052
- ui: {
1053
- tab: "editing",
1054
- label: "Prose Chunks",
1055
- description: "Enable chunk rendering for prose files in chunk edit mode",
1056
- },
1057
- },
1058
-
1059
- "read.explorechunks": {
1060
- type: "boolean",
1061
- default: false,
1062
- ui: {
1063
- tab: "editing",
1064
- label: "Explore Chunks",
1065
- description: "Show chunk tree without checksums for read-only agents like explore",
1066
- },
1067
- },
1068
-
1069
- "read.anchorstyle": {
1070
- type: "enum",
1071
- values: ["full", "kind", "bare"],
1072
- default: "full",
1073
- ui: {
1074
- tab: "editing",
1075
- label: "Anchor Style",
1076
- description: "Render chunk anchors with full names, kind prefixes, or checksum-only tags",
1077
- submenu: true,
1078
- },
1079
- },
1080
-
1081
1049
  // LSP
1082
1050
  "lsp.enabled": {
1083
1051
  type: "boolean",
@@ -1238,7 +1206,7 @@ export const SETTINGS_SCHEMA = {
1238
1206
 
1239
1207
  "grep.contextBefore": {
1240
1208
  type: "number",
1241
- default: 0,
1209
+ default: 1,
1242
1210
  ui: {
1243
1211
  tab: "tools",
1244
1212
  label: "Grep Context Before",
@@ -1249,7 +1217,7 @@ export const SETTINGS_SCHEMA = {
1249
1217
 
1250
1218
  "grep.contextAfter": {
1251
1219
  type: "number",
1252
- default: 0,
1220
+ default: 3,
1253
1221
  ui: {
1254
1222
  tab: "tools",
1255
1223
  label: "Grep Context After",
@@ -1278,6 +1246,16 @@ export const SETTINGS_SCHEMA = {
1278
1246
  },
1279
1247
  },
1280
1248
 
1249
+ "irc.enabled": {
1250
+ type: "boolean",
1251
+ default: true,
1252
+ ui: {
1253
+ tab: "tools",
1254
+ label: "IRC",
1255
+ description: "Enable agent-to-agent IRC messaging via the irc tool",
1256
+ },
1257
+ },
1258
+
1281
1259
  // Optional tools
1282
1260
  "notebook.enabled": {
1283
1261
  type: "boolean",
@@ -1433,6 +1411,18 @@ export const SETTINGS_SCHEMA = {
1433
1411
  },
1434
1412
  },
1435
1413
 
1414
+ "async.pollWaitDuration": {
1415
+ type: "enum",
1416
+ values: ["5s", "10s", "30s", "1m", "5m"] as const,
1417
+ default: "30s",
1418
+ ui: {
1419
+ tab: "tools",
1420
+ label: "Poll Wait Duration",
1421
+ description: "How long the poll tool waits for background job updates before returning the current state",
1422
+ submenu: true,
1423
+ },
1424
+ },
1425
+
1436
1426
  "bash.autoBackground.enabled": {
1437
1427
  type: "boolean",
1438
1428
  default: false,
@@ -326,7 +326,7 @@ export class Settings {
326
326
 
327
327
  /**
328
328
  * Get the edit variant for a specific model.
329
- * Returns "patch", "replace", "hashline", "chunk", "vim", "apply_patch", or null (use global default).
329
+ * Returns "patch", "replace", "hashline", "vim", "apply_patch", or null (use global default).
330
330
  */
331
331
  getEditVariantForModel(model: string | undefined): EditMode | null {
332
332
  if (!model) return null;
package/src/edit/index.ts CHANGED
@@ -10,7 +10,6 @@ import {
10
10
  } from "../lsp";
11
11
  import applyPatchDescription from "../prompts/tools/apply-patch.md" with { type: "text" };
12
12
  import atomDescription from "../prompts/tools/atom.md" with { type: "text" };
13
- import chunkEditDescription from "../prompts/tools/chunk-edit.md" with { type: "text" };
14
13
  import hashlineDescription from "../prompts/tools/hashline.md" with { type: "text" };
15
14
  import patchDescription from "../prompts/tools/patch.md" with { type: "text" };
16
15
  import replaceDescription from "../prompts/tools/replace.md" with { type: "text" };
@@ -27,15 +26,6 @@ import {
27
26
  executeAtomSingle,
28
27
  resolveAtomEntryPaths,
29
28
  } from "./modes/atom";
30
- import {
31
- type ChunkParams,
32
- type ChunkToolEdit,
33
- chunkEditParamsSchema,
34
- executeChunkSingle,
35
- parseChunkEditPath,
36
- resolveAnchorStyle,
37
- resolveChunkAutoIndent,
38
- } from "./modes/chunk";
39
29
  import {
40
30
  executeHashlineSingle,
41
31
  HashlineMismatchError,
@@ -53,7 +43,6 @@ export * from "./diff";
53
43
  export * from "./line-hash";
54
44
  export * from "./modes/apply-patch";
55
45
  export * from "./modes/atom";
56
- export * from "./modes/chunk";
57
46
  export * from "./modes/hashline";
58
47
  export * from "./modes/patch";
59
48
  export * from "./modes/replace";
@@ -66,19 +55,11 @@ type TInput =
66
55
  | typeof patchEditSchema
67
56
  | typeof hashlineEditParamsSchema
68
57
  | typeof atomEditParamsSchema
69
- | typeof chunkEditParamsSchema
70
58
  | typeof vimSchema
71
59
  | typeof applyPatchSchema;
72
60
 
73
61
  type VimParams = Static<typeof vimSchema>;
74
- type EditParams =
75
- | ReplaceParams
76
- | PatchParams
77
- | HashlineParams
78
- | AtomParams
79
- | ChunkParams
80
- | VimParams
81
- | ApplyPatchParams;
62
+ type EditParams = ReplaceParams | PatchParams | HashlineParams | AtomParams | VimParams | ApplyPatchParams;
82
63
  type EditToolResultDetails = EditToolDetails | VimToolDetails;
83
64
 
84
65
  type EditModeDefinition = {
@@ -325,39 +306,6 @@ export class EditTool implements AgentTool<TInput> {
325
306
 
326
307
  #getModeDefinition(): EditModeDefinition {
327
308
  return {
328
- chunk: {
329
- description: (session: ToolSession) =>
330
- prompt.render(chunkEditDescription, {
331
- anchorStyle: resolveAnchorStyle(session.settings),
332
- chunkAutoIndent: resolveChunkAutoIndent(),
333
- }),
334
- parameters: chunkEditParamsSchema,
335
- execute: (
336
- tool: EditTool,
337
- params: EditParams,
338
- signal: AbortSignal | undefined,
339
- batchRequest: LspBatchRequest | undefined,
340
- onUpdate?: (partialResult: AgentToolResult<EditToolDetails, TInput>) => void,
341
- ) => {
342
- const { edits, path: topPath } = params as ChunkParams & { path?: string };
343
- const resolved = resolveEntryPaths(edits as ChunkToolEdit[], topPath);
344
- const byFile = groupBy(resolved, (e: ChunkToolEdit) => parseChunkEditPath(e.path).filePath);
345
- const entries = [...byFile.entries()].map(([filePath, fileEdits]) => ({
346
- path: filePath,
347
- run: (br: LspBatchRequest | undefined) =>
348
- executeChunkSingle({
349
- session: tool.session,
350
- path: filePath,
351
- edits: fileEdits,
352
- signal,
353
- batchRequest: br,
354
- writethrough: tool.#writethrough,
355
- beginDeferredDiagnosticsForPath: p => tool.#beginDeferredDiagnosticsForPath(p),
356
- }),
357
- }));
358
- return executePerFile(entries, batchRequest, onUpdate);
359
- },
360
- },
361
309
  patch: {
362
310
  description: () => prompt.render(patchDescription),
363
311
  parameters: patchEditSchema,
@@ -667,66 +667,13 @@ export const HASHLINE_BIGRAMS = [
667
667
 
668
668
  export const HASHLINE_BIGRAMS_COUNT = HASHLINE_BIGRAMS.length;
669
669
 
670
- /**
671
- * 40 common English BPE bigrams used by chunk checksums (`path#checksum`).
672
- * Kept separate from {@link HASHLINE_BIGRAMS} because the chunk checksum
673
- * format is `path#bigram1bigram2` (4 chars from a 1600-code namespace) and
674
- * is independent of the line-anchor format.
675
- *
676
- * Order is stable forever — changing it invalidates every saved chunk path.
677
- */
678
- export const CHUNK_BIGRAMS = [
679
- "th",
680
- "he",
681
- "in",
682
- "er",
683
- "an",
684
- "re",
685
- "on",
686
- "at",
687
- "en",
688
- "nd",
689
- "ti",
690
- "es",
691
- "or",
692
- "te",
693
- "of",
694
- "ed",
695
- "is",
696
- "it",
697
- "al",
698
- "ar",
699
- "st",
700
- "to",
701
- "nt",
702
- "ng",
703
- "se",
704
- "ha",
705
- "as",
706
- "ou",
707
- "io",
708
- "le",
709
- "ve",
710
- "co",
711
- "me",
712
- "de",
713
- "hi",
714
- "ri",
715
- "ro",
716
- "ic",
717
- "ne",
718
- "ea",
719
- ] as const;
720
-
721
- export const CHUNK_BIGRAMS_COUNT = CHUNK_BIGRAMS.length;
722
-
723
670
  /**
724
671
  * Regex source matching exactly one bigram from {@link HASHLINE_BIGRAMS}.
725
672
  * Used by hashline parsers — keep in sync with the alphabet array above.
726
673
  */
727
674
  export const HASHLINE_BIGRAM_RE_SRC = `(?:${HASHLINE_BIGRAMS.join("|")})`;
728
675
 
729
- export const HASHLINE_CONTENT_SEPARATOR = ":";
676
+ export const HASHLINE_CONTENT_SEPARATOR = "|";
730
677
 
731
678
  const RE_SIGNIFICANT = /[\p{L}\p{N}]/u;
732
679
 
@@ -756,11 +703,19 @@ export function formatLineHash(line: number, lines: string): string {
756
703
  return `${line}${computeLineHash(line, lines)}`;
757
704
  }
758
705
 
706
+ /**
707
+ * Formats a single line with a hashline anchor.
708
+ * Returns `LINE+ID|TEXT` (e.g., `42nd|function hi() {\n2er| return;\n3in|}`)
709
+ */
710
+ export function formatHashLine(lineNumber: number, line: string): string {
711
+ return `${lineNumber}${computeLineHash(lineNumber, line)}${HASHLINE_CONTENT_SEPARATOR}${line}`;
712
+ }
713
+
759
714
  /**
760
715
  * Format file text with hashline prefixes for display.
761
716
  *
762
- * Each line becomes `LINE+ID:TEXT` where LINENUM is 1-indexed.
763
- * No padding on line numbers; colon separator between anchor and content.
717
+ * Each line becomes `LINE+ID|TEXT` where LINENUM is 1-indexed.
718
+ * No padding on line numbers; pipe separator between anchor and content.
764
719
  *
765
720
  * @param text - Raw file text string
766
721
  * @param startLine - First line number (1-indexed, defaults to 1)
@@ -769,15 +724,10 @@ export function formatLineHash(line: number, lines: string): string {
769
724
  * @example
770
725
  * ```
771
726
  * formatHashLines("function hi() {\n return;\n}")
772
- * // "1th:function hi() {\n2er: return;\n3in:}"
727
+ * // "1th|function hi() {\n2er| return;\n3in|}"
773
728
  * ```
774
729
  */
775
730
  export function formatHashLines(text: string, startLine = 1): string {
776
731
  const lines = text.split("\n");
777
- return lines
778
- .map((line, i) => {
779
- const num = startLine + i;
780
- return `${formatLineHash(num, line)}${HASHLINE_CONTENT_SEPARATOR}${line}`;
781
- })
782
- .join("\n");
732
+ return lines.map((line, i) => formatHashLine(startLine + i, line)).join("\n");
783
733
  }