@shrkcrft/cli 0.1.0-alpha.27 → 0.1.0-alpha.28

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.
Files changed (36) hide show
  1. package/dist/commands/baseline.command.d.ts +8 -0
  2. package/dist/commands/baseline.command.d.ts.map +1 -0
  3. package/dist/commands/baseline.command.js +511 -0
  4. package/dist/commands/changelog-data.d.ts.map +1 -1
  5. package/dist/commands/changelog-data.js +22 -0
  6. package/dist/commands/check.command.d.ts.map +1 -1
  7. package/dist/commands/check.command.js +28 -1
  8. package/dist/commands/command-catalog.d.ts.map +1 -1
  9. package/dist/commands/command-catalog.js +112 -0
  10. package/dist/commands/gates.command.d.ts +6 -0
  11. package/dist/commands/gates.command.d.ts.map +1 -0
  12. package/dist/commands/gates.command.js +334 -0
  13. package/dist/commands/generated.command.d.ts +6 -0
  14. package/dist/commands/generated.command.d.ts.map +1 -0
  15. package/dist/commands/generated.command.js +514 -0
  16. package/dist/commands/ingest.command.d.ts +11 -0
  17. package/dist/commands/ingest.command.d.ts.map +1 -1
  18. package/dist/commands/ingest.command.js +49 -23
  19. package/dist/commands/policy-lint.command.d.ts +37 -0
  20. package/dist/commands/policy-lint.command.d.ts.map +1 -1
  21. package/dist/commands/policy-lint.command.js +119 -2
  22. package/dist/commands/registry.command.d.ts.map +1 -1
  23. package/dist/commands/registry.command.js +36 -4
  24. package/dist/commands/wiring.command.d.ts.map +1 -1
  25. package/dist/commands/wiring.command.js +25 -0
  26. package/dist/finish/run-finish.d.ts.map +1 -1
  27. package/dist/finish/run-finish.js +9 -3
  28. package/dist/gates/gate-rule-view.d.ts +35 -0
  29. package/dist/gates/gate-rule-view.d.ts.map +1 -0
  30. package/dist/gates/gate-rule-view.js +80 -0
  31. package/dist/gates/rule-coverage.d.ts +53 -0
  32. package/dist/gates/rule-coverage.d.ts.map +1 -0
  33. package/dist/gates/rule-coverage.js +165 -0
  34. package/dist/main.d.ts.map +1 -1
  35. package/dist/main.js +25 -2
  36. package/package.json +33 -33
@@ -406,6 +406,118 @@ export const COMMAND_CATALOG = Object.freeze([
406
406
  surface: CommandSurface.Common,
407
407
  taskRole: CommandTaskRole.Validate,
408
408
  }),
