@intentius/chant 0.91.0 → 0.94.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-plugins.d.ts +6 -6
- 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/box-intent.d.ts +85 -0
- package/dist/workspace/box-intent.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 +104 -0
- package/dist/workspace/chant-migrations.d.ts.map +1 -0
- package/dist/workspace/checks/boxes.d.ts +47 -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 +7 -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/decision-points.schema.json +3 -3
- package/dist/workspace/declaration.d.ts +58 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.schema.json +119 -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-joins.d.ts +5 -5
- 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 +2 -0
- package/dist/workspace/points-cli.d.ts.map +1 -1
- package/dist/workspace/points.d.ts +4 -6
- package/dist/workspace/points.d.ts.map +1 -1
- package/dist/workspace/reason-codes.d.ts +16 -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 +65 -1
- package/dist/workspace/status-stewards.d.ts.map +1 -1
- package/dist/workspace/status.d.ts +24 -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 +23 -3
- package/src/cli/mcp/workspace-plugins.ts +6 -6
- 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/cli/serve-mcp-workspace.test.ts +6 -6
- 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 +308 -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 +364 -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/box-intent.test.ts +205 -0
- package/src/workspace/box-intent.ts +159 -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 +169 -0
- package/src/workspace/check-contract.test.ts +4 -2
- package/src/workspace/check.schema.json +19 -5
- package/src/workspace/checks/boxes.test.ts +82 -2
- package/src/workspace/checks/boxes.ts +202 -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 +20 -2
- 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/decision-points.schema.json +3 -3
- package/src/workspace/declaration.schema.json +119 -1
- package/src/workspace/declaration.test.ts +58 -0
- package/src/workspace/declaration.ts +112 -0
- package/src/workspace/declared-kinds.test.ts +31 -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-joins.test.ts +5 -5
- package/src/workspace/intent-joins.ts +5 -5
- 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 +17 -3
- package/src/workspace/points-write.schema.json +11 -1
- package/src/workspace/points.schema.json +51 -0
- package/src/workspace/points.test.ts +16 -15
- package/src/workspace/points.ts +5 -17
- package/src/workspace/read-contract.test.ts +9 -1
- package/src/workspace/reason-codes.test.ts +18 -8
- package/src/workspace/reason-codes.ts +21 -2
- package/src/workspace/records-amend.schema.json +2 -0
- package/src/workspace/records-cli.ts +27 -4
- package/src/workspace/records-close.schema.json +2 -0
- package/src/workspace/records-formats.test.ts +15 -15
- 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-since.test.ts +8 -7
- 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 +49 -4
- package/src/workspace/records.ts +109 -3
- package/src/workspace/status-contract.test.ts +14 -8
- package/src/workspace/status-gates.ts +9 -4
- package/src/workspace/status-stewards.ts +155 -5
- package/src/workspace/status.schema.json +129 -8
- package/src/workspace/status.ts +46 -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-readiness-chud.test.ts → work-readiness.test.ts} +30 -32
- package/src/workspace/work.test.ts +20 -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
|
@@ -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`.
|
|
@@ -165,6 +210,12 @@ export interface BoxDeclaration {
|
|
|
165
210
|
capabilities: BoxCapability[];
|
|
166
211
|
/** The host and slot of the box's isolation, or null when the block declares none (#2727). The values are derived in `boxes.ts`. */
|
|
167
212
|
isolation: BoxIsolationDeclaration | null;
|
|
213
|
+
/**
|
|
214
|
+
* The id of the decision record that says what the box is for, or null when
|
|
215
|
+
* the block names none (#2850). A box starts as a question: the record is
|
|
216
|
+
* proposed with no choice until the person who answers it decides it.
|
|
217
|
+
*/
|
|
218
|
+
intent: string | null;
|
|
168
219
|
/** The block's JSON Pointer in the file, for messages. */
|
|
169
220
|
pointer: string;
|
|
170
221
|
}
|
|
@@ -208,6 +259,8 @@ export interface Member {
|
|
|
208
259
|
links: LinkDeclaration[];
|
|
209
260
|
/** The record kinds this member declares, in file order (#2680). */
|
|
210
261
|
records: RecordKindDeclaration[];
|
|
262
|
+
/** The diagram artifacts this member declares, in file order (#2764). */
|
|
263
|
+
diagrams: DiagramDeclaration[];
|
|
211
264
|
/** The member's box block, or null when it declares none (#2726). */
|
|
212
265
|
box: BoxDeclaration | null;
|
|
213
266
|
upstream: string | null;
|
|
@@ -252,8 +305,12 @@ export interface Declaration {
|
|
|
252
305
|
quorum: number | null;
|
|
253
306
|
/** The workspace's own record kinds, from the top-level `records`, in file order (#2680). A member's are on the member. */
|
|
254
307
|
records: RecordKindDeclaration[];
|
|
308
|
+
/** The workspace's own diagram artifacts, from the top-level `diagrams`, in file order (#2764). A member's are on the member. */
|
|
309
|
+
diagrams: DiagramDeclaration[];
|
|
255
310
|
/** The hosts boxes run on, in file order (#2727). */
|
|
256
311
|
hosts: Host[];
|
|
312
|
+
/** The forward coverage check's policy (#2773), or null when the declaration has no `changes` block. */
|
|
313
|
+
changes: ChangesPolicy | null;
|
|
257
314
|
/** The file, relative to the workspace root's tree (`chant.workspace.json` or `.jsonc`). */
|
|
258
315
|
file: string;
|
|
259
316
|
}
|
|
@@ -466,6 +523,7 @@ export function parseDeclaration(text: string, file: string, reader: string = re
|
|
|
466
523
|
pointer: `${pointer}/links/${j}`,
|
|
467
524
|
}));
|
|
468
525
|
const records = recordKindsOf(e.records, e.dir as string, e.name as string, `${pointer}/records`);
|
|
526
|
+
const diagrams = diagramsOf(e.diagrams, e.name as string, `${pointer}/diagrams`);
|
|
469
527
|
const box = boxOf(e.box, `${pointer}/box`);
|
|
470
528
|
return {
|
|
471
529
|
type: "member",
|
|
@@ -477,6 +535,7 @@ export function parseDeclaration(text: string, file: string, reader: string = re
|
|
|
477
535
|
outputs: e.outputs === undefined ? null : [...(e.outputs as string[])],
|
|
478
536
|
links,
|
|
479
537
|
records,
|
|
538
|
+
diagrams,
|
|
480
539
|
box,
|
|
481
540
|
upstream: (e.upstream as string | undefined) ?? null,
|
|
482
541
|
because: (e.because as string | undefined) ?? null,
|
|
@@ -572,6 +631,17 @@ export function parseDeclaration(text: string, file: string, reader: string = re
|
|
|
572
631
|
}
|
|
573
632
|
}
|
|
574
633
|
|
|
634
|
+
// A diagram name is given once across the declaration (#2764): a reader keys diagrams by name.
|
|
635
|
+
const ownDiagrams = diagramsOf(obj.diagrams, null, "/diagrams");
|
|
636
|
+
const byDiagramName = new Map<string, DiagramDeclaration>();
|
|
637
|
+
for (const d of [...ownDiagrams, ...members.flatMap((m) => m.diagrams)]) {
|
|
638
|
+
const first = byDiagramName.get(d.name);
|
|
639
|
+
if (first) {
|
|
640
|
+
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`));
|
|
641
|
+
}
|
|
642
|
+
byDiagramName.set(d.name, d);
|
|
643
|
+
}
|
|
644
|
+
|
|
575
645
|
const hosts = hostsOf(obj, members, at);
|
|
576
646
|
|
|
577
647
|
const pins = ((obj.pins as Record<string, string>[] | undefined) ?? []).map((p) => ({
|
|
@@ -592,11 +662,19 @@ export function parseDeclaration(text: string, file: string, reader: string = re
|
|
|
592
662
|
checks: { ...((obj.checks as Record<string, CheckSeverity> | undefined) ?? {}) },
|
|
593
663
|
quorum: typeof obj.quorum === "number" ? obj.quorum : null,
|
|
594
664
|
records: ownRecords,
|
|
665
|
+
diagrams: ownDiagrams,
|
|
595
666
|
hosts,
|
|
667
|
+
changes: changesOf(obj.changes as Record<string, unknown> | undefined),
|
|
596
668
|
file,
|
|
597
669
|
};
|
|
598
670
|
}
|
|
599
671
|
|
|
672
|
+
/** The `changes` block, already validated, with its defaults (#2773). */
|
|
673
|
+
function changesOf(block: Record<string, unknown> | undefined): ChangesPolicy | null {
|
|
674
|
+
if (block === undefined) return null;
|
|
675
|
+
return { severity: (block.severity as ChangeSeverity | undefined) ?? "warn", ignore: [...((block.ignore as string[] | undefined) ?? [])] };
|
|
676
|
+
}
|
|
677
|
+
|
|
600
678
|
/** The `records` list at `pointer`, already validated, with each kind file's path from the workspace root. */
|
|
601
679
|
function recordKindsOf(raw: unknown, dir: string, member: string | null, pointer: string): RecordKindDeclaration[] {
|
|
602
680
|
return ((raw as { kind: string; name?: string }[] | undefined) ?? []).map((r, i) => ({
|
|
@@ -608,6 +686,29 @@ function recordKindsOf(raw: unknown, dir: string, member: string | null, pointer
|
|
|
608
686
|
}));
|
|
609
687
|
}
|
|
610
688
|
|
|
689
|
+
/**
|
|
690
|
+
* The `diagrams` list at `pointer`, already validated. `source` and `render`
|
|
691
|
+
* are workspace-root-relative as written: unlike a generated file or a
|
|
692
|
+
* record kind, a diagram's files need not sit inside the declaring member's
|
|
693
|
+
* directory, so there is no member-relative form to resolve (#2764).
|
|
694
|
+
*/
|
|
695
|
+
function diagramsOf(raw: unknown, member: string | null, pointer: string): DiagramDeclaration[] {
|
|
696
|
+
return (
|
|
697
|
+
(raw as
|
|
698
|
+
| { name: string; title: string; source?: string | null; render: string; renderer: { tool: DiagramTool; version: string; args?: string[] }; sourceHash?: string | null }[]
|
|
699
|
+
| undefined) ?? []
|
|
700
|
+
).map((d, i) => ({
|
|
701
|
+
name: d.name,
|
|
702
|
+
title: d.title,
|
|
703
|
+
source: d.source ?? null,
|
|
704
|
+
render: d.render,
|
|
705
|
+
renderer: { tool: d.renderer.tool, version: d.renderer.version, args: [...(d.renderer.args ?? [])] },
|
|
706
|
+
sourceHash: d.sourceHash ?? null,
|
|
707
|
+
member,
|
|
708
|
+
pointer: `${pointer}/${i}`,
|
|
709
|
+
}));
|
|
710
|
+
}
|
|
711
|
+
|
|
611
712
|
/** The `box` block at `pointer`, already validated, or null when there is none (#2726, #2727). */
|
|
612
713
|
function boxOf(raw: unknown, pointer: string): BoxDeclaration | null {
|
|
613
714
|
if (raw === undefined) return null;
|
|
@@ -618,6 +719,7 @@ function boxOf(raw: unknown, pointer: string): BoxDeclaration | null {
|
|
|
618
719
|
ports?: Record<string, number>;
|
|
619
720
|
state?: Record<string, string>;
|
|
620
721
|
cookies?: string[];
|
|
722
|
+
intent?: string;
|
|
621
723
|
};
|
|
622
724
|
return {
|
|
623
725
|
capabilities: (b.capabilities ?? []).map((c, i) => ({ name: c.name, broker: c.broker ?? null, scope: [...(c.scope ?? [])], pointer: `${pointer}/capabilities/${i}` })),
|
|
@@ -626,6 +728,7 @@ function boxOf(raw: unknown, pointer: string): BoxDeclaration | null {
|
|
|
626
728
|
b.host === undefined
|
|
627
729
|
? null
|
|
628
730
|
: { host: b.host, slot: b.slot!, ports: { ...(b.ports ?? {}) }, state: { ...(b.state ?? {}) }, cookies: [...(b.cookies ?? [])] },
|
|
731
|
+
intent: b.intent ?? null,
|
|
629
732
|
pointer,
|
|
630
733
|
};
|
|
631
734
|
}
|
|
@@ -687,6 +790,15 @@ export function declaredRecordKinds(declaration: Declaration): RecordKindDeclara
|
|
|
687
790
|
return [...declaration.records, ...declaration.members.flatMap((m) => m.records)];
|
|
688
791
|
}
|
|
689
792
|
|
|
793
|
+
/**
|
|
794
|
+
* Every diagram artifact the declaration names (#2764), in the order readers
|
|
795
|
+
* use them: the workspace's own first, then each member's, members in file
|
|
796
|
+
* order and each list in its own order.
|
|
797
|
+
*/
|
|
798
|
+
export function declaredDiagrams(declaration: Declaration): DiagramDeclaration[] {
|
|
799
|
+
return [...declaration.diagrams, ...declaration.members.flatMap((m) => m.diagrams)];
|
|
800
|
+
}
|
|
801
|
+
|
|
690
802
|
/** Whether `path` is `dir` or sits under it. Both are tree-relative; `"."` is the root. */
|
|
691
803
|
export function isInside(path: string, dir: string): boolean {
|
|
692
804
|
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([[], []]);
|
|
@@ -242,6 +242,32 @@ describe("records without --kind reads every declared kind (#2680)", () => {
|
|
|
242
242
|
expect(await run(workspace(), "--current")).toBe(0);
|
|
243
243
|
expect(out.filter((l) => l.includes("("))).toEqual(["decision (decisions/decision.kind.mjs)", "notes (design/notes/note.kind.mjs)"]);
|
|
244
244
|
});
|
|
245
|
+
|
|
246
|
+
test("--at an unknown revision with --json prints the error document, not just text on stderr (#2860)", async () => {
|
|
247
|
+
const root = workspace();
|
|
248
|
+
expect(await run(root, "--at", "refs/heads/no-such-branch", "--json")).toBe(1);
|
|
249
|
+
expect(err).toEqual([]);
|
|
250
|
+
const printed = JSON.parse(out.join("\n"));
|
|
251
|
+
expectValid(printed);
|
|
252
|
+
expect(printed).toEqual({
|
|
253
|
+
$schema: recordsSchema.$id,
|
|
254
|
+
contract: 1,
|
|
255
|
+
error: { code: "revision-unknown", message: expect.stringContaining("refs/heads/no-such-branch") as unknown as string },
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
out.length = 0;
|
|
259
|
+
err.length = 0;
|
|
260
|
+
expect(await run(root, "--at", "refs/heads/no-such-branch")).toBe(1);
|
|
261
|
+
expect(out).toEqual([]);
|
|
262
|
+
expect(err.join("\n")).toContain("revision-unknown");
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
test("a declaration-level code the records schema doesn't carry, such as declaration-invalid, stays text-only even with --json (#2860)", async () => {
|
|
266
|
+
const root = repo({ "chant.workspace.json": decl(members(), { records: [{ kind: "a.kind.mjs", file: "b" }] }), "app/x": "", "design/x": "" });
|
|
267
|
+
expect(await run(root, "--json")).toBe(1);
|
|
268
|
+
expect(out).toEqual([]);
|
|
269
|
+
expect(err.join("\n")).toContain("declaration-invalid");
|
|
270
|
+
});
|
|
245
271
|
});
|
|
246
272
|
|
|
247
273
|
describe("records new, amend and review without --kind (#2680)", () => {
|
|
@@ -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
|
}
|
|
@@ -36,7 +36,7 @@ describe("the data form reads every trailer value (#2663)", () => {
|
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
test("trailerValues keeps git's order, trims, and drops empty values and repeats", () => {
|
|
39
|
-
expect(trailerValues({ "
|
|
39
|
+
expect(trailerValues({ "Acme-Evidence": ["h1", "h2"], "acme-evidence": ["h2", " h3", " "] }, "ACME-EVIDENCE")).toEqual(["h1", "h2", "h3"]);
|
|
40
40
|
expect(trailerValues({}, "X")).toEqual([]);
|
|
41
41
|
expect(trailerValue({ X: [" "] }, "x")).toBeUndefined();
|
|
42
42
|
});
|
|
@@ -45,16 +45,16 @@ describe("the data form reads every trailer value (#2663)", () => {
|
|
|
45
45
|
describe("commitJoinsName (#2663)", () => {
|
|
46
46
|
test("names the findings of either form, and leaves the data form's own keys alone", () => {
|
|
47
47
|
const join = () => undefined;
|
|
48
|
-
expect(readCommitJoins({ commitJoins: join, commitJoinsName: "
|
|
49
|
-
expect(readCommitJoins({ commitJoins: { trailers: { unit: "Unit" } }, commitJoinsName: "
|
|
48
|
+
expect(readCommitJoins({ commitJoins: join, commitJoinsName: "acme" })).toEqual({ form: "function", join, name: "acme" });
|
|
49
|
+
expect(readCommitJoins({ commitJoins: { trailers: { unit: "Unit" } }, commitJoinsName: "acme" })).toEqual({ form: "data", data: { trailers: { unit: "Unit" } }, name: "acme" });
|
|
50
50
|
expect(readCommitJoins({ commitJoins: join })).toEqual({ form: "function", join });
|
|
51
51
|
// A name inside the data form is still a key the data form does not have.
|
|
52
|
-
expect(readCommitJoins({ commitJoins: { name: "
|
|
52
|
+
expect(readCommitJoins({ commitJoins: { name: "acme", trailers: {} } })).toMatch(/Unrecognized key/);
|
|
53
53
|
});
|
|
54
54
|
|
|
55
55
|
test("a name that can't be a plugin:<name>: segment, or one with no joins, is refused", () => {
|
|
56
56
|
for (const bad of ["a:b", "a b", "", 7]) expect(readCommitJoins({ commitJoins: () => undefined, commitJoinsName: bad }), String(bad)).toMatch(/^commitJoinsName:/);
|
|
57
|
-
expect(readCommitJoins({ commitJoinsName: "
|
|
57
|
+
expect(readCommitJoins({ commitJoinsName: "acme" })).toMatch(/no commitJoins/);
|
|
58
58
|
expect(readCommitJoins({})).toBeUndefined();
|
|
59
59
|
});
|
|
60
60
|
});
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
* The commit-join hook of the intent graph (#2651; #2650 C1 and C13).
|
|
3
3
|
*
|
|
4
4
|
* Commits, decisions and artifacts come from core. Units of work, contracts
|
|
5
|
-
* and evidence come from a plugin, such as
|
|
6
|
-
* core ships no model of them (#2555, "Core ships no decision
|
|
7
|
-
* file passed to `chant workspace graph --intent --kind <file>`
|
|
8
|
-
* through one export, `commitJoins`, in one of two forms:
|
|
5
|
+
* and evidence come from a plugin, such as a workspace's own development
|
|
6
|
+
* model, because core ships no model of them (#2555, "Core ships no decision
|
|
7
|
+
* kind"). A kind file passed to `chant workspace graph --intent --kind <file>`
|
|
8
|
+
* supplies them through one export, `commitJoins`, in one of two forms:
|
|
9
9
|
*
|
|
10
10
|
* - A function `commitJoins(commit, context)` that returns the unit, contract
|
|
11
11
|
* and evidence for one commit, or nothing. It is given the commit's sha,
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
* `<name>` is the kind's name by default: its record kind's name, or the
|
|
37
37
|
* file's name without `.kind.mjs`. A kind file may name its findings itself
|
|
38
38
|
* with a sibling export, `commitJoinsName` (#2663), so joins that live beside
|
|
39
|
-
* a record kind called `decision` can still report `plugin:
|
|
39
|
+
* a record kind called `decision` can still report `plugin:acme:<code>`. It is
|
|
40
40
|
* a sibling export rather than a property because the data form is a closed
|
|
41
41
|
* object whose keys are all joins, and a function's `name` property is
|
|
42
42
|
* already its own name.
|
|
@@ -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 ?? "" });
|
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
*
|
|
17
17
|
* `chant workspace upgrade` runs the same checks in its staging worktree
|
|
18
18
|
* before it reaches its gate.
|
|
19
|
+
*
|
|
20
|
+
* `--changes <base>..<head>` runs the forward coverage check instead
|
|
21
|
+
* (`changes-cli.ts`, #2773), which prints a document of its own.
|
|
19
22
|
*/
|
|
20
23
|
|
|
21
24
|
import { realpathSync } from "node:fs";
|
|
@@ -84,7 +87,8 @@ export function findingKey(f: CheckFinding): string {
|
|
|
84
87
|
return `${f.code}\0${f.scope ?? ""}\0${f.path ?? ""}`;
|
|
85
88
|
}
|
|
86
89
|
|
|
87
|
-
const USAGE =
|
|
90
|
+
const USAGE =
|
|
91
|
+
"chant workspace check [--at <rev>] [--json] [--format stylish|json|sarif] [--generated] [--kind <kind file>]\n chant workspace check --changes <base>..<head> [--work <id>] [--severity off|warn|fail] [--kind <kind file>...] [--json]";
|
|
88
92
|
const FORMATS = ["stylish", "json", "sarif"] as const;
|
|
89
93
|
|
|
90
94
|
/** A lock finding as a lint diagnostic, for `--format json` and `--format sarif`. */
|
|
@@ -223,6 +227,17 @@ export async function runWorkspaceCheck(ctx: CommandContext): Promise<number> {
|
|
|
223
227
|
console.error(formatError({ message: `chant workspace check takes no argument (got ${ctx.args.extraPositional})`, hint: USAGE }));
|
|
224
228
|
return 1;
|
|
225
229
|
}
|
|
230
|
+
// The forward coverage check (#2773) is its own document.
|
|
231
|
+
if (ctx.args.changes !== undefined) {
|
|
232
|
+
if (ctx.args.at !== undefined) {
|
|
233
|
+
console.error(formatError({ message: "--changes reads the records at the range's head, so it takes no --at", hint: USAGE }));
|
|
234
|
+
return 1;
|
|
235
|
+
}
|
|
236
|
+
const { handToRootChant } = await import("./which-chant");
|
|
237
|
+
const handed = await handToRootChant(root, undefined);
|
|
238
|
+
if (handed !== undefined) return handed;
|
|
239
|
+
return (await import("./changes-cli")).runWorkspaceChanges(ctx, root);
|
|
240
|
+
}
|
|
226
241
|
const format = (ctx.args.format || "stylish") as (typeof FORMATS)[number];
|
|
227
242
|
if (!FORMATS.includes(format)) {
|
|
228
243
|
console.error(formatError({ message: `--format ${format} is not a check format; use stylish, json or sarif`, hint: USAGE }));
|
|
@@ -71,7 +71,7 @@ export async function upgradeCommand(opts: UpgradeCommandOptions): Promise<Upgra
|
|
|
71
71
|
}),
|
|
72
72
|
);
|
|
73
73
|
}
|
|
74
|
-
if (!staged.changed) {
|
|
74
|
+
if (!staged.changed && staged.checksOk) {
|
|
75
75
|
if (!opts.json) console.error(formatSuccess(`scope "${staged.scope}" is already at ${staged.to ?? "its source"}; nothing to upgrade`));
|
|
76
76
|
return report("up-to-date", 0);
|
|
77
77
|
}
|
|
@@ -124,6 +124,76 @@ describe("chant workspace upgrade", () => {
|
|
|
124
124
|
expect(git(proj, ["worktree", "list"]).split("\n")).toHaveLength(1);
|
|
125
125
|
});
|
|
126
126
|
|
|
127
|
+
test("builds and lints the chant member chant.workspace.json declares, not the scope root (#2804)", async () => {
|
|
128
|
+
release("v2.0.0", {
|
|
129
|
+
"chant.workspace.json": JSON.stringify({ name: "proj", schema: 1, members: [{ name: "delivery", dir: "delivery", kind: "chant" }] }, null, 2) + "\n",
|
|
130
|
+
"delivery/chant.config.ts": "export default {};\n",
|
|
131
|
+
});
|
|
132
|
+
const calls: Array<{ command: string; cwd: string }> = [];
|
|
133
|
+
const recording: ChantRunner = async (command, cwd) => {
|
|
134
|
+
calls.push({ command, cwd });
|
|
135
|
+
return { exitCode: 0, output: "" };
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const staged = await stageUpgrade({ root: proj, to: "v2.0.0", runChant: recording });
|
|
139
|
+
try {
|
|
140
|
+
// Never at the scope root: it has no chant.config once the project is a workspace.
|
|
141
|
+
expect(calls.every((c) => c.cwd === join(staged.worktreeProject, "delivery"))).toBe(true);
|
|
142
|
+
expect(calls.map((c) => c.command).sort()).toEqual(["build", "lint"]);
|
|
143
|
+
expect(staged.checks).toEqual(
|
|
144
|
+
expect.arrayContaining([
|
|
145
|
+
{ name: "build", status: "passed", member: "delivery" },
|
|
146
|
+
{ name: "lint", status: "passed", member: "delivery" },
|
|
147
|
+
]),
|
|
148
|
+
);
|
|
149
|
+
} finally {
|
|
150
|
+
staged.dispose();
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("a member's own node_modules is linked into the staging worktree for its checks, and stays out of the patch (#2847)", async () => {
|
|
155
|
+
release("v2.0.0", {
|
|
156
|
+
"chant.workspace.json": JSON.stringify({ name: "proj", schema: 1, members: [{ name: "delivery", dir: "delivery", kind: "chant" }] }, null, 2) + "\n",
|
|
157
|
+
"delivery/chant.config.ts": "export default {};\n",
|
|
158
|
+
});
|
|
159
|
+
// The member installs its packages itself; git never sees them.
|
|
160
|
+
put(proj, "delivery/node_modules/member-only/package.json", JSON.stringify({ name: "member-only", version: "1.0.0" }) + "\n");
|
|
161
|
+
writeFileSync(join(proj, ".git", "info", "exclude"), "node_modules\n");
|
|
162
|
+
const seen: string[] = [];
|
|
163
|
+
const recording: ChantRunner = async (command, cwd) => {
|
|
164
|
+
if (existsSync(join(cwd, "node_modules", "member-only", "package.json"))) seen.push(command);
|
|
165
|
+
return { exitCode: 0, output: "" };
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const staged = await stageUpgrade({ root: proj, to: "v2.0.0", runChant: recording });
|
|
169
|
+
try {
|
|
170
|
+
expect(seen.sort()).toEqual(["build", "lint"]);
|
|
171
|
+
expect(staged.changedPaths.some((p) => p.includes("node_modules"))).toBe(false);
|
|
172
|
+
expect(staged.patch).not.toContain("node_modules");
|
|
173
|
+
} finally {
|
|
174
|
+
staged.dispose();
|
|
175
|
+
}
|
|
176
|
+
// Disposing the worktree removes the link, never the member's own packages.
|
|
177
|
+
expect(existsSync(join(proj, "delivery", "node_modules", "member-only", "package.json"))).toBe(true);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("a member's failing build fails the upgrade's checks before the gate (#2804)", async () => {
|
|
181
|
+
release("v2.0.0", {
|
|
182
|
+
"chant.workspace.json": JSON.stringify({ name: "proj", schema: 1, members: [{ name: "delivery", dir: "delivery", kind: "chant" }] }, null, 2) + "\n",
|
|
183
|
+
"delivery/chant.config.ts": "export default {};\n",
|
|
184
|
+
});
|
|
185
|
+
const failingBuild: ChantRunner = async (command) => (command === "build" ? { exitCode: 1, output: "boom\n" } : { exitCode: 0, output: "" });
|
|
186
|
+
|
|
187
|
+
const result = await upgradeCommand({ root: proj, to: "v2.0.0", runChant: failingBuild });
|
|
188
|
+
expect(result.outcome).toBe("checks-failed");
|
|
189
|
+
expect(result.exitCode).toBe(1);
|
|
190
|
+
expect(result.staged!.checks).toEqual(
|
|
191
|
+
expect.arrayContaining([{ name: "build", status: "failed", detail: "boom", member: "delivery" }]),
|
|
192
|
+
);
|
|
193
|
+
// The tree is unchanged: an unbuildable migration never reaches the gate.
|
|
194
|
+
expect(git(proj, ["status", "--porcelain"])).toBe("");
|
|
195
|
+
});
|
|
196
|
+
|
|
127
197
|
test("gates on the patch digest, then applies exactly the approved patch", async () => {
|
|
128
198
|
release("v2.0.0", { "README.md": "starter v2\n" });
|
|
129
199
|
const port = ledger();
|