Skip to content

feat(tui): inherited skills + interactive /skills picker#113

Merged
BunsDev merged 11 commits into
mainfrom
feat/inherited-skills-picker
Jun 24, 2026
Merged

feat(tui): inherited skills + interactive /skills picker#113
BunsDev merged 11 commits into
mainfrom
feat/inherited-skills-picker

Conversation

@BunsDev

@BunsDev BunsDev commented Jun 24, 2026

Copy link
Copy Markdown
Member

Summary

/skills now 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 scope label and an estimated always-on token cost:

Scope Roots
Project .coven-code/skills/, .agents/skills/, .claude/skills/ (walk up from cwd, stops at $HOME)
User ~/.coven-code/skills/, ~/.agents/skills/, ~/.claude/skills/, ~/.codex/prompts/
Plugin ~/.claude/plugins/*/skills/
existing config paths + git URLs

Handles both the directory layout (<name>/SKILL.md, used by Claude/superpowers) and flat .md prompts (Codex), including YAML block-scalar descriptions (description: |) that a naive line parser would mangle. The walk-up stops at $HOME so 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 disabledSkills in Settings (mirrors disabledPlugins). Toggling saves to ~/.coven-code/settings.json and 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)

  • Picker filter input leaked to the prompt. Fast-typed/pasted filter text was captured by the prompt's paste-burst detector because prompt_is_accepting_text() didn't know an overlay owned input. Fixed for the skills picker, model picker, and command palette.
  • Centralized (70899e2): replaced the hand-maintained list of overlay checks in prompt_is_accepting_text() with a single predicate. any_modal_open() is split into any_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, …).
  • Log noise: discover_skills logged a tracing::warn per duplicate skill; with multi-environment inheritance the same skill in ~/.claude and ~/.agents is expected and flooded the TUI. Downgraded to debug.

Verification

  • cargo build (full workspace): green.
  • Driven in the running TUI: /skills lists inherited skills with user/builtin/plugin scope and ~NN tok (e.g. brainstorming · user · ~54 tok, higgsfield-generate · user · ~245 tok after 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.
  • New tests: skill_discovery (16) and skills_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

  • One pre-existing, unrelated test failure (welcome_screen_renders_status_block_with_key_affordances) reflects the v0.0.31 welcome-panel redesign and is untouched by this change.
  • Design spec: docs/superpowers/specs/2026-06-23-inherited-skills-picker-design.md.

🤖 Generated with Claude Code

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>
Copilot AI review requested due to automatic review settings June 24, 2026 03:33
@vercel

vercel Bot commented Jun 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview Jun 24, 2026 9:09pm

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 /skills modal overlay with per-skill toggle + scope + token estimate.
  • Extend core skill discovery to scan additional roots (project/user/plugin) and support both SKILL.md directory layout and flat .md prompts, including YAML block-scalar descriptions.
  • Persist disabled skills in Settings.disabled_skills and 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>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

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>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 6 comments.

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>
@BunsDev BunsDev merged commit a491625 into main Jun 24, 2026
1 check passed
@BunsDev BunsDev deleted the feat/inherited-skills-picker branch June 24, 2026 21:11
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.

2 participants