@shrkcrft/mcp-server 0.1.0-alpha.17 → 0.1.0-alpha.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/tools/all-tools.d.ts.map +1 -1
- package/dist/tools/all-tools.js +6 -0
- package/dist/tools/code-find-usages.tool.d.ts.map +1 -1
- package/dist/tools/code-find-usages.tool.js +44 -6
- package/dist/tools/delegate-task.tool.d.ts +3 -0
- package/dist/tools/delegate-task.tool.d.ts.map +1 -0
- package/dist/tools/delegate-task.tool.js +94 -0
- package/dist/tools/deps-audit.tool.js +3 -3
- package/dist/tools/get-graph-callers.tool.d.ts.map +1 -1
- package/dist/tools/get-graph-callers.tool.js +19 -6
- package/dist/tools/get-graph-context.tool.d.ts.map +1 -1
- package/dist/tools/get-graph-context.tool.js +49 -15
- package/dist/tools/get-graph-cycles.tool.js +2 -2
- package/dist/tools/get-graph-deps.tool.js +2 -2
- package/dist/tools/get-graph-hubs.tool.d.ts +3 -0
- package/dist/tools/get-graph-hubs.tool.d.ts.map +1 -0
- package/dist/tools/get-graph-hubs.tool.js +61 -0
- package/dist/tools/get-graph-impact.tool.d.ts.map +1 -1
- package/dist/tools/get-graph-impact.tool.js +36 -14
- package/dist/tools/get-graph-path.tool.d.ts +3 -0
- package/dist/tools/get-graph-path.tool.d.ts.map +1 -0
- package/dist/tools/get-graph-path.tool.js +144 -0
- package/dist/tools/get-graph-search.tool.d.ts.map +1 -1
- package/dist/tools/get-graph-search.tool.js +22 -3
- package/dist/tools/get-graph-status.tool.d.ts +5 -3
- package/dist/tools/get-graph-status.tool.d.ts.map +1 -1
- package/dist/tools/get-graph-status.tool.js +15 -6
- package/dist/tools/graph-staleness.d.ts +34 -0
- package/dist/tools/graph-staleness.d.ts.map +1 -0
- package/dist/tools/graph-staleness.js +36 -0
- package/dist/tools/primary-tools.d.ts +1 -1
- package/dist/tools/primary-tools.d.ts.map +1 -1
- package/dist/tools/primary-tools.js +12 -1
- package/dist/tools/start-here.tool.js +2 -2
- package/package.json +27 -27
|
@@ -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;
|
|
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;AAkVpE,eAAO,MAAM,SAAS,EAAE,SAAS,eAAe,EA4R9C,CAAC"}
|
package/dist/tools/all-tools.js
CHANGED
|
@@ -46,7 +46,10 @@ import { getGraphStatusTool } from "./get-graph-status.tool.js";
|
|
|
46
46
|
import { getGraphSearchTool } from "./get-graph-search.tool.js";
|
|
47
47
|
import { getGraphContextTool } from "./get-graph-context.tool.js";
|
|
48
48
|
import { getGraphImpactTool } from "./get-graph-impact.tool.js";
|
|
49
|
+
import { getGraphPathTool } from "./get-graph-path.tool.js";
|
|
50
|
+
import { getGraphHubsTool } from "./get-graph-hubs.tool.js";
|
|
49
51
|
import { getGraphCallersTool } from "./get-graph-callers.tool.js";
|
|
52
|
+
import { delegateTaskTool } from "./delegate-task.tool.js";
|
|
50
53
|
import { getGraphCyclesTool } from "./get-graph-cycles.tool.js";
|
|
51
54
|
import { getGraphUnresolvedTool } from "./get-graph-unresolved.tool.js";
|
|
52
55
|
import { getGraphDepsTool } from "./get-graph-deps.tool.js";
|
|
@@ -227,7 +230,10 @@ export const ALL_TOOLS = Object.freeze([
|
|
|
227
230
|
getGraphSearchTool,
|
|
228
231
|
getGraphContextTool,
|
|
229
232
|
getGraphImpactTool,
|
|
233
|
+
getGraphPathTool,
|
|
234
|
+
getGraphHubsTool,
|
|
230
235
|
getGraphCallersTool,
|
|
236
|
+
delegateTaskTool,
|
|
231
237
|
getGraphCyclesTool,
|
|
232
238
|
getGraphUnresolvedTool,
|
|
233
239
|
getGraphDepsTool,
|
|
@@ -1 +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;
|
|
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;AAIpE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,kBAAkB,EAAE,eAuIhC,CAAC"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import * as nodePath from 'node:path';
|
|
3
|
-
import { EdgeKind, GraphQueryApi, GraphStore, NodeKind } from '@shrkcrft/graph';
|
|
3
|
+
import { EdgeKind, GraphQueryApi, GraphStore, NodeKind, loadGraphApiCached } from '@shrkcrft/graph';
|
|
4
4
|
import { FORMAT_INPUT_PROPERTY, formatObjectArrays } from "../server/columnar-format.js";
|
|
5
|
+
import { callGraphLanguageNote } from "./graph-staleness.js";
|
|
5
6
|
/**
|
|
6
7
|
* `code_find_usages` — structured usage finder backed by the
|
|
7
8
|
* SharkCraft graph (file + symbol nodes + import/declare edges).
|
|
@@ -17,7 +18,7 @@ import { FORMAT_INPUT_PROPERTY, formatObjectArrays } from "../server/columnar-fo
|
|
|
17
18
|
*/
|
|
18
19
|
export const codeFindUsagesTool = {
|
|
19
20
|
name: 'code_find_usages',
|
|
20
|
-
description: 'Find
|
|
21
|
+
description: 'Find where a symbol is used (use this instead of grep). Returns the definition site and exact use sites as path:line via the SharkCraft graph, plus files that import the declaring file and neighbouring symbols. Read-only; needs `shrk graph index`. Pass `format:"table"` for a token-efficient columnar encoding.',
|
|
21
22
|
inputSchema: {
|
|
22
23
|
type: 'object',
|
|
23
24
|
properties: {
|
|
@@ -40,12 +41,12 @@ export const codeFindUsagesTool = {
|
|
|
40
41
|
return {
|
|
41
42
|
data: {
|
|
42
43
|
error: 'no-graph',
|
|
43
|
-
message: 'The SharkCraft graph index has not been built yet. Build it with `shrk graph
|
|
44
|
-
nextCommand: 'shrk graph
|
|
44
|
+
message: 'The SharkCraft graph index has not been built yet. Build it with `shrk graph index`.',
|
|
45
|
+
nextCommand: 'shrk graph index',
|
|
45
46
|
},
|
|
46
47
|
};
|
|
47
48
|
}
|
|
48
|
-
const api = GraphQueryApi.fromStore(ctx.cwd);
|
|
49
|
+
const api = loadGraphApiCached(ctx.cwd) ?? GraphQueryApi.fromStore(ctx.cwd);
|
|
49
50
|
const matches = api.findSymbol(symbolName, { exact: true, limit: maxResults });
|
|
50
51
|
if (matches.length === 0) {
|
|
51
52
|
return {
|
|
@@ -62,13 +63,29 @@ export const codeFindUsagesTool = {
|
|
|
62
63
|
const definitions = [];
|
|
63
64
|
const importerSet = new Map();
|
|
64
65
|
const neighbours = [];
|
|
66
|
+
const useSites = [];
|
|
65
67
|
for (const sym of matches) {
|
|
66
68
|
const declaringFile = declaringFileOf(api, sym.id);
|
|
67
69
|
definitions.push({
|
|
68
70
|
symbolId: sym.id,
|
|
69
71
|
file: declaringFile?.path ?? null,
|
|
72
|
+
...(sym.line ? { line: sym.line } : {}),
|
|
70
73
|
kind: String(sym.label && sym.label.length > 0 ? sym.label : sym.kind),
|
|
71
74
|
});
|
|
75
|
+
// Exact use sites (path:line) from the symbol's own call/reference
|
|
76
|
+
// edges, so the agent jumps straight to where it's used rather than
|
|
77
|
+
// grepping inside each importing file.
|
|
78
|
+
for (const site of api.referenceSitesOf(sym.id)) {
|
|
79
|
+
if (!site.node.path)
|
|
80
|
+
continue;
|
|
81
|
+
// Prune use sites whose file no longer exists — uniformly with
|
|
82
|
+
// importersOfDeclaringFile below, so the payload never lists a deleted
|
|
83
|
+
// file in one field while dropping it in another (a self-contradicting,
|
|
84
|
+
// authoritative-looking result is worse than a uniformly-stale one).
|
|
85
|
+
if (!pathExists(ctx.cwd, site.node.path))
|
|
86
|
+
continue;
|
|
87
|
+
useSites.push({ file: site.node.path, ...(site.line ? { line: site.line } : {}) });
|
|
88
|
+
}
|
|
72
89
|
if (declaringFile) {
|
|
73
90
|
for (const importer of api.importersOf(declaringFile.id)) {
|
|
74
91
|
if (!importer.path)
|
|
@@ -98,13 +115,34 @@ export const codeFindUsagesTool = {
|
|
|
98
115
|
}
|
|
99
116
|
}
|
|
100
117
|
}
|
|
118
|
+
// Result-file staleness: which surviving result files changed content
|
|
119
|
+
// since indexing (deleted ones are already pruned above). Flags a payload
|
|
120
|
+
// whose line numbers / membership may be out of date for files the agent
|
|
121
|
+
// just edited. Read-only.
|
|
122
|
+
const resultPaths = [
|
|
123
|
+
...definitions.map((d) => d.file),
|
|
124
|
+
...useSites.map((u) => u.file),
|
|
125
|
+
...[...importerSet.values()].map((i) => i.file),
|
|
126
|
+
].filter((p) => !!p);
|
|
127
|
+
const stale = api.staleFilesAmong(ctx.cwd, resultPaths);
|
|
128
|
+
// Non-TS languages have no call/reference extraction, so empty useSites must
|
|
129
|
+
// not be read as "no usages".
|
|
130
|
+
const langNote = matches[0] ? callGraphLanguageNote(api, matches[0]) : undefined;
|
|
101
131
|
const data = {
|
|
102
132
|
symbol: { name: symbolName, kind: matches[0]?.kind ?? 'unknown' },
|
|
103
133
|
definitions,
|
|
134
|
+
useSites,
|
|
104
135
|
importersOfDeclaringFile: [...importerSet.values()],
|
|
105
136
|
neighbouringSymbols: neighbours.slice(0, 12),
|
|
106
137
|
totalSymbolMatches: matches.length,
|
|
107
|
-
note:
|
|
138
|
+
note: (langNote ? langNote + ' ' : '') +
|
|
139
|
+
'useSites = exact file:line of each call/reference to the symbol (first use per file). importersOfDeclaringFile = files that import the declaring file (coarser; may include type-only/unused imports). Pair with `shrk impact` for a tighter blast radius.',
|
|
140
|
+
...(stale.modified.length > 0
|
|
141
|
+
? {
|
|
142
|
+
stale: { modified: stale.modified },
|
|
143
|
+
staleHint: 'Some result files changed since indexing — run `shrk graph index --changed` for fresh line numbers.',
|
|
144
|
+
}
|
|
145
|
+
: {}),
|
|
108
146
|
};
|
|
109
147
|
// `format:"table"` columnar-encodes the homogeneous object-array fields
|
|
110
148
|
// (definitions, importersOfDeclaringFile, neighbouringSymbols); the scalar
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delegate-task.tool.d.ts","sourceRoot":"","sources":["../../src/tools/delegate-task.tool.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAUpE,eAAO,MAAM,gBAAgB,EAAE,eA0E9B,CAAC"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { compressMarkdown } from '@shrkcrft/compress';
|
|
2
|
+
import { loadProjectConfig } from '@shrkcrft/config';
|
|
3
|
+
const READONLY_NOTE = 'Read-only. The CLI is the only write path. The worker may emit ONLY the allowed ops and touch ONLY the guardrail globs; the edit is verified deterministically and auto-reverted on failure — so it lands only if it passes the recipe verification.';
|
|
4
|
+
export const delegateTaskTool = {
|
|
5
|
+
name: 'delegate_task',
|
|
6
|
+
description: 'Get a compact brief for delegating a MECHANICAL, deterministically-verifiable edit to the local-LLM worker (read-only). Returns the recipe fence — allowed ops, guardrail globs, verification — and the exact `shrk delegate run` next command. Hand the grunt edit to the local worker instead of spending your own tokens reading the whole file and writing the edit. Never writes; needs a `delegation` block in sharkcraft.config.ts.',
|
|
7
|
+
cliCommand: 'delegate',
|
|
8
|
+
inputSchema: {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
task: { type: 'string' },
|
|
12
|
+
recipe: { type: 'string' },
|
|
13
|
+
},
|
|
14
|
+
required: ['task', 'recipe'],
|
|
15
|
+
additionalProperties: false,
|
|
16
|
+
},
|
|
17
|
+
async handler(input, ctx) {
|
|
18
|
+
const args = input;
|
|
19
|
+
const task = (args.task ?? '').trim();
|
|
20
|
+
const recipeId = (args.recipe ?? '').trim();
|
|
21
|
+
if (!task || !recipeId) {
|
|
22
|
+
return { isError: true, error: { code: 'invalid-input', message: 'task and recipe are required' } };
|
|
23
|
+
}
|
|
24
|
+
const loaded = await loadProjectConfig(ctx.inspection.projectRoot);
|
|
25
|
+
if (!loaded.ok) {
|
|
26
|
+
return { isError: true, error: { code: 'config-error', message: loaded.error.message } };
|
|
27
|
+
}
|
|
28
|
+
const delegation = loaded.value.config.delegation;
|
|
29
|
+
if (!delegation || delegation.enabled === false) {
|
|
30
|
+
return {
|
|
31
|
+
isError: true,
|
|
32
|
+
error: {
|
|
33
|
+
code: 'not-enabled',
|
|
34
|
+
message: 'delegation is not enabled in sharkcraft.config.ts',
|
|
35
|
+
details: { nextCommand: 'add a delegation { recipes: [...] } block to sharkcraft.config.ts' },
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const recipes = delegation.recipes ?? [];
|
|
40
|
+
const recipe = recipes.find((r) => r.id === recipeId);
|
|
41
|
+
if (!recipe) {
|
|
42
|
+
return {
|
|
43
|
+
isError: true,
|
|
44
|
+
error: {
|
|
45
|
+
code: 'not-found',
|
|
46
|
+
message: `unknown recipe "${recipeId}". Available: ${recipes.map((r) => r.id).join(', ') || '(none)'}`,
|
|
47
|
+
details: { available: recipes.map((r) => r.id) },
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const provider = recipe.provider ?? delegation.provider ?? 'auto';
|
|
52
|
+
const briefMarkdown = buildBriefMarkdown(task, recipe, provider);
|
|
53
|
+
// Compress the brief body (CCR-reversible when the server store is present).
|
|
54
|
+
// Small briefs pass through unchanged via the net-loss guard.
|
|
55
|
+
const compressed = compressMarkdown(briefMarkdown, ctx.ccrStore ? { store: ctx.ccrStore, query: task } : { query: task });
|
|
56
|
+
return {
|
|
57
|
+
data: {
|
|
58
|
+
schema: 'sharkcraft.delegate-task/v1',
|
|
59
|
+
recipeId: recipe.id,
|
|
60
|
+
title: recipe.title ?? recipe.id,
|
|
61
|
+
task,
|
|
62
|
+
allowedOps: recipe.allowedOps,
|
|
63
|
+
guardrailGlobs: recipe.guardrailGlobs,
|
|
64
|
+
verificationIds: recipe.verificationIds,
|
|
65
|
+
provider,
|
|
66
|
+
riskCeiling: recipe.riskCeiling ?? null,
|
|
67
|
+
brief: compressed.compressed,
|
|
68
|
+
...(compressed.ccrKey ? { ccrKey: compressed.ccrKey } : {}),
|
|
69
|
+
next: `shrk delegate run "${task}" --recipe ${recipe.id} --apply`,
|
|
70
|
+
note: READONLY_NOTE,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
function buildBriefMarkdown(task, recipe, provider) {
|
|
76
|
+
return [
|
|
77
|
+
`# Delegate brief: ${recipe.title ?? recipe.id}`,
|
|
78
|
+
'',
|
|
79
|
+
`**Task:** ${task}`,
|
|
80
|
+
'',
|
|
81
|
+
`**Recipe:** \`${recipe.id}\``,
|
|
82
|
+
`**Allowed ops:** ${recipe.allowedOps.join(', ')}`,
|
|
83
|
+
`**Guardrail globs (the worker may ONLY touch files matching these):** ${recipe.guardrailGlobs.join(', ')}`,
|
|
84
|
+
`**Verification (must pass or the edit is reverted):** ${recipe.verificationIds.join(', ') || '(none)'}`,
|
|
85
|
+
`**Provider:** ${provider}${recipe.model ? ` (${recipe.model})` : ''}`,
|
|
86
|
+
'',
|
|
87
|
+
'## How to delegate',
|
|
88
|
+
'',
|
|
89
|
+
'The CLI is the only write path. Run the `next` command: the local worker generates the edit,',
|
|
90
|
+
'the deterministic engine verifies it against the recipe verification, and auto-reverts on failure —',
|
|
91
|
+
'so the edit lands only if it is correct. You pay for this brief and the compact result, not for',
|
|
92
|
+
'reading the whole file or writing the edit yourself.',
|
|
93
|
+
].join('\n');
|
|
94
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
2
2
|
import * as nodePath from 'node:path';
|
|
3
|
-
import { GraphQueryApi, GraphStore, NodeKind } from '@shrkcrft/graph';
|
|
3
|
+
import { GraphQueryApi, GraphStore, NodeKind, loadGraphApiCached } from '@shrkcrft/graph';
|
|
4
4
|
import { FORMAT_INPUT_PROPERTY, formatObjectArrays, COLUMNAR_LEGEND } from "../server/columnar-format.js";
|
|
5
5
|
import { fitArrayToBudget } from "../server/fit-array-to-budget.js";
|
|
6
6
|
/**
|
|
@@ -34,11 +34,11 @@ export const depsAuditTool = {
|
|
|
34
34
|
data: {
|
|
35
35
|
error: 'no-graph',
|
|
36
36
|
message: 'The SharkCraft graph index is required for deps-audit.',
|
|
37
|
-
nextCommand: 'shrk graph
|
|
37
|
+
nextCommand: 'shrk graph index',
|
|
38
38
|
},
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
|
-
const api = GraphQueryApi.fromStore(ctx.cwd);
|
|
41
|
+
const api = loadGraphApiCached(ctx.cwd) ?? GraphQueryApi.fromStore(ctx.cwd);
|
|
42
42
|
const packages = listWorkspacePackages(ctx.cwd, onlyPackage);
|
|
43
43
|
const reports = packages.map((p) => buildPackageReport(api, ctx.cwd, p));
|
|
44
44
|
const totals = reports.reduce((acc, r) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-graph-callers.tool.d.ts","sourceRoot":"","sources":["../../src/tools/get-graph-callers.tool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"get-graph-callers.tool.d.ts","sourceRoot":"","sources":["../../src/tools/get-graph-callers.tool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAWpE,eAAO,MAAM,mBAAmB,EAAE,eAsEjC,CAAC"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { GraphQueryApi, GraphStore } from '@shrkcrft/graph';
|
|
1
|
+
import { GraphQueryApi, GraphStore, loadGraphApiCached } from '@shrkcrft/graph';
|
|
2
2
|
import { FORMAT_INPUT_PROPERTY, formatObjectArrays } from "../server/columnar-format.js";
|
|
3
|
+
import { callGraphLanguageNote, graphResultStaleness } from "./graph-staleness.js";
|
|
3
4
|
const NEXT = 'shrk graph index';
|
|
4
5
|
export const getGraphCallersTool = {
|
|
5
6
|
name: 'get_graph_callers',
|
|
6
|
-
description: '
|
|
7
|
+
description: 'Find who calls/references a symbol (use this instead of grep before changing a function/type). Returns each caller as path:line of the first call site. Mode "call" → calls-symbol edges; mode "reference" → both references-symbol and calls-symbol. Read-only; needs `shrk graph index`.',
|
|
7
8
|
cliCommand: 'graph callers',
|
|
8
9
|
inputSchema: {
|
|
9
10
|
type: 'object',
|
|
@@ -36,7 +37,7 @@ export const getGraphCallersTool = {
|
|
|
36
37
|
},
|
|
37
38
|
};
|
|
38
39
|
}
|
|
39
|
-
const api = GraphQueryApi.fromStore(ctx.inspection.projectRoot);
|
|
40
|
+
const api = loadGraphApiCached(ctx.inspection.projectRoot) ?? GraphQueryApi.fromStore(ctx.inspection.projectRoot);
|
|
40
41
|
const sym = resolveSymbol(api, target);
|
|
41
42
|
if (!sym) {
|
|
42
43
|
return {
|
|
@@ -48,13 +49,25 @@ export const getGraphCallersTool = {
|
|
|
48
49
|
},
|
|
49
50
|
};
|
|
50
51
|
}
|
|
51
|
-
const
|
|
52
|
+
const cwd = ctx.inspection.projectRoot;
|
|
53
|
+
const sites = mode === 'reference' ? api.referenceSitesOf(sym.id) : api.callerSitesOf(sym.id);
|
|
54
|
+
// Targeted staleness over the result files: drop callers whose file was
|
|
55
|
+
// deleted on disk, flag those whose content changed since indexing — so a
|
|
56
|
+
// stale index never silently serves a wrong/dead caller. Read-only.
|
|
57
|
+
const fresh = graphResultStaleness(api, cwd, [sym.path, ...sites.map((s) => s.node.path)]);
|
|
58
|
+
const live = sites.filter((s) => !s.node.path || !fresh.deletedSet.has(s.node.path));
|
|
59
|
+
const note = callGraphLanguageNote(api, sym);
|
|
52
60
|
const data = {
|
|
53
61
|
schema: 'sharkcraft.graph-callers/v1',
|
|
54
62
|
symbol: summarise(sym),
|
|
55
63
|
mode,
|
|
56
|
-
total:
|
|
57
|
-
callers:
|
|
64
|
+
total: live.length,
|
|
65
|
+
callers: live.slice(0, 200).map((s) => ({
|
|
66
|
+
...summarise(s.node),
|
|
67
|
+
...(s.line ? { line: s.line } : {}),
|
|
68
|
+
})),
|
|
69
|
+
...(note ? { note } : {}),
|
|
70
|
+
...(fresh.field ?? {}),
|
|
58
71
|
};
|
|
59
72
|
return { data: formatObjectArrays(data, input) };
|
|
60
73
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-graph-context.tool.d.ts","sourceRoot":"","sources":["../../src/tools/get-graph-context.tool.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"get-graph-context.tool.d.ts","sourceRoot":"","sources":["../../src/tools/get-graph-context.tool.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAUpE,eAAO,MAAM,mBAAmB,EAAE,eAsGjC,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { GraphQueryApi, GraphStore, NodeKind, } from '@shrkcrft/graph';
|
|
1
|
+
import { GraphQueryApi, GraphStore, NodeKind, loadGraphApiCached, } from '@shrkcrft/graph';
|
|
2
2
|
import { FORMAT_INPUT_PROPERTY, formatObjectArrays } from "../server/columnar-format.js";
|
|
3
|
+
import { dropDeleted, graphResultStaleness } from "./graph-staleness.js";
|
|
3
4
|
const NEXT = 'shrk graph index';
|
|
4
5
|
export const getGraphContextTool = {
|
|
5
6
|
name: 'get_graph_context',
|
|
@@ -31,7 +32,7 @@ export const getGraphContextTool = {
|
|
|
31
32
|
},
|
|
32
33
|
};
|
|
33
34
|
}
|
|
34
|
-
const api = GraphQueryApi.fromStore(ctx.inspection.projectRoot);
|
|
35
|
+
const api = loadGraphApiCached(ctx.inspection.projectRoot) ?? GraphQueryApi.fromStore(ctx.inspection.projectRoot);
|
|
35
36
|
const anchor = resolveAnchor(api, target);
|
|
36
37
|
if (!anchor) {
|
|
37
38
|
return {
|
|
@@ -43,24 +44,57 @@ export const getGraphContextTool = {
|
|
|
43
44
|
},
|
|
44
45
|
};
|
|
45
46
|
}
|
|
46
|
-
|
|
47
|
+
// A SYMBOL has no imports-file edges (those are file→file), so its import
|
|
48
|
+
// context is the DECLARING FILE's imports — compute neighbours on that file
|
|
49
|
+
// (mirrors the CLI; otherwise importsFrom/importedBy are wrongly empty).
|
|
50
|
+
const declaringFile = anchor.kind === NodeKind.Symbol
|
|
51
|
+
? api.declaringFileOf(anchor.id) ?? (anchor.path ? api.findFile(anchor.path) : undefined)
|
|
52
|
+
: undefined;
|
|
53
|
+
const subjectId = anchor.kind === NodeKind.File ? anchor.id : declaringFile?.id ?? anchor.id;
|
|
54
|
+
const neighbours = api.neighbours(subjectId);
|
|
47
55
|
const symbols = anchor.kind === NodeKind.File ? api.symbolsIn(anchor.id) : [];
|
|
56
|
+
// Who uses this symbol — references + calls (the CLI provides these; the MCP
|
|
57
|
+
// previously omitted them, returning a confidently-wrong "nothing uses this").
|
|
58
|
+
const referencedBy = anchor.kind === NodeKind.Symbol ? api.referencesOf(anchor.id) : [];
|
|
59
|
+
const calledBy = anchor.kind === NodeKind.Symbol ? api.callersOf(anchor.id) : [];
|
|
60
|
+
// Typed subtype/supertype edges (extends / implements) — the precise
|
|
61
|
+
// "who implements this interface" answer for a symbol anchor.
|
|
62
|
+
const subtypes = anchor.kind === NodeKind.Symbol ? api.subtypesOf(anchor.id) : [];
|
|
63
|
+
const supertypes = anchor.kind === NodeKind.Symbol ? api.supertypesOf(anchor.id) : [];
|
|
64
|
+
const importsFrom = neighbours.out
|
|
65
|
+
.filter((o) => o.edge.kind === 'imports-file')
|
|
66
|
+
.slice(0, 50)
|
|
67
|
+
.map((o) => 'resolved' in o.target
|
|
68
|
+
? { id: o.target.id, resolved: false }
|
|
69
|
+
: { ...summarise(o.target), resolved: true });
|
|
70
|
+
const importedBy = neighbours.in
|
|
71
|
+
.filter((i) => i.edge.kind === 'imports-file')
|
|
72
|
+
.slice(0, 50)
|
|
73
|
+
.map((i) => 'resolved' in i.source
|
|
74
|
+
? { id: i.source.id, resolved: false }
|
|
75
|
+
: { ...summarise(i.source), resolved: true });
|
|
76
|
+
const referencedByRows = referencedBy.slice(0, 50).map(summarise);
|
|
77
|
+
const calledByRows = calledBy.slice(0, 50).map(summarise);
|
|
78
|
+
// Drop imports/refs to/from files deleted on disk; flag the rest if changed.
|
|
79
|
+
const fresh = graphResultStaleness(api, ctx.inspection.projectRoot, [
|
|
80
|
+
anchor.path,
|
|
81
|
+
...importsFrom.map((x) => ('path' in x ? x.path : undefined)),
|
|
82
|
+
...importedBy.map((x) => ('path' in x ? x.path : undefined)),
|
|
83
|
+
...referencedByRows.map((x) => x.path),
|
|
84
|
+
...calledByRows.map((x) => x.path),
|
|
85
|
+
]);
|
|
48
86
|
const data = {
|
|
49
87
|
schema: 'sharkcraft.graph-context/v1',
|
|
50
88
|
anchor: summarise(anchor),
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
.map((o) => ('resolved' in o.target
|
|
55
|
-
? { id: o.target.id, resolved: false }
|
|
56
|
-
: { ...summarise(o.target), resolved: true })),
|
|
57
|
-
importedBy: neighbours.in
|
|
58
|
-
.filter((i) => i.edge.kind === 'imports-file')
|
|
59
|
-
.slice(0, 50)
|
|
60
|
-
.map((i) => ('resolved' in i.source
|
|
61
|
-
? { id: i.source.id, resolved: false }
|
|
62
|
-
: { ...summarise(i.source), resolved: true })),
|
|
89
|
+
...(declaringFile ? { declaredIn: summarise(declaringFile) } : {}),
|
|
90
|
+
importsFrom: dropDeleted(importsFrom, fresh.deletedSet),
|
|
91
|
+
importedBy: dropDeleted(importedBy, fresh.deletedSet),
|
|
63
92
|
symbols: symbols.slice(0, 50).map(summarise),
|
|
93
|
+
...(referencedByRows.length > 0 ? { referencedBy: dropDeleted(referencedByRows, fresh.deletedSet) } : {}),
|
|
94
|
+
...(calledByRows.length > 0 ? { calledBy: dropDeleted(calledByRows, fresh.deletedSet) } : {}),
|
|
95
|
+
...(subtypes.length > 0 ? { subtypes: subtypes.slice(0, 50).map(summarise) } : {}),
|
|
96
|
+
...(supertypes.length > 0 ? { supertypes: supertypes.slice(0, 50).map(summarise) } : {}),
|
|
97
|
+
...(fresh.field ?? {}),
|
|
64
98
|
};
|
|
65
99
|
return { data: formatObjectArrays(data, input) };
|
|
66
100
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GraphQueryApi, GraphStore } from '@shrkcrft/graph';
|
|
1
|
+
import { GraphQueryApi, GraphStore, loadGraphApiCached } from '@shrkcrft/graph';
|
|
2
2
|
import { FORMAT_INPUT_PROPERTY, formatObjectArrays } from "../server/columnar-format.js";
|
|
3
3
|
const NEXT = 'shrk graph index';
|
|
4
4
|
/**
|
|
@@ -40,7 +40,7 @@ export const getGraphCyclesTool = {
|
|
|
40
40
|
},
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
|
-
const api = GraphQueryApi.fromStore(ctx.inspection.projectRoot);
|
|
43
|
+
const api = loadGraphApiCached(ctx.inspection.projectRoot) ?? GraphQueryApi.fromStore(ctx.inspection.projectRoot);
|
|
44
44
|
const all = api.cycles();
|
|
45
45
|
const filtered = all.filter((c) => c.size >= minSize);
|
|
46
46
|
const limited = filtered.slice(0, rawLimit);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EdgeKind, GraphQueryApi, GraphStore } from '@shrkcrft/graph';
|
|
1
|
+
import { EdgeKind, GraphQueryApi, GraphStore, loadGraphApiCached } from '@shrkcrft/graph';
|
|
2
2
|
const NEXT = 'shrk graph index';
|
|
3
3
|
/**
|
|
4
4
|
* Read-only MCP mirror of `shrk graph deps`. Returns the workspace
|
|
@@ -41,7 +41,7 @@ export const getGraphDepsTool = {
|
|
|
41
41
|
},
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
|
-
const api = GraphQueryApi.fromStore(ctx.inspection.projectRoot);
|
|
44
|
+
const api = loadGraphApiCached(ctx.inspection.projectRoot) ?? GraphQueryApi.fromStore(ctx.inspection.projectRoot);
|
|
45
45
|
const pkgId = `package:${target}`;
|
|
46
46
|
const pkgNode = api.neighbours(pkgId)?.node;
|
|
47
47
|
if (!pkgNode) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-graph-hubs.tool.d.ts","sourceRoot":"","sources":["../../src/tools/get-graph-hubs.tool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAWpE,eAAO,MAAM,gBAAgB,EAAE,eA4C9B,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { GraphQueryApi, GraphStore, loadGraphApiCached } from '@shrkcrft/graph';
|
|
2
|
+
import { FORMAT_INPUT_PROPERTY, formatObjectArrays } from "../server/columnar-format.js";
|
|
3
|
+
const NEXT = 'shrk graph index';
|
|
4
|
+
export const getGraphHubsTool = {
|
|
5
|
+
name: 'get_graph_hubs',
|
|
6
|
+
description: 'The most-depended-on code: symbols ranked by how many DISTINCT files reference them, files by how many import them. The "load-bearing code" to change most carefully (biggest blast radius) and understand first when onboarding — the companion to get_graph_impact. Pass `path` (e.g. "packages/foo") to scope to one subsystem. Read-only; needs `shrk graph index`.',
|
|
7
|
+
cliCommand: 'graph hubs',
|
|
8
|
+
inputSchema: {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
limit: { type: 'number' },
|
|
12
|
+
path: { type: 'string' },
|
|
13
|
+
...FORMAT_INPUT_PROPERTY,
|
|
14
|
+
},
|
|
15
|
+
additionalProperties: false,
|
|
16
|
+
},
|
|
17
|
+
handler(input, ctx) {
|
|
18
|
+
const args = input;
|
|
19
|
+
const limit = clampLimit(args.limit);
|
|
20
|
+
const projectRoot = ctx.inspection.projectRoot;
|
|
21
|
+
const store = new GraphStore(projectRoot);
|
|
22
|
+
if (!store.exists()) {
|
|
23
|
+
return {
|
|
24
|
+
isError: true,
|
|
25
|
+
error: {
|
|
26
|
+
code: 'graph-missing',
|
|
27
|
+
message: `Code-intelligence index is missing. Run '${NEXT}'.`,
|
|
28
|
+
details: { nextCommand: NEXT },
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const api = loadGraphApiCached(projectRoot) ?? GraphQueryApi.fromStore(projectRoot);
|
|
33
|
+
const pathScope = typeof args.path === 'string' && args.path.trim().length > 0 ? args.path.trim() : undefined;
|
|
34
|
+
const hubs = api.topHubs(limit, pathScope);
|
|
35
|
+
const row = (h) => ({
|
|
36
|
+
...summarise(h.node),
|
|
37
|
+
inDegree: h.inDegree,
|
|
38
|
+
});
|
|
39
|
+
const data = {
|
|
40
|
+
schema: 'sharkcraft.graph-hubs/v1',
|
|
41
|
+
...(pathScope ? { path: pathScope } : {}),
|
|
42
|
+
symbols: hubs.symbols.map(row),
|
|
43
|
+
files: hubs.files.map(row),
|
|
44
|
+
};
|
|
45
|
+
return { data: formatObjectArrays(data, input) };
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
function clampLimit(raw) {
|
|
49
|
+
if (typeof raw !== 'number' || !Number.isFinite(raw))
|
|
50
|
+
return 15;
|
|
51
|
+
return Math.max(1, Math.min(100, Math.floor(raw)));
|
|
52
|
+
}
|
|
53
|
+
function summarise(n) {
|
|
54
|
+
return {
|
|
55
|
+
id: n.id,
|
|
56
|
+
kind: n.kind,
|
|
57
|
+
label: n.label,
|
|
58
|
+
...(n.path ? { path: n.path } : {}),
|
|
59
|
+
...(n.line ? { line: n.line } : {}),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-graph-impact.tool.d.ts","sourceRoot":"","sources":["../../src/tools/get-graph-impact.tool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"get-graph-impact.tool.d.ts","sourceRoot":"","sources":["../../src/tools/get-graph-impact.tool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAYpE,eAAO,MAAM,kBAAkB,EAAE,eA4EhC,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { GraphQueryApi, GraphStore } from '@shrkcrft/graph';
|
|
1
|
+
import { GraphQueryApi, GraphStore, loadGraphApiCached } from '@shrkcrft/graph';
|
|
2
2
|
import { FORMAT_INPUT_PROPERTY, formatObjectArrays } from "../server/columnar-format.js";
|
|
3
|
+
import { dropDeleted, graphResultStaleness } from "./graph-staleness.js";
|
|
3
4
|
const NEXT = 'shrk graph index';
|
|
4
5
|
export const getGraphImpactTool = {
|
|
5
6
|
name: 'get_graph_impact',
|
|
@@ -38,7 +39,7 @@ export const getGraphImpactTool = {
|
|
|
38
39
|
},
|
|
39
40
|
};
|
|
40
41
|
}
|
|
41
|
-
const api = GraphQueryApi.fromStore(ctx.inspection.projectRoot);
|
|
42
|
+
const api = loadGraphApiCached(ctx.inspection.projectRoot) ?? GraphQueryApi.fromStore(ctx.inspection.projectRoot);
|
|
42
43
|
const anchor = resolveAnchor(api, target);
|
|
43
44
|
if (!anchor) {
|
|
44
45
|
return {
|
|
@@ -50,20 +51,29 @@ export const getGraphImpactTool = {
|
|
|
50
51
|
},
|
|
51
52
|
};
|
|
52
53
|
}
|
|
53
|
-
const closure = reverseClosure(api, anchor
|
|
54
|
+
const closure = reverseClosure(api, anchor, maxDepth, limit);
|
|
54
55
|
const direct = closure.layer[1] ?? [];
|
|
55
56
|
const transitive = closure.all.filter((id) => id !== anchor.id && !direct.includes(id));
|
|
57
|
+
const directNodes = direct.map((id) => summarise(api.neighbours(id).node));
|
|
58
|
+
const transitiveNodes = transitive.slice(0, limit).map((id) => summarise(api.neighbours(id).node));
|
|
59
|
+
// Targeted staleness over the blast-radius files: drop dependents whose
|
|
60
|
+
// file was deleted (they can't break), flag those whose content changed —
|
|
61
|
+
// so a stale index never misroutes which tests/files the agent trusts.
|
|
62
|
+
const fresh = graphResultStaleness(api, ctx.inspection.projectRoot, [
|
|
63
|
+
anchor.path,
|
|
64
|
+
...directNodes.map((n) => n.path),
|
|
65
|
+
...transitiveNodes.map((n) => n.path),
|
|
66
|
+
]);
|
|
56
67
|
const data = {
|
|
57
68
|
schema: 'sharkcraft.graph-impact/v1',
|
|
58
69
|
anchor: summarise(anchor),
|
|
59
70
|
maxDepth,
|
|
60
71
|
limit,
|
|
61
72
|
truncated: closure.truncated,
|
|
62
|
-
directDependents:
|
|
63
|
-
transitiveDependents:
|
|
64
|
-
.slice(0, limit)
|
|
65
|
-
.map((id) => summarise(api.neighbours(id).node)),
|
|
73
|
+
directDependents: dropDeleted(directNodes, fresh.deletedSet),
|
|
74
|
+
transitiveDependents: dropDeleted(transitiveNodes, fresh.deletedSet),
|
|
66
75
|
totalReached: closure.all.length - 1,
|
|
76
|
+
...(fresh.field ?? {}),
|
|
67
77
|
};
|
|
68
78
|
return { data: formatObjectArrays(data, input) };
|
|
69
79
|
},
|
|
@@ -83,13 +93,23 @@ function resolveAnchor(api, target) {
|
|
|
83
93
|
return syms[0];
|
|
84
94
|
return undefined;
|
|
85
95
|
}
|
|
86
|
-
function reverseClosure(api,
|
|
87
|
-
const seen = new Set([
|
|
96
|
+
function reverseClosure(api, anchor, maxDepth, limit) {
|
|
97
|
+
const seen = new Set([anchor.id]);
|
|
88
98
|
const layer = {};
|
|
89
|
-
let frontier = [startId];
|
|
90
|
-
let depth = 1;
|
|
91
99
|
let truncated = false;
|
|
92
|
-
|
|
100
|
+
// Layer 1 uses the anchor-kind-aware direct dependents (importersOf alone
|
|
101
|
+
// returns NOTHING for a symbol anchor — symbols have no import edges).
|
|
102
|
+
let frontier = directDependents(api, anchor).filter((id) => !seen.has(id));
|
|
103
|
+
if (frontier.length > limit) {
|
|
104
|
+
frontier = frontier.slice(0, limit);
|
|
105
|
+
truncated = true;
|
|
106
|
+
}
|
|
107
|
+
for (const id of frontier)
|
|
108
|
+
seen.add(id);
|
|
109
|
+
if (frontier.length > 0)
|
|
110
|
+
layer[1] = frontier;
|
|
111
|
+
let depth = 2;
|
|
112
|
+
while (depth <= maxDepth && frontier.length > 0 && !truncated) {
|
|
93
113
|
const next = [];
|
|
94
114
|
for (const id of frontier) {
|
|
95
115
|
for (const imp of api.importersOf(id)) {
|
|
@@ -109,11 +129,13 @@ function reverseClosure(api, startId, maxDepth, limit) {
|
|
|
109
129
|
layer[depth] = next;
|
|
110
130
|
frontier = next;
|
|
111
131
|
depth += 1;
|
|
112
|
-
if (truncated)
|
|
113
|
-
break;
|
|
114
132
|
}
|
|
115
133
|
return { all: [...seen], layer, truncated };
|
|
116
134
|
}
|
|
135
|
+
/** Kind-aware direct dependents — the shared `GraphQueryApi` implementation. */
|
|
136
|
+
function directDependents(api, anchor) {
|
|
137
|
+
return api.directDependentsOf(anchor).map((n) => n.id);
|
|
138
|
+
}
|
|
117
139
|
function summarise(n) {
|
|
118
140
|
return {
|
|
119
141
|
id: n.id,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-graph-path.tool.d.ts","sourceRoot":"","sources":["../../src/tools/get-graph-path.tool.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAYpE,eAAO,MAAM,gBAAgB,EAAE,eAgG9B,CAAC"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { EdgeKind, GraphQueryApi, GraphStore, loadGraphApiCached, NodeKind, } from '@shrkcrft/graph';
|
|
2
|
+
import { FORMAT_INPUT_PROPERTY, formatObjectArrays } from "../server/columnar-format.js";
|
|
3
|
+
import { callGraphLanguageNote, graphResultStaleness } from "./graph-staleness.js";
|
|
4
|
+
const NEXT = 'shrk graph index';
|
|
5
|
+
export const getGraphPathTool = {
|
|
6
|
+
name: 'get_graph_path',
|
|
7
|
+
description: 'Is code A actually wired to code B? Returns the shortest directed CODE path (import/call/reference/declare/re-export/extends/implements edges) from `from` to `to`, hop by hop with edge kind and call-site line — the deterministic answer to "is X wired to Y" that grep cannot give. If A does not reach B it also checks B→A and reports the direction. Each endpoint is a file path or a symbol name. Read-only; needs `shrk graph index`.',
|
|
8
|
+
cliCommand: 'graph path',
|
|
9
|
+
inputSchema: {
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
from: { type: 'string' },
|
|
13
|
+
to: { type: 'string' },
|
|
14
|
+
maxDepth: { type: 'number' },
|
|
15
|
+
...FORMAT_INPUT_PROPERTY,
|
|
16
|
+
},
|
|
17
|
+
required: ['from', 'to'],
|
|
18
|
+
additionalProperties: false,
|
|
19
|
+
},
|
|
20
|
+
handler(input, ctx) {
|
|
21
|
+
const args = input;
|
|
22
|
+
const fromArg = (args.from ?? '').trim();
|
|
23
|
+
const toArg = (args.to ?? '').trim();
|
|
24
|
+
if (!fromArg || !toArg) {
|
|
25
|
+
return { isError: true, error: { code: 'invalid-input', message: 'from and to are required' } };
|
|
26
|
+
}
|
|
27
|
+
const maxDepth = clampDepth(args.maxDepth);
|
|
28
|
+
const cwd = ctx.inspection.projectRoot;
|
|
29
|
+
const store = new GraphStore(cwd);
|
|
30
|
+
if (!store.exists()) {
|
|
31
|
+
return {
|
|
32
|
+
isError: true,
|
|
33
|
+
error: {
|
|
34
|
+
code: 'graph-missing',
|
|
35
|
+
message: `Code-intelligence index is missing. Run '${NEXT}'.`,
|
|
36
|
+
details: { nextCommand: NEXT },
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const api = loadGraphApiCached(cwd) ?? GraphQueryApi.fromStore(cwd);
|
|
41
|
+
const from = resolveAnchor(api, fromArg);
|
|
42
|
+
const to = resolveAnchor(api, toArg);
|
|
43
|
+
if (!from || !to) {
|
|
44
|
+
const missing = !from ? fromArg : toArg;
|
|
45
|
+
return {
|
|
46
|
+
isError: true,
|
|
47
|
+
error: { code: 'not-found', message: `No graph node matched "${missing}".`, details: { target: missing } },
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
// A symbol has no OUTGOING code edges (references are recorded file→symbol),
|
|
51
|
+
// so trace from its declaring file. The target may stay a symbol.
|
|
52
|
+
const fromStart = bfsStartNode(api, from);
|
|
53
|
+
const toStart = bfsStartNode(api, to);
|
|
54
|
+
const forward = api.pathBetween(fromStart.id, to.id, { maxDepth });
|
|
55
|
+
const reverse = forward.found ? null : api.pathBetween(toStart.id, from.id, { maxDepth });
|
|
56
|
+
const direction = forward.found
|
|
57
|
+
? 'forward'
|
|
58
|
+
: reverse?.found
|
|
59
|
+
? 'reverse'
|
|
60
|
+
: 'none';
|
|
61
|
+
const chosen = forward.found ? forward : reverse?.found ? reverse : forward;
|
|
62
|
+
const startEndpoint = direction === 'reverse' ? to : from;
|
|
63
|
+
const startFile = direction === 'reverse' ? toStart : fromStart;
|
|
64
|
+
const startNote = direction !== 'none' && startFile.id !== startEndpoint.id && startEndpoint.kind === NodeKind.Symbol
|
|
65
|
+
? `\`${startEndpoint.label}\` is declared in ${startFile.path ?? startFile.id}; path traced from that file (per-symbol out-edges are not tracked).`
|
|
66
|
+
: undefined;
|
|
67
|
+
const hops = chosen.hops.map((h) => ({
|
|
68
|
+
from: h.from.path ?? h.from.id,
|
|
69
|
+
to: h.to.path ?? h.to.id,
|
|
70
|
+
kind: h.kind,
|
|
71
|
+
label: h.to.label,
|
|
72
|
+
...(h.line ? { line: h.line } : {}),
|
|
73
|
+
}));
|
|
74
|
+
const fresh = graphResultStaleness(api, cwd, [
|
|
75
|
+
from.path,
|
|
76
|
+
to.path,
|
|
77
|
+
...chosen.hops.map((h) => h.from.path),
|
|
78
|
+
...chosen.hops.map((h) => h.to.path),
|
|
79
|
+
]);
|
|
80
|
+
// A no-path answer between non-TS endpoints may just be missing call edges.
|
|
81
|
+
const langNote = direction === 'none' ? callGraphLanguageNote(api, from) ?? callGraphLanguageNote(api, to) : undefined;
|
|
82
|
+
const data = {
|
|
83
|
+
schema: 'sharkcraft.graph-path/v1',
|
|
84
|
+
from: summarise(from),
|
|
85
|
+
to: summarise(to),
|
|
86
|
+
found: direction !== 'none',
|
|
87
|
+
direction,
|
|
88
|
+
...(direction !== 'none' && startFile.id !== startEndpoint.id ? { tracedFrom: summarise(startFile) } : {}),
|
|
89
|
+
hops,
|
|
90
|
+
hopCount: hops.length,
|
|
91
|
+
explored: forward.found ? forward.explored : reverse?.explored ?? forward.explored,
|
|
92
|
+
...(direction === 'none' && chosen.reason ? { reason: chosen.reason } : {}),
|
|
93
|
+
...(startNote ?? langNote ? { note: startNote ?? langNote } : {}),
|
|
94
|
+
...(fresh.field ?? {}),
|
|
95
|
+
};
|
|
96
|
+
return { data: formatObjectArrays(data, input) };
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
function clampDepth(raw) {
|
|
100
|
+
if (typeof raw !== 'number' || !Number.isFinite(raw))
|
|
101
|
+
return 16;
|
|
102
|
+
return Math.max(1, Math.min(32, Math.floor(raw)));
|
|
103
|
+
}
|
|
104
|
+
function resolveAnchor(api, target) {
|
|
105
|
+
const direct = api.neighbours(target);
|
|
106
|
+
if (direct)
|
|
107
|
+
return direct.node;
|
|
108
|
+
if (target.startsWith('file:') || target.startsWith('symbol:') || target.startsWith('package:')) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
const f = api.findFile(target);
|
|
112
|
+
if (f)
|
|
113
|
+
return f;
|
|
114
|
+
const syms = api.findSymbol(target, { exact: true, limit: 1 });
|
|
115
|
+
if (syms.length > 0)
|
|
116
|
+
return syms[0];
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
/** A file is its own BFS start; a symbol resolves to its declaring file. */
|
|
120
|
+
function bfsStartNode(api, node) {
|
|
121
|
+
if (node.kind !== NodeKind.Symbol)
|
|
122
|
+
return node;
|
|
123
|
+
const neighbours = api.neighbours(node.id);
|
|
124
|
+
if (neighbours) {
|
|
125
|
+
for (const incoming of neighbours.in) {
|
|
126
|
+
if (incoming.edge.kind !== EdgeKind.DeclaresSymbol)
|
|
127
|
+
continue;
|
|
128
|
+
if ('resolved' in incoming.source)
|
|
129
|
+
continue;
|
|
130
|
+
if (incoming.source.kind === NodeKind.File)
|
|
131
|
+
return incoming.source;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return (node.path ? api.findFile(node.path) : undefined) ?? node;
|
|
135
|
+
}
|
|
136
|
+
function summarise(n) {
|
|
137
|
+
return {
|
|
138
|
+
id: n.id,
|
|
139
|
+
kind: n.kind,
|
|
140
|
+
label: n.label,
|
|
141
|
+
...(n.path ? { path: n.path } : {}),
|
|
142
|
+
...(n.line ? { line: n.line } : {}),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-graph-search.tool.d.ts","sourceRoot":"","sources":["../../src/tools/get-graph-search.tool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAYpE,eAAO,MAAM,kBAAkB,EAAE,
|
|
1
|
+
{"version":3,"file":"get-graph-search.tool.d.ts","sourceRoot":"","sources":["../../src/tools/get-graph-search.tool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAYpE,eAAO,MAAM,kBAAkB,EAAE,eA8EhC,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GraphQueryApi, GraphStore } from '@shrkcrft/graph';
|
|
1
|
+
import { GraphQueryApi, GraphStore, loadGraphApiCached } from '@shrkcrft/graph';
|
|
2
2
|
import { FORMAT_INPUT_PROPERTY, formatObjectArrays } from "../server/columnar-format.js";
|
|
3
3
|
const NEXT = 'shrk graph index';
|
|
4
4
|
export const getGraphSearchTool = {
|
|
@@ -38,15 +38,34 @@ export const getGraphSearchTool = {
|
|
|
38
38
|
},
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
|
-
const api = GraphQueryApi.fromStore(ctx.inspection.projectRoot);
|
|
41
|
+
const api = loadGraphApiCached(ctx.inspection.projectRoot) ?? GraphQueryApi.fromStore(ctx.inspection.projectRoot);
|
|
42
|
+
const exact = args.exact ?? false;
|
|
42
43
|
const matches = [];
|
|
43
44
|
if (!args.kind || args.kind === 'file') {
|
|
44
45
|
const f = api.findFile(query);
|
|
45
46
|
if (f)
|
|
46
47
|
matches.push(f);
|
|
48
|
+
// Fuzzy fallback (mirrors the CLI): substring match on path/basename so a
|
|
49
|
+
// bare name like `Foo` finds `packages/x/Foo.ts` without the full path —
|
|
50
|
+
// otherwise the MCP returned an empty list where the CLI found the file.
|
|
51
|
+
if (!exact && matches.length < limit) {
|
|
52
|
+
const q = query.toLowerCase();
|
|
53
|
+
const seen = new Set(matches.map((n) => n.id));
|
|
54
|
+
for (const node of api.allFiles()) {
|
|
55
|
+
if (seen.has(node.id))
|
|
56
|
+
continue;
|
|
57
|
+
const p = node.path?.toLowerCase() ?? '';
|
|
58
|
+
const base = p.includes('/') ? p.slice(p.lastIndexOf('/') + 1) : p;
|
|
59
|
+
if (base.includes(q) || p.includes(q)) {
|
|
60
|
+
matches.push(node);
|
|
61
|
+
seen.add(node.id);
|
|
62
|
+
if (matches.length >= limit)
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
47
67
|
}
|
|
48
68
|
if (!args.kind || args.kind === 'symbol') {
|
|
49
|
-
const exact = args.exact ?? false;
|
|
50
69
|
for (const s of api.findSymbol(query, { exact, limit }))
|
|
51
70
|
matches.push(s);
|
|
52
71
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { IToolDefinition } from '../server/tool-definition.js';
|
|
2
2
|
/**
|
|
3
3
|
* Read-only status for the on-disk code graph. Returns
|
|
4
|
-
* { state: 'fresh' | 'corrupt' | 'missing' } and counters.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* { state: 'fresh' | 'stale' | 'corrupt' | 'missing' } and counters.
|
|
5
|
+
* `corrupt` (store self-integrity) and `stale` (files changed on disk since
|
|
6
|
+
* indexing) are orthogonal — a store can be digest-valid yet stale — so
|
|
7
|
+
* precedence is corrupt > stale > fresh. On 'missing', `isError` +
|
|
8
|
+
* `nextCommand` direct the caller to refresh.
|
|
7
9
|
*/
|
|
8
10
|
export declare const getGraphStatusTool: IToolDefinition;
|
|
9
11
|
//# sourceMappingURL=get-graph-status.tool.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-graph-status.tool.d.ts","sourceRoot":"","sources":["../../src/tools/get-graph-status.tool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAKpE
|
|
1
|
+
{"version":3,"file":"get-graph-status.tool.d.ts","sourceRoot":"","sources":["../../src/tools/get-graph-status.tool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAKpE;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,EAAE,eA4ChC,CAAC"}
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import { GraphStore } from '@shrkcrft/graph';
|
|
1
|
+
import { detectGraphFreshness, GraphStore } from '@shrkcrft/graph';
|
|
2
2
|
const NEXT = 'shrk graph index';
|
|
3
3
|
const STALE_HINT = `Code-intelligence index is missing or stale. Run '${NEXT}' to build it.`;
|
|
4
4
|
/**
|
|
5
5
|
* Read-only status for the on-disk code graph. Returns
|
|
6
|
-
* { state: 'fresh' | 'corrupt' | 'missing' } and counters.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* { state: 'fresh' | 'stale' | 'corrupt' | 'missing' } and counters.
|
|
7
|
+
* `corrupt` (store self-integrity) and `stale` (files changed on disk since
|
|
8
|
+
* indexing) are orthogonal — a store can be digest-valid yet stale — so
|
|
9
|
+
* precedence is corrupt > stale > fresh. On 'missing', `isError` +
|
|
10
|
+
* `nextCommand` direct the caller to refresh.
|
|
9
11
|
*/
|
|
10
12
|
export const getGraphStatusTool = {
|
|
11
13
|
name: 'get_graph_status',
|
|
12
|
-
description: 'Read-only status of the SharkCraft code-intelligence graph: file/node/edge counts,
|
|
14
|
+
description: 'Read-only status of the SharkCraft code-intelligence graph: state (fresh/stale/corrupt/missing), file/node/edge counts, and how many files changed/added/deleted since indexing. Returns nextCommand when stale or missing so the agent knows to refresh before trusting graph answers.',
|
|
13
15
|
cliCommand: 'graph status',
|
|
14
16
|
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
15
17
|
handler(_input, ctx) {
|
|
@@ -26,10 +28,13 @@ export const getGraphStatusTool = {
|
|
|
26
28
|
}
|
|
27
29
|
const verify = store.verifyDigest();
|
|
28
30
|
const snap = store.loadSnapshot();
|
|
31
|
+
const fresh = detectGraphFreshness(ctx.inspection.projectRoot);
|
|
32
|
+
const behind = fresh.modified.length + fresh.added.length + fresh.deleted.length;
|
|
33
|
+
const state = !verify.ok ? 'corrupt' : behind > 0 ? 'stale' : 'fresh';
|
|
29
34
|
return {
|
|
30
35
|
data: {
|
|
31
36
|
schema: snap.manifest.schema,
|
|
32
|
-
state
|
|
37
|
+
state,
|
|
33
38
|
digestOk: verify.ok,
|
|
34
39
|
fileCount: snap.manifest.filesIndexed,
|
|
35
40
|
nodeCount: snap.nodes.size,
|
|
@@ -39,6 +44,10 @@ export const getGraphStatusTool = {
|
|
|
39
44
|
workspacePackages: snap.manifest.workspacePackages,
|
|
40
45
|
lastIndexedAt: snap.manifest.lastIndexedAt,
|
|
41
46
|
lastIndexDurationMs: snap.manifest.lastIndexDurationMs,
|
|
47
|
+
modifiedSinceIndex: fresh.modified.length,
|
|
48
|
+
newSinceIndex: fresh.added.length,
|
|
49
|
+
deletedSinceIndex: fresh.deleted.length,
|
|
50
|
+
...(behind > 0 ? { nextCommand: 'shrk graph index --changed' } : {}),
|
|
42
51
|
...(verify.ok ? {} : { expectedDigest: verify.expected, actualDigest: verify.actual }),
|
|
43
52
|
},
|
|
44
53
|
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type GraphQueryApi, type INode } from '@shrkcrft/graph';
|
|
2
|
+
/**
|
|
3
|
+
* A note when a symbol's file language has no call-graph extraction (Go/Python/
|
|
4
|
+
* Java/…) — only TS/JS build call/reference edges — so an empty caller/usage
|
|
5
|
+
* result isn't read as "nothing calls it". Returns undefined for TS/JS.
|
|
6
|
+
*/
|
|
7
|
+
export declare function callGraphLanguageNote(api: GraphQueryApi, sym: INode): string | undefined;
|
|
8
|
+
export declare const GRAPH_STALE_HINT = "Result files changed since indexing \u2014 run `shrk graph index --changed` for fresh results.";
|
|
9
|
+
export interface IGraphStaleSurface {
|
|
10
|
+
/** Result file paths deleted on disk — drop entries whose `path` is in this set. */
|
|
11
|
+
deletedSet: ReadonlySet<string>;
|
|
12
|
+
/** Spread into the tool `data` object; null when every result file is fresh. */
|
|
13
|
+
field: {
|
|
14
|
+
stale: {
|
|
15
|
+
modified: readonly string[];
|
|
16
|
+
deleted: readonly string[];
|
|
17
|
+
};
|
|
18
|
+
staleHint: string;
|
|
19
|
+
} | null;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Targeted, read-only staleness over a query's result file paths. The graph
|
|
23
|
+
* MCP tools use it to DROP deleted result files and FLAG modified ones, so a
|
|
24
|
+
* stale index never silently serves a wrong/dead answer for a file the agent
|
|
25
|
+
* just edited. Cheap: stats only the handful of result files (mtime+size gate,
|
|
26
|
+
* sha1 only on mismatch) — never a whole-tree walk.
|
|
27
|
+
*/
|
|
28
|
+
export declare function graphResultStaleness(api: GraphQueryApi, cwd: string, paths: ReadonlyArray<string | undefined>): IGraphStaleSurface;
|
|
29
|
+
/** Filter out result entries whose file path was deleted on disk. */
|
|
30
|
+
export declare function dropDeleted<T extends {
|
|
31
|
+
id: string;
|
|
32
|
+
path?: string;
|
|
33
|
+
}>(rows: readonly T[], deletedSet: ReadonlySet<string>): T[];
|
|
34
|
+
//# sourceMappingURL=graph-staleness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-staleness.d.ts","sourceRoot":"","sources":["../../src/tools/graph-staleness.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,KAAK,aAAa,EAAE,KAAK,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAEzF;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,aAAa,EAAE,GAAG,EAAE,KAAK,GAAG,MAAM,GAAG,SAAS,CAKxF;AAED,eAAO,MAAM,gBAAgB,mGACgE,CAAC;AAE9F,MAAM,WAAW,kBAAkB;IACjC,oFAAoF;IACpF,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAChC,gFAAgF;IAChF,KAAK,EACD;QAAE,KAAK,EAAE;YAAE,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;YAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;SAAE,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GACzF,IAAI,CAAC;CACV;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,aAAa,EAClB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,aAAa,CAAC,MAAM,GAAG,SAAS,CAAC,GACvC,kBAAkB,CAUpB;AAED,qEAAqE;AACrE,wBAAgB,WAAW,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,EACjE,IAAI,EAAE,SAAS,CAAC,EAAE,EAClB,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,GAC9B,CAAC,EAAE,CAEL"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { hasCallGraphReferences } from '@shrkcrft/graph';
|
|
2
|
+
/**
|
|
3
|
+
* A note when a symbol's file language has no call-graph extraction (Go/Python/
|
|
4
|
+
* Java/…) — only TS/JS build call/reference edges — so an empty caller/usage
|
|
5
|
+
* result isn't read as "nothing calls it". Returns undefined for TS/JS.
|
|
6
|
+
*/
|
|
7
|
+
export function callGraphLanguageNote(api, sym) {
|
|
8
|
+
const file = sym.path ? api.findFile(sym.path) : undefined;
|
|
9
|
+
const lang = file?.data?.['language'];
|
|
10
|
+
if (hasCallGraphReferences(lang))
|
|
11
|
+
return undefined;
|
|
12
|
+
return `Call/reference edges are extracted for TS/JS only — \`${sym.label}\` is in a ${lang} file, so callers/usages are not tracked here (an empty result does NOT mean none).`;
|
|
13
|
+
}
|
|
14
|
+
export const GRAPH_STALE_HINT = 'Result files changed since indexing — run `shrk graph index --changed` for fresh results.';
|
|
15
|
+
/**
|
|
16
|
+
* Targeted, read-only staleness over a query's result file paths. The graph
|
|
17
|
+
* MCP tools use it to DROP deleted result files and FLAG modified ones, so a
|
|
18
|
+
* stale index never silently serves a wrong/dead answer for a file the agent
|
|
19
|
+
* just edited. Cheap: stats only the handful of result files (mtime+size gate,
|
|
20
|
+
* sha1 only on mismatch) — never a whole-tree walk.
|
|
21
|
+
*/
|
|
22
|
+
export function graphResultStaleness(api, cwd, paths) {
|
|
23
|
+
const rel = paths.filter((p) => !!p);
|
|
24
|
+
const stale = api.staleFilesAmong(cwd, rel);
|
|
25
|
+
const has = stale.modified.length > 0 || stale.deleted.length > 0;
|
|
26
|
+
return {
|
|
27
|
+
deletedSet: new Set(stale.deleted),
|
|
28
|
+
field: has
|
|
29
|
+
? { stale: { modified: stale.modified, deleted: stale.deleted }, staleHint: GRAPH_STALE_HINT }
|
|
30
|
+
: null,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/** Filter out result entries whose file path was deleted on disk. */
|
|
34
|
+
export function dropDeleted(rows, deletedSet) {
|
|
35
|
+
return rows.filter((r) => !r.path || !deletedSet.has(r.path));
|
|
36
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Primary MCP tools — the
|
|
2
|
+
* Primary MCP tools — the core set advertised to a connected agent
|
|
3
3
|
* by default. Every tool in {@link ALL_TOOLS} stays callable (so an
|
|
4
4
|
* agent that already knows the name can use it), but `tools/list`
|
|
5
5
|
* only advertises the primary set. Smaller surface = better
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"primary-tools.d.ts","sourceRoot":"","sources":["../../src/tools/primary-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,iBAAiB,EAAE,WAAW,CAAC,MAAM,
|
|
1
|
+
{"version":3,"file":"primary-tools.d.ts","sourceRoot":"","sources":["../../src/tools/primary-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,iBAAiB,EAAE,WAAW,CAAC,MAAM,CA4DhD,CAAC;AAEH;;;;GAIG;AACH,wBAAgB,0BAA0B,IAAI,OAAO,CAGpD"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Primary MCP tools — the
|
|
2
|
+
* Primary MCP tools — the core set advertised to a connected agent
|
|
3
3
|
* by default. Every tool in {@link ALL_TOOLS} stays callable (so an
|
|
4
4
|
* agent that already knows the name can use it), but `tools/list`
|
|
5
5
|
* only advertises the primary set. Smaller surface = better
|
|
@@ -54,6 +54,17 @@ export const PRIMARY_MCP_TOOLS = new Set([
|
|
|
54
54
|
// Doctor / readiness
|
|
55
55
|
'get_ai_readiness_report',
|
|
56
56
|
'doctor_packs',
|
|
57
|
+
// Code-intelligence graph — the agent's grep replacement for understanding
|
|
58
|
+
// existing code: who calls X, where is X used (path:line), what breaks if I
|
|
59
|
+
// change X, is X wired. Verifiable file:line truth from the indexed graph.
|
|
60
|
+
'get_graph_callers',
|
|
61
|
+
'code_find_usages',
|
|
62
|
+
'get_graph_impact',
|
|
63
|
+
'get_graph_path',
|
|
64
|
+
'get_graph_hubs',
|
|
65
|
+
'graph_why',
|
|
66
|
+
'get_graph_node',
|
|
67
|
+
'get_graph_search',
|
|
57
68
|
// Search
|
|
58
69
|
'search_all',
|
|
59
70
|
'search_knowledge',
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { buildStartHereReport, buildPrimaryCommandsReport, } from '@shrkcrft/inspector';
|
|
2
2
|
export const getStartHereTool = {
|
|
3
3
|
name: 'get_start_here',
|
|
4
|
-
description: 'Return the SharkCraft start-here flow list (30-second explanation +
|
|
4
|
+
description: 'Return the SharkCraft start-here flow list (30-second explanation + primary flows incl. "investigate existing code" + safety pledge). Read-only.',
|
|
5
5
|
inputSchema: {
|
|
6
6
|
type: 'object',
|
|
7
7
|
properties: {
|
|
8
8
|
flow: {
|
|
9
9
|
type: 'string',
|
|
10
|
-
enum: ['onboard', 'brief', 'dev', 'review', 'governance', 'packs', 'release'],
|
|
10
|
+
enum: ['onboard', 'investigate', 'brief', 'dev', 'review', 'governance', 'packs', 'release'],
|
|
11
11
|
},
|
|
12
12
|
},
|
|
13
13
|
additionalProperties: false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shrkcrft/mcp-server",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.18",
|
|
4
4
|
"description": "SharkCraft MCP server: 25 tools over @modelcontextprotocol/sdk's stdio transport.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "SharkCraft contributors",
|
|
@@ -44,32 +44,32 @@
|
|
|
44
44
|
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@shrkcrft/core": "^0.1.0-alpha.
|
|
48
|
-
"@shrkcrft/compress": "^0.1.0-alpha.
|
|
49
|
-
"@shrkcrft/config": "^0.1.0-alpha.
|
|
50
|
-
"@shrkcrft/workspace": "^0.1.0-alpha.
|
|
51
|
-
"@shrkcrft/knowledge": "^0.1.0-alpha.
|
|
52
|
-
"@shrkcrft/context": "^0.1.0-alpha.
|
|
53
|
-
"@shrkcrft/rules": "^0.1.0-alpha.
|
|
54
|
-
"@shrkcrft/paths": "^0.1.0-alpha.
|
|
55
|
-
"@shrkcrft/templates": "^0.1.0-alpha.
|
|
56
|
-
"@shrkcrft/pipelines": "^0.1.0-alpha.
|
|
57
|
-
"@shrkcrft/presets": "^0.1.0-alpha.
|
|
58
|
-
"@shrkcrft/boundaries": "^0.1.0-alpha.
|
|
59
|
-
"@shrkcrft/graph": "^0.1.0-alpha.
|
|
60
|
-
"@shrkcrft/rule-graph": "^0.1.0-alpha.
|
|
61
|
-
"@shrkcrft/structural-search": "^0.1.0-alpha.
|
|
62
|
-
"@shrkcrft/impact-engine": "^0.1.0-alpha.
|
|
63
|
-
"@shrkcrft/context-planner": "^0.1.0-alpha.
|
|
64
|
-
"@shrkcrft/architecture-guard": "^0.1.0-alpha.
|
|
65
|
-
"@shrkcrft/framework-scanners": "^0.1.0-alpha.
|
|
66
|
-
"@shrkcrft/api-surface-diff": "^0.1.0-alpha.
|
|
67
|
-
"@shrkcrft/quality-gates": "^0.1.0-alpha.
|
|
68
|
-
"@shrkcrft/migrate": "^0.1.0-alpha.
|
|
69
|
-
"@shrkcrft/packs": "^0.1.0-alpha.
|
|
70
|
-
"@shrkcrft/generator": "^0.1.0-alpha.
|
|
71
|
-
"@shrkcrft/inspector": "^0.1.0-alpha.
|
|
72
|
-
"@shrkcrft/embeddings": "^0.1.0-alpha.
|
|
47
|
+
"@shrkcrft/core": "^0.1.0-alpha.18",
|
|
48
|
+
"@shrkcrft/compress": "^0.1.0-alpha.18",
|
|
49
|
+
"@shrkcrft/config": "^0.1.0-alpha.18",
|
|
50
|
+
"@shrkcrft/workspace": "^0.1.0-alpha.18",
|
|
51
|
+
"@shrkcrft/knowledge": "^0.1.0-alpha.18",
|
|
52
|
+
"@shrkcrft/context": "^0.1.0-alpha.18",
|
|
53
|
+
"@shrkcrft/rules": "^0.1.0-alpha.18",
|
|
54
|
+
"@shrkcrft/paths": "^0.1.0-alpha.18",
|
|
55
|
+
"@shrkcrft/templates": "^0.1.0-alpha.18",
|
|
56
|
+
"@shrkcrft/pipelines": "^0.1.0-alpha.18",
|
|
57
|
+
"@shrkcrft/presets": "^0.1.0-alpha.18",
|
|
58
|
+
"@shrkcrft/boundaries": "^0.1.0-alpha.18",
|
|
59
|
+
"@shrkcrft/graph": "^0.1.0-alpha.18",
|
|
60
|
+
"@shrkcrft/rule-graph": "^0.1.0-alpha.18",
|
|
61
|
+
"@shrkcrft/structural-search": "^0.1.0-alpha.18",
|
|
62
|
+
"@shrkcrft/impact-engine": "^0.1.0-alpha.18",
|
|
63
|
+
"@shrkcrft/context-planner": "^0.1.0-alpha.18",
|
|
64
|
+
"@shrkcrft/architecture-guard": "^0.1.0-alpha.18",
|
|
65
|
+
"@shrkcrft/framework-scanners": "^0.1.0-alpha.18",
|
|
66
|
+
"@shrkcrft/api-surface-diff": "^0.1.0-alpha.18",
|
|
67
|
+
"@shrkcrft/quality-gates": "^0.1.0-alpha.18",
|
|
68
|
+
"@shrkcrft/migrate": "^0.1.0-alpha.18",
|
|
69
|
+
"@shrkcrft/packs": "^0.1.0-alpha.18",
|
|
70
|
+
"@shrkcrft/generator": "^0.1.0-alpha.18",
|
|
71
|
+
"@shrkcrft/inspector": "^0.1.0-alpha.18",
|
|
72
|
+
"@shrkcrft/embeddings": "^0.1.0-alpha.18",
|
|
73
73
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
74
74
|
"zod": "^3.25.0 || ^4.0.0"
|
|
75
75
|
},
|