Skip to content

Skills: deterministic invocation — /<skill-name>, searchable /skills picker, discovery-budget fix#520

Merged
gnanam1990 merged 5 commits into
mainfrom
feat/skill-invocation
Jul 5, 2026
Merged

Skills: deterministic invocation — /<skill-name>, searchable /skills picker, discovery-budget fix#520
gnanam1990 merged 5 commits into
mainfrom
feat/skill-invocation

Conversation

@gnanam1990

@gnanam1990 gnanam1990 commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

Makes skill invocation deterministic and discoverable. Direct user feedback: "Skill invocations — fix how they get invoked and I would happily keep using it."

Skills were model-pulled only: the model had to notice a skill in a 640-byte system-prompt list (~6 skills visible; the rest collapsed into "…and N more") and decide to call the skill tool on its own. Users could not run a skill at all — typing /<skill-name> reported "unknown command", because skills and .zero/commands user commands were two disconnected systems. The more skills someone installed, the less reliably any of them fired.

Three commits, layered:

1. feat(skills) — deterministic invocation + discovery fix

User invocation

  • /<skill-name> [args] runs an installed skill directly: resolved after builtins and user commands (precedence: builtin > user command > skill), the skill body is inlined as the prompt with the typed request appended, submitted through the same launch path user commands use.
  • Slash palette suggests skills (labeled (skill)), read through the same loader dispatch uses — mid-session installs appear without a restart; names shadowed by a builtin/alias/user command or by a case-colliding sibling are never advertised (a shadowed row would be dead at dispatch time).
  • Run-state guards: skill and user-command invocations now apply the plain-prompt guards — mid-run submissions queue the expanded prompt (the queue flush submits literal text, so queuing the raw /name would have sent it to the model as prose), compaction-in-flight warns, exiting drops. This also closes a pre-existing hole where a user command could start a second concurrent run.
  • Wiring: a lazy tui.Options.LoadSkills closure backed by plugins.MergedSkillsLoaded — the same merged set (default dir + plugin skill roots) the skill tool and the system prompt resolve against — TTL-cached (2s) so palette keystrokes don't re-read SKILL.md bodies.

Model triggering

  • Discovery budget 640 → 4096 bytes. The list is the model's only discovery surface; the old cap made skills past ~6 invisible, so invocation reliability collapsed exactly for users invested in skills. A regression test now pins that a realistic 20-skill set lists in full.
  • Description truncation 100 → 200 runes. The description carries the trigger conditions ("use when…"); cutting them mid-sentence is what stopped the model from firing the skill.
  • Stronger instruction line: scan the list before acting, call the matching skill FIRST, don't substitute your own approach.

2. feat(tui)/skills opens a searchable picker

/skills now behaves like /model: type-to-filter (ranked search), arrow keys, Enter. Selection fills the composer with /name (cursor at the end) so the user adds their request before submitting — it does not fire the skill blind. Skills whose names can't be typed as a slash command (non-slash shapes, or names shadowed by a builtin/user command) run immediately on selection instead: the picker is the only path that reaches them, and picking from the Skills list is unambiguous. The old text card remains only as the no-skills install hint.

3. feat(tui) — ask-first guard for bare invocations

A skill invoked with no request submits only its body — instructions with no target ("review the PR" — which PR?) — and the model improvised a target instead of asking, which is especially wrong in a fresh session. Bare invocations (slash and picker) now append a conditional note: if the instructions need a target or details not already clear from the conversation, ask first; self-contained skills (e.g. one that reads the staged diff) still just run. Invocations that carry a request are unchanged.

Notes for reviewers

  • The system-prompt skills block renders only when skills are installed, so skill-less runs stay byte-identical; the prompt-budget ratchet measures a skills-free prompt and is untouched.
  • A heavy-skill session now spends up to ~4 KB more system prompt than before — deliberate: an invisible skill is a broken skill, and the block is prompt-cache-friendly (stable across turns).
  • The clarify-note wording lives in one place (bareSkillInvocationNote) and is only attached when args == "".

Testing

Summary by CodeRabbit

  • New Features
    • Added a /skills command with a picker to browse and launch installed skills, including a clear “no skills installed” fallback with an install hint.
    • Improved autocomplete to suggest installed skills (case-insensitive) alongside commands, with labeled skill rows.
    • Expanded agent guidance to include more skills per turn and longer per-skill descriptions.
  • Bug Fixes
    • Skills loading is cached briefly in-session so newly installed skills can appear without restarting.
    • Improved safe handling for skill and expanded user-command launches during pending/exiting/compaction states; unknown slashes remain unknown and empty skill bodies report an error.

