mrplex 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (319) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +284 -0
  3. package/dist/cli/config.d.ts +28 -0
  4. package/dist/cli/config.js +48 -0
  5. package/dist/cli/config.js.map +1 -0
  6. package/dist/cli/exit-codes.d.ts +13 -0
  7. package/dist/cli/exit-codes.js +39 -0
  8. package/dist/cli/exit-codes.js.map +1 -0
  9. package/dist/cli/format.d.ts +22 -0
  10. package/dist/cli/format.js +148 -0
  11. package/dist/cli/format.js.map +1 -0
  12. package/dist/cli/main.d.ts +16 -0
  13. package/dist/cli/main.js +1518 -0
  14. package/dist/cli/main.js.map +1 -0
  15. package/dist/client/kernel-client.d.ts +97 -0
  16. package/dist/client/kernel-client.js +14 -0
  17. package/dist/client/kernel-client.js.map +1 -0
  18. package/dist/client/local.d.ts +26 -0
  19. package/dist/client/local.js +103 -0
  20. package/dist/client/local.js.map +1 -0
  21. package/dist/client/remote-mcp.d.ts +35 -0
  22. package/dist/client/remote-mcp.js +174 -0
  23. package/dist/client/remote-mcp.js.map +1 -0
  24. package/dist/embed/backfill.d.ts +24 -0
  25. package/dist/embed/backfill.js +33 -0
  26. package/dist/embed/backfill.js.map +1 -0
  27. package/dist/embed/chunker.d.ts +31 -0
  28. package/dist/embed/chunker.js +121 -0
  29. package/dist/embed/chunker.js.map +1 -0
  30. package/dist/embed/cmd-hook.d.ts +21 -0
  31. package/dist/embed/cmd-hook.js +100 -0
  32. package/dist/embed/cmd-hook.js.map +1 -0
  33. package/dist/embed/config.d.ts +61 -0
  34. package/dist/embed/config.js +109 -0
  35. package/dist/embed/config.js.map +1 -0
  36. package/dist/embed/hook.d.ts +38 -0
  37. package/dist/embed/hook.js +56 -0
  38. package/dist/embed/hook.js.map +1 -0
  39. package/dist/embed/http-hook.d.ts +13 -0
  40. package/dist/embed/http-hook.js +39 -0
  41. package/dist/embed/http-hook.js.map +1 -0
  42. package/dist/embed/worker.d.ts +62 -0
  43. package/dist/embed/worker.js +228 -0
  44. package/dist/embed/worker.js.map +1 -0
  45. package/dist/fixtures/hwe/characters/albion-ever.md +12 -0
  46. package/dist/fixtures/hwe/characters/cent.md +11 -0
  47. package/dist/fixtures/hwe/concepts/cent-demiurge-resonance.md +10 -0
  48. package/dist/fixtures/hwe/concepts/phase-looper.md +10 -0
  49. package/dist/fixtures/hwe/entities/demiurge.md +11 -0
  50. package/dist/fixtures/hwe/entities/monad.md +11 -0
  51. package/dist/fixtures/hwe/moc/globocorp-network.md +19 -0
  52. package/dist/fixtures/hwe/organizations/globocorp.md +11 -0
  53. package/dist/fixtures/hwe/readme.md +24 -0
  54. package/dist/fixtures/starship/crew/aria-okonkwo.md +14 -0
  55. package/dist/fixtures/starship/crew/bexley-orr.md +14 -0
  56. package/dist/fixtures/starship/crew/dax-thorne.md +14 -0
  57. package/dist/fixtures/starship/crew/isolde-marsh.md +16 -0
  58. package/dist/fixtures/starship/crew/kestrel-vance.md +15 -0
  59. package/dist/fixtures/starship/crew/quill-vasquez.md +14 -0
  60. package/dist/fixtures/starship/crew/soren-halloway.md +16 -0
  61. package/dist/fixtures/starship/encounters/the-drift-choir.md +14 -0
  62. package/dist/fixtures/starship/encounters/the-hollow-signal.md +16 -0
  63. package/dist/fixtures/starship/encounters/the-wandering-buoy.md +14 -0
  64. package/dist/fixtures/starship/equipment/coolant-loop-b.md +13 -0
  65. package/dist/fixtures/starship/equipment/emergency-ration-bars.md +12 -0
  66. package/dist/fixtures/starship/equipment/long-range-sensors.md +13 -0
  67. package/dist/fixtures/starship/equipment/plasma-manifold-3.md +15 -0
  68. package/dist/fixtures/starship/equipment/shuttle-corvid.md +13 -0
  69. package/dist/fixtures/starship/logs/marsh-4419-1.md +13 -0
  70. package/dist/fixtures/starship/logs/okonkwo-4415-1.md +13 -0
  71. package/dist/fixtures/starship/logs/orr-4413-1.md +13 -0
  72. package/dist/fixtures/starship/logs/orr-4420-1.md +12 -0
  73. package/dist/fixtures/starship/logs/thorne-4413-2.md +13 -0
  74. package/dist/fixtures/starship/logs/vance-4419-2.md +13 -0
  75. package/dist/fixtures/starship/misc/the-galley-ficus.md +11 -0
  76. package/dist/fixtures/starship/missions/the-cinder-run.md +17 -0
  77. package/dist/fixtures/starship/missions/the-drift-choir.md +17 -0
  78. package/dist/fixtures/starship/missions/the-hollow-signal.md +17 -0
  79. package/dist/fixtures/starship/missions/the-silent-beacon.md +17 -0
  80. package/dist/fixtures/starship/moc/crew.md +19 -0
  81. package/dist/fixtures/starship/moc/encounters.md +14 -0
  82. package/dist/fixtures/starship/moc/missions.md +15 -0
  83. package/dist/fixtures/starship/readme.md +60 -0
  84. package/dist/index.d.ts +1 -0
  85. package/dist/index.js +3 -0
  86. package/dist/index.js.map +1 -0
  87. package/dist/kernel/auth/glob.d.ts +46 -0
  88. package/dist/kernel/auth/glob.js +112 -0
  89. package/dist/kernel/auth/glob.js.map +1 -0
  90. package/dist/kernel/auth/scope.d.ts +53 -0
  91. package/dist/kernel/auth/scope.js +83 -0
  92. package/dist/kernel/auth/scope.js.map +1 -0
  93. package/dist/kernel/casefold.d.ts +37 -0
  94. package/dist/kernel/casefold.js +44 -0
  95. package/dist/kernel/casefold.js.map +1 -0
  96. package/dist/kernel/constants.d.ts +21 -0
  97. package/dist/kernel/constants.js +22 -0
  98. package/dist/kernel/constants.js.map +1 -0
  99. package/dist/kernel/context.d.ts +70 -0
  100. package/dist/kernel/context.js +114 -0
  101. package/dist/kernel/context.js.map +1 -0
  102. package/dist/kernel/deletion.d.ts +50 -0
  103. package/dist/kernel/deletion.js +83 -0
  104. package/dist/kernel/deletion.js.map +1 -0
  105. package/dist/kernel/diff.d.ts +36 -0
  106. package/dist/kernel/diff.js +61 -0
  107. package/dist/kernel/diff.js.map +1 -0
  108. package/dist/kernel/errors.d.ts +37 -0
  109. package/dist/kernel/errors.js +55 -0
  110. package/dist/kernel/errors.js.map +1 -0
  111. package/dist/kernel/frontmatter-input.d.ts +32 -0
  112. package/dist/kernel/frontmatter-input.js +85 -0
  113. package/dist/kernel/frontmatter-input.js.map +1 -0
  114. package/dist/kernel/graph.d.ts +34 -0
  115. package/dist/kernel/graph.js +559 -0
  116. package/dist/kernel/graph.js.map +1 -0
  117. package/dist/kernel/history.d.ts +74 -0
  118. package/dist/kernel/history.js +143 -0
  119. package/dist/kernel/history.js.map +1 -0
  120. package/dist/kernel/kernel.d.ts +162 -0
  121. package/dist/kernel/kernel.js +675 -0
  122. package/dist/kernel/kernel.js.map +1 -0
  123. package/dist/kernel/path-config.d.ts +101 -0
  124. package/dist/kernel/path-config.js +171 -0
  125. package/dist/kernel/path-config.js.map +1 -0
  126. package/dist/kernel/query/ast.d.ts +24 -0
  127. package/dist/kernel/query/ast.js +52 -0
  128. package/dist/kernel/query/ast.js.map +1 -0
  129. package/dist/kernel/query/cel-parse.d.ts +37 -0
  130. package/dist/kernel/query/cel-parse.js +88 -0
  131. package/dist/kernel/query/cel-parse.js.map +1 -0
  132. package/dist/kernel/query/degrees.d.ts +31 -0
  133. package/dist/kernel/query/degrees.js +135 -0
  134. package/dist/kernel/query/degrees.js.map +1 -0
  135. package/dist/kernel/query/graph-ast.d.ts +51 -0
  136. package/dist/kernel/query/graph-ast.js +139 -0
  137. package/dist/kernel/query/graph-ast.js.map +1 -0
  138. package/dist/kernel/query/query.d.ts +56 -0
  139. package/dist/kernel/query/query.js +276 -0
  140. package/dist/kernel/query/query.js.map +1 -0
  141. package/dist/kernel/validation.d.ts +64 -0
  142. package/dist/kernel/validation.js +183 -0
  143. package/dist/kernel/validation.js.map +1 -0
  144. package/dist/kernel/version-id.d.ts +10 -0
  145. package/dist/kernel/version-id.js +21 -0
  146. package/dist/kernel/version-id.js.map +1 -0
  147. package/dist/kernel/wire.d.ts +148 -0
  148. package/dist/kernel/wire.js +6 -0
  149. package/dist/kernel/wire.js.map +1 -0
  150. package/dist/links/backfill.d.ts +28 -0
  151. package/dist/links/backfill.js +33 -0
  152. package/dist/links/backfill.js.map +1 -0
  153. package/dist/links/extract.d.ts +29 -0
  154. package/dist/links/extract.js +233 -0
  155. package/dist/links/extract.js.map +1 -0
  156. package/dist/links/link-config.d.ts +70 -0
  157. package/dist/links/link-config.js +107 -0
  158. package/dist/links/link-config.js.map +1 -0
  159. package/dist/links/maintain.d.ts +37 -0
  160. package/dist/links/maintain.js +91 -0
  161. package/dist/links/maintain.js.map +1 -0
  162. package/dist/links/repair.d.ts +46 -0
  163. package/dist/links/repair.js +96 -0
  164. package/dist/links/repair.js.map +1 -0
  165. package/dist/links/resolve.d.ts +41 -0
  166. package/dist/links/resolve.js +121 -0
  167. package/dist/links/resolve.js.map +1 -0
  168. package/dist/links/stale.d.ts +32 -0
  169. package/dist/links/stale.js +73 -0
  170. package/dist/links/stale.js.map +1 -0
  171. package/dist/markdown/content-hash.d.ts +34 -0
  172. package/dist/markdown/content-hash.js +46 -0
  173. package/dist/markdown/content-hash.js.map +1 -0
  174. package/dist/markdown/frontmatter.d.ts +69 -0
  175. package/dist/markdown/frontmatter.js +147 -0
  176. package/dist/markdown/frontmatter.js.map +1 -0
  177. package/dist/markdown/hash-backfill.d.ts +22 -0
  178. package/dist/markdown/hash-backfill.js +43 -0
  179. package/dist/markdown/hash-backfill.js.map +1 -0
  180. package/dist/mcp/query-syntax.d.ts +20 -0
  181. package/dist/mcp/query-syntax.js +194 -0
  182. package/dist/mcp/query-syntax.js.map +1 -0
  183. package/dist/mcp/render.d.ts +31 -0
  184. package/dist/mcp/render.js +110 -0
  185. package/dist/mcp/render.js.map +1 -0
  186. package/dist/mcp/server.d.ts +60 -0
  187. package/dist/mcp/server.js +170 -0
  188. package/dist/mcp/server.js.map +1 -0
  189. package/dist/mcp/tools.d.ts +93 -0
  190. package/dist/mcp/tools.js +1218 -0
  191. package/dist/mcp/tools.js.map +1 -0
  192. package/dist/rest/conditional.d.ts +37 -0
  193. package/dist/rest/conditional.js +58 -0
  194. package/dist/rest/conditional.js.map +1 -0
  195. package/dist/rest/negotiate.d.ts +33 -0
  196. package/dist/rest/negotiate.js +54 -0
  197. package/dist/rest/negotiate.js.map +1 -0
  198. package/dist/rest/routes.d.ts +37 -0
  199. package/dist/rest/routes.js +744 -0
  200. package/dist/rest/routes.js.map +1 -0
  201. package/dist/seed/fixture-seed.d.ts +35 -0
  202. package/dist/seed/fixture-seed.js +75 -0
  203. package/dist/seed/fixture-seed.js.map +1 -0
  204. package/dist/seed/starship-config.d.ts +3 -0
  205. package/dist/seed/starship-config.js +5 -0
  206. package/dist/seed/starship-config.js.map +1 -0
  207. package/dist/server/headers.d.ts +36 -0
  208. package/dist/server/headers.js +30 -0
  209. package/dist/server/headers.js.map +1 -0
  210. package/dist/server/http-error.d.ts +53 -0
  211. package/dist/server/http-error.js +104 -0
  212. package/dist/server/http-error.js.map +1 -0
  213. package/dist/server/serve.d.ts +56 -0
  214. package/dist/server/serve.js +135 -0
  215. package/dist/server/serve.js.map +1 -0
  216. package/dist/shell/audit.d.ts +32 -0
  217. package/dist/shell/audit.js +28 -0
  218. package/dist/shell/audit.js.map +1 -0
  219. package/dist/shell/guard.d.ts +50 -0
  220. package/dist/shell/guard.js +176 -0
  221. package/dist/shell/guard.js.map +1 -0
  222. package/dist/shell/keys.d.ts +36 -0
  223. package/dist/shell/keys.js +64 -0
  224. package/dist/shell/keys.js.map +1 -0
  225. package/dist/shell/login.d.ts +56 -0
  226. package/dist/shell/login.js +130 -0
  227. package/dist/shell/login.js.map +1 -0
  228. package/dist/shell/oidc.d.ts +62 -0
  229. package/dist/shell/oidc.js +102 -0
  230. package/dist/shell/oidc.js.map +1 -0
  231. package/dist/shell/policy.d.ts +100 -0
  232. package/dist/shell/policy.js +232 -0
  233. package/dist/shell/policy.js.map +1 -0
  234. package/dist/shell/proxy-policy.d.ts +34 -0
  235. package/dist/shell/proxy-policy.js +120 -0
  236. package/dist/shell/proxy-policy.js.map +1 -0
  237. package/dist/shell/proxy.d.ts +47 -0
  238. package/dist/shell/proxy.js +253 -0
  239. package/dist/shell/proxy.js.map +1 -0
  240. package/dist/shell/serve.d.ts +86 -0
  241. package/dist/shell/serve.js +0 -0
  242. package/dist/shell/serve.js.map +1 -0
  243. package/dist/shell/stdio.d.ts +55 -0
  244. package/dist/shell/stdio.js +63 -0
  245. package/dist/shell/stdio.js.map +1 -0
  246. package/dist/storage/registry.d.ts +20 -0
  247. package/dist/storage/registry.js +38 -0
  248. package/dist/storage/registry.js.map +1 -0
  249. package/dist/storage/search-plan.d.ts +93 -0
  250. package/dist/storage/search-plan.js +15 -0
  251. package/dist/storage/search-plan.js.map +1 -0
  252. package/dist/storage/types.d.ts +448 -0
  253. package/dist/storage/types.js +14 -0
  254. package/dist/storage/types.js.map +1 -0
  255. package/dist/storage/versions-since.d.ts +72 -0
  256. package/dist/storage/versions-since.js +92 -0
  257. package/dist/storage/versions-since.js.map +1 -0
  258. package/dist/storage-postgres/adapter.d.ts +17 -0
  259. package/dist/storage-postgres/adapter.js +782 -0
  260. package/dist/storage-postgres/adapter.js.map +1 -0
  261. package/dist/storage-postgres/compile-postgres.d.ts +22 -0
  262. package/dist/storage-postgres/compile-postgres.js +830 -0
  263. package/dist/storage-postgres/compile-postgres.js.map +1 -0
  264. package/dist/storage-postgres/errors.d.ts +34 -0
  265. package/dist/storage-postgres/errors.js +50 -0
  266. package/dist/storage-postgres/errors.js.map +1 -0
  267. package/dist/storage-postgres/migrations/0001_init.sql +113 -0
  268. package/dist/storage-postgres/migrations/0002_content_hash.sql +7 -0
  269. package/dist/storage-postgres/migrations/index.d.ts +22 -0
  270. package/dist/storage-postgres/migrations/index.js +70 -0
  271. package/dist/storage-postgres/migrations/index.js.map +1 -0
  272. package/dist/storage-sqlite/adapter.d.ts +2 -0
  273. package/dist/storage-sqlite/adapter.js +688 -0
  274. package/dist/storage-sqlite/adapter.js.map +1 -0
  275. package/dist/storage-sqlite/compile-filter.d.ts +41 -0
  276. package/dist/storage-sqlite/compile-filter.js +881 -0
  277. package/dist/storage-sqlite/compile-filter.js.map +1 -0
  278. package/dist/storage-sqlite/compile-sqlite.d.ts +19 -0
  279. package/dist/storage-sqlite/compile-sqlite.js +176 -0
  280. package/dist/storage-sqlite/compile-sqlite.js.map +1 -0
  281. package/dist/storage-sqlite/migrations/0001_init.sql +123 -0
  282. package/dist/storage-sqlite/migrations/0002_content_hash.sql +7 -0
  283. package/dist/storage-sqlite/migrations/index.d.ts +6 -0
  284. package/dist/storage-sqlite/migrations/index.js +41 -0
  285. package/dist/storage-sqlite/migrations/index.js.map +1 -0
  286. package/dist/storage-sqlite/vec.d.ts +40 -0
  287. package/dist/storage-sqlite/vec.js +62 -0
  288. package/dist/storage-sqlite/vec.js.map +1 -0
  289. package/dist/sync/cursor.d.ts +36 -0
  290. package/dist/sync/cursor.js +66 -0
  291. package/dist/sync/cursor.js.map +1 -0
  292. package/dist/sync/daemon.d.ts +40 -0
  293. package/dist/sync/daemon.js +202 -0
  294. package/dist/sync/daemon.js.map +1 -0
  295. package/dist/sync/feed.d.ts +38 -0
  296. package/dist/sync/feed.js +125 -0
  297. package/dist/sync/feed.js.map +1 -0
  298. package/dist/sync/fs-store.d.ts +8 -0
  299. package/dist/sync/fs-store.js +76 -0
  300. package/dist/sync/fs-store.js.map +1 -0
  301. package/dist/sync/intrinsics.d.ts +52 -0
  302. package/dist/sync/intrinsics.js +76 -0
  303. package/dist/sync/intrinsics.js.map +1 -0
  304. package/dist/sync/paths.d.ts +38 -0
  305. package/dist/sync/paths.js +56 -0
  306. package/dist/sync/paths.js.map +1 -0
  307. package/dist/sync/push.d.ts +48 -0
  308. package/dist/sync/push.js +328 -0
  309. package/dist/sync/push.js.map +1 -0
  310. package/dist/sync/reconcile.d.ts +54 -0
  311. package/dist/sync/reconcile.js +252 -0
  312. package/dist/sync/reconcile.js.map +1 -0
  313. package/dist/sync/run.d.ts +27 -0
  314. package/dist/sync/run.js +46 -0
  315. package/dist/sync/run.js.map +1 -0
  316. package/dist/version.d.ts +2 -0
  317. package/dist/version.js +5 -0
  318. package/dist/version.js.map +1 -0
  319. package/package.json +80 -0
