@danielsimonjr/repo-tools 2.2.3 → 2.2.5

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
@@ -6,6 +6,55 @@ All notable changes to this project are recorded in this file. The format follow
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [2.2.5] - 2026-10-22
10
+
11
+ ### Changed
12
+
13
+ - The C# test-coverage report states its basis. A C# `using` usually names a namespace, so a test covers every file
14
+ that declares the namespace, and a covered file is not proof that a test uses the file. A test in the same namespace,
15
+ or in a nested namespace, needs no `using` and does not count, so the percentage can be too high or too low.
16
+ `TEST_COVERAGE.md` now has a note under the summary table that says "counted by namespace import".
17
+ `test-coverage.json` now has `metadata.coverageBasis` (`namespace-import`) and `metadata.coverageCaveat`. The
18
+ percentage does not change. The reports of TypeScript, Python and Rust have no note and no new key, and the golden
19
+ files do not change.
20
+ `importsNameSymbols` in `src/map/artifacts.ts` is now the one rule for "does an import name a symbol"; the
21
+ unused-analysis caveat and its export classification use it as well.
22
+ - `docs/design.md` passes `repo-tools ste` with 0 problems. The check reported 4 sentences of 31 to 43 words in
23
+ section 16.3. Each of the 4 sentences is now a group of shorter sentences. No fact changes.
24
+
25
+ ### Fixed
26
+
27
+ - `repo-tools docs` fails on a repository that has more files of a language it cannot read than TypeScript,
28
+ JavaScript and Python together. It printed `PASS -- 0/0 exported symbols documented, 0 MUST issues` and exited 0
29
+ for IronClaw, a Rust repository. `docs check src` measured the 1 JavaScript file below `src` and none of the 247
30
+ Rust files. `scan`, `check` and `stub` now exit 1 and print
31
+ `this repo is rust: 247 rust file(s) against 1 TypeScript, JavaScript or Python file(s)`. `map` already refused
32
+ such a repository. `docs` uses the file walk of `map`, so both commands give a repository the same language. A tie
33
+ is measured. A TypeScript repository with a few Rust files is measured as before. A repository of Go files only
34
+ now names Go. It said before that it found no source file.
35
+
36
+ ## [2.2.4] - 2026-10-19
37
+
38
+ ### Fixed
39
+
40
+ - `repo-tools map` and `repo-tools docs` read a `.js`, `.mjs` or `.cjs` file with the TSX grammar. These files used
41
+ the TypeScript grammar, which cannot read JSX. A JavaScript file that holds JSX then gave error nodes, and a
42
+ `docs` run counted the file as unparsed. A JavaScript file cannot hold an angle-bracket type assertion, which
43
+ TSX cannot read. The measurement used 2446 tracked JavaScript files in 87 repositories. It skipped build-output
44
+ and vendor folders, minified files and files over 1.5 MB. The TSX grammar added no error node to any file. It
45
+ removed the error nodes of the one file that held JSX. No export count changed. A `.ts` file keeps the
46
+ TypeScript grammar.
47
+
48
+ - `repo-tools map` writes `duplicate-symbols.md` and `unused-analysis.md` with text that passes `repo-tools ste`. A
49
+ TypeScript repository got a 70-word introduction in `duplicate-symbols.md`. `unused-analysis.md` held caveat
50
+ sentences of 28 and 44 words and an ambiguous "This is". For C#, it held four more findings (three long sentences
51
+ and one ambiguous reference), and for Rust one long sentence. A hand edit of a generated report is lost at the
52
+ next run, so the generator text had to change. The introduction of `duplicate-symbols.md` is now three short
53
+ sentences and a list of the four tags. Each long caveat is split into short sentences. No fact changed, and no
54
+ opt-out marker is added. The new test `map-reports-ste.test.ts` maps a TypeScript, a C#, a Rust and a Python
55
+ repository and expects no STE finding in either report. A repository that commits these reports sees a one-time
56
+ diff at its next `repo-tools map`.
57
+
9
58
  ## [2.2.3] - 2026-10-16
10
59
 
11
60
  ### Fixed
package/README.md CHANGED
@@ -13,7 +13,7 @@
13
13
  | `query` | Answers structural questions from the graph of `map`, and writes two derived reports. |
14
14
  | `ste` | Checks Markdown or docstring prose against Simplified Technical English. |
15
15
 
