Skip to content

CLI flag consistency audit: short-flag overloads, parity gaps, naming drift #23

Description

@imarios

Summary

Cross-command audit of skilltree CLI flags and positionals turns up several consistency gaps: flags whose short forms mean different things in different commands, the same concept named differently across commands, and uneven parity of "expected" flags like --json and --global. None of these are bugs in isolation, but together they make the CLI harder to learn and to script against.

Environment

  • Product/Service: skilltree CLI
  • Region/Version: main @ 0172643 (v0.23.0)

Reproduction Steps

  1. for c in init add install update remove verify list scan teach vendor unvendor search info; do skilltree $c --help; done
  2. Same for sub-sub-commands: registry {add,init,list,index,update}, targets {list,add,remove,detect,migrate}, deps tree.
  3. Tabulate flags, short forms, and meaning per command.

Expected Behavior

Within one CLI, a given short flag, long flag, or positional name should mean one thing; an "expected" flag (--json, --global, --dry-run, --frozen) should appear on every command where it could plausibly apply, not just on the ones where it was needed first.

Actual Behavior

1. Same short flag, different semantics

Command -f, --force meaning
install Overwrite locally modified files
remove Skip confirmation
unvendor Discard modified vendored files

Three different things. "Skip confirmation" should be -y, --yes (which is its conventional Unix meaning). "Overwrite local changes" / "discard modified files" can stay -f, --force and would then have a coherent meaning ("clobber on disk").

Command -y, --yes meaning
init With --scan, include all discovered entries without prompting
add Skip the glob-mode confirmation prompt

Both reduce to "skip interactive confirmation" — fine, but remove's -f should join them under -y, --yes.

2. Same concept, different flag names

Concept Command Flag
Alias for a registered registry registry add --name <alias>
Alias for a sources: map entry add --source <alias>
Filter/select a registry by name add, search --registry <name>

Three different "name a remote source" concepts collide. Worth documenting clearly; registry add --name could potentially become registry add --as <alias> to disambiguate from "the thing's name."

3. Positional naming inconsistent for the same concept

Command Positional Refers to
targets add <target> Agent name (claude, …)
teach --agent <name> Agent name
info <name> Skill/agent/command name
add / remove <name> Skill/agent/command name

targets add <target> and teach --agent <name> accept the same enum (getKnownAgentNames() from src/core/agents.ts). One should rename to match the other — <agent> reads best since AGENT_REGISTRY is the source of truth.

4. --json parity gaps

Has it: list, scan, search, info, registry list.

Missing: verify, targets list, deps tree, registry update (would emit per-registry update result), cache clean (would emit reclaimed bytes).

The asymmetry hurts scripting. targets list is the most glaring — it's literally a list command without --json.

5. --global / -g parity gaps

Has it: init, add, install, update, remove, verify, list, targets *, deps tree.

Missing: info. Today skilltree info foo only inspects the project manifest/lockfile; there is no way to inspect a globally-installed dep without cd-ing somewhere that doesn't have a project. info --global <name> should mirror the rest.

scan, search, vendor, unvendor, and registry * legitimately don't need --global.

6. --frozen and -n, --dry-run parity

--frozen -n, --dry-run
install yes yes
vendor yes yes
update yes
unvendor
remove

unvendor and remove both make filesystem changes; both should support -n, --dry-run. update could reasonably support --frozen to mean "fail if anything would actually change vs the lockfile" (effectively a no-op verification).

7. init overloaded

  • skilltree init — initialize a new project manifest.
  • skilltree registry init — seed popular community registries.
  • skilltree targets detect — auto-discover installed agents and add them.

Three subtly different "set up sensible defaults" verbs. At minimum they should be cross-referenced in each other's --help. A more aggressive cleanup would standardize on one verb (init for fresh state, seed or bootstrap for opinionated defaults), but that's breaking.

8. URL positional vs --repo flag

skilltree registry add github.com/foo/bar
skilltree add some-skill --repo github.com/foo/bar --path skills/foo

Both accept a git URL. registry add takes it positionally; add takes it via --repo. Probably right (registry's primary input is the URL), but registry add --repo X --name Y should likely be accepted as an alias so muscle memory transfers.

9. --type semantics differ between add and search

Already tracked in #22search filters by --type; add does not (it only uses it as declarative metadata for --repo/--path adds). Listed here for completeness; fix lives in #22.

10. add -D, --dev has no symmetric remove --dev

add foo -D puts foo in dev-dependencies. remove foo searches both groups silently. That's the right default, but there's no way to disambiguate remove foo --dev if a future state had a same-named entry in both groups. Probably fine to defer; flagging for awareness.

Impact

Medium — none of these block users today, but each one is a paper cut. Combined they raise the cost of learning the CLI and lock in inconsistencies before v1.0 if not addressed soon.

Additional Context

Suggested staging

Group fixes by breaking-vs-non-breaking:

Non-breaking (add aliases, keep current flag working with deprecation warning):

  • --json on verify, targets list, deps tree, registry update, cache clean.
  • --global on info.
  • -n, --dry-run on unvendor, remove.
  • --frozen on update.
  • Accept <agent> everywhere <target> is shown for the agent enum (or vice versa).
  • registry add accepts both positional URL and --repo <url> form.
  • Cross-references between init, registry init, targets detect in --help.

Breaking (queue for next major):

  • Remap -f, --force to mean only "clobber files on disk." Move remove's "skip confirmation" to -y, --yes.
  • Rename registry add --name to registry add --as for clarity.
  • Decide whether <target> or <agent> wins as canonical positional name; rename the loser.

Test coverage to add

  • Snapshot test of skilltree <cmd> --help output for every command, so future flag changes are intentional.
  • Parity test asserting that every command with mutating effects on the filesystem accepts -n, --dry-run.
  • Parity test asserting that every list-shaped command accepts --json.

Documentation impact

README.md is directly inconsistent with several of the issues raised here and will need updates as fixes land:

  • Key Flags table — line 317: the --force row currently reads Overwrite modified files / skip confirmation, which literally documents the overload this issue calls out. After the breaking-change rename:
    • --force row → "Overwrite locally modified files on disk" (single meaning).
    • New -y, --yes row → "Skip confirmation prompts (add, init, remove)".
  • Key Flags table — line 314 (--global): extend the command list to include info once --global is added there.
  • Key Flags table — line 318 (--dry-run): extend to include unvendor and remove once -n is added there. Note no --frozen row exists today; if update gains --frozen, line 316's row should be extended accordingly.
  • Key Flags table — --json: not present at all today. Add a row listing every command that supports it once parity is closed (list, scan, search, info, registry list, verify, targets list, deps tree, registry update, cache clean).
  • Commands table — line 295 (targets add <target>): rename positional in the docs to match whichever wins (<agent> or <target>) and update line 296/297 to match. Same for teach's --agent <name> if it is also renamed.
  • Commands table — registry add: if --repo <url> becomes an accepted alias for the positional URL, document it under the row's description ("…accepts a URL positionally or via --repo <url>").
  • Quick Start (lines 46–67): any user-visible breaking changes (e.g., remove -fremove -y) need updated examples. None in the current Quick Start use -f/-y/--force/--yes today, so this is only relevant if examples are added.
  • Snapshot test follow-on: if a --help snapshot test is added (per the "Test coverage to add" section), gate README's Commands and Key Flags tables to that snapshot to prevent future drift.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions