@danielblomma/cortex-mcp 2.1.4 → 2.2.0
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/README.md +53 -24
- package/bin/cortex.mjs +48 -21
- package/mcp-registry-submission.json +63 -0
- package/package.json +18 -5
- package/scaffold/AGENTS.md +13 -9
- package/scaffold/CLAUDE.md +13 -9
- package/scaffold/docs/architecture.md +1 -1
- package/scaffold/mcp/src/cli/query.ts +455 -0
- package/scaffold/mcp/tests/query-cli.test.mjs +115 -0
- package/scaffold/scripts/bootstrap.sh +3 -2
- package/scaffold/scripts/doctor.sh +9 -8
- package/scaffold/scripts/embed.sh +3 -2
- package/scaffold/scripts/load-ryu.sh +3 -2
- package/scaffold/scripts/memory-compile.mjs +5 -2
- package/scaffold/scripts/memory-lint.mjs +5 -2
- package/scaffold/scripts/parsers/node_modules/.package-lock.json +0 -56
- package/scaffold/scripts/parsers/node_modules/acorn/CHANGELOG.md +0 -972
- package/scaffold/scripts/parsers/node_modules/acorn/LICENSE +0 -21
- package/scaffold/scripts/parsers/node_modules/acorn/README.md +0 -301
- package/scaffold/scripts/parsers/node_modules/acorn/bin/acorn +0 -4
- package/scaffold/scripts/parsers/node_modules/acorn/dist/acorn.d.mts +0 -883
- package/scaffold/scripts/parsers/node_modules/acorn/dist/acorn.d.ts +0 -883
- package/scaffold/scripts/parsers/node_modules/acorn/dist/acorn.js +0 -6295
- package/scaffold/scripts/parsers/node_modules/acorn/dist/acorn.mjs +0 -6266
- package/scaffold/scripts/parsers/node_modules/acorn/dist/bin.js +0 -90
- package/scaffold/scripts/parsers/node_modules/acorn/package.json +0 -50
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/CHANGELOG.md +0 -421
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/LICENSE +0 -21
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/README.md +0 -81
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/error.d.ts +0 -103
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/error.js +0 -78
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/error.js.map +0 -1
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/decorators.d.ts +0 -167
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/decorators.js +0 -75
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/decorators.js.map +0 -1
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/import-assertions.d.ts +0 -177
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/import-assertions.js +0 -56
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/import-assertions.js.map +0 -1
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/index.d.ts +0 -198
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/index.js +0 -327
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/index.js.map +0 -1
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/xhtml.d.ts +0 -256
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/xhtml.js +0 -256
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/xhtml.js.map +0 -1
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/index.d.ts +0 -472
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/index.js +0 -1
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/index.js.map +0 -1
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/index.mjs +0 -1
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/middleware.d.ts +0 -159
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/middleware.js +0 -2
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/middleware.js.map +0 -1
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/parseutil.d.ts +0 -10
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/parseutil.js +0 -38
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/parseutil.js.map +0 -1
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/scopeflags.d.ts +0 -12
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/scopeflags.js +0 -29
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/scopeflags.js.map +0 -1
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/tokenType.d.ts +0 -2
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/tokenType.js +0 -118
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/tokenType.js.map +0 -1
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/types.d.ts +0 -60
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/types.js +0 -2
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/types.js.map +0 -1
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/whitespace.d.ts +0 -2
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/whitespace.js +0 -19
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/whitespace.js.map +0 -1
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/package.json +0 -53
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/tsconfig.json +0 -19
- package/scaffold/scripts/parsers/node_modules/acorn-walk/CHANGELOG.md +0 -209
- package/scaffold/scripts/parsers/node_modules/acorn-walk/LICENSE +0 -21
- package/scaffold/scripts/parsers/node_modules/acorn-walk/README.md +0 -124
- package/scaffold/scripts/parsers/node_modules/acorn-walk/dist/walk.d.mts +0 -152
- package/scaffold/scripts/parsers/node_modules/acorn-walk/dist/walk.d.ts +0 -152
- package/scaffold/scripts/parsers/node_modules/acorn-walk/dist/walk.js +0 -485
- package/scaffold/scripts/parsers/node_modules/acorn-walk/dist/walk.mjs +0 -467
- package/scaffold/scripts/parsers/node_modules/acorn-walk/package.json +0 -50
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/LICENSE +0 -24
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/README.md +0 -23
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-bash.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-c.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-c_sharp.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-cpp.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-css.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-dart.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-elisp.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-elixir.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-elm.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-embedded_template.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-go.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-html.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-java.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-javascript.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-json.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-kotlin.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-lua.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-objc.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-ocaml.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-php.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-python.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-ql.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-rescript.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-ruby.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-rust.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-scala.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-solidity.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-swift.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-systemrdl.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-tlaplus.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-toml.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-tsx.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-typescript.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-vue.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-yaml.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-zig.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/package.json +0 -64
- package/scaffold/scripts/parsers/node_modules/web-tree-sitter/LICENSE +0 -21
- package/scaffold/scripts/parsers/node_modules/web-tree-sitter/README.md +0 -198
- package/scaffold/scripts/parsers/node_modules/web-tree-sitter/package.json +0 -37
- package/scaffold/scripts/parsers/node_modules/web-tree-sitter/tree-sitter-web.d.ts +0 -242
- package/scaffold/scripts/parsers/node_modules/web-tree-sitter/tree-sitter.js +0 -1
- package/scaffold/scripts/parsers/node_modules/web-tree-sitter/tree-sitter.wasm +0 -0
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
import { runContextRules } from "../rules.js";
|
|
2
|
+
import { runContextImpact, runContextRelated, runContextSearch } from "../search.js";
|
|
3
|
+
import type {
|
|
4
|
+
ImpactParams,
|
|
5
|
+
RelatedParams,
|
|
6
|
+
RelationType,
|
|
7
|
+
RulesParams,
|
|
8
|
+
SearchParams,
|
|
9
|
+
ToolPayload,
|
|
10
|
+
} from "../types.js";
|
|
11
|
+
|
|
12
|
+
type Flags = Record<string, string | boolean>;
|
|
13
|
+
|
|
14
|
+
type ParsedArgs = {
|
|
15
|
+
flags: Flags;
|
|
16
|
+
rest: string[];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type JsonEnvelope = {
|
|
20
|
+
ok: boolean;
|
|
21
|
+
command: string;
|
|
22
|
+
input?: Record<string, unknown>;
|
|
23
|
+
context_source?: unknown;
|
|
24
|
+
warning?: unknown;
|
|
25
|
+
data?: ToolPayload;
|
|
26
|
+
error?: {
|
|
27
|
+
code: string;
|
|
28
|
+
message: string;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const QUERY_COMMANDS = new Set(["search", "related", "impact", "rules", "explain"]);
|
|
33
|
+
|
|
34
|
+
const ENTITY_ID_PREFIXES = [
|
|
35
|
+
"file:",
|
|
36
|
+
"chunk:",
|
|
37
|
+
"rule:",
|
|
38
|
+
"adr:",
|
|
39
|
+
"module:",
|
|
40
|
+
"project:",
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
export async function runQueryCommand(args: string[]): Promise<void> {
|
|
44
|
+
const command = args[0] ?? "help";
|
|
45
|
+
const rest = args.slice(1);
|
|
46
|
+
|
|
47
|
+
if (command === "help" || command === "--help" || command === "-h") {
|
|
48
|
+
printHelp();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!QUERY_COMMANDS.has(command)) {
|
|
53
|
+
throw new Error(`Unknown query command: ${command}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const json = wantsJson(rest);
|
|
57
|
+
try {
|
|
58
|
+
switch (command) {
|
|
59
|
+
case "search":
|
|
60
|
+
return await runSearch(rest);
|
|
61
|
+
case "related":
|
|
62
|
+
return await runRelated(rest);
|
|
63
|
+
case "impact":
|
|
64
|
+
return await runImpact(rest);
|
|
65
|
+
case "rules":
|
|
66
|
+
return await runRules(rest);
|
|
67
|
+
case "explain":
|
|
68
|
+
return await runExplain(rest);
|
|
69
|
+
default:
|
|
70
|
+
throw new Error(`Unknown query command: ${command}`);
|
|
71
|
+
}
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (!json) {
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
emitJson({
|
|
77
|
+
ok: false,
|
|
78
|
+
command,
|
|
79
|
+
error: {
|
|
80
|
+
code: "INVALID_ARGS",
|
|
81
|
+
message: error instanceof Error ? error.message : String(error),
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
process.exitCode = 1;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function printHelp(): void {
|
|
89
|
+
const lines = [
|
|
90
|
+
"Usage:",
|
|
91
|
+
" cortex search <query> [--top-k <n>] [--preset <full|compact|minimal>] [--include-content] [--json]",
|
|
92
|
+
" cortex related <entity-id> [--depth <n>] [--edges] [--metadata] [--json]",
|
|
93
|
+
" cortex impact <query-or-entity-id> [--entity-id <id>] [--query <q>] [--depth <n>] [--top-k <n>] [--json]",
|
|
94
|
+
" cortex rules [--scope <scope>] [--include-inactive] [--json]",
|
|
95
|
+
" cortex explain <query-or-entity-id> [--top-k <n>] [--json]",
|
|
96
|
+
"",
|
|
97
|
+
"These commands read the local Cortex graph and emit MCP-equivalent data with --json.",
|
|
98
|
+
];
|
|
99
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function parseArgs(args: string[]): ParsedArgs {
|
|
103
|
+
const flags: Flags = {};
|
|
104
|
+
const rest: string[] = [];
|
|
105
|
+
let i = 0;
|
|
106
|
+
|
|
107
|
+
while (i < args.length) {
|
|
108
|
+
const arg = args[i];
|
|
109
|
+
if (arg === "--") {
|
|
110
|
+
rest.push(...args.slice(i + 1));
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (arg.startsWith("--")) {
|
|
115
|
+
const name = arg.slice(2);
|
|
116
|
+
const next = args[i + 1];
|
|
117
|
+
if (next === undefined || next.startsWith("--")) {
|
|
118
|
+
flags[name] = true;
|
|
119
|
+
i += 1;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
flags[name] = next;
|
|
123
|
+
i += 2;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
rest.push(arg);
|
|
128
|
+
i += 1;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return { flags, rest };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function wantsJson(args: string[]): boolean {
|
|
135
|
+
return args.includes("--json");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function isFlagEnabled(flags: Flags, name: string): boolean {
|
|
139
|
+
return flags[name] === true;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function optionalString(flags: Flags, name: string): string | undefined {
|
|
143
|
+
const value = flags[name];
|
|
144
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function parsePositiveIntFlag(
|
|
148
|
+
flags: Flags,
|
|
149
|
+
name: string,
|
|
150
|
+
defaultValue: number,
|
|
151
|
+
max: number,
|
|
152
|
+
): number {
|
|
153
|
+
const raw = flags[name];
|
|
154
|
+
if (raw === undefined) {
|
|
155
|
+
return defaultValue;
|
|
156
|
+
}
|
|
157
|
+
if (typeof raw !== "string" || raw.trim().length === 0) {
|
|
158
|
+
throw new Error(`--${name} requires a numeric value`);
|
|
159
|
+
}
|
|
160
|
+
const parsed = Number.parseInt(raw, 10);
|
|
161
|
+
if (!Number.isInteger(parsed) || parsed <= 0 || parsed > max) {
|
|
162
|
+
throw new Error(`--${name} must be an integer from 1 to ${max}`);
|
|
163
|
+
}
|
|
164
|
+
return parsed;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function parsePreset(flags: Flags): "full" | "compact" | "minimal" | undefined {
|
|
168
|
+
const value = optionalString(flags, "preset") ?? optionalString(flags, "response-preset");
|
|
169
|
+
if (value === undefined) {
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
if (value === "full" || value === "compact" || value === "minimal") {
|
|
173
|
+
return value;
|
|
174
|
+
}
|
|
175
|
+
throw new Error("--preset must be one of full, compact, minimal");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function parseCsvFlag<T extends string>(
|
|
179
|
+
flags: Flags,
|
|
180
|
+
name: string,
|
|
181
|
+
allowed: readonly T[],
|
|
182
|
+
): T[] | undefined {
|
|
183
|
+
const raw = optionalString(flags, name);
|
|
184
|
+
if (!raw) {
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
const allowedSet = new Set<string>(allowed);
|
|
188
|
+
const parsed = raw.split(",").map((value) => value.trim()).filter(Boolean);
|
|
189
|
+
for (const value of parsed) {
|
|
190
|
+
if (!allowedSet.has(value)) {
|
|
191
|
+
throw new Error(`--${name} contains unsupported value: ${value}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return parsed as T[];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function positionalText(rest: string[], label: string): string {
|
|
198
|
+
const value = rest.join(" ").trim();
|
|
199
|
+
if (!value) {
|
|
200
|
+
throw new Error(`${label} is required`);
|
|
201
|
+
}
|
|
202
|
+
return value;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function emitJson(value: JsonEnvelope): void {
|
|
206
|
+
process.stdout.write(JSON.stringify(value, null, 2) + "\n");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function emitEnvelope(command: string, input: Record<string, unknown>, data: ToolPayload): void {
|
|
210
|
+
emitJson({
|
|
211
|
+
ok: true,
|
|
212
|
+
command,
|
|
213
|
+
input,
|
|
214
|
+
context_source: data.context_source,
|
|
215
|
+
warning: data.warning,
|
|
216
|
+
data,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function printSummary(command: string, data: ToolPayload): void {
|
|
221
|
+
const source = data.context_source ? ` context_source=${String(data.context_source)}` : "";
|
|
222
|
+
const warning = data.warning ? ` warning=${String(data.warning)}` : "";
|
|
223
|
+
|
|
224
|
+
if (Array.isArray(data.results)) {
|
|
225
|
+
process.stdout.write(`${command}: ${data.results.length} results${source}${warning}\n`);
|
|
226
|
+
for (const result of data.results.slice(0, 10)) {
|
|
227
|
+
printEntityLine(result);
|
|
228
|
+
}
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (Array.isArray(data.related)) {
|
|
233
|
+
process.stdout.write(`${command}: ${data.related.length} related${source}${warning}\n`);
|
|
234
|
+
for (const result of data.related.slice(0, 10)) {
|
|
235
|
+
printEntityLine(result);
|
|
236
|
+
}
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (Array.isArray(data.rules)) {
|
|
241
|
+
process.stdout.write(`${command}: ${data.rules.length} rules${source}${warning}\n`);
|
|
242
|
+
for (const rule of data.rules.slice(0, 10)) {
|
|
243
|
+
if (rule && typeof rule === "object") {
|
|
244
|
+
const row = rule as Record<string, unknown>;
|
|
245
|
+
process.stdout.write(`- ${String(row.id ?? "")} priority=${String(row.priority ?? "")} scope=${String(row.scope ?? "")}\n`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
process.stdout.write(`${command}: ok${source}${warning}\n`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function printEntityLine(value: unknown): void {
|
|
255
|
+
if (!value || typeof value !== "object") {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const row = value as Record<string, unknown>;
|
|
259
|
+
const title = row.title ?? row.label ?? row.id ?? "";
|
|
260
|
+
const path = row.path ? ` ${String(row.path)}` : "";
|
|
261
|
+
process.stdout.write(`- ${String(title)}${path}\n`);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async function runSearch(args: string[]): Promise<void> {
|
|
265
|
+
const { flags, rest } = parseArgs(args);
|
|
266
|
+
const query = optionalString(flags, "query") ?? positionalText(rest, "query");
|
|
267
|
+
const input: SearchParams = {
|
|
268
|
+
query,
|
|
269
|
+
top_k: parsePositiveIntFlag(flags, "top-k", 5, 20),
|
|
270
|
+
include_deprecated: isFlagEnabled(flags, "include-deprecated"),
|
|
271
|
+
response_preset: parsePreset(flags),
|
|
272
|
+
include_scores: isFlagEnabled(flags, "scores") || isFlagEnabled(flags, "include-scores") || undefined,
|
|
273
|
+
include_matched_rules:
|
|
274
|
+
isFlagEnabled(flags, "matched-rules") || isFlagEnabled(flags, "include-matched-rules") || undefined,
|
|
275
|
+
include_content: isFlagEnabled(flags, "include-content") || undefined,
|
|
276
|
+
};
|
|
277
|
+
const data = await runContextSearch(input);
|
|
278
|
+
if (isFlagEnabled(flags, "json")) {
|
|
279
|
+
emitEnvelope("search", input, data);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
printSummary("search", data);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async function runRelated(args: string[]): Promise<void> {
|
|
286
|
+
const { flags, rest } = parseArgs(args);
|
|
287
|
+
const entityId = optionalString(flags, "entity-id") ?? positionalText(rest, "entity-id");
|
|
288
|
+
const input: RelatedParams = {
|
|
289
|
+
entity_id: entityId,
|
|
290
|
+
depth: parsePositiveIntFlag(flags, "depth", 1, 3),
|
|
291
|
+
include_edges: isFlagEnabled(flags, "edges") || isFlagEnabled(flags, "include-edges") || undefined,
|
|
292
|
+
response_preset: parsePreset(flags),
|
|
293
|
+
include_entity_metadata:
|
|
294
|
+
isFlagEnabled(flags, "metadata") || isFlagEnabled(flags, "include-entity-metadata") || undefined,
|
|
295
|
+
};
|
|
296
|
+
const data = await runContextRelated(input);
|
|
297
|
+
if (isFlagEnabled(flags, "json")) {
|
|
298
|
+
emitEnvelope("related", input, data);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
printSummary("related", data);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async function runImpact(args: string[]): Promise<void> {
|
|
305
|
+
const { flags, rest } = parseArgs(args);
|
|
306
|
+
const explicitEntityId = optionalString(flags, "entity-id");
|
|
307
|
+
const explicitQuery = optionalString(flags, "query");
|
|
308
|
+
const positional = rest.length > 0 ? positionalText(rest, "query-or-entity-id") : undefined;
|
|
309
|
+
const seed = explicitEntityId ?? explicitQuery ?? positional;
|
|
310
|
+
|
|
311
|
+
if (!seed) {
|
|
312
|
+
throw new Error("Either --entity-id, --query, or a positional seed is required");
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const relationTypes = parseCsvFlag(flags, "relation-types", [
|
|
316
|
+
"CALLS",
|
|
317
|
+
"CALLS_SQL",
|
|
318
|
+
"IMPORTS",
|
|
319
|
+
"USES_CONFIG_KEY",
|
|
320
|
+
"USES_RESOURCE_KEY",
|
|
321
|
+
"USES_SETTING_KEY",
|
|
322
|
+
"USES_CONFIG",
|
|
323
|
+
"TRANSFORMS_CONFIG",
|
|
324
|
+
"PART_OF",
|
|
325
|
+
] as const);
|
|
326
|
+
const input: ImpactParams = {
|
|
327
|
+
depth: parsePositiveIntFlag(flags, "depth", 2, 4),
|
|
328
|
+
top_k: parsePositiveIntFlag(flags, "top-k", 8, 20),
|
|
329
|
+
include_edges: !isFlagEnabled(flags, "no-edges"),
|
|
330
|
+
response_preset: parsePreset(flags),
|
|
331
|
+
include_scores: isFlagEnabled(flags, "scores") || isFlagEnabled(flags, "include-scores") || undefined,
|
|
332
|
+
include_reasons: isFlagEnabled(flags, "reasons") || isFlagEnabled(flags, "include-reasons") || undefined,
|
|
333
|
+
verbose_paths: isFlagEnabled(flags, "verbose-paths") || undefined,
|
|
334
|
+
max_path_hops_shown: parsePositiveIntFlag(flags, "max-path-hops-shown", 3, 8),
|
|
335
|
+
profile: parseProfile(flags),
|
|
336
|
+
sort_by: parseSortBy(flags),
|
|
337
|
+
relation_types: relationTypes as RelationType[] | undefined,
|
|
338
|
+
path_must_include: parseCsvFlag(flags, "path-must-include", relationTypesAllowed()) as RelationType[] | undefined,
|
|
339
|
+
path_must_exclude: parseCsvFlag(flags, "path-must-exclude", relationTypesAllowed()) as RelationType[] | undefined,
|
|
340
|
+
result_domains: parseCsvFlag(flags, "result-domains", [
|
|
341
|
+
"code",
|
|
342
|
+
"config",
|
|
343
|
+
"resource",
|
|
344
|
+
"settings",
|
|
345
|
+
"sql",
|
|
346
|
+
"project",
|
|
347
|
+
] as const),
|
|
348
|
+
result_entity_types: parseCsvFlag(flags, "result-entity-types", [
|
|
349
|
+
"File",
|
|
350
|
+
"Chunk",
|
|
351
|
+
"Module",
|
|
352
|
+
"Project",
|
|
353
|
+
"ADR",
|
|
354
|
+
"Rule",
|
|
355
|
+
] as const),
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
if (explicitEntityId || (!explicitQuery && seedLooksLikeEntityId(seed))) {
|
|
359
|
+
input.entity_id = seed;
|
|
360
|
+
} else {
|
|
361
|
+
input.query = seed;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const data = await runContextImpact(input);
|
|
365
|
+
if (isFlagEnabled(flags, "json")) {
|
|
366
|
+
emitEnvelope("impact", input as Record<string, unknown>, data);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
printSummary("impact", data);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function relationTypesAllowed(): readonly RelationType[] {
|
|
373
|
+
return [
|
|
374
|
+
"CALLS",
|
|
375
|
+
"CALLS_SQL",
|
|
376
|
+
"IMPORTS",
|
|
377
|
+
"USES_CONFIG_KEY",
|
|
378
|
+
"USES_RESOURCE_KEY",
|
|
379
|
+
"USES_SETTING_KEY",
|
|
380
|
+
"USES_CONFIG",
|
|
381
|
+
"TRANSFORMS_CONFIG",
|
|
382
|
+
"PART_OF",
|
|
383
|
+
];
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function seedLooksLikeEntityId(value: string): boolean {
|
|
387
|
+
return ENTITY_ID_PREFIXES.some((prefix) => value.startsWith(prefix));
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function parseProfile(flags: Flags): ImpactParams["profile"] {
|
|
391
|
+
const value = optionalString(flags, "profile");
|
|
392
|
+
if (!value) {
|
|
393
|
+
return "all";
|
|
394
|
+
}
|
|
395
|
+
if (value === "all" || value === "config_only" || value === "config_to_sql" || value === "code_only" || value === "sql_only") {
|
|
396
|
+
return value;
|
|
397
|
+
}
|
|
398
|
+
throw new Error("--profile must be one of all, config_only, config_to_sql, code_only, sql_only");
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function parseSortBy(flags: Flags): ImpactParams["sort_by"] {
|
|
402
|
+
const value = optionalString(flags, "sort-by");
|
|
403
|
+
if (!value) {
|
|
404
|
+
return "impact_score";
|
|
405
|
+
}
|
|
406
|
+
if (value === "impact_score" || value === "shortest_path" || value === "semantic_score" || value === "graph_score" || value === "trust_score") {
|
|
407
|
+
return value;
|
|
408
|
+
}
|
|
409
|
+
throw new Error("--sort-by must be one of impact_score, shortest_path, semantic_score, graph_score, trust_score");
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
async function runRules(args: string[]): Promise<void> {
|
|
413
|
+
const { flags } = parseArgs(args);
|
|
414
|
+
const input: RulesParams = {
|
|
415
|
+
scope: optionalString(flags, "scope"),
|
|
416
|
+
include_inactive: isFlagEnabled(flags, "include-inactive"),
|
|
417
|
+
};
|
|
418
|
+
const data = await runContextRules(input);
|
|
419
|
+
if (isFlagEnabled(flags, "json")) {
|
|
420
|
+
emitEnvelope("rules", input, data);
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
printSummary("rules", data);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
async function runExplain(args: string[]): Promise<void> {
|
|
427
|
+
const { flags, rest } = parseArgs(args);
|
|
428
|
+
const target = optionalString(flags, "id") ?? optionalString(flags, "query") ?? positionalText(rest, "query-or-entity-id");
|
|
429
|
+
const input: SearchParams = {
|
|
430
|
+
query: target,
|
|
431
|
+
top_k: parsePositiveIntFlag(flags, "top-k", 3, 20),
|
|
432
|
+
include_deprecated: isFlagEnabled(flags, "include-deprecated"),
|
|
433
|
+
response_preset: parsePreset(flags) ?? "full",
|
|
434
|
+
include_scores: true,
|
|
435
|
+
include_matched_rules: true,
|
|
436
|
+
include_content: isFlagEnabled(flags, "include-content") || undefined,
|
|
437
|
+
};
|
|
438
|
+
const search = await runContextSearch(input);
|
|
439
|
+
const data: ToolPayload = {
|
|
440
|
+
query: target,
|
|
441
|
+
entity_id: seedLooksLikeEntityId(target) ? target : undefined,
|
|
442
|
+
context_source: search.context_source,
|
|
443
|
+
warning: search.warning,
|
|
444
|
+
semantic_engine: search.semantic_engine,
|
|
445
|
+
ranking: search.ranking,
|
|
446
|
+
results: search.results,
|
|
447
|
+
explanation: "Scores and matched_rules come from the same local search ranking used by context.search.",
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
if (isFlagEnabled(flags, "json")) {
|
|
451
|
+
emitEnvelope("explain", input, data);
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
printSummary("explain", data);
|
|
455
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
+
|
|
6
|
+
const QUERY_MODULE = fileURLToPath(new URL("../dist/cli/query.js", import.meta.url));
|
|
7
|
+
|
|
8
|
+
function runQuery(args) {
|
|
9
|
+
const script = [
|
|
10
|
+
"const mod = await import(process.argv[1]);",
|
|
11
|
+
"await mod.runQueryCommand(JSON.parse(process.argv[2]));",
|
|
12
|
+
].join("\n");
|
|
13
|
+
|
|
14
|
+
return spawnSync(
|
|
15
|
+
process.execPath,
|
|
16
|
+
[
|
|
17
|
+
"--input-type=module",
|
|
18
|
+
"-e",
|
|
19
|
+
script,
|
|
20
|
+
pathToFileURL(QUERY_MODULE).href,
|
|
21
|
+
JSON.stringify(args),
|
|
22
|
+
],
|
|
23
|
+
{ encoding: "utf8" },
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function runJson(args, expectedStatus = 0) {
|
|
28
|
+
const result = runQuery(args);
|
|
29
|
+
assert.equal(result.status, expectedStatus, result.stderr || result.stdout);
|
|
30
|
+
return JSON.parse(result.stdout);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
test("rules --json emits a stable envelope", () => {
|
|
34
|
+
const parsed = runJson(["rules", "--json"]);
|
|
35
|
+
|
|
36
|
+
assert.equal(parsed.ok, true);
|
|
37
|
+
assert.equal(parsed.command, "rules");
|
|
38
|
+
assert.equal(parsed.input.include_inactive, false);
|
|
39
|
+
assert.ok(["ryu", "cache"].includes(String(parsed.context_source)));
|
|
40
|
+
assert.ok(Array.isArray(parsed.data.rules));
|
|
41
|
+
assert.equal(parsed.data.count, parsed.data.rules.length);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("search --json maps flags to context.search input", () => {
|
|
45
|
+
const parsed = runJson([
|
|
46
|
+
"search",
|
|
47
|
+
"rule.source_of_truth",
|
|
48
|
+
"--top-k",
|
|
49
|
+
"3",
|
|
50
|
+
"--preset",
|
|
51
|
+
"full",
|
|
52
|
+
"--scores",
|
|
53
|
+
"--matched-rules",
|
|
54
|
+
"--json",
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
assert.equal(parsed.ok, true);
|
|
58
|
+
assert.equal(parsed.command, "search");
|
|
59
|
+
assert.equal(parsed.input.query, "rule.source_of_truth");
|
|
60
|
+
assert.equal(parsed.input.top_k, 3);
|
|
61
|
+
assert.equal(parsed.input.response_preset, "full");
|
|
62
|
+
assert.equal(parsed.input.include_scores, true);
|
|
63
|
+
assert.equal(parsed.input.include_matched_rules, true);
|
|
64
|
+
assert.ok(Array.isArray(parsed.data.results));
|
|
65
|
+
assert.ok(parsed.data.results.length <= 3);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("related --json preserves missing-entity runtime behavior", () => {
|
|
69
|
+
const parsed = runJson(["related", "file:no-such-file", "--json"]);
|
|
70
|
+
|
|
71
|
+
assert.equal(parsed.ok, true);
|
|
72
|
+
assert.equal(parsed.command, "related");
|
|
73
|
+
assert.equal(parsed.input.entity_id, "file:no-such-file");
|
|
74
|
+
assert.deepEqual(parsed.data.related, []);
|
|
75
|
+
assert.match(parsed.data.warning, /Entity not found/);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("impact --json supports positional entity ids", () => {
|
|
79
|
+
const parsed = runJson([
|
|
80
|
+
"impact",
|
|
81
|
+
"file:bin/cortex.mjs",
|
|
82
|
+
"--depth",
|
|
83
|
+
"1",
|
|
84
|
+
"--top-k",
|
|
85
|
+
"2",
|
|
86
|
+
"--json",
|
|
87
|
+
]);
|
|
88
|
+
|
|
89
|
+
assert.equal(parsed.ok, true);
|
|
90
|
+
assert.equal(parsed.command, "impact");
|
|
91
|
+
assert.equal(parsed.input.entity_id, "file:bin/cortex.mjs");
|
|
92
|
+
assert.equal(parsed.input.depth, 1);
|
|
93
|
+
assert.equal(parsed.input.top_k, 2);
|
|
94
|
+
assert.ok(Array.isArray(parsed.data.results));
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("explain --json enables scores and matched rules", () => {
|
|
98
|
+
const parsed = runJson(["explain", "rule.source_of_truth", "--json"]);
|
|
99
|
+
|
|
100
|
+
assert.equal(parsed.ok, true);
|
|
101
|
+
assert.equal(parsed.command, "explain");
|
|
102
|
+
assert.equal(parsed.input.include_scores, true);
|
|
103
|
+
assert.equal(parsed.input.include_matched_rules, true);
|
|
104
|
+
assert.equal(parsed.data.explanation.includes("context.search"), true);
|
|
105
|
+
assert.ok(Array.isArray(parsed.data.results));
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("json validation errors emit an error envelope", () => {
|
|
109
|
+
const parsed = runJson(["impact", "--json"], 1);
|
|
110
|
+
|
|
111
|
+
assert.equal(parsed.ok, false);
|
|
112
|
+
assert.equal(parsed.command, "impact");
|
|
113
|
+
assert.equal(parsed.error.code, "INVALID_ARGS");
|
|
114
|
+
assert.match(parsed.error.message, /Either --entity-id/);
|
|
115
|
+
});
|
|
@@ -3,7 +3,8 @@ set -euo pipefail
|
|
|
3
3
|
|
|
4
4
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
5
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
6
|
-
|
|
6
|
+
CONTEXT_RUNTIME_DIR="$REPO_ROOT/.context/mcp"
|
|
7
|
+
MCP_DIR="$CONTEXT_RUNTIME_DIR"
|
|
7
8
|
TOTAL_STEPS=6
|
|
8
9
|
STEP_INDEX=0
|
|
9
10
|
|
|
@@ -55,7 +56,7 @@ install_deps_if_changed() {
|
|
|
55
56
|
fi
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
step "Installing
|
|
59
|
+
step "Installing context runtime dependencies"
|
|
59
60
|
info "note: upstream RyuGraph dependencies may print deprecation warnings during install"
|
|
60
61
|
install_deps_if_changed "$MCP_DIR" "$MCP_DIR/.npm-cache"
|
|
61
62
|
install_deps_if_changed "$REPO_ROOT/.context/scripts/parsers" "$REPO_ROOT/.context/scripts/parsers/.npm-cache"
|
|
@@ -4,7 +4,8 @@ set -euo pipefail
|
|
|
4
4
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
5
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
6
6
|
CONTEXT_DIR="$REPO_ROOT/.context"
|
|
7
|
-
|
|
7
|
+
CONTEXT_RUNTIME_DIR="$CONTEXT_DIR/mcp"
|
|
8
|
+
MCP_DIR="$CONTEXT_RUNTIME_DIR"
|
|
8
9
|
|
|
9
10
|
PASS=0
|
|
10
11
|
FAIL=0
|
|
@@ -160,24 +161,24 @@ if [[ -f "$INGEST_MANIFEST" ]] && command -v git &>/dev/null && git -C "$REPO_RO
|
|
|
160
161
|
fi
|
|
161
162
|
fi
|
|
162
163
|
|
|
163
|
-
# ── MCP Server
|
|
164
|
+
# ── Context Runtime / MCP Server ─────────────────────────
|
|
164
165
|
|
|
165
166
|
echo ""
|
|
166
|
-
echo "
|
|
167
|
+
echo " Context Runtime"
|
|
167
168
|
|
|
168
169
|
if [[ -f "$MCP_DIR/dist/server.js" ]]; then
|
|
169
|
-
pass ".context/mcp/dist/server.js
|
|
170
|
+
pass "runtime dist present (.context/mcp/dist/server.js)"
|
|
170
171
|
else
|
|
171
|
-
fail "
|
|
172
|
+
fail "runtime dist missing — run: cd .context/mcp && npm run build"
|
|
172
173
|
fi
|
|
173
174
|
|
|
174
175
|
if [[ -d "$MCP_DIR/node_modules" ]]; then
|
|
175
|
-
pass ".context/mcp/node_modules
|
|
176
|
+
pass "runtime dependencies present (.context/mcp/node_modules)"
|
|
176
177
|
else
|
|
177
|
-
fail "
|
|
178
|
+
fail "runtime dependencies missing — run: cd .context/mcp && npm install"
|
|
178
179
|
fi
|
|
179
180
|
|
|
180
|
-
# Quick
|
|
181
|
+
# Quick runtime import check
|
|
181
182
|
if [[ -f "$MCP_DIR/dist/server.js" ]] && [[ -d "$MCP_DIR/node_modules" ]]; then
|
|
182
183
|
MCP_CHECK=$(cd "$REPO_ROOT" && timeout 10 node -e '
|
|
183
184
|
const start = Date.now();
|
|
@@ -3,7 +3,8 @@ set -euo pipefail
|
|
|
3
3
|
|
|
4
4
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
5
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
6
|
-
|
|
6
|
+
CONTEXT_RUNTIME_DIR="$REPO_ROOT/.context/mcp"
|
|
7
|
+
MCP_DIR="$CONTEXT_RUNTIME_DIR"
|
|
7
8
|
|
|
8
9
|
if ! command -v npm >/dev/null 2>&1; then
|
|
9
10
|
echo "[embed] npm is required but not found on PATH"
|
|
@@ -12,5 +13,5 @@ fi
|
|
|
12
13
|
|
|
13
14
|
mkdir -p "$MCP_DIR/.npm-cache"
|
|
14
15
|
|
|
15
|
-
echo "[embed] generating embeddings via
|
|
16
|
+
echo "[embed] generating embeddings via context runtime"
|
|
16
17
|
CORTEX_PROJECT_ROOT="$REPO_ROOT" NPM_CONFIG_CACHE="$MCP_DIR/.npm-cache" npm --prefix "$MCP_DIR" run embed --silent -- "$@"
|
|
@@ -3,7 +3,8 @@ set -euo pipefail
|
|
|
3
3
|
|
|
4
4
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
5
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
6
|
-
|
|
6
|
+
CONTEXT_RUNTIME_DIR="$REPO_ROOT/.context/mcp"
|
|
7
|
+
MCP_DIR="$CONTEXT_RUNTIME_DIR"
|
|
7
8
|
|
|
8
9
|
if [[ ! -f "$MCP_DIR/package.json" ]]; then
|
|
9
10
|
echo "[graph-load] missing $MCP_DIR/package.json"
|
|
@@ -11,7 +12,7 @@ if [[ ! -f "$MCP_DIR/package.json" ]]; then
|
|
|
11
12
|
fi
|
|
12
13
|
|
|
13
14
|
if [[ ! -d "$MCP_DIR/node_modules" ]]; then
|
|
14
|
-
echo "[graph-load] node_modules missing in .context/mcp
|
|
15
|
+
echo "[graph-load] node_modules missing in context runtime (.context/mcp compatibility path)"
|
|
15
16
|
echo "[graph-load] run: cd .context/mcp && NPM_CONFIG_CACHE=$MCP_DIR/.npm-cache npm install"
|
|
16
17
|
exit 1
|
|
17
18
|
fi
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { parseFrontmatter, parseStringList } from "../mcp/dist/frontmatter.js";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
5
|
|
|
7
6
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
7
|
const __dirname = path.dirname(__filename);
|
|
8
|
+
const CONTEXT_RUNTIME_DIST = path.resolve(__dirname, "../mcp/dist");
|
|
9
|
+
const { parseFrontmatter, parseStringList } = await import(
|
|
10
|
+
pathToFileURL(path.join(CONTEXT_RUNTIME_DIST, "frontmatter.js")).href
|
|
11
|
+
);
|
|
9
12
|
|
|
10
13
|
function normalizeForWsl(rawPath) {
|
|
11
14
|
const m = rawPath.match(/^([A-Za-z]):[/\\](.*)/);
|