@shrkcrft/cli 0.1.0-alpha.23 → 0.1.0-alpha.24

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 (33) hide show
  1. package/dist/commands/check.command.d.ts.map +1 -1
  2. package/dist/commands/check.command.js +109 -2
  3. package/dist/commands/command-catalog.d.ts +11 -0
  4. package/dist/commands/command-catalog.d.ts.map +1 -1
  5. package/dist/commands/command-catalog.js +91 -0
  6. package/dist/commands/finish.command.d.ts +3 -0
  7. package/dist/commands/finish.command.d.ts.map +1 -0
  8. package/dist/commands/finish.command.js +70 -0
  9. package/dist/commands/graph-code-subverbs.d.ts.map +1 -1
  10. package/dist/commands/graph-code-subverbs.js +42 -23
  11. package/dist/commands/help.command.d.ts.map +1 -1
  12. package/dist/commands/help.command.js +20 -2
  13. package/dist/commands/impact.command.d.ts.map +1 -1
  14. package/dist/commands/impact.command.js +17 -22
  15. package/dist/commands/smart-context.command.d.ts.map +1 -1
  16. package/dist/commands/smart-context.command.js +14 -37
  17. package/dist/commands/trace.command.d.ts.map +1 -1
  18. package/dist/commands/trace.command.js +73 -3
  19. package/dist/commands/wiring.command.d.ts +12 -0
  20. package/dist/commands/wiring.command.d.ts.map +1 -0
  21. package/dist/commands/wiring.command.js +384 -0
  22. package/dist/diff/collect-changed-paths.d.ts +5 -3
  23. package/dist/diff/collect-changed-paths.d.ts.map +1 -1
  24. package/dist/diff/collect-changed-paths.js +73 -36
  25. package/dist/diff/deleted-orphans.d.ts +42 -0
  26. package/dist/diff/deleted-orphans.d.ts.map +1 -0
  27. package/dist/diff/deleted-orphans.js +46 -0
  28. package/dist/finish/run-finish.d.ts +62 -0
  29. package/dist/finish/run-finish.d.ts.map +1 -0
  30. package/dist/finish/run-finish.js +239 -0
  31. package/dist/main.d.ts.map +1 -1
  32. package/dist/main.js +4 -0
  33. package/package.json +33 -33
@@ -1 +1 @@
1
- {"version":3,"file":"check.command.d.ts","sourceRoot":"","sources":["../../src/commands/check.command.ts"],"names":[],"mappings":"AAoBA,OAAO,EAML,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAurBhC,eAAO,MAAM,YAAY,EAAE,eAyE1B,CAAC"}
1
+ {"version":3,"file":"check.command.d.ts","sourceRoot":"","sources":["../../src/commands/check.command.ts"],"names":[],"mappings":"AAoBA,OAAO,EAML,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAkzBhC,eAAO,MAAM,YAAY,EAAE,eA0E1B,CAAC"}
@@ -4,9 +4,11 @@ import * as nodePath from 'node:path';
4
4
  import { flagBool, flagNumber, flagString, flagVars, resolveCwd, } from "../command-registry.js";
5
5
  import { asJson, header, kv } from "../output/format-output.js";
6
6
  import { maybeRunInWatchMode } from "../output/watch-loop.js";
7
+ import { computeDeletedOrphans } from "../diff/deleted-orphans.js";
8
+ import { renderWiringExplain } from "./wiring.command.js";
7
9
  import { validateTemplateVariables } from '@shrkcrft/templates';
8
10
  import { FileChangeType, planGeneration } from '@shrkcrft/generator';
