@shrkcrft/cli 0.1.0-alpha.29 → 0.1.0-alpha.31
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/asset-preview/apply-asset-preview.js +1 -1
- package/dist/authoring/authoring-kit.d.ts +9 -1
- package/dist/authoring/authoring-kit.d.ts.map +1 -1
- package/dist/authoring/authoring-kit.js +26 -13
- package/dist/bootstrap/unlinked-workspace-dependency.d.ts +11 -0
- package/dist/bootstrap/unlinked-workspace-dependency.d.ts.map +1 -0
- package/dist/bootstrap/unlinked-workspace-dependency.js +99 -0
- package/dist/command-registry.d.ts +100 -1
- package/dist/command-registry.d.ts.map +1 -1
- package/dist/command-registry.js +130 -3
- package/dist/commands/api-diff.command.d.ts.map +1 -1
- package/dist/commands/api-diff.command.js +12 -0
- package/dist/commands/apply.command.d.ts.map +1 -1
- package/dist/commands/apply.command.js +3 -0
- package/dist/commands/arch.command.d.ts.map +1 -1
- package/dist/commands/arch.command.js +26 -7
- package/dist/commands/architecture.command.d.ts.map +1 -1
- package/dist/commands/architecture.command.js +8 -2
- package/dist/commands/baseline.command.d.ts +76 -0
- package/dist/commands/baseline.command.d.ts.map +1 -1
- package/dist/commands/baseline.command.js +599 -75
- package/dist/commands/biome.command.d.ts.map +1 -1
- package/dist/commands/biome.command.js +20 -0
- package/dist/commands/boundaries.command.d.ts +9 -1
- package/dist/commands/boundaries.command.d.ts.map +1 -1
- package/dist/commands/boundaries.command.js +88 -20
- package/dist/commands/bundle.command.d.ts.map +1 -1
- package/dist/commands/bundle.command.js +57 -11
- package/dist/commands/cache-align.command.d.ts.map +1 -1
- package/dist/commands/cache-align.command.js +3 -1
- package/dist/commands/changelog-data.d.ts.map +1 -1
- package/dist/commands/changelog-data.js +164 -0
- package/dist/commands/changes.command.d.ts.map +1 -1
- package/dist/commands/changes.command.js +15 -0
- package/dist/commands/check.command.d.ts.map +1 -1
- package/dist/commands/check.command.js +1260 -324
- package/dist/commands/checks.command.d.ts.map +1 -1
- package/dist/commands/checks.command.js +188 -40
- package/dist/commands/ci.command.d.ts.map +1 -1
- package/dist/commands/ci.command.js +35 -2
- package/dist/commands/code-intel.command.d.ts.map +1 -1
- package/dist/commands/code-intel.command.js +8 -1
- package/dist/commands/command-catalog.d.ts +23 -1
- package/dist/commands/command-catalog.d.ts.map +1 -1
- package/dist/commands/command-catalog.js +152 -35
- package/dist/commands/commands.command.d.ts +21 -3
- package/dist/commands/commands.command.d.ts.map +1 -1
- package/dist/commands/commands.command.js +172 -27
- package/dist/commands/compress.command.d.ts.map +1 -1
- package/dist/commands/compress.command.js +4 -0
- package/dist/commands/constructs.command.d.ts.map +1 -1
- package/dist/commands/constructs.command.js +50 -12
- package/dist/commands/context.command.d.ts.map +1 -1
- package/dist/commands/context.command.js +40 -5
- package/dist/commands/contract-templates.command.d.ts.map +1 -1
- package/dist/commands/contract-templates.command.js +9 -1
- package/dist/commands/contract.command.d.ts.map +1 -1
- package/dist/commands/contract.command.js +35 -0
- package/dist/commands/conventions.command.d.ts.map +1 -1
- package/dist/commands/conventions.command.js +189 -29
- package/dist/commands/coverage.command.d.ts.map +1 -1
- package/dist/commands/coverage.command.js +9 -0
- package/dist/commands/daily.commands.d.ts +0 -1
- package/dist/commands/daily.commands.d.ts.map +1 -1
- package/dist/commands/daily.commands.js +12 -76
- package/dist/commands/dashboard.command.d.ts.map +1 -1
- package/dist/commands/dashboard.command.js +10 -0
- package/dist/commands/dev.command.d.ts.map +1 -1
- package/dist/commands/dev.command.js +45 -2
- package/dist/commands/diagnostics.command.d.ts +0 -2
- package/dist/commands/diagnostics.command.d.ts.map +1 -1
- package/dist/commands/diagnostics.command.js +5 -78
- package/dist/commands/diff-check.command.d.ts +6 -4
- package/dist/commands/diff-check.command.d.ts.map +1 -1
- package/dist/commands/diff-check.command.js +190 -103
- package/dist/commands/docs-references.command.d.ts +20 -0
- package/dist/commands/docs-references.command.d.ts.map +1 -0
- package/dist/commands/docs-references.command.js +520 -0
- package/dist/commands/doctor.command.d.ts.map +1 -1
- package/dist/commands/doctor.command.js +60 -5
- package/dist/commands/drift.command.d.ts.map +1 -1
- package/dist/commands/drift.command.js +17 -4
- package/dist/commands/eslint.command.d.ts.map +1 -1
- package/dist/commands/eslint.command.js +20 -0
- package/dist/commands/export.command.d.ts.map +1 -1
- package/dist/commands/export.command.js +23 -1
- package/dist/commands/feedback-dispatch.command.d.ts.map +1 -1
- package/dist/commands/feedback-dispatch.command.js +29 -0
- package/dist/commands/feedback.command.d.ts.map +1 -1
- package/dist/commands/feedback.command.js +6 -1
- package/dist/commands/finish.command.d.ts.map +1 -1
- package/dist/commands/finish.command.js +32 -8
- package/dist/commands/fix.command.d.ts.map +1 -1
- package/dist/commands/fix.command.js +19 -0
- package/dist/commands/gate.command.d.ts +6 -1
- package/dist/commands/gate.command.d.ts.map +1 -1
- package/dist/commands/gate.command.js +50 -124
- package/dist/commands/gates.command.d.ts +54 -0
- package/dist/commands/gates.command.d.ts.map +1 -1
- package/dist/commands/gates.command.js +1314 -64
- package/dist/commands/gen.command.d.ts.map +1 -1
- package/dist/commands/gen.command.js +4 -0
- package/dist/commands/generated.command.d.ts +62 -0
- package/dist/commands/generated.command.d.ts.map +1 -1
- package/dist/commands/generated.command.js +639 -156
- package/dist/commands/graph-code-subverbs.d.ts +15 -0
- package/dist/commands/graph-code-subverbs.d.ts.map +1 -1
- package/dist/commands/graph-code-subverbs.js +203 -25
- package/dist/commands/graph.command.d.ts.map +1 -1
- package/dist/commands/graph.command.js +79 -37
- package/dist/commands/help.command.d.ts +30 -6
- package/dist/commands/help.command.d.ts.map +1 -1
- package/dist/commands/help.command.js +323 -199
- package/dist/commands/helper.command.d.ts +1 -0
- package/dist/commands/helper.command.d.ts.map +1 -1
- package/dist/commands/helper.command.js +195 -56
- package/dist/commands/impact.command.d.ts.map +1 -1
- package/dist/commands/impact.command.js +115 -12
- package/dist/commands/infer.command.d.ts.map +1 -1
- package/dist/commands/infer.command.js +20 -45
- package/dist/commands/ingest.command.d.ts +0 -1
- package/dist/commands/ingest.command.d.ts.map +1 -1
- package/dist/commands/ingest.command.js +25 -41
- package/dist/commands/knowledge-author.command.d.ts +0 -3
- package/dist/commands/knowledge-author.command.d.ts.map +1 -1
- package/dist/commands/knowledge-author.command.js +10 -23
- package/dist/commands/knowledge.command.d.ts.map +1 -1
- package/dist/commands/knowledge.command.js +469 -129
- package/dist/commands/languages.command.d.ts.map +1 -1
- package/dist/commands/languages.command.js +27 -0
- package/dist/commands/lint.command.d.ts.map +1 -1
- package/dist/commands/lint.command.js +3 -0
- package/dist/commands/mcp.command.d.ts +18 -0
- package/dist/commands/mcp.command.d.ts.map +1 -1
- package/dist/commands/mcp.command.js +33 -8
- package/dist/commands/onboard.command.d.ts.map +1 -1
- package/dist/commands/onboard.command.js +20 -0
- package/dist/commands/owners.command.d.ts.map +1 -1
- package/dist/commands/owners.command.js +14 -2
- package/dist/commands/packs-new.d.ts +13 -1
- package/dist/commands/packs-new.d.ts.map +1 -1
- package/dist/commands/packs-new.js +500 -335
- package/dist/commands/packs.command.d.ts.map +1 -1
- package/dist/commands/packs.command.js +378 -36
- package/dist/commands/paths.command.d.ts.map +1 -1
- package/dist/commands/paths.command.js +9 -1
- package/dist/commands/pipelines.command.d.ts.map +1 -1
- package/dist/commands/pipelines.command.js +7 -1
- package/dist/commands/plan-check.command.d.ts.map +1 -1
- package/dist/commands/plan-check.command.js +3 -0
- package/dist/commands/plan.command.d.ts.map +1 -1
- package/dist/commands/plan.command.js +3 -0
- package/dist/commands/playbooks.command.d.ts.map +1 -1
- package/dist/commands/playbooks.command.js +7 -2
- package/dist/commands/policy-lint.command.d.ts +19 -2
- package/dist/commands/policy-lint.command.d.ts.map +1 -1
- package/dist/commands/policy-lint.command.js +224 -65
- package/dist/commands/policy.command.d.ts.map +1 -1
- package/dist/commands/policy.command.js +6 -1
- package/dist/commands/pr.command.d.ts.map +1 -1
- package/dist/commands/pr.command.js +14 -0
- package/dist/commands/presets.command.d.ts.map +1 -1
- package/dist/commands/presets.command.js +12 -27
- package/dist/commands/profiles.command.d.ts.map +1 -1
- package/dist/commands/profiles.command.js +94 -26
- package/dist/commands/provenance.command.js +1 -1
- package/dist/commands/quality.command.d.ts.map +1 -1
- package/dist/commands/quality.command.js +129 -33
- package/dist/commands/recommend.command.d.ts +20 -4
- package/dist/commands/recommend.command.d.ts.map +1 -1
- package/dist/commands/recommend.command.js +172 -278
- package/dist/commands/registrations.command.d.ts.map +1 -1
- package/dist/commands/registrations.command.js +138 -13
- package/dist/commands/registry-lifecycle-run.d.ts +14 -0
- package/dist/commands/registry-lifecycle-run.d.ts.map +1 -0
- package/dist/commands/registry-lifecycle-run.js +336 -0
- package/dist/commands/registry.command.d.ts.map +1 -1
- package/dist/commands/registry.command.js +235 -62
- package/dist/commands/release.command.d.ts.map +1 -1
- package/dist/commands/release.command.js +99 -20
- package/dist/commands/report.command.d.ts.map +1 -1
- package/dist/commands/report.command.js +38 -2
- package/dist/commands/reuse-coverage.command.d.ts +23 -0
- package/dist/commands/reuse-coverage.command.d.ts.map +1 -0
- package/dist/commands/reuse-coverage.command.js +536 -0
- package/dist/commands/reuse.command.d.ts +3 -18
- package/dist/commands/reuse.command.d.ts.map +1 -1
- package/dist/commands/reuse.command.js +395 -244
- package/dist/commands/review.command.d.ts.map +1 -1
- package/dist/commands/review.command.js +33 -1
- package/dist/commands/rounds.command.d.ts.map +1 -1
- package/dist/commands/rounds.command.js +5 -0
- package/dist/commands/safety.command.d.ts.map +1 -1
- package/dist/commands/safety.command.js +9 -0
- package/dist/commands/scaffolds.command.d.ts.map +1 -1
- package/dist/commands/scaffolds.command.js +97 -25
- package/dist/commands/search.command.d.ts +0 -8
- package/dist/commands/search.command.d.ts.map +1 -1
- package/dist/commands/search.command.js +136 -28
- package/dist/commands/self-config-xrefs.command.d.ts +24 -0
- package/dist/commands/self-config-xrefs.command.d.ts.map +1 -0
- package/dist/commands/self-config-xrefs.command.js +170 -0
- package/dist/commands/self-config.command.d.ts.map +1 -1
- package/dist/commands/self-config.command.js +261 -65
- package/dist/commands/smart-context.command.d.ts.map +1 -1
- package/dist/commands/smart-context.command.js +39 -3
- package/dist/commands/spec.command.d.ts.map +1 -1
- package/dist/commands/spec.command.js +3 -0
- package/dist/commands/stats.command.d.ts.map +1 -1
- package/dist/commands/stats.command.js +3 -0
- package/dist/commands/surface.command.d.ts +6 -1
- package/dist/commands/surface.command.d.ts.map +1 -1
- package/dist/commands/surface.command.js +256 -54
- package/dist/commands/task-context.command.js +1 -1
- package/dist/commands/task.command.d.ts.map +1 -1
- package/dist/commands/task.command.js +22 -1
- package/dist/commands/templates.command.d.ts +4 -0
- package/dist/commands/templates.command.d.ts.map +1 -1
- package/dist/commands/templates.command.js +109 -22
- package/dist/commands/test.command.d.ts.map +1 -1
- package/dist/commands/test.command.js +161 -33
- package/dist/commands/tests.command.d.ts.map +1 -1
- package/dist/commands/tests.command.js +29 -3
- package/dist/commands/trace.command.d.ts.map +1 -1
- package/dist/commands/trace.command.js +29 -4
- package/dist/commands/watch.command.d.ts.map +1 -1
- package/dist/commands/watch.command.js +3 -0
- package/dist/commands/why.command.d.ts.map +1 -1
- package/dist/commands/why.command.js +3 -0
- package/dist/commands/wiring.command.d.ts +18 -4
- package/dist/commands/wiring.command.d.ts.map +1 -1
- package/dist/commands/wiring.command.js +362 -74
- package/dist/dashboard/code-intelligence-data.js +4 -4
- package/dist/diff/deleted-orphan-coverage.d.ts +32 -0
- package/dist/diff/deleted-orphan-coverage.d.ts.map +1 -0
- package/dist/diff/deleted-orphan-coverage.js +107 -0
- package/dist/diff/deleted-orphan-scope-notes.d.ts +23 -0
- package/dist/diff/deleted-orphan-scope-notes.d.ts.map +1 -0
- package/dist/diff/deleted-orphan-scope-notes.js +49 -0
- package/dist/diff/deleted-orphans.d.ts +32 -0
- package/dist/diff/deleted-orphans.d.ts.map +1 -1
- package/dist/diff/deleted-orphans.js +42 -2
- package/dist/dispatch/closest-match.d.ts +15 -0
- package/dist/dispatch/closest-match.d.ts.map +1 -0
- package/dist/dispatch/closest-match.js +47 -0
- package/dist/dispatch/global-flags.d.ts +94 -0
- package/dist/dispatch/global-flags.d.ts.map +1 -0
- package/dist/dispatch/global-flags.js +123 -0
- package/dist/dispatch/guard-invocation-input.d.ts +24 -0
- package/dist/dispatch/guard-invocation-input.d.ts.map +1 -0
- package/dist/dispatch/guard-invocation-input.js +1 -0
- package/dist/dispatch/guard-invocation.d.ts +33 -0
- package/dist/dispatch/guard-invocation.d.ts.map +1 -0
- package/dist/dispatch/guard-invocation.js +183 -0
- package/dist/dispatch/help-intercept.d.ts +22 -0
- package/dist/dispatch/help-intercept.d.ts.map +1 -0
- package/dist/dispatch/help-intercept.js +42 -0
- package/dist/dispatch/i-declared-invocation.d.ts +17 -0
- package/dist/dispatch/i-declared-invocation.d.ts.map +1 -0
- package/dist/dispatch/i-declared-invocation.js +1 -0
- package/dist/dispatch/i-unknown-flag-refusal-input.d.ts +28 -0
- package/dist/dispatch/i-unknown-flag-refusal-input.d.ts.map +1 -0
- package/dist/dispatch/i-unknown-flag-refusal-input.js +1 -0
- package/dist/dispatch/invocation-rejection-kind.d.ts +15 -0
- package/dist/dispatch/invocation-rejection-kind.d.ts.map +1 -0
- package/dist/dispatch/invocation-rejection-kind.js +15 -0
- package/dist/dispatch/invocation-rejection.d.ts +18 -0
- package/dist/dispatch/invocation-rejection.d.ts.map +1 -0
- package/dist/dispatch/invocation-rejection.js +1 -0
- package/dist/dispatch/judge-invocation.d.ts +24 -0
- package/dist/dispatch/judge-invocation.d.ts.map +1 -0
- package/dist/dispatch/judge-invocation.js +50 -0
- package/dist/dispatch/positional-mode.d.ts +18 -0
- package/dist/dispatch/positional-mode.d.ts.map +1 -0
- package/dist/dispatch/positional-mode.js +18 -0
- package/dist/dispatch/read-tracking-map.d.ts +28 -0
- package/dist/dispatch/read-tracking-map.d.ts.map +1 -0
- package/dist/dispatch/read-tracking-map.js +58 -0
- package/dist/dispatch/refuse-sibling-valve-flags.d.ts +30 -0
- package/dist/dispatch/refuse-sibling-valve-flags.d.ts.map +1 -0
- package/dist/dispatch/refuse-sibling-valve-flags.js +65 -0
- package/dist/dispatch/subverb-spec.d.ts +26 -0
- package/dist/dispatch/subverb-spec.d.ts.map +1 -0
- package/dist/dispatch/subverb-spec.js +1 -0
- package/dist/dispatch/undocumented-flag-reads.d.ts +22 -0
- package/dist/dispatch/undocumented-flag-reads.d.ts.map +1 -0
- package/dist/dispatch/undocumented-flag-reads.js +283 -0
- package/dist/dispatch/unknown-flag-refusal-mode.d.ts +13 -0
- package/dist/dispatch/unknown-flag-refusal-mode.d.ts.map +1 -0
- package/dist/dispatch/unknown-flag-refusal-mode.js +13 -0
- package/dist/dispatch/unknown-flag-refusal.d.ts +22 -0
- package/dist/dispatch/unknown-flag-refusal.d.ts.map +1 -0
- package/dist/dispatch/unknown-flag-refusal.js +57 -0
- package/dist/dispatch/unread-flags.d.ts +152 -0
- package/dist/dispatch/unread-flags.d.ts.map +1 -0
- package/dist/dispatch/unread-flags.js +295 -0
- package/dist/dispatch/verdict-valve-flag.d.ts +21 -0
- package/dist/dispatch/verdict-valve-flag.d.ts.map +1 -0
- package/dist/dispatch/verdict-valve-flag.js +21 -0
- package/dist/dispatch/walk-declared-subverbs.d.ts +14 -0
- package/dist/dispatch/walk-declared-subverbs.d.ts.map +1 -0
- package/dist/dispatch/walk-declared-subverbs.js +28 -0
- package/dist/exit-codes.d.ts +41 -3
- package/dist/exit-codes.d.ts.map +1 -1
- package/dist/exit-codes.js +153 -14
- package/dist/finish/run-finish.d.ts +72 -5
- package/dist/finish/run-finish.d.ts.map +1 -1
- package/dist/finish/run-finish.js +453 -104
- package/dist/gates/accepted-empty-note.d.ts +10 -0
- package/dist/gates/accepted-empty-note.d.ts.map +1 -0
- package/dist/gates/accepted-empty-note.js +11 -0
- package/dist/gates/allow-empty.d.ts +25 -0
- package/dist/gates/allow-empty.d.ts.map +1 -0
- package/dist/gates/allow-empty.js +27 -0
- package/dist/gates/asset-doctor-failing-units.d.ts +16 -0
- package/dist/gates/asset-doctor-failing-units.d.ts.map +1 -0
- package/dist/gates/asset-doctor-failing-units.js +14 -0
- package/dist/gates/asset-doctor-failure-line.d.ts +16 -0
- package/dist/gates/asset-doctor-failure-line.d.ts.map +1 -0
- package/dist/gates/asset-doctor-failure-line.js +28 -0
- package/dist/gates/empty-rule-advice-lines.d.ts +15 -0
- package/dist/gates/empty-rule-advice-lines.d.ts.map +1 -0
- package/dist/gates/empty-rule-advice-lines.js +20 -0
- package/dist/gates/gate-envelope.d.ts +85 -8
- package/dist/gates/gate-envelope.d.ts.map +1 -1
- package/dist/gates/gate-envelope.js +56 -7
- package/dist/gates/gate-rule-globs.d.ts +67 -0
- package/dist/gates/gate-rule-globs.d.ts.map +1 -0
- package/dist/gates/gate-rule-globs.js +139 -0
- package/dist/gates/gate-rule-view.d.ts +25 -6
- package/dist/gates/gate-rule-view.d.ts.map +1 -1
- package/dist/gates/gate-rule-view.js +105 -77
- package/dist/gates/i-coverage-dead-glob.d.ts +19 -0
- package/dist/gates/i-coverage-dead-glob.d.ts.map +1 -0
- package/dist/gates/i-coverage-dead-glob.js +1 -0
- package/dist/gates/i-coverage-negation.d.ts +13 -0
- package/dist/gates/i-coverage-negation.d.ts.map +1 -0
- package/dist/gates/i-coverage-negation.js +1 -0
- package/dist/gates/i-registration-query-settle-input.d.ts +18 -0
- package/dist/gates/i-registration-query-settle-input.d.ts.map +1 -0
- package/dist/gates/i-registration-query-settle-input.js +1 -0
- package/dist/gates/i-unit-state-note-row.d.ts +16 -0
- package/dist/gates/i-unit-state-note-row.d.ts.map +1 -0
- package/dist/gates/i-unit-state-note-row.js +1 -0
- package/dist/gates/i-unit-state-notes.d.ts +32 -0
- package/dist/gates/i-unit-state-notes.d.ts.map +1 -0
- package/dist/gates/i-unit-state-notes.js +1 -0
- package/dist/gates/incomplete-registry-inventory.d.ts +19 -0
- package/dist/gates/incomplete-registry-inventory.d.ts.map +1 -0
- package/dist/gates/incomplete-registry-inventory.js +63 -0
- package/dist/gates/measure-registration-roles.d.ts +10 -0
- package/dist/gates/measure-registration-roles.d.ts.map +1 -0
- package/dist/gates/measure-registration-roles.js +9 -0
- package/dist/gates/plane-verdict.d.ts +14 -0
- package/dist/gates/plane-verdict.d.ts.map +1 -0
- package/dist/gates/plane-verdict.js +22 -0
- package/dist/gates/qualify-clean-for-units.d.ts +12 -0
- package/dist/gates/qualify-clean-for-units.d.ts.map +1 -0
- package/dist/gates/qualify-clean-for-units.js +18 -0
- package/dist/gates/registration-graph-verdict.d.ts +34 -0
- package/dist/gates/registration-graph-verdict.d.ts.map +1 -0
- package/dist/gates/registration-graph-verdict.js +70 -0
- package/dist/gates/registration-roles.d.ts +9 -0
- package/dist/gates/registration-roles.d.ts.map +1 -0
- package/dist/gates/registration-roles.js +1 -0
- package/dist/gates/registry-liveness.d.ts +11 -0
- package/dist/gates/registry-liveness.d.ts.map +1 -0
- package/dist/gates/registry-liveness.js +13 -0
- package/dist/gates/rule-coverage.d.ts +225 -4
- package/dist/gates/rule-coverage.d.ts.map +1 -1
- package/dist/gates/rule-coverage.js +610 -43
- package/dist/gates/run-gate-planes.d.ts +58 -0
- package/dist/gates/run-gate-planes.d.ts.map +1 -0
- package/dist/gates/run-gate-planes.js +403 -0
- package/dist/gates/scaffold-selftest.d.ts +56 -0
- package/dist/gates/scaffold-selftest.d.ts.map +1 -0
- package/dist/gates/scaffold-selftest.js +119 -0
- package/dist/gates/seam-rejected-rules.d.ts +28 -0
- package/dist/gates/seam-rejected-rules.d.ts.map +1 -0
- package/dist/gates/seam-rejected-rules.js +95 -0
- package/dist/gates/self-test-check.d.ts +29 -0
- package/dist/gates/self-test-check.d.ts.map +1 -0
- package/dist/gates/self-test-check.js +1 -0
- package/dist/gates/self-test-subject.d.ts +28 -0
- package/dist/gates/self-test-subject.d.ts.map +1 -0
- package/dist/gates/self-test-subject.js +1 -0
- package/dist/gates/self-test.d.ts +20 -0
- package/dist/gates/self-test.d.ts.map +1 -0
- package/dist/gates/self-test.js +82 -0
- package/dist/gates/settle-verdict.d.ts +14 -0
- package/dist/gates/settle-verdict.d.ts.map +1 -0
- package/dist/gates/settle-verdict.js +13 -0
- package/dist/gates/settled-verdict.d.ts +8 -0
- package/dist/gates/settled-verdict.d.ts.map +1 -0
- package/dist/gates/settled-verdict.js +1 -0
- package/dist/gates/template-registry-coverage.d.ts +18 -0
- package/dist/gates/template-registry-coverage.d.ts.map +1 -0
- package/dist/gates/template-registry-coverage.js +24 -0
- package/dist/gates/unit-state-notes.d.ts +40 -0
- package/dist/gates/unit-state-notes.d.ts.map +1 -0
- package/dist/gates/unit-state-notes.js +71 -0
- package/dist/gates/verdict-line.d.ts +18 -0
- package/dist/gates/verdict-line.d.ts.map +1 -0
- package/dist/gates/verdict-line.js +40 -0
- package/dist/graph/graph-reuse-lookup.d.ts +10 -0
- package/dist/graph/graph-reuse-lookup.d.ts.map +1 -0
- package/dist/graph/graph-reuse-lookup.js +41 -0
- package/dist/graph/index-behind-hint.d.ts +11 -0
- package/dist/graph/index-behind-hint.d.ts.map +1 -0
- package/dist/graph/index-behind-hint.js +27 -0
- package/dist/knowledge/knowledge-stale-gate-flags.d.ts +3 -0
- package/dist/knowledge/knowledge-stale-gate-flags.d.ts.map +1 -0
- package/dist/knowledge/knowledge-stale-gate-flags.js +1 -0
- package/dist/knowledge/knowledge-stale-gate-input.d.ts +3 -0
- package/dist/knowledge/knowledge-stale-gate-input.d.ts.map +1 -0
- package/dist/knowledge/knowledge-stale-gate-input.js +1 -0
- package/dist/knowledge/knowledge-stale-gate-result.d.ts +3 -0
- package/dist/knowledge/knowledge-stale-gate-result.d.ts.map +1 -0
- package/dist/knowledge/knowledge-stale-gate-result.js +1 -0
- package/dist/knowledge/knowledge-stale-gate.d.ts +10 -0
- package/dist/knowledge/knowledge-stale-gate.d.ts.map +1 -0
- package/dist/knowledge/knowledge-stale-gate.js +9 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +204 -83
- package/dist/output/failure-hints.d.ts.map +1 -1
- package/dist/output/failure-hints.js +11 -9
- package/dist/output/rejected-entries-note.d.ts +57 -0
- package/dist/output/rejected-entries-note.d.ts.map +1 -0
- package/dist/output/rejected-entries-note.js +103 -0
- package/dist/output/stdout-is-pipe.d.ts +13 -0
- package/dist/output/stdout-is-pipe.d.ts.map +1 -0
- package/dist/output/stdout-is-pipe.js +21 -0
- package/dist/packs/stale-build-warning.d.ts +11 -0
- package/dist/packs/stale-build-warning.d.ts.map +1 -0
- package/dist/packs/stale-build-warning.js +23 -0
- package/dist/quality/run-quality.d.ts +123 -0
- package/dist/quality/run-quality.d.ts.map +1 -0
- package/dist/quality/run-quality.js +442 -0
- package/dist/schemas/json-schemas.d.ts +184 -3
- package/dist/schemas/json-schemas.d.ts.map +1 -1
- package/dist/schemas/json-schemas.js +113 -2
- package/dist/shrk.d.ts +3 -0
- package/dist/shrk.d.ts.map +1 -0
- package/dist/shrk.js +30 -0
- package/dist/surface/audience-applicability.d.ts +26 -0
- package/dist/surface/audience-applicability.d.ts.map +1 -0
- package/dist/surface/audience-applicability.js +60 -0
- package/dist/surface/catalog-command-safety.d.ts +18 -0
- package/dist/surface/catalog-command-safety.d.ts.map +1 -0
- package/dist/surface/catalog-command-safety.js +62 -0
- package/dist/surface/cli-command-resolver.d.ts +21 -0
- package/dist/surface/cli-command-resolver.d.ts.map +1 -0
- package/dist/surface/cli-command-resolver.js +87 -0
- package/dist/surface/command-dispatch-kind.d.ts +20 -0
- package/dist/surface/command-dispatch-kind.d.ts.map +1 -0
- package/dist/surface/command-dispatch-kind.js +20 -0
- package/dist/surface/command-index-entry.d.ts +40 -0
- package/dist/surface/command-index-entry.d.ts.map +1 -0
- package/dist/surface/command-index-entry.js +1 -0
- package/dist/surface/command-index.d.ts +77 -0
- package/dist/surface/command-index.d.ts.map +1 -0
- package/dist/surface/command-index.js +323 -0
- package/dist/surface/i-command-index.d.ts +22 -0
- package/dist/surface/i-command-index.d.ts.map +1 -0
- package/dist/surface/i-command-index.js +1 -0
- package/dist/surface/i-command-string-context.d.ts +20 -0
- package/dist/surface/i-command-string-context.d.ts.map +1 -0
- package/dist/surface/i-command-string-context.js +1 -0
- package/dist/surface/i-surface-denial.d.ts +13 -0
- package/dist/surface/i-surface-denial.d.ts.map +1 -0
- package/dist/surface/i-surface-denial.js +1 -0
- package/dist/surface/load-surface-context.d.ts +17 -1
- package/dist/surface/load-surface-context.d.ts.map +1 -1
- package/dist/surface/load-surface-context.js +48 -10
- package/dist/surface/not-enabled-error.d.ts +38 -4
- package/dist/surface/not-enabled-error.d.ts.map +1 -1
- package/dist/surface/not-enabled-error.js +96 -6
- package/dist/surface/profiles.d.ts +7 -0
- package/dist/surface/profiles.d.ts.map +1 -1
- package/dist/surface/resolve-command-string.d.ts +10 -0
- package/dist/surface/resolve-command-string.d.ts.map +1 -0
- package/dist/surface/resolve-command-string.js +572 -0
- package/dist/surface/surface-config-writer.d.ts +7 -2
- package/dist/surface/surface-config-writer.d.ts.map +1 -1
- package/dist/surface/surface-config-writer.js +25 -6
- package/dist/surface/surface-layer.d.ts +11 -0
- package/dist/surface/surface-layer.d.ts.map +1 -0
- package/dist/surface/surface-layer.js +11 -0
- package/dist/surface/surface-refusal-reason.d.ts +16 -0
- package/dist/surface/surface-refusal-reason.d.ts.map +1 -0
- package/dist/surface/surface-refusal-reason.js +16 -0
- package/dist/surface/surface-selector.d.ts +26 -0
- package/dist/surface/surface-selector.d.ts.map +1 -0
- package/dist/surface/surface-selector.js +42 -0
- package/dist/surface/surface-summary.d.ts +61 -18
- package/dist/surface/surface-summary.d.ts.map +1 -1
- package/dist/surface/surface-summary.js +147 -60
- package/dist/surface/tier.d.ts +85 -22
- package/dist/surface/tier.d.ts.map +1 -1
- package/dist/surface/tier.js +132 -30
- package/dist/usage/usage-log.d.ts +6 -1
- package/dist/usage/usage-log.d.ts.map +1 -1
- package/dist/usage/usage-log.js +7 -2
- package/dist/validation/run-validation-loop.d.ts +9 -0
- package/dist/validation/run-validation-loop.d.ts.map +1 -1
- package/dist/validation/run-validation-loop.js +14 -11
- package/dist/validation/typecheck-emitted.d.ts +8 -9
- package/dist/validation/typecheck-emitted.d.ts.map +1 -1
- package/dist/validation/typecheck-emitted.js +17 -97
- package/package.json +34 -34
|
@@ -17,35 +17,47 @@
|
|
|
17
17
|
import { spawnSync } from 'node:child_process';
|
|
18
18
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
19
19
|
import * as nodePath from 'node:path';
|
|
20
|
-
import { failsWhenEmpty } from '@shrkcrft/core';
|
|
21
|
-
import {
|
|
20
|
+
import { coverageShortfall, failsWhenEmpty, formatEmptyRuleAdvice, normalizeRuleList, normalizeWiringSource, resolveSourceGlobs, ruleAssertsEmptyOutput, settleRuleEmptiness, unitStateLists, UnitLivenessState, } from '@shrkcrft/core';
|
|
21
|
+
import { settleVerdict } from "../gates/settle-verdict.js";
|
|
22
|
+
import { baselineCount, baselineFails, ceilingValue, clearFileReadCache, computeBaselineFromExtractor, diffBaseline, evaluateCeiling, globListSelects, planeScanExcludeDirs, readGlobListUnits, readScopeCoverage, readScopeHasUnread, settleGlobLists, sourceLivenessRequest, } from '@shrkcrft/boundaries';
|
|
22
23
|
import { resolveChangedFiles, resolveProjectConfig } from '@shrkcrft/inspector';
|
|
23
24
|
import { flagBool, flagString, resolveCwd, } from "../command-registry.js";
|
|
25
|
+
import { PositionalMode } from "../dispatch/positional-mode.js";
|
|
24
26
|
import { ExitCode } from "../exit-codes.js";
|
|
25
27
|
import { asJson, header, kv } from "../output/format-output.js";
|
|
26
28
|
import { buildGateEnvelope } from "../gates/gate-envelope.js";
|
|
29
|
+
import { verdictLine } from "../gates/verdict-line.js";
|
|
30
|
+
import { acceptedEmptyNote } from "../gates/accepted-empty-note.js";
|
|
31
|
+
import { emptyRuleAdviceLines } from "../gates/empty-rule-advice-lines.js";
|
|
32
|
+
import { qualifyCleanForUnits } from "../gates/qualify-clean-for-units.js";
|
|
33
|
+
import { unitStateNotes } from "../gates/unit-state-notes.js";
|
|
34
|
+
import { planeVerdictForExit } from "../gates/plane-verdict.js";
|
|
35
|
+
import { seamRejectedRules } from "../gates/seam-rejected-rules.js";
|
|
27
36
|
const SCHEMA = 'sharkcraft.baseline/v1';
|
|
28
37
|
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
29
38
|
async function loadBaselines(cwd) {
|
|
30
39
|
const loaded = await resolveProjectConfig(cwd);
|
|
31
40
|
if (!loaded.ok)
|
|
32
41
|
return { ok: false, message: loaded.error.message };
|
|
33
|
-
const rel = nodePath.relative(cwd, loaded.value.sharkcraftDir).split(nodePath.sep).join('/');
|
|
34
42
|
return {
|
|
35
43
|
ok: true,
|
|
36
44
|
value: {
|
|
37
45
|
rules: loaded.value.config.baselines ?? [],
|
|
38
46
|
planeDiagnostics: loaded.value.planeDiagnostics,
|
|
39
|
-
|
|
47
|
+
rejected: seamRejectedRules(loaded.value, ['baseline']),
|
|
48
|
+
// THE plane scan scope (one entry, or none).
|
|
49
|
+
sharkcraftDirRel: planeScanExcludeDirs(cwd, loaded.value.sharkcraftDir)[0] ?? '',
|
|
40
50
|
},
|
|
41
51
|
};
|
|
42
52
|
}
|
|
43
53
|
/** Narrow to `--id`, refusing an unknown id rather than silently selecting nothing. */
|
|
44
|
-
function selectRules(rules, id
|
|
54
|
+
function selectRules(rules, id,
|
|
55
|
+
/** Ids that are declared but did not load (merge-seam rejections) — selectable by a verdict verb. */
|
|
56
|
+
extraKnown = []) {
|
|
45
57
|
if (!id)
|
|
46
58
|
return { ok: true, rules };
|
|
47
59
|
const wanted = id.split(',').map((s) => s.trim()).filter(Boolean);
|
|
48
|
-
const known = new Set(rules.map((r) => r.id));
|
|
60
|
+
const known = new Set([...rules.map((r) => r.id), ...extraKnown]);
|
|
49
61
|
const unknown = wanted.filter((w) => !known.has(w));
|
|
50
62
|
if (unknown.length > 0) {
|
|
51
63
|
return {
|
|
@@ -59,15 +71,16 @@ function computeCurrent(cwd, rule, excludeDirs) {
|
|
|
59
71
|
if (rule.compute.kind === 'extractor') {
|
|
60
72
|
const source = rule.compute.source;
|
|
61
73
|
if (!source)
|
|
62
|
-
return { text: '', error: 'compute.kind "extractor" but no `source` declared' };
|
|
74
|
+
return { text: '', error: 'compute.kind "extractor" but no `source` declared', unitCount: 0 };
|
|
63
75
|
const res = computeBaselineFromExtractor(cwd, source, excludeDirs);
|
|
76
|
+
const readScope = res.unread.length > 0 ? { readScope: { read: res.filesScanned, unread: res.unread } } : {};
|
|
64
77
|
return res.error
|
|
65
|
-
? { text: '', error: res.error, filesScanned: res.filesScanned }
|
|
66
|
-
: { text: res.text, filesScanned: res.filesScanned };
|
|
78
|
+
? { text: '', error: res.error, filesScanned: res.filesScanned, unitCount: 0 }
|
|
79
|
+
: { text: res.text, filesScanned: res.filesScanned, unitCount: res.ids.length, ...readScope };
|
|
67
80
|
}
|
|
68
81
|
const run = rule.compute.run;
|
|
69
82
|
if (!run || run.trim() === '') {
|
|
70
|
-
return { text: '', error: 'compute.kind "command" but no `run` declared' };
|
|
83
|
+
return { text: '', error: 'compute.kind "command" but no `run` declared', unitCount: 0 };
|
|
71
84
|
}
|
|
72
85
|
const child = spawnSync(run, {
|
|
73
86
|
cwd,
|
|
@@ -76,18 +89,135 @@ function computeCurrent(cwd, rule, excludeDirs) {
|
|
|
76
89
|
timeout: rule.compute.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
77
90
|
maxBuffer: 16 * 1024 * 1024,
|
|
78
91
|
});
|
|
79
|
-
|
|
80
|
-
|
|
92
|
+
// The command may rewrite files anywhere: drop every memoised tree read, so
|
|
93
|
+
// a read-cache window (`withFileReadCache`, e.g. `shrk quality`'s plane
|
|
94
|
+
// section) never serves a pre-spawn snapshot after it.
|
|
95
|
+
clearFileReadCache();
|
|
96
|
+
if (child.error) {
|
|
97
|
+
return { text: '', error: `compute command failed to start: ${child.error.message}`, unitCount: 0 };
|
|
98
|
+
}
|
|
81
99
|
if (child.status !== 0) {
|
|
82
100
|
const tail = String(child.stderr ?? '').trim().split('\n').slice(-3).join(' | ');
|
|
83
101
|
return {
|
|
84
102
|
text: '',
|
|
85
103
|
error: `compute command exited ${child.status ?? 'null'}${tail ? ` — ${tail}` : ''}`,
|
|
104
|
+
unitCount: 0,
|
|
86
105
|
};
|
|
87
106
|
}
|
|
88
|
-
|
|
107
|
+
const text = String(child.stdout ?? '');
|
|
108
|
+
return { text, unitCount: text.trim() === '' ? 0 : 1 };
|
|
89
109
|
}
|
|
90
|
-
|
|
110
|
+
/** A ledger whose recompute and committed side are both empty, in its words. */
|
|
111
|
+
const LEDGER_EMPTY = 'the recompute produced no entries (and the committed baseline is empty too)';
|
|
112
|
+
/** A ceiling over a measurement that measured nothing, in its words. */
|
|
113
|
+
const CEILING_EMPTY = 'the compute produced nothing — a ceiling over an empty measurement proves nothing';
|
|
114
|
+
/** A bless (`baseline update`) whose recompute measured nothing, in its words. */
|
|
115
|
+
const UPDATE_EMPTY = 'the recompute produced no entries';
|
|
116
|
+
/**
|
|
117
|
+
* THE glob lists of a baseline's INPUTS (round 13): an extractor compute's
|
|
118
|
+
* `source` (`compute.source.files`, and `to.files` for an import-edges fence)
|
|
119
|
+
* and the rule's `watchFiles` — with their `expectEmpty` markers. `baseline
|
|
120
|
+
* check` settles it (`settleGlobLists`) and `gates coverage` builds the same
|
|
121
|
+
* request, so the two read one baseline's units alike. `primaryLists` names
|
|
122
|
+
* the lists whose liveness decides the rule's emptiness — a command compute
|
|
123
|
+
* has none (its `watchFiles` is a footprint, not an input it reads).
|
|
124
|
+
*/
|
|
125
|
+
export function baselineLivenessRequest(cwd, rule, excludeDirs) {
|
|
126
|
+
const source = rule.compute.kind === 'extractor' ? rule.compute.source : undefined;
|
|
127
|
+
const sources = sourceLivenessRequest(cwd, source !== undefined ? [{ label: 'compute.source', source }] : [], excludeDirs, rule.id);
|
|
128
|
+
const watch = rule.watchFiles ?? [];
|
|
129
|
+
return {
|
|
130
|
+
request: {
|
|
131
|
+
subject: rule.id,
|
|
132
|
+
lists: [
|
|
133
|
+
...sources.lists,
|
|
134
|
+
...(watch.length > 0
|
|
135
|
+
? [
|
|
136
|
+
{
|
|
137
|
+
list: 'watchFiles',
|
|
138
|
+
...(sources.lists.length > 0 ? { label: 'watchFiles' } : {}),
|
|
139
|
+
globs: watch,
|
|
140
|
+
units: readGlobListUnits(cwd, watch, new Set(excludeDirs)),
|
|
141
|
+
},
|
|
142
|
+
]
|
|
143
|
+
: []),
|
|
144
|
+
],
|
|
145
|
+
marks: [...sources.marks, ...(rule.expectEmptyUnits ?? [])],
|
|
146
|
+
},
|
|
147
|
+
primaryLists: source !== undefined ? ['compute.source.files'] : [],
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/** The unit fields every outcome carries when the rule's inputs were settled. */
|
|
151
|
+
function unitFieldsOf(liveness) {
|
|
152
|
+
if (liveness === undefined)
|
|
153
|
+
return {};
|
|
154
|
+
const nonLive = liveness.units.filter((u) => u.state !== UnitLivenessState.Live);
|
|
155
|
+
return {
|
|
156
|
+
...(liveness.acceptance !== undefined ? { unitAcceptance: liveness.acceptance } : {}),
|
|
157
|
+
...(liveness.dead.length + liveness.intendedEmpty.length + liveness.wentLive.length > 0
|
|
158
|
+
? { units: unitStateLists(liveness) }
|
|
159
|
+
: {}),
|
|
160
|
+
...(nonLive.length > 0 ? { unitLiveness: nonLive } : {}),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* THE rule-emptiness settle of a baseline whose compute measured nothing
|
|
165
|
+
* (round 13): the ledger's empty recompute, the ceiling's empty measurement
|
|
166
|
+
* (P1: decided on the extractor's UNIT count, never its text — an extractor
|
|
167
|
+
* serialises an empty set as `[]`, so the old text test could never fire for
|
|
168
|
+
* one) and `baseline update`'s refusal all ask it. A fence (`expectEmpty:
|
|
169
|
+
* true`) is accepted only over live or intended-empty inputs; over a dead
|
|
170
|
+
* input it is Stale (it proves nothing).
|
|
171
|
+
*/
|
|
172
|
+
function settleBaselineEmptiness(rule, computed, liveness, primaryLists, reason) {
|
|
173
|
+
return settleRuleEmptiness({
|
|
174
|
+
subject: rule.id,
|
|
175
|
+
unitLabel: 'entries',
|
|
176
|
+
filesMatched: computed.filesScanned ?? 0,
|
|
177
|
+
unitsMatched: 0,
|
|
178
|
+
unread: false,
|
|
179
|
+
liveness,
|
|
180
|
+
primaryLists,
|
|
181
|
+
assertsEmptyOutput: ruleAssertsEmptyOutput(rule),
|
|
182
|
+
failOnEmpty: failsWhenEmpty(rule),
|
|
183
|
+
noFilesReason: reason,
|
|
184
|
+
noUnitsReason: reason,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* The engine entry's idempotent normalisation (round 13): the extractor
|
|
189
|
+
* source's markable lists and `watchFiles` into plain lists plus their
|
|
190
|
+
* `expectEmptyUnits`. A malformed marker is an errored rule, never a crash.
|
|
191
|
+
*/
|
|
192
|
+
function normalizeBaselineRule(rule) {
|
|
193
|
+
const source = rule.compute.source !== undefined ? normalizeWiringSource(rule.compute.source) : undefined;
|
|
194
|
+
if (source !== undefined && !source.ok)
|
|
195
|
+
return { ok: false, error: `compute.source ${source.error.message}` };
|
|
196
|
+
const withSource = source !== undefined ? { ...rule, compute: { ...rule.compute, source: source.value } } : rule;
|
|
197
|
+
const watch = normalizeRuleList(withSource, 'watchFiles');
|
|
198
|
+
if (!watch.ok)
|
|
199
|
+
return { ok: false, error: `watchFiles ${watch.error.message}` };
|
|
200
|
+
return { ok: true, rule: watch.value };
|
|
201
|
+
}
|
|
202
|
+
export function evaluateBaselineRule(cwd, authored, excludeDirs, changedFiles) {
|
|
203
|
+
const normalized = normalizeBaselineRule(authored);
|
|
204
|
+
if (!normalized.ok) {
|
|
205
|
+
return { rule: authored, status: 'error', committedCount: 0, currentCount: 0, error: normalized.error };
|
|
206
|
+
}
|
|
207
|
+
const rule = normalized.rule;
|
|
208
|
+
// The rule's input globs, settled with their markers (round 13) — only when
|
|
209
|
+
// a unit is marked (its acceptance / went-live line rides on the outcome) or
|
|
210
|
+
// the compute measured nothing (what the empty rule is decided from).
|
|
211
|
+
const marked = (rule.expectEmptyUnits?.length ?? 0) > 0 || (rule.compute.source?.expectEmptyUnits?.length ?? 0) > 0;
|
|
212
|
+
let input;
|
|
213
|
+
const inputOf = () => {
|
|
214
|
+
if (input === undefined) {
|
|
215
|
+
const built = baselineLivenessRequest(cwd, rule, excludeDirs);
|
|
216
|
+
input = { liveness: settleGlobLists(built.request), primaryLists: built.primaryLists };
|
|
217
|
+
}
|
|
218
|
+
return input;
|
|
219
|
+
};
|
|
220
|
+
const markedFields = () => marked ? unitFieldsOf(inputOf().liveness) : {};
|
|
91
221
|
// --changed-only is honest about what it CANNOT scope: a command compute with
|
|
92
222
|
// no `watchFiles` has no file footprint, so it is reported as skipped rather
|
|
93
223
|
// than quietly passing.
|
|
@@ -95,7 +225,7 @@ function evaluateRule(cwd, rule, excludeDirs, changedFiles) {
|
|
|
95
225
|
const globs = rule.watchFiles && rule.watchFiles.length > 0
|
|
96
226
|
? rule.watchFiles
|
|
97
227
|
: rule.compute.kind === 'extractor'
|
|
98
|
-
? (rule.compute.source
|
|
228
|
+
? (resolveSourceGlobs(rule.compute.source ?? { files: [] }))
|
|
99
229
|
: undefined;
|
|
100
230
|
if (globs === undefined) {
|
|
101
231
|
return {
|
|
@@ -106,7 +236,9 @@ function evaluateRule(cwd, rule, excludeDirs, changedFiles) {
|
|
|
106
236
|
skipReason: 'command compute with no `watchFiles` cannot be scoped to a changeset',
|
|
107
237
|
};
|
|
108
238
|
}
|
|
109
|
-
|
|
239
|
+
// One list (watchFiles, else the extractor's files): a changed file the
|
|
240
|
+
// list EXCLUDES cannot move the value, so it does not select the rule.
|
|
241
|
+
if (!changedFiles.some((f) => globListSelects(f, globs))) {
|
|
110
242
|
return {
|
|
111
243
|
rule,
|
|
112
244
|
status: 'skipped',
|
|
@@ -116,6 +248,64 @@ function evaluateRule(cwd, rule, excludeDirs, changedFiles) {
|
|
|
116
248
|
};
|
|
117
249
|
}
|
|
118
250
|
}
|
|
251
|
+
// A ceiling rule pins a NUMBER declared in the config, so it has no committed
|
|
252
|
+
// artifact to read and no entry diff to compute — the comparison is the whole
|
|
253
|
+
// check. Branching here (rather than in a parallel engine) keeps `baseline
|
|
254
|
+
// check`, `gates check` and `quality` running the identical evaluation.
|
|
255
|
+
if (rule.mode === 'ceiling') {
|
|
256
|
+
const computed = computeCurrent(cwd, rule, excludeDirs);
|
|
257
|
+
if (computed.error) {
|
|
258
|
+
return { rule, status: 'error', committedCount: 0, currentCount: 0, error: computed.error, ...markedFields() };
|
|
259
|
+
}
|
|
260
|
+
const value = ceilingValue(rule, computed.text);
|
|
261
|
+
// A compute that MEASURED nothing measured nothing. Reporting `0 ≤ 200` as
|
|
262
|
+
// a pass is how a broken extractor greens a ratchet forever. Decided on the
|
|
263
|
+
// UNIT count (round 13, P1 — an extractor serialises its empty set as `[]`,
|
|
264
|
+
// so the old `text.trim() === ''` test could never fire for one) through
|
|
265
|
+
// THE rule-emptiness settle, the one `gates coverage` reads too.
|
|
266
|
+
if (computed.unitCount === 0 && !readScopeHasUnread(computed.readScope)) {
|
|
267
|
+
const { liveness, primaryLists } = inputOf();
|
|
268
|
+
const emptiness = settleBaselineEmptiness(rule, computed, liveness, primaryLists, CEILING_EMPTY);
|
|
269
|
+
if (emptiness.skipped) {
|
|
270
|
+
return {
|
|
271
|
+
rule,
|
|
272
|
+
status: emptiness.fails ? 'failed' : 'skipped',
|
|
273
|
+
current: computed.text,
|
|
274
|
+
committedCount: rule.ceiling ?? 0,
|
|
275
|
+
currentCount: 0,
|
|
276
|
+
skipReason: emptiness.skipReason ?? CEILING_EMPTY,
|
|
277
|
+
...unitFieldsOf(liveness),
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
// Accepted: the rule asserts an empty output (`expectEmpty: true` is
|
|
281
|
+
// honoured on a ceiling) or every input glob is intended-empty. The
|
|
282
|
+
// ceiling is still judged — on the measured 0.
|
|
283
|
+
const judged = evaluateCeiling(rule, value);
|
|
284
|
+
return {
|
|
285
|
+
rule,
|
|
286
|
+
status: judged.failed ? 'failed' : 'passed',
|
|
287
|
+
current: computed.text,
|
|
288
|
+
committedCount: judged.ceiling,
|
|
289
|
+
currentCount: judged.value,
|
|
290
|
+
ceiling: judged,
|
|
291
|
+
...(!judged.failed && emptiness.coverage !== undefined ? { emptyCoverage: emptiness.coverage } : {}),
|
|
292
|
+
...unitFieldsOf(liveness),
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
const verdict = evaluateCeiling(rule, value);
|
|
296
|
+
return {
|
|
297
|
+
rule,
|
|
298
|
+
status: verdict.failed ? 'failed' : 'passed',
|
|
299
|
+
current: computed.text,
|
|
300
|
+
committedCount: verdict.ceiling,
|
|
301
|
+
currentCount: verdict.value,
|
|
302
|
+
ceiling: verdict,
|
|
303
|
+
// An unread file makes the measurement a lower bound: over the ceiling
|
|
304
|
+
// is still a real failure, under it is not verified.
|
|
305
|
+
...(computed.readScope ? { readScope: computed.readScope } : {}),
|
|
306
|
+
...markedFields(),
|
|
307
|
+
};
|
|
308
|
+
}
|
|
119
309
|
const abs = nodePath.resolve(cwd, rule.baseline);
|
|
120
310
|
if (!existsSync(abs)) {
|
|
121
311
|
// No artifact yet. `check` must still fail (nothing to compare against),
|
|
@@ -157,15 +347,53 @@ function evaluateRule(cwd, rule, excludeDirs, changedFiles) {
|
|
|
157
347
|
// to prevent. But this only holds when the COMMITTED side is empty too: if
|
|
158
348
|
// the baseline has entries and the recompute has none, that is real drift
|
|
159
349
|
// (everything vanished) and must be reported as such, not swallowed as a skip.
|
|
350
|
+
const readScope = computed.readScope ? { readScope: computed.readScope } : {};
|
|
160
351
|
if (currentCount === 0 && committedCount === 0) {
|
|
352
|
+
// An empty recompute over a file the reader could not read is not "matched
|
|
353
|
+
// nothing": PARTIAL (the coverage names the file), never failOnEmpty's
|
|
354
|
+
// failure and never a pass.
|
|
355
|
+
if (readScopeHasUnread(computed.readScope)) {
|
|
356
|
+
return {
|
|
357
|
+
rule,
|
|
358
|
+
status: 'passed',
|
|
359
|
+
committed,
|
|
360
|
+
current: computed.text,
|
|
361
|
+
committedCount,
|
|
362
|
+
currentCount,
|
|
363
|
+
...readScope,
|
|
364
|
+
...markedFields(),
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
// THE rule-emptiness settle (round 13). A FENCE (`expectEmpty: true`)
|
|
368
|
+
// asserts the empty OUTPUT, so over live (or intended-empty) inputs its
|
|
369
|
+
// empty set is the verified pass — otherwise the rule could never be green.
|
|
370
|
+
// Over a DEAD input it proves nothing: Stale, and failOnEmpty's 1 (V2 f1d:
|
|
371
|
+
// it used to be accepted). An extractor whose every source glob is marked
|
|
372
|
+
// `expectEmpty` and matched no file is intended-empty, accepted. Anything
|
|
373
|
+
// else is the loud skip it always was.
|
|
374
|
+
const { liveness, primaryLists } = inputOf();
|
|
375
|
+
const emptiness = settleBaselineEmptiness(rule, computed, liveness, primaryLists, LEDGER_EMPTY);
|
|
376
|
+
if (!emptiness.skipped) {
|
|
377
|
+
return {
|
|
378
|
+
rule,
|
|
379
|
+
status: 'passed',
|
|
380
|
+
committed,
|
|
381
|
+
current: computed.text,
|
|
382
|
+
committedCount,
|
|
383
|
+
currentCount,
|
|
384
|
+
...(emptiness.coverage !== undefined ? { emptyCoverage: emptiness.coverage } : {}),
|
|
385
|
+
...unitFieldsOf(liveness),
|
|
386
|
+
};
|
|
387
|
+
}
|
|
161
388
|
return {
|
|
162
389
|
rule,
|
|
163
|
-
status:
|
|
390
|
+
status: emptiness.fails ? 'failed' : 'skipped',
|
|
164
391
|
committed,
|
|
165
392
|
current: computed.text,
|
|
166
393
|
committedCount,
|
|
167
394
|
currentCount,
|
|
168
|
-
skipReason:
|
|
395
|
+
skipReason: emptiness.skipReason ?? LEDGER_EMPTY,
|
|
396
|
+
...unitFieldsOf(liveness),
|
|
169
397
|
};
|
|
170
398
|
}
|
|
171
399
|
const diff = diffBaseline(rule, committed, computed.text);
|
|
@@ -180,8 +408,52 @@ function evaluateRule(cwd, rule, excludeDirs, changedFiles) {
|
|
|
180
408
|
// A total wipe is far more often a broken compute than a real emptying —
|
|
181
409
|
// say so next to the diff so it is not blessed by reflex.
|
|
182
410
|
...(currentCount === 0 ? { emptyCompute: true } : {}),
|
|
411
|
+
...readScope,
|
|
412
|
+
...markedFields(),
|
|
183
413
|
};
|
|
184
414
|
}
|
|
415
|
+
/**
|
|
416
|
+
* What one baseline rule examined — shared by `baseline check` and the
|
|
417
|
+
* aggregate (`gates check`, `quality`), so both report the same scope.
|
|
418
|
+
*
|
|
419
|
+
* A ledger examines the union of its committed and recomputed entries; a
|
|
420
|
+
* ceiling examines one measurement. A fence (`expectEmpty`) over an empty set
|
|
421
|
+
* is the asserted state, so its empty scope is ACCEPTED by that field — and
|
|
422
|
+
* printed as accepted, never silent. A skipped or errored rule examined nothing.
|
|
423
|
+
*
|
|
424
|
+
* An extractor compute that matched a file the reader did not read folds it
|
|
425
|
+
* in through `readScopeCoverage`, the one rule every plane uses: the record
|
|
426
|
+
* becomes `examined N of M files`, naming the unread file.
|
|
427
|
+
*/
|
|
428
|
+
export function baselineCoverage(o) {
|
|
429
|
+
return readScopeCoverage(planeBaselineCoverage(o), o.readScope);
|
|
430
|
+
}
|
|
431
|
+
function planeBaselineCoverage(o) {
|
|
432
|
+
// The accepted empty (round 13): the fence's asserted empty set, or an
|
|
433
|
+
// intended-empty input — the rule-emptiness settle's own record, one wording
|
|
434
|
+
// on every surface (`the rule asserts an empty set` / `asserted empty — …`).
|
|
435
|
+
if (o.emptyCoverage !== undefined)
|
|
436
|
+
return o.emptyCoverage;
|
|
437
|
+
if (o.ceiling !== undefined)
|
|
438
|
+
return { unit: 'measurements', expected: 1, examined: 1 };
|
|
439
|
+
if (o.skipReason !== undefined) {
|
|
440
|
+
return { unit: 'entries', expected: 0, examined: 0, reason: o.skipReason };
|
|
441
|
+
}
|
|
442
|
+
if (o.status === 'error') {
|
|
443
|
+
return { unit: 'entries', expected: 0, examined: 0, reason: 'the baseline could not be evaluated' };
|
|
444
|
+
}
|
|
445
|
+
const size = Math.max(o.committedCount, o.currentCount);
|
|
446
|
+
return { unit: 'entries', expected: size, examined: size };
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* What this rule pins, for display. A ledger names its committed file; a
|
|
450
|
+
* ceiling names the number in the config, because that IS its committed value.
|
|
451
|
+
*/
|
|
452
|
+
function pinLabel(rule) {
|
|
453
|
+
return rule.mode === 'ceiling'
|
|
454
|
+
? `ceiling ${rule.ceiling ?? 0} (sharkcraft.config.ts)`
|
|
455
|
+
: (rule.baseline ?? '(no artifact)');
|
|
456
|
+
}
|
|
185
457
|
function hintFor(rule) {
|
|
186
458
|
return rule.hint ?? `review the diff, then bless it with \`shrk baseline update --id ${rule.id}\``;
|
|
187
459
|
}
|
|
@@ -189,9 +461,11 @@ function outcomeJson(o) {
|
|
|
189
461
|
return {
|
|
190
462
|
id: o.rule.id,
|
|
191
463
|
...(o.rule.description ? { description: o.rule.description } : {}),
|
|
192
|
-
baseline: o.rule.baseline,
|
|
464
|
+
...(o.rule.baseline ? { baseline: o.rule.baseline } : {}),
|
|
465
|
+
mode: o.rule.mode ?? 'ledger',
|
|
193
466
|
severity: o.rule.severity ?? 'error',
|
|
194
|
-
direction: o.rule.direction ?? 'two-way',
|
|
467
|
+
direction: o.rule.direction ?? (o.rule.mode === 'ceiling' ? 'at-most' : 'two-way'),
|
|
468
|
+
...(o.ceiling ? { ceiling: o.ceiling } : {}),
|
|
195
469
|
status: o.status,
|
|
196
470
|
committedCount: o.committedCount,
|
|
197
471
|
currentCount: o.currentCount,
|
|
@@ -228,7 +502,13 @@ async function prepare(args, opts) {
|
|
|
228
502
|
process.stderr.write(`Could not load config: ${loaded.message}\n Run \`shrk doctor\` for details.\n`);
|
|
229
503
|
return { ok: false, code: ExitCode.UsageError };
|
|
230
504
|
}
|
|
231
|
-
const
|
|
505
|
+
const idFlag = flagString(args, 'id') ?? undefined;
|
|
506
|
+
// A verdict verb may select a rejected baseline by id (its errored row); a
|
|
507
|
+
// verb that acts on a rule (update / diff / explain) cannot — there is none.
|
|
508
|
+
const rejectedKnown = opts.rejectedKnown === true ? loaded.value.rejected.map((r) => r.id) : [];
|
|
509
|
+
const selected = selectRules(loaded.value.rules, idFlag, rejectedKnown);
|
|
510
|
+
const wantedIds = idFlag ? idFlag.split(',').map((s) => s.trim()).filter(Boolean) : undefined;
|
|
511
|
+
const rejected = wantedIds ? loaded.value.rejected.filter((r) => wantedIds.includes(r.id)) : loaded.value.rejected;
|
|
232
512
|
if (!selected.ok) {
|
|
233
513
|
process.stderr.write(selected.message + '\n');
|
|
234
514
|
return { ok: false, code: ExitCode.UsageError };
|
|
@@ -252,19 +532,53 @@ async function prepare(args, opts) {
|
|
|
252
532
|
excludeDirs: loaded.value.sharkcraftDirRel ? [loaded.value.sharkcraftDirRel] : [],
|
|
253
533
|
...(changedFiles !== undefined ? { changedFiles } : {}),
|
|
254
534
|
planeDiagnostics: loaded.value.planeDiagnostics,
|
|
535
|
+
rejected,
|
|
255
536
|
};
|
|
256
537
|
}
|
|
257
|
-
/**
|
|
258
|
-
function
|
|
538
|
+
/** ` ✗ <id> REJECTED — <why>` — a pack rule the merge seam refused (declared, never run). */
|
|
539
|
+
function writeRejected(rejected) {
|
|
540
|
+
for (const r of rejected)
|
|
541
|
+
process.stdout.write(` ✗ ${r.id} REJECTED — ${r.error ?? 'failed validation'}\n`);
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* The "no baselines declared" landing, shared by every subverb. A VERDICT verb
|
|
545
|
+
* passes its name so its JSON still carries the settled `gate` envelope —
|
|
546
|
+
* nothing declared is `2`, never a pass.
|
|
547
|
+
*/
|
|
548
|
+
function writeNoRules(json, verb) {
|
|
549
|
+
// A VERDICT verb settles first and renders second, in text AND JSON: nothing
|
|
550
|
+
// declared proposes 0 and the run coverage (expected 0) settles it to 2. The
|
|
551
|
+
// exit comes from the envelope — never a hard-coded code — so text, JSON and
|
|
552
|
+
// gate.exit cannot disagree. List / explain subverbs stay informational.
|
|
553
|
+
const gate = verb !== undefined
|
|
554
|
+
? buildGateEnvelope(verb, ExitCode.VerifiedPass, [], {
|
|
555
|
+
unit: 'baselines',
|
|
556
|
+
expected: 0,
|
|
557
|
+
examined: 0,
|
|
558
|
+
reason: 'no baselines[] declared',
|
|
559
|
+
})
|
|
560
|
+
: undefined;
|
|
561
|
+
const exit = gate?.exit ?? ExitCode.NotVerified;
|
|
259
562
|
if (json) {
|
|
260
|
-
process.stdout.write(asJson({
|
|
261
|
-
|
|
563
|
+
process.stdout.write(asJson({
|
|
564
|
+
schema: SCHEMA,
|
|
565
|
+
results: [],
|
|
566
|
+
evaluated: 0,
|
|
567
|
+
verdict: gate ? planeVerdictForExit(gate.exit) : 'not-verified',
|
|
568
|
+
...(gate ? { exitCode: gate.exit, gate } : {}),
|
|
569
|
+
}) + '\n');
|
|
570
|
+
return exit;
|
|
262
571
|
}
|
|
263
572
|
process.stdout.write(header('Baselines'));
|
|
264
573
|
process.stdout.write(' No baselines declared. Add `baselines[]` to sharkcraft.config.ts to replace a\n' +
|
|
265
574
|
' hand-rolled "committed file + recompute script + drift test" trio with one\n' +
|
|
266
575
|
' two-way engine (see docs/baseline-drift.md).\n');
|
|
267
|
-
|
|
576
|
+
if (gate) {
|
|
577
|
+
const line = verdictLine(gate, 'Nothing declared — accepted.');
|
|
578
|
+
if (line)
|
|
579
|
+
process.stdout.write(`\n${line}\n`);
|
|
580
|
+
}
|
|
581
|
+
return exit;
|
|
268
582
|
}
|
|
269
583
|
export const baselineListCommand = {
|
|
270
584
|
name: 'list',
|
|
@@ -276,11 +590,13 @@ export const baselineListCommand = {
|
|
|
276
590
|
if (!prep.ok)
|
|
277
591
|
return prep.code;
|
|
278
592
|
const json = flagBool(args, 'json');
|
|
279
|
-
if (prep.all.length === 0)
|
|
593
|
+
if (prep.all.length === 0 && prep.rejected.length === 0)
|
|
280
594
|
return writeNoRules(json);
|
|
281
595
|
if (json) {
|
|
282
596
|
process.stdout.write(asJson({
|
|
283
597
|
schema: SCHEMA,
|
|
598
|
+
// Declared by a pack, refused by the merge seam: never computed.
|
|
599
|
+
rejected: prep.rejected.map((r) => ({ id: r.id, error: r.error ?? null })),
|
|
284
600
|
baselines: prep.all.map((r) => ({
|
|
285
601
|
id: r.id,
|
|
286
602
|
description: r.description ?? null,
|
|
@@ -288,7 +604,9 @@ export const baselineListCommand = {
|
|
|
288
604
|
compute: r.compute.kind,
|
|
289
605
|
direction: r.direction ?? 'two-way',
|
|
290
606
|
keyBy: r.keyBy ?? null,
|
|
291
|
-
|
|
607
|
+
// The EFFECTIVE failOnEmpty (round 13) — THE authority, never the
|
|
608
|
+
// raw field: an error rule fails on empty by default.
|
|
609
|
+
failOnEmpty: failsWhenEmpty(r),
|
|
292
610
|
})),
|
|
293
611
|
diagnostics: prep.planeDiagnostics,
|
|
294
612
|
}) + '\n');
|
|
@@ -299,10 +617,14 @@ export const baselineListCommand = {
|
|
|
299
617
|
process.stdout.write(` • ${r.id} → ${r.baseline}\n`);
|
|
300
618
|
process.stdout.write(` compute ${r.compute.kind}${r.compute.kind === 'command' ? ` (${r.compute.run})` : ''}` +
|
|
301
619
|
` · direction ${r.direction ?? 'two-way'}${r.keyBy ? ` · keyBy ${r.keyBy}` : ''}` +
|
|
302
|
-
`${r
|
|
620
|
+
`${failsWhenEmpty(r) ? ' · failOnEmpty' : ''}\n`);
|
|
303
621
|
if (r.description)
|
|
304
622
|
process.stdout.write(` ${r.description}\n`);
|
|
305
623
|
}
|
|
624
|
+
if (prep.rejected.length > 0) {
|
|
625
|
+
process.stdout.write(`\n rejected at the pack-plane merge seam — never computed (${prep.rejected.length}):\n`);
|
|
626
|
+
writeRejected(prep.rejected);
|
|
627
|
+
}
|
|
306
628
|
for (const d of prep.planeDiagnostics)
|
|
307
629
|
process.stdout.write(` ! ${d}\n`);
|
|
308
630
|
return ExitCode.VerifiedPass;
|
|
@@ -314,52 +636,87 @@ export const baselineCheckCommand = {
|
|
|
314
636
|
usage: 'shrk baseline check [--id <ids>] [--changed-only] [--since <ref>] [--json]',
|
|
315
637
|
booleanFlags: new Set(['json', 'changed-only']),
|
|
316
638
|
async run(args) {
|
|
317
|
-
const prep = await prepare(args, { changedAware: true });
|
|
639
|
+
const prep = await prepare(args, { changedAware: true, rejectedKnown: true });
|
|
318
640
|
if (!prep.ok)
|
|
319
641
|
return prep.code;
|
|
320
642
|
const json = flagBool(args, 'json');
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
const
|
|
643
|
+
// A pack baseline the merge seam rejected is a declared baseline that was
|
|
644
|
+
// never computed — an errored row, exit 1 (round 12 review, R12-X1).
|
|
645
|
+
const rejected = prep.rejected;
|
|
646
|
+
if (prep.rules.length === 0 && rejected.length === 0)
|
|
647
|
+
return writeNoRules(json, 'baseline check');
|
|
648
|
+
const outcomes = prep.rules.map((r) => evaluateBaselineRule(prep.cwd, r, prep.excludeDirs, prep.changedFiles));
|
|
324
649
|
const failed = outcomes.filter((o) => o.status === 'failed' || (o.status === 'error' && (o.rule.severity ?? 'error') === 'error'));
|
|
325
650
|
const evaluated = outcomes.filter((o) => o.status !== 'skipped').length;
|
|
326
651
|
// 0 only when a NON-EMPTY scope was actually compared; 2 when nothing was.
|
|
327
652
|
// A skipped baseline is "partially verified", never a green 0.
|
|
328
653
|
const skippedCount = outcomes.filter((o) => o.status === 'skipped').length;
|
|
329
|
-
const
|
|
654
|
+
const proposed = failed.length > 0 || rejected.length > 0
|
|
330
655
|
? ExitCode.Failure
|
|
331
656
|
: evaluated === 0 || skippedCount > 0
|
|
332
657
|
? ExitCode.NotVerified
|
|
333
658
|
: ExitCode.VerifiedPass;
|
|
659
|
+
// Settle first, render second: one envelope for text AND JSON, and the ✓
|
|
660
|
+
// line is printed only from the settled verdict.
|
|
661
|
+
const unexamined = [
|
|
662
|
+
...outcomes.filter((o) => o.status === 'skipped' || o.status === 'error').map((o) => o.rule.id),
|
|
663
|
+
...rejected.map((r) => r.id),
|
|
664
|
+
];
|
|
665
|
+
const env = buildGateEnvelope('baseline check', proposed, [...outcomes.map((o) => ({
|
|
666
|
+
id: o.rule.id,
|
|
667
|
+
type: 'baseline',
|
|
668
|
+
status: o.status,
|
|
669
|
+
severity: o.rule.severity ?? 'error',
|
|
670
|
+
counts: { committed: o.committedCount, current: o.currentCount },
|
|
671
|
+
violations: [
|
|
672
|
+
...(o.diff?.added ?? []).map((id) => ({ id, message: 'added', hint: hintFor(o.rule) })),
|
|
673
|
+
...(o.diff?.removed ?? []).map((id) => ({ id, message: 'removed', hint: hintFor(o.rule) })),
|
|
674
|
+
],
|
|
675
|
+
...(o.skipReason ? { skipReason: o.skipReason } : {}),
|
|
676
|
+
...(o.error ? { error: o.error } : {}),
|
|
677
|
+
coverage: baselineCoverage(o),
|
|
678
|
+
// The rule's `expectEmpty` acceptance and unit lines (round 13), folded
|
|
679
|
+
// into the envelope's one settle.
|
|
680
|
+
...(o.unitAcceptance !== undefined ? { unitAcceptance: o.unitAcceptance } : {}),
|
|
681
|
+
...(o.units !== undefined ? { units: o.units } : {}),
|
|
682
|
+
})), ...rejected], {
|
|
683
|
+
unit: 'baselines',
|
|
684
|
+
expected: outcomes.length + rejected.length,
|
|
685
|
+
examined: outcomes.length + rejected.length - unexamined.length,
|
|
686
|
+
...(unexamined.length > 0 ? { unexamined, reason: 'compared nothing or could not run' } : {}),
|
|
687
|
+
});
|
|
688
|
+
const exit = env.exit;
|
|
334
689
|
if (json) {
|
|
335
690
|
process.stdout.write(asJson({
|
|
336
691
|
schema: SCHEMA,
|
|
337
692
|
results: outcomes.map(outcomeJson),
|
|
693
|
+
// Pack baselines the merge seam refused — errored rows in `gate.rules`.
|
|
694
|
+
rejected: rejected.map((r) => ({ id: r.id, error: r.error ?? null })),
|
|
338
695
|
evaluated,
|
|
339
|
-
skipped:
|
|
340
|
-
verdict:
|
|
696
|
+
skipped: skippedCount,
|
|
697
|
+
verdict: exit === ExitCode.Failure ? 'errors' : exit === ExitCode.VerifiedPass ? 'pass' : 'not-verified',
|
|
341
698
|
diagnostics: prep.planeDiagnostics,
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
type: 'baseline',
|
|
345
|
-
status: o.status,
|
|
346
|
-
severity: o.rule.severity ?? 'error',
|
|
347
|
-
counts: { committed: o.committedCount, current: o.currentCount },
|
|
348
|
-
violations: [
|
|
349
|
-
...(o.diff?.added ?? []).map((id) => ({ id, message: 'added', hint: hintFor(o.rule) })),
|
|
350
|
-
...(o.diff?.removed ?? []).map((id) => ({ id, message: 'removed', hint: hintFor(o.rule) })),
|
|
351
|
-
],
|
|
352
|
-
...(o.skipReason ? { skipReason: o.skipReason } : {}),
|
|
353
|
-
...(o.error ? { error: o.error } : {}),
|
|
354
|
-
}))),
|
|
699
|
+
exitCode: exit,
|
|
700
|
+
gate: env,
|
|
355
701
|
}) + '\n');
|
|
356
702
|
return exit;
|
|
357
703
|
}
|
|
358
704
|
process.stdout.write(header('Baseline drift'));
|
|
359
|
-
|
|
705
|
+
// Round 13 (K6): the printed count is the envelope's (`gate.evaluated`), which
|
|
706
|
+
// never counts a rule accepted as intended-empty — it is named apart.
|
|
707
|
+
process.stdout.write(kv('evaluated', `${env.evaluated} of ${prep.rules.length + rejected.length}${acceptedEmptyNote(env.acceptedEmpty)}` +
|
|
708
|
+
(rejected.length > 0 ? ` (${rejected.length} rejected at the pack-plane merge seam — NOT evaluated)` : '')) + '\n');
|
|
709
|
+
writeRejected(rejected);
|
|
360
710
|
for (const o of outcomes) {
|
|
361
711
|
if (o.status === 'passed') {
|
|
362
|
-
|
|
712
|
+
// Rendered from the SETTLED rule: a pass over part of its scope is
|
|
713
|
+
// `partial`, never a ✓ (the keystone emitter pattern, step 5).
|
|
714
|
+
const settledRule = env.rules.find((x) => x.id === o.rule.id);
|
|
715
|
+
process.stdout.write(settledRule?.status === 'partial'
|
|
716
|
+
? ` ~ ${o.rule.id} PARTIAL — ${settledRule.shortfall ?? 'part of its scope was not examined'}\n`
|
|
717
|
+
: o.ceiling
|
|
718
|
+
? ` ✓ ${o.rule.id} (${o.ceiling.value} ${o.ceiling.direction} ${o.ceiling.ceiling} — ${o.ceiling.slack} to spare)\n`
|
|
719
|
+
: ` ✓ ${o.rule.id} (${o.currentCount} entries, no drift)\n`);
|
|
363
720
|
continue;
|
|
364
721
|
}
|
|
365
722
|
if (o.status === 'skipped') {
|
|
@@ -374,6 +731,24 @@ export const baselineCheckCommand = {
|
|
|
374
731
|
}
|
|
375
732
|
continue;
|
|
376
733
|
}
|
|
734
|
+
// A rule that measured nothing and FAILS on it (failOnEmpty, or a fence
|
|
735
|
+
// over a dead input) has no diff — its reason is the only true line. It is
|
|
736
|
+
// never rendered as DRIFT with a bless hint (round 13, P1): blessing it
|
|
737
|
+
// would commit the empty measurement the loud skip refused.
|
|
738
|
+
if (o.status === 'failed' && o.skipReason !== undefined) {
|
|
739
|
+
process.stdout.write(` ✗ ${o.rule.id} FAILED — ${o.skipReason}\n`);
|
|
740
|
+
if (!ruleAssertsEmptyOutput(o.rule)) {
|
|
741
|
+
process.stdout.write(` → ${formatEmptyRuleAdvice({ fails: true })}\n`);
|
|
742
|
+
}
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
745
|
+
if (o.ceiling) {
|
|
746
|
+
const over = -o.ceiling.slack;
|
|
747
|
+
process.stdout.write(` ✗ ${o.rule.id} OVER CEILING — ${o.ceiling.value} is ${over} ` +
|
|
748
|
+
`${o.ceiling.direction === 'at-most' ? 'above' : 'below'} the limit of ${o.ceiling.ceiling}\n` +
|
|
749
|
+
` lower the measurement, or raise the ceiling deliberately: \`shrk baseline update --id ${o.rule.id}\`\n`);
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
377
752
|
const added = o.diff?.added.length ?? 0;
|
|
378
753
|
const removed = o.diff?.removed.length ?? 0;
|
|
379
754
|
process.stdout.write(` ✗ ${o.rule.id} DRIFT — ${added} added, ${removed} removed ` +
|
|
@@ -386,12 +761,29 @@ export const baselineCheckCommand = {
|
|
|
386
761
|
}
|
|
387
762
|
for (const d of prep.planeDiagnostics)
|
|
388
763
|
process.stdout.write(` ! ${d}\n`);
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
process.stdout.write(
|
|
764
|
+
// THE empty-rule advice (round 13) for a SOFT skip: a failing one is
|
|
765
|
+
// advised inline (above); a rule that matched nothing and does not fail on
|
|
766
|
+
// it was told nothing.
|
|
767
|
+
for (const a of emptyRuleAdviceLines(outcomes.filter((o) => o.status === 'skipped' && o.skipReason !== undefined).map(() => ({ fails: false })))) {
|
|
768
|
+
process.stdout.write(` ${a}.\n`);
|
|
394
769
|
}
|
|
770
|
+
// THE shared unit-state block (round 13, K2): a dead input glob — a dead
|
|
771
|
+
// import-edges `to.files` under an accepted fence included — and a LOCAL
|
|
772
|
+
// expectEmpty marker whose target appeared withhold the ✓ (exit
|
|
773
|
+
// unchanged); a pack marker is INFO.
|
|
774
|
+
const unitNotes = unitStateNotes(outcomes.map((o) => ({
|
|
775
|
+
id: o.rule.id,
|
|
776
|
+
...(o.unitLiveness !== undefined ? { unitLiveness: o.unitLiveness } : {}),
|
|
777
|
+
reportedEmpty: o.status === 'skipped' || o.skipReason !== undefined,
|
|
778
|
+
})));
|
|
779
|
+
process.stdout.write(unitNotes.text);
|
|
780
|
+
const line = verdictLine(env, qualifyCleanForUnits(outcomes.some((o) => o.ceiling)
|
|
781
|
+
? 'Every baseline is within its pinned value. ✓'
|
|
782
|
+
: 'Every baseline matches its committed artifact. ✓', unitNotes), proposed === ExitCode.NotVerified && evaluated === 0
|
|
783
|
+
? 'Nothing was compared — this is NOT a pass. Every selected baseline was skipped.'
|
|
784
|
+
: undefined);
|
|
785
|
+
if (line)
|
|
786
|
+
process.stdout.write(`\n${line}\n`);
|
|
395
787
|
return exit;
|
|
396
788
|
},
|
|
397
789
|
};
|
|
@@ -407,7 +799,7 @@ export const baselineDiffCommand = {
|
|
|
407
799
|
const json = flagBool(args, 'json');
|
|
408
800
|
if (prep.rules.length === 0)
|
|
409
801
|
return writeNoRules(json);
|
|
410
|
-
const outcomes = prep.rules.map((r) =>
|
|
802
|
+
const outcomes = prep.rules.map((r) => evaluateBaselineRule(prep.cwd, r, prep.excludeDirs, undefined));
|
|
411
803
|
if (json) {
|
|
412
804
|
process.stdout.write(asJson({ schema: SCHEMA, results: outcomes.map(outcomeJson), inspection: true }) + '\n');
|
|
413
805
|
return ExitCode.VerifiedPass;
|
|
@@ -416,7 +808,13 @@ export const baselineDiffCommand = {
|
|
|
416
808
|
for (const o of outcomes) {
|
|
417
809
|
const added = o.diff?.added.length ?? 0;
|
|
418
810
|
const removed = o.diff?.removed.length ?? 0;
|
|
419
|
-
process.stdout.write(`\n${o.rule.id} (${o.rule
|
|
811
|
+
process.stdout.write(`\n${o.rule.id} (${pinLabel(o.rule)}) ` +
|
|
812
|
+
(o.status === 'error'
|
|
813
|
+
? `! ${o.error}`
|
|
814
|
+
: o.ceiling
|
|
815
|
+
? `${o.ceiling.value} vs ${o.ceiling.direction} ${o.ceiling.ceiling} (slack ${o.ceiling.slack})`
|
|
816
|
+
: `+${added} / -${removed}`) +
|
|
817
|
+
'\n');
|
|
420
818
|
writeDiff(o, 100);
|
|
421
819
|
}
|
|
422
820
|
return ExitCode.VerifiedPass;
|
|
@@ -437,19 +835,101 @@ export const baselineUpdateCommand = {
|
|
|
437
835
|
return writeNoRules(json);
|
|
438
836
|
const written = [];
|
|
439
837
|
const errors = [];
|
|
838
|
+
// A ceiling's pinned value lives in `sharkcraft.config.ts`, so blessing it
|
|
839
|
+
// is a config edit, not a file write. `update` prints the exact one-line
|
|
840
|
+
// change instead of touching the config — the same explicit, reviewable
|
|
841
|
+
// bless, without this command growing the ability to rewrite the rules it
|
|
842
|
+
// is enforcing.
|
|
843
|
+
const reblessed = [];
|
|
844
|
+
// A value recomputed from an incomplete read (a file over the read cap)
|
|
845
|
+
// misses whatever the unread file holds. Blessing it would commit a number
|
|
846
|
+
// the loud-skip rule forbids reporting, and the next `baseline check`
|
|
847
|
+
// would be NOT VERIFIED anyway. So the rule is refused and named, and the
|
|
848
|
+
// run settles NOT VERIFIED (2), never "wrote … (exit 0)".
|
|
849
|
+
const unverified = [];
|
|
850
|
+
const unverifiedCoverage = [];
|
|
851
|
+
// Round 13 (K9): ceilings whose measurement is an EMPTY one the settle does
|
|
852
|
+
// not accept — the loud skip `baseline check` reports, never a proposal.
|
|
853
|
+
const emptyCeilings = [];
|
|
440
854
|
for (const rule of prep.rules) {
|
|
441
855
|
const computed = computeCurrent(prep.cwd, rule, prep.excludeDirs);
|
|
442
856
|
if (computed.error) {
|
|
443
857
|
errors.push({ id: rule.id, error: computed.error });
|
|
444
858
|
continue;
|
|
445
859
|
}
|
|
446
|
-
if (computed.
|
|
447
|
-
|
|
860
|
+
if (readScopeHasUnread(computed.readScope)) {
|
|
861
|
+
const record = {
|
|
862
|
+
...readScopeCoverage({ unit: 'baseline entries', expected: 1, examined: 1 }, computed.readScope),
|
|
863
|
+
subject: rule.id,
|
|
864
|
+
};
|
|
865
|
+
unverifiedCoverage.push(record);
|
|
866
|
+
unverified.push({
|
|
867
|
+
id: rule.id,
|
|
868
|
+
shortfall: coverageShortfall(record) ?? '',
|
|
869
|
+
unread: (computed.readScope?.unread ?? []).map((u) => u.path),
|
|
870
|
+
});
|
|
871
|
+
continue;
|
|
872
|
+
}
|
|
873
|
+
if (rule.mode === 'ceiling') {
|
|
874
|
+
// K9: never propose `ceiling: <measured>` over an EMPTY measurement.
|
|
875
|
+
// THE rule-emptiness settle `baseline check` reads decides it (on the
|
|
876
|
+
// extractor's UNIT count): an accepted empty (a fence, an intended-
|
|
877
|
+
// empty input) is judged like any value; a stale one is the loud skip
|
|
878
|
+
// — 2 NOT VERIFIED, or 1 under failOnEmpty — never `ceiling: 0`.
|
|
879
|
+
if (computed.unitCount === 0) {
|
|
880
|
+
const input = baselineLivenessRequest(prep.cwd, rule, prep.excludeDirs);
|
|
881
|
+
const emptiness = settleBaselineEmptiness(rule, computed, settleGlobLists(input.request), input.primaryLists, CEILING_EMPTY);
|
|
882
|
+
if (emptiness.skipped) {
|
|
883
|
+
const reason = emptiness.skipReason ?? CEILING_EMPTY;
|
|
884
|
+
if (emptiness.fails) {
|
|
885
|
+
errors.push({
|
|
886
|
+
id: rule.id,
|
|
887
|
+
error: `refusing to propose a ceiling from an EMPTY measurement for a \`failOnEmpty\` rule — ${reason} — fix the compute first`,
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
else {
|
|
891
|
+
unverifiedCoverage.push({ unit: 'measurements', expected: 1, examined: 0, subject: rule.id, reason });
|
|
892
|
+
emptyCeilings.push({ id: rule.id, reason });
|
|
893
|
+
}
|
|
894
|
+
continue;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
const value = ceilingValue(rule, computed.text);
|
|
898
|
+
reblessed.push({
|
|
448
899
|
id: rule.id,
|
|
449
|
-
|
|
900
|
+
from: rule.ceiling ?? 0,
|
|
901
|
+
to: value,
|
|
902
|
+
edit: `ceiling: ${value}, // was ${rule.ceiling ?? 0}`,
|
|
450
903
|
});
|
|
451
904
|
continue;
|
|
452
905
|
}
|
|
906
|
+
// An EMPTY bless is decided by the compute's UNIT count and THE
|
|
907
|
+
// rule-emptiness settle `baseline check` reads (round 13, P1) — never by
|
|
908
|
+
// its text: an extractor serialises its empty set as `[]`, so the old
|
|
909
|
+
// `text.trim() === ''` test never refused one, and a failOnEmpty ledger
|
|
910
|
+
// over a dead selector was blessed empty. A fence's asserted-empty output
|
|
911
|
+
// over live inputs, or an intended-empty input, is blessable; a stale
|
|
912
|
+
// empty (a dead or unmarked selector, files that yielded nothing) of a
|
|
913
|
+
// failOnEmpty rule is refused, naming why.
|
|
914
|
+
if (computed.unitCount === 0) {
|
|
915
|
+
const input = baselineLivenessRequest(prep.cwd, rule, prep.excludeDirs);
|
|
916
|
+
const liveness = settleGlobLists(input.request);
|
|
917
|
+
// Name the dead input the settle found (its own `.dead` lines), so the
|
|
918
|
+
// refusal says WHICH selector to fix.
|
|
919
|
+
const deadInputs = liveness.dead.map((u) => `${u.label}: ${u.deadReason ?? 'matched 0 files'}`);
|
|
920
|
+
const reason = deadInputs.length > 0
|
|
921
|
+
? `${UPDATE_EMPTY} — its input selector matched nothing (${deadInputs.join('; ')})`
|
|
922
|
+
: UPDATE_EMPTY;
|
|
923
|
+
const emptiness = settleBaselineEmptiness(rule, computed, liveness, input.primaryLists, reason);
|
|
924
|
+
if (emptiness.skipped && emptiness.fails) {
|
|
925
|
+
errors.push({
|
|
926
|
+
id: rule.id,
|
|
927
|
+
error: 'refusing to write an EMPTY baseline for a `failOnEmpty` rule — ' +
|
|
928
|
+
`${emptiness.skipReason ?? reason} — fix the compute first`,
|
|
929
|
+
});
|
|
930
|
+
continue;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
453
933
|
const abs = nodePath.resolve(prep.cwd, rule.baseline);
|
|
454
934
|
const previous = existsSync(abs) ? readFileSync(abs, 'utf8') : undefined;
|
|
455
935
|
const changed = previous !== computed.text;
|
|
@@ -459,20 +939,56 @@ export const baselineUpdateCommand = {
|
|
|
459
939
|
}
|
|
460
940
|
written.push({ id: rule.id, path: rule.baseline, bytes: computed.text.length, changed });
|
|
461
941
|
}
|
|
942
|
+
const settled = settleVerdict(errors.length > 0 ? ExitCode.Failure : ExitCode.VerifiedPass, unverifiedCoverage);
|
|
943
|
+
const exit = settled.exit;
|
|
462
944
|
if (json) {
|
|
463
|
-
process.stdout.write(asJson({
|
|
464
|
-
|
|
945
|
+
process.stdout.write(asJson({
|
|
946
|
+
schema: SCHEMA,
|
|
947
|
+
dryRun,
|
|
948
|
+
written,
|
|
949
|
+
reblessed,
|
|
950
|
+
errors,
|
|
951
|
+
...(unverified.length > 0 ? { unverified } : {}),
|
|
952
|
+
// K9: a ceiling over an empty measurement is the loud skip, never a
|
|
953
|
+
// proposal — named here with the reason `baseline check` prints.
|
|
954
|
+
...(emptyCeilings.length > 0 ? { skipped: emptyCeilings } : {}),
|
|
955
|
+
...(unverified.length > 0 || emptyCeilings.length > 0 ? { shortfalls: settled.shortfalls } : {}),
|
|
956
|
+
exitCode: exit,
|
|
957
|
+
}) + '\n');
|
|
958
|
+
return exit;
|
|
465
959
|
}
|
|
466
960
|
process.stdout.write(header(dryRun ? 'Baseline update (dry run)' : 'Baseline update'));
|
|
467
961
|
for (const w of written) {
|
|
468
962
|
process.stdout.write(` ${w.changed ? (dryRun ? 'would write' : 'wrote') : 'unchanged '} ${w.path} (${w.bytes} bytes)\n`);
|
|
469
963
|
}
|
|
964
|
+
for (const r of reblessed) {
|
|
965
|
+
const verb = r.from === r.to ? 'unchanged ' : 'edit config';
|
|
966
|
+
process.stdout.write(` ${verb} ${r.id} (ceiling ${r.from} → ${r.to})\n`);
|
|
967
|
+
if (r.from !== r.to)
|
|
968
|
+
process.stdout.write(` ${r.edit}\n`);
|
|
969
|
+
}
|
|
470
970
|
for (const e of errors)
|
|
471
971
|
process.stdout.write(` ! ${e.id}: ${e.error}\n`);
|
|
972
|
+
for (const u of unverified) {
|
|
973
|
+
process.stdout.write(` ! ${u.id}: not written — computed from an incomplete read (${u.unread.join(', ')})\n`);
|
|
974
|
+
}
|
|
975
|
+
for (const s of emptyCeilings) {
|
|
976
|
+
process.stdout.write(` – ${s.id}: no ceiling proposed — ${s.reason}\n`);
|
|
977
|
+
}
|
|
978
|
+
if (reblessed.some((r) => r.from !== r.to)) {
|
|
979
|
+
process.stdout.write('\nA ceiling lives in sharkcraft.config.ts — apply the line above by hand so raising it stays a reviewed diff.\n');
|
|
980
|
+
}
|
|
472
981
|
if (written.some((w) => w.changed) && !dryRun) {
|
|
473
982
|
process.stdout.write('\nReview the diff before committing — this is the bless step.\n');
|
|
474
983
|
}
|
|
475
|
-
|
|
984
|
+
const line = verdictLine(settled, '', unverified.length > 0
|
|
985
|
+
? 'Nothing was blessed for the baseline(s) above: their value came from an incomplete read.'
|
|
986
|
+
: emptyCeilings.length > 0
|
|
987
|
+
? 'No ceiling was proposed for the baseline(s) above: their measurement was empty — fix the compute, or mark a planned input { pattern, expectEmpty: true }.'
|
|
988
|
+
: undefined);
|
|
989
|
+
if (line)
|
|
990
|
+
process.stdout.write(`\n${line}\n`);
|
|
991
|
+
return exit;
|
|
476
992
|
},
|
|
477
993
|
};
|
|
478
994
|
export const baselineExplainCommand = {
|
|
@@ -494,7 +1010,7 @@ export const baselineExplainCommand = {
|
|
|
494
1010
|
process.stderr.write(`No baseline "${id}". Declared: ${prep.all.map((r) => r.id).join(', ') || '(none)'}\n`);
|
|
495
1011
|
return ExitCode.UsageError;
|
|
496
1012
|
}
|
|
497
|
-
const outcome =
|
|
1013
|
+
const outcome = evaluateBaselineRule(prep.cwd, rule, prep.excludeDirs, undefined);
|
|
498
1014
|
if (flagBool(args, 'json')) {
|
|
499
1015
|
process.stdout.write(asJson({
|
|
500
1016
|
schema: 'sharkcraft.baseline-explain/v1',
|
|
@@ -507,15 +1023,21 @@ export const baselineExplainCommand = {
|
|
|
507
1023
|
process.stdout.write(header(`Baseline: ${rule.id}`));
|
|
508
1024
|
if (rule.description)
|
|
509
1025
|
process.stdout.write(` ${rule.description}\n`);
|
|
510
|
-
process.stdout.write(kv('committed', rule
|
|
511
|
-
process.stdout.write(kv('compute', rule.compute.kind === 'command'
|
|
512
|
-
|
|
1026
|
+
process.stdout.write(kv('committed', pinLabel(rule)) + '\n');
|
|
1027
|
+
process.stdout.write(kv('compute', rule.compute.kind === 'command'
|
|
1028
|
+
? `command · ${rule.compute.run}`
|
|
1029
|
+
: `extractor · ${rule.compute.source?.extract ?? 'sugar'}` +
|
|
1030
|
+
(rule.compute.source?.$use ? ` (via $use:${rule.compute.source.$use})` : '')) + '\n');
|
|
1031
|
+
process.stdout.write(kv('direction', rule.direction ?? (rule.mode === 'ceiling' ? 'at-most' : 'two-way')) + '\n');
|
|
513
1032
|
process.stdout.write(kv('canonical', outcome.diff?.canonical ?? rule.compute.canonical ?? 'auto') + '\n');
|
|
514
1033
|
if (rule.keyBy)
|
|
515
1034
|
process.stdout.write(kv('keyBy', rule.keyBy) + '\n');
|
|
516
|
-
process.stdout.write(kv('entries', outcome.
|
|
517
|
-
?
|
|
518
|
-
|
|
1035
|
+
process.stdout.write(kv(outcome.ceiling ? 'measured' : 'entries', outcome.ceiling
|
|
1036
|
+
? `${outcome.ceiling.value} ${outcome.ceiling.direction} ${outcome.ceiling.ceiling}` +
|
|
1037
|
+
` (${outcome.ceiling.slack >= 0 ? `${outcome.ceiling.slack} to spare` : `${-outcome.ceiling.slack} over`})`
|
|
1038
|
+
: outcome.missingBaseline
|
|
1039
|
+
? `committed (none yet) → ${outcome.currentCount} now`
|
|
1040
|
+
: `${outcome.committedCount} committed → ${outcome.currentCount} now`) + '\n');
|
|
519
1041
|
process.stdout.write(kv('status', outcome.status) + '\n');
|
|
520
1042
|
if (outcome.error)
|
|
521
1043
|
process.stdout.write(` ! ${outcome.error}\n`);
|
|
@@ -532,11 +1054,13 @@ export const baselineCommand = {
|
|
|
532
1054
|
name: 'baseline',
|
|
533
1055
|
description: 'Committed-baseline drift engine: recompute a ledger/digest/allow-list and fail on drift in BOTH directions. Read-only except `update`.',
|
|
534
1056
|
usage: 'shrk baseline list | check | diff | update | explain --id <id>',
|
|
1057
|
+
// Every subverb is a registered trie child; any other bare token is refused
|
|
1058
|
+
// by the dispatcher guard (closest match named) before this body runs — the
|
|
1059
|
+
// one unknown-subcommand authority. Only a bare `shrk baseline` lands here.
|
|
1060
|
+
positionals: PositionalMode.None,
|
|
535
1061
|
booleanFlags: new Set(['json', 'changed-only', 'dry-run']),
|
|
536
|
-
async run(
|
|
537
|
-
|
|
538
|
-
process.stderr.write((sub ? `Unknown subcommand "${sub}". ` : '') +
|
|
539
|
-
'Usage: shrk baseline list | check [--id X] | diff | update | explain --id <id>\n');
|
|
1062
|
+
async run() {
|
|
1063
|
+
process.stderr.write('Usage: shrk baseline list | check [--id X] | diff | update | explain --id <id>\n');
|
|
540
1064
|
return ExitCode.UsageError;
|
|
541
1065
|
},
|
|
542
1066
|
};
|