@shrkcrft/cli 0.1.0-alpha.18 → 0.1.0-alpha.19

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.
@@ -1 +1 @@
1
- {"version":3,"file":"gen.command.d.ts","sourceRoot":"","sources":["../../src/commands/gen.command.ts"],"names":[],"mappings":"AASA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAkBhC,eAAO,MAAM,UAAU,EAAE,eAmJxB,CAAC"}
1
+ {"version":3,"file":"gen.command.d.ts","sourceRoot":"","sources":["../../src/commands/gen.command.ts"],"names":[],"mappings":"AASA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAkBhC,eAAO,MAAM,UAAU,EAAE,eA+JxB,CAAC"}
@@ -19,7 +19,7 @@ const CHANGE_LABEL = {
19
19
  export const genCommand = {
20
20
  name: 'gen',
21
21
  description: 'Generate code from a template. Defaults to dry-run.',
22
- usage: 'shrk gen <templateId> [<name>] [--var key=value ...] [--dry-run] [--write] [--force] [--save-plan <file>] [--json]',
22
+ usage: 'shrk gen <templateId> [<name>] [--var key=value ...] [--dry-run] [--write] [--force] [--save-plan <file>] [--show-content] [--json]',
23
23
  async run(args) {
24
24
  const templateId = args.positional[0];
25
25
  const name = args.positional[1];
@@ -114,6 +114,18 @@ export const genCommand = {
114
114
  for (const change of plan.changes) {
115
115
  process.stdout.write(`${CHANGE_LABEL[change.type]} ${change.relativePath} (${change.sizeBytes} bytes) — ${change.reason}\n`);
116
116
  }
117
+ // --show-content: print the already-computed virtual file content so an
118
+ // agent can review what a template would generate WITHOUT writing to disk
119
+ // (the saved plan stays content-free). The bytes are identical in dry-run
120
+ // and --write; this is purely additive.
121
+ if (flagBool(args, 'show-content')) {
122
+ process.stdout.write('\nVirtual content (not written to disk):\n');
123
+ for (const change of plan.changes) {
124
+ const body = change.contents ?? '';
125
+ process.stdout.write(`\n----- ${change.relativePath} (${change.sizeBytes} bytes) -----\n`);
126
+ process.stdout.write(body.endsWith('\n') ? body : body + '\n');
127
+ }
128
+ }
117
129
  if (plan.postGenerationNotes.length) {
118
130
  process.stdout.write('\nPost-generation notes:\n');
119
131
  for (const note of plan.postGenerationNotes)
@@ -1 +1 @@
1
- {"version":3,"file":"graph-code-subverbs.d.ts","sourceRoot":"","sources":["../../src/commands/graph-code-subverbs.ts"],"names":[],"mappings":"AA0BA,OAAO,EAAoC,KAAK,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAkH3F,wBAAsB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAgBrE;AA4FD,wBAAsB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA+DtE;AAiBD,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAwF1E;AAID,wBAAsB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA8EpE;AAID,wBAAsB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAmFtE;AAID,wBAAsB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAgDtE;AAID,wBAAsB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAgMvE;AAID,wBAAsB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAuHtE;AAID;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA+CpE;AAID,wBAAsB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAoEvE;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":"AA0BA,OAAO,EAAoC,KAAK,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAiI3F,wBAAsB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAgBrE;AA4FD,wBAAsB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA+DtE;AAiBD,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAwF1E;AAID,wBAAsB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA8EpE;AAID,wBAAsB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAmFtE;AAID,wBAAsB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAiDtE;AAID,wBAAsB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAgMvE;AAID,wBAAsB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAuHtE;AAID;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA+CpE;AAID,wBAAsB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA0EvE;AAyBD;;;;;;;;;;;;GAYG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAgHpE"}
@@ -58,19 +58,37 @@ function maybeColumnarize(payload, args) {
58
58
  return out;
59
59
  }
60
60
  const STALE_HINT = `Index is missing or stale. Run 'shrk graph index' to build it.`;
61
- const STALE_RESULT_HINT = 'Some result files changed since the index was built — run `shrk graph index --changed` (or pass --refresh) for fresh results.';
61
+ const STALE_RESULT_HINT = 'Some result files changed since the index was built — auto-refresh is on by default (you passed --no-refresh / SHRK_GRAPH_NO_REFRESH). Drop the opt-out, or run `shrk graph index --changed`, for fresh results.';
62
62
  /**
63
- * `--refresh`: incrementally reindex changed/deleted files BEFORE querying so
64
- * the agent's just-saved edits are reflected. CLI-only it writes the
65
- * gitignored `.sharkcraft` cache; MCP never does this (read-only contract).
63
+ * Refresh-by-default: incrementally reindex changed/deleted files BEFORE
64
+ * querying so an agent's just-saved edits are reflected, then print a one-line
65
+ * `(refreshed, N files)` notice to stderr. The incremental updater is
66
+ * sub-second on SharkCraft-sized indexes, so this removes the manual `shrk
67
+ * graph index --changed` step that otherwise leaves every read command
68
+ * answering from a silently-stale index — the #1 daily-friction tax.
69
+ *
70
+ * Opt out with `--no-refresh` or `SHRK_GRAPH_NO_REFRESH=1` (e.g. to keep a read
71
+ * perfectly side-effect-free, or on a huge repo where the rewrite is felt).
72
+ * `--refresh` is still accepted as a harmless explicit-on alias.
73
+ *
74
+ * CLI-only — it writes the gitignored `.sharkcraft` cache; MCP never calls this
75
+ * (the read-only contract). When there is no index yet, `detectChangedAndDeleted`
76
+ * returns nothing, so `updateChanged` (which requires an existing store) is
77
+ * never reached. The notice goes to stderr so it never corrupts a `--json`
78
+ * payload on stdout.
66
79
  */
67
80
  function maybeRefresh(args, cwd) {
68
- if (!flagBool(args, 'refresh'))
81
+ if (flagBool(args, 'no-refresh'))
82
+ return;
83
+ if ((process.env.SHRK_GRAPH_NO_REFRESH ?? '').trim().length > 0)
69
84
  return;
70
85
  const d = detectChangedAndDeleted(cwd);
71
- if (d.changed.length > 0 || d.deleted.length > 0) {
72
- updateChanged({ projectRoot: cwd, changedFiles: d.changed, deletedFiles: d.deleted });
73
- }
86
+ if (d.changed.length === 0 && d.deleted.length === 0)
87
+ return;
88
+ const result = updateChanged({ projectRoot: cwd, changedFiles: d.changed, deletedFiles: d.deleted });
89
+ const n = result.updated.length + result.deleted.length;
90
+ if (n > 0)
91
+ process.stderr.write(`(refreshed, ${n} file${n === 1 ? '' : 's'})\n`);
74
92
  }
75
93
  /**
76
94
  * Targeted staleness over a query's result file paths: which changed (flag)
@@ -519,6 +537,7 @@ export async function runGraphSearch(args) {
519
537
  }
520
538
  const kindFlag = flagString(args, 'kind');
521
539
  const limit = Number(flagString(args, 'limit') ?? '20');
540
+ maybeRefresh(args, cwd);
522
541
  const api = loadOrFail(cwd, wantJson);
523
542
  if (!api)
524
543
  return 1;
@@ -923,10 +942,16 @@ export async function runGraphCallers(args) {
923
942
  const wantJson = flagBool(args, 'json');
924
943
  const target = args.positional[1];
925
944
  if (!target) {
926
- process.stderr.write('Usage: shrk graph callers <symbol> [--mode call|reference] [--refresh]\n');
945
+ process.stderr.write('Usage: shrk graph callers <symbol> [--mode call|reference] [--limit N] [--no-refresh]\n');
927
946
  return 2;
928
947
  }
929
948
  const mode = (flagString(args, 'mode') ?? 'call');
949
+ // --limit N: cap the returned call sites (default 200). `total` still reports
950
+ // the true uncapped count, so a truncated result stays honest. Guard against
951
+ // non-numeric input — `Number('foo')` is NaN and `slice(0, NaN)` would zero
952
+ // the callers list while `total` kept showing the real count.
953
+ const parsedLimit = Number.parseInt(flagString(args, 'limit') ?? '200', 10);
954
+ const limit = Number.isFinite(parsedLimit) && parsedLimit > 0 ? parsedLimit : 200;
930
955
  maybeRefresh(args, cwd);
931
956
  const api = loadOrFail(cwd, wantJson);
932
957
  if (!api)
@@ -961,7 +986,7 @@ export async function runGraphCallers(args) {
961
986
  symbol: nodeSummary(sym),
962
987
  mode,
963
988
  total: liveSites.length,
964
- callers: liveSites.slice(0, 200).map((s) => ({
989
+ callers: liveSites.slice(0, limit).map((s) => ({
965
990
  ...nodeSummary(s.node),
966
991
  ...(s.line ? { line: s.line } : {}),
967
992
  })),
@@ -978,7 +1003,7 @@ export async function runGraphCallers(args) {
978
1003
  process.stdout.write(` ⓘ ${note}\n`);
979
1004
  // Render `path:line` so the agent jumps straight to the call site instead
980
1005
  // of having to grep inside each returned file.
981
- for (const c of payload.callers.slice(0, 50)) {
1006
+ for (const c of payload.callers.slice(0, Math.min(50, limit))) {
982
1007
  process.stdout.write(` ${c.path ?? c.id}${c.line ? ':' + c.line : ''}\n`);
983
1008
  }
984
1009
  if (fresh.field) {
@@ -1025,7 +1050,7 @@ export async function runGraphPath(args) {
1025
1050
  const fromArg = args.positional[1];
1026
1051
  const toArg = args.positional[2];
1027
1052
  if (!fromArg || !toArg) {
1028
- process.stderr.write('Usage: shrk graph path <from> <to> [--max-depth N] [--refresh] [--json]\n');
1053
+ process.stderr.write('Usage: shrk graph path <from> <to> [--max-depth N] [--no-refresh] [--json]\n');
1029
1054
  return 2;
1030
1055
  }
1031
1056
  const maxDepth = Math.max(1, Math.min(32, Number(flagString(args, 'max-depth') ?? '16')));
@@ -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,CAmD1C;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;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"}
@@ -31,21 +31,16 @@ export function renderStartScreen() {
31
31
  lines.push('Usage: shrk [--cwd <dir>] <command> [...args]');
32
32
  lines.push('');
33
33
  lines.push('Bootstrap:');
34
- lines.push(' $ shrk init --infer --write — scan the repo + populate sharkcraft/ from real signals (recommended for new repos)');
35
- lines.push(' $ shrk import claude-md ./CLAUDE.md --populate --write — populate sharkcraft/ from existing CLAUDE.md / AGENTS.md / .cursor/rules');
36
- lines.push(' $ shrk init --with-claude-skill --write — scaffold sharkcraft/ AND inline rules into .claude/skills/ (one-step)');
37
- lines.push(' $ shrk init — scaffold sharkcraft/ + config skeleton (preset defaults)');
34
+ lines.push(' $ shrk init --infer --write — scan the repo + populate sharkcraft/ from real signals (new repos)');
38
35
  lines.push(' $ shrk doctor — is the workspace healthy?');
39
- lines.push(' $ shrk inspect — detect frameworks, paths, package manager');
40
36
  lines.push(' $ shrk onboard — analyze an existing repo (advisory)');
41
37
  lines.push('');
42
38
  lines.push('Use it for a task:');
43
- lines.push(' $ shrk brief — single-page brief Claude reads first (project + rules + paths + verification)');
44
39
  lines.push(' $ shrk recommend "<task>" — what should I do?');
45
40
  lines.push(' $ shrk context --task "<task>" — token-budgeted relevant context');
46
41
  lines.push(' $ shrk task "<task>" — full AI-ready task packet (JSON)');
47
- lines.push(' $ shrk code-intelone-shot code-intelligence health summary');
48
- lines.push(' $ shrk coverage — what knowledge is missing');
42
+ lines.push(' $ shrk why <file>which rules govern this file');
43
+ lines.push(' $ shrk impact <file> blast radius: what breaks if I change this');
49
44
  lines.push('');
50
45
  lines.push('Generate code safely:');
51
46
  lines.push(' $ shrk gen <template> <name> — generate from template (dry-run by default)');
@@ -54,22 +49,16 @@ export function renderStartScreen() {
54
49
  lines.push(' $ shrk quality — pre-PR gate (doctor + boundaries + coverage + drift)');
55
50
  lines.push('');
56
51
  lines.push('Browse what shrk knows:');
57
- lines.push(' $ shrk graph status current code-graph freshness and health');
52
+ lines.push(' $ shrk graph status — code-graph freshness and health');
53
+ lines.push(' $ shrk coverage — what knowledge is missing');
58
54
  lines.push(' $ shrk knowledge list — knowledge entries');
59
- lines.push(' $ shrk rules list — rules + conventions');
60
- lines.push(' $ shrk templates list — generator templates');
61
- lines.push(' $ shrk import — parse AGENTS.md / CLAUDE.md / .cursor/rules');
62
- lines.push(' $ shrk export — render to a flat agent-rule file');
63
55
  lines.push('');
64
56
  lines.push('Run shrk for an agent:');
65
- lines.push(' $ shrk export claude-skill --write — generate .claude/skills/<name>/SKILL.md (rules INTO the prompt — no MCP roundtrip)');
66
- lines.push(' $ shrk export agents-md --write — generate AGENTS.md / CLAUDE.md / .cursor/rules / copilot-instructions');
67
57
  lines.push(' $ shrk mcp serve — start the MCP server (stdio) for live queries');
68
58
  lines.push(' $ shrk dashboard — start the local read-only dashboard');
69
59
  lines.push('');
70
- lines.push('Discover the rest (always callable, hidden from this screen by default):');
71
- lines.push(' $ shrk surface list — full ~70-verb catalog by tier');
72
- lines.push(' $ shrk surface profiles — named profiles (small-app / monorepo / ci / agent / pack-author)');
60
+ lines.push('Discover the rest (everything stays callable this screen shows ~17 of ~70 verbs):');
61
+ lines.push(' $ shrk surface list — full catalog by tier');
73
62
  lines.push(' $ shrk help <command> — usage for a specific command');
74
63
  lines.push(' $ shrk --full-help — long, exhaustive help');
75
64
  lines.push(' $ shrk --about — what shrk is and is not');
@@ -1 +1 @@
1
- {"version":3,"file":"knowledge-author.command.d.ts","sourceRoot":"","sources":["../../src/commands/knowledge-author.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAoBH,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAwChC,eAAO,MAAM,mBAAmB,EAAE,eAgEjC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eA0GpC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eAoEpC,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,eA2DlC,CAAC;AAEF,eAAO,MAAM,6BAA6B,EAAE,eAmB3C,CAAC"}
1
+ {"version":3,"file":"knowledge-author.command.d.ts","sourceRoot":"","sources":["../../src/commands/knowledge-author.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAoBH,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAwChC,eAAO,MAAM,mBAAmB,EAAE,eAgEjC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eA0GpC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eAoEpC,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,eAsElC,CAAC;AAEF,eAAO,MAAM,6BAA6B,EAAE,eAmB3C,CAAC"}
@@ -300,6 +300,15 @@ export const knowledgeLintCommand = {
300
300
  async run(args) {
301
301
  const cwd = resolveCwd(args);
302
302
  const inspection = await inspectSharkcraft({ cwd });
303
+ // Loud guard: a 0-entry scan is NOT a clean pass — it almost always means
304
+ // the loader found nothing (wrong cwd / no `sharkcraft/` folder / no
305
+ // entries), which otherwise reads as "lint passed". Surface it on stderr so
306
+ // it can't be mistaken for success, while keeping stdout/JSON clean.
307
+ if (inspection.knowledgeEntries.length === 0) {
308
+ process.stderr.write('WARN knowledge lint scanned 0 entries — there is nothing to lint (this is NOT a clean pass).\n' +
309
+ ' Likely causes: no `sharkcraft/` folder here, not at the workspace root, or no\n' +
310
+ ' knowledge entries are defined. Run `shrk doctor` to confirm how many entries load.\n');
311
+ }
303
312
  const entryIds = flagList(args, 'id');
304
313
  const includeAdvisory = !flagBool(args, 'no-advisory');
305
314
  const stale = buildKnowledgeStaleReport(inspection);
@@ -1 +1 @@
1
- {"version":3,"file":"knowledge-propose.command.d.ts","sourceRoot":"","sources":["../../src/commands/knowledge-propose.command.ts"],"names":[],"mappings":"AAqBA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAkDhC,eAAO,MAAM,uBAAuB,EAAE,eA0ErC,CAAC"}
1
+ {"version":3,"file":"knowledge-propose.command.d.ts","sourceRoot":"","sources":["../../src/commands/knowledge-propose.command.ts"],"names":[],"mappings":"AAqBA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAkDhC,eAAO,MAAM,uBAAuB,EAAE,eA4ErC,CAAC"}
@@ -10,7 +10,7 @@
10
10
  import { mkdirSync, writeFileSync } from 'node:fs';
11
11
  import * as nodePath from 'node:path';
12
12
  import { AssetKind, AssetProvenanceOperation, KNOWLEDGE_PROPOSE_SCHEMA, proposeKnowledge, recordProvenance, renderKnowledgeProposeMarkdown, } from '@shrkcrft/inspector';
13
- import { flagBool, flagString, resolveCwd, } from "../command-registry.js";
13
+ import { flagBool, flagNumber, flagString, resolveCwd, } from "../command-registry.js";
14
14
  import { asJson } from "../output/format-output.js";
15
15
  import { detectAuthoringSource } from "../authoring/authoring-kit.js";
16
16
  function renderProposalAsTs(p) {
@@ -54,13 +54,14 @@ function writeDraftFiles(cwd, report) {
54
54
  export const knowledgeProposeCommand = {
55
55
  name: 'propose',
56
56
  description: 'Propose stub knowledge entries for exported top-level constructs that lack coverage. Preview-first; --write materialises drafts under .sharkcraft/authoring/proposed/.',
57
- usage: 'shrk knowledge propose [--path <file>] [--symbol <name>] [--since <ref>|--all] [--json] [--write]',
57
+ usage: 'shrk knowledge propose [--path <file>] [--symbol <name>] [--since <ref>|--all] [--max <n>] [--json] [--write]',
58
58
  async run(args) {
59
59
  const cwd = resolveCwd(args);
60
60
  const path = flagString(args, 'path');
61
61
  const symbol = flagString(args, 'symbol');
62
62
  const sinceFlag = flagString(args, 'since');
63
63
  const all = flagBool(args, 'all');
64
+ const max = flagNumber(args, 'max');
64
65
  const since = path
65
66
  ? undefined
66
67
  : symbol
@@ -73,6 +74,7 @@ export const knowledgeProposeCommand = {
73
74
  ...(path ? { path } : {}),
74
75
  ...(symbol ? { symbol } : {}),
75
76
  ...(since !== undefined ? { since } : {}),
77
+ ...(max !== undefined && max >= 0 ? { max } : {}),
76
78
  });
77
79
  if (flagBool(args, 'json')) {
78
80
  const payload = flagBool(args, 'write')
@@ -1 +1 @@
1
- {"version":3,"file":"knowledge.command.d.ts","sourceRoot":"","sources":["../../src/commands/knowledge.command.ts"],"names":[],"mappings":"AAaA,OAAO,EAML,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAoFhC,eAAO,MAAM,oBAAoB,EAAE,eAsBlC,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,eAuBjC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eAqCpC,CAAC;AAoBF,eAAO,MAAM,0BAA0B,EAAE,eAWxC,CAAC;AA8NF,eAAO,MAAM,sBAAsB,EAAE,eAQpC,CAAC;AAEF,eAAO,MAAM,0BAA0B,EAAE,eA8CxC,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,eAuBrC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,4BAA4B,EAAE,eA8B1C,CAAC;AAEF,eAAO,MAAM,0BAA0B,EAAE,eA8BxC,CAAC;AAEF,eAAO,MAAM,4BAA4B,EAAE,eAkC1C,CAAC"}
1
+ {"version":3,"file":"knowledge.command.d.ts","sourceRoot":"","sources":["../../src/commands/knowledge.command.ts"],"names":[],"mappings":"AAaA,OAAO,EAML,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAoFhC,eAAO,MAAM,oBAAoB,EAAE,eA2ClC,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,eAuBjC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eAqCpC,CAAC;AAoBF,eAAO,MAAM,0BAA0B,EAAE,eAWxC,CAAC;AA8NF,eAAO,MAAM,sBAAsB,EAAE,eAQpC,CAAC;AAEF,eAAO,MAAM,0BAA0B,EAAE,eA8CxC,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,eAuBrC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,4BAA4B,EAAE,eA8B1C,CAAC;AAEF,eAAO,MAAM,0BAA0B,EAAE,eA8BxC,CAAC;AAEF,eAAO,MAAM,4BAA4B,EAAE,eAkC1C,CAAC"}
@@ -54,7 +54,7 @@ ${p.ci.exitNonZero ? `<p style="color:#a40000;font-weight:bold">FAIL: ${esc(p.ci
54
54
  export const knowledgeListCommand = {
55
55
  name: 'list',
56
56
  description: 'List knowledge entries.',
57
- usage: 'shrk knowledge list [--type rule] [--scope x,y] [--json]',
57
+ usage: 'shrk knowledge list [--type rule] [--scope x,y] [--top N] [--brief] [--json]',
58
58
  async run(args) {
59
59
  const inspection = await inspectSharkcraft({ cwd: resolveCwd(args) });
60
60
  const types = flagList(args, 'type');
@@ -64,8 +64,28 @@ export const knowledgeListCommand = {
64
64
  entries = entries.filter((e) => types.includes(String(e.type)));
65
65
  if (scope.length)
66
66
  entries = entries.filter((e) => scope.some((s) => e.scope.includes(s)));
67
+ // --top N: a deterministic, token-bounded slice. Sort by id first so the
68
+ // "top N" is stable across machines (entries otherwise load in fs-scan
69
+ // order). Reduce at the source instead of piping through `shrk compress`.
70
+ const top = flagNumber(args, 'top');
71
+ if (top !== undefined && top > 0) {
72
+ entries = [...entries].sort((a, b) => a.id.localeCompare(b.id)).slice(0, top);
73
+ }
67
74
  if (flagBool(args, 'json')) {
68
- process.stdout.write(asJson(entries.map((e) => ({ ...e }))) + '\n');
75
+ // --brief: project to the high-signal fields, dropping content / examples
76
+ // / metadata (the bulk of the payload) so an agent pays far fewer tokens.
77
+ const payload = flagBool(args, 'brief')
78
+ ? entries.map((e) => ({
79
+ id: e.id,
80
+ type: e.type,
81
+ priority: e.priority,
82
+ title: e.title,
83
+ scope: e.scope,
84
+ tags: e.tags,
85
+ appliesWhen: e.appliesWhen,
86
+ }))
87
+ : entries.map((e) => ({ ...e }));
88
+ process.stdout.write(asJson(payload) + '\n');
69
89
  return 0;
70
90
  }
71
91
  process.stdout.write(header(`Knowledge (${entries.length})`));
@@ -1 +1 @@
1
- {"version":3,"file":"preflight.command.d.ts","sourceRoot":"","sources":["../../src/commands/preflight.command.ts"],"names":[],"mappings":"AAoBA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAgBhC,eAAO,MAAM,gBAAgB,EAAE,eAmF9B,CAAC"}
1
+ {"version":3,"file":"preflight.command.d.ts","sourceRoot":"","sources":["../../src/commands/preflight.command.ts"],"names":[],"mappings":"AAoBA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAiBhC,eAAO,MAAM,gBAAgB,EAAE,eAoG9B,CAAC"}
@@ -13,6 +13,7 @@
13
13
  */
14
14
  import { planChangedPreflight, PreflightAction, PreflightProfile, renderChangedPreflightText, resolveChangedFiles, } from '@shrkcrft/inspector';
15
15
  import { flagBool, flagString, resolveCwd, } from "../command-registry.js";
16
+ import { loadProjectConfig } from '@shrkcrft/config';
16
17
  import { asJson, header } from "../output/format-output.js";
17
18
  function parseProfile(value) {
18
19
  if (value === 'quick')
@@ -55,10 +56,24 @@ export const preflightCommand = {
55
56
  resolveOpts.includeWorktree = true;
56
57
  }
57
58
  const changed = resolveChangedFiles(resolveOpts);
59
+ // Ground the test/typecheck gates in the project's declared verification
60
+ // commands when present, rather than the generic `bun test` / tsc defaults.
61
+ // Best-effort: a repo without sharkcraft/ config keeps the defaults.
62
+ let testCommand;
63
+ let typecheckCommand;
64
+ const loaded = await loadProjectConfig(cwd);
65
+ if (loaded.ok) {
66
+ const vcs = loaded.value.config.verificationCommands ?? [];
67
+ const pick = (...ids) => vcs.find((v) => ids.includes(v.id))?.command;
68
+ testCommand = pick('test', 'tests', 'unit', 'unit-tests');
69
+ typecheckCommand = pick('typecheck', 'tsc', 'types', 'typecheck-all');
70
+ }
58
71
  const plan = planChangedPreflight({
59
72
  projectRoot: cwd,
60
73
  changedFiles: changed.files,
61
74
  profile,
75
+ ...(testCommand ? { testCommand } : {}),
76
+ ...(typecheckCommand ? { typecheckCommand } : {}),
62
77
  });
63
78
  if (flagBool(args, 'json')) {
64
79
  process.stdout.write(asJson({ plan, scopeMode: changed.mode }) + '\n');
@@ -1,4 +1,10 @@
1
1
  import { type ICommandHandler } from '../command-registry.js';
2
2
  export declare const recommendCommand: ICommandHandler;
3
+ /**
4
+ * Does the query look like create/build work (a routing hint's `shrk gen`
5
+ * playbook is most useful here)? Mirrors {@link looksLikePlanning}: a
6
+ * create/build verb leading the query or in slots 1–3.
7
+ */
8
+ export declare function looksLikeCreateBuild(query: string): boolean;
3
9
  export declare function looksLikePlanning(query: string): boolean;
4
10
  //# sourceMappingURL=recommend.command.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"recommend.command.d.ts","sourceRoot":"","sources":["../../src/commands/recommend.command.ts"],"names":[],"mappings":"AASA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAShC,eAAO,MAAM,gBAAgB,EAAE,eA0K9B,CAAC;AAoGF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAcxD"}
1
+ {"version":3,"file":"recommend.command.d.ts","sourceRoot":"","sources":["../../src/commands/recommend.command.ts"],"names":[],"mappings":"AASA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAShC,eAAO,MAAM,gBAAgB,EAAE,eA+M9B,CAAC;AAgHF;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAY3D;AAcD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAcxD"}
@@ -79,6 +79,36 @@ export const recommendCommand = {
79
79
  searchReport = null;
80
80
  }
81
81
  }
82
+ // R1 — promote a strongly-matched task-routing hint's recommends.commands
83
+ // into the HEADLINE for create/build intents. Routing hints are scored by
84
+ // explainTaskRouting but were previously only shown under --verbose, so the
85
+ // headline fell back to generic review/report/impact commands even when the
86
+ // pack declared the right `shrk gen <template>` playbook. When the top hint
87
+ // clears the threshold AND the query looks like create/build work, its
88
+ // commands lead and the generic defaults slide to the tail. Deterministic.
89
+ const topHint = routingMatches[0];
90
+ const hintCommands = topHint?.hint.recommends.commands ?? [];
91
+ if (query.length > 0 &&
92
+ looksLikeCreateBuild(query) &&
93
+ topHint &&
94
+ topHint.score >= ROUTING_HINT_PROMOTE_THRESHOLD &&
95
+ hintCommands.length > 0) {
96
+ const promoted = hintCommands.map((command) => ({
97
+ command,
98
+ why: `Routing hint "${topHint.hint.id}" matched (score ${topHint.score}) — project playbook for this create/build task.`,
99
+ safetyLevel: promotedSafetyLevel(command),
100
+ }));
101
+ const promotedSet = new Set(promoted.map((p) => p.command));
102
+ const isGenericDefault = (c) => /^(bun run )?shrk (review|report|impact)\b/i.test(c);
103
+ const existing = report.recommendations;
104
+ const keptNonGeneric = existing.filter((r) => !promotedSet.has(r.command) && !isGenericDefault(r.command));
105
+ const keptGeneric = existing.filter((r) => !promotedSet.has(r.command) && isGenericDefault(r.command));
106
+ report.recommendations = [
107
+ ...promoted,
108
+ ...keptNonGeneric,
109
+ ...keptGeneric,
110
+ ];
111
+ }
82
112
  if (machineJson) {
83
113
  process.stdout.write(asJson({
84
114
  ...report,
@@ -250,6 +280,48 @@ const PLANNING_VERBS = new Set([
250
280
  'evaluate',
251
281
  'assess',
252
282
  ]);
283
+ /**
284
+ * Minimum routing-hint score (from `explainTaskRouting`: +2 per keyword, +3 per
285
+ * phrase, +2 per regex, + confidenceBoost) required to promote a hint's
286
+ * commands into the recommend headline. 3 ⇒ at least one phrase match or two
287
+ * keyword/regex hits — a real match, not a single weak keyword.
288
+ */
289
+ const ROUTING_HINT_PROMOTE_THRESHOLD = 3;
290
+ const CREATE_BUILD_VERBS = new Set([
291
+ 'create', 'build', 'add', 'generate', 'scaffold', 'implement', 'make', 'new', 'introduce', 'write',
292
+ ]);
293
+ /**
294
+ * Does the query look like create/build work (a routing hint's `shrk gen`
295
+ * playbook is most useful here)? Mirrors {@link looksLikePlanning}: a
296
+ * create/build verb leading the query or in slots 1–3.
297
+ */
298
+ export function looksLikeCreateBuild(query) {
299
+ const tokens = query
300
+ .toLowerCase()
301
+ .replace(/[^a-z0-9\s]/g, ' ')
302
+ .split(/\s+/)
303
+ .filter((t) => t.length > 0);
304
+ if (tokens.length === 0)
305
+ return false;
306
+ if (CREATE_BUILD_VERBS.has(tokens[0]))
307
+ return true;
308
+ for (let i = 1; i < Math.min(4, tokens.length); i++) {
309
+ if (CREATE_BUILD_VERBS.has(tokens[i]))
310
+ return true;
311
+ }
312
+ return false;
313
+ }
314
+ /** Conservative safety classification for a promoted routing-hint command. */
315
+ function promotedSafetyLevel(command) {
316
+ if (/^(bun run )?shrk (gen|init|apply|import)\b/i.test(command))
317
+ return 'writes-source';
318
+ if (/^(bun run )?shrk (brief|dev|onboard|simulate|orchestrate|spec)\b/i.test(command)) {
319
+ return 'writes-drafts';
320
+ }
321
+ if (/^(bun|bunx|npm|pnpm|node|git|nx) /i.test(command))
322
+ return 'runs-shell';
323
+ return 'read-only';
324
+ }
253
325
  export function looksLikePlanning(query) {
254
326
  const tokens = query
255
327
  .toLowerCase()
@@ -1 +1 @@
1
- {"version":3,"file":"rules.command.d.ts","sourceRoot":"","sources":["../../src/commands/rules.command.ts"],"names":[],"mappings":"AAcA,OAAO,EAML,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAQhC,eAAO,MAAM,gBAAgB,EAAE,eAe9B,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,eAuB7B,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,eAmClC,CAAC;AAuBF,eAAO,MAAM,oBAAoB,EAAE,eAiFlC,CAAC;AAkBF;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,EAAE,eAsB7B,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,EAAE,eA6BhC,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,kBAAkB,EAAE,eA6BhC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EAAE,eA2F9B,CAAC;AAoCF,eAAO,MAAM,kBAAkB,EAAE,eAiChC,CAAC"}
1
+ {"version":3,"file":"rules.command.d.ts","sourceRoot":"","sources":["../../src/commands/rules.command.ts"],"names":[],"mappings":"AAcA,OAAO,EAML,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAQhC,eAAO,MAAM,gBAAgB,EAAE,eAgC9B,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,eAuB7B,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,eAmClC,CAAC;AAuBF,eAAO,MAAM,oBAAoB,EAAE,eAiFlC,CAAC;AAkBF;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,EAAE,eAsB7B,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,EAAE,eA6BhC,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,kBAAkB,EAAE,eA6BhC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EAAE,eA2F9B,CAAC;AAoCF,eAAO,MAAM,kBAAkB,EAAE,eAiChC,CAAC"}
@@ -8,12 +8,29 @@ import { knowledgeAddCommand, knowledgeRemoveCommand, knowledgeUpdateCommand, }
8
8
  export const rulesListCommand = {
9
9
  name: 'list',
10
10
  description: 'List all rules.',
11
- usage: 'shrk rules list [--json]',
11
+ usage: 'shrk rules list [--top N] [--brief] [--json]',
12
12
  async run(args) {
13
13
  const inspection = await inspectSharkcraft({ cwd: resolveCwd(args) });
14
- const rules = inspection.ruleService.list();
14
+ let rules = inspection.ruleService.list();
15
+ // --top N: deterministic, token-bounded slice (id-sorted so it's stable).
16
+ const top = flagNumber(args, 'top');
17
+ if (top !== undefined && top > 0) {
18
+ rules = [...rules].sort((a, b) => a.id.localeCompare(b.id)).slice(0, top);
19
+ }
15
20
  if (flagBool(args, 'json')) {
16
- process.stdout.write(asJson(rules) + '\n');
21
+ // --brief: project to the high-signal fields (drop content/examples).
22
+ const payload = flagBool(args, 'brief')
23
+ ? rules.map((r) => ({
24
+ id: r.id,
25
+ type: r.type,
26
+ priority: r.priority,
27
+ title: r.title,
28
+ scope: r.scope,
29
+ tags: r.tags,
30
+ appliesWhen: r.appliesWhen,
31
+ }))
32
+ : rules;
33
+ process.stdout.write(asJson(payload) + '\n');
17
34
  return 0;
18
35
  }
19
36
  process.stdout.write(header(`Rules (${rules.length})`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shrkcrft/cli",
3
- "version": "0.1.0-alpha.18",
3
+ "version": "0.1.0-alpha.19",
4
4
  "description": "SharkCraft CLI (`shrk`): structured project intelligence for AI coding agents.",
5
5
  "license": "MIT",
6
6
  "author": "SharkCraft contributors",
@@ -47,38 +47,38 @@
47
47
  "typecheck": "tsc --noEmit -p tsconfig.json"
48
48
  },
49
49
  "dependencies": {
50
- "@shrkcrft/core": "^0.1.0-alpha.18",
51
- "@shrkcrft/compress": "^0.1.0-alpha.18",
52
- "@shrkcrft/config": "^0.1.0-alpha.18",
53
- "@shrkcrft/workspace": "^0.1.0-alpha.18",
54
- "@shrkcrft/knowledge": "^0.1.0-alpha.18",
55
- "@shrkcrft/context": "^0.1.0-alpha.18",
56
- "@shrkcrft/rules": "^0.1.0-alpha.18",
57
- "@shrkcrft/paths": "^0.1.0-alpha.18",
58
- "@shrkcrft/templates": "^0.1.0-alpha.18",
59
- "@shrkcrft/plugin-api": "^0.1.0-alpha.18",
60
- "@shrkcrft/dashboard": "^0.1.0-alpha.18",
61
- "@shrkcrft/dashboard-api": "^0.1.0-alpha.18",
62
- "@shrkcrft/pipelines": "^0.1.0-alpha.18",
63
- "@shrkcrft/presets": "^0.1.0-alpha.18",
64
- "@shrkcrft/boundaries": "^0.1.0-alpha.18",
65
- "@shrkcrft/graph": "^0.1.0-alpha.18",
66
- "@shrkcrft/rule-graph": "^0.1.0-alpha.18",
67
- "@shrkcrft/structural-search": "^0.1.0-alpha.18",
68
- "@shrkcrft/impact-engine": "^0.1.0-alpha.18",
69
- "@shrkcrft/context-planner": "^0.1.0-alpha.18",
70
- "@shrkcrft/architecture-guard": "^0.1.0-alpha.18",
71
- "@shrkcrft/framework-scanners": "^0.1.0-alpha.18",
72
- "@shrkcrft/api-surface-diff": "^0.1.0-alpha.18",
73
- "@shrkcrft/quality-gates": "^0.1.0-alpha.18",
74
- "@shrkcrft/migrate": "^0.1.0-alpha.18",
75
- "@shrkcrft/generator": "^0.1.0-alpha.18",
76
- "@shrkcrft/importer": "^0.1.0-alpha.18",
77
- "@shrkcrft/inspector": "^0.1.0-alpha.18",
78
- "@shrkcrft/ai": "^0.1.0-alpha.18",
79
- "@shrkcrft/embeddings": "^0.1.0-alpha.18",
80
- "@shrkcrft/shared": "^0.1.0-alpha.18",
81
- "@shrkcrft/mcp-server": "^0.1.0-alpha.18",
50
+ "@shrkcrft/core": "^0.1.0-alpha.19",
51
+ "@shrkcrft/compress": "^0.1.0-alpha.19",
52
+ "@shrkcrft/config": "^0.1.0-alpha.19",
53
+ "@shrkcrft/workspace": "^0.1.0-alpha.19",
54
+ "@shrkcrft/knowledge": "^0.1.0-alpha.19",
55
+ "@shrkcrft/context": "^0.1.0-alpha.19",
56
+ "@shrkcrft/rules": "^0.1.0-alpha.19",
57
+ "@shrkcrft/paths": "^0.1.0-alpha.19",
58
+ "@shrkcrft/templates": "^0.1.0-alpha.19",
59
+ "@shrkcrft/plugin-api": "^0.1.0-alpha.19",
60
+ "@shrkcrft/dashboard": "^0.1.0-alpha.19",
61
+ "@shrkcrft/dashboard-api": "^0.1.0-alpha.19",
62
+ "@shrkcrft/pipelines": "^0.1.0-alpha.19",
63
+ "@shrkcrft/presets": "^0.1.0-alpha.19",
64
+ "@shrkcrft/boundaries": "^0.1.0-alpha.19",
65
+ "@shrkcrft/graph": "^0.1.0-alpha.19",
66
+ "@shrkcrft/rule-graph": "^0.1.0-alpha.19",
67
+ "@shrkcrft/structural-search": "^0.1.0-alpha.19",
68
+ "@shrkcrft/impact-engine": "^0.1.0-alpha.19",
69
+ "@shrkcrft/context-planner": "^0.1.0-alpha.19",
70
+ "@shrkcrft/architecture-guard": "^0.1.0-alpha.19",
71
+ "@shrkcrft/framework-scanners": "^0.1.0-alpha.19",
72
+ "@shrkcrft/api-surface-diff": "^0.1.0-alpha.19",
73
+ "@shrkcrft/quality-gates": "^0.1.0-alpha.19",
74
+ "@shrkcrft/migrate": "^0.1.0-alpha.19",
75
+ "@shrkcrft/generator": "^0.1.0-alpha.19",
76
+ "@shrkcrft/importer": "^0.1.0-alpha.19",
77
+ "@shrkcrft/inspector": "^0.1.0-alpha.19",
78
+ "@shrkcrft/ai": "^0.1.0-alpha.19",
79
+ "@shrkcrft/embeddings": "^0.1.0-alpha.19",
80
+ "@shrkcrft/shared": "^0.1.0-alpha.19",
81
+ "@shrkcrft/mcp-server": "^0.1.0-alpha.19",
82
82
  "@huggingface/transformers": "^3.7.5"
83
83
  },
84
84
  "publishConfig": {