@astrosheep/pi-context 0.22.1 → 0.23.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.
@@ -71,25 +71,19 @@ function assertWithinBudget(result, label) {
71
71
  function appendText(sessionManager, text) {
72
72
  return sessionManager.appendMessage({ role: "user", content: [{ type: "text", text }], timestamp: Date.now() });
73
73
  }
74
- /**
75
- * Decode a raw read (notes_read / history_read): a one-line bracketed header, then
76
- * the payload verbatim (which may itself contain newlines), so split on the first newline only.
77
- */
74
+ /** Decode either raw read through the shared READ WINDOW grammar without including metadata in the payload. */
78
75
  function resultRead(result) {
79
76
  const text = result.content[0];
80
77
  assert.ok(text && text.type === "text", "read result carries text");
81
- const newline = text.text.indexOf("\n");
82
- assert.ok(newline !== -1, "raw read carries a header line and a payload");
83
- const header = text.text.slice(0, newline);
84
- const content = text.text.slice(newline + 1);
85
- assert.match(header, /^\[/, "the header is bracketed");
86
- assert.match(header, /\]$/, "the header closes its bracket");
87
- const match = header.match(/ · chars (\d+)-(\d+) of (\d+) · (end|continue at offset_chars=(\d+))/);
88
- assert.ok(match, `read header names the char range and resume cursor: ${header}`);
89
- const offset_chars = Number(match[1]);
90
- const total_chars = Number(match[3]);
91
- const next_offset_chars = match[4] === "end" ? null : Number(match[5]);
92
- assert.equal([...content].length, Number(match[2]) - offset_chars, "the header range matches the delivered payload");
78
+ const block = /^(--- READ WINDOW ---\n(?:[a-z_]+: [^\n]*\n)+chars: \[(\d+),(\d+)\) of (\d+)\nnext_offset_chars: (null|\d+)\n)\n/.exec(text.text);
79
+ assert.ok(block, "raw read carries one READ WINDOW block followed by exactly one blank line");
80
+ const header = block[1];
81
+ const content = text.text.slice(block[0].length);
82
+ const offset_chars = Number(block[2]);
83
+ const end = Number(block[3]);
84
+ const total_chars = Number(block[4]);
85
+ const next_offset_chars = block[5] === "null" ? null : Number(block[5]);
86
+ assert.equal([...content].length, end - offset_chars, "READ WINDOW range matches the delivered payload");
93
87
  return { header, content, offset_chars, total_chars, next_offset_chars };
94
88
  }
95
89
  const PROFILES = [
@@ -213,18 +207,17 @@ test("coherence: following the returned cursors reconstructs the original text e
213
207
  const searched = resultJson(await call(captured, "notes_search", { query: "历", pattern: "huge-cjk.md" }, ctx));
214
208
  const matchedFile = searched.files[0];
215
209
  const matched = matchedFile?.matches[0];
216
- report.push(`notes_search: matches_total=${String(matchedFile?.matches_total)} returned=${String(matchedFile?.matches.length)} first match delivered ${codePoints(matched?.text ?? "")} of ${String(matched?.total_chars)} chars, truncated=${String(matched?.truncated)}`);
210
+ report.push(`notes_search: matches_total=${String(matchedFile?.matches_total)} returned=${String(matchedFile?.matches.length)} first match delivered ${codePoints(matched?.text ?? "")} chars, truncated=${String(matched?.truncated)}`);
217
211
  if (!matched)
218
212
  failures.push("notes_search dropped the over-budget matched line entirely");
219
213
  else {
220
214
  if (matched.truncated !== true)
221
215
  failures.push("notes_search does not flag the over-budget matched line as truncated");
222
- if (matched.total_chars !== codePoints(hugeCjkLine))
223
- failures.push(`notes_search match total_chars=${matched.total_chars}, expected ${codePoints(hugeCjkLine)}`);
224
216
  if (!hugeCjkLine.startsWith(matched.text))
225
217
  failures.push("notes_search delivered a non-prefix of the matched line");
226
- if (codePoints(matched.text) >= matched.total_chars)
227
- failures.push("notes_search claims the over-budget line fits in one response");
218
+ const atMatch = resultRead(await call(captured, "notes_read", { path: "huge-cjk.md", offset_chars: matched.offset_chars }, ctx));
219
+ if (!atMatch.content.startsWith(""))
220
+ failures.push("notes_search offset does not start notes_read at the matched substring");
228
221
  const walked = stripLeadingFrontmatter(await walkNote(captured, ctx, "huge-cjk.md"));
229
222
  if (walked !== `${hugeCjkLine}\ntail line`)
230
223
  failures.push(`notes_search match line is not reconstructible from the note read: missing ${codePoints(`${hugeCjkLine}\ntail line`) - codePoints(walked)} chars`);
@@ -309,27 +302,30 @@ test("coherence: following the returned cursors reconstructs the original text e
309
302
  if (hugeEntry.matches[0]?.truncated !== true)
310
303
  failures.push("huge-many: the kept match is not flagged as a prefix");
311
304
  }
312
- // --- notes_search addresses: a match's offset_chars is the body-absolute code-point
313
- // position of the earliest query occurrence in its line, so search → read composes exactly like
314
- // history's match_offset_chars two-stage.
305
+ // --- notes_search addresses: each match offset directly starts notes_read at the earliest
306
+ // query occurrence in its line, including multi-query OR.
315
307
  const addressLine1 = "pad ".repeat(50);
316
308
  const addressLine3 = `${"历".repeat(20)}needle-address here`;
317
309
  const addressLine4 = "zeta 历 needle-address";
318
310
  await call(captured, "notes_write", { path: "address.md", content: `${addressLine1}\nsecond\n${addressLine3}\n${addressLine4}` }, ctx);
319
- const expectedAddress = codePoints(addressLine1) + 1 + codePoints("second") + 1 + 20;
320
311
  const addressHit = resultJson(await call(captured, "notes_search", { query: "needle-address", pattern: "address.md" }, ctx)).files[0]?.matches.find((match) => match.line === 3);
321
- report.push(`notes_search address: line=${String(addressHit?.line)} offset_chars=${String(addressHit?.offset_chars)} expected=${expectedAddress}`);
322
- const addressOffset = addressHit?.offset_chars;
323
- if (typeof addressOffset !== "number")
324
- failures.push("notes_search carries no offset_chars");
325
- else if (addressOffset !== expectedAddress)
326
- failures.push(`notes_search offset_chars=${addressOffset}, expected ${expectedAddress} (body-absolute, at the query)`);
327
- // Multi-query OR: a line's address is the earliest occurrence of any query inside that line.
328
- const line4Base = expectedAddress - 20 + codePoints(addressLine3) + 1;
312
+ report.push(`notes_search address: line=${String(addressHit?.line)} offset_chars=${String(addressHit?.offset_chars)}`);
313
+ if (addressHit === undefined)
314
+ failures.push("notes_search carries no line-three match");
315
+ else {
316
+ const atMatch = resultRead(await call(captured, "notes_read", { path: "address.md", offset_chars: addressHit.offset_chars }, ctx));
317
+ if (!atMatch.content.startsWith("needle-address"))
318
+ failures.push(`notes_search offset_chars=${addressHit.offset_chars} does not start at the line-three query`);
319
+ }
329
320
  const orLine4 = resultJson(await call(captured, "notes_search", { query: ["needle-address", "zeta"], pattern: "address.md" }, ctx)).files[0]?.matches.find((match) => match.line === 4);
330
- report.push(`notes_search OR address: offset_chars=${String(orLine4?.offset_chars)} expected=${line4Base}`);
331
- if (orLine4?.offset_chars !== line4Base)
332
- failures.push(`notes_search OR offset_chars=${String(orLine4?.offset_chars)}, expected ${line4Base} (earliest of any query)`);
321
+ report.push(`notes_search OR address: offset_chars=${String(orLine4?.offset_chars)}`);
322
+ if (orLine4 === undefined)
323
+ failures.push("notes_search carries no line-four OR match");
324
+ else {
325
+ const atMatch = resultRead(await call(captured, "notes_read", { path: "address.md", offset_chars: orLine4.offset_chars }, ctx));
326
+ if (!atMatch.content.startsWith("zeta"))
327
+ failures.push(`notes_search OR offset_chars=${orLine4.offset_chars} does not start at the earliest line-four query`);
328
+ }
333
329
  // --- Negative offsets on both stores: a tail read reaches the end in one call, the response
334
330
  // echoes the resolved absolute offset, N >= total_chars reads from the start, and the cursor
335
331
  // law still holds when a negative-start page is cut short.
@@ -1,7 +1,7 @@
1
1
  import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
- import { execFile, execFileSync } from "node:child_process";
4
- import { existsSync, linkSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, symlinkSync, unlinkSync, utimesSync, writeFileSync } from "node:fs";
3
+ import { execFile, execFileSync, spawnSync } from "node:child_process";
4
+ import { cpSync, existsSync, linkSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, symlinkSync, unlinkSync, utimesSync, writeFileSync } from "node:fs";
5
5
  import { mkdtempSync } from "node:fs";
6
6
  import { tmpdir } from "node:os";
7
7
  import { join } from "node:path";
@@ -225,14 +225,34 @@ test("dreamer session has exactly the jailed file-tool allowlist", async () => {
225
225
  session.dispose();
226
226
  }
227
227
  });
228
- test("playbook describes plain files and the retained frontmatter", () => {
228
+ test("playbook describes plain files, retained frontmatter, and the read-only session WAL", () => {
229
229
  const playbook = readFileSync(join(process.cwd(), "playbook.md"), "utf8");
230
230
  assert.equal(playbook.includes("notes_"), false);
231
231
  for (const field of ["origin", "status", "stale", "created_at", "updated_at", "last_accessed", "access_count"])
232
232
  assert.match(playbook, new RegExp(`^${field}:`, "m"));
233
233
  assert.equal(/^scope:/m.test(playbook), false, "scope is derived from the address rather than persisted");
234
+ assert.match(playbook, /`pi\/session\/\*\*` is a live agent's write-ahead log/);
235
+ assert.match(playbook, /never write or edit anything there/);
236
+ assert.match(playbook, /Session notes remain untouched even when promoted/);
234
237
  assert.match(playbook, /Nothing is physically deleted/);
235
238
  });
239
+ test("CLI finds its package root when the installed file URL contains spaces", () => {
240
+ const install = mkdtempSync(join(tmpdir(), "dream install "));
241
+ const home = fixture();
242
+ try {
243
+ cpSync(join(process.cwd(), "dist/src"), join(install, "dist/src"), { recursive: true });
244
+ symlinkSync(join(process.cwd(), "node_modules"), join(install, "node_modules"), process.platform === "win32" ? "junction" : "dir");
245
+ cpSync(join(process.cwd(), "playbook.md"), join(install, "playbook.md"));
246
+ writeFileSync(join(install, "package.json"), JSON.stringify({ type: "module" }));
247
+ const result = spawnSync(process.execPath, [join(install, "dist/src/dream/cli.js"), "--notes-home", home, "--force", "--dreamer", "definitely-not-a-real-model"], { encoding: "utf8" });
248
+ assert.equal(result.status, 1, result.stderr);
249
+ assert.doesNotMatch(result.stderr, /could not locate installed package root/);
250
+ assert.match(result.stderr, /definitely-not-a-real-model/);
251
+ }
252
+ finally {
253
+ rmSync(install, { recursive: true, force: true });
254
+ }
255
+ });
236
256
  test("provider errors are reported with partial writes instead of parsing a response", async () => {
237
257
  const factory = scriptedSession((handler) => {
238
258
  handler({ type: "tool_execution_start", toolName: "write", args: { path: "global/partial.md", content: "half" } });
@@ -152,26 +152,19 @@ export function assertWithinBudget(result, message) {
152
152
  const bytes = text && text.type === "text" ? Buffer.byteLength(text.text, "utf8") : 0;
153
153
  assert.ok(bytes <= TOOL_OUTPUT_MAX_BYTES, `${message}: ${bytes} bytes over the ${TOOL_OUTPUT_MAX_BYTES}-byte budget`);
154
154
  }
155
- /**
156
- * Decode a raw read (notes_read / history_read): a one-line bracketed header, then
157
- * the payload verbatim (which may itself contain newlines), so split on the first newline only.
158
- */
155
+ /** Decode either raw read without including its shared metadata block in the payload. */
159
156
  export function resultRead(result) {
160
157
  const text = result.content[0];
161
158
  assert.ok(text && text.type === "text", "read result carries text");
162
- const newline = text.text.indexOf("\n");
163
- assert.ok(newline !== -1, "raw read carries a header line and a payload");
164
- const header = text.text.slice(0, newline);
165
- const content = text.text.slice(newline + 1);
166
- assert.match(header, /^\[/, "the header is bracketed");
167
- assert.match(header, /\]$/, "the header closes its bracket");
168
- const match = header.match(/ · chars (\d+)-(\d+) of (\d+) · (end|continue at offset_chars=(\d+))/);
169
- assert.ok(match, `read header names the char range and resume cursor: ${header}`);
170
- const offset_chars = Number(match[1]);
171
- const end = Number(match[2]);
172
- const total_chars = Number(match[3]);
173
- const next_offset_chars = match[4] === "end" ? null : Number(match[5]);
174
- assert.equal(Array.from(content).length, end - offset_chars, "the header range matches the delivered payload");
159
+ const block = /^(--- READ WINDOW ---\n(?:[a-z_]+: [^\n]*\n)+chars: \[(\d+),(\d+)\) of (\d+)\nnext_offset_chars: (null|\d+)\n)\n/.exec(text.text);
160
+ assert.ok(block, "raw read carries one READ WINDOW block followed by exactly one blank line");
161
+ const header = block[1];
162
+ const content = text.text.slice(block[0].length);
163
+ const offset_chars = Number(block[2]);
164
+ const end = Number(block[3]);
165
+ const total_chars = Number(block[4]);
166
+ const next_offset_chars = block[5] === "null" ? null : Number(block[5]);
167
+ assert.equal(Array.from(content).length, end - offset_chars, "READ WINDOW range matches the delivered payload");
175
168
  return { header, content, offset_chars, total_chars, next_offset_chars, details: (result.details ?? {}) };
176
169
  }
177
170
  /**
@@ -328,7 +321,7 @@ test("notes_list is most-recently-updated first across merged scopes", async ()
328
321
  assert.deepEqual((await files({})).map((file) => file.address), ["@personal/e.md", "a.md", "b.md", "@project/c.md"], "updated_at descending with address ascending as the tiebreak");
329
322
  // A same-path pair in two scopes keeps both rows; equal timestamps tie-break by scope name.
330
323
  put("personal", "a.md", base + 10);
331
- assert.deepEqual((await files({})).filter((file) => file.address.endsWith("a.md")).map((file) => file.scope), ["personal", "session"], "equal timestamps tie-break by full address");
324
+ assert.deepEqual((await files({})).filter((file) => file.address.endsWith("a.md")).map((file) => file.address), ["@personal/a.md", "a.md"], "equal timestamps tie-break by full address");
332
325
  assert.deepEqual((await files({ pattern: "*.md" })).map((file) => file.address), ["a.md", "b.md"], "a bare pattern narrows to the session home");
333
326
  });
334
327
  test("notes are real files that persist across sessions and round-trip Unicode", async () => {
@@ -345,7 +338,6 @@ test("notes are real files that persist across sessions and round-trip Unicode",
345
338
  const read = resultRead(rawRead);
346
339
  assert.equal(read.details.address, "@personal/checkpoint/进度.md");
347
340
  assert.equal(read.content, "Café", "a negative offset reads the body tail in one call");
348
- assert.equal(read.details.scope, "personal");
349
341
  const searched = resultJson(await call(restoredCaptured, "notes_search", { query: "Café", scope: "personal" }, restoredCtx));
350
342
  assert.equal(searched.files[0]?.matches[0]?.line, 2);
351
343
  const listedFiles = resultJson(await call(restoredCaptured, "notes_list", { pattern: "checkpoint/**", scope: "personal" }, restoredCtx));
@@ -366,11 +358,11 @@ test("stale lifecycle: writes and metadata-only edits close and revive a note",
366
358
  // metadata-only: content unchanged, flag set, applied 0
367
359
  const markOnly = resultJson(await call(captured, "notes_edit", { path: "journal.md", stale: true }, ctx));
368
360
  assert.equal(markOnly.applied, 0);
369
- assert.equal(markOnly.meta.stale, true);
361
+ assert.equal(listNotes(ctx, { scope: "session" })[0]?.meta.stale, true);
370
362
  assert.equal(resultRead(await call(captured, "notes_read", { path: "journal.md" }, ctx)).content.endsWith("log line"), true, "mark-only leaves content unchanged");
371
363
  // explicit revive
372
364
  const revived = resultJson(await call(captured, "notes_edit", { path: "journal.md", stale: false }, ctx));
373
- assert.equal(revived.meta.stale, false, "stale:false revives");
365
+ assert.equal(listNotes(ctx, { scope: "session" })[0]?.meta.stale, false, "stale:false revives");
374
366
  // write+stale closure then plain write revival
375
367
  await call(captured, "notes_write", { path: "journal.md", content: "final", stale: true }, ctx);
376
368
  assert.equal(listNotes(ctx, { scope: "session" })[0]?.meta.stale, true);
@@ -705,8 +697,8 @@ test("an over-budget note is delivered as a prefix and resumed by next_offset_ch
705
697
  assert.ok(first.content.length > 0, "the page is not empty");
706
698
  assert.equal(first.content.includes("…"), false, "the payload is a plain prefix with no marker");
707
699
  assert.ok(first.content.startsWith("---\n"), "the frontmatter is delivered first");
708
- assert.equal(first.header, `[huge.md · chars 0-${first.next_offset_chars} of ${first.total_chars} · continue at offset_chars=${first.next_offset_chars} · session · created ${String(first.details.created_at)} · updated ${String(first.details.updated_at)}]`, "the raw header names the address, delivered range, resume cursor, scope and timestamps");
709
- assert.deepEqual(Object.keys(first.details).sort(), ["address", "created_at", "limit_chars", "next_offset_chars", "offset_chars", "scope", "total_chars", "updated_at"], "notes_read details carries exactly the slim window metadata plus scope");
700
+ assert.equal(first.header, `--- READ WINDOW ---\naddress: huge.md\nchars: [0,${first.next_offset_chars}) of ${first.total_chars}\nnext_offset_chars: ${first.next_offset_chars}\n`, "the raw block names the address, half-open range, and resume cursor");
701
+ assert.deepEqual(Object.keys(first.details).sort(), ["address", "next_offset_chars", "offset_chars", "total_chars"], "notes_read details carries exactly the raw window address and cursor metadata");
710
702
  assert.equal("content" in first.details, false, "details never duplicates the payload");
711
703
  assert.equal(first.offset_chars, 0, "the default window starts at the resolved offset 0");
712
704
  // Following the cursor reconstructs frontmatter + body by plain concatenation.
@@ -753,11 +745,11 @@ test("an over-budget note search match is a named prefix with an honest line add
753
745
  assert.equal(oversized.matches.length, 1);
754
746
  const match = oversized.matches[0];
755
747
  assert.equal(match.truncated, true, "the oversized match line is flagged as truncated");
756
- assert.equal(match.total_chars, Array.from(hugeLine).length, "total_chars names the full line length");
757
748
  assert.ok(hugeLine.startsWith(match.text), "the match text is a plain prefix of the line");
758
749
  assert.equal(match.text.includes("…"), false, "no marker is appended to the match text");
759
- assert.equal(match.offset_chars, 500, "the match carries the body-absolute offset of the query");
760
750
  assert.equal(match.line, 1, "the informational line number survives");
751
+ const atMatch = resultRead(await call(captured, "notes_read", { path: "search.md", offset_chars: match.offset_chars }, ctx));
752
+ assert.ok(atMatch.content.startsWith("needle"), "the search offset starts a read at the matched substring");
761
753
  // The body is reconstructible by following notes_read's cursor from the start of the file.
762
754
  const parts = [];
763
755
  let offset = 0;
@@ -784,7 +776,9 @@ test("history_read delivers a prefix and next_offset_chars names the delivered c
784
776
  assert.equal(read.content.includes("…"), false, "no marker is appended to the payload");
785
777
  assert.ok(original.startsWith(read.content), "the delivered text is a prefix of the item");
786
778
  assert.equal(read.total_chars, original.length);
787
- assert.deepEqual(Object.keys(read.details), ["window_id", "item_id", "offset_chars", "total_chars", "next_offset_chars", "limit_chars"], "history_read details carries exactly the slim window metadata");
779
+ assert.equal(read.header, `--- READ WINDOW ---\nwindow_id: ${historyFromSession(ctx)[0].windowId}\nitem_id: ${id}\nchars: [0,${read.next_offset_chars}) of ${read.total_chars}\nnext_offset_chars: ${read.next_offset_chars}\n`, "the paged history block names identities, half-open range, and resume cursor");
780
+ assert.deepEqual(Object.keys(read.details), ["window_id", "item_id", "offset_chars", "total_chars", "next_offset_chars"], "history_read details carries exactly the raw window identity and cursor metadata");
781
+ assert.equal("limit_chars" in read.details, false, "history_read details omits the request cap");
788
782
  assert.equal("content" in read.details, false, "details never duplicates the payload");
789
783
  assert.equal(read.next_offset_chars, read.offset_chars + Array.from(read.content).length, "the cursor is offset plus delivered code points");
790
784
  assert.ok(read.next_offset_chars !== null && read.next_offset_chars < read.total_chars, "the cursor points at the first undelivered character");
@@ -794,6 +788,7 @@ test("history_read delivers a prefix and next_offset_chars names the delivered c
794
788
  let next = offset;
795
789
  while (next !== null) {
796
790
  const page = resultRead(await call(captured, "history_read", { window_id: historyFromSession(ctx)[0].windowId, item_id: id, offset_chars: offset, limit_chars: 50000 }, ctx));
791
+ assert.equal(page.header, `--- READ WINDOW ---\nwindow_id: ${historyFromSession(ctx)[0].windowId}\nitem_id: ${id}\nchars: [${page.offset_chars},${page.offset_chars + Array.from(page.content).length}) of ${page.total_chars}\nnext_offset_chars: ${page.next_offset_chars}\n`, "every history page retains the exact shared READ WINDOW block");
797
792
  assert.equal(page.next_offset_chars, page.offset_chars + Array.from(page.content).length < page.total_chars ? page.offset_chars + Array.from(page.content).length : null, "the cursor is offset plus delivered, null only at item end");
798
793
  parts.push(page.content);
799
794
  next = page.next_offset_chars;
@@ -813,6 +808,7 @@ test("an empty body is a frontmatter-only file that terminates cleanly", async (
813
808
  assert.ok(empty.content.endsWith("---\n\n"), "an empty body leaves frontmatter and the blank separator only");
814
809
  assert.ok(empty.total_chars > 0, "the file is not zero-length once the harness frontmatter is written");
815
810
  assert.equal(empty.next_offset_chars, null, "a note that fits terminates instead of self-feeding");
811
+ assert.equal(empty.header, `--- READ WINDOW ---\naddress: empty.md\nchars: [0,${empty.total_chars}) of ${empty.total_chars}\nnext_offset_chars: null\n`, "an exhausted window writes literal null");
816
812
  // An offset beyond the file is an addressing error that names the real length,
817
813
  // not a silent empty page.
818
814
  const beyond = resultJson(await call(captured, "notes_read", { path: "empty.md", offset_chars: empty.total_chars + 9 }, ctx));
@@ -1727,13 +1723,16 @@ test("argument footguns die loudly and tool-run metadata surfaces (A1/A2/A3/B4/B
1727
1723
  const noteEnd = resultRead(await call(captured, "notes_read", { path: "a.md", offset_chars: noteTotal }, ctx));
1728
1724
  assert.equal(noteEnd.content, "", "notes: offset == total is the legal empty end-read");
1729
1725
  assert.equal(noteEnd.next_offset_chars, null, "notes: the end-read terminates");
1726
+ assert.equal(noteEnd.header, `--- READ WINDOW ---\naddress: a.md\nchars: [${noteTotal},${noteTotal}) of ${noteTotal}\nnext_offset_chars: null\n`, "notes: empty end-read retains the exact READ WINDOW block");
1727
+ const itemFull = resultRead(await call(captured, "history_read", { window_id: windowId, item_id: target.item_id }, ctx));
1728
+ assert.equal(itemFull.header, `--- READ WINDOW ---\nwindow_id: ${windowId}\nitem_id: ${target.item_id}\nchars: [0,11) of 11\nnext_offset_chars: null\n`, "history: a fitting read retains the exact shared READ WINDOW block");
1730
1729
  const itemPastEnd = resultJson(await call(captured, "history_read", { window_id: windowId, item_id: target.item_id, offset_chars: 12 }, ctx));
1731
1730
  assert.match(itemPastEnd.error ?? "", /past the end/, "history: past-end offset is a named error");
1732
1731
  assert.equal(itemPastEnd.total_chars, 11, "history: the error names the real length");
1733
1732
  assert.equal(itemPastEnd.item_id, target.item_id, "history: the error echoes the item_id");
1734
1733
  const itemEnd = resultRead(await call(captured, "history_read", { window_id: windowId, item_id: target.item_id, offset_chars: 11 }, ctx));
1735
1734
  assert.equal(itemEnd.content, "", "history: offset == total is the legal empty end-read");
1736
- assert.equal(itemEnd.next_offset_chars, null, "history: the end-read terminates");
1735
+ assert.equal(itemEnd.header, `--- READ WINDOW ---\nwindow_id: ${windowId}\nitem_id: ${target.item_id}\nchars: [11,11) of 11\nnext_offset_chars: null\n`, "history: empty end-read retains the exact READ WINDOW block");
1737
1736
  // A3: editing a missing note is the typed not-found arm; write still creates it.
1738
1737
  const editMissing = resultJson(await call(captured, "notes_edit", { path: "missing.md", stale: true }, ctx));
1739
1738
  assert.equal(editMissing.error, "note not found", "an edit of a missing note is named");
@@ -26,6 +26,20 @@ function setUpdatedAt(scope, path, ctx, timestamp) {
26
26
  const raw = readFileSync(file, "utf8");
27
27
  writeFileSync(file, raw.replace(/^updated_at: .*$/m, `updated_at: ${new Date(timestamp).toISOString()}`));
28
28
  }
29
+ function assertNoPublicScope(value, label) {
30
+ if (Array.isArray(value)) {
31
+ for (const item of value)
32
+ assertNoPublicScope(item, label);
33
+ return;
34
+ }
35
+ if (!value || typeof value !== "object")
36
+ return;
37
+ for (const [key, child] of Object.entries(value)) {
38
+ assert.notEqual(key, "scope", `${label} does not expose scope`);
39
+ assert.notEqual(key, "resolved_scope", `${label} does not expose resolved_scope`);
40
+ assertNoPublicScope(child, label);
41
+ }
42
+ }
29
43
  test("exactly the five notes tools are registered; the legacy five are gone", () => {
30
44
  const captured = makeExtension(manager());
31
45
  for (const name of ["notes_write", "notes_edit", "notes_read", "notes_list", "notes_search"]) {
@@ -34,6 +48,10 @@ test("exactly the five notes tools are registered; the legacy five are gone", ()
34
48
  for (const legacy of ["notes_write_file", "notes_append_to_file", "notes_read_file", "notes_search_contents", "notes_list_files"]) {
35
49
  assert.equal(captured.tools.get(legacy), undefined, `${legacy} is unregistered`);
36
50
  }
51
+ for (const name of ["notes_write", "notes_edit", "notes_read", "notes_list", "notes_search"]) {
52
+ const description = captured.tools.get(name)?.description ?? "";
53
+ assert.equal(/resolved_scope|\bscope\b/.test(description), false, `${name} describes home identity through address only`);
54
+ }
37
55
  assert.equal(captured.tools.get("notes_write")?.executionMode, "sequential");
38
56
  assert.equal(captured.tools.get("notes_edit")?.executionMode, "sequential");
39
57
  assert.equal(captured.tools.get("notes_read")?.executionMode, undefined);
@@ -45,8 +63,7 @@ test("write lands a real markdown file with harness frontmatter and a pure body"
45
63
  const ctx = context(session);
46
64
  const sessionId = session.getSessionId();
47
65
  const result = resultJson(await call(captured, "notes_write", { path: "a/b.md", content: "hello" }, ctx));
48
- assert.equal(result.scope, "session");
49
- assert.equal(result.size_bytes, 5);
66
+ assert.deepEqual(Object.keys(result).sort(), ["address", "written"]);
50
67
  const file = physicalPath("session", "a/b.md", ctx);
51
68
  assert.equal(file, join(root, "pi", "session", sessionId, "a", "b.md"));
52
69
  assert.ok(existsSync(file), "the note is a real file under the session scope dir");
@@ -60,7 +77,8 @@ test("write lands a real markdown file with harness frontmatter and a pure body"
60
77
  for (const key of ["created_at", "updated_at", "last_accessed"]) {
61
78
  assert.match(raw, new RegExp(`^${key}: \\d{4}-\\d{2}-\\d{2}T`, "m"), `frontmatter renders ${key} via localIso`);
62
79
  }
63
- assert.equal(typeof result.meta.created_at, "string", "wire meta renders timestamps as ISO strings");
80
+ assert.equal(result.address, "a/b.md");
81
+ assert.equal(result.written, true);
64
82
  // A leading YAML block in user content is stripped from the body.
65
83
  await call(captured, "notes_write", { path: "stripped.md", content: "---\nscope: personal\nnonsense: true\n---\nreal body" }, ctx);
66
84
  const stripped = readFileSync(physicalPath("session", "stripped.md", ctx), "utf8");
@@ -83,7 +101,7 @@ test("overwrite preserves created_at and unknown keys, bumps updated_at, and cle
83
101
  assert.equal(after.includes("sleep_shift_key: keep-me"), true, "an unknown key survives a rewrite");
84
102
  assert.equal(after.includes("recurrence_count: 4"), true, "a known sleep-shift key survives a rewrite");
85
103
  assert.match(after, /\n---\n\nsecond$/, "the body is replaced");
86
- assert.equal(rewrite.meta.origin, "self");
104
+ assert.deepEqual(Object.keys(rewrite).sort(), ["address", "written"]);
87
105
  const listed = listNotes(ctx, { scope: "session" })[0];
88
106
  assert.equal(listed.meta.created_at, created, "created_at is preserved across an overwrite");
89
107
  assert.ok(listed.meta.updated_at >= created, "updated_at is bumped");
@@ -111,7 +129,8 @@ test("edit is body-scoped with named failures and a replace_all escape hatch", a
111
129
  assert.equal(missing.edit_index, 0, "a zero-match anchor names the failing edit index");
112
130
  const all = resultJson(await call(captured, "notes_edit", { path: "edit.md", edits: [{ oldText: "beta", newText: "B" }], replace_all: true }, ctx));
113
131
  assert.equal(all.applied, 1);
114
- assert.equal(all.resolved_scope, "session", "the success return names the layer the file was resolved from");
132
+ assert.equal(all.address, "edit.md");
133
+ assertNoPublicScope(all, "notes_edit");
115
134
  assert.equal(resultRead(await call(captured, "notes_read", { path: "edit.md" }, ctx)).content.endsWith("alpha\nB\nB\ngamma"), true, "replace_all replaces every occurrence");
116
135
  // An anchor that occurs only in frontmatter is not matched: edits are body-only.
117
136
  const frontmatterOnly = resultJson(await call(captured, "notes_edit", { path: "edit.md", edits: [{ oldText: "scope", newText: "x" }] }, ctx));
@@ -149,11 +168,13 @@ test("metadata-only edit updates setters without touching the body", async () =>
149
168
  await call(captured, "notes_write", { path: "journal.md", content: "log line" }, ctx);
150
169
  const bare = resultJson(await call(captured, "notes_edit", { path: "journal.md", stale: true }, ctx));
151
170
  assert.equal(bare.applied, 0, "a metadata-only update applies no edits");
152
- assert.equal(bare.resolved_scope, "session");
153
- assert.equal(bare.meta.stale, true, "stale is set without a body edit");
171
+ assert.equal(bare.address, "journal.md");
172
+ assert.deepEqual(Object.keys(bare).sort(), ["address", "applied", "diff"]);
173
+ assert.equal(listNotes(ctx, { scope: "session" })[0].meta.stale, true, "stale is set without a body edit");
154
174
  assert.equal(resultRead(await call(captured, "notes_read", { path: "journal.md" }, ctx)).content.endsWith("log line"), true, "the body is untouched");
155
175
  const revived = resultJson(await call(captured, "notes_edit", { path: "journal.md", stale: false }, ctx));
156
- assert.equal(revived.meta.stale, false, "a later metadata-only update revives the note");
176
+ assert.deepEqual(Object.keys(revived).sort(), ["address", "applied", "diff"]);
177
+ assert.equal(listNotes(ctx, { scope: "session" })[0].meta.stale, false, "a later metadata-only update revives the note");
157
178
  });
158
179
  test("notes_edit returns a pi-edit-style diff of what changed", async () => {
159
180
  freshRoot();
@@ -234,7 +255,39 @@ test.skip("scope resolution and movement are superseded by explicit address test
234
255
  assert.match(refusal.error, /already exists/);
235
256
  assert.equal(readFileSync(physicalPath("personal", "clash.md", ctx), "utf8"), beforePersonal, "the target survives a refused move");
236
257
  });
237
- test("list and search merge scopes and carry scope; the path jail rejects escapes", async () => {
258
+ test("all notes tool results use address as the only home identity", async () => {
259
+ freshRoot();
260
+ const session = manager();
261
+ const captured = makeExtension(session);
262
+ const ctx = context(session);
263
+ const notes = [
264
+ { address: "session.md", body: "session needle" },
265
+ { address: "@project/project.md", body: "project needle" },
266
+ { address: "@personal/personal.md", body: "personal needle" },
267
+ ];
268
+ for (const note of notes) {
269
+ const written = resultJson(await call(captured, "notes_write", { address: note.address, content: note.body }, ctx));
270
+ assert.equal(written.address, note.address, `notes_write returns ${note.address}`);
271
+ assertNoPublicScope(written, `notes_write ${note.address}`);
272
+ const edited = resultJson(await call(captured, "notes_edit", { address: note.address, edits: [{ oldText: "needle", newText: "match" }] }, ctx));
273
+ assert.equal(edited.address, note.address, `notes_edit returns ${note.address}`);
274
+ assertNoPublicScope(edited, `notes_edit ${note.address}`);
275
+ const rawRead = await call(captured, "notes_read", { address: note.address }, ctx);
276
+ const read = resultRead(rawRead);
277
+ assert.equal(read.details.address, note.address, `notes_read details returns ${note.address}`);
278
+ assert.equal(read.header.startsWith("--- READ WINDOW ---\naddress: "), true, `notes_read starts a READ WINDOW block for ${note.address}`);
279
+ assert.equal(read.header.includes("scope"), false, `notes_read header omits scope for ${note.address}`);
280
+ assert.equal(read.header.includes("resolved_scope"), false, `notes_read header omits resolved_scope for ${note.address}`);
281
+ assertNoPublicScope(read.details, `notes_read ${note.address}`);
282
+ }
283
+ const listed = resultJson(await call(captured, "notes_list", { pattern: "**" }, ctx));
284
+ assert.deepEqual(listed.files.map((file) => file.address).sort(), notes.map((note) => note.address).sort(), "notes_list returns each full address");
285
+ assertNoPublicScope(listed, "notes_list");
286
+ const searched = resultJson(await call(captured, "notes_search", { query: "match", pattern: "**" }, ctx));
287
+ assert.deepEqual(searched.files.map((file) => file.address), notes.map((note) => note.address).sort(), "notes_search returns each full address");
288
+ assertNoPublicScope(searched, "notes_search");
289
+ });
290
+ test("list and search merge scopes and carry addresses; the path jail rejects escapes", async () => {
238
291
  freshRoot();
239
292
  const session = manager();
240
293
  const captured = makeExtension(session);
@@ -243,24 +296,24 @@ test("list and search merge scopes and carry scope; the path jail rejects escape
243
296
  await call(captured, "notes_write", { path: "two.md", content: "needle two", scope: "project" }, ctx);
244
297
  await call(captured, "notes_write", { path: "three.md", content: "needle three", scope: "personal" }, ctx);
245
298
  const listed = resultJson(await call(captured, "notes_list", {}, ctx));
246
- assert.deepEqual([...listed.files].map((file) => file.scope).sort(), ["personal", "project", "session"], "every merged row carries its scope");
299
+ assert.deepEqual([...listed.files].map((file) => file.address).sort(), ["@personal/three.md", "@project/two.md", "one.md"], "every merged row carries its full address");
247
300
  for (const row of listed.files) {
248
- assert.equal(typeof row.size_bytes, "number");
249
- assert.equal(row.origin, "self");
250
- assert.equal(row.status, "active");
301
+ assert.deepEqual(Object.keys(row).sort(), ["address", "stale", "updated_at"]);
251
302
  assert.equal(row.stale, false);
252
303
  }
253
304
  const scoped = resultJson(await call(captured, "notes_list", { scope: "personal" }, ctx));
254
- assert.deepEqual(scoped.files.map((file) => file.path), ["three.md"], "a scope filter narrows the set");
305
+ assert.deepEqual(scoped.files.map((file) => file.address), ["@personal/three.md"], "an address-pattern filter narrows the set");
255
306
  const searched = resultJson(await call(captured, "notes_search", { query: "needle" }, ctx));
256
307
  assert.equal(searched.files.length, 3, "literal search finds matches in every scope");
257
- assert.deepEqual([...searched.files].map((file) => file.scope).sort(), ["personal", "project", "session"]);
308
+ assert.deepEqual([...searched.files].map((file) => file.address).sort(), ["@personal/three.md", "@project/two.md", "one.md"]);
309
+ for (const row of [...listed.files, ...searched.files])
310
+ assertNoPublicScope(row, "notes_list/search");
258
311
  assert.equal(searched.files.every((file) => file.matches_total === 1), true);
259
312
  const hit = searched.files[0].matches[0];
260
313
  assert.equal(hit.line, 1);
261
- assert.equal(hit.offset_chars, 0);
314
+ assert.ok(hit.offset_chars > 0, "the offset includes serialized frontmatter");
262
315
  assert.equal(hit.truncated, false);
263
- assert.equal(hit.total_chars, searched.files[0].path === "three.md" ? "needle three".length : hit.text.length, "total_chars names the real line length");
316
+ assert.deepEqual(Object.keys(hit).sort(), ["line", "offset_chars", "text", "truncated"]);
264
317
  const escaped = ["../evil", "/abs", "a\\b"];
265
318
  for (const tool of ["notes_write", "notes_edit", "notes_read"]) {
266
319
  for (const path of escaped) {
@@ -291,6 +344,38 @@ test("the boot index reads the physical store across scopes and excludes stale n
291
344
  assert.equal(PROTOCOL_BLOCK.includes(legacy), false, `the protocol block no longer names ${legacy}`);
292
345
  }
293
346
  });
347
+ test("search offsets start reads at Unicode matches across homes without mutating search results", async () => {
348
+ freshRoot();
349
+ const session = manager();
350
+ const captured = makeExtension(session);
351
+ const ctx = context(session);
352
+ const notes = [
353
+ { address: "session.md", scope: "session", body: "first line\n前置 🐉 needle-session\nend", needle: "needle-session" },
354
+ { address: "@project/crossing.md", scope: "project", body: "prefix\nneedle-project 😀", needle: "needle-project" },
355
+ { address: "@personal/legacy.md", scope: "personal", body: "legacy 😺 needle-personal", needle: "needle-personal" },
356
+ ];
357
+ for (const note of notes)
358
+ await call(captured, "notes_write", { address: note.address, content: note.body }, ctx);
359
+ const crossing = physicalPath("project", "crossing.md", ctx);
360
+ writeFileSync(crossing, readFileSync(crossing, "utf8").replace(/^access_count: 0$/m, "access_count: 9"));
361
+ const legacy = physicalPath("personal", "legacy.md", ctx);
362
+ writeFileSync(legacy, notes[2].body);
363
+ const before = new Map(notes.map((note) => [note.address, readFileSync(physicalPath(note.scope, note.address.replace(/^@(?:project|personal)\//, ""), ctx), "utf8")]));
364
+ const searched = resultJson(await call(captured, "notes_search", { query: notes.map((note) => note.needle), pattern: "**" }, ctx));
365
+ for (const note of notes) {
366
+ assert.equal(readFileSync(physicalPath(note.scope, note.address.replace(/^@(?:project|personal)\//, ""), ctx), "utf8"), before.get(note.address), `search leaves ${note.address} byte-identical`);
367
+ const file = searched.files.find((candidate) => candidate.address === note.address);
368
+ assert.ok(file, `search returns ${note.address}`);
369
+ assert.deepEqual(Object.keys(file).sort(), ["address", "matches", "matches_total", "stale", "updated_at"]);
370
+ const hit = file.matches[0];
371
+ assert.deepEqual(Object.keys(hit).sort(), ["line", "offset_chars", "text", "truncated"]);
372
+ const read = resultRead(await call(captured, "notes_read", { address: note.address, offset_chars: hit.offset_chars }, ctx));
373
+ assert.ok(read.content.startsWith(note.needle), `search offset starts notes_read at ${note.needle}`);
374
+ assert.deepEqual(Object.keys(read.details).sort(), ["address", "next_offset_chars", "offset_chars", "total_chars"]);
375
+ }
376
+ assert.match(readFileSync(crossing, "utf8"), /^access_count: 10$/m, "the predicted read crosses access_count from 9 to 10");
377
+ assert.match(resultRead(await call(captured, "notes_read", { address: "@personal/legacy.md" }, ctx)).content, /^---\n/, "a missing-frontmatter note is normalized only by read");
378
+ });
294
379
  test("notes_read surfaces frontmatter and search reports body lines", async () => {
295
380
  freshRoot();
296
381
  const session = manager();
@@ -303,7 +388,7 @@ test("notes_read surfaces frontmatter and search reports body lines", async () =
303
388
  const searched = resultJson(await call(captured, "notes_search", { query: "needle" }, ctx));
304
389
  const match = searched.files[0].matches[0];
305
390
  assert.equal(match.line, 2, "search reports the body line number");
306
- assert.equal(match.offset_chars, Array.from("line one\n").length, "offset_chars addresses the query within the body");
391
+ assert.equal(match.offset_chars, read.content.indexOf("needle"), "offset_chars addresses the query in the serialized read stream");
307
392
  });
308
393
  test("mutations are atomic, leave no temp files, and a read bumps only the access keys", async () => {
309
394
  freshRoot();
@@ -443,11 +528,12 @@ test("full addresses drive outputs and patterns; on-disk scope is read then drop
443
528
  assert.deepEqual(resultJson(await call(captured, "notes_list", { pattern: "*.md" }, ctx)).files.map((file) => file.address), ["root.md"]);
444
529
  assert.deepEqual(resultJson(await call(captured, "notes_search", { query: "needle", pattern: "@project/**" }, ctx)).files.map((file) => file.address), ["@project/project.md"]);
445
530
  const read = resultRead(await call(captured, "notes_read", { address: "@personal/personal.md" }, ctx));
446
- assert.match(read.header, /^\[@personal\/personal\.md /, "the raw read header echoes the full address");
531
+ assert.equal(read.header, "--- READ WINDOW ---\naddress: @personal/personal.md\nchars: [0," + read.total_chars + ") of " + read.total_chars + "\nnext_offset_chars: null\n", "the raw READ WINDOW block echoes the full address");
447
532
  const legacy = physicalPath("project", "legacy.md", ctx);
448
533
  writeFileSync(legacy, "---\nscope: personal\norigin: self\nstatus: active\nstale: false\ncreated_at: 2026-01-01T00:00:00.000+00:00\nupdated_at: 2026-01-01T00:00:00.000+00:00\nlast_accessed: 2026-01-01T00:00:00.000+00:00\naccess_count: 0\n---\n\nlegacy");
449
534
  const legacyRead = resultRead(await call(captured, "notes_read", { address: "@project/legacy.md" }, ctx));
450
- assert.equal(legacyRead.details.scope, "project", "scope is derived from the file location");
535
+ assert.equal(legacyRead.details.address, "@project/legacy.md", "the read keeps the requested address while deriving its home internally");
536
+ assertNoPublicScope(legacyRead.details, "legacy notes_read");
451
537
  await call(captured, "notes_edit", { address: "@project/legacy.md", stale: true }, ctx);
452
538
  assert.equal(/^scope:/m.test(readFileSync(legacy, "utf8")), false, "the next write removes legacy scope frontmatter");
453
539
  });