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

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 (46) 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 +542 -0
  4. package/dist/commands/changelog-data.d.ts.map +1 -1
  5. package/dist/commands/changelog-data.js +42 -0
  6. package/dist/commands/check.command.d.ts.map +1 -1
  7. package/dist/commands/check.command.js +147 -12
  8. package/dist/commands/command-catalog.d.ts.map +1 -1
  9. package/dist/commands/command-catalog.js +120 -0
  10. package/dist/commands/daily.commands.d.ts.map +1 -1
  11. package/dist/commands/daily.commands.js +11 -1
  12. package/dist/commands/gates.command.d.ts +16 -0
  13. package/dist/commands/gates.command.d.ts.map +1 -0
  14. package/dist/commands/gates.command.js +377 -0
  15. package/dist/commands/generated.command.d.ts +6 -0
  16. package/dist/commands/generated.command.d.ts.map +1 -0
  17. package/dist/commands/generated.command.js +544 -0
  18. package/dist/commands/help.command.d.ts.map +1 -1
  19. package/dist/commands/help.command.js +64 -2
  20. package/dist/commands/ingest.command.d.ts +11 -0
  21. package/dist/commands/ingest.command.d.ts.map +1 -1
  22. package/dist/commands/ingest.command.js +49 -23
  23. package/dist/commands/policy-lint.command.d.ts +37 -0
  24. package/dist/commands/policy-lint.command.d.ts.map +1 -1
  25. package/dist/commands/policy-lint.command.js +167 -8
  26. package/dist/commands/registry.command.d.ts.map +1 -1
  27. package/dist/commands/registry.command.js +70 -13
  28. package/dist/commands/wiring.command.d.ts.map +1 -1
  29. package/dist/commands/wiring.command.js +25 -0
  30. package/dist/exit-codes.d.ts +27 -7
  31. package/dist/exit-codes.d.ts.map +1 -1
  32. package/dist/exit-codes.js +47 -8
  33. package/dist/finish/run-finish.d.ts.map +1 -1
  34. package/dist/finish/run-finish.js +9 -3
  35. package/dist/gates/gate-envelope.d.ts +64 -0
  36. package/dist/gates/gate-envelope.d.ts.map +1 -0
  37. package/dist/gates/gate-envelope.js +26 -0
  38. package/dist/gates/gate-rule-view.d.ts +35 -0
  39. package/dist/gates/gate-rule-view.d.ts.map +1 -0
  40. package/dist/gates/gate-rule-view.js +81 -0
  41. package/dist/gates/rule-coverage.d.ts +53 -0
  42. package/dist/gates/rule-coverage.d.ts.map +1 -0
  43. package/dist/gates/rule-coverage.js +165 -0
  44. package/dist/main.d.ts.map +1 -1
  45. package/dist/main.js +27 -3
  46. package/package.json +33 -33
@@ -9,7 +9,8 @@ import { computeDeletedOrphans } from "../diff/deleted-orphans.js";
9
9
  import { renderWiringExplain } from "./wiring.command.js";
10
10
  import { validateTemplateVariables } from '@shrkcrft/templates';
11
11
  import { FileChangeType, planGeneration } from '@shrkcrft/generator';
