forgeos 0.1.0-alpha.2 → 0.1.0-alpha.20

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 (406) hide show
  1. package/.npmignore +4 -0
  2. package/AGENTS.md +168 -81
  3. package/CHANGELOG.md +199 -0
  4. package/README.md +88 -14
  5. package/adapters/go/README.md +23 -0
  6. package/adapters/go/go.mod +3 -0
  7. package/adapters/go/http.go +149 -0
  8. package/adapters/go/registry.go +234 -0
  9. package/adapters/go/types.go +136 -0
  10. package/adapters/java/README.md +68 -0
  11. package/adapters/java/pom.xml +34 -0
  12. package/adapters/java/src/main/java/dev/forgeos/adapter/Auth.java +20 -0
  13. package/adapters/java/src/main/java/dev/forgeos/adapter/Diagnostic.java +16 -0
  14. package/adapters/java/src/main/java/dev/forgeos/adapter/Entry.java +38 -0
  15. package/adapters/java/src/main/java/dev/forgeos/adapter/EntryKind.java +16 -0
  16. package/adapters/java/src/main/java/dev/forgeos/adapter/ErrorInfo.java +4 -0
  17. package/adapters/java/src/main/java/dev/forgeos/adapter/Forge.java +94 -0
  18. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeCall.java +12 -0
  19. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeContext.java +11 -0
  20. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeHandler.java +8 -0
  21. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeHttpHandler.java +179 -0
  22. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeRegistry.java +121 -0
  23. package/adapters/java/src/main/java/dev/forgeos/adapter/Json.java +14 -0
  24. package/adapters/java/src/main/java/dev/forgeos/adapter/Manifest.java +14 -0
  25. package/adapters/java/src/main/java/dev/forgeos/adapter/RequestEnvelope.java +6 -0
  26. package/adapters/java/src/main/java/dev/forgeos/adapter/ResponseEnvelope.java +25 -0
  27. package/adapters/java/src/main/java/dev/forgeos/adapter/Risk.java +18 -0
  28. package/adapters/java/src/main/java/dev/forgeos/adapter/Schemas.java +36 -0
  29. package/adapters/java/src/main/java/dev/forgeos/adapter/Service.java +65 -0
  30. package/adapters/java/src/main/java/dev/forgeos/adapter/TransactionMode.java +18 -0
  31. package/adapters/java/src/main/java/dev/forgeos/adapter/TypedForgeHandler.java +6 -0
  32. package/adapters/java/target/classes/dev/forgeos/adapter/Auth.class +0 -0
  33. package/adapters/java/target/classes/dev/forgeos/adapter/Diagnostic.class +0 -0
  34. package/adapters/java/target/classes/dev/forgeos/adapter/Entry.class +0 -0
  35. package/adapters/java/target/classes/dev/forgeos/adapter/EntryKind.class +0 -0
  36. package/adapters/java/target/classes/dev/forgeos/adapter/ErrorInfo.class +0 -0
  37. package/adapters/java/target/classes/dev/forgeos/adapter/Forge.class +0 -0
  38. package/adapters/java/target/classes/dev/forgeos/adapter/ForgeCall.class +0 -0
  39. package/adapters/java/target/classes/dev/forgeos/adapter/ForgeContext.class +0 -0
  40. package/adapters/java/target/classes/dev/forgeos/adapter/ForgeHandler.class +0 -0
  41. package/adapters/java/target/classes/dev/forgeos/adapter/ForgeHttpHandler.class +0 -0
  42. package/adapters/java/target/classes/dev/forgeos/adapter/ForgeRegistry$EntryOption.class +0 -0
  43. package/adapters/java/target/classes/dev/forgeos/adapter/ForgeRegistry$RegisteredEntry.class +0 -0
  44. package/adapters/java/target/classes/dev/forgeos/adapter/ForgeRegistry$RegistryOption.class +0 -0
  45. package/adapters/java/target/classes/dev/forgeos/adapter/ForgeRegistry.class +0 -0
  46. package/adapters/java/target/classes/dev/forgeos/adapter/Json.class +0 -0
  47. package/adapters/java/target/classes/dev/forgeos/adapter/Manifest.class +0 -0
  48. package/adapters/java/target/classes/dev/forgeos/adapter/RequestEnvelope.class +0 -0
  49. package/adapters/java/target/classes/dev/forgeos/adapter/ResponseEnvelope.class +0 -0
  50. package/adapters/java/target/classes/dev/forgeos/adapter/Risk.class +0 -0
  51. package/adapters/java/target/classes/dev/forgeos/adapter/Schemas.class +0 -0
  52. package/adapters/java/target/classes/dev/forgeos/adapter/Service.class +0 -0
  53. package/adapters/java/target/classes/dev/forgeos/adapter/TransactionMode.class +0 -0
  54. package/adapters/java/target/classes/dev/forgeos/adapter/TypedForgeHandler.class +0 -0
  55. package/adapters/java/target/forge-java-adapter-0.1.0-alpha.11.jar +0 -0
  56. package/adapters/java/target/maven-archiver/pom.properties +3 -0
  57. package/adapters/java/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +23 -0
  58. package/adapters/java/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +20 -0
  59. package/adapters/java-spring-boot-starter/README.md +32 -0
  60. package/adapters/java-spring-boot-starter/pom.xml +36 -0
  61. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeCommand.java +22 -0
  62. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeExternalService.java +15 -0
  63. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeQuery.java +16 -0
  64. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeServiceBeanCondition.java +18 -0
  65. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeSpringAutoConfiguration.java +16 -0
  66. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeSpringRuntime.java +104 -0
  67. package/adapters/java-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +1 -0
  68. package/adapters/java-spring-boot-starter/target/classes/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +1 -0
  69. package/adapters/java-spring-boot-starter/target/classes/dev/forgeos/adapter/spring/ForgeCommand.class +0 -0
  70. package/adapters/java-spring-boot-starter/target/classes/dev/forgeos/adapter/spring/ForgeExternalService.class +0 -0
  71. package/adapters/java-spring-boot-starter/target/classes/dev/forgeos/adapter/spring/ForgeQuery.class +0 -0
  72. package/adapters/java-spring-boot-starter/target/classes/dev/forgeos/adapter/spring/ForgeServiceBeanCondition.class +0 -0
  73. package/adapters/java-spring-boot-starter/target/classes/dev/forgeos/adapter/spring/ForgeSpringAutoConfiguration.class +0 -0
  74. package/adapters/java-spring-boot-starter/target/classes/dev/forgeos/adapter/spring/ForgeSpringRuntime.class +0 -0
  75. package/adapters/java-spring-boot-starter/target/forge-java-spring-boot-starter-0.1.0-alpha.11.jar +0 -0
  76. package/adapters/java-spring-boot-starter/target/maven-archiver/pom.properties +3 -0
  77. package/adapters/java-spring-boot-starter/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +6 -0
  78. package/adapters/java-spring-boot-starter/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +6 -0
  79. package/bin/forge.mjs +18 -0
  80. package/docs/changelog.md +212 -0
  81. package/docs/forge-protocol.md +189 -0
  82. package/examples/go-billing/go.mod +7 -0
  83. package/examples/go-billing/main.go +120 -0
  84. package/examples/java-billing/pom.xml +52 -0
  85. package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/CreateInvoiceInput.java +4 -0
  86. package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/Invoice.java +11 -0
  87. package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/Main.java +127 -0
  88. package/examples/java-billing/target/classes/dev/forgeos/examples/billing/CreateInvoiceInput.class +0 -0
  89. package/examples/java-billing/target/classes/dev/forgeos/examples/billing/Invoice.class +0 -0
  90. package/examples/java-billing/target/classes/dev/forgeos/examples/billing/Main$EmptyInput.class +0 -0
  91. package/examples/java-billing/target/classes/dev/forgeos/examples/billing/Main$Options.class +0 -0
  92. package/examples/java-billing/target/classes/dev/forgeos/examples/billing/Main.class +0 -0
  93. package/examples/java-billing/target/java-billing-0.1.0-alpha.11-all.jar +0 -0
  94. package/examples/java-billing/target/java-billing-0.1.0-alpha.11.jar +0 -0
  95. package/examples/java-billing/target/maven-archiver/pom.properties +3 -0
  96. package/examples/java-billing/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +5 -0
  97. package/examples/java-billing/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +3 -0
  98. package/package.json +29 -7
  99. package/schemas/forge-manifest.schema.json +57 -0
  100. package/src/forge/_generated/releaseManifest.json +1 -2
  101. package/src/forge/_generated/releaseManifest.ts +3 -3
  102. package/src/forge/agent-adapters/index.ts +1511 -123
  103. package/src/forge/agent-adapters/types.ts +216 -1
  104. package/src/forge/agent-memory/bridge.ts +1192 -0
  105. package/src/forge/agent-memory/context-pack.ts +151 -0
  106. package/src/forge/agent-memory/hook-runner.ts +312 -0
  107. package/src/forge/agent-memory/mcp.ts +224 -0
  108. package/src/forge/agent-memory/normalize.ts +498 -0
  109. package/src/forge/agent-memory/redaction.ts +103 -0
  110. package/src/forge/agent-memory/sources/claude-code.ts +51 -0
  111. package/src/forge/agent-memory/sources/codex-hook-runner.mjs +84 -0
  112. package/src/forge/agent-memory/sources/codex.ts +119 -0
  113. package/src/forge/agent-memory/sources/cursor.ts +35 -0
  114. package/src/forge/agent-memory/types.ts +191 -0
  115. package/src/forge/bench.ts +248 -0
  116. package/src/forge/brownfield-import/index.ts +736 -0
  117. package/src/forge/brownfield-import/types.ts +127 -0
  118. package/src/forge/cair/action-journal.ts +61 -0
  119. package/src/forge/cair/action-parser.ts +314 -0
  120. package/src/forge/cair/action-validator.ts +40 -0
  121. package/src/forge/cair/actions.ts +1818 -0
  122. package/src/forge/cair/format.ts +77 -0
  123. package/src/forge/cair/index.ts +106 -0
  124. package/src/forge/cair/query.ts +478 -0
  125. package/src/forge/cair/snapshot.ts +315 -0
  126. package/src/forge/cair/types.ts +248 -0
  127. package/src/forge/cli/ai.ts +671 -3
  128. package/src/forge/cli/auth.ts +36 -1
  129. package/src/forge/cli/build.ts +20 -4
  130. package/src/forge/cli/changed.ts +300 -0
  131. package/src/forge/cli/codex-app-server.ts +877 -0
  132. package/src/forge/cli/commands.ts +1285 -7
  133. package/src/forge/cli/db.ts +121 -2
  134. package/src/forge/cli/deps.ts +79 -12
  135. package/src/forge/cli/dev.ts +502 -38
  136. package/src/forge/cli/docs.ts +265 -0
  137. package/src/forge/cli/handoff.ts +250 -0
  138. package/src/forge/cli/index.ts +1 -0
  139. package/src/forge/cli/main.ts +49 -3
  140. package/src/forge/cli/new.ts +3 -1
  141. package/src/forge/cli/next-actions.ts +23 -0
  142. package/src/forge/cli/output.ts +290 -1
  143. package/src/forge/cli/parse.ts +770 -36
  144. package/src/forge/cli/query.ts +32 -0
  145. package/src/forge/cli/release.ts +35 -11
  146. package/src/forge/cli/rls.ts +568 -17
  147. package/src/forge/cli/run.ts +41 -0
  148. package/src/forge/cli/secrets.ts +46 -1
  149. package/src/forge/cli/security.ts +381 -0
  150. package/src/forge/cli/self-host.ts +56 -14
  151. package/src/forge/cli/studio.ts +2163 -0
  152. package/src/forge/cli/verify.ts +1422 -32
  153. package/src/forge/compiler/agent-contract/build.ts +725 -41
  154. package/src/forge/compiler/agent-contract/types.ts +85 -0
  155. package/src/forge/compiler/ai-registry/build.ts +62 -1
  156. package/src/forge/compiler/ai-registry/constants.ts +1 -1
  157. package/src/forge/compiler/ai-registry/parse.ts +168 -5
  158. package/src/forge/compiler/api-surface/build.ts +47 -0
  159. package/src/forge/compiler/app-graph/build.ts +68 -8
  160. package/src/forge/compiler/app-graph/extract.ts +107 -0
  161. package/src/forge/compiler/app-graph/forge-apis.ts +1 -0
  162. package/src/forge/compiler/app-graph/module-graph.ts +73 -78
  163. package/src/forge/compiler/app-graph/parser.ts +24 -24
  164. package/src/forge/compiler/app-graph/profile.ts +26 -0
  165. package/src/forge/compiler/app-graph/versions.ts +1 -1
  166. package/src/forge/compiler/classifier/capabilities.ts +3 -2
  167. package/src/forge/compiler/classifier/classify.ts +32 -8
  168. package/src/forge/compiler/classifier/secrets.ts +3 -2
  169. package/src/forge/compiler/classifier/signals.ts +91 -1
  170. package/src/forge/compiler/client-sdk/build-manifest.ts +59 -0
  171. package/src/forge/compiler/client-sdk/render-client.ts +188 -13
  172. package/src/forge/compiler/data-graph/parse.ts +3 -3
  173. package/src/forge/compiler/data-graph/sql/ddl.ts +60 -2
  174. package/src/forge/compiler/data-graph/sql/serialize.ts +4 -0
  175. package/src/forge/compiler/data-graph/sql/types.ts +1 -0
  176. package/src/forge/compiler/dev-manifest/build.ts +3 -0
  177. package/src/forge/compiler/diagnostics/codes.ts +35 -0
  178. package/src/forge/compiler/diagnostics/create.ts +8 -3
  179. package/src/forge/compiler/diagnostics/index.ts +2 -0
  180. package/src/forge/compiler/emitter/barrel.ts +3 -0
  181. package/src/forge/compiler/emitter/render.ts +5 -0
  182. package/src/forge/compiler/external-manifest/registry.ts +205 -0
  183. package/src/forge/compiler/external-manifest/types.ts +91 -0
  184. package/src/forge/compiler/external-manifest/validate.ts +373 -0
  185. package/src/forge/compiler/frontend-graph/build.ts +85 -13
  186. package/src/forge/compiler/integration/add.ts +498 -22
  187. package/src/forge/compiler/integration/snapshot.ts +2 -0
  188. package/src/forge/compiler/make-registry/build.ts +19 -7
  189. package/src/forge/compiler/orchestrator/plan-profile.ts +23 -0
  190. package/src/forge/compiler/orchestrator/plan.ts +78 -7
  191. package/src/forge/compiler/orchestrator/profile.ts +65 -0
  192. package/src/forge/compiler/orchestrator/run.ts +97 -31
  193. package/src/forge/compiler/orchestrator/serialize.ts +101 -8
  194. package/src/forge/compiler/package-graph/compiler.ts +13 -3
  195. package/src/forge/compiler/package-manager/adapter.ts +4 -1
  196. package/src/forge/compiler/package-manager/commands.ts +4 -0
  197. package/src/forge/compiler/package-manager/executor.ts +30 -1
  198. package/src/forge/compiler/policy-registry/build.ts +44 -1
  199. package/src/forge/compiler/test-graph/build.ts +11 -3
  200. package/src/forge/compiler/types/ai-registry.ts +25 -1
  201. package/src/forge/compiler/types/app-graph.ts +9 -2
  202. package/src/forge/compiler/types/cli.ts +76 -1
  203. package/src/forge/compiler/types/dev-manifest.ts +3 -0
  204. package/src/forge/compiler/types/frontend-graph.ts +2 -2
  205. package/src/forge/delta/classifier.ts +52 -0
  206. package/src/forge/delta/explain.ts +126 -0
  207. package/src/forge/delta/git-observer.ts +43 -0
  208. package/src/forge/delta/ids.ts +44 -0
  209. package/src/forge/delta/index.ts +13 -0
  210. package/src/forge/delta/recorder.ts +402 -0
  211. package/src/forge/delta/redaction.ts +50 -0
  212. package/src/forge/delta/schema.ts +240 -0
  213. package/src/forge/delta/session.ts +142 -0
  214. package/src/forge/delta/status.ts +489 -0
  215. package/src/forge/delta/store.ts +2975 -0
  216. package/src/forge/delta/timeline.ts +104 -0
  217. package/src/forge/dev/server.ts +768 -15
  218. package/src/forge/dev/types.ts +15 -1
  219. package/src/forge/dev/watch.ts +17 -7
  220. package/src/forge/dev-console/cycle.ts +233 -21
  221. package/src/forge/dev-console/types.ts +46 -1
  222. package/src/forge/impact/index.ts +46 -8
  223. package/src/forge/impact/types.ts +6 -0
  224. package/src/forge/intent/index.ts +35 -16
  225. package/src/forge/make/index.ts +149 -6
  226. package/src/forge/make/templates.ts +343 -2
  227. package/src/forge/make/types.ts +3 -1
  228. package/src/forge/refactor/index.ts +1 -0
  229. package/src/forge/repair/rules/index.ts +2 -2
  230. package/src/forge/review/index.ts +158 -12
  231. package/src/forge/review/types.ts +15 -0
  232. package/src/forge/runtime/ai/context.ts +210 -5
  233. package/src/forge/runtime/ai/types.ts +70 -0
  234. package/src/forge/runtime/auth/claims.ts +32 -0
  235. package/src/forge/runtime/auth/errors.ts +2 -0
  236. package/src/forge/runtime/context/create-context.ts +30 -6
  237. package/src/forge/runtime/db/generated-client.ts +13 -2
  238. package/src/forge/runtime/db/memory-adapter.ts +2 -2
  239. package/src/forge/runtime/db/pglite-adapter.ts +77 -2
  240. package/src/forge/runtime/db/postgres-adapter.ts +6 -3
  241. package/src/forge/runtime/executor.ts +112 -2
  242. package/src/forge/runtime/external/bridge.ts +649 -0
  243. package/src/forge/runtime/runner/run-entry.ts +16 -7
  244. package/src/forge/runtime/telemetry/scrubber.ts +91 -10
  245. package/src/forge/runtime/webhooks/security.ts +184 -0
  246. package/src/forge/server.ts +100 -2
  247. package/src/forge/version.ts +1 -1
  248. package/src/forge/vue/index.ts +407 -0
  249. package/src/forge/workspace/change-summary.ts +209 -0
  250. package/src/forge/workspace/forge-cli.ts +14 -0
  251. package/src/forge/workspace/git-summary.ts +279 -0
  252. package/templates/agent-workroom/AGENTS.md +29 -0
  253. package/templates/agent-workroom/README.md +34 -0
  254. package/templates/agent-workroom/forge.config.ts +3 -0
  255. package/templates/agent-workroom/package.json +33 -0
  256. package/templates/agent-workroom/src/actions/indexAgentSignal.ts +10 -0
  257. package/templates/agent-workroom/src/commands/openWorkroom.ts +61 -0
  258. package/templates/agent-workroom/src/commands/recordAgentSignal.ts +119 -0
  259. package/templates/agent-workroom/src/commands/recordCheckRun.ts +52 -0
  260. package/templates/agent-workroom/src/forge/schema.ts +54 -0
  261. package/templates/agent-workroom/src/policies.ts +6 -0
  262. package/templates/agent-workroom/src/queries/listWorkrooms.ts +11 -0
  263. package/templates/agent-workroom/src/queries/liveWorkroom.ts +63 -0
  264. package/templates/agent-workroom/tsconfig.json +16 -0
  265. package/templates/agent-workroom/web/index.html +12 -0
  266. package/templates/agent-workroom/web/package.json +21 -0
  267. package/templates/agent-workroom/web/src/App.tsx +345 -0
  268. package/templates/agent-workroom/web/src/lib/forge.ts +13 -0
  269. package/templates/agent-workroom/web/src/main.tsx +13 -0
  270. package/templates/agent-workroom/web/src/styles.css +545 -0
  271. package/templates/agent-workroom/web/tsconfig.json +27 -0
  272. package/templates/b2b-support-web/package.json +2 -0
  273. package/templates/b2b-support-web/tsconfig.json +4 -1
  274. package/templates/b2b-support-web/web/package.json +1 -1
  275. package/templates/minimal-web/package.json +2 -1
  276. package/templates/minimal-web/tsconfig.json +3 -1
  277. package/templates/minimal-web/web/package.json +2 -2
  278. package/src/forge/_generated/actionSubscriptions.json +0 -2
  279. package/src/forge/_generated/actionSubscriptions.ts +0 -10
  280. package/src/forge/_generated/agentAdapterManifest.json +0 -2
  281. package/src/forge/_generated/agentAdapterManifest.ts +0 -73
  282. package/src/forge/_generated/agentContract.json +0 -2
  283. package/src/forge/_generated/agentContract.ts +0 -7696
  284. package/src/forge/_generated/agentQuickstart.md +0 -32
  285. package/src/forge/_generated/aiContext.ts +0 -59
  286. package/src/forge/_generated/aiModels.json +0 -2
  287. package/src/forge/_generated/aiModels.ts +0 -35
  288. package/src/forge/_generated/aiProviders.json +0 -2
  289. package/src/forge/_generated/aiProviders.ts +0 -23
  290. package/src/forge/_generated/aiRegistry.json +0 -2
  291. package/src/forge/_generated/aiRegistry.ts +0 -29
  292. package/src/forge/_generated/api.json +0 -2
  293. package/src/forge/_generated/api.ts +0 -8
  294. package/src/forge/_generated/appGraph.json +0 -2
  295. package/src/forge/_generated/appGraph.ts +0 -14667
  296. package/src/forge/_generated/appMap.md +0 -35
  297. package/src/forge/_generated/artifactManifest.json +0 -2
  298. package/src/forge/_generated/artifactManifest.ts +0 -7
  299. package/src/forge/_generated/authClaims.json +0 -2
  300. package/src/forge/_generated/authClaims.ts +0 -13
  301. package/src/forge/_generated/authConfig.json +0 -2
  302. package/src/forge/_generated/authConfig.ts +0 -17
  303. package/src/forge/_generated/authContext.ts +0 -23
  304. package/src/forge/_generated/authRegistry.json +0 -2
  305. package/src/forge/_generated/authRegistry.ts +0 -25
  306. package/src/forge/_generated/buildInfo.json +0 -2
  307. package/src/forge/_generated/buildInfo.ts +0 -9
  308. package/src/forge/_generated/capabilityMap.json +0 -2
  309. package/src/forge/_generated/capabilityMap.md +0 -15
  310. package/src/forge/_generated/capabilityMap.ts +0 -17
  311. package/src/forge/_generated/client.ts +0 -282
  312. package/src/forge/_generated/clientApi.ts +0 -9
  313. package/src/forge/_generated/clientManifest.json +0 -2
  314. package/src/forge/_generated/clientManifest.ts +0 -39
  315. package/src/forge/_generated/clientTypes.ts +0 -78
  316. package/src/forge/_generated/configRegistry.json +0 -2
  317. package/src/forge/_generated/configRegistry.ts +0 -4
  318. package/src/forge/_generated/dataGraph.json +0 -2
  319. package/src/forge/_generated/dataGraph.ts +0 -8
  320. package/src/forge/_generated/db.json +0 -2
  321. package/src/forge/_generated/db.ts +0 -2
  322. package/src/forge/_generated/dbSecurityManifest.json +0 -2
  323. package/src/forge/_generated/dbSecurityManifest.ts +0 -15
  324. package/src/forge/_generated/dbSessionContext.json +0 -2
  325. package/src/forge/_generated/dbSessionContext.ts +0 -39
  326. package/src/forge/_generated/deployManifest.json +0 -2
  327. package/src/forge/_generated/deployManifest.ts +0 -14
  328. package/src/forge/_generated/devManifest.json +0 -2
  329. package/src/forge/_generated/devManifest.ts +0 -47
  330. package/src/forge/_generated/envSchema.json +0 -2
  331. package/src/forge/_generated/envSchema.ts +0 -59
  332. package/src/forge/_generated/frontendGraph.json +0 -2
  333. package/src/forge/_generated/frontendGraph.ts +0 -27
  334. package/src/forge/_generated/importGuards.json +0 -2
  335. package/src/forge/_generated/importGuards.ts +0 -686
  336. package/src/forge/_generated/index.ts +0 -67
  337. package/src/forge/_generated/liveProductionManifest.json +0 -2
  338. package/src/forge/_generated/liveProductionManifest.ts +0 -23
  339. package/src/forge/_generated/liveProtocol.json +0 -2
  340. package/src/forge/_generated/liveProtocol.ts +0 -21
  341. package/src/forge/_generated/liveQueryRegistry.json +0 -2
  342. package/src/forge/_generated/liveQueryRegistry.ts +0 -9
  343. package/src/forge/_generated/liveTransportConfig.json +0 -2
  344. package/src/forge/_generated/liveTransportConfig.ts +0 -19
  345. package/src/forge/_generated/makeRegistry.json +0 -2
  346. package/src/forge/_generated/makeRegistry.ts +0 -163
  347. package/src/forge/_generated/makeTemplates.json +0 -2
  348. package/src/forge/_generated/makeTemplates.ts +0 -61
  349. package/src/forge/_generated/mockMap.json +0 -2
  350. package/src/forge/_generated/mockMap.ts +0 -7
  351. package/src/forge/_generated/operationPlaybooks.md +0 -147
  352. package/src/forge/_generated/packageGraph.json +0 -2
  353. package/src/forge/_generated/packageGraph.ts +0 -245249
  354. package/src/forge/_generated/packageUpgradeRegistry.json +0 -2
  355. package/src/forge/_generated/packageUpgradeRegistry.ts +0 -15
  356. package/src/forge/_generated/permissionMatrix.json +0 -2
  357. package/src/forge/_generated/permissionMatrix.ts +0 -7
  358. package/src/forge/_generated/policyRegistry.json +0 -2
  359. package/src/forge/_generated/policyRegistry.ts +0 -11
  360. package/src/forge/_generated/queryRegistry.json +0 -2
  361. package/src/forge/_generated/queryRegistry.ts +0 -9
  362. package/src/forge/_generated/react.d.ts +0 -22
  363. package/src/forge/_generated/react.ts +0 -29
  364. package/src/forge/_generated/reactManifest.json +0 -2
  365. package/src/forge/_generated/reactManifest.ts +0 -19
  366. package/src/forge/_generated/rlsPolicies.json +0 -2
  367. package/src/forge/_generated/rlsPolicies.sql +0 -34
  368. package/src/forge/_generated/rlsPolicies.ts +0 -6
  369. package/src/forge/_generated/runtimeGraph.json +0 -2
  370. package/src/forge/_generated/runtimeGraph.ts +0 -8
  371. package/src/forge/_generated/runtimeMatrix.json +0 -2
  372. package/src/forge/_generated/runtimeMatrix.ts +0 -327385
  373. package/src/forge/_generated/runtimeRegistry.ts +0 -2
  374. package/src/forge/_generated/runtimeRules.md +0 -79
  375. package/src/forge/_generated/secretRegistry.json +0 -2
  376. package/src/forge/_generated/secretRegistry.ts +0 -50
  377. package/src/forge/_generated/secretsContext.ts +0 -11
  378. package/src/forge/_generated/serverApi.ts +0 -10
  379. package/src/forge/_generated/sourceMapManifest.json +0 -2
  380. package/src/forge/_generated/sourceMapManifest.ts +0 -7
  381. package/src/forge/_generated/sqlPlan.json +0 -2
  382. package/src/forge/_generated/sqlPlan.ts +0 -88
  383. package/src/forge/_generated/subscriptionManifest.json +0 -2
  384. package/src/forge/_generated/subscriptionManifest.ts +0 -7
  385. package/src/forge/_generated/symbolicationManifest.json +0 -2
  386. package/src/forge/_generated/symbolicationManifest.ts +0 -17
  387. package/src/forge/_generated/telemetryRegistry.json +0 -2
  388. package/src/forge/_generated/telemetryRegistry.ts +0 -9
  389. package/src/forge/_generated/telemetrySinks.json +0 -2
  390. package/src/forge/_generated/telemetrySinks.ts +0 -11
  391. package/src/forge/_generated/tenantScope.json +0 -2
  392. package/src/forge/_generated/tenantScope.ts +0 -8
  393. package/src/forge/_generated/testGraph.json +0 -2
  394. package/src/forge/_generated/testGraph.ts +0 -3108
  395. package/src/forge/_generated/testPlanRegistry.json +0 -2
  396. package/src/forge/_generated/testPlanRegistry.ts +0 -33
  397. package/src/forge/_generated/uiRoutes.json +0 -2
  398. package/src/forge/_generated/uiRoutes.ts +0 -16
  399. package/src/forge/_generated/uiScenarios.json +0 -2
  400. package/src/forge/_generated/uiScenarios.ts +0 -30
  401. package/src/forge/_generated/uiTestManifest.json +0 -2
  402. package/src/forge/_generated/uiTestManifest.ts +0 -27
  403. package/src/forge/_generated/workflowRegistry.json +0 -2
  404. package/src/forge/_generated/workflowRegistry.ts +0 -9
  405. package/src/forge/_generated/workflowSubscriptions.json +0 -2
  406. package/src/forge/_generated/workflowSubscriptions.ts +0 -10
