spexcode 0.6.2 → 0.6.5

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 (479) hide show
  1. package/README.md +3 -1
  2. package/bin/spex.mjs +4 -0
  3. package/node_modules/@spexcode/spec-cli/bin/spex.mjs +76 -0
  4. package/node_modules/@spexcode/spec-cli/dist/attach.d.ts +2 -0
  5. package/node_modules/@spexcode/spec-cli/dist/attach.js +48 -0
  6. package/node_modules/@spexcode/spec-cli/dist/claude-headless.d.ts +33 -0
  7. package/node_modules/@spexcode/spec-cli/dist/claude-headless.js +307 -0
  8. package/node_modules/@spexcode/spec-cli/dist/cli.d.ts +1 -0
  9. package/node_modules/@spexcode/spec-cli/dist/cli.js +1859 -0
  10. package/node_modules/@spexcode/spec-cli/dist/client.d.ts +111 -0
  11. package/node_modules/@spexcode/spec-cli/dist/client.js +476 -0
  12. package/node_modules/@spexcode/spec-cli/dist/cockpit.d.ts +7 -0
  13. package/node_modules/@spexcode/spec-cli/dist/cockpit.js +26 -0
  14. package/node_modules/@spexcode/spec-cli/dist/codex-headless.d.ts +1 -0
  15. package/{spec-cli/src/codex-headless.ts → node_modules/@spexcode/spec-cli/dist/codex-headless.js} +3 -9
  16. package/node_modules/@spexcode/spec-cli/dist/codex-runtime-generations.d.ts +53 -0
  17. package/node_modules/@spexcode/spec-cli/dist/codex-runtime-generations.js +686 -0
  18. package/node_modules/@spexcode/spec-cli/dist/commit-surgery.d.ts +1 -0
  19. package/node_modules/@spexcode/spec-cli/dist/commit-surgery.js +90 -0
  20. package/node_modules/@spexcode/spec-cli/dist/contract-filter.d.ts +14 -0
  21. package/node_modules/@spexcode/spec-cli/dist/contract-filter.js +219 -0
  22. package/node_modules/@spexcode/spec-cli/dist/dashboard-assets.d.ts +6 -0
  23. package/node_modules/@spexcode/spec-cli/dist/dashboard-assets.js +50 -0
  24. package/node_modules/@spexcode/spec-cli/dist/delivery-queue.d.ts +23 -0
  25. package/node_modules/@spexcode/spec-cli/dist/delivery-queue.js +179 -0
  26. package/node_modules/@spexcode/spec-cli/dist/doctor.d.ts +9 -0
  27. package/node_modules/@spexcode/spec-cli/dist/doctor.js +547 -0
  28. package/node_modules/@spexcode/spec-cli/dist/eval-host.d.ts +1 -0
  29. package/node_modules/@spexcode/spec-cli/dist/eval-host.js +19 -0
  30. package/node_modules/@spexcode/spec-cli/dist/execution-trace.d.ts +26 -0
  31. package/node_modules/@spexcode/spec-cli/dist/execution-trace.js +475 -0
  32. package/node_modules/@spexcode/spec-cli/dist/file-write.d.ts +2 -0
  33. package/node_modules/@spexcode/spec-cli/dist/file-write.js +26 -0
  34. package/node_modules/@spexcode/spec-cli/dist/flat.d.ts +89 -0
  35. package/node_modules/@spexcode/spec-cli/dist/flat.js +1076 -0
  36. package/node_modules/@spexcode/spec-cli/dist/gateway-auth.d.ts +56 -0
  37. package/node_modules/@spexcode/spec-cli/dist/gateway-auth.js +168 -0
  38. package/node_modules/@spexcode/spec-cli/dist/gateway-hub.d.ts +28 -0
  39. package/node_modules/@spexcode/spec-cli/dist/gateway-hub.js +361 -0
  40. package/node_modules/@spexcode/spec-cli/dist/gateway.d.ts +37 -0
  41. package/node_modules/@spexcode/spec-cli/dist/gateway.js +503 -0
  42. package/node_modules/@spexcode/spec-cli/dist/graphCache.d.ts +27 -0
  43. package/node_modules/@spexcode/spec-cli/dist/graphCache.js +716 -0
  44. package/node_modules/@spexcode/spec-cli/dist/graphSnapshot.d.ts +14 -0
  45. package/node_modules/@spexcode/spec-cli/dist/graphSnapshot.js +35 -0
  46. package/node_modules/@spexcode/spec-cli/dist/graphStream.d.ts +85 -0
  47. package/node_modules/@spexcode/spec-cli/dist/graphStream.js +1195 -0
  48. package/node_modules/@spexcode/spec-cli/dist/guidance-catalog.d.ts +64 -0
  49. package/node_modules/@spexcode/spec-cli/dist/guidance-catalog.js +166 -0
  50. package/node_modules/@spexcode/spec-cli/dist/guide.d.ts +8 -0
  51. package/{spec-cli/src/guide.ts → node_modules/@spexcode/spec-cli/dist/guide.js} +36 -32
  52. package/node_modules/@spexcode/spec-cli/dist/harness-select.d.ts +18 -0
  53. package/node_modules/@spexcode/spec-cli/dist/harness-select.js +62 -0
  54. package/node_modules/@spexcode/spec-cli/dist/harness.d.ts +325 -0
  55. package/node_modules/@spexcode/spec-cli/dist/harness.js +3026 -0
  56. package/node_modules/@spexcode/spec-cli/dist/headless-controller.d.ts +9 -0
  57. package/node_modules/@spexcode/spec-cli/dist/headless-controller.js +47 -0
  58. package/node_modules/@spexcode/spec-cli/dist/help.d.ts +10 -0
  59. package/node_modules/@spexcode/spec-cli/dist/help.js +574 -0
  60. package/node_modules/@spexcode/spec-cli/dist/hook-prompts.d.ts +18 -0
  61. package/node_modules/@spexcode/spec-cli/dist/hook-prompts.js +122 -0
  62. package/node_modules/@spexcode/spec-cli/dist/hooks.d.ts +1 -0
  63. package/{spec-cli/src/hooks.ts → node_modules/@spexcode/spec-cli/dist/hooks.js} +15 -15
  64. package/node_modules/@spexcode/spec-cli/dist/host-resources.d.ts +97 -0
  65. package/node_modules/@spexcode/spec-cli/dist/host-resources.js +795 -0
  66. package/node_modules/@spexcode/spec-cli/dist/host.d.ts +88 -0
  67. package/node_modules/@spexcode/spec-cli/dist/host.js +663 -0
  68. package/node_modules/@spexcode/spec-cli/dist/index.d.ts +1 -0
  69. package/node_modules/@spexcode/spec-cli/dist/index.js +899 -0
  70. package/node_modules/@spexcode/spec-cli/dist/init.d.ts +1 -0
  71. package/node_modules/@spexcode/spec-cli/dist/init.js +284 -0
  72. package/node_modules/@spexcode/spec-cli/dist/issues-cli.d.ts +6 -0
  73. package/node_modules/@spexcode/spec-cli/dist/issues-cli.js +334 -0
  74. package/node_modules/@spexcode/spec-cli/dist/issues.d.ts +69 -0
  75. package/node_modules/@spexcode/spec-cli/dist/issues.js +189 -0
  76. package/node_modules/@spexcode/spec-cli/dist/lint.d.ts +34 -0
  77. package/node_modules/@spexcode/spec-cli/dist/lint.js +476 -0
  78. package/node_modules/@spexcode/spec-cli/dist/listen.d.ts +7 -0
  79. package/{spec-cli/src/listen.ts → node_modules/@spexcode/spec-cli/dist/listen.js} +13 -17
  80. package/node_modules/@spexcode/spec-cli/dist/localIssues.d.ts +65 -0
  81. package/node_modules/@spexcode/spec-cli/dist/localIssues.js +582 -0
  82. package/node_modules/@spexcode/spec-cli/dist/login-page.d.ts +5 -0
  83. package/{spec-cli/src/login-page.ts → node_modules/@spexcode/spec-cli/dist/login-page.js} +7 -7
  84. package/node_modules/@spexcode/spec-cli/dist/loop-in.d.ts +23 -0
  85. package/{spec-cli/src/loop-in.ts → node_modules/@spexcode/spec-cli/dist/loop-in.js} +35 -45
  86. package/node_modules/@spexcode/spec-cli/dist/machine-peer.d.ts +69 -0
  87. package/node_modules/@spexcode/spec-cli/dist/machine-peer.js +603 -0
  88. package/node_modules/@spexcode/spec-cli/dist/materialize.d.ts +18 -0
  89. package/node_modules/@spexcode/spec-cli/dist/materialize.js +468 -0
  90. package/node_modules/@spexcode/spec-cli/dist/mentions.d.ts +50 -0
  91. package/node_modules/@spexcode/spec-cli/dist/mentions.js +117 -0
  92. package/node_modules/@spexcode/spec-cli/dist/opencode-headless.d.ts +4 -0
  93. package/node_modules/@spexcode/spec-cli/dist/opencode-headless.js +208 -0
  94. package/node_modules/@spexcode/spec-cli/dist/opencode.d.ts +3 -0
  95. package/{spec-cli/src/opencode.ts → node_modules/@spexcode/spec-cli/dist/opencode.js} +9 -13
  96. package/node_modules/@spexcode/spec-cli/dist/pi-harness.d.ts +3 -0
  97. package/{spec-cli/src/pi-harness.ts → node_modules/@spexcode/spec-cli/dist/pi-harness.js} +45 -40
  98. package/node_modules/@spexcode/spec-cli/dist/pi-headless.d.ts +25 -0
  99. package/node_modules/@spexcode/spec-cli/dist/pi-headless.js +196 -0
  100. package/node_modules/@spexcode/spec-cli/dist/plugin-harness.d.ts +21 -0
  101. package/node_modules/@spexcode/spec-cli/dist/plugin-harness.js +146 -0
  102. package/node_modules/@spexcode/spec-cli/dist/pty-bridge.d.ts +10 -0
  103. package/node_modules/@spexcode/spec-cli/dist/pty-bridge.js +404 -0
  104. package/node_modules/@spexcode/spec-cli/dist/pty-helper.d.mts +1 -0
  105. package/node_modules/@spexcode/spec-cli/dist/pty-helper.mjs +101 -0
  106. package/node_modules/@spexcode/spec-cli/dist/pty-native-helper.d.mts +2 -0
  107. package/node_modules/@spexcode/spec-cli/dist/pty-native-helper.mjs +23 -0
  108. package/node_modules/@spexcode/spec-cli/dist/public-graph.d.ts +46 -0
  109. package/node_modules/@spexcode/spec-cli/dist/public-graph.js +56 -0
  110. package/node_modules/@spexcode/spec-cli/dist/ranker.d.ts +15 -0
  111. package/node_modules/@spexcode/spec-cli/dist/ranker.js +188 -0
  112. package/node_modules/@spexcode/spec-cli/dist/reaper.d.ts +6 -0
  113. package/node_modules/@spexcode/spec-cli/dist/reaper.js +81 -0
  114. package/node_modules/@spexcode/spec-cli/dist/reviews.d.ts +189 -0
  115. package/node_modules/@spexcode/spec-cli/dist/reviews.js +350 -0
  116. package/node_modules/@spexcode/spec-cli/dist/runtime-guard.d.ts +6 -0
  117. package/node_modules/@spexcode/spec-cli/dist/runtime-guard.js +37 -0
  118. package/node_modules/@spexcode/spec-cli/dist/runtime-ownership.d.ts +21 -0
  119. package/node_modules/@spexcode/spec-cli/dist/runtime-ownership.js +84 -0
  120. package/node_modules/@spexcode/spec-cli/dist/search.bench.d.mts +1 -0
  121. package/node_modules/@spexcode/spec-cli/dist/search.bench.mjs +75 -0
  122. package/node_modules/@spexcode/spec-cli/dist/search.d.ts +20 -0
  123. package/node_modules/@spexcode/spec-cli/dist/search.js +61 -0
  124. package/node_modules/@spexcode/spec-cli/dist/session-cursors.d.ts +14 -0
  125. package/node_modules/@spexcode/spec-cli/dist/session-cursors.js +82 -0
  126. package/node_modules/@spexcode/spec-cli/dist/session-declarations.d.ts +13 -0
  127. package/node_modules/@spexcode/spec-cli/dist/session-declarations.js +126 -0
  128. package/node_modules/@spexcode/spec-cli/dist/session-execution.d.ts +10 -0
  129. package/node_modules/@spexcode/spec-cli/dist/session-execution.js +70 -0
  130. package/node_modules/@spexcode/spec-cli/dist/session-files.d.ts +28 -0
  131. package/node_modules/@spexcode/spec-cli/dist/session-files.js +130 -0
  132. package/node_modules/@spexcode/spec-cli/dist/session-follow.d.ts +31 -0
  133. package/node_modules/@spexcode/spec-cli/dist/session-follow.js +179 -0
  134. package/node_modules/@spexcode/spec-cli/dist/session-reparent.d.ts +10 -0
  135. package/node_modules/@spexcode/spec-cli/dist/session-reparent.js +33 -0
  136. package/node_modules/@spexcode/spec-cli/dist/session-timeline.d.ts +51 -0
  137. package/node_modules/@spexcode/spec-cli/dist/session-timeline.js +233 -0
  138. package/node_modules/@spexcode/spec-cli/dist/session-web.d.ts +24 -0
  139. package/node_modules/@spexcode/spec-cli/dist/session-web.js +141 -0
  140. package/node_modules/@spexcode/spec-cli/dist/sessions.d.ts +412 -0
  141. package/node_modules/@spexcode/spec-cli/dist/sessions.js +4239 -0
  142. package/node_modules/@spexcode/spec-cli/dist/sh.d.ts +1 -0
  143. package/node_modules/@spexcode/spec-cli/dist/sh.js +3 -0
  144. package/node_modules/@spexcode/spec-cli/dist/shim-runtime.d.ts +1 -0
  145. package/{spec-cli/src/shim-runtime.ts → node_modules/@spexcode/spec-cli/dist/shim-runtime.js} +3 -4
  146. package/node_modules/@spexcode/spec-cli/dist/slash-commands.d.ts +9 -0
  147. package/node_modules/@spexcode/spec-cli/dist/slash-commands.js +307 -0
  148. package/node_modules/@spexcode/spec-cli/dist/source-files.d.ts +9 -0
  149. package/node_modules/@spexcode/spec-cli/dist/source-files.js +113 -0
  150. package/node_modules/@spexcode/spec-cli/dist/supervise.d.ts +1 -0
  151. package/node_modules/@spexcode/spec-cli/dist/supervise.js +308 -0
  152. package/node_modules/@spexcode/spec-cli/dist/tree.d.ts +25 -0
  153. package/node_modules/@spexcode/spec-cli/dist/tree.js +98 -0
  154. package/node_modules/@spexcode/spec-cli/dist/tsx-bin.d.ts +3 -0
  155. package/node_modules/@spexcode/spec-cli/dist/tsx-bin.js +41 -0
  156. package/node_modules/@spexcode/spec-cli/dist/uninstall.d.ts +3 -0
  157. package/node_modules/@spexcode/spec-cli/dist/uninstall.js +177 -0
  158. package/node_modules/@spexcode/spec-cli/dist/uploads.d.ts +36 -0
  159. package/node_modules/@spexcode/spec-cli/dist/uploads.js +243 -0
  160. package/node_modules/@spexcode/spec-cli/dist/worktree-sources.d.ts +1 -0
  161. package/node_modules/@spexcode/spec-cli/dist/worktree-sources.js +47 -0
  162. package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/dispatch.sh +5 -5
  163. package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/harness.sh +1 -1
  164. package/node_modules/@spexcode/spec-cli/package.json +47 -0
  165. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/commands/supervisor/spec.md +8 -0
  166. package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-first/spec-first.sh +1 -1
  167. package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-of-file/spec-of-file.sh +1 -0
  168. package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/stop-gate/spec.md +2 -2
  169. package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/stop-gate/stop-gate.sh +31 -29
  170. package/node_modules/@spexcode/spec-core/dist/anchors.d.ts +94 -0
  171. package/node_modules/@spexcode/spec-core/dist/anchors.js +730 -0
  172. package/node_modules/@spexcode/spec-core/dist/git.d.ts +166 -0
  173. package/node_modules/@spexcode/spec-core/dist/git.js +2736 -0
  174. package/node_modules/@spexcode/spec-core/dist/graph-delta.d.ts +44 -0
  175. package/node_modules/@spexcode/spec-core/dist/graph-delta.js +72 -0
  176. package/node_modules/@spexcode/spec-core/dist/graph.d.ts +34 -0
  177. package/node_modules/@spexcode/spec-core/dist/graph.js +237 -0
  178. package/node_modules/@spexcode/spec-core/dist/graphDelta.d.ts +3 -0
  179. package/node_modules/@spexcode/spec-core/dist/graphDelta.js +13 -0
  180. package/node_modules/@spexcode/spec-core/dist/harness-identity.d.ts +31 -0
  181. package/node_modules/@spexcode/spec-core/dist/harness-identity.js +20 -0
  182. package/node_modules/@spexcode/spec-core/dist/identity-presets.d.ts +152 -0
  183. package/node_modules/@spexcode/spec-core/dist/identity-presets.js +132 -0
  184. package/node_modules/@spexcode/spec-core/dist/index.d.ts +45 -0
  185. package/node_modules/@spexcode/spec-core/dist/index.js +19 -0
  186. package/node_modules/@spexcode/spec-core/dist/layout.d.ts +183 -0
  187. package/node_modules/@spexcode/spec-core/dist/layout.js +548 -0
  188. package/node_modules/@spexcode/spec-core/dist/process-identity.d.ts +37 -0
  189. package/node_modules/@spexcode/spec-core/dist/process-identity.js +214 -0
  190. package/node_modules/@spexcode/spec-core/dist/project-identity.d.ts +12 -0
  191. package/node_modules/@spexcode/spec-core/dist/project-identity.js +71 -0
  192. package/node_modules/@spexcode/spec-core/dist/project-store.d.ts +3 -0
  193. package/node_modules/@spexcode/spec-core/dist/project-store.js +14 -0
  194. package/node_modules/@spexcode/spec-core/dist/resilience.d.ts +2 -0
  195. package/node_modules/@spexcode/spec-core/dist/resilience.js +40 -0
  196. package/node_modules/@spexcode/spec-core/dist/review/index.d.ts +3 -0
  197. package/node_modules/@spexcode/spec-core/dist/review/index.js +5 -0
  198. package/node_modules/@spexcode/spec-core/dist/review/reviewFilters.d.ts +77 -0
  199. package/node_modules/@spexcode/spec-core/dist/review/reviewFilters.js +308 -0
  200. package/node_modules/@spexcode/spec-core/dist/review/reviewQuery.d.ts +66 -0
  201. package/node_modules/@spexcode/spec-core/dist/review/reviewQuery.js +180 -0
  202. package/node_modules/@spexcode/spec-core/dist/review/session.d.ts +4 -0
  203. package/node_modules/@spexcode/spec-core/dist/review/session.js +8 -0
  204. package/node_modules/@spexcode/spec-core/dist/reviewSnapshot.d.ts +15 -0
  205. package/node_modules/@spexcode/spec-core/dist/reviewSnapshot.js +12 -0
  206. package/node_modules/@spexcode/spec-core/dist/root-lru.d.ts +4 -0
  207. package/{spec-cli/src/root-lru.ts → node_modules/@spexcode/spec-core/dist/root-lru.js} +26 -30
  208. package/node_modules/@spexcode/spec-core/dist/specs.d.ts +117 -0
  209. package/node_modules/@spexcode/spec-core/dist/specs.js +489 -0
  210. package/node_modules/@spexcode/spec-core/package.json +28 -0
  211. package/node_modules/@spexcode/spec-eval/dist/cache.d.ts +10 -0
  212. package/node_modules/@spexcode/spec-eval/dist/cache.js +50 -0
  213. package/node_modules/@spexcode/spec-eval/dist/cli.d.ts +22 -0
  214. package/node_modules/@spexcode/spec-eval/dist/cli.js +981 -0
  215. package/node_modules/@spexcode/spec-eval/dist/evaltab.d.ts +98 -0
  216. package/node_modules/@spexcode/spec-eval/dist/evaltab.js +176 -0
  217. package/node_modules/@spexcode/spec-eval/dist/filing.d.ts +16 -0
  218. package/node_modules/@spexcode/spec-eval/dist/filing.js +36 -0
  219. package/node_modules/@spexcode/spec-eval/dist/freshness.d.ts +48 -0
  220. package/node_modules/@spexcode/spec-eval/dist/freshness.js +799 -0
  221. package/node_modules/@spexcode/spec-eval/dist/host.d.ts +46 -0
  222. package/node_modules/@spexcode/spec-eval/dist/host.js +53 -0
  223. package/node_modules/@spexcode/spec-eval/dist/humanok.d.ts +11 -0
  224. package/node_modules/@spexcode/spec-eval/dist/humanok.js +27 -0
  225. package/node_modules/@spexcode/spec-eval/dist/index.d.ts +4 -0
  226. package/node_modules/@spexcode/spec-eval/dist/index.js +4 -0
  227. package/node_modules/@spexcode/spec-eval/dist/remarks.d.ts +31 -0
  228. package/node_modules/@spexcode/spec-eval/dist/remarks.js +1 -0
  229. package/node_modules/@spexcode/spec-eval/dist/scenariofresh.d.ts +13 -0
  230. package/node_modules/@spexcode/spec-eval/dist/scenariofresh.js +303 -0
  231. package/node_modules/@spexcode/spec-eval/dist/scenarios.d.ts +99 -0
  232. package/node_modules/@spexcode/spec-eval/dist/scenarios.js +650 -0
  233. package/node_modules/@spexcode/spec-eval/dist/sessioneval.d.ts +307 -0
  234. package/node_modules/@spexcode/spec-eval/dist/sessioneval.js +1883 -0
  235. package/node_modules/@spexcode/spec-eval/dist/sidecar.d.ts +55 -0
  236. package/node_modules/@spexcode/spec-eval/dist/sidecar.js +82 -0
  237. package/node_modules/@spexcode/spec-eval/dist/timeline.d.ts +25 -0
  238. package/node_modules/@spexcode/spec-eval/dist/timeline.js +65 -0
  239. package/node_modules/@spexcode/spec-eval/dist/ui-path.d.ts +1 -0
  240. package/node_modules/@spexcode/spec-eval/dist/ui-path.js +2 -0
  241. package/node_modules/@spexcode/spec-eval/package.json +36 -0
  242. package/node_modules/@spexcode/spec-forge/dist/cache.d.ts +29 -0
  243. package/node_modules/@spexcode/spec-forge/dist/cache.js +59 -0
  244. package/node_modules/@spexcode/spec-forge/dist/cli.d.ts +1 -0
  245. package/node_modules/@spexcode/spec-forge/dist/cli.js +100 -0
  246. package/node_modules/@spexcode/spec-forge/dist/drivers/github.d.ts +2 -0
  247. package/node_modules/@spexcode/spec-forge/dist/drivers/github.js +116 -0
  248. package/node_modules/@spexcode/spec-forge/dist/drivers/gitlab.d.ts +7 -0
  249. package/node_modules/@spexcode/spec-forge/dist/drivers/gitlab.js +144 -0
  250. package/node_modules/@spexcode/spec-forge/dist/drivers.d.ts +10 -0
  251. package/node_modules/@spexcode/spec-forge/dist/drivers.js +83 -0
  252. package/node_modules/@spexcode/spec-forge/dist/index.d.ts +4 -0
  253. package/node_modules/@spexcode/spec-forge/dist/index.js +4 -0
  254. package/node_modules/@spexcode/spec-forge/dist/links.d.ts +15 -0
  255. package/node_modules/@spexcode/spec-forge/dist/links.js +65 -0
  256. package/node_modules/@spexcode/spec-forge/dist/needs-eval.d.ts +10 -0
  257. package/node_modules/@spexcode/spec-forge/dist/needs-eval.js +20 -0
  258. package/node_modules/@spexcode/spec-forge/dist/port.d.ts +53 -0
  259. package/node_modules/@spexcode/spec-forge/dist/port.js +1 -0
  260. package/node_modules/@spexcode/spec-forge/dist/resident.d.ts +7 -0
  261. package/node_modules/@spexcode/spec-forge/dist/resident.js +43 -0
  262. package/node_modules/@spexcode/spec-forge/package.json +32 -0
  263. package/package.json +33 -19
  264. package/spec-cli/bin/spex.mjs +0 -69
  265. package/spec-cli/src/anchors.ts +0 -728
  266. package/spec-cli/src/attach.ts +0 -43
  267. package/spec-cli/src/claude-headless.ts +0 -321
  268. package/spec-cli/src/cli.ts +0 -1258
  269. package/spec-cli/src/client.ts +0 -414
  270. package/spec-cli/src/cockpit.ts +0 -43
  271. package/spec-cli/src/codex-runtime-generations.ts +0 -646
  272. package/spec-cli/src/commit-surgery.ts +0 -68
  273. package/spec-cli/src/contract-filter.ts +0 -202
  274. package/spec-cli/src/delivery-queue.ts +0 -165
  275. package/spec-cli/src/doctor.ts +0 -516
  276. package/spec-cli/src/execution-trace.ts +0 -444
  277. package/spec-cli/src/file-write.ts +0 -22
  278. package/spec-cli/src/gateway-auth.ts +0 -181
  279. package/spec-cli/src/gateway-hub.ts +0 -358
  280. package/spec-cli/src/gateway.ts +0 -461
  281. package/spec-cli/src/git.ts +0 -2556
  282. package/spec-cli/src/graph.ts +0 -271
  283. package/spec-cli/src/graphCache.ts +0 -710
  284. package/spec-cli/src/graphDelta.ts +0 -77
  285. package/spec-cli/src/graphStream.ts +0 -1019
  286. package/spec-cli/src/harness-select.ts +0 -77
  287. package/spec-cli/src/harness.ts +0 -3025
  288. package/spec-cli/src/headless-controller.ts +0 -63
  289. package/spec-cli/src/help.ts +0 -492
  290. package/spec-cli/src/host-resources.ts +0 -762
  291. package/spec-cli/src/host.ts +0 -611
  292. package/spec-cli/src/identity-presets.d.ts +0 -13
  293. package/spec-cli/src/identity-presets.js +0 -138
  294. package/spec-cli/src/index.ts +0 -818
  295. package/spec-cli/src/init.ts +0 -258
  296. package/spec-cli/src/issues-cli.ts +0 -276
  297. package/spec-cli/src/issues.ts +0 -237
  298. package/spec-cli/src/layout.ts +0 -640
  299. package/spec-cli/src/lint.ts +0 -488
  300. package/spec-cli/src/localIssues.ts +0 -538
  301. package/spec-cli/src/materialize.ts +0 -417
  302. package/spec-cli/src/mentions.ts +0 -81
  303. package/spec-cli/src/opencode-headless.ts +0 -205
  304. package/spec-cli/src/pi-headless.ts +0 -153
  305. package/spec-cli/src/plugin-harness.ts +0 -157
  306. package/spec-cli/src/process-identity.ts +0 -207
  307. package/spec-cli/src/project-identity.ts +0 -73
  308. package/spec-cli/src/project-store.ts +0 -17
  309. package/spec-cli/src/pty-bridge.ts +0 -396
  310. package/spec-cli/src/pty-helper.mjs +0 -96
  311. package/spec-cli/src/pty-native-helper.mjs +0 -22
  312. package/spec-cli/src/ranker.ts +0 -191
  313. package/spec-cli/src/reaper.ts +0 -125
  314. package/spec-cli/src/resilience.ts +0 -41
  315. package/spec-cli/src/reviewSnapshot.ts +0 -28
  316. package/spec-cli/src/reviews.ts +0 -434
  317. package/spec-cli/src/runtime-guard.ts +0 -35
  318. package/spec-cli/src/runtime-ownership.ts +0 -88
  319. package/spec-cli/src/search.bench.mjs +0 -80
  320. package/spec-cli/src/search.ts +0 -63
  321. package/spec-cli/src/session-cursors.ts +0 -91
  322. package/spec-cli/src/session-execution.ts +0 -68
  323. package/spec-cli/src/session-files.ts +0 -121
  324. package/spec-cli/src/session-follow.ts +0 -182
  325. package/spec-cli/src/session-reparent.ts +0 -33
  326. package/spec-cli/src/session-timeline.ts +0 -247
  327. package/spec-cli/src/session-web.ts +0 -135
  328. package/spec-cli/src/sessions.ts +0 -3883
  329. package/spec-cli/src/sh.ts +0 -3
  330. package/spec-cli/src/slash-commands.ts +0 -318
  331. package/spec-cli/src/source-files.ts +0 -104
  332. package/spec-cli/src/specs.ts +0 -498
  333. package/spec-cli/src/supervise.ts +0 -245
  334. package/spec-cli/src/tree.ts +0 -118
  335. package/spec-cli/src/tsx-bin.ts +0 -18
  336. package/spec-cli/src/uninstall.ts +0 -171
  337. package/spec-cli/src/uploads.ts +0 -253
  338. package/spec-cli/src/worktree-sources.ts +0 -42
  339. package/spec-cli/templates/spec/project/.plugins/commands/supervisor/spec.md +0 -8
  340. package/spec-dashboard/dist/assets/App-B6IbFbGu.js +0 -2
  341. package/spec-dashboard/dist/assets/Dashboard-C5ap-Sga.css +0 -1
  342. package/spec-dashboard/dist/assets/Dashboard-_sDk7Rcg.js +0 -27
  343. package/spec-dashboard/dist/assets/EvalsPage-BT357Lyt.js +0 -2
  344. package/spec-dashboard/dist/assets/IssuesPage-BG9rNOgV.js +0 -1
  345. package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  346. package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  347. package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  348. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  349. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  350. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  351. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  352. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  353. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  354. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  355. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  356. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  357. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  358. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  359. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  360. package/spec-dashboard/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  361. package/spec-dashboard/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  362. package/spec-dashboard/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  363. package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  364. package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  365. package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  366. package/spec-dashboard/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  367. package/spec-dashboard/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  368. package/spec-dashboard/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  369. package/spec-dashboard/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  370. package/spec-dashboard/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  371. package/spec-dashboard/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  372. package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  373. package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  374. package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  375. package/spec-dashboard/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  376. package/spec-dashboard/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  377. package/spec-dashboard/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  378. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  379. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  380. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  381. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  382. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  383. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  384. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  385. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  386. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  387. package/spec-dashboard/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  388. package/spec-dashboard/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  389. package/spec-dashboard/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  390. package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  391. package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  392. package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  393. package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  394. package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  395. package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  396. package/spec-dashboard/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  397. package/spec-dashboard/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  398. package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  399. package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  400. package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  401. package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  402. package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  403. package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  404. package/spec-dashboard/dist/assets/MobileApp-D9yPMR3A.js +0 -2
  405. package/spec-dashboard/dist/assets/Modal-B3EcgeA3.js +0 -1
  406. package/spec-dashboard/dist/assets/PageScroll-CNZOugWV.js +0 -1
  407. package/spec-dashboard/dist/assets/ProjectsPage-C2CRHmvs.js +0 -1
  408. package/spec-dashboard/dist/assets/SessionInterface-6GBZ9nXN.css +0 -32
  409. package/spec-dashboard/dist/assets/SessionInterface-DOEjuT3l.js +0 -40
  410. package/spec-dashboard/dist/assets/SessionWindow-1nYgZ4CL.js +0 -1
  411. package/spec-dashboard/dist/assets/Settings-7WeTJn0V.js +0 -1
  412. package/spec-dashboard/dist/assets/Thread-CEbzAFQn.js +0 -13
  413. package/spec-dashboard/dist/assets/bindings-BC9vqpYU.js +0 -1
  414. package/spec-dashboard/dist/assets/data-SlvxNA2r.js +0 -1
  415. package/spec-dashboard/dist/assets/index-DLVeOHL-.js +0 -41
  416. package/spec-dashboard/dist/assets/index-DYLGfPjx.css +0 -1
  417. package/spec-dashboard/dist/assets/launch-Cp579UoJ.css +0 -1
  418. package/spec-dashboard/dist/assets/launch-yxCYr64x.js +0 -276
  419. package/spec-dashboard/dist/assets/sessionSurface--7l69h4F.js +0 -1
  420. package/spec-dashboard/dist/index.html +0 -29
  421. package/spec-dashboard/src/reviewFilters.js +0 -322
  422. package/spec-dashboard/src/reviewQuery.js +0 -174
  423. package/spec-dashboard/src/session.js +0 -199
  424. package/spec-eval/src/cache.ts +0 -57
  425. package/spec-eval/src/cli.ts +0 -894
  426. package/spec-eval/src/evaltab.ts +0 -264
  427. package/spec-eval/src/filing.ts +0 -39
  428. package/spec-eval/src/freshness.ts +0 -836
  429. package/spec-eval/src/humanok.ts +0 -28
  430. package/spec-eval/src/scenariofresh.ts +0 -244
  431. package/spec-eval/src/scenarios.ts +0 -672
  432. package/spec-eval/src/sessioneval.ts +0 -2220
  433. package/spec-eval/src/sidecar.ts +0 -93
  434. package/spec-eval/src/timeline.ts +0 -54
  435. package/spec-forge/src/__fixtures__/github-forge.json +0 -63
  436. package/spec-forge/src/cache.ts +0 -66
  437. package/spec-forge/src/cli.ts +0 -99
  438. package/spec-forge/src/drivers/github.ts +0 -132
  439. package/spec-forge/src/drivers/gitlab.ts +0 -153
  440. package/spec-forge/src/drivers.ts +0 -73
  441. package/spec-forge/src/links.ts +0 -72
  442. package/spec-forge/src/needs-eval.ts +0 -29
  443. package/spec-forge/src/port.ts +0 -36
  444. package/spec-forge/src/resident.ts +0 -46
  445. /package/{spec-cli → node_modules/@spexcode/spec-cli}/README.md +0 -0
  446. /package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/compat/mark-active-0.5.2-eef1.fixture +0 -0
  447. /package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/compat/mark-active-sed-v0.fixture +0 -0
  448. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/commit-msg +0 -0
  449. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/post-checkout +0 -0
  450. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/post-merge +0 -0
  451. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/pre-commit +0 -0
  452. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/prepare-commit-msg +0 -0
  453. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/reference-transaction +0 -0
  454. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/extract/spec.md +0 -0
  455. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/regroup/spec.md +0 -0
  456. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/rename/spec.md +0 -0
  457. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/spec.md +0 -0
  458. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/tidy/spec.md +0 -0
  459. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/comment-altitude/spec.md +0 -0
  460. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/idle/idle.sh +0 -0
  461. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/idle/spec.md +0 -0
  462. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/mark-active/mark-active.sh +0 -0
  463. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/mark-active/spec.md +0 -0
  464. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/session-fail/fail.sh +0 -0
  465. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/session-fail/spec.md +0 -0
  466. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-first/spec.md +0 -0
  467. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-of-file/spec.md +0 -0
  468. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec.md +0 -0
  469. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/atomic-landing/spec.md +0 -0
  470. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/forge-link/spec.md +0 -0
  471. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/memory-hygiene/spec.md +0 -0
  472. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/reproduce-before-fix/spec.md +0 -0
  473. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/spec.md +0 -0
  474. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/skills/distill/digest.mjs +0 -0
  475. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/skills/distill/spec.md +0 -0
  476. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/skills/spec.md +0 -0
  477. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/spec.md +0 -0
  478. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/spec.md +0 -0
  479. /package/{spec-cli → node_modules/@spexcode/spec-core}/templates/spexcode.json +0 -0
