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

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 (94) hide show
  1. package/dist/command-registry.d.ts +9 -0
  2. package/dist/command-registry.d.ts.map +1 -1
  3. package/dist/command-registry.js +15 -0
  4. package/dist/commands/ai-status.command.d.ts.map +1 -1
  5. package/dist/commands/ai-status.command.js +10 -0
  6. package/dist/commands/apply.command.d.ts.map +1 -1
  7. package/dist/commands/apply.command.js +9 -2
  8. package/dist/commands/ask.command.d.ts.map +1 -1
  9. package/dist/commands/ask.command.js +2 -1
  10. package/dist/commands/changes.command.d.ts.map +1 -1
  11. package/dist/commands/changes.command.js +14 -4
  12. package/dist/commands/check.command.d.ts.map +1 -1
  13. package/dist/commands/check.command.js +119 -3
  14. package/dist/commands/command-catalog.d.ts.map +1 -1
  15. package/dist/commands/command-catalog.js +224 -0
  16. package/dist/commands/commands.command.d.ts +15 -0
  17. package/dist/commands/commands.command.d.ts.map +1 -1
  18. package/dist/commands/commands.command.js +162 -3
  19. package/dist/commands/compress.command.d.ts.map +1 -1
  20. package/dist/commands/compress.command.js +39 -4
  21. package/dist/commands/constructs.command.d.ts.map +1 -1
  22. package/dist/commands/constructs.command.js +40 -9
  23. package/dist/commands/context.command.d.ts.map +1 -1
  24. package/dist/commands/context.command.js +3 -1
  25. package/dist/commands/daily.commands.d.ts.map +1 -1
  26. package/dist/commands/daily.commands.js +48 -5
  27. package/dist/commands/diff-check.command.d.ts.map +1 -1
  28. package/dist/commands/diff-check.command.js +6 -2
  29. package/dist/commands/feedback.command.d.ts.map +1 -1
  30. package/dist/commands/feedback.command.js +4 -0
  31. package/dist/commands/framework.command.d.ts.map +1 -1
  32. package/dist/commands/framework.command.js +20 -3
  33. package/dist/commands/gate.command.d.ts.map +1 -1
  34. package/dist/commands/gate.command.js +99 -2
  35. package/dist/commands/graph-code-subverbs.d.ts.map +1 -1
  36. package/dist/commands/graph-code-subverbs.js +183 -68
  37. package/dist/commands/graph.command.d.ts.map +1 -1
  38. package/dist/commands/graph.command.js +6 -0
  39. package/dist/commands/impact.command.d.ts.map +1 -1
  40. package/dist/commands/impact.command.js +91 -1
  41. package/dist/commands/knowledge.command.d.ts.map +1 -1
  42. package/dist/commands/knowledge.command.js +21 -1
  43. package/dist/commands/onboard.command.d.ts.map +1 -1
  44. package/dist/commands/onboard.command.js +11 -2
  45. package/dist/commands/orchestrate.command.d.ts.map +1 -1
  46. package/dist/commands/orchestrate.command.js +6 -1
  47. package/dist/commands/pack-author.command.d.ts.map +1 -1
  48. package/dist/commands/pack-author.command.js +13 -1
  49. package/dist/commands/packs.command.d.ts.map +1 -1
  50. package/dist/commands/packs.command.js +95 -17
  51. package/dist/commands/paths.command.d.ts.map +1 -1
  52. package/dist/commands/paths.command.js +8 -1
  53. package/dist/commands/pipelines.command.d.ts.map +1 -1
  54. package/dist/commands/pipelines.command.js +2 -1
  55. package/dist/commands/plan-context.command.d.ts.map +1 -1
  56. package/dist/commands/plan-context.command.js +6 -3
  57. package/dist/commands/plan-simulate.command.js +1 -1
  58. package/dist/commands/policy-lint.command.d.ts +3 -0
  59. package/dist/commands/policy-lint.command.d.ts.map +1 -0
  60. package/dist/commands/policy-lint.command.js +135 -0
  61. package/dist/commands/recommend.command.d.ts.map +1 -1
  62. package/dist/commands/recommend.command.js +10 -2
  63. package/dist/commands/registry.command.d.ts.map +1 -1
  64. package/dist/commands/registry.command.js +117 -9
  65. package/dist/commands/report.command.js +1 -1
  66. package/dist/commands/reuse.command.d.ts +3 -0
  67. package/dist/commands/reuse.command.d.ts.map +1 -0
  68. package/dist/commands/reuse.command.js +202 -0
  69. package/dist/commands/review.command.d.ts.map +1 -1
  70. package/dist/commands/review.command.js +20 -2
  71. package/dist/commands/rule-graph-subverbs.d.ts.map +1 -1
  72. package/dist/commands/rule-graph-subverbs.js +31 -4
  73. package/dist/commands/search-structural.command.d.ts.map +1 -1
  74. package/dist/commands/search-structural.command.js +2 -2
  75. package/dist/commands/search.command.d.ts.map +1 -1
  76. package/dist/commands/search.command.js +19 -1
  77. package/dist/commands/simulate.command.d.ts.map +1 -1
  78. package/dist/commands/simulate.command.js +6 -1
  79. package/dist/commands/smart-context.command.d.ts.map +1 -1
  80. package/dist/commands/smart-context.command.js +87 -4
  81. package/dist/commands/spec.command.d.ts.map +1 -1
  82. package/dist/commands/spec.command.js +73 -13
  83. package/dist/commands/task-context.command.d.ts.map +1 -1
  84. package/dist/commands/task-context.command.js +26 -2
  85. package/dist/commands/test.command.d.ts.map +1 -1
  86. package/dist/commands/test.command.js +17 -0
  87. package/dist/commands/trace.command.d.ts.map +1 -1
  88. package/dist/commands/trace.command.js +14 -1
  89. package/dist/main.d.ts.map +1 -1
  90. package/dist/main.js +19 -1
  91. package/dist/status/freshness.d.ts +30 -0
  92. package/dist/status/freshness.d.ts.map +1 -0
  93. package/dist/status/freshness.js +81 -0
  94. package/package.json +33 -33
