@danielblomma/cortex-mcp 0.4.5 → 0.6.4

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 (61) hide show
  1. package/README.md +38 -42
  2. package/bin/cortex.mjs +32 -60
  3. package/package.json +15 -3
  4. package/scaffold/.context/ontology.cypher +47 -0
  5. package/scaffold/.githooks/post-commit +14 -0
  6. package/scaffold/.githooks/post-rewrite +23 -0
  7. package/scaffold/mcp/package-lock.json +16 -16
  8. package/scaffold/mcp/package.json +3 -1
  9. package/scaffold/mcp/src/contextEntities.ts +311 -0
  10. package/scaffold/mcp/src/defaults.ts +6 -0
  11. package/scaffold/mcp/src/embed.ts +163 -37
  12. package/scaffold/mcp/src/frontmatter.ts +39 -0
  13. package/scaffold/mcp/src/graph.ts +253 -130
  14. package/scaffold/mcp/src/graphMetrics.ts +12 -0
  15. package/scaffold/mcp/src/impactPresentation.ts +202 -0
  16. package/scaffold/mcp/src/impactRanking.ts +237 -0
  17. package/scaffold/mcp/src/impactResponse.ts +47 -0
  18. package/scaffold/mcp/src/impactResults.ts +173 -0
  19. package/scaffold/mcp/src/impactSeed.ts +33 -0
  20. package/scaffold/mcp/src/impactTraversal.ts +83 -0
  21. package/scaffold/mcp/src/jsonl.ts +34 -0
  22. package/scaffold/mcp/src/loadGraph.ts +345 -86
  23. package/scaffold/mcp/src/paths.ts +17 -1
  24. package/scaffold/mcp/src/presets.ts +137 -0
  25. package/scaffold/mcp/src/relatedResponse.ts +30 -0
  26. package/scaffold/mcp/src/relatedTraversal.ts +101 -0
  27. package/scaffold/mcp/src/rules.ts +27 -0
  28. package/scaffold/mcp/src/search.ts +186 -455
  29. package/scaffold/mcp/src/searchCore.ts +274 -0
  30. package/scaffold/mcp/src/searchResults.ts +133 -0
  31. package/scaffold/mcp/src/server.ts +95 -3
  32. package/scaffold/mcp/src/types.ts +82 -3
  33. package/scaffold/scripts/context.sh +12 -46
  34. package/scaffold/scripts/dashboard.mjs +797 -0
  35. package/scaffold/scripts/dashboard.sh +13 -0
  36. package/scaffold/scripts/ingest.mjs +2219 -59
  37. package/scaffold/scripts/install-git-hooks.sh +3 -1
  38. package/scaffold/scripts/memory-compile.mjs +232 -0
  39. package/scaffold/scripts/memory-compile.sh +20 -0
  40. package/scaffold/scripts/memory-lint.mjs +375 -0
  41. package/scaffold/scripts/memory-lint.sh +20 -0
  42. package/scaffold/scripts/parsers/config.mjs +178 -0
  43. package/scaffold/scripts/parsers/cpp.mjs +316 -0
  44. package/scaffold/scripts/parsers/dotnet/VbNetParser/Program.cs +374 -0
  45. package/scaffold/scripts/parsers/dotnet/VbNetParser/VbNetParser.csproj +13 -0
  46. package/scaffold/scripts/parsers/javascript/ast.mjs +61 -0
  47. package/scaffold/scripts/parsers/javascript/calls.mjs +53 -0
  48. package/scaffold/scripts/parsers/javascript/chunks.mjs +388 -0
  49. package/scaffold/scripts/parsers/javascript/imports.mjs +162 -0
  50. package/scaffold/scripts/parsers/javascript/patterns.mjs +82 -0
  51. package/scaffold/scripts/parsers/javascript/scope-analysis.mjs +3 -0
  52. package/scaffold/scripts/parsers/javascript/scope-builder.mjs +305 -0
  53. package/scaffold/scripts/parsers/javascript/scope-resolver.mjs +82 -0
  54. package/scaffold/scripts/parsers/javascript.mjs +27 -350
  55. package/scaffold/scripts/parsers/resources.mjs +166 -0
  56. package/scaffold/scripts/parsers/sql.mjs +137 -0
  57. package/scaffold/scripts/parsers/vbnet.mjs +143 -0
  58. package/scaffold/scripts/status.sh +0 -7
  59. package/scaffold/scripts/capture-note.sh +0 -55
  60. package/scaffold/scripts/plan-state-engine.cjs +0 -310
  61. package/scaffold/scripts/plan-state.sh +0 -71
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Conditional VB.NET parser bridge for Cortex.
4
+ *
5
+ * Uses a Roslyn sidecar via `dotnet run` when a .NET runtime is available.
6
+ * If no runtime exists, callers should skip structured chunk extraction and
7
+ * fall back to plain file-level indexing.
8
+ */
9
+
10
+ import path from "node:path";
11
+ import { fileURLToPath } from "node:url";
12
+ import { spawnSync } from "node:child_process";
13
+
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = path.dirname(__filename);
16
+ const DEFAULT_DOTNET_COMMAND = "dotnet";
17
+ const DEFAULT_PROJECT_PATH = path.join(__dirname, "dotnet", "VbNetParser", "VbNetParser.csproj");
18
+
19
+ let runtimeCache = null;
20
+
21
+ function getDotnetCommand() {
22
+ const override = process.env.CORTEX_DOTNET_CMD;
23
+ return override && override.trim().length > 0 ? override.trim() : DEFAULT_DOTNET_COMMAND;
24
+ }
25
+
26
+ function getProjectPath() {
27
+ const override = process.env.CORTEX_VBNET_PARSER_PROJECT;
28
+ return override && override.trim().length > 0 ? override.trim() : DEFAULT_PROJECT_PATH;
29
+ }
30
+
31
+ export function resetVbNetParserRuntimeCache() {
32
+ runtimeCache = null;
33
+ }
34
+
35
+ export function getVbNetParserRuntime() {
36
+ if (runtimeCache) {
37
+ return runtimeCache;
38
+ }
39
+
40
+ const command = getDotnetCommand();
41
+ const versionProbe = spawnSync(command, ["--version"], {
42
+ encoding: "utf8",
43
+ timeout: 5000
44
+ });
45
+
46
+ if (versionProbe.error || versionProbe.status !== 0) {
47
+ runtimeCache = {
48
+ available: false,
49
+ command,
50
+ projectPath: getProjectPath(),
51
+ reason:
52
+ versionProbe.error?.message ||
53
+ versionProbe.stderr?.trim() ||
54
+ "dotnet runtime not available"
55
+ };
56
+ return runtimeCache;
57
+ }
58
+
59
+ runtimeCache = {
60
+ available: true,
61
+ command,
62
+ projectPath: getProjectPath(),
63
+ version: versionProbe.stdout.trim()
64
+ };
65
+ return runtimeCache;
66
+ }
67
+
68
+ export function isVbNetParserAvailable() {
69
+ return getVbNetParserRuntime().available;
70
+ }
71
+
72
+ export function parseCode(code, filePath, language = "vbnet") {
73
+ const runtime = getVbNetParserRuntime();
74
+ if (!runtime.available) {
75
+ return { chunks: [], errors: [] };
76
+ }
77
+
78
+ const args = [
79
+ "run",
80
+ "--project",
81
+ runtime.projectPath,
82
+ "--configuration",
83
+ "Release",
84
+ "--",
85
+ "--stdin",
86
+ "--file",
87
+ filePath,
88
+ "--language",
89
+ language
90
+ ];
91
+
92
+ const result = spawnSync(runtime.command, args, {
93
+ input: code,
94
+ encoding: "utf8",
95
+ timeout: 30000,
96
+ maxBuffer: 10 * 1024 * 1024
97
+ });
98
+
99
+ if (result.error || result.status !== 0) {
100
+ return {
101
+ chunks: [],
102
+ errors: [
103
+ {
104
+ message:
105
+ result.error?.message ||
106
+ result.stderr?.trim() ||
107
+ `VB.NET parser failed with exit code ${result.status ?? "unknown"}`
108
+ }
109
+ ]
110
+ };
111
+ }
112
+
113
+ try {
114
+ const parsed = JSON.parse(result.stdout);
115
+ return {
116
+ chunks: Array.isArray(parsed.chunks) ? parsed.chunks : [],
117
+ errors: Array.isArray(parsed.errors) ? parsed.errors : []
118
+ };
119
+ } catch (error) {
120
+ return {
121
+ chunks: [],
122
+ errors: [
123
+ {
124
+ message: `VB.NET parser returned invalid JSON: ${error instanceof Error ? error.message : String(error)}`
125
+ }
126
+ ]
127
+ };
128
+ }
129
+ }
130
+
131
+ if (import.meta.url === `file://${process.argv[1]}`) {
132
+ const fs = await import("node:fs");
133
+ const filePath = process.argv[2];
134
+
135
+ if (!filePath) {
136
+ console.error("Usage: vbnet.mjs <file.vb>");
137
+ process.exit(1);
138
+ }
139
+
140
+ const code = fs.readFileSync(filePath, "utf8");
141
+ const result = parseCode(code, filePath, "vbnet");
142
+ console.log(JSON.stringify(result, null, 2));
143
+ }
@@ -287,10 +287,3 @@ try {
287
287
  console.log(`[status] cortex_update_check=unavailable (${message})`);
288
288
  }
