@intentius/chant 0.46.0 → 0.50.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/audit/catalog.d.ts +13 -3
- package/dist/audit/catalog.d.ts.map +1 -1
- package/dist/audit/core.d.ts +30 -3
- package/dist/audit/core.d.ts.map +1 -1
- package/dist/audit/discover.d.ts +9 -2
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/fetch.d.ts.map +1 -1
- package/dist/audit/report-html.d.ts.map +1 -1
- package/dist/audit/report-model.d.ts +6 -0
- package/dist/audit/report-model.d.ts.map +1 -1
- package/dist/audit/report.d.ts.map +1 -1
- package/dist/audit/rules-doc.d.ts.map +1 -1
- package/dist/audit/secrets.d.ts +95 -0
- package/dist/audit/secrets.d.ts.map +1 -0
- package/dist/audit/wrangler.d.ts +33 -0
- package/dist/audit/wrangler.d.ts.map +1 -0
- package/dist/build.d.ts +3 -3
- package/dist/build.d.ts.map +1 -1
- package/dist/cli/commands/audit.d.ts +7 -0
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts +23 -0
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/components.d.ts +31 -0
- package/dist/cli/handlers/components.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.d.ts +12 -1
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +32 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -0
- package/dist/cli/handlers/scenario.d.ts +39 -0
- package/dist/cli/handlers/scenario.d.ts.map +1 -0
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
- package/dist/cli/mcp/server.d.ts +35 -2
- package/dist/cli/mcp/server.d.ts.map +1 -1
- package/dist/cli/mcp/tools/explain.d.ts +6 -0
- package/dist/cli/mcp/tools/explain.d.ts.map +1 -1
- package/dist/cli/mcp/types.d.ts +29 -1
- package/dist/cli/mcp/types.d.ts.map +1 -1
- package/dist/cli/plugins.d.ts +1 -1
- package/dist/cli/plugins.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +14 -2
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/cli/reporters/stylish.d.ts +15 -1
- package/dist/cli/reporters/stylish.d.ts.map +1 -1
- package/dist/codegen/docs-rule-scanning.d.ts.map +1 -1
- package/dist/components/auto-release.d.ts +4 -0
- package/dist/components/auto-release.d.ts.map +1 -1
- package/dist/components/capability.d.ts +17 -2
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +7 -0
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/component.d.ts +15 -0
- package/dist/components/component.d.ts.map +1 -1
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/starter-plugin.d.ts +2 -0
- package/dist/components/starter-plugin.d.ts.map +1 -1
- package/dist/components/verbs/ensure-secret.d.ts +50 -0
- package/dist/components/verbs/ensure-secret.d.ts.map +1 -0
- package/dist/components/verbs/index.d.ts +13 -0
- package/dist/components/verbs/index.d.ts.map +1 -1
- package/dist/components/verbs/r2-sync.d.ts +76 -0
- package/dist/components/verbs/r2-sync.d.ts.map +1 -0
- package/dist/components/verbs/run-agent.d.ts +499 -0
- package/dist/components/verbs/run-agent.d.ts.map +1 -0
- package/dist/components/verbs/sign.d.ts +30 -0
- package/dist/components/verbs/sign.d.ts.map +1 -1
- package/dist/components/verbs/wrangler.d.ts +108 -0
- package/dist/components/verbs/wrangler.d.ts.map +1 -0
- package/dist/composite.d.ts +6 -1
- package/dist/composite.d.ts.map +1 -1
- package/dist/config.d.ts +26 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/deep-observation.d.ts +14 -0
- package/dist/deep-observation.d.ts.map +1 -1
- package/dist/discovery/collect.d.ts.map +1 -1
- package/dist/discovery/fold-import.d.ts +15 -1
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/fold-rank.d.ts +66 -0
- package/dist/discovery/fold-rank.d.ts.map +1 -0
- package/dist/discovery/index.d.ts +15 -0
- package/dist/discovery/index.d.ts.map +1 -1
- package/dist/discovery/param-deps.d.ts +17 -0
- package/dist/discovery/param-deps.d.ts.map +1 -0
- package/dist/effect-receipt.d.ts +177 -0
- package/dist/effect-receipt.d.ts.map +1 -0
- package/dist/fold/fold.d.ts +55 -2
- package/dist/fold/fold.d.ts.map +1 -1
- package/dist/fold/subset.d.ts +20 -0
- package/dist/fold/subset.d.ts.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/lexicon-schema.d.ts +2 -0
- package/dist/lexicon-schema.d.ts.map +1 -1
- package/dist/lexicon.d.ts +137 -3
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/change-set.d.ts +33 -5
- package/dist/lifecycle/change-set.d.ts.map +1 -1
- package/dist/lifecycle/converge-ledger.d.ts +90 -0
- package/dist/lifecycle/converge-ledger.d.ts.map +1 -0
- package/dist/lifecycle/deep-diff.d.ts +18 -0
- package/dist/lifecycle/deep-diff.d.ts.map +1 -1
- package/dist/lifecycle/deep-observe.d.ts +9 -1
- package/dist/lifecycle/deep-observe.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts +33 -0
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -0
- package/dist/lifecycle/git.d.ts +145 -21
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/index.d.ts +6 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/lease.d.ts +113 -0
- package/dist/lifecycle/lease.d.ts.map +1 -0
- package/dist/lifecycle/observation-baseline.d.ts +21 -3
- package/dist/lifecycle/observation-baseline.d.ts.map +1 -1
- package/dist/lifecycle/receipt-plan.d.ts +62 -0
- package/dist/lifecycle/receipt-plan.d.ts.map +1 -0
- package/dist/lifecycle/release-ledger.d.ts +20 -0
- package/dist/lifecycle/release-ledger.d.ts.map +1 -1
- package/dist/lifecycle/scenario-eval.d.ts +42 -0
- package/dist/lifecycle/scenario-eval.d.ts.map +1 -0
- package/dist/lifecycle/scenario.d.ts +163 -0
- package/dist/lifecycle/scenario.d.ts.map +1 -0
- package/dist/lifecycle/symptoms.d.ts +63 -0
- package/dist/lifecycle/symptoms.d.ts.map +1 -0
- package/dist/lifecycle/teardown.d.ts +6 -4
- package/dist/lifecycle/teardown.d.ts.map +1 -1
- package/dist/lifecycle/unobserved-gate.d.ts +67 -0
- package/dist/lifecycle/unobserved-gate.d.ts.map +1 -0
- package/dist/lint/knowledge-checks.d.ts +48 -0
- package/dist/lint/knowledge-checks.d.ts.map +1 -0
- package/dist/lint/output-checks.d.ts +5 -0
- package/dist/lint/output-checks.d.ts.map +1 -0
- package/dist/lint/output-docs.d.ts +94 -0
- package/dist/lint/output-docs.d.ts.map +1 -0
- package/dist/lint/pipeline-change-gate.d.ts +101 -0
- package/dist/lint/pipeline-change-gate.d.ts.map +1 -0
- package/dist/lint/post-synth.d.ts +41 -0
- package/dist/lint/post-synth.d.ts.map +1 -1
- package/dist/lint/receipt-checks.d.ts +9 -0
- package/dist/lint/receipt-checks.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.d.ts +11 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.d.ts.map +1 -0
- package/dist/lint/rules/cor022-receipt-leaf.d.ts +13 -0
- package/dist/lint/rules/cor022-receipt-leaf.d.ts.map +1 -0
- package/dist/lint/rules/cor024-receipt-secret-pointer.d.ts +3 -0
- package/dist/lint/rules/cor024-receipt-secret-pointer.d.ts.map +1 -0
- package/dist/lint/rules/evl001-non-literal-expression.d.ts.map +1 -1
- package/dist/lint/rules/index.d.ts +3 -1
- package/dist/lint/rules/index.d.ts.map +1 -1
- package/dist/lsp/lexicon-providers.d.ts +7 -0
- package/dist/lsp/lexicon-providers.d.ts.map +1 -1
- package/dist/okf-read.d.ts +78 -0
- package/dist/okf-read.d.ts.map +1 -0
- package/dist/op/activity-contract.d.ts +139 -0
- package/dist/op/activity-contract.d.ts.map +1 -0
- package/dist/op/builders.d.ts +140 -3
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/converge-rule.d.ts +161 -0
- package/dist/op/converge-rule.d.ts.map +1 -0
- package/dist/op/generate-pipeline.d.ts +39 -0
- package/dist/op/generate-pipeline.d.ts.map +1 -0
- package/dist/op/index.d.ts +18 -2
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/local-executor.d.ts +2 -1
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/op-verb-class.d.ts +42 -0
- package/dist/op/op-verb-class.d.ts.map +1 -0
- package/dist/op/operator.d.ts +128 -0
- package/dist/op/operator.d.ts.map +1 -0
- package/dist/op/receipt-store.d.ts +138 -0
- package/dist/op/receipt-store.d.ts.map +1 -0
- package/dist/op/step-output-ref.d.ts +187 -0
- package/dist/op/step-output-ref.d.ts.map +1 -0
- package/dist/op/types.d.ts +49 -2
- package/dist/op/types.d.ts.map +1 -1
- package/dist/provenance.d.ts +73 -3
- package/dist/provenance.d.ts.map +1 -1
- package/dist/runtime-adapter.d.ts +7 -1
- package/dist/runtime-adapter.d.ts.map +1 -1
- package/dist/secret-materialization.d.ts +138 -0
- package/dist/secret-materialization.d.ts.map +1 -0
- package/dist/secret-provenance.d.ts +218 -0
- package/dist/secret-provenance.d.ts.map +1 -0
- package/dist/serializer.d.ts +29 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/toml.d.ts +40 -5
- package/dist/toml.d.ts.map +1 -1
- package/dist/yaml.d.ts.map +1 -1
- package/package.json +4 -1
- package/src/audit/catalog.test.ts +1 -1
- package/src/audit/catalog.ts +75 -3
- package/src/audit/core.test.ts +57 -0
- package/src/audit/core.ts +0 -0
- package/src/audit/detect-bundle.test.ts +1 -1
- package/src/audit/discover.test.ts +24 -0
- package/src/audit/discover.ts +40 -4
- package/src/audit/fetch.test.ts +216 -3
- package/src/audit/fetch.ts +270 -59
- package/src/audit/report-html.ts +5 -2
- package/src/audit/report-model.ts +9 -0
- package/src/audit/report.test.ts +22 -0
- package/src/audit/report.ts +3 -2
- package/src/audit/rules-doc.ts +13 -1
- package/src/audit/secrets.test.ts +303 -0
- package/src/audit/secrets.ts +406 -0
- package/src/audit/wrangler.test.ts +230 -0
- package/src/audit/wrangler.ts +290 -0
- package/src/build.test.ts +41 -0
- package/src/build.ts +39 -6
- package/src/cli/command-group.ts +1 -1
- package/src/cli/commands/__fixtures__/audit-fountain/agents/fleet.yaml +27 -0
- package/src/cli/commands/__fixtures__/audit-fountain/k8s/deploy.yaml +16 -0
- package/src/cli/commands/__fixtures__/audit-fountain-clean/fleet.yaml +20 -0
- package/src/cli/commands/__fixtures__/schemas/sarif-2.1.0.schema.json +2882 -0
- package/src/cli/commands/audit.test.ts +268 -1
- package/src/cli/commands/audit.ts +87 -18
- package/src/cli/commands/build.test.ts +245 -0
- package/src/cli/commands/build.ts +210 -21
- package/src/cli/commands/lint.ts +15 -3
- package/src/cli/handlers/build.ts +2 -0
- package/src/cli/handlers/components.test.ts +199 -1
- package/src/cli/handlers/components.ts +160 -3
- package/src/cli/handlers/explain.test.ts +70 -1
- package/src/cli/handlers/graph.test.ts +20 -0
- package/src/cli/handlers/graph.ts +12 -3
- package/src/cli/handlers/lifecycle.test.ts +115 -1
- package/src/cli/handlers/lifecycle.ts +96 -15
- package/src/cli/handlers/operator.test.ts +255 -0
- package/src/cli/handlers/operator.ts +240 -0
- package/src/cli/handlers/scenario.test.ts +456 -0
- package/src/cli/handlers/scenario.ts +330 -0
- package/src/cli/main.test.ts +23 -0
- package/src/cli/main.ts +72 -1
- package/src/cli/mcp/resource-handlers.ts +38 -1
- package/src/cli/mcp/server.test.ts +323 -3
- package/src/cli/mcp/server.ts +84 -7
- package/src/cli/mcp/tools/explain.ts +51 -2
- package/src/cli/mcp/types.ts +27 -1
- package/src/cli/plugins.ts +4 -2
- package/src/cli/registry.ts +14 -2
- package/src/cli/reporters/stylish.test.ts +154 -0
- package/src/cli/reporters/stylish.ts +154 -33
- package/src/codegen/docs-rule-scanning.test.ts +42 -0
- package/src/codegen/docs-rule-scanning.ts +25 -2
- package/src/components/README.md +7 -0
- package/src/components/auto-release.ts +6 -0
- package/src/components/capability.ts +17 -2
- package/src/components/cli-support.test.ts +17 -0
- package/src/components/cli-support.ts +13 -1
- package/src/components/component-schema.test.ts +32 -0
- package/src/components/component.schema.json +6 -0
- package/src/components/component.test.ts +21 -0
- package/src/components/component.ts +15 -0
- package/src/components/driver.ts +12 -4
- package/src/components/registry.test.ts +7 -2
- package/src/components/starter-plugin.ts +17 -0
- package/src/components/verbs/ensure-secret.test.ts +130 -0
- package/src/components/verbs/ensure-secret.ts +79 -0
- package/src/components/verbs/index.ts +13 -0
- package/src/components/verbs/r2-sync.test.ts +107 -0
- package/src/components/verbs/r2-sync.ts +124 -0
- package/src/components/verbs/run-agent.test.ts +683 -0
- package/src/components/verbs/run-agent.ts +786 -0
- package/src/components/verbs/sign.test.ts +19 -0
- package/src/components/verbs/sign.ts +34 -2
- package/src/components/verbs/wrangler.test.ts +170 -0
- package/src/components/verbs/wrangler.ts +241 -0
- package/src/composite.ts +31 -2
- package/src/config.test.ts +15 -0
- package/src/config.ts +30 -0
- package/src/deep-observation.test.ts +19 -0
- package/src/deep-observation.ts +17 -0
- package/src/discovery/collect.ts +11 -2
- package/src/discovery/fold-import.test.ts +54 -0
- package/src/discovery/fold-import.ts +178 -38
- package/src/discovery/fold-rank.test.ts +197 -0
- package/src/discovery/fold-rank.ts +346 -0
- package/src/discovery/index.ts +16 -1
- package/src/discovery/param-deps.test.ts +118 -0
- package/src/discovery/param-deps.ts +170 -0
- package/src/effect-receipt-exclusion.test.ts +190 -0
- package/src/effect-receipt.test.ts +419 -0
- package/src/effect-receipt.ts +412 -0
- package/src/fold/fold.test.ts +6 -2
- package/src/fold/fold.ts +184 -3
- package/src/fold/subset.test.ts +95 -6
- package/src/fold/subset.ts +66 -2
- package/src/index.ts +4 -0
- package/src/lexicon-schema.ts +3 -0
- package/src/lexicon.ts +151 -5
- package/src/lifecycle/change-set.ts +46 -7
- package/src/lifecycle/converge-ledger.test.ts +199 -0
- package/src/lifecycle/converge-ledger.ts +179 -0
- package/src/lifecycle/deep-diff.test.ts +79 -1
- package/src/lifecycle/deep-diff.ts +23 -0
- package/src/lifecycle/deep-observe.ts +13 -2
- package/src/lifecycle/gate-ledger.test.ts +103 -0
- package/src/lifecycle/gate-ledger.ts +140 -0
- package/src/lifecycle/git.test.ts +430 -0
- package/src/lifecycle/git.ts +446 -84
- package/src/lifecycle/index.ts +6 -0
- package/src/lifecycle/lease.test.ts +343 -0
- package/src/lifecycle/lease.ts +270 -0
- package/src/lifecycle/observation-baseline.test.ts +46 -0
- package/src/lifecycle/observation-baseline.ts +33 -1
- package/src/lifecycle/receipt-plan.test.ts +250 -0
- package/src/lifecycle/receipt-plan.ts +249 -0
- package/src/lifecycle/release-ledger.ts +20 -0
- package/src/lifecycle/scenario-eval.test.ts +199 -0
- package/src/lifecycle/scenario-eval.ts +158 -0
- package/src/lifecycle/scenario.test.ts +195 -0
- package/src/lifecycle/scenario.ts +321 -0
- package/src/lifecycle/symptoms.test.ts +116 -0
- package/src/lifecycle/symptoms.ts +126 -0
- package/src/lifecycle/teardown.test.ts +31 -0
- package/src/lifecycle/teardown.ts +6 -4
- package/src/lifecycle/unobserved-gate.test.ts +109 -0
- package/src/lifecycle/unobserved-gate.ts +102 -0
- package/src/lint/knowledge-checks.test.ts +80 -0
- package/src/lint/knowledge-checks.ts +74 -0
- package/src/lint/output-checks.test.ts +85 -0
- package/src/lint/output-checks.ts +99 -0
- package/src/lint/output-docs.test.ts +220 -0
- package/src/lint/output-docs.ts +204 -0
- package/src/lint/pipeline-change-gate.test.ts +144 -0
- package/src/lint/pipeline-change-gate.ts +153 -0
- package/src/lint/post-synth.test.ts +97 -0
- package/src/lint/post-synth.ts +60 -0
- package/src/lint/receipt-checks.test.ts +101 -0
- package/src/lint/receipt-checks.ts +93 -0
- package/src/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.ts +26 -0
- package/src/lint/rules/comp/comp.test.ts +49 -1
- package/src/lint/rules/cor022-receipt-leaf.test.ts +116 -0
- package/src/lint/rules/cor022-receipt-leaf.ts +130 -0
- package/src/lint/rules/cor024-receipt-secret-pointer.test.ts +121 -0
- package/src/lint/rules/cor024-receipt-secret-pointer.ts +218 -0
- package/src/lint/rules/evl001-non-literal-expression.test.ts +35 -3
- package/src/lint/rules/evl001-non-literal-expression.ts +7 -0
- package/src/lint/rules/index.ts +7 -1
- package/src/lsp/lexicon-providers.test.ts +44 -0
- package/src/lsp/lexicon-providers.ts +11 -1
- package/src/okf-read.test.ts +149 -0
- package/src/okf-read.ts +197 -0
- package/src/op/activity-contract.test.ts +180 -0
- package/src/op/activity-contract.ts +278 -0
- package/src/op/builders-exports.test.ts +17 -1
- package/src/op/builders.ts +198 -6
- package/src/op/converge-rule.test.ts +179 -0
- package/src/op/converge-rule.ts +311 -0
- package/src/op/effect-step.test.ts +311 -0
- package/src/op/generate-pipeline.test.ts +53 -0
- package/src/op/generate-pipeline.ts +99 -0
- package/src/op/index.ts +40 -3
- package/src/op/local-executor.test.ts +92 -0
- package/src/op/local-executor.ts +212 -29
- package/src/op/op-verb-class.test.ts +126 -0
- package/src/op/op-verb-class.ts +115 -0
- package/src/op/op.test.ts +25 -2
- package/src/op/operator.test.ts +346 -0
- package/src/op/operator.ts +213 -0
- package/src/op/receipt-store.ts +211 -0
- package/src/op/step-output-ref.test.ts +334 -0
- package/src/op/step-output-ref.ts +453 -0
- package/src/op/types.ts +51 -2
- package/src/provenance.test.ts +151 -4
- package/src/provenance.ts +118 -4
- package/src/runtime-adapter.ts +31 -10
- package/src/secret-materialization.test.ts +199 -0
- package/src/secret-materialization.ts +235 -0
- package/src/secret-provenance.test.ts +388 -0
- package/src/secret-provenance.ts +475 -0
- package/src/serializer.ts +30 -0
- package/src/toml.test.ts +157 -384
- package/src/toml.ts +371 -5
- package/src/yaml.test.ts +88 -0
- package/src/yaml.ts +76 -6
package/src/cli/registry.ts
CHANGED
|
@@ -194,9 +194,9 @@ export interface ParsedArgs {
|
|
|
194
194
|
check?: boolean;
|
|
195
195
|
/** `chant dev surface-diff --update-snapshot --bump` — bump the lexicon's package.json version by the drift severity so the accepted surface is publishable (#616). */
|
|
196
196
|
bump?: boolean;
|
|
197
|
-
/** `chant components release record --component <name>` (#568) — component name for the release record being appended. */
|
|
197
|
+
/** `chant components release record --component <name>` (#568) — component name for the release record being appended. Also `chant components export <env> --component <name>` (#929) — which component's most recent recorded build to export. */
|
|
198
198
|
component?: string;
|
|
199
|
-
/** `chant components release record --digest <sha256:...>` (#568) — artifact digest to record, joining this release to the build archive/ledger. */
|
|
199
|
+
/** `chant components release record --digest <sha256:...>` (#568) — artifact digest to record, joining this release to the build archive/ledger. Also `chant components export --digest <manifestDigest>` (#929) — a build archive manifest digest to export directly, bypassing env/component resolution. */
|
|
200
200
|
digest?: string;
|
|
201
201
|
/** `chant components release record --git-sha <sha>` (#568) — git commit the deploy was built from. */
|
|
202
202
|
gitSha?: string;
|
|
@@ -218,6 +218,10 @@ export interface ParsedArgs {
|
|
|
218
218
|
fold?: boolean;
|
|
219
219
|
/** `chant build --sandbox` (#1045 Phase 2) — opt-in: run-fallback source files (or every file, without `--fold`) execute together, isolated, in one sandboxed child process instead of in-process. Also settable project-wide via `chant.config.ts`'s `build.sandbox: true`; the flag always wins when set. Default (flag omitted): in-process execution, unchanged. */
|
|
220
220
|
sandbox?: boolean;
|
|
221
|
+
/** `chant build --fold --fold-rank` (#1083) — after a `--fold` build, print blockers ranked by dominator retained-count over the forward import-failure graph, plus the separate reverse-taint bucket (chant #1044). Bare boolean form; see {@link foldRankCollapsedFile} for the file-writing form. No-op without `--fold`. */
|
|
222
|
+
foldRank?: boolean;
|
|
223
|
+
/** `chant build --fold --fold-rank <path>` (#1083) — same ranking as {@link foldRank}, ALSO exported in Brendan Gregg collapsed stack format (weighted by retained count) to `<path>`, so it renders in any flame/icicle viewer. Mutually exclusive with the bare-boolean form at the parse level (same context-sensitive lookahead as `--report`), but the text report still prints either way. */
|
|
224
|
+
foldRankCollapsedFile?: string;
|
|
221
225
|
/** `chant build --param name=value` (#1064) — repeatable. Bound to `params.<name>` (`@intentius/chant/params`) for source to reference, after validation against `chant.config.ts`'s declared `buildParams`. Highest precedence over `--params-file`/a declared `env` mapping/the declared `default`. */
|
|
222
226
|
param?: string[];
|
|
223
227
|
/** `chant build --params-file <path>` (#1064) — a JSON file of `{ "name": value }` build-time parameter values. Second precedence, after `--param`. */
|
|
@@ -228,6 +232,14 @@ export interface ParsedArgs {
|
|
|
228
232
|
* forgejo today) — the same generator `chant build --components --generate
|
|
229
233
|
* <lexicon>` uses, reused rather than re-derived. */
|
|
230
234
|
projection?: string;
|
|
235
|
+
/** `chant operator --interval <duration>` (#1485) — poll interval between rounds, e.g. "30s", "5m". Default: 60s. */
|
|
236
|
+
interval?: string;
|
|
237
|
+
/** `chant operator --lease-ttl <duration>` (#1485) — how long an acquired lease is valid before it's reclaimable by another operator. Default: 5m. */
|
|
238
|
+
leaseTtl?: string;
|
|
239
|
+
/** `chant operator --once` (#1485) — run a single round and exit, instead of looping until Ctrl-C. Also the offline test/cron-invoker story. */
|
|
240
|
+
once?: boolean;
|
|
241
|
+
/** `chant approve <op> <gate> --note <text>` (#1485) — optional free-text context recorded on the gate-resolution fact (e.g. a PR URL). */
|
|
242
|
+
note?: string;
|
|
231
243
|
}
|
|
232
244
|
|
|
233
245
|
/**
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { describe, test, expect, beforeEach, afterEach } from "vitest";
|
|
2
2
|
import { formatStylish, formatSummary, formatJson, formatSarif } from "./stylish";
|
|
3
3
|
import type { LintDiagnostic, LintRule } from "../../lint/rule";
|
|
4
|
+
import type { OkfBundle, OkfConcept } from "../../okf-read";
|
|
5
|
+
|
|
6
|
+
function concept(overrides: Partial<OkfConcept> & { path: string }): OkfConcept {
|
|
7
|
+
return {
|
|
8
|
+
type: "decision",
|
|
9
|
+
binds: [],
|
|
10
|
+
frontmatter: {},
|
|
11
|
+
body: "",
|
|
12
|
+
...overrides,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
4
15
|
|
|
5
16
|
describe("formatStylish", () => {
|
|
6
17
|
const originalNoColor = process.env.NO_COLOR;
|
|
@@ -123,6 +134,149 @@ describe("formatStylish", () => {
|
|
|
123
134
|
});
|
|
124
135
|
});
|
|
125
136
|
|
|
137
|
+
describe("formatStylish suppressed section (#1866, design #1059)", () => {
|
|
138
|
+
beforeEach(() => {
|
|
139
|
+
process.env.NO_COLOR = "1";
|
|
140
|
+
});
|
|
141
|
+
afterEach(() => {
|
|
142
|
+
delete process.env.NO_COLOR;
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const suppressedDiag: LintDiagnostic & { reason?: string } = {
|
|
146
|
+
file: "src/storage.ts",
|
|
147
|
+
line: 12,
|
|
148
|
+
column: 1,
|
|
149
|
+
ruleId: "AWS021",
|
|
150
|
+
severity: "warning",
|
|
151
|
+
message: "Bucket ACL is public-read",
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
test("no section when there are no suppressed diagnostics", () => {
|
|
155
|
+
const result = formatStylish([], []);
|
|
156
|
+
expect(result).not.toContain("Suppressed");
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("renders a Suppressed section when suppressed diagnostics exist", () => {
|
|
160
|
+
const result = formatStylish([], [{ ...suppressedDiag, reason: "backwards compat" }]);
|
|
161
|
+
|
|
162
|
+
expect(result).toContain("Suppressed");
|
|
163
|
+
expect(result).toContain("src/storage.ts");
|
|
164
|
+
expect(result).toContain("AWS021");
|
|
165
|
+
expect(result).toContain("backwards compat");
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("renders the section even when there are no active diagnostics", () => {
|
|
169
|
+
const result = formatStylish([], [suppressedDiag]);
|
|
170
|
+
expect(result).toContain("Suppressed");
|
|
171
|
+
// No active errors/warnings were counted from a suppressed diagnostic
|
|
172
|
+
expect(result).toContain("No problems found");
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("an okf: citation resolves to the concept's title and bundle-relative path", () => {
|
|
176
|
+
const bundle: OkfBundle = {
|
|
177
|
+
concepts: [
|
|
178
|
+
concept({
|
|
179
|
+
path: "decisions/public-assets.md",
|
|
180
|
+
title: "Public asset bucket stays world-readable",
|
|
181
|
+
}),
|
|
182
|
+
],
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const result = formatStylish(
|
|
186
|
+
[],
|
|
187
|
+
[{ ...suppressedDiag, reason: "okf:/decisions/public-assets.md" }],
|
|
188
|
+
bundle,
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
expect(result).toContain("Public asset bucket stays world-readable");
|
|
192
|
+
expect(result).toContain("decisions/public-assets.md");
|
|
193
|
+
expect(result).not.toContain("okf:/decisions/public-assets.md");
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("also resolves a citation without a leading slash", () => {
|
|
197
|
+
const bundle: OkfBundle = {
|
|
198
|
+
concepts: [concept({ path: "decisions/public-assets.md", title: "Public assets" })],
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const result = formatStylish(
|
|
202
|
+
[],
|
|
203
|
+
[{ ...suppressedDiag, reason: "okf:decisions/public-assets.md" }],
|
|
204
|
+
bundle,
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
expect(result).toContain("Public assets");
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("an unresolvable citation prints the raw reason with a warning", () => {
|
|
211
|
+
const bundle: OkfBundle = { concepts: [] };
|
|
212
|
+
|
|
213
|
+
const result = formatStylish(
|
|
214
|
+
[],
|
|
215
|
+
[{ ...suppressedDiag, reason: "okf:/decisions/does-not-exist.md" }],
|
|
216
|
+
bundle,
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
expect(result).toContain("okf:/decisions/does-not-exist.md");
|
|
220
|
+
expect(result).toContain("unresolved okf citation");
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test("an okf: citation with no bundle loaded prints the raw reason with a warning", () => {
|
|
224
|
+
const result = formatStylish([], [{ ...suppressedDiag, reason: "okf:/decisions/public-assets.md" }]);
|
|
225
|
+
|
|
226
|
+
expect(result).toContain("okf:/decisions/public-assets.md");
|
|
227
|
+
expect(result).toContain("unresolved okf citation");
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test("a plain (non-okf) reason renders as-is, unaffected by a loaded bundle", () => {
|
|
231
|
+
const bundle: OkfBundle = {
|
|
232
|
+
concepts: [concept({ path: "decisions/public-assets.md", title: "Public assets" })],
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const result = formatStylish([], [{ ...suppressedDiag, reason: "grandfathered, see #412" }], bundle);
|
|
236
|
+
|
|
237
|
+
expect(result).toContain("grandfathered, see #412");
|
|
238
|
+
expect(result).not.toContain("Public assets");
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test("a suppressed diagnostic with no reason still renders", () => {
|
|
242
|
+
const result = formatStylish([], [suppressedDiag]);
|
|
243
|
+
expect(result).toContain("AWS021");
|
|
244
|
+
expect(result).toContain("suppressed");
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test("active diagnostics and the suppressed section coexist", () => {
|
|
248
|
+
const active: LintDiagnostic = {
|
|
249
|
+
file: "src/other.ts",
|
|
250
|
+
line: 1,
|
|
251
|
+
column: 1,
|
|
252
|
+
ruleId: "COR001",
|
|
253
|
+
severity: "error",
|
|
254
|
+
message: "Active problem",
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const result = formatStylish([active], [{ ...suppressedDiag, reason: "okf:/decisions/public-assets.md" }], {
|
|
258
|
+
concepts: [concept({ path: "decisions/public-assets.md", title: "Public assets" })],
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
expect(result).toContain("src/other.ts");
|
|
262
|
+
expect(result).toContain("Active problem");
|
|
263
|
+
expect(result).toContain("1 error");
|
|
264
|
+
expect(result).toContain("Suppressed");
|
|
265
|
+
expect(result).toContain("Public assets");
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test("a title-less resolved concept falls back to its path as the label", () => {
|
|
269
|
+
const bundle: OkfBundle = {
|
|
270
|
+
concepts: [concept({ path: "decisions/public-assets.md" })],
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const result = formatStylish([], [{ ...suppressedDiag, reason: "okf:/decisions/public-assets.md" }], bundle);
|
|
274
|
+
|
|
275
|
+
expect(result).toContain("decisions/public-assets.md");
|
|
276
|
+
expect(result).not.toContain("unresolved");
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
126
280
|
describe("formatSummary", () => {
|
|
127
281
|
beforeEach(() => {
|
|
128
282
|
process.env.NO_COLOR = "1";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { LintDiagnostic, LintRule, Severity } from "../../lint/rule";
|
|
2
|
+
import type { OkfBundle } from "../../okf-read";
|
|
2
3
|
import { pathToFileURL } from "node:url";
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -33,64 +34,184 @@ function color(text: string, colorCode: string): string {
|
|
|
33
34
|
/**
|
|
34
35
|
* Format diagnostics in stylish format (similar to ESLint)
|
|
35
36
|
* Groups by file, shows severity, message, and rule ID
|
|
37
|
+
*
|
|
38
|
+
* @param diagnostics - Active (non-suppressed) diagnostics
|
|
39
|
+
* @param suppressed - Suppressed diagnostics with optional reason (optional). When
|
|
40
|
+
* present and non-empty, a "Suppressed" section is appended (#1866, design #1059)
|
|
41
|
+
* below the active diagnostics, mirroring SARIF's `suppressed` param on
|
|
42
|
+
* {@link formatSarif} rather than changing what "diagnostics" means here.
|
|
43
|
+
* @param bundle - The project's loaded OKF knowledge bundle (`../../okf-read`'s
|
|
44
|
+
* `loadOkfBundle`), used to resolve `okf:` citations in suppression reasons.
|
|
45
|
+
* Omitted (or a bundle with no matching concept) falls back to printing the
|
|
46
|
+
* raw reason with a warning — this never affects suppression itself, only
|
|
47
|
+
* how a cited reason renders.
|
|
36
48
|
*/
|
|
37
|
-
export function formatStylish(
|
|
38
|
-
|
|
49
|
+
export function formatStylish(
|
|
50
|
+
diagnostics: LintDiagnostic[],
|
|
51
|
+
suppressed?: Array<LintDiagnostic & { reason?: string }>,
|
|
52
|
+
bundle?: OkfBundle,
|
|
53
|
+
): string {
|
|
54
|
+
const hasSuppressed = suppressed !== undefined && suppressed.length > 0;
|
|
55
|
+
|
|
56
|
+
if (diagnostics.length === 0 && !hasSuppressed) {
|
|
39
57
|
return formatSummary(0, 0);
|
|
40
58
|
}
|
|
41
59
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
60
|
+
const lines: string[] = [];
|
|
61
|
+
let errorCount = 0;
|
|
62
|
+
let warningCount = 0;
|
|
63
|
+
|
|
64
|
+
if (diagnostics.length > 0) {
|
|
65
|
+
// Group by file
|
|
66
|
+
const byFile = new Map<string, LintDiagnostic[]>();
|
|
67
|
+
for (const diag of diagnostics) {
|
|
68
|
+
const existing = byFile.get(diag.file) ?? [];
|
|
69
|
+
existing.push(diag);
|
|
70
|
+
byFile.set(diag.file, existing);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
for (const [file, fileDiags] of byFile) {
|
|
74
|
+
// File header with underline
|
|
75
|
+
lines.push("");
|
|
76
|
+
lines.push(color(file, colors.underline));
|
|
77
|
+
|
|
78
|
+
// Sort by line, then column
|
|
79
|
+
fileDiags.sort((a, b) => {
|
|
80
|
+
if (a.line !== b.line) return a.line - b.line;
|
|
81
|
+
return a.column - b.column;
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
for (const diag of fileDiags) {
|
|
85
|
+
// Count errors and warnings
|
|
86
|
+
if (diag.severity === "error") {
|
|
87
|
+
errorCount++;
|
|
88
|
+
} else if (diag.severity === "warning") {
|
|
89
|
+
warningCount++;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Format: " line:col severity message ruleId"
|
|
93
|
+
const location = color(
|
|
94
|
+
`${String(diag.line).padStart(4)}:${String(diag.column).padEnd(3)}`,
|
|
95
|
+
colors.gray
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const severityColor = diag.severity === "error" ? colors.red : colors.yellow;
|
|
99
|
+
const severity = color(diag.severity.padEnd(7), severityColor);
|
|
100
|
+
|
|
101
|
+
const ruleId = color(diag.ruleId, colors.gray);
|
|
102
|
+
|
|
103
|
+
lines.push(` ${location} ${severity} ${diag.message} ${ruleId}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (hasSuppressed) {
|
|
109
|
+
lines.push(...formatSuppressedSection(suppressed!, bundle));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Summary line
|
|
113
|
+
lines.push("");
|
|
114
|
+
const summary = formatSummary(errorCount, warningCount);
|
|
115
|
+
lines.push(summary);
|
|
116
|
+
|
|
117
|
+
return lines.join("\n");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* A suppression reason's `okf:` citation, resolved against a loaded bundle.
|
|
122
|
+
*/
|
|
123
|
+
interface ResolvedCitation {
|
|
124
|
+
/** What to print beside the rule id: the concept title when resolved, the raw reason otherwise. */
|
|
125
|
+
label: string;
|
|
126
|
+
/** The concept's bundle-relative path, printed on the line below — only set when resolved. */
|
|
127
|
+
path?: string;
|
|
128
|
+
/** True when the reason carried an `okf:` token that named no concept in `bundle`. */
|
|
129
|
+
unresolved: boolean;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Resolve a suppression reason's `okf:<path>` citation (#1866, design #1059)
|
|
134
|
+
* against a loaded bundle's concepts, matched on `OkfConcept.path`. Not an
|
|
135
|
+
* `okf:` citation at all: returns `null`, and the reason renders as plain
|
|
136
|
+
* text, same as before this section existed. A leading `/` on the cited path
|
|
137
|
+
* is accepted and stripped — `okf-read.ts`'s bundle-relative paths never
|
|
138
|
+
* carry one, but the design doc's worked example writes citations with one
|
|
139
|
+
* (mirroring an absolute-from-bundle-root reading), so both forms resolve.
|
|
140
|
+
*/
|
|
141
|
+
function resolveOkfCitation(reason: string, bundle: OkfBundle | undefined): ResolvedCitation | null {
|
|
142
|
+
const prefix = "okf:";
|
|
143
|
+
if (!reason.startsWith(prefix)) return null;
|
|
144
|
+
|
|
145
|
+
const token = reason.slice(prefix.length).trim().replace(/^\/+/, "");
|
|
146
|
+
const concept = bundle?.concepts.find((c) => c.path === token);
|
|
147
|
+
|
|
148
|
+
if (concept) {
|
|
149
|
+
return { label: concept.title ?? concept.path, path: concept.path, unresolved: false };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return { label: reason, unresolved: true };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Render the "Suppressed" section: every suppressed diagnostic, grouped by
|
|
157
|
+
* file like the active section above, with an `okf:` reason resolved to its
|
|
158
|
+
* concept's title (and bundle-relative path on the line below) when
|
|
159
|
+
* `bundle` has a match. A citation naming no concept — including when no
|
|
160
|
+
* `bundle` was loaded at all — prints the raw reason plus a warning; a
|
|
161
|
+
* plain (non-`okf:`) reason prints as-is. Never affects which diagnostics
|
|
162
|
+
* are suppressed, only how the reason renders (design #1059: "a cited
|
|
163
|
+
* suppression suppresses exactly as an uncited one does").
|
|
164
|
+
*/
|
|
165
|
+
function formatSuppressedSection(
|
|
166
|
+
suppressed: Array<LintDiagnostic & { reason?: string }>,
|
|
167
|
+
bundle: OkfBundle | undefined,
|
|
168
|
+
): string[] {
|
|
169
|
+
const lines: string[] = [];
|
|
170
|
+
|
|
171
|
+
const byFile = new Map<string, Array<LintDiagnostic & { reason?: string }>>();
|
|
172
|
+
for (const diag of suppressed) {
|
|
45
173
|
const existing = byFile.get(diag.file) ?? [];
|
|
46
174
|
existing.push(diag);
|
|
47
175
|
byFile.set(diag.file, existing);
|
|
48
176
|
}
|
|
49
177
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
let warningCount = 0;
|
|
178
|
+
lines.push("");
|
|
179
|
+
lines.push(color("Suppressed", colors.underline));
|
|
53
180
|
|
|
54
181
|
for (const [file, fileDiags] of byFile) {
|
|
55
|
-
// File header with underline
|
|
56
182
|
lines.push("");
|
|
57
183
|
lines.push(color(file, colors.underline));
|
|
58
184
|
|
|
59
|
-
// Sort by line, then column
|
|
60
185
|
fileDiags.sort((a, b) => {
|
|
61
186
|
if (a.line !== b.line) return a.line - b.line;
|
|
62
187
|
return a.column - b.column;
|
|
63
188
|
});
|
|
64
189
|
|
|
65
190
|
for (const diag of fileDiags) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
} else if (diag.severity === "warning") {
|
|
70
|
-
warningCount++;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Format: " line:col severity message ruleId"
|
|
74
|
-
const location = color(
|
|
75
|
-
`${String(diag.line).padStart(4)}:${String(diag.column).padEnd(3)}`,
|
|
76
|
-
colors.gray
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
const severityColor = diag.severity === "error" ? colors.red : colors.yellow;
|
|
80
|
-
const severity = color(diag.severity.padEnd(7), severityColor);
|
|
81
|
-
|
|
191
|
+
const locationPlain = `${String(diag.line).padStart(4)}:${String(diag.column).padEnd(3)}`;
|
|
192
|
+
const location = color(locationPlain, colors.gray);
|
|
193
|
+
const severity = color("suppressed", colors.cyan);
|
|
82
194
|
const ruleId = color(diag.ruleId, colors.gray);
|
|
83
195
|
|
|
84
|
-
|
|
196
|
+
const citation = diag.reason ? resolveOkfCitation(diag.reason, bundle) : null;
|
|
197
|
+
const indent = " ".repeat(2 + locationPlain.length + 2 + "suppressed".length + 2 + diag.ruleId.length + 2);
|
|
198
|
+
|
|
199
|
+
if (citation) {
|
|
200
|
+
lines.push(` ${location} ${severity} ${ruleId} ${citation.label}`);
|
|
201
|
+
if (!citation.unresolved) {
|
|
202
|
+
lines.push(`${indent}${color(citation.path!, colors.gray)}`);
|
|
203
|
+
} else {
|
|
204
|
+
lines.push(`${indent}${color("⚠ unresolved okf citation", colors.yellow)}`);
|
|
205
|
+
}
|
|
206
|
+
} else if (diag.reason) {
|
|
207
|
+
lines.push(` ${location} ${severity} ${ruleId} ${diag.reason}`);
|
|
208
|
+
} else {
|
|
209
|
+
lines.push(` ${location} ${severity} ${ruleId}`);
|
|
210
|
+
}
|
|
85
211
|
}
|
|
86
212
|
}
|
|
87
213
|
|
|
88
|
-
|
|
89
|
-
lines.push("");
|
|
90
|
-
const summary = formatSummary(errorCount, warningCount);
|
|
91
|
-
lines.push(summary);
|
|
92
|
-
|
|
93
|
-
return lines.join("\n");
|
|
214
|
+
return lines;
|
|
94
215
|
}
|
|
95
216
|
|
|
96
217
|
/**
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, test, expect, beforeAll, afterAll } from "vitest";
|
|
2
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { tmpdir } from "os";
|
|
5
|
+
import { scanRules } from "./docs-rule-scanning";
|
|
6
|
+
|
|
7
|
+
// #1938: a post-synth check's `description` string containing a
|
|
8
|
+
// backslash-escaped quote used to truncate at the first embedded `"`,
|
|
9
|
+
// garbling the generated docs table entry.
|
|
10
|
+
describe("scanRules — post-synth description extraction (#1938)", () => {
|
|
11
|
+
let dir: string;
|
|
12
|
+
|
|
13
|
+
beforeAll(() => {
|
|
14
|
+
dir = mkdtempSync(join(tmpdir(), "docs-rule-scanning-"));
|
|
15
|
+
mkdirSync(join(dir, "lint", "post-synth"), { recursive: true });
|
|
16
|
+
writeFileSync(
|
|
17
|
+
join(dir, "lint", "post-synth", "wk8505.ts"),
|
|
18
|
+
[
|
|
19
|
+
"export const wk8505 = {",
|
|
20
|
+
' id: "WK8505",',
|
|
21
|
+
' description: "A rule\'s \\"why\\" must be non-empty, and a backslash like C:\\\\path must survive intact",',
|
|
22
|
+
" check() { return []; },",
|
|
23
|
+
"};",
|
|
24
|
+
"",
|
|
25
|
+
].join("\n"),
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
afterAll(() => {
|
|
30
|
+
rmSync(dir, { recursive: true, force: true });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("extracts the full description past an escaped quote and a backslash", () => {
|
|
34
|
+
const rules = scanRules(dir);
|
|
35
|
+
const rule = rules.find((r) => r.id === "WK8505");
|
|
36
|
+
|
|
37
|
+
expect(rule).toBeDefined();
|
|
38
|
+
expect(rule?.description).toBe(
|
|
39
|
+
'A rule\'s "why" must be non-empty, and a backslash like C:\\path must survive intact',
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -92,7 +92,7 @@ function scanDir(dir: string, type: "lint" | "post-synth", out: ScannedRule[]):
|
|
|
92
92
|
} else {
|
|
93
93
|
// Extract from PostSynthCheck objects: id, description
|
|
94
94
|
const idMatch = content.match(/id:\s*"([^"]+)"/);
|
|
95
|
-
const descMatch = content.match(/description:\s*"([^"]
|
|
95
|
+
const descMatch = content.match(/description:\s*"((?:[^"\\]|\\.)*)"/);
|
|
96
96
|
|
|
97
97
|
if (idMatch) {
|
|
98
98
|
out.push({
|
|
@@ -100,7 +100,7 @@ function scanDir(dir: string, type: "lint" | "post-synth", out: ScannedRule[]):
|
|
|
100
100
|
id: idMatch[1],
|
|
101
101
|
severity: "error",
|
|
102
102
|
category: "post-synth",
|
|
103
|
-
description: descMatch
|
|
103
|
+
description: descMatch ? unescapeStringLiteral(descMatch[1]) : idMatch[1],
|
|
104
104
|
type: "post-synth",
|
|
105
105
|
},
|
|
106
106
|
source: content,
|
|
@@ -110,6 +110,29 @@ function scanDir(dir: string, type: "lint" | "post-synth", out: ScannedRule[]):
|
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Undo the backslash escaping of a double-quoted JS/TS string literal body
|
|
115
|
+
* captured by a regex like `/description:\s*"((?:[^"\\]|\\.)*)"/` — the
|
|
116
|
+
* capture group still has the source's escape sequences (`\"`, `\\`, `\n`,
|
|
117
|
+
* ...) in it verbatim, so a description written as `"say \"hi\""` would
|
|
118
|
+
* otherwise render in generated docs as `say \"hi\"` instead of `say "hi"`.
|
|
119
|
+
*/
|
|
120
|
+
function unescapeStringLiteral(raw: string): string {
|
|
121
|
+
return raw.replace(/\\(.)/g, (_, ch: string) => {
|
|
122
|
+
switch (ch) {
|
|
123
|
+
case "n":
|
|
124
|
+
return "\n";
|
|
125
|
+
case "t":
|
|
126
|
+
return "\t";
|
|
127
|
+
case "r":
|
|
128
|
+
return "\r";
|
|
129
|
+
default:
|
|
130
|
+
// \" -> ", \\ -> \, \' -> ', and anything else -> itself unescaped
|
|
131
|
+
return ch;
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
113
136
|
function extractDescriptionFromComment(
|
|
114
137
|
content: string,
|
|
115
138
|
ruleId: string,
|
package/src/components/README.md
CHANGED
|
@@ -33,6 +33,13 @@ adopting chant.
|
|
|
33
33
|
work for a component whose own name isn't the entity/resource name it
|
|
34
34
|
deploys, falling back to `name` itself when omitted. See
|
|
35
35
|
docs/src/content/docs/components/observability.mdx.
|
|
36
|
+
- **Composite mapping** — `composites` (#1492), optional: the composite kind
|
|
37
|
+
name(s) this component's deployed resources come from, when a consumer
|
|
38
|
+
joining a composite graph to the component DAG (e.g. `chant graph
|
|
39
|
+
--components --format ir`'s node `attrs.composites`, ../cli/handlers/
|
|
40
|
+
graph.ts) can't infer it by kebab-casing `name` against each composite's
|
|
41
|
+
kind. Omitted entirely (the default), that naming convention stays in
|
|
42
|
+
effect with no behavior change.
|
|
36
43
|
- **The three archetypes** — service (build → publish → apply → verify),
|
|
37
44
|
infra (apply → verify, no build), and producer/library (build → publish
|
|
38
45
|
only, no service apply). `deploy` being a plain composition is what makes
|
|
@@ -117,6 +117,10 @@ export interface AutoReleaseRunInfo {
|
|
|
117
117
|
digest?: string;
|
|
118
118
|
/** Orchestrator run identifier — a Temporal `runId`, or a locally generated id for the local executor (mirrors `runComponentsReleaseRecord`'s `--run-id` default). */
|
|
119
119
|
runId: string;
|
|
120
|
+
/** The bypassed capability-profile divergences, when the caller deliberately overrode a deploy-time profile assertion (chant #1244) — recorded verbatim as the release record's `profileOverride`. */
|
|
121
|
+
profileOverride?: string;
|
|
122
|
+
/** The deploy's input-side digest, when `digest` is a rendered-content identity (a pinned helm deploy, chant #1242) — recorded as the release record's `inputDigest` so ledger queries can still join on inputs across clusters whose bytes legitimately differ. */
|
|
123
|
+
inputDigest?: string;
|
|
120
124
|
}
|
|
121
125
|
|
|
122
126
|
/** Opt-out + field-override knobs for auto-release recording, threaded from CLI flags/config (#597: "opt-out-able (flag/config), documented default"). */
|
|
@@ -202,6 +206,8 @@ export async function maybeRecordAutoRelease(
|
|
|
202
206
|
runId: run.runId,
|
|
203
207
|
timestamp,
|
|
204
208
|
actor,
|
|
209
|
+
...(run.profileOverride ? { profileOverride: run.profileOverride } : {}),
|
|
210
|
+
...(run.inputDigest ? { inputDigest: run.inputDigest } : {}),
|
|
205
211
|
},
|
|
206
212
|
{ cwd: options.cwd },
|
|
207
213
|
);
|
|
@@ -44,8 +44,23 @@ export interface Capability<In = unknown, Out = unknown> {
|
|
|
44
44
|
readonly kind: string;
|
|
45
45
|
/** Perform the operation. */
|
|
46
46
|
run(ctx: DeployContext, input: In): Promise<Out>;
|
|
47
|
-
/**
|
|
48
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Optional paired compensation, invoked in reverse order on saga rollback.
|
|
49
|
+
* `output`, when supplied, is the exact value this step's own `run()` call
|
|
50
|
+
* returned (#1944, epic #1564 phase 4) — a serializable identity channel a
|
|
51
|
+
* capability can use to recover state `rollback` needs when it cannot rely
|
|
52
|
+
* on in-process object identity between its `run`/`rollback` calls. The
|
|
53
|
+
* local interpret driver (../driver.ts) always threads it through; the
|
|
54
|
+
* durable Temporal path (lexicons/temporal/src/component-op/*.ts) threads
|
|
55
|
+
* it across the Activity boundary as plain JSON, which is exactly the case
|
|
56
|
+
* this exists for — see ./verbs/run-agent.ts's "Rollback identity" doc
|
|
57
|
+
* comment for the motivating gap (a fresh sprite's checkpoint id, recorded
|
|
58
|
+
* only in an in-process `WeakMap` keyed by `run()`'s `input` object, never
|
|
59
|
+
* survives to a `rollback()` call that runs as a separate Activity with its
|
|
60
|
+
* own freshly-resolved `input`). Optional and additive: a capability that
|
|
61
|
+
* never needs it (most of them) simply ignores the third parameter.
|
|
62
|
+
*/
|
|
63
|
+
rollback?(ctx: DeployContext, input: In, output?: Out): Promise<void>;
|
|
49
64
|
/**
|
|
50
65
|
* How this verb relates to rollback, for the COMP003 composition check.
|
|
51
66
|
* Usually derivable and left unset: a capability with a `rollback` method is
|
|
@@ -371,6 +371,23 @@ export const backup =
|
|
|
371
371
|
expect(result.liveNames?.postgres).toEqual(["pgDeployment", "pgService", "pgClaim"]);
|
|
372
372
|
expect(result.liveNames?.plain).toEqual(["plain"]);
|
|
373
373
|
});
|
|
374
|
+
|
|
375
|
+
test("carries each component's declared composites, absent (not defaulted) when undeclared (#1492)", async () => {
|
|
376
|
+
await writeFile(
|
|
377
|
+
join(testDir, "aws-plane.component.ts"),
|
|
378
|
+
`export const awsPlane = { name: "aws-plane", composites: ["ArtifactBucket", "OperatorRole"], dependsOn: [], deploy: [{ phase: "Apply", steps: [{ kind: "shell", reason: "test" }] }] };`,
|
|
379
|
+
);
|
|
380
|
+
await writeFile(
|
|
381
|
+
join(testDir, "plain.component.ts"),
|
|
382
|
+
`export const plain = { name: "plain", dependsOn: [], deploy: [{ phase: "Apply", steps: [{ kind: "shell", reason: "test" }] }] };`,
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
const result = await computeComponentGraph(testDir);
|
|
386
|
+
|
|
387
|
+
expect(result.success).toBe(true);
|
|
388
|
+
expect(result.composites?.["aws-plane"]).toEqual(["ArtifactBucket", "OperatorRole"]);
|
|
389
|
+
expect(result.composites?.plain).toBeUndefined();
|
|
390
|
+
});
|
|
374
391
|
});
|
|
375
392
|
|
|
376
393
|
// ── runComponents (#585) ─────────────────────────────────────────────────────
|
|
@@ -141,6 +141,13 @@ export interface ComponentGraphResult {
|
|
|
141
141
|
* `Component` contract already specifies. This is what lets a consumer join
|
|
142
142
|
* the component DAG to the resource graph without guessing at kinds. */
|
|
143
143
|
liveNames?: Record<string, string[]>;
|
|
144
|
+
/** Component name → the composite kind name(s) its resources come from (#1492),
|
|
145
|
+
* present only for components that declared `composites` explicitly. Unlike
|
|
146
|
+
* `liveNames`, there is no identity fallback — a component's own name is not
|
|
147
|
+
* a composite kind — so an undeclared component is left out of this map
|
|
148
|
+
* entirely rather than defaulting to a guess; a consumer with no entry here
|
|
149
|
+
* keeps applying its own naming-convention default. */
|
|
150
|
+
composites?: Record<string, string[]>;
|
|
144
151
|
error?: string;
|
|
145
152
|
}
|
|
146
153
|
|
|
@@ -173,10 +180,15 @@ export async function computeComponentGraph(
|
|
|
173
180
|
// component name → owned live resource names (#1491), declared or the
|
|
174
181
|
// contract's identity fallback.
|
|
175
182
|
const liveNames: Record<string, string[]> = {};
|
|
183
|
+
// component name → declared composite kind(s) (#1492), only for components
|
|
184
|
+
// that declared them — no identity fallback (see ComponentGraphResult doc).
|
|
185
|
+
const composites: Record<string, string[]> = {};
|
|
176
186
|
for (const [name, discovered] of result.components) {
|
|
177
187
|
files[name] = relative(path, discovered.filePath);
|
|
178
188
|
const declared = discovered.component.liveNames;
|
|
179
189
|
liveNames[name] = declared && declared.length > 0 ? [...declared] : [name];
|
|
190
|
+
const declaredComposites = discovered.component.composites;
|
|
191
|
+
if (declaredComposites && declaredComposites.length > 0) composites[name] = [...declaredComposites];
|
|
180
192
|
}
|
|
181
193
|
|
|
182
194
|
try {
|
|
@@ -185,7 +197,7 @@ export async function computeComponentGraph(
|
|
|
185
197
|
for (const c of driverComponents) {
|
|
186
198
|
for (const dep of c.dependsOn ?? []) edges.push({ from: c.name, to: dep });
|
|
187
199
|
}
|
|
188
|
-
return { success: true, order, waves, edges, files, liveNames };
|
|
200
|
+
return { success: true, order, waves, edges, files, liveNames, composites };
|
|
189
201
|
} catch (err) {
|
|
190
202
|
if (err instanceof UnknownDependencyError || err instanceof DependencyCycleError) {
|
|
191
203
|
return { success: false, order: [], waves: [], edges: [], error: err.message };
|