@shrkcrft/cli 0.1.0-alpha.29 → 0.1.0-alpha.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/asset-preview/apply-asset-preview.js +1 -1
- package/dist/authoring/authoring-kit.d.ts +9 -1
- package/dist/authoring/authoring-kit.d.ts.map +1 -1
- package/dist/authoring/authoring-kit.js +26 -13
- package/dist/bootstrap/unlinked-workspace-dependency.d.ts +11 -0
- package/dist/bootstrap/unlinked-workspace-dependency.d.ts.map +1 -0
- package/dist/bootstrap/unlinked-workspace-dependency.js +99 -0
- package/dist/command-registry.d.ts +100 -1
- package/dist/command-registry.d.ts.map +1 -1
- package/dist/command-registry.js +130 -3
- package/dist/commands/api-diff.command.d.ts.map +1 -1
- package/dist/commands/api-diff.command.js +12 -0
- package/dist/commands/apply.command.d.ts.map +1 -1
- package/dist/commands/apply.command.js +3 -0
- package/dist/commands/arch.command.d.ts.map +1 -1
- package/dist/commands/arch.command.js +26 -7
- package/dist/commands/architecture.command.d.ts.map +1 -1
- package/dist/commands/architecture.command.js +8 -2
- package/dist/commands/baseline.command.d.ts +76 -0
- package/dist/commands/baseline.command.d.ts.map +1 -1
- package/dist/commands/baseline.command.js +599 -75
- package/dist/commands/biome.command.d.ts.map +1 -1
- package/dist/commands/biome.command.js +20 -0
- package/dist/commands/boundaries.command.d.ts +9 -1
- package/dist/commands/boundaries.command.d.ts.map +1 -1
- package/dist/commands/boundaries.command.js +88 -20
- package/dist/commands/bundle.command.d.ts.map +1 -1
- package/dist/commands/bundle.command.js +57 -11
- package/dist/commands/cache-align.command.d.ts.map +1 -1
- package/dist/commands/cache-align.command.js +3 -1
- package/dist/commands/changelog-data.d.ts.map +1 -1
- package/dist/commands/changelog-data.js +164 -0
- package/dist/commands/changes.command.d.ts.map +1 -1
- package/dist/commands/changes.command.js +15 -0
- package/dist/commands/check.command.d.ts.map +1 -1
- package/dist/commands/check.command.js +1260 -324
- package/dist/commands/checks.command.d.ts.map +1 -1
- package/dist/commands/checks.command.js +188 -40
- package/dist/commands/ci.command.d.ts.map +1 -1
- package/dist/commands/ci.command.js +35 -2
- package/dist/commands/code-intel.command.d.ts.map +1 -1
- package/dist/commands/code-intel.command.js +8 -1
- package/dist/commands/command-catalog.d.ts +23 -1
- package/dist/commands/command-catalog.d.ts.map +1 -1
- package/dist/commands/command-catalog.js +152 -35
- package/dist/commands/commands.command.d.ts +21 -3
- package/dist/commands/commands.command.d.ts.map +1 -1
- package/dist/commands/commands.command.js +172 -27
- package/dist/commands/compress.command.d.ts.map +1 -1
- package/dist/commands/compress.command.js +4 -0
- package/dist/commands/constructs.command.d.ts.map +1 -1
- package/dist/commands/constructs.command.js +50 -12
- package/dist/commands/context.command.d.ts.map +1 -1
- package/dist/commands/context.command.js +40 -5
- package/dist/commands/contract-templates.command.d.ts.map +1 -1
- package/dist/commands/contract-templates.command.js +9 -1
- package/dist/commands/contract.command.d.ts.map +1 -1
- package/dist/commands/contract.command.js +35 -0
- package/dist/commands/conventions.command.d.ts.map +1 -1
- package/dist/commands/conventions.command.js +189 -29
- package/dist/commands/coverage.command.d.ts.map +1 -1
- package/dist/commands/coverage.command.js +9 -0
- package/dist/commands/daily.commands.d.ts +0 -1
- package/dist/commands/daily.commands.d.ts.map +1 -1
- package/dist/commands/daily.commands.js +12 -76
- package/dist/commands/dashboard.command.d.ts.map +1 -1
- package/dist/commands/dashboard.command.js +10 -0
- package/dist/commands/dev.command.d.ts.map +1 -1
- package/dist/commands/dev.command.js +45 -2
- package/dist/commands/diagnostics.command.d.ts +0 -2
- package/dist/commands/diagnostics.command.d.ts.map +1 -1
- package/dist/commands/diagnostics.command.js +5 -78
- package/dist/commands/diff-check.command.d.ts +6 -4
- package/dist/commands/diff-check.command.d.ts.map +1 -1
- package/dist/commands/diff-check.command.js +190 -103
- package/dist/commands/docs-references.command.d.ts +20 -0
- package/dist/commands/docs-references.command.d.ts.map +1 -0
- package/dist/commands/docs-references.command.js +520 -0
- package/dist/commands/doctor.command.d.ts.map +1 -1
- package/dist/commands/doctor.command.js +60 -5
- package/dist/commands/drift.command.d.ts.map +1 -1
- package/dist/commands/drift.command.js +17 -4
- package/dist/commands/eslint.command.d.ts.map +1 -1
- package/dist/commands/eslint.command.js +20 -0
- package/dist/commands/export.command.d.ts.map +1 -1
- package/dist/commands/export.command.js +23 -1
- package/dist/commands/feedback-dispatch.command.d.ts.map +1 -1
- package/dist/commands/feedback-dispatch.command.js +29 -0
- package/dist/commands/feedback.command.d.ts.map +1 -1
- package/dist/commands/feedback.command.js +6 -1
- package/dist/commands/finish.command.d.ts.map +1 -1
- package/dist/commands/finish.command.js +32 -8
- package/dist/commands/fix.command.d.ts.map +1 -1
- package/dist/commands/fix.command.js +19 -0
- package/dist/commands/gate.command.d.ts +6 -1
- package/dist/commands/gate.command.d.ts.map +1 -1
- package/dist/commands/gate.command.js +50 -124
- package/dist/commands/gates.command.d.ts +54 -0
- package/dist/commands/gates.command.d.ts.map +1 -1
- package/dist/commands/gates.command.js +1314 -64
- package/dist/commands/gen.command.d.ts.map +1 -1
- package/dist/commands/gen.command.js +4 -0
- package/dist/commands/generated.command.d.ts +62 -0
- package/dist/commands/generated.command.d.ts.map +1 -1
- package/dist/commands/generated.command.js +639 -156
- package/dist/commands/graph-code-subverbs.d.ts +15 -0
- package/dist/commands/graph-code-subverbs.d.ts.map +1 -1
- package/dist/commands/graph-code-subverbs.js +203 -25
- package/dist/commands/graph.command.d.ts.map +1 -1
- package/dist/commands/graph.command.js +79 -37
- package/dist/commands/help.command.d.ts +30 -6
- package/dist/commands/help.command.d.ts.map +1 -1
- package/dist/commands/help.command.js +323 -199
- package/dist/commands/helper.command.d.ts +1 -0
- package/dist/commands/helper.command.d.ts.map +1 -1
- package/dist/commands/helper.command.js +195 -56
- package/dist/commands/impact.command.d.ts.map +1 -1
- package/dist/commands/impact.command.js +115 -12
- package/dist/commands/infer.command.d.ts.map +1 -1
- package/dist/commands/infer.command.js +20 -45
- package/dist/commands/ingest.command.d.ts +0 -1
- package/dist/commands/ingest.command.d.ts.map +1 -1
- package/dist/commands/ingest.command.js +25 -41
- package/dist/commands/knowledge-author.command.d.ts +0 -3
- package/dist/commands/knowledge-author.command.d.ts.map +1 -1
- package/dist/commands/knowledge-author.command.js +10 -23
- package/dist/commands/knowledge.command.d.ts.map +1 -1
- package/dist/commands/knowledge.command.js +469 -129
- package/dist/commands/languages.command.d.ts.map +1 -1
- package/dist/commands/languages.command.js +27 -0
- package/dist/commands/lint.command.d.ts.map +1 -1
- package/dist/commands/lint.command.js +3 -0
- package/dist/commands/mcp.command.d.ts +18 -0
- package/dist/commands/mcp.command.d.ts.map +1 -1
- package/dist/commands/mcp.command.js +33 -8
- package/dist/commands/onboard.command.d.ts.map +1 -1
- package/dist/commands/onboard.command.js +20 -0
- package/dist/commands/owners.command.d.ts.map +1 -1
- package/dist/commands/owners.command.js +14 -2
- package/dist/commands/packs-new.d.ts +13 -1
- package/dist/commands/packs-new.d.ts.map +1 -1
- package/dist/commands/packs-new.js +500 -335
- package/dist/commands/packs.command.d.ts.map +1 -1
- package/dist/commands/packs.command.js +378 -36
- package/dist/commands/paths.command.d.ts.map +1 -1
- package/dist/commands/paths.command.js +9 -1
- package/dist/commands/pipelines.command.d.ts.map +1 -1
- package/dist/commands/pipelines.command.js +7 -1
- package/dist/commands/plan-check.command.d.ts.map +1 -1
- package/dist/commands/plan-check.command.js +3 -0
- package/dist/commands/plan.command.d.ts.map +1 -1
- package/dist/commands/plan.command.js +3 -0
- package/dist/commands/playbooks.command.d.ts.map +1 -1
- package/dist/commands/playbooks.command.js +7 -2
- package/dist/commands/policy-lint.command.d.ts +19 -2
- package/dist/commands/policy-lint.command.d.ts.map +1 -1
- package/dist/commands/policy-lint.command.js +224 -65
- package/dist/commands/policy.command.d.ts.map +1 -1
- package/dist/commands/policy.command.js +6 -1
- package/dist/commands/pr.command.d.ts.map +1 -1
- package/dist/commands/pr.command.js +14 -0
- package/dist/commands/presets.command.d.ts.map +1 -1
- package/dist/commands/presets.command.js +12 -27
- package/dist/commands/profiles.command.d.ts.map +1 -1
- package/dist/commands/profiles.command.js +94 -26
- package/dist/commands/provenance.command.js +1 -1
- package/dist/commands/quality.command.d.ts.map +1 -1
- package/dist/commands/quality.command.js +129 -33
- package/dist/commands/recommend.command.d.ts +20 -4
- package/dist/commands/recommend.command.d.ts.map +1 -1
- package/dist/commands/recommend.command.js +172 -278
- package/dist/commands/registrations.command.d.ts.map +1 -1
- package/dist/commands/registrations.command.js +138 -13
- package/dist/commands/registry-lifecycle-run.d.ts +14 -0
- package/dist/commands/registry-lifecycle-run.d.ts.map +1 -0
- package/dist/commands/registry-lifecycle-run.js +336 -0
- package/dist/commands/registry.command.d.ts.map +1 -1
- package/dist/commands/registry.command.js +235 -62
- package/dist/commands/release.command.d.ts.map +1 -1
- package/dist/commands/release.command.js +99 -20
- package/dist/commands/report.command.d.ts.map +1 -1
- package/dist/commands/report.command.js +38 -2
- package/dist/commands/reuse-coverage.command.d.ts +23 -0
- package/dist/commands/reuse-coverage.command.d.ts.map +1 -0
- package/dist/commands/reuse-coverage.command.js +536 -0
- package/dist/commands/reuse.command.d.ts +3 -18
- package/dist/commands/reuse.command.d.ts.map +1 -1
- package/dist/commands/reuse.command.js +395 -244
- package/dist/commands/review.command.d.ts.map +1 -1
- package/dist/commands/review.command.js +33 -1
- package/dist/commands/rounds.command.d.ts.map +1 -1
- package/dist/commands/rounds.command.js +5 -0
- package/dist/commands/safety.command.d.ts.map +1 -1
- package/dist/commands/safety.command.js +9 -0
- package/dist/commands/scaffolds.command.d.ts.map +1 -1
- package/dist/commands/scaffolds.command.js +97 -25
- package/dist/commands/search.command.d.ts +0 -8
- package/dist/commands/search.command.d.ts.map +1 -1
- package/dist/commands/search.command.js +136 -28
- package/dist/commands/self-config-xrefs.command.d.ts +24 -0
- package/dist/commands/self-config-xrefs.command.d.ts.map +1 -0
- package/dist/commands/self-config-xrefs.command.js +170 -0
- package/dist/commands/self-config.command.d.ts.map +1 -1
- package/dist/commands/self-config.command.js +261 -65
- package/dist/commands/smart-context.command.d.ts.map +1 -1
- package/dist/commands/smart-context.command.js +39 -3
- package/dist/commands/spec.command.d.ts.map +1 -1
- package/dist/commands/spec.command.js +3 -0
- package/dist/commands/stats.command.d.ts.map +1 -1
- package/dist/commands/stats.command.js +3 -0
- package/dist/commands/surface.command.d.ts +6 -1
- package/dist/commands/surface.command.d.ts.map +1 -1
- package/dist/commands/surface.command.js +256 -54
- package/dist/commands/task-context.command.js +1 -1
- package/dist/commands/task.command.d.ts.map +1 -1
- package/dist/commands/task.command.js +22 -1
- package/dist/commands/templates.command.d.ts +4 -0
- package/dist/commands/templates.command.d.ts.map +1 -1
- package/dist/commands/templates.command.js +109 -22
- package/dist/commands/test.command.d.ts.map +1 -1
- package/dist/commands/test.command.js +161 -33
- package/dist/commands/tests.command.d.ts.map +1 -1
- package/dist/commands/tests.command.js +29 -3
- package/dist/commands/trace.command.d.ts.map +1 -1
- package/dist/commands/trace.command.js +29 -4
- package/dist/commands/watch.command.d.ts.map +1 -1
- package/dist/commands/watch.command.js +3 -0
- package/dist/commands/why.command.d.ts.map +1 -1
- package/dist/commands/why.command.js +3 -0
- package/dist/commands/wiring.command.d.ts +18 -4
- package/dist/commands/wiring.command.d.ts.map +1 -1
- package/dist/commands/wiring.command.js +362 -74
- package/dist/dashboard/code-intelligence-data.js +4 -4
- package/dist/diff/deleted-orphan-coverage.d.ts +32 -0
- package/dist/diff/deleted-orphan-coverage.d.ts.map +1 -0
- package/dist/diff/deleted-orphan-coverage.js +107 -0
- package/dist/diff/deleted-orphan-scope-notes.d.ts +23 -0
- package/dist/diff/deleted-orphan-scope-notes.d.ts.map +1 -0
- package/dist/diff/deleted-orphan-scope-notes.js +49 -0
- package/dist/diff/deleted-orphans.d.ts +32 -0
- package/dist/diff/deleted-orphans.d.ts.map +1 -1
- package/dist/diff/deleted-orphans.js +42 -2
- package/dist/dispatch/closest-match.d.ts +15 -0
- package/dist/dispatch/closest-match.d.ts.map +1 -0
- package/dist/dispatch/closest-match.js +47 -0
- package/dist/dispatch/global-flags.d.ts +94 -0
- package/dist/dispatch/global-flags.d.ts.map +1 -0
- package/dist/dispatch/global-flags.js +123 -0
- package/dist/dispatch/guard-invocation-input.d.ts +24 -0
- package/dist/dispatch/guard-invocation-input.d.ts.map +1 -0
- package/dist/dispatch/guard-invocation-input.js +1 -0
- package/dist/dispatch/guard-invocation.d.ts +33 -0
- package/dist/dispatch/guard-invocation.d.ts.map +1 -0
- package/dist/dispatch/guard-invocation.js +183 -0
- package/dist/dispatch/help-intercept.d.ts +22 -0
- package/dist/dispatch/help-intercept.d.ts.map +1 -0
- package/dist/dispatch/help-intercept.js +42 -0
- package/dist/dispatch/i-declared-invocation.d.ts +17 -0
- package/dist/dispatch/i-declared-invocation.d.ts.map +1 -0
- package/dist/dispatch/i-declared-invocation.js +1 -0
- package/dist/dispatch/i-unknown-flag-refusal-input.d.ts +28 -0
- package/dist/dispatch/i-unknown-flag-refusal-input.d.ts.map +1 -0
- package/dist/dispatch/i-unknown-flag-refusal-input.js +1 -0
- package/dist/dispatch/invocation-rejection-kind.d.ts +15 -0
- package/dist/dispatch/invocation-rejection-kind.d.ts.map +1 -0
- package/dist/dispatch/invocation-rejection-kind.js +15 -0
- package/dist/dispatch/invocation-rejection.d.ts +18 -0
- package/dist/dispatch/invocation-rejection.d.ts.map +1 -0
- package/dist/dispatch/invocation-rejection.js +1 -0
- package/dist/dispatch/judge-invocation.d.ts +24 -0
- package/dist/dispatch/judge-invocation.d.ts.map +1 -0
- package/dist/dispatch/judge-invocation.js +50 -0
- package/dist/dispatch/positional-mode.d.ts +18 -0
- package/dist/dispatch/positional-mode.d.ts.map +1 -0
- package/dist/dispatch/positional-mode.js +18 -0
- package/dist/dispatch/read-tracking-map.d.ts +28 -0
- package/dist/dispatch/read-tracking-map.d.ts.map +1 -0
- package/dist/dispatch/read-tracking-map.js +58 -0
- package/dist/dispatch/refuse-sibling-valve-flags.d.ts +30 -0
- package/dist/dispatch/refuse-sibling-valve-flags.d.ts.map +1 -0
- package/dist/dispatch/refuse-sibling-valve-flags.js +65 -0
- package/dist/dispatch/subverb-spec.d.ts +26 -0
- package/dist/dispatch/subverb-spec.d.ts.map +1 -0
- package/dist/dispatch/subverb-spec.js +1 -0
- package/dist/dispatch/undocumented-flag-reads.d.ts +22 -0
- package/dist/dispatch/undocumented-flag-reads.d.ts.map +1 -0
- package/dist/dispatch/undocumented-flag-reads.js +283 -0
- package/dist/dispatch/unknown-flag-refusal-mode.d.ts +13 -0
- package/dist/dispatch/unknown-flag-refusal-mode.d.ts.map +1 -0
- package/dist/dispatch/unknown-flag-refusal-mode.js +13 -0
- package/dist/dispatch/unknown-flag-refusal.d.ts +22 -0
- package/dist/dispatch/unknown-flag-refusal.d.ts.map +1 -0
- package/dist/dispatch/unknown-flag-refusal.js +57 -0
- package/dist/dispatch/unread-flags.d.ts +152 -0
- package/dist/dispatch/unread-flags.d.ts.map +1 -0
- package/dist/dispatch/unread-flags.js +295 -0
- package/dist/dispatch/verdict-valve-flag.d.ts +21 -0
- package/dist/dispatch/verdict-valve-flag.d.ts.map +1 -0
- package/dist/dispatch/verdict-valve-flag.js +21 -0
- package/dist/dispatch/walk-declared-subverbs.d.ts +14 -0
- package/dist/dispatch/walk-declared-subverbs.d.ts.map +1 -0
- package/dist/dispatch/walk-declared-subverbs.js +28 -0
- package/dist/exit-codes.d.ts +41 -3
- package/dist/exit-codes.d.ts.map +1 -1
- package/dist/exit-codes.js +153 -14
- package/dist/finish/run-finish.d.ts +72 -5
- package/dist/finish/run-finish.d.ts.map +1 -1
- package/dist/finish/run-finish.js +453 -104
- package/dist/gates/accepted-empty-note.d.ts +10 -0
- package/dist/gates/accepted-empty-note.d.ts.map +1 -0
- package/dist/gates/accepted-empty-note.js +11 -0
- package/dist/gates/allow-empty.d.ts +25 -0
- package/dist/gates/allow-empty.d.ts.map +1 -0
- package/dist/gates/allow-empty.js +27 -0
- package/dist/gates/asset-doctor-failing-units.d.ts +16 -0
- package/dist/gates/asset-doctor-failing-units.d.ts.map +1 -0
- package/dist/gates/asset-doctor-failing-units.js +14 -0
- package/dist/gates/asset-doctor-failure-line.d.ts +16 -0
- package/dist/gates/asset-doctor-failure-line.d.ts.map +1 -0
- package/dist/gates/asset-doctor-failure-line.js +28 -0
- package/dist/gates/empty-rule-advice-lines.d.ts +15 -0
- package/dist/gates/empty-rule-advice-lines.d.ts.map +1 -0
- package/dist/gates/empty-rule-advice-lines.js +20 -0
- package/dist/gates/gate-envelope.d.ts +85 -8
- package/dist/gates/gate-envelope.d.ts.map +1 -1
- package/dist/gates/gate-envelope.js +56 -7
- package/dist/gates/gate-rule-globs.d.ts +67 -0
- package/dist/gates/gate-rule-globs.d.ts.map +1 -0
- package/dist/gates/gate-rule-globs.js +139 -0
- package/dist/gates/gate-rule-view.d.ts +25 -6
- package/dist/gates/gate-rule-view.d.ts.map +1 -1
- package/dist/gates/gate-rule-view.js +105 -77
- package/dist/gates/i-coverage-dead-glob.d.ts +19 -0
- package/dist/gates/i-coverage-dead-glob.d.ts.map +1 -0
- package/dist/gates/i-coverage-dead-glob.js +1 -0
- package/dist/gates/i-coverage-negation.d.ts +13 -0
- package/dist/gates/i-coverage-negation.d.ts.map +1 -0
- package/dist/gates/i-coverage-negation.js +1 -0
- package/dist/gates/i-registration-query-settle-input.d.ts +18 -0
- package/dist/gates/i-registration-query-settle-input.d.ts.map +1 -0
- package/dist/gates/i-registration-query-settle-input.js +1 -0
- package/dist/gates/i-unit-state-note-row.d.ts +16 -0
- package/dist/gates/i-unit-state-note-row.d.ts.map +1 -0
- package/dist/gates/i-unit-state-note-row.js +1 -0
- package/dist/gates/i-unit-state-notes.d.ts +32 -0
- package/dist/gates/i-unit-state-notes.d.ts.map +1 -0
- package/dist/gates/i-unit-state-notes.js +1 -0
- package/dist/gates/incomplete-registry-inventory.d.ts +19 -0
- package/dist/gates/incomplete-registry-inventory.d.ts.map +1 -0
- package/dist/gates/incomplete-registry-inventory.js +63 -0
- package/dist/gates/measure-registration-roles.d.ts +10 -0
- package/dist/gates/measure-registration-roles.d.ts.map +1 -0
- package/dist/gates/measure-registration-roles.js +9 -0
- package/dist/gates/plane-verdict.d.ts +14 -0
- package/dist/gates/plane-verdict.d.ts.map +1 -0
- package/dist/gates/plane-verdict.js +22 -0
- package/dist/gates/qualify-clean-for-units.d.ts +12 -0
- package/dist/gates/qualify-clean-for-units.d.ts.map +1 -0
- package/dist/gates/qualify-clean-for-units.js +18 -0
- package/dist/gates/registration-graph-verdict.d.ts +34 -0
- package/dist/gates/registration-graph-verdict.d.ts.map +1 -0
- package/dist/gates/registration-graph-verdict.js +70 -0
- package/dist/gates/registration-roles.d.ts +9 -0
- package/dist/gates/registration-roles.d.ts.map +1 -0
- package/dist/gates/registration-roles.js +1 -0
- package/dist/gates/registry-liveness.d.ts +11 -0
- package/dist/gates/registry-liveness.d.ts.map +1 -0
- package/dist/gates/registry-liveness.js +13 -0
- package/dist/gates/rule-coverage.d.ts +225 -4
- package/dist/gates/rule-coverage.d.ts.map +1 -1
- package/dist/gates/rule-coverage.js +610 -43
- package/dist/gates/run-gate-planes.d.ts +58 -0
- package/dist/gates/run-gate-planes.d.ts.map +1 -0
- package/dist/gates/run-gate-planes.js +403 -0
- package/dist/gates/scaffold-selftest.d.ts +56 -0
- package/dist/gates/scaffold-selftest.d.ts.map +1 -0
- package/dist/gates/scaffold-selftest.js +119 -0
- package/dist/gates/seam-rejected-rules.d.ts +28 -0
- package/dist/gates/seam-rejected-rules.d.ts.map +1 -0
- package/dist/gates/seam-rejected-rules.js +95 -0
- package/dist/gates/self-test-check.d.ts +29 -0
- package/dist/gates/self-test-check.d.ts.map +1 -0
- package/dist/gates/self-test-check.js +1 -0
- package/dist/gates/self-test-subject.d.ts +28 -0
- package/dist/gates/self-test-subject.d.ts.map +1 -0
- package/dist/gates/self-test-subject.js +1 -0
- package/dist/gates/self-test.d.ts +20 -0
- package/dist/gates/self-test.d.ts.map +1 -0
- package/dist/gates/self-test.js +82 -0
- package/dist/gates/settle-verdict.d.ts +14 -0
- package/dist/gates/settle-verdict.d.ts.map +1 -0
- package/dist/gates/settle-verdict.js +13 -0
- package/dist/gates/settled-verdict.d.ts +8 -0
- package/dist/gates/settled-verdict.d.ts.map +1 -0
- package/dist/gates/settled-verdict.js +1 -0
- package/dist/gates/template-registry-coverage.d.ts +18 -0
- package/dist/gates/template-registry-coverage.d.ts.map +1 -0
- package/dist/gates/template-registry-coverage.js +24 -0
- package/dist/gates/unit-state-notes.d.ts +40 -0
- package/dist/gates/unit-state-notes.d.ts.map +1 -0
- package/dist/gates/unit-state-notes.js +71 -0
- package/dist/gates/verdict-line.d.ts +18 -0
- package/dist/gates/verdict-line.d.ts.map +1 -0
- package/dist/gates/verdict-line.js +40 -0
- package/dist/graph/graph-reuse-lookup.d.ts +10 -0
- package/dist/graph/graph-reuse-lookup.d.ts.map +1 -0
- package/dist/graph/graph-reuse-lookup.js +41 -0
- package/dist/graph/index-behind-hint.d.ts +11 -0
- package/dist/graph/index-behind-hint.d.ts.map +1 -0
- package/dist/graph/index-behind-hint.js +27 -0
- package/dist/knowledge/knowledge-stale-gate-flags.d.ts +3 -0
- package/dist/knowledge/knowledge-stale-gate-flags.d.ts.map +1 -0
- package/dist/knowledge/knowledge-stale-gate-flags.js +1 -0
- package/dist/knowledge/knowledge-stale-gate-input.d.ts +3 -0
- package/dist/knowledge/knowledge-stale-gate-input.d.ts.map +1 -0
- package/dist/knowledge/knowledge-stale-gate-input.js +1 -0
- package/dist/knowledge/knowledge-stale-gate-result.d.ts +3 -0
- package/dist/knowledge/knowledge-stale-gate-result.d.ts.map +1 -0
- package/dist/knowledge/knowledge-stale-gate-result.js +1 -0
- package/dist/knowledge/knowledge-stale-gate.d.ts +10 -0
- package/dist/knowledge/knowledge-stale-gate.d.ts.map +1 -0
- package/dist/knowledge/knowledge-stale-gate.js +9 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +204 -83
- package/dist/output/failure-hints.d.ts.map +1 -1
- package/dist/output/failure-hints.js +11 -9
- package/dist/output/rejected-entries-note.d.ts +57 -0
- package/dist/output/rejected-entries-note.d.ts.map +1 -0
- package/dist/output/rejected-entries-note.js +103 -0
- package/dist/output/stdout-is-pipe.d.ts +13 -0
- package/dist/output/stdout-is-pipe.d.ts.map +1 -0
- package/dist/output/stdout-is-pipe.js +21 -0
- package/dist/packs/stale-build-warning.d.ts +11 -0
- package/dist/packs/stale-build-warning.d.ts.map +1 -0
- package/dist/packs/stale-build-warning.js +23 -0
- package/dist/quality/run-quality.d.ts +123 -0
- package/dist/quality/run-quality.d.ts.map +1 -0
- package/dist/quality/run-quality.js +442 -0
- package/dist/schemas/json-schemas.d.ts +184 -3
- package/dist/schemas/json-schemas.d.ts.map +1 -1
- package/dist/schemas/json-schemas.js +113 -2
- package/dist/shrk.d.ts +3 -0
- package/dist/shrk.d.ts.map +1 -0
- package/dist/shrk.js +30 -0
- package/dist/surface/audience-applicability.d.ts +26 -0
- package/dist/surface/audience-applicability.d.ts.map +1 -0
- package/dist/surface/audience-applicability.js +60 -0
- package/dist/surface/catalog-command-safety.d.ts +18 -0
- package/dist/surface/catalog-command-safety.d.ts.map +1 -0
- package/dist/surface/catalog-command-safety.js +62 -0
- package/dist/surface/cli-command-resolver.d.ts +21 -0
- package/dist/surface/cli-command-resolver.d.ts.map +1 -0
- package/dist/surface/cli-command-resolver.js +87 -0
- package/dist/surface/command-dispatch-kind.d.ts +20 -0
- package/dist/surface/command-dispatch-kind.d.ts.map +1 -0
- package/dist/surface/command-dispatch-kind.js +20 -0
- package/dist/surface/command-index-entry.d.ts +40 -0
- package/dist/surface/command-index-entry.d.ts.map +1 -0
- package/dist/surface/command-index-entry.js +1 -0
- package/dist/surface/command-index.d.ts +77 -0
- package/dist/surface/command-index.d.ts.map +1 -0
- package/dist/surface/command-index.js +323 -0
- package/dist/surface/i-command-index.d.ts +22 -0
- package/dist/surface/i-command-index.d.ts.map +1 -0
- package/dist/surface/i-command-index.js +1 -0
- package/dist/surface/i-command-string-context.d.ts +20 -0
- package/dist/surface/i-command-string-context.d.ts.map +1 -0
- package/dist/surface/i-command-string-context.js +1 -0
- package/dist/surface/i-surface-denial.d.ts +13 -0
- package/dist/surface/i-surface-denial.d.ts.map +1 -0
- package/dist/surface/i-surface-denial.js +1 -0
- package/dist/surface/load-surface-context.d.ts +17 -1
- package/dist/surface/load-surface-context.d.ts.map +1 -1
- package/dist/surface/load-surface-context.js +48 -10
- package/dist/surface/not-enabled-error.d.ts +38 -4
- package/dist/surface/not-enabled-error.d.ts.map +1 -1
- package/dist/surface/not-enabled-error.js +96 -6
- package/dist/surface/profiles.d.ts +7 -0
- package/dist/surface/profiles.d.ts.map +1 -1
- package/dist/surface/resolve-command-string.d.ts +10 -0
- package/dist/surface/resolve-command-string.d.ts.map +1 -0
- package/dist/surface/resolve-command-string.js +572 -0
- package/dist/surface/surface-config-writer.d.ts +7 -2
- package/dist/surface/surface-config-writer.d.ts.map +1 -1
- package/dist/surface/surface-config-writer.js +25 -6
- package/dist/surface/surface-layer.d.ts +11 -0
- package/dist/surface/surface-layer.d.ts.map +1 -0
- package/dist/surface/surface-layer.js +11 -0
- package/dist/surface/surface-refusal-reason.d.ts +16 -0
- package/dist/surface/surface-refusal-reason.d.ts.map +1 -0
- package/dist/surface/surface-refusal-reason.js +16 -0
- package/dist/surface/surface-selector.d.ts +26 -0
- package/dist/surface/surface-selector.d.ts.map +1 -0
- package/dist/surface/surface-selector.js +42 -0
- package/dist/surface/surface-summary.d.ts +61 -18
- package/dist/surface/surface-summary.d.ts.map +1 -1
- package/dist/surface/surface-summary.js +147 -60
- package/dist/surface/tier.d.ts +85 -22
- package/dist/surface/tier.d.ts.map +1 -1
- package/dist/surface/tier.js +132 -30
- package/dist/usage/usage-log.d.ts +6 -1
- package/dist/usage/usage-log.d.ts.map +1 -1
- package/dist/usage/usage-log.js +7 -2
- package/dist/validation/run-validation-loop.d.ts +9 -0
- package/dist/validation/run-validation-loop.d.ts.map +1 -1
- package/dist/validation/run-validation-loop.js +14 -11
- package/dist/validation/typecheck-emitted.d.ts +8 -9
- package/dist/validation/typecheck-emitted.d.ts.map +1 -1
- package/dist/validation/typecheck-emitted.js +17 -97
- package/package.json +34 -34
|
@@ -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,60 +98,184 @@ 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;
|
|
129
110
|
}
|
|
130
|
-
/**
|
|
131
|
-
|
|
111
|
+
/**
|
|
112
|
+
* Run ONE regen command into a fresh temp dir and read the result back, keyed
|
|
113
|
+
* onto the committed paths it corresponds to. Always cleans up.
|
|
114
|
+
*
|
|
115
|
+
* `committed` is the slice this command OWNS, not the whole tree — that is what
|
|
116
|
+
* keeps a multi-writer tree honest: each command's output is aligned against
|
|
117
|
+
* (and later diffed against) only its own files, so writer A's output can never
|
|
118
|
+
* be reported as writer B's stale committed file.
|
|
119
|
+
*/
|
|
120
|
+
function runOneRegen(cwd, command, committed, timeoutMs, unreadCommitted = []) {
|
|
132
121
|
const tmp = mkdtempSync(nodePath.join(nodeOs.tmpdir(), 'shrk-generated-'));
|
|
133
122
|
try {
|
|
134
|
-
const
|
|
135
|
-
const child = spawnSync(command, {
|
|
123
|
+
const child = spawnSync(command.split('{TMP}').join(tmp), {
|
|
136
124
|
cwd,
|
|
137
125
|
shell: true,
|
|
138
126
|
encoding: 'utf8',
|
|
139
|
-
timeout:
|
|
127
|
+
timeout: timeoutMs,
|
|
140
128
|
maxBuffer: 16 * 1024 * 1024,
|
|
141
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();
|
|
142
134
|
if (child.error)
|
|
143
135
|
return { error: `regen failed to start: ${child.error.message}` };
|
|
144
136
|
if (child.status !== 0) {
|
|
145
137
|
const tail = String(child.stderr ?? '').trim().split('\n').slice(-3).join(' | ');
|
|
146
138
|
return { error: `regen exited ${child.status ?? 'null'}${tail ? ` — ${tail}` : ''}` };
|
|
147
139
|
}
|
|
148
|
-
const tree =
|
|
149
|
-
if (tree.size === 0) {
|
|
140
|
+
const tree = readRegenTree(tmp);
|
|
141
|
+
if (tree.files.size === 0 && tree.unread.length === 0) {
|
|
150
142
|
return { error: 'regen wrote no files into {TMP} — the command probably ignores the output path' };
|
|
151
143
|
}
|
|
152
|
-
|
|
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 })) };
|
|
153
152
|
}
|
|
154
153
|
finally {
|
|
155
154
|
rmSync(tmp, { recursive: true, force: true });
|
|
156
155
|
}
|
|
157
156
|
}
|
|
158
|
-
|
|
157
|
+
/**
|
|
158
|
+
* The regen units for a rule: one per `sources[]` writer, or a single unit for
|
|
159
|
+
* the classic `regen` form. A rule declaring neither yields none (header-only).
|
|
160
|
+
*/
|
|
161
|
+
function regenUnitsOf(rule, scan) {
|
|
162
|
+
const fallback = rule.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
163
|
+
if (scan.slices.length > 0) {
|
|
164
|
+
return scan.slices.map((slice) => ({
|
|
165
|
+
label: slice.label,
|
|
166
|
+
command: slice.regen,
|
|
167
|
+
committed: slice.files,
|
|
168
|
+
timeoutMs: slice.timeoutMs ?? fallback,
|
|
169
|
+
}));
|
|
170
|
+
}
|
|
171
|
+
if (rule.regen === undefined)
|
|
172
|
+
return [];
|
|
173
|
+
// Single-writer: the checkable set, so a hand-maintained bless inside the
|
|
174
|
+
// glob is not compared against output no generator produces.
|
|
175
|
+
return [{ label: rule.id, command: rule.regen, committed: scan.checkable, timeoutMs: fallback }];
|
|
176
|
+
}
|
|
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;
|
|
159
195
|
const scan = scanGeneratedFiles(cwd, rule, excludeDirs);
|
|
160
196
|
const severity = rule.severity ?? 'error';
|
|
161
|
-
|
|
162
|
-
|
|
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.
|
|
163
225
|
return {
|
|
164
226
|
rule,
|
|
165
|
-
status:
|
|
227
|
+
status: 'passed',
|
|
166
228
|
committedCount: 0,
|
|
167
229
|
provenance: [],
|
|
168
230
|
driftChecked: false,
|
|
169
|
-
|
|
231
|
+
handMaintained: [],
|
|
232
|
+
writers: [],
|
|
233
|
+
...unreadScope,
|
|
234
|
+
...unitFields,
|
|
170
235
|
};
|
|
171
236
|
}
|
|
172
|
-
|
|
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,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
// Headers + byte checks run over the CHECKABLE set — a hand-maintained bless
|
|
274
|
+
// is excluded from both, which is the whole point of declaring it.
|
|
275
|
+
const headers = checkProvenanceHeaders(rule, scan.checkable, scan.outside, {
|
|
276
|
+
unclassified: scan.unclassified,
|
|
277
|
+
staleHandMaintained: scan.staleHandMaintained,
|
|
278
|
+
});
|
|
173
279
|
if (headers.error) {
|
|
174
280
|
return {
|
|
175
281
|
rule,
|
|
@@ -177,26 +283,74 @@ function evaluateRule(cwd, rule, excludeDirs, headersOnly) {
|
|
|
177
283
|
committedCount: scan.generated.size,
|
|
178
284
|
provenance: [],
|
|
179
285
|
driftChecked: false,
|
|
286
|
+
handMaintained: scan.handMaintained,
|
|
287
|
+
writers: [],
|
|
180
288
|
error: headers.error,
|
|
289
|
+
...unreadScope,
|
|
181
290
|
};
|
|
182
291
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
292
|
+
const units = headersOnly ? [] : regenUnitsOf(rule, scan);
|
|
293
|
+
const differences = [];
|
|
294
|
+
const writers = [];
|
|
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 = [];
|
|
300
|
+
let committedCompared = 0;
|
|
301
|
+
let regeneratedCount = 0;
|
|
302
|
+
for (const unit of units) {
|
|
303
|
+
const regen = runOneRegen(cwd, unit.command, unit.committed, unit.timeoutMs, [...unreadPaths]);
|
|
304
|
+
if (regen.error) {
|
|
305
|
+
errors.push(units.length > 1 ? `${unit.label}: ${regen.error}` : regen.error);
|
|
306
|
+
writers.push({ label: unit.label, committedCount: unit.committed.size, differences: 0, error: regen.error });
|
|
307
|
+
continue;
|
|
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)]);
|
|
314
|
+
const diff = compareGeneratedTrees(unit.committed, regen.files, rule.compare ?? 'bytes');
|
|
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 });
|
|
192
328
|
}
|
|
329
|
+
const ranAnyWriter = units.length > 0 && errors.length < units.length;
|
|
330
|
+
const treeDiff = ranAnyWriter
|
|
331
|
+
? { differences, committedCount: committedCompared, regeneratedCount }
|
|
332
|
+
: undefined;
|
|
333
|
+
const error = errors.length > 0 ? errors.join(' · ') : undefined;
|
|
193
334
|
const hardFindings = headers.findings.filter((f) => f.severity === 'error');
|
|
194
|
-
const drifted =
|
|
335
|
+
const drifted = differences.length > 0;
|
|
336
|
+
// A writer that failed to RUN proved nothing, so the rule is in error even if
|
|
337
|
+
// its siblings were clean — a partial regen must never read as a full pass.
|
|
195
338
|
const status = error !== undefined
|
|
196
339
|
? 'error'
|
|
197
340
|
: drifted || (hardFindings.length > 0 && severity === 'error')
|
|
198
341
|
? 'failed'
|
|
199
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
|
+
};
|
|
200
354
|
return {
|
|
201
355
|
rule,
|
|
202
356
|
status,
|
|
@@ -204,12 +358,59 @@ function evaluateRule(cwd, rule, excludeDirs, headersOnly) {
|
|
|
204
358
|
...(treeDiff ? { treeDiff } : {}),
|
|
205
359
|
provenance: headers.findings,
|
|
206
360
|
...(error ? { error } : {}),
|
|
207
|
-
driftChecked:
|
|
361
|
+
driftChecked: units.length > 0 && errors.length === 0,
|
|
362
|
+
handMaintained: scan.handMaintained,
|
|
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,
|
|
208
368
|
};
|
|
209
369
|
}
|
|
210
|
-
|
|
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
|
+
}
|
|
411
|
+
export function generatedHintFor(rule) {
|
|
211
412
|
return (rule.hint ??
|
|
212
|
-
(rule.regen
|
|
413
|
+
(rule.regen || rule.sources
|
|
213
414
|
? `regenerate with \`shrk generated update --id ${rule.id}\` and commit the result`
|
|
214
415
|
: 'add the provenance header to the generated file, or move it out of the generated glob'));
|
|
215
416
|
}
|
|
@@ -221,14 +422,18 @@ function outcomeJson(o) {
|
|
|
221
422
|
severity: o.rule.severity ?? 'error',
|
|
222
423
|
committedCount: o.committedCount,
|
|
223
424
|
driftChecked: o.driftChecked,
|
|
425
|
+
handMaintained: o.handMaintained,
|
|
426
|
+
...(o.writers.length > 0 ? { writers: o.writers } : {}),
|
|
224
427
|
...(o.treeDiff ? { differences: o.treeDiff.differences, regeneratedCount: o.treeDiff.regeneratedCount } : {}),
|
|
225
428
|
provenance: o.provenance,
|
|
226
429
|
...(o.error ? { error: o.error } : {}),
|
|
227
430
|
...(o.skipReason ? { skipReason: o.skipReason } : {}),
|
|
228
|
-
hint:
|
|
431
|
+
hint: generatedHintFor(o.rule),
|
|
229
432
|
};
|
|
230
433
|
}
|
|
231
|
-
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 = {}) {
|
|
232
437
|
const cwd = resolveCwd(args);
|
|
233
438
|
const json = flagBool(args, 'json');
|
|
234
439
|
const loaded = await loadRules(cwd);
|
|
@@ -241,15 +446,20 @@ async function prepare(args) {
|
|
|
241
446
|
}
|
|
242
447
|
const id = flagString(args, 'id');
|
|
243
448
|
let rules = loaded.value.rules;
|
|
449
|
+
let rejected = loaded.value.rejected;
|
|
244
450
|
if (id) {
|
|
245
451
|
const wanted = id.split(',').map((s) => s.trim()).filter(Boolean);
|
|
246
|
-
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
|
+
]);
|
|
247
456
|
const unknown = wanted.filter((w) => !known.has(w));
|
|
248
457
|
if (unknown.length > 0) {
|
|
249
458
|
process.stderr.write(`Unknown generated-artifact id(s): ${unknown.join(', ')}. Declared: ${[...known].join(', ') || '(none)'}\n`);
|
|
250
459
|
return { ok: false, code: ExitCode.UsageError };
|
|
251
460
|
}
|
|
252
461
|
rules = rules.filter((r) => wanted.includes(r.id));
|
|
462
|
+
rejected = rejected.filter((r) => wanted.includes(r.id));
|
|
253
463
|
}
|
|
254
464
|
return {
|
|
255
465
|
ok: true,
|
|
@@ -258,18 +468,47 @@ async function prepare(args) {
|
|
|
258
468
|
all: loaded.value.rules,
|
|
259
469
|
excludeDirs: loaded.value.excludeDirs,
|
|
260
470
|
planeDiagnostics: loaded.value.planeDiagnostics,
|
|
471
|
+
rejected,
|
|
261
472
|
};
|
|
262
473
|
}
|
|
263
|
-
|
|
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;
|
|
264
492
|
if (json) {
|
|
265
|
-
process.stdout.write(asJson({
|
|
266
|
-
|
|
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;
|
|
267
501
|
}
|
|
268
502
|
process.stdout.write(header('Generated artifacts'));
|
|
269
503
|
process.stdout.write(' No generated-artifact rules declared. Add `generatedArtifacts[]` to\n' +
|
|
270
504
|
' sharkcraft.config.ts to catch hand-edited generated files and missing\n' +
|
|
271
505
|
' "do not edit" headers (see docs/generated-drift.md).\n');
|
|
272
|
-
|
|
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;
|
|
273
512
|
}
|
|
274
513
|
export const generatedListCommand = {
|
|
275
514
|
name: 'list',
|
|
@@ -281,19 +520,26 @@ export const generatedListCommand = {
|
|
|
281
520
|
if (!prep.ok)
|
|
282
521
|
return prep.code;
|
|
283
522
|
const json = flagBool(args, 'json');
|
|
284
|
-
if (prep.all.length === 0)
|
|
523
|
+
if (prep.all.length === 0 && prep.rejected.length === 0)
|
|
285
524
|
return writeNoRules(json);
|
|
286
525
|
if (json) {
|
|
287
526
|
process.stdout.write(asJson({
|
|
288
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 })),
|
|
289
530
|
rules: prep.all.map((r) => ({
|
|
290
531
|
id: r.id,
|
|
291
532
|
description: r.description ?? null,
|
|
292
533
|
generatedGlob: r.generatedGlob,
|
|
293
534
|
regen: r.regen ?? null,
|
|
535
|
+
sources: r.sources ?? null,
|
|
536
|
+
handMaintained: r.handMaintained ?? null,
|
|
537
|
+
handMaintainedMarker: r.handMaintainedMarker ?? null,
|
|
294
538
|
compare: r.compare ?? 'bytes',
|
|
295
539
|
provenanceHeader: r.provenanceHeader ?? null,
|
|
296
|
-
|
|
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),
|
|
297
543
|
})),
|
|
298
544
|
diagnostics: prep.planeDiagnostics,
|
|
299
545
|
}) + '\n');
|
|
@@ -303,13 +549,32 @@ export const generatedListCommand = {
|
|
|
303
549
|
for (const r of prep.all) {
|
|
304
550
|
process.stdout.write(` • ${r.id}\n`);
|
|
305
551
|
process.stdout.write(` glob ${r.generatedGlob.join(', ')}\n`);
|
|
306
|
-
|
|
552
|
+
if (r.sources && r.sources.length > 0) {
|
|
553
|
+
process.stdout.write(` regen ${r.sources.length} writer(s):\n`);
|
|
554
|
+
r.sources.forEach((src, i) => {
|
|
555
|
+
process.stdout.write(` [${src.id ?? i}] ${src.regen}\n`);
|
|
556
|
+
process.stdout.write(` owns ${src.glob.join(', ')}\n`);
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
else {
|
|
560
|
+
process.stdout.write(` regen ${r.regen ?? '(header-only — never spawns)'}\n`);
|
|
561
|
+
}
|
|
562
|
+
if (r.handMaintained && r.handMaintained.length > 0) {
|
|
563
|
+
process.stdout.write(` hand ${r.handMaintained.join(', ')} (exempt from header + byte checks)\n`);
|
|
564
|
+
}
|
|
565
|
+
if (r.handMaintainedMarker) {
|
|
566
|
+
process.stdout.write(` marker /${r.handMaintainedMarker}/ (in-file bless)\n`);
|
|
567
|
+
}
|
|
307
568
|
if (r.provenanceHeader) {
|
|
308
569
|
process.stdout.write(` header /${r.provenanceHeader.mustMatch}/${r.provenanceHeader.forbidOutside ? ' + mislabel check' : ''}\n`);
|
|
309
570
|
}
|
|
310
571
|
if (r.description)
|
|
311
572
|
process.stdout.write(` ${r.description}\n`);
|
|
312
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
|
+
}
|
|
313
578
|
for (const d of prep.planeDiagnostics)
|
|
314
579
|
process.stdout.write(` ! ${d}\n`);
|
|
315
580
|
return ExitCode.VerifiedPass;
|
|
@@ -321,63 +586,92 @@ export const generatedCheckCommand = {
|
|
|
321
586
|
usage: 'shrk generated check [--id <ids>] [--headers-only] [--json]',
|
|
322
587
|
booleanFlags: new Set(['json', 'headers-only']),
|
|
323
588
|
async run(args) {
|
|
324
|
-
const prep = await prepare(args);
|
|
589
|
+
const prep = await prepare(args, { rejectedKnown: true });
|
|
325
590
|
if (!prep.ok)
|
|
326
591
|
return prep.code;
|
|
327
592
|
const json = flagBool(args, 'json');
|
|
328
593
|
const headersOnly = flagBool(args, 'headers-only');
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
const
|
|
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');
|
|
599
|
+
const outcomes = prep.rules.map((r) => evaluateGeneratedRule(prep.cwd, r, prep.excludeDirs, headersOnly));
|
|
332
600
|
const failed = outcomes.filter((o) => o.status === 'failed' || (o.status === 'error' && (o.rule.severity ?? 'error') === 'error'));
|
|
333
601
|
const evaluated = outcomes.filter((o) => o.status !== 'skipped').length;
|
|
334
602
|
const skippedCount = outcomes.length - evaluated;
|
|
335
|
-
const
|
|
603
|
+
const proposed = failed.length > 0 || rejected.length > 0
|
|
336
604
|
? ExitCode.Failure
|
|
337
605
|
: evaluated === 0 || skippedCount > 0
|
|
338
606
|
? ExitCode.NotVerified
|
|
339
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;
|
|
340
653
|
if (json) {
|
|
341
654
|
process.stdout.write(asJson({
|
|
342
655
|
schema: SCHEMA,
|
|
343
656
|
headersOnly,
|
|
344
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 })),
|
|
345
660
|
evaluated,
|
|
346
661
|
skipped: outcomes.filter((o) => o.status === 'skipped').length,
|
|
347
|
-
verdict:
|
|
662
|
+
verdict: exit === ExitCode.Failure ? 'errors' : exit === ExitCode.VerifiedPass ? 'pass' : 'not-verified',
|
|
348
663
|
diagnostics: prep.planeDiagnostics,
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
type: 'generated',
|
|
352
|
-
status: o.status,
|
|
353
|
-
severity: o.rule.severity ?? 'error',
|
|
354
|
-
counts: {
|
|
355
|
-
files: o.committedCount,
|
|
356
|
-
differences: o.treeDiff?.differences.length ?? 0,
|
|
357
|
-
provenance: o.provenance.length,
|
|
358
|
-
},
|
|
359
|
-
violations: [
|
|
360
|
-
...(o.treeDiff?.differences ?? []).map((d) => ({
|
|
361
|
-
id: d.file,
|
|
362
|
-
file: d.file,
|
|
363
|
-
message: d.kind,
|
|
364
|
-
hint: hintFor(o.rule),
|
|
365
|
-
})),
|
|
366
|
-
...o.provenance.map((f) => ({
|
|
367
|
-
id: f.file,
|
|
368
|
-
file: f.file,
|
|
369
|
-
message: f.message,
|
|
370
|
-
hint: hintFor(o.rule),
|
|
371
|
-
})),
|
|
372
|
-
],
|
|
373
|
-
...(o.skipReason ? { skipReason: o.skipReason } : {}),
|
|
374
|
-
...(o.error ? { error: o.error } : {}),
|
|
375
|
-
}))),
|
|
664
|
+
exitCode: exit,
|
|
665
|
+
gate: env,
|
|
376
666
|
}) + '\n');
|
|
377
667
|
return exit;
|
|
378
668
|
}
|
|
379
669
|
process.stdout.write(header('Generated-artifact drift'));
|
|
380
|
-
|
|
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);
|
|
381
675
|
if (headersOnly)
|
|
382
676
|
process.stdout.write(kv('scope', 'headers only — no regen was run') + '\n');
|
|
383
677
|
for (const o of outcomes) {
|
|
@@ -389,13 +683,34 @@ export const generatedCheckCommand = {
|
|
|
389
683
|
process.stdout.write(` ! ${o.rule.id} ${o.error}\n`);
|
|
390
684
|
continue;
|
|
391
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
|
+
}
|
|
392
694
|
const diffs = o.treeDiff?.differences ?? [];
|
|
393
|
-
|
|
394
|
-
|
|
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') {
|
|
702
|
+
const exempt = o.handMaintained.length > 0 ? `, ${o.handMaintained.length} hand-maintained` : '';
|
|
703
|
+
process.stdout.write(` ✓ ${o.rule.id} (${o.committedCount} files${exempt}` +
|
|
395
704
|
`${o.driftChecked ? ', byte-identical to a fresh regen' : ', headers only'})\n`);
|
|
705
|
+
for (const w of o.writers) {
|
|
706
|
+
process.stdout.write(` · ${w.label}: ${w.committedCount} file(s) verified\n`);
|
|
707
|
+
}
|
|
396
708
|
}
|
|
397
709
|
else {
|
|
398
710
|
process.stdout.write(` ✗ ${o.rule.id} ${diffs.length} file(s) differ from a fresh regen\n`);
|
|
711
|
+
for (const w of o.writers) {
|
|
712
|
+
process.stdout.write(` · ${w.label}: ${w.error ? `ERROR — ${w.error}` : `${w.differences} of ${w.committedCount} differ`}\n`);
|
|
713
|
+
}
|
|
399
714
|
for (const d of diffs.slice(0, 25)) {
|
|
400
715
|
const label = d.kind === 'content'
|
|
401
716
|
? 'hand-edited (or source changed)'
|
|
@@ -414,22 +729,122 @@ export const generatedCheckCommand = {
|
|
|
414
729
|
process.stdout.write(` … (${o.provenance.length - 25} more header finding(s))\n`);
|
|
415
730
|
}
|
|
416
731
|
if (o.status === 'failed')
|
|
417
|
-
process.stdout.write(` → ${
|
|
732
|
+
process.stdout.write(` → ${generatedHintFor(o.rule)}\n`);
|
|
418
733
|
}
|
|
419
734
|
for (const d of prep.planeDiagnostics)
|
|
420
735
|
process.stdout.write(` ! ${d}\n`);
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
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`);
|
|
426
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`);
|
|
427
756
|
return exit;
|
|
428
757
|
},
|
|
429
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
|
+
}
|
|
430
845
|
export const generatedUpdateCommand = {
|
|
431
846
|
name: 'update',
|
|
432
|
-
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.',
|
|
433
848
|
usage: 'shrk generated update [--id <ids>] [--json]',
|
|
434
849
|
booleanFlags: new Set(['json']),
|
|
435
850
|
async run(args) {
|
|
@@ -441,44 +856,74 @@ export const generatedUpdateCommand = {
|
|
|
441
856
|
return writeNoRules(json);
|
|
442
857
|
const results = [];
|
|
443
858
|
for (const rule of prep.rules) {
|
|
444
|
-
|
|
445
|
-
|
|
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 });
|
|
446
869
|
continue;
|
|
447
870
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
results.push({ id: rule.id, 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;
|
|
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
|
+
});
|
|
467
889
|
}
|
|
468
890
|
}
|
|
469
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;
|
|
470
898
|
if (json) {
|
|
471
|
-
process.stdout.write(asJson({ schema: SCHEMA, results }) + '\n');
|
|
472
|
-
return
|
|
899
|
+
process.stdout.write(asJson({ schema: SCHEMA, inSync: failed.length === 0 && outOfSync.length === 0, results, exitCode: exit }) + '\n');
|
|
900
|
+
return exit;
|
|
473
901
|
}
|
|
474
902
|
process.stdout.write(header('Generated update'));
|
|
475
903
|
for (const r of results) {
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
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
|
+
}
|
|
480
922
|
}
|
|
481
|
-
|
|
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;
|
|
482
927
|
},
|
|
483
928
|
};
|
|
484
929
|
export const generatedExplainCommand = {
|
|
@@ -502,12 +947,23 @@ export const generatedExplainCommand = {
|
|
|
502
947
|
}
|
|
503
948
|
// explain never spawns — the point is to show what the rule SEES.
|
|
504
949
|
const scan = scanGeneratedFiles(prep.cwd, rule, prep.excludeDirs);
|
|
505
|
-
const outcome =
|
|
950
|
+
const outcome = evaluateGeneratedRule(prep.cwd, rule, prep.excludeDirs, true);
|
|
506
951
|
if (flagBool(args, 'json')) {
|
|
507
952
|
process.stdout.write(asJson({
|
|
508
953
|
schema: 'sharkcraft.generated-explain/v1',
|
|
509
954
|
...outcomeJson(outcome),
|
|
510
955
|
files: [...scan.generated.keys()],
|
|
956
|
+
checkable: [...scan.checkable.keys()],
|
|
957
|
+
handMaintained: scan.handMaintained,
|
|
958
|
+
markedHandMaintained: scan.markedHandMaintained,
|
|
959
|
+
staleHandMaintained: scan.staleHandMaintained,
|
|
960
|
+
unclassified: scan.unclassified,
|
|
961
|
+
slices: scan.slices.map((sl) => ({
|
|
962
|
+
label: sl.label,
|
|
963
|
+
regen: sl.regen,
|
|
964
|
+
glob: sl.glob,
|
|
965
|
+
files: [...sl.files.keys()],
|
|
966
|
+
})),
|
|
511
967
|
outsideScanned: scan.outside.size,
|
|
512
968
|
outsideGlobs: scan.outsideGlobs,
|
|
513
969
|
regen: rule.regen ?? null,
|
|
@@ -519,7 +975,34 @@ export const generatedExplainCommand = {
|
|
|
519
975
|
process.stdout.write(` ${rule.description}\n`);
|
|
520
976
|
process.stdout.write(kv('glob', rule.generatedGlob.join(', ')) + '\n');
|
|
521
977
|
process.stdout.write(kv('files matched', String(scan.generated.size)) + '\n');
|
|
522
|
-
|
|
978
|
+
if (scan.slices.length > 0) {
|
|
979
|
+
process.stdout.write(kv('writers', String(scan.slices.length)) + '\n');
|
|
980
|
+
for (const sl of scan.slices) {
|
|
981
|
+
process.stdout.write(` [${sl.label}] ${sl.files.size} file(s) — ${sl.regen}\n`);
|
|
982
|
+
process.stdout.write(` owns ${sl.glob.join(', ')}\n`);
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
else {
|
|
986
|
+
process.stdout.write(kv('regen', rule.regen ?? '(header-only)') + '\n');
|
|
987
|
+
}
|
|
988
|
+
if (scan.handMaintained.length > 0) {
|
|
989
|
+
process.stdout.write(kv('hand-maintained', `${scan.handMaintained.length} file(s) — exempt from header + byte checks`) + '\n');
|
|
990
|
+
const marked = new Set(scan.markedHandMaintained);
|
|
991
|
+
// Say WHICH mechanism blessed each file: a config entry and an in-file
|
|
992
|
+
// marker are reviewed in different places, so "why is this exempt?" has
|
|
993
|
+
// two different answers and the reader needs to know which one applies.
|
|
994
|
+
for (const f of scan.handMaintained.slice(0, 20)) {
|
|
995
|
+
process.stdout.write(` ${f}${marked.has(f) ? ' (in-file marker)' : ' (handMaintained[])'}\n`);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
if (scan.unclassified.length > 0) {
|
|
999
|
+
process.stdout.write(kv('unclassified', `${scan.unclassified.length} file(s) — owned by no writer, not blessed`) + '\n');
|
|
1000
|
+
for (const f of scan.unclassified.slice(0, 20))
|
|
1001
|
+
process.stdout.write(` ${f}\n`);
|
|
1002
|
+
}
|
|
1003
|
+
for (const pattern of scan.staleHandMaintained) {
|
|
1004
|
+
process.stdout.write(` ! handMaintained "${pattern}" matches no file — stale bless\n`);
|
|
1005
|
+
}
|
|
523
1006
|
process.stdout.write(kv('compare', rule.compare ?? 'bytes') + '\n');
|
|
524
1007
|
if (rule.provenanceHeader) {
|
|
525
1008
|
process.stdout.write(kv('header', `/${rule.provenanceHeader.mustMatch}/`) + '\n');
|