@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,603 @@
1
+ const TASK_LIMIT = 2_000;
2
+ const EDGE_LIMIT = 8_000;
3
+ const SWEEP_ROUNDS = 4;
4
+
5
+ const GEOMETRY = Object.freeze({
6
+ left: 16,
7
+ top: 16,
8
+ wave_gap: 104,
9
+ lane_gap: 296,
10
+ node_width: 272,
11
+ node_height: 68,
12
+ route_inset: 8,
13
+ route_spacing: 12,
14
+ });
15
+
16
+ function segmentKind(left, right) {
17
+ if (left[1] === right[1] && left[0] !== right[0]) return 'horizontal';
18
+ if (left[0] === right[0] && left[1] !== right[1]) return 'vertical';
19
+ return 'point';
20
+ }
21
+
22
+ function strictlyBetween(value, left, right) {
23
+ return value > Math.min(left, right) && value < Math.max(left, right);
24
+ }
25
+
26
+ function addCrossingBridges(projectedEdges, logicalContacts = new Set()) {
27
+ const horizontalByY = new Map();
28
+ const verticals = [];
29
+ for (const edge of projectedEdges) {
30
+ for (let segment = 0; segment < edge.route.length - 1; segment += 1) {
31
+ const start = edge.route[segment];
32
+ const end = edge.route[segment + 1];
33
+ const kind = segmentKind(start, end);
34
+ if (kind === 'horizontal') {
35
+ if (!horizontalByY.has(start[1])) horizontalByY.set(start[1], []);
36
+ horizontalByY.get(start[1]).push({ edge, segment, start, end });
37
+ } else if (kind === 'vertical') verticals.push({ edge, start, end });
38
+ }
39
+ }
40
+ const yValues = [...horizontalByY.keys()].sort((left, right) => left - right);
41
+ const firstGreater = (value) => {
42
+ let low = 0; let high = yValues.length;
43
+ while (low < high) {
44
+ const middle = Math.floor((low + high) / 2);
45
+ if (yValues[middle] <= value) low = middle + 1; else high = middle;
46
+ }
47
+ return low;
48
+ };
49
+ const firstAtLeast = (value) => {
50
+ let low = 0; let high = yValues.length;
51
+ while (low < high) {
52
+ const middle = Math.floor((low + high) / 2);
53
+ if (yValues[middle] < value) low = middle + 1; else high = middle;
54
+ }
55
+ return low;
56
+ };
57
+ for (const vertical of verticals) {
58
+ const minimumY = Math.min(vertical.start[1], vertical.end[1]);
59
+ const maximumY = Math.max(vertical.start[1], vertical.end[1]);
60
+ for (let yIndex = firstGreater(minimumY); yIndex < firstAtLeast(maximumY); yIndex += 1) {
61
+ const y = yValues[yIndex];
62
+ for (const horizontal of horizontalByY.get(y)) {
63
+ if (horizontal.edge === vertical.edge
64
+ || !strictlyBetween(vertical.start[0], horizontal.start[0], horizontal.end[0])) continue;
65
+ if (logicalContacts.has(`${vertical.start[0]},${y}`)) continue;
66
+ horizontal.edge.bridges.push({
67
+ segment_index: horizontal.segment, x: vertical.start[0], y,
68
+ });
69
+ }
70
+ }
71
+ }
72
+ for (const edge of projectedEdges) {
73
+ edge.bridges.sort((left, right) => left.segment_index - right.segment_index
74
+ || (edge.route[left.segment_index][0] <= edge.route[left.segment_index + 1][0]
75
+ ? left.x - right.x : right.x - left.x));
76
+ }
77
+ }
78
+
79
+ export class TodoGanttLayoutError extends Error {
80
+ constructor(code, message, detail = null) {
81
+ super(message);
82
+ this.name = 'TodoGanttLayoutError';
83
+ this.code = code;
84
+ this.detail = detail;
85
+ }
86
+ }
87
+
88
+ function fail(code, message, detail) {
89
+ throw new TodoGanttLayoutError(code, message, detail);
90
+ }
91
+
92
+ function plain(value) {
93
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
94
+ && Object.getPrototypeOf(value) === Object.prototype;
95
+ }
96
+
97
+ function compareText(left, right) {
98
+ return left < right ? -1 : left > right ? 1 : 0;
99
+ }
100
+
101
+ function refOf(value, context) {
102
+ if (!plain(value) || typeof value.project_id !== 'string'
103
+ || typeof value.plan_key !== 'string' || typeof value.task_id !== 'string') {
104
+ fail('TODO_LAYOUT_INVALID_INPUT', `${context} must be a task ref`);
105
+ }
106
+ return { project_id: value.project_id, plan_key: value.plan_key, task_id: value.task_id };
107
+ }
108
+
109
+ function refKey(ref) {
110
+ return JSON.stringify([ref.project_id, ref.plan_key, ref.task_id]);
111
+ }
112
+
113
+ function compareRefs(left, right) {
114
+ return compareText(left.project_id, right.project_id)
115
+ || compareText(left.plan_key, right.plan_key)
116
+ || compareText(left.task_id, right.task_id);
117
+ }
118
+
119
+ function groupKey(planKey, lane) {
120
+ return JSON.stringify([planKey, lane]);
121
+ }
122
+
123
+ function normalizeInput(readModel, chainProjection) {
124
+ if (!plain(readModel) || readModel.schema !== 'lattice.todo_store_read.v1'
125
+ || !Array.isArray(readModel.members)) {
126
+ fail('TODO_LAYOUT_INVALID_INPUT', 'read model must be lattice.todo_store_read.v1');
127
+ }
128
+ if (!plain(chainProjection) || chainProjection.schema !== 'lattice.todo_chain.v1'
129
+ || !Array.isArray(chainProjection.longest_chain_node_refs)
130
+ || !Array.isArray(chainProjection.longest_chain_edges)) {
131
+ fail('TODO_LAYOUT_INVALID_INPUT', 'chain projection must be lattice.todo_chain.v1');
132
+ }
133
+
134
+ const nodesByKey = new Map();
135
+ const members = [...readModel.members].sort((left, right) => {
136
+ const leftPlan = left?.plan;
137
+ const rightPlan = right?.plan;
138
+ return compareText(leftPlan?.project_id ?? '', rightPlan?.project_id ?? '')
139
+ || compareText(leftPlan?.plan_key ?? '', rightPlan?.plan_key ?? '');
140
+ });
141
+ for (let memberIndex = 0; memberIndex < members.length; memberIndex += 1) {
142
+ const member = members[memberIndex];
143
+ if (!plain(member) || !plain(member.plan) || !Array.isArray(member.plan.tasks)
144
+ || !Array.isArray(member.tasks) || !Array.isArray(member.plan.hard_dependencies)
145
+ || !Array.isArray(member.plan.joins)) {
146
+ fail('TODO_LAYOUT_INVALID_INPUT', `members[${memberIndex}] has an invalid read-model shape`);
147
+ }
148
+ const { plan } = member;
149
+ const statusByTask = new Map(member.tasks.map((task) => [task.task_id, task]));
150
+ const statusByPhase = new Map((member.snapshot?.phases ?? [])
151
+ .map((phase) => [phase.phase_id, phase.status]));
152
+ for (const task of plan.tasks) {
153
+ if (!plain(task) || typeof task.task_id !== 'string' || typeof task.lane !== 'string') {
154
+ fail('TODO_LAYOUT_INVALID_INPUT', 'plan task has an invalid shape');
155
+ }
156
+ const state = statusByTask.get(task.task_id);
157
+ if (!plain(state) || !['pending', 'in-progress', 'blocked', 'done'].includes(state.status)) {
158
+ fail('TODO_LAYOUT_INVALID_INPUT', `missing task state for ${task.task_id}`);
159
+ }
160
+ const ref = { project_id: plan.project_id, plan_key: plan.plan_key, task_id: task.task_id };
161
+ const key = refKey(ref);
162
+ if (nodesByKey.has(key)) fail('TODO_LAYOUT_INVALID_INPUT', `duplicate task: ${key}`);
163
+ nodesByKey.set(key, {
164
+ key,
165
+ ref,
166
+ title: typeof task.title === 'string' ? task.title : task.task_id,
167
+ lane: task.lane,
168
+ status: state.status,
169
+ plan_schema: plan.schema ?? null,
170
+ phase_id: task.phase_id ?? null,
171
+ phase_status: task.phase_id === undefined ? null : statusByPhase.get(task.phase_id) ?? null,
172
+ phase_ready: plan.schema !== 'lattice.todo_plan.v4'
173
+ || statusByPhase.get(task.phase_id) === 'active',
174
+ });
175
+ }
176
+ }
177
+ if (nodesByKey.size > TASK_LIMIT) {
178
+ fail('TODO_SCALE_EXCEEDED', 'todo gantt task limit exceeded', {
179
+ task_count: nodesByKey.size, task_limit: TASK_LIMIT, edge_count: null, edge_limit: EDGE_LIMIT,
180
+ });
181
+ }
182
+
183
+ const edgeMap = new Map();
184
+ const addEdge = (fromValue, toValue, kind, joinIdentity = null) => {
185
+ const fromRef = refOf(fromValue, 'edge.from');
186
+ const toRef = refOf(toValue, 'edge.to');
187
+ const from = refKey(fromRef);
188
+ const to = refKey(toRef);
189
+ if (!nodesByKey.has(from) || !nodesByKey.has(to) || from === to) {
190
+ fail('TODO_LAYOUT_INVALID_INPUT', 'edge must connect two distinct read-model tasks');
191
+ }
192
+ const key = JSON.stringify([from, to]);
193
+ let edge = edgeMap.get(key);
194
+ if (edge === undefined) {
195
+ edge = { key, from, to, kinds: new Set(), joinIdentities: new Map() };
196
+ edgeMap.set(key, edge);
197
+ }
198
+ edge.kinds.add(kind);
199
+ if (joinIdentity !== null) {
200
+ const identityKey = JSON.stringify([
201
+ joinIdentity.project_id, joinIdentity.plan_key, joinIdentity.join_id,
202
+ ]);
203
+ edge.joinIdentities.set(identityKey, joinIdentity);
204
+ }
205
+ };
206
+ for (const member of members) {
207
+ for (const edge of member.plan.hard_dependencies) addEdge(edge.from, edge.to, 'hard');
208
+ for (const join of member.plan.joins) {
209
+ if (!plain(join) || !Array.isArray(join.after) || typeof join.id !== 'string') {
210
+ fail('TODO_LAYOUT_INVALID_INPUT', 'join has an invalid shape');
211
+ }
212
+ const identity = { project_id: member.plan.project_id, plan_key: member.plan.plan_key,
213
+ join_id: join.id };
214
+ for (const after of join.after) addEdge(after, join.before, 'join', identity);
215
+ }
216
+ }
217
+ if (edgeMap.size > EDGE_LIMIT) {
218
+ fail('TODO_SCALE_EXCEEDED', 'todo gantt edge limit exceeded', {
219
+ task_count: nodesByKey.size, task_limit: TASK_LIMIT,
220
+ edge_count: edgeMap.size, edge_limit: EDGE_LIMIT,
221
+ });
222
+ }
223
+
224
+ const nodes = [...nodesByKey.values()].sort((left, right) => compareRefs(left.ref, right.ref));
225
+ const edges = [...edgeMap.values()].sort((left, right) => compareText(left.key, right.key));
226
+ return { nodes, nodesByKey, edges };
227
+ }
228
+
229
+ function readyTaskKeys(readModel, nodes, nodesByKey, incoming) {
230
+ const phaseStatuses = new Map();
231
+ const phaseAcceptIncoming = new Map(nodes.map(({ key }) => [key, new Set()]));
232
+ for (const member of readModel.members) {
233
+ for (const phase of member.snapshot?.phases ?? []) {
234
+ phaseStatuses.set(JSON.stringify([
235
+ member.plan.project_id, member.plan.plan_key, phase.phase_id,
236
+ ]), phase.status);
237
+ }
238
+ if (member.plan.schema === 'lattice.todo_plan.v5') {
239
+ for (const edge of member.plan.phase_accept_dependencies ?? []) {
240
+ const target = refKey(refOf(edge.to, 'phase_accept_dependencies.to'));
241
+ if (!nodesByKey.has(target)) {
242
+ fail('TODO_LAYOUT_INVALID_INPUT', 'phase accept dependency targets an absent task');
243
+ }
244
+ phaseAcceptIncoming.get(target).add(JSON.stringify([
245
+ edge.from.project_id, edge.from.plan_key, edge.from.phase_id,
246
+ ]));
247
+ }
248
+ }
249
+ }
250
+ return new Set(nodes.filter((node) => node.status === 'pending' && node.phase_ready
251
+ && incoming.get(node.key).every((predecessorKey) => {
252
+ const predecessor = nodesByKey.get(predecessorKey);
253
+ return predecessor.status === 'done'
254
+ && (predecessor.plan_schema !== 'lattice.todo_plan.v4'
255
+ || predecessor.phase_id === null
256
+ || (predecessor.ref.plan_key === node.ref.plan_key
257
+ && predecessor.phase_id === node.phase_id)
258
+ || predecessor.phase_status === 'accepted');
259
+ }) && [...phaseAcceptIncoming.get(node.key)]
260
+ .every((phaseKey) => phaseStatuses.get(phaseKey) === 'accepted'))
261
+ .map(({ key }) => key));
262
+ }
263
+
264
+ function assignWaves(nodes, nodesByKey, edges) {
265
+ const incoming = new Map(nodes.map(({ key }) => [key, []]));
266
+ const outgoing = new Map(nodes.map(({ key }) => [key, []]));
267
+ const indegree = new Map(nodes.map(({ key }) => [key, 0]));
268
+ for (const edge of edges) {
269
+ incoming.get(edge.to).push(edge.from);
270
+ outgoing.get(edge.from).push(edge.to);
271
+ indegree.set(edge.to, indegree.get(edge.to) + 1);
272
+ }
273
+ for (const values of incoming.values()) values.sort(compareText);
274
+ for (const values of outgoing.values()) values.sort(compareText);
275
+ let ready = nodes.filter(({ key }) => indegree.get(key) === 0).map(({ key }) => key);
276
+ ready.sort((left, right) => compareRefs(nodesByKey.get(left).ref, nodesByKey.get(right).ref));
277
+ const wave = new Map(nodes.map(({ key }) => [key, 0]));
278
+ let visited = 0;
279
+ while (ready.length > 0) {
280
+ const current = ready.shift();
281
+ visited += 1;
282
+ for (const next of outgoing.get(current)) {
283
+ wave.set(next, Math.max(wave.get(next), wave.get(current) + 1));
284
+ indegree.set(next, indegree.get(next) - 1);
285
+ if (indegree.get(next) === 0) {
286
+ ready.push(next);
287
+ ready.sort((left, right) => compareRefs(nodesByKey.get(left).ref, nodesByKey.get(right).ref));
288
+ }
289
+ }
290
+ }
291
+ if (visited !== nodes.length) fail('TODO_LAYOUT_CYCLE', 'todo layout input contains a cycle');
292
+ return { incoming, outgoing, wave };
293
+ }
294
+
295
+ function median(values) {
296
+ if (values.length === 0) return null;
297
+ values.sort((left, right) => left - right);
298
+ const middle = Math.floor(values.length / 2);
299
+ return values.length % 2 === 1 ? values[middle] : (values[middle - 1] + values[middle]) / 2;
300
+ }
301
+
302
+ function orderLayers(nodes, wave, incoming, outgoing) {
303
+ const maximumWave = nodes.reduce((maximum, node) => Math.max(maximum, wave.get(node.key)), 0);
304
+ const layers = Array.from({ length: maximumWave + 1 }, () => []);
305
+ for (const node of nodes) layers[wave.get(node.key)].push(node.key);
306
+ const nodeByKey = new Map(nodes.map((node) => [node.key, node]));
307
+ const baseCompare = (leftKey, rightKey) => {
308
+ const left = nodeByKey.get(leftKey);
309
+ const right = nodeByKey.get(rightKey);
310
+ return compareText(left.lane, right.lane)
311
+ || compareText(left.ref.plan_key, right.ref.plan_key)
312
+ || compareRefs(left.ref, right.ref);
313
+ };
314
+ for (const layer of layers) layer.sort(baseCompare);
315
+
316
+ const reorder = (layerIndex, neighborMap) => {
317
+ const layer = layers[layerIndex];
318
+ const previousPosition = new Map(layer.map((key, index) => [key, index]));
319
+ const allPositions = new Map();
320
+ for (const otherLayer of layers) {
321
+ for (let index = 0; index < otherLayer.length; index += 1) allPositions.set(otherLayer[index], index);
322
+ }
323
+ const score = new Map(layer.map((key) => [key, median(
324
+ neighborMap.get(key).filter((neighbor) => allPositions.has(neighbor)).map((neighbor) => allPositions.get(neighbor)),
325
+ )]));
326
+ layer.sort((leftKey, rightKey) => {
327
+ const left = nodeByKey.get(leftKey);
328
+ const right = nodeByKey.get(rightKey);
329
+ const groupOrder = compareText(left.lane, right.lane)
330
+ || compareText(left.ref.plan_key, right.ref.plan_key);
331
+ if (groupOrder !== 0) return groupOrder;
332
+ const leftScore = score.get(leftKey);
333
+ const rightScore = score.get(rightKey);
334
+ if (leftScore !== null && rightScore !== null && leftScore !== rightScore) return leftScore - rightScore;
335
+ if (leftScore === null && rightScore !== null) return 1;
336
+ if (leftScore !== null && rightScore === null) return -1;
337
+ return previousPosition.get(leftKey) - previousPosition.get(rightKey) || baseCompare(leftKey, rightKey);
338
+ });
339
+ };
340
+
341
+ for (let round = 0; round < SWEEP_ROUNDS; round += 1) {
342
+ for (let layer = 1; layer < layers.length; layer += 1) reorder(layer, incoming);
343
+ for (let layer = layers.length - 2; layer >= 0; layer -= 1) reorder(layer, outgoing);
344
+ }
345
+ return layers;
346
+ }
347
+
348
+ function crossingCount(edges, wave, transversePosition) {
349
+ const groups = new Map();
350
+ for (const edge of edges) {
351
+ const pair = `${wave.get(edge.from)}\0${wave.get(edge.to)}`;
352
+ if (!groups.has(pair)) groups.set(pair, []);
353
+ groups.get(pair).push([transversePosition.get(edge.from), transversePosition.get(edge.to)]);
354
+ }
355
+ let total = 0;
356
+ for (const group of groups.values()) {
357
+ group.sort((left, right) => left[0] - right[0] || left[1] - right[1]);
358
+ const targets = [...new Set(group.map((entry) => entry[1]))].sort((left, right) => left - right);
359
+ const rank = new Map(targets.map((value, index) => [value, index + 1]));
360
+ const tree = new Uint32Array(targets.length + 1);
361
+ let seen = 0;
362
+ for (const [, target] of group) {
363
+ let prefix = 0;
364
+ for (let index = rank.get(target); index > 0; index -= index & -index) prefix += tree[index];
365
+ total += seen - prefix;
366
+ for (let index = rank.get(target); index < tree.length; index += index & -index) tree[index] += 1;
367
+ seen += 1;
368
+ }
369
+ }
370
+ return total;
371
+ }
372
+
373
+ export function layoutTodoGantt(readModel, chainProjection) {
374
+ const { nodes, nodesByKey, edges } = normalizeInput(readModel, chainProjection);
375
+ const { incoming, outgoing, wave } = assignWaves(nodes, nodesByKey, edges);
376
+ const layers = orderLayers(nodes, wave, incoming, outgoing);
377
+ const transversePosition = new Map();
378
+ for (const layer of layers) {
379
+ for (let index = 0; index < layer.length; index += 1) transversePosition.set(layer[index], index);
380
+ }
381
+
382
+ const longestNodeKeys = new Set(chainProjection.longest_chain_node_refs.map((ref, index) => {
383
+ const key = refKey(refOf(ref, `longest_chain_node_refs[${index}]`));
384
+ if (!nodesByKey.has(key)) fail('TODO_LAYOUT_INVALID_INPUT', 'chain projection references an absent task');
385
+ return key;
386
+ }));
387
+ const longestEdgeKeys = new Set(chainProjection.longest_chain_edges.map((edge, index) => {
388
+ if (!plain(edge)) fail('TODO_LAYOUT_INVALID_INPUT', `longest_chain_edges[${index}] is invalid`);
389
+ const from = refKey(refOf(edge.from, `longest_chain_edges[${index}].from`));
390
+ const to = refKey(refOf(edge.to, `longest_chain_edges[${index}].to`));
391
+ const key = JSON.stringify([from, to]);
392
+ if (!edges.some((candidate) => candidate.key === key)) {
393
+ fail('TODO_LAYOUT_INVALID_INPUT', 'chain projection edge is absent from the read model');
394
+ }
395
+ return key;
396
+ }));
397
+ const readyKeys = readyTaskKeys(readModel, nodes, nodesByKey, incoming);
398
+ const visibleKeys = new Set(nodes.map(({ key }) => key));
399
+ const displayBranches = edges.flatMap((edge, semanticIndex) => {
400
+ const identities = [...edge.joinIdentities.entries()]
401
+ .sort(([left], [right]) => compareText(left, right)).map(([, identity]) => identity);
402
+ const variants = identities.length === 0 ? [null] : identities;
403
+ return variants.map((joinIdentity, branchIndex) => ({
404
+ key: JSON.stringify([edge.key, joinIdentity]), edge, semanticIndex, branchIndex, joinIdentity,
405
+ }));
406
+ });
407
+
408
+ const gapGroups = new Map();
409
+ const portGroups = new Map();
410
+ const portGroupKey = (gap, row) => `${gap}\0${row}`;
411
+ const portEntryKey = (edgeKey, direction) => `${edgeKey}\0${direction}`;
412
+ const addPort = (gap, row, entry) => {
413
+ const key = portGroupKey(gap, row);
414
+ if (!portGroups.has(key)) portGroups.set(key, []);
415
+ portGroups.get(key).push(entry);
416
+ };
417
+ displayBranches.forEach((branch) => {
418
+ const { edge } = branch;
419
+ for (const gap of new Set([wave.get(edge.from), wave.get(edge.to) - 1])) {
420
+ if (!gapGroups.has(gap)) gapGroups.set(gap, []);
421
+ gapGroups.get(gap).push(branch.key);
422
+ }
423
+ addPort(wave.get(edge.from), transversePosition.get(edge.from), portEntryKey(branch.key, 'departure'));
424
+ addPort(wave.get(edge.to) - 1, transversePosition.get(edge.to), portEntryKey(branch.key, 'arrival'));
425
+ });
426
+ for (const keys of gapGroups.values()) keys.sort(compareText);
427
+ for (const entries of portGroups.values()) entries.sort(compareText);
428
+ const maximumPortTraffic = Math.max(0, ...[...portGroups.values()].map(({ length }) => length));
429
+ const connectorGroupKeysByGap = new Map();
430
+ for (const branch of displayBranches) {
431
+ if (branch.joinIdentity === null) continue;
432
+ const targetGap = wave.get(branch.edge.to) - 1;
433
+ if (!connectorGroupKeysByGap.has(targetGap)) connectorGroupKeysByGap.set(targetGap, new Set());
434
+ connectorGroupKeysByGap.get(targetGap).add(JSON.stringify([branch.edge.to, branch.joinIdentity]));
435
+ }
436
+ const allGapIndexes = new Set([...gapGroups.keys(), ...connectorGroupKeysByGap.keys()]);
437
+ const maximumGapOccupancy = Math.max(0, ...[...allGapIndexes].map((gap) =>
438
+ (gapGroups.get(gap)?.length ?? 0) + (connectorGroupKeysByGap.get(gap)?.size ?? 0)));
439
+ const nodeWidth = Math.max(GEOMETRY.node_width,
440
+ GEOMETRY.route_inset * 2 + (maximumPortTraffic + 1) * GEOMETRY.route_spacing);
441
+ const laneGap = nodeWidth + 24;
442
+ const waveGap = GEOMETRY.node_height + Math.max(36,
443
+ (maximumGapOccupancy + 1) * GEOMETRY.route_spacing);
444
+
445
+ const coordinates = new Map();
446
+ const projectedNodes = nodes.map((node) => {
447
+ const visible = visibleKeys.has(node.key);
448
+ const geometry = visible ? {
449
+ x: GEOMETRY.left + transversePosition.get(node.key) * laneGap,
450
+ y: GEOMETRY.top + wave.get(node.key) * waveGap,
451
+ width: nodeWidth,
452
+ height: GEOMETRY.node_height,
453
+ } : null;
454
+ if (geometry !== null) coordinates.set(node.key, geometry);
455
+ return {
456
+ ref: { ...node.ref }, title: node.title, lane: node.lane, status: node.status,
457
+ wave: wave.get(node.key), row: transversePosition.get(node.key), visible,
458
+ visibility: {
459
+ longest_dependency_chain: longestNodeKeys.has(node.key),
460
+ active: node.status === 'in-progress', next_ready: readyKeys.has(node.key),
461
+ selected: false,
462
+ },
463
+ geometry,
464
+ };
465
+ });
466
+
467
+ const gapPosition = (edgeKey, waveIndex) => {
468
+ const keys = gapGroups.get(waveIndex);
469
+ const index = keys.indexOf(edgeKey);
470
+ return GEOMETRY.top + waveIndex * waveGap + GEOMETRY.node_height
471
+ + GEOMETRY.route_spacing * (index + 1);
472
+ };
473
+ const maximumLayerWidth = Math.max(...layers.map((layer) => layer.length));
474
+ const routeRight = GEOMETRY.left + (maximumLayerWidth - 1) * laneGap + nodeWidth + 12;
475
+ const branchCounts = new Map();
476
+ for (const branch of displayBranches) {
477
+ branchCounts.set(branch.semanticIndex, (branchCounts.get(branch.semanticIndex) ?? 0) + 1);
478
+ }
479
+ const portPosition = (edgeKey, direction, gap, row, nodeX) => {
480
+ const entries = portGroups.get(portGroupKey(gap, row));
481
+ return nodeX + GEOMETRY.route_inset
482
+ + GEOMETRY.route_spacing * (entries.indexOf(portEntryKey(edgeKey, direction)) + 1);
483
+ };
484
+
485
+ const projectedEdges = displayBranches.map((branch, index) => {
486
+ const { edge } = branch;
487
+ const onLongestChain = longestEdgeKeys.has(edge.key);
488
+ const from = coordinates.get(edge.from);
489
+ const to = coordinates.get(edge.to);
490
+ const departureGap = wave.get(edge.from);
491
+ const arrivalGap = wave.get(edge.to) - 1;
492
+ const startX = portPosition(branch.key, 'departure', departureGap,
493
+ transversePosition.get(edge.from), from.x);
494
+ const startY = from.y + from.height;
495
+ const endX = portPosition(branch.key, 'arrival', arrivalGap,
496
+ transversePosition.get(edge.to), to.x);
497
+ const endY = to.y;
498
+ const departureY = gapPosition(branch.key, departureGap);
499
+ const arrivalY = gapPosition(branch.key, arrivalGap);
500
+ const corridorX = routeRight + GEOMETRY.route_spacing * (index + 1);
501
+ const route = [[startX, startY], [startX, departureY]];
502
+ if (arrivalY === departureY) route.push([endX, arrivalY]);
503
+ else route.push([corridorX, departureY], [corridorX, arrivalY], [endX, arrivalY]);
504
+ route.push([endX, endY]);
505
+ return {
506
+ id: branchCounts.get(branch.semanticIndex) === 1
507
+ ? `edge-${branch.semanticIndex}` : `edge-${branch.semanticIndex}-join-${branch.branchIndex}`,
508
+ semantic_edge_id: `edge-${branch.semanticIndex}`,
509
+ from: { ...nodesByKey.get(edge.from).ref }, to: { ...nodesByKey.get(edge.to).ref },
510
+ kinds: [...edge.kinds].sort(compareText),
511
+ join_ids: branch.joinIdentity === null ? [] : [branch.joinIdentity.join_id],
512
+ join_owners: branch.joinIdentity === null ? [] : [{ ...branch.joinIdentity }],
513
+ visible: true, visibility: { longest_dependency_chain: onLongestChain, selected_incident: false },
514
+ route, bridges: [], junction: null,
515
+ };
516
+ });
517
+ const joinGroups = new Map();
518
+ for (const edge of projectedEdges) {
519
+ if (edge.join_ids.length === 0) continue;
520
+ const key = JSON.stringify([edge.to, edge.join_owners[0]]);
521
+ if (!joinGroups.has(key)) joinGroups.set(key, []);
522
+ joinGroups.get(key).push(edge);
523
+ }
524
+ const junctionConnectors = [];
525
+ const logicalContacts = new Set();
526
+ const connectorGapRanks = new Map();
527
+ for (const [, group] of [...joinGroups.entries()].sort(([left], [right]) => compareText(left, right))) {
528
+ group.sort((left, right) => compareText(left.id, right.id));
529
+ const target = coordinates.get(refKey(group[0].to));
530
+ const targetGap = wave.get(refKey(group[0].to)) - 1;
531
+ const groupRank = connectorGapRanks.get(targetGap) ?? 0;
532
+ connectorGapRanks.set(targetGap, groupRank + 1);
533
+ const primaryTargetPortX = group[0].route.at(-1)[0];
534
+ const junction = [primaryTargetPortX,
535
+ GEOMETRY.top + targetGap * waveGap + GEOMETRY.node_height
536
+ + GEOMETRY.route_spacing * ((gapGroups.get(targetGap)?.length ?? 0) + groupRank + 1)];
537
+ const contacts = [];
538
+ for (const [index, edge] of group.entries()) {
539
+ edge.route.pop();
540
+ const contact = [edge.route.at(-1)[0], junction[1]];
541
+ if (edge.route.at(-1)[1] !== contact[1]) edge.route.push(contact);
542
+ contacts.push(contact);
543
+ logicalContacts.add(`${contact[0]},${contact[1]}`);
544
+ if (index === 0) {
545
+ edge.route.push([junction[0], target.y]);
546
+ edge.junction = junction;
547
+ }
548
+ }
549
+ const contactXs = contacts.map(([x]) => x);
550
+ if (Math.min(...contactXs) !== Math.max(...contactXs)) {
551
+ junctionConnectors.push({
552
+ id: `join-connector-${junctionConnectors.length}`,
553
+ join_ids: [...group[0].join_ids], join_owners: group[0].join_owners.map((owner) => ({ ...owner })),
554
+ route: [[Math.min(...contactXs), junction[1]], [Math.max(...contactXs), junction[1]]],
555
+ bridges: [], contacts,
556
+ });
557
+ }
558
+ }
559
+ addCrossingBridges([...projectedEdges, ...junctionConnectors], logicalContacts);
560
+ let routeMaximumX = nodes.length === 0 ? 0
561
+ : GEOMETRY.left + (maximumLayerWidth - 1) * laneGap + nodeWidth;
562
+ for (const edge of projectedEdges) {
563
+ for (const [x] of edge.route) routeMaximumX = Math.max(routeMaximumX, x);
564
+ }
565
+ for (const connector of junctionConnectors) {
566
+ for (const [x] of connector.route) routeMaximumX = Math.max(routeMaximumX, x);
567
+ }
568
+
569
+ const planMap = new Map();
570
+ const laneMap = new Map();
571
+ for (const node of nodes) {
572
+ if (!planMap.has(node.ref.plan_key)) planMap.set(node.ref.plan_key, 0);
573
+ planMap.set(node.ref.plan_key, planMap.get(node.ref.plan_key) + 1);
574
+ const key = groupKey(node.ref.plan_key, node.lane);
575
+ if (!laneMap.has(key)) laneMap.set(key, { plan_key: node.ref.plan_key, lane: node.lane, task_count: 0 });
576
+ laneMap.get(key).task_count += 1;
577
+ }
578
+ return {
579
+ schema: 'lattice.todo_gantt_layout.v1',
580
+ assumptions: { logical_time: 'dependency_wave', duration_estimation: false, lane_is_presentation_only: true },
581
+ sweep: { method: 'stable_median', rounds: SWEEP_ROUNDS, tie_break: 'previous_position_then_task_ref' },
582
+ bounds: {
583
+ width: nodes.length === 0 ? 0 : routeMaximumX + GEOMETRY.left,
584
+ height: nodes.length === 0 ? 0 : GEOMETRY.top * 2
585
+ + (layers.length - 1) * waveGap + GEOMETRY.node_height,
586
+ wave_count: layers.length,
587
+ },
588
+ nodes: projectedNodes,
589
+ edges: projectedEdges,
590
+ connectors: junctionConnectors,
591
+ groups: {
592
+ plans: [...planMap.entries()].map(([plan_key, task_count]) => ({ plan_key, task_count })),
593
+ lanes: [...laneMap.values()],
594
+ },
595
+ metrics: {
596
+ crossing_count: crossingCount(edges, wave, transversePosition),
597
+ visible_node_count: visibleKeys.size,
598
+ visible_edge_count: projectedEdges.filter(({ visible }) => visible).length,
599
+ task_count: nodes.length,
600
+ edge_count: edges.length,
601
+ },
602
+ };
603
+ }