12
- import { evaluateBoundaries, explainWiring, loadTsconfigPaths, runWiring, scanImports, summarizeImports, } from '@shrkcrft/boundaries';
12
+ import { evaluateBoundaries, explainWiring, loadTsconfigPaths, runWiring, scanImports, summarizeImports, planWiringFix, readMatchingFiles, wiringGlobsOf, } from '@shrkcrft/boundaries';
13
+ import { buildGateEnvelope } from "../gates/gate-envelope.js";
13
14
  function knowledgeGroup(inspection) {
14
15
  const dup = inspection.validationIssues.filter((i) => i.code === 'duplicate-id');
15
16
  const missing = inspection.validationIssues.filter((i) => i.code !== 'duplicate-id');
@@ -532,6 +533,93 @@ const WIRING_CHECK_USAGE = 'shrk check wiring [--changed-only] [--since <ref>] [
532
533
  ' --only <ids> run only these rule ids (comma-separated)\n' +
533
534
  ' --explain <id> dry-run ONE rule and print the declared/registered sets it extracts\n' +
534
535
  ' Exit: 0 verified pass · 1 violations · 2 not-verified (0 rules evaluated in scope).';
536
+ /**
537
+ * The honest exit code for a wiring run.
538
+ *
539
+ * The banner already said "Not a full green" when some rules were skipped, but
540
+ * the code an agent chains on returned `0` — a rule that enforced NOTHING was
541
+ * masked by its passing siblings. The verdict must match the sentence:
542
+ *
543
+ * `1` violations (or a `failOnEmpty` skip, which the engine folds into the verdict)
544
+ * `2` nothing evaluated, OR anything skipped — partially verified is not verified
545
+ * `0` only when every configured rule actually ran and passed
546
+ */
547
+ function wiringExitCode(report, evaluated) {
548
+ if (report.verdict === 'errors')
549
+ return ExitCode.Failure;
550
+ if (evaluated === 0)
551
+ return ExitCode.NotVerified;
552
+ if (report.skipped.length > 0)
553
+ return ExitCode.NotVerified;
554
+ return ExitCode.VerifiedPass;
555
+ }
556
+ /**
557
+ * `check wiring --fix` — append a declared-but-unregistered token to its sink
558
+ * array, but ONLY where the edit is mechanically unambiguous.
559
+ *
560
+ * Dry-run by default: it prints the exact diff it would make and writes
561
+ * nothing. `--write` applies it. Anything the planner will not touch is listed
562
+ * with the reason, because a gate that silently half-fixes is worse than one
563
+ * that does nothing — the user must be able to see what was left.
564
+ */
565
+ function runWiringFix(cwd, rules, report, write, wantJson) {
566
+ const allEdits = [];
567
+ const allSkips = [];
568
+ // Read every file a sink could live in, once.
569
+ const globs = [...new Set(rules.flatMap((r) => wiringGlobsOf(r)))];
570
+ const cache = readMatchingFiles(cwd, globs, new Set());
571
+ const files = [...cache.entries()].map(([path, content]) => ({ path, content }));
572
+ for (const rule of rules) {
573
+ const violations = report.violations.filter((v) => v.ruleId === rule.id);
574
+ if (violations.length === 0)
575
+ continue;
576
+ const plan = planWiringFix(rule, violations, files);
577
+ allEdits.push(...plan.edits);
578
+ allSkips.push(...plan.skipped);
579
+ }
580
+ // One write per file: the planner threads each file's content forward, so the
581
+ // LAST edit for a path carries every earlier insertion.
582
+ const finalByFile = new Map();
583
+ for (const e of allEdits)
584
+ finalByFile.set(e.file, e.nextContent);
585
+ let written = 0;
586
+ if (write) {
587
+ for (const [rel, content] of finalByFile) {
588
+ writeFileSync(nodePath.resolve(cwd, rel), content, 'utf8');
589
+ written += 1;
590
+ }
591
+ }
592
+ if (wantJson) {
593
+ process.stdout.write(asJson({
594
+ schema: 'sharkcraft.wiring-fix/v1',
595
+ applied: write,
596
+ filesWritten: written,
597
+ edits: allEdits.map(({ nextContent: _drop, ...rest }) => rest),
598
+ skipped: allSkips,
599
+ }) + '\n');
600
+ return allEdits.length === 0 && allSkips.length > 0 ? ExitCode.Failure : ExitCode.VerifiedPass;
601
+ }
602
+ process.stdout.write(header(write ? 'Wiring fix (applied)' : 'Wiring fix (dry run)'));
603
+ if (allEdits.length === 0 && allSkips.length === 0) {
604
+ process.stdout.write(' Nothing to fix — no declared-but-unregistered tokens.\n');
605
+ return ExitCode.VerifiedPass;
606
+ }
607
+ for (const e of allEdits) {
608
+ process.stdout.write(` ${write ? 'wrote ' : 'would add'} ${e.token} → ${e.file}:${e.line}\n`);
609
+ process.stdout.write(` + ${e.insert}\n`);
610
+ }
611
+ if (allSkips.length > 0) {
612
+ process.stdout.write(`\n Left untouched (${allSkips.length}) — not mechanically unambiguous:\n`);
613
+ for (const sk of allSkips) {
614
+ process.stdout.write(` • ${sk.token} [${sk.reason}] ${sk.detail}\n`);
615
+ }
616
+ }
617
+ process.stdout.write(write
618
+ ? `\n${written} file(s) written. Re-run \`shrk check wiring\` to confirm, and review the diff.\n`
619
+ : '\nDry run — nothing written. Re-run with `--write` to apply.\n');
620
+ // An unfixable violation still means the gate is red.
621
+ return allSkips.length > 0 ? ExitCode.Failure : ExitCode.VerifiedPass;
622
+ }
535
623
  async function checkWiring(args) {
536
624
  const cwd = resolveCwd(args);
537
625
  // `--help`/`-h` on the subverb documents its own scoping flags instead of
@@ -570,7 +658,7 @@ async function checkWiring(args) {
570
658
  const explainId = flagString(args, 'explain');
571
659
  if (!explainId) {
572
660
  process.stderr.write('Usage: shrk check wiring --explain <ruleId> [--json]\n');
573
- return 2;
661
+ return ExitCode.UsageError;
574
662
  }
575
663
  const rule = rules.find((r) => r.id === explainId);
576
664
  if (!rule) {
@@ -580,7 +668,7 @@ async function checkWiring(args) {
580
668
  return 2;
581
669
  }
582
670
  process.stderr.write(`No wiring rule "${explainId}". Configured rules: ${ids.length > 0 ? ids.join(', ') : '(none)'}\n`);
583
- return 2;
671
+ return ExitCode.UsageError;
584
672
  }
585
673
  return renderWiringExplain(explainWiring(cwd, rule), wantJson);
586
674
  }
@@ -637,6 +725,10 @@ async function checkWiring(args) {
637
725
  // blast radius, not just the rules whose literal files were edited (a25 §3.2).
638
726
  const selectedRuleIds = report.rules.map((r) => r.ruleId);
639
727
  const scoped = changedOnly || since !== undefined;
728
+ // ── --fix: deterministic, heavily-guarded autofix ─────────────────────
729
+ if (flagBool(args, 'fix')) {
730
+ return runWiringFix(cwd, rules, report, flagBool(args, 'write'), wantJson);
731
+ }
640
732
  if (wantJson) {
641
733
  // Carry the honest counts so a machine consumer can tell "0 evaluated" from
642
734
  // a real green, and see how many rules the scope skipped + which fired.
@@ -650,15 +742,31 @@ async function checkWiring(args) {
650
742
  notVerified,
651
743
  selectedRuleIds,
652
744
  // Distinguish verified-pass / failure / not-verified for a chained gate.
653
- exitCode: evaluated === 0 ? ExitCode.NotVerified : report.verdict === 'errors' ? ExitCode.Failure : ExitCode.VerifiedPass,
745
+ exitCode: wiringExitCode(report, evaluated),
746
+ // One shape across every plane — see docs/gate-json.md.
747
+ gate: buildGateEnvelope('check wiring', wiringExitCode(report, evaluated), report.rules.map((r) => {
748
+ const skip = report.skipped.find((s) => s.ruleId === r.ruleId);
749
+ return {
750
+ id: r.ruleId,
751
+ type: 'wiring',
752
+ status: r.status,
753
+ severity: r.severity,
754
+ counts: { declared: r.declaredCount, registered: r.registeredCount },
755
+ violations: r.violations.map((v) => ({
756
+ id: v.token,
757
+ file: v.file,
758
+ line: v.line,
759
+ ...(v.message ? { message: v.message } : {}),
760
+ ...(v.hint ? { hint: v.hint } : {}),
761
+ })),
762
+ ...(skip ? { skipReason: skip.reason } : {}),
763
+ ...(r.error ? { error: r.error } : {}),
764
+ };
765
+ })),
654
766
  }) + '\n');
655
767
  // `evaluated === 0` = nothing checked in scope → NOT verified (`2`), never a
656
768
  // silent `0` an agent's `&& next` would march past (a25 §1.1).
657
- return evaluated === 0
658
- ? ExitCode.NotVerified
659
- : report.verdict === 'errors'
660
- ? ExitCode.Failure
661
- : ExitCode.VerifiedPass;
769
+ return wiringExitCode(report, evaluated);
662
770
  }
663
771
  process.stdout.write(header('Wiring check'));
664
772
  // `evaluated` counts rules that actually ran a comparison (globs matched >0
@@ -686,7 +794,32 @@ async function checkWiring(args) {
686
794
  for (const d of report.diagnostics)
687
795
  process.stdout.write(` ! ${d}\n`);
688
796
  }
797
+ // Rules that checked NOTHING, reported per-rule. `matchedNothing` gives the
798
+ // count; this names them, because "which rule went stale" is the actionable
799
+ // half. A `failOnEmpty` rule turns its own skip into a hard failure.
800
+ if (report.skipped.length > 0) {
801
+ process.stdout.write('\nRules that checked nothing:\n');
802
+ for (const sk of report.skipped) {
803
+ process.stdout.write(` ${sk.failed ? '✗' : '–'} ${sk.ruleId} ${sk.failed ? 'FAILED' : 'SKIPPED'} — ${sk.reason}\n`);
804
+ }
805
+ if (report.skipped.some((sk) => !sk.failed)) {
806
+ process.stdout.write(' Fix the selector, or set `failOnEmpty: true` once the rule is known to have\n' +
807
+ ' real subjects. Run `shrk gates coverage` to audit every plane at once.\n');
808
+ }
809
+ }
810
+ // A sink that extracted 0 ids while the source had some is a real failure, but
811
+ // almost always a stale SINK glob — say so instead of listing N "unwired"
812
+ // tokens with no explanation.
813
+ for (const r of report.rules) {
814
+ if (!r.emptySink)
815
+ continue;
816
+ process.stdout.write(`\n ! ${r.ruleId}: the registered side extracted 0 ids while ${r.declaredCount} were declared —\n` +
817
+ ` every declared token "fails". Check the registered glob before chasing the tokens.\n`);
818
+ }
689
819
  if (report.violations.length === 0 && report.diagnostics.length === 0) {
820
+ // A failOnEmpty skip produces no violation object but IS a failure.
821
+ if (report.verdict === 'errors')
822
+ return ExitCode.Failure;
690
823
  if (allEvaluated) {
691
824
  // Every configured rule ran — the earned full green.
692
825
  process.stdout.write('\nNo wiring violations — every declared token is registered. ✓\n');
@@ -697,10 +830,10 @@ async function checkWiring(args) {
697
830
  process.stdout.write(`\nNo wiring violations among the ${evaluated} rule(s) evaluated — ` +
698
831
  `${notVerified} of ${configured} NOT verified${breakdown}. Not a full green.\n`);
699
832
  }
700
- return 0;
833
+ return wiringExitCode(report, evaluated);
701
834
  }
702
835
  if (report.violations.length === 0) {
703
- return report.verdict === 'errors' ? 1 : 0;
836
+ return wiringExitCode(report, evaluated);
704
837
  }
705
838
  // Group by rule for a readable report.
706
839
  for (const r of report.rules) {
@@ -709,7 +842,9 @@ async function checkWiring(args) {
709
842
  process.stdout.write(`\n[${r.severity}] ${r.ruleId}${r.description ? ' — ' + r.description : ''}\n`);
710
843
  process.stdout.write(` declared ${r.declaredCount} / registered ${r.registeredCount} — ${r.violations.length} not wired:\n`);
711
844
  for (const v of r.violations.slice(0, 50)) {
712
- process.stdout.write(` • ${v.token} (${v.file}:${v.line})\n`);
845
+ const where = `(${v.file}:${v.line})`;
846
+ const hop = v.hop !== undefined ? ` [hop ${v.hop}]` : '';
847
+ process.stdout.write(v.message ? ` • ${v.message} ${where}${hop}\n` : ` • ${v.token} ${where}${hop}\n`);
713
848
  }
714
849
  if (r.violations.length > 50) {
715
850
  process.stdout.write(` … (${r.violations.length - 50} more)\n`);
@@ -1 +1 @@
1
- {"version":3,"file":"command-catalog.d.ts","sourceRoot":"","sources":["../../src/commands/command-catalog.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"command-catalog.d.ts","sourceRoot":"","sources":["../../src/commands/command-catalog.ts"],"names":[],"mappings":"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"}
@@ -406,6 +406,126 @@ export const COMMAND_CATALOG = Object.freeze([
406
406
  surface: CommandSurface.Common,
407
407
  taskRole: CommandTaskRole.Validate,
408
408
  }),
409
+ entry({
410
+ command: 'check wiring --fix',
411
+ description: 'Deterministic autofix for `declared-but-not-registered`: append the missing id to its sink array. Dry-run by default; `--write` applies. Refuses anything ambiguous (N sinks, non-array sink, non-unique file/array) and lists what it left alone with the reason — it never guesses. [--write] [--json]',
412
+ category: 'core',
413
+ safetyLevel: SafetyLevel.WritesSource,
414
+ surface: CommandSurface.Advanced,
415
+ taskRole: CommandTaskRole.Apply,
416
+ }),
417
+ entry({
418
+ command: 'policy-lint explain',
419
+ 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]',
420
+ category: 'core',
421
+ safetyLevel: SafetyLevel.ReadOnly,
422
+ surface: CommandSurface.Advanced,
423
+ taskRole: CommandTaskRole.Explain,
424
+ }),
425
+ entry({
426
+ command: 'registry duplicates',
427
+ 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]',
428
+ category: 'core',
429
+ safetyLevel: SafetyLevel.ReadOnly,
430
+ surface: CommandSurface.Common,
431
+ taskRole: CommandTaskRole.Validate,
432
+ }),
433
+ entry({
434
+ command: 'gates list',
435
+ description: 'Every data-defined rule across every plane (wiring / policy / registry / registration / baseline / generated) with its severity and empty-match policy. [--plane <p>] [--json]',
436
+ category: 'core',
437
+ safetyLevel: SafetyLevel.ReadOnly,
438
+ surface: CommandSurface.Common,
439
+ taskRole: CommandTaskRole.Inspect,
440
+ }),
441
+ entry({
442
+ command: 'gates coverage',
443
+ 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]',
444
+ category: 'core',
445
+ safetyLevel: SafetyLevel.ReadOnly,
446
+ surface: CommandSurface.Common,
447
+ taskRole: CommandTaskRole.Validate,
448
+ }),
449
+ entry({
450
+ command: 'gates explain',
451
+ 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]',
452
+ category: 'core',
453
+ safetyLevel: SafetyLevel.ReadOnly,
454
+ surface: CommandSurface.Advanced,
455
+ taskRole: CommandTaskRole.Explain,
456
+ }),
457
+ entry({
458
+ command: 'baseline list',
459
+ description: 'Every declared baseline: what it pins, how it recomputes, and which direction of drift fails. [--json]',
460
+ category: 'core',
461
+ safetyLevel: SafetyLevel.ReadOnly,
462
+ surface: CommandSurface.Common,
463
+ taskRole: CommandTaskRole.Inspect,
464
+ }),
465
+ entry({
466
+ command: 'baseline check',
467
+ 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]',
468
+ category: 'core',
469
+ safetyLevel: SafetyLevel.RunsShell,
470
+ surface: CommandSurface.Common,
471
+ taskRole: CommandTaskRole.Validate,
472
+ }),
473
+ entry({
474
+ command: 'baseline diff',
475
+ description: 'Human-readable +added / −removed for each baseline, without a verdict — the inspection verb. [--id X] [--json]',
476
+ category: 'core',
477
+ safetyLevel: SafetyLevel.RunsShell,
478
+ surface: CommandSurface.Advanced,
479
+ taskRole: CommandTaskRole.Explain,
480
+ }),
481
+ entry({
482
+ command: 'baseline update',
483
+ 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]',
484
+ category: 'core',
485
+ safetyLevel: SafetyLevel.WritesSource,
486
+ surface: CommandSurface.Common,
487
+ taskRole: CommandTaskRole.Apply,
488
+ }),
489
+ entry({
490
+ command: 'baseline explain',
491
+ 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]',
492
+ category: 'core',
493
+ safetyLevel: SafetyLevel.RunsShell,
494
+ surface: CommandSurface.Advanced,
495
+ taskRole: CommandTaskRole.Explain,
496
+ }),
497
+ entry({
498
+ command: 'generated list',
499
+ description: 'Every declared generated-artifact rule: its globs, regen command, and "do not edit" header contract. [--json]',
500
+ category: 'core',
501
+ safetyLevel: SafetyLevel.ReadOnly,
502
+ surface: CommandSurface.Common,
503
+ taskRole: CommandTaskRole.Inspect,
504
+ }),
505
+ entry({
506
+ command: 'generated check',
507
+ 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]',
508
+ category: 'core',
509
+ safetyLevel: SafetyLevel.RunsShell,
510
+ surface: CommandSurface.Common,
511
+ taskRole: CommandTaskRole.Validate,
512
+ }),
513
+ entry({
514
+ command: 'generated update',
515
+ description: 'Run the declared regen command in place — the one-command bless step after an intentional source change. Writes files. [--id X] [--json]',
516
+ category: 'core',
517
+ safetyLevel: SafetyLevel.WritesSource,
518
+ surface: CommandSurface.Common,
519
+ taskRole: CommandTaskRole.Apply,
520
+ }),
521
+ entry({
522
+ command: 'generated explain',
523
+ description: 'What ONE generated-artifact rule sees right now: files matched, header-contract results, mislabel candidates — without running the regen. [--id X] [--json]',
524
+ category: 'core',
525
+ safetyLevel: SafetyLevel.ReadOnly,
526
+ surface: CommandSurface.Advanced,
527
+ taskRole: CommandTaskRole.Explain,
528
+ }),
409
529
  entry({
410
530
  command: 'wiring explain',
411
531
  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]',
@@ -1 +1 @@
1
- {"version":3,"file":"daily.commands.d.ts","sourceRoot":"","sources":["../../src/commands/daily.commands.ts"],"names":[],"mappings":"AAOA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAMhC,eAAO,MAAM,WAAW,EAAE,eAsFzB,CAAC;AAcF,eAAO,MAAM,WAAW,EAAE,eAoGzB,CAAC;AAKF,eAAO,MAAM,cAAc,EAAE,eAyG5B,CAAC"}
1
+ {"version":3,"file":"daily.commands.d.ts","sourceRoot":"","sources":["../../src/commands/daily.commands.ts"],"names":[],"mappings":"AAOA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAOhC,eAAO,MAAM,WAAW,EAAE,eAsFzB,CAAC;AAcF,eAAO,MAAM,WAAW,EAAE,eAoGzB,CAAC;AAKF,eAAO,MAAM,cAAc,EAAE,eAmH5B,CAAC"}
@@ -2,6 +2,7 @@ import { buildAiReadinessReport, buildTaskPacket, inspectSharkcraft, runDoctor,
2
2
  import { recommendPresets } from '@shrkcrft/presets';
3
3
  import { flagBool, flagNumber, resolveCwd, } from "../command-registry.js";
4
4
  import { asJson, header, kv } from "../output/format-output.js";
5
+ import { tryExplainGateRule } from "./gates.command.js";
5
6
  // ────────────────────────────────────────────────────────────────────────
6
7
  // shrk next — "what should I do right now in this repo?"
7
8
  // ────────────────────────────────────────────────────────────────────────
@@ -177,9 +178,18 @@ export const explainCommand = {
177
178
  async run(args) {
178
179
  const topic = args.positional.join(' ').trim();
179
180
  if (!topic) {
180
- process.stderr.write('Usage: shrk explain "<topic>"\n');
181
+ process.stderr.write('Usage: shrk explain "<topic>" (a topic, or the id of any data-defined rule)\n');
181
182
  return 2;
182
183
  }
184
+ // A single token that exactly names a declared rule on ANY plane is
185
+ // explained as that rule — the one entrypoint the trust layer promises.
186
+ // Anything else keeps the original topic search, so no existing call
187
+ // changes meaning.
188
+ if (args.positional.length === 1) {
189
+ const asRule = await tryExplainGateRule(args, topic);
190
+ if (asRule !== undefined)
191
+ return asRule;
192
+ }
183
193
  const inspection = await inspectSharkcraft({ cwd: resolveCwd(args) });
184
194
  const maxEntries = flagNumber(args, 'max-entries') ?? 6;
185
195
  const packet = buildTaskPacket(inspection, topic, { maxTokens: 2500 });
@@ -0,0 +1,16 @@
1
+ import { type ICommandHandler, type ParsedArgs } from '../command-registry.js';
2
+ export declare const gatesListCommand: ICommandHandler;
3
+ export declare const gatesCoverageCommand: ICommandHandler;
4
+ export declare const gatesExplainCommand: ICommandHandler;
5
+ /**
6
+ * Try to explain `id` as a data-defined rule on ANY plane.
7
+ *
8
+ * Returns the exit code when the id resolves to exactly one declared rule, or
9
+ * `undefined` when it is not a rule id at all — which lets `shrk explain` keep
10
+ * its original topic-search behaviour for everything else. This is the D2
11
+ * unification: a user holding a rule id no longer has to know which plane owns
12
+ * it, and no existing invocation changes meaning.
13
+ */
14
+ export declare function tryExplainGateRule(args: ParsedArgs, id: string): Promise<number | undefined>;
15
+ export declare const gatesCommand: ICommandHandler;
16
+ //# 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,EACpB,KAAK,UAAU,EAChB,MAAM,wBAAwB,CAAC;AAgFhC,eAAO,MAAM,gBAAgB,EAAE,eAsD9B,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,eAuGlC,CAAC;AAuCF,eAAO,MAAM,mBAAmB,EAAE,eA4FjC,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,UAAU,EAChB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAQ7B;AAED,eAAO,MAAM,YAAY,EAAE,eAe1B,CAAC"}