@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
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* shrk generated list # every declared artifact set
|
|
8
8
|
* shrk generated check [--id X] # regen → temp dir → diff BOTH ways + headers
|
|
9
9
|
* [--headers-only] # header contract only; never spawns
|
|
10
|
-
* shrk generated update [--id X] #
|
|
10
|
+
* shrk generated update [--id X] # regen → temp dir → write the ALIGNED files in place (bless)
|
|
11
11
|
* shrk generated explain --id X # what it will run + what it currently sees
|
|
12
12
|
*
|
|
13
13
|
* The build compiles a hand-edited generated file perfectly happily; only
|
|
@@ -17,65 +17,43 @@
|
|
|
17
17
|
* fully useful and never spawns, so packs can still ship it.
|
|
18
18
|
*/
|
|
19
19
|
import { spawnSync } from 'node:child_process';
|
|
20
|
-
import {
|
|
20
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
21
21
|
import * as nodeOs from 'node:os';
|
|
22
22
|
import * as nodePath from 'node:path';
|
|
23
|
-
import { failsWhenEmpty } from '@shrkcrft/core';
|
|
24
|
-
import { checkProvenanceHeaders, compareGeneratedTrees, scanGeneratedFiles, } from '@shrkcrft/boundaries';
|
|
23
|
+
import { failsWhenEmpty, formatEmptyRuleAdvice, normalizeRuleList, RuleEmptiness, settleRuleEmptiness, unitStateLists, UnitLivenessState, } from '@shrkcrft/core';
|
|
24
|
+
import { checkProvenanceHeaders, clearFileReadCache, compareGeneratedTrees, globListSelects, globListWalkGlobs, planeScanExcludeDirs, readGlobListLiveness, readRegenTree, readScopeCoverage, readScopeHasUnread, scanGeneratedFiles, } from '@shrkcrft/boundaries';
|
|
25
25
|
import { resolveProjectConfig } from '@shrkcrft/inspector';
|
|
26
26
|
import { flagBool, flagString, resolveCwd, } from "../command-registry.js";
|
|
27
27
|
import { ExitCode } from "../exit-codes.js";
|
|
28
28
|
import { asJson, header, kv } from "../output/format-output.js";
|
|
29
29
|
import { buildGateEnvelope } from "../gates/gate-envelope.js";
|
|
30
|
+
import { verdictLine } from "../gates/verdict-line.js";
|
|
31
|
+
import { acceptedEmptyNote } from "../gates/accepted-empty-note.js";
|
|
32
|
+
import { emptyRuleAdviceLines } from "../gates/empty-rule-advice-lines.js";
|
|
33
|
+
import { qualifyCleanForUnits } from "../gates/qualify-clean-for-units.js";
|
|
34
|
+
import { unitStateNotes } from "../gates/unit-state-notes.js";
|
|
35
|
+
import { planeVerdictForExit } from "../gates/plane-verdict.js";
|
|
36
|
+
import { seamRejectedRules } from "../gates/seam-rejected-rules.js";
|
|
30
37
|
const SCHEMA = 'sharkcraft.generated-drift/v1';
|
|
31
38
|
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
32
|
-
/** Cap on the temp tree read — a runaway regen must not be read into memory whole. */
|
|
33
|
-
const MAX_REGEN_FILE_BYTES = 2_000_000;
|
|
34
39
|
async function loadRules(cwd) {
|
|
35
40
|
const loaded = await resolveProjectConfig(cwd);
|
|
36
41
|
if (!loaded.ok)
|
|
37
42
|
return { ok: false, message: loaded.error.message };
|
|
38
|
-
const rel = nodePath.relative(cwd, loaded.value.sharkcraftDir).split(nodePath.sep).join('/');
|
|
39
43
|
return {
|
|
40
44
|
ok: true,
|
|
41
45
|
value: {
|
|
42
46
|
rules: loaded.value.config.generatedArtifacts ?? [],
|
|
43
47
|
planeDiagnostics: loaded.value.planeDiagnostics,
|
|
44
|
-
excludeDirs:
|
|
48
|
+
excludeDirs: planeScanExcludeDirs(cwd, loaded.value.sharkcraftDir),
|
|
49
|
+
rejected: seamRejectedRules(loaded.value, ['generated']),
|
|
45
50
|
},
|
|
46
51
|
};
|
|
47
52
|
}
|
|
48
|
-
/**
|
|
49
|
-
function
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
let entries;
|
|
53
|
-
try {
|
|
54
|
-
entries = readdirSync(abs, { withFileTypes: true });
|
|
55
|
-
}
|
|
56
|
-
catch {
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
for (const e of entries) {
|
|
60
|
-
const child = nodePath.join(abs, e.name);
|
|
61
|
-
if (e.isDirectory()) {
|
|
62
|
-
visit(child);
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
if (!e.isFile())
|
|
66
|
-
continue;
|
|
67
|
-
try {
|
|
68
|
-
if (statSync(child).size > MAX_REGEN_FILE_BYTES)
|
|
69
|
-
continue;
|
|
70
|
-
out.set(nodePath.relative(root, child).split(nodePath.sep).join('/'), readFileSync(child, 'utf8'));
|
|
71
|
-
}
|
|
72
|
-
catch {
|
|
73
|
-
// unreadable — skip
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
visit(root);
|
|
78
|
-
return out;
|
|
53
|
+
/** ` ✗ <id> REJECTED — <why>` — a pack rule the merge seam refused (declared, never run). */
|
|
54
|
+
function writeRejected(rejected) {
|
|
55
|
+
for (const r of rejected)
|
|
56
|
+
process.stdout.write(` ✗ ${r.id} REJECTED — ${r.error ?? 'failed validation'}\n`);
|
|
79
57
|
}
|
|
80
58
|
/** Number of trailing path SEGMENTS `a` and `b` share (0 when none). */
|
|
81
59
|
function sharedSuffixSegments(a, b) {
|
|
@@ -97,17 +75,21 @@ function sharedSuffixSegments(a, b) {
|
|
|
97
75
|
* files. Ties break lexically so the mapping is deterministic, each committed
|
|
98
76
|
* path is claimed at most once, and anything unmatched keeps its temp-relative
|
|
99
77
|
* key and surfaces as `only-regenerated` rather than disappearing.
|
|
78
|
+
*
|
|
79
|
+
* Returns temp path → key, so the outputs the reader read AND the ones it did
|
|
80
|
+
* not (over the regen cap) key onto the same committed path through ONE
|
|
81
|
+
* alignment.
|
|
100
82
|
*/
|
|
101
|
-
function
|
|
102
|
-
const
|
|
83
|
+
function alignKeys(tempPaths, committedPaths) {
|
|
84
|
+
const targets = [...new Set(committedPaths)].sort();
|
|
103
85
|
const claimed = new Set();
|
|
104
86
|
const out = new Map();
|
|
105
87
|
// Best-match first: a temp path with a longer shared suffix has the stronger
|
|
106
88
|
// claim on a committed path, so resolve those before the weaker ones.
|
|
107
|
-
const scored = [...
|
|
89
|
+
const scored = [...tempPaths]
|
|
108
90
|
.map((tempPath) => {
|
|
109
91
|
let best;
|
|
110
|
-
for (const c of
|
|
92
|
+
for (const c of targets) {
|
|
111
93
|
const score = sharedSuffixSegments(tempPath, c);
|
|
112
94
|
if (score > 0 && (best === undefined || score > best.score))
|
|
113
95
|
best = { path: c, score };
|
|
@@ -116,13 +98,12 @@ function alignToCommitted(temp, committed) {
|
|
|
116
98
|
})
|
|
117
99
|
.sort((a, b) => (b.best?.score ?? 0) - (a.best?.score ?? 0) || a.tempPath.localeCompare(b.tempPath));
|
|
118
100
|
for (const { tempPath, best } of scored) {
|
|
119
|
-
const content = temp.get(tempPath);
|
|
120
101
|
if (best && !claimed.has(best.path)) {
|
|
121
102
|
claimed.add(best.path);
|
|
122
|
-
out.set(best.path
|
|
103
|
+
out.set(tempPath, best.path);
|
|
123
104
|
}
|
|
124
105
|
else {
|
|
125
|
-
out.set(tempPath,
|
|
106
|
+
out.set(tempPath, tempPath);
|
|
126
107
|
}
|
|
127
108
|
}
|
|
128
109
|
return out;
|
|
@@ -136,7 +117,7 @@ function alignToCommitted(temp, committed) {
|
|
|
136
117
|
* (and later diffed against) only its own files, so writer A's output can never
|
|
137
118
|
* be reported as writer B's stale committed file.
|
|
138
119
|
*/
|
|
139
|
-
function runOneRegen(cwd, command, committed, timeoutMs) {
|
|
120
|
+
function runOneRegen(cwd, command, committed, timeoutMs, unreadCommitted = []) {
|
|
140
121
|
const tmp = mkdtempSync(nodePath.join(nodeOs.tmpdir(), 'shrk-generated-'));
|
|
141
122
|
try {
|
|
142
123
|
const child = spawnSync(command.split('{TMP}').join(tmp), {
|
|
@@ -146,17 +127,28 @@ function runOneRegen(cwd, command, committed, timeoutMs) {
|
|
|
146
127
|
timeout: timeoutMs,
|
|
147
128
|
maxBuffer: 16 * 1024 * 1024,
|
|
148
129
|
});
|
|
130
|
+
// A spawned command may rewrite files anywhere: drop every memoised tree
|
|
131
|
+
// read, so a read-cache window (`withFileReadCache`, e.g. `shrk quality`'s
|
|
132
|
+
// plane section) never serves a pre-spawn snapshot after it.
|
|
133
|
+
clearFileReadCache();
|
|
149
134
|
if (child.error)
|
|
150
135
|
return { error: `regen failed to start: ${child.error.message}` };
|
|
151
136
|
if (child.status !== 0) {
|
|
152
137
|
const tail = String(child.stderr ?? '').trim().split('\n').slice(-3).join(' | ');
|
|
153
138
|
return { error: `regen exited ${child.status ?? 'null'}${tail ? ` — ${tail}` : ''}` };
|
|
154
139
|
}
|
|
155
|
-
const tree =
|
|
156
|
-
if (tree.size === 0) {
|
|
140
|
+
const tree = readRegenTree(tmp);
|
|
141
|
+
if (tree.files.size === 0 && tree.unread.length === 0) {
|
|
157
142
|
return { error: 'regen wrote no files into {TMP} — the command probably ignores the output path' };
|
|
158
143
|
}
|
|
159
|
-
|
|
144
|
+
// ONE alignment for the read and the unread outputs, against the committed
|
|
145
|
+
// slice AND the committed files the reader could not read, so an over-cap
|
|
146
|
+
// output keys onto the same committed path a read one would.
|
|
147
|
+
const keys = alignKeys([...tree.files.keys(), ...tree.unread.map((u) => u.path)], [...committed.keys(), ...unreadCommitted]);
|
|
148
|
+
const files = new Map();
|
|
149
|
+
for (const [p, content] of tree.files)
|
|
150
|
+
files.set(keys.get(p) ?? p, content);
|
|
151
|
+
return { files, unread: tree.unread.map((u) => ({ ...u, path: keys.get(u.path) ?? u.path })) };
|
|
160
152
|
}
|
|
161
153
|
finally {
|
|
162
154
|
rmSync(tmp, { recursive: true, force: true });
|
|
@@ -182,20 +174,100 @@ function regenUnitsOf(rule, scan) {
|
|
|
182
174
|
// glob is not compared against output no generator produces.
|
|
183
175
|
return [{ label: rule.id, command: rule.regen, committed: scan.checkable, timeoutMs: fallback }];
|
|
184
176
|
}
|
|
185
|
-
export function evaluateGeneratedRule(cwd,
|
|
177
|
+
export function evaluateGeneratedRule(cwd, authored, excludeDirs, headersOnly) {
|
|
178
|
+
// The engine entry normalises idempotently (round 13): a loaded rule comes
|
|
179
|
+
// back as the SAME object; a hand-built `{ pattern, expectEmpty }` entry is a
|
|
180
|
+
// glob plus a marker; a malformed entry is a misconfigured rule, never a crash.
|
|
181
|
+
const normalized = normalizeRuleList(authored, 'generatedGlob');
|
|
182
|
+
if (!normalized.ok) {
|
|
183
|
+
return {
|
|
184
|
+
rule: authored,
|
|
185
|
+
status: 'error',
|
|
186
|
+
committedCount: 0,
|
|
187
|
+
provenance: [],
|
|
188
|
+
driftChecked: false,
|
|
189
|
+
handMaintained: [],
|
|
190
|
+
writers: [],
|
|
191
|
+
error: `generatedGlob ${normalized.error.message}`,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
const rule = normalized.value;
|
|
186
195
|
const scan = scanGeneratedFiles(cwd, rule, excludeDirs);
|
|
187
196
|
const severity = rule.severity ?? 'error';
|
|
188
|
-
|
|
189
|
-
|
|
197
|
+
// A matched file the reader could not read (over the read cap) was never
|
|
198
|
+
// header-checked or compared. It rides on every outcome below, so the
|
|
199
|
+
// coverage names it; it is left out of the drift diff, where its absence
|
|
200
|
+
// from the committed side would otherwise read as `only-regenerated`.
|
|
201
|
+
const unreadScope = readScopeHasUnread(scan.readScope) ? { readScope: scan.readScope } : {};
|
|
202
|
+
const unreadPaths = new Set(scan.readScope.unread.map((u) => u.path));
|
|
203
|
+
// `generatedGlob` settled with its `expectEmpty` markers (round 13) — needed
|
|
204
|
+
// when a unit is marked (its acceptance / went-live line rides on the
|
|
205
|
+
// outcome) or when the rule matched nothing (what the empty rule is decided
|
|
206
|
+
// from). An unmarked rule that matched files pays nothing extra.
|
|
207
|
+
const live = (rule.expectEmptyUnits?.length ?? 0) > 0 || scan.generated.size === 0
|
|
208
|
+
? readGlobListLiveness(cwd, 'generatedGlob', rule.generatedGlob, rule.expectEmptyUnits ?? [], {
|
|
209
|
+
subject: rule.id,
|
|
210
|
+
excludeDirs: new Set(excludeDirs),
|
|
211
|
+
})
|
|
212
|
+
: undefined;
|
|
213
|
+
const nonLive = live?.liveness.units.filter((u) => u.state !== UnitLivenessState.Live) ?? [];
|
|
214
|
+
const unitFields = {
|
|
215
|
+
...(live?.liveness.acceptance !== undefined ? { unitAcceptance: live.liveness.acceptance } : {}),
|
|
216
|
+
...(live !== undefined &&
|
|
217
|
+
live.liveness.dead.length + live.liveness.intendedEmpty.length + live.liveness.wentLive.length > 0
|
|
218
|
+
? { units: unitStateLists(live.liveness) }
|
|
219
|
+
: {}),
|
|
220
|
+
...(nonLive.length > 0 ? { unitLiveness: nonLive } : {}),
|
|
221
|
+
};
|
|
222
|
+
if (scan.generated.size === 0 && unreadPaths.size > 0) {
|
|
223
|
+
// Every committed file it matched is unread: not "0 files matched", so
|
|
224
|
+
// PARTIAL, never failOnEmpty's failure and never a pass.
|
|
190
225
|
return {
|
|
191
226
|
rule,
|
|
192
|
-
status:
|
|
227
|
+
status: 'passed',
|
|
193
228
|
committedCount: 0,
|
|
194
229
|
provenance: [],
|
|
195
230
|
driftChecked: false,
|
|
196
231
|
handMaintained: [],
|
|
197
232
|
writers: [],
|
|
198
|
-
|
|
233
|
+
...unreadScope,
|
|
234
|
+
...unitFields,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
if (scan.generated.size === 0 && live !== undefined) {
|
|
238
|
+
// THE rule-emptiness settle (round 13, `settleRuleEmptiness`): every
|
|
239
|
+
// inclusion glob of `generatedGlob` marked `expectEmpty` and no file
|
|
240
|
+
// matched → a generated tree that does not exist yet, accepted and printed.
|
|
241
|
+
// Anything else is the loud skip (failOnEmpty's 1, else 2).
|
|
242
|
+
const noFiles = `0 files matched generatedGlob (${rule.generatedGlob.join(', ')})`;
|
|
243
|
+
const emptiness = settleRuleEmptiness({
|
|
244
|
+
subject: rule.id,
|
|
245
|
+
unitLabel: 'generated files',
|
|
246
|
+
filesMatched: 0,
|
|
247
|
+
unitsMatched: 0,
|
|
248
|
+
unread: false,
|
|
249
|
+
liveness: live.liveness,
|
|
250
|
+
primaryLists: ['generatedGlob'],
|
|
251
|
+
failOnEmpty: failsWhenEmpty(rule),
|
|
252
|
+
noFilesReason: noFiles,
|
|
253
|
+
noUnitsReason: noFiles,
|
|
254
|
+
});
|
|
255
|
+
const empty = {
|
|
256
|
+
rule,
|
|
257
|
+
committedCount: 0,
|
|
258
|
+
provenance: [],
|
|
259
|
+
driftChecked: false,
|
|
260
|
+
handMaintained: [],
|
|
261
|
+
writers: [],
|
|
262
|
+
...unitFields,
|
|
263
|
+
};
|
|
264
|
+
if (emptiness.state === RuleEmptiness.IntendedEmpty && emptiness.coverage !== undefined) {
|
|
265
|
+
return { ...empty, status: 'passed', emptyCoverage: emptiness.coverage };
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
...empty,
|
|
269
|
+
status: emptiness.fails ? 'failed' : 'skipped',
|
|
270
|
+
skipReason: emptiness.skipReason ?? noFiles,
|
|
199
271
|
};
|
|
200
272
|
}
|
|
201
273
|
// Headers + byte checks run over the CHECKABLE set — a hand-maintained bless
|
|
@@ -214,26 +286,45 @@ export function evaluateGeneratedRule(cwd, rule, excludeDirs, headersOnly) {
|
|
|
214
286
|
handMaintained: scan.handMaintained,
|
|
215
287
|
writers: [],
|
|
216
288
|
error: headers.error,
|
|
289
|
+
...unreadScope,
|
|
217
290
|
};
|
|
218
291
|
}
|
|
219
292
|
const units = headersOnly ? [] : regenUnitsOf(rule, scan);
|
|
220
293
|
const differences = [];
|
|
221
294
|
const writers = [];
|
|
222
295
|
const errors = [];
|
|
296
|
+
// Regen outputs the reader did not read (over the regen cap) that key onto a
|
|
297
|
+
// COMMITTED file: never byte-compared, so they join the rule's read scope
|
|
298
|
+
// and its coverage names them. Never dropped.
|
|
299
|
+
const uncomparable = [];
|
|
223
300
|
let committedCompared = 0;
|
|
224
301
|
let regeneratedCount = 0;
|
|
225
302
|
for (const unit of units) {
|
|
226
|
-
const regen = runOneRegen(cwd, unit.command, unit.committed, unit.timeoutMs);
|
|
303
|
+
const regen = runOneRegen(cwd, unit.command, unit.committed, unit.timeoutMs, [...unreadPaths]);
|
|
227
304
|
if (regen.error) {
|
|
228
305
|
errors.push(units.length > 1 ? `${unit.label}: ${regen.error}` : regen.error);
|
|
229
306
|
writers.push({ label: unit.label, committedCount: unit.committed.size, differences: 0, error: regen.error });
|
|
230
307
|
continue;
|
|
231
308
|
}
|
|
309
|
+
const regenUnread = regen.unread ?? [];
|
|
310
|
+
const keyedOnCommitted = (p) => unit.committed.has(p) || unreadPaths.has(p);
|
|
311
|
+
const unitUncomparable = regenUnread.filter((u) => keyedOnCommitted(u.path));
|
|
312
|
+
uncomparable.push(...unitUncomparable);
|
|
313
|
+
const skip = new Set([...unreadPaths, ...unitUncomparable.map((u) => u.path)]);
|
|
232
314
|
const diff = compareGeneratedTrees(unit.committed, regen.files, rule.compare ?? 'bytes');
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
315
|
+
// A path whose committed or regenerated side was never read cannot be
|
|
316
|
+
// compared: it is in the coverage gap, not the diff. An unread output
|
|
317
|
+
// keyed onto NOTHING committed is drift its path alone proves.
|
|
318
|
+
const compared = [
|
|
319
|
+
...diff.differences.filter((d) => !skip.has(d.file)),
|
|
320
|
+
...regenUnread
|
|
321
|
+
.filter((u) => !keyedOnCommitted(u.path))
|
|
322
|
+
.map((u) => ({ file: u.path, kind: 'only-regenerated' })),
|
|
323
|
+
];
|
|
324
|
+
differences.push(...compared);
|
|
325
|
+
committedCompared += diff.committedCount - unitUncomparable.filter((u) => unit.committed.has(u.path)).length;
|
|
326
|
+
regeneratedCount += diff.regeneratedCount + regenUnread.length;
|
|
327
|
+
writers.push({ label: unit.label, committedCount: unit.committed.size, differences: compared.length });
|
|
237
328
|
}
|
|
238
329
|
const ranAnyWriter = units.length > 0 && errors.length < units.length;
|
|
239
330
|
const treeDiff = ranAnyWriter
|
|
@@ -249,6 +340,17 @@ export function evaluateGeneratedRule(cwd, rule, excludeDirs, headersOnly) {
|
|
|
249
340
|
: drifted || (hardFindings.length > 0 && severity === 'error')
|
|
250
341
|
? 'failed'
|
|
251
342
|
: 'passed';
|
|
343
|
+
// The rule's read scope: the committed files the reader could not read, plus
|
|
344
|
+
// each uncomparable regen output keyed onto a READ committed file (read, but
|
|
345
|
+
// never compared, so it moves from examined to unexamined).
|
|
346
|
+
const extra = new Map();
|
|
347
|
+
for (const u of uncomparable)
|
|
348
|
+
if (!unreadPaths.has(u.path) && !extra.has(u.path))
|
|
349
|
+
extra.set(u.path, u);
|
|
350
|
+
const finalScope = {
|
|
351
|
+
read: scan.readScope.read - extra.size,
|
|
352
|
+
unread: [...scan.readScope.unread, ...extra.values()].sort((a, b) => a.path.localeCompare(b.path)),
|
|
353
|
+
};
|
|
252
354
|
return {
|
|
253
355
|
rule,
|
|
254
356
|
status,
|
|
@@ -259,8 +361,53 @@ export function evaluateGeneratedRule(cwd, rule, excludeDirs, headersOnly) {
|
|
|
259
361
|
driftChecked: units.length > 0 && errors.length === 0,
|
|
260
362
|
handMaintained: scan.handMaintained,
|
|
261
363
|
writers: writers.length > 1 ? writers : [],
|
|
364
|
+
...(readScopeHasUnread(finalScope) ? { readScope: finalScope } : {}),
|
|
365
|
+
// The acceptance of a marked planned glob and the unit lines ride on a
|
|
366
|
+
// connected rule too (round 13), not only on an empty one.
|
|
367
|
+
...unitFields,
|
|
262
368
|
};
|
|
263
369
|
}
|
|
370
|
+
/**
|
|
371
|
+
* What one generated-artifact rule examined — shared by `generated check` and
|
|
372
|
+
* the aggregate (`gates check`, `quality`), so both report the same scope.
|
|
373
|
+
*
|
|
374
|
+
* `driftSkipped` is the aggregate's `--no-spawn` case: headers and
|
|
375
|
+
* classification ran, the regen diff did not, so no file's CONTENTS were
|
|
376
|
+
* verified. (`generated check --headers-only` is a narrowing the caller asked
|
|
377
|
+
* for, not a gap.)
|
|
378
|
+
*/
|
|
379
|
+
export function generatedCoverage(o, driftSkipped = false) {
|
|
380
|
+
// An intended-empty rule (round 13: every `generatedGlob` inclusion glob
|
|
381
|
+
// marked `expectEmpty`, no file matched) is covered by its acceptance — the
|
|
382
|
+
// settle's own record, printed at exit 0.
|
|
383
|
+
if (o.emptyCoverage !== undefined)
|
|
384
|
+
return o.emptyCoverage;
|
|
385
|
+
// A file in scope the reader could not read folds in through the one rule
|
|
386
|
+
// every plane uses (`readScopeCoverage`): `examined N of M files`, named.
|
|
387
|
+
return readScopeCoverage(planeGeneratedCoverage(o, driftSkipped), o.readScope);
|
|
388
|
+
}
|
|
389
|
+
function planeGeneratedCoverage(o, driftSkipped) {
|
|
390
|
+
if (o.skipReason !== undefined) {
|
|
391
|
+
return { unit: 'generated files', expected: 0, examined: 0, reason: o.skipReason };
|
|
392
|
+
}
|
|
393
|
+
if (o.status === 'error') {
|
|
394
|
+
return {
|
|
395
|
+
unit: 'generated files',
|
|
396
|
+
expected: o.committedCount,
|
|
397
|
+
examined: 0,
|
|
398
|
+
reason: 'the rule could not be evaluated',
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
if (driftSkipped) {
|
|
402
|
+
return {
|
|
403
|
+
unit: 'generated files',
|
|
404
|
+
expected: o.committedCount,
|
|
405
|
+
examined: 0,
|
|
406
|
+
reason: 'regen drift check skipped by --no-spawn',
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
return { unit: 'generated files', expected: o.committedCount, examined: o.committedCount };
|
|
410
|
+
}
|
|
264
411
|
export function generatedHintFor(rule) {
|
|
265
412
|
return (rule.hint ??
|
|
266
413
|
(rule.regen || rule.sources
|
|
@@ -284,7 +431,9 @@ function outcomeJson(o) {
|
|
|
284
431
|
hint: generatedHintFor(o.rule),
|
|
285
432
|
};
|
|
286
433
|
}
|
|
287
|
-
async function prepare(args
|
|
434
|
+
async function prepare(args,
|
|
435
|
+
/** `rejectedKnown`: a verdict verb may select a rejected rule by `--id` (its errored row). */
|
|
436
|
+
opts = {}) {
|
|
288
437
|
const cwd = resolveCwd(args);
|
|
289
438
|
const json = flagBool(args, 'json');
|
|
290
439
|
const loaded = await loadRules(cwd);
|
|
@@ -297,15 +446,20 @@ async function prepare(args) {
|
|
|
297
446
|
}
|
|
298
447
|
const id = flagString(args, 'id');
|
|
299
448
|
let rules = loaded.value.rules;
|
|
449
|
+
let rejected = loaded.value.rejected;
|
|
300
450
|
if (id) {
|
|
301
451
|
const wanted = id.split(',').map((s) => s.trim()).filter(Boolean);
|
|
302
|
-
const known = new Set(
|
|
452
|
+
const known = new Set([
|
|
453
|
+
...rules.map((r) => r.id),
|
|
454
|
+
...(opts.rejectedKnown === true ? rejected.map((r) => r.id) : []),
|
|
455
|
+
]);
|
|
303
456
|
const unknown = wanted.filter((w) => !known.has(w));
|
|
304
457
|
if (unknown.length > 0) {
|
|
305
458
|
process.stderr.write(`Unknown generated-artifact id(s): ${unknown.join(', ')}. Declared: ${[...known].join(', ') || '(none)'}\n`);
|
|
306
459
|
return { ok: false, code: ExitCode.UsageError };
|
|
307
460
|
}
|
|
308
461
|
rules = rules.filter((r) => wanted.includes(r.id));
|
|
462
|
+
rejected = rejected.filter((r) => wanted.includes(r.id));
|
|
309
463
|
}
|
|
310
464
|
return {
|
|
311
465
|
ok: true,
|
|
@@ -314,18 +468,47 @@ async function prepare(args) {
|
|
|
314
468
|
all: loaded.value.rules,
|
|
315
469
|
excludeDirs: loaded.value.excludeDirs,
|
|
316
470
|
planeDiagnostics: loaded.value.planeDiagnostics,
|
|
471
|
+
rejected,
|
|
317
472
|
};
|
|
318
473
|
}
|
|
319
|
-
|
|
474
|
+
/**
|
|
475
|
+
* The "no rules declared" landing. A VERDICT verb passes its name so its JSON
|
|
476
|
+
* still carries the settled `gate` envelope — nothing declared is `2`.
|
|
477
|
+
*/
|
|
478
|
+
function writeNoRules(json, verb) {
|
|
479
|
+
// A VERDICT verb settles first and renders second, in text AND JSON: nothing
|
|
480
|
+
// declared proposes 0 and the run coverage (expected 0) settles it to 2. The
|
|
481
|
+
// exit comes from the envelope — never a hard-coded code — so text, JSON and
|
|
482
|
+
// gate.exit cannot disagree. List / explain subverbs stay informational.
|
|
483
|
+
const gate = verb !== undefined
|
|
484
|
+
? buildGateEnvelope(verb, ExitCode.VerifiedPass, [], {
|
|
485
|
+
unit: 'generated-artifact rules',
|
|
486
|
+
expected: 0,
|
|
487
|
+
examined: 0,
|
|
488
|
+
reason: 'no generatedArtifacts[] declared',
|
|
489
|
+
})
|
|
490
|
+
: undefined;
|
|
491
|
+
const exit = gate?.exit ?? ExitCode.NotVerified;
|
|
320
492
|
if (json) {
|
|
321
|
-
process.stdout.write(asJson({
|
|
322
|
-
|
|
493
|
+
process.stdout.write(asJson({
|
|
494
|
+
schema: SCHEMA,
|
|
495
|
+
results: [],
|
|
496
|
+
evaluated: 0,
|
|
497
|
+
verdict: gate ? planeVerdictForExit(gate.exit) : 'not-verified',
|
|
498
|
+
...(gate ? { exitCode: gate.exit, gate } : {}),
|
|
499
|
+
}) + '\n');
|
|
500
|
+
return exit;
|
|
323
501
|
}
|
|
324
502
|
process.stdout.write(header('Generated artifacts'));
|
|
325
503
|
process.stdout.write(' No generated-artifact rules declared. Add `generatedArtifacts[]` to\n' +
|
|
326
504
|
' sharkcraft.config.ts to catch hand-edited generated files and missing\n' +
|
|
327
505
|
' "do not edit" headers (see docs/generated-drift.md).\n');
|
|
328
|
-
|
|
506
|
+
if (gate) {
|
|
507
|
+
const line = verdictLine(gate, 'Nothing declared — accepted.');
|
|
508
|
+
if (line)
|
|
509
|
+
process.stdout.write(`\n${line}\n`);
|
|
510
|
+
}
|
|
511
|
+
return exit;
|
|
329
512
|
}
|
|
330
513
|
export const generatedListCommand = {
|
|
331
514
|
name: 'list',
|
|
@@ -337,11 +520,13 @@ export const generatedListCommand = {
|
|
|
337
520
|
if (!prep.ok)
|
|
338
521
|
return prep.code;
|
|
339
522
|
const json = flagBool(args, 'json');
|
|
340
|
-
if (prep.all.length === 0)
|
|
523
|
+
if (prep.all.length === 0 && prep.rejected.length === 0)
|
|
341
524
|
return writeNoRules(json);
|
|
342
525
|
if (json) {
|
|
343
526
|
process.stdout.write(asJson({
|
|
344
527
|
schema: SCHEMA,
|
|
528
|
+
// Declared by a pack, refused by the merge seam: never checked.
|
|
529
|
+
rejected: prep.rejected.map((r) => ({ id: r.id, error: r.error ?? null })),
|
|
345
530
|
rules: prep.all.map((r) => ({
|
|
346
531
|
id: r.id,
|
|
347
532
|
description: r.description ?? null,
|
|
@@ -352,7 +537,9 @@ export const generatedListCommand = {
|
|
|
352
537
|
handMaintainedMarker: r.handMaintainedMarker ?? null,
|
|
353
538
|
compare: r.compare ?? 'bytes',
|
|
354
539
|
provenanceHeader: r.provenanceHeader ?? null,
|
|
355
|
-
|
|
540
|
+
// The EFFECTIVE failOnEmpty (round 13) — THE authority, never the
|
|
541
|
+
// raw field: an error rule fails on empty by default.
|
|
542
|
+
failOnEmpty: failsWhenEmpty(r),
|
|
356
543
|
})),
|
|
357
544
|
diagnostics: prep.planeDiagnostics,
|
|
358
545
|
}) + '\n');
|
|
@@ -384,6 +571,10 @@ export const generatedListCommand = {
|
|
|
384
571
|
if (r.description)
|
|
385
572
|
process.stdout.write(` ${r.description}\n`);
|
|
386
573
|
}
|
|
574
|
+
if (prep.rejected.length > 0) {
|
|
575
|
+
process.stdout.write(`\n rejected at the pack-plane merge seam — never checked (${prep.rejected.length}):\n`);
|
|
576
|
+
writeRejected(prep.rejected);
|
|
577
|
+
}
|
|
387
578
|
for (const d of prep.planeDiagnostics)
|
|
388
579
|
process.stdout.write(` ! ${d}\n`);
|
|
389
580
|
return ExitCode.VerifiedPass;
|
|
@@ -395,63 +586,92 @@ export const generatedCheckCommand = {
|
|
|
395
586
|
usage: 'shrk generated check [--id <ids>] [--headers-only] [--json]',
|
|
396
587
|
booleanFlags: new Set(['json', 'headers-only']),
|
|
397
588
|
async run(args) {
|
|
398
|
-
const prep = await prepare(args);
|
|
589
|
+
const prep = await prepare(args, { rejectedKnown: true });
|
|
399
590
|
if (!prep.ok)
|
|
400
591
|
return prep.code;
|
|
401
592
|
const json = flagBool(args, 'json');
|
|
402
593
|
const headersOnly = flagBool(args, 'headers-only');
|
|
403
|
-
|
|
404
|
-
|
|
594
|
+
// A pack rule the merge seam rejected is a declared rule that was never
|
|
595
|
+
// checked — an errored row, exit 1 (round 12 review, R12-X1).
|
|
596
|
+
const rejected = prep.rejected;
|
|
597
|
+
if (prep.rules.length === 0 && rejected.length === 0)
|
|
598
|
+
return writeNoRules(json, 'generated check');
|
|
405
599
|
const outcomes = prep.rules.map((r) => evaluateGeneratedRule(prep.cwd, r, prep.excludeDirs, headersOnly));
|
|
406
600
|
const failed = outcomes.filter((o) => o.status === 'failed' || (o.status === 'error' && (o.rule.severity ?? 'error') === 'error'));
|
|
407
601
|
const evaluated = outcomes.filter((o) => o.status !== 'skipped').length;
|
|
408
602
|
const skippedCount = outcomes.length - evaluated;
|
|
409
|
-
const
|
|
603
|
+
const proposed = failed.length > 0 || rejected.length > 0
|
|
410
604
|
? ExitCode.Failure
|
|
411
605
|
: evaluated === 0 || skippedCount > 0
|
|
412
606
|
? ExitCode.NotVerified
|
|
413
607
|
: ExitCode.VerifiedPass;
|
|
608
|
+
// Settle first, render second: one envelope for text AND JSON, and the ✓
|
|
609
|
+
// line is printed only from the settled verdict. `--headers-only` is a
|
|
610
|
+
// narrowing the caller asked for, so it is not a coverage gap here.
|
|
611
|
+
const unexamined = [
|
|
612
|
+
...outcomes.filter((o) => o.status === 'skipped' || o.status === 'error').map((o) => o.rule.id),
|
|
613
|
+
...rejected.map((r) => r.id),
|
|
614
|
+
];
|
|
615
|
+
const env = buildGateEnvelope('generated check', proposed, [...outcomes.map((o) => ({
|
|
616
|
+
id: o.rule.id,
|
|
617
|
+
type: 'generated',
|
|
618
|
+
status: o.status,
|
|
619
|
+
severity: o.rule.severity ?? 'error',
|
|
620
|
+
counts: {
|
|
621
|
+
files: o.committedCount,
|
|
622
|
+
differences: o.treeDiff?.differences.length ?? 0,
|
|
623
|
+
provenance: o.provenance.length,
|
|
624
|
+
},
|
|
625
|
+
violations: [
|
|
626
|
+
...(o.treeDiff?.differences ?? []).map((d) => ({
|
|
627
|
+
id: d.file,
|
|
628
|
+
file: d.file,
|
|
629
|
+
message: d.kind,
|
|
630
|
+
hint: generatedHintFor(o.rule),
|
|
631
|
+
})),
|
|
632
|
+
...o.provenance.map((f) => ({
|
|
633
|
+
id: f.file,
|
|
634
|
+
file: f.file,
|
|
635
|
+
message: f.message,
|
|
636
|
+
hint: generatedHintFor(o.rule),
|
|
637
|
+
})),
|
|
638
|
+
],
|
|
639
|
+
...(o.skipReason ? { skipReason: o.skipReason } : {}),
|
|
640
|
+
...(o.error ? { error: o.error } : {}),
|
|
641
|
+
coverage: generatedCoverage(o),
|
|
642
|
+
// The rule's `expectEmpty` acceptance and unit lines (round 13), folded
|
|
643
|
+
// into the envelope's one settle.
|
|
644
|
+
...(o.unitAcceptance !== undefined ? { unitAcceptance: o.unitAcceptance } : {}),
|
|
645
|
+
...(o.units !== undefined ? { units: o.units } : {}),
|
|
646
|
+
})), ...rejected], {
|
|
647
|
+
unit: 'generated-artifact rules',
|
|
648
|
+
expected: outcomes.length + rejected.length,
|
|
649
|
+
examined: outcomes.length + rejected.length - unexamined.length,
|
|
650
|
+
...(unexamined.length > 0 ? { unexamined, reason: 'checked nothing or could not run' } : {}),
|
|
651
|
+
});
|
|
652
|
+
const exit = env.exit;
|
|
414
653
|
if (json) {
|
|
415
654
|
process.stdout.write(asJson({
|
|
416
655
|
schema: SCHEMA,
|
|
417
656
|
headersOnly,
|
|
418
657
|
results: outcomes.map(outcomeJson),
|
|
658
|
+
// Pack rules the merge seam refused — errored rows in `gate.rules`.
|
|
659
|
+
rejected: rejected.map((r) => ({ id: r.id, error: r.error ?? null })),
|
|
419
660
|
evaluated,
|
|
420
661
|
skipped: outcomes.filter((o) => o.status === 'skipped').length,
|
|
421
|
-
verdict:
|
|
662
|
+
verdict: exit === ExitCode.Failure ? 'errors' : exit === ExitCode.VerifiedPass ? 'pass' : 'not-verified',
|
|
422
663
|
diagnostics: prep.planeDiagnostics,
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
type: 'generated',
|
|
426
|
-
status: o.status,
|
|
427
|
-
severity: o.rule.severity ?? 'error',
|
|
428
|
-
counts: {
|
|
429
|
-
files: o.committedCount,
|
|
430
|
-
differences: o.treeDiff?.differences.length ?? 0,
|
|
431
|
-
provenance: o.provenance.length,
|
|
432
|
-
},
|
|
433
|
-
violations: [
|
|
434
|
-
...(o.treeDiff?.differences ?? []).map((d) => ({
|
|
435
|
-
id: d.file,
|
|
436
|
-
file: d.file,
|
|
437
|
-
message: d.kind,
|
|
438
|
-
hint: generatedHintFor(o.rule),
|
|
439
|
-
})),
|
|
440
|
-
...o.provenance.map((f) => ({
|
|
441
|
-
id: f.file,
|
|
442
|
-
file: f.file,
|
|
443
|
-
message: f.message,
|
|
444
|
-
hint: generatedHintFor(o.rule),
|
|
445
|
-
})),
|
|
446
|
-
],
|
|
447
|
-
...(o.skipReason ? { skipReason: o.skipReason } : {}),
|
|
448
|
-
...(o.error ? { error: o.error } : {}),
|
|
449
|
-
}))),
|
|
664
|
+
exitCode: exit,
|
|
665
|
+
gate: env,
|
|
450
666
|
}) + '\n');
|
|
451
667
|
return exit;
|
|
452
668
|
}
|
|
453
669
|
process.stdout.write(header('Generated-artifact drift'));
|
|
454
|
-
|
|
670
|
+
// Round 13 (K6): the printed count is the envelope's (`gate.evaluated`), which
|
|
671
|
+
// never counts a rule accepted as intended-empty — it is named apart.
|
|
672
|
+
process.stdout.write(kv('evaluated', `${env.evaluated} of ${prep.rules.length + rejected.length}${acceptedEmptyNote(env.acceptedEmpty)}` +
|
|
673
|
+
(rejected.length > 0 ? ` (${rejected.length} rejected at the pack-plane merge seam — NOT evaluated)` : '')) + '\n');
|
|
674
|
+
writeRejected(rejected);
|
|
455
675
|
if (headersOnly)
|
|
456
676
|
process.stdout.write(kv('scope', 'headers only — no regen was run') + '\n');
|
|
457
677
|
for (const o of outcomes) {
|
|
@@ -463,8 +683,22 @@ export const generatedCheckCommand = {
|
|
|
463
683
|
process.stdout.write(` ! ${o.rule.id} ${o.error}\n`);
|
|
464
684
|
continue;
|
|
465
685
|
}
|
|
686
|
+
// A rule that matched nothing and FAILS on it (failOnEmpty) produced no
|
|
687
|
+
// diff and no regen output — its skip reason is the only true line. It is
|
|
688
|
+
// never rendered as drift with a bless hint (round 13).
|
|
689
|
+
if (o.status === 'failed' && o.skipReason !== undefined) {
|
|
690
|
+
process.stdout.write(` ✗ ${o.rule.id} FAILED — ${o.skipReason}\n`);
|
|
691
|
+
process.stdout.write(` → ${formatEmptyRuleAdvice({ fails: true })}\n`);
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
466
694
|
const diffs = o.treeDiff?.differences ?? [];
|
|
467
|
-
|
|
695
|
+
// Rendered from the SETTLED rule: a pass over part of its scope is
|
|
696
|
+
// `partial`, never a ✓ (the keystone emitter pattern, step 5).
|
|
697
|
+
const settledRule = env.rules.find((x) => x.id === o.rule.id);
|
|
698
|
+
if (o.status === 'passed' && settledRule?.status === 'partial') {
|
|
699
|
+
process.stdout.write(` ~ ${o.rule.id} PARTIAL — ${settledRule.shortfall ?? 'part of its scope was not examined'}\n`);
|
|
700
|
+
}
|
|
701
|
+
else if (o.status === 'passed') {
|
|
468
702
|
const exempt = o.handMaintained.length > 0 ? `, ${o.handMaintained.length} hand-maintained` : '';
|
|
469
703
|
process.stdout.write(` ✓ ${o.rule.id} (${o.committedCount} files${exempt}` +
|
|
470
704
|
`${o.driftChecked ? ', byte-identical to a fresh regen' : ', headers only'})\n`);
|
|
@@ -499,18 +733,118 @@ export const generatedCheckCommand = {
|
|
|
499
733
|
}
|
|
500
734
|
for (const d of prep.planeDiagnostics)
|
|
501
735
|
process.stdout.write(` ! ${d}\n`);
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
process.stdout.write(
|
|
736
|
+
// THE empty-rule advice (round 13) for a SOFT skip: a failing one is
|
|
737
|
+
// advised inline (above); a rule that matched nothing and does not fail on
|
|
738
|
+
// it was told nothing.
|
|
739
|
+
for (const a of emptyRuleAdviceLines(outcomes.filter((o) => o.status === 'skipped' && o.skipReason !== undefined).map(() => ({ fails: false })))) {
|
|
740
|
+
process.stdout.write(` ${a}.\n`);
|
|
507
741
|
}
|
|
742
|
+
// THE shared unit-state block (round 13, K2): a dead `generatedGlob` of a
|
|
743
|
+
// rule that still matched, and a LOCAL expectEmpty marker whose target
|
|
744
|
+
// appeared, withhold the ✓ (exit unchanged); a pack marker is INFO.
|
|
745
|
+
const unitNotes = unitStateNotes(outcomes.map((o) => ({
|
|
746
|
+
id: o.rule.id,
|
|
747
|
+
...(o.unitLiveness !== undefined ? { unitLiveness: o.unitLiveness } : {}),
|
|
748
|
+
reportedEmpty: o.status === 'skipped' || o.skipReason !== undefined,
|
|
749
|
+
})));
|
|
750
|
+
process.stdout.write(unitNotes.text);
|
|
751
|
+
const line = verdictLine(env, qualifyCleanForUnits('Every generated artifact matches its source. ✓', unitNotes), proposed === ExitCode.NotVerified && evaluated === 0
|
|
752
|
+
? 'Nothing was checked — this is NOT a pass. Every rule matched 0 files.'
|
|
753
|
+
: undefined);
|
|
754
|
+
if (line)
|
|
755
|
+
process.stdout.write(`\n${line}\n`);
|
|
508
756
|
return exit;
|
|
509
757
|
},
|
|
510
758
|
};
|
|
759
|
+
const EMPTY_WRITES = { written: [], unchanged: [], created: [], notPlaced: [], noLongerProduced: [] };
|
|
760
|
+
/** The leading literal directory of a glob (`docs/schemas/*.json` → `docs/schemas`). */
|
|
761
|
+
function globStaticDir(glob) {
|
|
762
|
+
const out = [];
|
|
763
|
+
for (const seg of glob.split('/').slice(0, -1)) {
|
|
764
|
+
if (/[*?[\]{}]/.test(seg))
|
|
765
|
+
break;
|
|
766
|
+
out.push(seg);
|
|
767
|
+
}
|
|
768
|
+
return out.length > 0 ? out.join('/') : undefined;
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Where a regen unit's NEW files belong: the single directory its committed
|
|
772
|
+
* slice lives in, or — for a first generation with nothing committed yet — the
|
|
773
|
+
* single literal directory its INCLUSION globs name. Ambiguous → undefined,
|
|
774
|
+
* and the new file is reported instead of guessed.
|
|
775
|
+
*
|
|
776
|
+
* Only an inclusion glob says where files live: a negation
|
|
777
|
+
* (`!gen/**\/*.hand.ts`) names what is carved OUT, and read raw its leading
|
|
778
|
+
* `!gen` was a second "directory" that made every first generation ambiguous.
|
|
779
|
+
*/
|
|
780
|
+
function sliceTargetDir(committed, globs) {
|
|
781
|
+
const dirs = new Set([...committed.keys()].map((p) => nodePath.posix.dirname(p)));
|
|
782
|
+
if (dirs.size === 1)
|
|
783
|
+
return [...dirs][0];
|
|
784
|
+
if (dirs.size > 1)
|
|
785
|
+
return undefined;
|
|
786
|
+
const statics = new Set(globListWalkGlobs(globs)
|
|
787
|
+
.map(globStaticDir)
|
|
788
|
+
.filter((d) => d !== undefined));
|
|
789
|
+
return statics.size === 1 ? [...statics][0] : undefined;
|
|
790
|
+
}
|
|
791
|
+
function writeOut(cwd, rel, content) {
|
|
792
|
+
const abs = nodePath.join(cwd, rel);
|
|
793
|
+
mkdirSync(nodePath.dirname(abs), { recursive: true });
|
|
794
|
+
writeFileSync(abs, content, 'utf8');
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Write a regen unit's ALIGNED output over the committed tree.
|
|
798
|
+
*
|
|
799
|
+
* The keys come from {@link runOneRegen} — the same alignment `generated check`
|
|
800
|
+
* diffs against — so "which committed file does this output replace" has one
|
|
801
|
+
* answer for both verbs. Output that aligns to nothing is a new file: it is
|
|
802
|
+
* written only where the rule's own glob says generated files live, never at a
|
|
803
|
+
* bare temp-relative path (which is how a flat regen used to land at the repo
|
|
804
|
+
* root). A committed file the regen stopped producing is reported, not
|
|
805
|
+
* deleted: removing tracked files is a decision for a human.
|
|
806
|
+
*/
|
|
807
|
+
function writeAligned(cwd, rule, scan, committed, files) {
|
|
808
|
+
const written = [];
|
|
809
|
+
const unchanged = [];
|
|
810
|
+
const created = [];
|
|
811
|
+
const notPlaced = [];
|
|
812
|
+
const blessed = new Set(scan.handMaintained);
|
|
813
|
+
const targetDir = sliceTargetDir(committed, rule.generatedGlob);
|
|
814
|
+
for (const [key, content] of files) {
|
|
815
|
+
if (committed.has(key)) {
|
|
816
|
+
if (committed.get(key) === content)
|
|
817
|
+
unchanged.push(key);
|
|
818
|
+
else {
|
|
819
|
+
writeOut(cwd, key, content);
|
|
820
|
+
written.push(key);
|
|
821
|
+
}
|
|
822
|
+
continue;
|
|
823
|
+
}
|
|
824
|
+
const candidates = [key, ...(targetDir ? [nodePath.posix.join(targetDir, key)] : [])];
|
|
825
|
+
// A new output is placed only where `generatedGlob` SELECTS it — never on
|
|
826
|
+
// a path the list excludes, which the next check would not cover.
|
|
827
|
+
const dest = candidates.find((c) => globListSelects(c, rule.generatedGlob) && !blessed.has(c));
|
|
828
|
+
if (!dest) {
|
|
829
|
+
notPlaced.push(key);
|
|
830
|
+
continue;
|
|
831
|
+
}
|
|
832
|
+
writeOut(cwd, dest, content);
|
|
833
|
+
created.push(dest);
|
|
834
|
+
}
|
|
835
|
+
const produced = new Set([...written, ...unchanged]);
|
|
836
|
+
const noLongerProduced = [...committed.keys()].filter((p) => !produced.has(p)).sort();
|
|
837
|
+
return {
|
|
838
|
+
written: written.sort(),
|
|
839
|
+
unchanged: unchanged.sort(),
|
|
840
|
+
created: created.sort(),
|
|
841
|
+
notPlaced: notPlaced.sort(),
|
|
842
|
+
noLongerProduced,
|
|
843
|
+
};
|
|
844
|
+
}
|
|
511
845
|
export const generatedUpdateCommand = {
|
|
512
846
|
name: 'update',
|
|
513
|
-
description: '
|
|
847
|
+
description: 'The bless step after an intentional source change: regenerate into a temp dir with the SAME regen + path alignment `generated check` uses, then write the aligned files over their committed paths. Writes files; never deletes a committed file.',
|
|
514
848
|
usage: 'shrk generated update [--id <ids>] [--json]',
|
|
515
849
|
booleanFlags: new Set(['json']),
|
|
516
850
|
async run(args) {
|
|
@@ -522,56 +856,74 @@ export const generatedUpdateCommand = {
|
|
|
522
856
|
return writeNoRules(json);
|
|
523
857
|
const results = [];
|
|
524
858
|
for (const rule of prep.rules) {
|
|
525
|
-
//
|
|
526
|
-
//
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
: [];
|
|
536
|
-
if (commands.length === 0) {
|
|
537
|
-
results.push({ id: rule.id, ran: false, error: 'header-only rule — nothing to regenerate' });
|
|
859
|
+
// `{TMP}` means the same thing here as in `check`. Update used to
|
|
860
|
+
// substitute the project root instead, so a flat regen such as
|
|
861
|
+
// `schemas emit --out {TMP}` wrote every file at the repo ROOT while
|
|
862
|
+
// `check` compared against docs/schemas/ — two answers to "where does
|
|
863
|
+
// the output land". Regenerating through runOneRegen makes it one.
|
|
864
|
+
// A multi-writer rule blesses EVERY writer's slice, never just one.
|
|
865
|
+
const scan = scanGeneratedFiles(prep.cwd, rule, prep.excludeDirs);
|
|
866
|
+
const units = regenUnitsOf(rule, scan);
|
|
867
|
+
if (units.length === 0) {
|
|
868
|
+
results.push({ id: rule.id, ran: false, error: 'header-only rule — nothing to regenerate', ...EMPTY_WRITES });
|
|
538
869
|
continue;
|
|
539
870
|
}
|
|
540
|
-
for (const
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
shell: true,
|
|
546
|
-
encoding: 'utf8',
|
|
547
|
-
timeout: timeoutMs,
|
|
548
|
-
maxBuffer: 16 * 1024 * 1024,
|
|
549
|
-
stdio: json ? 'pipe' : 'inherit',
|
|
550
|
-
});
|
|
551
|
-
if (child.error) {
|
|
552
|
-
results.push({ id: label, ran: false, error: child.error.message });
|
|
553
|
-
}
|
|
554
|
-
else if (child.status !== 0) {
|
|
555
|
-
results.push({ id: label, ran: true, error: `exited ${child.status ?? 'null'}` });
|
|
556
|
-
}
|
|
557
|
-
else {
|
|
558
|
-
results.push({ id: label, ran: true });
|
|
871
|
+
for (const unit of units) {
|
|
872
|
+
const regen = runOneRegen(prep.cwd, unit.command, unit.committed, unit.timeoutMs);
|
|
873
|
+
if (regen.error || !regen.files) {
|
|
874
|
+
results.push({ id: unit.label, ran: true, error: regen.error ?? 'regen produced no files', ...EMPTY_WRITES });
|
|
875
|
+
continue;
|
|
559
876
|
}
|
|
877
|
+
// An output the reader did not read (over the regen cap) is never
|
|
878
|
+
// written: it is listed, and the bless is reported incomplete (1).
|
|
879
|
+
// Its committed counterpart is not "no longer produced".
|
|
880
|
+
const unreadKeys = new Set((regen.unread ?? []).map((u) => u.path));
|
|
881
|
+
const aligned = writeAligned(prep.cwd, rule, scan, unit.committed, regen.files);
|
|
882
|
+
results.push({
|
|
883
|
+
id: unit.label,
|
|
884
|
+
ran: true,
|
|
885
|
+
...aligned,
|
|
886
|
+
noLongerProduced: aligned.noLongerProduced.filter((p) => !unreadKeys.has(p)),
|
|
887
|
+
...(unreadKeys.size > 0 ? { unreadOutputs: [...unreadKeys].sort() } : {}),
|
|
888
|
+
});
|
|
560
889
|
}
|
|
561
890
|
}
|
|
562
891
|
const failed = results.filter((r) => r.error !== undefined);
|
|
892
|
+
// The bless is incomplete when output could not be placed, a committed
|
|
893
|
+
// file is no longer produced, or an output was too large to read:
|
|
894
|
+
// `generated check` would still fail, so exiting 0 here would claim a sync
|
|
895
|
+
// that did not happen.
|
|
896
|
+
const outOfSync = results.filter((r) => r.notPlaced.length > 0 || r.noLongerProduced.length > 0 || (r.unreadOutputs?.length ?? 0) > 0);
|
|
897
|
+
const exit = failed.length > 0 || outOfSync.length > 0 ? ExitCode.Failure : ExitCode.VerifiedPass;
|
|
563
898
|
if (json) {
|
|
564
|
-
process.stdout.write(asJson({ schema: SCHEMA, results }) + '\n');
|
|
565
|
-
return
|
|
899
|
+
process.stdout.write(asJson({ schema: SCHEMA, inSync: failed.length === 0 && outOfSync.length === 0, results, exitCode: exit }) + '\n');
|
|
900
|
+
return exit;
|
|
566
901
|
}
|
|
567
902
|
process.stdout.write(header('Generated update'));
|
|
568
903
|
for (const r of results) {
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
904
|
+
if (r.error) {
|
|
905
|
+
process.stdout.write(` ! ${r.id} — ${r.error}\n`);
|
|
906
|
+
continue;
|
|
907
|
+
}
|
|
908
|
+
process.stdout.write(` ✓ ${r.id} — ${r.written.length} rewritten, ${r.created.length} new, ${r.unchanged.length} unchanged\n`);
|
|
909
|
+
for (const f of r.written)
|
|
910
|
+
process.stdout.write(` ~ ${f}\n`);
|
|
911
|
+
for (const f of r.created)
|
|
912
|
+
process.stdout.write(` + ${f}\n`);
|
|
913
|
+
for (const f of r.notPlaced) {
|
|
914
|
+
process.stdout.write(` ! not written: ${f} — maps to no committed file and lies outside generatedGlob\n`);
|
|
915
|
+
}
|
|
916
|
+
for (const f of r.unreadOutputs ?? []) {
|
|
917
|
+
process.stdout.write(` ! not written: ${f} — over the regen read cap, so it was never read\n`);
|
|
918
|
+
}
|
|
919
|
+
for (const f of r.noLongerProduced) {
|
|
920
|
+
process.stdout.write(` ? no longer produced (left in place — delete it if that is intended): ${f}\n`);
|
|
921
|
+
}
|
|
573
922
|
}
|
|
574
|
-
|
|
923
|
+
process.stdout.write(exit === ExitCode.VerifiedPass
|
|
924
|
+
? '\nRegenerated. Review `git diff` before committing.\n'
|
|
925
|
+
: '\nNot fully blessed — `generated check` will still report the files above.\n');
|
|
926
|
+
return exit;
|
|
575
927
|
},
|
|
576
928
|
};
|
|
577
929
|
export const generatedExplainCommand = {
|