@danielblomma/cortex-mcp 0.4.5 → 0.6.5

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 (62) 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 +4 -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 +2227 -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/rust.mjs +515 -0
  57. package/scaffold/scripts/parsers/sql.mjs +137 -0
  58. package/scaffold/scripts/parsers/vbnet.mjs +143 -0
  59. package/scaffold/scripts/status.sh +0 -7
  60. package/scaffold/scripts/capture-note.sh +0 -55
  61. package/scaffold/scripts/plan-state-engine.cjs +0 -310
  62. package/scaffold/scripts/plan-state.sh +0 -71
@@ -4,41 +4,21 @@
4
4
  * Extracts functions, methods, classes and call relationships
5
5
  */
6
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"]);
7
+ let parseAst;
8
+ let discoverChunks;
9
+ let extractCalls;
10
+ let collectStaticImports;
11
+ let extractImportsForChunk;
12
+ let fallbackParseCode = null;
13
+
14
+ try {
15
+ ({ parseAst } = await import("./javascript/ast.mjs"));
16
+ ({ discoverChunks } = await import("./javascript/chunks.mjs"));
17
+ ({ extractCalls } = await import("./javascript/calls.mjs"));
18
+ ({ collectStaticImports, extractImportsForChunk } = await import("./javascript/imports.mjs"));
19
+ } catch {
20
+ ({ parseCode: fallbackParseCode } = await import("../../../scripts/parsers/javascript.mjs"));
21
+ }
42
22
 
43
23
  /**
44
24
  * Parse JavaScript/TypeScript code and extract chunks + calls
@@ -48,329 +28,26 @@ const CHUNK_KINDS = new Set(["function", "method", "class", "const", "let", "var
48
28
  * @returns {Object} { chunks: Array, errors: Array }
49
29
  */
50
30
  export function parseCode(code, filePath, language = "javascript") {
51
- const chunks = [];
52
- const errors = [];
53
- const lines = code.split(/\r?\n/);
31
+ if (fallbackParseCode) {
32
+ return fallbackParseCode(code, filePath, language);
33
+ }
54
34
 
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
- });
35
+ const { ast, errors } = parseAst(code, filePath);
36
+ if (!ast) {
72
37
  return { chunks: [], errors };
73
38
  }
74
39
 
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
- },
40
+ const staticImports = collectStaticImports(ast);
41
+ const chunks = discoverChunks(ast, code, language);
103
42
 
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
43
  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
- }
44
+ chunk.calls = extractCalls(chunk.callNode);
45
+ chunk.imports = extractImportsForChunk(chunk.importNode, staticImports);
46
+ delete chunk.callNode;
47
+ delete chunk.importNode;
193
48
  }
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
49
 
373
- return Array.from(new Set(imports)).sort();
50
+ return { chunks, errors };
374
51
  }
375
52
 
