@andresmassello/uscha 2.6.0 → 2.7.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 +12 -1
- package/package.json +1 -1
- package/uscha-kit/.claude/skills/uscha-adr-refine/SKILL.md +1 -1
- package/uscha-kit/.claude/skills/uscha-characterize/SKILL.md +1 -1
- package/uscha-kit/.claude/skills/uscha-devloop/SKILL.md +1 -1
- package/uscha-kit/.claude/skills/uscha-devloop/qa_ledger.py +42 -25
- package/uscha-kit/.claude/skills/uscha-discovery/SKILL.md +1 -1
- package/uscha-kit/.claude/skills/uscha-mirador/SKILL.md +1 -1
- package/uscha-kit/.claude/skills/uscha-reverse-discovery/SKILL.md +1 -1
- package/uscha-kit/.claude/skills/uscha-rubric/SKILL.md +1 -1
- package/uscha-kit/.claude/skills/uscha-status/SKILL.md +1 -1
- package/uscha-kit/.claude/skills/uscha-sysdoc/SKILL.md +1 -1
- package/uscha-kit/.claude-plugin/plugin.json +1 -1
- package/uscha-kit/.codex-plugin/plugin.json +1 -1
- package/uscha-kit/INSTALL.md +7 -0
- package/uscha-kit/README.md +16 -1
- package/uscha-kit/VERSION +1 -1
- package/uscha-kit/install-uscha.py +215 -2
- package/uscha-kit/skills/uscha-adr-refine/SKILL.md +1 -1
- package/uscha-kit/skills/uscha-characterize/SKILL.md +1 -1
- package/uscha-kit/skills/uscha-devloop/SKILL.md +1 -1
- package/uscha-kit/skills/uscha-devloop/qa_ledger.py +42 -25
- package/uscha-kit/skills/uscha-discovery/SKILL.md +1 -1
- package/uscha-kit/skills/uscha-mirador/SKILL.md +1 -1
- package/uscha-kit/skills/uscha-reverse-discovery/SKILL.md +1 -1
- package/uscha-kit/skills/uscha-rubric/SKILL.md +1 -1
- package/uscha-kit/skills/uscha-status/SKILL.md +1 -1
- package/uscha-kit/skills/uscha-sysdoc/SKILL.md +1 -1
- package/uscha-kit/uscha.config.json +1 -1
package/README.md
CHANGED
|
@@ -45,7 +45,18 @@ runtime dependencies). The npm package is a thin router; the canonical installer
|
|
|
45
45
|
`uscha-kit/install-uscha.py`. `init` also writes a minimal, per-repo-type `.gitignore` when the
|
|
46
46
|
project has none (kit 2.4.0) — it never lists `reports/`, the ledger's own evidence.
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
The kit declares no dependencies and installs none. Since kit 2.7.0 (ADR-052) `install` ends
|
|
49
|
+
with an **extras report** and the installer's `doctor` repeats it as an advisory section: how
|
|
50
|
+
the dev loop's QA tools (`code-review`, `judgment-day`, `improve`, the kit default order) resolve
|
|
51
|
+
on the target machine — through the engine's own resolver, so `code-review` reads "assumed,
|
|
52
|
+
harness-provided, not measured" — and whether the optional engram (uscha does not require it)
|
|
53
|
+
is present, with the commands to add it. Present means usable from Claude: the engram plugin
|
|
54
|
+
installed and enabled, or an MCP server named `engram` in the Claude configuration; a bare
|
|
55
|
+
binary on PATH reads "binary found, not wired into Claude". The MCP check reads only the Claude
|
|
56
|
+
configuration, for `--target codex` too. It only reports: it installs nothing, asks nothing and
|
|
57
|
+
runs none of the commands it prints, and it never changes an exit code.
|
|
58
|
+
|
|
59
|
+
**Kit v2.7.0** <!-- uscha:version --> · [uscha.dev](https://uscha.dev) ·
|
|
49
60
|
[changelog](https://github.com/andresmassello/uscha/blob/main/uscha-kit/CHANGELOG.md)
|
|
50
61
|
(the per-release changelogs live in the repo, not in the npm tarball)
|
|
51
62
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@andresmassello/uscha",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.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",
|
|
@@ -19,7 +19,7 @@ phases. **You are NOT a generator. You are an interrogator that distills.** The
|
|
|
19
19
|
is in the questions, not in agreeing.
|
|
20
20
|
|
|
21
21
|
<!-- uscha:orientation-block:begin -->
|
|
22
|
-
<!-- uscha kit: 2.
|
|
22
|
+
<!-- uscha kit: 2.7.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
23
23
|
|
|
24
24
|
## First contact (show ONCE, then never again)
|
|
25
25
|
|
|
@@ -19,7 +19,7 @@ what the code DOES, mechanically, by running it — never what it should do.** Y
|
|
|
19
19
|
the capture harness; you may NOT create, rename, or edit any `.approved` file.
|
|
20
20
|
|
|
21
21
|
<!-- uscha:orientation-block:begin -->
|
|
22
|
-
<!-- uscha kit: 2.
|
|
22
|
+
<!-- uscha kit: 2.7.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
23
23
|
|
|
24
24
|
## First contact (show ONCE, then never again)
|
|
25
25
|
|
|
@@ -27,7 +27,7 @@ artifacts; these can block) and **self-reported** agent counts (log-step — nar
|
|
|
27
27
|
recorded for the retrospective; a measured red always overrides a narrated green).
|
|
28
28
|
|
|
29
29
|
<!-- uscha:orientation-block:begin -->
|
|
30
|
-
<!-- uscha kit: 2.
|
|
30
|
+
<!-- uscha kit: 2.7.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
31
31
|
|
|
32
32
|
## First contact (show ONCE, then never again)
|
|
33
33
|
|
|
@@ -2522,13 +2522,16 @@ def _sha256_file(path):
|
|
|
2522
2522
|
return None
|
|
2523
2523
|
|
|
2524
2524
|
|
|
2525
|
-
def _enabled_plugins(home):
|
|
2525
|
+
def _enabled_plugins(home, cwd="."):
|
|
2526
2526
|
"""`<name>@<marketplace>` -> bool, merged from the SAME settings files doctor walks
|
|
2527
2527
|
(user ~/.claude, project .claude, project-local; later wins per key). A missing or malformed
|
|
2528
|
-
file is skipped, never raised: a broken settings file must not turn a real tool MISSING.
|
|
2528
|
+
file is skipped, never raised: a broken settings file must not turn a real tool MISSING.
|
|
2529
|
+
The project files are read under `cwd` (the resolver's project root; "." for every engine
|
|
2530
|
+
caller), so a machine-level caller -- the installer's extras report -- can keep the
|
|
2531
|
+
directory it happens to run from out of the answer."""
|
|
2529
2532
|
paths = [os.path.join(_claude_config_dir(home), "settings.json"),
|
|
2530
|
-
os.path.join(".claude", "settings.json"),
|
|
2531
|
-
os.path.join(".claude", "settings.local.json")]
|
|
2533
|
+
os.path.join(cwd, ".claude", "settings.json"),
|
|
2534
|
+
os.path.join(cwd, ".claude", "settings.local.json")]
|
|
2532
2535
|
enabled = {}
|
|
2533
2536
|
for p in paths:
|
|
2534
2537
|
try:
|
|
@@ -2560,28 +2563,23 @@ def _installed_plugins(home):
|
|
|
2560
2563
|
return plugins if isinstance(plugins, dict) else {}
|
|
2561
2564
|
|
|
2562
2565
|
|
|
2563
|
-
def
|
|
2564
|
-
"""
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
else:
|
|
2574
|
-
plugin_part, skill_part = "", name
|
|
2575
|
-
if not skill_part:
|
|
2576
|
-
return None
|
|
2577
|
-
enabled = _enabled_plugins(home)
|
|
2566
|
+
def enabled_plugin_installs(home, cwd=".", plugin=None):
|
|
2567
|
+
"""[(`<name>@<marketplace>`, installPath)] for every plugin that is installed AND enabled --
|
|
2568
|
+
the plugin-level half of the registry reader, shared by the QA-tool resolver below and the
|
|
2569
|
+
installer's optional-extras report (ADR-052), which asks about a PLUGIN (engram), not a
|
|
2570
|
+
skill, and must not push a plugin name through the skill resolver. `plugin` filters by the
|
|
2571
|
+
name part before the `@`. A `scope: project` record counts only when its projectPath resolves
|
|
2572
|
+
to `cwd` (realpath BOTH sides -- the Windows 8.3 short-path gotcha); `scope: user` always.
|
|
2573
|
+
Both registry shapes are read (version 1 maps a key to ONE record). Never raises on a broken
|
|
2574
|
+
registry: a malformed file reads as nothing installed."""
|
|
2575
|
+
enabled = _enabled_plugins(home, cwd)
|
|
2578
2576
|
installed = _installed_plugins(home)
|
|
2579
2577
|
cwd_real = os.path.realpath(cwd)
|
|
2578
|
+
out = []
|
|
2580
2579
|
for key, records in installed.items():
|
|
2581
2580
|
if not enabled.get(key):
|
|
2582
2581
|
continue
|
|
2583
|
-
|
|
2584
|
-
if plugin_part and pname != plugin_part:
|
|
2582
|
+
if plugin and key.split("@", 1)[0] != plugin:
|
|
2585
2583
|
continue
|
|
2586
2584
|
if isinstance(records, dict):
|
|
2587
2585
|
records = [records] # installed_plugins.json version 1: one record per key
|
|
@@ -2597,10 +2595,29 @@ def _resolve_plugin_tool(name, home, cwd="."):
|
|
|
2597
2595
|
ipath = rec.get("installPath")
|
|
2598
2596
|
if not ipath or not isinstance(ipath, str):
|
|
2599
2597
|
continue
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2598
|
+
out.append((key, ipath))
|
|
2599
|
+
return out
|
|
2600
|
+
|
|
2601
|
+
|
|
2602
|
+
def _resolve_plugin_tool(name, home, cwd="."):
|
|
2603
|
+
"""Resolve `name` against INSTALLED-AND-ENABLED plugins. `name` is either bare (`review`) or
|
|
2604
|
+
`<plugin>:<skill>` (the harness lists e.g. `open-code-review:review`). A plugin provides the
|
|
2605
|
+
tool when `skills/<skill>/SKILL.md` or `commands/<skill>.md` exists under one of its install
|
|
2606
|
+
paths (official code-review and open-code-review ship only `commands/`). Returns
|
|
2607
|
+
(path, sha256) or None. A `scope: project` record is honored only when its projectPath
|
|
2608
|
+
resolves to cwd (realpath BOTH sides -- the Windows 8.3 short-path gotcha); `scope: user`
|
|
2609
|
+
always. Never raises on a broken registry."""
|
|
2610
|
+
if ":" in name:
|
|
2611
|
+
plugin_part, skill_part = name.split(":", 1)
|
|
2612
|
+
else:
|
|
2613
|
+
plugin_part, skill_part = "", name
|
|
2614
|
+
if not skill_part:
|
|
2615
|
+
return None
|
|
2616
|
+
for _key, ipath in enabled_plugin_installs(home, cwd=cwd, plugin=plugin_part or None):
|
|
2617
|
+
for cand in (os.path.join(ipath, "skills", skill_part, "SKILL.md"),
|
|
2618
|
+
os.path.join(ipath, "commands", skill_part + ".md")):
|
|
2619
|
+
if os.path.isfile(cand):
|
|
2620
|
+
return cand, _sha256_file(cand)
|
|
2604
2621
|
return None
|
|
2605
2622
|
|
|
2606
2623
|
|
|
@@ -19,7 +19,7 @@ shape.** Your job is to interrogate until there is a shared system shape, and to
|
|
|
19
19
|
the documents as you go — not to ask the human to design the system for you.
|
|
20
20
|
|
|
21
21
|
<!-- uscha:orientation-block:begin -->
|
|
22
|
-
<!-- uscha kit: 2.
|
|
22
|
+
<!-- uscha kit: 2.7.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
23
23
|
|
|
24
24
|
## First contact (show ONCE, then never again)
|
|
25
25
|
|
|
@@ -18,7 +18,7 @@ Paints the REAL state of the project at a glance. It does not narrate or estimat
|
|
|
18
18
|
wires the JSON the engine emits into the template. Read-only.
|
|
19
19
|
|
|
20
20
|
<!-- uscha:orientation-block:begin -->
|
|
21
|
-
<!-- uscha kit: 2.
|
|
21
|
+
<!-- uscha kit: 2.7.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
22
22
|
|
|
23
23
|
## Orientation markers (non-negotiable)
|
|
24
24
|
|
|
@@ -24,7 +24,7 @@ evidence-classed, content-addressed, and promoted to the contract only by a per-
|
|
|
24
24
|
human verdict (ADR-013).**
|
|
25
25
|
|
|
26
26
|
<!-- uscha:orientation-block:begin -->
|
|
27
|
-
<!-- uscha kit: 2.
|
|
27
|
+
<!-- uscha kit: 2.7.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
28
28
|
|
|
29
29
|
## First contact (show ONCE, then never again)
|
|
30
30
|
|
|
@@ -22,7 +22,7 @@ the grader — this skill just wraps the neutral prompt so Claude Code users get
|
|
|
22
22
|
in one command. Never add Claude-specific behavior to the contract.
|
|
23
23
|
|
|
24
24
|
<!-- uscha:orientation-block:begin -->
|
|
25
|
-
<!-- uscha kit: 2.
|
|
25
|
+
<!-- uscha kit: 2.7.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
26
26
|
|
|
27
27
|
## First contact (show ONCE, then never again)
|
|
28
28
|
|
|
@@ -58,7 +58,7 @@ that surface the warning cannot come from the skill itself. The `doctor` seam is
|
|
|
58
58
|
that still works there — it runs from any kit checkout and reads the installs from outside.
|
|
59
59
|
|
|
60
60
|
<!-- uscha:orientation-block:begin -->
|
|
61
|
-
<!-- uscha kit: 2.
|
|
61
|
+
<!-- uscha kit: 2.7.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
62
62
|
|
|
63
63
|
## Orientation markers (non-negotiable)
|
|
64
64
|
|
|
@@ -22,7 +22,7 @@ switch between at any time:
|
|
|
22
22
|
coverage, known deferred issues.
|
|
23
23
|
|
|
24
24
|
<!-- uscha:orientation-block:begin -->
|
|
25
|
-
<!-- uscha kit: 2.
|
|
25
|
+
<!-- uscha kit: 2.7.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
26
26
|
|
|
27
27
|
## First contact (show ONCE, then never again)
|
|
28
28
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "uscha",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.7.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, 57 subcommands + universal installer + npm/npx router). Facts block, guesses advise; the human approves.",
|
|
7
7
|
"author": {
|
package/uscha-kit/INSTALL.md
CHANGED
|
@@ -123,6 +123,13 @@ npx --yes @andresmassello/uscha@latest doctor --target both
|
|
|
123
123
|
|
|
124
124
|
`doctor` exits 1 for any unhealthy target in either text or `--json` mode. It checks installed skill presence, manifest/marketplace or hook registration, marker, and version; it does not measure file-content integrity.
|
|
125
125
|
|
|
126
|
+
`install` (after its transaction commits) and `doctor` (as an advisory section) also report the
|
|
127
|
+
extras (ADR-052, kit 2.7.0): how the dev loop's QA tools resolve on the target machine and whether
|
|
128
|
+
the optional engram is present, with the commands to add it. engram is present when it is usable
|
|
129
|
+
from Claude (the plugin installed and enabled, or an `engram` MCP server in the Claude
|
|
130
|
+
configuration); a bare binary on PATH reads "binary found, not wired into Claude". They only
|
|
131
|
+
report: nothing is installed, no command is run, and neither exit code changes.
|
|
132
|
+
|
|
126
133
|
If `npm view` returns `404` immediately after a new release, wait a few minutes:
|
|
127
134
|
npm search/dist-tags can propagate before the package metadata endpoint used by
|
|
128
135
|
`npx`. Do not republish the same version while propagation is in progress.
|
package/uscha-kit/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# uscha-kit
|
|
2
2
|
|
|
3
|
-
**Kit version:** v2.
|
|
3
|
+
**Kit version:** v2.7.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`,
|
|
@@ -498,6 +498,21 @@ Claude Code's native plugin flow remains available for Claude-only users, but
|
|
|
498
498
|
|
|
499
499
|
|
|
500
500
|
|
|
501
|
+
**The extras report** (kit 2.7.0, ADR-052). After the install transaction commits, `install`
|
|
502
|
+
reports — and only reports — how the dev loop's QA tools resolve on the target machine (the
|
|
503
|
+
kit default order `code-review`, `judgment-day`, `improve`, through the engine's own
|
|
504
|
+
`resolve_qa_tool`; `code-review` is "assumed, harness-provided, not measured") and whether the
|
|
505
|
+
optional engram is present, printing the commands a human may run to add it. Present means
|
|
506
|
+
usable from Claude: the plugin installed and enabled (its install path on disk), or an MCP server
|
|
507
|
+
named exactly `engram` at machine level (the user-level `mcpServers` of `.claude.json` or the
|
|
508
|
+
plugin's own `.mcp.json`, never another project's entry). The binary on PATH is reported but
|
|
509
|
+
alone reads "binary found, not wired into Claude". The MCP check reads only the Claude
|
|
510
|
+
configuration, for `--target codex` too. It installs nothing beyond the
|
|
511
|
+
kit, shows no prompt, copies no QA skill and runs none of those commands; a detection error
|
|
512
|
+
prints `could not check extras: <reason>` and the install still succeeds. `--dry-run` lists the
|
|
513
|
+
checks without making them, and `install-uscha.py doctor` repeats the report, live, as an
|
|
514
|
+
advisory section that never changes its exit code.
|
|
515
|
+
|
|
501
516
|
**Verify the installation with `doctor`** (kit 1.22.0, in the spirit of flutter doctor —
|
|
502
517
|
Windows and Linux, ASCII output, exit 1 only on errors):
|
|
503
518
|
|
package/uscha-kit/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
uscha-kit 2.
|
|
1
|
+
uscha-kit 2.7.0
|
|
@@ -618,18 +618,231 @@ def cmd_install(args):
|
|
|
618
618
|
else:
|
|
619
619
|
root = installers[target](home, args.mode, args.dry_run, operations)
|
|
620
620
|
installed[target] = str(root)
|
|
621
|
-
|
|
621
|
+
# ADR-052: the extras report runs only AFTER every install transaction above has committed
|
|
622
|
+
# (an install that raised never gets here, so its exit code is untouched), and it can only
|
|
623
|
+
# ever ADD lines -- a detection error is reported, never raised.
|
|
624
|
+
extras = extras_plan() if args.dry_run else detect_extras(home, explicit_home=bool(args.home))
|
|
625
|
+
emit({"status": "planned" if args.dry_run else "installed", "dry_run": args.dry_run, "source_version": source_version(), "home": str(home), "installed": installed, "operations": operations, "next": next_steps(args.target), "extras": extras}, args.json)
|
|
626
|
+
if not args.json:
|
|
627
|
+
print_extras(extras)
|
|
622
628
|
|
|
623
629
|
|
|
624
630
|
def cmd_doctor(args):
|
|
625
631
|
home = home_path(args)
|
|
626
632
|
targets = {target: target_status(home, target) for target in selected_targets(args.target)}
|
|
627
633
|
ok = all(status["healthy"] for status in targets.values())
|
|
628
|
-
|
|
634
|
+
# Advisory, LIVE and stateless (ADR-052): re-detected on every run through the same functions
|
|
635
|
+
# the install summary uses, never read from the marker, and never part of `ok`.
|
|
636
|
+
extras = detect_extras(home, explicit_home=bool(args.home))
|
|
637
|
+
emit({"ok": ok, "source_version": source_version(), "home": str(home), "python": sys.version.split()[0], "targets": targets, "extras": extras}, args.json)
|
|
638
|
+
if not args.json:
|
|
639
|
+
print_extras(extras, advisory=True)
|
|
629
640
|
if not ok:
|
|
630
641
|
raise SystemExit(1)
|
|
631
642
|
|
|
632
643
|
|
|
644
|
+
# --------------------------------------------------------------------------------------------- #
|
|
645
|
+
# Optional extras (ADR-052, kit 2.7.0) -- DETECT AND TELL, never install.
|
|
646
|
+
# The dev loop orchestrates QA tools the kit does not ship (code-review / judgment-day / improve)
|
|
647
|
+
# and works with an optional memory plugin (engram). The installer REPORTS whether they are
|
|
648
|
+
# present and prints the commands a human may choose to run. It installs nothing beyond the kit,
|
|
649
|
+
# asks nothing, copies no QA skill and runs none of the commands it prints: the default install
|
|
650
|
+
# stays dependency-free. Tool resolution is NOT reimplemented here -- the engine's own
|
|
651
|
+
# resolve_qa_tool (ADR-051) answers, so `qa-tools-check` and this report cannot disagree.
|
|
652
|
+
# --------------------------------------------------------------------------------------------- #
|
|
653
|
+
EXTRAS_ENGINE = KIT_ROOT / ".claude" / "skills" / "uscha-devloop" / "qa_ledger.py"
|
|
654
|
+
QA_ORDER_NOTE = "the kit default order; your project's risk profile may need fewer"
|
|
655
|
+
QA_SOURCE_NOTES = {"builtin-assumed": "assumed, harness-provided, not measured"}
|
|
656
|
+
ENGRAM_NOTE = "optional -- uscha does not require it"
|
|
657
|
+
ENGRAM_CHECKS = (("binary", "binary on PATH"),
|
|
658
|
+
("plugin", "plugin installed and enabled"),
|
|
659
|
+
("mcp", "MCP server in the Claude config"))
|
|
660
|
+
# Printed for a human to run; this installer never executes them (smoke T170 pins that).
|
|
661
|
+
ENGRAM_ADD_COMMANDS = ("claude plugin marketplace add Gentleman-Programming/engram",
|
|
662
|
+
"claude plugin install engram",
|
|
663
|
+
"engram setup claude-code")
|
|
664
|
+
ENGRAM_BINARY_NOTE = "The engram binary itself is a separate download."
|
|
665
|
+
ENGRAM_NOT_WIRED = "binary found, not wired into Claude"
|
|
666
|
+
# The MCP server name the engram plugin's own `.mcp.json` declares. Matched EXACTLY, never as a
|
|
667
|
+
# substring: a server called `not-an-engramatic-thing` is not engram.
|
|
668
|
+
ENGRAM_MCP_SERVER = "engram"
|
|
669
|
+
# A directory that is never created: the resolver's PROJECT probes (./.claude/skills, the project
|
|
670
|
+
# settings files) are pointed at it, so the report describes the MACHINE, not whatever directory
|
|
671
|
+
# `npx` happened to be run from.
|
|
672
|
+
MACHINE_SCOPE_DIR = ".uscha-machine-scope"
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
def _extras_engine():
|
|
676
|
+
# the npm package's own engine, imported, never reimplemented; no bytecode is written into
|
|
677
|
+
# the package (the bench arms import it the same way)
|
|
678
|
+
sys.dont_write_bytecode = True
|
|
679
|
+
import importlib.util
|
|
680
|
+
spec = importlib.util.spec_from_file_location("uscha_extras_engine", str(EXTRAS_ENGINE))
|
|
681
|
+
mod = importlib.util.module_from_spec(spec)
|
|
682
|
+
spec.loader.exec_module(mod)
|
|
683
|
+
return mod
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
def kit_default_qa_order():
|
|
687
|
+
"""The kit's default qa_tools_order, read from its reference config -- one list, not two."""
|
|
688
|
+
cfg = load_json(KIT_ROOT / "uscha.config.json", "kit reference config")
|
|
689
|
+
order = (cfg.get("defaults") or {}).get("qa_tools_order")
|
|
690
|
+
if not isinstance(order, list) or not order or not all(isinstance(t, str) for t in order):
|
|
691
|
+
raise InstallError("kit reference config has no qa_tools_order list")
|
|
692
|
+
return order
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
def extras_plan():
|
|
696
|
+
"""--dry-run: list what would be checked; check nothing, write nothing."""
|
|
697
|
+
try:
|
|
698
|
+
order = kit_default_qa_order()
|
|
699
|
+
except Exception as exc:
|
|
700
|
+
return {"checked": False, "error": "%s: %s" % (type(exc).__name__, exc)}
|
|
701
|
+
return {"checked": False, "planned": True,
|
|
702
|
+
"qa_tools": {"order": order, "order_source": QA_ORDER_NOTE},
|
|
703
|
+
"engram": {"note": ENGRAM_NOTE, "checks": [label for _, label in ENGRAM_CHECKS]}}
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
def _mcp_names(data):
|
|
707
|
+
servers = data.get("mcpServers") if isinstance(data, dict) else None
|
|
708
|
+
return [str(k) for k in servers] if isinstance(servers, dict) else []
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
def _engram_mcp_wired(eng, home, installs):
|
|
712
|
+
"""Is an engram MCP server wired into Claude at MACHINE level? Two sources only: the user-level
|
|
713
|
+
`mcpServers` of `.claude.json`, and the `.mcp.json` of an installed AND enabled engram plugin
|
|
714
|
+
whose installPath exists (that is how the plugin wires its server; `installs` arrives already
|
|
715
|
+
filtered). A `projects[<path>].mcpServers` entry serves one OTHER project, not this machine,
|
|
716
|
+
so it never counts. The Claude config is the only one read, whatever the install --target.
|
|
717
|
+
The name must equal ENGRAM_MCP_SERVER. A missing or malformed file reads as not wired."""
|
|
718
|
+
claude_json = (os.path.join(eng._claude_config_dir(str(home)), ".claude.json")
|
|
719
|
+
if os.environ.get("CLAUDE_CONFIG_DIR")
|
|
720
|
+
else os.path.join(str(home), ".claude.json"))
|
|
721
|
+
names = []
|
|
722
|
+
try:
|
|
723
|
+
with open(claude_json, encoding="utf-8") as fh:
|
|
724
|
+
names.extend(_mcp_names(json.load(fh)))
|
|
725
|
+
except (OSError, ValueError):
|
|
726
|
+
pass
|
|
727
|
+
for _key, ipath in installs:
|
|
728
|
+
try:
|
|
729
|
+
with open(os.path.join(ipath, ".mcp.json"), encoding="utf-8") as fh:
|
|
730
|
+
names.extend(_mcp_names(json.load(fh)))
|
|
731
|
+
except (OSError, ValueError):
|
|
732
|
+
pass
|
|
733
|
+
return ENGRAM_MCP_SERVER in names
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
def _on_path(name):
|
|
737
|
+
"""Is `name` an executable in a PATH entry? A manual walk, NOT shutil.which: on Windows
|
|
738
|
+
shutil.which also searches the CURRENT directory (Python 3.8 always; later versions unless
|
|
739
|
+
NoDefaultCurrentDirectoryInExePath is set), so an `engram.cmd` lying in the directory `npx`
|
|
740
|
+
ran from read as installed. The cwd is searched only when PATH itself names it: a literal `.`,
|
|
741
|
+
or (POSIX semantics) an empty entry -- an empty entry on Windows, a trailing `;`, is skipped.
|
|
742
|
+
On Windows the PATHEXT extensions are tried."""
|
|
743
|
+
if os.name == "nt":
|
|
744
|
+
pathext = os.environ.get("PATHEXT") or ".COM;.EXE;.BAT;.CMD"
|
|
745
|
+
exts = [e for e in pathext.split(os.pathsep) if e]
|
|
746
|
+
if os.path.splitext(name)[1].lower() in [e.lower() for e in exts]:
|
|
747
|
+
names = [name]
|
|
748
|
+
else:
|
|
749
|
+
names = [name + e for e in exts]
|
|
750
|
+
else:
|
|
751
|
+
names = [name]
|
|
752
|
+
for entry in os.environ.get("PATH", "").split(os.pathsep):
|
|
753
|
+
if not entry:
|
|
754
|
+
if os.name == "nt":
|
|
755
|
+
continue
|
|
756
|
+
entry = os.curdir
|
|
757
|
+
for n in names:
|
|
758
|
+
cand = os.path.join(entry, n)
|
|
759
|
+
if os.path.isfile(cand) and (os.name == "nt" or os.access(cand, os.X_OK)):
|
|
760
|
+
return True
|
|
761
|
+
return False
|
|
762
|
+
|
|
763
|
+
|
|
764
|
+
def detect_extras(home, explicit_home=False):
|
|
765
|
+
"""The one detection path for the install summary AND doctor. Reads external state only and
|
|
766
|
+
never raises: any error becomes {"checked": False, "error": reason}."""
|
|
767
|
+
saved = os.environ.get("CLAUDE_CONFIG_DIR")
|
|
768
|
+
try:
|
|
769
|
+
# An explicit --home names the machine to describe; a CLAUDE_CONFIG_DIR inherited from
|
|
770
|
+
# the operator's own shell points at THEIR config, so it is set aside for the probe.
|
|
771
|
+
# Without --home the target IS this machine, and the variable is honoured, exactly as
|
|
772
|
+
# the harness and qa-tools-check honour it.
|
|
773
|
+
if explicit_home and saved is not None:
|
|
774
|
+
del os.environ["CLAUDE_CONFIG_DIR"]
|
|
775
|
+
order = kit_default_qa_order()
|
|
776
|
+
eng = _extras_engine()
|
|
777
|
+
scope = os.path.join(str(home), MACHINE_SCOPE_DIR)
|
|
778
|
+
tools = [eng.resolve_qa_tool(t, (), project_root=scope, home=str(home)) for t in order]
|
|
779
|
+
# an enabled record whose installPath is gone is a stale registry entry, not a plugin
|
|
780
|
+
# Claude can load (filtered here, not in the shared engine helper)
|
|
781
|
+
installs = [(k, p) for k, p in
|
|
782
|
+
eng.enabled_plugin_installs(str(home), cwd=scope, plugin="engram")
|
|
783
|
+
if os.path.isdir(p)]
|
|
784
|
+
signals = {"binary": _on_path("engram"),
|
|
785
|
+
"plugin": bool(installs),
|
|
786
|
+
"mcp": _engram_mcp_wired(eng, home, installs)}
|
|
787
|
+
# present = USABLE from Claude: the plugin or the MCP server. The binary alone is
|
|
788
|
+
# informational -- a binary nobody wired in gives Claude no engram.
|
|
789
|
+
usable = bool(signals["plugin"] or signals["mcp"])
|
|
790
|
+
return {"checked": True,
|
|
791
|
+
"claude_config_dir": eng._claude_config_dir(str(home)),
|
|
792
|
+
"qa_tools": {"order": order, "order_source": QA_ORDER_NOTE, "tools": tools},
|
|
793
|
+
"engram": {"note": ENGRAM_NOTE, "present": usable,
|
|
794
|
+
"signals": signals,
|
|
795
|
+
"add_commands": list(ENGRAM_ADD_COMMANDS),
|
|
796
|
+
"binary_note": ENGRAM_BINARY_NOTE}}
|
|
797
|
+
except (Exception, SystemExit) as exc:
|
|
798
|
+
return {"checked": False, "error": "%s: %s" % (type(exc).__name__, exc)}
|
|
799
|
+
finally:
|
|
800
|
+
if explicit_home and saved is not None:
|
|
801
|
+
os.environ["CLAUDE_CONFIG_DIR"] = saved
|
|
802
|
+
|
|
803
|
+
|
|
804
|
+
def extras_lines(extras, advisory=False):
|
|
805
|
+
if not extras.get("checked") and not extras.get("planned"):
|
|
806
|
+
return ["could not check extras: %s" % extras.get("error", "unknown error")]
|
|
807
|
+
qa, engram = extras["qa_tools"], extras["engram"]
|
|
808
|
+
if extras.get("planned"):
|
|
809
|
+
return ["Extras -- would be checked after the install (--dry-run checks nothing):",
|
|
810
|
+
" QA tools for the dev loop (%s): %s" % (qa["order_source"], ", ".join(qa["order"])),
|
|
811
|
+
" engram (%s): %s" % (engram["note"], ", ".join(engram["checks"]))]
|
|
812
|
+
lines = ["Extras -- reported only, never installed%s:"
|
|
813
|
+
% (" (advisory: not part of doctor's verdict)" if advisory else ""),
|
|
814
|
+
" QA tools for the dev loop (%s):" % qa["order_source"]]
|
|
815
|
+
for row in qa["tools"]:
|
|
816
|
+
src = row.get("source")
|
|
817
|
+
where = ("(%s) " % row["root"] if row.get("root") else "") + (row.get("path") or "")
|
|
818
|
+
detail = QA_SOURCE_NOTES.get(src) or where.strip()
|
|
819
|
+
lines.append(" %-14s %s%s" % (row.get("tool"), src, (" -- " + detail) if detail else ""))
|
|
820
|
+
if any(row.get("source") == "MISSING" for row in qa["tools"]):
|
|
821
|
+
lines.append(" A MISSING tool blocks the dev loop (qa-tools-check): install it as a skill or "
|
|
822
|
+
"plugin, or declare it in defaults.qa_tools_external.")
|
|
823
|
+
sig = engram["signals"]
|
|
824
|
+
lines.append(" engram (%s): %s" % (engram["note"], "present" if engram["present"] else "missing"))
|
|
825
|
+
lines.append(" " + " | ".join("%s: %s" % (label, "yes" if sig.get(key) else "no")
|
|
826
|
+
for key, label in ENGRAM_CHECKS))
|
|
827
|
+
if not engram["present"]:
|
|
828
|
+
if sig.get("binary"):
|
|
829
|
+
lines.append(" " + ENGRAM_NOT_WIRED)
|
|
830
|
+
lines.append(" To add it, run these yourself (this installer never runs them):")
|
|
831
|
+
lines.extend(" " + c for c in engram["add_commands"])
|
|
832
|
+
if not sig.get("binary"):
|
|
833
|
+
lines.append(" " + engram["binary_note"])
|
|
834
|
+
return lines
|
|
835
|
+
|
|
836
|
+
|
|
837
|
+
def print_extras(extras, advisory=False):
|
|
838
|
+
try:
|
|
839
|
+
lines = extras_lines(extras, advisory)
|
|
840
|
+
except Exception as exc: # a malformed report must not fail a finished install
|
|
841
|
+
lines = ["could not check extras: %s: %s" % (type(exc).__name__, exc)]
|
|
842
|
+
for line in lines:
|
|
843
|
+
print(line)
|
|
844
|
+
|
|
845
|
+
|
|
633
846
|
# statusline wiring (kit 1.46.0): the progress statusline + its Stop-hook refresher, installed
|
|
634
847
|
# per-project. Commands are by NAME with forward slashes (Windows eats backslashes in the
|
|
635
848
|
# statusLine command; absolute paths are brittle across machines).
|
|
@@ -19,7 +19,7 @@ phases. **You are NOT a generator. You are an interrogator that distills.** The
|
|
|
19
19
|
is in the questions, not in agreeing.
|
|
20
20
|
|
|
21
21
|
<!-- uscha:orientation-block:begin -->
|
|
22
|
-
<!-- uscha kit: 2.
|
|
22
|
+
<!-- uscha kit: 2.7.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
23
23
|
|
|
24
24
|
## First contact (show ONCE, then never again)
|
|
25
25
|
|
|
@@ -19,7 +19,7 @@ what the code DOES, mechanically, by running it — never what it should do.** Y
|
|
|
19
19
|
the capture harness; you may NOT create, rename, or edit any `.approved` file.
|
|
20
20
|
|
|
21
21
|
<!-- uscha:orientation-block:begin -->
|
|
22
|
-
<!-- uscha kit: 2.
|
|
22
|
+
<!-- uscha kit: 2.7.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
23
23
|
|
|
24
24
|
## First contact (show ONCE, then never again)
|
|
25
25
|
|
|
@@ -27,7 +27,7 @@ artifacts; these can block) and **self-reported** agent counts (log-step — nar
|
|
|
27
27
|
recorded for the retrospective; a measured red always overrides a narrated green).
|
|
28
28
|
|
|
29
29
|
<!-- uscha:orientation-block:begin -->
|
|
30
|
-
<!-- uscha kit: 2.
|
|
30
|
+
<!-- uscha kit: 2.7.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
31
31
|
|
|
32
32
|
## First contact (show ONCE, then never again)
|
|
33
33
|
|
|
@@ -2522,13 +2522,16 @@ def _sha256_file(path):
|
|
|
2522
2522
|
return None
|
|
2523
2523
|
|
|
2524
2524
|
|
|
2525
|
-
def _enabled_plugins(home):
|
|
2525
|
+
def _enabled_plugins(home, cwd="."):
|
|
2526
2526
|
"""`<name>@<marketplace>` -> bool, merged from the SAME settings files doctor walks
|
|
2527
2527
|
(user ~/.claude, project .claude, project-local; later wins per key). A missing or malformed
|
|
2528
|
-
file is skipped, never raised: a broken settings file must not turn a real tool MISSING.
|
|
2528
|
+
file is skipped, never raised: a broken settings file must not turn a real tool MISSING.
|
|
2529
|
+
The project files are read under `cwd` (the resolver's project root; "." for every engine
|
|
2530
|
+
caller), so a machine-level caller -- the installer's extras report -- can keep the
|
|
2531
|
+
directory it happens to run from out of the answer."""
|
|
2529
2532
|
paths = [os.path.join(_claude_config_dir(home), "settings.json"),
|
|
2530
|
-
os.path.join(".claude", "settings.json"),
|
|
2531
|
-
os.path.join(".claude", "settings.local.json")]
|
|
2533
|
+
os.path.join(cwd, ".claude", "settings.json"),
|
|
2534
|
+
os.path.join(cwd, ".claude", "settings.local.json")]
|
|
2532
2535
|
enabled = {}
|
|
2533
2536
|
for p in paths:
|
|
2534
2537
|
try:
|
|
@@ -2560,28 +2563,23 @@ def _installed_plugins(home):
|
|
|
2560
2563
|
return plugins if isinstance(plugins, dict) else {}
|
|
2561
2564
|
|
|
2562
2565
|
|
|
2563
|
-
def
|
|
2564
|
-
"""
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
else:
|
|
2574
|
-
plugin_part, skill_part = "", name
|
|
2575
|
-
if not skill_part:
|
|
2576
|
-
return None
|
|
2577
|
-
enabled = _enabled_plugins(home)
|
|
2566
|
+
def enabled_plugin_installs(home, cwd=".", plugin=None):
|
|
2567
|
+
"""[(`<name>@<marketplace>`, installPath)] for every plugin that is installed AND enabled --
|
|
2568
|
+
the plugin-level half of the registry reader, shared by the QA-tool resolver below and the
|
|
2569
|
+
installer's optional-extras report (ADR-052), which asks about a PLUGIN (engram), not a
|
|
2570
|
+
skill, and must not push a plugin name through the skill resolver. `plugin` filters by the
|
|
2571
|
+
name part before the `@`. A `scope: project` record counts only when its projectPath resolves
|
|
2572
|
+
to `cwd` (realpath BOTH sides -- the Windows 8.3 short-path gotcha); `scope: user` always.
|
|
2573
|
+
Both registry shapes are read (version 1 maps a key to ONE record). Never raises on a broken
|
|
2574
|
+
registry: a malformed file reads as nothing installed."""
|
|
2575
|
+
enabled = _enabled_plugins(home, cwd)
|
|
2578
2576
|
installed = _installed_plugins(home)
|
|
2579
2577
|
cwd_real = os.path.realpath(cwd)
|
|
2578
|
+
out = []
|
|
2580
2579
|
for key, records in installed.items():
|
|
2581
2580
|
if not enabled.get(key):
|
|
2582
2581
|
continue
|
|
2583
|
-
|
|
2584
|
-
if plugin_part and pname != plugin_part:
|
|
2582
|
+
if plugin and key.split("@", 1)[0] != plugin:
|
|
2585
2583
|
continue
|
|
2586
2584
|
if isinstance(records, dict):
|
|
2587
2585
|
records = [records] # installed_plugins.json version 1: one record per key
|
|
@@ -2597,10 +2595,29 @@ def _resolve_plugin_tool(name, home, cwd="."):
|
|
|
2597
2595
|
ipath = rec.get("installPath")
|
|
2598
2596
|
if not ipath or not isinstance(ipath, str):
|
|
2599
2597
|
continue
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2598
|
+
out.append((key, ipath))
|
|
2599
|
+
return out
|
|
2600
|
+
|
|
2601
|
+
|
|
2602
|
+
def _resolve_plugin_tool(name, home, cwd="."):
|
|
2603
|
+
"""Resolve `name` against INSTALLED-AND-ENABLED plugins. `name` is either bare (`review`) or
|
|
2604
|
+
`<plugin>:<skill>` (the harness lists e.g. `open-code-review:review`). A plugin provides the
|
|
2605
|
+
tool when `skills/<skill>/SKILL.md` or `commands/<skill>.md` exists under one of its install
|
|
2606
|
+
paths (official code-review and open-code-review ship only `commands/`). Returns
|
|
2607
|
+
(path, sha256) or None. A `scope: project` record is honored only when its projectPath
|
|
2608
|
+
resolves to cwd (realpath BOTH sides -- the Windows 8.3 short-path gotcha); `scope: user`
|
|
2609
|
+
always. Never raises on a broken registry."""
|
|
2610
|
+
if ":" in name:
|
|
2611
|
+
plugin_part, skill_part = name.split(":", 1)
|
|
2612
|
+
else:
|
|
2613
|
+
plugin_part, skill_part = "", name
|
|
2614
|
+
if not skill_part:
|
|
2615
|
+
return None
|
|
2616
|
+
for _key, ipath in enabled_plugin_installs(home, cwd=cwd, plugin=plugin_part or None):
|
|
2617
|
+
for cand in (os.path.join(ipath, "skills", skill_part, "SKILL.md"),
|
|
2618
|
+
os.path.join(ipath, "commands", skill_part + ".md")):
|
|
2619
|
+
if os.path.isfile(cand):
|
|
2620
|
+
return cand, _sha256_file(cand)
|
|
2604
2621
|
return None
|
|
2605
2622
|
|
|
2606
2623
|
|
|
@@ -19,7 +19,7 @@ shape.** Your job is to interrogate until there is a shared system shape, and to
|
|
|
19
19
|
the documents as you go — not to ask the human to design the system for you.
|
|
20
20
|
|
|
21
21
|
<!-- uscha:orientation-block:begin -->
|
|
22
|
-
<!-- uscha kit: 2.
|
|
22
|
+
<!-- uscha kit: 2.7.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
23
23
|
|
|
24
24
|
## First contact (show ONCE, then never again)
|
|
25
25
|
|
|
@@ -18,7 +18,7 @@ Paints the REAL state of the project at a glance. It does not narrate or estimat
|
|
|
18
18
|
wires the JSON the engine emits into the template. Read-only.
|
|
19
19
|
|
|
20
20
|
<!-- uscha:orientation-block:begin -->
|
|
21
|
-
<!-- uscha kit: 2.
|
|
21
|
+
<!-- uscha kit: 2.7.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
22
22
|
|
|
23
23
|
## Orientation markers (non-negotiable)
|
|
24
24
|
|
|
@@ -24,7 +24,7 @@ evidence-classed, content-addressed, and promoted to the contract only by a per-
|
|
|
24
24
|
human verdict (ADR-013).**
|
|
25
25
|
|
|
26
26
|
<!-- uscha:orientation-block:begin -->
|
|
27
|
-
<!-- uscha kit: 2.
|
|
27
|
+
<!-- uscha kit: 2.7.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
28
28
|
|
|
29
29
|
## First contact (show ONCE, then never again)
|
|
30
30
|
|
|
@@ -22,7 +22,7 @@ the grader — this skill just wraps the neutral prompt so Claude Code users get
|
|
|
22
22
|
in one command. Never add Claude-specific behavior to the contract.
|
|
23
23
|
|
|
24
24
|
<!-- uscha:orientation-block:begin -->
|
|
25
|
-
<!-- uscha kit: 2.
|
|
25
|
+
<!-- uscha kit: 2.7.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
26
26
|
|
|
27
27
|
## First contact (show ONCE, then never again)
|
|
28
28
|
|
|
@@ -58,7 +58,7 @@ that surface the warning cannot come from the skill itself. The `doctor` seam is
|
|
|
58
58
|
that still works there — it runs from any kit checkout and reads the installs from outside.
|
|
59
59
|
|
|
60
60
|
<!-- uscha:orientation-block:begin -->
|
|
61
|
-
<!-- uscha kit: 2.
|
|
61
|
+
<!-- uscha kit: 2.7.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
62
62
|
|
|
63
63
|
## Orientation markers (non-negotiable)
|
|
64
64
|
|
|
@@ -22,7 +22,7 @@ switch between at any time:
|
|
|
22
22
|
coverage, known deferred issues.
|
|
23
23
|
|
|
24
24
|
<!-- uscha:orientation-block:begin -->
|
|
25
|
-
<!-- uscha kit: 2.
|
|
25
|
+
<!-- uscha kit: 2.7.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
26
26
|
|
|
27
27
|
## First contact (show ONCE, then never again)
|
|
28
28
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_comment": "COMPREHENSIVE REFERENCE, not a project config. Every knob the engine and the skills understand, at the kit's own value, so a human can read what can be declared. `uscha init` does NOT copy this file: it GENERATES a minimal project config, because a copied default is an explicit declaration and an explicit declaration outranks the preset named by defaults.risk_profile (ADR-001, as amended). Copy a block from here into your project only when you mean to override the engine default or the preset. NOTE: no version string may be written into this comment -- the release script requires exactly one occurrence of the version in this file (I3), and a second one refuses the next release.",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"project": null,
|
|
5
5
|
"defaults": {
|
|
6
6
|
"coverage_threshold": 60,
|