forgeos 0.1.0-alpha.7 → 0.1.0-alpha.71

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 (598) hide show
  1. package/.agents/skills/forge-agent-fabric/SKILL.md +178 -0
  2. package/.agents/skills/forge-agent-fabric/references/managed-workflows.md +95 -0
  3. package/.agents/skills/forge-agent-fabric/references/repository-analysis.md +102 -0
  4. package/.agents/skills/forge-agent-fabric/scripts/fabric.mjs +46 -0
  5. package/.agents/skills/forge-agent-fabric/scripts/repository.mjs +44 -0
  6. package/.npmignore +10 -0
  7. package/AGENTS.md +142 -90
  8. package/CHANGELOG.md +870 -9
  9. package/LICENSE +21 -0
  10. package/README.md +121 -6
  11. package/adapters/go/README.md +23 -0
  12. package/adapters/go/go.mod +3 -0
  13. package/adapters/go/http.go +149 -0
  14. package/adapters/go/registry.go +234 -0
  15. package/adapters/go/types.go +136 -0
  16. package/adapters/java/README.md +68 -0
  17. package/adapters/java/pom.xml +47 -0
  18. package/adapters/java/src/main/java/dev/forgeos/adapter/Auth.java +20 -0
  19. package/adapters/java/src/main/java/dev/forgeos/adapter/Diagnostic.java +16 -0
  20. package/adapters/java/src/main/java/dev/forgeos/adapter/Entry.java +38 -0
  21. package/adapters/java/src/main/java/dev/forgeos/adapter/EntryKind.java +16 -0
  22. package/adapters/java/src/main/java/dev/forgeos/adapter/ErrorInfo.java +4 -0
  23. package/adapters/java/src/main/java/dev/forgeos/adapter/Forge.java +94 -0
  24. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeCall.java +12 -0
  25. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeContext.java +11 -0
  26. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeHandler.java +8 -0
  27. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeHttpHandler.java +179 -0
  28. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeRegistry.java +121 -0
  29. package/adapters/java/src/main/java/dev/forgeos/adapter/Json.java +14 -0
  30. package/adapters/java/src/main/java/dev/forgeos/adapter/Manifest.java +14 -0
  31. package/adapters/java/src/main/java/dev/forgeos/adapter/RequestEnvelope.java +6 -0
  32. package/adapters/java/src/main/java/dev/forgeos/adapter/ResponseEnvelope.java +25 -0
  33. package/adapters/java/src/main/java/dev/forgeos/adapter/Risk.java +18 -0
  34. package/adapters/java/src/main/java/dev/forgeos/adapter/Schemas.java +36 -0
  35. package/adapters/java/src/main/java/dev/forgeos/adapter/Service.java +65 -0
  36. package/adapters/java/src/main/java/dev/forgeos/adapter/TransactionMode.java +18 -0
  37. package/adapters/java/src/main/java/dev/forgeos/adapter/TypedForgeHandler.java +6 -0
  38. package/adapters/java-spring-boot-starter/README.md +32 -0
  39. package/adapters/java-spring-boot-starter/pom.xml +49 -0
  40. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeCommand.java +22 -0
  41. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeExternalService.java +15 -0
  42. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeQuery.java +16 -0
  43. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeServiceBeanCondition.java +18 -0
  44. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeSpringAutoConfiguration.java +16 -0
  45. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeSpringRuntime.java +104 -0
  46. package/adapters/java-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +1 -0
  47. package/bin/forge.mjs +18 -0
  48. package/docs/agent-fabric-programs.md +151 -0
  49. package/docs/agent-fabric.md +582 -0
  50. package/docs/architecture/agent-fabric/PORTABLE_PROJECTS_PLAN.md +136 -0
  51. package/docs/architecture/agent-fabric/README.md +180 -0
  52. package/docs/architecture/agent-fabric/REPOSITORY_MANIFEST_PLAN.md +284 -0
  53. package/docs/architecture/agent-fabric/WORKFLOW_PROGRAM_V2_ADR.md +81 -0
  54. package/docs/architecture/agent-fabric/WORKFLOW_PROGRAM_V2_CORRECTIONS.md +39 -0
  55. package/docs/architecture/agent-fabric/WORKFLOW_PROGRAM_V2_CORRECTIONS_EVIDENCE.md +53 -0
  56. package/docs/architecture/agent-fabric/WORKFLOW_PROGRAM_V2_EVIDENCE.md +49 -0
  57. package/docs/architecture/agent-fabric/WORKFLOW_PROGRAM_V2_PLAN.md +590 -0
  58. package/docs/cair-protocol.md +103 -0
  59. package/docs/changelog.md +1070 -0
  60. package/docs/forge-protocol.md +189 -0
  61. package/docs/repository-agent-benchmark.md +179 -0
  62. package/docs/repository-analysis.md +139 -0
  63. package/docs/repository-context-quality.md +179 -0
  64. package/docs/repository-runtime-observation.md +82 -0
  65. package/examples/agent-fabric-v2/README.md +70 -0
  66. package/examples/agent-fabric-v2/item-child.workflow.ts +11 -0
  67. package/examples/agent-fabric-v2/make-request.mjs +4 -0
  68. package/examples/agent-fabric-v2/map-child.workflow.ts +10 -0
  69. package/examples/agent-fabric-v2/migrate.workflow.ts +35 -0
  70. package/examples/agent-fabric-v2/registry.example.json +256 -0
  71. package/examples/agent-fabric-v2/run-ui-fixture.ts +48 -0
  72. package/examples/agent-fabric-v2/ui-audit.workflow.ts +28 -0
  73. package/examples/agent-fabric-v2/ui-fixture-worker.cjs +36 -0
  74. package/examples/agent-fabric-v2/visual-population.json +13 -0
  75. package/examples/agent-fabric-v2/workers/check.mjs +2 -0
  76. package/examples/agent-fabric-v2/workers/common.mjs +7 -0
  77. package/examples/agent-fabric-v2/workers/discover.mjs +2 -0
  78. package/examples/agent-fabric-v2/workers/migrate.mjs +4 -0
  79. package/examples/agent-fabric-v2/workers/review.mjs +3 -0
  80. package/examples/go-billing/go.mod +7 -0
  81. package/examples/go-billing/main.go +120 -0
  82. package/examples/java-billing/pom.xml +60 -0
  83. package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/CreateInvoiceInput.java +4 -0
  84. package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/Invoice.java +11 -0
  85. package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/Main.java +127 -0
  86. package/package.json +60 -5
  87. package/schemas/agent-fabric/v0.1/control-event.schema.json +281 -0
  88. package/schemas/forge-manifest.schema.json +389 -0
  89. package/scripts/field-test-forgeos.mjs +1021 -0
  90. package/scripts/install-agent-fabric-skill.mjs +105 -0
  91. package/src/forge/_generated/releaseManifest.json +1 -2
  92. package/src/forge/_generated/releaseManifest.ts +3 -3
  93. package/src/forge/agent-adapters/index.ts +1592 -125
  94. package/src/forge/agent-adapters/types.ts +225 -1
  95. package/src/forge/agent-fabric/adapter.ts +131 -0
  96. package/src/forge/agent-fabric/attached-snapshot.ts +55 -0
  97. package/src/forge/agent-fabric/attached-task-contract.ts +105 -0
  98. package/src/forge/agent-fabric/attached-task-service.ts +127 -0
  99. package/src/forge/agent-fabric/attached-task-store.ts +109 -0
  100. package/src/forge/agent-fabric/authority.ts +298 -0
  101. package/src/forge/agent-fabric/canonical.ts +135 -0
  102. package/src/forge/agent-fabric/codex-adversarial-review.ts +229 -0
  103. package/src/forge/agent-fabric/codex-sdk-worker.ts +203 -0
  104. package/src/forge/agent-fabric/conductor.ts +754 -0
  105. package/src/forge/agent-fabric/dictionary.ts +12 -0
  106. package/src/forge/agent-fabric/errors.ts +32 -0
  107. package/src/forge/agent-fabric/hardened-conductor.ts +485 -0
  108. package/src/forge/agent-fabric/hardened-reducer.ts +211 -0
  109. package/src/forge/agent-fabric/index.ts +48 -0
  110. package/src/forge/agent-fabric/journal.ts +116 -0
  111. package/src/forge/agent-fabric/local-adaptive-digest-worker.mjs +26 -0
  112. package/src/forge/agent-fabric/local-adaptive-harness.ts +343 -0
  113. package/src/forge/agent-fabric/local-adaptive-service.ts +398 -0
  114. package/src/forge/agent-fabric/local-adaptive-worker.ts +146 -0
  115. package/src/forge/agent-fabric/local-approval-window.ts +211 -0
  116. package/src/forge/agent-fabric/local-change-review-service.ts +374 -0
  117. package/src/forge/agent-fabric/local-coding-worker.ts +285 -0
  118. package/src/forge/agent-fabric/local-control-store.ts +239 -0
  119. package/src/forge/agent-fabric/local-effect-broker.ts +348 -0
  120. package/src/forge/agent-fabric/local-evolution-approval.ts +81 -0
  121. package/src/forge/agent-fabric/local-evolution-profile.ts +87 -0
  122. package/src/forge/agent-fabric/local-evolution-registry.ts +427 -0
  123. package/src/forge/agent-fabric/local-evolution-service.ts +200 -0
  124. package/src/forge/agent-fabric/local-intelligence.ts +281 -0
  125. package/src/forge/agent-fabric/local-paths.ts +18 -0
  126. package/src/forge/agent-fabric/local-task-contract.ts +203 -0
  127. package/src/forge/agent-fabric/local-task-inbox.ts +543 -0
  128. package/src/forge/agent-fabric/local-task-server.ts +374 -0
  129. package/src/forge/agent-fabric/local-task-service.ts +982 -0
  130. package/src/forge/agent-fabric/local-verification.ts +551 -0
  131. package/src/forge/agent-fabric/managed-environment.ts +205 -0
  132. package/src/forge/agent-fabric/managed-run-contract.ts +103 -0
  133. package/src/forge/agent-fabric/managed-run-service.ts +401 -0
  134. package/src/forge/agent-fabric/managed-run-store.ts +165 -0
  135. package/src/forge/agent-fabric/managed-workspace.ts +253 -0
  136. package/src/forge/agent-fabric/p0a.ts +104 -0
  137. package/src/forge/agent-fabric/p0b-model-adapter.ts +434 -0
  138. package/src/forge/agent-fabric/planning.ts +155 -0
  139. package/src/forge/agent-fabric/program-api.ts +37 -0
  140. package/src/forge/agent-fabric/program-contract.ts +283 -0
  141. package/src/forge/agent-fabric/program-dsl.ts +126 -0
  142. package/src/forge/agent-fabric/program-evidence.ts +22 -0
  143. package/src/forge/agent-fabric/program-image.ts +15 -0
  144. package/src/forge/agent-fabric/program-lock.ts +50 -0
  145. package/src/forge/agent-fabric/program-owner.ts +16 -0
  146. package/src/forge/agent-fabric/program-scheduler.ts +38 -0
  147. package/src/forge/agent-fabric/program-service.ts +793 -0
  148. package/src/forge/agent-fabric/program-store.ts +142 -0
  149. package/src/forge/agent-fabric/program-structure.ts +65 -0
  150. package/src/forge/agent-fabric/program-types.ts +110 -0
  151. package/src/forge/agent-fabric/program-view.ts +12 -0
  152. package/src/forge/agent-fabric/program-worker.ts +108 -0
  153. package/src/forge/agent-fabric/project-profile.ts +50 -0
  154. package/src/forge/agent-fabric/project-registry.ts +120 -0
  155. package/src/forge/agent-fabric/project-runtime.ts +85 -0
  156. package/src/forge/agent-fabric/readiness.ts +26 -0
  157. package/src/forge/agent-fabric/reducer.ts +863 -0
  158. package/src/forge/agent-fabric/repository-context.ts +83 -0
  159. package/src/forge/agent-fabric/resource-ledger.ts +266 -0
  160. package/src/forge/agent-fabric/serialized-local-adapter.ts +60 -0
  161. package/src/forge/agent-fabric/types.ts +424 -0
  162. package/src/forge/agent-fabric/validation.ts +500 -0
  163. package/src/forge/agent-fabric/workflow-engine.ts +218 -0
  164. package/src/forge/agent-fabric/workflow-task-actions.ts +104 -0
  165. package/src/forge/agent-fabric/workflow-templates.ts +11 -0
  166. package/src/forge/agent-memory/bridge.ts +1728 -0
  167. package/src/forge/agent-memory/context-pack.ts +284 -0
  168. package/src/forge/agent-memory/hook-runner.ts +331 -0
  169. package/src/forge/agent-memory/mcp.ts +597 -0
  170. package/src/forge/agent-memory/normalize.ts +498 -0
  171. package/src/forge/agent-memory/redaction.ts +103 -0
  172. package/src/forge/agent-memory/sources/claude-code.ts +51 -0
  173. package/src/forge/agent-memory/sources/codex-hook-runner.mjs +343 -0
  174. package/src/forge/agent-memory/sources/codex.ts +161 -0
  175. package/src/forge/agent-memory/sources/cursor.ts +35 -0
  176. package/src/forge/agent-memory/types.ts +215 -0
  177. package/src/forge/bench.ts +248 -0
  178. package/src/forge/brownfield-import/index.ts +896 -0
  179. package/src/forge/brownfield-import/types.ts +127 -0
  180. package/src/forge/cair/action-journal.ts +61 -0
  181. package/src/forge/cair/action-parser.ts +314 -0
  182. package/src/forge/cair/action-validator.ts +40 -0
  183. package/src/forge/cair/actions.ts +1818 -0
  184. package/src/forge/cair/format.ts +77 -0
  185. package/src/forge/cair/index.ts +118 -0
  186. package/src/forge/cair/query.ts +478 -0
  187. package/src/forge/cair/snapshot.ts +315 -0
  188. package/src/forge/cair/types.ts +255 -0
  189. package/src/forge/cli/adaptive.ts +51 -0
  190. package/src/forge/cli/ai.ts +16 -1
  191. package/src/forge/cli/auth.ts +459 -15
  192. package/src/forge/cli/authmd.ts +441 -0
  193. package/src/forge/cli/baseline.ts +112 -0
  194. package/src/forge/cli/build.ts +20 -4
  195. package/src/forge/cli/changed.ts +418 -0
  196. package/src/forge/cli/codex-app-server.ts +877 -0
  197. package/src/forge/cli/commands.ts +1859 -20
  198. package/src/forge/cli/db.ts +209 -2
  199. package/src/forge/cli/deploy.ts +1339 -0
  200. package/src/forge/cli/deps.ts +150 -23
  201. package/src/forge/cli/dev.ts +1137 -53
  202. package/src/forge/cli/docs.ts +265 -0
  203. package/src/forge/cli/doctor.ts +297 -0
  204. package/src/forge/cli/env-placeholders.ts +62 -0
  205. package/src/forge/cli/evolution.ts +40 -0
  206. package/src/forge/cli/fabric.ts +253 -0
  207. package/src/forge/cli/field-test.ts +568 -0
  208. package/src/forge/cli/golden-path.ts +487 -0
  209. package/src/forge/cli/handoff.ts +336 -0
  210. package/src/forge/cli/index.ts +1 -0
  211. package/src/forge/cli/last-run.ts +84 -0
  212. package/src/forge/cli/main.ts +196 -3
  213. package/src/forge/cli/new.ts +169 -17
  214. package/src/forge/cli/next-actions.ts +23 -0
  215. package/src/forge/cli/output.ts +398 -6
  216. package/src/forge/cli/parse.ts +1377 -38
  217. package/src/forge/cli/progress.ts +51 -0
  218. package/src/forge/cli/query.ts +32 -0
  219. package/src/forge/cli/release.ts +35 -11
  220. package/src/forge/cli/repository.ts +151 -0
  221. package/src/forge/cli/run.ts +42 -0
  222. package/src/forge/cli/secrets.ts +137 -1
  223. package/src/forge/cli/seed.ts +581 -0
  224. package/src/forge/cli/self-host.ts +56 -14
  225. package/src/forge/cli/studio.ts +2282 -0
  226. package/src/forge/cli/verify.ts +1458 -32
  227. package/src/forge/cli/windows.ts +23 -0
  228. package/src/forge/cli/workos.ts +3509 -0
  229. package/src/forge/compiler/agent-contract/build.ts +728 -64
  230. package/src/forge/compiler/agent-contract/types.ts +45 -2
  231. package/src/forge/compiler/ai-registry/parse.ts +71 -2
  232. package/src/forge/compiler/api-surface/build.ts +47 -0
  233. package/src/forge/compiler/app-graph/build.ts +112 -9
  234. package/src/forge/compiler/app-graph/extract.ts +107 -0
  235. package/src/forge/compiler/app-graph/module-graph.ts +73 -78
  236. package/src/forge/compiler/app-graph/parser.ts +24 -24
  237. package/src/forge/compiler/app-graph/profile.ts +26 -0
  238. package/src/forge/compiler/app-graph/tsconfig-hash.ts +41 -4
  239. package/src/forge/compiler/app-graph/versions.ts +1 -1
  240. package/src/forge/compiler/classifier/capabilities.ts +3 -2
  241. package/src/forge/compiler/classifier/classify.ts +32 -8
  242. package/src/forge/compiler/classifier/secrets.ts +3 -2
  243. package/src/forge/compiler/classifier/signals.ts +91 -1
  244. package/src/forge/compiler/client-sdk/build-manifest.ts +59 -0
  245. package/src/forge/compiler/client-sdk/render-client.ts +295 -13
  246. package/src/forge/compiler/data-graph/build.ts +29 -1
  247. package/src/forge/compiler/data-graph/parse.ts +20 -4
  248. package/src/forge/compiler/data-graph/sql/ddl.ts +144 -31
  249. package/src/forge/compiler/data-graph/sql/serialize.ts +4 -0
  250. package/src/forge/compiler/data-graph/sql/types.ts +1 -0
  251. package/src/forge/compiler/diagnostics/codes.ts +38 -0
  252. package/src/forge/compiler/diagnostics/create.ts +28 -2
  253. package/src/forge/compiler/diagnostics/index.ts +2 -0
  254. package/src/forge/compiler/emitter/barrel.ts +3 -0
  255. package/src/forge/compiler/emitter/render.ts +9 -0
  256. package/src/forge/compiler/external-manifest/registry.ts +217 -0
  257. package/src/forge/compiler/external-manifest/types.ts +91 -0
  258. package/src/forge/compiler/external-manifest/validate.ts +373 -0
  259. package/src/forge/compiler/frontend-graph/build.ts +142 -14
  260. package/src/forge/compiler/integration/add.ts +991 -33
  261. package/src/forge/compiler/integration/plan.ts +43 -12
  262. package/src/forge/compiler/integration/render.ts +29 -0
  263. package/src/forge/compiler/integration/snapshot.ts +2 -0
  264. package/src/forge/compiler/integration/templates/convex.ts +70 -0
  265. package/src/forge/compiler/integration/templates/index.ts +1 -0
  266. package/src/forge/compiler/integration/templates/render.ts +38 -0
  267. package/src/forge/compiler/integration/templates/types.ts +3 -0
  268. package/src/forge/compiler/integration/templates/workos.ts +1439 -0
  269. package/src/forge/compiler/make-registry/build.ts +8 -9
  270. package/src/forge/compiler/orchestrator/fast-check.ts +4 -0
  271. package/src/forge/compiler/orchestrator/generate-lock.ts +14 -3
  272. package/src/forge/compiler/orchestrator/manifest.ts +1 -1
  273. package/src/forge/compiler/orchestrator/plan-profile.ts +23 -0
  274. package/src/forge/compiler/orchestrator/plan.ts +249 -10
  275. package/src/forge/compiler/orchestrator/profile.ts +65 -0
  276. package/src/forge/compiler/orchestrator/run.ts +101 -33
  277. package/src/forge/compiler/orchestrator/serialize.ts +33 -8
  278. package/src/forge/compiler/orchestrator/types.ts +1 -1
  279. package/src/forge/compiler/package-graph/compiler.ts +3 -1
  280. package/src/forge/compiler/package-graph/constants.ts +1 -1
  281. package/src/forge/compiler/package-graph/dts-extractor.ts +14 -1
  282. package/src/forge/compiler/package-manager/adapter.ts +4 -1
  283. package/src/forge/compiler/package-manager/commands.ts +4 -0
  284. package/src/forge/compiler/package-manager/executor.ts +30 -1
  285. package/src/forge/compiler/package-manager/version.ts +36 -12
  286. package/src/forge/compiler/package-upgrades/planner.ts +155 -18
  287. package/src/forge/compiler/package-upgrades/types.ts +2 -0
  288. package/src/forge/compiler/policy-registry/build.ts +47 -2
  289. package/src/forge/compiler/policy-registry/parse.ts +32 -2
  290. package/src/forge/compiler/recipes/definitions.ts +63 -0
  291. package/src/forge/compiler/recipes/index.ts +2 -0
  292. package/src/forge/compiler/recipes/profiles.ts +81 -0
  293. package/src/forge/compiler/recipes/registry.ts +6 -0
  294. package/src/forge/compiler/test-graph/build.ts +11 -3
  295. package/src/forge/compiler/types/app-graph.ts +8 -2
  296. package/src/forge/compiler/types/cli.ts +76 -1
  297. package/src/forge/compiler/types/dev-manifest.ts +4 -0
  298. package/src/forge/compiler/types/emit.ts +2 -0
  299. package/src/forge/compiler/types/frontend-graph.ts +2 -2
  300. package/src/forge/compiler/types/integration.ts +1 -0
  301. package/src/forge/compiler/types/package-graph.ts +6 -0
  302. package/src/forge/compiler/types/policy-registry.ts +3 -1
  303. package/src/forge/delta/broker-runner.mjs +5 -0
  304. package/src/forge/delta/broker.ts +429 -0
  305. package/src/forge/delta/classifier.ts +52 -0
  306. package/src/forge/delta/explain.ts +238 -0
  307. package/src/forge/delta/git-observer.ts +67 -0
  308. package/src/forge/delta/ids.ts +44 -0
  309. package/src/forge/delta/index.ts +25 -0
  310. package/src/forge/delta/process-identity.ts +37 -0
  311. package/src/forge/delta/queue-status.ts +190 -0
  312. package/src/forge/delta/recorder.ts +540 -0
  313. package/src/forge/delta/redaction.ts +50 -0
  314. package/src/forge/delta/schema.ts +240 -0
  315. package/src/forge/delta/session.ts +142 -0
  316. package/src/forge/delta/status.ts +1139 -0
  317. package/src/forge/delta/store.ts +3329 -0
  318. package/src/forge/delta/timeline.ts +178 -0
  319. package/src/forge/dev/server.ts +1061 -22
  320. package/src/forge/dev/types.ts +15 -1
  321. package/src/forge/dev/watch.ts +17 -7
  322. package/src/forge/dev-console/cycle.ts +285 -33
  323. package/src/forge/dev-console/types.ts +47 -1
  324. package/src/forge/impact/index.ts +222 -30
  325. package/src/forge/impact/types.ts +33 -1
  326. package/src/forge/intent/index.ts +35 -16
  327. package/src/forge/make/fields.ts +26 -0
  328. package/src/forge/make/index.ts +42 -6
  329. package/src/forge/make/templates.ts +211 -4
  330. package/src/forge/make/types.ts +2 -1
  331. package/src/forge/policy.ts +1 -1
  332. package/src/forge/react/index.ts +160 -2
  333. package/src/forge/refactor/index.ts +1 -0
  334. package/src/forge/repair/rules/index.ts +2 -2
  335. package/src/forge/repository-analysis/adapters/common.ts +72 -0
  336. package/src/forge/repository-analysis/adapters/docker.ts +185 -0
  337. package/src/forge/repository-analysis/adapters/java.ts +133 -0
  338. package/src/forge/repository-analysis/adapters/typescript.ts +350 -0
  339. package/src/forge/repository-analysis/adapters/vue.ts +71 -0
  340. package/src/forge/repository-analysis/analyze.ts +434 -0
  341. package/src/forge/repository-analysis/benchmark.ts +336 -0
  342. package/src/forge/repository-analysis/cache-gc.ts +154 -0
  343. package/src/forge/repository-analysis/check-selection.ts +123 -0
  344. package/src/forge/repository-analysis/context-node.ts +17 -0
  345. package/src/forge/repository-analysis/context.ts +456 -0
  346. package/src/forge/repository-analysis/http-resolution.ts +135 -0
  347. package/src/forge/repository-analysis/identity.ts +45 -0
  348. package/src/forge/repository-analysis/index.ts +18 -0
  349. package/src/forge/repository-analysis/nuxt-resolution.ts +188 -0
  350. package/src/forge/repository-analysis/quality.ts +60 -0
  351. package/src/forge/repository-analysis/retrieval.ts +309 -0
  352. package/src/forge/repository-analysis/runtime-artifacts.ts +195 -0
  353. package/src/forge/repository-analysis/runtime-observation.ts +325 -0
  354. package/src/forge/repository-analysis/scanner.ts +174 -0
  355. package/src/forge/repository-analysis/storage.ts +193 -0
  356. package/src/forge/repository-analysis/test-associations.ts +59 -0
  357. package/src/forge/repository-analysis/types.ts +99 -0
  358. package/src/forge/repository-manifest/index.ts +178 -0
  359. package/src/forge/repository-manifest/types.ts +63 -0
  360. package/src/forge/review/index.ts +162 -12
  361. package/src/forge/review/types.ts +15 -0
  362. package/src/forge/runtime/ai/providers.ts +19 -4
  363. package/src/forge/runtime/auth/config.ts +17 -0
  364. package/src/forge/runtime/auth/evaluate.ts +15 -2
  365. package/src/forge/runtime/auth/resolve.ts +61 -6
  366. package/src/forge/runtime/auth/types.ts +1 -0
  367. package/src/forge/runtime/db/factory.ts +1 -3
  368. package/src/forge/runtime/db/generated-client.ts +13 -2
  369. package/src/forge/runtime/db/memory-adapter.ts +139 -32
  370. package/src/forge/runtime/db/pglite-adapter.ts +285 -2
  371. package/src/forge/runtime/executor.ts +129 -16
  372. package/src/forge/runtime/external/bridge.ts +663 -0
  373. package/src/forge/runtime/runner/run-entry.ts +16 -7
  374. package/src/forge/runtime/telemetry/buffer.ts +4 -1
  375. package/src/forge/runtime/telemetry/scrubber.ts +35 -5
  376. package/src/forge/runtime/webhooks/security.ts +12 -7
  377. package/src/forge/server.ts +71 -7
  378. package/src/forge/ui/index.ts +831 -19
  379. package/src/forge/ui/types.ts +3 -1
  380. package/src/forge/version.ts +1 -1
  381. package/src/forge/vue/index.ts +422 -0
  382. package/src/forge/workspace/baseline.ts +112 -0
  383. package/src/forge/workspace/change-summary.ts +292 -0
  384. package/src/forge/workspace/forge-cli.ts +42 -0
  385. package/src/forge/workspace/git-summary.ts +393 -0
  386. package/templates/agent-workroom/AGENTS.md +29 -0
  387. package/templates/agent-workroom/README.md +34 -0
  388. package/templates/agent-workroom/forge.config.ts +3 -0
  389. package/templates/agent-workroom/package.json +34 -0
  390. package/templates/agent-workroom/pnpm-workspace.yaml +2 -0
  391. package/templates/agent-workroom/src/actions/indexAgentSignal.ts +10 -0
  392. package/templates/agent-workroom/src/commands/openWorkroom.ts +61 -0
  393. package/templates/agent-workroom/src/commands/recordAgentSignal.ts +119 -0
  394. package/templates/agent-workroom/src/commands/recordCheckRun.ts +52 -0
  395. package/templates/agent-workroom/src/forge/schema.ts +54 -0
  396. package/templates/agent-workroom/src/policies.ts +6 -0
  397. package/templates/agent-workroom/src/queries/listWorkrooms.ts +11 -0
  398. package/templates/agent-workroom/src/queries/liveWorkroom.ts +63 -0
  399. package/templates/agent-workroom/tsconfig.json +16 -0
  400. package/templates/agent-workroom/web/index.html +13 -0
  401. package/templates/agent-workroom/web/package.json +21 -0
  402. package/templates/agent-workroom/web/src/App.tsx +345 -0
  403. package/templates/agent-workroom/web/src/lib/forge.ts +20 -0
  404. package/templates/agent-workroom/web/src/main.tsx +13 -0
  405. package/templates/agent-workroom/web/src/styles.css +545 -0
  406. package/templates/agent-workroom/web/tsconfig.json +27 -0
  407. package/templates/agent-workroom/web/vite.config.ts +36 -0
  408. package/templates/b2b-support-web/package.json +2 -0
  409. package/templates/b2b-support-web/pnpm-workspace.yaml +2 -0
  410. package/templates/b2b-support-web/web/app/page.tsx +2 -2
  411. package/templates/b2b-support-web/web/lib/forge.ts +9 -2
  412. package/templates/b2b-support-web/web/next.config.ts +24 -0
  413. package/templates/b2b-support-web/web/package.json +1 -1
  414. package/templates/minimal-web/package.json +2 -1
  415. package/templates/minimal-web/pnpm-workspace.yaml +2 -0
  416. package/templates/minimal-web/web/index.html +1 -0
  417. package/templates/minimal-web/web/src/App.tsx +12 -6
  418. package/templates/minimal-web/web/src/lib/forge.ts +9 -2
  419. package/templates/minimal-web/web/src/styles.css +34 -0
  420. package/templates/minimal-web/web/vite.config.ts +36 -0
  421. package/templates/nuxt-web/.vscode/settings.json +14 -0
  422. package/templates/nuxt-web/.yarnrc.yml +1 -0
  423. package/templates/nuxt-web/README.md +30 -0
  424. package/templates/nuxt-web/forge.config.ts +3 -0
  425. package/templates/nuxt-web/package.json +37 -0
  426. package/templates/nuxt-web/pnpm-workspace.yaml +2 -0
  427. package/templates/nuxt-web/src/actions/logNoteCreated.ts +11 -0
  428. package/templates/nuxt-web/src/commands/createNote.ts +26 -0
  429. package/templates/nuxt-web/src/forge/schema.ts +12 -0
  430. package/templates/nuxt-web/src/policies.ts +6 -0
  431. package/templates/nuxt-web/src/queries/listNotes.ts +8 -0
  432. package/templates/nuxt-web/src/queries/liveNotes.ts +8 -0
  433. package/templates/nuxt-web/tsconfig.json +17 -0
  434. package/templates/nuxt-web/web/app.vue +67 -0
  435. package/templates/nuxt-web/web/components/LiveNotes.vue +89 -0
  436. package/templates/nuxt-web/web/components/NoteComposer.vue +100 -0
  437. package/templates/nuxt-web/web/composables/forge.ts +13 -0
  438. package/templates/nuxt-web/web/composables/useNotes.ts +24 -0
  439. package/templates/nuxt-web/web/nuxt.config.ts +11 -0
  440. package/templates/nuxt-web/web/package.json +22 -0
  441. package/templates/nuxt-web/web/plugins/forge.client.ts +10 -0
  442. package/templates/nuxt-web/web/plugins/forge.server.ts +10 -0
  443. package/templates/nuxt-web/web/server/api/forge-health.get.ts +7 -0
  444. package/templates/nuxt-web/web/tsconfig.json +6 -0
  445. package/templates/vendor-access/.vscode/settings.json +14 -0
  446. package/templates/vendor-access/README.md +30 -0
  447. package/templates/vendor-access/forge.config.ts +3 -0
  448. package/templates/vendor-access/package.json +35 -0
  449. package/templates/vendor-access/pnpm-workspace.yaml +2 -0
  450. package/templates/vendor-access/src/commands/addEvidence.ts +42 -0
  451. package/templates/vendor-access/src/commands/approveAccessRequest.ts +43 -0
  452. package/templates/vendor-access/src/commands/createAccessRequest.ts +49 -0
  453. package/templates/vendor-access/src/commands/seedVendorAccessDemo.ts +200 -0
  454. package/templates/vendor-access/src/forge/schema.ts +74 -0
  455. package/templates/vendor-access/src/policies.ts +11 -0
  456. package/templates/vendor-access/src/queries/listVendorAccessDashboard.ts +24 -0
  457. package/templates/vendor-access/src/queries/liveVendorAccessDashboard.ts +24 -0
  458. package/templates/vendor-access/tsconfig.json +17 -0
  459. package/templates/vendor-access/web/index.html +13 -0
  460. package/templates/vendor-access/web/package.json +21 -0
  461. package/templates/vendor-access/web/src/App.tsx +573 -0
  462. package/templates/vendor-access/web/src/lib/forge.ts +20 -0
  463. package/templates/vendor-access/web/src/main.tsx +205 -0
  464. package/templates/vendor-access/web/src/styles.css +682 -0
  465. package/templates/vendor-access/web/tsconfig.json +18 -0
  466. package/templates/vendor-access/web/vite.config.ts +36 -0
  467. package/src/forge/_generated/actionSubscriptions.json +0 -2
  468. package/src/forge/_generated/actionSubscriptions.ts +0 -10
  469. package/src/forge/_generated/agentAdapterManifest.json +0 -2
  470. package/src/forge/_generated/agentAdapterManifest.ts +0 -73
  471. package/src/forge/_generated/agentContract.json +0 -2
  472. package/src/forge/_generated/agentContract.ts +0 -7829
  473. package/src/forge/_generated/agentQuickstart.md +0 -34
  474. package/src/forge/_generated/agentTools.json +0 -2
  475. package/src/forge/_generated/agentTools.md +0 -16
  476. package/src/forge/_generated/agentTools.ts +0 -12
  477. package/src/forge/_generated/aiContext.ts +0 -125
  478. package/src/forge/_generated/aiModels.json +0 -2
  479. package/src/forge/_generated/aiModels.ts +0 -51
  480. package/src/forge/_generated/aiProviders.json +0 -2
  481. package/src/forge/_generated/aiProviders.ts +0 -23
  482. package/src/forge/_generated/aiRegistry.json +0 -2
  483. package/src/forge/_generated/aiRegistry.ts +0 -31
  484. package/src/forge/_generated/api.json +0 -2
  485. package/src/forge/_generated/api.ts +0 -8
  486. package/src/forge/_generated/appGraph.json +0 -2
  487. package/src/forge/_generated/appGraph.ts +0 -14937
  488. package/src/forge/_generated/appMap.md +0 -55
  489. package/src/forge/_generated/artifactManifest.json +0 -2
  490. package/src/forge/_generated/artifactManifest.ts +0 -7
  491. package/src/forge/_generated/authClaims.json +0 -2
  492. package/src/forge/_generated/authClaims.ts +0 -13
  493. package/src/forge/_generated/authConfig.json +0 -2
  494. package/src/forge/_generated/authConfig.ts +0 -17
  495. package/src/forge/_generated/authContext.ts +0 -23
  496. package/src/forge/_generated/authRegistry.json +0 -2
  497. package/src/forge/_generated/authRegistry.ts +0 -25
  498. package/src/forge/_generated/buildInfo.json +0 -2
  499. package/src/forge/_generated/buildInfo.ts +0 -9
  500. package/src/forge/_generated/capabilityMap.json +0 -2
  501. package/src/forge/_generated/capabilityMap.md +0 -15
  502. package/src/forge/_generated/capabilityMap.ts +0 -17
  503. package/src/forge/_generated/client.ts +0 -282
  504. package/src/forge/_generated/clientApi.ts +0 -9
  505. package/src/forge/_generated/clientManifest.json +0 -2
  506. package/src/forge/_generated/clientManifest.ts +0 -39
  507. package/src/forge/_generated/clientTypes.ts +0 -78
  508. package/src/forge/_generated/configRegistry.json +0 -2
  509. package/src/forge/_generated/configRegistry.ts +0 -4
  510. package/src/forge/_generated/dataGraph.json +0 -2
  511. package/src/forge/_generated/dataGraph.ts +0 -8
  512. package/src/forge/_generated/db.json +0 -2
  513. package/src/forge/_generated/db.ts +0 -2
  514. package/src/forge/_generated/dbSecurityManifest.json +0 -2
  515. package/src/forge/_generated/dbSecurityManifest.ts +0 -15
  516. package/src/forge/_generated/dbSessionContext.json +0 -2
  517. package/src/forge/_generated/dbSessionContext.ts +0 -39
  518. package/src/forge/_generated/deployManifest.json +0 -2
  519. package/src/forge/_generated/deployManifest.ts +0 -14
  520. package/src/forge/_generated/devManifest.json +0 -2
  521. package/src/forge/_generated/devManifest.ts +0 -62
  522. package/src/forge/_generated/envSchema.json +0 -2
  523. package/src/forge/_generated/envSchema.ts +0 -59
  524. package/src/forge/_generated/frontendGraph.json +0 -2
  525. package/src/forge/_generated/frontendGraph.ts +0 -27
  526. package/src/forge/_generated/importGuards.json +0 -2
  527. package/src/forge/_generated/importGuards.ts +0 -686
  528. package/src/forge/_generated/index.ts +0 -68
  529. package/src/forge/_generated/liveProductionManifest.json +0 -2
  530. package/src/forge/_generated/liveProductionManifest.ts +0 -23
  531. package/src/forge/_generated/liveProtocol.json +0 -2
  532. package/src/forge/_generated/liveProtocol.ts +0 -21
  533. package/src/forge/_generated/liveQueryRegistry.json +0 -2
  534. package/src/forge/_generated/liveQueryRegistry.ts +0 -9
  535. package/src/forge/_generated/liveTransportConfig.json +0 -2
  536. package/src/forge/_generated/liveTransportConfig.ts +0 -19
  537. package/src/forge/_generated/makeRegistry.json +0 -2
  538. package/src/forge/_generated/makeRegistry.ts +0 -177
  539. package/src/forge/_generated/makeTemplates.json +0 -2
  540. package/src/forge/_generated/makeTemplates.ts +0 -66
  541. package/src/forge/_generated/mockMap.json +0 -2
  542. package/src/forge/_generated/mockMap.ts +0 -7
  543. package/src/forge/_generated/operationPlaybooks.md +0 -167
  544. package/src/forge/_generated/packageGraph.json +0 -2
  545. package/src/forge/_generated/packageGraph.ts +0 -249334
  546. package/src/forge/_generated/packageUpgradeRegistry.json +0 -2
  547. package/src/forge/_generated/packageUpgradeRegistry.ts +0 -15
  548. package/src/forge/_generated/permissionMatrix.json +0 -2
  549. package/src/forge/_generated/permissionMatrix.ts +0 -7
  550. package/src/forge/_generated/policyRegistry.json +0 -2
  551. package/src/forge/_generated/policyRegistry.ts +0 -11
  552. package/src/forge/_generated/queryRegistry.json +0 -2
  553. package/src/forge/_generated/queryRegistry.ts +0 -9
  554. package/src/forge/_generated/react.d.ts +0 -22
  555. package/src/forge/_generated/react.ts +0 -29
  556. package/src/forge/_generated/reactManifest.json +0 -2
  557. package/src/forge/_generated/reactManifest.ts +0 -19
  558. package/src/forge/_generated/rlsPolicies.json +0 -2
  559. package/src/forge/_generated/rlsPolicies.sql +0 -34
  560. package/src/forge/_generated/rlsPolicies.ts +0 -6
  561. package/src/forge/_generated/runtimeGraph.json +0 -2
  562. package/src/forge/_generated/runtimeGraph.ts +0 -8
  563. package/src/forge/_generated/runtimeMatrix.json +0 -2
  564. package/src/forge/_generated/runtimeMatrix.ts +0 -334130
  565. package/src/forge/_generated/runtimeRegistry.ts +0 -2
  566. package/src/forge/_generated/runtimeRules.md +0 -91
  567. package/src/forge/_generated/secretRegistry.json +0 -2
  568. package/src/forge/_generated/secretRegistry.ts +0 -50
  569. package/src/forge/_generated/secretsContext.ts +0 -11
  570. package/src/forge/_generated/serverApi.ts +0 -10
  571. package/src/forge/_generated/sourceMapManifest.json +0 -2
  572. package/src/forge/_generated/sourceMapManifest.ts +0 -7
  573. package/src/forge/_generated/sqlPlan.json +0 -2
  574. package/src/forge/_generated/sqlPlan.ts +0 -88
  575. package/src/forge/_generated/subscriptionManifest.json +0 -2
  576. package/src/forge/_generated/subscriptionManifest.ts +0 -7
  577. package/src/forge/_generated/symbolicationManifest.json +0 -2
  578. package/src/forge/_generated/symbolicationManifest.ts +0 -17
  579. package/src/forge/_generated/telemetryRegistry.json +0 -2
  580. package/src/forge/_generated/telemetryRegistry.ts +0 -9
  581. package/src/forge/_generated/telemetrySinks.json +0 -2
  582. package/src/forge/_generated/telemetrySinks.ts +0 -11
  583. package/src/forge/_generated/tenantScope.json +0 -2
  584. package/src/forge/_generated/tenantScope.ts +0 -8
  585. package/src/forge/_generated/testGraph.json +0 -2
  586. package/src/forge/_generated/testGraph.ts +0 -3452
  587. package/src/forge/_generated/testPlanRegistry.json +0 -2
  588. package/src/forge/_generated/testPlanRegistry.ts +0 -33
  589. package/src/forge/_generated/uiRoutes.json +0 -2
  590. package/src/forge/_generated/uiRoutes.ts +0 -16
  591. package/src/forge/_generated/uiScenarios.json +0 -2
  592. package/src/forge/_generated/uiScenarios.ts +0 -30
  593. package/src/forge/_generated/uiTestManifest.json +0 -2
  594. package/src/forge/_generated/uiTestManifest.ts +0 -27
  595. package/src/forge/_generated/workflowRegistry.json +0 -2
  596. package/src/forge/_generated/workflowRegistry.ts +0 -9
  597. package/src/forge/_generated/workflowSubscriptions.json +0 -2
  598. 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,13 @@ 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";