@@ -4,6 +4,7 @@ import { createDiagnostic } from "../compiler/diagnostics/create.ts";
4
4
  import { GENERATED_DIR, GENERATOR_VERSION } from "../compiler/emitter/constants.ts";
5
5
  import { stripDeterministicHeader } from "../compiler/primitives/header.ts";
6
6
  import { hashStable } from "../compiler/primitives/hash.ts";
7
+ import { normalizePath } from "../compiler/primitives/paths.ts";
7
8
  import { serializeCanonical } from "../compiler/primitives/serialize.ts";
8
9
  import { secretLeakScan } from "../compiler/sandbox/secret-scan.ts";
9
10
  import type { Diagnostic } from "../compiler/types/diagnostic.ts";
@@ -27,19 +28,40 @@ import type {
27
28
  AgentDoctorResult,
28
29
  AgentExportFile,
29
30
  AgentExportResult,
31
+ AgentHooksSmokeResult,
32
+ AgentHooksStatusResult,
33
+ AgentOnboardResult,
30
34
  AgentPrintContextResult,
35
+ AgentPrepareResult,
36
+ AgentTimelineItem,
37
+ AgentTimelineResult,
31
38
  AgentTargetsResult,
32
39
  CustomAdapterConfig,
33
40
  AgentCheckResult,
34
41
  } from "./types.ts";
42
+ import type { AgentMemoryEventRecord } from "../agent-memory/types.ts";
43
+ import {
44
+ formatAgentMemoryHuman,
45
+ formatAgentMemoryJson,
46
+ inspectAgentMemoryQueueFile,
47
+ runAgentMemoryCommand,
48
+ type AgentMemoryCommandResult,
49
+ } from "../agent-memory/bridge.ts";
50
+ import {
51
+ compareHookForgeVersions,
52
+ inspectCodexHookCommands,
53
+ probeCodexHookRunner,
54
+ readCodexHookMeta,
55
+ resolveForgeOnPath,
56
+ } from "../agent-memory/hook-runner.ts";
57
+ import { releaseManifest } from "../_generated/releaseManifest.ts";
58
+ import { runDevConsoleCycle } from "../dev-console/cycle.ts";
35
59
 
