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