@tested/cli 0.1.0 → 0.1.2
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/README.md +28 -11
- package/dist/td.js +135 -32
- package/dist/tested.js +135 -32
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -2,29 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
Coverage your agent can use. CLI for patch + project coverage with agent-readable JSON output.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
**→ [GitHub Action](action/README.md)** (`action/action.yml` composite)
|
|
5
|
+
Docs: https://tested.dev/docs
|
|
7
6
|
|
|
8
7
|
## Install
|
|
9
8
|
|
|
10
|
-
Node
|
|
9
|
+
Node 24+.
|
|
11
10
|
|
|
12
11
|
```bash
|
|
13
12
|
pnpm add -D @tested/cli
|
|
14
13
|
# or
|
|
15
|
-
npx @tested/cli
|
|
14
|
+
npx @tested/cli # runs `tested` (`td` is the same CLI after install)
|
|
16
15
|
```
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
```bash
|
|
18
|
+
npx @tested/cli --version
|
|
19
|
+
# after a local install, pnpm swallows --version unless you use exec:
|
|
20
|
+
pnpm exec -- tested --version
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
CI uses the composite Action (`tested-hq/cli/action@main`). It installs `@tested/cli` from npm.
|
|
19
24
|
|
|
20
25
|
```yaml
|
|
21
26
|
- uses: tested-hq/cli/action@main
|
|
22
27
|
with:
|
|
28
|
+
version: 0.1.2
|
|
23
29
|
token: ${{ secrets.TESTED_TOKEN }}
|
|
24
30
|
```
|
|
25
31
|
|
|
26
|
-
Pin `cli-ref` to a SHA in production. See [action/README.md](action/README.md).
|
|
27
|
-
|
|
28
32
|
## First 10 minutes
|
|
29
33
|
|
|
30
34
|
```
|
|
@@ -85,7 +89,7 @@ The hook runs `tested run && tested diff` against the upstream branch (or `HEAD~
|
|
|
85
89
|
```yaml
|
|
86
90
|
thresholds:
|
|
87
91
|
patch: 80 # % of newly-added lines that must be covered
|
|
88
|
-
project:
|
|
92
|
+
project: 90 # % of the whole project that must be covered
|
|
89
93
|
```
|
|
90
94
|
|
|
91
95
|
```
|
|
@@ -93,7 +97,7 @@ $ tested check
|
|
|
93
97
|
tested.dev — coverage gate [PASS]
|
|
94
98
|
|
|
95
99
|
Patch 87.3% (threshold 80) [PASS]
|
|
96
|
-
Project
|
|
100
|
+
Project 92.1% (threshold 90) [PASS]
|
|
97
101
|
|
|
98
102
|
thresholds met
|
|
99
103
|
$ echo $?
|
|
@@ -103,13 +107,25 @@ $ tested check
|
|
|
103
107
|
tested.dev — coverage gate [FAIL]
|
|
104
108
|
|
|
105
109
|
Patch 42.7% (threshold 80) [FAIL]
|
|
106
|
-
Project
|
|
110
|
+
Project 92.1% (threshold 90) [PASS]
|
|
107
111
|
|
|
108
112
|
→ add tests for uncovered ranges: tested diff
|
|
109
113
|
$ echo $?
|
|
110
114
|
1
|
|
111
115
|
```
|
|
112
116
|
|
|
117
|
+
If the patch has no executable lines in scope (tests-only, comments-only, docs-only, or ignored files), the patch gate is **skipped** — not reported as 0% coverage. Project still applies.
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
$ tested check
|
|
121
|
+
tested.dev — coverage gate [PASS]
|
|
122
|
+
|
|
123
|
+
Patch - no executable lines in the patch [SKIP]
|
|
124
|
+
Project 92.1% (threshold 90) [PASS]
|
|
125
|
+
|
|
126
|
+
No executable lines in the patch — patch gate skipped. Project threshold met.
|
|
127
|
+
```
|
|
128
|
+
|
|
113
129
|
If `thresholds` is missing from `.tested.yaml`, `tested check` prints a notice on stderr and exits 0 — configs that haven't opted in stay green.
|
|
114
130
|
|
|
115
131
|
### GitHub Actions
|
|
@@ -117,6 +133,7 @@ If `thresholds` is missing from `.tested.yaml`, `tested check` prints a notice o
|
|
|
117
133
|
```yaml
|
|
118
134
|
- uses: tested-hq/cli/action@main
|
|
119
135
|
with:
|
|
136
|
+
version: 0.1.2
|
|
120
137
|
push: true
|
|
121
138
|
pr-number: ${{ github.event.pull_request.number }}
|
|
122
139
|
token: ${{ secrets.TESTED_TOKEN }}
|
|
@@ -128,7 +145,7 @@ For programmatic consumers:
|
|
|
128
145
|
|
|
129
146
|
```
|
|
130
147
|
$ tested check --json
|
|
131
|
-
{"patch":{"pct":87.3,"threshold":80,"pass":true},"project":{"pct":
|
|
148
|
+
{"patch":{"pct":87.3,"threshold":80,"pass":true},"project":{"pct":92.1,"threshold":90,"pass":true},"overall":"pass"}
|
|
132
149
|
```
|
|
133
150
|
|
|
134
151
|
`--json` suppresses the human layout; the exit code is unchanged.
|
package/dist/td.js
CHANGED
|
@@ -44,6 +44,8 @@ function badge(kind) {
|
|
|
44
44
|
return pc.yellow("[WARN]");
|
|
45
45
|
case "info":
|
|
46
46
|
return pc.cyan("[INFO]");
|
|
47
|
+
case "skip":
|
|
48
|
+
return pc.cyan("[SKIP]");
|
|
47
49
|
}
|
|
48
50
|
}
|
|
49
51
|
function metricBar(pct3, width = 10) {
|
|
@@ -130,6 +132,10 @@ function formatCliError(message) {
|
|
|
130
132
|
|
|
131
133
|
// src/commands/init.ts
|
|
132
134
|
import pc2 from "picocolors";
|
|
135
|
+
var DEFAULT_INIT_THRESHOLDS = {
|
|
136
|
+
patch: 80,
|
|
137
|
+
project: 90
|
|
138
|
+
};
|
|
133
139
|
var INIT_YAML_HEADER = "# Config schema \u2014 see https://tested.dev/docs/config\n";
|
|
134
140
|
var DEFAULT_INIT_IGNORES = [
|
|
135
141
|
"**/*.test.ts",
|
|
@@ -179,9 +185,11 @@ function buildInitYaml(args) {
|
|
|
179
185
|
if (args.testRunner) {
|
|
180
186
|
lines.push(`testRunner: ${args.testRunner}`);
|
|
181
187
|
}
|
|
188
|
+
lines.push("# Patch gate is skipped when the diff has no executable lines");
|
|
189
|
+
lines.push("# (tests-only, comments-only, docs-only, or ignored files).");
|
|
182
190
|
lines.push("thresholds:");
|
|
183
|
-
lines.push(
|
|
184
|
-
lines.push(
|
|
191
|
+
lines.push(` patch: ${DEFAULT_INIT_THRESHOLDS.patch}`);
|
|
192
|
+
lines.push(` project: ${DEFAULT_INIT_THRESHOLDS.project}`);
|
|
185
193
|
lines.push("ignores:");
|
|
186
194
|
for (const pattern of DEFAULT_INIT_IGNORES) {
|
|
187
195
|
lines.push(` - "${pattern}"`);
|
|
@@ -545,7 +553,9 @@ var FileCoverageSchema = z2.object({
|
|
|
545
553
|
var CoverageTotalsSchema = z2.object({
|
|
546
554
|
executable: z2.number().int().nonnegative(),
|
|
547
555
|
covered: z2.number().int().nonnegative(),
|
|
548
|
-
pct: z2.number().min(0).max(100)
|
|
556
|
+
pct: z2.number().min(0).max(100),
|
|
557
|
+
/** Present when executable === 0 — not a 0% coverage result. */
|
|
558
|
+
empty: z2.literal(true).optional()
|
|
549
559
|
});
|
|
550
560
|
var ProjectTotalsSchema = CoverageTotalsSchema.extend({
|
|
551
561
|
delta: z2.number().nullable()
|
|
@@ -614,7 +624,14 @@ async function headSha(ctx) {
|
|
|
614
624
|
return (await ctx.git.revparse(["HEAD"])).trim();
|
|
615
625
|
}
|
|
616
626
|
async function unifiedDiff(ctx, base) {
|
|
617
|
-
|
|
627
|
+
try {
|
|
628
|
+
const mergeBase = (await ctx.git.raw(["merge-base", base, "HEAD"])).trim();
|
|
629
|
+
if (mergeBase) {
|
|
630
|
+
return ctx.git.diff([`${base}...HEAD`]);
|
|
631
|
+
}
|
|
632
|
+
} catch {
|
|
633
|
+
}
|
|
634
|
+
return ctx.git.diff([base, "HEAD"]);
|
|
618
635
|
}
|
|
619
636
|
async function remoteUrl(ctx, remote = "origin") {
|
|
620
637
|
return (await ctx.git.raw(["remote", "get-url", remote])).trim();
|
|
@@ -763,6 +780,10 @@ function assertWithinRoot(root, resolvedPath) {
|
|
|
763
780
|
}
|
|
764
781
|
|
|
765
782
|
// src/core/patch.ts
|
|
783
|
+
var EMPTY_PATCH_REASON = "no executable lines in the patch";
|
|
784
|
+
function isEmptyPatch(totals) {
|
|
785
|
+
return totals.executable === 0;
|
|
786
|
+
}
|
|
766
787
|
function pct(covered, executable) {
|
|
767
788
|
if (executable === 0) return 0;
|
|
768
789
|
return Math.round(covered / executable * 1e3) / 10;
|
|
@@ -862,10 +883,10 @@ function uncoveredRanges(file) {
|
|
|
862
883
|
function buildDiffOutput(args) {
|
|
863
884
|
const patch = computePatchCoverage(args.files, args.addedByFile);
|
|
864
885
|
const project = computeProjectCoverage(args.files);
|
|
865
|
-
const
|
|
866
|
-
|
|
867
|
-
...project.byFile.keys()
|
|
868
|
-
|
|
886
|
+
const emptyPatch = isEmptyPatch(patch.totals);
|
|
887
|
+
const fileNames = new Set(
|
|
888
|
+
emptyPatch ? [] : [...patch.byFile.keys(), ...project.byFile.keys()]
|
|
889
|
+
);
|
|
869
890
|
const files = [];
|
|
870
891
|
for (const name of fileNames) {
|
|
871
892
|
const file = args.files.find((f) => f.path === name);
|
|
@@ -882,7 +903,7 @@ function buildDiffOutput(args) {
|
|
|
882
903
|
schemaVersion: 1,
|
|
883
904
|
base: args.base,
|
|
884
905
|
head: args.head,
|
|
885
|
-
patch: patch.totals,
|
|
906
|
+
patch: emptyPatch ? { ...patch.totals, empty: true } : patch.totals,
|
|
886
907
|
project: { ...project.totals, delta: args.projectDelta ?? null },
|
|
887
908
|
files,
|
|
888
909
|
ignored: [...args.ignored]
|
|
@@ -1508,6 +1529,7 @@ function registerPushCommand(program2) {
|
|
|
1508
1529
|
|
|
1509
1530
|
// src/commands/doctor.ts
|
|
1510
1531
|
var TESTED_BIN_BASENAME_RE = /^tested(\.js)?$/;
|
|
1532
|
+
var MIN_NODE_MAJOR = 24;
|
|
1511
1533
|
function statusBadge(status) {
|
|
1512
1534
|
const kind = status === "pass" ? "pass" : status === "fail" ? "fail" : status === "warn" ? "warn" : "info";
|
|
1513
1535
|
return badge(kind);
|
|
@@ -1574,20 +1596,19 @@ async function runDoctor(deps) {
|
|
|
1574
1596
|
const checks = [];
|
|
1575
1597
|
const major = parseNodeMajor(nodeVersion);
|
|
1576
1598
|
const nodeDisplay = nodeVersion.replace(/^v/, "v");
|
|
1577
|
-
if (major !== null && major >=
|
|
1599
|
+
if (major !== null && major >= MIN_NODE_MAJOR) {
|
|
1578
1600
|
checks.push({
|
|
1579
1601
|
id: "node",
|
|
1580
1602
|
label: "Node.js",
|
|
1581
1603
|
status: "pass",
|
|
1582
|
-
detail: `${nodeDisplay} (>=
|
|
1604
|
+
detail: `${nodeDisplay} (>= ${MIN_NODE_MAJOR})`
|
|
1583
1605
|
});
|
|
1584
1606
|
} else {
|
|
1585
1607
|
checks.push({
|
|
1586
1608
|
id: "node",
|
|
1587
1609
|
label: "Node.js",
|
|
1588
|
-
status: "
|
|
1589
|
-
detail: `${nodeDisplay}
|
|
1590
|
-
optional: true
|
|
1610
|
+
status: "fail",
|
|
1611
|
+
detail: `${nodeDisplay} is below ${MIN_NODE_MAJOR}. Node >= ${MIN_NODE_MAJOR} required.`
|
|
1591
1612
|
});
|
|
1592
1613
|
}
|
|
1593
1614
|
let isRepo = false;
|
|
@@ -1809,7 +1830,7 @@ async function runDoctor(deps) {
|
|
|
1809
1830
|
optional: true
|
|
1810
1831
|
});
|
|
1811
1832
|
}
|
|
1812
|
-
const hardFailIds = /* @__PURE__ */ new Set(["git", "config", "origin"]);
|
|
1833
|
+
const hardFailIds = /* @__PURE__ */ new Set(["node", "git", "config", "origin"]);
|
|
1813
1834
|
const safetyFailIds = /* @__PURE__ */ new Set(["api_url", "tested_bin", "token"]);
|
|
1814
1835
|
const hasHardFail = checks.some(
|
|
1815
1836
|
(c) => c.status === "fail" && hardFailIds.has(c.id)
|
|
@@ -1854,6 +1875,73 @@ function registerDoctorCommand(program2) {
|
|
|
1854
1875
|
|
|
1855
1876
|
// src/commands/setup.ts
|
|
1856
1877
|
import pc3 from "picocolors";
|
|
1878
|
+
|
|
1879
|
+
// package.json
|
|
1880
|
+
var package_default = {
|
|
1881
|
+
name: "@tested/cli",
|
|
1882
|
+
version: "0.1.2",
|
|
1883
|
+
description: "Coverage your agent can use. CLI for patch + project coverage with agent-readable JSON output.",
|
|
1884
|
+
license: "MIT",
|
|
1885
|
+
homepage: "https://tested.dev",
|
|
1886
|
+
repository: {
|
|
1887
|
+
type: "git",
|
|
1888
|
+
url: "git+https://github.com/tested-hq/cli.git"
|
|
1889
|
+
},
|
|
1890
|
+
bugs: {
|
|
1891
|
+
url: "https://github.com/tested-hq/cli/issues"
|
|
1892
|
+
},
|
|
1893
|
+
publishConfig: {
|
|
1894
|
+
access: "public"
|
|
1895
|
+
},
|
|
1896
|
+
type: "module",
|
|
1897
|
+
packageManager: "pnpm@11.3.0",
|
|
1898
|
+
bin: {
|
|
1899
|
+
tested: "./dist/tested.js",
|
|
1900
|
+
td: "./dist/tested.js"
|
|
1901
|
+
},
|
|
1902
|
+
files: [
|
|
1903
|
+
"dist",
|
|
1904
|
+
"README.md",
|
|
1905
|
+
"LICENSE"
|
|
1906
|
+
],
|
|
1907
|
+
scripts: {
|
|
1908
|
+
build: "tsup",
|
|
1909
|
+
dev: "tsx bin/tested.ts",
|
|
1910
|
+
test: "vitest run",
|
|
1911
|
+
"test:watch": "vitest",
|
|
1912
|
+
"test:coverage": "vitest run --coverage",
|
|
1913
|
+
typecheck: "tsc --noEmit",
|
|
1914
|
+
prepare: "node ./scripts/prepare.mjs",
|
|
1915
|
+
prepublishOnly: "pnpm run build"
|
|
1916
|
+
},
|
|
1917
|
+
engines: {
|
|
1918
|
+
node: ">=24"
|
|
1919
|
+
},
|
|
1920
|
+
dependencies: {
|
|
1921
|
+
commander: "14.0.3",
|
|
1922
|
+
minimatch: "10.2.5",
|
|
1923
|
+
picocolors: "1.1.1",
|
|
1924
|
+
"simple-git": "3.36.0",
|
|
1925
|
+
yaml: "2.9.0",
|
|
1926
|
+
zod: "4.4.3"
|
|
1927
|
+
},
|
|
1928
|
+
devDependencies: {
|
|
1929
|
+
"@types/node": "25.9.1",
|
|
1930
|
+
"@vitest/coverage-v8": "4.1.7",
|
|
1931
|
+
husky: "9.1.7",
|
|
1932
|
+
"is-ci": "3.0.1",
|
|
1933
|
+
tsup: "8.5.1",
|
|
1934
|
+
tsx: "4.22.3",
|
|
1935
|
+
typescript: "6.0.3",
|
|
1936
|
+
vite: "8.0.14",
|
|
1937
|
+
vitest: "4.1.7"
|
|
1938
|
+
}
|
|
1939
|
+
};
|
|
1940
|
+
|
|
1941
|
+
// src/version.ts
|
|
1942
|
+
var CLI_VERSION = package_default.version;
|
|
1943
|
+
|
|
1944
|
+
// src/commands/setup.ts
|
|
1857
1945
|
var PINNED_CLI = "@tested/cli";
|
|
1858
1946
|
function buildCiSnippet() {
|
|
1859
1947
|
return [
|
|
@@ -1865,12 +1953,9 @@ function buildCiSnippet() {
|
|
|
1865
1953
|
" runs-on: ubuntu-latest",
|
|
1866
1954
|
" steps:",
|
|
1867
1955
|
" - uses: actions/checkout@v4",
|
|
1868
|
-
" with:",
|
|
1869
|
-
" fetch-depth: 0",
|
|
1870
1956
|
" - uses: tested-hq/cli/action@main",
|
|
1871
1957
|
" with:",
|
|
1872
|
-
|
|
1873
|
-
" cli-ref: main",
|
|
1958
|
+
` version: ${CLI_VERSION}`,
|
|
1874
1959
|
" push: true",
|
|
1875
1960
|
" pr-number: ${{ github.event.pull_request.number }}",
|
|
1876
1961
|
" token: ${{ secrets.TESTED_TOKEN }}"
|
|
@@ -1887,6 +1972,7 @@ function buildTokenInstructions() {
|
|
|
1887
1972
|
function buildInstallInstructions() {
|
|
1888
1973
|
return [
|
|
1889
1974
|
"Install CLI:",
|
|
1975
|
+
" Node 24+",
|
|
1890
1976
|
` pnpm add -D ${PINNED_CLI}`,
|
|
1891
1977
|
` # or: npx ${PINNED_CLI}`,
|
|
1892
1978
|
"",
|
|
@@ -1931,6 +2017,12 @@ function formatSetupHuman(opts) {
|
|
|
1931
2017
|
])
|
|
1932
2018
|
);
|
|
1933
2019
|
lines.push("");
|
|
2020
|
+
lines.push(
|
|
2021
|
+
dim(
|
|
2022
|
+
"Patch gate is skipped when the diff has no executable lines (tests-only, docs, comments)."
|
|
2023
|
+
)
|
|
2024
|
+
);
|
|
2025
|
+
lines.push("");
|
|
1934
2026
|
lines.push(tip("re-check anytime: tested doctor"));
|
|
1935
2027
|
lines.push("");
|
|
1936
2028
|
return lines.join("\n");
|
|
@@ -2196,8 +2288,8 @@ function coloredPctCell(pct3, width = 6) {
|
|
|
2196
2288
|
function formatMetricRow(label, pct3, covered, executable, emptyNote) {
|
|
2197
2289
|
const labelPad = label.padEnd(8);
|
|
2198
2290
|
if (executable === 0) {
|
|
2199
|
-
const note = emptyNote ??
|
|
2200
|
-
return ` ${labelPad} ${dim("-".padEnd(6))} ${
|
|
2291
|
+
const note = emptyNote ?? EMPTY_PATCH_REASON;
|
|
2292
|
+
return ` ${labelPad} ${dim("-".padEnd(6))} ${note}`;
|
|
2201
2293
|
}
|
|
2202
2294
|
const pctStr = coloredPctCell(pct3);
|
|
2203
2295
|
const bar = metricBar(pct3);
|
|
@@ -2216,7 +2308,7 @@ function formatHuman(out, opts = {}) {
|
|
|
2216
2308
|
out.patch.pct,
|
|
2217
2309
|
out.patch.covered,
|
|
2218
2310
|
out.patch.executable,
|
|
2219
|
-
|
|
2311
|
+
EMPTY_PATCH_REASON
|
|
2220
2312
|
)
|
|
2221
2313
|
);
|
|
2222
2314
|
const projectRow = formatMetricRow(
|
|
@@ -2229,7 +2321,7 @@ function formatHuman(out, opts = {}) {
|
|
|
2229
2321
|
if (opts.thresholds) {
|
|
2230
2322
|
const patchPass = out.patch.pct >= opts.thresholds.patch;
|
|
2231
2323
|
const projectPass = out.project.pct >= opts.thresholds.project;
|
|
2232
|
-
const patchOk = out.patch
|
|
2324
|
+
const patchOk = isEmptyPatch(out.patch) ? true : patchPass;
|
|
2233
2325
|
const overall = patchOk && projectPass;
|
|
2234
2326
|
const details = [];
|
|
2235
2327
|
if (!patchOk) {
|
|
@@ -2240,7 +2332,7 @@ function formatHuman(out, opts = {}) {
|
|
|
2240
2332
|
`project ${out.project.pct.toFixed(1)}% < ${opts.thresholds.project}%`
|
|
2241
2333
|
);
|
|
2242
2334
|
}
|
|
2243
|
-
const detail = details.length > 0 ? dim(` ${details.join("; ")}`) : "";
|
|
2335
|
+
const detail = details.length > 0 ? dim(` ${details.join("; ")}`) : isEmptyPatch(out.patch) ? dim(` ${EMPTY_PATCH_REASON}`) : "";
|
|
2244
2336
|
lines.push(
|
|
2245
2337
|
` ${"Gate".padEnd(8)} ${overall ? badge("pass") : badge("fail")}${detail}`
|
|
2246
2338
|
);
|
|
@@ -2253,12 +2345,15 @@ function formatHuman(out, opts = {}) {
|
|
|
2253
2345
|
);
|
|
2254
2346
|
}
|
|
2255
2347
|
}
|
|
2256
|
-
if (out.
|
|
2348
|
+
if (isEmptyPatch(out.patch)) {
|
|
2349
|
+
lines.push("");
|
|
2350
|
+
lines.push(dim("No executable lines in the patch \u2014 patch gate does not apply."));
|
|
2351
|
+
} else if (out.files.length > 0) {
|
|
2257
2352
|
lines.push("");
|
|
2258
2353
|
lines.push(heading("Files in diff:"));
|
|
2259
2354
|
const anyPatch = out.files.some((f) => f.patchCoverage !== null);
|
|
2260
2355
|
if (!anyPatch) {
|
|
2261
|
-
lines.push(dim(
|
|
2356
|
+
lines.push(dim(` (project coverage \u2014 ${EMPTY_PATCH_REASON})`));
|
|
2262
2357
|
}
|
|
2263
2358
|
for (const f of out.files) {
|
|
2264
2359
|
const hasPatch = f.patchCoverage !== null;
|
|
@@ -2344,7 +2439,7 @@ ${tip("add thresholds.patch / thresholds.project to enforce")}
|
|
|
2344
2439
|
const projectPct = diff.project.pct;
|
|
2345
2440
|
const patchThreshold = config.thresholds.patch;
|
|
2346
2441
|
const projectThreshold = config.thresholds.project;
|
|
2347
|
-
const patchSkipped = diff.patch
|
|
2442
|
+
const patchSkipped = isEmptyPatch(diff.patch);
|
|
2348
2443
|
const patchPass = patchSkipped ? true : patchPct >= patchThreshold;
|
|
2349
2444
|
const projectPass = projectPct >= projectThreshold;
|
|
2350
2445
|
const overall = patchPass && projectPass ? "pass" : "fail";
|
|
@@ -2355,10 +2450,11 @@ ${tip("add thresholds.patch / thresholds.project to enforce")}
|
|
|
2355
2450
|
pct: patchPct,
|
|
2356
2451
|
threshold: patchThreshold,
|
|
2357
2452
|
pass: patchPass,
|
|
2358
|
-
...patchSkipped ? { skipped: true } : {}
|
|
2453
|
+
...patchSkipped ? { skipped: true, reason: EMPTY_PATCH_REASON } : {}
|
|
2359
2454
|
},
|
|
2360
2455
|
project: { pct: projectPct, threshold: projectThreshold, pass: projectPass },
|
|
2361
|
-
overall
|
|
2456
|
+
overall,
|
|
2457
|
+
...patchSkipped ? { note: EMPTY_PATCH_REASON } : {}
|
|
2362
2458
|
};
|
|
2363
2459
|
return {
|
|
2364
2460
|
skipped: false,
|
|
@@ -2377,7 +2473,7 @@ ${tip("add thresholds.patch / thresholds.project to enforce")}
|
|
|
2377
2473
|
lines.push("");
|
|
2378
2474
|
if (patchSkipped) {
|
|
2379
2475
|
lines.push(
|
|
2380
|
-
` ${"Patch".padEnd(8)} ${dim("-")} ${
|
|
2476
|
+
` ${"Patch".padEnd(8)} ${dim("-")} ${EMPTY_PATCH_REASON} ${badge("skip")}`
|
|
2381
2477
|
);
|
|
2382
2478
|
} else {
|
|
2383
2479
|
lines.push(formatMetricLine("Patch", patchPct, patchThreshold, patchPass));
|
|
@@ -2385,10 +2481,17 @@ ${tip("add thresholds.patch / thresholds.project to enforce")}
|
|
|
2385
2481
|
lines.push(formatMetricLine("Project", projectPct, projectThreshold, projectPass));
|
|
2386
2482
|
if (overall === "fail") {
|
|
2387
2483
|
lines.push("");
|
|
2484
|
+
if (patchSkipped) {
|
|
2485
|
+
lines.push(dim("No executable lines in the patch \u2014 patch gate skipped."));
|
|
2486
|
+
}
|
|
2388
2487
|
lines.push(tip("add tests for uncovered ranges: tested diff"));
|
|
2389
2488
|
} else {
|
|
2390
2489
|
lines.push("");
|
|
2391
|
-
lines.push(
|
|
2490
|
+
lines.push(
|
|
2491
|
+
dim(
|
|
2492
|
+
patchSkipped ? "No executable lines in the patch \u2014 patch gate skipped. Project threshold met." : "thresholds met"
|
|
2493
|
+
)
|
|
2494
|
+
);
|
|
2392
2495
|
}
|
|
2393
2496
|
lines.push("");
|
|
2394
2497
|
return {
|
|
@@ -2561,7 +2664,7 @@ function createProgram() {
|
|
|
2561
2664
|
"Agent loop:",
|
|
2562
2665
|
" tested setup \u2192 tested run \u2192 tested diff \u2192 tested check \u2192 tested push --pr <n>"
|
|
2563
2666
|
].join("\n")
|
|
2564
|
-
).version(
|
|
2667
|
+
).version(CLI_VERSION);
|
|
2565
2668
|
registerSetupCommand(program2);
|
|
2566
2669
|
registerDoctorCommand(program2);
|
|
2567
2670
|
registerInitCommand(program2);
|
package/dist/tested.js
CHANGED
|
@@ -44,6 +44,8 @@ function badge(kind) {
|
|
|
44
44
|
return pc.yellow("[WARN]");
|
|
45
45
|
case "info":
|
|
46
46
|
return pc.cyan("[INFO]");
|
|
47
|
+
case "skip":
|
|
48
|
+
return pc.cyan("[SKIP]");
|
|
47
49
|
}
|
|
48
50
|
}
|
|
49
51
|
function metricBar(pct3, width = 10) {
|
|
@@ -130,6 +132,10 @@ function formatCliError(message) {
|
|
|
130
132
|
|
|
131
133
|
// src/commands/init.ts
|
|
132
134
|
import pc2 from "picocolors";
|
|
135
|
+
var DEFAULT_INIT_THRESHOLDS = {
|
|
136
|
+
patch: 80,
|
|
137
|
+
project: 90
|
|
138
|
+
};
|
|
133
139
|
var INIT_YAML_HEADER = "# Config schema \u2014 see https://tested.dev/docs/config\n";
|
|
134
140
|
var DEFAULT_INIT_IGNORES = [
|
|
135
141
|
"**/*.test.ts",
|
|
@@ -179,9 +185,11 @@ function buildInitYaml(args) {
|
|
|
179
185
|
if (args.testRunner) {
|
|
180
186
|
lines.push(`testRunner: ${args.testRunner}`);
|
|
181
187
|
}
|
|
188
|
+
lines.push("# Patch gate is skipped when the diff has no executable lines");
|
|
189
|
+
lines.push("# (tests-only, comments-only, docs-only, or ignored files).");
|
|
182
190
|
lines.push("thresholds:");
|
|
183
|
-
lines.push(
|
|
184
|
-
lines.push(
|
|
191
|
+
lines.push(` patch: ${DEFAULT_INIT_THRESHOLDS.patch}`);
|
|
192
|
+
lines.push(` project: ${DEFAULT_INIT_THRESHOLDS.project}`);
|
|
185
193
|
lines.push("ignores:");
|
|
186
194
|
for (const pattern of DEFAULT_INIT_IGNORES) {
|
|
187
195
|
lines.push(` - "${pattern}"`);
|
|
@@ -545,7 +553,9 @@ var FileCoverageSchema = z2.object({
|
|
|
545
553
|
var CoverageTotalsSchema = z2.object({
|
|
546
554
|
executable: z2.number().int().nonnegative(),
|
|
547
555
|
covered: z2.number().int().nonnegative(),
|
|
548
|
-
pct: z2.number().min(0).max(100)
|
|
556
|
+
pct: z2.number().min(0).max(100),
|
|
557
|
+
/** Present when executable === 0 — not a 0% coverage result. */
|
|
558
|
+
empty: z2.literal(true).optional()
|
|
549
559
|
});
|
|
550
560
|
var ProjectTotalsSchema = CoverageTotalsSchema.extend({
|
|
551
561
|
delta: z2.number().nullable()
|
|
@@ -614,7 +624,14 @@ async function headSha(ctx) {
|
|
|
614
624
|
return (await ctx.git.revparse(["HEAD"])).trim();
|
|
615
625
|
}
|
|
616
626
|
async function unifiedDiff(ctx, base) {
|
|
617
|
-
|
|
627
|
+
try {
|
|
628
|
+
const mergeBase = (await ctx.git.raw(["merge-base", base, "HEAD"])).trim();
|
|
629
|
+
if (mergeBase) {
|
|
630
|
+
return ctx.git.diff([`${base}...HEAD`]);
|
|
631
|
+
}
|
|
632
|
+
} catch {
|
|
633
|
+
}
|
|
634
|
+
return ctx.git.diff([base, "HEAD"]);
|
|
618
635
|
}
|
|
619
636
|
async function remoteUrl(ctx, remote = "origin") {
|
|
620
637
|
return (await ctx.git.raw(["remote", "get-url", remote])).trim();
|
|
@@ -763,6 +780,10 @@ function assertWithinRoot(root, resolvedPath) {
|
|
|
763
780
|
}
|
|
764
781
|
|
|
765
782
|
// src/core/patch.ts
|
|
783
|
+
var EMPTY_PATCH_REASON = "no executable lines in the patch";
|
|
784
|
+
function isEmptyPatch(totals) {
|
|
785
|
+
return totals.executable === 0;
|
|
786
|
+
}
|
|
766
787
|
function pct(covered, executable) {
|
|
767
788
|
if (executable === 0) return 0;
|
|
768
789
|
return Math.round(covered / executable * 1e3) / 10;
|
|
@@ -862,10 +883,10 @@ function uncoveredRanges(file) {
|
|
|
862
883
|
function buildDiffOutput(args) {
|
|
863
884
|
const patch = computePatchCoverage(args.files, args.addedByFile);
|
|
864
885
|
const project = computeProjectCoverage(args.files);
|
|
865
|
-
const
|
|
866
|
-
|
|
867
|
-
...project.byFile.keys()
|
|
868
|
-
|
|
886
|
+
const emptyPatch = isEmptyPatch(patch.totals);
|
|
887
|
+
const fileNames = new Set(
|
|
888
|
+
emptyPatch ? [] : [...patch.byFile.keys(), ...project.byFile.keys()]
|
|
889
|
+
);
|
|
869
890
|
const files = [];
|
|
870
891
|
for (const name of fileNames) {
|
|
871
892
|
const file = args.files.find((f) => f.path === name);
|
|
@@ -882,7 +903,7 @@ function buildDiffOutput(args) {
|
|
|
882
903
|
schemaVersion: 1,
|
|
883
904
|
base: args.base,
|
|
884
905
|
head: args.head,
|
|
885
|
-
patch: patch.totals,
|
|
906
|
+
patch: emptyPatch ? { ...patch.totals, empty: true } : patch.totals,
|
|
886
907
|
project: { ...project.totals, delta: args.projectDelta ?? null },
|
|
887
908
|
files,
|
|
888
909
|
ignored: [...args.ignored]
|
|
@@ -1508,6 +1529,7 @@ function registerPushCommand(program2) {
|
|
|
1508
1529
|
|
|
1509
1530
|
// src/commands/doctor.ts
|
|
1510
1531
|
var TESTED_BIN_BASENAME_RE = /^tested(\.js)?$/;
|
|
1532
|
+
var MIN_NODE_MAJOR = 24;
|
|
1511
1533
|
function statusBadge(status) {
|
|
1512
1534
|
const kind = status === "pass" ? "pass" : status === "fail" ? "fail" : status === "warn" ? "warn" : "info";
|
|
1513
1535
|
return badge(kind);
|
|
@@ -1574,20 +1596,19 @@ async function runDoctor(deps) {
|
|
|
1574
1596
|
const checks = [];
|
|
1575
1597
|
const major = parseNodeMajor(nodeVersion);
|
|
1576
1598
|
const nodeDisplay = nodeVersion.replace(/^v/, "v");
|
|
1577
|
-
if (major !== null && major >=
|
|
1599
|
+
if (major !== null && major >= MIN_NODE_MAJOR) {
|
|
1578
1600
|
checks.push({
|
|
1579
1601
|
id: "node",
|
|
1580
1602
|
label: "Node.js",
|
|
1581
1603
|
status: "pass",
|
|
1582
|
-
detail: `${nodeDisplay} (>=
|
|
1604
|
+
detail: `${nodeDisplay} (>= ${MIN_NODE_MAJOR})`
|
|
1583
1605
|
});
|
|
1584
1606
|
} else {
|
|
1585
1607
|
checks.push({
|
|
1586
1608
|
id: "node",
|
|
1587
1609
|
label: "Node.js",
|
|
1588
|
-
status: "
|
|
1589
|
-
detail: `${nodeDisplay}
|
|
1590
|
-
optional: true
|
|
1610
|
+
status: "fail",
|
|
1611
|
+
detail: `${nodeDisplay} is below ${MIN_NODE_MAJOR}. Node >= ${MIN_NODE_MAJOR} required.`
|
|
1591
1612
|
});
|
|
1592
1613
|
}
|
|
1593
1614
|
let isRepo = false;
|
|
@@ -1809,7 +1830,7 @@ async function runDoctor(deps) {
|
|
|
1809
1830
|
optional: true
|
|
1810
1831
|
});
|
|
1811
1832
|
}
|
|
1812
|
-
const hardFailIds = /* @__PURE__ */ new Set(["git", "config", "origin"]);
|
|
1833
|
+
const hardFailIds = /* @__PURE__ */ new Set(["node", "git", "config", "origin"]);
|
|
1813
1834
|
const safetyFailIds = /* @__PURE__ */ new Set(["api_url", "tested_bin", "token"]);
|
|
1814
1835
|
const hasHardFail = checks.some(
|
|
1815
1836
|
(c) => c.status === "fail" && hardFailIds.has(c.id)
|
|
@@ -1854,6 +1875,73 @@ function registerDoctorCommand(program2) {
|
|
|
1854
1875
|
|
|
1855
1876
|
// src/commands/setup.ts
|
|
1856
1877
|
import pc3 from "picocolors";
|
|
1878
|
+
|
|
1879
|
+
// package.json
|
|
1880
|
+
var package_default = {
|
|
1881
|
+
name: "@tested/cli",
|
|
1882
|
+
version: "0.1.2",
|
|
1883
|
+
description: "Coverage your agent can use. CLI for patch + project coverage with agent-readable JSON output.",
|
|
1884
|
+
license: "MIT",
|
|
1885
|
+
homepage: "https://tested.dev",
|
|
1886
|
+
repository: {
|
|
1887
|
+
type: "git",
|
|
1888
|
+
url: "git+https://github.com/tested-hq/cli.git"
|
|
1889
|
+
},
|
|
1890
|
+
bugs: {
|
|
1891
|
+
url: "https://github.com/tested-hq/cli/issues"
|
|
1892
|
+
},
|
|
1893
|
+
publishConfig: {
|
|
1894
|
+
access: "public"
|
|
1895
|
+
},
|
|
1896
|
+
type: "module",
|
|
1897
|
+
packageManager: "pnpm@11.3.0",
|
|
1898
|
+
bin: {
|
|
1899
|
+
tested: "./dist/tested.js",
|
|
1900
|
+
td: "./dist/tested.js"
|
|
1901
|
+
},
|
|
1902
|
+
files: [
|
|
1903
|
+
"dist",
|
|
1904
|
+
"README.md",
|
|
1905
|
+
"LICENSE"
|
|
1906
|
+
],
|
|
1907
|
+
scripts: {
|
|
1908
|
+
build: "tsup",
|
|
1909
|
+
dev: "tsx bin/tested.ts",
|
|
1910
|
+
test: "vitest run",
|
|
1911
|
+
"test:watch": "vitest",
|
|
1912
|
+
"test:coverage": "vitest run --coverage",
|
|
1913
|
+
typecheck: "tsc --noEmit",
|
|
1914
|
+
prepare: "node ./scripts/prepare.mjs",
|
|
1915
|
+
prepublishOnly: "pnpm run build"
|
|
1916
|
+
},
|
|
1917
|
+
engines: {
|
|
1918
|
+
node: ">=24"
|
|
1919
|
+
},
|
|
1920
|
+
dependencies: {
|
|
1921
|
+
commander: "14.0.3",
|
|
1922
|
+
minimatch: "10.2.5",
|
|
1923
|
+
picocolors: "1.1.1",
|
|
1924
|
+
"simple-git": "3.36.0",
|
|
1925
|
+
yaml: "2.9.0",
|
|
1926
|
+
zod: "4.4.3"
|
|
1927
|
+
},
|
|
1928
|
+
devDependencies: {
|
|
1929
|
+
"@types/node": "25.9.1",
|
|
1930
|
+
"@vitest/coverage-v8": "4.1.7",
|
|
1931
|
+
husky: "9.1.7",
|
|
1932
|
+
"is-ci": "3.0.1",
|
|
1933
|
+
tsup: "8.5.1",
|
|
1934
|
+
tsx: "4.22.3",
|
|
1935
|
+
typescript: "6.0.3",
|
|
1936
|
+
vite: "8.0.14",
|
|
1937
|
+
vitest: "4.1.7"
|
|
1938
|
+
}
|
|
1939
|
+
};
|
|
1940
|
+
|
|
1941
|
+
// src/version.ts
|
|
1942
|
+
var CLI_VERSION = package_default.version;
|
|
1943
|
+
|
|
1944
|
+
// src/commands/setup.ts
|
|
1857
1945
|
var PINNED_CLI = "@tested/cli";
|
|
1858
1946
|
function buildCiSnippet() {
|
|
1859
1947
|
return [
|
|
@@ -1865,12 +1953,9 @@ function buildCiSnippet() {
|
|
|
1865
1953
|
" runs-on: ubuntu-latest",
|
|
1866
1954
|
" steps:",
|
|
1867
1955
|
" - uses: actions/checkout@v4",
|
|
1868
|
-
" with:",
|
|
1869
|
-
" fetch-depth: 0",
|
|
1870
1956
|
" - uses: tested-hq/cli/action@main",
|
|
1871
1957
|
" with:",
|
|
1872
|
-
|
|
1873
|
-
" cli-ref: main",
|
|
1958
|
+
` version: ${CLI_VERSION}`,
|
|
1874
1959
|
" push: true",
|
|
1875
1960
|
" pr-number: ${{ github.event.pull_request.number }}",
|
|
1876
1961
|
" token: ${{ secrets.TESTED_TOKEN }}"
|
|
@@ -1887,6 +1972,7 @@ function buildTokenInstructions() {
|
|
|
1887
1972
|
function buildInstallInstructions() {
|
|
1888
1973
|
return [
|
|
1889
1974
|
"Install CLI:",
|
|
1975
|
+
" Node 24+",
|
|
1890
1976
|
` pnpm add -D ${PINNED_CLI}`,
|
|
1891
1977
|
` # or: npx ${PINNED_CLI}`,
|
|
1892
1978
|
"",
|
|
@@ -1931,6 +2017,12 @@ function formatSetupHuman(opts) {
|
|
|
1931
2017
|
])
|
|
1932
2018
|
);
|
|
1933
2019
|
lines.push("");
|
|
2020
|
+
lines.push(
|
|
2021
|
+
dim(
|
|
2022
|
+
"Patch gate is skipped when the diff has no executable lines (tests-only, docs, comments)."
|
|
2023
|
+
)
|
|
2024
|
+
);
|
|
2025
|
+
lines.push("");
|
|
1934
2026
|
lines.push(tip("re-check anytime: tested doctor"));
|
|
1935
2027
|
lines.push("");
|
|
1936
2028
|
return lines.join("\n");
|
|
@@ -2196,8 +2288,8 @@ function coloredPctCell(pct3, width = 6) {
|
|
|
2196
2288
|
function formatMetricRow(label, pct3, covered, executable, emptyNote) {
|
|
2197
2289
|
const labelPad = label.padEnd(8);
|
|
2198
2290
|
if (executable === 0) {
|
|
2199
|
-
const note = emptyNote ??
|
|
2200
|
-
return ` ${labelPad} ${dim("-".padEnd(6))} ${
|
|
2291
|
+
const note = emptyNote ?? EMPTY_PATCH_REASON;
|
|
2292
|
+
return ` ${labelPad} ${dim("-".padEnd(6))} ${note}`;
|
|
2201
2293
|
}
|
|
2202
2294
|
const pctStr = coloredPctCell(pct3);
|
|
2203
2295
|
const bar = metricBar(pct3);
|
|
@@ -2216,7 +2308,7 @@ function formatHuman(out, opts = {}) {
|
|
|
2216
2308
|
out.patch.pct,
|
|
2217
2309
|
out.patch.covered,
|
|
2218
2310
|
out.patch.executable,
|
|
2219
|
-
|
|
2311
|
+
EMPTY_PATCH_REASON
|
|
2220
2312
|
)
|
|
2221
2313
|
);
|
|
2222
2314
|
const projectRow = formatMetricRow(
|
|
@@ -2229,7 +2321,7 @@ function formatHuman(out, opts = {}) {
|
|
|
2229
2321
|
if (opts.thresholds) {
|
|
2230
2322
|
const patchPass = out.patch.pct >= opts.thresholds.patch;
|
|
2231
2323
|
const projectPass = out.project.pct >= opts.thresholds.project;
|
|
2232
|
-
const patchOk = out.patch
|
|
2324
|
+
const patchOk = isEmptyPatch(out.patch) ? true : patchPass;
|
|
2233
2325
|
const overall = patchOk && projectPass;
|
|
2234
2326
|
const details = [];
|
|
2235
2327
|
if (!patchOk) {
|
|
@@ -2240,7 +2332,7 @@ function formatHuman(out, opts = {}) {
|
|
|
2240
2332
|
`project ${out.project.pct.toFixed(1)}% < ${opts.thresholds.project}%`
|
|
2241
2333
|
);
|
|
2242
2334
|
}
|
|
2243
|
-
const detail = details.length > 0 ? dim(` ${details.join("; ")}`) : "";
|
|
2335
|
+
const detail = details.length > 0 ? dim(` ${details.join("; ")}`) : isEmptyPatch(out.patch) ? dim(` ${EMPTY_PATCH_REASON}`) : "";
|
|
2244
2336
|
lines.push(
|
|
2245
2337
|
` ${"Gate".padEnd(8)} ${overall ? badge("pass") : badge("fail")}${detail}`
|
|
2246
2338
|
);
|
|
@@ -2253,12 +2345,15 @@ function formatHuman(out, opts = {}) {
|
|
|
2253
2345
|
);
|
|
2254
2346
|
}
|
|
2255
2347
|
}
|
|
2256
|
-
if (out.
|
|
2348
|
+
if (isEmptyPatch(out.patch)) {
|
|
2349
|
+
lines.push("");
|
|
2350
|
+
lines.push(dim("No executable lines in the patch \u2014 patch gate does not apply."));
|
|
2351
|
+
} else if (out.files.length > 0) {
|
|
2257
2352
|
lines.push("");
|
|
2258
2353
|
lines.push(heading("Files in diff:"));
|
|
2259
2354
|
const anyPatch = out.files.some((f) => f.patchCoverage !== null);
|
|
2260
2355
|
if (!anyPatch) {
|
|
2261
|
-
lines.push(dim(
|
|
2356
|
+
lines.push(dim(` (project coverage \u2014 ${EMPTY_PATCH_REASON})`));
|
|
2262
2357
|
}
|
|
2263
2358
|
for (const f of out.files) {
|
|
2264
2359
|
const hasPatch = f.patchCoverage !== null;
|
|
@@ -2344,7 +2439,7 @@ ${tip("add thresholds.patch / thresholds.project to enforce")}
|
|
|
2344
2439
|
const projectPct = diff.project.pct;
|
|
2345
2440
|
const patchThreshold = config.thresholds.patch;
|
|
2346
2441
|
const projectThreshold = config.thresholds.project;
|
|
2347
|
-
const patchSkipped = diff.patch
|
|
2442
|
+
const patchSkipped = isEmptyPatch(diff.patch);
|
|
2348
2443
|
const patchPass = patchSkipped ? true : patchPct >= patchThreshold;
|
|
2349
2444
|
const projectPass = projectPct >= projectThreshold;
|
|
2350
2445
|
const overall = patchPass && projectPass ? "pass" : "fail";
|
|
@@ -2355,10 +2450,11 @@ ${tip("add thresholds.patch / thresholds.project to enforce")}
|
|
|
2355
2450
|
pct: patchPct,
|
|
2356
2451
|
threshold: patchThreshold,
|
|
2357
2452
|
pass: patchPass,
|
|
2358
|
-
...patchSkipped ? { skipped: true } : {}
|
|
2453
|
+
...patchSkipped ? { skipped: true, reason: EMPTY_PATCH_REASON } : {}
|
|
2359
2454
|
},
|
|
2360
2455
|
project: { pct: projectPct, threshold: projectThreshold, pass: projectPass },
|
|
2361
|
-
overall
|
|
2456
|
+
overall,
|
|
2457
|
+
...patchSkipped ? { note: EMPTY_PATCH_REASON } : {}
|
|
2362
2458
|
};
|
|
2363
2459
|
return {
|
|
2364
2460
|
skipped: false,
|
|
@@ -2377,7 +2473,7 @@ ${tip("add thresholds.patch / thresholds.project to enforce")}
|
|
|
2377
2473
|
lines.push("");
|
|
2378
2474
|
if (patchSkipped) {
|
|
2379
2475
|
lines.push(
|
|
2380
|
-
` ${"Patch".padEnd(8)} ${dim("-")} ${
|
|
2476
|
+
` ${"Patch".padEnd(8)} ${dim("-")} ${EMPTY_PATCH_REASON} ${badge("skip")}`
|
|
2381
2477
|
);
|
|
2382
2478
|
} else {
|
|
2383
2479
|
lines.push(formatMetricLine("Patch", patchPct, patchThreshold, patchPass));
|
|
@@ -2385,10 +2481,17 @@ ${tip("add thresholds.patch / thresholds.project to enforce")}
|
|
|
2385
2481
|
lines.push(formatMetricLine("Project", projectPct, projectThreshold, projectPass));
|
|
2386
2482
|
if (overall === "fail") {
|
|
2387
2483
|
lines.push("");
|
|
2484
|
+
if (patchSkipped) {
|
|
2485
|
+
lines.push(dim("No executable lines in the patch \u2014 patch gate skipped."));
|
|
2486
|
+
}
|
|
2388
2487
|
lines.push(tip("add tests for uncovered ranges: tested diff"));
|
|
2389
2488
|
} else {
|
|
2390
2489
|
lines.push("");
|
|
2391
|
-
lines.push(
|
|
2490
|
+
lines.push(
|
|
2491
|
+
dim(
|
|
2492
|
+
patchSkipped ? "No executable lines in the patch \u2014 patch gate skipped. Project threshold met." : "thresholds met"
|
|
2493
|
+
)
|
|
2494
|
+
);
|
|
2392
2495
|
}
|
|
2393
2496
|
lines.push("");
|
|
2394
2497
|
return {
|
|
@@ -2561,7 +2664,7 @@ function createProgram() {
|
|
|
2561
2664
|
"Agent loop:",
|
|
2562
2665
|
" tested setup \u2192 tested run \u2192 tested diff \u2192 tested check \u2192 tested push --pr <n>"
|
|
2563
2666
|
].join("\n")
|
|
2564
|
-
).version(
|
|
2667
|
+
).version(CLI_VERSION);
|
|
2565
2668
|
registerSetupCommand(program2);
|
|
2566
2669
|
registerDoctorCommand(program2);
|
|
2567
2670
|
registerInitCommand(program2);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tested/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Coverage your agent can use. CLI for patch + project coverage with agent-readable JSON output.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://tested.dev",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"packageManager": "pnpm@11.3.0",
|
|
19
19
|
"bin": {
|
|
20
20
|
"tested": "./dist/tested.js",
|
|
21
|
-
"td": "./dist/
|
|
21
|
+
"td": "./dist/tested.js"
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
24
|
"dist",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"prepublishOnly": "pnpm run build"
|
|
37
37
|
},
|
|
38
38
|
"engines": {
|
|
39
|
-
"node": ">=
|
|
39
|
+
"node": ">=24"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"commander": "14.0.3",
|