@danielblomma/cortex-mcp 0.4.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.
Files changed (41) hide show
  1. package/README.md +203 -0
  2. package/bin/cortex.mjs +621 -0
  3. package/docs/MCP_MARKETPLACE.md +160 -0
  4. package/package.json +42 -0
  5. package/scaffold/.context/config.yaml +21 -0
  6. package/scaffold/.context/ontology.cypher +63 -0
  7. package/scaffold/.context/rules.yaml +25 -0
  8. package/scaffold/.githooks/_cortex-update-runner.sh +58 -0
  9. package/scaffold/.githooks/post-checkout +22 -0
  10. package/scaffold/.githooks/post-merge +14 -0
  11. package/scaffold/docs/architecture.md +22 -0
  12. package/scaffold/mcp/package-lock.json +2623 -0
  13. package/scaffold/mcp/package.json +29 -0
  14. package/scaffold/mcp/src/embed.ts +416 -0
  15. package/scaffold/mcp/src/embeddings.ts +192 -0
  16. package/scaffold/mcp/src/graph.ts +666 -0
  17. package/scaffold/mcp/src/loadGraph.ts +597 -0
  18. package/scaffold/mcp/src/paths.ts +33 -0
  19. package/scaffold/mcp/src/search.ts +412 -0
  20. package/scaffold/mcp/src/server.ts +98 -0
  21. package/scaffold/mcp/src/types.ts +109 -0
  22. package/scaffold/mcp/tests/server.test.mjs +60 -0
  23. package/scaffold/mcp/tsconfig.json +13 -0
  24. package/scaffold/scripts/bootstrap.sh +57 -0
  25. package/scaffold/scripts/capture-note.sh +55 -0
  26. package/scaffold/scripts/context.sh +109 -0
  27. package/scaffold/scripts/embed.sh +15 -0
  28. package/scaffold/scripts/ingest.mjs +1118 -0
  29. package/scaffold/scripts/ingest.sh +20 -0
  30. package/scaffold/scripts/install-git-hooks.sh +21 -0
  31. package/scaffold/scripts/load-kuzu.sh +6 -0
  32. package/scaffold/scripts/load-ryu.sh +18 -0
  33. package/scaffold/scripts/parsers/javascript.mjs +390 -0
  34. package/scaffold/scripts/parsers/package-lock.json +51 -0
  35. package/scaffold/scripts/parsers/package.json +17 -0
  36. package/scaffold/scripts/plan-state-engine.cjs +310 -0
  37. package/scaffold/scripts/plan-state.sh +71 -0
  38. package/scaffold/scripts/refresh.sh +9 -0
  39. package/scaffold/scripts/status.sh +282 -0
  40. package/scaffold/scripts/update-context.sh +18 -0
  41. package/scaffold/scripts/watch.sh +374 -0
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+ CONTEXT_DIR="$REPO_ROOT/.context"
6
+
7
+ printf "[ingest] repo: %s\n" "$REPO_ROOT"
8
+ printf "[ingest] config: %s\n" "$CONTEXT_DIR/config.yaml"
9
+
10
+ if [[ ! -f "$CONTEXT_DIR/config.yaml" ]]; then
11
+ echo "[ingest] missing .context/config.yaml"
12
+ exit 1
13
+ fi
14
+
15
+ if ! command -v node >/dev/null 2>&1; then
16
+ echo "[ingest] Node.js is required but not found on PATH"
17
+ exit 1
18
+ fi
19
+
20
+ node "$REPO_ROOT/scripts/ingest.mjs" "$@"
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+ HOOKS_DIR="$REPO_ROOT/.githooks"
6
+
7
+ if [[ ! -d "$HOOKS_DIR" ]]; then
8
+ echo "[hooks] missing $HOOKS_DIR"
9
+ exit 1
10
+ fi
11
+
12
+ chmod +x \
13
+ "$HOOKS_DIR/post-merge" \
14
+ "$HOOKS_DIR/post-checkout" \
15
+ "$HOOKS_DIR/_cortex-update-runner.sh"
16
+
17
+ git -C "$REPO_ROOT" config core.hooksPath .githooks
18
+
19
+ echo "[hooks] installed core.hooksPath=.githooks"
20
+ echo "[hooks] post-merge + post-checkout now trigger background cortex update"
21
+ echo "[hooks] logs: .context/hooks/update.log"
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+ echo "[graph-load] warning: load-kuzu.sh is deprecated; using RyuGraph loader"
6
+ "$REPO_ROOT/scripts/load-ryu.sh" "$@"
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+ MCP_DIR="$REPO_ROOT/mcp"
6
+
7
+ if [[ ! -f "$MCP_DIR/package.json" ]]; then
8
+ echo "[graph-load] missing $MCP_DIR/package.json"
9
+ exit 1
10
+ fi
11
+
12
+ if [[ ! -d "$MCP_DIR/node_modules" ]]; then
13
+ echo "[graph-load] node_modules missing in mcp/"
14
+ echo "[graph-load] run: cd mcp && NPM_CONFIG_CACHE=$MCP_DIR/.npm-cache npm install"
15
+ exit 1
16
+ fi
17
+
18
+ NPM_CONFIG_CACHE="$MCP_DIR/.npm-cache" npm --prefix "$MCP_DIR" run graph:load -- "$@"
@@ -0,0 +1,390 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * JavaScript/TypeScript AST Parser for Cortex
4
+ * Extracts functions, methods, classes and call relationships
5
+ */
6
+
7
+ import { Parser } from "acorn";
8
+ import tsPlugin from "acorn-typescript";
9
+ import { simple as walkSimple, base } from "acorn-walk";
10
+
11
+ // Extend acorn-walk to handle TypeScript AST nodes
12
+ const tsNodeHandlers = {
13
+ TSAsExpression(node, st, c) { c(node.expression, st); },
14
+ TSTypeAnnotation(node, st, c) { /* skip type annotations */ },
15
+ TSTypeParameterInstantiation(node, st, c) { /* skip */ },
16
+ TSTypeParameterDeclaration(node, st, c) { /* skip */ },
17
+ TSTypeReference(node, st, c) { /* skip */ },
18
+ TSInterfaceDeclaration(node, st, c) { /* skip */ },
19
+ TSTypeAliasDeclaration(node, st, c) { /* skip */ },
20
+ TSEnumDeclaration(node, st, c) { /* skip */ },
21
+ TSModuleDeclaration(node, st, c) { /* skip */ },
22
+ TSDeclareFunction(node, st, c) { /* skip */ },
23
+ TSPropertySignature(node, st, c) { /* skip */ },
24
+ TSMethodSignature(node, st, c) { /* skip */ },
25
+ TSIndexSignature(node, st, c) { /* skip */ },
26
+ TSTypeLiteral(node, st, c) { /* skip */ },
27
+ TSUnionType(node, st, c) { /* skip */ },
28
+ TSIntersectionType(node, st, c) { /* skip */ },
29
+ TSArrayType(node, st, c) { /* skip */ },
30
+ TSTupleType(node, st, c) { /* skip */ },
31
+ TSOptionalType(node, st, c) { /* skip */ },
32
+ TSRestType(node, st, c) { /* skip */ },
33
+ TSFunctionType(node, st, c) { /* skip */ },
34
+ TSConstructorType(node, st, c) { /* skip */ },
35
+ TSNonNullExpression(node, st, c) { c(node.expression, st); },
36
+ TSInstantiationExpression(node, st, c) { c(node.expression, st); },
37
+ };
38
+
39
+ Object.assign(base, tsNodeHandlers);
40
+
41
+ const CHUNK_KINDS = new Set(["function", "method", "class", "const", "let", "var"]);
42
+
43
+ /**
44
+ * Parse JavaScript/TypeScript code and extract chunks + calls
45
+ * @param {string} code - Source code
46
+ * @param {string} filePath - File path (for error context)
47
+ * @param {string} language - "javascript" | "typescript" | "jsx" | "tsx"
48
+ * @returns {Object} { chunks: Array, errors: Array }
49
+ */
50
+ export function parseCode(code, filePath, language = "javascript") {
51
+ const chunks = [];
52
+ const errors = [];
53
+ const lines = code.split(/\r?\n/);
54
+
55
+ let ast;
56
+ try {
57
+ const TSParser = Parser.extend(tsPlugin());
58
+ ast = TSParser.parse(code, {
59
+ ecmaVersion: "latest",
60
+ sourceType: "module",
61
+ locations: true,
62
+ allowHashBang: true,
63
+ allowImportExportEverywhere: true,
64
+ allowAwaitOutsideFunction: true
65
+ });
66
+ } catch (error) {
67
+ errors.push({
68
+ message: `Parse error: ${error.message}`,
69
+ line: error.loc?.line,
70
+ column: error.loc?.column
71
+ });
72
+ return { chunks: [], errors };
73
+ }
74
+
75
+ // Extract top-level declarations
76
+ walkSimple(ast, {
77
+ FunctionDeclaration(node) {
78
+ if (!node.id) return; // Skip anonymous
79
+
80
+ const chunk = extractFunctionChunk(node, "function", lines, code);
81
+ if (chunk) {
82
+ chunk.language = language;
83
+ chunks.push(chunk);
84
+ }
85
+ },
86
+
87
+ ClassDeclaration(node) {
88
+ if (!node.id) return;
89
+
90
+ const chunk = extractClassChunk(node, lines, code);
91
+ if (chunk) {
92
+ chunk.language = language;
93
+ chunks.push(chunk);
94
+
95
+ // Extract methods as sub-chunks
96
+ for (const method of extractClassMethods(node, lines, code)) {
97
+ method.language = language;
98
+ method.parentChunk = chunk.name;
99
+ chunks.push(method);
100
+ }
101
+ }
102
+ },
103
+
104
+ VariableDeclaration(node) {
105
+ // Extract arrow functions and function expressions assigned to variables
106
+ for (const declarator of node.declarations) {
107
+ if (!declarator.id || declarator.id.type !== "Identifier") continue;
108
+ if (!declarator.init) continue;
109
+
110
+ const isFunctionExpr =
111
+ declarator.init.type === "FunctionExpression" ||
112
+ declarator.init.type === "ArrowFunctionExpression";
113
+
114
+ if (isFunctionExpr) {
115
+ const chunk = extractFunctionChunk(
116
+ declarator.init,
117
+ "const",
118
+ lines,
119
+ code,
120
+ declarator.id.name
121
+ );
122
+ if (chunk) {
123
+ chunk.language = language;
124
+ chunks.push(chunk);
125
+ }
126
+ }
127
+ }
128
+ },
129
+
130
+ ExportNamedDeclaration(node) {
131
+ // Handle export function/class
132
+ if (node.declaration) {
133
+ if (node.declaration.type === "FunctionDeclaration") {
134
+ const chunk = extractFunctionChunk(node.declaration, "function", lines, code);
135
+ if (chunk) {
136
+ chunk.exported = true;
137
+ chunk.language = language;
138
+ chunks.push(chunk);
139
+ }
140
+ } else if (node.declaration.type === "ClassDeclaration") {
141
+ const chunk = extractClassChunk(node.declaration, lines, code);
142
+ if (chunk) {
143
+ chunk.exported = true;
144
+ chunk.language = language;
145
+ chunks.push(chunk);
146
+
147
+ for (const method of extractClassMethods(node.declaration, lines, code)) {
148
+ method.language = language;
149
+ method.parentChunk = chunk.name;
150
+ chunks.push(method);
151
+ }
152
+ }
153
+ }
154
+ }
155
+ },
156
+
157
+ ExportDefaultDeclaration(node) {
158
+ if (node.declaration.type === "FunctionDeclaration") {
159
+ const chunk = extractFunctionChunk(node.declaration, "function", lines, code);
160
+ if (chunk) {
161
+ chunk.exported = true;
162
+ chunk.default = true;
163
+ chunk.language = language;
164
+ chunks.push(chunk);
165
+ }
166
+ } else if (node.declaration.type === "ClassDeclaration") {
167
+ const chunk = extractClassChunk(node.declaration, lines, code);
168
+ if (chunk) {
169
+ chunk.exported = true;
170
+ chunk.default = true;
171
+ chunk.language = language;
172
+ chunks.push(chunk);
173
+
174
+ for (const method of extractClassMethods(node.declaration, lines, code)) {
175
+ method.language = language;
176
+ method.parentChunk = chunk.name;
177
+ chunks.push(method);
178
+ }
179
+ }
180
+ }
181
+ }
182
+ });
183
+
184
+ // Deduplicate chunks by name+startLine (exports can cause double extraction)
185
+ const seenChunks = new Map();
186
+ for (const chunk of chunks) {
187
+ const key = `${chunk.name}:${chunk.startLine}`;
188
+ const existing = seenChunks.get(key);
189
+ // Prefer exported version over non-exported
190
+ if (!existing || chunk.exported) {
191
+ seenChunks.set(key, chunk);
192
+ }
193
+ }
194
+ const uniqueChunks = [...seenChunks.values()];
195
+
196
+ // Extract calls for each chunk
197
+ for (const chunk of uniqueChunks) {
198
+ chunk.calls = extractCalls(chunk.bodyNode, code);
199
+ chunk.imports = extractImports(ast);
200
+ delete chunk.bodyNode; // Remove AST node (not serializable)
201
+ }
202
+
203
+ return { chunks: uniqueChunks, errors };
204
+ }
205
+
206
+ function extractFunctionChunk(node, kind, lines, code, nameOverride = null) {
207
+ const name = nameOverride || node.id?.name;
208
+ if (!name) return null;
209
+
210
+ const startLine = node.loc.start.line;
211
+ const endLine = node.loc.end.line;
212
+ const body = code.slice(node.start, node.end);
213
+
214
+ const params = node.params.map(param => {
215
+ if (param.type === "Identifier") return param.name;
216
+ if (param.type === "RestElement") return `...${param.argument.name}`;
217
+ return "_"; // Complex patterns
218
+ });
219
+
220
+ const signature = `${name}(${params.join(", ")})`;
221
+
222
+ return {
223
+ name,
224
+ kind,
225
+ signature,
226
+ body,
227
+ startLine,
228
+ endLine,
229
+ bodyNode: node.body || node, // Keep AST for call extraction
230
+ async: node.async === true,
231
+ generator: node.generator === true
232
+ };
233
+ }
234
+
235
+ function extractClassChunk(node, lines, code) {
236
+ const name = node.id?.name;
237
+ if (!name) return null;
238
+
239
+ const startLine = node.loc.start.line;
240
+ const endLine = node.loc.end.line;
241
+ const body = code.slice(node.start, node.end);
242
+
243
+ const superClass = node.superClass?.name || null;
244
+ const signature = superClass ? `class ${name} extends ${superClass}` : `class ${name}`;
245
+
246
+ return {
247
+ name,
248
+ kind: "class",
249
+ signature,
250
+ body,
251
+ startLine,
252
+ endLine,
253
+ bodyNode: node.body,
254
+ superClass
255
+ };
256
+ }
257
+
258
+ function extractClassMethods(classNode, lines, code) {
259
+ const methods = [];
260
+ const className = classNode.id?.name || "UnknownClass";
261
+
262
+ for (const member of classNode.body.body) {
263
+ if (member.type !== "MethodDefinition") continue;
264
+ if (member.key.type !== "Identifier") continue;
265
+
266
+ const methodName = member.key.name;
267
+ const fullName = `${className}.${methodName}`;
268
+
269
+ const startLine = member.loc.start.line;
270
+ const endLine = member.loc.end.line;
271
+ const body = code.slice(member.start, member.end);
272
+
273
+ const params = member.value.params.map(param => {
274
+ if (param.type === "Identifier") return param.name;
275
+ if (param.type === "RestElement") return `...${param.argument.name}`;
276
+ return "_";
277
+ });
278
+
279
+ const isStatic = member.static === true;
280
+ const prefix = isStatic ? "static " : "";
281
+ const signature = `${prefix}${methodName}(${params.join(", ")})`;
282
+
283
+ methods.push({
284
+ name: fullName,
285
+ kind: "method",
286
+ signature,
287
+ body,
288
+ startLine,
289
+ endLine,
290
+ bodyNode: member.value.body,
291
+ static: isStatic,
292
+ async: member.value.async === true,
293
+ generator: member.value.generator === true
294
+ });
295
+ }
296
+
297
+ return methods;
298
+ }
299
+
300
+ function extractCalls(bodyNode, code) {
301
+ if (!bodyNode) return [];
302
+
303
+ const calls = new Set();
304
+
305
+ try {
306
+ walkSimple(bodyNode, {
307
+ CallExpression(node) {
308
+ const callee = node.callee;
309
+
310
+ // Direct function call: foo()
311
+ if (callee.type === "Identifier") {
312
+ calls.add(callee.name);
313
+ }
314
+
315
+ // Method call: obj.method()
316
+ else if (callee.type === "MemberExpression") {
317
+ if (callee.property.type === "Identifier") {
318
+ const objName = getObjectName(callee.object);
319
+ if (objName) {
320
+ calls.add(`${objName}.${callee.property.name}`);
321
+ } else {
322
+ calls.add(callee.property.name);
323
+ }
324
+ }
325
+ }
326
+ }
327
+ });
328
+ } catch (error) {
329
+ // Ignore walk errors (incomplete AST)
330
+ }
331
+
332
+ return Array.from(calls).sort();
333
+ }
334
+
335
+ function getObjectName(node) {
336
+ if (node.type === "Identifier") {
337
+ return node.name;
338
+ }
339
+ if (node.type === "ThisExpression") {
340
+ return "this";
341
+ }
342
+ if (node.type === "MemberExpression" && node.property.type === "Identifier") {
343
+ return node.property.name;
344
+ }
345
+ return null;
346
+ }
347
+
348
+ function extractImports(ast) {
349
+ const imports = [];
350
+
351
+ walkSimple(ast, {
352
+ ImportDeclaration(node) {
353
+ if (node.source && node.source.type === "Literal") {
354
+ imports.push(node.source.value);
355
+ }
356
+ },
357
+
358
+ CallExpression(node) {
359
+ // Dynamic imports: import('module')
360
+ if (node.callee.type === "Import" && node.arguments[0]?.type === "Literal") {
361
+ imports.push(node.arguments[0].value);
362
+ }
363
+
364
+ // Require: require('module')
365
+ if (node.callee.type === "Identifier" && node.callee.name === "require") {
366
+ if (node.arguments[0]?.type === "Literal") {
367
+ imports.push(node.arguments[0].value);
368
+ }
369
+ }
370
+ }
371
+ });
372
+
373
+ return Array.from(new Set(imports)).sort();
374
+ }
375
+
376
+ // CLI interface for testing
377
+ if (import.meta.url === `file://${process.argv[1]}`) {
378
+ const fs = await import("node:fs");
379
+ const filePath = process.argv[2];
380
+
381
+ if (!filePath) {
382
+ console.error("Usage: javascript.mjs <file.js>");
383
+ process.exit(1);
384
+ }
385
+
386
+ const code = fs.readFileSync(filePath, "utf8");
387
+ const result = parseCode(code, filePath, "javascript");
388
+
389
+ console.log(JSON.stringify(result, null, 2));
390
+ }
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "parsers",
3
+ "version": "1.0.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "parsers",
9
+ "version": "1.0.0",
10
+ "license": "ISC",
11
+ "dependencies": {
12
+ "acorn": "^8.16.0",
13
+ "acorn-typescript": "^1.4.13",
14
+ "acorn-walk": "^8.3.5"
15
+ }
16
+ },
17
+ "node_modules/acorn": {
18
+ "version": "8.16.0",
19
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
20
+ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
21
+ "license": "MIT",
22
+ "bin": {
23
+ "acorn": "bin/acorn"
24
+ },
25
+ "engines": {
26
+ "node": ">=0.4.0"
27
+ }
28
+ },
29
+ "node_modules/acorn-typescript": {
30
+ "version": "1.4.13",
31
+ "resolved": "https://registry.npmjs.org/acorn-typescript/-/acorn-typescript-1.4.13.tgz",
32
+ "integrity": "sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==",
33
+ "license": "MIT",
34
+ "peerDependencies": {
35
+ "acorn": ">=8.9.0"
36
+ }
37
+ },
38
+ "node_modules/acorn-walk": {
39
+ "version": "8.3.5",
40
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz",
41
+ "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==",
42
+ "license": "MIT",
43
+ "dependencies": {
44
+ "acorn": "^8.11.0"
45
+ },
46
+ "engines": {
47
+ "node": ">=0.4.0"
48
+ }
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "parsers",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "keywords": [],
10
+ "author": "",
11
+ "license": "ISC",
12
+ "dependencies": {
13
+ "acorn": "^8.16.0",
14
+ "acorn-typescript": "^1.4.13",
15
+ "acorn-walk": "^8.3.5"
16
+ }
17
+ }