289
289
  ' "$REPO_ROOT" "$REPO_ROOT/.context/cache" "${CORTEX_CLI_VERSION:-}"
290
-
291
- PLAN_SCRIPT="$REPO_ROOT/scripts/plan-state.sh"
292
- if [[ -x "$PLAN_SCRIPT" ]]; then
293
- if ! "$PLAN_SCRIPT" show; then
294
- echo "[plan] warning: failed to read plan state"
295
- fi
296
- fi
@@ -1,55 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
- NOTES_DIR="$REPO_ROOT/.context/notes"
6
- DECISIONS_DIR="$REPO_ROOT/.context/decisions"
7
-
8
- TITLE="${1:-}"
9
- if [[ -z "$TITLE" ]]; then
10
- echo "Usage: ./scripts/context.sh note <title> [text]"
11
- exit 1
12
- fi
13
- shift || true
14
-
15
- BODY="${*:-}"
16
- if [[ -z "$BODY" && ! -t 0 ]]; then
17
- BODY="$(cat)"
18
- fi
19
-
20
- mkdir -p "$NOTES_DIR" "$DECISIONS_DIR"
21
-
22
- DATE_UTC="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
23
- DATE_PREFIX="$(date -u +"%Y-%m-%d")"
24
- SLUG="$(echo "$TITLE" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//; s/-+/-/g')"
25
- if [[ -z "$SLUG" ]]; then
26
- SLUG="note"
27
- fi
28
- FILE_PATH="$NOTES_DIR/${DATE_PREFIX}-${SLUG}.md"
29
-
30
- if [[ -f "$FILE_PATH" ]]; then
31
- FILE_PATH="$NOTES_DIR/${DATE_PREFIX}-${SLUG}-$(date -u +"%H%M%S").md"
32
- fi
33
-
34
- {
35
- echo "---"
36
- echo "title: \"$TITLE\""
37
- echo "created_at: \"$DATE_UTC\""
38
- echo "kind: \"team-note\""
39
- echo "source_of_truth: false"
40
- echo "trust_level: 60"
41
- echo "status: active"
42
- echo "---"
43
- echo
44
- if [[ -n "$BODY" ]]; then
45
- echo "$BODY"
46
- else
47
- echo "_Skriv ner bakgrund, beslut, edge-cases och länkar här._"
48
- fi
49
- echo
50
- echo "## Follow-up"
51
- echo "- [ ] Convert to ADR in .context/decisions/ if this becomes a long-term rule."
52
- } > "$FILE_PATH"
53
-
54
- echo "[note] wrote $FILE_PATH"
55
- echo "[note] run: ./scripts/context.sh update"
@@ -1,310 +0,0 @@
1
- const fs = require("node:fs");
2
- const path = require("node:path");
3
-
4
- const statePath = process.argv[2];
5
- const action = process.argv[3] || "show";
6
- const arg = process.argv[4] || "";
7
-
8
- const STEP_DEFS = [
9
- {
10
- id: "initialize",
11
- title: "Initialize repository context",
12
- command: "cortex init --bootstrap"
13
- },
14
- {
15
- id: "connect",
16
- title: "Connect MCP clients (Codex/Claude)",
17
- command: "cortex connect"
18
- },
19
- {
20
- id: "update",
21
- title: "Refresh context while coding",
22
- command: "cortex update"
23
- },
24
- {
25
- id: "note",
26
- title: "Capture decisions and TODOs",
27
- command: "cortex note \"title\" \"details\""
28
- }
29
- ];
30
-
31
- const COMMAND_STEP_MAP = {
32
- init: ["initialize"],
33
- bootstrap: ["initialize", "update"],
34
- connect: ["connect"],
35
- update: ["update"],
36
- refresh: ["update"],
37
- ingest: ["update"],
38
- embed: ["update"],
39
- "graph-load": ["update"],
40
- note: ["note"]
41
- };
42
-
43
- function nowIso() {
44
- return new Date().toISOString();
45
- }
46
-
47
- function createDefaultState(now) {
48
- return {
49
- version: 1,
50
- created_at: now,
51
- updated_at: now,
52
- last_command: null,
53
- current_step: STEP_DEFS[0].title,
54
- next_command: STEP_DEFS[0].command,
55
- steps: STEP_DEFS.map((step) => ({
56
- id: step.id,
57
- title: step.title,
58
- command: step.command,
59
- status: "pending",
60
- updated_at: null
61
- })),
62
- history: [],
63
- next_todo_id: 1,
64
- todos: []
65
- };
66
- }
67
-
68
- function loadState() {
69
- if (!fs.existsSync(statePath)) {
70
- return null;
71
- }
72
-
73
- const parsed = JSON.parse(fs.readFileSync(statePath, "utf8"));
74
- if (!Array.isArray(parsed.steps)) {
75
- throw new Error("Invalid plan state: steps is missing");
76
- }
77
-
78
- if (!Array.isArray(parsed.history)) {
79
- parsed.history = [];
80
- }
81
-
82
- if (!Array.isArray(parsed.todos)) {
83
- parsed.todos = [];
84
- }
85
-
86
- if (!Number.isInteger(parsed.next_todo_id) || parsed.next_todo_id < 1) {
87
- parsed.next_todo_id = parsed.todos.reduce((max, todo) => Math.max(max, Number(todo.id) || 0), 0) + 1;
88
- }
89
-
90
- for (const def of STEP_DEFS) {
91
- if (!parsed.steps.find((step) => step.id === def.id)) {
92
- parsed.steps.push({
93
- id: def.id,
94
- title: def.title,
95
- command: def.command,
96
- status: "pending",
97
- updated_at: null
98
- });
99
- }
100
- }
101
-
102
- return parsed;
103
- }
104
-
105
- function saveState(state) {
106
- fs.mkdirSync(path.dirname(statePath), { recursive: true });
107
- fs.writeFileSync(statePath, `${JSON.stringify(state, null, 2)}\n`, "utf8");
108
- }
109
-
110
- function updateDerivedFields(state) {
111
- const nextStep = state.steps.find((step) => step.status !== "completed");
112
- if (nextStep) {
113
- state.current_step = nextStep.title;
114
- state.next_command = nextStep.command;
115
- return;
116
- }
117
-
118
- state.current_step = "Keep context fresh and capture decisions/TODOs";
119
- state.next_command = "cortex update";
120
- }
121
-
122
- function appendHistory(state, command, at) {
123
- state.last_command = {
124
- name: command,
125
- at
126
- };
127
- state.history.unshift({ command, at });
128
- state.history = state.history.slice(0, 100);
129
- }
130
-
131
- function markStepCompleted(state, stepId, at) {
132
- const step = state.steps.find((item) => item.id === stepId);
133
- if (!step) {
134
- return;
135
- }
136
- step.status = "completed";
137
- step.updated_at = at;
138
- }
139
-
140
- function ensureState() {
141
- const existing = loadState();
142
- if (existing) {
143
- return existing;
144
- }
145
- const now = nowIso();
146
- const created = createDefaultState(now);
147
- saveState(created);
148
- return created;
149
- }
150
-
151
- function parseTodoId(raw) {
152
- const cleaned = String(raw || "").trim().replace(/^#/, "");
153
- const parsed = Number(cleaned);
154
- if (!Number.isInteger(parsed) || parsed < 1) {
155
- return null;
156
- }
157
- return parsed;
158
- }
159
-
160
- function printSummary(state) {
161
- const openTodos = state.todos.filter((todo) => todo.status === "open");
162
- const doneTodos = state.todos.filter((todo) => todo.status === "done");
163
- const stepState = state.steps.map((step) => `${step.id}=${step.status}`).join(" ");
164
-
165
- console.log(`[plan] current_step=${state.current_step}`);
166
- console.log(`[plan] next_command=${state.next_command}`);
167
- console.log(`[plan] steps ${stepState}`);
168
-
169
- if (state.last_command && state.last_command.name) {
170
- console.log(`[plan] last_command=${state.last_command.name} at=${state.last_command.at}`);
171
- }
172
-
173
- console.log(`[todo] open=${openTodos.length} done=${doneTodos.length}`);
174
- if (openTodos.length > 0) {
175
- console.log(`[todo] next=#${openTodos[0].id} ${openTodos[0].text}`);
176
- }
177
- }
178
-
179
- try {
180
- if (action === "ensure") {
181
- ensureState();
182
- process.exit(0);
183
- }
184
-
185
- if (action === "reset") {
186
- const now = nowIso();
187
- const reset = createDefaultState(now);
188
- saveState(reset);
189
- console.log(`[plan] reset ${statePath}`);
190
- process.exit(0);
191
- }
192
-
193
- const state = ensureState();
194
- const now = nowIso();
195
-
196
- if (action === "event") {
197
- const commandName = arg.trim();
198
- if (!commandName) {
199
- throw new Error("Usage: plan-state.sh event <command>");
200
- }
201
-
202
- const stepsToMark = COMMAND_STEP_MAP[commandName] || [];
203
- for (const stepId of stepsToMark) {
204
- markStepCompleted(state, stepId, now);
205
- }
206
-
207
- appendHistory(state, commandName, now);
208
- state.updated_at = now;
209
- updateDerivedFields(state);
210
- saveState(state);
211
- process.exit(0);
212
- }
213
-
214
- if (action === "show") {
215
- updateDerivedFields(state);
216
- printSummary(state);
217
- process.exit(0);
218
- }
219
-
220
- if (action === "todo-add") {
221
- const text = arg.trim();
222
- if (!text) {
223
- throw new Error('Usage: plan-state.sh todo add "<text>"');
224
- }
225
-
226
- const todo = {
227
- id: state.next_todo_id,
228
- text,
229
- status: "open",
230
- created_at: now,
231
- updated_at: now,
232
- completed_at: null
233
- };
234
-
235
- state.todos.push(todo);
236
- state.next_todo_id += 1;
237
- markStepCompleted(state, "note", now);
238
- appendHistory(state, "todo:add", now);
239
- state.updated_at = now;
240
- updateDerivedFields(state);
241
- saveState(state);
242
- console.log(`[todo] added #${todo.id} ${todo.text}`);
243
- process.exit(0);
244
- }
245
-
246
- if (action === "todo-list") {
247
- const openTodos = state.todos.filter((todo) => todo.status === "open");
248
- const doneTodos = state.todos.filter((todo) => todo.status === "done");
249
-
250
- if (openTodos.length === 0 && doneTodos.length === 0) {
251
- console.log("[todo] no todos");
252
- process.exit(0);
253
- }
254
-
255
- for (const todo of openTodos) {
256
- console.log(`[todo] [open] #${todo.id} ${todo.text}`);
257
- }
258
- for (const todo of doneTodos) {
259
- console.log(`[todo] [done] #${todo.id} ${todo.text}`);
260
- }
261
- process.exit(0);
262
- }
263
-
264
- if (action === "todo-done" || action === "todo-reopen" || action === "todo-remove") {
265
- const todoId = parseTodoId(arg);
266
- if (!todoId) {
267
- throw new Error("Usage: plan-state.sh todo <done|reopen|remove> <id>");
268
- }
269
-
270
- const index = state.todos.findIndex((todo) => Number(todo.id) === todoId);
271
- if (index === -1) {
272
- throw new Error(`TODO #${todoId} not found`);
273
- }
274
-
275
- const todo = state.todos[index];
276
- if (action === "todo-remove") {
277
- state.todos.splice(index, 1);
278
- appendHistory(state, "todo:remove", now);
279
- state.updated_at = now;
280
- updateDerivedFields(state);
281
- saveState(state);
282
- console.log(`[todo] removed #${todoId}`);
283
- process.exit(0);
284
- }
285
-
286
- if (action === "todo-done") {
287
- todo.status = "done";
288
- todo.completed_at = now;
289
- appendHistory(state, "todo:done", now);
290
- console.log(`[todo] done #${todoId} ${todo.text}`);
291
- } else {
292
- todo.status = "open";
293
- todo.completed_at = null;
294
- appendHistory(state, "todo:reopen", now);
295
- console.log(`[todo] reopened #${todoId} ${todo.text}`);
296
- }
297
-
298
- todo.updated_at = now;
299
- state.updated_at = now;
300
- updateDerivedFields(state);
301
- saveState(state);
302
- process.exit(0);
303
- }
304
-
305
- throw new Error(`Unknown plan-state action: ${action}`);
306
- } catch (error) {
307
- const message = error instanceof Error ? error.message : String(error);
308
- process.stderr.write(`${message}\n`);
309
- process.exit(1);
310
- }
@@ -1,71 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
- PLAN_DIR="$REPO_ROOT/.context/plan"
7
- STATE_FILE="$PLAN_DIR/state.json"
8
-
9
- SUBCOMMAND="${1:-show}"
10
- if [[ $# -gt 0 ]]; then
11
- shift
12
- fi
13
-
14
- run_state_tool() {
15
- node "$SCRIPT_DIR/plan-state-engine.cjs" "$STATE_FILE" "$@"
16
- }
17
-
18
- mkdir -p "$PLAN_DIR"
19
-
20
- case "$SUBCOMMAND" in
21
- show)
22
- run_state_tool show
23
- ;;
24
- event)
25
- EVENT_NAME="${1:-}"
26
- if [[ -z "$EVENT_NAME" ]]; then
27
- echo "Usage: plan-state.sh event <command-name>"
28
- exit 1
29
- fi
30
- run_state_tool event "$EVENT_NAME"
31
- ;;
32
- reset)
33
- run_state_tool reset
34
- ;;
35
- todo)
36
- TODO_ACTION="${1:-list}"
37
- if [[ $# -gt 0 ]]; then
38
- shift
39
- fi
40
- case "$TODO_ACTION" in
41
- add)
42
- TODO_TEXT="${*:-}"
43
- run_state_tool todo-add "$TODO_TEXT"
44
- ;;
45
- list)
46
- run_state_tool todo-list
47
- ;;
48
- done)
49
- TODO_ID="${1:-}"
50
- run_state_tool todo-done "$TODO_ID"
51
- ;;
52
- reopen)
53
- TODO_ID="${1:-}"
54
- run_state_tool todo-reopen "$TODO_ID"
55
- ;;
56
- remove)
57
- TODO_ID="${1:-}"
58
- run_state_tool todo-remove "$TODO_ID"
59
- ;;
60
- *)
61
- # Keep ergonomic behavior: unknown first token is treated as todo text.
62
- run_state_tool todo-add "$TODO_ACTION ${*:-}"
63
- ;;
64
- esac
65
- ;;
66
- *)
67
- echo "Unknown subcommand: $SUBCOMMAND"
68
- echo "Usage: plan-state.sh [show|event <name>|reset|todo <action> [args]]"
69
- exit 1
70
- ;;
71
- esac