@shrkcrft/cli 0.1.0-alpha.30 → 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 +54 -3
- package/dist/commands/baseline.command.d.ts.map +1 -1
- package/dist/commands/baseline.command.js +582 -74
- 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 +138 -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 +1241 -322
- 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 +3 -0
- 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 +13 -0
- package/dist/commands/docs-references.command.d.ts.map +1 -1
- package/dist/commands/docs-references.command.js +269 -72
- package/dist/commands/doctor.command.d.ts.map +1 -1
- package/dist/commands/doctor.command.js +58 -4
- 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 +52 -0
- package/dist/commands/gates.command.d.ts.map +1 -1
- package/dist/commands/gates.command.js +811 -185
- 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 +33 -3
- package/dist/commands/generated.command.d.ts.map +1 -1
- package/dist/commands/generated.command.js +499 -147
- 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 +198 -24
- 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 +360 -73
- 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 +37 -3
- package/dist/gates/gate-rule-globs.d.ts.map +1 -1
- package/dist/gates/gate-rule-globs.js +78 -40
- package/dist/gates/gate-rule-view.d.ts +17 -4
- package/dist/gates/gate-rule-view.d.ts.map +1 -1
- package/dist/gates/gate-rule-view.js +105 -90
- 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 +187 -4
- package/dist/gates/rule-coverage.d.ts.map +1 -1
- package/dist/gates/rule-coverage.js +501 -49
- package/dist/gates/run-gate-planes.d.ts +14 -0
- package/dist/gates/run-gate-planes.d.ts.map +1 -1
- package/dist/gates/run-gate-planes.js +172 -30
- 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 +200 -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 {
|
|
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.
|
|
@@ -106,7 +236,9 @@ export function evaluateBaselineRule(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 @@ export function evaluateBaselineRule(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,11 +347,12 @@ export function evaluateBaselineRule(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) {
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
if (
|
|
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)) {
|
|
165
356
|
return {
|
|
166
357
|
rule,
|
|
167
358
|
status: 'passed',
|
|
@@ -169,16 +360,40 @@ export function evaluateBaselineRule(cwd, rule, excludeDirs, changedFiles) {
|
|
|
169
360
|
current: computed.text,
|
|
170
361
|
committedCount,
|
|
171
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),
|
|
172
386
|
};
|
|
173
387
|
}
|
|
174
388
|
return {
|
|
175
389
|
rule,
|
|
176
|
-
status:
|
|
390
|
+
status: emptiness.fails ? 'failed' : 'skipped',
|
|
177
391
|
committed,
|
|
178
392
|
current: computed.text,
|
|
179
393
|
committedCount,
|
|
180
394
|
currentCount,
|
|
181
|
-
skipReason:
|
|
395
|
+
skipReason: emptiness.skipReason ?? LEDGER_EMPTY,
|
|
396
|
+
...unitFieldsOf(liveness),
|
|
182
397
|
};
|
|
183
398
|
}
|
|
184
399
|
const diff = diffBaseline(rule, committed, computed.text);
|
|
@@ -193,8 +408,52 @@ export function evaluateBaselineRule(cwd, rule, excludeDirs, changedFiles) {
|
|
|
193
408
|
// A total wipe is far more often a broken compute than a real emptying —
|
|
194
409
|
// say so next to the diff so it is not blessed by reflex.
|
|
195
410
|
...(currentCount === 0 ? { emptyCompute: true } : {}),
|
|
411
|
+
...readScope,
|
|
412
|
+
...markedFields(),
|
|
196
413
|
};
|
|
197
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
|
+
}
|
|
198
457
|
function hintFor(rule) {
|
|
199
458
|
return rule.hint ?? `review the diff, then bless it with \`shrk baseline update --id ${rule.id}\``;
|
|
200
459
|
}
|
|
@@ -202,9 +461,11 @@ function outcomeJson(o) {
|
|
|
202
461
|
return {
|
|
203
462
|
id: o.rule.id,
|
|
204
463
|
...(o.rule.description ? { description: o.rule.description } : {}),
|
|
205
|
-
baseline: o.rule.baseline,
|
|
464
|
+
...(o.rule.baseline ? { baseline: o.rule.baseline } : {}),
|
|
465
|
+
mode: o.rule.mode ?? 'ledger',
|
|
206
466
|
severity: o.rule.severity ?? 'error',
|
|
207
|
-
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 } : {}),
|
|
208
469
|
status: o.status,
|
|
209
470
|
committedCount: o.committedCount,
|
|
210
471
|
currentCount: o.currentCount,
|
|
@@ -241,7 +502,13 @@ async function prepare(args, opts) {
|
|
|
241
502
|
process.stderr.write(`Could not load config: ${loaded.message}\n Run \`shrk doctor\` for details.\n`);
|
|
242
503
|
return { ok: false, code: ExitCode.UsageError };
|
|
243
504
|
}
|
|
244
|
-
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;
|
|
245
512
|
if (!selected.ok) {
|
|
246
513
|
process.stderr.write(selected.message + '\n');
|
|
247
514
|
return { ok: false, code: ExitCode.UsageError };
|
|
@@ -265,19 +532,53 @@ async function prepare(args, opts) {
|
|
|
265
532
|
excludeDirs: loaded.value.sharkcraftDirRel ? [loaded.value.sharkcraftDirRel] : [],
|
|
266
533
|
...(changedFiles !== undefined ? { changedFiles } : {}),
|
|
267
534
|
planeDiagnostics: loaded.value.planeDiagnostics,
|
|
535
|
+
rejected,
|
|
268
536
|
};
|
|
269
537
|
}
|
|
270
|
-
/**
|
|
271
|
-
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;
|
|
272
562
|
if (json) {
|
|
273
|
-
process.stdout.write(asJson({
|
|
274
|
-
|
|
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;
|
|
275
571
|
}
|
|
276
572
|
process.stdout.write(header('Baselines'));
|
|
277
573
|
process.stdout.write(' No baselines declared. Add `baselines[]` to sharkcraft.config.ts to replace a\n' +
|
|
278
574
|
' hand-rolled "committed file + recompute script + drift test" trio with one\n' +
|
|
279
575
|
' two-way engine (see docs/baseline-drift.md).\n');
|
|
280
|
-
|
|
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;
|
|
281
582
|
}
|
|
282
583
|
export const baselineListCommand = {
|
|
283
584
|
name: 'list',
|
|
@@ -289,11 +590,13 @@ export const baselineListCommand = {
|
|
|
289
590
|
if (!prep.ok)
|
|
290
591
|
return prep.code;
|
|
291
592
|
const json = flagBool(args, 'json');
|
|
292
|
-
if (prep.all.length === 0)
|
|
593
|
+
if (prep.all.length === 0 && prep.rejected.length === 0)
|
|
293
594
|
return writeNoRules(json);
|
|
294
595
|
if (json) {
|
|
295
596
|
process.stdout.write(asJson({
|
|
296
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 })),
|
|
297
600
|
baselines: prep.all.map((r) => ({
|
|
298
601
|
id: r.id,
|
|
299
602
|
description: r.description ?? null,
|
|
@@ -301,7 +604,9 @@ export const baselineListCommand = {
|
|
|
301
604
|
compute: r.compute.kind,
|
|
302
605
|
direction: r.direction ?? 'two-way',
|
|
303
606
|
keyBy: r.keyBy ?? null,
|
|
304
|
-
|
|
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),
|
|
305
610
|
})),
|
|
306
611
|
diagnostics: prep.planeDiagnostics,
|
|
307
612
|
}) + '\n');
|
|
@@ -312,10 +617,14 @@ export const baselineListCommand = {
|
|
|
312
617
|
process.stdout.write(` • ${r.id} → ${r.baseline}\n`);
|
|
313
618
|
process.stdout.write(` compute ${r.compute.kind}${r.compute.kind === 'command' ? ` (${r.compute.run})` : ''}` +
|
|
314
619
|
` · direction ${r.direction ?? 'two-way'}${r.keyBy ? ` · keyBy ${r.keyBy}` : ''}` +
|
|
315
|
-
`${r
|
|
620
|
+
`${failsWhenEmpty(r) ? ' · failOnEmpty' : ''}\n`);
|
|
316
621
|
if (r.description)
|
|
317
622
|
process.stdout.write(` ${r.description}\n`);
|
|
318
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
|
+
}
|
|
319
628
|
for (const d of prep.planeDiagnostics)
|
|
320
629
|
process.stdout.write(` ! ${d}\n`);
|
|
321
630
|
return ExitCode.VerifiedPass;
|
|
@@ -327,52 +636,87 @@ export const baselineCheckCommand = {
|
|
|
327
636
|
usage: 'shrk baseline check [--id <ids>] [--changed-only] [--since <ref>] [--json]',
|
|
328
637
|
booleanFlags: new Set(['json', 'changed-only']),
|
|
329
638
|
async run(args) {
|
|
330
|
-
const prep = await prepare(args, { changedAware: true });
|
|
639
|
+
const prep = await prepare(args, { changedAware: true, rejectedKnown: true });
|
|
331
640
|
if (!prep.ok)
|
|
332
641
|
return prep.code;
|
|
333
642
|
const json = flagBool(args, 'json');
|
|
334
|
-
|
|
335
|
-
|
|
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');
|
|
336
648
|
const outcomes = prep.rules.map((r) => evaluateBaselineRule(prep.cwd, r, prep.excludeDirs, prep.changedFiles));
|
|
337
649
|
const failed = outcomes.filter((o) => o.status === 'failed' || (o.status === 'error' && (o.rule.severity ?? 'error') === 'error'));
|
|
338
650
|
const evaluated = outcomes.filter((o) => o.status !== 'skipped').length;
|
|
339
651
|
// 0 only when a NON-EMPTY scope was actually compared; 2 when nothing was.
|
|
340
652
|
// A skipped baseline is "partially verified", never a green 0.
|
|
341
653
|
const skippedCount = outcomes.filter((o) => o.status === 'skipped').length;
|
|
342
|
-
const
|
|
654
|
+
const proposed = failed.length > 0 || rejected.length > 0
|
|
343
655
|
? ExitCode.Failure
|
|
344
656
|
: evaluated === 0 || skippedCount > 0
|
|
345
657
|
? ExitCode.NotVerified
|
|
346
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;
|
|
347
689
|
if (json) {
|
|
348
690
|
process.stdout.write(asJson({
|
|
349
691
|
schema: SCHEMA,
|
|
350
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 })),
|
|
351
695
|
evaluated,
|
|
352
|
-
skipped:
|
|
353
|
-
verdict:
|
|
696
|
+
skipped: skippedCount,
|
|
697
|
+
verdict: exit === ExitCode.Failure ? 'errors' : exit === ExitCode.VerifiedPass ? 'pass' : 'not-verified',
|
|
354
698
|
diagnostics: prep.planeDiagnostics,
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
type: 'baseline',
|
|
358
|
-
status: o.status,
|
|
359
|
-
severity: o.rule.severity ?? 'error',
|
|
360
|
-
counts: { committed: o.committedCount, current: o.currentCount },
|
|
361
|
-
violations: [
|
|
362
|
-
...(o.diff?.added ?? []).map((id) => ({ id, message: 'added', hint: hintFor(o.rule) })),
|
|
363
|
-
...(o.diff?.removed ?? []).map((id) => ({ id, message: 'removed', hint: hintFor(o.rule) })),
|
|
364
|
-
],
|
|
365
|
-
...(o.skipReason ? { skipReason: o.skipReason } : {}),
|
|
366
|
-
...(o.error ? { error: o.error } : {}),
|
|
367
|
-
}))),
|
|
699
|
+
exitCode: exit,
|
|
700
|
+
gate: env,
|
|
368
701
|
}) + '\n');
|
|
369
702
|
return exit;
|
|
370
703
|
}
|
|
371
704
|
process.stdout.write(header('Baseline drift'));
|
|
372
|
-
|
|
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);
|
|
373
710
|
for (const o of outcomes) {
|
|
374
711
|
if (o.status === 'passed') {
|
|
375
|
-
|
|
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`);
|
|
376
720
|
continue;
|
|
377
721
|
}
|
|
378
722
|
if (o.status === 'skipped') {
|
|
@@ -387,6 +731,24 @@ export const baselineCheckCommand = {
|
|
|
387
731
|
}
|
|
388
732
|
continue;
|
|
389
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
|
+
}
|
|
390
752
|
const added = o.diff?.added.length ?? 0;
|
|
391
753
|
const removed = o.diff?.removed.length ?? 0;
|
|
392
754
|
process.stdout.write(` ✗ ${o.rule.id} DRIFT — ${added} added, ${removed} removed ` +
|
|
@@ -399,12 +761,29 @@ export const baselineCheckCommand = {
|
|
|
399
761
|
}
|
|
400
762
|
for (const d of prep.planeDiagnostics)
|
|
401
763
|
process.stdout.write(` ! ${d}\n`);
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
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`);
|
|
407
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`);
|
|
408
787
|
return exit;
|
|
409
788
|
},
|
|
410
789
|
};
|
|
@@ -429,7 +808,13 @@ export const baselineDiffCommand = {
|
|
|
429
808
|
for (const o of outcomes) {
|
|
430
809
|
const added = o.diff?.added.length ?? 0;
|
|
431
810
|
const removed = o.diff?.removed.length ?? 0;
|
|
432
|
-
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');
|
|
433
818
|
writeDiff(o, 100);
|
|
434
819
|
}
|
|
435
820
|
return ExitCode.VerifiedPass;
|
|
@@ -450,19 +835,101 @@ export const baselineUpdateCommand = {
|
|
|
450
835
|
return writeNoRules(json);
|
|
451
836
|
const written = [];
|
|
452
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 = [];
|
|
453
854
|
for (const rule of prep.rules) {
|
|
454
855
|
const computed = computeCurrent(prep.cwd, rule, prep.excludeDirs);
|
|
455
856
|
if (computed.error) {
|
|
456
857
|
errors.push({ id: rule.id, error: computed.error });
|
|
457
858
|
continue;
|
|
458
859
|
}
|
|
459
|
-
if (computed.
|
|
460
|
-
|
|
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({
|
|
461
899
|
id: rule.id,
|
|
462
|
-
|
|
900
|
+
from: rule.ceiling ?? 0,
|
|
901
|
+
to: value,
|
|
902
|
+
edit: `ceiling: ${value}, // was ${rule.ceiling ?? 0}`,
|
|
463
903
|
});
|
|
464
904
|
continue;
|
|
465
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
|
+
}
|
|
466
933
|
const abs = nodePath.resolve(prep.cwd, rule.baseline);
|
|
467
934
|
const previous = existsSync(abs) ? readFileSync(abs, 'utf8') : undefined;
|
|
468
935
|
const changed = previous !== computed.text;
|
|
@@ -472,20 +939,56 @@ export const baselineUpdateCommand = {
|
|
|
472
939
|
}
|
|
473
940
|
written.push({ id: rule.id, path: rule.baseline, bytes: computed.text.length, changed });
|
|
474
941
|
}
|
|
942
|
+
const settled = settleVerdict(errors.length > 0 ? ExitCode.Failure : ExitCode.VerifiedPass, unverifiedCoverage);
|
|
943
|
+
const exit = settled.exit;
|
|
475
944
|
if (json) {
|
|
476
|
-
process.stdout.write(asJson({
|
|
477
|
-
|
|
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;
|
|
478
959
|
}
|
|
479
960
|
process.stdout.write(header(dryRun ? 'Baseline update (dry run)' : 'Baseline update'));
|
|
480
961
|
for (const w of written) {
|
|
481
962
|
process.stdout.write(` ${w.changed ? (dryRun ? 'would write' : 'wrote') : 'unchanged '} ${w.path} (${w.bytes} bytes)\n`);
|
|
482
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
|
+
}
|
|
483
970
|
for (const e of errors)
|
|
484
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
|
+
}
|
|
485
981
|
if (written.some((w) => w.changed) && !dryRun) {
|
|
486
982
|
process.stdout.write('\nReview the diff before committing — this is the bless step.\n');
|
|
487
983
|
}
|
|
488
|
-
|
|
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;
|
|
489
992
|
},
|
|
490
993
|
};
|
|
491
994
|
export const baselineExplainCommand = {
|
|
@@ -520,18 +1023,21 @@ export const baselineExplainCommand = {
|
|
|
520
1023
|
process.stdout.write(header(`Baseline: ${rule.id}`));
|
|
521
1024
|
if (rule.description)
|
|
522
1025
|
process.stdout.write(` ${rule.description}\n`);
|
|
523
|
-
process.stdout.write(kv('committed', rule
|
|
1026
|
+
process.stdout.write(kv('committed', pinLabel(rule)) + '\n');
|
|
524
1027
|
process.stdout.write(kv('compute', rule.compute.kind === 'command'
|
|
525
1028
|
? `command · ${rule.compute.run}`
|
|
526
1029
|
: `extractor · ${rule.compute.source?.extract ?? 'sugar'}` +
|
|
527
1030
|
(rule.compute.source?.$use ? ` (via $use:${rule.compute.source.$use})` : '')) + '\n');
|
|
528
|
-
process.stdout.write(kv('direction', rule.direction ?? 'two-way') + '\n');
|
|
1031
|
+
process.stdout.write(kv('direction', rule.direction ?? (rule.mode === 'ceiling' ? 'at-most' : 'two-way')) + '\n');
|
|
529
1032
|
process.stdout.write(kv('canonical', outcome.diff?.canonical ?? rule.compute.canonical ?? 'auto') + '\n');
|
|
530
1033
|
if (rule.keyBy)
|
|
531
1034
|
process.stdout.write(kv('keyBy', rule.keyBy) + '\n');
|
|
532
|
-
process.stdout.write(kv('entries', outcome.
|
|
533
|
-
?
|
|
534
|
-
|
|
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');
|
|
535
1041
|
process.stdout.write(kv('status', outcome.status) + '\n');
|
|
536
1042
|
if (outcome.error)
|
|
537
1043
|
process.stdout.write(` ! ${outcome.error}\n`);
|
|
@@ -548,11 +1054,13 @@ export const baselineCommand = {
|
|
|
548
1054
|
name: 'baseline',
|
|
549
1055
|
description: 'Committed-baseline drift engine: recompute a ledger/digest/allow-list and fail on drift in BOTH directions. Read-only except `update`.',
|
|
550
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,
|
|
551
1061
|
booleanFlags: new Set(['json', 'changed-only', 'dry-run']),
|
|
552
|
-
async run(
|
|
553
|
-
|
|
554
|
-
process.stderr.write((sub ? `Unknown subcommand "${sub}". ` : '') +
|
|
555
|
-
'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');
|
|
556
1064
|
return ExitCode.UsageError;
|
|
557
1065
|
},
|
|
558
1066
|
};
|