forgeos 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (540) hide show
  1. package/.npmignore +1 -0
  2. package/AGENTS.md +277 -0
  3. package/CHANGELOG.md +8 -0
  4. package/CONTRIBUTING.md +58 -0
  5. package/README.md +377 -0
  6. package/bin/forge-bun.mjs +110 -0
  7. package/bin/forge.mjs +19 -0
  8. package/package.json +96 -0
  9. package/packages/eslint-plugin-forge/index.ts +15 -0
  10. package/packages/eslint-plugin-forge/package.json +10 -0
  11. package/packages/eslint-plugin-forge/src/check-source.ts +95 -0
  12. package/packages/eslint-plugin-forge/src/load-artifacts.ts +24 -0
  13. package/packages/eslint-plugin-forge/src/rule-no-forge-guard-violation.ts +93 -0
  14. package/src/forge/_generated/actionSubscriptions.json +2 -0
  15. package/src/forge/_generated/actionSubscriptions.ts +10 -0
  16. package/src/forge/_generated/agentAdapterManifest.json +2 -0
  17. package/src/forge/_generated/agentAdapterManifest.ts +73 -0
  18. package/src/forge/_generated/agentContract.json +2 -0
  19. package/src/forge/_generated/agentContract.ts +912 -0
  20. package/src/forge/_generated/agentQuickstart.md +32 -0
  21. package/src/forge/_generated/aiContext.ts +59 -0
  22. package/src/forge/_generated/aiModels.json +2 -0
  23. package/src/forge/_generated/aiModels.ts +35 -0
  24. package/src/forge/_generated/aiProviders.json +2 -0
  25. package/src/forge/_generated/aiProviders.ts +23 -0
  26. package/src/forge/_generated/aiRegistry.json +2 -0
  27. package/src/forge/_generated/aiRegistry.ts +29 -0
  28. package/src/forge/_generated/api.json +2 -0
  29. package/src/forge/_generated/api.ts +8 -0
  30. package/src/forge/_generated/appGraph.json +2 -0
  31. package/src/forge/_generated/appGraph.ts +14511 -0
  32. package/src/forge/_generated/appMap.md +35 -0
  33. package/src/forge/_generated/artifactManifest.json +2 -0
  34. package/src/forge/_generated/artifactManifest.ts +7 -0
  35. package/src/forge/_generated/authClaims.json +2 -0
  36. package/src/forge/_generated/authClaims.ts +13 -0
  37. package/src/forge/_generated/authConfig.json +2 -0
  38. package/src/forge/_generated/authConfig.ts +17 -0
  39. package/src/forge/_generated/authContext.ts +23 -0
  40. package/src/forge/_generated/authRegistry.json +2 -0
  41. package/src/forge/_generated/authRegistry.ts +25 -0
  42. package/src/forge/_generated/buildInfo.json +2 -0
  43. package/src/forge/_generated/buildInfo.ts +9 -0
  44. package/src/forge/_generated/capabilityMap.json +2 -0
  45. package/src/forge/_generated/capabilityMap.md +15 -0
  46. package/src/forge/_generated/capabilityMap.ts +17 -0
  47. package/src/forge/_generated/client.ts +282 -0
  48. package/src/forge/_generated/clientApi.ts +9 -0
  49. package/src/forge/_generated/clientManifest.json +2 -0
  50. package/src/forge/_generated/clientManifest.ts +39 -0
  51. package/src/forge/_generated/clientTypes.ts +78 -0
  52. package/src/forge/_generated/configRegistry.json +2 -0
  53. package/src/forge/_generated/configRegistry.ts +4 -0
  54. package/src/forge/_generated/dataGraph.json +2 -0
  55. package/src/forge/_generated/dataGraph.ts +8 -0
  56. package/src/forge/_generated/db.json +2 -0
  57. package/src/forge/_generated/db.ts +2 -0
  58. package/src/forge/_generated/dbSecurityManifest.json +2 -0
  59. package/src/forge/_generated/dbSecurityManifest.ts +15 -0
  60. package/src/forge/_generated/dbSessionContext.json +2 -0
  61. package/src/forge/_generated/dbSessionContext.ts +39 -0
  62. package/src/forge/_generated/deployManifest.json +2 -0
  63. package/src/forge/_generated/deployManifest.ts +14 -0
  64. package/src/forge/_generated/devManifest.json +2 -0
  65. package/src/forge/_generated/devManifest.ts +47 -0
  66. package/src/forge/_generated/envSchema.json +2 -0
  67. package/src/forge/_generated/envSchema.ts +59 -0
  68. package/src/forge/_generated/frontendGraph.json +2 -0
  69. package/src/forge/_generated/frontendGraph.ts +27 -0
  70. package/src/forge/_generated/importGuards.json +2 -0
  71. package/src/forge/_generated/importGuards.ts +652 -0
  72. package/src/forge/_generated/index.ts +67 -0
  73. package/src/forge/_generated/liveProductionManifest.json +2 -0
  74. package/src/forge/_generated/liveProductionManifest.ts +23 -0
  75. package/src/forge/_generated/liveProtocol.json +2 -0
  76. package/src/forge/_generated/liveProtocol.ts +21 -0
  77. package/src/forge/_generated/liveQueryRegistry.json +2 -0
  78. package/src/forge/_generated/liveQueryRegistry.ts +9 -0
  79. package/src/forge/_generated/liveTransportConfig.json +2 -0
  80. package/src/forge/_generated/liveTransportConfig.ts +19 -0
  81. package/src/forge/_generated/makeRegistry.json +2 -0
  82. package/src/forge/_generated/makeRegistry.ts +163 -0
  83. package/src/forge/_generated/makeTemplates.json +2 -0
  84. package/src/forge/_generated/makeTemplates.ts +61 -0
  85. package/src/forge/_generated/mockMap.json +2 -0
  86. package/src/forge/_generated/mockMap.ts +7 -0
  87. package/src/forge/_generated/operationPlaybooks.md +145 -0
  88. package/src/forge/_generated/packageGraph.json +2 -0
  89. package/src/forge/_generated/packageGraph.ts +168569 -0
  90. package/src/forge/_generated/packageUpgradeRegistry.json +2 -0
  91. package/src/forge/_generated/packageUpgradeRegistry.ts +15 -0
  92. package/src/forge/_generated/permissionMatrix.json +2 -0
  93. package/src/forge/_generated/permissionMatrix.ts +7 -0
  94. package/src/forge/_generated/policyRegistry.json +2 -0
  95. package/src/forge/_generated/policyRegistry.ts +11 -0
  96. package/src/forge/_generated/queryRegistry.json +2 -0
  97. package/src/forge/_generated/queryRegistry.ts +9 -0
  98. package/src/forge/_generated/react.d.ts +22 -0
  99. package/src/forge/_generated/react.ts +29 -0
  100. package/src/forge/_generated/reactManifest.json +2 -0
  101. package/src/forge/_generated/reactManifest.ts +19 -0
  102. package/src/forge/_generated/releaseManifest.json +2 -0
  103. package/src/forge/_generated/releaseManifest.ts +25 -0
  104. package/src/forge/_generated/rlsPolicies.json +2 -0
  105. package/src/forge/_generated/rlsPolicies.sql +34 -0
  106. package/src/forge/_generated/rlsPolicies.ts +6 -0
  107. package/src/forge/_generated/runtimeGraph.json +2 -0
  108. package/src/forge/_generated/runtimeGraph.ts +8 -0
  109. package/src/forge/_generated/runtimeMatrix.json +2 -0
  110. package/src/forge/_generated/runtimeMatrix.ts +229125 -0
  111. package/src/forge/_generated/runtimeRegistry.ts +2 -0
  112. package/src/forge/_generated/runtimeRules.md +79 -0
  113. package/src/forge/_generated/secretRegistry.json +2 -0
  114. package/src/forge/_generated/secretRegistry.ts +50 -0
  115. package/src/forge/_generated/secretsContext.ts +11 -0
  116. package/src/forge/_generated/serverApi.ts +10 -0
  117. package/src/forge/_generated/sourceMapManifest.json +2 -0
  118. package/src/forge/_generated/sourceMapManifest.ts +7 -0
  119. package/src/forge/_generated/sqlPlan.json +2 -0
  120. package/src/forge/_generated/sqlPlan.ts +88 -0
  121. package/src/forge/_generated/subscriptionManifest.json +2 -0
  122. package/src/forge/_generated/subscriptionManifest.ts +7 -0
  123. package/src/forge/_generated/symbolicationManifest.json +2 -0
  124. package/src/forge/_generated/symbolicationManifest.ts +17 -0
  125. package/src/forge/_generated/telemetryRegistry.json +2 -0
  126. package/src/forge/_generated/telemetryRegistry.ts +9 -0
  127. package/src/forge/_generated/telemetrySinks.json +2 -0
  128. package/src/forge/_generated/telemetrySinks.ts +11 -0
  129. package/src/forge/_generated/tenantScope.json +2 -0
  130. package/src/forge/_generated/tenantScope.ts +8 -0
  131. package/src/forge/_generated/testGraph.json +2 -0
  132. package/src/forge/_generated/testGraph.ts +3054 -0
  133. package/src/forge/_generated/testPlanRegistry.json +2 -0
  134. package/src/forge/_generated/testPlanRegistry.ts +33 -0
  135. package/src/forge/_generated/uiRoutes.json +2 -0
  136. package/src/forge/_generated/uiRoutes.ts +16 -0
  137. package/src/forge/_generated/uiScenarios.json +2 -0
  138. package/src/forge/_generated/uiScenarios.ts +30 -0
  139. package/src/forge/_generated/uiTestManifest.json +2 -0
  140. package/src/forge/_generated/uiTestManifest.ts +27 -0
  141. package/src/forge/_generated/workflowRegistry.json +2 -0
  142. package/src/forge/_generated/workflowRegistry.ts +9 -0
  143. package/src/forge/_generated/workflowSubscriptions.json +2 -0
  144. package/src/forge/_generated/workflowSubscriptions.ts +10 -0
  145. package/src/forge/agent-adapters/index.ts +1002 -0
  146. package/src/forge/agent-adapters/types.ts +135 -0
  147. package/src/forge/cli/agent-contract.ts +50 -0
  148. package/src/forge/cli/ai.ts +148 -0
  149. package/src/forge/cli/auth.ts +198 -0
  150. package/src/forge/cli/build.ts +105 -0
  151. package/src/forge/cli/bun-exec.ts +4 -0
  152. package/src/forge/cli/commands.ts +1130 -0
  153. package/src/forge/cli/db.ts +316 -0
  154. package/src/forge/cli/deps.ts +277 -0
  155. package/src/forge/cli/dev.ts +529 -0
  156. package/src/forge/cli/doctor.ts +209 -0
  157. package/src/forge/cli/feature.ts +485 -0
  158. package/src/forge/cli/index.ts +25 -0
  159. package/src/forge/cli/lint-forge.ts +119 -0
  160. package/src/forge/cli/live.ts +179 -0
  161. package/src/forge/cli/main.ts +92 -0
  162. package/src/forge/cli/make.ts +133 -0
  163. package/src/forge/cli/new.ts +505 -0
  164. package/src/forge/cli/outbox.ts +297 -0
  165. package/src/forge/cli/output.ts +114 -0
  166. package/src/forge/cli/parse.ts +2211 -0
  167. package/src/forge/cli/policy.ts +204 -0
  168. package/src/forge/cli/query.ts +91 -0
  169. package/src/forge/cli/refactor.ts +221 -0
  170. package/src/forge/cli/release.ts +285 -0
  171. package/src/forge/cli/rls.ts +322 -0
  172. package/src/forge/cli/run.ts +76 -0
  173. package/src/forge/cli/secrets.ts +274 -0
  174. package/src/forge/cli/self-host.ts +468 -0
  175. package/src/forge/cli/serve.ts +93 -0
  176. package/src/forge/cli/telemetry.ts +219 -0
  177. package/src/forge/cli/verify.ts +587 -0
  178. package/src/forge/cli/version.ts +1 -0
  179. package/src/forge/cli/windows.ts +413 -0
  180. package/src/forge/cli/worker.ts +87 -0
  181. package/src/forge/cli/workflow.ts +424 -0
  182. package/src/forge/compiler/action-subscriptions/build.ts +116 -0
  183. package/src/forge/compiler/action-subscriptions/constants.ts +2 -0
  184. package/src/forge/compiler/action-subscriptions/index.ts +6 -0
  185. package/src/forge/compiler/action-subscriptions/parse.ts +6 -0
  186. package/src/forge/compiler/agent-contract/build.ts +1651 -0
  187. package/src/forge/compiler/agent-contract/types.ts +326 -0
  188. package/src/forge/compiler/ai-registry/build.ts +165 -0
  189. package/src/forge/compiler/ai-registry/constants.ts +2 -0
  190. package/src/forge/compiler/ai-registry/parse.ts +56 -0
  191. package/src/forge/compiler/api-surface/build.ts +107 -0
  192. package/src/forge/compiler/app-graph/build.ts +121 -0
  193. package/src/forge/compiler/app-graph/classify.ts +10 -0
  194. package/src/forge/compiler/app-graph/dup-symbol.ts +29 -0
  195. package/src/forge/compiler/app-graph/extract.ts +124 -0
  196. package/src/forge/compiler/app-graph/forge-apis.ts +29 -0
  197. package/src/forge/compiler/app-graph/index.ts +15 -0
  198. package/src/forge/compiler/app-graph/module-graph.ts +320 -0
  199. package/src/forge/compiler/app-graph/parser.ts +119 -0
  200. package/src/forge/compiler/app-graph/symbols.ts +48 -0
  201. package/src/forge/compiler/app-graph/tsconfig-hash.ts +62 -0
  202. package/src/forge/compiler/app-graph/types.ts +43 -0
  203. package/src/forge/compiler/app-graph/versions.ts +14 -0
  204. package/src/forge/compiler/cache/index.ts +17 -0
  205. package/src/forge/compiler/cache/key.ts +46 -0
  206. package/src/forge/compiler/cache/scheduler.ts +72 -0
  207. package/src/forge/compiler/cache/store.ts +78 -0
  208. package/src/forge/compiler/classifier/capabilities.ts +78 -0
  209. package/src/forge/compiler/classifier/classify.ts +113 -0
  210. package/src/forge/compiler/classifier/contexts.ts +188 -0
  211. package/src/forge/compiler/classifier/index.ts +18 -0
  212. package/src/forge/compiler/classifier/runtime-matrix.ts +45 -0
  213. package/src/forge/compiler/classifier/secrets.ts +41 -0
  214. package/src/forge/compiler/classifier/signals.ts +129 -0
  215. package/src/forge/compiler/client-sdk/build-manifest.ts +151 -0
  216. package/src/forge/compiler/client-sdk/render-client.ts +432 -0
  217. package/src/forge/compiler/data-graph/build.ts +131 -0
  218. package/src/forge/compiler/data-graph/constants.ts +5 -0
  219. package/src/forge/compiler/data-graph/index.ts +6 -0
  220. package/src/forge/compiler/data-graph/parse.ts +176 -0
  221. package/src/forge/compiler/data-graph/rls/build.ts +222 -0
  222. package/src/forge/compiler/data-graph/rls/types.ts +62 -0
  223. package/src/forge/compiler/data-graph/sql/ddl.ts +390 -0
  224. package/src/forge/compiler/data-graph/sql/naming.ts +10 -0
  225. package/src/forge/compiler/data-graph/sql/serialize.ts +85 -0
  226. package/src/forge/compiler/data-graph/sql/types.ts +37 -0
  227. package/src/forge/compiler/dev-manifest/build.ts +170 -0
  228. package/src/forge/compiler/dev-manifest/constants.ts +5 -0
  229. package/src/forge/compiler/diagnostics/codes.ts +611 -0
  230. package/src/forge/compiler/diagnostics/create.ts +245 -0
  231. package/src/forge/compiler/diagnostics/index.ts +55 -0
  232. package/src/forge/compiler/emitter/artifact-kind.ts +14 -0
  233. package/src/forge/compiler/emitter/barrel.ts +44 -0
  234. package/src/forge/compiler/emitter/constants.ts +7 -0
  235. package/src/forge/compiler/emitter/emit.ts +237 -0
  236. package/src/forge/compiler/emitter/index.ts +24 -0
  237. package/src/forge/compiler/emitter/lock.ts +62 -0
  238. package/src/forge/compiler/emitter/render.ts +73 -0
  239. package/src/forge/compiler/emitter/write.ts +35 -0
  240. package/src/forge/compiler/frontend-graph/build.ts +495 -0
  241. package/src/forge/compiler/fs/index.ts +23 -0
  242. package/src/forge/compiler/fs/memory.ts +233 -0
  243. package/src/forge/compiler/fs/node.ts +139 -0
  244. package/src/forge/compiler/fs/profile.ts +108 -0
  245. package/src/forge/compiler/fs/types.ts +52 -0
  246. package/src/forge/compiler/guards/artifacts.ts +96 -0
  247. package/src/forge/compiler/guards/check-ai-usage.ts +98 -0
  248. package/src/forge/compiler/guards/check-import-guards.ts +106 -0
  249. package/src/forge/compiler/guards/check-process-env.ts +98 -0
  250. package/src/forge/compiler/guards/check-query-usage.ts +76 -0
  251. package/src/forge/compiler/guards/index.ts +11 -0
  252. package/src/forge/compiler/guards/propagate-contexts.ts +57 -0
  253. package/src/forge/compiler/index.ts +17 -0
  254. package/src/forge/compiler/integration/add.ts +496 -0
  255. package/src/forge/compiler/integration/index.ts +17 -0
  256. package/src/forge/compiler/integration/plan.ts +283 -0
  257. package/src/forge/compiler/integration/render.ts +189 -0
  258. package/src/forge/compiler/integration/snapshot.ts +52 -0
  259. package/src/forge/compiler/integration/templates/ai.ts +131 -0
  260. package/src/forge/compiler/integration/templates/index.ts +8 -0
  261. package/src/forge/compiler/integration/templates/posthog.ts +145 -0
  262. package/src/forge/compiler/integration/templates/render.ts +113 -0
  263. package/src/forge/compiler/integration/templates/sentry.ts +151 -0
  264. package/src/forge/compiler/integration/templates/stripe.ts +109 -0
  265. package/src/forge/compiler/integration/templates/types.ts +14 -0
  266. package/src/forge/compiler/integration/templates/zod.ts +55 -0
  267. package/src/forge/compiler/live-production/types.ts +122 -0
  268. package/src/forge/compiler/live-query-registry/build.ts +150 -0
  269. package/src/forge/compiler/live-query-registry/constants.ts +2 -0
  270. package/src/forge/compiler/make-registry/build.ts +179 -0
  271. package/src/forge/compiler/orchestrator/discover.ts +214 -0
  272. package/src/forge/compiler/orchestrator/fast-check.ts +117 -0
  273. package/src/forge/compiler/orchestrator/generate-lock.ts +138 -0
  274. package/src/forge/compiler/orchestrator/guards.ts +5 -0
  275. package/src/forge/compiler/orchestrator/index.ts +27 -0
  276. package/src/forge/compiler/orchestrator/manifest-hashes.ts +21 -0
  277. package/src/forge/compiler/orchestrator/manifest.ts +92 -0
  278. package/src/forge/compiler/orchestrator/orphans.ts +51 -0
  279. package/src/forge/compiler/orchestrator/plan.ts +876 -0
  280. package/src/forge/compiler/orchestrator/profile.ts +36 -0
  281. package/src/forge/compiler/orchestrator/run.ts +277 -0
  282. package/src/forge/compiler/orchestrator/serialize.ts +886 -0
  283. package/src/forge/compiler/orchestrator/session.ts +96 -0
  284. package/src/forge/compiler/orchestrator/types.ts +31 -0
  285. package/src/forge/compiler/orchestrator/verify.ts +38 -0
  286. package/src/forge/compiler/orchestrator/workspace-index.ts +154 -0
  287. package/src/forge/compiler/package-graph/capabilities-stub.ts +33 -0
  288. package/src/forge/compiler/package-graph/checksum.ts +97 -0
  289. package/src/forge/compiler/package-graph/compiler.ts +392 -0
  290. package/src/forge/compiler/package-graph/constants.ts +4 -0
  291. package/src/forge/compiler/package-graph/dts-extractor.ts +142 -0
  292. package/src/forge/compiler/package-graph/exports-discovery.ts +84 -0
  293. package/src/forge/compiler/package-graph/extract-dts.ts +32 -0
  294. package/src/forge/compiler/package-graph/index.ts +33 -0
  295. package/src/forge/compiler/package-graph/jsdoc.ts +62 -0
  296. package/src/forge/compiler/package-graph/read-file.ts +21 -0
  297. package/src/forge/compiler/package-graph/resolve.ts +127 -0
  298. package/src/forge/compiler/package-manager/adapter.ts +237 -0
  299. package/src/forge/compiler/package-manager/bun-executable.ts +92 -0
  300. package/src/forge/compiler/package-manager/commands.ts +47 -0
  301. package/src/forge/compiler/package-manager/detect.ts +79 -0
  302. package/src/forge/compiler/package-manager/executor.ts +117 -0
  303. package/src/forge/compiler/package-manager/index.ts +22 -0
  304. package/src/forge/compiler/package-manager/parse-spec.ts +16 -0
  305. package/src/forge/compiler/package-manager/version.ts +27 -0
  306. package/src/forge/compiler/package-upgrades/apply.ts +195 -0
  307. package/src/forge/compiler/package-upgrades/comparator.ts +181 -0
  308. package/src/forge/compiler/package-upgrades/impact.ts +139 -0
  309. package/src/forge/compiler/package-upgrades/markdown.ts +97 -0
  310. package/src/forge/compiler/package-upgrades/planner.ts +532 -0
  311. package/src/forge/compiler/package-upgrades/risk.ts +208 -0
  312. package/src/forge/compiler/package-upgrades/types.ts +174 -0
  313. package/src/forge/compiler/policy-registry/build.ts +266 -0
  314. package/src/forge/compiler/policy-registry/constants.ts +2 -0
  315. package/src/forge/compiler/policy-registry/parse.ts +81 -0
  316. package/src/forge/compiler/primitives/compare.ts +26 -0
  317. package/src/forge/compiler/primitives/hash.ts +40 -0
  318. package/src/forge/compiler/primitives/header.ts +45 -0
  319. package/src/forge/compiler/primitives/index.ts +45 -0
  320. package/src/forge/compiler/primitives/paths.ts +24 -0
  321. package/src/forge/compiler/primitives/result.ts +164 -0
  322. package/src/forge/compiler/primitives/serialize.ts +66 -0
  323. package/src/forge/compiler/primitives/sort.ts +87 -0
  324. package/src/forge/compiler/query-registry/build.ts +114 -0
  325. package/src/forge/compiler/query-registry/constants.ts +2 -0
  326. package/src/forge/compiler/recipes/definitions.ts +289 -0
  327. package/src/forge/compiler/recipes/helpers.ts +37 -0
  328. package/src/forge/compiler/recipes/index.ts +21 -0
  329. package/src/forge/compiler/recipes/registry.ts +102 -0
  330. package/src/forge/compiler/release/build.ts +100 -0
  331. package/src/forge/compiler/release/types.ts +119 -0
  332. package/src/forge/compiler/runtime-graph/build.ts +137 -0
  333. package/src/forge/compiler/runtime-graph/constants.ts +5 -0
  334. package/src/forge/compiler/runtime-graph/index.ts +5 -0
  335. package/src/forge/compiler/sandbox/artifact-sanitize.ts +26 -0
  336. package/src/forge/compiler/sandbox/backends/child.ts +123 -0
  337. package/src/forge/compiler/sandbox/backends/docker.ts +173 -0
  338. package/src/forge/compiler/sandbox/index.ts +51 -0
  339. package/src/forge/compiler/sandbox/inspect.ts +143 -0
  340. package/src/forge/compiler/sandbox/inspector-entry.ts +115 -0
  341. package/src/forge/compiler/sandbox/limits.ts +31 -0
  342. package/src/forge/compiler/sandbox/scrub-env.ts +60 -0
  343. package/src/forge/compiler/sandbox/secret-scan.ts +54 -0
  344. package/src/forge/compiler/sandbox/serialize.ts +106 -0
  345. package/src/forge/compiler/sandbox/types.ts +7 -0
  346. package/src/forge/compiler/secret-registry/build.ts +123 -0
  347. package/src/forge/compiler/telemetry-registry/build.ts +89 -0
  348. package/src/forge/compiler/telemetry-registry/constants.ts +2 -0
  349. package/src/forge/compiler/telemetry-registry/parse.ts +13 -0
  350. package/src/forge/compiler/test-graph/build.ts +277 -0
  351. package/src/forge/compiler/types/action-subscriptions.ts +19 -0
  352. package/src/forge/compiler/types/ai-registry.ts +33 -0
  353. package/src/forge/compiler/types/app-graph.ts +80 -0
  354. package/src/forge/compiler/types/capability.ts +29 -0
  355. package/src/forge/compiler/types/classification.ts +9 -0
  356. package/src/forge/compiler/types/cli.ts +159 -0
  357. package/src/forge/compiler/types/data-graph.ts +24 -0
  358. package/src/forge/compiler/types/dev-manifest.ts +41 -0
  359. package/src/forge/compiler/types/diagnostic.ts +12 -0
  360. package/src/forge/compiler/types/emit.ts +25 -0
  361. package/src/forge/compiler/types/frontend-graph.ts +81 -0
  362. package/src/forge/compiler/types/import-guards.ts +19 -0
  363. package/src/forge/compiler/types/index.ts +98 -0
  364. package/src/forge/compiler/types/integration.ts +25 -0
  365. package/src/forge/compiler/types/json.ts +3 -0
  366. package/src/forge/compiler/types/live-query-registry.ts +32 -0
  367. package/src/forge/compiler/types/lock.ts +37 -0
  368. package/src/forge/compiler/types/package-graph.ts +84 -0
  369. package/src/forge/compiler/types/policy-registry.ts +69 -0
  370. package/src/forge/compiler/types/query-registry.ts +18 -0
  371. package/src/forge/compiler/types/runtime-graph.ts +30 -0
  372. package/src/forge/compiler/types/runtime-matrix.ts +16 -0
  373. package/src/forge/compiler/types/runtime.ts +30 -0
  374. package/src/forge/compiler/types/sandbox.ts +24 -0
  375. package/src/forge/compiler/types/secret-registry.ts +38 -0
  376. package/src/forge/compiler/types/telemetry-registry.ts +26 -0
  377. package/src/forge/compiler/types/test-graph.ts +45 -0
  378. package/src/forge/compiler/types/workflow-registry.ts +42 -0
  379. package/src/forge/compiler/workflow-registry/build.ts +180 -0
  380. package/src/forge/compiler/workflow-registry/constants.ts +2 -0
  381. package/src/forge/compiler/workflow-registry/index.ts +5 -0
  382. package/src/forge/compiler/workflow-registry/parse.ts +19 -0
  383. package/src/forge/dev/server.ts +1379 -0
  384. package/src/forge/dev/types.ts +49 -0
  385. package/src/forge/dev/watch.ts +109 -0
  386. package/src/forge/dev-console/cycle.ts +652 -0
  387. package/src/forge/dev-console/types.ts +99 -0
  388. package/src/forge/feature/compiler.ts +656 -0
  389. package/src/forge/feature/examples.ts +125 -0
  390. package/src/forge/feature/types.ts +177 -0
  391. package/src/forge/impact/index.ts +1160 -0
  392. package/src/forge/impact/types.ts +151 -0
  393. package/src/forge/intent/index.ts +490 -0
  394. package/src/forge/intent/types.ts +73 -0
  395. package/src/forge/make/fields.ts +146 -0
  396. package/src/forge/make/index.ts +1101 -0
  397. package/src/forge/make/naming.ts +42 -0
  398. package/src/forge/make/templates.ts +525 -0
  399. package/src/forge/make/types.ts +151 -0
  400. package/src/forge/platform/module.ts +20 -0
  401. package/src/forge/policy.ts +1 -0
  402. package/src/forge/react/index.ts +418 -0
  403. package/src/forge/refactor/index.ts +1936 -0
  404. package/src/forge/refactor/text-utils.ts +34 -0
  405. package/src/forge/refactor/types.ts +191 -0
  406. package/src/forge/refactor/workspace-fs.ts +171 -0
  407. package/src/forge/repair/index.ts +656 -0
  408. package/src/forge/repair/rules/index.ts +476 -0
  409. package/src/forge/repair/types.ts +175 -0
  410. package/src/forge/review/index.ts +992 -0
  411. package/src/forge/review/types.ts +196 -0
  412. package/src/forge/runtime/ai/check.ts +86 -0
  413. package/src/forge/runtime/ai/context.ts +394 -0
  414. package/src/forge/runtime/ai/cost-estimator.ts +41 -0
  415. package/src/forge/runtime/ai/mock.ts +49 -0
  416. package/src/forge/runtime/ai/providers.ts +78 -0
  417. package/src/forge/runtime/ai/state.ts +17 -0
  418. package/src/forge/runtime/ai/types.ts +67 -0
  419. package/src/forge/runtime/auth/authenticate.ts +58 -0
  420. package/src/forge/runtime/auth/claims.ts +119 -0
  421. package/src/forge/runtime/auth/config.ts +148 -0
  422. package/src/forge/runtime/auth/errors.ts +45 -0
  423. package/src/forge/runtime/auth/evaluate.ts +126 -0
  424. package/src/forge/runtime/auth/resolve.ts +74 -0
  425. package/src/forge/runtime/auth/types.ts +87 -0
  426. package/src/forge/runtime/auth/verifier.ts +138 -0
  427. package/src/forge/runtime/context/create-context.ts +204 -0
  428. package/src/forge/runtime/context/create-query-context.ts +34 -0
  429. package/src/forge/runtime/db/adapter.ts +31 -0
  430. package/src/forge/runtime/db/factory.ts +83 -0
  431. package/src/forge/runtime/db/generated-client.ts +294 -0
  432. package/src/forge/runtime/db/memory-adapter.ts +706 -0
  433. package/src/forge/runtime/db/migrate.ts +132 -0
  434. package/src/forge/runtime/db/outbox.ts +54 -0
  435. package/src/forge/runtime/db/pglite-adapter.ts +51 -0
  436. package/src/forge/runtime/db/postgres-adapter.ts +112 -0
  437. package/src/forge/runtime/db/read-only-client.ts +97 -0
  438. package/src/forge/runtime/db/session-context.ts +62 -0
  439. package/src/forge/runtime/executor.ts +446 -0
  440. package/src/forge/runtime/live/dependency-tracker.ts +57 -0
  441. package/src/forge/runtime/live/invalidation-log.ts +189 -0
  442. package/src/forge/runtime/live/live-query-runner.ts +267 -0
  443. package/src/forge/runtime/live/registry.ts +28 -0
  444. package/src/forge/runtime/live/sse.ts +75 -0
  445. package/src/forge/runtime/live/subscription-manager.ts +443 -0
  446. package/src/forge/runtime/live/types.ts +143 -0
  447. package/src/forge/runtime/outbox/claim.ts +153 -0
  448. package/src/forge/runtime/outbox/process.ts +298 -0
  449. package/src/forge/runtime/outbox/retry.ts +8 -0
  450. package/src/forge/runtime/outbox/subscriptions.ts +33 -0
  451. package/src/forge/runtime/outbox/types.ts +69 -0
  452. package/src/forge/runtime/policy/check.ts +157 -0
  453. package/src/forge/runtime/policy/load.ts +55 -0
  454. package/src/forge/runtime/query/registry.ts +19 -0
  455. package/src/forge/runtime/query/run-query.ts +347 -0
  456. package/src/forge/runtime/release/runtime.ts +322 -0
  457. package/src/forge/runtime/release/symbolicate.ts +175 -0
  458. package/src/forge/runtime/runner/command-transaction.ts +193 -0
  459. package/src/forge/runtime/runner/run-entry.ts +226 -0
  460. package/src/forge/runtime/secrets/check.ts +78 -0
  461. package/src/forge/runtime/secrets/create-context.ts +138 -0
  462. package/src/forge/runtime/secrets/env-loader.ts +94 -0
  463. package/src/forge/runtime/secrets/runtime-bundle.ts +47 -0
  464. package/src/forge/runtime/secrets/types.ts +31 -0
  465. package/src/forge/runtime/telemetry/buffer.ts +87 -0
  466. package/src/forge/runtime/telemetry/context.ts +192 -0
  467. package/src/forge/runtime/telemetry/correlation.ts +13 -0
  468. package/src/forge/runtime/telemetry/flush.ts +190 -0
  469. package/src/forge/runtime/telemetry/process.ts +20 -0
  470. package/src/forge/runtime/telemetry/scrubber.ts +115 -0
  471. package/src/forge/runtime/telemetry/sinks/local-jsonl.ts +39 -0
  472. package/src/forge/runtime/telemetry/sinks/posthog.ts +64 -0
  473. package/src/forge/runtime/telemetry/sinks/sentry.ts +60 -0
  474. package/src/forge/runtime/telemetry/spans.ts +58 -0
  475. package/src/forge/runtime/telemetry/types.ts +64 -0
  476. package/src/forge/runtime/workflows/cancel.ts +26 -0
  477. package/src/forge/runtime/workflows/create-run.ts +98 -0
  478. package/src/forge/runtime/workflows/process-run.ts +182 -0
  479. package/src/forge/runtime/workflows/process-step.ts +190 -0
  480. package/src/forge/runtime/workflows/process.ts +260 -0
  481. package/src/forge/runtime/workflows/registry.ts +51 -0
  482. package/src/forge/runtime/workflows/resolve-step.ts +46 -0
  483. package/src/forge/runtime/workflows/retry-run.ts +44 -0
  484. package/src/forge/runtime/workflows/retry.ts +8 -0
  485. package/src/forge/runtime/workflows/sanitize.ts +19 -0
  486. package/src/forge/runtime/workflows/start-from-outbox.ts +71 -0
  487. package/src/forge/runtime/workflows/types.ts +77 -0
  488. package/src/forge/server.ts +96 -0
  489. package/src/forge/ui/index.ts +770 -0
  490. package/src/forge/ui/types.ts +191 -0
  491. package/templates/b2b-support-web/.env.example +22 -0
  492. package/templates/b2b-support-web/.vscode/settings.json +14 -0
  493. package/templates/b2b-support-web/AGENTS.md +108 -0
  494. package/templates/b2b-support-web/README.md +48 -0
  495. package/templates/b2b-support-web/forge.config.ts +3 -0
  496. package/templates/b2b-support-web/package.json +34 -0
  497. package/templates/b2b-support-web/src/actions/captureTicketCreated.ts +14 -0
  498. package/templates/b2b-support-web/src/commands/closeTicket.ts +20 -0
  499. package/templates/b2b-support-web/src/commands/createTicket.ts +47 -0
  500. package/templates/b2b-support-web/src/commands/manageBilling.ts +9 -0
  501. package/templates/b2b-support-web/src/forge/schema.ts +35 -0
  502. package/templates/b2b-support-web/src/policies.ts +9 -0
  503. package/templates/b2b-support-web/src/queries/getTicket.ts +6 -0
  504. package/templates/b2b-support-web/src/queries/listTickets.ts +6 -0
  505. package/templates/b2b-support-web/src/queries/liveTickets.ts +9 -0
  506. package/templates/b2b-support-web/src/workflows/triageTicketWorkflow.ts +64 -0
  507. package/templates/b2b-support-web/tsconfig.json +14 -0
  508. package/templates/b2b-support-web/web/app/globals.css +77 -0
  509. package/templates/b2b-support-web/web/app/layout.tsx +13 -0
  510. package/templates/b2b-support-web/web/app/page.tsx +13 -0
  511. package/templates/b2b-support-web/web/app/providers.tsx +21 -0
  512. package/templates/b2b-support-web/web/app/tickets/page.tsx +21 -0
  513. package/templates/b2b-support-web/web/components/CreateTicketForm.tsx +43 -0
  514. package/templates/b2b-support-web/web/components/PolicyDeniedDemo.tsx +31 -0
  515. package/templates/b2b-support-web/web/components/TicketList.tsx +52 -0
  516. package/templates/b2b-support-web/web/components/TraceDetails.tsx +18 -0
  517. package/templates/b2b-support-web/web/components/TriageStatus.tsx +13 -0
  518. package/templates/b2b-support-web/web/lib/forge.ts +13 -0
  519. package/templates/b2b-support-web/web/next-env.d.ts +5 -0
  520. package/templates/b2b-support-web/web/next.config.ts +8 -0
  521. package/templates/b2b-support-web/web/package.json +21 -0
  522. package/templates/b2b-support-web/web/tsconfig.json +30 -0
  523. package/templates/minimal-web/.vscode/settings.json +14 -0
  524. package/templates/minimal-web/README.md +21 -0
  525. package/templates/minimal-web/forge.config.ts +3 -0
  526. package/templates/minimal-web/package.json +32 -0
  527. package/templates/minimal-web/src/actions/logNoteCreated.ts +11 -0
  528. package/templates/minimal-web/src/commands/createNote.ts +26 -0
  529. package/templates/minimal-web/src/forge/schema.ts +12 -0
  530. package/templates/minimal-web/src/policies.ts +6 -0
  531. package/templates/minimal-web/src/queries/listNotes.ts +8 -0
  532. package/templates/minimal-web/src/queries/liveNotes.ts +8 -0
  533. package/templates/minimal-web/tsconfig.json +15 -0
  534. package/templates/minimal-web/web/index.html +12 -0
  535. package/templates/minimal-web/web/package.json +21 -0
  536. package/templates/minimal-web/web/src/App.tsx +89 -0
  537. package/templates/minimal-web/web/src/lib/forge.ts +13 -0
  538. package/templates/minimal-web/web/src/main.tsx +13 -0
  539. package/templates/minimal-web/web/src/styles.css +156 -0
  540. package/templates/minimal-web/web/tsconfig.json +18 -0
