@andresmassello/uscha 1.67.0 → 1.68.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.67.0** <!-- uscha:version --> · [uscha.dev](https://uscha.dev) ·
43
+ **Kit v1.68.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
 
@@ -76,7 +76,7 @@ and see which file, which test, and when.
76
76
  | `/uscha-mirador` | Bird's-eye HTML dashboard: readiness, trail, acceptance, loops |
77
77
  | `/uscha-status` | One-line progress readout, in chat |
78
78
 
79
- **A measurement engine** (`qa_ledger.py`, 35 subcommands, Python stdlib) that ingests
79
+ **A measurement engine** (`qa_ledger.py`, 36 subcommands, Python stdlib) that ingests
80
80
  evidence from **11 language stacks** — maven, gradle, ant, python, node, go, rust, dotnet,
81
81
  cpp, swift, flutter — and computes a readiness score with hard caps and visible provenance.
82
82
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andresmassello/uscha",
3
- "version": "1.67.0",
3
+ "version": "1.68.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",
@@ -3862,6 +3862,142 @@ def cmd_curation_check(args):
3862
3862
 
3863
3863
 
3864
3864
 
3865
+ # --------------------------------------------------------------------------- #
3866
+ # facts (T0 / SYSTEM-FACTS: public claims become compiled artifacts of repo
3867
+ # facts -- Diamond applied to Diamond. ADR-012.)
3868
+ # --------------------------------------------------------------------------- #
3869
+
3870
+ FACTS_FILE = "SYSTEM-FACTS.json"
3871
+
3872
+
3873
+ def _derive_facts():
3874
+ """Facts derived from the ARTIFACTS themselves, never from prose and never from greps
3875
+ over documentation: the subcommand list comes from introspecting the REAL parser, the
3876
+ skill list from the REAL kit tree, the version from the kit VERSION file. No timestamp
3877
+ on purpose: regeneration over an unchanged repo must be byte-identical (AC-SF-01)."""
3878
+ here = os.path.abspath(__file__)
3879
+ # kit root by MARKER, not by fixed depth: the canonical engine sits 4 levels deep
3880
+ # (.claude/skills/uscha-devloop/) and the Codex twin 3 (skills/uscha-devloop/). A fixed
3881
+ # dirname walk made the twin silently derive the OUTER repo root -- version None,
3882
+ # 0 skills, no error (fresh-review HIGH, reproduced by running both copies).
3883
+ kit, cur = None, os.path.dirname(here)
3884
+ for _ in range(6):
3885
+ if os.path.isfile(os.path.join(cur, "VERSION")):
3886
+ kit = cur
3887
+ break
3888
+ nxt = os.path.dirname(cur)
3889
+ if nxt == cur:
3890
+ break
3891
+ cur = nxt
3892
+ if kit is None:
3893
+ print("[qa_ledger] facts: no VERSION file found walking up from the engine -- "
3894
+ "facts that cannot locate their own kit are not facts.", file=sys.stderr)
3895
+ sys.exit(2)
3896
+ with open(os.path.join(kit, "VERSION"), encoding="utf-8") as fh:
3897
+ version = fh.read().split()[-1].strip()
3898
+ subs = []
3899
+ for action in build_parser()._subparsers._group_actions:
3900
+ subs = sorted(action.choices.keys())
3901
+ skills = []
3902
+ for sdir in (os.path.join(kit, ".claude", "skills"), os.path.join(kit, "skills")):
3903
+ # canonical tree first; the Codex install ships only skills/ -- same inventory
3904
+ if os.path.isdir(sdir):
3905
+ skills = sorted(d for d in os.listdir(sdir)
3906
+ if os.path.isfile(os.path.join(sdir, d, "SKILL.md")))
3907
+ break
3908
+ return {
3909
+ "version": version,
3910
+ "subcommands": {"count": len(subs), "list": subs},
3911
+ "skills": {"count": len(skills), "list": skills},
3912
+ "_derivation": {
3913
+ "version": "uscha-kit/VERSION",
3914
+ "subcommands": "argparse introspection of build_parser()",
3915
+ "skills": "SKILL.md inventory under uscha-kit/.claude/skills/",
3916
+ "omitted": "stack matrix and REAL/VISION registry: no mechanical "
3917
+ "source exists yet -- omitted, not guessed (ADR-012)",
3918
+ },
3919
+ }
3920
+
3921
+
3922
+ _CLAIM_PATTERNS = (
3923
+ # (fact key path, regex over one line, needs-context substring or None)
3924
+ ("version", r"v(\d+\.\d+\.\d+)", "kit"),
3925
+ ("version", r"uscha-kit\s+v?(\d+\.\d+\.\d+)", None),
3926
+ ("subcommands.count", r"(\d+)\s+sub-?comm?ands", None),
3927
+ ("subcommands.count", r"(\d+)\s+subcomandos", None),
3928
+ ("skills.count", r"(\d+)\s+skills", None),
3929
+ )
3930
+
3931
+
3932
+ def _fact_value(facts, dotted):
3933
+ cur = facts
3934
+ for part in dotted.split("."):
3935
+ cur = cur[part]
3936
+ return str(cur)
3937
+
3938
+
3939
+ def cmd_facts(args):
3940
+ """Generate SYSTEM-FACTS.json, or --check published claims against the derived facts.
3941
+
3942
+ The founding fixture (recorded in ADR-012): the site claimed kit 1.65.0 with 32 engine
3943
+ subcommands while the repo was at 1.67.0 with 35 -- factual drift, live, in the project
3944
+ about factual drift. A claim that CI does not compare against a derived fact will
3945
+ drift; this makes the comparison mechanical and the drift a named red."""
3946
+ facts = _derive_facts()
3947
+ if args.check:
3948
+ problems = []
3949
+ # 1) the committed facts file must match a fresh derivation (stale facts are drift)
3950
+ if os.path.isfile(args.out):
3951
+ with open(args.out, encoding="utf-8") as fh:
3952
+ committed = fh.read()
3953
+ fresh = json.dumps(facts, indent=2, ensure_ascii=False, sort_keys=True) + "\n"
3954
+ if committed.replace("\r\n", "\n") != fresh:
3955
+ problems.append((args.out, 0, "committed facts file",
3956
+ "stale vs regenerated", "run: qa_ledger.py facts"))
3957
+ else:
3958
+ problems.append((args.out, 0, "facts file", "absent",
3959
+ "run: qa_ledger.py facts"))
3960
+ # 2) every recognizable claim in the given files must equal the derived fact
3961
+ for path in args.check:
3962
+ try:
3963
+ with open(path, encoding="utf-8", errors="replace") as fh:
3964
+ lines = fh.read().splitlines()
3965
+ except OSError as exc:
3966
+ problems.append((path, 0, "file", "unreadable: %s" % exc, ""))
3967
+ continue
3968
+ for n, line in enumerate(lines, 1):
3969
+ # an HTML comment is not a published claim -- the first live run flagged a
3970
+ # section marker (a comment reading "2 Skills") as a drifted count
3971
+ line = re.sub(r"<!--.*?-->", "", line)
3972
+ low = line.lower()
3973
+ for key, pat, ctx in _CLAIM_PATTERNS:
3974
+ if ctx and ctx not in low:
3975
+ continue
3976
+ for m in re.finditer(pat, line, re.I):
3977
+ claimed = m.group(1)
3978
+ actual = _fact_value(facts, key)
3979
+ if claimed != actual:
3980
+ problems.append((path, n, key, claimed, actual))
3981
+ if problems:
3982
+ print("FACTUAL DRIFT: %d claim(s) disagree with the derived facts"
3983
+ % len(problems))
3984
+ for path, n, key, claimed, actual in problems:
3985
+ loc = "%s:%d" % (path, n) if n else path
3986
+ print(" !! %s: %s claims %r, the artifact says %r"
3987
+ % (loc, key, claimed, actual))
3988
+ sys.exit(1)
3989
+ print("FACTS: %d file(s) checked, every claim matches the derived facts"
3990
+ % len(args.check))
3991
+ sys.exit(0)
3992
+ body = json.dumps(facts, indent=2, ensure_ascii=False, sort_keys=True) + "\n"
3993
+ with open(args.out, "w", encoding="utf-8", newline="\n") as fh:
3994
+ fh.write(body)
3995
+ print("FACTS -> %s: version %s · %d subcommands · %d skills"
3996
+ % (args.out, facts["version"], facts["subcommands"]["count"],
3997
+ facts["skills"]["count"]))
3998
+
3999
+
4000
+
3865
4001
  def cmd_escalate(args):
3866
4002
  ledger = _load(args.ledger)
3867
4003
  _repo_node(ledger, args.repo)
@@ -7696,6 +7832,13 @@ def build_parser():
7696
7832
  pcu.add_argument("--json", action="store_true")
7697
7833
  pcu.set_defaults(func=cmd_curation_check)
7698
7834
 
7835
+ pfa = sub.add_parser("facts",
7836
+ help="SYSTEM-FACTS: derive repo facts from the artifacts, or --check published claims against them (ADR-012)")
7837
+ pfa.add_argument("--out", default="SYSTEM-FACTS.json")
7838
+ pfa.add_argument("--check", nargs="*", default=None,
7839
+ help="files whose claims must match the derived facts; exit 1 on drift")
7840
+ pfa.set_defaults(func=cmd_facts)
7841
+
7699
7842
  prt = sub.add_parser("roundtrip",
7700
7843
  help="advisory: which promoted candidates are traceable in code via uscha-spec ids (ADR-009 slice 2)")
7701
7844
  prt.add_argument("--ledger", default="QA-LEDGER.json")
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
3
  "name": "uscha",
4
- "version": "1.67.0",
4
+ "version": "1.68.0",
5
5
  "displayName": "Uscha",
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, 35 subcommands + universal installer + npm/npx router). Facts block, guesses advise; the human approves.",
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, 36 subcommands + universal installer + npm/npx router). Facts block, guesses advise; the human approves.",
7
7
  "author": {
8
8
  "name": "Andres Massello",
9
9
  "url": "https://github.com/andresmassello"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uscha",
3
- "version": "1.67.0",
3
+ "version": "1.68.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.67.0 <!-- uscha:version --> · **[uscha.dev](https://uscha.dev)**
3
+ **Kit version:** v1.68.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.67.0
1
+ uscha-kit 1.68.0
@@ -0,0 +1 @@
1
+ {"AC-SF-01": true, "AC-SF-02": true, "AC-SF-03": true, "AC-SF-04": true, "AC-SF-05": true}
@@ -3862,6 +3862,142 @@ def cmd_curation_check(args):
3862
3862
 
3863
3863
 
3864
3864
 
3865
+ # --------------------------------------------------------------------------- #
3866
+ # facts (T0 / SYSTEM-FACTS: public claims become compiled artifacts of repo
3867
+ # facts -- Diamond applied to Diamond. ADR-012.)
3868
+ # --------------------------------------------------------------------------- #
3869
+
3870
+ FACTS_FILE = "SYSTEM-FACTS.json"
3871
+
3872
+
3873
+ def _derive_facts():
3874
+ """Facts derived from the ARTIFACTS themselves, never from prose and never from greps
3875
+ over documentation: the subcommand list comes from introspecting the REAL parser, the
3876
+ skill list from the REAL kit tree, the version from the kit VERSION file. No timestamp
3877
+ on purpose: regeneration over an unchanged repo must be byte-identical (AC-SF-01)."""
3878
+ here = os.path.abspath(__file__)
3879
+ # kit root by MARKER, not by fixed depth: the canonical engine sits 4 levels deep
3880
+ # (.claude/skills/uscha-devloop/) and the Codex twin 3 (skills/uscha-devloop/). A fixed
3881
+ # dirname walk made the twin silently derive the OUTER repo root -- version None,
3882
+ # 0 skills, no error (fresh-review HIGH, reproduced by running both copies).
3883
+ kit, cur = None, os.path.dirname(here)
3884
+ for _ in range(6):
3885
+ if os.path.isfile(os.path.join(cur, "VERSION")):
3886
+ kit = cur
3887
+ break
3888
+ nxt = os.path.dirname(cur)
3889
+ if nxt == cur:
3890
+ break
3891
+ cur = nxt
3892
+ if kit is None:
3893
+ print("[qa_ledger] facts: no VERSION file found walking up from the engine -- "
3894
+ "facts that cannot locate their own kit are not facts.", file=sys.stderr)
3895
+ sys.exit(2)
3896
+ with open(os.path.join(kit, "VERSION"), encoding="utf-8") as fh:
3897
+ version = fh.read().split()[-1].strip()
3898
+ subs = []
3899
+ for action in build_parser()._subparsers._group_actions:
3900
+ subs = sorted(action.choices.keys())
3901
+ skills = []
3902
+ for sdir in (os.path.join(kit, ".claude", "skills"), os.path.join(kit, "skills")):
3903
+ # canonical tree first; the Codex install ships only skills/ -- same inventory
3904
+ if os.path.isdir(sdir):
3905
+ skills = sorted(d for d in os.listdir(sdir)
3906
+ if os.path.isfile(os.path.join(sdir, d, "SKILL.md")))
3907
+ break
3908
+ return {
3909
+ "version": version,
3910
+ "subcommands": {"count": len(subs), "list": subs},
3911
+ "skills": {"count": len(skills), "list": skills},
3912
+ "_derivation": {
3913
+ "version": "uscha-kit/VERSION",
3914
+ "subcommands": "argparse introspection of build_parser()",
3915
+ "skills": "SKILL.md inventory under uscha-kit/.claude/skills/",
3916
+ "omitted": "stack matrix and REAL/VISION registry: no mechanical "
3917
+ "source exists yet -- omitted, not guessed (ADR-012)",
3918
+ },
3919
+ }
3920
+
3921
+
3922
+ _CLAIM_PATTERNS = (
3923
+ # (fact key path, regex over one line, needs-context substring or None)
3924
+ ("version", r"v(\d+\.\d+\.\d+)", "kit"),
3925
+ ("version", r"uscha-kit\s+v?(\d+\.\d+\.\d+)", None),
3926
+ ("subcommands.count", r"(\d+)\s+sub-?comm?ands", None),
3927
+ ("subcommands.count", r"(\d+)\s+subcomandos", None),
3928
+ ("skills.count", r"(\d+)\s+skills", None),
3929
+ )
3930
+
3931
+
3932
+ def _fact_value(facts, dotted):
3933
+ cur = facts
3934
+ for part in dotted.split("."):
3935
+ cur = cur[part]
3936
+ return str(cur)
3937
+
3938
+
3939
+ def cmd_facts(args):
3940
+ """Generate SYSTEM-FACTS.json, or --check published claims against the derived facts.
3941
+
3942
+ The founding fixture (recorded in ADR-012): the site claimed kit 1.65.0 with 32 engine
3943
+ subcommands while the repo was at 1.67.0 with 35 -- factual drift, live, in the project
3944
+ about factual drift. A claim that CI does not compare against a derived fact will
3945
+ drift; this makes the comparison mechanical and the drift a named red."""
3946
+ facts = _derive_facts()
3947
+ if args.check:
3948
+ problems = []
3949
+ # 1) the committed facts file must match a fresh derivation (stale facts are drift)
3950
+ if os.path.isfile(args.out):
3951
+ with open(args.out, encoding="utf-8") as fh:
3952
+ committed = fh.read()
3953
+ fresh = json.dumps(facts, indent=2, ensure_ascii=False, sort_keys=True) + "\n"
3954
+ if committed.replace("\r\n", "\n") != fresh:
3955
+ problems.append((args.out, 0, "committed facts file",
3956
+ "stale vs regenerated", "run: qa_ledger.py facts"))
3957
+ else:
3958
+ problems.append((args.out, 0, "facts file", "absent",
3959
+ "run: qa_ledger.py facts"))
3960
+ # 2) every recognizable claim in the given files must equal the derived fact
3961
+ for path in args.check:
3962
+ try:
3963
+ with open(path, encoding="utf-8", errors="replace") as fh:
3964
+ lines = fh.read().splitlines()
3965
+ except OSError as exc:
3966
+ problems.append((path, 0, "file", "unreadable: %s" % exc, ""))
3967
+ continue
3968
+ for n, line in enumerate(lines, 1):
3969
+ # an HTML comment is not a published claim -- the first live run flagged a
3970
+ # section marker (a comment reading "2 Skills") as a drifted count
3971
+ line = re.sub(r"<!--.*?-->", "", line)
3972
+ low = line.lower()
3973
+ for key, pat, ctx in _CLAIM_PATTERNS:
3974
+ if ctx and ctx not in low:
3975
+ continue
3976
+ for m in re.finditer(pat, line, re.I):
3977
+ claimed = m.group(1)
3978
+ actual = _fact_value(facts, key)
3979
+ if claimed != actual:
3980
+ problems.append((path, n, key, claimed, actual))
3981
+ if problems:
3982
+ print("FACTUAL DRIFT: %d claim(s) disagree with the derived facts"
3983
+ % len(problems))
3984
+ for path, n, key, claimed, actual in problems:
3985
+ loc = "%s:%d" % (path, n) if n else path
3986
+ print(" !! %s: %s claims %r, the artifact says %r"
3987
+ % (loc, key, claimed, actual))
3988
+ sys.exit(1)
3989
+ print("FACTS: %d file(s) checked, every claim matches the derived facts"
3990
+ % len(args.check))
3991
+ sys.exit(0)
3992
+ body = json.dumps(facts, indent=2, ensure_ascii=False, sort_keys=True) + "\n"
3993
+ with open(args.out, "w", encoding="utf-8", newline="\n") as fh:
3994
+ fh.write(body)
3995
+ print("FACTS -> %s: version %s · %d subcommands · %d skills"
3996
+ % (args.out, facts["version"], facts["subcommands"]["count"],
3997
+ facts["skills"]["count"]))
3998
+
3999
+
4000
+
3865
4001
  def cmd_escalate(args):
3866
4002
  ledger = _load(args.ledger)
3867
4003
  _repo_node(ledger, args.repo)
@@ -7696,6 +7832,13 @@ def build_parser():
7696
7832
  pcu.add_argument("--json", action="store_true")
7697
7833
  pcu.set_defaults(func=cmd_curation_check)
7698
7834
 
7835
+ pfa = sub.add_parser("facts",
7836
+ help="SYSTEM-FACTS: derive repo facts from the artifacts, or --check published claims against them (ADR-012)")
7837
+ pfa.add_argument("--out", default="SYSTEM-FACTS.json")
7838
+ pfa.add_argument("--check", nargs="*", default=None,
7839
+ help="files whose claims must match the derived facts; exit 1 on drift")
7840
+ pfa.set_defaults(func=cmd_facts)
7841
+
7699
7842
  prt = sub.add_parser("roundtrip",
7700
7843
  help="advisory: which promoted candidates are traceable in code via uscha-spec ids (ADR-009 slice 2)")
7701
7844
  prt.add_argument("--ledger", default="QA-LEDGER.json")
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.67.0",
2
+ "version": "1.68.0",
3
3
  "project": null,
4
4
  "defaults": {
5
5
  "coverage_threshold": 60,