@biffo/cli 0.296.10 → 0.296.11

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.
@@ -418,16 +418,47 @@ def main() -> int:
418
418
  file=sys.stderr,
419
419
  )
420
420
 
421
+ reports = [json.loads(p.read_text()) for p in present]
422
+ coverage = merge_coverage(reports)
423
+ file_count = len(coverage.get("files", {}))
424
+
421
425
  # Restated in every summary line below (not just the warning above) so the
422
426
  # denominator survives even if the warning scrolls past unread: a reader
423
427
  # who sees only the final line still learns how much of the requested
424
- # picture this result is actually over.
425
- coverage_note = f"{len(present)}/{len(coverage_paths)} coverage artefact(s)"
428
+ # picture this result is actually over. The file count is part of that
429
+ # denominator, not just the artefact count — see the fail-closed check
430
+ # immediately below for why (#1657).
431
+ coverage_note = (
432
+ f"{len(present)}/{len(coverage_paths)} coverage artefact(s), {file_count} file(s)"
433
+ )
426
434
  if absent:
427
435
  coverage_note += f" — MISSING: {', '.join(str(p) for p in absent)}"
428
436
 
429
- reports = [json.loads(p.read_text()) for p in present]
430
- coverage = merge_coverage(reports)
437
+ if file_count == 0:
438
+ # #1657: an artefact can be present and valid JSON while still
439
+ # describing NOTHING — `{"files": {}}`, or `files` absent entirely.
440
+ # That reads identically to "everything is covered" to the logic
441
+ # below (found == [], keys == [], nothing NEW), so --check would
442
+ # exit 0 over a report that examined zero files: a fail-open in the
443
+ # gate that exists specifically to catch fail-opens. Every other
444
+ # soft-open path here (no baseline, a missing --coverage file)
445
+ # announces itself loudly and still proceeds; this one cannot
446
+ # proceed at all, because there is no denominator left to reason
447
+ # over. Fail closed rather than fail open: refuse both --write
448
+ # (never baseline a measurement of nothing) and --check (never let
449
+ # a growth check pass over nothing to check).
450
+ print(
451
+ f"::error::error-branch coverage: {coverage_note} — the merged "
452
+ "coverage report describes 0 files. Refusing to treat that as "
453
+ "clean: a report with nothing in it is not evidence the tree is "
454
+ "covered, it is evidence nothing was measured (a "
455
+ "[tool.coverage.run] source pointed at the wrong tree, a "
456
+ "truncated or hand-staged artefact, a coverage run against an "
457
+ "empty package). Fix the coverage run and try again.",
458
+ file=sys.stderr,
459
+ )
460
+ return 2
461
+
431
462
  found = unexecuted(coverage, source_root)
432
463
  keys = sorted({b.key() for b in found})
433
464
 
@@ -436,7 +467,7 @@ def main() -> int:
436
467
  baseline_path.write_text(
437
468
  json.dumps({"total": len(keys), "branches": keys}, indent=2) + "\n",
438
469
  )
439
- print(f"baseline written: {len(keys)} unexecuted error branches")
470
+ print(f"baseline written: {len(keys)} unexecuted error branches ({coverage_note})")
440
471
  return 0
441
472
 
442
473
  baseline = load_baseline(baseline_path)
@@ -418,16 +418,47 @@ def main() -> int:
418
418
  file=sys.stderr,
419
419
  )
420
420
 
421
+ reports = [json.loads(p.read_text()) for p in present]
422
+ coverage = merge_coverage(reports)
423
+ file_count = len(coverage.get("files", {}))
424
+
421
425
  # Restated in every summary line below (not just the warning above) so the
422
426
  # denominator survives even if the warning scrolls past unread: a reader
423
427
  # who sees only the final line still learns how much of the requested
424
- # picture this result is actually over.
425
- coverage_note = f"{len(present)}/{len(coverage_paths)} coverage artefact(s)"
428
+ # picture this result is actually over. The file count is part of that
429
+ # denominator, not just the artefact count — see the fail-closed check
430
+ # immediately below for why (#1657).
431
+ coverage_note = (
432
+ f"{len(present)}/{len(coverage_paths)} coverage artefact(s), {file_count} file(s)"
433
+ )
426
434
  if absent:
427
435
  coverage_note += f" — MISSING: {', '.join(str(p) for p in absent)}"
428
436
 
429
- reports = [json.loads(p.read_text()) for p in present]
430
- coverage = merge_coverage(reports)
437
+ if file_count == 0:
438
+ # #1657: an artefact can be present and valid JSON while still
439
+ # describing NOTHING — `{"files": {}}`, or `files` absent entirely.
440
+ # That reads identically to "everything is covered" to the logic
441
+ # below (found == [], keys == [], nothing NEW), so --check would
442
+ # exit 0 over a report that examined zero files: a fail-open in the
443
+ # gate that exists specifically to catch fail-opens. Every other
444
+ # soft-open path here (no baseline, a missing --coverage file)
445
+ # announces itself loudly and still proceeds; this one cannot
446
+ # proceed at all, because there is no denominator left to reason
447
+ # over. Fail closed rather than fail open: refuse both --write
448
+ # (never baseline a measurement of nothing) and --check (never let
449
+ # a growth check pass over nothing to check).
450
+ print(
451
+ f"::error::error-branch coverage: {coverage_note} — the merged "
452
+ "coverage report describes 0 files. Refusing to treat that as "
453
+ "clean: a report with nothing in it is not evidence the tree is "
454
+ "covered, it is evidence nothing was measured (a "
455
+ "[tool.coverage.run] source pointed at the wrong tree, a "
456
+ "truncated or hand-staged artefact, a coverage run against an "
457
+ "empty package). Fix the coverage run and try again.",
458
+ file=sys.stderr,
459
+ )
460
+ return 2
461
+
431
462
  found = unexecuted(coverage, source_root)
432
463
  keys = sorted({b.key() for b in found})
433
464
 
@@ -436,7 +467,7 @@ def main() -> int:
436
467
  baseline_path.write_text(
437
468
  json.dumps({"total": len(keys), "branches": keys}, indent=2) + "\n",
438
469
  )
439
- print(f"baseline written: {len(keys)} unexecuted error branches")
470
+ print(f"baseline written: {len(keys)} unexecuted error branches ({coverage_note})")
440
471
  return 0
441
472
 
442
473
  baseline = load_baseline(baseline_path)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@biffo/cli",
3
- "version": "0.296.10",
3
+ "version": "0.296.11",
4
4
  "description": "Biffo project scaffolding CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",