forgeos 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (540) hide show
  1. package/.npmignore +1 -0
  2. package/AGENTS.md +277 -0
  3. package/CHANGELOG.md +8 -0
  4. package/CONTRIBUTING.md +58 -0
  5. package/README.md +377 -0
  6. package/bin/forge-bun.mjs +110 -0
  7. package/bin/forge.mjs +19 -0
  8. package/package.json +96 -0
  9. package/packages/eslint-plugin-forge/index.ts +15 -0
  10. package/packages/eslint-plugin-forge/package.json +10 -0
  11. package/packages/eslint-plugin-forge/src/check-source.ts +95 -0
  12. package/packages/eslint-plugin-forge/src/load-artifacts.ts +24 -0
  13. package/packages/eslint-plugin-forge/src/rule-no-forge-guard-violation.ts +93 -0
  14. package/src/forge/_generated/actionSubscriptions.json +2 -0
  15. package/src/forge/_generated/actionSubscriptions.ts +10 -0
  16. package/src/forge/_generated/agentAdapterManifest.json +2 -0
  17. package/src/forge/_generated/agentAdapterManifest.ts +73 -0
  18. package/src/forge/_generated/agentContract.json +2 -0
  19. package/src/forge/_generated/agentContract.ts +912 -0
  20. package/src/forge/_generated/agentQuickstart.md +32 -0
  21. package/src/forge/_generated/aiContext.ts +59 -0
  22. package/src/forge/_generated/aiModels.json +2 -0
  23. package/src/forge/_generated/aiModels.ts +35 -0
  24. package/src/forge/_generated/aiProviders.json +2 -0
  25. package/src/forge/_generated/aiProviders.ts +23 -0
  26. package/src/forge/_generated/aiRegistry.json +2 -0
  27. package/src/forge/_generated/aiRegistry.ts +29 -0
  28. package/src/forge/_generated/api.json +2 -0
  29. package/src/forge/_generated/api.ts +8 -0
  30. package/src/forge/_generated/appGraph.json +2 -0
  31. package/src/forge/_generated/appGraph.ts +14511 -0
  32. package/src/forge/_generated/appMap.md +35 -0
  33. package/src/forge/_generated/artifactManifest.json +2 -0
  34. package/src/forge/_generated/artifactManifest.ts +7 -0
  35. package/src/forge/_generated/authClaims.json +2 -0
  36. package/src/forge/_generated/authClaims.ts +13 -0
  37. package/src/forge/_generated/authConfig.json +2 -0
  38. package/src/forge/_generated/authConfig.ts +17 -0
  39. package/src/forge/_generated/authContext.ts +23 -0
  40. package/src/forge/_generated/authRegistry.json +2 -0
  41. package/src/forge/_generated/authRegistry.ts +25 -0
  42. package/src/forge/_generated/buildInfo.json +2 -0
  43. package/src/forge/_generated/buildInfo.ts +9 -0
  44. package/src/forge/_generated/capabilityMap.json +2 -0
  45. package/src/forge/_generated/capabilityMap.md +15 -0
  46. package/src/forge/_generated/capabilityMap.ts +17 -0
  47. package/src/forge/_generated/client.ts +282 -0
  48. package/src/forge/_generated/clientApi.ts +9 -0
  49. package/src/forge/_generated/clientManifest.json +2 -0
  50. package/src/forge/_generated/clientManifest.ts +39 -0
  51. package/src/forge/_generated/clientTypes.ts +78 -0
  52. package/src/forge/_generated/configRegistry.json +2 -0
  53. package/src/forge/_generated/configRegistry.ts +4 -0
  54. package/src/forge/_generated/dataGraph.json +2 -0
  55. package/src/forge/_generated/dataGraph.ts +8 -0
  56. package/src/forge/_generated/db.json +2 -0
  57. package/src/forge/_generated/db.ts +2 -0
  58. package/src/forge/_generated/dbSecurityManifest.json +2 -0
  59. package/src/forge/_generated/dbSecurityManifest.ts +15 -0
  60. package/src/forge/_generated/dbSessionContext.json +2 -0
  61. package/src/forge/_generated/dbSessionContext.ts +39 -0
  62. package/src/forge/_generated/deployManifest.json +2 -0
  63. package/src/forge/_generated/deployManifest.ts +14 -0
  64. package/src/forge/_generated/devManifest.json +2 -0
  65. package/src/forge/_generated/devManifest.ts +47 -0
  66. package/src/forge/_generated/envSchema.json +2 -0
  67. package/src/forge/_generated/envSchema.ts +59 -0
  68. package/src/forge/_generated/frontendGraph.json +2 -0
  69. package/src/forge/_generated/frontendGraph.ts +27 -0
  70. package/src/forge/_generated/importGuards.json +2 -0
  71. package/src/forge/_generated/importGuards.ts +652 -0
  72. package/src/forge/_generated/index.ts +67 -0
  73. package/src/forge/_generated/liveProductionManifest.json +2 -0
  74. package/src/forge/_generated/liveProductionManifest.ts +23 -0
  75. package/src/forge/_generated/liveProtocol.json +2 -0
  76. package/src/forge/_generated/liveProtocol.ts +21 -0
  77. package/src/forge/_generated/liveQueryRegistry.json +2 -0
  78. package/src/forge/_generated/liveQueryRegistry.ts +9 -0
  79. package/src/forge/_generated/liveTransportConfig.json +2 -0
  80. package/src/forge/_generated/liveTransportConfig.ts +19 -0
  81. package/src/forge/_generated/makeRegistry.json +2 -0
  82. package/src/forge/_generated/makeRegistry.ts +163 -0
  83. package/src/forge/_generated/makeTemplates.json +2 -0
  84. package/src/forge/_generated/makeTemplates.ts +61 -0
  85. package/src/forge/_generated/mockMap.json +2 -0
  86. package/src/forge/_generated/mockMap.ts +7 -0
  87. package/src/forge/_generated/operationPlaybooks.md +145 -0
  88. package/src/forge/_generated/packageGraph.json +2 -0
  89. package/src/forge/_generated/packageGraph.ts +168569 -0
  90. package/src/forge/_generated/packageUpgradeRegistry.json +2 -0
  91. package/src/forge/_generated/packageUpgradeRegistry.ts +15 -0
  92. package/src/forge/_generated/permissionMatrix.json +2 -0
  93. package/src/forge/_generated/permissionMatrix.ts +7 -0
  94. package/src/forge/_generated/policyRegistry.json +2 -0
  95. package/src/forge/_generated/policyRegistry.ts +11 -0
  96. package/src/forge/_generated/queryRegistry.json +2 -0
  97. package/src/forge/_generated/queryRegistry.ts +9 -0
  98. package/src/forge/_generated/react.d.ts +22 -0
  99. package/src/forge/_generated/react.ts +29 -0
  100. package/src/forge/_generated/reactManifest.json +2 -0
  101. package/src/forge/_generated/reactManifest.ts +19 -0
  102. package/src/forge/_generated/releaseManifest.json +2 -0
  103. package/src/forge/_generated/releaseManifest.ts +25 -0
  104. package/src/forge/_generated/rlsPolicies.json +2 -0
  105. package/src/forge/_generated/rlsPolicies.sql +34 -0
  106. package/src/forge/_generated/rlsPolicies.ts +6 -0
  107. package/src/forge/_generated/runtimeGraph.json +2 -0
  108. package/src/forge/_generated/runtimeGraph.ts +8 -0
  109. package/src/forge/_generated/runtimeMatrix.json +2 -0
  110. package/src/forge/_generated/runtimeMatrix.ts +229125 -0
  111. package/src/forge/_generated/runtimeRegistry.ts +2 -0
  112. package/src/forge/_generated/runtimeRules.md +79 -0
  113. package/src/forge/_generated/secretRegistry.json +2 -0
  114. package/src/forge/_generated/secretRegistry.ts +50 -0
  115. package/src/forge/_generated/secretsContext.ts +11 -0
  116. package/src/forge/_generated/serverApi.ts +10 -0
  117. package/src/forge/_generated/sourceMapManifest.json +2 -0
  118. package/src/forge/_generated/sourceMapManifest.ts +7 -0
  119. package/src/forge/_generated/sqlPlan.json +2 -0
  120. package/src/forge/_generated/sqlPlan.ts +88 -0
  121. package/src/forge/_generated/subscriptionManifest.json +2 -0
  122. package/src/forge/_generated/subscriptionManifest.ts +7 -0
  123. package/src/forge/_generated/symbolicationManifest.json +2 -0
  124. package/src/forge/_generated/symbolicationManifest.ts +17 -0
  125. package/src/forge/_generated/telemetryRegistry.json +2 -0
  126. package/src/forge/_generated/telemetryRegistry.ts +9 -0
  127. package/src/forge/_generated/telemetrySinks.json +2 -0
  128. package/src/forge/_generated/telemetrySinks.ts +11 -0
  129. package/src/forge/_generated/tenantScope.json +2 -0
  130. package/src/forge/_generated/tenantScope.ts +8 -0
  131. package/src/forge/_generated/testGraph.json +2 -0
  132. package/src/forge/_generated/testGraph.ts +3054 -0
  133. package/src/forge/_generated/testPlanRegistry.json +2 -0
  134. package/src/forge/_generated/testPlanRegistry.ts +33 -0
  135. package/src/forge/_generated/uiRoutes.json +2 -0
  136. package/src/forge/_generated/uiRoutes.ts +16 -0
  137. package/src/forge/_generated/uiScenarios.json +2 -0
  138. package/src/forge/_generated/uiScenarios.ts +30 -0
  139. package/src/forge/_generated/uiTestManifest.json +2 -0
  140. package/src/forge/_generated/uiTestManifest.ts +27 -0
  141. package/src/forge/_generated/workflowRegistry.json +2 -0
  142. package/src/forge/_generated/workflowRegistry.ts +9 -0
  143. package/src/forge/_generated/workflowSubscriptions.json +2 -0
  144. package/src/forge/_generated/workflowSubscriptions.ts +10 -0
  145. package/src/forge/agent-adapters/index.ts +1002 -0
  146. package/src/forge/agent-adapters/types.ts +135 -0
  147. package/src/forge/cli/agent-contract.ts +50 -0
  148. package/src/forge/cli/ai.ts +148 -0
  149. package/src/forge/cli/auth.ts +198 -0
  150. package/src/forge/cli/build.ts +105 -0
  151. package/src/forge/cli/bun-exec.ts +4 -0
  152. package/src/forge/cli/commands.ts +1130 -0
  153. package/src/forge/cli/db.ts +316 -0
  154. package/src/forge/cli/deps.ts +277 -0
  155. package/src/forge/cli/dev.ts +529 -0
  156. package/src/forge/cli/doctor.ts +209 -0
  157. package/src/forge/cli/feature.ts +485 -0
  158. package/src/forge/cli/index.ts +25 -0
  159. package/src/forge/cli/lint-forge.ts +119 -0
  160. package/src/forge/cli/live.ts +179 -0
  161. package/src/forge/cli/main.ts +92 -0
  162. package/src/forge/cli/make.ts +133 -0
  163. package/src/forge/cli/new.ts +505 -0
  164. package/src/forge/cli/outbox.ts +297 -0
  165. package/src/forge/cli/output.ts +114 -0
  166. package/src/forge/cli/parse.ts +2211 -0
  167. package/src/forge/cli/policy.ts +204 -0
  168. package/src/forge/cli/query.ts +91 -0
  169. package/src/forge/cli/refactor.ts +221 -0
  170. package/src/forge/cli/release.ts +285 -0
  171. package/src/forge/cli/rls.ts +322 -0
  172. package/src/forge/cli/run.ts +76 -0
  173. package/src/forge/cli/secrets.ts +274 -0
  174. package/src/forge/cli/self-host.ts +468 -0
  175. package/src/forge/cli/serve.ts +93 -0
  176. package/src/forge/cli/telemetry.ts +219 -0
  177. package/src/forge/cli/verify.ts +587 -0
  178. package/src/forge/cli/version.ts +1 -0
  179. package/src/forge/cli/windows.ts +413 -0
  180. package/src/forge/cli/worker.ts +87 -0
  181. package/src/forge/cli/workflow.ts +424 -0
  182. package/src/forge/compiler/action-subscriptions/build.ts +116 -0
  183. package/src/forge/compiler/action-subscriptions/constants.ts +2 -0
  184. package/src/forge/compiler/action-subscriptions/index.ts +6 -0
  185. package/src/forge/compiler/action-subscriptions/parse.ts +6 -0
  186. package/src/forge/compiler/agent-contract/build.ts +1651 -0
  187. package/src/forge/compiler/agent-contract/types.ts +326 -0
  188. package/src/forge/compiler/ai-registry/build.ts +165 -0
  189. package/src/forge/compiler/ai-registry/constants.ts +2 -0
  190. package/src/forge/compiler/ai-registry/parse.ts +56 -0
  191. package/src/forge/compiler/api-surface/build.ts +107 -0
  192. package/src/forge/compiler/app-graph/build.ts +121 -0
  193. package/src/forge/compiler/app-graph/classify.ts +10 -0
  194. package/src/forge/compiler/app-graph/dup-symbol.ts +29 -0
  195. package/src/forge/compiler/app-graph/extract.ts +124 -0
  196. package/src/forge/compiler/app-graph/forge-apis.ts +29 -0
  197. package/src/forge/compiler/app-graph/index.ts +15 -0
  198. package/src/forge/compiler/app-graph/module-graph.ts +320 -0
  199. package/src/forge/compiler/app-graph/parser.ts +119 -0
  200. package/src/forge/compiler/app-graph/symbols.ts +48 -0
  201. package/src/forge/compiler/app-graph/tsconfig-hash.ts +62 -0
  202. package/src/forge/compiler/app-graph/types.ts +43 -0
  203. package/src/forge/compiler/app-graph/versions.ts +14 -0
  204. package/src/forge/compiler/cache/index.ts +17 -0
  205. package/src/forge/compiler/cache/key.ts +46 -0
  206. package/src/forge/compiler/cache/scheduler.ts +72 -0
  207. package/src/forge/compiler/cache/store.ts +78 -0
  208. package/src/forge/compiler/classifier/capabilities.ts +78 -0
  209. package/src/forge/compiler/classifier/classify.ts +113 -0
  210. package/src/forge/compiler/classifier/contexts.ts +188 -0
  211. package/src/forge/compiler/classifier/index.ts +18 -0
  212. package/src/forge/compiler/classifier/runtime-matrix.ts +45 -0
  213. package/src/forge/compiler/classifier/secrets.ts +41 -0
  214. package/src/forge/compiler/classifier/signals.ts +129 -0
  215. package/src/forge/compiler/client-sdk/build-manifest.ts +151 -0
  216. package/src/forge/compiler/client-sdk/render-client.ts +432 -0
  217. package/src/forge/compiler/data-graph/build.ts +131 -0
  218. package/src/forge/compiler/data-graph/constants.ts +5 -0
  219. package/src/forge/compiler/data-graph/index.ts +6 -0
  220. package/src/forge/compiler/data-graph/parse.ts +176 -0
  221. package/src/forge/compiler/data-graph/rls/build.ts +222 -0
  222. package/src/forge/compiler/data-graph/rls/types.ts +62 -0
  223. package/src/forge/compiler/data-graph/sql/ddl.ts +390 -0
  224. package/src/forge/compiler/data-graph/sql/naming.ts +10 -0
  225. package/src/forge/compiler/data-graph/sql/serialize.ts +85 -0
  226. package/src/forge/compiler/data-graph/sql/types.ts +37 -0
  227. package/src/forge/compiler/dev-manifest/build.ts +170 -0
  228. package/src/forge/compiler/dev-manifest/constants.ts +5 -0
  229. package/src/forge/compiler/diagnostics/codes.ts +611 -0
  230. package/src/forge/compiler/diagnostics/create.ts +245 -0
  231. package/src/forge/compiler/diagnostics/index.ts +55 -0
  232. package/src/forge/compiler/emitter/artifact-kind.ts +14 -0
  233. package/src/forge/compiler/emitter/barrel.ts +44 -0
  234. package/src/forge/compiler/emitter/constants.ts +7 -0
  235. package/src/forge/compiler/emitter/emit.ts +237 -0
  236. package/src/forge/compiler/emitter/index.ts +24 -0
  237. package/src/forge/compiler/emitter/lock.ts +62 -0
  238. package/src/forge/compiler/emitter/render.ts +73 -0
  239. package/src/forge/compiler/emitter/write.ts +35 -0
  240. package/src/forge/compiler/frontend-graph/build.ts +495 -0
  241. package/src/forge/compiler/fs/index.ts +23 -0
  242. package/src/forge/compiler/fs/memory.ts +233 -0
  243. package/src/forge/compiler/fs/node.ts +139 -0
  244. package/src/forge/compiler/fs/profile.ts +108 -0
  245. package/src/forge/compiler/fs/types.ts +52 -0
  246. package/src/forge/compiler/guards/artifacts.ts +96 -0
  247. package/src/forge/compiler/guards/check-ai-usage.ts +98 -0
  248. package/src/forge/compiler/guards/check-import-guards.ts +106 -0
  249. package/src/forge/compiler/guards/check-process-env.ts +98 -0
  250. package/src/forge/compiler/guards/check-query-usage.ts +76 -0
  251. package/src/forge/compiler/guards/index.ts +11 -0
  252. package/src/forge/compiler/guards/propagate-contexts.ts +57 -0
  253. package/src/forge/compiler/index.ts +17 -0
  254. package/src/forge/compiler/integration/add.ts +496 -0
  255. package/src/forge/compiler/integration/index.ts +17 -0
  256. package/src/forge/compiler/integration/plan.ts +283 -0
  257. package/src/forge/compiler/integration/render.ts +189 -0
  258. package/src/forge/compiler/integration/snapshot.ts +52 -0
  259. package/src/forge/compiler/integration/templates/ai.ts +131 -0
  260. package/src/forge/compiler/integration/templates/index.ts +8 -0
  261. package/src/forge/compiler/integration/templates/posthog.ts +145 -0
  262. package/src/forge/compiler/integration/templates/render.ts +113 -0
  263. package/src/forge/compiler/integration/templates/sentry.ts +151 -0
  264. package/src/forge/compiler/integration/templates/stripe.ts +109 -0
  265. package/src/forge/compiler/integration/templates/types.ts +14 -0
  266. package/src/forge/compiler/integration/templates/zod.ts +55 -0
  267. package/src/forge/compiler/live-production/types.ts +122 -0
  268. package/src/forge/compiler/live-query-registry/build.ts +150 -0
  269. package/src/forge/compiler/live-query-registry/constants.ts +2 -0
  270. package/src/forge/compiler/make-registry/build.ts +179 -0
  271. package/src/forge/compiler/orchestrator/discover.ts +214 -0
  272. package/src/forge/compiler/orchestrator/fast-check.ts +117 -0
  273. package/src/forge/compiler/orchestrator/generate-lock.ts +138 -0
  274. package/src/forge/compiler/orchestrator/guards.ts +5 -0
  275. package/src/forge/compiler/orchestrator/index.ts +27 -0
  276. package/src/forge/compiler/orchestrator/manifest-hashes.ts +21 -0
  277. package/src/forge/compiler/orchestrator/manifest.ts +92 -0
  278. package/src/forge/compiler/orchestrator/orphans.ts +51 -0
  279. package/src/forge/compiler/orchestrator/plan.ts +876 -0
  280. package/src/forge/compiler/orchestrator/profile.ts +36 -0
  281. package/src/forge/compiler/orchestrator/run.ts +277 -0
  282. package/src/forge/compiler/orchestrator/serialize.ts +886 -0
  283. package/src/forge/compiler/orchestrator/session.ts +96 -0
  284. package/src/forge/compiler/orchestrator/types.ts +31 -0
  285. package/src/forge/compiler/orchestrator/verify.ts +38 -0
  286. package/src/forge/compiler/orchestrator/workspace-index.ts +154 -0
  287. package/src/forge/compiler/package-graph/capabilities-stub.ts +33 -0
  288. package/src/forge/compiler/package-graph/checksum.ts +97 -0
  289. package/src/forge/compiler/package-graph/compiler.ts +392 -0
  290. package/src/forge/compiler/package-graph/constants.ts +4 -0
  291. package/src/forge/compiler/package-graph/dts-extractor.ts +142 -0
  292. package/src/forge/compiler/package-graph/exports-discovery.ts +84 -0
  293. package/src/forge/compiler/package-graph/extract-dts.ts +32 -0
  294. package/src/forge/compiler/package-graph/index.ts +33 -0
  295. package/src/forge/compiler/package-graph/jsdoc.ts +62 -0
  296. package/src/forge/compiler/package-graph/read-file.ts +21 -0
  297. package/src/forge/compiler/package-graph/resolve.ts +127 -0
  298. package/src/forge/compiler/package-manager/adapter.ts +237 -0
  299. package/src/forge/compiler/package-manager/bun-executable.ts +92 -0
  300. package/src/forge/compiler/package-manager/commands.ts +47 -0
  301. package/src/forge/compiler/package-manager/detect.ts +79 -0
  302. package/src/forge/compiler/package-manager/executor.ts +117 -0
  303. package/src/forge/compiler/package-manager/index.ts +22 -0
  304. package/src/forge/compiler/package-manager/parse-spec.ts +16 -0
  305. package/src/forge/compiler/package-manager/version.ts +27 -0
  306. package/src/forge/compiler/package-upgrades/apply.ts +195 -0
  307. package/src/forge/compiler/package-upgrades/comparator.ts +181 -0
  308. package/src/forge/compiler/package-upgrades/impact.ts +139 -0
  309. package/src/forge/compiler/package-upgrades/markdown.ts +97 -0
  310. package/src/forge/compiler/package-upgrades/planner.ts +532 -0
  311. package/src/forge/compiler/package-upgrades/risk.ts +208 -0
  312. package/src/forge/compiler/package-upgrades/types.ts +174 -0
  313. package/src/forge/compiler/policy-registry/build.ts +266 -0
  314. package/src/forge/compiler/policy-registry/constants.ts +2 -0
  315. package/src/forge/compiler/policy-registry/parse.ts +81 -0
  316. package/src/forge/compiler/primitives/compare.ts +26 -0
  317. package/src/forge/compiler/primitives/hash.ts +40 -0
  318. package/src/forge/compiler/primitives/header.ts +45 -0
  319. package/src/forge/compiler/primitives/index.ts +45 -0
  320. package/src/forge/compiler/primitives/paths.ts +24 -0
  321. package/src/forge/compiler/primitives/result.ts +164 -0
  322. package/src/forge/compiler/primitives/serialize.ts +66 -0
  323. package/src/forge/compiler/primitives/sort.ts +87 -0
  324. package/src/forge/compiler/query-registry/build.ts +114 -0
  325. package/src/forge/compiler/query-registry/constants.ts +2 -0
  326. package/src/forge/compiler/recipes/definitions.ts +289 -0
  327. package/src/forge/compiler/recipes/helpers.ts +37 -0
  328. package/src/forge/compiler/recipes/index.ts +21 -0
  329. package/src/forge/compiler/recipes/registry.ts +102 -0
  330. package/src/forge/compiler/release/build.ts +100 -0
  331. package/src/forge/compiler/release/types.ts +119 -0
  332. package/src/forge/compiler/runtime-graph/build.ts +137 -0
  333. package/src/forge/compiler/runtime-graph/constants.ts +5 -0
  334. package/src/forge/compiler/runtime-graph/index.ts +5 -0
  335. package/src/forge/compiler/sandbox/artifact-sanitize.ts +26 -0
  336. package/src/forge/compiler/sandbox/backends/child.ts +123 -0
  337. package/src/forge/compiler/sandbox/backends/docker.ts +173 -0
  338. package/src/forge/compiler/sandbox/index.ts +51 -0
  339. package/src/forge/compiler/sandbox/inspect.ts +143 -0
  340. package/src/forge/compiler/sandbox/inspector-entry.ts +115 -0
  341. package/src/forge/compiler/sandbox/limits.ts +31 -0
  342. package/src/forge/compiler/sandbox/scrub-env.ts +60 -0
  343. package/src/forge/compiler/sandbox/secret-scan.ts +54 -0
  344. package/src/forge/compiler/sandbox/serialize.ts +106 -0
  345. package/src/forge/compiler/sandbox/types.ts +7 -0
  346. package/src/forge/compiler/secret-registry/build.ts +123 -0
  347. package/src/forge/compiler/telemetry-registry/build.ts +89 -0
  348. package/src/forge/compiler/telemetry-registry/constants.ts +2 -0
  349. package/src/forge/compiler/telemetry-registry/parse.ts +13 -0
  350. package/src/forge/compiler/test-graph/build.ts +277 -0
  351. package/src/forge/compiler/types/action-subscriptions.ts +19 -0
  352. package/src/forge/compiler/types/ai-registry.ts +33 -0
  353. package/src/forge/compiler/types/app-graph.ts +80 -0
  354. package/src/forge/compiler/types/capability.ts +29 -0
  355. package/src/forge/compiler/types/classification.ts +9 -0
  356. package/src/forge/compiler/types/cli.ts +159 -0
  357. package/src/forge/compiler/types/data-graph.ts +24 -0
  358. package/src/forge/compiler/types/dev-manifest.ts +41 -0
  359. package/src/forge/compiler/types/diagnostic.ts +12 -0
  360. package/src/forge/compiler/types/emit.ts +25 -0
  361. package/src/forge/compiler/types/frontend-graph.ts +81 -0
  362. package/src/forge/compiler/types/import-guards.ts +19 -0
  363. package/src/forge/compiler/types/index.ts +98 -0
  364. package/src/forge/compiler/types/integration.ts +25 -0
  365. package/src/forge/compiler/types/json.ts +3 -0
  366. package/src/forge/compiler/types/live-query-registry.ts +32 -0
  367. package/src/forge/compiler/types/lock.ts +37 -0
  368. package/src/forge/compiler/types/package-graph.ts +84 -0
  369. package/src/forge/compiler/types/policy-registry.ts +69 -0
  370. package/src/forge/compiler/types/query-registry.ts +18 -0
  371. package/src/forge/compiler/types/runtime-graph.ts +30 -0
  372. package/src/forge/compiler/types/runtime-matrix.ts +16 -0
  373. package/src/forge/compiler/types/runtime.ts +30 -0
  374. package/src/forge/compiler/types/sandbox.ts +24 -0
  375. package/src/forge/compiler/types/secret-registry.ts +38 -0
  376. package/src/forge/compiler/types/telemetry-registry.ts +26 -0
  377. package/src/forge/compiler/types/test-graph.ts +45 -0
  378. package/src/forge/compiler/types/workflow-registry.ts +42 -0
  379. package/src/forge/compiler/workflow-registry/build.ts +180 -0
  380. package/src/forge/compiler/workflow-registry/constants.ts +2 -0
  381. package/src/forge/compiler/workflow-registry/index.ts +5 -0
  382. package/src/forge/compiler/workflow-registry/parse.ts +19 -0
  383. package/src/forge/dev/server.ts +1379 -0
  384. package/src/forge/dev/types.ts +49 -0
  385. package/src/forge/dev/watch.ts +109 -0
  386. package/src/forge/dev-console/cycle.ts +652 -0
  387. package/src/forge/dev-console/types.ts +99 -0
  388. package/src/forge/feature/compiler.ts +656 -0
  389. package/src/forge/feature/examples.ts +125 -0
  390. package/src/forge/feature/types.ts +177 -0
  391. package/src/forge/impact/index.ts +1160 -0
  392. package/src/forge/impact/types.ts +151 -0
  393. package/src/forge/intent/index.ts +490 -0
  394. package/src/forge/intent/types.ts +73 -0
  395. package/src/forge/make/fields.ts +146 -0
  396. package/src/forge/make/index.ts +1101 -0
  397. package/src/forge/make/naming.ts +42 -0
  398. package/src/forge/make/templates.ts +525 -0
  399. package/src/forge/make/types.ts +151 -0
  400. package/src/forge/platform/module.ts +20 -0
  401. package/src/forge/policy.ts +1 -0
  402. package/src/forge/react/index.ts +418 -0
  403. package/src/forge/refactor/index.ts +1936 -0
  404. package/src/forge/refactor/text-utils.ts +34 -0
  405. package/src/forge/refactor/types.ts +191 -0
  406. package/src/forge/refactor/workspace-fs.ts +171 -0
  407. package/src/forge/repair/index.ts +656 -0
  408. package/src/forge/repair/rules/index.ts +476 -0
  409. package/src/forge/repair/types.ts +175 -0
  410. package/src/forge/review/index.ts +992 -0
  411. package/src/forge/review/types.ts +196 -0
  412. package/src/forge/runtime/ai/check.ts +86 -0
  413. package/src/forge/runtime/ai/context.ts +394 -0
  414. package/src/forge/runtime/ai/cost-estimator.ts +41 -0
  415. package/src/forge/runtime/ai/mock.ts +49 -0
  416. package/src/forge/runtime/ai/providers.ts +78 -0
  417. package/src/forge/runtime/ai/state.ts +17 -0
  418. package/src/forge/runtime/ai/types.ts +67 -0
  419. package/src/forge/runtime/auth/authenticate.ts +58 -0
  420. package/src/forge/runtime/auth/claims.ts +119 -0
  421. package/src/forge/runtime/auth/config.ts +148 -0
  422. package/src/forge/runtime/auth/errors.ts +45 -0
  423. package/src/forge/runtime/auth/evaluate.ts +126 -0
  424. package/src/forge/runtime/auth/resolve.ts +74 -0
  425. package/src/forge/runtime/auth/types.ts +87 -0
  426. package/src/forge/runtime/auth/verifier.ts +138 -0
  427. package/src/forge/runtime/context/create-context.ts +204 -0
  428. package/src/forge/runtime/context/create-query-context.ts +34 -0
  429. package/src/forge/runtime/db/adapter.ts +31 -0
  430. package/src/forge/runtime/db/factory.ts +83 -0
  431. package/src/forge/runtime/db/generated-client.ts +294 -0
  432. package/src/forge/runtime/db/memory-adapter.ts +706 -0
  433. package/src/forge/runtime/db/migrate.ts +132 -0
  434. package/src/forge/runtime/db/outbox.ts +54 -0
  435. package/src/forge/runtime/db/pglite-adapter.ts +51 -0
  436. package/src/forge/runtime/db/postgres-adapter.ts +112 -0
  437. package/src/forge/runtime/db/read-only-client.ts +97 -0
  438. package/src/forge/runtime/db/session-context.ts +62 -0
  439. package/src/forge/runtime/executor.ts +446 -0
  440. package/src/forge/runtime/live/dependency-tracker.ts +57 -0
  441. package/src/forge/runtime/live/invalidation-log.ts +189 -0
  442. package/src/forge/runtime/live/live-query-runner.ts +267 -0
  443. package/src/forge/runtime/live/registry.ts +28 -0
  444. package/src/forge/runtime/live/sse.ts +75 -0
  445. package/src/forge/runtime/live/subscription-manager.ts +443 -0
  446. package/src/forge/runtime/live/types.ts +143 -0
  447. package/src/forge/runtime/outbox/claim.ts +153 -0
  448. package/src/forge/runtime/outbox/process.ts +298 -0
  449. package/src/forge/runtime/outbox/retry.ts +8 -0
  450. package/src/forge/runtime/outbox/subscriptions.ts +33 -0
  451. package/src/forge/runtime/outbox/types.ts +69 -0
  452. package/src/forge/runtime/policy/check.ts +157 -0
  453. package/src/forge/runtime/policy/load.ts +55 -0
  454. package/src/forge/runtime/query/registry.ts +19 -0
  455. package/src/forge/runtime/query/run-query.ts +347 -0
  456. package/src/forge/runtime/release/runtime.ts +322 -0
  457. package/src/forge/runtime/release/symbolicate.ts +175 -0
  458. package/src/forge/runtime/runner/command-transaction.ts +193 -0
  459. package/src/forge/runtime/runner/run-entry.ts +226 -0
  460. package/src/forge/runtime/secrets/check.ts +78 -0
  461. package/src/forge/runtime/secrets/create-context.ts +138 -0
  462. package/src/forge/runtime/secrets/env-loader.ts +94 -0
  463. package/src/forge/runtime/secrets/runtime-bundle.ts +47 -0
  464. package/src/forge/runtime/secrets/types.ts +31 -0
  465. package/src/forge/runtime/telemetry/buffer.ts +87 -0
  466. package/src/forge/runtime/telemetry/context.ts +192 -0
  467. package/src/forge/runtime/telemetry/correlation.ts +13 -0
  468. package/src/forge/runtime/telemetry/flush.ts +190 -0
  469. package/src/forge/runtime/telemetry/process.ts +20 -0
  470. package/src/forge/runtime/telemetry/scrubber.ts +115 -0
  471. package/src/forge/runtime/telemetry/sinks/local-jsonl.ts +39 -0
  472. package/src/forge/runtime/telemetry/sinks/posthog.ts +64 -0
  473. package/src/forge/runtime/telemetry/sinks/sentry.ts +60 -0
  474. package/src/forge/runtime/telemetry/spans.ts +58 -0
  475. package/src/forge/runtime/telemetry/types.ts +64 -0
  476. package/src/forge/runtime/workflows/cancel.ts +26 -0
  477. package/src/forge/runtime/workflows/create-run.ts +98 -0
  478. package/src/forge/runtime/workflows/process-run.ts +182 -0
  479. package/src/forge/runtime/workflows/process-step.ts +190 -0
  480. package/src/forge/runtime/workflows/process.ts +260 -0
  481. package/src/forge/runtime/workflows/registry.ts +51 -0
  482. package/src/forge/runtime/workflows/resolve-step.ts +46 -0
  483. package/src/forge/runtime/workflows/retry-run.ts +44 -0
  484. package/src/forge/runtime/workflows/retry.ts +8 -0
  485. package/src/forge/runtime/workflows/sanitize.ts +19 -0
  486. package/src/forge/runtime/workflows/start-from-outbox.ts +71 -0
  487. package/src/forge/runtime/workflows/types.ts +77 -0
  488. package/src/forge/server.ts +96 -0
  489. package/src/forge/ui/index.ts +770 -0
  490. package/src/forge/ui/types.ts +191 -0
  491. package/templates/b2b-support-web/.env.example +22 -0
  492. package/templates/b2b-support-web/.vscode/settings.json +14 -0
  493. package/templates/b2b-support-web/AGENTS.md +108 -0
  494. package/templates/b2b-support-web/README.md +48 -0
  495. package/templates/b2b-support-web/forge.config.ts +3 -0
  496. package/templates/b2b-support-web/package.json +34 -0
  497. package/templates/b2b-support-web/src/actions/captureTicketCreated.ts +14 -0
  498. package/templates/b2b-support-web/src/commands/closeTicket.ts +20 -0
  499. package/templates/b2b-support-web/src/commands/createTicket.ts +47 -0
  500. package/templates/b2b-support-web/src/commands/manageBilling.ts +9 -0
  501. package/templates/b2b-support-web/src/forge/schema.ts +35 -0
  502. package/templates/b2b-support-web/src/policies.ts +9 -0
  503. package/templates/b2b-support-web/src/queries/getTicket.ts +6 -0
  504. package/templates/b2b-support-web/src/queries/listTickets.ts +6 -0
  505. package/templates/b2b-support-web/src/queries/liveTickets.ts +9 -0
  506. package/templates/b2b-support-web/src/workflows/triageTicketWorkflow.ts +64 -0
  507. package/templates/b2b-support-web/tsconfig.json +14 -0
  508. package/templates/b2b-support-web/web/app/globals.css +77 -0
  509. package/templates/b2b-support-web/web/app/layout.tsx +13 -0
  510. package/templates/b2b-support-web/web/app/page.tsx +13 -0
  511. package/templates/b2b-support-web/web/app/providers.tsx +21 -0
  512. package/templates/b2b-support-web/web/app/tickets/page.tsx +21 -0
  513. package/templates/b2b-support-web/web/components/CreateTicketForm.tsx +43 -0
  514. package/templates/b2b-support-web/web/components/PolicyDeniedDemo.tsx +31 -0
  515. package/templates/b2b-support-web/web/components/TicketList.tsx +52 -0
  516. package/templates/b2b-support-web/web/components/TraceDetails.tsx +18 -0
  517. package/templates/b2b-support-web/web/components/TriageStatus.tsx +13 -0
  518. package/templates/b2b-support-web/web/lib/forge.ts +13 -0
  519. package/templates/b2b-support-web/web/next-env.d.ts +5 -0
  520. package/templates/b2b-support-web/web/next.config.ts +8 -0
  521. package/templates/b2b-support-web/web/package.json +21 -0
  522. package/templates/b2b-support-web/web/tsconfig.json +30 -0
  523. package/templates/minimal-web/.vscode/settings.json +14 -0
  524. package/templates/minimal-web/README.md +21 -0
  525. package/templates/minimal-web/forge.config.ts +3 -0
  526. package/templates/minimal-web/package.json +32 -0
  527. package/templates/minimal-web/src/actions/logNoteCreated.ts +11 -0
  528. package/templates/minimal-web/src/commands/createNote.ts +26 -0
  529. package/templates/minimal-web/src/forge/schema.ts +12 -0
  530. package/templates/minimal-web/src/policies.ts +6 -0
  531. package/templates/minimal-web/src/queries/listNotes.ts +8 -0
  532. package/templates/minimal-web/src/queries/liveNotes.ts +8 -0
  533. package/templates/minimal-web/tsconfig.json +15 -0
  534. package/templates/minimal-web/web/index.html +12 -0
  535. package/templates/minimal-web/web/package.json +21 -0
  536. package/templates/minimal-web/web/src/App.tsx +89 -0
  537. package/templates/minimal-web/web/src/lib/forge.ts +13 -0
  538. package/templates/minimal-web/web/src/main.tsx +13 -0
  539. package/templates/minimal-web/web/src/styles.css +156 -0
  540. package/templates/minimal-web/web/tsconfig.json +18 -0
