Skip to content

feat: add the extra_commands registration seam for variant CLI verbs - #12

Merged
SunsetDrifter merged 1 commit into
mainfrom
feat/extra-commands-seam
Aug 24, 2026
Merged

feat: add the extra_commands registration seam for variant CLI verbs#12
SunsetDrifter merged 1 commit into
mainfrom
feat/extra-commands-seam

Conversation

@SunsetDrifter

@SunsetDrifter SunsetDrifter commented Aug 24, 2026

Copy link
Copy Markdown
Owner

What

A wiki can register its own lint.py <verb> subcommands with the engine, dispatched by cli.main() and listed in a single unified usage string. Adds help / -h / --help.

Why

A wiki that needs its own verb (a schema dump, a custom report) had no way to register it, so it had to intercept sys.argv in its own lint.py before calling wikilint.main(). That produces two partial usage strings — the engine's, missing the wiki's verb; the wiki's, missing the engine verbs — and neither is authoritative. Any workflow or doc that treats one verb as the single source of truth is then documented by a usage string that doesn't list it.

How

  • settings.BUILTIN_COMMANDS — the engine's own (verb, help) table, declared once. It lives in settings.py rather than cli.py because that module is the validation boundary and needs the reserved names to reject a shadowing registration.
  • settings.DEFAULTS["extra_commands"]{verb: (callable(root) -> exit code, help text)}, default {}, validated by _validate_commands() alongside the other extension points: dict shape, non-empty string verb, no shadowing a built-in, callable handler, non-empty help. Consistent with how extra_checks / extra_secret_patterns / skills_dir already fail fast at startup instead of mid-run.
  • cli.USAGE (a fixed literal) → cli.usage() — renders BUILTIN_COMMANDS plus the registered extra_commands, column-aligned. One string, every verb, one declaration of the verb list, so usage can't drift from what main() actually dispatches.
  • cli.main() dispatches a registered verb and the new help verb ahead of the CLAUDE.md wiki-root guard, so a pure-reporting verb works from any cwd. A registered command that does need the wiki root checks for it itself. Engine verbs keep the guard unchanged; an unknown verb still prints usage and exits 2.

Why it is behavior-neutral

  • The shipped wiki/ template registers nothing, so it gets extra_commands: {} and its dispatch is unchanged. Pinned by a test that asserts the template does not set the key.
  • usage() renders the four pre-existing verbs byte-identically to the old literal (same column width, same help text); the only difference in output is the added help row.
  • The guard reordering is scoped: only registered verbs and help bypass it. check, rebuild-index, reverse-deps, coverage, and unknown verbs all still hit the wiki-root guard first and still exit 2 outside a wiki — covered by a test.

Test plan

  • python -m unittest discover -s tests107 passed (was 91; +16)
  • New tests/test_extra_commands.py covers:
    • a registered command is dispatched, receives the resolved root, and its exit code is returned
    • a registered command works with no CLAUDE.md present (the root-guard bypass)
    • help, -h, --help each exit 0 without a wiki root
    • the root guard still applies to check / rebuild-index / an unknown verb (exit 2)
    • an unknown verb inside a wiki still exits 2
    • usage() lists engine + registered verbs, and covers every entry of BUILTIN_COMMANDS (verb and help text), so the table can't drift
    • registration validation rejects: a verb shadowing any built-in, a non-callable handler, a malformed (non-pair) entry, empty/blank/None help, an empty/blank verb, a non-dict table
    • a bad registration fails at configure() (exit 2) before any dispatch happens
    • the shipped template registers no command

Note on the sibling PR

Opened alongside #11. Both are cut from main and are independently mergeable, but they touch adjacent regions of settings.py and the same README bullet, so whichever merges second needs a trivial rebase (no logical conflict).

🤖 Generated with Claude Code

A wiki that needs its own `lint.py <verb>` had no way to register it, so it
had to intercept sys.argv in its lint.py before calling wikilint.main().
That produces two partial usage strings -- the engine's, missing the wiki's
verb, and the wiki's, missing the engine verbs -- and neither is
authoritative.

The engine now owns the whole verb list:

* settings.BUILTIN_COMMANDS: the engine's own (verb, help) table, declared
  once. It lives in settings.py because that module is the validation
  boundary and needs the reserved names.
* settings.DEFAULTS["extra_commands"]: {verb: (callable(root) -> exit code,
  help)}, default {}, validated by _validate_commands() alongside the other
  extension points -- dict shape, non-empty verb, no shadowing a built-in,
  callable handler, non-empty help.
* cli.USAGE (a fixed literal) became cli.usage(), rendering
  BUILTIN_COMMANDS plus the registered extra_commands. One string, every
  verb.
* cli.main() dispatches a registered verb, and the new help/-h/--help, ahead
  of the CLAUDE.md wiki-root guard, so a pure-reporting verb works from any
  cwd. A registered command that does need the root checks for it itself.
  Engine verbs keep the guard; an unknown verb still prints usage, exits 2.

Behavior-neutral: the shipped template registers nothing, so it gets
`extra_commands: {}`, and usage() renders the four pre-existing verbs
byte-identically to the old literal -- only the new `help` row is added.
@SunsetDrifter
SunsetDrifter force-pushed the feat/extra-commands-seam branch from 7bc3b70 to f957c7f Compare August 24, 2026 12:47
@SunsetDrifter
SunsetDrifter merged commit a6466aa into main Aug 24, 2026
1 check passed
@SunsetDrifter
SunsetDrifter deleted the feat/extra-commands-seam branch August 24, 2026 12:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant