@tested/cli 0.1.8 → 0.1.9

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 CHANGED
@@ -25,7 +25,7 @@ CI uses the composite Action (`tested-hq/cli/action@main`). It installs `@tested
25
25
  ```yaml
26
26
  - uses: tested-hq/cli/action@main
27
27
  with:
28
- version: 0.1.8
28
+ version: 0.1.9
29
29
  token: ${{ secrets.TESTED_TOKEN }}
30
30
  ```
31
31
 
@@ -132,7 +132,7 @@ If `thresholds` is missing from `.tested.yaml`, `tested check` prints a notice o
132
132
 
133
133
  ### Flags (per package)
134
134
 
135
- Independent floors so a monorepo total cannot hide one package. Each flag is graded from **this run's** coverage files — no carryforward.
135
+ Independent floors so a monorepo total cannot hide one package. Each flag is graded from **this run's** coverage files.
136
136
 
137
137
  ```yaml
138
138
  flags:
@@ -144,11 +144,11 @@ flags:
144
144
  paths: ["apps/api/**"]
145
145
  ```
146
146
 
147
- `tested check` applies global `thresholds` plus each flag whose paths appear in the merged coverage. Per-flag patch is new executable lines in those paths. A flag with no files this run is **missing** (fail / pending) — never last week's numbers.
147
+ `tested check` applies global `thresholds` plus each flag whose paths appear in the merged coverage. Per-flag patch is new executable lines in those paths. A flag with no files this run is **skipped** (not 0%) — an affected-graph or scoped job that did not collect that package is not a fail.
148
148
 
149
- A job already scoped to one package: `tested check --flag frontend` (Action `flag:`). That coverage file **is** the flag.
149
+ A job already scoped to one package: `tested check --flag frontend` (Action `flag:`). That coverage file **is** the flag. Other packages are omitted from this upload.
150
150
 
151
- `--json` lists per-flag results (`flags.frontend.patchCheck` → `tested.dev / patch / frontend`). `tested push` sends that same `flags` map on ingest so the app can post those GitHub checks. `tested diff --json` includes it too.
151
+ `--json` lists per-flag results (`flags.frontend.patchCheck` → `tested.dev / patch / frontend`). Skipped flags have `status: missing` and `skipped: true` with no `executable` / `pct`. `tested push` omits skipped flags from ingest so the last successful upload for that package still stands. `tested diff --json` includes the same map as check.
152
152
 
153
153
  ## Coverage formats
154
154
 
@@ -195,7 +195,7 @@ Or pass `--file` repeatedly / Action `files`. A CI matrix must not conclude the
195
195
  ```yaml
196
196
  - uses: tested-hq/cli/action@main
197
197
  with:
198
- version: 0.1.8
198
+ version: 0.1.9
199
199
  push: true
200
200
  pr-number: ${{ github.event.pull_request.number }}
201
201
  token: ${{ secrets.TESTED_TOKEN }}
package/dist/td.js CHANGED
@@ -1129,7 +1129,7 @@ var TestedConfigSchema = z2.object({
1129
1129
  }).optional(),
1130
1130
  /**
1131
1131
  * Per-package gates. Each flag is graded from this run's coverage files
1132
- * only — a missing path is missing (pending/fail), never last week's totals.
1132
+ * only — a missing path is skipped (not 0%), never another flag's totals.
1133
1133
  */
1134
1134
  flags: z2.record(FlagNameSchema, FlagConfigSchema).optional()
1135
1135
  });
@@ -1164,17 +1164,21 @@ var DiffOutputSchema = z2.object({
1164
1164
  ignored: z2.array(z2.string())
1165
1165
  });
1166
1166
  var FlagMetricJsonSchema = z2.object({
1167
- pct: z2.number().min(0).max(100),
1167
+ /** Omitted when this flag had no files this run (`skipped: true`). */
1168
+ pct: z2.number().min(0).max(100).optional(),
1168
1169
  threshold: z2.number().min(0).max(100),
1169
- pass: z2.boolean(),
1170
- executable: z2.number().int().nonnegative(),
1171
- covered: z2.number().int().nonnegative(),
1170
+ /** Omitted when skipped — a missing flag is not a 0% fail. */
1171
+ pass: z2.boolean().optional(),
1172
+ executable: z2.number().int().nonnegative().optional(),
1173
+ covered: z2.number().int().nonnegative().optional(),
1172
1174
  skipped: z2.literal(true).optional(),
1173
1175
  reason: z2.string().optional()
1174
1176
  });
