reasonix 0.16.0 → 0.17.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 +55 -16
- package/README.zh-CN.md +50 -11
- package/dashboard/app.css +148 -1
- package/dashboard/app.js +226 -0
- package/dist/cli/index.js +538 -281
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +12 -0
- package/dist/index.js +90 -64
- package/dist/index.js.map +1 -1
- package/package.json +113 -110
package/dist/cli/index.js
CHANGED
|
@@ -21,6 +21,127 @@ import { Command } from "commander";
|
|
|
21
21
|
import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
22
22
|
import { homedir } from "os";
|
|
23
23
|
import { dirname, join } from "path";
|
|
24
|
+
|
|
25
|
+
// src/index/config.ts
|
|
26
|
+
import picomatch from "picomatch";
|
|
27
|
+
var DEFAULT_INDEX_EXCLUDES = {
|
|
28
|
+
dirs: [
|
|
29
|
+
"node_modules",
|
|
30
|
+
".git",
|
|
31
|
+
".hg",
|
|
32
|
+
".svn",
|
|
33
|
+
"dist",
|
|
34
|
+
"build",
|
|
35
|
+
"out",
|
|
36
|
+
".next",
|
|
37
|
+
".nuxt",
|
|
38
|
+
"target",
|
|
39
|
+
".venv",
|
|
40
|
+
"venv",
|
|
41
|
+
"__pycache__",
|
|
42
|
+
".pytest_cache",
|
|
43
|
+
".mypy_cache",
|
|
44
|
+
".cache",
|
|
45
|
+
"coverage",
|
|
46
|
+
".turbo",
|
|
47
|
+
".vercel",
|
|
48
|
+
".reasonix"
|
|
49
|
+
],
|
|
50
|
+
files: [
|
|
51
|
+
"package-lock.json",
|
|
52
|
+
"yarn.lock",
|
|
53
|
+
"pnpm-lock.yaml",
|
|
54
|
+
"Cargo.lock",
|
|
55
|
+
"poetry.lock",
|
|
56
|
+
"Pipfile.lock",
|
|
57
|
+
"go.sum",
|
|
58
|
+
".DS_Store"
|
|
59
|
+
],
|
|
60
|
+
exts: [
|
|
61
|
+
".png",
|
|
62
|
+
".jpg",
|
|
63
|
+
".jpeg",
|
|
64
|
+
".gif",
|
|
65
|
+
".webp",
|
|
66
|
+
".bmp",
|
|
67
|
+
".ico",
|
|
68
|
+
".tiff",
|
|
69
|
+
".woff",
|
|
70
|
+
".woff2",
|
|
71
|
+
".ttf",
|
|
72
|
+
".otf",
|
|
73
|
+
".eot",
|
|
74
|
+
".zip",
|
|
75
|
+
".tar",
|
|
76
|
+
".gz",
|
|
77
|
+
".bz2",
|
|
78
|
+
".xz",
|
|
79
|
+
".rar",
|
|
80
|
+
".7z",
|
|
81
|
+
".exe",
|
|
82
|
+
".dll",
|
|
83
|
+
".so",
|
|
84
|
+
".dylib",
|
|
85
|
+
".bin",
|
|
86
|
+
".class",
|
|
87
|
+
".jar",
|
|
88
|
+
".war",
|
|
89
|
+
".wasm",
|
|
90
|
+
".o",
|
|
91
|
+
".obj",
|
|
92
|
+
".lib",
|
|
93
|
+
".a",
|
|
94
|
+
".pyc",
|
|
95
|
+
".pyo",
|
|
96
|
+
".mp3",
|
|
97
|
+
".mp4",
|
|
98
|
+
".wav",
|
|
99
|
+
".ogg",
|
|
100
|
+
".webm",
|
|
101
|
+
".mov",
|
|
102
|
+
".avi",
|
|
103
|
+
".pdf",
|
|
104
|
+
".sqlite",
|
|
105
|
+
".db"
|
|
106
|
+
]
|
|
107
|
+
};
|
|
108
|
+
var DEFAULT_MAX_FILE_BYTES = 256 * 1024;
|
|
109
|
+
var DEFAULT_RESPECT_GITIGNORE = true;
|
|
110
|
+
function defaultIndexConfig() {
|
|
111
|
+
return {
|
|
112
|
+
excludeDirs: [...DEFAULT_INDEX_EXCLUDES.dirs],
|
|
113
|
+
excludeFiles: [...DEFAULT_INDEX_EXCLUDES.files],
|
|
114
|
+
excludeExts: [...DEFAULT_INDEX_EXCLUDES.exts],
|
|
115
|
+
excludePatterns: [],
|
|
116
|
+
respectGitignore: DEFAULT_RESPECT_GITIGNORE,
|
|
117
|
+
maxFileBytes: DEFAULT_MAX_FILE_BYTES
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function resolveIndexConfig(user) {
|
|
121
|
+
const d = defaultIndexConfig();
|
|
122
|
+
if (!user) return d;
|
|
123
|
+
return {
|
|
124
|
+
excludeDirs: Array.isArray(user.excludeDirs) ? [...user.excludeDirs] : d.excludeDirs,
|
|
125
|
+
excludeFiles: Array.isArray(user.excludeFiles) ? [...user.excludeFiles] : d.excludeFiles,
|
|
126
|
+
excludeExts: Array.isArray(user.excludeExts) ? user.excludeExts.map((e) => e.toLowerCase()) : d.excludeExts,
|
|
127
|
+
excludePatterns: Array.isArray(user.excludePatterns) ? [...user.excludePatterns] : [],
|
|
128
|
+
respectGitignore: typeof user.respectGitignore === "boolean" ? user.respectGitignore : d.respectGitignore,
|
|
129
|
+
maxFileBytes: typeof user.maxFileBytes === "number" && user.maxFileBytes > 0 ? user.maxFileBytes : d.maxFileBytes
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function compileFilters(cfg) {
|
|
133
|
+
const matcher = cfg.excludePatterns.length === 0 ? () => false : picomatch(cfg.excludePatterns, { dot: true });
|
|
134
|
+
return {
|
|
135
|
+
dirSet: new Set(cfg.excludeDirs),
|
|
136
|
+
fileSet: new Set(cfg.excludeFiles),
|
|
137
|
+
extSet: new Set(cfg.excludeExts.map((e) => e.toLowerCase())),
|
|
138
|
+
patternMatch: matcher,
|
|
139
|
+
respectGitignore: cfg.respectGitignore,
|
|
140
|
+
maxFileBytes: cfg.maxFileBytes
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// src/config.ts
|
|
24
145
|
function defaultConfigPath() {
|
|
25
146
|
return join(homedir(), ".reasonix", "config.json");
|
|
26
147
|
}
|
|
@@ -116,6 +237,12 @@ function saveReasoningEffort(effort2, path5 = defaultConfigPath()) {
|
|
|
116
237
|
cfg.reasoningEffort = effort2;
|
|
117
238
|
writeConfig(cfg, path5);
|
|
118
239
|
}
|
|
240
|
+
function loadIndexUserConfig(path5 = defaultConfigPath()) {
|
|
241
|
+
return readConfig(path5).index ?? {};
|
|
242
|
+
}
|
|
243
|
+
function loadIndexConfig(path5 = defaultConfigPath()) {
|
|
244
|
+
return resolveIndexConfig(readConfig(path5).index);
|
|
245
|
+
}
|
|
119
246
|
function markEditModeHintShown(path5 = defaultConfigPath()) {
|
|
120
247
|
const cfg = readConfig(path5);
|
|
121
248
|
if (cfg.editModeHintShown === true) return;
|
|
@@ -2795,7 +2922,7 @@ ${summary}`;
|
|
|
2795
2922
|
assistantMessage(content, toolCalls, producingModel, reasoningContent) {
|
|
2796
2923
|
const msg = { role: "assistant", content };
|
|
2797
2924
|
if (toolCalls.length > 0) msg.tool_calls = toolCalls;
|
|
2798
|
-
if (isThinkingModeModel(producingModel)) {
|
|
2925
|
+
if (isThinkingModeModel(producingModel) || reasoningContent && reasoningContent.length > 0) {
|
|
2799
2926
|
msg.reasoning_content = reasoningContent ?? "";
|
|
2800
2927
|
}
|
|
2801
2928
|
return msg;
|
|
@@ -3090,7 +3217,7 @@ function listFilesSync(root, opts = {}) {
|
|
|
3090
3217
|
}
|
|
3091
3218
|
function listFilesWithStatsSync(root, opts = {}) {
|
|
3092
3219
|
const maxResults = Math.max(1, opts.maxResults ?? 500);
|
|
3093
|
-
const
|
|
3220
|
+
const ignore2 = new Set(opts.ignoreDirs ?? DEFAULT_PICKER_IGNORE_DIRS);
|
|
3094
3221
|
const rootAbs = resolve(root);
|
|
3095
3222
|
const out = [];
|
|
3096
3223
|
const walk3 = (dirAbs, dirRel) => {
|
|
@@ -3106,7 +3233,7 @@ function listFilesWithStatsSync(root, opts = {}) {
|
|
|
3106
3233
|
if (out.length >= maxResults) return;
|
|
3107
3234
|
const relPath = dirRel ? `${dirRel}/${ent.name}` : ent.name;
|
|
3108
3235
|
if (ent.isDirectory()) {
|
|
3109
|
-
if (ent.name.startsWith(".") ||
|
|
3236
|
+
if (ent.name.startsWith(".") || ignore2.has(ent.name)) continue;
|
|
3110
3237
|
walk3(join5(dirAbs, ent.name), relPath);
|
|
3111
3238
|
} else if (ent.isFile()) {
|
|
3112
3239
|
let mtimeMs = 0;
|
|
@@ -3123,7 +3250,7 @@ function listFilesWithStatsSync(root, opts = {}) {
|
|
|
3123
3250
|
}
|
|
3124
3251
|
async function listFilesWithStatsAsync(root, opts = {}) {
|
|
3125
3252
|
const maxResults = Math.max(1, opts.maxResults ?? 500);
|
|
3126
|
-
const
|
|
3253
|
+
const ignore2 = new Set(opts.ignoreDirs ?? DEFAULT_PICKER_IGNORE_DIRS);
|
|
3127
3254
|
const rootAbs = resolve(root);
|
|
3128
3255
|
const out = [];
|
|
3129
3256
|
const walk3 = async (dirAbs, dirRel) => {
|
|
@@ -3139,7 +3266,7 @@ async function listFilesWithStatsAsync(root, opts = {}) {
|
|
|
3139
3266
|
for (const ent of entries) {
|
|
3140
3267
|
if (out.length >= maxResults) break;
|
|
3141
3268
|
if (ent.isDirectory()) {
|
|
3142
|
-
if (ent.name.startsWith(".") ||
|
|
3269
|
+
if (ent.name.startsWith(".") || ignore2.has(ent.name)) continue;
|
|
3143
3270
|
if (fileEnts.length > 0) {
|
|
3144
3271
|
await statBatch(fileEnts, dirAbs, dirRel, out, maxResults);
|
|
3145
3272
|
fileEnts.length = 0;
|
|
@@ -3420,69 +3547,8 @@ var DEFAULT_MAX_LIST_BYTES = 256 * 1024;
|
|
|
3420
3547
|
var DEFAULT_AUTO_PREVIEW_LINES = 200;
|
|
3421
3548
|
var AUTO_PREVIEW_HEAD_LINES = 80;
|
|
3422
3549
|
var AUTO_PREVIEW_TAIL_LINES = 40;
|
|
3423
|
-
var SKIP_DIR_NAMES =
|
|
3424
|
-
|
|
3425
|
-
".git",
|
|
3426
|
-
".hg",
|
|
3427
|
-
".svn",
|
|
3428
|
-
"dist",
|
|
3429
|
-
"build",
|
|
3430
|
-
"out",
|
|
3431
|
-
".next",
|
|
3432
|
-
".nuxt",
|
|
3433
|
-
"target",
|
|
3434
|
-
// Rust / Java
|
|
3435
|
-
".venv",
|
|
3436
|
-
"venv",
|
|
3437
|
-
"__pycache__",
|
|
3438
|
-
".pytest_cache",
|
|
3439
|
-
".mypy_cache",
|
|
3440
|
-
".cache",
|
|
3441
|
-
"coverage"
|
|
3442
|
-
]);
|
|
3443
|
-
var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
3444
|
-
".png",
|
|
3445
|
-
".jpg",
|
|
3446
|
-
".jpeg",
|
|
3447
|
-
".gif",
|
|
3448
|
-
".bmp",
|
|
3449
|
-
".ico",
|
|
3450
|
-
".webp",
|
|
3451
|
-
".tiff",
|
|
3452
|
-
".pdf",
|
|
3453
|
-
".zip",
|
|
3454
|
-
".tar",
|
|
3455
|
-
".gz",
|
|
3456
|
-
".bz2",
|
|
3457
|
-
".xz",
|
|
3458
|
-
".7z",
|
|
3459
|
-
".rar",
|
|
3460
|
-
".exe",
|
|
3461
|
-
".dll",
|
|
3462
|
-
".so",
|
|
3463
|
-
".dylib",
|
|
3464
|
-
".bin",
|
|
3465
|
-
".class",
|
|
3466
|
-
".jar",
|
|
3467
|
-
".war",
|
|
3468
|
-
".o",
|
|
3469
|
-
".obj",
|
|
3470
|
-
".lib",
|
|
3471
|
-
".a",
|
|
3472
|
-
".woff",
|
|
3473
|
-
".woff2",
|
|
3474
|
-
".ttf",
|
|
3475
|
-
".otf",
|
|
3476
|
-
".eot",
|
|
3477
|
-
".mp3",
|
|
3478
|
-
".mp4",
|
|
3479
|
-
".mov",
|
|
3480
|
-
".avi",
|
|
3481
|
-
".webm",
|
|
3482
|
-
".wasm",
|
|
3483
|
-
".pyc",
|
|
3484
|
-
".pyo"
|
|
3485
|
-
]);
|
|
3550
|
+
var SKIP_DIR_NAMES = new Set(DEFAULT_INDEX_EXCLUDES.dirs);
|
|
3551
|
+
var BINARY_EXTENSIONS = new Set(DEFAULT_INDEX_EXCLUDES.exts);
|
|
3486
3552
|
function isLikelyBinaryByName(name) {
|
|
3487
3553
|
const dot2 = name.lastIndexOf(".");
|
|
3488
3554
|
if (dot2 < 0) return false;
|
|
@@ -8389,7 +8455,193 @@ async function handleHooks(method, rest, body, ctx) {
|
|
|
8389
8455
|
return { status: 405, body: { error: `method ${method} not supported on this path` } };
|
|
8390
8456
|
}
|
|
8391
8457
|
|
|
8392
|
-
// src/
|
|
8458
|
+
// src/index/semantic/chunker.ts
|
|
8459
|
+
import { promises as fs2 } from "fs";
|
|
8460
|
+
import path from "path";
|
|
8461
|
+
import ignore from "ignore";
|
|
8462
|
+
var DEFAULT_MAX_CHUNK_CHARS = 4e3;
|
|
8463
|
+
function chunkText(text2, filePath, windowLines, overlap, maxChunkChars = DEFAULT_MAX_CHUNK_CHARS) {
|
|
8464
|
+
const lines = text2.split(/\r?\n/);
|
|
8465
|
+
if (lines.length === 0 || lines.length === 1 && lines[0] === "") return [];
|
|
8466
|
+
const stride = Math.max(1, windowLines - overlap);
|
|
8467
|
+
const chunks = [];
|
|
8468
|
+
for (let start = 0; start < lines.length; start += stride) {
|
|
8469
|
+
const end = Math.min(lines.length, start + windowLines);
|
|
8470
|
+
const slice2 = lines.slice(start, end).join("\n").trim();
|
|
8471
|
+
if (slice2.length === 0) {
|
|
8472
|
+
if (end >= lines.length) break;
|
|
8473
|
+
continue;
|
|
8474
|
+
}
|
|
8475
|
+
const window = {
|
|
8476
|
+
path: filePath,
|
|
8477
|
+
startLine: start + 1,
|
|
8478
|
+
endLine: end,
|
|
8479
|
+
text: slice2
|
|
8480
|
+
};
|
|
8481
|
+
for (const sub of safeSplit(window, maxChunkChars)) chunks.push(sub);
|
|
8482
|
+
if (end >= lines.length) break;
|
|
8483
|
+
}
|
|
8484
|
+
return chunks;
|
|
8485
|
+
}
|
|
8486
|
+
function safeSplit(chunk, maxChars) {
|
|
8487
|
+
if (chunk.text.length <= maxChars) return [chunk];
|
|
8488
|
+
const lines = chunk.text.split("\n");
|
|
8489
|
+
const out = [];
|
|
8490
|
+
let bufLines = [];
|
|
8491
|
+
let bufStart = chunk.startLine;
|
|
8492
|
+
let bufLen = 0;
|
|
8493
|
+
const flush = (untilLineNo) => {
|
|
8494
|
+
if (bufLines.length === 0) return;
|
|
8495
|
+
out.push({
|
|
8496
|
+
path: chunk.path,
|
|
8497
|
+
startLine: bufStart,
|
|
8498
|
+
endLine: untilLineNo,
|
|
8499
|
+
text: bufLines.join("\n")
|
|
8500
|
+
});
|
|
8501
|
+
bufLines = [];
|
|
8502
|
+
bufLen = 0;
|
|
8503
|
+
};
|
|
8504
|
+
for (let i = 0; i < lines.length; i++) {
|
|
8505
|
+
const line = lines[i] ?? "";
|
|
8506
|
+
const lineLen = line.length + 1;
|
|
8507
|
+
if (lineLen > maxChars) {
|
|
8508
|
+
flush(chunk.startLine + i - 1);
|
|
8509
|
+
out.push({
|
|
8510
|
+
path: chunk.path,
|
|
8511
|
+
startLine: chunk.startLine + i,
|
|
8512
|
+
endLine: chunk.startLine + i,
|
|
8513
|
+
text: line.slice(0, maxChars)
|
|
8514
|
+
});
|
|
8515
|
+
bufStart = chunk.startLine + i + 1;
|
|
8516
|
+
continue;
|
|
8517
|
+
}
|
|
8518
|
+
if (bufLen + lineLen > maxChars && bufLines.length > 0) {
|
|
8519
|
+
flush(chunk.startLine + i - 1);
|
|
8520
|
+
bufStart = chunk.startLine + i;
|
|
8521
|
+
}
|
|
8522
|
+
bufLines.push(line);
|
|
8523
|
+
bufLen += lineLen;
|
|
8524
|
+
}
|
|
8525
|
+
flush(chunk.endLine);
|
|
8526
|
+
return out;
|
|
8527
|
+
}
|
|
8528
|
+
async function loadGitignoreAt(dirAbs) {
|
|
8529
|
+
try {
|
|
8530
|
+
const text2 = await fs2.readFile(path.join(dirAbs, ".gitignore"), "utf8");
|
|
8531
|
+
return ignore().add(text2);
|
|
8532
|
+
} catch {
|
|
8533
|
+
return null;
|
|
8534
|
+
}
|
|
8535
|
+
}
|
|
8536
|
+
function toForwardRel(root, abs) {
|
|
8537
|
+
return path.relative(root, abs).split(path.sep).join("/");
|
|
8538
|
+
}
|
|
8539
|
+
function ignoredByLayers(layers, abs, isDir) {
|
|
8540
|
+
for (const layer of layers) {
|
|
8541
|
+
const rel = path.relative(layer.dirAbs, abs).split(path.sep).join("/");
|
|
8542
|
+
if (!rel || rel.startsWith("..")) continue;
|
|
8543
|
+
if (layer.ig.ignores(isDir ? `${rel}/` : rel)) return true;
|
|
8544
|
+
}
|
|
8545
|
+
return false;
|
|
8546
|
+
}
|
|
8547
|
+
async function* walkChunks(root, opts = {}) {
|
|
8548
|
+
const windowLines = opts.windowLines ?? 60;
|
|
8549
|
+
const overlap = Math.min(opts.overlap ?? 12, Math.max(0, windowLines - 1));
|
|
8550
|
+
const maxChunkChars = opts.maxChunkChars ?? DEFAULT_MAX_CHUNK_CHARS;
|
|
8551
|
+
const filters = compileFilters(opts.config ?? defaultIndexConfig());
|
|
8552
|
+
const onSkip = opts.onSkip ?? (() => {
|
|
8553
|
+
});
|
|
8554
|
+
const initial = [];
|
|
8555
|
+
if (filters.respectGitignore) {
|
|
8556
|
+
const rootIg = await loadGitignoreAt(root);
|
|
8557
|
+
if (rootIg) initial.push({ dirAbs: root, ig: rootIg });
|
|
8558
|
+
}
|
|
8559
|
+
const stack = [{ dir: root, layers: initial }];
|
|
8560
|
+
while (stack.length > 0) {
|
|
8561
|
+
const frame = stack.pop();
|
|
8562
|
+
if (!frame) break;
|
|
8563
|
+
const { dir, layers } = frame;
|
|
8564
|
+
let entries;
|
|
8565
|
+
try {
|
|
8566
|
+
entries = await fs2.readdir(dir, { withFileTypes: true });
|
|
8567
|
+
} catch {
|
|
8568
|
+
continue;
|
|
8569
|
+
}
|
|
8570
|
+
for (const entry of entries) {
|
|
8571
|
+
const name = entry.name;
|
|
8572
|
+
const abs = path.join(dir, name);
|
|
8573
|
+
const rel = toForwardRel(root, abs);
|
|
8574
|
+
if (entry.isDirectory()) {
|
|
8575
|
+
if (filters.dirSet.has(name)) {
|
|
8576
|
+
onSkip(rel, "defaultDir");
|
|
8577
|
+
continue;
|
|
8578
|
+
}
|
|
8579
|
+
if (filters.respectGitignore && ignoredByLayers(layers, abs, true)) {
|
|
8580
|
+
onSkip(rel, "gitignore");
|
|
8581
|
+
continue;
|
|
8582
|
+
}
|
|
8583
|
+
if (filters.patternMatch(`${rel}/`) || filters.patternMatch(rel)) {
|
|
8584
|
+
onSkip(rel, "pattern");
|
|
8585
|
+
continue;
|
|
8586
|
+
}
|
|
8587
|
+
const childLayers = filters.respectGitignore ? await extendLayers(layers, abs) : layers;
|
|
8588
|
+
stack.push({ dir: abs, layers: childLayers });
|
|
8589
|
+
continue;
|
|
8590
|
+
}
|
|
8591
|
+
if (!entry.isFile()) continue;
|
|
8592
|
+
if (filters.fileSet.has(name)) {
|
|
8593
|
+
onSkip(rel, "defaultFile");
|
|
8594
|
+
continue;
|
|
8595
|
+
}
|
|
8596
|
+
const ext = path.extname(name).toLowerCase();
|
|
8597
|
+
if (filters.extSet.has(ext)) {
|
|
8598
|
+
onSkip(rel, "binaryExt");
|
|
8599
|
+
continue;
|
|
8600
|
+
}
|
|
8601
|
+
if (filters.respectGitignore && ignoredByLayers(layers, abs, false)) {
|
|
8602
|
+
onSkip(rel, "gitignore");
|
|
8603
|
+
continue;
|
|
8604
|
+
}
|
|
8605
|
+
if (filters.patternMatch(rel)) {
|
|
8606
|
+
onSkip(rel, "pattern");
|
|
8607
|
+
continue;
|
|
8608
|
+
}
|
|
8609
|
+
let stat2;
|
|
8610
|
+
try {
|
|
8611
|
+
stat2 = await fs2.stat(abs);
|
|
8612
|
+
} catch {
|
|
8613
|
+
onSkip(rel, "readError");
|
|
8614
|
+
continue;
|
|
8615
|
+
}
|
|
8616
|
+
if (stat2.size > filters.maxFileBytes) {
|
|
8617
|
+
onSkip(rel, "tooLarge");
|
|
8618
|
+
continue;
|
|
8619
|
+
}
|
|
8620
|
+
let text2;
|
|
8621
|
+
try {
|
|
8622
|
+
text2 = await fs2.readFile(abs, "utf8");
|
|
8623
|
+
} catch {
|
|
8624
|
+
onSkip(rel, "readError");
|
|
8625
|
+
continue;
|
|
8626
|
+
}
|
|
8627
|
+
if (text2.indexOf("\0") !== -1) {
|
|
8628
|
+
onSkip(rel, "binaryContent");
|
|
8629
|
+
continue;
|
|
8630
|
+
}
|
|
8631
|
+
for (const chunk of chunkText(text2, rel, windowLines, overlap, maxChunkChars)) {
|
|
8632
|
+
yield chunk;
|
|
8633
|
+
}
|
|
8634
|
+
}
|
|
8635
|
+
}
|
|
8636
|
+
}
|
|
8637
|
+
async function extendLayers(layers, dirAbs) {
|
|
8638
|
+
const ig = await loadGitignoreAt(dirAbs);
|
|
8639
|
+
return ig ? [...layers, { dirAbs, ig }] : layers;
|
|
8640
|
+
}
|
|
8641
|
+
|
|
8642
|
+
// src/server/api/index-config.ts
|
|
8643
|
+
var PREVIEW_INCLUDED_CAP = 50;
|
|
8644
|
+
var PREVIEW_PER_REASON_CAP = 10;
|
|
8393
8645
|
function parseBody4(raw) {
|
|
8394
8646
|
if (!raw) return {};
|
|
8395
8647
|
try {
|
|
@@ -8399,6 +8651,165 @@ function parseBody4(raw) {
|
|
|
8399
8651
|
return {};
|
|
8400
8652
|
}
|
|
8401
8653
|
}
|
|
8654
|
+
function isStringArray(v) {
|
|
8655
|
+
return Array.isArray(v) && v.every((x) => typeof x === "string");
|
|
8656
|
+
}
|
|
8657
|
+
async function handleIndexConfig(method, rest, body, ctx) {
|
|
8658
|
+
if (rest[0] === "preview" && method === "POST") {
|
|
8659
|
+
return await handlePreview(body, ctx);
|
|
8660
|
+
}
|
|
8661
|
+
if (method === "GET") {
|
|
8662
|
+
const user = loadIndexUserConfig(ctx.configPath);
|
|
8663
|
+
const resolved = resolveIndexConfig(user);
|
|
8664
|
+
return {
|
|
8665
|
+
status: 200,
|
|
8666
|
+
body: {
|
|
8667
|
+
user,
|
|
8668
|
+
resolved,
|
|
8669
|
+
defaults: {
|
|
8670
|
+
excludeDirs: [...DEFAULT_INDEX_EXCLUDES.dirs],
|
|
8671
|
+
excludeFiles: [...DEFAULT_INDEX_EXCLUDES.files],
|
|
8672
|
+
excludeExts: [...DEFAULT_INDEX_EXCLUDES.exts],
|
|
8673
|
+
excludePatterns: [],
|
|
8674
|
+
respectGitignore: DEFAULT_RESPECT_GITIGNORE,
|
|
8675
|
+
maxFileBytes: DEFAULT_MAX_FILE_BYTES
|
|
8676
|
+
}
|
|
8677
|
+
}
|
|
8678
|
+
};
|
|
8679
|
+
}
|
|
8680
|
+
if (method === "POST") {
|
|
8681
|
+
const fields = parseBody4(body);
|
|
8682
|
+
const next = {};
|
|
8683
|
+
const changed = [];
|
|
8684
|
+
if (fields.excludeDirs !== void 0) {
|
|
8685
|
+
if (!isStringArray(fields.excludeDirs)) {
|
|
8686
|
+
return { status: 400, body: { error: "excludeDirs must be string[]" } };
|
|
8687
|
+
}
|
|
8688
|
+
next.excludeDirs = fields.excludeDirs;
|
|
8689
|
+
changed.push("excludeDirs");
|
|
8690
|
+
}
|
|
8691
|
+
if (fields.excludeFiles !== void 0) {
|
|
8692
|
+
if (!isStringArray(fields.excludeFiles)) {
|
|
8693
|
+
return { status: 400, body: { error: "excludeFiles must be string[]" } };
|
|
8694
|
+
}
|
|
8695
|
+
next.excludeFiles = fields.excludeFiles;
|
|
8696
|
+
changed.push("excludeFiles");
|
|
8697
|
+
}
|
|
8698
|
+
if (fields.excludeExts !== void 0) {
|
|
8699
|
+
if (!isStringArray(fields.excludeExts)) {
|
|
8700
|
+
return { status: 400, body: { error: "excludeExts must be string[]" } };
|
|
8701
|
+
}
|
|
8702
|
+
next.excludeExts = fields.excludeExts;
|
|
8703
|
+
changed.push("excludeExts");
|
|
8704
|
+
}
|
|
8705
|
+
if (fields.excludePatterns !== void 0) {
|
|
8706
|
+
if (!isStringArray(fields.excludePatterns)) {
|
|
8707
|
+
return { status: 400, body: { error: "excludePatterns must be string[]" } };
|
|
8708
|
+
}
|
|
8709
|
+
next.excludePatterns = fields.excludePatterns;
|
|
8710
|
+
changed.push("excludePatterns");
|
|
8711
|
+
}
|
|
8712
|
+
if (fields.respectGitignore !== void 0) {
|
|
8713
|
+
if (typeof fields.respectGitignore !== "boolean") {
|
|
8714
|
+
return { status: 400, body: { error: "respectGitignore must be boolean" } };
|
|
8715
|
+
}
|
|
8716
|
+
next.respectGitignore = fields.respectGitignore;
|
|
8717
|
+
changed.push("respectGitignore");
|
|
8718
|
+
}
|
|
8719
|
+
if (fields.maxFileBytes !== void 0) {
|
|
8720
|
+
if (typeof fields.maxFileBytes !== "number" || fields.maxFileBytes <= 0) {
|
|
8721
|
+
return { status: 400, body: { error: "maxFileBytes must be a positive number" } };
|
|
8722
|
+
}
|
|
8723
|
+
next.maxFileBytes = fields.maxFileBytes;
|
|
8724
|
+
changed.push("maxFileBytes");
|
|
8725
|
+
}
|
|
8726
|
+
const cfg = readConfig(ctx.configPath);
|
|
8727
|
+
cfg.index = { ...cfg.index ?? {}, ...next };
|
|
8728
|
+
writeConfig(cfg, ctx.configPath);
|
|
8729
|
+
if (changed.length > 0) {
|
|
8730
|
+
ctx.audit?.({ ts: Date.now(), action: "set-index-config", payload: { fields: changed } });
|
|
8731
|
+
}
|
|
8732
|
+
return { status: 200, body: { changed, resolved: resolveIndexConfig(cfg.index) } };
|
|
8733
|
+
}
|
|
8734
|
+
return { status: 405, body: { error: "GET or POST only" } };
|
|
8735
|
+
}
|
|
8736
|
+
async function handlePreview(body, ctx) {
|
|
8737
|
+
const root = ctx.getCurrentCwd?.();
|
|
8738
|
+
if (!root) {
|
|
8739
|
+
return {
|
|
8740
|
+
status: 400,
|
|
8741
|
+
body: { error: "preview requires a code-mode session (no project root attached)" }
|
|
8742
|
+
};
|
|
8743
|
+
}
|
|
8744
|
+
const fields = parseBody4(body);
|
|
8745
|
+
const draft = {};
|
|
8746
|
+
if (isStringArray(fields.excludeDirs)) draft.excludeDirs = fields.excludeDirs;
|
|
8747
|
+
if (isStringArray(fields.excludeFiles)) draft.excludeFiles = fields.excludeFiles;
|
|
8748
|
+
if (isStringArray(fields.excludeExts)) draft.excludeExts = fields.excludeExts;
|
|
8749
|
+
if (isStringArray(fields.excludePatterns)) draft.excludePatterns = fields.excludePatterns;
|
|
8750
|
+
if (typeof fields.respectGitignore === "boolean")
|
|
8751
|
+
draft.respectGitignore = fields.respectGitignore;
|
|
8752
|
+
if (typeof fields.maxFileBytes === "number" && fields.maxFileBytes > 0) {
|
|
8753
|
+
draft.maxFileBytes = fields.maxFileBytes;
|
|
8754
|
+
}
|
|
8755
|
+
const resolved = resolveIndexConfig(draft);
|
|
8756
|
+
const skipBuckets = {
|
|
8757
|
+
defaultDir: 0,
|
|
8758
|
+
defaultFile: 0,
|
|
8759
|
+
binaryExt: 0,
|
|
8760
|
+
binaryContent: 0,
|
|
8761
|
+
tooLarge: 0,
|
|
8762
|
+
gitignore: 0,
|
|
8763
|
+
pattern: 0,
|
|
8764
|
+
readError: 0
|
|
8765
|
+
};
|
|
8766
|
+
const skipSamples = {
|
|
8767
|
+
defaultDir: [],
|
|
8768
|
+
defaultFile: [],
|
|
8769
|
+
binaryExt: [],
|
|
8770
|
+
binaryContent: [],
|
|
8771
|
+
tooLarge: [],
|
|
8772
|
+
gitignore: [],
|
|
8773
|
+
pattern: [],
|
|
8774
|
+
readError: []
|
|
8775
|
+
};
|
|
8776
|
+
const includedFiles = /* @__PURE__ */ new Set();
|
|
8777
|
+
const sampleIncluded = [];
|
|
8778
|
+
for await (const chunk of walkChunks(root, {
|
|
8779
|
+
config: resolved,
|
|
8780
|
+
onSkip: (rel, reason) => {
|
|
8781
|
+
skipBuckets[reason]++;
|
|
8782
|
+
const bucket = skipSamples[reason];
|
|
8783
|
+
if (bucket.length < PREVIEW_PER_REASON_CAP) bucket.push(rel);
|
|
8784
|
+
}
|
|
8785
|
+
})) {
|
|
8786
|
+
if (!includedFiles.has(chunk.path)) {
|
|
8787
|
+
includedFiles.add(chunk.path);
|
|
8788
|
+
if (sampleIncluded.length < PREVIEW_INCLUDED_CAP) sampleIncluded.push(chunk.path);
|
|
8789
|
+
}
|
|
8790
|
+
}
|
|
8791
|
+
return {
|
|
8792
|
+
status: 200,
|
|
8793
|
+
body: {
|
|
8794
|
+
filesIncluded: includedFiles.size,
|
|
8795
|
+
sampleIncluded,
|
|
8796
|
+
skipBuckets,
|
|
8797
|
+
skipSamples,
|
|
8798
|
+
resolved
|
|
8799
|
+
}
|
|
8800
|
+
};
|
|
8801
|
+
}
|
|
8802
|
+
|
|
8803
|
+
// src/server/api/mcp.ts
|
|
8804
|
+
function parseBody5(raw) {
|
|
8805
|
+
if (!raw) return {};
|
|
8806
|
+
try {
|
|
8807
|
+
const parsed = JSON.parse(raw);
|
|
8808
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
8809
|
+
} catch {
|
|
8810
|
+
return {};
|
|
8811
|
+
}
|
|
8812
|
+
}
|
|
8402
8813
|
async function handleMcp(method, rest, body, ctx) {
|
|
8403
8814
|
if (method === "GET" && rest.length === 0) {
|
|
8404
8815
|
const servers = (ctx.mcpServers ?? []).map((s) => ({
|
|
@@ -8427,7 +8838,7 @@ async function handleMcp(method, rest, body, ctx) {
|
|
|
8427
8838
|
return { status: 200, body: { specs: cfg.mcp ?? [] } };
|
|
8428
8839
|
}
|
|
8429
8840
|
if (method === "POST" && rest[0] === "specs") {
|
|
8430
|
-
const { spec } =
|
|
8841
|
+
const { spec } = parseBody5(body);
|
|
8431
8842
|
if (typeof spec !== "string" || !spec.trim()) {
|
|
8432
8843
|
return { status: 400, body: { error: "spec (non-empty string) required" } };
|
|
8433
8844
|
}
|
|
@@ -8442,7 +8853,7 @@ async function handleMcp(method, rest, body, ctx) {
|
|
|
8442
8853
|
return { status: 200, body: { added: true, requiresRestart: !ctx.reloadMcp } };
|
|
8443
8854
|
}
|
|
8444
8855
|
if (method === "DELETE" && rest[0] === "specs") {
|
|
8445
|
-
const { spec } =
|
|
8856
|
+
const { spec } = parseBody5(body);
|
|
8446
8857
|
if (typeof spec !== "string") {
|
|
8447
8858
|
return { status: 400, body: { error: "spec (string) required" } };
|
|
8448
8859
|
}
|
|
@@ -8475,7 +8886,7 @@ async function handleMcp(method, rest, body, ctx) {
|
|
|
8475
8886
|
body: { error: "MCP invocation requires an attached session." }
|
|
8476
8887
|
};
|
|
8477
8888
|
}
|
|
8478
|
-
const { server, tool: tool2, args } =
|
|
8889
|
+
const { server, tool: tool2, args } = parseBody5(body);
|
|
8479
8890
|
if (typeof server !== "string" || typeof tool2 !== "string") {
|
|
8480
8891
|
return { status: 400, body: { error: "server + tool (strings) required" } };
|
|
8481
8892
|
}
|
|
@@ -8515,7 +8926,7 @@ function globalMemoryDir() {
|
|
|
8515
8926
|
function projectMemoryDir(rootDir) {
|
|
8516
8927
|
return join14(homedir7(), ".reasonix", "memory", projectHash2(rootDir));
|
|
8517
8928
|
}
|
|
8518
|
-
function
|
|
8929
|
+
function parseBody6(raw) {
|
|
8519
8930
|
if (!raw) return {};
|
|
8520
8931
|
try {
|
|
8521
8932
|
const parsed = JSON.parse(raw);
|
|
@@ -8585,7 +8996,7 @@ async function handleMemory(method, rest, body, ctx) {
|
|
|
8585
8996
|
return { status: 400, body: { error: "bad scope or name" } };
|
|
8586
8997
|
}
|
|
8587
8998
|
if (method === "POST") {
|
|
8588
|
-
const { body: contents } =
|
|
8999
|
+
const { body: contents } = parseBody6(body);
|
|
8589
9000
|
if (typeof contents !== "string") {
|
|
8590
9001
|
return { status: 400, body: { error: "body (string) required" } };
|
|
8591
9002
|
}
|
|
@@ -8651,7 +9062,7 @@ async function handleMessages(method, _rest, _body, ctx) {
|
|
|
8651
9062
|
}
|
|
8652
9063
|
|
|
8653
9064
|
// src/server/api/modal.ts
|
|
8654
|
-
function
|
|
9065
|
+
function parseBody7(raw) {
|
|
8655
9066
|
if (!raw) return {};
|
|
8656
9067
|
try {
|
|
8657
9068
|
const parsed = JSON.parse(raw);
|
|
@@ -8668,7 +9079,7 @@ async function handleModal(method, rest, body, ctx) {
|
|
|
8668
9079
|
};
|
|
8669
9080
|
}
|
|
8670
9081
|
if (method === "POST" && rest[0] === "resolve") {
|
|
8671
|
-
const { kind, choice, text: text2 } =
|
|
9082
|
+
const { kind, choice, text: text2 } = parseBody7(body);
|
|
8672
9083
|
if (kind === "shell") {
|
|
8673
9084
|
if (!ctx.resolveShellConfirm) {
|
|
8674
9085
|
return { status: 503, body: { error: "shell modal resolution not wired" } };
|
|
@@ -8769,199 +9180,6 @@ async function handleModal(method, rest, body, ctx) {
|
|
|
8769
9180
|
import { promises as fs4 } from "fs";
|
|
8770
9181
|
import path3 from "path";
|
|
8771
9182
|
|
|
8772
|
-
// src/index/semantic/chunker.ts
|
|
8773
|
-
import { promises as fs2 } from "fs";
|
|
8774
|
-
import path from "path";
|
|
8775
|
-
var DEFAULT_MAX_CHUNK_CHARS = 4e3;
|
|
8776
|
-
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
8777
|
-
"node_modules",
|
|
8778
|
-
".git",
|
|
8779
|
-
"dist",
|
|
8780
|
-
"build",
|
|
8781
|
-
"out",
|
|
8782
|
-
".next",
|
|
8783
|
-
".nuxt",
|
|
8784
|
-
"target",
|
|
8785
|
-
".venv",
|
|
8786
|
-
"venv",
|
|
8787
|
-
"__pycache__",
|
|
8788
|
-
".pytest_cache",
|
|
8789
|
-
".mypy_cache",
|
|
8790
|
-
".cache",
|
|
8791
|
-
"coverage",
|
|
8792
|
-
".turbo",
|
|
8793
|
-
".vercel",
|
|
8794
|
-
".reasonix"
|
|
8795
|
-
]);
|
|
8796
|
-
var SKIP_FILES = /* @__PURE__ */ new Set([
|
|
8797
|
-
"package-lock.json",
|
|
8798
|
-
"yarn.lock",
|
|
8799
|
-
"pnpm-lock.yaml",
|
|
8800
|
-
"Cargo.lock",
|
|
8801
|
-
"poetry.lock",
|
|
8802
|
-
"Pipfile.lock",
|
|
8803
|
-
"go.sum",
|
|
8804
|
-
".DS_Store"
|
|
8805
|
-
]);
|
|
8806
|
-
var BINARY_EXTS = /* @__PURE__ */ new Set([
|
|
8807
|
-
// Images
|
|
8808
|
-
".png",
|
|
8809
|
-
".jpg",
|
|
8810
|
-
".jpeg",
|
|
8811
|
-
".gif",
|
|
8812
|
-
".webp",
|
|
8813
|
-
".bmp",
|
|
8814
|
-
".ico",
|
|
8815
|
-
".tiff",
|
|
8816
|
-
// Fonts
|
|
8817
|
-
".woff",
|
|
8818
|
-
".woff2",
|
|
8819
|
-
".ttf",
|
|
8820
|
-
".otf",
|
|
8821
|
-
".eot",
|
|
8822
|
-
// Archives / binaries
|
|
8823
|
-
".zip",
|
|
8824
|
-
".tar",
|
|
8825
|
-
".gz",
|
|
8826
|
-
".rar",
|
|
8827
|
-
".7z",
|
|
8828
|
-
".exe",
|
|
8829
|
-
".dll",
|
|
8830
|
-
".so",
|
|
8831
|
-
".dylib",
|
|
8832
|
-
".class",
|
|
8833
|
-
".jar",
|
|
8834
|
-
".wasm",
|
|
8835
|
-
".o",
|
|
8836
|
-
".a",
|
|
8837
|
-
// Media
|
|
8838
|
-
".mp3",
|
|
8839
|
-
".mp4",
|
|
8840
|
-
".wav",
|
|
8841
|
-
".ogg",
|
|
8842
|
-
".webm",
|
|
8843
|
-
".mov",
|
|
8844
|
-
// Other
|
|
8845
|
-
".pdf",
|
|
8846
|
-
".sqlite",
|
|
8847
|
-
".db"
|
|
8848
|
-
]);
|
|
8849
|
-
function chunkText(text2, filePath, windowLines, overlap, maxChunkChars = DEFAULT_MAX_CHUNK_CHARS) {
|
|
8850
|
-
const lines = text2.split(/\r?\n/);
|
|
8851
|
-
if (lines.length === 0 || lines.length === 1 && lines[0] === "") return [];
|
|
8852
|
-
const stride = Math.max(1, windowLines - overlap);
|
|
8853
|
-
const chunks = [];
|
|
8854
|
-
for (let start = 0; start < lines.length; start += stride) {
|
|
8855
|
-
const end = Math.min(lines.length, start + windowLines);
|
|
8856
|
-
const slice2 = lines.slice(start, end).join("\n").trim();
|
|
8857
|
-
if (slice2.length === 0) {
|
|
8858
|
-
if (end >= lines.length) break;
|
|
8859
|
-
continue;
|
|
8860
|
-
}
|
|
8861
|
-
const window = {
|
|
8862
|
-
path: filePath,
|
|
8863
|
-
startLine: start + 1,
|
|
8864
|
-
endLine: end,
|
|
8865
|
-
text: slice2
|
|
8866
|
-
};
|
|
8867
|
-
for (const sub of safeSplit(window, maxChunkChars)) chunks.push(sub);
|
|
8868
|
-
if (end >= lines.length) break;
|
|
8869
|
-
}
|
|
8870
|
-
return chunks;
|
|
8871
|
-
}
|
|
8872
|
-
function safeSplit(chunk, maxChars) {
|
|
8873
|
-
if (chunk.text.length <= maxChars) return [chunk];
|
|
8874
|
-
const lines = chunk.text.split("\n");
|
|
8875
|
-
const out = [];
|
|
8876
|
-
let bufLines = [];
|
|
8877
|
-
let bufStart = chunk.startLine;
|
|
8878
|
-
let bufLen = 0;
|
|
8879
|
-
const flush = (untilLineNo) => {
|
|
8880
|
-
if (bufLines.length === 0) return;
|
|
8881
|
-
out.push({
|
|
8882
|
-
path: chunk.path,
|
|
8883
|
-
startLine: bufStart,
|
|
8884
|
-
endLine: untilLineNo,
|
|
8885
|
-
text: bufLines.join("\n")
|
|
8886
|
-
});
|
|
8887
|
-
bufLines = [];
|
|
8888
|
-
bufLen = 0;
|
|
8889
|
-
};
|
|
8890
|
-
for (let i = 0; i < lines.length; i++) {
|
|
8891
|
-
const line = lines[i] ?? "";
|
|
8892
|
-
const lineLen = line.length + 1;
|
|
8893
|
-
if (lineLen > maxChars) {
|
|
8894
|
-
flush(chunk.startLine + i - 1);
|
|
8895
|
-
out.push({
|
|
8896
|
-
path: chunk.path,
|
|
8897
|
-
startLine: chunk.startLine + i,
|
|
8898
|
-
endLine: chunk.startLine + i,
|
|
8899
|
-
text: line.slice(0, maxChars)
|
|
8900
|
-
});
|
|
8901
|
-
bufStart = chunk.startLine + i + 1;
|
|
8902
|
-
continue;
|
|
8903
|
-
}
|
|
8904
|
-
if (bufLen + lineLen > maxChars && bufLines.length > 0) {
|
|
8905
|
-
flush(chunk.startLine + i - 1);
|
|
8906
|
-
bufStart = chunk.startLine + i;
|
|
8907
|
-
}
|
|
8908
|
-
bufLines.push(line);
|
|
8909
|
-
bufLen += lineLen;
|
|
8910
|
-
}
|
|
8911
|
-
flush(chunk.endLine);
|
|
8912
|
-
return out;
|
|
8913
|
-
}
|
|
8914
|
-
async function* walkChunks(root, opts = {}) {
|
|
8915
|
-
const windowLines = opts.windowLines ?? 60;
|
|
8916
|
-
const overlap = Math.min(opts.overlap ?? 12, Math.max(0, windowLines - 1));
|
|
8917
|
-
const maxFileBytes = opts.maxFileBytes ?? 256 * 1024;
|
|
8918
|
-
const maxChunkChars = opts.maxChunkChars ?? DEFAULT_MAX_CHUNK_CHARS;
|
|
8919
|
-
const stack = [root];
|
|
8920
|
-
while (stack.length > 0) {
|
|
8921
|
-
const dir = stack.pop();
|
|
8922
|
-
if (!dir) break;
|
|
8923
|
-
let entries;
|
|
8924
|
-
try {
|
|
8925
|
-
entries = await fs2.readdir(dir, { withFileTypes: true });
|
|
8926
|
-
} catch {
|
|
8927
|
-
continue;
|
|
8928
|
-
}
|
|
8929
|
-
for (const entry of entries) {
|
|
8930
|
-
const name = entry.name;
|
|
8931
|
-
if (entry.isDirectory()) {
|
|
8932
|
-
if (SKIP_DIRS.has(name) || name.startsWith(".")) {
|
|
8933
|
-
if (SKIP_DIRS.has(name) || name === ".git") continue;
|
|
8934
|
-
}
|
|
8935
|
-
stack.push(path.join(dir, name));
|
|
8936
|
-
continue;
|
|
8937
|
-
}
|
|
8938
|
-
if (!entry.isFile()) continue;
|
|
8939
|
-
if (SKIP_FILES.has(name)) continue;
|
|
8940
|
-
const ext = path.extname(name).toLowerCase();
|
|
8941
|
-
if (BINARY_EXTS.has(ext)) continue;
|
|
8942
|
-
const abs = path.join(dir, name);
|
|
8943
|
-
let stat2;
|
|
8944
|
-
try {
|
|
8945
|
-
stat2 = await fs2.stat(abs);
|
|
8946
|
-
} catch {
|
|
8947
|
-
continue;
|
|
8948
|
-
}
|
|
8949
|
-
if (stat2.size > maxFileBytes) continue;
|
|
8950
|
-
let text2;
|
|
8951
|
-
try {
|
|
8952
|
-
text2 = await fs2.readFile(abs, "utf8");
|
|
8953
|
-
} catch {
|
|
8954
|
-
continue;
|
|
8955
|
-
}
|
|
8956
|
-
if (text2.indexOf("\0") !== -1) continue;
|
|
8957
|
-
const rel = path.relative(root, abs).split(path.sep).join("/");
|
|
8958
|
-
for (const chunk of chunkText(text2, rel, windowLines, overlap, maxChunkChars)) {
|
|
8959
|
-
yield chunk;
|
|
8960
|
-
}
|
|
8961
|
-
}
|
|
8962
|
-
}
|
|
8963
|
-
}
|
|
8964
|
-
|
|
8965
9183
|
// src/index/semantic/embedding.ts
|
|
8966
9184
|
var DEFAULT_OLLAMA_URL = "http://localhost:11434";
|
|
8967
9185
|
var DEFAULT_EMBED_MODEL = "nomic-embed-text";
|
|
@@ -9278,6 +9496,18 @@ function deserializeEntry(line) {
|
|
|
9278
9496
|
|
|
9279
9497
|
// src/index/semantic/builder.ts
|
|
9280
9498
|
var INDEX_DIR_NAME = path3.join(".reasonix", "semantic");
|
|
9499
|
+
function emptyBuckets() {
|
|
9500
|
+
return {
|
|
9501
|
+
defaultDir: 0,
|
|
9502
|
+
defaultFile: 0,
|
|
9503
|
+
binaryExt: 0,
|
|
9504
|
+
binaryContent: 0,
|
|
9505
|
+
tooLarge: 0,
|
|
9506
|
+
gitignore: 0,
|
|
9507
|
+
pattern: 0,
|
|
9508
|
+
readError: 0
|
|
9509
|
+
};
|
|
9510
|
+
}
|
|
9281
9511
|
async function buildIndex(root, opts = {}) {
|
|
9282
9512
|
const t0 = Date.now();
|
|
9283
9513
|
const indexDir = path3.join(root, INDEX_DIR_NAME);
|
|
@@ -9295,10 +9525,14 @@ async function buildIndex(root, opts = {}) {
|
|
|
9295
9525
|
const fileChunks = /* @__PURE__ */ new Map();
|
|
9296
9526
|
let filesScanned = 0;
|
|
9297
9527
|
let filesSkipped = 0;
|
|
9528
|
+
const skipBuckets = emptyBuckets();
|
|
9298
9529
|
for await (const chunk of walkChunks(root, {
|
|
9299
9530
|
windowLines: opts.windowLines,
|
|
9300
9531
|
overlap: opts.overlap,
|
|
9301
|
-
|
|
9532
|
+
config: opts.indexConfig ?? defaultIndexConfig(),
|
|
9533
|
+
onSkip: (_p, reason) => {
|
|
9534
|
+
skipBuckets[reason]++;
|
|
9535
|
+
}
|
|
9302
9536
|
})) {
|
|
9303
9537
|
seenPaths.add(chunk.path);
|
|
9304
9538
|
let bucket = fileChunks.get(chunk.path);
|
|
@@ -9385,7 +9619,8 @@ async function buildIndex(root, opts = {}) {
|
|
|
9385
9619
|
filesSkipped,
|
|
9386
9620
|
filesChanged,
|
|
9387
9621
|
chunksTotal,
|
|
9388
|
-
chunksDone
|
|
9622
|
+
chunksDone,
|
|
9623
|
+
skipBuckets
|
|
9389
9624
|
});
|
|
9390
9625
|
return {
|
|
9391
9626
|
filesScanned,
|
|
@@ -9393,6 +9628,7 @@ async function buildIndex(root, opts = {}) {
|
|
|
9393
9628
|
chunksAdded,
|
|
9394
9629
|
chunksRemoved: removed,
|
|
9395
9630
|
chunksSkipped,
|
|
9631
|
+
skipBuckets,
|
|
9396
9632
|
durationMs: Date.now() - t0
|
|
9397
9633
|
};
|
|
9398
9634
|
}
|
|
@@ -9444,7 +9680,7 @@ async function handleOverview(method, _rest, _body, ctx) {
|
|
|
9444
9680
|
}
|
|
9445
9681
|
|
|
9446
9682
|
// src/server/api/permissions.ts
|
|
9447
|
-
function
|
|
9683
|
+
function parseBody8(raw) {
|
|
9448
9684
|
if (!raw) return {};
|
|
9449
9685
|
try {
|
|
9450
9686
|
const parsed = JSON.parse(raw);
|
|
@@ -9476,7 +9712,7 @@ async function handlePermissions(method, rest, body, ctx) {
|
|
|
9476
9712
|
};
|
|
9477
9713
|
}
|
|
9478
9714
|
if (method === "POST" && rest.length === 0) {
|
|
9479
|
-
const { prefix } =
|
|
9715
|
+
const { prefix } = parseBody8(body);
|
|
9480
9716
|
if (typeof prefix !== "string" || !prefix.trim()) {
|
|
9481
9717
|
return { status: 400, body: { error: "prefix (string) required" } };
|
|
9482
9718
|
}
|
|
@@ -9502,7 +9738,7 @@ async function handlePermissions(method, rest, body, ctx) {
|
|
|
9502
9738
|
return { status: 200, body: { added: true, prefix: trimmed } };
|
|
9503
9739
|
}
|
|
9504
9740
|
if (method === "DELETE" && rest.length === 0) {
|
|
9505
|
-
const { prefix } =
|
|
9741
|
+
const { prefix } = parseBody8(body);
|
|
9506
9742
|
if (typeof prefix !== "string" || !prefix.trim()) {
|
|
9507
9743
|
return { status: 400, body: { error: "prefix (string) required" } };
|
|
9508
9744
|
}
|
|
@@ -9526,7 +9762,7 @@ async function handlePermissions(method, rest, body, ctx) {
|
|
|
9526
9762
|
return { status: 200, body: { removed, prefix: trimmed } };
|
|
9527
9763
|
}
|
|
9528
9764
|
if (method === "POST" && rest[0] === "clear") {
|
|
9529
|
-
const { confirm: confirm2 } =
|
|
9765
|
+
const { confirm: confirm2 } = parseBody8(body);
|
|
9530
9766
|
if (confirm2 !== true) {
|
|
9531
9767
|
return {
|
|
9532
9768
|
status: 400,
|
|
@@ -9911,6 +10147,7 @@ async function runIndex(root, job, ctx) {
|
|
|
9911
10147
|
try {
|
|
9912
10148
|
const result = await buildIndex(root, {
|
|
9913
10149
|
rebuild: job.rebuild,
|
|
10150
|
+
indexConfig: loadIndexConfig(ctx.configPath),
|
|
9914
10151
|
onProgress: (p) => {
|
|
9915
10152
|
job.phase = p.phase;
|
|
9916
10153
|
if (p.filesScanned !== void 0) job.filesScanned = p.filesScanned;
|
|
@@ -10014,7 +10251,7 @@ async function handleSessions(method, rest, _body, _ctx) {
|
|
|
10014
10251
|
}
|
|
10015
10252
|
|
|
10016
10253
|
// src/server/api/settings.ts
|
|
10017
|
-
function
|
|
10254
|
+
function parseBody9(raw) {
|
|
10018
10255
|
if (!raw) return {};
|
|
10019
10256
|
try {
|
|
10020
10257
|
const parsed = JSON.parse(raw);
|
|
@@ -10052,7 +10289,7 @@ async function handleSettings(method, _rest, body, ctx) {
|
|
|
10052
10289
|
};
|
|
10053
10290
|
}
|
|
10054
10291
|
if (method === "POST") {
|
|
10055
|
-
const fields =
|
|
10292
|
+
const fields = parseBody9(body);
|
|
10056
10293
|
const cfg = readConfig(ctx.configPath);
|
|
10057
10294
|
const changed = [];
|
|
10058
10295
|
if (fields.apiKey !== void 0) {
|
|
@@ -10115,7 +10352,7 @@ import {
|
|
|
10115
10352
|
} from "fs";
|
|
10116
10353
|
import { homedir as homedir8 } from "os";
|
|
10117
10354
|
import { dirname as dirname15, join as join15 } from "path";
|
|
10118
|
-
function
|
|
10355
|
+
function parseBody10(raw) {
|
|
10119
10356
|
if (!raw) return {};
|
|
10120
10357
|
try {
|
|
10121
10358
|
const parsed = JSON.parse(raw);
|
|
@@ -10221,7 +10458,7 @@ async function handleSkills(method, rest, body, ctx) {
|
|
|
10221
10458
|
return { status: 200, body: { path: skillPath, body: readFileSync18(skillPath, "utf8") } };
|
|
10222
10459
|
}
|
|
10223
10460
|
if (method === "POST") {
|
|
10224
|
-
const { body: contents } =
|
|
10461
|
+
const { body: contents } = parseBody10(body);
|
|
10225
10462
|
if (typeof contents !== "string") {
|
|
10226
10463
|
return { status: 400, body: { error: "body (string) required" } };
|
|
10227
10464
|
}
|
|
@@ -10244,7 +10481,7 @@ async function handleSkills(method, rest, body, ctx) {
|
|
|
10244
10481
|
}
|
|
10245
10482
|
|
|
10246
10483
|
// src/server/api/submit.ts
|
|
10247
|
-
function
|
|
10484
|
+
function parseBody11(raw) {
|
|
10248
10485
|
if (!raw) return {};
|
|
10249
10486
|
try {
|
|
10250
10487
|
const parsed = JSON.parse(raw);
|
|
@@ -10265,7 +10502,7 @@ async function handleSubmit(method, _rest, body, ctx) {
|
|
|
10265
10502
|
}
|
|
10266
10503
|
};
|
|
10267
10504
|
}
|
|
10268
|
-
const { prompt } =
|
|
10505
|
+
const { prompt } = parseBody11(body);
|
|
10269
10506
|
if (typeof prompt !== "string" || !prompt.trim()) {
|
|
10270
10507
|
return { status: 400, body: { error: "prompt (non-empty string) required" } };
|
|
10271
10508
|
}
|
|
@@ -10428,6 +10665,8 @@ async function handleApi(pathTail, method, body, ctx) {
|
|
|
10428
10665
|
return await handleFile(method, rest, body, ctx);
|
|
10429
10666
|
case "semantic":
|
|
10430
10667
|
return await handleSemantic(method, rest, body, ctx);
|
|
10668
|
+
case "index-config":
|
|
10669
|
+
return await handleIndexConfig(method, rest, body, ctx);
|
|
10431
10670
|
default:
|
|
10432
10671
|
return { status: 404, body: { error: `no such endpoint: /${head}` } };
|
|
10433
10672
|
}
|
|
@@ -24064,6 +24303,7 @@ async function indexCommand(opts = {}) {
|
|
|
24064
24303
|
rebuild: opts.rebuild,
|
|
24065
24304
|
model: model2,
|
|
24066
24305
|
baseUrl: opts.ollamaUrl,
|
|
24306
|
+
indexConfig: loadIndexConfig(),
|
|
24067
24307
|
onProgress: (p) => writer.update(p)
|
|
24068
24308
|
});
|
|
24069
24309
|
} catch (err) {
|
|
@@ -24085,10 +24325,27 @@ async function indexCommand(opts = {}) {
|
|
|
24085
24325
|
seconds
|
|
24086
24326
|
})
|
|
24087
24327
|
);
|
|
24328
|
+
const breakdown = renderSkipBreakdown(result.skipBuckets);
|
|
24329
|
+
if (breakdown) process.stderr.write(`${breakdown}
|
|
24330
|
+
`);
|
|
24088
24331
|
if (result.filesChanged === 0 && !opts.rebuild) {
|
|
24089
24332
|
process.stderr.write(t("indexNothingToDo"));
|
|
24090
24333
|
}
|
|
24091
24334
|
}
|
|
24335
|
+
function renderSkipBreakdown(buckets) {
|
|
24336
|
+
const total = Object.values(buckets).reduce((a, b) => a + b, 0);
|
|
24337
|
+
if (total === 0) return "";
|
|
24338
|
+
const parts = [];
|
|
24339
|
+
if (buckets.gitignore) parts.push(`gitignore: ${buckets.gitignore}`);
|
|
24340
|
+
if (buckets.pattern) parts.push(`pattern: ${buckets.pattern}`);
|
|
24341
|
+
if (buckets.defaultDir) parts.push(`defaultDir: ${buckets.defaultDir}`);
|
|
24342
|
+
if (buckets.defaultFile) parts.push(`defaultFile: ${buckets.defaultFile}`);
|
|
24343
|
+
if (buckets.binaryExt) parts.push(`binaryExt: ${buckets.binaryExt}`);
|
|
24344
|
+
if (buckets.binaryContent) parts.push(`binaryContent: ${buckets.binaryContent}`);
|
|
24345
|
+
if (buckets.tooLarge) parts.push(`tooLarge: ${buckets.tooLarge}`);
|
|
24346
|
+
if (buckets.readError) parts.push(`readError: ${buckets.readError}`);
|
|
24347
|
+
return ` \xB7 skipped ${total} files (${parts.join(", ")})`;
|
|
24348
|
+
}
|
|
24092
24349
|
var SPINNER_FRAMES2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
24093
24350
|
var SPINNER_INTERVAL_MS = 120;
|
|
24094
24351
|
function makeProgressWriter(tty) {
|