@@ -0,0 +1,1651 @@
1
+ import { join } from "node:path";
2
+ import { nodeFileSystem } from "../fs/index.ts";
3
+ import type { ApiSurface } from "../api-surface/build.ts";
4
+ import type { ClassifiedPackage } from "../classifier/runtime-matrix.ts";
5
+ import { detectCapabilities } from "../classifier/capabilities.ts";
6
+ import { detectSecrets } from "../classifier/secrets.ts";
7
+ import { GENERATOR_VERSION } from "../emitter/constants.ts";
8
+ import { stripDeterministicHeader } from "../primitives/header.ts";
9
+ import { canonicalJson, normalizeNewlines, serializeCanonical } from "../primitives/serialize.ts";
10
+ import { resolveByPackageName } from "../recipes/registry.ts";
11
+ import { secretLeakScan } from "../sandbox/secret-scan.ts";
12
+ import type { AiRegistry } from "../types/ai-registry.ts";
13
+ import type { AppGraph } from "../types/app-graph.ts";
14
+ import type { DataGraph } from "../types/data-graph.ts";
15
+ import type { Diagnostic } from "../types/diagnostic.ts";
16
+ import type { PackageGraph } from "../types/package-graph.ts";
17
+ import type {
18
+ PermissionMatrix,
19
+ PolicyRegistry,
20
+ TenantScope,
21
+ } from "../types/policy-registry.ts";
22
+ import type { RuntimeContext } from "../types/runtime.ts";
23
+ import type { RuntimeGraph } from "../types/runtime-graph.ts";
24
+ import type { SecretRegistry } from "../types/secret-registry.ts";
25
+ import type {
26
+ TelemetryRegistry,
27
+ TelemetrySinks,
28
+ } from "../types/telemetry-registry.ts";
29
+ import type { WorkflowRegistry } from "../types/workflow-registry.ts";
30
+ import type { QueryRegistry } from "../types/query-registry.ts";
31
+ import type { LiveQueryRegistry } from "../types/live-query-registry.ts";
32
+ import type { ClientManifest } from "../client-sdk/build-manifest.ts";
33
+ import type { FrontendGraph } from "../types/frontend-graph.ts";
34
+ import { createDiagnostic } from "../diagnostics/create.ts";
35
+ import { AUTH_ENV, DEFAULT_AUTH_CLAIMS } from "../../runtime/auth/config.ts";
36
+ import type {
37
+ AgentCapabilityMap,
38
+ AgentCapabilityMapEntry,
39
+ AgentContract,
40
+ AgentFrontendRuntimeBindingInfo,
41
+ AgentFrontendUsageInfo,
42
+ AgentHttpEndpointInfo,
43
+ AgentIntegrationInfo,
44
+ AgentRuntimeRule,
45
+ AgentPlaybook,
46
+ } from "./types.ts";
47
+
48
+ const AGENTS_USER_START = "<!-- user-notes:start -->";
49
+ const AGENTS_USER_END = "<!-- user-notes:end -->";
50
+ const DEFAULT_USER_NOTES = "Project-specific notes can go here.";
51
+
52
+ export interface AgentContractInput {
53
+ workspaceRoot: string;
54
+ appGraph: AppGraph;
55
+ packageGraph: PackageGraph;
56
+ classified: ClassifiedPackage[];
57
+ runtimeGraph: RuntimeGraph;
58
+ dataGraph: DataGraph;
59
+ policyRegistry: PolicyRegistry;
60
+ permissionMatrix: PermissionMatrix;
61
+ tenantScope: TenantScope;
62
+ secretRegistry: SecretRegistry;
63
+ telemetryRegistry: TelemetryRegistry;
64
+ telemetrySinks: TelemetrySinks;
65
+ aiRegistry: AiRegistry;
66
+ queryRegistry: QueryRegistry;
67
+ liveQueryRegistry: LiveQueryRegistry;
68
+ workflowRegistry: WorkflowRegistry;
69
+ apiSurface: ApiSurface;
70
+ clientManifest: ClientManifest;
71
+ frontendGraph: FrontendGraph;
72
+ }
73
+
74
+ export interface AgentContractArtifacts {
75
+ contract: AgentContract;
76
+ capabilityMap: AgentCapabilityMap;
77
+ agentsMd: string;
78
+ appMapMd: string;
79
+ capabilityMapMd: string;
80
+ runtimeRulesMd: string;
81
+ operationPlaybooksMd: string;
82
+ agentQuickstartMd: string;
83
+ diagnostics: Diagnostic[];
84
+ }
85
+
86
+ function sorted<T>(items: T[], by: (item: T) => string): T[] {
87
+ return [...items].sort((a, b) => by(a).localeCompare(by(b)));
88
+ }
89
+
90
+ function uniqueSorted(items: string[]): string[] {
91
+ return [...new Set(items.filter(Boolean))].sort();
92
+ }
93
+
94
+ function readPackageInfo(workspaceRoot: string): { name: string; template?: string } {
95
+ const packagePath = join(workspaceRoot, "package.json");
96
+ if (!nodeFileSystem.exists(packagePath)) {
97
+ return { name: "forge-app" };
98
+ }
99
+
100
+ try {
101
+ const pkg = JSON.parse((nodeFileSystem.readText(packagePath) ?? "")) as {
102
+ name?: string;
103
+ forge?: { template?: string };
104
+ };
105
+ return {
106
+ name: pkg.name ?? "forge-app",
107
+ template: pkg.forge?.template,
108
+ };
109
+ } catch {
110
+ return { name: "forge-app" };
111
+ }
112
+ }
113
+
114
+ function authPolicy(
115
+ auth: { kind: string; policy?: string } | undefined,
116
+ ): string | undefined {
117
+ return auth?.kind === "policy" ? auth.policy : auth?.kind;
118
+ }
119
+
120
+ function packageNamesForModule(appGraph: AppGraph, moduleId: string): string[] {
121
+ const node = appGraph.moduleGraph.nodes.find((candidate) => candidate.id === moduleId);
122
+ return uniqueSorted(node?.directPackageImports.map((imp) => imp.packageName) ?? []);
123
+ }
124
+
125
+ function forbiddenForContext(
126
+ classified: ClassifiedPackage[],
127
+ context: RuntimeContext,
128
+ ): string[] {
129
+ const forbidden = new Set<string>();
130
+ for (const pkg of classified) {
131
+ const recipe = pkg.recipe ?? resolveByPackageName(pkg.api.name);
132
+ const denied = recipe?.contexts.denied ?? pkg.classification.incompatible;
133
+ if (denied.includes(context)) {
134
+ const capabilities = detectCapabilities(pkg.api, recipe ?? undefined);
135
+ for (const [name, status] of Object.entries(capabilities)) {
136
+ if (
137
+ typeof status === "object" &&
138
+ "status" in status &&
139
+ (status.status === "required" || status.status === "forbidden")
140
+ ) {
141
+ forbidden.add(name);
142
+ }
143
+ }
144
+ if ((detectSecrets(pkg.api, recipe ?? undefined).length > 0)) {
145
+ forbidden.add("secrets");
146
+ }
147
+ }
148
+ }
149
+ return uniqueSorted([...forbidden]);
150
+ }
151
+
152
+ const DB_READ_OPS = new Set(["all", "count", "find", "first", "get", "list", "where"]);
153
+ const DB_WRITE_OPS = new Set(["delete", "insert", "patch", "replace", "update", "upsert"]);
154
+
155
+ function sourceText(workspaceRoot: string, file: string | undefined): string {
156
+ if (!file) {
157
+ return "";
158
+ }
159
+ const absolute = join(workspaceRoot, file);
160
+ if (!nodeFileSystem.exists(absolute)) {
161
+ return "";
162
+ }
163
+ return nodeFileSystem.readText(absolute) ?? "";
164
+ }
165
+
166
+ function dbTablesForText(
167
+ text: string,
168
+ tableNames: Set<string>,
169
+ ops: Set<string>,
170
+ ): string[] {
171
+ const tables: string[] = [];
172
+ for (const match of text.matchAll(/ctx\.db\.([A-Za-z_$][A-Za-z0-9_$]*)\s*\.\s*([A-Za-z_$][A-Za-z0-9_$]*)/g)) {
173
+ const table = match[1] ?? "";
174
+ const op = match[2] ?? "";
175
+ if (tableNames.has(table) && ops.has(op)) {
176
+ tables.push(table);
177
+ }
178
+ }
179
+ return uniqueSorted(tables);
180
+ }
181
+
182
+ function dbTablesForFile(
183
+ workspaceRoot: string,
184
+ file: string | undefined,
185
+ tableNames: Set<string>,
186
+ ops: Set<string>,
187
+ ): string[] {
188
+ return dbTablesForText(sourceText(workspaceRoot, file), tableNames, ops);
189
+ }
190
+
191
+ function emittedEventsForFile(workspaceRoot: string, file: string | undefined): string[] {
192
+ const text = sourceText(workspaceRoot, file);
193
+ return uniqueSorted(
194
+ [...text.matchAll(/ctx\.emit\s*\(\s*["'`]([^"'`]+)["'`]/g)]
195
+ .map((match) => match[1] ?? ""),
196
+ );
197
+ }
198
+
199
+ function buildIntegrations(classified: ClassifiedPackage[]): AgentIntegrationInfo[] {
200
+ const byAlias = new Map<string, AgentIntegrationInfo>();
201
+ for (const pkg of classified) {
202
+ const recipe = pkg.recipe ?? resolveByPackageName(pkg.api.name);
203
+ if (!recipe) {
204
+ continue;
205
+ }
206
+ const entry = byAlias.get(recipe.alias) ?? {
207
+ alias: recipe.alias,
208
+ packages: [],
209
+ secrets: [],
210
+ allowedContexts: recipe.contexts.allowed,
211
+ deniedContexts: recipe.contexts.denied,
212
+ };
213
+ entry.packages = uniqueSorted([...entry.packages, pkg.api.name]);
214
+ entry.secrets = uniqueSorted([
215
+ ...entry.secrets,
216
+ ...recipe.secrets.map((secret) => secret.envVar),
217
+ ]);
218
+ byAlias.set(recipe.alias, entry);
219
+ }
220
+ return sorted([...byAlias.values()], (entry) => entry.alias);
221
+ }
222
+
223
+ function runtimeRules(): AgentRuntimeRule[] {
224
+ return [
225
+ {
226
+ context: "command",
227
+ allowed: ["ctx.db writes", "ctx.emit", "ctx.telemetry buffered events"],
228
+ forbidden: ["network packages", "ctx.secrets", "ctx.ai", "process.env", "filesystem access"],
229
+ },
230
+ {
231
+ context: "query",
232
+ allowed: ["ctx.db reads", "ctx.telemetry buffered events"],
233
+ forbidden: ["insert/update/delete", "ctx.emit", "ctx.secrets", "ctx.ai", "network integrations"],
234
+ },
235
+ {
236
+ context: "liveQuery",
237
+ allowed: ["ctx.db reads", "tenant-scoped subscriptions"],
238
+ forbidden: ["insert/update/delete", "ctx.emit", "ctx.secrets", "ctx.ai", "network integrations"],
239
+ },
240
+ {
241
+ context: "action",
242
+ allowed: ["ctx.secrets", "integrations", "ctx.ai", "ctx.db reads/writes", "network packages"],
243
+ forbidden: ["uncommitted transactional side effects"],
244
+ },
245
+ {
246
+ context: "workflow",
247
+ allowed: ["durable steps", "ctx.secrets", "integrations", "ctx.ai", "retries"],
248
+ forbidden: ["non-idempotent step behavior without guards"],
249
+ },
250
+ ];
251
+ }
252
+
253
+ function playbooks(): AgentPlaybook[] {
254
+ return [
255
+ {
256
+ title: "Choose the right workflow",
257
+ steps: [
258
+ "Run forge do \"<objective>\" --json when the next command is not obvious.",
259
+ "Use forge do fix --json for failures, forge do verify --json before handoff, and forge do connect-ui --json for frontend wiring.",
260
+ "Follow the returned plan, filesToInspect, risks, and nextAction before using lower-level commands directly.",
261
+ ],
262
+ },
263
+ {
264
+ title: "Add a command",
265
+ steps: [
266
+ "Add a file under src/commands.",
267
+ "Declare auth with can(\"policy.name\") unless intentionally public/system.",
268
+ "Use ctx.db for transactional writes.",
269
+ "Use ctx.emit for side effects.",
270
+ "Run forge generate.",
271
+ "Run forge verify --strict.",
272
+ ],
273
+ },
274
+ {
275
+ title: "Add a query",
276
+ steps: [
277
+ "Add a file under src/queries.",
278
+ "Keep it read-only.",
279
+ "Declare auth explicitly.",
280
+ "Run forge generate.",
281
+ "Run forge check.",
282
+ ],
283
+ },
284
+ {
285
+ title: "Add a liveQuery",
286
+ steps: [
287
+ "Add a liveQuery under src/queries.",
288
+ "Keep it read-only and tenant-scoped when reading tenant tables.",
289
+ "Run forge generate.",
290
+ "Use forge inspect client --json to confirm client exposure.",
291
+ ],
292
+ },
293
+ {
294
+ title: "Debug a stale liveQuery",
295
+ steps: [
296
+ "Run forge live status --json.",
297
+ "Run forge live invalidations list --json and confirm the table and tenant changed.",
298
+ "Run forge live debug <subscriptionId> --json when a subscription id is available.",
299
+ "Check that _forge_live_invalidations has revisions newer than the last sent snapshot.",
300
+ "Reconnect with Last-Event-ID or ?lastRevision=<revision> to verify resume behavior.",
301
+ ],
302
+ },
303
+ {
304
+ title: "Add a table",
305
+ steps: [
306
+ "Edit src/forge/schema.ts.",
307
+ "Include tenantId for tenant-scoped data.",
308
+ "Run forge generate.",
309
+ "Run forge db diff.",
310
+ "Run forge verify --strict.",
311
+ ],
312
+ },
313
+ {
314
+ title: "Scaffold a resource",
315
+ steps: [
316
+ "Run forge make resource <name> --fields name:type,status:enum(open,closed) --dry-run --json.",
317
+ "Review the plan and diagnostics.",
318
+ "Run forge make resource <name> --fields name:type --with-ui --yes when the resource should be visible in the web app.",
319
+ "Run forge generate.",
320
+ "Run forge verify --strict.",
321
+ ],
322
+ },
323
+ {
324
+ title: "Apply a feature blueprint",
325
+ steps: [
326
+ "Write a JSON blueprint under .forge/blueprints.",
327
+ "Run forge feature validate <blueprint> --json.",
328
+ "Run forge feature plan <blueprint>.",
329
+ "Review the plan, impact, and risk.",
330
+ "Run forge feature apply <blueprint> --yes.",
331
+ "Run forge verify --strict.",
332
+ ],
333
+ },
334
+ {
335
+ title: "Safely refactor a feature",
336
+ steps: [
337
+ "Run forge refactor rename field <table.field> <table.field> --dry-run --json.",
338
+ "Rename codemods are AST-aware for extract-action, rename field, and rename table.",
339
+ "Field renames are scoped to the target table, so tickets.priority only rewrites references linked to tickets.",
340
+ "Review filesToModify, migrationPlan, diagnostics, and risk.",
341
+ "Use --allow-high-risk only for intentional high-risk refactors.",
342
+ "Apply with forge refactor rename field <table.field> <table.field> --yes.",
343
+ "Run forge generate.",
344
+ "Run forge verify --strict.",
345
+ ],
346
+ },
347
+ {
348
+ title: "Plan impact-based tests",
349
+ steps: [
350
+ "Run forge impact --changed --json.",
351
+ "Run forge test plan --changed --json.",
352
+ "Run forge test run --changed --timeout-ms 120000 --json for targeted checks.",
353
+ "Use forge verify --changed for the fast impact gate.",
354
+ "Run forge verify --strict before final handoff.",
355
+ ],
356
+ },
357
+ {
358
+ title: "Repair a failing check",
359
+ steps: [
360
+ "Run forge test run --changed --json.",
361
+ "Run forge repair diagnose --from-last-test-run --json.",
362
+ "Review the failureKind, likelyCause, suggestedRepairs, and confidence.",
363
+ "Apply only high-confidence repairs automatically.",
364
+ "Run forge verify --changed.",
365
+ "Run forge verify --strict before final handoff.",
366
+ ],
367
+ },
368
+ {
369
+ title: "Add a package",
370
+ steps: [
371
+ "Use forge add <alias>.",
372
+ "Do not install packages manually unless the architecture exception is intentional.",
373
+ "Run forge generate.",
374
+ "Run forge check.",
375
+ ],
376
+ },
377
+ {
378
+ title: "Upgrade a package",
379
+ steps: [
380
+ "Run forge deps upgrade-plan <package> --to latest.",
381
+ "Read .forge/upgrades/.../plan.md.",
382
+ "If risk is high, inspect affected files and generated adapters before applying.",
383
+ "Apply with forge deps upgrade-apply <plan>.",
384
+ "Finish with forge verify --strict.",
385
+ ],
386
+ },
387
+ {
388
+ title: "Debug a policy error",
389
+ steps: [
390
+ "Capture the traceId from the response or frontend.",
391
+ "Run forge telemetry inspect <traceId>.",
392
+ "Run forge policy simulate <policy> --role <role>.",
393
+ ],
394
+ },
395
+ {
396
+ title: "Run dev",
397
+ steps: [
398
+ "Run forge dev for the full local loop: generated checks, API runtime, web app, DB, worker, watch, and startup URLs.",
399
+ "Run forge dev --once --json for a one-shot diagnostic cycle.",
400
+ "Use --api-only, --web-only, --no-watch, or --no-worker only when narrowing the loop intentionally.",
401
+ "When a web app exists, forge dev starts the API runtime and the web dev server together and prints both URLs.",
402
+ "Use generated client and React hooks through web/lib/forge.ts.",
403
+ ],
404
+ },
405
+ {
406
+ title: "Add or update frontend",
407
+ steps: [
408
+ "Run forge make ui --framework vite --dry-run --json when the app does not have a web root.",
409
+ "Use web/lib/forge.ts as the generated client bridge.",
410
+ "Mount ForgeProvider once in the web app provider/layout layer; use devAuth for local development.",
411
+ "Use useQuery, useCommand, and useLiveQuery instead of raw /commands or /queries fetches.",
412
+ "Run forge generate so frontendGraph and agentContract include routes and bindings.",
413
+ "Run forge inspect capabilities --json to confirm UI actions map to runtime capabilities.",
414
+ "Run forge dev --once --json and forge doctor --json.",
415
+ ],
416
+ },
417
+ {
418
+ title: "Self-host",
419
+ steps: [
420
+ "Run forge self-host compose.",
421
+ "Review deploy/.env.example.",
422
+ "Run forge self-host check.",
423
+ ],
424
+ },
425
+ {
426
+ title: "Debug a production stack trace",
427
+ steps: [
428
+ "Run forge release inspect <releaseId> --json.",
429
+ "Run forge release sourcemaps symbolicate --input stacktrace.json --json.",
430
+ "Open the original source file and line from the symbolicated frame.",
431
+ "Use forge telemetry inspect <traceId> --with-release --json when a trace id is available.",
432
+ ],
433
+ },
434
+ ];
435
+ }
436
+
437
+ function extractUserNotes(existing: string | null): string {
438
+ if (!existing) {
439
+ return DEFAULT_USER_NOTES;
440
+ }
441
+ const body = stripDeterministicHeader(existing);
442
+ const start = body.indexOf(AGENTS_USER_START);
443
+ const end = body.indexOf(AGENTS_USER_END);
444
+ if (start === -1 || end === -1 || end < start) {
445
+ return DEFAULT_USER_NOTES;
446
+ }
447
+ return body.slice(start + AGENTS_USER_START.length, end).trim() || DEFAULT_USER_NOTES;
448
+ }
449
+
450
+ function renderList(items: string[], empty = "none"): string {
451
+ if (items.length === 0) {
452
+ return `- ${empty}`;
453
+ }
454
+ return items.map((item) => `- ${item}`).join("\n");
455
+ }
456
+
457
+ function runtimeSummaryFromBinding(binding: AgentFrontendRuntimeBindingInfo): AgentCapabilityMapEntry["runtime"] {
458
+ return {
459
+ kind: binding.kind,
460
+ name: binding.name,
461
+ hook: binding.hook,
462
+ http: binding.http,
463
+ ...(binding.policy ? { policy: binding.policy } : {}),
464
+ tablesRead: binding.tablesRead,
465
+ tablesWritten: binding.tablesWritten,
466
+ emits: binding.emits,
467
+ dependencies: binding.dependencies,
468
+ };
469
+ }
470
+
471
+ function runtimeEntriesWithoutFrontend(contract: AgentContract): AgentCapabilityMapEntry[] {
472
+ const entries: AgentCapabilityMapEntry[] = [];
473
+ for (const commandEntry of contract.commands) {
474
+ if (commandEntry.frontend.routes.length === 0 && commandEntry.frontend.components.length === 0) {
475
+ entries.push({
476
+ id: `runtime:command:${commandEntry.name}`,
477
+ status: "backend-only",
478
+ userAction: `Call command ${commandEntry.name}`,
479
+ runtime: {
480
+ kind: "command",
481
+ name: commandEntry.name,
482
+ hook: commandEntry.frontend.hook,
483
+ http: commandEntry.http,
484
+ ...(commandEntry.policy ? { policy: commandEntry.policy } : {}),
485
+ tablesRead: commandEntry.tablesRead,
486
+ tablesWritten: commandEntry.tablesWritten,
487
+ emits: commandEntry.emits,
488
+ dependencies: [],
489
+ },
490
+ notes: ["Runtime entry is available to agents even though no frontend usage was detected."],
491
+ });
492
+ }
493
+ }
494
+ for (const queryEntry of contract.queries) {
495
+ if (queryEntry.frontend.routes.length === 0 && queryEntry.frontend.components.length === 0) {
496
+ entries.push({
497
+ id: `runtime:query:${queryEntry.name}`,
498
+ status: "backend-only",
499
+ userAction: `Read query ${queryEntry.name}`,
500
+ runtime: {
501
+ kind: "query",
502
+ name: queryEntry.name,
503
+ hook: queryEntry.frontend.hook,
504
+ http: queryEntry.http,
505
+ ...(queryEntry.policy ? { policy: queryEntry.policy } : {}),
506
+ tablesRead: queryEntry.tablesRead,
507
+ tablesWritten: [],
508
+ emits: [],
509
+ dependencies: [],
510
+ },
511
+ notes: ["Runtime entry is available to agents even though no frontend usage was detected."],
512
+ });
513
+ }
514
+ }
515
+ for (const liveQueryEntry of contract.liveQueries) {
516
+ if (liveQueryEntry.frontend.routes.length === 0 && liveQueryEntry.frontend.components.length === 0) {
517
+ entries.push({
518
+ id: `runtime:liveQuery:${liveQueryEntry.name}`,
519
+ status: "backend-only",
520
+ userAction: `Subscribe to liveQuery ${liveQueryEntry.name}`,
521
+ runtime: {
522
+ kind: "liveQuery",
523
+ name: liveQueryEntry.name,
524
+ hook: liveQueryEntry.frontend.hook,
525
+ http: liveQueryEntry.http,
526
+ ...(liveQueryEntry.policy ? { policy: liveQueryEntry.policy } : {}),
527
+ tablesRead: liveQueryEntry.tablesRead,
528
+ tablesWritten: [],
529
+ emits: [],
530
+ dependencies: liveQueryEntry.dependencies,
531
+ },
532
+ notes: ["Runtime entry is available to agents even though no frontend usage was detected."],
533
+ });
534
+ }
535
+ }
536
+ return entries;
537
+ }
538
+
539
+ function buildCapabilityMap(contract: AgentContract): AgentCapabilityMap {
540
+ const diagnostics: Diagnostic[] = [];
541
+ const coveredEntries: AgentCapabilityMapEntry[] = contract.frontend.routeBindings.map((binding) => ({
542
+ id: `ui:${binding.route ?? "route"}:${binding.kind}:${binding.name}:${binding.file}`,
543
+ status: "covered",
544
+ userAction: `${binding.route ?? "route"} uses ${binding.kind} ${binding.name}`,
545
+ ui: {
546
+ ...(binding.route ? { route: binding.route } : {}),
547
+ ...(binding.component ? { component: binding.component } : {}),
548
+ file: binding.file,
549
+ },
550
+ runtime: runtimeSummaryFromBinding(binding),
551
+ notes: ["Frontend route is connected to a generated Forge runtime hook."],
552
+ }));
553
+
554
+ const componentOnlyEntries = contract.frontend.componentBindings
555
+ .filter((binding) => !contract.frontend.routeBindings.some(
556
+ (routeBinding) =>
557
+ routeBinding.kind === binding.kind &&
558
+ routeBinding.name === binding.name &&
559
+ routeBinding.file === binding.file,
560
+ ))
561
+ .map((binding) => ({
562
+ id: `component:${binding.component ?? "component"}:${binding.kind}:${binding.name}:${binding.file}`,
563
+ status: "covered" as const,
564
+ userAction: `${binding.component ?? "component"} uses ${binding.kind} ${binding.name}`,
565
+ ui: {
566
+ ...(binding.component ? { component: binding.component } : {}),
567
+ file: binding.file,
568
+ },
569
+ runtime: runtimeSummaryFromBinding(binding),
570
+ notes: ["Frontend component is connected to a generated Forge runtime hook."],
571
+ }));
572
+
573
+ const rawFetchEntries: AgentCapabilityMapEntry[] = contract.frontend.clientBindings
574
+ .filter((binding) => binding.kind === "rawFetch")
575
+ .map((binding) => {
576
+ diagnostics.push(createDiagnostic({
577
+ severity: "warning",
578
+ code: "FORGE_CAPABILITY_RAW_RUNTIME_FETCH",
579
+ message: "frontend uses a raw Forge runtime endpoint instead of generated hooks",
580
+ file: binding.file,
581
+ fixHint: "Replace raw runtime fetches with useCommand, useQuery, or useLiveQuery through the local Forge bridge.",
582
+ suggestedCommands: ["forge do connect-ui --json", "forge inspect capabilities --json"],
583
+ docs: ["src/forge/_generated/capabilityMap.md", "src/forge/_generated/frontendGraph.json"],
584
+ }));
585
+ return {
586
+ id: `raw:${binding.file}:${binding.name}`,
587
+ status: "warning",
588
+ userAction: `Raw runtime fetch ${binding.name}`,
589
+ ui: {
590
+ ...(binding.route ? { route: binding.route } : {}),
591
+ ...(binding.component ? { component: binding.component } : {}),
592
+ file: binding.file,
593
+ },
594
+ notes: ["Raw runtime fetch detected; generated hook parity is not proven."],
595
+ };
596
+ });
597
+
598
+ const boundRoutes = new Set(contract.frontend.routeBindings.map((binding) => binding.route).filter(Boolean));
599
+ const routeOnlyEntries: AgentCapabilityMapEntry[] = contract.frontend.routes
600
+ .filter((route) => !boundRoutes.has(route.path))
601
+ .map((route) => ({
602
+ id: `route:${route.path}:${route.file}`,
603
+ status: "frontend-only",
604
+ userAction: `View route ${route.path}`,
605
+ ui: {
606
+ route: route.path,
607
+ file: route.file,
608
+ },
609
+ notes: ["Route has no detected Forge runtime binding. This is fine for static pages, but agents cannot infer a data/action capability from it."],
610
+ }));
611
+
612
+ const entries = sorted(
613
+ [
614
+ ...coveredEntries,
615
+ ...componentOnlyEntries,
616
+ ...runtimeEntriesWithoutFrontend(contract),
617
+ ...rawFetchEntries,
618
+ ...routeOnlyEntries,
619
+ ],
620
+ (entry) => entry.id,
621
+ );
622
+ return {
623
+ schemaVersion: "0.1.0",
624
+ generatorVersion: GENERATOR_VERSION,
625
+ project: contract.project,
626
+ summary: {
627
+ covered: entries.filter((entry) => entry.status === "covered").length,
628
+ backendOnly: entries.filter((entry) => entry.status === "backend-only").length,
629
+ frontendOnly: entries.filter((entry) => entry.status === "frontend-only").length,
630
+ warnings: entries.filter((entry) => entry.status === "warning").length,
631
+ },
632
+ entries,
633
+ diagnostics,
634
+ };
635
+ }
636
+
637
+ function jsAccess(group: string, name: string): string {
638
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name)
639
+ ? `api.${group}.${name}`
640
+ : `api.${group}[${JSON.stringify(name)}]`;
641
+ }
642
+
643
+ function frontendHookFor(kind: "command" | "query" | "liveQuery" | "action", name: string): string {
644
+ if (kind === "command") {
645
+ return `useCommand(${jsAccess("commands", name)})`;
646
+ }
647
+ if (kind === "query") {
648
+ return `useQuery(${jsAccess("queries", name)}, args)`;
649
+ }
650
+ if (kind === "liveQuery") {
651
+ return `useLiveQuery(${jsAccess("liveQueries", name)}, args)`;
652
+ }
653
+ return "no generated React hook; invoke from server/action code";
654
+ }
655
+
656
+ function httpEndpointFor(
657
+ kind: "command" | "query" | "liveQuery" | "action",
658
+ name: string,
659
+ ): AgentHttpEndpointInfo {
660
+ const encoded = encodeURIComponent(name);
661
+ if (kind === "liveQuery") {
662
+ return {
663
+ method: "GET",
664
+ path: `/live/${encoded}`,
665
+ exampleUrl: `/live/${encoded}?args={}`,
666
+ };
667
+ }
668
+ const collection = kind === "action" ? "actions" : kind === "query" ? "queries" : "commands";
669
+ return {
670
+ method: "POST",
671
+ path: `/${collection}/${encoded}`,
672
+ exampleBody: { args: {} },
673
+ };
674
+ }
675
+
676
+ function frontendUsageFor(
677
+ frontendGraph: FrontendGraph,
678
+ kind: "command" | "query" | "liveQuery" | "action",
679
+ name: string,
680
+ ): AgentFrontendUsageInfo {
681
+ if (kind === "action") {
682
+ return {
683
+ hook: frontendHookFor(kind, name),
684
+ routes: [],
685
+ components: [],
686
+ };
687
+ }
688
+ const bindings = frontendGraph.clientBindings.filter(
689
+ (binding) => binding.kind === kind && binding.name === name,
690
+ );
691
+ return {
692
+ hook: frontendHookFor(kind, name),
693
+ routes: uniqueSorted(bindings.map((binding) => binding.route ?? "")),
694
+ components: uniqueSorted(bindings.map((binding) => binding.component ?? "")),
695
+ };
696
+ }
697
+
698
+ function frontendRuntimeBindingFor(
699
+ binding: FrontendGraph["clientBindings"][number],
700
+ entries: {
701
+ commands: AgentContract["commands"];
702
+ queries: AgentContract["queries"];
703
+ liveQueries: AgentContract["liveQueries"];
704
+ },
705
+ ): AgentFrontendRuntimeBindingInfo | null {
706
+ if (binding.kind === "rawFetch") {
707
+ return null;
708
+ }
709
+ if (binding.kind === "command") {
710
+ const entry = entries.commands.find((candidate) => candidate.name === binding.name);
711
+ if (!entry) return null;
712
+ return {
713
+ kind: "command",
714
+ name: binding.name,
715
+ file: binding.file,
716
+ ...(binding.route ? { route: binding.route } : {}),
717
+ ...(binding.component ? { component: binding.component } : {}),
718
+ hook: entry.frontend.hook,
719
+ http: entry.http,
720
+ ...(entry.policy ? { policy: entry.policy } : {}),
721
+ tablesRead: entry.tablesRead,
722
+ tablesWritten: entry.tablesWritten,
723
+ emits: entry.emits,
724
+ dependencies: [],
725
+ };
726
+ }
727
+ if (binding.kind === "query") {
728
+ const entry = entries.queries.find((candidate) => candidate.name === binding.name);
729
+ if (!entry) return null;
730
+ return {
731
+ kind: "query",
732
+ name: binding.name,
733
+ file: binding.file,
734
+ ...(binding.route ? { route: binding.route } : {}),
735
+ ...(binding.component ? { component: binding.component } : {}),
736
+ hook: entry.frontend.hook,
737
+ http: entry.http,
738
+ ...(entry.policy ? { policy: entry.policy } : {}),
739
+ tablesRead: entry.tablesRead,
740
+ tablesWritten: [],
741
+ emits: [],
742
+ dependencies: [],
743
+ };
744
+ }
745
+ const entry = entries.liveQueries.find((candidate) => candidate.name === binding.name);
746
+ if (!entry) return null;
747
+ return {
748
+ kind: "liveQuery",
749
+ name: binding.name,
750
+ file: binding.file,
751
+ ...(binding.route ? { route: binding.route } : {}),
752
+ ...(binding.component ? { component: binding.component } : {}),
753
+ hook: entry.frontend.hook,
754
+ http: entry.http,
755
+ ...(entry.policy ? { policy: entry.policy } : {}),
756
+ tablesRead: entry.tablesRead,
757
+ tablesWritten: [],
758
+ emits: [],
759
+ dependencies: entry.dependencies,
760
+ };
761
+ }
762
+
763
+ function frontendRuntimeBindings(
764
+ frontendGraph: FrontendGraph,
765
+ entries: {
766
+ commands: AgentContract["commands"];
767
+ queries: AgentContract["queries"];
768
+ liveQueries: AgentContract["liveQueries"];
769
+ },
770
+ ): AgentFrontendRuntimeBindingInfo[] {
771
+ return uniqueSorted(
772
+ frontendGraph.clientBindings
773
+ .map((binding) => frontendRuntimeBindingFor(binding, entries))
774
+ .filter((binding): binding is AgentFrontendRuntimeBindingInfo => binding !== null)
775
+ .map((binding) => JSON.stringify(binding)),
776
+ ).map((binding) => JSON.parse(binding) as AgentFrontendRuntimeBindingInfo);
777
+ }
778
+
779
+ export function buildAgentContractArtifacts(
780
+ input: AgentContractInput,
781
+ ): AgentContractArtifacts {
782
+ const project = readPackageInfo(input.workspaceRoot);
783
+ const tenantTables = new Map(
784
+ input.tenantScope.tables.map((table) => [table.table, table.tenantIdColumn]),
785
+ );
786
+ const commandAuth = new Map(
787
+ input.policyRegistry.commandAuth.map((binding) => [binding.commandName, binding.auth]),
788
+ );
789
+ const queryAuth = new Map(
790
+ input.policyRegistry.queryAuth.map((binding) => [binding.queryName, binding.auth]),
791
+ );
792
+ const liveQueryPolicy = new Map(
793
+ input.liveQueryRegistry.liveQueries.map((entry) => [entry.name, entry.policy]),
794
+ );
795
+
796
+ const runtimeEntries = new Map(input.runtimeGraph.entries.map((entry) => [entry.name, entry]));
797
+ const tableNames = new Set(input.dataGraph.tables.map((table) => table.name));
798
+ const commandInfos: AgentContract["commands"] = sorted(Object.keys(input.apiSurface.commands), (name) => name).map((name) => {
799
+ const entry = runtimeEntries.get(name);
800
+ const file = entry?.file ?? "";
801
+ return {
802
+ name,
803
+ file,
804
+ policy: authPolicy(commandAuth.get(name)),
805
+ tablesRead: dbTablesForFile(input.workspaceRoot, file, tableNames, DB_READ_OPS),
806
+ tablesWritten: dbTablesForFile(input.workspaceRoot, file, tableNames, DB_WRITE_OPS),
807
+ emits: emittedEventsForFile(input.workspaceRoot, file),
808
+ allowedPackages: entry ? packageNamesForModule(input.appGraph, entry.moduleId) : [],
809
+ forbiddenCapabilities: forbiddenForContext(input.classified, "command"),
810
+ http: httpEndpointFor("command", name),
811
+ frontend: frontendUsageFor(input.frontendGraph, "command", name),
812
+ };
813
+ });
814
+ const queryInfos: AgentContract["queries"] = sorted(input.queryRegistry.queries, (query) => query.name).map((query) => ({
815
+ name: query.name,
816
+ file: query.file,
817
+ policy: authPolicy(queryAuth.get(query.name)),
818
+ readOnly: true,
819
+ tenantScoped: input.tenantScope.tables.length > 0,
820
+ tablesRead: dbTablesForFile(input.workspaceRoot, query.file, tableNames, DB_READ_OPS),
821
+ allowedPackages: packageNamesForModule(input.appGraph, query.moduleId),
822
+ forbiddenCapabilities: forbiddenForContext(input.classified, "query"),
823
+ http: httpEndpointFor("query", query.name),
824
+ frontend: frontendUsageFor(input.frontendGraph, "query", query.name),
825
+ }));
826
+ const liveQueryInfos: AgentContract["liveQueries"] = sorted(input.liveQueryRegistry.liveQueries, (liveQuery) => liveQuery.name).map(
827
+ (liveQuery) => {
828
+ const tablesRead = dbTablesForFile(input.workspaceRoot, liveQuery.file, tableNames, DB_READ_OPS);
829
+ return {
830
+ name: liveQuery.name,
831
+ file: liveQuery.file,
832
+ policy: liveQueryPolicy.get(liveQuery.name),
833
+ tablesRead,
834
+ dependencies: (tablesRead.length > 0 ? tablesRead : input.tenantScope.tables.map((table) => table.table)).map((tableName) => ({
835
+ table: tableName,
836
+ scope: tenantTables.has(tableName) ? "tenant" as const : "global" as const,
837
+ })),
838
+ allowedPackages: packageNamesForModule(input.appGraph, liveQuery.moduleId),
839
+ forbiddenCapabilities: forbiddenForContext(input.classified, "liveQuery"),
840
+ http: httpEndpointFor("liveQuery", liveQuery.name),
841
+ frontend: frontendUsageFor(input.frontendGraph, "liveQuery", liveQuery.name),
842
+ };
843
+ },
844
+ );
845
+ const fullStackBindings = frontendRuntimeBindings(input.frontendGraph, {
846
+ commands: commandInfos,
847
+ queries: queryInfos,
848
+ liveQueries: liveQueryInfos,
849
+ });
850
+ const contract: AgentContract = {
851
+ schemaVersion: "0.1.0",
852
+ generatorVersion: GENERATOR_VERSION,
853
+ project: {
854
+ name: project.name,
855
+ type: "forgeos-app",
856
+ ...(project.template ? { template: project.template } : {}),
857
+ },
858
+ commands: commandInfos,
859
+ queries: queryInfos,
860
+ liveQueries: liveQueryInfos,
861
+ actions: sorted(
862
+ input.runtimeGraph.entries.filter((entry) => entry.kind === "action"),
863
+ (entry) => entry.name,
864
+ ).map((entry) => ({
865
+ name: entry.name,
866
+ file: entry.file,
867
+ allowedPackages: packageNamesForModule(input.appGraph, entry.moduleId),
868
+ forbiddenCapabilities: [],
869
+ allowedCapabilities: ["network", "secrets", "ai", "db"],
870
+ http: httpEndpointFor("action", entry.name),
871
+ frontend: frontendUsageFor(input.frontendGraph, "action", entry.name),
872
+ })),
873
+ workflows: sorted(input.workflowRegistry.workflows, (workflow) => workflow.name).map(
874
+ (workflow) => ({
875
+ name: workflow.name,
876
+ file: workflow.file,
877
+ trigger: workflow.triggerEventType,
878
+ steps: workflow.steps
879
+ .slice()
880
+ .sort((a, b) => a.index - b.index)
881
+ .map((step) => step.name),
882
+ }),
883
+ ),
884
+ data: {
885
+ tables: sorted(input.dataGraph.tables, (table) => table.name).map((table) => ({
886
+ name: table.name,
887
+ file: table.file,
888
+ tenantScoped: tenantTables.has(table.name),
889
+ ...(tenantTables.has(table.name) ? { tenantField: tenantTables.get(table.name) } : {}),
890
+ fields: uniqueSorted(table.fields.map((field) => field.name)),
891
+ })),
892
+ },
893
+ policies: sorted(input.policyRegistry.policies, (policy) => policy.name).map((policy) => ({
894
+ name: policy.name,
895
+ kind: policy.kind,
896
+ roles: uniqueSorted(policy.roles),
897
+ file: policy.file,
898
+ })),
899
+ packages: sorted(input.packageGraph.packages, (pkg) => pkg.name).map((pkg) => {
900
+ const classified = input.classified.find((entry) => entry.api.name === pkg.name);
901
+ return {
902
+ name: pkg.name,
903
+ version: pkg.version,
904
+ allowedContexts: classified?.classification.compatible ?? [],
905
+ deniedContexts: classified?.classification.incompatible ?? [],
906
+ };
907
+ }),
908
+ integrations: buildIntegrations(input.classified),
909
+ secrets: sorted(input.secretRegistry.secrets, (secret) => secret.name).map((secret) => ({
910
+ name: secret.name,
911
+ integration: secret.integration,
912
+ required: secret.required,
913
+ public: secret.public,
914
+ allowedContexts: secret.allowedContexts,
915
+ })),
916
+ telemetry: {
917
+ events: uniqueSorted(input.telemetryRegistry.events.map((event) => event.name)),
918
+ sinks: uniqueSorted(input.telemetrySinks.sinks.map((sink) => sink.kind)),
919
+ },
920
+ ai: {
921
+ providers: uniqueSorted(input.aiRegistry.providers.map((provider) => provider.id)),
922
+ generations: input.aiRegistry.generations
923
+ .map((generation) => ({
924
+ provider: generation.provider,
925
+ model: generation.model,
926
+ method: generation.method,
927
+ file: generation.file,
928
+ ...(generation.purpose ? { purpose: generation.purpose } : {}),
929
+ }))
930
+ .sort((a, b) => `${a.file}:${a.method}:${a.model}`.localeCompare(`${b.file}:${b.method}:${b.model}`)),
931
+ },
932
+ client: {
933
+ queries: input.clientManifest.queries,
934
+ commands: input.clientManifest.commands,
935
+ liveQueries: input.clientManifest.liveQueries,
936
+ reactHooks: input.clientManifest.react.hooks,
937
+ transport: input.clientManifest.transport,
938
+ },
939
+ frontend: {
940
+ present: input.frontendGraph.present,
941
+ framework: input.frontendGraph.framework,
942
+ ...(input.frontendGraph.root ? { root: input.frontendGraph.root } : {}),
943
+ ...(input.frontendGraph.dev ? { dev: input.frontendGraph.dev } : {}),
944
+ routes: input.frontendGraph.routes,
945
+ components: input.frontendGraph.components,
946
+ providers: input.frontendGraph.providers,
947
+ bridgeFiles: input.frontendGraph.bridgeFiles,
948
+ webManifest: input.frontendGraph.webManifest,
949
+ clientBindings: input.frontendGraph.clientBindings,
950
+ runtimeEndpoints: [
951
+ ...sorted(Object.keys(input.apiSurface.commands), (name) => name).map((name) => ({
952
+ kind: "command" as const,
953
+ name,
954
+ http: httpEndpointFor("command", name),
955
+ frontend: frontendUsageFor(input.frontendGraph, "command", name),
956
+ })),
957
+ ...sorted(input.queryRegistry.queries, (query) => query.name).map((query) => ({
958
+ kind: "query" as const,
959
+ name: query.name,
960
+ http: httpEndpointFor("query", query.name),
961
+ frontend: frontendUsageFor(input.frontendGraph, "query", query.name),
962
+ })),
963
+ ...sorted(input.liveQueryRegistry.liveQueries, (liveQuery) => liveQuery.name).map(
964
+ (liveQuery) => ({
965
+ kind: "liveQuery" as const,
966
+ name: liveQuery.name,
967
+ http: httpEndpointFor("liveQuery", liveQuery.name),
968
+ frontend: frontendUsageFor(input.frontendGraph, "liveQuery", liveQuery.name),
969
+ }),
970
+ ),
971
+ ].sort((a, b) => `${a.kind}:${a.name}`.localeCompare(`${b.kind}:${b.name}`)),
972
+ routeBindings: fullStackBindings
973
+ .filter((binding) => binding.route)
974
+ .sort((a, b) => `${a.route}:${a.kind}:${a.name}:${a.file}`.localeCompare(`${b.route}:${b.kind}:${b.name}:${b.file}`)),
975
+ componentBindings: fullStackBindings
976
+ .filter((binding) => binding.component)
977
+ .sort((a, b) => `${a.component}:${a.kind}:${a.name}:${a.file}`.localeCompare(`${b.component}:${b.kind}:${b.name}:${b.file}`)),
978
+ diagnostics: input.frontendGraph.diagnostics,
979
+ },
980
+ auth: {
981
+ modes: ["dev-headers", "jwt", "oidc", "disabled"],
982
+ defaultMode: "dev-headers",
983
+ productionDefaultAllowed: false,
984
+ bearerTokenHeader: "Authorization",
985
+ env: {
986
+ mode: AUTH_ENV.mode,
987
+ issuer: AUTH_ENV.issuer,
988
+ audience: AUTH_ENV.audience,
989
+ jwksUri: AUTH_ENV.jwksUri,
990
+ algorithms: AUTH_ENV.algorithms,
991
+ },
992
+ claims: DEFAULT_AUTH_CLAIMS,
993
+ requiresTenant: input.tenantScope.tables.length > 0,
994
+ },
995
+ deploy: {
996
+ selfHost: true,
997
+ files: [
998
+ "deploy/docker-compose.yml",
999
+ "deploy/.env.example",
1000
+ "deploy/deployManifest.json",
1001
+ ],
1002
+ },
1003
+ rules: runtimeRules(),
1004
+ playbooks: playbooks(),
1005
+ commandsToRun: {
1006
+ beforeEditing: ["forge do inspect --json", "forge dev --once --json", "forge inspect all --json", "forge check --json"],
1007
+ afterEditing: ["forge generate", "forge check", "forge verify --standard", "forge verify --strict"],
1008
+ dev: ["forge dev", "forge dev --once --json", "forge do fix --json", "forge do verify --json", "forge dev --api-only", "forge dev --web-only"],
1009
+ },
1010
+ };
1011
+
1012
+ const existingAgentsPath = join(input.workspaceRoot, "AGENTS.md");
1013
+ const existingAgents = nodeFileSystem.exists(existingAgentsPath)
1014
+ ? (nodeFileSystem.readText(existingAgentsPath) ?? "")
1015
+ : null;
1016
+ const userNotes = extractUserNotes(existingAgents);
1017
+ const agentsMd = renderAgentsMd(contract, userNotes);
1018
+ const capabilityMap = buildCapabilityMap(contract);
1019
+ const capabilityMapMd = renderCapabilityMapMd(capabilityMap);
1020
+ const appMapMd = renderAppMapMd(contract);
1021
+ const runtimeRulesMd = renderRuntimeRulesMd(contract.rules);
1022
+ const operationPlaybooksMd = renderOperationPlaybooksMd(contract.playbooks);
1023
+ const agentQuickstartMd = renderAgentQuickstartMd();
1024
+ const diagnostics = scanAgentContractForLeaks(contract, [
1025
+ agentsMd,
1026
+ capabilityMapMd,
1027
+ appMapMd,
1028
+ runtimeRulesMd,
1029
+ operationPlaybooksMd,
1030
+ agentQuickstartMd,
1031
+ ]);
1032
+
1033
+ return {
1034
+ contract,
1035
+ capabilityMap,
1036
+ agentsMd,
1037
+ appMapMd,
1038
+ capabilityMapMd,
1039
+ runtimeRulesMd,
1040
+ operationPlaybooksMd,
1041
+ agentQuickstartMd,
1042
+ diagnostics: [...diagnostics, ...capabilityMap.diagnostics],
1043
+ };
1044
+ }
1045
+
1046
+ function scanAgentContractForLeaks(contract: AgentContract, markdown: string[]): Diagnostic[] {
1047
+ const serialized = `${canonicalJson(contract)}\n${markdown.join("\n")}`;
1048
+ const scan = secretLeakScan(serialized, { includeHighEntropy: false });
1049
+ if (!scan.hasLeak) {
1050
+ return [];
1051
+ }
1052
+ return [
1053
+ createDiagnostic({
1054
+ severity: "error",
1055
+ code: "FORGE_AGENT_CONTRACT_SECRET_LEAK",
1056
+ message: `agent contract contains secret-like material: ${uniqueSorted(scan.matches).join(", ")}`,
1057
+ }),
1058
+ ];
1059
+ }
1060
+
1061
+ export function serializeAgentContractJson(contract: AgentContract): string {
1062
+ return serializeCanonical(contract);
1063
+ }
1064
+
1065
+ export function serializeAgentContractTs(contract: AgentContract): string {
1066
+ const parsed = JSON.parse(serializeAgentContractJson(contract)) as unknown;
1067
+ return `export const agentContract = ${JSON.stringify(parsed, null, 2)} as const;\n`;
1068
+ }
1069
+
1070
+ export function serializeCapabilityMapJson(capabilityMap: AgentCapabilityMap): string {
1071
+ return serializeCanonical(capabilityMap);
1072
+ }
1073
+
1074
+ export function serializeCapabilityMapTs(capabilityMap: AgentCapabilityMap): string {
1075
+ const parsed = JSON.parse(serializeCapabilityMapJson(capabilityMap)) as unknown;
1076
+ return `export const capabilityMap = ${JSON.stringify(parsed, null, 2)} as const;\n`;
1077
+ }
1078
+
1079
+ function renderAgentsMd(contract: AgentContract, userNotes: string): string {
1080
+ const tenantTables = contract.data.tables
1081
+ .filter((table) => table.tenantScoped)
1082
+ .map((table) => `${table.name} via ${table.tenantField}`);
1083
+ const policies = contract.policies.map((policy) =>
1084
+ `${policy.name}: ${policy.roles.length > 0 ? policy.roles.join(", ") : policy.kind}`,
1085
+ );
1086
+ const secrets = contract.secrets.map((secret) => `${secret.name}${secret.required ? " (required)" : " (optional)"}`);
1087
+
1088
+ return normalizeNewlines(`# AGENTS.md
1089
+
1090
+ <!-- forge-generated:start -->
1091
+
1092
+ ## Project
1093
+
1094
+ This is a ForgeOS application named \`${contract.project.name}\`.
1095
+
1096
+ ## Required workflow
1097
+
1098
+ Before editing:
1099
+
1100
+ \`\`\`bash
1101
+ forge do inspect --json
1102
+ forge dev --once --json
1103
+ forge inspect all --json
1104
+ forge check --json
1105
+ \`\`\`
1106
+
1107
+ After editing:
1108
+
1109
+ \`\`\`bash
1110
+ forge generate
1111
+ forge check
1112
+ forge verify --strict
1113
+ \`\`\`
1114
+
1115
+ ## Do not edit
1116
+
1117
+ Do not:
1118
+
1119
+ - \`src/forge/_generated/**\`
1120
+ - \`forge.lock\`
1121
+ - \`deploy/docker-compose.yml\`, unless changing deployment config intentionally
1122
+
1123
+ Template apps may ignore \`src/forge/_generated/**\` and \`forge.lock\` in git to reduce visual noise. Recreate them with \`forge generate\` before checking, testing, or handing work off.
1124
+
1125
+ ## Runtime model
1126
+
1127
+ - Commands are transactional writes.
1128
+ - Queries and liveQueries are read-only.
1129
+ - Actions perform side effects after commit.
1130
+ - Workflows orchestrate durable steps.
1131
+ - Production liveQuery uses a durable invalidation log; polling/notify are wakeups only.
1132
+ - Production API calls use \`Authorization: Bearer <JWT>\` in \`jwt\` or \`oidc\` auth mode.
1133
+ - \`dev-headers\` auth is for \`forge dev\`, tests, and local agent workflows only.
1134
+ - AI is only allowed in actions, workflows, endpoints, and server code.
1135
+ - Secrets are accessed through \`ctx.secrets\`.
1136
+
1137
+ ## Runtime rules
1138
+
1139
+ - Do not import network packages inside \`command\`, \`query\`, or \`liveQuery\`.
1140
+ - Do not use \`process.env\` directly.
1141
+ - Do not access cross-tenant data.
1142
+ - Commands must use \`ctx.emit\` for side effects.
1143
+ - Actions and workflows handle side effects after commit.
1144
+ - Do not rely on in-memory Pub/Sub as the source of truth for liveQuery invalidation.
1145
+
1146
+ ## Useful commands
1147
+
1148
+ \`\`\`bash
1149
+ forge do "<objective>" --json
1150
+ forge do fix --json
1151
+ forge do verify --json
1152
+ forge dev --once --json
1153
+ forge dev
1154
+ forge inspect app --json
1155
+ forge inspect all --json
1156
+ forge inspect frontend --json
1157
+ forge inspect capabilities --json
1158
+ forge auth check --json
1159
+ forge inspect runtime-matrix --json
1160
+ forge inspect policies --json
1161
+ forge inspect client --json
1162
+ forge inspect live-production --json
1163
+ forge live status --json
1164
+ forge doctor
1165
+ forge doctor windows --json
1166
+ forge setup windows --json
1167
+ forge agent print-context --json
1168
+ forge verify --smoke
1169
+ forge verify --standard
1170
+ forge verify --strict
1171
+ \`\`\`
1172
+
1173
+ ## Data
1174
+
1175
+ Tenant-scoped tables:
1176
+
1177
+ ${renderList(tenantTables)}
1178
+
1179
+ ## Policies
1180
+
1181
+ ${renderList(policies)}
1182
+
1183
+ ## Secrets
1184
+
1185
+ ${renderList(secrets)}
1186
+
1187
+ ## Auth
1188
+
1189
+ - Modes: ${contract.auth.modes.join(", ")}
1190
+ - Production auth: \`jwt\` or \`oidc\`
1191
+ - Bearer header: \`${contract.auth.bearerTokenHeader}: Bearer <token>\`
1192
+ - Tenant claim: \`${contract.auth.claims.tenantId ?? "not configured"}\`
1193
+
1194
+ ## Frontend
1195
+
1196
+ - Present: ${contract.frontend.present ? "yes" : "no"}
1197
+ - Framework: ${contract.frontend.framework}
1198
+ ${contract.frontend.dev ? `- Web URL: ${contract.frontend.dev.url}
1199
+ - API URL env: \`${contract.frontend.dev.apiUrlEnv}\`
1200
+ - Web bridge valid: ${contract.frontend.webManifest.bridge.valid ? "yes" : "no"}
1201
+ - Client bridge: ${contract.frontend.bridgeFiles.length > 0 ? contract.frontend.bridgeFiles.map((file) => `\`${file}\``).join(", ") : "missing"}` : "- Web URL: none"}
1202
+ - Routes: ${contract.frontend.routes.length}
1203
+ - Components: ${contract.frontend.components.length}
1204
+ - Client bindings: ${contract.frontend.clientBindings.length}
1205
+ - Runtime endpoints: ${contract.frontend.runtimeEndpoints.length}
1206
+ - Full-stack route bindings: ${contract.frontend.routeBindings.length}
1207
+
1208
+ Rules:
1209
+
1210
+ - Use the local \`web/**/lib/forge.ts\` bridge to generated hooks.
1211
+ - Mount \`<ForgeProvider devAuth>\` in local development.
1212
+ - Use \`useQuery\`, \`useCommand\`, and \`useLiveQuery\` instead of raw Forge endpoint fetches in React components.
1213
+ - Keep frontend routes reflected in \`src/forge/_generated/frontendGraph.json\`.
1214
+
1215
+ ## Common tasks
1216
+
1217
+ ### Choose the right workflow
1218
+
1219
+ Use:
1220
+
1221
+ \`\`\`bash
1222
+ forge do "<objective>" --json
1223
+ forge do fix --json
1224
+ forge do connect-ui --json
1225
+ forge do verify --json
1226
+ \`\`\`
1227
+
1228
+ \`forge do\` returns intent, plan, filesToInspect, filesToChange, risks, concrete commands, and nextAction. Prefer it before choosing lower-level CLI commands manually.
1229
+
1230
+ ### Add a command
1231
+
1232
+ 1. Add file in \`src/commands\`.
1233
+ 2. Declare \`auth: can("...")\`.
1234
+ 3. Run \`forge generate\`.
1235
+ 4. Run \`forge verify --strict\`.
1236
+
1237
+ ### Scaffold a resource
1238
+
1239
+ Use:
1240
+
1241
+ \`\`\`bash
1242
+ forge make resource <name> --fields title:text,status:enum(open,closed) --dry-run --json
1243
+ forge make resource <name> --fields title:text,status:enum(open,closed) --with-ui --yes
1244
+ forge make ui --framework vite --dry-run --json
1245
+ \`\`\`
1246
+
1247
+ Review the plan before applying when the resource touches schema or policies.
1248
+
1249
+ ### Check frontend wiring
1250
+
1251
+ Use:
1252
+
1253
+ \`\`\`bash
1254
+ forge dev --once --json
1255
+ forge dev
1256
+ forge inspect frontend --json
1257
+ forge inspect capabilities --json
1258
+ \`\`\`
1259
+
1260
+ \`forge dev\` starts the API runtime and web app together when \`web/\` exists. \`forge dev --once --json\` reports routes, components, \`ForgeProvider\`, bridge files, generated client bindings, direct runtime fetch warnings, capability-map parity warnings, and fix hints.
1261
+
1262
+ ### Apply a feature blueprint
1263
+
1264
+ Use:
1265
+
1266
+ \`\`\`bash
1267
+ forge feature validate .forge/blueprints/<name>.json --json
1268
+ forge feature plan .forge/blueprints/<name>.json
1269
+ forge feature apply .forge/blueprints/<name>.json --yes
1270
+ \`\`\`
1271
+
1272
+ Review high-risk plans before applying. Use \`--allow-high-risk\` only when intentional.
1273
+
1274
+ ### Safely refactor a feature
1275
+
1276
+ Use:
1277
+
1278
+ \`\`\`bash
1279
+ forge refactor rename field tickets.priority tickets.urgency --dry-run --json
1280
+ forge refactor rename field tickets.priority tickets.urgency --yes
1281
+ \`\`\`
1282
+
1283
+ These codemods are AST-aware for \`extract-action\`, \`rename field\`, and \`rename table\`. Field renames are scoped to the target table, so \`tickets.priority\` only rewrites references linked to \`tickets\`.
1284
+
1285
+ Never edit \`src/forge/_generated/**\` directly. Review migration hints before applying field or table renames.
1286
+
1287
+ ### Plan impact-based tests
1288
+
1289
+ Use:
1290
+
1291
+ \`\`\`bash
1292
+ forge impact --changed --json
1293
+ forge test plan --changed --json
1294
+ forge test run --changed --timeout-ms 120000 --json
1295
+ forge verify --standard
1296
+ \`\`\`
1297
+
1298
+ Use \`forge verify --standard\` for the normal agent development loop. Finish handoffs with \`forge verify --strict\` when the change is ready.
1299
+
1300
+ ### Repair a failing check
1301
+
1302
+ When a Forge check fails, do not guess. Use:
1303
+
1304
+ \`\`\`bash
1305
+ forge repair diagnose --from-last-test-run --json
1306
+ forge repair plan --from-last-test-run --write
1307
+ \`\`\`
1308
+
1309
+ Apply only high-confidence deterministic repairs automatically. Review medium or low confidence repairs before changing code.
1310
+
1311
+ ### Export agent adapters
1312
+
1313
+ Use:
1314
+
1315
+ \`\`\`bash
1316
+ forge agent export --target generic
1317
+ forge agent export --target codex
1318
+ forge agent export --target cursor
1319
+ forge agent export --target claude
1320
+ \`\`\`
1321
+
1322
+ Adapter files are derived from \`agentContract.json\`, \`appMap.md\`, \`runtimeRules.md\`, \`operationPlaybooks.md\`, and this \`AGENTS.md\`. Do not treat Codex, Cursor, Claude, or custom adapter files as the source of truth.
1323
+
1324
+ ### Add a package
1325
+
1326
+ Use:
1327
+
1328
+ \`\`\`bash
1329
+ forge add <alias>
1330
+ \`\`\`
1331
+
1332
+ Do not install packages manually unless intentional.
1333
+
1334
+ ### Upgrade a package
1335
+
1336
+ Use:
1337
+
1338
+ \`\`\`bash
1339
+ forge deps upgrade-plan <package> --to latest
1340
+ forge deps upgrade-apply <plan>
1341
+ forge verify --strict
1342
+ \`\`\`
1343
+
1344
+ Do not manually edit \`package.json\` for package upgrades unless necessary.
1345
+
1346
+ ### Debug liveQuery
1347
+
1348
+ Use:
1349
+
1350
+ \`\`\`bash
1351
+ forge live status --json
1352
+ forge live invalidations list --json
1353
+ forge live debug <subscriptionId> --json
1354
+ \`\`\`
1355
+
1356
+ Durable invalidations live in \`_forge_live_invalidations\`.
1357
+
1358
+ <!-- forge-generated:end -->
1359
+
1360
+ ${AGENTS_USER_START}
1361
+
1362
+ ${userNotes}
1363
+
1364
+ ${AGENTS_USER_END}
1365
+ `);
1366
+ }
1367
+
1368
+ function renderAppMapMd(contract: AgentContract): string {
1369
+ const lines = ["# App Map", "", "## Data", ""];
1370
+ for (const table of contract.data.tables) {
1371
+ lines.push(`### ${table.name}`, `Tenant-scoped: ${table.tenantScoped ? "yes" : "no"}`);
1372
+ if (table.tenantField) {
1373
+ lines.push(`Tenant field: ${table.tenantField}`);
1374
+ }
1375
+ lines.push("Fields:", ...renderList(table.fields).split("\n"), "");
1376
+ }
1377
+
1378
+ lines.push("## Commands", "");
1379
+ for (const command of contract.commands) {
1380
+ lines.push(
1381
+ `### ${command.name}`,
1382
+ `Policy: ${command.policy ?? "none"}`,
1383
+ `HTTP: ${command.http.method} ${command.http.path}`,
1384
+ `Frontend hook: \`${command.frontend.hook}\``,
1385
+ "Frontend routes:",
1386
+ ...renderList(command.frontend.routes).split("\n"),
1387
+ "Frontend components:",
1388
+ ...renderList(command.frontend.components).split("\n"),
1389
+ "Writes:",
1390
+ ...renderList(command.tablesWritten).split("\n"),
1391
+ "Reads:",
1392
+ ...renderList(command.tablesRead).split("\n"),
1393
+ "Emits:",
1394
+ ...renderList(command.emits).split("\n"),
1395
+ "",
1396
+ );
1397
+ }
1398
+
1399
+ lines.push("## Queries", "");
1400
+ for (const query of contract.queries) {
1401
+ lines.push(
1402
+ `### ${query.name}`,
1403
+ `Policy: ${query.policy ?? "none"}`,
1404
+ `HTTP: ${query.http.method} ${query.http.path}`,
1405
+ `Frontend hook: \`${query.frontend.hook}\``,
1406
+ `Read-only: ${query.readOnly ? "yes" : "no"}`,
1407
+ "Reads:",
1408
+ ...renderList(query.tablesRead).split("\n"),
1409
+ "Frontend routes:",
1410
+ ...renderList(query.frontend.routes).split("\n"),
1411
+ "Frontend components:",
1412
+ ...renderList(query.frontend.components).split("\n"),
1413
+ "",
1414
+ );
1415
+ }
1416
+
1417
+ lines.push("## Live Queries", "");
1418
+ for (const liveQuery of contract.liveQueries) {
1419
+ lines.push(
1420
+ `### ${liveQuery.name}`,
1421
+ `Policy: ${liveQuery.policy ?? "none"}`,
1422
+ `HTTP: ${liveQuery.http.method} ${liveQuery.http.path}`,
1423
+ `Frontend hook: \`${liveQuery.frontend.hook}\``,
1424
+ "Reads:",
1425
+ ...renderList(liveQuery.tablesRead).split("\n"),
1426
+ "Frontend routes:",
1427
+ ...renderList(liveQuery.frontend.routes).split("\n"),
1428
+ "Frontend components:",
1429
+ ...renderList(liveQuery.frontend.components).split("\n"),
1430
+ "Dependencies:",
1431
+ ...renderList(liveQuery.dependencies.map((dep) => `${dep.table} (${dep.scope})`)).split("\n"),
1432
+ "",
1433
+ );
1434
+ }
1435
+
1436
+ lines.push("## Actions", "");
1437
+ for (const action of contract.actions) {
1438
+ lines.push(`### ${action.name}`, `File: ${action.file}`, "");
1439
+ }
1440
+
1441
+ lines.push("## Workflows", "");
1442
+ for (const workflow of contract.workflows) {
1443
+ lines.push(`### ${workflow.name}`, `Trigger: ${workflow.trigger ?? "manual"}`, "Steps:", ...renderList(workflow.steps).split("\n"), "");
1444
+ }
1445
+
1446
+ lines.push("## Frontend", "");
1447
+ lines.push(`Present: ${contract.frontend.present ? "yes" : "no"}`);
1448
+ lines.push(`Framework: ${contract.frontend.framework}`);
1449
+ if (contract.frontend.root) {
1450
+ lines.push(`Root: ${contract.frontend.root}`);
1451
+ }
1452
+ if (contract.frontend.dev) {
1453
+ lines.push(`Dev URL: ${contract.frontend.dev.url}`);
1454
+ lines.push(`API URL env: ${contract.frontend.dev.apiUrlEnv}`);
1455
+ }
1456
+ lines.push("");
1457
+
1458
+ lines.push("### Routes", "");
1459
+ for (const route of contract.frontend.routes) {
1460
+ lines.push(
1461
+ `#### ${route.path}`,
1462
+ `File: ${route.file}`,
1463
+ "Components:",
1464
+ ...renderList(route.components).split("\n"),
1465
+ "Uses commands:",
1466
+ ...renderList(route.usesCommands).split("\n"),
1467
+ "Uses queries:",
1468
+ ...renderList(route.usesQueries).split("\n"),
1469
+ "Uses liveQueries:",
1470
+ ...renderList(route.usesLiveQueries).split("\n"),
1471
+ "Raw Forge fetches:",
1472
+ ...renderList(route.rawForgeFetches).split("\n"),
1473
+ "",
1474
+ );
1475
+ }
1476
+
1477
+ lines.push("### Components", "");
1478
+ for (const component of contract.frontend.components) {
1479
+ lines.push(
1480
+ `#### ${component.name}`,
1481
+ `File: ${component.file}`,
1482
+ "Uses commands:",
1483
+ ...renderList(component.usesCommands).split("\n"),
1484
+ "Uses queries:",
1485
+ ...renderList(component.usesQueries).split("\n"),
1486
+ "Uses liveQueries:",
1487
+ ...renderList(component.usesLiveQueries).split("\n"),
1488
+ "",
1489
+ );
1490
+ }
1491
+
1492
+ lines.push("### Client Bindings", "");
1493
+ for (const binding of contract.frontend.clientBindings) {
1494
+ lines.push(
1495
+ `- ${binding.kind} ${binding.name} in ${binding.file}${binding.route ? ` (route ${binding.route})` : ""}${binding.component ? ` (${binding.component})` : ""}`,
1496
+ );
1497
+ }
1498
+ if (contract.frontend.clientBindings.length === 0) {
1499
+ lines.push("- none");
1500
+ }
1501
+ lines.push("");
1502
+
1503
+ lines.push("### Runtime Endpoints", "");
1504
+ for (const endpoint of contract.frontend.runtimeEndpoints) {
1505
+ lines.push(
1506
+ `- ${endpoint.kind} ${endpoint.name}: ${endpoint.http.method} ${endpoint.http.path}; ${endpoint.frontend.hook}`,
1507
+ );
1508
+ }
1509
+ if (contract.frontend.runtimeEndpoints.length === 0) {
1510
+ lines.push("- none");
1511
+ }
1512
+ lines.push("");
1513
+
1514
+ lines.push("### Full-Stack Route Bindings", "");
1515
+ for (const binding of contract.frontend.routeBindings) {
1516
+ lines.push(
1517
+ `- ${binding.route ?? "unknown route"} -> ${binding.hook} -> ${binding.kind} ${binding.name}`,
1518
+ ` File: ${binding.file}`,
1519
+ ` HTTP: ${binding.http.method} ${binding.http.path}`,
1520
+ ` Policy: ${binding.policy ?? "none"}`,
1521
+ ` Reads: ${binding.tablesRead.length > 0 ? binding.tablesRead.join(", ") : "none"}`,
1522
+ ` Writes: ${binding.tablesWritten.length > 0 ? binding.tablesWritten.join(", ") : "none"}`,
1523
+ ` Emits: ${binding.emits.length > 0 ? binding.emits.join(", ") : "none"}`,
1524
+ );
1525
+ }
1526
+ if (contract.frontend.routeBindings.length === 0) {
1527
+ lines.push("- none");
1528
+ }
1529
+ lines.push("");
1530
+
1531
+ return normalizeNewlines(lines.join("\n"));
1532
+ }
1533
+
1534
+ function renderRuntimeRulesMd(rules: AgentRuntimeRule[]): string {
1535
+ const lines = [
1536
+ "# Runtime Rules",
1537
+ "",
1538
+ "## LiveQuery Production",
1539
+ "",
1540
+ "Allowed:",
1541
+ "- durable invalidation rows in _forge_live_invalidations",
1542
+ "- polling fallback",
1543
+ "- Postgres notify wakeups",
1544
+ "- SSE heartbeats and Last-Event-ID resume",
1545
+ "",
1546
+ "Forbidden:",
1547
+ "- treating Pub/Sub or in-memory notification as the source of truth",
1548
+ "- unbounded snapshot queues",
1549
+ "- cross-tenant invalidation fanout",
1550
+ "",
1551
+ ];
1552
+ for (const rule of rules) {
1553
+ lines.push(`## ${rule.context}`, "", "Allowed:", ...renderList(rule.allowed).split("\n"), "", "Forbidden:", ...renderList(rule.forbidden).split("\n"), "");
1554
+ }
1555
+ return normalizeNewlines(lines.join("\n"));
1556
+ }
1557
+
1558
+ function renderCapabilityMapMd(capabilityMap: AgentCapabilityMap): string {
1559
+ const lines = [
1560
+ "# Capability Map",
1561
+ "",
1562
+ `Project: ${capabilityMap.project.name}`,
1563
+ "",
1564
+ "## Summary",
1565
+ "",
1566
+ `- Covered: ${capabilityMap.summary.covered}`,
1567
+ `- Backend-only: ${capabilityMap.summary.backendOnly}`,
1568
+ `- Frontend-only: ${capabilityMap.summary.frontendOnly}`,
1569
+ `- Warnings: ${capabilityMap.summary.warnings}`,
1570
+ "",
1571
+ "## Capabilities",
1572
+ "",
1573
+ ];
1574
+ for (const entry of capabilityMap.entries) {
1575
+ lines.push(`### ${entry.id}`, `Status: ${entry.status}`, `User action: ${entry.userAction}`);
1576
+ if (entry.ui) {
1577
+ lines.push(`UI file: ${entry.ui.file}`);
1578
+ if (entry.ui.route) lines.push(`Route: ${entry.ui.route}`);
1579
+ if (entry.ui.component) lines.push(`Component: ${entry.ui.component}`);
1580
+ }
1581
+ if (entry.runtime) {
1582
+ lines.push(
1583
+ `Runtime: ${entry.runtime.kind} ${entry.runtime.name}`,
1584
+ `Hook: ${entry.runtime.hook}`,
1585
+ `HTTP: ${entry.runtime.http.method} ${entry.runtime.http.path}`,
1586
+ `Policy: ${entry.runtime.policy ?? "none"}`,
1587
+ `Reads: ${entry.runtime.tablesRead.length > 0 ? entry.runtime.tablesRead.join(", ") : "none"}`,
1588
+ `Writes: ${entry.runtime.tablesWritten.length > 0 ? entry.runtime.tablesWritten.join(", ") : "none"}`,
1589
+ `Emits: ${entry.runtime.emits.length > 0 ? entry.runtime.emits.join(", ") : "none"}`,
1590
+ );
1591
+ }
1592
+ lines.push("Notes:", ...renderList(entry.notes).split("\n"), "");
1593
+ }
1594
+ if (capabilityMap.entries.length === 0) {
1595
+ lines.push("- none", "");
1596
+ }
1597
+ if (capabilityMap.diagnostics.length > 0) {
1598
+ lines.push("## Diagnostics", "");
1599
+ for (const diagnostic of capabilityMap.diagnostics) {
1600
+ lines.push(`- ${diagnostic.severity} ${diagnostic.code}: ${diagnostic.message}`);
1601
+ }
1602
+ }
1603
+ return normalizeNewlines(lines.join("\n"));
1604
+ }
1605
+
1606
+ function renderOperationPlaybooksMd(playbookEntries: AgentPlaybook[]): string {
1607
+ const lines = ["# Operation Playbooks", ""];
1608
+ for (const playbook of playbookEntries) {
1609
+ lines.push(`## ${playbook.title}`, "");
1610
+ for (let index = 0; index < playbook.steps.length; index++) {
1611
+ lines.push(`${index + 1}. ${playbook.steps[index]}`);
1612
+ }
1613
+ lines.push("");
1614
+ }
1615
+ return normalizeNewlines(lines.join("\n"));
1616
+ }
1617
+
1618
+ function renderAgentQuickstartMd(): string {
1619
+ return normalizeNewlines(`# Agent Quickstart
1620
+
1621
+ Run:
1622
+
1623
+ \`\`\`bash
1624
+ forge do inspect --json
1625
+ forge do fix --json
1626
+ forge do verify --json
1627
+ forge dev --once --json
1628
+ forge dev
1629
+ forge inspect all --json
1630
+ forge inspect frontend --json
1631
+ forge inspect capabilities --json
1632
+ forge check --json
1633
+ \`\`\`
1634
+
1635
+ Never edit:
1636
+
1637
+ \`\`\`txt
1638
+ src/forge/_generated/**
1639
+ forge.lock
1640
+ \`\`\`
1641
+
1642
+ If generated files are ignored by git, recreate them with \`forge generate\`.
1643
+
1644
+ Always finish with:
1645
+
1646
+ \`\`\`bash
1647
+ forge generate
1648
+ forge verify --strict
1649
+ \`\`\`
1650
+ `);
1651
+ }