dsh-codebase-chat 0.21.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 +186 -199
- package/dist/cli.js +114 -13
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +25 -2
- package/dist/index.js +148 -11
- package/dist/index.js.map +1 -1
- package/lib/index.js +41 -13
- package/package.json +9 -10
package/lib/index.js
CHANGED
|
@@ -16,9 +16,10 @@ import {
|
|
|
16
16
|
|
|
17
17
|
let analyzeProject;
|
|
18
18
|
let formatHealthReport;
|
|
19
|
+
let getChangedFiles;
|
|
19
20
|
try {
|
|
20
|
-
({ analyzeProject, formatHealthReport } = await import("../dist/index.js"));
|
|
21
|
-
} catch { analyzeProject = undefined; formatHealthReport = undefined; }
|
|
21
|
+
({ analyzeProject, formatHealthReport, getChangedFiles } = await import("../dist/index.js"));
|
|
22
|
+
} catch { analyzeProject = undefined; formatHealthReport = undefined; getChangedFiles = undefined; }
|
|
22
23
|
|
|
23
24
|
let defineTool;
|
|
24
25
|
let createUserMessage;
|
|
@@ -646,7 +647,7 @@ function buildFilePart(rel, text) {
|
|
|
646
647
|
}
|
|
647
648
|
|
|
648
649
|
async function collectCodebaseContext(projectPath, options = {}) {
|
|
649
|
-
const { focus = "", filePath = "", searchQuery = "", lang = "fr" } = options;
|
|
650
|
+
const { focus = "", filePath = "", searchQuery = "", lang = "fr", diff = "" } = options;
|
|
650
651
|
const isEn = lang === "en";
|
|
651
652
|
const t = isEn ? {
|
|
652
653
|
project: "Project",
|
|
@@ -673,6 +674,14 @@ async function collectCodebaseContext(projectPath, options = {}) {
|
|
|
673
674
|
const absProject = await resolveProjectPath(projectPath);
|
|
674
675
|
const startDir = await findProjectRoot(absProject);
|
|
675
676
|
|
|
677
|
+
// --diff <ref> — scope the gathered context to files changed vs a git ref.
|
|
678
|
+
let diffSet = null;
|
|
679
|
+
if (diff && !filePath && getChangedFiles) {
|
|
680
|
+
const scope = await getChangedFiles(startDir, diff).catch(() => null);
|
|
681
|
+
if (scope?.ok) diffSet = scope.files;
|
|
682
|
+
}
|
|
683
|
+
const inScope = (rel) => !diffSet || diffSet.has(rel);
|
|
684
|
+
|
|
676
685
|
reportProgress("Construction de l'arborescence...");
|
|
677
686
|
const tree = await buildTree(startDir);
|
|
678
687
|
reportProgress("Lecture des fichiers racine...");
|
|
@@ -681,12 +690,15 @@ async function collectCodebaseContext(projectPath, options = {}) {
|
|
|
681
690
|
const fileParts = [];
|
|
682
691
|
|
|
683
692
|
const focusHint = focus ? `\n${t.focus} : ${focus}` : "";
|
|
693
|
+
const scopeHint = diffSet
|
|
694
|
+
? `\n${isEn ? "Scope" : "Périmètre"} : ${diffSet.size} ${isEn ? "file(s) changed vs" : "fichier(s) modifié(s) vs"} ${diff}`
|
|
695
|
+
: "";
|
|
684
696
|
const productConstraints = await extractProductConstraints(absProject);
|
|
685
697
|
const constraintsText = productConstraints.length
|
|
686
698
|
? `\n== ${t.constraints} ==\n${productConstraints.map((c) => `- ${c}`).join("\n")}\n`
|
|
687
699
|
: `\n== ${t.constraints} ==\n${t.noConstraints}\n`;
|
|
688
700
|
|
|
689
|
-
const head = `${t.project} : ${absProject}${focusHint}\n${constraintsText}\n${t.tree} :\n${tree}\n\n${rootResult.prelude}\n`;
|
|
701
|
+
const head = `${t.project} : ${absProject}${focusHint}${scopeHint}\n${constraintsText}\n${t.tree} :\n${tree}\n\n${rootResult.prelude}\n`;
|
|
690
702
|
const headTokens = estimateTokens(head);
|
|
691
703
|
const maxFileTokens = Math.max(0, MAX_CONTEXT_TOKENS - headTokens - 200);
|
|
692
704
|
let usedFileTokens = 0;
|
|
@@ -703,7 +715,8 @@ async function collectCodebaseContext(projectPath, options = {}) {
|
|
|
703
715
|
// Not a file: treat it as a symbol/term and search for it instead of failing.
|
|
704
716
|
reportProgress(isEn ? `File not found, searching symbol: ${filePath}` : `Fichier introuvable, recherche du symbole : ${filePath}`);
|
|
705
717
|
const matches = await searchFiles(absProject, filePath, 20);
|
|
706
|
-
|
|
718
|
+
const scoped = matches.filter(m => inScope(m.rel));
|
|
719
|
+
for (const m of scoped) {
|
|
707
720
|
if (remainingBytes <= 0) break;
|
|
708
721
|
const entry = `--- ${m.rel} ---\n${m.snippet}\n`;
|
|
709
722
|
if (entry.length > remainingBytes) break;
|
|
@@ -716,7 +729,7 @@ async function collectCodebaseContext(projectPath, options = {}) {
|
|
|
716
729
|
|
|
717
730
|
if (searchQuery && !filePath) {
|
|
718
731
|
const matches = await searchFiles(absProject, searchQuery);
|
|
719
|
-
for (const m of matches) {
|
|
732
|
+
for (const m of matches.filter(m => inScope(m.rel))) {
|
|
720
733
|
if (remainingBytes <= 0) break;
|
|
721
734
|
const entry = `--- ${m.rel} ---\n${m.snippet}\n`;
|
|
722
735
|
if (entry.length > remainingBytes) break;
|
|
@@ -734,6 +747,7 @@ async function collectCodebaseContext(projectPath, options = {}) {
|
|
|
734
747
|
const ext = extname(fullPath).toLowerCase();
|
|
735
748
|
if (!SOURCE_EXTS.has(ext)) continue;
|
|
736
749
|
const rel = relative(startDir, fullPath).split(sep).join("/");
|
|
750
|
+
if (!inScope(rel)) continue;
|
|
737
751
|
|
|
738
752
|
const fstats = await fileStats(fullPath);
|
|
739
753
|
if (!fstats) continue;
|
|
@@ -2138,11 +2152,12 @@ function apply(ctx) {
|
|
|
2138
2152
|
filePath: { type: "string", description: "Optional specific file or symbol to focus on." },
|
|
2139
2153
|
crea: { type: "boolean", description: "If true, append a creative 'Fait avec passion par shinzarou-eng <project>' footer." },
|
|
2140
2154
|
creaTheme: { type: "string", description: "Optional creative theme for the footer." },
|
|
2141
|
-
lang: { type: "string", description: "Response language: 'fr' (default) or 'en'." }
|
|
2155
|
+
lang: { type: "string", description: "Response language: 'fr' (default) or 'en'." },
|
|
2156
|
+
diff: { type: "string", description: "Git ref (e.g. 'main', 'HEAD~5'). Scopes the context to files changed vs that ref." }
|
|
2142
2157
|
},
|
|
2143
2158
|
(value) => buildChatPrompt(value.query, value.context, value.projectName, value.crea, value.creaTheme, value.lang),
|
|
2144
2159
|
async (args, exec) => {
|
|
2145
|
-
const { absProject, context } = await collectCodebaseContext(args.projectPath, { focus: args.question, filePath: args.filePath, lang: args.lang });
|
|
2160
|
+
const { absProject, context } = await collectCodebaseContext(args.projectPath, { focus: args.question, filePath: args.filePath, lang: args.lang, diff: args.diff });
|
|
2146
2161
|
const projectName = await getProjectName(absProject);
|
|
2147
2162
|
return { query: args.question, projectName, crea: args.crea || false, creaTheme: args.creaTheme || "", lang: args.lang || "fr", context };
|
|
2148
2163
|
}
|
|
@@ -2157,11 +2172,12 @@ function apply(ctx) {
|
|
|
2157
2172
|
query: { type: "string", description: "Term, symbol, or pattern to search." },
|
|
2158
2173
|
crea: { type: "boolean", description: "If true, append a creative 'Fait avec passion par shinzarou-eng <project>' footer." },
|
|
2159
2174
|
creaTheme: { type: "string", description: "Optional creative theme for the footer." },
|
|
2160
|
-
lang: { type: "string", description: "Response language: 'fr' (default) or 'en'." }
|
|
2175
|
+
lang: { type: "string", description: "Response language: 'fr' (default) or 'en'." },
|
|
2176
|
+
diff: { type: "string", description: "Git ref (e.g. 'main', 'HEAD~5'). Scopes the search to files changed vs that ref." }
|
|
2161
2177
|
},
|
|
2162
2178
|
(value) => buildSearchPrompt(value.query, value.context, value.projectName, value.crea, value.creaTheme, value.lang),
|
|
2163
2179
|
async (args, exec) => {
|
|
2164
|
-
const { absProject, context } = await collectCodebaseContext(args.projectPath, { focus: args.query, searchQuery: args.query, lang: args.lang });
|
|
2180
|
+
const { absProject, context } = await collectCodebaseContext(args.projectPath, { focus: args.query, searchQuery: args.query, lang: args.lang, diff: args.diff });
|
|
2165
2181
|
const projectName = await getProjectName(absProject);
|
|
2166
2182
|
return { query: args.query, projectName, crea: args.crea || false, creaTheme: args.creaTheme || "", lang: args.lang || "fr", context };
|
|
2167
2183
|
}
|
|
@@ -2229,14 +2245,26 @@ function apply(ctx) {
|
|
|
2229
2245
|
"Run a deterministic static analysis of the codebase — circular dependencies, unused files and exports, duplicated code blocks, complexity hotspots, and a health score. Returns concrete findings directly, no LLM needed. Use for 'health', 'dead code', 'circular deps', 'duplication' or 'complexity' requests.",
|
|
2230
2246
|
{
|
|
2231
2247
|
projectPath: { type: "string", description: "Absolute local path to the project folder. If omitted, the project root is auto-detected from the current working directory." },
|
|
2232
|
-
lang: { type: "string", description: "Report language: 'fr' (default) or 'en'." }
|
|
2248
|
+
lang: { type: "string", description: "Report language: 'fr' (default) or 'en'." },
|
|
2249
|
+
diff: { type: "string", description: "Git ref (e.g. 'main', 'HEAD~5'). Scopes the analysis to files changed vs that ref." }
|
|
2233
2250
|
},
|
|
2234
2251
|
(value) => value.markdown || "No report generated.",
|
|
2235
2252
|
async (args, exec) => {
|
|
2236
2253
|
if (!analyzeProject) throw new Error("Static analysis module not available.");
|
|
2237
2254
|
const projectPath = await resolveProjectPath(args.projectPath);
|
|
2238
|
-
|
|
2239
|
-
|
|
2255
|
+
let scope;
|
|
2256
|
+
let scopeLine = "";
|
|
2257
|
+
if (args.diff && getChangedFiles) {
|
|
2258
|
+
const s = await getChangedFiles(await findProjectRoot(projectPath), String(args.diff)).catch(() => null);
|
|
2259
|
+
if (s?.ok) {
|
|
2260
|
+
scope = { files: s.files };
|
|
2261
|
+
scopeLine = (args.lang === "en"
|
|
2262
|
+
? `Scope: ${s.files.size} file(s) changed vs ${args.diff}\n\n`
|
|
2263
|
+
: `Périmètre : ${s.files.size} fichier(s) modifié(s) vs ${args.diff}\n\n`);
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
const report = await analyzeProject(projectPath, scope);
|
|
2267
|
+
return { projectName: basename(report.projectPath), lang: args.lang || "fr", markdown: scopeLine + formatHealthReport(report, args.lang === "en" ? "en" : "fr") };
|
|
2240
2268
|
}
|
|
2241
2269
|
);
|
|
2242
2270
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-codebase-chat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "Multi-language codebase intelligence for DeepSeek Harness and MCP-compatible IDEs: chat, search, audit, refactor, and board-ready reports from local code.",
|
|
5
5
|
"homepage": "https://shinzarou-eng.github.io/dsh-codebase-chat",
|
|
6
6
|
"repository": {
|
|
@@ -35,14 +35,6 @@
|
|
|
35
35
|
"LICENSE",
|
|
36
36
|
"package.json"
|
|
37
37
|
],
|
|
38
|
-
"scripts": {
|
|
39
|
-
"build": "tsup",
|
|
40
|
-
"dev": "tsup --watch",
|
|
41
|
-
"typecheck": "tsc --noEmit",
|
|
42
|
-
"test": "vitest run",
|
|
43
|
-
"test:watch": "vitest",
|
|
44
|
-
"prepublishOnly": "pnpm build && pnpm test"
|
|
45
|
-
},
|
|
46
38
|
"dsh": {
|
|
47
39
|
"bundle": {
|
|
48
40
|
"patch": "./cordis.patch.yml"
|
|
@@ -91,5 +83,12 @@
|
|
|
91
83
|
"tsup": "^8.5.1",
|
|
92
84
|
"typescript": "5.7.3",
|
|
93
85
|
"vitest": "^5.0.0"
|
|
86
|
+
},
|
|
87
|
+
"scripts": {
|
|
88
|
+
"build": "tsup",
|
|
89
|
+
"dev": "tsup --watch",
|
|
90
|
+
"typecheck": "tsc --noEmit",
|
|
91
|
+
"test": "vitest run",
|
|
92
|
+
"test:watch": "vitest"
|
|
94
93
|
}
|
|
95
|
-
}
|
|
94
|
+
}
|