Skills were model-pulled only: the model had to notice a skill in a
640-byte system-prompt list (~6 skills visible; the rest collapsed into
'…and N more') and call the skill tool on its own. Users could not run a
skill at all — typing /<skill-name> reported 'unknown command'. The two
halves of the fix:

User invocation
- /<skill-name> [args] now runs an installed skill directly: resolved
  after builtins and user commands, the skill body is inlined as the
  prompt with the typed args appended, and submitted through the same
  launch path user commands use.
- /skills lists installed skills with their slash form, live from disk.
  Labels are honest: a skill shadowed by a builtin, alias, user command,
  or a case-colliding sibling is listed bare with a '(via skill tool)'
  note instead of advertising a dead /name.
- The slash palette suggests skills (labeled '(skill)') read through the
  same loader dispatch uses, so mid-session installs appear and shadowed
  or case-colliding names are never advertised.
- Skill and user-command invocations now apply the plain-prompt run-state
  guards: mid-run submissions queue the EXPANDED prompt (the queue flush
  submits literal text), compaction-in-flight warns, exiting drops.
- Skills resolve through a lazy plugin-aware loader (default dir merged
  with plugin skill roots — the same set the skill tool and the system
  prompt see), TTL-cached so palette keystrokes don't re-read SKILL.md
  bodies.

Model triggering
- Raise the skills-list budget 640 -> 4096 bytes: the list is the model's
  only discovery surface, and the old cap made skills past ~6 invisible —
  invocation reliability collapsed exactly for users invested in skills.
- Raise description truncation 100 -> 200 runes: the description carries
  the trigger conditions ('use when…'); cutting them is what stopped the
  model from firing the skill.
- Strengthen the instruction line: scan the list before acting, call the
  matching skill FIRST, don't substitute your own approach.
Replace the static /skills card with the same picker UX /model uses:
type-to-filter (ranked search), arrow-key selection, Enter runs the
chosen skill immediately. Selection is by the skill's exact name, so
skills the slash path cannot reach — names shadowed by a builtin or
user command, or names that don't fit a slash token — are still
runnable from the picker, where the choice is unambiguous. The text
card remains only as the no-skills install hint, and the picker path
applies the same run-state guards as slash invocation.
A skill invoked with no request submits only its body — instructions
with no target ("review the PR" — which PR?) — and the model improvises
a target instead of asking, which is especially wrong in a fresh
session. Bare invocations (slash and picker) now append a conditional
note: if the instructions need a target or details not already clear
from the conversation, ask first; self-contained skills still just run.
Invocations that carry a request are unchanged.
@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Zero automated PR review

Verdict: No blockers found

Blockers

  • None found.

Validation

  • [pass] Diff hygiene: git diff --check
  • [pass] Tests: go test ./...
  • [pass] Build: go run ./cmd/zero-release build
  • [pass] Smoke build: go run ./cmd/zero-release smoke

Scope

Head: c427052f9b35
Changed files (13): .gitignore, internal/agent/skills_context_test.go, internal/agent/system_prompt.go, internal/cli/app.go, internal/tui/autocomplete.go, internal/tui/command_views.go, internal/tui/commands.go, internal/tui/model.go, internal/tui/options.go, internal/tui/picker.go, internal/tui/skill_commands.go, internal/tui/skill_commands_test.go, and 1 more

This deterministic review checks validation status and basic diff hygiene. A human reviewer still owns product judgment and design quality.

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 62a41c7f-a883-4c32-baa1-27f608371001

📥 Commits

Reviewing files that changed from the base of the PR and between 82b3583 and c427052.

📒 Files selected for processing (8)
  • .gitignore
  • internal/tui/autocomplete.go
  • internal/tui/command_views.go
  • internal/tui/model.go
  • internal/tui/picker.go
  • internal/tui/skill_commands.go
  • internal/tui/skill_commands_test.go
  • internal/tui/user_commands.go
✅ Files skipped from review due to trivial changes (1)
  • .gitignore
🚧 Files skipped from review as they are similar to previous changes (7)
  • internal/tui/user_commands.go
  • internal/tui/picker.go
  • internal/tui/command_views.go
  • internal/tui/model.go
  • internal/tui/autocomplete.go
  • internal/tui/skill_commands.go
  • internal/tui/skill_commands_test.go

Walkthrough

This PR adds installed-skill support to the TUI, including cached loading, a /skills command, autocomplete and picker integration, and slash-command execution. It also expands the agent prompt’s skills list budget and truncation limits.

Changes

Skills invocation and TUI integration

