forgeos 0.1.0-alpha.0
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 +1 -0
- package/AGENTS.md +277 -0
- package/CHANGELOG.md +8 -0
- package/CONTRIBUTING.md +58 -0
- package/README.md +377 -0
- package/bin/forge-bun.mjs +110 -0
- package/bin/forge.mjs +19 -0
- package/package.json +96 -0
- package/packages/eslint-plugin-forge/index.ts +15 -0
- package/packages/eslint-plugin-forge/package.json +10 -0
- package/packages/eslint-plugin-forge/src/check-source.ts +95 -0
- package/packages/eslint-plugin-forge/src/load-artifacts.ts +24 -0
- package/packages/eslint-plugin-forge/src/rule-no-forge-guard-violation.ts +93 -0
- package/src/forge/_generated/actionSubscriptions.json +2 -0
- package/src/forge/_generated/actionSubscriptions.ts +10 -0
- package/src/forge/_generated/agentAdapterManifest.json +2 -0
- package/src/forge/_generated/agentAdapterManifest.ts +73 -0
- package/src/forge/_generated/agentContract.json +2 -0
- package/src/forge/_generated/agentContract.ts +912 -0
- package/src/forge/_generated/agentQuickstart.md +32 -0
- package/src/forge/_generated/aiContext.ts +59 -0
- package/src/forge/_generated/aiModels.json +2 -0
- package/src/forge/_generated/aiModels.ts +35 -0
- package/src/forge/_generated/aiProviders.json +2 -0
- package/src/forge/_generated/aiProviders.ts +23 -0
- package/src/forge/_generated/aiRegistry.json +2 -0
- package/src/forge/_generated/aiRegistry.ts +29 -0
- package/src/forge/_generated/api.json +2 -0
- package/src/forge/_generated/api.ts +8 -0
- package/src/forge/_generated/appGraph.json +2 -0
- package/src/forge/_generated/appGraph.ts +14511 -0
- package/src/forge/_generated/appMap.md +35 -0
- package/src/forge/_generated/artifactManifest.json +2 -0
- package/src/forge/_generated/artifactManifest.ts +7 -0
- package/src/forge/_generated/authClaims.json +2 -0
- package/src/forge/_generated/authClaims.ts +13 -0
- package/src/forge/_generated/authConfig.json +2 -0
- package/src/forge/_generated/authConfig.ts +17 -0
- package/src/forge/_generated/authContext.ts +23 -0
- package/src/forge/_generated/authRegistry.json +2 -0
- package/src/forge/_generated/authRegistry.ts +25 -0
- package/src/forge/_generated/buildInfo.json +2 -0
- package/src/forge/_generated/buildInfo.ts +9 -0
- package/src/forge/_generated/capabilityMap.json +2 -0
- package/src/forge/_generated/capabilityMap.md +15 -0
- package/src/forge/_generated/capabilityMap.ts +17 -0
- package/src/forge/_generated/client.ts +282 -0
- package/src/forge/_generated/clientApi.ts +9 -0
- package/src/forge/_generated/clientManifest.json +2 -0
- package/src/forge/_generated/clientManifest.ts +39 -0
- package/src/forge/_generated/clientTypes.ts +78 -0
- package/src/forge/_generated/configRegistry.json +2 -0
- package/src/forge/_generated/configRegistry.ts +4 -0
- package/src/forge/_generated/dataGraph.json +2 -0
- package/src/forge/_generated/dataGraph.ts +8 -0
- package/src/forge/_generated/db.json +2 -0
- package/src/forge/_generated/db.ts +2 -0
- package/src/forge/_generated/dbSecurityManifest.json +2 -0
- package/src/forge/_generated/dbSecurityManifest.ts +15 -0
- package/src/forge/_generated/dbSessionContext.json +2 -0
- package/src/forge/_generated/dbSessionContext.ts +39 -0
- package/src/forge/_generated/deployManifest.json +2 -0
- package/src/forge/_generated/deployManifest.ts +14 -0
- package/src/forge/_generated/devManifest.json +2 -0
- package/src/forge/_generated/devManifest.ts +47 -0
- package/src/forge/_generated/envSchema.json +2 -0
- package/src/forge/_generated/envSchema.ts +59 -0
- package/src/forge/_generated/frontendGraph.json +2 -0
- package/src/forge/_generated/frontendGraph.ts +27 -0
- package/src/forge/_generated/importGuards.json +2 -0
- package/src/forge/_generated/importGuards.ts +652 -0
- package/src/forge/_generated/index.ts +67 -0
- package/src/forge/_generated/liveProductionManifest.json +2 -0
- package/src/forge/_generated/liveProductionManifest.ts +23 -0
- package/src/forge/_generated/liveProtocol.json +2 -0
- package/src/forge/_generated/liveProtocol.ts +21 -0
- package/src/forge/_generated/liveQueryRegistry.json +2 -0
- package/src/forge/_generated/liveQueryRegistry.ts +9 -0
- package/src/forge/_generated/liveTransportConfig.json +2 -0
- package/src/forge/_generated/liveTransportConfig.ts +19 -0
- package/src/forge/_generated/makeRegistry.json +2 -0
- package/src/forge/_generated/makeRegistry.ts +163 -0
- package/src/forge/_generated/makeTemplates.json +2 -0
- package/src/forge/_generated/makeTemplates.ts +61 -0
- package/src/forge/_generated/mockMap.json +2 -0
- package/src/forge/_generated/mockMap.ts +7 -0
- package/src/forge/_generated/operationPlaybooks.md +145 -0
- package/src/forge/_generated/packageGraph.json +2 -0
- package/src/forge/_generated/packageGraph.ts +168569 -0
- package/src/forge/_generated/packageUpgradeRegistry.json +2 -0
- package/src/forge/_generated/packageUpgradeRegistry.ts +15 -0
- package/src/forge/_generated/permissionMatrix.json +2 -0
- package/src/forge/_generated/permissionMatrix.ts +7 -0
- package/src/forge/_generated/policyRegistry.json +2 -0
- package/src/forge/_generated/policyRegistry.ts +11 -0
- package/src/forge/_generated/queryRegistry.json +2 -0
- package/src/forge/_generated/queryRegistry.ts +9 -0
- package/src/forge/_generated/react.d.ts +22 -0
- package/src/forge/_generated/react.ts +29 -0
- package/src/forge/_generated/reactManifest.json +2 -0
- package/src/forge/_generated/reactManifest.ts +19 -0
- package/src/forge/_generated/releaseManifest.json +2 -0
- package/src/forge/_generated/releaseManifest.ts +25 -0
- package/src/forge/_generated/rlsPolicies.json +2 -0
- package/src/forge/_generated/rlsPolicies.sql +34 -0
- package/src/forge/_generated/rlsPolicies.ts +6 -0
- package/src/forge/_generated/runtimeGraph.json +2 -0
- package/src/forge/_generated/runtimeGraph.ts +8 -0
- package/src/forge/_generated/runtimeMatrix.json +2 -0
- package/src/forge/_generated/runtimeMatrix.ts +229125 -0
- package/src/forge/_generated/runtimeRegistry.ts +2 -0
- package/src/forge/_generated/runtimeRules.md +79 -0
- package/src/forge/_generated/secretRegistry.json +2 -0
- package/src/forge/_generated/secretRegistry.ts +50 -0
- package/src/forge/_generated/secretsContext.ts +11 -0
- package/src/forge/_generated/serverApi.ts +10 -0
- package/src/forge/_generated/sourceMapManifest.json +2 -0
- package/src/forge/_generated/sourceMapManifest.ts +7 -0
- package/src/forge/_generated/sqlPlan.json +2 -0
- package/src/forge/_generated/sqlPlan.ts +88 -0
- package/src/forge/_generated/subscriptionManifest.json +2 -0
- package/src/forge/_generated/subscriptionManifest.ts +7 -0
- package/src/forge/_generated/symbolicationManifest.json +2 -0
- package/src/forge/_generated/symbolicationManifest.ts +17 -0
- package/src/forge/_generated/telemetryRegistry.json +2 -0
- package/src/forge/_generated/telemetryRegistry.ts +9 -0
- package/src/forge/_generated/telemetrySinks.json +2 -0
- package/src/forge/_generated/telemetrySinks.ts +11 -0
- package/src/forge/_generated/tenantScope.json +2 -0
- package/src/forge/_generated/tenantScope.ts +8 -0
- package/src/forge/_generated/testGraph.json +2 -0
- package/src/forge/_generated/testGraph.ts +3054 -0
- package/src/forge/_generated/testPlanRegistry.json +2 -0
- package/src/forge/_generated/testPlanRegistry.ts +33 -0
- package/src/forge/_generated/uiRoutes.json +2 -0
- package/src/forge/_generated/uiRoutes.ts +16 -0
- package/src/forge/_generated/uiScenarios.json +2 -0
- package/src/forge/_generated/uiScenarios.ts +30 -0
- package/src/forge/_generated/uiTestManifest.json +2 -0
- package/src/forge/_generated/uiTestManifest.ts +27 -0
- package/src/forge/_generated/workflowRegistry.json +2 -0
- package/src/forge/_generated/workflowRegistry.ts +9 -0
- package/src/forge/_generated/workflowSubscriptions.json +2 -0
- package/src/forge/_generated/workflowSubscriptions.ts +10 -0
- package/src/forge/agent-adapters/index.ts +1002 -0
- package/src/forge/agent-adapters/types.ts +135 -0
- package/src/forge/cli/agent-contract.ts +50 -0
- package/src/forge/cli/ai.ts +148 -0
- package/src/forge/cli/auth.ts +198 -0
- package/src/forge/cli/build.ts +105 -0
- package/src/forge/cli/bun-exec.ts +4 -0
- package/src/forge/cli/commands.ts +1130 -0
- package/src/forge/cli/db.ts +316 -0
- package/src/forge/cli/deps.ts +277 -0
- package/src/forge/cli/dev.ts +529 -0
- package/src/forge/cli/doctor.ts +209 -0
- package/src/forge/cli/feature.ts +485 -0
- package/src/forge/cli/index.ts +25 -0
- package/src/forge/cli/lint-forge.ts +119 -0
- package/src/forge/cli/live.ts +179 -0
- package/src/forge/cli/main.ts +92 -0
- package/src/forge/cli/make.ts +133 -0
- package/src/forge/cli/new.ts +505 -0
- package/src/forge/cli/outbox.ts +297 -0
- package/src/forge/cli/output.ts +114 -0
- package/src/forge/cli/parse.ts +2211 -0
- package/src/forge/cli/policy.ts +204 -0
- package/src/forge/cli/query.ts +91 -0
- package/src/forge/cli/refactor.ts +221 -0
- package/src/forge/cli/release.ts +285 -0
- package/src/forge/cli/rls.ts +322 -0
- package/src/forge/cli/run.ts +76 -0
- package/src/forge/cli/secrets.ts +274 -0
- package/src/forge/cli/self-host.ts +468 -0
- package/src/forge/cli/serve.ts +93 -0
- package/src/forge/cli/telemetry.ts +219 -0
- package/src/forge/cli/verify.ts +587 -0
- package/src/forge/cli/version.ts +1 -0
- package/src/forge/cli/windows.ts +413 -0
- package/src/forge/cli/worker.ts +87 -0
- package/src/forge/cli/workflow.ts +424 -0
- package/src/forge/compiler/action-subscriptions/build.ts +116 -0
- package/src/forge/compiler/action-subscriptions/constants.ts +2 -0
- package/src/forge/compiler/action-subscriptions/index.ts +6 -0
- package/src/forge/compiler/action-subscriptions/parse.ts +6 -0
- package/src/forge/compiler/agent-contract/build.ts +1651 -0
- package/src/forge/compiler/agent-contract/types.ts +326 -0
- package/src/forge/compiler/ai-registry/build.ts +165 -0
- package/src/forge/compiler/ai-registry/constants.ts +2 -0
- package/src/forge/compiler/ai-registry/parse.ts +56 -0
- package/src/forge/compiler/api-surface/build.ts +107 -0
- package/src/forge/compiler/app-graph/build.ts +121 -0
- package/src/forge/compiler/app-graph/classify.ts +10 -0
- package/src/forge/compiler/app-graph/dup-symbol.ts +29 -0
- package/src/forge/compiler/app-graph/extract.ts +124 -0
- package/src/forge/compiler/app-graph/forge-apis.ts +29 -0
- package/src/forge/compiler/app-graph/index.ts +15 -0
- package/src/forge/compiler/app-graph/module-graph.ts +320 -0
- package/src/forge/compiler/app-graph/parser.ts +119 -0
- package/src/forge/compiler/app-graph/symbols.ts +48 -0
- package/src/forge/compiler/app-graph/tsconfig-hash.ts +62 -0
- package/src/forge/compiler/app-graph/types.ts +43 -0
- package/src/forge/compiler/app-graph/versions.ts +14 -0
- package/src/forge/compiler/cache/index.ts +17 -0
- package/src/forge/compiler/cache/key.ts +46 -0
- package/src/forge/compiler/cache/scheduler.ts +72 -0
- package/src/forge/compiler/cache/store.ts +78 -0
- package/src/forge/compiler/classifier/capabilities.ts +78 -0
- package/src/forge/compiler/classifier/classify.ts +113 -0
- package/src/forge/compiler/classifier/contexts.ts +188 -0
- package/src/forge/compiler/classifier/index.ts +18 -0
- package/src/forge/compiler/classifier/runtime-matrix.ts +45 -0
- package/src/forge/compiler/classifier/secrets.ts +41 -0
- package/src/forge/compiler/classifier/signals.ts +129 -0
- package/src/forge/compiler/client-sdk/build-manifest.ts +151 -0
- package/src/forge/compiler/client-sdk/render-client.ts +432 -0
- package/src/forge/compiler/data-graph/build.ts +131 -0
- package/src/forge/compiler/data-graph/constants.ts +5 -0
- package/src/forge/compiler/data-graph/index.ts +6 -0
- package/src/forge/compiler/data-graph/parse.ts +176 -0
- package/src/forge/compiler/data-graph/rls/build.ts +222 -0
- package/src/forge/compiler/data-graph/rls/types.ts +62 -0
- package/src/forge/compiler/data-graph/sql/ddl.ts +390 -0
- package/src/forge/compiler/data-graph/sql/naming.ts +10 -0
- package/src/forge/compiler/data-graph/sql/serialize.ts +85 -0
- package/src/forge/compiler/data-graph/sql/types.ts +37 -0
- package/src/forge/compiler/dev-manifest/build.ts +170 -0
- package/src/forge/compiler/dev-manifest/constants.ts +5 -0
- package/src/forge/compiler/diagnostics/codes.ts +611 -0
- package/src/forge/compiler/diagnostics/create.ts +245 -0
- package/src/forge/compiler/diagnostics/index.ts +55 -0
- package/src/forge/compiler/emitter/artifact-kind.ts +14 -0
- package/src/forge/compiler/emitter/barrel.ts +44 -0
- package/src/forge/compiler/emitter/constants.ts +7 -0
- package/src/forge/compiler/emitter/emit.ts +237 -0
- package/src/forge/compiler/emitter/index.ts +24 -0
- package/src/forge/compiler/emitter/lock.ts +62 -0
- package/src/forge/compiler/emitter/render.ts +73 -0
- package/src/forge/compiler/emitter/write.ts +35 -0
- package/src/forge/compiler/frontend-graph/build.ts +495 -0
- package/src/forge/compiler/fs/index.ts +23 -0
- package/src/forge/compiler/fs/memory.ts +233 -0
- package/src/forge/compiler/fs/node.ts +139 -0
- package/src/forge/compiler/fs/profile.ts +108 -0
- package/src/forge/compiler/fs/types.ts +52 -0
- package/src/forge/compiler/guards/artifacts.ts +96 -0
- package/src/forge/compiler/guards/check-ai-usage.ts +98 -0
- package/src/forge/compiler/guards/check-import-guards.ts +106 -0
- package/src/forge/compiler/guards/check-process-env.ts +98 -0
- package/src/forge/compiler/guards/check-query-usage.ts +76 -0
- package/src/forge/compiler/guards/index.ts +11 -0
- package/src/forge/compiler/guards/propagate-contexts.ts +57 -0
- package/src/forge/compiler/index.ts +17 -0
- package/src/forge/compiler/integration/add.ts +496 -0
- package/src/forge/compiler/integration/index.ts +17 -0
- package/src/forge/compiler/integration/plan.ts +283 -0
- package/src/forge/compiler/integration/render.ts +189 -0
- package/src/forge/compiler/integration/snapshot.ts +52 -0
- package/src/forge/compiler/integration/templates/ai.ts +131 -0
- package/src/forge/compiler/integration/templates/index.ts +8 -0
- package/src/forge/compiler/integration/templates/posthog.ts +145 -0
- package/src/forge/compiler/integration/templates/render.ts +113 -0
- package/src/forge/compiler/integration/templates/sentry.ts +151 -0
- package/src/forge/compiler/integration/templates/stripe.ts +109 -0
- package/src/forge/compiler/integration/templates/types.ts +14 -0
- package/src/forge/compiler/integration/templates/zod.ts +55 -0
- package/src/forge/compiler/live-production/types.ts +122 -0
- package/src/forge/compiler/live-query-registry/build.ts +150 -0
- package/src/forge/compiler/live-query-registry/constants.ts +2 -0
- package/src/forge/compiler/make-registry/build.ts +179 -0
- package/src/forge/compiler/orchestrator/discover.ts +214 -0
- package/src/forge/compiler/orchestrator/fast-check.ts +117 -0
- package/src/forge/compiler/orchestrator/generate-lock.ts +138 -0
- package/src/forge/compiler/orchestrator/guards.ts +5 -0
- package/src/forge/compiler/orchestrator/index.ts +27 -0
- package/src/forge/compiler/orchestrator/manifest-hashes.ts +21 -0
- package/src/forge/compiler/orchestrator/manifest.ts +92 -0
- package/src/forge/compiler/orchestrator/orphans.ts +51 -0
- package/src/forge/compiler/orchestrator/plan.ts +876 -0
- package/src/forge/compiler/orchestrator/profile.ts +36 -0
- package/src/forge/compiler/orchestrator/run.ts +277 -0
- package/src/forge/compiler/orchestrator/serialize.ts +886 -0
- package/src/forge/compiler/orchestrator/session.ts +96 -0
- package/src/forge/compiler/orchestrator/types.ts +31 -0
- package/src/forge/compiler/orchestrator/verify.ts +38 -0
- package/src/forge/compiler/orchestrator/workspace-index.ts +154 -0
- package/src/forge/compiler/package-graph/capabilities-stub.ts +33 -0
- package/src/forge/compiler/package-graph/checksum.ts +97 -0
- package/src/forge/compiler/package-graph/compiler.ts +392 -0
- package/src/forge/compiler/package-graph/constants.ts +4 -0
- package/src/forge/compiler/package-graph/dts-extractor.ts +142 -0
- package/src/forge/compiler/package-graph/exports-discovery.ts +84 -0
- package/src/forge/compiler/package-graph/extract-dts.ts +32 -0
- package/src/forge/compiler/package-graph/index.ts +33 -0
- package/src/forge/compiler/package-graph/jsdoc.ts +62 -0
- package/src/forge/compiler/package-graph/read-file.ts +21 -0
- package/src/forge/compiler/package-graph/resolve.ts +127 -0
- package/src/forge/compiler/package-manager/adapter.ts +237 -0
- package/src/forge/compiler/package-manager/bun-executable.ts +92 -0
- package/src/forge/compiler/package-manager/commands.ts +47 -0
- package/src/forge/compiler/package-manager/detect.ts +79 -0
- package/src/forge/compiler/package-manager/executor.ts +117 -0
- package/src/forge/compiler/package-manager/index.ts +22 -0
- package/src/forge/compiler/package-manager/parse-spec.ts +16 -0
- package/src/forge/compiler/package-manager/version.ts +27 -0
- package/src/forge/compiler/package-upgrades/apply.ts +195 -0
- package/src/forge/compiler/package-upgrades/comparator.ts +181 -0
- package/src/forge/compiler/package-upgrades/impact.ts +139 -0
- package/src/forge/compiler/package-upgrades/markdown.ts +97 -0
- package/src/forge/compiler/package-upgrades/planner.ts +532 -0
- package/src/forge/compiler/package-upgrades/risk.ts +208 -0
- package/src/forge/compiler/package-upgrades/types.ts +174 -0
- package/src/forge/compiler/policy-registry/build.ts +266 -0
- package/src/forge/compiler/policy-registry/constants.ts +2 -0
- package/src/forge/compiler/policy-registry/parse.ts +81 -0
- package/src/forge/compiler/primitives/compare.ts +26 -0
- package/src/forge/compiler/primitives/hash.ts +40 -0
- package/src/forge/compiler/primitives/header.ts +45 -0
- package/src/forge/compiler/primitives/index.ts +45 -0
- package/src/forge/compiler/primitives/paths.ts +24 -0
- package/src/forge/compiler/primitives/result.ts +164 -0
- package/src/forge/compiler/primitives/serialize.ts +66 -0
- package/src/forge/compiler/primitives/sort.ts +87 -0
- package/src/forge/compiler/query-registry/build.ts +114 -0
- package/src/forge/compiler/query-registry/constants.ts +2 -0
- package/src/forge/compiler/recipes/definitions.ts +289 -0
- package/src/forge/compiler/recipes/helpers.ts +37 -0
- package/src/forge/compiler/recipes/index.ts +21 -0
- package/src/forge/compiler/recipes/registry.ts +102 -0
- package/src/forge/compiler/release/build.ts +100 -0
- package/src/forge/compiler/release/types.ts +119 -0
- package/src/forge/compiler/runtime-graph/build.ts +137 -0
- package/src/forge/compiler/runtime-graph/constants.ts +5 -0
- package/src/forge/compiler/runtime-graph/index.ts +5 -0
- package/src/forge/compiler/sandbox/artifact-sanitize.ts +26 -0
- package/src/forge/compiler/sandbox/backends/child.ts +123 -0
- package/src/forge/compiler/sandbox/backends/docker.ts +173 -0
- package/src/forge/compiler/sandbox/index.ts +51 -0
- package/src/forge/compiler/sandbox/inspect.ts +143 -0
- package/src/forge/compiler/sandbox/inspector-entry.ts +115 -0
- package/src/forge/compiler/sandbox/limits.ts +31 -0
- package/src/forge/compiler/sandbox/scrub-env.ts +60 -0
- package/src/forge/compiler/sandbox/secret-scan.ts +54 -0
- package/src/forge/compiler/sandbox/serialize.ts +106 -0
- package/src/forge/compiler/sandbox/types.ts +7 -0
- package/src/forge/compiler/secret-registry/build.ts +123 -0
- package/src/forge/compiler/telemetry-registry/build.ts +89 -0
- package/src/forge/compiler/telemetry-registry/constants.ts +2 -0
- package/src/forge/compiler/telemetry-registry/parse.ts +13 -0
- package/src/forge/compiler/test-graph/build.ts +277 -0
- package/src/forge/compiler/types/action-subscriptions.ts +19 -0
- package/src/forge/compiler/types/ai-registry.ts +33 -0
- package/src/forge/compiler/types/app-graph.ts +80 -0
- package/src/forge/compiler/types/capability.ts +29 -0
- package/src/forge/compiler/types/classification.ts +9 -0
- package/src/forge/compiler/types/cli.ts +159 -0
- package/src/forge/compiler/types/data-graph.ts +24 -0
- package/src/forge/compiler/types/dev-manifest.ts +41 -0
- package/src/forge/compiler/types/diagnostic.ts +12 -0
- package/src/forge/compiler/types/emit.ts +25 -0
- package/src/forge/compiler/types/frontend-graph.ts +81 -0
- package/src/forge/compiler/types/import-guards.ts +19 -0
- package/src/forge/compiler/types/index.ts +98 -0
- package/src/forge/compiler/types/integration.ts +25 -0
- package/src/forge/compiler/types/json.ts +3 -0
- package/src/forge/compiler/types/live-query-registry.ts +32 -0
- package/src/forge/compiler/types/lock.ts +37 -0
- package/src/forge/compiler/types/package-graph.ts +84 -0
- package/src/forge/compiler/types/policy-registry.ts +69 -0
- package/src/forge/compiler/types/query-registry.ts +18 -0
- package/src/forge/compiler/types/runtime-graph.ts +30 -0
- package/src/forge/compiler/types/runtime-matrix.ts +16 -0
- package/src/forge/compiler/types/runtime.ts +30 -0
- package/src/forge/compiler/types/sandbox.ts +24 -0
- package/src/forge/compiler/types/secret-registry.ts +38 -0
- package/src/forge/compiler/types/telemetry-registry.ts +26 -0
- package/src/forge/compiler/types/test-graph.ts +45 -0
- package/src/forge/compiler/types/workflow-registry.ts +42 -0
- package/src/forge/compiler/workflow-registry/build.ts +180 -0
- package/src/forge/compiler/workflow-registry/constants.ts +2 -0
- package/src/forge/compiler/workflow-registry/index.ts +5 -0
- package/src/forge/compiler/workflow-registry/parse.ts +19 -0
- package/src/forge/dev/server.ts +1379 -0
- package/src/forge/dev/types.ts +49 -0
- package/src/forge/dev/watch.ts +109 -0
- package/src/forge/dev-console/cycle.ts +652 -0
- package/src/forge/dev-console/types.ts +99 -0
- package/src/forge/feature/compiler.ts +656 -0
- package/src/forge/feature/examples.ts +125 -0
- package/src/forge/feature/types.ts +177 -0
- package/src/forge/impact/index.ts +1160 -0
- package/src/forge/impact/types.ts +151 -0
- package/src/forge/intent/index.ts +490 -0
- package/src/forge/intent/types.ts +73 -0
- package/src/forge/make/fields.ts +146 -0
- package/src/forge/make/index.ts +1101 -0
- package/src/forge/make/naming.ts +42 -0
- package/src/forge/make/templates.ts +525 -0
- package/src/forge/make/types.ts +151 -0
- package/src/forge/platform/module.ts +20 -0
- package/src/forge/policy.ts +1 -0
- package/src/forge/react/index.ts +418 -0
- package/src/forge/refactor/index.ts +1936 -0
- package/src/forge/refactor/text-utils.ts +34 -0
- package/src/forge/refactor/types.ts +191 -0
- package/src/forge/refactor/workspace-fs.ts +171 -0
- package/src/forge/repair/index.ts +656 -0
- package/src/forge/repair/rules/index.ts +476 -0
- package/src/forge/repair/types.ts +175 -0
- package/src/forge/review/index.ts +992 -0
- package/src/forge/review/types.ts +196 -0
- package/src/forge/runtime/ai/check.ts +86 -0
- package/src/forge/runtime/ai/context.ts +394 -0
- package/src/forge/runtime/ai/cost-estimator.ts +41 -0
- package/src/forge/runtime/ai/mock.ts +49 -0
- package/src/forge/runtime/ai/providers.ts +78 -0
- package/src/forge/runtime/ai/state.ts +17 -0
- package/src/forge/runtime/ai/types.ts +67 -0
- package/src/forge/runtime/auth/authenticate.ts +58 -0
- package/src/forge/runtime/auth/claims.ts +119 -0
- package/src/forge/runtime/auth/config.ts +148 -0
- package/src/forge/runtime/auth/errors.ts +45 -0
- package/src/forge/runtime/auth/evaluate.ts +126 -0
- package/src/forge/runtime/auth/resolve.ts +74 -0
- package/src/forge/runtime/auth/types.ts +87 -0
- package/src/forge/runtime/auth/verifier.ts +138 -0
- package/src/forge/runtime/context/create-context.ts +204 -0
- package/src/forge/runtime/context/create-query-context.ts +34 -0
- package/src/forge/runtime/db/adapter.ts +31 -0
- package/src/forge/runtime/db/factory.ts +83 -0
- package/src/forge/runtime/db/generated-client.ts +294 -0
- package/src/forge/runtime/db/memory-adapter.ts +706 -0
- package/src/forge/runtime/db/migrate.ts +132 -0
- package/src/forge/runtime/db/outbox.ts +54 -0
- package/src/forge/runtime/db/pglite-adapter.ts +51 -0
- package/src/forge/runtime/db/postgres-adapter.ts +112 -0
- package/src/forge/runtime/db/read-only-client.ts +97 -0
- package/src/forge/runtime/db/session-context.ts +62 -0
- package/src/forge/runtime/executor.ts +446 -0
- package/src/forge/runtime/live/dependency-tracker.ts +57 -0
- package/src/forge/runtime/live/invalidation-log.ts +189 -0
- package/src/forge/runtime/live/live-query-runner.ts +267 -0
- package/src/forge/runtime/live/registry.ts +28 -0
- package/src/forge/runtime/live/sse.ts +75 -0
- package/src/forge/runtime/live/subscription-manager.ts +443 -0
- package/src/forge/runtime/live/types.ts +143 -0
- package/src/forge/runtime/outbox/claim.ts +153 -0
- package/src/forge/runtime/outbox/process.ts +298 -0
- package/src/forge/runtime/outbox/retry.ts +8 -0
- package/src/forge/runtime/outbox/subscriptions.ts +33 -0
- package/src/forge/runtime/outbox/types.ts +69 -0
- package/src/forge/runtime/policy/check.ts +157 -0
- package/src/forge/runtime/policy/load.ts +55 -0
- package/src/forge/runtime/query/registry.ts +19 -0
- package/src/forge/runtime/query/run-query.ts +347 -0
- package/src/forge/runtime/release/runtime.ts +322 -0
- package/src/forge/runtime/release/symbolicate.ts +175 -0
- package/src/forge/runtime/runner/command-transaction.ts +193 -0
- package/src/forge/runtime/runner/run-entry.ts +226 -0
- package/src/forge/runtime/secrets/check.ts +78 -0
- package/src/forge/runtime/secrets/create-context.ts +138 -0
- package/src/forge/runtime/secrets/env-loader.ts +94 -0
- package/src/forge/runtime/secrets/runtime-bundle.ts +47 -0
- package/src/forge/runtime/secrets/types.ts +31 -0
- package/src/forge/runtime/telemetry/buffer.ts +87 -0
- package/src/forge/runtime/telemetry/context.ts +192 -0
- package/src/forge/runtime/telemetry/correlation.ts +13 -0
- package/src/forge/runtime/telemetry/flush.ts +190 -0
- package/src/forge/runtime/telemetry/process.ts +20 -0
- package/src/forge/runtime/telemetry/scrubber.ts +115 -0
- package/src/forge/runtime/telemetry/sinks/local-jsonl.ts +39 -0
- package/src/forge/runtime/telemetry/sinks/posthog.ts +64 -0
- package/src/forge/runtime/telemetry/sinks/sentry.ts +60 -0
- package/src/forge/runtime/telemetry/spans.ts +58 -0
- package/src/forge/runtime/telemetry/types.ts +64 -0
- package/src/forge/runtime/workflows/cancel.ts +26 -0
- package/src/forge/runtime/workflows/create-run.ts +98 -0
- package/src/forge/runtime/workflows/process-run.ts +182 -0
- package/src/forge/runtime/workflows/process-step.ts +190 -0
- package/src/forge/runtime/workflows/process.ts +260 -0
- package/src/forge/runtime/workflows/registry.ts +51 -0
- package/src/forge/runtime/workflows/resolve-step.ts +46 -0
- package/src/forge/runtime/workflows/retry-run.ts +44 -0
- package/src/forge/runtime/workflows/retry.ts +8 -0
- package/src/forge/runtime/workflows/sanitize.ts +19 -0
- package/src/forge/runtime/workflows/start-from-outbox.ts +71 -0
- package/src/forge/runtime/workflows/types.ts +77 -0
- package/src/forge/server.ts +96 -0
- package/src/forge/ui/index.ts +770 -0
- package/src/forge/ui/types.ts +191 -0
- package/templates/b2b-support-web/.env.example +22 -0
- package/templates/b2b-support-web/.vscode/settings.json +14 -0
- package/templates/b2b-support-web/AGENTS.md +108 -0
- package/templates/b2b-support-web/README.md +48 -0
- package/templates/b2b-support-web/forge.config.ts +3 -0
- package/templates/b2b-support-web/package.json +34 -0
- package/templates/b2b-support-web/src/actions/captureTicketCreated.ts +14 -0
- package/templates/b2b-support-web/src/commands/closeTicket.ts +20 -0
- package/templates/b2b-support-web/src/commands/createTicket.ts +47 -0
- package/templates/b2b-support-web/src/commands/manageBilling.ts +9 -0
- package/templates/b2b-support-web/src/forge/schema.ts +35 -0
- package/templates/b2b-support-web/src/policies.ts +9 -0
- package/templates/b2b-support-web/src/queries/getTicket.ts +6 -0
- package/templates/b2b-support-web/src/queries/listTickets.ts +6 -0
- package/templates/b2b-support-web/src/queries/liveTickets.ts +9 -0
- package/templates/b2b-support-web/src/workflows/triageTicketWorkflow.ts +64 -0
- package/templates/b2b-support-web/tsconfig.json +14 -0
- package/templates/b2b-support-web/web/app/globals.css +77 -0
- package/templates/b2b-support-web/web/app/layout.tsx +13 -0
- package/templates/b2b-support-web/web/app/page.tsx +13 -0
- package/templates/b2b-support-web/web/app/providers.tsx +21 -0
- package/templates/b2b-support-web/web/app/tickets/page.tsx +21 -0
- package/templates/b2b-support-web/web/components/CreateTicketForm.tsx +43 -0
- package/templates/b2b-support-web/web/components/PolicyDeniedDemo.tsx +31 -0
- package/templates/b2b-support-web/web/components/TicketList.tsx +52 -0
- package/templates/b2b-support-web/web/components/TraceDetails.tsx +18 -0
- package/templates/b2b-support-web/web/components/TriageStatus.tsx +13 -0
- package/templates/b2b-support-web/web/lib/forge.ts +13 -0
- package/templates/b2b-support-web/web/next-env.d.ts +5 -0
- package/templates/b2b-support-web/web/next.config.ts +8 -0
- package/templates/b2b-support-web/web/package.json +21 -0
- package/templates/b2b-support-web/web/tsconfig.json +30 -0
- package/templates/minimal-web/.vscode/settings.json +14 -0
- package/templates/minimal-web/README.md +21 -0
- package/templates/minimal-web/forge.config.ts +3 -0
- package/templates/minimal-web/package.json +32 -0
- package/templates/minimal-web/src/actions/logNoteCreated.ts +11 -0
- package/templates/minimal-web/src/commands/createNote.ts +26 -0
- package/templates/minimal-web/src/forge/schema.ts +12 -0
- package/templates/minimal-web/src/policies.ts +6 -0
- package/templates/minimal-web/src/queries/listNotes.ts +8 -0
- package/templates/minimal-web/src/queries/liveNotes.ts +8 -0
- package/templates/minimal-web/tsconfig.json +15 -0
- package/templates/minimal-web/web/index.html +12 -0
- package/templates/minimal-web/web/package.json +21 -0
- package/templates/minimal-web/web/src/App.tsx +89 -0
- package/templates/minimal-web/web/src/lib/forge.ts +13 -0
- package/templates/minimal-web/web/src/main.tsx +13 -0
- package/templates/minimal-web/web/src/styles.css +156 -0
- package/templates/minimal-web/web/tsconfig.json +18 -0
|
@@ -0,0 +1,1651 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { nodeFileSystem } from "../fs/index.ts";
|
|
3
|
+
import type { ApiSurface } from "../api-surface/build.ts";
|
|
4
|
+
import type { ClassifiedPackage } from "../classifier/runtime-matrix.ts";
|
|
5
|
+
import { detectCapabilities } from "../classifier/capabilities.ts";
|
|
6
|
+
import { detectSecrets } from "../classifier/secrets.ts";
|
|
7
|
+
import { GENERATOR_VERSION } from "../emitter/constants.ts";
|
|
8
|
+
import { stripDeterministicHeader } from "../primitives/header.ts";
|
|
9
|
+
import { canonicalJson, normalizeNewlines, serializeCanonical } from "../primitives/serialize.ts";
|
|
10
|
+
import { resolveByPackageName } from "../recipes/registry.ts";
|
|
11
|
+
import { secretLeakScan } from "../sandbox/secret-scan.ts";
|
|
12
|
+
import type { AiRegistry } from "../types/ai-registry.ts";
|
|
13
|
+
import type { AppGraph } from "../types/app-graph.ts";
|
|
14
|
+
import type { DataGraph } from "../types/data-graph.ts";
|
|
15
|
+
import type { Diagnostic } from "../types/diagnostic.ts";
|
|
16
|
+
import type { PackageGraph } from "../types/package-graph.ts";
|
|
17
|
+
import type {
|
|
18
|
+
PermissionMatrix,
|
|
19
|
+
PolicyRegistry,
|
|
20
|
+
TenantScope,
|
|
21
|
+
} from "../types/policy-registry.ts";
|
|
22
|
+
import type { RuntimeContext } from "../types/runtime.ts";
|
|
23
|
+
import type { RuntimeGraph } from "../types/runtime-graph.ts";
|
|
24
|
+
import type { SecretRegistry } from "../types/secret-registry.ts";
|
|
25
|
+
import type {
|
|
26
|
+
TelemetryRegistry,
|
|
27
|
+
TelemetrySinks,
|
|
28
|
+
} from "../types/telemetry-registry.ts";
|
|
29
|
+
import type { WorkflowRegistry } from "../types/workflow-registry.ts";
|
|
30
|
+
import type { QueryRegistry } from "../types/query-registry.ts";
|
|
31
|
+
import type { LiveQueryRegistry } from "../types/live-query-registry.ts";
|
|
32
|
+
import type { ClientManifest } from "../client-sdk/build-manifest.ts";
|
|
33
|
+
import type { FrontendGraph } from "../types/frontend-graph.ts";
|
|
34
|
+
import { createDiagnostic } from "../diagnostics/create.ts";
|
|
35
|
+
import { AUTH_ENV, DEFAULT_AUTH_CLAIMS } from "../../runtime/auth/config.ts";
|
|
36
|
+
import type {
|
|
37
|
+
AgentCapabilityMap,
|
|
38
|
+
AgentCapabilityMapEntry,
|
|
39
|
+
AgentContract,
|
|
40
|
+
AgentFrontendRuntimeBindingInfo,
|
|
41
|
+
AgentFrontendUsageInfo,
|
|
42
|
+
AgentHttpEndpointInfo,
|
|
43
|
+
AgentIntegrationInfo,
|
|
44
|
+
AgentRuntimeRule,
|
|
45
|
+
AgentPlaybook,
|
|
46
|
+
} from "./types.ts";
|
|
47
|
+
|
|
48
|
+
const AGENTS_USER_START = "<!-- user-notes:start -->";
|
|
49
|
+
const AGENTS_USER_END = "<!-- user-notes:end -->";
|
|
50
|
+
const DEFAULT_USER_NOTES = "Project-specific notes can go here.";
|
|
51
|
+
|
|
52
|
+
export interface AgentContractInput {
|
|
53
|
+
workspaceRoot: string;
|
|
54
|
+
appGraph: AppGraph;
|
|
55
|
+
packageGraph: PackageGraph;
|
|
56
|
+
classified: ClassifiedPackage[];
|
|
57
|
+
runtimeGraph: RuntimeGraph;
|
|
58
|
+
dataGraph: DataGraph;
|
|
59
|
+
policyRegistry: PolicyRegistry;
|
|
60
|
+
permissionMatrix: PermissionMatrix;
|
|
61
|
+
tenantScope: TenantScope;
|
|
62
|
+
secretRegistry: SecretRegistry;
|
|
63
|
+
telemetryRegistry: TelemetryRegistry;
|
|
64
|
+
telemetrySinks: TelemetrySinks;
|
|
65
|
+
aiRegistry: AiRegistry;
|
|
66
|
+
queryRegistry: QueryRegistry;
|
|
67
|
+
liveQueryRegistry: LiveQueryRegistry;
|
|
68
|
+
workflowRegistry: WorkflowRegistry;
|
|
69
|
+
apiSurface: ApiSurface;
|
|
70
|
+
clientManifest: ClientManifest;
|
|
71
|
+
frontendGraph: FrontendGraph;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface AgentContractArtifacts {
|
|
75
|
+
contract: AgentContract;
|
|
76
|
+
capabilityMap: AgentCapabilityMap;
|
|
77
|
+
agentsMd: string;
|
|
78
|
+
appMapMd: string;
|
|
79
|
+
capabilityMapMd: string;
|
|
80
|
+
runtimeRulesMd: string;
|
|
81
|
+
operationPlaybooksMd: string;
|
|
82
|
+
agentQuickstartMd: string;
|
|
83
|
+
diagnostics: Diagnostic[];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function sorted<T>(items: T[], by: (item: T) => string): T[] {
|
|
87
|
+
return [...items].sort((a, b) => by(a).localeCompare(by(b)));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function uniqueSorted(items: string[]): string[] {
|
|
91
|
+
return [...new Set(items.filter(Boolean))].sort();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function readPackageInfo(workspaceRoot: string): { name: string; template?: string } {
|
|
95
|
+
const packagePath = join(workspaceRoot, "package.json");
|
|
96
|
+
if (!nodeFileSystem.exists(packagePath)) {
|
|
97
|
+
return { name: "forge-app" };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const pkg = JSON.parse((nodeFileSystem.readText(packagePath) ?? "")) as {
|
|
102
|
+
name?: string;
|
|
103
|
+
forge?: { template?: string };
|
|
104
|
+
};
|
|
105
|
+
return {
|
|
106
|
+
name: pkg.name ?? "forge-app",
|
|
107
|
+
template: pkg.forge?.template,
|
|
108
|
+
};
|
|
109
|
+
} catch {
|
|
110
|
+
return { name: "forge-app" };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function authPolicy(
|
|
115
|
+
auth: { kind: string; policy?: string } | undefined,
|
|
116
|
+
): string | undefined {
|
|
117
|
+
return auth?.kind === "policy" ? auth.policy : auth?.kind;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function packageNamesForModule(appGraph: AppGraph, moduleId: string): string[] {
|
|
121
|
+
const node = appGraph.moduleGraph.nodes.find((candidate) => candidate.id === moduleId);
|
|
122
|
+
return uniqueSorted(node?.directPackageImports.map((imp) => imp.packageName) ?? []);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function forbiddenForContext(
|
|
126
|
+
classified: ClassifiedPackage[],
|
|
127
|
+
context: RuntimeContext,
|
|
128
|
+
): string[] {
|
|
129
|
+
const forbidden = new Set<string>();
|
|
130
|
+
for (const pkg of classified) {
|
|
131
|
+
const recipe = pkg.recipe ?? resolveByPackageName(pkg.api.name);
|
|
132
|
+
const denied = recipe?.contexts.denied ?? pkg.classification.incompatible;
|
|
133
|
+
if (denied.includes(context)) {
|
|
134
|
+
const capabilities = detectCapabilities(pkg.api, recipe ?? undefined);
|
|
135
|
+
for (const [name, status] of Object.entries(capabilities)) {
|
|
136
|
+
if (
|
|
137
|
+
typeof status === "object" &&
|
|
138
|
+
"status" in status &&
|
|
139
|
+
(status.status === "required" || status.status === "forbidden")
|
|
140
|
+
) {
|
|
141
|
+
forbidden.add(name);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if ((detectSecrets(pkg.api, recipe ?? undefined).length > 0)) {
|
|
145
|
+
forbidden.add("secrets");
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return uniqueSorted([...forbidden]);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const DB_READ_OPS = new Set(["all", "count", "find", "first", "get", "list", "where"]);
|
|
153
|
+
const DB_WRITE_OPS = new Set(["delete", "insert", "patch", "replace", "update", "upsert"]);
|
|
154
|
+
|
|
155
|
+
function sourceText(workspaceRoot: string, file: string | undefined): string {
|
|
156
|
+
if (!file) {
|
|
157
|
+
return "";
|
|
158
|
+
}
|
|
159
|
+
const absolute = join(workspaceRoot, file);
|
|
160
|
+
if (!nodeFileSystem.exists(absolute)) {
|
|
161
|
+
return "";
|
|
162
|
+
}
|
|
163
|
+
return nodeFileSystem.readText(absolute) ?? "";
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function dbTablesForText(
|
|
167
|
+
text: string,
|
|
168
|
+
tableNames: Set<string>,
|
|
169
|
+
ops: Set<string>,
|
|
170
|
+
): string[] {
|
|
171
|
+
const tables: string[] = [];
|
|
172
|
+
for (const match of text.matchAll(/ctx\.db\.([A-Za-z_$][A-Za-z0-9_$]*)\s*\.\s*([A-Za-z_$][A-Za-z0-9_$]*)/g)) {
|
|
173
|
+
const table = match[1] ?? "";
|
|
174
|
+
const op = match[2] ?? "";
|
|
175
|
+
if (tableNames.has(table) && ops.has(op)) {
|
|
176
|
+
tables.push(table);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return uniqueSorted(tables);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function dbTablesForFile(
|
|
183
|
+
workspaceRoot: string,
|
|
184
|
+
file: string | undefined,
|
|
185
|
+
tableNames: Set<string>,
|
|
186
|
+
ops: Set<string>,
|
|
187
|
+
): string[] {
|
|
188
|
+
return dbTablesForText(sourceText(workspaceRoot, file), tableNames, ops);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function emittedEventsForFile(workspaceRoot: string, file: string | undefined): string[] {
|
|
192
|
+
const text = sourceText(workspaceRoot, file);
|
|
193
|
+
return uniqueSorted(
|
|
194
|
+
[...text.matchAll(/ctx\.emit\s*\(\s*["'`]([^"'`]+)["'`]/g)]
|
|
195
|
+
.map((match) => match[1] ?? ""),
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function buildIntegrations(classified: ClassifiedPackage[]): AgentIntegrationInfo[] {
|
|
200
|
+
const byAlias = new Map<string, AgentIntegrationInfo>();
|
|
201
|
+
for (const pkg of classified) {
|
|
202
|
+
const recipe = pkg.recipe ?? resolveByPackageName(pkg.api.name);
|
|
203
|
+
if (!recipe) {
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
const entry = byAlias.get(recipe.alias) ?? {
|
|
207
|
+
alias: recipe.alias,
|
|
208
|
+
packages: [],
|
|
209
|
+
secrets: [],
|
|
210
|
+
allowedContexts: recipe.contexts.allowed,
|
|
211
|
+
deniedContexts: recipe.contexts.denied,
|
|
212
|
+
};
|
|
213
|
+
entry.packages = uniqueSorted([...entry.packages, pkg.api.name]);
|
|
214
|
+
entry.secrets = uniqueSorted([
|
|
215
|
+
...entry.secrets,
|
|
216
|
+
...recipe.secrets.map((secret) => secret.envVar),
|
|
217
|
+
]);
|
|
218
|
+
byAlias.set(recipe.alias, entry);
|
|
219
|
+
}
|
|
220
|
+
return sorted([...byAlias.values()], (entry) => entry.alias);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function runtimeRules(): AgentRuntimeRule[] {
|
|
224
|
+
return [
|
|
225
|
+
{
|
|
226
|
+
context: "command",
|
|
227
|
+
allowed: ["ctx.db writes", "ctx.emit", "ctx.telemetry buffered events"],
|
|
228
|
+
forbidden: ["network packages", "ctx.secrets", "ctx.ai", "process.env", "filesystem access"],
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
context: "query",
|
|
232
|
+
allowed: ["ctx.db reads", "ctx.telemetry buffered events"],
|
|
233
|
+
forbidden: ["insert/update/delete", "ctx.emit", "ctx.secrets", "ctx.ai", "network integrations"],
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
context: "liveQuery",
|
|
237
|
+
allowed: ["ctx.db reads", "tenant-scoped subscriptions"],
|
|
238
|
+
forbidden: ["insert/update/delete", "ctx.emit", "ctx.secrets", "ctx.ai", "network integrations"],
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
context: "action",
|
|
242
|
+
allowed: ["ctx.secrets", "integrations", "ctx.ai", "ctx.db reads/writes", "network packages"],
|
|
243
|
+
forbidden: ["uncommitted transactional side effects"],
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
context: "workflow",
|
|
247
|
+
allowed: ["durable steps", "ctx.secrets", "integrations", "ctx.ai", "retries"],
|
|
248
|
+
forbidden: ["non-idempotent step behavior without guards"],
|
|
249
|
+
},
|
|
250
|
+
];
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function playbooks(): AgentPlaybook[] {
|
|
254
|
+
return [
|
|
255
|
+
{
|
|
256
|
+
title: "Choose the right workflow",
|
|
257
|
+
steps: [
|
|
258
|
+
"Run forge do \"<objective>\" --json when the next command is not obvious.",
|
|
259
|
+
"Use forge do fix --json for failures, forge do verify --json before handoff, and forge do connect-ui --json for frontend wiring.",
|
|
260
|
+
"Follow the returned plan, filesToInspect, risks, and nextAction before using lower-level commands directly.",
|
|
261
|
+
],
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
title: "Add a command",
|
|
265
|
+
steps: [
|
|
266
|
+
"Add a file under src/commands.",
|
|
267
|
+
"Declare auth with can(\"policy.name\") unless intentionally public/system.",
|
|
268
|
+
"Use ctx.db for transactional writes.",
|
|
269
|
+
"Use ctx.emit for side effects.",
|
|
270
|
+
"Run forge generate.",
|
|
271
|
+
"Run forge verify --strict.",
|
|
272
|
+
],
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
title: "Add a query",
|
|
276
|
+
steps: [
|
|
277
|
+
"Add a file under src/queries.",
|
|
278
|
+
"Keep it read-only.",
|
|
279
|
+
"Declare auth explicitly.",
|
|
280
|
+
"Run forge generate.",
|
|
281
|
+
"Run forge check.",
|
|
282
|
+
],
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
title: "Add a liveQuery",
|
|
286
|
+
steps: [
|
|
287
|
+
"Add a liveQuery under src/queries.",
|
|
288
|
+
"Keep it read-only and tenant-scoped when reading tenant tables.",
|
|
289
|
+
"Run forge generate.",
|
|
290
|
+
"Use forge inspect client --json to confirm client exposure.",
|
|
291
|
+
],
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
title: "Debug a stale liveQuery",
|
|
295
|
+
steps: [
|
|
296
|
+
"Run forge live status --json.",
|
|
297
|
+
"Run forge live invalidations list --json and confirm the table and tenant changed.",
|
|
298
|
+
"Run forge live debug <subscriptionId> --json when a subscription id is available.",
|
|
299
|
+
"Check that _forge_live_invalidations has revisions newer than the last sent snapshot.",
|
|
300
|
+
"Reconnect with Last-Event-ID or ?lastRevision=<revision> to verify resume behavior.",
|
|
301
|
+
],
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
title: "Add a table",
|
|
305
|
+
steps: [
|
|
306
|
+
"Edit src/forge/schema.ts.",
|
|
307
|
+
"Include tenantId for tenant-scoped data.",
|
|
308
|
+
"Run forge generate.",
|
|
309
|
+
"Run forge db diff.",
|
|
310
|
+
"Run forge verify --strict.",
|
|
311
|
+
],
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
title: "Scaffold a resource",
|
|
315
|
+
steps: [
|
|
316
|
+
"Run forge make resource <name> --fields name:type,status:enum(open,closed) --dry-run --json.",
|
|
317
|
+
"Review the plan and diagnostics.",
|
|
318
|
+
"Run forge make resource <name> --fields name:type --with-ui --yes when the resource should be visible in the web app.",
|
|
319
|
+
"Run forge generate.",
|
|
320
|
+
"Run forge verify --strict.",
|
|
321
|
+
],
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
title: "Apply a feature blueprint",
|
|
325
|
+
steps: [
|
|
326
|
+
"Write a JSON blueprint under .forge/blueprints.",
|
|
327
|
+
"Run forge feature validate <blueprint> --json.",
|
|
328
|
+
"Run forge feature plan <blueprint>.",
|
|
329
|
+
"Review the plan, impact, and risk.",
|
|
330
|
+
"Run forge feature apply <blueprint> --yes.",
|
|
331
|
+
"Run forge verify --strict.",
|
|
332
|
+
],
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
title: "Safely refactor a feature",
|
|
336
|
+
steps: [
|
|
337
|
+
"Run forge refactor rename field <table.field> <table.field> --dry-run --json.",
|
|
338
|
+
"Rename codemods are AST-aware for extract-action, rename field, and rename table.",
|
|
339
|
+
"Field renames are scoped to the target table, so tickets.priority only rewrites references linked to tickets.",
|
|
340
|
+
"Review filesToModify, migrationPlan, diagnostics, and risk.",
|
|
341
|
+
"Use --allow-high-risk only for intentional high-risk refactors.",
|
|
342
|
+
"Apply with forge refactor rename field <table.field> <table.field> --yes.",
|
|
343
|
+
"Run forge generate.",
|
|
344
|
+
"Run forge verify --strict.",
|
|
345
|
+
],
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
title: "Plan impact-based tests",
|
|
349
|
+
steps: [
|
|
350
|
+
"Run forge impact --changed --json.",
|
|
351
|
+
"Run forge test plan --changed --json.",
|
|
352
|
+
"Run forge test run --changed --timeout-ms 120000 --json for targeted checks.",
|
|
353
|
+
"Use forge verify --changed for the fast impact gate.",
|
|
354
|
+
"Run forge verify --strict before final handoff.",
|
|
355
|
+
],
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
title: "Repair a failing check",
|
|
359
|
+
steps: [
|
|
360
|
+
"Run forge test run --changed --json.",
|
|
361
|
+
"Run forge repair diagnose --from-last-test-run --json.",
|
|
362
|
+
"Review the failureKind, likelyCause, suggestedRepairs, and confidence.",
|
|
363
|
+
"Apply only high-confidence repairs automatically.",
|
|
364
|
+
"Run forge verify --changed.",
|
|
365
|
+
"Run forge verify --strict before final handoff.",
|
|
366
|
+
],
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
title: "Add a package",
|
|
370
|
+
steps: [
|
|
371
|
+
"Use forge add <alias>.",
|
|
372
|
+
"Do not install packages manually unless the architecture exception is intentional.",
|
|
373
|
+
"Run forge generate.",
|
|
374
|
+
"Run forge check.",
|
|
375
|
+
],
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
title: "Upgrade a package",
|
|
379
|
+
steps: [
|
|
380
|
+
"Run forge deps upgrade-plan <package> --to latest.",
|
|
381
|
+
"Read .forge/upgrades/.../plan.md.",
|
|
382
|
+
"If risk is high, inspect affected files and generated adapters before applying.",
|
|
383
|
+
"Apply with forge deps upgrade-apply <plan>.",
|
|
384
|
+
"Finish with forge verify --strict.",
|
|
385
|
+
],
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
title: "Debug a policy error",
|
|
389
|
+
steps: [
|
|
390
|
+
"Capture the traceId from the response or frontend.",
|
|
391
|
+
"Run forge telemetry inspect <traceId>.",
|
|
392
|
+
"Run forge policy simulate <policy> --role <role>.",
|
|
393
|
+
],
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
title: "Run dev",
|
|
397
|
+
steps: [
|
|
398
|
+
"Run forge dev for the full local loop: generated checks, API runtime, web app, DB, worker, watch, and startup URLs.",
|
|
399
|
+
"Run forge dev --once --json for a one-shot diagnostic cycle.",
|
|
400
|
+
"Use --api-only, --web-only, --no-watch, or --no-worker only when narrowing the loop intentionally.",
|
|
401
|
+
"When a web app exists, forge dev starts the API runtime and the web dev server together and prints both URLs.",
|
|
402
|
+
"Use generated client and React hooks through web/lib/forge.ts.",
|
|
403
|
+
],
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
title: "Add or update frontend",
|
|
407
|
+
steps: [
|
|
408
|
+
"Run forge make ui --framework vite --dry-run --json when the app does not have a web root.",
|
|
409
|
+
"Use web/lib/forge.ts as the generated client bridge.",
|
|
410
|
+
"Mount ForgeProvider once in the web app provider/layout layer; use devAuth for local development.",
|
|
411
|
+
"Use useQuery, useCommand, and useLiveQuery instead of raw /commands or /queries fetches.",
|
|
412
|
+
"Run forge generate so frontendGraph and agentContract include routes and bindings.",
|
|
413
|
+
"Run forge inspect capabilities --json to confirm UI actions map to runtime capabilities.",
|
|
414
|
+
"Run forge dev --once --json and forge doctor --json.",
|
|
415
|
+
],
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
title: "Self-host",
|
|
419
|
+
steps: [
|
|
420
|
+
"Run forge self-host compose.",
|
|
421
|
+
"Review deploy/.env.example.",
|
|
422
|
+
"Run forge self-host check.",
|
|
423
|
+
],
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
title: "Debug a production stack trace",
|
|
427
|
+
steps: [
|
|
428
|
+
"Run forge release inspect <releaseId> --json.",
|
|
429
|
+
"Run forge release sourcemaps symbolicate --input stacktrace.json --json.",
|
|
430
|
+
"Open the original source file and line from the symbolicated frame.",
|
|
431
|
+
"Use forge telemetry inspect <traceId> --with-release --json when a trace id is available.",
|
|
432
|
+
],
|
|
433
|
+
},
|
|
434
|
+
];
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function extractUserNotes(existing: string | null): string {
|
|
438
|
+
if (!existing) {
|
|
439
|
+
return DEFAULT_USER_NOTES;
|
|
440
|
+
}
|
|
441
|
+
const body = stripDeterministicHeader(existing);
|
|
442
|
+
const start = body.indexOf(AGENTS_USER_START);
|
|
443
|
+
const end = body.indexOf(AGENTS_USER_END);
|
|
444
|
+
if (start === -1 || end === -1 || end < start) {
|
|
445
|
+
return DEFAULT_USER_NOTES;
|
|
446
|
+
}
|
|
447
|
+
return body.slice(start + AGENTS_USER_START.length, end).trim() || DEFAULT_USER_NOTES;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function renderList(items: string[], empty = "none"): string {
|
|
451
|
+
if (items.length === 0) {
|
|
452
|
+
return `- ${empty}`;
|
|
453
|
+
}
|
|
454
|
+
return items.map((item) => `- ${item}`).join("\n");
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function runtimeSummaryFromBinding(binding: AgentFrontendRuntimeBindingInfo): AgentCapabilityMapEntry["runtime"] {
|
|
458
|
+
return {
|
|
459
|
+
kind: binding.kind,
|
|
460
|
+
name: binding.name,
|
|
461
|
+
hook: binding.hook,
|
|
462
|
+
http: binding.http,
|
|
463
|
+
...(binding.policy ? { policy: binding.policy } : {}),
|
|
464
|
+
tablesRead: binding.tablesRead,
|
|
465
|
+
tablesWritten: binding.tablesWritten,
|
|
466
|
+
emits: binding.emits,
|
|
467
|
+
dependencies: binding.dependencies,
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function runtimeEntriesWithoutFrontend(contract: AgentContract): AgentCapabilityMapEntry[] {
|
|
472
|
+
const entries: AgentCapabilityMapEntry[] = [];
|
|
473
|
+
for (const commandEntry of contract.commands) {
|
|
474
|
+
if (commandEntry.frontend.routes.length === 0 && commandEntry.frontend.components.length === 0) {
|
|
475
|
+
entries.push({
|
|
476
|
+
id: `runtime:command:${commandEntry.name}`,
|
|
477
|
+
status: "backend-only",
|
|
478
|
+
userAction: `Call command ${commandEntry.name}`,
|
|
479
|
+
runtime: {
|
|
480
|
+
kind: "command",
|
|
481
|
+
name: commandEntry.name,
|
|
482
|
+
hook: commandEntry.frontend.hook,
|
|
483
|
+
http: commandEntry.http,
|
|
484
|
+
...(commandEntry.policy ? { policy: commandEntry.policy } : {}),
|
|
485
|
+
tablesRead: commandEntry.tablesRead,
|
|
486
|
+
tablesWritten: commandEntry.tablesWritten,
|
|
487
|
+
emits: commandEntry.emits,
|
|
488
|
+
dependencies: [],
|
|
489
|
+
},
|
|
490
|
+
notes: ["Runtime entry is available to agents even though no frontend usage was detected."],
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
for (const queryEntry of contract.queries) {
|
|
495
|
+
if (queryEntry.frontend.routes.length === 0 && queryEntry.frontend.components.length === 0) {
|
|
496
|
+
entries.push({
|
|
497
|
+
id: `runtime:query:${queryEntry.name}`,
|
|
498
|
+
status: "backend-only",
|
|
499
|
+
userAction: `Read query ${queryEntry.name}`,
|
|
500
|
+
runtime: {
|
|
501
|
+
kind: "query",
|
|
502
|
+
name: queryEntry.name,
|
|
503
|
+
hook: queryEntry.frontend.hook,
|
|
504
|
+
http: queryEntry.http,
|
|
505
|
+
...(queryEntry.policy ? { policy: queryEntry.policy } : {}),
|
|
506
|
+
tablesRead: queryEntry.tablesRead,
|
|
507
|
+
tablesWritten: [],
|
|
508
|
+
emits: [],
|
|
509
|
+
dependencies: [],
|
|
510
|
+
},
|
|
511
|
+
notes: ["Runtime entry is available to agents even though no frontend usage was detected."],
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
for (const liveQueryEntry of contract.liveQueries) {
|
|
516
|
+
if (liveQueryEntry.frontend.routes.length === 0 && liveQueryEntry.frontend.components.length === 0) {
|
|
517
|
+
entries.push({
|
|
518
|
+
id: `runtime:liveQuery:${liveQueryEntry.name}`,
|
|
519
|
+
status: "backend-only",
|
|
520
|
+
userAction: `Subscribe to liveQuery ${liveQueryEntry.name}`,
|
|
521
|
+
runtime: {
|
|
522
|
+
kind: "liveQuery",
|
|
523
|
+
name: liveQueryEntry.name,
|
|
524
|
+
hook: liveQueryEntry.frontend.hook,
|
|
525
|
+
http: liveQueryEntry.http,
|
|
526
|
+
...(liveQueryEntry.policy ? { policy: liveQueryEntry.policy } : {}),
|
|
527
|
+
tablesRead: liveQueryEntry.tablesRead,
|
|
528
|
+
tablesWritten: [],
|
|
529
|
+
emits: [],
|
|
530
|
+
dependencies: liveQueryEntry.dependencies,
|
|
531
|
+
},
|
|
532
|
+
notes: ["Runtime entry is available to agents even though no frontend usage was detected."],
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
return entries;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function buildCapabilityMap(contract: AgentContract): AgentCapabilityMap {
|
|
540
|
+
const diagnostics: Diagnostic[] = [];
|
|
541
|
+
const coveredEntries: AgentCapabilityMapEntry[] = contract.frontend.routeBindings.map((binding) => ({
|
|
542
|
+
id: `ui:${binding.route ?? "route"}:${binding.kind}:${binding.name}:${binding.file}`,
|
|
543
|
+
status: "covered",
|
|
544
|
+
userAction: `${binding.route ?? "route"} uses ${binding.kind} ${binding.name}`,
|
|
545
|
+
ui: {
|
|
546
|
+
...(binding.route ? { route: binding.route } : {}),
|
|
547
|
+
...(binding.component ? { component: binding.component } : {}),
|
|
548
|
+
file: binding.file,
|
|
549
|
+
},
|
|
550
|
+
runtime: runtimeSummaryFromBinding(binding),
|
|
551
|
+
notes: ["Frontend route is connected to a generated Forge runtime hook."],
|
|
552
|
+
}));
|
|
553
|
+
|
|
554
|
+
const componentOnlyEntries = contract.frontend.componentBindings
|
|
555
|
+
.filter((binding) => !contract.frontend.routeBindings.some(
|
|
556
|
+
(routeBinding) =>
|
|
557
|
+
routeBinding.kind === binding.kind &&
|
|
558
|
+
routeBinding.name === binding.name &&
|
|
559
|
+
routeBinding.file === binding.file,
|
|
560
|
+
))
|
|
561
|
+
.map((binding) => ({
|
|
562
|
+
id: `component:${binding.component ?? "component"}:${binding.kind}:${binding.name}:${binding.file}`,
|
|
563
|
+
status: "covered" as const,
|
|
564
|
+
userAction: `${binding.component ?? "component"} uses ${binding.kind} ${binding.name}`,
|
|
565
|
+
ui: {
|
|
566
|
+
...(binding.component ? { component: binding.component } : {}),
|
|
567
|
+
file: binding.file,
|
|
568
|
+
},
|
|
569
|
+
runtime: runtimeSummaryFromBinding(binding),
|
|
570
|
+
notes: ["Frontend component is connected to a generated Forge runtime hook."],
|
|
571
|
+
}));
|
|
572
|
+
|
|
573
|
+
const rawFetchEntries: AgentCapabilityMapEntry[] = contract.frontend.clientBindings
|
|
574
|
+
.filter((binding) => binding.kind === "rawFetch")
|
|
575
|
+
.map((binding) => {
|
|
576
|
+
diagnostics.push(createDiagnostic({
|
|
577
|
+
severity: "warning",
|
|
578
|
+
code: "FORGE_CAPABILITY_RAW_RUNTIME_FETCH",
|
|
579
|
+
message: "frontend uses a raw Forge runtime endpoint instead of generated hooks",
|
|
580
|
+
file: binding.file,
|
|
581
|
+
fixHint: "Replace raw runtime fetches with useCommand, useQuery, or useLiveQuery through the local Forge bridge.",
|
|
582
|
+
suggestedCommands: ["forge do connect-ui --json", "forge inspect capabilities --json"],
|
|
583
|
+
docs: ["src/forge/_generated/capabilityMap.md", "src/forge/_generated/frontendGraph.json"],
|
|
584
|
+
}));
|
|
585
|
+
return {
|
|
586
|
+
id: `raw:${binding.file}:${binding.name}`,
|
|
587
|
+
status: "warning",
|
|
588
|
+
userAction: `Raw runtime fetch ${binding.name}`,
|
|
589
|
+
ui: {
|
|
590
|
+
...(binding.route ? { route: binding.route } : {}),
|
|
591
|
+
...(binding.component ? { component: binding.component } : {}),
|
|
592
|
+
file: binding.file,
|
|
593
|
+
},
|
|
594
|
+
notes: ["Raw runtime fetch detected; generated hook parity is not proven."],
|
|
595
|
+
};
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
const boundRoutes = new Set(contract.frontend.routeBindings.map((binding) => binding.route).filter(Boolean));
|
|
599
|
+
const routeOnlyEntries: AgentCapabilityMapEntry[] = contract.frontend.routes
|
|
600
|
+
.filter((route) => !boundRoutes.has(route.path))
|
|
601
|
+
.map((route) => ({
|
|
602
|
+
id: `route:${route.path}:${route.file}`,
|
|
603
|
+
status: "frontend-only",
|
|
604
|
+
userAction: `View route ${route.path}`,
|
|
605
|
+
ui: {
|
|
606
|
+
route: route.path,
|
|
607
|
+
file: route.file,
|
|
608
|
+
},
|
|
609
|
+
notes: ["Route has no detected Forge runtime binding. This is fine for static pages, but agents cannot infer a data/action capability from it."],
|
|
610
|
+
}));
|
|
611
|
+
|
|
612
|
+
const entries = sorted(
|
|
613
|
+
[
|
|
614
|
+
...coveredEntries,
|
|
615
|
+
...componentOnlyEntries,
|
|
616
|
+
...runtimeEntriesWithoutFrontend(contract),
|
|
617
|
+
...rawFetchEntries,
|
|
618
|
+
...routeOnlyEntries,
|
|
619
|
+
],
|
|
620
|
+
(entry) => entry.id,
|
|
621
|
+
);
|
|
622
|
+
return {
|
|
623
|
+
schemaVersion: "0.1.0",
|
|
624
|
+
generatorVersion: GENERATOR_VERSION,
|
|
625
|
+
project: contract.project,
|
|
626
|
+
summary: {
|
|
627
|
+
covered: entries.filter((entry) => entry.status === "covered").length,
|
|
628
|
+
backendOnly: entries.filter((entry) => entry.status === "backend-only").length,
|
|
629
|
+
frontendOnly: entries.filter((entry) => entry.status === "frontend-only").length,
|
|
630
|
+
warnings: entries.filter((entry) => entry.status === "warning").length,
|
|
631
|
+
},
|
|
632
|
+
entries,
|
|
633
|
+
diagnostics,
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function jsAccess(group: string, name: string): string {
|
|
638
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name)
|
|
639
|
+
? `api.${group}.${name}`
|
|
640
|
+
: `api.${group}[${JSON.stringify(name)}]`;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function frontendHookFor(kind: "command" | "query" | "liveQuery" | "action", name: string): string {
|
|
644
|
+
if (kind === "command") {
|
|
645
|
+
return `useCommand(${jsAccess("commands", name)})`;
|
|
646
|
+
}
|
|
647
|
+
if (kind === "query") {
|
|
648
|
+
return `useQuery(${jsAccess("queries", name)}, args)`;
|
|
649
|
+
}
|
|
650
|
+
if (kind === "liveQuery") {
|
|
651
|
+
return `useLiveQuery(${jsAccess("liveQueries", name)}, args)`;
|
|
652
|
+
}
|
|
653
|
+
return "no generated React hook; invoke from server/action code";
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
function httpEndpointFor(
|
|
657
|
+
kind: "command" | "query" | "liveQuery" | "action",
|
|
658
|
+
name: string,
|
|
659
|
+
): AgentHttpEndpointInfo {
|
|
660
|
+
const encoded = encodeURIComponent(name);
|
|
661
|
+
if (kind === "liveQuery") {
|
|
662
|
+
return {
|
|
663
|
+
method: "GET",
|
|
664
|
+
path: `/live/${encoded}`,
|
|
665
|
+
exampleUrl: `/live/${encoded}?args={}`,
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
const collection = kind === "action" ? "actions" : kind === "query" ? "queries" : "commands";
|
|
669
|
+
return {
|
|
670
|
+
method: "POST",
|
|
671
|
+
path: `/${collection}/${encoded}`,
|
|
672
|
+
exampleBody: { args: {} },
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function frontendUsageFor(
|
|
677
|
+
frontendGraph: FrontendGraph,
|
|
678
|
+
kind: "command" | "query" | "liveQuery" | "action",
|
|
679
|
+
name: string,
|
|
680
|
+
): AgentFrontendUsageInfo {
|
|
681
|
+
if (kind === "action") {
|
|
682
|
+
return {
|
|
683
|
+
hook: frontendHookFor(kind, name),
|
|
684
|
+
routes: [],
|
|
685
|
+
components: [],
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
const bindings = frontendGraph.clientBindings.filter(
|
|
689
|
+
(binding) => binding.kind === kind && binding.name === name,
|
|
690
|
+
);
|
|
691
|
+
return {
|
|
692
|
+
hook: frontendHookFor(kind, name),
|
|
693
|
+
routes: uniqueSorted(bindings.map((binding) => binding.route ?? "")),
|
|
694
|
+
components: uniqueSorted(bindings.map((binding) => binding.component ?? "")),
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
function frontendRuntimeBindingFor(
|
|
699
|
+
binding: FrontendGraph["clientBindings"][number],
|
|
700
|
+
entries: {
|
|
701
|
+
commands: AgentContract["commands"];
|
|
702
|
+
queries: AgentContract["queries"];
|
|
703
|
+
liveQueries: AgentContract["liveQueries"];
|
|
704
|
+
},
|
|
705
|
+
): AgentFrontendRuntimeBindingInfo | null {
|
|
706
|
+
if (binding.kind === "rawFetch") {
|
|
707
|
+
return null;
|
|
708
|
+
}
|
|
709
|
+
if (binding.kind === "command") {
|
|
710
|
+
const entry = entries.commands.find((candidate) => candidate.name === binding.name);
|
|
711
|
+
if (!entry) return null;
|
|
712
|
+
return {
|
|
713
|
+
kind: "command",
|
|
714
|
+
name: binding.name,
|
|
715
|
+
file: binding.file,
|
|
716
|
+
...(binding.route ? { route: binding.route } : {}),
|
|
717
|
+
...(binding.component ? { component: binding.component } : {}),
|
|
718
|
+
hook: entry.frontend.hook,
|
|
719
|
+
http: entry.http,
|
|
720
|
+
...(entry.policy ? { policy: entry.policy } : {}),
|
|
721
|
+
tablesRead: entry.tablesRead,
|
|
722
|
+
tablesWritten: entry.tablesWritten,
|
|
723
|
+
emits: entry.emits,
|
|
724
|
+
dependencies: [],
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
if (binding.kind === "query") {
|
|
728
|
+
const entry = entries.queries.find((candidate) => candidate.name === binding.name);
|
|
729
|
+
if (!entry) return null;
|
|
730
|
+
return {
|
|
731
|
+
kind: "query",
|
|
732
|
+
name: binding.name,
|
|
733
|
+
file: binding.file,
|
|
734
|
+
...(binding.route ? { route: binding.route } : {}),
|
|
735
|
+
...(binding.component ? { component: binding.component } : {}),
|
|
736
|
+
hook: entry.frontend.hook,
|
|
737
|
+
http: entry.http,
|
|
738
|
+
...(entry.policy ? { policy: entry.policy } : {}),
|
|
739
|
+
tablesRead: entry.tablesRead,
|
|
740
|
+
tablesWritten: [],
|
|
741
|
+
emits: [],
|
|
742
|
+
dependencies: [],
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
const entry = entries.liveQueries.find((candidate) => candidate.name === binding.name);
|
|
746
|
+
if (!entry) return null;
|
|
747
|
+
return {
|
|
748
|
+
kind: "liveQuery",
|
|
749
|
+
name: binding.name,
|
|
750
|
+
file: binding.file,
|
|
751
|
+
...(binding.route ? { route: binding.route } : {}),
|
|
752
|
+
...(binding.component ? { component: binding.component } : {}),
|
|
753
|
+
hook: entry.frontend.hook,
|
|
754
|
+
http: entry.http,
|
|
755
|
+
...(entry.policy ? { policy: entry.policy } : {}),
|
|
756
|
+
tablesRead: entry.tablesRead,
|
|
757
|
+
tablesWritten: [],
|
|
758
|
+
emits: [],
|
|
759
|
+
dependencies: entry.dependencies,
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
function frontendRuntimeBindings(
|
|
764
|
+
frontendGraph: FrontendGraph,
|
|
765
|
+
entries: {
|
|
766
|
+
commands: AgentContract["commands"];
|
|
767
|
+
queries: AgentContract["queries"];
|
|
768
|
+
liveQueries: AgentContract["liveQueries"];
|
|
769
|
+
},
|
|
770
|
+
): AgentFrontendRuntimeBindingInfo[] {
|
|
771
|
+
return uniqueSorted(
|
|
772
|
+
frontendGraph.clientBindings
|
|
773
|
+
.map((binding) => frontendRuntimeBindingFor(binding, entries))
|
|
774
|
+
.filter((binding): binding is AgentFrontendRuntimeBindingInfo => binding !== null)
|
|
775
|
+
.map((binding) => JSON.stringify(binding)),
|
|
776
|
+
).map((binding) => JSON.parse(binding) as AgentFrontendRuntimeBindingInfo);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
export function buildAgentContractArtifacts(
|
|
780
|
+
input: AgentContractInput,
|
|
781
|
+
): AgentContractArtifacts {
|
|
782
|
+
const project = readPackageInfo(input.workspaceRoot);
|
|
783
|
+
const tenantTables = new Map(
|
|
784
|
+
input.tenantScope.tables.map((table) => [table.table, table.tenantIdColumn]),
|
|
785
|
+
);
|
|
786
|
+
const commandAuth = new Map(
|
|
787
|
+
input.policyRegistry.commandAuth.map((binding) => [binding.commandName, binding.auth]),
|
|
788
|
+
);
|
|
789
|
+
const queryAuth = new Map(
|
|
790
|
+
input.policyRegistry.queryAuth.map((binding) => [binding.queryName, binding.auth]),
|
|
791
|
+
);
|
|
792
|
+
const liveQueryPolicy = new Map(
|
|
793
|
+
input.liveQueryRegistry.liveQueries.map((entry) => [entry.name, entry.policy]),
|
|
794
|
+
);
|
|
795
|
+
|
|
796
|
+
const runtimeEntries = new Map(input.runtimeGraph.entries.map((entry) => [entry.name, entry]));
|
|
797
|
+
const tableNames = new Set(input.dataGraph.tables.map((table) => table.name));
|
|
798
|
+
const commandInfos: AgentContract["commands"] = sorted(Object.keys(input.apiSurface.commands), (name) => name).map((name) => {
|
|
799
|
+
const entry = runtimeEntries.get(name);
|
|
800
|
+
const file = entry?.file ?? "";
|
|
801
|
+
return {
|
|
802
|
+
name,
|
|
803
|
+
file,
|
|
804
|
+
policy: authPolicy(commandAuth.get(name)),
|
|
805
|
+
tablesRead: dbTablesForFile(input.workspaceRoot, file, tableNames, DB_READ_OPS),
|
|
806
|
+
tablesWritten: dbTablesForFile(input.workspaceRoot, file, tableNames, DB_WRITE_OPS),
|
|
807
|
+
emits: emittedEventsForFile(input.workspaceRoot, file),
|
|
808
|
+
allowedPackages: entry ? packageNamesForModule(input.appGraph, entry.moduleId) : [],
|
|
809
|
+
forbiddenCapabilities: forbiddenForContext(input.classified, "command"),
|
|
810
|
+
http: httpEndpointFor("command", name),
|
|
811
|
+
frontend: frontendUsageFor(input.frontendGraph, "command", name),
|
|
812
|
+
};
|
|
813
|
+
});
|
|
814
|
+
const queryInfos: AgentContract["queries"] = sorted(input.queryRegistry.queries, (query) => query.name).map((query) => ({
|
|
815
|
+
name: query.name,
|
|
816
|
+
file: query.file,
|
|
817
|
+
policy: authPolicy(queryAuth.get(query.name)),
|
|
818
|
+
readOnly: true,
|
|
819
|
+
tenantScoped: input.tenantScope.tables.length > 0,
|
|
820
|
+
tablesRead: dbTablesForFile(input.workspaceRoot, query.file, tableNames, DB_READ_OPS),
|
|
821
|
+
allowedPackages: packageNamesForModule(input.appGraph, query.moduleId),
|
|
822
|
+
forbiddenCapabilities: forbiddenForContext(input.classified, "query"),
|
|
823
|
+
http: httpEndpointFor("query", query.name),
|
|
824
|
+
frontend: frontendUsageFor(input.frontendGraph, "query", query.name),
|
|
825
|
+
}));
|
|
826
|
+
const liveQueryInfos: AgentContract["liveQueries"] = sorted(input.liveQueryRegistry.liveQueries, (liveQuery) => liveQuery.name).map(
|
|
827
|
+
(liveQuery) => {
|
|
828
|
+
const tablesRead = dbTablesForFile(input.workspaceRoot, liveQuery.file, tableNames, DB_READ_OPS);
|
|
829
|
+
return {
|
|
830
|
+
name: liveQuery.name,
|
|
831
|
+
file: liveQuery.file,
|
|
832
|
+
policy: liveQueryPolicy.get(liveQuery.name),
|
|
833
|
+
tablesRead,
|
|
834
|
+
dependencies: (tablesRead.length > 0 ? tablesRead : input.tenantScope.tables.map((table) => table.table)).map((tableName) => ({
|
|
835
|
+
table: tableName,
|
|
836
|
+
scope: tenantTables.has(tableName) ? "tenant" as const : "global" as const,
|
|
837
|
+
})),
|
|
838
|
+
allowedPackages: packageNamesForModule(input.appGraph, liveQuery.moduleId),
|
|
839
|
+
forbiddenCapabilities: forbiddenForContext(input.classified, "liveQuery"),
|
|
840
|
+
http: httpEndpointFor("liveQuery", liveQuery.name),
|
|
841
|
+
frontend: frontendUsageFor(input.frontendGraph, "liveQuery", liveQuery.name),
|
|
842
|
+
};
|
|
843
|
+
},
|
|
844
|
+
);
|
|
845
|
+
const fullStackBindings = frontendRuntimeBindings(input.frontendGraph, {
|
|
846
|
+
commands: commandInfos,
|
|
847
|
+
queries: queryInfos,
|
|
848
|
+
liveQueries: liveQueryInfos,
|
|
849
|
+
});
|
|
850
|
+
const contract: AgentContract = {
|
|
851
|
+
schemaVersion: "0.1.0",
|
|
852
|
+
generatorVersion: GENERATOR_VERSION,
|
|
853
|
+
project: {
|
|
854
|
+
name: project.name,
|
|
855
|
+
type: "forgeos-app",
|
|
856
|
+
...(project.template ? { template: project.template } : {}),
|
|
857
|
+
},
|
|
858
|
+
commands: commandInfos,
|
|
859
|
+
queries: queryInfos,
|
|
860
|
+
liveQueries: liveQueryInfos,
|
|
861
|
+
actions: sorted(
|
|
862
|
+
input.runtimeGraph.entries.filter((entry) => entry.kind === "action"),
|
|
863
|
+
(entry) => entry.name,
|
|
864
|
+
).map((entry) => ({
|
|
865
|
+
name: entry.name,
|
|
866
|
+
file: entry.file,
|
|
867
|
+
allowedPackages: packageNamesForModule(input.appGraph, entry.moduleId),
|
|
868
|
+
forbiddenCapabilities: [],
|
|
869
|
+
allowedCapabilities: ["network", "secrets", "ai", "db"],
|
|
870
|
+
http: httpEndpointFor("action", entry.name),
|
|
871
|
+
frontend: frontendUsageFor(input.frontendGraph, "action", entry.name),
|
|
872
|
+
})),
|
|
873
|
+
workflows: sorted(input.workflowRegistry.workflows, (workflow) => workflow.name).map(
|
|
874
|
+
(workflow) => ({
|
|
875
|
+
name: workflow.name,
|
|
876
|
+
file: workflow.file,
|
|
877
|
+
trigger: workflow.triggerEventType,
|
|
878
|
+
steps: workflow.steps
|
|
879
|
+
.slice()
|
|
880
|
+
.sort((a, b) => a.index - b.index)
|
|
881
|
+
.map((step) => step.name),
|
|
882
|
+
}),
|
|
883
|
+
),
|
|
884
|
+
data: {
|
|
885
|
+
tables: sorted(input.dataGraph.tables, (table) => table.name).map((table) => ({
|
|
886
|
+
name: table.name,
|
|
887
|
+
file: table.file,
|
|
888
|
+
tenantScoped: tenantTables.has(table.name),
|
|
889
|
+
...(tenantTables.has(table.name) ? { tenantField: tenantTables.get(table.name) } : {}),
|
|
890
|
+
fields: uniqueSorted(table.fields.map((field) => field.name)),
|
|
891
|
+
})),
|
|
892
|
+
},
|
|
893
|
+
policies: sorted(input.policyRegistry.policies, (policy) => policy.name).map((policy) => ({
|
|
894
|
+
name: policy.name,
|
|
895
|
+
kind: policy.kind,
|
|
896
|
+
roles: uniqueSorted(policy.roles),
|
|
897
|
+
file: policy.file,
|
|
898
|
+
})),
|
|
899
|
+
packages: sorted(input.packageGraph.packages, (pkg) => pkg.name).map((pkg) => {
|
|
900
|
+
const classified = input.classified.find((entry) => entry.api.name === pkg.name);
|
|
901
|
+
return {
|
|
902
|
+
name: pkg.name,
|
|
903
|
+
version: pkg.version,
|
|
904
|
+
allowedContexts: classified?.classification.compatible ?? [],
|
|
905
|
+
deniedContexts: classified?.classification.incompatible ?? [],
|
|
906
|
+
};
|
|
907
|
+
}),
|
|
908
|
+
integrations: buildIntegrations(input.classified),
|
|
909
|
+
secrets: sorted(input.secretRegistry.secrets, (secret) => secret.name).map((secret) => ({
|
|
910
|
+
name: secret.name,
|
|
911
|
+
integration: secret.integration,
|
|
912
|
+
required: secret.required,
|
|
913
|
+
public: secret.public,
|
|
914
|
+
allowedContexts: secret.allowedContexts,
|
|
915
|
+
})),
|
|
916
|
+
telemetry: {
|
|
917
|
+
events: uniqueSorted(input.telemetryRegistry.events.map((event) => event.name)),
|
|
918
|
+
sinks: uniqueSorted(input.telemetrySinks.sinks.map((sink) => sink.kind)),
|
|
919
|
+
},
|
|
920
|
+
ai: {
|
|
921
|
+
providers: uniqueSorted(input.aiRegistry.providers.map((provider) => provider.id)),
|
|
922
|
+
generations: input.aiRegistry.generations
|
|
923
|
+
.map((generation) => ({
|
|
924
|
+
provider: generation.provider,
|
|
925
|
+
model: generation.model,
|
|
926
|
+
method: generation.method,
|
|
927
|
+
file: generation.file,
|
|
928
|
+
...(generation.purpose ? { purpose: generation.purpose } : {}),
|
|
929
|
+
}))
|
|
930
|
+
.sort((a, b) => `${a.file}:${a.method}:${a.model}`.localeCompare(`${b.file}:${b.method}:${b.model}`)),
|
|
931
|
+
},
|
|
932
|
+
client: {
|
|
933
|
+
queries: input.clientManifest.queries,
|
|
934
|
+
commands: input.clientManifest.commands,
|
|
935
|
+
liveQueries: input.clientManifest.liveQueries,
|
|
936
|
+
reactHooks: input.clientManifest.react.hooks,
|
|
937
|
+
transport: input.clientManifest.transport,
|
|
938
|
+
},
|
|
939
|
+
frontend: {
|
|
940
|
+
present: input.frontendGraph.present,
|
|
941
|
+
framework: input.frontendGraph.framework,
|
|
942
|
+
...(input.frontendGraph.root ? { root: input.frontendGraph.root } : {}),
|
|
943
|
+
...(input.frontendGraph.dev ? { dev: input.frontendGraph.dev } : {}),
|
|
944
|
+
routes: input.frontendGraph.routes,
|
|
945
|
+
components: input.frontendGraph.components,
|
|
946
|
+
providers: input.frontendGraph.providers,
|
|
947
|
+
bridgeFiles: input.frontendGraph.bridgeFiles,
|
|
948
|
+
webManifest: input.frontendGraph.webManifest,
|
|
949
|
+
clientBindings: input.frontendGraph.clientBindings,
|
|
950
|
+
runtimeEndpoints: [
|
|
951
|
+
...sorted(Object.keys(input.apiSurface.commands), (name) => name).map((name) => ({
|
|
952
|
+
kind: "command" as const,
|
|
953
|
+
name,
|
|
954
|
+
http: httpEndpointFor("command", name),
|
|
955
|
+
frontend: frontendUsageFor(input.frontendGraph, "command", name),
|
|
956
|
+
})),
|
|
957
|
+
...sorted(input.queryRegistry.queries, (query) => query.name).map((query) => ({
|
|
958
|
+
kind: "query" as const,
|
|
959
|
+
name: query.name,
|
|
960
|
+
http: httpEndpointFor("query", query.name),
|
|
961
|
+
frontend: frontendUsageFor(input.frontendGraph, "query", query.name),
|
|
962
|
+
})),
|
|
963
|
+
...sorted(input.liveQueryRegistry.liveQueries, (liveQuery) => liveQuery.name).map(
|
|
964
|
+
(liveQuery) => ({
|
|
965
|
+
kind: "liveQuery" as const,
|
|
966
|
+
name: liveQuery.name,
|
|
967
|
+
http: httpEndpointFor("liveQuery", liveQuery.name),
|
|
968
|
+
frontend: frontendUsageFor(input.frontendGraph, "liveQuery", liveQuery.name),
|
|
969
|
+
}),
|
|
970
|
+
),
|
|
971
|
+
].sort((a, b) => `${a.kind}:${a.name}`.localeCompare(`${b.kind}:${b.name}`)),
|
|
972
|
+
routeBindings: fullStackBindings
|
|
973
|
+
.filter((binding) => binding.route)
|
|
974
|
+
.sort((a, b) => `${a.route}:${a.kind}:${a.name}:${a.file}`.localeCompare(`${b.route}:${b.kind}:${b.name}:${b.file}`)),
|
|
975
|
+
componentBindings: fullStackBindings
|
|
976
|
+
.filter((binding) => binding.component)
|
|
977
|
+
.sort((a, b) => `${a.component}:${a.kind}:${a.name}:${a.file}`.localeCompare(`${b.component}:${b.kind}:${b.name}:${b.file}`)),
|
|
978
|
+
diagnostics: input.frontendGraph.diagnostics,
|
|
979
|
+
},
|
|
980
|
+
auth: {
|
|
981
|
+
modes: ["dev-headers", "jwt", "oidc", "disabled"],
|
|
982
|
+
defaultMode: "dev-headers",
|
|
983
|
+
productionDefaultAllowed: false,
|
|
984
|
+
bearerTokenHeader: "Authorization",
|
|
985
|
+
env: {
|
|
986
|
+
mode: AUTH_ENV.mode,
|
|
987
|
+
issuer: AUTH_ENV.issuer,
|
|
988
|
+
audience: AUTH_ENV.audience,
|
|
989
|
+
jwksUri: AUTH_ENV.jwksUri,
|
|
990
|
+
algorithms: AUTH_ENV.algorithms,
|
|
991
|
+
},
|
|
992
|
+
claims: DEFAULT_AUTH_CLAIMS,
|
|
993
|
+
requiresTenant: input.tenantScope.tables.length > 0,
|
|
994
|
+
},
|
|
995
|
+
deploy: {
|
|
996
|
+
selfHost: true,
|
|
997
|
+
files: [
|
|
998
|
+
"deploy/docker-compose.yml",
|
|
999
|
+
"deploy/.env.example",
|
|
1000
|
+
"deploy/deployManifest.json",
|
|
1001
|
+
],
|
|
1002
|
+
},
|
|
1003
|
+
rules: runtimeRules(),
|
|
1004
|
+
playbooks: playbooks(),
|
|
1005
|
+
commandsToRun: {
|
|
1006
|
+
beforeEditing: ["forge do inspect --json", "forge dev --once --json", "forge inspect all --json", "forge check --json"],
|
|
1007
|
+
afterEditing: ["forge generate", "forge check", "forge verify --standard", "forge verify --strict"],
|
|
1008
|
+
dev: ["forge dev", "forge dev --once --json", "forge do fix --json", "forge do verify --json", "forge dev --api-only", "forge dev --web-only"],
|
|
1009
|
+
},
|
|
1010
|
+
};
|
|
1011
|
+
|
|
1012
|
+
const existingAgentsPath = join(input.workspaceRoot, "AGENTS.md");
|
|
1013
|
+
const existingAgents = nodeFileSystem.exists(existingAgentsPath)
|
|
1014
|
+
? (nodeFileSystem.readText(existingAgentsPath) ?? "")
|
|
1015
|
+
: null;
|
|
1016
|
+
const userNotes = extractUserNotes(existingAgents);
|
|
1017
|
+
const agentsMd = renderAgentsMd(contract, userNotes);
|
|
1018
|
+
const capabilityMap = buildCapabilityMap(contract);
|
|
1019
|
+
const capabilityMapMd = renderCapabilityMapMd(capabilityMap);
|
|
1020
|
+
const appMapMd = renderAppMapMd(contract);
|
|
1021
|
+
const runtimeRulesMd = renderRuntimeRulesMd(contract.rules);
|
|
1022
|
+
const operationPlaybooksMd = renderOperationPlaybooksMd(contract.playbooks);
|
|
1023
|
+
const agentQuickstartMd = renderAgentQuickstartMd();
|
|
1024
|
+
const diagnostics = scanAgentContractForLeaks(contract, [
|
|
1025
|
+
agentsMd,
|
|
1026
|
+
capabilityMapMd,
|
|
1027
|
+
appMapMd,
|
|
1028
|
+
runtimeRulesMd,
|
|
1029
|
+
operationPlaybooksMd,
|
|
1030
|
+
agentQuickstartMd,
|
|
1031
|
+
]);
|
|
1032
|
+
|
|
1033
|
+
return {
|
|
1034
|
+
contract,
|
|
1035
|
+
capabilityMap,
|
|
1036
|
+
agentsMd,
|
|
1037
|
+
appMapMd,
|
|
1038
|
+
capabilityMapMd,
|
|
1039
|
+
runtimeRulesMd,
|
|
1040
|
+
operationPlaybooksMd,
|
|
1041
|
+
agentQuickstartMd,
|
|
1042
|
+
diagnostics: [...diagnostics, ...capabilityMap.diagnostics],
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
function scanAgentContractForLeaks(contract: AgentContract, markdown: string[]): Diagnostic[] {
|
|
1047
|
+
const serialized = `${canonicalJson(contract)}\n${markdown.join("\n")}`;
|
|
1048
|
+
const scan = secretLeakScan(serialized, { includeHighEntropy: false });
|
|
1049
|
+
if (!scan.hasLeak) {
|
|
1050
|
+
return [];
|
|
1051
|
+
}
|
|
1052
|
+
return [
|
|
1053
|
+
createDiagnostic({
|
|
1054
|
+
severity: "error",
|
|
1055
|
+
code: "FORGE_AGENT_CONTRACT_SECRET_LEAK",
|
|
1056
|
+
message: `agent contract contains secret-like material: ${uniqueSorted(scan.matches).join(", ")}`,
|
|
1057
|
+
}),
|
|
1058
|
+
];
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
export function serializeAgentContractJson(contract: AgentContract): string {
|
|
1062
|
+
return serializeCanonical(contract);
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
export function serializeAgentContractTs(contract: AgentContract): string {
|
|
1066
|
+
const parsed = JSON.parse(serializeAgentContractJson(contract)) as unknown;
|
|
1067
|
+
return `export const agentContract = ${JSON.stringify(parsed, null, 2)} as const;\n`;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
export function serializeCapabilityMapJson(capabilityMap: AgentCapabilityMap): string {
|
|
1071
|
+
return serializeCanonical(capabilityMap);
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
export function serializeCapabilityMapTs(capabilityMap: AgentCapabilityMap): string {
|
|
1075
|
+
const parsed = JSON.parse(serializeCapabilityMapJson(capabilityMap)) as unknown;
|
|
1076
|
+
return `export const capabilityMap = ${JSON.stringify(parsed, null, 2)} as const;\n`;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
function renderAgentsMd(contract: AgentContract, userNotes: string): string {
|
|
1080
|
+
const tenantTables = contract.data.tables
|
|
1081
|
+
.filter((table) => table.tenantScoped)
|
|
1082
|
+
.map((table) => `${table.name} via ${table.tenantField}`);
|
|
1083
|
+
const policies = contract.policies.map((policy) =>
|
|
1084
|
+
`${policy.name}: ${policy.roles.length > 0 ? policy.roles.join(", ") : policy.kind}`,
|
|
1085
|
+
);
|
|
1086
|
+
const secrets = contract.secrets.map((secret) => `${secret.name}${secret.required ? " (required)" : " (optional)"}`);
|
|
1087
|
+
|
|
1088
|
+
return normalizeNewlines(`# AGENTS.md
|
|
1089
|
+
|
|
1090
|
+
<!-- forge-generated:start -->
|
|
1091
|
+
|
|
1092
|
+
## Project
|
|
1093
|
+
|
|
1094
|
+
This is a ForgeOS application named \`${contract.project.name}\`.
|
|
1095
|
+
|
|
1096
|
+
## Required workflow
|
|
1097
|
+
|
|
1098
|
+
Before editing:
|
|
1099
|
+
|
|
1100
|
+
\`\`\`bash
|
|
1101
|
+
forge do inspect --json
|
|
1102
|
+
forge dev --once --json
|
|
1103
|
+
forge inspect all --json
|
|
1104
|
+
forge check --json
|
|
1105
|
+
\`\`\`
|
|
1106
|
+
|
|
1107
|
+
After editing:
|
|
1108
|
+
|
|
1109
|
+
\`\`\`bash
|
|
1110
|
+
forge generate
|
|
1111
|
+
forge check
|
|
1112
|
+
forge verify --strict
|
|
1113
|
+
\`\`\`
|
|
1114
|
+
|
|
1115
|
+
## Do not edit
|
|
1116
|
+
|
|
1117
|
+
Do not:
|
|
1118
|
+
|
|
1119
|
+
- \`src/forge/_generated/**\`
|
|
1120
|
+
- \`forge.lock\`
|
|
1121
|
+
- \`deploy/docker-compose.yml\`, unless changing deployment config intentionally
|
|
1122
|
+
|
|
1123
|
+
Template apps may ignore \`src/forge/_generated/**\` and \`forge.lock\` in git to reduce visual noise. Recreate them with \`forge generate\` before checking, testing, or handing work off.
|
|
1124
|
+
|
|
1125
|
+
## Runtime model
|
|
1126
|
+
|
|
1127
|
+
- Commands are transactional writes.
|
|
1128
|
+
- Queries and liveQueries are read-only.
|
|
1129
|
+
- Actions perform side effects after commit.
|
|
1130
|
+
- Workflows orchestrate durable steps.
|
|
1131
|
+
- Production liveQuery uses a durable invalidation log; polling/notify are wakeups only.
|
|
1132
|
+
- Production API calls use \`Authorization: Bearer <JWT>\` in \`jwt\` or \`oidc\` auth mode.
|
|
1133
|
+
- \`dev-headers\` auth is for \`forge dev\`, tests, and local agent workflows only.
|
|
1134
|
+
- AI is only allowed in actions, workflows, endpoints, and server code.
|
|
1135
|
+
- Secrets are accessed through \`ctx.secrets\`.
|
|
1136
|
+
|
|
1137
|
+
## Runtime rules
|
|
1138
|
+
|
|
1139
|
+
- Do not import network packages inside \`command\`, \`query\`, or \`liveQuery\`.
|
|
1140
|
+
- Do not use \`process.env\` directly.
|
|
1141
|
+
- Do not access cross-tenant data.
|
|
1142
|
+
- Commands must use \`ctx.emit\` for side effects.
|
|
1143
|
+
- Actions and workflows handle side effects after commit.
|
|
1144
|
+
- Do not rely on in-memory Pub/Sub as the source of truth for liveQuery invalidation.
|
|
1145
|
+
|
|
1146
|
+
## Useful commands
|
|
1147
|
+
|
|
1148
|
+
\`\`\`bash
|
|
1149
|
+
forge do "<objective>" --json
|
|
1150
|
+
forge do fix --json
|
|
1151
|
+
forge do verify --json
|
|
1152
|
+
forge dev --once --json
|
|
1153
|
+
forge dev
|
|
1154
|
+
forge inspect app --json
|
|
1155
|
+
forge inspect all --json
|
|
1156
|
+
forge inspect frontend --json
|
|
1157
|
+
forge inspect capabilities --json
|
|
1158
|
+
forge auth check --json
|
|
1159
|
+
forge inspect runtime-matrix --json
|
|
1160
|
+
forge inspect policies --json
|
|
1161
|
+
forge inspect client --json
|
|
1162
|
+
forge inspect live-production --json
|
|
1163
|
+
forge live status --json
|
|
1164
|
+
forge doctor
|
|
1165
|
+
forge doctor windows --json
|
|
1166
|
+
forge setup windows --json
|
|
1167
|
+
forge agent print-context --json
|
|
1168
|
+
forge verify --smoke
|
|
1169
|
+
forge verify --standard
|
|
1170
|
+
forge verify --strict
|
|
1171
|
+
\`\`\`
|
|
1172
|
+
|
|
1173
|
+
## Data
|
|
1174
|
+
|
|
1175
|
+
Tenant-scoped tables:
|
|
1176
|
+
|
|
1177
|
+
${renderList(tenantTables)}
|
|
1178
|
+
|
|
1179
|
+
## Policies
|
|
1180
|
+
|
|
1181
|
+
${renderList(policies)}
|
|
1182
|
+
|
|
1183
|
+
## Secrets
|
|
1184
|
+
|
|
1185
|
+
${renderList(secrets)}
|
|
1186
|
+
|
|
1187
|
+
## Auth
|
|
1188
|
+
|
|
1189
|
+
- Modes: ${contract.auth.modes.join(", ")}
|
|
1190
|
+
- Production auth: \`jwt\` or \`oidc\`
|
|
1191
|
+
- Bearer header: \`${contract.auth.bearerTokenHeader}: Bearer <token>\`
|
|
1192
|
+
- Tenant claim: \`${contract.auth.claims.tenantId ?? "not configured"}\`
|
|
1193
|
+
|
|
1194
|
+
## Frontend
|
|
1195
|
+
|
|
1196
|
+
- Present: ${contract.frontend.present ? "yes" : "no"}
|
|
1197
|
+
- Framework: ${contract.frontend.framework}
|
|
1198
|
+
${contract.frontend.dev ? `- Web URL: ${contract.frontend.dev.url}
|
|
1199
|
+
- API URL env: \`${contract.frontend.dev.apiUrlEnv}\`
|
|
1200
|
+
- Web bridge valid: ${contract.frontend.webManifest.bridge.valid ? "yes" : "no"}
|
|
1201
|
+
- Client bridge: ${contract.frontend.bridgeFiles.length > 0 ? contract.frontend.bridgeFiles.map((file) => `\`${file}\``).join(", ") : "missing"}` : "- Web URL: none"}
|
|
1202
|
+
- Routes: ${contract.frontend.routes.length}
|
|
1203
|
+
- Components: ${contract.frontend.components.length}
|
|
1204
|
+
- Client bindings: ${contract.frontend.clientBindings.length}
|
|
1205
|
+
- Runtime endpoints: ${contract.frontend.runtimeEndpoints.length}
|
|
1206
|
+
- Full-stack route bindings: ${contract.frontend.routeBindings.length}
|
|
1207
|
+
|
|
1208
|
+
Rules:
|
|
1209
|
+
|
|
1210
|
+
- Use the local \`web/**/lib/forge.ts\` bridge to generated hooks.
|
|
1211
|
+
- Mount \`<ForgeProvider devAuth>\` in local development.
|
|
1212
|
+
- Use \`useQuery\`, \`useCommand\`, and \`useLiveQuery\` instead of raw Forge endpoint fetches in React components.
|
|
1213
|
+
- Keep frontend routes reflected in \`src/forge/_generated/frontendGraph.json\`.
|
|
1214
|
+
|
|
1215
|
+
## Common tasks
|
|
1216
|
+
|
|
1217
|
+
### Choose the right workflow
|
|
1218
|
+
|
|
1219
|
+
Use:
|
|
1220
|
+
|
|
1221
|
+
\`\`\`bash
|
|
1222
|
+
forge do "<objective>" --json
|
|
1223
|
+
forge do fix --json
|
|
1224
|
+
forge do connect-ui --json
|
|
1225
|
+
forge do verify --json
|
|
1226
|
+
\`\`\`
|
|
1227
|
+
|
|
1228
|
+
\`forge do\` returns intent, plan, filesToInspect, filesToChange, risks, concrete commands, and nextAction. Prefer it before choosing lower-level CLI commands manually.
|
|
1229
|
+
|
|
1230
|
+
### Add a command
|
|
1231
|
+
|
|
1232
|
+
1. Add file in \`src/commands\`.
|
|
1233
|
+
2. Declare \`auth: can("...")\`.
|
|
1234
|
+
3. Run \`forge generate\`.
|
|
1235
|
+
4. Run \`forge verify --strict\`.
|
|
1236
|
+
|
|
1237
|
+
### Scaffold a resource
|
|
1238
|
+
|
|
1239
|
+
Use:
|
|
1240
|
+
|
|
1241
|
+
\`\`\`bash
|
|
1242
|
+
forge make resource <name> --fields title:text,status:enum(open,closed) --dry-run --json
|
|
1243
|
+
forge make resource <name> --fields title:text,status:enum(open,closed) --with-ui --yes
|
|
1244
|
+
forge make ui --framework vite --dry-run --json
|
|
1245
|
+
\`\`\`
|
|
1246
|
+
|
|
1247
|
+
Review the plan before applying when the resource touches schema or policies.
|
|
1248
|
+
|
|
1249
|
+
### Check frontend wiring
|
|
1250
|
+
|
|
1251
|
+
Use:
|
|
1252
|
+
|
|
1253
|
+
\`\`\`bash
|
|
1254
|
+
forge dev --once --json
|
|
1255
|
+
forge dev
|
|
1256
|
+
forge inspect frontend --json
|
|
1257
|
+
forge inspect capabilities --json
|
|
1258
|
+
\`\`\`
|
|
1259
|
+
|
|
1260
|
+
\`forge dev\` starts the API runtime and web app together when \`web/\` exists. \`forge dev --once --json\` reports routes, components, \`ForgeProvider\`, bridge files, generated client bindings, direct runtime fetch warnings, capability-map parity warnings, and fix hints.
|
|
1261
|
+
|
|
1262
|
+
### Apply a feature blueprint
|
|
1263
|
+
|
|
1264
|
+
Use:
|
|
1265
|
+
|
|
1266
|
+
\`\`\`bash
|
|
1267
|
+
forge feature validate .forge/blueprints/<name>.json --json
|
|
1268
|
+
forge feature plan .forge/blueprints/<name>.json
|
|
1269
|
+
forge feature apply .forge/blueprints/<name>.json --yes
|
|
1270
|
+
\`\`\`
|
|
1271
|
+
|
|
1272
|
+
Review high-risk plans before applying. Use \`--allow-high-risk\` only when intentional.
|
|
1273
|
+
|
|
1274
|
+
### Safely refactor a feature
|
|
1275
|
+
|
|
1276
|
+
Use:
|
|
1277
|
+
|
|
1278
|
+
\`\`\`bash
|
|
1279
|
+
forge refactor rename field tickets.priority tickets.urgency --dry-run --json
|
|
1280
|
+
forge refactor rename field tickets.priority tickets.urgency --yes
|
|
1281
|
+
\`\`\`
|
|
1282
|
+
|
|
1283
|
+
These codemods are AST-aware for \`extract-action\`, \`rename field\`, and \`rename table\`. Field renames are scoped to the target table, so \`tickets.priority\` only rewrites references linked to \`tickets\`.
|
|
1284
|
+
|
|
1285
|
+
Never edit \`src/forge/_generated/**\` directly. Review migration hints before applying field or table renames.
|
|
1286
|
+
|
|
1287
|
+
### Plan impact-based tests
|
|
1288
|
+
|
|
1289
|
+
Use:
|
|
1290
|
+
|
|
1291
|
+
\`\`\`bash
|
|
1292
|
+
forge impact --changed --json
|
|
1293
|
+
forge test plan --changed --json
|
|
1294
|
+
forge test run --changed --timeout-ms 120000 --json
|
|
1295
|
+
forge verify --standard
|
|
1296
|
+
\`\`\`
|
|
1297
|
+
|
|
1298
|
+
Use \`forge verify --standard\` for the normal agent development loop. Finish handoffs with \`forge verify --strict\` when the change is ready.
|
|
1299
|
+
|
|
1300
|
+
### Repair a failing check
|
|
1301
|
+
|
|
1302
|
+
When a Forge check fails, do not guess. Use:
|
|
1303
|
+
|
|
1304
|
+
\`\`\`bash
|
|
1305
|
+
forge repair diagnose --from-last-test-run --json
|
|
1306
|
+
forge repair plan --from-last-test-run --write
|
|
1307
|
+
\`\`\`
|
|
1308
|
+
|
|
1309
|
+
Apply only high-confidence deterministic repairs automatically. Review medium or low confidence repairs before changing code.
|
|
1310
|
+
|
|
1311
|
+
### Export agent adapters
|
|
1312
|
+
|
|
1313
|
+
Use:
|
|
1314
|
+
|
|
1315
|
+
\`\`\`bash
|
|
1316
|
+
forge agent export --target generic
|
|
1317
|
+
forge agent export --target codex
|
|
1318
|
+
forge agent export --target cursor
|
|
1319
|
+
forge agent export --target claude
|
|
1320
|
+
\`\`\`
|
|
1321
|
+
|
|
1322
|
+
Adapter files are derived from \`agentContract.json\`, \`appMap.md\`, \`runtimeRules.md\`, \`operationPlaybooks.md\`, and this \`AGENTS.md\`. Do not treat Codex, Cursor, Claude, or custom adapter files as the source of truth.
|
|
1323
|
+
|
|
1324
|
+
### Add a package
|
|
1325
|
+
|
|
1326
|
+
Use:
|
|
1327
|
+
|
|
1328
|
+
\`\`\`bash
|
|
1329
|
+
forge add <alias>
|
|
1330
|
+
\`\`\`
|
|
1331
|
+
|
|
1332
|
+
Do not install packages manually unless intentional.
|
|
1333
|
+
|
|
1334
|
+
### Upgrade a package
|
|
1335
|
+
|
|
1336
|
+
Use:
|
|
1337
|
+
|
|
1338
|
+
\`\`\`bash
|
|
1339
|
+
forge deps upgrade-plan <package> --to latest
|
|
1340
|
+
forge deps upgrade-apply <plan>
|
|
1341
|
+
forge verify --strict
|
|
1342
|
+
\`\`\`
|
|
1343
|
+
|
|
1344
|
+
Do not manually edit \`package.json\` for package upgrades unless necessary.
|
|
1345
|
+
|
|
1346
|
+
### Debug liveQuery
|
|
1347
|
+
|
|
1348
|
+
Use:
|
|
1349
|
+
|
|
1350
|
+
\`\`\`bash
|
|
1351
|
+
forge live status --json
|
|
1352
|
+
forge live invalidations list --json
|
|
1353
|
+
forge live debug <subscriptionId> --json
|
|
1354
|
+
\`\`\`
|
|
1355
|
+
|
|
1356
|
+
Durable invalidations live in \`_forge_live_invalidations\`.
|
|
1357
|
+
|
|
1358
|
+
<!-- forge-generated:end -->
|
|
1359
|
+
|
|
1360
|
+
${AGENTS_USER_START}
|
|
1361
|
+
|
|
1362
|
+
${userNotes}
|
|
1363
|
+
|
|
1364
|
+
${AGENTS_USER_END}
|
|
1365
|
+
`);
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
function renderAppMapMd(contract: AgentContract): string {
|
|
1369
|
+
const lines = ["# App Map", "", "## Data", ""];
|
|
1370
|
+
for (const table of contract.data.tables) {
|
|
1371
|
+
lines.push(`### ${table.name}`, `Tenant-scoped: ${table.tenantScoped ? "yes" : "no"}`);
|
|
1372
|
+
if (table.tenantField) {
|
|
1373
|
+
lines.push(`Tenant field: ${table.tenantField}`);
|
|
1374
|
+
}
|
|
1375
|
+
lines.push("Fields:", ...renderList(table.fields).split("\n"), "");
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
lines.push("## Commands", "");
|
|
1379
|
+
for (const command of contract.commands) {
|
|
1380
|
+
lines.push(
|
|
1381
|
+
`### ${command.name}`,
|
|
1382
|
+
`Policy: ${command.policy ?? "none"}`,
|
|
1383
|
+
`HTTP: ${command.http.method} ${command.http.path}`,
|
|
1384
|
+
`Frontend hook: \`${command.frontend.hook}\``,
|
|
1385
|
+
"Frontend routes:",
|
|
1386
|
+
...renderList(command.frontend.routes).split("\n"),
|
|
1387
|
+
"Frontend components:",
|
|
1388
|
+
...renderList(command.frontend.components).split("\n"),
|
|
1389
|
+
"Writes:",
|
|
1390
|
+
...renderList(command.tablesWritten).split("\n"),
|
|
1391
|
+
"Reads:",
|
|
1392
|
+
...renderList(command.tablesRead).split("\n"),
|
|
1393
|
+
"Emits:",
|
|
1394
|
+
...renderList(command.emits).split("\n"),
|
|
1395
|
+
"",
|
|
1396
|
+
);
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
lines.push("## Queries", "");
|
|
1400
|
+
for (const query of contract.queries) {
|
|
1401
|
+
lines.push(
|
|
1402
|
+
`### ${query.name}`,
|
|
1403
|
+
`Policy: ${query.policy ?? "none"}`,
|
|
1404
|
+
`HTTP: ${query.http.method} ${query.http.path}`,
|
|
1405
|
+
`Frontend hook: \`${query.frontend.hook}\``,
|
|
1406
|
+
`Read-only: ${query.readOnly ? "yes" : "no"}`,
|
|
1407
|
+
"Reads:",
|
|
1408
|
+
...renderList(query.tablesRead).split("\n"),
|
|
1409
|
+
"Frontend routes:",
|
|
1410
|
+
...renderList(query.frontend.routes).split("\n"),
|
|
1411
|
+
"Frontend components:",
|
|
1412
|
+
...renderList(query.frontend.components).split("\n"),
|
|
1413
|
+
"",
|
|
1414
|
+
);
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
lines.push("## Live Queries", "");
|
|
1418
|
+
for (const liveQuery of contract.liveQueries) {
|
|
1419
|
+
lines.push(
|
|
1420
|
+
`### ${liveQuery.name}`,
|
|
1421
|
+
`Policy: ${liveQuery.policy ?? "none"}`,
|
|
1422
|
+
`HTTP: ${liveQuery.http.method} ${liveQuery.http.path}`,
|
|
1423
|
+
`Frontend hook: \`${liveQuery.frontend.hook}\``,
|
|
1424
|
+
"Reads:",
|
|
1425
|
+
...renderList(liveQuery.tablesRead).split("\n"),
|
|
1426
|
+
"Frontend routes:",
|
|
1427
|
+
...renderList(liveQuery.frontend.routes).split("\n"),
|
|
1428
|
+
"Frontend components:",
|
|
1429
|
+
...renderList(liveQuery.frontend.components).split("\n"),
|
|
1430
|
+
"Dependencies:",
|
|
1431
|
+
...renderList(liveQuery.dependencies.map((dep) => `${dep.table} (${dep.scope})`)).split("\n"),
|
|
1432
|
+
"",
|
|
1433
|
+
);
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
lines.push("## Actions", "");
|
|
1437
|
+
for (const action of contract.actions) {
|
|
1438
|
+
lines.push(`### ${action.name}`, `File: ${action.file}`, "");
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
lines.push("## Workflows", "");
|
|
1442
|
+
for (const workflow of contract.workflows) {
|
|
1443
|
+
lines.push(`### ${workflow.name}`, `Trigger: ${workflow.trigger ?? "manual"}`, "Steps:", ...renderList(workflow.steps).split("\n"), "");
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
lines.push("## Frontend", "");
|
|
1447
|
+
lines.push(`Present: ${contract.frontend.present ? "yes" : "no"}`);
|
|
1448
|
+
lines.push(`Framework: ${contract.frontend.framework}`);
|
|
1449
|
+
if (contract.frontend.root) {
|
|
1450
|
+
lines.push(`Root: ${contract.frontend.root}`);
|
|
1451
|
+
}
|
|
1452
|
+
if (contract.frontend.dev) {
|
|
1453
|
+
lines.push(`Dev URL: ${contract.frontend.dev.url}`);
|
|
1454
|
+
lines.push(`API URL env: ${contract.frontend.dev.apiUrlEnv}`);
|
|
1455
|
+
}
|
|
1456
|
+
lines.push("");
|
|
1457
|
+
|
|
1458
|
+
lines.push("### Routes", "");
|
|
1459
|
+
for (const route of contract.frontend.routes) {
|
|
1460
|
+
lines.push(
|
|
1461
|
+
`#### ${route.path}`,
|
|
1462
|
+
`File: ${route.file}`,
|
|
1463
|
+
"Components:",
|
|
1464
|
+
...renderList(route.components).split("\n"),
|
|
1465
|
+
"Uses commands:",
|
|
1466
|
+
...renderList(route.usesCommands).split("\n"),
|
|
1467
|
+
"Uses queries:",
|
|
1468
|
+
...renderList(route.usesQueries).split("\n"),
|
|
1469
|
+
"Uses liveQueries:",
|
|
1470
|
+
...renderList(route.usesLiveQueries).split("\n"),
|
|
1471
|
+
"Raw Forge fetches:",
|
|
1472
|
+
...renderList(route.rawForgeFetches).split("\n"),
|
|
1473
|
+
"",
|
|
1474
|
+
);
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
lines.push("### Components", "");
|
|
1478
|
+
for (const component of contract.frontend.components) {
|
|
1479
|
+
lines.push(
|
|
1480
|
+
`#### ${component.name}`,
|
|
1481
|
+
`File: ${component.file}`,
|
|
1482
|
+
"Uses commands:",
|
|
1483
|
+
...renderList(component.usesCommands).split("\n"),
|
|
1484
|
+
"Uses queries:",
|
|
1485
|
+
...renderList(component.usesQueries).split("\n"),
|
|
1486
|
+
"Uses liveQueries:",
|
|
1487
|
+
...renderList(component.usesLiveQueries).split("\n"),
|
|
1488
|
+
"",
|
|
1489
|
+
);
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
lines.push("### Client Bindings", "");
|
|
1493
|
+
for (const binding of contract.frontend.clientBindings) {
|
|
1494
|
+
lines.push(
|
|
1495
|
+
`- ${binding.kind} ${binding.name} in ${binding.file}${binding.route ? ` (route ${binding.route})` : ""}${binding.component ? ` (${binding.component})` : ""}`,
|
|
1496
|
+
);
|
|
1497
|
+
}
|
|
1498
|
+
if (contract.frontend.clientBindings.length === 0) {
|
|
1499
|
+
lines.push("- none");
|
|
1500
|
+
}
|
|
1501
|
+
lines.push("");
|
|
1502
|
+
|
|
1503
|
+
lines.push("### Runtime Endpoints", "");
|
|
1504
|
+
for (const endpoint of contract.frontend.runtimeEndpoints) {
|
|
1505
|
+
lines.push(
|
|
1506
|
+
`- ${endpoint.kind} ${endpoint.name}: ${endpoint.http.method} ${endpoint.http.path}; ${endpoint.frontend.hook}`,
|
|
1507
|
+
);
|
|
1508
|
+
}
|
|
1509
|
+
if (contract.frontend.runtimeEndpoints.length === 0) {
|
|
1510
|
+
lines.push("- none");
|
|
1511
|
+
}
|
|
1512
|
+
lines.push("");
|
|
1513
|
+
|
|
1514
|
+
lines.push("### Full-Stack Route Bindings", "");
|
|
1515
|
+
for (const binding of contract.frontend.routeBindings) {
|
|
1516
|
+
lines.push(
|
|
1517
|
+
`- ${binding.route ?? "unknown route"} -> ${binding.hook} -> ${binding.kind} ${binding.name}`,
|
|
1518
|
+
` File: ${binding.file}`,
|
|
1519
|
+
` HTTP: ${binding.http.method} ${binding.http.path}`,
|
|
1520
|
+
` Policy: ${binding.policy ?? "none"}`,
|
|
1521
|
+
` Reads: ${binding.tablesRead.length > 0 ? binding.tablesRead.join(", ") : "none"}`,
|
|
1522
|
+
` Writes: ${binding.tablesWritten.length > 0 ? binding.tablesWritten.join(", ") : "none"}`,
|
|
1523
|
+
` Emits: ${binding.emits.length > 0 ? binding.emits.join(", ") : "none"}`,
|
|
1524
|
+
);
|
|
1525
|
+
}
|
|
1526
|
+
if (contract.frontend.routeBindings.length === 0) {
|
|
1527
|
+
lines.push("- none");
|
|
1528
|
+
}
|
|
1529
|
+
lines.push("");
|
|
1530
|
+
|
|
1531
|
+
return normalizeNewlines(lines.join("\n"));
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
function renderRuntimeRulesMd(rules: AgentRuntimeRule[]): string {
|
|
1535
|
+
const lines = [
|
|
1536
|
+
"# Runtime Rules",
|
|
1537
|
+
"",
|
|
1538
|
+
"## LiveQuery Production",
|
|
1539
|
+
"",
|
|
1540
|
+
"Allowed:",
|
|
1541
|
+
"- durable invalidation rows in _forge_live_invalidations",
|
|
1542
|
+
"- polling fallback",
|
|
1543
|
+
"- Postgres notify wakeups",
|
|
1544
|
+
"- SSE heartbeats and Last-Event-ID resume",
|
|
1545
|
+
"",
|
|
1546
|
+
"Forbidden:",
|
|
1547
|
+
"- treating Pub/Sub or in-memory notification as the source of truth",
|
|
1548
|
+
"- unbounded snapshot queues",
|
|
1549
|
+
"- cross-tenant invalidation fanout",
|
|
1550
|
+
"",
|
|
1551
|
+
];
|
|
1552
|
+
for (const rule of rules) {
|
|
1553
|
+
lines.push(`## ${rule.context}`, "", "Allowed:", ...renderList(rule.allowed).split("\n"), "", "Forbidden:", ...renderList(rule.forbidden).split("\n"), "");
|
|
1554
|
+
}
|
|
1555
|
+
return normalizeNewlines(lines.join("\n"));
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
function renderCapabilityMapMd(capabilityMap: AgentCapabilityMap): string {
|
|
1559
|
+
const lines = [
|
|
1560
|
+
"# Capability Map",
|
|
1561
|
+
"",
|
|
1562
|
+
`Project: ${capabilityMap.project.name}`,
|
|
1563
|
+
"",
|
|
1564
|
+
"## Summary",
|
|
1565
|
+
"",
|
|
1566
|
+
`- Covered: ${capabilityMap.summary.covered}`,
|
|
1567
|
+
`- Backend-only: ${capabilityMap.summary.backendOnly}`,
|
|
1568
|
+
`- Frontend-only: ${capabilityMap.summary.frontendOnly}`,
|
|
1569
|
+
`- Warnings: ${capabilityMap.summary.warnings}`,
|
|
1570
|
+
"",
|
|
1571
|
+
"## Capabilities",
|
|
1572
|
+
"",
|
|
1573
|
+
];
|
|
1574
|
+
for (const entry of capabilityMap.entries) {
|
|
1575
|
+
lines.push(`### ${entry.id}`, `Status: ${entry.status}`, `User action: ${entry.userAction}`);
|
|
1576
|
+
if (entry.ui) {
|
|
1577
|
+
lines.push(`UI file: ${entry.ui.file}`);
|
|
1578
|
+
if (entry.ui.route) lines.push(`Route: ${entry.ui.route}`);
|
|
1579
|
+
if (entry.ui.component) lines.push(`Component: ${entry.ui.component}`);
|
|
1580
|
+
}
|
|
1581
|
+
if (entry.runtime) {
|
|
1582
|
+
lines.push(
|
|
1583
|
+
`Runtime: ${entry.runtime.kind} ${entry.runtime.name}`,
|
|
1584
|
+
`Hook: ${entry.runtime.hook}`,
|
|
1585
|
+
`HTTP: ${entry.runtime.http.method} ${entry.runtime.http.path}`,
|
|
1586
|
+
`Policy: ${entry.runtime.policy ?? "none"}`,
|
|
1587
|
+
`Reads: ${entry.runtime.tablesRead.length > 0 ? entry.runtime.tablesRead.join(", ") : "none"}`,
|
|
1588
|
+
`Writes: ${entry.runtime.tablesWritten.length > 0 ? entry.runtime.tablesWritten.join(", ") : "none"}`,
|
|
1589
|
+
`Emits: ${entry.runtime.emits.length > 0 ? entry.runtime.emits.join(", ") : "none"}`,
|
|
1590
|
+
);
|
|
1591
|
+
}
|
|
1592
|
+
lines.push("Notes:", ...renderList(entry.notes).split("\n"), "");
|
|
1593
|
+
}
|
|
1594
|
+
if (capabilityMap.entries.length === 0) {
|
|
1595
|
+
lines.push("- none", "");
|
|
1596
|
+
}
|
|
1597
|
+
if (capabilityMap.diagnostics.length > 0) {
|
|
1598
|
+
lines.push("## Diagnostics", "");
|
|
1599
|
+
for (const diagnostic of capabilityMap.diagnostics) {
|
|
1600
|
+
lines.push(`- ${diagnostic.severity} ${diagnostic.code}: ${diagnostic.message}`);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
return normalizeNewlines(lines.join("\n"));
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
function renderOperationPlaybooksMd(playbookEntries: AgentPlaybook[]): string {
|
|
1607
|
+
const lines = ["# Operation Playbooks", ""];
|
|
1608
|
+
for (const playbook of playbookEntries) {
|
|
1609
|
+
lines.push(`## ${playbook.title}`, "");
|
|
1610
|
+
for (let index = 0; index < playbook.steps.length; index++) {
|
|
1611
|
+
lines.push(`${index + 1}. ${playbook.steps[index]}`);
|
|
1612
|
+
}
|
|
1613
|
+
lines.push("");
|
|
1614
|
+
}
|
|
1615
|
+
return normalizeNewlines(lines.join("\n"));
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
function renderAgentQuickstartMd(): string {
|
|
1619
|
+
return normalizeNewlines(`# Agent Quickstart
|
|
1620
|
+
|
|
1621
|
+
Run:
|
|
1622
|
+
|
|
1623
|
+
\`\`\`bash
|
|
1624
|
+
forge do inspect --json
|
|
1625
|
+
forge do fix --json
|
|
1626
|
+
forge do verify --json
|
|
1627
|
+
forge dev --once --json
|
|
1628
|
+
forge dev
|
|
1629
|
+
forge inspect all --json
|
|
1630
|
+
forge inspect frontend --json
|
|
1631
|
+
forge inspect capabilities --json
|
|
1632
|
+
forge check --json
|
|
1633
|
+
\`\`\`
|
|
1634
|
+
|
|
1635
|
+
Never edit:
|
|
1636
|
+
|
|
1637
|
+
\`\`\`txt
|
|
1638
|
+
src/forge/_generated/**
|
|
1639
|
+
forge.lock
|
|
1640
|
+
\`\`\`
|
|
1641
|
+
|
|
1642
|
+
If generated files are ignored by git, recreate them with \`forge generate\`.
|
|
1643
|
+
|
|
1644
|
+
Always finish with:
|
|
1645
|
+
|
|
1646
|
+
\`\`\`bash
|
|
1647
|
+
forge generate
|
|
1648
|
+
forge verify --strict
|
|
1649
|
+
\`\`\`
|
|
1650
|
+
`);
|
|
1651
|
+
}
|