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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (598) hide show
  1. package/.agents/skills/forge-agent-fabric/SKILL.md +178 -0
  2. package/.agents/skills/forge-agent-fabric/references/managed-workflows.md +95 -0
  3. package/.agents/skills/forge-agent-fabric/references/repository-analysis.md +102 -0
  4. package/.agents/skills/forge-agent-fabric/scripts/fabric.mjs +46 -0
  5. package/.agents/skills/forge-agent-fabric/scripts/repository.mjs +44 -0
  6. package/.npmignore +10 -0
  7. package/AGENTS.md +142 -90
  8. package/CHANGELOG.md +870 -9
  9. package/LICENSE +21 -0
  10. package/README.md +121 -6
  11. package/adapters/go/README.md +23 -0
  12. package/adapters/go/go.mod +3 -0
  13. package/adapters/go/http.go +149 -0
  14. package/adapters/go/registry.go +234 -0
  15. package/adapters/go/types.go +136 -0
  16. package/adapters/java/README.md +68 -0
  17. package/adapters/java/pom.xml +47 -0
  18. package/adapters/java/src/main/java/dev/forgeos/adapter/Auth.java +20 -0
  19. package/adapters/java/src/main/java/dev/forgeos/adapter/Diagnostic.java +16 -0
  20. package/adapters/java/src/main/java/dev/forgeos/adapter/Entry.java +38 -0
  21. package/adapters/java/src/main/java/dev/forgeos/adapter/EntryKind.java +16 -0
  22. package/adapters/java/src/main/java/dev/forgeos/adapter/ErrorInfo.java +4 -0
  23. package/adapters/java/src/main/java/dev/forgeos/adapter/Forge.java +94 -0
  24. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeCall.java +12 -0
  25. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeContext.java +11 -0
  26. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeHandler.java +8 -0
  27. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeHttpHandler.java +179 -0
  28. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeRegistry.java +121 -0
  29. package/adapters/java/src/main/java/dev/forgeos/adapter/Json.java +14 -0
  30. package/adapters/java/src/main/java/dev/forgeos/adapter/Manifest.java +14 -0
  31. package/adapters/java/src/main/java/dev/forgeos/adapter/RequestEnvelope.java +6 -0
  32. package/adapters/java/src/main/java/dev/forgeos/adapter/ResponseEnvelope.java +25 -0
  33. package/adapters/java/src/main/java/dev/forgeos/adapter/Risk.java +18 -0
  34. package/adapters/java/src/main/java/dev/forgeos/adapter/Schemas.java +36 -0
  35. package/adapters/java/src/main/java/dev/forgeos/adapter/Service.java +65 -0
  36. package/adapters/java/src/main/java/dev/forgeos/adapter/TransactionMode.java +18 -0
  37. package/adapters/java/src/main/java/dev/forgeos/adapter/TypedForgeHandler.java +6 -0
  38. package/adapters/java-spring-boot-starter/README.md +32 -0
  39. package/adapters/java-spring-boot-starter/pom.xml +49 -0
  40. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeCommand.java +22 -0
  41. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeExternalService.java +15 -0
  42. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeQuery.java +16 -0
  43. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeServiceBeanCondition.java +18 -0
  44. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeSpringAutoConfiguration.java +16 -0
  45. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeSpringRuntime.java +104 -0
  46. package/adapters/java-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +1 -0
  47. package/bin/forge.mjs +18 -0
  48. package/docs/agent-fabric-programs.md +151 -0
  49. package/docs/agent-fabric.md +582 -0
  50. package/docs/architecture/agent-fabric/PORTABLE_PROJECTS_PLAN.md +136 -0
  51. package/docs/architecture/agent-fabric/README.md +180 -0
  52. package/docs/architecture/agent-fabric/REPOSITORY_MANIFEST_PLAN.md +284 -0
  53. package/docs/architecture/agent-fabric/WORKFLOW_PROGRAM_V2_ADR.md +81 -0
  54. package/docs/architecture/agent-fabric/WORKFLOW_PROGRAM_V2_CORRECTIONS.md +39 -0
  55. package/docs/architecture/agent-fabric/WORKFLOW_PROGRAM_V2_CORRECTIONS_EVIDENCE.md +53 -0
  56. package/docs/architecture/agent-fabric/WORKFLOW_PROGRAM_V2_EVIDENCE.md +49 -0
  57. package/docs/architecture/agent-fabric/WORKFLOW_PROGRAM_V2_PLAN.md +590 -0
  58. package/docs/cair-protocol.md +103 -0
  59. package/docs/changelog.md +1070 -0
  60. package/docs/forge-protocol.md +189 -0
  61. package/docs/repository-agent-benchmark.md +179 -0
  62. package/docs/repository-analysis.md +139 -0
  63. package/docs/repository-context-quality.md +179 -0
  64. package/docs/repository-runtime-observation.md +82 -0
  65. package/examples/agent-fabric-v2/README.md +70 -0
  66. package/examples/agent-fabric-v2/item-child.workflow.ts +11 -0
  67. package/examples/agent-fabric-v2/make-request.mjs +4 -0
  68. package/examples/agent-fabric-v2/map-child.workflow.ts +10 -0
  69. package/examples/agent-fabric-v2/migrate.workflow.ts +35 -0
  70. package/examples/agent-fabric-v2/registry.example.json +256 -0
  71. package/examples/agent-fabric-v2/run-ui-fixture.ts +48 -0
  72. package/examples/agent-fabric-v2/ui-audit.workflow.ts +28 -0
  73. package/examples/agent-fabric-v2/ui-fixture-worker.cjs +36 -0
  74. package/examples/agent-fabric-v2/visual-population.json +13 -0
  75. package/examples/agent-fabric-v2/workers/check.mjs +2 -0
  76. package/examples/agent-fabric-v2/workers/common.mjs +7 -0
  77. package/examples/agent-fabric-v2/workers/discover.mjs +2 -0
  78. package/examples/agent-fabric-v2/workers/migrate.mjs +4 -0
  79. package/examples/agent-fabric-v2/workers/review.mjs +3 -0
  80. package/examples/go-billing/go.mod +7 -0
  81. package/examples/go-billing/main.go +120 -0
  82. package/examples/java-billing/pom.xml +60 -0
  83. package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/CreateInvoiceInput.java +4 -0
  84. package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/Invoice.java +11 -0
  85. package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/Main.java +127 -0
  86. package/package.json +60 -5
  87. package/schemas/agent-fabric/v0.1/control-event.schema.json +281 -0
  88. package/schemas/forge-manifest.schema.json +389 -0
  89. package/scripts/field-test-forgeos.mjs +1021 -0
  90. package/scripts/install-agent-fabric-skill.mjs +105 -0
  91. package/src/forge/_generated/releaseManifest.json +1 -2
  92. package/src/forge/_generated/releaseManifest.ts +3 -3
  93. package/src/forge/agent-adapters/index.ts +1592 -125
  94. package/src/forge/agent-adapters/types.ts +225 -1
  95. package/src/forge/agent-fabric/adapter.ts +131 -0
  96. package/src/forge/agent-fabric/attached-snapshot.ts +55 -0
  97. package/src/forge/agent-fabric/attached-task-contract.ts +105 -0
  98. package/src/forge/agent-fabric/attached-task-service.ts +127 -0
  99. package/src/forge/agent-fabric/attached-task-store.ts +109 -0
  100. package/src/forge/agent-fabric/authority.ts +298 -0
  101. package/src/forge/agent-fabric/canonical.ts +135 -0
  102. package/src/forge/agent-fabric/codex-adversarial-review.ts +229 -0
  103. package/src/forge/agent-fabric/codex-sdk-worker.ts +203 -0
  104. package/src/forge/agent-fabric/conductor.ts +754 -0
  105. package/src/forge/agent-fabric/dictionary.ts +12 -0
  106. package/src/forge/agent-fabric/errors.ts +32 -0
  107. package/src/forge/agent-fabric/hardened-conductor.ts +485 -0
  108. package/src/forge/agent-fabric/hardened-reducer.ts +211 -0
  109. package/src/forge/agent-fabric/index.ts +48 -0
  110. package/src/forge/agent-fabric/journal.ts +116 -0
  111. package/src/forge/agent-fabric/local-adaptive-digest-worker.mjs +26 -0
  112. package/src/forge/agent-fabric/local-adaptive-harness.ts +343 -0
  113. package/src/forge/agent-fabric/local-adaptive-service.ts +398 -0
  114. package/src/forge/agent-fabric/local-adaptive-worker.ts +146 -0
  115. package/src/forge/agent-fabric/local-approval-window.ts +211 -0
  116. package/src/forge/agent-fabric/local-change-review-service.ts +374 -0
  117. package/src/forge/agent-fabric/local-coding-worker.ts +285 -0
  118. package/src/forge/agent-fabric/local-control-store.ts +239 -0
  119. package/src/forge/agent-fabric/local-effect-broker.ts +348 -0
  120. package/src/forge/agent-fabric/local-evolution-approval.ts +81 -0
  121. package/src/forge/agent-fabric/local-evolution-profile.ts +87 -0
  122. package/src/forge/agent-fabric/local-evolution-registry.ts +427 -0
  123. package/src/forge/agent-fabric/local-evolution-service.ts +200 -0
  124. package/src/forge/agent-fabric/local-intelligence.ts +281 -0
  125. package/src/forge/agent-fabric/local-paths.ts +18 -0
  126. package/src/forge/agent-fabric/local-task-contract.ts +203 -0
  127. package/src/forge/agent-fabric/local-task-inbox.ts +543 -0
  128. package/src/forge/agent-fabric/local-task-server.ts +374 -0
  129. package/src/forge/agent-fabric/local-task-service.ts +982 -0
  130. package/src/forge/agent-fabric/local-verification.ts +551 -0
  131. package/src/forge/agent-fabric/managed-environment.ts +205 -0
  132. package/src/forge/agent-fabric/managed-run-contract.ts +103 -0
  133. package/src/forge/agent-fabric/managed-run-service.ts +401 -0
  134. package/src/forge/agent-fabric/managed-run-store.ts +165 -0
  135. package/src/forge/agent-fabric/managed-workspace.ts +253 -0
  136. package/src/forge/agent-fabric/p0a.ts +104 -0
  137. package/src/forge/agent-fabric/p0b-model-adapter.ts +434 -0
  138. package/src/forge/agent-fabric/planning.ts +155 -0
  139. package/src/forge/agent-fabric/program-api.ts +37 -0
  140. package/src/forge/agent-fabric/program-contract.ts +283 -0
  141. package/src/forge/agent-fabric/program-dsl.ts +126 -0
  142. package/src/forge/agent-fabric/program-evidence.ts +22 -0
  143. package/src/forge/agent-fabric/program-image.ts +15 -0
  144. package/src/forge/agent-fabric/program-lock.ts +50 -0
  145. package/src/forge/agent-fabric/program-owner.ts +16 -0
  146. package/src/forge/agent-fabric/program-scheduler.ts +38 -0
  147. package/src/forge/agent-fabric/program-service.ts +793 -0
  148. package/src/forge/agent-fabric/program-store.ts +142 -0
  149. package/src/forge/agent-fabric/program-structure.ts +65 -0
  150. package/src/forge/agent-fabric/program-types.ts +110 -0
  151. package/src/forge/agent-fabric/program-view.ts +12 -0
  152. package/src/forge/agent-fabric/program-worker.ts +108 -0
  153. package/src/forge/agent-fabric/project-profile.ts +50 -0
  154. package/src/forge/agent-fabric/project-registry.ts +120 -0
  155. package/src/forge/agent-fabric/project-runtime.ts +85 -0
  156. package/src/forge/agent-fabric/readiness.ts +26 -0
  157. package/src/forge/agent-fabric/reducer.ts +863 -0
  158. package/src/forge/agent-fabric/repository-context.ts +83 -0
  159. package/src/forge/agent-fabric/resource-ledger.ts +266 -0
  160. package/src/forge/agent-fabric/serialized-local-adapter.ts +60 -0
  161. package/src/forge/agent-fabric/types.ts +424 -0
  162. package/src/forge/agent-fabric/validation.ts +500 -0
  163. package/src/forge/agent-fabric/workflow-engine.ts +218 -0
  164. package/src/forge/agent-fabric/workflow-task-actions.ts +104 -0
  165. package/src/forge/agent-fabric/workflow-templates.ts +11 -0
  166. package/src/forge/agent-memory/bridge.ts +1728 -0
  167. package/src/forge/agent-memory/context-pack.ts +284 -0
  168. package/src/forge/agent-memory/hook-runner.ts +331 -0
  169. package/src/forge/agent-memory/mcp.ts +597 -0
  170. package/src/forge/agent-memory/normalize.ts +498 -0
  171. package/src/forge/agent-memory/redaction.ts +103 -0
  172. package/src/forge/agent-memory/sources/claude-code.ts +51 -0
  173. package/src/forge/agent-memory/sources/codex-hook-runner.mjs +343 -0
  174. package/src/forge/agent-memory/sources/codex.ts +161 -0
  175. package/src/forge/agent-memory/sources/cursor.ts +35 -0
  176. package/src/forge/agent-memory/types.ts +215 -0
  177. package/src/forge/bench.ts +248 -0
  178. package/src/forge/brownfield-import/index.ts +896 -0
  179. package/src/forge/brownfield-import/types.ts +127 -0
  180. package/src/forge/cair/action-journal.ts +61 -0
  181. package/src/forge/cair/action-parser.ts +314 -0
  182. package/src/forge/cair/action-validator.ts +40 -0
  183. package/src/forge/cair/actions.ts +1818 -0
  184. package/src/forge/cair/format.ts +77 -0
  185. package/src/forge/cair/index.ts +118 -0
  186. package/src/forge/cair/query.ts +478 -0
  187. package/src/forge/cair/snapshot.ts +315 -0
  188. package/src/forge/cair/types.ts +255 -0
  189. package/src/forge/cli/adaptive.ts +51 -0
  190. package/src/forge/cli/ai.ts +16 -1
  191. package/src/forge/cli/auth.ts +459 -15
  192. package/src/forge/cli/authmd.ts +441 -0
  193. package/src/forge/cli/baseline.ts +112 -0
  194. package/src/forge/cli/build.ts +20 -4
  195. package/src/forge/cli/changed.ts +418 -0
  196. package/src/forge/cli/codex-app-server.ts +877 -0
  197. package/src/forge/cli/commands.ts +1859 -20
  198. package/src/forge/cli/db.ts +209 -2
  199. package/src/forge/cli/deploy.ts +1339 -0
  200. package/src/forge/cli/deps.ts +150 -23
  201. package/src/forge/cli/dev.ts +1137 -53
  202. package/src/forge/cli/docs.ts +265 -0
  203. package/src/forge/cli/doctor.ts +297 -0
  204. package/src/forge/cli/env-placeholders.ts +62 -0
  205. package/src/forge/cli/evolution.ts +40 -0
  206. package/src/forge/cli/fabric.ts +253 -0
  207. package/src/forge/cli/field-test.ts +568 -0
  208. package/src/forge/cli/golden-path.ts +487 -0
  209. package/src/forge/cli/handoff.ts +336 -0
  210. package/src/forge/cli/index.ts +1 -0
  211. package/src/forge/cli/last-run.ts +84 -0
  212. package/src/forge/cli/main.ts +196 -3
  213. package/src/forge/cli/new.ts +169 -17
  214. package/src/forge/cli/next-actions.ts +23 -0
  215. package/src/forge/cli/output.ts +398 -6
  216. package/src/forge/cli/parse.ts +1377 -38
  217. package/src/forge/cli/progress.ts +51 -0
  218. package/src/forge/cli/query.ts +32 -0
  219. package/src/forge/cli/release.ts +35 -11
  220. package/src/forge/cli/repository.ts +151 -0
  221. package/src/forge/cli/run.ts +42 -0
  222. package/src/forge/cli/secrets.ts +137 -1
  223. package/src/forge/cli/seed.ts +581 -0
  224. package/src/forge/cli/self-host.ts +56 -14
  225. package/src/forge/cli/studio.ts +2282 -0
  226. package/src/forge/cli/verify.ts +1458 -32
  227. package/src/forge/cli/windows.ts +23 -0
  228. package/src/forge/cli/workos.ts +3509 -0
  229. package/src/forge/compiler/agent-contract/build.ts +728 -64
  230. package/src/forge/compiler/agent-contract/types.ts +45 -2
  231. package/src/forge/compiler/ai-registry/parse.ts +71 -2
  232. package/src/forge/compiler/api-surface/build.ts +47 -0
  233. package/src/forge/compiler/app-graph/build.ts +112 -9
  234. package/src/forge/compiler/app-graph/extract.ts +107 -0
  235. package/src/forge/compiler/app-graph/module-graph.ts +73 -78
  236. package/src/forge/compiler/app-graph/parser.ts +24 -24
  237. package/src/forge/compiler/app-graph/profile.ts +26 -0
  238. package/src/forge/compiler/app-graph/tsconfig-hash.ts +41 -4
  239. package/src/forge/compiler/app-graph/versions.ts +1 -1
  240. package/src/forge/compiler/classifier/capabilities.ts +3 -2
  241. package/src/forge/compiler/classifier/classify.ts +32 -8
  242. package/src/forge/compiler/classifier/secrets.ts +3 -2
  243. package/src/forge/compiler/classifier/signals.ts +91 -1
  244. package/src/forge/compiler/client-sdk/build-manifest.ts +59 -0
  245. package/src/forge/compiler/client-sdk/render-client.ts +295 -13
  246. package/src/forge/compiler/data-graph/build.ts +29 -1
  247. package/src/forge/compiler/data-graph/parse.ts +20 -4
  248. package/src/forge/compiler/data-graph/sql/ddl.ts +144 -31
  249. package/src/forge/compiler/data-graph/sql/serialize.ts +4 -0
  250. package/src/forge/compiler/data-graph/sql/types.ts +1 -0
  251. package/src/forge/compiler/diagnostics/codes.ts +38 -0
  252. package/src/forge/compiler/diagnostics/create.ts +28 -2
  253. package/src/forge/compiler/diagnostics/index.ts +2 -0
  254. package/src/forge/compiler/emitter/barrel.ts +3 -0
  255. package/src/forge/compiler/emitter/render.ts +9 -0
  256. package/src/forge/compiler/external-manifest/registry.ts +217 -0
  257. package/src/forge/compiler/external-manifest/types.ts +91 -0
  258. package/src/forge/compiler/external-manifest/validate.ts +373 -0
  259. package/src/forge/compiler/frontend-graph/build.ts +142 -14
  260. package/src/forge/compiler/integration/add.ts +991 -33
  261. package/src/forge/compiler/integration/plan.ts +43 -12
  262. package/src/forge/compiler/integration/render.ts +29 -0
  263. package/src/forge/compiler/integration/snapshot.ts +2 -0
  264. package/src/forge/compiler/integration/templates/convex.ts +70 -0
  265. package/src/forge/compiler/integration/templates/index.ts +1 -0
  266. package/src/forge/compiler/integration/templates/render.ts +38 -0
  267. package/src/forge/compiler/integration/templates/types.ts +3 -0
  268. package/src/forge/compiler/integration/templates/workos.ts +1439 -0
  269. package/src/forge/compiler/make-registry/build.ts +8 -9
  270. package/src/forge/compiler/orchestrator/fast-check.ts +4 -0
  271. package/src/forge/compiler/orchestrator/generate-lock.ts +14 -3
  272. package/src/forge/compiler/orchestrator/manifest.ts +1 -1
  273. package/src/forge/compiler/orchestrator/plan-profile.ts +23 -0
  274. package/src/forge/compiler/orchestrator/plan.ts +249 -10
  275. package/src/forge/compiler/orchestrator/profile.ts +65 -0
  276. package/src/forge/compiler/orchestrator/run.ts +101 -33
  277. package/src/forge/compiler/orchestrator/serialize.ts +33 -8
  278. package/src/forge/compiler/orchestrator/types.ts +1 -1
  279. package/src/forge/compiler/package-graph/compiler.ts +3 -1
  280. package/src/forge/compiler/package-graph/constants.ts +1 -1
  281. package/src/forge/compiler/package-graph/dts-extractor.ts +14 -1
  282. package/src/forge/compiler/package-manager/adapter.ts +4 -1
  283. package/src/forge/compiler/package-manager/commands.ts +4 -0
  284. package/src/forge/compiler/package-manager/executor.ts +30 -1
  285. package/src/forge/compiler/package-manager/version.ts +36 -12
  286. package/src/forge/compiler/package-upgrades/planner.ts +155 -18
  287. package/src/forge/compiler/package-upgrades/types.ts +2 -0
  288. package/src/forge/compiler/policy-registry/build.ts +47 -2
  289. package/src/forge/compiler/policy-registry/parse.ts +32 -2
  290. package/src/forge/compiler/recipes/definitions.ts +63 -0
  291. package/src/forge/compiler/recipes/index.ts +2 -0
  292. package/src/forge/compiler/recipes/profiles.ts +81 -0
  293. package/src/forge/compiler/recipes/registry.ts +6 -0
  294. package/src/forge/compiler/test-graph/build.ts +11 -3
  295. package/src/forge/compiler/types/app-graph.ts +8 -2
  296. package/src/forge/compiler/types/cli.ts +76 -1
  297. package/src/forge/compiler/types/dev-manifest.ts +4 -0
  298. package/src/forge/compiler/types/emit.ts +2 -0
  299. package/src/forge/compiler/types/frontend-graph.ts +2 -2
  300. package/src/forge/compiler/types/integration.ts +1 -0
  301. package/src/forge/compiler/types/package-graph.ts +6 -0
  302. package/src/forge/compiler/types/policy-registry.ts +3 -1
  303. package/src/forge/delta/broker-runner.mjs +5 -0
  304. package/src/forge/delta/broker.ts +429 -0
  305. package/src/forge/delta/classifier.ts +52 -0
  306. package/src/forge/delta/explain.ts +238 -0
  307. package/src/forge/delta/git-observer.ts +67 -0
  308. package/src/forge/delta/ids.ts +44 -0
  309. package/src/forge/delta/index.ts +25 -0
  310. package/src/forge/delta/process-identity.ts +37 -0
  311. package/src/forge/delta/queue-status.ts +190 -0
  312. package/src/forge/delta/recorder.ts +540 -0
  313. package/src/forge/delta/redaction.ts +50 -0
  314. package/src/forge/delta/schema.ts +240 -0
  315. package/src/forge/delta/session.ts +142 -0
  316. package/src/forge/delta/status.ts +1139 -0
  317. package/src/forge/delta/store.ts +3329 -0
  318. package/src/forge/delta/timeline.ts +178 -0
  319. package/src/forge/dev/server.ts +1061 -22
  320. package/src/forge/dev/types.ts +15 -1
  321. package/src/forge/dev/watch.ts +17 -7
  322. package/src/forge/dev-console/cycle.ts +285 -33
  323. package/src/forge/dev-console/types.ts +47 -1
  324. package/src/forge/impact/index.ts +222 -30
  325. package/src/forge/impact/types.ts +33 -1
  326. package/src/forge/intent/index.ts +35 -16
  327. package/src/forge/make/fields.ts +26 -0
  328. package/src/forge/make/index.ts +42 -6
  329. package/src/forge/make/templates.ts +211 -4
  330. package/src/forge/make/types.ts +2 -1
  331. package/src/forge/policy.ts +1 -1
  332. package/src/forge/react/index.ts +160 -2
  333. package/src/forge/refactor/index.ts +1 -0
  334. package/src/forge/repair/rules/index.ts +2 -2
  335. package/src/forge/repository-analysis/adapters/common.ts +72 -0
  336. package/src/forge/repository-analysis/adapters/docker.ts +185 -0
  337. package/src/forge/repository-analysis/adapters/java.ts +133 -0
  338. package/src/forge/repository-analysis/adapters/typescript.ts +350 -0
  339. package/src/forge/repository-analysis/adapters/vue.ts +71 -0
  340. package/src/forge/repository-analysis/analyze.ts +434 -0
  341. package/src/forge/repository-analysis/benchmark.ts +336 -0
  342. package/src/forge/repository-analysis/cache-gc.ts +154 -0
  343. package/src/forge/repository-analysis/check-selection.ts +123 -0
  344. package/src/forge/repository-analysis/context-node.ts +17 -0
  345. package/src/forge/repository-analysis/context.ts +456 -0
  346. package/src/forge/repository-analysis/http-resolution.ts +135 -0
  347. package/src/forge/repository-analysis/identity.ts +45 -0
  348. package/src/forge/repository-analysis/index.ts +18 -0
  349. package/src/forge/repository-analysis/nuxt-resolution.ts +188 -0
  350. package/src/forge/repository-analysis/quality.ts +60 -0
  351. package/src/forge/repository-analysis/retrieval.ts +309 -0
  352. package/src/forge/repository-analysis/runtime-artifacts.ts +195 -0
  353. package/src/forge/repository-analysis/runtime-observation.ts +325 -0
  354. package/src/forge/repository-analysis/scanner.ts +174 -0
  355. package/src/forge/repository-analysis/storage.ts +193 -0
  356. package/src/forge/repository-analysis/test-associations.ts +59 -0
  357. package/src/forge/repository-analysis/types.ts +99 -0
  358. package/src/forge/repository-manifest/index.ts +178 -0
  359. package/src/forge/repository-manifest/types.ts +63 -0
  360. package/src/forge/review/index.ts +162 -12
  361. package/src/forge/review/types.ts +15 -0
  362. package/src/forge/runtime/ai/providers.ts +19 -4
  363. package/src/forge/runtime/auth/config.ts +17 -0
  364. package/src/forge/runtime/auth/evaluate.ts +15 -2
  365. package/src/forge/runtime/auth/resolve.ts +61 -6
  366. package/src/forge/runtime/auth/types.ts +1 -0
  367. package/src/forge/runtime/db/factory.ts +1 -3
  368. package/src/forge/runtime/db/generated-client.ts +13 -2
  369. package/src/forge/runtime/db/memory-adapter.ts +139 -32
  370. package/src/forge/runtime/db/pglite-adapter.ts +285 -2
  371. package/src/forge/runtime/executor.ts +129 -16
  372. package/src/forge/runtime/external/bridge.ts +663 -0
  373. package/src/forge/runtime/runner/run-entry.ts +16 -7
  374. package/src/forge/runtime/telemetry/buffer.ts +4 -1
  375. package/src/forge/runtime/telemetry/scrubber.ts +35 -5
  376. package/src/forge/runtime/webhooks/security.ts +12 -7
  377. package/src/forge/server.ts +71 -7
  378. package/src/forge/ui/index.ts +831 -19
  379. package/src/forge/ui/types.ts +3 -1
  380. package/src/forge/version.ts +1 -1
  381. package/src/forge/vue/index.ts +422 -0
  382. package/src/forge/workspace/baseline.ts +112 -0
  383. package/src/forge/workspace/change-summary.ts +292 -0
  384. package/src/forge/workspace/forge-cli.ts +42 -0
  385. package/src/forge/workspace/git-summary.ts +393 -0
  386. package/templates/agent-workroom/AGENTS.md +29 -0
  387. package/templates/agent-workroom/README.md +34 -0
  388. package/templates/agent-workroom/forge.config.ts +3 -0
  389. package/templates/agent-workroom/package.json +34 -0
  390. package/templates/agent-workroom/pnpm-workspace.yaml +2 -0
  391. package/templates/agent-workroom/src/actions/indexAgentSignal.ts +10 -0
  392. package/templates/agent-workroom/src/commands/openWorkroom.ts +61 -0
  393. package/templates/agent-workroom/src/commands/recordAgentSignal.ts +119 -0
  394. package/templates/agent-workroom/src/commands/recordCheckRun.ts +52 -0
  395. package/templates/agent-workroom/src/forge/schema.ts +54 -0
  396. package/templates/agent-workroom/src/policies.ts +6 -0
  397. package/templates/agent-workroom/src/queries/listWorkrooms.ts +11 -0
  398. package/templates/agent-workroom/src/queries/liveWorkroom.ts +63 -0
  399. package/templates/agent-workroom/tsconfig.json +16 -0
  400. package/templates/agent-workroom/web/index.html +13 -0
  401. package/templates/agent-workroom/web/package.json +21 -0
  402. package/templates/agent-workroom/web/src/App.tsx +345 -0
  403. package/templates/agent-workroom/web/src/lib/forge.ts +20 -0
  404. package/templates/agent-workroom/web/src/main.tsx +13 -0
  405. package/templates/agent-workroom/web/src/styles.css +545 -0
  406. package/templates/agent-workroom/web/tsconfig.json +27 -0
  407. package/templates/agent-workroom/web/vite.config.ts +36 -0
  408. package/templates/b2b-support-web/package.json +2 -0
  409. package/templates/b2b-support-web/pnpm-workspace.yaml +2 -0
  410. package/templates/b2b-support-web/web/app/page.tsx +2 -2
  411. package/templates/b2b-support-web/web/lib/forge.ts +9 -2
  412. package/templates/b2b-support-web/web/next.config.ts +24 -0
  413. package/templates/b2b-support-web/web/package.json +1 -1
  414. package/templates/minimal-web/package.json +2 -1
  415. package/templates/minimal-web/pnpm-workspace.yaml +2 -0
  416. package/templates/minimal-web/web/index.html +1 -0
  417. package/templates/minimal-web/web/src/App.tsx +12 -6
  418. package/templates/minimal-web/web/src/lib/forge.ts +9 -2
  419. package/templates/minimal-web/web/src/styles.css +34 -0
  420. package/templates/minimal-web/web/vite.config.ts +36 -0
  421. package/templates/nuxt-web/.vscode/settings.json +14 -0
  422. package/templates/nuxt-web/.yarnrc.yml +1 -0
  423. package/templates/nuxt-web/README.md +30 -0
  424. package/templates/nuxt-web/forge.config.ts +3 -0
  425. package/templates/nuxt-web/package.json +37 -0
  426. package/templates/nuxt-web/pnpm-workspace.yaml +2 -0
  427. package/templates/nuxt-web/src/actions/logNoteCreated.ts +11 -0
  428. package/templates/nuxt-web/src/commands/createNote.ts +26 -0
  429. package/templates/nuxt-web/src/forge/schema.ts +12 -0
  430. package/templates/nuxt-web/src/policies.ts +6 -0
  431. package/templates/nuxt-web/src/queries/listNotes.ts +8 -0
  432. package/templates/nuxt-web/src/queries/liveNotes.ts +8 -0
  433. package/templates/nuxt-web/tsconfig.json +17 -0
  434. package/templates/nuxt-web/web/app.vue +67 -0
  435. package/templates/nuxt-web/web/components/LiveNotes.vue +89 -0
  436. package/templates/nuxt-web/web/components/NoteComposer.vue +100 -0
  437. package/templates/nuxt-web/web/composables/forge.ts +13 -0
  438. package/templates/nuxt-web/web/composables/useNotes.ts +24 -0
  439. package/templates/nuxt-web/web/nuxt.config.ts +11 -0
  440. package/templates/nuxt-web/web/package.json +22 -0
  441. package/templates/nuxt-web/web/plugins/forge.client.ts +10 -0
  442. package/templates/nuxt-web/web/plugins/forge.server.ts +10 -0
  443. package/templates/nuxt-web/web/server/api/forge-health.get.ts +7 -0
  444. package/templates/nuxt-web/web/tsconfig.json +6 -0
  445. package/templates/vendor-access/.vscode/settings.json +14 -0
  446. package/templates/vendor-access/README.md +30 -0
  447. package/templates/vendor-access/forge.config.ts +3 -0
  448. package/templates/vendor-access/package.json +35 -0
  449. package/templates/vendor-access/pnpm-workspace.yaml +2 -0
  450. package/templates/vendor-access/src/commands/addEvidence.ts +42 -0
  451. package/templates/vendor-access/src/commands/approveAccessRequest.ts +43 -0
  452. package/templates/vendor-access/src/commands/createAccessRequest.ts +49 -0
  453. package/templates/vendor-access/src/commands/seedVendorAccessDemo.ts +200 -0
  454. package/templates/vendor-access/src/forge/schema.ts +74 -0
  455. package/templates/vendor-access/src/policies.ts +11 -0
  456. package/templates/vendor-access/src/queries/listVendorAccessDashboard.ts +24 -0
  457. package/templates/vendor-access/src/queries/liveVendorAccessDashboard.ts +24 -0
  458. package/templates/vendor-access/tsconfig.json +17 -0
  459. package/templates/vendor-access/web/index.html +13 -0
  460. package/templates/vendor-access/web/package.json +21 -0
  461. package/templates/vendor-access/web/src/App.tsx +573 -0
  462. package/templates/vendor-access/web/src/lib/forge.ts +20 -0
  463. package/templates/vendor-access/web/src/main.tsx +205 -0
  464. package/templates/vendor-access/web/src/styles.css +682 -0
  465. package/templates/vendor-access/web/tsconfig.json +18 -0
  466. package/templates/vendor-access/web/vite.config.ts +36 -0
  467. package/src/forge/_generated/actionSubscriptions.json +0 -2
  468. package/src/forge/_generated/actionSubscriptions.ts +0 -10
  469. package/src/forge/_generated/agentAdapterManifest.json +0 -2
  470. package/src/forge/_generated/agentAdapterManifest.ts +0 -73
  471. package/src/forge/_generated/agentContract.json +0 -2
  472. package/src/forge/_generated/agentContract.ts +0 -7829
  473. package/src/forge/_generated/agentQuickstart.md +0 -34
  474. package/src/forge/_generated/agentTools.json +0 -2
  475. package/src/forge/_generated/agentTools.md +0 -16
  476. package/src/forge/_generated/agentTools.ts +0 -12
  477. package/src/forge/_generated/aiContext.ts +0 -125
  478. package/src/forge/_generated/aiModels.json +0 -2
  479. package/src/forge/_generated/aiModels.ts +0 -51
  480. package/src/forge/_generated/aiProviders.json +0 -2
  481. package/src/forge/_generated/aiProviders.ts +0 -23
  482. package/src/forge/_generated/aiRegistry.json +0 -2
  483. package/src/forge/_generated/aiRegistry.ts +0 -31
  484. package/src/forge/_generated/api.json +0 -2
  485. package/src/forge/_generated/api.ts +0 -8
  486. package/src/forge/_generated/appGraph.json +0 -2
  487. package/src/forge/_generated/appGraph.ts +0 -14937
  488. package/src/forge/_generated/appMap.md +0 -55
  489. package/src/forge/_generated/artifactManifest.json +0 -2
  490. package/src/forge/_generated/artifactManifest.ts +0 -7
  491. package/src/forge/_generated/authClaims.json +0 -2
  492. package/src/forge/_generated/authClaims.ts +0 -13
  493. package/src/forge/_generated/authConfig.json +0 -2
  494. package/src/forge/_generated/authConfig.ts +0 -17
  495. package/src/forge/_generated/authContext.ts +0 -23
  496. package/src/forge/_generated/authRegistry.json +0 -2
  497. package/src/forge/_generated/authRegistry.ts +0 -25
  498. package/src/forge/_generated/buildInfo.json +0 -2
  499. package/src/forge/_generated/buildInfo.ts +0 -9
  500. package/src/forge/_generated/capabilityMap.json +0 -2
  501. package/src/forge/_generated/capabilityMap.md +0 -15
  502. package/src/forge/_generated/capabilityMap.ts +0 -17
  503. package/src/forge/_generated/client.ts +0 -282
  504. package/src/forge/_generated/clientApi.ts +0 -9
  505. package/src/forge/_generated/clientManifest.json +0 -2
  506. package/src/forge/_generated/clientManifest.ts +0 -39
  507. package/src/forge/_generated/clientTypes.ts +0 -78
  508. package/src/forge/_generated/configRegistry.json +0 -2
  509. package/src/forge/_generated/configRegistry.ts +0 -4
  510. package/src/forge/_generated/dataGraph.json +0 -2
  511. package/src/forge/_generated/dataGraph.ts +0 -8
  512. package/src/forge/_generated/db.json +0 -2
  513. package/src/forge/_generated/db.ts +0 -2
  514. package/src/forge/_generated/dbSecurityManifest.json +0 -2
  515. package/src/forge/_generated/dbSecurityManifest.ts +0 -15
  516. package/src/forge/_generated/dbSessionContext.json +0 -2
  517. package/src/forge/_generated/dbSessionContext.ts +0 -39
  518. package/src/forge/_generated/deployManifest.json +0 -2
  519. package/src/forge/_generated/deployManifest.ts +0 -14
  520. package/src/forge/_generated/devManifest.json +0 -2
  521. package/src/forge/_generated/devManifest.ts +0 -62
  522. package/src/forge/_generated/envSchema.json +0 -2
  523. package/src/forge/_generated/envSchema.ts +0 -59
  524. package/src/forge/_generated/frontendGraph.json +0 -2
  525. package/src/forge/_generated/frontendGraph.ts +0 -27
  526. package/src/forge/_generated/importGuards.json +0 -2
  527. package/src/forge/_generated/importGuards.ts +0 -686
  528. package/src/forge/_generated/index.ts +0 -68
  529. package/src/forge/_generated/liveProductionManifest.json +0 -2
  530. package/src/forge/_generated/liveProductionManifest.ts +0 -23
  531. package/src/forge/_generated/liveProtocol.json +0 -2
  532. package/src/forge/_generated/liveProtocol.ts +0 -21
  533. package/src/forge/_generated/liveQueryRegistry.json +0 -2
  534. package/src/forge/_generated/liveQueryRegistry.ts +0 -9
  535. package/src/forge/_generated/liveTransportConfig.json +0 -2
  536. package/src/forge/_generated/liveTransportConfig.ts +0 -19
  537. package/src/forge/_generated/makeRegistry.json +0 -2
  538. package/src/forge/_generated/makeRegistry.ts +0 -177
  539. package/src/forge/_generated/makeTemplates.json +0 -2
  540. package/src/forge/_generated/makeTemplates.ts +0 -66
  541. package/src/forge/_generated/mockMap.json +0 -2
  542. package/src/forge/_generated/mockMap.ts +0 -7
  543. package/src/forge/_generated/operationPlaybooks.md +0 -167
  544. package/src/forge/_generated/packageGraph.json +0 -2
  545. package/src/forge/_generated/packageGraph.ts +0 -249334
  546. package/src/forge/_generated/packageUpgradeRegistry.json +0 -2
  547. package/src/forge/_generated/packageUpgradeRegistry.ts +0 -15
  548. package/src/forge/_generated/permissionMatrix.json +0 -2
  549. package/src/forge/_generated/permissionMatrix.ts +0 -7
  550. package/src/forge/_generated/policyRegistry.json +0 -2
  551. package/src/forge/_generated/policyRegistry.ts +0 -11
  552. package/src/forge/_generated/queryRegistry.json +0 -2
  553. package/src/forge/_generated/queryRegistry.ts +0 -9
  554. package/src/forge/_generated/react.d.ts +0 -22
  555. package/src/forge/_generated/react.ts +0 -29
  556. package/src/forge/_generated/reactManifest.json +0 -2
  557. package/src/forge/_generated/reactManifest.ts +0 -19
  558. package/src/forge/_generated/rlsPolicies.json +0 -2
  559. package/src/forge/_generated/rlsPolicies.sql +0 -34
  560. package/src/forge/_generated/rlsPolicies.ts +0 -6
  561. package/src/forge/_generated/runtimeGraph.json +0 -2
  562. package/src/forge/_generated/runtimeGraph.ts +0 -8
  563. package/src/forge/_generated/runtimeMatrix.json +0 -2
  564. package/src/forge/_generated/runtimeMatrix.ts +0 -334130
  565. package/src/forge/_generated/runtimeRegistry.ts +0 -2
  566. package/src/forge/_generated/runtimeRules.md +0 -91
  567. package/src/forge/_generated/secretRegistry.json +0 -2
  568. package/src/forge/_generated/secretRegistry.ts +0 -50
  569. package/src/forge/_generated/secretsContext.ts +0 -11
  570. package/src/forge/_generated/serverApi.ts +0 -10
  571. package/src/forge/_generated/sourceMapManifest.json +0 -2
  572. package/src/forge/_generated/sourceMapManifest.ts +0 -7
  573. package/src/forge/_generated/sqlPlan.json +0 -2
  574. package/src/forge/_generated/sqlPlan.ts +0 -88
  575. package/src/forge/_generated/subscriptionManifest.json +0 -2
  576. package/src/forge/_generated/subscriptionManifest.ts +0 -7
  577. package/src/forge/_generated/symbolicationManifest.json +0 -2
  578. package/src/forge/_generated/symbolicationManifest.ts +0 -17
  579. package/src/forge/_generated/telemetryRegistry.json +0 -2
  580. package/src/forge/_generated/telemetryRegistry.ts +0 -9
  581. package/src/forge/_generated/telemetrySinks.json +0 -2
  582. package/src/forge/_generated/telemetrySinks.ts +0 -11
  583. package/src/forge/_generated/tenantScope.json +0 -2
  584. package/src/forge/_generated/tenantScope.ts +0 -8
  585. package/src/forge/_generated/testGraph.json +0 -2
  586. package/src/forge/_generated/testGraph.ts +0 -3452
  587. package/src/forge/_generated/testPlanRegistry.json +0 -2
  588. package/src/forge/_generated/testPlanRegistry.ts +0 -33
  589. package/src/forge/_generated/uiRoutes.json +0 -2
  590. package/src/forge/_generated/uiRoutes.ts +0 -16
  591. package/src/forge/_generated/uiScenarios.json +0 -2
  592. package/src/forge/_generated/uiScenarios.ts +0 -30
  593. package/src/forge/_generated/uiTestManifest.json +0 -2
  594. package/src/forge/_generated/uiTestManifest.ts +0 -27
  595. package/src/forge/_generated/workflowRegistry.json +0 -2
  596. package/src/forge/_generated/workflowRegistry.ts +0 -9
  597. package/src/forge/_generated/workflowSubscriptions.json +0 -2
  598. package/src/forge/_generated/workflowSubscriptions.ts +0 -10
