@danielblomma/cortex-mcp 0.4.2 → 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.
- package/README.md +64 -16
- package/bin/cortex.mjs +32 -60
- package/package.json +17 -3
- package/scaffold/.context/ontology.cypher +47 -0
- package/scaffold/.githooks/post-commit +14 -0
- package/scaffold/.githooks/post-rewrite +23 -0
- package/scaffold/mcp/package-lock.json +19 -23
- package/scaffold/mcp/package.json +3 -1
- package/scaffold/mcp/src/contextEntities.ts +311 -0
- package/scaffold/mcp/src/defaults.ts +6 -0
- package/scaffold/mcp/src/embed.ts +163 -37
- package/scaffold/mcp/src/frontmatter.ts +39 -0
- package/scaffold/mcp/src/graph.ts +330 -109
- package/scaffold/mcp/src/graphMetrics.ts +12 -0
- package/scaffold/mcp/src/impactPresentation.ts +202 -0
- package/scaffold/mcp/src/impactRanking.ts +237 -0
- package/scaffold/mcp/src/impactResponse.ts +47 -0
- package/scaffold/mcp/src/impactResults.ts +173 -0
- package/scaffold/mcp/src/impactSeed.ts +33 -0
- package/scaffold/mcp/src/impactTraversal.ts +83 -0
- package/scaffold/mcp/src/jsonl.ts +34 -0
- package/scaffold/mcp/src/loadGraph.ts +345 -86
- package/scaffold/mcp/src/paths.ts +24 -2
- package/scaffold/mcp/src/presets.ts +137 -0
- package/scaffold/mcp/src/relatedResponse.ts +30 -0
- package/scaffold/mcp/src/relatedTraversal.ts +101 -0
- package/scaffold/mcp/src/rules.ts +27 -0
- package/scaffold/mcp/src/search.ts +191 -355
- package/scaffold/mcp/src/searchCore.ts +274 -0
- package/scaffold/mcp/src/searchResults.ts +133 -0
- package/scaffold/mcp/src/server.ts +95 -3
- package/scaffold/mcp/src/types.ts +99 -3
- package/scaffold/scripts/context.sh +12 -46
- package/scaffold/scripts/dashboard.mjs +797 -0
- package/scaffold/scripts/dashboard.sh +13 -0
- package/scaffold/scripts/ingest.mjs +2219 -59
- package/scaffold/scripts/install-git-hooks.sh +3 -1
- package/scaffold/scripts/memory-compile.mjs +232 -0
- package/scaffold/scripts/memory-compile.sh +20 -0
- package/scaffold/scripts/memory-lint.mjs +375 -0
- package/scaffold/scripts/memory-lint.sh +20 -0
- package/scaffold/scripts/parsers/config.mjs +178 -0
- package/scaffold/scripts/parsers/cpp.mjs +316 -0
- package/scaffold/scripts/parsers/dotnet/VbNetParser/Program.cs +374 -0
- package/scaffold/scripts/parsers/dotnet/VbNetParser/VbNetParser.csproj +13 -0
- package/scaffold/scripts/parsers/javascript/ast.mjs +61 -0
- package/scaffold/scripts/parsers/javascript/calls.mjs +53 -0
- package/scaffold/scripts/parsers/javascript/chunks.mjs +388 -0
- package/scaffold/scripts/parsers/javascript/imports.mjs +162 -0
- package/scaffold/scripts/parsers/javascript/patterns.mjs +82 -0
- package/scaffold/scripts/parsers/javascript/scope-analysis.mjs +3 -0
- package/scaffold/scripts/parsers/javascript/scope-builder.mjs +305 -0
- package/scaffold/scripts/parsers/javascript/scope-resolver.mjs +82 -0
- package/scaffold/scripts/parsers/javascript.mjs +27 -350
- package/scaffold/scripts/parsers/resources.mjs +166 -0
- package/scaffold/scripts/parsers/sql.mjs +137 -0
- package/scaffold/scripts/parsers/vbnet.mjs +143 -0
- package/scaffold/scripts/status.sh +15 -8
- package/scaffold/scripts/capture-note.sh +0 -55
- package/scaffold/scripts/plan-state-engine.cjs +0 -310
- package/scaffold/scripts/plan-state.sh +0 -71
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* .NET config parser for Cortex.
|
|
4
|
+
* Extracts connection strings and app settings from .config files as chunks.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
function countLinesBefore(text, index) {
|
|
8
|
+
let line = 1;
|
|
9
|
+
for (let i = 0; i < index; i += 1) {
|
|
10
|
+
if (text[i] === "\n") {
|
|
11
|
+
line += 1;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return line;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function decodeXmlEntities(value) {
|
|
18
|
+
return String(value)
|
|
19
|
+
.replace(/"/g, '"')
|
|
20
|
+
.replace(/'/g, "'")
|
|
21
|
+
.replace(/</g, "<")
|
|
22
|
+
.replace(/>/g, ">")
|
|
23
|
+
.replace(/&/g, "&");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function normalizeConfigKey(value) {
|
|
27
|
+
return String(value ?? "")
|
|
28
|
+
.trim()
|
|
29
|
+
.toLowerCase()
|
|
30
|
+
.replace(/[^a-z0-9]+/g, "_")
|
|
31
|
+
.replace(/^_+|_+$/g, "");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function parseAttributes(raw) {
|
|
35
|
+
const attrs = new Map();
|
|
36
|
+
const pattern = /\b([A-Za-z_:][A-Za-z0-9_:\-]*)="([^"]*)"/g;
|
|
37
|
+
let match;
|
|
38
|
+
while ((match = pattern.exec(raw)) !== null) {
|
|
39
|
+
attrs.set(match[1], decodeXmlEntities(match[2]));
|
|
40
|
+
}
|
|
41
|
+
return attrs;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function parseConnectionStringParts(value) {
|
|
45
|
+
const parts = new Map();
|
|
46
|
+
for (const segment of String(value ?? "").split(";")) {
|
|
47
|
+
const trimmed = segment.trim();
|
|
48
|
+
if (!trimmed) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const separatorIndex = trimmed.indexOf("=");
|
|
52
|
+
if (separatorIndex === -1) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const key = trimmed.slice(0, separatorIndex).trim().toLowerCase();
|
|
56
|
+
const partValue = trimmed.slice(separatorIndex + 1).trim();
|
|
57
|
+
if (!key || !partValue) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
parts.set(key, partValue);
|
|
61
|
+
}
|
|
62
|
+
return parts;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function buildConnectionStringDescription(connectionString, providerName) {
|
|
66
|
+
const summary = String(connectionString ?? "").trim();
|
|
67
|
+
const provider = String(providerName ?? "").trim();
|
|
68
|
+
if (!provider) {
|
|
69
|
+
return summary;
|
|
70
|
+
}
|
|
71
|
+
return summary.includes(provider) ? summary : `${summary}; provider=${provider}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function parseCode(code, filePath, language = "config") {
|
|
75
|
+
const chunks = [];
|
|
76
|
+
const addPattern = /<add\b([^>]+?)\/?>/gi;
|
|
77
|
+
let match;
|
|
78
|
+
|
|
79
|
+
while ((match = addPattern.exec(code)) !== null) {
|
|
80
|
+
const attrs = parseAttributes(match[1]);
|
|
81
|
+
const key = attrs.get("key");
|
|
82
|
+
const name = attrs.get("name");
|
|
83
|
+
const connectionString = attrs.get("connectionString");
|
|
84
|
+
const providerName = attrs.get("providerName");
|
|
85
|
+
const value = attrs.get("value");
|
|
86
|
+
|
|
87
|
+
let kind = "";
|
|
88
|
+
let configKey = "";
|
|
89
|
+
let descriptionValue = "";
|
|
90
|
+
|
|
91
|
+
if (name && connectionString) {
|
|
92
|
+
kind = "connection_string";
|
|
93
|
+
configKey = name;
|
|
94
|
+
descriptionValue = buildConnectionStringDescription(connectionString, providerName);
|
|
95
|
+
} else if (key) {
|
|
96
|
+
kind = "app_setting";
|
|
97
|
+
configKey = key;
|
|
98
|
+
descriptionValue = value ?? "";
|
|
99
|
+
} else {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const normalizedKey = normalizeConfigKey(configKey);
|
|
104
|
+
if (!normalizedKey) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const body = match[0];
|
|
109
|
+
const startIndex = match.index ?? 0;
|
|
110
|
+
const endIndex = startIndex + body.length;
|
|
111
|
+
chunks.push({
|
|
112
|
+
name: `${kind}.${normalizedKey}`,
|
|
113
|
+
kind,
|
|
114
|
+
signature: `${kind} ${configKey}`.trim(),
|
|
115
|
+
body,
|
|
116
|
+
startLine: countLinesBefore(code, startIndex),
|
|
117
|
+
endLine: countLinesBefore(code, Math.max(startIndex, endIndex - 1)),
|
|
118
|
+
language,
|
|
119
|
+
description: descriptionValue,
|
|
120
|
+
configKey,
|
|
121
|
+
imports: [],
|
|
122
|
+
calls: []
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
if (kind === "connection_string") {
|
|
126
|
+
const connectionParts = parseConnectionStringParts(connectionString);
|
|
127
|
+
const server =
|
|
128
|
+
connectionParts.get("server") ??
|
|
129
|
+
connectionParts.get("data source") ??
|
|
130
|
+
connectionParts.get("addr") ??
|
|
131
|
+
connectionParts.get("address") ??
|
|
132
|
+
connectionParts.get("network address") ??
|
|
133
|
+
"";
|
|
134
|
+
const database =
|
|
135
|
+
connectionParts.get("database") ?? connectionParts.get("initial catalog") ?? "";
|
|
136
|
+
const provider = connectionParts.get("provider") ?? String(providerName ?? "").trim();
|
|
137
|
+
const targetName = `database_target.${normalizedKey}`;
|
|
138
|
+
const targetSummary = [
|
|
139
|
+
database ? `database=${database}` : "",
|
|
140
|
+
server ? `server=${server}` : "",
|
|
141
|
+
provider ? `provider=${provider}` : ""
|
|
142
|
+
]
|
|
143
|
+
.filter(Boolean)
|
|
144
|
+
.join("; ");
|
|
145
|
+
|
|
146
|
+
chunks[chunks.length - 1].calls = [targetName];
|
|
147
|
+
chunks.push({
|
|
148
|
+
name: targetName,
|
|
149
|
+
kind: "database_target",
|
|
150
|
+
signature: `database_target ${configKey}`.trim(),
|
|
151
|
+
body,
|
|
152
|
+
startLine: countLinesBefore(code, startIndex),
|
|
153
|
+
endLine: countLinesBefore(code, Math.max(startIndex, endIndex - 1)),
|
|
154
|
+
language,
|
|
155
|
+
description: targetSummary || connectionString,
|
|
156
|
+
configKey,
|
|
157
|
+
imports: [],
|
|
158
|
+
calls: []
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return { chunks, errors: [] };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
167
|
+
const fs = await import("node:fs");
|
|
168
|
+
const filePath = process.argv[2];
|
|
169
|
+
|
|
170
|
+
if (!filePath) {
|
|
171
|
+
console.error("Usage: config.mjs <file.config>");
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const code = fs.readFileSync(filePath, "utf8");
|
|
176
|
+
const result = parseCode(code, filePath, "config");
|
|
177
|
+
console.log(JSON.stringify(result, null, 2));
|
|
178
|
+
}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Capability-gated C/C++ parser bridge for Cortex.
|
|
4
|
+
*
|
|
5
|
+
* Uses `clang`/`clang++` as the runtime capability signal. When available,
|
|
6
|
+
* this parser extracts a lightweight first-pass structure from source text:
|
|
7
|
+
* functions, methods, classes/structs/enums, local call names, and quoted
|
|
8
|
+
* #include references. When the runtime is unavailable, callers should skip
|
|
9
|
+
* structured chunk extraction and fall back to file-level indexing.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import { spawnSync } from "node:child_process";
|
|
14
|
+
|
|
15
|
+
const DEFAULT_CLANG_COMMANDS = ["clang++", "clang"];
|
|
16
|
+
const CONTROL_KEYWORDS = new Set(["if", "for", "while", "switch", "catch", "return", "sizeof"]);
|
|
17
|
+
const CALL_KEYWORDS = new Set([
|
|
18
|
+
"if",
|
|
19
|
+
"for",
|
|
20
|
+
"while",
|
|
21
|
+
"switch",
|
|
22
|
+
"catch",
|
|
23
|
+
"return",
|
|
24
|
+
"sizeof",
|
|
25
|
+
"alignof",
|
|
26
|
+
"static_cast",
|
|
27
|
+
"reinterpret_cast",
|
|
28
|
+
"const_cast",
|
|
29
|
+
"dynamic_cast"
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
const RECORD_PATTERN = /\b(class|struct|enum)\s+([A-Za-z_]\w*)[^;{]*\{/g;
|
|
33
|
+
const FUNCTION_PATTERN =
|
|
34
|
+
/^(?!\s*(?:if|for|while|switch|catch|return)\b)\s*(?:template\s*<[\s\S]*?>\s*)?(?:(?:inline|static|constexpr|virtual|friend|extern|typename|auto|unsigned|signed|long|short|const|volatile|mutable|[\w:<>~*&]+\s+)+)?([A-Za-z_~]\w*(?:::\w+)*)\s*\(([\s\S]{0,240}?)\)\s*(?:const\b\s*)?(?:noexcept\b\s*)?(?:->\s*[^{};]+)?\s*\{/gm;
|
|
35
|
+
|
|
36
|
+
let runtimeCache = null;
|
|
37
|
+
|
|
38
|
+
function getCompilerCandidates() {
|
|
39
|
+
const override = process.env.CORTEX_CLANG_CMD?.trim();
|
|
40
|
+
return override ? [override] : DEFAULT_CLANG_COMMANDS;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function countLinesBefore(text, index) {
|
|
44
|
+
let line = 1;
|
|
45
|
+
for (let i = 0; i < index; i += 1) {
|
|
46
|
+
if (text[i] === "\n") {
|
|
47
|
+
line += 1;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return line;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function normalizeWhitespace(value) {
|
|
54
|
+
return value.replace(/\s+/g, " ").trim();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function findMatchingBrace(text, openBraceIndex) {
|
|
58
|
+
if (openBraceIndex < 0 || text[openBraceIndex] !== "{") {
|
|
59
|
+
return -1;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let depth = 0;
|
|
63
|
+
let inSingleLineComment = false;
|
|
64
|
+
let inBlockComment = false;
|
|
65
|
+
let inString = false;
|
|
66
|
+
let stringChar = "";
|
|
67
|
+
|
|
68
|
+
for (let index = openBraceIndex; index < text.length; index += 1) {
|
|
69
|
+
const current = text[index];
|
|
70
|
+
const next = text[index + 1];
|
|
71
|
+
|
|
72
|
+
if (inSingleLineComment) {
|
|
73
|
+
if (current === "\n") {
|
|
74
|
+
inSingleLineComment = false;
|
|
75
|
+
}
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (inBlockComment) {
|
|
80
|
+
if (current === "*" && next === "/") {
|
|
81
|
+
inBlockComment = false;
|
|
82
|
+
index += 1;
|
|
83
|
+
}
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (inString) {
|
|
88
|
+
if (current === "\\" && next) {
|
|
89
|
+
index += 1;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (current === stringChar) {
|
|
93
|
+
inString = false;
|
|
94
|
+
stringChar = "";
|
|
95
|
+
}
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (current === "/" && next === "/") {
|
|
100
|
+
inSingleLineComment = true;
|
|
101
|
+
index += 1;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (current === "/" && next === "*") {
|
|
106
|
+
inBlockComment = true;
|
|
107
|
+
index += 1;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (current === '"' || current === "'") {
|
|
112
|
+
inString = true;
|
|
113
|
+
stringChar = current;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (current === "{") {
|
|
118
|
+
depth += 1;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (current === "}") {
|
|
123
|
+
depth -= 1;
|
|
124
|
+
if (depth === 0) {
|
|
125
|
+
return index;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return -1;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function extractQuotedIncludes(code) {
|
|
134
|
+
const includes = [];
|
|
135
|
+
const pattern = /^\s*#include\s+"([^"]+)"/gm;
|
|
136
|
+
let match;
|
|
137
|
+
while ((match = pattern.exec(code)) !== null) {
|
|
138
|
+
includes.push(match[1].trim());
|
|
139
|
+
}
|
|
140
|
+
return [...new Set(includes)];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function collectCallNames(body, chunkName) {
|
|
144
|
+
const refs = new Set();
|
|
145
|
+
const ownTailName = chunkName.split("::").pop() || chunkName;
|
|
146
|
+
const pattern = /\b([A-Za-z_~]\w*(?:::\w+)*)\s*\(/g;
|
|
147
|
+
let match;
|
|
148
|
+
while ((match = pattern.exec(body)) !== null) {
|
|
149
|
+
const name = match[1];
|
|
150
|
+
const tailName = name.split("::").pop() || name;
|
|
151
|
+
if (CALL_KEYWORDS.has(tailName) || tailName === ownTailName) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
refs.add(tailName);
|
|
155
|
+
}
|
|
156
|
+
return [...refs];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function buildSignature(source) {
|
|
160
|
+
const snippet = normalizeWhitespace(source);
|
|
161
|
+
const braceIndex = snippet.indexOf("{");
|
|
162
|
+
return (braceIndex === -1 ? snippet : snippet.slice(0, braceIndex)).trim();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function extractRecordChunks(code, language) {
|
|
166
|
+
const chunks = [];
|
|
167
|
+
let match;
|
|
168
|
+
while ((match = RECORD_PATTERN.exec(code)) !== null) {
|
|
169
|
+
const kind = match[1];
|
|
170
|
+
const name = match[2];
|
|
171
|
+
const openBraceIndex = code.indexOf("{", match.index);
|
|
172
|
+
const closeBraceIndex = findMatchingBrace(code, openBraceIndex);
|
|
173
|
+
if (closeBraceIndex === -1) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const bodyEndIndex =
|
|
178
|
+
code[closeBraceIndex + 1] === ";" ? closeBraceIndex + 2 : closeBraceIndex + 1;
|
|
179
|
+
const body = code.slice(match.index, bodyEndIndex);
|
|
180
|
+
const startLine = countLinesBefore(code, match.index);
|
|
181
|
+
const endLine = countLinesBefore(code, Math.max(match.index, bodyEndIndex - 1));
|
|
182
|
+
|
|
183
|
+
chunks.push({
|
|
184
|
+
name,
|
|
185
|
+
kind,
|
|
186
|
+
signature: buildSignature(body),
|
|
187
|
+
body,
|
|
188
|
+
startLine,
|
|
189
|
+
endLine,
|
|
190
|
+
language,
|
|
191
|
+
calls: [],
|
|
192
|
+
imports: []
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
return chunks;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function extractFunctionChunks(code, language, recordChunks, includes) {
|
|
199
|
+
const chunks = [];
|
|
200
|
+
let match;
|
|
201
|
+
while ((match = FUNCTION_PATTERN.exec(code)) !== null) {
|
|
202
|
+
const rawName = match[1];
|
|
203
|
+
const tailName = rawName.split("::").pop() || rawName;
|
|
204
|
+
if (!tailName || CONTROL_KEYWORDS.has(tailName)) {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const openBraceOffset = match[0].lastIndexOf("{");
|
|
209
|
+
const openBraceIndex = match.index + openBraceOffset;
|
|
210
|
+
const closeBraceIndex = findMatchingBrace(code, openBraceIndex);
|
|
211
|
+
if (closeBraceIndex === -1) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const startIndex = match.index;
|
|
216
|
+
const bodyEndIndex = closeBraceIndex + 1;
|
|
217
|
+
const body = code.slice(startIndex, bodyEndIndex);
|
|
218
|
+
const startLine = countLinesBefore(code, startIndex);
|
|
219
|
+
const endLine = countLinesBefore(code, Math.max(startIndex, bodyEndIndex - 1));
|
|
220
|
+
const owningRecord = recordChunks.find(
|
|
221
|
+
(record) => startLine >= record.startLine && endLine <= record.endLine
|
|
222
|
+
);
|
|
223
|
+
const isMethod = rawName.includes("::") || Boolean(owningRecord);
|
|
224
|
+
const name =
|
|
225
|
+
rawName.includes("::") || !owningRecord ? rawName : `${owningRecord.name}::${rawName}`;
|
|
226
|
+
|
|
227
|
+
chunks.push({
|
|
228
|
+
name,
|
|
229
|
+
kind: isMethod ? "method" : "function",
|
|
230
|
+
signature: buildSignature(body),
|
|
231
|
+
body,
|
|
232
|
+
startLine,
|
|
233
|
+
endLine,
|
|
234
|
+
language,
|
|
235
|
+
calls: collectCallNames(body, name),
|
|
236
|
+
imports: includes
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
return chunks;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function resetCppParserRuntimeCache() {
|
|
243
|
+
runtimeCache = null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function getCppParserRuntime() {
|
|
247
|
+
if (runtimeCache) {
|
|
248
|
+
return runtimeCache;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const candidates = getCompilerCandidates();
|
|
252
|
+
for (const command of candidates) {
|
|
253
|
+
const probe = spawnSync(command, ["--version"], {
|
|
254
|
+
encoding: "utf8",
|
|
255
|
+
timeout: 5000
|
|
256
|
+
});
|
|
257
|
+
if (!probe.error && probe.status === 0) {
|
|
258
|
+
runtimeCache = {
|
|
259
|
+
available: true,
|
|
260
|
+
command,
|
|
261
|
+
version: (probe.stdout || probe.stderr || "").trim()
|
|
262
|
+
};
|
|
263
|
+
return runtimeCache;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
runtimeCache = {
|
|
268
|
+
available: false,
|
|
269
|
+
command: candidates[0],
|
|
270
|
+
reason: `clang runtime not available (${candidates.join(", ")})`
|
|
271
|
+
};
|
|
272
|
+
return runtimeCache;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function isCppParserAvailable() {
|
|
276
|
+
return getCppParserRuntime().available;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function parseCode(code, filePath, language = "cpp") {
|
|
280
|
+
const runtime = getCppParserRuntime();
|
|
281
|
+
if (!runtime.available) {
|
|
282
|
+
return { chunks: [], errors: [] };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const normalizedLanguage = language === "c" ? "c" : "cpp";
|
|
286
|
+
const includes = extractQuotedIncludes(code);
|
|
287
|
+
const recordChunks = extractRecordChunks(code, normalizedLanguage);
|
|
288
|
+
const functionChunks = extractFunctionChunks(code, normalizedLanguage, recordChunks, includes);
|
|
289
|
+
const seen = new Set();
|
|
290
|
+
const chunks = [...recordChunks, ...functionChunks].filter((chunk) => {
|
|
291
|
+
const key = `${chunk.kind}|${chunk.name}|${chunk.startLine}|${chunk.endLine}`;
|
|
292
|
+
if (seen.has(key)) {
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
seen.add(key);
|
|
296
|
+
return true;
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
return { chunks, errors: [] };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
303
|
+
const fs = await import("node:fs");
|
|
304
|
+
const filePath = process.argv[2];
|
|
305
|
+
|
|
306
|
+
if (!filePath) {
|
|
307
|
+
console.error("Usage: cpp.mjs <file.{c,cpp,h,hpp}>");
|
|
308
|
+
process.exit(1);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
312
|
+
const language = ext === ".c" || ext === ".h" ? "c" : "cpp";
|
|
313
|
+
const code = fs.readFileSync(filePath, "utf8");
|
|
314
|
+
const result = parseCode(code, filePath, language);
|
|
315
|
+
console.log(JSON.stringify(result, null, 2));
|
|
316
|
+
}
|