forgeos 0.1.0-alpha.65 → 0.1.0-alpha.67

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 (284) hide show
  1. package/.npmignore +19 -19
  2. package/AGENTS.md +1 -1
  3. package/CHANGELOG.md +24 -0
  4. package/LICENSE +21 -21
  5. package/README.md +525 -525
  6. package/adapters/java/README.md +68 -68
  7. package/adapters/java/pom.xml +47 -47
  8. package/adapters/java/src/main/java/dev/forgeos/adapter/Auth.java +20 -20
  9. package/adapters/java/src/main/java/dev/forgeos/adapter/Diagnostic.java +16 -16
  10. package/adapters/java/src/main/java/dev/forgeos/adapter/Entry.java +38 -38
  11. package/adapters/java/src/main/java/dev/forgeos/adapter/EntryKind.java +16 -16
  12. package/adapters/java/src/main/java/dev/forgeos/adapter/ErrorInfo.java +4 -4
  13. package/adapters/java/src/main/java/dev/forgeos/adapter/Forge.java +94 -94
  14. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeCall.java +12 -12
  15. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeContext.java +11 -11
  16. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeHandler.java +8 -8
  17. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeHttpHandler.java +179 -179
  18. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeRegistry.java +121 -121
  19. package/adapters/java/src/main/java/dev/forgeos/adapter/Json.java +14 -14
  20. package/adapters/java/src/main/java/dev/forgeos/adapter/Manifest.java +14 -14
  21. package/adapters/java/src/main/java/dev/forgeos/adapter/RequestEnvelope.java +6 -6
  22. package/adapters/java/src/main/java/dev/forgeos/adapter/ResponseEnvelope.java +25 -25
  23. package/adapters/java/src/main/java/dev/forgeos/adapter/Risk.java +18 -18
  24. package/adapters/java/src/main/java/dev/forgeos/adapter/Schemas.java +36 -36
  25. package/adapters/java/src/main/java/dev/forgeos/adapter/Service.java +65 -65
  26. package/adapters/java/src/main/java/dev/forgeos/adapter/TransactionMode.java +18 -18
  27. package/adapters/java/src/main/java/dev/forgeos/adapter/TypedForgeHandler.java +6 -6
  28. package/adapters/java-spring-boot-starter/README.md +32 -32
  29. package/adapters/java-spring-boot-starter/pom.xml +49 -49
  30. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeCommand.java +22 -22
  31. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeExternalService.java +15 -15
  32. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeQuery.java +16 -16
  33. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeServiceBeanCondition.java +18 -18
  34. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeSpringAutoConfiguration.java +16 -16
  35. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeSpringRuntime.java +104 -104
  36. package/adapters/java-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +1 -1
  37. package/bin/forge.mjs +38 -38
  38. package/docs/agent-fabric.md +131 -0
  39. package/examples/java-billing/pom.xml +60 -60
  40. package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/CreateInvoiceInput.java +4 -4
  41. package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/Invoice.java +11 -11
  42. package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/Main.java +127 -127
  43. package/package.json +5 -4
  44. package/packages/eslint-plugin-forge/index.ts +15 -15
  45. package/packages/eslint-plugin-forge/package.json +10 -10
  46. package/packages/eslint-plugin-forge/src/check-source.ts +95 -95
  47. package/packages/eslint-plugin-forge/src/load-artifacts.ts +24 -24
  48. package/packages/eslint-plugin-forge/src/rule-no-forge-guard-violation.ts +93 -93
  49. package/schemas/agent-fabric/v0.1/control-event.schema.json +281 -281
  50. package/scripts/field-test-forgeos.mjs +1021 -1021
  51. package/src/forge/_generated/releaseManifest.json +1 -1
  52. package/src/forge/_generated/releaseManifest.ts +3 -3
  53. package/src/forge/agent-adapters/index.ts +7 -3
  54. package/src/forge/agent-adapters/types.ts +1 -0
  55. package/src/forge/agent-fabric/attached-snapshot.ts +55 -0
  56. package/src/forge/agent-fabric/attached-task-contract.ts +105 -0
  57. package/src/forge/agent-fabric/attached-task-service.ts +127 -0
  58. package/src/forge/agent-fabric/attached-task-store.ts +109 -0
  59. package/src/forge/agent-fabric/codex-adversarial-review.ts +229 -0
  60. package/src/forge/agent-fabric/codex-sdk-worker.ts +163 -0
  61. package/src/forge/agent-fabric/index.ts +19 -0
  62. package/src/forge/agent-fabric/local-adaptive-digest-worker.mjs +26 -26
  63. package/src/forge/agent-fabric/local-change-review-service.ts +374 -0
  64. package/src/forge/agent-fabric/local-task-server.ts +324 -249
  65. package/src/forge/agent-fabric/managed-environment.ts +205 -0
  66. package/src/forge/agent-fabric/managed-run-contract.ts +102 -0
  67. package/src/forge/agent-fabric/managed-run-service.ts +392 -0
  68. package/src/forge/agent-fabric/managed-run-store.ts +135 -0
  69. package/src/forge/agent-fabric/managed-workspace.ts +214 -0
  70. package/src/forge/agent-fabric/readiness.ts +26 -0
  71. package/src/forge/agent-fabric/workflow-engine.ts +218 -0
  72. package/src/forge/agent-fabric/workflow-task-actions.ts +104 -0
  73. package/src/forge/agent-fabric/workflow-templates.ts +11 -0
  74. package/src/forge/agent-memory/bridge.ts +309 -47
  75. package/src/forge/agent-memory/context-pack.ts +8 -1
  76. package/src/forge/agent-memory/hook-runner.ts +38 -19
  77. package/src/forge/agent-memory/mcp.ts +500 -286
  78. package/src/forge/agent-memory/sources/codex-hook-runner.mjs +343 -234
  79. package/src/forge/agent-memory/sources/codex.ts +49 -7
  80. package/src/forge/agent-memory/types.ts +11 -0
  81. package/src/forge/bench.ts +154 -154
  82. package/src/forge/cair/action-journal.ts +61 -61
  83. package/src/forge/cair/action-parser.ts +314 -314
  84. package/src/forge/cair/action-validator.ts +40 -40
  85. package/src/forge/cair/actions.ts +1818 -1818
  86. package/src/forge/cair/format.ts +77 -77
  87. package/src/forge/cair/index.ts +106 -106
  88. package/src/forge/cair/query.ts +478 -478
  89. package/src/forge/cair/snapshot.ts +315 -315
  90. package/src/forge/cair/types.ts +248 -248
  91. package/src/forge/cli/ai.ts +816 -816
  92. package/src/forge/cli/codex-app-server.ts +877 -877
  93. package/src/forge/cli/fabric.ts +205 -131
  94. package/src/forge/cli/index.ts +26 -26
  95. package/src/forge/cli/main.ts +259 -245
  96. package/src/forge/cli/next-actions.ts +23 -23
  97. package/src/forge/cli/parse.ts +3574 -3542
  98. package/src/forge/cli/release.ts +309 -309
  99. package/src/forge/cli/self-host.ts +510 -510
  100. package/src/forge/compiler/ai-registry/parse.ts +219 -219
  101. package/src/forge/compiler/app-graph/classify.ts +10 -10
  102. package/src/forge/compiler/app-graph/dup-symbol.ts +29 -29
  103. package/src/forge/compiler/app-graph/extract.ts +231 -231
  104. package/src/forge/compiler/app-graph/forge-apis.ts +25 -25
  105. package/src/forge/compiler/app-graph/index.ts +11 -11
  106. package/src/forge/compiler/app-graph/module-graph.ts +151 -151
  107. package/src/forge/compiler/app-graph/parser.ts +71 -71
  108. package/src/forge/compiler/app-graph/symbols.ts +48 -48
  109. package/src/forge/compiler/app-graph/types.ts +43 -43
  110. package/src/forge/compiler/app-graph/versions.ts +14 -14
  111. package/src/forge/compiler/cache/index.ts +17 -17
  112. package/src/forge/compiler/cache/key.ts +46 -46
  113. package/src/forge/compiler/cache/scheduler.ts +72 -72
  114. package/src/forge/compiler/cache/store.ts +78 -78
  115. package/src/forge/compiler/classifier/capabilities.ts +64 -64
  116. package/src/forge/compiler/classifier/classify.ts +76 -76
  117. package/src/forge/compiler/classifier/contexts.ts +188 -188
  118. package/src/forge/compiler/classifier/index.ts +18 -18
  119. package/src/forge/compiler/classifier/runtime-matrix.ts +45 -45
  120. package/src/forge/compiler/classifier/secrets.ts +31 -31
  121. package/src/forge/compiler/classifier/signals.ts +95 -95
  122. package/src/forge/compiler/client-sdk/build-manifest.ts +210 -210
  123. package/src/forge/compiler/data-graph/sql/serialize.ts +89 -89
  124. package/src/forge/compiler/data-graph/sql/types.ts +38 -38
  125. package/src/forge/compiler/diagnostics/index.ts +57 -57
  126. package/src/forge/compiler/emitter/artifact-kind.ts +14 -14
  127. package/src/forge/compiler/emitter/barrel.ts +47 -47
  128. package/src/forge/compiler/emitter/constants.ts +7 -7
  129. package/src/forge/compiler/emitter/emit.ts +223 -223
  130. package/src/forge/compiler/emitter/index.ts +24 -24
  131. package/src/forge/compiler/emitter/lock.ts +61 -61
  132. package/src/forge/compiler/external-manifest/registry.ts +205 -205
  133. package/src/forge/compiler/external-manifest/types.ts +91 -91
  134. package/src/forge/compiler/external-manifest/validate.ts +373 -373
  135. package/src/forge/compiler/frontend-graph/build.ts +623 -623
  136. package/src/forge/compiler/guards/artifacts.ts +96 -96
  137. package/src/forge/compiler/guards/check-import-guards.ts +106 -106
  138. package/src/forge/compiler/guards/index.ts +11 -11
  139. package/src/forge/compiler/guards/propagate-contexts.ts +57 -57
  140. package/src/forge/compiler/index.ts +17 -17
  141. package/src/forge/compiler/integration/index.ts +17 -17
  142. package/src/forge/compiler/integration/snapshot.ts +54 -54
  143. package/src/forge/compiler/integration/templates/convex.ts +70 -70
  144. package/src/forge/compiler/orchestrator/discover.ts +214 -214
  145. package/src/forge/compiler/orchestrator/guards.ts +5 -5
  146. package/src/forge/compiler/orchestrator/index.ts +27 -27
  147. package/src/forge/compiler/orchestrator/orphans.ts +51 -51
  148. package/src/forge/compiler/orchestrator/profile.ts +23 -23
  149. package/src/forge/compiler/orchestrator/serialize.ts +979 -979
  150. package/src/forge/compiler/orchestrator/verify.ts +35 -35
  151. package/src/forge/compiler/package-graph/capabilities-stub.ts +33 -33
  152. package/src/forge/compiler/package-graph/checksum.ts +107 -107
  153. package/src/forge/compiler/package-graph/exports-discovery.ts +91 -91
  154. package/src/forge/compiler/package-graph/extract-dts.ts +32 -32
  155. package/src/forge/compiler/package-graph/index.ts +24 -24
  156. package/src/forge/compiler/package-graph/jsdoc.ts +50 -50
  157. package/src/forge/compiler/package-graph/oracle.ts +326 -326
  158. package/src/forge/compiler/package-graph/read-file.ts +21 -21
  159. package/src/forge/compiler/package-graph/resolve.ts +131 -131
  160. package/src/forge/compiler/package-manager/adapter.ts +240 -240
  161. package/src/forge/compiler/package-manager/commands.ts +51 -51
  162. package/src/forge/compiler/package-manager/detect.ts +65 -65
  163. package/src/forge/compiler/package-manager/executor.ts +146 -146
  164. package/src/forge/compiler/package-manager/index.ts +22 -22
  165. package/src/forge/compiler/package-manager/parse-spec.ts +16 -16
  166. package/src/forge/compiler/primitives/compare.ts +26 -26
  167. package/src/forge/compiler/primitives/hash.ts +29 -29
  168. package/src/forge/compiler/primitives/header.ts +43 -43
  169. package/src/forge/compiler/primitives/index.ts +45 -45
  170. package/src/forge/compiler/primitives/paths.ts +24 -24
  171. package/src/forge/compiler/primitives/serialize.ts +66 -66
  172. package/src/forge/compiler/primitives/sort.ts +87 -87
  173. package/src/forge/compiler/recipes/helpers.ts +37 -37
  174. package/src/forge/compiler/sandbox/artifact-sanitize.ts +26 -26
  175. package/src/forge/compiler/sandbox/backends/child.ts +123 -123
  176. package/src/forge/compiler/sandbox/backends/docker.ts +173 -173
  177. package/src/forge/compiler/sandbox/index.ts +51 -51
  178. package/src/forge/compiler/sandbox/inspect.ts +143 -143
  179. package/src/forge/compiler/sandbox/inspector-entry.ts +115 -115
  180. package/src/forge/compiler/sandbox/limits.ts +31 -31
  181. package/src/forge/compiler/sandbox/scrub-env.ts +60 -60
  182. package/src/forge/compiler/sandbox/secret-scan.ts +54 -54
  183. package/src/forge/compiler/sandbox/serialize.ts +106 -106
  184. package/src/forge/compiler/sandbox/types.ts +7 -7
  185. package/src/forge/compiler/test-graph/build.ts +285 -285
  186. package/src/forge/compiler/types/app-graph.ts +87 -87
  187. package/src/forge/compiler/types/capability.ts +29 -29
  188. package/src/forge/compiler/types/classification.ts +9 -9
  189. package/src/forge/compiler/types/diagnostic.ts +2 -2
  190. package/src/forge/compiler/types/frontend-graph.ts +81 -81
  191. package/src/forge/compiler/types/import-guards.ts +19 -19
  192. package/src/forge/compiler/types/index.ts +98 -98
  193. package/src/forge/compiler/types/json.ts +3 -3
  194. package/src/forge/compiler/types/lock.ts +37 -37
  195. package/src/forge/compiler/types/runtime-matrix.ts +16 -16
  196. package/src/forge/compiler/types/runtime.ts +30 -30
  197. package/src/forge/compiler/types/sandbox.ts +24 -24
  198. package/src/forge/delta/broker-runner.mjs +5 -0
  199. package/src/forge/delta/broker.ts +429 -0
  200. package/src/forge/delta/classifier.ts +52 -52
  201. package/src/forge/delta/explain.ts +238 -238
  202. package/src/forge/delta/git-observer.ts +24 -0
  203. package/src/forge/delta/ids.ts +40 -40
  204. package/src/forge/delta/index.ts +25 -25
  205. package/src/forge/delta/process-identity.ts +31 -0
  206. package/src/forge/delta/queue-status.ts +190 -0
  207. package/src/forge/delta/redaction.ts +50 -50
  208. package/src/forge/delta/schema.ts +135 -135
  209. package/src/forge/delta/session.ts +142 -142
  210. package/src/forge/delta/status.ts +82 -145
  211. package/src/forge/delta/store.ts +77 -32
  212. package/src/forge/delta/timeline.ts +178 -178
  213. package/src/forge/dev/types.ts +63 -63
  214. package/src/forge/dev/watch.ts +119 -119
  215. package/src/forge/intent/index.ts +509 -509
  216. package/src/forge/make/types.ts +153 -153
  217. package/src/forge/refactor/index.ts +1937 -1937
  218. package/src/forge/refactor/runtime-rename.ts +598 -598
  219. package/src/forge/repair/rules/index.ts +476 -476
  220. package/src/forge/review/types.ts +211 -211
  221. package/src/forge/runtime/db/generated-client.ts +305 -305
  222. package/src/forge/runtime/db/pglite-adapter.ts +33 -11
  223. package/src/forge/runtime/runner/run-entry.ts +236 -236
  224. package/src/forge/version.ts +1 -1
  225. package/templates/agent-workroom/AGENTS.md +29 -29
  226. package/templates/agent-workroom/README.md +34 -34
  227. package/templates/agent-workroom/forge.config.ts +3 -3
  228. package/templates/agent-workroom/package.json +34 -34
  229. package/templates/agent-workroom/pnpm-workspace.yaml +2 -2
  230. package/templates/agent-workroom/src/actions/indexAgentSignal.ts +10 -10
  231. package/templates/agent-workroom/src/commands/openWorkroom.ts +61 -61
  232. package/templates/agent-workroom/src/commands/recordAgentSignal.ts +119 -119
  233. package/templates/agent-workroom/src/commands/recordCheckRun.ts +52 -52
  234. package/templates/agent-workroom/src/forge/schema.ts +54 -54
  235. package/templates/agent-workroom/src/policies.ts +6 -6
  236. package/templates/agent-workroom/src/queries/listWorkrooms.ts +11 -11
  237. package/templates/agent-workroom/src/queries/liveWorkroom.ts +63 -63
  238. package/templates/agent-workroom/tsconfig.json +16 -16
  239. package/templates/agent-workroom/web/index.html +13 -13
  240. package/templates/agent-workroom/web/package.json +21 -21
  241. package/templates/agent-workroom/web/src/App.tsx +345 -345
  242. package/templates/agent-workroom/web/src/main.tsx +13 -13
  243. package/templates/agent-workroom/web/src/styles.css +545 -545
  244. package/templates/agent-workroom/web/tsconfig.json +27 -27
  245. package/templates/b2b-support-web/package.json +37 -37
  246. package/templates/b2b-support-web/pnpm-workspace.yaml +2 -2
  247. package/templates/b2b-support-web/web/package.json +21 -21
  248. package/templates/minimal-web/package.json +34 -34
  249. package/templates/minimal-web/pnpm-workspace.yaml +2 -2
  250. package/templates/minimal-web/web/index.html +13 -13
  251. package/templates/minimal-web/web/src/styles.css +190 -190
  252. package/templates/nuxt-web/.vscode/settings.json +14 -14
  253. package/templates/nuxt-web/.yarnrc.yml +1 -1
  254. package/templates/nuxt-web/README.md +30 -30
  255. package/templates/nuxt-web/forge.config.ts +3 -3
  256. package/templates/nuxt-web/package.json +34 -34
  257. package/templates/nuxt-web/pnpm-workspace.yaml +2 -2
  258. package/templates/nuxt-web/src/actions/logNoteCreated.ts +11 -11
  259. package/templates/nuxt-web/src/commands/createNote.ts +26 -26
  260. package/templates/nuxt-web/src/forge/schema.ts +12 -12
  261. package/templates/nuxt-web/src/policies.ts +6 -6
  262. package/templates/nuxt-web/src/queries/listNotes.ts +8 -8
  263. package/templates/nuxt-web/src/queries/liveNotes.ts +8 -8
  264. package/templates/nuxt-web/tsconfig.json +17 -17
  265. package/templates/nuxt-web/web/app.vue +67 -67
  266. package/templates/nuxt-web/web/components/LiveNotes.vue +89 -89
  267. package/templates/nuxt-web/web/components/NoteComposer.vue +100 -100
  268. package/templates/nuxt-web/web/composables/forge.ts +13 -13
  269. package/templates/nuxt-web/web/composables/useNotes.ts +24 -24
  270. package/templates/nuxt-web/web/nuxt.config.ts +11 -11
  271. package/templates/nuxt-web/web/package.json +19 -19
  272. package/templates/nuxt-web/web/plugins/forge.client.ts +10 -10
  273. package/templates/nuxt-web/web/plugins/forge.server.ts +10 -10
  274. package/templates/nuxt-web/web/server/api/forge-health.get.ts +7 -7
  275. package/templates/nuxt-web/web/tsconfig.json +6 -6
  276. package/templates/vendor-access/.vscode/settings.json +14 -14
  277. package/templates/vendor-access/README.md +30 -30
  278. package/templates/vendor-access/package.json +35 -35
  279. package/templates/vendor-access/pnpm-workspace.yaml +2 -2
  280. package/templates/vendor-access/tsconfig.json +17 -17
  281. package/templates/vendor-access/web/index.html +13 -13
  282. package/templates/vendor-access/web/package.json +21 -21
  283. package/templates/vendor-access/web/src/styles.css +682 -682
  284. package/templates/vendor-access/web/tsconfig.json +18 -18