Layer / File(s) Summary
Prompt budget and truncation
internal/agent/system_prompt.go, internal/agent/skills_context_test.go
skillsContextListBudget increases to 4096 bytes, per-skill truncation increases to 200 runes, the matching directive text changes, and tests cover larger realistic skill sets.
Cached skills loader
internal/cli/app.go
Adds a mutex-protected cached loader for installed skills and wires it into interactive TUI options.
TUI options, commands, and empty state
internal/tui/options.go, internal/tui/model.go, internal/tui/commands.go, internal/tui/command_views.go
Adds lazy skills loading to the TUI model, registers /skills, and renders the no-skills fallback view.
Skill command dispatch
internal/tui/skill_commands.go, internal/tui/model.go, internal/tui/user_commands.go
Resolves installed skills from slash input, builds skill prompts, guards launches by run state, and routes unknown-command handling and user-command expansion through the shared deferred-launch path.
Skills command and picker
internal/tui/picker.go, internal/tui/model.go, internal/tui/skill_commands.go
Adds the skill picker kind, opens the picker for /skills, and handles selection by inserting or directly running a skill based on slash-token availability.
Autocomplete and skill tests
internal/tui/autocomplete.go, internal/tui/skill_commands_test.go
Autocomplete includes skill matches, and the new test suite covers dispatch, autocomplete, picker behavior, run-state guards, and loader refreshes.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

  • Gitlawb/zero#123: Introduces the internal/skills types and loader flow that this PR wires into the TUI.
  • Gitlawb/zero#126: Shares the same TUI autocomplete and picker plumbing extended here for skills.
  • Gitlawb/zero#128: Also touches the interactive TUI setup in internal/cli/app.go.

Suggested reviewers: Vasanthdev2004, kevincodex1

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: direct skill invocation, a searchable /skills picker, and the skills discovery-budget update.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/skill-invocation

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

🧹 Nitpick comments (2)
internal/tui/autocomplete.go (1)

187-193: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

No overall cap on combined suggestion list.

Each source (matchCommandSuggestionsWithFilter, matchUserCommandSuggestions, matchSkillSuggestions) independently truncates to maxCommandSuggestions (32), but the merged out here isn't re-capped. With skills now added as a third source, the palette can surface up to ~96 rows for a single prefix.

