@shrkcrft/mcp-server 0.1.0-alpha.16 → 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/server/fit-array-to-budget.d.ts +6 -2
- package/dist/server/fit-array-to-budget.d.ts.map +1 -1
- package/dist/server/fit-array-to-budget.js +45 -13
- 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/compress-context.tool.d.ts.map +1 -1
- package/dist/tools/compress-context.tool.js +3 -2
- 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 +8 -4
- 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/get-knowledge-graph.tool.js +1 -1
- 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
- package/dist/tools/r22-extras.tool.d.ts +0 -4
- package/dist/tools/r22-extras.tool.d.ts.map +0 -1
- package/dist/tools/r22-extras.tool.js +0 -42
- package/dist/tools/r26-ingest.tool.d.ts +0 -10
- package/dist/tools/r26-ingest.tool.d.ts.map +0 -1
- package/dist/tools/r26-ingest.tool.js +0 -174
- package/dist/tools/r34-search-unified.tool.d.ts +0 -3
- package/dist/tools/r34-search-unified.tool.d.ts.map +0 -1
- package/dist/tools/r34-search-unified.tool.js +0 -38
|
@@ -13,8 +13,12 @@ export interface IFittedArray {
|
|
|
13
13
|
* rest dropped) with the FULL original cached in `store` — the agent can
|
|
14
14
|
* `retrieve_original` with the returned `ccrKey`.
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
16
|
+
* `compressJson({ maxTokens })` owns the lossless-vs-lossy DECISION, but its
|
|
17
|
+
* sampler keep-count is not derived from the budget, so a single sample can
|
|
18
|
+
* still exceed `maxTokens`. We therefore binary-search the row cap so the
|
|
19
|
+
* emitted payload actually fits the budget (down to a 1-row floor — a single
|
|
20
|
+
* row's columnar envelope may still exceed a very small budget, which is the
|
|
21
|
+
* best achievable while keeping any data).
|
|
18
22
|
*/
|
|
19
23
|
export declare function fitArrayToBudget(array: readonly unknown[], maxTokens: number | undefined, store?: ICcrStore): IFittedArray;
|
|
20
24
|
//# sourceMappingURL=fit-array-to-budget.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fit-array-to-budget.d.ts","sourceRoot":"","sources":["../../src/server/fit-array-to-budget.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"fit-array-to-budget.d.ts","sourceRoot":"","sources":["../../src/server/fit-array-to-budget.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,oBAAoB,CAAC;AAE5B,MAAM,WAAW,YAAY;IAC3B,yEAAyE;IACzE,KAAK,EAAE,OAAO,CAAC;IACf,+EAA+E;IAC/E,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,SAAS,OAAO,EAAE,EACzB,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,KAAK,CAAC,EAAE,SAAS,GAChB,YAAY,CA2Cd"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { compactArrayToColumnar, compressJson } from '@shrkcrft/compress';
|
|
1
|
+
import { compactArrayToColumnar, compressJson, estimateTokens, EContentType, } from '@shrkcrft/compress';
|
|
2
2
|
/**
|
|
3
3
|
* Fit a homogeneous object array to an optional token budget (P5.2).
|
|
4
4
|
*
|
|
@@ -7,22 +7,54 @@ import { compactArrayToColumnar, compressJson } from '@shrkcrft/compress';
|
|
|
7
7
|
* rest dropped) with the FULL original cached in `store` — the agent can
|
|
8
8
|
* `retrieve_original` with the returned `ccrKey`.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* `compressJson({ maxTokens })` owns the lossless-vs-lossy DECISION, but its
|
|
11
|
+
* sampler keep-count is not derived from the budget, so a single sample can
|
|
12
|
+
* still exceed `maxTokens`. We therefore binary-search the row cap so the
|
|
13
|
+
* emitted payload actually fits the budget (down to a 1-row floor — a single
|
|
14
|
+
* row's columnar envelope may still exceed a very small budget, which is the
|
|
15
|
+
* best achievable while keeping any data).
|
|
12
16
|
*/
|
|
13
17
|
export function fitArrayToBudget(array, maxTokens, store) {
|
|
14
18
|
const columnar = compactArrayToColumnar(array) ?? array;
|
|
15
19
|
if (!maxTokens || maxTokens <= 0)
|
|
16
20
|
return { value: columnar };
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
const json = JSON.stringify(array);
|
|
22
|
+
const run = (maxItems) => {
|
|
23
|
+
const r = compressJson(json, {
|
|
24
|
+
maxTokens,
|
|
25
|
+
...(maxItems !== undefined ? { maxItems } : {}),
|
|
26
|
+
...(store ? { store } : {}),
|
|
27
|
+
});
|
|
28
|
+
// A CCR key is set only on the lossy sample path; under budget the result
|
|
29
|
+
// is the lossless form, so the caller falls back to the columnar value.
|
|
30
|
+
if (!r.ccrKey)
|
|
31
|
+
return null;
|
|
32
|
+
const firstLine = r.compressed.split('\n')[0] ?? 'null';
|
|
33
|
+
return { value: JSON.parse(firstLine), ccrKey: r.ccrKey };
|
|
34
|
+
};
|
|
35
|
+
const fits = (fitted) => estimateTokens(JSON.stringify(fitted.value), EContentType.JsonArray) <= maxTokens;
|
|
36
|
+
// Default sample (no cap). Null → under budget, emit the lossless form.
|
|
37
|
+
const initial = run();
|
|
38
|
+
if (!initial)
|
|
39
|
+
return { value: columnar };
|
|
40
|
+
if (fits(initial))
|
|
41
|
+
return initial;
|
|
42
|
+
// Largest row cap whose sampled payload still fits the budget.
|
|
43
|
+
let lo = 1;
|
|
44
|
+
let hi = array.length;
|
|
45
|
+
let best = null;
|
|
46
|
+
while (lo <= hi) {
|
|
47
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
48
|
+
const candidate = run(mid);
|
|
49
|
+
if (candidate && fits(candidate)) {
|
|
50
|
+
best = candidate;
|
|
51
|
+
lo = mid + 1;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
hi = mid - 1;
|
|
55
|
+
}
|
|
26
56
|
}
|
|
27
|
-
|
|
57
|
+
// Even one row over budget → keep the smallest sample (best effort, still
|
|
58
|
+
// recoverable via ccrKey) rather than the much larger default sample.
|
|
59
|
+
return best ?? run(1) ?? initial;
|
|
28
60
|
}
|
|
@@ -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
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compress-context.tool.d.ts","sourceRoot":"","sources":["../../src/tools/compress-context.tool.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAIpE;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,
|
|
1
|
+
{"version":3,"file":"compress-context.tool.d.ts","sourceRoot":"","sources":["../../src/tools/compress-context.tool.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAIpE;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,eA6EjC,CAAC"}
|
|
@@ -21,11 +21,12 @@ export const compressContextTool = {
|
|
|
21
21
|
description: 'Optional task/query text that biases which lines or matches are kept.',
|
|
22
22
|
},
|
|
23
23
|
maxItems: {
|
|
24
|
-
type: '
|
|
24
|
+
type: 'integer',
|
|
25
|
+
minimum: 1,
|
|
25
26
|
description: 'Soft cap on retained lines / matches / hunks (compressor-specific).',
|
|
26
27
|
},
|
|
27
28
|
maxTokens: {
|
|
28
|
-
type: '
|
|
29
|
+
type: 'integer',
|
|
29
30
|
minimum: 1,
|
|
30
31
|
description: 'Token budget for a JSON array. When set and the lossless columnar form still exceeds it, falls back to the lossy SmartCrusher row-sampler (kept rows + CCR original).',
|
|
31
32
|
},
|
|
@@ -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
|
/**
|
|
@@ -19,7 +19,7 @@ export const depsAuditTool = {
|
|
|
19
19
|
package: { type: 'string' },
|
|
20
20
|
...FORMAT_INPUT_PROPERTY,
|
|
21
21
|
maxTokens: {
|
|
22
|
-
type: '
|
|
22
|
+
type: 'integer',
|
|
23
23
|
minimum: 1,
|
|
24
24
|
description: 'Token budget for the per-package report list. When set and the columnar form still exceeds it, falls back to the lossy SmartCrusher row-sampler (full original cached — retrieve via the returned ccrKey).',
|
|
25
25
|
},
|
|
@@ -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) => {
|
|
@@ -238,6 +238,10 @@ function rootOfSpecifier(spec) {
|
|
|
238
238
|
function isBuiltinModule(spec) {
|
|
239
239
|
if (spec.startsWith('node:'))
|
|
240
240
|
return true;
|
|
241
|
+
// Bun runtime builtins (`bun:test`, `bun:sqlite`, …) are runtime-provided,
|
|
242
|
+
// never an npm dependency — so they are not "missing".
|
|
243
|
+
if (spec.startsWith('bun:'))
|
|
244
|
+
return true;
|
|
241
245
|
return new Set([
|
|
242
246
|
'fs', 'path', 'os', 'crypto', 'http', 'https', 'url', 'util', 'stream',
|
|
243
247
|
'events', 'child_process', 'process', 'buffer', 'querystring', 'zlib',
|
|
@@ -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"}
|