409
+ entry({
410
+ command: 'policy-lint explain',
411
+ description: 'Dry-run ONE policyRule and print every hit with file:line — INCLUDING the hits an exemption (exemptFiles / exemptLines) or the lexical scan zone dropped, each labelled with which one applied. The author-loop view of what the gate sees. [--json]',
412
+ category: 'core',
413
+ safetyLevel: SafetyLevel.ReadOnly,
414
+ surface: CommandSurface.Advanced,
415
+ taskRole: CommandTaskRole.Explain,
416
+ }),
417
+ entry({
418
+ command: 'registry duplicates',
419
+ description: 'Ids declared more than once across roots in a config-defined registry — a latent bug the compiler cannot see (whichever registration wins at runtime is an accident of load order). Every declaration site is reported. Exit 1 on duplicates, 2 when the registry matched 0 ids. [--json]',
420
+ category: 'core',
421
+ safetyLevel: SafetyLevel.ReadOnly,
422
+ surface: CommandSurface.Common,
423
+ taskRole: CommandTaskRole.Validate,
424
+ }),
425
+ entry({
426
+ command: 'gates list',
427
+ description: 'Every data-defined rule across every plane (wiring / policy / registry / registration / baseline / generated) with its severity and empty-match policy. [--plane <p>] [--json]',
428
+ category: 'core',
429
+ safetyLevel: SafetyLevel.ReadOnly,
430
+ surface: CommandSurface.Common,
431
+ taskRole: CommandTaskRole.Inspect,
432
+ }),
433
+ entry({
434
+ command: 'gates coverage',
435
+ description: 'The stale-selector detector: what every data-defined rule actually MATCHED against the live tree, flagging each rule that matched 0 files/ids (a rule matching nothing is a bug in the rule, never a pass) and running each rule\'s declared selfTest expectations. Run it in CI so a rule quietly dying is itself a failure. Exit 2 when a rule matched nothing, 1 when it set failOnEmpty. [--plane <p>] [--json]',
436
+ category: 'core',
437
+ safetyLevel: SafetyLevel.ReadOnly,
438
+ surface: CommandSurface.Common,
439
+ taskRole: CommandTaskRole.Validate,
440
+ }),
441
+ entry({
442
+ command: 'gates explain',
443
+ description: 'Universal rule introspection: for a rule of ANY plane, print the concrete inputs it resolved — files matched, ids extracted with file:line, and the computed diff. Dispatches to the plane-specific explainer. [--plane <p>] [--json]',
444
+ category: 'core',
445
+ safetyLevel: SafetyLevel.ReadOnly,
446
+ surface: CommandSurface.Advanced,
447
+ taskRole: CommandTaskRole.Explain,
448
+ }),
449
+ entry({
450
+ command: 'baseline list',
451
+ description: 'Every declared baseline: what it pins, how it recomputes, and which direction of drift fails. [--json]',
452
+ category: 'core',
453
+ safetyLevel: SafetyLevel.ReadOnly,
454
+ surface: CommandSurface.Common,
455
+ taskRole: CommandTaskRole.Inspect,
456
+ }),
457
+ entry({
458
+ command: 'baseline check',
459
+ description: 'Recompute every committed baseline (adoption ledger / API digest / coverage ratchet / allow-list) and fail on drift. TWO-WAY by default: a silently LOST entry fails exactly like a gained one. Rules from sharkcraft.config.ts baselines[]. Runs the declared compute command for `kind: "command"` rules. [--id X] [--changed-only] [--json]',
460
+ category: 'core',
461
+ safetyLevel: SafetyLevel.RunsShell,
462
+ surface: CommandSurface.Common,
463
+ taskRole: CommandTaskRole.Validate,
464
+ }),
465
+ entry({
466
+ command: 'baseline diff',
467
+ description: 'Human-readable +added / −removed for each baseline, without a verdict — the inspection verb. [--id X] [--json]',
468
+ category: 'core',
469
+ safetyLevel: SafetyLevel.RunsShell,
470
+ surface: CommandSurface.Advanced,
471
+ taskRole: CommandTaskRole.Explain,
472
+ }),
473
+ entry({
474
+ command: 'baseline update',
475
+ description: 'Rewrite a committed baseline from the current value — the explicit, reviewable bless step (deliberately a separate verb from `check` so a drift can never be blessed by accident). Writes files. [--id X] [--dry-run] [--json]',
476
+ category: 'core',
477
+ safetyLevel: SafetyLevel.WritesSource,
478
+ surface: CommandSurface.Common,
479
+ taskRole: CommandTaskRole.Apply,
480
+ }),
481
+ entry({
482
+ command: 'baseline explain',
483
+ description: 'What ONE baseline computes and compares — the command or extractor, the canonical form, both entry counts, and the diff — without turning it into a verdict. [--id X] [--json]',
484
+ category: 'core',
485
+ safetyLevel: SafetyLevel.RunsShell,
486
+ surface: CommandSurface.Advanced,
487
+ taskRole: CommandTaskRole.Explain,
488
+ }),
489
+ entry({
490
+ command: 'generated list',
491
+ description: 'Every declared generated-artifact rule: its globs, regen command, and "do not edit" header contract. [--json]',
492
+ category: 'core',
493
+ safetyLevel: SafetyLevel.ReadOnly,
494
+ surface: CommandSurface.Common,
495
+ taskRole: CommandTaskRole.Inspect,
496
+ }),
497
+ entry({
498
+ command: 'generated check',
499
+ description: 'Regenerate into a temp dir and diff BOTH ways — catching a hand-edited generated file AND a regen that writes a subset — plus the provenance-header contract (missing header on a generated file, header on a hand-written one). `--headers-only` never spawns. Rules from sharkcraft.config.ts generatedArtifacts[]. [--id X] [--headers-only] [--json]',
500
+ category: 'core',
501
+ safetyLevel: SafetyLevel.RunsShell,
502
+ surface: CommandSurface.Common,
503
+ taskRole: CommandTaskRole.Validate,
504
+ }),
505
+ entry({
506
+ command: 'generated update',
507
+ description: 'Run the declared regen command in place — the one-command bless step after an intentional source change. Writes files. [--id X] [--json]',
508
+ category: 'core',
509
+ safetyLevel: SafetyLevel.WritesSource,
510
+ surface: CommandSurface.Common,
511
+ taskRole: CommandTaskRole.Apply,
512
+ }),
513
+ entry({
514
+ command: 'generated explain',
515
+ description: 'What ONE generated-artifact rule sees right now: files matched, header-contract results, mislabel candidates — without running the regen. [--id X] [--json]',
516
+ category: 'core',
517
+ safetyLevel: SafetyLevel.ReadOnly,
518
+ surface: CommandSurface.Advanced,
519
+ taskRole: CommandTaskRole.Explain,
520
+ }),
409
521
  entry({
410
522
  command: 'wiring explain',
411
523
  description: 'Dry-run ONE configured wiringRule and print the declared set + registered set it extracts (token + file:line), the alias-resolved set-difference, and the verdict — the author-loop view of what `check wiring` sees, without re-running the gate. [--json]',
@@ -0,0 +1,6 @@
1
+ import { type ICommandHandler } from '../command-registry.js';
2
+ export declare const gatesListCommand: ICommandHandler;
3
+ export declare const gatesCoverageCommand: ICommandHandler;
4
+ export declare const gatesExplainCommand: ICommandHandler;
5
+ export declare const gatesCommand: ICommandHandler;
6
+ //# sourceMappingURL=gates.command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gates.command.d.ts","sourceRoot":"","sources":["../../src/commands/gates.command.ts"],"names":[],"mappings":"AAyBA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AA+EhC,eAAO,MAAM,gBAAgB,EAAE,eAsD9B,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,eA4ElC,CAAC;AAuCF,eAAO,MAAM,mBAAmB,EAAE,eA4FjC,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,eAe1B,CAAC"}
@@ -0,0 +1,334 @@
1
+ /**
2
+ * `shrk gates` — the rule-authoring trust layer.
3
+ *
4
+ * shrk gates list [--plane <p>] # every data-defined rule, across every plane
5
+ * shrk gates coverage [--strict] # what each rule MATCHED; flags every rule matching 0
6
+ * shrk gates explain <id> # the concrete inputs one rule resolved
7
+ *
8
+ * Every rule engine in shrk is only as trustworthy as the author's ability to
9
+ * see what a rule actually matched, and the dominant real-world failure is a
10
+ * stale selector that silently matches nothing — a "pass" that checked zero
11
+ * files. `gates coverage` is the detector: run it in CI and a rule quietly
12
+ * dying becomes a failure in its own right.
13
+ *
14
+ * Distinct from `shrk gate` (singular), which RUNS the quality-gate pipeline.
15
+ * This verb inspects the data-defined RULES themselves.
16
+ */
17
+ import * as nodePath from 'node:path';
18
+ import { explainWiring, inspectSource, scanRegistry } from '@shrkcrft/boundaries';
19
+ import { resolveProjectConfig } from '@shrkcrft/inspector';
20
+ import { flagBool, flagString, resolveCwd, } from "../command-registry.js";
21
+ import { ExitCode } from "../exit-codes.js";
22
+ import { asJson, header, kv } from "../output/format-output.js";
23
+ import { collectGateRules, GATE_PLANES, } from "../gates/gate-rule-view.js";
24
+ import { buildGateCoverage } from "../gates/rule-coverage.js";
25
+ import { baselineExplainCommand } from "./baseline.command.js";
26
+ import { generatedExplainCommand } from "./generated.command.js";
27
+ import { renderPolicyExplain, runPolicyExplain } from "./policy-lint.command.js";
28
+ import { renderWiringExplain } from "./wiring.command.js";
29
+ const SCHEMA = 'sharkcraft.gates/v1';
30
+ async function prepare(args) {
31
+ const cwd = resolveCwd(args);
32
+ const json = flagBool(args, 'json');
33
+ const loaded = await resolveProjectConfig(cwd);
34
+ if (!loaded.ok) {
35
+ const msg = loaded.error.message;
36
+ if (json)
37
+ process.stdout.write(asJson({ schema: SCHEMA, error: msg }) + '\n');
38
+ else
39
+ process.stderr.write(`Could not load config: ${msg}\n Run \`shrk doctor\` for details.\n`);
40
+ return { ok: false, code: ExitCode.NotVerified };
41
+ }
42
+ const rel = nodePath.relative(cwd, loaded.value.sharkcraftDir).split(nodePath.sep).join('/');
43
+ return {
44
+ ok: true,
45
+ value: {
46
+ cwd,
47
+ rules: collectGateRules(loaded.value.config),
48
+ excludeDirs: rel && !rel.startsWith('..') ? [rel] : [],
49
+ planeDiagnostics: loaded.value.planeDiagnostics,
50
+ },
51
+ };
52
+ }
53
+ /** Parse `--plane`, refusing an unknown value rather than silently matching nothing. */
54
+ function parsePlanes(args) {
55
+ const raw = flagString(args, 'plane');
56
+ if (!raw)
57
+ return { ok: true };
58
+ const parts = raw.split(',').map((s) => s.trim()).filter(Boolean);
59
+ const bad = parts.filter((p) => !GATE_PLANES.includes(p));
60
+ if (bad.length > 0) {
61
+ process.stderr.write(`Unknown --plane "${bad.join(', ')}". Use ${GATE_PLANES.join(' | ')}.\n`);
62
+ return { ok: false };
63
+ }
64
+ return { ok: true, planes: new Set(parts) };
65
+ }
66
+ function writeNoRules(json) {
67
+ if (json) {
68
+ process.stdout.write(asJson({ schema: SCHEMA, rules: [], total: 0 }) + '\n');
69
+ return ExitCode.NotVerified;
70
+ }
71
+ process.stdout.write(header('Gate rules'));
72
+ process.stdout.write(' No data-defined rules declared. These planes live in sharkcraft.config.ts:\n' +
73
+ ' wiringRules[] declared-here → registered-there completeness\n' +
74
+ ' policyRules[] forbidden content the compiler never sees\n' +
75
+ ' registries[] id inventories (`shrk registry <name> list`)\n' +
76
+ ' registrationGraph[] DI/registration idioms (`shrk wiring chain`)\n' +
77
+ ' baselines[] committed ledgers that must not silently drift\n' +
78
+ ' generatedArtifacts[] generated files that must not be hand-edited\n');
79
+ return ExitCode.NotVerified;
80
+ }
81
+ export const gatesListCommand = {
82
+ name: 'list',
83
+ description: 'Every data-defined rule across every plane, with its severity and empty-match policy.',
84
+ usage: 'shrk gates list [--plane wiring|policy|registry|registration|baseline|generated] [--json]',
85
+ booleanFlags: new Set(['json']),
86
+ async run(args) {
87
+ const prep = await prepare(args);
88
+ if (!prep.ok)
89
+ return prep.code;
90
+ const planes = parsePlanes(args);
91
+ if (!planes.ok)
92
+ return ExitCode.NotVerified;
93
+ const json = flagBool(args, 'json');
94
+ const rules = planes.planes
95
+ ? prep.value.rules.filter((r) => planes.planes.has(r.plane))
96
+ : prep.value.rules;
97
+ if (rules.length === 0)
98
+ return writeNoRules(json);
99
+ if (json) {
100
+ process.stdout.write(asJson({
101
+ schema: SCHEMA,
102
+ total: rules.length,
103
+ rules: rules.map((r) => ({
104
+ id: r.id,
105
+ plane: r.plane,
106
+ description: r.description ?? null,
107
+ severity: r.severity,
108
+ failOnEmpty: r.failOnEmpty,
109
+ selfTest: r.selfTest ?? null,
110
+ })),
111
+ diagnostics: prep.value.planeDiagnostics,
112
+ }) + '\n');
113
+ return ExitCode.VerifiedPass;
114
+ }
115
+ process.stdout.write(header(`Gate rules (${rules.length})`));
116
+ for (const plane of GATE_PLANES) {
117
+ const inPlane = rules.filter((r) => r.plane === plane);
118
+ if (inPlane.length === 0)
119
+ continue;
120
+ process.stdout.write(`\n${plane} (${inPlane.length})\n`);
121
+ for (const r of inPlane) {
122
+ const flags = [
123
+ r.severity === 'warning' ? 'warning' : undefined,
124
+ r.failOnEmpty ? 'failOnEmpty' : undefined,
125
+ r.selfTest ? 'selfTest' : undefined,
126
+ ].filter(Boolean);
127
+ process.stdout.write(` • ${r.id}${flags.length > 0 ? ` [${flags.join(', ')}]` : ''}\n`);
128
+ if (r.description)
129
+ process.stdout.write(` ${r.description}\n`);
130
+ }
131
+ }
132
+ for (const d of prep.value.planeDiagnostics)
133
+ process.stdout.write(` ! ${d}\n`);
134
+ process.stdout.write('\nRun `shrk gates coverage` to see what each one actually matches.\n');
135
+ return ExitCode.VerifiedPass;
136
+ },
137
+ };
138
+ export const gatesCoverageCommand = {
139
+ name: 'coverage',
140
+ description: 'What every rule MATCHED against the live tree — the stale-selector detector. A rule matching 0 files/ids is a bug in the rule, never a pass. Also runs each rule\'s declared selfTest expectations.',
141
+ usage: 'shrk gates coverage [--plane <p>] [--strict] [--json]',
142
+ booleanFlags: new Set(['json', 'strict']),
143
+ async run(args) {
144
+ const prep = await prepare(args);
145
+ if (!prep.ok)
146
+ return prep.code;
147
+ const planes = parsePlanes(args);
148
+ if (!planes.ok)
149
+ return ExitCode.NotVerified;
150
+ const json = flagBool(args, 'json');
151
+ const rules = planes.planes
152
+ ? prep.value.rules.filter((r) => planes.planes.has(r.plane))
153
+ : prep.value.rules;
154
+ if (rules.length === 0)
155
+ return writeNoRules(json);
156
+ const report = buildGateCoverage(prep.value.cwd, rules, prep.value.excludeDirs);
157
+ // A rule that matched nothing is NOT-VERIFIED (2) by default — it neither
158
+ // passed nor failed, it never ran. `failOnEmpty` on the rule (or the global
159
+ // --strict promotion) turns that into a hard failure.
160
+ const hardFailures = report.rules.filter((r) => r.status === 'error' || r.status === 'failed-expectation' || (r.status === 'empty' && r.failOnEmpty));
161
+ const softEmpty = report.rules.filter((r) => r.status === 'empty' && !r.failOnEmpty);
162
+ const exit = hardFailures.length > 0
163
+ ? ExitCode.Failure
164
+ : softEmpty.length > 0
165
+ ? ExitCode.NotVerified
166
+ : ExitCode.VerifiedPass;
167
+ if (json) {
168
+ process.stdout.write(asJson({ ...report, hardFailures: hardFailures.length, exitCode: exit }) + '\n');
169
+ return exit;
170
+ }
171
+ process.stdout.write(header('Gate-rule coverage'));
172
+ process.stdout.write(kv('rules', String(report.total)) + '\n');
173
+ process.stdout.write(kv('matched nothing', `${report.empty}${report.empty > 0 ? ' ← stale selector suspects' : ''}`) + '\n');
174
+ if (report.errored > 0)
175
+ process.stdout.write(kv('misconfigured', String(report.errored)) + '\n');
176
+ if (report.expectationFailures > 0) {
177
+ process.stdout.write(kv('broken selfTest', String(report.expectationFailures)) + '\n');
178
+ }
179
+ process.stdout.write('\n');
180
+ for (const r of report.rules) {
181
+ const mark = r.status === 'ok' ? '✓' : r.status === 'empty' ? (r.failOnEmpty ? '✗' : '–') : '✗';
182
+ process.stdout.write(` ${mark} [${r.plane}] ${r.id} — ${r.unitsMatched} ${r.unitLabel} across ${r.filesMatched} file(s)\n`);
183
+ if (r.sampleIds.length > 0) {
184
+ process.stdout.write(` e.g. ${r.sampleIds.join(', ')}\n`);
185
+ }
186
+ if (r.status === 'empty') {
187
+ process.stdout.write(` ${r.failOnEmpty ? 'FAILED' : 'SKIPPED'} — matched nothing; the selector is probably stale\n`);
188
+ }
189
+ if (r.error)
190
+ process.stdout.write(` ! ${r.error}\n`);
191
+ for (const f of r.expectationFailures)
192
+ process.stdout.write(` ! selfTest: ${f}\n`);
193
+ }
194
+ if (exit === ExitCode.VerifiedPass) {
195
+ process.stdout.write('\nEvery rule is connected to something. ✓\n');
196
+ }
197
+ else if (exit === ExitCode.NotVerified) {
198
+ process.stdout.write(`\n${softEmpty.length} rule(s) matched nothing — NOT a pass. Fix the selector, or set \`failOnEmpty: true\`\n` +
199
+ 'once the rule is known to have real subjects (then this becomes a hard failure).\n');
200
+ }
201
+ return exit;
202
+ },
203
+ };
204
+ /** Render a registry inventory as the trust-layer explain view. */
205
+ function explainRegistry(cwd, decl, excludeDirs) {
206
+ const inventory = scanRegistry(cwd, decl, { excludeDirs });
207
+ const insp = inspectSource(cwd, decl.source, excludeDirs);
208
+ process.stdout.write(kv('files scanned', String(insp.filesScanned)) + '\n');
209
+ process.stdout.write(kv('ids', String(inventory.entries.length)) + '\n');
210
+ for (const e of inventory.entries.slice(0, 60)) {
211
+ process.stdout.write(` • ${e.id} (${e.sites.map((s) => `${s.file}:${s.line}`).join(', ')})\n`);
212
+ }
213
+ if (inventory.entries.length > 60) {
214
+ process.stdout.write(` … (${inventory.entries.length - 60} more)\n`);
215
+ }
216
+ for (const d of inventory.diagnostics)
217
+ process.stdout.write(` ! ${d}\n`);
218
+ }
219
+ /** Render the three sides of a registration idiom. */
220
+ function explainRegistration(cwd, idiom, excludeDirs) {
221
+ for (const [label, source] of [
222
+ ['declared', idiom.declared],
223
+ ['provided', idiom.provided],
224
+ ['consumed', idiom.consumed],
225
+ ]) {
226
+ const insp = inspectSource(cwd, source, excludeDirs);
227
+ process.stdout.write(kv(label, `${insp.ids.length} token(s) across ${insp.filesScanned} file(s)`) + '\n');
228
+ if (insp.error)
229
+ process.stdout.write(` ! ${insp.error}\n`);
230
+ for (const s of insp.sites.slice(0, 20)) {
231
+ process.stdout.write(` ${s.token} (${s.file}:${s.line})\n`);
232
+ }
233
+ if (insp.sites.length > 20)
234
+ process.stdout.write(` … (${insp.sites.length - 20} more)\n`);
235
+ }
236
+ process.stdout.write(`\n Query one token's chain with \`shrk wiring chain <token>\`.\n`);
237
+ }
238
+ export const gatesExplainCommand = {
239
+ name: 'explain',
240
+ description: 'The universal introspection: for a rule of ANY plane, print the concrete inputs it resolved — files matched, ids extracted with file:line, and the computed diff.',
241
+ usage: 'shrk gates explain <id> [--json]',
242
+ booleanFlags: new Set(['json']),
243
+ async run(args) {
244
+ const id = args.positional[0] ?? flagString(args, 'id');
245
+ if (!id) {
246
+ process.stderr.write('Usage: shrk gates explain <id> [--json]\n');
247
+ return ExitCode.NotVerified;
248
+ }
249
+ const prep = await prepare(args);
250
+ if (!prep.ok)
251
+ return prep.code;
252
+ const matches = prep.value.rules.filter((r) => r.id === id);
253
+ if (matches.length === 0) {
254
+ process.stderr.write(`No gate rule "${id}". Run \`shrk gates list\` to see the ${prep.value.rules.length} declared rule(s).\n`);
255
+ return ExitCode.NotVerified;
256
+ }
257
+ // An id may legitimately exist on two planes (a wiring rule and a registry
258
+ // can share a name); `--plane` disambiguates instead of guessing.
259
+ const planes = parsePlanes(args);
260
+ if (!planes.ok)
261
+ return ExitCode.NotVerified;
262
+ const candidates = planes.planes
263
+ ? matches.filter((r) => planes.planes.has(r.plane))
264
+ : matches;
265
+ if (candidates.length > 1) {
266
+ process.stderr.write(`"${id}" exists on ${candidates.length} planes (${candidates.map((c) => c.plane).join(', ')}). ` +
267
+ 'Disambiguate with --plane <p>.\n');
268
+ return ExitCode.NotVerified;
269
+ }
270
+ const view = candidates[0];
271
+ if (!view) {
272
+ process.stderr.write(`No gate rule "${id}" on the requested plane.\n`);
273
+ return ExitCode.NotVerified;
274
+ }
275
+ const json = flagBool(args, 'json');
276
+ // The two shell-executing planes own their explain output (and their trust
277
+ // rules), so delegate rather than re-implement — one behaviour, one place.
278
+ if (view.plane === 'baseline') {
279
+ args.flags.set('id', view.id);
280
+ return baselineExplainCommand.run(args);
281
+ }
282
+ if (view.plane === 'generated') {
283
+ args.flags.set('id', view.id);
284
+ return generatedExplainCommand.run(args);
285
+ }
286
+ if (view.plane === 'wiring') {
287
+ const explain = explainWiring(prep.value.cwd, view.raw, {
288
+ excludeDirs: prep.value.excludeDirs,
289
+ });
290
+ renderWiringExplain(explain, json);
291
+ return ExitCode.VerifiedPass;
292
+ }
293
+ if (view.plane === 'policy') {
294
+ const explain = runPolicyExplain(prep.value.cwd, view.raw, prep.value.excludeDirs);
295
+ renderPolicyExplain(explain, json);
296
+ return ExitCode.VerifiedPass;
297
+ }
298
+ if (json) {
299
+ const source = view.plane === 'registry'
300
+ ? view.raw.source
301
+ : view.raw.declared;
302
+ process.stdout.write(asJson({
303
+ schema: 'sharkcraft.gates-explain/v1',
304
+ id: view.id,
305
+ plane: view.plane,
306
+ ...inspectSource(prep.value.cwd, source, prep.value.excludeDirs),
307
+ }) + '\n');
308
+ return ExitCode.VerifiedPass;
309
+ }
310
+ process.stdout.write(header(`${view.plane} rule: ${view.id}`));
311
+ if (view.description)
312
+ process.stdout.write(` ${view.description}\n`);
313
+ if (view.plane === 'registry') {
314
+ explainRegistry(prep.value.cwd, view.raw, prep.value.excludeDirs);
315
+ }
316
+ else {
317
+ explainRegistration(prep.value.cwd, view.raw, prep.value.excludeDirs);
318
+ }
319
+ return ExitCode.VerifiedPass;
320
+ },
321
+ };
322
+ export const gatesCommand = {
323
+ name: 'gates',
324
+ description: 'Rule-authoring trust layer: list every data-defined rule, show what each one MATCHED (the stale-selector detector), and explain any one of them. Read-only. Not `shrk gate`, which runs the quality-gate pipeline.',
325
+ usage: 'shrk gates list | coverage [--strict] | explain <id>',
326
+ booleanFlags: new Set(['json', 'strict']),
327
+ async run(args) {
328
+ const sub = args.positional[0];
329
+ process.stderr.write((sub ? `Unknown subcommand "${sub}". ` : '') +
330
+ 'Usage: shrk gates list | coverage [--plane <p>] [--strict] | explain <id>\n' +
331
+ '(`shrk gate`, singular, runs the quality-gate pipeline — a different verb.)\n');
332
+ return ExitCode.NotVerified;
333
+ },
334
+ };
@@ -0,0 +1,6 @@
1
+ import { type ICommandHandler } from '../command-registry.js';
2
+ export declare const generatedListCommand: ICommandHandler;
3
+ export declare const generatedCheckCommand: ICommandHandler;
4
+ export declare const generatedUpdateCommand: ICommandHandler;
5
+ export declare const generatedExplainCommand: ICommandHandler;
6
+ //# sourceMappingURL=generated.command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generated.command.d.ts","sourceRoot":"","sources":["../../src/commands/generated.command.ts"],"names":[],"mappings":"AA+BA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAwShC,eAAO,MAAM,oBAAoB,EAAE,eA2ClC,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,eAuFnC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eAmDpC,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,eAiErC,CAAC"}