gnosys 5.11.4 → 5.12.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 (240) hide show
  1. package/dist/cli.js +324 -5150
  2. package/dist/index.js +364 -235
  3. package/dist/lib/addCommand.d.ts +9 -0
  4. package/dist/lib/addCommand.js +103 -0
  5. package/dist/lib/addStructuredCommand.d.ts +16 -0
  6. package/dist/lib/addStructuredCommand.js +103 -0
  7. package/dist/lib/ambiguityCommand.d.ts +4 -0
  8. package/dist/lib/ambiguityCommand.js +36 -0
  9. package/dist/lib/apiKeyVault.d.ts +78 -0
  10. package/dist/lib/apiKeyVault.js +447 -0
  11. package/dist/lib/askCommand.d.ts +13 -0
  12. package/dist/lib/askCommand.js +145 -0
  13. package/dist/lib/audioExtract.js +4 -1
  14. package/dist/lib/auditCommand.d.ts +7 -0
  15. package/dist/lib/auditCommand.js +27 -0
  16. package/dist/lib/backupCommand.d.ts +6 -0
  17. package/dist/lib/backupCommand.js +54 -0
  18. package/dist/lib/bootstrapCommand.d.ts +15 -0
  19. package/dist/lib/bootstrapCommand.js +51 -0
  20. package/dist/lib/briefingCommand.d.ts +7 -0
  21. package/dist/lib/briefingCommand.js +92 -0
  22. package/dist/lib/centralizeCommand.d.ts +5 -0
  23. package/dist/lib/centralizeCommand.js +16 -0
  24. package/dist/lib/chatCommand.d.ts +12 -0
  25. package/dist/lib/chatCommand.js +46 -0
  26. package/dist/lib/checkCommand.d.ts +4 -0
  27. package/dist/lib/checkCommand.js +133 -0
  28. package/dist/lib/clientReadOverlay.d.ts +27 -0
  29. package/dist/lib/clientReadOverlay.js +73 -0
  30. package/dist/lib/clientReadResolve.d.ts +32 -0
  31. package/dist/lib/clientReadResolve.js +84 -0
  32. package/dist/lib/commitContextCommand.d.ts +9 -0
  33. package/dist/lib/commitContextCommand.js +142 -0
  34. package/dist/lib/config.d.ts +43 -3
  35. package/dist/lib/config.js +58 -57
  36. package/dist/lib/configCommand.d.ts +10 -0
  37. package/dist/lib/configCommand.js +321 -0
  38. package/dist/lib/connectCommand.d.ts +8 -0
  39. package/dist/lib/connectCommand.js +19 -0
  40. package/dist/lib/db.d.ts +52 -0
  41. package/dist/lib/db.js +169 -1
  42. package/dist/lib/dearchiveCommand.d.ts +7 -0
  43. package/dist/lib/dearchiveCommand.js +41 -0
  44. package/dist/lib/discoverCommand.d.ts +9 -0
  45. package/dist/lib/discoverCommand.js +87 -0
  46. package/dist/lib/doctorCommand.d.ts +6 -0
  47. package/dist/lib/doctorCommand.js +256 -0
  48. package/dist/lib/dream.d.ts +42 -2
  49. package/dist/lib/dream.js +290 -30
  50. package/dist/lib/dreamCommand.d.ts +10 -0
  51. package/dist/lib/dreamCommand.js +195 -0
  52. package/dist/lib/dreamLaunchd.d.ts +2 -0
  53. package/dist/lib/dreamLaunchd.js +72 -0
  54. package/dist/lib/dreamLogCommand.d.ts +10 -0
  55. package/dist/lib/dreamLogCommand.js +58 -0
  56. package/dist/lib/dreamReport.d.ts +7 -0
  57. package/dist/lib/dreamReport.js +114 -0
  58. package/dist/lib/dreamRunLog.d.ts +121 -0
  59. package/dist/lib/dreamRunLog.js +212 -0
  60. package/dist/lib/embeddings.js +3 -0
  61. package/dist/lib/exportCommand.d.ts +18 -0
  62. package/dist/lib/exportCommand.js +101 -0
  63. package/dist/lib/fsearchCommand.d.ts +8 -0
  64. package/dist/lib/fsearchCommand.js +44 -0
  65. package/dist/lib/graphCommand.d.ts +4 -0
  66. package/dist/lib/graphCommand.js +68 -0
  67. package/dist/lib/helperGenerateCommand.d.ts +5 -0
  68. package/dist/lib/helperGenerateCommand.js +27 -0
  69. package/dist/lib/historyCommand.d.ts +5 -0
  70. package/dist/lib/historyCommand.js +51 -0
  71. package/dist/lib/hybridSearchCommand.d.ts +12 -0
  72. package/dist/lib/hybridSearchCommand.js +95 -0
  73. package/dist/lib/importCommand.d.ts +16 -0
  74. package/dist/lib/importCommand.js +89 -0
  75. package/dist/lib/importProjectCommand.d.ts +6 -0
  76. package/dist/lib/importProjectCommand.js +43 -0
  77. package/dist/lib/ingestCommand.d.ts +13 -0
  78. package/dist/lib/ingestCommand.js +95 -0
  79. package/dist/lib/installOutput.d.ts +36 -0
  80. package/dist/lib/installOutput.js +55 -0
  81. package/dist/lib/lensCommand.d.ts +20 -0
  82. package/dist/lib/lensCommand.js +61 -0
  83. package/dist/lib/lensing.d.ts +1 -0
  84. package/dist/lib/lensing.js +50 -9
  85. package/dist/lib/linksCommand.d.ts +7 -0
  86. package/dist/lib/linksCommand.js +48 -0
  87. package/dist/lib/listCommand.d.ts +8 -0
  88. package/dist/lib/listCommand.js +74 -0
  89. package/dist/lib/llm.d.ts +1 -1
  90. package/dist/lib/llm.js +26 -8
  91. package/dist/lib/localDiskCheck.d.ts +17 -0
  92. package/dist/lib/localDiskCheck.js +54 -0
  93. package/dist/lib/machineConfig.d.ts +11 -1
  94. package/dist/lib/machineConfig.js +16 -0
  95. package/dist/lib/machineRegistry.d.ts +61 -0
  96. package/dist/lib/machineRegistry.js +80 -0
  97. package/dist/lib/maintainCommand.d.ts +8 -0
  98. package/dist/lib/maintainCommand.js +34 -0
  99. package/dist/lib/masterLease.d.ts +20 -0
  100. package/dist/lib/masterLease.js +68 -0
  101. package/dist/lib/migrateCommand.d.ts +7 -0
  102. package/dist/lib/migrateCommand.js +158 -0
  103. package/dist/lib/migrateDbCommand.d.ts +9 -0
  104. package/dist/lib/migrateDbCommand.js +94 -0
  105. package/dist/lib/modelValidation.d.ts +5 -0
  106. package/dist/lib/modelValidation.js +27 -0
  107. package/dist/lib/openrouterTiers.d.ts +29 -0
  108. package/dist/lib/openrouterTiers.js +113 -0
  109. package/dist/lib/prefCommand.d.ts +10 -0
  110. package/dist/lib/prefCommand.js +118 -0
  111. package/dist/lib/projectsCommand.d.ts +8 -0
  112. package/dist/lib/projectsCommand.js +131 -0
  113. package/dist/lib/readCommand.d.ts +7 -0
  114. package/dist/lib/readCommand.js +62 -0
  115. package/dist/lib/recall.d.ts +3 -0
  116. package/dist/lib/recall.js +19 -4
  117. package/dist/lib/recallCommand.d.ts +11 -0
  118. package/dist/lib/recallCommand.js +112 -0
  119. package/dist/lib/reflectCommand.d.ts +8 -0
  120. package/dist/lib/reflectCommand.js +61 -0
  121. package/dist/lib/reindexCommand.d.ts +4 -0
  122. package/dist/lib/reindexCommand.js +34 -0
  123. package/dist/lib/reindexGraphCommand.d.ts +4 -0
  124. package/dist/lib/reindexGraphCommand.js +12 -0
  125. package/dist/lib/reinforceCommand.d.ts +8 -0
  126. package/dist/lib/reinforceCommand.js +40 -0
  127. package/dist/lib/remote.d.ts +5 -1
  128. package/dist/lib/remote.js +5 -1
  129. package/dist/lib/remoteWizard.d.ts +24 -5
  130. package/dist/lib/remoteWizard.js +308 -319
  131. package/dist/lib/restoreCommand.d.ts +5 -0
  132. package/dist/lib/restoreCommand.js +35 -0
  133. package/dist/lib/sandboxStartCommand.d.ts +6 -0
  134. package/dist/lib/sandboxStartCommand.js +25 -0
  135. package/dist/lib/sandboxStatusCommand.d.ts +4 -0
  136. package/dist/lib/sandboxStatusCommand.js +24 -0
  137. package/dist/lib/sandboxStopCommand.d.ts +4 -0
  138. package/dist/lib/sandboxStopCommand.js +21 -0
  139. package/dist/lib/searchCommand.d.ts +9 -0
  140. package/dist/lib/searchCommand.js +90 -0
  141. package/dist/lib/semanticSearchCommand.d.ts +8 -0
  142. package/dist/lib/semanticSearchCommand.js +52 -0
  143. package/dist/lib/setup/configSetRender.js +2 -0
  144. package/dist/lib/setup/providerGlyphs.d.ts +19 -0
  145. package/dist/lib/setup/providerGlyphs.js +42 -0
  146. package/dist/lib/setup/remoteRender.d.ts +31 -1
  147. package/dist/lib/setup/remoteRender.js +95 -4
  148. package/dist/lib/setup/sections/providers.d.ts +17 -0
  149. package/dist/lib/setup/sections/providers.js +255 -0
  150. package/dist/lib/setup/sections/routing.d.ts +2 -6
  151. package/dist/lib/setup/sections/routing.js +33 -85
  152. package/dist/lib/setup/sections/taskRoutingEditor.d.ts +17 -0
  153. package/dist/lib/setup/sections/taskRoutingEditor.js +149 -0
  154. package/dist/lib/setup/summary.d.ts +9 -0
  155. package/dist/lib/setup/summary.js +51 -37
  156. package/dist/lib/setup/ui/status.d.ts +1 -0
  157. package/dist/lib/setup/ui/status.js +2 -0
  158. package/dist/lib/setup.d.ts +108 -3
  159. package/dist/lib/setup.js +762 -157
  160. package/dist/lib/setupKeys.d.ts +42 -0
  161. package/dist/lib/setupKeys.js +564 -0
  162. package/dist/lib/setupRemoteCommand.d.ts +4 -0
  163. package/dist/lib/setupRemoteCommand.js +28 -0
  164. package/dist/lib/setupRemotePullCommand.d.ts +5 -0
  165. package/dist/lib/setupRemotePullCommand.js +52 -0
  166. package/dist/lib/setupRemotePushCommand.d.ts +5 -0
  167. package/dist/lib/setupRemotePushCommand.js +57 -0
  168. package/dist/lib/setupRemoteResolveCommand.d.ts +4 -0
  169. package/dist/lib/setupRemoteResolveCommand.js +48 -0
  170. package/dist/lib/setupRemoteStatusCommand.d.ts +4 -0
  171. package/dist/lib/setupRemoteStatusCommand.js +73 -0
  172. package/dist/lib/setupRemoteSyncCommand.d.ts +6 -0
  173. package/dist/lib/setupRemoteSyncCommand.js +65 -0
  174. package/dist/lib/setupSyncProjectsCommand.d.ts +4 -0
  175. package/dist/lib/setupSyncProjectsCommand.js +292 -0
  176. package/dist/lib/staleCommand.d.ts +8 -0
  177. package/dist/lib/staleCommand.js +34 -0
  178. package/dist/lib/statsCommand.d.ts +6 -0
  179. package/dist/lib/statsCommand.js +142 -0
  180. package/dist/lib/statusCommand.d.ts +18 -0
  181. package/dist/lib/statusCommand.js +250 -0
  182. package/dist/lib/storesCommand.d.ts +2 -0
  183. package/dist/lib/storesCommand.js +4 -0
  184. package/dist/lib/syncClient.d.ts +47 -0
  185. package/dist/lib/syncClient.js +212 -0
  186. package/dist/lib/syncCommand.d.ts +6 -0
  187. package/dist/lib/syncCommand.js +57 -0
  188. package/dist/lib/syncDoctorCommand.d.ts +5 -0
  189. package/dist/lib/syncDoctorCommand.js +100 -0
  190. package/dist/lib/syncIngest.d.ts +19 -0
  191. package/dist/lib/syncIngest.js +152 -0
  192. package/dist/lib/syncIngestLaunchd.d.ts +8 -0
  193. package/dist/lib/syncIngestLaunchd.js +93 -0
  194. package/dist/lib/syncIngestStartup.d.ts +5 -0
  195. package/dist/lib/syncIngestStartup.js +29 -0
  196. package/dist/lib/syncIngestSystemd.d.ts +10 -0
  197. package/dist/lib/syncIngestSystemd.js +97 -0
  198. package/dist/lib/syncIngestTimer.d.ts +8 -0
  199. package/dist/lib/syncIngestTimer.js +27 -0
  200. package/dist/lib/syncIngestTimerCommand.d.ts +7 -0
  201. package/dist/lib/syncIngestTimerCommand.js +83 -0
  202. package/dist/lib/syncLock.d.ts +6 -0
  203. package/dist/lib/syncLock.js +74 -0
  204. package/dist/lib/syncSnapshot.d.ts +30 -0
  205. package/dist/lib/syncSnapshot.js +184 -0
  206. package/dist/lib/syncStaging.d.ts +81 -0
  207. package/dist/lib/syncStaging.js +239 -0
  208. package/dist/lib/tagsAddCommand.d.ts +8 -0
  209. package/dist/lib/tagsAddCommand.js +18 -0
  210. package/dist/lib/tagsCommand.d.ts +4 -0
  211. package/dist/lib/tagsCommand.js +16 -0
  212. package/dist/lib/timelineCommand.d.ts +7 -0
  213. package/dist/lib/timelineCommand.js +49 -0
  214. package/dist/lib/traceCommand.d.ts +6 -0
  215. package/dist/lib/traceCommand.js +39 -0
  216. package/dist/lib/traverseCommand.d.ts +6 -0
  217. package/dist/lib/traverseCommand.js +58 -0
  218. package/dist/lib/updateCommand.d.ts +13 -0
  219. package/dist/lib/updateCommand.js +67 -0
  220. package/dist/lib/updateStatusCommand.d.ts +5 -0
  221. package/dist/lib/updateStatusCommand.js +38 -0
  222. package/dist/lib/webAddCommand.d.ts +8 -0
  223. package/dist/lib/webAddCommand.js +55 -0
  224. package/dist/lib/webBuildCommand.d.ts +10 -0
  225. package/dist/lib/webBuildCommand.js +65 -0
  226. package/dist/lib/webBuildIndexCommand.d.ts +8 -0
  227. package/dist/lib/webBuildIndexCommand.js +37 -0
  228. package/dist/lib/webIngestCommand.d.ts +11 -0
  229. package/dist/lib/webIngestCommand.js +51 -0
  230. package/dist/lib/webInitCommand.d.ts +9 -0
  231. package/dist/lib/webInitCommand.js +167 -0
  232. package/dist/lib/webRemoveCommand.d.ts +5 -0
  233. package/dist/lib/webRemoveCommand.js +41 -0
  234. package/dist/lib/webStatusCommand.d.ts +5 -0
  235. package/dist/lib/webStatusCommand.js +94 -0
  236. package/dist/lib/webUpdateCommand.d.ts +7 -0
  237. package/dist/lib/webUpdateCommand.js +72 -0
  238. package/dist/lib/workingSetCommand.d.ts +6 -0
  239. package/dist/lib/workingSetCommand.js +37 -0
  240. package/package.json +2 -1
