@shrkcrft/mcp-server 0.1.0-alpha.11 → 0.1.0-alpha.13

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":"all-tools.d.ts","sourceRoot":"","sources":["../../src/tools/all-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAuUpE,eAAO,MAAM,SAAS,EAAE,SAAS,eAAe,EAgR9C,CAAC"}
1
+ {"version":3,"file":"all-tools.d.ts","sourceRoot":"","sources":["../../src/tools/all-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AA4UpE,eAAO,MAAM,SAAS,EAAE,SAAS,eAAe,EAqR9C,CAAC"}
@@ -96,6 +96,11 @@ import { getImportGraphAnalysisTool } from "./import-graph-analysis.tool.js";
96
96
  import { getDashboardSummaryTool } from "./dashboard-summary.tool.js";
97
97
  import { searchAllTool } from "./search.tool.js";
98
98
  import { createAgentBriefTool } from "./agent-brief.tool.js";
99
+ import { smartContextBundleTool } from "./smart-context-bundle.tool.js";
100
+ import { smartContextFeedTool } from "./smart-context-feed.tool.js";
101
+ import { codeFindUsagesTool } from "./code-find-usages.tool.js";
102
+ import { planQualityReviewTool } from "./plan-quality-review.tool.js";
103
+ import { depsAuditTool } from "./deps-audit.tool.js";
99
104
  import { getConstructApiTool, getConstructTool, listConstructFacetsTool, listConstructsTool, traceConstructTool, } from "./constructs.tool.js";
100
105
  import { getPlaybookTool, listPlaybooksTool, recommendPlaybooksTool, } from "./playbooks.tool.js";
101
106
  import { replayBundleApplyTool } from "./bundle-replay.tool.js";
@@ -287,6 +292,11 @@ export const ALL_TOOLS = Object.freeze([
287
292
  getDashboardSummaryTool,
288
293
  searchAllTool,
289
294
  createAgentBriefTool,
295
+ smartContextBundleTool,
296
+ smartContextFeedTool,
297
+ codeFindUsagesTool,
298
+ planQualityReviewTool,
299
+ depsAuditTool,
290
300
  listConstructsTool,
291
301
  getConstructTool,
292
302
  traceConstructTool,
@@ -0,0 +1,16 @@
1
+ import type { IToolDefinition } from '../server/tool-definition.js';
2
+ /**
3
+ * `code_find_usages` — structured usage finder backed by the
4
+ * SharkCraft graph (file + symbol nodes + import/declare edges).
5
+ *
6
+ * Unlike grep, this distinguishes:
7
+ * - the symbol's definition site
8
+ * - which files import the declaring file (runtime + type-only
9
+ * can't be split without TS-level checks, so we mark them all as
10
+ * `import-of-declaring-file` for honesty)
11
+ * - which other symbols neighbour the target in the graph
12
+ *
13
+ * Read-only. Skips when the graph is missing (returns a hint).
14
+ */
15
+ export declare const codeFindUsagesTool: IToolDefinition;
16
+ //# sourceMappingURL=code-find-usages.tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-find-usages.tool.d.ts","sourceRoot":"","sources":["../../src/tools/code-find-usages.tool.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAEpE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,kBAAkB,EAAE,eA+FhC,CAAC"}
@@ -0,0 +1,137 @@
1
+ import { existsSync } from 'node:fs';
2
+ import * as nodePath from 'node:path';
3
+ import { EdgeKind, GraphQueryApi, GraphStore, NodeKind } from '@shrkcrft/graph';
4
+ /**
5
+ * `code_find_usages` — structured usage finder backed by the
6
+ * SharkCraft graph (file + symbol nodes + import/declare edges).
7
+ *
8
+ * Unlike grep, this distinguishes:
9
+ * - the symbol's definition site
10
+ * - which files import the declaring file (runtime + type-only
11
+ * can't be split without TS-level checks, so we mark them all as
12
+ * `import-of-declaring-file` for honesty)
13
+ * - which other symbols neighbour the target in the graph
14
+ *
15
+ * Read-only. Skips when the graph is missing (returns a hint).
16
+ */
17
+ export const codeFindUsagesTool = {
18
+ name: 'code_find_usages',
19
+ description: 'Find structured usages of a symbol via the SharkCraft graph (file + symbol nodes). Read-only. Distinguishes definition, import-of-declaring-file, and neighbouring symbols.',
20
+ inputSchema: {
21
+ type: 'object',
22
+ properties: {
23
+ symbolName: { type: 'string' },
24
+ kindHint: { type: 'string' },
25
+ maxResults: { type: 'number' },
26
+ },
27
+ required: ['symbolName'],
28
+ additionalProperties: false,
29
+ },
30
+ async handler(input, ctx) {
31
+ const symbolName = typeof input['symbolName'] === 'string' ? input['symbolName'].trim() : '';
32
+ if (symbolName.length === 0) {
33
+ return { data: { error: 'symbolName is required' } };
34
+ }
35
+ const maxResults = clamp(typeof input['maxResults'] === 'number' ? input['maxResults'] : 25, 1, 200);
36
+ const store = new GraphStore(ctx.cwd);
37
+ if (!store.exists()) {
38
+ return {
39
+ data: {
40
+ error: 'no-graph',
41
+ message: 'The SharkCraft graph index has not been built yet. Build it with `shrk graph build`.',
42
+ nextCommand: 'shrk graph build',
43
+ },
44
+ };
45
+ }
46
+ const api = GraphQueryApi.fromStore(ctx.cwd);
47
+ const matches = api.findSymbol(symbolName, { exact: true, limit: maxResults });
48
+ if (matches.length === 0) {
49
+ return {
50
+ data: {
51
+ symbol: { name: symbolName, kind: 'unknown' },
52
+ definitions: [],
53
+ importersOfDeclaringFile: [],
54
+ neighbouringSymbols: [],
55
+ totalSymbolMatches: 0,
56
+ note: 'No exact symbol match in the graph. Try `shrk knowledge search` for fuzzy lookup.',
57
+ },
58
+ };
59
+ }
60
+ const definitions = [];
61
+ const importerSet = new Map();
62
+ const neighbours = [];
63
+ for (const sym of matches) {
64
+ const declaringFile = declaringFileOf(api, sym.id);
65
+ definitions.push({
66
+ symbolId: sym.id,
67
+ file: declaringFile?.path ?? null,
68
+ kind: String(sym.label && sym.label.length > 0 ? sym.label : sym.kind),
69
+ });
70
+ if (declaringFile) {
71
+ for (const importer of api.importersOf(declaringFile.id)) {
72
+ if (!importer.path)
73
+ continue;
74
+ const exists = pathExists(ctx.cwd, importer.path);
75
+ if (!exists)
76
+ continue;
77
+ const key = `${importer.path}::${declaringFile.path}`;
78
+ if (!importerSet.has(key)) {
79
+ importerSet.set(key, {
80
+ file: importer.path,
81
+ via: declaringFile.path ?? '',
82
+ });
83
+ }
84
+ }
85
+ }
86
+ // Sibling symbols declared by the same file.
87
+ if (declaringFile) {
88
+ for (const sib of api.symbolsIn(declaringFile.id).slice(0, 6)) {
89
+ if (sib.id === sym.id)
90
+ continue;
91
+ neighbours.push({
92
+ name: sib.label,
93
+ kind: String(sib.kind),
94
+ file: declaringFile.path ?? null,
95
+ });
96
+ }
97
+ }
98
+ }
99
+ return {
100
+ data: {
101
+ symbol: { name: symbolName, kind: matches[0]?.kind ?? 'unknown' },
102
+ definitions,
103
+ importersOfDeclaringFile: [...importerSet.values()],
104
+ neighbouringSymbols: neighbours.slice(0, 12),
105
+ totalSymbolMatches: matches.length,
106
+ note: 'importersOfDeclaringFile = files that import the declaring file. This is a structural signal — it may include type-only imports and unused references. Pair with `shrk impact` for a tighter blast radius.',
107
+ },
108
+ };
109
+ },
110
+ };
111
+ function declaringFileOf(api, symbolId) {
112
+ const n = api.neighbours(symbolId);
113
+ if (!n)
114
+ return undefined;
115
+ for (const incoming of n.in) {
116
+ if (incoming.edge.kind !== EdgeKind.DeclaresSymbol)
117
+ continue;
118
+ if ('resolved' in incoming.source)
119
+ continue;
120
+ if (incoming.source.kind === NodeKind.File)
121
+ return incoming.source;
122
+ }
123
+ return undefined;
124
+ }
125
+ function pathExists(cwd, path) {
126
+ try {
127
+ return existsSync(nodePath.isAbsolute(path) ? path : nodePath.join(cwd, path));
128
+ }
129
+ catch {
130
+ return false;
131
+ }
132
+ }
133
+ function clamp(n, lo, hi) {
134
+ if (Number.isNaN(n))
135
+ return lo;
136
+ return Math.max(lo, Math.min(hi, Math.floor(n)));
137
+ }
@@ -0,0 +1,10 @@
1
+ import type { IToolDefinition } from '../server/tool-definition.js';
2
+ /**
3
+ * `deps_audit` — MCP wrapper around `shrk deps-audit`. Pure read-only.
4
+ * Returns the same shape the CLI's `--json` mode emits.
5
+ *
6
+ * NOTE: we re-implement the body here rather than shell out so MCP
7
+ * tools never spawn subprocesses. Identical logic, identical output.
8
+ */
9
+ export declare const depsAuditTool: IToolDefinition;
10
+ //# sourceMappingURL=deps-audit.tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deps-audit.tool.d.ts","sourceRoot":"","sources":["../../src/tools/deps-audit.tool.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAEpE;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,EAAE,eAoC3B,CAAC"}
@@ -0,0 +1,217 @@
1
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
2
+ import * as nodePath from 'node:path';
3
+ import { GraphQueryApi, GraphStore, NodeKind } from '@shrkcrft/graph';
4
+ /**
5
+ * `deps_audit` — MCP wrapper around `shrk deps-audit`. Pure read-only.
6
+ * Returns the same shape the CLI's `--json` mode emits.
7
+ *
8
+ * NOTE: we re-implement the body here rather than shell out so MCP
9
+ * tools never spawn subprocesses. Identical logic, identical output.
10
+ */
11
+ export const depsAuditTool = {
12
+ name: 'deps_audit',
13
+ description: 'Per-package audit of declared dependencies (package.json) vs actually-imported specifiers (graph). Reports missing + unused deps. Read-only.',
14
+ inputSchema: {
15
+ type: 'object',
16
+ properties: {
17
+ package: { type: 'string' },
18
+ },
19
+ additionalProperties: false,
20
+ },
21
+ async handler(input, ctx) {
22
+ const onlyPackage = typeof input['package'] === 'string' ? input['package'] : null;
23
+ const store = new GraphStore(ctx.cwd);
24
+ if (!store.exists()) {
25
+ return {
26
+ data: {
27
+ error: 'no-graph',
28
+ message: 'The SharkCraft graph index is required for deps-audit.',
29
+ nextCommand: 'shrk graph build',
30
+ },
31
+ };
32
+ }
33
+ const api = GraphQueryApi.fromStore(ctx.cwd);
34
+ const packages = listWorkspacePackages(ctx.cwd, onlyPackage);
35
+ const reports = packages.map((p) => buildPackageReport(api, ctx.cwd, p));
36
+ const totals = reports.reduce((acc, r) => {
37
+ acc.missing += r.missingDeps.length;
38
+ acc.unused += r.unusedDeps.length;
39
+ return acc;
40
+ }, { missing: 0, unused: 0 });
41
+ return { data: { totals, packages: reports } };
42
+ },
43
+ };
44
+ function listWorkspacePackages(cwd, onlyName) {
45
+ const roots = ['packages', 'libs', 'apps'].map((r) => nodePath.join(cwd, r)).filter((d) => existsSync(d));
46
+ const out = [];
47
+ for (const root of roots) {
48
+ let entries;
49
+ try {
50
+ entries = readdirSync(root);
51
+ }
52
+ catch {
53
+ continue;
54
+ }
55
+ for (const entry of entries) {
56
+ const dir = nodePath.join(root, entry);
57
+ let stat;
58
+ try {
59
+ stat = statSync(dir);
60
+ }
61
+ catch {
62
+ continue;
63
+ }
64
+ if (!stat.isDirectory())
65
+ continue;
66
+ const pkgJsonPath = nodePath.join(dir, 'package.json');
67
+ if (!existsSync(pkgJsonPath))
68
+ continue;
69
+ let pkgJson;
70
+ try {
71
+ pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf8'));
72
+ }
73
+ catch {
74
+ continue;
75
+ }
76
+ if (!pkgJson.name)
77
+ continue;
78
+ if (onlyName !== null && pkgJson.name !== onlyName)
79
+ continue;
80
+ out.push({ name: pkgJson.name, dir, pkgJsonPath });
81
+ }
82
+ }
83
+ return out;
84
+ }
85
+ function buildPackageReport(api, cwd, pkg) {
86
+ const declared = readDeclaredDeps(pkg.pkgJsonPath);
87
+ const importedSpecifiers = collectImportedSpecifiersForPackage(api, cwd, pkg.dir);
88
+ const importerCounts = new Map();
89
+ for (const s of importedSpecifiers)
90
+ importerCounts.set(s, (importerCounts.get(s) ?? 0) + 1);
91
+ const distinctImported = new Set(importedSpecifiers);
92
+ const declaredAll = new Map();
93
+ for (const [section, map] of [
94
+ ['dependencies', declared.dependencies],
95
+ ['devDependencies', declared.devDependencies],
96
+ ['peerDependencies', declared.peerDependencies],
97
+ ['optionalDependencies', declared.optionalDependencies],
98
+ ]) {
99
+ for (const k of Object.keys(map))
100
+ declaredAll.set(k, section);
101
+ }
102
+ const missingDeps = [];
103
+ for (const spec of distinctImported) {
104
+ if (declaredAll.has(spec))
105
+ continue;
106
+ if (spec === pkg.name)
107
+ continue;
108
+ missingDeps.push({ specifier: spec, importedFromCount: importerCounts.get(spec) ?? 0 });
109
+ }
110
+ missingDeps.sort((a, b) => b.importedFromCount - a.importedFromCount);
111
+ const unusedDeps = [];
112
+ for (const [spec, section] of declaredAll.entries()) {
113
+ if (distinctImported.has(spec))
114
+ continue;
115
+ unusedDeps.push({ specifier: spec, section });
116
+ }
117
+ unusedDeps.sort((a, b) => a.specifier.localeCompare(b.specifier));
118
+ return {
119
+ packageName: pkg.name,
120
+ packageDir: nodePath.relative(cwd, pkg.dir) || '.',
121
+ importedSpecifiers: [...distinctImported],
122
+ missingDeps,
123
+ unusedDeps,
124
+ };
125
+ }
126
+ function readDeclaredDeps(pkgJsonPath) {
127
+ try {
128
+ const body = JSON.parse(readFileSync(pkgJsonPath, 'utf8'));
129
+ return {
130
+ dependencies: asStringMap(body['dependencies']),
131
+ devDependencies: asStringMap(body['devDependencies']),
132
+ peerDependencies: asStringMap(body['peerDependencies']),
133
+ optionalDependencies: asStringMap(body['optionalDependencies']),
134
+ };
135
+ }
136
+ catch {
137
+ return { dependencies: {}, devDependencies: {}, peerDependencies: {}, optionalDependencies: {} };
138
+ }
139
+ }
140
+ function asStringMap(value) {
141
+ if (!value || typeof value !== 'object' || Array.isArray(value))
142
+ return {};
143
+ const out = {};
144
+ for (const [k, v] of Object.entries(value)) {
145
+ if (typeof v === 'string')
146
+ out[k] = v;
147
+ }
148
+ return out;
149
+ }
150
+ function collectImportedSpecifiersForPackage(api, cwd, packageDir) {
151
+ const out = [];
152
+ const relDir = nodePath.relative(cwd, packageDir).replace(/\\/g, '/');
153
+ for (const file of api.allFiles()) {
154
+ if (file.kind !== NodeKind.File)
155
+ continue;
156
+ const p = file.path ?? '';
157
+ if (!p.startsWith(relDir + '/src/') && !p.startsWith(relDir + '/'))
158
+ continue;
159
+ const abs = nodePath.isAbsolute(p) ? p : nodePath.join(cwd, p);
160
+ if (!existsSync(abs))
161
+ continue;
162
+ let body;
163
+ try {
164
+ body = readFileSync(abs, 'utf8');
165
+ }
166
+ catch {
167
+ continue;
168
+ }
169
+ for (const spec of extractRootSpecifiers(body)) {
170
+ if (isBuiltinModule(spec))
171
+ continue;
172
+ if (spec.startsWith('.') || spec.startsWith('/'))
173
+ continue;
174
+ out.push(rootOfSpecifier(spec));
175
+ }
176
+ }
177
+ return out;
178
+ }
179
+ const IMPORT_FROM_RE = /(?:^|\n)\s*(?:import|export)\s+[^;]*?\s+from\s+['"]([^'"]+)['"]/g;
180
+ const REQUIRE_RE = /\brequire\(\s*['"]([^'"]+)['"]\s*\)/g;
181
+ // `await import('pkg')` / `import('pkg')` — bare dynamic imports.
182
+ // Note: we intentionally match `\bimport\s*\(` not just `import(` so we
183
+ // don't false-trigger on `LocaleImport(...)` etc.
184
+ const DYNAMIC_IMPORT_RE = /\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
185
+ function extractRootSpecifiers(body) {
186
+ const out = [];
187
+ for (const m of body.matchAll(IMPORT_FROM_RE)) {
188
+ if (m[1])
189
+ out.push(m[1]);
190
+ }
191
+ for (const m of body.matchAll(REQUIRE_RE)) {
192
+ if (m[1])
193
+ out.push(m[1]);
194
+ }
195
+ for (const m of body.matchAll(DYNAMIC_IMPORT_RE)) {
196
+ if (m[1])
197
+ out.push(m[1]);
198
+ }
199
+ return out;
200
+ }
201
+ function rootOfSpecifier(spec) {
202
+ if (spec.startsWith('@')) {
203
+ const parts = spec.split('/');
204
+ return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : spec;
205
+ }
206
+ return spec.split('/')[0];
207
+ }
208
+ function isBuiltinModule(spec) {
209
+ if (spec.startsWith('node:'))
210
+ return true;
211
+ return new Set([
212
+ 'fs', 'path', 'os', 'crypto', 'http', 'https', 'url', 'util', 'stream',
213
+ 'events', 'child_process', 'process', 'buffer', 'querystring', 'zlib',
214
+ 'tls', 'net', 'dns', 'dgram', 'cluster', 'worker_threads', 'perf_hooks',
215
+ 'readline', 'tty', 'vm',
216
+ ]).has(spec);
217
+ }
@@ -0,0 +1,21 @@
1
+ import type { IToolDefinition } from '../server/tool-definition.js';
2
+ /**
3
+ * `plan_quality_review` — score a plan blob for hallucination,
4
+ * generic boilerplate, contradictions, and ungrounded commands.
5
+ *
6
+ * Inputs:
7
+ * - `plan`: either a stringified JSON / Markdown plan body, OR a
8
+ * `{ from: { slug } }` reference that resolves to a saved
9
+ * `.sharkcraft/smart-context/<slug>.plan.json`.
10
+ * - `recommendedCommands` (optional): the set of commands that are
11
+ * considered "real" (typically `verificationCommands` from the
12
+ * same focused bundle). Anything outside this set in
13
+ * `firstCommands`/`validationCommands` gets flagged.
14
+ *
15
+ * Output: structured findings + an overall verdict so the calling
16
+ * agent can decide whether to use, retry, or reject the plan.
17
+ *
18
+ * Read-only — does not modify or rewrite the plan.
19
+ */
20
+ export declare const planQualityReviewTool: IToolDefinition;
21
+ //# sourceMappingURL=plan-quality-review.tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-quality-review.tool.d.ts","sourceRoot":"","sources":["../../src/tools/plan-quality-review.tool.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAEpE;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,qBAAqB,EAAE,eAiFnC,CAAC"}
@@ -0,0 +1,294 @@
1
+ import { existsSync } from 'node:fs';
2
+ import * as nodePath from 'node:path';
3
+ /**
4
+ * `plan_quality_review` — score a plan blob for hallucination,
5
+ * generic boilerplate, contradictions, and ungrounded commands.
6
+ *
7
+ * Inputs:
8
+ * - `plan`: either a stringified JSON / Markdown plan body, OR a
9
+ * `{ from: { slug } }` reference that resolves to a saved
10
+ * `.sharkcraft/smart-context/<slug>.plan.json`.
11
+ * - `recommendedCommands` (optional): the set of commands that are
12
+ * considered "real" (typically `verificationCommands` from the
13
+ * same focused bundle). Anything outside this set in
14
+ * `firstCommands`/`validationCommands` gets flagged.
15
+ *
16
+ * Output: structured findings + an overall verdict so the calling
17
+ * agent can decide whether to use, retry, or reject the plan.
18
+ *
19
+ * Read-only — does not modify or rewrite the plan.
20
+ */
21
+ export const planQualityReviewTool = {
22
+ name: 'plan_quality_review',
23
+ description: 'Critique a plan (JSON or Markdown) for hallucinated paths, generic boilerplate, contradictions, and ungrounded commands. Read-only.',
24
+ inputSchema: {
25
+ type: 'object',
26
+ properties: {
27
+ plan: { type: 'string' },
28
+ planFrom: { type: 'object' },
29
+ recommendedCommands: { type: 'array', items: { type: 'string' } },
30
+ },
31
+ additionalProperties: false,
32
+ },
33
+ async handler(input, ctx) {
34
+ let planText;
35
+ const planFrom = input['planFrom'];
36
+ if (typeof input['plan'] === 'string' && input['plan'].length > 0) {
37
+ planText = input['plan'];
38
+ }
39
+ else if (planFrom && typeof planFrom === 'object') {
40
+ const slug = typeof planFrom['slug'] === 'string'
41
+ ? planFrom['slug']
42
+ : '';
43
+ if (slug.length === 0) {
44
+ return { data: { error: 'planFrom.slug is required when using planFrom' } };
45
+ }
46
+ const path = nodePath.join(ctx.cwd, '.sharkcraft', 'smart-context', `${slug}.plan.json`);
47
+ if (!existsSync(path)) {
48
+ return { data: { error: 'no-plan-for-slug', slug, lookedAt: path } };
49
+ }
50
+ try {
51
+ const { readFileSync } = await import('node:fs');
52
+ planText = readFileSync(path, 'utf8');
53
+ }
54
+ catch (e) {
55
+ return { data: { error: `read failed: ${e.message}` } };
56
+ }
57
+ }
58
+ else {
59
+ return { data: { error: 'one of `plan` or `planFrom` is required' } };
60
+ }
61
+ if (!planText)
62
+ return { data: { error: 'empty plan' } };
63
+ const parsed = tryExtractJson(planText);
64
+ const recommendedCommandsRaw = Array.isArray(input['recommendedCommands'])
65
+ ? input['recommendedCommands'].filter((c) => typeof c === 'string')
66
+ : [];
67
+ const knownCommands = new Set(recommendedCommandsRaw.map(normaliseCommand));
68
+ const findings = analysePlan({
69
+ cwd: ctx.cwd,
70
+ raw: planText,
71
+ parsed,
72
+ knownCommands,
73
+ });
74
+ const score = scoreFindings(findings);
75
+ const verdict = score >= 0.85
76
+ ? 'good'
77
+ : score >= 0.6
78
+ ? 'usable-with-review'
79
+ : score >= 0.35
80
+ ? 'weak'
81
+ : 'reject';
82
+ return {
83
+ data: {
84
+ verdict,
85
+ score,
86
+ parsed: parsed !== null,
87
+ findings,
88
+ handoffForClaude: verdict === 'reject'
89
+ ? 'Reject this plan — too many ungrounded claims.'
90
+ : verdict === 'weak'
91
+ ? 'Plan is weak; consider regenerating with --polish or a stronger model.'
92
+ : verdict === 'usable-with-review'
93
+ ? 'Plan is usable but verify the flagged paths and commands.'
94
+ : 'Plan looks well-grounded.',
95
+ },
96
+ };
97
+ },
98
+ };
99
+ function analysePlan(input) {
100
+ const out = [];
101
+ // 1. Hallucinated paths — walk the parsed JSON.
102
+ if (input.parsed) {
103
+ const seen = new Set();
104
+ walkPathLeaves(input.parsed, '$', (path, where) => {
105
+ const id = `${where}:${path}`;
106
+ if (seen.has(id))
107
+ return;
108
+ seen.add(id);
109
+ if (!looksLikePathRef(path))
110
+ return;
111
+ if (!pathExistsInWorkspace(input.cwd, path)) {
112
+ out.push({
113
+ category: 'hallucinated-path',
114
+ severity: 'high',
115
+ message: `Path "${path}" does not exist in the workspace.`,
116
+ where,
117
+ });
118
+ }
119
+ });
120
+ }
121
+ // 2. Generic boilerplate (the polish preamble's anti-patterns).
122
+ const genericPatterns = [
123
+ { re: /may require additional resources and infrastructure/i, msg: 'Generic "additional resources" boilerplate.' },
124
+ { re: /may introduce additional complexity/i, msg: 'Generic "additional complexity" boilerplate.' },
125
+ { re: /may require additional security and privacy considerations/i, msg: 'Generic "security and privacy" boilerplate.' },
126
+ { re: /can be implemented as a separate tool or as a plugin/i, msg: '"Can be implemented as a separate tool" — useless differentiation.' },
127
+ { re: /documentation and support level/i, msg: 'Enterprise-boilerplate question about "documentation and support level".' },
128
+ { re: /\bGET\b.*\b(cli|file|stdout|mcp)/i, msg: 'HTTP verb on a CLI/file/stdout/MCP surface.' },
129
+ ];
130
+ for (const p of genericPatterns) {
131
+ if (p.re.test(input.raw)) {
132
+ out.push({ category: 'generic-boilerplate', severity: 'medium', message: p.msg });
133
+ }
134
+ }
135
+ // 3. Contradictions: filesToAvoid ∩ likelyFilesToModify.
136
+ if (input.parsed) {
137
+ const avoid = collectPathSet(input.parsed['filesToAvoid']);
138
+ const modify = collectPathSet(input.parsed['likelyFilesToModify']);
139
+ const overlap = [...avoid].filter((p) => modify.has(p));
140
+ for (const p of overlap) {
141
+ out.push({
142
+ category: 'contradiction',
143
+ severity: 'high',
144
+ message: `"${p}" appears in both filesToAvoid and likelyFilesToModify.`,
145
+ });
146
+ }
147
+ }
148
+ // 4. Ungrounded commands.
149
+ if (input.parsed && input.knownCommands.size > 0) {
150
+ const firstCmds = collectStringArrayFromKey(input.parsed['firstCommands'], 'command');
151
+ const valCmds = collectStringArrayFromKey(input.parsed['validationCommands'], 'command');
152
+ const plainVal = collectStringArray(input.parsed['validationCommands']);
153
+ const all = new Set([...firstCmds, ...valCmds, ...plainVal].map(normaliseCommand));
154
+ for (const c of all) {
155
+ if (c.length === 0)
156
+ continue;
157
+ // Don't flag commands that obviously start with `git` or `bun x tsc` —
158
+ // those are universal even when the rule registry doesn't list them.
159
+ if (/^(git|bun\s+x\s+tsc|npm|pnpm|yarn)\b/.test(c))
160
+ continue;
161
+ if (!input.knownCommands.has(c)) {
162
+ out.push({
163
+ category: 'ungrounded-command',
164
+ severity: 'low',
165
+ message: `Command "${c}" is not in the supplied recommendedCommands.`,
166
+ });
167
+ }
168
+ }
169
+ }
170
+ // 5. Missing-section heuristic for arch-plan shapes.
171
+ if (input.parsed) {
172
+ if ('candidateArchitectures' in input.parsed) {
173
+ // architecture mode — recommendedMvp + firstSpike are required.
174
+ if (!input.parsed['recommendedMvp']) {
175
+ out.push({
176
+ category: 'missing-section',
177
+ severity: 'medium',
178
+ message: 'architecture plan is missing recommendedMvp.',
179
+ });
180
+ }
181
+ if (!input.parsed['firstSpike']) {
182
+ out.push({
183
+ category: 'missing-section',
184
+ severity: 'medium',
185
+ message: 'architecture plan is missing firstSpike.',
186
+ });
187
+ }
188
+ }
189
+ }
190
+ return out;
191
+ }
192
+ function scoreFindings(findings) {
193
+ if (findings.length === 0)
194
+ return 1;
195
+ let penalty = 0;
196
+ for (const f of findings) {
197
+ penalty += f.severity === 'high' ? 0.2 : f.severity === 'medium' ? 0.08 : 0.03;
198
+ }
199
+ return Math.max(0, 1 - penalty);
200
+ }
201
+ function walkPathLeaves(value, where, visit) {
202
+ if (Array.isArray(value)) {
203
+ for (let i = 0; i < value.length; i += 1)
204
+ walkPathLeaves(value[i], `${where}[${i}]`, visit);
205
+ return;
206
+ }
207
+ if (value === null || typeof value !== 'object')
208
+ return;
209
+ const rec = value;
210
+ for (const k of Object.keys(rec)) {
211
+ if (k === 'path' && typeof rec[k] === 'string') {
212
+ visit(rec[k], `${where}.${k}`);
213
+ continue;
214
+ }
215
+ walkPathLeaves(rec[k], `${where}.${k}`, visit);
216
+ }
217
+ }
218
+ function collectPathSet(value) {
219
+ const out = new Set();
220
+ if (!Array.isArray(value))
221
+ return out;
222
+ for (const item of value) {
223
+ if (item && typeof item === 'object' && typeof item['path'] === 'string') {
224
+ out.add(item['path'].trim());
225
+ }
226
+ }
227
+ return out;
228
+ }
229
+ function collectStringArrayFromKey(value, key) {
230
+ const out = [];
231
+ if (!Array.isArray(value))
232
+ return out;
233
+ for (const item of value) {
234
+ if (item && typeof item === 'object' && typeof item[key] === 'string') {
235
+ out.push(item[key].trim());
236
+ }
237
+ }
238
+ return out;
239
+ }
240
+ function collectStringArray(value) {
241
+ if (!Array.isArray(value))
242
+ return [];
243
+ return value.filter((s) => typeof s === 'string');
244
+ }
245
+ function looksLikePathRef(s) {
246
+ if (s.length === 0)
247
+ return false;
248
+ if (/[<>{}]/.test(s))
249
+ return false;
250
+ if (s.includes('/'))
251
+ return true;
252
+ return /\.(ts|tsx|js|jsx|mjs|cjs|json|md|yml|yaml|css|html)$/.test(s);
253
+ }
254
+ function pathExistsInWorkspace(cwd, candidate) {
255
+ const normalised = candidate.replace(/\\/g, '/').replace(/^\.\//, '');
256
+ const abs = nodePath.isAbsolute(normalised) ? normalised : nodePath.join(cwd, normalised);
257
+ try {
258
+ return existsSync(abs);
259
+ }
260
+ catch {
261
+ return false;
262
+ }
263
+ }
264
+ function normaliseCommand(c) {
265
+ return c.trim().replace(/\s+/g, ' ');
266
+ }
267
+ function tryExtractJson(text) {
268
+ const trimmed = text.trim();
269
+ const fenced = trimmed.match(/```json\s*([\s\S]*?)```/i);
270
+ const candidate = fenced?.[1]?.trim() ?? trimmed;
271
+ try {
272
+ const parsed = JSON.parse(candidate);
273
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
274
+ return parsed;
275
+ }
276
+ }
277
+ catch {
278
+ // Try balanced extraction
279
+ }
280
+ const first = candidate.indexOf('{');
281
+ const last = candidate.lastIndexOf('}');
282
+ if (first >= 0 && last > first) {
283
+ try {
284
+ const parsed = JSON.parse(candidate.slice(first, last + 1));
285
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
286
+ return parsed;
287
+ }
288
+ }
289
+ catch {
290
+ // ignore
291
+ }
292
+ }
293
+ return null;
294
+ }
@@ -0,0 +1,17 @@
1
+ import type { IToolDefinition } from '../server/tool-definition.js';
2
+ /**
3
+ * `smart_context_bundle` — read-only MCP surface for the focused
4
+ * context bundle that `shrk smart-context --focused` builds.
5
+ *
6
+ * The agent calls this with a task string. The tool runs BGE-based
7
+ * semantic search, declaration extraction, and re-ranking against
8
+ * the existing on-disk embedding index, then returns the bundle as
9
+ * JSON plus a hint pointing at the CLI commands the agent should
10
+ * run next.
11
+ *
12
+ * No writes. No LLM calls. If the index has not been built yet the
13
+ * tool returns a structured error pointing to
14
+ * `shrk smart-context embeddings-build`.
15
+ */
16
+ export declare const smartContextBundleTool: IToolDefinition;
17
+ //# sourceMappingURL=smart-context-bundle.tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"smart-context-bundle.tool.d.ts","sourceRoot":"","sources":["../../src/tools/smart-context-bundle.tool.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAEpE;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,sBAAsB,EAAE,eA6EpC,CAAC"}
@@ -0,0 +1,108 @@
1
+ import { buildTaskPacket } from '@shrkcrft/inspector';
2
+ import { SemanticIndex, TaskType, buildFocusedContext, classifyTask, parseTaskTypeOverride, } from '@shrkcrft/embeddings';
3
+ /**
4
+ * `smart_context_bundle` — read-only MCP surface for the focused
5
+ * context bundle that `shrk smart-context --focused` builds.
6
+ *
7
+ * The agent calls this with a task string. The tool runs BGE-based
8
+ * semantic search, declaration extraction, and re-ranking against
9
+ * the existing on-disk embedding index, then returns the bundle as
10
+ * JSON plus a hint pointing at the CLI commands the agent should
11
+ * run next.
12
+ *
13
+ * No writes. No LLM calls. If the index has not been built yet the
14
+ * tool returns a structured error pointing to
15
+ * `shrk smart-context embeddings-build`.
16
+ */
17
+ export const smartContextBundleTool = {
18
+ name: 'smart_context_bundle',
19
+ description: 'Build a task-focused context bundle (semantic-ranked code blocks + rules + doc hits + validation commands). Read-only.',
20
+ inputSchema: {
21
+ type: 'object',
22
+ properties: {
23
+ task: { type: 'string' },
24
+ taskType: { type: 'string' },
25
+ maxBlocks: { type: 'number' },
26
+ },
27
+ required: ['task'],
28
+ additionalProperties: false,
29
+ },
30
+ async handler(input, ctx) {
31
+ const task = typeof input['task'] === 'string' ? input['task'].trim() : '';
32
+ if (task.length === 0) {
33
+ return {
34
+ data: {
35
+ error: 'task is required',
36
+ },
37
+ };
38
+ }
39
+ const overrideRaw = typeof input['taskType'] === 'string' ? input['taskType'] : undefined;
40
+ const taskTypeOverride = parseTaskTypeOverride(overrideRaw);
41
+ const maxBlocks = typeof input['maxBlocks'] === 'number' ? input['maxBlocks'] : 10;
42
+ const index = await SemanticIndex.tryLoad(ctx.cwd);
43
+ if (!index) {
44
+ return {
45
+ data: {
46
+ error: 'no-semantic-index',
47
+ message: 'The semantic index has not been built for this workspace yet. The agent should ask the human to run the CLI command below.',
48
+ nextCommand: 'shrk smart-context embeddings-build',
49
+ },
50
+ };
51
+ }
52
+ const classification = taskTypeOverride
53
+ ? { type: taskTypeOverride, confidence: 1, signals: ['override'], scores: {} }
54
+ : classifyTask(task);
55
+ const packet = buildTaskPacket(ctx.inspection, task, { maxTokens: 3500 });
56
+ const focused = await buildFocusedContext({
57
+ cwd: ctx.cwd,
58
+ task,
59
+ index,
60
+ rules: packet.relevantRules,
61
+ verificationCommands: packet.verificationCommands,
62
+ maxBlocks: Math.max(2, Math.min(20, maxBlocks)),
63
+ });
64
+ return {
65
+ data: {
66
+ task,
67
+ taskType: classification.type,
68
+ classification: {
69
+ type: classification.type,
70
+ confidence: classification.confidence,
71
+ signals: classification.signals.slice(0, 6),
72
+ },
73
+ focused: {
74
+ model: focused.model,
75
+ approxTokens: focused.approxTokens,
76
+ files: focused.files,
77
+ rules: focused.rules,
78
+ docHits: focused.docHits,
79
+ verificationCommands: focused.verificationCommands,
80
+ },
81
+ nextCommands: nextCommandHints(task, classification.type),
82
+ notes: [
83
+ 'This bundle is read-only. To turn the recommended MVP into starter files, the human should run `shrk smart-context "<task>" --focused --plan --save` followed by `shrk spike <slug>`.',
84
+ ],
85
+ },
86
+ };
87
+ },
88
+ };
89
+ function nextCommandHints(task, taskType) {
90
+ const quotedTask = JSON.stringify(task);
91
+ if (taskType === TaskType.Architecture) {
92
+ return [
93
+ `shrk smart-context ${quotedTask} --focused --plan --save`,
94
+ `shrk smart-context list # find the saved slug`,
95
+ `shrk spike <slug> # scaffold the recommended MVP`,
96
+ ];
97
+ }
98
+ if (taskType === TaskType.Investigation) {
99
+ return [
100
+ `shrk smart-context ${quotedTask} --focused --save`,
101
+ `shrk graph why <a> <b> # trace structural relationships`,
102
+ ];
103
+ }
104
+ return [
105
+ `shrk smart-context ${quotedTask} --focused --plan --save`,
106
+ `shrk spike <slug> # if the plan has a firstSpike`,
107
+ ];
108
+ }
@@ -0,0 +1,17 @@
1
+ import type { IToolDefinition } from '../server/tool-definition.js';
2
+ /**
3
+ * `smart_context_feed` — read-only MCP surface for an active
4
+ * `shrk watch` JSONL feed.
5
+ *
6
+ * Two modes:
7
+ * - `list` → returns active watch manifests under .sharkcraft/feed/.
8
+ * - `tail` → returns the JSONL tail of one slug, optionally
9
+ * filtered by `since` (emittedAt ISO timestamp) or `tailLines`.
10
+ *
11
+ * The agent uses `list` first to discover which watcher is running for
12
+ * which task, then `tail` to pull new packets since the last call.
13
+ * Nothing is written; the daemon CLI (`shrk watch`) is the only
14
+ * producer.
15
+ */
16
+ export declare const smartContextFeedTool: IToolDefinition;
17
+ //# sourceMappingURL=smart-context-feed.tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"smart-context-feed.tool.d.ts","sourceRoot":"","sources":["../../src/tools/smart-context-feed.tool.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAKpE;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,oBAAoB,EAAE,eA+BlC,CAAC"}
@@ -0,0 +1,138 @@
1
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
2
+ import * as nodePath from 'node:path';
3
+ const FEED_DIR = nodePath.join('.sharkcraft', 'feed');
4
+ const MANIFEST_SCHEMA = 'sharkcraft.shrk-watch-manifest/v1';
5
+ /**
6
+ * `smart_context_feed` — read-only MCP surface for an active
7
+ * `shrk watch` JSONL feed.
8
+ *
9
+ * Two modes:
10
+ * - `list` → returns active watch manifests under .sharkcraft/feed/.
11
+ * - `tail` → returns the JSONL tail of one slug, optionally
12
+ * filtered by `since` (emittedAt ISO timestamp) or `tailLines`.
13
+ *
14
+ * The agent uses `list` first to discover which watcher is running for
15
+ * which task, then `tail` to pull new packets since the last call.
16
+ * Nothing is written; the daemon CLI (`shrk watch`) is the only
17
+ * producer.
18
+ */
19
+ export const smartContextFeedTool = {
20
+ name: 'smart_context_feed',
21
+ description: 'Poll the JSONL feed of an active `shrk watch` daemon. Read-only. Use mode="list" to discover slugs and mode="tail" to pull packets.',
22
+ inputSchema: {
23
+ type: 'object',
24
+ properties: {
25
+ mode: { type: 'string' },
26
+ slug: { type: 'string' },
27
+ since: { type: 'string' },
28
+ tailLines: { type: 'number' },
29
+ },
30
+ additionalProperties: false,
31
+ },
32
+ async handler(input, ctx) {
33
+ const mode = typeof input['mode'] === 'string' ? input['mode'] : 'list';
34
+ if (mode !== 'list' && mode !== 'tail') {
35
+ return { data: { error: 'invalid-mode', valid: ['list', 'tail'] } };
36
+ }
37
+ const dir = nodePath.join(ctx.cwd, FEED_DIR);
38
+ if (mode === 'list') {
39
+ return { data: listFeeds(dir) };
40
+ }
41
+ const slug = typeof input['slug'] === 'string' ? input['slug'].trim() : '';
42
+ if (slug.length === 0) {
43
+ return { data: { error: 'slug is required for mode=tail' } };
44
+ }
45
+ const since = typeof input['since'] === 'string' ? input['since'] : undefined;
46
+ const tailLines = typeof input['tailLines'] === 'number' ? input['tailLines'] : 50;
47
+ return { data: tailFeed(dir, slug, { since, tailLines }) };
48
+ },
49
+ };
50
+ function listFeeds(dir) {
51
+ if (!existsSync(dir))
52
+ return { active: [], stale: [] };
53
+ const active = [];
54
+ const stale = [];
55
+ let entries = [];
56
+ try {
57
+ entries = readdirSync(dir);
58
+ }
59
+ catch {
60
+ return { active: [], stale: [] };
61
+ }
62
+ for (const name of entries) {
63
+ if (!name.endsWith('.pid.json'))
64
+ continue;
65
+ const path = nodePath.join(dir, name);
66
+ let m;
67
+ try {
68
+ m = JSON.parse(readFileSync(path, 'utf8'));
69
+ }
70
+ catch {
71
+ continue;
72
+ }
73
+ if (m.schema !== MANIFEST_SCHEMA || typeof m.pid !== 'number')
74
+ continue;
75
+ const summary = {
76
+ slug: m.slug ?? '',
77
+ pid: m.pid,
78
+ task: m.task ?? '',
79
+ startedAt: m.startedAt ?? '',
80
+ feedPath: m.feedPath ?? '',
81
+ alive: isProcessAlive(m.pid),
82
+ feedExists: m.feedPath ? existsSync(m.feedPath) : false,
83
+ };
84
+ (summary.alive ? active : stale).push(summary);
85
+ }
86
+ return { active, stale };
87
+ }
88
+ function tailFeed(dir, slug, options) {
89
+ const feedPath = nodePath.join(dir, `${slug}.jsonl`);
90
+ if (!existsSync(feedPath)) {
91
+ return { slug, feedPath, packets: [], totalLines: 0, filteredOut: 0, feedExists: false };
92
+ }
93
+ let body;
94
+ try {
95
+ body = readFileSync(feedPath, 'utf8');
96
+ }
97
+ catch {
98
+ return { slug, feedPath, packets: [], totalLines: 0, filteredOut: 0, feedExists: true };
99
+ }
100
+ const rawLines = body.split('\n').filter((l) => l.trim().length > 0);
101
+ const packets = [];
102
+ let filteredOut = 0;
103
+ for (const line of rawLines) {
104
+ try {
105
+ const p = JSON.parse(line);
106
+ if (options.since) {
107
+ const emittedAt = typeof p['emittedAt'] === 'string' ? p['emittedAt'] : '';
108
+ if (emittedAt && emittedAt <= options.since) {
109
+ filteredOut += 1;
110
+ continue;
111
+ }
112
+ }
113
+ packets.push(p);
114
+ }
115
+ catch {
116
+ filteredOut += 1;
117
+ }
118
+ }
119
+ // Keep only the trailing N once filtered.
120
+ const tailed = options.tailLines > 0 ? packets.slice(-options.tailLines) : packets;
121
+ return {
122
+ slug,
123
+ feedPath,
124
+ packets: tailed,
125
+ totalLines: rawLines.length,
126
+ filteredOut,
127
+ feedExists: true,
128
+ };
129
+ }
130
+ function isProcessAlive(pid) {
131
+ try {
132
+ process.kill(pid, 0);
133
+ return true;
134
+ }
135
+ catch {
136
+ return false;
137
+ }
138
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shrkcrft/mcp-server",
3
- "version": "0.1.0-alpha.11",
3
+ "version": "0.1.0-alpha.13",
4
4
  "description": "SharkCraft MCP server: 25 tools over @modelcontextprotocol/sdk's stdio transport.",
5
5
  "license": "MIT",
6
6
  "author": "SharkCraft contributors",
@@ -44,30 +44,31 @@
44
44
  "typecheck": "tsc --noEmit -p tsconfig.json"
45
45
  },
46
46
  "dependencies": {
47
- "@shrkcrft/core": "^0.1.0-alpha.11",
48
- "@shrkcrft/config": "^0.1.0-alpha.11",
49
- "@shrkcrft/workspace": "^0.1.0-alpha.11",
50
- "@shrkcrft/knowledge": "^0.1.0-alpha.11",
51
- "@shrkcrft/context": "^0.1.0-alpha.11",
52
- "@shrkcrft/rules": "^0.1.0-alpha.11",
53
- "@shrkcrft/paths": "^0.1.0-alpha.11",
54
- "@shrkcrft/templates": "^0.1.0-alpha.11",
55
- "@shrkcrft/pipelines": "^0.1.0-alpha.11",
56
- "@shrkcrft/presets": "^0.1.0-alpha.11",
57
- "@shrkcrft/boundaries": "^0.1.0-alpha.11",
58
- "@shrkcrft/graph": "^0.1.0-alpha.11",
59
- "@shrkcrft/rule-graph": "^0.1.0-alpha.11",
60
- "@shrkcrft/structural-search": "^0.1.0-alpha.11",
61
- "@shrkcrft/impact-engine": "^0.1.0-alpha.11",
62
- "@shrkcrft/context-planner": "^0.1.0-alpha.11",
63
- "@shrkcrft/architecture-guard": "^0.1.0-alpha.11",
64
- "@shrkcrft/framework-scanners": "^0.1.0-alpha.11",
65
- "@shrkcrft/api-surface-diff": "^0.1.0-alpha.11",
66
- "@shrkcrft/quality-gates": "^0.1.0-alpha.11",
67
- "@shrkcrft/migrate": "^0.1.0-alpha.11",
68
- "@shrkcrft/packs": "^0.1.0-alpha.11",
69
- "@shrkcrft/generator": "^0.1.0-alpha.11",
70
- "@shrkcrft/inspector": "^0.1.0-alpha.11",
47
+ "@shrkcrft/core": "^0.1.0-alpha.13",
48
+ "@shrkcrft/config": "^0.1.0-alpha.13",
49
+ "@shrkcrft/workspace": "^0.1.0-alpha.13",
50
+ "@shrkcrft/knowledge": "^0.1.0-alpha.13",
51
+ "@shrkcrft/context": "^0.1.0-alpha.13",
52
+ "@shrkcrft/rules": "^0.1.0-alpha.13",
53
+ "@shrkcrft/paths": "^0.1.0-alpha.13",
54
+ "@shrkcrft/templates": "^0.1.0-alpha.13",
55
+ "@shrkcrft/pipelines": "^0.1.0-alpha.13",
56
+ "@shrkcrft/presets": "^0.1.0-alpha.13",
57
+ "@shrkcrft/boundaries": "^0.1.0-alpha.13",
58
+ "@shrkcrft/graph": "^0.1.0-alpha.13",
59
+ "@shrkcrft/rule-graph": "^0.1.0-alpha.13",
60
+ "@shrkcrft/structural-search": "^0.1.0-alpha.13",
61
+ "@shrkcrft/impact-engine": "^0.1.0-alpha.13",
62
+ "@shrkcrft/context-planner": "^0.1.0-alpha.13",
63
+ "@shrkcrft/architecture-guard": "^0.1.0-alpha.13",
64
+ "@shrkcrft/framework-scanners": "^0.1.0-alpha.13",
65
+ "@shrkcrft/api-surface-diff": "^0.1.0-alpha.13",
66
+ "@shrkcrft/quality-gates": "^0.1.0-alpha.13",
67
+ "@shrkcrft/migrate": "^0.1.0-alpha.13",
68
+ "@shrkcrft/packs": "^0.1.0-alpha.13",
69
+ "@shrkcrft/generator": "^0.1.0-alpha.13",
70
+ "@shrkcrft/inspector": "^0.1.0-alpha.13",
71
+ "@shrkcrft/embeddings": "^0.1.0-alpha.13",
71
72
  "@modelcontextprotocol/sdk": "^1.0.0",
72
73
  "zod": "^3.25.0 || ^4.0.0"
73
74
  },