376
53
  // CLI interface for testing
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * .NET resources/settings parser for Cortex.
4
+ * Extracts .resx and .settings entries as chunks.
5
+ */
6
+
7
+ const SQL_REFERENCE_PATTERNS = [
8
+ /\bexec(?:ute)?\s+([#@]?[A-Za-z0-9_[\].]+)/gi,
9
+ /\bfrom\s+([#@]?[A-Za-z0-9_[\].]+)/gi,
10
+ /\bjoin\s+([#@]?[A-Za-z0-9_[\].]+)/gi,
11
+ /\bupdate\s+([#@]?[A-Za-z0-9_[\].]+)/gi,
12
+ /\binsert\s+into\s+([#@]?[A-Za-z0-9_[\].]+)/gi,
13
+ /\bdelete\s+from\s+([#@]?[A-Za-z0-9_[\].]+)/gi,
14
+ /\bmerge\s+into\s+([#@]?[A-Za-z0-9_[\].]+)/gi
15
+ ];
16
+
17
+ function countLinesBefore(text, index) {
18
+ let line = 1;
19
+ for (let i = 0; i < index; i += 1) {
20
+ if (text[i] === "\n") {
21
+ line += 1;
22
+ }
23
+ }
24
+ return line;
25
+ }
26
+
27
+ function decodeXmlEntities(value) {
28
+ return String(value)
29
+ .replace(/&quot;/g, '"')
30
+ .replace(/&apos;/g, "'")
31
+ .replace(/&lt;/g, "<")
32
+ .replace(/&gt;/g, ">")
33
+ .replace(/&amp;/g, "&");
34
+ }
35
+
36
+ function normalizeEntryKey(value) {
37
+ return String(value ?? "")
38
+ .trim()
39
+ .toLowerCase()
40
+ .replace(/[^a-z0-9]+/g, "_")
41
+ .replace(/^_+|_+$/g, "");
42
+ }
43
+
44
+ function normalizeSqlName(value) {
45
+ return String(value ?? "")
46
+ .trim()
47
+ .replace(/[;"`]/g, "")
48
+ .replace(/\[(.+?)\]/g, "$1")
49
+ .replace(/\s+/g, "")
50
+ .replace(/^\.+|\.+$/g, "")
51
+ .replace(/\.\.+/g, ".")
52
+ .toLowerCase();
53
+ }
54
+
55
+ function extractSqlRefs(text) {
56
+ const refs = new Set();
57
+ const normalized = normalizeSqlName(text);
58
+ if (/^[a-z0-9_.]+$/i.test(normalized) && normalized.includes(".")) {
59
+ refs.add(normalized);
60
+ }
61
+
62
+ for (const pattern of SQL_REFERENCE_PATTERNS) {
63
+ let match;
64
+ while ((match = pattern.exec(text)) !== null) {
65
+ const name = normalizeSqlName(match[1]);
66
+ if (!name || name.startsWith("@") || name.startsWith("#")) {
67
+ continue;
68
+ }
69
+ refs.add(name);
70
+ }
71
+ }
72
+
73
+ return [...refs];
74
+ }
75
+
76
+ function parseResx(code, language) {
77
+ const chunks = [];
78
+ const pattern = /<data\b[^>]*\bname="([^"]+)"[^>]*>([\s\S]*?)<\/data>/gi;
79
+ let match;
80
+
81
+ while ((match = pattern.exec(code)) !== null) {
82
+ const originalKey = decodeXmlEntities(match[1]).trim();
83
+ const normalizedKey = normalizeEntryKey(originalKey);
84
+ if (!normalizedKey) {
85
+ continue;
86
+ }
87
+
88
+ const valueMatch = match[2].match(/<value>([\s\S]*?)<\/value>/i);
89
+ const value = decodeXmlEntities(valueMatch?.[1] ?? "").trim();
90
+ const body = match[0];
91
+ const startIndex = match.index ?? 0;
92
+ const endIndex = startIndex + body.length;
93
+
94
+ chunks.push({
95
+ name: `resource.${normalizedKey}`,
96
+ kind: "resource_entry",
97
+ signature: `resource ${originalKey}`.trim(),
98
+ body,
99
+ startLine: countLinesBefore(code, startIndex),
100
+ endLine: countLinesBefore(code, Math.max(startIndex, endIndex - 1)),
101
+ language,
102
+ resourceKey: originalKey,
103
+ description: value,
104
+ imports: [],
105
+ calls: extractSqlRefs(value)
106
+ });
107
+ }
108
+
109
+ return chunks;
110
+ }
111
+
112
+ function parseSettings(code, language) {
113
+ const chunks = [];
114
+ const pattern = /<Setting\b[^>]*\bName="([^"]+)"[^>]*>([\s\S]*?)<\/Setting>/gi;
115
+ let match;
116
+
117
+ while ((match = pattern.exec(code)) !== null) {
118
+ const originalKey = decodeXmlEntities(match[1]).trim();
119
+ const normalizedKey = normalizeEntryKey(originalKey);
120
+ if (!normalizedKey) {
121
+ continue;
122
+ }
123
+
124
+ const valueMatch = match[2].match(/<Value(?:\s[^>]*)?>([\s\S]*?)<\/Value>/i);
125
+ const value = decodeXmlEntities(valueMatch?.[1] ?? "").trim();
126
+ const body = match[0];
127
+ const startIndex = match.index ?? 0;
128
+ const endIndex = startIndex + body.length;
129
+
130
+ chunks.push({
131
+ name: `setting.${normalizedKey}`,
132
+ kind: "setting_entry",
133
+ signature: `setting ${originalKey}`.trim(),
134
+ body,
135
+ startLine: countLinesBefore(code, startIndex),
136
+ endLine: countLinesBefore(code, Math.max(startIndex, endIndex - 1)),
137
+ language,
138
+ resourceKey: originalKey,
139
+ description: value,
140
+ imports: [],
141
+ calls: extractSqlRefs(value)
142
+ });
143
+ }
144
+
145
+ return chunks;
146
+ }
147
+
148
+ export function parseCode(code, filePath, language = "resource") {
149
+ const chunks = language === "settings" ? parseSettings(code, language) : parseResx(code, language);
150
+ return { chunks, errors: [] };
151
+ }
152
+
153
+ if (import.meta.url === `file://${process.argv[1]}`) {
154
+ const fs = await import("node:fs");
155
+ const filePath = process.argv[2];
156
+
157
+ if (!filePath) {
158
+ console.error("Usage: resources.mjs <file.{resx,settings}>");
159
+ process.exit(1);
160
+ }
161
+
162
+ const code = fs.readFileSync(filePath, "utf8");
163
+ const language = filePath.toLowerCase().endsWith(".settings") ? "settings" : "resource";
164
+ const result = parseCode(code, filePath, language);
165
+ console.log(JSON.stringify(result, null, 2));
166
+ }