dsh-codebase-chat 0.22.0 → 0.23.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 +185 -200
- package/dist/cli.js +114 -13
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +24 -2
- package/dist/index.js +62 -11
- package/dist/index.js.map +1 -1
- package/lib/index.js +41 -13
- package/package.json +9 -10
package/dist/index.d.ts
CHANGED
|
@@ -57,12 +57,16 @@ interface ContextOptions {
|
|
|
57
57
|
instruction?: string;
|
|
58
58
|
/** Enable local semantic embeddings for retrieval */
|
|
59
59
|
embed?: boolean;
|
|
60
|
+
/** Git ref (branch/tag/SHA) — scope retrieval to files changed vs this ref */
|
|
61
|
+
diff?: string;
|
|
60
62
|
}
|
|
61
63
|
interface ContextResult {
|
|
62
64
|
absProject: string;
|
|
63
65
|
context: string;
|
|
64
66
|
chunks: CodeChunk[];
|
|
65
67
|
tokenCount: number;
|
|
68
|
+
/** Project-relative files in scope when `diff` was requested */
|
|
69
|
+
diffFiles?: string[];
|
|
66
70
|
}
|
|
67
71
|
|
|
68
72
|
declare function buildContext(options: ContextOptions): Promise<ContextResult>;
|
|
@@ -166,7 +170,11 @@ interface HealthReport {
|
|
|
166
170
|
score: number;
|
|
167
171
|
grade: 'A' | 'B' | 'C' | 'D' | 'E';
|
|
168
172
|
}
|
|
169
|
-
|
|
173
|
+
interface AnalyzeOptions {
|
|
174
|
+
/** Restrict analysis to these project-relative paths (e.g. a diff scope). */
|
|
175
|
+
files?: Set<string>;
|
|
176
|
+
}
|
|
177
|
+
declare function analyzeProject(projectPath: string, opts?: AnalyzeOptions): Promise<HealthReport>;
|
|
170
178
|
declare function formatHealthReport(r: HealthReport, lang?: 'fr' | 'en'): string;
|
|
171
179
|
declare function formatHealthReportMd(r: HealthReport, lang?: 'fr' | 'en'): string;
|
|
172
180
|
|
|
@@ -199,4 +207,18 @@ declare function globToRegExp(glob: string): RegExp;
|
|
|
199
207
|
/** Match a project-relative path (posix separators) against a list of globs. */
|
|
200
208
|
declare function matchesAnyGlob(relPath: string, globs: string[] | undefined): boolean;
|
|
201
209
|
|
|
202
|
-
|
|
210
|
+
interface DiffScope {
|
|
211
|
+
/** Project-relative paths (forward slashes) changed vs the base ref. */
|
|
212
|
+
files: Set<string>;
|
|
213
|
+
/** False when the project is not a git repo or git is unavailable. */
|
|
214
|
+
ok: boolean;
|
|
215
|
+
/** Why the scope could not be computed (when ok === false). */
|
|
216
|
+
error?: string;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Files changed vs a git ref — committed diffs, staged/unstaged edits and
|
|
220
|
+
* untracked files. Used to scope retrieval and audits to "what changed".
|
|
221
|
+
*/
|
|
222
|
+
declare function getChangedFiles(absProject: string, base: string): Promise<DiffScope>;
|
|
223
|
+
|
|
224
|
+
export { CONFIG_FILE, type CloneGroup, type CodeChunk, type CodeIndex, type ConfigLang, type ContextOptions, type ContextResult, type Cycle, type DiffScope, type HealthReport, type Hotspot, type IndexedFile, type InvertedIndex, type ProjectConfig, type UnusedExport, analyzeProject, buildContext, buildIndex, chunkByTokens, clearConfigCache, cosineSimilarity, countTokens, disposeTreeSitter, embedIndex, ensureTreeSitterForExt, extractChunks, findProjectRoot, formatHealthReport, formatHealthReportMd, getChangedFiles, getEmbedding, getEmbeddings, getExtractor, getIndex, globToRegExp, initTreeSitter, loadIndex, loadProjectConfig, matchesAnyGlob, resolveProjectPath, saveIndex, scoreChunks, selectChunks, treeSitterReady, truncateToTokens };
|
package/dist/index.js
CHANGED
|
@@ -1168,6 +1168,33 @@ function selectChunks(scored, maxTokens, maxChunkTokens = Infinity) {
|
|
|
1168
1168
|
return { chunks: result, tokens: used };
|
|
1169
1169
|
}
|
|
1170
1170
|
|
|
1171
|
+
// src/diff.ts
|
|
1172
|
+
import { execFile } from "child_process";
|
|
1173
|
+
import { sep as sep3 } from "path";
|
|
1174
|
+
import { promisify } from "util";
|
|
1175
|
+
var run = promisify(execFile);
|
|
1176
|
+
async function git(absProject, args) {
|
|
1177
|
+
const { stdout } = await run("git", ["-C", absProject, ...args], { maxBuffer: 16 * 1024 * 1024 });
|
|
1178
|
+
return stdout;
|
|
1179
|
+
}
|
|
1180
|
+
function toRelList(output) {
|
|
1181
|
+
return output.split("\n").map((l) => l.trim()).filter(Boolean).map((l) => l.split(sep3).join("/"));
|
|
1182
|
+
}
|
|
1183
|
+
async function getChangedFiles(absProject, base) {
|
|
1184
|
+
try {
|
|
1185
|
+
const [tracked, untracked] = await Promise.all([
|
|
1186
|
+
// Working tree vs base — covers committed, staged and unstaged edits.
|
|
1187
|
+
git(absProject, ["diff", "--name-only", base, "--"]),
|
|
1188
|
+
git(absProject, ["ls-files", "--others", "--exclude-standard"])
|
|
1189
|
+
]);
|
|
1190
|
+
const files = /* @__PURE__ */ new Set([...toRelList(tracked), ...toRelList(untracked)]);
|
|
1191
|
+
return { files, ok: true };
|
|
1192
|
+
} catch (err) {
|
|
1193
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1194
|
+
return { files: /* @__PURE__ */ new Set(), ok: false, error: msg.split("\n")[0] };
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1171
1198
|
// src/context.ts
|
|
1172
1199
|
var DEFAULT_MAX_TOKENS = 6e4;
|
|
1173
1200
|
var HEAD_BUDGET_TOKENS = 800;
|
|
@@ -1178,14 +1205,18 @@ function getLabels(lang) {
|
|
|
1178
1205
|
tree: "File tree",
|
|
1179
1206
|
noConstraints: "No explicit constraints documented.",
|
|
1180
1207
|
constraints: "IDENTIFIED PRODUCT CONSTRAINTS",
|
|
1181
|
-
answerIn: "Answer in English."
|
|
1208
|
+
answerIn: "Answer in English.",
|
|
1209
|
+
diffScope: (base, n) => `Scope: ${n} file(s) changed vs ${base}`,
|
|
1210
|
+
diffUnavailable: (base) => `Scope: diff vs ${base} unavailable (not a git repo?) \u2014 full project`
|
|
1182
1211
|
} : {
|
|
1183
1212
|
project: "Projet",
|
|
1184
1213
|
focus: "Focus",
|
|
1185
1214
|
tree: "Arborescence",
|
|
1186
1215
|
noConstraints: "Aucune contrainte explicite document\xE9e.",
|
|
1187
1216
|
constraints: "CONTRAINTES PRODUIT IDENTIFI\xC9ES",
|
|
1188
|
-
answerIn: "R\xE9ponds obligatoirement en fran\xE7ais."
|
|
1217
|
+
answerIn: "R\xE9ponds obligatoirement en fran\xE7ais.",
|
|
1218
|
+
diffScope: (base, n) => `P\xE9rim\xE8tre : ${n} fichier(s) modifi\xE9(s) vs ${base}`,
|
|
1219
|
+
diffUnavailable: (base) => `P\xE9rim\xE8tre : diff vs ${base} indisponible (pas un repo git ?) \u2014 projet complet`
|
|
1189
1220
|
};
|
|
1190
1221
|
}
|
|
1191
1222
|
async function extractProductConstraints(absProject) {
|
|
@@ -1212,13 +1243,30 @@ function formatChunk(chunk) {
|
|
|
1212
1243
|
${chunk.content}`;
|
|
1213
1244
|
}
|
|
1214
1245
|
async function buildContext(options) {
|
|
1215
|
-
const { project, query, filePath, searchQuery, instruction, embed = false } = options;
|
|
1246
|
+
const { project, query, filePath, searchQuery, instruction, embed = false, diff } = options;
|
|
1216
1247
|
const absProject = await findProjectRoot(resolveProjectPath(project));
|
|
1217
1248
|
const cfg = await loadProjectConfig(absProject);
|
|
1218
1249
|
const maxTokens = options.maxTokens ?? cfg.maxTokens ?? DEFAULT_MAX_TOKENS;
|
|
1219
1250
|
const lang = options.lang ?? cfg.lang ?? "fr";
|
|
1220
1251
|
const labels = getLabels(lang);
|
|
1221
1252
|
const index = await getIndex(absProject);
|
|
1253
|
+
let scopedIndex = index;
|
|
1254
|
+
let diffFiles;
|
|
1255
|
+
let scopeLine = "";
|
|
1256
|
+
if (diff && !filePath) {
|
|
1257
|
+
const scope = await getChangedFiles(absProject, diff);
|
|
1258
|
+
if (scope.ok) {
|
|
1259
|
+
diffFiles = [...scope.files].filter((f) => index.files[f]);
|
|
1260
|
+
const files = {};
|
|
1261
|
+
for (const f of diffFiles) files[f] = index.files[f];
|
|
1262
|
+
scopedIndex = { ...index, files };
|
|
1263
|
+
scopeLine = `${labels.diffScope(diff, diffFiles.length)}
|
|
1264
|
+
`;
|
|
1265
|
+
} else {
|
|
1266
|
+
scopeLine = `${labels.diffUnavailable(diff)}
|
|
1267
|
+
`;
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1222
1270
|
if (embed) {
|
|
1223
1271
|
try {
|
|
1224
1272
|
await embedIndex(index);
|
|
@@ -1240,17 +1288,17 @@ async function buildContext(options) {
|
|
|
1240
1288
|
);
|
|
1241
1289
|
selectedChunks = chunks;
|
|
1242
1290
|
} else {
|
|
1243
|
-
const scored = await scoreChunks(
|
|
1291
|
+
const scored = await scoreChunks(scopedIndex, filePath, embed);
|
|
1244
1292
|
const { chunks } = selectChunks(scored, bodyLimit, maxChunkTokens);
|
|
1245
1293
|
if (chunks.length === 0) throw new Error(`File not found: ${filePath}`);
|
|
1246
1294
|
selectedChunks = chunks;
|
|
1247
1295
|
}
|
|
1248
1296
|
} else if (searchQuery) {
|
|
1249
|
-
const scored = await scoreChunks(
|
|
1297
|
+
const scored = await scoreChunks(scopedIndex, searchQuery, embed);
|
|
1250
1298
|
const { chunks } = selectChunks(scored, bodyLimit, maxChunkTokens);
|
|
1251
1299
|
selectedChunks = chunks;
|
|
1252
1300
|
} else {
|
|
1253
|
-
const scored = await scoreChunks(
|
|
1301
|
+
const scored = await scoreChunks(scopedIndex, query, embed);
|
|
1254
1302
|
const { chunks } = selectChunks(scored, bodyLimit, maxChunkTokens);
|
|
1255
1303
|
selectedChunks = chunks;
|
|
1256
1304
|
}
|
|
@@ -1260,7 +1308,7 @@ ${constraints.map((c) => `- ${c}`).join("\n")}` : `== ${labels.constraints} ==
|
|
|
1260
1308
|
${labels.noConstraints}`;
|
|
1261
1309
|
const head = `${labels.project} : ${absProject}
|
|
1262
1310
|
${labels.focus} : ${focus}
|
|
1263
|
-
${constraintsText}
|
|
1311
|
+
${scopeLine}${constraintsText}
|
|
1264
1312
|
|
|
1265
1313
|
== ${labels.tree} ==
|
|
1266
1314
|
${index.tree}
|
|
@@ -1284,12 +1332,13 @@ ${finalInstruction}`;
|
|
|
1284
1332
|
absProject,
|
|
1285
1333
|
context: prompt,
|
|
1286
1334
|
chunks: selectedChunks,
|
|
1287
|
-
tokenCount
|
|
1335
|
+
tokenCount,
|
|
1336
|
+
diffFiles
|
|
1288
1337
|
};
|
|
1289
1338
|
}
|
|
1290
1339
|
|
|
1291
1340
|
// src/analysis.ts
|
|
1292
|
-
import { basename, extname as extname2, join as join6, relative as relative3, sep as
|
|
1341
|
+
import { basename, extname as extname2, join as join6, relative as relative3, sep as sep4, posix as posixPath } from "path";
|
|
1293
1342
|
import { readFile as readFile4 } from "fs/promises";
|
|
1294
1343
|
var CODE_EXTS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
|
|
1295
1344
|
var ENTRY_BASENAMES = /* @__PURE__ */ new Set(["index", "main", "app", "cli", "server", "bin", "mod"]);
|
|
@@ -1404,15 +1453,16 @@ function findDuplicates(fileTexts) {
|
|
|
1404
1453
|
}
|
|
1405
1454
|
return [...groups.values()].sort((a, b) => b.lines - a.lines).slice(0, 15);
|
|
1406
1455
|
}
|
|
1407
|
-
async function analyzeProject(projectPath) {
|
|
1456
|
+
async function analyzeProject(projectPath, opts = {}) {
|
|
1408
1457
|
const abs = await findProjectRoot(resolveProjectPath(projectPath));
|
|
1409
1458
|
const fileTexts = /* @__PURE__ */ new Map();
|
|
1410
1459
|
const codeFiles = [];
|
|
1411
1460
|
const walk = await getWalkOptions(abs);
|
|
1412
1461
|
for await (const full of walkFiles(abs, walk.skipDirs, walk.skipFiles, walk.ignoreGlobs)) {
|
|
1413
|
-
const rel = relative3(abs, full).split(
|
|
1462
|
+
const rel = relative3(abs, full).split(sep4).join("/");
|
|
1414
1463
|
const ext = extname2(rel).toLowerCase();
|
|
1415
1464
|
if (!CODE_EXTS.has(ext) || SKIP_EXTS.has(ext) || rel.includes(".min.")) continue;
|
|
1465
|
+
if (opts.files && !opts.files.has(rel)) continue;
|
|
1416
1466
|
const text = await safeReadText(full);
|
|
1417
1467
|
if (!text) continue;
|
|
1418
1468
|
codeFiles.push(rel);
|
|
@@ -1624,6 +1674,7 @@ export {
|
|
|
1624
1674
|
findProjectRoot,
|
|
1625
1675
|
formatHealthReport,
|
|
1626
1676
|
formatHealthReportMd,
|
|
1677
|
+
getChangedFiles,
|
|
1627
1678
|
getEmbedding,
|
|
1628
1679
|
getEmbeddings,
|
|
1629
1680
|
getExtractor,
|