@llblab/pi-kit 0.18.1 → 0.19.0

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 (139) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +4 -2
  3. package/node_modules/@llblab/pi-state-flow/AGENTS.md +35 -37
  4. package/node_modules/@llblab/pi-state-flow/BACKLOG.md +5 -3
  5. package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +19 -1
  6. package/node_modules/@llblab/pi-state-flow/README.md +102 -48
  7. package/node_modules/@llblab/pi-state-flow/dist/index.d.ts +6 -9
  8. package/node_modules/@llblab/pi-state-flow/dist/index.js +6 -9
  9. package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.d.ts +2 -2
  10. package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.js +21 -8
  11. package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.d.ts +40 -24
  12. package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.js +93 -63
  13. package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.d.ts +4 -3
  14. package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.js +9 -9
  15. package/node_modules/@llblab/pi-state-flow/dist/lib/config.d.ts +1 -1
  16. package/node_modules/@llblab/pi-state-flow/dist/lib/config.js +6 -5
  17. package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +8 -7
  18. package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +56 -58
  19. package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.js +12 -24
  20. package/node_modules/@llblab/pi-state-flow/dist/lib/durable.d.ts +7 -18
  21. package/node_modules/@llblab/pi-state-flow/dist/lib/durable.js +47 -78
  22. package/node_modules/@llblab/pi-state-flow/dist/lib/extension.d.ts +4 -6
  23. package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +241 -437
  24. package/node_modules/@llblab/pi-state-flow/dist/lib/git.d.ts +2 -72
  25. package/node_modules/@llblab/pi-state-flow/dist/lib/git.js +120 -499
  26. package/node_modules/@llblab/pi-state-flow/dist/lib/history.d.ts +2 -1
  27. package/node_modules/@llblab/pi-state-flow/dist/lib/history.js +4 -3
  28. package/node_modules/@llblab/pi-state-flow/dist/lib/json.d.ts +3 -0
  29. package/node_modules/@llblab/pi-state-flow/dist/lib/json.js +43 -22
  30. package/node_modules/@llblab/pi-state-flow/dist/lib/logging.d.ts +1 -5
  31. package/node_modules/@llblab/pi-state-flow/dist/lib/logging.js +0 -2
  32. package/node_modules/@llblab/pi-state-flow/dist/lib/memory.d.ts +1 -14
  33. package/node_modules/@llblab/pi-state-flow/dist/lib/memory.js +5 -37
  34. package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.d.ts +1 -2
  35. package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +13 -32
  36. package/node_modules/@llblab/pi-state-flow/dist/lib/query.d.ts +6 -6
  37. package/node_modules/@llblab/pi-state-flow/dist/lib/query.js +25 -21
  38. package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.d.ts +3 -3
  39. package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.js +15 -12
  40. package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.d.ts +2 -4
  41. package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.js +5 -1
  42. package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.d.ts +26 -88
  43. package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.js +159 -255
  44. package/node_modules/@llblab/pi-state-flow/dist/lib/skills.d.ts +0 -3
  45. package/node_modules/@llblab/pi-state-flow/dist/lib/skills.js +1 -47
  46. package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.d.ts +16 -33
  47. package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.js +48 -137
  48. package/node_modules/@llblab/pi-state-flow/dist/lib/state.d.ts +16 -11
  49. package/node_modules/@llblab/pi-state-flow/dist/lib/state.js +13 -14
  50. package/node_modules/@llblab/pi-state-flow/dist/lib/status.d.ts +1 -9
  51. package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +15 -43
  52. package/node_modules/@llblab/pi-state-flow/dist/lib/storage.d.ts +5 -9
  53. package/node_modules/@llblab/pi-state-flow/dist/lib/storage.js +13 -45
  54. package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +1 -1
  55. package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.d.ts +15 -7
  56. package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.js +104 -24
  57. package/node_modules/@llblab/pi-state-flow/dist/lib/transition.d.ts +0 -2
  58. package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +11 -14
  59. package/node_modules/@llblab/pi-state-flow/dist/package.json +9 -6
  60. package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-guide/SKILL.md +11 -17
  61. package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-memory/SKILL.md +8 -8
  62. package/node_modules/@llblab/pi-state-flow/docs/README.md +2 -2
  63. package/node_modules/@llblab/pi-state-flow/docs/architecture.md +64 -67
  64. package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +24 -6
  65. package/node_modules/@llblab/pi-state-flow/docs/filesystem-recovery.md +12 -15
  66. package/node_modules/@llblab/pi-state-flow/docs/fork-contract.md +22 -27
  67. package/node_modules/@llblab/pi-state-flow/docs/lazy-state.md +25 -41
  68. package/node_modules/@llblab/pi-state-flow/docs/performance.md +38 -421
  69. package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +33 -46
  70. package/node_modules/@llblab/pi-state-flow/docs/usage.md +37 -61
  71. package/node_modules/@llblab/pi-state-flow/index.ts +7 -71
  72. package/node_modules/@llblab/pi-state-flow/lib/acquisition.ts +26 -11
  73. package/node_modules/@llblab/pi-state-flow/lib/artifact.ts +116 -88
  74. package/node_modules/@llblab/pi-state-flow/lib/compaction.ts +12 -10
  75. package/node_modules/@llblab/pi-state-flow/lib/config.ts +6 -6
  76. package/node_modules/@llblab/pi-state-flow/lib/context.ts +52 -59
  77. package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +11 -20
  78. package/node_modules/@llblab/pi-state-flow/lib/durable.ts +44 -86
  79. package/node_modules/@llblab/pi-state-flow/lib/extension.ts +237 -460
  80. package/node_modules/@llblab/pi-state-flow/lib/git.ts +110 -552
  81. package/node_modules/@llblab/pi-state-flow/lib/history.ts +4 -3
  82. package/node_modules/@llblab/pi-state-flow/lib/json.ts +39 -23
  83. package/node_modules/@llblab/pi-state-flow/lib/logging.ts +1 -7
  84. package/node_modules/@llblab/pi-state-flow/lib/memory.ts +5 -44
  85. package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +14 -25
  86. package/node_modules/@llblab/pi-state-flow/lib/query.ts +26 -22
  87. package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +17 -11
  88. package/node_modules/@llblab/pi-state-flow/lib/rehydration.ts +7 -5
  89. package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +154 -254
  90. package/node_modules/@llblab/pi-state-flow/lib/skills.ts +1 -49
  91. package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +58 -142
  92. package/node_modules/@llblab/pi-state-flow/lib/state.ts +27 -19
  93. package/node_modules/@llblab/pi-state-flow/lib/status.ts +16 -54
  94. package/node_modules/@llblab/pi-state-flow/lib/storage.ts +12 -40
  95. package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +1 -1
  96. package/node_modules/@llblab/pi-state-flow/lib/temporal.ts +104 -22
  97. package/node_modules/@llblab/pi-state-flow/lib/transition.ts +16 -26
  98. package/node_modules/@llblab/pi-state-flow/package.json +9 -6
  99. package/node_modules/@llblab/pi-state-flow/skills/state-flow-guide/SKILL.md +11 -17
  100. package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +8 -8
  101. package/node_modules/@llblab/pi-telegram/CHANGELOG.md +7 -0
  102. package/node_modules/@llblab/pi-telegram/README.md +1 -2
  103. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +1 -3
  104. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +1 -6
  105. package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +1 -4
  106. package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +13 -33
  107. package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +1 -4
  108. package/node_modules/@llblab/pi-telegram/dist/lib/menu-queue.d.ts +1 -0
  109. package/node_modules/@llblab/pi-telegram/dist/lib/menu-queue.js +72 -1
  110. package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +7 -0
  111. package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +27 -0
  112. package/node_modules/@llblab/pi-telegram/dist/lib/replies.d.ts +3 -0
  113. package/node_modules/@llblab/pi-telegram/dist/lib/replies.js +17 -0
  114. package/node_modules/@llblab/pi-telegram/dist/lib/sync.js +8 -1
  115. package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
  116. package/node_modules/@llblab/pi-telegram/docs/architecture.md +2 -0
  117. package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +1 -1
  118. package/node_modules/@llblab/pi-telegram/docs/public-api.md +1 -1
  119. package/node_modules/@llblab/pi-telegram/lib/bindings.ts +0 -10
  120. package/node_modules/@llblab/pi-telegram/lib/commands.ts +18 -41
  121. package/node_modules/@llblab/pi-telegram/lib/extension.ts +1 -8
  122. package/node_modules/@llblab/pi-telegram/lib/menu-queue.ts +126 -1
  123. package/node_modules/@llblab/pi-telegram/lib/queue.ts +46 -0
  124. package/node_modules/@llblab/pi-telegram/lib/replies.ts +19 -0
  125. package/node_modules/@llblab/pi-telegram/lib/sync.ts +8 -1
  126. package/node_modules/@llblab/pi-telegram/package.json +1 -1
  127. package/package.json +3 -3
  128. package/node_modules/@llblab/pi-state-flow/dist/lib/discovery.d.ts +0 -21
  129. package/node_modules/@llblab/pi-state-flow/dist/lib/discovery.js +0 -125
  130. package/node_modules/@llblab/pi-state-flow/dist/lib/maintenance.d.ts +0 -36
  131. package/node_modules/@llblab/pi-state-flow/dist/lib/maintenance.js +0 -98
  132. package/node_modules/@llblab/pi-state-flow/dist/lib/migration.d.ts +0 -13
  133. package/node_modules/@llblab/pi-state-flow/dist/lib/migration.js +0 -167
  134. package/node_modules/@llblab/pi-state-flow/dist/lib/publication.d.ts +0 -86
  135. package/node_modules/@llblab/pi-state-flow/dist/lib/publication.js +0 -437
  136. package/node_modules/@llblab/pi-state-flow/lib/discovery.ts +0 -133
  137. package/node_modules/@llblab/pi-state-flow/lib/maintenance.ts +0 -147
  138. package/node_modules/@llblab/pi-state-flow/lib/migration.ts +0 -171
  139. package/node_modules/@llblab/pi-state-flow/lib/publication.ts +0 -458
