CLI ergonomics: per-subcommand --help, did-you-mean, arg validation, UTF-8 hardening - #214
CLI ergonomics: per-subcommand --help, did-you-mean, arg validation, UTF-8 hardening#214OsherElhadad wants to merge 2 commits into
Conversation
|
❌ Automatic Labeling Failed An error occurred while trying to automatically label this pull request. Please check the workflow logs for details and add labels manually. |
…UTF-8 hardening Closes #137. - Every subcommand gets a real `--help` with a description and examples, built through one `_parser()` helper so the shape can't drift per command. - The top-level listing is GENERATED from `COMMANDS` + each handler's docstring, so adding a subcommand can no longer leave the usage string stale (the one real conflict across this review batch was five branches editing that literal). - Unknown subcommand: `difflib.get_close_matches` suggestion, exit 2, nothing on stdout. A phase-skill name (`cap-evolve finalize`, #203) is redirected to the runnable `scripts/run.py` path instead of a bare "unknown command". - `cap-evolve run` rejects negative budget caps (0 means unlimited, so a negative value is a typo that would make the cap never bind) — exit 2, before any spend. - stdout stays a SINGLE JSON object: the dashboard-launch status line moved to stderr, where #116 already routes human progress. It used to be printed to stdout, so `cap-evolve run | json.loads` raised "Extra data" on every run. - UTF-8 hardening at entry: reports carry the arrow/delta/check glyphs, and an ascii stream (PYTHONIOENCODING=ascii, LC_ALL=C) used to raise UnicodeEncodeError mid-print — killing a run whose budget was already spent. - Extends #198's core/tests/test_documented_cli.py rather than adding a second checker: the `cap-evolve` front door (subcommands, --help, exit codes, the stdout contract) alongside #198's phase-script flag scanner.
🔬 EvidenceEvery command below was run in a clean worktree off 1. Top-level help — generated from
|
| count | |
|---|---|
baseline on main |
179 |
| this branch | 186 passed, 0 failed |
| new tests added | 7 |
test_dashboard_launch.py::test_maybe_launch_spawns_when_available (environmentally
flaky on port 7878, #200, unrelated) passed on this run.
9. compileall
$ python -m compileall -q core skills; echo $?
010. Merge sequencing against the five sibling cli.py branches
Each merged individually onto this branch (aborted after):
$ git merge origin/fix/issue-109-optimizer-context
→ clean
$ git merge origin/feat/issue-134-provider-creds
→ clean
$ git merge origin/feat/issue-132-model-tiering
→ clean
$ git merge origin/feat/issue-116-follow-tail
CONFLICT (content): Merge conflict in core/cap_evolve/cli.py
$ git merge origin/feat/issue-121-doctor
CONFLICT (content): Merge conflict in core/cap_evolve/cli.py
$ git merge origin/docs/issue-104-gate-modes
CONFLICT (add/add): Merge conflict in core/tests/test_documented_cli.pyBoth cli.py conflicts are the same two hunks: the module-docstring subcommand list
and the literal usage string in main(). This PR deletes both, so the resolution is
"take mine" in each case and the merged result loses nothing:
- Classic cap-evolve run is silent for its entire duration — add --follow / cap-evolve tail #116 keeps
--follow,tail, and all fivedone()drains (untouched here). - Add cap-evolve doctor install/health diagnostic #121 keeps its
COMMANDSrow, dispatcher, anddoctor.py(untouched here).
The test_documented_cli.py add/add with #198 is the two complementary halves of the
same file — concatenate them, no line overlaps (mine is the cap-evolve front door,
theirs is the phase-script flag scanner).
Verified by actually performing the merge with #116 and #121 on top and resolving both
hunks as "take mine". The generated listing then picks up tail and doctor with
zero edits from me — which is the entire point of generating it:
$ cap-evolve --help # 137 + 116 + 121 merged, resolution = take mine
usage: cap-evolve {version|splits|check|doctor|run|estimate|dashboard|tail} [args]
commands:
version Print the installed cap-evolve version as JSON.
splits Compute the seeded train/val/test split for a set of task ids.
check Verify a project's adapter is fully implemented and deterministic.
doctor see `cap-evolve doctor --help`
run Sequence the whole optimization run: baseline → algorithm → finalize → report.
estimate Pre-run cost estimate without spending anything.
dashboard Launch (or focus) the live dashboard server over a base dir of runs.
tail Attach to an existing/ongoing run dir and print its event stream.
run `cap-evolve <command> --help` for a command's flags and examples.
$ cap-evolve doctr
cap-evolve: unknown command 'doctr'
did you mean: doctor?
available commands: version, splits, check, doctor, run, estimate, dashboard, tail
$ cap-evolve tail --help | head -4
usage: cap-evolve tail [-h] [--base BASE] [--from-start]
[--idle-timeout IDLE_TIMEOUT] [--no-color]
[run_dir]
$ cap-evolve doctor --help | head -4
usage: cap-evolve doctor [-h] [--json] [cwd]
install/health diagnostic; exits non-zero on hard failure
Post-merge test suite (137 + 116 + 121, both conflicts resolved as "take mine"):
$ PYTHONPATH=core python -m pytest core/tests -q
........................................................................ [ 53%]
........................................................................ [ 80%]
...................................................... [100%]
270 passed in 71.89s (0:01:11)270 passed, 0 failed with all three branches combined.
Recommended merge order
#199 (109) → #205 (132) → #190 (134) → #191 (116) → #193 (121) → #137
#198 (104) anywhere; then concatenate the two test_documented_cli.py halves
#137 last of the cli.py five. It removes both copies of the subcommand list, so
anything merged after it needs no usage-string edit at all — whereas merging it first
means #116 and #121 each re-add a list this PR deleted.
Files touched
| File | Δ | What |
|---|---|---|
core/cap_evolve/cli.py |
+180/−37 | _parser, _did_you_mean, _usage, _harden_utf8, _PHASE_SCRIPTS, main(), per-command parser construction, dashboard print → stderr |
core/tests/test_documented_cli.py |
+155 (new here; shared with #198) | 7 tests |
core/cap_evolve/__main__.py |
+14/−3 | _cmd_splits takes prog=, gains a description/examples/flag help (it is the same parser cap-evolve splits delegates to — one parser, two front doors, no second copy) |
examples/toy_calc/run.sh |
+4/−2 | Working directory: echo → stderr so the example is pipe-safe |
f0f3d06 to
ea7b33e
Compare
🔍 Review — PR #214Verdict: CHANGES REQUESTED — two blocking items. The central claim (#217, stdout is one BlockingB1 — $ python skills/phases/finalize/scripts/run.py --run-dir /tmp/realrun/.capevolve/run_z
usage: finalize [-h] --run-dir RUN_DIR --project PROJECT [--n-trials N_TRIALS]
finalize: error: the following arguments are required: --project [exit 2]
$ python skills/phases/intake/scripts/run.py --run-dir /tmp/x
intake: error: unrecognized arguments: --run-dir /tmp/x [exit 2]
$ python skills/phases/implement-and-check/scripts/run.py --run-dir /tmp/x
implement-and-check: error: unrecognized arguments: --run-dir /tmp/x [exit 2]
$ python skills/phases/gate/scripts/run.py --run-dir /tmp/x
gate: error: the following arguments are required: --current, --candidate [exit 2]
$ python skills/phases/baseline/scripts/run.py --run-dir /tmp/x
baseline: error: the following arguments are required: --project, --capability [exit 2]Consequence: an agent following Fix (lazy): per-phase flags in the table, since the data already exists — _PHASE_SCRIPTS = {"intake": "", "implement-and-check": "--project <project>",
"baseline": "--project <project> --capability <cap>",
"diagnose": "--run-dir <dir>", "evaluate": "--run-dir <dir> --project <project> --candidate <c>",
"gate": "--current <a> --candidate <b>",
"finalize": "--run-dir <dir> --project <project>", "report": "--run-dir <dir>"}and render B2 — for stream in (sys.stdout, sys.stderr): # cli.py:723CPython opens stderr with $ PYTHONPATH=core python -m pytest core/tests/test_eventstream.py -q
FAILED core/tests/test_eventstream.py::test_run_follow_survives_ascii_io_encoding
> assert proc.stderr.encode("ascii", "strict")
E UnicodeEncodeError: 'ascii' codec can't encode character '\xb1' in position 101
1 failed, 62 passed in 11.78sYour own docstring already states the correct scope — $ PYTHONPATH=core python -m pytest core/tests/test_eventstream.py core/tests/test_documented_cli.py -q
71 passed in 15.67s
$ PYTHONPATH=core python -m pytest core/tests -q # full merged suite
250 passed in 80.40sSo the ruled resolution holds and needs nothing further. Please apply it in this PR (one Non-blockingN1 — $ python3.12 -c "
def f(): ''' '''
print((f.__doc__ or '').strip().splitlines()[0] if f.__doc__ else 'fallback')"
IndexError: list index out of range
$ python3.12 -c "def f(): ''' '''
print(repr(f.__doc__))" → ' ' # vs 3.13+: ''Consequence: on 3.10–3.12 a sibling branch registering a handler with a placeholder docstring N2 — $ cap-evolve run --spec ... --project ... --dashboard-port -5 --plan-only
OverflowError: bind(): port must be 0-65535 # uncaught traceback, no exit codeBecause N3 — Demonstrated abusable — a planted phantom is caught, but the same phantom on a line containing $ echo 'Run `cap-evolve frobnicate` to seal the run.' >> docs/INSTALL.md
E docs/INSTALL.md: `cap-evolve frobnicate` is not a command # caught ✓
$ echo 'You should not forget to run `cap-evolve frobnicate` daily.' >> docs/INSTALL.md
1 passed # missed ✗Consequence: ~28% of the surface is unchecked, and the guard's own protection is one stray N4 — $ pytest core/tests/test_documented_cli.py::test_run_stdout_is_a_single_json_object -q ; ps aux | grep capevolve_dashboard
python -m uvicorn capevolve_dashboard.asgi:app --host 127.0.0.1 --port 7879 ...
python -m uvicorn capevolve_dashboard.asgi:app --host 127.0.0.1 --port 7880 ...This is #200's flake mechanism (port contention) newly imported into #137's file, and a N5 — Nits
stdout contractBoth dashboard branches verified with a real zero-API Dashboard skipped ( $ bash examples/toy_calc/run.sh 2>/tmp/e_skip.txt >/tmp/o_skip.txt ; echo exit=$?
exit=0
$ python -c "import json;print('OK keys:',sorted(json.load(open('/tmp/o_skip.txt'))))"
OK keys: ['baseline_val', 'best_id', 'dashboard', 'dashboard_server', 'iterations',
'run_dir', 'test_baseline_reward', 'test_delta', 'test_pass_k', 'test_reward']
$ cat /tmp/e_skip.txt
Working directory: /var/folders/.../toy_calc.XXXXXX.AUachhb2ha
{"dashboard": "skipped", "reason": "capevolve-dashboard not installed (pip install -e dashboard/backend)"}Dashboard launched (stub $ ... python -m cap_evolve.cli run --spec ... --project ... --run-ts launched
exit=0
$ python -c "import json;d=json.load(open('/tmp/o_launch.txt'));print('OK keys:',sorted(d));print('dashboard_server=',d['dashboard_server'])"
OK keys: ['baseline_val', 'best_id', 'dashboard', 'dashboard_server', 'iterations',
'run_dir', 'test_baseline_reward', 'test_delta', 'test_pass_k', 'test_reward']
dashboard_server= http://127.0.0.1:7878
$ cat /tmp/e_launch.txt
{"dashboard": "http://127.0.0.1:7878"}Both parse. The fix at Broader
Verified per-phase against a real run dir:
Algorithm/optimizer Generated listingYes — verified by actually merging, not by reading. #191 and #193 each conflict on exactly $ cap-evolve --help
usage: cap-evolve {version|splits|check|doctor|run|estimate|dashboard|tail} [args]
commands:
version Print the installed cap-evolve version as JSON.
splits Compute the seeded train/val/test split for a set of task ids.
check Verify a project's adapter is fully implemented and deterministic.
doctor see `cap-evolve doctor --help`
run Sequence the whole optimization run: baseline → algorithm → finalize → report.
estimate Pre-run cost estimate without spending anything.
dashboard Launch (or focus) the live dashboard server over a base dir of runs.
tail Attach to an existing/ongoing run dir and print its event stream.
run `cap-evolve <command> --help` for a command's flags and examples. [exit 0]
$ cap-evolve doctr
cap-evolve: unknown command 'doctr'
did you mean: doctor? [exit 2]
$ PYTHONPATH=core python -m pytest core/tests -q
270 passed in 78.85sZero manual edits to the listing — the design claim holds, and Help exit codes and degradation. The #215 UTF-8 collisionReproduced, and the ruled resolution verified sufficient — see B2 for the failing Reconfiguring stdout to UTF-8 is right, and it is load-bearing. Unlike stderr, stdout is $ # _harden_utf8 changed to (sys.stderr,) only:
$ PYTHONIOENCODING=ascii LC_ALL=C python -m cap_evolve.cli --help
UnicodeEncodeError: 'ascii' codec can't encode character '→' in position 358
File "core/cap_evolve/cli.py", line 741, in mainSo keeping stdout is not merely harmless, it's what makes wire bytes: {"note": "baseline → best"}
parsesVerdict: keep stdout, drop stderr. One line. Merge-order noteTrial-merged this branch against all eight
I agree with #137 last, and would extend the tail: Rationale: every conflict is the same two hunks that #137 deletes, so putting #137 after the The Verification I re-ran$ PYTHONPATH=/tmp/rv-214/core python -m pytest core/tests -q
187 passed in 72.41s # main = 179, so 8 new — matches the claim exactly
# (PR body says 7 + "186"; the file collects 8, and 179+8=187)
$ python -m compileall -q core skills ; echo $?
0
$ PYTHONPATH=core python -m pytest core/tests/test_documented_cli.py -q
8 passed in 4.23s
Documented-CLI checker — load-bearing, confirmed: 68 invocations scanned Negative budgets — all six exit 2 with nothing on stdout, and max-iterations exit=2 stdout=0 :: cap-evolve run: error: --max-iterations must be >= 0 (0 = unlimited), got -5
max-metric-calls / max-usd / max-optimizer-usd / stall / optimizer-max-turns — same shape
$ cap-evolve run ... --max-usd 0 --max-iterations 0 --stall 0 --plan-only
0-unlimited accepted; budget= {'max_iterations': 0, 'stall': 0, 'max_metric_calls': 0,
'max_usd': 0.0, 'max_optimizer_usd': 0.0, 'optimizer_max_turns': 0}
did-you-mean — 13 typos, all exit 2 with 0 bytes on stdout:
Test qualityThe stdout contract is asserted by parsing, not substring-matching Untested paths: the printed phase-redirect command is never executed (B1's root cause — |
… to stdout Review fixes for #137 (PR #214). B1 — the `cap-evolve <phase>` redirect printed a fixed `--run-dir <dir>` template that was wrong for 5 of the 8 phases: finalize also needs --project, and intake/implement-and-check/gate reject --run-dir entirely. A confidently wrong remediation at the SEAL step is worse than a bare 'unknown command'. _PHASE_SCRIPTS is now a name -> required-flags map derived from each script's real argparse, and the message also points at `--help` and the phase's SKILL.md. New test_phase_redirect_commands_are_runnable executes every rendered command and fails on any structural argparse rejection. B2 — _harden_utf8 reconfigured stderr, which defeated #215's eventstream._encodable() pre-check (it reads stderr.encoding to decide whether to transliterate +/-), silently shipping mojibake to an ASCII terminal on the merged tree. stderr is already opened errors=backslashreplace by CPython and belongs to #144's TUI ladder; stdout is opened strict and IS load-bearing (ASCII --help carries a right-arrow). Narrowed to (sys.stdout,). N1 — a whitespace-only docstring is truthy but strips to '', so .splitlines()[0] raised IndexError and took down every invocation including --help on 3.10-3.12 (requires-python >= 3.10). Uses next(iter(...), fallback). N2 — --dashboard-port had no validation and reached bind() as an uncaught OverflowError; because the launch precedes the --plan-only return, even a spend-nothing preview crashed. Now a 1-65535 check in the same pre-spend block. N3 — the documented-CLI absence-exclusion was line-wide, silently skipping 19 of the scanned sites (including four real `cap-evolve finalize` instructions) and disabling itself for any line containing a stray 'not'. Anchored to the text immediately before the backtick: 3 skips now, all genuine absence statements, and the bypass shape is caught. N4 — the e2e stdout-contract test spawned a real uvicorn and opened a browser, importing #200's port-contention flake. Passes --dashboard off. N5 — CONTRIBUTING.md now states the contract explicitly (stdout is exactly one JSON object on success AND failure) and carves out the two audited exceptions: `report --terminal` and finalize's TestSealError on an already-sealed run. Also re-homes #116/#118's `cap-evolve tail` exit-code prose into the module docstring as a merge note, so it is not lost when the deleted subcommand list is resolved.
🔧 Review fixesBoth blocking items fixed, all 5 non-blocking addressed, 3 nits answered. Pushed as Merge order (restated, per the review)
B2 is fixed in this PR, so the order of the last two no longer carries the mojibake risk B1 — the 8 phase redirect commands are now runnable
$ cap-evolve finalize
cap-evolve: unknown command 'finalize'
'finalize' is a phase SKILL, not a cap-evolve subcommand — run it as
python $CAPEVOLVE_SKILLS_DIR/phases/finalize/scripts/run.py --run-dir <dir> --project <project>
(python $CAPEVOLVE_SKILLS_DIR/phases/finalize/scripts/run.py --help for all flags; phases/finalize/SKILL.md for context)
[exit 2]All 8 executed against a real completed run dir (
¹ ### intake
$ python skills/phases/intake/scripts/run.py
"run: cap-evolve check .capevolve/project (implement-and-check gates this)" } [exit 0]
### implement-and-check
$ python skills/phases/implement-and-check/scripts/run.py
"pipeline_selftest": {"ok": false, "problems": [
"optimizer_instructions_file points at a missing file: optimizer/INSTRUCTIONS.md"]} [exit 1]
### baseline
$ python skills/phases/baseline/scripts/run.py --project .../project --capability .../seed_capability
"seconds": 0.00039768218994140625 } } [exit 0]
### diagnose
$ python skills/phases/diagnose/scripts/run.py --run-dir .../run_fx
"kept_good": [] } [exit 0]
### evaluate
$ python skills/phases/evaluate/scripts/run.py --run-dir .../run_fx --project .../project --candidate seed
"seconds": 0.00048089027404785156 } [exit 0]
### gate
$ python skills/phases/gate/scripts/run.py --current 0.5 --candidate 0.9
"reason": "Δ=+0.4000 > 0 (SE=0 → STRICT fallback, warned)", "delta": 0.4 [exit 0]
### finalize (on the sealed run_fx — the seal correctly refuses)
$ python skills/phases/finalize/scripts/run.py --run-dir .../run_fx --project .../project
cap_evolve.splits.TestSealError: TEST split already scored once this run. [exit 1]
### finalize (same command, an UNSEALED run dir)
$ python skills/phases/finalize/scripts/run.py --run-dir .../run_20260730_135024 --project .../project
"baseline_id": "seed", "test_delta": 0.0 } [exit 0]
### report
$ python skills/phases/report/scripts/run.py --run-dir .../run_fx
"iterations": 3, "dashboard": ".../run_fx/dashboard.html" } [exit 0]New B2 —
|
| file | change |
|---|---|
core/cap_evolve/cli.py |
B1 _PHASE_SCRIPTS map + message; B2 (sys.stdout,) + docstring; N1 next(iter(...)); N2 port guard; module docstring: exit codes + tail merge note |
core/tests/test_documented_cli.py |
N3 anchored _DENIES + per-occurrence check; N4 --dashboard off; 3 new tests |
CONTRIBUTING.md |
N5 stdout contract stated; the two audited exceptions carved out |
Closes #107
The framework enforced an authoring budget on user skills and violated it in its own bundled skills, and nothing ran that lint in CI. This wires the existing validator over
skills/in CI and brings the violators into compliance.What the lint is
skills/_registry/lint_skills.pypoints the same shipped validator (skills/capabilities/skill-package/scripts/abstract.py) atskills/*/*/SKILL.mdand exits nonzero on a violation. New CI joblint-skillsruns it plusbuild_manifest.py.Two deliberate deltas from the in-run
validate():MAX_BODY_LINES=500/MAX_BODY_TOKENS=5000,references/nested more than one level, a long reference with no early TOC, a broken reference link. An optimizer mid-run should not be hard-blocked by a style budget; the repo's own skills have no such excuse.TODO/TBD/FIXME/<placeholder>reference.Discovery is dynamic — a
glob("*/*/SKILL.md"), never a committed list, so a new skill is linted the day it lands.MIN_SKILLS = 20is the anti-vacuity floor: a renamed or deleted skill dir makes the glob return fewer packages and the lint fails loudly instead of silently shrinking its own coverage (the failure mode #189's manifest-reading counts guard and #192's 5-host denylist both had).What the lint does NOT catch
Honesty about its reach:
advise, never fails): first-person POV, all-capsCRITICAL/ALWAYS/MUST/NEVER, "should say WHEN to use", and the 1,536-char listing-truncation risk. These are judgement calls, not measurable violations. 10 skills currently carry the "say WHEN" advisory — deliberately not "fixed", because rewording 10 descriptions to satisfy a regex is exactly the cosmetic churn this repo's own skills warn against.check.pybehavior.test_manifest.pycovers existence; each skill's owncheck.pycovers behavior. This lint is authoring-shape only.body.count("\n")+1,len(body)//4), not a real tokenizer.~4 chars/tokenis the documented approximation inabstract.py.references/is linted for shape, not size — a 400-line reference with a TOC passes by design (progressive disclosure means the body is the recurring cost).Before → after
Lint over ALL skills — BEFORE
Lint over ALL skills — AFTER (clean)
Line counts, every SKILL.md (file lines / body lines)
mcp-tool(227),system-prompt(190),diagnose(171) andintake(247) are all comfortably under the 500-line / 5000-token bar as measured by the validator, so the issue's "as needed" list needs no split.toolswas the only body over budget;using-cap-evolvewas the only hard problem.The split
tools/SKILL.md681 → 208 lines. Detail moved one level deep into two new references, each with a TOC, and SKILL.md links every section it moved:references/edit-playbook.md(474 lines) — the eight levers in depth, the in-body-guard before/after diffs, "generalize never hardcode", the deterministic-CODE argument, the three code-bearing patterns, the full symptom→fix table and full trigger list (both preserved verbatim from the old body), the safe tool-replacement protocol, worked composite bodies, the concrete before/afters, and good-vs-bad edits.references/documentation.md(127 lines) — how a tool-using model actually reads a tool definition (select vs. fill), the reader-capability-tier adjustment, response/error design, and the per-tool documentation checklist.SKILL.md keeps the decision rule: the core rule, a 12-row failure-signature → edit table, the action/policy table, the lean condensed doc+return rules, and the failure-modes list — each pointing at the reference that holds the depth.
Also fixed a real bug in the shipped validator. The broken-reference-link check did not strip a Markdown
#anchorbefore the existence test, so any deep link into a reference read as broken. My first split hit it (references/edit-playbook.md#the-safe-tool-replacement-protocolreported as "does not exist"), so it is fixed at root inabstract.pyrather than worked around.using-cap-evolve's description carried literal"<X>"/"<phase>", which the validator correctly reads as XML tags — a hard problem, not a warning, and a real discoverability hazard since the description is injected into a listing. Reworded to plain text; no content change.Coordination / expected merge order
This branch is based on
origin/mainand merges inorigin/refactor/issue-106-deleak-intake(PR #185), whosetools/SKILL.mdReferences hunk andreferences/optimizer-playbook.mdI build on directly — my References section links it as a sixth reference.Expected merge order: #185 → #181 → #198 → this PR.
tools/references/optimizer-playbook.mdand lefttools/SKILL.mdat 686 lines deliberately, correctly identifying the split as this issue's job. Already merged into this branch; the References hunk applied cleanly.intake/SKILL.md, which I do not touch. No conflict —git diff origin/main pr181/pr198 -- skills/phases/intake/SKILL.mdoverlaps nothing in this diff.skills/_registry/manifest.json(regenerated); trivially resolved by re-runningbuild_manifest.py.The lint may flag a body that #181/#198 grow — but
intakeis at 247 body lines against a 500 bar, so there is ~250 lines of headroom.Verification
Every claim below is pasted output; full transcript in the 🔬 Evidence comment.
Lint fails CI on a deliberate violation, passes after revert
The guard is NOT vacuous — removing OR renaming a skill fires the count assertion
Manifest builds; every skill's
check.pyprints"ok": true— 20/20, full output in Evidence.Moved content still reaches the optimizer — real zero-API
cap-evolve runonexamples/toy_calcwith themockoptimizer andcapabilities: [system-prompt, tools]:harness.py'scopytree(..., ignore=ignore_patterns("__pycache__","scripts","*.pyc"))has noreferencespattern, so the property #185's reviewer verified holds for the newly-split files too — byte-identical,diffrc=0.No substantive claim lost in the split. A 12-gram content-word coverage check of the old 673-line body against new SKILL.md + all references leaves 15 spans ≥30 words; every one is a heading-join artifact or prose I condensed while
concepts.md§5 /pitfalls.mdalready carry the depth. Spot-checked each compressed claim survives by name (safety boundary,blast radius,visible refusals rather than silent no-ops,Composite-tool sprawl,rarely-but-critically needed,fight reasoning models,Over-describing into contradiction,Schema changes that break callers,in sync) — all present. The first pass did drop the mis-selection / bad-argument-filling / fumbled-chain trigger bullets; the check caught it and they are back as table rows plus the verbatim full trigger list in the playbook.Tests
179 baseline + 6 new (
core/tests/test_skill_authoring_lint.py), 0 failed. The environmentally-flakytest_dashboard_launch.py::test_maybe_launch_spawns_when_available(#200) passed here.Files touched
skills/_registry/lint_skills.pycore/tests/test_skill_authoring_lint.pyskills/capabilities/tools/references/edit-playbook.mdskills/capabilities/tools/references/documentation.md.github/workflows/ci.ymllint-skillsjob (lint + manifest rebuild)skills/capabilities/tools/SKILL.mdskills/capabilities/skill-package/scripts/abstract.py#anchorbefore the broken-link existence checkskills/orchestrate/using-cap-evolve/SKILL.md<X>/<phase>XML tagsPlus the merged-in #185 commits (
tools/SKILL.mdReferences hunk,references/optimizer-playbook.md,intake/SKILL.md) and the regeneratedskills/_registry/manifest.json......................................................................... [ 38%]
........................................................................ [ 77%]
.......................................... [100%]
186 passed in 65.54s (0:01:05)
core/tests/test_documented_cli.py::test_every_subcommand_renders_help PASSED [ 14%]
core/tests/test_documented_cli.py::test_top_level_help_lists_exactly_the_real_commands PASSED [ 28%]
core/tests/test_documented_cli.py::test_documented_cap_evolve_subcommands_resolve PASSED [ 42%]
core/tests/test_documented_cli.py::test_unknown_subcommand_suggests_and_exits_nonzero PASSED [ 57%]
core/tests/test_documented_cli.py::test_run_rejects_negative_budget PASSED [ 71%]
core/tests/test_documented_cli.py::test_cli_survives_an_ascii_stdout PASSED [ 85%]
core/tests/test_documented_cli.py::test_run_stdout_is_a_single_json_object PASSED [100%]
============================== 7 passed in 4.46s ===============================
core skills: clean (exit 0)