@quolu/lattice 0.1.0 → 0.12.2

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 (370) hide show
  1. package/README.md +87 -4
  2. package/bin/lattice-bridge.mjs +87 -0
  3. package/bin/lattice-dashboard.mjs +70 -0
  4. package/bin/lattice-mcp.mjs +9 -9
  5. package/bin/lattice.mjs +61 -8
  6. package/docs/bridge-setup.md +87 -0
  7. package/docs/schemas/lattice.plan_create_input.v1.schema.json +56 -0
  8. package/docs/schemas/lattice.plan_create_input.v2.schema.json +72 -0
  9. package/docs/schemas/lattice.plan_create_input.v3.schema.json +81 -0
  10. package/package.json +19 -7
  11. package/sensor/dist/bin/command-supervision.d.ts +1 -1
  12. package/sensor/dist/bin/command-supervision.js +4 -4
  13. package/sensor/dist/bin/command-supervision.js.map +1 -1
  14. package/sensor/dist/bin/fatal-handler.js +2 -2
  15. package/sensor/dist/bin/fatal-handler.js.map +1 -1
  16. package/sensor/dist/bin/{codegraph.js → lattice-sensor.js} +207 -328
  17. package/sensor/dist/bin/node-version-check.d.ts +7 -5
  18. package/sensor/dist/bin/node-version-check.d.ts.map +1 -1
  19. package/sensor/dist/bin/node-version-check.js +19 -14
  20. package/sensor/dist/bin/node-version-check.js.map +1 -1
  21. package/sensor/dist/context/index.d.ts +1 -1
  22. package/sensor/dist/context/index.js +6 -6
  23. package/sensor/dist/context/index.js.map +1 -1
  24. package/sensor/dist/db/index.d.ts +5 -5
  25. package/sensor/dist/db/index.d.ts.map +1 -1
  26. package/sensor/dist/db/index.js +7 -7
  27. package/sensor/dist/db/index.js.map +1 -1
  28. package/sensor/dist/db/migrations.js +1 -1
  29. package/sensor/dist/db/migrations.js.map +1 -1
  30. package/sensor/dist/db/queries.d.ts +1 -1
  31. package/sensor/dist/db/queries.js +5 -5
  32. package/sensor/dist/db/queries.js.map +1 -1
  33. package/sensor/dist/db/schema.sql +2 -2
  34. package/sensor/dist/db/sqlite-adapter.d.ts +2 -2
  35. package/sensor/dist/db/sqlite-adapter.js +3 -3
  36. package/sensor/dist/db/sqlite-adapter.js.map +1 -1
  37. package/sensor/dist/db/wal-valve.d.ts +1 -1
  38. package/sensor/dist/db/wal-valve.d.ts.map +1 -1
  39. package/sensor/dist/db/wal-valve.js +2 -2
  40. package/sensor/dist/db/wal-valve.js.map +1 -1
  41. package/sensor/dist/directory.d.ts +27 -63
  42. package/sensor/dist/directory.d.ts.map +1 -1
  43. package/sensor/dist/directory.js +78 -134
  44. package/sensor/dist/directory.js.map +1 -1
  45. package/sensor/dist/errors.d.ts +15 -15
  46. package/sensor/dist/errors.d.ts.map +1 -1
  47. package/sensor/dist/errors.js +21 -21
  48. package/sensor/dist/errors.js.map +1 -1
  49. package/sensor/dist/extraction/dynamic-import.d.ts +1 -1
  50. package/sensor/dist/extraction/dynamic-import.js +1 -1
  51. package/sensor/dist/extraction/extraction-version.d.ts +1 -1
  52. package/sensor/dist/extraction/extraction-version.js +1 -1
  53. package/sensor/dist/extraction/generated-detection.d.ts +1 -1
  54. package/sensor/dist/extraction/generated-detection.js +1 -1
  55. package/sensor/dist/extraction/grammars.d.ts +3 -3
  56. package/sensor/dist/extraction/grammars.js +5 -5
  57. package/sensor/dist/extraction/grammars.js.map +1 -1
  58. package/sensor/dist/extraction/index.d.ts +6 -6
  59. package/sensor/dist/extraction/index.js +50 -50
  60. package/sensor/dist/extraction/index.js.map +1 -1
  61. package/sensor/dist/extraction/kernel/decode.d.ts +1 -1
  62. package/sensor/dist/extraction/kernel/decode.js +1 -1
  63. package/sensor/dist/extraction/kernel/index.d.ts +3 -3
  64. package/sensor/dist/extraction/kernel/index.js +6 -6
  65. package/sensor/dist/extraction/kernel/index.js.map +1 -1
  66. package/sensor/dist/extraction/kernel/layout.d.ts +1 -1
  67. package/sensor/dist/extraction/kernel/layout.js +1 -1
  68. package/sensor/dist/extraction/kernel/loader.d.ts +6 -6
  69. package/sensor/dist/extraction/kernel/loader.js +13 -13
  70. package/sensor/dist/extraction/kernel/loader.js.map +1 -1
  71. package/sensor/dist/extraction/languages/cfquery.d.ts +1 -1
  72. package/sensor/dist/extraction/languages/cfquery.js +1 -1
  73. package/sensor/dist/extraction/languages/lua.d.ts +11 -0
  74. package/sensor/dist/extraction/languages/lua.d.ts.map +1 -1
  75. package/sensor/dist/extraction/languages/lua.js +1 -0
  76. package/sensor/dist/extraction/languages/lua.js.map +1 -1
  77. package/sensor/dist/extraction/parse-pool.d.ts +5 -5
  78. package/sensor/dist/extraction/parse-pool.d.ts.map +1 -1
  79. package/sensor/dist/extraction/parse-pool.js +4 -4
  80. package/sensor/dist/extraction/parse-worker.js +1 -1
  81. package/sensor/dist/extraction/parse-worker.js.map +1 -1
  82. package/sensor/dist/extraction/store-writer.d.ts +1 -1
  83. package/sensor/dist/extraction/store-writer.js +1 -1
  84. package/sensor/dist/extraction/svelte-extractor.d.ts +1 -1
  85. package/sensor/dist/extraction/svelte-extractor.js +1 -1
  86. package/sensor/dist/extraction/tree-sitter-helpers.js +1 -1
  87. package/sensor/dist/extraction/tree-sitter.d.ts +2 -2
  88. package/sensor/dist/extraction/tree-sitter.d.ts.map +1 -1
  89. package/sensor/dist/extraction/tree-sitter.js +71 -4
  90. package/sensor/dist/extraction/tree-sitter.js.map +1 -1
  91. package/sensor/dist/extraction/wasm-runtime-flags.d.ts +2 -2
  92. package/sensor/dist/extraction/wasm-runtime-flags.d.ts.map +1 -1
  93. package/sensor/dist/extraction/wasm-runtime-flags.js +5 -5
  94. package/sensor/dist/extraction/wasm-runtime-flags.js.map +1 -1
  95. package/sensor/dist/index.d.ts +36 -36
  96. package/sensor/dist/index.d.ts.map +1 -1
  97. package/sensor/dist/index.js +71 -71
  98. package/sensor/dist/index.js.map +1 -1
  99. package/sensor/dist/mcp/daemon-manager.js +1 -1
  100. package/sensor/dist/mcp/daemon-paths.d.ts +3 -3
  101. package/sensor/dist/mcp/daemon-paths.js +7 -7
  102. package/sensor/dist/mcp/daemon-paths.js.map +1 -1
  103. package/sensor/dist/mcp/daemon-registry.d.ts +2 -2
  104. package/sensor/dist/mcp/daemon-registry.js +5 -5
  105. package/sensor/dist/mcp/daemon.d.ts +7 -7
  106. package/sensor/dist/mcp/daemon.d.ts.map +1 -1
  107. package/sensor/dist/mcp/daemon.js +21 -21
  108. package/sensor/dist/mcp/daemon.js.map +1 -1
  109. package/sensor/dist/mcp/dynamic-boundaries.js +1 -1
  110. package/sensor/dist/mcp/engine.d.ts +9 -9
  111. package/sensor/dist/mcp/engine.d.ts.map +1 -1
  112. package/sensor/dist/mcp/engine.js +44 -44
  113. package/sensor/dist/mcp/engine.js.map +1 -1
  114. package/sensor/dist/mcp/index.d.ts +14 -14
  115. package/sensor/dist/mcp/index.d.ts.map +1 -1
  116. package/sensor/dist/mcp/index.js +41 -41
  117. package/sensor/dist/mcp/index.js.map +1 -1
  118. package/sensor/dist/mcp/liveness-watchdog.js +7 -7
  119. package/sensor/dist/mcp/liveness-watchdog.js.map +1 -1
  120. package/sensor/dist/mcp/ppid-watchdog.d.ts +3 -3
  121. package/sensor/dist/mcp/ppid-watchdog.js +2 -2
  122. package/sensor/dist/mcp/proxy.d.ts.map +1 -1
  123. package/sensor/dist/mcp/proxy.js +37 -37
  124. package/sensor/dist/mcp/proxy.js.map +1 -1
  125. package/sensor/dist/mcp/query-pool.d.ts +3 -3
  126. package/sensor/dist/mcp/query-pool.js +7 -7
  127. package/sensor/dist/mcp/query-pool.js.map +1 -1
  128. package/sensor/dist/mcp/query-worker.d.ts +2 -2
  129. package/sensor/dist/mcp/query-worker.js +7 -7
  130. package/sensor/dist/mcp/query-worker.js.map +1 -1
  131. package/sensor/dist/mcp/server-instructions.d.ts +10 -12
  132. package/sensor/dist/mcp/server-instructions.d.ts.map +1 -1
  133. package/sensor/dist/mcp/server-instructions.js +39 -37
  134. package/sensor/dist/mcp/server-instructions.js.map +1 -1
  135. package/sensor/dist/mcp/session.d.ts +2 -2
  136. package/sensor/dist/mcp/session.js +18 -18
  137. package/sensor/dist/mcp/session.js.map +1 -1
  138. package/sensor/dist/mcp/startup-handshake.d.ts +3 -3
  139. package/sensor/dist/mcp/startup-handshake.d.ts.map +1 -1
  140. package/sensor/dist/mcp/startup-handshake.js +3 -3
  141. package/sensor/dist/mcp/startup-handshake.js.map +1 -1
  142. package/sensor/dist/mcp/tools.d.ts +53 -53
  143. package/sensor/dist/mcp/tools.d.ts.map +1 -1
  144. package/sensor/dist/mcp/tools.js +180 -216
  145. package/sensor/dist/mcp/tools.js.map +1 -1
  146. package/sensor/dist/mcp/transport.d.ts +1 -1
  147. package/sensor/dist/mcp/transport.js +6 -6
  148. package/sensor/dist/mcp/transport.js.map +1 -1
  149. package/sensor/dist/mcp/version.d.ts +2 -2
  150. package/sensor/dist/mcp/version.d.ts.map +1 -1
  151. package/sensor/dist/mcp/version.js +3 -3
  152. package/sensor/dist/mcp/version.js.map +1 -1
  153. package/sensor/dist/project-config.d.ts +6 -6
  154. package/sensor/dist/project-config.d.ts.map +1 -1
  155. package/sensor/dist/project-config.js +10 -10
  156. package/sensor/dist/project-config.js.map +1 -1
  157. package/sensor/dist/resolution/callback-synthesizer.js +4 -4
  158. package/sensor/dist/resolution/callback-synthesizer.js.map +1 -1
  159. package/sensor/dist/resolution/frameworks/drupal.d.ts +3 -3
  160. package/sensor/dist/resolution/frameworks/drupal.js +3 -3
  161. package/sensor/dist/resolution/frameworks/fabric.js +1 -1
  162. package/sensor/dist/resolution/frameworks/fabric.js.map +1 -1
  163. package/sensor/dist/resolution/frameworks/java.js +1 -1
  164. package/sensor/dist/resolution/frameworks/java.js.map +1 -1
  165. package/sensor/dist/resolution/frameworks/svelte.js +1 -1
  166. package/sensor/dist/resolution/frameworks/svelte.js.map +1 -1
  167. package/sensor/dist/resolution/goframe-synthesizer.d.ts +1 -1
  168. package/sensor/dist/resolution/goframe-synthesizer.js +1 -1
  169. package/sensor/dist/resolution/index.d.ts +1 -1
  170. package/sensor/dist/resolution/index.js +9 -9
  171. package/sensor/dist/resolution/index.js.map +1 -1
  172. package/sensor/dist/resolution/name-matcher.js +2 -2
  173. package/sensor/dist/resolution/name-matcher.js.map +1 -1
  174. package/sensor/dist/resolution/resolver-pool.d.ts +2 -2
  175. package/sensor/dist/resolution/resolver-pool.js +4 -4
  176. package/sensor/dist/resolution/resolver-pool.js.map +1 -1
  177. package/sensor/dist/resolution/resolver-worker.js +2 -2
  178. package/sensor/dist/resolution/resolver-worker.js.map +1 -1
  179. package/sensor/dist/resolution/workspace-packages.js +1 -1
  180. package/sensor/dist/resolution/workspace-packages.js.map +1 -1
  181. package/sensor/dist/search/identifier-segments.d.ts +1 -1
  182. package/sensor/dist/search/identifier-segments.js +1 -1
  183. package/sensor/dist/search/query-utils.d.ts +1 -0
  184. package/sensor/dist/search/query-utils.d.ts.map +1 -1
  185. package/sensor/dist/search/query-utils.js +10 -3
  186. package/sensor/dist/search/query-utils.js.map +1 -1
  187. package/sensor/dist/sync/git-hooks.d.ts +9 -9
  188. package/sensor/dist/sync/git-hooks.d.ts.map +1 -1
  189. package/sensor/dist/sync/git-hooks.js +15 -15
  190. package/sensor/dist/sync/git-hooks.js.map +1 -1
  191. package/sensor/dist/sync/watch-policy.d.ts +2 -2
  192. package/sensor/dist/sync/watch-policy.js +5 -5
  193. package/sensor/dist/sync/watch-policy.js.map +1 -1
  194. package/sensor/dist/sync/watcher.d.ts +2 -2
  195. package/sensor/dist/sync/watcher.d.ts.map +1 -1
  196. package/sensor/dist/sync/watcher.js +16 -16
  197. package/sensor/dist/sync/watcher.js.map +1 -1
  198. package/sensor/dist/sync/worktree.d.ts +5 -5
  199. package/sensor/dist/sync/worktree.d.ts.map +1 -1
  200. package/sensor/dist/sync/worktree.js +9 -9
  201. package/sensor/dist/sync/worktree.js.map +1 -1
  202. package/sensor/dist/telemetry/index.d.ts +8 -8
  203. package/sensor/dist/telemetry/index.d.ts.map +1 -1
  204. package/sensor/dist/telemetry/index.js +16 -16
  205. package/sensor/dist/telemetry/index.js.map +1 -1
  206. package/sensor/dist/types.d.ts +2 -2
  207. package/sensor/dist/types.js +1 -1
  208. package/sensor/dist/ui/color.d.ts +2 -2
  209. package/sensor/dist/ui/color.js +2 -2
  210. package/sensor/dist/ui/glyphs.d.ts +3 -3
  211. package/sensor/dist/ui/glyphs.js +7 -7
  212. package/sensor/dist/ui/glyphs.js.map +1 -1
  213. package/sensor/dist/utils.d.ts +4 -4
  214. package/sensor/dist/utils.js +8 -8
  215. package/sensor/dist/utils.js.map +1 -1
  216. package/sensor/package.json +8 -13
  217. package/src/artifact-contracts.mjs +2 -2
  218. package/src/boundary-compiler.mjs +28 -28
  219. package/src/boundary-observation-compiler-v2.mjs +10 -10
  220. package/src/bounded-seam.mjs +144 -0
  221. package/src/bridge-cli.mjs +227 -0
  222. package/src/bridge-config.mjs +346 -0
  223. package/src/bridge-daemon.mjs +328 -0
  224. package/src/bridge-launch-agent.mjs +311 -0
  225. package/src/bridge-server.mjs +309 -0
  226. package/src/cli-help.mjs +163 -0
  227. package/src/control-compiler.mjs +30 -30
  228. package/src/dag-chain.mjs +261 -0
  229. package/src/factory-diagnostics.mjs +8 -7
  230. package/src/hash-chain.mjs +76 -0
  231. package/src/node-version-guard.mjs +5 -4
  232. package/src/project-cli.mjs +392 -0
  233. package/src/project-identity.mjs +71 -0
  234. package/src/rc1-evidence-bundle.mjs +16 -16
  235. package/src/rc1-v4-campaign.mjs +15 -14
  236. package/src/rc1-v5-artifact-set.mjs +2 -2
  237. package/src/rc1-v5-campaign.mjs +15 -14
  238. package/src/rc1-v6-artifact-set.mjs +18 -18
  239. package/src/rc1-v6-campaign.mjs +16 -16
  240. package/src/rc1-v6-causal-binding.mjs +16 -14
  241. package/src/rc1-v6-measurement.mjs +20 -38
  242. package/src/rc2-artifact-set.mjs +45 -40
  243. package/src/rc2-campaign.mjs +48 -66
  244. package/src/rc2-delivery-policy-front-end.mjs +30 -30
  245. package/src/rc2-rc1-transfer-front-end.mjs +5 -5
  246. package/src/rc3-actual-dogfood.mjs +6 -6
  247. package/src/rc3-dogfood-scaffold.mjs +12 -7
  248. package/src/rc3-scripted-campaign.mjs +75 -21
  249. package/src/rc4-stage1-dogfood.mjs +24 -7
  250. package/src/runtime-cli.mjs +2424 -45
  251. package/src/runtime-contracts.mjs +7 -7
  252. package/src/runtime-control-store.mjs +547 -0
  253. package/src/runtime-controller-protocol.mjs +517 -0
  254. package/src/runtime-direct-os-observer.mjs +284 -0
  255. package/src/runtime-engine.mjs +2 -2
  256. package/src/runtime-errors.mjs +1 -1
  257. package/src/runtime-event-store.mjs +15 -43
  258. package/src/runtime-front-end.mjs +26 -26
  259. package/src/runtime-gate-store.mjs +477 -0
  260. package/src/runtime-hold-recompile.mjs +214 -23
  261. package/src/runtime-lifecycle-lock.mjs +290 -0
  262. package/src/runtime-managed-supervisor.mjs +1276 -0
  263. package/src/runtime-multi-epoch-store.mjs +761 -0
  264. package/src/runtime-projection.mjs +62 -0
  265. package/src/seam-transform.mjs +38 -38
  266. package/src/{codegraph-adapter.mjs → sensor-adapter.mjs} +7 -7
  267. package/src/sensor-cli.mjs +107 -0
  268. package/src/sensor-runtime.mjs +48 -0
  269. package/src/timestamp-contract.mjs +8 -0
  270. package/src/todo-chain.mjs +178 -0
  271. package/src/todo-cli.mjs +1032 -0
  272. package/src/todo-contracts.mjs +501 -0
  273. package/src/todo-dashboard-registry.mjs +318 -0
  274. package/src/todo-gantt-html.mjs +416 -0
  275. package/src/todo-gantt-layout.mjs +603 -0
  276. package/src/todo-gantt-live.mjs +269 -0
  277. package/src/todo-gantt-presentation.mjs +217 -0
  278. package/src/todo-gantt-svg.mjs +249 -0
  279. package/src/todo-markdown-renderer.mjs +260 -0
  280. package/src/todo-migration.mjs +260 -0
  281. package/src/todo-narrative-anchor.mjs +130 -0
  282. package/src/todo-revision.mjs +425 -0
  283. package/src/todo-status.mjs +340 -0
  284. package/src/todo-store.mjs +3451 -0
  285. package/src/treatment-compiler.mjs +28 -28
  286. package/src/treatment-runner.mjs +42 -44
  287. package/sensor/dist/bin/codegraph.d.ts +0 -26
  288. package/sensor/dist/bin/codegraph.d.ts.map +0 -1
  289. package/sensor/dist/bin/codegraph.js.map +0 -1
  290. package/sensor/dist/bin/uninstall.d.ts +0 -14
  291. package/sensor/dist/bin/uninstall.d.ts.map +0 -1
  292. package/sensor/dist/bin/uninstall.js +0 -36
  293. package/sensor/dist/bin/uninstall.js.map +0 -1
  294. package/sensor/dist/installer/beta-signup.d.ts +0 -54
  295. package/sensor/dist/installer/beta-signup.d.ts.map +0 -1
  296. package/sensor/dist/installer/beta-signup.js +0 -178
  297. package/sensor/dist/installer/beta-signup.js.map +0 -1
  298. package/sensor/dist/installer/config-writer.d.ts +0 -28
  299. package/sensor/dist/installer/config-writer.d.ts.map +0 -1
  300. package/sensor/dist/installer/config-writer.js +0 -91
  301. package/sensor/dist/installer/config-writer.js.map +0 -1
  302. package/sensor/dist/installer/index.d.ts +0 -142
  303. package/sensor/dist/installer/index.d.ts.map +0 -1
  304. package/sensor/dist/installer/index.js +0 -622
  305. package/sensor/dist/installer/index.js.map +0 -1
  306. package/sensor/dist/installer/instructions-template.d.ts +0 -41
  307. package/sensor/dist/installer/instructions-template.d.ts.map +0 -1
  308. package/sensor/dist/installer/instructions-template.js +0 -53
  309. package/sensor/dist/installer/instructions-template.js.map +0 -1
  310. package/sensor/dist/installer/targets/antigravity.d.ts +0 -57
  311. package/sensor/dist/installer/targets/antigravity.d.ts.map +0 -1
  312. package/sensor/dist/installer/targets/antigravity.js +0 -308
  313. package/sensor/dist/installer/targets/antigravity.js.map +0 -1
  314. package/sensor/dist/installer/targets/claude.d.ts +0 -62
  315. package/sensor/dist/installer/targets/claude.d.ts.map +0 -1
  316. package/sensor/dist/installer/targets/claude.js +0 -454
  317. package/sensor/dist/installer/targets/claude.js.map +0 -1
  318. package/sensor/dist/installer/targets/codex.d.ts +0 -18
  319. package/sensor/dist/installer/targets/codex.d.ts.map +0 -1
  320. package/sensor/dist/installer/targets/codex.js +0 -185
  321. package/sensor/dist/installer/targets/codex.js.map +0 -1
  322. package/sensor/dist/installer/targets/cursor.d.ts +0 -35
  323. package/sensor/dist/installer/targets/cursor.d.ts.map +0 -1
  324. package/sensor/dist/installer/targets/cursor.js +0 -254
  325. package/sensor/dist/installer/targets/cursor.js.map +0 -1
  326. package/sensor/dist/installer/targets/gemini.d.ts +0 -26
  327. package/sensor/dist/installer/targets/gemini.d.ts.map +0 -1
  328. package/sensor/dist/installer/targets/gemini.js +0 -165
  329. package/sensor/dist/installer/targets/gemini.js.map +0 -1
  330. package/sensor/dist/installer/targets/hermes.d.ts +0 -18
  331. package/sensor/dist/installer/targets/hermes.d.ts.map +0 -1
  332. package/sensor/dist/installer/targets/hermes.js +0 -359
  333. package/sensor/dist/installer/targets/hermes.js.map +0 -1
  334. package/sensor/dist/installer/targets/kiro.d.ts +0 -27
  335. package/sensor/dist/installer/targets/kiro.d.ts.map +0 -1
  336. package/sensor/dist/installer/targets/kiro.js +0 -178
  337. package/sensor/dist/installer/targets/kiro.js.map +0 -1
  338. package/sensor/dist/installer/targets/opencode.d.ts +0 -38
  339. package/sensor/dist/installer/targets/opencode.d.ts.map +0 -1
  340. package/sensor/dist/installer/targets/opencode.js +0 -288
  341. package/sensor/dist/installer/targets/opencode.js.map +0 -1
  342. package/sensor/dist/installer/targets/registry.d.ts +0 -35
  343. package/sensor/dist/installer/targets/registry.d.ts.map +0 -1
  344. package/sensor/dist/installer/targets/registry.js +0 -91
  345. package/sensor/dist/installer/targets/registry.js.map +0 -1
  346. package/sensor/dist/installer/targets/shared.d.ts +0 -101
  347. package/sensor/dist/installer/targets/shared.d.ts.map +0 -1
  348. package/sensor/dist/installer/targets/shared.js +0 -264
  349. package/sensor/dist/installer/targets/shared.js.map +0 -1
  350. package/sensor/dist/installer/targets/toml.d.ts +0 -52
  351. package/sensor/dist/installer/targets/toml.d.ts.map +0 -1
  352. package/sensor/dist/installer/targets/toml.js +0 -147
  353. package/sensor/dist/installer/targets/toml.js.map +0 -1
  354. package/sensor/dist/installer/targets/types.d.ts +0 -108
  355. package/sensor/dist/installer/targets/types.d.ts.map +0 -1
  356. package/sensor/dist/installer/targets/types.js +0 -16
  357. package/sensor/dist/installer/targets/types.js.map +0 -1
  358. package/sensor/dist/upgrade/index.d.ts +0 -171
  359. package/sensor/dist/upgrade/index.d.ts.map +0 -1
  360. package/sensor/dist/upgrade/index.js +0 -659
  361. package/sensor/dist/upgrade/index.js.map +0 -1
  362. package/sensor/dist/upgrade/remove-binary.d.ts +0 -87
  363. package/sensor/dist/upgrade/remove-binary.d.ts.map +0 -1
  364. package/sensor/dist/upgrade/remove-binary.js +0 -289
  365. package/sensor/dist/upgrade/remove-binary.js.map +0 -1
  366. package/sensor/dist/upgrade/update-check.d.ts +0 -92
  367. package/sensor/dist/upgrade/update-check.d.ts.map +0 -1
  368. package/sensor/dist/upgrade/update-check.js +0 -258
  369. package/sensor/dist/upgrade/update-check.js.map +0 -1
  370. package/src/bootstrap.mjs +0 -88
