@andresmassello/uscha 1.94.0 → 1.95.0

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
@@ -40,7 +40,7 @@ Requires **Python 3.8+** on the machine (the engine is Python stdlib — no pip
40
40
  runtime dependencies). The npm package is a thin router; the canonical installer is
41
41
  `uscha-kit/install-uscha.py`.
42
42
 
43
- **Kit v1.94.0** <!-- uscha:version --> · [uscha.dev](https://uscha.dev) ·
43
+ **Kit v1.95.0** <!-- uscha:version --> · [uscha.dev](https://uscha.dev) ·
44
44
  [changelog](https://github.com/andresmassello/uscha/blob/main/uscha-kit/CHANGELOG.md)
45
45
  (the per-release changelogs live in the repo, not in the npm tarball)
46
46
 
@@ -85,7 +85,7 @@ automatic tool can perform: a human verdict.
85
85
  from the compiled code: 0.828 measured (12 archetypes) — names AND behaviour
86
86
  ```
87
87
 
88
- **What each arrow is, in the engine (kit 1.94.0, 53 subcommands, all measured):**
88
+ **What each arrow is, in the engine (kit 1.95.0, 53 subcommands, all measured):**
89
89
 
90
90
  | Leg | Subcommands | What it establishes |
91
91
  |---|---|---|
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andresmassello/uscha",
3
- "version": "1.94.0",
3
+ "version": "1.95.0",
4
4
  "description": "Spec-driven development for LLM coding agents: 9 skills + a stdlib evidence engine. Facts block, guesses advise; the human approves.",
5
5
  "author": {
6
6
  "name": "Andres Massello",
@@ -206,17 +206,51 @@ def _repo_cfg(ledger, name):
206
206
  # measurement: coverage
207
207
  # --------------------------------------------------------------------------- #
208
208
  def _jacoco_line_counter(xml_path):
209
- """Return (missed, covered) for the report-level LINE counter."""
209
+ """Return (missed, covered) for the report-level LINE counter, or None when the report
210
+ could not be READ. The distinction is the whole point: a report that measured nothing
211
+ must never be summed as (0, 0) — that is an invented number, and with one module's XML
212
+ truncated mid-write it made the surviving modules' percentage look like the project's
213
+ (audit 1.94.1). Absence is never invented as a number — the rule `cobertura_coverage`
214
+ already states, applied to the JaCoCo readers too. A report with no LINE counter at all
215
+ is a real, readable zero and stays (0, 0)."""
210
216
  try:
211
217
  root = _parse_xml(xml_path).getroot()
212
- except ET.ParseError:
213
- return 0, 0
218
+ except (ET.ParseError, OSError, ReportTooLarge):
219
+ return None
214
220
  for c in root.findall("counter"):
215
221
  if c.get("type") == "LINE":
216
- return int(c.get("missed", 0)), int(c.get("covered", 0))
222
+ try:
223
+ return int(c.get("missed", 0)), int(c.get("covered", 0))
224
+ except (TypeError, ValueError):
225
+ return None # a counter that is not a number measured nothing
217
226
  return 0, 0
218
227
 
219
228
 
229
+ def _jacoco_result(files):
230
+ """Sum the LINE counters of `files`, or report the WHOLE reading unmeasured. Shared by
231
+ maven/gradle/ant: three hand-copied versions of this sum is how one of them would drift.
232
+
233
+ ONE unreadable report makes the reading unmeasured, and an unmeasured reading is
234
+ (0, 0, 0.0) -- not the survivors' percentage with a false flag beside it. Every other
235
+ reader in this file returns pct 0.0 whenever report_found is False, and readers of the
236
+ result rely on it: `readiness` scores coverage as pct/threshold WITHOUT consulting
237
+ report_found, and `snapshot` prints and persists the pct next to the flag. Handing them
238
+ the surviving modules' number would score a project on a question nobody asked
239
+ (audit 1.94.1). The report paths are still listed -- the operator needs to know which
240
+ files were looked at."""
241
+ parsed = [_jacoco_line_counter(f) for f in files]
242
+ reports = [f.replace("\\", "/") for f in files]
243
+ if not files or any(p is None for p in parsed):
244
+ return {"covered": 0, "missed": 0, "pct": 0.0, "report_found": False,
245
+ "reports": reports}
246
+ missed = sum(p[0] for p in parsed)
247
+ covered = sum(p[1] for p in parsed)
248
+ total = missed + covered
249
+ pct = round(covered / total * 100, 2) if total else 0.0
250
+ return {"covered": covered, "missed": missed, "pct": pct,
251
+ "report_found": True, "reports": reports}
252
+
253
+
220
254
  def maven_coverage(repo_path):
221
255
  """
222
256
  Prefer an aggregate report if present, else sum per-module reports.
@@ -231,16 +265,7 @@ def maven_coverage(repo_path):
231
265
  files = glob.glob(os.path.join(repo_path, "**", "target", "site",
232
266
  "jacoco", "jacoco.xml"),
233
267
  recursive=True)
234
- missed = covered = 0
235
- for f in files:
236
- m, c = _jacoco_line_counter(f)
237
- missed += m
238
- covered += c
239
- total = missed + covered
240
- pct = round(covered / total * 100, 2) if total else 0.0
241
- return {"covered": covered, "missed": missed, "pct": pct,
242
- "report_found": bool(files),
243
- "reports": [f.replace("\\", "/") for f in files]}
268
+ return _jacoco_result(files)
244
269
 
245
270
 
246
271
  def flutter_coverage(repo_path):
@@ -343,16 +368,7 @@ def gradle_coverage(repo_path):
343
368
  if not files:
344
369
  files = glob.glob(os.path.join(repo_path, "**", "build", "reports",
345
370
  "jacoco", "jacoco.xml"), recursive=True)
346
- missed = covered = 0
347
- for f in files:
348
- m, c = _jacoco_line_counter(f)
349
- missed += m
350
- covered += c
351
- total = missed + covered
352
- pct = round(covered / total * 100, 2) if total else 0.0
353
- return {"covered": covered, "missed": missed, "pct": pct,
354
- "report_found": bool(files),
355
- "reports": [f.replace("\\", "/") for f in files]}
371
+ return _jacoco_result(files)
356
372
 
357
373
 
358
374
  def ant_coverage(repo_path):
@@ -360,16 +376,7 @@ def ant_coverage(repo_path):
360
376
  report task writes wherever the build file says -- so the report is discovered
361
377
  RECURSIVELY by name instead of guessing one convention."""
362
378
  files = _ant_reports(repo_path, "jacoco.xml")
363
- missed = covered = 0
364
- for f in files:
365
- m, c = _jacoco_line_counter(f)
366
- missed += m
367
- covered += c
368
- total = missed + covered
369
- pct = round(covered / total * 100, 2) if total else 0.0
370
- return {"covered": covered, "missed": missed, "pct": pct,
371
- "report_found": bool(files),
372
- "reports": [f.replace("\\", "/") for f in files]}
379
+ return _jacoco_result(files)
373
380
 
374
381
 
375
382
  def coverage(repo_path, repo_type):
@@ -1276,7 +1283,15 @@ def _mk_id(tool, rule, fname, line, granularity):
1276
1283
 
1277
1284
  def _find_all(base, patterns, explicit):
1278
1285
  if explicit:
1279
- return [explicit] if os.path.exists(explicit) else []
1286
+ if not os.path.exists(explicit):
1287
+ # An EXPLICIT path is a claim the operator made about where the report is. When
1288
+ # it is not there, the honest reading is a typo or a build that never wrote it --
1289
+ # not "this linter has no findings". Returning [] made ingest-gate log nothing
1290
+ # and exit 0, so a mistyped --ruff read as a clean gate (audit 1.94.1). Same
1291
+ # fail-closed exit an unparseable report already gets, with the path named.
1292
+ _invalid_static_report(explicit, "linter",
1293
+ "no such file (explicit path given, nothing to ingest)")
1294
+ return [explicit]
1280
1295
  found = []
1281
1296
  for pat in patterns:
1282
1297
  found += glob.glob(os.path.join(base, pat), recursive=True)
@@ -3107,7 +3122,7 @@ def cmd_fastpath_eval(args):
3107
3122
  sig("configured", False, "defaults.fast_path present and enabled",
3108
3123
  "config.defaults.fast_path", False)
3109
3124
  else:
3110
- repo_path = _repo_cfg(ledger, args.repo).get("path", ".")
3125
+ repo_path = _scope_path(ledger, args.repo)
3111
3126
  base, base_src = args.base, "--base"
3112
3127
  if base:
3113
3128
  probe = subprocess.run(["git", "rev-parse", "--verify", base + "^{commit}"],
@@ -3694,7 +3709,7 @@ def cmd_cleanroom(args):
3694
3709
  import time
3695
3710
  ledger = _load(args.ledger)
3696
3711
  _repo_node(ledger, args.repo)
3697
- repo_path = _repo_cfg(ledger, args.repo).get("path", ".")
3712
+ repo_path = _scope_path(ledger, args.repo)
3698
3713
 
3699
3714
  def git(*a, **kw):
3700
3715
  cwd = kw.pop("cwd", repo_path)
@@ -7038,11 +7053,10 @@ def _struct_distance(a, b):
7038
7053
 
7039
7054
 
7040
7055
  def _oracle_hash(arm_dir):
7041
- try:
7042
- with open(os.path.join(arm_dir, "oracle", "ORACLE.json"), "rb") as fh:
7043
- return hashlib.sha256(fh.read()).hexdigest()
7044
- except OSError:
7045
- return None
7056
+ # the arm's withheld oracle, hashed by exact bytes -- an arm whose oracle differs by one
7057
+ # byte is a different experiment (cmd_lang_compare refuses the pair). Same rule, and now the
7058
+ # same code, as a manifest unit: `_sha256_file`.
7059
+ return _sha256_file(os.path.join(arm_dir, "oracle", "ORACLE.json"))
7046
7060
 
7047
7061
 
7048
7062
  def cmd_lang_compare(args):
@@ -7533,11 +7547,18 @@ DEFAULT_STATIC_ZERO_AT = 10 # gated-open count at which the static dimension hi
7533
7547
  BANDS = [(95, "READY"), (80, "RELEASE CANDIDATE"), (50, "IN PROGRESS"), (0, "NOT READY")]
7534
7548
 
7535
7549
 
7536
- def _band(score):
7537
- for floor, label in BANDS:
7550
+ def _banded(score, bands):
7551
+ """The label of the highest floor the score reaches. Every band table ends at floor 0, so
7552
+ the last row IS the default -- named once here instead of being restated as a trailing
7553
+ `return` in four callers, where it read as a fifth band nobody could reach (1.95.0)."""
7554
+ for floor, label in bands:
7538
7555
  if score >= floor:
7539
7556
  return label
7540
- return "NOT READY"
7557
+ return bands[-1][1]
7558
+
7559
+
7560
+ def _band(score):
7561
+ return _banded(score, BANDS)
7541
7562
 
7542
7563
 
7543
7564
  _AC_ID = re.compile(r"(?i)^[*_`]*\s*AC[-_]?0*(\d+)\b[*_`]*[\s.:—–·-]*")
@@ -9201,7 +9222,9 @@ def cmd_top(args):
9201
9222
 
9202
9223
  total = len(obligations)
9203
9224
  done, fail, quar = _n("MEASURED_PASS"), _n("MEASURED_FAIL"), _n("QUARANTINE")
9204
- unmeasured = _n("UNMEASURED") + _n("TRACED")
9225
+ # TRACED/TAGGED are declared in TOP_STATES for the renderer and NEVER assigned above
9226
+ # (v0.1 has no source for either), so only UNMEASURED can carry a count here.
9227
+ unmeasured = _n("UNMEASURED")
9205
9228
  pct = _top_pct(done, total)
9206
9229
  # INV-TOP-06 (ADR-038): DONE never publishes 100% while the seal is MEASURED broken --
9207
9230
  # every criterion green against evidence that no longer belongs to this code state is
@@ -9765,17 +9788,11 @@ _FUNC_RE = re.compile(
9765
9788
 
9766
9789
 
9767
9790
  def _simplicity_band(score):
9768
- for floor, label in SIMPLICITY_BANDS:
9769
- if score >= floor:
9770
- return label
9771
- return "OVERBUILT"
9791
+ return _banded(score, SIMPLICITY_BANDS)
9772
9792
 
9773
9793
 
9774
9794
  def _rebuild_band(score):
9775
- for floor, label in REBUILD_BANDS:
9776
- if score >= floor:
9777
- return label
9778
- return "DIVERGE"
9795
+ return _banded(score, REBUILD_BANDS)
9779
9796
 
9780
9797
 
9781
9798
  def _test_file_set(repo_path, repo_type):
@@ -10334,10 +10351,7 @@ _WASTE_COMMENT = ("//", "#", "*", "--", "/*", "*/", '"""', "'''", ";;", "<!--")
10334
10351
 
10335
10352
 
10336
10353
  def _waste_band(score):
10337
- for floor, label in WASTE_BANDS:
10338
- if score >= floor:
10339
- return label
10340
- return "WASTEFUL"
10354
+ return _banded(score, WASTE_BANDS)
10341
10355
 
10342
10356
 
10343
10357
  def _path_allowed(rel, allow_paths):
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
3
  "name": "uscha",
4
- "version": "1.94.0",
4
+ "version": "1.95.0",
5
5
  "displayName": "Uscha",
6
6
  "description": "Spec-driven development for LLM coding agents: 9 skills (discovery, adr-refine, reverse-discovery, characterize, devloop, sysdoc, rubric, mirador, status) + a stdlib measurement engine (qa_ledger.py, 53 subcommands + universal installer + npm/npx router). Facts block, guesses advise; the human approves.",
7
7
  "author": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uscha",
3
- "version": "1.94.0",
3
+ "version": "1.95.0",
4
4
  "description": "Uscha spec-driven development methodology for coding agents. Includes npm/npx router.",
5
5
  "author": {
6
6
  "name": "Andres Massello",
@@ -1,6 +1,6 @@
1
1
  # uscha-kit
2
2
 
3
- **Kit version:** v1.94.0 <!-- uscha:version --> · **[uscha.dev](https://uscha.dev)**
3
+ **Kit version:** v1.95.0 <!-- uscha:version --> · **[uscha.dev](https://uscha.dev)**
4
4
 
5
5
  Spec-driven orchestrator + multi-repo QA for Claude Code, with a deterministic ledger.
6
6
  **Nine skills** (`uscha-discovery`, `uscha-adr-refine`, `uscha-devloop`, `uscha-sysdoc`, `uscha-reverse-discovery`,
package/uscha-kit/VERSION CHANGED
@@ -1 +1 @@
1
- uscha-kit 1.94.0
1
+ uscha-kit 1.95.0
@@ -206,17 +206,51 @@ def _repo_cfg(ledger, name):
206
206
  # measurement: coverage
207
207
  # --------------------------------------------------------------------------- #
208
208
  def _jacoco_line_counter(xml_path):
209
- """Return (missed, covered) for the report-level LINE counter."""
209
+ """Return (missed, covered) for the report-level LINE counter, or None when the report
210
+ could not be READ. The distinction is the whole point: a report that measured nothing
211
+ must never be summed as (0, 0) — that is an invented number, and with one module's XML
212
+ truncated mid-write it made the surviving modules' percentage look like the project's
213
+ (audit 1.94.1). Absence is never invented as a number — the rule `cobertura_coverage`
214
+ already states, applied to the JaCoCo readers too. A report with no LINE counter at all
215
+ is a real, readable zero and stays (0, 0)."""
210
216
  try:
211
217
  root = _parse_xml(xml_path).getroot()
212
- except ET.ParseError:
213
- return 0, 0
218
+ except (ET.ParseError, OSError, ReportTooLarge):
219
+ return None
214
220
  for c in root.findall("counter"):
215
221
  if c.get("type") == "LINE":
216
- return int(c.get("missed", 0)), int(c.get("covered", 0))
222
+ try:
223
+ return int(c.get("missed", 0)), int(c.get("covered", 0))
224
+ except (TypeError, ValueError):
225
+ return None # a counter that is not a number measured nothing
217
226
  return 0, 0
218
227
 
219
228
 
229
+ def _jacoco_result(files):
230
+ """Sum the LINE counters of `files`, or report the WHOLE reading unmeasured. Shared by
231
+ maven/gradle/ant: three hand-copied versions of this sum is how one of them would drift.
232
+
233
+ ONE unreadable report makes the reading unmeasured, and an unmeasured reading is
234
+ (0, 0, 0.0) -- not the survivors' percentage with a false flag beside it. Every other
235
+ reader in this file returns pct 0.0 whenever report_found is False, and readers of the
236
+ result rely on it: `readiness` scores coverage as pct/threshold WITHOUT consulting
237
+ report_found, and `snapshot` prints and persists the pct next to the flag. Handing them
238
+ the surviving modules' number would score a project on a question nobody asked
239
+ (audit 1.94.1). The report paths are still listed -- the operator needs to know which
240
+ files were looked at."""
241
+ parsed = [_jacoco_line_counter(f) for f in files]
242
+ reports = [f.replace("\\", "/") for f in files]
243
+ if not files or any(p is None for p in parsed):
244
+ return {"covered": 0, "missed": 0, "pct": 0.0, "report_found": False,
245
+ "reports": reports}
246
+ missed = sum(p[0] for p in parsed)
247
+ covered = sum(p[1] for p in parsed)
248
+ total = missed + covered
249
+ pct = round(covered / total * 100, 2) if total else 0.0
250
+ return {"covered": covered, "missed": missed, "pct": pct,
251
+ "report_found": True, "reports": reports}
252
+
253
+
220
254
  def maven_coverage(repo_path):
221
255
  """
222
256
  Prefer an aggregate report if present, else sum per-module reports.
@@ -231,16 +265,7 @@ def maven_coverage(repo_path):
231
265
  files = glob.glob(os.path.join(repo_path, "**", "target", "site",
232
266
  "jacoco", "jacoco.xml"),
233
267
  recursive=True)
234
- missed = covered = 0
235
- for f in files:
236
- m, c = _jacoco_line_counter(f)
237
- missed += m
238
- covered += c
239
- total = missed + covered
240
- pct = round(covered / total * 100, 2) if total else 0.0
241
- return {"covered": covered, "missed": missed, "pct": pct,
242
- "report_found": bool(files),
243
- "reports": [f.replace("\\", "/") for f in files]}
268
+ return _jacoco_result(files)
244
269
 
245
270
 
246
271
  def flutter_coverage(repo_path):
@@ -343,16 +368,7 @@ def gradle_coverage(repo_path):
343
368
  if not files:
344
369
  files = glob.glob(os.path.join(repo_path, "**", "build", "reports",
345
370
  "jacoco", "jacoco.xml"), recursive=True)
346
- missed = covered = 0
347
- for f in files:
348
- m, c = _jacoco_line_counter(f)
349
- missed += m
350
- covered += c
351
- total = missed + covered
352
- pct = round(covered / total * 100, 2) if total else 0.0
353
- return {"covered": covered, "missed": missed, "pct": pct,
354
- "report_found": bool(files),
355
- "reports": [f.replace("\\", "/") for f in files]}
371
+ return _jacoco_result(files)
356
372
 
357
373
 
358
374
  def ant_coverage(repo_path):
@@ -360,16 +376,7 @@ def ant_coverage(repo_path):
360
376
  report task writes wherever the build file says -- so the report is discovered
361
377
  RECURSIVELY by name instead of guessing one convention."""
362
378
  files = _ant_reports(repo_path, "jacoco.xml")
363
- missed = covered = 0
364
- for f in files:
365
- m, c = _jacoco_line_counter(f)
366
- missed += m
367
- covered += c
368
- total = missed + covered
369
- pct = round(covered / total * 100, 2) if total else 0.0
370
- return {"covered": covered, "missed": missed, "pct": pct,
371
- "report_found": bool(files),
372
- "reports": [f.replace("\\", "/") for f in files]}
379
+ return _jacoco_result(files)
373
380
 
374
381
 
375
382
  def coverage(repo_path, repo_type):
@@ -1276,7 +1283,15 @@ def _mk_id(tool, rule, fname, line, granularity):
1276
1283
 
1277
1284
  def _find_all(base, patterns, explicit):
1278
1285
  if explicit:
1279
- return [explicit] if os.path.exists(explicit) else []
1286
+ if not os.path.exists(explicit):
1287
+ # An EXPLICIT path is a claim the operator made about where the report is. When
1288
+ # it is not there, the honest reading is a typo or a build that never wrote it --
1289
+ # not "this linter has no findings". Returning [] made ingest-gate log nothing
1290
+ # and exit 0, so a mistyped --ruff read as a clean gate (audit 1.94.1). Same
1291
+ # fail-closed exit an unparseable report already gets, with the path named.
1292
+ _invalid_static_report(explicit, "linter",
1293
+ "no such file (explicit path given, nothing to ingest)")
1294
+ return [explicit]
1280
1295
  found = []
1281
1296
  for pat in patterns:
1282
1297
  found += glob.glob(os.path.join(base, pat), recursive=True)
@@ -3107,7 +3122,7 @@ def cmd_fastpath_eval(args):
3107
3122
  sig("configured", False, "defaults.fast_path present and enabled",
3108
3123
  "config.defaults.fast_path", False)
3109
3124
  else:
3110
- repo_path = _repo_cfg(ledger, args.repo).get("path", ".")
3125
+ repo_path = _scope_path(ledger, args.repo)
3111
3126
  base, base_src = args.base, "--base"
3112
3127
  if base:
3113
3128
  probe = subprocess.run(["git", "rev-parse", "--verify", base + "^{commit}"],
@@ -3694,7 +3709,7 @@ def cmd_cleanroom(args):
3694
3709
  import time
3695
3710
  ledger = _load(args.ledger)
3696
3711
  _repo_node(ledger, args.repo)
3697
- repo_path = _repo_cfg(ledger, args.repo).get("path", ".")
3712
+ repo_path = _scope_path(ledger, args.repo)
3698
3713
 
3699
3714
  def git(*a, **kw):
3700
3715
  cwd = kw.pop("cwd", repo_path)
@@ -7038,11 +7053,10 @@ def _struct_distance(a, b):
7038
7053
 
7039
7054
 
7040
7055
  def _oracle_hash(arm_dir):
7041
- try:
7042
- with open(os.path.join(arm_dir, "oracle", "ORACLE.json"), "rb") as fh:
7043
- return hashlib.sha256(fh.read()).hexdigest()
7044
- except OSError:
7045
- return None
7056
+ # the arm's withheld oracle, hashed by exact bytes -- an arm whose oracle differs by one
7057
+ # byte is a different experiment (cmd_lang_compare refuses the pair). Same rule, and now the
7058
+ # same code, as a manifest unit: `_sha256_file`.
7059
+ return _sha256_file(os.path.join(arm_dir, "oracle", "ORACLE.json"))
7046
7060
 
7047
7061
 
7048
7062
  def cmd_lang_compare(args):
@@ -7533,11 +7547,18 @@ DEFAULT_STATIC_ZERO_AT = 10 # gated-open count at which the static dimension hi
7533
7547
  BANDS = [(95, "READY"), (80, "RELEASE CANDIDATE"), (50, "IN PROGRESS"), (0, "NOT READY")]
7534
7548
 
7535
7549
 
7536
- def _band(score):
7537
- for floor, label in BANDS:
7550
+ def _banded(score, bands):
7551
+ """The label of the highest floor the score reaches. Every band table ends at floor 0, so
7552
+ the last row IS the default -- named once here instead of being restated as a trailing
7553
+ `return` in four callers, where it read as a fifth band nobody could reach (1.95.0)."""
7554
+ for floor, label in bands:
7538
7555
  if score >= floor:
7539
7556
  return label
7540
- return "NOT READY"
7557
+ return bands[-1][1]
7558
+
7559
+
7560
+ def _band(score):
7561
+ return _banded(score, BANDS)
7541
7562
 
7542
7563
 
7543
7564
  _AC_ID = re.compile(r"(?i)^[*_`]*\s*AC[-_]?0*(\d+)\b[*_`]*[\s.:—–·-]*")
@@ -9201,7 +9222,9 @@ def cmd_top(args):
9201
9222
 
9202
9223
  total = len(obligations)
9203
9224
  done, fail, quar = _n("MEASURED_PASS"), _n("MEASURED_FAIL"), _n("QUARANTINE")
9204
- unmeasured = _n("UNMEASURED") + _n("TRACED")
9225
+ # TRACED/TAGGED are declared in TOP_STATES for the renderer and NEVER assigned above
9226
+ # (v0.1 has no source for either), so only UNMEASURED can carry a count here.
9227
+ unmeasured = _n("UNMEASURED")
9205
9228
  pct = _top_pct(done, total)
9206
9229
  # INV-TOP-06 (ADR-038): DONE never publishes 100% while the seal is MEASURED broken --
9207
9230
  # every criterion green against evidence that no longer belongs to this code state is
@@ -9765,17 +9788,11 @@ _FUNC_RE = re.compile(
9765
9788
 
9766
9789
 
9767
9790
  def _simplicity_band(score):
9768
- for floor, label in SIMPLICITY_BANDS:
9769
- if score >= floor:
9770
- return label
9771
- return "OVERBUILT"
9791
+ return _banded(score, SIMPLICITY_BANDS)
9772
9792
 
9773
9793
 
9774
9794
  def _rebuild_band(score):
9775
- for floor, label in REBUILD_BANDS:
9776
- if score >= floor:
9777
- return label
9778
- return "DIVERGE"
9795
+ return _banded(score, REBUILD_BANDS)
9779
9796
 
9780
9797
 
9781
9798
  def _test_file_set(repo_path, repo_type):
@@ -10334,10 +10351,7 @@ _WASTE_COMMENT = ("//", "#", "*", "--", "/*", "*/", '"""', "'''", ";;", "<!--")
10334
10351
 
10335
10352
 
10336
10353
  def _waste_band(score):
10337
- for floor, label in WASTE_BANDS:
10338
- if score >= floor:
10339
- return label
10340
- return "WASTEFUL"
10354
+ return _banded(score, WASTE_BANDS)
10341
10355
 
10342
10356
 
10343
10357
  def _path_allowed(rel, allow_paths):
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.94.0",
2
+ "version": "1.95.0",
3
3
  "project": null,
4
4
  "defaults": {
5
5
  "coverage_threshold": 60,