forgeos 0.1.0-alpha.2 → 0.1.0-alpha.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (406) hide show
  1. package/.npmignore +4 -0
  2. package/AGENTS.md +168 -81
  3. package/CHANGELOG.md +211 -0
  4. package/README.md +88 -14
  5. package/adapters/go/README.md +23 -0
  6. package/adapters/go/go.mod +3 -0
  7. package/adapters/go/http.go +149 -0
  8. package/adapters/go/registry.go +234 -0
  9. package/adapters/go/types.go +136 -0
  10. package/adapters/java/README.md +68 -0
  11. package/adapters/java/pom.xml +34 -0
  12. package/adapters/java/src/main/java/dev/forgeos/adapter/Auth.java +20 -0
  13. package/adapters/java/src/main/java/dev/forgeos/adapter/Diagnostic.java +16 -0
  14. package/adapters/java/src/main/java/dev/forgeos/adapter/Entry.java +38 -0
  15. package/adapters/java/src/main/java/dev/forgeos/adapter/EntryKind.java +16 -0
  16. package/adapters/java/src/main/java/dev/forgeos/adapter/ErrorInfo.java +4 -0
  17. package/adapters/java/src/main/java/dev/forgeos/adapter/Forge.java +94 -0
  18. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeCall.java +12 -0
  19. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeContext.java +11 -0
  20. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeHandler.java +8 -0
  21. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeHttpHandler.java +179 -0
  22. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeRegistry.java +121 -0
  23. package/adapters/java/src/main/java/dev/forgeos/adapter/Json.java +14 -0
  24. package/adapters/java/src/main/java/dev/forgeos/adapter/Manifest.java +14 -0
  25. package/adapters/java/src/main/java/dev/forgeos/adapter/RequestEnvelope.java +6 -0
  26. package/adapters/java/src/main/java/dev/forgeos/adapter/ResponseEnvelope.java +25 -0
  27. package/adapters/java/src/main/java/dev/forgeos/adapter/Risk.java +18 -0
  28. package/adapters/java/src/main/java/dev/forgeos/adapter/Schemas.java +36 -0
  29. package/adapters/java/src/main/java/dev/forgeos/adapter/Service.java +65 -0
  30. package/adapters/java/src/main/java/dev/forgeos/adapter/TransactionMode.java +18 -0
  31. package/adapters/java/src/main/java/dev/forgeos/adapter/TypedForgeHandler.java +6 -0
  32. package/adapters/java/target/classes/dev/forgeos/adapter/Auth.class +0 -0
  33. package/adapters/java/target/classes/dev/forgeos/adapter/Diagnostic.class +0 -0
  34. package/adapters/java/target/classes/dev/forgeos/adapter/Entry.class +0 -0
  35. package/adapters/java/target/classes/dev/forgeos/adapter/EntryKind.class +0 -0
  36. package/adapters/java/target/classes/dev/forgeos/adapter/ErrorInfo.class +0 -0
  37. package/adapters/java/target/classes/dev/forgeos/adapter/Forge.class +0 -0
  38. package/adapters/java/target/classes/dev/forgeos/adapter/ForgeCall.class +0 -0
  39. package/adapters/java/target/classes/dev/forgeos/adapter/ForgeContext.class +0 -0
  40. package/adapters/java/target/classes/dev/forgeos/adapter/ForgeHandler.class +0 -0
  41. package/adapters/java/target/classes/dev/forgeos/adapter/ForgeHttpHandler.class +0 -0
  42. package/adapters/java/target/classes/dev/forgeos/adapter/ForgeRegistry$EntryOption.class +0 -0
  43. package/adapters/java/target/classes/dev/forgeos/adapter/ForgeRegistry$RegisteredEntry.class +0 -0
  44. package/adapters/java/target/classes/dev/forgeos/adapter/ForgeRegistry$RegistryOption.class +0 -0
  45. package/adapters/java/target/classes/dev/forgeos/adapter/ForgeRegistry.class +0 -0
  46. package/adapters/java/target/classes/dev/forgeos/adapter/Json.class +0 -0
  47. package/adapters/java/target/classes/dev/forgeos/adapter/Manifest.class +0 -0
  48. package/adapters/java/target/classes/dev/forgeos/adapter/RequestEnvelope.class +0 -0
  49. package/adapters/java/target/classes/dev/forgeos/adapter/ResponseEnvelope.class +0 -0
  50. package/adapters/java/target/classes/dev/forgeos/adapter/Risk.class +0 -0
  51. package/adapters/java/target/classes/dev/forgeos/adapter/Schemas.class +0 -0
  52. package/adapters/java/target/classes/dev/forgeos/adapter/Service.class +0 -0
  53. package/adapters/java/target/classes/dev/forgeos/adapter/TransactionMode.class +0 -0
  54. package/adapters/java/target/classes/dev/forgeos/adapter/TypedForgeHandler.class +0 -0
  55. package/adapters/java/target/forge-java-adapter-0.1.0-alpha.11.jar +0 -0
  56. package/adapters/java/target/maven-archiver/pom.properties +3 -0
  57. package/adapters/java/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +23 -0
  58. package/adapters/java/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +20 -0
  59. package/adapters/java-spring-boot-starter/README.md +32 -0
  60. package/adapters/java-spring-boot-starter/pom.xml +36 -0
  61. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeCommand.java +22 -0
  62. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeExternalService.java +15 -0
  63. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeQuery.java +16 -0
  64. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeServiceBeanCondition.java +18 -0
  65. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeSpringAutoConfiguration.java +16 -0
  66. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeSpringRuntime.java +104 -0
  67. package/adapters/java-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +1 -0
  68. package/adapters/java-spring-boot-starter/target/classes/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +1 -0
  69. package/adapters/java-spring-boot-starter/target/classes/dev/forgeos/adapter/spring/ForgeCommand.class +0 -0
  70. package/adapters/java-spring-boot-starter/target/classes/dev/forgeos/adapter/spring/ForgeExternalService.class +0 -0
  71. package/adapters/java-spring-boot-starter/target/classes/dev/forgeos/adapter/spring/ForgeQuery.class +0 -0
  72. package/adapters/java-spring-boot-starter/target/classes/dev/forgeos/adapter/spring/ForgeServiceBeanCondition.class +0 -0
  73. package/adapters/java-spring-boot-starter/target/classes/dev/forgeos/adapter/spring/ForgeSpringAutoConfiguration.class +0 -0
  74. package/adapters/java-spring-boot-starter/target/classes/dev/forgeos/adapter/spring/ForgeSpringRuntime.class +0 -0
  75. package/adapters/java-spring-boot-starter/target/forge-java-spring-boot-starter-0.1.0-alpha.11.jar +0 -0
  76. package/adapters/java-spring-boot-starter/target/maven-archiver/pom.properties +3 -0
  77. package/adapters/java-spring-boot-starter/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +6 -0
  78. package/adapters/java-spring-boot-starter/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +6 -0
  79. package/bin/forge.mjs +18 -0
  80. package/docs/changelog.md +242 -0
  81. package/docs/forge-protocol.md +189 -0
  82. package/examples/go-billing/go.mod +7 -0
  83. package/examples/go-billing/main.go +120 -0
  84. package/examples/java-billing/pom.xml +52 -0
  85. package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/CreateInvoiceInput.java +4 -0
  86. package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/Invoice.java +11 -0
  87. package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/Main.java +127 -0
  88. package/examples/java-billing/target/classes/dev/forgeos/examples/billing/CreateInvoiceInput.class +0 -0
  89. package/examples/java-billing/target/classes/dev/forgeos/examples/billing/Invoice.class +0 -0
  90. package/examples/java-billing/target/classes/dev/forgeos/examples/billing/Main$EmptyInput.class +0 -0
  91. package/examples/java-billing/target/classes/dev/forgeos/examples/billing/Main$Options.class +0 -0
  92. package/examples/java-billing/target/classes/dev/forgeos/examples/billing/Main.class +0 -0
  93. package/examples/java-billing/target/java-billing-0.1.0-alpha.11-all.jar +0 -0
  94. package/examples/java-billing/target/java-billing-0.1.0-alpha.11.jar +0 -0
  95. package/examples/java-billing/target/maven-archiver/pom.properties +3 -0
  96. package/examples/java-billing/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +5 -0
  97. package/examples/java-billing/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +3 -0
  98. package/package.json +29 -7
  99. package/schemas/forge-manifest.schema.json +57 -0
  100. package/src/forge/_generated/releaseManifest.json +1 -2
  101. package/src/forge/_generated/releaseManifest.ts +3 -3
  102. package/src/forge/agent-adapters/index.ts +1511 -123
  103. package/src/forge/agent-adapters/types.ts +216 -1
  104. package/src/forge/agent-memory/bridge.ts +1245 -0
  105. package/src/forge/agent-memory/context-pack.ts +151 -0
  106. package/src/forge/agent-memory/hook-runner.ts +312 -0
  107. package/src/forge/agent-memory/mcp.ts +224 -0
  108. package/src/forge/agent-memory/normalize.ts +498 -0
  109. package/src/forge/agent-memory/redaction.ts +103 -0
  110. package/src/forge/agent-memory/sources/claude-code.ts +51 -0
  111. package/src/forge/agent-memory/sources/codex-hook-runner.mjs +273 -0
  112. package/src/forge/agent-memory/sources/codex.ts +119 -0
  113. package/src/forge/agent-memory/sources/cursor.ts +35 -0
  114. package/src/forge/agent-memory/types.ts +191 -0
  115. package/src/forge/bench.ts +248 -0
  116. package/src/forge/brownfield-import/index.ts +801 -0
  117. package/src/forge/brownfield-import/types.ts +127 -0
  118. package/src/forge/cair/action-journal.ts +61 -0
  119. package/src/forge/cair/action-parser.ts +314 -0
  120. package/src/forge/cair/action-validator.ts +40 -0
  121. package/src/forge/cair/actions.ts +1818 -0
  122. package/src/forge/cair/format.ts +77 -0
  123. package/src/forge/cair/index.ts +106 -0
  124. package/src/forge/cair/query.ts +478 -0
  125. package/src/forge/cair/snapshot.ts +315 -0
  126. package/src/forge/cair/types.ts +248 -0
  127. package/src/forge/cli/ai.ts +671 -3
  128. package/src/forge/cli/auth.ts +36 -1
  129. package/src/forge/cli/build.ts +20 -4
  130. package/src/forge/cli/changed.ts +300 -0
  131. package/src/forge/cli/codex-app-server.ts +877 -0
  132. package/src/forge/cli/commands.ts +1285 -7
  133. package/src/forge/cli/db.ts +121 -2
  134. package/src/forge/cli/deps.ts +79 -12
  135. package/src/forge/cli/dev.ts +502 -38
  136. package/src/forge/cli/docs.ts +265 -0
  137. package/src/forge/cli/handoff.ts +250 -0
  138. package/src/forge/cli/index.ts +1 -0
  139. package/src/forge/cli/main.ts +49 -3
  140. package/src/forge/cli/new.ts +3 -1
  141. package/src/forge/cli/next-actions.ts +23 -0
  142. package/src/forge/cli/output.ts +290 -1
  143. package/src/forge/cli/parse.ts +770 -36
  144. package/src/forge/cli/query.ts +32 -0
  145. package/src/forge/cli/release.ts +35 -11
  146. package/src/forge/cli/rls.ts +568 -17
  147. package/src/forge/cli/run.ts +41 -0
  148. package/src/forge/cli/secrets.ts +46 -1
  149. package/src/forge/cli/security.ts +381 -0
  150. package/src/forge/cli/self-host.ts +56 -14
  151. package/src/forge/cli/studio.ts +2163 -0
  152. package/src/forge/cli/verify.ts +1422 -32
  153. package/src/forge/compiler/agent-contract/build.ts +725 -41
  154. package/src/forge/compiler/agent-contract/types.ts +85 -0
  155. package/src/forge/compiler/ai-registry/build.ts +62 -1
  156. package/src/forge/compiler/ai-registry/constants.ts +1 -1
  157. package/src/forge/compiler/ai-registry/parse.ts +168 -5
  158. package/src/forge/compiler/api-surface/build.ts +47 -0
  159. package/src/forge/compiler/app-graph/build.ts +68 -8
  160. package/src/forge/compiler/app-graph/extract.ts +107 -0
  161. package/src/forge/compiler/app-graph/forge-apis.ts +1 -0
  162. package/src/forge/compiler/app-graph/module-graph.ts +73 -78
  163. package/src/forge/compiler/app-graph/parser.ts +24 -24
  164. package/src/forge/compiler/app-graph/profile.ts +26 -0
  165. package/src/forge/compiler/app-graph/versions.ts +1 -1
  166. package/src/forge/compiler/classifier/capabilities.ts +3 -2
  167. package/src/forge/compiler/classifier/classify.ts +32 -8
  168. package/src/forge/compiler/classifier/secrets.ts +3 -2
  169. package/src/forge/compiler/classifier/signals.ts +91 -1
  170. package/src/forge/compiler/client-sdk/build-manifest.ts +59 -0
  171. package/src/forge/compiler/client-sdk/render-client.ts +188 -13
  172. package/src/forge/compiler/data-graph/parse.ts +3 -3
  173. package/src/forge/compiler/data-graph/sql/ddl.ts +60 -2
  174. package/src/forge/compiler/data-graph/sql/serialize.ts +4 -0
  175. package/src/forge/compiler/data-graph/sql/types.ts +1 -0
  176. package/src/forge/compiler/dev-manifest/build.ts +3 -0
  177. package/src/forge/compiler/diagnostics/codes.ts +35 -0
  178. package/src/forge/compiler/diagnostics/create.ts +8 -3
  179. package/src/forge/compiler/diagnostics/index.ts +2 -0
  180. package/src/forge/compiler/emitter/barrel.ts +3 -0
  181. package/src/forge/compiler/emitter/render.ts +5 -0
  182. package/src/forge/compiler/external-manifest/registry.ts +205 -0
  183. package/src/forge/compiler/external-manifest/types.ts +91 -0
  184. package/src/forge/compiler/external-manifest/validate.ts +373 -0
  185. package/src/forge/compiler/frontend-graph/build.ts +85 -13
  186. package/src/forge/compiler/integration/add.ts +498 -22
  187. package/src/forge/compiler/integration/snapshot.ts +2 -0
  188. package/src/forge/compiler/make-registry/build.ts +19 -7
  189. package/src/forge/compiler/orchestrator/plan-profile.ts +23 -0
  190. package/src/forge/compiler/orchestrator/plan.ts +78 -7
  191. package/src/forge/compiler/orchestrator/profile.ts +65 -0
  192. package/src/forge/compiler/orchestrator/run.ts +97 -31
  193. package/src/forge/compiler/orchestrator/serialize.ts +101 -8
  194. package/src/forge/compiler/package-graph/compiler.ts +13 -3
  195. package/src/forge/compiler/package-manager/adapter.ts +4 -1
  196. package/src/forge/compiler/package-manager/commands.ts +4 -0
  197. package/src/forge/compiler/package-manager/executor.ts +30 -1
  198. package/src/forge/compiler/policy-registry/build.ts +44 -1
  199. package/src/forge/compiler/test-graph/build.ts +11 -3
  200. package/src/forge/compiler/types/ai-registry.ts +25 -1
  201. package/src/forge/compiler/types/app-graph.ts +9 -2
  202. package/src/forge/compiler/types/cli.ts +76 -1
  203. package/src/forge/compiler/types/dev-manifest.ts +3 -0
  204. package/src/forge/compiler/types/frontend-graph.ts +2 -2
  205. package/src/forge/delta/classifier.ts +52 -0
  206. package/src/forge/delta/explain.ts +126 -0
  207. package/src/forge/delta/git-observer.ts +43 -0
  208. package/src/forge/delta/ids.ts +44 -0
  209. package/src/forge/delta/index.ts +13 -0
  210. package/src/forge/delta/recorder.ts +402 -0
  211. package/src/forge/delta/redaction.ts +50 -0
  212. package/src/forge/delta/schema.ts +240 -0
  213. package/src/forge/delta/session.ts +142 -0
  214. package/src/forge/delta/status.ts +489 -0
  215. package/src/forge/delta/store.ts +2975 -0
  216. package/src/forge/delta/timeline.ts +104 -0
  217. package/src/forge/dev/server.ts +768 -15
  218. package/src/forge/dev/types.ts +15 -1
  219. package/src/forge/dev/watch.ts +17 -7
  220. package/src/forge/dev-console/cycle.ts +233 -21
  221. package/src/forge/dev-console/types.ts +46 -1
  222. package/src/forge/impact/index.ts +46 -8
  223. package/src/forge/impact/types.ts +6 -0
  224. package/src/forge/intent/index.ts +35 -16
  225. package/src/forge/make/index.ts +149 -6
  226. package/src/forge/make/templates.ts +343 -2
  227. package/src/forge/make/types.ts +3 -1
  228. package/src/forge/refactor/index.ts +1 -0
  229. package/src/forge/repair/rules/index.ts +2 -2
  230. package/src/forge/review/index.ts +158 -12
  231. package/src/forge/review/types.ts +15 -0
  232. package/src/forge/runtime/ai/context.ts +210 -5
  233. package/src/forge/runtime/ai/types.ts +70 -0
  234. package/src/forge/runtime/auth/claims.ts +32 -0
  235. package/src/forge/runtime/auth/errors.ts +2 -0
  236. package/src/forge/runtime/context/create-context.ts +30 -6
  237. package/src/forge/runtime/db/generated-client.ts +13 -2
  238. package/src/forge/runtime/db/memory-adapter.ts +2 -2
  239. package/src/forge/runtime/db/pglite-adapter.ts +77 -2
  240. package/src/forge/runtime/db/postgres-adapter.ts +6 -3
  241. package/src/forge/runtime/executor.ts +112 -2
  242. package/src/forge/runtime/external/bridge.ts +649 -0
  243. package/src/forge/runtime/runner/run-entry.ts +16 -7
  244. package/src/forge/runtime/telemetry/scrubber.ts +91 -10
  245. package/src/forge/runtime/webhooks/security.ts +184 -0
  246. package/src/forge/server.ts +100 -2
  247. package/src/forge/version.ts +1 -1
  248. package/src/forge/vue/index.ts +407 -0
  249. package/src/forge/workspace/change-summary.ts +209 -0
  250. package/src/forge/workspace/forge-cli.ts +14 -0
  251. package/src/forge/workspace/git-summary.ts +279 -0
  252. package/templates/agent-workroom/AGENTS.md +29 -0
  253. package/templates/agent-workroom/README.md +34 -0
  254. package/templates/agent-workroom/forge.config.ts +3 -0
  255. package/templates/agent-workroom/package.json +33 -0
  256. package/templates/agent-workroom/src/actions/indexAgentSignal.ts +10 -0
  257. package/templates/agent-workroom/src/commands/openWorkroom.ts +61 -0
  258. package/templates/agent-workroom/src/commands/recordAgentSignal.ts +119 -0
  259. package/templates/agent-workroom/src/commands/recordCheckRun.ts +52 -0
  260. package/templates/agent-workroom/src/forge/schema.ts +54 -0
  261. package/templates/agent-workroom/src/policies.ts +6 -0
  262. package/templates/agent-workroom/src/queries/listWorkrooms.ts +11 -0
  263. package/templates/agent-workroom/src/queries/liveWorkroom.ts +63 -0
  264. package/templates/agent-workroom/tsconfig.json +16 -0
  265. package/templates/agent-workroom/web/index.html +12 -0
  266. package/templates/agent-workroom/web/package.json +21 -0
  267. package/templates/agent-workroom/web/src/App.tsx +345 -0
  268. package/templates/agent-workroom/web/src/lib/forge.ts +13 -0
  269. package/templates/agent-workroom/web/src/main.tsx +13 -0
  270. package/templates/agent-workroom/web/src/styles.css +545 -0
  271. package/templates/agent-workroom/web/tsconfig.json +27 -0
  272. package/templates/b2b-support-web/package.json +2 -0
  273. package/templates/b2b-support-web/tsconfig.json +4 -1
  274. package/templates/b2b-support-web/web/package.json +1 -1
  275. package/templates/minimal-web/package.json +2 -1
  276. package/templates/minimal-web/tsconfig.json +3 -1
  277. package/templates/minimal-web/web/package.json +2 -2
  278. package/src/forge/_generated/actionSubscriptions.json +0 -2
  279. package/src/forge/_generated/actionSubscriptions.ts +0 -10
  280. package/src/forge/_generated/agentAdapterManifest.json +0 -2
  281. package/src/forge/_generated/agentAdapterManifest.ts +0 -73
  282. package/src/forge/_generated/agentContract.json +0 -2
  283. package/src/forge/_generated/agentContract.ts +0 -7696
  284. package/src/forge/_generated/agentQuickstart.md +0 -32
  285. package/src/forge/_generated/aiContext.ts +0 -59
  286. package/src/forge/_generated/aiModels.json +0 -2
  287. package/src/forge/_generated/aiModels.ts +0 -35
  288. package/src/forge/_generated/aiProviders.json +0 -2
  289. package/src/forge/_generated/aiProviders.ts +0 -23
  290. package/src/forge/_generated/aiRegistry.json +0 -2
  291. package/src/forge/_generated/aiRegistry.ts +0 -29
  292. package/src/forge/_generated/api.json +0 -2
  293. package/src/forge/_generated/api.ts +0 -8
  294. package/src/forge/_generated/appGraph.json +0 -2
  295. package/src/forge/_generated/appGraph.ts +0 -14667
  296. package/src/forge/_generated/appMap.md +0 -35
  297. package/src/forge/_generated/artifactManifest.json +0 -2
  298. package/src/forge/_generated/artifactManifest.ts +0 -7
  299. package/src/forge/_generated/authClaims.json +0 -2
  300. package/src/forge/_generated/authClaims.ts +0 -13
  301. package/src/forge/_generated/authConfig.json +0 -2
  302. package/src/forge/_generated/authConfig.ts +0 -17
  303. package/src/forge/_generated/authContext.ts +0 -23
  304. package/src/forge/_generated/authRegistry.json +0 -2
  305. package/src/forge/_generated/authRegistry.ts +0 -25
  306. package/src/forge/_generated/buildInfo.json +0 -2
  307. package/src/forge/_generated/buildInfo.ts +0 -9
  308. package/src/forge/_generated/capabilityMap.json +0 -2
  309. package/src/forge/_generated/capabilityMap.md +0 -15
  310. package/src/forge/_generated/capabilityMap.ts +0 -17
  311. package/src/forge/_generated/client.ts +0 -282
  312. package/src/forge/_generated/clientApi.ts +0 -9
  313. package/src/forge/_generated/clientManifest.json +0 -2
  314. package/src/forge/_generated/clientManifest.ts +0 -39
  315. package/src/forge/_generated/clientTypes.ts +0 -78
  316. package/src/forge/_generated/configRegistry.json +0 -2
  317. package/src/forge/_generated/configRegistry.ts +0 -4
  318. package/src/forge/_generated/dataGraph.json +0 -2
  319. package/src/forge/_generated/dataGraph.ts +0 -8
  320. package/src/forge/_generated/db.json +0 -2
  321. package/src/forge/_generated/db.ts +0 -2
  322. package/src/forge/_generated/dbSecurityManifest.json +0 -2
  323. package/src/forge/_generated/dbSecurityManifest.ts +0 -15
  324. package/src/forge/_generated/dbSessionContext.json +0 -2
  325. package/src/forge/_generated/dbSessionContext.ts +0 -39
  326. package/src/forge/_generated/deployManifest.json +0 -2
  327. package/src/forge/_generated/deployManifest.ts +0 -14
  328. package/src/forge/_generated/devManifest.json +0 -2
  329. package/src/forge/_generated/devManifest.ts +0 -47
  330. package/src/forge/_generated/envSchema.json +0 -2
  331. package/src/forge/_generated/envSchema.ts +0 -59
  332. package/src/forge/_generated/frontendGraph.json +0 -2
  333. package/src/forge/_generated/frontendGraph.ts +0 -27
  334. package/src/forge/_generated/importGuards.json +0 -2
  335. package/src/forge/_generated/importGuards.ts +0 -686
  336. package/src/forge/_generated/index.ts +0 -67
  337. package/src/forge/_generated/liveProductionManifest.json +0 -2
  338. package/src/forge/_generated/liveProductionManifest.ts +0 -23
  339. package/src/forge/_generated/liveProtocol.json +0 -2
  340. package/src/forge/_generated/liveProtocol.ts +0 -21
  341. package/src/forge/_generated/liveQueryRegistry.json +0 -2
  342. package/src/forge/_generated/liveQueryRegistry.ts +0 -9
  343. package/src/forge/_generated/liveTransportConfig.json +0 -2
  344. package/src/forge/_generated/liveTransportConfig.ts +0 -19
  345. package/src/forge/_generated/makeRegistry.json +0 -2
  346. package/src/forge/_generated/makeRegistry.ts +0 -163
  347. package/src/forge/_generated/makeTemplates.json +0 -2
  348. package/src/forge/_generated/makeTemplates.ts +0 -61
  349. package/src/forge/_generated/mockMap.json +0 -2
  350. package/src/forge/_generated/mockMap.ts +0 -7
  351. package/src/forge/_generated/operationPlaybooks.md +0 -147
  352. package/src/forge/_generated/packageGraph.json +0 -2
  353. package/src/forge/_generated/packageGraph.ts +0 -245249
  354. package/src/forge/_generated/packageUpgradeRegistry.json +0 -2
  355. package/src/forge/_generated/packageUpgradeRegistry.ts +0 -15
  356. package/src/forge/_generated/permissionMatrix.json +0 -2
  357. package/src/forge/_generated/permissionMatrix.ts +0 -7
  358. package/src/forge/_generated/policyRegistry.json +0 -2
  359. package/src/forge/_generated/policyRegistry.ts +0 -11
  360. package/src/forge/_generated/queryRegistry.json +0 -2
  361. package/src/forge/_generated/queryRegistry.ts +0 -9
  362. package/src/forge/_generated/react.d.ts +0 -22
  363. package/src/forge/_generated/react.ts +0 -29
  364. package/src/forge/_generated/reactManifest.json +0 -2
  365. package/src/forge/_generated/reactManifest.ts +0 -19
  366. package/src/forge/_generated/rlsPolicies.json +0 -2
  367. package/src/forge/_generated/rlsPolicies.sql +0 -34
  368. package/src/forge/_generated/rlsPolicies.ts +0 -6
  369. package/src/forge/_generated/runtimeGraph.json +0 -2
  370. package/src/forge/_generated/runtimeGraph.ts +0 -8
  371. package/src/forge/_generated/runtimeMatrix.json +0 -2
  372. package/src/forge/_generated/runtimeMatrix.ts +0 -327385
  373. package/src/forge/_generated/runtimeRegistry.ts +0 -2
  374. package/src/forge/_generated/runtimeRules.md +0 -79
  375. package/src/forge/_generated/secretRegistry.json +0 -2
  376. package/src/forge/_generated/secretRegistry.ts +0 -50
  377. package/src/forge/_generated/secretsContext.ts +0 -11
  378. package/src/forge/_generated/serverApi.ts +0 -10
  379. package/src/forge/_generated/sourceMapManifest.json +0 -2
  380. package/src/forge/_generated/sourceMapManifest.ts +0 -7
  381. package/src/forge/_generated/sqlPlan.json +0 -2
  382. package/src/forge/_generated/sqlPlan.ts +0 -88
  383. package/src/forge/_generated/subscriptionManifest.json +0 -2
  384. package/src/forge/_generated/subscriptionManifest.ts +0 -7
  385. package/src/forge/_generated/symbolicationManifest.json +0 -2
  386. package/src/forge/_generated/symbolicationManifest.ts +0 -17
  387. package/src/forge/_generated/telemetryRegistry.json +0 -2
  388. package/src/forge/_generated/telemetryRegistry.ts +0 -9
  389. package/src/forge/_generated/telemetrySinks.json +0 -2
  390. package/src/forge/_generated/telemetrySinks.ts +0 -11
  391. package/src/forge/_generated/tenantScope.json +0 -2
  392. package/src/forge/_generated/tenantScope.ts +0 -8
  393. package/src/forge/_generated/testGraph.json +0 -2
  394. package/src/forge/_generated/testGraph.ts +0 -3108
  395. package/src/forge/_generated/testPlanRegistry.json +0 -2
  396. package/src/forge/_generated/testPlanRegistry.ts +0 -33
  397. package/src/forge/_generated/uiRoutes.json +0 -2
  398. package/src/forge/_generated/uiRoutes.ts +0 -16
  399. package/src/forge/_generated/uiScenarios.json +0 -2
  400. package/src/forge/_generated/uiScenarios.ts +0 -30
  401. package/src/forge/_generated/uiTestManifest.json +0 -2
  402. package/src/forge/_generated/uiTestManifest.ts +0 -27
  403. package/src/forge/_generated/workflowRegistry.json +0 -2
  404. package/src/forge/_generated/workflowRegistry.ts +0 -9
  405. package/src/forge/_generated/workflowSubscriptions.json +0 -2
  406. package/src/forge/_generated/workflowSubscriptions.ts +0 -10
@@ -0,0 +1,1245 @@
1
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, watch, writeFileSync } from "node:fs";
2
+ import { dirname, isAbsolute, join, resolve } from "node:path";
3
+ import { createDiagnostic } from "../compiler/diagnostics/create.ts";
4
+ import { createDeltaId } from "../delta/ids.ts";
5
+ import { DeltaStore, DeltaStoreBusyError, describeDeltaStoreBusy, summarizeDeltaStoreBusy } from "../delta/store.ts";
6
+ import { extractAgentEventBindings, normalizeAgentEvent, summarizeAgentEvent } from "./normalize.ts";
7
+ import { redactAgentPayload } from "./redaction.ts";
8
+ import { buildAgentMemoryContext } from "./context-pack.ts";
9
+ import { claudeCodeInstallFiles, claudeCodeInstallResult } from "./sources/claude-code.ts";
10
+ import { codexInstallFiles, codexInstallResult, privacyDefaults } from "./sources/codex.ts";
11
+ import { cursorInstallFiles, cursorInstallResult } from "./sources/cursor.ts";
12
+ import type {
13
+ AgentEventEnvelope,
14
+ AgentIngestResult,
15
+ AgentIngestWatchResult,
16
+ AgentInstallResult,
17
+ AgentMemoryUnavailableResult,
18
+ AgentMemoryContextPack,
19
+ AgentMemoryEventRecord,
20
+ AgentMemorySourceName,
21
+ } from "./types.ts";
22
+
23
+ export interface AgentMemoryCommandOptions {
24
+ subcommand: "install" | "ingest" | "context" | "memory";
25
+ workspaceRoot: string;
26
+ json: boolean;
27
+ target?: string;
28
+ source?: string;
29
+ eventName?: string;
30
+ input?: unknown;
31
+ entry?: string;
32
+ current?: boolean;
33
+ dryRun?: boolean;
34
+ force?: boolean;
35
+ limit?: number;
36
+ watch?: boolean;
37
+ file?: string;
38
+ pollIntervalMs?: number;
39
+ }
40
+
41
+ export type AgentMemoryCommandResult =
42
+ | AgentInstallResult
43
+ | AgentIngestResult
44
+ | AgentIngestWatchResult
45
+ | AgentMemoryContextPack
46
+ | { ok: true; events: AgentMemoryEventRecord[]; exitCode: 0 }
47
+ | AgentMemoryUnavailableResult;
48
+
49
+ export interface AgentMemoryQueueInspectionResult {
50
+ exists: boolean;
51
+ source: string;
52
+ file: string;
53
+ events: number;
54
+ nativeSignals: number;
55
+ canarySignals: number;
56
+ usefulSignals: number;
57
+ ignoredOutOfWorkspaceEvents: number;
58
+ bytesRead: number;
59
+ pendingBytes: number;
60
+ inspectedBytes?: number;
61
+ skippedBytes?: number;
62
+ truncated?: boolean;
63
+ checkpointFile: string;
64
+ errors: string[];
65
+ latestEventAt?: string;
66
+ }
67
+
68
+ function memoryUnavailable(error: unknown, workspaceRoot: string): AgentMemoryUnavailableResult {
69
+ const message = error instanceof Error ? error.message : "agent memory store is unavailable";
70
+ const busy = error instanceof DeltaStoreBusyError;
71
+ const busyInfo = busy ? describeDeltaStoreBusy(error, workspaceRoot) : undefined;
72
+ const busySummary = busyInfo ? summarizeDeltaStoreBusy(busyInfo) : undefined;
73
+ return {
74
+ ok: false,
75
+ error: busySummary ? `${message} (${busySummary})` : message,
76
+ events: [],
77
+ ...(busyInfo ? { busy: busyInfo } : {}),
78
+ diagnostics: [
79
+ createDiagnostic({
80
+ severity: "error",
81
+ code: busy ? "FORGE_DELTA_BUSY" : "FORGE_AGENT_MEMORY_UNAVAILABLE",
82
+ message: busy
83
+ ? `Forge Delta local store is busy: ${message}${busySummary ? ` (${busySummary})` : ""}`
84
+ : message,
85
+ ...(busy
86
+ ? {
87
+ fixHint: busyInfo?.processAlive
88
+ ? `Wait for pid ${busyInfo.pid ?? "shown in the lock file"} to finish, then retry the agent memory command.`
89
+ : `If no Forge/agent process is still running, inspect ${busyInfo?.relativeLockPath ?? ".forge/delta/delta.lock"} and retry.`,
90
+ suggestedCommands: [
91
+ "forge delta status --json",
92
+ "forge agent timeline --json",
93
+ "forge agent hooks status --target codex --json",
94
+ ],
95
+ }
96
+ : {}),
97
+ }),
98
+ ],
99
+ nextActions: [
100
+ "forge delta status --json",
101
+ ...(busyInfo?.processAlive ? [] : ["forge delta repair --dry-run --json"]),
102
+ "forge agent timeline --json",
103
+ "forge agent hooks status --target codex --json",
104
+ ],
105
+ exitCode: 1,
106
+ };
107
+ }
108
+
109
+ async function openMemoryStore(
110
+ workspaceRoot: string,
111
+ access: "read" | "write" = "write",
112
+ ): Promise<DeltaStore | AgentMemoryUnavailableResult> {
113
+ try {
114
+ return await DeltaStore.open(workspaceRoot, { access });
115
+ } catch (error) {
116
+ return memoryUnavailable(error, workspaceRoot);
117
+ }
118
+ }
119
+
120
+ function isMemoryUnavailable(result: DeltaStore | AgentMemoryUnavailableResult): result is AgentMemoryUnavailableResult {
121
+ return "ok" in result && result.ok === false;
122
+ }
123
+
124
+ function isExternalPgliteRead(result: AgentMemoryUnavailableResult): boolean {
125
+ return Boolean(
126
+ result.busy?.relativeLockPath.endsWith("postmaster.pid") &&
127
+ result.busy.processAlive === false,
128
+ );
129
+ }
130
+
131
+ function fallbackMemoryPath(workspaceRoot: string): string {
132
+ return join(workspaceRoot, ".forge", "agent", "events.ndjson");
133
+ }
134
+
135
+ function hasExternalPglitePostmaster(workspaceRoot: string): boolean {
136
+ return existsSync(join(workspaceRoot, ".forge", "delta", "delta.db", "postmaster.pid")) &&
137
+ !existsSync(join(workspaceRoot, ".forge", "delta", "delta.lock"));
138
+ }
139
+
140
+ function shouldUseFallbackMemory(
141
+ result: AgentMemoryUnavailableResult,
142
+ workspaceRoot: string,
143
+ ): boolean {
144
+ return isExternalPgliteRead(result) || (!result.busy && hasExternalPglitePostmaster(workspaceRoot));
145
+ }
146
+
147
+ function eventRecordFromEnvelope(
148
+ envelope: AgentEventEnvelope,
149
+ summary: string | undefined,
150
+ bindings: Record<string, unknown>,
151
+ ): AgentMemoryEventRecord {
152
+ const capturedAt = envelope.event.timestamp || new Date().toISOString();
153
+ return {
154
+ id: createDeltaId("amem"),
155
+ externalEventId: createDeltaId("aevt"),
156
+ sourceName: String(envelope.source.agent),
157
+ integrationKind: String(envelope.source.integration),
158
+ trustLevel: envelope.capture.trustLevel,
159
+ externalSessionId: envelope.session.externalSessionId,
160
+ externalTurnId: envelope.session.turnId,
161
+ eventKind: envelope.event.kind,
162
+ normalizedKind: envelope.event.kind,
163
+ summary,
164
+ confidence: envelope.capture.confidence,
165
+ capturedAt,
166
+ data: { envelope, bindings },
167
+ };
168
+ }
169
+
170
+ function appendFallbackAgentMemoryEvent(
171
+ workspaceRoot: string,
172
+ envelope: AgentEventEnvelope,
173
+ summary: string | undefined,
174
+ bindings: Record<string, unknown>,
175
+ ): AgentMemoryEventRecord {
176
+ const file = fallbackMemoryPath(workspaceRoot);
177
+ mkdirSync(dirname(file), { recursive: true });
178
+ const event = eventRecordFromEnvelope(envelope, summary, bindings);
179
+ appendFileSync(file, `${JSON.stringify(event)}\n`, "utf8");
180
+ return event;
181
+ }
182
+
183
+ function readFallbackAgentMemoryEvents(
184
+ workspaceRoot: string,
185
+ target: string | undefined,
186
+ limit: number | undefined,
187
+ ): AgentMemoryEventRecord[] {
188
+ const file = fallbackMemoryPath(workspaceRoot);
189
+ if (!existsSync(file)) {
190
+ return [];
191
+ }
192
+ const events: AgentMemoryEventRecord[] = [];
193
+ for (const line of readFileSync(file, "utf8").split(/\r?\n/)) {
194
+ if (!line.trim()) {
195
+ continue;
196
+ }
197
+ try {
198
+ const parsed = JSON.parse(line) as unknown;
199
+ if (isAgentMemoryEventRecord(parsed) && agentMemoryEventMatchesTarget(parsed, target)) {
200
+ events.push(parsed);
201
+ }
202
+ } catch {
203
+ // Keep fallback recovery best effort; malformed lines should not break hooks.
204
+ }
205
+ }
206
+ return limit ? events.slice(-Math.max(1, Math.min(limit, 200))) : events;
207
+ }
208
+
209
+ function isAgentMemoryEventRecord(value: unknown): value is AgentMemoryEventRecord {
210
+ return Boolean(
211
+ value &&
212
+ typeof value === "object" &&
213
+ !Array.isArray(value) &&
214
+ typeof (value as { id?: unknown }).id === "string" &&
215
+ typeof (value as { sourceName?: unknown }).sourceName === "string" &&
216
+ typeof (value as { eventKind?: unknown }).eventKind === "string" &&
217
+ typeof (value as { capturedAt?: unknown }).capturedAt === "string",
218
+ );
219
+ }
220
+
221
+ function agentMemoryEventMatchesTarget(event: AgentMemoryEventRecord, target: string | undefined): boolean {
222
+ if (!target) {
223
+ return true;
224
+ }
225
+ return event.sourceName === target ||
226
+ event.summary?.includes(target) === true ||
227
+ JSON.stringify(event.data).includes(target);
228
+ }
229
+
230
+ function mergeAgentMemoryEvents(
231
+ primary: AgentMemoryEventRecord[],
232
+ fallback: AgentMemoryEventRecord[],
233
+ limit: number | undefined,
234
+ ): AgentMemoryEventRecord[] {
235
+ const seen = new Set<string>();
236
+ const merged = [...primary, ...fallback]
237
+ .filter((event) => {
238
+ if (seen.has(event.id)) {
239
+ return false;
240
+ }
241
+ seen.add(event.id);
242
+ return true;
243
+ })
244
+ .sort((left, right) => {
245
+ const byTime = left.capturedAt.localeCompare(right.capturedAt);
246
+ return byTime === 0 ? left.id.localeCompare(right.id) : byTime;
247
+ });
248
+ return limit ? merged.slice(-Math.max(1, Math.min(limit, 200))) : merged;
249
+ }
250
+
251
+ function isMissingAgentMemorySchema(error: unknown): boolean {
252
+ const message = error instanceof Error ? error.message : String(error);
253
+ return /agent_memory_events/i.test(message) && /does not exist|no such table|missing/i.test(message);
254
+ }
255
+
256
+ async function listAgentMemoryEventsWithSchemaRepair(
257
+ workspaceRoot: string,
258
+ target: string | undefined,
259
+ limit: number | undefined,
260
+ ): Promise<AgentMemoryEventRecord[] | AgentMemoryUnavailableResult> {
261
+ const fallbackEvents = readFallbackAgentMemoryEvents(workspaceRoot, target, limit);
262
+ let store = await openMemoryStore(workspaceRoot, "read");
263
+ if (isMemoryUnavailable(store)) {
264
+ if (shouldUseFallbackMemory(store, workspaceRoot)) {
265
+ return fallbackEvents;
266
+ }
267
+ return store;
268
+ }
269
+ try {
270
+ return mergeAgentMemoryEvents(await store.listAgentMemoryEvents({ target, limit }), fallbackEvents, limit);
271
+ } catch (error) {
272
+ await store.close().catch(() => undefined);
273
+ if (!isMissingAgentMemorySchema(error)) {
274
+ return memoryUnavailable(error, workspaceRoot);
275
+ }
276
+ const repairStore = await openMemoryStore(workspaceRoot, "write");
277
+ if (isMemoryUnavailable(repairStore)) {
278
+ return repairStore;
279
+ }
280
+ try {
281
+ await repairStore.init();
282
+ return await repairStore.listAgentMemoryEvents({ target, limit });
283
+ } catch (repairError) {
284
+ return memoryUnavailable(repairError, workspaceRoot);
285
+ } finally {
286
+ await repairStore.close();
287
+ }
288
+ } finally {
289
+ await store.close().catch(() => undefined);
290
+ }
291
+ }
292
+
293
+ export async function runAgentMemoryCommand(options: AgentMemoryCommandOptions): Promise<AgentMemoryCommandResult> {
294
+ if (options.subcommand === "install") {
295
+ return installAgentMemory(options);
296
+ }
297
+ if (options.subcommand === "ingest") {
298
+ if (options.watch) {
299
+ return watchAgentMemoryIngest(options);
300
+ }
301
+ if (options.file) {
302
+ return ingestAgentMemoryQueueFile(options);
303
+ }
304
+ return ingestAgentMemory(options);
305
+ }
306
+ if (options.subcommand === "context") {
307
+ try {
308
+ return await buildAgentMemoryContext({
309
+ workspaceRoot: options.workspaceRoot,
310
+ entry: options.entry,
311
+ limit: options.limit,
312
+ });
313
+ } catch (error) {
314
+ return memoryUnavailable(error, options.workspaceRoot);
315
+ }
316
+ }
317
+ const events = await listAgentMemoryEventsWithSchemaRepair(options.workspaceRoot, options.entry, options.limit);
318
+ if (!Array.isArray(events)) {
319
+ return events;
320
+ }
321
+ return {
322
+ ok: true,
323
+ events,
324
+ exitCode: 0,
325
+ };
326
+ }
327
+
328
+ export async function ingestEnvelope(workspaceRoot: string, envelope: AgentEventEnvelope): Promise<AgentIngestResult> {
329
+ const bindings = extractAgentEventBindings(envelope);
330
+ const summary = summarizeAgentEvent(envelope);
331
+ const store = await openMemoryStore(workspaceRoot, "write");
332
+ if (isMemoryUnavailable(store)) {
333
+ if (shouldUseFallbackMemory(store, workspaceRoot)) {
334
+ const event = appendFallbackAgentMemoryEvent(workspaceRoot, envelope, summary, bindings);
335
+ return {
336
+ ok: true,
337
+ event,
338
+ envelope,
339
+ exitCode: 0,
340
+ fallback: {
341
+ kind: "agent-events-ndjson",
342
+ path: ".forge/agent/events.ndjson",
343
+ reason: "pglite-active",
344
+ },
345
+ };
346
+ }
347
+ return {
348
+ ...store,
349
+ envelope,
350
+ };
351
+ }
352
+ try {
353
+ const event = await store.recordAgentMemoryEvent({ envelope, summary, bindings });
354
+ return { ok: true, event, envelope, exitCode: 0 };
355
+ } finally {
356
+ await store.close();
357
+ }
358
+ }
359
+
360
+ async function ingestAgentMemory(options: AgentMemoryCommandOptions): Promise<AgentIngestResult> {
361
+ const source = options.source ?? options.target ?? "generic";
362
+ const raw = normalizeRawInput(options.input ?? await readStdinJson({ timeoutMs: 2000 }));
363
+ if (!raw) {
364
+ return { ok: false, exitCode: 1, error: "agent ingest requires JSON input on stdin or --input" };
365
+ }
366
+ const envelope = normalizeAgentEvent({
367
+ workspaceRoot: options.workspaceRoot,
368
+ source,
369
+ eventName: options.eventName,
370
+ raw,
371
+ integration: source === "cursor" ? "mcp" : "native-hook",
372
+ });
373
+ return ingestEnvelope(options.workspaceRoot, envelope);
374
+ }
375
+
376
+ async function ingestAgentMemoryQueueFile(options: AgentMemoryCommandOptions): Promise<AgentIngestWatchResult> {
377
+ const source = options.source ?? options.target ?? "generic";
378
+ const watchFile = isAbsolute(options.file ?? "")
379
+ ? options.file as string
380
+ : resolve(options.workspaceRoot, options.file ?? "");
381
+ if (!options.file) {
382
+ return {
383
+ ok: false,
384
+ watch: false,
385
+ source,
386
+ eventsIngested: 0,
387
+ errors: ["agent ingest --file requires --file <events.jsonl|events.ndjson>"],
388
+ nextActions: [`forge agent ingest ${source} --file .forge/agent/events.ndjson --json`],
389
+ exitCode: 1,
390
+ };
391
+ }
392
+ if (!existsSync(watchFile)) {
393
+ return {
394
+ ok: false,
395
+ watch: false,
396
+ source,
397
+ file: options.file,
398
+ eventsIngested: 0,
399
+ errors: [`queue file does not exist: ${options.file}`],
400
+ nextActions: [`forge agent hooks status --target ${source} --json`],
401
+ exitCode: 1,
402
+ };
403
+ }
404
+ const drained = await drainAgentMemoryQueueFile({
405
+ workspaceRoot: options.workspaceRoot,
406
+ watchFile,
407
+ source,
408
+ eventName: options.eventName,
409
+ });
410
+ return {
411
+ ok: drained.errors.length === 0,
412
+ watch: false,
413
+ source,
414
+ file: options.file,
415
+ eventsIngested: drained.eventsIngested,
416
+ errors: drained.errors,
417
+ bytesRead: drained.bytesRead,
418
+ pendingBytes: drained.pendingBytes,
419
+ checkpointFile: drained.checkpointFile,
420
+ compacted: drained.compacted,
421
+ historyFile: drained.historyFile,
422
+ nextActions: [
423
+ `forge agent memory --entry ${source} --json`,
424
+ `forge agent hooks status --target ${source} --json`,
425
+ ],
426
+ exitCode: drained.errors.length === 0 ? 0 : 1,
427
+ };
428
+ }
429
+
430
+ function queueCheckpointPath(watchFile: string): string {
431
+ return `${watchFile}.checkpoint.json`;
432
+ }
433
+
434
+ function queueHistoryPath(watchFile: string): string {
435
+ return `${watchFile}.history`;
436
+ }
437
+
438
+ function readQueueCheckpoint(watchFile: string, fileSize: number): number {
439
+ const checkpointFile = queueCheckpointPath(watchFile);
440
+ if (!existsSync(checkpointFile)) {
441
+ return 0;
442
+ }
443
+ try {
444
+ const parsed = JSON.parse(readFileSync(checkpointFile, "utf8")) as unknown;
445
+ const offset = parsed && typeof parsed === "object" && !Array.isArray(parsed)
446
+ ? (parsed as { offset?: unknown }).offset
447
+ : undefined;
448
+ if (typeof offset !== "number" || !Number.isFinite(offset) || offset < 0) {
449
+ return 0;
450
+ }
451
+ return offset > fileSize ? 0 : Math.floor(offset);
452
+ } catch {
453
+ return 0;
454
+ }
455
+ }
456
+
457
+ function writeQueueCheckpoint(watchFile: string, offset: number): void {
458
+ const checkpointFile = queueCheckpointPath(watchFile);
459
+ mkdirSync(dirname(checkpointFile), { recursive: true });
460
+ writeFileSync(
461
+ checkpointFile,
462
+ `${JSON.stringify({
463
+ schema: "forge.agent-hook-queue-checkpoint.v1",
464
+ file: watchFile,
465
+ offset,
466
+ updatedAt: new Date().toISOString(),
467
+ }, null, 2)}\n`,
468
+ "utf8",
469
+ );
470
+ }
471
+
472
+ const DEFAULT_QUEUE_COMPACT_AFTER_BYTES = 256 * 1024;
473
+ const DEFAULT_QUEUE_HISTORY_MAX_BYTES = 1024 * 1024;
474
+
475
+ function trimBufferStart(buffer: Buffer, maxBytes: number): Buffer {
476
+ if (buffer.length <= maxBytes) {
477
+ return buffer;
478
+ }
479
+ return buffer.subarray(buffer.length - maxBytes);
480
+ }
481
+
482
+ function compactAgentMemoryQueueFile(options: {
483
+ watchFile: string;
484
+ originalBuffer: Buffer;
485
+ consumedOffset: number;
486
+ compactAfterBytes: number;
487
+ historyMaxBytes: number;
488
+ }): { compacted: boolean; historyFile: string } {
489
+ const historyFile = queueHistoryPath(options.watchFile);
490
+ if (options.consumedOffset < options.compactAfterBytes) {
491
+ return { compacted: false, historyFile };
492
+ }
493
+ const currentBuffer = readFileSync(options.watchFile);
494
+ const originalConsumed = options.originalBuffer.subarray(0, options.consumedOffset);
495
+ const currentPrefix = currentBuffer.subarray(0, options.consumedOffset);
496
+ if (!currentPrefix.equals(originalConsumed)) {
497
+ return { compacted: false, historyFile };
498
+ }
499
+ mkdirSync(dirname(historyFile), { recursive: true });
500
+ const existingHistory = existsSync(historyFile) ? readFileSync(historyFile) : Buffer.alloc(0);
501
+ const redactedConsumedHistory = redactedQueueHistoryBuffer(originalConsumed);
502
+ writeFileSync(
503
+ historyFile,
504
+ trimBufferStart(Buffer.concat([existingHistory, redactedConsumedHistory]), options.historyMaxBytes),
505
+ );
506
+ writeFileSync(options.watchFile, currentBuffer.subarray(options.consumedOffset));
507
+ writeQueueCheckpoint(options.watchFile, 0);
508
+ return { compacted: true, historyFile };
509
+ }
510
+
511
+ function redactedQueueHistoryBuffer(consumedBuffer: Buffer): Buffer {
512
+ const { complete } = splitCompleteJsonLines(consumedBuffer);
513
+ const lines: string[] = [];
514
+ for (const line of complete) {
515
+ if (!line.raw.trim()) {
516
+ continue;
517
+ }
518
+ const parsed = normalizeRawInput(line.raw);
519
+ if (!parsed) {
520
+ lines.push(JSON.stringify({
521
+ forgeHookQueueV1: true,
522
+ historyRedacted: true,
523
+ rawStored: false,
524
+ payloadRedacted: true,
525
+ payload: { _parseError: true },
526
+ }));
527
+ continue;
528
+ }
529
+ lines.push(JSON.stringify(redactedQueueHistoryEntry(parsed)));
530
+ }
531
+ return Buffer.from(lines.length > 0 ? `${lines.join("\n")}\n` : "", "utf8");
532
+ }
533
+
534
+ function redactedQueueHistoryEntry(parsed: Record<string, unknown>): Record<string, unknown> {
535
+ if (parsed.forgeHookQueueV1 !== true) {
536
+ return {
537
+ historyRedacted: true,
538
+ rawStored: false,
539
+ payloadRedacted: true,
540
+ payload: redactAgentPayload(parsed).value,
541
+ };
542
+ }
543
+ const queuedPayload = objectField(parsed, "payload") ?? objectField(parsed, "raw") ?? {};
544
+ return {
545
+ forgeHookQueueV1: true,
546
+ source: typeof parsed.source === "string" ? parsed.source : "codex",
547
+ eventName: typeof parsed.eventName === "string" ? parsed.eventName : undefined,
548
+ workspaceRoot: typeof parsed.workspaceRoot === "string" ? parsed.workspaceRoot : undefined,
549
+ enqueuedAt: typeof parsed.enqueuedAt === "string" ? parsed.enqueuedAt : undefined,
550
+ historyRedacted: true,
551
+ rawStored: false,
552
+ payloadRedacted: true,
553
+ payload: parsed.payloadRedacted === true ? queuedPayload : redactAgentPayload(queuedPayload).value,
554
+ };
555
+ }
556
+
557
+ function splitCompleteJsonLines(buffer: Buffer): {
558
+ complete: Array<{ raw: string; endOffset: number }>;
559
+ completeBytes: number;
560
+ pendingBytes: number;
561
+ } {
562
+ const lastNewline = buffer.lastIndexOf(10);
563
+ if (lastNewline < 0) {
564
+ return { complete: [], completeBytes: 0, pendingBytes: buffer.length };
565
+ }
566
+ const completeBytes = lastNewline + 1;
567
+ const text = buffer.subarray(0, completeBytes).toString("utf8");
568
+ const lines: Array<{ raw: string; endOffset: number }> = [];
569
+ let offset = 0;
570
+ for (const rawLine of text.split(/(?<=\n)/)) {
571
+ if (!rawLine) {
572
+ continue;
573
+ }
574
+ const byteLength = Buffer.byteLength(rawLine);
575
+ offset += byteLength;
576
+ const normalized = rawLine.replace(/\r?\n$/, "");
577
+ lines.push({ raw: normalized, endOffset: offset });
578
+ }
579
+ return { complete: lines, completeBytes, pendingBytes: buffer.length - completeBytes };
580
+ }
581
+
582
+ const DEFAULT_QUEUE_INSPECT_MAX_BYTES = 1024 * 1024;
583
+
584
+ function inspectionBufferFromCheckpoint(fileBuffer: Buffer, checkpointOffset: number, maxBytes: number): {
585
+ buffer: Buffer;
586
+ offset: number;
587
+ truncated: boolean;
588
+ skippedBytes: number;
589
+ } {
590
+ const availableBytes = Math.max(0, fileBuffer.length - checkpointOffset);
591
+ if (availableBytes <= maxBytes) {
592
+ return {
593
+ buffer: fileBuffer.subarray(checkpointOffset),
594
+ offset: checkpointOffset,
595
+ truncated: false,
596
+ skippedBytes: 0,
597
+ };
598
+ }
599
+ let offset = fileBuffer.length - maxBytes;
600
+ const firstNewline = fileBuffer.subarray(offset).indexOf(10);
601
+ if (firstNewline >= 0) {
602
+ offset += firstNewline + 1;
603
+ }
604
+ return {
605
+ buffer: fileBuffer.subarray(offset),
606
+ offset,
607
+ truncated: true,
608
+ skippedBytes: Math.max(0, offset - checkpointOffset),
609
+ };
610
+ }
611
+
612
+ function shouldSkipQueuedHookEnvelope(
613
+ envelope: AgentEventEnvelope,
614
+ options: { source: string; workspaceRoot: string },
615
+ ): boolean {
616
+ return (
617
+ envelope.source.agent !== options.source ||
618
+ !workspaceRootsMatch(envelope.workspace.root, options.workspaceRoot) ||
619
+ envelope.payload.forgeHookProbe === true ||
620
+ envelope.payload._parseError === true ||
621
+ envelope.payload._invalidPayload === true
622
+ );
623
+ }
624
+
625
+ export async function drainAgentMemoryQueueFile(options: {
626
+ workspaceRoot: string;
627
+ watchFile: string;
628
+ source: string;
629
+ eventName?: string;
630
+ startOffset?: number;
631
+ compactAfterBytes?: number;
632
+ historyMaxBytes?: number;
633
+ }): Promise<{
634
+ eventsIngested: number;
635
+ errors: string[];
636
+ bytesRead: number;
637
+ pendingBytes: number;
638
+ checkpointFile: string;
639
+ compacted: boolean;
640
+ historyFile: string;
641
+ }> {
642
+ const historyFile = queueHistoryPath(options.watchFile);
643
+ if (!existsSync(options.watchFile)) {
644
+ return {
645
+ eventsIngested: 0,
646
+ errors: [],
647
+ bytesRead: 0,
648
+ pendingBytes: 0,
649
+ checkpointFile: queueCheckpointPath(options.watchFile),
650
+ compacted: false,
651
+ historyFile,
652
+ };
653
+ }
654
+ const fileBuffer = readFileSync(options.watchFile);
655
+ let bytesRead = options.startOffset ?? readQueueCheckpoint(options.watchFile, fileBuffer.length);
656
+ if (bytesRead > fileBuffer.length) {
657
+ bytesRead = 0;
658
+ }
659
+ const { complete, pendingBytes } = splitCompleteJsonLines(fileBuffer.subarray(bytesRead));
660
+ let eventsIngested = 0;
661
+ const errors: string[] = [];
662
+ let consumedOffset = bytesRead;
663
+
664
+ for (const line of complete) {
665
+ if (!line.raw.trim()) {
666
+ consumedOffset = bytesRead + line.endOffset;
667
+ writeQueueCheckpoint(options.watchFile, consumedOffset);
668
+ continue;
669
+ }
670
+ const parsed = normalizeRawInput(line.raw);
671
+ if (!parsed) {
672
+ errors.push(`could not parse queued hook line at byte ${bytesRead + line.endOffset}`);
673
+ break;
674
+ }
675
+ const queued = parseQueuedHookLine(parsed);
676
+ const payload = queued?.payload ?? parsed;
677
+ const ingestRoot = queued?.workspaceRoot ?? options.workspaceRoot;
678
+ const ingestSource = queued?.source ?? options.source;
679
+ const envelope = normalizeAgentEvent({
680
+ workspaceRoot: ingestRoot,
681
+ source: ingestSource,
682
+ eventName: queued?.eventName ?? options.eventName,
683
+ raw: payload,
684
+ integration: ingestSource === "cursor" ? "mcp" : "native-hook",
685
+ });
686
+ if (shouldSkipQueuedHookEnvelope(envelope, { source: options.source, workspaceRoot: options.workspaceRoot })) {
687
+ consumedOffset = bytesRead + line.endOffset;
688
+ writeQueueCheckpoint(options.watchFile, consumedOffset);
689
+ continue;
690
+ }
691
+ const result = await ingestEnvelope(ingestRoot, envelope);
692
+ if (result.ok) {
693
+ eventsIngested += 1;
694
+ consumedOffset = bytesRead + line.endOffset;
695
+ writeQueueCheckpoint(options.watchFile, consumedOffset);
696
+ } else {
697
+ errors.push(result.error ?? "agent memory ingest failed");
698
+ break;
699
+ }
700
+ }
701
+
702
+ const retention = errors.length === 0 && consumedOffset > 0
703
+ ? compactAgentMemoryQueueFile({
704
+ watchFile: options.watchFile,
705
+ originalBuffer: fileBuffer,
706
+ consumedOffset,
707
+ compactAfterBytes: options.compactAfterBytes ?? DEFAULT_QUEUE_COMPACT_AFTER_BYTES,
708
+ historyMaxBytes: options.historyMaxBytes ?? DEFAULT_QUEUE_HISTORY_MAX_BYTES,
709
+ })
710
+ : { compacted: false, historyFile };
711
+ const bytesAfterRetention = retention.compacted ? 0 : consumedOffset;
712
+
713
+ return {
714
+ eventsIngested,
715
+ errors,
716
+ bytesRead: bytesAfterRetention,
717
+ pendingBytes,
718
+ checkpointFile: queueCheckpointPath(options.watchFile),
719
+ compacted: retention.compacted,
720
+ historyFile: retention.historyFile,
721
+ };
722
+ }
723
+
724
+ function queuedEventHasUsefulSignal(envelope: AgentEventEnvelope): boolean {
725
+ const bindings = extractAgentEventBindings(envelope);
726
+ const files = bindings.files;
727
+ const entries = bindings.entries;
728
+ const proofs = bindings.proofs;
729
+ return (
730
+ typeof bindings.toolName === "string" ||
731
+ typeof bindings.command === "string" ||
732
+ typeof bindings.status === "string" ||
733
+ (Array.isArray(files) && files.length > 0) ||
734
+ (Array.isArray(entries) && entries.length > 0) ||
735
+ (Array.isArray(proofs) && proofs.length > 0)
736
+ );
737
+ }
738
+
739
+ function queuedEventTimestamp(raw: Record<string, unknown>, envelope: AgentEventEnvelope): string | undefined {
740
+ const enqueuedAt = raw.enqueuedAt;
741
+ return typeof enqueuedAt === "string" && enqueuedAt.length > 0
742
+ ? enqueuedAt
743
+ : envelope.event.timestamp;
744
+ }
745
+
746
+ function workspaceRootsMatch(left: string | undefined, right: string): boolean {
747
+ if (!left) {
748
+ return true;
749
+ }
750
+ return resolve(left) === resolve(right);
751
+ }
752
+
753
+ export function inspectAgentMemoryQueueFile(options: {
754
+ workspaceRoot: string;
755
+ watchFile: string;
756
+ source: string;
757
+ eventName?: string;
758
+ }): AgentMemoryQueueInspectionResult {
759
+ const checkpointFile = queueCheckpointPath(options.watchFile);
760
+ const base = {
761
+ exists: existsSync(options.watchFile),
762
+ source: options.source,
763
+ file: options.watchFile,
764
+ events: 0,
765
+ nativeSignals: 0,
766
+ canarySignals: 0,
767
+ usefulSignals: 0,
768
+ ignoredOutOfWorkspaceEvents: 0,
769
+ bytesRead: 0,
770
+ pendingBytes: 0,
771
+ checkpointFile,
772
+ errors: [] as string[],
773
+ };
774
+ if (!base.exists) {
775
+ return base;
776
+ }
777
+ const fileBuffer = readFileSync(options.watchFile);
778
+ const bytesRead = readQueueCheckpoint(options.watchFile, fileBuffer.length);
779
+ const inspected = inspectionBufferFromCheckpoint(fileBuffer, bytesRead, DEFAULT_QUEUE_INSPECT_MAX_BYTES);
780
+ const { complete, pendingBytes } = splitCompleteJsonLines(inspected.buffer);
781
+ const result: AgentMemoryQueueInspectionResult = {
782
+ ...base,
783
+ bytesRead,
784
+ inspectedBytes: inspected.buffer.length,
785
+ skippedBytes: inspected.skippedBytes,
786
+ truncated: inspected.truncated,
787
+ pendingBytes,
788
+ };
789
+ for (const line of complete) {
790
+ if (!line.raw.trim()) {
791
+ continue;
792
+ }
793
+ const parsed = normalizeRawInput(line.raw);
794
+ if (!parsed) {
795
+ result.errors.push(`could not parse queued hook line at byte ${inspected.offset + line.endOffset}`);
796
+ continue;
797
+ }
798
+ const queued = parseQueuedHookLine(parsed);
799
+ const payload = queued?.payload ?? parsed;
800
+ const source = queued?.source ?? options.source;
801
+ const workspaceRoot = queued?.workspaceRoot ?? options.workspaceRoot;
802
+ const envelope = normalizeAgentEvent({
803
+ workspaceRoot,
804
+ source,
805
+ eventName: queued?.eventName ?? options.eventName,
806
+ raw: payload,
807
+ integration: source === "cursor" ? "mcp" : "native-hook",
808
+ });
809
+ if (source !== options.source) {
810
+ continue;
811
+ }
812
+ if (!workspaceRootsMatch(workspaceRoot, options.workspaceRoot)) {
813
+ result.ignoredOutOfWorkspaceEvents += 1;
814
+ continue;
815
+ }
816
+ const canary = envelope.payload.forgeHookCanary === "FORGE_HOOK_SMOKE_CANARY";
817
+ if (shouldSkipQueuedHookEnvelope(envelope, { source: options.source, workspaceRoot: options.workspaceRoot })) {
818
+ continue;
819
+ }
820
+ result.events += 1;
821
+ if (canary) {
822
+ result.canarySignals += 1;
823
+ } else if (envelope.source.integration === "native-hook" && envelope.capture.trustLevel === "direct-hook") {
824
+ result.nativeSignals += 1;
825
+ }
826
+ if (queuedEventHasUsefulSignal(envelope)) {
827
+ result.usefulSignals += 1;
828
+ }
829
+ const timestamp = queuedEventTimestamp(parsed, envelope);
830
+ if (timestamp && (!result.latestEventAt || timestamp > result.latestEventAt)) {
831
+ result.latestEventAt = timestamp;
832
+ }
833
+ }
834
+ return result;
835
+ }
836
+
837
+ async function watchAgentMemoryIngest(options: AgentMemoryCommandOptions): Promise<AgentIngestWatchResult> {
838
+ const source = options.source ?? options.target ?? "generic";
839
+ const file = options.file;
840
+ if (options.dryRun) {
841
+ return {
842
+ ok: true,
843
+ watch: true,
844
+ source,
845
+ ...(file ? { file } : {}),
846
+ dryRun: true,
847
+ eventsIngested: 0,
848
+ errors: [],
849
+ nextActions: [
850
+ `forge agent ingest ${source} --watch${file ? ` --file ${file}` : ""} --json`,
851
+ `forge agent hooks status --target ${source} --json`,
852
+ ],
853
+ exitCode: 0,
854
+ };
855
+ }
856
+ if (!file) {
857
+ return {
858
+ ok: false,
859
+ watch: true,
860
+ source,
861
+ eventsIngested: 0,
862
+ errors: ["agent ingest --watch requires --file <events.jsonl|events.ndjson>"],
863
+ nextActions: [`forge agent ingest ${source} --watch --file .forge/agent/events.ndjson --json`],
864
+ exitCode: 1,
865
+ };
866
+ }
867
+ const watchFile = isAbsolute(file) ? file : resolve(options.workspaceRoot, file);
868
+ if (!existsSync(watchFile)) {
869
+ return {
870
+ ok: false,
871
+ watch: true,
872
+ source,
873
+ file,
874
+ eventsIngested: 0,
875
+ errors: [`watch file does not exist: ${file}`],
876
+ nextActions: [`New-Item -ItemType File -Path ${file}`, `forge agent ingest ${source} --watch --file ${file} --json`],
877
+ exitCode: 1,
878
+ };
879
+ }
880
+
881
+ let eventsIngested = 0;
882
+ const errors: string[] = [];
883
+ let pendingIngest = Promise.resolve();
884
+ const ingestNewContent = async () => {
885
+ const result = await drainAgentMemoryQueueFile({
886
+ workspaceRoot: options.workspaceRoot,
887
+ watchFile,
888
+ source,
889
+ eventName: options.eventName,
890
+ });
891
+ eventsIngested += result.eventsIngested;
892
+ errors.push(...result.errors);
893
+ };
894
+
895
+ await ingestNewContent();
896
+ return await new Promise<AgentIngestWatchResult>((resolve) => {
897
+ const watcher = watch(watchFile, { persistent: true }, () => {
898
+ pendingIngest = pendingIngest.then(ingestNewContent, ingestNewContent);
899
+ });
900
+ const shutdown = () => {
901
+ watcher.close();
902
+ void pendingIngest.finally(() => {
903
+ resolve({
904
+ ok: errors.length === 0,
905
+ watch: true,
906
+ source,
907
+ file,
908
+ eventsIngested,
909
+ errors,
910
+ nextActions: [
911
+ `forge agent memory --entry ${source} --json`,
912
+ `forge agent hooks status --target ${source} --json`,
913
+ ],
914
+ exitCode: errors.length === 0 ? 0 : 1,
915
+ });
916
+ });
917
+ };
918
+ process.once("SIGINT", shutdown);
919
+ process.once("SIGTERM", shutdown);
920
+ });
921
+ }
922
+
923
+ function installAgentMemory(options: AgentMemoryCommandOptions): AgentInstallResult {
924
+ const target = normalizeInstallTarget(options.target ?? options.source ?? "generic");
925
+ const files =
926
+ target === "codex"
927
+ ? codexInstallFiles(options.workspaceRoot)
928
+ : target === "claude-code"
929
+ ? claudeCodeInstallFiles()
930
+ : target === "cursor"
931
+ ? cursorInstallFiles()
932
+ : [];
933
+ if (files.length === 0) {
934
+ return {
935
+ ok: false,
936
+ target,
937
+ filesWritten: [],
938
+ filesPlanned: [],
939
+ privacy: privacyDefaults(),
940
+ warnings: [`unknown agent memory install target: ${target}`],
941
+ exitCode: 1,
942
+ };
943
+ }
944
+ const filesWritten: string[] = [];
945
+ for (const file of files) {
946
+ const absolute = join(options.workspaceRoot, file.path);
947
+ const content = maybeMergeJson(absolute, file.content);
948
+ if (options.dryRun) {
949
+ continue;
950
+ }
951
+ if (!options.force && existsSync(absolute) && readFileSync(absolute, "utf8") === content) {
952
+ continue;
953
+ }
954
+ mkdirSync(dirname(absolute), { recursive: true });
955
+ writeFileSync(absolute, content);
956
+ filesWritten.push(file.path);
957
+ }
958
+ const planned = files.map((file) => file.path);
959
+ if (target === "codex") {
960
+ return codexInstallResult(filesWritten, planned);
961
+ }
962
+ if (target === "claude-code") {
963
+ return claudeCodeInstallResult(filesWritten, planned);
964
+ }
965
+ return cursorInstallResult(filesWritten, planned);
966
+ }
967
+
968
+ export function formatAgentMemoryJson(result: AgentMemoryCommandResult): string {
969
+ return `${JSON.stringify(result, null, 2)}\n`;
970
+ }
971
+
972
+ export function formatAgentMemoryHuman(result: AgentMemoryCommandResult): string {
973
+ if ("event" in result || "envelope" in result) {
974
+ return result.ok
975
+ ? `agent memory ingested: ${result.event?.normalizedKind ?? "event"}\n`
976
+ : `agent memory ingest failed: ${result.error ?? "unknown error"}\n`;
977
+ }
978
+ if ("watch" in result) {
979
+ return [
980
+ `agent memory ${result.watch ? "watch" : "queue ingest"} ${result.ok ? "ready" : "failed"} for ${result.source}`,
981
+ ...(result.file ? [`file: ${result.file}`] : []),
982
+ `events ingested: ${result.eventsIngested}`,
983
+ ...(result.errors.length > 0 ? ["errors:", ...result.errors.map((error) => `- ${error}`)] : []),
984
+ ].join("\n") + "\n";
985
+ }
986
+ if ("filesPlanned" in result) {
987
+ return [
988
+ `Forge Agent Memory Bridge ${result.ok ? "installed" : "failed"} for ${result.target}.`,
989
+ "files written:",
990
+ ...(result.filesWritten.length > 0 ? result.filesWritten.map((file) => `- ${file}`) : ["- none"]),
991
+ "privacy:",
992
+ "- raw prompts: off",
993
+ "- raw completions: off",
994
+ "- raw tool args: off",
995
+ "- transcript import: off",
996
+ ].join("\n") + "\n";
997
+ }
998
+ if ("agentMemory" in result) {
999
+ return formatAgentMemoryContextHuman(result);
1000
+ }
1001
+ if (!result.ok) {
1002
+ const nextActions = "nextActions" in result && Array.isArray(result.nextActions) ? result.nextActions : [];
1003
+ return [
1004
+ "Forge Agent Memory unavailable",
1005
+ "",
1006
+ result.error ?? "agent memory command failed",
1007
+ ...(nextActions.length > 0 ? ["", "Next:", ...nextActions.map((action) => ` ${action}`)] : []),
1008
+ ].join("\n") + "\n";
1009
+ }
1010
+ return formatAgentMemoryEventsHuman("events" in result ? result.events : []);
1011
+ }
1012
+
1013
+ function formatAgentMemoryContextHuman(result: AgentMemoryContextPack): string {
1014
+ const summary = result.agentMemory.summary;
1015
+ const lines = [
1016
+ `Forge Agent Context (${result.scope}${result.entry ? `: ${result.entry}` : ""})`,
1017
+ "",
1018
+ `events: ${summary.events}`,
1019
+ `sources: ${summary.sources.length > 0 ? summary.sources.join(", ") : "none"}`,
1020
+ `tools: ${summary.tools.length > 0 ? summary.tools.join(", ") : "none"}`,
1021
+ `files: ${summary.files}`,
1022
+ `entries: ${summary.entries}`,
1023
+ `proofs: ${summary.proofs}`,
1024
+ ...(summary.latestEventAt ? [`latest: ${summary.latestEventAt}`] : []),
1025
+ ];
1026
+ if (Object.keys(result.currentState).length > 0) {
1027
+ lines.push("", "Current:");
1028
+ for (const [key, value] of Object.entries(result.currentState)) {
1029
+ if (value !== undefined) {
1030
+ lines.push(` ${key}: ${Array.isArray(value) ? value.join(", ") : String(value)}`);
1031
+ }
1032
+ }
1033
+ }
1034
+ const recent = result.agentMemory.events.slice(-5);
1035
+ if (recent.length > 0) {
1036
+ lines.push("", "Recent:");
1037
+ for (const event of recent) {
1038
+ const parts = [
1039
+ event.capturedAt,
1040
+ event.source,
1041
+ event.kind,
1042
+ event.tool,
1043
+ event.status,
1044
+ event.summary,
1045
+ ].filter(Boolean);
1046
+ lines.push(` - ${parts.join(" | ")}`);
1047
+ }
1048
+ }
1049
+ if (result.agentMemory.openQuestions.length > 0) {
1050
+ lines.push("", "Open questions:");
1051
+ for (const question of result.agentMemory.openQuestions.slice(0, 5)) {
1052
+ lines.push(` - ${question}`);
1053
+ }
1054
+ }
1055
+ return `${lines.join("\n")}\n`;
1056
+ }
1057
+
1058
+ function formatAgentMemoryEventsHuman(events: AgentMemoryEventRecord[]): string {
1059
+ const sources = uniqueStrings(events.map((event) => event.sourceName));
1060
+ const tools = uniqueStrings(events.flatMap((event) => {
1061
+ const tool = agentMemoryEventBindings(event).toolName;
1062
+ return tool ? [tool] : [];
1063
+ }));
1064
+ const latest = events.at(-1)?.capturedAt;
1065
+ const lines = [
1066
+ "Forge Agent Memory",
1067
+ "",
1068
+ `events: ${events.length}`,
1069
+ `sources: ${sources.length > 0 ? sources.join(", ") : "none"}`,
1070
+ `tools: ${tools.length > 0 ? tools.join(", ") : "none"}`,
1071
+ ...(latest ? [`latest: ${latest}`] : []),
1072
+ ];
1073
+ if (events.length === 0) {
1074
+ lines.push("", "no agent memory events recorded");
1075
+ return `${lines.join("\n")}\n`;
1076
+ }
1077
+ lines.push("", "Recent:");
1078
+ for (const event of events.slice(-12)) {
1079
+ const bindings = agentMemoryEventBindings(event);
1080
+ const parts = [
1081
+ event.capturedAt,
1082
+ event.sourceName,
1083
+ event.normalizedKind,
1084
+ bindings.toolName,
1085
+ bindings.status,
1086
+ event.summary,
1087
+ ].filter(Boolean);
1088
+ lines.push(` - ${parts.join(" | ")}`);
1089
+ const details = [
1090
+ bindings.command ? `command: ${bindings.command}` : undefined,
1091
+ bindings.files.length > 0 ? `files: ${bindings.files.slice(0, 4).join(", ")}` : undefined,
1092
+ bindings.entries.length > 0 ? `entries: ${bindings.entries.slice(0, 4).join(", ")}` : undefined,
1093
+ bindings.proofs.length > 0 ? `proofs: ${bindings.proofs.slice(0, 4).join(", ")}` : undefined,
1094
+ ].filter(Boolean);
1095
+ if (details.length > 0) {
1096
+ lines.push(` ${details.join(" | ")}`);
1097
+ }
1098
+ }
1099
+ return `${lines.join("\n")}\n`;
1100
+ }
1101
+
1102
+ function agentMemoryEventBindings(event: AgentMemoryEventRecord): {
1103
+ toolName?: string;
1104
+ command?: string;
1105
+ status?: string;
1106
+ files: string[];
1107
+ entries: string[];
1108
+ proofs: string[];
1109
+ } {
1110
+ const raw = event.data.bindings;
1111
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
1112
+ return { files: [], entries: [], proofs: [] };
1113
+ }
1114
+ const record = raw as Record<string, unknown>;
1115
+ return {
1116
+ toolName: typeof record.toolName === "string" ? record.toolName : undefined,
1117
+ command: typeof record.command === "string" ? record.command : undefined,
1118
+ status: typeof record.status === "string" ? record.status : undefined,
1119
+ files: arrayOfStrings(record.files),
1120
+ entries: arrayOfStrings(record.entries),
1121
+ proofs: arrayOfStrings(record.proofs),
1122
+ };
1123
+ }
1124
+
1125
+ function arrayOfStrings(value: unknown): string[] {
1126
+ return Array.isArray(value) ? value.filter((item): item is string => typeof item === "string" && item.length > 0) : [];
1127
+ }
1128
+
1129
+ function uniqueStrings(values: string[]): string[] {
1130
+ return [...new Set(values)].sort();
1131
+ }
1132
+
1133
+ function normalizeInstallTarget(target: string): AgentMemorySourceName | string {
1134
+ if (target === "claude") {
1135
+ return "claude-code";
1136
+ }
1137
+ return target;
1138
+ }
1139
+
1140
+ function normalizeRawInput(input: unknown): Record<string, unknown> | null {
1141
+ if (input && typeof input === "object" && !Array.isArray(input)) {
1142
+ return input as Record<string, unknown>;
1143
+ }
1144
+ if (typeof input === "string" && input.trim()) {
1145
+ try {
1146
+ const parsed = JSON.parse(input) as unknown;
1147
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed as Record<string, unknown> : null;
1148
+ } catch {
1149
+ return null;
1150
+ }
1151
+ }
1152
+ return null;
1153
+ }
1154
+
1155
+ function objectField(value: Record<string, unknown>, key: string): Record<string, unknown> | undefined {
1156
+ const child = value[key];
1157
+ return child && typeof child === "object" && !Array.isArray(child) ? child as Record<string, unknown> : undefined;
1158
+ }
1159
+
1160
+ export async function readStdinJson(options?: { timeoutMs?: number }): Promise<unknown> {
1161
+ if (process.stdin.isTTY) {
1162
+ return undefined;
1163
+ }
1164
+ const timeoutMs = options?.timeoutMs ?? 2000;
1165
+ const chunks: Buffer[] = [];
1166
+ let settled = false;
1167
+
1168
+ return await new Promise<unknown>((resolve) => {
1169
+ const finish = () => {
1170
+ if (settled) {
1171
+ return;
1172
+ }
1173
+ settled = true;
1174
+ clearTimeout(timer);
1175
+ process.stdin.removeListener("data", onData);
1176
+ process.stdin.removeListener("end", finish);
1177
+ process.stdin.removeListener("close", finish);
1178
+ process.stdin.removeListener("error", finish);
1179
+ const raw = Buffer.concat(chunks).toString("utf8").trim();
1180
+ resolve(raw ? raw : undefined);
1181
+ };
1182
+ const onData = (chunk: Buffer | string) => {
1183
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
1184
+ };
1185
+ const timer = setTimeout(() => {
1186
+ process.stdin.destroy();
1187
+ finish();
1188
+ }, timeoutMs);
1189
+ process.stdin.on("data", onData);
1190
+ process.stdin.on("end", finish);
1191
+ process.stdin.on("close", finish);
1192
+ process.stdin.on("error", finish);
1193
+ process.stdin.resume();
1194
+ });
1195
+ }
1196
+
1197
+ function parseQueuedHookLine(raw: Record<string, unknown>): {
1198
+ source: string;
1199
+ eventName?: string;
1200
+ workspaceRoot?: string;
1201
+ payload: Record<string, unknown>;
1202
+ } | null {
1203
+ if (raw.forgeHookQueueV1 !== true) {
1204
+ return null;
1205
+ }
1206
+ const payload = objectField(raw, "payload") ?? objectField(raw, "raw");
1207
+ if (!payload) {
1208
+ return null;
1209
+ }
1210
+ return {
1211
+ source: typeof raw.source === "string" ? raw.source : "codex",
1212
+ eventName: typeof raw.eventName === "string" ? raw.eventName : undefined,
1213
+ workspaceRoot: typeof raw.workspaceRoot === "string" ? raw.workspaceRoot : undefined,
1214
+ payload,
1215
+ };
1216
+ }
1217
+
1218
+ function maybeMergeJson(path: string, generated: string): string {
1219
+ if (!existsSync(path) || !path.endsWith(".json")) {
1220
+ return generated;
1221
+ }
1222
+ try {
1223
+ const current = JSON.parse(readFileSync(path, "utf8")) as unknown;
1224
+ const next = JSON.parse(generated) as unknown;
1225
+ if (!current || typeof current !== "object" || Array.isArray(current) || !next || typeof next !== "object" || Array.isArray(next)) {
1226
+ return generated;
1227
+ }
1228
+ return `${JSON.stringify(deepMerge(current as Record<string, unknown>, next as Record<string, unknown>), null, 2)}\n`;
1229
+ } catch {
1230
+ return generated;
1231
+ }
1232
+ }
1233
+
1234
+ function deepMerge(left: Record<string, unknown>, right: Record<string, unknown>): Record<string, unknown> {
1235
+ const output: Record<string, unknown> = { ...left };
1236
+ for (const [key, value] of Object.entries(right)) {
1237
+ const existing = output[key];
1238
+ output[key] =
1239
+ existing && typeof existing === "object" && !Array.isArray(existing) &&
1240
+ value && typeof value === "object" && !Array.isArray(value)
1241
+ ? deepMerge(existing as Record<string, unknown>, value as Record<string, unknown>)
1242
+ : value;
1243
+ }
1244
+ return output;
1245
+ }