@@ -0,0 +1,269 @@
1
+ import { createServer } from 'node:http';
2
+
3
+ const LOOPBACK = '127.0.0.1';
4
+ const POLL_MS = 500;
5
+ const HTTP_ERROR_SCHEMA = 'lattice.todo_gantt_http_error.v1';
6
+ const PROJECT_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
7
+
8
+ function projectPath(projectId) {
9
+ return `/projects/${encodeURIComponent(projectId)}/`;
10
+ }
11
+
12
+ function liveHtml(html, headDigest, eventsPath) {
13
+ const controller = `<script>(()=>{const badge=document.createElement('div');badge.setAttribute('role','status');badge.style.cssText='position:fixed;right:12px;bottom:12px;z-index:99;padding:6px 10px;border:1px solid #d9d8d4;border-radius:4px;background:#fcfcfb;font:600 12px system-ui';badge.textContent='進捗: 接続中';document.body.append(badge);let head=${JSON.stringify(headDigest)};const stream=new EventSource(${JSON.stringify(eventsPath)});stream.addEventListener('state',event=>{const next=JSON.parse(event.data);badge.textContent='進捗: 最新';if(next.head_digest!==head){badge.textContent='進捗: 更新を反映中';location.reload();}});stream.addEventListener('lattice-error',event=>{const detail=JSON.parse(event.data);badge.textContent='進捗: エラー '+detail.code;badge.style.borderColor='#d03b3b';});stream.onerror=()=>{badge.textContent='進捗: 再接続中';};})();</script>`;
14
+ return html
15
+ .replace("default-src 'none';", "default-src 'none'; connect-src 'self';")
16
+ .replace('</body>', `${controller}</body>`);
17
+ }
18
+
19
+ function escapeHtml(value) {
20
+ return String(value).replaceAll('&', '&amp;').replaceAll('<', '&lt;')
21
+ .replaceAll('>', '&gt;').replaceAll('"', '&quot;').replaceAll("'", '&#39;');
22
+ }
23
+
24
+ function dashboardHtml(projects) {
25
+ const rows = projects.map(({ projectId, displayName }) => {
26
+ const href = projectPath(projectId);
27
+ return `<li><a href="${escapeHtml(href)}"><strong>${escapeHtml(displayName)}</strong><code>${escapeHtml(projectId)}</code></a></li>`;
28
+ }).join('');
29
+ const content = rows.length === 0 ? '<p>アクティブなプロジェクトはありません。</p>'
30
+ : `<ul>${rows}</ul>`;
31
+ return `<!doctype html><html lang="ja"><head><meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Lattice — アクティブプロジェクト</title><style>body{max-width:800px;margin:48px auto;padding:0 20px;color:#262626;background:#fafaf8;font:16px system-ui}h1{font-size:1.6rem}ul{list-style:none;padding:0}li{margin:10px 0}a{display:flex;justify-content:space-between;gap:20px;padding:16px;border:1px solid #d9d8d4;border-radius:8px;color:inherit;background:white;text-decoration:none}a:hover{border-color:#4b78d0}code{color:#666}</style></head><body><main><h1>Lattice アクティブプロジェクト</h1>${content}</main></body></html>`;
32
+ }
33
+
34
+ function validateProject(project) {
35
+ if (project === null || typeof project !== 'object' || Array.isArray(project)
36
+ || typeof project.projectId !== 'string' || !PROJECT_ID.test(project.projectId)
37
+ || typeof project.displayName !== 'string' || project.displayName.length === 0
38
+ || project.displayName !== project.displayName.trim()
39
+ || typeof project.render !== 'function' || typeof project.readHead !== 'function') {
40
+ throw new TypeError('project descriptor is invalid');
41
+ }
42
+ return Object.freeze({
43
+ projectId: project.projectId,
44
+ displayName: project.displayName,
45
+ render: project.render,
46
+ readHead: project.readHead,
47
+ });
48
+ }
49
+
50
+ export function createTodoGanttProjectRegistry(initialProjects = []) {
51
+ if (!Array.isArray(initialProjects)) throw new TypeError('initialProjects must be an array');
52
+ const entries = new Map();
53
+ const listeners = new Set();
54
+ const notify = () => {
55
+ for (const listener of listeners) listener();
56
+ };
57
+ const register = (project) => {
58
+ const validated = validateProject(project);
59
+ if (entries.has(validated.projectId)) {
60
+ const error = new Error(`project already registered: ${validated.projectId}`);
61
+ error.code = 'PROJECT_ALREADY_REGISTERED';
62
+ throw error;
63
+ }
64
+ entries.set(validated.projectId, validated);
65
+ notify();
66
+ return validated;
67
+ };
68
+ for (const project of initialProjects) register(project);
69
+ return Object.freeze({
70
+ register,
71
+ unregister(projectId) {
72
+ const deleted = entries.delete(projectId);
73
+ if (deleted) notify();
74
+ return deleted;
75
+ },
76
+ get(projectId) { return entries.get(projectId) ?? null; },
77
+ list() {
78
+ return [...entries.values()].sort((left, right) => left.displayName.localeCompare(right.displayName, 'ja')
79
+ || left.projectId.localeCompare(right.projectId, 'en'));
80
+ },
81
+ subscribe(listener) {
82
+ if (typeof listener !== 'function') throw new TypeError('registry listener required');
83
+ listeners.add(listener);
84
+ return () => listeners.delete(listener);
85
+ },
86
+ });
87
+ }
88
+
89
+ function sendEvent(response, name, id, value) {
90
+ response.write(`id: ${id}\nevent: ${name}\ndata: ${JSON.stringify(value)}\n\n`);
91
+ }
92
+
93
+ function sendHttpError(response, status, code, path) {
94
+ response.writeHead(status, { 'content-type': 'application/json; charset=utf-8',
95
+ 'cache-control': 'no-store', 'x-content-type-options': 'nosniff' });
96
+ response.end(`${JSON.stringify({ schema: HTTP_ERROR_SCHEMA, code, path })}\n`);
97
+ }
98
+
99
+ function finishRequestFailure(response, code, path) {
100
+ try {
101
+ if (!response.headersSent) sendHttpError(response, 500, code, path);
102
+ else if (!response.writableEnded) response.end();
103
+ } catch { response.destroy(); }
104
+ }
105
+
106
+ export async function startTodoGanttDashboardServer({ registry, port = 0, redirectSingleProject = false }) {
107
+ if (!Number.isSafeInteger(port) || port < 0 || port > 65_535) throw new TypeError('port must be 0..65535');
108
+ if (registry === null || typeof registry !== 'object' || typeof registry.get !== 'function'
109
+ || typeof registry.list !== 'function') throw new TypeError('registry required');
110
+ const clientsByProject = new Map();
111
+ const lastHeads = new Map();
112
+ let eventId = 0;
113
+ let checking = false;
114
+ let closed = false;
115
+ const reconcileClients = () => {
116
+ const active = new Set(registry.list().map(({ projectId }) => projectId));
117
+ for (const [projectId, clients] of clientsByProject) {
118
+ if (active.has(projectId)) continue;
119
+ for (const client of clients) client.end();
120
+ clientsByProject.delete(projectId);
121
+ lastHeads.delete(projectId);
122
+ }
123
+ };
124
+ const unsubscribe = typeof registry.subscribe === 'function'
125
+ ? registry.subscribe(reconcileClients) : () => {};
126
+ const handleRequest = async (request, response) => {
127
+ let requestPath = request.url ?? '/';
128
+ try {
129
+ const url = new URL(requestPath, `http://${LOOPBACK}`);
130
+ requestPath = url.pathname;
131
+ if (request.method !== 'GET') {
132
+ response.setHeader('allow', 'GET');
133
+ sendHttpError(response, 405, 'METHOD_NOT_ALLOWED', requestPath);
134
+ return;
135
+ }
136
+ if (url.pathname === '/__lattice/health') {
137
+ response.writeHead(200, { 'content-type': 'application/json; charset=utf-8',
138
+ 'cache-control': 'no-store', 'x-content-type-options': 'nosniff' });
139
+ response.end(`${JSON.stringify({ schema: 'lattice.todo_dashboard_health.v1', pid: process.pid,
140
+ port: actualPort, project_ids: registry.list().map(({ projectId }) => projectId) })}\n`);
141
+ return;
142
+ }
143
+ if (url.pathname === '/' || url.pathname === '/projects/') {
144
+ const projects = registry.list();
145
+ if (url.pathname === '/' && redirectSingleProject && projects.length === 1) {
146
+ response.writeHead(302, { location: projectPath(projects[0].projectId), 'cache-control': 'no-store' });
147
+ response.end();
148
+ return;
149
+ }
150
+ response.writeHead(200, { 'content-type': 'text/html; charset=utf-8',
151
+ 'cache-control': 'no-store', 'x-content-type-options': 'nosniff' });
152
+ response.end(dashboardHtml(projects));
153
+ return;
154
+ }
155
+ const match = /^\/projects\/([^/]+)\/(events)?$/u.exec(url.pathname);
156
+ if (match === null) {
157
+ sendHttpError(response, 404, 'ROUTE_NOT_FOUND', url.pathname);
158
+ return;
159
+ }
160
+ let requestedId;
161
+ try { requestedId = decodeURIComponent(match[1]); } catch {
162
+ sendHttpError(response, 400, 'PROJECT_ID_INVALID', url.pathname);
163
+ return;
164
+ }
165
+ const project = registry.get(requestedId);
166
+ if (project === null) {
167
+ sendHttpError(response, 404, 'PROJECT_NOT_FOUND', url.pathname);
168
+ return;
169
+ }
170
+ if (match[2] === 'events') {
171
+ response.writeHead(200, { 'content-type': 'text/event-stream; charset=utf-8',
172
+ 'cache-control': 'no-store', connection: 'keep-alive', 'x-content-type-options': 'nosniff' });
173
+ if (!clientsByProject.has(project.projectId)) clientsByProject.set(project.projectId, new Set());
174
+ clientsByProject.get(project.projectId).add(response);
175
+ request.on('close', () => clientsByProject.get(project.projectId)?.delete(response));
176
+ try {
177
+ const head = await project.readHead();
178
+ lastHeads.set(project.projectId, head);
179
+ sendEvent(response, 'state', ++eventId, { head_digest: head });
180
+ } catch (error) {
181
+ sendEvent(response, 'lattice-error', ++eventId, { code: error?.code ?? 'STORE_READ_FAILED' });
182
+ clientsByProject.get(project.projectId)?.delete(response);
183
+ response.end();
184
+ }
185
+ return;
186
+ }
187
+ const rendered = await project.render({
188
+ projectId: project.projectId,
189
+ displayName: project.displayName,
190
+ });
191
+ lastHeads.set(project.projectId, rendered.head_digest);
192
+ const html = liveHtml(rendered.html, rendered.head_digest, `${projectPath(project.projectId)}events`);
193
+ response.writeHead(200, { 'content-type': 'text/html; charset=utf-8',
194
+ 'cache-control': 'no-store', 'x-content-type-options': 'nosniff' });
195
+ response.end(html);
196
+ } catch (error) {
197
+ finishRequestFailure(response, error?.code ?? 'GANTT_RENDER_FAILED', requestPath);
198
+ }
199
+ };
200
+ const server = createServer((request, response) => {
201
+ handleRequest(request, response).catch((error) => {
202
+ finishRequestFailure(response, error?.code ?? 'GANTT_REQUEST_FAILED', request.url ?? '/');
203
+ });
204
+ });
205
+ await new Promise((resolve, reject) => {
206
+ server.once('error', reject);
207
+ server.listen({ host: LOOPBACK, port }, resolve);
208
+ });
209
+ const timer = setInterval(async () => {
210
+ if (checking) return;
211
+ checking = true;
212
+ try {
213
+ reconcileClients();
214
+ for (const project of registry.list()) {
215
+ const clients = clientsByProject.get(project.projectId);
216
+ if (clients === undefined || clients.size === 0) continue;
217
+ try {
218
+ const head = await project.readHead();
219
+ if (lastHeads.get(project.projectId) !== head) {
220
+ lastHeads.set(project.projectId, head);
221
+ for (const client of clients) sendEvent(client, 'state', ++eventId, { head_digest: head });
222
+ }
223
+ } catch (error) {
224
+ for (const client of clients) sendEvent(client, 'lattice-error', ++eventId,
225
+ { code: error?.code ?? 'STORE_READ_FAILED' });
226
+ }
227
+ }
228
+ } finally { checking = false; }
229
+ }, POLL_MS);
230
+ timer.unref();
231
+ const address = server.address();
232
+ const actualPort = typeof address === 'object' && address !== null ? address.port : port;
233
+ const close = async () => {
234
+ if (closed) return;
235
+ closed = true;
236
+ clearInterval(timer);
237
+ unsubscribe();
238
+ for (const clients of clientsByProject.values()) for (const client of clients) client.end();
239
+ clientsByProject.clear();
240
+ await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
241
+ };
242
+ return Object.freeze({
243
+ host: LOOPBACK,
244
+ port: actualPort,
245
+ url: `http://${LOOPBACK}:${actualPort}/`,
246
+ projectsUrl: `http://${LOOPBACK}:${actualPort}/projects/`,
247
+ registry,
248
+ close,
249
+ });
250
+ }
251
+
252
+ export async function startTodoGanttLiveServer({ projectId, displayName = projectId, port = 0, render, readHead }) {
253
+ const registry = createTodoGanttProjectRegistry([{ projectId, displayName, render, readHead }]);
254
+ const dashboard = await startTodoGanttDashboardServer({ registry, port, redirectSingleProject: true });
255
+ const path = projectPath(projectId);
256
+ return Object.freeze({
257
+ projectId,
258
+ displayName,
259
+ host: dashboard.host,
260
+ port: dashboard.port,
261
+ projectPath: path,
262
+ eventsPath: `${path}events`,
263
+ url: `http://${dashboard.host}:${dashboard.port}${path}`,
264
+ eventsUrl: `http://${dashboard.host}:${dashboard.port}${path}events`,
265
+ projectsUrl: dashboard.projectsUrl,
266
+ registry,
267
+ close: dashboard.close,
268
+ });
269
+ }
@@ -0,0 +1,217 @@
1
+ import {
2
+ lstat, readFile, realpath,
3
+ } from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import { parseTree } from 'jsonc-parser';
6
+
7
+ import {
8
+ digestTodoArtifact,
9
+ exactRecord,
10
+ isTodoIdentifier,
11
+ } from './todo-contracts.mjs';
12
+ import { TodoStoreError } from './todo-store.mjs';
13
+
14
+ export const TODO_GANTT_PRESENTATION_REF = '.lattice/todo/gantt-presentation.json';
15
+ export const TODO_GANTT_PRESENTATION_SCHEMA = 'lattice.todo_gantt_presentation.v1';
16
+ export const TODO_GANTT_PRESENTATION_MODEL_SCHEMA = 'lattice.todo_gantt_presentation_model.v1';
17
+
18
+ const MAX_PRESENTATION_BYTES = 64 * 1_024;
19
+ const MAX_PLANS = 256;
20
+ const MAX_LANES_PER_PLAN = 256;
21
+ const MAX_NAME_BYTES = 256;
22
+ const MAX_DESCRIPTION_BYTES = 2_048;
23
+ const MAX_TASK_NUMBER_DIGITS = 12;
24
+ const CONTROL = /[\u0000-\u001f\u007f]/u;
25
+ const NUMERIC_SUFFIX = /(\d+)$/u;
26
+
27
+ function fail(reason, detail = {}) {
28
+ throw new TodoStoreError('PRESENTATION_INVALID', reason, undefined, detail);
29
+ }
30
+
31
+ function compareText(left, right) {
32
+ return left < right ? -1 : left > right ? 1 : 0;
33
+ }
34
+
35
+ function within(root, candidate) {
36
+ return candidate === root || candidate.startsWith(`${root}${path.sep}`);
37
+ }
38
+
39
+ function boundedText(value, maximumBytes) {
40
+ return typeof value === 'string' && value.length > 0 && value === value.trim()
41
+ && !CONTROL.test(value) && Buffer.byteLength(value, 'utf8') <= maximumBytes;
42
+ }
43
+
44
+ function hasDuplicateJsonKey(node) {
45
+ if (node?.type === 'object') {
46
+ const keys = new Set();
47
+ for (const property of node.children ?? []) {
48
+ const [key, value] = property.children ?? [];
49
+ if (keys.has(key?.value) || hasDuplicateJsonKey(value)) return true;
50
+ keys.add(key?.value);
51
+ }
52
+ } else if (node?.type === 'array') {
53
+ return (node.children ?? []).some(hasDuplicateJsonKey);
54
+ }
55
+ return false;
56
+ }
57
+
58
+ function validateDocument(value) {
59
+ if (!exactRecord(value, ['schema', 'project_id', 'plans'])
60
+ || value.schema !== TODO_GANTT_PRESENTATION_SCHEMA
61
+ || !isTodoIdentifier(value.project_id)
62
+ || !Array.isArray(value.plans) || value.plans.length > MAX_PLANS) {
63
+ fail('presentation_schema_invalid');
64
+ }
65
+ const planKeys = new Set();
66
+ for (const plan of value.plans) {
67
+ if (!exactRecord(plan, ['plan_key', 'lanes']) || !isTodoIdentifier(plan.plan_key)
68
+ || !Array.isArray(plan.lanes) || plan.lanes.length > MAX_LANES_PER_PLAN) {
69
+ fail('presentation_schema_invalid');
70
+ }
71
+ if (planKeys.has(plan.plan_key)) fail('presentation_plan_duplicate', { plan_key: plan.plan_key });
72
+ planKeys.add(plan.plan_key);
73
+ const lanes = new Set();
74
+ for (const lane of plan.lanes) {
75
+ if (!exactRecord(lane, ['lane', 'name', 'description']) || !isTodoIdentifier(lane.lane)
76
+ || !boundedText(lane.name, MAX_NAME_BYTES)
77
+ || !boundedText(lane.description, MAX_DESCRIPTION_BYTES)) {
78
+ fail('presentation_schema_invalid');
79
+ }
80
+ if (lanes.has(lane.lane)) {
81
+ fail('presentation_lane_duplicate', { plan_key: plan.plan_key, lane: lane.lane });
82
+ }
83
+ lanes.add(lane.lane);
84
+ }
85
+ }
86
+ return value;
87
+ }
88
+
89
+ function normalizedTaskNumber(taskId) {
90
+ const suffix = NUMERIC_SUFFIX.exec(taskId)?.[1] ?? null;
91
+ if (suffix === null || suffix.length > MAX_TASK_NUMBER_DIGITS) return null;
92
+ return { display_number: suffix, normalized_number: suffix.replace(/^0+(?=\d)/u, '') };
93
+ }
94
+
95
+ function taskNumberBindings(readModel) {
96
+ const candidatesByPlan = new Map();
97
+ for (const member of readModel.members) {
98
+ const plan = member?.plan;
99
+ for (const task of plan?.tasks ?? []) {
100
+ const candidate = normalizedTaskNumber(task.task_id);
101
+ if (candidate === null) continue;
102
+ const planKey = JSON.stringify([plan.project_id, plan.plan_key]);
103
+ if (!candidatesByPlan.has(planKey)) candidatesByPlan.set(planKey, new Map());
104
+ const byNumber = candidatesByPlan.get(planKey);
105
+ if (!byNumber.has(candidate.normalized_number)) byNumber.set(candidate.normalized_number, []);
106
+ byNumber.get(candidate.normalized_number).push({
107
+ project_id: plan.project_id,
108
+ plan_key: plan.plan_key,
109
+ task_id: task.task_id,
110
+ ...candidate,
111
+ });
112
+ }
113
+ }
114
+ const bindings = [];
115
+ for (const byNumber of candidatesByPlan.values()) {
116
+ for (const candidates of byNumber.values()) if (candidates.length === 1) bindings.push(candidates[0]);
117
+ }
118
+ const globalCounts = new Map();
119
+ for (const binding of bindings) {
120
+ globalCounts.set(binding.normalized_number, (globalCounts.get(binding.normalized_number) ?? 0) + 1);
121
+ }
122
+ return bindings.map((binding) => ({
123
+ ...binding,
124
+ globally_unique: globalCounts.get(binding.normalized_number) === 1,
125
+ })).sort((left, right) => compareText(left.project_id, right.project_id)
126
+ || compareText(left.plan_key, right.plan_key)
127
+ || compareText(left.task_id, right.task_id));
128
+ }
129
+
130
+ export function projectTodoGanttPresentation(readModel, document) {
131
+ if (!exactRecord(readModel, ['schema', 'project_id', 'manifest', 'members', 'snapshot_stale'])
132
+ && !(readModel?.schema === 'lattice.todo_store_read.v1'
133
+ && isTodoIdentifier(readModel.project_id) && Array.isArray(readModel.members))) {
134
+ throw new TypeError('readModel must be lattice.todo_store_read.v1');
135
+ }
136
+ if (document !== null) validateDocument(document);
137
+ if (document !== null && document.project_id !== readModel.project_id) {
138
+ fail('presentation_project_mismatch', {
139
+ expected_project_id: readModel.project_id,
140
+ actual_project_id: document.project_id,
141
+ });
142
+ }
143
+
144
+ const planLanes = new Map();
145
+ for (const member of readModel.members) {
146
+ const plan = member?.plan;
147
+ if (!isTodoIdentifier(plan?.project_id) || !isTodoIdentifier(plan?.plan_key)
148
+ || !Array.isArray(plan?.tasks)) throw new TypeError('readModel member plan is invalid');
149
+ planLanes.set(plan.plan_key, new Set(plan.tasks.map(({ lane }) => lane)));
150
+ }
151
+
152
+ const lanes = [];
153
+ for (const plan of document?.plans ?? []) {
154
+ const knownLanes = planLanes.get(plan.plan_key);
155
+ if (knownLanes === undefined) fail('presentation_plan_unknown', { plan_key: plan.plan_key });
156
+ for (const lane of plan.lanes) {
157
+ if (!knownLanes.has(lane.lane)) {
158
+ fail('presentation_lane_unknown', { plan_key: plan.plan_key, lane: lane.lane });
159
+ }
160
+ lanes.push({ plan_key: plan.plan_key, ...lane });
161
+ }
162
+ }
163
+ lanes.sort((left, right) => compareText(left.plan_key, right.plan_key)
164
+ || compareText(left.lane, right.lane));
165
+
166
+ const model = {
167
+ schema: TODO_GANTT_PRESENTATION_MODEL_SCHEMA,
168
+ project_id: readModel.project_id,
169
+ configured: document !== null,
170
+ lanes,
171
+ task_numbers: taskNumberBindings(readModel),
172
+ };
173
+ return { ...model, presentation_digest: digestTodoArtifact(model) };
174
+ }
175
+
176
+ async function readDocument(repoRoot) {
177
+ const canonicalRoot = await realpath(repoRoot);
178
+ const absolute = path.resolve(canonicalRoot, TODO_GANTT_PRESENTATION_REF);
179
+ if (!within(canonicalRoot, absolute) || absolute === canonicalRoot) fail('presentation_path_outside_repo');
180
+ let stats;
181
+ try {
182
+ stats = await lstat(absolute);
183
+ } catch (error) {
184
+ if (error?.code === 'ENOENT') return null;
185
+ fail('presentation_unreadable');
186
+ }
187
+ if (stats.isSymbolicLink() || !stats.isFile() || stats.size > MAX_PRESENTATION_BYTES) {
188
+ fail(stats.size > MAX_PRESENTATION_BYTES
189
+ ? 'presentation_size_limit_exceeded' : 'presentation_path_unsafe');
190
+ }
191
+ const resolved = await realpath(absolute);
192
+ if (resolved !== absolute || !within(canonicalRoot, resolved)) fail('presentation_path_unsafe');
193
+ const bytes = await readFile(resolved);
194
+ if (bytes.length > MAX_PRESENTATION_BYTES) fail('presentation_size_limit_exceeded');
195
+ let text;
196
+ try {
197
+ text = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
198
+ } catch {
199
+ fail('presentation_invalid_utf8');
200
+ }
201
+ const errors = [];
202
+ const tree = parseTree(text, errors, { allowTrailingComma: false, disallowComments: true });
203
+ if (errors.length > 0 || tree === undefined) fail('presentation_json_parse_failed');
204
+ if (hasDuplicateJsonKey(tree)) fail('presentation_duplicate_key');
205
+ let document;
206
+ try {
207
+ document = JSON.parse(text);
208
+ } catch {
209
+ fail('presentation_json_parse_failed');
210
+ }
211
+ return validateDocument(document);
212
+ }
213
+
214
+ export async function loadTodoGanttPresentation({ repoRoot, readModel }) {
215
+ if (typeof repoRoot !== 'string' || repoRoot.length === 0) throw new TypeError('repoRoot is required');
216
+ return projectTodoGanttPresentation(readModel, await readDocument(repoRoot));
217
+ }