@intentius/chant 0.124.13 → 0.124.14
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/bin/chant +11 -0
- package/dist/agents/checks.js +326 -0
- package/dist/agents/discover.js +885 -0
- package/dist/agents/importer.js +17 -0
- package/dist/agents/index.js +12 -0
- package/dist/agents/types.js +19 -0
- package/dist/apply.js +151 -0
- package/dist/attrref.js +70 -0
- package/dist/audit/catalog.js +350 -0
- package/dist/audit/core.js +252 -0
- package/dist/audit/discover.js +633 -0
- package/dist/audit/fetch.js +666 -0
- package/dist/audit/lineage.js +91 -0
- package/dist/audit/nginx.js +366 -0
- package/dist/audit/prior-art.js +132 -0
- package/dist/audit/proof.js +321 -0
- package/dist/audit/report-html.js +194 -0
- package/dist/audit/report-model.js +198 -0
- package/dist/audit/report.js +102 -0
- package/dist/audit/rules-doc.js +144 -0
- package/dist/audit/secrets.js +405 -0
- package/dist/audit/terraform-state.js +140 -0
- package/dist/audit/wrangler.js +262 -0
- package/dist/behaviour-delta.js +401 -0
- package/dist/behaviour-engine.js +357 -0
- package/dist/behaviour-graph.js +83 -0
- package/dist/behaviour-http.js +188 -0
- package/dist/behaviour-kinds.js +172 -0
- package/dist/behaviour-overlay.js +50 -0
- package/dist/behaviour-predict.js +188 -0
- package/dist/behaviour-request.js +194 -0
- package/dist/behaviour.js +1325 -0
- package/dist/build-params.js +108 -0
- package/dist/build.js +668 -0
- package/dist/builder.js +44 -0
- package/dist/cdk/advise.js +30 -0
- package/dist/cdk/assembly.js +122 -0
- package/dist/cdk/graph.js +444 -0
- package/dist/cdk/tier-map.js +64 -0
- package/dist/cdk/types.js +24 -0
- package/dist/change-set.js +312 -0
- package/dist/child-project.js +21 -0
- package/dist/claimed-fields.js +135 -0
- package/dist/cli/build-options.js +70 -0
- package/dist/cli/build-params-cli.js +139 -0
- package/dist/cli/command-group.js +123 -0
- package/dist/cli/commands/audit-agents.js +326 -0
- package/dist/cli/commands/audit.js +571 -0
- package/dist/cli/commands/build.js +920 -0
- package/dist/cli/commands/carve-apply.js +153 -0
- package/dist/cli/commands/carve-bridge.js +201 -0
- package/dist/cli/commands/carve-emit.js +332 -0
- package/dist/cli/commands/carve-status.js +119 -0
- package/dist/cli/commands/carve.js +204 -0
- package/dist/cli/commands/check-lexicon-docs.js +79 -0
- package/dist/cli/commands/check-lexicon-examples.js +177 -0
- package/dist/cli/commands/check-lexicon-intrinsics.js +291 -0
- package/dist/cli/commands/check-lexicon-mcp.js +82 -0
- package/dist/cli/commands/check-lexicon-plugin.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon-plugin.js +104 -0
- package/dist/cli/commands/check-lexicon.js +766 -0
- package/dist/cli/commands/describe.js +85 -0
- package/dist/cli/commands/diff.js +175 -0
- package/dist/cli/commands/doctor.js +350 -0
- package/dist/cli/commands/import-agents.js +155 -0
- package/dist/cli/commands/import.js +670 -0
- package/dist/cli/commands/init-lexicon/templates/codegen.js +194 -0
- package/dist/cli/commands/init-lexicon/templates/docs.js +100 -0
- package/dist/cli/commands/init-lexicon/templates/examples.js +29 -0
- package/dist/cli/commands/init-lexicon/templates/lint.js +34 -0
- package/dist/cli/commands/init-lexicon/templates/lsp.js +37 -0
- package/dist/cli/commands/init-lexicon/templates/plugin.js +108 -0
- package/dist/cli/commands/init-lexicon/templates/project.js +240 -0
- package/dist/cli/commands/init-lexicon/templates/spec.js +56 -0
- package/dist/cli/commands/init-lexicon/templates/tests.js +66 -0
- package/dist/cli/commands/init-lexicon.js +172 -0
- package/dist/cli/commands/init.js +381 -0
- package/dist/cli/commands/lexicon-rolling-upgrade.js +145 -0
- package/dist/cli/commands/lexicon-surface-diff.js +145 -0
- package/dist/cli/commands/licenses.js +125 -0
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/commands/lint.js +948 -0
- package/dist/cli/commands/list.js +63 -0
- package/dist/cli/commands/migrate.d.ts.map +1 -1
- package/dist/cli/commands/migrate.js +423 -0
- package/dist/cli/commands/onboard.js +403 -0
- package/dist/cli/commands/pinned-upgrade.js +191 -0
- package/dist/cli/commands/update.js +194 -0
- package/dist/cli/commands/vendor.js +217 -0
- package/dist/cli/conflict-check.js +104 -0
- package/dist/cli/debug.js +8 -0
- package/dist/cli/format.js +104 -0
- package/dist/cli/handlers/build.js +267 -0
- package/dist/cli/handlers/carve-apply.js +30 -0
- package/dist/cli/handlers/carve-bridge.js +28 -0
- package/dist/cli/handlers/carve-emit.js +50 -0
- package/dist/cli/handlers/carve-status.js +26 -0
- package/dist/cli/handlers/carve.js +40 -0
- package/dist/cli/handlers/change-set.js +46 -0
- package/dist/cli/handlers/components.js +730 -0
- package/dist/cli/handlers/dev.js +113 -0
- package/dist/cli/handlers/emulator.js +233 -0
- package/dist/cli/handlers/explain.js +54 -0
- package/dist/cli/handlers/fan-out.js +422 -0
- package/dist/cli/handlers/gate-resume.js +275 -0
- package/dist/cli/handlers/generate.js +84 -0
- package/dist/cli/handlers/graph.js +969 -0
- package/dist/cli/handlers/init.js +102 -0
- package/dist/cli/handlers/lifecycle.js +1727 -0
- package/dist/cli/handlers/lint.js +62 -0
- package/dist/cli/handlers/migrate.js +48 -0
- package/dist/cli/handlers/misc.js +391 -0
- package/dist/cli/handlers/operator.js +1162 -0
- package/dist/cli/handlers/override-approve.js +102 -0
- package/dist/cli/handlers/pr.js +503 -0
- package/dist/cli/handlers/promote.js +342 -0
- package/dist/cli/handlers/run-generate.js +206 -0
- package/dist/cli/handlers/run-wave.js +66 -0
- package/dist/cli/handlers/run.js +1191 -0
- package/dist/cli/handlers/scenario.js +325 -0
- package/dist/cli/handlers/search.js +924 -0
- package/dist/cli/handlers/serve.js +32 -0
- package/dist/cli/handlers/vendor.js +154 -0
- package/dist/cli/index.js +3 -0
- package/dist/cli/is-entry-point.js +23 -0
- package/dist/cli/lsp/capabilities.js +28 -0
- package/dist/cli/lsp/diagnostics.js +34 -0
- package/dist/cli/lsp/server.js +329 -0
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +2540 -0
- package/dist/cli/mcp/lifecycle-tools.js +80 -0
- package/dist/cli/mcp/op-tools.js +306 -0
- package/dist/cli/mcp/resource-handlers.js +258 -0
- package/dist/cli/mcp/resources/context.js +85 -0
- package/dist/cli/mcp/server.js +353 -0
- package/dist/cli/mcp/tools/build.js +111 -0
- package/dist/cli/mcp/tools/composites.js +95 -0
- package/dist/cli/mcp/tools/explain.js +166 -0
- package/dist/cli/mcp/tools/import.js +49 -0
- package/dist/cli/mcp/tools/lint.js +41 -0
- package/dist/cli/mcp/tools/scaffold.js +111 -0
- package/dist/cli/mcp/tools/search.js +112 -0
- package/dist/cli/mcp/types.js +1 -0
- package/dist/cli/mcp/workspace-plugins.js +91 -0
- package/dist/cli/mcp/workspace-tools.d.ts +1 -1
- package/dist/cli/mcp/workspace-tools.js +564 -0
- package/dist/cli/mcp-config.js +70 -0
- package/dist/cli/plugins.js +313 -0
- package/dist/cli/registry.js +23 -0
- package/dist/cli/reporters/stylish.js +329 -0
- package/dist/cli/version.js +15 -0
- package/dist/cli/watch.js +79 -0
- package/dist/codegen/bound-property-types.js +82 -0
- package/dist/codegen/coverage.js +119 -0
- package/dist/codegen/docs-file-markers.js +60 -0
- package/dist/codegen/docs-pages.js +115 -0
- package/dist/codegen/docs-rule-scanning.js +164 -0
- package/dist/codegen/docs-rules.js +76 -0
- package/dist/codegen/docs-sections.js +117 -0
- package/dist/codegen/docs-sidebar.js +65 -0
- package/dist/codegen/docs-types.js +4 -0
- package/dist/codegen/docs.js +356 -0
- package/dist/codegen/fetch.js +340 -0
- package/dist/codegen/generate-post-synth-barrel.js +63 -0
- package/dist/codegen/generate-registry.js +71 -0
- package/dist/codegen/generate-runtime-index.js +56 -0
- package/dist/codegen/generate-typescript.js +119 -0
- package/dist/codegen/generate.js +115 -0
- package/dist/codegen/json-patch.js +141 -0
- package/dist/codegen/json-schema-to-ts.js +344 -0
- package/dist/codegen/json-schema.js +246 -0
- package/dist/codegen/lexicon-regen.js +412 -0
- package/dist/codegen/naming.js +217 -0
- package/dist/codegen/okf-lexicon.js +245 -0
- package/dist/codegen/package.js +150 -0
- package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
- package/dist/codegen/pinned-upgrade.js +494 -0
- package/dist/codegen/registry.js +60 -0
- package/dist/codegen/rehype-base-url.mjs +68 -0
- package/dist/codegen/rolling-upgrade.js +217 -0
- package/dist/codegen/surface-snapshot.js +537 -0
- package/dist/codegen/testdata/postgres-tags.json +242 -0
- package/dist/codegen/topo-sort.js +36 -0
- package/dist/codegen/typecheck.js +115 -0
- package/dist/codegen/validate.js +200 -0
- package/dist/codegen/version-bump.js +35 -0
- package/dist/components/auto-release.js +143 -0
- package/dist/components/builders.js +47 -0
- package/dist/components/capability-plugin-loader.js +175 -0
- package/dist/components/capability-plugin-schema.js +46 -0
- package/dist/components/capability-plugin.js +99 -0
- package/dist/components/capability.js +60 -0
- package/dist/components/changed-files.js +72 -0
- package/dist/components/cli-support.js +427 -0
- package/dist/components/component.js +120 -0
- package/dist/components/component.schema.json +271 -0
- package/dist/components/config-defaults.js +130 -0
- package/dist/components/deploy-units.js +60 -0
- package/dist/components/discover.d.ts.map +1 -1
- package/dist/components/discover.js +189 -0
- package/dist/components/driver-output.js +86 -0
- package/dist/components/driver.js +728 -0
- package/dist/components/fan-out-output.js +165 -0
- package/dist/components/fan-out-record.js +94 -0
- package/dist/components/fan-out-run.js +373 -0
- package/dist/components/fan-out-support.js +60 -0
- package/dist/components/fan-out.js +273 -0
- package/dist/components/gate-plan.js +41 -0
- package/dist/components/gated-wave-pipeline.js +46 -0
- package/dist/components/index.js +24 -0
- package/dist/components/layers.js +28 -0
- package/dist/components/manifest-persistence.js +108 -0
- package/dist/components/pilots/alb-ecs.pilot.js +59 -0
- package/dist/components/pilots/dynamodb.pilot.js +51 -0
- package/dist/components/pilots/jar-emr.pilot.js +65 -0
- package/dist/components/pilots/lambda.pilot.js +52 -0
- package/dist/components/pilots/neo4j-fanout.pilot.js +68 -0
- package/dist/components/pilots/project.js +12 -0
- package/dist/components/pr-pipeline.js +134 -0
- package/dist/components/pr-run.js +363 -0
- package/dist/components/presets/ecs-fargate.js +72 -0
- package/dist/components/presets/index.js +28 -0
- package/dist/components/presets/lambda.js +34 -0
- package/dist/components/presets/single-host-compose.js +41 -0
- package/dist/components/promote.js +549 -0
- package/dist/components/registry.js +34 -0
- package/dist/components/run-progress.js +33 -0
- package/dist/components/sandbox/driver.d.ts.map +1 -1
- package/dist/components/sandbox/driver.js +93 -0
- package/dist/components/sandbox/run.js +115 -0
- package/dist/components/starter-plugin.js +104 -0
- package/dist/components/verbs/__tests__/mock-cloud-executor.js +68 -0
- package/dist/components/verbs/__tests__/mock-process-runner.js +45 -0
- package/dist/components/verbs/__tests__/mock-sbom-generator.js +56 -0
- package/dist/components/verbs/bom-writer.js +287 -0
- package/dist/components/verbs/build-archive.js +188 -0
- package/dist/components/verbs/build.js +142 -0
- package/dist/components/verbs/cloud-executor.js +110 -0
- package/dist/components/verbs/component-bom.js +125 -0
- package/dist/components/verbs/ensure-secret.js +50 -0
- package/dist/components/verbs/index.js +61 -0
- package/dist/components/verbs/license-policy.js +75 -0
- package/dist/components/verbs/lockfile-sbom-generator.js +191 -0
- package/dist/components/verbs/process-runner.js +78 -0
- package/dist/components/verbs/r2-sync.js +93 -0
- package/dist/components/verbs/reproducibility.js +62 -0
- package/dist/components/verbs/run-agent.js +577 -0
- package/dist/components/verbs/sbom-generator.js +119 -0
- package/dist/components/verbs/sbom.js +97 -0
- package/dist/components/verbs/shell.js +32 -0
- package/dist/components/verbs/sign.js +221 -0
- package/dist/components/verbs/stub.js +28 -0
- package/dist/components/verbs/tool-sbom-generator.js +154 -0
- package/dist/components/verbs/verify.js +112 -0
- package/dist/components/verbs/vex.js +131 -0
- package/dist/components/verbs/vuln-gate.js +152 -0
- package/dist/components/verbs/vuln-scan.js +288 -0
- package/dist/components/verbs/wait-verify.js +106 -0
- package/dist/components/verbs/wrangler.js +179 -0
- package/dist/components/wave-plan.js +102 -0
- package/dist/composite.js +305 -0
- package/dist/config-import.d.ts.map +1 -1
- package/dist/config-import.js +22 -0
- package/dist/config-sandbox.js +122 -0
- package/dist/config-static.js +206 -0
- package/dist/config.js +605 -0
- package/dist/content-digest.js +23 -0
- package/dist/declarable.js +114 -0
- package/dist/deep-observation.js +437 -0
- package/dist/detectLexicon.js +54 -0
- package/dist/discovery/cache.js +70 -0
- package/dist/discovery/collect.js +208 -0
- package/dist/discovery/cycles.js +95 -0
- package/dist/discovery/entity-wire-codec.js +391 -0
- package/dist/discovery/entity-wire.js +42 -0
- package/dist/discovery/files.js +121 -0
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/fold-import.js +4178 -0
- package/dist/discovery/fold-rank.js +292 -0
- package/dist/discovery/graph.js +115 -0
- package/dist/discovery/import.d.ts.map +1 -1
- package/dist/discovery/import.js +65 -0
- package/dist/discovery/index.js +333 -0
- package/dist/discovery/param-deps.js +487 -0
- package/dist/discovery/resolve.js +39 -0
- package/dist/discovery/sandbox/bundle.js +214 -0
- package/dist/discovery/sandbox/child-errors.js +26 -0
- package/dist/discovery/sandbox/config-run.js +100 -0
- package/dist/discovery/sandbox/config-wire.js +183 -0
- package/dist/discovery/sandbox/driver.d.ts.map +1 -1
- package/dist/discovery/sandbox/driver.js +333 -0
- package/dist/discovery/sandbox/fork.js +229 -0
- package/dist/discovery/sandbox/policy-run.js +142 -0
- package/dist/discovery/sandbox/policy-wire.js +152 -0
- package/dist/discovery/sandbox/run.js +77 -0
- package/dist/discovery/specifier-resolve.js +320 -0
- package/dist/discovery/walk.js +284 -0
- package/dist/effect-receipt.js +340 -0
- package/dist/env.js +55 -0
- package/dist/errors.js +62 -0
- package/dist/fold/fold.js +1434 -0
- package/dist/fold/foldable-helpers.js +229 -0
- package/dist/fold/subset.js +696 -0
- package/dist/fold-provenance.js +414 -0
- package/dist/gate-decision.js +260 -0
- package/dist/gate-resume-run.js +154 -0
- package/dist/gated-waves.js +165 -0
- package/dist/governance.js +43 -0
- package/dist/graph-declared.js +45 -0
- package/dist/graph-detail.js +207 -0
- package/dist/graph-dot.js +54 -0
- package/dist/graph-effective.js +107 -0
- package/dist/graph-ir.js +623 -0
- package/dist/graph-layout.js +184 -0
- package/dist/graph-lens.js +105 -0
- package/dist/graph-mermaid.js +83 -0
- package/dist/graph-ops.js +39 -0
- package/dist/graph-refs.js +156 -0
- package/dist/held-elsewhere.js +87 -0
- package/dist/identity.js +251 -0
- package/dist/import/base-parser.js +34 -0
- package/dist/import/embedded.js +255 -0
- package/dist/import/generator.js +1 -0
- package/dist/import/ir-utils.js +85 -0
- package/dist/import/parser-options.js +45 -0
- package/dist/import/parser.js +1 -0
- package/dist/index.js +118 -0
- package/dist/intrinsic-interpolation.js +73 -0
- package/dist/intrinsic.js +31 -0
- package/dist/join-key.js +35 -0
- package/dist/kubectl-context.js +150 -0
- package/dist/lexicon-config.js +58 -0
- package/dist/lexicon-integrity.js +47 -0
- package/dist/lexicon-manifest.js +70 -0
- package/dist/lexicon-module.d.ts.map +1 -1
- package/dist/lexicon-module.js +241 -0
- package/dist/lexicon-output.js +218 -0
- package/dist/lexicon-plugin-helpers.js +104 -0
- package/dist/lexicon-schema.js +136 -0
- package/dist/lexicon.js +130 -0
- package/dist/lifecycle/affected.js +224 -0
- package/dist/lifecycle/assert-live.js +110 -0
- package/dist/lifecycle/build-ledger-store.js +160 -0
- package/dist/lifecycle/build-ledger.js +155 -0
- package/dist/lifecycle/change-set.js +356 -0
- package/dist/lifecycle/converge-ledger.js +161 -0
- package/dist/lifecycle/deep-diff.js +233 -0
- package/dist/lifecycle/deep-observe.js +147 -0
- package/dist/lifecycle/digest.js +143 -0
- package/dist/lifecycle/disruption.js +135 -0
- package/dist/lifecycle/gate-applied.js +113 -0
- package/dist/lifecycle/gate-audit.js +341 -0
- package/dist/lifecycle/gate-ledger-select.js +89 -0
- package/dist/lifecycle/gate-ledger.js +193 -0
- package/dist/lifecycle/gate-origin.js +117 -0
- package/dist/lifecycle/gate-pending.js +58 -0
- package/dist/lifecycle/git.js +1182 -0
- package/dist/lifecycle/identity.js +58 -0
- package/dist/lifecycle/index.js +26 -0
- package/dist/lifecycle/lease.js +283 -0
- package/dist/lifecycle/ledger-port.js +1 -0
- package/dist/lifecycle/legacy-digest.js +91 -0
- package/dist/lifecycle/live-diff.js +274 -0
- package/dist/lifecycle/local-note.js +15 -0
- package/dist/lifecycle/member-ledger.js +122 -0
- package/dist/lifecycle/observation-baseline.js +155 -0
- package/dist/lifecycle/observe.js +375 -0
- package/dist/lifecycle/oras-referrer-lookup.js +82 -0
- package/dist/lifecycle/override-ledger.js +54 -0
- package/dist/lifecycle/plan-digest.js +110 -0
- package/dist/lifecycle/plan-ledger.js +129 -0
- package/dist/lifecycle/receipt-plan.js +192 -0
- package/dist/lifecycle/release-ledger.js +155 -0
- package/dist/lifecycle/release-read.js +64 -0
- package/dist/lifecycle/replay.js +193 -0
- package/dist/lifecycle/rollback.js +102 -0
- package/dist/lifecycle/run-ledger.js +183 -0
- package/dist/lifecycle/scenario-eval.js +204 -0
- package/dist/lifecycle/scenario.js +214 -0
- package/dist/lifecycle/snapshot.js +239 -0
- package/dist/lifecycle/status.js +447 -0
- package/dist/lifecycle/symptoms.js +117 -0
- package/dist/lifecycle/teardown.js +255 -0
- package/dist/lifecycle/types.js +1 -0
- package/dist/lifecycle/unobserved-gate.js +33 -0
- package/dist/lifecycle/wave-plans.js +68 -0
- package/dist/lifecycle/work-item-id.js +36 -0
- package/dist/lifecycle/work-lease.js +393 -0
- package/dist/lint/component-checks.js +87 -0
- package/dist/lint/config.js +483 -0
- package/dist/lint/declarative.js +122 -0
- package/dist/lint/discover.d.ts.map +1 -1
- package/dist/lint/discover.js +138 -0
- package/dist/lint/engine.js +246 -0
- package/dist/lint/knowledge-checks.js +64 -0
- package/dist/lint/named-checks.js +22 -0
- package/dist/lint/output-checks.js +87 -0
- package/dist/lint/output-docs.js +163 -0
- package/dist/lint/parser.js +33 -0
- package/dist/lint/pipeline-change-gate.js +70 -0
- package/dist/lint/policy-import.d.ts +1 -1
- package/dist/lint/policy-import.d.ts.map +1 -1
- package/dist/lint/policy-import.js +62 -0
- package/dist/lint/policy-sandbox.js +86 -0
- package/dist/lint/policy.js +122 -0
- package/dist/lint/post-synth.js +87 -0
- package/dist/lint/presets/relaxed.json +26 -0
- package/dist/lint/presets/strict.json +24 -24
- package/dist/lint/receipt-checks.js +83 -0
- package/dist/lint/rule-loader.d.ts.map +1 -1
- package/dist/lint/rule-loader.js +89 -0
- package/dist/lint/rule-registry.js +86 -0
- package/dist/lint/rule.js +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.js +7 -0
- package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.js +7 -0
- package/dist/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.js +9 -0
- package/dist/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.js +11 -0
- package/dist/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.js +9 -0
- package/dist/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.js +25 -0
- package/dist/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.js +8 -0
- package/dist/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.js +21 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.js +8 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.js +24 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.js +17 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.js +8 -0
- package/dist/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.js +18 -0
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.js +20 -0
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.js +8 -0
- package/dist/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.js +12 -0
- package/dist/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.js +16 -0
- package/dist/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.js +8 -0
- package/dist/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.js +12 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.js +11 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.js +11 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.js +10 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.js +16 -0
- package/dist/lint/rules/comp/comp001-publishes-never-applies.js +69 -0
- package/dist/lint/rules/comp/comp002-dangling-wiring-ref.js +84 -0
- package/dist/lint/rules/comp/comp003-mutating-no-rollback.js +103 -0
- package/dist/lint/rules/comp/comp004-gate-needs-durable-runtime.js +70 -0
- package/dist/lint/rules/comp/comp005-capability-kind-is-noun.js +109 -0
- package/dist/lint/rules/comp/comp006-shell-needs-reason.js +49 -0
- package/dist/lint/rules/comp/comp007-composition-sprawl.js +64 -0
- package/dist/lint/rules/comp/index.js +33 -0
- package/dist/lint/rules/comp/support.js +129 -0
- package/dist/lint/rules/composite-scope.js +51 -0
- package/dist/lint/rules/cor017-composite-name-match.js +93 -0
- package/dist/lint/rules/cor018-composite-prefer-lexicon-type.js +136 -0
- package/dist/lint/rules/cor021-env-literal-name.js +100 -0
- package/dist/lint/rules/cor022-receipt-leaf.js +118 -0
- package/dist/lint/rules/cor024-receipt-secret-pointer.js +199 -0
- package/dist/lint/rules/declarable-naming-convention.js +62 -0
- package/dist/lint/rules/evl001-non-literal-expression.js +66 -0
- package/dist/lint/rules/evl002-control-flow-resource.js +69 -0
- package/dist/lint/rules/evl003-dynamic-property-access.js +44 -0
- package/dist/lint/rules/evl004-spread-non-const.js +94 -0
- package/dist/lint/rules/evl005-resource-block-body.js +41 -0
- package/dist/lint/rules/evl007-invalid-siblings.js +105 -0
- package/dist/lint/rules/evl009-composite-no-constant.js +158 -0
- package/dist/lint/rules/evl010-composite-no-transform.js +59 -0
- package/dist/lint/rules/evl011-symbolic-in-template.js +106 -0
- package/dist/lint/rules/export-required.js +115 -0
- package/dist/lint/rules/file-declarable-limit.js +88 -0
- package/dist/lint/rules/flat-declarations.js +62 -0
- package/dist/lint/rules/index.js +90 -0
- package/dist/lint/rules/no-cyclic-declarable-ref.js +138 -0
- package/dist/lint/rules/no-redundant-type-import.js +76 -0
- package/dist/lint/rules/no-redundant-value-cast.js +41 -0
- package/dist/lint/rules/no-string-ref.js +59 -0
- package/dist/lint/rules/no-unused-declarable-import.js +75 -0
- package/dist/lint/rules/no-unused-declarable.js +142 -0
- package/dist/lint/rules/op/index.js +28 -0
- package/dist/lint/rules/op/ops012-activity-contract.js +54 -0
- package/dist/lint/rules/op/ops013-step-output-ref.js +88 -0
- package/dist/lint/rules/op/ops014-converge-rule-refusals.js +114 -0
- package/dist/lint/rules/op/ops015-gate-approval.js +45 -0
- package/dist/lint/rules/op/support.js +11 -0
- package/dist/lint/rules/op/sys010-key-over-plain-http.js +100 -0
- package/dist/lint/rules/property-kind.js +56 -0
- package/dist/lint/rules/single-concern-file.js +71 -0
- package/dist/lint/rules/sys001-no-literal-key.js +60 -0
- package/dist/lint/selectors.js +168 -0
- package/dist/lint/suppressions.js +129 -0
- package/dist/lint/typescript.js +17 -0
- package/dist/live-endpoint.js +113 -0
- package/dist/live-read-session.js +76 -0
- package/dist/lsp/lexicon-providers.js +163 -0
- package/dist/lsp/types.js +5 -0
- package/dist/managed-fields.js +293 -0
- package/dist/mcp/types.js +4 -0
- package/dist/notify-verify.js +0 -1
- package/dist/notify.js +0 -1
- package/dist/notify.schema.json +72 -0
- package/dist/observation.js +266 -0
- package/dist/okf-read.js +134 -0
- package/dist/okf.js +275 -0
- package/dist/op/__fixtures__/decide-stub-backend.js +125 -0
- package/dist/op/activities/activity-contracts.js +313 -0
- package/dist/op/activities/apply.js +582 -0
- package/dist/op/activities/build.js +18 -0
- package/dist/op/activities/change-coverage.js +40 -0
- package/dist/op/activities/change-set.js +64 -0
- package/dist/op/activities/converge.js +492 -0
- package/dist/op/activities/decide.js +211 -0
- package/dist/op/activities/env-teardown.js +110 -0
- package/dist/op/activities/factory.js +591 -0
- package/dist/op/activities/forge-pull-request.js +82 -0
- package/dist/op/activities/gh-host.js +78 -0
- package/dist/op/activities/guard-validate.js +165 -0
- package/dist/op/activities/http-check.js +40 -0
- package/dist/op/activities/index.js +59 -0
- package/dist/op/activities/lexicon-upgrade.js +629 -0
- package/dist/op/activities/lifecycle.js +109 -0
- package/dist/op/activities/pipeline-audit.js +147 -0
- package/dist/op/activities/policy.js +26 -0
- package/dist/op/activities/predict-behaviour.js +341 -0
- package/dist/op/activities/propose-upgrade.js +165 -0
- package/dist/op/activities/reconcile.js +997 -0
- package/dist/op/activities/shell.js +118 -0
- package/dist/op/activities/source-release.js +97 -0
- package/dist/op/activities/source-rollback.js +165 -0
- package/dist/op/activities/teardown.js +17 -0
- package/dist/op/activities/util.js +6 -0
- package/dist/op/activities/wait.js +51 -0
- package/dist/op/activities/work-evidence.js +35 -0
- package/dist/op/activities/workflow-audit.js +133 -0
- package/dist/op/activity-contract-registry.js +106 -0
- package/dist/op/activity-contract.js +232 -0
- package/dist/op/activity-failure.js +31 -0
- package/dist/op/activity-profiles.js +129 -0
- package/dist/op/activity-registry.js +105 -0
- package/dist/op/activity-runtime.js +28 -0
- package/dist/op/builders.js +801 -0
- package/dist/op/change-signal.js +118 -0
- package/dist/op/composites/apply-op.js +151 -0
- package/dist/op/composites/behaviour-op.js +65 -0
- package/dist/op/composites/converge-op.js +141 -0
- package/dist/op/composites/index.js +19 -0
- package/dist/op/composites/lexicon-upgrade-op.js +107 -0
- package/dist/op/composites/pipeline-audit-op.js +50 -0
- package/dist/op/composites/reconcile-op.js +84 -0
- package/dist/op/composites/watch-op.js +69 -0
- package/dist/op/composites/workflow-audit-op.js +57 -0
- package/dist/op/converge-rule.js +205 -0
- package/dist/op/cron.js +146 -0
- package/dist/op/decide-backend.js +181 -0
- package/dist/op/decide-config.js +68 -0
- package/dist/op/decide-read-inputs.js +141 -0
- package/dist/op/discover.d.ts.map +1 -1
- package/dist/op/discover.js +213 -0
- package/dist/op/duration.js +21 -0
- package/dist/op/emulator-freshness.js +80 -0
- package/dist/op/emulator-lifecycle.js +269 -0
- package/dist/op/factory.js +83 -0
- package/dist/op/gate-approval.js +172 -0
- package/dist/op/gate-name.js +86 -0
- package/dist/op/gate-plan-context.js +167 -0
- package/dist/op/gate-point-run.js +67 -0
- package/dist/op/gate-point.js +111 -0
- package/dist/op/gate-resume-plan.js +155 -0
- package/dist/op/gate-resume.js +31 -0
- package/dist/op/gate-review.js +290 -0
- package/dist/op/gate-run-locator.js +85 -0
- package/dist/op/gate-summary.js +100 -0
- package/dist/op/gate-wait.js +56 -0
- package/dist/op/gate.js +449 -0
- package/dist/op/generate-pipeline.js +124 -0
- package/dist/op/index.js +51 -0
- package/dist/op/lifecycle-receipt-store.js +45 -0
- package/dist/op/local-executor.js +1164 -0
- package/dist/op/local-output.js +140 -0
- package/dist/op/op-ir.js +317 -0
- package/dist/op/op-verb-class.js +131 -0
- package/dist/op/op-waves-notify.js +136 -0
- package/dist/op/op-waves-run.js +1657 -0
- package/dist/op/op-waves.js +409 -0
- package/dist/op/operator.js +743 -0
- package/dist/op/receipt-store.js +94 -0
- package/dist/op/resource.js +23 -0
- package/dist/op/run-context.js +28 -0
- package/dist/op/run-live.js +256 -0
- package/dist/op/runtime.js +21 -0
- package/dist/op/runtimes/local.js +195 -0
- package/dist/op/source-archive.js +131 -0
- package/dist/op/step-output-ref.js +397 -0
- package/dist/op/steward-beside.js +169 -0
- package/dist/op/steward-points.js +138 -0
- package/dist/op/steward-turn.js +59 -0
- package/dist/op/steward.js +317 -0
- package/dist/op/types.js +25 -0
- package/dist/op/work-lease-decl.js +81 -0
- package/dist/op/work-lease-run.js +393 -0
- package/dist/override-gate.js +284 -0
- package/dist/owner-chain.js +74 -0
- package/dist/ownership.js +92 -0
- package/dist/params.js +61 -0
- package/dist/plan-summary.js +711 -0
- package/dist/point-decision.js +211 -0
- package/dist/pr-forge.js +810 -0
- package/dist/pr-loop.js +203 -0
- package/dist/project-codegen.js +191 -0
- package/dist/project-root.js +74 -0
- package/dist/project-validation.js +50 -0
- package/dist/provenance.js +157 -0
- package/dist/pseudo-parameter.js +40 -0
- package/dist/reconcile.js +450 -0
- package/dist/resource-attributes.js +33 -0
- package/dist/runtime-adapter.js +77 -0
- package/dist/runtime.js +66 -0
- package/dist/secret-materialization.js +153 -0
- package/dist/secret-provenance.js +250 -0
- package/dist/serializer-walker.js +112 -0
- package/dist/serializer.js +1 -0
- package/dist/sort.js +45 -0
- package/dist/stack-output.js +115 -0
- package/dist/telemetry-attribution.js +149 -0
- package/dist/terraform/__fixtures__/build-graph.js +63 -0
- package/dist/terraform/adopt-state.js +40 -0
- package/dist/terraform/aws-resources.js +384 -0
- package/dist/terraform/bridge.js +204 -0
- package/dist/terraform/carve-provider.js +155 -0
- package/dist/terraform/carve.js +138 -0
- package/dist/terraform/data-source-shape.js +57 -0
- package/dist/terraform/emit-source.js +36 -0
- package/dist/terraform/excise.js +123 -0
- package/dist/terraform/graduate.js +128 -0
- package/dist/terraform/graph.js +435 -0
- package/dist/terraform/hcl2json.js +60 -0
- package/dist/terraform/manifest.js +149 -0
- package/dist/terraform/parse.js +81 -0
- package/dist/terraform/providers/aws.js +146 -0
- package/dist/terraform/providers/gcp.js +294 -0
- package/dist/terraform/providers/index.js +17 -0
- package/dist/terraform/providers/kubernetes.js +253 -0
- package/dist/terraform/score.js +151 -0
- package/dist/terraform/state.js +67 -0
- package/dist/terraform/tier-map.js +74 -0
- package/dist/terraform/types.js +13 -0
- package/dist/terraform/unified-diff.js +132 -0
- package/dist/testing.js +295 -0
- package/dist/toml-emit.js +153 -0
- package/dist/toml-parse.js +355 -0
- package/dist/toml-utils.js +60 -0
- package/dist/toml.js +428 -0
- package/dist/ts-loader.d.ts +46 -0
- package/dist/ts-loader.d.ts.map +1 -0
- package/dist/ts-loader.js +103 -0
- package/dist/types.js +1 -0
- package/dist/utils.js +61 -0
- package/dist/validation.js +20 -0
- package/dist/vendor/js-yaml-load.js +1662 -0
- package/dist/workspace/__fixtures__/contract-repo.js +103 -0
- package/dist/workspace/__fixtures__/decide-workspace.js +112 -0
- package/dist/workspace/__fixtures__/sessions.js +91 -0
- package/dist/workspace/__fixtures__/two-member-workspace.js +34 -0
- package/dist/workspace/agent-cli.js +99 -0
- package/dist/workspace/agent.schema.json +390 -0
- package/dist/workspace/answers-ledger.js +89 -0
- package/dist/workspace/box-cli.js +142 -0
- package/dist/workspace/box-factory-write.js +196 -0
- package/dist/workspace/box-factory-write.schema.json +342 -0
- package/dist/workspace/box-factory.js +88 -0
- package/dist/workspace/box-intent.js +126 -0
- package/dist/workspace/box-isolation.js +169 -0
- package/dist/workspace/box-listing-write.schema.json +204 -0
- package/dist/workspace/box-listing.js +298 -0
- package/dist/workspace/box-publish.js +352 -0
- package/dist/workspace/box-publish.schema.json +498 -0
- package/dist/workspace/box-services.js +39 -0
- package/dist/workspace/broker-protocol.js +359 -0
- package/dist/workspace/broker-protocol.schema.json +228 -0
- package/dist/workspace/change-set.schema.json +460 -0
- package/dist/workspace/changes-cli.js +56 -0
- package/dist/workspace/changes.js +446 -0
- package/dist/workspace/changes.schema.json +642 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.js +198 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.js +155 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.js +214 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.js +180 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.js +99 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit.js +847 -0
- package/dist/workspace/chant-migrations.js +113 -0
- package/dist/workspace/check.schema.json +433 -0
- package/dist/workspace/checks/box-isolation.js +62 -0
- package/dist/workspace/checks/boxes.js +491 -0
- package/dist/workspace/checks/diagrams.js +104 -0
- package/dist/workspace/checks/generated.js +419 -0
- package/dist/workspace/checks/ledgers.js +212 -0
- package/dist/workspace/checks/links.js +191 -0
- package/dist/workspace/checks/live.js +100 -0
- package/dist/workspace/checks/pipelines.js +188 -0
- package/dist/workspace/checks/records.js +169 -0
- package/dist/workspace/checks.js +426 -0
- package/dist/workspace/ci-green-cli.js +125 -0
- package/dist/workspace/ci-green-forge.js +83 -0
- package/dist/workspace/ci-green-workflow.js +199 -0
- package/dist/workspace/ci-green.js +325 -0
- package/dist/workspace/ci-last-green.schema.json +158 -0
- package/dist/workspace/compose-graph.js +235 -0
- package/dist/workspace/compose-reports.js +146 -0
- package/dist/workspace/composites.js +264 -0
- package/dist/workspace/composites.schema.json +617 -0
- package/dist/workspace/conformance/__fixture__/app/package.json +7 -0
- package/dist/workspace/conformance/__fixture__/app/src/server.mjs +29 -0
- package/dist/workspace/conformance/__fixture__/decisions/decision.kind.mjs +54 -0
- package/dist/workspace/conformance/__fixture__/decisions/decision.schema.json +512 -0
- package/dist/workspace/conformance/__fixture__/decisions/fix-001-how-the-app-is-deployed.md +40 -0
- package/dist/workspace/conformance/__fixture__/delivery/chant.config.ts +7 -0
- package/dist/workspace/conformance/__fixture__/delivery/lexicon/index.ts +26 -0
- package/dist/workspace/conformance/__fixture__/delivery/package.json +7 -0
- package/dist/workspace/conformance/__fixture__/delivery/src/app.component.ts +14 -0
- package/dist/workspace/conformance/__fixture__/delivery/src/app.ts +4 -0
- package/dist/workspace/conformance/__fixtures__/reference-broker.js +205 -0
- package/dist/workspace/conformance/__writer_fixture__/answers/answer.kind.mjs +32 -0
- package/dist/workspace/conformance/__writer_fixture__/answers/answer.schema.json +135 -0
- package/dist/workspace/conformance/__writer_fixture__/contracts/contract.kind.mjs +37 -0
- package/dist/workspace/conformance/__writer_fixture__/contracts/contract.schema.json +213 -0
- package/dist/workspace/conformance/__writer_fixture__/decisions/points.json +228 -0
- package/dist/workspace/conformance/__writer_fixture__/drivers/driver.kind.mjs +15 -0
- package/dist/workspace/conformance/__writer_fixture__/drivers/driver.schema.json +38 -0
- package/dist/workspace/conformance/__writer_fixture__/sessions/session.kind.mjs +54 -0
- package/dist/workspace/conformance/__writer_fixture__/sessions/session.schema.json +289 -0
- package/dist/workspace/conformance/__writer_fixture__/work/W-001-the-writer-suite-works-this-item.md +25 -0
- package/dist/workspace/conformance/__writer_fixture__/work/work.kind.mjs +60 -0
- package/dist/workspace/conformance/__writer_fixture__/work/work.schema.json +603 -0
- package/dist/workspace/conformance/broker.js +736 -0
- package/dist/workspace/conformance/index.js +673 -0
- package/dist/workspace/conformance/vitest.js +108 -0
- package/dist/workspace/conformance/writer.js +1080 -0
- package/dist/workspace/decide.js +879 -0
- package/dist/workspace/decision-points.schema.json +135 -135
- package/dist/workspace/declaration-write-cli.js +74 -0
- package/dist/workspace/declaration-write.js +238 -0
- package/dist/workspace/declaration.js +938 -0
- package/dist/workspace/declaration.schema.json +1413 -1413
- package/dist/workspace/declared-kinds.js +106 -0
- package/dist/workspace/environments.js +164 -0
- package/dist/workspace/evidence.schema.json +279 -0
- package/dist/workspace/export-cli.js +185 -0
- package/dist/workspace/export.js +499 -0
- package/dist/workspace/factory-rules.js +175 -0
- package/dist/workspace/generated-files.js +92 -0
- package/dist/workspace/glob.js +45 -0
- package/dist/workspace/graph-cache.js +352 -0
- package/dist/workspace/graph-cli.js +348 -0
- package/dist/workspace/graph.schema.json +1338 -0
- package/dist/workspace/identity-gate-rule.js +75 -0
- package/dist/workspace/identity.js +126 -0
- package/dist/workspace/import.js +283 -0
- package/dist/workspace/init.js +620 -0
- package/dist/workspace/intent-cli.js +263 -0
- package/dist/workspace/intent-joins.js +201 -0
- package/dist/workspace/intent-record.js +352 -0
- package/dist/workspace/intent-record.schema.json +864 -0
- package/dist/workspace/intent.js +1582 -0
- package/dist/workspace/intent.schema.json +2420 -0
- package/dist/workspace/json-edit.js +221 -0
- package/dist/workspace/jsonc.js +235 -0
- package/dist/workspace/kind-readers.js +103 -0
- package/dist/workspace/kinds.js +587 -0
- package/dist/workspace/lexicon-symbols.js +112 -0
- package/dist/workspace/lineage-adopt-cli.js +102 -0
- package/dist/workspace/lineage-adopt.js +360 -0
- package/dist/workspace/lineage-check.js +282 -0
- package/dist/workspace/lineage-cli.js +121 -0
- package/dist/workspace/lineage-hash-index.js +284 -0
- package/dist/workspace/lineage-init.js +507 -0
- package/dist/workspace/lineage-lock.js +387 -0
- package/dist/workspace/lineage-merge.js +45 -0
- package/dist/workspace/lineage-migrations.d.ts.map +1 -1
- package/dist/workspace/lineage-migrations.js +329 -0
- package/dist/workspace/lineage-provenance.js +36 -0
- package/dist/workspace/lineage-update.js +138 -0
- package/dist/workspace/lineage-upgrade-cli.js +131 -0
- package/dist/workspace/lineage-upgrade.js +889 -0
- package/dist/workspace/lineage-vendor.js +171 -0
- package/dist/workspace/lineage-version.js +128 -0
- package/dist/workspace/lineage-versions.js +275 -0
- package/dist/workspace/links.js +330 -0
- package/dist/workspace/ls-generated.js +95 -0
- package/dist/workspace/ls.js +277 -0
- package/dist/workspace/ls.schema.json +333 -0
- package/dist/workspace/member-commands.js +508 -0
- package/dist/workspace/member-handles.js +216 -0
- package/dist/workspace/member-pipeline.js +258 -0
- package/dist/workspace/member-run.js +117 -0
- package/dist/workspace/member-write.schema.json +193 -0
- package/dist/workspace/nested-graph.js +156 -0
- package/dist/workspace/nesting.js +38 -0
- package/dist/workspace/patch.js +301 -0
- package/dist/workspace/patch.schema.json +321 -0
- package/dist/workspace/pin-cli.js +55 -0
- package/dist/workspace/pin-integrity.js +100 -0
- package/dist/workspace/plan-summary.schema.json +155 -0
- package/dist/workspace/point-answer.schema.json +129 -129
- package/dist/workspace/points-cli.js +291 -0
- package/dist/workspace/points-write.schema.json +660 -0
- package/dist/workspace/points.js +521 -0
- package/dist/workspace/points.schema.json +916 -0
- package/dist/workspace/pr-report.schema.json +108 -0
- package/dist/workspace/principal-classes.js +245 -0
- package/dist/workspace/reason-codes.js +326 -0
- package/dist/workspace/record-assets.js +151 -0
- package/dist/workspace/record-decided.js +105 -0
- package/dist/workspace/record-sessions.js +96 -0
- package/dist/workspace/record-source.js +114 -0
- package/dist/workspace/records-amend.schema.json +284 -0
- package/dist/workspace/records-checkout.js +76 -0
- package/dist/workspace/records-cli.js +707 -0
- package/dist/workspace/records-close.js +116 -0
- package/dist/workspace/records-close.schema.json +278 -0
- package/dist/workspace/records-new.schema.json +202 -0
- package/dist/workspace/records-review.schema.json +367 -0
- package/dist/workspace/records-since.js +317 -0
- package/dist/workspace/records-since.schema.json +218 -0
- package/dist/workspace/records-write.js +1229 -0
- package/dist/workspace/records.js +1345 -0
- package/dist/workspace/records.schema.json +604 -0
- package/dist/workspace/reference-kinds/app/package.json +10 -0
- package/dist/workspace/reference-kinds/app/workspace-kinds.json +45 -0
- package/dist/workspace/returns.js +303 -0
- package/dist/workspace/root-refusal.js +75 -0
- package/dist/workspace/run-statement.schema.json +622 -0
- package/dist/workspace/runs-cli.js +584 -0
- package/dist/workspace/runs-write.schema.json +994 -0
- package/dist/workspace/runs.js +708 -0
- package/dist/workspace/runs.schema.json +1257 -0
- package/dist/workspace/runtimes.js +76 -0
- package/dist/workspace/session-kinds.js +83 -0
- package/dist/workspace/signers.schema.json +206 -0
- package/dist/workspace/source-block.js +125 -0
- package/dist/workspace/squash.js +177 -0
- package/dist/workspace/status-gates.js +162 -0
- package/dist/workspace/status-stewards.js +330 -0
- package/dist/workspace/status.js +514 -0
- package/dist/workspace/status.schema.json +998 -0
- package/dist/workspace/steward-index.js +191 -0
- package/dist/workspace/symbols.js +188 -0
- package/dist/workspace/template-manifest.js +227 -0
- package/dist/workspace/template-pins.js +115 -0
- package/dist/workspace/trailer-joins.js +65 -0
- package/dist/workspace/trailers.js +111 -0
- package/dist/workspace/tree.js +136 -0
- package/dist/workspace/trust/attestor.js +63 -0
- package/dist/workspace/trust/dsse.js +108 -0
- package/dist/workspace/trust/evidence-cli.js +175 -0
- package/dist/workspace/trust/evidence.js +161 -0
- package/dist/workspace/trust/gate-seal.js +226 -0
- package/dist/workspace/trust/policy.js +190 -0
- package/dist/workspace/trust/provenance.js +180 -0
- package/dist/workspace/trust/rotation.js +298 -0
- package/dist/workspace/trust/run-commits.js +101 -0
- package/dist/workspace/trust/run-statement.js +182 -0
- package/dist/workspace/trust/seal.js +272 -0
- package/dist/workspace/trust/signers-cli.js +152 -0
- package/dist/workspace/trust/ssh-commit.js +178 -0
- package/dist/workspace/trust/test-repo.js +118 -0
- package/dist/workspace/trust/trust-files.js +193 -0
- package/dist/workspace/trust/verify-cli.js +63 -0
- package/dist/workspace/trust/verify.js +192 -0
- package/dist/workspace/which-chant.js +141 -0
- package/dist/workspace/wip-cli.js +95 -0
- package/dist/workspace/wip-write.schema.json +504 -0
- package/dist/workspace/wip.js +631 -0
- package/dist/workspace/wip.schema.json +334 -0
- package/dist/workspace/work-cli.js +448 -0
- package/dist/workspace/work-evidence.js +122 -0
- package/dist/workspace/work-evidence.schema.json +222 -0
- package/dist/workspace/work-history.schema.json +421 -0
- package/dist/workspace/work-lease.schema.json +246 -0
- package/dist/workspace/work-size.js +77 -0
- package/dist/workspace/work.js +283 -0
- package/dist/workspace/workspace-kinds.schema.json +259 -259
- package/dist/workspace/workspace-principals.schema.json +53 -53
- package/dist/workspace/write-lock-cli.js +105 -0
- package/dist/workspace/write-lock.js +424 -0
- package/dist/workspace/write-lock.schema.json +172 -0
- package/dist/workspace/write-scope.js +484 -0
- package/dist/yaml.js +459 -0
- package/package.json +2 -2
- package/src/cli/commands/check-lexicon-plugin.ts +2 -0
- package/src/cli/commands/lint.ts +3 -0
- package/src/cli/commands/migrate.ts +2 -0
- package/src/cli/compiled-cli.e2e.test.ts +165 -0
- package/src/cli/main.ts +121 -27
- package/src/cli/mcp/workspace-tools.ts +1 -1
- package/src/codegen/pinned-upgrade.ts +2 -0
- package/src/components/discover.ts +2 -0
- package/src/components/sandbox/driver.ts +3 -1
- package/src/config-import.ts +3 -0
- package/src/discovery/fold-import.ts +23 -14
- package/src/discovery/import.ts +2 -0
- package/src/discovery/sandbox/driver.ts +3 -1
- package/src/lexicon-module.ts +3 -0
- package/src/lint/config.ts +1 -1
- package/src/lint/discover.ts +28 -8
- package/src/lint/policy-import.ts +4 -1
- package/src/lint/rule-loader.ts +2 -0
- package/src/op/discover.ts +3 -0
- package/src/ts-loader.test.ts +23 -0
- package/src/ts-loader.ts +122 -0
- package/src/workspace/chant-migrations/chud-lexicon-exit.ts +1 -1
- package/src/workspace/declaration.ts +1 -1
- package/src/workspace/kinds.ts +1 -1
- package/src/workspace/lineage-migrations.ts +2 -0
- package/src/workspace/points.ts +1 -1
- package/src/workspace/principal-classes.ts +1 -1
|
@@ -0,0 +1,1582 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The intent graph over one region: `chant workspace graph --intent
|
|
3
|
+
* <path[:start-end]|path#symbol>` (#2651; #2650 sections B and C1; #2524 D8,
|
|
4
|
+
* D15; #3034).
|
|
5
|
+
*
|
|
6
|
+
* A person points at some code and asks how it got this way and who decided
|
|
7
|
+
* it should be this way. This module gathers what the workspace records about
|
|
8
|
+
* that, as one document of nodes and edges, and decides nothing. It gathers
|
|
9
|
+
* three sources before it relates any of them, so no one of them frames the
|
|
10
|
+
* others:
|
|
11
|
+
*
|
|
12
|
+
* - The commits that touched the region, from git: `git log -L` for a line
|
|
13
|
+
* range, `git log --follow` for a file and `git log -- <dir>` for a
|
|
14
|
+
* directory. Each carries its trailers and its provenance level, and a
|
|
15
|
+
* plugin may join it to a unit of work, a contract and evidence
|
|
16
|
+
* (`intent-joins.ts`).
|
|
17
|
+
* - The decisions whose `constrains` cover the region: a `path:` entry that
|
|
18
|
+
* is the region or a directory above it, the region's `member:`, an issue
|
|
19
|
+
* the region's commits name, or a contract a plugin joined. Their
|
|
20
|
+
* supersession chains come along.
|
|
21
|
+
* - The artifacts those decisions pin. Artifacts relate to code only through
|
|
22
|
+
* decisions (#2549), so an artifact is in the graph because a decision in
|
|
23
|
+
* the graph pins it.
|
|
24
|
+
*
|
|
25
|
+
* Every gap the walk finds is a `finding` node with a closed code, never
|
|
26
|
+
* prose, so a reader can draw it and a test can assert it. A commit made
|
|
27
|
+
* inside a decision's window is not taken as that decision's work: unless
|
|
28
|
+
* the decision's own unit made it, it is `decided-by-window`, shown for the
|
|
29
|
+
* person to judge (#2656). A plugin's `commitJoins` may add findings of its
|
|
30
|
+
* own, in its own code namespace. Without `kinds`, the walk reads every
|
|
31
|
+
* record kind the declaration names (#2680), and each one's `commitJoins`
|
|
32
|
+
* runs for every commit in that order. chant emits the
|
|
33
|
+
* graph and hud renders it (#2524 D8, D15). Git is read through a local
|
|
34
|
+
* `git` subprocess only: no fetch, no network, except to follow a squash
|
|
35
|
+
* when asked (below).
|
|
36
|
+
*
|
|
37
|
+
* A region can name a symbol, `path#symbol`, which `symbols.ts` resolves to
|
|
38
|
+
* its current lines (#3034). For a file region the document also carries
|
|
39
|
+
* `why`: who made the region's current lines, from `git blame`, the agent run
|
|
40
|
+
* behind each (narrowed by the hunks a run recorded when several share a
|
|
41
|
+
* commit), and the decisions governing the region, most relevant first, with
|
|
42
|
+
* `explained: false` and closed gap codes when nothing accounts for it.
|
|
43
|
+
*
|
|
44
|
+
* A commit joins an agent run by its `Chant-Run` trailer or the run's own
|
|
45
|
+
* list of commits, and, failing both, by content (#3036): a commit whose
|
|
46
|
+
* `git patch-id --stable` equals one the run recorded, as after a rebase or a
|
|
47
|
+
* cherry-pick that dropped the trailer. Its `made-by` edge says `patch-id`,
|
|
48
|
+
* and a commit joined one way whose content matches another run's recorded
|
|
49
|
+
* commit is `intent-commit-join-conflict`.
|
|
50
|
+
*
|
|
51
|
+
* Asked to (`followSquash`, #3035), the walk follows a squash merge to its
|
|
52
|
+
* pull request's original commits (`squash.ts`): the squash commit node lists
|
|
53
|
+
* them with their trailers, joins and signatures, carries what their trailers
|
|
54
|
+
* and plugin joins carry, and joins their runs, marked `squash`. That is the
|
|
55
|
+
* one part of the walk that may reach the network, to fetch a pull request
|
|
56
|
+
* ref the clone lacks; a ref that can't be read is `squash-unfollowed` in
|
|
57
|
+
* `reasons`, and the walk keeps its answer without it.
|
|
58
|
+
*/
|
|
59
|
+
import { execFileSync } from "node:child_process";
|
|
60
|
+
import { realpathSync } from "node:fs";
|
|
61
|
+
import { basename, dirname, relative, resolve, sep } from "node:path";
|
|
62
|
+
import { declaredRecordKinds, readDeclaration, readerVersion, resolveGroups, WORKSPACE_ERROR_CODES, WorkspaceReadError } from "./declaration.js";
|
|
63
|
+
import { declaredKindFile } from "./declared-kinds.js";
|
|
64
|
+
import { isGeneratedPath } from "./generated-files.js";
|
|
65
|
+
import { entityDecisions, hasTrailer, readCommitJoins, runCommitJoins } from "./intent-joins.js";
|
|
66
|
+
import { loadKindRegistry } from "./kinds.js";
|
|
67
|
+
import { resolveLinks } from "./links.js";
|
|
68
|
+
import { sourceMemberHandles } from "./member-handles.js";
|
|
69
|
+
import { constraintCovers, isWorkspacePath, memberHolding } from "./record-assets.js";
|
|
70
|
+
import { importKindModule, loadRecordKind, parseFrontMatter, RecordReadError, supersedesTargets } from "./records.js";
|
|
71
|
+
import { queryRecords } from "./records-cli.js";
|
|
72
|
+
import { isPluginCode } from "./reason-codes.js";
|
|
73
|
+
import { addSquashRuns, RUN_JOIN_ORDER, runsForCommits } from "./runs.js";
|
|
74
|
+
import { followSquashes, squashLineOrigins } from "./squash.js";
|
|
75
|
+
import { memberDirOnDisk, memberSymbolResolvers } from "./lexicon-symbols.js";
|
|
76
|
+
import { resolveSymbol, symbolResolverFor } from "./symbols.js";
|
|
77
|
+
import { readTrailerJoins } from "./trailer-joins.js";
|
|
78
|
+
import { joinPath, skippedDir } from "./tree.js";
|
|
79
|
+
import { activeAttestors } from "./trust/attestor.js";
|
|
80
|
+
import { commitProvenance, policyAtBase, resolveBase } from "./trust/provenance.js";
|
|
81
|
+
import { locateWorkspace } from "./which-chant.js";
|
|
82
|
+
import { idList, isDecided, WORK_WARNING_CODES } from "./work.js";
|
|
83
|
+
/** The version of the `intent` document this chant writes. */
|
|
84
|
+
export const INTENT_CONTRACT_VERSION = 1;
|
|
85
|
+
/** `$id` of the JSON Schema for the document, shipped beside this file. */
|
|
86
|
+
export const INTENT_OUTPUT_SCHEMA_ID = "https://intentius.io/chant/schemas/workspace/intent/v1/intent.schema.json";
|
|
87
|
+
// ── Codes ────────────────────────────────────────────────────────────────────
|
|
88
|
+
/** The finding codes. Closed: a reader may switch on them. */
|
|
89
|
+
export const INTENT_FINDING_CODES = [
|
|
90
|
+
/** A commit touched the region when no decision constrained it at path granularity. */
|
|
91
|
+
"intent-commit-undecided",
|
|
92
|
+
/** A commit has no unit, carries no record through chant's trailers, has no pull request reference and no decision covering the region at its time. */
|
|
93
|
+
"intent-commit-bare",
|
|
94
|
+
/** A pinned artifact's bytes no longer hash to the pin. */
|
|
95
|
+
"intent-pin-drifted",
|
|
96
|
+
/** A pinned artifact does not exist in the tree read. */
|
|
97
|
+
"intent-pin-missing",
|
|
98
|
+
/** A current decision's pin is unchanged at the hash a record it supersedes pinned: the decision moved on and the artifact did not (#2686). */
|
|
99
|
+
"intent-pin-stale",
|
|
100
|
+
/** An artifact that decisions in the graph pinned, and that no current decision pins. */
|
|
101
|
+
"intent-artifact-unpinned",
|
|
102
|
+
/** Every decision constraining the region is superseded. */
|
|
103
|
+
"intent-decision-superseded-live",
|
|
104
|
+
/** The current decisions constraining the region are all in states their kind does not close, such as decided and not ratified. */
|
|
105
|
+
"intent-decision-provisional",
|
|
106
|
+
/** A current decision constraining the region has an open concern: a dissent neither addressed nor withdrawn (#2686). */
|
|
107
|
+
"intent-decision-contested",
|
|
108
|
+
/** The region is constrained only at member granularity. */
|
|
109
|
+
"intent-constraint-coarse",
|
|
110
|
+
/** A decision's path constraint names a path that does not exist. */
|
|
111
|
+
"intent-constraint-lost",
|
|
112
|
+
/** A decision's evidence has no hash: a URL, or a path with no sha256. */
|
|
113
|
+
"intent-evidence-unpinned",
|
|
114
|
+
/** A commit carries a trailer a plugin says claims authorship, and the commit is not attested. */
|
|
115
|
+
"intent-trailer-unverified",
|
|
116
|
+
/** No decision constrains the region at any granularity. */
|
|
117
|
+
"intent-region-unconstrained",
|
|
118
|
+
/** A decided decision constrains the region, no work item that is not dropped implements it, and no commit falls in its window (#2683). Only with a work kind read. */
|
|
119
|
+
"intent-decision-unimplemented",
|
|
120
|
+
/** A work item constraining the region has commits in its window while its blockedBy is not empty (#2683). */
|
|
121
|
+
"intent-work-blocked",
|
|
122
|
+
/** Commits in the region are a decision's own work while the work item implementing it is still open: the code says done and the queue says not (#2683). */
|
|
123
|
+
"intent-work-open-decided-code",
|
|
124
|
+
/** A commit joined to an agent run by its trailer or the run's record has the patch-id of a commit another run recorded; the content join is not made (#3036). */
|
|
125
|
+
"intent-commit-join-conflict",
|
|
126
|
+
];
|
|
127
|
+
/** Why part of the walk could not be read. The read still succeeds. */
|
|
128
|
+
export const INTENT_REASON_CODES = [
|
|
129
|
+
/** The repository is a shallow clone, so the history is cut. */
|
|
130
|
+
"intent-history-shallow",
|
|
131
|
+
/** A plugin's commitJoins failed for a commit. */
|
|
132
|
+
"intent-plugin-failed",
|
|
133
|
+
/** A squash commit's pull request ref could not be read or fetched, so its original commits are not followed (#3035). */
|
|
134
|
+
"squash-unfollowed",
|
|
135
|
+
];
|
|
136
|
+
/** Why the read failed as a whole: the declaration's codes, the record kind's codes, or a region that isn't there. */
|
|
137
|
+
export const INTENT_ERROR_CODES = [
|
|
138
|
+
...WORKSPACE_ERROR_CODES,
|
|
139
|
+
"kind-unreadable",
|
|
140
|
+
"kind-invalid",
|
|
141
|
+
"schema-unreadable",
|
|
142
|
+
"schema-id-mismatch",
|
|
143
|
+
"schema-invalid",
|
|
144
|
+
"location-missing",
|
|
145
|
+
/** The region's path, or its line range, does not exist in the tree read. */
|
|
146
|
+
"intent-region-invalid",
|
|
147
|
+
/** --record names an id no record of a decision kind read has. */
|
|
148
|
+
"intent-record-unknown",
|
|
149
|
+
/** The region names a symbol in a file no resolver reads (#3034). */
|
|
150
|
+
"intent-symbol-unsupported",
|
|
151
|
+
/** The region names a symbol the file does not declare (#3034). */
|
|
152
|
+
"intent-symbol-unknown",
|
|
153
|
+
/** The region names a symbol that matches more than one declaration (#3034). */
|
|
154
|
+
"intent-symbol-ambiguous",
|
|
155
|
+
];
|
|
156
|
+
/** Why `why` can't account for some or all of a region (#3034). Closed. */
|
|
157
|
+
export const INTENT_WHY_CODES = [
|
|
158
|
+
/** No current decision governs the region, at any granularity, and none is carried out by the commits or runs that made its lines. */
|
|
159
|
+
"intent-why-no-decision",
|
|
160
|
+
/** None of the region's current lines was made by a commit an agent run is joined to. */
|
|
161
|
+
"intent-why-no-run",
|
|
162
|
+
/** Some of the region's lines are not committed, so nothing made them yet. */
|
|
163
|
+
"intent-why-uncommitted",
|
|
164
|
+
/** Some lines come from a commit that several runs made, and no run's recorded hunks say which wrote them. */
|
|
165
|
+
"intent-why-run-ambiguous",
|
|
166
|
+
];
|
|
167
|
+
export class IntentError extends Error {
|
|
168
|
+
constructor(code, message) {
|
|
169
|
+
super(message);
|
|
170
|
+
this.code = code;
|
|
171
|
+
this.name = "IntentError";
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// ── Git ──────────────────────────────────────────────────────────────────────
|
|
175
|
+
function git(top, args, input) {
|
|
176
|
+
return execFileSync("git", args, { cwd: top, encoding: "utf-8", input, stdio: [input === undefined ? "ignore" : "pipe", "pipe", "pipe"], maxBuffer: 512 * 1024 * 1024 });
|
|
177
|
+
}
|
|
178
|
+
export function tryGit(top, args, input) {
|
|
179
|
+
try {
|
|
180
|
+
return git(top, args, input);
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/** The new-side line ranges of each hunk header in a patch. */
|
|
187
|
+
function hunkRanges(patch) {
|
|
188
|
+
const out = [];
|
|
189
|
+
for (const m of patch.matchAll(/^@@+ [^@]*\+(\d+)(?:,(\d+))? @@/gm)) {
|
|
190
|
+
const start = Number(m[1]);
|
|
191
|
+
const count = m[2] === undefined ? 1 : Number(m[2]);
|
|
192
|
+
if (count > 0)
|
|
193
|
+
out.push({ start, end: start + count - 1 });
|
|
194
|
+
}
|
|
195
|
+
return out;
|
|
196
|
+
}
|
|
197
|
+
/** The commits that touched the region, newest first, with the lines each changed for a line range. */
|
|
198
|
+
export function regionHistory(top, rev, gitPath, type, lines) {
|
|
199
|
+
if (lines) {
|
|
200
|
+
const out = tryGit(top, ["log", `-L${lines.start},${lines.end}:${gitPath}`, "--format=%x00%H", "--no-color", rev]);
|
|
201
|
+
if (out === undefined)
|
|
202
|
+
return [];
|
|
203
|
+
return out
|
|
204
|
+
.split("\0")
|
|
205
|
+
.filter(Boolean)
|
|
206
|
+
.map((chunk) => {
|
|
207
|
+
const nl = chunk.indexOf("\n");
|
|
208
|
+
const sha = (nl < 0 ? chunk : chunk.slice(0, nl)).trim();
|
|
209
|
+
return { sha, lines: hunkRanges(nl < 0 ? "" : chunk.slice(nl + 1)) };
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
const args = type === "file" ? ["log", "--follow", "--format=%H", rev, "--", gitPath] : ["log", "--format=%H", rev, "--", gitPath === "" ? "." : gitPath];
|
|
213
|
+
const out = tryGit(top, args);
|
|
214
|
+
if (out === undefined)
|
|
215
|
+
return [];
|
|
216
|
+
return out
|
|
217
|
+
.split("\n")
|
|
218
|
+
.map((s) => s.trim())
|
|
219
|
+
.filter(Boolean)
|
|
220
|
+
.map((sha) => ({ sha, lines: null }));
|
|
221
|
+
}
|
|
222
|
+
const ZERO_SHA = /^0+$/;
|
|
223
|
+
/**
|
|
224
|
+
* `git blame` over a file's lines (#3034): at `rev`, or in the working tree
|
|
225
|
+
* when `rev` is null, where a line not committed yet has no commit. Undefined
|
|
226
|
+
* when git can't blame the file, such as one never committed.
|
|
227
|
+
*/
|
|
228
|
+
export function blameLines(top, rev, gitPath, lines) {
|
|
229
|
+
const out = tryGit(top, ["blame", "--line-porcelain", ...(lines ? [`-L${lines.start},${lines.end}`] : []), ...(rev ? [rev] : []), "--", gitPath]);
|
|
230
|
+
if (out === undefined)
|
|
231
|
+
return undefined;
|
|
232
|
+
const result = [];
|
|
233
|
+
let current;
|
|
234
|
+
for (const l of out.split("\n")) {
|
|
235
|
+
if (l.startsWith("\t")) {
|
|
236
|
+
if (current)
|
|
237
|
+
result.push({ line: current.line, sha: ZERO_SHA.test(current.sha) ? null : current.sha, origPath: current.origPath, origLine: current.origLine });
|
|
238
|
+
current = undefined;
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
const head = l.match(/^([0-9a-f]{40,64}) (\d+) (\d+)(?: \d+)?$/);
|
|
242
|
+
if (head) {
|
|
243
|
+
current = { sha: head[1], origLine: Number(head[2]), line: Number(head[3]), origPath: gitPath };
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
if (current && l.startsWith("filename "))
|
|
247
|
+
current.origPath = l.slice("filename ".length);
|
|
248
|
+
}
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
251
|
+
/** Subject, body, author, date and trailers of each commit. */
|
|
252
|
+
export function commitDetails(top, shas) {
|
|
253
|
+
const out = new Map();
|
|
254
|
+
if (shas.length === 0)
|
|
255
|
+
return out;
|
|
256
|
+
const text = git(top, ["log", "--no-walk=unsorted", "--stdin", "--format=%x00%H%x1f%an%x1f%ae%x1f%aI%x1f%(trailers:only,unfold,separator=%x1e)%x1f%B"], `${shas.join("\n")}\n`);
|
|
257
|
+
for (const record of text.split("\0")) {
|
|
258
|
+
if (!record)
|
|
259
|
+
continue;
|
|
260
|
+
const [sha, name, email, date, trailerText, ...rest] = record.split("\x1f");
|
|
261
|
+
const message = rest.join("\x1f").replace(/\n+$/, "");
|
|
262
|
+
const nl = message.indexOf("\n");
|
|
263
|
+
const trailers = {};
|
|
264
|
+
for (const t of (trailerText ?? "").split("\x1e")) {
|
|
265
|
+
const colon = t.indexOf(":");
|
|
266
|
+
if (colon <= 0)
|
|
267
|
+
continue;
|
|
268
|
+
const key = t.slice(0, colon).trim();
|
|
269
|
+
(trailers[key] ?? (trailers[key] = [])).push(t.slice(colon + 1).trim());
|
|
270
|
+
}
|
|
271
|
+
out.set(sha.trim(), {
|
|
272
|
+
sha: sha.trim(),
|
|
273
|
+
subject: nl < 0 ? message : message.slice(0, nl),
|
|
274
|
+
body: nl < 0 ? "" : message.slice(nl + 1).replace(/^\n+/, ""),
|
|
275
|
+
author: { name, email },
|
|
276
|
+
date,
|
|
277
|
+
trailers,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
return out;
|
|
281
|
+
}
|
|
282
|
+
/** The commit reachable from `rev` that last added `path` (from the repository root), or null. */
|
|
283
|
+
export function addingCommit(top, rev, path) {
|
|
284
|
+
const out = tryGit(top, ["log", "--diff-filter=A", "-1", "--format=%H", rev, "--", path]);
|
|
285
|
+
return out?.trim() || null;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* The commit reachable from `rev` that moved the record at `path` into a
|
|
289
|
+
* closed state and kept it there, or null when the record is not closed at
|
|
290
|
+
* `rev` (#2683). A record added in a closed state closes in the commit that
|
|
291
|
+
* added it.
|
|
292
|
+
*/
|
|
293
|
+
export function closingCommit(top, rev, path, stateField, closed) {
|
|
294
|
+
const out = tryGit(top, ["log", "--format=%H", rev, "--", path]);
|
|
295
|
+
let closing = null;
|
|
296
|
+
for (const sha of (out ?? "").split("\n").map((l) => l.trim()).filter(Boolean)) {
|
|
297
|
+
const text = tryGit(top, ["show", `${sha}:${path}`]);
|
|
298
|
+
const fm = text === undefined ? undefined : parseFrontMatter(text);
|
|
299
|
+
const state = fm?.ok ? fm.value[stateField] : undefined;
|
|
300
|
+
if (typeof state !== "string" || !closed.includes(state))
|
|
301
|
+
break;
|
|
302
|
+
closing = sha;
|
|
303
|
+
}
|
|
304
|
+
return closing;
|
|
305
|
+
}
|
|
306
|
+
/** The commit a decision record's window opens at: the one that decided it, or, when it is not approved, the one that added it. */
|
|
307
|
+
export function windowOpening(top, rev, view) {
|
|
308
|
+
return view.decidedIn?.sha ?? addingCommit(top, rev, view.path);
|
|
309
|
+
}
|
|
310
|
+
/** `commit` and every commit between it and `rev` that has it as an ancestor. */
|
|
311
|
+
export function descendants(top, commit, rev) {
|
|
312
|
+
const out = new Set([commit]);
|
|
313
|
+
const text = tryGit(top, ["rev-list", "--ancestry-path", `${commit}..${rev}`]);
|
|
314
|
+
for (const line of (text ?? "").split("\n"))
|
|
315
|
+
if (line.trim())
|
|
316
|
+
out.add(line.trim());
|
|
317
|
+
return out;
|
|
318
|
+
}
|
|
319
|
+
/** `owner/repo` of the `origin` remote, lower case, or null. Read from local config; nothing is fetched. */
|
|
320
|
+
function originRepo(top) {
|
|
321
|
+
const url = tryGit(top, ["config", "--get", "remote.origin.url"])?.trim();
|
|
322
|
+
const m = url?.match(/[:/]([^/:]+)\/([^/]+?)(?:\.git)?\/?$/);
|
|
323
|
+
return m ? `${m[1]}/${m[2]}`.toLowerCase() : null;
|
|
324
|
+
}
|
|
325
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
326
|
+
const toPosix = (p) => (sep === "/" ? p : p.split(sep).join("/"));
|
|
327
|
+
function realpathOr(p) {
|
|
328
|
+
try {
|
|
329
|
+
return realpathSync(p);
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
return p;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
/** Parse `path`, `path:line` or `path:start-end`. */
|
|
336
|
+
export function parseRegion(arg) {
|
|
337
|
+
const m = arg.match(/^(.+?):(\d+)(?:-(\d+))?$/);
|
|
338
|
+
if (!m)
|
|
339
|
+
return { path: arg, lines: null };
|
|
340
|
+
const start = Number(m[2]);
|
|
341
|
+
const end = m[3] === undefined ? start : Number(m[3]);
|
|
342
|
+
if (start < 1 || end < start)
|
|
343
|
+
return { error: `${arg}: a line range is start-end with 1 <= start <= end` };
|
|
344
|
+
return { path: m[1], lines: { start, end } };
|
|
345
|
+
}
|
|
346
|
+
function regionId(path, lines, symbol) {
|
|
347
|
+
if (symbol !== undefined)
|
|
348
|
+
return `region:${path}#${symbol}`;
|
|
349
|
+
return `region:${path}${lines ? `:${lines.start}${lines.end === lines.start ? "" : `-${lines.end}`}` : ""}`;
|
|
350
|
+
}
|
|
351
|
+
function filesUnder(tree, dir) {
|
|
352
|
+
const out = [];
|
|
353
|
+
const walk = (d) => {
|
|
354
|
+
for (const e of (tree.list(d) ?? []).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
355
|
+
const p = joinPath(d, e.name);
|
|
356
|
+
if (e.type === "dir") {
|
|
357
|
+
if (!skippedDir(e.name))
|
|
358
|
+
walk(p);
|
|
359
|
+
}
|
|
360
|
+
else
|
|
361
|
+
out.push(p);
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
walk(dir === "." ? "" : dir);
|
|
365
|
+
return out;
|
|
366
|
+
}
|
|
367
|
+
function stringOr(v) {
|
|
368
|
+
return typeof v === "string" ? v : null;
|
|
369
|
+
}
|
|
370
|
+
/** A work record's `source` when it names the gap it came from (#2683), or null. */
|
|
371
|
+
function gapSource(data) {
|
|
372
|
+
const src = data?.source;
|
|
373
|
+
if (src === null || typeof src !== "object" || Array.isArray(src))
|
|
374
|
+
return null;
|
|
375
|
+
const s = src;
|
|
376
|
+
if (typeof s.finding !== "string" || typeof s.region !== "string")
|
|
377
|
+
return null;
|
|
378
|
+
return {
|
|
379
|
+
finding: s.finding,
|
|
380
|
+
region: s.region,
|
|
381
|
+
...(typeof s.decision === "string" ? { decision: s.decision } : {}),
|
|
382
|
+
...(typeof s.artifact === "string" ? { artifact: s.artifact } : {}),
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
function reviewSummary(data) {
|
|
386
|
+
const out = { agree: 0, dissent: 0, abstain: 0, openConcerns: 0 };
|
|
387
|
+
const list = data?.reviews;
|
|
388
|
+
if (!Array.isArray(list))
|
|
389
|
+
return out;
|
|
390
|
+
for (const r of list) {
|
|
391
|
+
if (r === null || typeof r !== "object")
|
|
392
|
+
continue;
|
|
393
|
+
const review = r;
|
|
394
|
+
if (review.verdict === "agree")
|
|
395
|
+
out.agree++;
|
|
396
|
+
else if (review.verdict === "abstain")
|
|
397
|
+
out.abstain++;
|
|
398
|
+
else if (review.verdict === "dissent") {
|
|
399
|
+
out.dissent++;
|
|
400
|
+
if (review.addressed_by == null && review.withdrawn_on == null)
|
|
401
|
+
out.openConcerns++;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return out;
|
|
405
|
+
}
|
|
406
|
+
const SHA256 = /^[0-9a-f]{64}$/;
|
|
407
|
+
const ISSUE = /^([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)#([0-9]+)$/;
|
|
408
|
+
const PIN_ORDER = ["missing", "drifted", "stale", "pinned"];
|
|
409
|
+
/** The default node resolver: the member's graph, read as `workspace graph --member` reads it. */
|
|
410
|
+
async function resolveNodeFromGraph(cwd, at, member, id) {
|
|
411
|
+
const { workspaceGraph } = await import("./graph-cli.js");
|
|
412
|
+
const { doc } = await workspaceGraph({ cwd, at, members: [member] });
|
|
413
|
+
if ("error" in doc)
|
|
414
|
+
return null;
|
|
415
|
+
const node = doc.nodes.find((n) => n.id === id);
|
|
416
|
+
if (!node)
|
|
417
|
+
return null;
|
|
418
|
+
return node.sourceLoc ? { file: node.sourceLoc.file, line: node.sourceLoc.line ?? null } : { file: ".", line: null };
|
|
419
|
+
}
|
|
420
|
+
export async function loadKinds(query, top) {
|
|
421
|
+
const out = [];
|
|
422
|
+
for (const k of query.kinds ?? []) {
|
|
423
|
+
const file = resolve(query.cwd, k);
|
|
424
|
+
const display = toPosix(relative(top, realpathOr(file)));
|
|
425
|
+
let mod;
|
|
426
|
+
try {
|
|
427
|
+
mod = await importKindModule(file);
|
|
428
|
+
}
|
|
429
|
+
catch (err) {
|
|
430
|
+
throw new IntentError("kind-unreadable", `kind file ${k} could not be loaded: ${err instanceof Error ? err.message : String(err)}`);
|
|
431
|
+
}
|
|
432
|
+
const joins = readCommitJoins(mod);
|
|
433
|
+
if (typeof joins === "string")
|
|
434
|
+
throw new IntentError("kind-invalid", `kind file ${k} has a commitJoins export that can't be read: ${joins}`);
|
|
435
|
+
const kind = { file, display, name: joins?.name ?? basename(file).replace(/(?:\.kind)?\.[cm]?[jt]s$/, ""), ...(joins ? { joins } : {}) };
|
|
436
|
+
if (mod.recordKind !== undefined) {
|
|
437
|
+
const doc = await queryRecords({ kind: file, at: query.at, cwd: query.cwd, workGaps: false });
|
|
438
|
+
if ("error" in doc)
|
|
439
|
+
throw new IntentError(doc.error.code, doc.error.message);
|
|
440
|
+
try {
|
|
441
|
+
kind.records = { loaded: await loadRecordKind(file), views: doc.records, workspaceRoot: doc.workspaceRoot };
|
|
442
|
+
kind.name = joins?.name ?? kind.records.loaded.kind.name;
|
|
443
|
+
}
|
|
444
|
+
catch (err) {
|
|
445
|
+
if (err instanceof RecordReadError)
|
|
446
|
+
throw new IntentError(err.code, err.message);
|
|
447
|
+
throw err;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
else if (!joins) {
|
|
451
|
+
throw new IntentError("kind-invalid", `kind file ${k} exports neither recordKind nor commitJoins`);
|
|
452
|
+
}
|
|
453
|
+
out.push(kind);
|
|
454
|
+
}
|
|
455
|
+
return out;
|
|
456
|
+
}
|
|
457
|
+
async function resolveRegion(query, located, declaration) {
|
|
458
|
+
const parsed = parseRegion(query.region);
|
|
459
|
+
if ("error" in parsed)
|
|
460
|
+
throw new IntentError("intent-region-invalid", parsed.error);
|
|
461
|
+
const inWorkspace = (p) => {
|
|
462
|
+
const rel = toPosix(relative(realpathOr(located.rootOnDisk), resolve(realpathOr(query.cwd), p)));
|
|
463
|
+
const path = rel === "" ? "." : rel;
|
|
464
|
+
return path === "." || (isWorkspacePath(path) && !path.startsWith("../") && located.tree.stat(path) !== undefined) ? path : undefined;
|
|
465
|
+
};
|
|
466
|
+
const path = inWorkspace(parsed.path);
|
|
467
|
+
if (path !== undefined)
|
|
468
|
+
return { path, lines: parsed.lines, node: null, symbol: null };
|
|
469
|
+
// path#symbol (#3034): the symbol's current lines in the tree read.
|
|
470
|
+
const hash = parsed.lines ? -1 : query.region.lastIndexOf("#");
|
|
471
|
+
if (hash > 0) {
|
|
472
|
+
const name = query.region.slice(hash + 1);
|
|
473
|
+
const file = inWorkspace(query.region.slice(0, hash));
|
|
474
|
+
if (file !== undefined && name !== "") {
|
|
475
|
+
if (file === "." || located.tree.stat(file) !== "file")
|
|
476
|
+
throw new IntentError("intent-region-invalid", `${file} is a directory, and a symbol needs a file`);
|
|
477
|
+
// A file core's resolver doesn't read may have one from its member's lexicons (#3313).
|
|
478
|
+
let extra = [];
|
|
479
|
+
let why = "";
|
|
480
|
+
if (!symbolResolverFor(file)) {
|
|
481
|
+
const holder = memberHolding(file, declaration.members);
|
|
482
|
+
const m = holder ? declaration.members.find((x) => x.name === holder) : undefined;
|
|
483
|
+
if (m && m.kind === "chant") {
|
|
484
|
+
const got = await memberSymbolResolvers(memberDirOnDisk(located.rootOnDisk, m.dir), query.loadLexicon);
|
|
485
|
+
extra = got.resolvers;
|
|
486
|
+
if (got.problems.length > 0)
|
|
487
|
+
why = `; member ${m.name}: ${got.problems.join("; ")}`;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
const r = resolveSymbol(file, located.tree.read(file), name, extra);
|
|
491
|
+
if (!r.ok)
|
|
492
|
+
throw new IntentError(r.reason === "unsupported" ? "intent-symbol-unsupported" : r.reason === "ambiguous" ? "intent-symbol-ambiguous" : "intent-symbol-unknown", `${r.message}${r.reason === "unsupported" ? why : ""}${located.tree.label}`);
|
|
493
|
+
return { path: file, lines: r.declaration.lines, node: null, symbol: { name, qualified: r.declaration.qualified, kind: r.declaration.kind } };
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
// A graph node id, <member>/<id>, when no such path exists (#2650 B1).
|
|
497
|
+
const slash = query.region.indexOf("/");
|
|
498
|
+
const member = slash > 0 ? declaration.members.find((m) => m.name === query.region.slice(0, slash)) : undefined;
|
|
499
|
+
if (member && !parsed.lines) {
|
|
500
|
+
const loc = await (query.resolveNode ?? resolveNodeFromGraph)(query.cwd, query.at, member.name, query.region);
|
|
501
|
+
if (loc) {
|
|
502
|
+
const file = joinPath(member.dir, loc.file);
|
|
503
|
+
return { path: file === "" ? "." : file, lines: loc.line ? { start: loc.line, end: loc.line } : null, node: query.region, symbol: null };
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
throw new IntentError("intent-region-invalid", `${query.region} is not a path in the workspace${located.tree.label}, a path#symbol, or a graph node id`);
|
|
507
|
+
}
|
|
508
|
+
/** Build the intent document. Never throws an {@link IntentError}, {@link WorkspaceReadError} or {@link RecordReadError}. */
|
|
509
|
+
export async function intentGraph(query) {
|
|
510
|
+
const head = { $schema: INTENT_OUTPUT_SCHEMA_ID, contract: INTENT_CONTRACT_VERSION, chant: readerVersion() };
|
|
511
|
+
try {
|
|
512
|
+
return await walk(query, head);
|
|
513
|
+
}
|
|
514
|
+
catch (err) {
|
|
515
|
+
if (err instanceof IntentError || err instanceof WorkspaceReadError || err instanceof RecordReadError) {
|
|
516
|
+
return { doc: { ...head, error: { code: err.code, message: err.message } }, failed: true };
|
|
517
|
+
}
|
|
518
|
+
throw err;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
async function walk(query, head) {
|
|
522
|
+
const located = locateWorkspace(query.cwd, query.at);
|
|
523
|
+
const top = located.top;
|
|
524
|
+
if (!top)
|
|
525
|
+
throw new IntentError("not-a-git-repository", "the intent graph reads git history, and this directory is not in a git repository");
|
|
526
|
+
const declaration = readDeclaration(located.tree, "", { rootChant: true });
|
|
527
|
+
const region = await resolveRegion(query, located, declaration);
|
|
528
|
+
const type = region.path === "." ? "dir" : located.tree.stat(region.path);
|
|
529
|
+
if (!type)
|
|
530
|
+
throw new IntentError("intent-region-invalid", `${region.path} does not exist${located.tree.label}`);
|
|
531
|
+
if (region.lines) {
|
|
532
|
+
if (type !== "file")
|
|
533
|
+
throw new IntentError("intent-region-invalid", `${region.path} is a directory, and a line range needs a file`);
|
|
534
|
+
const count = located.tree.read(region.path).replace(/\n$/, "").split("\n").length;
|
|
535
|
+
if (region.lines.end > count)
|
|
536
|
+
throw new IntentError("intent-region-invalid", `${region.path} has ${count} lines${located.tree.label}, so ${region.lines.start}-${region.lines.end} is not in it`);
|
|
537
|
+
}
|
|
538
|
+
// Without --kind, the declared record kinds, read from the working tree as a --kind file is (#2680).
|
|
539
|
+
const kindFiles = query.kinds ?? declaredRecordKinds(declaration).map((d) => declaredKindFile(d, located.rootOnDisk));
|
|
540
|
+
const kinds = await loadKinds({ ...query, kinds: kindFiles }, top);
|
|
541
|
+
const nodes = new Map();
|
|
542
|
+
const edges = [];
|
|
543
|
+
const reasons = [];
|
|
544
|
+
const findings = [];
|
|
545
|
+
const add = (n) => {
|
|
546
|
+
if (!nodes.has(n.id))
|
|
547
|
+
nodes.set(n.id, n);
|
|
548
|
+
return nodes.get(n.id);
|
|
549
|
+
};
|
|
550
|
+
const find = (code, message, concerns) => {
|
|
551
|
+
findings.push({ id: `finding:${code}:${findings.filter((f) => f.code === code).length + 1}`, kind: "finding", code, message, concerns });
|
|
552
|
+
};
|
|
553
|
+
// The region, its files and its member.
|
|
554
|
+
const member = region.path === "." ? (declaration.members.find((m) => m.dir === ".")?.name ?? null) : memberHolding(region.path, declaration.members);
|
|
555
|
+
const rid = regionId(region.path, region.lines, region.symbol?.name);
|
|
556
|
+
add({
|
|
557
|
+
id: rid,
|
|
558
|
+
kind: "region",
|
|
559
|
+
path: region.path,
|
|
560
|
+
lines: region.lines,
|
|
561
|
+
member,
|
|
562
|
+
at: located.at,
|
|
563
|
+
type,
|
|
564
|
+
generated: type === "file" ? isGeneratedPath(declaration, region.path) : null,
|
|
565
|
+
node: region.node,
|
|
566
|
+
symbol: region.symbol,
|
|
567
|
+
});
|
|
568
|
+
const files = type === "dir" ? filesUnder(located.tree, region.path) : [];
|
|
569
|
+
for (const f of files)
|
|
570
|
+
add({ id: `file:${f}`, kind: "file", path: f, member: memberHolding(f, declaration.members), generated: isGeneratedPath(declaration, f) });
|
|
571
|
+
const memberNode = (name) => {
|
|
572
|
+
const m = declaration.members.find((x) => x.name === name);
|
|
573
|
+
return add({ id: `member:${name}`, kind: "member", name, dir: m?.dir ?? "", memberKind: m?.kind ?? "" });
|
|
574
|
+
};
|
|
575
|
+
if (member)
|
|
576
|
+
memberNode(member);
|
|
577
|
+
// 1. The region's history.
|
|
578
|
+
const rev = located.at ?? (tryGit(top, ["rev-parse", "--verify", "--quiet", "HEAD^{commit}"])?.trim() || null);
|
|
579
|
+
const shallow = tryGit(top, ["rev-parse", "--is-shallow-repository"])?.trim() === "true";
|
|
580
|
+
if (shallow)
|
|
581
|
+
reasons.push({ code: "intent-history-shallow", message: "this is a shallow clone, so the history stops at its boundary and the oldest commit listed may stand for many" });
|
|
582
|
+
const workspacePrefix = located.root === "." ? "" : located.root;
|
|
583
|
+
const gitPath = joinPath(workspacePrefix, region.path);
|
|
584
|
+
const touched = rev ? regionHistory(top, rev, gitPath, type, region.lines) : [];
|
|
585
|
+
// Who last wrote each current line of a file region (#3034). A commit blame
|
|
586
|
+
// names and the history walk missed, as when a move confuses git log -L,
|
|
587
|
+
// joins the commits that touched the region.
|
|
588
|
+
const fileLines = type === "file" ? (region.lines ?? { start: 1, end: Math.max(1, located.tree.read(region.path).replace(/\n$/, "").split("\n").length) }) : null;
|
|
589
|
+
const blamed = fileLines === null
|
|
590
|
+
? []
|
|
591
|
+
: ((rev ? blameLines(top, located.at, gitPath, region.lines) : undefined) ??
|
|
592
|
+
// A file git can't blame in the working tree has never been committed: every line is new.
|
|
593
|
+
(located.at === null ? Array.from({ length: fileLines.end - fileLines.start + 1 }, (_, i) => ({ line: fileLines.start + i, sha: null, origPath: gitPath, origLine: fileLines.start + i })) : []));
|
|
594
|
+
for (const sha of new Set(blamed.map((b) => b.sha).filter((x) => x !== null))) {
|
|
595
|
+
if (!touched.some((t) => t.sha === sha))
|
|
596
|
+
touched.push({ sha, lines: null });
|
|
597
|
+
}
|
|
598
|
+
const details = commitDetails(top, touched.map((t) => t.sha));
|
|
599
|
+
// Squash merges, followed to the pull request's original commits when asked (#3035).
|
|
600
|
+
const follows = query.followSquash ? followSquashes(top, [...details.values()], { fetchMissing: true }) : new Map();
|
|
601
|
+
for (const f of follows.values()) {
|
|
602
|
+
if (f.problem)
|
|
603
|
+
reasons.push({ code: "squash-unfollowed", message: `${f.sha.slice(0, 8)} squashes pull request #${f.pullRequest}, and ${f.problem}, so its original commits are not followed` });
|
|
604
|
+
}
|
|
605
|
+
const originalDetails = commitDetails(top, [...new Set([...follows.values()].flatMap((f) => f.commits))]);
|
|
606
|
+
const originalsOf = (sha) => (follows.get(sha)?.commits ?? []).map((o) => originalDetails.get(o)).filter((x) => x !== undefined);
|
|
607
|
+
// Each commit's provenance, judged by the policy at base as records judges a record's (#2547).
|
|
608
|
+
const policy = policyAtBase(top, resolveBase(top));
|
|
609
|
+
const attestors = policy.active ? await activeAttestors() : [];
|
|
610
|
+
const readAt = (path) => {
|
|
611
|
+
if (!isWorkspacePath(path) || located.tree.stat(path) !== "file")
|
|
612
|
+
return undefined;
|
|
613
|
+
return located.tree.read(path);
|
|
614
|
+
};
|
|
615
|
+
// The same tree as readAt (#2663): entries directly inside dir, from the workspace root, a directory's with a trailing slash.
|
|
616
|
+
const listAt = (dir) => {
|
|
617
|
+
const d = dir === "." ? "" : dir.replace(/\/+$/, "");
|
|
618
|
+
if (d !== "" && (!isWorkspacePath(d) || located.tree.stat(d) !== "dir"))
|
|
619
|
+
return undefined;
|
|
620
|
+
const entries = located.tree.list(d);
|
|
621
|
+
return entries?.map((e) => `${joinPath(d, e.name)}${e.type === "dir" ? "/" : ""}`).sort();
|
|
622
|
+
};
|
|
623
|
+
const joined = new Map();
|
|
624
|
+
// chant's own trailers on each commit (#3149), with the items their lease tokens name.
|
|
625
|
+
const trailerJoins = readTrailerJoins(top, [...details.values(), ...originalDetails.values()]);
|
|
626
|
+
const pluginFindings = [];
|
|
627
|
+
const signatureOf = (sha) => {
|
|
628
|
+
if (!policy.active)
|
|
629
|
+
return { level: "unattested", reason: policy.problems.length ? policy.problems.join("; ") : `no signers file (${policy.signersPath}) at base; attestation is off` };
|
|
630
|
+
const p = commitProvenance(top, policy, sha, attestors);
|
|
631
|
+
return { level: p.level, reason: p.reason, ...(p.principal ? { principal: p.principal } : {}) };
|
|
632
|
+
};
|
|
633
|
+
const failedPlugins = new Set();
|
|
634
|
+
for (const t of touched) {
|
|
635
|
+
const c = details.get(t.sha);
|
|
636
|
+
if (!c)
|
|
637
|
+
continue;
|
|
638
|
+
const signature = signatureOf(c.sha);
|
|
639
|
+
const pr = c.subject.match(/\(#([0-9]+)\)\s*$/);
|
|
640
|
+
const cid = `commit:${c.sha}`;
|
|
641
|
+
const f = follows.get(c.sha);
|
|
642
|
+
const squash = f
|
|
643
|
+
? {
|
|
644
|
+
pullRequest: f.pullRequest,
|
|
645
|
+
forge: f.forge,
|
|
646
|
+
ref: f.ref,
|
|
647
|
+
head: f.head,
|
|
648
|
+
fetched: f.fetched,
|
|
649
|
+
followed: f.followed,
|
|
650
|
+
commits: originalsOf(c.sha).map((o) => ({ sha: o.sha, subject: o.subject, author: o.author, date: o.date, trailers: o.trailers, signature: signatureOf(o.sha), joins: trailerJoins.get(o.sha).joins })),
|
|
651
|
+
}
|
|
652
|
+
: undefined;
|
|
653
|
+
add({ id: cid, kind: "commit", sha: c.sha, subject: c.subject, author: c.author, date: c.date, trailers: c.trailers, pullRequest: pr ? Number(pr[1]) : null, signature, lines: t.lines, state: null, joins: trailerJoins.get(c.sha).joins, ...(squash ? { squash } : {}) });
|
|
654
|
+
edges.push({ kind: "touched-by", from: rid, to: cid, lines: t.lines });
|
|
655
|
+
// 2. Each commit's origin, from the plugins. A followed squash also takes its original commits' (#3035).
|
|
656
|
+
const entry = { contracts: [], authorship: [], decisions: [], carries: [] };
|
|
657
|
+
joined.set(c.sha, entry);
|
|
658
|
+
const pushEdge = (e) => {
|
|
659
|
+
if (!edges.some((x) => x.kind === e.kind && x.from === e.from && x.to === e.to))
|
|
660
|
+
edges.push(e);
|
|
661
|
+
};
|
|
662
|
+
const subjects = [c, ...originalsOf(c.sha)];
|
|
663
|
+
for (const k of kinds) {
|
|
664
|
+
if (!k.joins)
|
|
665
|
+
continue;
|
|
666
|
+
for (const subject of subjects) {
|
|
667
|
+
const original = subject !== c;
|
|
668
|
+
let result;
|
|
669
|
+
try {
|
|
670
|
+
result = await runCommitJoins(k.joins, subject, { read: readAt, list: listAt, at: located.at }, k.name);
|
|
671
|
+
}
|
|
672
|
+
catch (err) {
|
|
673
|
+
const message = `${k.display}: commitJoins failed for ${subject.sha.slice(0, 8)}: ${err instanceof Error ? err.message : String(err)}`;
|
|
674
|
+
if (!failedPlugins.has(message))
|
|
675
|
+
reasons.push({ code: "intent-plugin-failed", message });
|
|
676
|
+
failedPlugins.add(message);
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
679
|
+
const joinedNode = (kind, e) => {
|
|
680
|
+
const { id, ...data } = e;
|
|
681
|
+
return add({ id: `${kind}:${id}`, kind, ref: id, plugin: k.display, data }).id;
|
|
682
|
+
};
|
|
683
|
+
const unitId = result.unit ? joinedNode("unit", result.unit) : undefined;
|
|
684
|
+
const contractId = result.contract ? joinedNode("contract", result.contract) : undefined;
|
|
685
|
+
if (unitId) {
|
|
686
|
+
if (original)
|
|
687
|
+
entry.unit ?? (entry.unit = unitId);
|
|
688
|
+
else
|
|
689
|
+
entry.unit = unitId;
|
|
690
|
+
pushEdge({ kind: "produced-by", from: cid, to: unitId });
|
|
691
|
+
if (contractId)
|
|
692
|
+
pushEdge({ kind: "serves", from: unitId, to: contractId });
|
|
693
|
+
}
|
|
694
|
+
if (contractId)
|
|
695
|
+
entry.contracts.push(result.contract.id);
|
|
696
|
+
if (unitId)
|
|
697
|
+
entry.decisions.push(...entityDecisions(result.unit), ...entityDecisions(result.contract));
|
|
698
|
+
for (const f of result.findings ?? [])
|
|
699
|
+
pluginFindings.push({ commit: cid, plugin: k.display, finding: f });
|
|
700
|
+
const evidence = result.evidence === undefined ? [] : Array.isArray(result.evidence) ? result.evidence : [result.evidence];
|
|
701
|
+
for (const e of evidence)
|
|
702
|
+
pushEdge({ kind: "cites-evidence", from: unitId ?? contractId ?? cid, to: joinedNode("evidence", e) });
|
|
703
|
+
// Authorship is checked against the squash's own trailers, so only its own claims count.
|
|
704
|
+
if (!original)
|
|
705
|
+
entry.authorship.push(...(result.authorship ?? []));
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
// The agent runs that made each commit (#3033): the one its Chant-Run names, and any whose record lists it.
|
|
710
|
+
const runJoins = await runsForCommits(top, located.rootOnDisk, [...new Set([...touched.map((t) => t.sha), ...originalDetails.keys()])].map((sha) => ({ sha, run: trailerJoins.get(sha)?.joins.run ?? null })));
|
|
711
|
+
// A followed squash joins its original commits' runs (#3035).
|
|
712
|
+
addSquashRuns(runJoins.refs, follows);
|
|
713
|
+
for (const t of touched) {
|
|
714
|
+
if (!nodes.has(`commit:${t.sha}`))
|
|
715
|
+
continue;
|
|
716
|
+
for (const ref of runJoins.refs.get(t.sha) ?? []) {
|
|
717
|
+
const r = runJoins.runs.get(ref.id);
|
|
718
|
+
const node = add({
|
|
719
|
+
id: `run:${ref.id}`,
|
|
720
|
+
kind: "run",
|
|
721
|
+
run: ref.id,
|
|
722
|
+
recorded: r !== undefined,
|
|
723
|
+
state: r?.state ?? null,
|
|
724
|
+
harness: r?.harness ?? null,
|
|
725
|
+
model: r?.model ?? null,
|
|
726
|
+
provider: r?.provider ?? null,
|
|
727
|
+
by: r?.by ?? null,
|
|
728
|
+
agent: r?.agent ?? null,
|
|
729
|
+
unit: r?.unit?.id ?? null,
|
|
730
|
+
startedAt: r?.startedAt ?? null,
|
|
731
|
+
endedAt: r?.endedAt ?? null,
|
|
732
|
+
outcome: r?.outcome ?? null,
|
|
733
|
+
usage: r?.usage ?? null,
|
|
734
|
+
cost: r?.cost ?? null,
|
|
735
|
+
transcript: r?.transcript ?? null,
|
|
736
|
+
instruction: r?.instruction ?? null,
|
|
737
|
+
lease: r?.lease ?? null,
|
|
738
|
+
});
|
|
739
|
+
edges.push({ kind: "made-by", from: `commit:${t.sha}`, to: node.id, joinedBy: ref.joinedBy, ...(ref.recordedAs ? { recordedAs: ref.recordedAs } : {}), ...(ref.via ? { via: ref.via } : {}) });
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
// A commit a trailer or a record joins to one run, with the content of a commit another run recorded (#3036).
|
|
743
|
+
for (const x of runJoins.conflicts) {
|
|
744
|
+
if (!nodes.has(`commit:${x.sha}`))
|
|
745
|
+
continue;
|
|
746
|
+
const others = x.content.map((m) => `${m.run} (${m.sha.slice(0, 8)})`).join(", ");
|
|
747
|
+
find("intent-commit-join-conflict", `${x.sha.slice(0, 8)} joins ${x.joined.join(", ")} by its trailer or the run's record, and has the patch-id of a commit ${others} recorded; it is not joined to ${x.content.length === 1 ? "that run" : "those runs"}`, [`commit:${x.sha}`, ...x.joined.map((id) => `run:${id}`)]);
|
|
748
|
+
}
|
|
749
|
+
// 3. The decisions whose constrains cover the region, and their chains.
|
|
750
|
+
const regionFull = region.path === "." ? workspacePrefix : gitPath;
|
|
751
|
+
const origin = originRepo(top);
|
|
752
|
+
const commitRefs = new Set();
|
|
753
|
+
for (const c of details.values())
|
|
754
|
+
for (const m of c.subject.matchAll(/#([0-9]+)/g))
|
|
755
|
+
commitRefs.add(m[1]);
|
|
756
|
+
const contractIds = new Set([...joined.values()].flatMap((j) => j.contracts));
|
|
757
|
+
const covering = [];
|
|
758
|
+
const decisionById = new Map();
|
|
759
|
+
const decisionId = (k, id) => `record:${k.records.loaded.kind.name}/${id}`;
|
|
760
|
+
const decisionNode = (k, v) => {
|
|
761
|
+
const kind = k.records.loaded.kind;
|
|
762
|
+
const supersedes = supersedesTargets(kind, v.data);
|
|
763
|
+
return add({
|
|
764
|
+
id: decisionId(k, v.id),
|
|
765
|
+
kind: "decision",
|
|
766
|
+
recordKind: kind.name,
|
|
767
|
+
record: v.id,
|
|
768
|
+
path: v.path,
|
|
769
|
+
title: stringOr(v.data?.title),
|
|
770
|
+
state: v.state,
|
|
771
|
+
closed: v.state !== null && (kind.closedStates ?? []).includes(v.state),
|
|
772
|
+
valid: v.valid,
|
|
773
|
+
reasons: v.reasons,
|
|
774
|
+
provenance: { level: v.provenance.level, commit: v.provenance.commit, reason: v.provenance.reason },
|
|
775
|
+
decided_by: stringOr(v.data?.decided_by),
|
|
776
|
+
decided_on: stringOr(v.data?.decided_on),
|
|
777
|
+
decidedIn: v.decidedIn ?? null,
|
|
778
|
+
reviews: reviewSummary(v.data),
|
|
779
|
+
supersededBy: v.supersededBy,
|
|
780
|
+
supersedes,
|
|
781
|
+
constrains: [],
|
|
782
|
+
});
|
|
783
|
+
};
|
|
784
|
+
const fileNodesUnder = (full) => files.filter((f) => constraintCovers(full, joinPath(workspacePrefix, f)));
|
|
785
|
+
/** The constrains entries of a record that cover the region or a file under it. Decisions and work items match the same way (#2683). */
|
|
786
|
+
const matchConstrains = (k, v) => {
|
|
787
|
+
const field = k.records.loaded.kind.constrains?.field;
|
|
788
|
+
const kindPrefix = k.records.workspaceRoot === "." ? "" : k.records.workspaceRoot;
|
|
789
|
+
const list = field ? v.data?.[field] : undefined;
|
|
790
|
+
const matched = [];
|
|
791
|
+
if (!Array.isArray(list))
|
|
792
|
+
return matched;
|
|
793
|
+
for (const entry of list) {
|
|
794
|
+
if (typeof entry !== "string")
|
|
795
|
+
continue;
|
|
796
|
+
if (entry.startsWith("path:")) {
|
|
797
|
+
const p = entry.slice("path:".length);
|
|
798
|
+
if (!isWorkspacePath(p))
|
|
799
|
+
continue;
|
|
800
|
+
const full = joinPath(kindPrefix, p);
|
|
801
|
+
if (regionFull !== "" && constraintCovers(full, regionFull)) {
|
|
802
|
+
matched.push({ entry, granularity: "path", to: rid });
|
|
803
|
+
}
|
|
804
|
+
else if (type === "dir" && full !== regionFull && (regionFull === "" || full.startsWith(`${regionFull}/`))) {
|
|
805
|
+
// A path below a directory region constrains the files under it, not the whole region.
|
|
806
|
+
for (const f of fileNodesUnder(full))
|
|
807
|
+
matched.push({ entry, granularity: "path", to: `file:${f}` });
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
else if (entry.startsWith("member:")) {
|
|
811
|
+
if (member !== null && entry.slice("member:".length) === member)
|
|
812
|
+
matched.push({ entry, granularity: "member", to: rid });
|
|
813
|
+
}
|
|
814
|
+
else {
|
|
815
|
+
const issue = entry.match(ISSUE);
|
|
816
|
+
if (issue && origin !== null && issue[1].toLowerCase() === origin && commitRefs.has(issue[2]))
|
|
817
|
+
matched.push({ entry, granularity: "issue", to: rid });
|
|
818
|
+
else if (contractIds.has(entry))
|
|
819
|
+
matched.push({ entry, granularity: "contract", to: `contract:${entry}` });
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
return matched;
|
|
823
|
+
};
|
|
824
|
+
for (const k of kinds) {
|
|
825
|
+
if (!k.records || k.records.loaded.kind.work)
|
|
826
|
+
continue;
|
|
827
|
+
for (const v of k.records.views) {
|
|
828
|
+
if (v.id === null)
|
|
829
|
+
continue;
|
|
830
|
+
decisionById.set(`${k.records.loaded.kind.name}/${v.id}`, { view: v, kind: k });
|
|
831
|
+
const matched = matchConstrains(k, v);
|
|
832
|
+
if (matched.length === 0)
|
|
833
|
+
continue;
|
|
834
|
+
const node = decisionNode(k, v);
|
|
835
|
+
for (const m of matched) {
|
|
836
|
+
edges.push({ kind: "constrains", from: node.id, to: m.to, granularity: m.granularity, entry: m.entry });
|
|
837
|
+
if (m.to === rid || m.granularity === "contract")
|
|
838
|
+
node.constrains.push({ entry: m.entry, granularity: m.granularity });
|
|
839
|
+
}
|
|
840
|
+
if (node.constrains.length > 0)
|
|
841
|
+
covering.push({ view: v, kind: k, node });
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
// Supersession chains, both ways, as records derives them.
|
|
845
|
+
const queue = [...nodes.values()].filter((n) => n.kind === "decision");
|
|
846
|
+
while (queue.length > 0) {
|
|
847
|
+
const d = queue.shift();
|
|
848
|
+
const related = [];
|
|
849
|
+
if (d.supersededBy)
|
|
850
|
+
related.push(`${d.recordKind}/${d.supersededBy}`);
|
|
851
|
+
for (const [key, { view }] of decisionById)
|
|
852
|
+
if (key.startsWith(`${d.recordKind}/`) && view.supersededBy === d.record)
|
|
853
|
+
related.push(key);
|
|
854
|
+
for (const key of related) {
|
|
855
|
+
const hit = decisionById.get(key);
|
|
856
|
+
if (!hit)
|
|
857
|
+
continue;
|
|
858
|
+
const id = decisionId(hit.kind, hit.view.id);
|
|
859
|
+
if (nodes.has(id))
|
|
860
|
+
continue;
|
|
861
|
+
queue.push(decisionNode(hit.kind, hit.view));
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
const decisions = [...nodes.values()].filter((n) => n.kind === "decision");
|
|
865
|
+
for (const d of decisions) {
|
|
866
|
+
if (d.supersededBy) {
|
|
867
|
+
const to = `record:${d.recordKind}/${d.supersededBy}`;
|
|
868
|
+
if (nodes.has(to))
|
|
869
|
+
edges.push({ kind: "supersedes", from: to, to: d.id });
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
// 4. The artifacts those decisions pin, and their evidence entries.
|
|
873
|
+
const pinsOf = new Map();
|
|
874
|
+
for (const d of decisions) {
|
|
875
|
+
const hit = decisionById.get(`${d.recordKind}/${d.record}`);
|
|
876
|
+
const kind = hit.kind.records.loaded.kind;
|
|
877
|
+
for (const a of hit.view.assets) {
|
|
878
|
+
const list = pinsOf.get(a.path) ?? [];
|
|
879
|
+
list.push({ decision: d, state: a.state, sha256: a.sha256, actual: a.actual });
|
|
880
|
+
pinsOf.set(a.path, list);
|
|
881
|
+
}
|
|
882
|
+
const evidence = kind.pins ? hit.view.data?.[kind.pins.field] : undefined;
|
|
883
|
+
if (Array.isArray(evidence)) {
|
|
884
|
+
for (const e of evidence) {
|
|
885
|
+
if (e === null || typeof e !== "object" || Array.isArray(e))
|
|
886
|
+
continue;
|
|
887
|
+
const entry = e;
|
|
888
|
+
const hashed = typeof entry.sha256 === "string" && SHA256.test(entry.sha256);
|
|
889
|
+
if (isWorkspacePath(entry.path)) {
|
|
890
|
+
if (hashed)
|
|
891
|
+
continue;
|
|
892
|
+
const list = pinsOf.get(entry.path) ?? [];
|
|
893
|
+
list.push({ decision: d, state: "unpinned", sha256: null, actual: null });
|
|
894
|
+
pinsOf.set(entry.path, list);
|
|
895
|
+
find("intent-evidence-unpinned", `${d.record} names ${entry.path} as evidence with no sha256, so a later edit can't be told from what was decided on`, [d.id, `artifact:${entry.path}`]);
|
|
896
|
+
continue;
|
|
897
|
+
}
|
|
898
|
+
if (typeof entry.url !== "string")
|
|
899
|
+
continue;
|
|
900
|
+
const ev = add({
|
|
901
|
+
id: `evidence:${entry.url}`,
|
|
902
|
+
kind: "evidence",
|
|
903
|
+
ref: entry.url,
|
|
904
|
+
plugin: null,
|
|
905
|
+
data: { title: stringOr(entry.title), url: entry.url, path: null, as_of: stringOr(entry.as_of), sha256: hashed ? entry.sha256 : null },
|
|
906
|
+
});
|
|
907
|
+
edges.push({ kind: "cites-evidence", from: d.id, to: ev.id });
|
|
908
|
+
if (!hashed)
|
|
909
|
+
find("intent-evidence-unpinned", `${d.record} cites ${entry.url} with no hash, so what the page said when it was decided can't be checked`, [d.id, ev.id]);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
for (const [path, pins] of pinsOf) {
|
|
914
|
+
const aid = `artifact:${path}`;
|
|
915
|
+
const current = pins.filter((p) => p.decision.supersededBy === null && p.state !== "unpinned");
|
|
916
|
+
const worst = [...current].sort((a, b) => PIN_ORDER.indexOf(a.state) - PIN_ORDER.indexOf(b.state))[0];
|
|
917
|
+
add({
|
|
918
|
+
id: aid,
|
|
919
|
+
kind: "artifact",
|
|
920
|
+
path,
|
|
921
|
+
anchor: null,
|
|
922
|
+
pinnedSha256: worst?.sha256 ?? null,
|
|
923
|
+
currentSha256: pins.find((p) => p.state !== "unpinned")?.actual ?? null,
|
|
924
|
+
pinState: worst?.state ?? "unpinned",
|
|
925
|
+
});
|
|
926
|
+
for (const p of pins) {
|
|
927
|
+
edges.push({ kind: "pins", from: p.decision.id, to: aid, pinnedSha256: p.sha256, pinState: p.state });
|
|
928
|
+
if (p.state === "drifted")
|
|
929
|
+
find("intent-pin-drifted", `${path} changed after ${p.decision.record} pinned it: sha256 ${p.sha256?.slice(0, 12)} is pinned, the file hashes to ${p.actual?.slice(0, 12)}`, [p.decision.id, aid]);
|
|
930
|
+
if (p.state === "missing")
|
|
931
|
+
find("intent-pin-missing", `${p.decision.record} pins ${path}, which does not exist${located.tree.label}`, [p.decision.id, aid]);
|
|
932
|
+
// Stale against a current decision constraining the region (#2686): the
|
|
933
|
+
// record it supersedes pinned the same hash, and the file has not
|
|
934
|
+
// changed since. A decision in the graph only through supersession
|
|
935
|
+
// does not govern the region, so its stale pin is not raised here.
|
|
936
|
+
if (p.state === "stale" && p.decision.supersededBy === null && p.decision.constrains.length > 0) {
|
|
937
|
+
const old = pins.find((o) => o.decision.supersededBy === p.decision.record && o.decision.recordKind === p.decision.recordKind && o.sha256 === p.sha256);
|
|
938
|
+
find("intent-pin-stale", `${path} is pinned by ${p.decision.record} at sha256 ${p.sha256?.slice(0, 12)}, the hash ${old ? old.decision.record : "the record it supersedes"} pinned, and it has not changed since: the decision moved on and the artifact did not`, [p.decision.id, aid, ...(old ? [old.decision.id] : [])]);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
if (!worst && pins.some((p) => p.state !== "unpinned")) {
|
|
942
|
+
find("intent-artifact-unpinned", `${path} was pinned by ${[...new Set(pins.map((p) => p.decision.record))].join(", ")}, and no current decision pins it`, [aid, ...new Set(pins.map((p) => p.decision.id))]);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
// A path constraint that no longer resolves (#2650 A15).
|
|
946
|
+
for (const d of decisions) {
|
|
947
|
+
const hit = decisionById.get(`${d.recordKind}/${d.record}`);
|
|
948
|
+
const field = hit.kind.records.loaded.kind.constrains?.field;
|
|
949
|
+
const list = field ? hit.view.data?.[field] : undefined;
|
|
950
|
+
if (!Array.isArray(list))
|
|
951
|
+
continue;
|
|
952
|
+
const kindPrefix = hit.kind.records.workspaceRoot === "." ? "" : hit.kind.records.workspaceRoot;
|
|
953
|
+
for (const entry of list) {
|
|
954
|
+
if (typeof entry !== "string" || !entry.startsWith("path:"))
|
|
955
|
+
continue;
|
|
956
|
+
const p = entry.slice("path:".length);
|
|
957
|
+
if (!isWorkspacePath(p))
|
|
958
|
+
continue;
|
|
959
|
+
const full = joinPath(kindPrefix, p);
|
|
960
|
+
const inTree = workspacePrefix === "" ? full : full.startsWith(`${workspacePrefix}/`) ? full.slice(workspacePrefix.length + 1) : undefined;
|
|
961
|
+
if (inTree !== undefined && located.tree.stat(inTree) === undefined) {
|
|
962
|
+
find("intent-constraint-lost", `${d.record} constrains ${p}, which does not exist${located.tree.label}`, [d.id]);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
// Which decisions covered the region at each commit's time: from the commit
|
|
967
|
+
// that decided the record, or added it when it is not approved, until the
|
|
968
|
+
// commit that opened the window of the record superseding it.
|
|
969
|
+
const windows = new Map();
|
|
970
|
+
if (rev) {
|
|
971
|
+
for (const c of covering) {
|
|
972
|
+
const opened = windowOpening(top, rev, c.view);
|
|
973
|
+
const successor = c.view.supersededBy ? decisionById.get(`${c.node.recordKind}/${c.view.supersededBy}`) : undefined;
|
|
974
|
+
const replaced = successor ? windowOpening(top, rev, successor.view) : null;
|
|
975
|
+
windows.set(c.node.id, { from: opened ? descendants(top, opened, rev) : new Set(), until: replaced ? descendants(top, replaced, rev) : null });
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
const workKinds = kinds.filter((k) => k.records?.loaded.kind.work);
|
|
979
|
+
const readsWork = workKinds.length > 0;
|
|
980
|
+
const workAll = [];
|
|
981
|
+
const workByKey = new Map();
|
|
982
|
+
for (const k of workKinds) {
|
|
983
|
+
for (const v of k.records.views) {
|
|
984
|
+
if (v.id === null)
|
|
985
|
+
continue;
|
|
986
|
+
const hit = { view: v, kind: k };
|
|
987
|
+
workAll.push(hit);
|
|
988
|
+
const key = `${k.records.loaded.kind.name}/${v.id}`;
|
|
989
|
+
if (!workByKey.has(key))
|
|
990
|
+
workByKey.set(key, hit);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
const workSpec = (h) => h.kind.records.loaded.kind.work;
|
|
994
|
+
const workNode = (h) => {
|
|
995
|
+
if (h.node)
|
|
996
|
+
return h.node;
|
|
997
|
+
const kind = h.kind.records.loaded.kind;
|
|
998
|
+
const v = h.view;
|
|
999
|
+
h.node = add({
|
|
1000
|
+
id: `record:${kind.name}/${v.id}`,
|
|
1001
|
+
kind: "work",
|
|
1002
|
+
recordKind: kind.name,
|
|
1003
|
+
record: v.id,
|
|
1004
|
+
path: v.path,
|
|
1005
|
+
title: stringOr(v.data?.title),
|
|
1006
|
+
state: v.state,
|
|
1007
|
+
closed: v.state !== null && (kind.closedStates ?? []).includes(v.state),
|
|
1008
|
+
valid: v.valid,
|
|
1009
|
+
reasons: v.reasons,
|
|
1010
|
+
provenance: { level: v.provenance.level, commit: v.provenance.commit, reason: v.provenance.reason },
|
|
1011
|
+
owner: stringOr(v.data?.owner),
|
|
1012
|
+
ready: v.ready ?? false,
|
|
1013
|
+
blockedBy: v.blockedBy ?? [],
|
|
1014
|
+
implements: v.implements ?? [],
|
|
1015
|
+
needs: idList(v.data, kind.work.needs),
|
|
1016
|
+
source: gapSource(v.data),
|
|
1017
|
+
supersededBy: v.supersededBy,
|
|
1018
|
+
constrains: [],
|
|
1019
|
+
warnings: v.warnings.filter((w) => WORK_WARNING_CODES.includes(w.code)),
|
|
1020
|
+
});
|
|
1021
|
+
return h.node;
|
|
1022
|
+
};
|
|
1023
|
+
/** The decision kind a work kind names, when it is among the kinds read. */
|
|
1024
|
+
const decisionKindOf = (k) => {
|
|
1025
|
+
const file = realpathOr(resolve(dirname(k.records.loaded.file), k.records.loaded.kind.work.decisions));
|
|
1026
|
+
return kinds.find((x) => x.records && !x.records.loaded.kind.work && realpathOr(x.records.loaded.file) === file);
|
|
1027
|
+
};
|
|
1028
|
+
/** The node ids of the decisions a work item implements, among the decisions read. */
|
|
1029
|
+
const implementedIds = (h) => {
|
|
1030
|
+
const dk = decisionKindOf(h.kind);
|
|
1031
|
+
if (!dk)
|
|
1032
|
+
return [];
|
|
1033
|
+
return idList(h.view.data, workSpec(h).implements)
|
|
1034
|
+
.map((d) => decisionById.get(`${dk.records.loaded.kind.name}/${d}`))
|
|
1035
|
+
.filter((x) => x !== undefined)
|
|
1036
|
+
.map((x) => decisionId(x.kind, x.view.id));
|
|
1037
|
+
};
|
|
1038
|
+
/** The work items implementing a decision, leaving out dropped ones: a closed state other than done. */
|
|
1039
|
+
const implementersOf = (d) => workAll.filter((h) => {
|
|
1040
|
+
const kind = h.kind.records.loaded.kind;
|
|
1041
|
+
const dropped = h.view.state !== null && h.view.state !== kind.work.done && (kind.closedStates ?? []).includes(h.view.state);
|
|
1042
|
+
return !dropped && implementedIds(h).includes(d.id);
|
|
1043
|
+
});
|
|
1044
|
+
const workCovering = [];
|
|
1045
|
+
for (const h of workAll) {
|
|
1046
|
+
const matched = matchConstrains(h.kind, h.view);
|
|
1047
|
+
if (matched.length === 0)
|
|
1048
|
+
continue;
|
|
1049
|
+
const node = workNode(h);
|
|
1050
|
+
for (const m of matched) {
|
|
1051
|
+
edges.push({ kind: "constrains", from: node.id, to: m.to, granularity: m.granularity, entry: m.entry });
|
|
1052
|
+
if (m.to === rid || m.granularity === "contract")
|
|
1053
|
+
node.constrains.push({ entry: m.entry, granularity: m.granularity });
|
|
1054
|
+
}
|
|
1055
|
+
if (node.constrains.length > 0)
|
|
1056
|
+
workCovering.push(h);
|
|
1057
|
+
}
|
|
1058
|
+
const workWindows = new Map();
|
|
1059
|
+
if (rev) {
|
|
1060
|
+
for (const h of workCovering) {
|
|
1061
|
+
const kind = h.kind.records.loaded.kind;
|
|
1062
|
+
const added = addingCommit(top, rev, h.view.path);
|
|
1063
|
+
const closing = kind.stateField ? closingCommit(top, rev, h.view.path, kind.stateField, kind.closedStates ?? []) : null;
|
|
1064
|
+
const until = closing ? descendants(top, closing, rev) : null;
|
|
1065
|
+
if (closing)
|
|
1066
|
+
until.delete(closing);
|
|
1067
|
+
workWindows.set(h.node.id, { from: added ? descendants(top, added, rev) : new Set(), until });
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
const inWorkWindow = (h, sha) => {
|
|
1071
|
+
const w = workWindows.get(h.node.id);
|
|
1072
|
+
return !!w && w.from.has(sha) && !w.until?.has(sha);
|
|
1073
|
+
};
|
|
1074
|
+
// The records chant's own trailers name (#3149): a Chant-Record of a kind
|
|
1075
|
+
// read, and the work item a Chant-Lease was taken on. The commit carries
|
|
1076
|
+
// each, and is the own work of a decision it names or that a work item it
|
|
1077
|
+
// names implements.
|
|
1078
|
+
const recordKinds = new Map(kinds.filter((k) => k.records).map((k) => [k.records.loaded.kind.name, k]));
|
|
1079
|
+
/** The work item a run worked on and the records it names, among the kinds read, deduplicated. */
|
|
1080
|
+
const runTargets = (r) => {
|
|
1081
|
+
const out = [];
|
|
1082
|
+
const push = (x) => {
|
|
1083
|
+
if (!out.some((o) => o.node === x.node))
|
|
1084
|
+
out.push(x);
|
|
1085
|
+
};
|
|
1086
|
+
if (r.unit) {
|
|
1087
|
+
const named = r.unit.kind ? workKinds.filter((k) => k.records.loaded.kind.name === r.unit.kind) : workKinds;
|
|
1088
|
+
const hits = named.map((k) => workByKey.get(`${k.records.loaded.kind.name}/${r.unit.id}`)).filter((h) => h !== undefined);
|
|
1089
|
+
if (hits.length === 1)
|
|
1090
|
+
push({ node: workNode(hits[0]).id, work: hits[0] });
|
|
1091
|
+
}
|
|
1092
|
+
for (const ref of r.records) {
|
|
1093
|
+
const k = recordKinds.get(ref.kind);
|
|
1094
|
+
if (!k)
|
|
1095
|
+
continue;
|
|
1096
|
+
if (k.records.loaded.kind.work) {
|
|
1097
|
+
const h = workByKey.get(`${ref.kind}/${ref.id}`);
|
|
1098
|
+
if (h)
|
|
1099
|
+
push({ node: workNode(h).id, work: h });
|
|
1100
|
+
continue;
|
|
1101
|
+
}
|
|
1102
|
+
const hit = decisionById.get(`${ref.kind}/${ref.id}`);
|
|
1103
|
+
if (hit)
|
|
1104
|
+
push({ node: decisionNode(hit.kind, hit.view).id, decision: `${ref.kind}/${ref.id}` });
|
|
1105
|
+
}
|
|
1106
|
+
return out;
|
|
1107
|
+
};
|
|
1108
|
+
for (const t of touched) {
|
|
1109
|
+
const c = nodes.get(`commit:${t.sha}`);
|
|
1110
|
+
const j = joined.get(t.sha);
|
|
1111
|
+
if (!c || !j)
|
|
1112
|
+
continue;
|
|
1113
|
+
const carry = (to) => {
|
|
1114
|
+
if (j.carries.includes(to))
|
|
1115
|
+
return;
|
|
1116
|
+
j.carries.push(to);
|
|
1117
|
+
edges.push({ kind: "carries", from: c.id, to });
|
|
1118
|
+
};
|
|
1119
|
+
const carryWork = (h) => {
|
|
1120
|
+
const node = workNode(h);
|
|
1121
|
+
carry(node.id);
|
|
1122
|
+
j.decisions.push(...implementedIds(h).map((id) => id.slice("record:".length)));
|
|
1123
|
+
return node.id;
|
|
1124
|
+
};
|
|
1125
|
+
// The commit's own trailers, and a followed squash's original commits' (#3035).
|
|
1126
|
+
for (const joins of [c.joins, ...(c.squash?.commits ?? []).map((o) => o.joins)]) {
|
|
1127
|
+
for (const r of joins.records) {
|
|
1128
|
+
const k = recordKinds.get(r.kind);
|
|
1129
|
+
if (!k)
|
|
1130
|
+
continue;
|
|
1131
|
+
if (k.records.loaded.kind.work) {
|
|
1132
|
+
const h = workByKey.get(`${r.kind}/${r.id}`);
|
|
1133
|
+
if (h)
|
|
1134
|
+
r.node = carryWork(h);
|
|
1135
|
+
continue;
|
|
1136
|
+
}
|
|
1137
|
+
const hit = decisionById.get(`${r.kind}/${r.id}`);
|
|
1138
|
+
if (!hit)
|
|
1139
|
+
continue;
|
|
1140
|
+
r.node = decisionNode(hit.kind, hit.view).id;
|
|
1141
|
+
carry(r.node);
|
|
1142
|
+
j.decisions.push(`${r.kind}/${r.id}`);
|
|
1143
|
+
}
|
|
1144
|
+
const item = joins.lease?.item;
|
|
1145
|
+
if (item) {
|
|
1146
|
+
const hits = workKinds.map((k) => workByKey.get(`${k.records.loaded.kind.name}/${item}`)).filter((h) => h !== undefined);
|
|
1147
|
+
if (hits.length === 1)
|
|
1148
|
+
carryWork(hits[0]);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
// The runs that made the commit (#3034): its run's work item and the
|
|
1152
|
+
// records the run names are carried as a Chant-Lease and a Chant-Record are.
|
|
1153
|
+
for (const ref of runJoins.refs.get(t.sha) ?? []) {
|
|
1154
|
+
const r = runJoins.runs.get(ref.id);
|
|
1155
|
+
if (!r)
|
|
1156
|
+
continue;
|
|
1157
|
+
for (const target of runTargets(r)) {
|
|
1158
|
+
if (target.work)
|
|
1159
|
+
carryWork(target.work);
|
|
1160
|
+
else if (target.decision) {
|
|
1161
|
+
carry(target.node);
|
|
1162
|
+
j.decisions.push(target.decision);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
// Each recorded run in the walk to the work item and records it worked on (#3034).
|
|
1168
|
+
for (const n of [...nodes.values()]) {
|
|
1169
|
+
if (n.kind !== "run")
|
|
1170
|
+
continue;
|
|
1171
|
+
const r = runJoins.runs.get(n.run);
|
|
1172
|
+
if (!r)
|
|
1173
|
+
continue;
|
|
1174
|
+
for (const target of runTargets(r))
|
|
1175
|
+
edges.push({ kind: "worked-on", from: n.id, to: target.node });
|
|
1176
|
+
}
|
|
1177
|
+
// Each work item in the graph, those its commits and runs carry included, to the decisions it implements and the items it needs.
|
|
1178
|
+
for (const h of workAll.filter((x) => x.node)) {
|
|
1179
|
+
for (const id of implementedIds(h)) {
|
|
1180
|
+
const hit = decisionById.get(id.slice("record:".length));
|
|
1181
|
+
edges.push({ kind: "implements", from: h.node.id, to: decisionNode(hit.kind, hit.view).id });
|
|
1182
|
+
}
|
|
1183
|
+
for (const n of idList(h.view.data, workSpec(h).needs)) {
|
|
1184
|
+
const hit = workByKey.get(`${h.kind.records.loaded.kind.name}/${n}`);
|
|
1185
|
+
if (hit)
|
|
1186
|
+
edges.push({ kind: "needs", from: h.node.id, to: workNode(hit).id });
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
const coveredAt = (sha, granularities) => covering.filter((c) => {
|
|
1190
|
+
if (!c.node.constrains.some((x) => granularities.includes(x.granularity)))
|
|
1191
|
+
return false;
|
|
1192
|
+
const w = windows.get(c.node.id);
|
|
1193
|
+
return !!w && w.from.has(sha) && !w.until?.has(sha);
|
|
1194
|
+
});
|
|
1195
|
+
const readsRecords = kinds.some((k) => k.records);
|
|
1196
|
+
// A decision's own work: a commit whose unit, or that unit's contract, names
|
|
1197
|
+
// the decision, or whose unit serves a contract the decision constrains; or
|
|
1198
|
+
// a commit whose own trailers name the decision, or a work item implementing it.
|
|
1199
|
+
const ownWork = (j, d) => (!!j.unit || j.carries.length > 0) && (j.decisions.some((x) => x === d.record || x === `${d.recordKind}/${d.record}`) || d.constrains.some((x) => x.granularity === "contract" && j.contracts.includes(x.entry)));
|
|
1200
|
+
for (const t of touched) {
|
|
1201
|
+
const c = nodes.get(`commit:${t.sha}`);
|
|
1202
|
+
if (!c)
|
|
1203
|
+
continue;
|
|
1204
|
+
const j = joined.get(t.sha);
|
|
1205
|
+
if (readsRecords) {
|
|
1206
|
+
const inWindow = coveredAt(t.sha, ["path"]);
|
|
1207
|
+
for (const w of inWindow)
|
|
1208
|
+
edges.push({ kind: "within", from: c.id, to: w.node.id, state: ownWork(j, w.node) ? "decided" : "decided-by-window" });
|
|
1209
|
+
c.state = inWindow.length === 0 ? "undecided" : inWindow.some((w) => ownWork(j, w.node)) ? "decided" : "decided-by-window";
|
|
1210
|
+
}
|
|
1211
|
+
// A commit in a work item's window is worked; its own state still comes from decisions (#2683).
|
|
1212
|
+
for (const h of workCovering) {
|
|
1213
|
+
if (h.node.constrains.some((x) => x.granularity === "path") && inWorkWindow(h, t.sha))
|
|
1214
|
+
edges.push({ kind: "within", from: c.id, to: h.node.id, state: "worked" });
|
|
1215
|
+
}
|
|
1216
|
+
if (readsRecords && c.state === "undecided") {
|
|
1217
|
+
find("intent-commit-undecided", `${t.sha.slice(0, 8)} changed the region when no decision constrained ${region.path} by path`, [c.id, rid]);
|
|
1218
|
+
}
|
|
1219
|
+
if (readsRecords && !j.unit && j.carries.length === 0 && c.pullRequest === null && coveredAt(t.sha, ["path", "member", "contract", "issue"]).length === 0) {
|
|
1220
|
+
find("intent-commit-bare", `${t.sha.slice(0, 8)} names no unit, no record, no pull request and no decision`, [c.id]);
|
|
1221
|
+
}
|
|
1222
|
+
const claims = [...new Set(j.authorship)].filter((key) => hasTrailer(c.trailers, key));
|
|
1223
|
+
if (claims.length > 0 && c.signature.level !== "attested") {
|
|
1224
|
+
find("intent-trailer-unverified", `${t.sha.slice(0, 8)} carries ${claims.join(", ")}, and the commit is ${c.signature.level}, so nothing vouches for the trailer`, [c.id]);
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
// Findings about the region as a whole.
|
|
1228
|
+
if (readsRecords) {
|
|
1229
|
+
const current = covering.filter((c) => c.node.supersededBy === null);
|
|
1230
|
+
if (covering.length === 0) {
|
|
1231
|
+
find("intent-region-unconstrained", `no decision constrains ${region.path}`, [rid]);
|
|
1232
|
+
}
|
|
1233
|
+
else if (current.length === 0) {
|
|
1234
|
+
find("intent-decision-superseded-live", `every decision constraining ${region.path} is superseded: ${covering.map((c) => `${c.node.record} by ${c.node.supersededBy}`).join(", ")}`, [rid, ...covering.map((c) => c.node.id)]);
|
|
1235
|
+
}
|
|
1236
|
+
else if (current.every((c) => !c.node.closed)) {
|
|
1237
|
+
find("intent-decision-provisional", `the decisions constraining ${region.path} are ${[...new Set(current.map((c) => c.node.state ?? "stateless"))].join(" or ")}, and none is in a closed state`, [rid, ...current.map((c) => c.node.id)]);
|
|
1238
|
+
}
|
|
1239
|
+
// A current decision with a dissent nobody addressed or withdrew (#2686), from its quorum.
|
|
1240
|
+
for (const c of current) {
|
|
1241
|
+
const open = c.view.quorum?.openConcerns ?? [];
|
|
1242
|
+
if (open.length === 0)
|
|
1243
|
+
continue;
|
|
1244
|
+
const principals = [...new Set(open.map((o) => o.principal))];
|
|
1245
|
+
findings.push({
|
|
1246
|
+
id: `finding:intent-decision-contested:${findings.filter((f) => f.code === "intent-decision-contested").length + 1}`,
|
|
1247
|
+
kind: "finding",
|
|
1248
|
+
code: "intent-decision-contested",
|
|
1249
|
+
message: `${c.node.record} constrains ${region.path} and has ${open.length} open ${open.length === 1 ? "concern" : "concerns"}, from ${open.map((o) => o.reviewer).join(", ")}: a dissent neither addressed nor withdrawn`,
|
|
1250
|
+
concerns: [c.node.id, rid],
|
|
1251
|
+
openConcerns: { count: open.length, principals },
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
const granularities = new Set(covering.flatMap((c) => c.node.constrains.map((x) => x.granularity)));
|
|
1255
|
+
if (covering.length > 0 && !granularities.has("path") && granularities.has("member")) {
|
|
1256
|
+
find("intent-constraint-coarse", `${region.path} is constrained only through its member, ${member}`, [rid, ...covering.map((c) => c.node.id)]);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
// Findings about work items (#2683).
|
|
1260
|
+
if (readsWork) {
|
|
1261
|
+
for (const c of covering) {
|
|
1262
|
+
if (c.node.supersededBy !== null || !isDecided(c.kind.records.loaded.kind, c.node.state))
|
|
1263
|
+
continue;
|
|
1264
|
+
if (implementersOf(c.node).length > 0)
|
|
1265
|
+
continue;
|
|
1266
|
+
const w = windows.get(c.node.id);
|
|
1267
|
+
if (w && touched.some((t) => w.from.has(t.sha) && !w.until?.has(t.sha)))
|
|
1268
|
+
continue;
|
|
1269
|
+
find("intent-decision-unimplemented", `${c.node.record} is ${c.node.state} and constrains ${region.path}, and no work item that is not dropped implements it, and no commit changed the region in its window`, [c.node.id, rid]);
|
|
1270
|
+
}
|
|
1271
|
+
for (const h of workCovering) {
|
|
1272
|
+
const node = h.node;
|
|
1273
|
+
if (node.blockedBy.length === 0)
|
|
1274
|
+
continue;
|
|
1275
|
+
const worked = touched.filter((t) => inWorkWindow(h, t.sha));
|
|
1276
|
+
if (worked.length === 0)
|
|
1277
|
+
continue;
|
|
1278
|
+
const waiting = node.blockedBy.map((b) => `${b.id} (${b.state ?? "unknown"})`).join(", ");
|
|
1279
|
+
const blockers = node.blockedBy.map((b) => workByKey.get(`${node.recordKind}/${b.id}`)).filter((x) => x !== undefined).map((x) => workNode(x).id);
|
|
1280
|
+
find("intent-work-blocked", `${node.record} has ${worked.length} ${worked.length === 1 ? "commit" : "commits"} in its window while it waits on ${waiting}`, [node.id, ...worked.map((t) => `commit:${t.sha}`), ...blockers]);
|
|
1281
|
+
}
|
|
1282
|
+
for (const d of [...nodes.values()].filter((n) => n.kind === "decision")) {
|
|
1283
|
+
const own = edges.filter((e) => e.kind === "within" && e.to === d.id && e.state === "decided").map((e) => e.from);
|
|
1284
|
+
if (own.length === 0)
|
|
1285
|
+
continue;
|
|
1286
|
+
const impl = implementersOf(d);
|
|
1287
|
+
if (impl.length === 0 || impl.some((h) => h.view.state === workSpec(h).done))
|
|
1288
|
+
continue;
|
|
1289
|
+
find("intent-work-open-decided-code", `${own.length} ${own.length === 1 ? "commit" : "commits"} in ${region.path} ${own.length === 1 ? "is" : "are"} ${d.record}'s own work, and ${impl.map((h) => `${h.view.id} is ${h.view.state ?? "stateless"}`).join(", ")}: the code says done and the queue says not`, [d.id, ...own, ...impl.map((h) => workNode(h).id)]);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
// 5. Links: the declared member links that touch the region's member, read in source.
|
|
1293
|
+
if (member) {
|
|
1294
|
+
let rows = [];
|
|
1295
|
+
try {
|
|
1296
|
+
const groups = resolveGroups(declaration, located.tree);
|
|
1297
|
+
const kindsRegistry = loadKindRegistry(declaration.pins, located.rootOnDisk).registry;
|
|
1298
|
+
rows = resolveLinks(declaration, sourceMemberHandles(declaration, located.tree, groups, kindsRegistry)).filter((r) => r.origin === "declared");
|
|
1299
|
+
}
|
|
1300
|
+
catch (err) {
|
|
1301
|
+
if (!(err instanceof WorkspaceReadError))
|
|
1302
|
+
throw err;
|
|
1303
|
+
}
|
|
1304
|
+
rows.forEach((row, i) => {
|
|
1305
|
+
const producer = "producer" in row ? row.producer : null;
|
|
1306
|
+
if (row.consumer !== member && producer !== member)
|
|
1307
|
+
return;
|
|
1308
|
+
const { pointer: _pointer, ...clean } = row;
|
|
1309
|
+
add({ id: `link:${i + 1}`, kind: "link", row: clean });
|
|
1310
|
+
memberNode(row.consumer);
|
|
1311
|
+
if (producer && declaration.members.some((m) => m.name === producer)) {
|
|
1312
|
+
memberNode(producer);
|
|
1313
|
+
edges.push({ kind: "links", from: `member:${row.consumer}`, to: `member:${producer}` });
|
|
1314
|
+
}
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
// Findings last, in the order of the code list, so the same walk always prints the same way.
|
|
1318
|
+
findings.sort((x, y) => INTENT_FINDING_CODES.indexOf(x.code) - INTENT_FINDING_CODES.indexOf(y.code));
|
|
1319
|
+
// Then the plugins' findings, in commit order, each about its commit and the nodes its refs name.
|
|
1320
|
+
const resolveRef = (ref) => {
|
|
1321
|
+
if (nodes.has(ref))
|
|
1322
|
+
return ref;
|
|
1323
|
+
for (const prefix of ["commit", "unit", "contract", "evidence", "artifact", "file", "member"])
|
|
1324
|
+
if (nodes.has(`${prefix}:${ref}`))
|
|
1325
|
+
return `${prefix}:${ref}`;
|
|
1326
|
+
for (const n of nodes.values()) {
|
|
1327
|
+
if (n.kind === "decision" && (ref === n.record || ref === `${n.recordKind}/${n.record}`))
|
|
1328
|
+
return n.id;
|
|
1329
|
+
if (n.kind === "commit" && /^[0-9a-f]{7,}$/.test(ref) && n.sha.startsWith(ref))
|
|
1330
|
+
return n.id;
|
|
1331
|
+
}
|
|
1332
|
+
return undefined;
|
|
1333
|
+
};
|
|
1334
|
+
for (const { commit, plugin, finding } of pluginFindings) {
|
|
1335
|
+
const code = finding.code;
|
|
1336
|
+
const refs = finding.refs ?? [];
|
|
1337
|
+
const concerns = [...new Set([commit, ...refs.map(resolveRef).filter((x) => x !== undefined)])];
|
|
1338
|
+
findings.push({ id: `finding:${code}:${findings.filter((f) => f.code === code).length + 1}`, kind: "finding", code, message: finding.message, concerns, plugin, refs });
|
|
1339
|
+
}
|
|
1340
|
+
// A finding a work item addresses (#2683): the item came from that gap on
|
|
1341
|
+
// this region, or it implements the decision a drifted, missing or stale pin, or a
|
|
1342
|
+
// plugin's finding, is about. A done item whose gap still fires here gets
|
|
1343
|
+
// work-done-gap-open.
|
|
1344
|
+
if (readsWork) {
|
|
1345
|
+
// Implementing a decision closes the gaps between it and the code or its
|
|
1346
|
+
// artifacts, not gaps in the decision record itself (its review, its
|
|
1347
|
+
// granularity, its evidence), so only these findings, and a plugin's,
|
|
1348
|
+
// count as addressed through implements.
|
|
1349
|
+
const CLOSED_BY_IMPLEMENTING = new Set(["intent-pin-drifted", "intent-pin-missing", "intent-pin-stale"]);
|
|
1350
|
+
const fromGap = (h, code) => {
|
|
1351
|
+
const src = gapSource(h.view.data);
|
|
1352
|
+
if (src === null || src.finding !== code)
|
|
1353
|
+
return false;
|
|
1354
|
+
const parsed = parseRegion(src.region);
|
|
1355
|
+
if ("error" in parsed)
|
|
1356
|
+
return false;
|
|
1357
|
+
const kindPrefix = h.kind.records.workspaceRoot === "." ? "" : h.kind.records.workspaceRoot;
|
|
1358
|
+
const full = joinPath(kindPrefix, parsed.path);
|
|
1359
|
+
const contains = (a, b) => a === b || a === "" || b.startsWith(`${a}/`);
|
|
1360
|
+
if (!contains(full, regionFull) && !contains(regionFull, full))
|
|
1361
|
+
return false;
|
|
1362
|
+
if (full === regionFull && parsed.lines && region.lines)
|
|
1363
|
+
return parsed.lines.start <= region.lines.end && region.lines.start <= parsed.lines.end;
|
|
1364
|
+
return true;
|
|
1365
|
+
};
|
|
1366
|
+
for (const f of findings) {
|
|
1367
|
+
const by = [];
|
|
1368
|
+
for (const h of workAll) {
|
|
1369
|
+
const gap = fromGap(h, f.code);
|
|
1370
|
+
const viaDecision = (CLOSED_BY_IMPLEMENTING.has(f.code) || isPluginCode(f.code)) && implementedIds(h).some((id) => f.concerns.includes(id));
|
|
1371
|
+
if (gap || viaDecision)
|
|
1372
|
+
by.push(h);
|
|
1373
|
+
if (gap && h.view.state === workSpec(h).done) {
|
|
1374
|
+
const node = workNode(h);
|
|
1375
|
+
if (!node.warnings.some((w) => w.code === "work-done-gap-open")) {
|
|
1376
|
+
node.warnings.push({ code: "work-done-gap-open", message: `${node.record} is ${h.view.state}, and ${f.code}, the gap it came from, still fires on ${region.path}` });
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
f.addressed = by.length > 0;
|
|
1381
|
+
f.addressedBy = by.map((h) => ({ id: h.view.id, state: h.view.state }));
|
|
1382
|
+
for (const h of by)
|
|
1383
|
+
edges.push({ kind: "addressed-by", from: f.id, to: workNode(h).id });
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
// Reviews that led to a work item in the graph (#3154, E21): a session's comments[].follow_ups name it.
|
|
1387
|
+
const workNodes = [...nodes.values()].filter((n) => n.kind === "work");
|
|
1388
|
+
if (workNodes.length > 0) {
|
|
1389
|
+
const byRef = new Map(workNodes.map((n) => [`${n.recordKind}:${n.record}`, n]));
|
|
1390
|
+
for (const k of kinds) {
|
|
1391
|
+
const loaded = k.records?.loaded.kind;
|
|
1392
|
+
if (!loaded?.session)
|
|
1393
|
+
continue;
|
|
1394
|
+
for (const v of k.records.views) {
|
|
1395
|
+
if (v.id === null || !v.data)
|
|
1396
|
+
continue;
|
|
1397
|
+
const hits = new Map();
|
|
1398
|
+
const note = (refs, comment) => {
|
|
1399
|
+
if (!Array.isArray(refs))
|
|
1400
|
+
return;
|
|
1401
|
+
for (const ref of refs) {
|
|
1402
|
+
const n = typeof ref === "string" ? byRef.get(ref) : undefined;
|
|
1403
|
+
if (!n)
|
|
1404
|
+
continue;
|
|
1405
|
+
const list = hits.get(n) ?? [];
|
|
1406
|
+
if (comment !== null && !list.includes(comment))
|
|
1407
|
+
list.push(comment);
|
|
1408
|
+
hits.set(n, list);
|
|
1409
|
+
}
|
|
1410
|
+
};
|
|
1411
|
+
note(v.data.follow_ups, null);
|
|
1412
|
+
const comments = Array.isArray(v.data.comments) ? v.data.comments : [];
|
|
1413
|
+
comments.forEach((c, i) => note(c?.follow_ups, i + 1));
|
|
1414
|
+
for (const [n, list] of hits) {
|
|
1415
|
+
(n.reviews ?? (n.reviews = [])).push({ id: `record:${loaded.name}/${v.id}`, recordKind: loaded.name, record: v.id, path: v.path, state: v.state, comments: list.sort((a, b) => a - b) });
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
for (const f of findings)
|
|
1421
|
+
nodes.set(f.id, f);
|
|
1422
|
+
const why = explain();
|
|
1423
|
+
const all = [...nodes.values()];
|
|
1424
|
+
return {
|
|
1425
|
+
doc: {
|
|
1426
|
+
...head,
|
|
1427
|
+
at: located.at,
|
|
1428
|
+
workspace: { name: declaration.name, root: located.root },
|
|
1429
|
+
region: rid,
|
|
1430
|
+
history: { rev, follows: region.lines ? "line-range" : type === "file" ? "file" : "directory", shallow },
|
|
1431
|
+
kinds: kinds.map((k) => ({ file: k.display, name: k.name, records: k.records?.loaded.kind.name ?? null, joins: k.joins?.form ?? null })),
|
|
1432
|
+
nodes: all,
|
|
1433
|
+
edges,
|
|
1434
|
+
reasons,
|
|
1435
|
+
summary: {
|
|
1436
|
+
commits: all.filter((n) => n.kind === "commit").length,
|
|
1437
|
+
decisions: all.filter((n) => n.kind === "decision").length,
|
|
1438
|
+
artifacts: all.filter((n) => n.kind === "artifact").length,
|
|
1439
|
+
findings: findings.length,
|
|
1440
|
+
},
|
|
1441
|
+
why,
|
|
1442
|
+
},
|
|
1443
|
+
failed: reasons.some((r) => r.code === "intent-plugin-failed"),
|
|
1444
|
+
};
|
|
1445
|
+
/** "Why is it like this" over the region (#3034): who made its current lines, and the decisions governing it, most relevant first. */
|
|
1446
|
+
function explain() {
|
|
1447
|
+
const hunksOf = (run, sha) => runJoins.runs.get(run)?.commits.find((c) => c.sha === sha)?.hunks ?? null;
|
|
1448
|
+
const joinOf = (run, sha) => (runJoins.refs.get(sha) ?? []).find((r) => r.id === run)?.joinedBy ?? [];
|
|
1449
|
+
const joinsOf = (runs, shas) => {
|
|
1450
|
+
const all = new Set(runs.flatMap((id) => shas.flatMap((sha) => joinOf(id, sha))));
|
|
1451
|
+
return RUN_JOIN_ORDER.filter((j) => all.has(j));
|
|
1452
|
+
};
|
|
1453
|
+
// For a followed squash (#3035), the original commit behind each of its lines, by blame at the pull request's head.
|
|
1454
|
+
const origins = new Map();
|
|
1455
|
+
const originOf = (sha, path, line) => {
|
|
1456
|
+
const f = follows.get(sha);
|
|
1457
|
+
if (!f?.followed || !f.head)
|
|
1458
|
+
return { squash: false, origin: undefined };
|
|
1459
|
+
const key = `${sha}\0${path}`;
|
|
1460
|
+
if (!origins.has(key))
|
|
1461
|
+
origins.set(key, squashLineOrigins(top, sha, f.head, path, new Set(f.commits)));
|
|
1462
|
+
return { squash: true, origin: origins.get(key)?.get(line) };
|
|
1463
|
+
};
|
|
1464
|
+
// Each current line: its commit, and the runs that wrote it.
|
|
1465
|
+
const who = blamed.map((b) => {
|
|
1466
|
+
if (b.sha === null)
|
|
1467
|
+
return { line: b.line, sha: null, runs: [], narrowedBy: null, via: undefined };
|
|
1468
|
+
const sha = b.sha;
|
|
1469
|
+
const { squash, origin } = originOf(sha, b.origPath, b.origLine);
|
|
1470
|
+
const via = squash ? (origin ? [origin] : []) : undefined;
|
|
1471
|
+
// On a squash line blame at the head traced to one original, a run joined only through the other originals didn't write it.
|
|
1472
|
+
const refs = (runJoins.refs.get(sha) ?? []).filter((r) => !origin || r.joinedBy.some((j) => j !== "squash") || (r.via ?? []).includes(origin)).map((r) => r.id);
|
|
1473
|
+
const withHunks = refs.filter((id) => hunksOf(id, sha) !== null);
|
|
1474
|
+
if (withHunks.length === 0)
|
|
1475
|
+
return { line: b.line, sha, runs: refs, narrowedBy: null, via };
|
|
1476
|
+
const wrote = withHunks.filter((id) => hunksOf(id, sha).some((h) => h.path === b.origPath && h.start <= b.origLine && b.origLine <= h.end));
|
|
1477
|
+
// A run that recorded its hunks and doesn't cover the line didn't write it; a run that recorded none may have.
|
|
1478
|
+
return { line: b.line, sha, runs: wrote.length > 0 ? wrote : refs.filter((id) => !withHunks.includes(id)), narrowedBy: "hunks", via };
|
|
1479
|
+
});
|
|
1480
|
+
const spans = [];
|
|
1481
|
+
for (const w of who) {
|
|
1482
|
+
const last = spans[spans.length - 1];
|
|
1483
|
+
const via = w.via;
|
|
1484
|
+
if (last && last.end === w.line - 1 && last.sha === w.sha && last.narrowedBy === w.narrowedBy && last.runs.join("\0") === w.runs.map((r) => `run:${r}`).join("\0") && last.via?.join("\0") === via?.join("\0")) {
|
|
1485
|
+
last.end = w.line;
|
|
1486
|
+
continue;
|
|
1487
|
+
}
|
|
1488
|
+
spans.push({ start: w.line, end: w.line, commit: w.sha ? `commit:${w.sha}` : null, sha: w.sha, runs: w.runs.map((r) => `run:${r}`), narrowedBy: w.narrowedBy, joinedBy: w.sha ? joinsOf(w.runs, [w.sha]) : [], ...(via ? { via } : {}) });
|
|
1489
|
+
}
|
|
1490
|
+
const ranges = (lines) => {
|
|
1491
|
+
const out = [];
|
|
1492
|
+
for (const l of lines) {
|
|
1493
|
+
const last = out[out.length - 1];
|
|
1494
|
+
if (last && last.end === l - 1)
|
|
1495
|
+
last.end = l;
|
|
1496
|
+
else
|
|
1497
|
+
out.push({ start: l, end: l });
|
|
1498
|
+
}
|
|
1499
|
+
return out;
|
|
1500
|
+
};
|
|
1501
|
+
// The decisions, most relevant first.
|
|
1502
|
+
const decisionNodes = [...nodes.values()].filter((n) => n.kind === "decision");
|
|
1503
|
+
const ownLines = (d) => who.filter((w) => w.sha !== null && ownWork(joined.get(w.sha) ?? { contracts: [], authorship: [], decisions: [], carries: [] }, d)).length;
|
|
1504
|
+
const ORDER = ["carried", "path", "contract", "issue", "member", "related"];
|
|
1505
|
+
const ranked = decisionNodes.map((d) => {
|
|
1506
|
+
const lines = ownLines(d);
|
|
1507
|
+
const carried = lines > 0 || (fileLines === null && touched.some((t) => ownWork(joined.get(t.sha) ?? { contracts: [], authorship: [], decisions: [], carries: [] }, d)));
|
|
1508
|
+
const granularities = new Set(d.constrains.map((c) => c.granularity));
|
|
1509
|
+
const relevance = carried ? "carried" : (["path", "contract", "issue", "member"].find((g) => granularities.has(g)) ?? "related");
|
|
1510
|
+
// A narrower path constraint is more specific to the region.
|
|
1511
|
+
const specificity = Math.max(0, ...d.constrains.filter((c) => c.granularity === "path").map((c) => c.entry.length));
|
|
1512
|
+
return { d, entry: { decision: d.id, relevance, current: d.supersededBy === null, closed: d.closed, lines }, specificity };
|
|
1513
|
+
});
|
|
1514
|
+
ranked.sort((a, b) => Number(b.entry.current) - Number(a.entry.current) ||
|
|
1515
|
+
ORDER.indexOf(a.entry.relevance) - ORDER.indexOf(b.entry.relevance) ||
|
|
1516
|
+
b.entry.lines - a.entry.lines ||
|
|
1517
|
+
Number(b.entry.closed) - Number(a.entry.closed) ||
|
|
1518
|
+
b.specificity - a.specificity ||
|
|
1519
|
+
(b.d.decidedIn?.date ?? "").localeCompare(a.d.decidedIn?.date ?? "") ||
|
|
1520
|
+
a.d.id.localeCompare(b.d.id));
|
|
1521
|
+
const decisions = ranked.map((r) => r.entry);
|
|
1522
|
+
// The runs that wrote the current lines, most lines first; for a directory, every run in the walk.
|
|
1523
|
+
const runNodes = [...nodes.values()].filter((n) => n.kind === "run");
|
|
1524
|
+
const runEntry = (n, lines, commits) => {
|
|
1525
|
+
const r = runJoins.runs.get(n.run);
|
|
1526
|
+
const targets = edges.filter((e) => e.kind === "worked-on" && e.from === n.id).map((e) => nodes.get(e.to));
|
|
1527
|
+
const work = r?.unit ? targets.find((t) => t?.kind === "work" && t.record === r.unit.id) : undefined;
|
|
1528
|
+
const carried = new Set();
|
|
1529
|
+
for (const t of targets) {
|
|
1530
|
+
if (t?.kind === "decision")
|
|
1531
|
+
carried.add(t.id);
|
|
1532
|
+
if (t?.kind === "work")
|
|
1533
|
+
for (const e of edges)
|
|
1534
|
+
if (e.kind === "implements" && e.from === t.id)
|
|
1535
|
+
carried.add(e.to);
|
|
1536
|
+
}
|
|
1537
|
+
const shas = commits.map((c) => c.slice("commit:".length));
|
|
1538
|
+
return { run: n.id, lines, commits, unit: r?.unit ? { id: r.unit.id, kind: r.unit.kind, node: work?.id ?? null } : null, decisions: [...carried], joinedBy: joinsOf([n.run], shas) };
|
|
1539
|
+
};
|
|
1540
|
+
let runs;
|
|
1541
|
+
if (fileLines !== null) {
|
|
1542
|
+
const tally = new Map();
|
|
1543
|
+
for (const w of who) {
|
|
1544
|
+
for (const id of w.runs) {
|
|
1545
|
+
const t = tally.get(id) ?? tally.set(id, { lines: 0, commits: new Set() }).get(id);
|
|
1546
|
+
t.lines++;
|
|
1547
|
+
t.commits.add(`commit:${w.sha}`);
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
runs = [...tally]
|
|
1551
|
+
.map(([id, t]) => ({ n: nodes.get(`run:${id}`), t }))
|
|
1552
|
+
.filter((x) => x.n !== undefined)
|
|
1553
|
+
.sort((a, b) => b.t.lines - a.t.lines || (b.n.startedAt ?? "").localeCompare(a.n.startedAt ?? "") || a.n.run.localeCompare(b.n.run))
|
|
1554
|
+
.map((x) => runEntry(x.n, x.t.lines, [...x.t.commits]));
|
|
1555
|
+
}
|
|
1556
|
+
else {
|
|
1557
|
+
runs = runNodes
|
|
1558
|
+
.sort((a, b) => (b.startedAt ?? "").localeCompare(a.startedAt ?? "") || a.run.localeCompare(b.run))
|
|
1559
|
+
.map((n) => runEntry(n, 0, edges.filter((e) => e.kind === "made-by" && e.to === n.id).map((e) => e.from)));
|
|
1560
|
+
}
|
|
1561
|
+
const explained = decisions.some((d) => d.current && d.relevance !== "related");
|
|
1562
|
+
const gaps = [];
|
|
1563
|
+
if (!explained) {
|
|
1564
|
+
gaps.push({
|
|
1565
|
+
code: "intent-why-no-decision",
|
|
1566
|
+
message: readsRecords ? `no current decision governs ${region.path}, and none is carried out by what made its lines` : `no record kind was read, so no decision can account for ${region.path}`,
|
|
1567
|
+
});
|
|
1568
|
+
}
|
|
1569
|
+
const committed = who.filter((w) => w.sha !== null);
|
|
1570
|
+
if (fileLines !== null ? committed.length > 0 && committed.every((w) => w.runs.length === 0) : touched.length > 0 && runNodes.length === 0) {
|
|
1571
|
+
gaps.push({ code: "intent-why-no-run", message: `no agent run is joined to the commits that made ${fileLines !== null ? "the current lines of " : ""}${region.path}` });
|
|
1572
|
+
}
|
|
1573
|
+
const uncommitted = who.filter((w) => w.sha === null).map((w) => w.line);
|
|
1574
|
+
if (uncommitted.length > 0)
|
|
1575
|
+
gaps.push({ code: "intent-why-uncommitted", message: `${uncommitted.length} ${uncommitted.length === 1 ? "line is" : "lines are"} not committed yet`, lines: ranges(uncommitted) });
|
|
1576
|
+
const ambiguous = who.filter((w) => w.runs.length > 1).map((w) => w.line);
|
|
1577
|
+
if (ambiguous.length > 0) {
|
|
1578
|
+
gaps.push({ code: "intent-why-run-ambiguous", message: `${ambiguous.length} ${ambiguous.length === 1 ? "line comes" : "lines come"} from a commit several runs made, and no run's recorded hunks say which wrote ${ambiguous.length === 1 ? "it" : "them"}`, lines: ranges(ambiguous) });
|
|
1579
|
+
}
|
|
1580
|
+
return { lines: fileLines, blame: spans, decisions, runs, explained, gaps };
|
|
1581
|
+
}
|
|
1582
|
+
}
|