@intentius/chant 0.91.0 → 0.93.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +6 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/workspace-tools.d.ts +2 -1
- package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
- package/dist/cli/plugins.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +12 -2
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
- package/dist/config.d.ts +22 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/lexicon-module.d.ts +22 -0
- package/dist/lexicon-module.d.ts.map +1 -1
- package/dist/lifecycle/converge-ledger.d.ts +19 -0
- package/dist/lifecycle/converge-ledger.d.ts.map +1 -1
- package/dist/lifecycle/run-ledger.d.ts +2 -0
- package/dist/lifecycle/run-ledger.d.ts.map +1 -1
- package/dist/lifecycle/symptoms.d.ts +51 -0
- package/dist/lifecycle/symptoms.d.ts.map +1 -1
- package/dist/lint/rules/index.d.ts +4 -2
- package/dist/lint/rules/index.d.ts.map +1 -1
- package/dist/lint/rules/op/index.d.ts +14 -3
- package/dist/lint/rules/op/index.d.ts.map +1 -1
- package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts.map +1 -1
- package/dist/lint/rules/op/sys010-key-over-plain-http.d.ts +29 -0
- package/dist/lint/rules/op/sys010-key-over-plain-http.d.ts.map +1 -0
- package/dist/lint/rules/sys001-no-literal-key.d.ts +3 -0
- package/dist/lint/rules/sys001-no-literal-key.d.ts.map +1 -0
- package/dist/op/__fixtures__/decide-stub-backend.d.ts +53 -0
- package/dist/op/__fixtures__/decide-stub-backend.d.ts.map +1 -0
- package/dist/op/activities/activity-contracts.d.ts +201 -0
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/apply.d.ts.map +1 -1
- package/dist/op/activities/change-coverage.d.ts +44 -0
- package/dist/op/activities/change-coverage.d.ts.map +1 -0
- package/dist/op/activities/converge.d.ts +24 -1
- package/dist/op/activities/converge.d.ts.map +1 -1
- package/dist/op/activities/decide.d.ts +107 -0
- package/dist/op/activities/decide.d.ts.map +1 -0
- package/dist/op/activities/index.d.ts +10 -0
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/shell.d.ts +32 -0
- package/dist/op/activities/shell.d.ts.map +1 -1
- package/dist/op/activities/source-release.d.ts +86 -0
- package/dist/op/activities/source-release.d.ts.map +1 -0
- package/dist/op/activities/source-rollback.d.ts +100 -0
- package/dist/op/activities/source-rollback.d.ts.map +1 -0
- package/dist/op/activities/work-evidence.d.ts +42 -0
- package/dist/op/activities/work-evidence.d.ts.map +1 -0
- package/dist/op/activity-contract.d.ts +1 -0
- package/dist/op/activity-contract.d.ts.map +1 -1
- package/dist/op/activity-profiles.d.ts +16 -0
- package/dist/op/activity-profiles.d.ts.map +1 -1
- package/dist/op/builders.d.ts +84 -2
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/composites/converge-op.d.ts +31 -4
- package/dist/op/composites/converge-op.d.ts.map +1 -1
- package/dist/op/decide-backend.d.ts +116 -0
- package/dist/op/decide-backend.d.ts.map +1 -0
- package/dist/op/decide-config.d.ts +120 -0
- package/dist/op/decide-config.d.ts.map +1 -0
- package/dist/op/decide-read-inputs.d.ts +40 -0
- package/dist/op/decide-read-inputs.d.ts.map +1 -0
- package/dist/op/gate-wait.d.ts +43 -0
- package/dist/op/gate-wait.d.ts.map +1 -0
- package/dist/op/gate.d.ts +5 -2
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/op/index.d.ts +11 -4
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/local-executor.d.ts +29 -4
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-output.d.ts +2 -0
- package/dist/op/local-output.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/operator.d.ts +97 -1
- package/dist/op/operator.d.ts.map +1 -1
- package/dist/op/runtime.d.ts +37 -5
- package/dist/op/runtime.d.ts.map +1 -1
- package/dist/op/runtimes/local.d.ts.map +1 -1
- package/dist/op/source-archive.d.ts +63 -0
- package/dist/op/source-archive.d.ts.map +1 -0
- package/dist/op/steward-points.d.ts +136 -0
- package/dist/op/steward-points.d.ts.map +1 -0
- package/dist/op/steward-turn.d.ts +53 -0
- package/dist/op/steward-turn.d.ts.map +1 -0
- package/dist/op/steward.d.ts +22 -1
- package/dist/op/steward.d.ts.map +1 -1
- package/dist/op/types.d.ts +7 -0
- package/dist/op/types.d.ts.map +1 -1
- package/dist/op/work-lease-run.d.ts +1 -1
- package/dist/op/work-lease-run.d.ts.map +1 -1
- package/dist/workspace/__fixtures__/decide-workspace.d.ts +100 -0
- package/dist/workspace/__fixtures__/decide-workspace.d.ts.map +1 -0
- package/dist/workspace/answers-ledger.d.ts +76 -0
- package/dist/workspace/answers-ledger.d.ts.map +1 -0
- package/dist/workspace/changes-cli.d.ts +16 -0
- package/dist/workspace/changes-cli.d.ts.map +1 -0
- package/dist/workspace/changes.d.ts +200 -0
- package/dist/workspace/changes.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.d.ts +59 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.d.ts +51 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.d.ts +26 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.d.ts +59 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.d.ts +22 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts +71 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts.map +1 -0
- package/dist/workspace/chant-migrations.d.ts +99 -0
- package/dist/workspace/chant-migrations.d.ts.map +1 -0
- package/dist/workspace/checks/boxes.d.ts +34 -1
- package/dist/workspace/checks/boxes.d.ts.map +1 -1
- package/dist/workspace/checks/diagrams.d.ts +31 -0
- package/dist/workspace/checks/diagrams.d.ts.map +1 -0
- package/dist/workspace/checks/records.d.ts +7 -0
- package/dist/workspace/checks/records.d.ts.map +1 -1
- package/dist/workspace/checks.d.ts +3 -0
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/compose-graph.d.ts +2 -0
- package/dist/workspace/compose-graph.d.ts.map +1 -1
- package/dist/workspace/conformance/index.d.ts.map +1 -1
- package/dist/workspace/decide.d.ts +49 -4
- package/dist/workspace/decide.d.ts.map +1 -1
- package/dist/workspace/declaration.d.ts +52 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.schema.json +114 -1
- package/dist/workspace/declared-kinds.d.ts +20 -0
- package/dist/workspace/declared-kinds.d.ts.map +1 -1
- package/dist/workspace/generated-files.d.ts +7 -0
- package/dist/workspace/generated-files.d.ts.map +1 -1
- package/dist/workspace/intent.d.ts.map +1 -1
- package/dist/workspace/lineage-check.d.ts +3 -0
- package/dist/workspace/lineage-check.d.ts.map +1 -1
- package/dist/workspace/lineage-upgrade.d.ts +6 -1
- package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
- package/dist/workspace/ls.d.ts +39 -1
- package/dist/workspace/ls.d.ts.map +1 -1
- package/dist/workspace/point-answer.schema.json +95 -0
- package/dist/workspace/points-cli.d.ts.map +1 -1
- package/dist/workspace/points.d.ts +1 -1
- package/dist/workspace/reason-codes.d.ts +14 -2
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/records-cli.d.ts +11 -1
- package/dist/workspace/records-cli.d.ts.map +1 -1
- package/dist/workspace/records-close.d.ts +1 -1
- package/dist/workspace/records-close.d.ts.map +1 -1
- package/dist/workspace/records-write.d.ts +11 -5
- package/dist/workspace/records-write.d.ts.map +1 -1
- package/dist/workspace/records.d.ts +34 -4
- package/dist/workspace/records.d.ts.map +1 -1
- package/dist/workspace/status-gates.d.ts +7 -2
- package/dist/workspace/status-gates.d.ts.map +1 -1
- package/dist/workspace/status-stewards.d.ts +59 -1
- package/dist/workspace/status-stewards.d.ts.map +1 -1
- package/dist/workspace/status.d.ts +16 -0
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/work-cli.d.ts +113 -2
- package/dist/workspace/work-cli.d.ts.map +1 -1
- package/dist/workspace/work-evidence.d.ts +74 -0
- package/dist/workspace/work-evidence.d.ts.map +1 -0
- package/dist/workspace/work.d.ts +43 -1
- package/dist/workspace/work.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cli/commands/audit.test.ts +9 -1
- package/src/cli/commands/build.ts +3 -2
- package/src/cli/commands/lint.ts +7 -1
- package/src/cli/handlers/graph.test.ts +20 -0
- package/src/cli/handlers/graph.ts +16 -2
- package/src/cli/handlers/operator.ts +34 -2
- package/src/cli/handlers/run.test.ts +154 -2
- package/src/cli/handlers/run.ts +121 -2
- package/src/cli/main.test.ts +10 -0
- package/src/cli/main.ts +19 -0
- package/src/cli/mcp/workspace-tools.test.ts +35 -19
- package/src/cli/mcp/workspace-tools.ts +62 -4
- package/src/cli/plugins.ts +11 -3
- package/src/cli/registry.ts +12 -2
- package/src/codegen/pinned-upgrade.ts +2 -1
- package/src/codegen/release-wiring.test.ts +49 -2
- package/src/components/capability-plugin-loader.ts +3 -3
- package/src/components/cli-support.ts +2 -2
- package/src/config.ts +11 -0
- package/src/lexicon-module.ts +56 -1
- package/src/lexicon-package.test.ts +96 -0
- package/src/lifecycle/converge-ledger.ts +20 -0
- package/src/lifecycle/run-ledger.ts +6 -0
- package/src/lifecycle/symptoms.ts +95 -0
- package/src/lint/rules/index.ts +6 -2
- package/src/lint/rules/op/index.ts +17 -4
- package/src/lint/rules/op/ops014-converge-rule-refusals.test.ts +23 -1
- package/src/lint/rules/op/ops014-converge-rule-refusals.ts +10 -6
- package/src/lint/rules/op/sys010-key-over-plain-http.test.ts +52 -0
- package/src/lint/rules/op/sys010-key-over-plain-http.ts +100 -0
- package/src/lint/rules/sys001-no-literal-key.test.ts +31 -0
- package/src/lint/rules/sys001-no-literal-key.ts +67 -0
- package/src/op/__fixtures__/decide-stub-backend.ts +143 -0
- package/src/op/activities/activity-contracts.ts +190 -1
- package/src/op/activities/apply.ts +7 -6
- package/src/op/activities/change-coverage.ts +60 -0
- package/src/op/activities/converge.test.ts +55 -1
- package/src/op/activities/converge.ts +208 -29
- package/src/op/activities/decide.live.test.ts +78 -0
- package/src/op/activities/decide.test.ts +300 -0
- package/src/op/activities/decide.ts +267 -0
- package/src/op/activities/index.ts +21 -0
- package/src/op/activities/shell.test.ts +91 -1
- package/src/op/activities/shell.ts +91 -2
- package/src/op/activities/source-release.test.ts +118 -0
- package/src/op/activities/source-release.ts +168 -0
- package/src/op/activities/source-rollback.test.ts +131 -0
- package/src/op/activities/source-rollback.ts +239 -0
- package/src/op/activities/work-evidence.ts +62 -0
- package/src/op/activity-contract-registry.test.ts +2 -0
- package/src/op/activity-contract-registry.ts +2 -2
- package/src/op/activity-contract.test.ts +12 -0
- package/src/op/activity-contract.ts +6 -1
- package/src/op/activity-profiles.ts +29 -0
- package/src/op/activity-registry.test.ts +1 -1
- package/src/op/activity-registry.ts +2 -2
- package/src/op/builders.ts +119 -5
- package/src/op/command-gate.test.ts +192 -0
- package/src/op/composites/converge-op.test.ts +27 -1
- package/src/op/composites/converge-op.ts +69 -15
- package/src/op/decide-backend.test.ts +44 -0
- package/src/op/decide-backend.ts +212 -0
- package/src/op/decide-config.test.ts +52 -0
- package/src/op/decide-config.ts +80 -0
- package/src/op/decide-read-inputs.ts +117 -0
- package/src/op/gate-approval.ts +2 -2
- package/src/op/gate-wait.ts +57 -0
- package/src/op/gate.ts +8 -3
- package/src/op/generate-pipeline.ts +2 -2
- package/src/op/index.ts +18 -3
- package/src/op/local-executor.ts +151 -15
- package/src/op/local-output.test.ts +33 -0
- package/src/op/local-output.ts +20 -2
- package/src/op/op-ir.test.ts +10 -0
- package/src/op/op-ir.ts +4 -0
- package/src/op/op-verb-class.ts +1 -0
- package/src/op/operator.ts +290 -11
- package/src/op/runtime.ts +31 -8
- package/src/op/runtimes/local.ts +3 -1
- package/src/op/source-archive.ts +161 -0
- package/src/op/steward-points.test.ts +313 -0
- package/src/op/steward-points.ts +194 -0
- package/src/op/steward-turn.ts +79 -0
- package/src/op/steward.test.ts +87 -3
- package/src/op/steward.ts +25 -1
- package/src/op/types.ts +7 -0
- package/src/op/work-lease-run.test.ts +20 -1
- package/src/op/work-lease-run.ts +2 -2
- package/src/workspace/__fixtures__/decide-workspace.ts +129 -0
- package/src/workspace/__fixtures__/studio-template-84c8b21.json +67 -0
- package/src/workspace/answers-ledger.ts +118 -0
- package/src/workspace/changes-cli.ts +56 -0
- package/src/workspace/changes.schema.json +472 -0
- package/src/workspace/changes.test.ts +315 -0
- package/src/workspace/changes.ts +512 -0
- package/src/workspace/chant-migrations/chud-lexicon-exit-fly-site.ts +234 -0
- package/src/workspace/chant-migrations/chud-lexicon-exit-live-names.ts +164 -0
- package/src/workspace/chant-migrations/chud-lexicon-exit-rollback.ts +223 -0
- package/src/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.ts +195 -0
- package/src/workspace/chant-migrations/chud-lexicon-exit-ship-policy.ts +108 -0
- package/src/workspace/chant-migrations/chud-lexicon-exit.ts +882 -0
- package/src/workspace/chant-migrations.e2e.test.ts +1089 -0
- package/src/workspace/chant-migrations.ts +164 -0
- package/src/workspace/check-contract.test.ts +4 -2
- package/src/workspace/check.schema.json +15 -5
- package/src/workspace/checks/boxes.test.ts +80 -1
- package/src/workspace/checks/boxes.ts +136 -1
- package/src/workspace/checks/diagrams.test.ts +134 -0
- package/src/workspace/checks/diagrams.ts +108 -0
- package/src/workspace/checks/records.ts +47 -0
- package/src/workspace/checks.ts +8 -1
- package/src/workspace/compose-graph.ts +2 -0
- package/src/workspace/conformance/__fixture__/decisions/decision.kind.mjs +10 -0
- package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +26 -0
- package/src/workspace/conformance/index.ts +5 -0
- package/src/workspace/decide.test.ts +1 -1
- package/src/workspace/decide.ts +156 -23
- package/src/workspace/declaration.schema.json +114 -1
- package/src/workspace/declaration.test.ts +58 -0
- package/src/workspace/declaration.ts +104 -0
- package/src/workspace/declared-kinds.test.ts +5 -5
- package/src/workspace/declared-kinds.ts +46 -1
- package/src/workspace/generated-files.ts +14 -0
- package/src/workspace/graph-cli.ts +1 -1
- package/src/workspace/graph.schema.json +9 -1
- package/src/workspace/intent.schema.json +8 -2
- package/src/workspace/intent.ts +3 -10
- package/src/workspace/lineage-check.ts +16 -1
- package/src/workspace/lineage-upgrade-cli.ts +1 -1
- package/src/workspace/lineage-upgrade.test.ts +70 -0
- package/src/workspace/lineage-upgrade.ts +81 -10
- package/src/workspace/ls-contract.test.ts +57 -0
- package/src/workspace/ls.schema.json +46 -1
- package/src/workspace/ls.ts +62 -3
- package/src/workspace/points-cli.ts +14 -3
- package/src/workspace/points-write.schema.json +11 -1
- package/src/workspace/points.schema.json +33 -0
- package/src/workspace/points.test.ts +1 -1
- package/src/workspace/points.ts +1 -1
- package/src/workspace/read-contract.test.ts +1 -1
- package/src/workspace/reason-codes.test.ts +11 -1
- package/src/workspace/reason-codes.ts +18 -2
- package/src/workspace/records-amend.schema.json +2 -0
- package/src/workspace/records-cli.ts +13 -3
- package/src/workspace/records-close.schema.json +2 -0
- package/src/workspace/records-new.schema.json +2 -0
- package/src/workspace/records-review.schema.json +2 -0
- package/src/workspace/records-sessions-write.e2e.test.ts +77 -0
- package/src/workspace/records-sessions-write.test.ts +2 -34
- package/src/workspace/records-write.e2e.test.ts +108 -0
- package/src/workspace/records-write.test.ts +32 -41
- package/src/workspace/records-write.ts +33 -15
- package/src/workspace/records.schema.json +36 -2
- package/src/workspace/records.test.ts +48 -3
- package/src/workspace/records.ts +109 -3
- package/src/workspace/status-contract.test.ts +13 -8
- package/src/workspace/status-gates.ts +9 -4
- package/src/workspace/status-stewards.ts +108 -4
- package/src/workspace/status.schema.json +111 -7
- package/src/workspace/status.ts +36 -1
- package/src/workspace/work-acceptance.test.ts +274 -0
- package/src/workspace/{work-cli.test.ts → work-cli.e2e.test.ts} +1 -1
- package/src/workspace/work-cli.ts +193 -10
- package/src/workspace/work-evidence.schema.json +212 -0
- package/src/workspace/work-evidence.ts +169 -0
- package/src/workspace/work-history.schema.json +349 -0
- package/src/workspace/work-history.test.ts +156 -0
- package/src/workspace/work.test.ts +3 -2
- package/src/workspace/work.ts +100 -1
- /package/src/cli/{discovery-skip.test.ts → discovery-skip.e2e.test.ts} +0 -0
- /package/src/workspace/conformance/{conformance.test.ts → conformance.e2e.test.ts} +0 -0
- /package/src/workspace/{which-chant.test.ts → which-chant.e2e.test.ts} +0 -0
|
@@ -63,6 +63,13 @@
|
|
|
63
63
|
"$ref": "#/$defs/host"
|
|
64
64
|
}
|
|
65
65
|
},
|
|
66
|
+
"diagrams": {
|
|
67
|
+
"type": "array",
|
|
68
|
+
"description": "The workspace's own diagram artifacts (#2764): the diagrams that belong to no one member, such as a diagrams directory at the root. A member names its own in its diagrams. chant workspace ls lists every declared diagram, and chant workspace check reports one whose render is stale for its source when the entry records a sourceHash. Added in schema 1 by chant 0.91.0, so a declaration that uses it sets minReader to 0.91.0 or newer.",
|
|
69
|
+
"items": {
|
|
70
|
+
"$ref": "#/$defs/diagram"
|
|
71
|
+
}
|
|
72
|
+
},
|
|
66
73
|
"checks": {
|
|
67
74
|
"type": "object",
|
|
68
75
|
"description": "Severity for the declaration checks that allow it, keyed by WSP id (#2535, ws-028): error, warning, info or off. chant workspace check refuses an id it does not know, and one whose severity is fixed.",
|
|
@@ -72,6 +79,28 @@
|
|
|
72
79
|
"additionalProperties": {
|
|
73
80
|
"$ref": "#/$defs/severity"
|
|
74
81
|
}
|
|
82
|
+
},
|
|
83
|
+
"changes": {
|
|
84
|
+
"type": "object",
|
|
85
|
+
"additionalProperties": false,
|
|
86
|
+
"description": "The forward coverage check, chant workspace check --changes <base>..<head> (#2773): each path a diff changes is mapped to the current records whose constrains cover it. severity says what a finding does: off reports none, warn (the default) reports them and passes, fail reports them and fails. ignore lists globs over files whose changes need no record, such as lockfiles and generated output. Added in schema 1 by chant 0.92.0, so a declaration that uses it sets minReader to 0.92.0 or newer.",
|
|
87
|
+
"properties": {
|
|
88
|
+
"severity": {
|
|
89
|
+
"enum": [
|
|
90
|
+
"off",
|
|
91
|
+
"warn",
|
|
92
|
+
"fail"
|
|
93
|
+
],
|
|
94
|
+
"description": "What a change-uncovered or change-out-of-scope finding does. warn when absent."
|
|
95
|
+
},
|
|
96
|
+
"ignore": {
|
|
97
|
+
"type": "array",
|
|
98
|
+
"description": "Globs over file paths from the workspace root: * and ? within one segment, ** across segments. A changed path one matches is listed as ignored and never reported.",
|
|
99
|
+
"items": {
|
|
100
|
+
"$ref": "#/$defs/fileGlob"
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
75
104
|
}
|
|
76
105
|
},
|
|
77
106
|
"patternProperties": {
|
|
@@ -276,6 +305,13 @@
|
|
|
276
305
|
"$ref": "#/$defs/recordKind"
|
|
277
306
|
}
|
|
278
307
|
},
|
|
308
|
+
"diagrams": {
|
|
309
|
+
"type": "array",
|
|
310
|
+
"description": "The diagram artifacts this member holds (#2764). chant workspace ls lists every declared diagram, and chant workspace check reports one whose render is stale for its source when the entry records a sourceHash. Added in schema 1 by chant 0.91.0, so a declaration that uses it sets minReader to 0.91.0 or newer.",
|
|
311
|
+
"items": {
|
|
312
|
+
"$ref": "#/$defs/diagram"
|
|
313
|
+
}
|
|
314
|
+
},
|
|
279
315
|
"box": {
|
|
280
316
|
"$ref": "#/$defs/box"
|
|
281
317
|
},
|
|
@@ -366,7 +402,7 @@
|
|
|
366
402
|
},
|
|
367
403
|
"box": {
|
|
368
404
|
"type": "object",
|
|
369
|
-
"description": "The member is a box, or holds a box's declarations (#2726). A box holds no credential: each capability it needs outside itself is reached through a broker, a runtime such as a lobby or a door, which holds the credential and enforces the scope. chant workspace check fails when a file in the member's directory carries a literal secret (WSP121) and when a capability names no broker (WSP122). chant workspace status --json lists the capabilities, so a broker can read the scopes. Its host, slot, ports, state and cookies state the box's isolation (#2727): chant derives each value from the box's identity, its host, the member's name and its slot, and prints them in chant workspace status --json, so a runtime reads them instead of choosing its own. chant workspace check fails when two boxes on a host resolve to the same value (WSP123) and when a box hard-codes a machine path (WSP124). Added in schema 1 by chant 0.91.0, so a declaration that uses it sets minReader to 0.91.0 or newer.",
|
|
405
|
+
"description": "The member is a box, or holds a box's declarations (#2726). A box holds no credential: each capability it needs outside itself is reached through a broker, a runtime such as a lobby or a door, which holds the credential and enforces the scope. chant workspace check fails when a file in the member's directory carries a literal secret (WSP121) and when a capability names no broker (WSP122). chant workspace status --json lists the capabilities, so a broker can read the scopes. Its host, slot, ports, state and cookies state the box's isolation (#2727): chant derives each value from the box's identity, its host, the member's name and its slot, and prints them in chant workspace status --json, so a runtime reads them instead of choosing its own. chant workspace check fails when two boxes on a host resolve to the same value (WSP123) and when a box hard-codes a machine path (WSP124). A member that builds the fountain lexicon's Box declares the callback token fountain gives its persistent sandbox as the capability fountain-callback, brokered by fountain, with scope owner, or chant workspace check fails (WSP125, #2780). Added in schema 1 by chant 0.91.0, so a declaration that uses it sets minReader to 0.91.0 or newer.",
|
|
370
406
|
"properties": {
|
|
371
407
|
"capabilities": {
|
|
372
408
|
"type": "array",
|
|
@@ -493,6 +529,78 @@
|
|
|
493
529
|
},
|
|
494
530
|
"additionalProperties": false
|
|
495
531
|
},
|
|
532
|
+
"diagram": {
|
|
533
|
+
"type": "object",
|
|
534
|
+
"description": "A diagram artifact the workspace declares (#2764): a rendered image with a source, pinned to the renderer that made it. chant never runs the renderer.",
|
|
535
|
+
"required": [
|
|
536
|
+
"name",
|
|
537
|
+
"title",
|
|
538
|
+
"render",
|
|
539
|
+
"renderer"
|
|
540
|
+
],
|
|
541
|
+
"properties": {
|
|
542
|
+
"name": {
|
|
543
|
+
"$ref": "#/$defs/name",
|
|
544
|
+
"description": "Unique across the declaration."
|
|
545
|
+
},
|
|
546
|
+
"title": {
|
|
547
|
+
"type": "string",
|
|
548
|
+
"minLength": 1,
|
|
549
|
+
"description": "Shown above the diagram."
|
|
550
|
+
},
|
|
551
|
+
"source": {
|
|
552
|
+
"oneOf": [
|
|
553
|
+
{ "$ref": "#/$defs/path" },
|
|
554
|
+
{ "type": "null" }
|
|
555
|
+
],
|
|
556
|
+
"description": "The source file, from the workspace root, with / separators. Null for an SVG with no source. Default null."
|
|
557
|
+
},
|
|
558
|
+
"render": {
|
|
559
|
+
"$ref": "#/$defs/path",
|
|
560
|
+
"description": "The rendered image, from the workspace root, with / separators."
|
|
561
|
+
},
|
|
562
|
+
"renderer": {
|
|
563
|
+
"$ref": "#/$defs/diagramRenderer"
|
|
564
|
+
},
|
|
565
|
+
"sourceHash": {
|
|
566
|
+
"type": ["string", "null"],
|
|
567
|
+
"pattern": "^[0-9a-f]{64}$",
|
|
568
|
+
"description": "sha256 hex of the source's bytes when render was last produced from it. chant workspace check compares this against the source in the tree read and reports drift (WSP133) on a mismatch; without it, or without a source, the render is never checked for drift. Default null."
|
|
569
|
+
}
|
|
570
|
+
},
|
|
571
|
+
"patternProperties": {
|
|
572
|
+
"^x-": true
|
|
573
|
+
},
|
|
574
|
+
"additionalProperties": false
|
|
575
|
+
},
|
|
576
|
+
"diagramRenderer": {
|
|
577
|
+
"type": "object",
|
|
578
|
+
"description": "The renderer a diagram's render was made with. chant records the pin; it never installs or runs the renderer (ws-052).",
|
|
579
|
+
"required": [
|
|
580
|
+
"tool",
|
|
581
|
+
"version"
|
|
582
|
+
],
|
|
583
|
+
"properties": {
|
|
584
|
+
"tool": {
|
|
585
|
+
"enum": ["d2", "mermaid", "graphviz"]
|
|
586
|
+
},
|
|
587
|
+
"version": {
|
|
588
|
+
"$ref": "#/$defs/version",
|
|
589
|
+
"description": "The exact release the render was made with, such as 0.9.0."
|
|
590
|
+
},
|
|
591
|
+
"args": {
|
|
592
|
+
"type": "array",
|
|
593
|
+
"description": "Passed before the input and output paths, in order, such as [\"--layout=elk\", \"--theme=0\", \"--pad=40\", \"--omit-version\"]. Empty when absent.",
|
|
594
|
+
"items": {
|
|
595
|
+
"type": "string"
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
},
|
|
599
|
+
"patternProperties": {
|
|
600
|
+
"^x-": true
|
|
601
|
+
},
|
|
602
|
+
"additionalProperties": false
|
|
603
|
+
},
|
|
496
604
|
"generatedFile": {
|
|
497
605
|
"type": "object",
|
|
498
606
|
"description": "One generated file of a member (#2524 D14, #2541).",
|
|
@@ -641,6 +749,11 @@
|
|
|
641
749
|
"type": "integer",
|
|
642
750
|
"minimum": 1,
|
|
643
751
|
"maximum": 65535
|
|
752
|
+
},
|
|
753
|
+
"fileGlob": {
|
|
754
|
+
"type": "string",
|
|
755
|
+
"description": "A glob over file paths, relative to the workspace root: * and ? within one segment, ** across segments, [...] classes and {a,b} alternatives. No leading /, no . or .. segments.",
|
|
756
|
+
"pattern": "^(?!\\.\\.?(/|$))[^/\\\\]+(/(?!\\.\\.?(/|$))[^/\\\\]+)*$"
|
|
644
757
|
}
|
|
645
758
|
}
|
|
646
759
|
}
|
|
@@ -6,6 +6,7 @@ import { afterAll, describe, expect, test } from "vitest";
|
|
|
6
6
|
import {
|
|
7
7
|
compareVersions,
|
|
8
8
|
DECLARATION_SCHEMA_ID,
|
|
9
|
+
declaredDiagrams,
|
|
9
10
|
ownerOf,
|
|
10
11
|
parseDeclaration,
|
|
11
12
|
readDeclaration,
|
|
@@ -345,3 +346,60 @@ describe("generated files (#2541)", () => {
|
|
|
345
346
|
expect(failure(base([{ name: "g", kind: "examples", glob: "examples/*", generated: [] }])).message).toContain('unknown field "generated"');
|
|
346
347
|
});
|
|
347
348
|
});
|
|
349
|
+
|
|
350
|
+
describe("diagram artifacts (#2764)", () => {
|
|
351
|
+
const renderer = { tool: "d2", version: "0.9.0", args: ["--layout=elk", "--theme=0", "--pad=40", "--omit-version"] };
|
|
352
|
+
const diagram = (extra: Record<string, unknown> = {}) => ({
|
|
353
|
+
name: "architecture",
|
|
354
|
+
title: "Studio architecture",
|
|
355
|
+
source: "docs/diagrams/architecture.d2",
|
|
356
|
+
render: "docs/diagrams/architecture.svg",
|
|
357
|
+
renderer,
|
|
358
|
+
...extra,
|
|
359
|
+
});
|
|
360
|
+
const member = (diagrams: unknown[], extra: Record<string, unknown> = {}) => ({ name: "docs", dir: "docs", kind: "other", because: "the docs site", diagrams, ...extra });
|
|
361
|
+
|
|
362
|
+
test("a member's diagram is read with the workspace-relative source and render, and a null source is the default", () => {
|
|
363
|
+
const d = parse(base([member([diagram()])]));
|
|
364
|
+
expect(d.members[0].diagrams).toEqual([
|
|
365
|
+
{ name: "architecture", title: "Studio architecture", source: "docs/diagrams/architecture.d2", render: "docs/diagrams/architecture.svg", renderer, sourceHash: null, member: "docs", pointer: "/members/0/diagrams/0" },
|
|
366
|
+
]);
|
|
367
|
+
const noSource = parse(base([member([diagram({ source: undefined })])]));
|
|
368
|
+
expect(noSource.members[0].diagrams[0].source).toBeNull();
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
test("the workspace's own diagrams are in the top-level diagrams, with member null", () => {
|
|
372
|
+
const d = parse(base([{ name: "x", dir: "x", kind: "chant" }], { diagrams: [diagram({ name: "boundary" })] }));
|
|
373
|
+
expect(d.diagrams).toEqual([{ ...diagram({ name: "boundary" }), sourceHash: null, member: null, pointer: "/diagrams/0" }]);
|
|
374
|
+
expect(declaredDiagrams(d)).toEqual(d.diagrams);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
test("declaredDiagrams lists the workspace's own first, then each member's, in file order", () => {
|
|
378
|
+
const d = parse(base([member([diagram()]), { name: "app", dir: "app", kind: "chant" }], { diagrams: [diagram({ name: "boundary", render: "docs/diagrams/boundary.svg" })] }));
|
|
379
|
+
expect(declaredDiagrams(d).map((x) => x.name)).toEqual(["boundary", "architecture"]);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
test("a diagram name is unique across the declaration, workspace and members together", () => {
|
|
383
|
+
const clash = failure(base([member([diagram()])], { diagrams: [diagram()] }));
|
|
384
|
+
expect(clash.code).toBe("declaration-invalid");
|
|
385
|
+
expect(clash.message).toContain('the diagram name "architecture" is already used by the diagram at /diagrams/0');
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
test("renderer.tool is a closed list, and title, render and renderer are required", () => {
|
|
389
|
+
expect(failure(base([member([diagram({ renderer: { tool: "mscgen", version: "1.0.0" } })])])).code).toBe("declaration-invalid");
|
|
390
|
+
expect(failure(base([member([{ name: "a", source: null, render: "a.svg", renderer }])])).message).toContain('missing required field "title"');
|
|
391
|
+
expect(failure(base([member([{ name: "a", title: "A", source: null, renderer }])])).message).toContain('missing required field "render"');
|
|
392
|
+
expect(failure(base([member([{ name: "a", title: "A", source: null, render: "a.svg" }])])).message).toContain('missing required field "renderer"');
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
test("a sourceHash is a 64-character lowercase hex string, or absent", () => {
|
|
396
|
+
const hash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
|
|
397
|
+
expect(parse(base([member([diagram({ sourceHash: hash })])])).members[0].diagrams[0].sourceHash).toBe(hash);
|
|
398
|
+
expect(failure(base([member([diagram({ sourceHash: "not-a-hash" })])])).code).toBe("declaration-invalid");
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
test("source and render stay inside the workspace, like every other path", () => {
|
|
402
|
+
expect(failure(base([member([diagram({ source: "/etc/passwd" })])])).code).toBe("declaration-invalid");
|
|
403
|
+
expect(failure(base([member([diagram({ render: "../out.svg" })])])).code).toBe("declaration-invalid");
|
|
404
|
+
});
|
|
405
|
+
});
|
|
@@ -96,6 +96,17 @@ export interface Suppression {
|
|
|
96
96
|
/** What `checks` may set a declaration check's severity to. */
|
|
97
97
|
export type CheckSeverity = "error" | "warning" | "info" | "off";
|
|
98
98
|
|
|
99
|
+
/** How `chant workspace check --changes` treats its findings (#2773): not at all, as warnings, or as failures. */
|
|
100
|
+
export type ChangeSeverity = "off" | "warn" | "fail";
|
|
101
|
+
|
|
102
|
+
/** The declaration's `changes` block (#2773): the severity of the forward coverage check, and the paths it never reports. */
|
|
103
|
+
export interface ChangesPolicy {
|
|
104
|
+
/** Absent from the declaration, `warn`. */
|
|
105
|
+
severity: ChangeSeverity;
|
|
106
|
+
/** Globs over files, relative to the workspace root, whose changes need no record: lockfiles, generated output. */
|
|
107
|
+
ignore: string[];
|
|
108
|
+
}
|
|
109
|
+
|
|
99
110
|
export interface MemberRole {
|
|
100
111
|
name: string;
|
|
101
112
|
/** Relative to the member's directory, or null for the whole member. */
|
|
@@ -128,6 +139,40 @@ export interface LinkDeclaration {
|
|
|
128
139
|
pointer: string;
|
|
129
140
|
}
|
|
130
141
|
|
|
142
|
+
/** A renderer a diagram is pinned to (#2764): a closed list of tools chant does not run. */
|
|
143
|
+
export const DIAGRAM_TOOLS = ["d2", "mermaid", "graphviz"] as const;
|
|
144
|
+
export type DiagramTool = (typeof DIAGRAM_TOOLS)[number];
|
|
145
|
+
|
|
146
|
+
/** The renderer a diagram's render was made with (#2764). chant never runs it; it is recorded so a reader can. */
|
|
147
|
+
export interface DiagramRenderer {
|
|
148
|
+
tool: DiagramTool;
|
|
149
|
+
/** The exact release the render was made with, such as "0.9.0". */
|
|
150
|
+
version: string;
|
|
151
|
+
/** Passed before the input and output paths, in order. */
|
|
152
|
+
args: string[];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* A diagram artifact the declaration names (#2764): the workspace's own, in
|
|
157
|
+
* the top-level `diagrams`, or a member's, in that member's `diagrams`. A
|
|
158
|
+
* name is given once across the declaration, like a record kind's.
|
|
159
|
+
*/
|
|
160
|
+
export interface DiagramDeclaration {
|
|
161
|
+
name: string;
|
|
162
|
+
title: string;
|
|
163
|
+
/** From the workspace root, with / separators. Null for an SVG with no source. */
|
|
164
|
+
source: string | null;
|
|
165
|
+
/** From the workspace root, with / separators. */
|
|
166
|
+
render: string;
|
|
167
|
+
renderer: DiagramRenderer;
|
|
168
|
+
/** sha256 hex of the source's bytes when the render was last produced, for `chant workspace check`'s drift finding. Null when not recorded, or when source is null: the render is then never checked for drift. */
|
|
169
|
+
sourceHash: string | null;
|
|
170
|
+
/** The member that declares it, or null for the workspace's own. */
|
|
171
|
+
member: string | null;
|
|
172
|
+
/** The entry's JSON Pointer in the file, for messages. */
|
|
173
|
+
pointer: string;
|
|
174
|
+
}
|
|
175
|
+
|
|
131
176
|
/**
|
|
132
177
|
* A record kind the declaration names (#2680): the workspace's own, in the
|
|
133
178
|
* top-level `records`, or a member's, in that member's `records`.
|
|
@@ -208,6 +253,8 @@ export interface Member {
|
|
|
208
253
|
links: LinkDeclaration[];
|
|
209
254
|
/** The record kinds this member declares, in file order (#2680). */
|
|
210
255
|
records: RecordKindDeclaration[];
|
|
256
|
+
/** The diagram artifacts this member declares, in file order (#2764). */
|
|
257
|
+
diagrams: DiagramDeclaration[];
|
|
211
258
|
/** The member's box block, or null when it declares none (#2726). */
|
|
212
259
|
box: BoxDeclaration | null;
|
|
213
260
|
upstream: string | null;
|
|
@@ -252,8 +299,12 @@ export interface Declaration {
|
|
|
252
299
|
quorum: number | null;
|
|
253
300
|
/** The workspace's own record kinds, from the top-level `records`, in file order (#2680). A member's are on the member. */
|
|
254
301
|
records: RecordKindDeclaration[];
|
|
302
|
+
/** The workspace's own diagram artifacts, from the top-level `diagrams`, in file order (#2764). A member's are on the member. */
|
|
303
|
+
diagrams: DiagramDeclaration[];
|
|
255
304
|
/** The hosts boxes run on, in file order (#2727). */
|
|
256
305
|
hosts: Host[];
|
|
306
|
+
/** The forward coverage check's policy (#2773), or null when the declaration has no `changes` block. */
|
|
307
|
+
changes: ChangesPolicy | null;
|
|
257
308
|
/** The file, relative to the workspace root's tree (`chant.workspace.json` or `.jsonc`). */
|
|
258
309
|
file: string;
|
|
259
310
|
}
|
|
@@ -466,6 +517,7 @@ export function parseDeclaration(text: string, file: string, reader: string = re
|
|
|
466
517
|
pointer: `${pointer}/links/${j}`,
|
|
467
518
|
}));
|
|
468
519
|
const records = recordKindsOf(e.records, e.dir as string, e.name as string, `${pointer}/records`);
|
|
520
|
+
const diagrams = diagramsOf(e.diagrams, e.name as string, `${pointer}/diagrams`);
|
|
469
521
|
const box = boxOf(e.box, `${pointer}/box`);
|
|
470
522
|
return {
|
|
471
523
|
type: "member",
|
|
@@ -477,6 +529,7 @@ export function parseDeclaration(text: string, file: string, reader: string = re
|
|
|
477
529
|
outputs: e.outputs === undefined ? null : [...(e.outputs as string[])],
|
|
478
530
|
links,
|
|
479
531
|
records,
|
|
532
|
+
diagrams,
|
|
480
533
|
box,
|
|
481
534
|
upstream: (e.upstream as string | undefined) ?? null,
|
|
482
535
|
because: (e.because as string | undefined) ?? null,
|
|
@@ -572,6 +625,17 @@ export function parseDeclaration(text: string, file: string, reader: string = re
|
|
|
572
625
|
}
|
|
573
626
|
}
|
|
574
627
|
|
|
628
|
+
// A diagram name is given once across the declaration (#2764): a reader keys diagrams by name.
|
|
629
|
+
const ownDiagrams = diagramsOf(obj.diagrams, null, "/diagrams");
|
|
630
|
+
const byDiagramName = new Map<string, DiagramDeclaration>();
|
|
631
|
+
for (const d of [...ownDiagrams, ...members.flatMap((m) => m.diagrams)]) {
|
|
632
|
+
const first = byDiagramName.get(d.name);
|
|
633
|
+
if (first) {
|
|
634
|
+
throw new WorkspaceReadError("declaration-invalid", `the diagram name ${JSON.stringify(d.name)} is already used by the diagram at ${first.pointer}`, at(`${d.pointer}/name`));
|
|
635
|
+
}
|
|
636
|
+
byDiagramName.set(d.name, d);
|
|
637
|
+
}
|
|
638
|
+
|
|
575
639
|
const hosts = hostsOf(obj, members, at);
|
|
576
640
|
|
|
577
641
|
const pins = ((obj.pins as Record<string, string>[] | undefined) ?? []).map((p) => ({
|
|
@@ -592,11 +656,19 @@ export function parseDeclaration(text: string, file: string, reader: string = re
|
|
|
592
656
|
checks: { ...((obj.checks as Record<string, CheckSeverity> | undefined) ?? {}) },
|
|
593
657
|
quorum: typeof obj.quorum === "number" ? obj.quorum : null,
|
|
594
658
|
records: ownRecords,
|
|
659
|
+
diagrams: ownDiagrams,
|
|
595
660
|
hosts,
|
|
661
|
+
changes: changesOf(obj.changes as Record<string, unknown> | undefined),
|
|
596
662
|
file,
|
|
597
663
|
};
|
|
598
664
|
}
|
|
599
665
|
|
|
666
|
+
/** The `changes` block, already validated, with its defaults (#2773). */
|
|
667
|
+
function changesOf(block: Record<string, unknown> | undefined): ChangesPolicy | null {
|
|
668
|
+
if (block === undefined) return null;
|
|
669
|
+
return { severity: (block.severity as ChangeSeverity | undefined) ?? "warn", ignore: [...((block.ignore as string[] | undefined) ?? [])] };
|
|
670
|
+
}
|
|
671
|
+
|
|
600
672
|
/** The `records` list at `pointer`, already validated, with each kind file's path from the workspace root. */
|
|
601
673
|
function recordKindsOf(raw: unknown, dir: string, member: string | null, pointer: string): RecordKindDeclaration[] {
|
|
602
674
|
return ((raw as { kind: string; name?: string }[] | undefined) ?? []).map((r, i) => ({
|
|
@@ -608,6 +680,29 @@ function recordKindsOf(raw: unknown, dir: string, member: string | null, pointer
|
|
|
608
680
|
}));
|
|
609
681
|
}
|
|
610
682
|
|
|
683
|
+
/**
|
|
684
|
+
* The `diagrams` list at `pointer`, already validated. `source` and `render`
|
|
685
|
+
* are workspace-root-relative as written: unlike a generated file or a
|
|
686
|
+
* record kind, a diagram's files need not sit inside the declaring member's
|
|
687
|
+
* directory, so there is no member-relative form to resolve (#2764).
|
|
688
|
+
*/
|
|
689
|
+
function diagramsOf(raw: unknown, member: string | null, pointer: string): DiagramDeclaration[] {
|
|
690
|
+
return (
|
|
691
|
+
(raw as
|
|
692
|
+
| { name: string; title: string; source?: string | null; render: string; renderer: { tool: DiagramTool; version: string; args?: string[] }; sourceHash?: string | null }[]
|
|
693
|
+
| undefined) ?? []
|
|
694
|
+
).map((d, i) => ({
|
|
695
|
+
name: d.name,
|
|
696
|
+
title: d.title,
|
|
697
|
+
source: d.source ?? null,
|
|
698
|
+
render: d.render,
|
|
699
|
+
renderer: { tool: d.renderer.tool, version: d.renderer.version, args: [...(d.renderer.args ?? [])] },
|
|
700
|
+
sourceHash: d.sourceHash ?? null,
|
|
701
|
+
member,
|
|
702
|
+
pointer: `${pointer}/${i}`,
|
|
703
|
+
}));
|
|
704
|
+
}
|
|
705
|
+
|
|
611
706
|
/** The `box` block at `pointer`, already validated, or null when there is none (#2726, #2727). */
|
|
612
707
|
function boxOf(raw: unknown, pointer: string): BoxDeclaration | null {
|
|
613
708
|
if (raw === undefined) return null;
|
|
@@ -687,6 +782,15 @@ export function declaredRecordKinds(declaration: Declaration): RecordKindDeclara
|
|
|
687
782
|
return [...declaration.records, ...declaration.members.flatMap((m) => m.records)];
|
|
688
783
|
}
|
|
689
784
|
|
|
785
|
+
/**
|
|
786
|
+
* Every diagram artifact the declaration names (#2764), in the order readers
|
|
787
|
+
* use them: the workspace's own first, then each member's, members in file
|
|
788
|
+
* order and each list in its own order.
|
|
789
|
+
*/
|
|
790
|
+
export function declaredDiagrams(declaration: Declaration): DiagramDeclaration[] {
|
|
791
|
+
return [...declaration.diagrams, ...declaration.members.flatMap((m) => m.diagrams)];
|
|
792
|
+
}
|
|
793
|
+
|
|
690
794
|
/** Whether `path` is `dir` or sits under it. Both are tree-relative; `"."` is the root. */
|
|
691
795
|
export function isInside(path: string, dir: string): boolean {
|
|
692
796
|
if (dir === ".") return true;
|
|
@@ -111,20 +111,20 @@ describe("workspace ls lists the declared kinds (#2680)", () => {
|
|
|
111
111
|
]);
|
|
112
112
|
const doc = result(await listWorkspaceWithKinds({ cwd: root }));
|
|
113
113
|
expect(doc.contract).toBe(1);
|
|
114
|
-
expect(doc.workspace.records).toEqual([{ name: "decision", path: "decisions/decision.kind.mjs", kind: "decision", reason: null }]);
|
|
114
|
+
expect(doc.workspace.records).toEqual([{ name: "decision", path: "decisions/decision.kind.mjs", kind: "decision", reason: null, acceptance: null }]);
|
|
115
115
|
const design = doc.members.find((m) => m.name === "design")!;
|
|
116
116
|
expect(design.readable).toBe(true);
|
|
117
117
|
expect(design.records).toEqual([
|
|
118
|
-
{ name: "notes", path: "design/notes/note.kind.mjs", kind: "note", reason: null },
|
|
119
|
-
{ name: null, path: "design/gone.kind.mjs", kind: null, reason: { code: "kind-unreadable", message: "kind file design/gone.kind.mjs does not exist" } },
|
|
120
|
-
{ name: null, path: "design/broken.kind.mjs", kind: null, reason: { code: "kind-invalid", message: "kind file design/broken.kind.mjs is not a record kind: it has no recordKind export" } },
|
|
118
|
+
{ name: "notes", path: "design/notes/note.kind.mjs", kind: "note", reason: null, acceptance: null },
|
|
119
|
+
{ name: null, path: "design/gone.kind.mjs", kind: null, reason: { code: "kind-unreadable", message: "kind file design/gone.kind.mjs does not exist" }, acceptance: null },
|
|
120
|
+
{ name: null, path: "design/broken.kind.mjs", kind: null, reason: { code: "kind-invalid", message: "kind file design/broken.kind.mjs is not a record kind: it has no recordKind export" }, acceptance: null },
|
|
121
121
|
]);
|
|
122
122
|
expect(doc.members.find((m) => m.name === "app")!.records).toEqual([]);
|
|
123
123
|
});
|
|
124
124
|
|
|
125
125
|
test("listWorkspace lists them unloaded, and a workspace that names none lists empty arrays", async () => {
|
|
126
126
|
const doc = result(listWorkspace({ cwd: workspace() }));
|
|
127
|
-
expect(doc.workspace.records).toEqual([{ name: null, path: "decisions/decision.kind.mjs", kind: null, reason: null }]);
|
|
127
|
+
expect(doc.workspace.records).toEqual([{ name: null, path: "decisions/decision.kind.mjs", kind: null, reason: null, acceptance: null }]);
|
|
128
128
|
const none = result(await listWorkspaceWithKinds({ cwd: repo({ "chant.workspace.json": decl(members()), "app/x": "", "design/x": "" }) }));
|
|
129
129
|
expect(none.workspace.records).toEqual([]);
|
|
130
130
|
expect(none.members.map((m) => m.records)).toEqual([[], []]);
|
|
@@ -36,6 +36,25 @@ export interface DeclaredKind {
|
|
|
36
36
|
* other kind, and when the kind wasn't loaded.
|
|
37
37
|
*/
|
|
38
38
|
points?: { file: string; problems: { field: string | null; message: string }[] };
|
|
39
|
+
/**
|
|
40
|
+
* For a work kind with acceptance criteria (#2772), read with
|
|
41
|
+
* `acceptance: true`: each current record that lists criteria, with how
|
|
42
|
+
* many are met. Absent for any other kind, when the kind wasn't loaded,
|
|
43
|
+
* and when its records can't be read (`records` reports why).
|
|
44
|
+
*/
|
|
45
|
+
acceptance?: DeclaredAcceptance[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** One work record's acceptance criteria, counted, as `ls`, `status` and `check` read them (#2772). */
|
|
49
|
+
export interface DeclaredAcceptance {
|
|
50
|
+
item: string;
|
|
51
|
+
state: string | null;
|
|
52
|
+
met: number;
|
|
53
|
+
total: number;
|
|
54
|
+
/** The record file on disk, absolute. */
|
|
55
|
+
file: string;
|
|
56
|
+
/** The record's `work-acceptance-unmet` warning, when it is done with a criterion unmet; null otherwise. */
|
|
57
|
+
unmet: string | null;
|
|
39
58
|
}
|
|
40
59
|
|
|
41
60
|
/** The kind file of `declared` on disk, under the workspace root `rootOnDisk`. */
|
|
@@ -53,7 +72,7 @@ export async function loadDeclaredKinds(
|
|
|
53
72
|
declaration: Declaration,
|
|
54
73
|
tree: WorkspaceTree,
|
|
55
74
|
rootOnDisk: string,
|
|
56
|
-
options: { load?: boolean } = {},
|
|
75
|
+
options: { load?: boolean; acceptance?: boolean; at?: string } = {},
|
|
57
76
|
): Promise<DeclaredKind[]> {
|
|
58
77
|
const declared = declaredRecordKinds(declaration);
|
|
59
78
|
if (declared.length === 0) return [];
|
|
@@ -73,6 +92,10 @@ export async function loadDeclaredKinds(
|
|
|
73
92
|
const loaded = await records.loadRecordKind(file);
|
|
74
93
|
const entry: DeclaredKind = { declared: d, file, kind: loaded.kind.name, reason: null };
|
|
75
94
|
if (loaded.kind.answers) entry.points = await checkPoints(resolve(dirname(file), loaded.kind.answers.points), rootOnDisk, tree);
|
|
95
|
+
if (loaded.kind.work?.acceptance && options.acceptance) {
|
|
96
|
+
const counted = await countAcceptance(file, rootOnDisk, options.at);
|
|
97
|
+
if (counted) entry.acceptance = counted;
|
|
98
|
+
}
|
|
76
99
|
out.push(entry);
|
|
77
100
|
} catch (err) {
|
|
78
101
|
if (!(err instanceof records.RecordReadError)) throw err;
|
|
@@ -83,6 +106,28 @@ export async function loadDeclaredKinds(
|
|
|
83
106
|
return out;
|
|
84
107
|
}
|
|
85
108
|
|
|
109
|
+
/** The acceptance counts of a work kind's current records, or undefined when they can't be read. */
|
|
110
|
+
async function countAcceptance(file: string, rootOnDisk: string, at: string | undefined): Promise<DeclaredAcceptance[] | undefined> {
|
|
111
|
+
const { readRecordsFor } = await import("./records-cli");
|
|
112
|
+
const { RecordReadError } = await import("./records");
|
|
113
|
+
try {
|
|
114
|
+
const read = await readRecordsFor({ kind: file, cwd: rootOnDisk, current: true, ...(at !== undefined ? { at } : {}) });
|
|
115
|
+
return read.result.records
|
|
116
|
+
.filter((r): r is typeof r & { id: string; acceptance: NonNullable<typeof r.acceptance> } => r.id !== null && r.acceptance != null)
|
|
117
|
+
.map((r) => ({
|
|
118
|
+
item: r.id,
|
|
119
|
+
state: r.state,
|
|
120
|
+
met: r.acceptance.met,
|
|
121
|
+
total: r.acceptance.total,
|
|
122
|
+
file: join(read.root, ...r.path.split("/")),
|
|
123
|
+
unmet: r.warnings.find((w) => w.code === "work-acceptance-unmet")?.message ?? null,
|
|
124
|
+
}));
|
|
125
|
+
} catch (err) {
|
|
126
|
+
if (err instanceof RecordReadError) return undefined;
|
|
127
|
+
throw err;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
86
131
|
/** An answer kind's points file (ws-058), read from `tree` and validated as `points ask` reads it. */
|
|
87
132
|
async function checkPoints(abs: string, rootOnDisk: string, tree: WorkspaceTree): Promise<NonNullable<DeclaredKind["points"]>> {
|
|
88
133
|
const file = relative(rootOnDisk, abs).split(sep).join("/");
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
import { relative, resolve, sep } from "node:path";
|
|
23
23
|
import { findWorkspaceRoot } from "../project-root";
|
|
24
24
|
import { isInside, readDeclaration, type Declaration } from "./declaration";
|
|
25
|
+
import { memberHolding } from "./record-assets";
|
|
25
26
|
import { workingTree } from "./tree";
|
|
26
27
|
|
|
27
28
|
/** What `chant update` rewrites, registered as implicit generated entries. */
|
|
@@ -79,6 +80,19 @@ export function declaredFilesUnder(declaration: Declaration, dir: string): Map<s
|
|
|
79
80
|
return out;
|
|
80
81
|
}
|
|
81
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Whether a workspace path (from the workspace root) is a generated file:
|
|
85
|
+
* declared in its member's `generated` entries, or one core registers. The
|
|
86
|
+
* `generated` of `graph --intent`'s region and file nodes and of `check
|
|
87
|
+
* --changes`'s paths.
|
|
88
|
+
*/
|
|
89
|
+
export function isGeneratedPath(declaration: Declaration, path: string): boolean {
|
|
90
|
+
const m = declaration.members.find((x) => x.name === memberHolding(path, declaration.members));
|
|
91
|
+
const dir = m?.dir ?? ".";
|
|
92
|
+
const rel = dir === "." ? path : path.slice(dir.length + 1);
|
|
93
|
+
return classifyFile(rel, declaredFilesUnder(declaration, dir)).class === "generated";
|
|
94
|
+
}
|
|
95
|
+
|
|
82
96
|
/**
|
|
83
97
|
* The declared generated files for the absolute directory `absDir`, from the
|
|
84
98
|
* nearest `chant.workspace.json` above it. Empty when there is none, which is
|
|
@@ -220,7 +220,7 @@ export async function workspaceGraph(query: GraphQuery): Promise<GraphResult> {
|
|
|
220
220
|
try {
|
|
221
221
|
const read = await readRecordsFor({ kind: query.kind, cwd: query.cwd, at: query.at });
|
|
222
222
|
const name = read.loaded.kind.name;
|
|
223
|
-
graph.records = read.result.records.map((r) => ({ kind: name, id: r.id, path: r.path, state: r.state, valid: r.valid, supersededBy: r.supersededBy }));
|
|
223
|
+
graph.records = read.result.records.map((r) => ({ kind: name, id: r.id, path: r.path, state: r.state, valid: r.valid, supersededBy: r.supersededBy, remediatedBy: r.remediatedBy }));
|
|
224
224
|
graph.links.push(...recordLinkRows(name, read.result.records, read.loaded.kind.constrains?.field, located.tree, declaration.members));
|
|
225
225
|
} catch (err) {
|
|
226
226
|
if (!(err instanceof RecordReadError)) throw err;
|
|
@@ -752,7 +752,8 @@
|
|
|
752
752
|
"path",
|
|
753
753
|
"state",
|
|
754
754
|
"valid",
|
|
755
|
-
"supersededBy"
|
|
755
|
+
"supersededBy",
|
|
756
|
+
"remediatedBy"
|
|
756
757
|
],
|
|
757
758
|
"properties": {
|
|
758
759
|
"kind": {
|
|
@@ -784,6 +785,13 @@
|
|
|
784
785
|
"string",
|
|
785
786
|
"null"
|
|
786
787
|
]
|
|
788
|
+
},
|
|
789
|
+
"remediatedBy": {
|
|
790
|
+
"description": "Added in contract 1 by #2774. The ids of records whose remediates link names this one.",
|
|
791
|
+
"type": "array",
|
|
792
|
+
"items": {
|
|
793
|
+
"type": "string"
|
|
794
|
+
}
|
|
787
795
|
}
|
|
788
796
|
}
|
|
789
797
|
}
|
|
@@ -600,7 +600,9 @@
|
|
|
600
600
|
"record-schema-invalid",
|
|
601
601
|
"record-id-duplicate",
|
|
602
602
|
"record-supersedes-unknown",
|
|
603
|
-
"record-supersedes-conflict"
|
|
603
|
+
"record-supersedes-conflict",
|
|
604
|
+
"record-remediates-unknown",
|
|
605
|
+
"record-remediates-not-closed"
|
|
604
606
|
]
|
|
605
607
|
},
|
|
606
608
|
"message": {
|
|
@@ -794,7 +796,9 @@
|
|
|
794
796
|
"record-schema-invalid",
|
|
795
797
|
"record-id-duplicate",
|
|
796
798
|
"record-supersedes-unknown",
|
|
797
|
-
"record-supersedes-conflict"
|
|
799
|
+
"record-supersedes-conflict",
|
|
800
|
+
"record-remediates-unknown",
|
|
801
|
+
"record-remediates-not-closed"
|
|
798
802
|
]
|
|
799
803
|
},
|
|
800
804
|
"message": {
|
|
@@ -941,6 +945,8 @@
|
|
|
941
945
|
"work-done-unpinned",
|
|
942
946
|
"work-closed-without-date",
|
|
943
947
|
"work-done-gap-open",
|
|
948
|
+
"work-acceptance-unmet",
|
|
949
|
+
"work-acceptance-self-verified",
|
|
944
950
|
"answer-points-unreadable",
|
|
945
951
|
"answer-point-unknown",
|
|
946
952
|
"answer-point-changed"
|
package/src/workspace/intent.ts
CHANGED
|
@@ -38,7 +38,7 @@ import { realpathSync } from "node:fs";
|
|
|
38
38
|
import { basename, dirname, relative, resolve, sep } from "node:path";
|
|
39
39
|
import { declaredRecordKinds, readDeclaration, readerVersion, resolveGroups, WORKSPACE_ERROR_CODES, WorkspaceReadError, type Declaration } from "./declaration";
|
|
40
40
|
import { declaredKindFile } from "./declared-kinds";
|
|
41
|
-
import {
|
|
41
|
+
import { isGeneratedPath } from "./generated-files";
|
|
42
42
|
import { entityDecisions, hasTrailer, readCommitJoins, runCommitJoins, type CommitJoins, type IntentCommit, type JoinedEntity, type PluginFinding } from "./intent-joins";
|
|
43
43
|
import { loadKindRegistry } from "./kinds";
|
|
44
44
|
import { resolveLinks, type LinkTableRow } from "./links";
|
|
@@ -541,13 +541,6 @@ function filesUnder(tree: WorkspaceTree, dir: string): string[] {
|
|
|
541
541
|
return out;
|
|
542
542
|
}
|
|
543
543
|
|
|
544
|
-
function isGenerated(declaration: Declaration, path: string): boolean {
|
|
545
|
-
const m = declaration.members.find((x) => x.name === memberHolding(path, declaration.members));
|
|
546
|
-
const dir = m?.dir ?? ".";
|
|
547
|
-
const rel = dir === "." ? path : path.slice(dir.length + 1);
|
|
548
|
-
return classifyFile(rel, declaredFilesUnder(declaration, dir)).class === "generated";
|
|
549
|
-
}
|
|
550
|
-
|
|
551
544
|
function stringOr(v: unknown): string | null {
|
|
552
545
|
return typeof v === "string" ? v : null;
|
|
553
546
|
}
|
|
@@ -707,9 +700,9 @@ async function walk(query: IntentQuery, head: Head): Promise<IntentResult> {
|
|
|
707
700
|
// The region, its files and its member.
|
|
708
701
|
const member = region.path === "." ? (declaration.members.find((m) => m.dir === ".")?.name ?? null) : memberHolding(region.path, declaration.members);
|
|
709
702
|
const rid = regionId(region.path, region.lines);
|
|
710
|
-
add<RegionNode>({ id: rid, kind: "region", path: region.path, lines: region.lines, member, at: located.at, type, generated: type === "file" ?
|
|
703
|
+
add<RegionNode>({ id: rid, kind: "region", path: region.path, lines: region.lines, member, at: located.at, type, generated: type === "file" ? isGeneratedPath(declaration, region.path) : null, node: region.node });
|
|
711
704
|
const files = type === "dir" ? filesUnder(located.tree, region.path) : [];
|
|
712
|
-
for (const f of files) add<FileNode>({ id: `file:${f}`, kind: "file", path: f, member: memberHolding(f, declaration.members), generated:
|
|
705
|
+
for (const f of files) add<FileNode>({ id: `file:${f}`, kind: "file", path: f, member: memberHolding(f, declaration.members), generated: isGeneratedPath(declaration, f) });
|
|
713
706
|
const memberNode = (name: string) => {
|
|
714
707
|
const m = declaration.members.find((x) => x.name === name);
|
|
715
708
|
return add<MemberNode>({ id: `member:${name}`, kind: "member", name, dir: m?.dir ?? "", memberKind: m?.kind ?? "" });
|