@@ -0,0 +1,83 @@
1
+ import { printStatus } from "./setup/ui/status.js";
2
+ import { getSyncIngestTimerStatus, installSyncIngestTimer, uninstallSyncIngestTimer, } from "./syncIngestTimer.js";
3
+ export async function runSyncIngestTimerCommand(opts) {
4
+ const intervalMinutes = Math.max(1, parseInt(opts.interval ?? "15", 10) || 15);
5
+ const timerStatus = getSyncIngestTimerStatus();
6
+ if (opts.json) {
7
+ if (opts.install) {
8
+ try {
9
+ const path = installSyncIngestTimer(intervalMinutes);
10
+ console.log(JSON.stringify({
11
+ action: "install",
12
+ installed: true,
13
+ path,
14
+ intervalMinutes,
15
+ platform: timerStatus.platform,
16
+ }));
17
+ }
18
+ catch (err) {
19
+ process.exitCode = 1;
20
+ console.log(JSON.stringify({
21
+ action: "install",
22
+ installed: false,
23
+ error: err instanceof Error ? err.message : String(err),
24
+ platform: timerStatus.platform,
25
+ }));
26
+ }
27
+ return;
28
+ }
29
+ if (opts.uninstall) {
30
+ try {
31
+ const path = uninstallSyncIngestTimer();
32
+ console.log(JSON.stringify({ action: "uninstall", installed: false, path }));
33
+ }
34
+ catch (err) {
35
+ process.exitCode = 1;
36
+ console.log(JSON.stringify({
37
+ action: "uninstall",
38
+ error: err instanceof Error ? err.message : String(err),
39
+ }));
40
+ }
41
+ return;
42
+ }
43
+ console.log(JSON.stringify({ ...getSyncIngestTimerStatus(), intervalMinutes }));
44
+ return;
45
+ }
46
+ if (timerStatus.platform === "unsupported") {
47
+ console.log("OS-level ingest timer is not supported on this platform.");
48
+ console.log("Windows: use Task Scheduler manually or run `gnosys setup remote doctor --ingest`.");
49
+ if (opts.install || opts.uninstall)
50
+ process.exitCode = 1;
51
+ return;
52
+ }
53
+ if (opts.install) {
54
+ try {
55
+ const installedPath = installSyncIngestTimer(intervalMinutes);
56
+ printStatus("ok", `Ingest timer installed (${installedPath}, every ${intervalMinutes} min)`);
57
+ }
58
+ catch (err) {
59
+ printStatus("fail", err instanceof Error ? err.message : String(err));
60
+ process.exitCode = 1;
61
+ }
62
+ return;
63
+ }
64
+ if (opts.uninstall) {
65
+ try {
66
+ const removedPath = uninstallSyncIngestTimer();
67
+ printStatus("ok", `Ingest timer uninstalled (${removedPath})`);
68
+ }
69
+ catch (err) {
70
+ printStatus("fail", err instanceof Error ? err.message : String(err));
71
+ process.exitCode = 1;
72
+ }
73
+ return;
74
+ }
75
+ const current = getSyncIngestTimerStatus();
76
+ if (current.installed) {
77
+ console.log(`Ingest timer: installed (${current.path})`);
78
+ }
79
+ else {
80
+ console.log("Ingest timer: not installed");
81
+ console.log("Install with: gnosys setup remote timer --install");
82
+ }
83
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Sync advisory lock on the master's **local** ~/.gnosys (never on network share).
3
+ */
4
+ /** Synchronous ingest lock for master sweep (same directory as lock file). */
5
+ export declare function acquireWriteLockSync(lockPath: string, operation: string): () => void;
6
+ export declare function releaseWriteLockSync(lockPath: string): void;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Sync advisory lock on the master's **local** ~/.gnosys (never on network share).
3
+ */
4
+ import fs from "fs";
5
+ import path from "path";
6
+ const LOCK_STALE_MS = 120_000;
7
+ function readLock(lockPath) {
8
+ try {
9
+ const raw = fs.readFileSync(lockPath, "utf-8");
10
+ const o = JSON.parse(raw);
11
+ if (typeof o.pid === "number" && typeof o.timestamp === "number")
12
+ return o;
13
+ }
14
+ catch {
15
+ // no lock
16
+ }
17
+ return null;
18
+ }
19
+ function isStale(lock) {
20
+ if (Date.now() - lock.timestamp > LOCK_STALE_MS)
21
+ return true;
22
+ try {
23
+ process.kill(lock.pid, 0);
24
+ return false;
25
+ }
26
+ catch {
27
+ return true;
28
+ }
29
+ }
30
+ /** Synchronous ingest lock for master sweep (same directory as lock file). */
31
+ export function acquireWriteLockSync(lockPath, operation) {
32
+ fs.mkdirSync(path.dirname(lockPath), { recursive: true });
33
+ const start = Date.now();
34
+ while (true) {
35
+ const existing = readLock(lockPath);
36
+ if (existing && !isStale(existing)) {
37
+ if (Date.now() - start > 30_000) {
38
+ throw new Error(`ingest lock timeout (held by pid ${existing.pid})`);
39
+ }
40
+ // busy-wait briefly
41
+ const waitUntil = Date.now() + 50;
42
+ while (Date.now() < waitUntil) {
43
+ /* spin */
44
+ }
45
+ continue;
46
+ }
47
+ if (existing) {
48
+ try {
49
+ fs.unlinkSync(lockPath);
50
+ }
51
+ catch {
52
+ // race
53
+ }
54
+ }
55
+ try {
56
+ fs.writeFileSync(lockPath, JSON.stringify({ pid: process.pid, timestamp: Date.now(), operation }));
57
+ return () => releaseWriteLockSync(lockPath);
58
+ }
59
+ catch {
60
+ if (Date.now() - start > 30_000)
61
+ throw new Error("failed to acquire ingest lock");
62
+ }
63
+ }
64
+ }
65
+ export function releaseWriteLockSync(lockPath) {
66
+ try {
67
+ const existing = readLock(lockPath);
68
+ if (existing?.pid === process.pid)
69
+ fs.unlinkSync(lockPath);
70
+ }
71
+ catch {
72
+ // ignore
73
+ }
74
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * v13 snapshot publication — immutable DB copies + manifest with epoch fencing.
3
+ */
4
+ export interface SnapshotManifestFile {
5
+ epoch: number;
6
+ seq: number;
7
+ snapshotFile: string;
8
+ publishedAt: string;
9
+ checksum: string;
10
+ sizeBytes: number;
11
+ }
12
+ export declare function masterSnapshotsDir(masterPath: string): string;
13
+ export declare function clientSnapshotStore(masterPath: string): string;
14
+ /** Publish a new immutable snapshot on the master (re-validates lease epoch first). */
15
+ export declare function publishMasterSnapshot(masterPath: string): Promise<SnapshotManifestFile | null>;
16
+ export declare function compareSnapshotVersion(accepted: {
17
+ epoch: number;
18
+ seq: number;
19
+ } | null, incoming: {
20
+ epoch: number;
21
+ seq: number;
22
+ }): boolean;
23
+ /** Copy master snapshot to local client store atomically. */
24
+ export declare function acceptClientSnapshot(masterPath: string, manifest: SnapshotManifestFile): {
25
+ ok: boolean;
26
+ localPath?: string;
27
+ reason?: string;
28
+ };
29
+ export declare function getClientAcceptedManifest(masterPath: string): SnapshotManifestFile | null;
30
+ export declare function formatSnapshotAge(publishedAt: string): string;
@@ -0,0 +1,184 @@
1
+ /**
2
+ * v13 snapshot publication — immutable DB copies + manifest with epoch fencing.
3
+ */
4
+ import { createHash } from "crypto";
5
+ import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, unlinkSync, } from "fs";
6
+ import path from "path";
7
+ import { GnosysDB } from "./db.js";
8
+ import { getGnosysHome } from "./paths.js";
9
+ import { atomicWriteFileSync } from "./atomicWrite.js";
10
+ import { assertMasterLeaseHeld, readMasterMarker, touchMasterMarkerHeartbeat, validateLeaseEpochBeforeWrite, } from "./masterLease.js";
11
+ import { ensureMachineConfig } from "./machineConfig.js";
12
+ import { acquireWriteLockSync } from "./syncLock.js";
13
+ const SNAPSHOT_RETENTION = 2;
14
+ const MANIFEST_FILENAME = "snapshot-manifest.json";
15
+ export function masterSnapshotsDir(masterPath) {
16
+ return path.join(masterPath, "snapshots");
17
+ }
18
+ export function clientSnapshotStore(masterPath) {
19
+ const key = createHash("sha256").update(path.resolve(masterPath)).digest("hex").slice(0, 16);
20
+ return path.join(getGnosysHome(), "client-snapshots", key);
21
+ }
22
+ function sha256File(filePath) {
23
+ const buf = readFileSync(filePath);
24
+ return createHash("sha256").update(buf).digest("hex");
25
+ }
26
+ function readManifestFile(masterPath) {
27
+ const p = path.join(masterSnapshotsDir(masterPath), MANIFEST_FILENAME);
28
+ if (!existsSync(p))
29
+ return null;
30
+ try {
31
+ return JSON.parse(readFileSync(p, "utf-8"));
32
+ }
33
+ catch {
34
+ return null;
35
+ }
36
+ }
37
+ function writeManifestFile(masterPath, manifest) {
38
+ const dir = masterSnapshotsDir(masterPath);
39
+ mkdirSync(dir, { recursive: true });
40
+ atomicWriteFileSync(path.join(dir, MANIFEST_FILENAME), JSON.stringify(manifest, null, 2) + "\n");
41
+ }
42
+ function gcOldSnapshots(masterPath, keepFiles) {
43
+ const dir = masterSnapshotsDir(masterPath);
44
+ if (!existsSync(dir))
45
+ return;
46
+ for (const name of readdirSync(dir)) {
47
+ if (!name.endsWith(".db"))
48
+ continue;
49
+ if (keepFiles.has(name))
50
+ continue;
51
+ try {
52
+ unlinkSync(path.join(dir, name));
53
+ }
54
+ catch {
55
+ // ignore
56
+ }
57
+ }
58
+ }
59
+ /** Publish a new immutable snapshot on the master (re-validates lease epoch first). */
60
+ export async function publishMasterSnapshot(masterPath) {
61
+ const { config: mc } = ensureMachineConfig();
62
+ const lockPath = path.join(getGnosysHome(), "master-ingest.lock");
63
+ const release = acquireWriteLockSync(lockPath, "snapshot-publish");
64
+ try {
65
+ touchMasterMarkerHeartbeat(masterPath);
66
+ assertMasterLeaseHeld(masterPath, mc.machineId);
67
+ const marker = readMasterMarker(masterPath);
68
+ if (!marker)
69
+ return null;
70
+ validateLeaseEpochBeforeWrite(masterPath, marker.epoch, mc.machineId);
71
+ const masterDb = new GnosysDB(masterPath);
72
+ if (!masterDb.isAvailable()) {
73
+ masterDb.close();
74
+ return null;
75
+ }
76
+ const prevDb = masterDb.getSnapshotManifest();
77
+ const prevFile = readManifestFile(masterPath);
78
+ const prevSeq = Math.max(prevDb?.seq ?? 0, prevFile?.seq ?? 0);
79
+ const nextSeq = prevSeq + 1;
80
+ const snapDir = masterSnapshotsDir(masterPath);
81
+ mkdirSync(snapDir, { recursive: true });
82
+ const snapName = `snap-${marker.epoch}-${nextSeq}.db`;
83
+ const finalPath = path.join(snapDir, snapName);
84
+ const tmpPath = path.join(snapDir, `.${snapName}.tmp`);
85
+ await masterDb.backup(path.dirname(tmpPath));
86
+ const created = readdirSync(snapDir).find((n) => n.startsWith("gnosys-backup-") && n.endsWith(".db"));
87
+ if (created) {
88
+ renameSync(path.join(snapDir, created), tmpPath);
89
+ }
90
+ if (existsSync(tmpPath)) {
91
+ renameSync(tmpPath, finalPath);
92
+ }
93
+ const checksum = sha256File(finalPath);
94
+ const sizeBytes = statSync(finalPath).size;
95
+ const publishedAt = new Date().toISOString();
96
+ const fileManifest = {
97
+ epoch: marker.epoch,
98
+ seq: nextSeq,
99
+ snapshotFile: snapName,
100
+ publishedAt,
101
+ checksum,
102
+ sizeBytes,
103
+ };
104
+ writeManifestFile(masterPath, fileManifest);
105
+ masterDb.publishSnapshotManifest({
106
+ epoch: marker.epoch,
107
+ seq: nextSeq,
108
+ snapshotPath: finalPath,
109
+ publishedAt,
110
+ checksum,
111
+ sizeBytes,
112
+ heartbeatAt: publishedAt,
113
+ });
114
+ const keep = new Set(readdirSync(snapDir)
115
+ .filter((n) => n.endsWith(".db"))
116
+ .sort()
117
+ .slice(-SNAPSHOT_RETENTION));
118
+ gcOldSnapshots(masterPath, keep);
119
+ masterDb.close();
120
+ return fileManifest;
121
+ }
122
+ finally {
123
+ release();
124
+ }
125
+ }
126
+ export function compareSnapshotVersion(accepted, incoming) {
127
+ if (!accepted)
128
+ return true;
129
+ if (incoming.epoch > accepted.epoch)
130
+ return true;
131
+ if (incoming.epoch === accepted.epoch && incoming.seq > accepted.seq)
132
+ return true;
133
+ return false;
134
+ }
135
+ /** Copy master snapshot to local client store atomically. */
136
+ export function acceptClientSnapshot(masterPath, manifest) {
137
+ const src = path.join(masterSnapshotsDir(masterPath), manifest.snapshotFile);
138
+ if (!existsSync(src)) {
139
+ return { ok: false, reason: "snapshot file missing on master" };
140
+ }
141
+ const localChecksum = sha256File(src);
142
+ if (localChecksum !== manifest.checksum) {
143
+ return { ok: false, reason: "snapshot checksum mismatch" };
144
+ }
145
+ const store = clientSnapshotStore(masterPath);
146
+ mkdirSync(store, { recursive: true });
147
+ const tmp = path.join(store, ".gnosys.db.tmp");
148
+ const current = path.join(store, "gnosys.db");
149
+ copyFileSync(src, tmp);
150
+ const roundTrip = sha256File(tmp);
151
+ if (roundTrip !== manifest.checksum) {
152
+ try {
153
+ unlinkSync(tmp);
154
+ }
155
+ catch {
156
+ // ignore
157
+ }
158
+ return { ok: false, reason: "local copy verification failed" };
159
+ }
160
+ renameSync(tmp, current);
161
+ const metaPath = path.join(store, "accepted-manifest.json");
162
+ atomicWriteFileSync(metaPath, JSON.stringify(manifest, null, 2) + "\n");
163
+ return { ok: true, localPath: current };
164
+ }
165
+ export function getClientAcceptedManifest(masterPath) {
166
+ const metaPath = path.join(clientSnapshotStore(masterPath), "accepted-manifest.json");
167
+ if (!existsSync(metaPath))
168
+ return null;
169
+ try {
170
+ return JSON.parse(readFileSync(metaPath, "utf-8"));
171
+ }
172
+ catch {
173
+ return null;
174
+ }
175
+ }
176
+ export function formatSnapshotAge(publishedAt) {
177
+ const ms = Date.now() - new Date(publishedAt).getTime();
178
+ const minutes = Math.max(0, Math.floor(ms / 60_000));
179
+ if (minutes < 1)
180
+ return "just now";
181
+ if (minutes === 1)
182
+ return "1 minute ago";
183
+ return `${minutes} minutes ago`;
184
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * v13 hardened JSON staging for multi-machine sync.
3
+ *
4
+ * Clients write small JSON files under master-folder/.gnosys-staging/<machineId>/.
5
+ * Master ingest walks files ordered by ledger firstSeenAt (see GnosysDB.sync_staging_ledger).
6
+ */
7
+ import type { GnosysDB } from "./db.js";
8
+ /** Current staged-memory JSON schema version. */
9
+ export declare const STAGING_SCHEMA_VERSION = 1;
10
+ export declare const KNOWN_STAGING_SCHEMA_VERSIONS: Set<number>;
11
+ export declare const UNKNOWN_SCHEMA_MESSAGE = "Update your master machine to a newer version of Gnosys.";
12
+ export interface StagedMemoryPayload {
13
+ schemaVersion: number;
14
+ id: string;
15
+ title: string;
16
+ category: string;
17
+ content: string;
18
+ tags: string[];
19
+ project_id: string | null;
20
+ scope: string;
21
+ machineId: string;
22
+ writtenAt: string;
23
+ checksum: string;
24
+ }
25
+ export type StagedFileParseResult = {
26
+ ok: true;
27
+ payload: StagedMemoryPayload;
28
+ filePath: string;
29
+ } | {
30
+ ok: false;
31
+ filePath: string;
32
+ reason: string;
33
+ quarantine: boolean;
34
+ };
35
+ export declare function stagingRoot(masterPath: string): string;
36
+ export declare function machineStagingDir(masterPath: string, machineId: string): string;
37
+ /** Alias used by remoteWizard and tests. */
38
+ export declare const stagingDirForMachine: typeof machineStagingDir;
39
+ export declare function clientPresencePath(masterPath: string, machineId: string): string;
40
+ export declare function failedQuarantineDir(masterPath: string, machineId: string): string;
41
+ export declare function buildStagingFileName(memoryUlid: string, unixMs?: number): string;
42
+ /** SHA-256 of canonical JSON body (all fields except checksum). */
43
+ export declare function stagingPayloadChecksum(body: Omit<StagedMemoryPayload, "checksum">): string;
44
+ export declare function buildStagedMemoryPayload(input: {
45
+ id: string;
46
+ title: string;
47
+ category: string;
48
+ content: string;
49
+ machineId: string;
50
+ tags?: string[];
51
+ project_id?: string | null;
52
+ scope?: string;
53
+ writtenAt?: string;
54
+ schemaVersion?: number;
55
+ }): StagedMemoryPayload;
56
+ /**
57
+ * Atomically write a staged memory JSON file (`.tmp` in the same directory, then rename).
58
+ * Returns the final file path relative to the machine staging dir.
59
+ */
60
+ export declare function writeStagedMemoryFile(masterPath: string, machineId: string, payload: StagedMemoryPayload): string;
61
+ export declare function parseStagedFile(filePath: string): StagedFileParseResult;
62
+ /** Move a bad staging file into machineId/failed/ (does not delete). */
63
+ export declare function quarantineStagingFile(filePath: string, masterPath: string, machineId: string): string;
64
+ /** Move stale `.tmp` files in a machine staging dir into `failed/`. */
65
+ export declare function quarantineStaleTmpFiles(masterPath: string, machineId: string): number;
66
+ export declare function countFailedStagingFiles(masterPath: string, machineId: string): number;
67
+ export interface StagingQueueEntry {
68
+ filePath: string;
69
+ fileName: string;
70
+ memoryUlid: string;
71
+ sortKey: string;
72
+ }
73
+ /**
74
+ * List pending `.json` staging files for a machine (excludes failed/, tmp, presence).
75
+ * Sort by ledger firstSeenAt when db is provided, else by filename timestamp prefix.
76
+ */
77
+ export declare function listPendingStagingQueue(masterPath: string, machineId: string, masterDb?: GnosysDB): StagingQueueEntry[];
78
+ /** Master read-back: memory row exists after ingest before deleting staging file. */
79
+ export declare function verifyMemoryExistsInDb(db: GnosysDB, memoryUlid: string): boolean;
80
+ /** Record first observation in the master ledger (idempotent upsert without clobbering status). */
81
+ export declare function observeStagingFile(masterDb: GnosysDB, machineId: string, fileName: string, memoryUlid: string | null): void;