dsh-codebase-chat 0.22.0 → 0.24.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,12 @@ import {
16
16
 
17
17
  let analyzeProject;
18
18
  let formatHealthReport;
19
+ let getChangedFiles;
20
+ let analyzeImpact;
21
+ let formatImpactReport;
19
22
  try {
20
- ({ analyzeProject, formatHealthReport } = await import("../dist/index.js"));
21
- } catch { analyzeProject = undefined; formatHealthReport = undefined; }
23
+ ({ analyzeProject, formatHealthReport, getChangedFiles, analyzeImpact, formatImpactReport } = await import("../dist/index.js"));
24
+ } catch { analyzeProject = undefined; formatHealthReport = undefined; getChangedFiles = undefined; analyzeImpact = undefined; formatImpactReport = undefined; }
22
25
 
23
26
  let defineTool;
24
27
  let createUserMessage;
@@ -32,14 +35,12 @@ try {
32
35
  const name = "codebase-chat";
33
36
  const inject = ["tools", "commands", "agents", "systemPrompt"];
34
37
 
35
- const VERSION = "0.19.0";
38
+ const VERSION = "0.23.0";
36
39
  const execAsync = promisify(exec);
37
40
 
38
- const PROTECTED_PATHS = [
39
- ...(process.env.DSH_PROTECTED_PATHS ? process.env.DSH_PROTECTED_PATHS.split(/[;|]/).map((p) => p.trim()).filter(Boolean) : []),
40
- "C:\\Users\\shinzarou-eng\\Downloads\\marketing-session-app",
41
- "marketing-session-app"
42
- ];
41
+ const PROTECTED_PATHS = process.env.DSH_PROTECTED_PATHS
42
+ ? process.env.DSH_PROTECTED_PATHS.split(/[;|]/).map((p) => p.trim()).filter(Boolean)
43
+ : [];
43
44
 
44
45
  // Per-project config: .codebase-chat.json at the project root.
45
46
  const projectConfigCache = new Map();
@@ -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,52 @@ 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") };
2268
+ }
2269
+ );
2270
+
2271
+ registerTool(
2272
+ tools,
2273
+ "codebase_impact",
2274
+ "Blast-radius analysis: which files transitively depend on a target file — what breaks if it changes. Deterministic, returns findings directly, no LLM needed. Use for 'impact', 'what breaks', 'dependents', 'who imports X' requests.",
2275
+ {
2276
+ projectPath: { type: "string", description: "Absolute local path to the project folder. If omitted, the project root is auto-detected from the current working directory." },
2277
+ file: { type: "string", description: "File to analyze — relative path or name (e.g. 'src/store.ts'). Required." },
2278
+ lang: { type: "string", description: "Report language: 'fr' (default) or 'en'." }
2279
+ },
2280
+ (value) => value.markdown || "No report generated.",
2281
+ async (args, exec) => {
2282
+ if (!analyzeImpact) throw new Error("Static analysis module not available.");
2283
+ const projectPath = await resolveProjectPath(args.projectPath);
2284
+ const file = String(args.file || "").trim();
2285
+ if (!file) throw new Error("Missing `file` argument (e.g. 'src/store.ts').");
2286
+ const r = await analyzeImpact(projectPath, file);
2287
+ if (!r.ok) {
2288
+ const msg = r.candidates.length
2289
+ ? `Cible ambiguë "${file}" — candidats : ${r.candidates.join(', ')}`
2290
+ : `Aucun fichier de code ne correspond à "${file}".`;
2291
+ return { projectName: basename(projectPath), lang: args.lang || "fr", markdown: msg };
2292
+ }
2293
+ return { projectName: basename(r.report.projectPath), lang: args.lang || "fr", markdown: formatImpactReport(r.report, args.lang === "en" ? "en" : "fr") };
2240
2294
  }
2241
2295
  );
2242
2296
 
@@ -2513,6 +2567,28 @@ function apply(ctx) {
2513
2567
  }
2514
2568
  });
2515
2569
 