💡 Proposed fix to cap the merged result
 func (m model) matchCommandSuggestions(token string) []commandSuggestion {
 	out := matchCommandSuggestionsWithFilter(token, func(command commandDefinition) bool {
 		return command.kind != commandSandboxSetup || m.sandboxSetupCommand != nil
 	})
 	out = append(out, m.matchUserCommandSuggestions(token)...)
-	return append(out, m.matchSkillSuggestions(token)...)
+	out = append(out, m.matchSkillSuggestions(token)...)
+	if len(out) > maxCommandSuggestions {
+		out = out[:maxCommandSuggestions]
+	}
+	return out
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/tui/autocomplete.go` around lines 187 - 193, The merged autocomplete
list in matchCommandSuggestions can exceed the intended overall limit because it
appends results from matchCommandSuggestionsWithFilter,
matchUserCommandSuggestions, and matchSkillSuggestions without reapplying
maxCommandSuggestions. Add a final cap after combining all three sources so the
returned []commandSuggestion never exceeds the shared maximum, keeping the
palette size bounded regardless of how many sources match.
internal/tui/skill_commands_test.go (1)

177-204: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Weak assertion: checks formatted Desc text, not absence of the shadowed Name.

This test only fails if a suggestion's Desc exactly equals "Skill: /help" etc. If the default-description format in matchSkillSuggestions (Line 223) ever changes, this test silently stops verifying the actual invariant (that shadowed names aren't advertised at all), while still passing.

✅ Proposed tightening to assert on Name directly
 	for _, token := range []string{"/hel", "/ses", "/dep"} {
 		for _, s := range m.matchCommandSuggestions(token) {
-			if s.Desc == "Skill: /help" || s.Desc == "Skill: /sessions" || s.Desc == "Skill: /deploy" {
+			if s.Name == "/help" || s.Name == "/sessions" || s.Name == "/deploy" {
 				t.Fatalf("shadowed skill advertised for %q: %#v", token, s)
 			}
 		}
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/tui/skill_commands_test.go` around lines 177 - 204, The
shadowed-skill test is asserting against the rendered Desc string instead of the
suggestion identity, so it can miss regressions if formatting changes. Update
TestShadowedSkillNotSuggested to verify that matchCommandSuggestions does not
return any suggestion whose Name corresponds to the shadowed skills (/help,
/sessions, /deploy), rather than matching on Desc text. Keep the existing token
loop and use the suggestion Name field as the invariant, with
matchSkillSuggestions/matchCommandSuggestions as the key locations to inspect.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/tui/skill_commands.go`:
- Around line 65-88: The compactInFlight branch in launchOrDeferExpandedPrompt
loses the original typed invocation because commandUnknown clears the composer
before handleSkillCommand/handleUserCommand runs. Thread the raw invocation
through the skill/user-command dispatch path, and when compactInFlight is true
restore that text back into the composer before returning so the user can re-run
it without retyping; update the relevant call sites around commandUnknown,
invokeSkillByName, and the user command handling to pass the invocation along.
- Around line 169-177: The installedSkills() path is still performing a
synchronous disk refresh through m.loadSkills(), which can block the Bubble Tea
Update flow on cache misses. Update the model/loader wiring so skill refreshes
happen asynchronously or are preloaded outside the TUI hot path, and have
installedSkills() read from a cached snapshot instead of waiting on
internal/cli/app.go’s loader. Keep the behavior for bare sessions intact while
ensuring /skills, autocomplete, and direct skill invocation no longer stall in
model.installedSkills().

---

Nitpick comments:
In `@internal/tui/autocomplete.go`:
- Around line 187-193: The merged autocomplete list in matchCommandSuggestions
can exceed the intended overall limit because it appends results from
matchCommandSuggestionsWithFilter, matchUserCommandSuggestions, and
matchSkillSuggestions without reapplying maxCommandSuggestions. Add a final cap
after combining all three sources so the returned []commandSuggestion never
exceeds the shared maximum, keeping the palette size bounded regardless of how
many sources match.

In `@internal/tui/skill_commands_test.go`:
- Around line 177-204: The shadowed-skill test is asserting against the rendered
Desc string instead of the suggestion identity, so it can miss regressions if
formatting changes. Update TestShadowedSkillNotSuggested to verify that
matchCommandSuggestions does not return any suggestion whose Name corresponds to
the shadowed skills (/help, /sessions, /deploy), rather than matching on Desc
text. Keep the existing token loop and use the suggestion Name field as the
invariant, with matchSkillSuggestions/matchCommandSuggestions as the key
locations to inspect.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 99354357-ecba-4d31-b3db-90d9af46e305

📥 Commits

Reviewing files that changed from the base of the PR and between 0f69d99 and ce67c2c.

📒 Files selected for processing (13)
  • internal/agent/skills_context_test.go
  • internal/agent/system_prompt.go
  • internal/cli/app.go
  • internal/tui/autocomplete.go
  • internal/tui/command_views.go
  • internal/tui/commands.go
  • internal/tui/model.go
  • internal/tui/options.go
  • internal/tui/picker.go
  • internal/tui/skill_commands.go
  • internal/tui/skill_commands_test.go
  • internal/tui/user_commands.go
  • zero-skills

Comment thread internal/tui/skill_commands.go
Comment thread internal/tui/skill_commands.go
…guard, cap merged suggestions

- The compaction-in-flight guard for skill/user-command invocations fired
  after the dispatch path had already cleared the composer, so the typed
  "/name args" was lost and had to be retyped; thread the raw invocation
  through and restore it into the composer (the plain-prompt guard keeps
  the composer intact by firing before the clear). The picker path has no
  typed form and restores nothing.
- Cap the merged autocomplete list at the shared bound: each source
  self-caps, but three sources could stack to ~3x the palette limit.
- Tighten the shadowed-skill palette test to assert on the advertised
  Name rather than the fallback Desc wording.
@gnanam1990

Copy link
Copy Markdown
Collaborator Author

Addressed the review: the compaction-guard text-loss is fixed (raw invocation threaded through and restored into the composer, with a test pinning it), the merged suggestion list is now capped at the shared bound, and the shadowed-skill test asserts on the advertised Name. The synchronous cache-miss load is kept as-is with reasoning on the thread — bounded I/O, TTL-limited, consistent with existing patterns.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

@gnanam1990 Thanks for the summary — kicking off a fresh review to verify the compaction-guard fix, the suggestion-list cap, and the shadowed-skill test assertion, and to re-check the synchronous cache-miss load against the bounded I/O / TTL reasoning you noted.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@gnanam1990

Copy link
Copy Markdown
Collaborator Author

@CodeRabbit approve

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown
✅ Action performed

Comments resolved and changes approved.

@gnanam1990

Copy link
Copy Markdown
Collaborator Author

@kevincodex1 check it out

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I found an issue that needs to be addressed before this is ready.

Findings

  • [P2] Remove the committed build artifact
    zero-skills
    This PR adds a 30 MB Mach-O arm64 executable at the repository root, but the change is otherwise source-level Go/TUI work and the binary is not referenced by the command wiring, package metadata, or release scripts. Keeping a prebuilt platform-specific executable in source history bloats every clone and bypasses the normal build/release provenance for a file that Linux and Windows users cannot run anyway. Please remove zero-skills from the PR, and if a local build command produced it, add that generated name to the ignored build-artifact patterns or build into the existing ignored output path instead.

A locally built test binary under this name was accidentally committed
(and has been scrubbed from the branch history); ignore the name so it
cannot recur.
@gnanam1990 gnanam1990 force-pushed the feat/skill-invocation branch from 82b3583 to c427052 Compare July 5, 2026 15:19
@gnanam1990

Copy link
Copy Markdown
Collaborator Author

@jatmn addressed — and thanks for the catch, that was a locally built test binary swept in by a broad git add. Rather than just deleting it at the tip, I rewrote the branch history (filter-branch --index-filter) so the 30 MB blob no longer exists in any commit of this PR — a tip-only removal would still have shipped it into every clone via history. Also added /zero-skills to the ignored build-artifact patterns alongside /zero and /zero.exe so the name can't recur. Force-pushed; the four source commits are content-identical to what you reviewed (same diffs, authorship preserved), plus the one-line gitignore commit.

@Vasanthdev2004 Vasanthdev2004 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed the whole thing and it holds up — approving. This solves a real problem (skills that only fired if the model happened to notice them in a truncated list), and the dispatch design is careful.

Precedence is right: parseCommand takes builtins, then commandUnknown falls through handleUserCommandhandleSkillCommand → "unknown," so it's builtin > user command > skill exactly as documented, and a shadowed skill name never reaches dispatch.

The guard refactor is the sharp bit. launchOrDeferExpandedPrompt queues the EXPANDED prompt rather than the raw /name — right call, since the queue flush resubmits literal text, so queuing /name args would have sent it to the model as prose. And routing user commands through the same path (user_commands.go:35) closes the pre-existing hole where a user command could kick off a second concurrent run mid-turn. Good catch folding that in.

The rest is thoughtful too: an empty SKILL.md body surfaces a real error instead of "unknown command," the bare-invocation ask-first note is conditional so self-contained skills still run, and the skills block only renders when skills are installed so skill-less prompts stay byte-identical (the prompt-budget ratchet is untouched). The 640 → 4096 discovery budget is a deliberate, prompt-cache-friendly cost for heavy-skill users — reasonable, since an invisible skill is a broken skill.

On the binary: jatmn's zero-skills flag is resolved — it's out of the tree and /zero-skills is in .gitignore now (that review predates the cleanup commit). Build's clean, tui + agent suites pass here, and CI is green across all nine checks.

Good work. Good to merge.

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the update. I rechecked the previously discussed paths and do not see any remaining actionable issues from my side.

@gnanam1990 gnanam1990 requested a review from kevincodex1 July 5, 2026 15:30

@anandh8x anandh8x left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Independently verified from source, not just the review threads: checked out the branch in an isolated worktree, go build/go vet clean, and -race passes on all 4 touched packages (agent/tui/cli/plugins).

Confirmed directly against code (not just taking the PR body's word for it):

  • Precedence (builtin > user command > skill) matches handleSubmit's actual dispatch order.
  • The compaction-guard fix is correctly targeted — commandUnknown clears the composer unconditionally before reaching skill/user-command dispatch (unlike the plain-prompt path, which checks compactInFlight first), so threading raw through to restore it is the right fix, not a workaround.
  • zero-skills is gone from every commit in the PR's range, not just the tip — checked via git rev-list + blob-size scan across the full range.
  • cachedSkillsLoader's TTL isn't defeated by the zero-skills case (checked whether mergeSkills could return nil there — it always inits []skills.Skill{}, so the cache holds).
  • Real bonus fix: handleUserCommand had no run-state guard at all before this PR — a pre-existing hole where a user command could start a second concurrent run, closed as a side effect.

One non-blocking gap for a follow-up: the merged-suggestion-list cap (autocomplete.go's re-cap after combining command/user-command/skill sources) is correct by inspection but has no regression test constructing a >32-combined-match scenario. All existing tests check functional correctness (shadowing, case-collision) not this scale invariant — worth pinning so a future "simplification" can't silently drop the cap.

Good to merge.

@gnanam1990 gnanam1990 merged commit 284a6c8 into main Jul 5, 2026
9 checks passed
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.

4 participants