The single tracking table for the argh→cw programme. Every column was re-verified against real source and the fleet dataset; corrections to earlier drafts are marked [C].
Legend — argh dep: declared in packaging metadata. i2: declares i2. tests: argv-level tests that exercise the dispatcher (⚠ = a test_cli.py exists but calls the functions as plain Python and would pass with the parser deleted — a false green, worse than no tests).
Rollup
| verdict |
repos |
commands |
| cw |
34 |
~380 |
| argparse-inline |
3 |
12 |
| drop-cli (delete the block, keep the repo, drop the dep) |
11 |
21 |
| dead-or-example |
11 |
22 |
already-migrated (an → typer) |
1 |
17 |
| Total distinct |
60 |
|
Test coverage is the cost, not the code. Of the 34 cw-bound repos, 8 have argv-level dispatcher tests (xa, opsward, coact, skill, lacing, illustration, grub, ocracy). 26 do not. Two more (enlace_auth, reelee) have the false green described above.
Two verified ordering traps — do not split either pair
1. cw + theremin. cw/pyproject.toml:17 declares argh and imports it nowhere. theremin/pyproject.toml:35-43 declares dol, i2, cw, meshed, opencv-python, hum, mediapipe — no argh — while theremin/main.py:25 and theremin/script_utils.py:12 both import it at module scope. Deleting cw's line breaks theremin's console script.
Resolution adopted: theremin declares argh explicitly first (one line, and a genuine packaging-bug fix), which decouples them — cw's deletion then lands immediately, unblocked by cw's own build. theremin's argh line dies at its own migration.
2. wads + isee. wads/pyproject.toml:21 declares argh in core deps. isee/pyproject.toml:13 declares ["semver==2.13.0", "wads", "tomli…"] — no argh — and isee/__init__.py:37 imports argh inside main(), which is the console script isee = "isee:main". isee free-rides on wads. Splitting them kills isee's console script in every CI job that calls isee, and 8 GitHub Action YAMLs invoke isee <cmd> by name.
Wave 0 — cw itself
| repo |
pkg |
cmds |
entry point |
argh dep |
i2 |
tests |
effort |
| i2mint/cw |
cw |
0 |
none |
✅ unused |
✅ |
✗ |
trivial |
| thorwhalen/theremin |
theremin |
1 |
theremin.main:dispatched_theremin_cli |
✗ via cw |
✅ |
✗ |
medium |
theremin is the fleet's heaviest per-parameter-override user: 10 @argh.arg decorators (script_utils.py:378-425), nargs='?' const='list' on five parameters, explicit short flags -p -s -k -v -r -n -w. It is the stress test for cw's override channel — and the worst possible repo to prove the API on, which is why cw's parity corpus must not consist of it alone.
Wave 0b — deletion track (23 repos, no cw, no design decisions)
See the deletion-track issues. [C] the set is 23 distinct repos, not 22 (the earlier list already de-duplicated the two checkouts of i2mint/taped and still names 23). [C] the edit is ~200 lines, not ~40 (ten files alone account for 143). [C] 11 argh declarations come off, 9 of them from PyPI-published distributions (oui, scoopy, odus, wealth, brand, loopyng, pipoke, mood, cw); projreg and glossa-data-prep are unpublished.
Wave 1 — pilots with pre-existing subprocess test suites
| repo |
pkg |
cmds |
entry point |
argh dep |
i2 |
tests |
effort |
| thorwhalen/opsward |
opsward |
6 |
opsward.__main__:main |
✅ |
✗ |
✅ |
trivial |
| thorwhalen/grub |
grub |
1 |
grub.cli:main |
✅ |
✗ |
✅ |
trivial |
| thorwhalen/ocracy |
ocracy |
10 |
ocracy.__main__:main |
extra [cli] |
✗ |
✅ |
trivial |
| thorwhalen/coact |
coact |
11 |
coact.__main__:main |
✅ |
✗ |
✅ |
small |
| thorwhalen/xa |
xa |
13 |
xa.cli:main |
✅ |
✗ |
✅ |
medium |
These five are the only repos whose CLI contract is already pinned by tests somebody else wrote. Four different shapes: single-command (grub), optional-[cli]-extra over a dep-free core (ocracy), three distinct nargs shapes + a test that monkeypatches argh's module object (coact tests/test_cli.py:162), and namespaces with a leaf-name collision (xa: list at top level and inside the archive group).
[C] xa has 13 func.__name__ mutations, not 9 — xa/cli.py:783-792 (ten, including the non-identifier "gen-secret" at :792) plus :794-796 (three archive handlers). All 13 are deletable via a {name: func} mapping form.
Wave 2 — load-bearing tooling
| repo |
pkg |
cmds |
entry point |
argh dep |
i2 |
tests |
effort |
| thorwhalen/skill |
skill |
10 |
skill.__main__:main |
✅ |
✗ |
✅ |
small |
| i2mint/epythet |
epythet |
6 |
epythet.cli:epythet_cli |
✅ |
✗ |
✗ |
small |
| i2mint/wads |
wads |
17 |
pack, populate |
✅ |
✗ |
⚠ |
medium |
| i2mint/isee |
isee |
9 |
isee:main |
✗ undeclared |
✗ |
⚠ |
small |
epythet has the highest blast radius in the fleet: actions/publish-github-pages/action.yml:54 runs epythet quickstart . --ignore ${{inputs.ignore}} in every fleet docs job, with no default — so --ignore arrives with zero values and only parses because of nargs='*'. Zero test files in the repo. The action installs from PyPI, so a migration reaches CI only after a release.
wads step 0 is a deletion: wads/__init__.py:98 main() is dead and broken — it calls parser.add_commands(**pack_kw) where pack_kw carries namespace= (renamed to group_name in argh 0.30, so it raises TypeError), it is not among the package's 8 console scripts, and there is no wads/__main__.py. Delete it; do not port it. wads also holds the fleet's only non-dispatch argh API: argh.interaction.confirm at wads/pack.py:219.
Wave 3 — the house twins and the convention docs
| repo |
pkg |
cmds |
entry point |
argh dep |
i2 |
tests |
effort |
| i2mint/reci |
reci |
4 |
reci.__main__:main |
✅ |
✗ |
✗ |
trivial |
| thorwhalen/illustration |
illustration |
5 |
illustration.__main__:main |
✅ |
✗ |
✅ |
trivial |
| thorwhalen/article |
article |
2 |
article.__main__:main |
✅ |
✗ |
✗ |
trivial |
| thorwhalen/ke |
ke |
6 |
ke.__main__:main |
✅ |
✗ |
✗ |
trivial |
| thorwhalen/ek |
ek |
9 |
ek.__main__:main |
✅ |
✗ |
✗ |
small |
| thorwhalen/toolery |
toolery |
8 |
toolery.__main__:main |
✅ |
✗ |
✗ |
small |
| thorwhalen/hedger |
hedger |
11 |
hedger.__main__:main |
✅ |
✗ |
✗ |
small |
| thorwhalen/scribed |
scribed |
10 |
scribed.__main__:main |
extra [cli] |
✗ |
✗ |
small |
| thorwhalen/hearing |
hearing |
6 |
hearing.cli:main |
✅ |
✗ |
✗ |
small |
| thorwhalen/lacing |
lacing |
5 |
lacing.cli:main |
✅ |
✗ |
✅ |
small |
ek must land its argh removal in the same PR as the _GPL_ALLOW inversion (.github/scripts/check_licenses.py:34 — [C] not :29) or master goes red. hearing migrates with scribed (it depends on it). lacing is the ingress-codec exemplar: lacing/cli.py:249-253 hand-writes int() with the comment "argh delivers option values as strings; coerce here" — and the same bug is still live and unpatched at cli.py:118-120 for --start/--end. reci's own misc/docs/*.md prescribe argh by name; rewrite them or the pattern re-seeds.
Wave 4 — the long tail
| repo |
pkg |
cmds |
entry point |
argh dep |
i2 |
tests |
effort |
| thorwhalen/lookbook |
lookbook |
5 |
lookbook.__main__:main |
✅ |
via meshed |
✗ |
small |
| thorwhalen/ov |
ov |
10 |
ov.__main__:main |
✅ |
✗ |
✗ |
small |
| thorwhalen/citeget |
citeget |
6 |
citeget.cli:main |
✅ |
✗ |
✗ |
small |
| thorwhalen/reelee |
reelee |
26 |
reelee.__main__:main |
✅ |
✗ |
⚠ |
small |
| thorwhalen/tw_platform |
tw_platform |
8 |
none (python deploy.py) |
✗ |
✗ |
✗ |
small |
| thorwhalen/scraped |
scraped |
3 |
scraped.tools:main |
✅ |
✗ |
✗ |
small |
| i2mint/ir |
ir |
14 |
ir.__main__:main |
✅ |
✗ |
✗ |
small |
| i2mint/enlace |
enlace |
8 |
enlace.__main__:main |
✅ |
✗ |
✗ |
small |
| i2mint/enlace_auth |
enlace_auth |
11 |
enlace_auth.__main__:main |
✅ |
✗ |
⚠ |
small |
| i2mint/http2py |
http2py |
— |
api-pkg-maker (dead) |
✅ |
✅ |
✗ |
small |
| thorwhalen/ge |
ge |
27 |
ge.__main__:main |
✅ |
✗ |
✗ |
medium |
| thorwhalen/muvid |
muvid |
17 |
muvid.__main__:main |
✅ |
✗ |
✗ |
medium |
| thorwhalen/imbed_data_prep |
imbed_data_prep |
10 |
none (python -m) |
✗ |
✅ |
✗ |
trivial |
| i2mint/gurgle |
gurgle |
2 |
gurgle-terminal (broken) |
✗ |
✗ |
✗ |
trivial |
ov is the fleet's only generator-returning command (runs() yields) — an egress that only does print(result) prints <generator object …>. tw_platform is the hardest constraint on cw's dependency budget (4222-line deliberately stdlib-only deploy.py; both workflows pip install argh with a comment saying why; live-server deploy, surface must not change). enlace carries the proven --app-name naming trap. http2py is a design source, not really a target: cli_maker.py is a working prototype of signature-ingress + argparse; read it before writing cw. gurgle's gurgle-terminal ImportErrors on a clean install today (undeclared argh at gurgle/terminal.py:221) — decide retire-or-migrate.
Wave 5 — priv, with the most care
| repo |
pkg |
cmds |
entry point |
argh dep |
i2 |
tests |
effort |
| thorwhalen/priv |
priv |
88 |
priv.__main__:main |
✗ undeclared |
✗ |
✗ |
large |
Highest value, highest risk, last. 88 commands across 4 namespaces (47 in __all__ + 22 git_ops + 8 pkg + 11 group); zero CLI tests; invoked constantly by the user and by agents via documented commands; priv --help is 489 lines. A functools.partial in __all__ produces the wrong command name, the partial's class docstring as help, and re-exposes the pre-bound keyword. The group name priv git_ops appears verbatim in agent-facing docs and is emitted as a suggested command by the code itself — hyphenating it is a user-visible break, not a cosmetic one.
Optional Wave 5b: re-migrate thorwhalen/an from typer to cw. Drops 7 distributions, restores argcomplete (which an had to abandon when typer replaced argparse), deletes ~120 lines of compensation. Its 443-line suite is written against typer's CliRunner and would need rewriting. Open question, not a recommendation.
Other verdicts
argparse-inline (3): thorwhalen/myproj (the argparse dispatcher is already written and exercised at projreg/__main__.py:244-273), thorwhalen/glossa data-prep, thorwhalen/mood. All Wave 0b, all net code removal, no new dependency.
drop-cli (11): oui, scoopy, odus, wealth, brand, loopyng, pipoke, tec, umpyre, mixing, hubcap. All Wave 0b.
dead-or-example (11): examples, py2dash, py2misc, viral, tapyoca, ut, taped, know, slang, raglab_bak, mv. Wave 0b, except know (a single TODO comment — leave it; it states cw's ingress spec years early) and raglab_bak (owner outside the landing perimeter).
already-migrated (1): thorwhalen/an → typer. Its 400-line tests/test_licence_perimeter.py is the source material for wads-licence-check.
Cross-track dependencies
ek's argh removal must ship with its _GPL_ALLOW inversion (licence track).
wads + isee land in one window.
cw + theremin — decoupled by having theremin declare argh first.
- The doctrine rewrites split in two: remove the advocacy now (unblocked); install
cw as the replacement after v1 ships. Pointing agents at a package that does not exist is worse than pointing them at argh.
- Every wave's exit gate is a golden captured before the migration and replayed after — recorded exit code, stdout, stderr, and normalised
usage: line, byte-identical.
Research & rationale: the canonical spec and the full fleet audit live in the private research ledger (https://github.com/thorwhalen/priv/discussions/65). Effort: large.
The single tracking table for the argh→cw programme. Every column was re-verified against real source and the fleet dataset; corrections to earlier drafts are marked [C].
Legend —
argh dep: declared in packaging metadata.i2: declares i2.tests: argv-level tests that exercise the dispatcher (⚠ = atest_cli.pyexists but calls the functions as plain Python and would pass with the parser deleted — a false green, worse than no tests).Rollup
an→ typer)Test coverage is the cost, not the code. Of the 34 cw-bound repos, 8 have argv-level dispatcher tests (
xa,opsward,coact,skill,lacing,illustration,grub,ocracy). 26 do not. Two more (enlace_auth,reelee) have the false green described above.Two verified ordering traps — do not split either pair
1.
cw+theremin.cw/pyproject.toml:17declares argh and imports it nowhere.theremin/pyproject.toml:35-43declaresdol, i2, cw, meshed, opencv-python, hum, mediapipe— no argh — whiletheremin/main.py:25andtheremin/script_utils.py:12both import it at module scope. Deleting cw's line breaks theremin's console script.Resolution adopted: theremin declares argh explicitly first (one line, and a genuine packaging-bug fix), which decouples them — cw's deletion then lands immediately, unblocked by cw's own build. theremin's argh line dies at its own migration.
2.
wads+isee.wads/pyproject.toml:21declares argh in core deps.isee/pyproject.toml:13declares["semver==2.13.0", "wads", "tomli…"]— no argh — andisee/__init__.py:37imports argh insidemain(), which is the console scriptisee = "isee:main". isee free-rides on wads. Splitting them kills isee's console script in every CI job that callsisee, and 8 GitHub Action YAMLs invokeisee <cmd>by name.Wave 0 — cw itself
theremin.main:dispatched_theremin_clitheremin is the fleet's heaviest per-parameter-override user: 10
@argh.argdecorators (script_utils.py:378-425),nargs='?' const='list'on five parameters, explicit short flags-p -s -k -v -r -n -w. It is the stress test for cw's override channel — and the worst possible repo to prove the API on, which is why cw's parity corpus must not consist of it alone.Wave 0b — deletion track (23 repos, no cw, no design decisions)
See the deletion-track issues. [C] the set is 23 distinct repos, not 22 (the earlier list already de-duplicated the two checkouts of
i2mint/tapedand still names 23). [C] the edit is ~200 lines, not ~40 (ten files alone account for 143). [C] 11 argh declarations come off, 9 of them from PyPI-published distributions (oui, scoopy, odus, wealth, brand, loopyng, pipoke, mood, cw);projregandglossa-data-prepare unpublished.Wave 1 — pilots with pre-existing subprocess test suites
opsward.__main__:maingrub.cli:mainocracy.__main__:main[cli]coact.__main__:mainxa.cli:mainThese five are the only repos whose CLI contract is already pinned by tests somebody else wrote. Four different shapes: single-command (
grub), optional-[cli]-extra over a dep-free core (ocracy), three distinct nargs shapes + a test that monkeypatches argh's module object (coacttests/test_cli.py:162), and namespaces with a leaf-name collision (xa:listat top level and inside thearchivegroup).[C]
xahas 13func.__name__mutations, not 9 —xa/cli.py:783-792(ten, including the non-identifier"gen-secret"at:792) plus:794-796(three archive handlers). All 13 are deletable via a{name: func}mapping form.Wave 2 — load-bearing tooling
skill.__main__:mainepythet.cli:epythet_clipack,populateisee:mainepythet has the highest blast radius in the fleet:
actions/publish-github-pages/action.yml:54runsepythet quickstart . --ignore ${{inputs.ignore}}in every fleet docs job, with no default — so--ignorearrives with zero values and only parses because ofnargs='*'. Zero test files in the repo. The action installs from PyPI, so a migration reaches CI only after a release.wads step 0 is a deletion:
wads/__init__.py:98 main()is dead and broken — it callsparser.add_commands(**pack_kw)wherepack_kwcarriesnamespace=(renamed togroup_namein argh 0.30, so it raisesTypeError), it is not among the package's 8 console scripts, and there is nowads/__main__.py. Delete it; do not port it. wads also holds the fleet's only non-dispatch argh API:argh.interaction.confirmatwads/pack.py:219.Wave 3 — the house twins and the convention docs
reci.__main__:mainillustration.__main__:mainarticle.__main__:mainke.__main__:mainek.__main__:maintoolery.__main__:mainhedger.__main__:mainscribed.__main__:main[cli]hearing.cli:mainlacing.cli:mainekmust land its argh removal in the same PR as the_GPL_ALLOWinversion (.github/scripts/check_licenses.py:34— [C] not:29) ormastergoes red.hearingmigrates withscribed(it depends on it).lacingis the ingress-codec exemplar:lacing/cli.py:249-253hand-writesint()with the comment "argh delivers option values as strings; coerce here" — and the same bug is still live and unpatched atcli.py:118-120for--start/--end.reci's ownmisc/docs/*.mdprescribe argh by name; rewrite them or the pattern re-seeds.Wave 4 — the long tail
lookbook.__main__:mainov.__main__:mainciteget.cli:mainreelee.__main__:mainpython deploy.py)scraped.tools:mainir.__main__:mainenlace.__main__:mainenlace_auth.__main__:mainapi-pkg-maker(dead)ge.__main__:mainmuvid.__main__:mainpython -m)gurgle-terminal(broken)ovis the fleet's only generator-returning command (runs()yields) — an egress that only doesprint(result)prints<generator object …>.tw_platformis the hardest constraint on cw's dependency budget (4222-line deliberately stdlib-onlydeploy.py; both workflowspip install arghwith a comment saying why; live-server deploy, surface must not change).enlacecarries the proven--app-namenaming trap.http2pyis a design source, not really a target:cli_maker.pyis a working prototype of signature-ingress + argparse; read it before writing cw.gurgle'sgurgle-terminalImportErrors on a clean install today (undeclared argh atgurgle/terminal.py:221) — decide retire-or-migrate.Wave 5 — priv, with the most care
priv.__main__:mainHighest value, highest risk, last. 88 commands across 4 namespaces (47 in
__all__+ 22git_ops+ 8pkg+ 11group); zero CLI tests; invoked constantly by the user and by agents via documented commands;priv --helpis 489 lines. Afunctools.partialin__all__produces the wrong command name, the partial's class docstring as help, and re-exposes the pre-bound keyword. The group namepriv git_opsappears verbatim in agent-facing docs and is emitted as a suggested command by the code itself — hyphenating it is a user-visible break, not a cosmetic one.Optional Wave 5b: re-migrate
thorwhalen/anfrom typer to cw. Drops 7 distributions, restores argcomplete (whichanhad to abandon when typer replaced argparse), deletes ~120 lines of compensation. Its 443-line suite is written against typer'sCliRunnerand would need rewriting. Open question, not a recommendation.Other verdicts
argparse-inline (3):
thorwhalen/myproj(the argparse dispatcher is already written and exercised atprojreg/__main__.py:244-273),thorwhalen/glossadata-prep,thorwhalen/mood. All Wave 0b, all net code removal, no new dependency.drop-cli (11):
oui, scoopy, odus, wealth, brand, loopyng, pipoke, tec, umpyre, mixing, hubcap. All Wave 0b.dead-or-example (11):
examples, py2dash, py2misc, viral, tapyoca, ut, taped, know, slang, raglab_bak, mv. Wave 0b, exceptknow(a single TODO comment — leave it; it states cw's ingress spec years early) andraglab_bak(owner outside the landing perimeter).already-migrated (1):
thorwhalen/an→ typer. Its 400-linetests/test_licence_perimeter.pyis the source material forwads-licence-check.Cross-track dependencies
ek's argh removal must ship with its_GPL_ALLOWinversion (licence track).wads+iseeland in one window.cw+theremin— decoupled by having theremin declare argh first.cwas the replacement after v1 ships. Pointing agents at a package that does not exist is worse than pointing them at argh.usage:line, byte-identical.Research & rationale: the canonical spec and the full fleet audit live in the private research ledger (https://github.com/thorwhalen/priv/discussions/65). Effort: large.