@@ -0,0 +1,1379 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import {
3
+ createServer,
4
+ type IncomingMessage,
5
+ type Server as NodeHttpServer,
6
+ type ServerResponse,
7
+ } from "node:http";
8
+ import { join } from "node:path";
9
+ import { createDiagnostic } from "../compiler/diagnostics/create.ts";
10
+ import {
11
+ FORGE_AUTH_DEV_HEADERS_IN_PRODUCTION,
12
+ FORGE_DEV_INVOKE_FAILED,
13
+ FORGE_DEV_SERVER_ERROR,
14
+ FORGE_POLICY_DENIED,
15
+ FORGE_RUNTIME_NOT_FOUND,
16
+ } from "../compiler/diagnostics/codes.ts";
17
+ import { authenticateHeaders } from "../runtime/auth/authenticate.ts";
18
+ import { loadAuthConfigFromEnv } from "../runtime/auth/config.ts";
19
+ import { ForgeAuthError } from "../runtime/auth/errors.ts";
20
+ import type { TableMapEntry } from "../compiler/data-graph/sql/serialize.ts";
21
+ import type { SqlPlan } from "../compiler/data-graph/sql/types.ts";
22
+ import { GENERATED_DIR } from "../compiler/emitter/constants.ts";
23
+ import { stripDeterministicHeader } from "../compiler/primitives/header.ts";
24
+ import type { DevManifest } from "../compiler/types/dev-manifest.ts";
25
+ import type { RuntimeGraph } from "../compiler/types/runtime-graph.ts";
26
+ import { createDbAdapter } from "../runtime/db/factory.ts";
27
+ import { applyMigrations } from "../runtime/db/migrate.ts";
28
+ import {
29
+ listEntries,
30
+ prepareRuntimeEnvironment,
31
+ runEntry,
32
+ } from "../runtime/executor.ts";
33
+ import { listQueries, runQuery } from "../runtime/query/run-query.ts";
34
+ import {
35
+ getOutboxSummary,
36
+ listOutboxDeliveries,
37
+ startOutboxWorker,
38
+ } from "../runtime/outbox/process.ts";
39
+ import { cancelWorkflowRun } from "../runtime/workflows/cancel.ts";
40
+ import { createWorkflowRun } from "../runtime/workflows/create-run.ts";
41
+ import {
42
+ getWorkflowSummary,
43
+ inspectWorkflowRun,
44
+ listWorkflowRuns,
45
+ runWorkerTick,
46
+ } from "../runtime/workflows/process.ts";
47
+ import { loadWorkflowRegistry } from "../runtime/workflows/registry.ts";
48
+ import { retryWorkflowRun } from "../runtime/workflows/retry-run.ts";
49
+ import { hashStable } from "../compiler/primitives/hash.ts";
50
+ import { canonicalJson } from "../compiler/primitives/serialize.ts";
51
+ import type { DevServerHandle, DevServerOptions, DevServerState } from "./types.ts";
52
+ import { getTelemetrySummary, inspectTrace } from "../runtime/telemetry/flush.ts";
53
+ import { processTelemetryBatch } from "../runtime/telemetry/process.ts";
54
+ import { getRuntimeEnvStore } from "../runtime/context/create-context.ts";
55
+ import {
56
+ countMissingRequiredSecrets,
57
+ loadSecretRegistry,
58
+ } from "../runtime/secrets/check.ts";
59
+ import { checkAiProviders, loadAiRegistry } from "../runtime/ai/check.ts";
60
+ import { isMockAiEnabled } from "../runtime/ai/state.ts";
61
+ import { createAiContext } from "../runtime/ai/context.ts";
62
+ import { createRuntimeSecretsBundle } from "../runtime/secrets/runtime-bundle.ts";
63
+ import { createNoopTelemetryContext } from "../runtime/telemetry/context.ts";
64
+ import { generateTraceId } from "../runtime/telemetry/correlation.ts";
65
+ import { loadLiveQueryRegistry } from "../runtime/live/registry.ts";
66
+ import { createLiveSubscriptionManager } from "../runtime/live/subscription-manager.ts";
67
+ import { createSseResponse } from "../runtime/live/sse.ts";
68
+ import {
69
+ ensureLiveInvalidationSchema,
70
+ listLiveInvalidations,
71
+ } from "../runtime/live/invalidation-log.ts";
72
+ import { currentReleaseInfo } from "../runtime/release/runtime.ts";
73
+ import { DEFAULT_LIVE_LIMITS } from "../runtime/live/types.ts";
74
+
75
+ interface FetchServer {
76
+ hostname?: string;
77
+ port: number;
78
+ stop(force?: boolean): void;
79
+ }
80
+
81
+ async function readIncomingBody(request: IncomingMessage): Promise<Buffer | undefined> {
82
+ if (request.method === "GET" || request.method === "HEAD") {
83
+ return undefined;
84
+ }
85
+
86
+ const chunks: Buffer[] = [];
87
+ for await (const chunk of request) {
88
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
89
+ }
90
+ return chunks.length > 0 ? Buffer.concat(chunks) : undefined;
91
+ }
92
+
93
+ async function nodeRequestToFetch(
94
+ request: IncomingMessage,
95
+ input: { host: string; port: number },
96
+ ): Promise<Request> {
97
+ const headers = new Headers();
98
+ for (const [name, value] of Object.entries(request.headers)) {
99
+ if (Array.isArray(value)) {
100
+ headers.set(name, value.join(", "));
101
+ } else if (value !== undefined) {
102
+ headers.set(name, value);
103
+ }
104
+ }
105
+
106
+ const url = `http://${input.host}:${input.port}${request.url ?? "/"}`;
107
+ const body = await readIncomingBody(request);
108
+ const init: RequestInit & { duplex?: "half" } = {
109
+ method: request.method,
110
+ headers,
111
+ body: body ? new Blob([new Uint8Array(body)]) : undefined,
112
+ ...(body ? { duplex: "half" as const } : {}),
113
+ };
114
+ return new Request(url, init);
115
+ }
116
+
117
+ async function writeFetchResponse(
118
+ response: ServerResponse,
119
+ fetchResponse: Response,
120
+ ): Promise<void> {
121
+ response.statusCode = fetchResponse.status;
122
+ fetchResponse.headers.forEach((value, key) => {
123
+ response.setHeader(key, value);
124
+ });
125
+
126
+ if (!fetchResponse.body) {
127
+ response.end();
128
+ return;
129
+ }
130
+
131
+ response.end(Buffer.from(await fetchResponse.arrayBuffer()));
132
+ }
133
+
134
+ async function createNodeFetchServer(
135
+ input: { hostname: string; port: number },
136
+ fetchHandler: (request: Request) => Promise<Response>,
137
+ ): Promise<FetchServer> {
138
+ let actualPort = input.port;
139
+ const nodeServer: NodeHttpServer = createServer(async (request, response) => {
140
+ try {
141
+ const fetchRequest = await nodeRequestToFetch(request, {
142
+ host: input.hostname,
143
+ port: actualPort,
144
+ });
145
+ const fetchResponse = await fetchHandler(fetchRequest);
146
+ await writeFetchResponse(response, fetchResponse);
147
+ } catch (error) {
148
+ const message = error instanceof Error ? error.message : "dev server request failed";
149
+ await writeFetchResponse(
150
+ response,
151
+ jsonResponse(
152
+ {
153
+ ok: false,
154
+ diagnostics: [
155
+ createDiagnostic({
156
+ severity: "error",
157
+ code: FORGE_DEV_SERVER_ERROR,
158
+ message,
159
+ }),
160
+ ],
161
+ },
162
+ 500,
163
+ ),
164
+ );
165
+ }
166
+ });
167
+
168
+ await new Promise<void>((resolveListen, reject) => {
169
+ nodeServer.once("error", reject);
170
+ nodeServer.listen(input.port, input.hostname, () => {
171
+ nodeServer.off("error", reject);
172
+ resolveListen();
173
+ });
174
+ });
175
+
176
+ const address = nodeServer.address();
177
+ actualPort = typeof address === "object" && address ? address.port : input.port;
178
+ return {
179
+ hostname: input.hostname,
180
+ port: actualPort,
181
+ stop: () => {
182
+ nodeServer.close();
183
+ },
184
+ };
185
+ }
186
+
187
+ async function createFetchServer(
188
+ input: { hostname: string; port: number },
189
+ fetchHandler: (request: Request) => Promise<Response>,
190
+ ): Promise<FetchServer> {
191
+ const bunGlobal = globalThis as {
192
+ Bun?: {
193
+ serve?: (options: {
194
+ hostname: string;
195
+ port: number;
196
+ idleTimeout: number;
197
+ fetch: (request: Request) => Promise<Response>;
198
+ }) => FetchServer;
199
+ };
200
+ };
201
+
202
+ if (bunGlobal.Bun?.serve) {
203
+ return bunGlobal.Bun.serve({
204
+ hostname: input.hostname,
205
+ port: input.port,
206
+ idleTimeout: 255,
207
+ fetch: fetchHandler,
208
+ });
209
+ }
210
+
211
+ return createNodeFetchServer(input, fetchHandler);
212
+ }
213
+
214
+ function readGeneratedJson<T>(workspaceRoot: string, relative: string): T | null {
215
+ const absolute = join(workspaceRoot, relative);
216
+ if (!existsSync(absolute)) {
217
+ return null;
218
+ }
219
+ const raw = stripDeterministicHeader(readFileSync(absolute, "utf8"));
220
+ return JSON.parse(raw) as T;
221
+ }
222
+
223
+ function jsonResponse(
224
+ body: unknown,
225
+ status = 200,
226
+ extraHeaders: Record<string, string> = {},
227
+ ): Response {
228
+ return new Response(JSON.stringify(body), {
229
+ status,
230
+ headers: {
231
+ "Content-Type": "application/json",
232
+ "Access-Control-Allow-Origin": "*",
233
+ ...extraHeaders,
234
+ },
235
+ });
236
+ }
237
+
238
+ function htmlResponse(body: string, status = 200): Response {
239
+ return new Response(body, {
240
+ status,
241
+ headers: {
242
+ "Content-Type": "text/html; charset=utf-8",
243
+ "Access-Control-Allow-Origin": "*",
244
+ },
245
+ });
246
+ }
247
+
248
+ function corsPreflight(): Response {
249
+ return new Response(null, {
250
+ status: 204,
251
+ headers: {
252
+ "Access-Control-Allow-Origin": "*",
253
+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
254
+ "Access-Control-Allow-Headers": "Authorization, Content-Type, x-forge-user-id, x-forge-tenant-id, x-forge-role",
255
+ },
256
+ });
257
+ }
258
+
259
+ function acceptsHtml(request: Request): boolean {
260
+ return (request.headers.get("accept") ?? "").includes("text/html");
261
+ }
262
+
263
+ function escapeHtml(value: unknown): string {
264
+ return String(value ?? "")
265
+ .replace(/&/g, "&amp;")
266
+ .replace(/</g, "&lt;")
267
+ .replace(/>/g, "&gt;")
268
+ .replace(/"/g, "&quot;");
269
+ }
270
+
271
+ function renderDevHome(input: {
272
+ service: string;
273
+ db: unknown;
274
+ webUrl?: string;
275
+ entries: Array<{ name: string; kind: string; path: string; method: string }>;
276
+ routes: Array<{ method: string; path: string; purpose: string }>;
277
+ }): string {
278
+ const entries = input.entries
279
+ .map(
280
+ (entry) =>
281
+ `<li><code>${escapeHtml(entry.method)} ${escapeHtml(entry.path)}</code> <span>${escapeHtml(entry.kind)}:${escapeHtml(entry.name)}</span></li>`,
282
+ )
283
+ .join("");
284
+ const routes = input.routes
285
+ .slice(0, 20)
286
+ .map(
287
+ (route) =>
288
+ `<li><code>${escapeHtml(route.method)} ${escapeHtml(route.path)}</code> <span>${escapeHtml(route.purpose)}</span></li>`,
289
+ )
290
+ .join("");
291
+
292
+ return `<!doctype html>
293
+ <html lang="en">
294
+ <head>
295
+ <meta charset="utf-8">
296
+ <meta name="viewport" content="width=device-width, initial-scale=1">
297
+ <title>Forge Dev</title>
298
+ <style>
299
+ :root { color-scheme: light dark; font-family: ui-sans-serif, system-ui, sans-serif; }
300
+ body { margin: 0; padding: 32px; line-height: 1.5; background: Canvas; color: CanvasText; }
301
+ main { max-width: 920px; margin: 0 auto; }
302
+ h1 { margin: 0 0 8px; font-size: 32px; }
303
+ h2 { margin-top: 28px; font-size: 18px; }
304
+ p { color: color-mix(in srgb, CanvasText 75%, Canvas 25%); }
305
+ ul { padding-left: 20px; }
306
+ li { margin: 8px 0; }
307
+ code { padding: 2px 6px; border-radius: 6px; background: color-mix(in srgb, CanvasText 10%, Canvas 90%); }
308
+ .meta { display: flex; gap: 12px; flex-wrap: wrap; margin: 18px 0; }
309
+ .pill { border: 1px solid color-mix(in srgb, CanvasText 18%, Canvas 82%); border-radius: 999px; padding: 4px 10px; }
310
+ </style>
311
+ </head>
312
+ <body>
313
+ <main>
314
+ <h1>Forge Dev</h1>
315
+ <p>This server is an API surface for Forge commands, queries, liveQueries, workflows, telemetry, and health checks.</p>
316
+ <div class="meta">
317
+ <span class="pill">service: ${escapeHtml(input.service)}</span>
318
+ <span class="pill">db: ${escapeHtml(input.db)}</span>
319
+ ${input.webUrl ? `<a class="pill" href="${escapeHtml(input.webUrl)}">web: ${escapeHtml(input.webUrl)}</a>` : ""}
320
+ </div>
321
+ <h2>Start Here</h2>
322
+ <ul>
323
+ <li><code>GET /health</code> checks server, DB, worker, auth, env, AI, and liveQuery state.</li>
324
+ <li><code>GET /entries</code> lists callable commands, actions, queries, and liveQueries.</li>
325
+ <li>Commands and queries require <code>POST</code> with JSON body <code>{"args":{}}</code>.</li>
326
+ </ul>
327
+ <h2>Entries</h2>
328
+ <ul>${entries || "<li>No entries generated yet.</li>"}</ul>
329
+ <h2>Routes</h2>
330
+ <ul>${routes || "<li>No routes generated yet.</li>"}</ul>
331
+ </main>
332
+ </body>
333
+ </html>`;
334
+ }
335
+
336
+ function methodHelpResponse(input: {
337
+ kind: "command" | "action" | "query";
338
+ name: string;
339
+ path: string;
340
+ }): Response {
341
+ return jsonResponse(
342
+ {
343
+ ok: false,
344
+ diagnostics: [
345
+ createDiagnostic({
346
+ severity: "error",
347
+ code: FORGE_DEV_INVOKE_FAILED,
348
+ message: `${input.kind} '${input.name}' requires POST ${input.path}`,
349
+ fixHint: `Send JSON like {"args":{}} to POST ${input.path}.`,
350
+ }),
351
+ ],
352
+ example: {
353
+ method: "POST",
354
+ path: input.path,
355
+ body: { args: {} },
356
+ },
357
+ },
358
+ 405,
359
+ { Allow: "POST, OPTIONS" },
360
+ );
361
+ }
362
+
363
+ function errorMessage(error: unknown, fallback: string): string {
364
+ if (error instanceof Error && error.message) {
365
+ return error.message;
366
+ }
367
+ if (
368
+ error &&
369
+ typeof error === "object" &&
370
+ "message" in error &&
371
+ typeof (error as { message?: unknown }).message === "string" &&
372
+ (error as { message: string }).message.length > 0
373
+ ) {
374
+ return (error as { message: string }).message;
375
+ }
376
+ if (typeof error === "string" && error.length > 0) {
377
+ return error;
378
+ }
379
+ const stringified = String(error);
380
+ return stringified && stringified !== "[object Object]" ? stringified : fallback;
381
+ }
382
+
383
+ function parseInvokeName(pathname: string, prefix: string): string | null {
384
+ if (!pathname.startsWith(prefix)) {
385
+ return null;
386
+ }
387
+ const name = pathname.slice(prefix.length);
388
+ return name.length > 0 ? decodeURIComponent(name) : null;
389
+ }
390
+
391
+ async function parseRequestArgs(request: Request): Promise<unknown> {
392
+ const contentType = request.headers.get("content-type") ?? "";
393
+ if (!contentType.includes("application/json")) {
394
+ return {};
395
+ }
396
+
397
+ try {
398
+ const body = (await request.json()) as { args?: unknown };
399
+ return body.args ?? {};
400
+ } catch {
401
+ return {};
402
+ }
403
+ }
404
+
405
+ export async function startDevServer(
406
+ options: DevServerOptions,
407
+ ): Promise<DevServerHandle> {
408
+ const workspaceRoot = options.workspaceRoot.replace(/\\/g, "/");
409
+ const authConfig = loadAuthConfigFromEnv(workspaceRoot, {
410
+ defaultMode: "dev-headers",
411
+ });
412
+ if (
413
+ options.mode === "serve" &&
414
+ authConfig.mode === "dev-headers" &&
415
+ !options.allowDevAuth
416
+ ) {
417
+ throw new ForgeAuthError(
418
+ FORGE_AUTH_DEV_HEADERS_IN_PRODUCTION,
419
+ "forge serve rejects FORGE_AUTH_MODE=dev-headers unless --allow-dev-auth is set",
420
+ { status: 403 },
421
+ );
422
+ }
423
+
424
+ const devManifest = readGeneratedJson<DevManifest>(
425
+ workspaceRoot,
426
+ `${GENERATED_DIR}/devManifest.json`,
427
+ );
428
+ const runtimeGraph = readGeneratedJson<RuntimeGraph>(
429
+ workspaceRoot,
430
+ `${GENERATED_DIR}/runtimeGraph.json`,
431
+ );
432
+
433
+ if (!runtimeGraph || !devManifest) {
434
+ throw new Error(
435
+ `missing generated dev artifacts; run forge generate first (${GENERATED_DIR}/devManifest.json)`,
436
+ );
437
+ }
438
+
439
+ const telemetrySinks = options.telemetry ?? ["local"];
440
+
441
+ const dbMode = options.db ?? "none";
442
+
443
+ const serverState: DevServerState = {
444
+ adapter: null,
445
+ db: {
446
+ kind: dbMode,
447
+ connected: false,
448
+ },
449
+ };
450
+
451
+ if (dbMode !== "none") {
452
+ const { adapter, diagnostics } = await createDbAdapter({
453
+ kind: dbMode,
454
+ workspaceRoot,
455
+ databaseUrl: options.databaseUrl,
456
+ });
457
+
458
+ if (!adapter) {
459
+ const message = diagnostics.map((diagnostic) => diagnostic.message).join("; ");
460
+ throw new Error(message || "failed to create database adapter");
461
+ }
462
+
463
+ const sqlPlan = readGeneratedJson<SqlPlan>(
464
+ workspaceRoot,
465
+ `${GENERATED_DIR}/sqlPlan.json`,
466
+ );
467
+
468
+ if (sqlPlan) {
469
+ const migrationDiagnostics = await applyMigrations(adapter, sqlPlan);
470
+ const errors = migrationDiagnostics.filter(
471
+ (diagnostic) => diagnostic.severity === "error",
472
+ );
473
+ if (errors.length > 0) {
474
+ await adapter.close();
475
+ throw new Error(errors.map((diagnostic) => diagnostic.message).join("; "));
476
+ }
477
+ }
478
+ await ensureLiveInvalidationSchema(adapter);
479
+
480
+ serverState.adapter = adapter;
481
+ serverState.db.connected = true;
482
+ }
483
+
484
+ const restoreRuntimeEnvironment = await prepareRuntimeEnvironment(workspaceRoot, {
485
+ mock: options.mock,
486
+ mockAi: options.mockAi,
487
+ db: serverState.adapter,
488
+ });
489
+
490
+ function loadArtifacts(): {
491
+ devManifest: DevManifest;
492
+ runtimeGraph: RuntimeGraph;
493
+ tableMap: Record<string, TableMapEntry>;
494
+ } {
495
+ const freshDevManifest = readGeneratedJson<DevManifest>(
496
+ workspaceRoot,
497
+ `${GENERATED_DIR}/devManifest.json`,
498
+ );
499
+ const freshRuntimeGraph = readGeneratedJson<RuntimeGraph>(
500
+ workspaceRoot,
501
+ `${GENERATED_DIR}/runtimeGraph.json`,
502
+ );
503
+ const dbJson = readGeneratedJson<{ tableMap: Record<string, TableMapEntry> }>(
504
+ workspaceRoot,
505
+ `${GENERATED_DIR}/db.json`,
506
+ );
507
+
508
+ if (!freshRuntimeGraph || !freshDevManifest) {
509
+ throw new Error(
510
+ `missing generated dev artifacts; run forge generate first (${GENERATED_DIR}/devManifest.json)`,
511
+ );
512
+ }
513
+
514
+ return {
515
+ devManifest: freshDevManifest,
516
+ runtimeGraph: freshRuntimeGraph,
517
+ tableMap: dbJson?.tableMap ?? {},
518
+ };
519
+ }
520
+
521
+ const initialArtifacts = loadArtifacts();
522
+ const liveManager = serverState.adapter
523
+ ? createLiveSubscriptionManager({
524
+ workspaceRoot,
525
+ adapter: serverState.adapter,
526
+ loadTableMap: () => loadArtifacts().tableMap,
527
+ enablePolling: true,
528
+ pollIntervalMs: Number(process.env.FORGE_LIVE_POLL_INTERVAL_MS ?? "1000"),
529
+ })
530
+ : null;
531
+
532
+ if (options.worker && serverState.adapter) {
533
+ serverState.outboxWorker = startOutboxWorker(
534
+ serverState.adapter,
535
+ workspaceRoot,
536
+ initialArtifacts.tableMap,
537
+ runtimeGraph.entries,
538
+ { mock: options.mock, intervalMs: 2_000, telemetrySinks, workspaceRoot },
539
+ );
540
+ }
541
+
542
+ const server = await createFetchServer({
543
+ hostname: options.host,
544
+ port: options.port,
545
+ }, async (request) => {
546
+ if (request.method === "OPTIONS") {
547
+ return corsPreflight();
548
+ }
549
+
550
+ const url = new URL(request.url);
551
+ const pathname = url.pathname;
552
+
553
+ try {
554
+ const { devManifest: currentDevManifest, runtimeGraph: currentRuntimeGraph, tableMap } =
555
+ loadArtifacts();
556
+
557
+ if (request.method === "GET" && pathname === "/") {
558
+ const listed = listEntries(workspaceRoot);
559
+ const queries = listQueries(workspaceRoot);
560
+ const liveQueries = loadLiveQueryRegistry(workspaceRoot);
561
+ const entries = [
562
+ ...queries.queries.map((query) => ({
563
+ name: query.name,
564
+ kind: "query",
565
+ path: `/queries/${query.name}`,
566
+ method: "POST",
567
+ })),
568
+ ...liveQueries.liveQueries.map((liveQuery) => ({
569
+ name: liveQuery.name,
570
+ kind: "liveQuery",
571
+ path: `/live/${liveQuery.name}`,
572
+ method: "GET",
573
+ })),
574
+ ...listed.entries.map((entry) => ({
575
+ name: entry.name,
576
+ kind: entry.kind,
577
+ path:
578
+ entry.kind === "command"
579
+ ? `/commands/${entry.name}`
580
+ : `/actions/${entry.name}`,
581
+ method: "POST",
582
+ })),
583
+ ].sort((a, b) =>
584
+ a.path === b.path ? a.kind.localeCompare(b.kind) : a.path.localeCompare(b.path),
585
+ );
586
+ const routes = currentDevManifest.routes.map((route) => ({
587
+ method: route.method,
588
+ path: route.path,
589
+ purpose: route.purpose,
590
+ }));
591
+ const payload = {
592
+ ok: true,
593
+ service: options.mode === "serve" ? "forge-serve" : "forge-dev",
594
+ message: "Forge dev is an API server. Use POST for commands and queries.",
595
+ health: "/health",
596
+ entries,
597
+ routes,
598
+ db: serverState.db,
599
+ web: options.webUrl ? { url: options.webUrl } : null,
600
+ diagnostics: [
601
+ ...listed.diagnostics,
602
+ ...queries.diagnostics,
603
+ ...(liveQueries.registry?.diagnostics ?? []),
604
+ ],
605
+ };
606
+
607
+ if (acceptsHtml(request)) {
608
+ return htmlResponse(
609
+ renderDevHome({
610
+ service: payload.service,
611
+ db: payload.db,
612
+ webUrl: options.webUrl,
613
+ entries,
614
+ routes,
615
+ }),
616
+ );
617
+ }
618
+
619
+ return jsonResponse(payload);
620
+ }
621
+
622
+ if (request.method === "GET" && pathname === "/health") {
623
+ const outboxSummary = serverState.adapter
624
+ ? await getOutboxSummary(serverState.adapter)
625
+ : { pending: 0, dead: 0, processing: 0, processed: 0, failed: 0, events: 0 };
626
+ const workflowSummary = serverState.adapter
627
+ ? await getWorkflowSummary(serverState.adapter)
628
+ : { pending: 0, running: 0, completed: 0, failed: 0, dead: 0, canceled: 0 };
629
+ const telemetrySummary = serverState.adapter
630
+ ? await getTelemetrySummary(serverState.adapter)
631
+ : { pending: 0, failed: 0, processed: 0 };
632
+
633
+ const envStore = getRuntimeEnvStore(workspaceRoot);
634
+ const secretRegistry = loadSecretRegistry(workspaceRoot);
635
+ const missingRequiredSecrets = secretRegistry
636
+ ? countMissingRequiredSecrets(envStore, secretRegistry)
637
+ : 0;
638
+
639
+ const aiRegistry = loadAiRegistry(workspaceRoot);
640
+ const aiCheck = checkAiProviders(envStore, aiRegistry, secretRegistry);
641
+ const mockAi = isMockAiEnabled({ mockAi: options.mockAi });
642
+
643
+ return jsonResponse({
644
+ ok: true,
645
+ service: options.mode === "serve" ? "forge-serve" : "forge-dev",
646
+ mode: options.mode ?? "dev",
647
+ entries: currentRuntimeGraph.entries.length,
648
+ db: serverState.db,
649
+ outbox: {
650
+ worker: serverState.outboxWorker?.isRunning() ? "running" : "stopped",
651
+ pending: outboxSummary.pending,
652
+ dead: outboxSummary.dead,
653
+ },
654
+ workflows: {
655
+ running: workflowSummary.running,
656
+ pending: workflowSummary.pending,
657
+ dead: workflowSummary.dead,
658
+ },
659
+ telemetry: {
660
+ pending: telemetrySummary.pending,
661
+ failed: telemetrySummary.failed,
662
+ sinks: telemetrySinks,
663
+ },
664
+ auth: {
665
+ mode: authConfig.mode,
666
+ issuerConfigured: Boolean(authConfig.issuer),
667
+ audienceConfigured: Boolean(authConfig.audience),
668
+ jwksConfigured: Boolean(authConfig.jwksUri),
669
+ requiresTenant: authConfig.requiresTenant,
670
+ },
671
+ env: {
672
+ loadedFiles: envStore.loadedFiles,
673
+ missingRequiredSecrets,
674
+ },
675
+ ai: {
676
+ enabled: true,
677
+ mode: mockAi ? "mock" : "live",
678
+ providers: aiCheck.providers,
679
+ },
680
+ live: liveManager?.stats() ?? {
681
+ subscriptions: 0,
682
+ liveQueries: loadLiveQueryRegistry(workspaceRoot).liveQueries.length,
683
+ lastRevision: 0,
684
+ },
685
+ liveStatus: liveManager?.status() ?? null,
686
+ });
687
+ }
688
+
689
+ if (request.method === "GET" && pathname === "/live") {
690
+ const liveQueries = loadLiveQueryRegistry(workspaceRoot).liveQueries;
691
+ return jsonResponse({
692
+ ok: true,
693
+ liveQueries: liveQueries.map((liveQuery) => liveQuery.name),
694
+ });
695
+ }
696
+
697
+ if (request.method === "GET" && pathname === "/live/status") {
698
+ return jsonResponse({
699
+ ok: true,
700
+ status: liveManager?.status() ?? {
701
+ runtime: { id: "runtime_unavailable", transport: "sse", subscriptions: 0 },
702
+ invalidation: {
703
+ lastSeenRevision: 0,
704
+ lastProcessedRevision: 0,
705
+ polling: false,
706
+ postgresNotify: false,
707
+ },
708
+ limits: DEFAULT_LIVE_LIMITS,
709
+ },
710
+ });
711
+ }
712
+
713
+ if (request.method === "GET" && pathname === "/live/invalidations") {
714
+ if (!serverState.adapter) {
715
+ return jsonResponse({ ok: true, invalidations: [] });
716
+ }
717
+ const limit = Number(url.searchParams.get("limit") ?? "50");
718
+ return jsonResponse({
719
+ ok: true,
720
+ invalidations: await listLiveInvalidations(serverState.adapter, limit),
721
+ });
722
+ }
723
+
724
+ const liveDebugMatch = pathname.match(/^\/live\/debug\/([^/]+)$/);
725
+ if (request.method === "GET" && liveDebugMatch) {
726
+ const subscriptionId = decodeURIComponent(liveDebugMatch[1]!);
727
+ const subscription = liveManager?.debug(subscriptionId);
728
+ return jsonResponse({
729
+ ok: Boolean(subscription),
730
+ subscription: subscription
731
+ ? {
732
+ id: subscription.id,
733
+ name: subscription.name,
734
+ tenantId:
735
+ subscription.auth.kind === "user"
736
+ ? subscription.auth.tenantId
737
+ : subscription.auth.kind === "system"
738
+ ? subscription.auth.tenantId
739
+ : undefined,
740
+ dependencies: subscription.dependencies,
741
+ lastSentRevision: subscription.lastSentRevision,
742
+ status: subscription.status,
743
+ createdAt: subscription.createdAt,
744
+ lastSentAt: subscription.lastSentAt,
745
+ }
746
+ : null,
747
+ }, subscription ? 200 : 404);
748
+ }
749
+
750
+ const liveMatch = pathname.match(/^\/live\/([^/]+)$/);
751
+ if (request.method === "GET" && liveMatch) {
752
+ if (!serverState.adapter || !liveManager) {
753
+ return jsonResponse(
754
+ {
755
+ ok: false,
756
+ diagnostics: [
757
+ createDiagnostic({
758
+ severity: "error",
759
+ code: FORGE_DEV_SERVER_ERROR,
760
+ message: "database not connected",
761
+ }),
762
+ ],
763
+ },
764
+ 400,
765
+ );
766
+ }
767
+
768
+ const name = decodeURIComponent(liveMatch[1]!);
769
+ let args: unknown = {};
770
+ const argsRaw = url.searchParams.get("args");
771
+ if (argsRaw) {
772
+ try {
773
+ args = JSON.parse(argsRaw);
774
+ } catch {
775
+ args = {};
776
+ }
777
+ }
778
+
779
+ const auth = await authenticateHeaders(request.headers, authConfig);
780
+ const lastRevision = Number(
781
+ request.headers.get("last-event-id") ??
782
+ url.searchParams.get("lastRevision") ??
783
+ "NaN",
784
+ );
785
+ let subscriptionId: string | null = null;
786
+
787
+ return createSseResponse(
788
+ async (send, close) => {
789
+ const subscription = await liveManager.subscribe({
790
+ name,
791
+ args,
792
+ auth,
793
+ lastRevision: Number.isFinite(lastRevision) ? lastRevision : undefined,
794
+ send,
795
+ });
796
+ subscriptionId = subscription.id;
797
+ const known = loadLiveQueryRegistry(workspaceRoot).liveQueries.some(
798
+ (liveQuery) => liveQuery.name === name,
799
+ );
800
+ if (!known) {
801
+ close();
802
+ }
803
+ },
804
+ () => {
805
+ if (subscriptionId) {
806
+ liveManager.unsubscribe(subscriptionId);
807
+ }
808
+ },
809
+ {
810
+ heartbeatIntervalMs: Number(process.env.FORGE_LIVE_HEARTBEAT_MS ?? "15000"),
811
+ hello: {
812
+ type: "hello",
813
+ protocolVersion: "0.1.0",
814
+ releaseId: currentReleaseInfo().releaseId,
815
+ deployId: currentReleaseInfo().deployId,
816
+ serverTime: new Date().toISOString(),
817
+ },
818
+ },
819
+ );
820
+ }
821
+
822
+ if (request.method === "GET" && pathname === "/ai/providers") {
823
+ const aiRegistry = loadAiRegistry(workspaceRoot);
824
+ const envStore = getRuntimeEnvStore(workspaceRoot);
825
+ const secretRegistry = loadSecretRegistry(workspaceRoot);
826
+ const aiCheck = checkAiProviders(envStore, aiRegistry, secretRegistry);
827
+ return jsonResponse({ ok: true, providers: aiCheck.providers });
828
+ }
829
+
830
+ if (request.method === "POST" && pathname === "/ai/test") {
831
+ const body = (await request.json().catch(() => ({}))) as {
832
+ provider?: string;
833
+ model?: string;
834
+ prompt?: string;
835
+ };
836
+ const envStore = getRuntimeEnvStore(workspaceRoot);
837
+ const secretRegistry = loadSecretRegistry(workspaceRoot);
838
+ const bundle = createRuntimeSecretsBundle({
839
+ store: envStore,
840
+ registry: secretRegistry,
841
+ envSchema: null,
842
+ runtimeKind: "server",
843
+ });
844
+ const telemetry = createNoopTelemetryContext(generateTraceId());
845
+ const ai = createAiContext({
846
+ secrets: bundle.secrets,
847
+ telemetry,
848
+ runtimeKind: "server",
849
+ mockAi: isMockAiEnabled({ mockAi: options.mockAi }),
850
+ });
851
+
852
+ try {
853
+ const result = await ai.generateText({
854
+ provider: (body.provider ?? "openai") as "openai" | "anthropic" | "gateway",
855
+ model: body.model ?? "gpt-4o-mini",
856
+ prompt: body.prompt ?? "ping",
857
+ purpose: "dev_test",
858
+ });
859
+ return jsonResponse({ ok: true, result });
860
+ } catch (error) {
861
+ const message = error instanceof Error ? error.message : String(error);
862
+ return jsonResponse({ ok: false, error: message }, 400);
863
+ }
864
+ }
865
+
866
+ if (request.method === "GET" && pathname === "/telemetry") {
867
+ if (!serverState.adapter) {
868
+ return jsonResponse({
869
+ ok: true,
870
+ summary: { pending: 0, failed: 0, processed: 0 },
871
+ events: [],
872
+ });
873
+ }
874
+
875
+ const summary = await getTelemetrySummary(serverState.adapter);
876
+ const { listTelemetryEvents } = await import("../runtime/telemetry/flush.ts");
877
+ const events = await listTelemetryEvents(serverState.adapter);
878
+ return jsonResponse({ ok: true, summary, events });
879
+ }
880
+
881
+ const telemetryTraceMatch = pathname.match(/^\/telemetry\/traces\/([^/]+)$/);
882
+ if (request.method === "GET" && telemetryTraceMatch && serverState.adapter) {
883
+ const traceId = decodeURIComponent(telemetryTraceMatch[1]!);
884
+ const inspected = await inspectTrace(serverState.adapter, traceId);
885
+ return jsonResponse({ ok: true, traceId, ...inspected });
886
+ }
887
+
888
+ if (request.method === "GET" && pathname === "/outbox") {
889
+ if (!serverState.adapter) {
890
+ return jsonResponse({ ok: true, summary: null, deliveries: [] });
891
+ }
892
+
893
+ const summary = await getOutboxSummary(serverState.adapter);
894
+ const deliveries = await listOutboxDeliveries(serverState.adapter);
895
+ return jsonResponse({ ok: true, summary, deliveries });
896
+ }
897
+
898
+ if (request.method === "GET" && pathname === "/entries") {
899
+ const listed = listEntries(workspaceRoot);
900
+ const queries = listQueries(workspaceRoot);
901
+ const liveQueries = loadLiveQueryRegistry(workspaceRoot);
902
+ return jsonResponse({
903
+ ok: true,
904
+ entries: [
905
+ ...queries.queries.map((query) => ({
906
+ name: query.name,
907
+ kind: "query",
908
+ file: query.file,
909
+ })),
910
+ ...liveQueries.liveQueries.map((liveQuery) => ({
911
+ name: liveQuery.name,
912
+ kind: "liveQuery",
913
+ file: liveQuery.file,
914
+ })),
915
+ ...listed.entries,
916
+ ],
917
+ liveQueries: liveQueries.liveQueries,
918
+ diagnostics: [
919
+ ...listed.diagnostics,
920
+ ...queries.diagnostics,
921
+ ...(liveQueries.registry?.diagnostics ?? []),
922
+ ],
923
+ });
924
+ }
925
+
926
+ if (request.method === "GET" && pathname === "/queries") {
927
+ const queries = listQueries(workspaceRoot);
928
+ return jsonResponse({
929
+ ok: true,
930
+ queries: queries.queries,
931
+ diagnostics: queries.diagnostics,
932
+ });
933
+ }
934
+
935
+ if (request.method === "GET" && pathname === "/workflows") {
936
+ const { workflows } = loadWorkflowRegistry(workspaceRoot);
937
+ return jsonResponse({
938
+ ok: true,
939
+ workflows: currentDevManifest.workflows,
940
+ registry: workflows,
941
+ });
942
+ }
943
+
944
+ if (request.method === "GET" && pathname === "/workflows/runs") {
945
+ if (!serverState.adapter) {
946
+ return jsonResponse({ ok: true, runs: [], summary: null });
947
+ }
948
+
949
+ const runs = await listWorkflowRuns(serverState.adapter);
950
+ const summary = await getWorkflowSummary(serverState.adapter);
951
+ return jsonResponse({ ok: true, runs, summary });
952
+ }
953
+
954
+ const workflowRunMatch = pathname.match(/^\/workflows\/runs\/(\d+)$/);
955
+ if (request.method === "GET" && workflowRunMatch) {
956
+ if (!serverState.adapter) {
957
+ return jsonResponse({ ok: true, run: null, steps: [] });
958
+ }
959
+
960
+ const runId = Number(workflowRunMatch[1]);
961
+ const inspected = await inspectWorkflowRun(serverState.adapter, runId);
962
+ return jsonResponse({ ok: true, ...inspected });
963
+ }
964
+
965
+ if (request.method === "GET" && pathname === "/db/tables") {
966
+ if (serverState.adapter) {
967
+ const result = await serverState.adapter.query(
968
+ `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type = 'BASE TABLE' ORDER BY table_name`,
969
+ );
970
+ return jsonResponse({
971
+ ok: true,
972
+ tables: result.rows.map((row) => String(row.table_name)),
973
+ });
974
+ }
975
+
976
+ return jsonResponse({
977
+ ok: true,
978
+ tables: Object.keys(tableMap).sort(),
979
+ });
980
+ }
981
+
982
+ if (request.method === "GET") {
983
+ const queryName = parseInvokeName(pathname, "/queries/");
984
+ if (queryName) {
985
+ return methodHelpResponse({
986
+ kind: "query",
987
+ name: queryName,
988
+ path: `/queries/${queryName}`,
989
+ });
990
+ }
991
+
992
+ const commandName = parseInvokeName(pathname, "/commands/");
993
+ if (commandName) {
994
+ return methodHelpResponse({
995
+ kind: "command",
996
+ name: commandName,
997
+ path: `/commands/${commandName}`,
998
+ });
999
+ }
1000
+
1001
+ const actionName = parseInvokeName(pathname, "/actions/");
1002
+ if (actionName) {
1003
+ return methodHelpResponse({
1004
+ kind: "action",
1005
+ name: actionName,
1006
+ path: `/actions/${actionName}`,
1007
+ });
1008
+ }
1009
+ }
1010
+
1011
+ if (request.method === "POST") {
1012
+ if (pathname === "/telemetry/flush") {
1013
+ if (!serverState.adapter) {
1014
+ return jsonResponse(
1015
+ {
1016
+ ok: false,
1017
+ diagnostics: [
1018
+ createDiagnostic({
1019
+ severity: "error",
1020
+ code: FORGE_DEV_SERVER_ERROR,
1021
+ message: "database not connected",
1022
+ }),
1023
+ ],
1024
+ },
1025
+ 400,
1026
+ );
1027
+ }
1028
+
1029
+ let bodySink: string | undefined;
1030
+ try {
1031
+ const body = (await request.json()) as { sink?: string };
1032
+ bodySink = body.sink;
1033
+ } catch {
1034
+ bodySink = undefined;
1035
+ }
1036
+
1037
+ const sinks = bodySink ? [bodySink] : telemetrySinks;
1038
+ const batch = await processTelemetryBatch(
1039
+ serverState.adapter,
1040
+ workspaceRoot,
1041
+ sinks,
1042
+ );
1043
+
1044
+ return jsonResponse({ ok: true, batch });
1045
+ }
1046
+
1047
+ if (pathname === "/outbox/process") {
1048
+ if (!serverState.adapter) {
1049
+ return jsonResponse(
1050
+ {
1051
+ ok: false,
1052
+ diagnostics: [
1053
+ createDiagnostic({
1054
+ severity: "error",
1055
+ code: FORGE_DEV_SERVER_ERROR,
1056
+ message: "database not connected",
1057
+ }),
1058
+ ],
1059
+ },
1060
+ 400,
1061
+ );
1062
+ }
1063
+
1064
+ const batch = await runWorkerTick(
1065
+ serverState.adapter,
1066
+ workspaceRoot,
1067
+ tableMap,
1068
+ currentRuntimeGraph.entries,
1069
+ { mock: options.mock },
1070
+ );
1071
+
1072
+ return jsonResponse({ ok: true, batch });
1073
+ }
1074
+
1075
+ if (pathname === "/workflows/process") {
1076
+ if (!serverState.adapter) {
1077
+ return jsonResponse(
1078
+ {
1079
+ ok: false,
1080
+ diagnostics: [
1081
+ createDiagnostic({
1082
+ severity: "error",
1083
+ code: FORGE_DEV_SERVER_ERROR,
1084
+ message: "database not connected",
1085
+ }),
1086
+ ],
1087
+ },
1088
+ 400,
1089
+ );
1090
+ }
1091
+
1092
+ const batch = await runWorkerTick(
1093
+ serverState.adapter,
1094
+ workspaceRoot,
1095
+ tableMap,
1096
+ currentRuntimeGraph.entries,
1097
+ { mock: options.mock },
1098
+ );
1099
+
1100
+ return jsonResponse({ ok: true, batch });
1101
+ }
1102
+
1103
+ const workflowRunPostMatch = pathname.match(/^\/workflows\/runs\/(\d+)\/(retry|cancel)$/);
1104
+ if (workflowRunPostMatch && serverState.adapter) {
1105
+ const runId = Number(workflowRunPostMatch[1]);
1106
+ const action = workflowRunPostMatch[2];
1107
+
1108
+ if (action === "retry") {
1109
+ const retried = await retryWorkflowRun(serverState.adapter, runId);
1110
+ return jsonResponse({ ok: retried, runId, status: retried ? "pending" : "not_found" });
1111
+ }
1112
+
1113
+ const canceled = await cancelWorkflowRun(serverState.adapter, runId);
1114
+ return jsonResponse({ ok: canceled, runId, status: canceled ? "canceled" : "not_found" });
1115
+ }
1116
+
1117
+ const workflowRunMatchPost = pathname.match(/^\/workflows\/([^/]+)\/run$/);
1118
+ if (workflowRunMatchPost && serverState.adapter) {
1119
+ const workflowName = decodeURIComponent(workflowRunMatchPost[1]!);
1120
+ let bodyInput: unknown = {};
1121
+ try {
1122
+ const body = (await request.json()) as { input?: unknown };
1123
+ bodyInput = body.input ?? {};
1124
+ } catch {
1125
+ bodyInput = {};
1126
+ }
1127
+
1128
+ const { workflows } = loadWorkflowRegistry(workspaceRoot);
1129
+ const idempotencyKey = `${workflowName}:manual:${hashStable(canonicalJson(bodyInput))}`;
1130
+ const result = await createWorkflowRun(serverState.adapter, workflows, {
1131
+ workflowName,
1132
+ input: bodyInput,
1133
+ triggerType: "manual",
1134
+ idempotencyKey,
1135
+ });
1136
+
1137
+ return jsonResponse({ ok: true, ...result });
1138
+ }
1139
+
1140
+ let entryName: string | null = null;
1141
+ let expectedKind: "command" | "action" | null = null;
1142
+ let queryName: string | null = null;
1143
+
1144
+ if (pathname.startsWith("/queries/")) {
1145
+ queryName = parseInvokeName(pathname, "/queries/");
1146
+ } else if (pathname.startsWith("/run/")) {
1147
+ entryName = parseInvokeName(pathname, "/run/");
1148
+ } else if (pathname.startsWith("/commands/")) {
1149
+ entryName = parseInvokeName(pathname, "/commands/");
1150
+ expectedKind = "command";
1151
+ } else if (pathname.startsWith("/actions/")) {
1152
+ entryName = parseInvokeName(pathname, "/actions/");
1153
+ expectedKind = "action";
1154
+ }
1155
+
1156
+ if (queryName) {
1157
+ if (!serverState.adapter) {
1158
+ return jsonResponse(
1159
+ {
1160
+ ok: false,
1161
+ diagnostics: [
1162
+ createDiagnostic({
1163
+ severity: "error",
1164
+ code: FORGE_DEV_SERVER_ERROR,
1165
+ message: "database not connected",
1166
+ }),
1167
+ ],
1168
+ },
1169
+ 400,
1170
+ );
1171
+ }
1172
+
1173
+ const args = await parseRequestArgs(request);
1174
+ const auth = await authenticateHeaders(request.headers, authConfig);
1175
+
1176
+ const result = await runQuery(
1177
+ workspaceRoot,
1178
+ queryName,
1179
+ { args, auth },
1180
+ {
1181
+ adapter: serverState.adapter,
1182
+ tableMap,
1183
+ },
1184
+ );
1185
+
1186
+ const policyDenied = result.diagnostics.some(
1187
+ (diagnostic) => diagnostic.code === FORGE_POLICY_DENIED,
1188
+ );
1189
+
1190
+ return jsonResponse(
1191
+ {
1192
+ ok: result.ok,
1193
+ result: result.result,
1194
+ traceId: result.traceId,
1195
+ diagnostics: result.diagnostics,
1196
+ },
1197
+ result.ok ? 200 : policyDenied ? 403 : 400,
1198
+ result.traceId ? { "x-forge-trace-id": result.traceId } : {},
1199
+ );
1200
+ }
1201
+
1202
+ if (entryName) {
1203
+ const entry = currentRuntimeGraph.entries.find(
1204
+ (candidate) => candidate.name === entryName,
1205
+ );
1206
+
1207
+ if (!entry) {
1208
+ return jsonResponse(
1209
+ {
1210
+ ok: false,
1211
+ diagnostics: [
1212
+ createDiagnostic({
1213
+ severity: "error",
1214
+ code: FORGE_RUNTIME_NOT_FOUND,
1215
+ message: `runtime entry '${entryName}' not found`,
1216
+ }),
1217
+ ],
1218
+ },
1219
+ 404,
1220
+ );
1221
+ }
1222
+
1223
+ if (expectedKind && entry.kind !== expectedKind) {
1224
+ return jsonResponse(
1225
+ {
1226
+ ok: false,
1227
+ diagnostics: [
1228
+ createDiagnostic({
1229
+ severity: "error",
1230
+ code: FORGE_DEV_INVOKE_FAILED,
1231
+ message: `entry '${entryName}' is a ${entry.kind}, not ${expectedKind}`,
1232
+ }),
1233
+ ],
1234
+ },
1235
+ 404,
1236
+ );
1237
+ }
1238
+
1239
+ const args = await parseRequestArgs(request);
1240
+ const auth = await authenticateHeaders(request.headers, authConfig);
1241
+
1242
+ await prepareRuntimeEnvironment(workspaceRoot, {
1243
+ mock: options.mock,
1244
+ db: serverState.adapter,
1245
+ });
1246
+
1247
+ const result = await runEntry(workspaceRoot, entryName, {
1248
+ json: options.json,
1249
+ mock: options.mock,
1250
+ args,
1251
+ db: serverState.adapter,
1252
+ auth,
1253
+ liveManager: liveManager ?? undefined,
1254
+ });
1255
+
1256
+ const policyDenied = result.diagnostics.some(
1257
+ (diagnostic) => diagnostic.code === FORGE_POLICY_DENIED,
1258
+ );
1259
+
1260
+ if (policyDenied) {
1261
+ const denied = result.diagnostics.find(
1262
+ (diagnostic) => diagnostic.code === FORGE_POLICY_DENIED,
1263
+ );
1264
+ return jsonResponse(
1265
+ {
1266
+ ok: false,
1267
+ error: {
1268
+ code: FORGE_POLICY_DENIED,
1269
+ message: denied?.message ?? "policy denied",
1270
+ },
1271
+ traceId: result.traceId,
1272
+ diagnostics: result.diagnostics,
1273
+ },
1274
+ 403,
1275
+ );
1276
+ }
1277
+
1278
+ return jsonResponse(
1279
+ {
1280
+ ok: result.ok,
1281
+ result: result.result,
1282
+ diagnostics: result.diagnostics,
1283
+ traceId: result.traceId,
1284
+ },
1285
+ result.ok ? 200 : 400,
1286
+ );
1287
+ }
1288
+ }
1289
+
1290
+ return jsonResponse(
1291
+ {
1292
+ ok: false,
1293
+ diagnostics: [
1294
+ createDiagnostic({
1295
+ severity: "error",
1296
+ code: FORGE_DEV_SERVER_ERROR,
1297
+ message: `unknown route ${request.method} ${pathname}`,
1298
+ fixHint: "Open GET / for the Forge API index, GET /entries for callable entries, or use POST /commands/:name and POST /queries/:name with JSON body {\"args\":{}}.",
1299
+ suggestedCommands: ["forge dev --once --json", "forge inspect frontend --json"],
1300
+ docs: ["AGENTS.md", "src/forge/_generated/agentContract.json"],
1301
+ }),
1302
+ ],
1303
+ },
1304
+ 404,
1305
+ );
1306
+ } catch (error) {
1307
+ if (error instanceof ForgeAuthError) {
1308
+ return jsonResponse(
1309
+ {
1310
+ ok: false,
1311
+ diagnostics: [
1312
+ createDiagnostic({
1313
+ severity: "error",
1314
+ code: error.code,
1315
+ message: error.message,
1316
+ }),
1317
+ ],
1318
+ },
1319
+ error.status,
1320
+ );
1321
+ }
1322
+ const message = errorMessage(error, "dev server request failed");
1323
+ return jsonResponse(
1324
+ {
1325
+ ok: false,
1326
+ diagnostics: [
1327
+ createDiagnostic({
1328
+ severity: "error",
1329
+ code: FORGE_DEV_SERVER_ERROR,
1330
+ message,
1331
+ }),
1332
+ ],
1333
+ },
1334
+ 500,
1335
+ );
1336
+ }
1337
+ });
1338
+
1339
+ const host = server.hostname ?? options.host;
1340
+ const port = server.port ?? options.port;
1341
+ const protocol = "http";
1342
+ const url = `${protocol}://${host}:${port}`;
1343
+
1344
+ return {
1345
+ host,
1346
+ port,
1347
+ url,
1348
+ routes: devManifest.routes,
1349
+ state: serverState,
1350
+ outboxWorker: serverState.outboxWorker,
1351
+ stop: () => {
1352
+ serverState.outboxWorker?.stop();
1353
+ liveManager?.stop();
1354
+ server.stop(true);
1355
+ restoreRuntimeEnvironment();
1356
+ const adapter = serverState.adapter;
1357
+ serverState.adapter = null;
1358
+ void adapter?.close().catch(() => undefined);
1359
+ },
1360
+ };
1361
+ }
1362
+
1363
+ export function resolveDevPort(explicit?: number): number {
1364
+ if (explicit !== undefined && Number.isFinite(explicit) && explicit >= 0) {
1365
+ return explicit;
1366
+ }
1367
+ const fromEnv = process.env.FORGE_DEV_PORT;
1368
+ if (fromEnv) {
1369
+ const parsed = Number(fromEnv);
1370
+ if (Number.isFinite(parsed) && parsed >= 0) {
1371
+ return parsed;
1372
+ }
1373
+ }
1374
+ return 3765;
1375
+ }
1376
+
1377
+ export function resolveDevHost(explicit?: string): string {
1378
+ return explicit ?? "127.0.0.1";
1379
+ }