@@ -0,0 +1,66 @@
1
+ /**
2
+ * The one persistent client state: a single cursor (sync/history plan §4.2).
3
+ * `<root>/.mrplex/sync.json` holds `{ server | database, repo,
4
+ * last_synced_version_id }`, meaning "every change at or before this position
5
+ * in the log is reflected on disk." The source is recorded as exactly one of
6
+ * `server` (remote sync) or `database` (local sync) — they are mutually
7
+ * exclusive, mirroring the CLI's --server/--database gate. In-vault so it
8
+ * travels with the vault it describes. There is no per-file manifest or
9
+ * tombstone journal.
10
+ */
11
+ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
12
+ import { dirname, join } from "node:path";
13
+ import { CURSOR_FILE } from "./paths.js";
14
+ /**
15
+ * Resolve the cursor's source fields (§4.2). The source is exactly one of
16
+ * `server` (remote sync) or `database` (local sync); a set `server` always
17
+ * wins, otherwise a set `database` is recorded. When neither is supplied on
18
+ * this run, carry forward whatever the existing marker held so an incidental
19
+ * re-run (e.g. via a different code path) doesn't strip provenance.
20
+ */
21
+ export function sourceFields(server, database, existing) {
22
+ if (server !== undefined)
23
+ return { server };
24
+ if (database !== undefined)
25
+ return { database };
26
+ if (existing?.server !== undefined)
27
+ return { server: existing.server };
28
+ if (existing?.database !== undefined)
29
+ return { database: existing.database };
30
+ return {};
31
+ }
32
+ /**
33
+ * Read the cursor file, or null when absent (marker absent → full index
34
+ * reconciliation, §4.9). A malformed file throws — the caller must not silently
35
+ * treat corruption as "start fresh."
36
+ */
37
+ export async function readCursor(root) {
38
+ const path = join(root, CURSOR_FILE);
39
+ let text;
40
+ try {
41
+ text = await readFile(path, "utf8");
42
+ }
43
+ catch (err) {
44
+ if (err.code === "ENOENT")
45
+ return null;
46
+ throw err;
47
+ }
48
+ const parsed = JSON.parse(text);
49
+ if (typeof parsed.last_synced_version_id !== "string") {
50
+ throw new Error(`${path}: missing last_synced_version_id`);
51
+ }
52
+ return parsed;
53
+ }
54
+ /**
55
+ * Write the cursor atomically (temp + rename) so a crash mid-write can't leave
56
+ * a torn file. The cursor advances only after a batch's filesystem effects
57
+ * complete (§4.3), so this is called at safe points.
58
+ */
59
+ export async function writeCursor(root, cursor) {
60
+ const path = join(root, CURSOR_FILE);
61
+ await mkdir(dirname(path), { recursive: true });
62
+ const tmp = `${path}.tmp`;
63
+ await writeFile(tmp, `${JSON.stringify(cursor, null, 2)}\n`, "utf8");
64
+ await rename(tmp, path);
65
+ }
66
+ //# sourceMappingURL=cursor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../src/sync/cursor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AASzC;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAC1B,MAA0B,EAC1B,QAA4B,EAC5B,QAA2B;IAE3B,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAC5C,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;IAChD,IAAI,QAAQ,EAAE,MAAM,KAAK,SAAS;QAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC;IACvE,IAAI,QAAQ,EAAE,QAAQ,KAAK,SAAS;QAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,CAAC;IAC7E,OAAO,EAAE,CAAC;AACZ,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAY;IAC3C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IACrC,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACtC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QAClE,MAAM,GAAG,CAAC;IACZ,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAe,CAAC;IAC9C,IAAI,OAAO,MAAM,CAAC,sBAAsB,KAAK,QAAQ,EAAE,CAAC;QACtD,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,kCAAkC,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAY,EAAE,MAAkB;IAChE,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IACrC,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,MAAM,GAAG,GAAG,GAAG,IAAI,MAAM,CAAC;IAC1B,MAAM,SAAS,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACrE,MAAM,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The two-way sync daemon (sync/history plan §4.3–4.8, M8). Startup
3
+ * reconciliation, then two steady-state loops:
4
+ *
5
+ * • remote → local: poll history.since(cursor) every --interval, apply refs
6
+ * (feed.ts), advance the cursor after fs effects.
7
+ * • local → remote: a chokidar watcher coalesces events into a debounce
8
+ * *burst* (all paths that settled together). The burst runs present files
9
+ * first so unlink+add of a rename is a move, not a delete (§4.7); each
10
+ * path is still stated, not trusted by event type (§4.6).
11
+ *
12
+ * chokidar is confined to this module (§4.4). Echo suppression falls out of
13
+ * self-description (§4.5): our own pushes come back on the feed but the local
14
+ * file already embeds that version+hash, and our own writes trip the watcher
15
+ * but the hash gate no-ops them.
16
+ */
17
+ import type { KernelClient } from "../client/kernel-client.js";
18
+ export type DaemonOptions = {
19
+ root: string;
20
+ repo: string;
21
+ server?: string;
22
+ database?: string;
23
+ include?: string[];
24
+ exclude?: string[];
25
+ intervalMs?: number;
26
+ debounceMs?: number;
27
+ settleMs?: number;
28
+ log?: (msg: string) => void;
29
+ };
30
+ export type Daemon = {
31
+ /** Resolves once the initial reconciliation + first feed drain complete. */
32
+ ready: Promise<void>;
33
+ /** Stop the watcher and poll loop; resolves when fully torn down. */
34
+ stop(): Promise<void>;
35
+ };
36
+ /**
37
+ * Start the daemon. Returns handles rather than blocking, so the CLI (or a
38
+ * test) controls its lifetime. The poll loop and watcher run until `stop()`.
39
+ */
40
+ export declare function startDaemon(client: KernelClient, opts: DaemonOptions): Daemon;
@@ -0,0 +1,202 @@
1
+ /**
2
+ * The two-way sync daemon (sync/history plan §4.3–4.8, M8). Startup
3
+ * reconciliation, then two steady-state loops:
4
+ *
5
+ * • remote → local: poll history.since(cursor) every --interval, apply refs
6
+ * (feed.ts), advance the cursor after fs effects.
7
+ * • local → remote: a chokidar watcher coalesces events into a debounce
8
+ * *burst* (all paths that settled together). The burst runs present files
9
+ * first so unlink+add of a rename is a move, not a delete (§4.7); each
10
+ * path is still stated, not trusted by event type (§4.6).
11
+ *
12
+ * chokidar is confined to this module (§4.4). Echo suppression falls out of
13
+ * self-description (§4.5): our own pushes come back on the feed but the local
14
+ * file already embeds that version+hash, and our own writes trip the watcher
15
+ * but the hash gate no-ops them.
16
+ */
17
+ import { watch } from "chokidar";
18
+ import { readCursor, sourceFields, writeCursor } from "./cursor.js";
19
+ import { applyFeed } from "./feed.js";
20
+ import { createFsStore } from "./fs-store.js";
21
+ import { isIgnored, readFileIntrinsics } from "./intrinsics.js";
22
+ import { SYNC_DIR, makeScopeFilter, toDocPath } from "./paths.js";
23
+ import { pushBurst, pushPath } from "./push.js";
24
+ import { reconcileOnce } from "./reconcile.js";
25
+ /**
26
+ * Start the daemon. Returns handles rather than blocking, so the CLI (or a
27
+ * test) controls its lifetime. The poll loop and watcher run until `stop()`.
28
+ */
29
+ export function startDaemon(client, opts) {
30
+ const log = opts.log ?? (() => { });
31
+ const store = createFsStore(opts.root);
32
+ const scope = makeScopeFilter({ include: opts.include, exclude: opts.exclude });
33
+ const map = new Map();
34
+ const intervalMs = opts.intervalMs ?? 5000;
35
+ const debounceMs = opts.debounceMs ?? 5000;
36
+ let stopped = false;
37
+ let watcher;
38
+ const pending = new Set();
39
+ let burstTimer;
40
+ let cursor = "";
41
+ let existingCursor = null;
42
+ // Serialize all remote-touching work so a poll and a push never interleave a
43
+ // read-modify-write on the same path.
44
+ let chain = Promise.resolve();
45
+ const serialize = (fn) => {
46
+ chain = chain.then(fn, fn);
47
+ return chain;
48
+ };
49
+ async function persistCursor() {
50
+ await writeCursor(opts.root, {
51
+ ...sourceFields(opts.server, opts.database, existingCursor),
52
+ repo: opts.repo,
53
+ last_synced_version_id: cursor,
54
+ });
55
+ }
56
+ /**
57
+ * The marker-present offline reconcile (§4.9): a purely-local walk that
58
+ * pushes offline edits and creations without enumerating the remote. Each
59
+ * file runs the ordinary push pass — clean files no-op via the hash gate,
60
+ * dirty ones push, files lacking provenance resolve the remote path first.
61
+ * Offline deletions and moves reconcile lazily (they are not witnessed), per
62
+ * the plan's stated consequence; a full pass (--once) is the lever for those.
63
+ */
64
+ async function localDirtyWalk() {
65
+ for (const docPath of await store.list()) {
66
+ if (!scope.matches(docPath))
67
+ continue;
68
+ try {
69
+ await pushPath(docPath, { client, store, repo: opts.repo, map, log });
70
+ }
71
+ catch (err) {
72
+ log(`push error\t${docPath}\t${err.message}`);
73
+ }
74
+ }
75
+ }
76
+ async function pollFeedOnce() {
77
+ const { cursor: next } = await applyFeed(client, store, scope, {
78
+ repo: opts.repo,
79
+ since: cursor,
80
+ log,
81
+ map,
82
+ });
83
+ if (next !== cursor) {
84
+ cursor = next;
85
+ await persistCursor();
86
+ }
87
+ }
88
+ function schedulePush(docPath) {
89
+ if (!scope.matches(docPath))
90
+ return;
91
+ pending.add(docPath);
92
+ if (burstTimer)
93
+ clearTimeout(burstTimer);
94
+ burstTimer = setTimeout(() => {
95
+ burstTimer = undefined;
96
+ const batch = [...pending];
97
+ pending.clear();
98
+ void serialize(async () => {
99
+ if (stopped)
100
+ return;
101
+ await pushBurst(batch, { client, store, repo: opts.repo, map, log });
102
+ });
103
+ }, debounceMs);
104
+ }
105
+ const ready = (async () => {
106
+ // 1. Startup is deterministic on the cursor marker (§4.9, §7).
107
+ const existing = await readCursor(opts.root);
108
+ existingCursor = existing;
109
+ // Seed the map from local provenance first, so the dirty walk's witnessed
110
+ // deletes (and the feed's) know each file's prev_version_id.
111
+ await seedMapFromLocal(store, scope, map);
112
+ if (existing === null) {
113
+ // Marker absent → full index reconciliation, then resume the feed from R.
114
+ const report = await reconcileOnce(client, store, scope, { repo: opts.repo, log });
115
+ // reconcileOnce may have materialized/pushed files; re-seed so the map
116
+ // reflects the post-reconcile provenance.
117
+ await seedMapFromLocal(store, scope, map);
118
+ cursor = report.through_version;
119
+ }
120
+ else {
121
+ // Marker present → skip the index scan. Run a purely-local dirty walk
122
+ // (pushes offline edits + creations; no remote enumeration), then resume
123
+ // history.since(cursor). The feed is gap-free from any cursor age (§3.2),
124
+ // so an old marker is merely more replay, never a correctness question.
125
+ // Offline deletions/moves reconcile lazily until a full pass (§4.9).
126
+ await localDirtyWalk();
127
+ cursor = existing.last_synced_version_id;
128
+ }
129
+ await persistCursor();
130
+ // 2. Drain the feed from the cursor (advances + re-persists if it moves).
131
+ await pollFeedOnce();
132
+ // 3. Start the watcher (local → remote).
133
+ watcher = watch(opts.root, {
134
+ ignoreInitial: true,
135
+ // Prune the sync state dir; the scope filter still guards everything else.
136
+ ignored: (p) => p.includes(`/${SYNC_DIR}/`) || p.endsWith(`/${SYNC_DIR}`),
137
+ ...(opts.settleMs
138
+ ? { awaitWriteFinish: { stabilityThreshold: opts.settleMs, pollInterval: 100 } }
139
+ : {}),
140
+ });
141
+ const onEvent = (abs) => {
142
+ const docPath = toDocPath(opts.root, abs);
143
+ if (docPath !== null)
144
+ schedulePush(docPath);
145
+ };
146
+ watcher.on("add", onEvent).on("change", onEvent).on("unlink", onEvent);
147
+ // Wait for the initial scan to finish so events aren't missed right after
148
+ // ready resolves (chokidar only watches reliably once it emits `ready`).
149
+ const w = watcher;
150
+ await new Promise((resolve) => w.once("ready", () => resolve()));
151
+ // 4. Start the remote → local poll loop.
152
+ void pollLoop();
153
+ })();
154
+ async function pollLoop() {
155
+ while (!stopped) {
156
+ await sleep(intervalMs);
157
+ if (stopped)
158
+ break;
159
+ await serialize(async () => {
160
+ if (stopped)
161
+ return;
162
+ try {
163
+ await pollFeedOnce();
164
+ }
165
+ catch (err) {
166
+ log(`poll error\t${err.message}`);
167
+ }
168
+ });
169
+ }
170
+ }
171
+ return {
172
+ ready,
173
+ async stop() {
174
+ stopped = true;
175
+ if (burstTimer)
176
+ clearTimeout(burstTimer);
177
+ burstTimer = undefined;
178
+ pending.clear();
179
+ if (watcher)
180
+ await watcher.close();
181
+ await chain.catch(() => { });
182
+ },
183
+ };
184
+ }
185
+ /** Seed the in-memory map from the local files' embedded provenance (§4.2). */
186
+ async function seedMapFromLocal(store, scope, map) {
187
+ for (const path of await store.list()) {
188
+ if (!scope.matches(path))
189
+ continue;
190
+ const text = await store.read(path);
191
+ if (text === null)
192
+ continue;
193
+ const intr = readFileIntrinsics(text);
194
+ if (isIgnored(intr) || !intr.version || !intr.content_hash)
195
+ continue;
196
+ map.set(path, { version_id: intr.version, content_hash: intr.content_hash });
197
+ }
198
+ }
199
+ function sleep(ms) {
200
+ return new Promise((r) => setTimeout(r, ms));
201
+ }
202
+ //# sourceMappingURL=daemon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon.js","sourceRoot":"","sources":["../../src/sync/daemon.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Remote → local: consume the change feed (sync/history plan §4.3). Drains
3
+ * `history.since` from a cursor and applies each ref to the local store, then
4
+ * returns the final cursor and a count. Shared by `--once` (drain to the tip
5
+ * after reconciliation) and the daemon's poll loop (M8).
6
+ *
7
+ * Every operation is idempotent (§4.3): same bytes → same file; removing an
8
+ * absent file is a no-op; the dirty check prevents a replay from clobbering an
9
+ * edit made in a crash window. So a crash between apply and cursor-advance
10
+ * simply replays the batch harmlessly.
11
+ */
12
+ import type { KernelClient } from "../client/kernel-client.js";
13
+ import type { ScopeFilter } from "./paths.js";
14
+ import type { RemoteMap } from "./push.js";
15
+ import type { FileStore } from "./reconcile.js";
16
+ export type ApplyFeedOptions = {
17
+ repo: string;
18
+ since: string;
19
+ log?: (msg: string) => void;
20
+ /**
21
+ * Optional daemon working-map (§4.2 tier 3). When supplied, feed application
22
+ * keeps it current — set on materialize, delete on remove — so a later
23
+ * witnessed unlink knows the file's prev_version_id.
24
+ */
25
+ map?: RemoteMap;
26
+ };
27
+ export type ApplyFeedResult = {
28
+ /** Final resume cursor after draining the currently-safe feed. */
29
+ cursor: string;
30
+ /** Number of refs whose filesystem effect was applied. */
31
+ applied: number;
32
+ };
33
+ /**
34
+ * Drain the safe feed from `since` to the live tip, applying each in-scope ref.
35
+ * Returns the final cursor to persist. Stops when a poll returns no new refs
36
+ * (caught up, or stalled at a hot gap — the caller decides whether to re-poll).
37
+ */
38
+ export declare function applyFeed(client: KernelClient, store: FileStore, scope: ScopeFilter, opts: ApplyFeedOptions): Promise<ApplyFeedResult>;
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Remote → local: consume the change feed (sync/history plan §4.3). Drains
3
+ * `history.since` from a cursor and applies each ref to the local store, then
4
+ * returns the final cursor and a count. Shared by `--once` (drain to the tip
5
+ * after reconciliation) and the daemon's poll loop (M8).
6
+ *
7
+ * Every operation is idempotent (§4.3): same bytes → same file; removing an
8
+ * absent file is a no-op; the dirty check prevents a replay from clobbering an
9
+ * edit made in a crash window. So a crash between apply and cursor-advance
10
+ * simply replays the batch harmlessly.
11
+ */
12
+ import { withVersionSuffix } from "../kernel/deletion.js";
13
+ import { decodeVersionId } from "../kernel/version-id.js";
14
+ import { isDirty, isIgnored, readFileIntrinsics, renderIgnoredSibling, renderMaterialized, } from "./intrinsics.js";
15
+ /**
16
+ * Drain the safe feed from `since` to the live tip, applying each in-scope ref.
17
+ * Returns the final cursor to persist. Stops when a poll returns no new refs
18
+ * (caught up, or stalled at a hot gap — the caller decides whether to re-poll).
19
+ */
20
+ export async function applyFeed(client, store, scope, opts) {
21
+ const log = opts.log ?? (() => { });
22
+ let cursor = opts.since;
23
+ let applied = 0;
24
+ for (;;) {
25
+ const page = await client.history.since({ after_version: cursor, repo: opts.repo });
26
+ for (const ref of page.refs) {
27
+ if (await applyRef(client, store, scope, opts.repo, ref, log, opts.map))
28
+ applied++;
29
+ }
30
+ // No forward progress → caught up (or a hot gap). Stop draining.
31
+ if (page.next_since === cursor || page.refs.length === 0) {
32
+ cursor = page.next_since;
33
+ break;
34
+ }
35
+ cursor = page.next_since;
36
+ }
37
+ return { cursor, applied };
38
+ }
39
+ /** Apply one feed ref to the local store. Returns true if it changed a file. */
40
+ async function applyRef(client, store, scope, repo, ref, log, map) {
41
+ if (ref.op === "delete") {
42
+ // Remove the local file at prev_path IF clean; dirty ⇒ keep (resurrection
43
+ // happens on its next local-change cycle). §4.3 delete.
44
+ const target = ref.prev_path;
45
+ if (target === null || !scope.matches(target))
46
+ return false;
47
+ map?.delete(target);
48
+ const text = await store.read(target);
49
+ if (text === null)
50
+ return false;
51
+ const intr = readFileIntrinsics(text);
52
+ if (isIgnored(intr) || isDirty(intr))
53
+ return false; // preserve local work
54
+ await store.remove(target);
55
+ log(`feed delete\t${target}`);
56
+ return true;
57
+ }
58
+ if (ref.op === "move") {
59
+ const from = ref.prev_path;
60
+ if (from !== null && scope.matches(from)) {
61
+ map?.delete(from);
62
+ const text = await store.read(from);
63
+ if (text !== null) {
64
+ const intr = readFileIntrinsics(text);
65
+ if (!isIgnored(intr) && !isDirty(intr))
66
+ await store.remove(from);
67
+ }
68
+ }
69
+ // Fall through to materialize at the destination path.
70
+ }
71
+ // create / update / move-destination: materialize at ref.path unless the
72
+ // local file already holds these bytes, is at-or-ahead of this ref, is
73
+ // dirty against a *newer* remote, or is $sync: ignore.
74
+ if (!scope.matches(ref.path))
75
+ return false;
76
+ const existing = await store.read(ref.path);
77
+ if (existing !== null) {
78
+ const intr = readFileIntrinsics(existing);
79
+ if (isIgnored(intr))
80
+ return false;
81
+ if (intr.computed_hash === ref.content_hash) {
82
+ // Bytes already present (our own push echoing back, or a vault copy).
83
+ map?.set(ref.path, { version_id: ref.version_id, content_hash: ref.content_hash });
84
+ // Repair provenance only if the embedded version lags.
85
+ if (intr.version === ref.version_id)
86
+ return false;
87
+ if (versionAtOrAhead(intr.version, ref.version_id))
88
+ return false;
89
+ const v = await client.docs.get_version(repo, ref.version_id);
90
+ await store.write(ref.path, renderMaterialized(v), { preserveMtime: true });
91
+ return true;
92
+ }
93
+ // Local is at or ahead of this ref (echo of our push, or a replay of an
94
+ // older version). Never clobber and never park a sibling of something we
95
+ // already have — including when the user has typed more since (dirty).
96
+ if (versionAtOrAhead(intr.version, ref.version_id))
97
+ return false;
98
+ if (isDirty(intr)) {
99
+ // A local edit collides with a *newer* incoming version → conflict, not
100
+ // overwrite. Park the remote as an ignored sibling; keep local bytes.
101
+ const v = await client.docs.get_version(repo, ref.version_id);
102
+ await store.write(withVersionSuffix(ref.path, ref.version_id), renderIgnoredSibling(v));
103
+ log(`feed conflict\t${ref.path}`);
104
+ return true;
105
+ }
106
+ }
107
+ const v = await client.docs.get_version(repo, ref.version_id);
108
+ await store.write(ref.path, renderMaterialized(v));
109
+ map?.set(ref.path, { version_id: ref.version_id, content_hash: ref.content_hash });
110
+ log(`feed ${ref.op}\t${ref.path}`);
111
+ return true;
112
+ }
113
+ /** True when the local embedded version is this ref or a later one. */
114
+ function versionAtOrAhead(localVersion, refVersion) {
115
+ if (!localVersion)
116
+ return false;
117
+ if (localVersion === refVersion)
118
+ return true;
119
+ const local = decodeVersionId(localVersion);
120
+ const ref = decodeVersionId(refVersion);
121
+ if (local === null || ref === null)
122
+ return false;
123
+ return local > ref;
124
+ }
125
+ //# sourceMappingURL=feed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feed.js","sourceRoot":"","sources":["../../src/sync/feed.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The real filesystem `FileStore` (sync/history plan §4). Maps repo-relative
3
+ * POSIX doc paths to files under `<root>`, walking the vault to enumerate the
4
+ * live set. The `.mrplex/` state dir is skipped by the scope filter, but we
5
+ * also prune it (and dotdirs like `.obsidian/`) during the walk for speed.
6
+ */
7
+ import type { FileStore } from "./reconcile.js";
8
+ export declare function createFsStore(root: string): FileStore;
@@ -0,0 +1,76 @@
1
+ /**
2
+ * The real filesystem `FileStore` (sync/history plan §4). Maps repo-relative
3
+ * POSIX doc paths to files under `<root>`, walking the vault to enumerate the
4
+ * live set. The `.mrplex/` state dir is skipped by the scope filter, but we
5
+ * also prune it (and dotdirs like `.obsidian/`) during the walk for speed.
6
+ */
7
+ import { mkdir, readFile, readdir, rm, stat, utimes, writeFile } from "node:fs/promises";
8
+ import { dirname } from "node:path";
9
+ import { toDocPath, toLocalPath } from "./paths.js";
10
+ export function createFsStore(root) {
11
+ return {
12
+ async list() {
13
+ const out = [];
14
+ await walk(root, root, out);
15
+ return out;
16
+ },
17
+ async read(docPath) {
18
+ try {
19
+ return await readFile(toLocalPath(root, docPath), "utf8");
20
+ }
21
+ catch (err) {
22
+ if (err.code === "ENOENT")
23
+ return null;
24
+ throw err;
25
+ }
26
+ },
27
+ async write(docPath, text, opts) {
28
+ const abs = toLocalPath(root, docPath);
29
+ await mkdir(dirname(abs), { recursive: true });
30
+ let mtime;
31
+ if (opts?.preserveMtime) {
32
+ try {
33
+ mtime = (await stat(abs)).mtime;
34
+ }
35
+ catch (err) {
36
+ if (err.code !== "ENOENT")
37
+ throw err;
38
+ }
39
+ }
40
+ await writeFile(abs, text, "utf8");
41
+ if (mtime)
42
+ await utimes(abs, mtime, mtime);
43
+ },
44
+ async remove(docPath) {
45
+ await rm(toLocalPath(root, docPath), { force: true });
46
+ },
47
+ };
48
+ }
49
+ async function walk(root, dir, out) {
50
+ let entries;
51
+ try {
52
+ entries = await readdir(dir, { withFileTypes: true });
53
+ }
54
+ catch (err) {
55
+ if (err.code === "ENOENT")
56
+ return;
57
+ throw err;
58
+ }
59
+ for (const ent of entries) {
60
+ if (ent.isDirectory()) {
61
+ // Prune any dot-prefixed dir (the `.mrplex/` state dir, app dotdirs like
62
+ // `.obsidian/`). This is exactly what makeScopeFilter's hasDotSegment
63
+ // excludes, so the walk and the scope filter agree in both directions
64
+ // (§4.1); pruning here just avoids the descent.
65
+ if (ent.name.startsWith("."))
66
+ continue;
67
+ await walk(root, `${dir}/${ent.name}`, out);
68
+ }
69
+ else if (ent.isFile()) {
70
+ const docPath = toDocPath(root, `${dir}/${ent.name}`);
71
+ if (docPath !== null)
72
+ out.push(docPath);
73
+ }
74
+ }
75
+ }
76
+ //# sourceMappingURL=fs-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs-store.js","sourceRoot":"","sources":["../../src/sync/fs-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Reading and writing the self-describing frontmatter that makes sync work
3
+ * (sync/history plan §2.4, §4.2). A materialized file embeds `$version` and
4
+ * `$content_hash`; a user may also add the `$sync: ignore` client directive.
5
+ * Per-file sync state lives *in the file* — this module is the single place
6
+ * that parses it out and renders it back.
7
+ */
8
+ import type { Version } from "../kernel/wire.js";
9
+ /** The `$sync` directive value we honor (§4.2). Only `ignore` is defined. */
10
+ export declare const SYNC_IGNORE = "ignore";
11
+ export type FileIntrinsics = {
12
+ /** Embedded `$version` (ancestry), or undefined if absent. */
13
+ version?: string;
14
+ /** Embedded `$content_hash` (clean-state fingerprint), or undefined. */
15
+ content_hash?: string;
16
+ /** Embedded `$sync` client directive, or undefined. */
17
+ sync?: string;
18
+ /** The canonical content hash computed from the file's user bytes. */
19
+ computed_hash: string;
20
+ };
21
+ /**
22
+ * Parse a file's embedded intrinsics and compute its canonical content hash.
23
+ * The computed hash excludes all `$*` lines, so adding/removing intrinsics or
24
+ * the `$sync` directive never changes it (§4.2).
25
+ */
26
+ export declare function readFileIntrinsics(text: string): FileIntrinsics;
27
+ /** True when the file opts out of sync via `$sync: ignore` (§4.2). */
28
+ export declare function isIgnored(intr: FileIntrinsics): boolean;
29
+ /**
30
+ * A file is "dirty" when its user bytes differ from the version its embedded
31
+ * `$version` names — i.e. the computed hash ≠ the embedded `$content_hash`
32
+ * (§4.4 hash gate). A file with no embedded hash is treated as dirty (we
33
+ * cannot prove it clean).
34
+ */
35
+ export declare function isDirty(intr: FileIntrinsics): boolean;
36
+ /**
37
+ * Render a version as a self-describing file: strip any stale `$*` lines from
38
+ * the stored frontmatter, then append `$version` and `$content_hash` in fixed
39
+ * order (§2.4). Robust whether or not the transport already injected them.
40
+ */
41
+ export declare function renderMaterialized(v: Version): string;
42
+ /**
43
+ * Render a version as a conflict sibling (§4.8): the normal materialization
44
+ * plus `$sync: ignore`, which is the *only* thing keeping it out of sync.
45
+ */
46
+ export declare function renderIgnoredSibling(v: Version): string;
47
+ /**
48
+ * Stamp `$version` / `$content_hash` onto an existing file without replacing
49
+ * its user bytes. Used when the editor saved again between our kernel write
50
+ * and the ack rewrite — we must not revert their typing.
51
+ */
52
+ export declare function stampProvenance(text: string, versionId: string, contentHash: string): string;