@vohongtho.infotech/code-intel 0.4.0 → 0.5.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 +30 -8
- package/dist/cli/main.js +1764 -293
- package/dist/cli/main.js.map +1 -1
- package/dist/index.js +1276 -80
- package/dist/index.js.map +1 -1
- package/dist/web/assets/es-CnPQcqTr.js +10 -0
- package/dist/web/assets/{index-Ds0yq7oU.js → index-j-iO6isa.js} +23 -7
- package/dist/web/assets/index-rprt8Su_.css +2 -0
- package/dist/web/index.html +2 -2
- package/package.json +3 -1
- package/dist/web/assets/index-BcUIJvDD.css +0 -2
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRequire } from 'module';
|
|
2
2
|
import { fileURLToPath } from 'url';
|
|
3
|
-
import
|
|
3
|
+
import path27 from 'path';
|
|
4
4
|
import fs19, { existsSync } from 'fs';
|
|
5
5
|
import { Parser, Language, Query } from 'web-tree-sitter';
|
|
6
6
|
import { NodeSDK } from '@opentelemetry/sdk-node';
|
|
@@ -26,6 +26,7 @@ import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSche
|
|
|
26
26
|
import { Database, Connection } from '@ladybugdb/core';
|
|
27
27
|
import { execSync } from 'child_process';
|
|
28
28
|
import express from 'express';
|
|
29
|
+
import compression from 'compression';
|
|
29
30
|
import cors from 'cors';
|
|
30
31
|
import helmet from 'helmet';
|
|
31
32
|
import cookieParser from 'cookie-parser';
|
|
@@ -125,11 +126,11 @@ var init_shared = __esm({
|
|
|
125
126
|
}
|
|
126
127
|
});
|
|
127
128
|
function findBundledWasmDir() {
|
|
128
|
-
const fileDir =
|
|
129
|
+
const fileDir = path27.dirname(fileURLToPath(import.meta.url));
|
|
129
130
|
const candidates = [
|
|
130
|
-
|
|
131
|
+
path27.join(fileDir, "wasm"),
|
|
131
132
|
// dist/index.js → dist/wasm/
|
|
132
|
-
|
|
133
|
+
path27.join(fileDir, "../wasm")
|
|
133
134
|
// dist/cli/main.js → dist/wasm/
|
|
134
135
|
];
|
|
135
136
|
for (const candidate of candidates) {
|
|
@@ -170,7 +171,7 @@ function wasmPath(lang) {
|
|
|
170
171
|
}
|
|
171
172
|
const bundled = BUNDLED_WASM_MAP[lang];
|
|
172
173
|
if (bundled) {
|
|
173
|
-
const bundledPath =
|
|
174
|
+
const bundledPath = path27.join(_bundledWasmDir, bundled);
|
|
174
175
|
if (existsSync(bundledPath)) return bundledPath;
|
|
175
176
|
}
|
|
176
177
|
return null;
|
|
@@ -1161,7 +1162,7 @@ var init_logger = __esm({
|
|
|
1161
1162
|
};
|
|
1162
1163
|
}
|
|
1163
1164
|
/** Global log directory: ~/.code-intel/logs */
|
|
1164
|
-
static LOG_DIR =
|
|
1165
|
+
static LOG_DIR = path27.join(os12.homedir(), ".code-intel", "logs");
|
|
1165
1166
|
static getLogger() {
|
|
1166
1167
|
if (!_Logger.instance) {
|
|
1167
1168
|
const isProduction = process.env.NODE_ENV === "production";
|
|
@@ -1175,7 +1176,7 @@ var init_logger = __esm({
|
|
|
1175
1176
|
}
|
|
1176
1177
|
transports.push(
|
|
1177
1178
|
new DailyRotateFile({
|
|
1178
|
-
filename:
|
|
1179
|
+
filename: path27.join(_Logger.LOG_DIR, "%DATE%-code-intel.log"),
|
|
1179
1180
|
datePattern: "YYYY-MM-DD",
|
|
1180
1181
|
maxSize: "20m",
|
|
1181
1182
|
maxFiles: "14d"
|
|
@@ -1973,14 +1974,14 @@ var init_parse_phase = __esm({
|
|
|
1973
1974
|
const lang = detectLanguage(filePath);
|
|
1974
1975
|
if (!lang) {
|
|
1975
1976
|
if (context2.verbose) {
|
|
1976
|
-
const relativePath2 =
|
|
1977
|
+
const relativePath2 = path27.relative(context2.workspaceRoot, filePath);
|
|
1977
1978
|
logger_default.info(` [parse] skipped (no parser): ${relativePath2}`);
|
|
1978
1979
|
}
|
|
1979
1980
|
continue;
|
|
1980
1981
|
}
|
|
1981
1982
|
const source = context2.fileCache.get(filePath);
|
|
1982
1983
|
if (!source) continue;
|
|
1983
|
-
const relativePath =
|
|
1984
|
+
const relativePath = path27.relative(context2.workspaceRoot, filePath);
|
|
1984
1985
|
const fileNodeId = generateNodeId("file", relativePath, relativePath);
|
|
1985
1986
|
const fileNode = context2.graph.getNode(fileNodeId);
|
|
1986
1987
|
if (fileNode) {
|
|
@@ -2220,11 +2221,11 @@ var init_resolve_phase = __esm({
|
|
|
2220
2221
|
let heritageEdges = 0;
|
|
2221
2222
|
const fileIndex = /* @__PURE__ */ new Map();
|
|
2222
2223
|
for (const fp of filePaths) {
|
|
2223
|
-
const rel =
|
|
2224
|
+
const rel = path27.relative(workspaceRoot, fp);
|
|
2224
2225
|
fileIndex.set(rel, fp);
|
|
2225
2226
|
const noExt = rel.replace(/\.\w+$/, "");
|
|
2226
2227
|
if (!fileIndex.has(noExt)) fileIndex.set(noExt, fp);
|
|
2227
|
-
const base =
|
|
2228
|
+
const base = path27.basename(rel, path27.extname(rel));
|
|
2228
2229
|
if (!fileIndex.has(base)) fileIndex.set(base, fp);
|
|
2229
2230
|
}
|
|
2230
2231
|
const symbolIndex = /* @__PURE__ */ new Map();
|
|
@@ -2255,7 +2256,7 @@ var init_resolve_phase = __esm({
|
|
|
2255
2256
|
for (const filePath of filePaths) {
|
|
2256
2257
|
const lang = detectLanguage(filePath);
|
|
2257
2258
|
if (!lang) continue;
|
|
2258
|
-
const relativePath =
|
|
2259
|
+
const relativePath = path27.relative(workspaceRoot, filePath);
|
|
2259
2260
|
const fileNodeId = generateNodeId("file", relativePath, relativePath);
|
|
2260
2261
|
const source = fileCache.get(filePath);
|
|
2261
2262
|
if (!source) continue;
|
|
@@ -2268,13 +2269,13 @@ var init_resolve_phase = __esm({
|
|
|
2268
2269
|
let resolvedRelPath = null;
|
|
2269
2270
|
if (cleaned.startsWith(".")) {
|
|
2270
2271
|
const cleanedNoJs = cleaned.replace(/\.(js|jsx)$/, "");
|
|
2271
|
-
const fromDir =
|
|
2272
|
+
const fromDir = path27.dirname(relativePath);
|
|
2272
2273
|
for (const ext of ["", ".ts", ".tsx", ".js", ".jsx", ".py", ".java", ".go", "/index.ts", "/index.js"]) {
|
|
2273
|
-
const candidate =
|
|
2274
|
-
const normalized =
|
|
2274
|
+
const candidate = path27.join(fromDir, cleanedNoJs + ext);
|
|
2275
|
+
const normalized = path27.normalize(candidate);
|
|
2275
2276
|
if (fileIndex.has(normalized)) {
|
|
2276
2277
|
const absPath = fileIndex.get(normalized);
|
|
2277
|
-
resolvedRelPath =
|
|
2278
|
+
resolvedRelPath = path27.relative(workspaceRoot, absPath);
|
|
2278
2279
|
break;
|
|
2279
2280
|
}
|
|
2280
2281
|
}
|
|
@@ -2588,7 +2589,7 @@ __export(group_registry_exports, {
|
|
|
2588
2589
|
saveSyncResult: () => saveSyncResult
|
|
2589
2590
|
});
|
|
2590
2591
|
function groupFile(name) {
|
|
2591
|
-
return
|
|
2592
|
+
return path27.join(GROUPS_DIR, `${name}.json`);
|
|
2592
2593
|
}
|
|
2593
2594
|
function loadGroup(name) {
|
|
2594
2595
|
try {
|
|
@@ -2608,7 +2609,7 @@ function listGroups() {
|
|
|
2608
2609
|
if (!file.endsWith(".json") || file.endsWith(".sync.json")) continue;
|
|
2609
2610
|
try {
|
|
2610
2611
|
const g = JSON.parse(
|
|
2611
|
-
fs19.readFileSync(
|
|
2612
|
+
fs19.readFileSync(path27.join(GROUPS_DIR, file), "utf-8")
|
|
2612
2613
|
);
|
|
2613
2614
|
groups.push(g);
|
|
2614
2615
|
} catch {
|
|
@@ -2624,7 +2625,7 @@ function deleteGroup(name) {
|
|
|
2624
2625
|
} catch {
|
|
2625
2626
|
}
|
|
2626
2627
|
try {
|
|
2627
|
-
fs19.unlinkSync(
|
|
2628
|
+
fs19.unlinkSync(path27.join(GROUPS_DIR, `${name}.sync.json`));
|
|
2628
2629
|
} catch {
|
|
2629
2630
|
}
|
|
2630
2631
|
}
|
|
@@ -2657,14 +2658,14 @@ function removeMember(groupName, groupPath) {
|
|
|
2657
2658
|
function saveSyncResult(result) {
|
|
2658
2659
|
fs19.mkdirSync(GROUPS_DIR, { recursive: true });
|
|
2659
2660
|
fs19.writeFileSync(
|
|
2660
|
-
|
|
2661
|
+
path27.join(GROUPS_DIR, `${result.groupName}.sync.json`),
|
|
2661
2662
|
JSON.stringify(result, null, 2) + "\n"
|
|
2662
2663
|
);
|
|
2663
2664
|
}
|
|
2664
2665
|
function loadSyncResult(groupName) {
|
|
2665
2666
|
try {
|
|
2666
2667
|
return JSON.parse(
|
|
2667
|
-
fs19.readFileSync(
|
|
2668
|
+
fs19.readFileSync(path27.join(GROUPS_DIR, `${groupName}.sync.json`), "utf-8")
|
|
2668
2669
|
);
|
|
2669
2670
|
} catch {
|
|
2670
2671
|
return null;
|
|
@@ -2673,7 +2674,7 @@ function loadSyncResult(groupName) {
|
|
|
2673
2674
|
var GROUPS_DIR;
|
|
2674
2675
|
var init_group_registry = __esm({
|
|
2675
2676
|
"src/multi-repo/group-registry.ts"() {
|
|
2676
|
-
GROUPS_DIR =
|
|
2677
|
+
GROUPS_DIR = path27.join(os12.homedir(), ".code-intel", "groups");
|
|
2677
2678
|
}
|
|
2678
2679
|
});
|
|
2679
2680
|
|
|
@@ -2954,6 +2955,753 @@ var init_health_score = __esm({
|
|
|
2954
2955
|
}
|
|
2955
2956
|
});
|
|
2956
2957
|
|
|
2958
|
+
// src/query/gql-parser.ts
|
|
2959
|
+
var gql_parser_exports = {};
|
|
2960
|
+
__export(gql_parser_exports, {
|
|
2961
|
+
isGQLParseError: () => isGQLParseError,
|
|
2962
|
+
parseGQL: () => parseGQL
|
|
2963
|
+
});
|
|
2964
|
+
function isGQLParseError(v) {
|
|
2965
|
+
return v.type === "GQLParseError";
|
|
2966
|
+
}
|
|
2967
|
+
function tokenize(input) {
|
|
2968
|
+
const tokens = [];
|
|
2969
|
+
let i = 0;
|
|
2970
|
+
const len = input.length;
|
|
2971
|
+
while (i < len) {
|
|
2972
|
+
if (/\s/.test(input[i])) {
|
|
2973
|
+
i++;
|
|
2974
|
+
continue;
|
|
2975
|
+
}
|
|
2976
|
+
if (input[i] === "#") {
|
|
2977
|
+
while (i < len && input[i] !== "\n") i++;
|
|
2978
|
+
continue;
|
|
2979
|
+
}
|
|
2980
|
+
const pos = i;
|
|
2981
|
+
if (input[i] === '"' || input[i] === "'") {
|
|
2982
|
+
const quote = input[i];
|
|
2983
|
+
i++;
|
|
2984
|
+
let str = "";
|
|
2985
|
+
while (i < len && input[i] !== quote) {
|
|
2986
|
+
if (input[i] === "\\") {
|
|
2987
|
+
i++;
|
|
2988
|
+
if (i < len) {
|
|
2989
|
+
const esc = input[i];
|
|
2990
|
+
str += esc === "n" ? "\n" : esc === "t" ? " " : esc;
|
|
2991
|
+
i++;
|
|
2992
|
+
}
|
|
2993
|
+
} else {
|
|
2994
|
+
str += input[i++];
|
|
2995
|
+
}
|
|
2996
|
+
}
|
|
2997
|
+
if (i >= len) {
|
|
2998
|
+
return { type: "GQLParseError", message: `Unterminated string at position ${pos}`, pos };
|
|
2999
|
+
}
|
|
3000
|
+
i++;
|
|
3001
|
+
tokens.push({ kind: "STRING", value: str, pos });
|
|
3002
|
+
continue;
|
|
3003
|
+
}
|
|
3004
|
+
if (/[0-9]/.test(input[i])) {
|
|
3005
|
+
let num = "";
|
|
3006
|
+
while (i < len && /[0-9]/.test(input[i])) num += input[i++];
|
|
3007
|
+
tokens.push({ kind: "NUMBER", value: num, pos });
|
|
3008
|
+
continue;
|
|
3009
|
+
}
|
|
3010
|
+
if (input[i] === "[") {
|
|
3011
|
+
tokens.push({ kind: "LBRACKET", value: "[", pos });
|
|
3012
|
+
i++;
|
|
3013
|
+
continue;
|
|
3014
|
+
}
|
|
3015
|
+
if (input[i] === "]") {
|
|
3016
|
+
tokens.push({ kind: "RBRACKET", value: "]", pos });
|
|
3017
|
+
i++;
|
|
3018
|
+
continue;
|
|
3019
|
+
}
|
|
3020
|
+
if (input[i] === "*") {
|
|
3021
|
+
tokens.push({ kind: "STAR", value: "*", pos });
|
|
3022
|
+
i++;
|
|
3023
|
+
continue;
|
|
3024
|
+
}
|
|
3025
|
+
if (input[i] === "!" && input[i + 1] === "=") {
|
|
3026
|
+
tokens.push({ kind: "OPERATOR", value: "!=", pos });
|
|
3027
|
+
i += 2;
|
|
3028
|
+
continue;
|
|
3029
|
+
}
|
|
3030
|
+
if (input[i] === "=") {
|
|
3031
|
+
tokens.push({ kind: "OPERATOR", value: "=", pos });
|
|
3032
|
+
i++;
|
|
3033
|
+
continue;
|
|
3034
|
+
}
|
|
3035
|
+
if (/[a-zA-Z_]/.test(input[i])) {
|
|
3036
|
+
let ident = "";
|
|
3037
|
+
while (i < len && /[a-zA-Z0-9_]/.test(input[i])) ident += input[i++];
|
|
3038
|
+
const upper = ident.toUpperCase();
|
|
3039
|
+
if (upper === "CONTAINS" || upper === "STARTS_WITH" || upper === "IN") {
|
|
3040
|
+
tokens.push({ kind: "OPERATOR", value: upper, pos });
|
|
3041
|
+
} else if (KEYWORDS.has(upper)) {
|
|
3042
|
+
tokens.push({ kind: "KEYWORD", value: upper, pos });
|
|
3043
|
+
} else {
|
|
3044
|
+
tokens.push({ kind: "IDENT", value: ident, pos });
|
|
3045
|
+
}
|
|
3046
|
+
continue;
|
|
3047
|
+
}
|
|
3048
|
+
if (input[i] === ",") {
|
|
3049
|
+
i++;
|
|
3050
|
+
continue;
|
|
3051
|
+
}
|
|
3052
|
+
return {
|
|
3053
|
+
type: "GQLParseError",
|
|
3054
|
+
message: `Unexpected character '${input[i]}' at position ${i}`,
|
|
3055
|
+
pos: i
|
|
3056
|
+
};
|
|
3057
|
+
}
|
|
3058
|
+
tokens.push({ kind: "EOF", value: "", pos: len });
|
|
3059
|
+
return tokens;
|
|
3060
|
+
}
|
|
3061
|
+
function parseGQL(input) {
|
|
3062
|
+
const tokens = tokenize(input.trim());
|
|
3063
|
+
if (!Array.isArray(tokens)) return tokens;
|
|
3064
|
+
const parser = new Parser2(tokens);
|
|
3065
|
+
return parser.parse();
|
|
3066
|
+
}
|
|
3067
|
+
var KEYWORDS, Parser2;
|
|
3068
|
+
var init_gql_parser = __esm({
|
|
3069
|
+
"src/query/gql-parser.ts"() {
|
|
3070
|
+
KEYWORDS = /* @__PURE__ */ new Set([
|
|
3071
|
+
"FIND",
|
|
3072
|
+
"TRAVERSE",
|
|
3073
|
+
"PATH",
|
|
3074
|
+
"COUNT",
|
|
3075
|
+
"WHERE",
|
|
3076
|
+
"FROM",
|
|
3077
|
+
"TO",
|
|
3078
|
+
"IN",
|
|
3079
|
+
"BY",
|
|
3080
|
+
"AND",
|
|
3081
|
+
"NOT",
|
|
3082
|
+
"LIMIT",
|
|
3083
|
+
"OFFSET",
|
|
3084
|
+
"DEPTH",
|
|
3085
|
+
"GROUP",
|
|
3086
|
+
"CONTAINS",
|
|
3087
|
+
"STARTS_WITH",
|
|
3088
|
+
"CALLS",
|
|
3089
|
+
"IMPORTS",
|
|
3090
|
+
"EXTENDS",
|
|
3091
|
+
"IMPLEMENTS",
|
|
3092
|
+
"HAS_MEMBER",
|
|
3093
|
+
"ACCESSES",
|
|
3094
|
+
"OVERRIDES",
|
|
3095
|
+
"BELONGS_TO",
|
|
3096
|
+
"STEP_OF",
|
|
3097
|
+
"HANDLES",
|
|
3098
|
+
"CONTAINS_EDGE",
|
|
3099
|
+
"OUTGOING",
|
|
3100
|
+
"INCOMING",
|
|
3101
|
+
"BOTH"
|
|
3102
|
+
]);
|
|
3103
|
+
Parser2 = class {
|
|
3104
|
+
tokens;
|
|
3105
|
+
pos = 0;
|
|
3106
|
+
constructor(tokens) {
|
|
3107
|
+
this.tokens = tokens;
|
|
3108
|
+
}
|
|
3109
|
+
peek() {
|
|
3110
|
+
return this.tokens[this.pos];
|
|
3111
|
+
}
|
|
3112
|
+
consume() {
|
|
3113
|
+
return this.tokens[this.pos++];
|
|
3114
|
+
}
|
|
3115
|
+
expect(kind, value) {
|
|
3116
|
+
const tok = this.peek();
|
|
3117
|
+
if (tok.kind !== kind) {
|
|
3118
|
+
return {
|
|
3119
|
+
type: "GQLParseError",
|
|
3120
|
+
message: `Expected ${value ?? kind} but got '${tok.value}' at position ${tok.pos}`,
|
|
3121
|
+
pos: tok.pos,
|
|
3122
|
+
expected: value ?? kind,
|
|
3123
|
+
got: tok.value
|
|
3124
|
+
};
|
|
3125
|
+
}
|
|
3126
|
+
if (value !== void 0 && tok.value !== value) {
|
|
3127
|
+
return {
|
|
3128
|
+
type: "GQLParseError",
|
|
3129
|
+
message: `Expected '${value}' but got '${tok.value}' at position ${tok.pos}`,
|
|
3130
|
+
pos: tok.pos,
|
|
3131
|
+
expected: value,
|
|
3132
|
+
got: tok.value
|
|
3133
|
+
};
|
|
3134
|
+
}
|
|
3135
|
+
return this.consume();
|
|
3136
|
+
}
|
|
3137
|
+
matchKeyword(...values) {
|
|
3138
|
+
const tok = this.peek();
|
|
3139
|
+
return tok.kind === "KEYWORD" && values.includes(tok.value);
|
|
3140
|
+
}
|
|
3141
|
+
optionalKeyword(...values) {
|
|
3142
|
+
if (this.matchKeyword(...values)) {
|
|
3143
|
+
return this.consume();
|
|
3144
|
+
}
|
|
3145
|
+
return null;
|
|
3146
|
+
}
|
|
3147
|
+
/** Parse the node kind filter (IDENT, KEYWORD that's a kind, or STAR) */
|
|
3148
|
+
parseNodeKind() {
|
|
3149
|
+
const tok = this.peek();
|
|
3150
|
+
if (tok.kind === "STAR") {
|
|
3151
|
+
this.consume();
|
|
3152
|
+
return "*";
|
|
3153
|
+
}
|
|
3154
|
+
if (tok.kind === "IDENT" || tok.kind === "KEYWORD") {
|
|
3155
|
+
this.consume();
|
|
3156
|
+
return tok.value.toLowerCase();
|
|
3157
|
+
}
|
|
3158
|
+
return {
|
|
3159
|
+
type: "GQLParseError",
|
|
3160
|
+
message: `Expected node kind or '*' at position ${tok.pos}`,
|
|
3161
|
+
pos: tok.pos
|
|
3162
|
+
};
|
|
3163
|
+
}
|
|
3164
|
+
/** Parse a string value (STRING or IDENT) */
|
|
3165
|
+
parseStringValue() {
|
|
3166
|
+
const tok = this.peek();
|
|
3167
|
+
if (tok.kind === "STRING") {
|
|
3168
|
+
this.consume();
|
|
3169
|
+
return tok.value;
|
|
3170
|
+
}
|
|
3171
|
+
if (tok.kind === "IDENT" || tok.kind === "KEYWORD") {
|
|
3172
|
+
this.consume();
|
|
3173
|
+
return tok.value;
|
|
3174
|
+
}
|
|
3175
|
+
return {
|
|
3176
|
+
type: "GQLParseError",
|
|
3177
|
+
message: `Expected string value at position ${tok.pos}`,
|
|
3178
|
+
pos: tok.pos
|
|
3179
|
+
};
|
|
3180
|
+
}
|
|
3181
|
+
/** Parse an IN list: [ value, value, ... ] */
|
|
3182
|
+
parseInList() {
|
|
3183
|
+
const lb = this.expect("LBRACKET");
|
|
3184
|
+
if (isGQLParseError(lb)) return lb;
|
|
3185
|
+
const values = [];
|
|
3186
|
+
while (!this.matchKeyword() && this.peek().kind !== "RBRACKET" && this.peek().kind !== "EOF") {
|
|
3187
|
+
const v = this.parseStringValue();
|
|
3188
|
+
if (typeof v !== "string") return v;
|
|
3189
|
+
values.push(v);
|
|
3190
|
+
}
|
|
3191
|
+
const rb = this.expect("RBRACKET");
|
|
3192
|
+
if (isGQLParseError(rb)) return rb;
|
|
3193
|
+
return values;
|
|
3194
|
+
}
|
|
3195
|
+
/** Parse a single WHERE expression */
|
|
3196
|
+
parseWhereExpr() {
|
|
3197
|
+
const propTok = this.peek();
|
|
3198
|
+
if (propTok.kind !== "IDENT" && propTok.kind !== "KEYWORD") {
|
|
3199
|
+
return {
|
|
3200
|
+
type: "GQLParseError",
|
|
3201
|
+
message: `Expected property name at position ${propTok.pos}`,
|
|
3202
|
+
pos: propTok.pos
|
|
3203
|
+
};
|
|
3204
|
+
}
|
|
3205
|
+
this.consume();
|
|
3206
|
+
const property = propTok.value.toLowerCase();
|
|
3207
|
+
const opTok = this.peek();
|
|
3208
|
+
if (opTok.kind !== "OPERATOR") {
|
|
3209
|
+
return {
|
|
3210
|
+
type: "GQLParseError",
|
|
3211
|
+
message: `Expected operator (=, !=, CONTAINS, STARTS_WITH, IN) at position ${opTok.pos}`,
|
|
3212
|
+
pos: opTok.pos,
|
|
3213
|
+
expected: "operator",
|
|
3214
|
+
got: opTok.value
|
|
3215
|
+
};
|
|
3216
|
+
}
|
|
3217
|
+
this.consume();
|
|
3218
|
+
const operator = opTok.value;
|
|
3219
|
+
if (operator === "IN") {
|
|
3220
|
+
const list = this.parseInList();
|
|
3221
|
+
if (!Array.isArray(list)) return list;
|
|
3222
|
+
return { property, operator, value: list };
|
|
3223
|
+
}
|
|
3224
|
+
const val = this.parseStringValue();
|
|
3225
|
+
if (typeof val !== "string") return val;
|
|
3226
|
+
return { property, operator, value: val };
|
|
3227
|
+
}
|
|
3228
|
+
/** Parse WHERE clause: WHERE expr (AND expr)* */
|
|
3229
|
+
parseWhereClause() {
|
|
3230
|
+
const kw = this.expect("KEYWORD", "WHERE");
|
|
3231
|
+
if (isGQLParseError(kw)) return kw;
|
|
3232
|
+
const exprs = [];
|
|
3233
|
+
const first = this.parseWhereExpr();
|
|
3234
|
+
if ("type" in first && first.type === "GQLParseError") return first;
|
|
3235
|
+
exprs.push(first);
|
|
3236
|
+
while (this.matchKeyword("AND")) {
|
|
3237
|
+
this.consume();
|
|
3238
|
+
const expr = this.parseWhereExpr();
|
|
3239
|
+
if ("type" in expr && expr.type === "GQLParseError") return expr;
|
|
3240
|
+
exprs.push(expr);
|
|
3241
|
+
}
|
|
3242
|
+
return { exprs };
|
|
3243
|
+
}
|
|
3244
|
+
/** Parse FIND statement */
|
|
3245
|
+
parseFindStatement() {
|
|
3246
|
+
this.consume();
|
|
3247
|
+
const kind = this.parseNodeKind();
|
|
3248
|
+
if (typeof kind !== "string") return kind;
|
|
3249
|
+
let where;
|
|
3250
|
+
if (this.matchKeyword("WHERE")) {
|
|
3251
|
+
const w = this.parseWhereClause();
|
|
3252
|
+
if ("type" in w && w.type === "GQLParseError") return w;
|
|
3253
|
+
where = w;
|
|
3254
|
+
}
|
|
3255
|
+
let limit;
|
|
3256
|
+
let offset;
|
|
3257
|
+
while (this.matchKeyword("LIMIT", "OFFSET")) {
|
|
3258
|
+
const kw = this.consume();
|
|
3259
|
+
const numTok = this.peek();
|
|
3260
|
+
if (numTok.kind !== "NUMBER") {
|
|
3261
|
+
return {
|
|
3262
|
+
type: "GQLParseError",
|
|
3263
|
+
message: `Expected number after ${kw.value} at position ${numTok.pos}`,
|
|
3264
|
+
pos: numTok.pos
|
|
3265
|
+
};
|
|
3266
|
+
}
|
|
3267
|
+
this.consume();
|
|
3268
|
+
const n = parseInt(numTok.value, 10);
|
|
3269
|
+
if (kw.value === "LIMIT") limit = n;
|
|
3270
|
+
else offset = n;
|
|
3271
|
+
}
|
|
3272
|
+
return { type: "FIND", target: kind, where, limit, offset };
|
|
3273
|
+
}
|
|
3274
|
+
/** Parse TRAVERSE statement */
|
|
3275
|
+
parseTraverseStatement() {
|
|
3276
|
+
this.consume();
|
|
3277
|
+
const edgeTok = this.peek();
|
|
3278
|
+
if (edgeTok.kind !== "KEYWORD" && edgeTok.kind !== "IDENT") {
|
|
3279
|
+
return {
|
|
3280
|
+
type: "GQLParseError",
|
|
3281
|
+
message: `Expected edge kind after TRAVERSE at position ${edgeTok.pos}`,
|
|
3282
|
+
pos: edgeTok.pos
|
|
3283
|
+
};
|
|
3284
|
+
}
|
|
3285
|
+
this.consume();
|
|
3286
|
+
const edgeKind = edgeTok.value.toLowerCase();
|
|
3287
|
+
const fromKw = this.expect("KEYWORD", "FROM");
|
|
3288
|
+
if (isGQLParseError(fromKw)) return fromKw;
|
|
3289
|
+
const fromVal = this.parseStringValue();
|
|
3290
|
+
if (typeof fromVal !== "string") return fromVal;
|
|
3291
|
+
let depth;
|
|
3292
|
+
let direction;
|
|
3293
|
+
if (this.matchKeyword("DEPTH")) {
|
|
3294
|
+
this.consume();
|
|
3295
|
+
const numTok = this.peek();
|
|
3296
|
+
if (numTok.kind !== "NUMBER") {
|
|
3297
|
+
return {
|
|
3298
|
+
type: "GQLParseError",
|
|
3299
|
+
message: `Expected number after DEPTH at position ${numTok.pos}`,
|
|
3300
|
+
pos: numTok.pos
|
|
3301
|
+
};
|
|
3302
|
+
}
|
|
3303
|
+
this.consume();
|
|
3304
|
+
depth = parseInt(numTok.value, 10);
|
|
3305
|
+
}
|
|
3306
|
+
if (this.matchKeyword("OUTGOING", "INCOMING", "BOTH")) {
|
|
3307
|
+
direction = this.consume().value;
|
|
3308
|
+
}
|
|
3309
|
+
return { type: "TRAVERSE", edgeKind, from: fromVal, depth, direction };
|
|
3310
|
+
}
|
|
3311
|
+
/** Parse PATH statement */
|
|
3312
|
+
parsePathStatement() {
|
|
3313
|
+
this.consume();
|
|
3314
|
+
const fromKw = this.expect("KEYWORD", "FROM");
|
|
3315
|
+
if (isGQLParseError(fromKw)) return fromKw;
|
|
3316
|
+
const fromVal = this.parseStringValue();
|
|
3317
|
+
if (typeof fromVal !== "string") return fromVal;
|
|
3318
|
+
const toKw = this.expect("KEYWORD", "TO");
|
|
3319
|
+
if (isGQLParseError(toKw)) return toKw;
|
|
3320
|
+
const toVal = this.parseStringValue();
|
|
3321
|
+
if (typeof toVal !== "string") return toVal;
|
|
3322
|
+
return { type: "PATH", from: fromVal, to: toVal };
|
|
3323
|
+
}
|
|
3324
|
+
/** Parse COUNT statement */
|
|
3325
|
+
parseCountStatement() {
|
|
3326
|
+
this.consume();
|
|
3327
|
+
const kind = this.parseNodeKind();
|
|
3328
|
+
if (typeof kind !== "string") return kind;
|
|
3329
|
+
let where;
|
|
3330
|
+
if (this.matchKeyword("WHERE")) {
|
|
3331
|
+
const w = this.parseWhereClause();
|
|
3332
|
+
if ("type" in w && w.type === "GQLParseError") return w;
|
|
3333
|
+
where = w;
|
|
3334
|
+
}
|
|
3335
|
+
let groupBy;
|
|
3336
|
+
if (this.matchKeyword("GROUP")) {
|
|
3337
|
+
this.consume();
|
|
3338
|
+
const byKw = this.expect("KEYWORD", "BY");
|
|
3339
|
+
if (isGQLParseError(byKw)) return byKw;
|
|
3340
|
+
const propTok = this.peek();
|
|
3341
|
+
if (propTok.kind !== "IDENT" && propTok.kind !== "KEYWORD") {
|
|
3342
|
+
return {
|
|
3343
|
+
type: "GQLParseError",
|
|
3344
|
+
message: `Expected property name after GROUP BY at position ${propTok.pos}`,
|
|
3345
|
+
pos: propTok.pos
|
|
3346
|
+
};
|
|
3347
|
+
}
|
|
3348
|
+
this.consume();
|
|
3349
|
+
groupBy = propTok.value.toLowerCase();
|
|
3350
|
+
}
|
|
3351
|
+
return { type: "COUNT", target: kind, where, groupBy };
|
|
3352
|
+
}
|
|
3353
|
+
parse() {
|
|
3354
|
+
const tok = this.peek();
|
|
3355
|
+
if (tok.kind !== "KEYWORD") {
|
|
3356
|
+
return {
|
|
3357
|
+
type: "GQLParseError",
|
|
3358
|
+
message: `Expected FIND, TRAVERSE, PATH, or COUNT at position ${tok.pos}`,
|
|
3359
|
+
pos: tok.pos,
|
|
3360
|
+
expected: "FIND | TRAVERSE | PATH | COUNT",
|
|
3361
|
+
got: tok.value
|
|
3362
|
+
};
|
|
3363
|
+
}
|
|
3364
|
+
let result;
|
|
3365
|
+
switch (tok.value) {
|
|
3366
|
+
case "FIND":
|
|
3367
|
+
result = this.parseFindStatement();
|
|
3368
|
+
break;
|
|
3369
|
+
case "TRAVERSE":
|
|
3370
|
+
result = this.parseTraverseStatement();
|
|
3371
|
+
break;
|
|
3372
|
+
case "PATH":
|
|
3373
|
+
result = this.parsePathStatement();
|
|
3374
|
+
break;
|
|
3375
|
+
case "COUNT":
|
|
3376
|
+
result = this.parseCountStatement();
|
|
3377
|
+
break;
|
|
3378
|
+
default:
|
|
3379
|
+
return {
|
|
3380
|
+
type: "GQLParseError",
|
|
3381
|
+
message: `Unknown statement type '${tok.value}' at position ${tok.pos}`,
|
|
3382
|
+
pos: tok.pos,
|
|
3383
|
+
expected: "FIND | TRAVERSE | PATH | COUNT",
|
|
3384
|
+
got: tok.value
|
|
3385
|
+
};
|
|
3386
|
+
}
|
|
3387
|
+
if (isGQLParseError(result)) return result;
|
|
3388
|
+
const remaining = this.peek();
|
|
3389
|
+
if (remaining.kind !== "EOF") {
|
|
3390
|
+
return {
|
|
3391
|
+
type: "GQLParseError",
|
|
3392
|
+
message: `Unexpected token '${remaining.value}' at position ${remaining.pos}`,
|
|
3393
|
+
pos: remaining.pos,
|
|
3394
|
+
got: remaining.value
|
|
3395
|
+
};
|
|
3396
|
+
}
|
|
3397
|
+
return result;
|
|
3398
|
+
}
|
|
3399
|
+
};
|
|
3400
|
+
}
|
|
3401
|
+
});
|
|
3402
|
+
|
|
3403
|
+
// src/query/gql-executor.ts
|
|
3404
|
+
var gql_executor_exports = {};
|
|
3405
|
+
__export(gql_executor_exports, {
|
|
3406
|
+
executeGQL: () => executeGQL
|
|
3407
|
+
});
|
|
3408
|
+
function getNodeProperty(node, property) {
|
|
3409
|
+
switch (property) {
|
|
3410
|
+
case "name":
|
|
3411
|
+
return node.name;
|
|
3412
|
+
case "kind":
|
|
3413
|
+
return node.kind;
|
|
3414
|
+
case "filepath":
|
|
3415
|
+
case "filePath":
|
|
3416
|
+
return node.filePath;
|
|
3417
|
+
case "exported":
|
|
3418
|
+
return node.exported;
|
|
3419
|
+
case "language":
|
|
3420
|
+
return node.metadata?.language ?? void 0;
|
|
3421
|
+
case "cluster":
|
|
3422
|
+
return node.metadata?.cluster ?? void 0;
|
|
3423
|
+
default:
|
|
3424
|
+
return node.metadata?.[property] ?? void 0;
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
function evaluateExpr(node, expr) {
|
|
3428
|
+
const val = getNodeProperty(node, expr.property);
|
|
3429
|
+
if (val === void 0) return false;
|
|
3430
|
+
const strVal = String(val).toLowerCase();
|
|
3431
|
+
switch (expr.operator) {
|
|
3432
|
+
case "=":
|
|
3433
|
+
if (typeof expr.value === "string") {
|
|
3434
|
+
return strVal === expr.value.toLowerCase();
|
|
3435
|
+
}
|
|
3436
|
+
return false;
|
|
3437
|
+
case "!=":
|
|
3438
|
+
if (typeof expr.value === "string") {
|
|
3439
|
+
return strVal !== expr.value.toLowerCase();
|
|
3440
|
+
}
|
|
3441
|
+
return true;
|
|
3442
|
+
case "CONTAINS":
|
|
3443
|
+
if (typeof expr.value === "string") {
|
|
3444
|
+
return strVal.includes(expr.value.toLowerCase());
|
|
3445
|
+
}
|
|
3446
|
+
return false;
|
|
3447
|
+
case "STARTS_WITH":
|
|
3448
|
+
if (typeof expr.value === "string") {
|
|
3449
|
+
return strVal.startsWith(expr.value.toLowerCase());
|
|
3450
|
+
}
|
|
3451
|
+
return false;
|
|
3452
|
+
case "IN":
|
|
3453
|
+
if (Array.isArray(expr.value)) {
|
|
3454
|
+
return expr.value.some((v) => strVal === v.toLowerCase());
|
|
3455
|
+
}
|
|
3456
|
+
return false;
|
|
3457
|
+
default:
|
|
3458
|
+
return false;
|
|
3459
|
+
}
|
|
3460
|
+
}
|
|
3461
|
+
function evaluateWhere(node, where) {
|
|
3462
|
+
return where.exprs.every((expr) => evaluateExpr(node, expr));
|
|
3463
|
+
}
|
|
3464
|
+
function executeFIND(stmt, graph) {
|
|
3465
|
+
const start = Date.now();
|
|
3466
|
+
const limit = stmt.limit ?? 1e3;
|
|
3467
|
+
const offset = stmt.offset ?? 0;
|
|
3468
|
+
let totalCount = 0;
|
|
3469
|
+
let truncated = false;
|
|
3470
|
+
const allMatching = [];
|
|
3471
|
+
const deadline = start + EXECUTION_TIMEOUT_MS;
|
|
3472
|
+
for (const node of graph.allNodes()) {
|
|
3473
|
+
if (Date.now() > deadline) {
|
|
3474
|
+
truncated = true;
|
|
3475
|
+
break;
|
|
3476
|
+
}
|
|
3477
|
+
if (stmt.target !== "*" && node.kind !== stmt.target) continue;
|
|
3478
|
+
if (stmt.where && !evaluateWhere(node, stmt.where)) continue;
|
|
3479
|
+
allMatching.push(node);
|
|
3480
|
+
}
|
|
3481
|
+
totalCount = allMatching.length;
|
|
3482
|
+
const paginated = allMatching.slice(offset, offset + limit);
|
|
3483
|
+
return {
|
|
3484
|
+
nodes: paginated,
|
|
3485
|
+
executionTimeMs: Date.now() - start,
|
|
3486
|
+
truncated,
|
|
3487
|
+
totalCount
|
|
3488
|
+
};
|
|
3489
|
+
}
|
|
3490
|
+
function executeTRAVERSE(stmt, graph) {
|
|
3491
|
+
const start = Date.now();
|
|
3492
|
+
const maxDepth = stmt.depth ?? 5;
|
|
3493
|
+
const edgeKind = stmt.edgeKind;
|
|
3494
|
+
const direction = stmt.direction ?? "OUTGOING";
|
|
3495
|
+
const deadline = start + EXECUTION_TIMEOUT_MS;
|
|
3496
|
+
let startNode;
|
|
3497
|
+
for (const node of graph.allNodes()) {
|
|
3498
|
+
if (node.name === stmt.from) {
|
|
3499
|
+
startNode = node;
|
|
3500
|
+
break;
|
|
3501
|
+
}
|
|
3502
|
+
}
|
|
3503
|
+
if (!startNode) {
|
|
3504
|
+
return {
|
|
3505
|
+
nodes: [],
|
|
3506
|
+
edges: [],
|
|
3507
|
+
executionTimeMs: Date.now() - start,
|
|
3508
|
+
truncated: false,
|
|
3509
|
+
totalCount: 0
|
|
3510
|
+
};
|
|
3511
|
+
}
|
|
3512
|
+
const visitedNodes = /* @__PURE__ */ new Set();
|
|
3513
|
+
const visitedEdges = /* @__PURE__ */ new Set();
|
|
3514
|
+
const resultNodes = [];
|
|
3515
|
+
const resultEdges = [];
|
|
3516
|
+
const queue = [{ id: startNode.id, depth: 0 }];
|
|
3517
|
+
visitedNodes.add(startNode.id);
|
|
3518
|
+
resultNodes.push(startNode);
|
|
3519
|
+
let truncated = false;
|
|
3520
|
+
while (queue.length > 0) {
|
|
3521
|
+
if (Date.now() > deadline) {
|
|
3522
|
+
truncated = true;
|
|
3523
|
+
break;
|
|
3524
|
+
}
|
|
3525
|
+
const { id, depth } = queue.shift();
|
|
3526
|
+
if (depth >= maxDepth) continue;
|
|
3527
|
+
const nextEdges = [];
|
|
3528
|
+
if (direction === "OUTGOING" || direction === "BOTH") {
|
|
3529
|
+
for (const edge of graph.findEdgesFrom(id)) {
|
|
3530
|
+
if (!edgeKind || edge.kind === edgeKind) nextEdges.push(edge);
|
|
3531
|
+
}
|
|
3532
|
+
}
|
|
3533
|
+
if (direction === "INCOMING" || direction === "BOTH") {
|
|
3534
|
+
for (const edge of graph.findEdgesTo(id)) {
|
|
3535
|
+
if (!edgeKind || edge.kind === edgeKind) nextEdges.push(edge);
|
|
3536
|
+
}
|
|
3537
|
+
}
|
|
3538
|
+
for (const edge of nextEdges) {
|
|
3539
|
+
if (!visitedEdges.has(edge.id)) {
|
|
3540
|
+
visitedEdges.add(edge.id);
|
|
3541
|
+
resultEdges.push(edge);
|
|
3542
|
+
}
|
|
3543
|
+
const neighborId = direction === "INCOMING" ? edge.source : edge.target;
|
|
3544
|
+
const effectiveNeighborId = direction === "BOTH" ? edge.source === id ? edge.target : edge.source : neighborId;
|
|
3545
|
+
if (!visitedNodes.has(effectiveNeighborId)) {
|
|
3546
|
+
visitedNodes.add(effectiveNeighborId);
|
|
3547
|
+
const neighborNode = graph.getNode(effectiveNeighborId);
|
|
3548
|
+
if (neighborNode) {
|
|
3549
|
+
resultNodes.push(neighborNode);
|
|
3550
|
+
queue.push({ id: effectiveNeighborId, depth: depth + 1 });
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3555
|
+
return {
|
|
3556
|
+
nodes: resultNodes,
|
|
3557
|
+
edges: resultEdges,
|
|
3558
|
+
executionTimeMs: Date.now() - start,
|
|
3559
|
+
truncated,
|
|
3560
|
+
totalCount: resultNodes.length
|
|
3561
|
+
};
|
|
3562
|
+
}
|
|
3563
|
+
function executePATH(stmt, graph) {
|
|
3564
|
+
const start = Date.now();
|
|
3565
|
+
const deadline = start + EXECUTION_TIMEOUT_MS;
|
|
3566
|
+
let startNode;
|
|
3567
|
+
let endNode;
|
|
3568
|
+
for (const node of graph.allNodes()) {
|
|
3569
|
+
if (node.name === stmt.from) startNode = node;
|
|
3570
|
+
if (node.name === stmt.to) endNode = node;
|
|
3571
|
+
if (startNode && endNode) break;
|
|
3572
|
+
}
|
|
3573
|
+
if (!startNode || !endNode) {
|
|
3574
|
+
return {
|
|
3575
|
+
path: null,
|
|
3576
|
+
nodes: [],
|
|
3577
|
+
executionTimeMs: Date.now() - start,
|
|
3578
|
+
truncated: false,
|
|
3579
|
+
totalCount: 0
|
|
3580
|
+
};
|
|
3581
|
+
}
|
|
3582
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3583
|
+
const parent = /* @__PURE__ */ new Map();
|
|
3584
|
+
const queue = [startNode.id];
|
|
3585
|
+
visited.add(startNode.id);
|
|
3586
|
+
let found = false;
|
|
3587
|
+
let truncated = false;
|
|
3588
|
+
outer: while (queue.length > 0) {
|
|
3589
|
+
if (Date.now() > deadline) {
|
|
3590
|
+
truncated = true;
|
|
3591
|
+
break;
|
|
3592
|
+
}
|
|
3593
|
+
const current2 = queue.shift();
|
|
3594
|
+
for (const edge of graph.findEdgesFrom(current2)) {
|
|
3595
|
+
const next = edge.target;
|
|
3596
|
+
if (!visited.has(next)) {
|
|
3597
|
+
visited.add(next);
|
|
3598
|
+
parent.set(next, { nodeId: current2, edgeId: edge.id });
|
|
3599
|
+
if (next === endNode.id) {
|
|
3600
|
+
found = true;
|
|
3601
|
+
break outer;
|
|
3602
|
+
}
|
|
3603
|
+
queue.push(next);
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3606
|
+
for (const edge of graph.findEdgesTo(current2)) {
|
|
3607
|
+
const next = edge.source;
|
|
3608
|
+
if (!visited.has(next)) {
|
|
3609
|
+
visited.add(next);
|
|
3610
|
+
parent.set(next, { nodeId: current2, edgeId: edge.id });
|
|
3611
|
+
if (next === endNode.id) {
|
|
3612
|
+
found = true;
|
|
3613
|
+
break outer;
|
|
3614
|
+
}
|
|
3615
|
+
queue.push(next);
|
|
3616
|
+
}
|
|
3617
|
+
}
|
|
3618
|
+
}
|
|
3619
|
+
if (!found) {
|
|
3620
|
+
return {
|
|
3621
|
+
path: null,
|
|
3622
|
+
nodes: [],
|
|
3623
|
+
executionTimeMs: Date.now() - start,
|
|
3624
|
+
truncated,
|
|
3625
|
+
totalCount: 0
|
|
3626
|
+
};
|
|
3627
|
+
}
|
|
3628
|
+
const pathNodeIds = [];
|
|
3629
|
+
const pathEdgeIds = [];
|
|
3630
|
+
let current = endNode.id;
|
|
3631
|
+
while (current !== startNode.id) {
|
|
3632
|
+
pathNodeIds.unshift(current);
|
|
3633
|
+
const p = parent.get(current);
|
|
3634
|
+
pathEdgeIds.unshift(p.edgeId);
|
|
3635
|
+
current = p.nodeId;
|
|
3636
|
+
}
|
|
3637
|
+
pathNodeIds.unshift(startNode.id);
|
|
3638
|
+
const pathNodes = pathNodeIds.map((id) => graph.getNode(id)).filter(Boolean);
|
|
3639
|
+
const pathEdges = pathEdgeIds.map((id) => graph.getEdge(id)).filter(Boolean);
|
|
3640
|
+
return {
|
|
3641
|
+
path: pathNodes,
|
|
3642
|
+
nodes: pathNodes,
|
|
3643
|
+
edges: pathEdges,
|
|
3644
|
+
executionTimeMs: Date.now() - start,
|
|
3645
|
+
truncated,
|
|
3646
|
+
totalCount: pathNodes.length
|
|
3647
|
+
};
|
|
3648
|
+
}
|
|
3649
|
+
function executeCOUNT(stmt, graph) {
|
|
3650
|
+
const start = Date.now();
|
|
3651
|
+
const deadline = start + EXECUTION_TIMEOUT_MS;
|
|
3652
|
+
let truncated = false;
|
|
3653
|
+
const groups = /* @__PURE__ */ new Map();
|
|
3654
|
+
let total = 0;
|
|
3655
|
+
for (const node of graph.allNodes()) {
|
|
3656
|
+
if (Date.now() > deadline) {
|
|
3657
|
+
truncated = true;
|
|
3658
|
+
break;
|
|
3659
|
+
}
|
|
3660
|
+
if (stmt.target !== "*" && node.kind !== stmt.target) continue;
|
|
3661
|
+
if (stmt.where && !evaluateWhere(node, stmt.where)) continue;
|
|
3662
|
+
total++;
|
|
3663
|
+
if (stmt.groupBy) {
|
|
3664
|
+
const key = String(getNodeProperty(node, stmt.groupBy) ?? "(none)");
|
|
3665
|
+
groups.set(key, (groups.get(key) ?? 0) + 1);
|
|
3666
|
+
} else {
|
|
3667
|
+
groups.set("total", (groups.get("total") ?? 0) + 1);
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3670
|
+
const groupList = [...groups.entries()].map(([key, count]) => ({ key, count }));
|
|
3671
|
+
groupList.sort((a, b) => b.count - a.count);
|
|
3672
|
+
return {
|
|
3673
|
+
groups: groupList,
|
|
3674
|
+
executionTimeMs: Date.now() - start,
|
|
3675
|
+
truncated,
|
|
3676
|
+
totalCount: total
|
|
3677
|
+
};
|
|
3678
|
+
}
|
|
3679
|
+
function executeGQL(ast, graph) {
|
|
3680
|
+
switch (ast.type) {
|
|
3681
|
+
case "FIND":
|
|
3682
|
+
return executeFIND(ast, graph);
|
|
3683
|
+
case "TRAVERSE":
|
|
3684
|
+
return executeTRAVERSE(ast, graph);
|
|
3685
|
+
case "PATH":
|
|
3686
|
+
return executePATH(ast, graph);
|
|
3687
|
+
case "COUNT":
|
|
3688
|
+
return executeCOUNT(ast, graph);
|
|
3689
|
+
default:
|
|
3690
|
+
return {
|
|
3691
|
+
nodes: [],
|
|
3692
|
+
executionTimeMs: 0,
|
|
3693
|
+
truncated: false,
|
|
3694
|
+
totalCount: 0
|
|
3695
|
+
};
|
|
3696
|
+
}
|
|
3697
|
+
}
|
|
3698
|
+
var EXECUTION_TIMEOUT_MS;
|
|
3699
|
+
var init_gql_executor = __esm({
|
|
3700
|
+
"src/query/gql-executor.ts"() {
|
|
3701
|
+
EXECUTION_TIMEOUT_MS = 1e4;
|
|
3702
|
+
}
|
|
3703
|
+
});
|
|
3704
|
+
|
|
2957
3705
|
// src/errors/codes.ts
|
|
2958
3706
|
var ErrorCodes, AppError;
|
|
2959
3707
|
var init_codes = __esm({
|
|
@@ -3008,7 +3756,7 @@ function tightenDbFiles(dir) {
|
|
|
3008
3756
|
for (const name of fs19.readdirSync(dir)) {
|
|
3009
3757
|
if (name.endsWith(".db") || name.endsWith(".db-wal") || name.endsWith(".db-shm")) {
|
|
3010
3758
|
try {
|
|
3011
|
-
fs19.chmodSync(
|
|
3759
|
+
fs19.chmodSync(path27.join(dir, name), SECURE_FILE_MODE);
|
|
3012
3760
|
} catch {
|
|
3013
3761
|
}
|
|
3014
3762
|
}
|
|
@@ -3022,7 +3770,7 @@ var init_fs_secure = __esm({
|
|
|
3022
3770
|
}
|
|
3023
3771
|
});
|
|
3024
3772
|
function getUsersDBPath() {
|
|
3025
|
-
return process.env["CODE_INTEL_USERS_DB_PATH"] ??
|
|
3773
|
+
return process.env["CODE_INTEL_USERS_DB_PATH"] ?? path27.join(os12.homedir(), ".code-intel", "users.db");
|
|
3026
3774
|
}
|
|
3027
3775
|
function getOrCreateUsersDB() {
|
|
3028
3776
|
if (!_usersDB) {
|
|
@@ -3038,7 +3786,7 @@ var init_users_db = __esm({
|
|
|
3038
3786
|
UsersDB = class {
|
|
3039
3787
|
db;
|
|
3040
3788
|
constructor(dbPath) {
|
|
3041
|
-
const dir =
|
|
3789
|
+
const dir = path27.dirname(dbPath);
|
|
3042
3790
|
secureMkdir(dir);
|
|
3043
3791
|
this.db = new Database3(dbPath);
|
|
3044
3792
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -3315,7 +4063,7 @@ function getScryptN() {
|
|
|
3315
4063
|
return Number.isInteger(v) && v >= 1024 ? v : 1 << 14;
|
|
3316
4064
|
}
|
|
3317
4065
|
function getSecretsPath() {
|
|
3318
|
-
return process.env["CODE_INTEL_SECRETS_PATH"] ??
|
|
4066
|
+
return process.env["CODE_INTEL_SECRETS_PATH"] ?? path27.join(os12.homedir(), ".code-intel", ".secrets");
|
|
3319
4067
|
}
|
|
3320
4068
|
function getMasterPassword() {
|
|
3321
4069
|
const fromEnv = process.env["CODE_INTEL_SECRET_KEY"];
|
|
@@ -3904,7 +4652,7 @@ init_shared();
|
|
|
3904
4652
|
init_shared();
|
|
3905
4653
|
init_typescript();
|
|
3906
4654
|
function resolveRelative(rawPath, fromFile, workspace) {
|
|
3907
|
-
const fromDir =
|
|
4655
|
+
const fromDir = path27.dirname(fromFile);
|
|
3908
4656
|
const cleaned = rawPath.replace(/['"]/g, "");
|
|
3909
4657
|
const extensions = [".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.js"];
|
|
3910
4658
|
const resolved = workspace.resolve(fromDir, cleaned);
|
|
@@ -3956,7 +4704,7 @@ var pythonModule = {
|
|
|
3956
4704
|
resolveImport(rawPath, fromFile, workspace) {
|
|
3957
4705
|
const cleaned = rawPath.replace(/['"]/g, "");
|
|
3958
4706
|
const parts = cleaned.split(".");
|
|
3959
|
-
const fromDir =
|
|
4707
|
+
const fromDir = path27.dirname(fromFile);
|
|
3960
4708
|
const relPath = parts.join("/");
|
|
3961
4709
|
for (const suffix of ["/__init__.py", ".py"]) {
|
|
3962
4710
|
const r = workspace.resolve(fromDir, relPath + suffix);
|
|
@@ -4035,7 +4783,7 @@ var cModule = {
|
|
|
4035
4783
|
inheritanceStrategy: "none",
|
|
4036
4784
|
resolveImport(rawPath, fromFile, workspace) {
|
|
4037
4785
|
const cleaned = rawPath.replace(/[<>"']/g, "");
|
|
4038
|
-
const fromDir =
|
|
4786
|
+
const fromDir = path27.dirname(fromFile);
|
|
4039
4787
|
return workspace.resolve(fromDir, cleaned);
|
|
4040
4788
|
},
|
|
4041
4789
|
isExported(_node) {
|
|
@@ -4058,7 +4806,7 @@ var cppModule = {
|
|
|
4058
4806
|
inheritanceStrategy: "depth-first",
|
|
4059
4807
|
resolveImport(rawPath, fromFile, workspace) {
|
|
4060
4808
|
const cleaned = rawPath.replace(/[<>"']/g, "");
|
|
4061
|
-
const fromDir =
|
|
4809
|
+
const fromDir = path27.dirname(fromFile);
|
|
4062
4810
|
return workspace.resolve(fromDir, cleaned);
|
|
4063
4811
|
},
|
|
4064
4812
|
isExported(_node) {
|
|
@@ -4220,7 +4968,7 @@ var dartModule = {
|
|
|
4220
4968
|
const pkg = cleaned.replace("package:", "");
|
|
4221
4969
|
return workspace.findByPackage(pkg);
|
|
4222
4970
|
}
|
|
4223
|
-
const fromDir =
|
|
4971
|
+
const fromDir = path27.dirname(fromFile);
|
|
4224
4972
|
return workspace.resolve(fromDir, cleaned);
|
|
4225
4973
|
},
|
|
4226
4974
|
isExported(node) {
|
|
@@ -4806,7 +5554,7 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
|
4806
5554
|
]);
|
|
4807
5555
|
function loadIgnorePatterns(workspaceRoot) {
|
|
4808
5556
|
try {
|
|
4809
|
-
const raw = fs19.readFileSync(
|
|
5557
|
+
const raw = fs19.readFileSync(path27.join(workspaceRoot, ".codeintelignore"), "utf-8");
|
|
4810
5558
|
const extras = /* @__PURE__ */ new Set();
|
|
4811
5559
|
for (const line of raw.split("\n")) {
|
|
4812
5560
|
const trimmed = line.trim();
|
|
@@ -4839,13 +5587,13 @@ var scanPhase = {
|
|
|
4839
5587
|
if (entry.name.startsWith(".")) continue;
|
|
4840
5588
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
4841
5589
|
if (extraIgnore.has(entry.name)) continue;
|
|
4842
|
-
walk(
|
|
5590
|
+
walk(path27.join(dir, entry.name));
|
|
4843
5591
|
} else if (entry.isFile()) {
|
|
4844
5592
|
const name = entry.name;
|
|
4845
5593
|
if (IGNORED_FILE_SUFFIXES.some((s) => name.endsWith(s))) continue;
|
|
4846
|
-
const ext =
|
|
5594
|
+
const ext = path27.extname(name);
|
|
4847
5595
|
if (!extensions.has(ext)) continue;
|
|
4848
|
-
const fullPath =
|
|
5596
|
+
const fullPath = path27.join(dir, name);
|
|
4849
5597
|
try {
|
|
4850
5598
|
const stat = fs19.statSync(fullPath);
|
|
4851
5599
|
if (stat.size > MAX_FILE_SIZE_BYTES) continue;
|
|
@@ -4874,20 +5622,20 @@ var structurePhase = {
|
|
|
4874
5622
|
const dirs = /* @__PURE__ */ new Set();
|
|
4875
5623
|
let structDone = 0;
|
|
4876
5624
|
for (const filePath of context2.filePaths) {
|
|
4877
|
-
const relativePath =
|
|
5625
|
+
const relativePath = path27.relative(context2.workspaceRoot, filePath);
|
|
4878
5626
|
const lang = detectLanguage(filePath);
|
|
4879
5627
|
context2.graph.addNode({
|
|
4880
5628
|
id: generateNodeId("file", relativePath, relativePath),
|
|
4881
5629
|
kind: "file",
|
|
4882
|
-
name:
|
|
5630
|
+
name: path27.basename(filePath),
|
|
4883
5631
|
filePath: relativePath,
|
|
4884
5632
|
metadata: lang ? { language: lang } : void 0
|
|
4885
5633
|
});
|
|
4886
|
-
let dir =
|
|
5634
|
+
let dir = path27.dirname(relativePath);
|
|
4887
5635
|
while (dir && dir !== "." && dir !== "") {
|
|
4888
5636
|
if (dirs.has(dir)) break;
|
|
4889
5637
|
dirs.add(dir);
|
|
4890
|
-
dir =
|
|
5638
|
+
dir = path27.dirname(dir);
|
|
4891
5639
|
}
|
|
4892
5640
|
structDone++;
|
|
4893
5641
|
context2.onPhaseProgress?.("structure", structDone, context2.filePaths.length);
|
|
@@ -4896,7 +5644,7 @@ var structurePhase = {
|
|
|
4896
5644
|
context2.graph.addNode({
|
|
4897
5645
|
id: generateNodeId("directory", dir, dir),
|
|
4898
5646
|
kind: "directory",
|
|
4899
|
-
name:
|
|
5647
|
+
name: path27.basename(dir),
|
|
4900
5648
|
filePath: dir
|
|
4901
5649
|
});
|
|
4902
5650
|
}
|
|
@@ -5053,7 +5801,7 @@ var LLMGovernanceLogger = class {
|
|
|
5053
5801
|
}
|
|
5054
5802
|
/** Path to the JSONL log file. */
|
|
5055
5803
|
getLogPath() {
|
|
5056
|
-
return process.env["CODE_INTEL_GOVERNANCE_LOG_PATH"] ??
|
|
5804
|
+
return process.env["CODE_INTEL_GOVERNANCE_LOG_PATH"] ?? path27.join(os12.homedir(), ".code-intel", "llm-governance.jsonl");
|
|
5057
5805
|
}
|
|
5058
5806
|
/**
|
|
5059
5807
|
* Append an entry to the governance log.
|
|
@@ -5069,7 +5817,7 @@ var LLMGovernanceLogger = class {
|
|
|
5069
5817
|
...entry
|
|
5070
5818
|
};
|
|
5071
5819
|
const logPath = this.getLogPath();
|
|
5072
|
-
fs19.mkdirSync(
|
|
5820
|
+
fs19.mkdirSync(path27.dirname(logPath), { recursive: true });
|
|
5073
5821
|
fs19.appendFileSync(logPath, JSON.stringify(full) + "\n", "utf-8");
|
|
5074
5822
|
} catch {
|
|
5075
5823
|
}
|
|
@@ -5525,7 +6273,7 @@ var DbManager = class {
|
|
|
5525
6273
|
this.dbPath = dbPath;
|
|
5526
6274
|
}
|
|
5527
6275
|
async init() {
|
|
5528
|
-
fs19.mkdirSync(
|
|
6276
|
+
fs19.mkdirSync(path27.dirname(this.dbPath), { recursive: true });
|
|
5529
6277
|
this.db = new Database(this.dbPath);
|
|
5530
6278
|
await this.db.init();
|
|
5531
6279
|
this.conn = new Connection(this.db);
|
|
@@ -5622,7 +6370,7 @@ function writeNodeCSVs(graph, outputDir) {
|
|
|
5622
6370
|
const table = NODE_TABLE_MAP[node.kind];
|
|
5623
6371
|
if (!tableBuffers.has(table)) {
|
|
5624
6372
|
tableBuffers.set(table, [header]);
|
|
5625
|
-
tableFilePaths.set(table,
|
|
6373
|
+
tableFilePaths.set(table, path27.join(outputDir, `${table}.csv`));
|
|
5626
6374
|
}
|
|
5627
6375
|
tableBuffers.get(table).push(
|
|
5628
6376
|
csvRow([
|
|
@@ -5658,7 +6406,7 @@ function writeEdgeCSV(graph, outputDir) {
|
|
|
5658
6406
|
const toTable = NODE_TABLE_MAP[targetNode.kind];
|
|
5659
6407
|
const key = `${fromTable}->${toTable}`;
|
|
5660
6408
|
if (!groups.has(key)) {
|
|
5661
|
-
const filePath =
|
|
6409
|
+
const filePath = path27.join(outputDir, `edges_${fromTable}_${toTable}.csv`);
|
|
5662
6410
|
groups.set(key, { lines: [header], from: fromTable, to: toTable, filePath });
|
|
5663
6411
|
}
|
|
5664
6412
|
groups.get(key).lines.push(
|
|
@@ -5701,7 +6449,7 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
5701
6449
|
} catch {
|
|
5702
6450
|
}
|
|
5703
6451
|
}
|
|
5704
|
-
const tmpDir = fs19.mkdtempSync(
|
|
6452
|
+
const tmpDir = fs19.mkdtempSync(path27.join(os12.tmpdir(), "code-intel-csv-"));
|
|
5705
6453
|
try {
|
|
5706
6454
|
const nodeTableFiles = writeNodeCSVs(graph, tmpDir);
|
|
5707
6455
|
const edgeGroups = writeEdgeCSV(graph, tmpDir);
|
|
@@ -5801,8 +6549,8 @@ function buildNodeProps(node) {
|
|
|
5801
6549
|
function escCypher(s) {
|
|
5802
6550
|
return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "");
|
|
5803
6551
|
}
|
|
5804
|
-
var GLOBAL_DIR =
|
|
5805
|
-
var REPOS_FILE =
|
|
6552
|
+
var GLOBAL_DIR = path27.join(os12.homedir(), ".code-intel");
|
|
6553
|
+
var REPOS_FILE = path27.join(GLOBAL_DIR, "repos.json");
|
|
5806
6554
|
function loadRegistry() {
|
|
5807
6555
|
try {
|
|
5808
6556
|
const data = fs19.readFileSync(REPOS_FILE, "utf-8");
|
|
@@ -5830,23 +6578,23 @@ function removeRepo(repoPath) {
|
|
|
5830
6578
|
saveRegistry(entries);
|
|
5831
6579
|
}
|
|
5832
6580
|
function saveMetadata(repoDir, metadata) {
|
|
5833
|
-
const metaDir =
|
|
6581
|
+
const metaDir = path27.join(repoDir, ".code-intel");
|
|
5834
6582
|
fs19.mkdirSync(metaDir, { recursive: true });
|
|
5835
|
-
fs19.writeFileSync(
|
|
6583
|
+
fs19.writeFileSync(path27.join(metaDir, "meta.json"), JSON.stringify(metadata, null, 2));
|
|
5836
6584
|
}
|
|
5837
6585
|
function loadMetadata(repoDir) {
|
|
5838
6586
|
try {
|
|
5839
|
-
const data = fs19.readFileSync(
|
|
6587
|
+
const data = fs19.readFileSync(path27.join(repoDir, ".code-intel", "meta.json"), "utf-8");
|
|
5840
6588
|
return JSON.parse(data);
|
|
5841
6589
|
} catch {
|
|
5842
6590
|
return null;
|
|
5843
6591
|
}
|
|
5844
6592
|
}
|
|
5845
6593
|
function getDbPath(repoDir) {
|
|
5846
|
-
return
|
|
6594
|
+
return path27.join(repoDir, ".code-intel", "graph.db");
|
|
5847
6595
|
}
|
|
5848
6596
|
function getVectorDbPath(repoDir) {
|
|
5849
|
-
return
|
|
6597
|
+
return path27.join(repoDir, ".code-intel", "vector.db");
|
|
5850
6598
|
}
|
|
5851
6599
|
|
|
5852
6600
|
// src/mcp-server/server.ts
|
|
@@ -6045,7 +6793,7 @@ async function syncGroup(group) {
|
|
|
6045
6793
|
logger_default.warn(` \u26A0 Registry entry "${member.registryName}" not found \u2014 skipping ${member.groupPath}`);
|
|
6046
6794
|
continue;
|
|
6047
6795
|
}
|
|
6048
|
-
const dbPath =
|
|
6796
|
+
const dbPath = path27.join(regEntry.path, ".code-intel", "graph.db");
|
|
6049
6797
|
if (!fs19.existsSync(dbPath)) {
|
|
6050
6798
|
logger_default.warn(` \u26A0 No index at ${dbPath} \u2014 run \`code-intel analyze ${regEntry.path}\` first`);
|
|
6051
6799
|
continue;
|
|
@@ -6081,7 +6829,7 @@ async function queryGroup(group, query, limit = 20) {
|
|
|
6081
6829
|
for (const member of group.members) {
|
|
6082
6830
|
const regEntry = registry.find((r) => r.name === member.registryName);
|
|
6083
6831
|
if (!regEntry) continue;
|
|
6084
|
-
const dbPath =
|
|
6832
|
+
const dbPath = path27.join(regEntry.path, ".code-intel", "graph.db");
|
|
6085
6833
|
if (!fs19.existsSync(dbPath)) continue;
|
|
6086
6834
|
const graph = createKnowledgeGraph();
|
|
6087
6835
|
const db = new DbManager(dbPath);
|
|
@@ -6265,6 +7013,23 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
6265
7013
|
}
|
|
6266
7014
|
}
|
|
6267
7015
|
},
|
|
7016
|
+
// ── query (GQL) ────────────────────────────────────────────────────────
|
|
7017
|
+
{
|
|
7018
|
+
name: "query",
|
|
7019
|
+
description: "Execute a GQL (Graph Query Language) query. Supports FIND, TRAVERSE, PATH, and COUNT. More expressive than raw_query.",
|
|
7020
|
+
inputSchema: {
|
|
7021
|
+
type: "object",
|
|
7022
|
+
properties: {
|
|
7023
|
+
gql: {
|
|
7024
|
+
type: "string",
|
|
7025
|
+
description: 'GQL query string. Examples: "FIND function WHERE name CONTAINS \\"auth\\"", "TRAVERSE CALLS FROM \\"handleLogin\\" DEPTH 3", "PATH FROM \\"createUser\\" TO \\"sendEmail\\"", "COUNT function GROUP BY cluster"'
|
|
7026
|
+
},
|
|
7027
|
+
limit: { type: "number", description: "Override LIMIT in the query (optional)" },
|
|
7028
|
+
..._tokenProp
|
|
7029
|
+
},
|
|
7030
|
+
required: ["gql"]
|
|
7031
|
+
}
|
|
7032
|
+
},
|
|
6268
7033
|
// ── Raw query ─────────────────────────────────────────────────────────
|
|
6269
7034
|
{
|
|
6270
7035
|
name: "raw_query",
|
|
@@ -6445,7 +7210,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
6445
7210
|
const limit = a.limit ?? 20;
|
|
6446
7211
|
const vdbPath = workspaceRoot ? getVectorDbPath(workspaceRoot) : void 0;
|
|
6447
7212
|
const { results, searchMode } = await hybridSearch(graph, query, limit, { vectorDbPath: vdbPath });
|
|
6448
|
-
return { content: [{ type: "text", text: JSON.stringify({ results, searchMode }, null, 2) }] };
|
|
7213
|
+
return { content: [{ type: "text", text: JSON.stringify({ results, searchMode, suggested_next_tools: ["inspect", "query", "blast_radius"] }, null, 2) }] };
|
|
6449
7214
|
}
|
|
6450
7215
|
// ── inspect ────────────────────────────────────────────────────────────
|
|
6451
7216
|
case "inspect": {
|
|
@@ -6684,7 +7449,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
6684
7449
|
for (const { filePath: changedFile, changedLines } of changedFiles) {
|
|
6685
7450
|
for (const node of graph.allNodes()) {
|
|
6686
7451
|
if (!node.filePath) continue;
|
|
6687
|
-
const normNode = node.filePath.replace(repoRoot + "/", "").replace(repoRoot +
|
|
7452
|
+
const normNode = node.filePath.replace(repoRoot + "/", "").replace(repoRoot + path27.sep, "");
|
|
6688
7453
|
const normChanged = changedFile.replace(/^a\/|^b\//, "");
|
|
6689
7454
|
if (!normNode.endsWith(normChanged) && !normChanged.endsWith(normNode)) continue;
|
|
6690
7455
|
if (node.startLine !== void 0 && node.endLine !== void 0) {
|
|
@@ -6733,16 +7498,51 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
6733
7498
|
}]
|
|
6734
7499
|
};
|
|
6735
7500
|
}
|
|
7501
|
+
// ── query (GQL) ───────────────────────────────────────────────────────────
|
|
7502
|
+
case "query": {
|
|
7503
|
+
const gqlInput = a.gql;
|
|
7504
|
+
if (!gqlInput) {
|
|
7505
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "Missing required parameter: gql" }) }], isError: true };
|
|
7506
|
+
}
|
|
7507
|
+
const { parseGQL: parseGQL2, isGQLParseError: isGQLParseError2 } = await Promise.resolve().then(() => (init_gql_parser(), gql_parser_exports));
|
|
7508
|
+
const { executeGQL: executeGQL2 } = await Promise.resolve().then(() => (init_gql_executor(), gql_executor_exports));
|
|
7509
|
+
const ast = parseGQL2(gqlInput);
|
|
7510
|
+
if (isGQLParseError2(ast)) {
|
|
7511
|
+
return {
|
|
7512
|
+
content: [{ type: "text", text: JSON.stringify({ error: `GQL parse error: ${ast.message}`, pos: ast.pos, expected: ast.expected, got: ast.got }) }],
|
|
7513
|
+
isError: true
|
|
7514
|
+
};
|
|
7515
|
+
}
|
|
7516
|
+
if (a.limit !== void 0 && ast.type === "FIND") {
|
|
7517
|
+
ast.limit = a.limit;
|
|
7518
|
+
}
|
|
7519
|
+
const result = executeGQL2(ast, graph);
|
|
7520
|
+
return {
|
|
7521
|
+
content: [{
|
|
7522
|
+
type: "text",
|
|
7523
|
+
text: JSON.stringify({
|
|
7524
|
+
nodes: result.nodes,
|
|
7525
|
+
edges: result.edges,
|
|
7526
|
+
groups: result.groups,
|
|
7527
|
+
path: result.path,
|
|
7528
|
+
executionTimeMs: result.executionTimeMs,
|
|
7529
|
+
truncated: result.truncated,
|
|
7530
|
+
totalCount: result.totalCount
|
|
7531
|
+
}, null, 2)
|
|
7532
|
+
}]
|
|
7533
|
+
};
|
|
7534
|
+
}
|
|
6736
7535
|
// ── raw_query ──────────────────────────────────────────────────────────
|
|
6737
7536
|
case "raw_query": {
|
|
6738
7537
|
const q = a.cypher;
|
|
7538
|
+
const deprecationWarning = "raw_query is deprecated, use query instead";
|
|
6739
7539
|
const nameMatch = q?.match(/name\s*=\s*['"]([^'"]+)['"]/i);
|
|
6740
7540
|
if (nameMatch) {
|
|
6741
7541
|
const results = [];
|
|
6742
7542
|
for (const node of graph.allNodes()) {
|
|
6743
7543
|
if (node.name === nameMatch[1]) results.push(node);
|
|
6744
7544
|
}
|
|
6745
|
-
return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
|
|
7545
|
+
return { content: [{ type: "text", text: JSON.stringify({ deprecation: deprecationWarning, results }, null, 2) }] };
|
|
6746
7546
|
}
|
|
6747
7547
|
const kindMatch = q?.match(/:\s*(\w+)/);
|
|
6748
7548
|
if (kindMatch) {
|
|
@@ -6751,9 +7551,9 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
6751
7551
|
if (node.kind === kindMatch[1]) results.push(node);
|
|
6752
7552
|
if (results.length >= 50) break;
|
|
6753
7553
|
}
|
|
6754
|
-
return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
|
|
7554
|
+
return { content: [{ type: "text", text: JSON.stringify({ deprecation: deprecationWarning, results }, null, 2) }] };
|
|
6755
7555
|
}
|
|
6756
|
-
return { content: [{ type: "text", text: "Query not recognized. Use name='X' or :kind syntax." }] };
|
|
7556
|
+
return { content: [{ type: "text", text: JSON.stringify({ deprecation: deprecationWarning, error: "Query not recognized. Use name='X' or :kind syntax. Or use the query tool with GQL instead." }) }] };
|
|
6757
7557
|
}
|
|
6758
7558
|
// ── group_list ─────────────────────────────────────────────────────────
|
|
6759
7559
|
case "group_list": {
|
|
@@ -6964,7 +7764,7 @@ var STUCK_THRESHOLD_MINUTES = 30;
|
|
|
6964
7764
|
var JobsDB = class {
|
|
6965
7765
|
db;
|
|
6966
7766
|
constructor(dbPath) {
|
|
6967
|
-
fs19.mkdirSync(
|
|
7767
|
+
fs19.mkdirSync(path27.dirname(dbPath), { recursive: true });
|
|
6968
7768
|
this.db = new Database3(dbPath);
|
|
6969
7769
|
this.db.pragma("journal_mode = WAL");
|
|
6970
7770
|
this.db.pragma("foreign_keys = ON");
|
|
@@ -7106,7 +7906,7 @@ var JobsDB = class {
|
|
|
7106
7906
|
}
|
|
7107
7907
|
};
|
|
7108
7908
|
function getJobsDBPath() {
|
|
7109
|
-
return
|
|
7909
|
+
return path27.join(os12.homedir(), ".code-intel", "jobs.db");
|
|
7110
7910
|
}
|
|
7111
7911
|
var _jobsDB = null;
|
|
7112
7912
|
function getOrCreateJobsDB() {
|
|
@@ -7198,7 +7998,7 @@ var BACKUP_VERSION = "1.0";
|
|
|
7198
7998
|
var ALGORITHM = "aes-256-gcm";
|
|
7199
7999
|
var IV_LENGTH = 16;
|
|
7200
8000
|
function getBackupDir() {
|
|
7201
|
-
return
|
|
8001
|
+
return path27.join(os12.homedir(), ".code-intel", "backups");
|
|
7202
8002
|
}
|
|
7203
8003
|
function getBackupKey() {
|
|
7204
8004
|
const keyHex = process.env["CODE_INTEL_BACKUP_KEY"];
|
|
@@ -7236,22 +8036,22 @@ var BackupService = class {
|
|
|
7236
8036
|
* Returns the backup entry.
|
|
7237
8037
|
*/
|
|
7238
8038
|
createBackup(repoPath) {
|
|
7239
|
-
const codeIntelDir =
|
|
8039
|
+
const codeIntelDir = path27.join(repoPath, ".code-intel");
|
|
7240
8040
|
const id = v4();
|
|
7241
8041
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7242
8042
|
const filesToBackup = [];
|
|
7243
8043
|
const candidates = ["graph.db", "vector.db", "meta.json"];
|
|
7244
8044
|
for (const f of candidates) {
|
|
7245
|
-
const fp =
|
|
8045
|
+
const fp = path27.join(codeIntelDir, f);
|
|
7246
8046
|
if (fs19.existsSync(fp)) {
|
|
7247
8047
|
filesToBackup.push({ name: f, localPath: fp });
|
|
7248
8048
|
}
|
|
7249
8049
|
}
|
|
7250
|
-
const registryPath =
|
|
8050
|
+
const registryPath = path27.join(os12.homedir(), ".code-intel", "registry.json");
|
|
7251
8051
|
if (fs19.existsSync(registryPath)) {
|
|
7252
8052
|
filesToBackup.push({ name: "registry.json", localPath: registryPath });
|
|
7253
8053
|
}
|
|
7254
|
-
const usersDbPath =
|
|
8054
|
+
const usersDbPath = path27.join(os12.homedir(), ".code-intel", "users.db");
|
|
7255
8055
|
if (fs19.existsSync(usersDbPath)) {
|
|
7256
8056
|
filesToBackup.push({ name: "users.db", localPath: usersDbPath });
|
|
7257
8057
|
}
|
|
@@ -7288,7 +8088,7 @@ var BackupService = class {
|
|
|
7288
8088
|
const plaintext = Buffer.concat(parts);
|
|
7289
8089
|
const encrypted = encryptBuffer(plaintext, this.key);
|
|
7290
8090
|
const backupFileName = `backup-${id}.cib`;
|
|
7291
|
-
const backupPath =
|
|
8091
|
+
const backupPath = path27.join(this.backupDir, backupFileName);
|
|
7292
8092
|
fs19.writeFileSync(backupPath, encrypted);
|
|
7293
8093
|
const entry = {
|
|
7294
8094
|
id,
|
|
@@ -7316,7 +8116,7 @@ var BackupService = class {
|
|
|
7316
8116
|
async uploadToS3(entry) {
|
|
7317
8117
|
const cfg = getS3Config();
|
|
7318
8118
|
if (!cfg) throw new Error("S3 not configured. Set CODE_INTEL_BACKUP_S3_BUCKET, CODE_INTEL_BACKUP_S3_ACCESS_KEY_ID, CODE_INTEL_BACKUP_S3_SECRET_ACCESS_KEY.");
|
|
7319
|
-
const fileName =
|
|
8119
|
+
const fileName = path27.basename(entry.path);
|
|
7320
8120
|
const s3Key = `${cfg.prefix}${fileName}`;
|
|
7321
8121
|
const body = fs19.readFileSync(entry.path);
|
|
7322
8122
|
const result = await s3Request({ method: "PUT", cfg, key: s3Key, body });
|
|
@@ -7335,7 +8135,7 @@ var BackupService = class {
|
|
|
7335
8135
|
if (result.statusCode < 200 || result.statusCode >= 300) {
|
|
7336
8136
|
throw new Error(`S3 download failed (HTTP ${result.statusCode}): ${result.body.slice(0, 200)}`);
|
|
7337
8137
|
}
|
|
7338
|
-
fs19.mkdirSync(
|
|
8138
|
+
fs19.mkdirSync(path27.dirname(destPath), { recursive: true });
|
|
7339
8139
|
fs19.writeFileSync(destPath, Buffer.from(result.body, "binary"));
|
|
7340
8140
|
}
|
|
7341
8141
|
/**
|
|
@@ -7399,7 +8199,7 @@ var BackupService = class {
|
|
|
7399
8199
|
offset += manifestLen;
|
|
7400
8200
|
const manifest = JSON.parse(manifestStr);
|
|
7401
8201
|
const restoreBase = targetRepoPath ?? entry.repoPath;
|
|
7402
|
-
const codeIntelDir =
|
|
8202
|
+
const codeIntelDir = path27.join(restoreBase, ".code-intel");
|
|
7403
8203
|
fs19.mkdirSync(codeIntelDir, { recursive: true });
|
|
7404
8204
|
for (const fileEntry of manifest.files) {
|
|
7405
8205
|
const nameLen = plaintext.readUInt16BE(offset);
|
|
@@ -7417,9 +8217,9 @@ var BackupService = class {
|
|
|
7417
8217
|
}
|
|
7418
8218
|
let destPath;
|
|
7419
8219
|
if (name === "registry.json" || name === "users.db") {
|
|
7420
|
-
destPath =
|
|
8220
|
+
destPath = path27.join(os12.homedir(), ".code-intel", name);
|
|
7421
8221
|
} else {
|
|
7422
|
-
destPath =
|
|
8222
|
+
destPath = path27.join(codeIntelDir, name);
|
|
7423
8223
|
}
|
|
7424
8224
|
fs19.writeFileSync(destPath, data);
|
|
7425
8225
|
}
|
|
@@ -7470,7 +8270,7 @@ var BackupService = class {
|
|
|
7470
8270
|
}
|
|
7471
8271
|
// ── Index helpers ──────────────────────────────────────────────────────────
|
|
7472
8272
|
_indexPath() {
|
|
7473
|
-
return
|
|
8273
|
+
return path27.join(this.backupDir, "index.json");
|
|
7474
8274
|
}
|
|
7475
8275
|
_loadIndex() {
|
|
7476
8276
|
try {
|
|
@@ -7971,6 +8771,60 @@ var openApiSpec = {
|
|
|
7971
8771
|
}
|
|
7972
8772
|
}
|
|
7973
8773
|
},
|
|
8774
|
+
"/source": {
|
|
8775
|
+
get: {
|
|
8776
|
+
tags: ["Files"],
|
|
8777
|
+
summary: "Get source code preview with context around specified lines",
|
|
8778
|
+
description: "Returns the file content around the specified line range (\xB120 lines context), with language detection. Requires viewer role.",
|
|
8779
|
+
security: [{ BearerAuth: [] }, { SessionCookie: [] }],
|
|
8780
|
+
parameters: [
|
|
8781
|
+
{
|
|
8782
|
+
name: "file",
|
|
8783
|
+
in: "query",
|
|
8784
|
+
required: true,
|
|
8785
|
+
description: "Absolute path to the file",
|
|
8786
|
+
schema: { type: "string" }
|
|
8787
|
+
},
|
|
8788
|
+
{
|
|
8789
|
+
name: "startLine",
|
|
8790
|
+
in: "query",
|
|
8791
|
+
required: false,
|
|
8792
|
+
description: "Start line number (1-indexed)",
|
|
8793
|
+
schema: { type: "integer", minimum: 1 }
|
|
8794
|
+
},
|
|
8795
|
+
{
|
|
8796
|
+
name: "endLine",
|
|
8797
|
+
in: "query",
|
|
8798
|
+
required: false,
|
|
8799
|
+
description: "End line number (1-indexed)",
|
|
8800
|
+
schema: { type: "integer", minimum: 1 }
|
|
8801
|
+
}
|
|
8802
|
+
],
|
|
8803
|
+
responses: {
|
|
8804
|
+
"200": {
|
|
8805
|
+
description: "Source code preview",
|
|
8806
|
+
content: {
|
|
8807
|
+
"application/json": {
|
|
8808
|
+
schema: {
|
|
8809
|
+
type: "object",
|
|
8810
|
+
properties: {
|
|
8811
|
+
content: { type: "string", description: "File content (with context lines)" },
|
|
8812
|
+
language: { type: "string", description: "Detected programming language", example: "typescript" },
|
|
8813
|
+
startLine: { type: "integer", description: "Actual start line returned (with context)" },
|
|
8814
|
+
endLine: { type: "integer", description: "Actual end line returned (with context)" }
|
|
8815
|
+
},
|
|
8816
|
+
required: ["content", "language", "startLine", "endLine"]
|
|
8817
|
+
}
|
|
8818
|
+
}
|
|
8819
|
+
}
|
|
8820
|
+
},
|
|
8821
|
+
"400": { description: "Bad request (missing file param or path traversal detected)", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } },
|
|
8822
|
+
"401": { description: "Unauthorized", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } },
|
|
8823
|
+
"403": { description: "Forbidden (file outside indexed repos)", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } },
|
|
8824
|
+
"404": { description: "File not found", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } }
|
|
8825
|
+
}
|
|
8826
|
+
}
|
|
8827
|
+
},
|
|
7974
8828
|
"/grep": {
|
|
7975
8829
|
post: {
|
|
7976
8830
|
tags: ["Files"],
|
|
@@ -8084,16 +8938,122 @@ var openApiSpec = {
|
|
|
8084
8938
|
"404": { description: "Group not found", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } }
|
|
8085
8939
|
}
|
|
8086
8940
|
}
|
|
8941
|
+
},
|
|
8942
|
+
"/query": {
|
|
8943
|
+
post: {
|
|
8944
|
+
tags: ["GQL"],
|
|
8945
|
+
summary: "Execute a GQL (Graph Query Language) query against the knowledge graph",
|
|
8946
|
+
description: "Supports FIND, TRAVERSE, PATH, and COUNT statements. Requires viewer role minimum.",
|
|
8947
|
+
security: [{ BearerAuth: [] }, { SessionCookie: [] }],
|
|
8948
|
+
requestBody: {
|
|
8949
|
+
required: true,
|
|
8950
|
+
content: {
|
|
8951
|
+
"application/json": {
|
|
8952
|
+
schema: {
|
|
8953
|
+
type: "object",
|
|
8954
|
+
properties: {
|
|
8955
|
+
gql: {
|
|
8956
|
+
type: "string",
|
|
8957
|
+
description: "GQL query string",
|
|
8958
|
+
example: 'FIND function WHERE name CONTAINS "auth"'
|
|
8959
|
+
},
|
|
8960
|
+
format: {
|
|
8961
|
+
type: "string",
|
|
8962
|
+
enum: ["json", "table", "csv"],
|
|
8963
|
+
default: "json",
|
|
8964
|
+
description: "Output format"
|
|
8965
|
+
}
|
|
8966
|
+
},
|
|
8967
|
+
required: ["gql"]
|
|
8968
|
+
}
|
|
8969
|
+
}
|
|
8970
|
+
}
|
|
8971
|
+
},
|
|
8972
|
+
responses: {
|
|
8973
|
+
"200": {
|
|
8974
|
+
description: "GQL execution result",
|
|
8975
|
+
content: {
|
|
8976
|
+
"application/json": {
|
|
8977
|
+
schema: {
|
|
8978
|
+
type: "object",
|
|
8979
|
+
properties: {
|
|
8980
|
+
nodes: { type: "array", items: { "$ref": "#/components/schemas/CodeNode" } },
|
|
8981
|
+
edges: { type: "array", items: { type: "object" } },
|
|
8982
|
+
groups: { type: "array", items: { type: "object", properties: { key: { type: "string" }, count: { type: "integer" } } } },
|
|
8983
|
+
path: { type: "array", items: { "$ref": "#/components/schemas/CodeNode" }, nullable: true },
|
|
8984
|
+
executionTimeMs: { type: "number" },
|
|
8985
|
+
truncated: { type: "boolean" },
|
|
8986
|
+
totalCount: { type: "integer" }
|
|
8987
|
+
}
|
|
8988
|
+
}
|
|
8989
|
+
}
|
|
8990
|
+
}
|
|
8991
|
+
},
|
|
8992
|
+
"400": { description: "Missing gql field", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } },
|
|
8993
|
+
"401": { description: "Unauthorized", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } },
|
|
8994
|
+
"403": { description: "Forbidden (insufficient role)", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } },
|
|
8995
|
+
"422": { description: "GQL parse error", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } }
|
|
8996
|
+
}
|
|
8997
|
+
}
|
|
8998
|
+
},
|
|
8999
|
+
"/query/explain": {
|
|
9000
|
+
post: {
|
|
9001
|
+
tags: ["GQL"],
|
|
9002
|
+
summary: "Explain a GQL query \u2014 returns the execution plan without running it",
|
|
9003
|
+
description: "Returns a query plan object describing the steps that would be executed. Requires viewer role minimum.",
|
|
9004
|
+
security: [{ BearerAuth: [] }, { SessionCookie: [] }],
|
|
9005
|
+
requestBody: {
|
|
9006
|
+
required: true,
|
|
9007
|
+
content: {
|
|
9008
|
+
"application/json": {
|
|
9009
|
+
schema: {
|
|
9010
|
+
type: "object",
|
|
9011
|
+
properties: {
|
|
9012
|
+
gql: { type: "string", description: "GQL query string", example: 'FIND function WHERE name CONTAINS "auth"' }
|
|
9013
|
+
},
|
|
9014
|
+
required: ["gql"]
|
|
9015
|
+
}
|
|
9016
|
+
}
|
|
9017
|
+
}
|
|
9018
|
+
},
|
|
9019
|
+
responses: {
|
|
9020
|
+
"200": {
|
|
9021
|
+
description: "Query plan",
|
|
9022
|
+
content: {
|
|
9023
|
+
"application/json": {
|
|
9024
|
+
schema: {
|
|
9025
|
+
type: "object",
|
|
9026
|
+
properties: {
|
|
9027
|
+
plan: {
|
|
9028
|
+
type: "object",
|
|
9029
|
+
properties: {
|
|
9030
|
+
type: { type: "string", enum: ["FIND", "TRAVERSE", "PATH", "COUNT"] },
|
|
9031
|
+
gql: { type: "string" },
|
|
9032
|
+
steps: { type: "array", items: { type: "object" } },
|
|
9033
|
+
estimatedCost: { type: "number" }
|
|
9034
|
+
}
|
|
9035
|
+
},
|
|
9036
|
+
graphSize: { type: "object", properties: { nodes: { type: "integer" }, edges: { type: "integer" } } }
|
|
9037
|
+
}
|
|
9038
|
+
}
|
|
9039
|
+
}
|
|
9040
|
+
}
|
|
9041
|
+
},
|
|
9042
|
+
"400": { description: "Missing gql field", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } },
|
|
9043
|
+
"401": { description: "Unauthorized", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } },
|
|
9044
|
+
"422": { description: "GQL parse error", content: { "application/json": { schema: { "$ref": "#/components/schemas/ErrorResponse" } } } }
|
|
9045
|
+
}
|
|
9046
|
+
}
|
|
8087
9047
|
}
|
|
8088
9048
|
}
|
|
8089
9049
|
};
|
|
8090
9050
|
|
|
8091
9051
|
// src/http/app.ts
|
|
8092
|
-
var __dirname$1 =
|
|
9052
|
+
var __dirname$1 = path27.dirname(fileURLToPath(import.meta.url));
|
|
8093
9053
|
var WEB_DIST = (() => {
|
|
8094
|
-
const bundled =
|
|
9054
|
+
const bundled = path27.resolve(__dirname$1, "..", "web");
|
|
8095
9055
|
if (fs19.existsSync(bundled)) return bundled;
|
|
8096
|
-
return
|
|
9056
|
+
return path27.resolve(__dirname$1, "..", "..", "..", "web", "dist");
|
|
8097
9057
|
})();
|
|
8098
9058
|
function getAllowedOrigins() {
|
|
8099
9059
|
const env = process.env["CODE_INTEL_CORS_ORIGINS"];
|
|
@@ -8121,6 +9081,7 @@ function createDefaultLimiter() {
|
|
|
8121
9081
|
function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
8122
9082
|
const app = express();
|
|
8123
9083
|
app.set("trust proxy", 1);
|
|
9084
|
+
app.use(compression());
|
|
8124
9085
|
app.use(
|
|
8125
9086
|
helmet({
|
|
8126
9087
|
contentSecurityPolicy: false
|
|
@@ -8623,7 +9584,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
8623
9584
|
const registry = loadRegistry();
|
|
8624
9585
|
const entry = registry.find((r) => r.name === requestedRepo || r.path === requestedRepo);
|
|
8625
9586
|
if (!entry) return null;
|
|
8626
|
-
const dbPath =
|
|
9587
|
+
const dbPath = path27.join(entry.path, ".code-intel", "graph.db");
|
|
8627
9588
|
if (!fs19.existsSync(dbPath)) return null;
|
|
8628
9589
|
const repoGraph = createKnowledgeGraph();
|
|
8629
9590
|
const db = new DbManager(dbPath);
|
|
@@ -8969,7 +9930,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
8969
9930
|
for (const member of group.members) {
|
|
8970
9931
|
const regEntry = registry.find((r) => r.name === member.registryName);
|
|
8971
9932
|
if (!regEntry) continue;
|
|
8972
|
-
const dbPath =
|
|
9933
|
+
const dbPath = path27.join(regEntry.path, ".code-intel", "graph.db");
|
|
8973
9934
|
if (!fs19.existsSync(dbPath)) continue;
|
|
8974
9935
|
const db = new DbManager(dbPath);
|
|
8975
9936
|
try {
|
|
@@ -8982,10 +9943,245 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
8982
9943
|
}
|
|
8983
9944
|
res.json({ nodes: [...mergedGraph.allNodes()], edges: [...mergedGraph.allEdges()] });
|
|
8984
9945
|
});
|
|
9946
|
+
app.get("/api/v1/source", requireAuth, requireRole("viewer"), (req, res) => {
|
|
9947
|
+
const { file, startLine: startLineStr, endLine: endLineStr } = req.query;
|
|
9948
|
+
if (!file) {
|
|
9949
|
+
res.status(400).json({
|
|
9950
|
+
error: {
|
|
9951
|
+
code: ErrorCodes.INVALID_REQUEST,
|
|
9952
|
+
message: "Missing required query parameter: file",
|
|
9953
|
+
requestId: req.requestId,
|
|
9954
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
9955
|
+
}
|
|
9956
|
+
});
|
|
9957
|
+
return;
|
|
9958
|
+
}
|
|
9959
|
+
if (file.includes("../")) {
|
|
9960
|
+
res.status(400).json({
|
|
9961
|
+
error: {
|
|
9962
|
+
code: ErrorCodes.INVALID_REQUEST,
|
|
9963
|
+
message: "Path traversal detected",
|
|
9964
|
+
hint: 'File paths must not contain "../"',
|
|
9965
|
+
requestId: req.requestId,
|
|
9966
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
9967
|
+
}
|
|
9968
|
+
});
|
|
9969
|
+
return;
|
|
9970
|
+
}
|
|
9971
|
+
let rawResolved = path27.normalize(file);
|
|
9972
|
+
if (!path27.isAbsolute(rawResolved) && workspaceRoot) {
|
|
9973
|
+
rawResolved = path27.join(workspaceRoot, rawResolved);
|
|
9974
|
+
}
|
|
9975
|
+
const resolvedFile = path27.resolve(rawResolved);
|
|
9976
|
+
function isInsideDir(fileAbs, dir) {
|
|
9977
|
+
const rel = path27.relative(path27.resolve(dir), fileAbs);
|
|
9978
|
+
return !rel.startsWith("..") && !path27.isAbsolute(rel);
|
|
9979
|
+
}
|
|
9980
|
+
if (workspaceRoot) {
|
|
9981
|
+
if (!isInsideDir(resolvedFile, workspaceRoot)) {
|
|
9982
|
+
const registry = loadRegistry();
|
|
9983
|
+
const inKnownRepo = registry.some((r) => isInsideDir(resolvedFile, r.path));
|
|
9984
|
+
if (!inKnownRepo) {
|
|
9985
|
+
res.status(403).json({
|
|
9986
|
+
error: {
|
|
9987
|
+
code: ErrorCodes.FORBIDDEN,
|
|
9988
|
+
message: "Access denied",
|
|
9989
|
+
hint: "File path must be within an indexed repository",
|
|
9990
|
+
requestId: req.requestId,
|
|
9991
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
9992
|
+
}
|
|
9993
|
+
});
|
|
9994
|
+
return;
|
|
9995
|
+
}
|
|
9996
|
+
}
|
|
9997
|
+
} else {
|
|
9998
|
+
const registry = loadRegistry();
|
|
9999
|
+
const inKnownRepo = registry.some((r) => isInsideDir(resolvedFile, r.path));
|
|
10000
|
+
if (!inKnownRepo) {
|
|
10001
|
+
res.status(403).json({
|
|
10002
|
+
error: {
|
|
10003
|
+
code: ErrorCodes.FORBIDDEN,
|
|
10004
|
+
message: "Access denied",
|
|
10005
|
+
hint: "File path must be within an indexed repository",
|
|
10006
|
+
requestId: req.requestId,
|
|
10007
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
10008
|
+
}
|
|
10009
|
+
});
|
|
10010
|
+
return;
|
|
10011
|
+
}
|
|
10012
|
+
}
|
|
10013
|
+
let fileContent;
|
|
10014
|
+
try {
|
|
10015
|
+
fileContent = fs19.readFileSync(resolvedFile, "utf-8");
|
|
10016
|
+
} catch {
|
|
10017
|
+
res.status(404).json({
|
|
10018
|
+
error: {
|
|
10019
|
+
code: ErrorCodes.NOT_FOUND,
|
|
10020
|
+
message: "File not found",
|
|
10021
|
+
requestId: req.requestId,
|
|
10022
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
10023
|
+
}
|
|
10024
|
+
});
|
|
10025
|
+
return;
|
|
10026
|
+
}
|
|
10027
|
+
const lines = fileContent.split("\n");
|
|
10028
|
+
const parsedStart = startLineStr ? Number.parseInt(startLineStr, 10) : 1;
|
|
10029
|
+
const parsedEnd = endLineStr ? Number.parseInt(endLineStr, 10) : parsedStart;
|
|
10030
|
+
if (!Number.isFinite(parsedStart) || parsedStart < 1 || !Number.isFinite(parsedEnd) || parsedEnd < 1) {
|
|
10031
|
+
res.status(400).json({
|
|
10032
|
+
error: {
|
|
10033
|
+
code: ErrorCodes.INVALID_REQUEST,
|
|
10034
|
+
message: "Invalid startLine or endLine: must be positive integers",
|
|
10035
|
+
requestId: req.requestId,
|
|
10036
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
10037
|
+
}
|
|
10038
|
+
});
|
|
10039
|
+
return;
|
|
10040
|
+
}
|
|
10041
|
+
const startLine = Math.max(1, parsedStart);
|
|
10042
|
+
const endLine = Math.min(lines.length, parsedEnd);
|
|
10043
|
+
const contextStart = Math.max(1, startLine - 20);
|
|
10044
|
+
const contextEnd = Math.min(lines.length, endLine + 20);
|
|
10045
|
+
const content = lines.slice(contextStart - 1, contextEnd).join("\n");
|
|
10046
|
+
const ext = path27.extname(resolvedFile).toLowerCase();
|
|
10047
|
+
const languageMap = {
|
|
10048
|
+
".ts": "typescript",
|
|
10049
|
+
".tsx": "typescript",
|
|
10050
|
+
".js": "javascript",
|
|
10051
|
+
".jsx": "javascript",
|
|
10052
|
+
".mjs": "javascript",
|
|
10053
|
+
".cjs": "javascript",
|
|
10054
|
+
".py": "python",
|
|
10055
|
+
".go": "go",
|
|
10056
|
+
".rs": "rust",
|
|
10057
|
+
".java": "java",
|
|
10058
|
+
".cs": "csharp",
|
|
10059
|
+
".cpp": "cpp",
|
|
10060
|
+
".cc": "cpp",
|
|
10061
|
+
".cxx": "cpp",
|
|
10062
|
+
".c": "c",
|
|
10063
|
+
".h": "c",
|
|
10064
|
+
".hpp": "cpp",
|
|
10065
|
+
".rb": "ruby",
|
|
10066
|
+
".php": "php",
|
|
10067
|
+
".swift": "swift",
|
|
10068
|
+
".kt": "kotlin",
|
|
10069
|
+
".kts": "kotlin",
|
|
10070
|
+
".json": "json",
|
|
10071
|
+
".yaml": "yaml",
|
|
10072
|
+
".yml": "yaml",
|
|
10073
|
+
".md": "markdown",
|
|
10074
|
+
".sh": "bash",
|
|
10075
|
+
".bash": "bash",
|
|
10076
|
+
".zsh": "bash",
|
|
10077
|
+
".sql": "sql",
|
|
10078
|
+
".html": "html",
|
|
10079
|
+
".htm": "html",
|
|
10080
|
+
".css": "css",
|
|
10081
|
+
".scss": "scss",
|
|
10082
|
+
".less": "less",
|
|
10083
|
+
".xml": "xml",
|
|
10084
|
+
".toml": "toml"
|
|
10085
|
+
};
|
|
10086
|
+
const language = languageMap[ext] ?? "plaintext";
|
|
10087
|
+
res.json({
|
|
10088
|
+
content,
|
|
10089
|
+
language,
|
|
10090
|
+
startLine: contextStart,
|
|
10091
|
+
endLine: contextEnd
|
|
10092
|
+
});
|
|
10093
|
+
});
|
|
10094
|
+
app.post("/api/v1/query", requireRole("viewer"), async (req, res) => {
|
|
10095
|
+
const { gql, format } = req.body;
|
|
10096
|
+
if (!gql || typeof gql !== "string") {
|
|
10097
|
+
res.status(400).json({
|
|
10098
|
+
error: { code: ErrorCodes.INVALID_REQUEST, message: "Missing required field: gql", requestId: req.requestId, timestamp: (/* @__PURE__ */ new Date()).toISOString() }
|
|
10099
|
+
});
|
|
10100
|
+
return;
|
|
10101
|
+
}
|
|
10102
|
+
try {
|
|
10103
|
+
const { parseGQL: parseGQL2, isGQLParseError: isGQLParseError2 } = await Promise.resolve().then(() => (init_gql_parser(), gql_parser_exports));
|
|
10104
|
+
const { executeGQL: executeGQL2 } = await Promise.resolve().then(() => (init_gql_executor(), gql_executor_exports));
|
|
10105
|
+
const ast = parseGQL2(gql);
|
|
10106
|
+
if (isGQLParseError2(ast)) {
|
|
10107
|
+
res.status(422).json({
|
|
10108
|
+
error: {
|
|
10109
|
+
code: ErrorCodes.INVALID_REQUEST,
|
|
10110
|
+
message: `GQL parse error: ${ast.message}`,
|
|
10111
|
+
hint: `Position: ${ast.pos}${ast.expected ? `, expected: ${ast.expected}` : ""}${ast.got ? `, got: ${ast.got}` : ""}`,
|
|
10112
|
+
requestId: req.requestId,
|
|
10113
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
10114
|
+
}
|
|
10115
|
+
});
|
|
10116
|
+
return;
|
|
10117
|
+
}
|
|
10118
|
+
const result = executeGQL2(ast, graph);
|
|
10119
|
+
const statusCode = result.truncated ? 408 : 200;
|
|
10120
|
+
res.status(statusCode).json({ ...result, format: format ?? "json" });
|
|
10121
|
+
} catch (err) {
|
|
10122
|
+
res.status(500).json({ error: { code: ErrorCodes.INTERNAL_ERROR, message: err instanceof Error ? err.message : String(err), requestId: req.requestId, timestamp: (/* @__PURE__ */ new Date()).toISOString() } });
|
|
10123
|
+
}
|
|
10124
|
+
});
|
|
10125
|
+
app.post("/api/v1/query/explain", requireRole("viewer"), async (req, res) => {
|
|
10126
|
+
const { gql } = req.body;
|
|
10127
|
+
if (!gql || typeof gql !== "string") {
|
|
10128
|
+
res.status(400).json({
|
|
10129
|
+
error: { code: ErrorCodes.INVALID_REQUEST, message: "Missing required field: gql", requestId: req.requestId, timestamp: (/* @__PURE__ */ new Date()).toISOString() }
|
|
10130
|
+
});
|
|
10131
|
+
return;
|
|
10132
|
+
}
|
|
10133
|
+
try {
|
|
10134
|
+
const { parseGQL: parseGQL2, isGQLParseError: isGQLParseError2 } = await Promise.resolve().then(() => (init_gql_parser(), gql_parser_exports));
|
|
10135
|
+
const ast = parseGQL2(gql);
|
|
10136
|
+
if (isGQLParseError2(ast)) {
|
|
10137
|
+
res.status(422).json({
|
|
10138
|
+
error: {
|
|
10139
|
+
code: ErrorCodes.INVALID_REQUEST,
|
|
10140
|
+
message: `GQL parse error: ${ast.message}`,
|
|
10141
|
+
hint: `Position: ${ast.pos}`,
|
|
10142
|
+
requestId: req.requestId,
|
|
10143
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
10144
|
+
}
|
|
10145
|
+
});
|
|
10146
|
+
return;
|
|
10147
|
+
}
|
|
10148
|
+
const plan = { type: ast.type, gql };
|
|
10149
|
+
if (ast.type === "FIND") {
|
|
10150
|
+
plan.steps = [
|
|
10151
|
+
{ step: 1, op: "SCAN_NODES", filter: ast.target === "*" ? "all" : `kind=${ast.target}` },
|
|
10152
|
+
...ast.where ? [{ step: 2, op: "WHERE", conditions: ast.where.exprs.length }] : [],
|
|
10153
|
+
...ast.limit !== void 0 ? [{ step: 3, op: "LIMIT", value: ast.limit }] : []
|
|
10154
|
+
];
|
|
10155
|
+
plan.estimatedCost = graph.size.nodes;
|
|
10156
|
+
} else if (ast.type === "TRAVERSE") {
|
|
10157
|
+
plan.steps = [
|
|
10158
|
+
{ step: 1, op: "FIND_START_NODE", name: ast.from },
|
|
10159
|
+
{ step: 2, op: "BFS", edgeKind: ast.edgeKind, maxDepth: ast.depth ?? 5 }
|
|
10160
|
+
];
|
|
10161
|
+
plan.estimatedCost = Math.min(graph.size.nodes, Math.pow(4, ast.depth ?? 5));
|
|
10162
|
+
} else if (ast.type === "PATH") {
|
|
10163
|
+
plan.steps = [
|
|
10164
|
+
{ step: 1, op: "FIND_NODES", from: ast.from, to: ast.to },
|
|
10165
|
+
{ step: 2, op: "BFS_SHORTEST_PATH" }
|
|
10166
|
+
];
|
|
10167
|
+
plan.estimatedCost = graph.size.nodes + graph.size.edges;
|
|
10168
|
+
} else if (ast.type === "COUNT") {
|
|
10169
|
+
plan.steps = [
|
|
10170
|
+
{ step: 1, op: "SCAN_NODES", filter: ast.target === "*" ? "all" : `kind=${ast.target}` },
|
|
10171
|
+
...ast.where ? [{ step: 2, op: "WHERE", conditions: ast.where.exprs.length }] : [],
|
|
10172
|
+
...ast.groupBy ? [{ step: 3, op: "GROUP_BY", property: ast.groupBy }] : [{ step: 3, op: "COUNT" }]
|
|
10173
|
+
];
|
|
10174
|
+
plan.estimatedCost = graph.size.nodes;
|
|
10175
|
+
}
|
|
10176
|
+
res.json({ plan, graphSize: graph.size });
|
|
10177
|
+
} catch (err) {
|
|
10178
|
+
res.status(500).json({ error: { code: ErrorCodes.INTERNAL_ERROR, message: err instanceof Error ? err.message : String(err), requestId: req.requestId, timestamp: (/* @__PURE__ */ new Date()).toISOString() } });
|
|
10179
|
+
}
|
|
10180
|
+
});
|
|
8985
10181
|
if (fs19.existsSync(WEB_DIST)) {
|
|
8986
10182
|
app.use(express.static(WEB_DIST));
|
|
8987
10183
|
app.get("/{*path}", (_req, res) => {
|
|
8988
|
-
res.sendFile(
|
|
10184
|
+
res.sendFile(path27.join(WEB_DIST, "index.html"));
|
|
8989
10185
|
});
|
|
8990
10186
|
}
|
|
8991
10187
|
app.use("/admin", requireRole("admin"));
|