@chappibunny/repolens 1.14.3 → 1.15.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/CHANGELOG.md CHANGED
@@ -2,6 +2,40 @@
2
2
 
3
3
  All notable changes to RepoLens will be documented in this file.
4
4
 
5
+ ## 1.15.0
6
+
7
+ ### CLI usability
8
+
9
+ - `init --quick` detects the repository language (Python, Go, Rust, Java/Kotlin, JavaScript/TypeScript, Ruby, PHP, C#, Swift, C/C++, and mixes of those) and writes matching `scan.include` globs and `module_roots`. It no longer defaults to JavaScript-only patterns such as `src/**/*.{ts,tsx,js,jsx,md}`
10
+ - `init --quick` enables the Markdown publisher only. The interactive wizard can still add Notion, Confluence, GitHub Wiki, and Obsidian
11
+ - `doctor` fails when `scan.include` matches 0 files
12
+ - `demo` and `publish` print a warning and exit 2 when 0 files are scanned. They do not write empty documentation
13
+ - `repolens <command> --help` prints that command's usage and flags for init, doctor, migrate, publish, demo, watch, uninstall, feedback, and mcp
14
+ - `--target` is checked before any scan or publish. A missing path exits 2 with `Target directory not found: <path>`
15
+ - A directory that is not a git repository is reported as `not a git repository`. Code health does not claim an average of 100/100 across 0 files
16
+ - `watch` exits 2 when none of the `module_roots` directories exist
17
+ - `publish --strict` exits 2 when a configured publisher is missing credentials
18
+ - `repolens` with no arguments prints help and does not publish
19
+ - The ASCII banner is omitted when stdout is not a terminal or `CI` is set
20
+ - Init's next-steps text uses `repolens publish` when the CLI is installed globally, and `npx @chappibunny/repolens publish` otherwise
21
+
22
+ Exit codes stay 0 success, 1 runtime error or unknown command, and 2 validation failure (bad flags, missing config, missing target, doctor failure, 0 files scanned, nothing to watch, `--strict` credentials, feedback without a terminal).
23
+
24
+ ## Unreleased
25
+
26
+ ### Release workflow
27
+
28
+ - Before publishing, the release workflow queries `npm view <pkg>@<ver> version --prefer-online` up to three times, waiting 10 seconds between tries, and skips `npm publish` when that version is already on npm
29
+ - If `npm publish` fails, the same check runs again. The step passes when the version is live
30
+ - The GitHub Release uploads `chappibunny-repolens-*.tgz`, which is the tarball name `npm pack` writes for `@chappibunny/repolens`
31
+
32
+ ## Unreleased
33
+
34
+ ### Docs
35
+
36
+ - README VS Code badge switched to a static shields.io badge (the visual-studio-marketplace badges were retired)
37
+ - New `assets/social-preview.png` (1280x640, rendered from HTML with headless Chrome)
38
+
5
39
  ## 1.14.3
6
40
 
7
41
  ### Docs
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  ```
10
10
 
11
11
  [![npm version](https://img.shields.io/npm/v/@chappibunny/repolens)](https://www.npmjs.com/package/@chappibunny/repolens)
12
- [![VS Code Extension](https://img.shields.io/visual-studio-marketplace/v/repolensdox.repolensdox?label=VS%20Code)](https://marketplace.visualstudio.com/items?itemName=repolensdox.repolensdox)
12
+ [![VS Code Extension](https://img.shields.io/badge/VS_Code-Marketplace-007ACC?logo=visualstudiocode&logoColor=white)](https://marketplace.visualstudio.com/items?itemName=repolensdox.repolensdox)
13
13
  [![Tests](https://img.shields.io/github/actions/workflow/status/CHAPIBUNNY/repolens/test.yml?label=tests)](https://github.com/CHAPIBUNNY/repolens/actions/workflows/test.yml)
14
14
  [![License](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
15
15
  [![MCP Registry](https://img.shields.io/badge/MCP_Registry-io.github.CHAPIBUNNY%2Frepolens-6E56CF)](https://registry.modelcontextprotocol.io/v0/servers?search=io.github.CHAPIBUNNY/repolens)
@@ -29,7 +29,7 @@ RepoLens scans your repository, generates living architecture documentation, and
29
29
 
30
30
  ## 🎬 Demo
31
31
 
32
- > **Try it now** — no installation required. Run `npx @chappibunny/repolens demo` on any repo for an instant local preview.
32
+ > **Try it now** — no installation required. `npx @chappibunny/repolens demo` previews the repository you point it at. It detects common languages (Python, Go, Rust, Java, JavaScript/TypeScript, and others). If the scan matches no source files, demo exits 2 and does not write empty docs.
33
33
 
34
34
  **[See it on real repos](https://chapibunny.github.io/repolens/)** — architecture pages RepoLens generated for Express, FastAPI, Django, Gin, mdBook, and other public projects. Deterministic mode, no API keys.
35
35
 
@@ -173,21 +173,34 @@ Step-by-step setup for publishers, AI features, Notion, Confluence, GitHub Wiki,
173
173
 
174
174
  | Command | Description |
175
175
  |---|---|
176
+ | `npx @chappibunny/repolens` | Show help. A bare invocation does not publish |
176
177
  | `npx @chappibunny/repolens init` | **Interactive wizard** — configure publishers, AI, credentials |
177
- | `npx @chappibunny/repolens init --quick` | Minimal setup, skip wizard |
178
+ | `npx @chappibunny/repolens init --quick` | Markdown-only setup. Globs follow the detected language |
179
+ | `npx @chappibunny/repolens init --help` | Flags for that command. Same pattern for every command below |
178
180
  | `npx @chappibunny/repolens publish` | Scan, generate, and publish documentation |
179
- | `npx @chappibunny/repolens demo` | Quick local preview — no API keys needed |
180
- | `npx @chappibunny/repolens doctor` | Validate your setup |
181
- | `npx @chappibunny/repolens watch` | Auto-regenerate docs on file changes |
181
+ | `npx @chappibunny/repolens publish --strict` | Exit 2 when a configured publisher is missing credentials |
182
+ | `npx @chappibunny/repolens demo` | Local preview — no API keys. Exits 2 when 0 files match |
183
+ | `npx @chappibunny/repolens doctor` | Validate your setup, including that scan globs match files |
184
+ | `npx @chappibunny/repolens watch` | Auto-regenerate docs on file changes. Exits 2 if nothing is watchable |
182
185
  | `npx @chappibunny/repolens migrate` | Upgrade from v0.3.0 workflows ([details](docs/MIGRATION.md)) |
183
186
  | `npx @chappibunny/repolens uninstall` | Remove all RepoLens files (config, docs, workflow) |
184
187
  | `npx @chappibunny/repolens uninstall --force` | Remove without confirmation prompt |
185
188
  | `npx @chappibunny/repolens feedback` | Send feedback to the team |
186
189
  | `npx @chappibunny/repolens mcp` | Start the stdio MCP server (`repolens-mcp` is the same server) |
187
190
 
188
- > **Note:** If you installed globally with `npm install -g @chappibunny/repolens`, you can use the shorter `repolens <command>` form.
191
+ > **Note:** If you installed globally with `npm install -g @chappibunny/repolens`, you can use the shorter `repolens <command>` form. Init's next-steps text uses that shorter form when the command on your PATH is the global install.
189
192
  > **Warning:** Do not run bare `npx repolens` — there is an unrelated `repolens@0.0.1` placeholder package on npm. Always use the scoped name `@chappibunny/repolens`.
190
193
 
194
+ ### Exit codes
195
+
196
+ | Code | Meaning |
197
+ |---|---|
198
+ | `0` | Success, including a cancelled wizard or help with no other work |
199
+ | `1` | Runtime error, or an unknown command |
200
+ | `2` | Invalid flags, missing config, target directory not found, a failed `doctor` check (including 0 files matched), `demo` or `publish` scanned 0 files, `watch` has nothing to watch, `publish --strict` is missing publisher credentials, or `feedback` has no terminal |
201
+
202
+ `--target` must already exist. A missing path exits 2 with `Target directory not found: <path>` before any scan. `repolens <command> --help` prints that command's flags. The ASCII banner is omitted when stdout is not a terminal or `CI` is set.
203
+
191
204
  ---
192
205
 
193
206
  ## MCP server
@@ -224,7 +237,7 @@ If `npx` reports `Unknown command: mcp`, an `.npmrc` with `min-release-age` (or
224
237
 
225
238
  ## GitHub Action
226
239
 
227
- `action.yml` is a composite action named **RepoLens Architecture Docs**. It runs the published npm package (`npx --yes @chappibunny/repolens@1.14.3`). Inputs are `command` (`publish`, `demo`, or `doctor`), `config`, `node-version`, `pr-comment`, and `version`.
240
+ `action.yml` is a composite action named **RepoLens Architecture Docs**. It runs the published npm package (`npx --yes @chappibunny/repolens@1.15.0`). Inputs are `command` (`publish`, `demo`, or `doctor`), `config`, `node-version`, `pr-comment`, and `version`.
228
241
 
229
242
  ```yaml
230
243
  name: Architecture docs
@@ -240,7 +253,7 @@ jobs:
240
253
  runs-on: ubuntu-latest
241
254
  steps:
242
255
  - uses: actions/checkout@9f698171ed81b15d1823a05fc7211befd50c8ae0 # v6.0.3
243
- - uses: CHAPIBUNNY/repolens@v1.14.3
256
+ - uses: CHAPIBUNNY/repolens@v1.15.0
244
257
  with:
245
258
  command: publish
246
259
  config: .repolens.yml
@@ -248,7 +261,7 @@ jobs:
248
261
  pr-comment: ${{ github.event_name == 'pull_request' && 'true' || 'false' }}
249
262
  ```
250
263
 
251
- The action is listed on the GitHub Marketplace only after a release is published with **Publish this Action to the GitHub Marketplace** checked. Those steps are in [docs/RELEASE.md](docs/RELEASE.md). The tag `v1.14.1` has to exist before `uses: CHAPIBUNNY/repolens@v1.14.3` resolves, and `@chappibunny/repolens@1.14.3` has to be on npm before the action can install it.
264
+ The action is listed on the GitHub Marketplace only after a release is published with **Publish this Action to the GitHub Marketplace** checked. Those steps are in [docs/RELEASE.md](docs/RELEASE.md). The tag `v1.15.0` has to exist before `uses: CHAPIBUNNY/repolens@v1.15.0` resolves, and `@chappibunny/repolens@1.15.0` has to be on npm before the action can install it.
252
265
 
253
266
  ## Add the badge to your repo
254
267
 
@@ -268,7 +281,7 @@ Generated docs also link to the [gallery](https://chapibunny.github.io/repolens/
268
281
 
269
282
  Published system maps are Unicode diagrams drawn from import edges. RepoLens does not emit Mermaid.
270
283
 
271
- The publisher set is Notion, Confluence, GitHub Wiki, Obsidian, and Markdown (`src/publishers`, 7 files). 1.14.0 adds the MCP server, the Marketplace action, and the docs badge. 1.14.1 sets the registry name to `io.github.CHAPIBUNNY/repolens`. 1.14.2 adds the gallery link to the badge line, `glama.json`, and `CITATION.cff`. 1.14.3 adds Cline setup docs and `llms-install.md`. Layout and counts are in [ARCHITECTURE.md](docs/ARCHITECTURE.md).
284
+ The publisher set is Notion, Confluence, GitHub Wiki, Obsidian, and Markdown (`src/publishers`, 7 files). 1.14.0 adds the MCP server, the Marketplace action, and the docs badge. 1.14.1 sets the registry name to `io.github.CHAPIBUNNY/repolens`. 1.14.2 adds the gallery link to the badge line, `glama.json`, and `CITATION.cff`. 1.14.3 adds Cline setup docs and `llms-install.md`. 1.15.0 detects repository languages in `init --quick`, refuses empty scans, and adds per-command help. Layout and counts are in [ARCHITECTURE.md](docs/ARCHITECTURE.md).
272
285
 
273
286
  ### System Overview (Technical Profile)
274
287
 
@@ -13,7 +13,7 @@ if (wantsVersion && !wantsHelp) {
13
13
 
14
14
  if (wantsHelp) {
15
15
  const { printHelp } = await import("../src/help.js");
16
- printHelp();
16
+ printHelp("mcp");
17
17
  process.exit(0);
18
18
  }
19
19
 
package/bin/repolens.js CHANGED
@@ -2,19 +2,22 @@
2
2
  import { readFileSync } from "node:fs";
3
3
 
4
4
  const argv = process.argv.slice(2);
5
- const wantsHelp = argv.includes("--help") || argv.includes("-h") || argv[0] === "help";
6
- const wantsVersion = argv.includes("--version") || argv.includes("-v") || argv[0] === "version";
5
+ const command = argv.find((arg) => !arg.startsWith("-"));
6
+ const wantsHelp = argv.includes("--help") || argv.includes("-h") || command === "help";
7
+ const wantsVersion = argv.includes("--version") || argv.includes("-v") || command === "version";
7
8
 
8
- // --version and --help must not load the scanner, publishers, or telemetry.
9
+ // --version, --help, and a bare invocation must not load the scanner,
10
+ // publishers, or telemetry. `repolens <command> --help` prints that command.
9
11
  if (wantsVersion && !wantsHelp) {
10
12
  const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
11
13
  console.log(pkg.version || "0.0.0");
12
14
  process.exit(0);
13
15
  }
14
16
 
15
- if (wantsHelp) {
17
+ if (argv.length === 0 || wantsHelp) {
16
18
  const { printHelp } = await import("../src/help.js");
17
- printHelp();
19
+ const topic = wantsHelp && command && command !== "help" ? command : undefined;
20
+ printHelp(topic);
18
21
  process.exit(0);
19
22
  }
20
23
 
package/llms-install.md CHANGED
@@ -11,3 +11,4 @@
11
11
  4. No API keys are needed. Tools run deterministically and offline unless `generate_docs` is called with `deterministic: false`.
12
12
  5. Verify: the server should list six tools — `get_system_overview`, `list_modules`, `get_api_surface`, `get_dependency_graph`, `get_architecture_diff`, `generate_docs`. Call `get_system_overview` with no arguments.
13
13
  6. If you see `Unknown command: mcp`, an older release was resolved (often an `.npmrc` with `min-release-age`). Use `@chappibunny/repolens@latest`.
14
+ 7. CLI (1.15.0): `repolens` with no arguments prints help. `repolens <command> --help` prints that command's flags. `init --quick` writes language-specific globs and Markdown only. `demo` and `publish` exit 2 when zero source files match, instead of writing empty docs. `publish --strict` exits 2 when publisher credentials are missing.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chappibunny/repolens",
3
- "version": "1.14.3",
3
+ "version": "1.15.0",
4
4
  "mcpName": "io.github.CHAPIBUNNY/repolens",
5
5
  "description": "AI-assisted documentation intelligence system for technical and non-technical audiences",
6
6
  "license": "MIT",
@@ -264,7 +264,7 @@ export function computeCodeHealth(complexityResult, depGraph, jsdocResult, secur
264
264
 
265
265
  // Aggregate stats
266
266
  const totalFiles = modules.length;
267
- const avgScore = totalFiles > 0 ? Math.round(modules.reduce((s, m) => s + m.score, 0) / totalFiles) : 100;
267
+ const avgScore = totalFiles > 0 ? Math.round(modules.reduce((s, m) => s + m.score, 0) / totalFiles) : null;
268
268
  const avgComplexity = totalFiles > 0 ? Math.round(modules.reduce((s, m) => s + m.complexity, 0) / totalFiles) : 0;
269
269
 
270
270
  const gradeDistribution = { A: 0, B: 0, C: 0, D: 0, F: 0 };
@@ -280,7 +280,11 @@ export function computeCodeHealth(complexityResult, depGraph, jsdocResult, secur
280
280
  ? `${totalFiles} files analyzed · Average health: ${avgScore}/100 · ${gradeDistribution.A} A, ${gradeDistribution.B} B, ${gradeDistribution.C} C, ${gradeDistribution.D} D, ${gradeDistribution.F} F`
281
281
  : "No source files analyzed.";
282
282
 
283
- info(`Code health: avg ${avgScore}/100 across ${totalFiles} files (${hotspots.length} hotspots)`);
283
+ if (totalFiles === 0) {
284
+ info("Code health: no source files analyzed.");
285
+ } else {
286
+ info(`Code health: avg ${avgScore}/100 across ${totalFiles} files (${hotspots.length} hotspots)`);
287
+ }
284
288
 
285
289
  return {
286
290
  modules,
package/src/cli-args.js CHANGED
@@ -4,6 +4,7 @@ const BOOLEAN_FLAGS = new Set([
4
4
  "--non-interactive",
5
5
  "--dry-run",
6
6
  "--force",
7
+ "--strict",
7
8
  "--verbose",
8
9
  "--help",
9
10
  "-h",
package/src/cli.js CHANGED
@@ -17,7 +17,6 @@ import {
17
17
  import { renderSystemMap } from "./renderers/renderMap.js";
18
18
  import { renderArchitectureDiff, buildArchitectureDiffData } from "./renderers/renderDiff.js";
19
19
 
20
- import { publishDocs } from "./publishers/index.js";
21
20
  import { upsertPrComment } from "./delivery/comment.js";
22
21
  import { runInit } from "./init.js";
23
22
  import { runMigrate } from "./migrate.js";
@@ -32,6 +31,9 @@ import { PluginManager } from "./plugins/manager.js";
32
31
  import { printHelp } from "./help.js";
33
32
  import { parseCliArgs } from "./cli-args.js";
34
33
  import { isNonInteractiveEnvironment } from "./utils/interactive.js";
34
+ import { shouldPrintBanner } from "./utils/banner.js";
35
+ import { detectRepoLanguages } from "./core/languages.js";
36
+ import { publishDocs, missingPublisherCredentials } from "./publishers/index.js";
35
37
  import {
36
38
  initTelemetry,
37
39
  captureError,
@@ -49,6 +51,18 @@ const EXIT_SUCCESS = 0;
49
51
  const EXIT_ERROR = 1;
50
52
  const EXIT_VALIDATION = 2;
51
53
 
54
+ function exitIfNoFilesScanned(scan, cfg) {
55
+ if ((scan?.filesCount || 0) > 0) return;
56
+ const patterns = Array.isArray(cfg?.scan?.include) && cfg.scan.include.length
57
+ ? cfg.scan.include.join(", ")
58
+ : "(none)";
59
+ error("WARNING: 0 files scanned.");
60
+ error(`scan.include matched no files (${patterns}).`);
61
+ error("Empty documentation was not written.");
62
+ error("Update scan.include, or run `repolens init --quick` to detect this repository's language.");
63
+ process.exit(EXIT_VALIDATION);
64
+ }
65
+
52
66
  function formatDuration(ms) {
53
67
  if (ms < 1000) return `${ms}ms`;
54
68
  return `${(ms / 1000).toFixed(1)}s`;
@@ -172,6 +186,7 @@ async function getPackageVersion() {
172
186
  }
173
187
 
174
188
  async function printBanner() {
189
+ if (!shouldPrintBanner()) return;
175
190
  const version = await getPackageVersion();
176
191
  console.log(`
177
192
  ██████╗ ███████╗██████╗ ██████╗ ██╗ ███████╗███╗ ██╗███████╗
@@ -228,7 +243,8 @@ async function main() {
228
243
  // Help and version return before telemetry and the update check.
229
244
  // bin/repolens.js answers --version/--help without loading this module.
230
245
  if (parsed.help) {
231
- printHelp();
246
+ const topic = parsed.command && parsed.command !== "help" ? parsed.command : undefined;
247
+ printHelp(topic);
232
248
  return;
233
249
  }
234
250
 
@@ -244,8 +260,28 @@ async function main() {
244
260
  process.exit(EXIT_VALIDATION);
245
261
  }
246
262
 
263
+ if (parsed.flags["--target"]) {
264
+ const targetPath = parsed.flags["--target"];
265
+ let targetIsDirectory = false;
266
+ try {
267
+ const stat = await fs.stat(targetPath);
268
+ targetIsDirectory = stat.isDirectory();
269
+ } catch {
270
+ targetIsDirectory = false;
271
+ }
272
+ if (!targetIsDirectory) {
273
+ error(`Target directory not found: ${targetPath}`);
274
+ process.exit(EXIT_VALIDATION);
275
+ }
276
+ }
277
+
247
278
  const command = parsed.command;
248
279
 
280
+ if (!command) {
281
+ printHelp();
282
+ return;
283
+ }
284
+
249
285
  // MCP stdio must start before telemetry and the npm update check.
250
286
  // Those both can open the network, and logs must stay off stdout.
251
287
  if (command === "mcp") {
@@ -366,11 +402,15 @@ async function main() {
366
402
  error(err.message);
367
403
  process.exit(EXIT_VALIDATION);
368
404
  }
369
- await runWatch(configPath);
405
+ const watchResult = await runWatch(configPath);
406
+ if (watchResult && watchResult.ok === false) {
407
+ await closeTelemetry();
408
+ process.exit(EXIT_VALIDATION);
409
+ }
370
410
  return;
371
411
  }
372
412
 
373
- if (command === "publish" || !command) {
413
+ if (command === "publish") {
374
414
  await printBanner();
375
415
 
376
416
  const commandTimer = startTimer("publish");
@@ -399,6 +439,18 @@ async function main() {
399
439
  process.exit(EXIT_VALIDATION);
400
440
  }
401
441
 
442
+ if (parsed.flags["--strict"]) {
443
+ const missing = missingPublisherCredentials(cfg);
444
+ if (missing.length > 0) {
445
+ stopTimer(commandTimer);
446
+ error("Missing publisher credentials (--strict):");
447
+ for (const item of missing) error(` ${item}`);
448
+ trackUsage("publish", "failure", { step: "strict-credentials" });
449
+ await closeTelemetry();
450
+ process.exit(EXIT_VALIDATION);
451
+ }
452
+ }
453
+
402
454
  // Prompt for AI enhancement if GITHUB_TOKEN is available
403
455
  const aiResult = await promptForAI(cfg);
404
456
 
@@ -421,6 +473,7 @@ async function main() {
421
473
  scan = await scanRepo(cfg);
422
474
  stopTimer(scanTimer);
423
475
  info(`Detected ${scan.modules?.length || 0} modules`);
476
+ exitIfNoFilesScanned(scan, cfg);
424
477
  } catch (err) {
425
478
  stopTimer(commandTimer);
426
479
  captureError(err, { command: "publish", step: "scan", patterns: cfg.scan?.patterns });
@@ -514,16 +567,20 @@ async function main() {
514
567
  info(`Using config: ${configPath}`);
515
568
  cfg = await loadConfig(configPath);
516
569
  } catch {
517
- info("No .repolens.yml found — using default scan patterns");
570
+ const detected = await detectRepoLanguages(targetDir);
571
+ const languageNames = detected.languages.map((lang) => lang.label).join(", ");
572
+ info(languageNames
573
+ ? `No .repolens.yml found — scanning ${languageNames}`
574
+ : "No .repolens.yml found — using multi-language scan patterns");
518
575
  cfg = {
519
576
  configVersion: 1,
520
577
  project: { name: path.basename(targetDir) },
521
578
  publishers: ["markdown"],
522
579
  scan: {
523
- include: ["**/*.{js,ts,jsx,tsx,mjs,cjs,py,go,rs,java,rb,php,cs,swift,kt}"],
524
- ignore: ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.git/**", "**/vendor/**", "**/target/**", "**/__pycache__/**"],
580
+ include: detected.include,
581
+ ignore: ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.git/**", "**/vendor/**", "**/target/**", "**/__pycache__/**", "**/.venv/**"],
525
582
  },
526
- module_roots: ["src", "lib", "app", "packages"],
583
+ module_roots: detected.moduleRoots,
527
584
  outputs: {
528
585
  pages: [
529
586
  { key: "system_overview", title: "System Overview" },
@@ -552,7 +609,8 @@ async function main() {
552
609
  const scan = await scanRepo(cfg);
553
610
  stopTimer(scanTimer);
554
611
  info(`Detected ${scan.modules?.length || 0} modules, ${scan.filesCount || 0} files`);
555
-
612
+ exitIfNoFilesScanned(scan, cfg);
613
+
556
614
  const rawDiff = getGitDiff("origin/main", cfg.__repoRoot || targetDir);
557
615
 
558
616
  info("Generating documentation set...");
package/src/core/diff.js CHANGED
@@ -34,6 +34,19 @@ export function classifyDiffLine(line) {
34
34
  return { bucket: "modified", file };
35
35
  }
36
36
 
37
+ /**
38
+ * Plain message for a failed git diff.
39
+ * A directory that is not a repository says so. The old internal
40
+ * "git diff failed, returning empty diff" line is not used.
41
+ */
42
+ export function gitDiffFailureMessage(stderr = "", message = "") {
43
+ const text = `${stderr}\n${message}`;
44
+ if (/not a git repository/i.test(text)) {
45
+ return "not a git repository";
46
+ }
47
+ return "git diff could not be read; continuing with an empty diff.";
48
+ }
49
+
37
50
  export function getGitDiff(baseRef = "origin/main", cwd = process.cwd(), headRef) {
38
51
  if (!isSafeGitRef(baseRef)) {
39
52
  warn("git diff skipped: base ref contains unsupported characters.");
@@ -60,7 +73,8 @@ export function getGitDiff(baseRef = "origin/main", cwd = process.cwd(), headRef
60
73
  stdio: ["ignore", "pipe", "pipe"],
61
74
  });
62
75
  } catch (error) {
63
- warn("git diff failed, returning empty diff.");
76
+ const stderr = typeof error.stderr === "string" ? error.stderr : "";
77
+ warn(gitDiffFailureMessage(stderr, error.message));
64
78
  return emptyDiff();
65
79
  }
66
80