@andresmassello/uscha 1.61.0 → 1.62.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.61.0** <!-- uscha:version --> · [uscha.dev](https://uscha.dev) ·
43
+ **Kit v1.62.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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andresmassello/uscha",
3
- "version": "1.61.0",
3
+ "version": "1.62.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",
@@ -3026,7 +3026,7 @@ def _sd_governs(path):
3026
3026
  return None
3027
3027
  if not lines or lines[0].strip() != "---":
3028
3028
  return None
3029
- globs, in_governs = None, False
3029
+ globs, in_governs, explicit_empty = None, False, False
3030
3030
  # scan runs to the CLOSING fence, not an arbitrary window -- a governs: key late in a
3031
3031
  # long frontmatter block must not silently read as UNMAPPED (fresh-review finding).
3032
3032
  for ln in lines[1:]:
@@ -3038,6 +3038,8 @@ def _sd_governs(path):
3038
3038
  if rest.startswith("[") and rest.endswith("]"):
3039
3039
  globs = [x.strip().strip("\x27\x22")
3040
3040
  for x in rest[1:-1].split(",") if x.strip()]
3041
+ # only an INLINE [] is a declaration of "nothing to govern"
3042
+ explicit_empty = not globs
3041
3043
  in_governs = False
3042
3044
  elif rest:
3043
3045
  # bare scalar (`governs: src/**`) -- a plausible authoring shorthand;
@@ -3051,6 +3053,11 @@ def _sd_governs(path):
3051
3053
  globs.append(s[2:].strip().strip("\x27\x22"))
3052
3054
  elif s and not ln.startswith((" ", "\t")):
3053
3055
  in_governs = False
3056
+ if globs == [] and not explicit_empty:
3057
+ # a `governs:` key with nothing usable under it (a placeholder, a comment, a typo) is
3058
+ # an UNFINISHED declaration, not a statement that this spec governs nothing. Report it
3059
+ # as UNMAPPED, which is what it is (fresh-review finding).
3060
+ return None
3054
3061
  return globs
3055
3062
 
3056
3063
 
@@ -3111,6 +3118,16 @@ def cmd_spec_drift(args):
3111
3118
  row.update({"verdict": "UNMAPPED", "reason": "no governs: frontmatter"})
3112
3119
  results.append(row)
3113
3120
  continue
3121
+ if not governs:
3122
+ # An EXPLICIT empty list is a declaration, not an omission: this decision governs
3123
+ # no code and never will. Negative ADRs ("we are NOT doing X, and why") are a
3124
+ # documented practice in this kit, and reporting them UNMAPPED forever turns a
3125
+ # correct state into permanent noise -- which is how an advisory gets ignored.
3126
+ # Found by running spec-drift on this repo's own ADR-004.
3127
+ row.update({"verdict": "NO-CODE",
3128
+ "reason": "declares governs: [] -- a decision that governs no code"})
3129
+ results.append(row)
3130
+ continue
3114
3131
  matched = []
3115
3132
  pats = [_fp_glob_re(g) for g in governs]
3116
3133
  for f in tracked:
@@ -3171,7 +3188,8 @@ def cmd_spec_drift(args):
3171
3188
  print("SPEC-DRIFT %s (advisory, lag > %dd):" % (args.repo, lag_days))
3172
3189
  if not results:
3173
3190
  print(" no spec documents found (SPEC.md / docs/adr/*.md)")
3174
- mark = {"SPEC_STALE": "!!", "CLEAN": "ok", "UNMAPPED": "--", "UNTRACKED": "--"}
3191
+ mark = {"SPEC_STALE": "!!", "CLEAN": "ok", "UNMAPPED": "--", "UNTRACKED": "--",
3192
+ "NO-CODE": "ok"}
3175
3193
  for r_ in results:
3176
3194
  line = " %s %s: %s" % (mark.get(r_["verdict"], "??"), r_["file"],
3177
3195
  r_["verdict"])
@@ -71,7 +71,7 @@ than inventing a step. Keep the CONTENT in the conversation's language and the l
71
71
  repo straight from the ledger, or null when none was requested. The template degrades when
72
72
  absent, like every other field.
73
73
  - **Spec-drift (ADR-005):** `dashboard --json` carries `spec_drift` — the latest advisory
74
- run (per-document verdicts: SPEC_STALE / CLEAN / UNMAPPED / UNTRACKED) — only when a run
74
+ run (per-document verdicts: SPEC_STALE / CLEAN / UNMAPPED / UNTRACKED / NO-CODE) — only when a run
75
75
  exists in the ledger; a virgin ledger keeps the exact prior schema. Advisory visibility of
76
76
  the spec-maintenance tax, never readiness input.
77
77
  - **Modes card:** the template draws one card for both modes — fast-path verdict chips per
@@ -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.61.0",
4
+ "version": "1.62.0",
5
5
  "displayName": "Uscha",
6
6
  "description": "Spec-driven development for LLM coding agents: 9 skills (discovery, adr-refine, reverse-discovery, characterize, devloop, sysdoc, rubric, mirador, status) + a stdlib measurement engine (qa_ledger.py, 32 subcommands + universal installer + npm/npx router). Facts block, guesses advise; the human approves.",
7
7
  "author": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uscha",
3
- "version": "1.61.0",
3
+ "version": "1.62.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.61.0 <!-- uscha:version --> · **[uscha.dev](https://uscha.dev)**
3
+ **Kit version:** v1.62.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`,
@@ -123,7 +123,8 @@ Per document: **`SPEC_STALE`** when governed code outran the spec by more than
123
123
  `defaults.spec_drift.max_lag_days` (default 30), listing the newer files; **`CLEAN`** when it
124
124
  did not; **`UNMAPPED`** when there is no `governs:` frontmatter *or its globs match nothing*
125
125
  — absence of a mapping is absence of measurement, not "no drift"; **`UNTRACKED`** when the
126
- spec has no commit date to compare. The latest run lands in the ledger (`spec_drift`) so the
126
+ spec has no commit date to compare; **`NO-CODE`** when it declares `governs: []`, i.e. a
127
+ decision that governs no source (negative ADRs) — a declaration, not an omission. The latest run lands in the ledger (`spec_drift`) so the
127
128
  mirador can surface it. No readiness impact, no exit-code gate: a stale spec is a prompt for
128
129
  a human conversation, not a blocked pipeline.
129
130
 
package/uscha-kit/VERSION CHANGED
@@ -1 +1 @@
1
- uscha-kit 1.61.0
1
+ uscha-kit 1.62.0
@@ -1 +1 @@
1
- {"AC-SD-01": true, "AC-SD-03": true, "AC-SD-02": true, "AC-SD-04": true}
1
+ {"AC-SD-01": true, "AC-SD-03": true, "AC-SD-05": true, "AC-SD-02": true, "AC-SD-04": true}
@@ -3026,7 +3026,7 @@ def _sd_governs(path):
3026
3026
  return None
3027
3027
  if not lines or lines[0].strip() != "---":
3028
3028
  return None
3029
- globs, in_governs = None, False
3029
+ globs, in_governs, explicit_empty = None, False, False
3030
3030
  # scan runs to the CLOSING fence, not an arbitrary window -- a governs: key late in a
3031
3031
  # long frontmatter block must not silently read as UNMAPPED (fresh-review finding).
3032
3032
  for ln in lines[1:]:
@@ -3038,6 +3038,8 @@ def _sd_governs(path):
3038
3038
  if rest.startswith("[") and rest.endswith("]"):
3039
3039
  globs = [x.strip().strip("\x27\x22")
3040
3040
  for x in rest[1:-1].split(",") if x.strip()]
3041
+ # only an INLINE [] is a declaration of "nothing to govern"
3042
+ explicit_empty = not globs
3041
3043
  in_governs = False
3042
3044
  elif rest:
3043
3045
  # bare scalar (`governs: src/**`) -- a plausible authoring shorthand;
@@ -3051,6 +3053,11 @@ def _sd_governs(path):
3051
3053
  globs.append(s[2:].strip().strip("\x27\x22"))
3052
3054
  elif s and not ln.startswith((" ", "\t")):
3053
3055
  in_governs = False
3056
+ if globs == [] and not explicit_empty:
3057
+ # a `governs:` key with nothing usable under it (a placeholder, a comment, a typo) is
3058
+ # an UNFINISHED declaration, not a statement that this spec governs nothing. Report it
3059
+ # as UNMAPPED, which is what it is (fresh-review finding).
3060
+ return None
3054
3061
  return globs
3055
3062
 
3056
3063
 
@@ -3111,6 +3118,16 @@ def cmd_spec_drift(args):
3111
3118
  row.update({"verdict": "UNMAPPED", "reason": "no governs: frontmatter"})
3112
3119
  results.append(row)
3113
3120
  continue
3121
+ if not governs:
3122
+ # An EXPLICIT empty list is a declaration, not an omission: this decision governs
3123
+ # no code and never will. Negative ADRs ("we are NOT doing X, and why") are a
3124
+ # documented practice in this kit, and reporting them UNMAPPED forever turns a
3125
+ # correct state into permanent noise -- which is how an advisory gets ignored.
3126
+ # Found by running spec-drift on this repo's own ADR-004.
3127
+ row.update({"verdict": "NO-CODE",
3128
+ "reason": "declares governs: [] -- a decision that governs no code"})
3129
+ results.append(row)
3130
+ continue
3114
3131
  matched = []
3115
3132
  pats = [_fp_glob_re(g) for g in governs]
3116
3133
  for f in tracked:
@@ -3171,7 +3188,8 @@ def cmd_spec_drift(args):
3171
3188
  print("SPEC-DRIFT %s (advisory, lag > %dd):" % (args.repo, lag_days))
3172
3189
  if not results:
3173
3190
  print(" no spec documents found (SPEC.md / docs/adr/*.md)")
3174
- mark = {"SPEC_STALE": "!!", "CLEAN": "ok", "UNMAPPED": "--", "UNTRACKED": "--"}
3191
+ mark = {"SPEC_STALE": "!!", "CLEAN": "ok", "UNMAPPED": "--", "UNTRACKED": "--",
3192
+ "NO-CODE": "ok"}
3175
3193
  for r_ in results:
3176
3194
  line = " %s %s: %s" % (mark.get(r_["verdict"], "??"), r_["file"],
3177
3195
  r_["verdict"])
@@ -71,7 +71,7 @@ than inventing a step. Keep the CONTENT in the conversation's language and the l
71
71
  repo straight from the ledger, or null when none was requested. The template degrades when
72
72
  absent, like every other field.
73
73
  - **Spec-drift (ADR-005):** `dashboard --json` carries `spec_drift` — the latest advisory
74
- run (per-document verdicts: SPEC_STALE / CLEAN / UNMAPPED / UNTRACKED) — only when a run
74
+ run (per-document verdicts: SPEC_STALE / CLEAN / UNMAPPED / UNTRACKED / NO-CODE) — only when a run
75
75
  exists in the ledger; a virgin ledger keeps the exact prior schema. Advisory visibility of
76
76
  the spec-maintenance tax, never readiness input.
77
77
  - **Modes card:** the template draws one card for both modes — fast-path verdict chips per
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.61.0",
2
+ "version": "1.62.0",
3
3
  "project": null,
4
4
  "defaults": {
5
5
  "coverage_threshold": 60,