1175
1177
  var FlagResultJsonSchema = z2.object({
1176
1178
  status: z2.enum(["pass", "fail", "missing"]),
1177
1179
  present: z2.boolean(),
1180
+ /** True when this flag had no coverage files this run (not a 0% result). */
1181
+ skipped: z2.literal(true).optional(),
1178
1182
  reason: z2.string().optional(),
1179
1183
  patchCheck: z2.string(),
1180
1184
  projectCheck: z2.string(),
@@ -1742,13 +1746,11 @@ function totalsToMetric(totals, threshold, kind) {
1742
1746
  covered: totals.covered
1743
1747
  };
1744
1748
  }
1745
- function missingMetric(threshold) {
1749
+ function missingMetric(threshold, reason) {
1746
1750
  return {
1747
- pct: 0,
1748
1751
  threshold,
1749
- pass: false,
1750
- executable: 0,
1751
- covered: 0
1752
+ skipped: true,
1753
+ reason
1752
1754
  };
1753
1755
  }
1754
1756
  function checkSlug(kind, name) {
@@ -1778,8 +1780,8 @@ function missingFlag(name, thresholds, reason) {
1778
1780
  reason,
1779
1781
  patchCheck: checkSlug("patch", name),
1780
1782
  projectCheck: checkSlug("project", name),
1781
- patch: missingMetric(thresholds.patch),
1782
- project: missingMetric(thresholds.project)
1783
+ patch: missingMetric(thresholds.patch, reason),
1784
+ project: missingMetric(thresholds.project, reason)
1783
1785
  };
1784
1786
  }
1785
1787
  function evaluateFlags(input) {
@@ -1810,7 +1812,7 @@ function evaluateFlags(input) {
1810
1812
  return results;
1811
1813
  }
1812
1814
  function flagsPass(results) {
1813
- return results.every((f) => f.status === "pass");
1815
+ return results.every((f) => f.status !== "fail");
1814
1816
  }
1815
1817
  function flagsToJson(results) {
1816
1818
  const out = {};
@@ -1818,19 +1820,44 @@ function flagsToJson(results) {
1818
1820
  out[flag.name] = {
1819
1821
  status: flag.status,
1820
1822
  present: flag.present,
1823
+ ...flag.status === "missing" ? { skipped: true } : {},
1821
1824
  ...flag.reason ? { reason: flag.reason } : {},
1822
1825
  patchCheck: flag.patchCheck,
1823
1826
  projectCheck: flag.projectCheck,
1824
- patch: flag.patch,
1825
- project: flag.project
1827
+ patch: metricToJson(flag.patch),
1828
+ project: metricToJson(flag.project)
1826
1829
  };
1827
1830
  }
1828
1831
  return out;
1829
1832
  }
1833
+ function metricToJson(metric) {
1834
+ if (metric.skipped) {
1835
+ return {
1836
+ threshold: metric.threshold,
1837
+ skipped: true,
1838
+ ...metric.reason ? { reason: metric.reason } : {}
1839
+ };
1840
+ }
1841
+ return {
1842
+ pct: metric.pct ?? 0,
1843
+ threshold: metric.threshold,
1844
+ pass: metric.pass ?? false,
1845
+ executable: metric.executable ?? 0,
1846
+ covered: metric.covered ?? 0,
1847
+ ...metric.reason ? { reason: metric.reason } : {}
1848
+ };
1849
+ }
1850
+ function flagsToIngestJson(results) {
1851
+ const present = results.filter((f) => f.present);
1852
+ return present.length > 0 ? flagsToJson(present) : void 0;
1853
+ }
1830
1854
  function resolveFlagsJson(input) {
1831
1855
  const results = evaluateFlags(input);
1832
1856
  return results.length > 0 ? flagsToJson(results) : void 0;
1833
1857
  }
1858
+ function resolveIngestFlagsJson(input) {
1859
+ return flagsToIngestJson(evaluateFlags(input));
1860
+ }
1834
1861
 
1835
1862
  // src/core/coverage-merge.ts
1836
1863
  function emptyToUndef(value) {
@@ -2553,7 +2580,7 @@ async function executePush(cli, deps) {
2553
2580
  let flags;
2554
2581
  if (!handshakeOnly && !computeDiffFn) {
2555
2582
  try {
2556
- flags = resolveFlagsJson({
2583
+ flags = resolveIngestFlagsJson({
2557
2584
  config,
2558
2585
  files,
2559
2586
  addedByFile,
@@ -3072,7 +3099,7 @@ import pc3 from "picocolors";
3072
3099
  // package.json
3073
3100
  var package_default = {
3074
3101
  name: "@tested/cli",
3075
- version: "0.1.8",
3102
+ version: "0.1.9",
3076
3103
  description: "Coverage your agent can use. CLI for patch + project coverage with agent-readable JSON output.",
3077
3104
  license: "MIT",
3078
3105
  homepage: "https://tested.dev",
@@ -3718,6 +3745,12 @@ function formatMetricLine(label, pct3, threshold, pass, indent = " ") {
3718
3745
  const status = pass ? badge("pass") : badge("fail");
3719
3746
  return `${indent}${label.padEnd(8)} ${pctStr}% (threshold ${threshold}) ${status}`;
3720
3747
  }
3748
+ function presentPct(metric) {
3749
+ if (metric.pct === void 0 || metric.pass === void 0) {
3750
+ throw new Error("present flag metric missing pct/pass");
3751
+ }
3752
+ return { pct: metric.pct, pass: metric.pass };
3753
+ }
3721
3754
  function formatFlagLines(results) {
3722
3755
  if (results.length === 0) return [];
3723
3756
  const lines = [""];
@@ -3734,16 +3767,18 @@ function formatFlagLines(results) {
3734
3767
  ` ${"Patch".padEnd(8)} ${dim("-")} ${EMPTY_PATCH_REASON} ${badge("skip")}`
3735
3768
  );
3736
3769
  } else {
3770
+ const patch = presentPct(flag.patch);
3737
3771
  lines.push(
3738
- formatMetricLine("Patch", flag.patch.pct, flag.patch.threshold, flag.patch.pass, " ")
3772
+ formatMetricLine("Patch", patch.pct, flag.patch.threshold, patch.pass, " ")
3739
3773
  );
3740
3774
  }
3775
+ const project = presentPct(flag.project);
3741
3776
  lines.push(
3742
3777
  formatMetricLine(
3743
3778
  "Project",
3744
- flag.project.pct,
3779
+ project.pct,
3745
3780
  flag.project.threshold,
3746
- flag.project.pass,
3781
+ project.pass,
3747
3782
  " "
3748
3783
  )
3749
3784
  );
@@ -3831,7 +3866,7 @@ ${tip("add thresholds.patch / thresholds.project to enforce")}
3831
3866
  if (missing.length > 0) {
3832
3867
  lines.push(
3833
3868
  dim(
3834
- "Missing flags fail this run (no carryforward). Collect that package's coverage or scope the job with --flag."
3869
+ "Flags with no files this run are skipped (not 0%). Scope the job with --flag when this coverage file is one package."
3835
3870
  )
3836
3871
  );
3837
3872
  }
package/dist/tested.js CHANGED
@@ -1129,7 +1129,7 @@ var TestedConfigSchema = z2.object({
1129
1129
  }).optional(),
1130
1130
  /**
1131
1131
  * Per-package gates. Each flag is graded from this run's coverage files
1132
- * only — a missing path is missing (pending/fail), never last week's totals.
1132
+ * only — a missing path is skipped (not 0%), never another flag's totals.
1133
1133
  */
1134
1134
  flags: z2.record(FlagNameSchema, FlagConfigSchema).optional()
1135
1135
  });
@@ -1164,17 +1164,21 @@ var DiffOutputSchema = z2.object({
1164
1164
  ignored: z2.array(z2.string())
1165
1165
  });
1166
1166
  var FlagMetricJsonSchema = z2.object({
1167
- pct: z2.number().min(0).max(100),
1167
+ /** Omitted when this flag had no files this run (`skipped: true`). */
1168
+ pct: z2.number().min(0).max(100).optional(),
1168
1169
  threshold: z2.number().min(0).max(100),
1169
- pass: z2.boolean(),
1170
- executable: z2.number().int().nonnegative(),
1171
- covered: z2.number().int().nonnegative(),
1170
+ /** Omitted when skipped — a missing flag is not a 0% fail. */
1171
+ pass: z2.boolean().optional(),
1172
+ executable: z2.number().int().nonnegative().optional(),
1173
+ covered: z2.number().int().nonnegative().optional(),
1172
1174
  skipped: z2.literal(true).optional(),
1173
1175
  reason: z2.string().optional()
1174
1176
  });
1175
1177
  var FlagResultJsonSchema = z2.object({
1176
1178
  status: z2.enum(["pass", "fail", "missing"]),
1177
1179
  present: z2.boolean(),
1180
+ /** True when this flag had no coverage files this run (not a 0% result). */
1181
+ skipped: z2.literal(true).optional(),
1178
1182
  reason: z2.string().optional(),
1179
1183
  patchCheck: z2.string(),
1180
1184
  projectCheck: z2.string(),
@@ -1742,13 +1746,11 @@ function totalsToMetric(totals, threshold, kind) {
1742
1746
  covered: totals.covered
1743
1747
  };
1744
1748
  }
1745
- function missingMetric(threshold) {
1749
+ function missingMetric(threshold, reason) {
1746
1750
  return {
1747
- pct: 0,
1748
1751
  threshold,
1749
- pass: false,
1750
- executable: 0,
1751
- covered: 0
1752
+ skipped: true,
1753
+ reason
1752
1754
  };
1753
1755
  }
1754
1756
  function checkSlug(kind, name) {
@@ -1778,8 +1780,8 @@ function missingFlag(name, thresholds, reason) {
1778
1780
  reason,
1779
1781
  patchCheck: checkSlug("patch", name),
1780
1782
  projectCheck: checkSlug("project", name),
1781
- patch: missingMetric(thresholds.patch),
1782
- project: missingMetric(thresholds.project)
1783
+ patch: missingMetric(thresholds.patch, reason),
1784
+ project: missingMetric(thresholds.project, reason)
1783
1785
  };
1784
1786
  }
1785
1787
  function evaluateFlags(input) {
@@ -1810,7 +1812,7 @@ function evaluateFlags(input) {
1810
1812
  return results;
1811
1813
  }
1812
1814
  function flagsPass(results) {
1813
- return results.every((f) => f.status === "pass");
1815
+ return results.every((f) => f.status !== "fail");
1814
1816
  }
1815
1817
  function flagsToJson(results) {
1816
1818
  const out = {};
@@ -1818,19 +1820,44 @@ function flagsToJson(results) {
1818
1820
  out[flag.name] = {
1819
1821
  status: flag.status,
1820
1822
  present: flag.present,
1823
+ ...flag.status === "missing" ? { skipped: true } : {},
1821
1824
  ...flag.reason ? { reason: flag.reason } : {},
1822
1825
  patchCheck: flag.patchCheck,
1823
1826
  projectCheck: flag.projectCheck,
1824
- patch: flag.patch,
1825
- project: flag.project
1827
+ patch: metricToJson(flag.patch),
1828
+ project: metricToJson(flag.project)
1826
1829
  };
1827
1830
  }
1828
1831
  return out;
1829
1832
  }
1833
+ function metricToJson(metric) {
1834
+ if (metric.skipped) {
1835
+ return {
1836
+ threshold: metric.threshold,
1837
+ skipped: true,
1838
+ ...metric.reason ? { reason: metric.reason } : {}
1839
+ };
1840
+ }
1841
+ return {
1842
+ pct: metric.pct ?? 0,
1843
+ threshold: metric.threshold,
1844
+ pass: metric.pass ?? false,
1845
+ executable: metric.executable ?? 0,
1846
+ covered: metric.covered ?? 0,
1847
+ ...metric.reason ? { reason: metric.reason } : {}
1848
+ };
1849
+ }
1850
+ function flagsToIngestJson(results) {
1851
+ const present = results.filter((f) => f.present);
1852
+ return present.length > 0 ? flagsToJson(present) : void 0;
1853
+ }
1830
1854
  function resolveFlagsJson(input) {
1831
1855
  const results = evaluateFlags(input);
1832
1856
  return results.length > 0 ? flagsToJson(results) : void 0;
1833
1857
  }
1858
+ function resolveIngestFlagsJson(input) {
1859
+ return flagsToIngestJson(evaluateFlags(input));
1860
+ }
1834
1861
 
1835
1862
  // src/core/coverage-merge.ts
1836
1863
  function emptyToUndef(value) {
@@ -2553,7 +2580,7 @@ async function executePush(cli, deps) {
2553
2580
  let flags;
2554
2581
  if (!handshakeOnly && !computeDiffFn) {
2555
2582
  try {
2556
- flags = resolveFlagsJson({
2583
+ flags = resolveIngestFlagsJson({
2557
2584
  config,
2558
2585
  files,
2559
2586
  addedByFile,
@@ -3072,7 +3099,7 @@ import pc3 from "picocolors";
3072
3099
  // package.json
3073
3100
  var package_default = {
3074
3101
  name: "@tested/cli",
3075
- version: "0.1.8",
3102
+ version: "0.1.9",
3076
3103
  description: "Coverage your agent can use. CLI for patch + project coverage with agent-readable JSON output.",
3077
3104
  license: "MIT",
3078
3105
  homepage: "https://tested.dev",
@@ -3718,6 +3745,12 @@ function formatMetricLine(label, pct3, threshold, pass, indent = " ") {
3718
3745
  const status = pass ? badge("pass") : badge("fail");
3719
3746
  return `${indent}${label.padEnd(8)} ${pctStr}% (threshold ${threshold}) ${status}`;
3720
3747
  }
3748
+ function presentPct(metric) {
3749
+ if (metric.pct === void 0 || metric.pass === void 0) {
3750
+ throw new Error("present flag metric missing pct/pass");
3751
+ }
3752
+ return { pct: metric.pct, pass: metric.pass };
3753
+ }
3721
3754
  function formatFlagLines(results) {
3722
3755
  if (results.length === 0) return [];
3723
3756
  const lines = [""];
@@ -3734,16 +3767,18 @@ function formatFlagLines(results) {
3734
3767
  ` ${"Patch".padEnd(8)} ${dim("-")} ${EMPTY_PATCH_REASON} ${badge("skip")}`
3735
3768
  );
3736
3769
  } else {
3770
+ const patch = presentPct(flag.patch);
3737
3771
  lines.push(
3738
- formatMetricLine("Patch", flag.patch.pct, flag.patch.threshold, flag.patch.pass, " ")
3772
+ formatMetricLine("Patch", patch.pct, flag.patch.threshold, patch.pass, " ")
3739
3773
  );
3740
3774
  }
3775
+ const project = presentPct(flag.project);
3741
3776
  lines.push(
3742
3777
  formatMetricLine(
3743
3778
  "Project",
3744
- flag.project.pct,
3779
+ project.pct,
3745
3780
  flag.project.threshold,
3746
- flag.project.pass,
3781
+ project.pass,
3747
3782
  " "
3748
3783
  )
3749
3784
  );
@@ -3831,7 +3866,7 @@ ${tip("add thresholds.patch / thresholds.project to enforce")}
3831
3866
  if (missing.length > 0) {
3832
3867
  lines.push(
3833
3868
  dim(
3834
- "Missing flags fail this run (no carryforward). Collect that package's coverage or scope the job with --flag."
3869
+ "Flags with no files this run are skipped (not 0%). Scope the job with --flag when this coverage file is one package."
3835
3870
  )
3836
3871
  );
3837
3872
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tested/cli",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
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",