codemini-cli 0.5.9 → 0.5.11
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/OPERATIONS.md +242 -242
- package/README.md +588 -489
- package/codemini-web/dist/assets/{highlighted-body-OFNGDK62-HgeDi9HJ.js → highlighted-body-OFNGDK62-CANOG7Xg.js} +1 -1
- package/codemini-web/dist/assets/{index-C4tKT3v4.js → index-B71xykPM.js} +108 -108
- package/codemini-web/dist/assets/index-Dkq1DdDX.css +2 -0
- package/codemini-web/dist/assets/mermaid-GHXKKRXX-Z_w7M93P.js +1 -0
- package/codemini-web/dist/index.html +23 -23
- package/codemini-web/lib/approval-manager.js +32 -32
- package/codemini-web/lib/runtime-bridge.js +17 -11
- package/codemini-web/server.js +534 -205
- package/deployment.md +212 -212
- package/package.json +1 -1
- package/skills/brainstorm/SKILL.md +77 -72
- package/skills/codemini.skills.json +40 -40
- package/skills/grill-me/SKILL.md +30 -30
- package/skills/superpowers-lite/SKILL.md +82 -82
- package/src/cli.js +74 -74
- package/src/commands/chat.js +210 -210
- package/src/commands/run.js +313 -313
- package/src/commands/skill.js +438 -304
- package/src/commands/web.js +57 -57
- package/src/core/agent-loop.js +980 -980
- package/src/core/ast.js +309 -292
- package/src/core/chat-runtime.js +6261 -6240
- package/src/core/command-evaluator.js +72 -72
- package/src/core/command-loader.js +311 -311
- package/src/core/command-policy.js +301 -301
- package/src/core/command-risk.js +156 -156
- package/src/core/config-store.js +289 -287
- package/src/core/constants.js +18 -1
- package/src/core/context-compact.js +365 -365
- package/src/core/default-system-prompt.js +114 -107
- package/src/core/dream-audit.js +105 -105
- package/src/core/dream-consolidate.js +229 -229
- package/src/core/dream-evaluator.js +185 -185
- package/src/core/fff-adapter.js +383 -383
- package/src/core/memory-store.js +543 -543
- package/src/core/project-index.js +737 -529
- package/src/core/project-instructions.js +98 -0
- package/src/core/provider/anthropic.js +514 -514
- package/src/core/provider/openai-compatible.js +501 -501
- package/src/core/reflect-skill.js +178 -178
- package/src/core/reply-language.js +40 -40
- package/src/core/session-store.js +474 -474
- package/src/core/shell-profile.js +237 -237
- package/src/core/shell.js +323 -317
- package/src/core/soul.js +69 -69
- package/src/core/system-prompt-composer.js +52 -42
- package/src/core/tool-args.js +199 -154
- package/src/core/tool-output.js +184 -184
- package/src/core/tool-result-store.js +206 -206
- package/src/core/tools.js +3024 -2893
- package/src/core/version.js +11 -11
- package/src/tui/chat-app.js +5171 -5171
- package/src/tui/tool-activity/presenters/misc.js +30 -30
- package/src/tui/tool-activity/presenters/system.js +20 -20
- package/templates/project-requirements/report-shell.html +582 -582
- package/codemini-web/dist/assets/index-BSdIdn3L.css +0 -2
- package/codemini-web/dist/assets/mermaid-GHXKKRXX-CDgkkDBg.js +0 -1
|
@@ -1,595 +1,803 @@
|
|
|
1
|
-
import fs from 'node:fs/promises';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { getFileIndexPath, getProjectIndexDir, getProjectMapPath, getProjectWorkspaceDir } from './paths.js';
|
|
4
|
-
import { INDEX_SKIP_DIRS as SKIP_DIRS, SOURCE_EXTENSIONS, EXTENSION_LANGUAGE_MAP } from './constants.js';
|
|
5
|
-
import { sha256 } from './crypto-utils.js';
|
|
6
|
-
import { BoundedCache } from './bounded-cache.js';
|
|
7
|
-
import { trimInline, normalizeRelativePath, escapeRegex } from './string-utils.js';
|
|
8
|
-
|
|
9
|
-
const PROJECT_MARKER_FILES = new Set([
|
|
10
|
-
'package.json',
|
|
11
|
-
'tsconfig.json',
|
|
12
|
-
'pyproject.toml',
|
|
13
|
-
'requirements.txt',
|
|
14
|
-
'go.mod',
|
|
15
|
-
'Cargo.toml',
|
|
16
|
-
'composer.json',
|
|
17
|
-
'Gemfile',
|
|
18
|
-
'pom.xml',
|
|
19
|
-
'build.gradle',
|
|
20
|
-
'build.gradle.kts',
|
|
21
|
-
'Makefile',
|
|
22
|
-
'.gitignore'
|
|
23
|
-
]);
|
|
24
|
-
|
|
25
|
-
const LANGUAGE_BY_EXT = EXTENSION_LANGUAGE_MAP;
|
|
26
|
-
|
|
27
|
-
const initCache = new BoundedCache({ maxSize: 32, ttlMs: 10 * 60 * 1000 });
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (text
|
|
62
|
-
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { getFileIndexPath, getProjectIndexDir, getProjectMapPath, getProjectWorkspaceDir } from './paths.js';
|
|
4
|
+
import { INDEX_SKIP_DIRS as SKIP_DIRS, SOURCE_EXTENSIONS, EXTENSION_LANGUAGE_MAP } from './constants.js';
|
|
5
|
+
import { sha256 } from './crypto-utils.js';
|
|
6
|
+
import { BoundedCache } from './bounded-cache.js';
|
|
7
|
+
import { trimInline, normalizeRelativePath, escapeRegex } from './string-utils.js';
|
|
8
|
+
|
|
9
|
+
const PROJECT_MARKER_FILES = new Set([
|
|
10
|
+
'package.json',
|
|
11
|
+
'tsconfig.json',
|
|
12
|
+
'pyproject.toml',
|
|
13
|
+
'requirements.txt',
|
|
14
|
+
'go.mod',
|
|
15
|
+
'Cargo.toml',
|
|
16
|
+
'composer.json',
|
|
17
|
+
'Gemfile',
|
|
18
|
+
'pom.xml',
|
|
19
|
+
'build.gradle',
|
|
20
|
+
'build.gradle.kts',
|
|
21
|
+
'Makefile',
|
|
22
|
+
'.gitignore'
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
const LANGUAGE_BY_EXT = EXTENSION_LANGUAGE_MAP;
|
|
26
|
+
|
|
27
|
+
const initCache = new BoundedCache({ maxSize: 32, ttlMs: 10 * 60 * 1000 });
|
|
28
|
+
const ignoreRulesCache = new BoundedCache({ maxSize: 128, ttlMs: 60 * 1000 });
|
|
29
|
+
const PROJECT_CONTEXT_MAX_FILES = 6;
|
|
30
|
+
|
|
31
|
+
function clipList(values, max = 32) {
|
|
32
|
+
return [...new Set((Array.isArray(values) ? values : []).filter(Boolean))].slice(0, max);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function rel(cwd, filePath) {
|
|
36
|
+
return normalizeRelativePath(path.relative(cwd, filePath));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function safeStat(filePath) {
|
|
40
|
+
try {
|
|
41
|
+
return await fs.stat(filePath);
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function safeReadJson(filePath, fallback) {
|
|
48
|
+
try {
|
|
49
|
+
return JSON.parse(await fs.readFile(filePath, 'utf8'));
|
|
50
|
+
} catch {
|
|
51
|
+
return fallback;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function tokenizeQuery(text) {
|
|
56
|
+
return [...new Set(String(text || '').toLowerCase().match(/[a-z0-9_./-]+/g) || [])].filter(Boolean);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function trimMultiline(value, max = 1800) {
|
|
60
|
+
const text = String(value || '').trim();
|
|
61
|
+
if (!text) return '';
|
|
62
|
+
if (text.length <= max) return text;
|
|
63
|
+
return `${text.slice(0, max - 3).trimEnd()}...`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function writeJson(filePath, value) {
|
|
67
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
68
|
+
await fs.writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function gitignorePatternToRegex(pattern) {
|
|
72
|
+
const normalized = normalizeRelativePath(pattern);
|
|
73
|
+
let regexBody = '';
|
|
74
|
+
for (let index = 0; index < normalized.length; index += 1) {
|
|
75
|
+
const ch = normalized[index];
|
|
76
|
+
const next = normalized[index + 1];
|
|
77
|
+
if (ch === '*') {
|
|
78
|
+
if (next === '*') {
|
|
79
|
+
regexBody += '.*';
|
|
80
|
+
index += 1;
|
|
81
|
+
} else {
|
|
82
|
+
regexBody += '[^/]*';
|
|
83
|
+
}
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (ch === '?') {
|
|
87
|
+
regexBody += '[^/]';
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
regexBody += escapeRegex(ch);
|
|
91
|
+
}
|
|
92
|
+
return new RegExp(`^${regexBody}$`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function readIgnoreFileRules(cwd, fileName) {
|
|
96
|
+
const filePath = path.join(cwd, fileName);
|
|
97
|
+
const stat = await safeStat(filePath);
|
|
98
|
+
const cacheKey = `${filePath}:${Number(stat?.mtimeMs || 0)}:${Number(stat?.size || 0)}`;
|
|
99
|
+
if (ignoreRulesCache.has(cacheKey)) return ignoreRulesCache.get(cacheKey);
|
|
100
|
+
|
|
101
|
+
for (const key of ignoreRulesCache.keys()) {
|
|
102
|
+
if (String(key).startsWith(`${filePath}:`) && key !== cacheKey) {
|
|
103
|
+
ignoreRulesCache.delete(key);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
if (!stat?.isFile()) {
|
|
109
|
+
ignoreRulesCache.set(cacheKey, []);
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
const raw = await fs.readFile(filePath, 'utf8');
|
|
113
|
+
const rules = raw
|
|
114
|
+
.split(/\r?\n/)
|
|
115
|
+
.map((line) => line.trim())
|
|
116
|
+
.filter((line) => line && !line.startsWith('#'))
|
|
117
|
+
.map((line) => {
|
|
118
|
+
const negated = line.startsWith('!');
|
|
119
|
+
const source = negated ? line.slice(1) : line;
|
|
120
|
+
const dirOnly = source.endsWith('/');
|
|
121
|
+
const anchored = source.startsWith('/');
|
|
122
|
+
const normalized = normalizeRelativePath(dirOnly ? source.slice(0, -1) : source);
|
|
123
|
+
return {
|
|
124
|
+
negated,
|
|
125
|
+
dirOnly,
|
|
126
|
+
anchored,
|
|
127
|
+
normalized,
|
|
128
|
+
hasSlash: normalized.includes('/'),
|
|
129
|
+
regex: gitignorePatternToRegex(normalized)
|
|
130
|
+
};
|
|
131
|
+
})
|
|
132
|
+
.filter((rule) => rule.normalized);
|
|
133
|
+
ignoreRulesCache.set(cacheKey, rules);
|
|
134
|
+
return rules;
|
|
135
|
+
} catch {
|
|
136
|
+
ignoreRulesCache.set(cacheKey, []);
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function readProjectIgnoreRules(cwd) {
|
|
142
|
+
const [gitignoreRules, llmignoreRules] = await Promise.all([
|
|
143
|
+
readIgnoreFileRules(cwd, '.gitignore'),
|
|
144
|
+
readIgnoreFileRules(cwd, '.llmignore')
|
|
145
|
+
]);
|
|
146
|
+
return {
|
|
147
|
+
gitignoreRules,
|
|
148
|
+
llmignoreRules,
|
|
149
|
+
combinedRules: [...gitignoreRules, ...llmignoreRules]
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function matchesGitignoreRule(rule, relativePath, isDirectory) {
|
|
154
|
+
if (!rule || !relativePath) return false;
|
|
155
|
+
if (rule.dirOnly && !isDirectory) return false;
|
|
156
|
+
const normalizedPath = normalizeRelativePath(relativePath);
|
|
157
|
+
if (!normalizedPath) return false;
|
|
158
|
+
if (rule.anchored || rule.hasSlash) {
|
|
159
|
+
return rule.regex.test(normalizedPath);
|
|
160
|
+
}
|
|
161
|
+
return normalizedPath.split('/').some((segment) => rule.regex.test(segment));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function shouldIgnorePath(relativePath, isDirectory, gitignoreRules = []) {
|
|
165
|
+
const normalizedPath = normalizeRelativePath(relativePath);
|
|
166
|
+
if (!normalizedPath) return false;
|
|
167
|
+
const segments = normalizedPath.split('/').filter(Boolean);
|
|
168
|
+
if (segments.some((segment) => SKIP_DIRS.has(segment))) return true;
|
|
169
|
+
if (segments.some((segment) => /^venv[-_]/i.test(segment) || /\.egg-info$/i.test(segment))) return true;
|
|
170
|
+
let ignored = false;
|
|
171
|
+
for (const rule of gitignoreRules) {
|
|
172
|
+
if (!matchesGitignoreRule(rule, normalizedPath, isDirectory)) continue;
|
|
173
|
+
ignored = !rule.negated;
|
|
174
|
+
}
|
|
175
|
+
return ignored;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function detectWorkspaceKind(cwd) {
|
|
179
|
+
const gitDir = await safeStat(path.join(cwd, '.git'));
|
|
180
|
+
if (gitDir?.isDirectory()) return 'project';
|
|
181
|
+
for (const marker of PROJECT_MARKER_FILES) {
|
|
182
|
+
const stat = await safeStat(path.join(cwd, marker));
|
|
183
|
+
if (stat?.isFile()) return 'project';
|
|
184
|
+
}
|
|
185
|
+
return 'directory';
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function findNearestProjectRoot(startDir, workspaceRoot) {
|
|
189
|
+
let current = path.resolve(startDir);
|
|
190
|
+
const root = path.resolve(workspaceRoot);
|
|
191
|
+
while (current.startsWith(root)) {
|
|
192
|
+
if ((await detectWorkspaceKind(current)) === 'project') return current;
|
|
193
|
+
if (current === root) break;
|
|
194
|
+
const parent = path.dirname(current);
|
|
195
|
+
if (parent === current) break;
|
|
196
|
+
current = parent;
|
|
197
|
+
}
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function findProjectRootFromFile(workspaceRoot, relativePath = '') {
|
|
202
|
+
const absolutePath = path.resolve(workspaceRoot, String(relativePath || '.'));
|
|
203
|
+
const stat = await safeStat(absolutePath);
|
|
204
|
+
const probeStart = stat?.isDirectory() ? absolutePath : path.dirname(absolutePath);
|
|
205
|
+
return findNearestProjectRoot(probeStart, workspaceRoot);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async function findNearestIndexedProjectRoot(startDir, workspaceRoot) {
|
|
209
|
+
let current = path.resolve(startDir);
|
|
210
|
+
const root = path.resolve(workspaceRoot);
|
|
211
|
+
while (current.startsWith(root)) {
|
|
212
|
+
const projectMapStat = await safeStat(getProjectMapPath(current));
|
|
213
|
+
const fileIndexStat = await safeStat(getFileIndexPath(current));
|
|
214
|
+
if (projectMapStat?.isFile() && fileIndexStat?.isFile()) return current;
|
|
215
|
+
if (current === root) break;
|
|
216
|
+
const parent = path.dirname(current);
|
|
217
|
+
if (parent === current) break;
|
|
218
|
+
current = parent;
|
|
219
|
+
}
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function walkFiles(cwd, start = cwd, out = [], ignoreRules = []) {
|
|
224
|
+
const entries = await fs.readdir(start, { withFileTypes: true });
|
|
225
|
+
for (const entry of entries) {
|
|
226
|
+
const absolutePath = path.join(start, entry.name);
|
|
227
|
+
const relativePath = rel(cwd, absolutePath);
|
|
228
|
+
if (entry.isDirectory()) {
|
|
229
|
+
if (shouldIgnorePath(relativePath, true, ignoreRules)) continue;
|
|
230
|
+
await walkFiles(cwd, absolutePath, out, ignoreRules);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (shouldIgnorePath(relativePath, false, ignoreRules)) continue;
|
|
234
|
+
out.push(absolutePath);
|
|
235
|
+
}
|
|
236
|
+
return out;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function categorizeDirectory(relativeDir) {
|
|
240
|
+
const text = String(relativeDir || '').toLowerCase();
|
|
241
|
+
if (!text || text === '.') return 'root';
|
|
242
|
+
if (/(^|\/)(src|app|apps)\b/.test(text)) return 'source';
|
|
243
|
+
if (/(^|\/)(test|tests|__tests__|spec)\b/.test(text)) return 'test';
|
|
244
|
+
if (/(^|\/)(scripts|bin)\b/.test(text)) return 'script';
|
|
245
|
+
if (/(^|\/)(config|configs)\b/.test(text)) return 'config';
|
|
246
|
+
return 'other';
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function extractMatches(regex, text, group = 1) {
|
|
250
|
+
const out = [];
|
|
251
|
+
for (const match of String(text || '').matchAll(regex)) {
|
|
252
|
+
const value = String(match[group] || '').trim();
|
|
253
|
+
if (value) out.push(value);
|
|
254
|
+
}
|
|
255
|
+
return out;
|
|
63
256
|
}
|
|
64
257
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
await fs.writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
258
|
+
function lineNumberForIndex(content, index) {
|
|
259
|
+
return String(content || '').slice(0, Math.max(0, index)).split(/\r?\n/).length;
|
|
68
260
|
}
|
|
69
261
|
|
|
70
|
-
function
|
|
71
|
-
|
|
72
|
-
let
|
|
73
|
-
for (let index =
|
|
74
|
-
const ch =
|
|
75
|
-
|
|
76
|
-
if (ch === '
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
index += 1;
|
|
80
|
-
} else {
|
|
81
|
-
regexBody += '[^/]*';
|
|
82
|
-
}
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
if (ch === '?') {
|
|
86
|
-
regexBody += '[^/]';
|
|
87
|
-
continue;
|
|
262
|
+
function findBraceRange(content, openBraceIndex) {
|
|
263
|
+
if (openBraceIndex < 0) return null;
|
|
264
|
+
let depth = 0;
|
|
265
|
+
for (let index = openBraceIndex; index < content.length; index += 1) {
|
|
266
|
+
const ch = content[index];
|
|
267
|
+
if (ch === '{') depth += 1;
|
|
268
|
+
if (ch === '}') {
|
|
269
|
+
depth -= 1;
|
|
270
|
+
if (depth === 0) return { start: openBraceIndex, end: index + 1 };
|
|
88
271
|
}
|
|
89
|
-
regexBody += escapeRegex(ch);
|
|
90
272
|
}
|
|
91
|
-
return
|
|
273
|
+
return null;
|
|
92
274
|
}
|
|
93
275
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
.map((line) => line.trim())
|
|
100
|
-
.filter((line) => line && !line.startsWith('#'))
|
|
101
|
-
.map((line) => {
|
|
102
|
-
const negated = line.startsWith('!');
|
|
103
|
-
const source = negated ? line.slice(1) : line;
|
|
104
|
-
const dirOnly = source.endsWith('/');
|
|
105
|
-
const anchored = source.startsWith('/');
|
|
106
|
-
const normalized = normalizeRelativePath(dirOnly ? source.slice(0, -1) : source);
|
|
107
|
-
return {
|
|
108
|
-
negated,
|
|
109
|
-
dirOnly,
|
|
110
|
-
anchored,
|
|
111
|
-
normalized,
|
|
112
|
-
hasSlash: normalized.includes('/'),
|
|
113
|
-
regex: gitignorePatternToRegex(normalized)
|
|
114
|
-
};
|
|
115
|
-
})
|
|
116
|
-
.filter((rule) => rule.normalized);
|
|
117
|
-
} catch {
|
|
118
|
-
return [];
|
|
119
|
-
}
|
|
276
|
+
function inferSymbolType(kind) {
|
|
277
|
+
if (kind === 'class') return 'class';
|
|
278
|
+
if (kind === 'method') return 'method';
|
|
279
|
+
if (kind === 'const') return 'function';
|
|
280
|
+
return 'function';
|
|
120
281
|
}
|
|
121
282
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
gitignoreRules,
|
|
129
|
-
llmignoreRules,
|
|
130
|
-
combinedRules: [...gitignoreRules, ...llmignoreRules]
|
|
131
|
-
};
|
|
283
|
+
function extractCallNames(content) {
|
|
284
|
+
return clipList(
|
|
285
|
+
extractMatches(/\b([A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)?)\s*\(/g, content)
|
|
286
|
+
.filter((name) => !['if', 'for', 'while', 'switch', 'return', 'function', 'class', 'catch'].includes(String(name).split('.')[0])),
|
|
287
|
+
64
|
|
288
|
+
);
|
|
132
289
|
}
|
|
133
290
|
|
|
134
|
-
function
|
|
135
|
-
|
|
136
|
-
if (rule.dirOnly && !isDirectory) return false;
|
|
137
|
-
const normalizedPath = normalizeRelativePath(relativePath);
|
|
138
|
-
if (!normalizedPath) return false;
|
|
139
|
-
if (rule.anchored || rule.hasSlash) {
|
|
140
|
-
return rule.regex.test(normalizedPath);
|
|
141
|
-
}
|
|
142
|
-
return normalizedPath.split('/').some((segment) => rule.regex.test(segment));
|
|
291
|
+
function extractSemanticWrites(calls) {
|
|
292
|
+
return clipList((calls || []).filter((name) => /\.(insert|update|upsert|delete|save|write|create)$/i.test(String(name)) || /^(insert|update|upsert|delete|save|write|create)$/i.test(String(name))), 16);
|
|
143
293
|
}
|
|
144
294
|
|
|
145
|
-
function
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
for (const rule of gitignoreRules) {
|
|
152
|
-
if (!matchesGitignoreRule(rule, normalizedPath, isDirectory)) continue;
|
|
153
|
-
ignored = !rule.negated;
|
|
154
|
-
}
|
|
155
|
-
return ignored;
|
|
295
|
+
function extractSemanticEmits(calls, content) {
|
|
296
|
+
const eventNames = extractMatches(/\b(?:emit|publish|dispatch)\s*\(\s*['"`]([^'"`]+)['"`]/g, content);
|
|
297
|
+
return clipList([
|
|
298
|
+
...eventNames,
|
|
299
|
+
...(calls || []).filter((name) => /\.(emit|publish|dispatch)$/i.test(String(name)) || /^(emit|publish|dispatch)$/i.test(String(name)))
|
|
300
|
+
], 16);
|
|
156
301
|
}
|
|
157
302
|
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
303
|
+
function extractSymbolDefinitions(relativePath, content, imports = []) {
|
|
304
|
+
const definitions = [];
|
|
305
|
+
const patterns = [
|
|
306
|
+
{ kind: 'class', regex: /\b(?:export\s+)?class\s+([A-Za-z_$][A-Za-z0-9_$]*)[^{]*\{/g },
|
|
307
|
+
{ kind: 'function', regex: /\b(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*\([^)]*\)\s*\{/g },
|
|
308
|
+
{ kind: 'const', regex: /\b(?:export\s+)?const\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*=\s*(?:async\s*)?\([^)]*\)\s*=>\s*\{/g },
|
|
309
|
+
{ kind: 'python', regex: /^\s*(?:async\s+)?def\s+([A-Za-z_][A-Za-z0-9_]*)\s*\([^)]*\)\s*:/gm },
|
|
310
|
+
{ kind: 'go', regex: /^\s*func\s+(?:\([^)]*\)\s*)?([A-Za-z_][A-Za-z0-9_]*)\s*\([^)]*\)\s*\{/gm }
|
|
311
|
+
];
|
|
167
312
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
313
|
+
for (const { kind, regex } of patterns) {
|
|
314
|
+
for (const match of String(content || '').matchAll(regex)) {
|
|
315
|
+
const name = String(match[1] || '').trim();
|
|
316
|
+
if (!name) continue;
|
|
317
|
+
const start = match.index || 0;
|
|
318
|
+
const openBrace = content.indexOf('{', start);
|
|
319
|
+
const braceRange = openBrace >= 0 ? findBraceRange(content, openBrace) : null;
|
|
320
|
+
const end = braceRange?.end || content.indexOf('\n', start + String(match[0] || '').length);
|
|
321
|
+
const safeEnd = end > start ? end : start + String(match[0] || '').length;
|
|
322
|
+
const body = content.slice(start, safeEnd);
|
|
323
|
+
const startLine = lineNumberForIndex(content, start);
|
|
324
|
+
const endLine = lineNumberForIndex(content, safeEnd);
|
|
325
|
+
const signature = trimInline(String(match[0] || '').replace(/\s*\{\s*$/, '').replace(/\s*:\s*$/, ''), 220);
|
|
326
|
+
const calls = extractCallNames(body);
|
|
327
|
+
definitions.push({
|
|
328
|
+
symbol_id: `${relativePath}#${name}`,
|
|
329
|
+
name,
|
|
330
|
+
type: inferSymbolType(kind),
|
|
331
|
+
file: relativePath,
|
|
332
|
+
range: { start_line: startLine, end_line: endLine },
|
|
333
|
+
signature,
|
|
334
|
+
calls,
|
|
335
|
+
called_by: [],
|
|
336
|
+
imports: clipList(imports, 12),
|
|
337
|
+
writes: extractSemanticWrites(calls),
|
|
338
|
+
emits: extractSemanticEmits(calls, body),
|
|
339
|
+
used_by: []
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
if (kind === 'class' && braceRange) {
|
|
343
|
+
const classBodyStart = openBrace + 1;
|
|
344
|
+
const classBody = content.slice(classBodyStart, braceRange.end - 1);
|
|
345
|
+
for (const methodMatch of classBody.matchAll(/^\s*(?:async\s+)?([A-Za-z_$][A-Za-z0-9_$]*)\s*\([^)]*\)\s*\{/gm)) {
|
|
346
|
+
const methodName = String(methodMatch[1] || '').trim();
|
|
347
|
+
if (!methodName || ['if', 'for', 'while', 'switch', 'catch'].includes(methodName)) continue;
|
|
348
|
+
const leadingWhitespace = String(methodMatch[0] || '').search(/\S/);
|
|
349
|
+
const methodStart = classBodyStart + (methodMatch.index || 0) + Math.max(0, leadingWhitespace);
|
|
350
|
+
const methodOpenBrace = content.indexOf('{', methodStart);
|
|
351
|
+
const methodBraceRange = findBraceRange(content, methodOpenBrace);
|
|
352
|
+
const methodEnd = methodBraceRange?.end || methodStart + String(methodMatch[0] || '').length;
|
|
353
|
+
const methodBody = content.slice(methodStart, methodEnd);
|
|
354
|
+
const methodCalls = extractCallNames(methodBody);
|
|
355
|
+
definitions.push({
|
|
356
|
+
symbol_id: `${relativePath}#${name}.${methodName}`,
|
|
357
|
+
name: `${name}.${methodName}`,
|
|
358
|
+
type: 'method',
|
|
359
|
+
file: relativePath,
|
|
360
|
+
range: {
|
|
361
|
+
start_line: lineNumberForIndex(content, methodStart),
|
|
362
|
+
end_line: lineNumberForIndex(content, methodEnd)
|
|
363
|
+
},
|
|
364
|
+
signature: trimInline(`${name}.${String(methodMatch[0] || '').replace(/\s*\{\s*$/, '')}`, 220),
|
|
365
|
+
calls: methodCalls,
|
|
366
|
+
called_by: [],
|
|
367
|
+
imports: clipList(imports, 12),
|
|
368
|
+
writes: extractSemanticWrites(methodCalls),
|
|
369
|
+
emits: extractSemanticEmits(methodCalls, methodBody),
|
|
370
|
+
used_by: []
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
177
375
|
}
|
|
178
|
-
return null;
|
|
179
|
-
}
|
|
180
376
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const probeStart = stat?.isDirectory() ? absolutePath : path.dirname(absolutePath);
|
|
185
|
-
return findNearestProjectRoot(probeStart, workspaceRoot);
|
|
377
|
+
return definitions
|
|
378
|
+
.sort((left, right) => left.range.start_line - right.range.start_line || left.name.localeCompare(right.name))
|
|
379
|
+
.slice(0, 200);
|
|
186
380
|
}
|
|
187
381
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const
|
|
197
|
-
if (
|
|
198
|
-
|
|
382
|
+
function enrichSymbolGraph(files) {
|
|
383
|
+
const nextFiles = (Array.isArray(files) ? files : []).map((entry) => ({
|
|
384
|
+
...entry,
|
|
385
|
+
symbols: Array.isArray(entry.symbols) ? entry.symbols.map((symbol) => ({ ...symbol, called_by: [], used_by: [] })) : []
|
|
386
|
+
}));
|
|
387
|
+
const symbols = nextFiles.flatMap((entry) => entry.symbols || []);
|
|
388
|
+
const byName = new Map();
|
|
389
|
+
for (const symbol of symbols) {
|
|
390
|
+
const shortName = String(symbol.name || '').split('.').pop();
|
|
391
|
+
if (!shortName) continue;
|
|
392
|
+
if (!byName.has(shortName)) byName.set(shortName, []);
|
|
393
|
+
byName.get(shortName).push(symbol);
|
|
199
394
|
}
|
|
200
|
-
return null;
|
|
201
|
-
}
|
|
202
395
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
continue;
|
|
396
|
+
for (const source of symbols) {
|
|
397
|
+
for (const rawCall of source.calls || []) {
|
|
398
|
+
const callName = String(rawCall || '').split('.').pop();
|
|
399
|
+
const targets = byName.get(callName) || [];
|
|
400
|
+
for (const target of targets) {
|
|
401
|
+
if (!target?.symbol_id || target.symbol_id === source.symbol_id) continue;
|
|
402
|
+
target.called_by = clipList([...(target.called_by || []), source.symbol_id], 32);
|
|
403
|
+
}
|
|
212
404
|
}
|
|
213
|
-
if (shouldIgnorePath(relativePath, false, ignoreRules)) continue;
|
|
214
|
-
out.push(absolutePath);
|
|
215
405
|
}
|
|
216
|
-
return out;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function categorizeDirectory(relativeDir) {
|
|
220
|
-
const text = String(relativeDir || '').toLowerCase();
|
|
221
|
-
if (!text || text === '.') return 'root';
|
|
222
|
-
if (/(^|\/)(src|app|apps)\b/.test(text)) return 'source';
|
|
223
|
-
if (/(^|\/)(test|tests|__tests__|spec)\b/.test(text)) return 'test';
|
|
224
|
-
if (/(^|\/)(scripts|bin)\b/.test(text)) return 'script';
|
|
225
|
-
if (/(^|\/)(config|configs)\b/.test(text)) return 'config';
|
|
226
|
-
return 'other';
|
|
227
|
-
}
|
|
228
406
|
|
|
229
|
-
|
|
230
|
-
const out = [];
|
|
231
|
-
for (const match of String(text || '').matchAll(regex)) {
|
|
232
|
-
const value = String(match[group] || '').trim();
|
|
233
|
-
if (value) out.push(value);
|
|
234
|
-
}
|
|
235
|
-
return out;
|
|
407
|
+
return nextFiles;
|
|
236
408
|
}
|
|
237
409
|
|
|
238
410
|
function buildFileEntry(relativePath, content, stat) {
|
|
239
411
|
const ext = path.extname(relativePath).toLowerCase();
|
|
240
412
|
const imports = clipList([
|
|
241
|
-
...extractMatches(/import\s+(?:[^'"]*from\s+)?['"]([^'"]+)['"]/g, content),
|
|
242
|
-
...extractMatches(/require\(\s*['"]([^'"]+)['"]\s*\)/g, content),
|
|
243
|
-
...extractMatches(/\buse\s+([A-Za-z0-9_:\\]+)/g, ext === '.rs' ? content : '')
|
|
244
|
-
]);
|
|
245
|
-
const exports = clipList([
|
|
246
|
-
...extractMatches(/export\s+(?:async\s+)?function\s+([A-Za-z0-9_$]+)/g, content),
|
|
247
|
-
...extractMatches(/export\s+class\s+([A-Za-z0-9_$]+)/g, content),
|
|
248
|
-
...extractMatches(/export\s+const\s+([A-Za-z0-9_$]+)/g, content),
|
|
249
|
-
...extractMatches(/module\.exports\s*=\s*([A-Za-z0-9_$]+)/g, content),
|
|
250
|
-
...extractMatches(/exports\.([A-Za-z0-9_$]+)/g, content)
|
|
251
|
-
]);
|
|
252
|
-
const functions = clipList([
|
|
253
|
-
...extractMatches(/\bfunction\s+([A-Za-z0-9_$]+)/g, content),
|
|
254
|
-
...extractMatches(/\bdef\s+([A-Za-z0-9_]+)/g, content),
|
|
255
|
-
...extractMatches(/\bfunc\s+([A-Za-z0-9_]+)/g, content),
|
|
256
|
-
...extractMatches(/\bfn\s+([A-Za-z0-9_]+)/g, content),
|
|
257
|
-
...extractMatches(/^\s*(?:public|private|protected|internal)?\s*(?:static\s+)?[A-Za-z0-9_<>,[\]?]+\s+([A-Za-z0-9_]+)\s*\(/gm, content),
|
|
258
|
-
...extractMatches(/^\s*function\s+([A-Za-z0-9_]+)/gm, content),
|
|
259
|
-
...extractMatches(/^\s*def\s+([A-Za-z0-9_]+)/gm, content)
|
|
260
|
-
]);
|
|
261
|
-
const classes = clipList([
|
|
262
|
-
...extractMatches(/\bclass\s+([A-Za-z0-9_$]+)/g, content)
|
|
263
|
-
]);
|
|
264
|
-
const calls =
|
|
265
|
-
|
|
266
|
-
], 64);
|
|
413
|
+
...extractMatches(/import\s+(?:[^'"]*from\s+)?['"]([^'"]+)['"]/g, content),
|
|
414
|
+
...extractMatches(/require\(\s*['"]([^'"]+)['"]\s*\)/g, content),
|
|
415
|
+
...extractMatches(/\buse\s+([A-Za-z0-9_:\\]+)/g, ext === '.rs' ? content : '')
|
|
416
|
+
]);
|
|
417
|
+
const exports = clipList([
|
|
418
|
+
...extractMatches(/export\s+(?:async\s+)?function\s+([A-Za-z0-9_$]+)/g, content),
|
|
419
|
+
...extractMatches(/export\s+class\s+([A-Za-z0-9_$]+)/g, content),
|
|
420
|
+
...extractMatches(/export\s+const\s+([A-Za-z0-9_$]+)/g, content),
|
|
421
|
+
...extractMatches(/module\.exports\s*=\s*([A-Za-z0-9_$]+)/g, content),
|
|
422
|
+
...extractMatches(/exports\.([A-Za-z0-9_$]+)/g, content)
|
|
423
|
+
]);
|
|
424
|
+
const functions = clipList([
|
|
425
|
+
...extractMatches(/\bfunction\s+([A-Za-z0-9_$]+)/g, content),
|
|
426
|
+
...extractMatches(/\bdef\s+([A-Za-z0-9_]+)/g, content),
|
|
427
|
+
...extractMatches(/\bfunc\s+([A-Za-z0-9_]+)/g, content),
|
|
428
|
+
...extractMatches(/\bfn\s+([A-Za-z0-9_]+)/g, content),
|
|
429
|
+
...extractMatches(/^\s*(?:public|private|protected|internal)?\s*(?:static\s+)?[A-Za-z0-9_<>,[\]?]+\s+([A-Za-z0-9_]+)\s*\(/gm, content),
|
|
430
|
+
...extractMatches(/^\s*function\s+([A-Za-z0-9_]+)/gm, content),
|
|
431
|
+
...extractMatches(/^\s*def\s+([A-Za-z0-9_]+)/gm, content)
|
|
432
|
+
]);
|
|
433
|
+
const classes = clipList([
|
|
434
|
+
...extractMatches(/\bclass\s+([A-Za-z0-9_$]+)/g, content)
|
|
435
|
+
]);
|
|
436
|
+
const calls = extractCallNames(content);
|
|
437
|
+
const symbols = extractSymbolDefinitions(relativePath, content, imports);
|
|
267
438
|
|
|
268
439
|
return {
|
|
269
|
-
file: relativePath,
|
|
270
|
-
language: LANGUAGE_BY_EXT[ext] || 'text',
|
|
271
|
-
hash: sha256(content),
|
|
272
|
-
size: Number(stat?.size || content.length || 0),
|
|
273
|
-
mtimeMs: Number(stat?.mtimeMs || 0),
|
|
274
|
-
imports,
|
|
275
|
-
exports,
|
|
440
|
+
file: relativePath,
|
|
441
|
+
language: LANGUAGE_BY_EXT[ext] || 'text',
|
|
442
|
+
hash: sha256(content),
|
|
443
|
+
size: Number(stat?.size || content.length || 0),
|
|
444
|
+
mtimeMs: Number(stat?.mtimeMs || 0),
|
|
445
|
+
imports,
|
|
446
|
+
exports,
|
|
276
447
|
functions,
|
|
277
448
|
classes,
|
|
278
|
-
calls
|
|
449
|
+
calls,
|
|
450
|
+
symbols
|
|
279
451
|
};
|
|
280
452
|
}
|
|
281
|
-
|
|
282
|
-
async function scanProject(cwd) {
|
|
283
|
-
const workspaceKind = await detectWorkspaceKind(cwd);
|
|
284
|
-
if (workspaceKind !== 'project') {
|
|
285
|
-
return {
|
|
286
|
-
workspaceKind,
|
|
287
|
-
projectMap: null,
|
|
288
|
-
fileIndex: null,
|
|
289
|
-
ignoreRules: []
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
const { gitignoreRules, llmignoreRules, combinedRules } = await readProjectIgnoreRules(cwd);
|
|
294
|
-
const allFiles = await walkFiles(cwd, cwd, [], combinedRules);
|
|
295
|
-
const relativeFiles = allFiles.map((filePath) => rel(cwd, filePath));
|
|
296
|
-
const sourceFiles = allFiles.filter((filePath) => SOURCE_EXTENSIONS.has(path.extname(filePath).toLowerCase()));
|
|
297
|
-
|
|
298
|
-
const packageJson = await safeReadJson(path.join(cwd, 'package.json'), null);
|
|
299
|
-
const tsconfigExists = Boolean(await safeStat(path.join(cwd, 'tsconfig.json')));
|
|
300
|
-
const sourceRoots = clipList(relativeFiles.filter((value) => /^(src|app|apps)\b/.test(value)).map((value) => value.split('/')[0]), 12);
|
|
301
|
-
const testRoots = clipList(relativeFiles.filter((value) => /^(tests|test|__tests__)\b/.test(value)).map((value) => value.split('/')[0]), 12);
|
|
302
|
-
const entryCandidates = clipList(
|
|
303
|
-
relativeFiles.filter((value) => /(^|\/)(main|index|server|app)\.(js|jsx|mjs|cjs|ts|tsx|py|go|rs|java|cs|php|rb)$/.test(value)),
|
|
304
|
-
16
|
|
305
|
-
);
|
|
306
|
-
const languages = clipList(sourceFiles.map((filePath) => LANGUAGE_BY_EXT[path.extname(filePath).toLowerCase()] || '').filter(Boolean), 16);
|
|
307
|
-
const importantFiles = clipList(
|
|
308
|
-
relativeFiles.filter((value) => ['package.json', 'tsconfig.json', 'pyproject.toml', 'go.mod', 'Cargo.toml', 'composer.json', 'Gemfile'].includes(value)),
|
|
309
|
-
16
|
|
310
|
-
);
|
|
311
|
-
const packageManagers = clipList([
|
|
312
|
-
packageJson ? 'npm' : '',
|
|
313
|
-
relativeFiles.includes('bun.lockb') ? 'bun' : '',
|
|
314
|
-
relativeFiles.includes('pnpm-lock.yaml') ? 'pnpm' : '',
|
|
315
|
-
relativeFiles.includes('yarn.lock') ? 'yarn' : ''
|
|
316
|
-
].filter(Boolean));
|
|
317
|
-
const frameworkHints = clipList([
|
|
318
|
-
packageJson?.dependencies?.react || packageJson?.devDependencies?.react ? 'react' : '',
|
|
319
|
-
packageJson?.dependencies?.express ? 'express' : '',
|
|
320
|
-
packageJson?.dependencies?.vue ? 'vue' : '',
|
|
321
|
-
packageJson?.dependencies?.next ? 'next' : '',
|
|
322
|
-
tsconfigExists ? 'typescript' : ''
|
|
323
|
-
].filter(Boolean));
|
|
324
|
-
|
|
325
|
-
const directories = {};
|
|
326
|
-
for (const value of relativeFiles) {
|
|
327
|
-
const dir = path.posix.dirname(value);
|
|
328
|
-
if (!dir || dir === '.') continue;
|
|
329
|
-
if (!(dir in directories)) directories[dir] = categorizeDirectory(dir);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
|
|
453
|
+
|
|
454
|
+
async function scanProject(cwd) {
|
|
455
|
+
const workspaceKind = await detectWorkspaceKind(cwd);
|
|
456
|
+
if (workspaceKind !== 'project') {
|
|
457
|
+
return {
|
|
458
|
+
workspaceKind,
|
|
459
|
+
projectMap: null,
|
|
460
|
+
fileIndex: null,
|
|
461
|
+
ignoreRules: []
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const { gitignoreRules, llmignoreRules, combinedRules } = await readProjectIgnoreRules(cwd);
|
|
466
|
+
const allFiles = await walkFiles(cwd, cwd, [], combinedRules);
|
|
467
|
+
const relativeFiles = allFiles.map((filePath) => rel(cwd, filePath));
|
|
468
|
+
const sourceFiles = allFiles.filter((filePath) => SOURCE_EXTENSIONS.has(path.extname(filePath).toLowerCase()));
|
|
469
|
+
|
|
470
|
+
const packageJson = await safeReadJson(path.join(cwd, 'package.json'), null);
|
|
471
|
+
const tsconfigExists = Boolean(await safeStat(path.join(cwd, 'tsconfig.json')));
|
|
472
|
+
const sourceRoots = clipList(relativeFiles.filter((value) => /^(src|app|apps)\b/.test(value)).map((value) => value.split('/')[0]), 12);
|
|
473
|
+
const testRoots = clipList(relativeFiles.filter((value) => /^(tests|test|__tests__)\b/.test(value)).map((value) => value.split('/')[0]), 12);
|
|
474
|
+
const entryCandidates = clipList(
|
|
475
|
+
relativeFiles.filter((value) => /(^|\/)(main|index|server|app)\.(js|jsx|mjs|cjs|ts|tsx|py|go|rs|java|cs|php|rb)$/.test(value)),
|
|
476
|
+
16
|
|
477
|
+
);
|
|
478
|
+
const languages = clipList(sourceFiles.map((filePath) => LANGUAGE_BY_EXT[path.extname(filePath).toLowerCase()] || '').filter(Boolean), 16);
|
|
479
|
+
const importantFiles = clipList(
|
|
480
|
+
relativeFiles.filter((value) => ['package.json', 'tsconfig.json', 'pyproject.toml', 'go.mod', 'Cargo.toml', 'composer.json', 'Gemfile'].includes(value)),
|
|
481
|
+
16
|
|
482
|
+
);
|
|
483
|
+
const packageManagers = clipList([
|
|
484
|
+
packageJson ? 'npm' : '',
|
|
485
|
+
relativeFiles.includes('bun.lockb') ? 'bun' : '',
|
|
486
|
+
relativeFiles.includes('pnpm-lock.yaml') ? 'pnpm' : '',
|
|
487
|
+
relativeFiles.includes('yarn.lock') ? 'yarn' : ''
|
|
488
|
+
].filter(Boolean));
|
|
489
|
+
const frameworkHints = clipList([
|
|
490
|
+
packageJson?.dependencies?.react || packageJson?.devDependencies?.react ? 'react' : '',
|
|
491
|
+
packageJson?.dependencies?.express ? 'express' : '',
|
|
492
|
+
packageJson?.dependencies?.vue ? 'vue' : '',
|
|
493
|
+
packageJson?.dependencies?.next ? 'next' : '',
|
|
494
|
+
tsconfigExists ? 'typescript' : ''
|
|
495
|
+
].filter(Boolean));
|
|
496
|
+
|
|
497
|
+
const directories = {};
|
|
498
|
+
for (const value of relativeFiles) {
|
|
499
|
+
const dir = path.posix.dirname(value);
|
|
500
|
+
if (!dir || dir === '.') continue;
|
|
501
|
+
if (!(dir in directories)) directories[dir] = categorizeDirectory(dir);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
let files = [];
|
|
333
505
|
for (const filePath of sourceFiles) {
|
|
334
506
|
const content = await fs.readFile(filePath, 'utf8');
|
|
335
507
|
const stat = await fs.stat(filePath);
|
|
336
508
|
files.push(buildFileEntry(rel(cwd, filePath), content, stat));
|
|
337
509
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
await
|
|
384
|
-
await writeJson(
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
const
|
|
410
|
-
const
|
|
411
|
-
const
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
const
|
|
415
|
-
const
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
510
|
+
files = enrichSymbolGraph(files);
|
|
511
|
+
|
|
512
|
+
return {
|
|
513
|
+
workspaceKind,
|
|
514
|
+
projectMap: {
|
|
515
|
+
projectRoot: cwd,
|
|
516
|
+
workspaceKind,
|
|
517
|
+
languages,
|
|
518
|
+
packageManagers,
|
|
519
|
+
importantFiles,
|
|
520
|
+
sourceRoots,
|
|
521
|
+
testRoots,
|
|
522
|
+
entryCandidates,
|
|
523
|
+
frameworkHints,
|
|
524
|
+
directories,
|
|
525
|
+
gitignoreEnabled: gitignoreRules.length > 0,
|
|
526
|
+
llmignoreEnabled: llmignoreRules.length > 0,
|
|
527
|
+
updatedAt: new Date().toISOString()
|
|
528
|
+
},
|
|
529
|
+
fileIndex: {
|
|
530
|
+
updatedAt: new Date().toISOString(),
|
|
531
|
+
files
|
|
532
|
+
},
|
|
533
|
+
ignoreRules: combinedRules
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
export async function initializeProjectIndex(cwd = process.cwd()) {
|
|
538
|
+
const targetRoot = (await findNearestProjectRoot(cwd, cwd)) || path.resolve(cwd);
|
|
539
|
+
const cacheKey = targetRoot;
|
|
540
|
+
if (initCache.has(cacheKey)) return initCache.get(cacheKey);
|
|
541
|
+
const promise = (async () => {
|
|
542
|
+
const workspaceDir = getProjectWorkspaceDir(cwd);
|
|
543
|
+
await fs.mkdir(workspaceDir, { recursive: true });
|
|
544
|
+
const { workspaceKind, projectMap, fileIndex } = await scanProject(targetRoot);
|
|
545
|
+
if (workspaceKind !== 'project' || !projectMap || !fileIndex) {
|
|
546
|
+
return {
|
|
547
|
+
workspaceKind,
|
|
548
|
+
projectRoot: null,
|
|
549
|
+
projectMap: null,
|
|
550
|
+
fileIndex: null,
|
|
551
|
+
summary: '',
|
|
552
|
+
skipped: true
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
await fs.mkdir(getProjectIndexDir(targetRoot), { recursive: true });
|
|
556
|
+
await writeJson(getProjectMapPath(targetRoot), projectMap);
|
|
557
|
+
await writeJson(getFileIndexPath(targetRoot), fileIndex);
|
|
558
|
+
return {
|
|
559
|
+
workspaceKind,
|
|
560
|
+
projectRoot: targetRoot,
|
|
561
|
+
projectMap,
|
|
562
|
+
fileIndex,
|
|
563
|
+
summary: `initialized ${path.basename(targetRoot) || '.'}/.codemini (${Array.isArray(fileIndex?.files) ? fileIndex.files.length : 0} files)`
|
|
564
|
+
};
|
|
565
|
+
})();
|
|
566
|
+
initCache.set(cacheKey, promise);
|
|
567
|
+
try {
|
|
568
|
+
return await promise;
|
|
569
|
+
} catch (error) {
|
|
570
|
+
initCache.delete(cacheKey);
|
|
571
|
+
throw error;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
export async function refreshIndexedFile(cwd = process.cwd(), relativePath = '') {
|
|
576
|
+
if (!relativePath) return null;
|
|
577
|
+
const workspaceDir = getProjectWorkspaceDir(cwd);
|
|
578
|
+
await fs.mkdir(workspaceDir, { recursive: true });
|
|
579
|
+
const projectRoot = await findProjectRootFromFile(cwd, relativePath);
|
|
580
|
+
if (!projectRoot) return null;
|
|
581
|
+
const fileIndexPath = getFileIndexPath(projectRoot);
|
|
582
|
+
const { combinedRules } = await readProjectIgnoreRules(projectRoot);
|
|
583
|
+
const absolutePath = path.join(cwd, relativePath);
|
|
584
|
+
const stat = await safeStat(absolutePath);
|
|
585
|
+
let action = 'updated';
|
|
586
|
+
const projectRelativePath = path.relative(projectRoot, absolutePath).replace(/\\/g, '/');
|
|
587
|
+
const current = await safeReadJson(fileIndexPath, { updatedAt: '', files: [] });
|
|
588
|
+
const files = Array.isArray(current.files) ? [...current.files] : [];
|
|
589
|
+
const index = files.findIndex((entry) => entry.file === projectRelativePath);
|
|
590
|
+
|
|
591
|
+
if (shouldIgnorePath(projectRelativePath, Boolean(stat?.isDirectory?.()), combinedRules)) {
|
|
592
|
+
if (index >= 0) files.splice(index, 1);
|
|
593
|
+
action = 'removed';
|
|
594
|
+
} else if (!stat || !stat.isFile()) {
|
|
595
|
+
if (index >= 0) files.splice(index, 1);
|
|
596
|
+
action = 'removed';
|
|
597
|
+
} else {
|
|
598
|
+
const ext = path.extname(relativePath).toLowerCase();
|
|
599
|
+
if (!SOURCE_EXTENSIONS.has(ext)) {
|
|
600
|
+
if (index >= 0) files.splice(index, 1);
|
|
601
|
+
action = 'removed';
|
|
602
|
+
} else {
|
|
603
|
+
const content = await fs.readFile(absolutePath, 'utf8');
|
|
604
|
+
const nextEntry = buildFileEntry(projectRelativePath, content, stat);
|
|
605
|
+
if (index >= 0) {
|
|
606
|
+
files[index] = nextEntry;
|
|
607
|
+
} else {
|
|
608
|
+
files.push(nextEntry);
|
|
609
|
+
action = 'added';
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const enrichedFiles = enrichSymbolGraph(files);
|
|
441
615
|
await writeJson(fileIndexPath, {
|
|
442
616
|
updatedAt: new Date().toISOString(),
|
|
443
|
-
files:
|
|
617
|
+
files: enrichedFiles.sort((left, right) => left.file.localeCompare(right.file))
|
|
444
618
|
});
|
|
445
|
-
|
|
446
|
-
return {
|
|
447
|
-
path: projectRelativePath,
|
|
448
|
-
projectRoot,
|
|
449
|
-
action,
|
|
450
|
-
summary: `${action} ${path.basename(projectRoot) || '.'}/.codemini for ${projectRelativePath}`
|
|
451
|
-
};
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
export async function buildProjectContextSnippet(cwd = process.cwd(), userText = '') {
|
|
455
|
-
const indexedRoot = await findNearestIndexedProjectRoot(cwd, cwd);
|
|
456
|
-
if (!indexedRoot) return '';
|
|
457
|
-
|
|
458
|
-
const projectMap = await safeReadJson(getProjectMapPath(indexedRoot), null);
|
|
459
|
-
const fileIndex = await safeReadJson(getFileIndexPath(indexedRoot), null);
|
|
460
|
-
if (!projectMap || !Array.isArray(fileIndex?.files)) return '';
|
|
461
|
-
|
|
462
|
-
const lines = [
|
|
463
|
-
'Project Context:',
|
|
464
|
-
`- project_root: ${indexedRoot}`,
|
|
465
|
-
`- languages: ${(projectMap.languages || []).slice(0, 6).join(', ') || 'unknown'}`,
|
|
466
|
-
`- source_roots: ${(projectMap.sourceRoots || []).slice(0, 6).join(', ') || 'none'}`,
|
|
467
|
-
`- test_roots: ${(projectMap.testRoots || []).slice(0, 6).join(', ') || 'none'}`,
|
|
468
|
-
`- entry_candidates: ${(projectMap.entryCandidates || []).slice(0, 6).join(', ') || 'none'}`,
|
|
469
|
-
`- framework_hints: ${(projectMap.frameworkHints || []).slice(0, 6).join(', ') || 'none'}`
|
|
470
|
-
];
|
|
471
|
-
|
|
472
|
-
const tokens = tokenizeQuery(userText);
|
|
473
|
-
const scored = [];
|
|
474
|
-
for (const entry of fileIndex.files) {
|
|
475
|
-
let score = 0;
|
|
476
|
-
const fileText = String(entry.file || '').toLowerCase();
|
|
477
|
-
for (const token of tokens) {
|
|
478
|
-
if (fileText.includes(token)) score += 5;
|
|
479
|
-
if ((entry.exports || []).some((value) => String(value).toLowerCase() === token)) score += 4;
|
|
480
|
-
if ((entry.functions || []).some((value) => String(value).toLowerCase() === token)) score += 4;
|
|
481
|
-
if ((entry.classes || []).some((value) => String(value).toLowerCase() === token)) score += 4;
|
|
482
|
-
if ((entry.imports || []).some((value) => String(value).toLowerCase().includes(token))) score += 1;
|
|
483
|
-
}
|
|
484
|
-
if (score > 0) scored.push({ entry, score });
|
|
485
|
-
}
|
|
486
|
-
scored.sort((left, right) => right.score - left.score || String(left.entry.file).localeCompare(String(right.entry.file)));
|
|
487
|
-
const selected = scored.slice(0, PROJECT_CONTEXT_MAX_FILES).map((item) => item.entry);
|
|
488
|
-
if (selected.length > 0) {
|
|
489
|
-
lines.push('- relevant_files:');
|
|
619
|
+
|
|
620
|
+
return {
|
|
621
|
+
path: projectRelativePath,
|
|
622
|
+
projectRoot,
|
|
623
|
+
action,
|
|
624
|
+
summary: `${action} ${path.basename(projectRoot) || '.'}/.codemini for ${projectRelativePath}`
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
export async function buildProjectContextSnippet(cwd = process.cwd(), userText = '') {
|
|
629
|
+
const indexedRoot = await findNearestIndexedProjectRoot(cwd, cwd);
|
|
630
|
+
if (!indexedRoot) return '';
|
|
631
|
+
|
|
632
|
+
const projectMap = await safeReadJson(getProjectMapPath(indexedRoot), null);
|
|
633
|
+
const fileIndex = await safeReadJson(getFileIndexPath(indexedRoot), null);
|
|
634
|
+
if (!projectMap || !Array.isArray(fileIndex?.files)) return '';
|
|
635
|
+
|
|
636
|
+
const lines = [
|
|
637
|
+
'Project Context:',
|
|
638
|
+
`- project_root: ${indexedRoot}`,
|
|
639
|
+
`- languages: ${(projectMap.languages || []).slice(0, 6).join(', ') || 'unknown'}`,
|
|
640
|
+
`- source_roots: ${(projectMap.sourceRoots || []).slice(0, 6).join(', ') || 'none'}`,
|
|
641
|
+
`- test_roots: ${(projectMap.testRoots || []).slice(0, 6).join(', ') || 'none'}`,
|
|
642
|
+
`- entry_candidates: ${(projectMap.entryCandidates || []).slice(0, 6).join(', ') || 'none'}`,
|
|
643
|
+
`- framework_hints: ${(projectMap.frameworkHints || []).slice(0, 6).join(', ') || 'none'}`
|
|
644
|
+
];
|
|
645
|
+
|
|
646
|
+
const tokens = tokenizeQuery(userText);
|
|
647
|
+
const scored = [];
|
|
648
|
+
for (const entry of fileIndex.files) {
|
|
649
|
+
let score = 0;
|
|
650
|
+
const fileText = String(entry.file || '').toLowerCase();
|
|
651
|
+
for (const token of tokens) {
|
|
652
|
+
if (fileText.includes(token)) score += 5;
|
|
653
|
+
if ((entry.exports || []).some((value) => String(value).toLowerCase() === token)) score += 4;
|
|
654
|
+
if ((entry.functions || []).some((value) => String(value).toLowerCase() === token)) score += 4;
|
|
655
|
+
if ((entry.classes || []).some((value) => String(value).toLowerCase() === token)) score += 4;
|
|
656
|
+
if ((entry.imports || []).some((value) => String(value).toLowerCase().includes(token))) score += 1;
|
|
657
|
+
}
|
|
658
|
+
if (score > 0) scored.push({ entry, score });
|
|
659
|
+
}
|
|
660
|
+
scored.sort((left, right) => right.score - left.score || String(left.entry.file).localeCompare(String(right.entry.file)));
|
|
661
|
+
const selected = scored.slice(0, PROJECT_CONTEXT_MAX_FILES).map((item) => item.entry);
|
|
662
|
+
if (selected.length > 0) {
|
|
663
|
+
lines.push('- relevant_files:');
|
|
490
664
|
for (const entry of selected) {
|
|
665
|
+
const symbolText = (entry.symbols || [])
|
|
666
|
+
.slice(0, 4)
|
|
667
|
+
.map((symbol) => `${symbol.name}@${symbol.range?.start_line || '?'}`)
|
|
668
|
+
.join(', ');
|
|
491
669
|
lines.push(
|
|
492
|
-
` - ${entry.file} ::
|
|
670
|
+
` - ${entry.file} :: symbols=[${symbolText}] exports=[${(entry.exports || []).slice(0, 4).join(', ')}] classes=[${(entry.classes || []).slice(0, 4).join(', ')}]`
|
|
493
671
|
);
|
|
494
672
|
}
|
|
495
673
|
}
|
|
496
|
-
|
|
497
|
-
const snippet = trimMultiline(lines.join('\n'));
|
|
498
|
-
return snippet;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
export async function queryProjectIndex(cwd = process.cwd(), args = {}) {
|
|
502
|
-
const indexedRoot = await findNearestIndexedProjectRoot(cwd, cwd);
|
|
503
|
-
if (!indexedRoot) {
|
|
504
|
-
return {
|
|
505
|
-
query: String(args?.query || '').trim(),
|
|
506
|
-
project_root: '',
|
|
507
|
-
project_map: null,
|
|
508
|
-
matches: []
|
|
509
|
-
};
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
const projectMap = await safeReadJson(getProjectMapPath(indexedRoot), null);
|
|
513
|
-
const fileIndex = await safeReadJson(getFileIndexPath(indexedRoot), null);
|
|
514
|
-
const query = String(args?.query || '').trim();
|
|
515
|
-
const pathPrefix = normalizeRelativePath(args?.path || args?.path_prefix || '');
|
|
516
|
-
const languageFilter = String(args?.language || '').trim().toLowerCase();
|
|
517
|
-
const maxResults = Math.max(1, Math.min(20, Number(args?.max_results || 8) || 8));
|
|
518
|
-
const files = Array.isArray(fileIndex?.files) ? fileIndex.files : [];
|
|
519
|
-
const tokens = tokenizeQuery(query);
|
|
520
|
-
|
|
674
|
+
|
|
675
|
+
const snippet = trimMultiline(lines.join('\n'));
|
|
676
|
+
return snippet;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
export async function queryProjectIndex(cwd = process.cwd(), args = {}) {
|
|
680
|
+
const indexedRoot = await findNearestIndexedProjectRoot(cwd, cwd);
|
|
681
|
+
if (!indexedRoot) {
|
|
682
|
+
return {
|
|
683
|
+
query: String(args?.query || '').trim(),
|
|
684
|
+
project_root: '',
|
|
685
|
+
project_map: null,
|
|
686
|
+
matches: []
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
const projectMap = await safeReadJson(getProjectMapPath(indexedRoot), null);
|
|
691
|
+
const fileIndex = await safeReadJson(getFileIndexPath(indexedRoot), null);
|
|
692
|
+
const query = String(args?.query || '').trim();
|
|
693
|
+
const pathPrefix = normalizeRelativePath(args?.path || args?.path_prefix || '');
|
|
694
|
+
const languageFilter = String(args?.language || '').trim().toLowerCase();
|
|
695
|
+
const maxResults = Math.max(1, Math.min(20, Number(args?.max_results || 8) || 8));
|
|
696
|
+
const files = Array.isArray(fileIndex?.files) ? fileIndex.files : [];
|
|
697
|
+
const tokens = tokenizeQuery(query);
|
|
698
|
+
|
|
521
699
|
const matches = [];
|
|
522
700
|
for (const entry of files) {
|
|
523
|
-
const relativePath = String(entry?.file || '');
|
|
524
|
-
if (!relativePath) continue;
|
|
525
|
-
if (pathPrefix && !relativePath.startsWith(pathPrefix)) continue;
|
|
526
|
-
if (languageFilter && String(entry?.language || '').toLowerCase() !== languageFilter) continue;
|
|
527
|
-
|
|
528
|
-
let score = 0;
|
|
529
|
-
const reasons = [];
|
|
530
|
-
const fileText = relativePath.toLowerCase();
|
|
701
|
+
const relativePath = String(entry?.file || '');
|
|
702
|
+
if (!relativePath) continue;
|
|
703
|
+
if (pathPrefix && !relativePath.startsWith(pathPrefix)) continue;
|
|
704
|
+
if (languageFilter && String(entry?.language || '').toLowerCase() !== languageFilter) continue;
|
|
705
|
+
|
|
706
|
+
let score = 0;
|
|
707
|
+
const reasons = [];
|
|
708
|
+
const fileText = relativePath.toLowerCase();
|
|
709
|
+
const symbolMatches = [];
|
|
531
710
|
for (const token of tokens) {
|
|
532
711
|
if (!token) continue;
|
|
533
712
|
if (fileText.includes(token)) {
|
|
534
713
|
score += 5;
|
|
535
|
-
reasons.push(`path:${token}`);
|
|
536
|
-
}
|
|
537
|
-
if ((entry.exports || []).some((value) => String(value).toLowerCase() === token)) {
|
|
538
|
-
score += 4;
|
|
539
|
-
reasons.push(`export:${token}`);
|
|
540
|
-
}
|
|
541
|
-
if ((entry.functions || []).some((value) => String(value).toLowerCase().includes(token))) {
|
|
542
|
-
score += 4;
|
|
543
|
-
reasons.push(`function:${token}`);
|
|
544
|
-
}
|
|
545
|
-
if ((entry.classes || []).some((value) => String(value).toLowerCase().includes(token))) {
|
|
546
|
-
score += 4;
|
|
547
|
-
reasons.push(`class:${token}`);
|
|
548
|
-
}
|
|
714
|
+
reasons.push(`path:${token}`);
|
|
715
|
+
}
|
|
716
|
+
if ((entry.exports || []).some((value) => String(value).toLowerCase() === token)) {
|
|
717
|
+
score += 4;
|
|
718
|
+
reasons.push(`export:${token}`);
|
|
719
|
+
}
|
|
720
|
+
if ((entry.functions || []).some((value) => String(value).toLowerCase().includes(token))) {
|
|
721
|
+
score += 4;
|
|
722
|
+
reasons.push(`function:${token}`);
|
|
723
|
+
}
|
|
724
|
+
if ((entry.classes || []).some((value) => String(value).toLowerCase().includes(token))) {
|
|
725
|
+
score += 4;
|
|
726
|
+
reasons.push(`class:${token}`);
|
|
727
|
+
}
|
|
549
728
|
if ((entry.imports || []).some((value) => String(value).toLowerCase().includes(token))) {
|
|
550
729
|
score += 2;
|
|
551
730
|
reasons.push(`import:${token}`);
|
|
552
731
|
}
|
|
732
|
+
for (const symbol of entry.symbols || []) {
|
|
733
|
+
const nameText = String(symbol.name || '').toLowerCase();
|
|
734
|
+
const idText = String(symbol.symbol_id || '').toLowerCase();
|
|
735
|
+
if (nameText.includes(token) || idText.includes(token)) {
|
|
736
|
+
score += 6;
|
|
737
|
+
reasons.push(`symbol:${token}`);
|
|
738
|
+
symbolMatches.push(symbol);
|
|
739
|
+
} else if ((symbol.calls || []).some((value) => String(value).toLowerCase().includes(token))) {
|
|
740
|
+
score += 3;
|
|
741
|
+
reasons.push(`calls:${token}`);
|
|
742
|
+
symbolMatches.push(symbol);
|
|
743
|
+
} else if ((symbol.called_by || []).some((value) => String(value).toLowerCase().includes(token))) {
|
|
744
|
+
score += 3;
|
|
745
|
+
reasons.push(`called_by:${token}`);
|
|
746
|
+
symbolMatches.push(symbol);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
553
749
|
}
|
|
554
|
-
|
|
555
|
-
if (!query) {
|
|
556
|
-
if ((projectMap?.entryCandidates || []).includes(relativePath)) score += 3;
|
|
557
|
-
if ((projectMap?.importantFiles || []).includes(relativePath)) score += 2;
|
|
558
|
-
if (String(relativePath).startsWith('src/')) score += 1;
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
if (score <= 0 && query) continue;
|
|
562
|
-
matches.push({
|
|
563
|
-
file: relativePath,
|
|
564
|
-
language: entry.language || 'text',
|
|
565
|
-
score,
|
|
566
|
-
reasons: clipList(reasons, 8),
|
|
750
|
+
|
|
751
|
+
if (!query) {
|
|
752
|
+
if ((projectMap?.entryCandidates || []).includes(relativePath)) score += 3;
|
|
753
|
+
if ((projectMap?.importantFiles || []).includes(relativePath)) score += 2;
|
|
754
|
+
if (String(relativePath).startsWith('src/')) score += 1;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
if (score <= 0 && query) continue;
|
|
758
|
+
matches.push({
|
|
759
|
+
file: relativePath,
|
|
760
|
+
language: entry.language || 'text',
|
|
761
|
+
score,
|
|
762
|
+
reasons: clipList(reasons, 8),
|
|
567
763
|
exports: clipList(entry.exports || [], 6),
|
|
568
764
|
functions: clipList(entry.functions || [], 6),
|
|
569
765
|
classes: clipList(entry.classes || [], 6),
|
|
570
|
-
imports: clipList(entry.imports || [], 6)
|
|
766
|
+
imports: clipList(entry.imports || [], 6),
|
|
767
|
+
symbols: clipList((symbolMatches.length > 0 ? symbolMatches : entry.symbols || []).map((symbol) => ({
|
|
768
|
+
symbol_id: symbol.symbol_id,
|
|
769
|
+
name: symbol.name,
|
|
770
|
+
type: symbol.type,
|
|
771
|
+
range: symbol.range,
|
|
772
|
+
signature: symbol.signature,
|
|
773
|
+
calls: clipList(symbol.calls || [], 8),
|
|
774
|
+
called_by: clipList(symbol.called_by || [], 8),
|
|
775
|
+
imports: clipList(symbol.imports || [], 6),
|
|
776
|
+
writes: clipList(symbol.writes || [], 6),
|
|
777
|
+
emits: clipList(symbol.emits || [], 6)
|
|
778
|
+
})), 6)
|
|
571
779
|
});
|
|
572
780
|
}
|
|
573
|
-
|
|
574
|
-
matches.sort((left, right) => right.score - left.score || String(left.file).localeCompare(String(right.file)));
|
|
575
|
-
|
|
576
|
-
return {
|
|
577
|
-
query,
|
|
578
|
-
project_root: indexedRoot,
|
|
579
|
-
project_map: projectMap
|
|
580
|
-
? {
|
|
581
|
-
workspace_kind: projectMap.workspaceKind || 'project',
|
|
582
|
-
languages: clipList(projectMap.languages || [], 8),
|
|
583
|
-
package_managers: clipList(projectMap.packageManagers || [], 8),
|
|
584
|
-
important_files: clipList(projectMap.importantFiles || [], 8),
|
|
585
|
-
source_roots: clipList(projectMap.sourceRoots || [], 8),
|
|
586
|
-
test_roots: clipList(projectMap.testRoots || [], 8),
|
|
587
|
-
entry_candidates: clipList(projectMap.entryCandidates || [], 8),
|
|
588
|
-
framework_hints: clipList(projectMap.frameworkHints || [], 8),
|
|
589
|
-
gitignore_enabled: Boolean(projectMap.gitignoreEnabled),
|
|
590
|
-
llmignore_enabled: Boolean(projectMap.llmignoreEnabled)
|
|
591
|
-
}
|
|
592
|
-
: null,
|
|
593
|
-
matches: matches.slice(0, maxResults)
|
|
594
|
-
};
|
|
595
|
-
}
|
|
781
|
+
|
|
782
|
+
matches.sort((left, right) => right.score - left.score || String(left.file).localeCompare(String(right.file)));
|
|
783
|
+
|
|
784
|
+
return {
|
|
785
|
+
query,
|
|
786
|
+
project_root: indexedRoot,
|
|
787
|
+
project_map: projectMap
|
|
788
|
+
? {
|
|
789
|
+
workspace_kind: projectMap.workspaceKind || 'project',
|
|
790
|
+
languages: clipList(projectMap.languages || [], 8),
|
|
791
|
+
package_managers: clipList(projectMap.packageManagers || [], 8),
|
|
792
|
+
important_files: clipList(projectMap.importantFiles || [], 8),
|
|
793
|
+
source_roots: clipList(projectMap.sourceRoots || [], 8),
|
|
794
|
+
test_roots: clipList(projectMap.testRoots || [], 8),
|
|
795
|
+
entry_candidates: clipList(projectMap.entryCandidates || [], 8),
|
|
796
|
+
framework_hints: clipList(projectMap.frameworkHints || [], 8),
|
|
797
|
+
gitignore_enabled: Boolean(projectMap.gitignoreEnabled),
|
|
798
|
+
llmignore_enabled: Boolean(projectMap.llmignoreEnabled)
|
|
799
|
+
}
|
|
800
|
+
: null,
|
|
801
|
+
matches: matches.slice(0, maxResults)
|
|
802
|
+
};
|
|
803
|
+
}
|