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

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 (31) hide show
  1. package/dist/commands/api-diff.command.js +35 -7
  2. package/dist/commands/codemod.command.d.ts.map +1 -1
  3. package/dist/commands/codemod.command.js +27 -6
  4. package/dist/commands/command-catalog.d.ts +8 -0
  5. package/dist/commands/command-catalog.d.ts.map +1 -1
  6. package/dist/commands/command-catalog.js +21 -0
  7. package/dist/commands/compress.command.d.ts.map +1 -1
  8. package/dist/commands/compress.command.js +14 -0
  9. package/dist/commands/constructs.command.d.ts.map +1 -1
  10. package/dist/commands/constructs.command.js +85 -2
  11. package/dist/commands/contract-gate.command.d.ts.map +1 -1
  12. package/dist/commands/contract-gate.command.js +5 -1
  13. package/dist/commands/gate.command.d.ts.map +1 -1
  14. package/dist/commands/gate.command.js +5 -1
  15. package/dist/commands/graph-code-subverbs.d.ts.map +1 -1
  16. package/dist/commands/graph-code-subverbs.js +10 -1
  17. package/dist/commands/ingest.command.d.ts.map +1 -1
  18. package/dist/commands/ingest.command.js +5 -1
  19. package/dist/commands/knowledge.command.d.ts.map +1 -1
  20. package/dist/commands/knowledge.command.js +5 -2
  21. package/dist/commands/plan-context.command.d.ts.map +1 -1
  22. package/dist/commands/plan-context.command.js +13 -5
  23. package/dist/commands/recommend.command.d.ts.map +1 -1
  24. package/dist/commands/recommend.command.js +47 -5
  25. package/dist/commands/task-context.command.d.ts.map +1 -1
  26. package/dist/commands/task-context.command.js +5 -1
  27. package/dist/main.d.ts.map +1 -1
  28. package/dist/main.js +9 -1
  29. package/dist/surface/profiles.d.ts.map +1 -1
  30. package/dist/surface/profiles.js +54 -9
  31. package/package.json +34 -34
