portable-agent-layer 0.85.8 → 0.85.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "portable-agent-layer",
3
- "version": "0.85.8",
3
+ "version": "0.85.9",
4
4
  "description": "PAL — Portable Agent Layer: persistent personal context for AI coding assistants",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,7 +12,6 @@
12
12
 
13
13
  import {
14
14
  existsSync,
15
- mkdirSync,
16
15
  readdirSync,
17
16
  readFileSync,
18
17
  renameSync,
@@ -22,7 +21,7 @@ import {
22
21
  } from "node:fs";
23
22
  import { basename, dirname, resolve } from "node:path";
24
23
  import { readBindings } from "../hooks/lib/bindings";
25
- import { mergeJsonlLines } from "../hooks/lib/import-merge";
24
+ import { foldHistoryInto } from "../hooks/lib/parked-history";
26
25
  import { palHome, paths } from "../hooks/lib/paths";
27
26
  import {
28
27
  legacyJsonToProgress,
@@ -55,6 +54,8 @@ interface Migration {
55
54
  description: string;
56
55
  check(): { pending: boolean; detail?: string };
57
56
  run(dryRun?: boolean): MigrationResult;
57
+ /** Moves its sources rather than copying them, so there is nothing left to delete. */
58
+ movesSources?: true;
58
59
  }
59
60
 
60
61
  // ── v1-projects: JSON progress files → ISA.md ─────────────────────
@@ -588,15 +589,6 @@ function destinationFor(item: StrandedHistory): string {
588
589
  : resolve(paths.unboundHistory(), `${item.slug}.jsonl`);
589
590
  }
590
591
 
591
- /** Union both sides so a destination that already has history keeps it. */
592
- function foldHistoryInto(target: string, source: string): number {
593
- const local = existsSync(target) ? readFileSync(target, "utf-8") : "";
594
- const { text, added } = mergeJsonlLines(local, readFileSync(source, "utf-8"));
595
- mkdirSync(dirname(target), { recursive: true });
596
- writeFileSync(target, text, "utf-8");
597
- return added;
598
- }
599
-
600
592
  function discardEmptyFolder(dir: string): void {
601
593
  try {
602
594
  if (readdirSync(dir).length === 0) rmdirSync(dir);
@@ -608,6 +600,7 @@ function discardEmptyFolder(dir: string): void {
608
600
  const v6HistorySlugs: Migration = {
609
601
  id: "v6-history-slugs",
610
602
  description: "Re-file session history keyed on a cwd name onto its owning project",
603
+ movesSources: true,
611
604
 
612
605
  check() {
613
606
  const stranded = strandedHistory();
@@ -728,7 +721,7 @@ export function runMigrate(args: string[]): void {
728
721
  console.log(
729
722
  ` ${dryRun ? "Would migrate" : "Migrated"}: ${totalMigrated} | Skipped: ${totalSkipped}`
730
723
  );
731
- if (!dryRun && totalMigrated > 0) {
724
+ if (!dryRun && totalMigrated > 0 && pending.some((m) => !m.movesSources)) {
732
725
  console.log(" Source files preserved — delete manually once verified.");
733
726
  }
734
727
  console.log("");
@@ -20,6 +20,7 @@
20
20
  import { copyFileSync, existsSync, readFileSync, writeFileSync } from "node:fs";
21
21
  import { homedir } from "node:os";
22
22
  import { resolve } from "node:path";
23
+ import { adoptParkedHistory } from "./parked-history";
23
24
  import { palHome, toPath } from "./paths";
24
25
 
25
26
  /** Project name → absolute path on this machine. */
@@ -91,6 +92,7 @@ export function writeBindings(bindings: Bindings, home: string = palHome()): voi
91
92
  const sorted: Bindings = {};
92
93
  for (const key of Object.keys(bindings).sort()) sorted[key] = bindings[key];
93
94
  writeFileSync(bindingsFilePath(home), `${JSON.stringify(sorted, null, 2)}\n`);
95
+ adoptParkedHistory(sorted, home);
94
96
  }
95
97
 
96
98
  /** The absolute path this machine has for `project`, or null when unbound. */
@@ -0,0 +1,59 @@
1
+ /**
2
+ * History from a directory no project claimed is parked under the directory's
3
+ * name. Once exactly one project is bound to a directory of that name, the
4
+ * parked history is its own and moves in.
5
+ */
6
+
7
+ import {
8
+ existsSync,
9
+ mkdirSync,
10
+ readdirSync,
11
+ readFileSync,
12
+ unlinkSync,
13
+ writeFileSync,
14
+ } from "node:fs";
15
+ import { basename, dirname, resolve } from "node:path";
16
+ import { mergeJsonlLines } from "./import-merge";
17
+ import { logError } from "./log";
18
+
19
+ type Bindings = Record<string, string>;
20
+
21
+ /** Union both sides so a destination that already has history keeps it. */
22
+ export function foldHistoryInto(target: string, source: string): number {
23
+ const local = existsSync(target) ? readFileSync(target, "utf-8") : "";
24
+ const { text, added } = mergeJsonlLines(local, readFileSync(source, "utf-8"));
25
+ mkdirSync(dirname(target), { recursive: true });
26
+ writeFileSync(target, text, "utf-8");
27
+ return added;
28
+ }
29
+
30
+ /** The project bound to a directory named `slug`, when exactly one is. */
31
+ function projectBoundUnder(slug: string, bindings: Bindings): string | null {
32
+ const owners = Object.entries(bindings).filter(([, path]) => basename(path) === slug);
33
+ return owners.length === 1 ? owners[0][0] : null;
34
+ }
35
+
36
+ function parkedDir(home: string): string {
37
+ return resolve(home, "memory", "state", "unbound-history");
38
+ }
39
+
40
+ function recordedProjectDir(home: string, project: string): string | null {
41
+ const dir = resolve(home, "memory", "projects", project);
42
+ return existsSync(resolve(dir, "ISA.md")) ? dir : null;
43
+ }
44
+
45
+ export function adoptParkedHistory(bindings: Bindings, home: string): void {
46
+ const dir = parkedDir(home);
47
+ if (!existsSync(dir)) return;
48
+ for (const file of readdirSync(dir).filter((f) => f.endsWith(".jsonl"))) {
49
+ const owner = projectBoundUnder(file.slice(0, -".jsonl".length), bindings);
50
+ const projectDir = owner && recordedProjectDir(home, owner);
51
+ if (!projectDir) continue;
52
+ try {
53
+ foldHistoryInto(resolve(projectDir, "history.jsonl"), resolve(dir, file));
54
+ unlinkSync(resolve(dir, file));
55
+ } catch (err) {
56
+ logError("parked-history:adopt", err);
57
+ }
58
+ }
59
+ }
@@ -267,11 +267,6 @@ export function writeProject(p: ProjectProgress): void {
267
267
  const detected = detectRemote(p.path);
268
268
  if (detected) p.remote = detected;
269
269
  }
270
- // Only a path that exists here may be bound. Saving a record is not a claim
271
- // about this machine's disk — `path` may have arrived from an imported record
272
- // written elsewhere, and binding it would recreate the very leak bindings exist
273
- // to prevent. An explicit `writeBinding` from the user stays unguarded.
274
- if (p.path && existsSync(p.path)) writeBinding(p.name, p.path);
275
270
  const meta: Record<string, unknown> = {
276
271
  name: p.name,
277
272
  status: p.status,
@@ -290,6 +285,11 @@ export function writeProject(p: ProjectProgress): void {
290
285
  if (p.placed) meta.placed = p.placed;
291
286
  if (p.placed_by) meta.placed_by = p.placed_by;
292
287
  writeFileSync(ensureAndGetIsaFile(p.name), stringify(meta, buildBody(p)), "utf-8");
288
+ // Only a path that exists here may be bound. Saving a record is not a claim
289
+ // about this machine's disk — `path` may have arrived from an imported record
290
+ // written elsewhere, and binding it would recreate the very leak bindings exist
291
+ // to prevent. An explicit `writeBinding` from the user stays unguarded.
292
+ if (p.path && existsSync(p.path)) writeBinding(p.name, p.path);
293
293
  }
294
294
 
295
295
  export function deleteProject(name: string): boolean {