36
60
  export const AGENT_ADAPTER_VERSION = "agent-adapter-0.1.0";
37
61
  export const AGENT_FORMAT_VERSION = "2026-06";
38
62
 
39
63
  const USER_START = "<!-- user-notes:start -->";
40
64
  const USER_END = "<!-- user-notes:end -->";
41
- const GENERATED_START = "<!-- forge-generated:start -->";
42
- const GENERATED_END = "<!-- forge-generated:end -->";
43
65
  const CUSTOM_ADAPTERS_DIR = ".forge/agent-adapters";
44
66
 
45
67
  function sorted(values: string[]): string[] {
@@ -172,7 +194,7 @@ export function buildAgentContext(contract: AgentContract): AgentContext {
172
194
  },
173
195
  knownPitfalls: [
174
196
  "Do not edit src/forge/_generated/** directly.",
175
- "Do not use process.env directly in app code.",
197
+ "Do not read secrets or server runtime config through process.env in Forge runtime code; public frontend bridge env is allowed.",
176
198
  "Do not import network packages in command/query/liveQuery.",
177
199
  "Preserve tenant isolation and policy declarations.",
178
200
  "Use forge make, forge feature, forge refactor, forge impact, and forge repair before hand-editing architecture.",
@@ -222,99 +244,6 @@ export function buildAgentDoneCriteria(): AgentDoneCriteria {
222
244
  };
223
245
  }
224
246
 
225
- function replaceGeneratedBlock(existing: string | null, generated: string, fallbackUserNotes: string): string {
226
- const userBlock = extractUserBlock(existing) ?? `${USER_START}\n\n${fallbackUserNotes}\n\n${USER_END}`;
227
- return `${GENERATED_START}\n${generated.trim()}\n${GENERATED_END}\n\n${userBlock.trim()}\n`;
228
- }
229
-
230
- function extractUserBlock(existing: string | null): string | null {
231
- if (!existing) {
232
- return null;
233
- }
234
- const start = existing.indexOf(USER_START);
235
- const end = existing.indexOf(USER_END);
236
- if (start === -1 || end === -1 || end < start) {
237
- return null;
238
- }
239
- return existing.slice(start, end + USER_END.length);
240
- }
241
-
242
- function agentsMarkdown(contract: AgentContract, existing: string | null): string {
243
- const context = buildAgentContext(contract);
244
- const generated = `# AGENTS.md
245
-
246
- ## Project Type
247
-
248
- This is a ForgeOS application.
249
-
250
- ## Required Workflow
251
-
252
- Before editing:
253
-
254
- \`\`\`bash
255
- forge inspect all --json
256
- forge doctor --json
257
- \`\`\`
258
-
259
- During editing:
260
-
261
- \`\`\`bash
262
- forge impact --changed --json
263
- forge test plan --changed --json
264
- \`\`\`
265
-
266
- After editing:
267
-
268
- \`\`\`bash
269
- forge generate
270
- forge check
271
- forge verify --strict
272
- \`\`\`
273
-
274
- ## Do Not
275
-
276
- - Do not edit \`src/forge/_generated/**\`.
277
- - Do not import network packages in \`command\`, \`query\`, or \`liveQuery\`.
278
- - Do not use \`process.env\` directly in app code.
279
- - Use \`ctx.secrets\`.
280
- - Do not bypass tenant isolation.
281
- - Do not call \`ctx.ai\` in \`command\`, \`query\`, or \`liveQuery\`.
282
- - Do not manually modify \`forge.lock\` unless instructed.
283
-
284
- ## Runtime Model
285
-
286
- - \`command\`: ${context.runtimeModel.command}.
287
- - \`query\`: ${context.runtimeModel.query}.
288
- - \`liveQuery\`: ${context.runtimeModel.liveQuery}.
289
- - \`action\`: ${context.runtimeModel.action}.
290
- - \`workflow\`: ${context.runtimeModel.workflow}.
291
-
292
- ## Common Commands
293
-
294
- \`\`\`bash
295
- forge make resource <name>
296
- forge feature plan <blueprint>
297
- forge refactor rename field <from> <to>
298
- forge impact --changed --json
299
- forge repair diagnose --from-last-test-run --json
300
- forge agent print-context --json
301
- \`\`\`
302
-
303
- ## Agent Adapter Exports
304
-
305
- - Generic agents read \`.forge/agent/context.json\` and \`.forge/agent/playbooks/*.md\`.
306
- - Codex skills are generated under \`.codex/skills/**\`.
307
- - Cursor rules are generated under \`.cursor/rules/**\`.
308
- - Claude instructions are generated in \`CLAUDE.md\` and \`.claude/**\`.
309
-
310
- These files are derived from ForgeOS generated contracts. Regenerate them with:
311
-
312
- \`\`\`bash
313
- forge agent export --target all
314
- \`\`\``;
315
- return `# AGENTS.md\n\n${replaceGeneratedBlock(existing, generated, "Project-specific human notes go here.")}`;
316
- }
317
-
318
247
  function playbook(title: string, steps: string[]): string {
319
248
  return `# Playbook: ${title}\n\n${steps.map((step, index) => `${index + 1}. ${step}`).join("\n")}\n`;
320
249
  }
@@ -322,7 +251,7 @@ function playbook(title: string, steps: string[]): string {
322
251
  function playbookFiles(): AgentExportFile[] {
323
252
  const books: Array<[string, string, string[]]> = [
324
253
  ["add-command.md", "Add Command", [
325
- "Run `forge inspect all --json`.",
254
+ "Run `forge status --json`, `forge handoff --json`, and `forge agent print-context --json`.",
326
255
  "Prefer `forge make command <resource.action> --table <table> --policy <policy>`.",
327
256
  "Commands may write through `ctx.db` and emit with `ctx.emit`.",
328
257
  "Commands must not import network packages, use `ctx.secrets`, or call `ctx.ai`.",
@@ -378,7 +307,7 @@ function playbookFiles(): AgentExportFile[] {
378
307
  "Prefer targeted repairs and impacted tests before full verify.",
379
308
  ]],
380
309
  ["frontend-change.md", "Frontend Change", [
381
- "Use generated client APIs and React hooks.",
310
+ "Use generated client APIs and framework bindings: React hooks or Vue composables.",
382
311
  "Do not import server adapters or server-only packages into client code.",
383
312
  "Preserve `ForgeError.traceId` in visible error states.",
384
313
  "Run affected frontend tests and `forge verify --changed`.",
@@ -395,9 +324,8 @@ function playbookFiles(): AgentExportFile[] {
395
324
  }));
396
325
  }
397
326
 
398
- function buildGenericFiles(contract: AgentContract, existingAgentsMd?: string | null): AgentExportFile[] {
327
+ function buildGenericFiles(contract: AgentContract): AgentExportFile[] {
399
328
  return [
400
- { path: "AGENTS.md", content: existingAgentsMd ?? agentsMarkdown(contract, null) },
401
329
  { path: ".forge/agent/context.json", content: renderJson(buildAgentContext(contract)) },
402
330
  { path: ".forge/agent/commands.json", content: renderJson(buildAgentCommandsMap()) },
403
331
  { path: ".forge/agent/done-criteria.json", content: renderJson(buildAgentDoneCriteria()) },
@@ -405,10 +333,25 @@ function buildGenericFiles(contract: AgentContract, existingAgentsMd?: string |
405
333
  ];
406
334
  }
407
335
 
336
+ function buildGenericSupportFiles(contract: AgentContract): AgentExportFile[] {
337
+ return buildGenericFiles(contract);
338
+ }
339
+
408
340
  function skill(name: string, description: string, body: string): string {
409
341
  return `---\nname: ${name}\ndescription: ${description}\n---\n\n# ${description}\n\n${body.trim()}\n`;
410
342
  }
411
343
 
344
+ function codexAgentRole(name: string, description: string, developerInstructions: string): string {
345
+ return [
346
+ `name = "forge-${name}"`,
347
+ `description = "${description}"`,
348
+ "developer_instructions = '''",
349
+ developerInstructions.trim(),
350
+ "'''",
351
+ "",
352
+ ].join("\n");
353
+ }
354
+
412
355
  function buildCodexFiles(_contract: AgentContract, options: { skills: boolean }): AgentExportFile[] {
413
356
  if (!options.skills) {
414
357
  return [];
@@ -423,7 +366,7 @@ Rules:
423
366
  - Commands must not use \`ctx.secrets\` or \`ctx.ai\`.
424
367
 
425
368
  Steps:
426
- 1. Run \`forge inspect all --json\`.
369
+ 1. Run \`forge status --json\`, \`forge handoff --json\`, and \`forge agent print-context --json\`.
427
370
  2. Prefer \`forge make command <resource.action> --table <table> --policy <policy>\`.
428
371
  3. Run \`forge generate\`, \`forge check\`, and \`forge verify --changed\`.
429
372
  4. Finish with \`forge verify --strict\`.
@@ -457,11 +400,45 @@ Prefer targeted checks before full verification.
457
400
  path: `.codex/skills/${name}/SKILL.md`,
458
401
  content: skill(name, description, body),
459
402
  }));
460
- const agents = ["explorer", "worker", "reviewer", "security"].map((name) => ({
403
+ const agents: Array<[string, string, string]> = [
404
+ ["explorer", "Explore a ForgeOS app contract, routes, runtime entries, policies, and generated context before editing.", `
405
+ You are the ForgeOS explorer role.
406
+
407
+ Use ForgeOS inspection commands before reading broad file trees:
408
+ - forge status --json
409
+ - forge changed --json
410
+ - forge agent print-context --json
411
+ - forge inspect all --brief --json
412
+
413
+ Explain the app shape, key runtime entries, frontend bindings, policies, generated files, and likely files to inspect next. Prefer concise findings with exact file paths. Do not modify files.
414
+ `],
415
+ ["worker", "Implement ForgeOS app changes using generated contracts, playbooks, and verification commands.", `
416
+ You are the ForgeOS worker role.
417
+
418
+ Before editing, read the generated agent context and changed-file summary. Prefer ForgeOS primitives such as forge do, forge make, forge add, forge refactor, and forge repair when they fit. Keep authored changes separate from generated artifacts, never edit src/forge/_generated directly, and run forge generate plus focused checks after source changes.
419
+
420
+ Return the implemented change, files touched, verification commands, and any remaining risks.
421
+ `],
422
+ ["reviewer", "Review ForgeOS changes with focus on behavior, generated drift, policies, tests, and handoff readiness.", `
423
+ You are the ForgeOS reviewer role.
424
+
425
+ Start from forge changed --json and forge review run --changed --json. Review authored source, tests, docs, and config before generated artifacts. Look for runtime boundary violations, stale generated files, missing tests, tenant/auth mistakes, frontend binding drift, and unclear handoff state.
426
+
427
+ Lead with findings ordered by severity and include file paths and commands that prove the issue.
428
+ `],
429
+ ["security", "Audit ForgeOS security boundaries, secrets, tenant scope, policies, hooks, and agent memory surfaces.", `
430
+ You are the ForgeOS security role.
431
+
432
+ Focus on command/query/liveQuery/action/workflow boundaries, process.env usage, ctx.secrets placement, tenant isolation, policy coverage, generated RLS artifacts, hook trust, Agent Memory privacy, and AI tool approval risks. Use forge inspect policies --json, forge auth check --json, forge ai redteam --json, and forge changed --json when relevant.
433
+
434
+ Report concrete risks, impacted files, exploit path if applicable, and the smallest safe fix.
435
+ `],
436
+ ];
437
+ const agentFiles = agents.map(([name, description, instructions]) => ({
461
438
  path: `.codex/agents/forge-${name}.toml`,
462
- content: `name = "forge-${name}"\ndescription = "ForgeOS ${name} helper generated from agentAdapterManifest."\n`,
439
+ content: codexAgentRole(name, description, instructions),
463
440
  }));
464
- return [...files, ...agents];
441
+ return [...files, ...agentFiles];
465
442
  }
466
443
 
467
444
  function mdc(description: string, globs: string[], body: string): string {
@@ -511,7 +488,7 @@ function buildCursorFiles(_contract: AgentContract, options: { rules: boolean })
511
488
  `# ForgeOS Security Rules
512
489
 
513
490
  - Never include secret values in generated files, logs, or adapter exports.
514
- - Use \`ctx.secrets\`; do not read \`process.env\` directly in app code.
491
+ - Use \`ctx.secrets\` or generated config context for runtime secrets/config; public frontend bridge env is allowed.
515
492
  - Preserve tenant-scoped reads and writes.
516
493
  - Run \`forge policy check --strict-policies\` after access changes.`,
517
494
  ),
@@ -541,8 +518,11 @@ This is a ForgeOS app.
541
518
  Start with:
542
519
 
543
520
  \`\`\`bash
544
- forge inspect all --json
545
- forge doctor --json
521
+ forge status --json
522
+ forge handoff --json
523
+ forge dev --once --json
524
+ forge agent print-context --json
525
+ forge check --json
546
526
  \`\`\`
547
527
 
548
528
  After changes:
@@ -558,7 +538,7 @@ Critical rules:
558
538
  - Commands cannot use network packages, secrets, or AI.
559
539
  - Queries/liveQueries are read-only.
560
540
  - Use \`ctx.emit\` for side effects.
561
- - Use \`ctx.secrets\`, never \`process.env\`.
541
+ - Use \`ctx.secrets\` or generated config context for runtime secrets/config; public frontend bridge env is allowed.
562
542
  `;
563
543
  return [
564
544
  { path: "CLAUDE.md", content: claude },
@@ -668,23 +648,22 @@ function builtFilesForTarget(
668
648
  target: AgentAdapterTarget,
669
649
  options: Pick<AgentCommandOptions, "skills" | "rules">,
670
650
  ): { files: AgentExportFile[]; diagnostics: Diagnostic[] } {
671
- const existingAgentsMd = readText(workspaceRoot, "AGENTS.md");
672
651
  if (target === "generic") {
673
- return { files: buildGenericFiles(contract, existingAgentsMd), diagnostics: [] };
652
+ return { files: buildGenericFiles(contract), diagnostics: [] };
674
653
  }
675
654
  if (target === "codex") {
676
- return { files: [...buildGenericFiles(contract, existingAgentsMd), ...buildCodexFiles(contract, { skills: options.skills })], diagnostics: [] };
655
+ return { files: [...buildGenericSupportFiles(contract), ...buildCodexFiles(contract, { skills: options.skills })], diagnostics: [] };
677
656
  }
678
657
  if (target === "cursor") {
679
- return { files: [...buildGenericFiles(contract, existingAgentsMd), ...buildCursorFiles(contract, { rules: options.rules })], diagnostics: [] };
658
+ return { files: [...buildGenericSupportFiles(contract), ...buildCursorFiles(contract, { rules: options.rules })], diagnostics: [] };
680
659
  }
681
660
  if (target === "claude") {
682
- return { files: [...buildGenericFiles(contract, existingAgentsMd), ...buildClaudeFiles(contract)], diagnostics: [] };
661
+ return { files: [...buildGenericSupportFiles(contract), ...buildClaudeFiles(contract)], diagnostics: [] };
683
662
  }
684
663
  if (target === "all") {
685
664
  const byPath = new Map<string, AgentExportFile>();
686
665
  for (const file of [
687
- ...buildGenericFiles(contract, existingAgentsMd),
666
+ ...buildGenericFiles(contract),
688
667
  ...buildCodexFiles(contract, { skills: options.skills }),
689
668
  ...buildCursorFiles(contract, { rules: options.rules }),
690
669
  ...buildClaudeFiles(contract),
@@ -831,10 +810,26 @@ export function runAgentCheck(options: AgentCommandOptions): AgentCheckResult {
831
810
  }
832
811
  const diag = [...built.diagnostics, ...validation];
833
812
  for (const file of stale) {
834
- diag.push(diagnostic("error", FORGE_AGENT_STALE_EXPORT, `stale agent adapter export: ${file}`, file));
813
+ diag.push(createDiagnostic({
814
+ severity: "error",
815
+ code: FORGE_AGENT_STALE_EXPORT,
816
+ message: `stale agent adapter export: ${file}`,
817
+ file,
818
+ fixHint: `Regenerate the ${options.target} adapter export.`,
819
+ suggestedCommands: [`forge agent export --target ${options.target}`, "forge verify --strict"],
820
+ docs: ["src/forge/_generated/agentAdapterManifest.json", "AGENTS.md"],
821
+ }));
835
822
  }
836
823
  for (const file of missing) {
837
- diag.push(diagnostic("error", FORGE_AGENT_STALE_EXPORT, `missing agent adapter export: ${file}`, file));
824
+ diag.push(createDiagnostic({
825
+ severity: "error",
826
+ code: FORGE_AGENT_STALE_EXPORT,
827
+ message: `missing agent adapter export: ${file}`,
828
+ file,
829
+ fixHint: `Generate the ${options.target} adapter export.`,
830
+ suggestedCommands: [`forge agent export --target ${options.target}`, "forge verify --strict"],
831
+ docs: ["src/forge/_generated/agentAdapterManifest.json", "AGENTS.md"],
832
+ }));
838
833
  }
839
834
  const ok = stale.length === 0 && missing.length === 0 && diag.every((item) => item.severity !== "error");
840
835
  return {
@@ -930,22 +925,1263 @@ export function runAgentClean(options: AgentCommandOptions): AgentExportResult {
930
925
  };
931
926
  }
932
927
 
933
- export function runAgentDoctor(options: AgentCommandOptions): AgentDoctorResult {
928
+ function eventBindings(event: AgentMemoryEventRecord): Record<string, unknown> {
929
+ const data = event.data;
930
+ const bindings = data && typeof data === "object" && "bindings" in data
931
+ ? (data as { bindings?: unknown }).bindings
932
+ : undefined;
933
+ return bindings && typeof bindings === "object" && !Array.isArray(bindings)
934
+ ? bindings as Record<string, unknown>
935
+ : {};
936
+ }
937
+
938
+ function eventPayload(event: AgentMemoryEventRecord): Record<string, unknown> {
939
+ const data = event.data;
940
+ const envelope = data && typeof data === "object" && "envelope" in data
941
+ ? (data as { envelope?: unknown }).envelope
942
+ : undefined;
943
+ const payload = envelope && typeof envelope === "object" && "payload" in envelope
944
+ ? (envelope as { payload?: unknown }).payload
945
+ : undefined;
946
+ return payload && typeof payload === "object" && !Array.isArray(payload)
947
+ ? payload as Record<string, unknown>
948
+ : {};
949
+ }
950
+
951
+ function eventIsForgeHookCanary(event: AgentMemoryEventRecord): boolean {
952
+ return eventPayload(event).forgeHookCanary === "FORGE_HOOK_SMOKE_CANARY";
953
+ }
954
+
955
+ function eventIsForgeHookProbe(event: AgentMemoryEventRecord): boolean {
956
+ const payload = eventPayload(event);
957
+ return payload.forgeHookProbe === true ||
958
+ payload._parseError === true ||
959
+ payload._invalidPayload === true;
960
+ }
961
+
962
+ function eventHasUsefulSignal(event: AgentMemoryEventRecord): boolean {
963
+ const bindings = eventBindings(event);
964
+ const files = bindings.files;
965
+ const entries = bindings.entries;
966
+ const proofs = bindings.proofs;
967
+ return (
968
+ typeof bindings.toolName === "string" ||
969
+ typeof bindings.command === "string" ||
970
+ typeof bindings.status === "string" ||
971
+ (Array.isArray(files) && files.length > 0) ||
972
+ (Array.isArray(entries) && entries.length > 0) ||
973
+ (Array.isArray(proofs) && proofs.length > 0)
974
+ );
975
+ }
976
+
977
+ function eventIsNativeHookEvent(event: AgentMemoryEventRecord): boolean {
978
+ return (
979
+ !eventIsForgeHookCanary(event) &&
980
+ !eventIsForgeHookProbe(event) &&
981
+ event.integrationKind === "native-hook" &&
982
+ event.trustLevel === "direct-hook"
983
+ );
984
+ }
985
+
986
+ function hookApprovalStatusFor(
987
+ target: AgentAdapterTarget,
988
+ installed: boolean,
989
+ nativeSignals: number,
990
+ memoryReadable = true,
991
+ ): "not-required" | "waiting-for-user-trust" | "trusted" | "memory-unavailable" {
992
+ if (target !== "codex" || !installed) {
993
+ return "not-required";
994
+ }
995
+ if (!memoryReadable) {
996
+ return "memory-unavailable";
997
+ }
998
+ return nativeSignals > 0 ? "trusted" : "waiting-for-user-trust";
999
+ }
1000
+
1001
+ function codexHookApprovalMessage(
1002
+ approvalStatus: ReturnType<typeof hookApprovalStatusFor>,
1003
+ canarySignals = 0,
1004
+ ): string {
1005
+ if (approvalStatus === "waiting-for-user-trust") {
1006
+ if (canarySignals > 0) {
1007
+ return "ForgeOS can see the Codex smoke canary, but has not seen a normal trusted Codex hook event yet; continue or send one Codex message in this workspace";
1008
+ }
1009
+ return "ForgeOS has not seen a trusted native Codex hook signal yet; approve the Codex Desktop hook prompt if shown, then continue a Codex session in this workspace";
1010
+ }
1011
+ if (approvalStatus === "trusted") {
1012
+ return "Codex Desktop hook trust is confirmed by a native hook signal";
1013
+ }
1014
+ if (approvalStatus === "memory-unavailable") {
1015
+ return "Codex Desktop hook trust cannot be verified until Agent Memory is readable";
1016
+ }
1017
+ return "Codex Desktop hook trust is not required";
1018
+ }
1019
+
1020
+ function hookApprovalNextActions(target: AgentAdapterTarget, canarySignals = 0): string[] {
1021
+ if (target !== "codex") {
1022
+ return [];
1023
+ }
1024
+ return canarySignals > 0
1025
+ ? [
1026
+ "Continue or send one Codex message in this workspace so a normal native hook event is emitted",
1027
+ "If Codex Desktop shows a hook approval prompt, approve it",
1028
+ `forge agent hooks status --target ${target} --json`,
1029
+ ]
1030
+ : [
1031
+ "Approve the installed hooks in Codex Desktop (Confiar em tudo or Revisar hooks)",
1032
+ "Start or continue a Codex session in this workspace so a native hook event is emitted",
1033
+ `forge agent hooks status --target ${target} --json`,
1034
+ ];
1035
+ }
1036
+
1037
+ function inspectQueuedHookSignals(workspaceRoot: string, installTarget: string | null) {
1038
+ if (installTarget !== "codex") {
1039
+ return undefined;
1040
+ }
1041
+ return inspectAgentMemoryQueueFile({
1042
+ workspaceRoot,
1043
+ watchFile: join(workspaceRoot, ".forge", "agent", "events.ndjson"),
1044
+ source: "codex",
1045
+ });
1046
+ }
1047
+
1048
+ function stringArray(value: unknown): string[] {
1049
+ return Array.isArray(value)
1050
+ ? value.filter((item): item is string => typeof item === "string" && item.length > 0)
1051
+ : [];
1052
+ }
1053
+
1054
+ function stringValue(value: unknown): string | undefined {
1055
+ return typeof value === "string" && value.length > 0 ? value : undefined;
1056
+ }
1057
+
1058
+ function agentTimelineItem(event: AgentMemoryEventRecord): AgentTimelineItem {
1059
+ const bindings = eventBindings(event);
1060
+ return {
1061
+ id: event.id,
1062
+ source: event.sourceName,
1063
+ integration: event.integrationKind,
1064
+ trustLevel: event.trustLevel,
1065
+ kind: event.normalizedKind,
1066
+ capturedAt: event.capturedAt,
1067
+ ...(event.externalSessionId ? { sessionId: event.externalSessionId } : {}),
1068
+ ...(event.externalTurnId ? { turnId: event.externalTurnId } : {}),
1069
+ ...(event.summary ? { summary: event.summary } : {}),
1070
+ ...(stringValue(bindings.toolName) ? { toolName: stringValue(bindings.toolName) } : {}),
1071
+ ...(stringValue(bindings.command) ? { command: stringValue(bindings.command) } : {}),
1072
+ ...(stringValue(bindings.status) ? { status: stringValue(bindings.status) } : {}),
1073
+ files: stringArray(bindings.files),
1074
+ entries: stringArray(bindings.entries),
1075
+ proofs: stringArray(bindings.proofs),
1076
+ confidence: event.confidence,
1077
+ };
1078
+ }
1079
+
1080
+ function agentTimelineSourceFilter(target: AgentAdapterTarget): string | undefined {
1081
+ if (!target || target === "all" || target === "generic") {
1082
+ return undefined;
1083
+ }
1084
+ return agentMemorySourceForTarget(target);
1085
+ }
1086
+
1087
+ export async function runAgentTimeline(options: AgentCommandOptions): Promise<AgentTimelineResult> {
1088
+ const target = options.target || "all";
1089
+ const sourceFilter = agentTimelineSourceFilter(target);
1090
+ const requestedLimit = options.limit ?? 50;
1091
+ const memoryResult = await runAgentMemoryCommand({
1092
+ subcommand: "memory",
1093
+ workspaceRoot: options.workspaceRoot,
1094
+ json: true,
1095
+ target: "generic",
1096
+ source: "generic",
1097
+ limit: sourceFilter ? 200 : requestedLimit,
1098
+ });
1099
+ if (!("events" in memoryResult) || memoryResult.ok === false) {
1100
+ const diagnostics = "diagnostics" in memoryResult ? memoryResult.diagnostics ?? [] : [];
1101
+ const nextActions = "nextActions" in memoryResult
1102
+ ? memoryResult.nextActions ?? ["forge delta status --json"]
1103
+ : ["forge delta status --json"];
1104
+ return {
1105
+ schemaVersion: "0.1.0",
1106
+ ok: false,
1107
+ timeline: "agent",
1108
+ target,
1109
+ ...(sourceFilter ? { sourceFilter } : {}),
1110
+ summary: { events: 0, sessions: 0, files: 0, entries: 0, proofs: 0, tools: 0 },
1111
+ events: [],
1112
+ files: [],
1113
+ entries: [],
1114
+ proofs: [],
1115
+ sessions: [],
1116
+ nextActions,
1117
+ diagnostics,
1118
+ exitCode: 1,
1119
+ };
1120
+ }
1121
+
1122
+ const filtered = sourceFilter
1123
+ ? memoryResult.events.filter((event) => event.sourceName === sourceFilter)
1124
+ : memoryResult.events;
1125
+ const events = filtered.slice(-requestedLimit).map(agentTimelineItem);
1126
+ const files = sorted(events.flatMap((event) => event.files));
1127
+ const entries = sorted(events.flatMap((event) => event.entries));
1128
+ const proofs = sorted(events.flatMap((event) => event.proofs));
1129
+ const sessions = sorted(events.flatMap((event) => event.sessionId ? [event.sessionId] : []));
1130
+ const tools = sorted(events.flatMap((event) => event.toolName ? [event.toolName] : []));
1131
+ return {
1132
+ schemaVersion: "0.1.0",
1133
+ ok: true,
1134
+ timeline: "agent",
1135
+ target,
1136
+ ...(sourceFilter ? { sourceFilter } : {}),
1137
+ summary: {
1138
+ events: events.length,
1139
+ sessions: sessions.length,
1140
+ files: files.length,
1141
+ entries: entries.length,
1142
+ proofs: proofs.length,
1143
+ tools: tools.length,
1144
+ ...(events.at(-1)?.capturedAt ? { latestEventAt: events.at(-1)?.capturedAt } : {}),
1145
+ },
1146
+ events,
1147
+ files,
1148
+ entries,
1149
+ proofs,
1150
+ sessions,
1151
+ nextActions: [
1152
+ "forge agent context --current --json",
1153
+ "forge changed --json",
1154
+ "forge timeline --json --for-agent",
1155
+ ],
1156
+ diagnostics: [],
1157
+ exitCode: 0,
1158
+ };
1159
+ }
1160
+
1161
+ function agentMemorySourceForTarget(target: AgentAdapterTarget): string {
1162
+ if (target === "claude") return "claude-code";
1163
+ if (target === "codex" || target === "cursor" || target === "claude-code") return target;
1164
+ return String(target || "generic");
1165
+ }
1166
+
1167
+ function hookInstallFilesPresent(workspaceRoot: string, installResult: unknown): {
1168
+ planned: string[];
1169
+ missing: string[];
1170
+ } {
1171
+ const planned =
1172
+ installResult &&
1173
+ typeof installResult === "object" &&
1174
+ "filesPlanned" in installResult &&
1175
+ Array.isArray((installResult as { filesPlanned?: unknown }).filesPlanned)
1176
+ ? ((installResult as { filesPlanned: unknown[] }).filesPlanned.filter((file): file is string => typeof file === "string"))
1177
+ : [];
1178
+ return {
1179
+ planned,
1180
+ missing: planned.filter((file) => !nodeFileSystem.exists(join(workspaceRoot, file))),
1181
+ };
1182
+ }
1183
+
1184
+ function lastAgentSignal(events: AgentMemoryEventRecord[]): AgentHooksStatusResult["lastSignal"] {
1185
+ const event = events.at(-1);
1186
+ return event
1187
+ ? {
1188
+ kind: event.normalizedKind,
1189
+ ...(event.summary ? { summary: event.summary } : {}),
1190
+ capturedAt: event.capturedAt,
1191
+ ...(eventWorkspaceRoot(event) ? { workspaceRoot: eventWorkspaceRoot(event) } : {}),
1192
+ }
1193
+ : undefined;
1194
+ }
1195
+
1196
+ function normalizeAgentWorkspaceRoot(value: unknown): string {
1197
+ return normalizePath(String(value ?? "")).replace(/\/+$/, "").toLowerCase();
1198
+ }
1199
+
1200
+ function eventWorkspaceRoot(event: AgentMemoryEventRecord): string | undefined {
1201
+ const envelope = event.data?.envelope;
1202
+ if (!envelope || typeof envelope !== "object" || Array.isArray(envelope)) {
1203
+ return undefined;
1204
+ }
1205
+ const workspace = (envelope as { workspace?: unknown }).workspace;
1206
+ if (!workspace || typeof workspace !== "object" || Array.isArray(workspace)) {
1207
+ return undefined;
1208
+ }
1209
+ const root = (workspace as { root?: unknown }).root;
1210
+ return typeof root === "string" && root.trim() ? root : undefined;
1211
+ }
1212
+
1213
+ function eventBelongsToWorkspace(event: AgentMemoryEventRecord, workspaceRoot: string): boolean {
1214
+ const root = eventWorkspaceRoot(event);
1215
+ if (!root) {
1216
+ return true;
1217
+ }
1218
+ return normalizeAgentWorkspaceRoot(root) === normalizeAgentWorkspaceRoot(workspaceRoot);
1219
+ }
1220
+
1221
+ async function readHookMemoryStatus(
1222
+ workspaceRoot: string,
1223
+ source: string,
1224
+ limit: number,
1225
+ ): Promise<{
1226
+ events: AgentMemoryEventRecord[];
1227
+ diagnostics: Diagnostic[];
1228
+ ignoredOutOfWorkspaceEvents: number;
1229
+ workspaceRoot: string;
1230
+ }> {
1231
+ const diagnostics: Diagnostic[] = [];
1232
+ const memoryResult = await runAgentMemoryCommand({
1233
+ subcommand: "memory",
1234
+ workspaceRoot,
1235
+ json: true,
1236
+ target: source,
1237
+ source,
1238
+ entry: source,
1239
+ limit,
1240
+ }).catch((error: unknown) => {
1241
+ diagnostics.push(diagnostic(
1242
+ "error",
1243
+ "FORGE_AGENT_MEMORY_UNAVAILABLE",
1244
+ error instanceof Error ? error.message : "agent memory store is unavailable",
1245
+ ));
1246
+ return { ok: false as const, events: [], exitCode: 1 as const };
1247
+ });
1248
+ if (
1249
+ memoryResult &&
1250
+ typeof memoryResult === "object" &&
1251
+ "diagnostics" in memoryResult &&
1252
+ Array.isArray((memoryResult as { diagnostics?: unknown }).diagnostics)
1253
+ ) {
1254
+ diagnostics.push(...(memoryResult as { diagnostics: Diagnostic[] }).diagnostics);
1255
+ }
1256
+ const allEvents = "events" in memoryResult ? memoryResult.events ?? [] : [];
1257
+ const events = allEvents.filter((event) => eventBelongsToWorkspace(event, workspaceRoot));
1258
+ return {
1259
+ events,
1260
+ diagnostics,
1261
+ ignoredOutOfWorkspaceEvents: allEvents.length - events.length,
1262
+ workspaceRoot: normalizeAgentWorkspaceRoot(workspaceRoot),
1263
+ };
1264
+ }
1265
+
1266
+ async function readAgentHookStatus(options: AgentCommandOptions): Promise<AgentHooksStatusResult> {
1267
+ const target = options.target || "codex";
1268
+ const source = agentMemorySourceForTarget(target);
1269
+ const installTarget = hookInstallTarget(target);
1270
+ if (!installTarget) {
1271
+ const diag = diagnostic(
1272
+ "error",
1273
+ "FORGE_AGENT_HOOK_TARGET_UNSUPPORTED",
1274
+ `agent hooks supports codex, claude, and cursor targets; got ${target}`,
1275
+ );
1276
+ return {
1277
+ ok: false,
1278
+ target,
1279
+ installed: false,
1280
+ bridgeWritable: false,
1281
+ deltaWritable: false,
1282
+ visibleInMemory: false,
1283
+ recentEvents: 0,
1284
+ usefulSignals: 0,
1285
+ nativeSignals: 0,
1286
+ canarySignals: 0,
1287
+ approvalRequired: false,
1288
+ approvalStatus: "not-required",
1289
+ checks: [{ name: "target", ok: false, message: diag.message }],
1290
+ nextActions: ["forge agent list-targets --json"],
1291
+ diagnostics: [diag],
1292
+ exitCode: 1,
1293
+ };
1294
+ }
1295
+
1296
+ const installResult = await runAgentMemoryCommand({
1297
+ subcommand: "install",
1298
+ workspaceRoot: options.workspaceRoot,
1299
+ json: options.json,
1300
+ target: installTarget,
1301
+ source: installTarget,
1302
+ dryRun: true,
1303
+ force: false,
1304
+ });
1305
+ const installOk =
1306
+ typeof installResult === "object" && installResult !== null && "exitCode" in installResult
1307
+ ? (installResult as { exitCode?: number }).exitCode === 0
1308
+ : true;
1309
+ const hookFiles = hookInstallFilesPresent(options.workspaceRoot, installResult);
1310
+ const memory = await readHookMemoryStatus(options.workspaceRoot, source, options.limit ?? 25);
1311
+ const queuedHooks = inspectQueuedHookSignals(options.workspaceRoot, installTarget);
1312
+ const usefulEvents = memory.events.filter(eventHasUsefulSignal);
1313
+ const nativeEvents = memory.events.filter(eventIsNativeHookEvent);
1314
+ const canaryEvents = memory.events.filter(eventIsForgeHookCanary);
1315
+ const installed = hookFiles.missing.length === 0;
1316
+ const bridgeWritable = installOk;
1317
+ const deltaWritable = memory.diagnostics.length === 0;
1318
+ const visibleInMemory = memory.events.length > 0;
1319
+ const queuedEvents = queuedHooks?.events ?? 0;
1320
+ const usefulSignals = usefulEvents.length + (queuedHooks?.usefulSignals ?? 0);
1321
+ const nativeSignals = nativeEvents.length + (queuedHooks?.nativeSignals ?? 0);
1322
+ const canarySignals = canaryEvents.length + (queuedHooks?.canarySignals ?? 0);
1323
+ const visibleHookSignals = visibleInMemory || queuedEvents > 0;
1324
+ const approvalStatus = hookApprovalStatusFor(target, installed, nativeSignals, deltaWritable);
1325
+ const approvalRequired = approvalStatus === "waiting-for-user-trust";
1326
+ const trustedHookSignals = target === "codex" ? nativeSignals > 0 : true;
1327
+ const codexHookInspection = installTarget === "codex"
1328
+ ? inspectCodexHookCommands(options.workspaceRoot)
1329
+ : undefined;
1330
+ const codexHookMeta = installTarget === "codex"
1331
+ ? readCodexHookMeta(options.workspaceRoot)
1332
+ : undefined;
1333
+ const codexVersionMatch = installTarget === "codex"
1334
+ ? compareHookForgeVersions(codexHookMeta ?? null)
1335
+ : undefined;
1336
+ const forgeOnPath = installTarget === "codex"
1337
+ ? resolveForgeOnPath(options.workspaceRoot)
1338
+ : undefined;
1339
+ const usesLegacyForgeCli = codexHookInspection?.usesLegacyForgeCli === true;
1340
+ const usesLightweightRunner = codexHookInspection?.usesLightweightRunner === true;
1341
+ const hookCommandHealthy = installTarget !== "codex" || (usesLightweightRunner && !usesLegacyForgeCli);
1342
+ const hookVersionHealthy = installTarget !== "codex" || !usesLightweightRunner || codexVersionMatch?.matches === true;
1343
+ const ok = installed && bridgeWritable && deltaWritable && visibleHookSignals && usefulSignals > 0 && trustedHookSignals && !approvalRequired && hookCommandHealthy && hookVersionHealthy;
1344
+ const nextActions = ok
1345
+ ? uniqueCommands([
1346
+ ...(queuedEvents > 0 ? [`forge agent ingest ${source} --file .forge/agent/events.ndjson --json`] : []),
1347
+ `forge agent memory --entry ${source} --json`,
1348
+ `forge agent context --current --json`,
1349
+ ])
1350
+ : uniqueCommands([
1351
+ ...(!installed ? [`forge agent install ${installTarget} --json`] : []),
1352
+ ...(usesLegacyForgeCli ? [`forge agent install ${installTarget} --force --json`] : []),
1353
+ ...(codexVersionMatch && !codexVersionMatch.matches ? [`forge agent install ${installTarget} --force --json`] : []),
1354
+ ...(approvalRequired ? hookApprovalNextActions(target, canarySignals) : []),
1355
+ ...(installed && deltaWritable && !visibleHookSignals ? [`forge agent hooks smoke --target ${target} --json`] : []),
1356
+ ...(visibleHookSignals && usefulSignals === 0 ? [`forge agent ingest ${source} --event PostToolUse --json`] : []),
1357
+ ...(!deltaWritable ? ["forge delta status --json", "forge delta repair --dry-run --json"] : []),
1358
+ ...(queuedEvents > 0 ? [`forge agent ingest ${source} --file .forge/agent/events.ndjson --json`] : []),
1359
+ ...(usesLightweightRunner ? [`forge agent ingest ${source} --watch --file .forge/agent/events.ndjson --json`] : []),
1360
+ ]);
1361
+
1362
+ return {
1363
+ ok,
1364
+ target,
1365
+ installTarget,
1366
+ installed,
1367
+ bridgeWritable,
1368
+ deltaWritable,
1369
+ visibleInMemory,
1370
+ recentEvents: memory.events.length,
1371
+ queuedEvents,
1372
+ usefulSignals,
1373
+ nativeSignals,
1374
+ canarySignals,
1375
+ approvalRequired,
1376
+ approvalStatus,
1377
+ workspaceRoot: memory.workspaceRoot,
1378
+ ignoredOutOfWorkspaceEvents: memory.ignoredOutOfWorkspaceEvents,
1379
+ ...(lastAgentSignal(memory.events) ? { lastSignal: lastAgentSignal(memory.events) } : {}),
1380
+ checks: [
1381
+ {
1382
+ name: "hook-bridge-installed",
1383
+ ok: installed,
1384
+ message: installed
1385
+ ? `${installTarget} hook bridge files are present`
1386
+ : `missing hook bridge files: ${hookFiles.missing.join(", ")}`,
1387
+ evidence: { planned: hookFiles.planned, missing: hookFiles.missing },
1388
+ },
1389
+ {
1390
+ name: "hook-bridge-installable",
1391
+ ok: bridgeWritable,
1392
+ message: bridgeWritable ? "hook bridge install plan is valid" : "hook bridge install failed",
1393
+ },
1394
+ {
1395
+ name: "agent-memory-readable",
1396
+ ok: deltaWritable,
1397
+ message: deltaWritable ? "agent memory store is readable" : "agent memory store is unavailable",
1398
+ },
1399
+ {
1400
+ name: "visible-in-memory",
1401
+ ok: visibleHookSignals,
1402
+ message: visibleInMemory
1403
+ ? `${memory.events.length} recent ${source} events visible for this workspace`
1404
+ : queuedEvents > 0
1405
+ ? `${queuedEvents} queued ${source} hook event(s) visible for this workspace and waiting for ingest`
1406
+ : memory.ignoredOutOfWorkspaceEvents > 0
1407
+ ? `ignored ${memory.ignoredOutOfWorkspaceEvents} ${source} event(s) from other workspaces`
1408
+ : "no hook events visible in memory yet",
1409
+ evidence: {
1410
+ workspaceRoot: memory.workspaceRoot,
1411
+ ignoredOutOfWorkspaceEvents: memory.ignoredOutOfWorkspaceEvents,
1412
+ queuedEvents,
1413
+ queuedNativeSignals: queuedHooks?.nativeSignals ?? 0,
1414
+ queuedCanarySignals: queuedHooks?.canarySignals ?? 0,
1415
+ queuedLatestEventAt: queuedHooks?.latestEventAt,
1416
+ },
1417
+ },
1418
+ {
1419
+ name: "workspace-scope",
1420
+ ok: true,
1421
+ message: memory.ignoredOutOfWorkspaceEvents > 0
1422
+ ? `ignored ${memory.ignoredOutOfWorkspaceEvents} out-of-workspace agent event(s)`
1423
+ : "agent memory events are scoped to this workspace",
1424
+ evidence: {
1425
+ workspaceRoot: memory.workspaceRoot,
1426
+ ignoredOutOfWorkspaceEvents: memory.ignoredOutOfWorkspaceEvents,
1427
+ },
1428
+ },
1429
+ {
1430
+ name: "useful-signals",
1431
+ ok: usefulSignals > 0,
1432
+ message: usefulSignals > 0
1433
+ ? `${usefulSignals} events include useful tool, file, command, status, entry, or proof signals`
1434
+ : "no useful tool, file, command, status, entry, or proof signals found",
1435
+ },
1436
+ {
1437
+ name: "native-hook-signal",
1438
+ ok: target !== "codex" || !deltaWritable || nativeSignals > 0,
1439
+ message: target !== "codex"
1440
+ ? "native Codex hook approval is not required for this target"
1441
+ : !deltaWritable
1442
+ ? "trusted Codex native hook signals cannot be verified until Agent Memory is readable"
1443
+ : nativeSignals > 0
1444
+ ? `${nativeSignals} trusted Codex native hook signal(s) visible or queued`
1445
+ : "Codex has not emitted a trusted native hook signal yet",
1446
+ evidence: {
1447
+ nativeSignals,
1448
+ canarySignals,
1449
+ queuedNativeSignals: queuedHooks?.nativeSignals ?? 0,
1450
+ queuedCanarySignals: queuedHooks?.canarySignals ?? 0,
1451
+ memoryReadable: deltaWritable,
1452
+ trustBoundary: target === "codex" ? "codex-desktop-hook-approval" : "not-required",
1453
+ },
1454
+ },
1455
+ {
1456
+ name: "codex-hook-approval",
1457
+ ok: !approvalRequired && approvalStatus !== "memory-unavailable",
1458
+ message: codexHookApprovalMessage(approvalStatus, canarySignals),
1459
+ evidence: { approvalStatus, approvalRequired },
1460
+ },
1461
+ ...(installTarget === "codex"
1462
+ ? [
1463
+ {
1464
+ name: "hook-runner-mode",
1465
+ ok: hookCommandHealthy,
1466
+ message: usesLegacyForgeCli
1467
+ ? "Codex hooks still call full forge agent ingest; reinstall with forge agent install codex --force"
1468
+ : usesLightweightRunner
1469
+ ? "Codex hooks use the lightweight workspace runner (.forge/agent/codex-hook.mjs)"
1470
+ : "Codex hook command mode is unknown; reinstall hooks",
1471
+ evidence: codexHookInspection,
1472
+ },
1473
+ {
1474
+ name: "hook-forge-version",
1475
+ ok: hookVersionHealthy,
1476
+ message: codexVersionMatch?.matches
1477
+ ? `hook manifest matches runtime Forge ${releaseManifest.packageVersion}`
1478
+ : codexVersionMatch?.installedVersion
1479
+ ? `hook manifest is ${codexVersionMatch.installedVersion}, runtime is ${codexVersionMatch.runtimeVersion}`
1480
+ : "hook manifest is missing; reinstall hooks to pin workspace runner version",
1481
+ evidence: {
1482
+ manifest: codexHookMeta,
1483
+ runtimeVersion: releaseManifest.packageVersion,
1484
+ forgeOnPath,
1485
+ },
1486
+ },
1487
+ {
1488
+ name: "hook-global-forge",
1489
+ ok: !usesLegacyForgeCli,
1490
+ message: usesLegacyForgeCli
1491
+ ? `hooks call global/legacy forge CLI${forgeOnPath?.path ? ` (${forgeOnPath.path}${forgeOnPath.version ? ` ${forgeOnPath.version}` : ""})` : ""}`
1492
+ : forgeOnPath
1493
+ ? `PATH forge resolves to ${forgeOnPath.path}${forgeOnPath.version ? ` (${forgeOnPath.version})` : ""}; hooks use workspace runner instead`
1494
+ : "hooks use workspace runner; no global forge resolution needed",
1495
+ evidence: { forgeOnPath, usesLegacyForgeCli },
1496
+ },
1497
+ ]
1498
+ : []),
1499
+ ],
1500
+ nextActions,
1501
+ installResult,
1502
+ diagnostics: [
1503
+ ...memory.diagnostics,
1504
+ ...(usesLegacyForgeCli
1505
+ ? [createDiagnostic({
1506
+ severity: "warning",
1507
+ code: "FORGE_AGENT_HOOK_LEGACY_CLI",
1508
+ message: "Codex hooks still spawn the full Forge CLI per event. Reinstall with forge agent install codex --force to use the lightweight queue runner.",
1509
+ suggestedCommands: [`forge agent install ${installTarget} --force --json`, `forge agent hooks smoke --target ${target} --json`],
1510
+ })]
1511
+ : []),
1512
+ ...(codexVersionMatch && !codexVersionMatch.matches
1513
+ ? [createDiagnostic({
1514
+ severity: "warning",
1515
+ code: "FORGE_AGENT_HOOK_VERSION_MISMATCH",
1516
+ message: `Installed Codex hook manifest is ${codexVersionMatch.installedVersion}, but this Forge runtime is ${codexVersionMatch.runtimeVersion}.`,
1517
+ suggestedCommands: [`forge agent install ${installTarget} --force --json`],
1518
+ })]
1519
+ : []),
1520
+ ],
1521
+ exitCode: ok ? 0 : 1,
1522
+ };
1523
+ }
1524
+
1525
+ export async function runAgentDoctor(options: AgentCommandOptions): Promise<AgentDoctorResult> {
1526
+ const target = options.target || "generic";
934
1527
  const check = runAgentCheck(options);
1528
+ const source = agentMemorySourceForTarget(target);
1529
+ const installTarget = hookInstallTarget(target);
1530
+ const installResult = installTarget
1531
+ ? await runAgentMemoryCommand({
1532
+ subcommand: "install",
1533
+ workspaceRoot: options.workspaceRoot,
1534
+ json: options.json,
1535
+ target: installTarget,
1536
+ source: installTarget,
1537
+ dryRun: true,
1538
+ force: false,
1539
+ })
1540
+ : undefined;
1541
+ const hookFiles = installTarget
1542
+ ? hookInstallFilesPresent(options.workspaceRoot, installResult)
1543
+ : { planned: [], missing: [] };
1544
+ const memory = await readHookMemoryStatus(options.workspaceRoot, source, options.limit ?? 25);
1545
+ const queuedHooks = inspectQueuedHookSignals(options.workspaceRoot, installTarget);
1546
+ const memoryDiagnostics = memory.diagnostics;
1547
+ const recentEvents = memory.events;
1548
+ const usefulEvents = recentEvents.filter(eventHasUsefulSignal);
1549
+ const nativeEvents = recentEvents.filter(eventIsNativeHookEvent);
1550
+ const canaryEvents = recentEvents.filter(eventIsForgeHookCanary);
1551
+ const queuedEvents = queuedHooks?.events ?? 0;
1552
+ const visibleHookSignals = recentEvents.length > 0 || queuedEvents > 0;
1553
+ const usefulSignals = usefulEvents.length + (queuedHooks?.usefulSignals ?? 0);
1554
+ const nativeSignals = nativeEvents.length + (queuedHooks?.nativeSignals ?? 0);
1555
+ const canarySignals = canaryEvents.length + (queuedHooks?.canarySignals ?? 0);
1556
+ const adapterState = check.missing.length > 0 ? "missing" : check.stale.length > 0 ? "stale" : "ready";
1557
+ const installed = !installTarget || hookFiles.missing.length === 0;
1558
+ const memoryReadable = memoryDiagnostics.length === 0;
1559
+ const approvalStatus = hookApprovalStatusFor(target, Boolean(installTarget && installed), nativeSignals, memoryReadable);
1560
+ const approvalRequired = approvalStatus === "waiting-for-user-trust";
1561
+ const hookBridgeState = !installTarget
1562
+ ? "not-supported"
1563
+ : !memoryReadable
1564
+ ? "memory-unavailable"
1565
+ : approvalRequired
1566
+ ? "waiting-for-user-trust"
1567
+ : hookFiles.missing.length === 0
1568
+ ? "ready"
1569
+ : "missing";
935
1570
  const checks = [
936
- { name: "AGENTS.md", ok: !check.missing.includes("AGENTS.md") },
1571
+ {
1572
+ name: "adapter-export",
1573
+ ok: check.missing.length === 0 && check.stale.length === 0,
1574
+ message: adapterState === "ready" ? "agent adapter exports are current" : `adapter exports are ${adapterState}`,
1575
+ evidence: { missing: check.missing, stale: check.stale },
1576
+ },
1577
+ { name: "AGENTS.md", ok: readText(options.workspaceRoot, "AGENTS.md") !== null },
937
1578
  { name: "agent-context", ok: !check.missing.includes(".forge/agent/context.json") },
938
1579
  { name: "commands", ok: !check.missing.includes(".forge/agent/commands.json") },
939
1580
  { name: "done-criteria", ok: !check.missing.includes(".forge/agent/done-criteria.json") },
940
- { name: "stale-exports", ok: check.stale.length === 0, message: check.stale.join(", ") || undefined },
1581
+ {
1582
+ name: "hook-bridge",
1583
+ ok: !installTarget || hookFiles.missing.length === 0,
1584
+ message: !installTarget
1585
+ ? "this target has no native hook bridge"
1586
+ : hookFiles.missing.length === 0
1587
+ ? `${installTarget} hook bridge files are present`
1588
+ : `missing hook bridge files: ${hookFiles.missing.join(", ")}`,
1589
+ evidence: { target: installTarget, planned: hookFiles.planned, missing: hookFiles.missing },
1590
+ },
1591
+ {
1592
+ name: "recent-memory",
1593
+ ok: (!installTarget || visibleHookSignals) && memoryDiagnostics.length === 0,
1594
+ message: memoryDiagnostics.length > 0
1595
+ ? "agent memory store is unavailable"
1596
+ : recentEvents.length > 0
1597
+ ? `${recentEvents.length} recent ${source} memory events for this workspace`
1598
+ : queuedEvents > 0
1599
+ ? `${queuedEvents} queued ${source} hook event(s) for this workspace are waiting for ingest`
1600
+ : memory.ignoredOutOfWorkspaceEvents > 0
1601
+ ? `ignored ${memory.ignoredOutOfWorkspaceEvents} ${source} event(s) from other workspaces`
1602
+ : "no recent agent memory events found",
1603
+ evidence: {
1604
+ workspaceRoot: memory.workspaceRoot,
1605
+ ignoredOutOfWorkspaceEvents: memory.ignoredOutOfWorkspaceEvents,
1606
+ recent: recentEvents.slice(-5).map((event) => ({
1607
+ kind: event.normalizedKind,
1608
+ summary: event.summary,
1609
+ capturedAt: event.capturedAt,
1610
+ workspaceRoot: eventWorkspaceRoot(event),
1611
+ })),
1612
+ queuedEvents,
1613
+ queuedNativeSignals: queuedHooks?.nativeSignals ?? 0,
1614
+ queuedCanarySignals: queuedHooks?.canarySignals ?? 0,
1615
+ queuedLatestEventAt: queuedHooks?.latestEventAt,
1616
+ },
1617
+ },
1618
+ {
1619
+ name: "workspace-scope",
1620
+ ok: true,
1621
+ message: memory.ignoredOutOfWorkspaceEvents > 0
1622
+ ? `ignored ${memory.ignoredOutOfWorkspaceEvents} out-of-workspace agent event(s)`
1623
+ : "agent memory events are scoped to this workspace",
1624
+ evidence: {
1625
+ workspaceRoot: memory.workspaceRoot,
1626
+ ignoredOutOfWorkspaceEvents: memory.ignoredOutOfWorkspaceEvents,
1627
+ },
1628
+ },
1629
+ {
1630
+ name: "useful-signals",
1631
+ ok: !installTarget || usefulSignals > 0,
1632
+ message: usefulSignals > 0
1633
+ ? `${usefulSignals} events include files, entries, commands, tools, status, or proofs`
1634
+ : "events do not yet include useful files, entries, commands, tools, status, or proofs",
1635
+ },
1636
+ {
1637
+ name: "native-hook-signal",
1638
+ ok: !installTarget || target !== "codex" || !memoryReadable || nativeSignals > 0,
1639
+ message: !installTarget || target !== "codex"
1640
+ ? "native Codex hook approval is not required for this target"
1641
+ : !memoryReadable
1642
+ ? "trusted Codex native hook signals cannot be verified until Agent Memory is readable"
1643
+ : nativeSignals > 0
1644
+ ? `${nativeSignals} trusted Codex native hook signal(s) visible or queued`
1645
+ : "Codex has not emitted a trusted native hook signal yet",
1646
+ evidence: {
1647
+ nativeSignals,
1648
+ canarySignals,
1649
+ queuedNativeSignals: queuedHooks?.nativeSignals ?? 0,
1650
+ queuedCanarySignals: queuedHooks?.canarySignals ?? 0,
1651
+ memoryReadable,
1652
+ trustBoundary: target === "codex" ? "codex-desktop-hook-approval" : "not-required",
1653
+ },
1654
+ },
1655
+ {
1656
+ name: "codex-hook-approval",
1657
+ ok: !approvalRequired && approvalStatus !== "memory-unavailable",
1658
+ message: codexHookApprovalMessage(approvalStatus, canarySignals),
1659
+ evidence: { approvalStatus, approvalRequired },
1660
+ },
941
1661
  { name: "secret-scan", ok: !check.diagnostics.some((diag) => diag.code === FORGE_AGENT_SECRET_LEAK) },
942
1662
  ];
943
1663
  const ok = checks.every((item) => item.ok) && check.exitCode === 0;
944
- return { ok, checks, diagnostics: check.diagnostics, exitCode: ok ? 0 : 1 };
1664
+ const nextActions = ok
1665
+ ? [
1666
+ `forge agent context --current --json`,
1667
+ `forge agent memory --entry ${source} --json`,
1668
+ ]
1669
+ : [
1670
+ ...(check.missing.length > 0 || check.stale.length > 0 ? [`forge agent export --target ${target}`] : []),
1671
+ ...(installTarget && hookFiles.missing.length > 0 ? [`forge agent install ${installTarget} --json`] : []),
1672
+ ...(approvalRequired ? hookApprovalNextActions(target, canarySignals) : []),
1673
+ ...(memoryDiagnostics.length > 0 ? ["forge delta status --json", "forge delta repair --dry-run --json"] : []),
1674
+ ...(installTarget && !visibleHookSignals && memoryDiagnostics.length === 0 ? [`forge agent hooks smoke --target ${target} --json`] : []),
1675
+ ...(installTarget && visibleHookSignals && usefulSignals === 0 ? [`forge agent ingest ${source} --event PostToolUse --json`] : []),
1676
+ ...(queuedEvents > 0 ? [`forge agent ingest ${source} --file .forge/agent/events.ndjson --json`] : []),
1677
+ ];
1678
+ return {
1679
+ ok,
1680
+ target,
1681
+ summary: {
1682
+ adapter: adapterState,
1683
+ hookBridge: hookBridgeState,
1684
+ approvalRequired,
1685
+ approvalStatus,
1686
+ recentEvents: recentEvents.length,
1687
+ queuedEvents,
1688
+ usefulSignals,
1689
+ nativeSignals,
1690
+ canarySignals,
1691
+ ...(recentEvents.at(-1)?.capturedAt || queuedHooks?.latestEventAt
1692
+ ? { lastEventAt: queuedHooks?.latestEventAt ?? recentEvents.at(-1)?.capturedAt }
1693
+ : {}),
1694
+ },
1695
+ checks,
1696
+ nextActions,
1697
+ diagnostics: [...check.diagnostics, ...memoryDiagnostics],
1698
+ exitCode: ok ? 0 : 1,
1699
+ };
1700
+ }
1701
+
1702
+ function hookInstallTarget(target: AgentAdapterTarget): string | null {
1703
+ if (target === "codex") return "codex";
1704
+ if (target === "claude") return "claude-code";
1705
+ if (target === "cursor") return "cursor";
1706
+ return null;
1707
+ }
1708
+
1709
+ function openCommandForTarget(target: AgentAdapterTarget): string | undefined {
1710
+ if (target === "codex") return "codex";
1711
+ if (target === "claude") return "claude";
1712
+ if (target === "cursor") return "cursor .";
1713
+ return undefined;
1714
+ }
1715
+
1716
+ function agentCommandHints(target: AgentAdapterTarget): AgentPrepareResult["commands"] {
1717
+ const installTarget = hookInstallTarget(target);
1718
+ return {
1719
+ context: "forge agent context --current --json",
1720
+ export: `forge agent export --target ${target}`,
1721
+ check: `forge agent check --target ${target} --json`,
1722
+ ...(installTarget ? { install: `forge agent install ${installTarget} --json` } : {}),
1723
+ ...(installTarget ? { hooksStatus: `forge agent hooks status --target ${target} --json` } : {}),
1724
+ ...(installTarget ? { hooksSmoke: `forge agent hooks smoke --target ${target} --json` } : {}),
1725
+ ...(openCommandForTarget(target) ? { open: openCommandForTarget(target) } : {}),
1726
+ };
1727
+ }
1728
+
1729
+ export async function runAgentPrepare(options: AgentCommandOptions): Promise<AgentPrepareResult> {
1730
+ const target = options.target || "generic";
1731
+ const exportResult = runAgentExport({ ...options, target });
1732
+ const installTarget = hookInstallTarget(target);
1733
+ const installResult = installTarget
1734
+ ? await runAgentMemoryCommand({
1735
+ subcommand: "install",
1736
+ workspaceRoot: options.workspaceRoot,
1737
+ json: options.json,
1738
+ target: installTarget,
1739
+ source: installTarget,
1740
+ dryRun: options.dryRun,
1741
+ force: options.force,
1742
+ })
1743
+ : undefined;
1744
+ const checkResult = runAgentCheck({ ...options, target });
1745
+ const diagnostics = [
1746
+ ...exportResult.diagnostics,
1747
+ ...checkResult.diagnostics,
1748
+ ];
1749
+ const installOk =
1750
+ !installResult ||
1751
+ (typeof installResult === "object" && installResult !== null && "exitCode" in installResult
1752
+ ? (installResult as { exitCode?: number }).exitCode === 0
1753
+ : true);
1754
+ const ok = exportResult.ok && checkResult.ok && installOk;
1755
+ return {
1756
+ ok,
1757
+ target,
1758
+ exportResult,
1759
+ checkResult,
1760
+ ...(installResult ? { installResult } : {}),
1761
+ commands: agentCommandHints(target),
1762
+ diagnostics,
1763
+ exitCode: ok ? 0 : 1,
1764
+ };
1765
+ }
1766
+
1767
+ function uniqueCommands(commands: Array<string | undefined>): string[] {
1768
+ return [...new Set(commands.filter((command): command is string => Boolean(command)))];
1769
+ }
1770
+
1771
+ export async function runAgentOnboard(options: AgentCommandOptions): Promise<AgentOnboardResult> {
1772
+ const target = options.target || "codex";
1773
+ const installTarget = hookInstallTarget(target);
1774
+ const initialContext = runAgentPrintContext(options.workspaceRoot);
1775
+ const preflightDev = initialContext.context === null
1776
+ ? await runDevConsoleCycle({
1777
+ workspaceRoot: options.workspaceRoot,
1778
+ mode: "once",
1779
+ strictSecrets: false,
1780
+ includeImpact: false,
1781
+ })
1782
+ : undefined;
1783
+ const prepare = await runAgentPrepare({ ...options, target });
1784
+ const hookSmoke = installTarget && !options.dryRun
1785
+ ? await runAgentHooksSmoke({ ...options, target, subcommand: "hooks", hookAction: "smoke" })
1786
+ : undefined;
1787
+ const doctor = await runAgentDoctor({ ...options, target, subcommand: "doctor" });
1788
+ const context = runAgentPrintContext(options.workspaceRoot);
1789
+ const dev = await runDevConsoleCycle({
1790
+ workspaceRoot: options.workspaceRoot,
1791
+ mode: "once",
1792
+ strictSecrets: true,
1793
+ includeImpact: true,
1794
+ });
1795
+ const agentContext = dev.summary.agentContext;
1796
+ const diagnostics = [
1797
+ ...(preflightDev?.ok ? [] : initialContext.diagnostics),
1798
+ ...(preflightDev?.diagnostics ?? []),
1799
+ ...prepare.diagnostics,
1800
+ ...(hookSmoke?.diagnostics ?? []),
1801
+ ...doctor.diagnostics,
1802
+ ...context.diagnostics,
1803
+ ...dev.diagnostics,
1804
+ ];
1805
+ const readyToEdit =
1806
+ prepare.ok &&
1807
+ context.context !== null &&
1808
+ dev.ok &&
1809
+ agentContext.safeToEdit &&
1810
+ (!hookSmoke || hookSmoke.ok) &&
1811
+ doctor.summary.adapter === "ready" &&
1812
+ !doctor.summary.approvalRequired;
1813
+ const steps = [
1814
+ ...(preflightDev
1815
+ ? [{
1816
+ name: "generated-preflight",
1817
+ ok: preflightDev.ok,
1818
+ message: preflightDev.ok
1819
+ ? "generated context was created before adapter preparation"
1820
+ : "generated context preflight failed",
1821
+ }]
1822
+ : []),
1823
+ {
1824
+ name: "adapter-prepare",
1825
+ ok: prepare.ok,
1826
+ message: prepare.ok
1827
+ ? `${target} adapter files are present and current`
1828
+ : `${target} adapter files need attention`,
1829
+ },
1830
+ ...(hookSmoke
1831
+ ? [{
1832
+ name: "hook-smoke",
1833
+ ok: hookSmoke.ok,
1834
+ message: hookSmoke.approvalRequired
1835
+ ? `${target} hook files and canary are visible, but Codex Desktop still needs user trust approval`
1836
+ : hookSmoke.ok
1837
+ ? `${target} hooks recorded a useful canary signal`
1838
+ : `${target} hooks did not prove memory visibility`,
1839
+ }]
1840
+ : [{
1841
+ name: "hook-smoke",
1842
+ ok: !installTarget,
1843
+ message: installTarget
1844
+ ? "hook smoke skipped because this was a dry run"
1845
+ : "this target has no native hook bridge",
1846
+ }]),
1847
+ ...(installTarget
1848
+ ? [{
1849
+ name: "hook-approval",
1850
+ ok: !doctor.summary.approvalRequired,
1851
+ message: doctor.summary.approvalRequired
1852
+ ? "approve the installed hooks in Codex Desktop if prompted, then continue or start a Codex session in this workspace"
1853
+ : doctor.summary.approvalStatus === "memory-unavailable"
1854
+ ? "Codex hook trust cannot be verified until Agent Memory is readable"
1855
+ : doctor.summary.approvalStatus === "trusted"
1856
+ ? "Codex hook trust is confirmed by a native hook signal"
1857
+ : "hook approval is not required for this target",
1858
+ }]
1859
+ : []),
1860
+ {
1861
+ name: "agent-doctor",
1862
+ ok: doctor.ok,
1863
+ message: doctor.ok ? "adapter, hooks, and memory are ready" : "agent doctor found follow-up actions",
1864
+ },
1865
+ {
1866
+ name: "dev-snapshot",
1867
+ ok: dev.ok && agentContext.safeToEdit,
1868
+ message: dev.ok
1869
+ ? `safeToEdit=${agentContext.safeToEdit}; generatedFresh=${agentContext.generatedFresh}; generatedChangedFiles=${agentContext.generatedChangedFiles}; changedFiles=${agentContext.changedFiles}`
1870
+ : "dev snapshot found blocking diagnostics",
1871
+ },
1872
+ {
1873
+ name: "context",
1874
+ ok: context.context !== null,
1875
+ message: context.context ? "generated agent context is readable" : "generated agent context is missing",
1876
+ },
1877
+ ];
1878
+ const commandHints = agentCommandHints(target);
1879
+ const nextActions = readyToEdit
1880
+ ? uniqueCommands([
1881
+ commandHints.open,
1882
+ "forge changed --json",
1883
+ "forge agent context --current --json",
1884
+ "forge do verify --json",
1885
+ ])
1886
+ : uniqueCommands([
1887
+ ...doctor.nextActions,
1888
+ ...dev.nextActions.map((action) => action.command),
1889
+ ...(context.context ? [] : ["forge generate"]),
1890
+ "forge dev --once --json",
1891
+ ]);
1892
+ return {
1893
+ schemaVersion: "0.1.0",
1894
+ ok: readyToEdit,
1895
+ target,
1896
+ readyToEdit,
1897
+ summary: {
1898
+ adapter: doctor.summary.adapter,
1899
+ hookBridge: doctor.summary.hookBridge,
1900
+ approvalRequired: doctor.summary.approvalRequired,
1901
+ approvalStatus: doctor.summary.approvalStatus,
1902
+ memorySignals: doctor.summary.usefulSignals,
1903
+ nativeSignals: doctor.summary.nativeSignals,
1904
+ canarySignals: doctor.summary.canarySignals,
1905
+ generatedFresh: agentContext.generatedFresh,
1906
+ generatedChanged: agentContext.generatedChanged,
1907
+ generatedChangedFiles: agentContext.generatedChangedFiles,
1908
+ safeToEdit: agentContext.safeToEdit,
1909
+ changedFiles: agentContext.changedFiles,
1910
+ ...(dev.summary.primaryAction?.command ? { primaryAction: dev.summary.primaryAction.command } : {}),
1911
+ },
1912
+ steps,
1913
+ recommendedReadFiles: agentContext.recommendedReadFiles,
1914
+ commands: {
1915
+ changed: "forge changed --json",
1916
+ dev: "forge dev --once --json",
1917
+ context: "forge agent context --current --json",
1918
+ verify: "forge do verify --json",
1919
+ ...(commandHints.hooksStatus ? { hooksStatus: commandHints.hooksStatus } : {}),
1920
+ ...(commandHints.hooksSmoke ? { hooksSmoke: commandHints.hooksSmoke } : {}),
1921
+ ...(commandHints.open ? { open: commandHints.open } : {}),
1922
+ },
1923
+ nextActions,
1924
+ diagnostics,
1925
+ exitCode: readyToEdit ? 0 : 1,
1926
+ };
1927
+ }
1928
+
1929
+ export async function runAgentHooksStatus(options: AgentCommandOptions): Promise<AgentHooksStatusResult> {
1930
+ return readAgentHookStatus(options);
1931
+ }
1932
+
1933
+ export async function runAgentHooksSmoke(options: AgentCommandOptions): Promise<AgentHooksSmokeResult> {
1934
+ const target = options.target || "codex";
1935
+ const installTarget = hookInstallTarget(target);
1936
+ const source = agentMemorySourceForTarget(target);
1937
+ const canaryMarker = "FORGE_HOOK_SMOKE_CANARY";
1938
+ if (!installTarget) {
1939
+ const diag = diagnostic(
1940
+ "error",
1941
+ "FORGE_AGENT_HOOK_TARGET_UNSUPPORTED",
1942
+ `agent hook smoke supports codex, claude, and cursor targets; got ${target}`,
1943
+ );
1944
+ return {
1945
+ ok: false,
1946
+ target,
1947
+ smokeReady: false,
1948
+ trustedNativeReady: false,
1949
+ readinessLevel: "none",
1950
+ installed: false,
1951
+ bridgeWritable: false,
1952
+ deltaWritable: false,
1953
+ visibleInMemory: false,
1954
+ usefulSignals: 0,
1955
+ nativeSignals: 0,
1956
+ canarySignals: 0,
1957
+ approvalRequired: false,
1958
+ approvalStatus: "not-required",
1959
+ checks: [{ name: "target", ok: false, message: diag.message }],
1960
+ nextActions: ["forge agent list-targets --json"],
1961
+ diagnostics: [diag],
1962
+ exitCode: 1,
1963
+ };
1964
+ }
1965
+
1966
+ const installResult = await runAgentMemoryCommand({
1967
+ subcommand: "install",
1968
+ workspaceRoot: options.workspaceRoot,
1969
+ json: options.json,
1970
+ target: installTarget,
1971
+ source: installTarget,
1972
+ dryRun: options.dryRun,
1973
+ force: options.force,
1974
+ });
1975
+ const installOk =
1976
+ typeof installResult === "object" && installResult !== null && "exitCode" in installResult
1977
+ ? (installResult as { exitCode?: number }).exitCode === 0
1978
+ : true;
1979
+
1980
+ const ingestResult = options.dryRun
1981
+ ? undefined
1982
+ : await runAgentMemoryCommand({
1983
+ subcommand: "ingest",
1984
+ workspaceRoot: options.workspaceRoot,
1985
+ json: options.json,
1986
+ target: installTarget,
1987
+ source: installTarget,
1988
+ eventName: installTarget === "cursor" ? "FileChange" : "SessionStart",
1989
+ input: {
1990
+ forgeHookCanary: canaryMarker,
1991
+ cwd: options.workspaceRoot,
1992
+ provider: installTarget,
1993
+ status: "completed",
1994
+ summary: "Forge hook smoke event recorded",
1995
+ filesChanged: ["AGENTS.md"],
1996
+ command: "forge agent hooks smoke",
1997
+ },
1998
+ });
1999
+ const ingestOk =
2000
+ options.dryRun ||
2001
+ (typeof ingestResult === "object" && ingestResult !== null && "exitCode" in ingestResult
2002
+ ? (ingestResult as { exitCode?: number }).exitCode === 0
2003
+ : false);
2004
+ const ingestDiagnostics =
2005
+ ingestResult &&
2006
+ typeof ingestResult === "object" &&
2007
+ "diagnostics" in ingestResult &&
2008
+ Array.isArray((ingestResult as { diagnostics?: unknown }).diagnostics)
2009
+ ? (ingestResult as { diagnostics: Diagnostic[] }).diagnostics
2010
+ : [];
2011
+ const ingestNextActions =
2012
+ ingestResult &&
2013
+ typeof ingestResult === "object" &&
2014
+ "nextActions" in ingestResult &&
2015
+ Array.isArray((ingestResult as { nextActions?: unknown }).nextActions)
2016
+ ? (ingestResult as { nextActions: unknown[] }).nextActions.filter((action): action is string => typeof action === "string")
2017
+ : [];
2018
+ const ingestStoreBusy = ingestDiagnostics.some((diag) => diag.code === "FORGE_DELTA_BUSY");
2019
+ const hookRunnerProbe = installTarget === "codex" && !options.dryRun
2020
+ ? await probeCodexHookRunner(options.workspaceRoot, { maxDurationMs: 5000, stdinHangBudgetMs: 3000 })
2021
+ : undefined;
2022
+ const ingestedEventId =
2023
+ ingestResult &&
2024
+ typeof ingestResult === "object" &&
2025
+ "event" in ingestResult &&
2026
+ (ingestResult as { event?: { id?: string } }).event?.id;
2027
+ const memoryAfterSmoke = options.dryRun
2028
+ ? { events: [], diagnostics: [] as Diagnostic[] }
2029
+ : await readHookMemoryStatus(options.workspaceRoot, source, Math.max(options.limit ?? 25, 50));
2030
+ const status = await readAgentHookStatus({ ...options, target });
2031
+ const canaryEvent = ingestedEventId
2032
+ ? memoryAfterSmoke.events.find((event) => event.id === ingestedEventId)
2033
+ : undefined;
2034
+ const visibleInMemory = options.dryRun
2035
+ ? false
2036
+ : Boolean(canaryEvent);
2037
+ const checks = [
2038
+ { name: "hook-install", ok: installOk, message: installOk ? "hook bridge files are available" : "hook bridge install failed" },
2039
+ { name: "canary-ingest", ok: ingestOk, message: options.dryRun ? "dry-run skipped ingest" : ingestOk ? "canary event was normalized and stored" : "canary ingest failed" },
2040
+ {
2041
+ name: "canary-memory-readable",
2042
+ ok: options.dryRun || memoryAfterSmoke.diagnostics.length === 0,
2043
+ message: options.dryRun
2044
+ ? "dry-run skipped memory read"
2045
+ : memoryAfterSmoke.diagnostics.length === 0
2046
+ ? `${memoryAfterSmoke.events.length} memory event(s) inspected after canary ingest`
2047
+ : "agent memory was not readable after canary ingest",
2048
+ },
2049
+ {
2050
+ name: "canary-visible",
2051
+ ok: options.dryRun || !ingestOk || visibleInMemory,
2052
+ message: options.dryRun
2053
+ ? "dry-run skipped memory visibility check"
2054
+ : !ingestOk
2055
+ ? "not checked because canary ingest failed"
2056
+ : visibleInMemory
2057
+ ? "canary event is visible in agent memory"
2058
+ : "canary event was not visible in agent memory",
2059
+ },
2060
+ {
2061
+ name: "codex-hook-approval",
2062
+ ok: status.approvalStatus !== "memory-unavailable",
2063
+ message: codexHookApprovalMessage(status.approvalStatus, status.canarySignals),
2064
+ },
2065
+ ...(installTarget === "codex"
2066
+ ? [
2067
+ {
2068
+ name: "hook-runner-latency",
2069
+ ok: options.dryRun || hookRunnerProbe?.ok === true,
2070
+ message: options.dryRun
2071
+ ? "dry-run skipped hook runner latency probe"
2072
+ : hookRunnerProbe?.ok
2073
+ ? `lightweight hook runner exited in ${hookRunnerProbe.durationMs}ms and queued NDJSON`
2074
+ : hookRunnerProbe?.error ?? "hook runner latency probe failed",
2075
+ },
2076
+ {
2077
+ name: "hook-stdin-hang-safe",
2078
+ ok: options.dryRun || hookRunnerProbe?.stdinHangSafe === true,
2079
+ message: options.dryRun
2080
+ ? "dry-run skipped stdin hang probe"
2081
+ : hookRunnerProbe?.stdinHangSafe
2082
+ ? `hook runner exited without waiting for stdin EOF (${hookRunnerProbe.stdinHangDurationMs ?? 0}ms)`
2083
+ : "hook runner may hang when stdin never closes",
2084
+ },
2085
+ ]
2086
+ : []),
2087
+ ];
2088
+ const diagnostics = [
2089
+ ...ingestDiagnostics,
2090
+ ...memoryAfterSmoke.diagnostics,
2091
+ ...(!installOk
2092
+ ? [diagnostic("error", "FORGE_AGENT_HOOK_INSTALL_FAILED", `hook bridge install failed for ${installTarget}`)]
2093
+ : []),
2094
+ ...(!ingestOk && !ingestStoreBusy
2095
+ ? [diagnostic(
2096
+ "error",
2097
+ "FORGE_AGENT_HOOK_CANARY_MISSING",
2098
+ `Forge hook smoke did not record a canary event for ${installTarget}; install hooks and restart the external agent, then run forge agent hooks smoke --target ${target} --json`,
2099
+ )]
2100
+ : []),
2101
+ ...(!options.dryRun && ingestOk && !visibleInMemory
2102
+ ? [createDiagnostic({
2103
+ severity: "error",
2104
+ code: "FORGE_AGENT_HOOK_CANARY_NOT_VISIBLE",
2105
+ message: `Forge hook smoke ingested canary ${ingestedEventId ?? canaryMarker} for ${installTarget}, but that event was not visible in agent memory; inspect hook status and DeltaDB before trusting hooks.`,
2106
+ suggestedCommands: [`forge agent hooks status --target ${target} --json`, `forge agent memory --entry ${source} --json`, "forge delta status --json"],
2107
+ })]
2108
+ : []),
2109
+ ...(status.approvalRequired
2110
+ ? [createDiagnostic({
2111
+ severity: "warning",
2112
+ code: "FORGE_AGENT_HOOK_APPROVAL_REQUIRED",
2113
+ message: "Codex Desktop has installed hook files, but ForgeOS has not seen a trusted native hook signal yet. Approve the hook prompt if Codex shows one, then continue a Codex session in this workspace.",
2114
+ suggestedCommands: hookApprovalNextActions(target, status.canarySignals),
2115
+ })]
2116
+ : []),
2117
+ ...(hookRunnerProbe && !hookRunnerProbe.ok
2118
+ ? [createDiagnostic({
2119
+ severity: "error",
2120
+ code: "FORGE_AGENT_HOOK_RUNNER_SLOW",
2121
+ message: hookRunnerProbe.error ?? `Codex hook runner probe failed after ${hookRunnerProbe.durationMs}ms`,
2122
+ suggestedCommands: [`forge agent install ${installTarget} --force --json`, `forge agent hooks status --target ${target} --json`],
2123
+ })]
2124
+ : []),
2125
+ ];
2126
+ const smokeReady = checks.every((check) => check.ok);
2127
+ const trustedNativeReady =
2128
+ status.approvalStatus === "trusted" || status.approvalStatus === "not-required";
2129
+ const readinessLevel = trustedNativeReady
2130
+ ? "trusted-native"
2131
+ : smokeReady
2132
+ ? "canary"
2133
+ : "none";
2134
+ return {
2135
+ ok: smokeReady,
2136
+ target,
2137
+ installTarget,
2138
+ smokeReady,
2139
+ trustedNativeReady,
2140
+ readinessLevel,
2141
+ installed: status.installed,
2142
+ bridgeWritable: installOk,
2143
+ deltaWritable: status.deltaWritable && ingestOk && memoryAfterSmoke.diagnostics.length === 0,
2144
+ visibleInMemory,
2145
+ usefulSignals: status.usefulSignals,
2146
+ nativeSignals: status.nativeSignals,
2147
+ canarySignals: status.canarySignals,
2148
+ approvalRequired: status.approvalRequired,
2149
+ approvalStatus: status.approvalStatus,
2150
+ ...(canaryEvent ? { lastSignal: lastAgentSignal([canaryEvent]) } : status.lastSignal ? { lastSignal: status.lastSignal } : {}),
2151
+ ...(hookRunnerProbe ? { hookRunnerProbe } : {}),
2152
+ canary: {
2153
+ marker: canaryMarker,
2154
+ source,
2155
+ eventName: installTarget === "cursor" ? "FileChange" : "SessionStart",
2156
+ ...(ingestedEventId ? { ingestedEventId } : {}),
2157
+ memoryEventsChecked: memoryAfterSmoke.events.length,
2158
+ visible: visibleInMemory,
2159
+ },
2160
+ checks,
2161
+ nextActions: smokeReady
2162
+ ? uniqueCommands([
2163
+ ...(status.approvalRequired ? hookApprovalNextActions(target, status.canarySignals) : []),
2164
+ `forge agent hooks status --target ${target} --json`,
2165
+ `forge agent memory --entry ${source} --json`,
2166
+ ])
2167
+ : uniqueCommands([
2168
+ ...ingestNextActions,
2169
+ ...(status.approvalRequired ? hookApprovalNextActions(target, status.canarySignals) : []),
2170
+ ...status.nextActions,
2171
+ `forge agent hooks status --target ${target} --json`,
2172
+ `forge agent memory --entry ${source} --json`,
2173
+ `forge agent timeline --target ${target} --json`,
2174
+ "forge delta status --json",
2175
+ ]),
2176
+ installResult,
2177
+ ...(ingestResult ? { ingestResult } : {}),
2178
+ diagnostics,
2179
+ exitCode: smokeReady ? 0 : 1,
2180
+ };
945
2181
  }
946
2182
 
947
2183
  export async function runAgentCommand(options: AgentCommandOptions): Promise<
948
- AgentExportResult | AgentCheckResult | AgentTargetsResult | AgentPrintContextResult | AgentDoctorResult
2184
+ AgentExportResult | AgentCheckResult | AgentTargetsResult | AgentPrintContextResult | AgentDoctorResult | AgentPrepareResult | AgentOnboardResult | AgentHooksSmokeResult | AgentHooksStatusResult | AgentTimelineResult | AgentMemoryCommandResult
949
2185
  > {
950
2186
  if (options.subcommand === "list-targets") {
951
2187
  return runAgentListTargets(options.workspaceRoot);
@@ -959,12 +2195,51 @@ export async function runAgentCommand(options: AgentCommandOptions): Promise<
959
2195
  if (options.subcommand === "doctor") {
960
2196
  return runAgentDoctor({ ...options, target: options.target || "generic" });
961
2197
  }
2198
+ if (options.subcommand === "onboard") {
2199
+ return runAgentOnboard({ ...options, target: options.target || "codex" });
2200
+ }
962
2201
  if (options.subcommand === "print-context") {
963
2202
  return runAgentPrintContext(options.workspaceRoot);
964
2203
  }
965
2204
  if (options.subcommand === "clean") {
966
2205
  return runAgentClean(options);
967
2206
  }
2207
+ if (options.subcommand === "prepare") {
2208
+ return runAgentPrepare(options);
2209
+ }
2210
+ if (options.subcommand === "hooks") {
2211
+ if (options.hookAction === "status") {
2212
+ return runAgentHooksStatus(options);
2213
+ }
2214
+ return runAgentHooksSmoke(options);
2215
+ }
2216
+ if (options.subcommand === "timeline") {
2217
+ return runAgentTimeline(options);
2218
+ }
2219
+ if (
2220
+ options.subcommand === "install" ||
2221
+ options.subcommand === "ingest" ||
2222
+ options.subcommand === "context" ||
2223
+ options.subcommand === "memory"
2224
+ ) {
2225
+ return runAgentMemoryCommand({
2226
+ subcommand: options.subcommand,
2227
+ workspaceRoot: options.workspaceRoot,
2228
+ json: options.json,
2229
+ target: options.target,
2230
+ source: options.target,
2231
+ eventName: options.eventName,
2232
+ input: options.input,
2233
+ entry: options.entry,
2234
+ current: options.current,
2235
+ dryRun: options.dryRun,
2236
+ force: options.force,
2237
+ limit: options.limit,
2238
+ watch: options.watch,
2239
+ file: options.file,
2240
+ pollIntervalMs: options.pollIntervalMs,
2241
+ });
2242
+ }
968
2243
  return {
969
2244
  ok: false,
970
2245
  target: options.target,
@@ -979,18 +2254,130 @@ export async function runAgentCommand(options: AgentCommandOptions): Promise<
979
2254
  }
980
2255
 
981
2256
  export function formatAgentJson(result: Awaited<ReturnType<typeof runAgentCommand>>): string {
2257
+ if ("timeline" in result && result.timeline === "agent") {
2258
+ return `${JSON.stringify(result, null, 2)}\n`;
2259
+ }
2260
+ if ("privacy" in result || "event" in result || "agentMemory" in result || "events" in result || "watch" in result) {
2261
+ return formatAgentMemoryJson(result as AgentMemoryCommandResult);
2262
+ }
982
2263
  return `${JSON.stringify(result, null, 2)}\n`;
983
2264
  }
984
2265
 
985
2266
  export function formatAgentHuman(result: Awaited<ReturnType<typeof runAgentCommand>>): string {
2267
+ if ("timeline" in result && result.timeline === "agent") {
2268
+ return [
2269
+ `agent timeline for ${result.target}: ${result.summary.events} event(s)`,
2270
+ ...(result.summary.latestEventAt ? [`latest: ${result.summary.latestEventAt}`] : []),
2271
+ ...(result.files.length > 0 ? [`files: ${result.files.slice(0, 8).join(", ")}`] : []),
2272
+ ...(result.entries.length > 0 ? [`entries: ${result.entries.slice(0, 8).join(", ")}`] : []),
2273
+ "",
2274
+ ...result.events.slice(-12).map((event) => {
2275
+ const parts = [
2276
+ event.capturedAt,
2277
+ event.source,
2278
+ event.kind,
2279
+ event.toolName,
2280
+ event.status,
2281
+ event.summary,
2282
+ ].filter(Boolean);
2283
+ return `- ${parts.join(" | ")}`;
2284
+ }),
2285
+ ...(result.nextActions.length > 0 ? ["", "Next:", ...result.nextActions.map((command) => ` ${command}`)] : []),
2286
+ ].join("\n") + "\n";
2287
+ }
2288
+ if ("privacy" in result || "event" in result || "agentMemory" in result || "events" in result || "watch" in result) {
2289
+ return formatAgentMemoryHuman(result as AgentMemoryCommandResult);
2290
+ }
986
2291
  if ("targets" in result) {
987
2292
  return `${result.targets.map((target) => `${target.name}${target.default ? " (default)" : ""}${target.optional ? " (optional)" : ""}${target.custom ? " (custom)" : ""}`).join("\n")}\n`;
988
2293
  }
989
2294
  if ("context" in result) {
990
2295
  return `${JSON.stringify(result.context, null, 2)}\n`;
991
2296
  }
2297
+ if ("exportResult" in result) {
2298
+ return [
2299
+ `agent prepare ${result.ok ? "ok" : "failed"} for ${result.target}`,
2300
+ "commands:",
2301
+ ...Object.entries(result.commands).map(([name, command]) => `- ${name}: ${command}`),
2302
+ "files written:",
2303
+ ...(result.exportResult.filesWritten.length > 0 ? result.exportResult.filesWritten.map((file) => `- ${file}`) : ["- none"]),
2304
+ ].join("\n") + "\n";
2305
+ }
2306
+ if ("readyToEdit" in result) {
2307
+ return [
2308
+ `agent onboard ${result.ok ? "ready" : "needs attention"} for ${result.target}`,
2309
+ `ready to edit: ${result.readyToEdit ? "yes" : "no"}`,
2310
+ `hook bridge: ${result.summary.hookBridge}`,
2311
+ `hook approval: ${result.summary.approvalStatus}`,
2312
+ `generated fresh: ${result.summary.generatedFresh ? "yes" : "no"}`,
2313
+ `generated changed: ${result.summary.generatedChangedFiles}`,
2314
+ `changed files: ${result.summary.changedFiles}`,
2315
+ "",
2316
+ "steps:",
2317
+ ...result.steps.map((step) => `${step.ok ? "OK" : "WARN"} ${step.name}: ${step.message}`),
2318
+ ...(result.nextActions.length > 0 ? ["", "Next:", ...result.nextActions.map((command) => ` ${command}`)] : []),
2319
+ ].join("\n") + "\n";
2320
+ }
2321
+ if ("ingestResult" in result || ("checks" in result && "installResult" in result)) {
2322
+ const smoke = result as AgentHooksSmokeResult;
2323
+ return [
2324
+ `agent hooks smoke ${smoke.ok ? "ok" : "failed"} for ${smoke.target}`,
2325
+ `smoke ready: ${smoke.smokeReady ? "yes" : "no"}`,
2326
+ `trusted native ready: ${smoke.trustedNativeReady ? "yes" : "no"}`,
2327
+ `readiness level: ${smoke.readinessLevel}`,
2328
+ `approval: ${smoke.approvalStatus}`,
2329
+ `native signals: ${smoke.nativeSignals}`,
2330
+ `canary signals: ${smoke.canarySignals}`,
2331
+ ...smoke.checks.map((check) => `${check.ok ? "OK" : "FAIL"} ${check.name}${check.message ? `: ${check.message}` : ""}`),
2332
+ ...(smoke.canary
2333
+ ? [
2334
+ "",
2335
+ "Canary:",
2336
+ ` marker: ${smoke.canary.marker}`,
2337
+ ` source: ${smoke.canary.source}`,
2338
+ ` event: ${smoke.canary.eventName}`,
2339
+ ...(smoke.canary.ingestedEventId ? [` ingested id: ${smoke.canary.ingestedEventId}`] : []),
2340
+ ` memory events checked: ${smoke.canary.memoryEventsChecked}`,
2341
+ ` visible: ${smoke.canary.visible ? "yes" : "no"}`,
2342
+ ]
2343
+ : []),
2344
+ ...(smoke.lastSignal
2345
+ ? [
2346
+ "",
2347
+ `last signal: ${smoke.lastSignal.kind}${smoke.lastSignal.summary ? ` - ${smoke.lastSignal.summary}` : ""}`,
2348
+ `captured at: ${smoke.lastSignal.capturedAt}`,
2349
+ ]
2350
+ : []),
2351
+ ...(smoke.nextActions.length > 0 ? ["", "Next:", ...smoke.nextActions.map((command) => ` ${command}`)] : []),
2352
+ ].join("\n") + "\n";
2353
+ }
2354
+ if ("installed" in result && "visibleInMemory" in result) {
2355
+ return [
2356
+ `agent hooks status ${result.ok ? "ready" : "needs attention"} for ${result.target}`,
2357
+ `installed: ${result.installed ? "yes" : "no"}`,
2358
+ `bridge writable: ${result.bridgeWritable ? "yes" : "no"}`,
2359
+ `delta writable: ${result.deltaWritable ? "yes" : "no"}`,
2360
+ `visible in memory: ${result.visibleInMemory ? "yes" : "no"}`,
2361
+ `useful signals: ${result.usefulSignals}`,
2362
+ `native signals: ${result.nativeSignals}`,
2363
+ `canary signals: ${result.canarySignals}`,
2364
+ `approval: ${result.approvalStatus}`,
2365
+ ...("recentEvents" in result ? [`recent events: ${result.recentEvents}`] : []),
2366
+ ...("queuedEvents" in result ? [`queued events: ${result.queuedEvents ?? 0}`] : []),
2367
+ ...(result.lastSignal ? [`last signal: ${result.lastSignal.kind}${result.lastSignal.summary ? ` - ${result.lastSignal.summary}` : ""}`] : []),
2368
+ ...(result.nextActions.length > 0 ? ["", "Next:", ...result.nextActions.map((command) => ` ${command}`)] : []),
2369
+ ].join("\n") + "\n";
2370
+ }
992
2371
  if ("checks" in result) {
993
- return `Forge Agent Doctor\n\n${result.checks.map((check) => `${check.ok ? "OK" : "WARN"} ${check.name}${check.message ? `: ${check.message}` : ""}`).join("\n")}\n`;
2372
+ const nextActions = "nextActions" in result && Array.isArray(result.nextActions)
2373
+ ? result.nextActions as string[]
2374
+ : [];
2375
+ return [
2376
+ `Forge Agent Doctor ${result.ok ? "ready" : "needs attention"}`,
2377
+ "",
2378
+ ...result.checks.map((check) => `${check.ok ? "OK" : "WARN"} ${check.name}${check.message ? `: ${check.message}` : ""}`),
2379
+ ...(nextActions.length > 0 ? ["", "Next:", ...nextActions.map((command) => ` ${command}`)] : []),
2380
+ ].join("\n") + "\n";
994
2381
  }
995
2382
  if ("stale" in result) {
996
2383
  if (result.ok) {
@@ -998,5 +2385,6 @@ export function formatAgentHuman(result: Awaited<ReturnType<typeof runAgentComma
998
2385
  }
999
2386
  return `agent adapter exports are stale\nmissing: ${result.missing.join(", ") || "none"}\nstale: ${result.stale.join(", ") || "none"}\n`;
1000
2387
  }
1001
- return `agent export ${result.ok ? "ok" : "failed"} for ${result.target}\nfiles written:\n${result.filesWritten.map((file) => `- ${file}`).join("\n") || "- none"}\n`;
2388
+ const exportResult = result as AgentExportResult;
2389
+ return `agent export ${exportResult.ok ? "ok" : "failed"} for ${exportResult.target}\nfiles written:\n${exportResult.filesWritten.map((file: string) => `- ${file}`).join("\n") || "- none"}\n`;
1002
2390
  }