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
for c in init add install update remove verify list scan teach vendor unvendor search info; do skilltree $c --help; done
- Same for sub-sub-commands:
registry {add,init,list,index,update}, targets {list,add,remove,detect,migrate}, deps tree.
- 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 #22 — search 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 -f → remove -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.
Summary
Cross-command audit of
skilltreeCLI 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--jsonand--global. None of these are bugs in isolation, but together they make the CLI harder to learn and to script against.Environment
Reproduction Steps
for c in init add install update remove verify list scan teach vendor unvendor search info; do skilltree $c --help; doneregistry {add,init,list,index,update},targets {list,add,remove,detect,migrate},deps tree.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
-f, --forcemeaninginstallremoveunvendorThree different things. "Skip confirmation" should be
-y, --yes(which is its conventional Unix meaning). "Overwrite local changes" / "discard modified files" can stay-f, --forceand would then have a coherent meaning ("clobber on disk").-y, --yesmeaninginit--scan, include all discovered entries without promptingaddBoth reduce to "skip interactive confirmation" — fine, but
remove's-fshould join them under-y, --yes.2. Same concept, different flag names
registry add--name <alias>sources:map entryadd--source <alias>add,search--registry <name>Three different "name a remote source" concepts collide. Worth documenting clearly;
registry add --namecould potentially becomeregistry add --as <alias>to disambiguate from "the thing's name."3. Positional naming inconsistent for the same concept
targets add<target>claude, …)teach --agent<name>info<name>add/remove<name>targets add <target>andteach --agent <name>accept the same enum (getKnownAgentNames()fromsrc/core/agents.ts). One should rename to match the other —<agent>reads best sinceAGENT_REGISTRYis the source of truth.4.
--jsonparity gapsHas 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 listis the most glaring — it's literally alistcommand without--json.5.
--global / -gparity gapsHas it:
init,add,install,update,remove,verify,list,targets *,deps tree.Missing:
info. Todayskilltree info fooonly inspects the project manifest/lockfile; there is no way to inspect a globally-installed dep withoutcd-ing somewhere that doesn't have a project.info --global <name>should mirror the rest.scan,search,vendor,unvendor, andregistry *legitimately don't need--global.6.
--frozenand-n, --dry-runparity--frozen-n, --dry-runinstallvendorupdateunvendorremoveunvendorandremoveboth make filesystem changes; both should support-n, --dry-run.updatecould reasonably support--frozento mean "fail if anything would actually change vs the lockfile" (effectively a no-op verification).7.
initoverloadedskilltree 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 (initfor fresh state,seedorbootstrapfor opinionated defaults), but that's breaking.8. URL positional vs
--repoflagBoth accept a git URL.
registry addtakes it positionally;addtakes it via--repo. Probably right (registry's primary input is the URL), butregistry add --repo X --name Yshould likely be accepted as an alias so muscle memory transfers.9.
--typesemantics differ betweenaddandsearchAlready tracked in #22 —
searchfilters by--type;adddoes not (it only uses it as declarative metadata for--repo/--pathadds). Listed here for completeness; fix lives in #22.10.
add -D, --devhas no symmetricremove --devadd foo -Dputsfooindev-dependencies.remove foosearches both groups silently. That's the right default, but there's no way to disambiguateremove foo --devif 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):
--jsononverify,targets list,deps tree,registry update,cache clean.--globaloninfo.-n, --dry-runonunvendor,remove.--frozenonupdate.<agent>everywhere<target>is shown for the agent enum (or vice versa).registry addaccepts both positional URL and--repo <url>form.init,registry init,targets detectin--help.Breaking (queue for next major):
-f, --forceto mean only "clobber files on disk." Moveremove's "skip confirmation" to-y, --yes.registry add --nametoregistry add --asfor clarity.<target>or<agent>wins as canonical positional name; rename the loser.Test coverage to add
skilltree <cmd> --helpoutput for every command, so future flag changes are intentional.-n, --dry-run.--json.Documentation impact
README.mdis directly inconsistent with several of the issues raised here and will need updates as fixes land:Key Flagstable — line 317: the--forcerow currently readsOverwrite modified files / skip confirmation, which literally documents the overload this issue calls out. After the breaking-change rename:--forcerow → "Overwrite locally modified files on disk" (single meaning).-y, --yesrow → "Skip confirmation prompts (add,init,remove)".Key Flagstable — line 314 (--global): extend the command list to includeinfoonce--globalis added there.Key Flagstable — line 318 (--dry-run): extend to includeunvendorandremoveonce-nis added there. Note no--frozenrow exists today; ifupdategains--frozen, line 316's row should be extended accordingly.Key Flagstable —--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).Commandstable — 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 forteach's--agent <name>if it is also renamed.Commandstable —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>").remove -f→remove -y) need updated examples. None in the current Quick Start use-f/-y/--force/--yestoday, so this is only relevant if examples are added.--helpsnapshot test is added (per the "Test coverage to add" section), gate README'sCommandsandKey Flagstables to that snapshot to prevent future drift.