@@ -1 +1 @@
1
- {"version":3,"file":"commands.command.d.ts","sourceRoot":"","sources":["../../src/commands/commands.command.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"commands.command.d.ts","sourceRoot":"","sources":["../../src/commands/commands.command.ts"],"names":[],"mappings":"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"}
@@ -1,12 +1,13 @@
1
- import { flagBool, flagString } from "../command-registry.js";
1
+ import { flagBool, flagString, resolveCwd } from "../command-registry.js";
2
2
  import { asJson, header } from "../output/format-output.js";
3
+ import { BUILTIN_PROFILES, getProfile } from "../surface/profiles.js";
3
4
  import { buildCommandSafetyMatrix, COMMAND_CATALOG, CommandLifecycle, commandLifecycle, CommandSurface, commandSurface, commandTaskRole, defaultShowInHelp, R46_OVERLAY, renderCommandSafetyMatrixMarkdown, SafetyLevel } from "./command-catalog.js";
4
5
  import { buildCommandTaxonomy, buildPrimaryCommandsReport, renderCommandTaxonomyMarkdown, renderCommandTaxonomyText, renderPrimaryCommandsText } from '@shrkcrft/inspector';
5
6
  export function makeCommandsCommand(registry) {
6
7
  return {
7
8
  name: 'commands',
8
- description: 'Browse the command catalog. Default view is compact (primary + common); pass --all for the full catalog. Subcommands: primary | advanced | deprecated | hidden | retirement-plan | docs-check | surface | machine | legacy | overlaps | taxonomy | matrix | tree | search | suggest | explain | entrypoints | doctor | ux-check.',
9
- usage: 'shrk commands [primary|advanced|deprecated|hidden|retirement-plan|docs-check|surface <s>|machine|legacy|overlaps|taxonomy|matrix|tree|search <q>|suggest <p>|explain <cmd>|entrypoints|doctor|ux-check] [--all] [--safety <level>] [--category <name>] [--json]',
9
+ description: 'Browse the command catalog. Default view is compact (primary + common); pass --all for the full catalog, or --profile <id> (e.g. agent) for the surface a profile sees. Subcommands: primary | advanced | deprecated | hidden | retirement-plan | docs-check | surface | profile | machine | legacy | overlaps | taxonomy | matrix | tree | search | suggest | explain | entrypoints | doctor | ux-check.',
10
+ usage: 'shrk commands [primary|advanced|deprecated|hidden|retirement-plan|docs-check|surface <s>|profile [<id>]|machine|legacy|overlaps|taxonomy|matrix|tree|search <q>|suggest <p>|explain <cmd>|entrypoints|doctor|ux-check] [--all] [--profile <id>] [--safety <level>] [--category <name>] [--json]',
10
11
  async run(args) {
11
12
  const sub = args.positional[0];
12
13
  if (sub === 'doctor') {
@@ -95,6 +96,12 @@ export function makeCommandsCommand(registry) {
95
96
  if (sub === 'surface') {
96
97
  return runCommandsSurface(args);
97
98
  }
99
+ // `shrk commands profile [<id>]` — list surface profiles, or render the
100
+ // catalog as one profile sees it (the curated view, e.g. `agent`).
101
+ if (sub === 'profile') {
102
+ const id = args.positional[1];
103
+ return id ? runCommandsProfile(args, id) : runCommandsProfileList(args);
104
+ }
98
105
  if (sub === 'machine') {
99
106
  return runCommandsMachine(args);
100
107
  }
@@ -120,8 +127,34 @@ export function makeCommandsCommand(registry) {
120
127
  if (sub === 'docs-check') {
121
128
  return runCommandsDocsCheck(args);
122
129
  }
130
+ // `--profile <id>` renders the catalog curated for a surface profile
131
+ // (e.g. `--profile agent` hides CI/release/pack-maintenance machinery an
132
+ // inline agent doesn't need). Commands stay callable; this is a listing
133
+ // filter, not a disable.
134
+ const profileFilter = flagString(args, 'profile');
135
+ if (profileFilter) {
136
+ return runCommandsProfile(args, profileFilter);
137
+ }
123
138
  const safetyFilter = flagString(args, 'safety');
124
139
  const categoryFilter = flagString(args, 'category');
140
+ // Validate the filter values against real allowlists — a typo (e.g.
141
+ // `--safety read` or `--category typo`) previously matched nothing and
142
+ // returned "commands (0)" with exit 0, which reads like an empty result
143
+ // rather than a mistake. Reject with the valid set + exit 2 instead.
144
+ if (safetyFilter !== undefined) {
145
+ const validSafety = new Set(Object.values(SafetyLevel));
146
+ if (!validSafety.has(safetyFilter)) {
147
+ process.stderr.write(`Unknown --safety "${safetyFilter}". Valid: ${[...validSafety].sort().join(', ')}\n`);
148
+ return 2;
149
+ }
150
+ }
151
+ if (categoryFilter !== undefined) {
152
+ const validCategories = new Set(COMMAND_CATALOG.map((e) => e.category));
153
+ if (!validCategories.has(categoryFilter)) {
154
+ process.stderr.write(`Unknown --category "${categoryFilter}". Valid: ${[...validCategories].sort().join(', ')}\n`);
155
+ return 2;
156
+ }
157
+ }
125
158
  const wantsAll = flagBool(args, 'all');
126
159
  let entries = COMMAND_CATALOG;
127
160
  if (safetyFilter) {
@@ -186,6 +219,97 @@ function runCommandsSurface(args) {
186
219
  }
187
220
  return 0;
188
221
  }
222
+ /**
223
+ * Pure: render the catalog as a surface profile sees it. Every command the
224
+ * profile lists in `hidden` is filtered out of `entries` (commands stay
225
+ * callable — this is a listing filter, not a disable). Returns undefined when
226
+ * the profile id is unknown. Exported for tests.
227
+ */
228
+ export function buildCommandsProfileView(profileId, packProfiles = []) {
229
+ const profile = getProfile(profileId, packProfiles);
230
+ if (!profile)
231
+ return undefined;
232
+ const hidden = new Set(profile.hidden ?? []);
233
+ const entries = COMMAND_CATALOG.filter((e) => !hidden.has(e.command));
234
+ return { profile, entries, hidden: [...hidden].sort(), catalogTotal: COMMAND_CATALOG.length };
235
+ }
236
+ /**
237
+ * `shrk commands --profile <id>` / `shrk commands profile <id>` renders the
238
+ * catalog curated for a surface profile (e.g. `agent` hides
239
+ * CI/release/pack-maintenance machinery an inline agent doesn't need) without
240
+ * editing `sharkcraft.config.ts`. Builtin profiles resolve instantly; an
241
+ * unknown id falls back to discovering pack-contributed profiles via the
242
+ * surface context (best-effort — needs a workspace).
243
+ */
244
+ async function runCommandsProfile(args, profileId) {
245
+ let view = buildCommandsProfileView(profileId);
246
+ let validIds = BUILTIN_PROFILES.map((p) => p.id);
247
+ if (!view) {
248
+ try {
249
+ const { loadSurfaceContext } = await import("../surface/load-surface-context.js");
250
+ const ctx = await loadSurfaceContext({ cwd: resolveCwd(args) });
251
+ validIds = ctx.availableProfiles.map((p) => p.id);
252
+ view = buildCommandsProfileView(profileId, ctx.availableProfiles);
253
+ }
254
+ catch {
255
+ // Pack discovery failed (e.g. outside a project) — builtin-only.
256
+ }
257
+ }
258
+ if (!view) {
259
+ process.stderr.write(`Unknown profile "${profileId}". Valid: ${[...validIds].sort().join(', ')}\n`);
260
+ return 2;
261
+ }
262
+ const { profile, entries, hidden, catalogTotal } = view;
263
+ if (flagBool(args, 'json')) {
264
+ process.stdout.write(asJson({
265
+ profile: profile.id,
266
+ description: profile.description,
267
+ source: profile.source,
268
+ total: entries.length,
269
+ catalogTotal,
270
+ hiddenCount: hidden.length,
271
+ hidden,
272
+ entries,
273
+ }) + '\n');
274
+ return 0;
275
+ }
276
+ process.stdout.write(header(`Commands — profile=${profile.id} (${entries.length} of ${catalogTotal} visible)`));
277
+ process.stdout.write(` ${profile.description}\n`);
278
+ if (hidden.length > 0) {
279
+ process.stdout.write(` (${hidden.length} command(s) hidden by this profile — still callable)\n`);
280
+ }
281
+ const grouped = new Map();
282
+ for (const e of entries) {
283
+ const arr = grouped.get(e.category) ?? [];
284
+ arr.push(e);
285
+ grouped.set(e.category, arr);
286
+ }
287
+ for (const [cat, items] of [...grouped.entries()].sort(([a], [b]) => a.localeCompare(b))) {
288
+ process.stdout.write(`\n## ${cat}\n`);
289
+ for (const e of items.sort((a, b) => a.command.localeCompare(b.command))) {
290
+ process.stdout.write(` ${safetyTag(e.safetyLevel)} ${e.command.padEnd(34)} ${e.description}\n`);
291
+ }
292
+ }
293
+ return 0;
294
+ }
295
+ /** `shrk commands profile` (no id) lists the available surface profiles. */
296
+ function runCommandsProfileList(args) {
297
+ const rows = BUILTIN_PROFILES.map((p) => ({
298
+ id: p.id,
299
+ description: p.description,
300
+ hides: (p.hidden ?? []).length,
301
+ }));
302
+ if (flagBool(args, 'json')) {
303
+ process.stdout.write(asJson({ total: rows.length, profiles: rows }) + '\n');
304
+ return 0;
305
+ }
306
+ process.stdout.write(header(`Surface profiles (${rows.length})`));
307
+ for (const r of rows) {
308
+ process.stdout.write(` ${r.id.padEnd(12)} hides=${String(r.hides).padEnd(4)} ${r.description}\n`);
309
+ }
310
+ process.stdout.write(`\nUsage: shrk commands profile <id> | shrk commands --profile <id> (render the curated view)\n`);
311
+ return 0;
312
+ }
189
313
  /** `shrk commands machine` lists machine-oriented commands. */
190
314
  function runCommandsMachine(args) {
191
315
  const matches = COMMAND_CATALOG.filter((e) => commandSurface(e) === CommandSurface.Machine || e.machineOnly === true);
@@ -487,6 +611,10 @@ export const commandsCommand = {
487
611
  // Keep the new views available even via the back-compat entry.
488
612
  if (sub === 'surface')
489
613
  return runCommandsSurface(args);
614
+ if (sub === 'profile') {
615
+ const id = args.positional[1];
616
+ return id ? runCommandsProfile(args, id) : runCommandsProfileList(args);
617
+ }
490
618
  if (sub === 'machine')
491
619
  return runCommandsMachine(args);
492
620
  if (sub === 'legacy')
@@ -503,6 +631,10 @@ export const commandsCommand = {
503
631
  return runCommandsRetirementPlan(args);
504
632
  if (sub === 'docs-check')
505
633
  return await runCommandsDocsCheck(args);
634
+ const profileFilter = flagString(args, 'profile');
635
+ if (profileFilter) {
636
+ return runCommandsProfile(args, profileFilter);
637
+ }
506
638
  const safetyFilter = flagString(args, 'safety');
507
639
  const categoryFilter = flagString(args, 'category');
508
640
  let entries = COMMAND_CATALOG;
@@ -882,6 +1014,33 @@ export function buildCommandsDoctorReport(registry) {
882
1014
  });
883
1015
  }
884
1016
  }
1017
+ // 8. registerSubcommand-only GROUPS without a catalog entry.
1018
+ // Check #7 above only sees depth-1 nodes that carry a handler
1019
+ // (registry.list()). Groups registered purely via registerSubcommand
1020
+ // (e.g. paths / pipelines / checks / audit / ownership) are handler-less
1021
+ // group nodes — they resolve live yet never appear in list(), so the
1022
+ // doctor reported "No issues" while those groups had zero catalog
1023
+ // coverage. Walk listGroups() and flag any group no catalog entry
1024
+ // covers. Pure alias groups (listGroupAliases) are skipped — their
1025
+ // canonical target carries the entry.
1026
+ const aliasGroups = new Set(registry.listGroupAliases().keys());
1027
+ const catalogTopWords = new Set();
1028
+ for (const e of COMMAND_CATALOG) {
1029
+ const top = e.command.split(/\s+/)[0];
1030
+ if (top)
1031
+ catalogTopWords.add(top);
1032
+ }
1033
+ for (const g of registry.listGroups()) {
1034
+ if (aliasGroups.has(g))
1035
+ continue;
1036
+ if (!catalogTopWords.has(g)) {
1037
+ issues.push({
1038
+ code: 'missing-catalog-entry',
1039
+ message: `command group "${g}" is registered (registerSubcommand) but no catalog entry covers it`,
1040
+ severity: 'error',
1041
+ });
1042
+ }
1043
+ }
885
1044
  }
886
1045
  const errors = issues.filter((i) => i.severity === 'error').length;
887
1046
  const warnings = issues.filter((i) => i.severity === 'warning').length;
@@ -1 +1 @@
1
- {"version":3,"file":"compress.command.d.ts","sourceRoot":"","sources":["../../src/commands/compress.command.ts"],"names":[],"mappings":"AAQA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAgChC,eAAO,MAAM,eAAe,EAAE,eA8F7B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,eA4B3B,CAAC"}
1
+ {"version":3,"file":"compress.command.d.ts","sourceRoot":"","sources":["../../src/commands/compress.command.ts"],"names":[],"mappings":"AAQA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAqEhC,eAAO,MAAM,eAAe,EAAE,eAoG7B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,eA4B3B,CAAC"}
@@ -5,6 +5,35 @@ import { flagBool, flagNumber, flagString, resolveCwd, } from "../command-regist
5
5
  import { asJson } from "../output/format-output.js";
6
6
  import { ccrDir, openCcrStore } from "../output/ccr-store-config.js";
7
7
  const CONTENT_TYPES = new Set(Object.values(EContentType));
8
+ // `--type` accepts the EContentType wire strings plus ergonomic aliases. `code`
9
+ // is an alias for `source-code`: it routes to the structure-preserving code
10
+ // outline (ECompressionStrategy.Code), NOT the markdown line-omission a bare
11
+ // `code` used to silently fall through to under auto-detect.
12
+ const TYPE_ALIASES = {
13
+ code: EContentType.SourceCode,
14
+ };
15
+ // The full set of accepted `--type` tokens (content-type wire strings +
16
+ // aliases), sorted and rendered once so the rejection message and the
17
+ // passthrough hint advertise exactly what `resolveContentType` accepts.
18
+ const VALID_TYPE_TOKENS = [...CONTENT_TYPES, ...Object.keys(TYPE_ALIASES)].sort().join(', ');
19
+ /**
20
+ * Resolve a raw `--type` value into a forced {@link EContentType}. Unlike the
21
+ * old `CONTENT_TYPES.has` check — which silently dropped unknown values and let
22
+ * the phantom `code` fall through to auto-detect (markdown line-omission) — an
23
+ * unrecognized non-empty value yields an `error` so the caller can reject with a
24
+ * non-zero exit, and `code` is mapped to the real source-code strategy. A
25
+ * missing/empty value is a no-op (auto-detect).
26
+ */
27
+ function resolveContentType(raw) {
28
+ if (raw === undefined || raw === '')
29
+ return {};
30
+ const aliased = TYPE_ALIASES[raw];
31
+ if (aliased)
32
+ return { type: aliased };
33
+ if (CONTENT_TYPES.has(raw))
34
+ return { type: raw };
35
+ return { error: `compress: unknown --type "${raw}". Valid types: ${VALID_TYPE_TOKENS}.` };
36
+ }
8
37
  // Below this size a passthrough no-op isn't worth a warning (tiny snippets
9
38
  // legitimately have nothing to compress). Above it, a silent `−0%` re-emit is
10
39
  // the opposite of the tool's purpose — nudge the user toward `--type`.
@@ -36,6 +65,13 @@ export const compressCommand = {
36
65
  booleanFlags: COMPRESS_BOOLEAN_FLAGS,
37
66
  run(args) {
38
67
  const cwd = resolveCwd(args);
68
+ // Validate `--type` up front: an unknown value is a user error, not a
69
+ // silent fall-through to auto-detect — fail fast before reading input.
70
+ const resolvedType = resolveContentType(flagString(args, 'type'));
71
+ if (resolvedType.error) {
72
+ process.stderr.write(resolvedType.error + '\n');
73
+ return 1;
74
+ }
39
75
  let content;
40
76
  try {
41
77
  content = readInput(args);
@@ -59,9 +95,8 @@ export const compressCommand = {
59
95
  opts.maxItems = Math.floor(max);
60
96
  if (flagBool(args, 'lossless'))
61
97
  opts.lossless = true;
62
- const type = flagString(args, 'type');
63
- if (type && CONTENT_TYPES.has(type))
64
- opts.contentType = type;
98
+ if (resolvedType.type)
99
+ opts.contentType = resolvedType.type;
65
100
  const result = compressContent(content, opts);
66
101
  const pct = Math.round(result.savings.ratio * 100);
67
102
  // A lossy result with no cached original (i.e. --no-cache) can't be undone
@@ -109,7 +144,7 @@ export const compressCommand = {
109
144
  inputBytes >= PASSTHROUGH_HINT_MIN_BYTES &&
110
145
  !flagBool(args, 'lossless')) {
111
146
  process.stderr.write(`hint: nothing was compressed — auto-detect classified this as ${result.contentType} and found no win. ` +
112
- 'Try `--type <content-type>` to force a strategy (json, git-diff, search-results, build-log, source-code, markdown).\n');
147
+ `Try \`--type <content-type>\` to force a strategy (${VALID_TYPE_TOKENS}).\n`);
113
148
  }
114
149
  return 0;
115
150
  },
@@ -1 +1 @@
1
- {"version":3,"file":"constructs.command.d.ts","sourceRoot":"","sources":["../../src/commands/constructs.command.ts"],"names":[],"mappings":"AA8BA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAiGhC,eAAO,MAAM,qBAAqB,EAAE,eAqBnC,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,eAoDlC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eAwFpC,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,eAoErC,CAAC;AAEF,eAAO,MAAM,wBAAwB,EAAE,eAmCtC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eAwBpC,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,eAiClC,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,eAiBrC,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,eAiBrC,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,eA4BrC,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,eA8BrC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eA0DpC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eA0LpC,CAAC"}
1
+ {"version":3,"file":"constructs.command.d.ts","sourceRoot":"","sources":["../../src/commands/constructs.command.ts"],"names":[],"mappings":"AA8BA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAkHhC,eAAO,MAAM,qBAAqB,EAAE,eAqBnC,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,eAoDlC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eAiGpC,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,eA0ErC,CAAC;AAEF,eAAO,MAAM,wBAAwB,EAAE,eAmCtC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eA4BpC,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,eAkClC,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,eAiBrC,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,eAkBrC,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,eA4BrC,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,eA8BrC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eA0DpC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eA0LpC,CAAC"}
@@ -68,6 +68,20 @@ function enrichTraceWithGraph(declaredFiles, declaredNames, cwd) {
68
68
  undeclaredSymbols: [...undeclared].sort(),
69
69
  };
70
70
  }
71
+ /**
72
+ * The AUTHORITATIVE file set for a construct: declared globs replaced by their
73
+ * graph-resolved matches, declared concrete files kept as-is. This is what the
74
+ * counts, the `files` subverb, and the impact risk heuristic should use — the
75
+ * declared list under-counts because a single `src/**` glob is one entry that
76
+ * really covers N files. Falls back to the raw declared list when the graph is
77
+ * missing, preserving offline/no-index determinism.
78
+ */
79
+ function effectiveConstructFiles(declaredFiles, graph) {
80
+ if (graph.graphState !== 'fresh')
81
+ return [...declaredFiles];
82
+ const concrete = declaredFiles.filter((f) => !GLOB_MAGIC.test(f));
83
+ return [...new Set([...concrete, ...graph.resolvedFiles])].sort();
84
+ }
71
85
  export const constructsListCommand = {
72
86
  name: 'list',
73
87
  description: 'List registered constructs.',
@@ -184,14 +198,21 @@ export const constructsTraceCommand = {
184
198
  registryHints: c.tags?.filter((t) => /(registry|barrel)/i.test(t)) ?? [],
185
199
  }
186
200
  : undefined;
201
+ const effectiveFiles = effectiveConstructFiles(trace.files, graph);
187
202
  if (flagBool(args, 'json')) {
188
- process.stdout.write(asJson({ ...trace, graph, ...(deepBlock ? { deep: deepBlock } : {}) }) + '\n');
203
+ process.stdout.write(asJson({ ...trace, effectiveFiles, graph, ...(deepBlock ? { deep: deepBlock } : {}) }) + '\n');
189
204
  return 0;
190
205
  }
191
206
  process.stdout.write(header(`Trace: ${id}`));
192
- process.stdout.write(`Files (${trace.files.length}):\n`);
193
- for (const f of trace.files)
207
+ // Show the graph-resolved file set (globs expanded) as the count, not the
208
+ // raw declared list — otherwise a single `src/**` glob reads as "1 file".
209
+ process.stdout.write(`Files (${effectiveFiles.length}):\n`);
210
+ for (const f of effectiveFiles)
194
211
  process.stdout.write(` • ${f}\n`);
212
+ const declaredGlobs = trace.files.filter((f) => GLOB_MAGIC.test(f));
213
+ if (graph.graphState === 'fresh' && declaredGlobs.length > 0) {
214
+ process.stdout.write(` (expanded from declared glob(s): ${declaredGlobs.join(', ')})\n`);
215
+ }
195
216
  if (trace.publicApi.length > 0) {
196
217
  process.stdout.write(`Public API:\n`);
197
218
  for (const a of trace.publicApi)
@@ -271,13 +292,19 @@ export const constructsImpactCommand = {
271
292
  const verCfg = inspection.config?.verificationCommands ?? [];
272
293
  for (const v of verCfg.slice(0, 3))
273
294
  verificationCommands.push(v.command);
274
- const fileCount = trace.files.length;
295
+ // Risk must be computed from the graph-resolved file set: a glob-declared
296
+ // construct (`src/**`) is one declared entry but really touches N files, so
297
+ // the old `trace.files.length` under-counted it to risk='low'.
298
+ const declaredNames = new Set([...trace.publicApi, ...trace.tokens, ...trace.events]);
299
+ const graph = enrichTraceWithGraph(trace.files, declaredNames, resolveCwd(args));
300
+ const effectiveFiles = effectiveConstructFiles(trace.files, graph);
301
+ const fileCount = effectiveFiles.length;
275
302
  const risk = fileCount > 12 ? 'high' : fileCount > 4 ? 'medium' : 'low';
276
303
  const humanReview = risk !== 'low';
277
304
  const report = {
278
305
  schema: 'sharkcraft.construct-impact/v1',
279
306
  id,
280
- files: trace.files,
307
+ files: effectiveFiles,
281
308
  publicApi: trace.publicApi,
282
309
  events: trace.events,
283
310
  tokens: trace.tokens,
@@ -366,18 +393,22 @@ export const constructsFilesCommand = {
366
393
  return 1;
367
394
  }
368
395
  const trace = traceConstruct(c);
396
+ // Emit the graph-resolved files (globs expanded), not the raw declared
397
+ // globs — consumers piping `constructs files` expect real paths.
398
+ const graph = enrichTraceWithGraph(trace.files, new Set(), resolveCwd(args));
399
+ const effectiveFiles = effectiveConstructFiles(trace.files, graph);
369
400
  if (flagBool(args, 'json')) {
370
- process.stdout.write(asJson({ id, files: trace.files }) + '\n');
401
+ process.stdout.write(asJson({ id, files: effectiveFiles }) + '\n');
371
402
  return 0;
372
403
  }
373
- for (const f of trace.files)
404
+ for (const f of effectiveFiles)
374
405
  process.stdout.write(`${f}\n`);
375
406
  return 0;
376
407
  },
377
408
  };
378
409
  export const constructsApiCommand = {
379
410
  name: 'api',
380
- description: 'Show public-API entries for a construct. --public-only emits the raw list with no header.',
411
+ description: 'Show the DECLARED public-API entries for a construct (the hand-authored list, not an exhaustive scan — run `shrk constructs trace <id>` for graph-verified symbols defined in the files). --public-only emits the raw list with no header.',
381
412
  usage: 'shrk constructs api <id> [--public-only] [--json]',
382
413
  async run(args) {
383
414
  const id = args.positional[0];
@@ -432,7 +463,7 @@ export const constructsEventsCommand = {
432
463
  };
433
464
  export const constructsTokensCommand = {
434
465
  name: 'tokens',
435
- description: 'List tokens contributed by constructs.',
466
+ description: 'List the DECLARED tokens contributed by constructs (the hand-authored list, not an exhaustive scan).',
436
467
  usage: 'shrk constructs tokens [<id>] [--json]',
437
468
  async run(args) {
438
469
  const id = args.positional[0];
@@ -1 +1 @@
1
- {"version":3,"file":"context.command.d.ts","sourceRoot":"","sources":["../../src/commands/context.command.ts"],"names":[],"mappings":"AAiBA,OAAO,EAML,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAgChC,eAAO,MAAM,cAAc,EAAE,eAyI5B,CAAC"}
1
+ {"version":3,"file":"context.command.d.ts","sourceRoot":"","sources":["../../src/commands/context.command.ts"],"names":[],"mappings":"AAiBA,OAAO,EAML,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAgChC,eAAO,MAAM,cAAc,EAAE,eA2I5B,CAAC"}
@@ -1,4 +1,4 @@
1
- import { entrypointBanner, inspectSharkcraft } from '@shrkcrft/inspector';
1
+ import { contextTuningBoostFor, entrypointBanner, inspectSharkcraft } from '@shrkcrft/inspector';
2
2
  import { buildContext } from '@shrkcrft/context';
3
3
  import { loadIntentBenchmark, runIntentBenchmark, STARTER_INTENT_BENCHMARK, writeBenchmarkRun, } from '@shrkcrft/context-planner';
4
4
  import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
@@ -67,6 +67,7 @@ export const contextCommand = {
67
67
  const includeCommands = flagBool(args, 'include-commands');
68
68
  const inspection = await inspectSharkcraft({ cwd: resolveCwd(args) });
69
69
  const overview = buildProjectOverview(inspection.workspace, inspection.config?.projectName);
70
+ const contextBoost = contextTuningBoostFor(inspection, task);
70
71
  const result = buildContext(inspection.knowledgeEntries, {
71
72
  task,
72
73
  framework,
@@ -81,6 +82,7 @@ export const contextCommand = {
81
82
  includeDocs,
82
83
  includeCommands,
83
84
  projectOverview: renderOverviewText(overview),
85
+ ...(contextBoost ? { boostFor: contextBoost } : {}),
84
86
  });
85
87
  // Surface top commands prominently before the long context body.
86
88
  // Auto-promote commands-first for action-like tasks (rename / add /
@@ -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,eA6D5B,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;AAMhC,eAAO,MAAM,WAAW,EAAE,eAsFzB,CAAC;AAcF,eAAO,MAAM,WAAW,EAAE,eAoGzB,CAAC;AAKF,eAAO,MAAM,cAAc,EAAE,eAyG5B,CAAC"}
@@ -186,14 +186,33 @@ export const explainCommand = {
186
186
  const top = inspection.knowledgeEntries
187
187
  .filter((e) => matches(topic, e.id, e.title, e.content, e.tags.join(' ')))
188
188
  .slice(0, maxEntries);
189
+ // Surface the matched items themselves, not just counts. `explain`
190
+ // otherwise duplicates `shrk search`'s job while hiding what matched.
191
+ const relevantRules = packet.relevantRules.map((r) => ({
192
+ id: r.id,
193
+ title: r.title,
194
+ priority: r.priority,
195
+ }));
196
+ const relevantPaths = packet.relevantPaths.map((p) => ({
197
+ id: p.id,
198
+ title: p.title,
199
+ priority: p.priority,
200
+ }));
201
+ const relevantTemplates = packet.relevantTemplates.map((t) => ({
202
+ id: t.id,
203
+ name: t.name,
204
+ }));
189
205
  if (flagBool(args, 'json')) {
190
206
  process.stdout.write(asJson({
191
207
  topic,
192
208
  summary: {
193
- relevantRules: packet.relevantRules.length,
194
- relevantPaths: packet.relevantPaths.length,
195
- relevantTemplates: packet.relevantTemplates.length,
209
+ relevantRules: relevantRules.length,
210
+ relevantPaths: relevantPaths.length,
211
+ relevantTemplates: relevantTemplates.length,
196
212
  },
213
+ relevantRules,
214
+ relevantPaths,
215
+ relevantTemplates,
197
216
  entries: top.map((e) => ({
198
217
  id: e.id,
199
218
  title: e.title,
@@ -205,11 +224,35 @@ export const explainCommand = {
205
224
  return 0;
206
225
  }
207
226
  process.stdout.write(header(`explain: ${topic}`));
208
- process.stdout.write(kv('matches', `rules=${packet.relevantRules.length} paths=${packet.relevantPaths.length} templates=${packet.relevantTemplates.length}`) + '\n\n');
227
+ process.stdout.write(kv('matches', `rules=${relevantRules.length} paths=${relevantPaths.length} templates=${relevantTemplates.length}`) + '\n\n');
228
+ if (relevantRules.length > 0) {
229
+ process.stdout.write('Rules:\n');
230
+ for (const r of relevantRules) {
231
+ process.stdout.write(` • [${r.priority}] ${r.id} — ${r.title}\n`);
232
+ }
233
+ process.stdout.write('\n');
234
+ }
235
+ if (relevantPaths.length > 0) {
236
+ process.stdout.write('Paths:\n');
237
+ for (const p of relevantPaths) {
238
+ process.stdout.write(` • [${p.priority}] ${p.id} — ${p.title}\n`);
239
+ }
240
+ process.stdout.write('\n');
241
+ }
242
+ if (relevantTemplates.length > 0) {
243
+ process.stdout.write('Templates:\n');
244
+ for (const t of relevantTemplates) {
245
+ process.stdout.write(` • ${t.id} — ${t.name}\n`);
246
+ }
247
+ process.stdout.write('\n');
248
+ }
209
249
  if (top.length === 0) {
210
- process.stdout.write('(no matching knowledge entries)\n');
250
+ if (relevantRules.length + relevantPaths.length + relevantTemplates.length === 0) {
251
+ process.stdout.write('(no matching knowledge entries)\n');
252
+ }
211
253
  return 0;
212
254
  }
255
+ process.stdout.write('Knowledge:\n');
213
256
  for (const e of top) {
214
257
  process.stdout.write(`• [${e.priority}] ${e.id} — ${e.title}\n`);
215
258
  const body = e.content
@@ -1 +1 @@
1
- {"version":3,"file":"diff-check.command.d.ts","sourceRoot":"","sources":["../../src/commands/diff-check.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAcH,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AA0GhC,eAAO,MAAM,gBAAgB,EAAE,eAiI9B,CAAC"}
1
+ {"version":3,"file":"diff-check.command.d.ts","sourceRoot":"","sources":["../../src/commands/diff-check.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAcH,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AA6GhC,eAAO,MAAM,gBAAgB,EAAE,eAkI9B,CAAC"}
@@ -34,9 +34,12 @@ function resolveScope(args, cwd) {
34
34
  const staged = flagBool(args, 'staged');
35
35
  const since = flagString(args, 'since');
36
36
  const filesRaw = flagString(args, 'files');
37
+ // Files come from `--files a,b` or as bare positional args
38
+ // (`shrk diff-check a.ts b.ts`). Positionals were previously ignored, which
39
+ // silently widened the scope back to the full worktree.
37
40
  const files = filesRaw
38
41
  ? filesRaw.split(',').map((s) => s.trim()).filter((s) => s.length > 0)
39
- : [];
42
+ : args.positional.filter((s) => s.length > 0);
40
43
  if (files.length > 0) {
41
44
  return { mode: 'files', options: { projectRoot: cwd, files } };
42
45
  }
@@ -97,7 +100,8 @@ function deriveVerdict(env) {
97
100
  export const diffCheckCommand = {
98
101
  name: 'diff-check',
99
102
  description: 'Self-check the current git diff against this project\'s boundary + import-hygiene rules. Single-call composite of `shrk check boundaries --changed-only` + `shrk check imports --changed-only`, with one verdict and one nextAction line. Designed for AI agents to run after editing — pass --json for the structured envelope.',
100
- usage: 'shrk [--cwd <dir>] diff-check [--staged | --since <ref> | --files a.ts,b.ts] [--json]',
103
+ usage: 'shrk [--cwd <dir>] diff-check [files... | --files a.ts,b.ts | --staged | --since <ref>] [--json]',
104
+ booleanFlags: new Set(['json', 'staged']),
101
105
  async run(args) {
102
106
  const cwd = resolveCwd(args);
103
107
  const wantJson = flagBool(args, 'json');
@@ -1 +1 @@
1
- {"version":3,"file":"feedback.command.d.ts","sourceRoot":"","sources":["../../src/commands/feedback.command.ts"],"names":[],"mappings":"AAiBA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAgBhC,eAAO,MAAM,qBAAqB,EAAE,eAiCnC,CAAC;AAEF,eAAO,MAAM,wBAAwB,EAAE,eA0CtC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eAyDpC,CAAC;AAEF,eAAO,MAAM,+BAA+B,EAAE,eAwB7C,CAAC;AAIF,eAAO,MAAM,wBAAwB,EAAE,eAqBtC,CAAC;AAEF,eAAO,MAAM,0BAA0B,EAAE,eAiDxC,CAAC;AAGF,eAAO,MAAM,sBAAsB,EAAE,eAqBpC,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,eAqBnC,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,eA6BjC,CAAC"}
1
+ {"version":3,"file":"feedback.command.d.ts","sourceRoot":"","sources":["../../src/commands/feedback.command.ts"],"names":[],"mappings":"AAiBA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAiBhC,eAAO,MAAM,qBAAqB,EAAE,eAiCnC,CAAC;AAEF,eAAO,MAAM,wBAAwB,EAAE,eA0CtC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eAyDpC,CAAC;AAEF,eAAO,MAAM,+BAA+B,EAAE,eAwB7C,CAAC;AAIF,eAAO,MAAM,wBAAwB,EAAE,eAqBtC,CAAC;AAEF,eAAO,MAAM,0BAA0B,EAAE,eAoDxC,CAAC;AAGF,eAAO,MAAM,sBAAsB,EAAE,eAqBpC,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,eAqBnC,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,eA6BjC,CAAC"}
@@ -10,6 +10,7 @@ import * as nodePath from 'node:path';
10
10
  import { FeedbackBucket, ingestFeedbackFile, inspectSharkcraft, loadFeedbackRules, renderFeedbackBacklog, } from '@shrkcrft/inspector';
11
11
  import { flagBool, flagString, resolveCwd, } from "../command-registry.js";
12
12
  import { asJson, header } from "../output/format-output.js";
13
+ import { feedbackRulesDoctorHints, renderFailureHints } from "../output/failure-hints.js";
13
14
  function ingestPositional(args) {
14
15
  return args.positional[0];
15
16
  }
@@ -256,6 +257,9 @@ export const feedbackRulesDoctorCommand = {
256
257
  process.stdout.write(` ERROR ${e.id}: ${e.message}\n`);
257
258
  for (const w of warnings)
258
259
  process.stdout.write(` WARN ${w.id}: ${w.message}\n`);
260
+ if (errors.length > 0) {
261
+ process.stdout.write(renderFailureHints(feedbackRulesDoctorHints()));
262
+ }
259
263
  return errors.length > 0 ? 1 : 0;
260
264
  },
261
265
  };
@@ -1 +1 @@
1
- {"version":3,"file":"framework.command.d.ts","sourceRoot":"","sources":["../../src/commands/framework.command.ts"],"names":[],"mappings":"AAOA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAGhC;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,EAAE,eAe9B,CAAC"}
1
+ {"version":3,"file":"framework.command.d.ts","sourceRoot":"","sources":["../../src/commands/framework.command.ts"],"names":[],"mappings":"AAOA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAIhC;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,EAAE,eAe9B,CAAC"}
@@ -1,7 +1,8 @@
1
1
  import { buildRegistryWithPacks, defaultRegistry, FrameworkQueryApi, runExtractors, } from '@shrkcrft/framework-scanners';
2
2
  import { inspectSharkcraft } from '@shrkcrft/inspector';
3
- import { flagBool, flagString, resolveCwd, } from "../command-registry.js";
3
+ import { flagBool, flagPositiveInt, flagString, resolveCwd, } from "../command-registry.js";
4
4
  import { asJson, header, kv } from "../output/format-output.js";
5
+ import { computeMtimeFreshness } from "../status/freshness.js";
5
6
  /**
6
7
  * `shrk framework` — run / inspect the framework-aware extractors.
7
8
  *
@@ -109,8 +110,20 @@ async function runStatus(args) {
109
110
  }
110
111
  const api = FrameworkQueryApi.fromStore(cwd);
111
112
  const m = api.manifest();
113
+ // Honest freshness: the framework store is stale once a source file changed
114
+ // after it was built. Mirror the `state + lastBuiltAt + drift` shape that
115
+ // `graph status` exposes.
116
+ const fresh = computeMtimeFreshness(cwd, m.lastBuiltAt);
112
117
  if (wantJson) {
113
- process.stdout.write(asJson({ ok: true, manifest: m }) + '\n');
118
+ process.stdout.write(asJson({
119
+ ok: true,
120
+ state: fresh.state,
121
+ lastBuiltAt: m.lastBuiltAt,
122
+ lastChangedAt: fresh.lastChangedAt,
123
+ behindMs: fresh.behindMs,
124
+ ...(fresh.state === 'stale' ? { nextCommand: 'shrk framework index' } : {}),
125
+ manifest: m,
126
+ }) + '\n');
114
127
  return 0;
115
128
  }
116
129
  process.stdout.write(header('Framework status'));
@@ -120,6 +133,10 @@ async function runStatus(args) {
120
133
  process.stdout.write(kv(` ${k}`, String(v)) + '\n');
121
134
  }
122
135
  process.stdout.write(kv('last built', m.lastBuiltAt) + '\n');
136
+ process.stdout.write(kv('state', fresh.state) + '\n');
137
+ if (fresh.state === 'stale') {
138
+ process.stdout.write(`! stale — source changed since last build${fresh.lastChangedAt ? ` (last change ${fresh.lastChangedAt})` : ''}; re-run \`shrk framework index\`\n`);
139
+ }
123
140
  return 0;
124
141
  }
125
142
  async function runList(args) {
@@ -134,7 +151,7 @@ async function runList(args) {
134
151
  const framework = flagString(args, 'framework');
135
152
  const subtype = flagString(args, 'subtype');
136
153
  const file = flagString(args, 'file');
137
- const limit = Number(flagString(args, 'limit') ?? '50');
154
+ const limit = flagPositiveInt(args, 'limit', 50);
138
155
  const entities = api.list({
139
156
  ...(framework ? { framework } : {}),
140
157
  ...(subtype ? { subtype } : {}),
@@ -1 +1 @@
1
- {"version":3,"file":"gate.command.d.ts","sourceRoot":"","sources":["../../src/commands/gate.command.ts"],"names":[],"mappings":"AAOA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAGhC;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,WAAW,EAAE,eA+FzB,CAAC"}
1
+ {"version":3,"file":"gate.command.d.ts","sourceRoot":"","sources":["../../src/commands/gate.command.ts"],"names":[],"mappings":"AAaA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAGhC;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,WAAW,EAAE,eAiMzB,CAAC"}