@danielsimonjr/repo-tools 2.2.4 → 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 +27 -0
- package/README.md +5 -3
- package/dist/cli.js +42 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,33 @@ 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
|
+
|
|
9
36
|
## [2.2.4] - 2026-10-19
|
|
10
37
|
|
|
11
38
|
### 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.
|
|
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.
|
|
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.
|
|
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",
|
|
@@ -13114,6 +13114,9 @@ var IMPORTS_NAME_SYMBOLS = {
|
|
|
13114
13114
|
csharp: false,
|
|
13115
13115
|
rust: true
|
|
13116
13116
|
};
|
|
13117
|
+
function importsNameSymbols(language) {
|
|
13118
|
+
return IMPORTS_NAME_SYMBOLS[language] ?? true;
|
|
13119
|
+
}
|
|
13117
13120
|
var PUBLIC_API_LANGUAGES = new Set(["rust"]);
|
|
13118
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.";
|
|
13119
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.";
|
|
@@ -13144,7 +13147,7 @@ function identifierUsers(graph) {
|
|
|
13144
13147
|
}
|
|
13145
13148
|
function caveatsFor(graph) {
|
|
13146
13149
|
const caveats = [...UNUSED_CAVEATS];
|
|
13147
|
-
if (!(
|
|
13150
|
+
if (!importsNameSymbols(graph.language))
|
|
13148
13151
|
caveats.push(NAMESPACE_IMPORT_CAVEAT);
|
|
13149
13152
|
if (PUBLIC_API_LANGUAGES.has(graph.language))
|
|
13150
13153
|
caveats.push(PUBLIC_API_CAVEAT);
|
|
@@ -13185,7 +13188,7 @@ function classifyUnusedExports(graph) {
|
|
|
13185
13188
|
const unreferenced = {};
|
|
13186
13189
|
const referencedInModule = {};
|
|
13187
13190
|
const unclassified = {};
|
|
13188
|
-
const namesSymbols =
|
|
13191
|
+
const namesSymbols = importsNameSymbols(graph.language);
|
|
13189
13192
|
const users = namesSymbols ? new Map : identifierUsers(graph);
|
|
13190
13193
|
const push = (bucket, path, name2) => {
|
|
13191
13194
|
const list = bucket[path] ?? [];
|
|
@@ -13530,6 +13533,8 @@ import { basename as basename5 } from "node:path";
|
|
|
13530
13533
|
function percent(tested, total) {
|
|
13531
13534
|
return total > 0 ? (tested / total * 100).toFixed(1) : "0";
|
|
13532
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.";
|
|
13533
13538
|
function generateTestCoverageMarkdown(coverage, options = {}) {
|
|
13534
13539
|
const hint = options.expectedTestHint ?? true;
|
|
13535
13540
|
const lines = [];
|
|
@@ -13548,9 +13553,11 @@ function generateTestCoverageMarkdown(coverage, options = {}) {
|
|
|
13548
13553
|
lines.push(`| Source Files without Tests | ${totalUntested} |`);
|
|
13549
13554
|
lines.push(`| Coverage (raw, direct-import) | **${percent(totalTested, totalSource)}%** |`);
|
|
13550
13555
|
const b = coverage.policyBreakdown;
|
|
13556
|
+
const basisNote = options.namespaceImportBasis ? [`> ${NAMESPACE_IMPORT_COVERAGE_CAVEAT}`, ""] : [];
|
|
13551
13557
|
if (coverage.policy) {
|
|
13552
13558
|
lines.push(`| Coverage (effective, active code only) | **${b.effectivePercent}%** (${b.testedActive} / ${b.activeFiles}) |`);
|
|
13553
13559
|
lines.push("");
|
|
13560
|
+
lines.push(...basisNote);
|
|
13554
13561
|
const policyPath = options.policyPath ?? "docs/architecture/coverage-policy.json";
|
|
13555
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.");
|
|
13556
13563
|
lines.push("");
|
|
@@ -13566,6 +13573,8 @@ function generateTestCoverageMarkdown(coverage, options = {}) {
|
|
|
13566
13573
|
}
|
|
13567
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. |`);
|
|
13568
13575
|
lines.push("");
|
|
13576
|
+
} else if (basisNote.length > 0) {
|
|
13577
|
+
lines.push("", ...basisNote);
|
|
13569
13578
|
}
|
|
13570
13579
|
lines.push("---");
|
|
13571
13580
|
lines.push("");
|
|
@@ -13621,7 +13630,7 @@ function generateTestCoverageMarkdown(coverage, options = {}) {
|
|
|
13621
13630
|
return lines.join(`
|
|
13622
13631
|
`);
|
|
13623
13632
|
}
|
|
13624
|
-
function generateTestCoverageJson(coverage) {
|
|
13633
|
+
function generateTestCoverageJson(coverage, options = {}) {
|
|
13625
13634
|
const coverageMapObj = {};
|
|
13626
13635
|
for (const [source, tests] of coverage.coverageMap)
|
|
13627
13636
|
coverageMapObj[source] = tests;
|
|
@@ -13636,6 +13645,10 @@ function generateTestCoverageJson(coverage) {
|
|
|
13636
13645
|
testedCount: coverage.testedFiles.length,
|
|
13637
13646
|
untestedCount: coverage.untestedFiles.length,
|
|
13638
13647
|
coveragePercent: percent(coverage.testedFiles.length, coverage.sourceFiles.length),
|
|
13648
|
+
...options.namespaceImportBasis ? {
|
|
13649
|
+
coverageBasis: NAMESPACE_IMPORT_BASIS,
|
|
13650
|
+
coverageCaveat: NAMESPACE_IMPORT_COVERAGE_CAVEAT
|
|
13651
|
+
} : {},
|
|
13639
13652
|
effectiveCoverage: {
|
|
13640
13653
|
policyLoaded: coverage.policy !== null,
|
|
13641
13654
|
activeFiles: b.activeFiles,
|
|
@@ -14140,12 +14153,14 @@ function emitTestCoverage(graph, root, outDir, options = {}) {
|
|
|
14140
14153
|
const coverage = analyzeTestCoverage(sources, tests, root, options.policyPath ?? join16(root, "docs", "architecture", "coverage-policy.json"));
|
|
14141
14154
|
const mdPath = join16(outDir, "TEST_COVERAGE.md");
|
|
14142
14155
|
const jsonPath = join16(outDir, "test-coverage.json");
|
|
14156
|
+
const namespaceImportBasis = !importsNameSymbols(graph.language);
|
|
14143
14157
|
const markdown = generateTestCoverageMarkdown(coverage, {
|
|
14144
14158
|
expectedTestHint: graph.language === "typescript",
|
|
14145
|
-
policyPath: options.policyNote ?? "docs/architecture/coverage-policy.json"
|
|
14159
|
+
policyPath: options.policyNote ?? "docs/architecture/coverage-policy.json",
|
|
14160
|
+
namespaceImportBasis
|
|
14146
14161
|
});
|
|
14147
14162
|
writeReport(mdPath, withBanner(markdown, { command: MAP_REGENERATE_COMMAND, ...options.banner }));
|
|
14148
|
-
writeReport(jsonPath, JSON.stringify(generateTestCoverageJson(coverage), null, 2));
|
|
14163
|
+
writeReport(jsonPath, JSON.stringify(generateTestCoverageJson(coverage, { namespaceImportBasis }), null, 2));
|
|
14149
14164
|
return [mdPath, jsonPath];
|
|
14150
14165
|
}
|
|
14151
14166
|
|
|
@@ -17517,6 +17532,22 @@ class ConfigError extends Error {
|
|
|
17517
17532
|
this.name = "ConfigError";
|
|
17518
17533
|
}
|
|
17519
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
|
+
}
|
|
17520
17551
|
function languageOf(path) {
|
|
17521
17552
|
const suffix = suffixOf(path.slice(path.lastIndexOf("/") + 1));
|
|
17522
17553
|
if (PYTHON_SUFFIXES2.has(suffix))
|
|
@@ -18711,7 +18742,9 @@ var DOCS_HELP = `Usage: repo-tools docs scan [root] [--out=<dir>]
|
|
|
18711
18742
|
Measure, stub and gate the documentation comments of the TypeScript, JavaScript
|
|
18712
18743
|
and Python files of a repository. The files are the files that git tracks. A
|
|
18713
18744
|
file in a skipped folder (node_modules, dist, build, bundle, out, vendor) or
|
|
18714
|
-
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.
|
|
18715
18748
|
|
|
18716
18749
|
Actions:
|
|
18717
18750
|
scan Measure the coverage. Write COVERAGE.md and coverage.json.
|
|
@@ -19085,6 +19118,7 @@ async function run5(argv, io) {
|
|
|
19085
19118
|
root = resolve8(options.root);
|
|
19086
19119
|
if (!isDirectory(root))
|
|
19087
19120
|
throw new Error("the root <root> is not an existing directory");
|
|
19121
|
+
assertReadableLanguage(root);
|
|
19088
19122
|
if (options.action === "scan")
|
|
19089
19123
|
return await runScan(root, options, io);
|
|
19090
19124
|
if (options.action === "stub")
|
|
@@ -19094,7 +19128,7 @@ async function run5(argv, io) {
|
|
|
19094
19128
|
const message = error instanceof Error ? error.message : String(error);
|
|
19095
19129
|
stderr(`repo-tools docs: ${message}
|
|
19096
19130
|
`);
|
|
19097
|
-
return error instanceof NothingToMeasure ? 1 : 2;
|
|
19131
|
+
return error instanceof NothingToMeasure || error instanceof UnreadableRepoLanguage ? 1 : 2;
|
|
19098
19132
|
}
|
|
19099
19133
|
}
|
|
19100
19134
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danielsimonjr/repo-tools",
|
|
3
|
-
"version": "2.2.
|
|
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",
|