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/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
- for (const m of matches) {
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
- const report = await analyzeProject(projectPath);
2239
- return { projectName: basename(report.projectPath), lang: args.lang || "fr", markdown: formatHealthReport(report, args.lang === "en" ? "en" : "fr") };
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.22.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
+ }