@@ -0,0 +1,1439 @@
1
+ import type { IntegrationTemplateInput } from "./types.ts";
2
+ import { buildDataGraph } from "../../data-graph/build.ts";
3
+
4
+ const SECRETS_IMPORT = 'import type { SecretsContext } from "../secretsContext.js";';
5
+ const CONFIG_IMPORT = 'import type { ConfigContext } from "../secretsContext.js";';
6
+
7
+ const FALLBACK_PERMISSIONS = [
8
+ "onboarding:read",
9
+ "projects:manage",
10
+ "members:manage",
11
+ "invitations:create",
12
+ "tasks:manage",
13
+ "tasks:update",
14
+ ] as const;
15
+
16
+ const FALLBACK_RESOURCE_TYPES = [
17
+ { slug: "organization", name: "Organization" },
18
+ { slug: "project", name: "Project", parent: "organization" },
19
+ { slug: "team", name: "Team", parent: "organization" },
20
+ { slug: "taskGroup", name: "Task Group", parent: "project" },
21
+ { slug: "task", name: "Task", parent: "project" },
22
+ ] as const;
23
+
24
+ function quotedValues(text: string): string[] {
25
+ return [...text.matchAll(/["']([^"']+)["']/g)].map((match) => match[1]!).filter(Boolean);
26
+ }
27
+
28
+ function uniqueSorted(values: Iterable<string>): string[] {
29
+ return [...new Set([...values].filter(Boolean))].sort();
30
+ }
31
+
32
+ function toTitle(value: string): string {
33
+ return value
34
+ .replace(/[:._-]+/g, " ")
35
+ .replace(/([a-z])([A-Z])/g, "$1 $2")
36
+ .split(/\s+/)
37
+ .filter(Boolean)
38
+ .map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`)
39
+ .join(" ");
40
+ }
41
+
42
+ function policyNameForPermission(permission: string): string {
43
+ return permission.replace(/:/g, ".").replace(/[^a-zA-Z0-9_.-]+/g, ".");
44
+ }
45
+
46
+ function singularResourceName(name: string): string {
47
+ if (name.endsWith("ies")) return `${name.slice(0, -3)}y`;
48
+ if (name.endsWith("ses")) return name.slice(0, -2);
49
+ if (name.endsWith("s") && name.length > 1) return name.slice(0, -1);
50
+ return name;
51
+ }
52
+
53
+ function idFieldForResource(slug: string): string {
54
+ return `${slug.charAt(0).toLowerCase()}${slug.slice(1)}Id`;
55
+ }
56
+
57
+ function permissionsFromApp(input: IntegrationTemplateInput): string[] {
58
+ const permissions = new Set<string>();
59
+ for (const symbol of input.appGraph?.symbols ?? []) {
60
+ const sourceSlice = typeof symbol.meta.sourceSlice === "string" ? symbol.meta.sourceSlice : "";
61
+ for (const match of sourceSlice.matchAll(/canPermission\s*\(([^)]*)\)/g)) {
62
+ for (const value of quotedValues(match[1] ?? "")) {
63
+ permissions.add(value);
64
+ }
65
+ }
66
+ }
67
+ return permissions.size > 0 ? uniqueSorted(permissions) : [...FALLBACK_PERMISSIONS];
68
+ }
69
+
70
+ function resourceTypesFromApp(input: IntegrationTemplateInput): Array<{ slug: string; name: string; parent?: string }> {
71
+ if (!input.appGraph) {
72
+ return [...FALLBACK_RESOURCE_TYPES];
73
+ }
74
+ const dataGraph = buildDataGraph(input.appGraph);
75
+ const tenantTables = dataGraph.tables.filter((table) =>
76
+ table.fields.some((field) => field.name === "tenantId")
77
+ );
78
+ if (tenantTables.length === 0) {
79
+ return [...FALLBACK_RESOURCE_TYPES];
80
+ }
81
+ const resourceTypes = new Map<string, { slug: string; name: string; parent?: string }>();
82
+ resourceTypes.set("organization", { slug: "organization", name: "Organization" });
83
+ for (const table of tenantTables) {
84
+ if (["organization", "organizations", "membership", "memberships"].includes(table.name)) {
85
+ continue;
86
+ }
87
+ const slug = singularResourceName(table.name);
88
+ resourceTypes.set(slug, { slug, name: toTitle(slug), parent: "organization" });
89
+ }
90
+ for (const table of tenantTables) {
91
+ const slug = singularResourceName(table.name);
92
+ const current = resourceTypes.get(slug);
93
+ if (!current || slug === "organization") {
94
+ continue;
95
+ }
96
+ const parent = [...resourceTypes.keys()]
97
+ .filter((candidate) => candidate !== slug && candidate !== "organization")
98
+ .find((candidate) =>
99
+ table.fields.some((field) =>
100
+ field.name === idFieldForResource(candidate) ||
101
+ field.name === `${candidate}_id`
102
+ )
103
+ );
104
+ if (parent) {
105
+ resourceTypes.set(slug, { ...current, parent });
106
+ }
107
+ }
108
+ return [...resourceTypes.values()].sort((a, b) => a.slug.localeCompare(b.slug));
109
+ }
110
+
111
+ function readPermissions(permissions: string[]): string[] {
112
+ return permissions.filter((permission) => /(^|:)read$|read|list|view/i.test(permission));
113
+ }
114
+
115
+ function updatePermissions(permissions: string[]): string[] {
116
+ return permissions.filter((permission) => /update|progress|write/i.test(permission));
117
+ }
118
+
119
+ function rolePermissions(permissions: string[]): Record<string, string[]> {
120
+ const member = uniqueSorted([...readPermissions(permissions), ...updatePermissions(permissions)]);
121
+ return {
122
+ owner: permissions,
123
+ manager: permissions,
124
+ member: member.length > 0 ? member : permissions.slice(0, 1),
125
+ };
126
+ }
127
+
128
+ function workosFgaEnabled(input: IntegrationTemplateInput): boolean {
129
+ return Boolean(
130
+ input.recipe.integrations?.includes("workos/fga.ts") ||
131
+ input.recipe.integrations?.includes("workos/resource-map.ts"),
132
+ );
133
+ }
134
+
135
+ export function renderWorkosServerAdapter(_input: IntegrationTemplateInput): string {
136
+ return [
137
+ "/** Forge generated WorkOS server adapter — action/workflow/endpoint/server contexts only. */",
138
+ 'import { createWorkOS, type WorkOS } from "@workos-inc/node";',
139
+ SECRETS_IMPORT,
140
+ CONFIG_IMPORT,
141
+ "",
142
+ "export interface ForgeWorkOSAuthKitConfig {",
143
+ " apiKey: string;",
144
+ " clientId: string;",
145
+ " cookiePassword: string;",
146
+ " redirectUri?: string;",
147
+ " cookieName?: string;",
148
+ " postLoginRedirectUri?: string;",
149
+ " postLogoutRedirectUri?: string;",
150
+ "}",
151
+ "",
152
+ "export function createForgeWorkOS(secrets: SecretsContext): WorkOS {",
153
+ " return createWorkOS({",
154
+ ' apiKey: secrets.get("WORKOS_API_KEY"),',
155
+ ' clientId: secrets.get("WORKOS_CLIENT_ID"),',
156
+ " });",
157
+ "}",
158
+ "",
159
+ "export function resolveWorkOSAuthKitConfig(",
160
+ " secrets: SecretsContext,",
161
+ " config: ConfigContext,",
162
+ "): ForgeWorkOSAuthKitConfig {",
163
+ " return {",
164
+ ' apiKey: secrets.get("WORKOS_API_KEY"),',
165
+ ' clientId: secrets.get("WORKOS_CLIENT_ID"),',
166
+ ' cookiePassword: secrets.get("WORKOS_COOKIE_PASSWORD"),',
167
+ ' redirectUri: config.optional("WORKOS_REDIRECT_URI"),',
168
+ ' cookieName: config.optional("WORKOS_COOKIE_NAME"),',
169
+ ' postLoginRedirectUri: config.optional("WORKOS_POST_LOGIN_REDIRECT_URI"),',
170
+ ' postLogoutRedirectUri: config.optional("WORKOS_POST_LOGOUT_REDIRECT_URI"),',
171
+ " };",
172
+ "}",
173
+ "",
174
+ "export const forgeWorkOSRequiredClaims = {",
175
+ ' userId: "sub",',
176
+ ' tenantId: "organization_id",',
177
+ ' alternateTenantClaims: ["org_id", "tenant_id"],',
178
+ ' email: "email",',
179
+ ' organizationMembershipId: "organization_membership_id",',
180
+ ' role: "role",',
181
+ ' roles: "roles",',
182
+ ' permissions: "permissions",',
183
+ "} as const;",
184
+ "",
185
+ ].join("\n");
186
+ }
187
+
188
+ export function renderWorkosAuthkit(_input: IntegrationTemplateInput): string {
189
+ return [
190
+ "/** Forge generated WorkOS AuthKit configuration notes. */",
191
+ "",
192
+ "export const workosAuthKitEnv = {",
193
+ ' mode: "oidc",',
194
+ ' apiKey: "WORKOS_API_KEY",',
195
+ ' clientId: "WORKOS_CLIENT_ID",',
196
+ ' cookiePassword: "WORKOS_COOKIE_PASSWORD",',
197
+ ' redirectUri: "WORKOS_REDIRECT_URI",',
198
+ ' cookieName: "WORKOS_COOKIE_NAME",',
199
+ ' postLoginRedirectUri: "WORKOS_POST_LOGIN_REDIRECT_URI",',
200
+ ' postLogoutRedirectUri: "WORKOS_POST_LOGOUT_REDIRECT_URI",',
201
+ ' issuer: "FORGE_AUTH_ISSUER",',
202
+ ' audience: "FORGE_AUTH_AUDIENCE",',
203
+ ' tenantClaim: "org_id",',
204
+ ' normalizedTenantClaim: "organization_id",',
205
+ ' emailClaim: "email",',
206
+ ' membershipClaim: "organization_membership_id",',
207
+ ' roleClaim: "role",',
208
+ ' permissionsClaim: "permissions",',
209
+ "} as const;",
210
+ "",
211
+ "export const workosAuthKitCommands = [",
212
+ ' "forge workos install --json",',
213
+ ' "forge workos install --yes --json",',
214
+ ' "forge workos doctor --json",',
215
+ ' "forge workos doctor --yes --json",',
216
+ ' "forge workos seed --file workos-seed.yml --dry-run --json",',
217
+ ' "forge workos seed --file workos-seed.yml --json",',
218
+ ' "forge workos prove --file workos-seed.yml --json",',
219
+ ' "forge workos setup --real --file workos-seed.yml --json",',
220
+ ' "forge auth check --json",',
221
+ ' "forge auth prove --json",',
222
+ "] as const;",
223
+ "",
224
+ "export const workosAuthKitRoutes = [",
225
+ ' { method: "GET", path: "/login", purpose: "start AuthKit login" },',
226
+ ' { method: "GET", path: "/callback", purpose: "exchange AuthKit code and set session cookie" },',
227
+ ' { method: "POST", path: "/logout", purpose: "clear local AuthKit session" },',
228
+ ' { method: "GET", path: "/session", purpose: "read the current Forge-normalized auth session" },',
229
+ "] as const;",
230
+ "",
231
+ "export function explainWorkOSProductionClaims(): string[] {",
232
+ " return [",
233
+ ' "Use jwt or oidc auth mode in production; never dev-headers.",',
234
+ ' "Map the WorkOS organization claim to Forge organization_id consistently; org_id and tenant_id are accepted migration aliases.",',
235
+ ' "Include sub, email, organization_id, role, roles, permissions, and organization_membership_id in issued tokens.",',
236
+ ' "Include role, permissions, and organization_membership_id in the token when resource checks need them.",',
237
+ ' "Run tenant isolation tests with at least two organizations before public production.",',
238
+ " ];",
239
+ "}",
240
+ "",
241
+ ].join("\n");
242
+ }
243
+
244
+ export function renderWorkosPolicies(input: IntegrationTemplateInput): string {
245
+ const permissions = permissionsFromApp(input);
246
+ return [
247
+ "/** Forge generated WorkOS-derived policy template.",
248
+ " * Import or merge these policies into src/policies.ts after reviewing names against your app.",
249
+ " */",
250
+ 'import { canPermission, definePolicies } from "forge/policy";',
251
+ "",
252
+ "export const workosPermissionPolicies = definePolicies({",
253
+ ...permissions.map((permission) =>
254
+ ` "${policyNameForPermission(permission)}": canPermission("${permission}"),`
255
+ ),
256
+ "});",
257
+ "",
258
+ "export const workosPermissionPolicyMap = {",
259
+ ...permissions.map((permission) =>
260
+ ` "${permission}": "${policyNameForPermission(permission)}",`
261
+ ),
262
+ "} as const;",
263
+ "",
264
+ ].join("\n");
265
+ }
266
+
267
+ export function renderWorkosEnvExample(_input: IntegrationTemplateInput): string {
268
+ return [
269
+ "# Forge WorkOS/AuthKit production auth example.",
270
+ "# Secret values are intentionally blank. Keep real values in .env.local, your host secrets, or WorkOS/Vercel integration env.",
271
+ "FORGE_AUTH_MODE=oidc",
272
+ "FORGE_AUTH_ISSUER=https://api.workos.com",
273
+ "FORGE_AUTH_AUDIENCE=",
274
+ "# Set to https://api.workos.com/sso/jwks/<WORKOS_CLIENT_ID> after filling WORKOS_CLIENT_ID.",
275
+ "FORGE_AUTH_JWKS_URI=",
276
+ "FORGE_AUTH_ALGORITHMS=RS256",
277
+ "",
278
+ "WORKOS_API_KEY=",
279
+ "WORKOS_CLIENT_ID=",
280
+ "WORKOS_COOKIE_PASSWORD=",
281
+ "WORKOS_REDIRECT_URI=http://localhost:5173/callback",
282
+ "WORKOS_COOKIE_NAME=forgeos_workos_session",
283
+ "WORKOS_POST_LOGIN_REDIRECT_URI=http://localhost:5173/",
284
+ "WORKOS_POST_LOGOUT_REDIRECT_URI=http://localhost:5173/",
285
+ "WORKOS_WEBHOOK_SECRET=",
286
+ "",
287
+ "# Browser AuthKit settings for Vite/React. These are public client identifiers, not secrets.",
288
+ "VITE_FORGE_URL=http://localhost:3765",
289
+ "VITE_WORKOS_CLIENT_ID=",
290
+ "VITE_WORKOS_REDIRECT_URI=http://localhost:5173/callback",
291
+ "# Optional: map hosted WorkOS organization IDs to Forge tenant IDs when your app uses internal UUID tenants.",
292
+ "# VITE_FORGE_WORKOS_TENANT_MAP={\"org_...\":\"11111111-1111-4111-8111-111111111111\"}",
293
+ "# VITE_FORGE_WORKOS_TENANT_ID_FALLBACK=",
294
+ "",
295
+ "# WorkOS token claims expected by ForgeOS after forge add auth workos:",
296
+ "# sub, email, organization_id, role, roles, permissions, organization_membership_id",
297
+ "",
298
+ ].join("\n");
299
+ }
300
+
301
+ export function renderWorkosReactBridge(_input: IntegrationTemplateInput): string {
302
+ return [
303
+ "/** Forge generated WorkOS/AuthKit React bridge.",
304
+ " * Import ForgeWorkOSAuthProvider from web/src/main.tsx, or copy the pattern into your app shell.",
305
+ " */",
306
+ 'import { useEffect, useState, type ReactNode } from "react";',
307
+ 'import { ForgeProvider, forgeUrl } from "./forge";',
308
+ "",
309
+ "const clientId = import.meta.env.VITE_WORKOS_CLIENT_ID as string | undefined;",
310
+ "const redirectUri = import.meta.env.VITE_WORKOS_REDIRECT_URI as string | undefined;",
311
+ "const forgeApiUrl = import.meta.env.VITE_FORGE_URL as string | undefined;",
312
+ "const workosTenantMap = import.meta.env.VITE_FORGE_WORKOS_TENANT_MAP as string | undefined;",
313
+ "const workosTenantFallback = import.meta.env.VITE_FORGE_WORKOS_TENANT_ID_FALLBACK as string | undefined;",
314
+ "",
315
+ "function workOSApiUrl(path: string): URL {",
316
+ " if (typeof window !== 'undefined' && forgeApiUrl) {",
317
+ " const configured = new URL(forgeApiUrl, window.location.origin);",
318
+ " if ((configured.hostname === 'localhost' || configured.hostname === '127.0.0.1') && (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1')) {",
319
+ " return new URL(path, window.location.origin);",
320
+ " }",
321
+ " }",
322
+ " return new URL(path, forgeApiUrl || forgeUrl || window.location.origin);",
323
+ "}",
324
+ "",
325
+ "export function hasWorkOSBrowserConfig(): boolean {",
326
+ " return Boolean(clientId && redirectUri);",
327
+ "}",
328
+ "",
329
+ "function forgeTenantIdForWorkOSOrganization(claims: ForgeWorkOSBrowserSession['claims']) {",
330
+ " if (!claims) {",
331
+ " return undefined;",
332
+ " }",
333
+ " const explicitTenant = claims.tenant_id ?? claims.forge_tenant_id;",
334
+ " if (explicitTenant) {",
335
+ " return explicitTenant;",
336
+ " }",
337
+ " const organizationId = claims.organization_id;",
338
+ " if (!organizationId) {",
339
+ " return undefined;",
340
+ " }",
341
+ " if (workosTenantMap) {",
342
+ " try {",
343
+ " const parsed = JSON.parse(workosTenantMap) as Record<string, string>;",
344
+ " if (parsed[organizationId]) {",
345
+ " return parsed[organizationId];",
346
+ " }",
347
+ " } catch {",
348
+ " // Keep authentication usable; forge workos doctor validates mapping syntax separately.",
349
+ " }",
350
+ " }",
351
+ " if (organizationId.startsWith('org_') && workosTenantFallback) {",
352
+ " return workosTenantFallback;",
353
+ " }",
354
+ " return organizationId;",
355
+ "}",
356
+ "",
357
+ "function workOSSessionToForgeAuth(session: ForgeWorkOSBrowserSession | null | undefined) {",
358
+ " const claims = session?.claims;",
359
+ " if (!claims?.sub) {",
360
+ " return {};",
361
+ " }",
362
+ " const forgeTenantId = forgeTenantIdForWorkOSOrganization(claims);",
363
+ " return {",
364
+ " userId: claims.sub,",
365
+ " tenantId: forgeTenantId,",
366
+ " organizationId: forgeTenantId,",
367
+ " organizationMembershipId: claims.organization_membership_id,",
368
+ " role: claims.role ?? claims.roles?.[0],",
369
+ " roles: claims.roles,",
370
+ " permissions: claims.permissions,",
371
+ " claims,",
372
+ " };",
373
+ "}",
374
+ "",
375
+ "const workOSAuthProvider = async () => {",
376
+ " const response = await fetch(workOSApiUrl('/session'), { credentials: 'include' });",
377
+ " if (response.status === 401) {",
378
+ " return {};",
379
+ " }",
380
+ " if (!response.ok) {",
381
+ " throw new Error(`WorkOS session request failed with HTTP ${response.status}`);",
382
+ " }",
383
+ " const payload = await response.json() as { ok?: boolean; session?: ForgeWorkOSBrowserSession | null };",
384
+ " return workOSSessionToForgeAuth(payload.ok ? payload.session ?? null : null);",
385
+ "};",
386
+ "",
387
+ "export function ForgeWorkOSAuthProvider({ children }: { children: ReactNode }) {",
388
+ " if (!clientId || !redirectUri) {",
389
+ " throw new Error(\"WorkOS AuthKit requires VITE_WORKOS_CLIENT_ID and VITE_WORKOS_REDIRECT_URI\");",
390
+ " }",
391
+ " return (",
392
+ " <ForgeProvider url={forgeUrl} auth={workOSAuthProvider}>",
393
+ " {children}",
394
+ " </ForgeProvider>",
395
+ " );",
396
+ "}",
397
+ "",
398
+ "export interface ForgeWorkOSBrowserSession {",
399
+ " user?: { id?: string; email?: string; firstName?: string; lastName?: string };",
400
+ " claims?: {",
401
+ " sub?: string;",
402
+ " email?: string;",
403
+ " organization_id?: string;",
404
+ " tenant_id?: string;",
405
+ " forge_tenant_id?: string;",
406
+ " organization_membership_id?: string;",
407
+ " role?: string;",
408
+ " roles?: string[];",
409
+ " permissions?: string[];",
410
+ " };",
411
+ "}",
412
+ "",
413
+ "export function useForgeWorkOSSession(): { loading: boolean; session: ForgeWorkOSBrowserSession | null; error: Error | null; refresh: () => Promise<void> } {",
414
+ " const [loading, setLoading] = useState(true);",
415
+ " const [session, setSession] = useState<ForgeWorkOSBrowserSession | null>(null);",
416
+ " const [error, setError] = useState<Error | null>(null);",
417
+ "",
418
+ " const refresh = async () => {",
419
+ " setLoading(true);",
420
+ " setError(null);",
421
+ " try {",
422
+ " const response = await fetch(workOSApiUrl('/session'), { credentials: 'include' });",
423
+ " if (response.status === 401) {",
424
+ " setSession(null);",
425
+ " return;",
426
+ " }",
427
+ " if (!response.ok) {",
428
+ " throw new Error(`WorkOS session request failed with HTTP ${response.status}`);",
429
+ " }",
430
+ " const payload = await response.json() as { ok?: boolean; session?: ForgeWorkOSBrowserSession | null };",
431
+ " setSession(payload.ok ? payload.session ?? null : null);",
432
+ " } catch (sessionError) {",
433
+ " setSession(null);",
434
+ " setError(sessionError instanceof Error ? sessionError : new Error('WorkOS session request failed'));",
435
+ " } finally {",
436
+ " setLoading(false);",
437
+ " }",
438
+ " };",
439
+ "",
440
+ " useEffect(() => {",
441
+ " void refresh();",
442
+ " }, []);",
443
+ "",
444
+ " return { loading, session, error, refresh };",
445
+ "}",
446
+ "",
447
+ "export function useWorkOSAuth() {",
448
+ " const session = useForgeWorkOSSession();",
449
+ " const user = session.session?.user ?? null;",
450
+ " return {",
451
+ " isLoading: session.loading,",
452
+ " user,",
453
+ " session: session.session,",
454
+ " sessionError: session.error,",
455
+ " refresh: session.refresh,",
456
+ " signIn: () => {",
457
+ " const url = workOSApiUrl('/login');",
458
+ " url.searchParams.set('returnTo', window.location.pathname + window.location.search);",
459
+ " window.location.assign(url);",
460
+ " },",
461
+ " signUp: () => {",
462
+ " const url = workOSApiUrl('/login');",
463
+ " url.searchParams.set('returnTo', window.location.pathname + window.location.search);",
464
+ " window.location.assign(url);",
465
+ " },",
466
+ " signOut: () => { void fetch(workOSApiUrl('/logout'), { method: 'POST', credentials: 'include' }).finally(() => { window.location.assign('/'); }); },",
467
+ " getAccessToken: async () => null,",
468
+ " };",
469
+ "}",
470
+ "",
471
+ "export function WorkOSLoginPanel() {",
472
+ " const { isLoading, user, signIn, signUp, signOut } = useWorkOSAuth();",
473
+ " useEffect(() => {",
474
+ " if (!isLoading && !user && window.location.pathname === \"/login\") {",
475
+ " void signIn();",
476
+ " }",
477
+ " }, [isLoading, signIn, user]);",
478
+ " if (isLoading) return <p>Loading authentication...</p>;",
479
+ " if (!user && window.location.pathname === \"/login\") return <p>Redirecting...</p>;",
480
+ " if (!user) {",
481
+ " return (",
482
+ " <div>",
483
+ " <button type=\"button\" onClick={() => void signIn()}>Sign in</button>",
484
+ " <button type=\"button\" onClick={() => void signUp()}>Sign up</button>",
485
+ " </div>",
486
+ " );",
487
+ " }",
488
+ " return (",
489
+ " <div>",
490
+ " <span>{user.email}</span>",
491
+ " <button type=\"button\" onClick={() => signOut()}>Sign out</button>",
492
+ " </div>",
493
+ " );",
494
+ "}",
495
+ "",
496
+ "",
497
+ ].join("\n");
498
+ }
499
+
500
+ export function renderWorkosFga(input: IntegrationTemplateInput): string {
501
+ const permissions = permissionsFromApp(input);
502
+ const roles = rolePermissions(permissions);
503
+ const resourceTypes = resourceTypesFromApp(input);
504
+ return [
505
+ "/** Forge generated WorkOS FGA bridge helpers. */",
506
+ "",
507
+ "export const forgeWorkOSDefaultPermissions = [",
508
+ ...permissions.map((permission) => ` "${permission}",`),
509
+ "] as const;",
510
+ "",
511
+ "export type ForgeWorkOSPermission = (typeof forgeWorkOSDefaultPermissions)[number];",
512
+ "",
513
+ "export const forgeWorkOSDefaultRoles = {",
514
+ ...Object.entries(roles).map(([role, rolePerms]) =>
515
+ ` ${role}: [${rolePerms.map((permission) => `"${permission}"`).join(", ")}],`
516
+ ),
517
+ "} as const satisfies Record<string, readonly ForgeWorkOSPermission[]>;",
518
+ "",
519
+ "export const forgeWorkOSResourceTypes = [",
520
+ ...resourceTypes.map((resource) =>
521
+ ` { slug: "${resource.slug}",${resource.parent ? ` parent: "${resource.parent}",` : ""} description: "${resource.name} resource" },`
522
+ ),
523
+ "] as const;",
524
+ "",
525
+ "export interface ForgeWorkOSAccessCheckInput {",
526
+ " organizationId: string;",
527
+ " organizationMembershipId: string;",
528
+ " permission: ForgeWorkOSPermission | string;",
529
+ " resourceTypeSlug: string;",
530
+ " resourceExternalId: string;",
531
+ "}",
532
+ "",
533
+ "export function buildWorkOSAccessCheck(input: ForgeWorkOSAccessCheckInput) {",
534
+ " return {",
535
+ " organizationMembershipId: input.organizationMembershipId,",
536
+ " permissionSlug: input.permission,",
537
+ " resourceTypeSlug: input.resourceTypeSlug,",
538
+ " resourceExternalId: input.resourceExternalId,",
539
+ " } as const;",
540
+ "}",
541
+ "",
542
+ ].join("\n");
543
+ }
544
+
545
+ export function renderWorkosResourceMap(input: IntegrationTemplateInput): string {
546
+ const resourceTypes = resourceTypesFromApp(input);
547
+ const helperResourceKinds = uniqueSorted([
548
+ "organization",
549
+ ...resourceTypes.map((resource) => resource.slug),
550
+ "project",
551
+ "team",
552
+ "taskGroup",
553
+ "task",
554
+ ]);
555
+ const resourceKindUnion = helperResourceKinds.map((resource) => `"${resource}"`).join(" | ");
556
+ return [
557
+ "/** Forge generated WorkOS FGA resource bridge.",
558
+ " * Keep calls to WorkOS Authorization API in server/action/workflow/endpoint contexts.",
559
+ " */",
560
+ "",
561
+ `export type ForgeWorkOSResourceKind = ${resourceKindUnion};`,
562
+ "export type ForgeWorkOSFgaFallback = \"deny\" | \"throw\";",
563
+ "",
564
+ "export interface ForgeWorkOSResourceRef {",
565
+ " kind: ForgeWorkOSResourceKind;",
566
+ " externalId: string;",
567
+ " name?: string;",
568
+ " description?: string;",
569
+ " parent?: ForgeWorkOSResourceRef;",
570
+ "}",
571
+ "",
572
+ "export interface ForgeWorkOSApplicationGraph {",
573
+ " organization: ForgeWorkOSResourceRef;",
574
+ " resources: ForgeWorkOSResourceRef[];",
575
+ " projects: ForgeWorkOSResourceRef[];",
576
+ " teams: ForgeWorkOSResourceRef[];",
577
+ " tasks: ForgeWorkOSResourceRef[];",
578
+ "}",
579
+ "",
580
+ "export interface ForgeWorkOSResourceRecord {",
581
+ " organizationId: string;",
582
+ " resourceTypeSlug: string;",
583
+ " externalId: string;",
584
+ " name: string;",
585
+ " description?: string;",
586
+ " parentResourceTypeSlug?: string;",
587
+ " parentResourceExternalId?: string;",
588
+ "}",
589
+ "",
590
+ "function slug(value: string): string {",
591
+ " return value.toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-+|-+$/g, \"\") || \"resource\";",
592
+ "}",
593
+ "",
594
+ "export function workosResource(kind: ForgeWorkOSResourceKind, id: string, parent?: ForgeWorkOSResourceRef, name?: string): ForgeWorkOSResourceRef {",
595
+ " return { kind, externalId: `${kind}:${slug(id)}`, name: name ?? id, ...(parent ? { parent } : {}) };",
596
+ "}",
597
+ "",
598
+ "export function rootWorkOSResource(resource: ForgeWorkOSResourceRef): ForgeWorkOSResourceRef {",
599
+ " let current = resource;",
600
+ " while (current.parent) current = current.parent;",
601
+ " return current;",
602
+ "}",
603
+ "",
604
+ "export function assertWorkOSResourceTenant(input: { resource: ForgeWorkOSResourceRef; organization: ForgeWorkOSResourceRef }): void {",
605
+ " const root = rootWorkOSResource(input.resource);",
606
+ " if (root.kind !== \"organization\" || root.externalId !== input.organization.externalId) {",
607
+ " throw new Error(\"FORGE_WORKOS_CROSS_TENANT_RESOURCE: resource parent does not match organization\");",
608
+ " }",
609
+ "}",
610
+ "",
611
+ "export function buildOnboardingResourceGraph(input: {",
612
+ " organizationId: string;",
613
+ " organizationName?: string;",
614
+ " projects?: Array<string | { id: string; name?: string }>;",
615
+ " teams?: Array<string | { id: string; name?: string }>;",
616
+ " taskGroups?: Array<string | { id: string; name?: string; projectId?: string }>;",
617
+ " tasks?: Array<string | { id: string; name?: string; projectId?: string }>;",
618
+ "}): ForgeWorkOSApplicationGraph {",
619
+ " const organization = { kind: \"organization\", externalId: input.organizationId, name: input.organizationName ?? input.organizationId } satisfies ForgeWorkOSResourceRef;",
620
+ " const normalize = (value: string | { id: string; name?: string }) => typeof value === \"string\" ? { id: value, name: value } : { id: value.id, name: value.name ?? value.id };",
621
+ " const projects = (input.projects ?? []).map((value) => {",
622
+ " const project = normalize(value);",
623
+ " return workosResource(\"project\", project.id, organization, project.name);",
624
+ " });",
625
+ " const teams = (input.teams ?? []).map((value) => {",
626
+ " const team = normalize(value);",
627
+ " return workosResource(\"team\", team.id, organization, team.name);",
628
+ " });",
629
+ " const taskGroups = (input.taskGroups ?? []).map((value) => {",
630
+ " const group = normalize(value);",
631
+ " const projectId = typeof value === \"string\" ? undefined : value.projectId;",
632
+ " const parent = projectId ? projects.find((project) => project.externalId === workosResource(\"project\", projectId, organization).externalId) ?? organization : organization;",
633
+ " return workosResource(\"taskGroup\", group.id, parent, group.name);",
634
+ " });",
635
+ " const tasks = (input.tasks ?? []).map((value) => {",
636
+ " const task = normalize(value);",
637
+ " const projectId = typeof value === \"string\" ? undefined : value.projectId;",
638
+ " const parent = projectId ? projects.find((project) => project.externalId === workosResource(\"project\", projectId, organization).externalId) ?? organization : organization;",
639
+ " return workosResource(\"task\", task.id, parent, task.name);",
640
+ " });",
641
+ " const resources = [...projects, ...teams, ...taskGroups, ...tasks];",
642
+ " return { organization, resources, projects, teams, tasks: [...taskGroups, ...tasks] };",
643
+ "}",
644
+ "",
645
+ "export interface ForgeWorkOSAuthorizationClient {",
646
+ " authorization: {",
647
+ " check(input: {",
648
+ " organizationMembershipId: string;",
649
+ " permissionSlug: string;",
650
+ " resourceTypeSlug: string;",
651
+ " resourceExternalId: string;",
652
+ " }): Promise<{ authorized?: boolean; allowed?: boolean }>;",
653
+ " createResource?(input: {",
654
+ " organizationId: string;",
655
+ " resourceTypeSlug: string;",
656
+ " externalId: string;",
657
+ " name: string;",
658
+ " description?: string;",
659
+ " parentResourceTypeSlug?: string;",
660
+ " parentResourceExternalId?: string;",
661
+ " }): Promise<unknown>;",
662
+ " getResourceByExternalId?(input: { organizationId: string; resourceTypeSlug: string; externalId: string }): Promise<unknown>;",
663
+ " updateResourceByExternalId?(input: { organizationId: string; resourceTypeSlug: string; externalId: string; name?: string; description?: string; parentResourceTypeSlug?: string; parentResourceExternalId?: string }): Promise<unknown>;",
664
+ " };",
665
+ "}",
666
+ "",
667
+ "export interface ForgeWorkOSFgaTelemetry {",
668
+ " emit?: (event: string, properties: Record<string, unknown>) => void | Promise<void>;",
669
+ "}",
670
+ "",
671
+ "export class ForgeWorkOSFgaDecisionCache {",
672
+ " private entries = new Map<string, { allowed: boolean; expiresAt: number }>();",
673
+ " constructor(private ttlMs = 30_000) {}",
674
+ " key(input: { organizationMembershipId: string; permission: string; resource: ForgeWorkOSResourceRef }): string {",
675
+ " return [input.organizationMembershipId, input.permission, input.resource.kind, input.resource.externalId].join(\"|\");",
676
+ " }",
677
+ " get(input: { organizationMembershipId: string; permission: string; resource: ForgeWorkOSResourceRef }): boolean | undefined {",
678
+ " const entry = this.entries.get(this.key(input));",
679
+ " if (!entry || entry.expiresAt < Date.now()) return undefined;",
680
+ " return entry.allowed;",
681
+ " }",
682
+ " set(input: { organizationMembershipId: string; permission: string; resource: ForgeWorkOSResourceRef; allowed: boolean }): void {",
683
+ " this.entries.set(this.key(input), { allowed: input.allowed, expiresAt: Date.now() + this.ttlMs });",
684
+ " }",
685
+ "}",
686
+ "",
687
+ "function flattenResource(resource: ForgeWorkOSResourceRef): ForgeWorkOSResourceRef[] {",
688
+ " const parent = resource.parent ? flattenResource(resource.parent) : [];",
689
+ " return [...parent, resource];",
690
+ "}",
691
+ "",
692
+ "export function workOSResourceRecords(input: { organizationId: string; graph: ForgeWorkOSApplicationGraph }): ForgeWorkOSResourceRecord[] {",
693
+ " const organization = input.graph.organization;",
694
+ " const legacyResources = [...(input.graph.projects ?? []), ...(input.graph.teams ?? []), ...(input.graph.tasks ?? [])];",
695
+ " const resources = [...(input.graph.resources ?? []), ...legacyResources]",
696
+ " .flatMap((resource) => flattenResource(resource))",
697
+ " .filter((resource) => resource.kind !== \"organization\");",
698
+ " const byKey = new Map<string, ForgeWorkOSResourceRecord>();",
699
+ " for (const resource of resources) {",
700
+ " assertWorkOSResourceTenant({ resource, organization });",
701
+ " byKey.set(`${resource.kind}:${resource.externalId}`, {",
702
+ " organizationId: input.organizationId,",
703
+ " resourceTypeSlug: resource.kind,",
704
+ " externalId: resource.externalId,",
705
+ " name: resource.name ?? resource.externalId,",
706
+ " ...(resource.description ? { description: resource.description } : {}),",
707
+ " ...(resource.parent && resource.parent.kind !== \"organization\" ? {",
708
+ " parentResourceTypeSlug: resource.parent.kind,",
709
+ " parentResourceExternalId: resource.parent.externalId,",
710
+ " } : {}),",
711
+ " });",
712
+ " }",
713
+ " return [...byKey.values()];",
714
+ "}",
715
+ "",
716
+ "export async function syncWorkOSResourceGraph(input: {",
717
+ " client: ForgeWorkOSAuthorizationClient;",
718
+ " organizationId: string;",
719
+ " graph: ForgeWorkOSApplicationGraph;",
720
+ " mode?: \"create\" | \"upsert\";",
721
+ " telemetry?: ForgeWorkOSFgaTelemetry;",
722
+ "}): Promise<{ synced: number; created: number; updated: number; skipped: number; records: ForgeWorkOSResourceRecord[] }> {",
723
+ " const records = workOSResourceRecords({ organizationId: input.organizationId, graph: input.graph });",
724
+ " let created = 0;",
725
+ " let updated = 0;",
726
+ " let skipped = 0;",
727
+ " for (const record of records) {",
728
+ " if (!input.client.authorization.createResource) {",
729
+ " skipped += 1;",
730
+ " continue;",
731
+ " }",
732
+ " if (input.mode === \"upsert\" && input.client.authorization.getResourceByExternalId && input.client.authorization.updateResourceByExternalId) {",
733
+ " try {",
734
+ " await input.client.authorization.getResourceByExternalId({ organizationId: record.organizationId, resourceTypeSlug: record.resourceTypeSlug, externalId: record.externalId });",
735
+ " await input.client.authorization.updateResourceByExternalId(record);",
736
+ " updated += 1;",
737
+ " await input.telemetry?.emit?.(\"forge.workos.fga.resource.updated\", { resourceTypeSlug: record.resourceTypeSlug, externalId: record.externalId });",
738
+ " continue;",
739
+ " } catch {",
740
+ " // Missing resources fall through to create; other SDK/API errors surface from create.",
741
+ " }",
742
+ " }",
743
+ " await input.client.authorization.createResource(record);",
744
+ " created += 1;",
745
+ " await input.telemetry?.emit?.(\"forge.workos.fga.resource.created\", { resourceTypeSlug: record.resourceTypeSlug, externalId: record.externalId });",
746
+ " }",
747
+ " return { synced: created + updated, created, updated, skipped, records };",
748
+ "}",
749
+ "",
750
+ "export async function canWorkOS(input: {",
751
+ " client: ForgeWorkOSAuthorizationClient;",
752
+ " organizationMembershipId: string;",
753
+ " permission: string;",
754
+ " resource: ForgeWorkOSResourceRef;",
755
+ " organization?: ForgeWorkOSResourceRef;",
756
+ " cache?: ForgeWorkOSFgaDecisionCache;",
757
+ " telemetry?: ForgeWorkOSFgaTelemetry;",
758
+ " fallback?: ForgeWorkOSFgaFallback;",
759
+ "}): Promise<boolean> {",
760
+ " if (input.organization) {",
761
+ " assertWorkOSResourceTenant({ resource: input.resource, organization: input.organization });",
762
+ " }",
763
+ " const cached = input.cache?.get(input);",
764
+ " if (typeof cached === \"boolean\") {",
765
+ " await input.telemetry?.emit?.(\"forge.workos.fga.cache_hit\", { permission: input.permission, resourceTypeSlug: input.resource.kind, resourceExternalId: input.resource.externalId, allowed: cached });",
766
+ " return cached;",
767
+ " }",
768
+ " try {",
769
+ " const result = await input.client.authorization.check({",
770
+ " organizationMembershipId: input.organizationMembershipId,",
771
+ " permissionSlug: input.permission,",
772
+ " resourceTypeSlug: input.resource.kind,",
773
+ " resourceExternalId: input.resource.externalId,",
774
+ " });",
775
+ " const allowed = Boolean(result.authorized ?? result.allowed);",
776
+ " input.cache?.set({ ...input, allowed });",
777
+ " await input.telemetry?.emit?.(\"forge.workos.fga.check\", { permission: input.permission, resourceTypeSlug: input.resource.kind, resourceExternalId: input.resource.externalId, allowed });",
778
+ " return allowed;",
779
+ " } catch (error) {",
780
+ " await input.telemetry?.emit?.(\"forge.workos.fga.error\", { permission: input.permission, resourceTypeSlug: input.resource.kind, resourceExternalId: input.resource.externalId, error: error instanceof Error ? error.message : String(error) });",
781
+ " if (input.fallback === \"deny\" || !input.fallback) return false;",
782
+ " throw error;",
783
+ " }",
784
+ "}",
785
+ "",
786
+ ].join("\n");
787
+ }
788
+
789
+ export function renderWorkosSession(_input: IntegrationTemplateInput): string {
790
+ return [
791
+ "/** Forge generated WorkOS AuthKit session helpers. */",
792
+ 'import type { ForgeWorkOSAuthKitConfig } from "../../packages/workos.server.js";',
793
+ "",
794
+ "export interface ForgeWorkOSSession {",
795
+ " user: { id: string; email?: string; firstName?: string; lastName?: string; [key: string]: unknown };",
796
+ " accessToken: string;",
797
+ " refreshToken?: string;",
798
+ " organizationId?: string;",
799
+ " organizationMembershipId?: string;",
800
+ " role?: string;",
801
+ " roles?: string[];",
802
+ " permissions?: string[];",
803
+ " issuedAt: number;",
804
+ "}",
805
+ "",
806
+ "export interface ForgeWorkOSClaims {",
807
+ " sub: string;",
808
+ " email?: string;",
809
+ " name?: string;",
810
+ " organization_id?: string;",
811
+ " organization_membership_id?: string;",
812
+ " role?: string;",
813
+ " roles?: string[];",
814
+ " permissions?: string[];",
815
+ "}",
816
+ "",
817
+ "function bytesToBase64url(bytes: Uint8Array): string {",
818
+ " let binary = \"\";",
819
+ " for (const byte of bytes) binary += String.fromCharCode(byte);",
820
+ " return btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/g, \"\");",
821
+ "}",
822
+ "",
823
+ "function base64urlToBytes(input: string): Uint8Array {",
824
+ " const padded = input.replace(/-/g, \"+\").replace(/_/g, \"/\").padEnd(Math.ceil(input.length / 4) * 4, \"=\");",
825
+ " return Uint8Array.from(atob(padded), (char) => char.charCodeAt(0));",
826
+ "}",
827
+ "",
828
+ "function base64url(input: string): string {",
829
+ " return bytesToBase64url(new TextEncoder().encode(input));",
830
+ "}",
831
+ "",
832
+ "function fromBase64url(input: string): string {",
833
+ " return new TextDecoder().decode(base64urlToBytes(input));",
834
+ "}",
835
+ "",
836
+ "async function sign(payload: string, secret: string): Promise<string> {",
837
+ " const key = await crypto.subtle.importKey(",
838
+ " \"raw\",",
839
+ " new TextEncoder().encode(secret),",
840
+ " { name: \"HMAC\", hash: \"SHA-256\" },",
841
+ " false,",
842
+ " [\"sign\"],",
843
+ " );",
844
+ " const signature = await crypto.subtle.sign(\"HMAC\", key, new TextEncoder().encode(payload));",
845
+ " return bytesToBase64url(new Uint8Array(signature));",
846
+ "}",
847
+ "",
848
+ "function safeEqual(a: string, b: string): boolean {",
849
+ " if (a.length !== b.length) return false;",
850
+ " let diff = 0;",
851
+ " for (let index = 0; index < a.length; index += 1) diff |= a.charCodeAt(index) ^ b.charCodeAt(index);",
852
+ " return diff === 0;",
853
+ "}",
854
+ "",
855
+ "export async function encodeWorkOSSession(session: ForgeWorkOSSession, cookiePassword: string): Promise<string> {",
856
+ " const payload = base64url(JSON.stringify(session));",
857
+ " return `${payload}.${await sign(payload, cookiePassword)}`;",
858
+ "}",
859
+ "",
860
+ "export async function decodeWorkOSSession(value: string | undefined, cookiePassword: string): Promise<ForgeWorkOSSession | null> {",
861
+ " if (!value) return null;",
862
+ " const [payload, signature] = value.split(\".\");",
863
+ " if (!payload || !signature || !safeEqual(signature, await sign(payload, cookiePassword))) return null;",
864
+ " return JSON.parse(fromBase64url(payload)) as ForgeWorkOSSession;",
865
+ "}",
866
+ "",
867
+ "export function readCookie(header: string | null | undefined, name: string): string | undefined {",
868
+ " return (header ?? \"\")",
869
+ " .split(\";\")",
870
+ " .map((part) => part.trim())",
871
+ " .find((part) => part.startsWith(`${name}=`))",
872
+ " ?.slice(name.length + 1);",
873
+ "}",
874
+ "",
875
+ "export function sessionCookieName(config: ForgeWorkOSAuthKitConfig): string {",
876
+ " return config.cookieName || \"forgeos_workos_session\";",
877
+ "}",
878
+ "",
879
+ "export async function createWorkOSSessionCookie(config: ForgeWorkOSAuthKitConfig, session: ForgeWorkOSSession): Promise<string> {",
880
+ " const value = await encodeWorkOSSession(session, config.cookiePassword);",
881
+ " return `${sessionCookieName(config)}=${value}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=2592000`;",
882
+ "}",
883
+ "",
884
+ "export function clearWorkOSSessionCookie(config: ForgeWorkOSAuthKitConfig): string {",
885
+ " return `${sessionCookieName(config)}=; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=0`;",
886
+ "}",
887
+ "",
888
+ "export function workOSSessionToClaims(session: ForgeWorkOSSession): ForgeWorkOSClaims {",
889
+ " return {",
890
+ " sub: session.user.id,",
891
+ " ...(session.user.email ? { email: String(session.user.email) } : {}),",
892
+ " ...(session.user.firstName || session.user.lastName ? { name: [session.user.firstName, session.user.lastName].filter(Boolean).join(\" \") } : {}),",
893
+ " ...(session.organizationId ? { organization_id: session.organizationId } : {}),",
894
+ " ...(session.organizationMembershipId ? { organization_membership_id: session.organizationMembershipId } : {}),",
895
+ " ...(session.role ? { role: session.role } : {}),",
896
+ " ...(session.roles ? { roles: session.roles } : {}),",
897
+ " ...(session.permissions ? { permissions: session.permissions } : {}),",
898
+ " };",
899
+ "}",
900
+ "",
901
+ ].join("\n");
902
+ }
903
+
904
+ export function renderWorkosAuthRoutes(input: IntegrationTemplateInput): string {
905
+ const fallbackRolePermissions = rolePermissions(permissionsFromApp(input));
906
+ return [
907
+ "/** Forge generated framework-agnostic WorkOS AuthKit routes. */",
908
+ 'import type { WorkOS } from "@workos-inc/node";',
909
+ 'import type { ForgeWorkOSAuthKitConfig } from "../../packages/workos.server.js";',
910
+ 'import { clearWorkOSSessionCookie, createWorkOSSessionCookie, decodeWorkOSSession, readCookie, sessionCookieName, workOSSessionToClaims, type ForgeWorkOSSession } from "./session.js";',
911
+ "",
912
+ "export interface WorkOSAuthRoutesOptions {",
913
+ " workos: WorkOS;",
914
+ " config: ForgeWorkOSAuthKitConfig;",
915
+ " resolveOrganization?: (input: { user: Record<string, unknown>; accessToken: string; organizationId?: string; organizationMembershipId?: string; result: Record<string, unknown> }) => Promise<{ organizationId?: string; organizationMembershipId?: string; role?: string; roles?: string[]; permissions?: string[] }> | { organizationId?: string; organizationMembershipId?: string; role?: string; roles?: string[]; permissions?: string[] };",
916
+ "}",
917
+ "",
918
+ "function jsonResponse(body: unknown, status = 200, headers?: HeadersInit): Response {",
919
+ " return new Response(JSON.stringify(body), { status, headers: { \"content-type\": \"application/json\", ...(headers ?? {}) } });",
920
+ "}",
921
+ "",
922
+ "function redirectResponse(location: string, headers?: HeadersInit): Response {",
923
+ " return new Response(null, { status: 302, headers: { location, ...(headers ?? {}) } });",
924
+ "}",
925
+ "",
926
+ "function normalizeLocalRedirect(value: string | null | undefined, fallback = \"/\"): string {",
927
+ " if (!value || !value.startsWith(\"/\") || value.startsWith(\"//\")) return fallback;",
928
+ " return value;",
929
+ "}",
930
+ "",
931
+ "function workOSUserRecord(user: unknown): Record<string, unknown> {",
932
+ " if (!user || typeof user !== \"object\") return {};",
933
+ " return { ...(user as object) } as Record<string, unknown>;",
934
+ "}",
935
+ "",
936
+ "function workOSSessionUser(user: Record<string, unknown>): ForgeWorkOSSession[\"user\"] {",
937
+ " const rawId = user.id ?? user.objectId ?? user.userId;",
938
+ " return {",
939
+ " ...user,",
940
+ " id: typeof rawId === \"string\" && rawId.length > 0 ? rawId : String(rawId ?? \"workos-user\"),",
941
+ " ...(typeof user.email === \"string\" ? { email: user.email } : {}),",
942
+ " ...(typeof user.firstName === \"string\" ? { firstName: user.firstName } : {}),",
943
+ " ...(typeof user.lastName === \"string\" ? { lastName: user.lastName } : {}),",
944
+ " };",
945
+ "}",
946
+ "",
947
+ "const forgeWorkOSFallbackRolePermissions = {",
948
+ ...Object.entries(fallbackRolePermissions).map(([role, permissions]) =>
949
+ ` ${JSON.stringify(role)}: [${permissions.map((permission) => JSON.stringify(permission)).join(", ")}],`
950
+ ),
951
+ "} as const;",
952
+ "",
953
+ "function fallbackPermissionsForRole(role: string | undefined): string[] {",
954
+ " if (!role) return [];",
955
+ " const permissions = forgeWorkOSFallbackRolePermissions[role.toLowerCase() as keyof typeof forgeWorkOSFallbackRolePermissions];",
956
+ " return permissions ? [...permissions] : [];",
957
+ "}",
958
+ "",
959
+ "export function getWorkOSAuthorizationUrl(input: { workos: WorkOS; config: ForgeWorkOSAuthKitConfig; state?: string }): string {",
960
+ " if (!input.config.redirectUri) throw new Error(\"WORKOS_REDIRECT_URI is required\");",
961
+ " const url = new URL(input.workos.userManagement.getAuthorizationUrl({",
962
+ " provider: \"authkit\",",
963
+ " redirectUri: input.config.redirectUri,",
964
+ " clientId: input.config.clientId,",
965
+ " ...(input.state ? { state: input.state } : {}),",
966
+ " }));",
967
+ " url.searchParams.set(\"response_type\", \"code\");",
968
+ " url.searchParams.delete(\"organization_id\");",
969
+ " return url.toString();",
970
+ "}",
971
+ "",
972
+ "export async function authenticateWorkOSCallback(input: { workos: WorkOS; config: ForgeWorkOSAuthKitConfig; code: string; resolveOrganization?: WorkOSAuthRoutesOptions[\"resolveOrganization\"] }): Promise<ForgeWorkOSSession> {",
973
+ " const result = await input.workos.userManagement.authenticateWithCode({",
974
+ " code: input.code,",
975
+ " clientId: input.config.clientId,",
976
+ " });",
977
+ " const user = workOSUserRecord(result.user);",
978
+ " const rawResult = result as unknown as Record<string, unknown>;",
979
+ " const resultOrganization = (rawResult.organization ?? {}) as Record<string, unknown>;",
980
+ " const resultMembership = (rawResult.organizationMembership ?? {}) as Record<string, unknown>;",
981
+ " const resultOrganizationId = typeof rawResult.organizationId === \"string\" ? rawResult.organizationId : typeof resultOrganization.id === \"string\" ? resultOrganization.id : undefined;",
982
+ " const resultOrganizationMembershipId = typeof rawResult.organizationMembershipId === \"string\" ? rawResult.organizationMembershipId : typeof resultMembership.id === \"string\" ? resultMembership.id : undefined;",
983
+ " const resolved = await input.resolveOrganization?.({",
984
+ " user,",
985
+ " accessToken: result.accessToken,",
986
+ " organizationId: resultOrganizationId,",
987
+ " organizationMembershipId: resultOrganizationMembershipId,",
988
+ " result: rawResult,",
989
+ " });",
990
+ " const resultRoles = Array.isArray(rawResult.roles) ? rawResult.roles.filter((role): role is string => typeof role === \"string\") : [];",
991
+ " const resultPermissions = Array.isArray(rawResult.permissions) ? rawResult.permissions.filter((permission): permission is string => typeof permission === \"string\") : [];",
992
+ " const organizationId = resolved?.organizationId ?? resultOrganizationId;",
993
+ " const organizationMembershipId = resolved?.organizationMembershipId ?? resultOrganizationMembershipId;",
994
+ " const role = resolved?.role ?? (typeof rawResult.role === \"string\" ? rawResult.role : resultRoles[0]);",
995
+ " const roles = resolved?.roles ?? resultRoles;",
996
+ " const permissions = resolved?.permissions ?? (resultPermissions.length > 0 ? resultPermissions : fallbackPermissionsForRole(role));",
997
+ " return {",
998
+ " user: workOSSessionUser(user),",
999
+ " accessToken: result.accessToken,",
1000
+ " ...(result.refreshToken ? { refreshToken: result.refreshToken } : {}),",
1001
+ " ...(organizationId ? { organizationId } : {}),",
1002
+ " ...(organizationMembershipId ? { organizationMembershipId } : {}),",
1003
+ " ...(role ? { role } : {}),",
1004
+ " ...(roles.length > 0 ? { roles } : {}),",
1005
+ " ...(permissions.length > 0 ? { permissions } : {}),",
1006
+ " issuedAt: Math.floor(Date.now() / 1000),",
1007
+ " };",
1008
+ "}",
1009
+ "",
1010
+ "export async function handleWorkOSAuthRequest(request: Request, options: WorkOSAuthRoutesOptions): Promise<Response> {",
1011
+ " const url = new URL(request.url);",
1012
+ " if (request.method === \"GET\" && url.pathname === \"/login\") {",
1013
+ " return redirectResponse(getWorkOSAuthorizationUrl({",
1014
+ " workos: options.workos,",
1015
+ " config: options.config,",
1016
+ " state: normalizeLocalRedirect(url.searchParams.get(\"returnTo\"), options.config.postLoginRedirectUri ?? \"/\"),",
1017
+ " }));",
1018
+ " }",
1019
+ " if (request.method === \"GET\" && url.pathname === \"/callback\") {",
1020
+ " const code = url.searchParams.get(\"code\");",
1021
+ " if (!code) return jsonResponse({ ok: false, error: \"missing_code\" }, 400);",
1022
+ " const session = await authenticateWorkOSCallback({",
1023
+ " workos: options.workos,",
1024
+ " config: options.config,",
1025
+ " code,",
1026
+ " resolveOrganization: options.resolveOrganization,",
1027
+ " });",
1028
+ " const location = normalizeLocalRedirect(url.searchParams.get(\"state\"), options.config.postLoginRedirectUri || \"/\");",
1029
+ " return redirectResponse(location, { \"set-cookie\": await createWorkOSSessionCookie(options.config, session) });",
1030
+ " }",
1031
+ " if (request.method === \"POST\" && url.pathname === \"/logout\") {",
1032
+ " return redirectResponse(normalizeLocalRedirect(options.config.postLogoutRedirectUri, \"/\"), { \"set-cookie\": clearWorkOSSessionCookie(options.config) });",
1033
+ " }",
1034
+ " if (request.method === \"GET\" && url.pathname === \"/session\") {",
1035
+ " const session = await decodeWorkOSSession(readCookie(request.headers.get(\"cookie\"), sessionCookieName(options.config)), options.config.cookiePassword);",
1036
+ " return session ? jsonResponse({ ok: true, session: { user: session.user, claims: workOSSessionToClaims(session) } }) : jsonResponse({ ok: false, session: null }, 401);",
1037
+ " }",
1038
+ " return jsonResponse({ ok: false, error: \"not_found\" }, 404);",
1039
+ "}",
1040
+ "",
1041
+ "export const workosAuthHttpRoutes = [",
1042
+ ' { method: "GET", path: "/login" },',
1043
+ ' { method: "GET", path: "/callback" },',
1044
+ ' { method: "POST", path: "/logout" },',
1045
+ ' { method: "GET", path: "/session" },',
1046
+ "] as const;",
1047
+ "",
1048
+ ].join("\n");
1049
+ }
1050
+
1051
+ export function renderWorkosHttpHandler(_input: IntegrationTemplateInput): string {
1052
+ return [
1053
+ "/** Forge generated WorkOS webhook HTTP handler.",
1054
+ " * Mount this handler at POST /webhooks/workos in your web/server framework.",
1055
+ " */",
1056
+ "import { handleWorkOSWebhook, type WorkOSWebhookEvent, type WorkOSWebhookVerificationInput } from \"./webhook.js\";",
1057
+ "",
1058
+ "export interface WorkOSWebhookHttpHandlerOptions extends Pick<WorkOSWebhookVerificationInput, \"secrets\" | \"replayStore\" | \"nowSeconds\" | \"toleranceSeconds\"> {",
1059
+ " onEvent?: (event: WorkOSWebhookEvent) => Promise<void> | void;",
1060
+ "}",
1061
+ "",
1062
+ "function jsonResponse(body: unknown, status: number): Response {",
1063
+ " return new Response(JSON.stringify(body), {",
1064
+ " status,",
1065
+ " headers: { \"content-type\": \"application/json\" },",
1066
+ " });",
1067
+ "}",
1068
+ "",
1069
+ "export async function handleWorkOSWebhookRequest(request: Request, options: WorkOSWebhookHttpHandlerOptions): Promise<Response> {",
1070
+ " if (request.method !== \"POST\") {",
1071
+ " return jsonResponse({ ok: false, error: \"method_not_allowed\" }, 405);",
1072
+ " }",
1073
+ " const payload = await request.text();",
1074
+ " const result = await handleWorkOSWebhook({",
1075
+ " payload,",
1076
+ " signatureHeader: request.headers.get(\"WorkOS-Signature\"),",
1077
+ " secrets: options.secrets,",
1078
+ " replayStore: options.replayStore,",
1079
+ " nowSeconds: options.nowSeconds,",
1080
+ " toleranceSeconds: options.toleranceSeconds,",
1081
+ " });",
1082
+ " if (result.status !== 200 || !result.event) {",
1083
+ " return jsonResponse({ ok: false, error: result.error ?? \"invalid_workos_webhook\" }, 401);",
1084
+ " }",
1085
+ " await options.onEvent?.(result.event);",
1086
+ " return jsonResponse({ ok: true, event: result.event.event ?? \"unknown\" }, 200);",
1087
+ "}",
1088
+ "",
1089
+ "export const workosWebhookHttpRoute = {",
1090
+ " method: \"POST\",",
1091
+ " path: \"/webhooks/workos\",",
1092
+ " handler: \"handleWorkOSWebhookRequest\",",
1093
+ " signatureHeader: \"WorkOS-Signature\",",
1094
+ "} as const;",
1095
+ "",
1096
+ ].join("\n");
1097
+ }
1098
+
1099
+ export function renderWorkosWebhook(_input: IntegrationTemplateInput): string {
1100
+ return [
1101
+ "/** Forge generated WorkOS webhook verification and endpoint helper. */",
1102
+ 'import { verifyWebhookSignature, type WebhookReplayStore } from "forge/server";',
1103
+ 'import type { SecretsContext } from "../../secretsContext.js";',
1104
+ "",
1105
+ "export interface WorkOSWebhookEvent {",
1106
+ " id?: string;",
1107
+ " event?: string;",
1108
+ " data?: unknown;",
1109
+ " created_at?: string;",
1110
+ " context?: Record<string, unknown>;",
1111
+ "}",
1112
+ "",
1113
+ "export interface WorkOSWebhookVerificationInput {",
1114
+ " payload: string | Uint8Array;",
1115
+ " signatureHeader: string | null | undefined;",
1116
+ " secrets: SecretsContext;",
1117
+ " eventId?: string;",
1118
+ " replayStore?: WebhookReplayStore;",
1119
+ " nowSeconds?: number;",
1120
+ " toleranceSeconds?: number;",
1121
+ "}",
1122
+ "",
1123
+ "function bodyString(input: string | Uint8Array): string {",
1124
+ ' return typeof input === "string" ? input : new TextDecoder().decode(input);',
1125
+ "}",
1126
+ "",
1127
+ "export async function verifyWorkOSWebhook(input: WorkOSWebhookVerificationInput): Promise<WorkOSWebhookEvent> {",
1128
+ ' const secret = input.secrets.get("WORKOS_WEBHOOK_SECRET");',
1129
+ ' if (!secret) throw new Error("WORKOS_WEBHOOK_SECRET is required");',
1130
+ " const verified = await verifyWebhookSignature({",
1131
+ ' provider: "workos",',
1132
+ " secret,",
1133
+ " payload: input.payload,",
1134
+ " signatureHeader: input.signatureHeader,",
1135
+ " eventId: input.eventId,",
1136
+ " replayStore: input.replayStore,",
1137
+ " nowSeconds: input.nowSeconds,",
1138
+ " toleranceSeconds: input.toleranceSeconds,",
1139
+ " });",
1140
+ ' if (!verified.ok) throw new Error(`${verified.code}: ${verified.reason}`);',
1141
+ " return JSON.parse(bodyString(input.payload)) as WorkOSWebhookEvent;",
1142
+ "}",
1143
+ "",
1144
+ "export async function handleWorkOSWebhook(input: WorkOSWebhookVerificationInput): Promise<{ status: 200 | 401; event?: WorkOSWebhookEvent; error?: string }> {",
1145
+ " try {",
1146
+ " const event = await verifyWorkOSWebhook(input);",
1147
+ " return { status: 200, event };",
1148
+ " } catch (error) {",
1149
+ " return { status: 401, error: error instanceof Error ? error.message : String(error) };",
1150
+ " }",
1151
+ "}",
1152
+ "",
1153
+ "export const workosWebhookEndpoint = {",
1154
+ ' method: "POST",',
1155
+ ' path: "/webhooks/workos",',
1156
+ ' signatureHeader: "WorkOS-Signature",',
1157
+ ' secret: "WORKOS_WEBHOOK_SECRET",',
1158
+ "} as const;",
1159
+ "",
1160
+ ].join("\n");
1161
+ }
1162
+
1163
+ export function renderWorkosSeed(_input: IntegrationTemplateInput): string {
1164
+ return [
1165
+ "/** Forge generated WorkOS seed metadata. */",
1166
+ "",
1167
+ "export const workosSeedFile = \"workos-seed.yml\" as const;",
1168
+ "",
1169
+ "export const workosSeedCommands = [",
1170
+ " `npx workos@latest seed --file ${workosSeedFile}`,",
1171
+ " `npx workos@latest seed --file ${workosSeedFile} --clean`,",
1172
+ "] as const;",
1173
+ "",
1174
+ "export const workosSeedStateFile = \".workos-seed-state.json\" as const;",
1175
+ "",
1176
+ ].join("\n");
1177
+ }
1178
+
1179
+ export function renderWorkosSeedYaml(input: IntegrationTemplateInput): string {
1180
+ const permissions = permissionsFromApp(input);
1181
+ const roles = rolePermissions(permissions);
1182
+ const resourceTypes = workosFgaEnabled(input) ? resourceTypesFromApp(input) : [];
1183
+ const resourceTypeSection = resourceTypes.length > 0
1184
+ ? [
1185
+ "",
1186
+ "resource_types:",
1187
+ ...resourceTypes.flatMap((resource) => [
1188
+ ` - slug: '${resource.slug}'`,
1189
+ ` name: '${resource.name}'`,
1190
+ ...(resource.parent ? [` parent: '${resource.parent}'`] : []),
1191
+ ]),
1192
+ ]
1193
+ : [];
1194
+ return [
1195
+ "# Forge generated WorkOS seed file.",
1196
+ "# Review names, domains, redirect URIs, and origins before applying to a real WorkOS environment.",
1197
+ "permissions:",
1198
+ ...permissions.flatMap((permission) => [
1199
+ ` - name: '${toTitle(permission)}'`,
1200
+ ` slug: '${permission}'`,
1201
+ ]),
1202
+ ...resourceTypeSection,
1203
+ "",
1204
+ "roles:",
1205
+ ...Object.entries(roles).flatMap(([role, rolePermissions]) => [
1206
+ ` - name: '${toTitle(role)}'`,
1207
+ ` slug: '${role}'`,
1208
+ " permissions:",
1209
+ ...rolePermissions.map((permission) => ` - '${permission}'`),
1210
+ ]),
1211
+ "",
1212
+ "organizations:",
1213
+ " - name: 'Acme Corp'",
1214
+ " domains: ['acme.test']",
1215
+ " - name: 'Globex'",
1216
+ " domains: ['globex.test']",
1217
+ "",
1218
+ "config:",
1219
+ " redirect_uris:",
1220
+ " - 'http://localhost:5173'",
1221
+ " - 'http://localhost:5173/callback'",
1222
+ " - 'http://127.0.0.1:5173'",
1223
+ " - 'http://127.0.0.1:5173/callback'",
1224
+ " cors_origins:",
1225
+ " - 'http://localhost:5173'",
1226
+ " - 'http://127.0.0.1:5173'",
1227
+ " homepage_url: 'http://localhost:5173'",
1228
+ " webhook_endpoints:",
1229
+ " - url: 'http://localhost:3765/webhooks/workos'",
1230
+ " events:",
1231
+ " - 'authentication.succeeded'",
1232
+ " - 'organization_membership.created'",
1233
+ " - 'organization_membership.updated'",
1234
+ " - 'organization_membership.deleted'",
1235
+ "",
1236
+ ].join("\n");
1237
+ }
1238
+
1239
+ export function renderWorkosTestkit(input: IntegrationTemplateInput): string {
1240
+ const permissions = permissionsFromApp(input);
1241
+ const roles = rolePermissions(permissions);
1242
+ const ownerPermissions = JSON.stringify(roles.owner);
1243
+ const managerPermissions = JSON.stringify(roles.manager);
1244
+ const memberPermissions = JSON.stringify(roles.member);
1245
+ return [
1246
+ "/** Forge generated mock testkit for WorkOS auth and authorization. */",
1247
+ "",
1248
+ "type Resource = { organizationId: string; resourceTypeSlug: string; externalId: string; name: string; parentResourceTypeSlug?: string; parentResourceExternalId?: string };",
1249
+ "type Membership = { id: string; organizationId: string; roleSlug: string; permissions: string[] };",
1250
+ "type RoleAssignment = { organizationMembershipId: string; roleSlug: string; resourceTypeSlug: string; resourceExternalId: string; permissions: string[] };",
1251
+ "",
1252
+ "export function createWorkOSMock() {",
1253
+ " const organizations = new Map<string, { id: string; name: string }>();",
1254
+ " organizations.set(\"org_acme\", { id: \"org_acme\", name: \"Acme Corp\" });",
1255
+ " organizations.set(\"org_globex\", { id: \"org_globex\", name: \"Globex\" });",
1256
+ " const memberships = new Map<string, Membership>([",
1257
+ ` ["om_acme_owner", { id: "om_acme_owner", organizationId: "org_acme", roleSlug: "owner", permissions: ${ownerPermissions} }],`,
1258
+ ` ["om_acme_member", { id: "om_acme_member", organizationId: "org_acme", roleSlug: "member", permissions: ${memberPermissions} }],`,
1259
+ ` ["om_globex_member", { id: "om_globex_member", organizationId: "org_globex", roleSlug: "member", permissions: ${memberPermissions} }],`,
1260
+ " ]);",
1261
+ " const resources = new Map<string, Resource>();",
1262
+ " const roleAssignments: RoleAssignment[] = [];",
1263
+ " const accessChecks: Array<{ organizationMembershipId: string; permissionSlug: string; resourceTypeSlug: string; resourceExternalId: string; authorized: boolean; reason: string }> = [];",
1264
+ " const key = (organizationId: string, resourceTypeSlug: string, externalId: string) => `${organizationId}|${resourceTypeSlug}|${externalId}`;",
1265
+ " const resourceOrg = (resourceTypeSlug: string, resourceExternalId: string): string | undefined => {",
1266
+ " for (const resource of resources.values()) {",
1267
+ " if (resource.resourceTypeSlug === resourceTypeSlug && resource.externalId === resourceExternalId) return resource.organizationId;",
1268
+ " }",
1269
+ " return undefined;",
1270
+ " };",
1271
+ " return {",
1272
+ " organizations: {",
1273
+ " createOrganization: async (input: { name: string; externalId?: string }) => {",
1274
+ " const id = input.externalId ?? `org_${organizations.size + 1}`;",
1275
+ " const organization = { id, name: input.name };",
1276
+ " organizations.set(id, organization);",
1277
+ " return organization;",
1278
+ " },",
1279
+ " listOrganizations: async () => ({ data: [...organizations.values()] }),",
1280
+ " },",
1281
+ " userManagement: {",
1282
+ " createOrganizationMembership: async (input: { organizationId: string; roleSlug: string; userId?: string }) => {",
1283
+ " const id = `om_${input.organizationId}_${memberships.size + 1}`;",
1284
+ ` const permissions = input.roleSlug === "owner" ? ${ownerPermissions} : input.roleSlug === "manager" ? ${managerPermissions} : ${memberPermissions};`,
1285
+ " const membership = { id, organizationId: input.organizationId, roleSlug: input.roleSlug, permissions };",
1286
+ " memberships.set(id, membership);",
1287
+ " return membership;",
1288
+ " },",
1289
+ " },",
1290
+ " authorization: {",
1291
+ " createResource: async (input: Resource) => {",
1292
+ " resources.set(key(input.organizationId, input.resourceTypeSlug, input.externalId), input);",
1293
+ " return { id: `authz_resource_${resources.size}`, ...input };",
1294
+ " },",
1295
+ " getResourceByExternalId: async (input: { organizationId: string; resourceTypeSlug: string; externalId: string }) => {",
1296
+ " const resource = resources.get(key(input.organizationId, input.resourceTypeSlug, input.externalId));",
1297
+ " if (!resource) throw new Error(\"resource_not_found\");",
1298
+ " return { id: `authz_resource_${input.externalId}`, ...resource };",
1299
+ " },",
1300
+ " updateResourceByExternalId: async (input: Resource) => {",
1301
+ " resources.set(key(input.organizationId, input.resourceTypeSlug, input.externalId), input);",
1302
+ " return { id: `authz_resource_${input.externalId}`, ...input };",
1303
+ " },",
1304
+ " assignRole: async (input: { organizationMembershipId: string; roleSlug: string; resourceTypeSlug: string; resourceExternalId: string; permissions?: string[] }) => {",
1305
+ " const assignment = { organizationMembershipId: input.organizationMembershipId, roleSlug: input.roleSlug, resourceTypeSlug: input.resourceTypeSlug, resourceExternalId: input.resourceExternalId, permissions: input.permissions ?? [] };",
1306
+ " roleAssignments.push(assignment);",
1307
+ " return assignment;",
1308
+ " },",
1309
+ " check: async (input: { organizationMembershipId: string; permissionSlug: string; resourceTypeSlug: string; resourceExternalId: string }) => {",
1310
+ " const membership = memberships.get(input.organizationMembershipId);",
1311
+ " const organizationId = resourceOrg(input.resourceTypeSlug, input.resourceExternalId);",
1312
+ " const sameTenant = Boolean(membership && organizationId && membership.organizationId === organizationId);",
1313
+ " const direct = Boolean(membership?.permissions.includes(input.permissionSlug));",
1314
+ " const assigned = roleAssignments.some((assignment) => assignment.organizationMembershipId === input.organizationMembershipId && assignment.resourceTypeSlug === input.resourceTypeSlug && assignment.resourceExternalId === input.resourceExternalId && assignment.permissions.includes(input.permissionSlug));",
1315
+ " const authorized = sameTenant && (direct || assigned);",
1316
+ " accessChecks.push({",
1317
+ " organizationMembershipId: input.organizationMembershipId,",
1318
+ " permissionSlug: input.permissionSlug,",
1319
+ " resourceTypeSlug: input.resourceTypeSlug,",
1320
+ " resourceExternalId: input.resourceExternalId,",
1321
+ " authorized,",
1322
+ " reason: sameTenant ? \"permission\" : \"cross_tenant\",",
1323
+ " });",
1324
+ " return { authorized };",
1325
+ " },",
1326
+ " },",
1327
+ " getResources: () => [...resources.values()],",
1328
+ " getMemberships: () => [...memberships.values()],",
1329
+ " getRoleAssignments: () => [...roleAssignments],",
1330
+ " getAccessChecks: () => [...accessChecks],",
1331
+ " } as const;",
1332
+ "}",
1333
+ "",
1334
+ ].join("\n");
1335
+ }
1336
+
1337
+ export function renderWorkosDoc(input: IntegrationTemplateInput): string {
1338
+ const fgaEnabled = workosFgaEnabled(input);
1339
+ const fgaCommandLines = fgaEnabled
1340
+ ? [
1341
+ "forge workos fga plan --file workos-seed.yml --write --json",
1342
+ "forge workos fga sync --file workos-seed.yml --json",
1343
+ "forge workos fga prove --file workos-seed.yml --json",
1344
+ ]
1345
+ : [
1346
+ "# Optional when resource-level WorkOS Authorization is required:",
1347
+ "forge add auth workos --with-fga",
1348
+ ];
1349
+ const fgaSection = fgaEnabled
1350
+ ? [
1351
+ "## FGA strategy",
1352
+ "",
1353
+ "Use JWT permissions for organization-wide checks. Use the WorkOS Authorization API for resource-level checks such as project, workspace, or task access. Keep those API calls in actions, workflows, endpoints, or server code; commands and queries should consume validated auth/policy context rather than importing the WorkOS SDK.",
1354
+ "",
1355
+ "Use `syncWorkOSResourceGraph(...)` after app resources are created or renamed to mirror the Forge resource graph into WorkOS FGA resources. Use `canWorkOS(...)` with `ForgeWorkOSFgaDecisionCache` for resource-level checks; it calls WorkOS `authorization.check` with `organizationMembershipId`, `permissionSlug`, `resourceTypeSlug`, and `resourceExternalId`, emits optional telemetry, and denies by default on provider errors unless `fallback: \"throw\"` is set.",
1356
+ "",
1357
+ "Use `assertWorkOSResourceTenant(...)` before resource-level FGA checks whenever the resource came from user input or an external id. This keeps Acme resources from being checked under Globex membership context.",
1358
+ ]
1359
+ : [
1360
+ "## Authorization strategy",
1361
+ "",
1362
+ "The default WorkOS recipe uses AuthKit, organization claims, roles, permissions, Forge policies, and tenant isolation. That is enough for most SaaS apps.",
1363
+ "",
1364
+ "FGA is optional. Add it only when your app needs resource-level authorization beyond organization roles and permission claims, for example per-project, per-document, or inherited resource access.",
1365
+ ];
1366
+ return [
1367
+ "# WorkOS integration",
1368
+ "",
1369
+ "Generated by Forge (recipe v1).",
1370
+ "",
1371
+ "WorkOS is the preferred ForgeOS auth adapter for B2B, organization-scoped, agent-operable authentication and authorization.",
1372
+ "",
1373
+ "## What this recipe does",
1374
+ "",
1375
+ "- Installs `@workos-inc/node` for server/action/workflow/endpoint code.",
1376
+ "- Installs `@workos-inc/authkit-react` and generates a Vite React bridge when a `web/package.json` workspace exists.",
1377
+ "- Registers WorkOS secret names without storing values.",
1378
+ "- Generates an AuthKit/RBAC checklist and a `workos-seed.yml` for roles, permissions, organizations, redirect URIs, and CORS origins.",
1379
+ ...(fgaEnabled ? ["- Generates optional WorkOS FGA resource graph helpers and resource type seed metadata."] : []),
1380
+ "- Generates framework-agnostic AuthKit route/session helpers for `/login`, `/callback`, `/logout`, and `/session`.",
1381
+ "- Generates a framework-agnostic `Request -> Response` webhook handler for `POST /webhooks/workos`.",
1382
+ "- Marks WorkOS SDK imports as forbidden from commands, queries, liveQueries, client, shared, and edge code.",
1383
+ "",
1384
+ "## Commands",
1385
+ "",
1386
+ "```bash",
1387
+ "forge add auth workos",
1388
+ "forge workos install --yes --json",
1389
+ "forge workos doctor --json",
1390
+ "forge workos doctor --yes --json",
1391
+ "forge workos seed --file workos-seed.yml --dry-run --json",
1392
+ "forge workos seed --file workos-seed.yml --json",
1393
+ "forge workos prove --file workos-seed.yml --json",
1394
+ "forge workos setup --real --file workos-seed.yml --json",
1395
+ ...fgaCommandLines,
1396
+ "forge auth check --json",
1397
+ "forge auth prove --json",
1398
+ "forge check --json",
1399
+ "```",
1400
+ "",
1401
+ "## Required environment names",
1402
+ "",
1403
+ ...input.secrets
1404
+ .filter((secret) => secret.required !== false)
1405
+ .map((secret) => `- \`${secret.envVar}\``),
1406
+ "",
1407
+ "## Optional environment names",
1408
+ "",
1409
+ ...input.secrets
1410
+ .filter((secret) => secret.required === false)
1411
+ .map((secret) => `- \`${secret.envVar}\``),
1412
+ "",
1413
+ "## Production auth mapping",
1414
+ "",
1415
+ "- `FORGE_AUTH_MODE=oidc` or `jwt`",
1416
+ "- `FORGE_AUTH_ISSUER=https://api.workos.com` for hosted WorkOS AuthKit.",
1417
+ "- `FORGE_AUTH_JWKS_URI=https://api.workos.com/sso/jwks/<WORKOS_CLIENT_ID>` for hosted WorkOS AuthKit.",
1418
+ "- `FORGE_AUTH_AUDIENCE` matches the API audience configured for the app.",
1419
+ "- Tenant claim should map to WorkOS `org_id` or a normalized `tenant_id` claim.",
1420
+ "- Role and permissions should come from WorkOS organization roles/permissions, not from dev headers.",
1421
+ "- Use generated `src/policies.workos.ts` as a permission-first policy template; it uses `canPermission(...)` rather than role-only checks.",
1422
+ "- `dev-headers` and frontend `devAuth` are local-only simulation paths. Real WorkOS production-like tests should use AuthKit and Bearer tokens.",
1423
+ "",
1424
+ "## AuthKit routes",
1425
+ "",
1426
+ "Mount `handleWorkOSAuthRequest(request, { workos, config, resolveOrganization })` for `/login`, `/callback`, `/logout`, and `/session`. The callback exchanges the AuthKit authorization code, signs an HttpOnly session cookie, and exposes Forge-normalized claims through `/session`.",
1427
+ "",
1428
+ ...fgaSection,
1429
+ "",
1430
+ "## Webhook route",
1431
+ "",
1432
+ "Mount `handleWorkOSWebhookRequest(request, { secrets, replayStore, onEvent })` at `POST /webhooks/workos`. The handler verifies `WorkOS-Signature`, rejects stale/replayed/tampered events, and returns JSON responses.",
1433
+ "",
1434
+ `Compatible contexts: ${input.compatible.join(", ")}`,
1435
+ "",
1436
+ `Incompatible contexts: ${input.incompatible.join(", ")}`,
1437
+ "",
1438
+ ].join("\n");
1439
+ }