@@ -1,476 +1,476 @@
1
- import { createDiagnostic } from "../../compiler/diagnostics/create.ts";
2
- import { hashStable } from "../../compiler/primitives/hash.ts";
3
- import type {
4
- FailureInput,
5
- FailureKind,
6
- RepairAffected,
7
- RepairConfidence,
8
- RepairDiagnosis,
9
- RepairRule,
10
- SuggestedRepair,
11
- } from "../types.ts";
12
-
13
- const REPAIR_VERSION = "repair-0.1.0";
14
-
15
- function emptyAffected(): RepairAffected {
16
- return {
17
- files: [],
18
- commands: [],
19
- queries: [],
20
- liveQueries: [],
21
- actions: [],
22
- workflows: [],
23
- tables: [],
24
- policies: [],
25
- components: [],
26
- packages: [],
27
- };
28
- }
29
-
30
- function push(values: string[], value: string | undefined): void {
31
- if (value && !values.includes(value)) {
32
- values.push(value);
33
- }
34
- }
35
-
36
- function affectedFromDiagnostics(input: FailureInput): RepairAffected {
37
- const affected = emptyAffected();
38
- const haystack = `${input.stdout}\n${input.stderr}\n${input.diagnostics.map((d) => `${d.message} ${d.file ?? ""}`).join("\n")}`;
39
- for (const diagnostic of input.diagnostics) {
40
- push(affected.files, diagnostic.file);
41
- const policy = diagnostic.message.match(/\b([a-z][a-z0-9_-]+\.[a-z][a-z0-9_-]+)\b/i)?.[1];
42
- if (policy) push(affected.policies, policy);
43
- }
44
- const command = haystack.match(/\bcommand\s+([a-zA-Z][a-zA-Z0-9_]*)\b/)?.[1] ??
45
- haystack.match(/src\/commands\/([a-zA-Z][a-zA-Z0-9_]*)\.ts/)?.[1];
46
- push(affected.commands, command);
47
- const query = haystack.match(/\bquery\s+([a-zA-Z][a-zA-Z0-9_]*)\b/)?.[1] ??
48
- haystack.match(/src\/queries\/([a-zA-Z][a-zA-Z0-9_]*)\.ts/)?.[1];
49
- if (query?.toLowerCase().startsWith("live")) push(affected.liveQueries, query);
50
- else push(affected.queries, query);
51
- const pkg = haystack.match(/\b(stripe|posthog|sentry|openai|anthropic|ai|jose)\b/i)?.[1];
52
- push(affected.packages, pkg);
53
- affected.files.sort();
54
- affected.commands.sort();
55
- affected.queries.sort();
56
- affected.liveQueries.sort();
57
- affected.policies.sort();
58
- affected.packages.sort();
59
- return affected;
60
- }
61
-
62
- function diagnosis(input: FailureInput, args: {
63
- failureKind: FailureKind;
64
- code: string;
65
- summary: string;
66
- likelyCause: string;
67
- suggestedRepairs: SuggestedRepair[];
68
- recommendedChecks?: string[];
69
- confidence: RepairConfidence;
70
- }): RepairDiagnosis {
71
- const affected = affectedFromDiagnostics(input);
72
- const id = `repair_${hashStable(`${args.code}:${args.summary}:${input.source.kind}:${input.source.id ?? ""}:${input.source.file ?? ""}`).slice(0, 12)}`;
73
- const diagnostics =
74
- input.diagnostics.length > 0
75
- ? input.diagnostics
76
- : [
77
- createDiagnostic({
78
- severity: "error",
79
- code: args.code,
80
- message: args.summary,
81
- file: input.source.file,
82
- }),
83
- ];
84
- return {
85
- schemaVersion: "0.1.0",
86
- repairVersion: REPAIR_VERSION,
87
- id,
88
- failureKind: args.failureKind,
89
- source: input.source,
90
- diagnostics,
91
- summary: args.summary,
92
- likelyCause: args.likelyCause,
93
- affected,
94
- suggestedRepairs: args.suggestedRepairs,
95
- recommendedChecks: args.recommendedChecks ?? [
96
- "forge generate",
97
- "forge check",
98
- "forge verify --changed",
99
- "forge verify --strict",
100
- ],
101
- confidence: args.confidence,
102
- };
103
- }
104
-
105
- function hasCode(input: FailureInput, ...codes: string[]): boolean {
106
- const text = `${input.stdout}\n${input.stderr}\n${input.diagnostics.map((d) => `${d.code}\n${d.message}`).join("\n")}`;
107
- return codes.some((code) => text.includes(code));
108
- }
109
-
110
- function firstPolicy(input: FailureInput): string {
111
- const text = `${input.stdout}\n${input.stderr}\n${input.diagnostics.map((d) => d.message).join("\n")}`;
112
- return text.match(/\b([a-z][a-z0-9_-]+\.[a-z][a-z0-9_-]+)\b/i)?.[1] ?? "resource.action";
113
- }
114
-
115
- function firstSecret(input: FailureInput): string {
116
- const text = `${input.stdout}\n${input.stderr}\n${input.diagnostics.map((d) => d.message).join("\n")}`;
117
- return text
118
- .match(/\b[A-Z][A-Z0-9_]{2,}\b/g)
119
- ?.find((candidate) => !candidate.startsWith("FORGE_")) ?? "SECRET_NAME";
120
- }
121
-
122
- function firstCommand(input: FailureInput): string {
123
- const affected = affectedFromDiagnostics(input);
124
- return affected.commands[0] ?? "commandName";
125
- }
126
-
127
- function firstPackage(input: FailureInput): string {
128
- const affected = affectedFromDiagnostics(input);
129
- return affected.packages[0] ?? "package-name";
130
- }
131
-
132
- export const repairRules: RepairRule[] = [
133
- {
134
- id: "generated-drift",
135
- matches: (input) => hasCode(input, "FORGE_DRIFT", "FORGE_ORPHANED_GENERATED_FILE"),
136
- diagnose: (input) =>
137
- diagnosis(input, {
138
- failureKind: "generated-drift",
139
- code: "FORGE_DRIFT",
140
- summary: "Generated artifacts are stale.",
141
- likelyCause: "Source files or compiler inputs changed without regenerating Forge artifacts.",
142
- confidence: "high",
143
- suggestedRepairs: [
144
- {
145
- id: "run-generate",
146
- kind: "run-command",
147
- title: "Regenerate Forge artifacts",
148
- description: "Run the deterministic generator to update generated files and forge.lock.",
149
- command: "forge generate",
150
- confidence: "high",
151
- risk: { level: "low", reasons: ["deterministic generated output"] },
152
- requiresConfirmation: false,
153
- },
154
- ],
155
- }),
156
- },
157
- {
158
- id: "runtime-guard",
159
- matches: (input) => hasCode(input, "FORGE_GUARD_VIOLATION", "FORGE_RUNTIME_GUARD_BLOCKED"),
160
- diagnose: (input) => {
161
- const command = firstCommand(input);
162
- const pkg = firstPackage(input);
163
- return diagnosis(input, {
164
- failureKind: "runtime-guard",
165
- code: "FORGE_GUARD_VIOLATION",
166
- summary: `A forbidden package or capability is reachable from deterministic runtime code.`,
167
- likelyCause: "A network, secret, AI, or server-only package was imported from a command/query/liveQuery path.",
168
- confidence: command !== "commandName" ? "medium" : "low",
169
- suggestedRepairs: [
170
- {
171
- id: "extract-action",
172
- kind: "refactor",
173
- title: "Move side effect to an action",
174
- description: "Make the command emit an event and move the package call to an action.",
175
- command: `forge refactor extract-action ${command} --package ${pkg} --event ${command}.requested`,
176
- confidence: command !== "commandName" ? "medium" : "low",
177
- risk: { level: "medium", reasons: ["changes runtime side-effect boundary"] },
178
- requiresConfirmation: true,
179
- },
180
- ],
181
- });
182
- },
183
- },
184
- {
185
- id: "policy-auth",
186
- matches: (input) =>
187
- hasCode(input, "FORGE_POLICY_UNKNOWN", "FORGE_POLICY_MISSING", "FORGE_POLICY_DENIED", "FORGE_AUTH_TENANT_MISSING", "FORGE_AUTH_INVALID_TOKEN"),
188
- diagnose: (input) => {
189
- const policy = firstPolicy(input);
190
- return diagnosis(input, {
191
- failureKind: "policy-auth",
192
- code: "FORGE_POLICY_UNKNOWN",
193
- summary: `Policy or auth configuration needs attention for ${policy}.`,
194
- likelyCause: "A runtime entry references a missing policy, an auth context is incomplete, or the caller role is denied.",
195
- confidence: "medium",
196
- suggestedRepairs: [
197
- {
198
- id: "make-policy",
199
- kind: "make",
200
- title: "Create missing policy",
201
- description: "Create the policy with conservative owner/admin/member roles, then review access intentionally.",
202
- command: `forge make policy ${policy} --roles owner,admin,member --apply --yes`,
203
- confidence: "medium",
204
- risk: { level: "high", reasons: ["permission changes require human review"] },
205
- requiresConfirmation: true,
206
- },
207
- {
208
- id: "simulate-policy",
209
- kind: "run-command",
210
- title: "Simulate policy",
211
- description: "Check whether the expected role is allowed before changing permissions.",
212
- command: `forge policy simulate ${policy} --role member`,
213
- confidence: "high",
214
- risk: { level: "low", reasons: ["read-only diagnostic command"] },
215
- requiresConfirmation: false,
216
- },
217
- ],
218
- });
219
- },
220
- },
221
- {
222
- id: "secrets",
223
- matches: (input) =>
224
- hasCode(input, "FORGE_SECRET_DIRECT_PROCESS_ENV", "FORGE_SECRET_MISSING", "FORGE_SECRET_FORBIDDEN_CONTEXT"),
225
- diagnose: (input) => {
226
- const secret = firstSecret(input);
227
- return diagnosis(input, {
228
- failureKind: "secrets",
229
- code: "FORGE_SECRET_DIRECT_PROCESS_ENV",
230
- summary: `Secret/config usage is unsafe or missing for ${secret}.`,
231
- likelyCause: "Code reads secret/config values through process.env in Forge runtime code, accesses secrets in a forbidden runtime, or the environment lacks a required secret.",
232
- confidence: "high",
233
- suggestedRepairs: [
234
- {
235
- id: "replace-process-env",
236
- kind: "refactor",
237
- title: "Replace process.env access",
238
- description: "Use ctx.secrets where a runtime context is available.",
239
- command: `forge refactor replace-process-env ${secret} --yes`,
240
- confidence: "high",
241
- risk: { level: "low", reasons: ["mechanical secret access replacement"] },
242
- requiresConfirmation: true,
243
- },
244
- {
245
- id: "check-secrets",
246
- kind: "run-command",
247
- title: "Check secrets",
248
- description: "Validate secret registry and missing environment names.",
249
- command: "forge secrets check",
250
- confidence: "high",
251
- risk: { level: "low", reasons: ["read-only diagnostic command"] },
252
- requiresConfirmation: false,
253
- },
254
- ],
255
- });
256
- },
257
- },
258
- {
259
- id: "ai",
260
- matches: (input) => hasCode(input, "FORGE_AI_FORBIDDEN_CONTEXT", "FORGE_AI_SECRET_MISSING", "FORGE_AI_PROVIDER_UNKNOWN", "FORGE_AI_GENERATION_FAILED"),
261
- diagnose: (input) =>
262
- diagnosis(input, {
263
- failureKind: "ai",
264
- code: "FORGE_AI_FORBIDDEN_CONTEXT",
265
- summary: "AI usage failed or appears in a forbidden runtime context.",
266
- likelyCause: "AI calls belong in actions/workflows/endpoints/server, not deterministic commands/queries/liveQueries.",
267
- confidence: "medium",
268
- suggestedRepairs: [
269
- {
270
- id: "ai-check",
271
- kind: "run-command",
272
- title: "Check AI providers",
273
- description: "Validate provider configuration and mock mode.",
274
- command: "forge ai check",
275
- confidence: "high",
276
- risk: { level: "low", reasons: ["read-only diagnostic command"] },
277
- requiresConfirmation: false,
278
- },
279
- {
280
- id: "move-ai",
281
- kind: "manual",
282
- title: "Move AI call to action/workflow",
283
- description: "Store AI output in the database from an action/workflow; queries should read the stored result.",
284
- confidence: "medium",
285
- risk: { level: "medium", reasons: ["changes runtime architecture"] },
286
- requiresConfirmation: true,
287
- },
288
- ],
289
- }),
290
- },
291
- {
292
- id: "query-readonly",
293
- matches: (input) =>
294
- hasCode(input, "FORGE_QUERY_WRITE_FORBIDDEN", "FORGE_QUERY_EMIT_FORBIDDEN", "FORGE_QUERY_SECRET_FORBIDDEN", "FORGE_QUERY_AI_FORBIDDEN"),
295
- diagnose: (input) =>
296
- diagnosis(input, {
297
- failureKind: "query-readonly",
298
- code: "FORGE_QUERY_WRITE_FORBIDDEN",
299
- summary: "A query performs work that is forbidden in read-only runtime.",
300
- likelyCause: "Queries and liveQueries must be deterministic reads without writes, emits, secrets, AI, or integrations.",
301
- confidence: "medium",
302
- suggestedRepairs: [
303
- {
304
- id: "make-command",
305
- kind: "make",
306
- title: "Move mutation to a command",
307
- description: "Create a command for writes or event emission, then keep the query read-only.",
308
- command: "forge make command <name> --table <table> --policy <policy>",
309
- confidence: "medium",
310
- risk: { level: "medium", reasons: ["requires choosing command/table/policy names"] },
311
- requiresConfirmation: true,
312
- },
313
- ],
314
- }),
315
- },
316
- {
317
- id: "livequery",
318
- matches: (input) =>
319
- hasCode(input, "FORGE_LIVE_INVALIDATION_MISSING", "FORGE_LIVE_RERUN_FAILED", "FORGE_LIVE_SNAPSHOT_TOO_LARGE", "FORGE_LIVEQUERY_UNKNOWN", "FORGE_UI_LIVE_UPDATE_TIMEOUT"),
320
- diagnose: (input) =>
321
- diagnosis(input, {
322
- failureKind: "livequery-reactivity",
323
- code: "FORGE_LIVE_INVALIDATION_MISSING",
324
- summary: "LiveQuery reactivity or subscription delivery failed.",
325
- likelyCause: "A write was not tracked, the liveQuery dependency is unknown, the tenant scope mismatched, or the snapshot is too large.",
326
- confidence: "medium",
327
- suggestedRepairs: [
328
- {
329
- id: "live-debug",
330
- kind: "run-command",
331
- title: "Inspect live subscriptions",
332
- description: "Inspect liveQuery state and invalidation history.",
333
- command: "forge live invalidations --json",
334
- confidence: "high",
335
- risk: { level: "low", reasons: ["read-only diagnostic command"] },
336
- requiresConfirmation: false,
337
- },
338
- ],
339
- }),
340
- },
341
- {
342
- id: "workflow",
343
- matches: (input) => hasCode(input, "FORGE_WORKFLOW_STEP_FAILED", "FORGE_WORKFLOW_STEP_DEAD", "FORGE_WORKFLOW_RUN_NOT_FOUND"),
344
- diagnose: (input) =>
345
- diagnosis(input, {
346
- failureKind: "workflow",
347
- code: "FORGE_WORKFLOW_STEP_FAILED",
348
- summary: "Workflow run or step failed.",
349
- likelyCause: "A workflow step threw, missed a secret, received invalid input, or exhausted retries.",
350
- confidence: "medium",
351
- suggestedRepairs: [
352
- {
353
- id: "workflow-inspect",
354
- kind: "run-command",
355
- title: "Inspect workflow run",
356
- description: "Inspect step state, attempts, traceId, and last error.",
357
- command: "forge workflow inspect <runId> --json",
358
- confidence: "high",
359
- risk: { level: "low", reasons: ["read-only diagnostic command"] },
360
- requiresConfirmation: false,
361
- },
362
- ],
363
- }),
364
- },
365
- {
366
- id: "outbox",
367
- matches: (input) => hasCode(input, "FORGE_OUTBOX_PROCESS_FAILED", "FORGE_OUTBOX_DELIVERY_NOT_FOUND", "dead-letter", "delivery dead"),
368
- diagnose: (input) =>
369
- diagnosis(input, {
370
- failureKind: "outbox",
371
- code: "FORGE_OUTBOX_PROCESS_FAILED",
372
- summary: "Outbox delivery failed or is dead-lettered.",
373
- likelyCause: "The subscribed action failed repeatedly, often due to secret, integration, payload, or network configuration.",
374
- confidence: "medium",
375
- suggestedRepairs: [
376
- {
377
- id: "outbox-retry",
378
- kind: "run-command",
379
- title: "Retry delivery after fixing action",
380
- description: "Retry the delivery once the underlying action/integration issue is fixed.",
381
- command: "forge outbox retry <deliveryId>",
382
- confidence: "medium",
383
- risk: { level: "medium", reasons: ["replays a side-effect delivery"] },
384
- requiresConfirmation: true,
385
- },
386
- ],
387
- }),
388
- },
389
- {
390
- id: "package-upgrade",
391
- matches: (input) =>
392
- hasCode(input, "FORGE_DEPS_API_BREAKING_CHANGE", "FORGE_DEPS_REMOVED_EXPORT_USED", "FORGE_DEPS_SIGNATURE_CHANGED_USED", "FORGE_DEPS_APPLY_FAILED"),
393
- diagnose: (input) =>
394
- diagnosis(input, {
395
- failureKind: "package-upgrade",
396
- code: "FORGE_DEPS_API_BREAKING_CHANGE",
397
- summary: "Package upgrade introduced an API or runtime compatibility issue.",
398
- likelyCause: "The upgrade plan contains removed exports, changed signatures, new capabilities, or failing callsites.",
399
- confidence: "medium",
400
- suggestedRepairs: [
401
- {
402
- id: "deps-check",
403
- kind: "run-command",
404
- title: "Inspect package upgrade risk",
405
- description: "Re-run the upgrade checker and inspect affected callsites.",
406
- command: "forge deps upgrade-check --json",
407
- confidence: "high",
408
- risk: { level: "low", reasons: ["read-only diagnostic command"] },
409
- requiresConfirmation: false,
410
- },
411
- ],
412
- }),
413
- },
414
- {
415
- id: "frontend-client",
416
- matches: (input) => hasCode(input, "server-only", "client", "ForgeError", "React", "FORGE_UI_EXPECTATION_FAILED", "FORGE_UI_SELECTOR_NOT_FOUND", "FORGE_UI_CONSOLE_ERROR", "FORGE_UI_NETWORK_ERROR"),
417
- diagnose: (input) =>
418
- diagnosis(input, {
419
- failureKind: "frontend-client",
420
- code: "FORGE_REPAIR_FRONTEND_CLIENT",
421
- summary: "Frontend/client failure needs client-safe API or hook review.",
422
- likelyCause: "A frontend component may import server-only code or use Forge client hooks without the expected provider/auth context.",
423
- confidence: "low",
424
- suggestedRepairs: [
425
- {
426
- id: "client-safe-imports",
427
- kind: "manual",
428
- title: "Use generated client-safe entrypoints",
429
- description: "Import from generated client/react surfaces instead of server adapters.",
430
- confidence: "low",
431
- risk: { level: "medium", reasons: ["requires component-specific review"] },
432
- requiresConfirmation: true,
433
- },
434
- ],
435
- }),
436
- },
437
- {
438
- id: "release-deploy",
439
- matches: (input) => hasCode(input, "FORGE_RELEASE_", "FORGE_SOURCEMAP_", "self-host", "deploy"),
440
- diagnose: (input) =>
441
- diagnosis(input, {
442
- failureKind: "release-deploy",
443
- code: "FORGE_RELEASE_ARTIFACT_MISSING",
444
- summary: "Release or deployment verification failed.",
445
- likelyCause: "Release artifacts, source maps, self-host files, or environment configuration are missing or stale.",
446
- confidence: "medium",
447
- suggestedRepairs: [
448
- {
449
- id: "release-check",
450
- kind: "run-command",
451
- title: "Run release and self-host checks",
452
- description: "Inspect release/deploy generated artifacts and runtime health assumptions.",
453
- command: "forge release check",
454
- confidence: "high",
455
- risk: { level: "low", reasons: ["read-only diagnostic command"] },
456
- requiresConfirmation: false,
457
- },
458
- ],
459
- }),
460
- },
461
- ];
462
-
463
- export function explainDiagnostic(code: string): string {
464
- const explanations: Record<string, string> = {
465
- FORGE_DRIFT: "Generated artifacts are stale. Run forge generate, then forge generate --check.",
466
- FORGE_GUARD_VIOLATION: "A package or capability is used in a runtime context where it is forbidden. Move side effects to actions/workflows.",
467
- FORGE_POLICY_UNKNOWN: "A runtime entry references a missing policy. Create or correct the policy, then simulate expected roles.",
468
- FORGE_POLICY_DENIED: "The caller auth context does not satisfy the policy. Fix test auth or intentionally update policy roles.",
469
- FORGE_SECRET_DIRECT_PROCESS_ENV: "Code reads secret/config values through process.env in Forge runtime code. Use ctx.secrets/ctx.config in allowed runtime contexts.",
470
- FORGE_AI_FORBIDDEN_CONTEXT: "AI is being used in a deterministic context. Move AI calls to action/workflow/server runtime.",
471
- FORGE_WORKFLOW_STEP_FAILED: "A workflow step failed. Inspect the workflow run and trace before retrying.",
472
- FORGE_OUTBOX_PROCESS_FAILED: "An outbox delivery failed. Inspect the subscribed action and retry after fixing the cause.",
473
- FORGE_DEPS_API_BREAKING_CHANGE: "A package upgrade changed API/runtime compatibility. Inspect the upgrade plan and affected callsites.",
474
- };
475
- return explanations[code] ?? `${code}: no specific repair explanation is registered yet. Run forge repair diagnose --diagnostic ${code} --json for generic guidance.`;
476
- }
1
+ import { createDiagnostic } from "../../compiler/diagnostics/create.ts";
2
+ import { hashStable } from "../../compiler/primitives/hash.ts";
3
+ import type {
4
+ FailureInput,
5
+ FailureKind,
6
+ RepairAffected,
7
+ RepairConfidence,
8
+ RepairDiagnosis,
9
+ RepairRule,
10
+ SuggestedRepair,
11
+ } from "../types.ts";
12
+
13
+ const REPAIR_VERSION = "repair-0.1.0";
14
+
15
+ function emptyAffected(): RepairAffected {
16
+ return {
17
+ files: [],
18
+ commands: [],
19
+ queries: [],
20
+ liveQueries: [],
21
+ actions: [],
22
+ workflows: [],
23
+ tables: [],
24
+ policies: [],
25
+ components: [],
26
+ packages: [],
27
+ };
28
+ }
29
+
30
+ function push(values: string[], value: string | undefined): void {
31
+ if (value && !values.includes(value)) {
32
+ values.push(value);
33
+ }
34
+ }
35
+
36
+ function affectedFromDiagnostics(input: FailureInput): RepairAffected {
37
+ const affected = emptyAffected();
38
+ const haystack = `${input.stdout}\n${input.stderr}\n${input.diagnostics.map((d) => `${d.message} ${d.file ?? ""}`).join("\n")}`;
39
+ for (const diagnostic of input.diagnostics) {
40
+ push(affected.files, diagnostic.file);
41
+ const policy = diagnostic.message.match(/\b([a-z][a-z0-9_-]+\.[a-z][a-z0-9_-]+)\b/i)?.[1];
42
+ if (policy) push(affected.policies, policy);
43
+ }
44
+ const command = haystack.match(/\bcommand\s+([a-zA-Z][a-zA-Z0-9_]*)\b/)?.[1] ??
45
+ haystack.match(/src\/commands\/([a-zA-Z][a-zA-Z0-9_]*)\.ts/)?.[1];
46
+ push(affected.commands, command);
47
+ const query = haystack.match(/\bquery\s+([a-zA-Z][a-zA-Z0-9_]*)\b/)?.[1] ??
48
+ haystack.match(/src\/queries\/([a-zA-Z][a-zA-Z0-9_]*)\.ts/)?.[1];
49
+ if (query?.toLowerCase().startsWith("live")) push(affected.liveQueries, query);
50
+ else push(affected.queries, query);
51
+ const pkg = haystack.match(/\b(stripe|posthog|sentry|openai|anthropic|ai|jose)\b/i)?.[1];
52
+ push(affected.packages, pkg);
53
+ affected.files.sort();
54
+ affected.commands.sort();
55
+ affected.queries.sort();
56
+ affected.liveQueries.sort();
57
+ affected.policies.sort();
58
+ affected.packages.sort();
59
+ return affected;
60
+ }
61
+
62
+ function diagnosis(input: FailureInput, args: {
63
+ failureKind: FailureKind;
64
+ code: string;
65
+ summary: string;
66
+ likelyCause: string;
67
+ suggestedRepairs: SuggestedRepair[];
68
+ recommendedChecks?: string[];
69
+ confidence: RepairConfidence;
70
+ }): RepairDiagnosis {
71
+ const affected = affectedFromDiagnostics(input);
72
+ const id = `repair_${hashStable(`${args.code}:${args.summary}:${input.source.kind}:${input.source.id ?? ""}:${input.source.file ?? ""}`).slice(0, 12)}`;
73
+ const diagnostics =
74
+ input.diagnostics.length > 0
75
+ ? input.diagnostics
76
+ : [
77
+ createDiagnostic({
78
+ severity: "error",
79
+ code: args.code,
80
+ message: args.summary,
81
+ file: input.source.file,
82
+ }),
83
+ ];
84
+ return {
85
+ schemaVersion: "0.1.0",
86
+ repairVersion: REPAIR_VERSION,
87
+ id,
88
+ failureKind: args.failureKind,
89
+ source: input.source,
90
+ diagnostics,
91
+ summary: args.summary,
92
+ likelyCause: args.likelyCause,
93
+ affected,
94
+ suggestedRepairs: args.suggestedRepairs,
95
+ recommendedChecks: args.recommendedChecks ?? [
96
+ "forge generate",
97
+ "forge check",
98
+ "forge verify --changed",
99
+ "forge verify --strict",
100
+ ],
101
+ confidence: args.confidence,
102
+ };
103
+ }
104
+
105
+ function hasCode(input: FailureInput, ...codes: string[]): boolean {
106
+ const text = `${input.stdout}\n${input.stderr}\n${input.diagnostics.map((d) => `${d.code}\n${d.message}`).join("\n")}`;
107
+ return codes.some((code) => text.includes(code));
108
+ }
109
+
110
+ function firstPolicy(input: FailureInput): string {
111
+ const text = `${input.stdout}\n${input.stderr}\n${input.diagnostics.map((d) => d.message).join("\n")}`;
112
+ return text.match(/\b([a-z][a-z0-9_-]+\.[a-z][a-z0-9_-]+)\b/i)?.[1] ?? "resource.action";
113
+ }
114
+
115
+ function firstSecret(input: FailureInput): string {
116
+ const text = `${input.stdout}\n${input.stderr}\n${input.diagnostics.map((d) => d.message).join("\n")}`;
117
+ return text
118
+ .match(/\b[A-Z][A-Z0-9_]{2,}\b/g)
119
+ ?.find((candidate) => !candidate.startsWith("FORGE_")) ?? "SECRET_NAME";
120
+ }
121
+
122
+ function firstCommand(input: FailureInput): string {
123
+ const affected = affectedFromDiagnostics(input);
124
+ return affected.commands[0] ?? "commandName";
125
+ }
126
+
127
+ function firstPackage(input: FailureInput): string {
128
+ const affected = affectedFromDiagnostics(input);
129
+ return affected.packages[0] ?? "package-name";
130
+ }
131
+
132
+ export const repairRules: RepairRule[] = [
133
+ {
134
+ id: "generated-drift",
135
+ matches: (input) => hasCode(input, "FORGE_DRIFT", "FORGE_ORPHANED_GENERATED_FILE"),
136
+ diagnose: (input) =>
137
+ diagnosis(input, {
138
+ failureKind: "generated-drift",
139
+ code: "FORGE_DRIFT",
140
+ summary: "Generated artifacts are stale.",
141
+ likelyCause: "Source files or compiler inputs changed without regenerating Forge artifacts.",
142
+ confidence: "high",
143
+ suggestedRepairs: [
144
+ {
145
+ id: "run-generate",
146
+ kind: "run-command",
147
+ title: "Regenerate Forge artifacts",
148
+ description: "Run the deterministic generator to update generated files and forge.lock.",
149
+ command: "forge generate",
150
+ confidence: "high",
151
+ risk: { level: "low", reasons: ["deterministic generated output"] },
152
+ requiresConfirmation: false,
153
+ },
154
+ ],
155
+ }),
156
+ },
157
+ {
158
+ id: "runtime-guard",
159
+ matches: (input) => hasCode(input, "FORGE_GUARD_VIOLATION", "FORGE_RUNTIME_GUARD_BLOCKED"),
160
+ diagnose: (input) => {
161
+ const command = firstCommand(input);
162
+ const pkg = firstPackage(input);
163
+ return diagnosis(input, {
164
+ failureKind: "runtime-guard",
165
+ code: "FORGE_GUARD_VIOLATION",
166
+ summary: `A forbidden package or capability is reachable from deterministic runtime code.`,
167
+ likelyCause: "A network, secret, AI, or server-only package was imported from a command/query/liveQuery path.",
168
+ confidence: command !== "commandName" ? "medium" : "low",
169
+ suggestedRepairs: [
170
+ {
171
+ id: "extract-action",
172
+ kind: "refactor",
173
+ title: "Move side effect to an action",
174
+ description: "Make the command emit an event and move the package call to an action.",
175
+ command: `forge refactor extract-action ${command} --package ${pkg} --event ${command}.requested`,
176
+ confidence: command !== "commandName" ? "medium" : "low",
177
+ risk: { level: "medium", reasons: ["changes runtime side-effect boundary"] },
178
+ requiresConfirmation: true,
179
+ },
180
+ ],
181
+ });
182
+ },
183
+ },
184
+ {
185
+ id: "policy-auth",
186
+ matches: (input) =>
187
+ hasCode(input, "FORGE_POLICY_UNKNOWN", "FORGE_POLICY_MISSING", "FORGE_POLICY_DENIED", "FORGE_AUTH_TENANT_MISSING", "FORGE_AUTH_INVALID_TOKEN"),
188
+ diagnose: (input) => {
189
+ const policy = firstPolicy(input);
190
+ return diagnosis(input, {
191
+ failureKind: "policy-auth",
192
+ code: "FORGE_POLICY_UNKNOWN",
193
+ summary: `Policy or auth configuration needs attention for ${policy}.`,
194
+ likelyCause: "A runtime entry references a missing policy, an auth context is incomplete, or the caller role is denied.",
195
+ confidence: "medium",
196
+ suggestedRepairs: [
197
+ {
198
+ id: "make-policy",
199
+ kind: "make",
200
+ title: "Create missing policy",
201
+ description: "Create the policy with conservative owner/admin/member roles, then review access intentionally.",
202
+ command: `forge make policy ${policy} --roles owner,admin,member --apply --yes`,
203
+ confidence: "medium",
204
+ risk: { level: "high", reasons: ["permission changes require human review"] },
205
+ requiresConfirmation: true,
206
+ },
207
+ {
208
+ id: "simulate-policy",
209
+ kind: "run-command",
210
+ title: "Simulate policy",
211
+ description: "Check whether the expected role is allowed before changing permissions.",
212
+ command: `forge policy simulate ${policy} --role member`,
213
+ confidence: "high",
214
+ risk: { level: "low", reasons: ["read-only diagnostic command"] },
215
+ requiresConfirmation: false,
216
+ },
217
+ ],
218
+ });
219
+ },
220
+ },
221
+ {
222
+ id: "secrets",
223
+ matches: (input) =>
224
+ hasCode(input, "FORGE_SECRET_DIRECT_PROCESS_ENV", "FORGE_SECRET_MISSING", "FORGE_SECRET_FORBIDDEN_CONTEXT"),
225
+ diagnose: (input) => {
226
+ const secret = firstSecret(input);
227
+ return diagnosis(input, {
228
+ failureKind: "secrets",
229
+ code: "FORGE_SECRET_DIRECT_PROCESS_ENV",
230
+ summary: `Secret/config usage is unsafe or missing for ${secret}.`,
231
+ likelyCause: "Code reads secret/config values through process.env in Forge runtime code, accesses secrets in a forbidden runtime, or the environment lacks a required secret.",
232
+ confidence: "high",
233
+ suggestedRepairs: [
234
+ {
235
+ id: "replace-process-env",
236
+ kind: "refactor",
237
+ title: "Replace process.env access",
238
+ description: "Use ctx.secrets where a runtime context is available.",
239
+ command: `forge refactor replace-process-env ${secret} --yes`,
240
+ confidence: "high",
241
+ risk: { level: "low", reasons: ["mechanical secret access replacement"] },
242
+ requiresConfirmation: true,
243
+ },
244
+ {
245
+ id: "check-secrets",
246
+ kind: "run-command",
247
+ title: "Check secrets",
248
+ description: "Validate secret registry and missing environment names.",
249
+ command: "forge secrets check",
250
+ confidence: "high",
251
+ risk: { level: "low", reasons: ["read-only diagnostic command"] },
252
+ requiresConfirmation: false,
253
+ },
254
+ ],
255
+ });
256
+ },
257
+ },
258
+ {
259
+ id: "ai",
260
+ matches: (input) => hasCode(input, "FORGE_AI_FORBIDDEN_CONTEXT", "FORGE_AI_SECRET_MISSING", "FORGE_AI_PROVIDER_UNKNOWN", "FORGE_AI_GENERATION_FAILED"),
261
+ diagnose: (input) =>
262
+ diagnosis(input, {
263
+ failureKind: "ai",
264
+ code: "FORGE_AI_FORBIDDEN_CONTEXT",
265
+ summary: "AI usage failed or appears in a forbidden runtime context.",
266
+ likelyCause: "AI calls belong in actions/workflows/endpoints/server, not deterministic commands/queries/liveQueries.",
267
+ confidence: "medium",
268
+ suggestedRepairs: [
269
+ {
270
+ id: "ai-check",
271
+ kind: "run-command",
272
+ title: "Check AI providers",
273
+ description: "Validate provider configuration and mock mode.",
274
+ command: "forge ai check",
275
+ confidence: "high",
276
+ risk: { level: "low", reasons: ["read-only diagnostic command"] },
277
+ requiresConfirmation: false,
278
+ },
279
+ {
280
+ id: "move-ai",
281
+ kind: "manual",
282
+ title: "Move AI call to action/workflow",
283
+ description: "Store AI output in the database from an action/workflow; queries should read the stored result.",
284
+ confidence: "medium",
285
+ risk: { level: "medium", reasons: ["changes runtime architecture"] },
286
+ requiresConfirmation: true,
287
+ },
288
+ ],
289
+ }),
290
+ },
291
+ {
292
+ id: "query-readonly",
293
+ matches: (input) =>
294
+ hasCode(input, "FORGE_QUERY_WRITE_FORBIDDEN", "FORGE_QUERY_EMIT_FORBIDDEN", "FORGE_QUERY_SECRET_FORBIDDEN", "FORGE_QUERY_AI_FORBIDDEN"),
295
+ diagnose: (input) =>
296
+ diagnosis(input, {
297
+ failureKind: "query-readonly",
298
+ code: "FORGE_QUERY_WRITE_FORBIDDEN",
299
+ summary: "A query performs work that is forbidden in read-only runtime.",
300
+ likelyCause: "Queries and liveQueries must be deterministic reads without writes, emits, secrets, AI, or integrations.",
301
+ confidence: "medium",
302
+ suggestedRepairs: [
303
+ {
304
+ id: "make-command",
305
+ kind: "make",
306
+ title: "Move mutation to a command",
307
+ description: "Create a command for writes or event emission, then keep the query read-only.",
308
+ command: "forge make command <name> --table <table> --policy <policy>",
309
+ confidence: "medium",
310
+ risk: { level: "medium", reasons: ["requires choosing command/table/policy names"] },
311
+ requiresConfirmation: true,
312
+ },
313
+ ],
314
+ }),
315
+ },
316
+ {
317
+ id: "livequery",
318
+ matches: (input) =>
319
+ hasCode(input, "FORGE_LIVE_INVALIDATION_MISSING", "FORGE_LIVE_RERUN_FAILED", "FORGE_LIVE_SNAPSHOT_TOO_LARGE", "FORGE_LIVEQUERY_UNKNOWN", "FORGE_UI_LIVE_UPDATE_TIMEOUT"),
320
+ diagnose: (input) =>
321
+ diagnosis(input, {
322
+ failureKind: "livequery-reactivity",
323
+ code: "FORGE_LIVE_INVALIDATION_MISSING",
324
+ summary: "LiveQuery reactivity or subscription delivery failed.",
325
+ likelyCause: "A write was not tracked, the liveQuery dependency is unknown, the tenant scope mismatched, or the snapshot is too large.",
326
+ confidence: "medium",
327
+ suggestedRepairs: [
328
+ {
329
+ id: "live-debug",
330
+ kind: "run-command",
331
+ title: "Inspect live subscriptions",
332
+ description: "Inspect liveQuery state and invalidation history.",
333
+ command: "forge live invalidations --json",
334
+ confidence: "high",
335
+ risk: { level: "low", reasons: ["read-only diagnostic command"] },
336
+ requiresConfirmation: false,
337
+ },
338
+ ],
339
+ }),
340
+ },
341
+ {
342
+ id: "workflow",
343
+ matches: (input) => hasCode(input, "FORGE_WORKFLOW_STEP_FAILED", "FORGE_WORKFLOW_STEP_DEAD", "FORGE_WORKFLOW_RUN_NOT_FOUND"),
344
+ diagnose: (input) =>
345
+ diagnosis(input, {
346
+ failureKind: "workflow",
347
+ code: "FORGE_WORKFLOW_STEP_FAILED",
348
+ summary: "Workflow run or step failed.",
349
+ likelyCause: "A workflow step threw, missed a secret, received invalid input, or exhausted retries.",
350
+ confidence: "medium",
351
+ suggestedRepairs: [
352
+ {
353
+ id: "workflow-inspect",
354
+ kind: "run-command",
355
+ title: "Inspect workflow run",
356
+ description: "Inspect step state, attempts, traceId, and last error.",
357
+ command: "forge workflow inspect <runId> --json",
358
+ confidence: "high",
359
+ risk: { level: "low", reasons: ["read-only diagnostic command"] },
360
+ requiresConfirmation: false,
361
+ },
362
+ ],
363
+ }),
364
+ },
365
+ {
366
+ id: "outbox",
367
+ matches: (input) => hasCode(input, "FORGE_OUTBOX_PROCESS_FAILED", "FORGE_OUTBOX_DELIVERY_NOT_FOUND", "dead-letter", "delivery dead"),
368
+ diagnose: (input) =>
369
+ diagnosis(input, {
370
+ failureKind: "outbox",
371
+ code: "FORGE_OUTBOX_PROCESS_FAILED",
372
+ summary: "Outbox delivery failed or is dead-lettered.",
373
+ likelyCause: "The subscribed action failed repeatedly, often due to secret, integration, payload, or network configuration.",
374
+ confidence: "medium",
375
+ suggestedRepairs: [
376
+ {
377
+ id: "outbox-retry",
378
+ kind: "run-command",
379
+ title: "Retry delivery after fixing action",
380
+ description: "Retry the delivery once the underlying action/integration issue is fixed.",
381
+ command: "forge outbox retry <deliveryId>",
382
+ confidence: "medium",
383
+ risk: { level: "medium", reasons: ["replays a side-effect delivery"] },
384
+ requiresConfirmation: true,
385
+ },
386
+ ],
387
+ }),
388
+ },
389
+ {
390
+ id: "package-upgrade",
391
+ matches: (input) =>
392
+ hasCode(input, "FORGE_DEPS_API_BREAKING_CHANGE", "FORGE_DEPS_REMOVED_EXPORT_USED", "FORGE_DEPS_SIGNATURE_CHANGED_USED", "FORGE_DEPS_APPLY_FAILED"),
393
+ diagnose: (input) =>
394
+ diagnosis(input, {
395
+ failureKind: "package-upgrade",
396
+ code: "FORGE_DEPS_API_BREAKING_CHANGE",
397
+ summary: "Package upgrade introduced an API or runtime compatibility issue.",
398
+ likelyCause: "The upgrade plan contains removed exports, changed signatures, new capabilities, or failing callsites.",
399
+ confidence: "medium",
400
+ suggestedRepairs: [
401
+ {
402
+ id: "deps-check",
403
+ kind: "run-command",
404
+ title: "Inspect package upgrade risk",
405
+ description: "Re-run the upgrade checker and inspect affected callsites.",
406
+ command: "forge deps upgrade-check --json",
407
+ confidence: "high",
408
+ risk: { level: "low", reasons: ["read-only diagnostic command"] },
409
+ requiresConfirmation: false,
410
+ },
411
+ ],
412
+ }),
413
+ },
414
+ {
415
+ id: "frontend-client",
416
+ matches: (input) => hasCode(input, "server-only", "client", "ForgeError", "React", "FORGE_UI_EXPECTATION_FAILED", "FORGE_UI_SELECTOR_NOT_FOUND", "FORGE_UI_CONSOLE_ERROR", "FORGE_UI_NETWORK_ERROR"),
417
+ diagnose: (input) =>
418
+ diagnosis(input, {
419
+ failureKind: "frontend-client",
420
+ code: "FORGE_REPAIR_FRONTEND_CLIENT",
421
+ summary: "Frontend/client failure needs client-safe API or hook review.",
422
+ likelyCause: "A frontend component may import server-only code or use Forge client hooks without the expected provider/auth context.",
423
+ confidence: "low",
424
+ suggestedRepairs: [
425
+ {
426
+ id: "client-safe-imports",
427
+ kind: "manual",
428
+ title: "Use generated client-safe entrypoints",
429
+ description: "Import from generated client/react surfaces instead of server adapters.",
430
+ confidence: "low",
431
+ risk: { level: "medium", reasons: ["requires component-specific review"] },
432
+ requiresConfirmation: true,
433
+ },
434
+ ],
435
+ }),
436
+ },
437
+ {
438
+ id: "release-deploy",
439
+ matches: (input) => hasCode(input, "FORGE_RELEASE_", "FORGE_SOURCEMAP_", "self-host", "deploy"),
440
+ diagnose: (input) =>
441
+ diagnosis(input, {
442
+ failureKind: "release-deploy",
443
+ code: "FORGE_RELEASE_ARTIFACT_MISSING",
444
+ summary: "Release or deployment verification failed.",
445
+ likelyCause: "Release artifacts, source maps, self-host files, or environment configuration are missing or stale.",
446
+ confidence: "medium",
447
+ suggestedRepairs: [
448
+ {
449
+ id: "release-check",
450
+ kind: "run-command",
451
+ title: "Run release and self-host checks",
452
+ description: "Inspect release/deploy generated artifacts and runtime health assumptions.",
453
+ command: "forge release check",
454
+ confidence: "high",
455
+ risk: { level: "low", reasons: ["read-only diagnostic command"] },
456
+ requiresConfirmation: false,
457
+ },
458
+ ],
459
+ }),
460
+ },
461
+ ];
462
+
463
+ export function explainDiagnostic(code: string): string {
464
+ const explanations: Record<string, string> = {
465
+ FORGE_DRIFT: "Generated artifacts are stale. Run forge generate, then forge generate --check.",
466
+ FORGE_GUARD_VIOLATION: "A package or capability is used in a runtime context where it is forbidden. Move side effects to actions/workflows.",
467
+ FORGE_POLICY_UNKNOWN: "A runtime entry references a missing policy. Create or correct the policy, then simulate expected roles.",
468
+ FORGE_POLICY_DENIED: "The caller auth context does not satisfy the policy. Fix test auth or intentionally update policy roles.",
469
+ FORGE_SECRET_DIRECT_PROCESS_ENV: "Code reads secret/config values through process.env in Forge runtime code. Use ctx.secrets/ctx.config in allowed runtime contexts.",
470
+ FORGE_AI_FORBIDDEN_CONTEXT: "AI is being used in a deterministic context. Move AI calls to action/workflow/server runtime.",
471
+ FORGE_WORKFLOW_STEP_FAILED: "A workflow step failed. Inspect the workflow run and trace before retrying.",
472
+ FORGE_OUTBOX_PROCESS_FAILED: "An outbox delivery failed. Inspect the subscribed action and retry after fixing the cause.",
473
+ FORGE_DEPS_API_BREAKING_CHANGE: "A package upgrade changed API/runtime compatibility. Inspect the upgrade plan and affected callsites.",
474
+ };
475
+ return explanations[code] ?? `${code}: no specific repair explanation is registered yet. Run forge repair diagnose --diagnostic ${code} --json for generic guidance.`;
476
+ }