forgeos 0.1.0-alpha.7 → 0.1.0-alpha.70
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/.agents/skills/forge-agent-fabric/SKILL.md +169 -0
- package/.agents/skills/forge-agent-fabric/references/managed-workflows.md +95 -0
- package/.agents/skills/forge-agent-fabric/references/repository-analysis.md +102 -0
- package/.agents/skills/forge-agent-fabric/scripts/fabric.mjs +46 -0
- package/.agents/skills/forge-agent-fabric/scripts/repository.mjs +44 -0
- package/.npmignore +10 -0
- package/AGENTS.md +142 -90
- package/CHANGELOG.md +864 -9
- package/LICENSE +21 -0
- package/README.md +121 -6
- 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 +47 -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 +49 -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/agent-fabric-programs.md +168 -0
- package/docs/agent-fabric.md +584 -0
- package/docs/architecture/agent-fabric/PORTABLE_PROJECTS_PLAN.md +136 -0
- package/docs/architecture/agent-fabric/README.md +180 -0
- package/docs/architecture/agent-fabric/REPOSITORY_MANIFEST_PLAN.md +284 -0
- package/docs/architecture/agent-fabric/WORKFLOW_PROGRAM_V2_ADR.md +41 -0
- package/docs/architecture/agent-fabric/WORKFLOW_PROGRAM_V2_EVIDENCE.md +109 -0
- package/docs/architecture/agent-fabric/WORKFLOW_PROGRAM_V2_PLAN.md +550 -0
- package/docs/cair-protocol.md +103 -0
- package/docs/changelog.md +1070 -0
- package/docs/forge-protocol.md +189 -0
- package/docs/repository-agent-benchmark.md +179 -0
- package/docs/repository-analysis.md +139 -0
- package/docs/repository-context-quality.md +179 -0
- package/docs/repository-runtime-observation.md +82 -0
- package/examples/agent-fabric-v2/README.md +54 -0
- package/examples/agent-fabric-v2/make-request.mjs +4 -0
- package/examples/agent-fabric-v2/migrate.workflow.ts +35 -0
- package/examples/agent-fabric-v2/registry.example.json +256 -0
- package/examples/agent-fabric-v2/workers/check.mjs +2 -0
- package/examples/agent-fabric-v2/workers/common.mjs +7 -0
- package/examples/agent-fabric-v2/workers/discover.mjs +2 -0
- package/examples/agent-fabric-v2/workers/migrate.mjs +4 -0
- package/examples/agent-fabric-v2/workers/review.mjs +3 -0
- package/examples/go-billing/go.mod +7 -0
- package/examples/go-billing/main.go +120 -0
- package/examples/java-billing/pom.xml +60 -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 +58 -5
- package/schemas/agent-fabric/v0.1/control-event.schema.json +281 -0
- package/schemas/forge-manifest.schema.json +389 -0
- package/scripts/field-test-forgeos.mjs +1021 -0
- package/scripts/install-agent-fabric-skill.mjs +105 -0
- package/src/forge/_generated/releaseManifest.json +1 -2
- package/src/forge/_generated/releaseManifest.ts +3 -3
- package/src/forge/agent-adapters/index.ts +1592 -125
- package/src/forge/agent-adapters/types.ts +225 -1
- package/src/forge/agent-fabric/adapter.ts +131 -0
- package/src/forge/agent-fabric/attached-snapshot.ts +55 -0
- package/src/forge/agent-fabric/attached-task-contract.ts +105 -0
- package/src/forge/agent-fabric/attached-task-service.ts +127 -0
- package/src/forge/agent-fabric/attached-task-store.ts +109 -0
- package/src/forge/agent-fabric/authority.ts +298 -0
- package/src/forge/agent-fabric/canonical.ts +135 -0
- package/src/forge/agent-fabric/codex-adversarial-review.ts +229 -0
- package/src/forge/agent-fabric/codex-sdk-worker.ts +201 -0
- package/src/forge/agent-fabric/conductor.ts +754 -0
- package/src/forge/agent-fabric/dictionary.ts +12 -0
- package/src/forge/agent-fabric/errors.ts +32 -0
- package/src/forge/agent-fabric/hardened-conductor.ts +485 -0
- package/src/forge/agent-fabric/hardened-reducer.ts +211 -0
- package/src/forge/agent-fabric/index.ts +48 -0
- package/src/forge/agent-fabric/journal.ts +116 -0
- package/src/forge/agent-fabric/local-adaptive-digest-worker.mjs +26 -0
- package/src/forge/agent-fabric/local-adaptive-harness.ts +343 -0
- package/src/forge/agent-fabric/local-adaptive-service.ts +398 -0
- package/src/forge/agent-fabric/local-adaptive-worker.ts +146 -0
- package/src/forge/agent-fabric/local-approval-window.ts +211 -0
- package/src/forge/agent-fabric/local-change-review-service.ts +374 -0
- package/src/forge/agent-fabric/local-coding-worker.ts +285 -0
- package/src/forge/agent-fabric/local-control-store.ts +239 -0
- package/src/forge/agent-fabric/local-effect-broker.ts +348 -0
- package/src/forge/agent-fabric/local-evolution-approval.ts +81 -0
- package/src/forge/agent-fabric/local-evolution-profile.ts +87 -0
- package/src/forge/agent-fabric/local-evolution-registry.ts +427 -0
- package/src/forge/agent-fabric/local-evolution-service.ts +200 -0
- package/src/forge/agent-fabric/local-intelligence.ts +281 -0
- package/src/forge/agent-fabric/local-paths.ts +18 -0
- package/src/forge/agent-fabric/local-task-contract.ts +203 -0
- package/src/forge/agent-fabric/local-task-inbox.ts +543 -0
- package/src/forge/agent-fabric/local-task-server.ts +374 -0
- package/src/forge/agent-fabric/local-task-service.ts +982 -0
- package/src/forge/agent-fabric/local-verification.ts +551 -0
- package/src/forge/agent-fabric/managed-environment.ts +205 -0
- package/src/forge/agent-fabric/managed-run-contract.ts +103 -0
- package/src/forge/agent-fabric/managed-run-service.ts +401 -0
- package/src/forge/agent-fabric/managed-run-store.ts +165 -0
- package/src/forge/agent-fabric/managed-workspace.ts +253 -0
- package/src/forge/agent-fabric/p0a.ts +104 -0
- package/src/forge/agent-fabric/p0b-model-adapter.ts +434 -0
- package/src/forge/agent-fabric/planning.ts +155 -0
- package/src/forge/agent-fabric/program-contract.ts +229 -0
- package/src/forge/agent-fabric/program-dsl.ts +112 -0
- package/src/forge/agent-fabric/program-service.ts +619 -0
- package/src/forge/agent-fabric/program-store.ts +117 -0
- package/src/forge/agent-fabric/program-types.ts +98 -0
- package/src/forge/agent-fabric/program-worker.ts +91 -0
- package/src/forge/agent-fabric/project-profile.ts +50 -0
- package/src/forge/agent-fabric/project-registry.ts +120 -0
- package/src/forge/agent-fabric/project-runtime.ts +85 -0
- package/src/forge/agent-fabric/readiness.ts +26 -0
- package/src/forge/agent-fabric/reducer.ts +863 -0
- package/src/forge/agent-fabric/repository-context.ts +83 -0
- package/src/forge/agent-fabric/resource-ledger.ts +266 -0
- package/src/forge/agent-fabric/serialized-local-adapter.ts +60 -0
- package/src/forge/agent-fabric/types.ts +424 -0
- package/src/forge/agent-fabric/validation.ts +500 -0
- package/src/forge/agent-fabric/workflow-engine.ts +218 -0
- package/src/forge/agent-fabric/workflow-task-actions.ts +104 -0
- package/src/forge/agent-fabric/workflow-templates.ts +11 -0
- package/src/forge/agent-memory/bridge.ts +1728 -0
- package/src/forge/agent-memory/context-pack.ts +284 -0
- package/src/forge/agent-memory/hook-runner.ts +331 -0
- package/src/forge/agent-memory/mcp.ts +597 -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 +343 -0
- package/src/forge/agent-memory/sources/codex.ts +161 -0
- package/src/forge/agent-memory/sources/cursor.ts +35 -0
- package/src/forge/agent-memory/types.ts +215 -0
- package/src/forge/bench.ts +248 -0
- package/src/forge/brownfield-import/index.ts +896 -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 +118 -0
- package/src/forge/cair/query.ts +478 -0
- package/src/forge/cair/snapshot.ts +315 -0
- package/src/forge/cair/types.ts +255 -0
- package/src/forge/cli/adaptive.ts +51 -0
- package/src/forge/cli/ai.ts +16 -1
- package/src/forge/cli/auth.ts +459 -15
- 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 +418 -0
- package/src/forge/cli/codex-app-server.ts +877 -0
- package/src/forge/cli/commands.ts +1859 -20
- package/src/forge/cli/db.ts +209 -2
- package/src/forge/cli/deploy.ts +1339 -0
- package/src/forge/cli/deps.ts +150 -23
- package/src/forge/cli/dev.ts +1137 -53
- package/src/forge/cli/docs.ts +265 -0
- package/src/forge/cli/doctor.ts +297 -0
- package/src/forge/cli/env-placeholders.ts +62 -0
- package/src/forge/cli/evolution.ts +40 -0
- package/src/forge/cli/fabric.ts +253 -0
- package/src/forge/cli/field-test.ts +568 -0
- package/src/forge/cli/golden-path.ts +487 -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 +194 -3
- package/src/forge/cli/new.ts +169 -17
- package/src/forge/cli/next-actions.ts +23 -0
- package/src/forge/cli/output.ts +398 -6
- package/src/forge/cli/parse.ts +1377 -38
- 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/repository.ts +151 -0
- package/src/forge/cli/run.ts +42 -0
- package/src/forge/cli/secrets.ts +137 -1
- package/src/forge/cli/seed.ts +581 -0
- package/src/forge/cli/self-host.ts +56 -14
- package/src/forge/cli/studio.ts +2282 -0
- package/src/forge/cli/verify.ts +1458 -32
- package/src/forge/cli/windows.ts +23 -0
- package/src/forge/cli/workos.ts +3509 -0
- package/src/forge/compiler/agent-contract/build.ts +728 -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/tsconfig-hash.ts +41 -4
- 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/build.ts +29 -1
- 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 +38 -0
- package/src/forge/compiler/diagnostics/create.ts +28 -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 +217 -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 +991 -33
- 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 +1439 -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 +249 -10
- package/src/forge/compiler/orchestrator/profile.ts +65 -0
- package/src/forge/compiler/orchestrator/run.ts +101 -33
- 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-graph/dts-extractor.ts +14 -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/profiles.ts +81 -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 +76 -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/broker-runner.mjs +5 -0
- package/src/forge/delta/broker.ts +429 -0
- package/src/forge/delta/classifier.ts +52 -0
- package/src/forge/delta/explain.ts +238 -0
- package/src/forge/delta/git-observer.ts +67 -0
- package/src/forge/delta/ids.ts +44 -0
- package/src/forge/delta/index.ts +25 -0
- package/src/forge/delta/process-identity.ts +37 -0
- package/src/forge/delta/queue-status.ts +190 -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 +1139 -0
- package/src/forge/delta/store.ts +3329 -0
- package/src/forge/delta/timeline.ts +178 -0
- package/src/forge/dev/server.ts +1061 -22
- package/src/forge/dev/types.ts +15 -1
- package/src/forge/dev/watch.ts +17 -7
- package/src/forge/dev-console/cycle.ts +285 -33
- package/src/forge/dev-console/types.ts +47 -1
- package/src/forge/impact/index.ts +222 -30
- 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 +42 -6
- package/src/forge/make/templates.ts +211 -4
- 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/repository-analysis/adapters/common.ts +72 -0
- package/src/forge/repository-analysis/adapters/docker.ts +185 -0
- package/src/forge/repository-analysis/adapters/java.ts +133 -0
- package/src/forge/repository-analysis/adapters/typescript.ts +350 -0
- package/src/forge/repository-analysis/adapters/vue.ts +71 -0
- package/src/forge/repository-analysis/analyze.ts +434 -0
- package/src/forge/repository-analysis/benchmark.ts +336 -0
- package/src/forge/repository-analysis/cache-gc.ts +154 -0
- package/src/forge/repository-analysis/check-selection.ts +123 -0
- package/src/forge/repository-analysis/context-node.ts +17 -0
- package/src/forge/repository-analysis/context.ts +456 -0
- package/src/forge/repository-analysis/http-resolution.ts +135 -0
- package/src/forge/repository-analysis/identity.ts +45 -0
- package/src/forge/repository-analysis/index.ts +18 -0
- package/src/forge/repository-analysis/nuxt-resolution.ts +188 -0
- package/src/forge/repository-analysis/quality.ts +60 -0
- package/src/forge/repository-analysis/retrieval.ts +309 -0
- package/src/forge/repository-analysis/runtime-artifacts.ts +195 -0
- package/src/forge/repository-analysis/runtime-observation.ts +325 -0
- package/src/forge/repository-analysis/scanner.ts +174 -0
- package/src/forge/repository-analysis/storage.ts +193 -0
- package/src/forge/repository-analysis/test-associations.ts +59 -0
- package/src/forge/repository-analysis/types.ts +99 -0
- package/src/forge/repository-manifest/index.ts +178 -0
- package/src/forge/repository-manifest/types.ts +63 -0
- package/src/forge/review/index.ts +162 -12
- package/src/forge/review/types.ts +15 -0
- package/src/forge/runtime/ai/providers.ts +19 -4
- 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 +285 -2
- package/src/forge/runtime/executor.ts +129 -16
- package/src/forge/runtime/external/bridge.ts +663 -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 +71 -7
- package/src/forge/ui/index.ts +831 -19
- package/src/forge/ui/types.ts +3 -1
- 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 +292 -0
- package/src/forge/workspace/forge-cli.ts +42 -0
- package/src/forge/workspace/git-summary.ts +393 -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 +34 -0
- package/templates/agent-workroom/pnpm-workspace.yaml +2 -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 +13 -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 +20 -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/agent-workroom/web/vite.config.ts +36 -0
- package/templates/b2b-support-web/package.json +2 -0
- package/templates/b2b-support-web/pnpm-workspace.yaml +2 -0
- package/templates/b2b-support-web/web/app/page.tsx +2 -2
- package/templates/b2b-support-web/web/lib/forge.ts +9 -2
- package/templates/b2b-support-web/web/next.config.ts +24 -0
- package/templates/b2b-support-web/web/package.json +1 -1
- package/templates/minimal-web/package.json +2 -1
- package/templates/minimal-web/pnpm-workspace.yaml +2 -0
- package/templates/minimal-web/web/index.html +1 -0
- package/templates/minimal-web/web/src/App.tsx +12 -6
- package/templates/minimal-web/web/src/lib/forge.ts +9 -2
- package/templates/minimal-web/web/src/styles.css +34 -0
- package/templates/minimal-web/web/vite.config.ts +36 -0
- package/templates/nuxt-web/.vscode/settings.json +14 -0
- package/templates/nuxt-web/.yarnrc.yml +1 -0
- package/templates/nuxt-web/README.md +30 -0
- package/templates/nuxt-web/forge.config.ts +3 -0
- package/templates/nuxt-web/package.json +37 -0
- package/templates/nuxt-web/pnpm-workspace.yaml +2 -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 +22 -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/templates/vendor-access/.vscode/settings.json +14 -0
- package/templates/vendor-access/README.md +30 -0
- package/templates/vendor-access/forge.config.ts +3 -0
- package/templates/vendor-access/package.json +35 -0
- package/templates/vendor-access/pnpm-workspace.yaml +2 -0
- package/templates/vendor-access/src/commands/addEvidence.ts +42 -0
- package/templates/vendor-access/src/commands/approveAccessRequest.ts +43 -0
- package/templates/vendor-access/src/commands/createAccessRequest.ts +49 -0
- package/templates/vendor-access/src/commands/seedVendorAccessDemo.ts +200 -0
- package/templates/vendor-access/src/forge/schema.ts +74 -0
- package/templates/vendor-access/src/policies.ts +11 -0
- package/templates/vendor-access/src/queries/listVendorAccessDashboard.ts +24 -0
- package/templates/vendor-access/src/queries/liveVendorAccessDashboard.ts +24 -0
- package/templates/vendor-access/tsconfig.json +17 -0
- package/templates/vendor-access/web/index.html +13 -0
- package/templates/vendor-access/web/package.json +21 -0
- package/templates/vendor-access/web/src/App.tsx +573 -0
- package/templates/vendor-access/web/src/lib/forge.ts +20 -0
- package/templates/vendor-access/web/src/main.tsx +205 -0
- package/templates/vendor-access/web/src/styles.css +682 -0
- package/templates/vendor-access/web/tsconfig.json +18 -0
- package/templates/vendor-access/web/vite.config.ts +36 -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 -14937
- 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 -3452
- 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
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import type { RepositoryEdge, RepositoryNode, RepositorySnapshot } from "./types.ts";
|
|
3
|
+
import { repositoryNodeLookup, repositoryNodeSummaryLookup, repositoryEdgeLookup } from "./storage.ts";
|
|
4
|
+
import { compactRepositoryContextNode, compactRepositoryMetadata } from "./context-node.ts";
|
|
5
|
+
|
|
6
|
+
const STOP = new Set("a as ao aos o os de da das do dos e em no na nos nas um uma para por com que se este esta nesse nessa neste nesta eu voce quero favor adicione adicionar implemente implementar corrija corrigir ajuste alterar melhore melhorar fazer faca precisa projeto repositorio arquivo arquivos the and or of to for with in on at an this that please implement add fix update change improve repository project file files should must using use".split(" "));
|
|
7
|
+
const ALIASES: Record<string, string[]> = {
|
|
8
|
+
autenticacao: ["auth", "authentication", "login"], autenticar: ["auth", "authenticate", "login"],
|
|
9
|
+
autorizacao: ["authorization", "permission", "policy"], permissoes: ["permission", "permissions"],
|
|
10
|
+
usuario: ["user", "users"], usuarios: ["user", "users"], pedidos: ["order", "orders"], pedido: ["order", "orders"],
|
|
11
|
+
cliente: ["customer", "customers", "client", "clients"], clientes: ["customer", "customers", "client", "clients"],
|
|
12
|
+
pagamento: ["payment", "payments"], pagamentos: ["payment", "payments"], fatura: ["invoice", "invoices"],
|
|
13
|
+
excluir: ["delete", "remove"], exclusao: ["delete", "remove"], criar: ["create"], criacao: ["create"],
|
|
14
|
+
consultar: ["query", "get", "list"], busca: ["search", "find"], teste: ["test", "tests"], testes: ["test", "tests"],
|
|
15
|
+
agendamento: ["schedule", "scheduling", "booking"], agendamentos: ["schedule", "scheduling", "booking"],
|
|
16
|
+
validacao: ["validate", "validation"], validar: ["validate", "validation"],
|
|
17
|
+
homologacao: ["homologation", "staging"], categoria: ["category"], categorias: ["category", "categories"],
|
|
18
|
+
};
|
|
19
|
+
const SUPPORT_EDGES = new Set(["references", "calls", "imports", "renders", "routes-to", "tests", "test-references", "test-exercises", "test-file-depends-on", "implements", "depends-on", "connects-to", "builds", "runs"]);
|
|
20
|
+
const PRIORITY: Record<string, number> = { symbol: 20, endpoint: 20, "ui-component": 18, page: 18, test: 16, "container-service": 12, package: 10, component: 8, file: 5 };
|
|
21
|
+
const GENERIC_ACTION_TERMS = new Set("validate validation validar validacao check get set query consultar list search find busca create criar criacao update delete remove excluir exclusao test tests teste testes".split(" "));
|
|
22
|
+
|
|
23
|
+
export function normalizeRepositoryTerms(value: string): string[] {
|
|
24
|
+
return value.replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2")
|
|
25
|
+
.normalize("NFD").replace(/\p{M}/gu, "").toLowerCase().match(/[\p{L}\p{N}]+/gu) ?? [];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function repositoryQueryTerms(query: string): string[] {
|
|
29
|
+
const result = new Set<string>();
|
|
30
|
+
for (const term of normalizeRepositoryTerms(query.slice(0, 4000))) {
|
|
31
|
+
if (term.length < 2 || STOP.has(term)) continue;
|
|
32
|
+
result.add(term);
|
|
33
|
+
for (const alias of ALIASES[term] ?? []) result.add(alias);
|
|
34
|
+
for (const [portuguese, aliases] of Object.entries(ALIASES)) if (aliases.includes(term)) {
|
|
35
|
+
result.add(portuguese); for (const alias of aliases) result.add(alias);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return [...result].slice(0, 80);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function scopeMatch(file: string | undefined, scope: string[]): boolean {
|
|
42
|
+
if (!file) return false;
|
|
43
|
+
const normalize = (value: string) => value.replace(/\\/g, "/").split("/").filter(part => part && part !== ".").join("/");
|
|
44
|
+
const actual = normalize(file);
|
|
45
|
+
return scope.some(path => {
|
|
46
|
+
const normalizedInput = path.replace(/\\/g, "/");
|
|
47
|
+
if (!normalizedInput || normalizedInput.startsWith("/") || /^[a-zA-Z]:/.test(normalizedInput) || normalizedInput.split("/").includes("..")) return false;
|
|
48
|
+
const prefix = normalize(path); return !prefix || actual === prefix || actual.startsWith(`${prefix}/`);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface RepositoryCandidate {
|
|
53
|
+
node: RepositoryNode;
|
|
54
|
+
score: number;
|
|
55
|
+
selectionReasons: string[];
|
|
56
|
+
inWriteScope: boolean;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function rankRepositoryCandidates(snapshot: RepositorySnapshot, query: string, options: { writeScope?: string[]; includeCallSites?: boolean } = {}): RepositoryCandidate[] {
|
|
60
|
+
const text = query.trim();
|
|
61
|
+
const terms = repositoryQueryTerms(text);
|
|
62
|
+
const explicit = new Set(text.split(/[\s`"',;]+/).map(value => value.replace(/^name=/, "").replace(/^\.\//, "")).filter(Boolean));
|
|
63
|
+
const scope = options.writeScope ?? [];
|
|
64
|
+
const exactTargets = snapshot.nodes.filter(node => explicit.has(node.id) || explicit.has(node.name)
|
|
65
|
+
|| typeof node.metadata.qualifiedName === "string" && explicit.has(node.metadata.qualifiedName) || !!node.file && explicit.has(node.file));
|
|
66
|
+
const anchorWords = new Set(exactTargets.flatMap(node => [node.id, node.name, node.metadata.qualifiedName, node.file]).filter((value): value is string => typeof value === "string"));
|
|
67
|
+
// A standalone symbol/path, optionally surrounded by instruction words, defines a focused target.
|
|
68
|
+
// Additional domain words (e.g. "agendamento ScheduleApi") intentionally retain cross-file discovery.
|
|
69
|
+
const meaningfulWords = [...explicit].filter(word => !normalizeRepositoryTerms(word).every(term => STOP.has(term)));
|
|
70
|
+
const focused = exactTargets.length > 0 && meaningfulWords.length === 1 && [...explicit].every(word => anchorWords.has(word)
|
|
71
|
+
|| normalizeRepositoryTerms(word).every(term => STOP.has(term)));
|
|
72
|
+
const anchoredFiles = new Set(exactTargets.map(node => node.file).filter(Boolean));
|
|
73
|
+
const domainTerms = terms.filter(term => !GENERIC_ACTION_TERMS.has(term));
|
|
74
|
+
const termFrequency = new Map<string, number>();
|
|
75
|
+
for (const node of snapshot.nodes) {
|
|
76
|
+
const observed = new Set(normalizeRepositoryTerms(`${node.name} ${node.file ?? ""} ${node.metadata.qualifiedName ?? ""}`));
|
|
77
|
+
for (const term of terms) if (observed.has(term)) termFrequency.set(term, (termFrequency.get(term) ?? 0) + 1);
|
|
78
|
+
}
|
|
79
|
+
const candidates: RepositoryCandidate[] = [];
|
|
80
|
+
for (const node of snapshot.nodes) {
|
|
81
|
+
if (node.metadata.suite === true || node.kind === "test-suite") continue;
|
|
82
|
+
const exactId = explicit.has(node.id);
|
|
83
|
+
const exactName = explicit.has(node.name) || (typeof node.metadata.qualifiedName === "string" && explicit.has(node.metadata.qualifiedName));
|
|
84
|
+
const exactPath = !!node.file && explicit.has(node.file);
|
|
85
|
+
if (node.kind === "java-call" && !options.includeCallSites && !exactId) continue;
|
|
86
|
+
const nameTerms = new Set(normalizeRepositoryTerms(node.name));
|
|
87
|
+
const fileTerms = new Set(normalizeRepositoryTerms(`${node.file ?? ""} ${node.component}`));
|
|
88
|
+
const descriptive = new Set(normalizeRepositoryTerms(["qualifiedName", "path", "method", "javaClass", "signature"]
|
|
89
|
+
.map(key => typeof node.metadata[key] === "string" ? node.metadata[key] : "").join(" ")));
|
|
90
|
+
const matched = terms.filter(term => nameTerms.has(term) || fileTerms.has(term) || descriptive.has(term));
|
|
91
|
+
const inWriteScope = scopeMatch(node.file, scope);
|
|
92
|
+
if (focused && !exactId && !exactName && !exactPath && !inWriteScope && !anchoredFiles.has(node.file)) continue;
|
|
93
|
+
// Generic actions such as "validate" cannot independently attach another feature to a domain-specific instruction.
|
|
94
|
+
if (!focused && domainTerms.length && matched.length && !exactId && !exactName && !exactPath && !inWriteScope
|
|
95
|
+
&& !matched.some(term => domainTerms.includes(term))) continue;
|
|
96
|
+
// Sharing a test filename is not evidence that every sibling case exercises the requested feature.
|
|
97
|
+
if (node.kind === "test" && terms.length && !exactId && !exactName && !exactPath && !inWriteScope
|
|
98
|
+
&& !terms.some(term => nameTerms.has(term) || descriptive.has(term))) continue;
|
|
99
|
+
const reasons: string[] = [];
|
|
100
|
+
let score = 0;
|
|
101
|
+
if (exactId) { score += 1000; reasons.push("exact-node-id"); }
|
|
102
|
+
if (exactName) { score += 250; reasons.push("exact-symbol-or-name"); }
|
|
103
|
+
if (exactPath) { score += 220; reasons.push("exact-file-path"); }
|
|
104
|
+
if (matched.length) {
|
|
105
|
+
const rarity = (term: string) => 1 + Math.log(1 + snapshot.nodes.length / (1 + (termFrequency.get(term) ?? 0)));
|
|
106
|
+
const proportion = matched.length / Math.max(1, terms.length);
|
|
107
|
+
score += matched.reduce((total, term) => total + (nameTerms.has(term) ? 25 : descriptive.has(term) ? 15 : 8)
|
|
108
|
+
* rarity(term) * (GENERIC_ACTION_TERMS.has(term) ? 0.5 : 1), 0) * (0.5 + 0.5 * proportion);
|
|
109
|
+
reasons.push(`matched-terms:${matched.slice(0, 8).join(",")}`);
|
|
110
|
+
if (focused) reasons.push("focused-exact-target");
|
|
111
|
+
}
|
|
112
|
+
if (!score && terms.length && !inWriteScope) continue;
|
|
113
|
+
if (inWriteScope) { score += score ? 80 : 10; reasons.push("within-declared-write-scope"); }
|
|
114
|
+
if (!score && !["component", "package", "endpoint", "page", "symbol", "ui-component", "test"].includes(node.kind)) continue;
|
|
115
|
+
score += PRIORITY[node.kind] ?? 0;
|
|
116
|
+
if (node.kind === "symbol") {
|
|
117
|
+
const declaration = typeof node.metadata.symbolKind === "string" ? node.metadata.symbolKind : "";
|
|
118
|
+
if (node.metadata.exported === true) { score += 40; reasons.push("exported-declaration"); }
|
|
119
|
+
else if (/function|method|class|interface|record/i.test(declaration)) { score += 15; reasons.push("callable-or-type-declaration"); }
|
|
120
|
+
else if (/variable|const/i.test(declaration)) score -= 10;
|
|
121
|
+
}
|
|
122
|
+
score += node.evidence.assurance === "resolved" ? 8 : node.evidence.assurance === "syntactic" ? 5 : node.evidence.assurance === "declared" ? 3 : 0;
|
|
123
|
+
score += node.evidence.resolution === "complete" ? 3 : 0;
|
|
124
|
+
if (!reasons.length) reasons.push("representative-context");
|
|
125
|
+
candidates.push({ node, score, selectionReasons: reasons, inWriteScope });
|
|
126
|
+
}
|
|
127
|
+
return candidates.sort((a, b) => b.score - a.score || a.node.id.localeCompare(b.node.id));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Aggregate syntactic Java call sites; this never claims dispatch/type resolution. */
|
|
131
|
+
export function groupRepositoryJavaCalls(nodes: RepositoryNode[]): RepositoryNode[] {
|
|
132
|
+
const groups = new Map<string, { first: RepositoryNode; count: number; samples: string[] }>();
|
|
133
|
+
for (const node of nodes) {
|
|
134
|
+
if (node.kind !== "java-call") continue;
|
|
135
|
+
const key = JSON.stringify([node.file, node.metadata.owner, node.metadata.qualifier, node.name]);
|
|
136
|
+
const group = groups.get(key) ?? { first: node, count: 0, samples: [] };
|
|
137
|
+
group.count++; if (group.samples.length < 3) group.samples.push(node.id); groups.set(key, group);
|
|
138
|
+
}
|
|
139
|
+
return [...groups].map(([key, group]) => {
|
|
140
|
+
const first = group.first;
|
|
141
|
+
return { ...first, id: `java-call-group:${createHash("sha256").update(key).digest("hex").slice(0, 24)}`,
|
|
142
|
+
kind: "java-call-group", metadata: { owner: first.metadata.owner, qualifier: first.metadata.qualifier,
|
|
143
|
+
callSiteCount: group.count, callSiteSampleIds: group.samples, resolution: "syntactic-call-sites-only" } };
|
|
144
|
+
}).sort((a, b) => a.id.localeCompare(b.id));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface RepositoryContextSelectionOptions { writeScope?: string[]; maxChars?: number; maxNodes?: number; maxEdges?: number }
|
|
148
|
+
export interface RepositoryContextPacket {
|
|
149
|
+
provider: "repository";
|
|
150
|
+
snapshotId: string;
|
|
151
|
+
writeScope: string[];
|
|
152
|
+
writeScopeOmitted?: true;
|
|
153
|
+
queryTerms: string[];
|
|
154
|
+
limitations: string[];
|
|
155
|
+
nodes: Array<{ id: string; kind: string; name: string; file?: string; component: string; location?: RepositoryNode["location"]; evidence: RepositoryNode["evidence"]; metadata: Record<string, unknown>; access: "write-scope" | "read-only"; selectionReasons: string[] }>;
|
|
156
|
+
edges: Array<Pick<RepositoryEdge, "id" | "from" | "to" | "kind" | "evidence" | "metadata">>;
|
|
157
|
+
groups: Array<{ subject: string; consumers: string[]; tests: string[]; related: string[] }>;
|
|
158
|
+
metrics: { candidates: number; selected: number; readOnlyExpansion: number; unresolvedCandidates: number; javaCallSitesOmitted: number; unsupportedFiles: number; analysisErrors: number; omittedRelations: number };
|
|
159
|
+
truncated: boolean;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Deterministic context planning only. Scope is copied verbatim and never widened by graph expansion. */
|
|
163
|
+
export function selectRepositoryContext(snapshot: RepositorySnapshot, query: string, options: RepositoryContextSelectionOptions = {}): RepositoryContextPacket {
|
|
164
|
+
const bounded = (value: number | undefined, fallback: number, maximum: number) => Number.isFinite(value) ? Math.max(1, Math.min(maximum, Math.floor(value!))) : fallback;
|
|
165
|
+
const maxChars = Math.max(2048, bounded(options.maxChars, 12000, 50000));
|
|
166
|
+
const maxNodes = bounded(options.maxNodes, 30, 100);
|
|
167
|
+
const maxEdges = bounded(options.maxEdges, 30, 100);
|
|
168
|
+
const scope = [...(options.writeScope ?? [])];
|
|
169
|
+
const allRanked = rankRepositoryCandidates(snapshot, query, { writeScope: scope });
|
|
170
|
+
// A declared scope supplies the task's anchors; lexical matches elsewhere are discovered only
|
|
171
|
+
// through graph evidence, rather than unrelated repositories/features sharing a domain word.
|
|
172
|
+
const scoped = allRanked.filter(candidate => candidate.inWriteScope);
|
|
173
|
+
const ranked = scope.length ? scoped : allRanked;
|
|
174
|
+
const packet: RepositoryContextPacket = { provider: "repository", snapshotId: snapshot.snapshotId, writeScope: scope, queryTerms: repositoryQueryTerms(query),
|
|
175
|
+
limitations: snapshot.coverage.limitations.slice(0, 5).map(item => item.slice(0, 256)),
|
|
176
|
+
nodes: [], edges: [], groups: [], metrics: { candidates: ranked.length, selected: 0, readOnlyExpansion: 0,
|
|
177
|
+
unresolvedCandidates: ranked.filter(candidate => candidate.node.evidence.resolution !== "complete").length,
|
|
178
|
+
javaCallSitesOmitted: snapshot.nodes.filter(node => node.kind === "java-call").length,
|
|
179
|
+
unsupportedFiles: snapshot.coverage.unsupported, analysisErrors: snapshot.coverage.errors, omittedRelations: 0 }, truncated: false };
|
|
180
|
+
const byId = repositoryNodeLookup(snapshot);
|
|
181
|
+
const summaries = repositoryNodeSummaryLookup(snapshot);
|
|
182
|
+
const adjacency = repositoryEdgeLookup(snapshot);
|
|
183
|
+
const httpByFile = new Map<string, RepositoryNode[]>();
|
|
184
|
+
for (const node of snapshot.nodes) if (node.kind === "http-call" && node.file) {
|
|
185
|
+
const calls = httpByFile.get(node.file) ?? []; calls.push(node); httpByFile.set(node.file, calls);
|
|
186
|
+
}
|
|
187
|
+
const pickMetadata = compactRepositoryMetadata;
|
|
188
|
+
const selected = new Set<string>();
|
|
189
|
+
const addNode = (node: RepositoryNode, reasons: string[]): boolean => {
|
|
190
|
+
if (selected.has(node.id)) return true;
|
|
191
|
+
if (packet.nodes.length >= maxNodes) return false;
|
|
192
|
+
const inScope = scopeMatch(node.file, scope);
|
|
193
|
+
const entry = compactRepositoryContextNode(node, inScope ? "write-scope" : "read-only", reasons);
|
|
194
|
+
packet.nodes.push(entry);
|
|
195
|
+
if (JSON.stringify(packet).length + 300 > maxChars) { packet.nodes.pop(); return false; }
|
|
196
|
+
selected.add(node.id); return true;
|
|
197
|
+
};
|
|
198
|
+
// Reserve most node capacity for consumers/tests, rather than filling it with local declarations.
|
|
199
|
+
const seedCapacity = Math.max(1, Math.min(4, Math.floor(maxNodes / 3)));
|
|
200
|
+
const exactCandidates = ranked.filter(candidate => candidate.selectionReasons.some(reason => ["exact-node-id", "exact-symbol-or-name"].includes(reason)));
|
|
201
|
+
const declarations = ranked.filter(candidate => candidate.node.kind !== "file" && candidate.node.kind !== "import-reference"
|
|
202
|
+
&& (candidate.node.metadata.exported === true || /function|method|class|interface|record/i.test(String(candidate.node.metadata.symbolKind))));
|
|
203
|
+
const seedPool = exactCandidates.length ? exactCandidates : declarations.length ? declarations : ranked;
|
|
204
|
+
const capacity = scoped.length && scope.length === 1 && /\.[^/\\]+$/.test(scope[0]!) ? 1 : seedCapacity;
|
|
205
|
+
const distinctFiles = new Set<string>();
|
|
206
|
+
const diverse = scope.length > 1 ? seedPool.filter(candidate => {
|
|
207
|
+
const key = candidate.node.file ?? candidate.node.id;
|
|
208
|
+
if (distinctFiles.has(key)) return false;
|
|
209
|
+
distinctFiles.add(key); return true;
|
|
210
|
+
}) : seedPool;
|
|
211
|
+
const seeds = diverse.slice(0, capacity);
|
|
212
|
+
for (const candidate of seedPool) if (seeds.length < capacity && !seeds.includes(candidate)) seeds.push(candidate);
|
|
213
|
+
for (const seed of seeds) addNode(seed.node, seed.selectionReasons);
|
|
214
|
+
// Old snapshots may attach a request to its local response variable. Containment selects the
|
|
215
|
+
// existing request fact as context without creating a semantic edge or dispatch claim.
|
|
216
|
+
for (const seed of seeds) if (seed.node.file && seed.node.location) {
|
|
217
|
+
for (const call of httpByFile.get(seed.node.file) ?? []) if (call.location
|
|
218
|
+
&& call.location.start >= seed.node.location.start && call.location.end <= seed.node.location.end) {
|
|
219
|
+
addNode(call, ["lexical-declaration-contained-http-call", "read-only-context-expansion-does-not-authorize-edits"]);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const related: RepositoryEdge[] = [];
|
|
223
|
+
const seenEdges = new Set<string>();
|
|
224
|
+
const acceptedPairs = new Set<string>();
|
|
225
|
+
let frontier = new Set(selected);
|
|
226
|
+
for (let depth = 0; depth < 2 && frontier.size; depth++) {
|
|
227
|
+
// A second hop follows concrete control/UI connections only. Test cases, shared files and
|
|
228
|
+
// import inventories never become bridges to unrelated sibling cases or imported features.
|
|
229
|
+
const traversable = new Set([...frontier].filter(id => {
|
|
230
|
+
const node = summaries.get(id);
|
|
231
|
+
return depth === 0 || !!node && !["file", "test", "test-suite", "import", "java-call"].includes(node.kind) && node.suite !== true;
|
|
232
|
+
}));
|
|
233
|
+
const incident = new Map<string, RepositoryEdge>();
|
|
234
|
+
for (const id of traversable) for (const edge of adjacency.get(id) ?? []) incident.set(edge.id, edge);
|
|
235
|
+
// Reserve the complete request/endpoint capsule ahead of dense consumer/local-reference
|
|
236
|
+
// inventories. This follows existing edges and is still capped at two graph hops.
|
|
237
|
+
if (depth === 0) for (const edge of [...incident.values()]) if (edge.kind === "calls") {
|
|
238
|
+
const request = [edge.from, edge.to].find(id => summaries.get(id)?.kind === "http-call");
|
|
239
|
+
if (request) for (const target of adjacency.get(request) ?? []) if (target.kind === "calls"
|
|
240
|
+
&& target.from === request && summaries.get(target.to)?.kind === "endpoint") incident.set(target.id, target);
|
|
241
|
+
}
|
|
242
|
+
const edgePriority = (edge: RepositoryEdge) => {
|
|
243
|
+
const from = summaries.get(edge.from), to = summaries.get(edge.to);
|
|
244
|
+
if (edge.kind === "calls" && from?.kind === "http-call" && to?.kind === "endpoint") return -7;
|
|
245
|
+
if (edge.kind === "calls" && (from?.kind === "http-call" || to?.kind === "http-call")) return -6;
|
|
246
|
+
if (edge.kind === "test-exercises") return -5;
|
|
247
|
+
if (edge.kind === "test-references") return -4;
|
|
248
|
+
if (edge.kind === "tests") return -3;
|
|
249
|
+
if (from?.file && to?.file && from.file !== to.file && ["calls", "renders", "routes-to"].includes(edge.kind)) return -2;
|
|
250
|
+
if (from?.file && to?.file && from.file !== to.file) return -1;
|
|
251
|
+
return edge.kind === "references" && edge.metadata.binding === "lexical" ? 2 : 0;
|
|
252
|
+
};
|
|
253
|
+
const wave = [...incident.values()].filter(edge => !seenEdges.has(edge.id) && SUPPORT_EDGES.has(edge.kind)
|
|
254
|
+
&& (depth === 0 || ["calls", "renders", "routes-to"].includes(edge.kind))
|
|
255
|
+
&& (traversable.has(edge.from) || traversable.has(edge.to)
|
|
256
|
+
|| depth === 0 && edge.kind === "calls" && summaries.get(edge.from)?.kind === "http-call" && summaries.get(edge.to)?.kind === "endpoint")).sort((a, b) => {
|
|
257
|
+
return edgePriority(a) - edgePriority(b) || a.id.localeCompare(b.id);
|
|
258
|
+
});
|
|
259
|
+
related.push(...wave); wave.forEach(edge => seenEdges.add(edge.id));
|
|
260
|
+
const next = new Set<string>();
|
|
261
|
+
for (const edge of wave) {
|
|
262
|
+
if (packet.edges.length >= maxEdges) break;
|
|
263
|
+
const requiredNodes = new Set([edge.from, edge.to].filter(id => !selected.has(id))).size;
|
|
264
|
+
if (packet.nodes.length + requiredNodes > maxNodes) continue;
|
|
265
|
+
const minimumChars = [...new Set([edge.from, edge.to])].filter(id => !selected.has(id)).reduce((sum, id) => sum + (summaries.get(id)?.minimumContextChars ?? 0), 0);
|
|
266
|
+
if (JSON.stringify(packet).length + minimumChars + 300 > maxChars) continue;
|
|
267
|
+
const from = byId.get(edge.from), to = byId.get(edge.to);
|
|
268
|
+
if (!from || !to || from.kind === "java-call" || to.kind === "java-call" || from.kind === "test-suite" || to.kind === "test-suite" || from.metadata.suite === true || to.metadata.suite === true) continue;
|
|
269
|
+
const pair = JSON.stringify([edge.from, edge.to, edge.kind]);
|
|
270
|
+
if (acceptedPairs.has(pair)) continue;
|
|
271
|
+
const before = packet.nodes.length;
|
|
272
|
+
const oldIds = new Set(selected);
|
|
273
|
+
const graphDepth = depth + (traversable.has(edge.from) || traversable.has(edge.to) ? 1 : 2);
|
|
274
|
+
const reasons = [`graph-neighbor:${edge.kind}`, `graph-depth:${graphDepth}`, "read-only-context-expansion-does-not-authorize-edits"];
|
|
275
|
+
if (addNode(from, reasons) && addNode(to, reasons)) {
|
|
276
|
+
packet.edges.push({ id: edge.id, from: edge.from, to: edge.to, kind: edge.kind, evidence: edge.evidence,
|
|
277
|
+
metadata: pickMetadata(edge.metadata, ["binding", "association", "runtimeRoutingVerified", "pathPatternMatch", "requestPathPattern", "resolvedPath", "ambiguity", "scenario", "offset"]) });
|
|
278
|
+
if (JSON.stringify(packet).length + 300 <= maxChars) {
|
|
279
|
+
acceptedPairs.add(pair);
|
|
280
|
+
for (const id of selected) if (!oldIds.has(id)) next.add(id);
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
packet.edges.pop();
|
|
284
|
+
}
|
|
285
|
+
packet.nodes.splice(before); for (const id of selected) if (!oldIds.has(id)) selected.delete(id);
|
|
286
|
+
}
|
|
287
|
+
frontier = next;
|
|
288
|
+
}
|
|
289
|
+
for (const candidate of ranked) addNode(candidate.node, candidate.selectionReasons);
|
|
290
|
+
for (const seed of seeds.filter(seed => selected.has(seed.node.id))) {
|
|
291
|
+
const incoming = packet.edges.filter(edge => edge.to === seed.node.id);
|
|
292
|
+
const testRelation = (edge: RepositoryEdge | { kind: string }) => ["tests", "test-references", "test-exercises"].includes(edge.kind);
|
|
293
|
+
const group = { subject: seed.node.id, consumers: [...new Set(incoming.filter(edge => !testRelation(edge)).map(edge => edge.from))],
|
|
294
|
+
tests: [...new Set(incoming.filter(testRelation).map(edge => edge.from))],
|
|
295
|
+
related: [...new Set(packet.edges.filter(edge => edge.from === seed.node.id).map(edge => edge.to))] };
|
|
296
|
+
packet.groups.push(group);
|
|
297
|
+
if (JSON.stringify(packet).length + 200 > maxChars) { packet.groups.pop(); packet.truncated = true; break; }
|
|
298
|
+
}
|
|
299
|
+
packet.metrics.selected = packet.nodes.length;
|
|
300
|
+
packet.metrics.readOnlyExpansion = packet.nodes.filter(node => node.access === "read-only").length;
|
|
301
|
+
packet.metrics.omittedRelations = related.length - packet.edges.length;
|
|
302
|
+
packet.truncated ||= ranked.some(candidate => !selected.has(candidate.node.id)) || packet.metrics.omittedRelations > 0;
|
|
303
|
+
// Scope/query headers can themselves exceed the requested budget. Omit scope values without changing authorization.
|
|
304
|
+
if (JSON.stringify(packet).length > maxChars) {
|
|
305
|
+
packet.queryTerms = []; packet.limitations = []; packet.groups = []; packet.nodes = []; packet.edges = [];
|
|
306
|
+
packet.writeScope = []; packet.writeScopeOmitted = true; packet.metrics.selected = 0; packet.metrics.readOnlyExpansion = 0; packet.truncated = true;
|
|
307
|
+
}
|
|
308
|
+
return packet;
|
|
309
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import type { RepositoryRuntimeArtifactFormat } from "../repository-manifest/types.ts";
|
|
2
|
+
|
|
3
|
+
export interface RuntimeFact {
|
|
4
|
+
kind: string;
|
|
5
|
+
name: string;
|
|
6
|
+
component: string;
|
|
7
|
+
details: Record<string, string | string[] | number | boolean>;
|
|
8
|
+
}
|
|
9
|
+
export interface RuntimeArtifactCollection { facts: RuntimeFact[]; limitations: string[] }
|
|
10
|
+
export const RUNTIME_ARTIFACT_MAX_BYTES = 2 * 1024 * 1024;
|
|
11
|
+
export const RUNTIME_ARTIFACT_MAX_FACTS = 2000;
|
|
12
|
+
const object = (v: unknown): v is Record<string, unknown> => !!v && typeof v === "object" && !Array.isArray(v);
|
|
13
|
+
const componentId = (v: unknown): v is string => typeof v === "string" && /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/.test(v);
|
|
14
|
+
const identifier = (v: unknown): v is string => typeof v === "string" && v.length <= 256 && /^[A-Za-z_$][A-Za-z0-9_$./:@-]*$/.test(v) && !/bearer|password|secret|token|credential|api[-_]?key/i.test(v);
|
|
15
|
+
const safePath = (v: unknown): string | undefined => {
|
|
16
|
+
if (typeof v !== "string" || v.length > 1024 || !/^\/(?!\/)[A-Za-z0-9_{}:*~.,()!+@/-]*$/.test(v) || v.split("/").includes("..") || /password|secret|token|credential|api[-_]?key/i.test(v)) return undefined;
|
|
17
|
+
// Concrete IDs and opaque/high entropy segments are intentionally not retained.
|
|
18
|
+
return v.split("/").map(segment => /^\d+$/.test(segment) || segment.length > 48 || /^[a-f\d]{16,}$/i.test(segment) || /^[a-f\d]{8}(?:-[a-f\d]{4}){3}-[a-f\d]{12}$/i.test(segment) ? "{id}" : segment).join("/");
|
|
19
|
+
};
|
|
20
|
+
const sourcePath = (v: unknown): v is string => {
|
|
21
|
+
if (typeof v !== "string" || v.length > 1024 || !/^(?:\.\.?\/)*[A-Za-z0-9_$@.-][A-Za-z0-9_$@./-]*$/.test(v) || /secret|credential|\.env/i.test(v)) return false;
|
|
22
|
+
const containedTail = v.replace(/^(?:\.\.?\/)+/, "");
|
|
23
|
+
if (containedTail.split("/").some(part => !part || part === "." || part === "..")) return false;
|
|
24
|
+
return /\.(?:vue|[cm]?[jt]sx?)$/.test(v) || (/^\.\.?\//.test(v) && /^[A-Za-z0-9_$@-]+$/.test(containedTail.split("/").at(-1)!));
|
|
25
|
+
};
|
|
26
|
+
const methods = new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS", "TRACE", "CONNECT"]);
|
|
27
|
+
const factFields: Record<string, readonly string[]> = {
|
|
28
|
+
"nuxt-component": ["sourcePath"],
|
|
29
|
+
"nuxt-import": ["sourcePath", "exportName"],
|
|
30
|
+
"spring-bean": ["type", "scope", "dependencies"],
|
|
31
|
+
"docker-container": ["state", "running", "service", "networks"],
|
|
32
|
+
"runtime-resource": ["type", "scope", "service", "state", "exportName", "sourcePath", "dependencies", "networks", "running"],
|
|
33
|
+
"spring-route": ["method", "path"],
|
|
34
|
+
"http-request": ["method", "path", "status"],
|
|
35
|
+
};
|
|
36
|
+
/** Validate a saved report independently of its digest. Unknown fields never survive reload. */
|
|
37
|
+
export function validateRuntimeFact(value: unknown, component: string): value is RuntimeFact {
|
|
38
|
+
if (!object(value) || Object.keys(value).length !== 4 || !["kind", "name", "component", "details"].every(key => Object.hasOwn(value, key)) || value.component !== component || !componentId(component) || typeof value.kind !== "string" || !Object.hasOwn(factFields, value.kind) || !object(value.details)) return false;
|
|
39
|
+
const fields = factFields[value.kind]!;
|
|
40
|
+
for (const [key, item] of Object.entries(value.details)) {
|
|
41
|
+
if (!fields.includes(key)) return false;
|
|
42
|
+
if (key === "sourcePath") { if (!sourcePath(item)) return false; }
|
|
43
|
+
else if (key === "path") { if (safePath(item) !== item || typeof item !== "string") return false; }
|
|
44
|
+
else if (key === "method") { if (typeof item !== "string" || !methods.has(item)) return false; }
|
|
45
|
+
else if (key === "status") { if (!Number.isInteger(item) || (item as number) < 100 || (item as number) > 599) return false; }
|
|
46
|
+
else if (key === "running") { if (typeof item !== "boolean") return false; }
|
|
47
|
+
else if (key === "dependencies" || key === "networks") { if (!Array.isArray(item) || item.length > 32 || !item.every(identifier)) return false; }
|
|
48
|
+
else if (!identifier(item)) return false;
|
|
49
|
+
}
|
|
50
|
+
if (value.kind === "http-request" || value.kind === "spring-route") return typeof value.details.method === "string" && typeof value.details.path === "string" && value.name === `${value.details.method} ${value.details.path}`;
|
|
51
|
+
return identifier(value.name);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const artifactLimitations = new Set([
|
|
55
|
+
"Runtime artifact exceeds bounds or has invalid component",
|
|
56
|
+
"Runtime facts truncated at 2000 entries",
|
|
57
|
+
"Generated declarations describe registered resources; component rendering and call execution are not observed",
|
|
58
|
+
"Declaration sourcePath is relative to the artifact and is not a validated repository source link",
|
|
59
|
+
"Bare package and virtual alias imports are omitted; extensionless relative declarations remain artifact-relative references",
|
|
60
|
+
"Runtime artifact is malformed or exceeds structural bounds",
|
|
61
|
+
"Docker state reflects the supplied inspect export; environment, labels, addresses and mounts are omitted",
|
|
62
|
+
"HTTP evidence retains method, sanitized pathname and status only; origins, queries, headers and bodies are omitted",
|
|
63
|
+
"Pathname segments may still identify sensitive resources; field minimization cannot guarantee removal of every opaque credential",
|
|
64
|
+
"Spring mappings reflect supplied exports; handlers without explicit HTTP methods are omitted",
|
|
65
|
+
"Bean exports establish registration in one environment; invocation and profile completeness are not observed",
|
|
66
|
+
"Explicit runtime exports are producer-reported structural evidence, not authenticated execution traces",
|
|
67
|
+
"Unsupported runtime artifact format",
|
|
68
|
+
"No supported structural runtime facts found",
|
|
69
|
+
"Selected structural identifiers are producer supplied; field minimization is not a guarantee of universal secret redaction",
|
|
70
|
+
]);
|
|
71
|
+
export function isRuntimeArtifactLimitation(value: unknown): value is string {
|
|
72
|
+
return typeof value === "string" && artifactLimitations.has(value);
|
|
73
|
+
}
|
|
74
|
+
function boundedJson(text: string): unknown {
|
|
75
|
+
const parsed: unknown = JSON.parse(text);
|
|
76
|
+
let count = 0;
|
|
77
|
+
const visit = (value: unknown, depth: number): void => {
|
|
78
|
+
if (++count > 100000 || depth > 24) throw new Error("bounded");
|
|
79
|
+
if (Array.isArray(value)) for (const item of value) visit(item, depth + 1);
|
|
80
|
+
else if (object(value)) for (const item of Object.values(value)) visit(item, depth + 1);
|
|
81
|
+
};
|
|
82
|
+
visit(parsed, 0);
|
|
83
|
+
return parsed;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Pure collector. Only selected structural fields leave this boundary; raw exports never do. */
|
|
87
|
+
export function collectRuntimeArtifact(format: RepositoryRuntimeArtifactFormat, text: string, component: string): RuntimeArtifactCollection {
|
|
88
|
+
const facts: RuntimeFact[] = [], limitations = new Set<string>(), identities = new Set<string>();
|
|
89
|
+
if (!componentId(component) || typeof text !== "string" || Buffer.byteLength(text) > RUNTIME_ARTIFACT_MAX_BYTES) return { facts, limitations: ["Runtime artifact exceeds bounds or has invalid component"] };
|
|
90
|
+
const add = (kind: string, name: string, details: RuntimeFact["details"]): void => {
|
|
91
|
+
if (!validateRuntimeFact({ kind, name, component, details }, component)) return;
|
|
92
|
+
const key = JSON.stringify([kind, name, details]);
|
|
93
|
+
if (identities.has(key)) return;
|
|
94
|
+
if (facts.length >= RUNTIME_ARTIFACT_MAX_FACTS) { limitations.add("Runtime facts truncated at 2000 entries"); return; }
|
|
95
|
+
identities.add(key); facts.push({ kind, name, component, details });
|
|
96
|
+
};
|
|
97
|
+
const route = (method: unknown, rawPath: unknown, status?: unknown): void => {
|
|
98
|
+
if (typeof method !== "string" || !methods.has(method.toUpperCase()) || typeof rawPath !== "string") return;
|
|
99
|
+
let clean: string | undefined;
|
|
100
|
+
try {
|
|
101
|
+
const pathname = rawPath.startsWith("/") && !rawPath.startsWith("//") ? rawPath.split(/[?#]/)[0]! : new URL(rawPath, "http://runtime.invalid").pathname;
|
|
102
|
+
clean = safePath(pathname);
|
|
103
|
+
} catch { return; }
|
|
104
|
+
if (!clean) return;
|
|
105
|
+
const details: RuntimeFact["details"] = { method: method.toUpperCase(), path: clean };
|
|
106
|
+
if (Number.isInteger(status) && (status as number) >= 100 && (status as number) <= 599) details.status = status as number;
|
|
107
|
+
add(format === "http-trace" ? "http-request" : "spring-route", `${method.toUpperCase()} ${clean}`, details);
|
|
108
|
+
};
|
|
109
|
+
if (format === "nuxt-components" || format === "nuxt-imports") {
|
|
110
|
+
if (format === "nuxt-components") {
|
|
111
|
+
const pattern = /(?:^|[\s;{])['"]?([A-Za-z_$][A-Za-z0-9_$]{0,255})['"]?\s*:\s*(?:typeof\s+)?import\(['"]([^'"\r\n]{1,1024})['"]\)/g;
|
|
112
|
+
for (const match of text.matchAll(pattern)) if (identifier(match[1]) && sourcePath(match[2])) add("nuxt-component", match[1], { sourcePath: match[2] });
|
|
113
|
+
} else {
|
|
114
|
+
const pattern = /(?:export\s*\{([^}]{1,4096})\}\s*from\s*['"]([^'"\r\n]{1,1024})['"]|(?:const|let|var)\s+([A-Za-z_$][A-Za-z0-9_$]{0,255})\s*:\s*typeof\s+import\(['"]([^'"\r\n]{1,1024})['"]\)\[['"]([A-Za-z_$][A-Za-z0-9_$]{0,255})['"]\])/g;
|
|
115
|
+
for (const match of text.matchAll(pattern)) {
|
|
116
|
+
if (match[2] && sourcePath(match[2])) for (const entry of match[1]!.split(",")) { const names = /^\s*([A-Za-z_$][A-Za-z0-9_$]*)(?:\s+as\s+([A-Za-z_$][A-Za-z0-9_$]*))?\s*$/.exec(entry); if (names && identifier(names[1]) && identifier(names[2] ?? names[1])) add("nuxt-import", names[2] ?? names[1], { sourcePath: match[2], exportName: names[1] }); }
|
|
117
|
+
else if (sourcePath(match[4]) && identifier(match[3]) && identifier(match[5])) add("nuxt-import", match[3], { sourcePath: match[4], exportName: match[5] });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
limitations.add("Generated declarations describe registered resources; component rendering and call execution are not observed");
|
|
121
|
+
limitations.add("Declaration sourcePath is relative to the artifact and is not a validated repository source link");
|
|
122
|
+
limitations.add("Bare package and virtual alias imports are omitted; extensionless relative declarations remain artifact-relative references");
|
|
123
|
+
} else {
|
|
124
|
+
let data: unknown;
|
|
125
|
+
try { data = boundedJson(text); } catch { return { facts, limitations: ["Runtime artifact is malformed or exceeds structural bounds"] }; }
|
|
126
|
+
if (format === "docker-inspect") {
|
|
127
|
+
for (const container of Array.isArray(data) ? data : [data]) {
|
|
128
|
+
if (!object(container)) continue;
|
|
129
|
+
const name = typeof container.Name === "string" ? container.Name.replace(/^\//, "") : undefined;
|
|
130
|
+
if (!identifier(name)) continue;
|
|
131
|
+
const details: RuntimeFact["details"] = {};
|
|
132
|
+
const state = object(container.State) ? container.State : {};
|
|
133
|
+
if (identifier(state.Status)) details.state = state.Status;
|
|
134
|
+
if (typeof state.Running === "boolean") details.running = state.Running;
|
|
135
|
+
const config = object(container.Config) ? container.Config : {}, labels = object(config.Labels) ? config.Labels : {};
|
|
136
|
+
if (identifier(labels["com.docker.compose.service"])) details.service = labels["com.docker.compose.service"];
|
|
137
|
+
const settings = object(container.NetworkSettings) ? container.NetworkSettings : {}, networks = object(settings.Networks) ? settings.Networks : {};
|
|
138
|
+
details.networks = Object.keys(networks).filter(identifier).slice(0, 32);
|
|
139
|
+
add("docker-container", name, details);
|
|
140
|
+
}
|
|
141
|
+
limitations.add("Docker state reflects the supplied inspect export; environment, labels, addresses and mounts are omitted");
|
|
142
|
+
} else if (format === "http-trace") {
|
|
143
|
+
const log = object(data) && object(data.log) ? data.log : data;
|
|
144
|
+
const entries = Array.isArray(log) ? log : object(log) && Array.isArray(log.entries) ? log.entries : [];
|
|
145
|
+
for (const entry of entries) if (object(entry)) { const request = object(entry.request) ? entry.request : entry, response = object(entry.response) ? entry.response : entry; route(request.method, request.url ?? request.path, response.status); }
|
|
146
|
+
limitations.add("HTTP evidence retains method, sanitized pathname and status only; origins, queries, headers and bodies are omitted");
|
|
147
|
+
limitations.add("Pathname segments may still identify sensitive resources; field minimization cannot guarantee removal of every opaque credential");
|
|
148
|
+
} else if (format === "spring-mappings") {
|
|
149
|
+
if (object(data) && object(data.paths)) for (const [path, operations] of Object.entries(data.paths)) if (object(operations)) for (const method of Object.keys(operations)) route(method, path);
|
|
150
|
+
const contexts = object(data) && object(data.contexts) ? Object.values(data.contexts) : [data];
|
|
151
|
+
for (const context of contexts) {
|
|
152
|
+
if (!object(context)) continue;
|
|
153
|
+
const mappings = object(context.mappings) ? context.mappings : {};
|
|
154
|
+
const dispatchers = [mappings.dispatcherServlets, mappings.dispatcherHandlers].flatMap(group => object(group) ? Object.values(group) : []);
|
|
155
|
+
for (const entries of dispatchers) if (Array.isArray(entries)) for (const entry of entries) {
|
|
156
|
+
if (!object(entry)) continue;
|
|
157
|
+
const details = object(entry.details) ? entry.details : {}, conditions = object(details.requestMappingConditions) ? details.requestMappingConditions : {};
|
|
158
|
+
const patterns = Array.isArray(conditions.patterns) ? conditions.patterns : [], verbs = Array.isArray(conditions.methods) ? conditions.methods : [];
|
|
159
|
+
for (const path of patterns.slice(0, 32)) for (const method of verbs.slice(0, 16)) route(method, path);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
limitations.add("Spring mappings reflect supplied exports; handlers without explicit HTTP methods are omitted");
|
|
163
|
+
} else if (format === "spring-beans") {
|
|
164
|
+
const contexts = object(data) && object(data.contexts) ? Object.values(data.contexts) : [data];
|
|
165
|
+
for (const context of contexts) {
|
|
166
|
+
if (!object(context) || !object(context.beans)) continue;
|
|
167
|
+
for (const [name, bean] of Object.entries(context.beans)) if (identifier(name) && object(bean)) {
|
|
168
|
+
const details: RuntimeFact["details"] = {};
|
|
169
|
+
if (identifier(bean.type)) details.type = bean.type;
|
|
170
|
+
if (identifier(bean.scope)) details.scope = bean.scope;
|
|
171
|
+
if (Array.isArray(bean.dependencies)) details.dependencies = bean.dependencies.filter(identifier).slice(0, 32);
|
|
172
|
+
add("spring-bean", name, details);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
limitations.add("Bean exports establish registration in one environment; invocation and profile completeness are not observed");
|
|
176
|
+
} else if (format === "forge-runtime") {
|
|
177
|
+
const entries = object(data) && Array.isArray(data.facts) ? data.facts : [];
|
|
178
|
+
const kinds = new Set(["nuxt-component", "nuxt-import", "spring-bean", "docker-container", "runtime-resource"]);
|
|
179
|
+
for (const entry of entries) if (object(entry) && identifier(entry.name) && typeof entry.kind === "string" && kinds.has(entry.kind)) {
|
|
180
|
+
// A portable explicit export may describe resource identities and relationships only.
|
|
181
|
+
const details: RuntimeFact["details"] = {}, raw = object(entry.details) ? entry.details : {};
|
|
182
|
+
for (const key of ["type", "scope", "service", "state", "exportName"]) if (identifier(raw[key])) details[key] = raw[key];
|
|
183
|
+
if (sourcePath(raw.sourcePath)) details.sourcePath = raw.sourcePath;
|
|
184
|
+
for (const key of ["dependencies", "networks"]) if (Array.isArray(raw[key])) details[key] = raw[key].filter(identifier).slice(0, 32);
|
|
185
|
+
if (typeof raw.running === "boolean") details.running = raw.running;
|
|
186
|
+
const selected = Object.fromEntries(Object.entries(details).filter(([key]) => factFields[entry.kind as string]!.includes(key)));
|
|
187
|
+
add(entry.kind, entry.name, selected);
|
|
188
|
+
}
|
|
189
|
+
limitations.add("Explicit runtime exports are producer-reported structural evidence, not authenticated execution traces");
|
|
190
|
+
} else return { facts, limitations: ["Unsupported runtime artifact format"] };
|
|
191
|
+
}
|
|
192
|
+
if (!facts.length) limitations.add("No supported structural runtime facts found");
|
|
193
|
+
limitations.add("Selected structural identifiers are producer supplied; field minimization is not a guarantee of universal secret redaction");
|
|
194
|
+
return { facts, limitations: [...limitations] };
|
|
195
|
+
}
|