@intentius/chant 0.102.0 → 0.104.1
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/audit/core.d.ts +2 -0
- package/dist/audit/core.d.ts.map +1 -1
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/change-set.d.ts +232 -0
- package/dist/change-set.d.ts.map +1 -0
- package/dist/cli/commands/audit.d.ts +7 -0
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/change-set.d.ts +11 -0
- package/dist/cli/handlers/change-set.d.ts.map +1 -0
- package/dist/cli/handlers/fan-out.d.ts +33 -0
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +7 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -1
- package/dist/cli/handlers/pr.d.ts +31 -0
- package/dist/cli/handlers/pr.d.ts.map +1 -0
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/op-tools.d.ts.map +1 -1
- package/dist/cli/mcp/workspace-tools.d.ts +3 -2
- package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +63 -1
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/components/auto-release.d.ts +16 -0
- package/dist/components/auto-release.d.ts.map +1 -1
- package/dist/components/capability.d.ts +53 -0
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/changed-files.d.ts +31 -0
- package/dist/components/changed-files.d.ts.map +1 -0
- package/dist/components/cli-support.d.ts +9 -1
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/component.d.ts +24 -0
- package/dist/components/component.d.ts.map +1 -1
- package/dist/components/deploy-units.d.ts.map +1 -1
- package/dist/components/driver.d.ts +13 -0
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/fan-out-output.d.ts +6 -0
- package/dist/components/fan-out-output.d.ts.map +1 -1
- package/dist/components/fan-out-record.d.ts +41 -0
- package/dist/components/fan-out-record.d.ts.map +1 -0
- package/dist/components/fan-out-run.d.ts +65 -0
- package/dist/components/fan-out-run.d.ts.map +1 -1
- package/dist/components/fan-out-support.d.ts +2 -0
- package/dist/components/fan-out-support.d.ts.map +1 -1
- package/dist/components/fan-out.d.ts +6 -0
- package/dist/components/fan-out.d.ts.map +1 -1
- package/dist/components/gated-wave-pipeline.d.ts +50 -0
- package/dist/components/gated-wave-pipeline.d.ts.map +1 -0
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/layers.d.ts +25 -0
- package/dist/components/layers.d.ts.map +1 -0
- package/dist/components/pr-pipeline.d.ts +61 -0
- package/dist/components/pr-pipeline.d.ts.map +1 -0
- package/dist/components/pr-run.d.ts +119 -0
- package/dist/components/pr-run.d.ts.map +1 -0
- package/dist/components/promote.d.ts.map +1 -1
- package/dist/components/verbs/shell.d.ts.map +1 -1
- package/dist/components/wave-plan.d.ts +49 -0
- package/dist/components/wave-plan.d.ts.map +1 -0
- package/dist/gated-waves.d.ts +147 -0
- package/dist/gated-waves.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/lexicon.d.ts +48 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts +7 -0
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/git.d.ts +46 -1
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lint/post-synth.d.ts +18 -0
- package/dist/lint/post-synth.d.ts.map +1 -1
- package/dist/op/activities/activity-contracts.d.ts +126 -0
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/change-set.d.ts +62 -0
- package/dist/op/activities/change-set.d.ts.map +1 -0
- package/dist/op/activities/decide.d.ts +2 -0
- package/dist/op/activities/decide.d.ts.map +1 -1
- package/dist/op/activities/factory.d.ts +137 -0
- package/dist/op/activities/factory.d.ts.map +1 -0
- package/dist/op/activities/index.d.ts +4 -0
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activity-contract.d.ts.map +1 -1
- package/dist/op/builders.d.ts +11 -0
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/decide-backend.d.ts +5 -1
- package/dist/op/decide-backend.d.ts.map +1 -1
- package/dist/op/decide-read-inputs.d.ts +4 -2
- package/dist/op/decide-read-inputs.d.ts.map +1 -1
- package/dist/op/factory.d.ts +83 -0
- package/dist/op/factory.d.ts.map +1 -0
- package/dist/op/index.d.ts +4 -1
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/op-ir.d.ts +2 -0
- package/dist/op/op-ir.d.ts.map +1 -1
- package/dist/op/op-verb-class.d.ts.map +1 -1
- package/dist/op/types.d.ts +8 -0
- package/dist/op/types.d.ts.map +1 -1
- package/dist/op/work-lease-run.d.ts.map +1 -1
- package/dist/plan-summary.d.ts +207 -0
- package/dist/plan-summary.d.ts.map +1 -0
- package/dist/pr-forge.d.ts +105 -0
- package/dist/pr-forge.d.ts.map +1 -0
- package/dist/pr-loop.d.ts +178 -0
- package/dist/pr-loop.d.ts.map +1 -0
- package/dist/telemetry-attribution.d.ts +31 -0
- package/dist/telemetry-attribution.d.ts.map +1 -1
- package/dist/terraform/hcl2json.d.ts +45 -0
- package/dist/terraform/hcl2json.d.ts.map +1 -0
- package/dist/terraform/parse.d.ts +4 -40
- package/dist/terraform/parse.d.ts.map +1 -1
- package/dist/vendor/js-yaml-load.d.ts +56 -0
- package/dist/vendor/js-yaml-load.d.ts.map +1 -0
- package/dist/workspace/box-cli.d.ts +12 -0
- package/dist/workspace/box-cli.d.ts.map +1 -0
- package/dist/workspace/box-factory.d.ts +27 -3
- package/dist/workspace/box-factory.d.ts.map +1 -1
- package/dist/workspace/box-listing.d.ts +85 -0
- package/dist/workspace/box-listing.d.ts.map +1 -0
- package/dist/workspace/box-publish.d.ts +147 -0
- package/dist/workspace/box-publish.d.ts.map +1 -0
- package/dist/workspace/broker-protocol.d.ts +190 -0
- package/dist/workspace/broker-protocol.d.ts.map +1 -0
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/composites.d.ts +2 -2
- package/dist/workspace/composites.d.ts.map +1 -1
- package/dist/workspace/conformance/__fixtures__/reference-broker.d.ts +17 -0
- package/dist/workspace/conformance/__fixtures__/reference-broker.d.ts.map +1 -0
- package/dist/workspace/conformance/broker.d.ts +202 -0
- package/dist/workspace/conformance/broker.d.ts.map +1 -0
- package/dist/workspace/conformance/index.d.ts +2 -1
- package/dist/workspace/conformance/index.d.ts.map +1 -1
- package/dist/workspace/conformance/vitest.d.ts +9 -2
- package/dist/workspace/conformance/vitest.d.ts.map +1 -1
- package/dist/workspace/conformance/writer.d.ts +108 -14
- package/dist/workspace/conformance/writer.d.ts.map +1 -1
- package/dist/workspace/decide.d.ts +126 -12
- package/dist/workspace/decide.d.ts.map +1 -1
- package/dist/workspace/decision-points.schema.json +11 -4
- package/dist/workspace/declaration.d.ts +65 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.schema.json +153 -4
- package/dist/workspace/environments.d.ts +36 -10
- package/dist/workspace/environments.d.ts.map +1 -1
- package/dist/workspace/factory-rules.d.ts +180 -0
- package/dist/workspace/factory-rules.d.ts.map +1 -0
- package/dist/workspace/init.d.ts +21 -0
- package/dist/workspace/init.d.ts.map +1 -1
- package/dist/workspace/intent-cli.d.ts +1 -1
- package/dist/workspace/intent-cli.d.ts.map +1 -1
- package/dist/workspace/intent-record.d.ts +28 -1
- package/dist/workspace/intent-record.d.ts.map +1 -1
- package/dist/workspace/intent.d.ts +102 -5
- package/dist/workspace/intent.d.ts.map +1 -1
- package/dist/workspace/json-edit.d.ts +24 -0
- package/dist/workspace/json-edit.d.ts.map +1 -0
- package/dist/workspace/jsonc.d.ts +11 -0
- package/dist/workspace/jsonc.d.ts.map +1 -1
- package/dist/workspace/kinds.d.ts +48 -0
- package/dist/workspace/kinds.d.ts.map +1 -1
- package/dist/workspace/lexicon-symbols.d.ts +29 -0
- package/dist/workspace/lexicon-symbols.d.ts.map +1 -0
- package/dist/workspace/lineage-upgrade.d.ts +31 -2
- package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
- package/dist/workspace/point-answer.schema.json +42 -2
- package/dist/workspace/points-cli.d.ts +10 -3
- package/dist/workspace/points-cli.d.ts.map +1 -1
- package/dist/workspace/points.d.ts +66 -5
- package/dist/workspace/points.d.ts.map +1 -1
- package/dist/workspace/reason-codes.d.ts +33 -4
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/record-sessions.d.ts +5 -7
- package/dist/workspace/record-sessions.d.ts.map +1 -1
- package/dist/workspace/records-cli.d.ts +7 -0
- package/dist/workspace/records-cli.d.ts.map +1 -1
- package/dist/workspace/records-close.d.ts +7 -3
- package/dist/workspace/records-close.d.ts.map +1 -1
- package/dist/workspace/records-write.d.ts +66 -6
- package/dist/workspace/records-write.d.ts.map +1 -1
- package/dist/workspace/records.d.ts +28 -9
- package/dist/workspace/records.d.ts.map +1 -1
- package/dist/workspace/runs-cli.d.ts +138 -6
- package/dist/workspace/runs-cli.d.ts.map +1 -1
- package/dist/workspace/runs.d.ts +132 -11
- package/dist/workspace/runs.d.ts.map +1 -1
- package/dist/workspace/session-kinds.d.ts +1 -1
- package/dist/workspace/session-kinds.d.ts.map +1 -1
- package/dist/workspace/squash.d.ts +80 -0
- package/dist/workspace/squash.d.ts.map +1 -0
- package/dist/workspace/status-gates.d.ts +2 -0
- package/dist/workspace/status-gates.d.ts.map +1 -1
- package/dist/workspace/status.d.ts +54 -0
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/symbols.d.ts +16 -5
- package/dist/workspace/symbols.d.ts.map +1 -1
- package/dist/workspace/trust/run-commits.d.ts +46 -0
- package/dist/workspace/trust/run-commits.d.ts.map +1 -0
- package/dist/workspace/trust/run-statement.d.ts +128 -0
- package/dist/workspace/trust/run-statement.d.ts.map +1 -0
- package/dist/workspace/trust/seal.d.ts +6 -1
- package/dist/workspace/trust/seal.d.ts.map +1 -1
- package/dist/workspace/trust/verify-cli.d.ts +1 -1
- package/dist/workspace/trust/verify.d.ts +16 -2
- package/dist/workspace/trust/verify.d.ts.map +1 -1
- package/dist/workspace/wip-cli.d.ts +21 -0
- package/dist/workspace/wip-cli.d.ts.map +1 -0
- package/dist/workspace/wip.d.ts +268 -0
- package/dist/workspace/wip.d.ts.map +1 -0
- 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-size.d.ts +55 -0
- package/dist/workspace/work-size.d.ts.map +1 -0
- package/dist/workspace/workspace-kinds.schema.json +122 -0
- package/dist/workspace/write-lock-cli.d.ts +60 -0
- package/dist/workspace/write-lock-cli.d.ts.map +1 -0
- package/dist/workspace/write-lock.d.ts +152 -0
- package/dist/workspace/write-lock.d.ts.map +1 -0
- package/dist/workspace/write-scope.d.ts +6 -2
- package/dist/workspace/write-scope.d.ts.map +1 -1
- package/dist/yaml.d.ts +1 -1
- package/dist/yaml.d.ts.map +1 -1
- package/package.json +6 -1
- package/src/__fixtures__/change-set/lifecycle-plan.floci.json +59 -0
- package/src/__fixtures__/change-set/lifecycle-v1.ts.txt +4 -0
- package/src/__fixtures__/change-set/lifecycle-v2.ts.txt +4 -0
- package/src/__fixtures__/change-set/lifecycle.change-set.golden.json +95 -0
- package/src/__fixtures__/change-set/warden.change-set.golden.json +106 -0
- package/src/__fixtures__/change-set/warden.plan.json +44 -0
- package/src/__fixtures__/plan-summary/normalization-vectors.json +1593 -0
- package/src/__fixtures__/pr-loop/pr-apply-applied.note.golden.md +31 -0
- package/src/__fixtures__/pr-loop/pr-apply-refused.note.golden.md +31 -0
- package/src/__fixtures__/pr-loop/pr-plan.golden.json +127 -0
- package/src/__fixtures__/pr-loop/pr-plan.note.golden.md +37 -0
- package/src/audit/core.test.ts +15 -0
- package/src/audit/core.ts +8 -2
- package/src/audit/discover.test.ts +11 -0
- package/src/audit/discover.ts +7 -2
- package/src/change-set-bundle.test.ts +57 -0
- package/src/change-set.test.ts +215 -0
- package/src/change-set.ts +445 -0
- package/src/cli/commands/audit-opt-in.test.ts +54 -0
- package/src/cli/commands/audit.ts +29 -2
- package/src/cli/commands/build.test.ts +19 -0
- package/src/cli/commands/build.ts +3 -2
- package/src/cli/handlers/build.ts +13 -1
- package/src/cli/handlers/change-set.test.ts +56 -0
- package/src/cli/handlers/change-set.ts +44 -0
- package/src/cli/handlers/fan-out.test.ts +221 -2
- package/src/cli/handlers/fan-out.ts +165 -75
- package/src/cli/handlers/graph.ts +3 -0
- package/src/cli/handlers/operator.test.ts +21 -0
- package/src/cli/handlers/operator.ts +34 -3
- package/src/cli/handlers/pr.ts +373 -0
- package/src/cli/handlers/run.ts +1 -0
- package/src/cli/main.test.ts +20 -0
- package/src/cli/main.ts +185 -23
- package/src/cli/mcp/never-over-mcp.test.ts +101 -0
- package/src/cli/mcp/op-tools.ts +24 -2
- package/src/cli/mcp/workspace-tools.test.ts +1 -1
- package/src/cli/mcp/workspace-tools.ts +34 -6
- package/src/cli/registry.ts +63 -1
- package/src/components/auto-release.ts +1 -1
- package/src/components/capability.ts +55 -0
- package/src/components/changed-files.ts +66 -0
- package/src/components/cli-support.ts +28 -5
- package/src/components/component.schema.json +17 -0
- package/src/components/component.ts +25 -0
- package/src/components/deploy-units.ts +3 -0
- package/src/components/driver.test.ts +52 -0
- package/src/components/driver.ts +31 -13
- package/src/components/fan-out-output.ts +42 -1
- package/src/components/fan-out-record.ts +69 -0
- package/src/components/fan-out-run.test.ts +17 -0
- package/src/components/fan-out-run.ts +304 -1
- package/src/components/fan-out-support.ts +3 -0
- package/src/components/fan-out-waves.test.ts +375 -0
- package/src/components/fan-out.ts +18 -17
- package/src/components/gated-wave-pipeline.ts +82 -0
- package/src/components/index.ts +1 -0
- package/src/components/layers.ts +30 -0
- package/src/components/pr-pipeline.ts +91 -0
- package/src/components/pr-run.test.ts +205 -0
- package/src/components/pr-run.ts +280 -0
- package/src/components/promote.ts +5 -0
- package/src/components/verbs/shell.test.ts +11 -0
- package/src/components/verbs/shell.ts +7 -3
- package/src/components/wave-plan.ts +124 -0
- package/src/discovery/sandbox/bundle.ts +1 -1
- package/src/gated-waves-bundle.test.ts +62 -0
- package/src/gated-waves.test.ts +118 -0
- package/src/gated-waves.ts +240 -0
- package/src/index.ts +1 -0
- package/src/lexicon.ts +51 -0
- package/src/lifecycle/gate-ledger.ts +7 -0
- package/src/lifecycle/git.ts +102 -16
- package/src/lint/post-synth.test.ts +19 -0
- package/src/lint/post-synth.ts +19 -1
- package/src/meta/declared-imports.test.ts +1 -1
- package/src/op/activities/activity-contracts.ts +84 -0
- package/src/op/activities/change-set.ts +114 -0
- package/src/op/activities/decide.ts +3 -0
- package/src/op/activities/factory.ts +675 -0
- package/src/op/activities/index.ts +6 -0
- package/src/op/activity-contract.test.ts +21 -1
- package/src/op/activity-contract.ts +19 -9
- package/src/op/builders.ts +23 -0
- package/src/op/decide-backend.ts +5 -1
- package/src/op/decide-read-inputs.ts +33 -5
- package/src/op/factory.e2e.test.ts +232 -0
- package/src/op/factory.ts +118 -0
- package/src/op/index.ts +9 -1
- package/src/op/op-ir.test.ts +9 -0
- package/src/op/op-ir.ts +4 -0
- package/src/op/op-verb-class.ts +3 -0
- package/src/op/types.ts +8 -0
- package/src/op/work-lease-run.ts +3 -0
- package/src/plan-summary.test.ts +263 -0
- package/src/plan-summary.ts +845 -0
- package/src/pr-forge.test.ts +237 -0
- package/src/pr-forge.ts +332 -0
- package/src/pr-loop-bundle.test.ts +46 -0
- package/src/pr-loop.test.ts +171 -0
- package/src/pr-loop.ts +324 -0
- package/src/telemetry-attribution.test.ts +23 -1
- package/src/telemetry-attribution.ts +51 -0
- package/src/terraform/hcl2json.test.ts +21 -0
- package/src/terraform/hcl2json.ts +81 -0
- package/src/terraform/parse.ts +11 -74
- package/src/vendor/js-yaml-load.test.ts +115 -0
- package/src/vendor/js-yaml-load.ts +1939 -0
- package/src/workspace/__fixtures__/studio-template-84c8b21.json +1 -1
- package/src/workspace/app-kind.test.ts +196 -0
- package/src/workspace/box-cli.ts +113 -0
- package/src/workspace/box-factory.test.ts +4 -1
- package/src/workspace/box-factory.ts +37 -4
- package/src/workspace/box-listing-write.schema.json +204 -0
- package/src/workspace/box-listing.test.ts +234 -0
- package/src/workspace/box-listing.ts +324 -0
- package/src/workspace/box-publish.schema.json +498 -0
- package/src/workspace/box-publish.test.ts +204 -0
- package/src/workspace/box-publish.ts +420 -0
- package/src/workspace/box-ship.test.ts +96 -0
- package/src/workspace/broker-protocol.schema.json +186 -0
- package/src/workspace/broker-protocol.test.ts +122 -0
- package/src/workspace/broker-protocol.ts +317 -0
- package/src/workspace/builder-tiers.test.ts +146 -0
- package/src/workspace/change-set.schema.json +460 -0
- package/src/workspace/chant-migrations.e2e.test.ts +17 -5
- package/src/workspace/checks/boxes.test.ts +3 -0
- package/src/workspace/checks.ts +10 -3
- package/src/workspace/component-environments.test.ts +105 -0
- package/src/workspace/composites.schema.json +23 -4
- package/src/workspace/composites.test.ts +8 -8
- package/src/workspace/composites.ts +13 -2
- package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +2 -2
- package/src/workspace/conformance/__fixtures__/reference-broker.ts +207 -0
- package/src/workspace/conformance/__writer_fixture__/answers/answer.kind.mjs +7 -2
- package/src/workspace/conformance/__writer_fixture__/answers/answer.schema.json +42 -2
- package/src/workspace/conformance/__writer_fixture__/contracts/contract.kind.mjs +37 -0
- package/src/workspace/conformance/__writer_fixture__/contracts/contract.schema.json +213 -0
- package/src/workspace/conformance/__writer_fixture__/decisions/points.json +96 -0
- package/src/workspace/conformance/__writer_fixture__/drivers/driver.kind.mjs +15 -0
- package/src/workspace/conformance/__writer_fixture__/drivers/driver.schema.json +38 -0
- package/src/workspace/conformance/__writer_fixture__/sessions/session.kind.mjs +14 -8
- package/src/workspace/conformance/__writer_fixture__/sessions/session.schema.json +187 -6
- package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +25 -0
- package/src/workspace/conformance/broker.test.ts +122 -0
- package/src/workspace/conformance/broker.ts +792 -0
- package/src/workspace/conformance/conformance.e2e.test.ts +2 -1
- package/src/workspace/conformance/index.mjs +22 -1
- package/src/workspace/conformance/index.ts +5 -1
- package/src/workspace/conformance/vitest.ts +33 -1
- package/src/workspace/conformance/writer-conformance.e2e.test.ts +49 -3
- package/src/workspace/conformance/writer.test.ts +32 -2
- package/src/workspace/conformance/writer.ts +339 -28
- package/src/workspace/decide.test.ts +208 -4
- package/src/workspace/decide.ts +427 -73
- package/src/workspace/decision-points.schema.json +11 -4
- package/src/workspace/declaration.schema.json +153 -4
- package/src/workspace/declaration.ts +141 -0
- package/src/workspace/environments.ts +66 -14
- package/src/workspace/factory-rules.test.ts +62 -0
- package/src/workspace/factory-rules.ts +218 -0
- package/src/workspace/graph-cli.ts +2 -2
- package/src/workspace/graph.schema.json +67 -0
- package/src/workspace/identity.test.ts +23 -0
- package/src/workspace/init.ts +82 -2
- package/src/workspace/intent-cli.ts +26 -9
- package/src/workspace/intent-patch-id.test.ts +162 -0
- package/src/workspace/intent-record.schema.json +130 -3
- package/src/workspace/intent-record.ts +74 -28
- package/src/workspace/intent-reviews.test.ts +76 -0
- package/src/workspace/intent-squash.test.ts +213 -0
- package/src/workspace/intent-why.test.ts +5 -5
- package/src/workspace/intent.schema.json +234 -4
- package/src/workspace/intent.ts +260 -66
- package/src/workspace/json-edit.test.ts +121 -0
- package/src/workspace/json-edit.ts +142 -0
- package/src/workspace/jsonc.ts +20 -0
- package/src/workspace/kinds.ts +157 -2
- package/src/workspace/lexicon-symbols.test.ts +148 -0
- package/src/workspace/lexicon-symbols.ts +125 -0
- package/src/workspace/lineage-upgrade.test.ts +63 -0
- package/src/workspace/lineage-upgrade.ts +146 -5
- package/src/workspace/plan-summary.schema.json +155 -0
- package/src/workspace/point-answer.schema.json +42 -2
- package/src/workspace/points-cli.ts +42 -16
- package/src/workspace/points-write.schema.json +162 -4
- package/src/workspace/points.schema.json +157 -2
- package/src/workspace/points.test.ts +13 -1
- package/src/workspace/points.ts +118 -12
- package/src/workspace/pr-report.schema.json +95 -0
- package/src/workspace/profiles.test.ts +159 -0
- package/src/workspace/read-contract.test.ts +2 -2
- package/src/workspace/reason-codes.test.ts +13 -1
- package/src/workspace/reason-codes.ts +38 -4
- package/src/workspace/record-sessions.ts +9 -8
- package/src/workspace/records-amend.schema.json +78 -0
- package/src/workspace/records-cli.ts +20 -6
- package/src/workspace/records-close.schema.json +78 -0
- package/src/workspace/records-close.ts +31 -7
- package/src/workspace/records-concurrent.e2e.test.ts +135 -0
- package/src/workspace/records-concurrent.test.ts +145 -0
- package/src/workspace/records-formats.test.ts +46 -10
- package/src/workspace/records-new.schema.json +7 -0
- package/src/workspace/records-review.schema.json +78 -0
- package/src/workspace/records-sessions-write.test.ts +69 -0
- package/src/workspace/records-sessions.test.ts +22 -0
- package/src/workspace/records-write.ts +192 -25
- package/src/workspace/records.schema.json +16 -0
- package/src/workspace/records.ts +50 -9
- package/src/workspace/reference-kinds/app/package.json +10 -0
- package/src/workspace/reference-kinds/app/workspace-kinds.json +45 -0
- package/src/workspace/run-statement.schema.json +622 -0
- package/src/workspace/runs-cli.ts +343 -14
- package/src/workspace/runs-statement.test.ts +239 -0
- package/src/workspace/runs-write.schema.json +416 -5
- package/src/workspace/runs.schema.json +451 -4
- package/src/workspace/runs.test.ts +39 -0
- package/src/workspace/runs.ts +296 -35
- package/src/workspace/session-kinds.ts +3 -3
- package/src/workspace/squash.ts +198 -0
- package/src/workspace/status-contract.test.ts +5 -2
- package/src/workspace/status-gates.ts +3 -1
- package/src/workspace/status.schema.json +126 -3
- package/src/workspace/status.ts +90 -1
- package/src/workspace/symbols.ts +20 -9
- package/src/workspace/trust/run-commits.ts +115 -0
- package/src/workspace/trust/run-statement.ts +226 -0
- package/src/workspace/trust/seal.ts +8 -2
- package/src/workspace/trust/verify-cli.ts +6 -5
- package/src/workspace/trust/verify.ts +39 -6
- package/src/workspace/wip-cli.ts +97 -0
- package/src/workspace/wip-write.schema.json +504 -0
- package/src/workspace/wip.schema.json +334 -0
- package/src/workspace/wip.test.ts +294 -0
- package/src/workspace/wip.ts +753 -0
- package/src/workspace/work-cli.ts +3 -0
- package/src/workspace/work-evidence.schema.json +3 -0
- package/src/workspace/work-size.test.ts +77 -0
- package/src/workspace/work-size.ts +93 -0
- package/src/workspace/workspace-kinds.schema.json +122 -0
- package/src/workspace/write-lock-cli.ts +131 -0
- package/src/workspace/write-lock.schema.json +172 -0
- package/src/workspace/write-lock.test.ts +189 -0
- package/src/workspace/write-lock.ts +474 -0
- package/src/workspace/write-scope.test.ts +16 -1
- package/src/workspace/write-scope.ts +33 -11
- package/src/yaml.ts +31 -21
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Joining a commit to its agent run by patch-id when the trailer is gone (#3036).
|
|
3
|
+
*
|
|
4
|
+
* - app/server.ts; s-001 (path:app/server.ts) is decided and W-001 implements it.
|
|
5
|
+
* - On a side branch, run A (on W-001) records c1, which carries no trailer.
|
|
6
|
+
* main cherry-picks it as p1: a new sha, the same patch-id.
|
|
7
|
+
* - Run B records b1 on the side branch; main then commits the same change
|
|
8
|
+
* as p2 with `Chant-Run: run-c`: a trailer join, and content that matches
|
|
9
|
+
* another run's recorded commit.
|
|
10
|
+
* - Run D records d1 and d2 on the side branch; main squashes them into one
|
|
11
|
+
* commit, sq, whose patch-id is neither's.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { readFileSync } from "node:fs";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
17
|
+
import { cleanScratch, contract, git, REPO, repo, writeFiles } from "./__fixtures__/contract-repo";
|
|
18
|
+
import { intentGraph, type IntentDocument } from "./intent";
|
|
19
|
+
import { formatIntent } from "./intent-cli";
|
|
20
|
+
import { intentRecord } from "./intent-record";
|
|
21
|
+
import intentRecordSchema from "./intent-record.schema.json";
|
|
22
|
+
import intentSchema from "./intent.schema.json";
|
|
23
|
+
import { parseFrontMatter } from "./records";
|
|
24
|
+
import { runsWrite, workspaceRuns } from "./runs-cli";
|
|
25
|
+
import runsSchema from "./runs.schema.json";
|
|
26
|
+
|
|
27
|
+
const REF = join(REPO, "reference-workspace");
|
|
28
|
+
const BASE = (() => {
|
|
29
|
+
const fm = parseFrontMatter(readFileSync(join(REF, "decisions", "ref-001-how-the-app-is-deployed.md"), "utf-8"));
|
|
30
|
+
if (!fm.ok) throw new Error(fm.message);
|
|
31
|
+
return fm.value;
|
|
32
|
+
})();
|
|
33
|
+
const decision = (id: string, fields: Record<string, unknown>) => `---\n${JSON.stringify({ ...BASE, id, title: `Decision ${id}`, state: "decided", supersedes: [], evidence: [], reviews: [], ...fields }, null, 2)}\n---\n\n# ${id}\n`;
|
|
34
|
+
const work = (id: string, fields: Record<string, unknown>) =>
|
|
35
|
+
`---\n${JSON.stringify({ schema: 1, id, title: `Work ${id}`, state: "open", implements: [], needs: [], constrains: [], evidence: [], opened_on: "2026-10-01", supersedes: [], ...fields }, null, 2)}\n---\n\n# ${id}\n\nWhat the work is.\n`;
|
|
36
|
+
|
|
37
|
+
const LINES = ["// The app server.", "export const a = 0;", "export const b = 0;", "export const c = 0;", "export const d = 0;", "export const e = 0;"];
|
|
38
|
+
const text = (lines: string[]) => `${lines.join("\n")}\n`;
|
|
39
|
+
const edit = (lines: string[], at: number, to: string) => lines.map((l, i) => (i === at - 1 ? to : l));
|
|
40
|
+
|
|
41
|
+
type Doc = Exclude<IntentDocument, { error: unknown }>;
|
|
42
|
+
|
|
43
|
+
let root: string;
|
|
44
|
+
const sha: Record<string, string> = {};
|
|
45
|
+
|
|
46
|
+
function commit(message: string[]): string {
|
|
47
|
+
git(root, "add", "-A");
|
|
48
|
+
git(root, "commit", "-q", ...message.flatMap((m) => ["-m", m]));
|
|
49
|
+
return git(root, "rev-parse", "HEAD");
|
|
50
|
+
}
|
|
51
|
+
async function record(fields: object): Promise<void> {
|
|
52
|
+
const doc = await runsWrite({ verb: "record", fields: JSON.stringify(fields), cwd: root });
|
|
53
|
+
if ("error" in doc) throw new Error(`${doc.error.code}: ${doc.error.message}`);
|
|
54
|
+
}
|
|
55
|
+
async function walk(region: string): Promise<Doc> {
|
|
56
|
+
const { doc } = await intentGraph({ cwd: root, region });
|
|
57
|
+
contract(intentSchema).expectValid(doc);
|
|
58
|
+
if ("error" in doc) throw new Error(`${doc.error.code}: ${doc.error.message}`);
|
|
59
|
+
return doc;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
beforeAll(async () => {
|
|
63
|
+
root = repo({
|
|
64
|
+
"chant.workspace.json": JSON.stringify({ name: "patch-id", schema: 1, records: [{ kind: "decisions/decision.kind.mjs" }, { kind: "work/work.kind.mjs" }], members: [{ name: "app", dir: "app", kind: "other", because: "the app" }] }),
|
|
65
|
+
"decisions/decision.kind.mjs": readFileSync(join(REF, "decisions", "decision.kind.mjs"), "utf-8"),
|
|
66
|
+
"decisions/decision.schema.json": readFileSync(join(REF, "decisions", "decision.schema.json"), "utf-8"),
|
|
67
|
+
"work/work.kind.mjs": readFileSync(join(REF, "work", "work.kind.mjs"), "utf-8"),
|
|
68
|
+
"work/work.schema.json": readFileSync(join(REF, "work", "work.schema.json"), "utf-8"),
|
|
69
|
+
"app/server.ts": text(LINES),
|
|
70
|
+
"decisions/s-001-server.md": decision("s-001", { constrains: ["path:app/server.ts"] }),
|
|
71
|
+
"work/W-001-server.md": work("W-001", { implements: ["s-001"] }),
|
|
72
|
+
});
|
|
73
|
+
sha.c0 = commit(["the workspace"]);
|
|
74
|
+
const main = git(root, "rev-parse", "--abbrev-ref", "HEAD");
|
|
75
|
+
|
|
76
|
+
// The side branch, where the runs made their commits.
|
|
77
|
+
git(root, "checkout", "-q", "-b", "side");
|
|
78
|
+
writeFiles(root, { "app/server.ts": text(edit(LINES, 2, "export const a = 1;")) });
|
|
79
|
+
sha.c1 = commit(["a is 1"]);
|
|
80
|
+
writeFiles(root, { "app/server.ts": text(edit(edit(LINES, 2, "export const a = 1;"), 3, "export const b = 1;")) });
|
|
81
|
+
sha.b1 = commit(["b is 1"]);
|
|
82
|
+
git(root, "checkout", "-q", "-b", "squashed", sha.c0);
|
|
83
|
+
writeFiles(root, { "app/server.ts": text(edit(LINES, 5, "export const d = 1;")) });
|
|
84
|
+
sha.d1 = commit(["d is 1"]);
|
|
85
|
+
writeFiles(root, { "app/server.ts": text(edit(edit(LINES, 5, "export const d = 1;"), 6, "export const e = 1;")) });
|
|
86
|
+
sha.d2 = commit(["e is 1"]);
|
|
87
|
+
git(root, "checkout", "-q", main);
|
|
88
|
+
|
|
89
|
+
await record({ id: "run-a", harness: "claude-code", model: "claude-opus-5-5", unit: "W-001", startedAt: "2026-10-01T10:00:00Z", commits: [{ sha: sha.c1, hunks: [{ path: "app/server.ts", start: 2, end: 2 }] }] });
|
|
90
|
+
await record({ id: "run-b", harness: "claude-code", startedAt: "2026-10-01T11:00:00Z", commits: [sha.b1] });
|
|
91
|
+
await record({ id: "run-c", harness: "other", startedAt: "2026-10-01T11:30:00Z" });
|
|
92
|
+
await record({ id: "run-d", harness: "claude-code", startedAt: "2026-10-01T12:00:00Z", commits: [sha.d1, sha.d2] });
|
|
93
|
+
|
|
94
|
+
// main: a cherry-pick that keeps no trailer, a commit whose trailer names another run, and a squash.
|
|
95
|
+
git(root, "cherry-pick", sha.c1);
|
|
96
|
+
sha.p1 = git(root, "rev-parse", "HEAD");
|
|
97
|
+
writeFiles(root, { "app/server.ts": text(edit(edit(LINES, 2, "export const a = 1;"), 3, "export const b = 1;")) });
|
|
98
|
+
sha.p2 = commit(["b is 1, again", "Chant-Run: run-c"]);
|
|
99
|
+
git(root, "merge", "--squash", "-q", "squashed");
|
|
100
|
+
sha.sq = commit(["d and e (#7)"]);
|
|
101
|
+
}, 60_000);
|
|
102
|
+
afterAll(cleanScratch);
|
|
103
|
+
|
|
104
|
+
describe("graph --intent joins by patch-id when the trailer is gone", () => {
|
|
105
|
+
test("a cherry-pick of a recorded commit joins its run by content", async () => {
|
|
106
|
+
const doc = await walk("app/server.ts:2");
|
|
107
|
+
expect(sha.p1).not.toBe(sha.c1);
|
|
108
|
+
expect(doc.edges.filter((e) => e.kind === "made-by" && e.from === `commit:${sha.p1}`)).toEqual([{ kind: "made-by", from: `commit:${sha.p1}`, to: "run:run-a", joinedBy: ["patch-id"], recordedAs: sha.c1 }]);
|
|
109
|
+
// The run's work item is carried as by any join, so the commit is s-001's own work.
|
|
110
|
+
expect(doc.edges).toContainEqual({ kind: "carries", from: `commit:${sha.p1}`, to: "record:work/W-001" });
|
|
111
|
+
expect(doc.edges).toContainEqual({ kind: "within", from: `commit:${sha.p1}`, to: "record:decision/s-001", state: "decided" });
|
|
112
|
+
// The why block marks the span and the run as joined by content. The recorded hunks are
|
|
113
|
+
// line numbers in the recorded commit, so a content join is not narrowed by them.
|
|
114
|
+
expect(doc.why.blame).toEqual([{ start: 2, end: 2, commit: `commit:${sha.p1}`, sha: sha.p1, runs: ["run:run-a"], narrowedBy: null, joinedBy: ["patch-id"] }]);
|
|
115
|
+
expect(doc.why.runs).toEqual([{ run: "run:run-a", lines: 1, commits: [`commit:${sha.p1}`], unit: { id: "W-001", kind: null, node: "record:work/W-001" }, decisions: ["record:decision/s-001"], joinedBy: ["patch-id"] }]);
|
|
116
|
+
expect(doc.why.decisions[0]).toMatchObject({ decision: "record:decision/s-001", relevance: "carried", lines: 1 });
|
|
117
|
+
expect(formatIntent(doc)).toContain(`run-a: claude-code/claude-opus-5-5, unpriced; joined by content, as ${sha.c1.slice(0, 8)}`);
|
|
118
|
+
expect(formatIntent(doc)).toContain("(joined by content)");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("a trailer join is never overridden, and content that matches another run is a finding", async () => {
|
|
122
|
+
const doc = await walk("app/server.ts:3");
|
|
123
|
+
expect(doc.edges.filter((e) => e.kind === "made-by" && e.from === `commit:${sha.p2}`)).toEqual([{ kind: "made-by", from: `commit:${sha.p2}`, to: "run:run-c", joinedBy: ["trailer"] }]);
|
|
124
|
+
const conflict = doc.nodes.find((n) => n.kind === "finding" && n.code === "intent-commit-join-conflict");
|
|
125
|
+
expect(conflict).toMatchObject({ concerns: [`commit:${sha.p2}`, "run:run-c"] });
|
|
126
|
+
expect(conflict && "message" in conflict ? conflict.message : "").toContain(`run-b (${sha.b1.slice(0, 8)})`);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("a squash of several commits has a patch-id of its own and doesn't join by content", async () => {
|
|
130
|
+
const doc = await walk("app/server.ts:5-6");
|
|
131
|
+
expect(doc.edges.filter((e) => e.kind === "made-by" && e.from === `commit:${sha.sq}`)).toEqual([]);
|
|
132
|
+
expect(doc.why.gaps.map((g) => g.code)).toContain("intent-why-no-run");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("graph --intent --record lists the content join on the commit", async () => {
|
|
136
|
+
const { doc } = await intentRecord({ cwd: root, record: "s-001" });
|
|
137
|
+
contract(intentRecordSchema).expectValid(doc);
|
|
138
|
+
if ("error" in doc) throw new Error(doc.error.message);
|
|
139
|
+
expect(doc.commits.find((c) => c.sha === sha.p1)?.runs).toEqual([
|
|
140
|
+
{ id: "run-a", recorded: true, state: "ended", harness: "claude-code", model: "claude-opus-5-5", provider: null, by: null, agent: null, unit: "W-001", joinedBy: ["patch-id"], recordedAs: sha.c1 },
|
|
141
|
+
]);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe("runs --json joins by patch-id", () => {
|
|
146
|
+
test("a run lists the rewritten commit after the one it recorded, with no hunks", async () => {
|
|
147
|
+
const doc = await workspaceRuns({ cwd: root });
|
|
148
|
+
contract(runsSchema).expectValid(doc);
|
|
149
|
+
if ("error" in doc) throw new Error(doc.error.message);
|
|
150
|
+
const byId = new Map(doc.runs.map((r) => [r.id, r]));
|
|
151
|
+
const a = byId.get("run-a")!;
|
|
152
|
+
expect(a.commits).toEqual([
|
|
153
|
+
{ sha: sha.c1, patchId: expect.stringMatching(/^[0-9a-f]{40}$/), joinedBy: ["record"], hunks: [{ path: "app/server.ts", start: 2, end: 2 }] },
|
|
154
|
+
{ sha: sha.p1, patchId: a.commits[0].patchId, joinedBy: ["patch-id"], hunks: null, recordedAs: sha.c1 },
|
|
155
|
+
]);
|
|
156
|
+
// p2 carries Chant-Run: run-c, so it joins run C by trailer and run B not at all.
|
|
157
|
+
expect(byId.get("run-b")!.commits.map((c) => c.sha)).toEqual([sha.b1]);
|
|
158
|
+
expect(byId.get("run-c")!.commits).toEqual([{ sha: sha.p2, patchId: expect.any(String), joinedBy: ["trailer"], hunks: null }]);
|
|
159
|
+
// The squash joins neither of run D's commits.
|
|
160
|
+
expect(byId.get("run-d")!.commits.map((c) => c.sha)).toEqual([sha.d1, sha.d2]);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
@@ -412,6 +412,9 @@
|
|
|
412
412
|
"$ref": "#/$defs/runRef"
|
|
413
413
|
},
|
|
414
414
|
"description": "The agent runs that made the commit (#3033). Added within version 1, so an older document may lack it."
|
|
415
|
+
},
|
|
416
|
+
"squash": {
|
|
417
|
+
"$ref": "#/$defs/squash"
|
|
415
418
|
}
|
|
416
419
|
}
|
|
417
420
|
},
|
|
@@ -473,7 +476,8 @@
|
|
|
473
476
|
"code": {
|
|
474
477
|
"enum": [
|
|
475
478
|
"intent-history-shallow",
|
|
476
|
-
"intent-plugin-failed"
|
|
479
|
+
"intent-plugin-failed",
|
|
480
|
+
"squash-unfollowed"
|
|
477
481
|
]
|
|
478
482
|
},
|
|
479
483
|
"message": {
|
|
@@ -726,10 +730,133 @@
|
|
|
726
730
|
"items": {
|
|
727
731
|
"enum": [
|
|
728
732
|
"trailer",
|
|
729
|
-
"record"
|
|
733
|
+
"record",
|
|
734
|
+
"patch-id",
|
|
735
|
+
"squash"
|
|
730
736
|
]
|
|
731
737
|
},
|
|
732
|
-
"description": "trailer: the commit carries Chant-Run with the run's id. record: the run's end lists the commit."
|
|
738
|
+
"description": "trailer: the commit carries Chant-Run with the run's id. record: the run's end lists the commit. patch-id: neither holds, and the commit's git patch-id --stable equals a commit's the run's end recorded, as after a rebase or a cherry-pick that dropped the trailer (#3036); never alongside the others. Added within version 1. squash: the commit squashes a pull request whose original commits join the run one of those ways, and the read was asked to follow squashes (#3035); via names them. Added within version 1."
|
|
739
|
+
},
|
|
740
|
+
"recordedAs": {
|
|
741
|
+
"type": "string",
|
|
742
|
+
"pattern": "^[0-9a-f]{40,64}$",
|
|
743
|
+
"description": "For a patch-id join (#3036): the commit the run's end recorded with the same patch-id, which this one rewrites. Added within version 1."
|
|
744
|
+
},
|
|
745
|
+
"via": {
|
|
746
|
+
"type": "array",
|
|
747
|
+
"items": {
|
|
748
|
+
"type": "string",
|
|
749
|
+
"pattern": "^[0-9a-f]{40,64}$"
|
|
750
|
+
},
|
|
751
|
+
"description": "For a squash join (#3035): the pull request's original commits through which the commit joins the run, oldest first. Added within version 1."
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
},
|
|
755
|
+
"squash": {
|
|
756
|
+
"type": "object",
|
|
757
|
+
"description": "Present only when the read follows squashes (--follow-squash, #3035) and the commit squashes a pull request: its subject ends with (#<n>), it has one parent, and the pull request's head is not in its history. The commit carries what the original commits' trailers carry and joins their runs. Added within version 1.",
|
|
758
|
+
"required": [
|
|
759
|
+
"pullRequest",
|
|
760
|
+
"forge",
|
|
761
|
+
"ref",
|
|
762
|
+
"head",
|
|
763
|
+
"fetched",
|
|
764
|
+
"followed",
|
|
765
|
+
"commits"
|
|
766
|
+
],
|
|
767
|
+
"properties": {
|
|
768
|
+
"pullRequest": {
|
|
769
|
+
"type": "integer",
|
|
770
|
+
"minimum": 1
|
|
771
|
+
},
|
|
772
|
+
"forge": {
|
|
773
|
+
"enum": [
|
|
774
|
+
"github",
|
|
775
|
+
"forgejo",
|
|
776
|
+
null
|
|
777
|
+
],
|
|
778
|
+
"description": "The forge origin points at, from its URL, or null for one chant doesn't know or no origin. Both keep the pull request's head at refs/pull/<n>/head."
|
|
779
|
+
},
|
|
780
|
+
"ref": {
|
|
781
|
+
"type": [
|
|
782
|
+
"string",
|
|
783
|
+
"null"
|
|
784
|
+
],
|
|
785
|
+
"description": "The local ref the head was read from: refs/chant/pull/<n>/head where a read fetched it, refs/pull/<n>/head, or refs/remotes/origin/pr/<n>. Null when none could be read."
|
|
786
|
+
},
|
|
787
|
+
"head": {
|
|
788
|
+
"oneOf": [
|
|
789
|
+
{
|
|
790
|
+
"type": "string",
|
|
791
|
+
"pattern": "^[0-9a-f]{40,64}$"
|
|
792
|
+
},
|
|
793
|
+
{
|
|
794
|
+
"type": "null"
|
|
795
|
+
}
|
|
796
|
+
],
|
|
797
|
+
"description": "The pull request's head commit, or null."
|
|
798
|
+
},
|
|
799
|
+
"fetched": {
|
|
800
|
+
"type": "boolean",
|
|
801
|
+
"description": "Whether this read fetched the ref from origin."
|
|
802
|
+
},
|
|
803
|
+
"followed": {
|
|
804
|
+
"type": "boolean",
|
|
805
|
+
"description": "Whether the original commits were read. When false, squash-unfollowed in reasons says why, and commits is empty."
|
|
806
|
+
},
|
|
807
|
+
"commits": {
|
|
808
|
+
"type": "array",
|
|
809
|
+
"items": {
|
|
810
|
+
"type": "object",
|
|
811
|
+
"required": [
|
|
812
|
+
"sha",
|
|
813
|
+
"subject",
|
|
814
|
+
"author",
|
|
815
|
+
"date",
|
|
816
|
+
"trailers",
|
|
817
|
+
"joins"
|
|
818
|
+
],
|
|
819
|
+
"properties": {
|
|
820
|
+
"sha": {
|
|
821
|
+
"type": "string",
|
|
822
|
+
"pattern": "^[0-9a-f]{40,64}$"
|
|
823
|
+
},
|
|
824
|
+
"subject": {
|
|
825
|
+
"type": "string"
|
|
826
|
+
},
|
|
827
|
+
"author": {
|
|
828
|
+
"type": "object",
|
|
829
|
+
"required": [
|
|
830
|
+
"name",
|
|
831
|
+
"email"
|
|
832
|
+
],
|
|
833
|
+
"properties": {
|
|
834
|
+
"name": {
|
|
835
|
+
"type": "string"
|
|
836
|
+
},
|
|
837
|
+
"email": {
|
|
838
|
+
"type": "string"
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
},
|
|
842
|
+
"date": {
|
|
843
|
+
"type": "string"
|
|
844
|
+
},
|
|
845
|
+
"trailers": {
|
|
846
|
+
"type": "object",
|
|
847
|
+
"additionalProperties": {
|
|
848
|
+
"type": "array",
|
|
849
|
+
"items": {
|
|
850
|
+
"type": "string"
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
},
|
|
854
|
+
"joins": {
|
|
855
|
+
"$ref": "#/$defs/trailerJoins"
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
},
|
|
859
|
+
"description": "The original commits, oldest first: the head's commits the squash's parent doesn't have, merges left out."
|
|
733
860
|
}
|
|
734
861
|
}
|
|
735
862
|
}
|
|
@@ -29,12 +29,16 @@
|
|
|
29
29
|
* `workedBy` and `alsoWithin` list every work item and other decision that
|
|
30
30
|
* matched, whatever the bucket. `issue` and `contract` entries are listed and
|
|
31
31
|
* not walked: they name no path. Git is read through a local `git`
|
|
32
|
-
* subprocess only: no fetch, no network
|
|
32
|
+
* subprocess only: no fetch, no network, unless the walk is asked to follow
|
|
33
|
+
* squashes (#3035). Then a squash commit lists its pull request's original
|
|
34
|
+
* commits, joins their runs and counts as the record's own work when one of
|
|
35
|
+
* them carries it, and a pull request ref the clone lacks is fetched from
|
|
36
|
+
* `origin`.
|
|
33
37
|
*/
|
|
34
38
|
|
|
35
39
|
import { declaredRecordKinds, readDeclaration, readerVersion, WorkspaceReadError } from "./declaration";
|
|
36
40
|
import { declaredKindFile } from "./declared-kinds";
|
|
37
|
-
import { entityDecisions, runCommitJoins } from "./intent-joins";
|
|
41
|
+
import { entityDecisions, runCommitJoins, type IntentCommit } from "./intent-joins";
|
|
38
42
|
import {
|
|
39
43
|
addingCommit,
|
|
40
44
|
closingCommit,
|
|
@@ -53,7 +57,8 @@ import { constraintCovers, isWorkspacePath, memberHolding } from "./record-asset
|
|
|
53
57
|
import type { DecidedIn } from "./record-decided";
|
|
54
58
|
import { RecordReadError } from "./records";
|
|
55
59
|
import type { RecordView } from "./records-cli";
|
|
56
|
-
import { runsForCommits, type RunRef } from "./runs";
|
|
60
|
+
import { addSquashRuns, runsForCommits, type RunRef } from "./runs";
|
|
61
|
+
import { followSquashes, type Forge, type SquashFollow } from "./squash";
|
|
57
62
|
import { readTrailerJoins, type CommitTrailerJoins } from "./trailer-joins";
|
|
58
63
|
import { joinPath } from "./tree";
|
|
59
64
|
import { locateWorkspace } from "./which-chant";
|
|
@@ -98,6 +103,16 @@ export interface RecordCommit {
|
|
|
98
103
|
joins: CommitTrailerJoins;
|
|
99
104
|
/** The agent runs that made the commit (#3033): the one its Chant-Run names, and any whose record lists it. */
|
|
100
105
|
runs: RunRef[];
|
|
106
|
+
/** Only when the walk follows squashes (#3035) and the commit squashes a pull request: its original commits and what their trailers join. */
|
|
107
|
+
squash?: {
|
|
108
|
+
pullRequest: number;
|
|
109
|
+
forge: Forge | null;
|
|
110
|
+
ref: string | null;
|
|
111
|
+
head: string | null;
|
|
112
|
+
fetched: boolean;
|
|
113
|
+
followed: boolean;
|
|
114
|
+
commits: { sha: string; subject: string; author: { name: string; email: string }; date: string; trailers: Record<string, string[]>; joins: CommitTrailerJoins }[];
|
|
115
|
+
};
|
|
101
116
|
/** The record's constrains entries whose history listed the commit. */
|
|
102
117
|
entries: string[];
|
|
103
118
|
/** The files the commit changed in the record's region, from the workspace root. Empty when git lists none, as for a merge that changed nothing against its first parent there. */
|
|
@@ -146,6 +161,8 @@ export interface IntentRecordQuery {
|
|
|
146
161
|
record: string;
|
|
147
162
|
at?: string;
|
|
148
163
|
kinds?: string[];
|
|
164
|
+
/** Follow squash merges to their pull requests' original commits, fetching a missing pull request ref from `origin` (#3035). */
|
|
165
|
+
followSquash?: boolean;
|
|
149
166
|
}
|
|
150
167
|
|
|
151
168
|
export interface IntentRecordResult {
|
|
@@ -248,6 +265,13 @@ async function walk(query: IntentRecordQuery, head: Head): Promise<IntentRecordR
|
|
|
248
265
|
const own = decisionWindow(hit.kind, view);
|
|
249
266
|
const shas = [...listed.keys()].filter((sha) => inWindow(own, sha));
|
|
250
267
|
const details = commitDetails(top, shas);
|
|
268
|
+
// Squash merges, followed to the pull request's original commits when asked (#3035).
|
|
269
|
+
const follows = query.followSquash ? followSquashes(top, [...details.values()], { fetchMissing: true }) : new Map<string, SquashFollow>();
|
|
270
|
+
for (const f of follows.values()) {
|
|
271
|
+
if (f.problem) 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` });
|
|
272
|
+
}
|
|
273
|
+
const originalDetails = commitDetails(top, [...new Set([...follows.values()].flatMap((f) => f.commits))]);
|
|
274
|
+
const originalsOf = (sha: string): IntentCommit[] => (follows.get(sha)?.commits ?? []).map((o) => originalDetails.get(o)).filter((x): x is IntentCommit => x !== undefined);
|
|
251
275
|
// Newest first by commit date, as git log lists them.
|
|
252
276
|
const order = (shas.length > 0 ? tryGit(top, ["rev-list", "--no-walk=sorted", "--stdin"], `${shas.join("\n")}\n`) : "")?.split("\n").map((s) => s.trim()).filter(Boolean) ?? shas;
|
|
253
277
|
shas.sort((a, b) => order.indexOf(a) - order.indexOf(b));
|
|
@@ -307,14 +331,15 @@ async function walk(query: IntentRecordQuery, head: Head): Promise<IntentRecordR
|
|
|
307
331
|
const constrainedContracts = constrains.filter((c) => c.granularity === "contract").map((c) => c.entry);
|
|
308
332
|
const commits: RecordCommit[] = [];
|
|
309
333
|
// chant's own trailers (#3149): the record named outright, or a work item that implements it.
|
|
310
|
-
const trailerJoins = readTrailerJoins(top, [...details.values()]);
|
|
334
|
+
const trailerJoins = readTrailerJoins(top, [...details.values(), ...originalDetails.values()]);
|
|
311
335
|
const recordKinds = new Map(kinds.filter((k) => k.records).map((k) => [k.records!.loaded.kind.name, k]));
|
|
312
336
|
const workKinds = kinds.filter((k) => k.records?.loaded.kind.work);
|
|
313
337
|
const runJoins = await runsForCommits(
|
|
314
338
|
top,
|
|
315
339
|
located.rootOnDisk,
|
|
316
|
-
shas.map((sha) => ({ sha, run: trailerJoins.get(sha)?.joins.run ?? null })),
|
|
340
|
+
[...new Set([...shas, ...originalDetails.keys()])].map((sha) => ({ sha, run: trailerJoins.get(sha)?.joins.run ?? null })),
|
|
317
341
|
);
|
|
342
|
+
addSquashRuns(runJoins.refs, follows);
|
|
318
343
|
const workImplements = (k: LoadedKind, id: string): boolean | undefined => {
|
|
319
344
|
const v = k.records!.views.find((x) => x.id === id);
|
|
320
345
|
return v ? idList(v.data, k.records!.loaded.kind.work!.implements).includes(view.id!) : undefined;
|
|
@@ -325,34 +350,42 @@ async function walk(query: IntentRecordQuery, head: Head): Promise<IntentRecordR
|
|
|
325
350
|
let unit: string | null = null;
|
|
326
351
|
let isOwn = false;
|
|
327
352
|
const joins = trailerJoins.get(sha)!.joins;
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
r
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
353
|
+
const originals = originalsOf(sha);
|
|
354
|
+
// The commit's own trailers, and a followed squash's original commits' (#3035).
|
|
355
|
+
for (const j of [joins, ...originals.map((o) => trailerJoins.get(o.sha)!.joins)]) {
|
|
356
|
+
for (const r of j.records) {
|
|
357
|
+
const k = recordKinds.get(r.kind);
|
|
358
|
+
if (!k || !k.records!.views.some((v) => v.id === r.id)) continue;
|
|
359
|
+
r.node = `record:${r.kind}/${r.id}`;
|
|
360
|
+
if (k.records!.loaded.kind.work ? workImplements(k, r.id) : r.kind === kind.name && r.id === view.id) isOwn = true;
|
|
361
|
+
}
|
|
362
|
+
const item = j.lease?.item;
|
|
363
|
+
if (item) {
|
|
364
|
+
const holding = workKinds.filter((k) => k.records!.views.some((v) => v.id === item));
|
|
365
|
+
if (holding.length === 1 && workImplements(holding[0], item)) isOwn = true;
|
|
366
|
+
}
|
|
338
367
|
}
|
|
368
|
+
const subjects: IntentCommit[] = [c, ...originals];
|
|
339
369
|
for (const k of kinds) {
|
|
340
370
|
if (!k.joins) continue;
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
371
|
+
for (const subject of subjects) {
|
|
372
|
+
let result;
|
|
373
|
+
try {
|
|
374
|
+
result = await runCommitJoins(k.joins, subject, { read: readAt, list: listAt, at: located.at }, k.name);
|
|
375
|
+
} catch (err) {
|
|
376
|
+
const message = `${k.display}: commitJoins failed for ${subject.sha.slice(0, 8)}: ${err instanceof Error ? err.message : String(err)}`;
|
|
377
|
+
if (!failedPlugins.has(message)) reasons.push({ code: "intent-plugin-failed", message });
|
|
378
|
+
failedPlugins.add(message);
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
if (!result.unit) continue;
|
|
382
|
+
unit ??= result.unit.id;
|
|
383
|
+
const named = [...entityDecisions(result.unit), ...entityDecisions(result.contract)];
|
|
384
|
+
if (named.some((x) => x === view.id || x === `${kind.name}/${view.id}`)) isOwn = true;
|
|
385
|
+
if (result.contract && constrainedContracts.includes(result.contract.id)) isOwn = true;
|
|
349
386
|
}
|
|
350
|
-
if (!result.unit) continue;
|
|
351
|
-
unit ??= result.unit.id;
|
|
352
|
-
const named = [...entityDecisions(result.unit), ...entityDecisions(result.contract)];
|
|
353
|
-
if (named.some((x) => x === view.id || x === `${kind.name}/${view.id}`)) isOwn = true;
|
|
354
|
-
if (result.contract && constrainedContracts.includes(result.contract.id)) isOwn = true;
|
|
355
387
|
}
|
|
388
|
+
const f = follows.get(sha);
|
|
356
389
|
const changed = (files.get(sha) ?? []).map((f) => joinPath(workspacePrefix, f));
|
|
357
390
|
const covers = (paths: string[]) => paths.some((p) => changed.some((f) => constraintCovers(p, f)));
|
|
358
391
|
const workedBy = works.filter((w) => inWindow(w.window, sha) && (w.implementsRecord || covers(w.paths))).map((w) => w.ref);
|
|
@@ -368,6 +401,19 @@ async function walk(query: IntentRecordQuery, head: Head): Promise<IntentRecordR
|
|
|
368
401
|
unit,
|
|
369
402
|
joins,
|
|
370
403
|
runs: runJoins.refs.get(sha) ?? [],
|
|
404
|
+
...(f
|
|
405
|
+
? {
|
|
406
|
+
squash: {
|
|
407
|
+
pullRequest: f.pullRequest,
|
|
408
|
+
forge: f.forge,
|
|
409
|
+
ref: f.ref,
|
|
410
|
+
head: f.head,
|
|
411
|
+
fetched: f.fetched,
|
|
412
|
+
followed: f.followed,
|
|
413
|
+
commits: originals.map((o) => ({ sha: o.sha, subject: o.subject, author: o.author, date: o.date, trailers: o.trailers, joins: trailerJoins.get(o.sha)!.joins })),
|
|
414
|
+
},
|
|
415
|
+
}
|
|
416
|
+
: {}),
|
|
371
417
|
entries: listed.get(sha)!,
|
|
372
418
|
files: files.get(sha) ?? [],
|
|
373
419
|
bucket: isOwn ? "own" : workedBy.length > 0 ? "worked" : alsoWithin.length > 0 ? "within-other" : "unexplained",
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* From a commit back to the review that asked for it (#3154, E21 in
|
|
3
|
+
* arugula-salad/hud#823): a review session's comments[].follow_ups name a
|
|
4
|
+
* work item, a builder's commit carries that item with `Chant-Record:
|
|
5
|
+
* work:<id>`, and `graph --intent` lists the session on the work node.
|
|
6
|
+
*
|
|
7
|
+
* The workspace is a copy of the reference workspace, where W-001
|
|
8
|
+
* constrains design/screens/home.json, with one open review session added.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { cpSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { afterAll, describe, expect, test } from "vitest";
|
|
14
|
+
import { cleanScratch, contract, git, REPO, scratchDir } from "./__fixtures__/contract-repo";
|
|
15
|
+
import { intentGraph, type CommitNode, type WorkNode } from "./intent";
|
|
16
|
+
import intentSchema from "./intent.schema.json";
|
|
17
|
+
|
|
18
|
+
afterAll(cleanScratch);
|
|
19
|
+
|
|
20
|
+
const intent = contract(intentSchema);
|
|
21
|
+
|
|
22
|
+
const SESSION = `---
|
|
23
|
+
${JSON.stringify(
|
|
24
|
+
{
|
|
25
|
+
schema: 1,
|
|
26
|
+
id: "S-0002",
|
|
27
|
+
title: "Review of the home page",
|
|
28
|
+
state: "open",
|
|
29
|
+
agenda: [{ text: "The home page as built" }],
|
|
30
|
+
attendance: [{ principal: "alice", class: "person" }],
|
|
31
|
+
opened: "2026-10-03T10:00:00Z",
|
|
32
|
+
closed: null,
|
|
33
|
+
verdicts: [],
|
|
34
|
+
comments: [
|
|
35
|
+
{ text: "The header is cut off." },
|
|
36
|
+
{ text: "The hero image should follow the spec.", follow_ups: ["work:W-001"] },
|
|
37
|
+
{ text: "Same for the footer.", follow_ups: ["work:W-001", "work:W-404"] },
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
null,
|
|
41
|
+
2,
|
|
42
|
+
)}
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
# Review of the home page
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
function workspace(): string {
|
|
49
|
+
const root = scratchDir("chant-intent-reviews-");
|
|
50
|
+
cpSync(join(REPO, "reference-workspace"), root, { recursive: true, filter: (src) => !/[\\/](node_modules|dist)$/.test(src) });
|
|
51
|
+
writeFileSync(join(root, "design", "sessions", "S-0002-review-of-the-home-page.md"), SESSION);
|
|
52
|
+
const g = (...a: string[]) => git(root, ...a);
|
|
53
|
+
g("init", "-q");
|
|
54
|
+
g("add", "-A");
|
|
55
|
+
g("commit", "-q", "-m", "the workspace");
|
|
56
|
+
g("branch", "-M", "main");
|
|
57
|
+
writeFileSync(join(root, "design", "screens", "home.json"), `${JSON.stringify({ regions: ["header", "hero", "footer"] }, null, 2)}\n`);
|
|
58
|
+
g("add", "-A");
|
|
59
|
+
g("commit", "-q", "-m", "the hero follows the spec", "-m", "Chant-Record: work:W-001");
|
|
60
|
+
return root;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
describe("graph --intent joins a work item back to the review that asked for it (#3154, E21)", () => {
|
|
64
|
+
test("the work node lists the session and the comments whose follow_ups name it, and the builder's commit carries the item", async () => {
|
|
65
|
+
const root = workspace();
|
|
66
|
+
const head = git(root, "rev-parse", "HEAD");
|
|
67
|
+
const { doc } = await intentGraph({ cwd: root, region: "design/screens/home.json" });
|
|
68
|
+
intent.expectValid(doc);
|
|
69
|
+
if ("error" in doc) throw new Error(`${doc.error.code}: ${doc.error.message}`);
|
|
70
|
+
const w = doc.nodes.find((n) => n.id === "record:work/W-001") as WorkNode;
|
|
71
|
+
expect(w.reviews).toEqual([{ id: "record:session/S-0002", recordKind: "session", record: "S-0002", path: "design/sessions/S-0002-review-of-the-home-page.md", state: "open", comments: [2, 3] }]);
|
|
72
|
+
const c = doc.nodes.find((n) => n.id === `commit:${head}`) as CommitNode;
|
|
73
|
+
expect(c).toBeDefined();
|
|
74
|
+
expect(doc.edges).toContainEqual({ kind: "carries", from: c.id, to: w.id });
|
|
75
|
+
});
|
|
76
|
+
});
|