37
+ import { runSeedCommand, type SeedCommandResult } from "./seed.ts";
22
38
 
23
39
  export interface DevCommandOptions {
24
40
  workspaceRoot: string;
@@ -29,7 +45,7 @@ export interface DevCommandOptions {
29
45
  once?: boolean;
30
46
  watch: boolean;
31
47
  json: boolean;
32
- db: "pglite" | "postgres" | "none";
48
+ db: "memory" | "pglite" | "postgres" | "none";
33
49
  databaseUrl?: string;
34
50
  worker: boolean;
35
51
  withWeb?: boolean;
@@ -37,10 +53,17 @@ export interface DevCommandOptions {
37
53
  webOnly?: boolean;
38
54
  open?: boolean;
39
55
  webPort?: number;
56
+ publicApiUrl?: string;
40
57
  telemetry: string[];
41
58
  envFile?: string;
42
59
  mode?: "dev" | "serve";
43
60
  allowDevAuth?: boolean;
61
+ skipStartupConsole?: boolean;
62
+ detach?: boolean;
63
+ seed?: boolean;
64
+ seedCommand?: string;
65
+ seedAllTenants?: boolean;
66
+ lifecycle?: "status" | "stop";
44
67
  }
45
68
 
46
69
  export interface DevCommandResult {
@@ -49,17 +72,39 @@ export interface DevCommandResult {
49
72
  exitCode: 0 | 1;
50
73
  }
51
74
 
52
- interface WebDevServerHandle {
75
+ export interface DevGenerateResult {
76
+ ok: boolean;
77
+ changed: string[];
78
+ unchanged: string[];
79
+ diagnostics: Array<{ severity: string; code: string; message: string }>;
80
+ exitCode: 0 | 1;
81
+ }
82
+
83
+ export interface DevStartupGeneratedEvidence {
84
+ ok: boolean;
85
+ state: "fresh" | "regenerated" | "stale-risk";
86
+ changedFiles: number;
87
+ sampleChanged: string[];
88
+ hiddenChanged: number;
89
+ message: string;
90
+ command: string;
91
+ checkCommand: string;
92
+ }
93
+
94
+ export interface WebDevServerHandle {
53
95
  url: string;
54
96
  port: number;
97
+ requestedPort?: number;
98
+ autoPortSelected: boolean;
55
99
  command: string[];
56
100
  stop: () => void;
57
101
  }
58
102
 
59
- interface DevStartupSummary {
103
+ export interface DevStartupSummary {
60
104
  schemaVersion: "0.1.0";
61
105
  ok: true;
62
106
  mode: "dev";
107
+ warnings: Array<{ code: string; message: string; nextAction?: string }>;
63
108
  api: {
64
109
  url: string;
65
110
  host: string;
@@ -70,14 +115,25 @@ interface DevStartupSummary {
70
115
  web: null | {
71
116
  url: string;
72
117
  port: number;
118
+ requestedPort?: number;
119
+ autoPortSelected: boolean;
73
120
  command: string[];
74
121
  framework: string;
75
122
  routes: string[];
76
123
  bridgeFiles: string[];
77
124
  apiUrlEnv?: string;
78
125
  };
126
+ preview: {
127
+ studioUrl?: string;
128
+ targetAppUrl: string;
129
+ targetAppPort: number;
130
+ isStudioSelfPreview: boolean;
131
+ note: string;
132
+ };
79
133
  watch: {
80
134
  enabled: boolean;
135
+ autoGenerate: boolean;
136
+ reloadsRuntime: boolean;
81
137
  };
82
138
  runtime: {
83
139
  routes: Array<{ method: string; path: string; purpose: string }>;
@@ -90,15 +146,576 @@ interface DevStartupSummary {
90
146
  bridgeFiles: string[];
91
147
  diagnostics: number;
92
148
  };
149
+ generated: {
150
+ state: "fresh" | "regenerated" | "stale-risk";
151
+ generatorVersion?: string;
152
+ inputHash?: string;
153
+ buildInfoHash?: string;
154
+ runtimeLoadedHash?: string;
155
+ runtimeStaleRisk: boolean;
156
+ changedFiles: number;
157
+ sampleChanged: string[];
158
+ hiddenChanged: number;
159
+ command: string;
160
+ checkCommand: string;
161
+ message: string;
162
+ };
163
+ seed: {
164
+ requested: boolean;
165
+ available: boolean;
166
+ commands: string[];
167
+ selectedCommand?: string;
168
+ readiness?: SeedCommandResult["readiness"];
169
+ ran: boolean;
170
+ ok?: boolean;
171
+ responseStatus?: number;
172
+ tenantRuns?: Array<{
173
+ tenantId: string;
174
+ label?: string;
175
+ organizationName?: string;
176
+ ok: boolean;
177
+ responseStatus?: number;
178
+ }>;
179
+ error?: string;
180
+ nextActions: string[];
181
+ };
93
182
  next: {
94
183
  browserUrl: string;
95
184
  apiIndex: string;
96
185
  inspect: string;
97
186
  verify: string;
187
+ changed: string;
98
188
  };
99
189
  pid: number;
100
190
  }
101
191
 
192
+ interface DevStartFailure {
193
+ message: string;
194
+ code?: string;
195
+ failureKind: "port_busy" | "pglite_store_aborted" | "dev_start_failed";
196
+ nextActions: string[];
197
+ busy?: {
198
+ port: number;
199
+ host: string;
200
+ suggestedCommands: string[];
201
+ };
202
+ }
203
+
204
+ const DEV_STATE_DIR = ".forge/dev";
205
+ const DEV_PID_FILE = `${DEV_STATE_DIR}/dev.pid`;
206
+ const DEV_LOG_FILE = `${DEV_STATE_DIR}/dev.log`;
207
+
208
+ function devStatePaths(workspaceRoot: string): { dir: string; pidFile: string; logFile: string } {
209
+ return {
210
+ dir: join(workspaceRoot, DEV_STATE_DIR),
211
+ pidFile: join(workspaceRoot, DEV_PID_FILE),
212
+ logFile: join(workspaceRoot, DEV_LOG_FILE),
213
+ };
214
+ }
215
+
216
+ function readDetachedDevPid(workspaceRoot: string): number | null {
217
+ const text = nodeFileSystem.readText(devStatePaths(workspaceRoot).pidFile);
218
+ if (text === null) {
219
+ return null;
220
+ }
221
+ const pid = Number(text.trim());
222
+ return Number.isInteger(pid) && pid > 0 ? pid : null;
223
+ }
224
+
225
+ function isProcessRunning(pid: number): boolean {
226
+ try {
227
+ process.kill(pid, 0);
228
+ return true;
229
+ } catch (error) {
230
+ return error instanceof Error && "code" in error && error.code === "EPERM";
231
+ }
232
+ }
233
+
234
+ function removeDevPidFile(workspaceRoot: string): void {
235
+ const paths = devStatePaths(workspaceRoot);
236
+ if (nodeFileSystem.exists(paths.pidFile)) {
237
+ nodeFileSystem.remove(paths.pidFile);
238
+ }
239
+ }
240
+
241
+ function processDetails(pid: number): { pid: number; cwd?: string; command?: string } {
242
+ const details: { pid: number; cwd?: string; command?: string } = { pid };
243
+ try {
244
+ details.cwd = readlinkSync(`/proc/${pid}/cwd`);
245
+ } catch {
246
+ // /proc is Linux-only; process metadata is best-effort.
247
+ }
248
+ try {
249
+ const raw = readFileSync(`/proc/${pid}/cmdline`, "utf8");
250
+ const command = raw.split("\0").filter(Boolean).join(" ");
251
+ if (command) details.command = command;
252
+ } catch {
253
+ // /proc is Linux-only; process metadata is best-effort.
254
+ }
255
+ return details;
256
+ }
257
+
258
+ function printDevLifecycleResult(input: {
259
+ options: DevCommandOptions;
260
+ ok: boolean;
261
+ action: "detach" | "status" | "stop";
262
+ running: boolean;
263
+ pid?: number;
264
+ process?: { pid: number; cwd?: string; command?: string };
265
+ logFile: string;
266
+ message: string;
267
+ exitCode: 0 | 1;
268
+ }): DevCommandResult {
269
+ const payload = {
270
+ ok: input.ok,
271
+ action: input.action,
272
+ running: input.running,
273
+ ...(input.pid !== undefined ? { pid: input.pid } : {}),
274
+ ...(input.process ? { process: input.process } : {}),
275
+ logFile: input.logFile,
276
+ message: input.message,
277
+ nextActions: input.running
278
+ ? ["forge dev status --json", "forge dev stop --json", `tail -f ${input.logFile}`]
279
+ : ["forge dev --detach --json"],
280
+ exitCode: input.exitCode,
281
+ };
282
+ if (input.options.json) {
283
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
284
+ } else {
285
+ process.stdout.write(`${input.message}\n`);
286
+ if (input.pid !== undefined) process.stdout.write(`pid: ${input.pid}\n`);
287
+ if (input.process?.cwd) process.stdout.write(`cwd: ${input.process.cwd}\n`);
288
+ if (input.process?.command) process.stdout.write(`command: ${input.process.command}\n`);
289
+ process.stdout.write(`log: ${input.logFile}\n`);
290
+ }
291
+ return { exitCode: input.exitCode };
292
+ }
293
+
294
+ function buildDetachedDevArgs(options: DevCommandOptions): string[] {
295
+ const args = ["dev", "--skip-startup-console"];
296
+ if (options.host) args.push("--host", options.host);
297
+ if (options.port !== undefined) args.push("--port", String(options.port));
298
+ if (options.mock) args.push("--mock");
299
+ if (options.mockAi) args.push("--mock-ai");
300
+ if (!options.watch) args.push("--no-watch");
301
+ if (options.db) args.push("--db", options.db);
302
+ if (options.databaseUrl) args.push("--database-url", options.databaseUrl);
303
+ if (!options.worker) args.push("--no-worker");
304
+ if (options.withWeb === false) args.push("--no-web");
305
+ if (options.apiOnly) args.push("--api-only");
306
+ if (options.webOnly) args.push("--web-only");
307
+ if (options.open) args.push("--open");
308
+ if (options.webPort !== undefined) args.push("--web-port", String(options.webPort));
309
+ if (options.publicApiUrl) args.push("--public-api-url", options.publicApiUrl);
310
+ if (options.telemetry.length > 0) args.push("--telemetry", options.telemetry.join(","));
311
+ if (options.envFile) args.push("--env-file", options.envFile);
312
+ if (options.seed) args.push("--seed");
313
+ if (options.seedCommand) args.push("--seed-command", options.seedCommand);
314
+ if (options.seedAllTenants) args.push("--all-tenants");
315
+ return args;
316
+ }
317
+
318
+ function runDevStatus(options: DevCommandOptions): DevCommandResult {
319
+ const paths = devStatePaths(options.workspaceRoot);
320
+ const pid = readDetachedDevPid(options.workspaceRoot);
321
+ const running = pid !== null && isProcessRunning(pid);
322
+ if (pid !== null && !running) {
323
+ removeDevPidFile(options.workspaceRoot);
324
+ }
325
+ return printDevLifecycleResult({
326
+ options,
327
+ ok: true,
328
+ action: "status",
329
+ running,
330
+ ...(pid !== null ? { pid } : {}),
331
+ ...(pid !== null && running ? { process: processDetails(pid) } : {}),
332
+ logFile: paths.logFile,
333
+ message: running ? "forge dev detached server is running" : "forge dev detached server is not running",
334
+ exitCode: 0,
335
+ });
336
+ }
337
+
338
+ function runDevStop(options: DevCommandOptions): DevCommandResult {
339
+ const paths = devStatePaths(options.workspaceRoot);
340
+ const pid = readDetachedDevPid(options.workspaceRoot);
341
+ if (pid === null || !isProcessRunning(pid)) {
342
+ removeDevPidFile(options.workspaceRoot);
343
+ return printDevLifecycleResult({
344
+ options,
345
+ ok: true,
346
+ action: "stop",
347
+ running: false,
348
+ ...(pid !== null ? { pid } : {}),
349
+ ...(pid !== null ? { process: processDetails(pid) } : {}),
350
+ logFile: paths.logFile,
351
+ message: "no detached forge dev server was running",
352
+ exitCode: 0,
353
+ });
354
+ }
355
+ process.kill(pid, "SIGTERM");
356
+ removeDevPidFile(options.workspaceRoot);
357
+ return printDevLifecycleResult({
358
+ options,
359
+ ok: true,
360
+ action: "stop",
361
+ running: false,
362
+ pid,
363
+ process: processDetails(pid),
364
+ logFile: paths.logFile,
365
+ message: "stopped detached forge dev server",
366
+ exitCode: 0,
367
+ });
368
+ }
369
+
370
+ function runDevDetach(options: DevCommandOptions): DevCommandResult {
371
+ if (options.once) {
372
+ return printDevLifecycleResult({
373
+ options,
374
+ ok: false,
375
+ action: "detach",
376
+ running: false,
377
+ logFile: devStatePaths(options.workspaceRoot).logFile,
378
+ message: "forge dev --detach cannot be combined with --once",
379
+ exitCode: 1,
380
+ });
381
+ }
382
+ const existingPid = readDetachedDevPid(options.workspaceRoot);
383
+ const paths = devStatePaths(options.workspaceRoot);
384
+ nodeFileSystem.mkdirp(paths.dir);
385
+ if (existingPid !== null && isProcessRunning(existingPid)) {
386
+ return printDevLifecycleResult({
387
+ options,
388
+ ok: true,
389
+ action: "detach",
390
+ running: true,
391
+ pid: existingPid,
392
+ process: processDetails(existingPid),
393
+ logFile: paths.logFile,
394
+ message: "forge dev detached server is already running",
395
+ exitCode: 0,
396
+ });
397
+ }
398
+
399
+ const cliPath = process.argv[1];
400
+ if (!cliPath) {
401
+ return printDevLifecycleResult({
402
+ options,
403
+ ok: false,
404
+ action: "detach",
405
+ running: false,
406
+ logFile: paths.logFile,
407
+ message: "could not resolve current forge CLI path for detached dev server",
408
+ exitCode: 1,
409
+ });
410
+ }
411
+
412
+ const fd = openSync(paths.logFile, "a");
413
+ try {
414
+ const child = spawn(process.execPath, [cliPath, ...buildDetachedDevArgs(options)], {
415
+ cwd: options.workspaceRoot,
416
+ detached: true,
417
+ stdio: ["ignore", fd, fd],
418
+ windowsHide: true,
419
+ });
420
+ if (child.pid === undefined) {
421
+ return printDevLifecycleResult({
422
+ options,
423
+ ok: false,
424
+ action: "detach",
425
+ running: false,
426
+ logFile: paths.logFile,
427
+ message: "detached forge dev process started without a pid",
428
+ exitCode: 1,
429
+ });
430
+ }
431
+ child.unref();
432
+ nodeFileSystem.writeText(paths.pidFile, `${child.pid}\n`);
433
+ return printDevLifecycleResult({
434
+ options,
435
+ ok: true,
436
+ action: "detach",
437
+ running: true,
438
+ pid: child.pid,
439
+ process: processDetails(child.pid),
440
+ logFile: paths.logFile,
441
+ message: "started detached forge dev server",
442
+ exitCode: 0,
443
+ });
444
+ } finally {
445
+ closeSync(fd);
446
+ }
447
+ }
448
+
449
+ function nextPreviewPort(webUrl?: string): number {
450
+ if (!webUrl) {
451
+ return 5174;
452
+ }
453
+ try {
454
+ const parsed = new URL(webUrl);
455
+ const port = parsed.port ? Number(parsed.port) : parsed.protocol === "https:" ? 443 : 80;
456
+ return Number.isFinite(port) && port > 0 ? port + 1 : 5174;
457
+ } catch {
458
+ return 5174;
459
+ }
460
+ }
461
+
462
+ function isForgeStudioWorkspace(workspaceRoot: string): boolean {
463
+ return basename(workspaceRoot).toLowerCase() === "forge-studio";
464
+ }
465
+
466
+ function previewSummaryFor(input: {
467
+ workspaceRoot: string;
468
+ host: string;
469
+ webUrl?: string;
470
+ }): DevStartupSummary["preview"] {
471
+ if (input.webUrl && !isForgeStudioWorkspace(input.workspaceRoot)) {
472
+ const parsed = new URL(input.webUrl);
473
+ const targetAppPort = parsed.port ? Number(parsed.port) : parsed.protocol === "https:" ? 443 : 80;
474
+ return {
475
+ targetAppUrl: input.webUrl,
476
+ targetAppPort,
477
+ isStudioSelfPreview: false,
478
+ note: `Web app preview is running at ${input.webUrl}.`,
479
+ };
480
+ }
481
+
482
+ const targetAppPort = nextPreviewPort(input.webUrl);
483
+ const targetAppUrl = `http://${input.host}:${targetAppPort}`;
484
+ const isStudioSelfPreview = Boolean(input.webUrl && input.webUrl === targetAppUrl);
485
+ return {
486
+ ...(input.webUrl ? { studioUrl: input.webUrl } : {}),
487
+ targetAppUrl,
488
+ targetAppPort,
489
+ isStudioSelfPreview,
490
+ note: input.webUrl
491
+ ? `Use ${targetAppUrl} for the app being built when ${input.webUrl} is Forge Studio itself.`
492
+ : `No web app was detected; ${targetAppUrl} is the default target app preview URL for Studio attach flows.`,
493
+ };
494
+ }
495
+
496
+ async function isPortAvailable(host: string, port: number): Promise<boolean> {
497
+ if (port === 0) {
498
+ return true;
499
+ }
500
+ const server = createNetServer();
501
+ return new Promise<boolean>((resolve) => {
502
+ const cleanup = () => {
503
+ server.removeAllListeners();
504
+ };
505
+ server.once("error", () => {
506
+ cleanup();
507
+ resolve(false);
508
+ });
509
+ server.listen(port, host, () => {
510
+ server.close(() => {
511
+ cleanup();
512
+ resolve(true);
513
+ });
514
+ });
515
+ });
516
+ }
517
+
518
+ async function reserveEphemeralPort(host: string): Promise<number> {
519
+ const server = createNetServer();
520
+ return new Promise<number>((resolve, reject) => {
521
+ const cleanup = () => {
522
+ server.removeAllListeners();
523
+ };
524
+ server.once("error", (error) => {
525
+ cleanup();
526
+ reject(error);
527
+ });
528
+ server.listen(0, host, () => {
529
+ const address = server.address();
530
+ const port = typeof address === "object" && address !== null ? address.port : 0;
531
+ server.close(() => {
532
+ cleanup();
533
+ resolve(port);
534
+ });
535
+ });
536
+ });
537
+ }
538
+
539
+ function classifyDevStartFailure(input: {
540
+ rawMessage: string;
541
+ host: string;
542
+ port: number;
543
+ webPort?: number;
544
+ db: DevCommandOptions["db"];
545
+ }): DevStartFailure {
546
+ const lowerMessage = input.rawMessage.toLowerCase();
547
+ const busy =
548
+ lowerMessage.includes("eaddrinuse") ||
549
+ lowerMessage.includes("address already in use") ||
550
+ /port\s+\d+\s+.*in use/i.test(input.rawMessage);
551
+ if (busy) {
552
+ const suggestedCommands = [
553
+ "forge dev status --json",
554
+ "forge dev stop --json",
555
+ `forge dev --port 0${input.webPort ? ` --web-port ${input.webPort}` : ""} --json`,
556
+ "forge doctor windows --json",
557
+ ];
558
+ return {
559
+ message: `${input.rawMessage}. Port ${input.port} appears busy; stop the existing process or rerun with --port 0 / --port <free-port>.`,
560
+ failureKind: "port_busy",
561
+ nextActions: suggestedCommands,
562
+ busy: {
563
+ port: input.port,
564
+ host: input.host,
565
+ suggestedCommands,
566
+ },
567
+ };
568
+ }
569
+
570
+ if (input.db === "pglite" && isPgliteAbortMessage(input.rawMessage)) {
571
+ return {
572
+ message: `${input.rawMessage}. Local PGlite store may be corrupted or stale at .forge/pglite.`,
573
+ code: FORGE_PGLITE_STORE_ABORTED,
574
+ failureKind: "pglite_store_aborted",
575
+ nextActions: [
576
+ "forge doctor pglite --json",
577
+ "forge db repair --local --adapter pglite --json",
578
+ "forge dev --db memory --json",
579
+ ],
580
+ };
581
+ }
582
+
583
+ return {
584
+ message: input.rawMessage,
585
+ failureKind: "dev_start_failed",
586
+ nextActions: ["forge dev --once --json", "forge check --json"],
587
+ };
588
+ }
589
+
590
+ function writeDevStartFailure(input: {
591
+ workspaceRoot: string;
592
+ startedAt: Date;
593
+ finishedAt: Date;
594
+ options: DevCommandOptions;
595
+ host: string;
596
+ port: number;
597
+ failure: DevStartFailure;
598
+ }): DevCommandResult {
599
+ const nextActions = forgeCliCommandsForWorkspace(input.workspaceRoot, input.failure.nextActions);
600
+ const busy = input.failure.busy
601
+ ? {
602
+ ...input.failure.busy,
603
+ suggestedCommands: forgeCliCommandsForWorkspace(input.workspaceRoot, input.failure.busy.suggestedCommands),
604
+ }
605
+ : undefined;
606
+ writeLastRunRecord(input.workspaceRoot, {
607
+ schemaVersion: "0.1.0",
608
+ command: forgeCliCommandForWorkspace(input.workspaceRoot, "forge dev"),
609
+ ok: false,
610
+ startedAt: input.startedAt.toISOString(),
611
+ finishedAt: input.finishedAt.toISOString(),
612
+ durationMs: input.finishedAt.getTime() - input.startedAt.getTime(),
613
+ ...(input.failure.code ? { code: input.failure.code } : {}),
614
+ failureKind: input.failure.failureKind,
615
+ message: input.failure.message,
616
+ nextActions,
617
+ details: {
618
+ db: input.options.db,
619
+ host: input.host,
620
+ port: input.port,
621
+ },
622
+ });
623
+ if (input.options.json) {
624
+ process.stdout.write(
625
+ `${JSON.stringify({
626
+ ok: false,
627
+ error: input.failure.message,
628
+ code: input.failure.code,
629
+ failureKind: input.failure.failureKind,
630
+ busy,
631
+ nextActions,
632
+ exitCode: 1,
633
+ })}\n`,
634
+ );
635
+ } else {
636
+ console.error(`error${input.failure.code ? ` ${input.failure.code}` : ""}: ${input.failure.message}`);
637
+ if (nextActions.length > 0) {
638
+ console.error("next:");
639
+ for (const action of nextActions) {
640
+ console.error(` ${action}`);
641
+ }
642
+ }
643
+ }
644
+ return { exitCode: 1 };
645
+ }
646
+
647
+ export async function resolveAvailableWebPort(input: {
648
+ host: string;
649
+ preferredPort: number;
650
+ maxAttempts?: number;
651
+ }): Promise<{ port: number; requestedPort?: number; autoPortSelected: boolean }> {
652
+ const attempts = input.maxAttempts ?? 20;
653
+ const start = Math.max(0, Math.floor(input.preferredPort));
654
+ if (start === 0) {
655
+ return {
656
+ port: await reserveEphemeralPort(input.host),
657
+ requestedPort: 0,
658
+ autoPortSelected: true,
659
+ };
660
+ }
661
+
662
+ for (let offset = 0; offset < attempts; offset += 1) {
663
+ const candidate = start + offset;
664
+ if (await isPortAvailable(input.host, candidate)) {
665
+ return {
666
+ port: candidate,
667
+ ...(candidate !== start ? { requestedPort: start } : {}),
668
+ autoPortSelected: candidate !== start,
669
+ };
670
+ }
671
+ }
672
+
673
+ return {
674
+ port: 0,
675
+ requestedPort: start,
676
+ autoPortSelected: true,
677
+ };
678
+ }
679
+
680
+ export function devStartupConsoleUrlOptions(input: {
681
+ host: string;
682
+ port: number;
683
+ webUrl?: string;
684
+ }): { apiUrl?: string; webUrl?: string } {
685
+ return {
686
+ ...(input.port === 0 ? {} : { apiUrl: `http://${input.host}:${input.port}` }),
687
+ ...(input.webUrl ? { webUrl: input.webUrl } : {}),
688
+ };
689
+ }
690
+
691
+ export interface DevWatchGenerateFailureEvent {
692
+ schemaVersion: "0.1.0";
693
+ event: "dev.generate_failed";
694
+ ok: false;
695
+ changedFiles: number;
696
+ changedPaths: string[];
697
+ generated: DevConsoleGeneratedSummary;
698
+ diagnostics: DevGenerateResult["diagnostics"];
699
+ nextActions: string[];
700
+ }
701
+
702
+ export interface DevWatchReloadEvent {
703
+ schemaVersion: "0.1.0";
704
+ event: "dev.reload";
705
+ ok: boolean;
706
+ changedFiles: number;
707
+ changedPaths: string[];
708
+ migrated: boolean;
709
+ routes: number;
710
+ runtimeEntries: number;
711
+ worker: unknown;
712
+ diagnostics: DevGenerateResult["diagnostics"];
713
+ generated: DevConsoleGeneratedSummary;
714
+ preview: DevConsoleSummary["preview"];
715
+ agentContext: DevConsoleAgentContext;
716
+ nextActions: string[];
717
+ }
718
+
102
719
  function readGeneratedJson<T>(workspaceRoot: string, relative: string): T | null {
103
720
  const absolute = join(workspaceRoot, relative);
104
721
  if (!nodeFileSystem.exists(absolute)) {
@@ -175,6 +792,8 @@ function startWebDevServer(input: {
175
792
  workspaceRoot: string;
176
793
  host: string;
177
794
  port: number;
795
+ requestedPort?: number;
796
+ autoPortSelected?: boolean;
178
797
  apiUrl: string;
179
798
  json: boolean;
180
799
  }): WebDevServerHandle | null {
@@ -209,6 +828,8 @@ function startWebDevServer(input: {
209
828
  return {
210
829
  url: `http://${input.host}:${input.port}`,
211
830
  port: input.port,
831
+ ...(input.requestedPort !== undefined ? { requestedPort: input.requestedPort } : {}),
832
+ autoPortSelected: input.autoPortSelected ?? false,
212
833
  command,
213
834
  stop: () => {
214
835
  try {
@@ -220,24 +841,79 @@ function startWebDevServer(input: {
220
841
  };
221
842
  }
222
843
 
223
- function buildStartupSummary(input: {
844
+ function seedReadinessForWorkspace(
845
+ workspaceRoot: string,
846
+ readiness: SeedCommandResult["readiness"],
847
+ ): SeedCommandResult["readiness"] {
848
+ return {
849
+ ...readiness,
850
+ emptyWorkspaceRecovery: forgeCliCommandsForWorkspace(
851
+ workspaceRoot,
852
+ readiness.emptyWorkspaceRecovery,
853
+ ),
854
+ };
855
+ }
856
+
857
+ export function buildStartupSummary(input: {
224
858
  workspaceRoot: string;
225
859
  handle: DevServerHandle;
226
860
  web?: WebDevServerHandle | null;
227
861
  watch: boolean;
862
+ generated?: DevStartupGeneratedEvidence;
863
+ seed?: SeedCommandResult;
864
+ seedRequested?: boolean;
228
865
  }): DevStartupSummary {
229
866
  const frontend = readGeneratedJson<FrontendGraph>(
230
867
  input.workspaceRoot,
231
868
  `${GENERATED_DIR}/frontendGraph.json`,
232
869
  );
870
+ const buildInfo = readGeneratedJson<{ generatorVersion?: string; inputHash?: string }>(
871
+ input.workspaceRoot,
872
+ `${GENERATED_DIR}/buildInfo.json`,
873
+ );
874
+ const buildInfoRaw = nodeFileSystem.exists(join(input.workspaceRoot, `${GENERATED_DIR}/buildInfo.json`))
875
+ ? stripDeterministicHeader(nodeFileSystem.readText(join(input.workspaceRoot, `${GENERATED_DIR}/buildInfo.json`)) ?? "")
876
+ : "";
233
877
  const bindings = frontend
234
878
  ? [...new Set(frontend.clientBindings.map((binding) => `${binding.kind}:${binding.name}`))].sort()
235
879
  : [];
236
880
  const browserUrl = input.web?.url ?? input.handle.url;
881
+ const warnings = input.handle.state.db.kind === "memory"
882
+ ? [
883
+ {
884
+ code: "FORGE_DEV_MEMORY_DB_FIDELITY",
885
+ message:
886
+ "Memory DB is fast and non-persistent; PGlite/Postgres remain the authoritative checks for full SQL constraints and adapter fidelity.",
887
+ nextAction: `rerun with ${forgeCliCommandForWorkspace(input.workspaceRoot, "forge dev --db pglite --once --json")} before treating the result as database-realistic`,
888
+ },
889
+ ]
890
+ : [];
891
+ if (input.seed && input.seed.subcommand !== "status" && input.seed.ok === false) {
892
+ warnings.push({
893
+ code: input.seed.diagnostics[0]?.code ?? "FORGE_SEED_FAILED",
894
+ message: input.seed.diagnostics[0]?.message ?? "Seed command failed.",
895
+ nextAction: forgeCliCommandForWorkspace(input.workspaceRoot, "forge seed status --json"),
896
+ });
897
+ }
898
+ for (const diagnostic of input.seed?.diagnostics.filter((item) => item.severity === "warning") ?? []) {
899
+ warnings.push({
900
+ code: diagnostic.code,
901
+ message: diagnostic.message,
902
+ nextAction: diagnostic.suggestedCommands?.[0]
903
+ ? forgeCliCommandForWorkspace(input.workspaceRoot, diagnostic.suggestedCommands[0])
904
+ : forgeCliCommandForWorkspace(input.workspaceRoot, "forge seed status --json"),
905
+ });
906
+ }
907
+ const preview = previewSummaryFor({
908
+ workspaceRoot: input.workspaceRoot,
909
+ host: input.handle.host,
910
+ ...(input.web?.url ? { webUrl: input.web.url } : {}),
911
+ });
237
912
  return {
238
913
  schemaVersion: "0.1.0",
239
914
  ok: true,
240
915
  mode: "dev",
916
+ warnings,
241
917
  api: {
242
918
  url: input.handle.url,
243
919
  host: input.handle.host,
@@ -249,6 +925,8 @@ function buildStartupSummary(input: {
249
925
  ? {
250
926
  url: input.web.url,
251
927
  port: input.web.port,
928
+ ...(input.web.requestedPort !== undefined ? { requestedPort: input.web.requestedPort } : {}),
929
+ autoPortSelected: input.web.autoPortSelected,
252
930
  command: input.web.command,
253
931
  framework: frontend?.framework ?? "unknown",
254
932
  routes: frontend?.routes.map((route) => route.path) ?? [],
@@ -256,8 +934,11 @@ function buildStartupSummary(input: {
256
934
  ...(frontend?.dev?.apiUrlEnv ? { apiUrlEnv: frontend.dev.apiUrlEnv } : {}),
257
935
  }
258
936
  : null,
937
+ preview,
259
938
  watch: {
260
939
  enabled: input.watch,
940
+ autoGenerate: input.watch,
941
+ reloadsRuntime: input.watch,
261
942
  },
262
943
  runtime: {
263
944
  routes: input.handle.routes.map((route) => ({
@@ -274,11 +955,48 @@ function buildStartupSummary(input: {
274
955
  bridgeFiles: frontend?.bridgeFiles ?? [],
275
956
  diagnostics: frontend?.diagnostics.length ?? 0,
276
957
  },
958
+ generated: {
959
+ state: input.generated?.state ?? (buildInfoRaw ? "fresh" : "stale-risk"),
960
+ ...(buildInfo?.generatorVersion ? { generatorVersion: buildInfo.generatorVersion } : {}),
961
+ ...(buildInfo?.inputHash ? { inputHash: buildInfo.inputHash } : {}),
962
+ ...(buildInfoRaw ? { buildInfoHash: hashStable(buildInfoRaw) } : {}),
963
+ ...(buildInfoRaw ? { runtimeLoadedHash: hashStable(buildInfoRaw) } : {}),
964
+ runtimeStaleRisk: !buildInfoRaw || input.generated?.state === "stale-risk",
965
+ changedFiles: input.generated?.changedFiles ?? 0,
966
+ sampleChanged: input.generated?.sampleChanged ?? [],
967
+ hiddenChanged: input.generated?.hiddenChanged ?? 0,
968
+ command: input.generated?.command ?? forgeCliCommandForWorkspace(input.workspaceRoot, "forge generate"),
969
+ checkCommand: input.generated?.checkCommand ?? forgeCliCommandForWorkspace(input.workspaceRoot, "forge generate --check --json"),
970
+ message: input.generated?.message ?? (buildInfoRaw ? "generated artifacts are loaded" : "generated build info is missing"),
971
+ },
972
+ seed: {
973
+ requested: input.seedRequested === true,
974
+ available: (input.seed?.commands.length ?? 0) > 0,
975
+ commands: input.seed?.commands.map((command) => command.name) ?? [],
976
+ ...(input.seed?.selectedCommand ? { selectedCommand: input.seed.selectedCommand } : {}),
977
+ ...(input.seed?.readiness ? { readiness: seedReadinessForWorkspace(input.workspaceRoot, input.seed.readiness) } : {}),
978
+ ran: input.seedRequested === true && input.seed?.subcommand !== "status",
979
+ ...(input.seed && input.seed.subcommand !== "status" ? { ok: input.seed.ok } : {}),
980
+ ...(input.seed?.response ? { responseStatus: input.seed.response.status } : {}),
981
+ ...(input.seed?.tenantRuns ? {
982
+ tenantRuns: input.seed.tenantRuns.map((run) => ({
983
+ tenantId: run.tenantId,
984
+ ...(run.label ? { label: run.label } : {}),
985
+ ...(run.organizationName ? { organizationName: run.organizationName } : {}),
986
+ ok: run.ok,
987
+ ...(run.response ? { responseStatus: run.response.status } : {}),
988
+ })),
989
+ } : {}),
990
+ ...(!input.seed?.ok && input.seed?.diagnostics[0]?.message ? { error: input.seed.diagnostics[0].message } : {}),
991
+ nextActions: input.seed?.nextActions.map((action) => forgeCliCommandForWorkspace(input.workspaceRoot, action)) ??
992
+ [forgeCliCommandForWorkspace(input.workspaceRoot, "forge seed status --json")],
993
+ },
277
994
  next: {
278
995
  browserUrl,
279
996
  apiIndex: input.handle.url,
280
- inspect: "forge inspect all --json",
281
- verify: "forge dev --once --json",
997
+ inspect: forgeCliCommandForWorkspace(input.workspaceRoot, "forge inspect summary --json"),
998
+ verify: forgeCliCommandForWorkspace(input.workspaceRoot, "forge dev --once --json"),
999
+ changed: forgeCliCommandForWorkspace(input.workspaceRoot, "forge changed --json"),
282
1000
  },
283
1001
  pid: process.pid,
284
1002
  };
@@ -288,18 +1006,65 @@ function printStartupJson(summary: DevStartupSummary): void {
288
1006
  process.stdout.write(`${JSON.stringify(summary)}\n`);
289
1007
  }
290
1008
 
291
- function printStartupHuman(summary: DevStartupSummary): void {
1009
+ export function formatStartupHuman(summary: DevStartupSummary): string {
292
1010
  const lines = ["Forge Dev", ""];
293
1011
  lines.push("API runtime");
294
1012
  lines.push(` URL: ${summary.api.url}`);
295
1013
  lines.push(` DB: ${summary.api.db.kind} ${summary.api.db.connected ? "connected" : "not connected"}`);
296
1014
  lines.push(` Worker: ${summary.api.worker}`);
297
1015
  lines.push(` Watch: ${summary.watch.enabled ? "on" : "off"}`);
1016
+ lines.push(` Auto-generate: ${summary.watch.autoGenerate ? "on" : "off"}`);
1017
+ lines.push(` Generated: ${summary.generated.state}${summary.generated.changedFiles > 0 ? ` (${summary.generated.changedFiles} changed)` : ""}${summary.generated.runtimeStaleRisk ? " (stale risk)" : ""}`);
1018
+ lines.push(` Generated note: ${summary.generated.message}`);
1019
+ lines.push(` Generated check: ${summary.generated.checkCommand}`);
1020
+ if (summary.seed.available || summary.seed.requested) {
1021
+ lines.push(` Seed: ${summary.seed.available ? summary.seed.commands.join(", ") : "none detected"}`);
1022
+ if (summary.seed.readiness) {
1023
+ const autoSeedNote = summary.seed.readiness.autoSeedMode !== "none"
1024
+ ? summary.seed.readiness.autoSeedMode === "all-tenants"
1025
+ ? " (npm run dev auto-seeds all local tenants)"
1026
+ : " (npm run dev auto-seeds)"
1027
+ : "";
1028
+ lines.push(` Seed readiness: ${summary.seed.readiness.ready ? "ready" : summary.seed.readiness.reason}${autoSeedNote}`);
1029
+ if (summary.seed.readiness.emptyWorkspaceRecovery.length > 0) {
1030
+ lines.push(" Seed recovery:");
1031
+ for (const action of summary.seed.readiness.emptyWorkspaceRecovery) {
1032
+ lines.push(` - ${action}`);
1033
+ }
1034
+ }
1035
+ }
1036
+ if (summary.seed.requested) {
1037
+ lines.push(` Seed run: ${summary.seed.ok ? "ok" : "failed"}${summary.seed.selectedCommand ? ` (${summary.seed.selectedCommand})` : ""}${summary.seed.responseStatus ? ` HTTP ${summary.seed.responseStatus}` : ""}`);
1038
+ if (summary.seed.tenantRuns && summary.seed.tenantRuns.length > 0) {
1039
+ const okCount = summary.seed.tenantRuns.filter((run) => run.ok).length;
1040
+ lines.push(` Seed tenants: ${okCount}/${summary.seed.tenantRuns.length} ok`);
1041
+ for (const run of summary.seed.tenantRuns) {
1042
+ const label = run.organizationName ?? run.label ?? run.tenantId;
1043
+ lines.push(` - ${run.ok ? "ok" : "failed"} ${label} (${run.tenantId})${run.responseStatus ? ` HTTP ${run.responseStatus}` : ""}`);
1044
+ }
1045
+ }
1046
+ if (summary.seed.error) {
1047
+ lines.push(` Seed error: ${summary.seed.error}`);
1048
+ }
1049
+ } else if (summary.seed.available) {
1050
+ lines.push(` Seed next: ${summary.seed.nextActions[0] ?? "forge seed dev --json"}`);
1051
+ }
1052
+ }
1053
+ for (const warning of summary.warnings) {
1054
+ lines.push(` Warning ${warning.code}: ${warning.message}`);
1055
+ if (warning.nextAction) {
1056
+ lines.push(` Next: ${warning.nextAction}`);
1057
+ }
1058
+ }
298
1059
  lines.push("");
299
1060
 
300
1061
  lines.push("Web app");
301
1062
  if (summary.web) {
302
1063
  lines.push(` URL: ${summary.web.url}`);
1064
+ if (summary.web.autoPortSelected && summary.web.requestedPort !== undefined) {
1065
+ const reason = summary.web.requestedPort === 0 ? "ephemeral" : "busy";
1066
+ lines.push(` Requested port: ${summary.web.requestedPort} (${reason}; selected ${summary.web.port})`);
1067
+ }
303
1068
  lines.push(` Framework: ${summary.web.framework}`);
304
1069
  lines.push(` API env: ${summary.web.apiUrlEnv ?? "unknown"}=${summary.api.url}`);
305
1070
  lines.push(` Bridge: ${summary.web.bridgeFiles.length > 0 ? summary.web.bridgeFiles.join(", ") : "missing"}`);
@@ -325,9 +1090,22 @@ function printStartupHuman(summary: DevStartupSummary): void {
325
1090
  lines.push("Agent checks");
326
1091
  lines.push(` ${summary.next.verify}`);
327
1092
  lines.push(` ${summary.next.inspect}`);
1093
+ lines.push(` ${summary.next.changed}`);
1094
+ lines.push("");
1095
+
1096
+ lines.push("Preview");
1097
+ lines.push(` Target app: ${summary.preview.targetAppUrl}`);
1098
+ if (summary.preview.studioUrl) {
1099
+ lines.push(` Studio: ${summary.preview.studioUrl}`);
1100
+ }
1101
+ lines.push(` Note: ${summary.preview.note}`);
328
1102
  lines.push("");
329
1103
 
330
- process.stdout.write(`${lines.join("\n")}\n`);
1104
+ return `${lines.join("\n")}\n`;
1105
+ }
1106
+
1107
+ function printStartupHuman(summary: DevStartupSummary): void {
1108
+ process.stdout.write(formatStartupHuman(summary));
331
1109
  }
332
1110
 
333
1111
  function openBrowser(url: string): void {
@@ -350,10 +1128,152 @@ function openBrowser(url: string): void {
350
1128
  }
351
1129
  }
352
1130
 
1131
+ export async function ensureGeneratedForDev(workspaceRoot: string): Promise<DevGenerateResult> {
1132
+ resetCompileSessions();
1133
+ const result = await run({
1134
+ workspaceRoot,
1135
+ check: false,
1136
+ dryRun: false,
1137
+ json: false,
1138
+ concurrency: 4,
1139
+ });
1140
+ return {
1141
+ ok: result.exitCode === 0,
1142
+ changed: result.changed,
1143
+ unchanged: result.unchanged,
1144
+ diagnostics: [...result.errors, ...result.warnings],
1145
+ exitCode: result.exitCode,
1146
+ };
1147
+ }
1148
+
1149
+ export function generatedEvidenceFromCycle(cycle: DevConsoleCycle): DevStartupGeneratedEvidence {
1150
+ const phase = cycle.phases.find((item) => item.name === "generated");
1151
+ const changedFiles = Number(phase?.details?.changed ?? 0);
1152
+ const sampleChanged = Array.isArray(phase?.details?.sampleChanged)
1153
+ ? phase.details.sampleChanged.filter((item): item is string => typeof item === "string")
1154
+ : [];
1155
+ const hiddenChanged = Number(phase?.details?.hiddenChanged ?? 0);
1156
+ return {
1157
+ ok: phase?.ok === true,
1158
+ state: phase?.ok === true
1159
+ ? changedFiles > 0 ? "regenerated" : "fresh"
1160
+ : "stale-risk",
1161
+ changedFiles,
1162
+ sampleChanged,
1163
+ hiddenChanged,
1164
+ message: phase?.message ?? "generated phase did not report a message",
1165
+ command: cycle.summary.generated.command,
1166
+ checkCommand: cycle.summary.generated.checkCommand,
1167
+ };
1168
+ }
1169
+
1170
+ export function generatedEvidenceFromGenerateResult(
1171
+ result: DevGenerateResult,
1172
+ options: { workspaceRoot?: string } = {},
1173
+ ): DevStartupGeneratedEvidence {
1174
+ return {
1175
+ ok: result.ok,
1176
+ state: result.ok ? result.changed.length > 0 ? "regenerated" : "fresh" : "stale-risk",
1177
+ changedFiles: result.changed.length,
1178
+ sampleChanged: result.changed.slice(0, 12),
1179
+ hiddenChanged: Math.max(0, result.changed.length - 12),
1180
+ message: result.ok
1181
+ ? result.changed.length > 0
1182
+ ? `regenerated ${result.changed.length} generated artifacts`
1183
+ : "generated artifacts are up to date"
1184
+ : "generated artifacts could not be regenerated",
1185
+ command: options.workspaceRoot
1186
+ ? forgeCliCommandForWorkspace(options.workspaceRoot, "forge generate")
1187
+ : "forge generate",
1188
+ checkCommand: options.workspaceRoot
1189
+ ? forgeCliCommandForWorkspace(options.workspaceRoot, "forge generate --check --json")
1190
+ : "forge generate --check --json",
1191
+ };
1192
+ }
1193
+
1194
+ function printDevGenerateFailure(result: DevGenerateResult, json: boolean): void {
1195
+ if (json) {
1196
+ process.stdout.write(`${JSON.stringify({
1197
+ ok: false,
1198
+ phase: "generated",
1199
+ diagnostics: result.diagnostics,
1200
+ exitCode: 1,
1201
+ })}\n`);
1202
+ return;
1203
+ }
1204
+ console.error("error: forge dev could not regenerate generated artifacts");
1205
+ for (const diagnostic of result.diagnostics.filter((item) => item.severity === "error").slice(0, 5)) {
1206
+ console.error(`error ${diagnostic.code}: ${diagnostic.message}`);
1207
+ }
1208
+ }
1209
+
1210
+ export function buildDevWatchGenerateFailureEvent(input: {
1211
+ changedCount: number;
1212
+ changedPaths: string[];
1213
+ result: DevGenerateResult;
1214
+ workspaceRoot?: string;
1215
+ }): DevWatchGenerateFailureEvent {
1216
+ const nextActions = input.workspaceRoot
1217
+ ? forgeCliCommandsForWorkspace(input.workspaceRoot, ["forge dev --once --json", "forge check --json"])
1218
+ : ["forge dev --once --json", "forge check --json"];
1219
+ return {
1220
+ schemaVersion: "0.1.0",
1221
+ event: "dev.generate_failed",
1222
+ ok: false,
1223
+ changedFiles: input.changedCount,
1224
+ changedPaths: input.changedPaths,
1225
+ generated: generatedEvidenceFromGenerateResult(input.result, { workspaceRoot: input.workspaceRoot }),
1226
+ diagnostics: input.result.diagnostics,
1227
+ nextActions,
1228
+ };
1229
+ }
1230
+
1231
+ export function buildDevWatchReloadEvent(input: {
1232
+ changedCount: number;
1233
+ changedPaths: string[];
1234
+ generated: DevGenerateResult;
1235
+ reload: Awaited<ReturnType<DevServerHandle["reload"]>>;
1236
+ cycle: DevConsoleCycle;
1237
+ workspaceRoot?: string;
1238
+ }): DevWatchReloadEvent {
1239
+ const failureNextActions = input.workspaceRoot
1240
+ ? forgeCliCommandsForWorkspace(input.workspaceRoot, ["forge dev --once --json", "forge check --json"])
1241
+ : ["forge dev --once --json", "forge check --json"];
1242
+ return {
1243
+ schemaVersion: "0.1.0",
1244
+ event: "dev.reload",
1245
+ ok: input.reload.ok,
1246
+ changedFiles: input.changedCount,
1247
+ changedPaths: input.changedPaths,
1248
+ migrated: input.reload.migrated,
1249
+ routes: input.reload.routes,
1250
+ runtimeEntries: input.reload.runtimeEntries,
1251
+ worker: input.reload.worker,
1252
+ diagnostics: input.reload.diagnostics,
1253
+ generated: generatedEvidenceFromGenerateResult(input.generated, { workspaceRoot: input.workspaceRoot }),
1254
+ preview: input.cycle.summary.preview,
1255
+ agentContext: input.cycle.summary.agentContext,
1256
+ nextActions: input.reload.ok
1257
+ ? input.cycle.summary.agentContext.recommendedCommands
1258
+ : failureNextActions,
1259
+ };
1260
+ }
1261
+
353
1262
  export async function runDevCommand(
354
1263
  options: DevCommandOptions,
355
1264
  ): Promise<DevCommandResult> {
356
1265
  const workspaceRoot = options.workspaceRoot.replace(/\\/g, "/");
1266
+ const startedAt = new Date();
1267
+
1268
+ if (options.lifecycle === "status") {
1269
+ return runDevStatus({ ...options, workspaceRoot });
1270
+ }
1271
+ if (options.lifecycle === "stop") {
1272
+ return runDevStop({ ...options, workspaceRoot });
1273
+ }
1274
+ if (options.detach) {
1275
+ return runDevDetach({ ...options, workspaceRoot });
1276
+ }
357
1277
 
358
1278
  if (options.once) {
359
1279
  const cycle = await runDevConsoleCycle({
@@ -368,31 +1288,71 @@ export async function runDevCommand(
368
1288
 
369
1289
  const host = resolveDevHost(options.host);
370
1290
  const port = resolveDevPort(options.port);
371
- const webPort = options.webPort ?? detectDefaultWebPort(workspaceRoot);
1291
+ if (!options.webOnly && !(await isPortAvailable(host, port))) {
1292
+ const failure = classifyDevStartFailure({
1293
+ rawMessage: `listen EADDRINUSE: address already in use ${host}:${port}`,
1294
+ host,
1295
+ port,
1296
+ webPort: options.webPort,
1297
+ db: options.db,
1298
+ });
1299
+ return writeDevStartFailure({
1300
+ workspaceRoot,
1301
+ startedAt,
1302
+ finishedAt: new Date(),
1303
+ options,
1304
+ host,
1305
+ port,
1306
+ failure,
1307
+ });
1308
+ }
1309
+ const requestedWebPort = options.webPort ?? detectDefaultWebPort(workspaceRoot);
372
1310
  const shouldStartWeb =
373
1311
  options.webOnly === true ||
374
1312
  (options.withWeb !== false && !options.apiOnly && hasWebApp(workspaceRoot));
1313
+ const webPortSelection = shouldStartWeb
1314
+ ? await resolveAvailableWebPort({ host, preferredPort: requestedWebPort })
1315
+ : { port: requestedWebPort, autoPortSelected: false };
1316
+ const webPort = webPortSelection.port;
375
1317
  const webUrl = shouldStartWeb ? `http://${host}:${webPort}` : undefined;
1318
+ let startupGenerated: DevStartupGeneratedEvidence | undefined;
376
1319
 
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));
1320
+ if (!options.skipStartupConsole) {
1321
+ const startupCycle = await runDevConsoleCycle({
1322
+ workspaceRoot,
1323
+ mode: "startup",
1324
+ strictSecrets: false,
1325
+ includeImpact: true,
1326
+ ...devStartupConsoleUrlOptions({ host, port, ...(webUrl ? { webUrl } : {}) }),
1327
+ });
1328
+ if (options.json) {
1329
+ process.stdout.write(formatDevConsoleJson(startupCycle));
1330
+ } else {
1331
+ process.stdout.write(formatDevConsoleHuman(startupCycle));
1332
+ }
1333
+ startupGenerated = generatedEvidenceFromCycle(startupCycle);
1334
+ if (startupCycle.exitCode !== 0) {
1335
+ return { exitCode: 1 };
1336
+ }
385
1337
  } else {
386
- process.stdout.write(formatDevConsoleHuman(startupCycle));
1338
+ const generated = await ensureGeneratedForDev(workspaceRoot);
1339
+ startupGenerated = generatedEvidenceFromGenerateResult(generated);
1340
+ if (!generated.ok) {
1341
+ printDevGenerateFailure(generated, options.json);
1342
+ return { exitCode: 1 };
1343
+ }
387
1344
  }
388
1345
 
389
1346
  if (options.webOnly) {
390
1347
  const apiUrl = `http://${host}:${port}`;
1348
+ const publicApiUrl = options.publicApiUrl ?? apiUrl;
391
1349
  const webHandle = startWebDevServer({
392
1350
  workspaceRoot,
393
1351
  host,
394
1352
  port: webPort,
395
- apiUrl,
1353
+ requestedPort: webPortSelection.requestedPort,
1354
+ autoPortSelected: webPortSelection.autoPortSelected,
1355
+ apiUrl: publicApiUrl,
396
1356
  json: options.json,
397
1357
  });
398
1358
  if (!webHandle) {
@@ -413,6 +1373,8 @@ export async function runDevCommand(
413
1373
  web: {
414
1374
  url: webHandle.url,
415
1375
  port: webHandle.port,
1376
+ ...(webHandle.requestedPort !== undefined ? { requestedPort: webHandle.requestedPort } : {}),
1377
+ autoPortSelected: webHandle.autoPortSelected,
416
1378
  command: webHandle.command,
417
1379
  },
418
1380
  api: {
@@ -440,6 +1402,8 @@ export async function runDevCommand(
440
1402
  return { web: webHandle, exitCode: 0 };
441
1403
  }
442
1404
 
1405
+ const deltaRecorder = await createAmbientDeltaRecorder(workspaceRoot, "forge-dev", "forge dev");
1406
+
443
1407
  let handle: DevServerHandle;
444
1408
  try {
445
1409
  handle = await startDevServer({
@@ -457,18 +1421,29 @@ export async function runDevCommand(
457
1421
  mode: options.mode,
458
1422
  allowDevAuth: options.allowDevAuth,
459
1423
  webUrl,
1424
+ deltaRecorder,
460
1425
  });
461
1426
  } catch (error) {
462
- const message =
1427
+ await deltaRecorder.close("forge dev failed to start");
1428
+ const finishedAt = new Date();
1429
+ const rawMessage =
463
1430
  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 };
1431
+ const failure = classifyDevStartFailure({
1432
+ rawMessage,
1433
+ host,
1434
+ port,
1435
+ webPort: options.webPort,
1436
+ db: options.db,
1437
+ });
1438
+ return writeDevStartFailure({
1439
+ workspaceRoot,
1440
+ startedAt,
1441
+ finishedAt,
1442
+ options,
1443
+ host,
1444
+ port,
1445
+ failure,
1446
+ });
472
1447
  }
473
1448
 
474
1449
  const webHandle = !shouldStartWeb
@@ -477,15 +1452,40 @@ export async function runDevCommand(
477
1452
  workspaceRoot,
478
1453
  host,
479
1454
  port: webPort,
480
- apiUrl: handle.url,
1455
+ requestedPort: webPortSelection.requestedPort,
1456
+ autoPortSelected: webPortSelection.autoPortSelected,
1457
+ apiUrl: options.publicApiUrl ?? handle.url,
481
1458
  json: options.json,
482
1459
  });
483
1460
 
1461
+ const seedStatus = await runSeedCommand({
1462
+ subcommand: "status",
1463
+ command: options.seedCommand,
1464
+ args: {},
1465
+ url: handle.url,
1466
+ json: true,
1467
+ workspaceRoot,
1468
+ });
1469
+ const seedResult = options.seed
1470
+ ? await runSeedCommand({
1471
+ subcommand: "dev",
1472
+ command: options.seedCommand ?? seedStatus.selectedCommand,
1473
+ args: {},
1474
+ url: handle.url,
1475
+ allTenants: options.seedAllTenants,
1476
+ json: true,
1477
+ workspaceRoot,
1478
+ })
1479
+ : seedStatus;
1480
+
484
1481
  const startupSummary = buildStartupSummary({
485
1482
  workspaceRoot,
486
1483
  handle,
487
1484
  web: webHandle,
488
1485
  watch: options.watch,
1486
+ generated: startupGenerated,
1487
+ seed: seedResult,
1488
+ seedRequested: options.seed,
489
1489
  });
490
1490
  if (options.json) {
491
1491
  printStartupJson(startupSummary);
@@ -496,6 +1496,29 @@ export async function runDevCommand(
496
1496
  openBrowser(webHandle?.url ?? handle.url);
497
1497
  }
498
1498
 
1499
+ {
1500
+ const finishedAt = new Date();
1501
+ writeLastRunRecord(workspaceRoot, {
1502
+ schemaVersion: "0.1.0",
1503
+ command: "forge dev",
1504
+ ok: true,
1505
+ startedAt: startedAt.toISOString(),
1506
+ finishedAt: finishedAt.toISOString(),
1507
+ durationMs: finishedAt.getTime() - startedAt.getTime(),
1508
+ nextActions: [
1509
+ webHandle?.url ?? handle.url,
1510
+ forgeCliCommandForWorkspace(workspaceRoot, "forge last --json"),
1511
+ ],
1512
+ details: {
1513
+ db: handle.state.db.kind,
1514
+ apiUrl: handle.url,
1515
+ webUrl: webHandle?.url,
1516
+ watch: options.watch,
1517
+ worker: options.worker,
1518
+ },
1519
+ });
1520
+ }
1521
+
499
1522
  let watchHandle: { stop: () => void } | null = null;
500
1523
  let outboxWorkerHandle: { stop: () => void } | null = null;
501
1524
 
@@ -503,8 +1526,17 @@ export async function runDevCommand(
503
1526
  outboxWorkerHandle = handle.outboxWorker;
504
1527
  }
505
1528
 
1529
+ const devConsoleUrlOptions = (): { apiUrl: string; webUrl?: string } => ({
1530
+ apiUrl: handle.url,
1531
+ ...(webHandle?.url ? { webUrl: webHandle.url } : {}),
1532
+ });
1533
+
506
1534
  if (options.watch) {
507
- watchHandle = startDevWatch(workspaceRoot, async (changedCount) => {
1535
+ watchHandle = startDevWatch(workspaceRoot, async (changedCount, changedPaths) => {
1536
+ for (const changedPath of changedPaths) {
1537
+ await deltaRecorder.recordFileChanged(changedPath);
1538
+ }
1539
+ resetCompileSessions();
508
1540
  const result = await run({
509
1541
  workspaceRoot,
510
1542
  check: false,
@@ -514,39 +1546,91 @@ export async function runDevCommand(
514
1546
  });
515
1547
 
516
1548
  if (result.exitCode === 0) {
1549
+ const reload = await handle.reload("watch");
1550
+ const cycle = await runDevConsoleCycle({
1551
+ workspaceRoot,
1552
+ mode: "watch",
1553
+ strictSecrets: false,
1554
+ includeImpact: true,
1555
+ ...devConsoleUrlOptions(),
1556
+ });
517
1557
  if (!options.json) {
518
1558
  process.stdout.write(
519
- `[forge dev] regenerated (${changedCount} changed files)\n`,
1559
+ `[forge dev] regenerated (${changedCount} changed files), ` +
1560
+ `migrations ${reload.migrated ? "applied" : "skipped"}, ` +
1561
+ `runtime ${reload.ok ? "reloaded" : "reload failed"}\n`,
520
1562
  );
1563
+ } else {
1564
+ process.stdout.write(`${JSON.stringify(buildDevWatchReloadEvent({
1565
+ changedCount,
1566
+ changedPaths,
1567
+ generated: {
1568
+ ok: true,
1569
+ changed: result.changed,
1570
+ unchanged: result.unchanged,
1571
+ diagnostics: [...result.errors, ...result.warnings],
1572
+ exitCode: 0,
1573
+ },
1574
+ reload,
1575
+ cycle,
1576
+ workspaceRoot,
1577
+ }))}\n`);
521
1578
  }
522
- } else if (!options.json) {
523
- for (const diagnostic of result.errors) {
524
- console.error(`error ${diagnostic.code}: ${diagnostic.message}`);
1579
+ if (!reload.ok && !options.json) {
1580
+ for (const diagnostic of reload.diagnostics.filter((item) => item.severity === "error")) {
1581
+ console.error(`error ${diagnostic.code}: ${diagnostic.message}`);
1582
+ }
1583
+ }
1584
+ if (options.json) {
1585
+ process.stdout.write(formatDevConsoleJson(cycle));
1586
+ } else {
1587
+ process.stdout.write(formatDevConsoleHuman(cycle));
525
1588
  }
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
1589
  } else {
537
- process.stdout.write(formatDevConsoleHuman(cycle));
1590
+ if (options.json) {
1591
+ process.stdout.write(`${JSON.stringify(buildDevWatchGenerateFailureEvent({
1592
+ changedCount,
1593
+ changedPaths,
1594
+ result: {
1595
+ ok: false,
1596
+ changed: result.changed,
1597
+ unchanged: result.unchanged,
1598
+ diagnostics: [...result.errors, ...result.warnings],
1599
+ exitCode: 1,
1600
+ },
1601
+ workspaceRoot,
1602
+ }))}\n`);
1603
+ } else {
1604
+ console.error(`[forge dev] regeneration failed after ${changedCount} changed files; runtime was not reloaded`);
1605
+ for (const diagnostic of [...result.errors, ...result.warnings]) {
1606
+ console.error(`${diagnostic.severity} ${diagnostic.code}: ${diagnostic.message}`);
1607
+ }
1608
+ }
1609
+ const cycle = await runDevConsoleCycle({
1610
+ workspaceRoot,
1611
+ mode: "watch",
1612
+ strictSecrets: false,
1613
+ includeImpact: true,
1614
+ ...devConsoleUrlOptions(),
1615
+ });
1616
+ if (options.json) {
1617
+ process.stdout.write(formatDevConsoleJson(cycle));
1618
+ } else {
1619
+ process.stdout.write(formatDevConsoleHuman(cycle));
1620
+ }
538
1621
  }
539
1622
  });
540
1623
  }
541
1624
 
542
1625
  await new Promise<void>((resolve) => {
543
- const shutdown = () => {
544
- watchHandle?.stop();
545
- outboxWorkerHandle?.stop();
546
- webHandle?.stop();
547
- handle.stop();
548
- resolve();
549
- };
1626
+ const shutdown = () => {
1627
+ watchHandle?.stop();
1628
+ outboxWorkerHandle?.stop();
1629
+ webHandle?.stop();
1630
+ handle.stop();
1631
+ void deltaRecorder.close("forge dev stopped");
1632
+ resolve();
1633
+ };
550
1634
 
551
1635
  process.once("SIGINT", shutdown);
552
1636
  process.once("SIGTERM", shutdown);