2570
+ commands.register({
2571
+ name: "codebase-impact",
2572
+ description: "Analyse d'impact : quels fichiers dépendent de X (rayon d'impact). /codebase-impact <fichier> --project <chemin> [--lang en|fr]",
2573
+ input: { hint: "<fichier>, --project <chemin>, --lang <en|fr>" },
2574
+ async handler(invocation) {
2575
+ if (!analyzeImpact) return { kind: "error", text: "Module d'analyse statique indisponible (build dist manquant)." };
2576
+ const { rawInput } = invocation;
2577
+ const { projectPath, query, filePath, lang } = parseCodebaseInput(rawInput.trim());
2578
+ const file = (filePath || query || "").trim();
2579
+ if (!file) return { kind: "error", text: "Précise un fichier : /codebase-impact src/store.ts" };
2580
+ const absProject = await resolveProjectPath(projectPath);
2581
+ const r = await analyzeImpact(absProject, file);
2582
+ if (!r.ok) {
2583
+ const msg = r.candidates.length
2584
+ ? `Cible ambiguë "${file}" — candidats :\n ${r.candidates.join("\n ")}`
2585
+ : `Aucun fichier de code ne correspond à "${file}".`;
2586
+ return { kind: "error", text: msg };
2587
+ }
2588
+ return { kind: "success", text: formatImpactReport(r.report, lang === "en" ? "en" : "fr") };
2589
+ }
2590
+ });
2591
+
2516
2592
  commands.register({
2517
2593
  name: "codebase-intel",
2518
2594
  description: "Brief d'Intelligence Pro : architecture, graphe de modules, data flow, risques, opportunités et créa. /codebase-intel --project <chemin> [focus] [--style <ouf|punchy|dense|pedagogique|minimal>]",
@@ -2697,7 +2773,7 @@ function apply(ctx) {
2697
2773
  systemPrompt?.section?.({
2698
2774
  name: "codebase-chat",
2699
2775
  order: 130,
2700
- text: `You have access to a powerful codebase plugin (dsh-codebase-chat v${VERSION}). When the user asks about code, files, project structure, architecture, how something works, where something is, or wants to search/explain/refactor/audit/report on code, use the appropriate tool: codebase_chat, codebase_search, codebase_explain, codebase_refactor, codebase_crea, codebase_intelligence, codebase_audit, codebase_report, codebase_ceo, codebase_tasks, codebase_build, codebase_git, codebase_apply, codebase_health, or codebase_player. codebase_health runs a deterministic static analysis (circular deps, dead code, duplication, complexity, health score) and returns findings directly — prefer it when the user asks for measurable code quality. codebase_intelligence is the premium auto-mode: it builds a structured context and asks for a professional, stylish intelligence brief. codebase_ceo is the one-page executive brief: metrics, score cards, top risks, top opportunities, SWOT, killer move, perfect for a board or investor. codebase_audit is the dedicated non-conformity and tech-debt auditor. codebase_report is the executive strategic report: a beautiful, visual, deep assessment (SWOT, score cards, 90-day roadmap, marketing) perfect for boards or clients. codebase_player is the UX / playthrough / player perspective brief: user journey, onboarding, friction, wow moments, gamification, and score cards from a user's point of view.\n\nIf the user asks for raw tasks, a TASKS.md with Avant/Après code blocks, or uses --raw, call codebase_tasks with raw=true. It writes TASKS.md directly in the project and returns the path/content. If the user asks to apply the plan, run the fixes, or says 'apply tasks', call codebase_apply_tasks. Use dryRun=true only if the user asks for a simulation or dry-run. If the user confirms or says 'apply', 'execute', 'yes', or 'oui', call with dryRun=false and apply the patches.\n\nIf no project path is given, first attempt to auto-detect the project root from the current working directory (cwd). Auto-detection walks up the tree looking for package.json, .git or tsconfig.json. Do NOT fall back to any known project (including the 'Dako' alias) unless the user explicitly mentions it. The only recognized alias is 'Dako' for the path 'D:\\Nouveau dossier'; use it only when the user explicitly says 'Dako' or uses --project Dako. If auto-detection fails and no explicit path is provided, ask the user for the path. Always answer in the same language as the user's message. If the user wants a creative idea at the end, set crea=true or use codebase_crea. Users may also use slash commands /codebase, /codebase-search, /codebase-explain, /codebase-refactor, /codebase-crea, /codebase-intel, /codebase-audit, /codebase-report, /codebase-ceo, /codebase-tasks, /codebase-tasks-raw, /codebase-apply-tasks, /codebase-build, /codebase-git, /codebase-apply, /codebase-player.`
2776
+ text: `You have access to a powerful codebase plugin (dsh-codebase-chat v${VERSION}). When the user asks about code, files, project structure, architecture, how something works, where something is, or wants to search/explain/refactor/audit/report on code, use the appropriate tool: codebase_chat, codebase_search, codebase_explain, codebase_refactor, codebase_crea, codebase_intelligence, codebase_audit, codebase_report, codebase_ceo, codebase_tasks, codebase_build, codebase_git, codebase_apply, codebase_health, codebase_impact, or codebase_player. codebase_health runs a deterministic static analysis (circular deps, dead code, duplication, complexity, health score) and returns findings directly. codebase_impact computes the blast radius of a file which files transitively depend on it — deterministic, no LLM — prefer it when the user asks for measurable code quality. codebase_intelligence is the premium auto-mode: it builds a structured context and asks for a professional, stylish intelligence brief. codebase_ceo is the one-page executive brief: metrics, score cards, top risks, top opportunities, SWOT, killer move, perfect for a board or investor. codebase_audit is the dedicated non-conformity and tech-debt auditor. codebase_report is the executive strategic report: a beautiful, visual, deep assessment (SWOT, score cards, 90-day roadmap, marketing) perfect for boards or clients. codebase_player is the UX / playthrough / player perspective brief: user journey, onboarding, friction, wow moments, gamification, and score cards from a user's point of view.\n\nIf the user asks for raw tasks, a TASKS.md with Avant/Après code blocks, or uses --raw, call codebase_tasks with raw=true. It writes TASKS.md directly in the project and returns the path/content. If the user asks to apply the plan, run the fixes, or says 'apply tasks', call codebase_apply_tasks. Use dryRun=true only if the user asks for a simulation or dry-run. If the user confirms or says 'apply', 'execute', 'yes', or 'oui', call with dryRun=false and apply the patches.\n\nIf no project path is given, first attempt to auto-detect the project root from the current working directory (cwd). Auto-detection walks up the tree looking for package.json, .git or tsconfig.json. Do NOT fall back to any known project (including the 'Dako' alias) unless the user explicitly mentions it. The only recognized alias is 'Dako' for the path 'D:\\Nouveau dossier'; use it only when the user explicitly says 'Dako' or uses --project Dako. If auto-detection fails and no explicit path is provided, ask the user for the path. Always answer in the same language as the user's message. If the user wants a creative idea at the end, set crea=true or use codebase_crea. Users may also use slash commands /codebase, /codebase-search, /codebase-explain, /codebase-refactor, /codebase-crea, /codebase-intel, /codebase-audit, /codebase-report, /codebase-ceo, /codebase-tasks, /codebase-tasks-raw, /codebase-apply-tasks, /codebase-build, /codebase-git, /codebase-apply, /codebase-impact, /codebase-player.`
2701
2777
  });
2702
2778
 
2703
2779
  function sameOrigin(req) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-codebase-chat",
3
- "version": "0.22.0",
3
+ "version": "0.24.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": {
@@ -11,6 +11,9 @@
11
11
  "url": "https://github.com/shinzarou-eng/dsh-codebase-chat/issues"
12
12
  },
13
13
  "type": "module",
14
+ "engines": {
15
+ "node": ">=20"
16
+ },
14
17
  "main": "./dist/index.js",
15
18
  "types": "./dist/index.d.ts",
16
19
  "bin": {
@@ -35,14 +38,6 @@
35
38
  "LICENSE",
36
39
  "package.json"
37
40
  ],
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
41
  "dsh": {
47
42
  "bundle": {
48
43
  "patch": "./cordis.patch.yml"
@@ -91,5 +86,12 @@
91
86
  "tsup": "^8.5.1",
92
87
  "typescript": "5.7.3",
93
88
  "vitest": "^5.0.0"
89
+ },
90
+ "scripts": {
91
+ "build": "tsup",
92
+ "dev": "tsup --watch",
93
+ "typecheck": "tsc --noEmit",
94
+ "test": "vitest run",
95
+ "test:watch": "vitest"
94
96
  }
95
- }
97
+ }