feat(tui): inherited skills + interactive /skills picker#113
Merged
Conversation
Discover skills from other environments (Claude ~/.claude/skills and ~/.claude/plugins/*/skills, Codex ~/.codex/prompts, project .claude/skills) alongside the existing coven/agents sources, deduped by name with a scope label and an estimated always-on token cost. Handles both the directory layout (<name>/SKILL.md) and flat .md prompts, including YAML block-scalar descriptions. /skills now opens an interactive, searchable picker (skills_picker.rs) showing each skill's on/off state, scope, and ~NN tok estimate. Space/Enter toggles a skill; the choice persists to Settings.disabledSkills and is honoured by both the always-on skill index (skill_prefetch) and the model-facing skill list (skill_tool), so disabling reclaims context tokens. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
Adds an interactive /skills picker in the TUI and expands skill discovery to inherit skills from adjacent environments (Claude/Codex/plugins), with persisted enable/disable to reclaim always-on context tokens.
Changes:
- Implement new searchable
/skillsmodal overlay with per-skill toggle + scope + token estimate. - Extend core skill discovery to scan additional roots (project/user/plugin) and support both
SKILL.mddirectory layout and flat.mdprompts, including YAML block-scalar descriptions. - Persist disabled skills in
Settings.disabled_skillsand honor them in both the always-on skill index and the model-facing skill listing.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src-rust/crates/tui/src/skills_picker.rs | New modal UI + state management for interactive skill toggling and filtering. |
| src-rust/crates/tui/src/render.rs | Renders the skills picker overlay; improves badge text contrast via readable_fg_on. |
| src-rust/crates/tui/src/lib.rs | Exposes the new skills_picker module. |
| src-rust/crates/tui/src/app.rs | Wires /skills command, picker state, input handling, and persisted toggles; adds readable_fg_on. |
| src-rust/crates/tools/src/skill_tool.rs | Filters disabled skills from the model-facing skill="list" output. |
| src-rust/crates/query/src/skill_prefetch.rs | Excludes disabled skills from the always-on skill index prefetch. |
| src-rust/crates/core/src/skill_discovery.rs | Adds scope/origin/token estimates and expanded root scanning + YAML block-scalar parsing. |
| src-rust/crates/core/src/lib.rs | Re-exports discovery additions; adds disabledSkills settings field + merge + helper. |
| docs/superpowers/specs/2026-06-23-inherited-skills-picker-design.md | Documents the intended design for inherited skills + picker behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+53
to
+55
| /// Rough token estimate for a string, mirroring the `chars / 4` convention | ||
| /// used elsewhere in the codebase (see `token_budget`). | ||
| pub fn estimate_tokens(text: &str) -> usize { |
Comment on lines
+261
to
+263
| // Directory layout: look for SKILL.md (case-insensitive on the stem) | ||
| // and default the name to the directory name. | ||
| let skill_md = path.join("SKILL.md"); |
Comment on lines
+41
to
+47
| `DiscoveredSkill` gains: | ||
|
|
||
| - `scope: SkillScope` — new enum `{ Bundled, Project, User, Plugin }`. | ||
| - `origin: String` — human label of where it came from: `"claude"`, `"codex"`, | ||
| `"coven"`, or the plugin directory name. Used only for diagnostics/tooltip; | ||
| the picker renders the `scope` word. | ||
| - `est_tokens: usize` — estimated always-on context cost (see §2). |
Comment on lines
+56
to
+58
| | 3 | Plugin | `~/.claude/plugins/*/skills/` and coven plugin-registry skill paths | | ||
| | 4 | Bundled | in-binary `BUNDLED_SKILLS` (merged at the listing layer, not in this fn) | | ||
| | — | Config | existing `SkillsConfig.paths` (User scope) and `urls` (User scope) | |
Found while verifying the /skills picker in the running app: - Fast-typed/pasted filter input leaked into the prompt because prompt_is_accepting_text() didn't treat the picker as owning input; the paste-burst detector captured it. Guard on skills_picker.visible (and register it in any_modal_open so the background prompt cursor is suppressed while the picker is open). - discover_skills logged a tracing::warn per duplicate skill; with multi-environment inheritance, the same skill in ~/.claude and ~/.agents is expected, and the warnings flooded the TUI. Downgrade to debug. - Inherited home-level skills were labeled "project" instead of "user" when the checkout lives under $HOME (the project walk-up reached the home dir first). Stop the walk-up at $HOME and attribute home-level .coven-code/.agents/.claude skill dirs to the User scope. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The /model picker has its own search box but, like the skills picker before it, wasn't listed in prompt_is_accepting_text(), so a fast-typed or pasted filter was captured by the prompt's paste-burst detector instead of the picker. Guard on model_picker.visible. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Same class as the skills/model picker fixes: the Ctrl+K command palette has a search box but wasn't listed in prompt_is_accepting_text(), so a fast-typed or pasted filter was captured by the prompt's paste-burst detector. Guard on command_palette.visible. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…icate Replace the hand-maintained list of overlay `.visible` checks in prompt_is_accepting_text() (the parallel list that caused the skills / model / command-palette paste-burst leaks) with one source of truth. Split any_modal_open() into any_blocking_modal_open() (input-capturing overlays) plus the passive notices (overage / voice / memory) that the user can keep typing underneath. prompt_is_accepting_text() now gates on any_blocking_modal_open(), so every current and future input-owning overlay automatically blocks prompt text and paste-burst capture — no parallel list to forget. any_modal_open() (used for rendering) keeps the notices and is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- estimate_tokens: drop the inaccurate reference to `token_budget` (the
heuristic lives here, not there); describe it as a display-only estimate.
- scan_skill_root: the SKILL.md lookup checks `SKILL.md`/`skill.md`, not a
case-insensitive stem — fix the comment to match.
- spec: SkillScope is `{ Project, User, Plugin }`; bundled skills aren't a
discovery scope, they're merged at the picker layer with a `builtin`
label. Correct the enum and the roots table.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment on lines
+261
to
+262
| // Directory layout: look for SKILL.md (case-insensitive on the stem) | ||
| // and default the name to the directory name. |
Comment on lines
156
to
+160
| async fn list_skills(dirs: &[PathBuf]) -> ToolResult { | ||
| // Start with the bundled skills. | ||
| // Skills toggled off in the `/skills` picker are hidden from the model. | ||
| let disabled = claurst_core::config::Settings::load_sync() | ||
| .map(|s| s.disabled_skills) | ||
| .unwrap_or_default(); |
Comment on lines
+3002
to
+3015
| if let Some((name, enabled)) = self.skills_picker.toggle_selected() { | ||
| let mut settings = Settings::load_sync().unwrap_or_default(); | ||
| if enabled { | ||
| settings.disabled_skills.remove(&name); | ||
| } else { | ||
| settings.disabled_skills.insert(name.clone()); | ||
| } | ||
| let _ = settings.save_sync(); | ||
| self.status_message = Some(format!( | ||
| "Skill '{}' {}.", | ||
| name, | ||
| if enabled { "enabled" } else { "disabled" } | ||
| )); | ||
| } |
…r handling - skill_tool::list_skills and query::prefetch_skills are async but read settings via the blocking Settings::load_sync(); switch to the async Settings::load().await so the Tokio runtime isn't blocked. - toggle_selected_skill ignored save_sync() failures, leaving the on-screen on/off state out of sync with disk. Handle the error: revert the toggle and surface a failure message. (The third comment — a stale "case-insensitive" doc note — was already fixed in 9280763.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment on lines
+94
to
+97
| ( | ||
| "learn", | ||
| "Codify the script/workflow we just built into a reusable skill", | ||
| ), |
Comment on lines
+176
to
+178
| "copy" | "skills" | "learn" | "plugin" | "reload-plugins" | "whisper" | "incant" => { | ||
| "Tools & Extras" | ||
| } |
Comment on lines
+53
to
+57
| /// Rough token estimate for a string using the common `chars / 4` | ||
| /// approximation. This is an estimate for display only, not an exact count. | ||
| pub fn estimate_tokens(text: &str) -> usize { | ||
| text.chars().count().div_ceil(4) | ||
| } |
Comment on lines
127
to
131
| let name = name.unwrap_or_else(|| { | ||
| path.file_stem() | ||
| .and_then(|s| s.to_str()) | ||
| .unwrap_or("unnamed") | ||
| .to_string() |
| (name, description, when_to_use) | ||
| } | ||
|
|
||
| /// First non-empty, non-heading line of a body, truncated to 200 chars. |
Comment on lines
+99
to
+101
| /// Expects optional YAML frontmatter delimited by `---`. When `description` | ||
| /// is absent (e.g. Codex prompts), it falls back to the first non-empty, | ||
| /// non-heading body line. Returns `None` when the file is empty after trimming. |
…miliar names
/learn ("Hermes, the coven's scribe") injects a guided prompt that distils the
script or workflow we just built into a reusable .coven-code/skills/<name>/SKILL.md
— auto-discovered, invocable as /<name>, model-callable, and toggleable in /skills.
Wired into COMMANDS, PROMPT_SLASH_COMMANDS, and slash_command_category; unit tested.
Reserve names so they stay the user's own:
- val / vale / valentina: blocked for ANY agent.
- nova / echo / sage / kitty / cody / charm / astra: blocked for familiars.
Enforced at the runtime agent-map builders, the /familiar loader, and the
create/edit commands (covers renaming onto a reserved name). Case-insensitive.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rename user-facing CLI from `coven-code` to `coven` across docs and UI copy, prefer @opencoven/coven npm package, and update install/upgrade/uninstall/build instructions. Add a new `/splash` slash command with persistence to settings (show_splash config), expose Config.show_splash accessor, and wire rendering so the welcome/splash panel respects the setting. Add UI/terminal rendering and tests for splash behavior, plus small formatting and whitespace tweaks. Docs: add /splash usage, demo entry, and package/hero copy updates.
Addresses the remaining Copilot review nits on skill discovery: - estimate_tokens delegates to the canonical message_utils::estimate_tokens so the heuristic is single-sourced and counts don't drift. - A present-but-empty `name:` frontmatter value now falls back to the file stem instead of becoming an empty HashMap key. - Corrected the parse_skill_file / first_body_line docs to say leading `#` heading markers are stripped (the prior "non-heading" wording was wrong). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts: # README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
/skillsnow opens an interactive, searchable picker that inherits skills from other environments, matching the picker shown in adjacent tooling. Subsequent commits fix a class of input-routing bugs found while verifying it in the running app, and centralize the fix.Skill inheritance (
core/skill_discovery.rs)Discovery now scans, deduped by name (first-match-wins) with a
scopelabel and an estimated always-on token cost:.coven-code/skills/,.agents/skills/,.claude/skills/(walk up from cwd, stops at$HOME)~/.coven-code/skills/,~/.agents/skills/,~/.claude/skills/,~/.codex/prompts/~/.claude/plugins/*/skills/Handles both the directory layout (
<name>/SKILL.md, used by Claude/superpowers) and flat.mdprompts (Codex), including YAML block-scalar descriptions (description: |) that a naive line parser would mangle. The walk-up stops at$HOMEso home-level skill dirs are attributed to the User scope (not "project") when the checkout lives under$HOME.Interactive picker (
tui/skills_picker.rs, new)Built on the existing modal/search helpers:
⌕ Search skills…box, rows showing› ✓ on <name> · <scope> · ~NN tok, accent-highlighted selection, scroll hints. Keys: type to filter · ↑↓ / Ctrl-p/n navigate · Space/Enter toggle · Esc close./skills <query>opens pre-filtered.Persisted enable/disable
New
disabledSkillsinSettings(mirrorsdisabledPlugins). Toggling saves to~/.coven-code/settings.jsonand is honoured by both the always-on skill index (query/skill_prefetch.rs) and the model-facing list (tools/skill_tool.rs), so disabling genuinely reclaims context tokens.Input-routing fixes (found by running the app)
prompt_is_accepting_text()didn't know an overlay owned input. Fixed for the skills picker, model picker, and command palette.70899e2): replaced the hand-maintained list of overlay checks inprompt_is_accepting_text()with a single predicate.any_modal_open()is split intoany_blocking_modal_open()(input-capturing overlays) + the passive notices (overage/voice/memory) the user can type under; the prompt gates on the blocking set, so every current and future input-owning overlay auto-blocks paste-burst capture. This also closed the same latent leak in every other blocking overlay (help, rewind, dialogs, …).discover_skillslogged atracing::warnper duplicate skill; with multi-environment inheritance the same skill in~/.claudeand~/.agentsis expected and flooded the TUI. Downgraded todebug.Verification
cargo build(full workspace): green./skillslists inherited skills withuser/builtin/pluginscope and~NN tok(e.g.brainstorming · user · ~54 tok,higgsfield-generate · user · ~245 tokafter the block-scalar fix); search filters; Space toggles and persists across restart. Skills, model, and command-palette filters all land in their search boxes with the prompt empty; baseline prompt typing/paste still works.skill_discovery(16) andskills_picker(4, incl. a render test asserting the on/off/scope/tok/search layout). Settings-merge, query, and tools tests pass; 647/648 TUI tests pass.Notes
welcome_screen_renders_status_block_with_key_affordances) reflects the v0.0.31 welcome-panel redesign and is untouched by this change.docs/superpowers/specs/2026-06-23-inherited-skills-picker-design.md.🤖 Generated with Claude Code