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,57 @@
1
+ import path from "path";
2
+ import { GnosysDB } from "./db.js";
3
+ import { readProjectIdentity } from "./projectIdentity.js";
4
+ import { syncToTarget } from "./rulesGen.js";
5
+ export async function runSyncCommand(opts) {
6
+ const projectDir = opts.directory ? path.resolve(opts.directory) : process.cwd();
7
+ const target = opts.global ? "global" : (opts.target || null);
8
+ let centralDb = null;
9
+ try {
10
+ centralDb = GnosysDB.openCentral();
11
+ if (!centralDb.isAvailable()) {
12
+ console.error("Central DB not available (better-sqlite3 missing).");
13
+ process.exitCode = 1;
14
+ return;
15
+ }
16
+ if (target === "global") {
17
+ const results = await syncToTarget(centralDb, projectDir, "global", null);
18
+ for (const result of results) {
19
+ const action = result.created ? "Created" : "Updated";
20
+ console.log(`${action} global rules: ${result.filePath}`);
21
+ console.log(` Preferences injected: ${result.prefCount}`);
22
+ }
23
+ console.log(`\nContent is inside <!-- GNOSYS:START --> / <!-- GNOSYS:END --> markers.`);
24
+ console.log(`User content outside these markers is preserved.`);
25
+ return;
26
+ }
27
+ const identity = await readProjectIdentity(projectDir);
28
+ if (!identity) {
29
+ console.error("No project identity found. Run 'gnosys init' first.");
30
+ process.exitCode = 1;
31
+ return;
32
+ }
33
+ const resolvedTarget = target || identity.agentRulesTarget || "all";
34
+ const results = await syncToTarget(centralDb, projectDir, resolvedTarget, identity.projectId);
35
+ if (results.length === 0) {
36
+ console.error("No targets found. Create a CLAUDE.md, .cursor/, or .codex/ directory first.");
37
+ process.exitCode = 1;
38
+ return;
39
+ }
40
+ for (const result of results) {
41
+ const action = result.created ? "Created" : "Updated";
42
+ console.log(`${action} rules file: ${result.filePath}`);
43
+ console.log(` Preferences injected: ${result.prefCount}`);
44
+ console.log(` Project conventions: ${result.conventionCount}`);
45
+ }
46
+ console.log(`\nContent is inside <!-- GNOSYS:START --> / <!-- GNOSYS:END --> markers.`);
47
+ console.log(`User content outside these markers is preserved.`);
48
+ }
49
+ catch (err) {
50
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
51
+ process.exitCode = 1;
52
+ return;
53
+ }
54
+ finally {
55
+ centralDb?.close();
56
+ }
57
+ }
@@ -0,0 +1,5 @@
1
+ export declare function runSyncDoctorCommand(opts: {
2
+ json?: boolean;
3
+ ingest?: boolean;
4
+ quiet?: boolean;
5
+ }): Promise<void>;
@@ -0,0 +1,100 @@
1
+ import { GnosysDB } from "./db.js";
2
+ import { getConfiguredRemotePath } from "./remote.js";
3
+ import { readMachineConfig } from "./machineConfig.js";
4
+ import { getV13SyncStatus } from "./syncClient.js";
5
+ import { runMasterIngestSweep } from "./syncIngest.js";
6
+ import { getSyncIngestTimerStatus } from "./syncIngestTimer.js";
7
+ import { quarantineStaleTmpFiles, stagingRoot } from "./syncStaging.js";
8
+ import { existsSync, readdirSync } from "fs";
9
+ import { readMasterMarker } from "./masterLease.js";
10
+ import { printStatus } from "./setup/ui/status.js";
11
+ export async function runSyncDoctorCommand(opts) {
12
+ let db = null;
13
+ try {
14
+ db = GnosysDB.openLocal();
15
+ if (!db.isAvailable()) {
16
+ console.error("Central DB not available.");
17
+ process.exitCode = 1;
18
+ return;
19
+ }
20
+ const masterPath = getConfiguredRemotePath(db);
21
+ const mc = readMachineConfig();
22
+ if (!masterPath || !mc?.remote.enabled) {
23
+ console.log("Multi-machine sync is not configured.");
24
+ return;
25
+ }
26
+ const status = getV13SyncStatus(db);
27
+ const marker = readMasterMarker(masterPath);
28
+ const timerStatus = getSyncIngestTimerStatus();
29
+ const report = {
30
+ role: status.role,
31
+ masterPath,
32
+ masterReachable: status.masterReachable,
33
+ waitingToSync: status.waitingToSync,
34
+ failedToSync: status.failedToSync,
35
+ pendingOfflineAdds: status.pendingOfflineAdds,
36
+ masterEpoch: marker?.epoch ?? null,
37
+ snapshotAge: status.snapshotAge,
38
+ timerInstalled: timerStatus.installed,
39
+ timerPath: timerStatus.path || null,
40
+ timerPlatform: timerStatus.platform,
41
+ };
42
+ let ingestResult = null;
43
+ if (opts.ingest && mc.remote.role === "master") {
44
+ const root = stagingRoot(masterPath);
45
+ if (existsSync(root)) {
46
+ for (const id of readdirSync(root)) {
47
+ if (!id.startsWith("."))
48
+ quarantineStaleTmpFiles(masterPath, id);
49
+ }
50
+ }
51
+ ingestResult = runMasterIngestSweep(masterPath, {
52
+ quiet: opts.quiet || !!opts.json,
53
+ });
54
+ if (ingestResult.errors.length > 0) {
55
+ process.exitCode = 1;
56
+ }
57
+ if (opts.json) {
58
+ report.ingest = ingestResult;
59
+ }
60
+ }
61
+ if (opts.json) {
62
+ console.log(JSON.stringify(report, null, 2));
63
+ return;
64
+ }
65
+ console.log("\nGnosys sync doctor (v13)\n");
66
+ for (const line of status.lines)
67
+ console.log(line);
68
+ if (marker) {
69
+ console.log(`\nMaster lease: epoch ${marker.epoch}, holder ${marker.holderMachineId}`);
70
+ }
71
+ if (timerStatus.installed) {
72
+ console.log(`\nIngest timer: installed (${timerStatus.path})`);
73
+ }
74
+ else if (timerStatus.platform === "unsupported") {
75
+ console.log("\nIngest timer: not available on this platform (Windows — use doctor --ingest manually)");
76
+ }
77
+ else {
78
+ console.log("\nIngest timer: not installed");
79
+ console.log("Install with: gnosys setup remote timer --install");
80
+ }
81
+ if (status.failedToSync > 0) {
82
+ printStatus("progress", "remediation", "inspect master-folder/.gnosys-staging/*/failed/");
83
+ }
84
+ if (!status.masterReachable) {
85
+ printStatus("warn", "mount or VPN path to master folder, then re-run doctor");
86
+ }
87
+ if (opts.ingest && mc.remote.role === "master" && ingestResult && !opts.quiet) {
88
+ console.log(`\nIngest sweep: +${ingestResult.ingested} ingested, ${ingestResult.skipped} skipped, ${ingestResult.quarantined} quarantined`);
89
+ for (const e of ingestResult.errors)
90
+ printStatus("fail", e);
91
+ }
92
+ }
93
+ catch (err) {
94
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
95
+ process.exitCode = 1;
96
+ }
97
+ finally {
98
+ db?.close();
99
+ }
100
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * v13 master ingest sweep — process staged JSON into gnosys.db.
3
+ */
4
+ export interface IngestSweepResult {
5
+ ingested: number;
6
+ skipped: number;
7
+ quarantined: number;
8
+ errors: string[];
9
+ }
10
+ export interface IngestSweepOptions {
11
+ /** Suppress stdout (for timer / background contexts). */
12
+ quiet?: boolean;
13
+ /** Write a single JSON object to stdout with the sweep result. */
14
+ json?: boolean;
15
+ }
16
+ /**
17
+ * Always-on cheap ingest sweep (agent start + timer). Single-writer lock on local disk.
18
+ */
19
+ export declare function runMasterIngestSweep(masterPath: string, opts?: IngestSweepOptions): IngestSweepResult;
@@ -0,0 +1,152 @@
1
+ /**
2
+ * v13 master ingest sweep — process staged JSON into gnosys.db.
3
+ */
4
+ import { existsSync, readdirSync, unlinkSync, writeFileSync, mkdirSync } from "fs";
5
+ import path from "path";
6
+ import { GnosysDB, fnv1a } from "./db.js";
7
+ import { getGnosysHome } from "./paths.js";
8
+ import { ensureMachineConfig } from "./machineConfig.js";
9
+ import { listPendingStagingQueue, observeStagingFile, parseStagedFile, quarantineStagingFile, quarantineStaleTmpFiles, stagingRoot, verifyMemoryExistsInDb, } from "./syncStaging.js";
10
+ import { assertMasterLeaseHeld, readMasterMarker, touchMasterMarkerHeartbeat, validateLeaseEpochBeforeWrite, } from "./masterLease.js";
11
+ import { acquireWriteLockSync } from "./syncLock.js";
12
+ const INGEST_LOCK_NAME = "master-ingest.lock";
13
+ function payloadToMemory(p, now) {
14
+ const contentHash = fnv1a(`${p.title}\n${p.content}`);
15
+ return {
16
+ id: p.id,
17
+ title: p.title,
18
+ category: p.category,
19
+ content: p.content,
20
+ summary: null,
21
+ tags: JSON.stringify(p.tags),
22
+ relevance: "",
23
+ author: "user",
24
+ authority: "user",
25
+ confidence: 0.8,
26
+ reinforcement_count: 0,
27
+ content_hash: contentHash,
28
+ status: "active",
29
+ tier: "active",
30
+ supersedes: null,
31
+ superseded_by: null,
32
+ last_reinforced: null,
33
+ created: p.writtenAt || now,
34
+ modified: now,
35
+ source_path: null,
36
+ project_id: p.project_id,
37
+ scope: p.scope || "project",
38
+ };
39
+ }
40
+ function writeIngestReceipt(masterPath, machineId, ulid, outcome) {
41
+ const dir = path.join(stagingRoot(masterPath), machineId, "receipts");
42
+ mkdirSync(dir, { recursive: true });
43
+ writeFileSync(path.join(dir, `${ulid}.json`), JSON.stringify({ ulid, outcome, at: new Date().toISOString() }, null, 2) + "\n");
44
+ }
45
+ function listMachineIds(masterPath) {
46
+ const root = stagingRoot(masterPath);
47
+ if (!existsSync(root))
48
+ return [];
49
+ return readdirSync(root).filter((name) => {
50
+ if (name.startsWith("."))
51
+ return false;
52
+ try {
53
+ return existsSync(path.join(root, name));
54
+ }
55
+ catch {
56
+ return false;
57
+ }
58
+ });
59
+ }
60
+ /**
61
+ * Always-on cheap ingest sweep (agent start + timer). Single-writer lock on local disk.
62
+ */
63
+ export function runMasterIngestSweep(masterPath, opts) {
64
+ const result = { ingested: 0, skipped: 0, quarantined: 0, errors: [] };
65
+ const { config: mc } = ensureMachineConfig();
66
+ const lockPath = path.join(getGnosysHome(), INGEST_LOCK_NAME);
67
+ let release = null;
68
+ try {
69
+ release = acquireWriteLockSync(lockPath, "master-ingest");
70
+ touchMasterMarkerHeartbeat(masterPath);
71
+ assertMasterLeaseHeld(masterPath, mc.machineId);
72
+ const marker = readMasterMarker(masterPath);
73
+ if (!marker) {
74
+ result.errors.push("master.json missing");
75
+ return result;
76
+ }
77
+ const masterDb = new GnosysDB(masterPath);
78
+ if (!masterDb.isAvailable()) {
79
+ result.errors.push("master database unavailable");
80
+ masterDb.close();
81
+ return result;
82
+ }
83
+ try {
84
+ for (const machineId of listMachineIds(masterPath)) {
85
+ quarantineStaleTmpFiles(masterPath, machineId);
86
+ const queue = listPendingStagingQueue(masterPath, machineId, masterDb);
87
+ for (const entry of queue) {
88
+ observeStagingFile(masterDb, machineId, entry.fileName, entry.memoryUlid);
89
+ const parsed = parseStagedFile(entry.filePath);
90
+ if (!parsed.ok) {
91
+ if (parsed.quarantine) {
92
+ quarantineStagingFile(entry.filePath, masterPath, machineId);
93
+ result.quarantined++;
94
+ }
95
+ else {
96
+ result.errors.push(parsed.reason);
97
+ }
98
+ continue;
99
+ }
100
+ if (masterDb.isUlidProcessed(parsed.payload.id)) {
101
+ try {
102
+ unlinkSync(entry.filePath);
103
+ }
104
+ catch {
105
+ // ignore
106
+ }
107
+ writeIngestReceipt(masterPath, machineId, parsed.payload.id, "deduped");
108
+ result.skipped++;
109
+ continue;
110
+ }
111
+ try {
112
+ validateLeaseEpochBeforeWrite(masterPath, marker.epoch, mc.machineId);
113
+ const now = new Date().toISOString();
114
+ masterDb.insertMemory(payloadToMemory(parsed.payload, now));
115
+ if (!verifyMemoryExistsInDb(masterDb, parsed.payload.id)) {
116
+ result.errors.push(`read-back failed for ${parsed.payload.id}`);
117
+ continue;
118
+ }
119
+ masterDb.markUlidProcessed(parsed.payload.id, marker.epoch);
120
+ masterDb.recordStagingLedgerEntry({
121
+ stagingKey: `${machineId}/${entry.fileName}`,
122
+ machineId,
123
+ memoryUlid: parsed.payload.id,
124
+ firstSeenAt: masterDb.getStagingLedgerFirstSeenAt(`${machineId}/${entry.fileName}`) ?? now,
125
+ ingestEpoch: marker.epoch,
126
+ status: "ingested",
127
+ });
128
+ unlinkSync(entry.filePath);
129
+ writeIngestReceipt(masterPath, machineId, parsed.payload.id, "ingested");
130
+ result.ingested++;
131
+ }
132
+ catch (err) {
133
+ result.errors.push(err instanceof Error ? err.message : String(err));
134
+ }
135
+ }
136
+ }
137
+ }
138
+ finally {
139
+ masterDb.close();
140
+ }
141
+ }
142
+ catch (err) {
143
+ result.errors.push(err instanceof Error ? err.message : String(err));
144
+ }
145
+ finally {
146
+ release?.();
147
+ }
148
+ if (opts?.json) {
149
+ console.log(JSON.stringify(result));
150
+ }
151
+ return result;
152
+ }
@@ -0,0 +1,8 @@
1
+ /** Build launchd plist XML (testable without writing to disk). */
2
+ export declare function buildSyncIngestLaunchAgentPlist(intervalMinutes: number): string;
3
+ export declare function installSyncIngestLaunchAgent(intervalMinutes?: number): string | null;
4
+ export declare function uninstallSyncIngestLaunchAgent(): string | null;
5
+ export declare function getSyncIngestLaunchAgentStatus(): {
6
+ installed: boolean;
7
+ path: string;
8
+ };
@@ -0,0 +1,93 @@
1
+ import fs from "fs";
2
+ import os from "os";
3
+ import path from "path";
4
+ import { execSync } from "child_process";
5
+ const LABEL = "com.gnosys.sync-ingest";
6
+ function plistPath() {
7
+ return path.join(os.homedir(), "Library", "LaunchAgents", `${LABEL}.plist`);
8
+ }
9
+ function logPath() {
10
+ return path.join(os.tmpdir(), "gnosys-sync-ingest.log");
11
+ }
12
+ function xmlEscape(value) {
13
+ return value
14
+ .replace(/&/g, "&amp;")
15
+ .replace(/</g, "&lt;")
16
+ .replace(/>/g, "&gt;")
17
+ .replace(/"/g, "&quot;");
18
+ }
19
+ /** Build launchd plist XML (testable without writing to disk). */
20
+ export function buildSyncIngestLaunchAgentPlist(intervalMinutes) {
21
+ const nodePath = process.execPath;
22
+ const cliPath = process.argv[1] || "gnosys";
23
+ const intervalSec = Math.max(1, intervalMinutes) * 60;
24
+ const pathEnv = `${path.dirname(nodePath)}:/usr/local/bin:/usr/bin:/bin`;
25
+ return `<?xml version="1.0" encoding="UTF-8"?>
26
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
27
+ <plist version="1.0">
28
+ <dict>
29
+ <key>Label</key>
30
+ <string>${LABEL}</string>
31
+ <key>ProgramArguments</key>
32
+ <array>
33
+ <string>${xmlEscape(nodePath)}</string>
34
+ <string>${xmlEscape(cliPath)}</string>
35
+ <string>setup</string>
36
+ <string>remote</string>
37
+ <string>doctor</string>
38
+ <string>--ingest</string>
39
+ <string>--quiet</string>
40
+ </array>
41
+ <key>StartInterval</key>
42
+ <integer>${intervalSec}</integer>
43
+ <key>StandardOutPath</key>
44
+ <string>${xmlEscape(logPath())}</string>
45
+ <key>StandardErrorPath</key>
46
+ <string>${xmlEscape(logPath())}</string>
47
+ <key>EnvironmentVariables</key>
48
+ <dict>
49
+ <key>HOME</key>
50
+ <string>${xmlEscape(os.homedir())}</string>
51
+ <key>PATH</key>
52
+ <string>${xmlEscape(pathEnv)}</string>
53
+ </dict>
54
+ </dict>
55
+ </plist>
56
+ `;
57
+ }
58
+ export function installSyncIngestLaunchAgent(intervalMinutes = 15) {
59
+ if (process.platform !== "darwin")
60
+ return null;
61
+ const file = plistPath();
62
+ fs.mkdirSync(path.dirname(file), { recursive: true });
63
+ fs.writeFileSync(file, buildSyncIngestLaunchAgentPlist(intervalMinutes), "utf8");
64
+ try {
65
+ execSync(`launchctl load -w "${file}"`, { stdio: "ignore" });
66
+ }
67
+ catch (err) {
68
+ throw new Error(`Failed to load launch agent: ${err instanceof Error ? err.message : String(err)}`);
69
+ }
70
+ return file;
71
+ }
72
+ export function uninstallSyncIngestLaunchAgent() {
73
+ if (process.platform !== "darwin")
74
+ return null;
75
+ const file = plistPath();
76
+ try {
77
+ execSync(`launchctl unload "${file}"`, { stdio: "ignore" });
78
+ }
79
+ catch {
80
+ // Already unloaded or missing.
81
+ }
82
+ try {
83
+ fs.unlinkSync(file);
84
+ }
85
+ catch {
86
+ // Missing is already uninstalled.
87
+ }
88
+ return file;
89
+ }
90
+ export function getSyncIngestLaunchAgentStatus() {
91
+ const file = plistPath();
92
+ return { installed: fs.existsSync(file), path: file };
93
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Run one ingest sweep on MCP server startup (master role only).
3
+ * Non-blocking — callers should fire-and-forget.
4
+ */
5
+ export declare function maybeRunStartupIngestSweep(): Promise<void>;
@@ -0,0 +1,29 @@
1
+ import { GnosysDB } from "./db.js";
2
+ import { readMachineConfig } from "./machineConfig.js";
3
+ import { getConfiguredRemotePath } from "./remote.js";
4
+ import { runMasterIngestSweep } from "./syncIngest.js";
5
+ /**
6
+ * Run one ingest sweep on MCP server startup (master role only).
7
+ * Non-blocking — callers should fire-and-forget.
8
+ */
9
+ export async function maybeRunStartupIngestSweep() {
10
+ const mc = readMachineConfig();
11
+ if (!mc?.remote.enabled || mc.remote.role !== "master")
12
+ return;
13
+ const db = GnosysDB.openLocal();
14
+ let masterPath = null;
15
+ try {
16
+ if (!db.isAvailable())
17
+ return;
18
+ masterPath = getConfiguredRemotePath(db);
19
+ }
20
+ finally {
21
+ db.close();
22
+ }
23
+ if (!masterPath)
24
+ return;
25
+ const result = runMasterIngestSweep(masterPath, { quiet: true });
26
+ if (result.errors.length > 0) {
27
+ console.error(`[sync] Startup ingest sweep: ${result.ingested} ingested, ${result.errors.length} error(s)`);
28
+ }
29
+ }
@@ -0,0 +1,10 @@
1
+ /** Build systemd service unit (testable without writing to disk). */
2
+ export declare function buildSyncIngestSystemdService(): string;
3
+ /** Build systemd timer unit (testable without writing to disk). */
4
+ export declare function buildSyncIngestSystemdTimer(intervalMinutes: number): string;
5
+ export declare function installSyncIngestSystemdTimer(intervalMinutes?: number): string | null;
6
+ export declare function uninstallSyncIngestSystemdTimer(): string | null;
7
+ export declare function getSyncIngestSystemdTimerStatus(): {
8
+ installed: boolean;
9
+ path: string;
10
+ };
@@ -0,0 +1,97 @@
1
+ import fs from "fs";
2
+ import os from "os";
3
+ import path from "path";
4
+ import { execSync } from "child_process";
5
+ const SERVICE_NAME = "gnosys-sync-ingest";
6
+ function systemdUserDir() {
7
+ return path.join(os.homedir(), ".config", "systemd", "user");
8
+ }
9
+ function serviceFilePath() {
10
+ return path.join(systemdUserDir(), `${SERVICE_NAME}.service`);
11
+ }
12
+ function timerFilePath() {
13
+ return path.join(systemdUserDir(), `${SERVICE_NAME}.timer`);
14
+ }
15
+ function execLine() {
16
+ const nodePath = process.execPath;
17
+ const cliPath = process.argv[1] || "gnosys";
18
+ return `${nodePath} ${cliPath} setup remote doctor --ingest --quiet`;
19
+ }
20
+ /** Build systemd service unit (testable without writing to disk). */
21
+ export function buildSyncIngestSystemdService() {
22
+ return `[Unit]
23
+ Description=Gnosys sync ingest sweep
24
+
25
+ [Service]
26
+ Type=oneshot
27
+ ExecStart=${execLine()}
28
+ Environment=HOME=${os.homedir()}
29
+ `;
30
+ }
31
+ /** Build systemd timer unit (testable without writing to disk). */
32
+ export function buildSyncIngestSystemdTimer(intervalMinutes) {
33
+ const intervalSec = Math.max(1, intervalMinutes) * 60;
34
+ return `[Unit]
35
+ Description=Gnosys sync ingest timer
36
+
37
+ [Timer]
38
+ OnBootSec=5min
39
+ OnUnitActiveSec=${intervalSec}s
40
+
41
+ [Install]
42
+ WantedBy=timers.target
43
+ `;
44
+ }
45
+ export function installSyncIngestSystemdTimer(intervalMinutes = 15) {
46
+ if (process.platform !== "linux")
47
+ return null;
48
+ const dir = systemdUserDir();
49
+ fs.mkdirSync(dir, { recursive: true });
50
+ const servicePath = serviceFilePath();
51
+ const timerPath = timerFilePath();
52
+ fs.writeFileSync(servicePath, buildSyncIngestSystemdService(), "utf8");
53
+ fs.writeFileSync(timerPath, buildSyncIngestSystemdTimer(intervalMinutes), "utf8");
54
+ try {
55
+ execSync("systemctl --user daemon-reload", { stdio: "ignore" });
56
+ execSync(`systemctl --user enable --now ${SERVICE_NAME}.timer`, { stdio: "ignore" });
57
+ }
58
+ catch (err) {
59
+ throw new Error(`Failed to enable systemd timer: ${err instanceof Error ? err.message : String(err)}`);
60
+ }
61
+ return timerPath;
62
+ }
63
+ export function uninstallSyncIngestSystemdTimer() {
64
+ if (process.platform !== "linux")
65
+ return null;
66
+ const servicePath = serviceFilePath();
67
+ const timerPath = timerFilePath();
68
+ try {
69
+ execSync(`systemctl --user disable --now ${SERVICE_NAME}.timer`, { stdio: "ignore" });
70
+ }
71
+ catch {
72
+ // Timer may already be removed.
73
+ }
74
+ for (const file of [timerPath, servicePath]) {
75
+ try {
76
+ fs.unlinkSync(file);
77
+ }
78
+ catch {
79
+ // Missing is already uninstalled.
80
+ }
81
+ }
82
+ try {
83
+ execSync("systemctl --user daemon-reload", { stdio: "ignore" });
84
+ }
85
+ catch {
86
+ // Best effort.
87
+ }
88
+ return timerPath;
89
+ }
90
+ export function getSyncIngestSystemdTimerStatus() {
91
+ const timerPath = timerFilePath();
92
+ const servicePath = serviceFilePath();
93
+ return {
94
+ installed: fs.existsSync(timerPath) && fs.existsSync(servicePath),
95
+ path: timerPath,
96
+ };
97
+ }
@@ -0,0 +1,8 @@
1
+ export interface SyncIngestTimerStatus {
2
+ installed: boolean;
3
+ path: string;
4
+ platform: "darwin" | "linux" | "unsupported";
5
+ }
6
+ export declare function getSyncIngestTimerStatus(): SyncIngestTimerStatus;
7
+ export declare function installSyncIngestTimer(intervalMinutes?: number): string | null;
8
+ export declare function uninstallSyncIngestTimer(): string | null;
@@ -0,0 +1,27 @@
1
+ import { getSyncIngestLaunchAgentStatus, installSyncIngestLaunchAgent, uninstallSyncIngestLaunchAgent, } from "./syncIngestLaunchd.js";
2
+ import { getSyncIngestSystemdTimerStatus, installSyncIngestSystemdTimer, uninstallSyncIngestSystemdTimer, } from "./syncIngestSystemd.js";
3
+ export function getSyncIngestTimerStatus() {
4
+ if (process.platform === "darwin") {
5
+ const status = getSyncIngestLaunchAgentStatus();
6
+ return { ...status, platform: "darwin" };
7
+ }
8
+ if (process.platform === "linux") {
9
+ const status = getSyncIngestSystemdTimerStatus();
10
+ return { ...status, platform: "linux" };
11
+ }
12
+ return { installed: false, path: "", platform: "unsupported" };
13
+ }
14
+ export function installSyncIngestTimer(intervalMinutes = 15) {
15
+ if (process.platform === "darwin")
16
+ return installSyncIngestLaunchAgent(intervalMinutes);
17
+ if (process.platform === "linux")
18
+ return installSyncIngestSystemdTimer(intervalMinutes);
19
+ return null;
20
+ }
21
+ export function uninstallSyncIngestTimer() {
22
+ if (process.platform === "darwin")
23
+ return uninstallSyncIngestLaunchAgent();
24
+ if (process.platform === "linux")
25
+ return uninstallSyncIngestSystemdTimer();
26
+ return null;
27
+ }
@@ -0,0 +1,7 @@
1
+ export declare function runSyncIngestTimerCommand(opts: {
2
+ install?: boolean;
3
+ uninstall?: boolean;
4
+ status?: boolean;
5
+ interval?: string;
6
+ json?: boolean;
7
+ }): Promise<void>;