@@ -1,444 +0,0 @@
1
- import { execFileSync } from 'node:child_process'
2
- import { closeSync, openSync, readFileSync, readSync, readdirSync, statSync } from 'node:fs'
3
- import { homedir } from 'node:os'
4
- import { join } from 'node:path'
5
-
6
- export type ExecutionTurn = Readonly<{
7
- token: string
8
- acceptedAt: string
9
- }>
10
- export type ExecutionStepKind = 'command' | 'read' | 'write' | 'search' | 'tool'
11
- export type ExecutionStep = Readonly<{
12
- id: string
13
- kind: ExecutionStepKind
14
- label: string
15
- detail?: string
16
- state: 'running' | 'done'
17
- }>
18
- export type ExecutionTrace = Readonly<{
19
- revision: string
20
- turnId: string | null
21
- workingNote: string | null
22
- steps: readonly ExecutionStep[]
23
- }>
24
-
25
- type TraceEvent =
26
- | Readonly<{ type: 'boundary'; ids: readonly string[]; at: number | null }>
27
- | Readonly<{ type: 'note'; text: string }>
28
- | Readonly<{ type: 'call'; step: ExecutionStep }>
29
- | Readonly<{ type: 'done'; id: string }>
30
- type CachedTrace = { size: number; remainder: Buffer; events: TraceEvent[] }
31
- type ApplyEvent = (value: unknown, ordinal: number) => readonly TraceEvent[]
32
- type ExportLoader = (threadId: string) => string
33
-
34
- const cache = new Map<string, CachedTrace>()
35
- const sessionJsonlPaths = new Map<string, string>()
36
- const localStoreCache = new Map<string, { revision: string; events: TraceEvent[] }>()
37
-
38
- const emptyTrace = (turn: ExecutionTurn | null, revision = '0'): ExecutionTrace => ({
39
- revision: `${turn?.token ?? '0'}:${revision}`,
40
- turnId: turn?.token ?? null,
41
- workingNote: null,
42
- steps: [],
43
- })
44
-
45
- const object = (value: unknown): Record<string, unknown> | null =>
46
- value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : null
47
- const items = (value: unknown): readonly unknown[] => Array.isArray(value) ? value : []
48
- const text = (value: unknown): string | null => typeof value === 'string' && value.trim() ? value.trim() : null
49
- const field = (value: Record<string, unknown>, ...names: string[]): string | null => {
50
- for (const name of names) {
51
- const found = text(value[name])
52
- if (found) return found
53
- }
54
- return null
55
- }
56
- const children = (dir: string): string[] => { try { return readdirSync(dir).sort().reverse() } catch { return [] } }
57
-
58
- const sensitive = /(token|secret|password|authorization|bearer|cookie|credential|api[_-]?key|private[ _-]?key)/i
59
- const detailFields = [
60
- ['cmd', 'cmd'], ['command', 'command'], ['path', 'path'], ['file_path', 'path'],
61
- ['query', 'query'], ['pattern', 'pattern'], ['url', 'url'], ['workdir', 'in'],
62
- ] as const
63
-
64
- function toolKind(name: string): ExecutionStepKind {
65
- const normalized = name.toLowerCase()
66
- if (/(search|browse|web)/.test(normalized)) return 'search'
67
- if (/(read|list|find|grep|glob|stat)/.test(normalized)) return 'read'
68
- if (/(write|edit|patch|create|delete|move|rename)/.test(normalized)) return 'write'
69
- if (/(exec|shell|command|terminal|bash|npm|git)/.test(normalized)) return 'command'
70
- return 'tool'
71
- }
72
-
73
- function toolLabel(name: string, kind: ExecutionStepKind): string {
74
- const labels: Record<ExecutionStepKind, string> = {
75
- command: 'Run command', read: 'Read files', write: 'Update files', search: 'Search', tool: 'Use tool',
76
- }
77
- return name.trim() || labels[kind]
78
- }
79
-
80
- function recordInput(value: unknown): Record<string, unknown> | null {
81
- if (value && typeof value === 'object' && !Array.isArray(value)) return value as Record<string, unknown>
82
- if (typeof value !== 'string') return null
83
- try {
84
- const parsed = JSON.parse(value)
85
- return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed as Record<string, unknown> : null
86
- } catch { return null }
87
- }
88
-
89
- function sensitiveInput(value: unknown): boolean {
90
- if (typeof value === 'string') return sensitive.test(value)
91
- if (Array.isArray(value)) return value.some(sensitiveInput)
92
- if (value && typeof value === 'object') return Object.entries(value as Record<string, unknown>)
93
- .some(([key, entry]) => sensitive.test(key) || sensitiveInput(entry))
94
- return false
95
- }
96
-
97
- function compactValue(key: string, value: string | number | boolean): string {
98
- const compact = String(value).trim().replace(/\s+/g, ' ')
99
- if (key === 'path' || key === 'file_path' || key === 'workdir') {
100
- const parts = compact.replace(/\\/g, '/').split('/').filter(Boolean)
101
- return parts.slice(-2).join('/') || compact
102
- }
103
- return compact.length > 96 ? `${compact.slice(0, 95)}...` : compact
104
- }
105
-
106
- function detailFromInput(value: unknown): string | undefined {
107
- const input = recordInput(value)
108
- if (!input || sensitiveInput(input)) return undefined
109
- const details: string[] = []
110
- for (const [key, title] of detailFields) {
111
- const value = input[key]
112
- if (typeof value !== 'string' && typeof value !== 'number' && typeof value !== 'boolean') continue
113
- details.push(`${title}: ${compactValue(key, value)}`)
114
- if (details.length === 2) break
115
- }
116
- const lineStart = input.line_start
117
- const lineEnd = input.line_end
118
- if (details.length < 2 && (typeof lineStart === 'number' || typeof lineEnd === 'number')) {
119
- details.push(`lines: ${typeof lineStart === 'number' ? lineStart : 1}-${typeof lineEnd === 'number' ? lineEnd : lineStart}`)
120
- }
121
- return details.length ? details.join(' · ') : undefined
122
- }
123
-
124
- function displayableNote(value: unknown): string | null {
125
- const note = text(value)?.replace(/\s+/g, ' ')
126
- return note ? (note.length > 240 ? `${note.slice(0, 239)}...` : note) : null
127
- }
128
-
129
- function stepFromInput(id: string, name: string, input: unknown, state: ExecutionStep['state']): ExecutionStep {
130
- const kind = toolKind(name)
131
- const detail = detailFromInput(input)
132
- return { id, kind, label: toolLabel(name, kind), ...(detail ? { detail } : {}), state }
133
- }
134
-
135
- function timestamp(value: unknown): number | null {
136
- if (typeof value === 'number' && Number.isFinite(value)) return value
137
- if (typeof value !== 'string' || !value.trim()) return null
138
- const number = Number(value)
139
- if (Number.isFinite(number)) return number
140
- const parsed = Date.parse(value)
141
- return Number.isFinite(parsed) ? parsed : null
142
- }
143
-
144
- function identities(...values: Array<Record<string, unknown> | null>): string[] {
145
- const result = new Set<string>()
146
- for (const value of values) for (const key of ['id', 'uuid', 'message_id', 'messageId', 'client_id', 'clientId', 'client_user_message_id', 'clientUserMessageId', 'promptId']) {
147
- const id = field(value || {}, key)
148
- if (id) result.add(id)
149
- }
150
- return [...result]
151
- }
152
-
153
- function at(value: Record<string, unknown>): number | null {
154
- for (const key of ['timestamp', 'created_at', 'createdAt', 'created', 'time']) {
155
- const found = timestamp(value[key])
156
- if (found !== null) return found
157
- }
158
- return null
159
- }
160
-
161
- function isPrompt(content: unknown): boolean {
162
- if (typeof content === 'string') return !!content.trim()
163
- const blocks = items(content)
164
- return blocks.some((block) => object(block)?.type !== 'tool_result')
165
- }
166
-
167
- function project(events: readonly TraceEvent[], sourceRevision: string, turn: ExecutionTurn | null): ExecutionTrace {
168
- let start = -1
169
- if (!turn) {
170
- for (const [index, event] of events.entries()) if (event.type === 'boundary') start = index
171
- } else {
172
- const acceptedAt = timestamp(turn.acceptedAt)
173
- const exact = events.flatMap((event, index) => event.type === 'boundary' && event.ids.includes(turn.token) ? [index] : [])
174
- start = exact.length === 1 ? exact[0] : -1
175
- if (start < 0 && exact.length === 0 && acceptedAt !== null) {
176
- const candidates = events.flatMap((event, index) => event.type === 'boundary' && event.at !== null && event.at >= acceptedAt
177
- ? [{ index, at: event.at }] : [])
178
- const earliest = candidates.reduce<number | null>((value, candidate) => value === null || candidate.at < value ? candidate.at : value, null)
179
- const matches = earliest === null ? [] : candidates.filter((candidate) => candidate.at === earliest)
180
- if (matches.length === 1) start = matches[0].index
181
- }
182
- }
183
- if (turn && start < 0) return emptyTrace(turn, sourceRevision)
184
-
185
- let note: string | null = null
186
- let steps: ExecutionStep[] = []
187
- for (let index = start + 1; index < events.length; index++) {
188
- const event = events[index]
189
- if (event.type === 'boundary') return emptyTrace(turn, sourceRevision)
190
- if (event.type === 'note') { note = event.text; steps = []; continue }
191
- if (event.type === 'call') {
192
- if (note) steps.push(event.step)
193
- continue
194
- }
195
- const step = steps.find((value) => value.id === event.id)
196
- if (step) steps[steps.indexOf(step)] = { ...step, state: 'done' }
197
- }
198
- return note
199
- ? { revision: `${turn?.token ?? '0'}:${sourceRevision}`, turnId: turn?.token ?? null, workingNote: note, steps }
200
- : emptyTrace(turn, sourceRevision)
201
- }
202
-
203
- function applyRolloutEvent(value: unknown, ordinal: number): readonly TraceEvent[] {
204
- const event = object(value)
205
- const payload = object(event?.payload)
206
- if (!event || !payload) return []
207
- const result: TraceEvent[] = []
208
- const type = field(payload, 'type')
209
- if ((event.type === 'event_msg' && type === 'user_message')
210
- || (event.type === 'response_item' && (type === 'message' || type === 'input_message') && payload.role === 'user')) {
211
- result.push({ type: 'boundary', ids: identities(payload, event), at: at(payload) ?? at(event) })
212
- }
213
- if (event.type === 'event_msg' && type === 'agent_message' && payload.phase === 'commentary') {
214
- const note = displayableNote(payload.message ?? payload.text)
215
- if (note) result.push({ type: 'note', text: note })
216
- }
217
- if (event.type === 'response_item' && (type === 'custom_tool_call' || type === 'function_call')) {
218
- const name = field(payload, 'name', 'tool_name') || 'tool'
219
- const id = field(payload, 'call_id', 'id') || `tool-${ordinal}`
220
- result.push({ type: 'call', step: stepFromInput(id, name, payload.input ?? payload.arguments ?? payload.args, 'running') })
221
- }
222
- if (event.type === 'response_item' && (type === 'custom_tool_call_output' || type === 'function_call_output')) {
223
- const id = field(payload, 'call_id', 'id')
224
- if (id) result.push({ type: 'done', id })
225
- }
226
- if (event.type === 'event_msg' && type === 'web_search_end') {
227
- result.push({ type: 'call', step: stepFromInput(field(payload, 'id', 'call_id') || `search-${ordinal}`, 'Search', payload, 'done') })
228
- }
229
- return result
230
- }
231
-
232
- function applyProjectJsonlEvent(value: unknown, ordinal: number): readonly TraceEvent[] {
233
- const entry = object(value)
234
- const message = object(entry?.message)
235
- if (!entry || !message) return []
236
- const result: TraceEvent[] = []
237
- if (entry.type === 'user' && message.role === 'user' && isPrompt(message.content))
238
- result.push({ type: 'boundary', ids: identities(entry, message), at: at(entry) ?? at(message) })
239
- if (entry.type === 'assistant' && message.role === 'assistant') for (const blockValue of items(message.content)) {
240
- const block = object(blockValue)
241
- if (block?.type === 'text') {
242
- const note = displayableNote(block.text)
243
- if (note) result.push({ type: 'note', text: note })
244
- }
245
- if (block?.type === 'tool_use') {
246
- const id = field(block, 'id') || `tool-${ordinal}`
247
- result.push({ type: 'call', step: stepFromInput(id, field(block, 'name') || 'tool', block.input, 'running') })
248
- }
249
- }
250
- if (entry.type === 'user' && message.role === 'user') for (const blockValue of items(message.content)) {
251
- const block = object(blockValue)
252
- if (block?.type === 'tool_result') {
253
- const id = field(block, 'tool_use_id')
254
- if (id) result.push({ type: 'done', id })
255
- }
256
- }
257
- return result
258
- }
259
-
260
- function applySessionJsonlEvent(value: unknown, ordinal: number): readonly TraceEvent[] {
261
- const entry = object(value)
262
- const message = object(entry?.message)
263
- if (!entry || entry.type !== 'message' || !message) return []
264
- const result: TraceEvent[] = []
265
- if (message.role === 'user' && isPrompt(message.content))
266
- result.push({ type: 'boundary', ids: identities(entry, message), at: at(entry) ?? at(message) })
267
- if (message.role === 'assistant') for (const blockValue of items(message.content)) {
268
- const block = object(blockValue)
269
- if (block?.type === 'text') {
270
- const note = displayableNote(block.text)
271
- if (note) result.push({ type: 'note', text: note })
272
- }
273
- if (block?.type === 'toolCall') {
274
- const id = field(block, 'id') || `tool-${ordinal}`
275
- result.push({ type: 'call', step: stepFromInput(id, field(block, 'name') || 'tool', block.arguments, 'running') })
276
- }
277
- }
278
- if (message.role === 'toolResult') {
279
- const id = field(message, 'toolCallId')
280
- if (id) result.push({ type: 'done', id })
281
- }
282
- return result
283
- }
284
-
285
- function completeLines(buffer: Buffer): { lines: Buffer[]; remainder: Buffer } {
286
- const lines: Buffer[] = []
287
- let start = 0
288
- for (let index = 0; index < buffer.length; index++) {
289
- if (buffer[index] !== 10) continue
290
- if (index > start) lines.push(buffer.subarray(start, index))
291
- start = index + 1
292
- }
293
- return { lines, remainder: buffer.subarray(start) }
294
- }
295
-
296
- function readFrom(path: string, start: number): Buffer | null {
297
- let fd: number | null = null
298
- try {
299
- const size = statSync(path).size
300
- if (size <= start) return Buffer.alloc(0)
301
- const chunk = Buffer.allocUnsafe(size - start)
302
- fd = openSync(path, 'r')
303
- readSync(fd, chunk, 0, chunk.length, start)
304
- return chunk
305
- } catch { return null }
306
- finally { if (fd !== null) closeSync(fd) }
307
- }
308
-
309
- function readIncremental(path: string | null, apply: ApplyEvent, turn: ExecutionTurn | null): ExecutionTrace {
310
- if (!path) return emptyTrace(turn)
311
- let size: number
312
- try { size = statSync(path).size } catch { return emptyTrace(turn) }
313
- const prior = cache.get(path)
314
- const trace = prior && prior.size <= size
315
- ? { ...prior, events: [...prior.events] }
316
- : { size: 0, remainder: Buffer.alloc(0), events: [] as TraceEvent[] }
317
- const appended = readFrom(path, trace.size)
318
- if (appended === null) return emptyTrace(turn)
319
- const { lines, remainder } = completeLines(Buffer.concat([trace.remainder, appended]))
320
- for (const [index, line] of lines.entries()) {
321
- try { trace.events.push(...apply(JSON.parse(line.toString('utf8')), trace.size + index)) } catch { /* malformed native data is ignored */ }
322
- }
323
- trace.size = size
324
- trace.remainder = remainder
325
- cache.set(path, trace)
326
- return project(trace.events, String(size), turn)
327
- }
328
-
329
- const codexSessionsDir = () => join(process.env.CODEX_HOME || join(homedir(), '.codex'), 'sessions')
330
-
331
- export function codexRolloutPath(threadId: string, root = codexSessionsDir()): string | null {
332
- for (const year of children(root)) for (const month of children(join(root, year))) for (const day of children(join(root, year, month))) {
333
- const dir = join(root, year, month, day)
334
- const file = children(dir).find((name) => name.includes(threadId))
335
- if (file) return join(dir, file)
336
- }
337
- return null
338
- }
339
-
340
- export function readCodexExecutionTrace(threadId: string, turn: ExecutionTurn | null, root = codexSessionsDir()): ExecutionTrace {
341
- return readIncremental(codexRolloutPath(threadId, root), applyRolloutEvent, turn)
342
- }
343
-
344
- const projectTranscriptRoot = () => join(process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude'), 'projects')
345
-
346
- function projectJsonlPath(threadId: string, root = projectTranscriptRoot()): string | null {
347
- for (const project of children(root)) {
348
- const path = join(root, project, `${threadId}.jsonl`)
349
- try { if (statSync(path).isFile()) return path } catch { /* try next */ }
350
- }
351
- return null
352
- }
353
-
354
- export function readProjectJsonlExecutionTrace(threadId: string, turn: ExecutionTurn | null, root = projectTranscriptRoot()): ExecutionTrace {
355
- return readIncremental(projectJsonlPath(threadId, root), applyProjectJsonlEvent, turn)
356
- }
357
-
358
- const sessionJsonlRoot = () => join(process.env.SPEXCODE_PI_AGENT_DIR || join(homedir(), '.pi', 'agent'), 'sessions')
359
-
360
- function sessionJsonlPath(threadId: string, root = sessionJsonlRoot()): string | null {
361
- const key = `${root}:${threadId}`
362
- const cached = sessionJsonlPaths.get(key)
363
- if (cached) {
364
- try { if (statSync(cached).isFile()) return cached } catch { sessionJsonlPaths.delete(key) }
365
- }
366
- for (const directory of children(root)) for (const file of children(join(root, directory))) {
367
- if (!file.endsWith('.jsonl')) continue
368
- const path = join(root, directory, file)
369
- try {
370
- const header = object(JSON.parse(readFileSync(path, 'utf8').split('\n', 1)[0]))
371
- if (header?.type === 'session' && header.id === threadId) {
372
- sessionJsonlPaths.set(key, path)
373
- return path
374
- }
375
- } catch { /* unreadable entries are not a match */ }
376
- }
377
- return null
378
- }
379
-
380
- export function readSessionJsonlExecutionTrace(threadId: string, turn: ExecutionTurn | null, root = sessionJsonlRoot()): ExecutionTrace {
381
- return readIncremental(sessionJsonlPath(threadId, root), applySessionJsonlEvent, turn)
382
- }
383
-
384
- const localStoreRoot = () => process.env.SPEXCODE_OPENCODE_DATA_DIR
385
- || join(process.env.XDG_DATA_HOME || join(homedir(), '.local', 'share'), 'opencode')
386
-
387
- function readLocalExport(threadId: string): string {
388
- return execFileSync(process.env.SPEXCODE_OPENCODE_CMD || 'opencode', ['export', threadId, '--sanitize'], {
389
- encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'],
390
- })
391
- }
392
-
393
- function localStoreRevision(root: string): string | null {
394
- try {
395
- const database = statSync(join(root, 'opencode.db'))
396
- let writeAheadLog = '0:0'
397
- try {
398
- const stat = statSync(join(root, 'opencode.db-wal'))
399
- writeAheadLog = `${stat.size}:${Math.floor(stat.mtimeMs)}`
400
- } catch { /* a checkpointed database has no separate write-ahead log */ }
401
- return `${database.size}:${Math.floor(database.mtimeMs)}:${writeAheadLog}`
402
- } catch { return null }
403
- }
404
-
405
- function localStoreEvents(value: unknown): TraceEvent[] {
406
- const events: TraceEvent[] = []
407
- for (const messageValue of items(object(value)?.messages)) {
408
- const message = object(messageValue)
409
- const info = object(message?.info)
410
- if (!message || !info) continue
411
- if (info.role === 'user') events.push({ type: 'boundary', ids: identities(info, message), at: at(object(info.time) || info) })
412
- if (info.role !== 'assistant') continue
413
- for (const partValue of items(message.parts)) {
414
- const part = object(partValue)
415
- if (part?.type === 'text') {
416
- const note = displayableNote(part.text)
417
- if (note) events.push({ type: 'note', text: note })
418
- }
419
- if (part?.type === 'tool') {
420
- const state = object(part.state)
421
- const status = field(state || {}, 'status')?.toLowerCase() || ''
422
- const id = field(part, 'callID', 'id') || `tool-${events.length}`
423
- events.push({ type: 'call', step: stepFromInput(id, field(part, 'tool') || 'tool', state?.input, /completed|error|cancelled/.test(status) ? 'done' : 'running') })
424
- }
425
- }
426
- }
427
- return events
428
- }
429
-
430
- export function readLocalStoreExecutionTrace(threadId: string, turn: ExecutionTurn | null, root = localStoreRoot(), load: ExportLoader = readLocalExport): ExecutionTrace {
431
- const revision = localStoreRevision(root)
432
- if (!revision) return emptyTrace(turn)
433
- const key = `${root}:${threadId}`
434
- let cached = localStoreCache.get(key)
435
- if (!cached || cached.revision !== revision) {
436
- try {
437
- cached = { revision, events: localStoreEvents(JSON.parse(load(threadId))) }
438
- localStoreCache.set(key, cached)
439
- } catch { return emptyTrace(turn, revision) }
440
- }
441
- return project(cached.events, revision, turn)
442
- }
443
-
444
- export const noExecutionTrace = (_threadId: string, _turn: ExecutionTurn | null): ExecutionTrace | null => null
@@ -1,22 +0,0 @@
1
- import { copyFileSync, readFileSync, writeFileSync } from 'node:fs'
2
-
3
- export function writeFileIfChanged(path: string, content: string | Uint8Array): boolean {
4
- const next = Buffer.from(content)
5
- try {
6
- if (readFileSync(path).equals(next)) return false
7
- } catch (error: any) {
8
- if (error?.code !== 'ENOENT') throw error
9
- }
10
- writeFileSync(path, content)
11
- return true
12
- }
13
-
14
- export function copyFileIfChanged(source: string, target: string): boolean {
15
- try {
16
- if (readFileSync(target).equals(readFileSync(source))) return false
17
- } catch (error: any) {
18
- if (error?.code !== 'ENOENT') throw error
19
- }
20
- copyFileSync(source, target)
21
- return true
22
- }
@@ -1,181 +0,0 @@
1
- import { createHmac, createHash, randomBytes, scryptSync, timingSafeEqual } from 'node:crypto'
2
- import { chmodSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'
3
- import { dirname, join } from 'node:path'
4
- import { spexcodeHome } from './layout.js'
5
-
6
- export type Verifier = { algo: 'scrypt'; N: number; r: number; p: number; salt: string; hash: string; gen: string }
7
- export type AuthStore = { v: 1; secret: string; admin?: Verifier; projects: Record<string, Verifier> }
8
- export type Scope = { s: 'admin' } | { s: 'project'; p: string }
9
- export type Claims = { v: 1; s: 'admin' | 'project'; p?: string; g: string; t: number }
10
- export type Decision =
11
- | { ok: true; via: 'admin' | 'project' | 'open' | 'loopback' }
12
- | { ok: false; reason: 'admin-login' | 'locked' | 'project-login' }
13
-
14
- const TOKEN_TTL_MS = 30 * 24 * 3600 * 1000 // matches the single-project gateway's 30-day cookie
15
- const SCRYPT = { N: 16384, r: 8, p: 1 } as const
16
-
17
- // ---- the private per-user store --------------------------------------------------------------------
18
-
19
- export function authStorePath(): string {
20
- return join(spexcodeHome(), 'gateway', 'auth.json')
21
- }
22
-
23
- // first load creates the store (and its random signing secret) so sessions survive restarts; the file is
24
- // 0600 in a 0700 dir — this is a per-USER secret store, no other principal has business reading it.
25
- export function loadAuthStore(): AuthStore {
26
- try {
27
- const raw = JSON.parse(readFileSync(authStorePath(), 'utf8'))
28
- if (raw?.v === 1 && typeof raw.secret === 'string' && raw.secret) return { v: 1, secret: raw.secret, admin: raw.admin, projects: raw.projects ?? {} }
29
- } catch { /* absent or unreadable → fresh below; malformed secrets must not be half-trusted */ }
30
- const fresh: AuthStore = { v: 1, secret: randomBytes(32).toString('base64url'), projects: {} }
31
- saveAuthStore(fresh)
32
- return fresh
33
- }
34
-
35
- function saveAuthStore(store: AuthStore): void {
36
- const file = authStorePath()
37
- mkdirSync(dirname(file), { recursive: true, mode: 0o700 })
38
- const tmp = `${file}.tmp`
39
- writeFileSync(tmp, JSON.stringify(store, null, 2) + '\n', { mode: 0o600 })
40
- renameSync(tmp, file)
41
- chmodSync(file, 0o600) // rename preserves the tmp mode, but never let a pre-existing looser file win
42
- }
43
-
44
- // ---- password verifiers (scrypt, salted, constant-time) --------------------------------------------
45
-
46
- export function makeVerifier(password: string): Verifier {
47
- const salt = randomBytes(16)
48
- const hash = scryptSync(password, salt, 32, SCRYPT)
49
- return { algo: 'scrypt', ...SCRYPT, salt: salt.toString('base64url'), hash: hash.toString('base64url'), gen: randomBytes(8).toString('base64url') }
50
- }
51
-
52
- export function verifyPassword(v: Verifier | undefined, password: string): boolean {
53
- if (!v || v.algo !== 'scrypt' || typeof password !== 'string') return false
54
- const got = scryptSync(password, Buffer.from(v.salt, 'base64url'), 32, { N: v.N, r: v.r, p: v.p })
55
- return constEq(got, Buffer.from(v.hash, 'base64url'))
56
- }
57
-
58
- function constEq(a: Buffer | string, b: Buffer | string): boolean {
59
- const ab = Buffer.isBuffer(a) ? a : Buffer.from(a)
60
- const bb = Buffer.isBuffer(b) ? b : Buffer.from(b)
61
- return ab.length === bb.length && timingSafeEqual(ab, bb)
62
- }
63
-
64
- // ---- verifier management (the gateway admin APIs write through these) ------------------------------
65
-
66
- export function setAdminPassword(password: string): AuthStore {
67
- const store = loadAuthStore()
68
- store.admin = makeVerifier(password)
69
- saveAuthStore(store)
70
- return store
71
- }
72
- export function clearAdminPassword(): AuthStore {
73
- const store = loadAuthStore()
74
- delete store.admin
75
- saveAuthStore(store)
76
- return store
77
- }
78
- export function setProjectPassword(projectId: string, password: string): AuthStore {
79
- const store = loadAuthStore()
80
- store.projects[projectId] = makeVerifier(password)
81
- saveAuthStore(store)
82
- return store
83
- }
84
- export function clearProjectPassword(projectId: string): AuthStore {
85
- const store = loadAuthStore()
86
- delete store.projects[projectId]
87
- saveAuthStore(store)
88
- return store
89
- }
90
-
91
- // ---- signed session tokens --------------------------------------------------------------------------
92
-
93
- function sign(secret: string, payload: string): string {
94
- return createHmac('sha256', Buffer.from(secret, 'base64url')).update(payload).digest('base64url')
95
- }
96
-
97
- // a token exists only for a scope that has a verifier — its `g` claim is that verifier's current gen.
98
- export function mintToken(store: AuthStore, scope: Scope, nowMs = Date.now()): string {
99
- const v = scope.s === 'admin' ? store.admin : store.projects[scope.p]
100
- if (!v) throw new Error(`gateway-auth: cannot mint a ${scope.s} token with no verifier configured`)
101
- const claims: Claims = { v: 1, s: scope.s, ...(scope.s === 'project' ? { p: scope.p } : {}), g: v.gen, t: nowMs }
102
- const payload = Buffer.from(JSON.stringify(claims)).toString('base64url')
103
- return `${payload}.${sign(store.secret, payload)}`
104
- }
105
-
106
- // full validity lives here: signature (constant-time), shape, expiry, and the gen check against the
107
- // CURRENT verifier — a cleared or re-set password leaves no valid tokens behind.
108
- export function verifyToken(store: AuthStore, token: string, nowMs = Date.now()): Claims | null {
109
- const dot = token.indexOf('.')
110
- if (dot <= 0) return null
111
- const payload = token.slice(0, dot), sig = token.slice(dot + 1)
112
- if (!constEq(sig, sign(store.secret, payload))) return null
113
- let claims: Claims
114
- try { claims = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8')) } catch { return null }
115
- if (claims?.v !== 1 || typeof claims.t !== 'number' || typeof claims.g !== 'string') return null
116
- if (nowMs > claims.t + TOKEN_TTL_MS || claims.t > nowMs + 60_000) return null
117
- if (claims.s === 'admin') return store.admin && constEq(claims.g, store.admin.gen) ? claims : null
118
- if (claims.s === 'project' && typeof claims.p === 'string') {
119
- const v = store.projects[claims.p]
120
- return v && constEq(claims.g, v.gen) ? claims : null
121
- }
122
- return null
123
- }
124
-
125
- // ---- cookies ----------------------------------------------------------------------------------------
126
- // Cookie names are keyed by the gateway's public port (cookies are host-scoped; the port is the
127
- // discriminator — same rationale as the single-project gateway) and, for project scopes, by a hash of the
128
- // projectId (ids are path-derived and may hold non-token chars). The NAME is only a mailbox: authorization
129
- // always re-validates the token's own projectId claim against the requested route — never the cookie's
130
- // name, and never its Path attribute, both of which are client-controlled.
131
-
132
- export function adminCookieName(port: number): string { return `spex_admin_${port}` }
133
- export function projectCookieName(port: number, projectId: string): string {
134
- return `spex_proj_${port}_${createHash('sha256').update(projectId).digest('hex').slice(0, 12)}`
135
- }
136
-
137
- export function cookieOf(header: string | undefined, name: string): string | null {
138
- for (const part of (header ?? '').split(';')) {
139
- const eq = part.indexOf('=')
140
- if (eq > 0 && part.slice(0, eq).trim() === name) return decodeURIComponent(part.slice(eq + 1).trim())
141
- }
142
- return null
143
- }
144
-
145
- // ---- loopback ---------------------------------------------------------------------------------------
146
- // The implicit-admin decision reads the SOCKET's remote address only. Never a header (X-Forwarded-For,
147
- // X-Real-IP): headers are attacker-controlled, and this gateway is designed to face the internet directly,
148
- // not to sit behind a trusted proxy.
149
- export function isLoopback(addr: string | null | undefined): boolean {
150
- if (!addr) return false
151
- const a = addr.startsWith('::ffff:') ? addr.slice(7) : addr
152
- return a === '::1' || /^127(\.\d{1,3}){3}$/.test(a)
153
- }
154
-
155
- // ---- the decision -----------------------------------------------------------------------------------
156
-
157
- export type Route = { kind: 'admin' } | { kind: 'project'; projectId: string }
158
-
159
- export function authorize(store: AuthStore, route: Route, cookieHeader: string | undefined, remoteAddr: string | null | undefined, port: number): Decision {
160
- const adminTok = cookieOf(cookieHeader, adminCookieName(port))
161
- const adminClaims = adminTok ? verifyToken(store, adminTok) : null
162
- const adminOk = adminClaims?.s === 'admin'
163
-
164
- if (route.kind === 'admin') {
165
- if (adminOk) return { ok: true, via: 'admin' }
166
- // no admin verifier configured: loopback manages implicitly (the bootstrap path — set the first
167
- // password from the machine itself); anyone else finds /projects locked, not open.
168
- if (!store.admin) return isLoopback(remoteAddr) ? { ok: true, via: 'loopback' } : { ok: false, reason: 'locked' }
169
- return { ok: false, reason: 'admin-login' }
170
- }
171
-
172
- // project route — admin reaches every project; then the project's own gate decides.
173
- if (adminOk) return { ok: true, via: 'admin' }
174
- if (!store.projects[route.projectId]) return { ok: true, via: 'open' }
175
- const tok = cookieOf(cookieHeader, projectCookieName(port, route.projectId))
176
- const claims = tok ? verifyToken(store, tok) : null
177
- // the projectId CLAIM must match the route — presenting project A's token under project B's cookie
178
- // name (or any Path trick) authorizes nothing.
179
- if (claims?.s === 'project' && claims.p === route.projectId) return { ok: true, via: 'project' }
180
- return { ok: false, reason: 'project-login' }
181
- }