@@ -15,7 +15,7 @@ import { asJson, header, kv } from "../output/format-output.js";
15
15
  export const apiDiffCommand = {
16
16
  name: 'api-diff',
17
17
  description: 'Compare the current public API surface to a saved baseline. Reports added / removed / kind-changed / moved symbols, with breaking-change severity.',
18
- usage: 'shrk api-diff capture --output <path> [--packages a,b] [--with-signatures] | shrk api-diff <baseline.json> [--packages a,b] [--with-signatures] [--json] [--fail-on-breaking]',
18
+ usage: 'shrk api-diff capture --output <path> [--packages @scope/a,@scope/b] [--with-signatures] | shrk api-diff <baseline.json> [--packages @scope/a,@scope/b] [--with-signatures] [--json] [--fail-on-breaking] (--packages takes EXACT workspace package names, comma-separated or repeated)',
19
19
  async run(args) {
20
20
  const cwd = resolveCwd(args);
21
21
  const wantJson = flagBool(args, 'json');
@@ -96,6 +96,7 @@ async function runDiff(args, cwd, wantJson, baselinePath) {
96
96
  function readSurfaceFromCwd(cwd, args) {
97
97
  const packages = flagList(args, 'packages');
98
98
  const withSignatures = flagBool(args, 'with-signatures');
99
+ let surface;
99
100
  if (withSignatures) {
100
101
  const result = extractApiSurfaceWithProgram({
101
102
  projectRoot: cwd,
@@ -104,13 +105,40 @@ function readSurfaceFromCwd(cwd, args) {
104
105
  for (const d of result.diagnostics.slice(0, 5)) {
105
106
  process.stderr.write(`! ${d}\n`);
106
107
  }
107
- return result.surface;
108
+ surface = result.surface;
108
109
  }
109
- const store = new GraphStore(cwd);
110
- if (!store.exists()) {
111
- process.stderr.write("Code-graph store missing. Run 'shrk graph index' first.\n");
110
+ else {
111
+ const store = new GraphStore(cwd);
112
+ if (!store.exists()) {
113
+ process.stderr.write("Code-graph store missing. Run 'shrk graph index' first.\n");
114
+ return undefined;
115
+ }
116
+ const snap = store.loadSnapshot();
117
+ surface = extractApiSurface(snap, {
118
+ ...(packages.length > 0 ? { packageFilter: packages } : {}),
119
+ });
120
+ }
121
+ return applyPackageFilterGuard(surface, packages);
122
+ }
123
+ /**
124
+ * A `--packages` filter that resolves to no known workspace package is a quiet
125
+ * footgun: the extractor silently returns a 0-symbol surface (exit 0) that is
126
+ * indistinguishable from a package that genuinely exports nothing. When EVERY
127
+ * requested filter is unknown, abort loudly; when only some are, warn and keep
128
+ * the partial result.
129
+ */
130
+ function applyPackageFilterGuard(surface, packages) {
131
+ const unmatched = surface.unmatchedFilters ?? [];
132
+ if (packages.length === 0 || unmatched.length === 0)
133
+ return surface;
134
+ const matched = Object.keys(surface.countsByPackage).filter((k) => k !== '<no-package>');
135
+ if (unmatched.length === packages.length) {
136
+ process.stderr.write(`--packages matched no known workspace package: ${unmatched.join(', ')}\n` +
137
+ ' Pass exact package names (npm "name", e.g. @shrkcrft/core), comma-separated or repeated.\n');
112
138
  return undefined;
113
139
  }
114
- const snap = store.loadSnapshot();
115
- return extractApiSurface(snap, { ...(packages.length > 0 ? { packageFilter: packages } : {}) });
140
+ process.stderr.write(`! --packages: ignoring unknown package(s): ${unmatched.join(', ')}` +
141
+ (matched.length > 0 ? ` (matched: ${matched.join(', ')})` : '') +
142
+ '\n');
143
+ return surface;
116
144
  }
@@ -1 +1 @@
1
- {"version":3,"file":"codemod.command.d.ts","sourceRoot":"","sources":["../../src/commands/codemod.command.ts"],"names":[],"mappings":"AAgBA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAoDhC,eAAO,MAAM,cAAc,EAAE,eAoE5B,CAAC"}
1
+ {"version":3,"file":"codemod.command.d.ts","sourceRoot":"","sources":["../../src/commands/codemod.command.ts"],"names":[],"mappings":"AAiBA,OAAO,EAML,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAuEhC,eAAO,MAAM,cAAc,EAAE,eAsE5B,CAAC"}
@@ -8,23 +8,41 @@
8
8
  import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
9
9
  import * as nodePath from 'node:path';
10
10
  import { affectedFromCheckReport, buildCodemodAssistReport, inspectSharkcraft, parseCustomCheckReportFromFile, renderCodemodAssistMarkdown, } from '@shrkcrft/inspector';
11
- import { flagBool, flagString, flagList, resolveCwd, } from "../command-registry.js";
11
+ import { formatRuleCompact } from '@shrkcrft/rules';
12
+ import { flagBool, flagNumber, flagString, flagList, resolveCwd, } from "../command-registry.js";
12
13
  import { asJson, header } from "../output/format-output.js";
13
14
  async function loadRule(args) {
14
15
  const ruleId = flagString(args, 'rule');
15
16
  if (!ruleId) {
16
- process.stderr.write('Missing --rule <ruleId>\n');
17
+ process.stderr.write('Missing --rule <ruleId>. Run `shrk codemod list` to see available rule ids.\n');
17
18
  return null;
18
19
  }
19
20
  const cwd = resolveCwd(args);
20
21
  const inspection = await inspectSharkcraft({ cwd });
21
22
  const rule = inspection.ruleService.get(ruleId);
22
23
  if (!rule) {
23
- process.stderr.write(`No rule with id "${ruleId}".\n`);
24
+ process.stderr.write(`No rule with id "${ruleId}". Run \`shrk codemod list\` to see available rule ids.\n`);
24
25
  return null;
25
26
  }
26
27
  return { rule, cwd };
27
28
  }
29
+ /** `shrk codemod list` — enumerate the rule ids codemod-assist accepts. */
30
+ async function listRules(args) {
31
+ const inspection = await inspectSharkcraft({ cwd: resolveCwd(args) });
32
+ let rules = inspection.ruleService.list();
33
+ const top = flagNumber(args, 'top');
34
+ if (top !== undefined && top > 0) {
35
+ rules = [...rules].sort((a, b) => a.id.localeCompare(b.id)).slice(0, top);
36
+ }
37
+ if (flagBool(args, 'json')) {
38
+ process.stdout.write(asJson(rules.map((r) => ({ id: r.id, type: r.type, priority: r.priority, title: r.title }))) + '\n');
39
+ return 0;
40
+ }
41
+ process.stdout.write(header(`Codemod rules (${rules.length})`));
42
+ for (const r of rules)
43
+ process.stdout.write(formatRuleCompact(r) + '\n');
44
+ return 0;
45
+ }
28
46
  async function gatherAffected(args) {
29
47
  const fromReport = flagString(args, 'from-report');
30
48
  const out = [];
@@ -57,14 +75,17 @@ function writeAssistOutputs(cwd, ruleId, markdown, scriptTemplate) {
57
75
  export const codemodCommand = {
58
76
  name: 'codemod',
59
77
  description: 'Codemod-assist (NOT a codemod engine). Inventory + risk grouping + checklist + project-script template. Never rewrites source.',
60
- usage: 'shrk codemod <inventory|plan|checklist> --rule <ruleId> [--from-report <path>] [--targets a,b,c] [--write-preview] [--json]',
78
+ usage: 'shrk codemod <list|inventory|plan|checklist> --rule <ruleId> [--from-report <path>] [--targets a,b,c] [--write-preview] [--json]',
61
79
  async run(args) {
62
80
  const sub = args.positional[0] ?? 'plan';
63
- if (!['inventory', 'plan', 'checklist'].includes(sub)) {
64
- process.stderr.write('Usage: shrk codemod <inventory|plan|checklist> --rule <ruleId>\n');
81
+ if (!['list', 'inventory', 'plan', 'checklist'].includes(sub)) {
82
+ process.stderr.write('Usage: shrk codemod <list|inventory|plan|checklist> --rule <ruleId>\n');
65
83
  return 2;
66
84
  }
67
85
  const inner = { ...args, positional: args.positional.slice(1) };
86
+ // `list` enumerates rule ids and needs no --rule.
87
+ if (sub === 'list')
88
+ return listRules(inner);
68
89
  const loaded = await loadRule(inner);
69
90
  if (!loaded)
70
91
  return 1;
@@ -266,4 +266,12 @@ export interface ICommandSafetyMatrixRow {
266
266
  }
267
267
  export declare function buildCommandSafetyMatrix(): readonly ICommandSafetyMatrixRow[];
268
268
  export declare function renderCommandSafetyMatrixMarkdown(rows: readonly ICommandSafetyMatrixRow[]): string;
269
+ /**
270
+ * The set of TOP-LEVEL command verbs in the catalogue (the first token of each
271
+ * entry's `command`). The single source of truth for "is `shrk <verb>` a real
272
+ * command" checks — e.g. the contradictions doc-vs-CLI scan, which lives in the
273
+ * inspector layer and so receives this set by injection rather than importing
274
+ * the CLI.
275
+ */
276
+ export declare function cliCommandNameSet(): Set<string>;
269
277
  //# sourceMappingURL=command-catalog.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"command-catalog.d.ts","sourceRoot":"","sources":["../../src/commands/command-catalog.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"command-catalog.d.ts","sourceRoot":"","sources":["../../src/commands/command-catalog.ts"],"names":[],"mappings":"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"}
@@ -3572,3 +3572,24 @@ export function renderCommandSafetyMatrixMarkdown(rows) {
3572
3572
  }
3573
3573
  return lines.join('\n') + '\n';
3574
3574
  }
3575
+ /**
3576
+ * The set of TOP-LEVEL command verbs in the catalogue (the first token of each
3577
+ * entry's `command`). The single source of truth for "is `shrk <verb>` a real
3578
+ * command" checks — e.g. the contradictions doc-vs-CLI scan, which lives in the
3579
+ * inspector layer and so receives this set by injection rather than importing
3580
+ * the CLI.
3581
+ */
3582
+ export function cliCommandNameSet() {
3583
+ const out = new Set();
3584
+ for (const entry of COMMAND_CATALOG) {
3585
+ const top = entry.command.split(/\s+/)[0];
3586
+ if (top)
3587
+ out.add(top);
3588
+ for (const alias of entry.aliases) {
3589
+ const aliasTop = alias.split(/\s+/)[0];
3590
+ if (aliasTop)
3591
+ out.add(aliasTop);
3592
+ }
3593
+ }
3594
+ return out;
3595
+ }
@@ -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;AA2BhC,eAAO,MAAM,eAAe,EAAE,eAgF7B,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;AAgChC,eAAO,MAAM,eAAe,EAAE,eA8F7B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,eA4B3B,CAAC"}
@@ -5,6 +5,10 @@ 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
+ // Below this size a passthrough no-op isn't worth a warning (tiny snippets
9
+ // legitimately have nothing to compress). Above it, a silent `−0%` re-emit is
10
+ // the opposite of the tool's purpose — nudge the user toward `--type`.
11
+ const PASSTHROUGH_HINT_MIN_BYTES = 128;
8
12
  function readInput(args) {
9
13
  const positional = args.positional[0];
10
14
  const useStdin = flagBool(args, 'stdin') || positional === undefined || positional === '-';
@@ -97,6 +101,16 @@ export const compressCommand = {
97
101
  process.stdout.write(result.compressed + '\n');
98
102
  const cached = result.ccrKey ? ` · original cached as ${result.ccrKey} (shrk expand ${result.ccrKey})` : '';
99
103
  process.stderr.write(`${result.strategy}: ~${result.savings.before} → ~${result.savings.after} tokens (−${pct}%, est.)${cached}\n`);
104
+ // Token-economy guard: when auto-detect declines to compress a non-trivial
105
+ // input, a silent `−0%` re-emit looks like success. Nudge toward `--type`
106
+ // (stdout stays the verbatim blob; exit code unchanged).
107
+ const inputBytes = Buffer.byteLength(content, 'utf8');
108
+ if (result.strategy === ECompressionStrategy.Passthrough &&
109
+ inputBytes >= PASSTHROUGH_HINT_MIN_BYTES &&
110
+ !flagBool(args, 'lossless')) {
111
+ 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');
113
+ }
100
114
  return 0;
101
115
  },
102
116
  };
@@ -1 +1 @@
1
- {"version":3,"file":"constructs.command.d.ts","sourceRoot":"","sources":["../../src/commands/constructs.command.ts"],"names":[],"mappings":"AA6BA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAahC,eAAO,MAAM,qBAAqB,EAAE,eAqBnC,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,eAoDlC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eA8DpC,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;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,6 +1,7 @@
1
1
  import { mkdirSync, writeFileSync } from 'node:fs';
2
2
  import * as nodePath from 'node:path';
3
3
  import { AdoptionCheckpointStatus, buildConstructAdoptionDiff, buildConstructAdoptionPlan, buildSearchIndex, ConstructAdoptionCategory, evaluateAdoptionCheckpoint, hashDiffBody, inferConstructs, InferredConstructConfidence, inspectSharkcraft, loadConstructs, loadPlaybooks, readAdoptionCheckpoint, readConstructAdoptionStatus, recordAdoptionCheckpoint, renderConstructAdoptionDiff, renderConstructAdoptionMarkdown, renderConstructDraftsModule, searchIndex, SearchKind, traceConstruct, writeConstructAdoption, } from '@shrkcrft/inspector';
4
+ import { GraphQueryApi, GraphStore } from '@shrkcrft/graph';
4
5
  import { flagBool, flagNumber, flagString, resolveCwd, } from "../command-registry.js";
5
6
  import { asJson, header } from "../output/format-output.js";
6
7
  async function loadAll(args) {
@@ -9,6 +10,64 @@ async function loadAll(args) {
9
10
  const constructs = await loadConstructs(inspection);
10
11
  return { constructs, inspection };
11
12
  }
13
+ const GLOB_MAGIC = /[*?[\]{}]/;
14
+ function globToRegExp(glob) {
15
+ // Compile a minimal file glob (`**`, `*`, `?`) to an anchored RegExp. Mark the
16
+ // wildcards with control-char placeholders BEFORE regex-escaping the rest, so
17
+ // the escape pass and the wildcard expansion can never clobber each other (an
18
+ // earlier space-sentinel version was corrupted by editor whitespace handling).
19
+ const DSTAR_SLASH = '\u0000';
20
+ const DSTAR = '\u0001';
21
+ const STAR = '\u0002';
22
+ const QMARK = '\u0003';
23
+ const marked = glob
24
+ .replace(/\*\*\//g, DSTAR_SLASH)
25
+ .replace(/\*\*/g, DSTAR)
26
+ .replace(/\*/g, STAR)
27
+ .replace(/\?/g, QMARK);
28
+ const pattern = marked
29
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
30
+ .replaceAll(DSTAR_SLASH, '(?:.*/)?')
31
+ .replaceAll(DSTAR, '.*')
32
+ .replaceAll(STAR, '[^/]*')
33
+ .replaceAll(QMARK, '[^/]');
34
+ return new RegExp(`^${pattern}$`);
35
+ }
36
+ function enrichTraceWithGraph(declaredFiles, declaredNames, cwd) {
37
+ if (!new GraphStore(cwd).exists()) {
38
+ return { graphState: 'missing', resolvedFiles: [], unresolvedGlobs: [], undeclaredSymbols: [] };
39
+ }
40
+ const api = GraphQueryApi.fromStore(cwd);
41
+ const allPaths = [...api.allFiles()].map((n) => n.path).filter((p) => !!p);
42
+ const resolved = new Set();
43
+ const unresolvedGlobs = [];
44
+ for (const entry of declaredFiles) {
45
+ if (GLOB_MAGIC.test(entry)) {
46
+ const re = globToRegExp(entry);
47
+ const matches = allPaths.filter((p) => re.test(p));
48
+ if (matches.length === 0)
49
+ unresolvedGlobs.push(entry);
50
+ for (const m of matches)
51
+ resolved.add(m);
52
+ }
53
+ else if (api.findFile(entry)) {
54
+ resolved.add(entry);
55
+ }
56
+ }
57
+ const undeclared = new Set();
58
+ for (const path of resolved) {
59
+ for (const sym of api.symbolsIn(`file:${path}`)) {
60
+ if (sym.label && !declaredNames.has(sym.label))
61
+ undeclared.add(sym.label);
62
+ }
63
+ }
64
+ return {
65
+ graphState: 'fresh',
66
+ resolvedFiles: [...resolved].sort(),
67
+ unresolvedGlobs,
68
+ undeclaredSymbols: [...undeclared].sort(),
69
+ };
70
+ }
12
71
  export const constructsListCommand = {
13
72
  name: 'list',
14
73
  description: 'List registered constructs.',
@@ -91,7 +150,7 @@ export const constructsGetCommand = {
91
150
  };
92
151
  export const constructsTraceCommand = {
93
152
  name: 'trace',
94
- description: 'Trace all files / publicApi / events / tokens belonging to a construct. --deep adds related / test pointers when present.',
153
+ description: 'Trace a construct\'s declared files / publicApi / events / tokens, verified against the code graph (expands file globs, flags globs that match nothing, lists undeclared symbols defined in those files). --deep adds related / test pointers.',
95
154
  usage: 'shrk constructs trace <id> [--deep] [--json]',
96
155
  async run(args) {
97
156
  const id = args.positional[0];
@@ -107,6 +166,10 @@ export const constructsTraceCommand = {
107
166
  }
108
167
  const trace = traceConstruct(c);
109
168
  const deep = flagBool(args, 'deep');
169
+ // Verify the hand-declared inventory against the code graph (additive — does
170
+ // not change the declared files/tokens the other subverbs rely on).
171
+ const declaredNames = new Set([...trace.publicApi, ...trace.tokens, ...trace.events]);
172
+ const graph = enrichTraceWithGraph(trace.files, declaredNames, resolveCwd(args));
110
173
  const relatedAll = [
111
174
  ...(c.relatedKnowledge ?? []),
112
175
  ...(c.relatedRules ?? []),
@@ -122,7 +185,7 @@ export const constructsTraceCommand = {
122
185
  }
123
186
  : undefined;
124
187
  if (flagBool(args, 'json')) {
125
- process.stdout.write(asJson({ ...trace, ...(deepBlock ? { deep: deepBlock } : {}) }) + '\n');
188
+ process.stdout.write(asJson({ ...trace, graph, ...(deepBlock ? { deep: deepBlock } : {}) }) + '\n');
126
189
  return 0;
127
190
  }
128
191
  process.stdout.write(header(`Trace: ${id}`));
@@ -145,6 +208,26 @@ export const constructsTraceCommand = {
145
208
  for (const w of trace.warnings)
146
209
  process.stdout.write(` ! ${w}\n`);
147
210
  }
211
+ // Graph-verified view: this is the part that makes the declared inventory
212
+ // trustworthy rather than a hand-typed partial map.
213
+ if (graph.graphState === 'missing') {
214
+ process.stdout.write('\n(declared inventory only — run `shrk graph index` for a graph-verified inventory)\n');
215
+ }
216
+ else {
217
+ if (graph.unresolvedGlobs.length > 0) {
218
+ process.stdout.write('Unresolved file globs (matched 0 files in the graph):\n');
219
+ for (const g of graph.unresolvedGlobs)
220
+ process.stdout.write(` ! ${g}\n`);
221
+ }
222
+ if (graph.undeclaredSymbols.length > 0) {
223
+ process.stdout.write(`Graph found ${graph.undeclaredSymbols.length} symbol(s) defined in these files but NOT declared on the construct:\n`);
224
+ for (const s of graph.undeclaredSymbols.slice(0, 30))
225
+ process.stdout.write(` + ${s}\n`);
226
+ if (graph.undeclaredSymbols.length > 30) {
227
+ process.stdout.write(` … (${graph.undeclaredSymbols.length - 30} more)\n`);
228
+ }
229
+ }
230
+ }
148
231
  if (deepBlock) {
149
232
  process.stdout.write('\nDeep:\n');
150
233
  if (deepBlock.related.length > 0)
@@ -1 +1 @@
1
- {"version":3,"file":"contract-gate.command.d.ts","sourceRoot":"","sources":["../../src/commands/contract-gate.command.ts"],"names":[],"mappings":"AAYA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAWhC,eAAO,MAAM,oBAAoB,EAAE,eAqDlC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eA6EpC,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,eA6DnC,CAAC"}
1
+ {"version":3,"file":"contract-gate.command.d.ts","sourceRoot":"","sources":["../../src/commands/contract-gate.command.ts"],"names":[],"mappings":"AAYA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAWhC,eAAO,MAAM,oBAAoB,EAAE,eAqDlC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eA6EpC,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,eAiEnC,CAAC"}
@@ -148,7 +148,11 @@ export const contractStatusCommand = {
148
148
  async run(args) {
149
149
  const contractFile = args.positional[0];
150
150
  if (!contractFile) {
151
- process.stderr.write('Usage: shrk contract status <contract.json>\n');
151
+ // `contract status` inspects a SAVED contract file — it is not a zero-arg
152
+ // project-health command. Point at the canonical task form so the verb
153
+ // doesn't read as a broken status command.
154
+ process.stderr.write('Usage: shrk contract status <contract.json> (inspect a saved contract file)\n');
155
+ process.stderr.write('To build/inspect a contract for a task, run: shrk contract "<task>"\n');
152
156
  return 2;
153
157
  }
154
158
  const cwd = resolveCwd(args);
@@ -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,eA2FzB,CAAC"}
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"}
@@ -18,7 +18,7 @@ import { asJson, header, kv } from "../output/format-output.js";
18
18
  export const gateCommand = {
19
19
  name: 'gate',
20
20
  description: 'Aggregator: runs the code-intelligence quality gates (graph freshness, architecture, impact-since-ref) and reports a single pass/fail.',
21
- usage: 'shrk gate [--since <gitref>] [--fail-on critical,high] [--disable arch,impact,api-diff] [--api-baseline <path>] [--no-fail-on-breaking] [--strict] [--no-persist] [--json] [--markdown] [--output <path>]\n shrk gate scaffold-ci [--provider github|generic] [--force] [--json]\n shrk gate scaffold-hook [--provider husky|raw] [--force] [--json]',
21
+ usage: 'shrk gate [--since <gitref>] [--fail-on critical,high] [--arch-all] [--disable arch,impact,api-diff] [--api-baseline <path>] [--no-fail-on-breaking] [--strict] [--no-persist] [--json] [--markdown] [--output <path>]\n (the arch gate is baseline-relative by default once a baseline is frozen — fails only on NEW errors; --arch-all fails on total)\n shrk gate scaffold-ci [--provider github|generic] [--force] [--json]\n shrk gate scaffold-hook [--provider husky|raw] [--force] [--json]',
22
22
  async run(args) {
23
23
  if (args.positional[0] === 'scaffold-ci') {
24
24
  const sliced = { ...args, positional: args.positional.slice(1) };
@@ -42,8 +42,12 @@ export const gateCommand = {
42
42
  ? failOnRaw.split(',').map((s) => s.trim()).filter(Boolean)
43
43
  : undefined;
44
44
  const disable = disableRaw ? disableRaw.split(',').map((s) => s.trim()).filter(Boolean) : undefined;
45
+ // --arch-all: fail on TOTAL architecture errors (ignore the frozen baseline).
46
+ // By default the arch gate is baseline-relative — it fails only on NEW errors.
47
+ const archAll = flagBool(args, 'arch-all');
45
48
  const report = runQualityGates({
46
49
  projectRoot: cwd,
50
+ ...(archAll ? { arch: { baselineRelative: false } } : {}),
47
51
  impact: {
48
52
  ...(sinceRef ? { sinceRef } : {}),
49
53
  ...(failOn ? { failOn } : {}),
@@ -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;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"}
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,CA8HtE;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,CA+EvE;AAyBD;;;;;;;;;;;;GAYG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAgHpE"}
@@ -878,6 +878,11 @@ export async function runGraphImpact(args) {
878
878
  for (const d of liveDirect.slice(0, 30)) {
879
879
  process.stdout.write(` ${d.path ?? d.id}\n`);
880
880
  }
881
+ // No silent caps: when the reverse closure hit the limit, say so explicitly so
882
+ // the reader knows the blast radius is larger than what's shown.
883
+ if (closure.truncated) {
884
+ process.stdout.write(`\n ⓘ Showing ${liveDirect.length + liveTransitive.length} of ${payload.totalReached} dependents (capped at --limit ${limit}); raise --limit to see the full blast radius.\n`);
885
+ }
881
886
  if (fresh.field) {
882
887
  process.stdout.write(`\n ⚠ ${fresh.modified.length} dependent file(s) changed, ${fresh.deleted.length} deleted since indexing — run \`shrk graph index --changed\`.\n`);
883
888
  }
@@ -980,7 +985,11 @@ export async function runGraphCallers(args) {
980
985
  const ambiguityNote = alsoNamed > 0
981
986
  ? `${alsoNamed + 1} symbols named "${sym.label}"; showing callers of the one at ${sym.path ?? sym.id}${sym.line ? ':' + sym.line : ''}. Pass a symbol: id to disambiguate.`
982
987
  : undefined;
983
- const note = [ambiguityNote, langNote].filter(Boolean).join(' ');
988
+ // `total` is distinct caller FILES: at index time the graph collapses many
989
+ // call/reference sites in one file to a single edge. Say so, otherwise `total`
990
+ // reads as a raw invocation count and under-reports.
991
+ const dedupNote = 'total counts distinct caller files — multiple sites within one file collapse to a single entry.';
992
+ const note = [ambiguityNote, langNote, dedupNote].filter(Boolean).join(' ');
984
993
  const payload = {
985
994
  schema: 'sharkcraft.graph-callers/v1',
986
995
  symbol: nodeSummary(sym),
@@ -1 +1 @@
1
- {"version":3,"file":"ingest.command.d.ts","sourceRoot":"","sources":["../../src/commands/ingest.command.ts"],"names":[],"mappings":"AAyCA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAOhC,eAAO,MAAM,aAAa,EAAE,eA4B3B,CAAC;AAmZF,eAAO,MAAM,qBAAqB,EAAE,eAenC,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,eA8B9B,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,eAkC9B,CAAC"}
1
+ {"version":3,"file":"ingest.command.d.ts","sourceRoot":"","sources":["../../src/commands/ingest.command.ts"],"names":[],"mappings":"AA0CA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAOhC,eAAO,MAAM,aAAa,EAAE,eA4B3B,CAAC;AAmZF,eAAO,MAAM,qBAAqB,EAAE,eAkBnC,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,eA8B9B,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,eAkC9B,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs';
2
2
  import * as nodePath from 'node:path';
3
+ import { cliCommandNameSet } from "./command-catalog.js";
3
4
  import { applyIngestPlan, buildContradictionReport, buildGeneratedCodeReport, buildIngestAdoptionPlan, buildIngestApplyPlan, buildRepositoryKnowledgeModel, buildStabilityMap, inspectSharkcraft, IngestAdoptionStatus, IngestDepth, IngestSection, loadIngestApplyPlan, renderContradictionReportHtml, renderContradictionReportJson, renderContradictionReportMarkdown, renderContradictionReportText, renderGeneratedCodeReportJson, renderGeneratedCodeReportMarkdown, renderGeneratedCodeReportText, renderIngestAdoptionPatch, renderIngestAdoptionPlanMarkdown, renderIngestApplyReviewMarkdown, renderRepositoryKnowledgeModelHtml, renderRepositoryKnowledgeModelJson, renderRepositoryKnowledgeModelMarkdown, renderRepositoryKnowledgeModelText, renderStabilityMapJson, renderStabilityMapMarkdown, renderStabilityMapText, saveIngestApplyPlan, signIngestApplyPlan, writeIngestAdoption, writeIngestDrafts, } from '@shrkcrft/inspector';
4
5
  import { flagBool, flagString, flagList, resolveCwd, } from "../command-registry.js";
5
6
  import { asJson, header, kv } from "../output/format-output.js";
@@ -442,7 +443,10 @@ export const contradictionsCommand = {
442
443
  const cwd = resolveCwd(args);
443
444
  const format = parseFormat(flagString(args, 'format'));
444
445
  const inspection = await inspectSharkcraft({ cwd });
445
- const report = buildContradictionReport({ inspection });
446
+ const report = buildContradictionReport({
447
+ inspection,
448
+ cliCommandNames: cliCommandNameSet(),
449
+ });
446
450
  if (format === 'json')
447
451
  process.stdout.write(renderContradictionReportJson(report) + '\n');
448
452
  else if (format === 'markdown')
@@ -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,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"}
1
+ {"version":3,"file":"knowledge.command.d.ts","sourceRoot":"","sources":["../../src/commands/knowledge.command.ts"],"names":[],"mappings":"AAkBA,OAAO,EAML,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAoFhC,eAAO,MAAM,oBAAoB,EAAE,eA8ClC,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,7 +1,7 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import { join as nodePathJoin, resolve as nodePathResolve } from 'node:path';
3
3
  import { buildAnchorUpdatePlan, buildKnowledgeStaleReport, buildRenameFilePlan, buildRenameSymbolPlan, inspectSharkcraft, ReferenceCheckOutcome, resolveChangedFiles, } from '@shrkcrft/inspector';
4
- import { formatEntryCompact, formatEntryFull, searchKnowledge } from '@shrkcrft/knowledge';
4
+ import { formatEntryCompact, formatEntryFull, projectKnowledgeEntryForJson, searchKnowledge, } from '@shrkcrft/knowledge';
5
5
  import { flagBool, flagNumber, flagString, flagList, resolveCwd, } from "../command-registry.js";
6
6
  import { asJson, header } from "../output/format-output.js";
7
7
  import { maybeRunInWatchMode } from "../output/watch-loop.js";
@@ -84,7 +84,10 @@ export const knowledgeListCommand = {
84
84
  tags: e.tags,
85
85
  appliesWhen: e.appliesWhen,
86
86
  }))
87
- : entries.map((e) => ({ ...e }));
87
+ : // Project the declared IKnowledgeEntry fields by direct access rather
88
+ // than spreading (`{ ...e }`), which copies only own-enumerable props
89
+ // and would strip pack entries whose fields are getters/non-enumerable.
90
+ entries.map(projectKnowledgeEntryForJson);
88
91
  process.stdout.write(asJson(payload) + '\n');
89
92
  return 0;
90
93
  }
@@ -1 +1 @@
1
- {"version":3,"file":"plan-context.command.d.ts","sourceRoot":"","sources":["../../src/commands/plan-context.command.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAGhC;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,EAAE,eA2DhC,CAAC"}
1
+ {"version":3,"file":"plan-context.command.d.ts","sourceRoot":"","sources":["../../src/commands/plan-context.command.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAGhC;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,EAAE,eAsEhC,CAAC"}
@@ -11,7 +11,7 @@ import { asJson, header, kv } from "../output/format-output.js";
11
11
  */
12
12
  export const planContextCommand = {
13
13
  name: 'plan-context',
14
- description: 'Produce a deterministic, token-budgeted context pack (`sharkcraft.context-pack/v1`) for an AI agent: ranked files, applicable rules, likely tests, surfaced risks, do-not-touch zones.',
14
+ description: 'Produce a deterministic, token-budgeted context pack (`sharkcraft.context-pack/v1`) for an AI agent: ranked files, likely tests, and (shallow) surfaced risks / do-not-touch zones. Rules/paths/templates require the rule-graph bridge (`shrk rule-graph index`); the pack reports per-field `coverage` so empty sections are distinguishable from not-computed ones.',
15
15
  usage: 'shrk plan-context "<task>" [--budget N] [--max-files N] [--hint-file <path>] [--hint-package <prefix>] [--json]',
16
16
  async run(args) {
17
17
  const cwd = resolveCwd(args);
@@ -40,11 +40,19 @@ export const planContextCommand = {
40
40
  process.stdout.write(header(`Context pack: ${task}`));
41
41
  process.stdout.write(kv('intent', pack.intent) + '\n');
42
42
  process.stdout.write(kv('files', String(pack.files.length)) + '\n');
43
- process.stdout.write(kv('rules', String(pack.rules.length)) + '\n');
44
- process.stdout.write(kv('paths', String(pack.paths.length)) + '\n');
45
- process.stdout.write(kv('templates', String(pack.templates.length)) + '\n');
43
+ // Distinguish "computed, none apply" from "not computed". When the bridge
44
+ // is missing, rules/paths/templates are omitted, not empty — say so rather
45
+ // than printing a misleading `0`.
46
+ if (pack.coverage.rulesComputed) {
47
+ process.stdout.write(kv('rules', String(pack.rules.length)) + '\n');
48
+ process.stdout.write(kv('paths', String(pack.paths.length)) + '\n');
49
+ process.stdout.write(kv('templates', String(pack.templates.length)) + '\n');
50
+ }
51
+ else {
52
+ process.stdout.write(kv('rules/paths/templates', 'not computed (run `shrk rule-graph index`)') + '\n');
53
+ }
46
54
  process.stdout.write(kv('tests', String(pack.tests.length)) + '\n');
47
- process.stdout.write(kv('risks', String(pack.risks.length)) + '\n');
55
+ process.stdout.write(kv('risks', pack.coverage.risksComputed ? String(pack.risks.length) : 'not computed') + '\n');
48
56
  process.stdout.write(kv('budget', `${pack.budget.used}/${pack.budget.requested} tokens` + (pack.budget.truncated ? ' (truncated)' : '')) + '\n');
49
57
  if (pack.files.length > 0) {
50
58
  process.stdout.write('\nTop files:\n');
@@ -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,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"}
1
+ {"version":3,"file":"recommend.command.d.ts","sourceRoot":"","sources":["../../src/commands/recommend.command.ts"],"names":[],"mappings":"AAUA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAShC,eAAO,MAAM,gBAAgB,EAAE,eAkP9B,CAAC;AA0HF;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAY3D;AAcD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAcxD"}
@@ -1,5 +1,5 @@
1
1
  import { readFileSync } from 'node:fs';
2
- import { buildUniversalSearch, entrypointBanner, explainTaskRouting, inspectSharkcraft, recommendCommands, renderUncertaintyReportText, } from '@shrkcrft/inspector';
2
+ import { buildUniversalSearch, entrypointBanner, explainTaskRouting, inspectSharkcraft, rankAll, recommendCommands, renderUncertaintyReportText, } from '@shrkcrft/inspector';
3
3
  import { flagBool, flagNumber, flagString, resolveCwd, } from "../command-registry.js";
4
4
  import { asJson } from "../output/format-output.js";
5
5
  import { loadSurfaceContext } from "../surface/load-surface-context.js";
@@ -65,6 +65,10 @@ export const recommendCommand = {
65
65
  const machineJson = flagBool(args, 'json') || flagBool(args, 'machine-json');
66
66
  let routingMatches = [];
67
67
  let searchReport = null;
68
+ // Reconcile with `brief`/`task`: those route through the SAME shared ranker
69
+ // (`rankAll`). Consult it here so `recommend` never claims a "coverage gap"
70
+ // for a task the rest of the engine confidently matches to a template/pipeline.
71
+ let ranking = null;
68
72
  if (query.length > 0) {
69
73
  try {
70
74
  routingMatches = await explainTaskRouting(inspection, query);
@@ -78,7 +82,17 @@ export const recommendCommand = {
78
82
  catch {
79
83
  searchReport = null;
80
84
  }
85
+ try {
86
+ ranking = rankAll(inspection, query);
87
+ }
88
+ catch {
89
+ ranking = null;
90
+ }
81
91
  }
92
+ const topTemplate = ranking?.templates[0];
93
+ const topPipeline = ranking?.pipelines[0];
94
+ const engineHasMatch = (topTemplate?.score ?? 0) >= TEMPLATE_MATCH_THRESHOLD ||
95
+ (topPipeline?.score ?? 0) >= PIPELINE_MATCH_THRESHOLD;
82
96
  // R1 — promote a strongly-matched task-routing hint's recommends.commands
83
97
  // into the HEADLINE for create/build intents. Routing hints are scored by
84
98
  // explainTaskRouting but were previously only shown under --verbose, so the
@@ -115,6 +129,12 @@ export const recommendCommand = {
115
129
  routingMatches,
116
130
  search: searchReport,
117
131
  gated,
132
+ rankerMatch: ranking
133
+ ? {
134
+ topTemplate: topTemplate ? { id: topTemplate.item.id, score: topTemplate.score } : null,
135
+ topPipeline: topPipeline ? { id: topPipeline.item.id, score: topPipeline.score } : null,
136
+ }
137
+ : null,
118
138
  }) + '\n');
119
139
  return 0;
120
140
  }
@@ -158,16 +178,29 @@ export const recommendCommand = {
158
178
  }
159
179
  }
160
180
  }
161
- // Coverage gap — explicit if recommendations look thin and no routing hint fired.
162
- if (report.recommendations.length <= 1 &&
163
- routingMatches.length === 0 &&
164
- query.length > 0) {
181
+ // Coverage gap — explicit if recommendations look thin AND no routing hint
182
+ // fired AND the shared ranker (the engine `brief`/`task` use) also found no
183
+ // template/pipeline. The last clause stops `recommend` from contradicting
184
+ // `task`/`brief`, which would confidently route the same task.
185
+ const thinResult = report.recommendations.length <= 1 && routingMatches.length === 0 && query.length > 0;
186
+ if (thinResult && !engineHasMatch) {
165
187
  process.stdout.write(`\n⚠ Coverage gap — no recipe, no routing hint, and no helper/template matched "${query}".\n` +
166
188
  ` Suggest:\n` +
167
189
  ` shrk coverage scaffolds --task "${query}"\n` +
168
190
  ` shrk feedback actions\n` +
169
191
  ` (or contribute a pack template / helper / routing hint)\n`);
170
192
  }
193
+ else if (thinResult && engineHasMatch && !actionsOnly) {
194
+ // The recipe/routing surface was thin, but the shared ranker DID match —
195
+ // surface that concrete next step instead of a misleading gap.
196
+ process.stdout.write('\nEngine match (shared ranker — same as `shrk task` / `shrk brief`):\n');
197
+ if (topTemplate && topTemplate.score >= TEMPLATE_MATCH_THRESHOLD) {
198
+ process.stdout.write(` $ shrk gen ${topTemplate.item.id} <name> --dry-run [writes-source] — template "${topTemplate.item.name}" matched (score ${topTemplate.score}).\n`);
199
+ }
200
+ if (topPipeline && topPipeline.score >= PIPELINE_MATCH_THRESHOLD) {
201
+ process.stdout.write(` Pipeline: ${topPipeline.item.id} — run \`shrk task "${query}"\` for the full packet.\n`);
202
+ }
203
+ }
171
204
  if (gated.length > 0 && !actionsOnly) {
172
205
  process.stdout.write(`\nGated (experimental, not enabled in this repo):\n`);
173
206
  for (const g of gated.slice(0, 3)) {
@@ -287,6 +320,15 @@ const PLANNING_VERBS = new Set([
287
320
  * keyword/regex hits — a real match, not a single weak keyword.
288
321
  */
289
322
  const ROUTING_HINT_PROMOTE_THRESHOLD = 3;
323
+ /**
324
+ * Minimum `rankAll` score for a template / pipeline to count as a real engine
325
+ * match — used only to suppress a false "coverage gap" verdict (and surface the
326
+ * match) when the recipe/routing surface is thin but the shared ranker, which
327
+ * `brief`/`task` also use, found project coverage. Conservative: a single weak
328
+ * token hit scores below this.
329
+ */
330
+ const TEMPLATE_MATCH_THRESHOLD = 3;
331
+ const PIPELINE_MATCH_THRESHOLD = 3;
290
332
  const CREATE_BUILD_VERBS = new Set([
291
333
  'create', 'build', 'add', 'generate', 'scaffold', 'implement', 'make', 'new', 'introduce', 'write',
292
334
  ]);
@@ -1 +1 @@
1
- {"version":3,"file":"task-context.command.d.ts","sourceRoot":"","sources":["../../src/commands/task-context.command.ts"],"names":[],"mappings":"AAsBA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAahC,eAAO,MAAM,qBAAqB,EAAE,eAyEnC,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,eAmEnC,CAAC;AAIF,eAAO,MAAM,mBAAmB,EAAE,eA2DjC,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,eAqBnC,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,eAoBlC,CAAC"}
1
+ {"version":3,"file":"task-context.command.d.ts","sourceRoot":"","sources":["../../src/commands/task-context.command.ts"],"names":[],"mappings":"AAuBA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAahC,eAAO,MAAM,qBAAqB,EAAE,eAyEnC,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,eAsEnC,CAAC;AAIF,eAAO,MAAM,mBAAmB,EAAE,eA2DjC,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,eAqBnC,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,eAoBlC,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
2
2
  import * as nodePath from 'node:path';
3
+ import { cliCommandNameSet } from "./command-catalog.js";
3
4
  import { analyzeImportGraph, buildAgentBrief, buildContradictionReport, buildGeneratedCodeReport, buildRepositoryKnowledgeModel, buildTaskPacket, buildTaskRiskReport, classifyChangeIntent, detectLanguageProfiles, getChangedFiles, getStatusSummary, inspectSharkcraft, isGitRepo, listConstructs, loadRepositoryMemory, } from '@shrkcrft/inspector';
4
5
  import { flagBool, flagString, flagList, resolveCwd, } from "../command-registry.js";
5
6
  import { asJson, header, kv } from "../output/format-output.js";
@@ -102,7 +103,10 @@ export const validateChangeCommand = {
102
103
  }
103
104
  // Fallback path through getStatusSummary not necessary — getChangedFiles already covers it.
104
105
  void getStatusSummary;
105
- const contradictions = buildContradictionReport({ inspection });
106
+ const contradictions = buildContradictionReport({
107
+ inspection,
108
+ cliCommandNames: cliCommandNameSet(),
109
+ });
106
110
  const generated = buildGeneratedCodeReport({ inspection });
107
111
  const generatedPaths = new Set(generated.generatedFiles.map((f) => f.path));
108
112
  const boundaryHits = changed.filter((f) => looksLikeBoundaryViolation(f));
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";AAEA,OAAO,EACL,eAAe,EAKhB,MAAM,uBAAuB,CAAC;AAwX/B,wBAAgB,aAAa,IAAI,eAAe,CA4X/C;AAED,wBAAsB,MAAM,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA2BrE;AAsHD;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CA4CxE"}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";AAEA,OAAO,EACL,eAAe,EAKhB,MAAM,uBAAuB,CAAC;AAwX/B,wBAAgB,aAAa,IAAI,eAAe,CA4X/C;AAED,wBAAsB,MAAM,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA2BrE;AAsHD;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAoDxE"}
package/dist/main.js CHANGED
@@ -644,7 +644,15 @@ async function runCliInner(argv) {
644
644
  * Exported for tests.
645
645
  */
646
646
  export function looksLikeFreeFormTask(tokens) {
647
- const clean = tokens.filter((t) => t.length > 0 && !t.startsWith('-'));
647
+ // Flatten on internal whitespace so the canonical *quoted* form
648
+ // (`shrk "refactor the auth module"`) — which the shell delivers as ONE argv
649
+ // element — is counted by word, exactly like the unquoted multi-token form.
650
+ // Without this, the documented quoted form was a single token (length 1) and
651
+ // fell through to the did-you-mean matcher instead of routing to `recommend`.
652
+ const clean = tokens
653
+ .filter((t) => t.length > 0 && !t.startsWith('-'))
654
+ .flatMap((t) => t.trim().split(/\s+/))
655
+ .filter((w) => w.length > 0);
648
656
  if (clean.length < 2)
649
657
  return false;
650
658
  // 3+ tokens is almost always a sentence, not a command — `shrk` only has
@@ -1 +1 @@
1
- {"version":3,"file":"profiles.d.ts","sourceRoot":"","sources":["../../src/surface/profiles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,eAAe;IAC9B,kEAAkE;IAClE,EAAE,EAAE,MAAM,CAAC;IACX,6DAA6D;IAC7D,WAAW,EAAE,MAAM,CAAC;IACpB,0DAA0D;IAC1D,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B;;;;OAIG;IACH,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,uEAAuE;IACvE,MAAM,EAAE,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;IACrC,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAgID,oCAAoC;AACpC,eAAO,MAAM,gBAAgB,EAAE,SAAS,eAAe,EAOrD,CAAC;AAEH,gEAAgE;AAChE,wBAAgB,aAAa,CAC3B,YAAY,GAAE,SAAS,eAAe,EAAO,GAC5C,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAQ9B;AAED,6DAA6D;AAC7D,wBAAgB,UAAU,CACxB,EAAE,EAAE,MAAM,EACV,YAAY,GAAE,SAAS,eAAe,EAAO,GAC5C,eAAe,GAAG,SAAS,CAE7B"}
1
+ {"version":3,"file":"profiles.d.ts","sourceRoot":"","sources":["../../src/surface/profiles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,MAAM,WAAW,eAAe;IAC9B,kEAAkE;IAClE,EAAE,EAAE,MAAM,CAAC;IACX,6DAA6D;IAC7D,WAAW,EAAE,MAAM,CAAC;IACpB,0DAA0D;IAC1D,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B;;;;OAIG;IACH,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,uEAAuE;IACvE,MAAM,EAAE,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;IACrC,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAmLD,oCAAoC;AACpC,eAAO,MAAM,gBAAgB,EAAE,SAAS,eAAe,EAOrD,CAAC;AAEH,gEAAgE;AAChE,wBAAgB,aAAa,CAC3B,YAAY,GAAE,SAAS,eAAe,EAAO,GAC5C,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAQ9B;AAED,6DAA6D;AAC7D,wBAAgB,UAAU,CACxB,EAAE,EAAE,MAAM,EACV,YAAY,GAAE,SAAS,eAAe,EAAO,GAC5C,eAAe,GAAG,SAAS,CAE7B"}
@@ -10,6 +10,7 @@
10
10
  * Profiles are PURE DATA — no logic, no AI. Adding a profile is just
11
11
  * adding an entry here (or in a pack manifest).
12
12
  */
13
+ import { COMMAND_CATALOG } from "../commands/command-catalog.js";
13
14
  /**
14
15
  * `developer` profile. Default for monorepo / app-with-libs shapes.
15
16
  * Hides nothing — pack authors / power users see the full extended
@@ -108,20 +109,64 @@ const CI_PROFILE = {
108
109
  ],
109
110
  };
110
111
  /**
111
- * `agent` profile. Optimized for MCP / agent use. Hides interactive
112
- * verbs; keeps JSON / read-only verbs visible.
112
+ * Generic machinery categories that are noise for an inline coding agent:
113
+ * CI / release gates, pack maintenance & signing, bundle replay, provenance,
114
+ * report generation, schema / governance / ingestion / lifecycle / polyglot /
115
+ * integration tooling. Commands stay fully CALLABLE — the agent profile just
116
+ * filters them from the agent-facing listing.
117
+ */
118
+ const AGENT_HIDDEN_CATEGORIES = new Set([
119
+ 'release',
120
+ 'ci',
121
+ 'bundles',
122
+ 'bundle',
123
+ 'packs',
124
+ 'pack-author',
125
+ 'provenance',
126
+ 'reports',
127
+ 'schemas',
128
+ 'governance',
129
+ 'ingestion',
130
+ 'lifecycle',
131
+ 'export',
132
+ 'polyglot',
133
+ 'integrations',
134
+ ]);
135
+ /** Read-only discovery verbs kept visible even though their category is hidden,
136
+ * so an agent can still inspect packs. */
137
+ const AGENT_KEEP_VISIBLE = new Set(['packs list', 'packs doctor']);
138
+ /** Interactive / write-source verbs hidden from the agent surface. */
139
+ const AGENT_INTERACTIVE_HIDDEN = [
140
+ 'dev',
141
+ 'dev start',
142
+ 'dev status',
143
+ 'dev report',
144
+ 'orchestrate',
145
+ 'ask',
146
+ ];
147
+ /**
148
+ * Derive the hidden command list from machinery categories MECHANICALLY from the
149
+ * catalogue, so it never drifts as commands are added/removed.
150
+ */
151
+ function deriveHiddenByCategory(hiddenCats, keep) {
152
+ return COMMAND_CATALOG.filter((e) => hiddenCats.has(e.category) && !keep.has(e.command))
153
+ .map((e) => e.command)
154
+ .sort();
155
+ }
156
+ /**
157
+ * `agent` profile. Optimized for inline coding-agent / MCP use. Hides
158
+ * interactive verbs AND CI/release/pack-maintenance machinery; favors JSON-pipe
159
+ * read surfaces. Every hidden command remains callable (hide != disable).
113
160
  */
114
161
  const AGENT_PROFILE = {
115
162
  id: 'agent',
116
- description: 'Agent / MCP-friendly default. Hides interactive verbs; favors JSON-pipe surfaces.',
163
+ description: 'Agent / MCP-friendly default. Hides interactive verbs + CI/release/pack-maintenance machinery from the listing (commands stay callable); favors JSON-pipe read surfaces.',
117
164
  source: 'builtin',
118
165
  hidden: [
119
- 'dev',
120
- 'dev start',
121
- 'dev status',
122
- 'dev report',
123
- 'orchestrate',
124
- 'ask',
166
+ ...new Set([
167
+ ...AGENT_INTERACTIVE_HIDDEN,
168
+ ...deriveHiddenByCategory(AGENT_HIDDEN_CATEGORIES, AGENT_KEEP_VISIBLE),
169
+ ]),
125
170
  ],
126
171
  };
127
172
  /** Catalog of built-in profiles. */
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@shrkcrft/cli",
3
- "version": "0.1.0-alpha.19",
3
+ "version": "0.1.0-alpha.20",
4
4
  "description": "SharkCraft CLI (`shrk`): structured project intelligence for AI coding agents.",
5
5
  "license": "MIT",
6
6
  "author": "SharkCraft contributors",
7
7
  "type": "module",
8
8
  "main": "./dist/index.js",
9
- "types": "./dist/index.d.d.ts",
9
+ "types": "./dist/index.d.ts",
10
10
  "exports": {
11
11
  ".": {
12
12
  "types": "./dist/index.d.ts",
@@ -47,38 +47,38 @@
47
47
  "typecheck": "tsc --noEmit -p tsconfig.json"
48
48
  },
49
49
  "dependencies": {
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",
50
+ "@shrkcrft/core": "^0.1.0-alpha.20",
51
+ "@shrkcrft/compress": "^0.1.0-alpha.20",
52
+ "@shrkcrft/config": "^0.1.0-alpha.20",
53
+ "@shrkcrft/workspace": "^0.1.0-alpha.20",
54
+ "@shrkcrft/knowledge": "^0.1.0-alpha.20",
55
+ "@shrkcrft/context": "^0.1.0-alpha.20",
56
+ "@shrkcrft/rules": "^0.1.0-alpha.20",
57
+ "@shrkcrft/paths": "^0.1.0-alpha.20",
58
+ "@shrkcrft/templates": "^0.1.0-alpha.20",
59
+ "@shrkcrft/plugin-api": "^0.1.0-alpha.20",
60
+ "@shrkcrft/dashboard": "^0.1.0-alpha.20",
61
+ "@shrkcrft/dashboard-api": "^0.1.0-alpha.20",
62
+ "@shrkcrft/pipelines": "^0.1.0-alpha.20",
63
+ "@shrkcrft/presets": "^0.1.0-alpha.20",
64
+ "@shrkcrft/boundaries": "^0.1.0-alpha.20",
65
+ "@shrkcrft/graph": "^0.1.0-alpha.20",
66
+ "@shrkcrft/rule-graph": "^0.1.0-alpha.20",
67
+ "@shrkcrft/structural-search": "^0.1.0-alpha.20",
68
+ "@shrkcrft/impact-engine": "^0.1.0-alpha.20",
69
+ "@shrkcrft/context-planner": "^0.1.0-alpha.20",
70
+ "@shrkcrft/architecture-guard": "^0.1.0-alpha.20",
71
+ "@shrkcrft/framework-scanners": "^0.1.0-alpha.20",
72
+ "@shrkcrft/api-surface-diff": "^0.1.0-alpha.20",
73
+ "@shrkcrft/quality-gates": "^0.1.0-alpha.20",
74
+ "@shrkcrft/migrate": "^0.1.0-alpha.20",
75
+ "@shrkcrft/generator": "^0.1.0-alpha.20",
76
+ "@shrkcrft/importer": "^0.1.0-alpha.20",
77
+ "@shrkcrft/inspector": "^0.1.0-alpha.20",
78
+ "@shrkcrft/ai": "^0.1.0-alpha.20",
79
+ "@shrkcrft/embeddings": "^0.1.0-alpha.20",
80
+ "@shrkcrft/shared": "^0.1.0-alpha.20",
81
+ "@shrkcrft/mcp-server": "^0.1.0-alpha.20",
82
82
  "@huggingface/transformers": "^3.7.5"
83
83
  },
84
84
  "publishConfig": {