16
- Status: version 2.2.3.
16
+ Status: version 2.2.5.
17
17
 
18
18
  - `map`, `check` and `query` read their settings from `repo-tools.config.json` in the root. The
19
19
  section of `map` is `map` (its old name, `depgraph`, is also read). `--config=<file>` makes any
@@ -41,7 +41,7 @@ repo-tools --version
41
41
 
42
42
  The executable holds the tool and the Bun runtime. It needs no Bun, Node or `node_modules`.
43
43
  Download the file for your platform and `SHA256SUMS` from the
44
- [GitHub release](https://github.com/danielsimonjr/repo-tools/releases/tag/v2.2.3):
44
+ [GitHub release](https://github.com/danielsimonjr/repo-tools/releases/tag/v2.2.5):
45
45
 
46
46
  | Platform | File |
47
47
  |---|---|
@@ -238,7 +238,9 @@ The rules are:
238
238
  | S6 | SHOULD | The prose follows Simplified Technical English (the rules of `repo-tools ste`). |
239
239
 
240
240
  Only a MUST issue changes the exit code of `check`. A file that does not parse is also a failure,
241
- because an unknown file is not a clean file. A repository with no source file fails too.
241
+ because an unknown file is not a clean file. A repository with no source file fails too. So does a
242
+ repository with more files of another language (Rust or Go, for example) than TypeScript, JavaScript
243
+ and Python together. The command names the language and exits 1.
242
244
 
243
245
  `stub` is a dry run until you pass `--apply`. Each stub holds the marker `TODO:`, so `check` fails
244
246
  until you replace the stub with real prose. The command parses a file again after the change.
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // package.json
3
3
  var package_default = {
4
4
  name: "@danielsimonjr/repo-tools",
5
- version: "2.2.3",
5
+ version: "2.2.5",
6
6
  description: "One CLI for repository analysis: dependency graphs creation and query with per-export API facts, file chunking, and context compression. TypeScript on Bun, compiled to a single executable.",
7
7
  type: "module",
8
8
  license: "MIT",
@@ -10895,8 +10895,9 @@ function loadGrammar(name2) {
10895
10895
  }
10896
10896
  return pending;
10897
10897
  }
10898
+ var TSX_SUFFIXES = [".tsx", ".jsx", ".js", ".mjs", ".cjs"];
10898
10899
  function grammarFor(path) {
10899
- return path.endsWith(".tsx") || path.endsWith(".jsx") ? "tsx" : "typescript";
10900
+ return TSX_SUFFIXES.some((suffix) => path.endsWith(suffix)) ? "tsx" : "typescript";
10900
10901
  }
10901
10902
  function parserFor(name2) {
10902
10903
  const parser = parsers.get(name2);
@@ -13104,7 +13105,7 @@ var UNUSED_CAVEATS = [
13104
13105
  "A literal relative `import(...)` is a graph edge. A runtime call is a namespace use (`*`), so the exports of that file count as used. A type-position `import(...).Name` counts only `Name`, and `typeof import(...)` counts none. A dynamic import built from a variable or a template, and other runtime loads (e.g. `new Worker(path)`), are still invisible -- a file or export reached ONLY through one of those is reported as no-importer/unreferenced even though it may be live.",
13105
13106
  "`noImporterFiles` is NOT a deletion-candidate list. A file with zero in-repo importers is expected, not suspicious, for: a standalone script invoked directly (e.g. a smoketest run via `node script.mjs`, never `import`ed by anything), or a build/lint config file loaded by its own tool rather than by source code (e.g. `eslint.config.mjs`, read by eslint itself). Cross-check against package.json scripts / tool configs before treating any entry here as dead.",
13106
13107
  "Only NAMED exports are analysed (`FileNode.exports` carries named exports only) -- `export default` usage is not checked here.",
13107
- "The `referencedInModule` vs `unreferencedAnywhere` split for exports is a TEXT-LEVEL heuristic (whole-identifier occurrence count within the defining file's own source, minus the declaration site itself), not an AST reference count -- it can over-count a name that also appears in a string literal or comment. When the source text can't be read at all (no `RepoGraph.root_path`, or the file is missing) the export lands in `unclassifiedExports` instead of being guessed into either bucket.",
13108
+ "The split of exports into `referencedInModule` and `unreferencedAnywhere` is a TEXT-LEVEL heuristic. The analysis counts whole-identifier occurrences in the source of the defining file, minus the declaration site itself. The count is not an AST reference count. The analysis can over-count a name that also appears in a string literal or comment. When the source text cannot be read (no `RepoGraph.root_path`, or the file is missing), the export lands in `unclassifiedExports`. The analysis does not guess a bucket for it.",
13108
13109
  "`unreferencedAnywhereNotes`'s dynamic-import check (`_dynamic_import_referrers`) only sees `import(...)` calls with a LITERAL string specifier. A dynamic import built from a variable or template literal (e.g. `import(`./cmds/${name}.js`)`, a command-dispatcher pattern) is invisible to it -- 'found none' in a note means no LITERAL match was found, NOT that nothing imports the file."
13109
13110
  ];
13110
13111
  var IMPORTS_NAME_SYMBOLS = {
@@ -13113,9 +13114,12 @@ var IMPORTS_NAME_SYMBOLS = {
13113
13114
  csharp: false,
13114
13115
  rust: true
13115
13116
  };
13117
+ function importsNameSymbols(language) {
13118
+ return IMPORTS_NAME_SYMBOLS[language] ?? true;
13119
+ }
13116
13120
  var PUBLIC_API_LANGUAGES = new Set(["rust"]);
13117
- var PUBLIC_API_CAVEAT = "A `pub` item with no user inside this crate is normally PUBLIC API, not dead code. `unused-analysis` can only see this repository, and a library crate's public surface exists for DOWNSTREAM consumers that are not in the graph -- so 'no importer here' is the expected state for most public items, not a finding. Measured on one real crate: 2,635 of 2,739 exports (96%) had no in-crate user, and essentially all of them were live API. Treat this list as 'candidates to check against the crate's intended surface', never as a deletion list.";
13118
- var NAMESPACE_IMPORT_CAVEAT = "This language's imports name a namespace, not the symbols they bring in (C#'s `using App.Hosting;`), so per-symbol usage cannot be read off the import edges at all. Usage here is a whole-identifier text search across every scanned file instead -- the same class of TEXT-LEVEL heuristic as the `referencedInModule` split above, and subject to the same limit: a name appearing in another file's comment or string literal counts as a use. That makes this direction conservative -- it UNDER-reports dead code rather than inventing it -- which is the safe direction for a list whose entries read as deletion candidates. A type reached only by reflection (`Activator.CreateInstance`, DI-by-convention) or named solely in a .csproj/XAML file is invisible either way.";
13121
+ var PUBLIC_API_CAVEAT = "A `pub` item with no user inside this crate is normally PUBLIC API, not dead code. `unused-analysis` sees only the repository. A library crate's public surface exists for DOWNSTREAM consumers. These consumers are not in the graph. Therefore, 'no importer here' is the expected state for most public items. The state is not a finding. Measured on one real crate: 2,635 of 2,739 exports (96%) had no in-crate user, and essentially all of them were live API. Treat this list as 'candidates to check against the crate's intended surface', never as a deletion list.";
13122
+ var NAMESPACE_IMPORT_CAVEAT = "In this language, an import names a namespace, not the symbols that it brings in (C#'s `using App.Hosting;`). The import edges therefore cannot show the use of one symbol. Usage here is a whole-identifier text search across every scanned file instead. The search is the same class of TEXT-LEVEL heuristic as the `referencedInModule` split above. The limit is the same: a name in the comment or string literal of another file counts as a use. The search is conservative. It UNDER-reports dead code and never invents it. That direction is safe for a list whose entries read as deletion candidates. Neither the import edges nor the text search can see a type that only reflection reaches (`Activator.CreateInstance`, DI-by-convention) or that only a .csproj/XAML file names.";
13119
13123
  var IDENTIFIER = new RegExp(`${B}[A-Za-z_]${W}*`, "gu");
13120
13124
  function readSourceText(rootPath, rel) {
13121
13125
  if (rootPath === null)
@@ -13143,7 +13147,7 @@ function identifierUsers(graph) {
13143
13147
  }
13144
13148
  function caveatsFor(graph) {
13145
13149
  const caveats = [...UNUSED_CAVEATS];
13146
- if (!(IMPORTS_NAME_SYMBOLS[graph.language] ?? true))
13150
+ if (!importsNameSymbols(graph.language))
13147
13151
  caveats.push(NAMESPACE_IMPORT_CAVEAT);
13148
13152
  if (PUBLIC_API_LANGUAGES.has(graph.language))
13149
13153
  caveats.push(PUBLIC_API_CAVEAT);
@@ -13184,7 +13188,7 @@ function classifyUnusedExports(graph) {
13184
13188
  const unreferenced = {};
13185
13189
  const referencedInModule = {};
13186
13190
  const unclassified = {};
13187
- const namesSymbols = IMPORTS_NAME_SYMBOLS[graph.language] ?? true;
13191
+ const namesSymbols = importsNameSymbols(graph.language);
13188
13192
  const users = namesSymbols ? new Map : identifierUsers(graph);
13189
13193
  const push = (bucket, path, name2) => {
13190
13194
  const list = bucket[path] ?? [];
@@ -13529,6 +13533,8 @@ import { basename as basename5 } from "node:path";
13529
13533
  function percent(tested, total) {
13530
13534
  return total > 0 ? (tested / total * 100).toFixed(1) : "0";
13531
13535
  }
13536
+ var NAMESPACE_IMPORT_BASIS = "namespace-import";
13537
+ var NAMESPACE_IMPORT_COVERAGE_CAVEAT = "Coverage is counted by namespace import. A C# `using` directive usually names a namespace, not a type, so a test covers every file that declares the namespace. A namespace import does not show that a test uses the file. A test in the same namespace, or in a nested namespace, needs no `using` and does not count. The percentage can be too high or too low.";
13532
13538
  function generateTestCoverageMarkdown(coverage, options = {}) {
13533
13539
  const hint = options.expectedTestHint ?? true;
13534
13540
  const lines = [];
@@ -13547,9 +13553,11 @@ function generateTestCoverageMarkdown(coverage, options = {}) {
13547
13553
  lines.push(`| Source Files without Tests | ${totalUntested} |`);
13548
13554
  lines.push(`| Coverage (raw, direct-import) | **${percent(totalTested, totalSource)}%** |`);
13549
13555
  const b = coverage.policyBreakdown;
13556
+ const basisNote = options.namespaceImportBasis ? [`> ${NAMESPACE_IMPORT_COVERAGE_CAVEAT}`, ""] : [];
13550
13557
  if (coverage.policy) {
13551
13558
  lines.push(`| Coverage (effective, active code only) | **${b.effectivePercent}%** (${b.testedActive} / ${b.activeFiles}) |`);
13552
13559
  lines.push("");
13560
+ lines.push(...basisNote);
13553
13561
  const policyPath = options.policyPath ?? "docs/architecture/coverage-policy.json";
13554
13562
  lines.push("> The raw figure counts every source file the CDG tool finds, including code that is " + "intentionally not direct-imported by a vitest `*.test.ts` (synced mathjs categories, " + "AssemblyScript sources, type-only barrels, …). The **effective** figure excludes those " + `per \`${policyPath}\` so the number reflects the genuinely-active hand-written code only. ` + "See [`COVERAGE_POLICY.md`](./COVERAGE_POLICY.md) for the policy.");
13555
13563
  lines.push("");
@@ -13565,6 +13573,8 @@ function generateTestCoverageMarkdown(coverage, options = {}) {
13565
13573
  }
13566
13574
  lines.push(`| **Active (real gap — needs a test)** | ${b.byCategory.active_untested ?? 0} | These are the files that should grow a direct-import test. |`);
13567
13575
  lines.push("");
13576
+ } else if (basisNote.length > 0) {
13577
+ lines.push("", ...basisNote);
13568
13578
  }
13569
13579
  lines.push("---");
13570
13580
  lines.push("");
@@ -13620,7 +13630,7 @@ function generateTestCoverageMarkdown(coverage, options = {}) {
13620
13630
  return lines.join(`
13621
13631
  `);
13622
13632
  }
13623
- function generateTestCoverageJson(coverage) {
13633
+ function generateTestCoverageJson(coverage, options = {}) {
13624
13634
  const coverageMapObj = {};
13625
13635
  for (const [source, tests] of coverage.coverageMap)
13626
13636
  coverageMapObj[source] = tests;
@@ -13635,6 +13645,10 @@ function generateTestCoverageJson(coverage) {
13635
13645
  testedCount: coverage.testedFiles.length,
13636
13646
  untestedCount: coverage.untestedFiles.length,
13637
13647
  coveragePercent: percent(coverage.testedFiles.length, coverage.sourceFiles.length),
13648
+ ...options.namespaceImportBasis ? {
13649
+ coverageBasis: NAMESPACE_IMPORT_BASIS,
13650
+ coverageCaveat: NAMESPACE_IMPORT_COVERAGE_CAVEAT
13651
+ } : {},
13638
13652
  effectiveCoverage: {
13639
13653
  policyLoaded: coverage.policy !== null,
13640
13654
  activeFiles: b.activeFiles,
@@ -14139,12 +14153,14 @@ function emitTestCoverage(graph, root, outDir, options = {}) {
14139
14153
  const coverage = analyzeTestCoverage(sources, tests, root, options.policyPath ?? join16(root, "docs", "architecture", "coverage-policy.json"));
14140
14154
  const mdPath = join16(outDir, "TEST_COVERAGE.md");
14141
14155
  const jsonPath = join16(outDir, "test-coverage.json");
14156
+ const namespaceImportBasis = !importsNameSymbols(graph.language);
14142
14157
  const markdown = generateTestCoverageMarkdown(coverage, {
14143
14158
  expectedTestHint: graph.language === "typescript",
14144
- policyPath: options.policyNote ?? "docs/architecture/coverage-policy.json"
14159
+ policyPath: options.policyNote ?? "docs/architecture/coverage-policy.json",
14160
+ namespaceImportBasis
14145
14161
  });
14146
14162
  writeReport(mdPath, withBanner(markdown, { command: MAP_REGENERATE_COMMAND, ...options.banner }));
14147
- writeReport(jsonPath, JSON.stringify(generateTestCoverageJson(coverage), null, 2));
14163
+ writeReport(jsonPath, JSON.stringify(generateTestCoverageJson(coverage, { namespaceImportBasis }), null, 2));
14148
14164
  return [mdPath, jsonPath];
14149
14165
  }
14150
14166
 
@@ -14194,14 +14210,22 @@ function generateDuplicateSymbolsMarkdown(report) {
14194
14210
  let md = `# Duplicate Symbols
14195
14211
 
14196
14212
  `;
14197
- md += "Names that are OWN-DEFINED (not merely re-exported) by >= 2 distinct files across ";
14198
- md += "the monorepo, then CLASSIFIED (see `DupEntryTag`) so the actionable subset is clear: ";
14199
- md += "`TRUE_DUPLICATE` (real merge targets) vs `DISPATCH_VARIANT` (>=2 `mathTyped(...)` ";
14200
- md += "registrations of the same public name — distinct dispatch surfaces, Bucket C delegation ";
14201
- md += "candidates, not copy-paste bodies), `ALIAS_DELEGATION` (a `const X = importedY` ";
14202
- md += "forward, excluded once <2 real bodies remain), and `ALLOWLISTED` (matches ";
14203
- md += "`duplicate-allowlist.json`: hot-path `is*` guards, AssemblyScript mirrors, ";
14204
- md += "per-package `VERSION` strings).\n\n";
14213
+ md += "A name is listed here when two or more distinct files across the monorepo define it. ";
14214
+ md += "A file that only re-exports the name does not count. ";
14215
+ md += "Each name carries one tag (see `DupEntryTag`), so the actionable subset is clear:\n\n";
14216
+ md += "- `TRUE_DUPLICATE`: a real merge target.\n";
14217
+ md += "- `DISPATCH_VARIANT`: two or more `mathTyped(...)` registrations of the same public name. ";
14218
+ md += "The registrations are distinct dispatch surfaces and Bucket C delegation candidates, ";
14219
+ md += `not copy-paste bodies.
14220
+ `;
14221
+ md += "- `ALIAS_DELEGATION`: a `const X = importedY` forward. ";
14222
+ md += `The entry is excluded once fewer than two real bodies remain.
14223
+ `;
14224
+ md += "- `ALLOWLISTED`: the name matches `duplicate-allowlist.json`. ";
14225
+ md += "Examples are hot-path `is*` guards, AssemblyScript mirrors and per-package `VERSION` ";
14226
+ md += `strings.
14227
+
14228
+ `;
14205
14229
  md += `> **Note:** ${report.note}
14206
14230
 
14207
14231
  `;
@@ -14357,7 +14381,7 @@ function unusedMarkdown(unused, graph) {
14357
14381
  lines.push("Source files that only a test reaches. They ship nothing, but a test uses them.", "");
14358
14382
  bulletList(lines, testOnly);
14359
14383
  lines.push("## Files with no in-repo importer", "");
14360
- lines.push("No file of this repository imports these files. This is not a deletion list.", "");
14384
+ lines.push("No file of this repository imports these files. Do not use the list as a deletion list.", "");
14361
14385
  bulletList(lines, unused.noImporterFiles ?? []);
14362
14386
  bucket(lines, "Exports unreferenced anywhere", "No other file imports these names, and their own module does not use them.", unused.unreferencedAnywhere ?? {});
14363
14387
  const notes = unused.unreferencedAnywhereNotes ?? {};
@@ -17508,6 +17532,22 @@ class ConfigError extends Error {
17508
17532
  this.name = "ConfigError";
17509
17533
  }
17510
17534
  }
17535
+
17536
+ class UnreadableRepoLanguage extends Error {
17537
+ constructor(message) {
17538
+ super(message);
17539
+ this.name = "UnreadableRepoLanguage";
17540
+ }
17541
+ }
17542
+ function assertReadableLanguage(root) {
17543
+ const counts = countLanguages(root);
17544
+ const readable = (counts.typescript ?? 0) + (counts.python ?? 0);
17545
+ const unreadable = Object.entries(counts).filter(([language]) => language !== "typescript" && language !== "python").sort(([a, x], [b, y]) => y - x || (a < b ? -1 : a > b ? 1 : 0));
17546
+ const [language, n] = unreadable[0] ?? ["", 0];
17547
+ if (n > readable) {
17548
+ throw new UnreadableRepoLanguage(`this repo is ${language}: ${n} ${language} file(s) against ${readable} TypeScript, ` + "JavaScript or Python file(s). repo-tools docs reads TypeScript, JavaScript and Python " + "only, so a PASS here would describe the wrong files. It fails rather than print a gate " + "that measured nothing.");
17549
+ }
17550
+ }
17511
17551
  function languageOf(path) {
17512
17552
  const suffix = suffixOf(path.slice(path.lastIndexOf("/") + 1));
17513
17553
  if (PYTHON_SUFFIXES2.has(suffix))
@@ -18702,7 +18742,9 @@ var DOCS_HELP = `Usage: repo-tools docs scan [root] [--out=<dir>]
18702
18742
  Measure, stub and gate the documentation comments of the TypeScript, JavaScript
18703
18743
  and Python files of a repository. The files are the files that git tracks. A
18704
18744
  file in a skipped folder (node_modules, dist, build, bundle, out, vendor) or
18705
- with a generated name (.d.ts, .min.js, _pb2.py) is not read.
18745
+ with a generated name (.d.ts, .min.js, _pb2.py) is not read. A repository with
18746
+ more files of another language (for example Rust or Go) than TypeScript,
18747
+ JavaScript and Python together is refused: the run names the language and exits 1.
18706
18748
 
18707
18749
  Actions:
18708
18750
  scan Measure the coverage. Write COVERAGE.md and coverage.json.
@@ -19076,6 +19118,7 @@ async function run5(argv, io) {
19076
19118
  root = resolve8(options.root);
19077
19119
  if (!isDirectory(root))
19078
19120
  throw new Error("the root <root> is not an existing directory");
19121
+ assertReadableLanguage(root);
19079
19122
  if (options.action === "scan")
19080
19123
  return await runScan(root, options, io);
19081
19124
  if (options.action === "stub")
@@ -19085,7 +19128,7 @@ async function run5(argv, io) {
19085
19128
  const message = error instanceof Error ? error.message : String(error);
19086
19129
  stderr(`repo-tools docs: ${message}
19087
19130
  `);
19088
- return error instanceof NothingToMeasure ? 1 : 2;
19131
+ return error instanceof NothingToMeasure || error instanceof UnreadableRepoLanguage ? 1 : 2;
19089
19132
  }
19090
19133
  }
19091
19134
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danielsimonjr/repo-tools",
3
- "version": "2.2.3",
3
+ "version": "2.2.5",
4
4
  "description": "One CLI for repository analysis: dependency graphs creation and query with per-export API facts, file chunking, and context compression. TypeScript on Bun, compiled to a single executable.",
5
5
  "type": "module",
6
6
  "license": "MIT",