forgeos 0.1.0-alpha.4 → 0.1.0-alpha.41

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