@andresmassello/uscha 1.40.1

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.
Files changed (98) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +104 -0
  3. package/bin/README.md +6 -0
  4. package/bin/uscha.js +28 -0
  5. package/package.json +38 -0
  6. package/uscha-kit/.claude/skills/uscha-adr-refine/SKILL.md +161 -0
  7. package/uscha-kit/.claude/skills/uscha-characterize/SKILL.md +124 -0
  8. package/uscha-kit/.claude/skills/uscha-devloop/SKILL.md +490 -0
  9. package/uscha-kit/.claude/skills/uscha-devloop/qa_ledger.py +5602 -0
  10. package/uscha-kit/.claude/skills/uscha-discovery/SKILL.md +161 -0
  11. package/uscha-kit/.claude/skills/uscha-mirador/SKILL.md +145 -0
  12. package/uscha-kit/.claude/skills/uscha-mirador/mirador-render.py +112 -0
  13. package/uscha-kit/.claude/skills/uscha-mirador/mirador-watch.ps1 +22 -0
  14. package/uscha-kit/.claude/skills/uscha-mirador/mirador-watch.sh +26 -0
  15. package/uscha-kit/.claude/skills/uscha-mirador/mirador.template.html +586 -0
  16. package/uscha-kit/.claude/skills/uscha-mirador/telemetry-extract.py +130 -0
  17. package/uscha-kit/.claude/skills/uscha-reverse-discovery/SKILL.md +116 -0
  18. package/uscha-kit/.claude/skills/uscha-rubric/SKILL.md +79 -0
  19. package/uscha-kit/.claude/skills/uscha-sysdoc/SKILL.md +88 -0
  20. package/uscha-kit/.claude-plugin/plugin.json +24 -0
  21. package/uscha-kit/.codex-plugin/plugin.json +37 -0
  22. package/uscha-kit/CHANGELOG-1.10.0.md +84 -0
  23. package/uscha-kit/CHANGELOG-1.11.0.md +67 -0
  24. package/uscha-kit/CHANGELOG-1.12.0.md +46 -0
  25. package/uscha-kit/CHANGELOG-1.13.0.md +33 -0
  26. package/uscha-kit/CHANGELOG-1.14.0.md +42 -0
  27. package/uscha-kit/CHANGELOG-1.15.0.md +58 -0
  28. package/uscha-kit/CHANGELOG-1.16.0.md +55 -0
  29. package/uscha-kit/CHANGELOG-1.17.0.md +44 -0
  30. package/uscha-kit/CHANGELOG-1.18.0.md +42 -0
  31. package/uscha-kit/CHANGELOG-1.19.0.md +41 -0
  32. package/uscha-kit/CHANGELOG-1.2.2.md +16 -0
  33. package/uscha-kit/CHANGELOG-1.2.3.md +20 -0
  34. package/uscha-kit/CHANGELOG-1.2.4.md +10 -0
  35. package/uscha-kit/CHANGELOG-1.2.5.md +23 -0
  36. package/uscha-kit/CHANGELOG-1.2.6.md +11 -0
  37. package/uscha-kit/CHANGELOG-1.2.7.md +15 -0
  38. package/uscha-kit/CHANGELOG-1.2.8.md +24 -0
  39. package/uscha-kit/CHANGELOG-1.2.9.md +4 -0
  40. package/uscha-kit/CHANGELOG-1.20.0.md +29 -0
  41. package/uscha-kit/CHANGELOG-1.21.0.md +33 -0
  42. package/uscha-kit/CHANGELOG-1.22.0.md +60 -0
  43. package/uscha-kit/CHANGELOG-1.23.0.md +75 -0
  44. package/uscha-kit/CHANGELOG-1.24.0.md +50 -0
  45. package/uscha-kit/CHANGELOG-1.25.0.md +55 -0
  46. package/uscha-kit/CHANGELOG-1.26.0.md +70 -0
  47. package/uscha-kit/CHANGELOG-1.27.0.md +45 -0
  48. package/uscha-kit/CHANGELOG-1.28.0.md +35 -0
  49. package/uscha-kit/CHANGELOG-1.29.0.md +20 -0
  50. package/uscha-kit/CHANGELOG-1.3.0.md +74 -0
  51. package/uscha-kit/CHANGELOG-1.30.0.md +46 -0
  52. package/uscha-kit/CHANGELOG-1.31.0.md +59 -0
  53. package/uscha-kit/CHANGELOG-1.32.0.md +50 -0
  54. package/uscha-kit/CHANGELOG-1.33.0.md +46 -0
  55. package/uscha-kit/CHANGELOG-1.34.0.md +55 -0
  56. package/uscha-kit/CHANGELOG-1.35.0.md +30 -0
  57. package/uscha-kit/CHANGELOG-1.36.0.md +33 -0
  58. package/uscha-kit/CHANGELOG-1.37.0.md +41 -0
  59. package/uscha-kit/CHANGELOG-1.38.0.md +11 -0
  60. package/uscha-kit/CHANGELOG-1.39.0.md +14 -0
  61. package/uscha-kit/CHANGELOG-1.4.0.md +68 -0
  62. package/uscha-kit/CHANGELOG-1.40.0.md +16 -0
  63. package/uscha-kit/CHANGELOG-1.40.1.md +11 -0
  64. package/uscha-kit/CHANGELOG-1.5.0.md +64 -0
  65. package/uscha-kit/CHANGELOG-1.6.0.md +57 -0
  66. package/uscha-kit/CHANGELOG-1.7.0.md +74 -0
  67. package/uscha-kit/CHANGELOG-1.8.0.md +46 -0
  68. package/uscha-kit/CHANGELOG-1.9.0.md +112 -0
  69. package/uscha-kit/LICENSE +21 -0
  70. package/uscha-kit/README.md +497 -0
  71. package/uscha-kit/VERSION +1 -0
  72. package/uscha-kit/WORKBENCH.md +178 -0
  73. package/uscha-kit/hooks/block-approved-writes.ps1 +46 -0
  74. package/uscha-kit/hooks/hooks.json +15 -0
  75. package/uscha-kit/install-uscha.py +344 -0
  76. package/uscha-kit/skills/uscha-adr-refine/SKILL.md +161 -0
  77. package/uscha-kit/skills/uscha-characterize/SKILL.md +124 -0
  78. package/uscha-kit/skills/uscha-devloop/SKILL.md +490 -0
  79. package/uscha-kit/skills/uscha-devloop/qa_ledger.py +5602 -0
  80. package/uscha-kit/skills/uscha-discovery/SKILL.md +161 -0
  81. package/uscha-kit/skills/uscha-mirador/SKILL.md +145 -0
  82. package/uscha-kit/skills/uscha-mirador/mirador-render.py +112 -0
  83. package/uscha-kit/skills/uscha-mirador/mirador-watch.ps1 +22 -0
  84. package/uscha-kit/skills/uscha-mirador/mirador-watch.sh +26 -0
  85. package/uscha-kit/skills/uscha-mirador/mirador.template.html +586 -0
  86. package/uscha-kit/skills/uscha-mirador/telemetry-extract.py +130 -0
  87. package/uscha-kit/skills/uscha-reverse-discovery/SKILL.md +116 -0
  88. package/uscha-kit/skills/uscha-rubric/SKILL.md +79 -0
  89. package/uscha-kit/skills/uscha-sysdoc/SKILL.md +88 -0
  90. package/uscha-kit/templates/.gitattributes +6 -0
  91. package/uscha-kit/templates/CLAUDE.md +56 -0
  92. package/uscha-kit/templates/CONSTITUTION.md +149 -0
  93. package/uscha-kit/templates/RUBRIC.md +38 -0
  94. package/uscha-kit/templates/docs/adr/README.md +19 -0
  95. package/uscha-kit/templates/rubric-grader-prompt.md +63 -0
  96. package/uscha-kit/tests/smoke-engine.sh +1739 -0
  97. package/uscha-kit/uscha.config.json +181 -0
  98. package/uscha-kit/workbench-doctor.sh +45 -0
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env python3
2
+ """telemetry-extract.py -- vendor telemetry for the mirador (uscha-kit 1.33.0).
3
+
4
+ Parses a Claude Code session transcript (*.jsonl) and appends ONE line to the
5
+ telemetry sidecar (.uscha/telemetry.jsonl) summarizing that session's token and
6
+ wall-time cost, broken down by model.
7
+
8
+ This is VENDOR telemetry (Claude Code), NOT engine measurement. It lives in the
9
+ mirador skill (the vendor adapter), NEVER in qa_ledger.py -- the engine stays
10
+ model-agnostic and never sees a token. The mirador shows this in a segregated
11
+ strip; it is never gated and never feeds readiness (measured beats narrated).
12
+
13
+ Usage:
14
+ python3 telemetry-extract.py <transcript.jsonl> [--sidecar .uscha/telemetry.jsonl] [--note "..."]
15
+
16
+ Best-effort: unknown/older transcript schemas degrade (missing fields -> 0/None),
17
+ never crash. If no usage data is found, nothing is appended.
18
+ """
19
+ import argparse
20
+ import json
21
+ import os
22
+ import sys
23
+ from datetime import datetime
24
+
25
+
26
+ def _ts(v):
27
+ if not v:
28
+ return None
29
+ try:
30
+ return datetime.fromisoformat(str(v).replace("Z", "+00:00"))
31
+ except (ValueError, TypeError):
32
+ return None
33
+
34
+
35
+ def extract(transcript_path):
36
+ """Returns (by_model: {model: [tin, tout]}, t_min, t_max, seen: bool)."""
37
+ by_model = {}
38
+ t_min = t_max = None
39
+ seen = False
40
+ with open(transcript_path, encoding="utf-8") as f:
41
+ for line in f:
42
+ line = line.strip()
43
+ if not line:
44
+ continue
45
+ try:
46
+ rec = json.loads(line)
47
+ except ValueError:
48
+ continue
49
+ ts = _ts(rec.get("timestamp"))
50
+ if ts:
51
+ t_min = ts if (t_min is None or ts < t_min) else t_min
52
+ t_max = ts if (t_max is None or ts > t_max) else t_max
53
+ msg = rec.get("message") or {}
54
+ usage = msg.get("usage")
55
+ if not isinstance(usage, dict):
56
+ continue
57
+ model = msg.get("model") or rec.get("model") or "unknown"
58
+ tin = ((usage.get("input_tokens") or 0)
59
+ + (usage.get("cache_read_input_tokens") or 0)
60
+ + (usage.get("cache_creation_input_tokens") or 0))
61
+ tout = usage.get("output_tokens") or 0
62
+ if tin == 0 and tout == 0:
63
+ continue
64
+ agg = by_model.setdefault(model, [0, 0])
65
+ agg[0] += tin
66
+ agg[1] += tout
67
+ seen = True
68
+ return by_model, t_min, t_max, seen
69
+
70
+
71
+ def main():
72
+ ap = argparse.ArgumentParser(description="append a Claude Code session's token/time cost to the mirador telemetry sidecar")
73
+ ap.add_argument("transcript", help="path to a Claude Code session transcript (*.jsonl)")
74
+ ap.add_argument("--sidecar", default=os.path.join(".uscha", "telemetry.jsonl"),
75
+ help="append-only telemetry file (default: .uscha/telemetry.jsonl)")
76
+ ap.add_argument("--session", default=None,
77
+ help="session key for idempotent upsert (default: transcript basename); "
78
+ "re-running replaces this session's line instead of appending -- "
79
+ "so a watch/refresh loop does not inflate the totals")
80
+ ap.add_argument("--note", default=None, help="optional label for this session line")
81
+ args = ap.parse_args()
82
+
83
+ by_model, t_min, t_max, seen = extract(args.transcript)
84
+ if not seen:
85
+ print("[telemetry-extract] no usage data found -- nothing appended", file=sys.stderr)
86
+ return 0
87
+
88
+ bm = [{"model": m, "tokens_in": v[0], "tokens_out": v[1]}
89
+ for m, v in sorted(by_model.items())]
90
+ total_in = sum(v["tokens_in"] for v in bm)
91
+ total_out = sum(v["tokens_out"] for v in bm)
92
+ ms = int((t_max - t_min).total_seconds() * 1000) if (t_min and t_max) else None
93
+ at = t_max.isoformat() if t_max else None
94
+ rec = {
95
+ "at": at,
96
+ "model": bm[0]["model"] if len(bm) == 1 else "+".join(v["model"] for v in bm),
97
+ "tokens_in": total_in, "tokens_out": total_out, "ms": ms,
98
+ "by_model": bm,
99
+ }
100
+ session = args.session or os.path.basename(args.transcript)
101
+ rec["session"] = session
102
+ if args.note:
103
+ rec["note"] = args.note
104
+
105
+ os.makedirs(os.path.dirname(args.sidecar) or ".", exist_ok=True)
106
+ # upsert by session: re-running (watch mode) REPLACES this session's line instead of
107
+ # appending a duplicate -> a refresh loop never inflates the aggregated totals.
108
+ kept = []
109
+ if os.path.isfile(args.sidecar):
110
+ for line in open(args.sidecar, encoding="utf-8"):
111
+ line = line.strip()
112
+ if not line:
113
+ continue
114
+ try:
115
+ prev = json.loads(line)
116
+ except ValueError:
117
+ continue
118
+ if prev.get("session") != session:
119
+ kept.append(prev)
120
+ kept.append(rec)
121
+ with open(args.sidecar, "w", encoding="utf-8") as f:
122
+ for r in kept:
123
+ f.write(json.dumps(r, ensure_ascii=False) + "\n")
124
+ print(f"[telemetry-extract] {session}: {total_in} in / {total_out} out "
125
+ f"across {len(bm)} model(s) -> {args.sidecar} (upsert)")
126
+ return 0
127
+
128
+
129
+ if __name__ == "__main__":
130
+ sys.exit(main())
@@ -0,0 +1,116 @@
1
+ ---
2
+ name: uscha-reverse-discovery
3
+ description: >
4
+ Brownfield front of the methodology, for migrating/modernizing an EXISTING system. The
5
+ inverse of discovery: the system already exists and its behavior IS the truth, so you
6
+ EXTRACT facts instead of proposing shape. Produce ONLY facts — a system map (endpoints,
7
+ contracts, dependency graph, module candidates via static analysis) and a golden suite
8
+ captured mechanically at the boundaries. NEVER author an inferred SPEC or ADR of the old
9
+ system; the human writes those reading your facts. Invoke for "reverse-discovery",
10
+ "migrar/modernizar este sistema", "caracterizar el sistema viejo antes de tocarlo".
11
+ allowed-tools: Read, Write, Glob, Grep, Bash
12
+ disable-model-invocation: false
13
+ ---
14
+
15
+ # reverse-discovery — extract the facts of an existing system before migrating it
16
+
17
+ `uscha-discovery` is greenfield: you only have an idea, so you PROPOSE the shape. This is the
18
+ opposite. The system already runs; its observable behavior is the ground truth. **You do
19
+ not invent anything — you characterize what is already there, as facts.**
20
+
21
+ ## The one non-negotiable: produce ONLY facts
22
+
23
+ A system map (from static analysis) and a golden suite (byte-captured) are FACTS —
24
+ verifiable, not opinions. **You do NOT author a SPEC of "what it does" or ADRs of "why it
25
+ is built this way."** Those are inference, and if the agent writes them it encodes its own
26
+ (mis)reading of the code — the exact blind spot the golden exists to counter. The golden
27
+ is field truth; a SPEC the agent writes about legacy code is a claim. So this skill emits
28
+ facts, and the human infers meaning from them.
29
+
30
+ If you catch yourself writing a requirement or a rationale, stop: that belongs to the human
31
+ (and to `/uscha-adr-refine` for the FORWARD decisions), not here.
32
+
33
+ ## Phase 1 — Map (fact)
34
+
35
+ Static analysis only. Extract, and write to `SYSTEM-MAP.md`:
36
+ - **Boundaries**: every public endpoint / operation / message topic, with its contract
37
+ (request/response schema, status codes, idempotency where observable).
38
+ - **Dependency graph**: who calls whom (service→service, module→module, DB, external APIs).
39
+ Flag cycles and hubs.
40
+ - **Module candidates**: current package/service structure and natural seams (this is
41
+ observation of the CURRENT layout, NOT a proposal for the new one).
42
+ Everything here must be traceable to code. No "this seems to…", no guessed intent.
43
+
44
+ ## Phase 2 — Characterize (fact)
45
+
46
+ Capture the golden at the system boundaries by running the ORIGINAL code with REAL inputs.
47
+ Delegate to the `uscha-characterize` skill; if it is not installed, follow its contract inline:
48
+ - Write a deterministic capture harness (you MAY author the harness).
49
+ - Normalize every non-determinism source before serializing: timestamps, seeds, map/set
50
+ iteration order, GUIDs/auto-increment, concurrency, **the target locale**, and use
51
+ deterministic serialization (sorted keys, fixed float precision, explicit encoding).
52
+ - Run the capture → `.received`. **STOP.** Return control to the human to review and
53
+ approve the `.approved`.
54
+ - **You NEVER create, rename, or edit a `.approved` file** (INV-GOLDEN-01). It is field
55
+ truth; only a human approves it.
56
+ - Corpus in order of value: real/recorded production inputs → hand-built edge cases →
57
+ inputs of past bugs. A boundary whose corpus does not exercise its known branches is
58
+ marked **PARTIAL**, never covered.
59
+
60
+ ## Phase 3 — Summary (facts, no opinion)
61
+
62
+ Write `DISCOVERY-SUMMARY.md`: the system map + the golden coverage report (which boundaries
63
+ are captured and approved, which are PARTIAL and why). This is the fact base the human reads
64
+ to write the migration SPEC. Do not editorialize.
65
+
66
+ ## What you do NOT do (the human's job)
67
+
68
+ - Do NOT write a SPEC of the old system's behavior — the golden IS the executable spec.
69
+ - Do NOT write ADRs of the old system's implicit decisions.
70
+ - Do NOT decide the NEW structure (module boundaries, shared kernel, sync vs events). Those
71
+ are forward decisions → `/uscha-adr-refine`.
72
+
73
+ ## Guardrails
74
+
75
+ - `.approved` files are sacred and human-approved; the agent is mechanically forbidden from
76
+ writing them (a `PreToolUse` hook on `**/*.approved.*` should enforce it).
77
+ - `.gitattributes`: `*.approved.* binary` (line endings must not create false diffs).
78
+ - Corpus insufficient → PARTIAL. Never claim coverage you did not exercise.
79
+
80
+ ## Convergence — finish when
81
+
82
+ The map is complete (every boundary and dependency accounted for, or explicitly marked
83
+ unknown), the golden is captured and **human-approved**, and the coverage report states
84
+ what is covered vs PARTIAL. State plainly that the facts are ready, then hand off.
85
+
86
+ ## Handoff
87
+
88
+ > "Read SYSTEM-MAP.md and DISCOVERY-SUMMARY.md, and inspect the approved golden. These are
89
+ > FACTS about the current system. Now write the migration SPEC — behavior == golden,
90
+ > structure == the new module boundaries — and take the partition decisions via /uscha-adr-refine.
91
+ > Do not treat any of my output as a requirement or a rationale; those are yours to decide."
92
+
93
+ Flow (migration): `uscha-reverse-discovery` (facts) → human writes SPEC + `/uscha-adr-refine` (forward
94
+ module decisions) → `/uscha-devloop` (restructure; `golden-diff` + `ApplicationModules.verify()`
95
+ stay green the whole way) → readiness + human gate.
96
+
97
+ ## Relationship to the other skills
98
+
99
+ - **discovery** (greenfield): you PROPOSE the shape from an idea. **reverse-discovery**
100
+ (brownfield): you EXTRACT facts from a running system. Opposite direction, opposite trust
101
+ model.
102
+ - **characterize**: the golden-capture sub-step this skill orchestrates in Phase 2.
103
+ - **adr-refine**: where the FORWARD decisions (the new module boundaries) are taken — this
104
+ skill deliberately does not.
105
+
106
+ ## Why this skill is safe by construction
107
+
108
+ It lives entirely on the FACTS side of the facts-vs-prose line: static analysis and
109
+ byte-capture, both verifiable. Unlike a prose-heuristic gate, there is no interpretation to
110
+ get wrong — so there is nothing fragile to break. If a step would require guessing, it has
111
+ left this skill's scope.
112
+
113
+ ## Tracked-markdown protocol
114
+
115
+ If `SYSTEM-MAP.md` / `DISCOVERY-SUMMARY.md` already exist and are tracked, ask for the
116
+ current version before overwriting — never silently replace real progress.
@@ -0,0 +1,79 @@
1
+ ---
2
+ name: uscha-rubric
3
+ description: >
4
+ Grade the change against the versioned RUBRIC.md (the ACCEPTANCE of the
5
+ non-testable: conventions, error-handling sanity, API ergonomics, doc quality)
6
+ and ingest the verdict into the ledger. This skill is a THIN ADAPTER for
7
+ Claude Code: the portable core is templates/rubric-grader-prompt.md (works on
8
+ Codex, Gemini CLI, Cursor, raw API, or a human) + the vendor-neutral JSON
9
+ contract that `qa_ledger.py rubric-ingest` validates. Advisory by default;
10
+ gates only when the human declares it. Invoke for "grade the rubric",
11
+ "evaluá la rúbrica", "rubric pass".
12
+ allowed-tools: Read, Write, Glob, Grep, Bash
13
+ disable-model-invocation: false
14
+ ---
15
+
16
+ # uscha-rubric — grade the non-testable against versioned criteria (adapter)
17
+
18
+ **Architecture note (read this first).** You are the Claude Code ADAPTER of a
19
+ vendor-neutral layer. The core is: `RUBRIC.md` (versioned criteria) + the JSON
20
+ contract + `qa_ledger.py rubric-ingest` (stdlib, runs anywhere). ANY runner can be
21
+ the grader — this skill just wraps the neutral prompt so Claude Code users get it
22
+ in one command. Never add Claude-specific behavior to the contract.
23
+
24
+ ## Protocol
25
+
26
+ 1. **Locate the rubric**: `defaults.rubric.file` in `uscha.config.json`, else
27
+ `./RUBRIC.md`. If absent, offer to create one from `templates/RUBRIC.md` and STOP
28
+ (the criteria are the human's to approve — propose, don't impose).
29
+ 2. **Validate structure first** (facts block):
30
+
31
+ ```bash
32
+ QL="./.claude/skills/uscha-devloop/qa_ledger.py"
33
+ [ -f "$QL" ] || QL="$HOME/.codex/skills/uscha-devloop/qa_ledger.py"
34
+ [ -f "$QL" ] || QL="$HOME/plugins/uscha/skills/uscha-devloop/qa_ledger.py"
35
+ [ -f "$QL" ] || QL="$HOME/.claude/skills/uscha-devloop/qa_ledger.py"
36
+ python3 $QL spec-check --rubric RUBRIC.md # exit 1 = fix the rubric before grading
37
+ ```
38
+
39
+ 3. **Grade with ISOLATED context** — follow `templates/rubric-grader-prompt.md` to
40
+ the letter: read ONLY the diff + RUBRIC.md (not the maker's reasoning, not the PR
41
+ body). For every criterion emit `pass|fail`; **evidence `file:line` is mandatory
42
+ for any verdict that affects the score** (a positive's pass, a negative's fail) —
43
+ without it the engine discards the verdict. Anchors calibrate you; when in doubt,
44
+ fail (the optimist bias is the failure mode this layer exists to counter).
45
+ 4. **Write the contract JSON** to `reports/rubric-grade.json`:
46
+
47
+ ```json
48
+ {"criteria": [{"id": "RB-01", "verdict": "pass",
49
+ "evidence": "src/x.py:42 — ...", "note": "..."}]}
50
+ ```
51
+
52
+ 5. **Ingest** (the ledger validates IDs, applies evidence-or-nothing, computes the
53
+ weighted score vs threshold, and persists — advisory by default):
54
+
55
+ ```bash
56
+ python3 $QL rubric-ingest --repo <REPO> --report reports/rubric-grade.json \
57
+ --iteration <N> # add --gate ONLY if the human declared it
58
+ ```
59
+
60
+ A below-threshold score with the gate declared (config `defaults.rubric.gate: true`
61
+ or `--gate`) blocks convergence and caps readiness ≤65 through the existing ledger
62
+ plumbing. Without the declaration it advises — never silently escalate it yourself.
63
+
64
+ ## Non-negotiables
65
+
66
+ - **Maker ≠ grader**: never grade a change you authored in this same context. Run
67
+ the grade in a fresh/isolated pass (that separation is the entire value).
68
+ - **Evidence-or-nothing**: a verdict without a `file:line` citation does not count —
69
+ the engine enforces it, you comply with it.
70
+ - The rubric file is the HUMAN's criterion: propose edits, never rewrite it silently
71
+ (tracked-markdown protocol applies).
72
+ - This layer never replaces the hard gates (tests, golden, gate-check, simplicity):
73
+ it is the structured-guess layer — facts block, guesses advise.
74
+
75
+ ## Relationship to the other skills
76
+
77
+ - `uscha-devloop` runs this in Phase 3b alongside gate-check when a rubric exists.
78
+ - `uscha-discovery` / `uscha-adr-refine` are where the human's quality criteria
79
+ crystallize — a RUBRIC.md can be drafted there (step: quality bar).
@@ -0,0 +1,88 @@
1
+ ---
2
+ name: uscha-sysdoc
3
+ description: >
4
+ Generate a single self-contained, navigable HTML deck (PowerPoint-style, keyboard +
5
+ click navigation) documenting a system in two parallel tracks: a commercial/CEO view
6
+ and a technical view. Pulls real metrics from QA-LEDGER.json, includes inline SVG
7
+ diagrams, dark control-room aesthetic. Invoke for "document this system",
8
+ "make the system deck", "commercial + tech doc". Pairs with the dev-loop skill.
9
+ allowed-tools: Read, Write, Glob, Grep, Bash
10
+ disable-model-invocation: false
11
+ ---
12
+
13
+ # sys-doc — two-view system deck generator
14
+
15
+ Produce ONE self-contained `.html` file (no external assets, no CDN, no localStorage)
16
+ that reads like a slide deck and documents the system on two tracks the reader can
17
+ switch between at any time:
18
+
19
+ - **Commercial / CEO track** — what the system does, the value, the risk posture, the
20
+ status. No code. Plain business language. Money/time/reliability framing.
21
+ - **Technical track** — architecture, modules, data flow, contracts, QA results,
22
+ coverage, known deferred issues.
23
+
24
+ ## Inputs
25
+
26
+ 1. **Metrics (authoritative):** run the ledger summary and use its numbers verbatim —
27
+ never invent figures.
28
+
29
+ ```bash
30
+ QL="./.claude/skills/uscha-devloop/qa_ledger.py" # instalacion por proyecto
31
+ [ -f "$QL" ] || QL="$HOME/.codex/skills/uscha-devloop/qa_ledger.py" # Codex raw-skills install
32
+ [ -f "$QL" ] || QL="$HOME/plugins/uscha/skills/uscha-devloop/qa_ledger.py" # Codex plugin install
33
+ [ -f "$QL" ] || QL="$HOME/.claude/skills/uscha-devloop/qa_ledger.py" # Claude global install
34
+ python3 $QL summary --json > /tmp/qa-summary.json
35
+ python3 $QL readiness --json > /tmp/qa-readiness.json
36
+ ```
37
+
38
+ From the summary use: `total_steps`, `by_tool`, `by_repo`, `aggregate`, `escalations`.
39
+ From readiness use: `score`, `status`, `cap_reason`, `dimensions`, `acceptance`,
40
+ `by_repo`. Render readiness as a **semaphore widget** at the top of slide 5 and as a
41
+ per-repo readiness column on the technical QA slide: green ≥80, amber 50–79, red <50,
42
+ and always print the `cap_reason` when a hard cap is active.
43
+
44
+ 2. **System understanding:** read the ADR/PLAN, CLAUDE.md, module layout, and key
45
+ contracts to describe architecture and value. If no ledger exists, ask whether to
46
+ proceed without QA metrics (the deck still works, just without the QA section).
47
+
48
+ 3. **Tracked-markdown protocol:** the HTML output itself is not tracked markdown, so
49
+ generate freely. But if asked to also update a tracked `.md`, ask for its current
50
+ version first.
51
+
52
+ ## Structure (each is one navigable slide)
53
+
54
+ 1. **Title** — system name, one-line purpose, date, run id.
55
+ 2. **Track switcher** — persistent toggle: Commercial ⇄ Technical (affects which
56
+ slides/sections show; default Commercial).
57
+ 3. Commercial: **What it does** (plain language, the job it removes).
58
+ 4. Commercial: **Value & status** (what's done, what's in flight, risk posture).
59
+ 5. Commercial: **Quality at a glance** — coverage %, tests count, a simple
60
+ "issues found and resolved" readout from `by_tool`. No jargon.
61
+ 6. Technical: **Architecture** — inline SVG: modules/repos as boxes, data flow as
62
+ arrows, external systems (DB, external APIs, devices) distinct.
63
+ 7. Technical: **Key contracts / interfaces** — the seams between repos/modules.
64
+ 8. Technical: **QA results** — per-tool table (reported / fixed / %fixed / deferred /
65
+ suppressed), coverage per repo, tests/kLOC, escalations list.
66
+ 9. Technical: **Deferred issues** — summarize `ISSUES-DEFERRED.md` honestly.
67
+ 10. **Smoke checklist** — the manual verification steps.
68
+
69
+ ## Build constraints
70
+
71
+ - Single `.html`, all CSS/JS inline. Works opened directly from disk and deployable to
72
+ Cloudflare Pages / S3 as-is.
73
+ - **No localStorage / sessionStorage** (won't run in some sandboxes). Hold nav state in
74
+ JS variables only.
75
+ - Navigation: arrow keys (← →), on-screen prev/next, a slide index/dots, and Esc for an
76
+ overview grid. Slide counter visible.
77
+ - Diagrams are hand-authored inline `<svg>` using `currentColor`/CSS variables so they
78
+ theme with the deck. No raster images, no external diagram libs.
79
+ - Aesthetic: dark control-room (deep neutral background, one accent, high-contrast
80
+ mono for technical figures), but keep the Commercial track clean and uncluttered.
81
+ - Accessible: semantic headings, `aria-label`s on nav controls, visible focus, contrast
82
+ AA. Readable when printed (print stylesheet flattens slides to a linear document).
83
+
84
+ ## Output
85
+
86
+ Write to `docs/system-deck.html` (or the path the human gives). Then state the file
87
+ path and the two or three things the reader should look at first. Do not paste the HTML
88
+ into chat — present the file.
@@ -0,0 +1,24 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
+ "name": "uscha",
4
+ "version": "1.40.1",
5
+ "displayName": "Uscha",
6
+ "description": "Spec-driven development for LLM coding agents: 8 skills (discovery, adr-refine, reverse-discovery, characterize, devloop, sysdoc, rubric, mirador) + a stdlib measurement engine (qa_ledger.py, 29 subcommands + universal installer + npm/npx router). Facts block, guesses advise; the human approves.",
7
+ "author": {
8
+ "name": "Andres Massello",
9
+ "url": "https://github.com/andresmassello"
10
+ },
11
+ "homepage": "https://github.com/andresmassello/uscha",
12
+ "repository": "https://github.com/andresmassello/uscha",
13
+ "license": "MIT",
14
+ "keywords": [
15
+ "spec-driven",
16
+ "qa",
17
+ "gates",
18
+ "golden-testing",
19
+ "readiness",
20
+ "methodology"
21
+ ],
22
+ "skills": "./.claude/skills/",
23
+ "hooks": "./hooks/hooks.json"
24
+ }
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "uscha",
3
+ "version": "1.40.1",
4
+ "description": "Uscha spec-driven development methodology for coding agents. Includes npm/npx router.",
5
+ "author": {
6
+ "name": "Andres Massello",
7
+ "url": "https://github.com/andresmassello"
8
+ },
9
+ "homepage": "https://github.com/andresmassello/uscha",
10
+ "repository": "https://github.com/andresmassello/uscha",
11
+ "license": "MIT",
12
+ "keywords": [
13
+ "spec-driven",
14
+ "qa",
15
+ "gates",
16
+ "golden-testing",
17
+ "readiness"
18
+ ],
19
+ "skills": "./skills/",
20
+ "interface": {
21
+ "displayName": "Uscha",
22
+ "shortDescription": "Spec-driven development with fact gates and readiness.",
23
+ "longDescription": "Uscha installs discovery, ADR, characterization, devloop, rubric, sysdoc and Mirador skills plus the qa_ledger.py evidence engine.",
24
+ "developerName": "Andres Massello",
25
+ "category": "Productivity",
26
+ "capabilities": [
27
+ "Write",
28
+ "Interactive"
29
+ ],
30
+ "defaultPrompt": [
31
+ "Run Uscha discovery for this feature.",
32
+ "Use Uscha devloop to verify this change.",
33
+ "Show the Uscha readiness for this repo."
34
+ ],
35
+ "brandColor": "#7C3AED"
36
+ }
37
+ }
@@ -0,0 +1,84 @@
1
+ # dev-loop-kit 1.10.0 — acceptance trazable: AC-n cierra por testcase MEDIDO (2026-07-02)
2
+
3
+ Primera mejora del backlog PragProg (M2 de `docs/analisis-pragmatic-programmer.md`;
4
+ Topic 50 "Do What Works" + la anécdota Jeffries/Sudoku + Tip 94 "Find Bugs Once").
5
+ Ataca el modo de falla típico del agente: **pulir la métrica sin acercarse a la
6
+ solución**. El readiness deja de estar dominado por coverage/tests-verdes y pasa a
7
+ estar dominado por **criterios de aceptación cerrados con evidencia medida**.
8
+ Smoke suite: 68/68.
9
+
10
+ ## La idea (measured beats narrated, ahora a nivel CRITERIO)
11
+
12
+ - Cada criterio de `ACCEPTANCE.md` lleva un ID estable: `- [ ] AC-01 — cuando X
13
+ entonces Y`.
14
+ - Un criterio cierra **MEDIDO** solo cuando existe ≥1 testcase VERDE cuyo nombre
15
+ lleva el tag (`test_ac1_x`, `testAC01X`, `"AC-01: ..."`) en los reportes JUnit
16
+ que el engine ya ingiere — y **ningún** testcase taggeado en rojo (evidencia
17
+ roja veta: fail-closed).
18
+ - El checkbox es RELATO; el testcase es HECHO. Un `[x]` sin test verde se
19
+ reporta como `narrated_only` y NO cierra.
20
+
21
+ ## Engine (qa_ledger.py)
22
+
23
+ - `_parse_acceptance_items()`: parser de checkboxes con ID opcional; IDs
24
+ normalizados por número (`AC-01 == AC_1 == ac1` — los nombres de test de
25
+ python/go no admiten `-`).
26
+ - `_ac_tags()`: scan de NOMBRES de testcase en los reportes JUnit por type
27
+ (reusa el selector de ubicaciones vía `_junit_report_files()`, extraído para
28
+ no duplicar la lista — surefire/gradle per-clase, junit-family, dual-file
29
+ swift; flutter no emite JUnit → sus criterios no cierran medido, documentado).
30
+ Boundaries explícitos en el regex del tag: `\b` NO sirve (`_` es word char y
31
+ `test_ac1` quedaría invisible); soporta separador no-alfanumérico y camelCase.
32
+ - `readiness`: nueva dimensión **acceptance** (dominante, peso 30) = criterios
33
+ cerrados medidos / criterios totales (un criterio sin ID no puede cerrar →
34
+ cuenta como abierto). Pesos default rebalanceados:
35
+ acceptance 30 · adr 15 · coverage 15 · static 20 · convergencia 10 ·
36
+ integración 10 — el techo a coverage/verde es el anti-Goodhart. JSON expone
37
+ `traceable/ids/measured_closed/narrated_only/measured_unchecked/untagged`;
38
+ warnings en texto para narrated-only y sin-IDs.
39
+ - **Fallback legacy**: ACCEPTANCE sin ningún AC-ID → la dimensión cae al ratio
40
+ de checkboxes con warning (adopción incremental, no rotura retroactiva).
41
+ - `spec-check --acceptance ACCEPTANCE.md`: la trazabilidad es estructura =
42
+ FACT → bloquea: archivo ausente, cero criterios, CERO criterios trazables,
43
+ IDs duplicados (normalizados). Criterios sueltos sin ID = advisory. Puede
44
+ correr solo (sin `--spec`).
45
+
46
+ ## Skills / docs
47
+
48
+ - `discovery`: ACCEPTANCE se genera con AC-NN secuenciales, nunca reusados;
49
+ cada criterio pensado para ser cubrible por un test con nombre.
50
+ - `dev-loop`: al escribir los tests de un criterio, el tag AC-n va en el nombre
51
+ del test; `spec-check --acceptance` al arrancar.
52
+
53
+ ## Hardening (review fresco pre-commit, 10 hallazgos aplicados)
54
+
55
+ - `_ac_tags`: el tag ahora lee SOLO el nombre del testcase (nunca classname) —
56
+ un módulo/clase que matchea "ACn" por coincidencia (`test_ac3_flow.py`) ya no
57
+ contamina los OTROS tests del mismo archivo.
58
+ - `_AC_ID`: tolera IDs markdown-formateados (`**AC-01**`, `` `AC-01` ``) — antes
59
+ degradaban en silencio a `id=None` y toda la trazabilidad caía a legacy.
60
+ - `readiness`: IDs duplicados en ACCEPTANCE cuentan **una sola vez** (antes un
61
+ test verde podía cerrar "medido" tantos criterios como copias del ID).
62
+ - `readiness`: config pre-1.10.0 con `readiness_weights` explícitos que no
63
+ conocían `acceptance` ya no la reciben inyectada por default — se excluye
64
+ (peso 0) con warning hasta que el usuario la agregue o taggee AC-IDs (si no,
65
+ duplicaba el peso de `adr` en silencio).
66
+ - `readiness`: `--section` sin match ahora avisa (`0 criterios en scope`) en
67
+ vez de zonear en silencio adr+acceptance.
68
+ - `readiness`: ledger sin `config.repos` ya no crashea (KeyError) — usa
69
+ `.get("repos", [])` como el resto del comando.
70
+ - `spec-check --acceptance`: pipear un SPEC por stdin junto con `--acceptance`
71
+ ya no se descarta en silencio — se lee stdin salvo modo interactivo puro
72
+ acceptance-only.
73
+ - `spec-check --acceptance --strict`: los criterios sin AC-ID ahora gatean
74
+ `--strict` (antes el verdict imprimía "OK" con advisories pendientes).
75
+ - Documentado (no resuelto): reportes JUnit stale de maven/gradle pueden
76
+ vetear/cerrar un AC sin evidencia vigente — mismo límite que
77
+ `junit_test_count`, ahora con blast radius mayor. Mitigación real (mtime +
78
+ correlación con el árbol de fuentes) diferida.
79
+
80
+ ## Diferido consciente
81
+
82
+ - El resto del backlog PragProg (M1 regression-capture, M3 ledger atómico,
83
+ M8 secret-scan, M9 tests fuera del presupuesto de simplicity, etc.) sigue en
84
+ `docs/analisis-pragmatic-programmer.md` — una mejora por release.
@@ -0,0 +1,67 @@
1
+ # dev-loop-kit 1.11.0 — tests fuera del presupuesto de simplicity (2026-07-03)
2
+
3
+ Segunda mejora del backlog PragProg (M9 de `docs/analisis-pragmatic-programmer.md`;
4
+ Topic 51: *"un buen proyecto puede tener MÁS código de test que de producción, y
5
+ vale la pena"*). Elimina un **incentivo perverso activo**: el simplicity-check
6
+ contaba las líneas de test junto a las de producción contra un único presupuesto —
7
+ el gate castigaba escribir tests y empujaba al agente a testear menos para pasar.
8
+ Smoke suite: 73/73.
9
+
10
+ ## La idea
11
+
12
+ - Escribir tests **nunca** acerca un diff a OVERBUILT. Los archivos de test se
13
+ detectan, se cuentan y se **reportan aparte** (`test_lines_added`,
14
+ `test_files_changed`) — pero no gatean ninguna dimensión del score.
15
+ - La otra dirección ya estaba protegida: **borrar** tests lo bloquea gate-check.
16
+ Con esto el incentivo queda alineado en ambas direcciones.
17
+
18
+ ## Engine (qa_ledger.py)
19
+
20
+ - `_is_simplicity_test_file()`: clasificador type-agnóstico (el diff no trae
21
+ `repo_type`) — unión de las convenciones de los 9 stacks: dirs
22
+ `test/tests/__tests__/Tests/*.Tests`, source sets Gradle (`src/*Test/`),
23
+ `test_*.py`, `*_test.go`, `*.test.ts`/`*.spec.js` (multi-dot incluido),
24
+ `*Test.java`/`*Tests.cs` CamelCase **case-sensitive** — `backtest.cpp` /
25
+ `protest.cc` siguen contando como producción (misma trampa que ya evitan
26
+ dotnet/cpp en `_is_test_path`).
27
+ - Dirección de fallo benigna y documentada: un falso positivo solo EXIME del
28
+ presupuesto — nunca bloquea ni borra nada.
29
+ - `_simplicity_metrics()`: tercer estado de conteo (`prod`/`test`/fuera);
30
+ las líneas de test no alimentan `lines_added`, `net_lines`, `files_changed`,
31
+ `max_nesting`, `max_hunk_added` ni abstracciones. Output humano: línea
32
+ informativa "tests FUERA del presupuesto: +N líneas en M archivo(s)".
33
+
34
+ ## Smoke
35
+
36
+ - **T32**: diff sintético 6 líneas prod + 302 de test → el presupuesto ve 6/1;
37
+ batería del clasificador (9 convenciones positivas + backtest/protest/Engine
38
+ negativas).
39
+ - **T31** (edges 1.10.0, deuda del release anterior): batería de falsos
40
+ positivos del tag regex (`HVAC2`, `mac1`, `track12` no taggean), classname
41
+ jamás taggea, y semántica flaky de surefire (`<flakyFailure>` que pasó tras
42
+ retry = verde; `<failure>`+`<rerunFailure>` = rojo, veta).
43
+
44
+ ## Hardening (review fresco pre-commit)
45
+
46
+ - El review detectó que gate-check tenía SU PROPIO clasificador de tests
47
+ (`_gc_is_test_file`) más débil: no reconocía `foo_test.go` (Go),
48
+ `*.Tests/*.cs` (dotnet), `*.spec.tsx` ni `__tests__/` — así que "borrar
49
+ tests lo bloquea gate-check" era overclaim para 4+ stacks. Fix: unión
50
+ fail-closed — gate-check reusa el clasificador compartido de los 9 stacks
51
+ MÁS sus sufijos legacy; solo se AMPLÍA qué cuenta como test, ningún path
52
+ antes protegido se desprotege.
53
+ - `_GC_TESTDEF` ampliado con las definiciones de test que faltaban:
54
+ `func TestX` (Go), `[Fact]`/`[Theory]` (xunit), `#[test]` (rust),
55
+ `it(`/`test(`/`describe(` (js) — seguro porque TESTDEF solo se evalúa
56
+ dentro de archivos ya clasificados como test.
57
+ - Smoke **T33**: borrado de tests Go/dotnet/JS → BLOCKER (antes invisible).
58
+ - Corrección truth-pass en `dev-loop-kit/README.md`: los pesos documentados
59
+ de simplicity (`diff_size 30, nesting 25, abstraction 20...`) no coincidían
60
+ con el engine (`35/30/20/8/7`, abstraction advisory sin peso) — drift
61
+ pre-existente, alineado acá.
62
+
63
+ ## Diferido consciente
64
+
65
+ - El resto del backlog PragProg (M1 regression-capture, M3 ledger atómico,
66
+ M8 secret-scan, etc.) sigue en `docs/analisis-pragmatic-programmer.md` —
67
+ una mejora por release.