@andresmassello/uscha 1.93.0 → 1.93.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.
- package/README.md +2 -2
- package/package.json +1 -1
- package/uscha-kit/.claude/skills/uscha-devloop/qa_ledger.py +62 -4
- 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-devloop/qa_ledger.py +62 -4
- package/uscha-kit/uscha.config.json +1 -1
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.93.
|
|
43
|
+
**Kit v1.93.1** <!-- 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.93.
|
|
88
|
+
**What each arrow is, in the engine (kit 1.93.1, 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.93.
|
|
3
|
+
"version": "1.93.1",
|
|
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",
|
|
@@ -780,7 +780,9 @@ def _content_state(repo_path, repo_type, last_snapshot):
|
|
|
780
780
|
# verdict, or a report the record itself marked stale, is no anchor at all.
|
|
781
781
|
if ((snap.get("tests") or {}).get("freshness") or {}).get("status") == "stale":
|
|
782
782
|
return None
|
|
783
|
-
|
|
783
|
+
# the RECORD, not just the hash string: `_evidence_hash_matches` needs the `sha256_eol`
|
|
784
|
+
# marker beside it to know whether the compatibility comparison is still on offer
|
|
785
|
+
hashes = {r["path"]: r
|
|
784
786
|
for r in ((snap.get("tests") or {}).get("reports") or [])
|
|
785
787
|
if isinstance(r, dict) and r.get("path") and r.get("sha256")
|
|
786
788
|
and r.get("fresh_by") != "stale"}
|
|
@@ -814,7 +816,7 @@ def _report_fresh(repo_path, report_path, clock_fresh, content_state):
|
|
|
814
816
|
# cannot arise here, while normalizing one side would stop matching the recorded key.
|
|
815
817
|
rel = os.path.relpath(report_path, repo_path).replace("\\", "/")
|
|
816
818
|
recorded = content_state["hashes"].get(rel)
|
|
817
|
-
if
|
|
819
|
+
if _evidence_hash_matches(report_path, recorded) is not True:
|
|
818
820
|
return None
|
|
819
821
|
return "content"
|
|
820
822
|
|
|
@@ -842,7 +844,10 @@ def _test_evidence_provenance(repo_path, repo_type, last_snapshot=None):
|
|
|
842
844
|
"mtime_ns": mtime_ns,
|
|
843
845
|
"mtime": datetime.fromtimestamp(
|
|
844
846
|
mtime_ns / 1_000_000_000, timezone.utc).isoformat(),
|
|
845
|
-
"sha256":
|
|
847
|
+
"sha256": _sha256_evidence(path),
|
|
848
|
+
# the marker says HOW the hash above was taken, so a reader never has to guess
|
|
849
|
+
# (and 1.93.0-and-older records, which have no marker, keep their compatibility)
|
|
850
|
+
"sha256_eol": "lf",
|
|
846
851
|
})
|
|
847
852
|
if not reports:
|
|
848
853
|
status = "not-applicable" if repo_type == "flutter" else "missing"
|
|
@@ -5304,6 +5309,59 @@ def _sha256_file(path):
|
|
|
5304
5309
|
return None
|
|
5305
5310
|
|
|
5306
5311
|
|
|
5312
|
+
# kit 1.93.1: the hash of a TEXT evidence file, EOL-NORMALIZED (CRLF -> LF before hashing).
|
|
5313
|
+
#
|
|
5314
|
+
# A JUnit report is text, and a version control system is allowed to rewrite its line endings
|
|
5315
|
+
# on checkout -- `* text=auto eol=lf` is the recommended `.gitattributes` and the kit's own.
|
|
5316
|
+
# The suite that produced the report on Windows wrote CRLF, the repository stores LF, and a
|
|
5317
|
+
# clean checkout therefore yields a file that is byte-different and semantically identical.
|
|
5318
|
+
# The exact-byte hash read that as `evidence altered after ingest`: 1.93.0 shipped with the
|
|
5319
|
+
# limit merely NAMED in SPEC 4, and the release machine's own board hit it the same day.
|
|
5320
|
+
# Normalizing the ONE difference git is allowed to introduce keeps the guarantee that matters
|
|
5321
|
+
# -- any other changed byte (a swapped log, an edited count, a different run) still fails.
|
|
5322
|
+
#
|
|
5323
|
+
# `_sha256_file` is deliberately left alone: compile-validate hashes MANIFEST UNITS, where the
|
|
5324
|
+
# exact bytes ARE the claim, and a manifest that tolerated a rewrite would be tolerating the
|
|
5325
|
+
# thing it exists to detect.
|
|
5326
|
+
def _sha256_evidence(path):
|
|
5327
|
+
try:
|
|
5328
|
+
with open(path, "rb") as fh:
|
|
5329
|
+
data = fh.read()
|
|
5330
|
+
except OSError:
|
|
5331
|
+
return None
|
|
5332
|
+
return hashlib.sha256(data.replace(b"\r\n", b"\n")).hexdigest()
|
|
5333
|
+
|
|
5334
|
+
|
|
5335
|
+
def _evidence_hash_matches(path, record):
|
|
5336
|
+
"""Does the file on disk still hash to what the RECORD carries? True / False, or None when
|
|
5337
|
+
the record carries no hash at all (the caller decides: UNMEASURED, never a pass).
|
|
5338
|
+
|
|
5339
|
+
Records written by 1.93.1 and later carry `sha256_eol: "lf"` and are compared NORMALIZED,
|
|
5340
|
+
full stop. A record written BEFORE that marker hashed the file's exact bytes, whatever
|
|
5341
|
+
line endings the machine that ran the suite happened to write -- and 1.93.0's own release
|
|
5342
|
+
record is the proof: the suite wrote CRLF on Windows, git stored LF, and the LF checkout
|
|
5343
|
+
matched neither the recorded hash nor its normalized form, because the recording was of the
|
|
5344
|
+
CRLF RENDERING of the same text. So a pre-marker record is compared against all three
|
|
5345
|
+
renderings of the bytes on disk: normalized, exact, and CRLF. The door is narrow on purpose
|
|
5346
|
+
-- it opens only for records that predate the marker, and it admits only the line-ending
|
|
5347
|
+
renderings of THIS file's text: a report whose content really changed matches none of them."""
|
|
5348
|
+
want = record.get("sha256") if isinstance(record, dict) else None
|
|
5349
|
+
if not want:
|
|
5350
|
+
return None
|
|
5351
|
+
if _sha256_evidence(path) == want:
|
|
5352
|
+
return True
|
|
5353
|
+
if record.get("sha256_eol") == "lf":
|
|
5354
|
+
return False
|
|
5355
|
+
try:
|
|
5356
|
+
with open(path, "rb") as fh:
|
|
5357
|
+
data = fh.read()
|
|
5358
|
+
except OSError:
|
|
5359
|
+
return False
|
|
5360
|
+
lf = data.replace(b"\r\n", b"\n")
|
|
5361
|
+
return want in (hashlib.sha256(data).hexdigest(),
|
|
5362
|
+
hashlib.sha256(lf.replace(b"\n", b"\r\n")).hexdigest())
|
|
5363
|
+
|
|
5364
|
+
|
|
5307
5365
|
def _contained_unit(base, unit):
|
|
5308
5366
|
"""A compilation's units must be RELATIVE paths CONTAINED within the compilation
|
|
5309
5367
|
directory: the manifest references what was compiled, and what was compiled lives with
|
|
@@ -8989,7 +9047,7 @@ def _sealed_state(ledger, ledger_path):
|
|
|
8989
9047
|
elif not r.get("sha256"):
|
|
8990
9048
|
unmeasured.append("evidence hash unmeasured: %s — no hash recorded at ingest "
|
|
8991
9049
|
"(older snapshot, or the file was unreadable)" % rel)
|
|
8992
|
-
elif
|
|
9050
|
+
elif _evidence_hash_matches(full, r) is not True:
|
|
8993
9051
|
failures.append("evidence altered after ingest: %s" % rel)
|
|
8994
9052
|
|
|
8995
9053
|
out["reasons"] = failures + unmeasured
|
|
@@ -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.93.
|
|
4
|
+
"version": "1.93.1",
|
|
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": {
|
package/uscha-kit/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# uscha-kit
|
|
2
2
|
|
|
3
|
-
**Kit version:** v1.93.
|
|
3
|
+
**Kit version:** v1.93.1 <!-- 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.93.
|
|
1
|
+
uscha-kit 1.93.1
|
|
@@ -780,7 +780,9 @@ def _content_state(repo_path, repo_type, last_snapshot):
|
|
|
780
780
|
# verdict, or a report the record itself marked stale, is no anchor at all.
|
|
781
781
|
if ((snap.get("tests") or {}).get("freshness") or {}).get("status") == "stale":
|
|
782
782
|
return None
|
|
783
|
-
|
|
783
|
+
# the RECORD, not just the hash string: `_evidence_hash_matches` needs the `sha256_eol`
|
|
784
|
+
# marker beside it to know whether the compatibility comparison is still on offer
|
|
785
|
+
hashes = {r["path"]: r
|
|
784
786
|
for r in ((snap.get("tests") or {}).get("reports") or [])
|
|
785
787
|
if isinstance(r, dict) and r.get("path") and r.get("sha256")
|
|
786
788
|
and r.get("fresh_by") != "stale"}
|
|
@@ -814,7 +816,7 @@ def _report_fresh(repo_path, report_path, clock_fresh, content_state):
|
|
|
814
816
|
# cannot arise here, while normalizing one side would stop matching the recorded key.
|
|
815
817
|
rel = os.path.relpath(report_path, repo_path).replace("\\", "/")
|
|
816
818
|
recorded = content_state["hashes"].get(rel)
|
|
817
|
-
if
|
|
819
|
+
if _evidence_hash_matches(report_path, recorded) is not True:
|
|
818
820
|
return None
|
|
819
821
|
return "content"
|
|
820
822
|
|
|
@@ -842,7 +844,10 @@ def _test_evidence_provenance(repo_path, repo_type, last_snapshot=None):
|
|
|
842
844
|
"mtime_ns": mtime_ns,
|
|
843
845
|
"mtime": datetime.fromtimestamp(
|
|
844
846
|
mtime_ns / 1_000_000_000, timezone.utc).isoformat(),
|
|
845
|
-
"sha256":
|
|
847
|
+
"sha256": _sha256_evidence(path),
|
|
848
|
+
# the marker says HOW the hash above was taken, so a reader never has to guess
|
|
849
|
+
# (and 1.93.0-and-older records, which have no marker, keep their compatibility)
|
|
850
|
+
"sha256_eol": "lf",
|
|
846
851
|
})
|
|
847
852
|
if not reports:
|
|
848
853
|
status = "not-applicable" if repo_type == "flutter" else "missing"
|
|
@@ -5304,6 +5309,59 @@ def _sha256_file(path):
|
|
|
5304
5309
|
return None
|
|
5305
5310
|
|
|
5306
5311
|
|
|
5312
|
+
# kit 1.93.1: the hash of a TEXT evidence file, EOL-NORMALIZED (CRLF -> LF before hashing).
|
|
5313
|
+
#
|
|
5314
|
+
# A JUnit report is text, and a version control system is allowed to rewrite its line endings
|
|
5315
|
+
# on checkout -- `* text=auto eol=lf` is the recommended `.gitattributes` and the kit's own.
|
|
5316
|
+
# The suite that produced the report on Windows wrote CRLF, the repository stores LF, and a
|
|
5317
|
+
# clean checkout therefore yields a file that is byte-different and semantically identical.
|
|
5318
|
+
# The exact-byte hash read that as `evidence altered after ingest`: 1.93.0 shipped with the
|
|
5319
|
+
# limit merely NAMED in SPEC 4, and the release machine's own board hit it the same day.
|
|
5320
|
+
# Normalizing the ONE difference git is allowed to introduce keeps the guarantee that matters
|
|
5321
|
+
# -- any other changed byte (a swapped log, an edited count, a different run) still fails.
|
|
5322
|
+
#
|
|
5323
|
+
# `_sha256_file` is deliberately left alone: compile-validate hashes MANIFEST UNITS, where the
|
|
5324
|
+
# exact bytes ARE the claim, and a manifest that tolerated a rewrite would be tolerating the
|
|
5325
|
+
# thing it exists to detect.
|
|
5326
|
+
def _sha256_evidence(path):
|
|
5327
|
+
try:
|
|
5328
|
+
with open(path, "rb") as fh:
|
|
5329
|
+
data = fh.read()
|
|
5330
|
+
except OSError:
|
|
5331
|
+
return None
|
|
5332
|
+
return hashlib.sha256(data.replace(b"\r\n", b"\n")).hexdigest()
|
|
5333
|
+
|
|
5334
|
+
|
|
5335
|
+
def _evidence_hash_matches(path, record):
|
|
5336
|
+
"""Does the file on disk still hash to what the RECORD carries? True / False, or None when
|
|
5337
|
+
the record carries no hash at all (the caller decides: UNMEASURED, never a pass).
|
|
5338
|
+
|
|
5339
|
+
Records written by 1.93.1 and later carry `sha256_eol: "lf"` and are compared NORMALIZED,
|
|
5340
|
+
full stop. A record written BEFORE that marker hashed the file's exact bytes, whatever
|
|
5341
|
+
line endings the machine that ran the suite happened to write -- and 1.93.0's own release
|
|
5342
|
+
record is the proof: the suite wrote CRLF on Windows, git stored LF, and the LF checkout
|
|
5343
|
+
matched neither the recorded hash nor its normalized form, because the recording was of the
|
|
5344
|
+
CRLF RENDERING of the same text. So a pre-marker record is compared against all three
|
|
5345
|
+
renderings of the bytes on disk: normalized, exact, and CRLF. The door is narrow on purpose
|
|
5346
|
+
-- it opens only for records that predate the marker, and it admits only the line-ending
|
|
5347
|
+
renderings of THIS file's text: a report whose content really changed matches none of them."""
|
|
5348
|
+
want = record.get("sha256") if isinstance(record, dict) else None
|
|
5349
|
+
if not want:
|
|
5350
|
+
return None
|
|
5351
|
+
if _sha256_evidence(path) == want:
|
|
5352
|
+
return True
|
|
5353
|
+
if record.get("sha256_eol") == "lf":
|
|
5354
|
+
return False
|
|
5355
|
+
try:
|
|
5356
|
+
with open(path, "rb") as fh:
|
|
5357
|
+
data = fh.read()
|
|
5358
|
+
except OSError:
|
|
5359
|
+
return False
|
|
5360
|
+
lf = data.replace(b"\r\n", b"\n")
|
|
5361
|
+
return want in (hashlib.sha256(data).hexdigest(),
|
|
5362
|
+
hashlib.sha256(lf.replace(b"\n", b"\r\n")).hexdigest())
|
|
5363
|
+
|
|
5364
|
+
|
|
5307
5365
|
def _contained_unit(base, unit):
|
|
5308
5366
|
"""A compilation's units must be RELATIVE paths CONTAINED within the compilation
|
|
5309
5367
|
directory: the manifest references what was compiled, and what was compiled lives with
|
|
@@ -8989,7 +9047,7 @@ def _sealed_state(ledger, ledger_path):
|
|
|
8989
9047
|
elif not r.get("sha256"):
|
|
8990
9048
|
unmeasured.append("evidence hash unmeasured: %s — no hash recorded at ingest "
|
|
8991
9049
|
"(older snapshot, or the file was unreadable)" % rel)
|
|
8992
|
-
elif
|
|
9050
|
+
elif _evidence_hash_matches(full, r) is not True:
|
|
8993
9051
|
failures.append("evidence altered after ingest: %s" % rel)
|
|
8994
9052
|
|
|
8995
9053
|
out["reasons"] = failures + unmeasured
|