9
- import { evaluateBoundaries, loadTsconfigPaths, runWiring, scanImports, summarizeImports, } from '@shrkcrft/boundaries';
11
+ import { evaluateBoundaries, explainWiring, loadTsconfigPaths, runWiring, scanImports, summarizeImports, } from '@shrkcrft/boundaries';
10
12
  function knowledgeGroup(inspection) {
11
13
  const dup = inspection.validationIssues.filter((i) => i.code === 'duplicate-id');
12
14
  const missing = inspection.validationIssues.filter((i) => i.code !== 'duplicate-id');
@@ -542,6 +544,27 @@ async function checkWiring(args) {
542
544
  }
543
545
  const rules = loaded.value.config.wiringRules ?? [];
544
546
  const planeDiagnostics = loaded.value.planeDiagnostics;
547
+ // `--explain <ruleId>`: dry-run ONE rule and print the declared + registered
548
+ // sets it extracts (file:line), the set-difference, and the verdict — the
549
+ // author-loop view of what the gate sees, without re-running the whole gate.
550
+ if (args.flags.has('explain')) {
551
+ const explainId = flagString(args, 'explain');
552
+ if (!explainId) {
553
+ process.stderr.write('Usage: shrk check wiring --explain <ruleId> [--json]\n');
554
+ return 2;
555
+ }
556
+ const rule = rules.find((r) => r.id === explainId);
557
+ if (!rule) {
558
+ const ids = rules.map((r) => r.id);
559
+ if (wantJson) {
560
+ process.stdout.write(asJson({ ok: false, error: 'not-found', ruleId: explainId, available: ids }) + '\n');
561
+ return 2;
562
+ }
563
+ process.stderr.write(`No wiring rule "${explainId}". Configured rules: ${ids.length > 0 ? ids.join(', ') : '(none)'}\n`);
564
+ return 2;
565
+ }
566
+ return renderWiringExplain(explainWiring(cwd, rule), wantJson);
567
+ }
545
568
  if (rules.length === 0) {
546
569
  if (wantJson) {
547
570
  process.stdout.write(asJson({ schema: 'sharkcraft.wiring/v1', rules: [], violations: [], verdict: 'pass' }) + '\n');
@@ -619,12 +642,94 @@ async function checkWiring(args) {
619
642
  }
620
643
  return report.verdict === 'errors' ? 1 : 0;
621
644
  }
645
+ /**
646
+ * `shrk check orphans [--since <ref>] [--staged]`: first-class, diff-robust
647
+ * reverse-closure over REMOVED files. Reads the deleted files from the diff
648
+ * (vs `--since`, or the staged index with `--staged`) and queries the
649
+ * code-graph snapshot for surviving files that still import them or reference a
650
+ * symbol they declared — alias-resolved, incl. barrel re-exports. Each survivor
651
+ * is an error with `file:line`; the type checker misses a deleted barrel
652
+ * re-export or string-keyed registration, so this is a write-safety guard an
653
+ * agent can't replicate natively. Generalizes `impact --deleted`; pairs with
654
+ * the composite `finish` gate.
655
+ */
656
+ async function checkOrphans(args) {
657
+ const cwd = resolveCwd(args);
658
+ const wantJson = flagBool(args, 'json');
659
+ const since = flagString(args, 'since');
660
+ const staged = flagBool(args, 'staged');
661
+ const scan = await computeDeletedOrphans(cwd, {
662
+ ...(since ? { since } : {}),
663
+ ...(staged ? { staged: true } : {}),
664
+ });
665
+ if (!scan.ok) {
666
+ if (wantJson) {
667
+ process.stdout.write(asJson({
668
+ schema: 'sharkcraft.deleted-orphans/v1',
669
+ error: scan.error,
670
+ reason: scan.reason,
671
+ orphans: [],
672
+ }) + '\n');
673
+ return 2;
674
+ }
675
+ process.stdout.write(header('Orphan check'));
676
+ process.stdout.write(scan.reason === 'diff-unavailable'
677
+ ? ` ✗ Cannot resolve diff: ${scan.error ?? 'unknown'}\n`
678
+ : ` ✗ ${scan.error ?? 'orphan check unavailable'}\n`);
679
+ return 2;
680
+ }
681
+ const scopeLabel = staged ? 'staged' : `vs ${scan.ref}`;
682
+ // Nothing deleted → there is nothing to check. Report a LOUD skip, not a
683
+ // green "no orphans" — "checked nothing" must never read as "verified clean".
684
+ if (scan.deleted.length === 0) {
685
+ if (wantJson) {
686
+ process.stdout.write(asJson({
687
+ schema: 'sharkcraft.deleted-orphans/v1',
688
+ skipped: true,
689
+ ref: scan.ref,
690
+ resolvedDeleted: [],
691
+ unresolvedDeleted: [],
692
+ orphans: [],
693
+ diagnostics: [`no deleted files (${scopeLabel}) — nothing to check`],
694
+ }) + '\n');
695
+ return 0;
696
+ }
697
+ process.stdout.write(header('Orphan check'));
698
+ process.stdout.write(` ! Nothing deleted (${scopeLabel}) — orphan check skipped.\n`);
699
+ return 0;
700
+ }
701
+ const report = scan.report;
702
+ if (wantJson) {
703
+ process.stdout.write(asJson(report) + '\n');
704
+ return report.orphans.length > 0 ? 1 : 0;
705
+ }
706
+ process.stdout.write(header('Orphan check'));
707
+ process.stdout.write(kv('deleted files', `${scan.deleted.length} (${scopeLabel})`) + '\n');
708
+ if (report.orphans.length === 0) {
709
+ process.stdout.write('\nNo orphaned importers — nothing still references the deleted code. ✓\n');
710
+ for (const d of report.diagnostics.slice(0, 5))
711
+ process.stdout.write(` ! ${d}\n`);
712
+ return 0;
713
+ }
714
+ process.stdout.write(`\n${report.orphans.length} surviving importer(s) still reference deleted code:\n`);
715
+ for (const o of report.orphans.slice(0, 100)) {
716
+ const loc = o.path ? `${o.path}${o.line ? `:${o.line}` : ''}` : o.id;
717
+ const detail = o.via === 'reference' && o.symbol ? `references \`${o.symbol}\`` : 'imports';
718
+ process.stdout.write(` ✗ ${loc} ${detail} from deleted ${o.deletedFile}\n`);
719
+ }
720
+ if (report.orphans.length > 100) {
721
+ process.stdout.write(` … (${report.orphans.length - 100} more)\n`);
722
+ }
723
+ for (const d of report.diagnostics.slice(0, 5))
724
+ process.stdout.write(` ! ${d}\n`);
725
+ return 1;
726
+ }
622
727
  // Main shrk check + subcommands
623
728
  // ────────────────────────────────────────────────────────────────────────
624
729
  export const checkCommand = {
625
730
  name: 'check',
626
731
  description: 'Run SharkCraft-level validation across knowledge / rules / templates / pipelines / packs / action hints / doctor. `check boundaries [--watch [--paths a,b] [--debounce N] [--once]]` re-runs the boundary scan on file changes.',
627
- usage: 'shrk [--cwd <dir>] check [packs|pipelines|knowledge|generation|boundaries|imports|wiring] [--strict] [--min-score <0-100>] [--changed-only] [--since <ref>] [--only <ids>] [--json] [--watch [--paths <list>] [--debounce N] [--once]]',
732
+ usage: 'shrk [--cwd <dir>] check [packs|pipelines|knowledge|generation|boundaries|imports|wiring|orphans] [--strict] [--min-score <0-100>] [--changed-only] [--since <ref>] [--staged] [--only <ids>] [--json] [--watch [--paths <list>] [--debounce N] [--once]]',
628
733
  async run(args) {
629
734
  const sub = args.positional[0];
630
735
  // `check generation <id> <name>` legitimately takes extra positionals;
@@ -647,6 +752,8 @@ export const checkCommand = {
647
752
  return checkImports(args);
648
753
  if (sub === 'wiring')
649
754
  return checkWiring(args);
755
+ if (sub === 'orphans')
756
+ return checkOrphans(args);
650
757
  if (sub === 'registry-lifecycle') {
651
758
  const cwd = resolveCwd(args);
652
759
  const { buildRegistryLifecycleReport, renderRegistryLifecycleReportText } = await import('@shrkcrft/inspector');
@@ -245,6 +245,17 @@ export declare function commandLifecycle(e: ICommandCatalogEntry): CommandLifecy
245
245
  * membership.
246
246
  */
247
247
  export declare function defaultShowInHelp(e: ICommandCatalogEntry): boolean;
248
+ /**
249
+ * The "explain / dry-run" command family: entries that show you what a gate,
250
+ * ranker, or graph *sees* before you act (`search tuning explain`, `wiring
251
+ * explain`/`wiring test`, `boundaries explain`, `surface explain`, …). Several
252
+ * carry an Advanced surface, so {@link defaultShowInHelp} hides them despite
253
+ * being high-value — which is exactly why an agent finds them only by guessing.
254
+ * `shrk --full-help` lists this family in a dedicated section so they stop
255
+ * being hidden. Returns callable entries only (retired/deprecated/alias and
256
+ * R46-pruned are excluded), sorted by command.
257
+ */
258
+ export declare function listExplainFamily(): readonly ICommandCatalogEntry[];
248
259
  /**
249
260
  * Short "Use this when…" line derived from `surface`, `taskRole`,
250
261
  * `preferredCommand`, `replacedBy`, and `machineOnly`. Empty string when
@@ -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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"}
@@ -390,6 +390,14 @@ export const COMMAND_CATALOG = Object.freeze([
390
390
  surface: CommandSurface.Common,
391
391
  taskRole: CommandTaskRole.Validate,
392
392
  }),
393
+ entry({
394
+ command: 'check orphans',
395
+ description: 'Write-safety guard: after deleting file(s)/export(s), finds surviving files that still import them or reference a symbol they declared — alias-resolved, incl. barrel re-exports the type checker misses. Reverse-closure over the diff vs --since (or --staged) against the code-graph snapshot; each survivor reported with file:line. [--since <ref>] [--staged] [--json]',
396
+ category: 'core',
397
+ safetyLevel: SafetyLevel.ReadOnly,
398
+ surface: CommandSurface.Common,
399
+ taskRole: CommandTaskRole.Validate,
400
+ }),
393
401
  entry({
394
402
  command: 'policy-lint',
395
403
  description: 'Lint template/markup, stylesheet, and AOT-invisible TS surfaces against config-defined policyRules[] — sees `.html` files AND inline `template:` strings that tsc/AOT cannot. Deterministic; no AI. [--surface template|style|ts] [--changed-only] [--only <ids>] [--json]',
@@ -398,6 +406,46 @@ export const COMMAND_CATALOG = Object.freeze([
398
406
  surface: CommandSurface.Common,
399
407
  taskRole: CommandTaskRole.Validate,
400
408
  }),
409
+ entry({
410
+ command: 'wiring explain',
411
+ 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]',
412
+ category: 'core',
413
+ safetyLevel: SafetyLevel.ReadOnly,
414
+ surface: CommandSurface.Advanced,
415
+ taskRole: CommandTaskRole.Explain,
416
+ }),
417
+ entry({
418
+ command: 'wiring test',
419
+ description: 'Dry-run an EPHEMERAL candidate wiring rule (a .json file or inline JSON) against the live tree — see the declared/registered sets + diff it would produce before committing it to sharkcraft.config.ts. Never writes config. [--json]',
420
+ category: 'core',
421
+ safetyLevel: SafetyLevel.ReadOnly,
422
+ surface: CommandSurface.Advanced,
423
+ taskRole: CommandTaskRole.Explain,
424
+ }),
425
+ entry({
426
+ command: 'wiring chain',
427
+ description: 'Registration/DI graph query: the declared → provided → consumed chain of a token across files/layers, alias-resolved, with file:line at each hop + a verdict (wired / unprovided / orphan). Models runtime wiring imports can\'t see. Idioms from sharkcraft.config.ts registrationGraph[]. [--json]',
428
+ category: 'analysis',
429
+ safetyLevel: SafetyLevel.ReadOnly,
430
+ surface: CommandSurface.Advanced,
431
+ taskRole: CommandTaskRole.Inspect,
432
+ }),
433
+ entry({
434
+ command: 'wiring unprovided',
435
+ description: 'Registration/DI graph query: tokens DECLARED or INJECTED but never PROVIDED — the silent-at-runtime class (typecheck/AOT-green, resolves to undefined at runtime). The write-safety check imports/grep structurally can\'t do. [--json]',
436
+ category: 'analysis',
437
+ safetyLevel: SafetyLevel.ReadOnly,
438
+ surface: CommandSurface.Advanced,
439
+ taskRole: CommandTaskRole.Validate,
440
+ }),
441
+ entry({
442
+ command: 'wiring orphans',
443
+ description: 'Registration/DI graph query: tokens PROVIDED/registered that nothing CONSUMES — a build-clean dead registration (or a sign the consumer was renamed/removed). [--json]',
444
+ category: 'analysis',
445
+ safetyLevel: SafetyLevel.ReadOnly,
446
+ surface: CommandSurface.Advanced,
447
+ taskRole: CommandTaskRole.Inspect,
448
+ }),
401
449
  entry({
402
450
  command: 'reuse',
403
451
  description: 'Intent → the canonical primitive to reuse: matches your intent against config reusePrimitives[] then resolves the symbol through the code graph (transitive star-barrels) to its import path, sibling exports, and real consumer files to copy. Read-only; no AI.',
@@ -415,6 +463,14 @@ export const COMMAND_CATALOG = Object.freeze([
415
463
  surface: CommandSurface.Common,
416
464
  taskRole: CommandTaskRole.Validate,
417
465
  }),
466
+ entry({
467
+ command: 'finish',
468
+ description: 'Composite "is this changeset safe to finish?" gate: EXECUTES boundaries + import-hygiene + wiring + policy + deleted-orphans changed-only inline, plus an impact summary, and returns ONE pass/fail. The trustworthy "done?" call after editing — superset of diff-check; honors 0-rules→skipped. Read-only. [files… | --staged | --since <ref>] [--json]',
469
+ category: 'core',
470
+ safetyLevel: SafetyLevel.ReadOnly,
471
+ surface: CommandSurface.Common,
472
+ taskRole: CommandTaskRole.Validate,
473
+ }),
418
474
  entry({
419
475
  command: 'review',
420
476
  description: 'PR-review packet — changed files, affected rules, missing tests heuristic.',
@@ -1658,6 +1714,14 @@ export const COMMAND_CATALOG = Object.freeze([
1658
1714
  safetyLevel: SafetyLevel.ReadOnly,
1659
1715
  mcpAvailable: true,
1660
1716
  }),
1717
+ entry({
1718
+ command: 'trace literal',
1719
+ description: 'Trace an EXACT string literal across the codebase, classified by direction: declare → register → consume sites of a cross-fence contract (kind slug, permission id, route key) the type system can\'t link and grep can\'t classify. Resolves `const X = "lit"` aliases. Generalizes `registry … where` to any literal — no pre-declared registry. [--glob <g>] [--no-aliases] [--limit N] [--json]',
1720
+ category: 'analysis',
1721
+ safetyLevel: SafetyLevel.ReadOnly,
1722
+ surface: CommandSurface.Advanced,
1723
+ taskRole: CommandTaskRole.Inspect,
1724
+ }),
1661
1725
  entry({
1662
1726
  command: 'feedback',
1663
1727
  description: 'Feedback ingestion (ingest|summarize|actions|convert-to-backlog). Read-only.',
@@ -3664,6 +3728,7 @@ const PRIMARY_VERBS_ALLOWLIST = new Set([
3664
3728
  'gen',
3665
3729
  'apply',
3666
3730
  'check',
3731
+ 'finish',
3667
3732
  'quality',
3668
3733
  'plan',
3669
3734
  'fix',
@@ -3734,6 +3799,32 @@ export function defaultShowInHelp(e) {
3734
3799
  const surface = commandSurface(e);
3735
3800
  return surface === CommandSurface.Primary || surface === CommandSurface.Common;
3736
3801
  }
3802
+ /**
3803
+ * The "explain / dry-run" command family: entries that show you what a gate,
3804
+ * ranker, or graph *sees* before you act (`search tuning explain`, `wiring
3805
+ * explain`/`wiring test`, `boundaries explain`, `surface explain`, …). Several
3806
+ * carry an Advanced surface, so {@link defaultShowInHelp} hides them despite
3807
+ * being high-value — which is exactly why an agent finds them only by guessing.
3808
+ * `shrk --full-help` lists this family in a dedicated section so they stop
3809
+ * being hidden. Returns callable entries only (retired/deprecated/alias and
3810
+ * R46-pruned are excluded), sorted by command.
3811
+ */
3812
+ export function listExplainFamily() {
3813
+ return COMMAND_CATALOG.filter((e) => {
3814
+ const lc = commandLifecycle(e);
3815
+ if (lc === CommandLifecycle.Deprecated ||
3816
+ lc === CommandLifecycle.Retired ||
3817
+ lc === CommandLifecycle.Alias) {
3818
+ return false;
3819
+ }
3820
+ if (R46_OVERLAY[e.command])
3821
+ return false;
3822
+ const role = commandTaskRole(e);
3823
+ return role === CommandTaskRole.Explain || /(^|\s)explain$/.test(e.command);
3824
+ })
3825
+ .slice()
3826
+ .sort((a, b) => a.command.localeCompare(b.command));
3827
+ }
3737
3828
  /**
3738
3829
  * Short "Use this when…" line derived from `surface`, `taskRole`,
3739
3830
  * `preferredCommand`, `replacedBy`, and `machineOnly`. Empty string when
@@ -0,0 +1,3 @@
1
+ import { type ICommandHandler } from '../command-registry.js';
2
+ export declare const finishCommand: ICommandHandler;
3
+ //# sourceMappingURL=finish.command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finish.command.d.ts","sourceRoot":"","sources":["../../src/commands/finish.command.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AA8DhC,eAAO,MAAM,aAAa,EAAE,eAmB3B,CAAC"}
@@ -0,0 +1,70 @@
1
+ import { flagBool, flagString, resolveCwd, } from "../command-registry.js";
2
+ import { asJson, bullet, header, kv } from "../output/format-output.js";
3
+ import { runFinishGates } from "../finish/run-finish.js";
4
+ function resolveScope(args, cwd) {
5
+ const staged = flagBool(args, 'staged');
6
+ const since = flagString(args, 'since');
7
+ const filesRaw = flagString(args, 'files');
8
+ const files = filesRaw
9
+ ? filesRaw.split(',').map((s) => s.trim()).filter((s) => s.length > 0)
10
+ : args.positional.filter((s) => s.length > 0);
11
+ if (files.length > 0)
12
+ return { mode: 'files', options: { projectRoot: cwd, files } };
13
+ if (staged)
14
+ return { mode: 'staged', options: { projectRoot: cwd, staged: true } };
15
+ if (since)
16
+ return { mode: 'since', options: { projectRoot: cwd, since } };
17
+ return { mode: 'worktree', options: { projectRoot: cwd, includeWorktree: true } };
18
+ }
19
+ const STATUS_GLYPH = {
20
+ pass: '✓',
21
+ fail: '✗',
22
+ skipped: '–',
23
+ };
24
+ function renderText(report) {
25
+ process.stdout.write(header('Finish — is this changeset safe to complete?'));
26
+ process.stdout.write(kv('scope', `${report.scope.mode} (${report.scope.fileCount} file${report.scope.fileCount === 1 ? '' : 's'})`) +
27
+ '\n');
28
+ for (const g of report.gates) {
29
+ process.stdout.write(` ${STATUS_GLYPH[g.status]} ${g.name.padEnd(11)} ${g.status.padEnd(8)} ${g.detail}\n`);
30
+ }
31
+ if (report.impact.ran) {
32
+ process.stdout.write(kv('impact', `risk=${report.impact.risk}, ${report.impact.directDependents} direct / ${report.impact.transitiveDependents} transitive dependents`) +
33
+ '\n');
34
+ }
35
+ else if (report.impact.note) {
36
+ process.stdout.write(kv('impact', `(skipped — ${report.impact.note})`) + '\n');
37
+ }
38
+ process.stdout.write(kv('verdict', report.verdict) + '\n\n');
39
+ process.stdout.write(report.summary + '\n');
40
+ const failing = report.gates.filter((g) => g.status === 'fail');
41
+ for (const g of failing) {
42
+ process.stdout.write(`\n${g.name} — failing items:\n`);
43
+ for (const item of g.items.slice(0, 15)) {
44
+ const loc = item.file ? `${item.file}${item.line ? `:${item.line}` : ''}` : '';
45
+ process.stdout.write(bullet(`${loc ? loc + ' — ' : ''}${item.message}`) + '\n');
46
+ }
47
+ if (g.items.length > 15) {
48
+ process.stdout.write(` … and ${g.items.length - 15} more (pass --json for the full list).\n`);
49
+ }
50
+ }
51
+ process.stdout.write(`\nNext: ${report.nextAction}\n`);
52
+ }
53
+ export const finishCommand = {
54
+ name: 'finish',
55
+ description: 'Composite "is this changeset safe to finish?" gate: EXECUTES every deterministic changed-only check inline — boundaries + import-hygiene + wiring + policy + deleted-orphans — plus an impact summary, and returns ONE pass/fail. The single trustworthy "done?" call after editing (superset of `diff-check`; honors 0-rules→skipped). Read-only.',
56
+ usage: 'shrk [--cwd <dir>] finish [files... | --files a.ts,b.ts | --staged | --since <ref>] [--json]',
57
+ booleanFlags: new Set(['json', 'staged']),
58
+ async run(args) {
59
+ const cwd = resolveCwd(args);
60
+ const wantJson = flagBool(args, 'json');
61
+ const { mode, options } = resolveScope(args, cwd);
62
+ const report = await runFinishGates({ cwd, mode, scope: options });
63
+ if (wantJson) {
64
+ process.stdout.write(asJson(report) + '\n');
65
+ return report.verdict === 'fail' ? 1 : 0;
66
+ }
67
+ renderText(report);
68
+ return report.verdict === 'fail' ? 1 : 0;
69
+ },
70
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"graph-code-subverbs.d.ts","sourceRoot":"","sources":["../../src/commands/graph-code-subverbs.ts"],"names":[],"mappings":"AA2BA,OAAO,EAAqD,KAAK,UAAU,EAAE,MAAM,wBAAwB,CAAC;AA+J5G,wBAAsB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAkBrE;AA4FD,wBAAsB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAkEtE;AAiBD,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA2F1E;AAID,wBAAsB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA+EpE;AAID,wBAAsB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA+FtE;AAID,wBAAsB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAwEtE;AAID,wBAAsB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA0MvE;AAID,wBAAsB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA8HtE;AAID;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAgEpE;AAID,wBAAsB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAkGvE;AAyBD;;;;;;;;;;;;GAYG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAgHpE"}
1
+ {"version":3,"file":"graph-code-subverbs.d.ts","sourceRoot":"","sources":["../../src/commands/graph-code-subverbs.ts"],"names":[],"mappings":"AA2BA,OAAO,EAAiE,KAAK,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAgLxH,wBAAsB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAkBrE;AA4FD,wBAAsB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAkEtE;AAiBD,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA2F1E;AAID,wBAAsB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA+EpE;AAID,wBAAsB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA+FtE;AAID,wBAAsB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAwEtE;AAID,wBAAsB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA2MvE;AAID,wBAAsB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA+HtE;AAID;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAgEpE;AAID,wBAAsB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAiGvE;AAyBD;;;;;;;;;;;;GAYG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAgHpE"}
@@ -12,7 +12,7 @@ import { FrameworkQueryApi, FrameworkStore } from '@shrkcrft/framework-scanners'
12
12
  import { existsSync } from 'node:fs';
13
13
  import * as nodePath from 'node:path';
14
14
  import { compactArrayToColumnar } from '@shrkcrft/compress';
15
- import { flagBool, flagPositiveInt, flagString, resolveCwd } from "../command-registry.js";
15
+ import { flagBool, flagNumber, flagPositiveInt, flagString, resolveCwd } from "../command-registry.js";
16
16
  import { asJson, header, kv } from "../output/format-output.js";
17
17
  import { maybeRunInWatchMode } from "../output/watch-loop.js";
18
18
  /**
@@ -67,6 +67,24 @@ const CORRUPT_HINT = 'code graph store is corrupt — run `shrk graph index` to
67
67
  * runGraphCallers). Kept in sync with the MCP `get_graph_context` tool.
68
68
  */
69
69
  const CONTEXT_LIST_CAP = 50;
70
+ /**
71
+ * Resolve a `--limit N` per-list cap for the graph read commands. Absent → the
72
+ * command's `fallback` (the historical default — 50 for `context`, 200 for
73
+ * `callers`/`impact`); `--limit 0` (or any non-positive) → unbounded so a
74
+ * consumer can pull the full set off a high-fan-in barrel/hub in one call.
75
+ * Returns `Infinity` for the unbounded case: `arr.slice(0, Infinity)` yields
76
+ * every row and `len > Infinity` is `false`, so the `<list>Truncated` flag stays
77
+ * honest (never "truncated" when nothing was dropped). Malformed `--limit abc`
78
+ * (NaN) degrades to `fallback`, matching `flagPositiveInt`.
79
+ */
80
+ function resolveListLimit(args, fallback) {
81
+ const v = flagNumber(args, 'limit');
82
+ if (v === undefined)
83
+ return fallback;
84
+ if (v <= 0)
85
+ return Number.POSITIVE_INFINITY;
86
+ return Math.floor(v);
87
+ }
70
88
  /**
71
89
  * Refresh-by-default: incrementally reindex changed/deleted files BEFORE
72
90
  * querying so an agent's just-saved edits are reflected, then print a one-line
@@ -659,10 +677,11 @@ export async function runGraphContext(args) {
659
677
  const wantJson = flagBool(args, 'json');
660
678
  const target = args.positional[1];
661
679
  if (!target) {
662
- process.stderr.write('Usage: shrk graph context <fileOrSymbol> [--depth N] [--no-bridge] [--no-framework]\n');
680
+ process.stderr.write('Usage: shrk graph context <fileOrSymbol> [--depth N] [--limit N|0] [--no-bridge] [--no-framework]\n');
663
681
  return 2;
664
682
  }
665
683
  const depth = Math.min(3, flagPositiveInt(args, 'depth', 1));
684
+ const cap = resolveListLimit(args, CONTEXT_LIST_CAP);
666
685
  const includeBridge = !flagBool(args, 'no-bridge');
667
686
  const includeFramework = !flagBool(args, 'no-framework');
668
687
  maybeRefresh(args, cwd);
@@ -708,13 +727,13 @@ export async function runGraphContext(args) {
708
727
  const importsFromAll = neighbours.out.filter((o) => o.edge.kind === 'imports-file');
709
728
  const importedByAll = neighbours.in.filter((i) => i.edge.kind === 'imports-file');
710
729
  const importsFromList = importsFromAll
711
- .slice(0, CONTEXT_LIST_CAP)
730
+ .slice(0, cap)
712
731
  .map((o) => ('target' in o ? targetSummary(o.target) : { id: 'unknown', resolved: false }));
713
732
  const importedByList = importedByAll
714
- .slice(0, CONTEXT_LIST_CAP)
733
+ .slice(0, cap)
715
734
  .map((i) => ('source' in i ? sourceSummary(i.source) : { id: 'unknown', resolved: false }));
716
- const referencedByList = references.slice(0, CONTEXT_LIST_CAP).map(nodeSummary);
717
- const calledByList = callers.slice(0, CONTEXT_LIST_CAP).map(nodeSummary);
735
+ const referencedByList = references.slice(0, cap).map(nodeSummary);
736
+ const calledByList = callers.slice(0, cap).map(nodeSummary);
718
737
  // Staleness over the anchor + every referenced file: drop dead paths from the
719
738
  // usage lists, flag changed ones.
720
739
  const ctxPathOf = (x) => x.path;
@@ -733,19 +752,19 @@ export async function runGraphContext(args) {
733
752
  depth,
734
753
  importsFrom: ctxDropDel(importsFromList),
735
754
  totalImportsFrom: importsFromAll.length,
736
- importsFromTruncated: importsFromAll.length > CONTEXT_LIST_CAP,
755
+ importsFromTruncated: importsFromAll.length > cap,
737
756
  importedBy: ctxDropDel(importedByList),
738
757
  totalImportedBy: importedByAll.length,
739
- importedByTruncated: importedByAll.length > CONTEXT_LIST_CAP,
740
- symbols: symbols.slice(0, 50).map(nodeSummary),
758
+ importedByTruncated: importedByAll.length > cap,
759
+ symbols: symbols.slice(0, cap).map(nodeSummary),
741
760
  referencedBy: ctxDropDel(referencedByList),
742
761
  totalReferencedBy: references.length,
743
- referencedByTruncated: references.length > CONTEXT_LIST_CAP,
762
+ referencedByTruncated: references.length > cap,
744
763
  calledBy: ctxDropDel(calledByList),
745
764
  totalCalledBy: callers.length,
746
- calledByTruncated: callers.length > CONTEXT_LIST_CAP,
747
- ...(subtypes.length > 0 ? { subtypes: subtypes.slice(0, 50).map(nodeSummary) } : {}),
748
- ...(supertypes.length > 0 ? { supertypes: supertypes.slice(0, 50).map(nodeSummary) } : {}),
765
+ calledByTruncated: callers.length > cap,
766
+ ...(subtypes.length > 0 ? { subtypes: subtypes.slice(0, cap).map(nodeSummary) } : {}),
767
+ ...(supertypes.length > 0 ? { supertypes: supertypes.slice(0, cap).map(nodeSummary) } : {}),
749
768
  ...(fresh.field ?? {}),
750
769
  bridge: bridgeFor
751
770
  ? {
@@ -860,11 +879,11 @@ export async function runGraphImpact(args) {
860
879
  const wantFull = flagBool(args, 'full');
861
880
  const target = args.positional[1];
862
881
  if (!target) {
863
- process.stderr.write('Usage: shrk graph impact <fileOrSymbol> [--max-depth N] [--limit N] [--full]\n');
882
+ process.stderr.write('Usage: shrk graph impact <fileOrSymbol> [--max-depth N] [--limit N|0] [--full]\n');
864
883
  return 2;
865
884
  }
866
885
  const maxDepth = Math.min(10, flagPositiveInt(args, 'max-depth', 5));
867
- const limit = flagPositiveInt(args, 'limit', 200);
886
+ const limit = resolveListLimit(args, 200);
868
887
  maybeRefresh(args, cwd);
869
888
  // --full → delegate to the impact-engine for a richer v3 payload.
870
889
  if (wantFull) {
@@ -949,7 +968,8 @@ export async function runGraphImpact(args) {
949
968
  schema: 'sharkcraft.graph-impact/v1',
950
969
  anchor: nodeSummary(anchor),
951
970
  maxDepth,
952
- limit,
971
+ // 0 signals "unbounded" (`--limit 0`); JSON can't carry Infinity.
972
+ limit: Number.isFinite(limit) ? limit : 0,
953
973
  truncated: closure.truncated,
954
974
  directDependents: liveDirect,
955
975
  transitiveDependents: liveTransitive,
@@ -1052,16 +1072,15 @@ export async function runGraphCallers(args) {
1052
1072
  const wantJson = flagBool(args, 'json');
1053
1073
  const target = args.positional[1];
1054
1074
  if (!target) {
1055
- process.stderr.write('Usage: shrk graph callers <symbol> [--mode call|reference] [--limit N] [--no-refresh]\n');
1075
+ process.stderr.write('Usage: shrk graph callers <symbol> [--mode call|reference] [--limit N|0] [--no-refresh]\n');
1056
1076
  return 2;
1057
1077
  }
1058
1078
  const mode = (flagString(args, 'mode') ?? 'call');
1059
- // --limit N: cap the returned call sites (default 200). `total` still reports
1060
- // the true uncapped count, so a truncated result stays honest. Guard against
1061
- // non-numeric input — `Number('foo')` is NaN and `slice(0, NaN)` would zero
1062
- // the callers list while `total` kept showing the real count.
1063
- const parsedLimit = Number.parseInt(flagString(args, 'limit') ?? '200', 10);
1064
- const limit = Number.isFinite(parsedLimit) && parsedLimit > 0 ? parsedLimit : 200;
1079
+ // --limit N: cap the returned call sites (default 200; `--limit 0` = all).
1080
+ // `total` still reports the true uncapped count, so a truncated result stays
1081
+ // honest. resolveListLimit guards non-numeric input (NaN fallback) so a
1082
+ // fat-fingered `--limit foo` can't zero the list via `slice(0, NaN)`.
1083
+ const limit = resolveListLimit(args, 200);
1065
1084
  maybeRefresh(args, cwd);
1066
1085
  const api = loadOrFail(cwd, wantJson);
1067
1086
  if (!api)
@@ -1 +1 @@
1
- {"version":3,"file":"help.command.d.ts","sourceRoot":"","sources":["../../src/commands/help.command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAqB9D;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAwC1C;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,eAAe;;;;cAK3C;QAAE,UAAU,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAA;KAAE,GAAG,MAAM;EA2HpF"}
1
+ {"version":3,"file":"help.command.d.ts","sourceRoot":"","sources":["../../src/commands/help.command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AA4B9D;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAwC1C;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,eAAe;;;;cAK3C;QAAE,UAAU,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAA;KAAE,GAAG,MAAM;EAwIpF"}
@@ -1,5 +1,11 @@
1
1
  import { header } from "../output/format-output.js";
2
- import { COMMAND_CATALOG, defaultShowInHelp } from "./command-catalog.js";
2
+ import { COMMAND_CATALOG, defaultShowInHelp, listExplainFamily } from "./command-catalog.js";
3
+ /** First sentence of a catalog description, for the compact explain-family list. */
4
+ function firstSentence(description) {
5
+ const dot = description.indexOf('. ');
6
+ const head = dot > 0 ? description.slice(0, dot + 1) : description;
7
+ return head.length > 100 ? head.slice(0, 97).trimEnd() + '…' : head;
8
+ }
3
9
  const EXTRA_HELP_LINES = Object.freeze({
4
10
  graph: [
5
11
  '',
@@ -60,7 +66,7 @@ export function renderStartScreen() {
60
66
  lines.push('Discover the rest (everything stays callable — this screen shows ~17 of ~70 verbs):');
61
67
  lines.push(' $ shrk surface list — full catalog by tier');
62
68
  lines.push(' $ shrk help <command> — usage for a specific command');
63
- lines.push(' $ shrk --full-help — long, exhaustive help');
69
+ lines.push(' $ shrk --full-help — long, exhaustive help (incl. the explain/dry-run family)');
64
70
  lines.push(' $ shrk --about — what shrk is and is not');
65
71
  lines.push('');
66
72
  lines.push('Free-form input is fine — `shrk "<task>"` routes to `shrk recommend`.');
@@ -188,6 +194,18 @@ export function makeHelpCommand(registry) {
188
194
  process.stdout.write(` ${group} ${s.name.padEnd(10)} — ${s.description}\n`);
189
195
  }
190
196
  }
197
+ // Explain / dry-run family — several carry an Advanced surface and are
198
+ // filtered out of the listings above, so surface them explicitly. These
199
+ // show you what a gate, ranker, or graph SEES before you act; an agent
200
+ // would otherwise find them only by guessing (the #4.4 friction).
201
+ const explainFamily = listExplainFamily();
202
+ if (explainFamily.length > 0) {
203
+ process.stdout.write(header('Inspect / explain (dry-run what a gate, ranker, or graph sees)'));
204
+ for (const e of explainFamily) {
205
+ process.stdout.write(` ${e.command.padEnd(24)} — ${firstSentence(e.description)}\n`);
206
+ }
207
+ process.stdout.write(' (also: shrk check wiring --explain <ruleId>)\n');
208
+ }
191
209
  process.stdout.write('\nRun `shrk help <command>` for detailed usage.\n');
192
210
  return 0;
193
211
  },
@@ -1 +1 @@
1
- {"version":3,"file":"impact.command.d.ts","sourceRoot":"","sources":["../../src/commands/impact.command.ts"],"names":[],"mappings":"AAsBA,OAAO,EAML,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAibhC,eAAO,MAAM,aAAa,EAAE,eAsW3B,CAAC"}
1
+ {"version":3,"file":"impact.command.d.ts","sourceRoot":"","sources":["../../src/commands/impact.command.ts"],"names":[],"mappings":"AAsBA,OAAO,EAML,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AA2ahC,eAAO,MAAM,aAAa,EAAE,eAsW3B,CAAC"}