@andresmassello/uscha 1.53.0 → 1.55.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 +2 -2
- package/package.json +1 -1
- package/uscha-kit/.claude/skills/uscha-adr-refine/SKILL.md +28 -0
- package/uscha-kit/.claude/skills/uscha-characterize/SKILL.md +28 -0
- package/uscha-kit/.claude/skills/uscha-devloop/SKILL.md +28 -0
- package/uscha-kit/.claude/skills/uscha-devloop/qa_ledger.py +171 -171
- package/uscha-kit/.claude/skills/uscha-discovery/SKILL.md +28 -0
- package/uscha-kit/.claude/skills/uscha-reverse-discovery/SKILL.md +30 -0
- package/uscha-kit/.claude/skills/uscha-rubric/SKILL.md +28 -0
- package/uscha-kit/.claude/skills/uscha-sysdoc/SKILL.md +28 -0
- package/uscha-kit/.claude-plugin/plugin.json +1 -1
- package/uscha-kit/.codex-plugin/plugin.json +1 -1
- package/uscha-kit/README.md +1 -1
- package/uscha-kit/VERSION +1 -1
- package/uscha-kit/skills/uscha-adr-refine/SKILL.md +28 -0
- package/uscha-kit/skills/uscha-characterize/SKILL.md +28 -0
- package/uscha-kit/skills/uscha-devloop/SKILL.md +28 -0
- package/uscha-kit/skills/uscha-devloop/qa_ledger.py +171 -171
- package/uscha-kit/skills/uscha-discovery/SKILL.md +28 -0
- package/uscha-kit/skills/uscha-reverse-discovery/SKILL.md +30 -0
- package/uscha-kit/skills/uscha-rubric/SKILL.md +28 -0
- package/uscha-kit/skills/uscha-sysdoc/SKILL.md +28 -0
- package/uscha-kit/uscha.config.json +1 -1
|
@@ -2419,9 +2419,9 @@ def cmd_flag_blocker(args):
|
|
|
2419
2419
|
raise SystemExit("[qa_ledger] flag-blocker requires --note describing the breach.")
|
|
2420
2420
|
if args.resolve and not args.escape_analysis:
|
|
2421
2421
|
raise SystemExit(
|
|
2422
|
-
"[qa_ledger]
|
|
2423
|
-
"
|
|
2424
|
-
"
|
|
2422
|
+
"[qa_ledger] resolving a BLOCKER requires --escape-analysis: which gate/test "
|
|
2423
|
+
"should have caught it, and what was done about it (new test / new gate / "
|
|
2424
|
+
"sibling hunt)? Finding each bug ONCE is the rule.")
|
|
2425
2425
|
rec = _append_gate_record(ledger, node, args.repo, tool, args.iteration,
|
|
2426
2426
|
failing, 1, args.note)
|
|
2427
2427
|
if args.resolve:
|
|
@@ -2432,7 +2432,7 @@ def cmd_flag_blocker(args):
|
|
|
2432
2432
|
f"(readiness capped <=65, convergence blocked until --resolve)")
|
|
2433
2433
|
else:
|
|
2434
2434
|
print(f"[qa_ledger] {args.repo}/{tool}: resolved — gate cleared "
|
|
2435
|
-
f"(step #{rec['n']}, escape analysis
|
|
2435
|
+
f"(step #{rec['n']}, escape analysis recorded)")
|
|
2436
2436
|
|
|
2437
2437
|
|
|
2438
2438
|
def _converged(node, k, qa_order=None):
|
|
@@ -2595,16 +2595,16 @@ def cmd_phase(args):
|
|
|
2595
2595
|
"spike_branch": spike, "satisfied": ok},
|
|
2596
2596
|
indent=2, ensure_ascii=False))
|
|
2597
2597
|
sys.exit(0 if ok else 1)
|
|
2598
|
-
print(f"PHASE {args.repo}: {state} (
|
|
2598
|
+
print(f"PHASE {args.repo}: {state} (derived from the ledger — not declared)")
|
|
2599
2599
|
for e in evidence:
|
|
2600
2600
|
print(f" · {e}")
|
|
2601
2601
|
if spike:
|
|
2602
|
-
print(f" !
|
|
2603
|
-
f"(Tip 21) —
|
|
2604
|
-
f"
|
|
2602
|
+
print(f" ! branch '{spike}': SPIKE code, disposable by contract "
|
|
2603
|
+
f"(Tip 21) — its legitimate output is an ADR with the lessons, "
|
|
2604
|
+
f"never a PR. Write the ADR and start clean on a normal branch.")
|
|
2605
2605
|
elif args.require and not ok:
|
|
2606
|
-
print(f" !
|
|
2607
|
-
f"
|
|
2606
|
+
print(f" ! '{args.require}' is required and the FACTS say '{state}' — "
|
|
2607
|
+
f"state is not negotiated, it is built")
|
|
2608
2608
|
sys.exit(0 if ok else 1)
|
|
2609
2609
|
|
|
2610
2610
|
|
|
@@ -4003,34 +4003,34 @@ def cmd_readiness(args):
|
|
|
4003
4003
|
# sin ella no hay % honesto que mostrar. Informativo: jamas gatea (kit 1.28.0).
|
|
4004
4004
|
if acc_traceable and total:
|
|
4005
4005
|
print(f" acceptance medido: {out['acceptance']['measured_pct']}% "
|
|
4006
|
-
f"({len(measured_closed)}/{total}
|
|
4007
|
-
f"—
|
|
4006
|
+
f"({len(measured_closed)}/{total} criteria closed by a green test "
|
|
4007
|
+
f"— measured, does not gate)")
|
|
4008
4008
|
if not acc_found:
|
|
4009
4009
|
print(f" ! acceptance file not found: {acc_path or '(unset)'} — ADR dimension = 0")
|
|
4010
4010
|
if acc_found and not total:
|
|
4011
|
-
print(f" ! acceptance
|
|
4012
|
-
f"(--section {args.section!r}
|
|
4013
|
-
f"
|
|
4011
|
+
print(f" ! acceptance found but 0 criteria in scope "
|
|
4012
|
+
f"(--section {args.section!r} matched nothing in the file?) — "
|
|
4013
|
+
f"adr/acceptance dimensions at 0")
|
|
4014
4014
|
if acc_found and total and not acc_traceable:
|
|
4015
|
-
print(" ! acceptance
|
|
4016
|
-
"dimension
|
|
4015
|
+
print(" ! acceptance has no traceable IDs ('- [ ] AC-01 — ...') — the "
|
|
4016
|
+
"acceptance dimension falls back to the checkbox ratio (NARRATED, not measured)")
|
|
4017
4017
|
if dupe_ids:
|
|
4018
|
-
print(f" ! IDs
|
|
4019
|
-
f"—
|
|
4018
|
+
print(f" ! duplicate IDs in acceptance (normalized): {', '.join(dupe_ids)} "
|
|
4019
|
+
f"— each ID counts ONCE in the acceptance dimension")
|
|
4020
4020
|
if legacy_weights_cfg and not acc_traceable:
|
|
4021
|
-
print(" ! config
|
|
4022
|
-
"'acceptance' — dimension
|
|
4023
|
-
"
|
|
4021
|
+
print(" ! config carries explicit readiness_weights from kit <=1.9.0 without "
|
|
4022
|
+
"'acceptance' — dimension excluded (weight 0) until you add AC-IDs "
|
|
4023
|
+
"or the explicit weight in config.defaults.readiness_weights")
|
|
4024
4024
|
if narrated_only:
|
|
4025
|
-
print(f" ! narrated-only: {', '.join(narrated_only)} — checkbox
|
|
4026
|
-
f"
|
|
4027
|
-
f"narrated:
|
|
4025
|
+
print(f" ! narrated-only: {', '.join(narrated_only)} — checkbox ticked "
|
|
4026
|
+
f"WITHOUT a green 'AC-n' testcase in the reports (measured beats "
|
|
4027
|
+
f"narrated: does NOT close)")
|
|
4028
4028
|
if measured_unchecked:
|
|
4029
|
-
print(f" ·
|
|
4030
|
-
f"testcase
|
|
4029
|
+
print(f" · measured but unticked: {', '.join(measured_unchecked)} — there is "
|
|
4030
|
+
f"a green testcase; tick the checkbox if the criterion is done")
|
|
4031
4031
|
if acc_traceable and ac_untagged:
|
|
4032
|
-
print(f" · {ac_untagged}
|
|
4033
|
-
f"
|
|
4032
|
+
print(f" · {ac_untagged} criterion(s) without an AC-ID — cannot close "
|
|
4033
|
+
f"MEASURED (they count as open in the acceptance dimension)")
|
|
4034
4034
|
if unmeasured_repos:
|
|
4035
4035
|
print(f" ! static gate NEVER ran in: {', '.join(unmeasured_repos)} — "
|
|
4036
4036
|
f"dimension scored UNMEASURED (0.0), silence is not success")
|
|
@@ -4045,42 +4045,42 @@ def cmd_readiness(args):
|
|
|
4045
4045
|
f"measured 0%. Instrument it, or DECLARE the exemption with "
|
|
4046
4046
|
f"defaults.readiness_weights.coverage = 0 (config = requirement)")
|
|
4047
4047
|
if stale_reports:
|
|
4048
|
-
print(f" ! {len(stale_reports)}
|
|
4049
|
-
f"(
|
|
4050
|
-
f"
|
|
4048
|
+
print(f" ! {len(stale_reports)} STALE JUnit report(s) discarded "
|
|
4049
|
+
f"(code newer than the evidence) — the ACs that relied only "
|
|
4050
|
+
f"on them go UNMEASURED; re-run the tests")
|
|
4051
4051
|
if production_open:
|
|
4052
4052
|
labels = ", ".join(f"{p.get('id')}({p.get('severity')})" for p in production_open[:3])
|
|
4053
|
-
more = "" if len(production_open) <= 3 else f" +{len(production_open)-3}
|
|
4054
|
-
print(f" ! production findings open: {labels}{more} --
|
|
4055
|
-
f"
|
|
4053
|
+
more = "" if len(production_open) <= 3 else f" +{len(production_open)-3} more"
|
|
4054
|
+
print(f" ! production findings open: {labels}{more} -- real field "
|
|
4055
|
+
f"feedback; reopen discovery/SPEC in the next cycle")
|
|
4056
4056
|
if spec_doubts_open:
|
|
4057
4057
|
labels = ", ".join(f"{s.get('id')}({s.get('kind')}/{s.get('severity')})"
|
|
4058
4058
|
for s in spec_doubts_open[:3])
|
|
4059
|
-
more = "" if len(spec_doubts_open) <= 3 else f" +{len(spec_doubts_open)-3}
|
|
4060
|
-
print(f" ! spec-doubt open: {labels}{more} --
|
|
4061
|
-
f"
|
|
4059
|
+
more = "" if len(spec_doubts_open) <= 3 else f" +{len(spec_doubts_open)-3} more"
|
|
4060
|
+
print(f" ! spec-doubt open: {labels}{more} -- do not code around a "
|
|
4061
|
+
f"doubtful SPEC; this needs human review")
|
|
4062
4062
|
if spec_change_requests_open:
|
|
4063
4063
|
labels = ", ".join(f"{r.get('id')}({r.get('source')})"
|
|
4064
4064
|
for r in spec_change_requests_open[:3])
|
|
4065
|
-
more = "" if len(spec_change_requests_open) <= 3 else f" +{len(spec_change_requests_open)-3}
|
|
4066
|
-
print(f" ! spec-change-request open: {labels}{more} --
|
|
4067
|
-
f"
|
|
4065
|
+
more = "" if len(spec_change_requests_open) <= 3 else f" +{len(spec_change_requests_open)-3} more"
|
|
4066
|
+
print(f" ! spec-change-request open: {labels}{more} -- the contract "
|
|
4067
|
+
f"needs a human decision and an amended SPEC/ADR")
|
|
4068
4068
|
if stalled_repos:
|
|
4069
|
-
print(f" ! stall: {', '.join(stalled_repos)} — findings
|
|
4070
|
-
f"
|
|
4071
|
-
f"
|
|
4072
|
-
f"re-
|
|
4069
|
+
print(f" ! stall: {', '.join(stalled_repos)} — gated findings flat or "
|
|
4070
|
+
f"rising for {STALL_WINDOW} cycles: iterating more is not getting "
|
|
4071
|
+
f"closer. Likely a design/SPEC problem — go back to the ADR / "
|
|
4072
|
+
f"re-plan with the human (advisory)")
|
|
4073
4073
|
# rubrica (1.23.0): el ultimo grade por repo, siempre visible — guess
|
|
4074
4074
|
# estructurado que aconseja; si esta gateado ya bloqueo por el ledger
|
|
4075
4075
|
for rname, rnode in ledger["repos"].items():
|
|
4076
4076
|
rub = _latest_static_by_tool(rnode).get("rubric:grade")
|
|
4077
4077
|
if rub:
|
|
4078
4078
|
m_ = "!" if rub.get("gated_reported") else "·"
|
|
4079
|
-
print(f" {m_}
|
|
4079
|
+
print(f" {m_} rubric {rname}: {rub.get('note', 'n/a')}")
|
|
4080
4080
|
if stop_signal:
|
|
4081
|
-
print(" · stop-signal:
|
|
4082
|
-
"
|
|
4083
|
-
"acceptance),
|
|
4081
|
+
print(" · stop-signal: every repo converged and no blocking fact "
|
|
4082
|
+
"remains — what is left is measurable debt (coverage/"
|
|
4083
|
+
"acceptance), not findings: a candidate to stop and go to PR (advisory)")
|
|
4084
4084
|
# veredicto unico (1.25.0, anti-ceremonia): los gates persistidos se colapsan a
|
|
4085
4085
|
# UNA linea bajo el KPI. El detalle (dimensiones, acceptance, churn, by-repo) es
|
|
4086
4086
|
# ruido de auditoria en el caso normal -> detras de --verbose. Las lineas de
|
|
@@ -4089,12 +4089,12 @@ def cmd_readiness(args):
|
|
|
4089
4089
|
if gate_roll:
|
|
4090
4090
|
blocking = [g for g in gate_roll if g["blocking"]]
|
|
4091
4091
|
n_ok = len(gate_roll) - len(blocking)
|
|
4092
|
-
hint = "" if args.verbose else " (readiness --verbose
|
|
4092
|
+
hint = "" if args.verbose else " (readiness --verbose for the detail)"
|
|
4093
4093
|
if blocking:
|
|
4094
4094
|
names = ", ".join(f"{g['repo']}/{g['tool']}" for g in blocking)
|
|
4095
|
-
print(f"--- gates: {n_ok} ok · {len(blocking)}
|
|
4095
|
+
print(f"--- gates: {n_ok} ok · {len(blocking)} blocking ({names}){hint}")
|
|
4096
4096
|
else:
|
|
4097
|
-
print(f"--- gates: {n_ok} ok,
|
|
4097
|
+
print(f"--- gates: {n_ok} ok, none blocking{hint}")
|
|
4098
4098
|
if not args.verbose:
|
|
4099
4099
|
return
|
|
4100
4100
|
print("--- dimensions (weight | raw | contribution) ---")
|
|
@@ -4643,7 +4643,7 @@ def cmd_simplicity_check(args):
|
|
|
4643
4643
|
sys.exit(0 if verdict != "OVERBUILT" else 1)
|
|
4644
4644
|
|
|
4645
4645
|
print(f"SIMPLICITY: {score}/100 — {verdict}")
|
|
4646
|
-
print("---
|
|
4646
|
+
print("--- metrics (value / budget · * = declared by the human) ---")
|
|
4647
4647
|
rows = [
|
|
4648
4648
|
("lines_added", m["lines_added"], b["max_lines_added"], "max_lines_added"),
|
|
4649
4649
|
("net_lines", m["net_lines"], b["max_net_lines"], "max_net_lines"),
|
|
@@ -4657,21 +4657,21 @@ def cmd_simplicity_check(args):
|
|
|
4657
4657
|
mark = "*" if key in declared else ""
|
|
4658
4658
|
print(f" {name:17s} {str(val):>7s} / {bud}{mark}")
|
|
4659
4659
|
if not declared:
|
|
4660
|
-
print(" (
|
|
4661
|
-
"
|
|
4662
|
-
print(f" (new_functions: {m['new_functions']} —
|
|
4660
|
+
print(" (every budget is a kit default — an opinion, not a "
|
|
4661
|
+
"requirement: declare yours in config.defaults.simplicity)")
|
|
4662
|
+
print(f" (new_functions: {m['new_functions']} — informational, not gated)")
|
|
4663
4663
|
if m.get("test_files_changed"):
|
|
4664
|
-
print(f" (tests
|
|
4665
|
-
f"
|
|
4666
|
-
f"
|
|
4664
|
+
print(f" (tests OUTSIDE the budget: +{m['test_lines_added']} lines "
|
|
4665
|
+
f"in {m['test_files_changed']} test file(s) — writing tests "
|
|
4666
|
+
f"never penalizes this gate)")
|
|
4667
4667
|
if m.get("files_skipped"):
|
|
4668
|
-
print(f" ({m['files_skipped']}
|
|
4668
|
+
print(f" ({m['files_skipped']} non-code file(s) skipped: docs/config/resources)")
|
|
4669
4669
|
if flags:
|
|
4670
|
-
print("--- flags (Reduce:
|
|
4670
|
+
print("--- flags (Reduce: what to cut) ---")
|
|
4671
4671
|
for fl in flags:
|
|
4672
4672
|
print(f" ! {fl}")
|
|
4673
4673
|
else:
|
|
4674
|
-
print("
|
|
4674
|
+
print(" within budget — nothing to cut")
|
|
4675
4675
|
sys.exit(0 if verdict != "OVERBUILT" else 1)
|
|
4676
4676
|
|
|
4677
4677
|
|
|
@@ -4955,23 +4955,23 @@ def cmd_waste_check(args):
|
|
|
4955
4955
|
print(json.dumps(out, indent=2, ensure_ascii=False))
|
|
4956
4956
|
sys.exit(exit_code)
|
|
4957
4957
|
|
|
4958
|
-
mode = "gate
|
|
4958
|
+
mode = "declared gate" if gate else "advisory (declare defaults.waste.gate to make it block)"
|
|
4959
4959
|
print(f"WASTE: {score}/100 — {verdict} ({mode})")
|
|
4960
|
-
print(f"---
|
|
4961
|
-
f"
|
|
4960
|
+
print(f"--- windows of {b['window_size']} normalized lines over {m['sig_lines_added']} "
|
|
4961
|
+
f"added lines ({m['added_windows']} window(s), {m['files_added_to']} file(s)) ---")
|
|
4962
4962
|
print(f" dup_vs_repo: {m['dup_windows_vs_repo']} (budget {b['max_dup_windows_vs_repo']}"
|
|
4963
4963
|
f"{'*' if 'max_dup_windows_vs_repo' in declared else ''}) "
|
|
4964
|
-
f"
|
|
4965
|
-
print(f" (call_density: {m['call_density']}/100 —
|
|
4964
|
+
f"dup_internal: {m['dup_windows_internal']} dup_ratio: {m['dup_ratio']}")
|
|
4965
|
+
print(f" (call_density: {m['call_density']}/100 — informational, neither scored nor gated)")
|
|
4966
4966
|
if not declared:
|
|
4967
|
-
print(" (
|
|
4968
|
-
"
|
|
4967
|
+
print(" (every budget is a kit default — an opinion, not a "
|
|
4968
|
+
"requirement: declare yours in config.defaults.waste)")
|
|
4969
4969
|
if flags:
|
|
4970
|
-
print("--- flags (REUSE-FIRST:
|
|
4970
|
+
print("--- flags (REUSE-FIRST: what to reuse instead of cloning) ---")
|
|
4971
4971
|
for fl in flags:
|
|
4972
4972
|
print(f" ! {fl}")
|
|
4973
|
-
print("---
|
|
4974
|
-
"
|
|
4973
|
+
print("--- honest: a Type-1/Type-2 proxy over normalized lines, NOT semantic "
|
|
4974
|
+
"clones (Type-3/4) nor AST-based CC ---")
|
|
4975
4975
|
sys.exit(exit_code)
|
|
4976
4976
|
|
|
4977
4977
|
|
|
@@ -5003,17 +5003,17 @@ def cmd_pit_check(args):
|
|
|
5003
5003
|
|
|
5004
5004
|
print(f"MUTATION: {m['mutation_score']}/100 — {verdict} "
|
|
5005
5005
|
f"(gate {args.min_score}, test-strength {m['test_strength']})")
|
|
5006
|
-
print("---
|
|
5007
|
-
print(f"
|
|
5008
|
-
f"
|
|
5006
|
+
print("--- effectiveness (NOT coverage: it measures that the tests ASSERT) ---")
|
|
5007
|
+
print(f" mutants {m['total']} · killed {m['killed']} · "
|
|
5008
|
+
f"survived {m['survived']} · uncovered {m['no_coverage']}")
|
|
5009
5009
|
if m.get("excluded"):
|
|
5010
5010
|
print(f" ({m['excluded']} excluidos NON_VIABLE/RUN_ERROR — fuera del score, como PIT)")
|
|
5011
5011
|
if hotspots:
|
|
5012
|
-
print("--- hotspots (
|
|
5012
|
+
print("--- hotspots (more surviving mutants = weaker tests there) ---")
|
|
5013
5013
|
for f, c in hotspots:
|
|
5014
5014
|
print(f" ! {f}: {c['survived']} sobreviven, {c['no_coverage']} sin cubrir")
|
|
5015
5015
|
else:
|
|
5016
|
-
print("
|
|
5016
|
+
print(" no survivors — the tests catch the mutations")
|
|
5017
5017
|
if m["no_coverage"]:
|
|
5018
5018
|
print(f" nota: {m['no_coverage']} mutante(s) sin NINGÚN test "
|
|
5019
5019
|
f"(coverage-gap, distinto de assertion-gap)")
|
|
@@ -5261,26 +5261,26 @@ def cmd_gate_check(args):
|
|
|
5261
5261
|
if items:
|
|
5262
5262
|
uniq = sorted(set(items))
|
|
5263
5263
|
tail = " ..." if len(uniq) > 5 else ""
|
|
5264
|
-
print(f" ! {label}: {len(items)}
|
|
5264
|
+
print(f" ! {label}: {len(items)} in {', '.join(uniq[:5])}{tail}")
|
|
5265
5265
|
|
|
5266
|
-
_show("tests
|
|
5267
|
-
_show("tests
|
|
5266
|
+
_show("tests deleted (BLOCKER)", removed_tests)
|
|
5267
|
+
_show("tests disabled (BLOCKER)", disabled_tests)
|
|
5268
5268
|
if thresholds:
|
|
5269
|
-
print(f" ! thresholds
|
|
5269
|
+
print(f" ! thresholds lowered/removed (BLOCKER): {'; '.join(thresholds)}")
|
|
5270
5270
|
if secrets:
|
|
5271
|
-
print(f" !
|
|
5272
|
-
_show("
|
|
5273
|
-
_show("
|
|
5274
|
-
_show("
|
|
5275
|
-
_show("
|
|
5271
|
+
print(f" ! secrets added (BLOCKER): {'; '.join(sorted(set(secrets)))}")
|
|
5272
|
+
_show("password/token-shaped literals added (review)", secret_literals)
|
|
5273
|
+
_show("golden scrub rules edited (review: they can mask divergence)", scrub_edits)
|
|
5274
|
+
_show("lint suppressions added (review)", suppressions)
|
|
5275
|
+
_show("new dependencies (review: 0 new deps without approval)", new_deps)
|
|
5276
5276
|
if assertions_removed:
|
|
5277
|
-
print(f" ~ asserts
|
|
5277
|
+
print(f" ~ asserts removed from tests: {assertions_removed} (review)")
|
|
5278
5278
|
if test_count_drop:
|
|
5279
|
-
print(f" ~
|
|
5279
|
+
print(f" ~ executed-test count dropped: {test_count_drop} (measured in snapshots — review)")
|
|
5280
5280
|
if verdict == "CLEAN":
|
|
5281
|
-
print("
|
|
5281
|
+
print(" the change does not weaken the measuring apparatus")
|
|
5282
5282
|
elif not blocker:
|
|
5283
|
-
print("
|
|
5283
|
+
print(" soft signals: they require justification (--strict to gate)")
|
|
5284
5284
|
sys.exit(1 if blocker else 0)
|
|
5285
5285
|
|
|
5286
5286
|
|
|
@@ -5359,20 +5359,20 @@ def cmd_regression_check(args):
|
|
|
5359
5359
|
"has_assertion": has_assert},
|
|
5360
5360
|
indent=2, ensure_ascii=False))
|
|
5361
5361
|
sys.exit(1 if fail else 0)
|
|
5362
|
-
print(f"REGRESSION-CAPTURE: {verdict} ({fixed} finding(s)
|
|
5363
|
-
f"+{test_added}
|
|
5362
|
+
print(f"REGRESSION-CAPTURE: {verdict} ({fixed} finding(s) closed · "
|
|
5363
|
+
f"+{test_added} test lines in {len(test_files)} file(s))")
|
|
5364
5364
|
if verdict == "NARRATED":
|
|
5365
|
-
print(" !
|
|
5366
|
-
"
|
|
5367
|
-
"test
|
|
5365
|
+
print(" ! NARRATED close: findings vanished without touching a single test — "
|
|
5366
|
+
"which test reproduces the bug you say you fixed? (Tip 31: the "
|
|
5367
|
+
"failing test comes BEFORE the fix; Tip 94: each bug is found ONCE)")
|
|
5368
5368
|
elif verdict == "MEASURED":
|
|
5369
|
-
print("
|
|
5369
|
+
print(" close with evidence: the fixing diff brings tests")
|
|
5370
5370
|
if not (has_testdef or has_assert):
|
|
5371
|
-
print(" ·
|
|
5372
|
-
"
|
|
5373
|
-
"(
|
|
5371
|
+
print(" · WEAK evidence: the added test lines carry neither a test "
|
|
5372
|
+
"definition nor an assert — this tripwire does not judge quality "
|
|
5373
|
+
"(that is pit-check), but it deserves a human eye")
|
|
5374
5374
|
else:
|
|
5375
|
-
print("
|
|
5375
|
+
print(" nothing closed in the last iteration — nothing to demand")
|
|
5376
5376
|
sys.exit(1 if fail else 0)
|
|
5377
5377
|
|
|
5378
5378
|
|
|
@@ -5592,7 +5592,7 @@ def cmd_rubric_ingest(args):
|
|
|
5592
5592
|
blockers = _rubric_structure(rubric_path)
|
|
5593
5593
|
if blockers:
|
|
5594
5594
|
for b in blockers:
|
|
5595
|
-
print(f"[qa_ledger]
|
|
5595
|
+
print(f"[qa_ledger] invalid rubric: {b}", file=sys.stderr)
|
|
5596
5596
|
sys.exit(2)
|
|
5597
5597
|
items, threshold, _found = _parse_rubric(rubric_path)
|
|
5598
5598
|
by_id = {i["id"]: i for i in items}
|
|
@@ -5673,15 +5673,15 @@ def cmd_rubric_ingest(args):
|
|
|
5673
5673
|
sys.exit(1 if failing else 0)
|
|
5674
5674
|
print(f"RUBRIC: {'PASS' if passed else 'BELOW'} {note}")
|
|
5675
5675
|
if failed_pos:
|
|
5676
|
-
print(f" !
|
|
5676
|
+
print(f" ! criteria not closed: {', '.join(sorted(failed_pos))}")
|
|
5677
5677
|
if unsupported:
|
|
5678
|
-
print(f" !
|
|
5679
|
-
f"— evidence-or-nothing,
|
|
5678
|
+
print(f" ! verdicts WITHOUT evidence (they do not score): {', '.join(sorted(unsupported))} "
|
|
5679
|
+
f"— evidence-or-nothing, a judgement without a citation does not count")
|
|
5680
5680
|
if failing:
|
|
5681
|
-
print("
|
|
5682
|
-
"
|
|
5681
|
+
print(" the gate is DECLARED (config/--gate): it blocks convergence and "
|
|
5682
|
+
"caps readiness <=65 until a clean grade")
|
|
5683
5683
|
elif not passed:
|
|
5684
|
-
print(" advisory:
|
|
5684
|
+
print(" advisory: it does not gate — declare it in defaults.rubric.gate to make it block")
|
|
5685
5685
|
sys.exit(1 if failing else 0)
|
|
5686
5686
|
|
|
5687
5687
|
|
|
@@ -5740,13 +5740,13 @@ def cmd_spec_check(args):
|
|
|
5740
5740
|
print(f" · {c[:80]}")
|
|
5741
5741
|
if m["stack_hits"]:
|
|
5742
5742
|
terms = sorted({t for _, t in m["stack_hits"]})
|
|
5743
|
-
print(f" ~ stack
|
|
5743
|
+
print(f" ~ stack named in criteria ({len(m['stack_hits'])}): {', '.join(terms[:6])} — the 'how' belongs in the ADR, not the SPEC")
|
|
5744
5744
|
if m["non_ears"] and m["n_criteria"]:
|
|
5745
|
-
print(f" · advisory: {m['non_ears']}/{m['n_criteria']}
|
|
5746
|
-
print(" i
|
|
5747
|
-
"
|
|
5745
|
+
print(f" · advisory: {m['non_ears']}/{m['n_criteria']} criteria do not follow the EARS pattern (When/If/While … shall)")
|
|
5746
|
+
print(" i consistency: INFERENTIAL (an uncorrelated checker), not this lint · "
|
|
5747
|
+
"structure = FACT (blocks) · prose = advisory (--strict to gate)")
|
|
5748
5748
|
if verdict == "OK":
|
|
5749
|
-
print("
|
|
5749
|
+
print(" structure complete and criteria testable")
|
|
5750
5750
|
sys.exit(1 if fail else 0)
|
|
5751
5751
|
|
|
5752
5752
|
|
|
@@ -5796,8 +5796,8 @@ def _load_scrub_rules(root):
|
|
|
5796
5796
|
rules.append((re.compile(r["pattern"]), r["replace"], r["pattern"]))
|
|
5797
5797
|
return rules
|
|
5798
5798
|
except (json.JSONDecodeError, re.error, KeyError, TypeError) as exc:
|
|
5799
|
-
print(f"[qa_ledger] {path}
|
|
5800
|
-
f"
|
|
5799
|
+
print(f"[qa_ledger] {path} invalid ({exc}) — the scrub is not skipped in "
|
|
5800
|
+
f"silence: fix the file or delete it.", file=sys.stderr)
|
|
5801
5801
|
sys.exit(2)
|
|
5802
5802
|
|
|
5803
5803
|
|
|
@@ -5829,7 +5829,7 @@ def _load_golden_labels(path):
|
|
|
5829
5829
|
with open(path, "r", encoding="utf-8") as fh:
|
|
5830
5830
|
raw = json.load(fh)
|
|
5831
5831
|
except (OSError, json.JSONDecodeError) as exc:
|
|
5832
|
-
print(f"[qa_ledger] golden labels
|
|
5832
|
+
print(f"[qa_ledger] invalid golden labels ({exc})", file=sys.stderr)
|
|
5833
5833
|
sys.exit(2)
|
|
5834
5834
|
fixtures = raw.get("fixtures", raw) if isinstance(raw, dict) else None
|
|
5835
5835
|
if not isinstance(fixtures, dict):
|
|
@@ -5914,7 +5914,7 @@ def cmd_golden_diff(args):
|
|
|
5914
5914
|
ab = fa.read()
|
|
5915
5915
|
except OSError as exc:
|
|
5916
5916
|
fixture["result"] = "read_error"
|
|
5917
|
-
diverged.append((rec, f"
|
|
5917
|
+
diverged.append((rec, f"could not read: {exc}"))
|
|
5918
5918
|
continue
|
|
5919
5919
|
if rb == ab:
|
|
5920
5920
|
fixture["result"] = "matched"
|
|
@@ -5941,8 +5941,8 @@ def cmd_golden_diff(args):
|
|
|
5941
5941
|
"note": "no .received fixtures found — run the capture first"},
|
|
5942
5942
|
indent=2, ensure_ascii=False))
|
|
5943
5943
|
else:
|
|
5944
|
-
print("GOLDEN-DIFF: NOT-RUN —
|
|
5945
|
-
"(
|
|
5944
|
+
print("GOLDEN-DIFF: NOT-RUN — no .received fixtures, nothing to compare "
|
|
5945
|
+
"(run the capture first; absence is NOT green)")
|
|
5946
5946
|
sys.exit(2)
|
|
5947
5947
|
verdict = "CLEAN" if passed else "DIVERGE"
|
|
5948
5948
|
if args.json:
|
|
@@ -5957,20 +5957,20 @@ def cmd_golden_diff(args):
|
|
|
5957
5957
|
}, indent=2, ensure_ascii=False))
|
|
5958
5958
|
sys.exit(0 if passed else 1)
|
|
5959
5959
|
scrub_str = f" · {matched_scrubbed} via scrub" if matched_scrubbed else ""
|
|
5960
|
-
print(f"GOLDEN-DIFF: {verdict} ({matched}
|
|
5960
|
+
print(f"GOLDEN-DIFF: {verdict} ({matched} match{scrub_str} · {len(diverged)} diverge)")
|
|
5961
5961
|
if rules:
|
|
5962
5962
|
subs = sum(scrub_counts.values())
|
|
5963
|
-
print(f" · scrub
|
|
5964
|
-
f"{subs}
|
|
5963
|
+
print(f" · scrub active: {len(rules)} rule(s) from {GOLDEN_SCRUB_FILE}, "
|
|
5964
|
+
f"{subs} substitution(s) — the masking is visible, not magic")
|
|
5965
5965
|
for f, r in diverged[:12]:
|
|
5966
5966
|
print(f" ! {f}: {r}")
|
|
5967
5967
|
if len(diverged) > 12:
|
|
5968
|
-
print(f" ...
|
|
5968
|
+
print(f" ... and {len(diverged) - 12} more")
|
|
5969
5969
|
if passed:
|
|
5970
|
-
print("
|
|
5970
|
+
print(" behaviour matches the approved golden byte for byte"
|
|
5971
5971
|
+ (" (volatiles declarados enmascarados)" if matched_scrubbed else ""))
|
|
5972
5972
|
else:
|
|
5973
|
-
print("
|
|
5973
|
+
print(" the .approved is field truth: if the diff is right a HUMAN approves it — the agent never touches it")
|
|
5974
5974
|
sys.exit(0 if passed else 1)
|
|
5975
5975
|
|
|
5976
5976
|
|
|
@@ -6000,8 +6000,8 @@ DOCTOR_FIX = {
|
|
|
6000
6000
|
"go": "https://go.dev/dl/",
|
|
6001
6001
|
"cargo": "https://rustup.rs",
|
|
6002
6002
|
"dotnet": "https://dotnet.microsoft.com/download",
|
|
6003
|
-
"ctest": "https://cmake.org/download/ (ctest
|
|
6004
|
-
"gradle": "https://gradle.org/install/ (
|
|
6003
|
+
"ctest": "https://cmake.org/download/ (ctest ships with CMake)",
|
|
6004
|
+
"gradle": "https://gradle.org/install/ (or use the repo gradlew)",
|
|
6005
6005
|
"swift": "https://www.swift.org/install/",
|
|
6006
6006
|
}
|
|
6007
6007
|
# Two valid hooks: the installer wires the portable .py; the plugin flow ships the .ps1.
|
|
@@ -6042,13 +6042,13 @@ def cmd_doctor(args):
|
|
|
6042
6042
|
if py >= (3, 8):
|
|
6043
6043
|
ok(f"Python {py.major}.{py.minor}.{py.micro} (>= 3.8)")
|
|
6044
6044
|
else:
|
|
6045
|
-
err(f"Python {py.major}.{py.minor} -
|
|
6046
|
-
"
|
|
6045
|
+
err(f"Python {py.major}.{py.minor} - the kit requires 3.8+",
|
|
6046
|
+
"install: https://www.python.org/downloads/")
|
|
6047
6047
|
if shutil.which("git"):
|
|
6048
|
-
ok("git
|
|
6048
|
+
ok("git on PATH")
|
|
6049
6049
|
else:
|
|
6050
|
-
err("git
|
|
6051
|
-
"
|
|
6050
|
+
err("git is not on PATH - gate-check/simplicity/regression use it (--from-git)",
|
|
6051
|
+
"install: https://git-scm.com/downloads")
|
|
6052
6052
|
|
|
6053
6053
|
engine_dir = os.path.dirname(os.path.abspath(__file__))
|
|
6054
6054
|
home_skills = os.path.join(os.path.expanduser("~"), ".claude", "skills")
|
|
@@ -6064,7 +6064,7 @@ def cmd_doctor(args):
|
|
|
6064
6064
|
is_global = (not is_plugin) and _under(engine_dir, home_skills)
|
|
6065
6065
|
mode = ("instalacion como PLUGIN (~/.claude/plugins, 1.24.0)" if is_plugin
|
|
6066
6066
|
else "instalacion global (~/.claude/skills)" if is_global
|
|
6067
|
-
else "
|
|
6067
|
+
else "per-project installation")
|
|
6068
6068
|
ok(f"engine: {os.path.abspath(__file__)}", mode)
|
|
6069
6069
|
|
|
6070
6070
|
# --- skills: hermanas del engine ---------------------------------------
|
|
@@ -6083,14 +6083,14 @@ def cmd_doctor(args):
|
|
|
6083
6083
|
except OSError:
|
|
6084
6084
|
missing.append(s)
|
|
6085
6085
|
if not missing and not mismatched:
|
|
6086
|
-
ok(f"skills {len(USCHA_SKILLS)}/{len(USCHA_SKILLS)}
|
|
6086
|
+
ok(f"skills {len(USCHA_SKILLS)}/{len(USCHA_SKILLS)} next to the engine",
|
|
6087
6087
|
", ".join(USCHA_SKILLS))
|
|
6088
6088
|
else:
|
|
6089
6089
|
if missing:
|
|
6090
6090
|
err(f"skills faltantes en {skills_root}: {', '.join(missing)}",
|
|
6091
|
-
"
|
|
6092
|
-
"(README
|
|
6093
|
-
"
|
|
6091
|
+
"install: copy uscha-kit/.claude/skills/* to ~/.claude/skills/ "
|
|
6092
|
+
"(kit README > Installation, option B) - from the uscha-kit-X.Y.Z zip "
|
|
6093
|
+
"or from your checkout of the uscha repo")
|
|
6094
6094
|
if mismatched:
|
|
6095
6095
|
err(f"SKILL.md con frontmatter name distinto al directorio: {', '.join(mismatched)}")
|
|
6096
6096
|
|
|
@@ -6115,18 +6115,18 @@ def cmd_doctor(args):
|
|
|
6115
6115
|
needs_ps = bool(registered and registered.endswith(".ps1"))
|
|
6116
6116
|
ps_ok = (not needs_ps) or bool(shutil.which("powershell") or shutil.which("pwsh"))
|
|
6117
6117
|
if hook_file and registered and ps_ok:
|
|
6118
|
-
ok("hook INV-GOLDEN-01:
|
|
6118
|
+
ok("hook INV-GOLDEN-01: present, registered (PreToolUse) and interpretable",
|
|
6119
6119
|
f"{hook_file} ({registered})")
|
|
6120
6120
|
elif not hook_file:
|
|
6121
|
-
warn("
|
|
6122
|
-
f"
|
|
6123
|
-
f"
|
|
6124
|
-
f"(
|
|
6121
|
+
warn("INV-GOLDEN-01 hook not found (neither ~/.claude/hooks nor <kit>/hooks)",
|
|
6122
|
+
f"install: `uscha install` copies {HOOK_NAMES[0]} to ~/.claude/hooks/ and "
|
|
6123
|
+
f"registers it in settings.json - without it the agent CAN write a .approved "
|
|
6124
|
+
f"(mandatory for migrations, profile E)")
|
|
6125
6125
|
elif not registered:
|
|
6126
|
-
warn("hook
|
|
6127
|
-
"re-
|
|
6126
|
+
warn("hook present but NOT registered in settings.json (PreToolUse)",
|
|
6127
|
+
"re-run `uscha install` to write the PreToolUse snippet")
|
|
6128
6128
|
else:
|
|
6129
|
-
warn(f"hook .ps1
|
|
6129
|
+
warn(f"hook .ps1 registered but no powershell/pwsh on PATH",
|
|
6130
6130
|
"instala pwsh, o usa el hook .py (portable) via `uscha install`: "
|
|
6131
6131
|
"https://learn.microsoft.com/powershell/scripting/install/installing-powershell")
|
|
6132
6132
|
|
|
@@ -6138,22 +6138,22 @@ def cmd_doctor(args):
|
|
|
6138
6138
|
cfg = _load(cfg_path)
|
|
6139
6139
|
defaults = cfg.get("defaults", {})
|
|
6140
6140
|
repos = cfg.get("repos", [])
|
|
6141
|
-
ok(f"
|
|
6141
|
+
ok(f"project: {cfg_path} v{cfg.get('version', '?')} ({len(repos)} repo(s))")
|
|
6142
6142
|
acc = defaults.get("acceptance_file")
|
|
6143
6143
|
if acc and os.path.isfile(acc):
|
|
6144
6144
|
items, _found = _parse_acceptance_items(acc)
|
|
6145
6145
|
ids = sum(1 for i in items if i["id"])
|
|
6146
6146
|
if items and ids:
|
|
6147
|
-
ok(f"ACCEPTANCE: {len(items)}
|
|
6147
|
+
ok(f"ACCEPTANCE: {len(items)} criterion(s), {ids} with a traceable AC-ID")
|
|
6148
6148
|
elif items:
|
|
6149
|
-
warn(f"ACCEPTANCE
|
|
6150
|
-
"
|
|
6151
|
-
"(
|
|
6152
|
-
"
|
|
6149
|
+
warn(f"ACCEPTANCE without traceable AC-IDs ({len(items)} criterion(s))",
|
|
6150
|
+
"generate it with /uscha-discovery or /uscha-adr-refine "
|
|
6151
|
+
"(format '- [ ] AC-01 - ...') - without IDs the dominant readiness "
|
|
6152
|
+
"dimension falls back to the checkbox ratio")
|
|
6153
6153
|
else:
|
|
6154
|
-
warn(f"ACCEPTANCE {acc}
|
|
6154
|
+
warn(f"ACCEPTANCE {acc} has no criteria (zero checkboxes)")
|
|
6155
6155
|
elif acc:
|
|
6156
|
-
warn(f"acceptance_file
|
|
6156
|
+
warn(f"acceptance_file declared but missing: {acc}")
|
|
6157
6157
|
qa_order = defaults.get("qa_tools_order", qa_order)
|
|
6158
6158
|
for r in repos:
|
|
6159
6159
|
tool = DOCTOR_TOOLS.get(r.get("type", ""))
|
|
@@ -6164,11 +6164,11 @@ def cmd_doctor(args):
|
|
|
6164
6164
|
or os.path.isfile(os.path.join(r.get("path", "."), "gradlew.bat"))):
|
|
6165
6165
|
ok(f"toolchain {r['name']} (gradle): gradlew del repo")
|
|
6166
6166
|
elif shutil.which(tool):
|
|
6167
|
-
ok(f"toolchain {r['name']} ({r.get('type')}): {tool}
|
|
6167
|
+
ok(f"toolchain {r['name']} ({r.get('type')}): {tool} on PATH")
|
|
6168
6168
|
else:
|
|
6169
|
-
warn(f"toolchain {r['name']} ({r.get('type')}): {tool} NO esta
|
|
6170
|
-
f"
|
|
6171
|
-
f"(
|
|
6169
|
+
warn(f"toolchain {r['name']} ({r.get('type')}): {tool} NO esta on PATH",
|
|
6170
|
+
f"install: {DOCTOR_FIX.get(tool, 'see the toolchain docs')} "
|
|
6171
|
+
f"(or it may live only in CI)")
|
|
6172
6172
|
rub_cfg = defaults.get("rubric", {}) if isinstance(defaults.get("rubric"), dict) else {}
|
|
6173
6173
|
if rub_cfg.get("file"):
|
|
6174
6174
|
if os.path.isfile(rub_cfg["file"]):
|
|
@@ -6178,24 +6178,24 @@ def cmd_doctor(args):
|
|
|
6178
6178
|
"; ".join(rb_block) + " - ver templates/RUBRIC.md")
|
|
6179
6179
|
else:
|
|
6180
6180
|
ok(f"rubrica declarada: {rub_cfg['file']}"
|
|
6181
|
-
+ (" (GATE
|
|
6181
|
+
+ (" (GATE declared)" if rub_cfg.get("gate") is True else " (advisory)"))
|
|
6182
6182
|
else:
|
|
6183
6183
|
warn(f"rubrica declarada pero ausente: {rub_cfg['file']}",
|
|
6184
|
-
"
|
|
6185
|
-
"
|
|
6184
|
+
"install: create it from templates/RUBRIC.md (weighted RB-nn "
|
|
6185
|
+
"criteria + threshold)")
|
|
6186
6186
|
ledger_path = args.ledger
|
|
6187
6187
|
if os.path.isfile(ledger_path):
|
|
6188
6188
|
try:
|
|
6189
6189
|
_load(ledger_path)
|
|
6190
|
-
ok(f"ledger {ledger_path}:
|
|
6190
|
+
ok(f"ledger {ledger_path}: loads, integrity OK")
|
|
6191
6191
|
except SystemExit as exc:
|
|
6192
|
-
err(f"ledger {ledger_path}
|
|
6192
|
+
err(f"ledger {ledger_path} corrupt or mutated", str(exc))
|
|
6193
6193
|
except SystemExit as exc:
|
|
6194
|
-
err(f"{cfg_path}
|
|
6194
|
+
err(f"{cfg_path} invalid", str(exc))
|
|
6195
6195
|
else:
|
|
6196
|
-
warn(f"
|
|
6197
|
-
"
|
|
6198
|
-
"
|
|
6196
|
+
warn(f"no {cfg_path} in this directory",
|
|
6197
|
+
"install: copy the kit uscha.config.json to the repo root and declare "
|
|
6198
|
+
"your repos/types and your quality bar - only needed to RUN the loop here")
|
|
6199
6199
|
|
|
6200
6200
|
# --- skills de QA del loop (externas al kit, se orquestan sin traerlas) --
|
|
6201
6201
|
# sin ellas la fase 3 (QA loop) no corre; chequeables con o sin config.
|
|
@@ -6206,9 +6206,9 @@ def cmd_doctor(args):
|
|
|
6206
6206
|
ok(f"skills de QA del loop instaladas: {', '.join(qa_order)}")
|
|
6207
6207
|
else:
|
|
6208
6208
|
warn(f"skills de QA no encontradas como archivo: {', '.join(missing_qa)}",
|
|
6209
|
-
"
|
|
6210
|
-
"config.defaults.qa_tools_order;
|
|
6211
|
-
"(
|
|
6209
|
+
"install your QA skills in ~/.claude/skills/ or declare others in "
|
|
6210
|
+
"config.defaults.qa_tools_order; if it is a harness built-in "
|
|
6211
|
+
"(e.g. code-review), ignore this notice")
|
|
6212
6212
|
|
|
6213
6213
|
# --- veredicto ----------------------------------------------------------
|
|
6214
6214
|
n_ok = sum(1 for lv, _, _ in checks if lv == "ok")
|
|
@@ -6223,14 +6223,14 @@ def cmd_doctor(args):
|
|
|
6223
6223
|
for lv, t, d in checks]},
|
|
6224
6224
|
indent=2, ensure_ascii=True))
|
|
6225
6225
|
sys.exit(1 if n_err else 0)
|
|
6226
|
-
print("USCHA DOCTOR -
|
|
6226
|
+
print("USCHA DOCTOR - installation diagnosis")
|
|
6227
6227
|
mark = {"ok": "[OK]", "warn": "[ !]", "error": "[ X]"}
|
|
6228
6228
|
for lv, t, d in checks:
|
|
6229
6229
|
print(f" {mark[lv]} {t}")
|
|
6230
6230
|
if d:
|
|
6231
6231
|
print(f" {d}")
|
|
6232
|
-
print(f"
|
|
6233
|
-
+ (" ->
|
|
6232
|
+
print(f"RESULT: {n_ok} ok - {n_warn} warning(s) - {n_err} error(s)"
|
|
6233
|
+
+ (" -> installation healthy" if not n_err else " -> errors to fix"))
|
|
6234
6234
|
sys.exit(1 if n_err else 0)
|
|
6235
6235
|
|
|
6236
6236
|
|