@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
package/src/workspace/intent.ts
CHANGED
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
* record kind the declaration names (#2680), and each one's `commitJoins`
|
|
32
32
|
* runs for every commit in that order. chant emits the
|
|
33
33
|
* graph and hud renders it (#2524 D8, D15). Git is read through a local
|
|
34
|
-
* `git` subprocess only: no fetch, no network
|
|
34
|
+
* `git` subprocess only: no fetch, no network, except to follow a squash
|
|
35
|
+
* when asked (below).
|
|
35
36
|
*
|
|
36
37
|
* A region can name a symbol, `path#symbol`, which `symbols.ts` resolves to
|
|
37
38
|
* its current lines (#3034). For a file region the document also carries
|
|
@@ -39,6 +40,21 @@
|
|
|
39
40
|
* behind each (narrowed by the hunks a run recorded when several share a
|
|
40
41
|
* commit), and the decisions governing the region, most relevant first, with
|
|
41
42
|
* `explained: false` and closed gap codes when nothing accounts for it.
|
|
43
|
+
*
|
|
44
|
+
* A commit joins an agent run by its `Chant-Run` trailer or the run's own
|
|
45
|
+
* list of commits, and, failing both, by content (#3036): a commit whose
|
|
46
|
+
* `git patch-id --stable` equals one the run recorded, as after a rebase or a
|
|
47
|
+
* cherry-pick that dropped the trailer. Its `made-by` edge says `patch-id`,
|
|
48
|
+
* and a commit joined one way whose content matches another run's recorded
|
|
49
|
+
* commit is `intent-commit-join-conflict`.
|
|
50
|
+
*
|
|
51
|
+
* Asked to (`followSquash`, #3035), the walk follows a squash merge to its
|
|
52
|
+
* pull request's original commits (`squash.ts`): the squash commit node lists
|
|
53
|
+
* them with their trailers, joins and signatures, carries what their trailers
|
|
54
|
+
* and plugin joins carry, and joins their runs, marked `squash`. That is the
|
|
55
|
+
* one part of the walk that may reach the network, to fetch a pull request
|
|
56
|
+
* ref the clone lacks; a ref that can't be read is `squash-unfollowed` in
|
|
57
|
+
* `reasons`, and the walk keeps its answer without it.
|
|
42
58
|
*/
|
|
43
59
|
|
|
44
60
|
import { execFileSync } from "node:child_process";
|
|
@@ -56,8 +72,10 @@ import type { DecidedIn } from "./record-decided";
|
|
|
56
72
|
import { importKindModule, loadRecordKind, parseFrontMatter, RecordReadError, supersedesTargets, type LoadedRecordKind } from "./records";
|
|
57
73
|
import { queryRecords, type RecordView } from "./records-cli";
|
|
58
74
|
import { isPluginCode, type PluginCode, type ReasonCode } from "./reason-codes";
|
|
59
|
-
import { runsForCommits, type RunCost, type RunPin, type RunUsage, type RunView } from "./runs";
|
|
60
|
-
import {
|
|
75
|
+
import { addSquashRuns, RUN_JOIN_ORDER, runsForCommits, type RunCost, type RunJoin, type RunPin, type RunUsage, type RunView } from "./runs";
|
|
76
|
+
import { followSquashes, squashLineOrigins, type Forge, type SquashFollow } from "./squash";
|
|
77
|
+
import { memberDirOnDisk, memberSymbolResolvers, type SymbolPluginLoader } from "./lexicon-symbols";
|
|
78
|
+
import { resolveSymbol, symbolResolverFor, type SymbolResolver } from "./symbols";
|
|
61
79
|
import { readTrailerJoins, type CommitTrailerJoins } from "./trailer-joins";
|
|
62
80
|
import { joinPath, skippedDir, type WorkspaceTree } from "./tree";
|
|
63
81
|
import { activeAttestors, type ProvenanceLevel } from "./trust/attestor";
|
|
@@ -109,6 +127,8 @@ export const INTENT_FINDING_CODES = [
|
|
|
109
127
|
"intent-work-blocked",
|
|
110
128
|
/** Commits in the region are a decision's own work while the work item implementing it is still open: the code says done and the queue says not (#2683). */
|
|
111
129
|
"intent-work-open-decided-code",
|
|
130
|
+
/** A commit joined to an agent run by its trailer or the run's record has the patch-id of a commit another run recorded; the content join is not made (#3036). */
|
|
131
|
+
"intent-commit-join-conflict",
|
|
112
132
|
] as const satisfies readonly ReasonCode[];
|
|
113
133
|
export type IntentFindingCode = (typeof INTENT_FINDING_CODES)[number];
|
|
114
134
|
|
|
@@ -118,6 +138,8 @@ export const INTENT_REASON_CODES = [
|
|
|
118
138
|
"intent-history-shallow",
|
|
119
139
|
/** A plugin's commitJoins failed for a commit. */
|
|
120
140
|
"intent-plugin-failed",
|
|
141
|
+
/** A squash commit's pull request ref could not be read or fetched, so its original commits are not followed (#3035). */
|
|
142
|
+
"squash-unfollowed",
|
|
121
143
|
] as const satisfies readonly ReasonCode[];
|
|
122
144
|
export type IntentReasonCode = (typeof INTENT_REASON_CODES)[number];
|
|
123
145
|
|
|
@@ -235,6 +257,39 @@ export interface CommitNode {
|
|
|
235
257
|
* a kind read has is linked by a `carries` edge.
|
|
236
258
|
*/
|
|
237
259
|
joins: CommitTrailerJoins;
|
|
260
|
+
/**
|
|
261
|
+
* Set only when the walk follows squashes (#3035) and the commit squashes
|
|
262
|
+
* a pull request: its original commits, read from the forge's pull request
|
|
263
|
+
* ref. The commit carries what they carry and joins their runs.
|
|
264
|
+
*/
|
|
265
|
+
squash?: SquashNode;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/** A squash commit's pull request, followed to its original commits (#3035). */
|
|
269
|
+
export interface SquashNode {
|
|
270
|
+
pullRequest: number;
|
|
271
|
+
/** The forge `origin` points at, or null for one chant doesn't know. */
|
|
272
|
+
forge: Forge | null;
|
|
273
|
+
/** The local ref the pull request's head was read from, or null. */
|
|
274
|
+
ref: string | null;
|
|
275
|
+
/** The pull request's head commit, or null. */
|
|
276
|
+
head: string | null;
|
|
277
|
+
/** Whether this read fetched the ref. */
|
|
278
|
+
fetched: boolean;
|
|
279
|
+
/** Whether the original commits were read; when false, `squash-unfollowed` in `reasons` says why. */
|
|
280
|
+
followed: boolean;
|
|
281
|
+
/** The original commits, oldest first, each with what its own trailers join and its provenance level. */
|
|
282
|
+
commits: SquashedCommit[];
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export interface SquashedCommit {
|
|
286
|
+
sha: string;
|
|
287
|
+
subject: string;
|
|
288
|
+
author: { name: string; email: string };
|
|
289
|
+
date: string;
|
|
290
|
+
trailers: Record<string, string[]>;
|
|
291
|
+
signature: { level: ProvenanceLevel; reason: string; principal?: string };
|
|
292
|
+
joins: CommitTrailerJoins;
|
|
238
293
|
}
|
|
239
294
|
|
|
240
295
|
export type CommitState = "decided" | "decided-by-window" | "undecided";
|
|
@@ -338,6 +393,18 @@ export interface WorkGapSource {
|
|
|
338
393
|
artifact?: string;
|
|
339
394
|
}
|
|
340
395
|
|
|
396
|
+
/** A review session that led to a work item (#3154, E21). */
|
|
397
|
+
export interface WorkReview {
|
|
398
|
+
/** The session record's node-style id, `record:<kind>/<id>`. */
|
|
399
|
+
id: string;
|
|
400
|
+
recordKind: string;
|
|
401
|
+
record: string;
|
|
402
|
+
path: string;
|
|
403
|
+
state: string | null;
|
|
404
|
+
/** The comments whose follow_ups name the item, numbered from 1; empty when only the session's own follow_ups do. */
|
|
405
|
+
comments: number[];
|
|
406
|
+
}
|
|
407
|
+
|
|
341
408
|
export interface WorkNode {
|
|
342
409
|
id: string;
|
|
343
410
|
kind: "work";
|
|
@@ -368,6 +435,15 @@ export interface WorkNode {
|
|
|
368
435
|
constrains: { entry: string; granularity: Granularity }[];
|
|
369
436
|
/** The item's work warnings, as records reports them, and work-done-gap-open. */
|
|
370
437
|
warnings: { code: WorkWarningCode; message: string }[];
|
|
438
|
+
/**
|
|
439
|
+
* The review sessions whose comments led to this item (#3154, E21 in
|
|
440
|
+
* arugula-salad/hud#823): each session record of a session kind read whose
|
|
441
|
+
* `comments[].follow_ups` (or its own `follow_ups`) names the item as
|
|
442
|
+
* `<kind>:<id>`, with the 1-based numbers of those comments. A commit that
|
|
443
|
+
* carries the item, by its `Chant-Record: work:<id>` trailer, joins back
|
|
444
|
+
* to the review through it. Empty when none does. Added within contract 1.
|
|
445
|
+
*/
|
|
446
|
+
reviews?: WorkReview[];
|
|
371
447
|
}
|
|
372
448
|
|
|
373
449
|
/**
|
|
@@ -408,7 +484,7 @@ export type IntentEdge =
|
|
|
408
484
|
| { kind: "pins"; from: string; to: string; pinnedSha256: string | null; pinState: PinState }
|
|
409
485
|
| { kind: "touched-by"; from: string; to: string; lines: LineRange[] | null }
|
|
410
486
|
| { kind: "within"; from: string; to: string; state: "decided" | "decided-by-window" | "worked" }
|
|
411
|
-
| { kind: "made-by"; from: string; to: string; joinedBy:
|
|
487
|
+
| { kind: "made-by"; from: string; to: string; joinedBy: RunJoin[]; recordedAs?: string; via?: string[] }
|
|
412
488
|
| { kind: "produced-by" | "serves" | "cites-evidence" | "supersedes" | "links" | "implements" | "needs" | "addressed-by" | "carries" | "worked-on"; from: string; to: string };
|
|
413
489
|
|
|
414
490
|
/** Lines of the region that one commit, and the same runs, last wrote (#3034). */
|
|
@@ -422,6 +498,16 @@ export interface WhySpan {
|
|
|
422
498
|
runs: string[];
|
|
423
499
|
/** `hunks` when a run's recorded hunks chose among the commit's runs; null when every run that made the commit is listed. */
|
|
424
500
|
narrowedBy: "hunks" | null;
|
|
501
|
+
/** How the runs in `runs` join the commit, as their `made-by` edges say, without repeats (#3036). Empty with no runs. */
|
|
502
|
+
joinedBy: RunJoin[];
|
|
503
|
+
/**
|
|
504
|
+
* For lines of a followed squash (#3035): the sha of the original commit
|
|
505
|
+
* that last wrote them, one of the commit node's `squash.commits`, by blame
|
|
506
|
+
* at the pull request's head; `runs` keeps that commit's runs and the
|
|
507
|
+
* squash's own. Empty when the file at the head is not the squash's, so
|
|
508
|
+
* blame there can't say. Absent for any other commit.
|
|
509
|
+
*/
|
|
510
|
+
via?: string[];
|
|
425
511
|
}
|
|
426
512
|
|
|
427
513
|
/** Why a decision is in `why`, most relevant first (#3034). */
|
|
@@ -454,6 +540,8 @@ export interface WhyRun {
|
|
|
454
540
|
unit: { id: string; kind: string | null; node: string | null } | null;
|
|
455
541
|
/** The decision nodes it carried out: through its work item's implements, and the decision records it names. */
|
|
456
542
|
decisions: string[];
|
|
543
|
+
/** How it joins the commits in `commits`, as their `made-by` edges say, without repeats (#3036). */
|
|
544
|
+
joinedBy: RunJoin[];
|
|
457
545
|
}
|
|
458
546
|
|
|
459
547
|
export interface WhyGap {
|
|
@@ -510,6 +598,8 @@ export interface IntentQuery {
|
|
|
510
598
|
/** `path`, `path:line`, `path:start-end` or `path#symbol`, or a graph node id `<member>/<id>`. */
|
|
511
599
|
region: string;
|
|
512
600
|
at?: string;
|
|
601
|
+
/** Follow squash merges to their pull requests' original commits, fetching a missing pull request ref from `origin` (#3035). */
|
|
602
|
+
followSquash?: boolean;
|
|
513
603
|
/**
|
|
514
604
|
* Kind files: record kinds, plugins with `commitJoins`, or both. Relative to
|
|
515
605
|
* `cwd`. Left out, every record kind the declaration names, in its order
|
|
@@ -522,6 +612,11 @@ export interface IntentQuery {
|
|
|
522
612
|
* --member` does.
|
|
523
613
|
*/
|
|
524
614
|
resolveNode?: (cwd: string, at: string | undefined, member: string, id: string) => Promise<{ file: string; line: number | null } | null | undefined>;
|
|
615
|
+
/**
|
|
616
|
+
* Load a lexicon plugin by name, for the symbol resolvers a member's
|
|
617
|
+
* lexicons contribute (#3313). The default is the CLI's `loadPlugin`.
|
|
618
|
+
*/
|
|
619
|
+
loadLexicon?: SymbolPluginLoader;
|
|
525
620
|
}
|
|
526
621
|
|
|
527
622
|
export interface IntentResult {
|
|
@@ -850,8 +945,20 @@ async function resolveRegion(query: IntentQuery, located: LocatedWorkspace, decl
|
|
|
850
945
|
const file = inWorkspace(query.region.slice(0, hash));
|
|
851
946
|
if (file !== undefined && name !== "") {
|
|
852
947
|
if (file === "." || located.tree.stat(file) !== "file") throw new IntentError("intent-region-invalid", `${file} is a directory, and a symbol needs a file`);
|
|
853
|
-
|
|
854
|
-
|
|
948
|
+
// A file core's resolver doesn't read may have one from its member's lexicons (#3313).
|
|
949
|
+
let extra: SymbolResolver[] = [];
|
|
950
|
+
let why = "";
|
|
951
|
+
if (!symbolResolverFor(file)) {
|
|
952
|
+
const holder = memberHolding(file, declaration.members);
|
|
953
|
+
const m = holder ? declaration.members.find((x) => x.name === holder) : undefined;
|
|
954
|
+
if (m && m.kind === "chant") {
|
|
955
|
+
const got = await memberSymbolResolvers(memberDirOnDisk(located.rootOnDisk, m.dir), query.loadLexicon);
|
|
956
|
+
extra = got.resolvers;
|
|
957
|
+
if (got.problems.length > 0) why = `; member ${m.name}: ${got.problems.join("; ")}`;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
const r = resolveSymbol(file, located.tree.read(file), name, extra);
|
|
961
|
+
if (!r.ok) throw new IntentError(r.reason === "unsupported" ? "intent-symbol-unsupported" : r.reason === "ambiguous" ? "intent-symbol-ambiguous" : "intent-symbol-unknown", `${r.message}${r.reason === "unsupported" ? why : ""}${located.tree.label}`);
|
|
855
962
|
return { path: file, lines: r.declaration.lines, node: null, symbol: { name, qualified: r.declaration.qualified, kind: r.declaration.kind } };
|
|
856
963
|
}
|
|
857
964
|
}
|
|
@@ -954,6 +1061,13 @@ async function walk(query: IntentQuery, head: Head): Promise<IntentResult> {
|
|
|
954
1061
|
if (!touched.some((t) => t.sha === sha)) touched.push({ sha, lines: null });
|
|
955
1062
|
}
|
|
956
1063
|
const details = commitDetails(top, touched.map((t) => t.sha));
|
|
1064
|
+
// Squash merges, followed to the pull request's original commits when asked (#3035).
|
|
1065
|
+
const follows = query.followSquash ? followSquashes(top, [...details.values()], { fetchMissing: true }) : new Map<string, SquashFollow>();
|
|
1066
|
+
for (const f of follows.values()) {
|
|
1067
|
+
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` });
|
|
1068
|
+
}
|
|
1069
|
+
const originalDetails = commitDetails(top, [...new Set([...follows.values()].flatMap((f) => f.commits))]);
|
|
1070
|
+
const originalsOf = (sha: string): IntentCommit[] => (follows.get(sha)?.commits ?? []).map((o) => originalDetails.get(o)).filter((x): x is IntentCommit => x !== undefined);
|
|
957
1071
|
|
|
958
1072
|
// Each commit's provenance, judged by the policy at base as records judges a record's (#2547).
|
|
959
1073
|
const policy = policyAtBase(top, resolveBase(top));
|
|
@@ -981,54 +1095,75 @@ async function walk(query: IntentQuery, head: Head): Promise<IntentResult> {
|
|
|
981
1095
|
}
|
|
982
1096
|
const joined = new Map<string, Joined>();
|
|
983
1097
|
// chant's own trailers on each commit (#3149), with the items their lease tokens name.
|
|
984
|
-
const trailerJoins = readTrailerJoins(top, [...details.values()]);
|
|
1098
|
+
const trailerJoins = readTrailerJoins(top, [...details.values(), ...originalDetails.values()]);
|
|
985
1099
|
const pluginFindings: { commit: string; plugin: string; finding: PluginFinding }[] = [];
|
|
1100
|
+
const signatureOf = (sha: string): CommitNode["signature"] => {
|
|
1101
|
+
if (!policy.active) return { level: "unattested", reason: policy.problems.length ? policy.problems.join("; ") : `no signers file (${policy.signersPath}) at base; attestation is off` };
|
|
1102
|
+
const p = commitProvenance(top, policy, sha, attestors);
|
|
1103
|
+
return { level: p.level, reason: p.reason, ...(p.principal ? { principal: p.principal } : {}) };
|
|
1104
|
+
};
|
|
986
1105
|
const failedPlugins = new Set<string>();
|
|
987
1106
|
for (const t of touched) {
|
|
988
1107
|
const c = details.get(t.sha);
|
|
989
1108
|
if (!c) continue;
|
|
990
|
-
const signature =
|
|
991
|
-
? (() => {
|
|
992
|
-
const p = commitProvenance(top, policy, c.sha, attestors);
|
|
993
|
-
return { level: p.level, reason: p.reason, ...(p.principal ? { principal: p.principal } : {}) };
|
|
994
|
-
})()
|
|
995
|
-
: { level: "unattested" as const, reason: policy.problems.length ? policy.problems.join("; ") : `no signers file (${policy.signersPath}) at base; attestation is off` };
|
|
1109
|
+
const signature = signatureOf(c.sha);
|
|
996
1110
|
const pr = c.subject.match(/\(#([0-9]+)\)\s*$/);
|
|
997
1111
|
const cid = `commit:${c.sha}`;
|
|
998
|
-
|
|
1112
|
+
const f = follows.get(c.sha);
|
|
1113
|
+
const squash: SquashNode | undefined = f
|
|
1114
|
+
? {
|
|
1115
|
+
pullRequest: f.pullRequest,
|
|
1116
|
+
forge: f.forge,
|
|
1117
|
+
ref: f.ref,
|
|
1118
|
+
head: f.head,
|
|
1119
|
+
fetched: f.fetched,
|
|
1120
|
+
followed: f.followed,
|
|
1121
|
+
commits: originalsOf(c.sha).map((o) => ({ sha: o.sha, subject: o.subject, author: o.author, date: o.date, trailers: o.trailers, signature: signatureOf(o.sha), joins: trailerJoins.get(o.sha)!.joins })),
|
|
1122
|
+
}
|
|
1123
|
+
: undefined;
|
|
1124
|
+
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, ...(squash ? { squash } : {}) });
|
|
999
1125
|
edges.push({ kind: "touched-by", from: rid, to: cid, lines: t.lines });
|
|
1000
1126
|
|
|
1001
|
-
// 2. Each commit's origin, from the plugins.
|
|
1127
|
+
// 2. Each commit's origin, from the plugins. A followed squash also takes its original commits' (#3035).
|
|
1002
1128
|
const entry: Joined = { contracts: [], authorship: [], decisions: [], carries: [] };
|
|
1003
1129
|
joined.set(c.sha, entry);
|
|
1130
|
+
const pushEdge = (e: IntentEdge) => {
|
|
1131
|
+
if (!edges.some((x) => x.kind === e.kind && x.from === e.from && x.to === e.to)) edges.push(e);
|
|
1132
|
+
};
|
|
1133
|
+
const subjects: IntentCommit[] = [c, ...originalsOf(c.sha)];
|
|
1004
1134
|
for (const k of kinds) {
|
|
1005
1135
|
if (!k.joins) continue;
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
result
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1136
|
+
for (const subject of subjects) {
|
|
1137
|
+
const original = subject !== c;
|
|
1138
|
+
let result;
|
|
1139
|
+
try {
|
|
1140
|
+
result = await runCommitJoins(k.joins, subject, { read: readAt, list: listAt, at: located.at }, k.name);
|
|
1141
|
+
} catch (err) {
|
|
1142
|
+
const message = `${k.display}: commitJoins failed for ${subject.sha.slice(0, 8)}: ${err instanceof Error ? err.message : String(err)}`;
|
|
1143
|
+
if (!failedPlugins.has(message)) reasons.push({ code: "intent-plugin-failed", message });
|
|
1144
|
+
failedPlugins.add(message);
|
|
1145
|
+
continue;
|
|
1146
|
+
}
|
|
1147
|
+
const joinedNode = (kind: JoinedNode["kind"], e: JoinedEntity): string => {
|
|
1148
|
+
const { id, ...data } = e;
|
|
1149
|
+
return add<JoinedNode>({ id: `${kind}:${id}`, kind, ref: id, plugin: k.display, data }).id;
|
|
1150
|
+
};
|
|
1151
|
+
const unitId = result.unit ? joinedNode("unit", result.unit) : undefined;
|
|
1152
|
+
const contractId = result.contract ? joinedNode("contract", result.contract) : undefined;
|
|
1153
|
+
if (unitId) {
|
|
1154
|
+
if (original) entry.unit ??= unitId;
|
|
1155
|
+
else entry.unit = unitId;
|
|
1156
|
+
pushEdge({ kind: "produced-by", from: cid, to: unitId });
|
|
1157
|
+
if (contractId) pushEdge({ kind: "serves", from: unitId, to: contractId });
|
|
1158
|
+
}
|
|
1159
|
+
if (contractId) entry.contracts.push(result.contract!.id);
|
|
1160
|
+
if (unitId) entry.decisions.push(...entityDecisions(result.unit), ...entityDecisions(result.contract));
|
|
1161
|
+
for (const f of result.findings ?? []) pluginFindings.push({ commit: cid, plugin: k.display, finding: f });
|
|
1162
|
+
const evidence = result.evidence === undefined ? [] : Array.isArray(result.evidence) ? result.evidence : [result.evidence];
|
|
1163
|
+
for (const e of evidence) pushEdge({ kind: "cites-evidence", from: unitId ?? contractId ?? cid, to: joinedNode("evidence", e) });
|
|
1164
|
+
// Authorship is checked against the squash's own trailers, so only its own claims count.
|
|
1165
|
+
if (!original) entry.authorship.push(...(result.authorship ?? []));
|
|
1025
1166
|
}
|
|
1026
|
-
if (contractId) entry.contracts.push(result.contract!.id);
|
|
1027
|
-
if (unitId) entry.decisions.push(...entityDecisions(result.unit), ...entityDecisions(result.contract));
|
|
1028
|
-
for (const f of result.findings ?? []) pluginFindings.push({ commit: cid, plugin: k.display, finding: f });
|
|
1029
|
-
const evidence = result.evidence === undefined ? [] : Array.isArray(result.evidence) ? result.evidence : [result.evidence];
|
|
1030
|
-
for (const e of evidence) edges.push({ kind: "cites-evidence", from: unitId ?? contractId ?? cid, to: joinedNode("evidence", e) });
|
|
1031
|
-
entry.authorship.push(...(result.authorship ?? []));
|
|
1032
1167
|
}
|
|
1033
1168
|
}
|
|
1034
1169
|
|
|
@@ -1036,8 +1171,10 @@ async function walk(query: IntentQuery, head: Head): Promise<IntentResult> {
|
|
|
1036
1171
|
const runJoins = await runsForCommits(
|
|
1037
1172
|
top,
|
|
1038
1173
|
located.rootOnDisk,
|
|
1039
|
-
touched.map((t) => ({ sha
|
|
1174
|
+
[...new Set([...touched.map((t) => t.sha), ...originalDetails.keys()])].map((sha) => ({ sha, run: trailerJoins.get(sha)?.joins.run ?? null })),
|
|
1040
1175
|
);
|
|
1176
|
+
// A followed squash joins its original commits' runs (#3035).
|
|
1177
|
+
addSquashRuns(runJoins.refs, follows);
|
|
1041
1178
|
for (const t of touched) {
|
|
1042
1179
|
if (!nodes.has(`commit:${t.sha}`)) continue;
|
|
1043
1180
|
for (const ref of runJoins.refs.get(t.sha) ?? []) {
|
|
@@ -1063,9 +1200,15 @@ async function walk(query: IntentQuery, head: Head): Promise<IntentResult> {
|
|
|
1063
1200
|
instruction: r?.instruction ?? null,
|
|
1064
1201
|
lease: r?.lease ?? null,
|
|
1065
1202
|
});
|
|
1066
|
-
edges.push({ kind: "made-by", from: `commit:${t.sha}`, to: node.id, joinedBy: ref.joinedBy });
|
|
1203
|
+
edges.push({ kind: "made-by", from: `commit:${t.sha}`, to: node.id, joinedBy: ref.joinedBy, ...(ref.recordedAs ? { recordedAs: ref.recordedAs } : {}), ...(ref.via ? { via: ref.via } : {}) });
|
|
1067
1204
|
}
|
|
1068
1205
|
}
|
|
1206
|
+
// A commit a trailer or a record joins to one run, with the content of a commit another run recorded (#3036).
|
|
1207
|
+
for (const x of runJoins.conflicts) {
|
|
1208
|
+
if (!nodes.has(`commit:${x.sha}`)) continue;
|
|
1209
|
+
const others = x.content.map((m) => `${m.run} (${m.sha.slice(0, 8)})`).join(", ");
|
|
1210
|
+
find("intent-commit-join-conflict", `${x.sha.slice(0, 8)} joins ${x.joined.join(", ")} by its trailer or the run's record, and has the patch-id of a commit ${others} recorded; it is not joined to ${x.content.length === 1 ? "that run" : "those runs"}`, [`commit:${x.sha}`, ...x.joined.map((id) => `run:${id}`)]);
|
|
1211
|
+
}
|
|
1069
1212
|
|
|
1070
1213
|
// 3. The decisions whose constrains cover the region, and their chains.
|
|
1071
1214
|
const regionFull = region.path === "." ? workspacePrefix : gitPath;
|
|
@@ -1420,24 +1563,27 @@ async function walk(query: IntentQuery, head: Head): Promise<IntentResult> {
|
|
|
1420
1563
|
j.decisions.push(...implementedIds(h).map((id) => id.slice("record:".length)));
|
|
1421
1564
|
return node.id;
|
|
1422
1565
|
};
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
if (
|
|
1429
|
-
|
|
1566
|
+
// The commit's own trailers, and a followed squash's original commits' (#3035).
|
|
1567
|
+
for (const joins of [c.joins, ...(c.squash?.commits ?? []).map((o) => o.joins)]) {
|
|
1568
|
+
for (const r of joins.records) {
|
|
1569
|
+
const k = recordKinds.get(r.kind);
|
|
1570
|
+
if (!k) continue;
|
|
1571
|
+
if (k.records!.loaded.kind.work) {
|
|
1572
|
+
const h = workByKey.get(`${r.kind}/${r.id}`);
|
|
1573
|
+
if (h) r.node = carryWork(h);
|
|
1574
|
+
continue;
|
|
1575
|
+
}
|
|
1576
|
+
const hit = decisionById.get(`${r.kind}/${r.id}`);
|
|
1577
|
+
if (!hit) continue;
|
|
1578
|
+
r.node = decisionNode(hit.kind, hit.view).id;
|
|
1579
|
+
carry(r.node);
|
|
1580
|
+
j.decisions.push(`${r.kind}/${r.id}`);
|
|
1581
|
+
}
|
|
1582
|
+
const item = joins.lease?.item;
|
|
1583
|
+
if (item) {
|
|
1584
|
+
const hits = workKinds.map((k) => workByKey.get(`${k.records!.loaded.kind.name}/${item}`)).filter((h): h is WorkHit => h !== undefined);
|
|
1585
|
+
if (hits.length === 1) carryWork(hits[0]);
|
|
1430
1586
|
}
|
|
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
1587
|
}
|
|
1442
1588
|
// The runs that made the commit (#3034): its run's work item and the
|
|
1443
1589
|
// records the run names are carried as a Chant-Lease and a Chant-Record are.
|
|
@@ -1651,6 +1797,35 @@ async function walk(query: IntentQuery, head: Head): Promise<IntentResult> {
|
|
|
1651
1797
|
for (const h of by) edges.push({ kind: "addressed-by", from: f.id, to: workNode(h).id });
|
|
1652
1798
|
}
|
|
1653
1799
|
}
|
|
1800
|
+
// Reviews that led to a work item in the graph (#3154, E21): a session's comments[].follow_ups name it.
|
|
1801
|
+
const workNodes = [...nodes.values()].filter((n): n is WorkNode => n.kind === "work");
|
|
1802
|
+
if (workNodes.length > 0) {
|
|
1803
|
+
const byRef = new Map(workNodes.map((n) => [`${n.recordKind}:${n.record}`, n]));
|
|
1804
|
+
for (const k of kinds) {
|
|
1805
|
+
const loaded = k.records?.loaded.kind;
|
|
1806
|
+
if (!loaded?.session) continue;
|
|
1807
|
+
for (const v of k.records!.views) {
|
|
1808
|
+
if (v.id === null || !v.data) continue;
|
|
1809
|
+
const hits = new Map<WorkNode, number[]>();
|
|
1810
|
+
const note = (refs: unknown, comment: number | null) => {
|
|
1811
|
+
if (!Array.isArray(refs)) return;
|
|
1812
|
+
for (const ref of refs) {
|
|
1813
|
+
const n = typeof ref === "string" ? byRef.get(ref) : undefined;
|
|
1814
|
+
if (!n) continue;
|
|
1815
|
+
const list = hits.get(n) ?? [];
|
|
1816
|
+
if (comment !== null && !list.includes(comment)) list.push(comment);
|
|
1817
|
+
hits.set(n, list);
|
|
1818
|
+
}
|
|
1819
|
+
};
|
|
1820
|
+
note(v.data.follow_ups, null);
|
|
1821
|
+
const comments = Array.isArray(v.data.comments) ? v.data.comments : [];
|
|
1822
|
+
comments.forEach((c: unknown, i: number) => note((c as { follow_ups?: unknown } | null)?.follow_ups, i + 1));
|
|
1823
|
+
for (const [n, list] of hits) {
|
|
1824
|
+
(n.reviews ??= []).push({ id: `record:${loaded.name}/${v.id}`, recordKind: loaded.name, record: v.id, path: v.path, state: v.state, comments: list.sort((a, b) => a - b) });
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1654
1829
|
for (const f of findings) nodes.set(f.id, f);
|
|
1655
1830
|
const why = explain();
|
|
1656
1831
|
const all = [...nodes.values()];
|
|
@@ -1679,25 +1854,43 @@ async function walk(query: IntentQuery, head: Head): Promise<IntentResult> {
|
|
|
1679
1854
|
/** "Why is it like this" over the region (#3034): who made its current lines, and the decisions governing it, most relevant first. */
|
|
1680
1855
|
function explain(): WhyAnswer {
|
|
1681
1856
|
const hunksOf = (run: string, sha: string) => runJoins.runs.get(run)?.commits.find((c) => c.sha === sha)?.hunks ?? null;
|
|
1857
|
+
const joinOf = (run: string, sha: string): RunJoin[] => (runJoins.refs.get(sha) ?? []).find((r) => r.id === run)?.joinedBy ?? [];
|
|
1858
|
+
const joinsOf = (runs: string[], shas: string[]): RunJoin[] => {
|
|
1859
|
+
const all = new Set(runs.flatMap((id) => shas.flatMap((sha) => joinOf(id, sha))));
|
|
1860
|
+
return RUN_JOIN_ORDER.filter((j) => all.has(j));
|
|
1861
|
+
};
|
|
1862
|
+
// For a followed squash (#3035), the original commit behind each of its lines, by blame at the pull request's head.
|
|
1863
|
+
const origins = new Map<string, Map<number, string> | undefined>();
|
|
1864
|
+
const originOf = (sha: string, path: string, line: number): { squash: boolean; origin: string | undefined } => {
|
|
1865
|
+
const f = follows.get(sha);
|
|
1866
|
+
if (!f?.followed || !f.head) return { squash: false, origin: undefined };
|
|
1867
|
+
const key = `${sha}\0${path}`;
|
|
1868
|
+
if (!origins.has(key)) origins.set(key, squashLineOrigins(top!, sha, f.head, path, new Set(f.commits)));
|
|
1869
|
+
return { squash: true, origin: origins.get(key)?.get(line) };
|
|
1870
|
+
};
|
|
1682
1871
|
// Each current line: its commit, and the runs that wrote it.
|
|
1683
1872
|
const who = blamed.map((b) => {
|
|
1684
|
-
if (b.sha === null) return { line: b.line, sha: null, runs: [] as string[], narrowedBy: null as "hunks" | null };
|
|
1873
|
+
if (b.sha === null) return { line: b.line, sha: null, runs: [] as string[], narrowedBy: null as "hunks" | null, via: undefined as string[] | undefined };
|
|
1685
1874
|
const sha = b.sha;
|
|
1686
|
-
const
|
|
1875
|
+
const { squash, origin } = originOf(sha, b.origPath, b.origLine);
|
|
1876
|
+
const via = squash ? (origin ? [origin] : []) : undefined;
|
|
1877
|
+
// On a squash line blame at the head traced to one original, a run joined only through the other originals didn't write it.
|
|
1878
|
+
const refs = (runJoins.refs.get(sha) ?? []).filter((r) => !origin || r.joinedBy.some((j) => j !== "squash") || (r.via ?? []).includes(origin)).map((r) => r.id);
|
|
1687
1879
|
const withHunks = refs.filter((id) => hunksOf(id, sha) !== null);
|
|
1688
|
-
if (withHunks.length === 0) return { line: b.line, sha, runs: refs, narrowedBy: null };
|
|
1880
|
+
if (withHunks.length === 0) return { line: b.line, sha, runs: refs, narrowedBy: null, via };
|
|
1689
1881
|
const wrote = withHunks.filter((id) => hunksOf(id, sha)!.some((h) => h.path === b.origPath && h.start <= b.origLine && b.origLine <= h.end));
|
|
1690
1882
|
// 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 };
|
|
1883
|
+
return { line: b.line, sha, runs: wrote.length > 0 ? wrote : refs.filter((id) => !withHunks.includes(id)), narrowedBy: "hunks" as const, via };
|
|
1692
1884
|
});
|
|
1693
1885
|
const spans: WhySpan[] = [];
|
|
1694
1886
|
for (const w of who) {
|
|
1695
1887
|
const last = spans[spans.length - 1];
|
|
1696
|
-
|
|
1888
|
+
const via = w.via;
|
|
1889
|
+
if (last && last.end === w.line - 1 && last.sha === w.sha && last.narrowedBy === w.narrowedBy && last.runs.join("\0") === w.runs.map((r) => `run:${r}`).join("\0") && last.via?.join("\0") === via?.join("\0")) {
|
|
1697
1890
|
last.end = w.line;
|
|
1698
1891
|
continue;
|
|
1699
1892
|
}
|
|
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 });
|
|
1893
|
+
spans.push({ start: w.line, end: w.line, commit: w.sha ? `commit:${w.sha}` : null, sha: w.sha, runs: w.runs.map((r) => `run:${r}`), narrowedBy: w.narrowedBy, joinedBy: w.sha ? joinsOf(w.runs, [w.sha]) : [], ...(via ? { via } : {}) });
|
|
1701
1894
|
}
|
|
1702
1895
|
const ranges = (lines: number[]): LineRange[] => {
|
|
1703
1896
|
const out: LineRange[] = [];
|
|
@@ -1745,7 +1938,8 @@ async function walk(query: IntentQuery, head: Head): Promise<IntentResult> {
|
|
|
1745
1938
|
if (t?.kind === "decision") carried.add(t.id);
|
|
1746
1939
|
if (t?.kind === "work") for (const e of edges) if (e.kind === "implements" && e.from === t.id) carried.add(e.to);
|
|
1747
1940
|
}
|
|
1748
|
-
|
|
1941
|
+
const shas = commits.map((c) => c.slice("commit:".length));
|
|
1942
|
+
return { run: n.id, lines, commits, unit: r?.unit ? { id: r.unit.id, kind: r.unit.kind, node: work?.id ?? null } : null, decisions: [...carried], joinedBy: joinsOf([n.run], shas) };
|
|
1749
1943
|
};
|
|
1750
1944
|
let runs: WhyRun[];
|
|
1751
1945
|
if (fileLines !== null) {
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Editing one property of a JSON or JSONC document in place (#3308): the
|
|
3
|
+
* rest of the text is left as it was, comments and trailing commas included.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, expect, test } from "vitest";
|
|
7
|
+
import { removeProperty, setProperty, valueAt } from "./json-edit";
|
|
8
|
+
import { parseJsonText } from "./jsonc";
|
|
9
|
+
|
|
10
|
+
const read = (text: string, jsonc = false) => {
|
|
11
|
+
const r = parseJsonText(text, { jsonc });
|
|
12
|
+
if (!r.ok) throw new Error(r.message);
|
|
13
|
+
return r.value;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const JSON_TEXT = `{
|
|
17
|
+
"name": "w",
|
|
18
|
+
"members": [
|
|
19
|
+
{ "name": "a", "box": { "services": [] } },
|
|
20
|
+
{
|
|
21
|
+
"name": "b",
|
|
22
|
+
"box": {
|
|
23
|
+
"intent": "ws-001"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
const JSONC_TEXT = `{
|
|
31
|
+
// the workspace
|
|
32
|
+
"name": "w",
|
|
33
|
+
"members": [
|
|
34
|
+
{
|
|
35
|
+
"name": "b",
|
|
36
|
+
"box": {
|
|
37
|
+
"listing": {
|
|
38
|
+
"title": "Old", // kept as is
|
|
39
|
+
"line": "one line",
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
}
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
describe("setProperty", () => {
|
|
48
|
+
test("replaces a value in place and leaves every other byte", () => {
|
|
49
|
+
const out = setProperty(JSON_TEXT, false, "/members/1/box", "intent", "ws-002");
|
|
50
|
+
expect(out).toBe(JSON_TEXT.replace('"ws-001"', '"ws-002"'));
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("adds a key after the last one, indented like it, and an object value on its own lines", () => {
|
|
54
|
+
const out = setProperty(JSON_TEXT, false, "/members/1/box", "listing", { title: "B", published: false });
|
|
55
|
+
expect(out).toContain(` "intent": "ws-001",\n "listing": {\n "title": "B",\n "published": false\n }\n }`);
|
|
56
|
+
expect(read(out)).toEqual({ ...(read(JSON_TEXT) as object), members: [{ name: "a", box: { services: [] } }, { name: "b", box: { intent: "ws-001", listing: { title: "B", published: false } } }] });
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("keeps an object on one line on one line", () => {
|
|
60
|
+
const out = setProperty(JSON_TEXT, false, "/members/0/box", "listing", { title: "A" });
|
|
61
|
+
expect(out).toContain(`{ "name": "a", "box": { "services": [], "listing": {"title":"A"} } }`);
|
|
62
|
+
expect(valueAt(read(out), "/members/0/box/listing")).toEqual({ title: "A" });
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("fills an empty object", () => {
|
|
66
|
+
const out = setProperty('{\n "box": {}\n}\n', false, "/box", "listing", { title: "T" });
|
|
67
|
+
expect(out).toBe('{\n "box": {\n "listing": {\n "title": "T"\n }\n }\n}\n');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("in JSONC, keeps comments with their lines and the trailing comma style", () => {
|
|
71
|
+
const out = setProperty(JSONC_TEXT, true, "/members/0/box/listing", "published", false);
|
|
72
|
+
expect(out).toContain(` "line": "one line",\n "published": false,\n },`);
|
|
73
|
+
const out2 = setProperty(JSONC_TEXT, true, "/members/0/box/listing", "title", "New");
|
|
74
|
+
expect(out2).toBe(JSONC_TEXT.replace('"Old"', '"New"'));
|
|
75
|
+
expect(out2).toContain("// kept as is");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("puts a new key after a comment that ends the last property's line", () => {
|
|
79
|
+
const text = '{\n "a": 1, // one\n "b": 2 // two\n}\n';
|
|
80
|
+
const out = setProperty(text, true, "", "c", 3);
|
|
81
|
+
expect(out).toBe('{\n "a": 1, // one\n "b": 2, // two\n "c": 3\n}\n');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("keeps CRLF line endings", () => {
|
|
85
|
+
const text = '{\r\n "box": {\r\n "intent": "x"\r\n }\r\n}\r\n';
|
|
86
|
+
const out = setProperty(text, false, "/box", "listing", { title: "T" });
|
|
87
|
+
expect(out).toBe('{\r\n "box": {\r\n "intent": "x",\r\n "listing": {\r\n "title": "T"\r\n }\r\n }\r\n}\r\n');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("refuses when there is no object at the pointer", () => {
|
|
91
|
+
expect(() => setProperty(JSON_TEXT, false, "/members/5/box", "listing", {})).toThrow(/no object at \/members\/5\/box/);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe("removeProperty", () => {
|
|
96
|
+
test("removes a key with the comma after it, and its line", () => {
|
|
97
|
+
const text = '{\n "a": 1,\n "b": 2,\n "c": 3\n}\n';
|
|
98
|
+
expect(removeProperty(text, false, "", "b")).toBe('{\n "a": 1,\n "c": 3\n}\n');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("removes the last key with the comma before it", () => {
|
|
102
|
+
const text = '{\n "a": 1,\n "b": 2\n}\n';
|
|
103
|
+
expect(removeProperty(text, false, "", "b")).toBe('{\n "a": 1\n}\n');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("removes a key on a one-line object", () => {
|
|
107
|
+
expect(removeProperty('{ "a": 1, "b": 2 }', false, "", "a")).toBe('{ "b": 2 }');
|
|
108
|
+
expect(removeProperty('{ "a": 1, "b": 2 }', false, "", "b")).toBe('{ "a": 1 }');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("leaves {} when the only key goes, and the text unchanged when the key is not there", () => {
|
|
112
|
+
expect(removeProperty('{\n "box": {\n "listing": {\n "a": 1\n }\n }\n}', false, "/box/listing", "a")).toBe('{\n "box": {\n "listing": {}\n }\n}');
|
|
113
|
+
expect(removeProperty('{ "a": 1 }', false, "", "z")).toBe('{ "a": 1 }');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("in JSONC, removes a key with its trailing comma and keeps the comments of the others", () => {
|
|
117
|
+
const out = removeProperty(JSONC_TEXT, true, "/members/0/box/listing", "line");
|
|
118
|
+
expect(out).toContain(`"title": "Old", // kept as is\n },`);
|
|
119
|
+
expect(valueAt(read(out, true), "/members/0/box/listing")).toEqual({ title: "Old" });
|
|
120
|
+
});
|
|
121
|
+
});
|