@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.
- package/dist/commands/check.command.d.ts.map +1 -1
- package/dist/commands/check.command.js +109 -2
- package/dist/commands/command-catalog.d.ts +11 -0
- package/dist/commands/command-catalog.d.ts.map +1 -1
- package/dist/commands/command-catalog.js +91 -0
- package/dist/commands/finish.command.d.ts +3 -0
- package/dist/commands/finish.command.d.ts.map +1 -0
- package/dist/commands/finish.command.js +70 -0
- package/dist/commands/graph-code-subverbs.d.ts.map +1 -1
- package/dist/commands/graph-code-subverbs.js +42 -23
- package/dist/commands/help.command.d.ts.map +1 -1
- package/dist/commands/help.command.js +20 -2
- package/dist/commands/impact.command.d.ts.map +1 -1
- package/dist/commands/impact.command.js +17 -22
- package/dist/commands/smart-context.command.d.ts.map +1 -1
- package/dist/commands/smart-context.command.js +14 -37
- package/dist/commands/trace.command.d.ts.map +1 -1
- package/dist/commands/trace.command.js +73 -3
- package/dist/commands/wiring.command.d.ts +12 -0
- package/dist/commands/wiring.command.d.ts.map +1 -0
- package/dist/commands/wiring.command.js +384 -0
- package/dist/diff/collect-changed-paths.d.ts +5 -3
- package/dist/diff/collect-changed-paths.d.ts.map +1 -1
- package/dist/diff/collect-changed-paths.js +73 -36
- package/dist/diff/deleted-orphans.d.ts +42 -0
- package/dist/diff/deleted-orphans.d.ts.map +1 -0
- package/dist/diff/deleted-orphans.js +46 -0
- package/dist/finish/run-finish.d.ts +62 -0
- package/dist/finish/run-finish.d.ts.map +1 -0
- package/dist/finish/run-finish.js +239 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +4 -0
- 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;
|
|
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":"AAAA,oBAAY,WAAW;IACrB,QAAQ,cAAc;IACtB,iBAAiB,mBAAmB;IACpC,gBAAgB,kBAAkB;IAClC,YAAY,kBAAkB;IAC9B,SAAS,eAAe;IACxB,cAAc,oBAAoB;CACnC;AAED;;;;;;GAMG;AACH,oBAAY,cAAc;IACxB,OAAO,YAAY;IACnB,MAAM,WAAW;IACjB,QAAQ,aAAa;IACrB,OAAO,YAAY;IACnB,QAAQ,aAAa;IACrB,MAAM,WAAW;CAClB;AAED,8BAA8B;AAC9B,oBAAY,eAAe;IACzB,KAAK,UAAU;IACf,KAAK,UAAU;IACf,EAAE,OAAO;IACT,UAAU,gBAAgB;IAC1B,UAAU,eAAe;CAC1B;AAED;;;;;;GAMG;AACH,oBAAY,eAAe;IACzB,KAAK,UAAU;IACf,OAAO,YAAY;IACnB,MAAM,WAAW;IACjB,OAAO,YAAY;IACnB,QAAQ,aAAa;IACrB,MAAM,WAAW;IACjB,QAAQ,aAAa;IACrB,OAAO,YAAY;IACnB,QAAQ,aAAa;IACrB,OAAO,YAAY;IACnB,KAAK,UAAU;IACf,MAAM,WAAW;CAClB;AAED;;;;;;GAMG;AACH,oBAAY,gBAAgB;IAC1B,MAAM,WAAW;IACjB,SAAS,cAAc;IACvB,KAAK,UAAU;IACf,UAAU,eAAe;IACzB,OAAO,YAAY;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,oBAAY,WAAW;IACrB,IAAI,SAAS;IACb,QAAQ,aAAa;IACrB,YAAY,iBAAiB;CAC9B;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,WAAW,CAAC;IACzB,WAAW,EAAE,OAAO,CAAC;IACrB,YAAY,EAAE,OAAO,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,OAAO,CAAC;IACxB,YAAY,EAAE,OAAO,CAAC;IACtB,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B;;;;OAIG;IACH,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,8DAA8D;IAC9D,gBAAgB,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IAC9C,kCAAkC;IAClC,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,6DAA6D;IAC7D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,0DAA0D;IAC1D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,+DAA+D;IAC/D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;;;;;;;OAWG;IACH,IAAI,CAAC,EAAE,WAAW,CAAC;CACpB;AAED;;;;;GAKG;AACH,eAAO,MAAM,eAAe,EAAE,SAAS,oBAAoB,
|
|
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 @@
|
|
|
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,
|
|
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,
|
|
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,
|
|
733
|
+
.slice(0, cap)
|
|
715
734
|
.map((i) => ('source' in i ? sourceSummary(i.source) : { id: 'unknown', resolved: false }));
|
|
716
|
-
const referencedByList = references.slice(0,
|
|
717
|
-
const calledByList = callers.slice(0,
|
|
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 >
|
|
755
|
+
importsFromTruncated: importsFromAll.length > cap,
|
|
737
756
|
importedBy: ctxDropDel(importedByList),
|
|
738
757
|
totalImportedBy: importedByAll.length,
|
|
739
|
-
importedByTruncated: importedByAll.length >
|
|
740
|
-
symbols: symbols.slice(0,
|
|
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 >
|
|
762
|
+
referencedByTruncated: references.length > cap,
|
|
744
763
|
calledBy: ctxDropDel(calledByList),
|
|
745
764
|
totalCalledBy: callers.length,
|
|
746
|
-
calledByTruncated: callers.length >
|
|
747
|
-
...(subtypes.length > 0 ? { subtypes: subtypes.slice(0,
|
|
748
|
-
...(supertypes.length > 0 ? { supertypes: supertypes.slice(0,
|
|
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 =
|
|
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
|
|
1060
|
-
// the true uncapped count, so a truncated result stays
|
|
1061
|
-
// non-numeric input
|
|
1062
|
-
//
|
|
1063
|
-
const
|
|
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;
|
|
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;
|
|
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"}
|