@@ -1,147 +0,0 @@
1
- import {
2
- classifyArtifactFreshness,
3
- type ArtifactMetadata,
4
- type ArtifactProvenance,
5
- type ArtifactRegistry,
6
- type ArtifactSourceIdentity,
7
- } from "./artifact.ts";
8
- import type { ArtifactSourceCandidate } from "./discovery.ts";
9
- import { isObject } from "./json.ts";
10
-
11
- export const DEFAULT_ARTIFACT_MAINTENANCE_MAX_READS = 1;
12
- export const DEFAULT_ARTIFACT_MAINTENANCE_MAX_SOURCE_BYTES = 16 * 1024;
13
- export const DEFAULT_ARTIFACT_MAINTENANCE_MINIMUM_AGE_MS = 30 * 24 * 60 * 60 * 1_000;
14
-
15
- export interface ArtifactMaintenanceOptions {
16
- /** Stable cycle time supplied by the caller. */
17
- now?: Date | number | string;
18
- /** A source must be at least this old. Missing/unparseable timestamps rank as oldest. */
19
- minimumAgeMs?: number;
20
- /** Strict source-read count ceiling for this cycle. */
21
- maxReads?: number;
22
- /** Strict source-byte ceiling; bytes conservatively upper-bound source tokenizer input. */
23
- maxSourceBytes?: number;
24
- }
25
-
26
- export interface ArtifactMaintenanceRequest extends ArtifactSourceIdentity {
27
- reason: "maintenance";
28
- sourceBytes: number;
29
- }
30
-
31
- export interface ArtifactMaintenancePlan {
32
- requiresCompilation: ArtifactMaintenanceRequest[];
33
- deferred: ArtifactMaintenanceRequest[];
34
- budget: {
35
- maxReads: number;
36
- maxSourceBytes: number;
37
- usedReads: number;
38
- usedSourceBytes: number;
39
- };
40
- }
41
-
42
- function nonNegativeSafeInteger(value: number, name: string): number {
43
- if (!Number.isSafeInteger(value) || value < 0) throw new Error(`${name} must be a non-negative safe integer`);
44
- return value;
45
- }
46
-
47
- function cycleTime(value: Date | number | string | undefined): number {
48
- const timestamp = value === undefined
49
- ? Date.now()
50
- : value instanceof Date
51
- ? value.getTime()
52
- : typeof value === "number"
53
- ? value
54
- : Date.parse(value);
55
- if (!Number.isFinite(timestamp)) throw new Error("Artifact maintenance cycle time must be valid");
56
- return timestamp;
57
- }
58
-
59
- function compiledTime(entry: ArtifactProvenance | undefined, legacy: unknown): number {
60
- const timestamp = entry?.malformed === true ? undefined
61
- : entry !== undefined && Object.hasOwn(entry, "compiledAt") ? entry.compiledAt
62
- : legacy;
63
- if (typeof timestamp !== "string") return Number.NEGATIVE_INFINITY;
64
- const parsed = Date.parse(timestamp);
65
- return Number.isFinite(parsed) ? parsed : Number.NEGATIVE_INFINITY;
66
- }
67
-
68
- function request(source: ArtifactSourceCandidate): ArtifactMaintenanceRequest {
69
- return {
70
- path: source.path,
71
- hash: source.hash,
72
- reason: "maintenance",
73
- sourceBytes: source.bytes,
74
- };
75
- }
76
-
77
- /**
78
- * Select a bounded, oldest-first maintenance cohort from otherwise fresh artifacts.
79
- *
80
- * This planner is opt-in and side-effect free. Correctness invalidations remain the
81
- * responsibility of planArtifactInvalidation; they are never displaced by maintenance.
82
- */
83
- export function planArtifactMaintenance(
84
- sources: readonly ArtifactSourceCandidate[],
85
- registry: Readonly<Record<string, unknown>>,
86
- compiler: string,
87
- options: ArtifactMaintenanceOptions = {},
88
- provenance: Readonly<Record<string, ArtifactProvenance>> = {},
89
- ): ArtifactMaintenancePlan {
90
- if (!isObject(registry)) throw new Error("Artifacts must be a path-keyed JSON object");
91
- const now = cycleTime(options.now);
92
- const minimumAgeMs = nonNegativeSafeInteger(
93
- options.minimumAgeMs ?? DEFAULT_ARTIFACT_MAINTENANCE_MINIMUM_AGE_MS,
94
- "Artifact maintenance minimum age",
95
- );
96
- const maxReads = nonNegativeSafeInteger(
97
- options.maxReads ?? DEFAULT_ARTIFACT_MAINTENANCE_MAX_READS,
98
- "Artifact maintenance read budget",
99
- );
100
- const maxSourceBytes = nonNegativeSafeInteger(
101
- options.maxSourceBytes ?? DEFAULT_ARTIFACT_MAINTENANCE_MAX_SOURCE_BYTES,
102
- "Artifact maintenance source-byte budget",
103
- );
104
-
105
- const seen = new Set<string>();
106
- const eligible: { source: ArtifactSourceCandidate; compiledAt: number }[] = [];
107
- for (const source of sources) {
108
- if (seen.has(source.path)) throw new Error(`Duplicate artifact source path: ${source.path}`);
109
- seen.add(source.path);
110
- nonNegativeSafeInteger(source.bytes, `Artifact source bytes at ${source.path}`);
111
- const metadata = Object.hasOwn(registry, source.path) ? registry[source.path] : undefined;
112
- const entry = Object.hasOwn(provenance, source.path) ? provenance[source.path] : undefined;
113
- const freshness = classifyArtifactFreshness(source, metadata, compiler, false, entry);
114
- if (freshness.kind !== "fresh") continue;
115
- const compiledAt = compiledTime(entry, isObject(metadata) ? (metadata as ArtifactMetadata).compiled_at : undefined);
116
- if (compiledAt !== Number.NEGATIVE_INFINITY && now - compiledAt < minimumAgeMs) continue;
117
- eligible.push({ source, compiledAt });
118
- }
119
- eligible.sort((left, right) => {
120
- if (left.compiledAt !== right.compiledAt) return left.compiledAt - right.compiledAt;
121
- return left.source.path < right.source.path ? -1 : left.source.path > right.source.path ? 1 : 0;
122
- });
123
-
124
- const requiresCompilation: ArtifactMaintenanceRequest[] = [];
125
- const deferred: ArtifactMaintenanceRequest[] = [];
126
- let usedSourceBytes = 0;
127
- for (const candidate of eligible) {
128
- const next = request(candidate.source);
129
- if (requiresCompilation.length >= maxReads
130
- || candidate.source.bytes > maxSourceBytes - usedSourceBytes) {
131
- deferred.push(next);
132
- continue;
133
- }
134
- requiresCompilation.push(next);
135
- usedSourceBytes += candidate.source.bytes;
136
- }
137
- return {
138
- requiresCompilation,
139
- deferred,
140
- budget: {
141
- maxReads,
142
- maxSourceBytes,
143
- usedReads: requiresCompilation.length,
144
- usedSourceBytes,
145
- },
146
- };
147
- }
@@ -1,171 +0,0 @@
1
- // Domain: predecessor temporal-envelope migration planning; Git/files backends own publication and rollback.
2
- import { lstatSync, readFileSync, readdirSync } from "node:fs";
3
- import { join, resolve } from "node:path";
4
- import {
5
- captureOwnedFileBases,
6
- cwdScopePaths,
7
- parseScopeStream,
8
- serializeScopeMetadata,
9
- serializeScopeStream,
10
- sessionScopeKey,
11
- sessionScopePaths,
12
- type DurableFileBase,
13
- type OwnedFileUpdate,
14
- } from "./durable.ts";
15
- import { parseSessionRuntime, serializeSessionRuntime } from "./snapshot.ts";
16
- import { migratePreIntentState, type StateScope } from "./state.ts";
17
-
18
- export interface LegacyStorageMigration {
19
- bases: DurableFileBase[];
20
- updates: OwnedFileUpdate[];
21
- scopes: StateScope[];
22
- }
23
-
24
- interface MigrationDirectory {
25
- directory: string;
26
- scope: StateScope;
27
- cwdIdentity?: string;
28
- sessionId?: string;
29
- }
30
-
31
- /** Discover every owner-proven CWD and session cohort beneath the configured store. */
32
- function migrationDirectories(cwd: string, sessionId: string, root: string, sessionKey: string): MigrationDirectory[] {
33
- const selectedCwd = cwdScopePaths(cwd, root).directory;
34
- const selectedSession = sessionScopePaths(cwd, sessionId, root, sessionKey).directory;
35
- const directories: MigrationDirectory[] = [{ directory: root, scope: "global" }];
36
- const cwdOwners = new Map<string, string>([[selectedCwd, resolve(cwd)]]);
37
- if (lstatSync(root, { throwIfNoEntry: false }) !== undefined) {
38
- for (const entry of readdirSync(root, { withFileTypes: true })) {
39
- if (!entry.isDirectory()) continue;
40
- const directory = join(root, entry.name);
41
- let checkpoint: unknown;
42
- let meta: unknown;
43
- try { checkpoint = JSON.parse(readFileSync(join(directory, "checkpoint.json"), "utf8")); }
44
- catch { continue; }
45
- try { meta = JSON.parse(readFileSync(join(directory, "meta.json"), "utf8")); } catch { /* predecessor metadata may be absent */ }
46
- const owner = checkpoint && typeof checkpoint === "object" && !Array.isArray(checkpoint) && (checkpoint as { owner?: unknown }).owner
47
- ? (checkpoint as { owner: { cwd?: unknown } }).owner
48
- : meta && typeof meta === "object" && !Array.isArray(meta) ? (meta as { owner?: { cwd?: unknown } }).owner : undefined;
49
- if (owner && typeof owner.cwd === "string" && resolve(owner.cwd) === owner.cwd) cwdOwners.set(directory, owner.cwd);
50
- }
51
- }
52
- for (const [cwdDirectory, cwdOwner] of cwdOwners) {
53
- directories.push({ directory: cwdDirectory, scope: "cwd", cwdIdentity: cwdOwner });
54
- if (lstatSync(cwdDirectory, { throwIfNoEntry: false }) === undefined) continue;
55
- for (const entry of readdirSync(cwdDirectory, { withFileTypes: true })) {
56
- if (!entry.isDirectory()) continue;
57
- try { sessionScopeKey(entry.name); } catch { continue; }
58
- const directory = join(cwdDirectory, entry.name);
59
- let meta: unknown;
60
- let runtime: unknown;
61
- try { meta = JSON.parse(readFileSync(join(directory, "meta.json"), "utf8")); }
62
- catch { continue; }
63
- try { runtime = JSON.parse(readFileSync(join(directory, "runtime.json"), "utf8")); }
64
- catch { /* predecessor sessions keep identity in meta.json */ }
65
- const identity = meta && typeof meta === "object" && !Array.isArray(meta) && (meta as { identity?: unknown }).identity !== undefined
66
- ? (meta as { identity?: unknown }).identity
67
- : runtime && typeof runtime === "object" && !Array.isArray(runtime) ? (runtime as { identity?: unknown }).identity : undefined;
68
- if (!identity || typeof identity !== "object" || Array.isArray(identity)) continue;
69
- const owner = identity as { cwd?: unknown; sessionId?: unknown };
70
- if (owner.cwd === cwdOwner && typeof owner.sessionId === "string" && owner.sessionId.length > 0) {
71
- directories.push({ directory, scope: "session", cwdIdentity: cwdOwner, sessionId: owner.sessionId });
72
- }
73
- }
74
- }
75
- if (!directories.some(({ directory }) => directory === selectedSession)) {
76
- directories.push({ directory: selectedSession, scope: "session", cwdIdentity: resolve(cwd), sessionId });
77
- }
78
- return directories;
79
- }
80
-
81
- /** Read-only activation eligibility, before global migration or any repository publication. */
82
- export function hasCwdMaterialization(cwd: string, repositoryRoot: string): boolean {
83
- const root = resolve(repositoryRoot);
84
- const directory = cwdScopePaths(cwd, root).directory;
85
- const [checkpoint, patches, meta] = captureOwnedFileBases([
86
- join(directory, "checkpoint.json"), join(directory, "patches.jsonl"), join(directory, "meta.json"),
87
- ], root);
88
- if (checkpoint!.content !== undefined) return parseScopeStream(checkpoint!.content, patches!.content, "cwd", cwd, meta!.content) !== undefined;
89
- if (patches!.content !== undefined) throw new Error(`State Flow tail has no provable checkpoint: ${directory}`);
90
- return false;
91
- }
92
-
93
- function checkpointNeedsIntentMigration(source: string): boolean {
94
- const checkpoint = JSON.parse(source) as unknown;
95
- if (migratePreIntentState(checkpoint) !== undefined) return true;
96
- return checkpoint !== null && typeof checkpoint === "object" && !Array.isArray(checkpoint)
97
- && migratePreIntentState((checkpoint as { state?: unknown }).state) !== undefined;
98
- }
99
-
100
- /** Detect predecessor snapshots, temporal envelopes, or pre-intents semantic states without mutating the store. */
101
- export function hasLegacyStateSources(
102
- cwd: string,
103
- sessionId: string,
104
- repositoryRoot: string,
105
- sessionKey = sessionId,
106
- ): boolean {
107
- const root = resolve(repositoryRoot);
108
- return migrationDirectories(cwd, sessionId, root, sessionKey).some(({ directory, scope }) => {
109
- const checkpointPath = join(directory, "checkpoint.json");
110
- if (lstatSync(checkpointPath, { throwIfNoEntry: false }) === undefined) return false;
111
- try {
112
- if (checkpointNeedsIntentMigration(readFileSync(checkpointPath, "utf8"))) return true;
113
- const meta = JSON.parse(readFileSync(join(directory, "meta.json"), "utf8"));
114
- if (meta === null || typeof meta !== "object" || !("temporal" in meta)) return true;
115
- return scope === "session"
116
- && lstatSync(join(directory, "runtime.json"), { throwIfNoEntry: false }) === undefined
117
- && ("identity" in meta || "lineage" in meta);
118
- } catch { return true; }
119
- });
120
- }
121
-
122
- /** Plan from current scope snapshots only; old explanatory journals are never replay input. */
123
- export function planLegacyStorageMigration(
124
- cwd: string,
125
- sessionId: string,
126
- repositoryRoot: string,
127
- _origin?: string,
128
- sessionKey = sessionId,
129
- ): LegacyStorageMigration {
130
- const root = resolve(repositoryRoot);
131
- const directories = migrationDirectories(cwd, sessionId, root, sessionKey);
132
- const paths = directories.flatMap(({ directory, scope }) => [
133
- join(directory, "checkpoint.json"), join(directory, "patches.jsonl"), join(directory, "meta.json"),
134
- ...(scope === "session" ? [join(directory, "config.json"), join(directory, "runtime.json")] : []),
135
- ]);
136
- const bases = captureOwnedFileBases(paths, root);
137
- const byPath = new Map(bases.map((base) => [base.path, base]));
138
- const updates: OwnedFileUpdate[] = [];
139
- const scopes: StateScope[] = [];
140
- for (const { scope, directory, cwdIdentity, sessionId: ownedSessionId } of directories) {
141
- const checkpoint = byPath.get(join(directory, "checkpoint.json"))!;
142
- const patches = byPath.get(join(directory, "patches.jsonl"))!;
143
- const meta = byPath.get(join(directory, "meta.json"))!;
144
- if (checkpoint.content !== undefined) {
145
- const stream = parseScopeStream(checkpoint.content, patches.content, scope, scope === "cwd" ? cwdIdentity : undefined, meta.content)!;
146
- const source = serializeScopeStream(stream, scope, scope === "cwd" ? cwdIdentity : undefined);
147
- const metadata = serializeScopeMetadata(undefined, stream, scope, scope === "cwd" ? cwdIdentity : undefined, meta.content);
148
- const cohortUpdates: OwnedFileUpdate[] = [
149
- ...(checkpoint.content === source.checkpoint ? [] : [{ path: checkpoint.path, content: source.checkpoint }]),
150
- ...(patches.content === source.patches ? [] : [{ path: patches.path, content: source.patches }]),
151
- ...(meta.content === metadata ? [] : [{ path: meta.path, content: metadata }]),
152
- ];
153
- if (scope === "session") {
154
- const config = byPath.get(join(directory, "config.json"))!;
155
- const runtimeFile = byPath.get(join(directory, "runtime.json"))!;
156
- const runtime = parseSessionRuntime(config.content, runtimeFile.content, cwdIdentity!, ownedSessionId!, meta.content);
157
- if (runtime !== undefined) {
158
- const serialized = serializeSessionRuntime(runtime, cwdIdentity!, ownedSessionId!);
159
- if (runtimeFile.content !== serialized.runtime) cohortUpdates.push({ path: runtimeFile.path, content: serialized.runtime });
160
- }
161
- }
162
- if (cohortUpdates.length > 0) {
163
- if (!scopes.includes(scope)) scopes.push(scope);
164
- updates.push(...cohortUpdates);
165
- }
166
- continue;
167
- }
168
- if (patches.content !== undefined) throw new Error(`State Flow tail has no provable checkpoint: ${directory}`);
169
- }
170
- return { bases, updates, scopes };
171
- }