forgeos 0.1.0-alpha.4 → 0.1.0-alpha.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.npmignore +10 -0
- package/AGENTS.md +142 -90
- package/CHANGELOG.md +433 -0
- package/LICENSE +21 -0
- package/README.md +67 -8
- package/adapters/go/README.md +23 -0
- package/adapters/go/go.mod +3 -0
- package/adapters/go/http.go +149 -0
- package/adapters/go/registry.go +234 -0
- package/adapters/go/types.go +136 -0
- package/adapters/java/README.md +68 -0
- package/adapters/java/pom.xml +34 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Auth.java +20 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Diagnostic.java +16 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Entry.java +38 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/EntryKind.java +16 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/ErrorInfo.java +4 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Forge.java +94 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeCall.java +12 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeContext.java +11 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeHandler.java +8 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeHttpHandler.java +179 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeRegistry.java +121 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Json.java +14 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Manifest.java +14 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/RequestEnvelope.java +6 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/ResponseEnvelope.java +25 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Risk.java +18 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Schemas.java +36 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Service.java +65 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/TransactionMode.java +18 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/TypedForgeHandler.java +6 -0
- package/adapters/java-spring-boot-starter/README.md +32 -0
- package/adapters/java-spring-boot-starter/pom.xml +36 -0
- package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeCommand.java +22 -0
- package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeExternalService.java +15 -0
- package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeQuery.java +16 -0
- package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeServiceBeanCondition.java +18 -0
- package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeSpringAutoConfiguration.java +16 -0
- package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeSpringRuntime.java +104 -0
- package/adapters/java-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +1 -0
- package/bin/forge.mjs +18 -0
- package/docs/cair-protocol.md +103 -0
- package/docs/changelog.md +548 -0
- package/docs/forge-protocol.md +189 -0
- package/examples/go-billing/go.mod +7 -0
- package/examples/go-billing/main.go +120 -0
- package/examples/java-billing/pom.xml +52 -0
- package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/CreateInvoiceInput.java +4 -0
- package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/Invoice.java +11 -0
- package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/Main.java +127 -0
- package/package.json +31 -3
- package/schemas/forge-manifest.schema.json +57 -0
- package/src/forge/_generated/releaseManifest.json +1 -2
- package/src/forge/_generated/releaseManifest.ts +3 -3
- package/src/forge/agent-adapters/index.ts +1584 -125
- package/src/forge/agent-adapters/types.ts +224 -1
- package/src/forge/agent-memory/bridge.ts +1333 -0
- package/src/forge/agent-memory/context-pack.ts +277 -0
- package/src/forge/agent-memory/hook-runner.ts +312 -0
- package/src/forge/agent-memory/mcp.ts +224 -0
- package/src/forge/agent-memory/normalize.ts +498 -0
- package/src/forge/agent-memory/redaction.ts +103 -0
- package/src/forge/agent-memory/sources/claude-code.ts +51 -0
- package/src/forge/agent-memory/sources/codex-hook-runner.mjs +273 -0
- package/src/forge/agent-memory/sources/codex.ts +119 -0
- package/src/forge/agent-memory/sources/cursor.ts +35 -0
- package/src/forge/agent-memory/types.ts +204 -0
- package/src/forge/bench.ts +248 -0
- package/src/forge/brownfield-import/index.ts +801 -0
- package/src/forge/brownfield-import/types.ts +127 -0
- package/src/forge/cair/action-journal.ts +61 -0
- package/src/forge/cair/action-parser.ts +314 -0
- package/src/forge/cair/action-validator.ts +40 -0
- package/src/forge/cair/actions.ts +1818 -0
- package/src/forge/cair/format.ts +77 -0
- package/src/forge/cair/index.ts +106 -0
- package/src/forge/cair/query.ts +478 -0
- package/src/forge/cair/snapshot.ts +315 -0
- package/src/forge/cair/types.ts +248 -0
- package/src/forge/cli/ai.ts +339 -21
- package/src/forge/cli/auth.ts +348 -10
- package/src/forge/cli/authmd.ts +441 -0
- package/src/forge/cli/baseline.ts +112 -0
- package/src/forge/cli/build.ts +20 -4
- package/src/forge/cli/changed.ts +437 -0
- package/src/forge/cli/codex-app-server.ts +877 -0
- package/src/forge/cli/commands.ts +1620 -20
- package/src/forge/cli/db.ts +209 -2
- package/src/forge/cli/deploy.ts +887 -0
- package/src/forge/cli/deps.ts +82 -13
- package/src/forge/cli/dev.ts +961 -46
- package/src/forge/cli/docs.ts +265 -0
- package/src/forge/cli/doctor.ts +297 -0
- package/src/forge/cli/field-test.ts +329 -0
- package/src/forge/cli/handoff.ts +336 -0
- package/src/forge/cli/index.ts +1 -0
- package/src/forge/cli/last-run.ts +84 -0
- package/src/forge/cli/main.ts +107 -3
- package/src/forge/cli/new.ts +152 -13
- package/src/forge/cli/next-actions.ts +23 -0
- package/src/forge/cli/output.ts +389 -6
- package/src/forge/cli/parse.ts +1050 -34
- package/src/forge/cli/progress.ts +51 -0
- package/src/forge/cli/query.ts +32 -0
- package/src/forge/cli/release.ts +35 -11
- package/src/forge/cli/rls.ts +43 -4
- package/src/forge/cli/run.ts +41 -0
- package/src/forge/cli/security.ts +112 -0
- package/src/forge/cli/self-host.ts +56 -14
- package/src/forge/cli/studio.ts +2218 -0
- package/src/forge/cli/verify.ts +1455 -32
- package/src/forge/cli/windows.ts +23 -0
- package/src/forge/cli/workos.ts +1020 -0
- package/src/forge/compiler/agent-contract/build.ts +670 -64
- package/src/forge/compiler/agent-contract/types.ts +45 -2
- package/src/forge/compiler/ai-registry/parse.ts +71 -2
- package/src/forge/compiler/api-surface/build.ts +47 -0
- package/src/forge/compiler/app-graph/build.ts +112 -9
- package/src/forge/compiler/app-graph/extract.ts +107 -0
- package/src/forge/compiler/app-graph/module-graph.ts +73 -78
- package/src/forge/compiler/app-graph/parser.ts +24 -24
- package/src/forge/compiler/app-graph/profile.ts +26 -0
- package/src/forge/compiler/app-graph/versions.ts +1 -1
- package/src/forge/compiler/classifier/capabilities.ts +3 -2
- package/src/forge/compiler/classifier/classify.ts +32 -8
- package/src/forge/compiler/classifier/secrets.ts +3 -2
- package/src/forge/compiler/classifier/signals.ts +91 -1
- package/src/forge/compiler/client-sdk/build-manifest.ts +59 -0
- package/src/forge/compiler/client-sdk/render-client.ts +295 -13
- package/src/forge/compiler/data-graph/parse.ts +20 -4
- package/src/forge/compiler/data-graph/sql/ddl.ts +144 -31
- package/src/forge/compiler/data-graph/sql/serialize.ts +4 -0
- package/src/forge/compiler/data-graph/sql/types.ts +1 -0
- package/src/forge/compiler/diagnostics/codes.ts +30 -0
- package/src/forge/compiler/diagnostics/create.ts +21 -2
- package/src/forge/compiler/diagnostics/index.ts +2 -0
- package/src/forge/compiler/emitter/barrel.ts +3 -0
- package/src/forge/compiler/emitter/render.ts +9 -0
- package/src/forge/compiler/external-manifest/registry.ts +205 -0
- package/src/forge/compiler/external-manifest/types.ts +91 -0
- package/src/forge/compiler/external-manifest/validate.ts +373 -0
- package/src/forge/compiler/frontend-graph/build.ts +142 -14
- package/src/forge/compiler/integration/add.ts +618 -24
- package/src/forge/compiler/integration/plan.ts +43 -12
- package/src/forge/compiler/integration/render.ts +29 -0
- package/src/forge/compiler/integration/snapshot.ts +2 -0
- package/src/forge/compiler/integration/templates/convex.ts +70 -0
- package/src/forge/compiler/integration/templates/index.ts +1 -0
- package/src/forge/compiler/integration/templates/render.ts +38 -0
- package/src/forge/compiler/integration/templates/types.ts +3 -0
- package/src/forge/compiler/integration/templates/workos.ts +1219 -0
- package/src/forge/compiler/make-registry/build.ts +8 -9
- package/src/forge/compiler/orchestrator/fast-check.ts +4 -0
- package/src/forge/compiler/orchestrator/generate-lock.ts +14 -3
- package/src/forge/compiler/orchestrator/manifest.ts +1 -1
- package/src/forge/compiler/orchestrator/plan-profile.ts +23 -0
- package/src/forge/compiler/orchestrator/plan.ts +247 -10
- package/src/forge/compiler/orchestrator/profile.ts +65 -0
- package/src/forge/compiler/orchestrator/run.ts +97 -31
- package/src/forge/compiler/orchestrator/serialize.ts +33 -8
- package/src/forge/compiler/orchestrator/types.ts +1 -1
- package/src/forge/compiler/package-graph/compiler.ts +3 -1
- package/src/forge/compiler/package-graph/constants.ts +1 -1
- package/src/forge/compiler/package-manager/adapter.ts +4 -1
- package/src/forge/compiler/package-manager/commands.ts +4 -0
- package/src/forge/compiler/package-manager/executor.ts +30 -1
- package/src/forge/compiler/package-manager/version.ts +36 -12
- package/src/forge/compiler/package-upgrades/planner.ts +155 -18
- package/src/forge/compiler/package-upgrades/types.ts +2 -0
- package/src/forge/compiler/policy-registry/build.ts +47 -2
- package/src/forge/compiler/policy-registry/parse.ts +32 -2
- package/src/forge/compiler/recipes/definitions.ts +63 -0
- package/src/forge/compiler/recipes/index.ts +2 -0
- package/src/forge/compiler/recipes/registry.ts +6 -0
- package/src/forge/compiler/test-graph/build.ts +11 -3
- package/src/forge/compiler/types/app-graph.ts +8 -2
- package/src/forge/compiler/types/cli.ts +75 -1
- package/src/forge/compiler/types/dev-manifest.ts +4 -0
- package/src/forge/compiler/types/emit.ts +2 -0
- package/src/forge/compiler/types/frontend-graph.ts +2 -2
- package/src/forge/compiler/types/integration.ts +1 -0
- package/src/forge/compiler/types/package-graph.ts +6 -0
- package/src/forge/compiler/types/policy-registry.ts +3 -1
- package/src/forge/delta/classifier.ts +52 -0
- package/src/forge/delta/explain.ts +238 -0
- package/src/forge/delta/git-observer.ts +43 -0
- package/src/forge/delta/ids.ts +44 -0
- package/src/forge/delta/index.ts +25 -0
- package/src/forge/delta/recorder.ts +540 -0
- package/src/forge/delta/redaction.ts +50 -0
- package/src/forge/delta/schema.ts +240 -0
- package/src/forge/delta/session.ts +142 -0
- package/src/forge/delta/status.ts +1202 -0
- package/src/forge/delta/store.ts +3284 -0
- package/src/forge/delta/timeline.ts +178 -0
- package/src/forge/dev/server.ts +808 -17
- package/src/forge/dev/types.ts +15 -1
- package/src/forge/dev/watch.ts +17 -7
- package/src/forge/dev-console/cycle.ts +280 -33
- package/src/forge/dev-console/types.ts +47 -1
- package/src/forge/impact/index.ts +204 -9
- package/src/forge/impact/types.ts +33 -1
- package/src/forge/intent/index.ts +35 -16
- package/src/forge/make/fields.ts +26 -0
- package/src/forge/make/index.ts +40 -6
- package/src/forge/make/templates.ts +155 -2
- package/src/forge/make/types.ts +2 -1
- package/src/forge/policy.ts +1 -1
- package/src/forge/react/index.ts +160 -2
- package/src/forge/refactor/index.ts +1 -0
- package/src/forge/repair/rules/index.ts +2 -2
- package/src/forge/review/index.ts +158 -12
- package/src/forge/review/types.ts +15 -0
- package/src/forge/runtime/auth/config.ts +17 -0
- package/src/forge/runtime/auth/evaluate.ts +15 -2
- package/src/forge/runtime/auth/resolve.ts +61 -6
- package/src/forge/runtime/auth/types.ts +1 -0
- package/src/forge/runtime/db/factory.ts +1 -3
- package/src/forge/runtime/db/generated-client.ts +13 -2
- package/src/forge/runtime/db/memory-adapter.ts +139 -32
- package/src/forge/runtime/db/pglite-adapter.ts +263 -2
- package/src/forge/runtime/db/postgres-adapter.ts +6 -3
- package/src/forge/runtime/executor.ts +129 -16
- package/src/forge/runtime/external/bridge.ts +649 -0
- package/src/forge/runtime/runner/run-entry.ts +16 -7
- package/src/forge/runtime/telemetry/buffer.ts +4 -1
- package/src/forge/runtime/telemetry/scrubber.ts +35 -5
- package/src/forge/runtime/webhooks/security.ts +12 -7
- package/src/forge/server.ts +17 -3
- package/src/forge/ui/index.ts +209 -0
- package/src/forge/ui/types.ts +1 -0
- package/src/forge/version.ts +1 -1
- package/src/forge/vue/index.ts +422 -0
- package/src/forge/workspace/baseline.ts +112 -0
- package/src/forge/workspace/change-summary.ts +250 -0
- package/src/forge/workspace/forge-cli.ts +42 -0
- package/src/forge/workspace/git-summary.ts +391 -0
- package/templates/agent-workroom/AGENTS.md +29 -0
- package/templates/agent-workroom/README.md +34 -0
- package/templates/agent-workroom/forge.config.ts +3 -0
- package/templates/agent-workroom/package.json +33 -0
- package/templates/agent-workroom/src/actions/indexAgentSignal.ts +10 -0
- package/templates/agent-workroom/src/commands/openWorkroom.ts +61 -0
- package/templates/agent-workroom/src/commands/recordAgentSignal.ts +119 -0
- package/templates/agent-workroom/src/commands/recordCheckRun.ts +52 -0
- package/templates/agent-workroom/src/forge/schema.ts +54 -0
- package/templates/agent-workroom/src/policies.ts +6 -0
- package/templates/agent-workroom/src/queries/listWorkrooms.ts +11 -0
- package/templates/agent-workroom/src/queries/liveWorkroom.ts +63 -0
- package/templates/agent-workroom/tsconfig.json +16 -0
- package/templates/agent-workroom/web/index.html +12 -0
- package/templates/agent-workroom/web/package.json +21 -0
- package/templates/agent-workroom/web/src/App.tsx +345 -0
- package/templates/agent-workroom/web/src/lib/forge.ts +13 -0
- package/templates/agent-workroom/web/src/main.tsx +13 -0
- package/templates/agent-workroom/web/src/styles.css +545 -0
- package/templates/agent-workroom/web/tsconfig.json +27 -0
- package/templates/b2b-support-web/package.json +1 -0
- package/templates/b2b-support-web/web/package.json +1 -1
- package/templates/minimal-web/package.json +1 -1
- package/templates/minimal-web/web/package.json +2 -2
- package/templates/nuxt-web/.vscode/settings.json +14 -0
- package/templates/nuxt-web/README.md +30 -0
- package/templates/nuxt-web/forge.config.ts +3 -0
- package/templates/nuxt-web/package.json +33 -0
- package/templates/nuxt-web/src/actions/logNoteCreated.ts +11 -0
- package/templates/nuxt-web/src/commands/createNote.ts +26 -0
- package/templates/nuxt-web/src/forge/schema.ts +12 -0
- package/templates/nuxt-web/src/policies.ts +6 -0
- package/templates/nuxt-web/src/queries/listNotes.ts +8 -0
- package/templates/nuxt-web/src/queries/liveNotes.ts +8 -0
- package/templates/nuxt-web/tsconfig.json +17 -0
- package/templates/nuxt-web/web/app.vue +67 -0
- package/templates/nuxt-web/web/components/LiveNotes.vue +89 -0
- package/templates/nuxt-web/web/components/NoteComposer.vue +100 -0
- package/templates/nuxt-web/web/composables/forge.ts +13 -0
- package/templates/nuxt-web/web/composables/useNotes.ts +24 -0
- package/templates/nuxt-web/web/nuxt.config.ts +11 -0
- package/templates/nuxt-web/web/package.json +18 -0
- package/templates/nuxt-web/web/plugins/forge.client.ts +10 -0
- package/templates/nuxt-web/web/plugins/forge.server.ts +10 -0
- package/templates/nuxt-web/web/server/api/forge-health.get.ts +7 -0
- package/templates/nuxt-web/web/tsconfig.json +6 -0
- package/src/forge/_generated/actionSubscriptions.json +0 -2
- package/src/forge/_generated/actionSubscriptions.ts +0 -10
- package/src/forge/_generated/agentAdapterManifest.json +0 -2
- package/src/forge/_generated/agentAdapterManifest.ts +0 -73
- package/src/forge/_generated/agentContract.json +0 -2
- package/src/forge/_generated/agentContract.ts +0 -7829
- package/src/forge/_generated/agentQuickstart.md +0 -34
- package/src/forge/_generated/agentTools.json +0 -2
- package/src/forge/_generated/agentTools.md +0 -16
- package/src/forge/_generated/agentTools.ts +0 -12
- package/src/forge/_generated/aiContext.ts +0 -125
- package/src/forge/_generated/aiModels.json +0 -2
- package/src/forge/_generated/aiModels.ts +0 -51
- package/src/forge/_generated/aiProviders.json +0 -2
- package/src/forge/_generated/aiProviders.ts +0 -23
- package/src/forge/_generated/aiRegistry.json +0 -2
- package/src/forge/_generated/aiRegistry.ts +0 -31
- package/src/forge/_generated/api.json +0 -2
- package/src/forge/_generated/api.ts +0 -8
- package/src/forge/_generated/appGraph.json +0 -2
- package/src/forge/_generated/appGraph.ts +0 -14919
- package/src/forge/_generated/appMap.md +0 -55
- package/src/forge/_generated/artifactManifest.json +0 -2
- package/src/forge/_generated/artifactManifest.ts +0 -7
- package/src/forge/_generated/authClaims.json +0 -2
- package/src/forge/_generated/authClaims.ts +0 -13
- package/src/forge/_generated/authConfig.json +0 -2
- package/src/forge/_generated/authConfig.ts +0 -17
- package/src/forge/_generated/authContext.ts +0 -23
- package/src/forge/_generated/authRegistry.json +0 -2
- package/src/forge/_generated/authRegistry.ts +0 -25
- package/src/forge/_generated/buildInfo.json +0 -2
- package/src/forge/_generated/buildInfo.ts +0 -9
- package/src/forge/_generated/capabilityMap.json +0 -2
- package/src/forge/_generated/capabilityMap.md +0 -15
- package/src/forge/_generated/capabilityMap.ts +0 -17
- package/src/forge/_generated/client.ts +0 -282
- package/src/forge/_generated/clientApi.ts +0 -9
- package/src/forge/_generated/clientManifest.json +0 -2
- package/src/forge/_generated/clientManifest.ts +0 -39
- package/src/forge/_generated/clientTypes.ts +0 -78
- package/src/forge/_generated/configRegistry.json +0 -2
- package/src/forge/_generated/configRegistry.ts +0 -4
- package/src/forge/_generated/dataGraph.json +0 -2
- package/src/forge/_generated/dataGraph.ts +0 -8
- package/src/forge/_generated/db.json +0 -2
- package/src/forge/_generated/db.ts +0 -2
- package/src/forge/_generated/dbSecurityManifest.json +0 -2
- package/src/forge/_generated/dbSecurityManifest.ts +0 -15
- package/src/forge/_generated/dbSessionContext.json +0 -2
- package/src/forge/_generated/dbSessionContext.ts +0 -39
- package/src/forge/_generated/deployManifest.json +0 -2
- package/src/forge/_generated/deployManifest.ts +0 -14
- package/src/forge/_generated/devManifest.json +0 -2
- package/src/forge/_generated/devManifest.ts +0 -62
- package/src/forge/_generated/envSchema.json +0 -2
- package/src/forge/_generated/envSchema.ts +0 -59
- package/src/forge/_generated/frontendGraph.json +0 -2
- package/src/forge/_generated/frontendGraph.ts +0 -27
- package/src/forge/_generated/importGuards.json +0 -2
- package/src/forge/_generated/importGuards.ts +0 -686
- package/src/forge/_generated/index.ts +0 -68
- package/src/forge/_generated/liveProductionManifest.json +0 -2
- package/src/forge/_generated/liveProductionManifest.ts +0 -23
- package/src/forge/_generated/liveProtocol.json +0 -2
- package/src/forge/_generated/liveProtocol.ts +0 -21
- package/src/forge/_generated/liveQueryRegistry.json +0 -2
- package/src/forge/_generated/liveQueryRegistry.ts +0 -9
- package/src/forge/_generated/liveTransportConfig.json +0 -2
- package/src/forge/_generated/liveTransportConfig.ts +0 -19
- package/src/forge/_generated/makeRegistry.json +0 -2
- package/src/forge/_generated/makeRegistry.ts +0 -177
- package/src/forge/_generated/makeTemplates.json +0 -2
- package/src/forge/_generated/makeTemplates.ts +0 -66
- package/src/forge/_generated/mockMap.json +0 -2
- package/src/forge/_generated/mockMap.ts +0 -7
- package/src/forge/_generated/operationPlaybooks.md +0 -167
- package/src/forge/_generated/packageGraph.json +0 -2
- package/src/forge/_generated/packageGraph.ts +0 -249334
- package/src/forge/_generated/packageUpgradeRegistry.json +0 -2
- package/src/forge/_generated/packageUpgradeRegistry.ts +0 -15
- package/src/forge/_generated/permissionMatrix.json +0 -2
- package/src/forge/_generated/permissionMatrix.ts +0 -7
- package/src/forge/_generated/policyRegistry.json +0 -2
- package/src/forge/_generated/policyRegistry.ts +0 -11
- package/src/forge/_generated/queryRegistry.json +0 -2
- package/src/forge/_generated/queryRegistry.ts +0 -9
- package/src/forge/_generated/react.d.ts +0 -22
- package/src/forge/_generated/react.ts +0 -29
- package/src/forge/_generated/reactManifest.json +0 -2
- package/src/forge/_generated/reactManifest.ts +0 -19
- package/src/forge/_generated/rlsPolicies.json +0 -2
- package/src/forge/_generated/rlsPolicies.sql +0 -34
- package/src/forge/_generated/rlsPolicies.ts +0 -6
- package/src/forge/_generated/runtimeGraph.json +0 -2
- package/src/forge/_generated/runtimeGraph.ts +0 -8
- package/src/forge/_generated/runtimeMatrix.json +0 -2
- package/src/forge/_generated/runtimeMatrix.ts +0 -334130
- package/src/forge/_generated/runtimeRegistry.ts +0 -2
- package/src/forge/_generated/runtimeRules.md +0 -91
- package/src/forge/_generated/secretRegistry.json +0 -2
- package/src/forge/_generated/secretRegistry.ts +0 -50
- package/src/forge/_generated/secretsContext.ts +0 -11
- package/src/forge/_generated/serverApi.ts +0 -10
- package/src/forge/_generated/sourceMapManifest.json +0 -2
- package/src/forge/_generated/sourceMapManifest.ts +0 -7
- package/src/forge/_generated/sqlPlan.json +0 -2
- package/src/forge/_generated/sqlPlan.ts +0 -88
- package/src/forge/_generated/subscriptionManifest.json +0 -2
- package/src/forge/_generated/subscriptionManifest.ts +0 -7
- package/src/forge/_generated/symbolicationManifest.json +0 -2
- package/src/forge/_generated/symbolicationManifest.ts +0 -17
- package/src/forge/_generated/telemetryRegistry.json +0 -2
- package/src/forge/_generated/telemetryRegistry.ts +0 -9
- package/src/forge/_generated/telemetrySinks.json +0 -2
- package/src/forge/_generated/telemetrySinks.ts +0 -11
- package/src/forge/_generated/tenantScope.json +0 -2
- package/src/forge/_generated/tenantScope.ts +0 -8
- package/src/forge/_generated/testGraph.json +0 -2
- package/src/forge/_generated/testGraph.ts +0 -3430
- package/src/forge/_generated/testPlanRegistry.json +0 -2
- package/src/forge/_generated/testPlanRegistry.ts +0 -33
- package/src/forge/_generated/uiRoutes.json +0 -2
- package/src/forge/_generated/uiRoutes.ts +0 -16
- package/src/forge/_generated/uiScenarios.json +0 -2
- package/src/forge/_generated/uiScenarios.ts +0 -30
- package/src/forge/_generated/uiTestManifest.json +0 -2
- package/src/forge/_generated/uiTestManifest.ts +0 -27
- package/src/forge/_generated/workflowRegistry.json +0 -2
- package/src/forge/_generated/workflowRegistry.ts +0 -9
- package/src/forge/_generated/workflowSubscriptions.json +0 -2
- package/src/forge/_generated/workflowSubscriptions.ts +0 -10
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
FORGE_TELEMETRY_SECRET_REDACTED,
|
|
5
5
|
} from "../../compiler/diagnostics/codes.ts";
|
|
6
6
|
import type { Diagnostic } from "../../compiler/types/diagnostic.ts";
|
|
7
|
+
import { hashStable } from "../../compiler/primitives/hash.ts";
|
|
7
8
|
|
|
8
9
|
const SECRET_KEY_PATTERN =
|
|
9
10
|
/password|secret|token|apikey|authorization|cookie/i;
|
|
@@ -101,6 +102,28 @@ function scrubObject(
|
|
|
101
102
|
return result;
|
|
102
103
|
}
|
|
103
104
|
|
|
105
|
+
function summarizeValue(value: unknown): Record<string, unknown> {
|
|
106
|
+
if (Array.isArray(value)) {
|
|
107
|
+
return { type: "array", length: value.length };
|
|
108
|
+
}
|
|
109
|
+
if (value && typeof value === "object") {
|
|
110
|
+
return {
|
|
111
|
+
type: "object",
|
|
112
|
+
keys: Object.keys(value as Record<string, unknown>).sort().slice(0, 20),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
if (typeof value === "string") {
|
|
116
|
+
return { type: "string", length: value.length };
|
|
117
|
+
}
|
|
118
|
+
return { type: value === null ? "null" : typeof value };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function summarizePayload(payload: Record<string, unknown>): Record<string, unknown> {
|
|
122
|
+
return Object.fromEntries(
|
|
123
|
+
Object.entries(payload).map(([key, value]) => [key, summarizeValue(value)]),
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
104
127
|
function truncateString(
|
|
105
128
|
value: string,
|
|
106
129
|
maxBytes: number,
|
|
@@ -148,7 +171,8 @@ export function scrubEnvelopePayload<T extends Record<string, unknown>>(
|
|
|
148
171
|
}
|
|
149
172
|
|
|
150
173
|
const serialized = JSON.stringify(scrubbed);
|
|
151
|
-
|
|
174
|
+
const originalBytes = Buffer.byteLength(serialized, "utf8");
|
|
175
|
+
if (originalBytes > MAX_PAYLOAD_BYTES) {
|
|
152
176
|
diagnostics.push(
|
|
153
177
|
createDiagnostic({
|
|
154
178
|
severity: "warning",
|
|
@@ -156,10 +180,16 @@ export function scrubEnvelopePayload<T extends Record<string, unknown>>(
|
|
|
156
180
|
message: `truncated telemetry payload to ${MAX_PAYLOAD_BYTES} bytes`,
|
|
157
181
|
}),
|
|
158
182
|
);
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
183
|
+
return {
|
|
184
|
+
value: {
|
|
185
|
+
truncated: true,
|
|
186
|
+
originalBytes,
|
|
187
|
+
maxBytes: MAX_PAYLOAD_BYTES,
|
|
188
|
+
hash: hashStable(serialized),
|
|
189
|
+
summary: summarizePayload(scrubbed),
|
|
190
|
+
} as unknown as T,
|
|
191
|
+
diagnostics,
|
|
192
|
+
};
|
|
163
193
|
}
|
|
164
194
|
|
|
165
195
|
return { value: scrubbed, diagnostics };
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
FORGE_WEBHOOK_TIMESTAMP_INVALID,
|
|
7
7
|
} from "../../compiler/diagnostics/codes.ts";
|
|
8
8
|
|
|
9
|
-
export type WebhookProvider = "generic" | "github" | "stripe";
|
|
9
|
+
export type WebhookProvider = "generic" | "github" | "stripe" | "workos";
|
|
10
10
|
|
|
11
11
|
export interface WebhookReplayStore {
|
|
12
12
|
has(eventId: string): boolean | Promise<boolean>;
|
|
@@ -69,12 +69,14 @@ function fail(
|
|
|
69
69
|
return { ok: false, provider, code, reason };
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
function
|
|
72
|
+
function parseTimestampedSignatureHeader(header: string): { timestampRaw?: string; timestamp?: number; signatures: string[] } {
|
|
73
73
|
const signatures: string[] = [];
|
|
74
|
+
let timestampRaw: string | undefined;
|
|
74
75
|
let timestamp: number | undefined;
|
|
75
76
|
for (const part of header.split(",")) {
|
|
76
77
|
const [key, value] = part.split("=", 2).map((item) => item.trim());
|
|
77
78
|
if (key === "t") {
|
|
79
|
+
timestampRaw = value;
|
|
78
80
|
const parsed = Number(value);
|
|
79
81
|
if (Number.isFinite(parsed)) {
|
|
80
82
|
timestamp = parsed;
|
|
@@ -84,7 +86,7 @@ function parseStripeHeader(header: string): { timestamp?: number; signatures: st
|
|
|
84
86
|
signatures.push(value);
|
|
85
87
|
}
|
|
86
88
|
}
|
|
87
|
-
return { timestamp, signatures };
|
|
89
|
+
return { timestampRaw, timestamp, signatures };
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
function validateTimestamp(input: {
|
|
@@ -131,9 +133,11 @@ export async function verifyWebhookSignature(
|
|
|
131
133
|
let valid = false;
|
|
132
134
|
let timestamp: number | undefined;
|
|
133
135
|
|
|
134
|
-
if (input.provider === "stripe") {
|
|
135
|
-
const parsed =
|
|
136
|
-
timestamp = parsed.timestamp
|
|
136
|
+
if (input.provider === "stripe" || input.provider === "workos") {
|
|
137
|
+
const parsed = parseTimestampedSignatureHeader(signatureHeader);
|
|
138
|
+
timestamp = input.provider === "workos" && parsed.timestamp !== undefined
|
|
139
|
+
? parsed.timestamp / 1000
|
|
140
|
+
: parsed.timestamp;
|
|
137
141
|
const timestampFailure = validateTimestamp({
|
|
138
142
|
provider: input.provider,
|
|
139
143
|
timestamp,
|
|
@@ -143,7 +147,8 @@ export async function verifyWebhookSignature(
|
|
|
143
147
|
if (timestampFailure) {
|
|
144
148
|
return timestampFailure;
|
|
145
149
|
}
|
|
146
|
-
const
|
|
150
|
+
const timestampForSignature = parsed.timestampRaw ?? String(parsed.timestamp);
|
|
151
|
+
const signedPayload = `${timestampForSignature}.${Buffer.from(bytes(input.payload)).toString("utf8")}`;
|
|
147
152
|
const expected = hmacHex(input.secret, signedPayload);
|
|
148
153
|
valid = parsed.signatures.some((signature) => safeEqualHex(signature, expected));
|
|
149
154
|
} else if (input.provider === "github") {
|
package/src/forge/server.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export type ForgeAuthRule =
|
|
2
2
|
| { kind: "policy"; policy: string }
|
|
3
3
|
| { kind: "roles"; roles: string[] }
|
|
4
|
+
| { kind: "permissions"; permissions: string[] }
|
|
4
5
|
| { kind: "public" };
|
|
5
6
|
|
|
6
7
|
export type ForgeDefinition<T extends Record<string, unknown>> = T;
|
|
@@ -134,11 +135,19 @@ export function canRole(...roles: string[]): ForgeAuthRule {
|
|
|
134
135
|
return { kind: "roles", roles };
|
|
135
136
|
}
|
|
136
137
|
|
|
138
|
+
export function canPermission(...permissions: string[]): ForgeAuthRule {
|
|
139
|
+
return { kind: "permissions", permissions };
|
|
140
|
+
}
|
|
141
|
+
|
|
137
142
|
export function public_(): ForgeAuthRule {
|
|
138
143
|
return { kind: "public" };
|
|
139
144
|
}
|
|
140
145
|
|
|
141
|
-
export function command<
|
|
146
|
+
export function command<
|
|
147
|
+
TArgs = unknown,
|
|
148
|
+
TResult = unknown,
|
|
149
|
+
T extends ForgeCommandDefinition<TArgs, TResult> = ForgeCommandDefinition<TArgs, TResult>,
|
|
150
|
+
>(definition: T): ForgeDefinition<T> {
|
|
142
151
|
return definition;
|
|
143
152
|
}
|
|
144
153
|
|
|
@@ -173,8 +182,13 @@ export function step<T extends (...args: any[]) => unknown>(
|
|
|
173
182
|
return { kind: "step", name, handler };
|
|
174
183
|
}
|
|
175
184
|
|
|
176
|
-
export function workflow<T extends Record<string, unknown>>(
|
|
177
|
-
|
|
185
|
+
export function workflow<T extends Record<string, unknown>>(
|
|
186
|
+
definition: T,
|
|
187
|
+
): ForgeDefinition<T & { __forge: { kind: "workflow" } }> {
|
|
188
|
+
return {
|
|
189
|
+
...definition,
|
|
190
|
+
__forge: { kind: "workflow" },
|
|
191
|
+
};
|
|
178
192
|
}
|
|
179
193
|
|
|
180
194
|
export {
|
package/src/forge/ui/index.ts
CHANGED
|
@@ -57,6 +57,120 @@ function uniqueSorted(values: Array<string | undefined>): string[] {
|
|
|
57
57
|
return [...new Set(values.filter((value): value is string => Boolean(value)))].sort();
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
function walkFiles(root: string, relative = ""): string[] {
|
|
61
|
+
const absolute = relative ? join(root, relative) : root;
|
|
62
|
+
return nodeFileSystem.readDir(absolute).flatMap((entry) => {
|
|
63
|
+
const child = relative ? `${relative}/${entry.name}` : entry.name;
|
|
64
|
+
const normalized = normalize(child);
|
|
65
|
+
if (entry.isDirectory) {
|
|
66
|
+
if (["node_modules", ".next", ".nuxt", "dist", "build", ".vite"].includes(entry.name)) {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
return walkFiles(root, normalized);
|
|
70
|
+
}
|
|
71
|
+
return entry.isFile ? [normalized] : [];
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function listWebSourceFiles(workspaceRoot: string, webRoot: string): Array<{ path: string; text: string }> {
|
|
76
|
+
if (!webRoot) return [];
|
|
77
|
+
const absoluteWebRoot = join(workspaceRoot, normalize(webRoot));
|
|
78
|
+
if (!nodeFileSystem.exists(absoluteWebRoot)) return [];
|
|
79
|
+
return walkFiles(absoluteWebRoot)
|
|
80
|
+
.filter((file) => /\.(tsx|jsx|vue|svelte|html)$/.test(file))
|
|
81
|
+
.map((file) => {
|
|
82
|
+
const path = `${normalize(webRoot)}/${file}`;
|
|
83
|
+
return { path, text: readText(workspaceRoot, path) };
|
|
84
|
+
})
|
|
85
|
+
.filter((source) => source.text.trim().length > 0);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function readOptionalGeneratedJson(workspaceRoot: string, name: string): unknown {
|
|
89
|
+
try {
|
|
90
|
+
return readJson<unknown>(workspaceRoot, `${GENERATED}/${name}`, null);
|
|
91
|
+
} catch {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function hasTenantScopedData(workspaceRoot: string): boolean {
|
|
97
|
+
const dataGraph = readOptionalGeneratedJson(workspaceRoot, "dataGraph.json") as { tables?: Array<Record<string, unknown>> } | null;
|
|
98
|
+
return (dataGraph?.tables ?? []).some((table) =>
|
|
99
|
+
table?.tenantScoped === true ||
|
|
100
|
+
table?.tenantScope === true ||
|
|
101
|
+
typeof table?.tenantField === "string" ||
|
|
102
|
+
(Array.isArray(table?.fields) && table.fields.some((field) =>
|
|
103
|
+
typeof field === "object" &&
|
|
104
|
+
field !== null &&
|
|
105
|
+
("tenantId" === (field as { name?: string }).name || "tenant_id" === (field as { name?: string }).name)
|
|
106
|
+
))
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function hasProductionAuthMode(workspaceRoot: string): boolean {
|
|
111
|
+
const agentContract = readOptionalGeneratedJson(workspaceRoot, "agentContract.json") as { auth?: { modes?: string[]; production?: unknown } } | null;
|
|
112
|
+
const modes = agentContract?.auth?.modes ?? [];
|
|
113
|
+
return modes.includes("jwt") || modes.includes("oidc") || Boolean(agentContract?.auth?.production);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function looksLikeAuthFlow(text: string): boolean {
|
|
117
|
+
return /sign\s*in|signin|login|logout|authkit|organization|tenant|session|workos|clerk|auth0/i.test(text);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function hasWorkOSIntegration(workspaceRoot: string): boolean {
|
|
121
|
+
return nodeFileSystem.exists(join(workspaceRoot, `${GENERATED}/integrations/workos/authkit.ts`)) ||
|
|
122
|
+
nodeFileSystem.exists(join(workspaceRoot, `${GENERATED}/integrations/workos/auth-routes.ts`));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function webPackageHasAuthKit(workspaceRoot: string): boolean {
|
|
126
|
+
const path = join(workspaceRoot, "web/package.json");
|
|
127
|
+
if (!nodeFileSystem.exists(path)) return false;
|
|
128
|
+
try {
|
|
129
|
+
const packageJson = JSON.parse(nodeFileSystem.readText(path) ?? "{}") as {
|
|
130
|
+
dependencies?: Record<string, string>;
|
|
131
|
+
devDependencies?: Record<string, string>;
|
|
132
|
+
};
|
|
133
|
+
return Boolean(packageJson.dependencies?.["@workos-inc/authkit-react"] ?? packageJson.devDependencies?.["@workos-inc/authkit-react"]);
|
|
134
|
+
} catch {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function hasMainLandmark(text: string): boolean {
|
|
140
|
+
return /<main[\s>]|role=["']main["']|<header[\s>]|<nav[\s>]/i.test(text);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function hasRuntimeDataHook(text: string): boolean {
|
|
144
|
+
return /\b(useLiveQuery|useQuery|useForgeLiveQuery|useForgeQuery)\b/.test(text);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function hasStateText(text: string, pattern: RegExp): boolean {
|
|
148
|
+
return pattern.test(text);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function findFormWithoutLabel(text: string): boolean {
|
|
152
|
+
if (!/<(form|input|select|textarea)\b/i.test(text)) return false;
|
|
153
|
+
const controls = text.match(/<(input|select|textarea)\b[^>]*>/gi) ?? [];
|
|
154
|
+
return controls.some((control) => {
|
|
155
|
+
if (/\b(type=["']?(hidden|submit|button|checkbox|radio)["']?)/i.test(control)) return false;
|
|
156
|
+
if (/\b(aria-label|aria-labelledby|title)=/i.test(control)) return false;
|
|
157
|
+
const id = /\bid=["']([^"']+)["']/i.exec(control)?.[1];
|
|
158
|
+
if (id && new RegExp(`<label\\b[^>]*\\bfor=["']${id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}["']`, "i").test(text)) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
return !/<label\b/i.test(text);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function findUnnamedButton(text: string): boolean {
|
|
166
|
+
const buttons = text.match(/<button\b[\s\S]*?<\/button>/gi) ?? [];
|
|
167
|
+
return buttons.some((button) => {
|
|
168
|
+
if (/\b(aria-label|aria-labelledby|title)=/i.test(button)) return false;
|
|
169
|
+
const inner = button.replace(/<button\b[^>]*>/i, "").replace(/<\/button>/i, "").replace(/<[^>]+>/g, "").trim();
|
|
170
|
+
return inner.length === 0;
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
60
174
|
function routeName(path: string): string {
|
|
61
175
|
if (path === "/") return "home";
|
|
62
176
|
return path.replace(/^\//, "").replace(/[^a-zA-Z0-9]+/g, "-") || "route";
|
|
@@ -641,6 +755,9 @@ function selectScenarios(options: UiCommandOptions, scenarios: UiScenario[]): Ui
|
|
|
641
755
|
}
|
|
642
756
|
|
|
643
757
|
export async function runUiCommand(options: UiCommandOptions): Promise<UiCommandResult> {
|
|
758
|
+
if (options.subcommand === "audit") {
|
|
759
|
+
return runUiAudit(options);
|
|
760
|
+
}
|
|
644
761
|
if (options.subcommand === "doctor") {
|
|
645
762
|
return runUiDoctor(options);
|
|
646
763
|
}
|
|
@@ -671,6 +788,98 @@ export async function runUiCommand(options: UiCommandOptions): Promise<UiCommand
|
|
|
671
788
|
};
|
|
672
789
|
}
|
|
673
790
|
|
|
791
|
+
function runUiAudit(options: UiCommandOptions): UiCommandResult {
|
|
792
|
+
const manifest = loadUiManifest(options.workspaceRoot);
|
|
793
|
+
const scenarios = loadUiScenarios(options.workspaceRoot);
|
|
794
|
+
const diagnostics: Diagnostic[] = [];
|
|
795
|
+
const webSources = listWebSourceFiles(options.workspaceRoot, manifest.webRoot);
|
|
796
|
+
const webText = webSources.map((source) => source.text).join("\n");
|
|
797
|
+
const appShellText = webSources
|
|
798
|
+
.filter((source) => !source.path.endsWith("/lib/workos-auth.tsx"))
|
|
799
|
+
.map((source) => source.text)
|
|
800
|
+
.join("\n");
|
|
801
|
+
const scenarioRoutes = new Set(scenarios.map((scenario) => scenario.route));
|
|
802
|
+
const scenarioNames = scenarios.map((scenario) => scenario.name);
|
|
803
|
+
const selectorSet = new Set(manifest.selectors);
|
|
804
|
+
|
|
805
|
+
if (manifest.routes.length === 0) {
|
|
806
|
+
diagnostics.push(diagnostic("warning", "FORGE_UI_ROUTE_FAILED", "No frontend routes are present in uiTestManifest."));
|
|
807
|
+
}
|
|
808
|
+
if (manifest.routes.length > 0 && scenarios.length === 0) {
|
|
809
|
+
diagnostics.push(diagnostic("error", "FORGE_UI_TESTID_MISSING", "Frontend routes exist but no UI scenarios were generated."));
|
|
810
|
+
}
|
|
811
|
+
for (const route of manifest.routes) {
|
|
812
|
+
if (!scenarioRoutes.has(route.path)) {
|
|
813
|
+
diagnostics.push(diagnostic("warning", "FORGE_UI_ROUTE_FAILED", `No UI scenario covers route '${route.path}'.`));
|
|
814
|
+
}
|
|
815
|
+
const usesRuntime = route.uses.commands.length > 0 || route.uses.queries.length > 0 || route.uses.liveQueries.length > 0;
|
|
816
|
+
if (usesRuntime && selectorSet.size === 0) {
|
|
817
|
+
diagnostics.push(diagnostic("warning", "FORGE_UI_TESTID_MISSING", `Route '${route.path}' uses Forge runtime bindings but no stable data-forge-testid selectors were detected.`));
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
const hasPolicySensitiveRuntime = scenarios.some((scenario) =>
|
|
821
|
+
scenario.requires.policies.length > 0 || scenario.requires.commands.length > 0
|
|
822
|
+
);
|
|
823
|
+
if (hasPolicySensitiveRuntime && !scenarioNames.some((name) => name.includes("policy-denied"))) {
|
|
824
|
+
diagnostics.push(diagnostic("warning", "FORGE_UI_POLICY_ERROR_MISSING", "Policy-sensitive UI flows should include a visible policy-denied scenario."));
|
|
825
|
+
}
|
|
826
|
+
if (manifest.routes.length > 0 && !manifest.selectors.some((selector) => selector.includes("data-forge-testid"))) {
|
|
827
|
+
diagnostics.push(diagnostic("warning", "FORGE_UI_TESTID_MISSING", "No data-forge-testid selectors were captured for UI smoke/audit stability."));
|
|
828
|
+
}
|
|
829
|
+
if (manifest.webRoot && manifest.routes.length > 0 && webSources.length === 0) {
|
|
830
|
+
diagnostics.push(diagnostic("warning", "FORGE_UI_SOURCE_MISSING", `No frontend source files were found under '${manifest.webRoot}' for static UX audit.`));
|
|
831
|
+
}
|
|
832
|
+
if (webSources.length > 0 && !webSources.some((source) => hasMainLandmark(source.text))) {
|
|
833
|
+
diagnostics.push(diagnostic("warning", "FORGE_UI_LANDMARK_MISSING", "Frontend source does not appear to include a main/header/nav landmark; add semantic landmarks for scanning and accessibility."));
|
|
834
|
+
}
|
|
835
|
+
for (const source of webSources) {
|
|
836
|
+
if (findFormWithoutLabel(source.text)) {
|
|
837
|
+
diagnostics.push(diagnostic("warning", "FORGE_UI_FORM_LABEL_MISSING", "Form controls should have labels or aria-label/aria-labelledby for accessible testing and real users.", source.path));
|
|
838
|
+
}
|
|
839
|
+
if (findUnnamedButton(source.text)) {
|
|
840
|
+
diagnostics.push(diagnostic("warning", "FORGE_UI_BUTTON_NAME_MISSING", "Icon-only or empty buttons should include aria-label/title text.", source.path));
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
if (webSources.some((source) => hasRuntimeDataHook(source.text))) {
|
|
844
|
+
if (!hasStateText(webText, /\b(isLoading|loading|pending|skeleton|spinner|carregando|loading\.\.\.)\b/i)) {
|
|
845
|
+
diagnostics.push(diagnostic("warning", "FORGE_UI_LOADING_STATE_MISSING", "Forge data-bound UI should expose a loading or pending state."));
|
|
846
|
+
}
|
|
847
|
+
if (!hasStateText(webText, /\b(error|erro|failed|failure|FORGE_|traceId|trace)\b/i)) {
|
|
848
|
+
diagnostics.push(diagnostic("warning", "FORGE_UI_ERROR_STATE_MISSING", "Forge data-bound UI should surface runtime/policy errors with enough context to debug."));
|
|
849
|
+
}
|
|
850
|
+
if (!hasStateText(webText, /\b(empty|no\s+\w+|none|vazio|sem\s+\w+|length\s*===\s*0)\b/i)) {
|
|
851
|
+
diagnostics.push(diagnostic("warning", "FORGE_UI_EMPTY_STATE_MISSING", "Forge data-bound UI should include an empty state so first-run apps do not look broken."));
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
if ((hasTenantScopedData(options.workspaceRoot) || hasProductionAuthMode(options.workspaceRoot)) && webSources.length > 0 && !looksLikeAuthFlow(webText)) {
|
|
855
|
+
diagnostics.push(diagnostic(
|
|
856
|
+
"warning",
|
|
857
|
+
"FORGE_UI_AUTH_FLOW_MISSING",
|
|
858
|
+
"Tenant-scoped or production-auth app has no obvious sign-in/session/organization UI; local devAuth is not a production auth flow.",
|
|
859
|
+
));
|
|
860
|
+
}
|
|
861
|
+
if (
|
|
862
|
+
hasWorkOSIntegration(options.workspaceRoot) &&
|
|
863
|
+
manifest.webRoot &&
|
|
864
|
+
webSources.length > 0 &&
|
|
865
|
+
(!webPackageHasAuthKit(options.workspaceRoot) || !/AuthKitProvider/.test(appShellText) || !/getAccessToken/.test(appShellText))
|
|
866
|
+
) {
|
|
867
|
+
diagnostics.push(diagnostic(
|
|
868
|
+
"warning",
|
|
869
|
+
"FORGE_UI_WORKOS_AUTHKIT_MISSING",
|
|
870
|
+
"WorkOS integration is present, but the web app does not appear to mount AuthKitProvider and pass getAccessToken into ForgeProvider.",
|
|
871
|
+
));
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
return {
|
|
875
|
+
ok: diagnostics.every((item) => item.severity !== "error"),
|
|
876
|
+
manifest,
|
|
877
|
+
scenarios,
|
|
878
|
+
diagnostics,
|
|
879
|
+
exitCode: diagnostics.some((item) => item.severity === "error") ? 1 : 0,
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
|
|
674
883
|
function runUiDoctor(options: UiCommandOptions): UiCommandResult {
|
|
675
884
|
const manifest = loadUiManifest(options.workspaceRoot);
|
|
676
885
|
const diagnostics: Diagnostic[] = [];
|
package/src/forge/ui/types.ts
CHANGED
package/src/forge/version.ts
CHANGED