forgeos 0.1.0-alpha.4 → 0.1.0-alpha.40

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 (415) hide show
  1. package/.npmignore +10 -0
  2. package/AGENTS.md +142 -90
  3. package/CHANGELOG.md +418 -0
  4. package/LICENSE +21 -0
  5. package/README.md +67 -8
  6. package/adapters/go/README.md +23 -0
  7. package/adapters/go/go.mod +3 -0
  8. package/adapters/go/http.go +149 -0
  9. package/adapters/go/registry.go +234 -0
  10. package/adapters/go/types.go +136 -0
  11. package/adapters/java/README.md +68 -0
  12. package/adapters/java/pom.xml +34 -0
  13. package/adapters/java/src/main/java/dev/forgeos/adapter/Auth.java +20 -0
  14. package/adapters/java/src/main/java/dev/forgeos/adapter/Diagnostic.java +16 -0
  15. package/adapters/java/src/main/java/dev/forgeos/adapter/Entry.java +38 -0
  16. package/adapters/java/src/main/java/dev/forgeos/adapter/EntryKind.java +16 -0
  17. package/adapters/java/src/main/java/dev/forgeos/adapter/ErrorInfo.java +4 -0
  18. package/adapters/java/src/main/java/dev/forgeos/adapter/Forge.java +94 -0
  19. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeCall.java +12 -0
  20. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeContext.java +11 -0
  21. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeHandler.java +8 -0
  22. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeHttpHandler.java +179 -0
  23. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeRegistry.java +121 -0
  24. package/adapters/java/src/main/java/dev/forgeos/adapter/Json.java +14 -0
  25. package/adapters/java/src/main/java/dev/forgeos/adapter/Manifest.java +14 -0
  26. package/adapters/java/src/main/java/dev/forgeos/adapter/RequestEnvelope.java +6 -0
  27. package/adapters/java/src/main/java/dev/forgeos/adapter/ResponseEnvelope.java +25 -0
  28. package/adapters/java/src/main/java/dev/forgeos/adapter/Risk.java +18 -0
  29. package/adapters/java/src/main/java/dev/forgeos/adapter/Schemas.java +36 -0
  30. package/adapters/java/src/main/java/dev/forgeos/adapter/Service.java +65 -0
  31. package/adapters/java/src/main/java/dev/forgeos/adapter/TransactionMode.java +18 -0
  32. package/adapters/java/src/main/java/dev/forgeos/adapter/TypedForgeHandler.java +6 -0
  33. package/adapters/java-spring-boot-starter/README.md +32 -0
  34. package/adapters/java-spring-boot-starter/pom.xml +36 -0
  35. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeCommand.java +22 -0
  36. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeExternalService.java +15 -0
  37. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeQuery.java +16 -0
  38. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeServiceBeanCondition.java +18 -0
  39. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeSpringAutoConfiguration.java +16 -0
  40. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeSpringRuntime.java +104 -0
  41. package/adapters/java-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +1 -0
  42. package/bin/forge.mjs +18 -0
  43. package/docs/cair-protocol.md +103 -0
  44. package/docs/changelog.md +534 -0
  45. package/docs/forge-protocol.md +189 -0
  46. package/examples/go-billing/go.mod +7 -0
  47. package/examples/go-billing/main.go +120 -0
  48. package/examples/java-billing/pom.xml +52 -0
  49. package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/CreateInvoiceInput.java +4 -0
  50. package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/Invoice.java +11 -0
  51. package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/Main.java +127 -0
  52. package/package.json +31 -3
  53. package/schemas/forge-manifest.schema.json +57 -0
  54. package/src/forge/_generated/releaseManifest.json +1 -2
  55. package/src/forge/_generated/releaseManifest.ts +3 -3
  56. package/src/forge/agent-adapters/index.ts +1584 -125
  57. package/src/forge/agent-adapters/types.ts +224 -1
  58. package/src/forge/agent-memory/bridge.ts +1333 -0
  59. package/src/forge/agent-memory/context-pack.ts +277 -0
  60. package/src/forge/agent-memory/hook-runner.ts +312 -0
  61. package/src/forge/agent-memory/mcp.ts +224 -0
  62. package/src/forge/agent-memory/normalize.ts +498 -0
  63. package/src/forge/agent-memory/redaction.ts +103 -0
  64. package/src/forge/agent-memory/sources/claude-code.ts +51 -0
  65. package/src/forge/agent-memory/sources/codex-hook-runner.mjs +273 -0
  66. package/src/forge/agent-memory/sources/codex.ts +119 -0
  67. package/src/forge/agent-memory/sources/cursor.ts +35 -0
  68. package/src/forge/agent-memory/types.ts +204 -0
  69. package/src/forge/bench.ts +248 -0
  70. package/src/forge/brownfield-import/index.ts +801 -0
  71. package/src/forge/brownfield-import/types.ts +127 -0
  72. package/src/forge/cair/action-journal.ts +61 -0
  73. package/src/forge/cair/action-parser.ts +314 -0
  74. package/src/forge/cair/action-validator.ts +40 -0
  75. package/src/forge/cair/actions.ts +1818 -0
  76. package/src/forge/cair/format.ts +77 -0
  77. package/src/forge/cair/index.ts +106 -0
  78. package/src/forge/cair/query.ts +478 -0
  79. package/src/forge/cair/snapshot.ts +315 -0
  80. package/src/forge/cair/types.ts +248 -0
  81. package/src/forge/cli/ai.ts +339 -21
  82. package/src/forge/cli/auth.ts +348 -10
  83. package/src/forge/cli/authmd.ts +441 -0
  84. package/src/forge/cli/baseline.ts +112 -0
  85. package/src/forge/cli/build.ts +20 -4
  86. package/src/forge/cli/changed.ts +437 -0
  87. package/src/forge/cli/codex-app-server.ts +877 -0
  88. package/src/forge/cli/commands.ts +1620 -20
  89. package/src/forge/cli/db.ts +209 -2
  90. package/src/forge/cli/deploy.ts +887 -0
  91. package/src/forge/cli/deps.ts +82 -13
  92. package/src/forge/cli/dev.ts +961 -46
  93. package/src/forge/cli/docs.ts +265 -0
  94. package/src/forge/cli/doctor.ts +297 -0
  95. package/src/forge/cli/field-test.ts +329 -0
  96. package/src/forge/cli/handoff.ts +336 -0
  97. package/src/forge/cli/index.ts +1 -0
  98. package/src/forge/cli/last-run.ts +84 -0
  99. package/src/forge/cli/main.ts +107 -3
  100. package/src/forge/cli/new.ts +152 -13
  101. package/src/forge/cli/next-actions.ts +23 -0
  102. package/src/forge/cli/output.ts +389 -6
  103. package/src/forge/cli/parse.ts +1050 -34
  104. package/src/forge/cli/progress.ts +51 -0
  105. package/src/forge/cli/query.ts +32 -0
  106. package/src/forge/cli/release.ts +35 -11
  107. package/src/forge/cli/rls.ts +43 -4
  108. package/src/forge/cli/run.ts +41 -0
  109. package/src/forge/cli/security.ts +112 -0
  110. package/src/forge/cli/self-host.ts +56 -14
  111. package/src/forge/cli/studio.ts +2218 -0
  112. package/src/forge/cli/verify.ts +1455 -32
  113. package/src/forge/cli/windows.ts +23 -0
  114. package/src/forge/cli/workos.ts +1020 -0
  115. package/src/forge/compiler/agent-contract/build.ts +670 -64
  116. package/src/forge/compiler/agent-contract/types.ts +45 -2
  117. package/src/forge/compiler/ai-registry/parse.ts +71 -2
  118. package/src/forge/compiler/api-surface/build.ts +47 -0
  119. package/src/forge/compiler/app-graph/build.ts +112 -9
  120. package/src/forge/compiler/app-graph/extract.ts +107 -0
  121. package/src/forge/compiler/app-graph/module-graph.ts +73 -78
  122. package/src/forge/compiler/app-graph/parser.ts +24 -24
  123. package/src/forge/compiler/app-graph/profile.ts +26 -0
  124. package/src/forge/compiler/app-graph/versions.ts +1 -1
  125. package/src/forge/compiler/classifier/capabilities.ts +3 -2
  126. package/src/forge/compiler/classifier/classify.ts +32 -8
  127. package/src/forge/compiler/classifier/secrets.ts +3 -2
  128. package/src/forge/compiler/classifier/signals.ts +91 -1
  129. package/src/forge/compiler/client-sdk/build-manifest.ts +59 -0
  130. package/src/forge/compiler/client-sdk/render-client.ts +295 -13
  131. package/src/forge/compiler/data-graph/parse.ts +20 -4
  132. package/src/forge/compiler/data-graph/sql/ddl.ts +144 -31
  133. package/src/forge/compiler/data-graph/sql/serialize.ts +4 -0
  134. package/src/forge/compiler/data-graph/sql/types.ts +1 -0
  135. package/src/forge/compiler/diagnostics/codes.ts +30 -0
  136. package/src/forge/compiler/diagnostics/create.ts +21 -2
  137. package/src/forge/compiler/diagnostics/index.ts +2 -0
  138. package/src/forge/compiler/emitter/barrel.ts +3 -0
  139. package/src/forge/compiler/emitter/render.ts +9 -0
  140. package/src/forge/compiler/external-manifest/registry.ts +205 -0
  141. package/src/forge/compiler/external-manifest/types.ts +91 -0
  142. package/src/forge/compiler/external-manifest/validate.ts +373 -0
  143. package/src/forge/compiler/frontend-graph/build.ts +142 -14
  144. package/src/forge/compiler/integration/add.ts +618 -24
  145. package/src/forge/compiler/integration/plan.ts +43 -12
  146. package/src/forge/compiler/integration/render.ts +29 -0
  147. package/src/forge/compiler/integration/snapshot.ts +2 -0
  148. package/src/forge/compiler/integration/templates/convex.ts +70 -0
  149. package/src/forge/compiler/integration/templates/index.ts +1 -0
  150. package/src/forge/compiler/integration/templates/render.ts +38 -0
  151. package/src/forge/compiler/integration/templates/types.ts +3 -0
  152. package/src/forge/compiler/integration/templates/workos.ts +1219 -0
  153. package/src/forge/compiler/make-registry/build.ts +8 -9
  154. package/src/forge/compiler/orchestrator/fast-check.ts +4 -0
  155. package/src/forge/compiler/orchestrator/generate-lock.ts +14 -3
  156. package/src/forge/compiler/orchestrator/manifest.ts +1 -1
  157. package/src/forge/compiler/orchestrator/plan-profile.ts +23 -0
  158. package/src/forge/compiler/orchestrator/plan.ts +247 -10
  159. package/src/forge/compiler/orchestrator/profile.ts +65 -0
  160. package/src/forge/compiler/orchestrator/run.ts +97 -31
  161. package/src/forge/compiler/orchestrator/serialize.ts +33 -8
  162. package/src/forge/compiler/orchestrator/types.ts +1 -1
  163. package/src/forge/compiler/package-graph/compiler.ts +3 -1
  164. package/src/forge/compiler/package-graph/constants.ts +1 -1
  165. package/src/forge/compiler/package-manager/adapter.ts +4 -1
  166. package/src/forge/compiler/package-manager/commands.ts +4 -0
  167. package/src/forge/compiler/package-manager/executor.ts +30 -1
  168. package/src/forge/compiler/package-upgrades/planner.ts +155 -18
  169. package/src/forge/compiler/package-upgrades/types.ts +2 -0
  170. package/src/forge/compiler/policy-registry/build.ts +47 -2
  171. package/src/forge/compiler/policy-registry/parse.ts +32 -2
  172. package/src/forge/compiler/recipes/definitions.ts +63 -0
  173. package/src/forge/compiler/recipes/index.ts +2 -0
  174. package/src/forge/compiler/recipes/registry.ts +6 -0
  175. package/src/forge/compiler/test-graph/build.ts +11 -3
  176. package/src/forge/compiler/types/app-graph.ts +8 -2
  177. package/src/forge/compiler/types/cli.ts +75 -1
  178. package/src/forge/compiler/types/dev-manifest.ts +4 -0
  179. package/src/forge/compiler/types/emit.ts +2 -0
  180. package/src/forge/compiler/types/frontend-graph.ts +2 -2
  181. package/src/forge/compiler/types/integration.ts +1 -0
  182. package/src/forge/compiler/types/package-graph.ts +6 -0
  183. package/src/forge/compiler/types/policy-registry.ts +3 -1
  184. package/src/forge/delta/classifier.ts +52 -0
  185. package/src/forge/delta/explain.ts +238 -0
  186. package/src/forge/delta/git-observer.ts +43 -0
  187. package/src/forge/delta/ids.ts +44 -0
  188. package/src/forge/delta/index.ts +25 -0
  189. package/src/forge/delta/recorder.ts +540 -0
  190. package/src/forge/delta/redaction.ts +50 -0
  191. package/src/forge/delta/schema.ts +240 -0
  192. package/src/forge/delta/session.ts +142 -0
  193. package/src/forge/delta/status.ts +1202 -0
  194. package/src/forge/delta/store.ts +3284 -0
  195. package/src/forge/delta/timeline.ts +178 -0
  196. package/src/forge/dev/server.ts +808 -17
  197. package/src/forge/dev/types.ts +15 -1
  198. package/src/forge/dev/watch.ts +17 -7
  199. package/src/forge/dev-console/cycle.ts +280 -33
  200. package/src/forge/dev-console/types.ts +47 -1
  201. package/src/forge/impact/index.ts +204 -9
  202. package/src/forge/impact/types.ts +33 -1
  203. package/src/forge/intent/index.ts +35 -16
  204. package/src/forge/make/fields.ts +26 -0
  205. package/src/forge/make/index.ts +40 -6
  206. package/src/forge/make/templates.ts +155 -2
  207. package/src/forge/make/types.ts +2 -1
  208. package/src/forge/policy.ts +1 -1
  209. package/src/forge/react/index.ts +160 -2
  210. package/src/forge/refactor/index.ts +1 -0
  211. package/src/forge/repair/rules/index.ts +2 -2
  212. package/src/forge/review/index.ts +158 -12
  213. package/src/forge/review/types.ts +15 -0
  214. package/src/forge/runtime/auth/config.ts +17 -0
  215. package/src/forge/runtime/auth/evaluate.ts +15 -2
  216. package/src/forge/runtime/auth/resolve.ts +61 -6
  217. package/src/forge/runtime/auth/types.ts +1 -0
  218. package/src/forge/runtime/db/factory.ts +1 -3
  219. package/src/forge/runtime/db/generated-client.ts +13 -2
  220. package/src/forge/runtime/db/memory-adapter.ts +139 -32
  221. package/src/forge/runtime/db/pglite-adapter.ts +263 -2
  222. package/src/forge/runtime/db/postgres-adapter.ts +6 -3
  223. package/src/forge/runtime/executor.ts +129 -16
  224. package/src/forge/runtime/external/bridge.ts +649 -0
  225. package/src/forge/runtime/runner/run-entry.ts +16 -7
  226. package/src/forge/runtime/telemetry/buffer.ts +4 -1
  227. package/src/forge/runtime/telemetry/scrubber.ts +35 -5
  228. package/src/forge/runtime/webhooks/security.ts +12 -7
  229. package/src/forge/server.ts +17 -3
  230. package/src/forge/ui/index.ts +209 -0
  231. package/src/forge/ui/types.ts +1 -0
  232. package/src/forge/version.ts +1 -1
  233. package/src/forge/vue/index.ts +422 -0
  234. package/src/forge/workspace/baseline.ts +112 -0
  235. package/src/forge/workspace/change-summary.ts +250 -0
  236. package/src/forge/workspace/forge-cli.ts +42 -0
  237. package/src/forge/workspace/git-summary.ts +391 -0
  238. package/templates/agent-workroom/AGENTS.md +29 -0
  239. package/templates/agent-workroom/README.md +34 -0
  240. package/templates/agent-workroom/forge.config.ts +3 -0
  241. package/templates/agent-workroom/package.json +33 -0
  242. package/templates/agent-workroom/src/actions/indexAgentSignal.ts +10 -0
  243. package/templates/agent-workroom/src/commands/openWorkroom.ts +61 -0
  244. package/templates/agent-workroom/src/commands/recordAgentSignal.ts +119 -0
  245. package/templates/agent-workroom/src/commands/recordCheckRun.ts +52 -0
  246. package/templates/agent-workroom/src/forge/schema.ts +54 -0
  247. package/templates/agent-workroom/src/policies.ts +6 -0
  248. package/templates/agent-workroom/src/queries/listWorkrooms.ts +11 -0
  249. package/templates/agent-workroom/src/queries/liveWorkroom.ts +63 -0
  250. package/templates/agent-workroom/tsconfig.json +16 -0
  251. package/templates/agent-workroom/web/index.html +12 -0
  252. package/templates/agent-workroom/web/package.json +21 -0
  253. package/templates/agent-workroom/web/src/App.tsx +345 -0
  254. package/templates/agent-workroom/web/src/lib/forge.ts +13 -0
  255. package/templates/agent-workroom/web/src/main.tsx +13 -0
  256. package/templates/agent-workroom/web/src/styles.css +545 -0
  257. package/templates/agent-workroom/web/tsconfig.json +27 -0
  258. package/templates/b2b-support-web/package.json +1 -0
  259. package/templates/b2b-support-web/web/package.json +1 -1
  260. package/templates/minimal-web/package.json +1 -1
  261. package/templates/minimal-web/web/package.json +2 -2
  262. package/templates/nuxt-web/.vscode/settings.json +14 -0
  263. package/templates/nuxt-web/README.md +30 -0
  264. package/templates/nuxt-web/forge.config.ts +3 -0
  265. package/templates/nuxt-web/package.json +33 -0
  266. package/templates/nuxt-web/src/actions/logNoteCreated.ts +11 -0
  267. package/templates/nuxt-web/src/commands/createNote.ts +26 -0
  268. package/templates/nuxt-web/src/forge/schema.ts +12 -0
  269. package/templates/nuxt-web/src/policies.ts +6 -0
  270. package/templates/nuxt-web/src/queries/listNotes.ts +8 -0
  271. package/templates/nuxt-web/src/queries/liveNotes.ts +8 -0
  272. package/templates/nuxt-web/tsconfig.json +17 -0
  273. package/templates/nuxt-web/web/app.vue +67 -0
  274. package/templates/nuxt-web/web/components/LiveNotes.vue +89 -0
  275. package/templates/nuxt-web/web/components/NoteComposer.vue +100 -0
  276. package/templates/nuxt-web/web/composables/forge.ts +13 -0
  277. package/templates/nuxt-web/web/composables/useNotes.ts +24 -0
  278. package/templates/nuxt-web/web/nuxt.config.ts +11 -0
  279. package/templates/nuxt-web/web/package.json +18 -0
  280. package/templates/nuxt-web/web/plugins/forge.client.ts +10 -0
  281. package/templates/nuxt-web/web/plugins/forge.server.ts +10 -0
  282. package/templates/nuxt-web/web/server/api/forge-health.get.ts +7 -0
  283. package/templates/nuxt-web/web/tsconfig.json +6 -0
  284. package/src/forge/_generated/actionSubscriptions.json +0 -2
  285. package/src/forge/_generated/actionSubscriptions.ts +0 -10
  286. package/src/forge/_generated/agentAdapterManifest.json +0 -2
  287. package/src/forge/_generated/agentAdapterManifest.ts +0 -73
  288. package/src/forge/_generated/agentContract.json +0 -2
  289. package/src/forge/_generated/agentContract.ts +0 -7829
  290. package/src/forge/_generated/agentQuickstart.md +0 -34
  291. package/src/forge/_generated/agentTools.json +0 -2
  292. package/src/forge/_generated/agentTools.md +0 -16
  293. package/src/forge/_generated/agentTools.ts +0 -12
  294. package/src/forge/_generated/aiContext.ts +0 -125
  295. package/src/forge/_generated/aiModels.json +0 -2
  296. package/src/forge/_generated/aiModels.ts +0 -51
  297. package/src/forge/_generated/aiProviders.json +0 -2
  298. package/src/forge/_generated/aiProviders.ts +0 -23
  299. package/src/forge/_generated/aiRegistry.json +0 -2
  300. package/src/forge/_generated/aiRegistry.ts +0 -31
  301. package/src/forge/_generated/api.json +0 -2
  302. package/src/forge/_generated/api.ts +0 -8
  303. package/src/forge/_generated/appGraph.json +0 -2
  304. package/src/forge/_generated/appGraph.ts +0 -14919
  305. package/src/forge/_generated/appMap.md +0 -55
  306. package/src/forge/_generated/artifactManifest.json +0 -2
  307. package/src/forge/_generated/artifactManifest.ts +0 -7
  308. package/src/forge/_generated/authClaims.json +0 -2
  309. package/src/forge/_generated/authClaims.ts +0 -13
  310. package/src/forge/_generated/authConfig.json +0 -2
  311. package/src/forge/_generated/authConfig.ts +0 -17
  312. package/src/forge/_generated/authContext.ts +0 -23
  313. package/src/forge/_generated/authRegistry.json +0 -2
  314. package/src/forge/_generated/authRegistry.ts +0 -25
  315. package/src/forge/_generated/buildInfo.json +0 -2
  316. package/src/forge/_generated/buildInfo.ts +0 -9
  317. package/src/forge/_generated/capabilityMap.json +0 -2
  318. package/src/forge/_generated/capabilityMap.md +0 -15
  319. package/src/forge/_generated/capabilityMap.ts +0 -17
  320. package/src/forge/_generated/client.ts +0 -282
  321. package/src/forge/_generated/clientApi.ts +0 -9
  322. package/src/forge/_generated/clientManifest.json +0 -2
  323. package/src/forge/_generated/clientManifest.ts +0 -39
  324. package/src/forge/_generated/clientTypes.ts +0 -78
  325. package/src/forge/_generated/configRegistry.json +0 -2
  326. package/src/forge/_generated/configRegistry.ts +0 -4
  327. package/src/forge/_generated/dataGraph.json +0 -2
  328. package/src/forge/_generated/dataGraph.ts +0 -8
  329. package/src/forge/_generated/db.json +0 -2
  330. package/src/forge/_generated/db.ts +0 -2
  331. package/src/forge/_generated/dbSecurityManifest.json +0 -2
  332. package/src/forge/_generated/dbSecurityManifest.ts +0 -15
  333. package/src/forge/_generated/dbSessionContext.json +0 -2
  334. package/src/forge/_generated/dbSessionContext.ts +0 -39
  335. package/src/forge/_generated/deployManifest.json +0 -2
  336. package/src/forge/_generated/deployManifest.ts +0 -14
  337. package/src/forge/_generated/devManifest.json +0 -2
  338. package/src/forge/_generated/devManifest.ts +0 -62
  339. package/src/forge/_generated/envSchema.json +0 -2
  340. package/src/forge/_generated/envSchema.ts +0 -59
  341. package/src/forge/_generated/frontendGraph.json +0 -2
  342. package/src/forge/_generated/frontendGraph.ts +0 -27
  343. package/src/forge/_generated/importGuards.json +0 -2
  344. package/src/forge/_generated/importGuards.ts +0 -686
  345. package/src/forge/_generated/index.ts +0 -68
  346. package/src/forge/_generated/liveProductionManifest.json +0 -2
  347. package/src/forge/_generated/liveProductionManifest.ts +0 -23
  348. package/src/forge/_generated/liveProtocol.json +0 -2
  349. package/src/forge/_generated/liveProtocol.ts +0 -21
  350. package/src/forge/_generated/liveQueryRegistry.json +0 -2
  351. package/src/forge/_generated/liveQueryRegistry.ts +0 -9
  352. package/src/forge/_generated/liveTransportConfig.json +0 -2
  353. package/src/forge/_generated/liveTransportConfig.ts +0 -19
  354. package/src/forge/_generated/makeRegistry.json +0 -2
  355. package/src/forge/_generated/makeRegistry.ts +0 -177
  356. package/src/forge/_generated/makeTemplates.json +0 -2
  357. package/src/forge/_generated/makeTemplates.ts +0 -66
  358. package/src/forge/_generated/mockMap.json +0 -2
  359. package/src/forge/_generated/mockMap.ts +0 -7
  360. package/src/forge/_generated/operationPlaybooks.md +0 -167
  361. package/src/forge/_generated/packageGraph.json +0 -2
  362. package/src/forge/_generated/packageGraph.ts +0 -249334
  363. package/src/forge/_generated/packageUpgradeRegistry.json +0 -2
  364. package/src/forge/_generated/packageUpgradeRegistry.ts +0 -15
  365. package/src/forge/_generated/permissionMatrix.json +0 -2
  366. package/src/forge/_generated/permissionMatrix.ts +0 -7
  367. package/src/forge/_generated/policyRegistry.json +0 -2
  368. package/src/forge/_generated/policyRegistry.ts +0 -11
  369. package/src/forge/_generated/queryRegistry.json +0 -2
  370. package/src/forge/_generated/queryRegistry.ts +0 -9
  371. package/src/forge/_generated/react.d.ts +0 -22
  372. package/src/forge/_generated/react.ts +0 -29
  373. package/src/forge/_generated/reactManifest.json +0 -2
  374. package/src/forge/_generated/reactManifest.ts +0 -19
  375. package/src/forge/_generated/rlsPolicies.json +0 -2
  376. package/src/forge/_generated/rlsPolicies.sql +0 -34
  377. package/src/forge/_generated/rlsPolicies.ts +0 -6
  378. package/src/forge/_generated/runtimeGraph.json +0 -2
  379. package/src/forge/_generated/runtimeGraph.ts +0 -8
  380. package/src/forge/_generated/runtimeMatrix.json +0 -2
  381. package/src/forge/_generated/runtimeMatrix.ts +0 -334130
  382. package/src/forge/_generated/runtimeRegistry.ts +0 -2
  383. package/src/forge/_generated/runtimeRules.md +0 -91
  384. package/src/forge/_generated/secretRegistry.json +0 -2
  385. package/src/forge/_generated/secretRegistry.ts +0 -50
  386. package/src/forge/_generated/secretsContext.ts +0 -11
  387. package/src/forge/_generated/serverApi.ts +0 -10
  388. package/src/forge/_generated/sourceMapManifest.json +0 -2
  389. package/src/forge/_generated/sourceMapManifest.ts +0 -7
  390. package/src/forge/_generated/sqlPlan.json +0 -2
  391. package/src/forge/_generated/sqlPlan.ts +0 -88
  392. package/src/forge/_generated/subscriptionManifest.json +0 -2
  393. package/src/forge/_generated/subscriptionManifest.ts +0 -7
  394. package/src/forge/_generated/symbolicationManifest.json +0 -2
  395. package/src/forge/_generated/symbolicationManifest.ts +0 -17
  396. package/src/forge/_generated/telemetryRegistry.json +0 -2
  397. package/src/forge/_generated/telemetryRegistry.ts +0 -9
  398. package/src/forge/_generated/telemetrySinks.json +0 -2
  399. package/src/forge/_generated/telemetrySinks.ts +0 -11
  400. package/src/forge/_generated/tenantScope.json +0 -2
  401. package/src/forge/_generated/tenantScope.ts +0 -8
  402. package/src/forge/_generated/testGraph.json +0 -2
  403. package/src/forge/_generated/testGraph.ts +0 -3430
  404. package/src/forge/_generated/testPlanRegistry.json +0 -2
  405. package/src/forge/_generated/testPlanRegistry.ts +0 -33
  406. package/src/forge/_generated/uiRoutes.json +0 -2
  407. package/src/forge/_generated/uiRoutes.ts +0 -16
  408. package/src/forge/_generated/uiScenarios.json +0 -2
  409. package/src/forge/_generated/uiScenarios.ts +0 -30
  410. package/src/forge/_generated/uiTestManifest.json +0 -2
  411. package/src/forge/_generated/uiTestManifest.ts +0 -27
  412. package/src/forge/_generated/workflowRegistry.json +0 -2
  413. package/src/forge/_generated/workflowRegistry.ts +0 -9
  414. package/src/forge/_generated/workflowSubscriptions.json +0 -2
  415. package/src/forge/_generated/workflowSubscriptions.ts +0 -10
