@intentius/chant 0.101.0 → 0.102.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/checks.d.ts.map +1 -1
- package/dist/attrref.d.ts.map +1 -1
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/nginx.d.ts.map +1 -1
- package/dist/audit/prior-art.d.ts +48 -0
- package/dist/audit/prior-art.d.ts.map +1 -1
- package/dist/behaviour-kinds.d.ts.map +1 -1
- package/dist/builder.d.ts.map +1 -1
- package/dist/cli/commands/carve-emit.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon.d.ts +16 -0
- package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/import.d.ts +8 -0
- package/dist/cli/commands/import.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/init.d.ts +19 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/onboard.d.ts +9 -0
- package/dist/cli/commands/onboard.d.ts.map +1 -1
- package/dist/cli/commands/pinned-upgrade.d.ts +19 -5
- package/dist/cli/commands/pinned-upgrade.d.ts.map +1 -1
- package/dist/cli/handlers/dev.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/misc.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +9 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/lsp/server.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/server.d.ts.map +1 -1
- package/dist/cli/mcp/tools/build.d.ts.map +1 -1
- package/dist/cli/mcp/tools/composites.d.ts.map +1 -1
- package/dist/cli/mcp/tools/explain.d.ts.map +1 -1
- package/dist/cli/mcp/tools/import.d.ts.map +1 -1
- package/dist/cli/mcp/tools/lint.d.ts.map +1 -1
- package/dist/cli/mcp/tools/scaffold.d.ts.map +1 -1
- package/dist/cli/mcp/tools/search.d.ts.map +1 -1
- package/dist/cli/mcp/workspace-tools.d.ts +1 -1
- package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +11 -2
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/fetch.d.ts.map +1 -1
- package/dist/codegen/naming.d.ts.map +1 -1
- package/dist/codegen/pinned-upgrade.d.ts +67 -3
- package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
- package/dist/codegen/registry.d.ts.map +1 -1
- package/dist/components/capability-plugin-loader.d.ts.map +1 -1
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/fan-out.d.ts.map +1 -1
- package/dist/components/starter-plugin.d.ts.map +1 -1
- package/dist/components/verbs/process-runner.d.ts.map +1 -1
- package/dist/components/verbs/r2-sync.d.ts.map +1 -1
- package/dist/components/verbs/run-agent.d.ts.map +1 -1
- package/dist/components/verbs/sbom-generator.d.ts.map +1 -1
- package/dist/components/verbs/sign.d.ts.map +1 -1
- package/dist/components/verbs/verify.d.ts.map +1 -1
- package/dist/components/verbs/vuln-gate.d.ts.map +1 -1
- package/dist/components/verbs/vuln-scan.d.ts.map +1 -1
- package/dist/components/verbs/wrangler.d.ts.map +1 -1
- package/dist/composite.d.ts.map +1 -1
- package/dist/config.d.ts +19 -7
- package/dist/config.d.ts.map +1 -1
- package/dist/declarable.d.ts +16 -0
- package/dist/declarable.d.ts.map +1 -1
- package/dist/discovery/cache.d.ts.map +1 -1
- package/dist/discovery/fold-import.d.ts +15 -5
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/param-deps.d.ts +16 -0
- package/dist/discovery/param-deps.d.ts.map +1 -1
- package/dist/discovery/sandbox/policy-wire.d.ts.map +1 -1
- package/dist/discovery/specifier-resolve.d.ts +37 -0
- package/dist/discovery/specifier-resolve.d.ts.map +1 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/fold/fold.d.ts +3 -1
- package/dist/fold/fold.d.ts.map +1 -1
- package/dist/graph-detail.d.ts.map +1 -1
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/graph-layout.d.ts.map +1 -1
- package/dist/import/base-parser.d.ts.map +1 -1
- package/dist/import/embedded.d.ts +7 -0
- package/dist/import/embedded.d.ts.map +1 -1
- package/dist/import/parser-options.d.ts +14 -0
- package/dist/import/parser-options.d.ts.map +1 -0
- package/dist/import/parser.d.ts +17 -0
- package/dist/import/parser.d.ts.map +1 -1
- package/dist/kubectl-context.d.ts.map +1 -1
- package/dist/lexicon-output.d.ts.map +1 -1
- package/dist/lexicon-schema.d.ts +3 -3
- package/dist/lexicon.d.ts +55 -3
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/assert-live.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts +15 -0
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/member-ledger.d.ts.map +1 -1
- package/dist/lifecycle/observe.d.ts.map +1 -1
- package/dist/lifecycle/plan-ledger.d.ts.map +1 -1
- package/dist/lifecycle/release-ledger.d.ts.map +1 -1
- package/dist/lifecycle/scenario.d.ts.map +1 -1
- package/dist/lifecycle/status.d.ts.map +1 -1
- package/dist/lifecycle/work-lease.d.ts +61 -2
- package/dist/lifecycle/work-lease.d.ts.map +1 -1
- package/dist/lint/config.d.ts +8 -8
- package/dist/lint/engine.d.ts +6 -0
- package/dist/lint/engine.d.ts.map +1 -1
- package/dist/lsp/lexicon-providers.d.ts.map +1 -1
- package/dist/op/activities/activity-contracts.d.ts +11 -11
- package/dist/op/activities/apply.d.ts +79 -9
- package/dist/op/activities/apply.d.ts.map +1 -1
- package/dist/op/activities/decide.d.ts.map +1 -1
- package/dist/op/activities/index.d.ts +1 -1
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/lexicon-upgrade.d.ts +44 -12
- package/dist/op/activities/lexicon-upgrade.d.ts.map +1 -1
- package/dist/op/activity-contract.d.ts +1 -1
- package/dist/op/activity-failure.d.ts.map +1 -1
- package/dist/op/activity-profiles.d.ts.map +1 -1
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/composites/apply-op.d.ts +12 -5
- package/dist/op/composites/apply-op.d.ts.map +1 -1
- package/dist/op/composites/lexicon-upgrade-op.d.ts +22 -6
- package/dist/op/composites/lexicon-upgrade-op.d.ts.map +1 -1
- package/dist/op/decide-backend.d.ts.map +1 -1
- package/dist/op/decide-read-inputs.d.ts.map +1 -1
- package/dist/op/emulator-lifecycle.d.ts +21 -2
- package/dist/op/emulator-lifecycle.d.ts.map +1 -1
- package/dist/op/gate-approval.d.ts +5 -1
- package/dist/op/gate-approval.d.ts.map +1 -1
- package/dist/op/gate-wait.d.ts.map +1 -1
- package/dist/op/gate.d.ts +19 -3
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/op/index.d.ts +2 -0
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/local-executor.d.ts +6 -0
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/operator.d.ts.map +1 -1
- package/dist/op/run-context.d.ts +39 -0
- package/dist/op/run-context.d.ts.map +1 -0
- package/dist/op/run-live.d.ts.map +1 -1
- package/dist/op/runtimes/local.d.ts.map +1 -1
- package/dist/op/source-archive.d.ts.map +1 -1
- package/dist/op/steward-beside.d.ts.map +1 -1
- package/dist/op/steward-points.d.ts.map +1 -1
- package/dist/op/steward.d.ts.map +1 -1
- package/dist/op/types.d.ts +8 -3
- package/dist/op/types.d.ts.map +1 -1
- package/dist/op/work-lease-run.d.ts +24 -4
- package/dist/op/work-lease-run.d.ts.map +1 -1
- package/dist/project-root.d.ts.map +1 -1
- package/dist/provenance.d.ts +21 -0
- package/dist/provenance.d.ts.map +1 -1
- package/dist/pseudo-parameter.d.ts.map +1 -1
- package/dist/reconcile.d.ts.map +1 -1
- package/dist/secret-materialization.d.ts.map +1 -1
- package/dist/terraform/emit-source.d.ts.map +1 -1
- package/dist/terraform/parse.d.ts.map +1 -1
- package/dist/testing.d.ts.map +1 -1
- package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -1
- package/dist/workspace/__fixtures__/decide-workspace.d.ts +1 -1
- package/dist/workspace/__fixtures__/decide-workspace.d.ts.map +1 -1
- package/dist/workspace/agent-cli.d.ts +5 -0
- package/dist/workspace/agent-cli.d.ts.map +1 -1
- package/dist/workspace/box-factory.d.ts +73 -0
- package/dist/workspace/box-factory.d.ts.map +1 -0
- package/dist/workspace/changes.d.ts.map +1 -1
- package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts.map +1 -1
- package/dist/workspace/checks/boxes.d.ts.map +1 -1
- package/dist/workspace/checks.d.ts +5 -0
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/compose-graph.d.ts +10 -0
- package/dist/workspace/compose-graph.d.ts.map +1 -1
- package/dist/workspace/conformance/index.d.ts +67 -6
- package/dist/workspace/conformance/index.d.ts.map +1 -1
- package/dist/workspace/conformance/vitest.d.ts +12 -3
- package/dist/workspace/conformance/vitest.d.ts.map +1 -1
- package/dist/workspace/conformance/writer.d.ts +368 -0
- package/dist/workspace/conformance/writer.d.ts.map +1 -0
- package/dist/workspace/decide.d.ts +1 -1
- package/dist/workspace/decide.d.ts.map +1 -1
- package/dist/workspace/declaration.d.ts +91 -4
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.schema.json +234 -5
- package/dist/workspace/generated-files.d.ts.map +1 -1
- package/dist/workspace/graph-cache.d.ts.map +1 -1
- package/dist/workspace/graph-cli.d.ts +2 -0
- package/dist/workspace/graph-cli.d.ts.map +1 -1
- package/dist/workspace/identity.d.ts +111 -0
- package/dist/workspace/identity.d.ts.map +1 -0
- package/dist/workspace/intent-cli.d.ts +7 -4
- package/dist/workspace/intent-cli.d.ts.map +1 -1
- package/dist/workspace/intent-record.d.ts +8 -1
- package/dist/workspace/intent-record.d.ts.map +1 -1
- package/dist/workspace/intent.d.ts +149 -5
- package/dist/workspace/intent.d.ts.map +1 -1
- package/dist/workspace/kinds.d.ts +18 -4
- package/dist/workspace/kinds.d.ts.map +1 -1
- package/dist/workspace/lineage-hash-index.d.ts.map +1 -1
- package/dist/workspace/lineage-lock.d.ts +2 -2
- package/dist/workspace/lineage-lock.d.ts.map +1 -1
- package/dist/workspace/links.d.ts.map +1 -1
- package/dist/workspace/points.d.ts.map +1 -1
- package/dist/workspace/principal-classes.d.ts +114 -0
- package/dist/workspace/principal-classes.d.ts.map +1 -0
- package/dist/workspace/reason-codes.d.ts +21 -1
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/record-assets.d.ts.map +1 -1
- package/dist/workspace/records-checkout.d.ts +54 -0
- package/dist/workspace/records-checkout.d.ts.map +1 -0
- package/dist/workspace/records-cli.d.ts +27 -1
- package/dist/workspace/records-cli.d.ts.map +1 -1
- package/dist/workspace/records-close.d.ts +1 -1
- package/dist/workspace/records-close.d.ts.map +1 -1
- package/dist/workspace/records-since.d.ts.map +1 -1
- package/dist/workspace/records-write.d.ts +5 -4
- package/dist/workspace/records-write.d.ts.map +1 -1
- package/dist/workspace/records.d.ts +15 -0
- package/dist/workspace/records.d.ts.map +1 -1
- package/dist/workspace/runs-cli.d.ts +164 -0
- package/dist/workspace/runs-cli.d.ts.map +1 -0
- package/dist/workspace/runs.d.ts +423 -0
- package/dist/workspace/runs.d.ts.map +1 -0
- package/dist/workspace/source-block.d.ts +1 -1
- package/dist/workspace/source-block.d.ts.map +1 -1
- package/dist/workspace/status-gates.d.ts +15 -3
- package/dist/workspace/status-gates.d.ts.map +1 -1
- package/dist/workspace/status.d.ts +10 -0
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/symbols.d.ts +52 -0
- package/dist/workspace/symbols.d.ts.map +1 -0
- package/dist/workspace/trailer-joins.d.ts +46 -0
- package/dist/workspace/trailer-joins.d.ts.map +1 -0
- package/dist/workspace/trailers.d.ts +89 -0
- package/dist/workspace/trailers.d.ts.map +1 -0
- package/dist/workspace/trust/attestor.d.ts.map +1 -1
- package/dist/workspace/trust/rotation.d.ts.map +1 -1
- package/dist/workspace/trust/seal.d.ts +33 -0
- package/dist/workspace/trust/seal.d.ts.map +1 -1
- package/dist/workspace/trust/test-repo.d.ts.map +1 -1
- package/dist/workspace/work-cli.d.ts +39 -3
- package/dist/workspace/work-cli.d.ts.map +1 -1
- package/dist/workspace/work-evidence.d.ts +1 -1
- package/dist/workspace/work-evidence.d.ts.map +1 -1
- package/dist/workspace/work.d.ts +26 -1
- package/dist/workspace/work.d.ts.map +1 -1
- package/dist/workspace/workspace-principals.schema.json +60 -0
- package/dist/workspace/write-scope.d.ts +79 -19
- package/dist/workspace/write-scope.d.ts.map +1 -1
- package/dist/yaml.d.ts +21 -39
- package/dist/yaml.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/audit/prior-art.ts +15 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +1 -1
- package/src/cli/commands/carve-emit-state.test.ts +4 -0
- package/src/cli/commands/carve-emit.ts +5 -19
- package/src/cli/commands/check-lexicon.test.ts +62 -1
- package/src/cli/commands/check-lexicon.ts +36 -6
- package/src/cli/commands/import-live.test.ts +18 -0
- package/src/cli/commands/import-parser-options.test.ts +133 -0
- package/src/cli/commands/import.ts +21 -3
- package/src/cli/commands/init-lexicon/templates/project.ts +1 -1
- package/src/cli/commands/init.test.ts +8 -1
- package/src/cli/commands/init.ts +23 -8
- package/src/cli/commands/lint.test.ts +37 -0
- package/src/cli/commands/onboard.test.ts +53 -3
- package/src/cli/commands/onboard.ts +52 -4
- package/src/cli/commands/pinned-upgrade.ts +73 -33
- package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +2 -2
- package/src/cli/handlers/components.ts +2 -2
- package/src/cli/handlers/dev.ts +8 -6
- package/src/cli/handlers/lifecycle.test.ts +67 -0
- package/src/cli/handlers/lifecycle.ts +6 -5
- package/src/cli/handlers/misc.ts +2 -0
- package/src/cli/handlers/operator.ts +87 -3
- package/src/cli/handlers/run.test.ts +111 -0
- package/src/cli/handlers/run.ts +85 -24
- package/src/cli/handlers/scenario.ts +2 -2
- package/src/cli/main.test.ts +1 -0
- package/src/cli/main.ts +65 -11
- package/src/cli/mcp/workspace-tools.test.ts +5 -2
- package/src/cli/mcp/workspace-tools.ts +29 -2
- package/src/cli/registry.ts +11 -2
- package/src/codegen/pinned-upgrade-multi.test.ts +157 -0
- package/src/codegen/pinned-upgrade-tags.test.ts +112 -0
- package/src/codegen/pinned-upgrade.test.ts +104 -0
- package/src/codegen/pinned-upgrade.ts +250 -56
- package/src/codegen/testdata/postgres-tags.json +242 -0
- package/src/config.test.ts +21 -0
- package/src/config.ts +15 -0
- package/src/declarable.test.ts +21 -0
- package/src/declarable.ts +19 -0
- package/src/discovery/fold-composite.test.ts +41 -5
- package/src/discovery/fold-import.test.ts +129 -0
- package/src/discovery/fold-import.ts +643 -131
- package/src/discovery/fold-package-composite.test.ts +264 -0
- package/src/discovery/fold-specifiers.test.ts +191 -0
- package/src/discovery/param-deps.test.ts +66 -1
- package/src/discovery/param-deps.ts +45 -0
- package/src/discovery/specifier-resolve.ts +344 -0
- package/src/fold/fold.test.ts +39 -1
- package/src/fold/fold.ts +83 -9
- package/src/fold-provenance.test.ts +140 -0
- package/src/graph-ir.test.ts +83 -0
- package/src/graph-ir.ts +29 -0
- package/src/import/embedded.test.ts +20 -0
- package/src/import/embedded.ts +13 -0
- package/src/import/parser-options.ts +46 -0
- package/src/import/parser.ts +20 -0
- package/src/lexicon.ts +55 -3
- package/src/lifecycle/gate-ledger.ts +16 -0
- package/src/lifecycle/observe.test.ts +18 -0
- package/src/lifecycle/observe.ts +6 -4
- package/src/lifecycle/snapshot.ts +2 -2
- package/src/lifecycle/work-lease.test.ts +1 -1
- package/src/lifecycle/work-lease.ts +93 -2
- package/src/lint/engine.test.ts +55 -0
- package/src/lint/engine.ts +30 -4
- package/src/lint/output-docs.test.ts +59 -56
- package/src/op/activities/apply.test.ts +189 -5
- package/src/op/activities/apply.ts +230 -8
- package/src/op/activities/index.ts +3 -0
- package/src/op/activities/lexicon-upgrade.test.ts +160 -6
- package/src/op/activities/lexicon-upgrade.ts +154 -51
- package/src/op/composites/apply-op.ts +12 -5
- package/src/op/composites/composites.test.ts +30 -3
- package/src/op/composites/lexicon-upgrade-op.test.ts +7 -2
- package/src/op/composites/lexicon-upgrade-op.ts +29 -8
- package/src/op/emulator-lifecycle.test.ts +23 -0
- package/src/op/emulator-lifecycle.ts +52 -14
- package/src/op/gate-approval.test.ts +74 -0
- package/src/op/gate-approval.ts +5 -1
- package/src/op/gate.ts +31 -5
- package/src/op/index.ts +2 -0
- package/src/op/local-executor.test.ts +15 -0
- package/src/op/local-executor.ts +40 -12
- package/src/op/local-output.ts +1 -1
- package/src/op/operator.ts +4 -0
- package/src/op/run-context.test.ts +141 -0
- package/src/op/run-context.ts +51 -0
- package/src/op/runtimes/local.test.ts +14 -0
- package/src/op/runtimes/local.ts +2 -0
- package/src/op/steward-beside.ts +2 -0
- package/src/op/types.ts +8 -3
- package/src/op/work-lease-run.test.ts +47 -0
- package/src/op/work-lease-run.ts +73 -11
- package/src/provenance.ts +35 -0
- package/src/workspace/__fixtures__/studio-template-84c8b21.json +1 -1
- package/src/workspace/agent-cli.ts +7 -3
- package/src/workspace/agent.schema.json +22 -0
- package/src/workspace/box-factory.test.ts +320 -0
- package/src/workspace/box-factory.ts +103 -0
- package/src/workspace/changes.schema.json +12 -20
- package/src/workspace/changes.ts +4 -1
- package/src/workspace/checks/boxes.test.ts +2 -0
- package/src/workspace/checks.ts +29 -5
- package/src/workspace/compose-graph.ts +7 -0
- package/src/workspace/conformance/__writer_fixture__/answers/answer.kind.mjs +27 -0
- package/src/workspace/conformance/__writer_fixture__/answers/answer.schema.json +95 -0
- package/src/workspace/conformance/__writer_fixture__/decisions/points.json +132 -0
- package/src/workspace/conformance/__writer_fixture__/sessions/session.kind.mjs +48 -0
- package/src/workspace/conformance/__writer_fixture__/sessions/session.schema.json +108 -0
- package/src/workspace/conformance/__writer_fixture__/work/W-001-the-writer-suite-works-this-item.md +25 -0
- package/src/workspace/conformance/__writer_fixture__/work/work.kind.mjs +60 -0
- package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +578 -0
- package/src/workspace/conformance/conformance.e2e.test.ts +35 -3
- package/src/workspace/conformance/index.mjs +30 -3
- package/src/workspace/conformance/index.ts +103 -15
- package/src/workspace/conformance/vitest.ts +44 -2
- package/src/workspace/conformance/writer-conformance.e2e.test.ts +131 -0
- package/src/workspace/conformance/writer.test.ts +156 -0
- package/src/workspace/conformance/writer.ts +880 -0
- package/src/workspace/decide.ts +7 -1
- package/src/workspace/declaration.schema.json +234 -5
- package/src/workspace/declaration.ts +174 -10
- package/src/workspace/graph-cli.ts +5 -2
- package/src/workspace/graph.schema.json +247 -0
- package/src/workspace/identity.test.ts +196 -0
- package/src/workspace/identity.ts +186 -0
- package/src/workspace/intent-cli.ts +64 -10
- package/src/workspace/intent-record.schema.json +208 -0
- package/src/workspace/intent-record.ts +35 -1
- package/src/workspace/intent-why.test.ts +274 -0
- package/src/workspace/intent.schema.json +718 -7
- package/src/workspace/intent.test.ts +11 -2
- package/src/workspace/intent.ts +495 -32
- package/src/workspace/kinds.ts +44 -37
- package/src/workspace/points-write.schema.json +1 -0
- package/src/workspace/principal-classes.test.ts +175 -0
- package/src/workspace/principal-classes.ts +307 -0
- package/src/workspace/reason-codes.test.ts +11 -3
- package/src/workspace/reason-codes.ts +24 -1
- package/src/workspace/records-amend.schema.json +2 -0
- package/src/workspace/records-checkout.ts +106 -0
- package/src/workspace/records-cli.ts +103 -11
- package/src/workspace/records-close.schema.json +1 -0
- package/src/workspace/records-new.schema.json +2 -0
- package/src/workspace/records-review.schema.json +2 -0
- package/src/workspace/records-uncommitted.test.ts +171 -0
- package/src/workspace/records-write.ts +28 -6
- package/src/workspace/records.schema.json +72 -1
- package/src/workspace/records.ts +27 -0
- package/src/workspace/runs-cli.ts +381 -0
- package/src/workspace/runs-write.schema.json +583 -0
- package/src/workspace/runs.schema.json +810 -0
- package/src/workspace/runs.test.ts +242 -0
- package/src/workspace/runs.ts +709 -0
- package/src/workspace/status-contract.test.ts +5 -0
- package/src/workspace/status-gates.ts +22 -8
- package/src/workspace/status.schema.json +127 -2
- package/src/workspace/status.ts +21 -1
- package/src/workspace/symbols.ts +180 -0
- package/src/workspace/trailer-joins.test.ts +171 -0
- package/src/workspace/trailer-joins.ts +82 -0
- package/src/workspace/trailers.ts +148 -0
- package/src/workspace/trust/seal.ts +52 -1
- package/src/workspace/work-cli.e2e.test.ts +28 -1
- package/src/workspace/work-cli.ts +133 -8
- package/src/workspace/work-evidence.schema.json +2 -0
- package/src/workspace/work-history.schema.json +73 -1
- package/src/workspace/work-history.test.ts +48 -5
- package/src/workspace/work-kind-fields.test.ts +154 -0
- package/src/workspace/work-lease.schema.json +14 -1
- package/src/workspace/work.ts +82 -0
- package/src/workspace/workspace-principals.schema.json +60 -0
- package/src/workspace/write-scope.test.ts +128 -2
- package/src/workspace/write-scope.ts +170 -45
- package/src/yaml.test.ts +111 -1
- package/src/yaml.ts +141 -549
package/src/workspace/intent.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The intent graph over one region: `chant workspace graph --intent
|
|
3
|
-
* <path[:start-end]>` (#2651; #2650 sections B and C1; #2524 D8,
|
|
3
|
+
* <path[:start-end]|path#symbol>` (#2651; #2650 sections B and C1; #2524 D8,
|
|
4
|
+
* D15; #3034).
|
|
4
5
|
*
|
|
5
6
|
* A person points at some code and asks how it got this way and who decided
|
|
6
7
|
* it should be this way. This module gathers what the workspace records about
|
|
@@ -31,6 +32,13 @@
|
|
|
31
32
|
* runs for every commit in that order. chant emits the
|
|
32
33
|
* graph and hud renders it (#2524 D8, D15). Git is read through a local
|
|
33
34
|
* `git` subprocess only: no fetch, no network.
|
|
35
|
+
*
|
|
36
|
+
* A region can name a symbol, `path#symbol`, which `symbols.ts` resolves to
|
|
37
|
+
* its current lines (#3034). For a file region the document also carries
|
|
38
|
+
* `why`: who made the region's current lines, from `git blame`, the agent run
|
|
39
|
+
* behind each (narrowed by the hunks a run recorded when several share a
|
|
40
|
+
* commit), and the decisions governing the region, most relevant first, with
|
|
41
|
+
* `explained: false` and closed gap codes when nothing accounts for it.
|
|
34
42
|
*/
|
|
35
43
|
|
|
36
44
|
import { execFileSync } from "node:child_process";
|
|
@@ -48,6 +56,9 @@ import type { DecidedIn } from "./record-decided";
|
|
|
48
56
|
import { importKindModule, loadRecordKind, parseFrontMatter, RecordReadError, supersedesTargets, type LoadedRecordKind } from "./records";
|
|
49
57
|
import { queryRecords, type RecordView } from "./records-cli";
|
|
50
58
|
import { isPluginCode, type PluginCode, type ReasonCode } from "./reason-codes";
|
|
59
|
+
import { runsForCommits, type RunCost, type RunPin, type RunUsage, type RunView } from "./runs";
|
|
60
|
+
import { resolveSymbol } from "./symbols";
|
|
61
|
+
import { readTrailerJoins, type CommitTrailerJoins } from "./trailer-joins";
|
|
51
62
|
import { joinPath, skippedDir, type WorkspaceTree } from "./tree";
|
|
52
63
|
import { activeAttestors, type ProvenanceLevel } from "./trust/attestor";
|
|
53
64
|
import { commitProvenance, policyAtBase, resolveBase } from "./trust/provenance";
|
|
@@ -66,7 +77,7 @@ export const INTENT_OUTPUT_SCHEMA_ID = "https://intentius.io/chant/schemas/works
|
|
|
66
77
|
export const INTENT_FINDING_CODES = [
|
|
67
78
|
/** A commit touched the region when no decision constrained it at path granularity. */
|
|
68
79
|
"intent-commit-undecided",
|
|
69
|
-
/** A commit has no unit, no pull request reference and no decision covering the region at its time. */
|
|
80
|
+
/** 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. */
|
|
70
81
|
"intent-commit-bare",
|
|
71
82
|
/** A pinned artifact's bytes no longer hash to the pin. */
|
|
72
83
|
"intent-pin-drifted",
|
|
@@ -123,9 +134,28 @@ export const INTENT_ERROR_CODES = [
|
|
|
123
134
|
"intent-region-invalid",
|
|
124
135
|
/** --record names an id no record of a decision kind read has. */
|
|
125
136
|
"intent-record-unknown",
|
|
137
|
+
/** The region names a symbol in a file no resolver reads (#3034). */
|
|
138
|
+
"intent-symbol-unsupported",
|
|
139
|
+
/** The region names a symbol the file does not declare (#3034). */
|
|
140
|
+
"intent-symbol-unknown",
|
|
141
|
+
/** The region names a symbol that matches more than one declaration (#3034). */
|
|
142
|
+
"intent-symbol-ambiguous",
|
|
126
143
|
] as const satisfies readonly ReasonCode[];
|
|
127
144
|
export type IntentErrorCode = (typeof INTENT_ERROR_CODES)[number];
|
|
128
145
|
|
|
146
|
+
/** Why `why` can't account for some or all of a region (#3034). Closed. */
|
|
147
|
+
export const INTENT_WHY_CODES = [
|
|
148
|
+
/** No current decision governs the region, at any granularity, and none is carried out by the commits or runs that made its lines. */
|
|
149
|
+
"intent-why-no-decision",
|
|
150
|
+
/** None of the region's current lines was made by a commit an agent run is joined to. */
|
|
151
|
+
"intent-why-no-run",
|
|
152
|
+
/** Some of the region's lines are not committed, so nothing made them yet. */
|
|
153
|
+
"intent-why-uncommitted",
|
|
154
|
+
/** Some lines come from a commit that several runs made, and no run's recorded hunks say which wrote them. */
|
|
155
|
+
"intent-why-run-ambiguous",
|
|
156
|
+
] as const satisfies readonly ReasonCode[];
|
|
157
|
+
export type IntentWhyCode = (typeof INTENT_WHY_CODES)[number];
|
|
158
|
+
|
|
129
159
|
export class IntentError extends Error {
|
|
130
160
|
constructor(
|
|
131
161
|
readonly code: IntentErrorCode,
|
|
@@ -157,6 +187,8 @@ export interface RegionNode {
|
|
|
157
187
|
generated: boolean | null;
|
|
158
188
|
/** Set when the region was given as a graph node id. */
|
|
159
189
|
node: string | null;
|
|
190
|
+
/** Set when the region was given as `path#symbol` (#3034): the symbol as given, the declaration it resolved to, and its kind. `lines` holds its current lines. */
|
|
191
|
+
symbol?: { name: string; qualified: string; kind: string } | null;
|
|
160
192
|
}
|
|
161
193
|
|
|
162
194
|
export interface FileNode {
|
|
@@ -196,6 +228,13 @@ export interface CommitNode {
|
|
|
196
228
|
* null when no record kind was read.
|
|
197
229
|
*/
|
|
198
230
|
state: CommitState | null;
|
|
231
|
+
/**
|
|
232
|
+
* What chant's own trailers join the commit to (#3149, ws-075): the agent
|
|
233
|
+
* session, the work lease and the item it was taken on, the agent run, the
|
|
234
|
+
* records it names and the apply it records. A record or a lease item that
|
|
235
|
+
* a kind read has is linked by a `carries` edge.
|
|
236
|
+
*/
|
|
237
|
+
joins: CommitTrailerJoins;
|
|
199
238
|
}
|
|
200
239
|
|
|
201
240
|
export type CommitState = "decided" | "decided-by-window" | "undecided";
|
|
@@ -331,7 +370,36 @@ export interface WorkNode {
|
|
|
331
370
|
warnings: { code: WorkWarningCode; message: string }[];
|
|
332
371
|
}
|
|
333
372
|
|
|
334
|
-
|
|
373
|
+
/**
|
|
374
|
+
* An agent run that made a commit in the walk (#3033): from the run ledger on
|
|
375
|
+
* the local `chant/lifecycle` branch. A run a `Chant-Run` trailer names and
|
|
376
|
+
* the ledger lacks is listed with `recorded: false` and nothing else known.
|
|
377
|
+
*/
|
|
378
|
+
export interface RunNode {
|
|
379
|
+
id: string;
|
|
380
|
+
kind: "run";
|
|
381
|
+
run: string;
|
|
382
|
+
recorded: boolean;
|
|
383
|
+
state: "running" | "ended" | null;
|
|
384
|
+
harness: { name: string; version: string | null } | null;
|
|
385
|
+
model: string | null;
|
|
386
|
+
provider: string | null;
|
|
387
|
+
by: string | null;
|
|
388
|
+
agent: string | null;
|
|
389
|
+
unit: string | null;
|
|
390
|
+
startedAt: string | null;
|
|
391
|
+
endedAt: string | null;
|
|
392
|
+
outcome: string | null;
|
|
393
|
+
usage: RunUsage | null;
|
|
394
|
+
cost: RunCost | null;
|
|
395
|
+
transcript: RunPin | null;
|
|
396
|
+
/** The instruction the run was given, pinned by hash, with the writer's excerpt when it gave one (#3034). */
|
|
397
|
+
instruction?: RunPin | null;
|
|
398
|
+
/** The fencing token of the work lease it worked under (#3034). */
|
|
399
|
+
lease?: string | null;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export type IntentNode = RegionNode | FileNode | MemberNode | CommitNode | JoinedNode | EvidenceEntryNode | DecisionNode | WorkNode | ArtifactNode | LinkNode | FindingNode | RunNode;
|
|
335
403
|
|
|
336
404
|
export type Granularity = "path" | "member" | "contract" | "issue";
|
|
337
405
|
|
|
@@ -340,7 +408,75 @@ export type IntentEdge =
|
|
|
340
408
|
| { kind: "pins"; from: string; to: string; pinnedSha256: string | null; pinState: PinState }
|
|
341
409
|
| { kind: "touched-by"; from: string; to: string; lines: LineRange[] | null }
|
|
342
410
|
| { kind: "within"; from: string; to: string; state: "decided" | "decided-by-window" | "worked" }
|
|
343
|
-
| { kind: "
|
|
411
|
+
| { kind: "made-by"; from: string; to: string; joinedBy: ("trailer" | "record")[] }
|
|
412
|
+
| { kind: "produced-by" | "serves" | "cites-evidence" | "supersedes" | "links" | "implements" | "needs" | "addressed-by" | "carries" | "worked-on"; from: string; to: string };
|
|
413
|
+
|
|
414
|
+
/** Lines of the region that one commit, and the same runs, last wrote (#3034). */
|
|
415
|
+
export interface WhySpan {
|
|
416
|
+
start: number;
|
|
417
|
+
end: number;
|
|
418
|
+
/** The commit node, or null for lines not committed yet. */
|
|
419
|
+
commit: string | null;
|
|
420
|
+
sha: string | null;
|
|
421
|
+
/** The run nodes behind these lines: the run the commit's Chant-Run names first, then those whose record lists it. */
|
|
422
|
+
runs: string[];
|
|
423
|
+
/** `hunks` when a run's recorded hunks chose among the commit's runs; null when every run that made the commit is listed. */
|
|
424
|
+
narrowedBy: "hunks" | null;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/** Why a decision is in `why`, most relevant first (#3034). */
|
|
428
|
+
export type WhyRelevance = "carried" | "path" | "contract" | "issue" | "member" | "related";
|
|
429
|
+
|
|
430
|
+
export interface WhyDecision {
|
|
431
|
+
decision: string;
|
|
432
|
+
/**
|
|
433
|
+
* carried: a commit or run that made the region's current lines carries it
|
|
434
|
+
* out. path, contract, issue, member: it constrains the region that way.
|
|
435
|
+
* related: it is in the graph only through supersession, or because a
|
|
436
|
+
* commit that no longer made any current line carried it out.
|
|
437
|
+
*/
|
|
438
|
+
relevance: WhyRelevance;
|
|
439
|
+
/** Not superseded. */
|
|
440
|
+
current: boolean;
|
|
441
|
+
/** Its state is one its kind closes, such as ratified. */
|
|
442
|
+
closed: boolean;
|
|
443
|
+
/** How many of the region's current lines were made as its own work. */
|
|
444
|
+
lines: number;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
export interface WhyRun {
|
|
448
|
+
run: string;
|
|
449
|
+
/** How many of the region's current lines it wrote. */
|
|
450
|
+
lines: number;
|
|
451
|
+
/** The commit nodes through which it made them. */
|
|
452
|
+
commits: string[];
|
|
453
|
+
/** The work item it worked on, as the run record names it, and its node when a work kind read has it. */
|
|
454
|
+
unit: { id: string; kind: string | null; node: string | null } | null;
|
|
455
|
+
/** The decision nodes it carried out: through its work item's implements, and the decision records it names. */
|
|
456
|
+
decisions: string[];
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
export interface WhyGap {
|
|
460
|
+
code: IntentWhyCode;
|
|
461
|
+
message: string;
|
|
462
|
+
/** The lines the gap is about, when it is about some of them. */
|
|
463
|
+
lines?: LineRange[];
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/** The answer to "why is it like this" over the region (#3034, hud#700). */
|
|
467
|
+
export interface WhyAnswer {
|
|
468
|
+
/** The lines accounted for: the region's range, the whole file, or null for a directory, which is not blamed. */
|
|
469
|
+
lines: LineRange | null;
|
|
470
|
+
/** The current lines, top to bottom, each span with the commit and runs that last wrote it. Empty for a directory. */
|
|
471
|
+
blame: WhySpan[];
|
|
472
|
+
/** The decisions governing the region, most relevant first. */
|
|
473
|
+
decisions: WhyDecision[];
|
|
474
|
+
/** The runs that wrote the current lines, most lines first; for a directory, every run in the walk, newest first. */
|
|
475
|
+
runs: WhyRun[];
|
|
476
|
+
/** Whether a current decision governs the region or is carried out by what made its lines. */
|
|
477
|
+
explained: boolean;
|
|
478
|
+
gaps: WhyGap[];
|
|
479
|
+
}
|
|
344
480
|
|
|
345
481
|
export interface IntentReason {
|
|
346
482
|
code: IntentReasonCode;
|
|
@@ -364,13 +500,14 @@ export type IntentDocument =
|
|
|
364
500
|
edges: IntentEdge[];
|
|
365
501
|
reasons: IntentReason[];
|
|
366
502
|
summary: { commits: number; decisions: number; artifacts: number; findings: number };
|
|
503
|
+
why: WhyAnswer;
|
|
367
504
|
})
|
|
368
505
|
| (Head & { error: { code: IntentErrorCode; message: string } });
|
|
369
506
|
|
|
370
507
|
export interface IntentQuery {
|
|
371
508
|
/** Where the walk up to the declaration starts, and what the region path is relative to. */
|
|
372
509
|
cwd: string;
|
|
373
|
-
/** `path`, `path:line
|
|
510
|
+
/** `path`, `path:line`, `path:start-end` or `path#symbol`, or a graph node id `<member>/<id>`. */
|
|
374
511
|
region: string;
|
|
375
512
|
at?: string;
|
|
376
513
|
/**
|
|
@@ -442,6 +579,45 @@ export function regionHistory(top: string, rev: string, gitPath: string, type: "
|
|
|
442
579
|
.map((sha) => ({ sha, lines: null }));
|
|
443
580
|
}
|
|
444
581
|
|
|
582
|
+
/** One current line of a file, and the commit that last wrote it. */
|
|
583
|
+
export interface BlamedLine {
|
|
584
|
+
line: number;
|
|
585
|
+
/** Null for a line not committed yet. */
|
|
586
|
+
sha: string | null;
|
|
587
|
+
/** The file's path in that commit, from the repository root. */
|
|
588
|
+
origPath: string;
|
|
589
|
+
/** The line's number in that commit's version of the file. */
|
|
590
|
+
origLine: number;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const ZERO_SHA = /^0+$/;
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* `git blame` over a file's lines (#3034): at `rev`, or in the working tree
|
|
597
|
+
* when `rev` is null, where a line not committed yet has no commit. Undefined
|
|
598
|
+
* when git can't blame the file, such as one never committed.
|
|
599
|
+
*/
|
|
600
|
+
export function blameLines(top: string, rev: string | null, gitPath: string, lines: LineRange | null): BlamedLine[] | undefined {
|
|
601
|
+
const out = tryGit(top, ["blame", "--line-porcelain", ...(lines ? [`-L${lines.start},${lines.end}`] : []), ...(rev ? [rev] : []), "--", gitPath]);
|
|
602
|
+
if (out === undefined) return undefined;
|
|
603
|
+
const result: BlamedLine[] = [];
|
|
604
|
+
let current: { sha: string; origLine: number; line: number; origPath: string } | undefined;
|
|
605
|
+
for (const l of out.split("\n")) {
|
|
606
|
+
if (l.startsWith("\t")) {
|
|
607
|
+
if (current) result.push({ line: current.line, sha: ZERO_SHA.test(current.sha) ? null : current.sha, origPath: current.origPath, origLine: current.origLine });
|
|
608
|
+
current = undefined;
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
const head = l.match(/^([0-9a-f]{40,64}) (\d+) (\d+)(?: \d+)?$/);
|
|
612
|
+
if (head) {
|
|
613
|
+
current = { sha: head[1], origLine: Number(head[2]), line: Number(head[3]), origPath: gitPath };
|
|
614
|
+
continue;
|
|
615
|
+
}
|
|
616
|
+
if (current && l.startsWith("filename ")) current.origPath = l.slice("filename ".length);
|
|
617
|
+
}
|
|
618
|
+
return result;
|
|
619
|
+
}
|
|
620
|
+
|
|
445
621
|
/** Subject, body, author, date and trailers of each commit. */
|
|
446
622
|
export function commitDetails(top: string, shas: string[]): Map<string, IntentCommit> {
|
|
447
623
|
const out = new Map<string, IntentCommit>();
|
|
@@ -538,7 +714,8 @@ export function parseRegion(arg: string): { path: string; lines: LineRange | nul
|
|
|
538
714
|
return { path: m[1], lines: { start, end } };
|
|
539
715
|
}
|
|
540
716
|
|
|
541
|
-
function regionId(path: string, lines: LineRange | null): string {
|
|
717
|
+
function regionId(path: string, lines: LineRange | null, symbol?: string): string {
|
|
718
|
+
if (symbol !== undefined) return `region:${path}#${symbol}`;
|
|
542
719
|
return `region:${path}${lines ? `:${lines.start}${lines.end === lines.start ? "" : `-${lines.end}`}` : ""}`;
|
|
543
720
|
}
|
|
544
721
|
|
|
@@ -649,13 +826,34 @@ export async function loadKinds(query: IntentQuery, top: string): Promise<Loaded
|
|
|
649
826
|
return out;
|
|
650
827
|
}
|
|
651
828
|
|
|
652
|
-
|
|
829
|
+
interface ResolvedRegion {
|
|
830
|
+
path: string;
|
|
831
|
+
lines: LineRange | null;
|
|
832
|
+
node: string | null;
|
|
833
|
+
symbol: { name: string; qualified: string; kind: string } | null;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
async function resolveRegion(query: IntentQuery, located: LocatedWorkspace, declaration: Declaration): Promise<ResolvedRegion> {
|
|
653
837
|
const parsed = parseRegion(query.region);
|
|
654
838
|
if ("error" in parsed) throw new IntentError("intent-region-invalid", parsed.error);
|
|
655
|
-
const
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
return
|
|
839
|
+
const inWorkspace = (p: string): string | undefined => {
|
|
840
|
+
const rel = toPosix(relative(realpathOr(located.rootOnDisk), resolve(realpathOr(query.cwd), p)));
|
|
841
|
+
const path = rel === "" ? "." : rel;
|
|
842
|
+
return path === "." || (isWorkspacePath(path) && !path.startsWith("../") && located.tree.stat(path) !== undefined) ? path : undefined;
|
|
843
|
+
};
|
|
844
|
+
const path = inWorkspace(parsed.path);
|
|
845
|
+
if (path !== undefined) return { path, lines: parsed.lines, node: null, symbol: null };
|
|
846
|
+
// path#symbol (#3034): the symbol's current lines in the tree read.
|
|
847
|
+
const hash = parsed.lines ? -1 : query.region.lastIndexOf("#");
|
|
848
|
+
if (hash > 0) {
|
|
849
|
+
const name = query.region.slice(hash + 1);
|
|
850
|
+
const file = inWorkspace(query.region.slice(0, hash));
|
|
851
|
+
if (file !== undefined && name !== "") {
|
|
852
|
+
if (file === "." || located.tree.stat(file) !== "file") throw new IntentError("intent-region-invalid", `${file} is a directory, and a symbol needs a file`);
|
|
853
|
+
const r = resolveSymbol(file, located.tree.read(file), name);
|
|
854
|
+
if (!r.ok) throw new IntentError(r.reason === "unsupported" ? "intent-symbol-unsupported" : r.reason === "ambiguous" ? "intent-symbol-ambiguous" : "intent-symbol-unknown", `${r.message}${located.tree.label}`);
|
|
855
|
+
return { path: file, lines: r.declaration.lines, node: null, symbol: { name, qualified: r.declaration.qualified, kind: r.declaration.kind } };
|
|
856
|
+
}
|
|
659
857
|
}
|
|
660
858
|
// A graph node id, <member>/<id>, when no such path exists (#2650 B1).
|
|
661
859
|
const slash = query.region.indexOf("/");
|
|
@@ -664,10 +862,10 @@ async function resolveRegion(query: IntentQuery, located: LocatedWorkspace, decl
|
|
|
664
862
|
const loc = await (query.resolveNode ?? resolveNodeFromGraph)(query.cwd, query.at, member.name, query.region);
|
|
665
863
|
if (loc) {
|
|
666
864
|
const file = joinPath(member.dir, loc.file);
|
|
667
|
-
return { path: file === "" ? "." : file, lines: loc.line ? { start: loc.line, end: loc.line } : null, node: query.region };
|
|
865
|
+
return { path: file === "" ? "." : file, lines: loc.line ? { start: loc.line, end: loc.line } : null, node: query.region, symbol: null };
|
|
668
866
|
}
|
|
669
867
|
}
|
|
670
|
-
throw new IntentError("intent-region-invalid", `${query.region} is not a path in the workspace${located.tree.label}, or a graph node id`);
|
|
868
|
+
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`);
|
|
671
869
|
}
|
|
672
870
|
|
|
673
871
|
/** Build the intent document. Never throws an {@link IntentError}, {@link WorkspaceReadError} or {@link RecordReadError}. */
|
|
@@ -714,8 +912,19 @@ async function walk(query: IntentQuery, head: Head): Promise<IntentResult> {
|
|
|
714
912
|
|
|
715
913
|
// The region, its files and its member.
|
|
716
914
|
const member = region.path === "." ? (declaration.members.find((m) => m.dir === ".")?.name ?? null) : memberHolding(region.path, declaration.members);
|
|
717
|
-
const rid = regionId(region.path, region.lines);
|
|
718
|
-
add<RegionNode>({
|
|
915
|
+
const rid = regionId(region.path, region.lines, region.symbol?.name);
|
|
916
|
+
add<RegionNode>({
|
|
917
|
+
id: rid,
|
|
918
|
+
kind: "region",
|
|
919
|
+
path: region.path,
|
|
920
|
+
lines: region.lines,
|
|
921
|
+
member,
|
|
922
|
+
at: located.at,
|
|
923
|
+
type,
|
|
924
|
+
generated: type === "file" ? isGeneratedPath(declaration, region.path) : null,
|
|
925
|
+
node: region.node,
|
|
926
|
+
symbol: region.symbol,
|
|
927
|
+
});
|
|
719
928
|
const files = type === "dir" ? filesUnder(located.tree, region.path) : [];
|
|
720
929
|
for (const f of files) add<FileNode>({ id: `file:${f}`, kind: "file", path: f, member: memberHolding(f, declaration.members), generated: isGeneratedPath(declaration, f) });
|
|
721
930
|
const memberNode = (name: string) => {
|
|
@@ -731,6 +940,19 @@ async function walk(query: IntentQuery, head: Head): Promise<IntentResult> {
|
|
|
731
940
|
const workspacePrefix = located.root === "." ? "" : located.root;
|
|
732
941
|
const gitPath = joinPath(workspacePrefix, region.path);
|
|
733
942
|
const touched = rev ? regionHistory(top, rev, gitPath, type, region.lines) : [];
|
|
943
|
+
// Who last wrote each current line of a file region (#3034). A commit blame
|
|
944
|
+
// names and the history walk missed, as when a move confuses git log -L,
|
|
945
|
+
// joins the commits that touched the region.
|
|
946
|
+
const fileLines = type === "file" ? (region.lines ?? { start: 1, end: Math.max(1, located.tree.read(region.path).replace(/\n$/, "").split("\n").length) }) : null;
|
|
947
|
+
const blamed: BlamedLine[] =
|
|
948
|
+
fileLines === null
|
|
949
|
+
? []
|
|
950
|
+
: ((rev ? blameLines(top, located.at, gitPath, region.lines) : undefined) ??
|
|
951
|
+
// A file git can't blame in the working tree has never been committed: every line is new.
|
|
952
|
+
(located.at === null ? Array.from({ length: fileLines.end - fileLines.start + 1 }, (_, i) => ({ line: fileLines.start + i, sha: null, origPath: gitPath, origLine: fileLines.start + i })) : []));
|
|
953
|
+
for (const sha of new Set(blamed.map((b) => b.sha).filter((x): x is string => x !== null))) {
|
|
954
|
+
if (!touched.some((t) => t.sha === sha)) touched.push({ sha, lines: null });
|
|
955
|
+
}
|
|
734
956
|
const details = commitDetails(top, touched.map((t) => t.sha));
|
|
735
957
|
|
|
736
958
|
// Each commit's provenance, judged by the policy at base as records judges a record's (#2547).
|
|
@@ -752,10 +974,14 @@ async function walk(query: IntentQuery, head: Head): Promise<IntentResult> {
|
|
|
752
974
|
unit?: string;
|
|
753
975
|
contracts: string[];
|
|
754
976
|
authorship: string[];
|
|
755
|
-
/** The record ids the commit's units and contracts say they carry out. */
|
|
977
|
+
/** The record ids the commit's units and contracts say they carry out, and the ones its Chant-Record and Chant-Lease trailers name or implement. */
|
|
756
978
|
decisions: string[];
|
|
979
|
+
/** The nodes of the records the commit's own trailers name (#3149). */
|
|
980
|
+
carries: string[];
|
|
757
981
|
}
|
|
758
982
|
const joined = new Map<string, Joined>();
|
|
983
|
+
// chant's own trailers on each commit (#3149), with the items their lease tokens name.
|
|
984
|
+
const trailerJoins = readTrailerJoins(top, [...details.values()]);
|
|
759
985
|
const pluginFindings: { commit: string; plugin: string; finding: PluginFinding }[] = [];
|
|
760
986
|
const failedPlugins = new Set<string>();
|
|
761
987
|
for (const t of touched) {
|
|
@@ -769,11 +995,11 @@ async function walk(query: IntentQuery, head: Head): Promise<IntentResult> {
|
|
|
769
995
|
: { level: "unattested" as const, reason: policy.problems.length ? policy.problems.join("; ") : `no signers file (${policy.signersPath}) at base; attestation is off` };
|
|
770
996
|
const pr = c.subject.match(/\(#([0-9]+)\)\s*$/);
|
|
771
997
|
const cid = `commit:${c.sha}`;
|
|
772
|
-
add<CommitNode>({ 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 });
|
|
998
|
+
add<CommitNode>({ 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 });
|
|
773
999
|
edges.push({ kind: "touched-by", from: rid, to: cid, lines: t.lines });
|
|
774
1000
|
|
|
775
1001
|
// 2. Each commit's origin, from the plugins.
|
|
776
|
-
const entry: Joined = { contracts: [], authorship: [], decisions: [] };
|
|
1002
|
+
const entry: Joined = { contracts: [], authorship: [], decisions: [], carries: [] };
|
|
777
1003
|
joined.set(c.sha, entry);
|
|
778
1004
|
for (const k of kinds) {
|
|
779
1005
|
if (!k.joins) continue;
|
|
@@ -806,6 +1032,41 @@ async function walk(query: IntentQuery, head: Head): Promise<IntentResult> {
|
|
|
806
1032
|
}
|
|
807
1033
|
}
|
|
808
1034
|
|
|
1035
|
+
// The agent runs that made each commit (#3033): the one its Chant-Run names, and any whose record lists it.
|
|
1036
|
+
const runJoins = await runsForCommits(
|
|
1037
|
+
top,
|
|
1038
|
+
located.rootOnDisk,
|
|
1039
|
+
touched.map((t) => ({ sha: t.sha, run: trailerJoins.get(t.sha)?.joins.run ?? null })),
|
|
1040
|
+
);
|
|
1041
|
+
for (const t of touched) {
|
|
1042
|
+
if (!nodes.has(`commit:${t.sha}`)) continue;
|
|
1043
|
+
for (const ref of runJoins.refs.get(t.sha) ?? []) {
|
|
1044
|
+
const r = runJoins.runs.get(ref.id);
|
|
1045
|
+
const node = add<RunNode>({
|
|
1046
|
+
id: `run:${ref.id}`,
|
|
1047
|
+
kind: "run",
|
|
1048
|
+
run: ref.id,
|
|
1049
|
+
recorded: r !== undefined,
|
|
1050
|
+
state: r?.state ?? null,
|
|
1051
|
+
harness: r?.harness ?? null,
|
|
1052
|
+
model: r?.model ?? null,
|
|
1053
|
+
provider: r?.provider ?? null,
|
|
1054
|
+
by: r?.by ?? null,
|
|
1055
|
+
agent: r?.agent ?? null,
|
|
1056
|
+
unit: r?.unit?.id ?? null,
|
|
1057
|
+
startedAt: r?.startedAt ?? null,
|
|
1058
|
+
endedAt: r?.endedAt ?? null,
|
|
1059
|
+
outcome: r?.outcome ?? null,
|
|
1060
|
+
usage: r?.usage ?? null,
|
|
1061
|
+
cost: r?.cost ?? null,
|
|
1062
|
+
transcript: r?.transcript ?? null,
|
|
1063
|
+
instruction: r?.instruction ?? null,
|
|
1064
|
+
lease: r?.lease ?? null,
|
|
1065
|
+
});
|
|
1066
|
+
edges.push({ kind: "made-by", from: `commit:${t.sha}`, to: node.id, joinedBy: ref.joinedBy });
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
|
|
809
1070
|
// 3. The decisions whose constrains cover the region, and their chains.
|
|
810
1071
|
const regionFull = region.path === "." ? workspacePrefix : gitPath;
|
|
811
1072
|
const origin = originRepo(top);
|
|
@@ -1099,16 +1360,6 @@ async function walk(query: IntentQuery, head: Head): Promise<IntentResult> {
|
|
|
1099
1360
|
}
|
|
1100
1361
|
if (node.constrains.length > 0) workCovering.push(h);
|
|
1101
1362
|
}
|
|
1102
|
-
for (const h of workAll.filter((x) => x.node)) {
|
|
1103
|
-
for (const id of implementedIds(h)) {
|
|
1104
|
-
const hit = decisionById.get(id.slice("record:".length))!;
|
|
1105
|
-
edges.push({ kind: "implements", from: h.node!.id, to: decisionNode(hit.kind, hit.view).id });
|
|
1106
|
-
}
|
|
1107
|
-
for (const n of idList(h.view.data, workSpec(h).needs)) {
|
|
1108
|
-
const hit = workByKey.get(`${h.kind.records!.loaded.kind.name}/${n}`);
|
|
1109
|
-
if (hit) edges.push({ kind: "needs", from: h.node!.id, to: workNode(hit).id });
|
|
1110
|
-
}
|
|
1111
|
-
}
|
|
1112
1363
|
const workWindows = new Map<string, { from: Set<string>; until: Set<string> | null }>();
|
|
1113
1364
|
if (rev) {
|
|
1114
1365
|
for (const h of workCovering) {
|
|
@@ -1125,6 +1376,102 @@ async function walk(query: IntentQuery, head: Head): Promise<IntentResult> {
|
|
|
1125
1376
|
return !!w && w.from.has(sha) && !w.until?.has(sha);
|
|
1126
1377
|
};
|
|
1127
1378
|
|
|
1379
|
+
// The records chant's own trailers name (#3149): a Chant-Record of a kind
|
|
1380
|
+
// read, and the work item a Chant-Lease was taken on. The commit carries
|
|
1381
|
+
// each, and is the own work of a decision it names or that a work item it
|
|
1382
|
+
// names implements.
|
|
1383
|
+
const recordKinds = new Map(kinds.filter((k) => k.records).map((k) => [k.records!.loaded.kind.name, k]));
|
|
1384
|
+
/** The work item a run worked on and the records it names, among the kinds read, deduplicated. */
|
|
1385
|
+
const runTargets = (r: RunView): { node: string; work?: WorkHit; decision?: string }[] => {
|
|
1386
|
+
const out: { node: string; work?: WorkHit; decision?: string }[] = [];
|
|
1387
|
+
const push = (x: { node: string; work?: WorkHit; decision?: string }) => {
|
|
1388
|
+
if (!out.some((o) => o.node === x.node)) out.push(x);
|
|
1389
|
+
};
|
|
1390
|
+
if (r.unit) {
|
|
1391
|
+
const named = r.unit.kind ? workKinds.filter((k) => k.records!.loaded.kind.name === r.unit!.kind) : workKinds;
|
|
1392
|
+
const hits = named.map((k) => workByKey.get(`${k.records!.loaded.kind.name}/${r.unit!.id}`)).filter((h): h is WorkHit => h !== undefined);
|
|
1393
|
+
if (hits.length === 1) push({ node: workNode(hits[0]).id, work: hits[0] });
|
|
1394
|
+
}
|
|
1395
|
+
for (const ref of r.records) {
|
|
1396
|
+
const k = recordKinds.get(ref.kind);
|
|
1397
|
+
if (!k) continue;
|
|
1398
|
+
if (k.records!.loaded.kind.work) {
|
|
1399
|
+
const h = workByKey.get(`${ref.kind}/${ref.id}`);
|
|
1400
|
+
if (h) push({ node: workNode(h).id, work: h });
|
|
1401
|
+
continue;
|
|
1402
|
+
}
|
|
1403
|
+
const hit = decisionById.get(`${ref.kind}/${ref.id}`);
|
|
1404
|
+
if (hit) push({ node: decisionNode(hit.kind, hit.view).id, decision: `${ref.kind}/${ref.id}` });
|
|
1405
|
+
}
|
|
1406
|
+
return out;
|
|
1407
|
+
};
|
|
1408
|
+
for (const t of touched) {
|
|
1409
|
+
const c = nodes.get(`commit:${t.sha}`) as CommitNode | undefined;
|
|
1410
|
+
const j = joined.get(t.sha);
|
|
1411
|
+
if (!c || !j) continue;
|
|
1412
|
+
const carry = (to: string) => {
|
|
1413
|
+
if (j.carries.includes(to)) return;
|
|
1414
|
+
j.carries.push(to);
|
|
1415
|
+
edges.push({ kind: "carries", from: c.id, to });
|
|
1416
|
+
};
|
|
1417
|
+
const carryWork = (h: WorkHit): string => {
|
|
1418
|
+
const node = workNode(h);
|
|
1419
|
+
carry(node.id);
|
|
1420
|
+
j.decisions.push(...implementedIds(h).map((id) => id.slice("record:".length)));
|
|
1421
|
+
return node.id;
|
|
1422
|
+
};
|
|
1423
|
+
for (const r of c.joins.records) {
|
|
1424
|
+
const k = recordKinds.get(r.kind);
|
|
1425
|
+
if (!k) continue;
|
|
1426
|
+
if (k.records!.loaded.kind.work) {
|
|
1427
|
+
const h = workByKey.get(`${r.kind}/${r.id}`);
|
|
1428
|
+
if (h) r.node = carryWork(h);
|
|
1429
|
+
continue;
|
|
1430
|
+
}
|
|
1431
|
+
const hit = decisionById.get(`${r.kind}/${r.id}`);
|
|
1432
|
+
if (!hit) continue;
|
|
1433
|
+
r.node = decisionNode(hit.kind, hit.view).id;
|
|
1434
|
+
carry(r.node);
|
|
1435
|
+
j.decisions.push(`${r.kind}/${r.id}`);
|
|
1436
|
+
}
|
|
1437
|
+
const item = c.joins.lease?.item;
|
|
1438
|
+
if (item) {
|
|
1439
|
+
const hits = workKinds.map((k) => workByKey.get(`${k.records!.loaded.kind.name}/${item}`)).filter((h): h is WorkHit => h !== undefined);
|
|
1440
|
+
if (hits.length === 1) carryWork(hits[0]);
|
|
1441
|
+
}
|
|
1442
|
+
// The runs that made the commit (#3034): its run's work item and the
|
|
1443
|
+
// records the run names are carried as a Chant-Lease and a Chant-Record are.
|
|
1444
|
+
for (const ref of runJoins.refs.get(t.sha) ?? []) {
|
|
1445
|
+
const r = runJoins.runs.get(ref.id);
|
|
1446
|
+
if (!r) continue;
|
|
1447
|
+
for (const target of runTargets(r)) {
|
|
1448
|
+
if (target.work) carryWork(target.work);
|
|
1449
|
+
else if (target.decision) {
|
|
1450
|
+
carry(target.node);
|
|
1451
|
+
j.decisions.push(target.decision);
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
// Each recorded run in the walk to the work item and records it worked on (#3034).
|
|
1457
|
+
for (const n of [...nodes.values()]) {
|
|
1458
|
+
if (n.kind !== "run") continue;
|
|
1459
|
+
const r = runJoins.runs.get(n.run);
|
|
1460
|
+
if (!r) continue;
|
|
1461
|
+
for (const target of runTargets(r)) edges.push({ kind: "worked-on", from: n.id, to: target.node });
|
|
1462
|
+
}
|
|
1463
|
+
// Each work item in the graph, those its commits and runs carry included, to the decisions it implements and the items it needs.
|
|
1464
|
+
for (const h of workAll.filter((x) => x.node)) {
|
|
1465
|
+
for (const id of implementedIds(h)) {
|
|
1466
|
+
const hit = decisionById.get(id.slice("record:".length))!;
|
|
1467
|
+
edges.push({ kind: "implements", from: h.node!.id, to: decisionNode(hit.kind, hit.view).id });
|
|
1468
|
+
}
|
|
1469
|
+
for (const n of idList(h.view.data, workSpec(h).needs)) {
|
|
1470
|
+
const hit = workByKey.get(`${h.kind.records!.loaded.kind.name}/${n}`);
|
|
1471
|
+
if (hit) edges.push({ kind: "needs", from: h.node!.id, to: workNode(hit).id });
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1128
1475
|
const coveredAt = (sha: string, granularities: Granularity[]) =>
|
|
1129
1476
|
covering.filter((c) => {
|
|
1130
1477
|
if (!c.node.constrains.some((x) => granularities.includes(x.granularity))) return false;
|
|
@@ -1133,9 +1480,10 @@ async function walk(query: IntentQuery, head: Head): Promise<IntentResult> {
|
|
|
1133
1480
|
});
|
|
1134
1481
|
const readsRecords = kinds.some((k) => k.records);
|
|
1135
1482
|
// A decision's own work: a commit whose unit, or that unit's contract, names
|
|
1136
|
-
// the decision, or whose unit serves a contract the decision constrains
|
|
1483
|
+
// the decision, or whose unit serves a contract the decision constrains; or
|
|
1484
|
+
// a commit whose own trailers name the decision, or a work item implementing it.
|
|
1137
1485
|
const ownWork = (j: Joined, d: DecisionNode) =>
|
|
1138
|
-
!!j.unit && (j.decisions.some((x) => x === d.record || x === `${d.recordKind}/${d.record}`) || d.constrains.some((x) => x.granularity === "contract" && j.contracts.includes(x.entry)));
|
|
1486
|
+
(!!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)));
|
|
1139
1487
|
for (const t of touched) {
|
|
1140
1488
|
const c = nodes.get(`commit:${t.sha}`) as CommitNode | undefined;
|
|
1141
1489
|
if (!c) continue;
|
|
@@ -1152,8 +1500,8 @@ async function walk(query: IntentQuery, head: Head): Promise<IntentResult> {
|
|
|
1152
1500
|
if (readsRecords && c.state === "undecided") {
|
|
1153
1501
|
find("intent-commit-undecided", `${t.sha.slice(0, 8)} changed the region when no decision constrained ${region.path} by path`, [c.id, rid]);
|
|
1154
1502
|
}
|
|
1155
|
-
if (readsRecords && !j.unit && c.pullRequest === null && coveredAt(t.sha, ["path", "member", "contract", "issue"]).length === 0) {
|
|
1156
|
-
find("intent-commit-bare", `${t.sha.slice(0, 8)} names no unit, no pull request and no decision`, [c.id]);
|
|
1503
|
+
if (readsRecords && !j.unit && j.carries.length === 0 && c.pullRequest === null && coveredAt(t.sha, ["path", "member", "contract", "issue"]).length === 0) {
|
|
1504
|
+
find("intent-commit-bare", `${t.sha.slice(0, 8)} names no unit, no record, no pull request and no decision`, [c.id]);
|
|
1157
1505
|
}
|
|
1158
1506
|
const claims = [...new Set(j.authorship)].filter((key) => hasTrailer(c.trailers, key));
|
|
1159
1507
|
if (claims.length > 0 && c.signature.level !== "attested") {
|
|
@@ -1304,6 +1652,7 @@ async function walk(query: IntentQuery, head: Head): Promise<IntentResult> {
|
|
|
1304
1652
|
}
|
|
1305
1653
|
}
|
|
1306
1654
|
for (const f of findings) nodes.set(f.id, f);
|
|
1655
|
+
const why = explain();
|
|
1307
1656
|
const all = [...nodes.values()];
|
|
1308
1657
|
return {
|
|
1309
1658
|
doc: {
|
|
@@ -1322,7 +1671,121 @@ async function walk(query: IntentQuery, head: Head): Promise<IntentResult> {
|
|
|
1322
1671
|
artifacts: all.filter((n) => n.kind === "artifact").length,
|
|
1323
1672
|
findings: findings.length,
|
|
1324
1673
|
},
|
|
1674
|
+
why,
|
|
1325
1675
|
},
|
|
1326
1676
|
failed: reasons.some((r) => r.code === "intent-plugin-failed"),
|
|
1327
1677
|
};
|
|
1678
|
+
|
|
1679
|
+
/** "Why is it like this" over the region (#3034): who made its current lines, and the decisions governing it, most relevant first. */
|
|
1680
|
+
function explain(): WhyAnswer {
|
|
1681
|
+
const hunksOf = (run: string, sha: string) => runJoins.runs.get(run)?.commits.find((c) => c.sha === sha)?.hunks ?? null;
|
|
1682
|
+
// Each current line: its commit, and the runs that wrote it.
|
|
1683
|
+
const who = blamed.map((b) => {
|
|
1684
|
+
if (b.sha === null) return { line: b.line, sha: null, runs: [] as string[], narrowedBy: null as "hunks" | null };
|
|
1685
|
+
const sha = b.sha;
|
|
1686
|
+
const refs = (runJoins.refs.get(sha) ?? []).map((r) => r.id);
|
|
1687
|
+
const withHunks = refs.filter((id) => hunksOf(id, sha) !== null);
|
|
1688
|
+
if (withHunks.length === 0) return { line: b.line, sha, runs: refs, narrowedBy: null };
|
|
1689
|
+
const wrote = withHunks.filter((id) => hunksOf(id, sha)!.some((h) => h.path === b.origPath && h.start <= b.origLine && b.origLine <= h.end));
|
|
1690
|
+
// A run that recorded its hunks and doesn't cover the line didn't write it; a run that recorded none may have.
|
|
1691
|
+
return { line: b.line, sha, runs: wrote.length > 0 ? wrote : refs.filter((id) => !withHunks.includes(id)), narrowedBy: "hunks" as const };
|
|
1692
|
+
});
|
|
1693
|
+
const spans: WhySpan[] = [];
|
|
1694
|
+
for (const w of who) {
|
|
1695
|
+
const last = spans[spans.length - 1];
|
|
1696
|
+
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")) {
|
|
1697
|
+
last.end = w.line;
|
|
1698
|
+
continue;
|
|
1699
|
+
}
|
|
1700
|
+
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 });
|
|
1701
|
+
}
|
|
1702
|
+
const ranges = (lines: number[]): LineRange[] => {
|
|
1703
|
+
const out: LineRange[] = [];
|
|
1704
|
+
for (const l of lines) {
|
|
1705
|
+
const last = out[out.length - 1];
|
|
1706
|
+
if (last && last.end === l - 1) last.end = l;
|
|
1707
|
+
else out.push({ start: l, end: l });
|
|
1708
|
+
}
|
|
1709
|
+
return out;
|
|
1710
|
+
};
|
|
1711
|
+
|
|
1712
|
+
// The decisions, most relevant first.
|
|
1713
|
+
const decisionNodes = [...nodes.values()].filter((n): n is DecisionNode => n.kind === "decision");
|
|
1714
|
+
const ownLines = (d: DecisionNode) => who.filter((w) => w.sha !== null && ownWork(joined.get(w.sha) ?? { contracts: [], authorship: [], decisions: [], carries: [] }, d)).length;
|
|
1715
|
+
const ORDER: WhyRelevance[] = ["carried", "path", "contract", "issue", "member", "related"];
|
|
1716
|
+
const ranked = decisionNodes.map((d) => {
|
|
1717
|
+
const lines = ownLines(d);
|
|
1718
|
+
const carried = lines > 0 || (fileLines === null && touched.some((t) => ownWork(joined.get(t.sha) ?? { contracts: [], authorship: [], decisions: [], carries: [] }, d)));
|
|
1719
|
+
const granularities = new Set(d.constrains.map((c) => c.granularity));
|
|
1720
|
+
const relevance: WhyRelevance = carried ? "carried" : ((["path", "contract", "issue", "member"] as const).find((g) => granularities.has(g)) ?? "related");
|
|
1721
|
+
// A narrower path constraint is more specific to the region.
|
|
1722
|
+
const specificity = Math.max(0, ...d.constrains.filter((c) => c.granularity === "path").map((c) => c.entry.length));
|
|
1723
|
+
return { d, entry: { decision: d.id, relevance, current: d.supersededBy === null, closed: d.closed, lines } satisfies WhyDecision, specificity };
|
|
1724
|
+
});
|
|
1725
|
+
ranked.sort(
|
|
1726
|
+
(a, b) =>
|
|
1727
|
+
Number(b.entry.current) - Number(a.entry.current) ||
|
|
1728
|
+
ORDER.indexOf(a.entry.relevance) - ORDER.indexOf(b.entry.relevance) ||
|
|
1729
|
+
b.entry.lines - a.entry.lines ||
|
|
1730
|
+
Number(b.entry.closed) - Number(a.entry.closed) ||
|
|
1731
|
+
b.specificity - a.specificity ||
|
|
1732
|
+
(b.d.decidedIn?.date ?? "").localeCompare(a.d.decidedIn?.date ?? "") ||
|
|
1733
|
+
a.d.id.localeCompare(b.d.id),
|
|
1734
|
+
);
|
|
1735
|
+
const decisions = ranked.map((r) => r.entry);
|
|
1736
|
+
|
|
1737
|
+
// The runs that wrote the current lines, most lines first; for a directory, every run in the walk.
|
|
1738
|
+
const runNodes = [...nodes.values()].filter((n): n is RunNode => n.kind === "run");
|
|
1739
|
+
const runEntry = (n: RunNode, lines: number, commits: string[]): WhyRun => {
|
|
1740
|
+
const r = runJoins.runs.get(n.run);
|
|
1741
|
+
const targets = edges.filter((e) => e.kind === "worked-on" && e.from === n.id).map((e) => nodes.get(e.to));
|
|
1742
|
+
const work = r?.unit ? targets.find((t): t is WorkNode => t?.kind === "work" && t.record === r.unit!.id) : undefined;
|
|
1743
|
+
const carried = new Set<string>();
|
|
1744
|
+
for (const t of targets) {
|
|
1745
|
+
if (t?.kind === "decision") carried.add(t.id);
|
|
1746
|
+
if (t?.kind === "work") for (const e of edges) if (e.kind === "implements" && e.from === t.id) carried.add(e.to);
|
|
1747
|
+
}
|
|
1748
|
+
return { run: n.id, lines, commits, unit: r?.unit ? { id: r.unit.id, kind: r.unit.kind, node: work?.id ?? null } : null, decisions: [...carried] };
|
|
1749
|
+
};
|
|
1750
|
+
let runs: WhyRun[];
|
|
1751
|
+
if (fileLines !== null) {
|
|
1752
|
+
const tally = new Map<string, { lines: number; commits: Set<string> }>();
|
|
1753
|
+
for (const w of who) {
|
|
1754
|
+
for (const id of w.runs) {
|
|
1755
|
+
const t = tally.get(id) ?? tally.set(id, { lines: 0, commits: new Set() }).get(id)!;
|
|
1756
|
+
t.lines++;
|
|
1757
|
+
t.commits.add(`commit:${w.sha}`);
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
runs = [...tally]
|
|
1761
|
+
.map(([id, t]) => ({ n: nodes.get(`run:${id}`) as RunNode, t }))
|
|
1762
|
+
.filter((x) => x.n !== undefined)
|
|
1763
|
+
.sort((a, b) => b.t.lines - a.t.lines || (b.n.startedAt ?? "").localeCompare(a.n.startedAt ?? "") || a.n.run.localeCompare(b.n.run))
|
|
1764
|
+
.map((x) => runEntry(x.n, x.t.lines, [...x.t.commits]));
|
|
1765
|
+
} else {
|
|
1766
|
+
runs = runNodes
|
|
1767
|
+
.sort((a, b) => (b.startedAt ?? "").localeCompare(a.startedAt ?? "") || a.run.localeCompare(b.run))
|
|
1768
|
+
.map((n) => runEntry(n, 0, edges.filter((e) => e.kind === "made-by" && e.to === n.id).map((e) => e.from)));
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
const explained = decisions.some((d) => d.current && d.relevance !== "related");
|
|
1772
|
+
const gaps: WhyGap[] = [];
|
|
1773
|
+
if (!explained) {
|
|
1774
|
+
gaps.push({
|
|
1775
|
+
code: "intent-why-no-decision",
|
|
1776
|
+
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}`,
|
|
1777
|
+
});
|
|
1778
|
+
}
|
|
1779
|
+
const committed = who.filter((w) => w.sha !== null);
|
|
1780
|
+
if (fileLines !== null ? committed.length > 0 && committed.every((w) => w.runs.length === 0) : touched.length > 0 && runNodes.length === 0) {
|
|
1781
|
+
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}` });
|
|
1782
|
+
}
|
|
1783
|
+
const uncommitted = who.filter((w) => w.sha === null).map((w) => w.line);
|
|
1784
|
+
if (uncommitted.length > 0) gaps.push({ code: "intent-why-uncommitted", message: `${uncommitted.length} ${uncommitted.length === 1 ? "line is" : "lines are"} not committed yet`, lines: ranges(uncommitted) });
|
|
1785
|
+
const ambiguous = who.filter((w) => w.runs.length > 1).map((w) => w.line);
|
|
1786
|
+
if (ambiguous.length > 0) {
|
|
1787
|
+
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) });
|
|
1788
|
+
}
|
|
1789
|
+
return { lines: fileLines, blame: spans, decisions, runs, explained, gaps };
|
|
1790
|
+
}
|
|
1328
1791
|
}
|