@astrosheep/pi-context 0.23.0 → 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.
@@ -17,7 +17,7 @@ function args(argv) { const out = {}; for (let i = 0; i < argv.length; i++) {
17
17
  out[a.slice(2)] = argv[++i] ?? "";
18
18
  } return out; }
19
19
  function packageRoot() {
20
- let dir = dirname(new URL(import.meta.url).pathname);
20
+ let dir = dirname(fileURLToPath(import.meta.url));
21
21
  while (true) {
22
22
  if (existsSync(join(dir, "package.json")))
23
23
  return dir;
@@ -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" } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrosheep/pi-context",
3
- "version": "0.23.0",
3
+ "version": "0.23.1",
4
4
  "type": "module",
5
5
  "description": "Codex-style context windows for Pi: reset-style compaction, durable session history tools, and persistent notes.",
6
6
  "license": "MIT",
package/playbook.md CHANGED
@@ -24,9 +24,9 @@ access_count: 0
24
24
  2. **Merge threshold.** Supersede another note only when all three hold: same topic (name it in the survivor's body), same kind of note (checkpoint/design/log…), and the survivor is strictly newer or strictly more specific. Otherwise keep both and record the open conflict in the survivor.
25
25
  3. **Size budget.** Keep every note under ~200 lines / ~8KB. Oversized notes get split by topic with a one-line cross-link in each (`see also: @home/<vpath>`). Checkpoints may exceed the budget — trim prose, never facts.
26
26
  4. **Keep the maps.** Each home's `MAP.md` maps that home's durable notes: one line per entry — its address and a short gist in your own words, never a mechanical body slice. Project notes go on `@project/MAP.md`, cross-project knowledge on `@personal/MAP.md`; session notes are never mapped — the pocket covers them. When a note is promoted across homes, move its line to the destination map; when a note goes stale, drop its line. Maps obey the same size budget as any note.
27
- 5. **Jurisdiction.** Your mandate is the whole store every session home, every project home, personal. Nothing is skipped: notes are never physically deleted and every run is bracketed by git commits, so the human gate can audit and revert whatever you touch. Group your report by home so the gate can see what moved. In every home: map entry lines are yours to maintain, but prose that carries rules or guidance is not — flag it in your report instead of rewriting it.
27
+ 5. **Jurisdiction.** Read the whole store, including every session home, to extract durable knowledge. `pi/session/**` is a live agent's write-ahead log: never write or edit anything there, including frontmatter or stale markers. Promote useful facts by writing to project or personal instead. Other notes are never physically deleted and every run is bracketed by git commits, so the human gate can audit and revert whatever you touch. Group your report by home so the gate can see what moved. In writable homes: map entry lines are yours to maintain, but prose that carries rules or guidance is not — flag it in your report instead of rewriting it.
28
28
  6. **Leave stable notes alone.** Change notes to incorporate new evidence, resolve verified errors, merge genuine duplicates, or split oversized files—not merely to shorten or rephrase them. Preserve facts, conditions, exceptions, and uncertainty. No change is a valid outcome.
29
29
 
30
- Read the files and merge genuinely duplicate notes by editing the survivor, then set `stale: true` in the absorbed note's frontmatter. Nothing is physically deleted; stale notes remain readable. Promote durable cross-project knowledge by writing or editing at `@personal/<vpath>`. Keep notes compact and preserve useful provenance in the body.
30
+ Read the files and merge genuinely duplicate notes in writable homes by editing the survivor, then set `stale: true` in the absorbed note's frontmatter if it is writable. Session notes remain untouched even when promoted; record their source in the destination. Nothing is physically deleted; stale notes remain readable. Promote durable cross-project knowledge by writing or editing at `@personal/<vpath>`. Keep notes compact and preserve useful provenance in the body.
31
31
 
32
32
  Do not write skill ideas as files. Put skill ideas and unresolved questions in your final assistant message as proposals for the human. Your final message should be a concise report of what you inspected, changed, and left unresolved. If you made no file writes, say so.
package/src/dream/cli.ts CHANGED
@@ -12,7 +12,7 @@ import { notesRoot } from "../notes/paths.js";
12
12
 
13
13
  function args(argv: string[]) { const out: Record<string, string | boolean> = {}; for (let i=0;i<argv.length;i++) { const a=argv[i]!; if (a === "--force" || a === "--help") out[a.slice(2)] = true; else if (a.startsWith("--")) out[a.slice(2)] = argv[++i] ?? ""; } return out; }
14
14
  function packageRoot(): string {
15
- let dir = dirname(new URL(import.meta.url).pathname);
15
+ let dir = dirname(fileURLToPath(import.meta.url));
16
16
  while (true) { if (existsSync(join(dir, "package.json"))) return dir; const parent = dirname(dir); if (parent === dir) throw new Error("could not locate installed package root"); dir = parent; }
17
17
  }
18
18