@@ -1,8 +1,11 @@
1
1
  import { spawn } from "node:child_process";
2
+ import { closeSync, readFileSync, readlinkSync, openSync } from "node:fs";
3
+ import { createServer as createNetServer } from "node:net";
2
4
  import { run } from "../compiler/orchestrator/run.ts";
3
- import { join } from "node:path";
5
+ import { basename, join } from "node:path";
4
6
  import { nodeFileSystem } from "../compiler/fs/index.ts";
5
7
  import { stripDeterministicHeader } from "../compiler/primitives/header.ts";
8
+ import { hashStable } from "../compiler/primitives/hash.ts";
6
9
  import { GENERATED_DIR } from "../compiler/emitter/constants.ts";
7
10
  import type { FrontendGraph } from "../compiler/types/frontend-graph.ts";
8
11
  import { detectPackageManager } from "../compiler/package-manager/detect.ts";
@@ -12,6 +15,12 @@ import {
12
15
  formatDevConsoleJson,
13
16
  runDevConsoleCycle,
14
17
  } from "../dev-console/cycle.ts";
18
+ import type {
19
+ DevConsoleAgentContext,
20
+ DevConsoleCycle,
21
+ DevConsoleGeneratedSummary,
22
+ DevConsoleSummary,
23
+ } from "../dev-console/types.ts";
15
24
  import {
16
25
  resolveDevHost,
17
26
  resolveDevPort,
@@ -19,6 +28,12 @@ import {
19
28
  } from "../dev/server.ts";
20
29
  import { startDevWatch } from "../dev/watch.ts";
21
30
  import type { DevServerHandle } from "../dev/types.ts";
31
+ import { createAmbientDeltaRecorder } from "../delta/index.ts";
32
+ import { resetCompileSessions } from "../compiler/orchestrator/session.ts";
33
+ import { FORGE_PGLITE_STORE_ABORTED } from "../compiler/diagnostics/codes.ts";
34
+ import { isPgliteAbortMessage } from "../runtime/db/pglite-adapter.ts";
35
+ import { forgeCliCommandForWorkspace, forgeCliCommandsForWorkspace } from "../workspace/forge-cli.ts";
36
+ import { writeLastRunRecord } from "./last-run.ts";
22
37
 
23
38
  export interface DevCommandOptions {
24
39
  workspaceRoot: string;
@@ -29,7 +44,7 @@ export interface DevCommandOptions {
29
44
  once?: boolean;
30
45
  watch: boolean;
31
46
  json: boolean;
32
- db: "pglite" | "postgres" | "none";
47
+ db: "memory" | "pglite" | "postgres" | "none";
33
48
  databaseUrl?: string;
34
49
  worker: boolean;
35
50
  withWeb?: boolean;
@@ -41,6 +56,9 @@ export interface DevCommandOptions {
41
56
  envFile?: string;
42
57
  mode?: "dev" | "serve";
43
58
  allowDevAuth?: boolean;
59
+ skipStartupConsole?: boolean;
60
+ detach?: boolean;
61
+ lifecycle?: "status" | "stop";
44
62
  }
45
63
 
46
64
  export interface DevCommandResult {
@@ -49,9 +67,30 @@ export interface DevCommandResult {
49
67
  exitCode: 0 | 1;
50
68
  }
51
69
 
70
+ export interface DevGenerateResult {
71
+ ok: boolean;
72
+ changed: string[];
73
+ unchanged: string[];
74
+ diagnostics: Array<{ severity: string; code: string; message: string }>;
75
+ exitCode: 0 | 1;
76
+ }
77
+
78
+ interface DevStartupGeneratedEvidence {
79
+ ok: boolean;
80
+ state: "fresh" | "regenerated" | "stale-risk";
81
+ changedFiles: number;
82
+ sampleChanged: string[];
83
+ hiddenChanged: number;
84
+ message: string;
85
+ command: string;
86
+ checkCommand: string;
87
+ }
88
+
52
89
  interface WebDevServerHandle {
53
90
  url: string;
54
91
  port: number;
92
+ requestedPort?: number;
93
+ autoPortSelected: boolean;
55
94
  command: string[];
56
95
  stop: () => void;
57
96
  }
@@ -60,6 +99,7 @@ interface DevStartupSummary {
60
99
  schemaVersion: "0.1.0";
61
100
  ok: true;
62
101
  mode: "dev";
102
+ warnings: Array<{ code: string; message: string; nextAction?: string }>;
63
103
  api: {
64
104
  url: string;
65
105
  host: string;
@@ -70,14 +110,25 @@ interface DevStartupSummary {
70
110
  web: null | {
71
111
  url: string;
72
112
  port: number;
113
+ requestedPort?: number;
114
+ autoPortSelected: boolean;
73
115
  command: string[];
74
116
  framework: string;
75
117
  routes: string[];
76
118
  bridgeFiles: string[];
77
119
  apiUrlEnv?: string;
78
120
  };
121
+ preview: {
122
+ studioUrl?: string;
123
+ targetAppUrl: string;
124
+ targetAppPort: number;
125
+ isStudioSelfPreview: boolean;
126
+ note: string;
127
+ };
79
128
  watch: {
80
129
  enabled: boolean;
130
+ autoGenerate: boolean;
131
+ reloadsRuntime: boolean;
81
132
  };
82
133
  runtime: {
83
134
  routes: Array<{ method: string; path: string; purpose: string }>;
@@ -90,6 +141,20 @@ interface DevStartupSummary {
90
141
  bridgeFiles: string[];
91
142
  diagnostics: number;
92
143
  };
144
+ generated: {
145
+ state: "fresh" | "regenerated" | "stale-risk";
146
+ generatorVersion?: string;
147
+ inputHash?: string;
148
+ buildInfoHash?: string;
149
+ runtimeLoadedHash?: string;
150
+ runtimeStaleRisk: boolean;
151
+ changedFiles: number;
152
+ sampleChanged: string[];
153
+ hiddenChanged: number;
154
+ command: string;
155
+ checkCommand: string;
156
+ message: string;
157
+ };
93
158
  next: {
94
159
  browserUrl: string;
95
160
  apiIndex: string;
@@ -99,6 +164,504 @@ interface DevStartupSummary {
99
164
  pid: number;
100
165
  }
101
166
 
167
+ interface DevStartFailure {
168
+ message: string;
169
+ code?: string;
170
+ failureKind: "port_busy" | "pglite_store_aborted" | "dev_start_failed";
171
+ nextActions: string[];
172
+ busy?: {
173
+ port: number;
174
+ host: string;
175
+ suggestedCommands: string[];
176
+ };
177
+ }
178
+
179
+ const DEV_STATE_DIR = ".forge/dev";
180
+ const DEV_PID_FILE = `${DEV_STATE_DIR}/dev.pid`;
181
+ const DEV_LOG_FILE = `${DEV_STATE_DIR}/dev.log`;
182
+
183
+ function devStatePaths(workspaceRoot: string): { dir: string; pidFile: string; logFile: string } {
184
+ return {
185
+ dir: join(workspaceRoot, DEV_STATE_DIR),
186
+ pidFile: join(workspaceRoot, DEV_PID_FILE),
187
+ logFile: join(workspaceRoot, DEV_LOG_FILE),
188
+ };
189
+ }
190
+
191
+ function readDetachedDevPid(workspaceRoot: string): number | null {
192
+ const text = nodeFileSystem.readText(devStatePaths(workspaceRoot).pidFile);
193
+ if (text === null) {
194
+ return null;
195
+ }
196
+ const pid = Number(text.trim());
197
+ return Number.isInteger(pid) && pid > 0 ? pid : null;
198
+ }
199
+
200
+ function isProcessRunning(pid: number): boolean {
201
+ try {
202
+ process.kill(pid, 0);
203
+ return true;
204
+ } catch (error) {
205
+ return error instanceof Error && "code" in error && error.code === "EPERM";
206
+ }
207
+ }
208
+
209
+ function removeDevPidFile(workspaceRoot: string): void {
210
+ const paths = devStatePaths(workspaceRoot);
211
+ if (nodeFileSystem.exists(paths.pidFile)) {
212
+ nodeFileSystem.remove(paths.pidFile);
213
+ }
214
+ }
215
+
216
+ function processDetails(pid: number): { pid: number; cwd?: string; command?: string } {
217
+ const details: { pid: number; cwd?: string; command?: string } = { pid };
218
+ try {
219
+ details.cwd = readlinkSync(`/proc/${pid}/cwd`);
220
+ } catch {
221
+ // /proc is Linux-only; process metadata is best-effort.
222
+ }
223
+ try {
224
+ const raw = readFileSync(`/proc/${pid}/cmdline`, "utf8");
225
+ const command = raw.split("\0").filter(Boolean).join(" ");
226
+ if (command) details.command = command;
227
+ } catch {
228
+ // /proc is Linux-only; process metadata is best-effort.
229
+ }
230
+ return details;
231
+ }
232
+
233
+ function printDevLifecycleResult(input: {
234
+ options: DevCommandOptions;
235
+ ok: boolean;
236
+ action: "detach" | "status" | "stop";
237
+ running: boolean;
238
+ pid?: number;
239
+ process?: { pid: number; cwd?: string; command?: string };
240
+ logFile: string;
241
+ message: string;
242
+ exitCode: 0 | 1;
243
+ }): DevCommandResult {
244
+ const payload = {
245
+ ok: input.ok,
246
+ action: input.action,
247
+ running: input.running,
248
+ ...(input.pid !== undefined ? { pid: input.pid } : {}),
249
+ ...(input.process ? { process: input.process } : {}),
250
+ logFile: input.logFile,
251
+ message: input.message,
252
+ nextActions: input.running
253
+ ? ["forge dev status --json", "forge dev stop --json", `tail -f ${input.logFile}`]
254
+ : ["forge dev --detach --json"],
255
+ exitCode: input.exitCode,
256
+ };
257
+ if (input.options.json) {
258
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
259
+ } else {
260
+ process.stdout.write(`${input.message}\n`);
261
+ if (input.pid !== undefined) process.stdout.write(`pid: ${input.pid}\n`);
262
+ if (input.process?.cwd) process.stdout.write(`cwd: ${input.process.cwd}\n`);
263
+ if (input.process?.command) process.stdout.write(`command: ${input.process.command}\n`);
264
+ process.stdout.write(`log: ${input.logFile}\n`);
265
+ }
266
+ return { exitCode: input.exitCode };
267
+ }
268
+
269
+ function buildDetachedDevArgs(options: DevCommandOptions): string[] {
270
+ const args = ["dev", "--skip-startup-console"];
271
+ if (options.host) args.push("--host", options.host);
272
+ if (options.port !== undefined) args.push("--port", String(options.port));
273
+ if (options.mock) args.push("--mock");
274
+ if (options.mockAi) args.push("--mock-ai");
275
+ if (!options.watch) args.push("--no-watch");
276
+ if (options.db) args.push("--db", options.db);
277
+ if (options.databaseUrl) args.push("--database-url", options.databaseUrl);
278
+ if (!options.worker) args.push("--no-worker");
279
+ if (options.withWeb === false) args.push("--no-web");
280
+ if (options.apiOnly) args.push("--api-only");
281
+ if (options.webOnly) args.push("--web-only");
282
+ if (options.open) args.push("--open");
283
+ if (options.webPort !== undefined) args.push("--web-port", String(options.webPort));
284
+ if (options.telemetry.length > 0) args.push("--telemetry", options.telemetry.join(","));
285
+ if (options.envFile) args.push("--env-file", options.envFile);
286
+ return args;
287
+ }
288
+
289
+ function runDevStatus(options: DevCommandOptions): DevCommandResult {
290
+ const paths = devStatePaths(options.workspaceRoot);
291
+ const pid = readDetachedDevPid(options.workspaceRoot);
292
+ const running = pid !== null && isProcessRunning(pid);
293
+ if (pid !== null && !running) {
294
+ removeDevPidFile(options.workspaceRoot);
295
+ }
296
+ return printDevLifecycleResult({
297
+ options,
298
+ ok: true,
299
+ action: "status",
300
+ running,
301
+ ...(pid !== null ? { pid } : {}),
302
+ ...(pid !== null && running ? { process: processDetails(pid) } : {}),
303
+ logFile: paths.logFile,
304
+ message: running ? "forge dev detached server is running" : "forge dev detached server is not running",
305
+ exitCode: 0,
306
+ });
307
+ }
308
+
309
+ function runDevStop(options: DevCommandOptions): DevCommandResult {
310
+ const paths = devStatePaths(options.workspaceRoot);
311
+ const pid = readDetachedDevPid(options.workspaceRoot);
312
+ if (pid === null || !isProcessRunning(pid)) {
313
+ removeDevPidFile(options.workspaceRoot);
314
+ return printDevLifecycleResult({
315
+ options,
316
+ ok: true,
317
+ action: "stop",
318
+ running: false,
319
+ ...(pid !== null ? { pid } : {}),
320
+ ...(pid !== null ? { process: processDetails(pid) } : {}),
321
+ logFile: paths.logFile,
322
+ message: "no detached forge dev server was running",
323
+ exitCode: 0,
324
+ });
325
+ }
326
+ process.kill(pid, "SIGTERM");
327
+ removeDevPidFile(options.workspaceRoot);
328
+ return printDevLifecycleResult({
329
+ options,
330
+ ok: true,
331
+ action: "stop",
332
+ running: false,
333
+ pid,
334
+ process: processDetails(pid),
335
+ logFile: paths.logFile,
336
+ message: "stopped detached forge dev server",
337
+ exitCode: 0,
338
+ });
339
+ }
340
+
341
+ function runDevDetach(options: DevCommandOptions): DevCommandResult {
342
+ if (options.once) {
343
+ return printDevLifecycleResult({
344
+ options,
345
+ ok: false,
346
+ action: "detach",
347
+ running: false,
348
+ logFile: devStatePaths(options.workspaceRoot).logFile,
349
+ message: "forge dev --detach cannot be combined with --once",
350
+ exitCode: 1,
351
+ });
352
+ }
353
+ const existingPid = readDetachedDevPid(options.workspaceRoot);
354
+ const paths = devStatePaths(options.workspaceRoot);
355
+ nodeFileSystem.mkdirp(paths.dir);
356
+ if (existingPid !== null && isProcessRunning(existingPid)) {
357
+ return printDevLifecycleResult({
358
+ options,
359
+ ok: true,
360
+ action: "detach",
361
+ running: true,
362
+ pid: existingPid,
363
+ process: processDetails(existingPid),
364
+ logFile: paths.logFile,
365
+ message: "forge dev detached server is already running",
366
+ exitCode: 0,
367
+ });
368
+ }
369
+
370
+ const cliPath = process.argv[1];
371
+ if (!cliPath) {
372
+ return printDevLifecycleResult({
373
+ options,
374
+ ok: false,
375
+ action: "detach",
376
+ running: false,
377
+ logFile: paths.logFile,
378
+ message: "could not resolve current forge CLI path for detached dev server",
379
+ exitCode: 1,
380
+ });
381
+ }
382
+
383
+ const fd = openSync(paths.logFile, "a");
384
+ try {
385
+ const child = spawn(process.execPath, [cliPath, ...buildDetachedDevArgs(options)], {
386
+ cwd: options.workspaceRoot,
387
+ detached: true,
388
+ stdio: ["ignore", fd, fd],
389
+ windowsHide: true,
390
+ });
391
+ if (child.pid === undefined) {
392
+ return printDevLifecycleResult({
393
+ options,
394
+ ok: false,
395
+ action: "detach",
396
+ running: false,
397
+ logFile: paths.logFile,
398
+ message: "detached forge dev process started without a pid",
399
+ exitCode: 1,
400
+ });
401
+ }
402
+ child.unref();
403
+ nodeFileSystem.writeText(paths.pidFile, `${child.pid}\n`);
404
+ return printDevLifecycleResult({
405
+ options,
406
+ ok: true,
407
+ action: "detach",
408
+ running: true,
409
+ pid: child.pid,
410
+ process: processDetails(child.pid),
411
+ logFile: paths.logFile,
412
+ message: "started detached forge dev server",
413
+ exitCode: 0,
414
+ });
415
+ } finally {
416
+ closeSync(fd);
417
+ }
418
+ }
419
+
420
+ function nextPreviewPort(webUrl?: string): number {
421
+ if (!webUrl) {
422
+ return 5174;
423
+ }
424
+ try {
425
+ const parsed = new URL(webUrl);
426
+ const port = parsed.port ? Number(parsed.port) : parsed.protocol === "https:" ? 443 : 80;
427
+ return Number.isFinite(port) && port > 0 ? port + 1 : 5174;
428
+ } catch {
429
+ return 5174;
430
+ }
431
+ }
432
+
433
+ function isForgeStudioWorkspace(workspaceRoot: string): boolean {
434
+ return basename(workspaceRoot).toLowerCase() === "forge-studio";
435
+ }
436
+
437
+ function previewSummaryFor(input: {
438
+ workspaceRoot: string;
439
+ host: string;
440
+ webUrl?: string;
441
+ }): DevStartupSummary["preview"] {
442
+ if (input.webUrl && !isForgeStudioWorkspace(input.workspaceRoot)) {
443
+ const parsed = new URL(input.webUrl);
444
+ const targetAppPort = parsed.port ? Number(parsed.port) : parsed.protocol === "https:" ? 443 : 80;
445
+ return {
446
+ targetAppUrl: input.webUrl,
447
+ targetAppPort,
448
+ isStudioSelfPreview: false,
449
+ note: `Web app preview is running at ${input.webUrl}.`,
450
+ };
451
+ }
452
+
453
+ const targetAppPort = nextPreviewPort(input.webUrl);
454
+ const targetAppUrl = `http://${input.host}:${targetAppPort}`;
455
+ const isStudioSelfPreview = Boolean(input.webUrl && input.webUrl === targetAppUrl);
456
+ return {
457
+ ...(input.webUrl ? { studioUrl: input.webUrl } : {}),
458
+ targetAppUrl,
459
+ targetAppPort,
460
+ isStudioSelfPreview,
461
+ note: input.webUrl
462
+ ? `Use ${targetAppUrl} for the app being built when ${input.webUrl} is Forge Studio itself.`
463
+ : `No web app was detected; ${targetAppUrl} is the default target app preview URL for Studio attach flows.`,
464
+ };
465
+ }
466
+
467
+ async function isPortAvailable(host: string, port: number): Promise<boolean> {
468
+ if (port === 0) {
469
+ return true;
470
+ }
471
+ const server = createNetServer();
472
+ return new Promise<boolean>((resolve) => {
473
+ const cleanup = () => {
474
+ server.removeAllListeners();
475
+ };
476
+ server.once("error", () => {
477
+ cleanup();
478
+ resolve(false);
479
+ });
480
+ server.listen(port, host, () => {
481
+ server.close(() => {
482
+ cleanup();
483
+ resolve(true);
484
+ });
485
+ });
486
+ });
487
+ }
488
+
489
+ function classifyDevStartFailure(input: {
490
+ rawMessage: string;
491
+ host: string;
492
+ port: number;
493
+ webPort?: number;
494
+ db: DevCommandOptions["db"];
495
+ }): DevStartFailure {
496
+ const lowerMessage = input.rawMessage.toLowerCase();
497
+ const busy =
498
+ lowerMessage.includes("eaddrinuse") ||
499
+ lowerMessage.includes("address already in use") ||
500
+ /port\s+\d+\s+.*in use/i.test(input.rawMessage);
501
+ if (busy) {
502
+ const suggestedCommands = [
503
+ "forge dev status --json",
504
+ "forge dev stop --json",
505
+ `forge dev --port 0${input.webPort ? ` --web-port ${input.webPort}` : ""} --json`,
506
+ "forge doctor windows --json",
507
+ ];
508
+ return {
509
+ message: `${input.rawMessage}. Port ${input.port} appears busy; stop the existing process or rerun with --port 0 / --port <free-port>.`,
510
+ failureKind: "port_busy",
511
+ nextActions: suggestedCommands,
512
+ busy: {
513
+ port: input.port,
514
+ host: input.host,
515
+ suggestedCommands,
516
+ },
517
+ };
518
+ }
519
+
520
+ if (input.db === "pglite" && isPgliteAbortMessage(input.rawMessage)) {
521
+ return {
522
+ message: `${input.rawMessage}. Local PGlite store may be corrupted or stale at .forge/pglite.`,
523
+ code: FORGE_PGLITE_STORE_ABORTED,
524
+ failureKind: "pglite_store_aborted",
525
+ nextActions: [
526
+ "forge doctor pglite --json",
527
+ "forge db repair --local --adapter pglite --json",
528
+ "forge dev --db memory --json",
529
+ ],
530
+ };
531
+ }
532
+
533
+ return {
534
+ message: input.rawMessage,
535
+ failureKind: "dev_start_failed",
536
+ nextActions: ["forge dev --once --json", "forge check --json"],
537
+ };
538
+ }
539
+
540
+ function writeDevStartFailure(input: {
541
+ workspaceRoot: string;
542
+ startedAt: Date;
543
+ finishedAt: Date;
544
+ options: DevCommandOptions;
545
+ host: string;
546
+ port: number;
547
+ failure: DevStartFailure;
548
+ }): DevCommandResult {
549
+ const nextActions = forgeCliCommandsForWorkspace(input.workspaceRoot, input.failure.nextActions);
550
+ const busy = input.failure.busy
551
+ ? {
552
+ ...input.failure.busy,
553
+ suggestedCommands: forgeCliCommandsForWorkspace(input.workspaceRoot, input.failure.busy.suggestedCommands),
554
+ }
555
+ : undefined;
556
+ writeLastRunRecord(input.workspaceRoot, {
557
+ schemaVersion: "0.1.0",
558
+ command: forgeCliCommandForWorkspace(input.workspaceRoot, "forge dev"),
559
+ ok: false,
560
+ startedAt: input.startedAt.toISOString(),
561
+ finishedAt: input.finishedAt.toISOString(),
562
+ durationMs: input.finishedAt.getTime() - input.startedAt.getTime(),
563
+ ...(input.failure.code ? { code: input.failure.code } : {}),
564
+ failureKind: input.failure.failureKind,
565
+ message: input.failure.message,
566
+ nextActions,
567
+ details: {
568
+ db: input.options.db,
569
+ host: input.host,
570
+ port: input.port,
571
+ },
572
+ });
573
+ if (input.options.json) {
574
+ process.stdout.write(
575
+ `${JSON.stringify({
576
+ ok: false,
577
+ error: input.failure.message,
578
+ code: input.failure.code,
579
+ failureKind: input.failure.failureKind,
580
+ busy,
581
+ nextActions,
582
+ exitCode: 1,
583
+ })}\n`,
584
+ );
585
+ } else {
586
+ console.error(`error${input.failure.code ? ` ${input.failure.code}` : ""}: ${input.failure.message}`);
587
+ if (nextActions.length > 0) {
588
+ console.error("next:");
589
+ for (const action of nextActions) {
590
+ console.error(` ${action}`);
591
+ }
592
+ }
593
+ }
594
+ return { exitCode: 1 };
595
+ }
596
+
597
+ export async function resolveAvailableWebPort(input: {
598
+ host: string;
599
+ preferredPort: number;
600
+ maxAttempts?: number;
601
+ }): Promise<{ port: number; requestedPort?: number; autoPortSelected: boolean }> {
602
+ const attempts = input.maxAttempts ?? 20;
603
+ const start = Math.max(0, Math.floor(input.preferredPort));
604
+ if (start === 0) {
605
+ return { port: 0, autoPortSelected: false };
606
+ }
607
+
608
+ for (let offset = 0; offset < attempts; offset += 1) {
609
+ const candidate = start + offset;
610
+ if (await isPortAvailable(input.host, candidate)) {
611
+ return {
612
+ port: candidate,
613
+ ...(candidate !== start ? { requestedPort: start } : {}),
614
+ autoPortSelected: candidate !== start,
615
+ };
616
+ }
617
+ }
618
+
619
+ return {
620
+ port: 0,
621
+ requestedPort: start,
622
+ autoPortSelected: true,
623
+ };
624
+ }
625
+
626
+ export function devStartupConsoleUrlOptions(input: {
627
+ host: string;
628
+ port: number;
629
+ webUrl?: string;
630
+ }): { apiUrl?: string; webUrl?: string } {
631
+ return {
632
+ ...(input.port === 0 ? {} : { apiUrl: `http://${input.host}:${input.port}` }),
633
+ ...(input.webUrl ? { webUrl: input.webUrl } : {}),
634
+ };
635
+ }
636
+
637
+ export interface DevWatchGenerateFailureEvent {
638
+ schemaVersion: "0.1.0";
639
+ event: "dev.generate_failed";
640
+ ok: false;
641
+ changedFiles: number;
642
+ changedPaths: string[];
643
+ generated: DevConsoleGeneratedSummary;
644
+ diagnostics: DevGenerateResult["diagnostics"];
645
+ nextActions: string[];
646
+ }
647
+
648
+ export interface DevWatchReloadEvent {
649
+ schemaVersion: "0.1.0";
650
+ event: "dev.reload";
651
+ ok: boolean;
652
+ changedFiles: number;
653
+ changedPaths: string[];
654
+ migrated: boolean;
655
+ routes: number;
656
+ runtimeEntries: number;
657
+ worker: unknown;
658
+ diagnostics: DevGenerateResult["diagnostics"];
659
+ generated: DevConsoleGeneratedSummary;
660
+ preview: DevConsoleSummary["preview"];
661
+ agentContext: DevConsoleAgentContext;
662
+ nextActions: string[];
663
+ }
664
+
102
665
  function readGeneratedJson<T>(workspaceRoot: string, relative: string): T | null {
103
666
  const absolute = join(workspaceRoot, relative);
104
667
  if (!nodeFileSystem.exists(absolute)) {
@@ -175,6 +738,8 @@ function startWebDevServer(input: {
175
738
  workspaceRoot: string;
176
739
  host: string;
177
740
  port: number;
741
+ requestedPort?: number;
742
+ autoPortSelected?: boolean;
178
743
  apiUrl: string;
179
744
  json: boolean;
180
745
  }): WebDevServerHandle | null {
@@ -209,6 +774,8 @@ function startWebDevServer(input: {
209
774
  return {
210
775
  url: `http://${input.host}:${input.port}`,
211
776
  port: input.port,
777
+ ...(input.requestedPort !== undefined ? { requestedPort: input.requestedPort } : {}),
778
+ autoPortSelected: input.autoPortSelected ?? false,
212
779
  command,
213
780
  stop: () => {
214
781
  try {
@@ -225,19 +792,43 @@ function buildStartupSummary(input: {
225
792
  handle: DevServerHandle;
226
793
  web?: WebDevServerHandle | null;
227
794
  watch: boolean;
795
+ generated?: DevStartupGeneratedEvidence;
228
796
  }): DevStartupSummary {
229
797
  const frontend = readGeneratedJson<FrontendGraph>(
230
798
  input.workspaceRoot,
231
799
  `${GENERATED_DIR}/frontendGraph.json`,
232
800
  );
801
+ const buildInfo = readGeneratedJson<{ generatorVersion?: string; inputHash?: string }>(
802
+ input.workspaceRoot,
803
+ `${GENERATED_DIR}/buildInfo.json`,
804
+ );
805
+ const buildInfoRaw = nodeFileSystem.exists(join(input.workspaceRoot, `${GENERATED_DIR}/buildInfo.json`))
806
+ ? stripDeterministicHeader(nodeFileSystem.readText(join(input.workspaceRoot, `${GENERATED_DIR}/buildInfo.json`)) ?? "")
807
+ : "";
233
808
  const bindings = frontend
234
809
  ? [...new Set(frontend.clientBindings.map((binding) => `${binding.kind}:${binding.name}`))].sort()
235
810
  : [];
236
811
  const browserUrl = input.web?.url ?? input.handle.url;
812
+ const warnings = input.handle.state.db.kind === "memory"
813
+ ? [
814
+ {
815
+ code: "FORGE_DEV_MEMORY_DB_FIDELITY",
816
+ message:
817
+ "Memory DB is fast and non-persistent; PGlite/Postgres remain the authoritative checks for full SQL constraints and adapter fidelity.",
818
+ nextAction: `rerun with ${forgeCliCommandForWorkspace(input.workspaceRoot, "forge dev --db pglite --once --json")} before treating the result as database-realistic`,
819
+ },
820
+ ]
821
+ : [];
822
+ const preview = previewSummaryFor({
823
+ workspaceRoot: input.workspaceRoot,
824
+ host: input.handle.host,
825
+ ...(input.web?.url ? { webUrl: input.web.url } : {}),
826
+ });
237
827
  return {
238
828
  schemaVersion: "0.1.0",
239
829
  ok: true,
240
830
  mode: "dev",
831
+ warnings,
241
832
  api: {
242
833
  url: input.handle.url,
243
834
  host: input.handle.host,
@@ -249,6 +840,8 @@ function buildStartupSummary(input: {
249
840
  ? {
250
841
  url: input.web.url,
251
842
  port: input.web.port,
843
+ ...(input.web.requestedPort !== undefined ? { requestedPort: input.web.requestedPort } : {}),
844
+ autoPortSelected: input.web.autoPortSelected,
252
845
  command: input.web.command,
253
846
  framework: frontend?.framework ?? "unknown",
254
847
  routes: frontend?.routes.map((route) => route.path) ?? [],
@@ -256,8 +849,11 @@ function buildStartupSummary(input: {
256
849
  ...(frontend?.dev?.apiUrlEnv ? { apiUrlEnv: frontend.dev.apiUrlEnv } : {}),
257
850
  }
258
851
  : null,
852
+ preview,
259
853
  watch: {
260
854
  enabled: input.watch,
855
+ autoGenerate: input.watch,
856
+ reloadsRuntime: input.watch,
261
857
  },
262
858
  runtime: {
263
859
  routes: input.handle.routes.map((route) => ({
@@ -274,11 +870,25 @@ function buildStartupSummary(input: {
274
870
  bridgeFiles: frontend?.bridgeFiles ?? [],
275
871
  diagnostics: frontend?.diagnostics.length ?? 0,
276
872
  },
873
+ generated: {
874
+ state: input.generated?.state ?? (buildInfoRaw ? "fresh" : "stale-risk"),
875
+ ...(buildInfo?.generatorVersion ? { generatorVersion: buildInfo.generatorVersion } : {}),
876
+ ...(buildInfo?.inputHash ? { inputHash: buildInfo.inputHash } : {}),
877
+ ...(buildInfoRaw ? { buildInfoHash: hashStable(buildInfoRaw) } : {}),
878
+ ...(buildInfoRaw ? { runtimeLoadedHash: hashStable(buildInfoRaw) } : {}),
879
+ runtimeStaleRisk: !buildInfoRaw || input.generated?.state === "stale-risk",
880
+ changedFiles: input.generated?.changedFiles ?? 0,
881
+ sampleChanged: input.generated?.sampleChanged ?? [],
882
+ hiddenChanged: input.generated?.hiddenChanged ?? 0,
883
+ command: input.generated?.command ?? forgeCliCommandForWorkspace(input.workspaceRoot, "forge generate"),
884
+ checkCommand: input.generated?.checkCommand ?? forgeCliCommandForWorkspace(input.workspaceRoot, "forge generate --check --json"),
885
+ message: input.generated?.message ?? (buildInfoRaw ? "generated artifacts are loaded" : "generated build info is missing"),
886
+ },
277
887
  next: {
278
888
  browserUrl,
279
889
  apiIndex: input.handle.url,
280
- inspect: "forge inspect all --json",
281
- verify: "forge dev --once --json",
890
+ inspect: forgeCliCommandForWorkspace(input.workspaceRoot, "forge inspect summary --json"),
891
+ verify: forgeCliCommandForWorkspace(input.workspaceRoot, "forge dev --once --json"),
282
892
  },
283
893
  pid: process.pid,
284
894
  };
@@ -295,11 +905,24 @@ function printStartupHuman(summary: DevStartupSummary): void {
295
905
  lines.push(` DB: ${summary.api.db.kind} ${summary.api.db.connected ? "connected" : "not connected"}`);
296
906
  lines.push(` Worker: ${summary.api.worker}`);
297
907
  lines.push(` Watch: ${summary.watch.enabled ? "on" : "off"}`);
908
+ lines.push(` Auto-generate: ${summary.watch.autoGenerate ? "on" : "off"}`);
909
+ lines.push(` Generated: ${summary.generated.state}${summary.generated.changedFiles > 0 ? ` (${summary.generated.changedFiles} changed)` : ""}${summary.generated.runtimeStaleRisk ? " (stale risk)" : ""}`);
910
+ lines.push(` Generated note: ${summary.generated.message}`);
911
+ lines.push(` Generated check: ${summary.generated.checkCommand}`);
912
+ for (const warning of summary.warnings) {
913
+ lines.push(` Warning ${warning.code}: ${warning.message}`);
914
+ if (warning.nextAction) {
915
+ lines.push(` Next: ${warning.nextAction}`);
916
+ }
917
+ }
298
918
  lines.push("");
299
919
 
300
920
  lines.push("Web app");
301
921
  if (summary.web) {
302
922
  lines.push(` URL: ${summary.web.url}`);
923
+ if (summary.web.autoPortSelected && summary.web.requestedPort) {
924
+ lines.push(` Requested port: ${summary.web.requestedPort} (busy; selected ${summary.web.port})`);
925
+ }
303
926
  lines.push(` Framework: ${summary.web.framework}`);
304
927
  lines.push(` API env: ${summary.web.apiUrlEnv ?? "unknown"}=${summary.api.url}`);
305
928
  lines.push(` Bridge: ${summary.web.bridgeFiles.length > 0 ? summary.web.bridgeFiles.join(", ") : "missing"}`);
@@ -325,6 +948,15 @@ function printStartupHuman(summary: DevStartupSummary): void {
325
948
  lines.push("Agent checks");
326
949
  lines.push(` ${summary.next.verify}`);
327
950
  lines.push(` ${summary.next.inspect}`);
951
+ lines.push(" forge changed --json");
952
+ lines.push("");
953
+
954
+ lines.push("Preview");
955
+ lines.push(` Target app: ${summary.preview.targetAppUrl}`);
956
+ if (summary.preview.studioUrl) {
957
+ lines.push(` Studio: ${summary.preview.studioUrl}`);
958
+ }
959
+ lines.push(` Note: ${summary.preview.note}`);
328
960
  lines.push("");
329
961
 
330
962
  process.stdout.write(`${lines.join("\n")}\n`);
@@ -350,10 +982,152 @@ function openBrowser(url: string): void {
350
982
  }
351
983
  }
352
984
 
985
+ export async function ensureGeneratedForDev(workspaceRoot: string): Promise<DevGenerateResult> {
986
+ resetCompileSessions();
987
+ const result = await run({
988
+ workspaceRoot,
989
+ check: false,
990
+ dryRun: false,
991
+ json: false,
992
+ concurrency: 4,
993
+ });
994
+ return {
995
+ ok: result.exitCode === 0,
996
+ changed: result.changed,
997
+ unchanged: result.unchanged,
998
+ diagnostics: [...result.errors, ...result.warnings],
999
+ exitCode: result.exitCode,
1000
+ };
1001
+ }
1002
+
1003
+ export function generatedEvidenceFromCycle(cycle: DevConsoleCycle): DevStartupGeneratedEvidence {
1004
+ const phase = cycle.phases.find((item) => item.name === "generated");
1005
+ const changedFiles = Number(phase?.details?.changed ?? 0);
1006
+ const sampleChanged = Array.isArray(phase?.details?.sampleChanged)
1007
+ ? phase.details.sampleChanged.filter((item): item is string => typeof item === "string")
1008
+ : [];
1009
+ const hiddenChanged = Number(phase?.details?.hiddenChanged ?? 0);
1010
+ return {
1011
+ ok: phase?.ok === true,
1012
+ state: phase?.ok === true
1013
+ ? changedFiles > 0 ? "regenerated" : "fresh"
1014
+ : "stale-risk",
1015
+ changedFiles,
1016
+ sampleChanged,
1017
+ hiddenChanged,
1018
+ message: phase?.message ?? "generated phase did not report a message",
1019
+ command: cycle.summary.generated.command,
1020
+ checkCommand: cycle.summary.generated.checkCommand,
1021
+ };
1022
+ }
1023
+
1024
+ export function generatedEvidenceFromGenerateResult(
1025
+ result: DevGenerateResult,
1026
+ options: { workspaceRoot?: string } = {},
1027
+ ): DevStartupGeneratedEvidence {
1028
+ return {
1029
+ ok: result.ok,
1030
+ state: result.ok ? result.changed.length > 0 ? "regenerated" : "fresh" : "stale-risk",
1031
+ changedFiles: result.changed.length,
1032
+ sampleChanged: result.changed.slice(0, 12),
1033
+ hiddenChanged: Math.max(0, result.changed.length - 12),
1034
+ message: result.ok
1035
+ ? result.changed.length > 0
1036
+ ? `regenerated ${result.changed.length} generated artifacts`
1037
+ : "generated artifacts are up to date"
1038
+ : "generated artifacts could not be regenerated",
1039
+ command: options.workspaceRoot
1040
+ ? forgeCliCommandForWorkspace(options.workspaceRoot, "forge generate")
1041
+ : "forge generate",
1042
+ checkCommand: options.workspaceRoot
1043
+ ? forgeCliCommandForWorkspace(options.workspaceRoot, "forge generate --check --json")
1044
+ : "forge generate --check --json",
1045
+ };
1046
+ }
1047
+
1048
+ function printDevGenerateFailure(result: DevGenerateResult, json: boolean): void {
1049
+ if (json) {
1050
+ process.stdout.write(`${JSON.stringify({
1051
+ ok: false,
1052
+ phase: "generated",
1053
+ diagnostics: result.diagnostics,
1054
+ exitCode: 1,
1055
+ })}\n`);
1056
+ return;
1057
+ }
1058
+ console.error("error: forge dev could not regenerate generated artifacts");
1059
+ for (const diagnostic of result.diagnostics.filter((item) => item.severity === "error").slice(0, 5)) {
1060
+ console.error(`error ${diagnostic.code}: ${diagnostic.message}`);
1061
+ }
1062
+ }
1063
+
1064
+ export function buildDevWatchGenerateFailureEvent(input: {
1065
+ changedCount: number;
1066
+ changedPaths: string[];
1067
+ result: DevGenerateResult;
1068
+ workspaceRoot?: string;
1069
+ }): DevWatchGenerateFailureEvent {
1070
+ const nextActions = input.workspaceRoot
1071
+ ? forgeCliCommandsForWorkspace(input.workspaceRoot, ["forge dev --once --json", "forge check --json"])
1072
+ : ["forge dev --once --json", "forge check --json"];
1073
+ return {
1074
+ schemaVersion: "0.1.0",
1075
+ event: "dev.generate_failed",
1076
+ ok: false,
1077
+ changedFiles: input.changedCount,
1078
+ changedPaths: input.changedPaths,
1079
+ generated: generatedEvidenceFromGenerateResult(input.result, { workspaceRoot: input.workspaceRoot }),
1080
+ diagnostics: input.result.diagnostics,
1081
+ nextActions,
1082
+ };
1083
+ }
1084
+
1085
+ export function buildDevWatchReloadEvent(input: {
1086
+ changedCount: number;
1087
+ changedPaths: string[];
1088
+ generated: DevGenerateResult;
1089
+ reload: Awaited<ReturnType<DevServerHandle["reload"]>>;
1090
+ cycle: DevConsoleCycle;
1091
+ workspaceRoot?: string;
1092
+ }): DevWatchReloadEvent {
1093
+ const failureNextActions = input.workspaceRoot
1094
+ ? forgeCliCommandsForWorkspace(input.workspaceRoot, ["forge dev --once --json", "forge check --json"])
1095
+ : ["forge dev --once --json", "forge check --json"];
1096
+ return {
1097
+ schemaVersion: "0.1.0",
1098
+ event: "dev.reload",
1099
+ ok: input.reload.ok,
1100
+ changedFiles: input.changedCount,
1101
+ changedPaths: input.changedPaths,
1102
+ migrated: input.reload.migrated,
1103
+ routes: input.reload.routes,
1104
+ runtimeEntries: input.reload.runtimeEntries,
1105
+ worker: input.reload.worker,
1106
+ diagnostics: input.reload.diagnostics,
1107
+ generated: generatedEvidenceFromGenerateResult(input.generated, { workspaceRoot: input.workspaceRoot }),
1108
+ preview: input.cycle.summary.preview,
1109
+ agentContext: input.cycle.summary.agentContext,
1110
+ nextActions: input.reload.ok
1111
+ ? input.cycle.summary.agentContext.recommendedCommands
1112
+ : failureNextActions,
1113
+ };
1114
+ }
1115
+
353
1116
  export async function runDevCommand(
354
1117
  options: DevCommandOptions,
355
1118
  ): Promise<DevCommandResult> {
356
1119
  const workspaceRoot = options.workspaceRoot.replace(/\\/g, "/");
1120
+ const startedAt = new Date();
1121
+
1122
+ if (options.lifecycle === "status") {
1123
+ return runDevStatus({ ...options, workspaceRoot });
1124
+ }
1125
+ if (options.lifecycle === "stop") {
1126
+ return runDevStop({ ...options, workspaceRoot });
1127
+ }
1128
+ if (options.detach) {
1129
+ return runDevDetach({ ...options, workspaceRoot });
1130
+ }
357
1131
 
358
1132
  if (options.once) {
359
1133
  const cycle = await runDevConsoleCycle({
@@ -368,22 +1142,59 @@ export async function runDevCommand(
368
1142
 
369
1143
  const host = resolveDevHost(options.host);
370
1144
  const port = resolveDevPort(options.port);
371
- const webPort = options.webPort ?? detectDefaultWebPort(workspaceRoot);
1145
+ if (!options.webOnly && !(await isPortAvailable(host, port))) {
1146
+ const failure = classifyDevStartFailure({
1147
+ rawMessage: `listen EADDRINUSE: address already in use ${host}:${port}`,
1148
+ host,
1149
+ port,
1150
+ webPort: options.webPort,
1151
+ db: options.db,
1152
+ });
1153
+ return writeDevStartFailure({
1154
+ workspaceRoot,
1155
+ startedAt,
1156
+ finishedAt: new Date(),
1157
+ options,
1158
+ host,
1159
+ port,
1160
+ failure,
1161
+ });
1162
+ }
1163
+ const requestedWebPort = options.webPort ?? detectDefaultWebPort(workspaceRoot);
372
1164
  const shouldStartWeb =
373
1165
  options.webOnly === true ||
374
1166
  (options.withWeb !== false && !options.apiOnly && hasWebApp(workspaceRoot));
1167
+ const webPortSelection = shouldStartWeb
1168
+ ? await resolveAvailableWebPort({ host, preferredPort: requestedWebPort })
1169
+ : { port: requestedWebPort, autoPortSelected: false };
1170
+ const webPort = webPortSelection.port;
375
1171
  const webUrl = shouldStartWeb ? `http://${host}:${webPort}` : undefined;
1172
+ let startupGenerated: DevStartupGeneratedEvidence | undefined;
376
1173
 
377
- const startupCycle = await runDevConsoleCycle({
378
- workspaceRoot,
379
- mode: "startup",
380
- strictSecrets: false,
381
- includeImpact: true,
382
- });
383
- if (options.json) {
384
- process.stdout.write(formatDevConsoleJson(startupCycle));
1174
+ if (!options.skipStartupConsole) {
1175
+ const startupCycle = await runDevConsoleCycle({
1176
+ workspaceRoot,
1177
+ mode: "startup",
1178
+ strictSecrets: false,
1179
+ includeImpact: true,
1180
+ ...devStartupConsoleUrlOptions({ host, port, ...(webUrl ? { webUrl } : {}) }),
1181
+ });
1182
+ if (options.json) {
1183
+ process.stdout.write(formatDevConsoleJson(startupCycle));
1184
+ } else {
1185
+ process.stdout.write(formatDevConsoleHuman(startupCycle));
1186
+ }
1187
+ startupGenerated = generatedEvidenceFromCycle(startupCycle);
1188
+ if (startupCycle.exitCode !== 0) {
1189
+ return { exitCode: 1 };
1190
+ }
385
1191
  } else {
386
- process.stdout.write(formatDevConsoleHuman(startupCycle));
1192
+ const generated = await ensureGeneratedForDev(workspaceRoot);
1193
+ startupGenerated = generatedEvidenceFromGenerateResult(generated);
1194
+ if (!generated.ok) {
1195
+ printDevGenerateFailure(generated, options.json);
1196
+ return { exitCode: 1 };
1197
+ }
387
1198
  }
388
1199
 
389
1200
  if (options.webOnly) {
@@ -392,6 +1203,8 @@ export async function runDevCommand(
392
1203
  workspaceRoot,
393
1204
  host,
394
1205
  port: webPort,
1206
+ requestedPort: webPortSelection.requestedPort,
1207
+ autoPortSelected: webPortSelection.autoPortSelected,
395
1208
  apiUrl,
396
1209
  json: options.json,
397
1210
  });
@@ -413,6 +1226,8 @@ export async function runDevCommand(
413
1226
  web: {
414
1227
  url: webHandle.url,
415
1228
  port: webHandle.port,
1229
+ ...(webHandle.requestedPort !== undefined ? { requestedPort: webHandle.requestedPort } : {}),
1230
+ autoPortSelected: webHandle.autoPortSelected,
416
1231
  command: webHandle.command,
417
1232
  },
418
1233
  api: {
@@ -440,6 +1255,8 @@ export async function runDevCommand(
440
1255
  return { web: webHandle, exitCode: 0 };
441
1256
  }
442
1257
 
1258
+ const deltaRecorder = await createAmbientDeltaRecorder(workspaceRoot, "forge-dev", "forge dev");
1259
+
443
1260
  let handle: DevServerHandle;
444
1261
  try {
445
1262
  handle = await startDevServer({
@@ -457,18 +1274,29 @@ export async function runDevCommand(
457
1274
  mode: options.mode,
458
1275
  allowDevAuth: options.allowDevAuth,
459
1276
  webUrl,
1277
+ deltaRecorder,
460
1278
  });
461
1279
  } catch (error) {
462
- const message =
1280
+ await deltaRecorder.close("forge dev failed to start");
1281
+ const finishedAt = new Date();
1282
+ const rawMessage =
463
1283
  error instanceof Error ? error.message : "failed to start dev server";
464
- if (options.json) {
465
- process.stdout.write(
466
- `${JSON.stringify({ ok: false, error: message, exitCode: 1 })}\n`,
467
- );
468
- } else {
469
- console.error(`error: ${message}`);
470
- }
471
- return { exitCode: 1 };
1284
+ const failure = classifyDevStartFailure({
1285
+ rawMessage,
1286
+ host,
1287
+ port,
1288
+ webPort: options.webPort,
1289
+ db: options.db,
1290
+ });
1291
+ return writeDevStartFailure({
1292
+ workspaceRoot,
1293
+ startedAt,
1294
+ finishedAt,
1295
+ options,
1296
+ host,
1297
+ port,
1298
+ failure,
1299
+ });
472
1300
  }
473
1301
 
474
1302
  const webHandle = !shouldStartWeb
@@ -477,6 +1305,8 @@ export async function runDevCommand(
477
1305
  workspaceRoot,
478
1306
  host,
479
1307
  port: webPort,
1308
+ requestedPort: webPortSelection.requestedPort,
1309
+ autoPortSelected: webPortSelection.autoPortSelected,
480
1310
  apiUrl: handle.url,
481
1311
  json: options.json,
482
1312
  });
@@ -486,6 +1316,7 @@ export async function runDevCommand(
486
1316
  handle,
487
1317
  web: webHandle,
488
1318
  watch: options.watch,
1319
+ generated: startupGenerated,
489
1320
  });
490
1321
  if (options.json) {
491
1322
  printStartupJson(startupSummary);
@@ -496,6 +1327,29 @@ export async function runDevCommand(
496
1327
  openBrowser(webHandle?.url ?? handle.url);
497
1328
  }
498
1329
 
1330
+ {
1331
+ const finishedAt = new Date();
1332
+ writeLastRunRecord(workspaceRoot, {
1333
+ schemaVersion: "0.1.0",
1334
+ command: "forge dev",
1335
+ ok: true,
1336
+ startedAt: startedAt.toISOString(),
1337
+ finishedAt: finishedAt.toISOString(),
1338
+ durationMs: finishedAt.getTime() - startedAt.getTime(),
1339
+ nextActions: [
1340
+ webHandle?.url ?? handle.url,
1341
+ forgeCliCommandForWorkspace(workspaceRoot, "forge last --json"),
1342
+ ],
1343
+ details: {
1344
+ db: handle.state.db.kind,
1345
+ apiUrl: handle.url,
1346
+ webUrl: webHandle?.url,
1347
+ watch: options.watch,
1348
+ worker: options.worker,
1349
+ },
1350
+ });
1351
+ }
1352
+
499
1353
  let watchHandle: { stop: () => void } | null = null;
500
1354
  let outboxWorkerHandle: { stop: () => void } | null = null;
501
1355
 
@@ -503,8 +1357,17 @@ export async function runDevCommand(
503
1357
  outboxWorkerHandle = handle.outboxWorker;
504
1358
  }
505
1359
 
1360
+ const devConsoleUrlOptions = (): { apiUrl: string; webUrl?: string } => ({
1361
+ apiUrl: handle.url,
1362
+ ...(webHandle?.url ? { webUrl: webHandle.url } : {}),
1363
+ });
1364
+
506
1365
  if (options.watch) {
507
- watchHandle = startDevWatch(workspaceRoot, async (changedCount) => {
1366
+ watchHandle = startDevWatch(workspaceRoot, async (changedCount, changedPaths) => {
1367
+ for (const changedPath of changedPaths) {
1368
+ await deltaRecorder.recordFileChanged(changedPath);
1369
+ }
1370
+ resetCompileSessions();
508
1371
  const result = await run({
509
1372
  workspaceRoot,
510
1373
  check: false,
@@ -514,39 +1377,91 @@ export async function runDevCommand(
514
1377
  });
515
1378
 
516
1379
  if (result.exitCode === 0) {
1380
+ const reload = await handle.reload("watch");
1381
+ const cycle = await runDevConsoleCycle({
1382
+ workspaceRoot,
1383
+ mode: "watch",
1384
+ strictSecrets: false,
1385
+ includeImpact: true,
1386
+ ...devConsoleUrlOptions(),
1387
+ });
517
1388
  if (!options.json) {
518
1389
  process.stdout.write(
519
- `[forge dev] regenerated (${changedCount} changed files)\n`,
1390
+ `[forge dev] regenerated (${changedCount} changed files), ` +
1391
+ `migrations ${reload.migrated ? "applied" : "skipped"}, ` +
1392
+ `runtime ${reload.ok ? "reloaded" : "reload failed"}\n`,
520
1393
  );
1394
+ } else {
1395
+ process.stdout.write(`${JSON.stringify(buildDevWatchReloadEvent({
1396
+ changedCount,
1397
+ changedPaths,
1398
+ generated: {
1399
+ ok: true,
1400
+ changed: result.changed,
1401
+ unchanged: result.unchanged,
1402
+ diagnostics: [...result.errors, ...result.warnings],
1403
+ exitCode: 0,
1404
+ },
1405
+ reload,
1406
+ cycle,
1407
+ workspaceRoot,
1408
+ }))}\n`);
521
1409
  }
522
- } else if (!options.json) {
523
- for (const diagnostic of result.errors) {
524
- console.error(`error ${diagnostic.code}: ${diagnostic.message}`);
1410
+ if (!reload.ok && !options.json) {
1411
+ for (const diagnostic of reload.diagnostics.filter((item) => item.severity === "error")) {
1412
+ console.error(`error ${diagnostic.code}: ${diagnostic.message}`);
1413
+ }
1414
+ }
1415
+ if (options.json) {
1416
+ process.stdout.write(formatDevConsoleJson(cycle));
1417
+ } else {
1418
+ process.stdout.write(formatDevConsoleHuman(cycle));
525
1419
  }
526
- }
527
-
528
- const cycle = await runDevConsoleCycle({
529
- workspaceRoot,
530
- mode: "watch",
531
- strictSecrets: false,
532
- includeImpact: true,
533
- });
534
- if (options.json) {
535
- process.stdout.write(formatDevConsoleJson(cycle));
536
1420
  } else {
537
- process.stdout.write(formatDevConsoleHuman(cycle));
1421
+ if (options.json) {
1422
+ process.stdout.write(`${JSON.stringify(buildDevWatchGenerateFailureEvent({
1423
+ changedCount,
1424
+ changedPaths,
1425
+ result: {
1426
+ ok: false,
1427
+ changed: result.changed,
1428
+ unchanged: result.unchanged,
1429
+ diagnostics: [...result.errors, ...result.warnings],
1430
+ exitCode: 1,
1431
+ },
1432
+ workspaceRoot,
1433
+ }))}\n`);
1434
+ } else {
1435
+ console.error(`[forge dev] regeneration failed after ${changedCount} changed files; runtime was not reloaded`);
1436
+ for (const diagnostic of [...result.errors, ...result.warnings]) {
1437
+ console.error(`${diagnostic.severity} ${diagnostic.code}: ${diagnostic.message}`);
1438
+ }
1439
+ }
1440
+ const cycle = await runDevConsoleCycle({
1441
+ workspaceRoot,
1442
+ mode: "watch",
1443
+ strictSecrets: false,
1444
+ includeImpact: true,
1445
+ ...devConsoleUrlOptions(),
1446
+ });
1447
+ if (options.json) {
1448
+ process.stdout.write(formatDevConsoleJson(cycle));
1449
+ } else {
1450
+ process.stdout.write(formatDevConsoleHuman(cycle));
1451
+ }
538
1452
  }
539
1453
  });
540
1454
  }
541
1455
 
542
1456
  await new Promise<void>((resolve) => {
543
- const shutdown = () => {
544
- watchHandle?.stop();
545
- outboxWorkerHandle?.stop();
546
- webHandle?.stop();
547
- handle.stop();
548
- resolve();
549
- };
1457
+ const shutdown = () => {
1458
+ watchHandle?.stop();
1459
+ outboxWorkerHandle?.stop();
1460
+ webHandle?.stop();
1461
+ handle.stop();
1462
+ void deltaRecorder.close("forge dev stopped");
1463
+ resolve();
1464
+ };
550
1465
 
551
1466
  process.once("SIGINT", shutdown);
552
1467
  process.once("SIGTERM", shutdown);