Skip to content

refactor: dedupe cross-package logic and remove dead code from audit#58

Merged
ezynda3 merged 12 commits into
masterfrom
audit/fixes
Jun 11, 2026
Merged

refactor: dedupe cross-package logic and remove dead code from audit#58
ezynda3 merged 12 commits into
masterfrom
audit/fixes

Conversation

@ezynda3

@ezynda3 ezynda3 commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Description

This PR implements the actionable findings from a full read-only code audit of the repository: dead-code removal, consolidation of seven verified duplication clusters, and low-risk refactors of the largest functions. Net result is −109 lines (892 insertions / 1,001 deletions) while adding shared infrastructure that removes copy-paste drift between cmd/, internal/acpserver/, internal/ui/, and pkg/kit/.

Two of the consolidations fix real bugs:

  • pkg/kit.ExtractModelFromPath mis-parsed model IDs containing / (e.g. openrouter/meta/llama"meta"). It now delegates to RemoveProviderFromModel and is deprecated alongside ExtractProviderFromPath per the SDK deprecation policy.
  • ACP mode silently had nil extension-context fields (state, tree navigation, skills, template parsing, model resolution). The new extbridge.BaseContext provides the headless half of extensions.Context; cmd/extension_context.go and internal/acpserver/session.go now overlay only their UI-specific closures instead of maintaining two ~150-line literals by hand.

Other highlights, grounded in the commits:

  • Dead code: removed 5 symbols with zero non-test references (LoadModelSettingsFromConfig, PromptTemplate.ExpandWithArgs, NewMessageStore, HasEnvVars, ContextWithSudoPassword); verified against SDK reachability and the Yaegi symbol table before deletion.
  • Tool-kind classification: coreToolKinds/toolKindFor existed verbatim in both internal/extensions/wrapper.go and pkg/kit/events.go, risking divergent ToolKind values between extension events and SDK events. Single source now lives in internal/extensions/toolkinds.go; pkg/kit re-exports the constants.
  • Anthropic OAuth detection: the "stored OAuth" magic-string prefix match was copy-pasted at 5 sites. Now one auth.CredentialSourceOAuth constant + auth.IsAnthropicOAuth(), and a shared ui.UpdateUsageTrackerForModel() replaces two verbatim tracker-refresh blocks in cmd/.
  • Template engine unification: skill prompts and the extension template API used two different {{variable}} grammars (whitespace tolerance vs first-char rules) — the same template rendered differently depending on entry point. One engine in internal/skills now uses the superset grammar; pkg/kit ParseTemplate/RenderTemplate are thin adapters. Same treatment for the quote/escape argument tokenizer (parseFieldsprompts.ParseCommandArgs).
  • Refactors: extracted switchModel (model selector + /model command shared one drifted sequence), withOAuthRetry/marshalToolResult in MCP tool execution (2× retry stanza, 4× marshal stanza), buildShareFile with defer-based cleanup (4× cleanup chain), and validateModeFlags/restorePersistedPreferences/applyProviderURLRouting out of the 663-line runNormalMode.

All changes respect the documented invariants in AGENTS.md: no prog.Send() from Update(), closures crossing the Yaegi boundary remain anonymous literals, no interfaces in extension-facing types, and no dependency-name leakage into SDK exports.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Refactor (no functional change, code cleanup/consolidation; includes two small bug fixes noted above)
  • Breaking change
  • Documentation update

How Has This Been Tested?

  • go test -race ./... (all packages, green) plus go vet and staticcheck (clean)
  • Interactive tmux smoke test against a live model: flag-combination validation, --provider-url model rewriting (custom · gemma-test / custom · custom), /model direct switch + selector-overlay switch with OnModelChange events, OAuth cost suppression ($0.00 across 6.6K tokens with tracker refreshes), OnToolCall kind classification (bash→execute, read→read), /share happy path against a fake gh shim (system-prompt entry injected after header) and in-memory early-return path
  • ACP-mode smoke test via JSON-RPC (initialize + session/new): previously-nil extension APIs now work — state=OK template=OK tokenizer=OK treenav=OK skills=OK stats=OK
  • A custom Yaegi smoke extension verified the unified template grammar ({{name}} and {{ name }}), tokenizer (quotes, backslash escapes, tab splitting), and BaseContext delegation from inside the interpreter

Checklist

  • My code follows the style guidelines of this project (go fmt, go vet, staticcheck clean)
  • I have performed a self-review of my own code
  • Existing tests pass; tests covering removed symbols were migrated, not deleted blindly
  • I have added new tests (consolidations are covered by existing suites; no new public behavior)
  • Documentation updated (no user-facing docs affected)

Additional Information

Added

  • internal/extbridge/context.go — shared headless extensions.Context builder (BaseContext)
  • internal/extensions/toolkinds.go — canonical ToolKind* constants + ToolKindFor

Backward compatibility

  • Public SDK surface preserved: ExtractProviderFromPath/ExtractModelFromPath kept as deprecated wrappers; NewTreeManagerAdapter/InitTreeSession signatures now spell the existing kit.TreeManager alias (same type, cleaner go doc)
  • Behavioral supersets, intentional: prompts.ParseCommandArgs now also splits on tabs; the template grammar now accepts whitespace inside {{ }} everywhere (previously accepted in only one of the two engines)
  • Removed internal/ symbols had zero references outside their own tests

Known pre-existing issue surfaced during testing (not addressed here)

  • kit acp -e <file> does not propagate the -e flag into ACP's isolated per-session config store; only global/project-local extensions load in ACP mode. Worth a follow-up issue.

Summary by CodeRabbit

  • Bug Fixes
    • Improved OAuth credential handling for more reliable auth flows.
    • Template variable matching now tolerates extra whitespace.
  • Improvements
    • Optimized tool execution with consolidated retry and error handling.
    • Streamlined model switching for faster, more consistent updates.
    • More robust command-argument parsing (spaces and tabs handled consistently).

ezynda3 added 11 commits June 11, 2026 14:42
- internal/models: LoadModelSettingsFromConfig (zero refs)
- internal/prompts: PromptTemplate.ExpandWithArgs (zero refs)
- internal/app: NewMessageStore (tests migrated to NewMessageStoreWithMessages)
- internal/config: HasEnvVars (+ its test)
- internal/core: ContextWithSudoPassword (test migrated to context.WithValue)
NewTreeManagerAdapter and InitTreeSession now spell their signatures with
the public kit.TreeManager alias instead of internal/session.TreeManager,
so go doc renders domain types rather than internal paths.
coreToolKinds + toolKindFor were duplicated verbatim in
internal/extensions/wrapper.go and pkg/kit/events.go, risking silent
divergence between extension events and SDK events. Single source of
truth now lives in internal/extensions/toolkinds.go; pkg/kit re-exports
the constants.
The 'is the active Anthropic credential a stored OAuth token' check was
copy-pasted at 5 sites, all prefix-matching the magic string
'stored OAuth' produced in internal/auth. Now:

- internal/auth: new CredentialSourceOAuth constant + IsAnthropicOAuth()
- internal/ui: new UpdateUsageTrackerForModel(); CreateUsageTracker and
  SetupCLI share lookupTrackableModel (SetupCLI no longer re-inlines the
  tracker construction)
- cmd/root.go + cmd/extension_context.go: verbatim-duplicated tracker
  refresh blocks replaced with ui.UpdateUsageTrackerForModel
- pkg/kit isAnthropicOAuth delegates to auth.IsAnthropicOAuth
- internal/models compares source against the constant
- ExtractModelFromPath mis-parsed model IDs containing '/' (e.g.
  'openrouter/meta/llama' -> 'meta'); it now delegates to
  RemoveProviderFromModel and is deprecated alongside
  ExtractProviderFromPath (-> GetCurrentProvider)
- parseFields delegated to prompts.ParseCommandArgs so extension argument
  parsing and builtin prompt-template parsing share one quote/escape
  grammar; ParseCommandArgs now also splits on tabs (superset of both
  previous tokenizers)
internal/skills and pkg/kit/template_bridge each had their own grammar:
skills rejected '{{ name }}' (whitespace) but allowed digit-first names;
the bridge was the opposite. A template behaved differently depending on
whether it was loaded as a skill prompt or via the extension API.

internal/skills is now the single engine using the superset grammar
(\{\{\s*(\w+)\s*\}\}); pkg/kit ParseTemplate/RenderTemplate are thin
adapters over it. Expand is now regex-based so whitespace placeholders
expand consistently; missing variables are still left as-is.
The model-selector handler (ModelSelectedMsg) and /model slash command
duplicated the full switch sequence (thinking-level fallback, setModel,
display-state update, preference persistence, ModelChange emit) and had
already drifted in ordering. Both now call a single switchModel method.
Display state is still updated directly (no prog.Send from Update).
cmd/extension_context.go and internal/acpserver/session.go each built a
giant extensions.Context literal, duplicating ~15 delegation closures
(GetContextStats, GetMessages, AppendEntry, options, SetModel core,
Complete, SpawnSubagent, ...) that had to be kept in sync by hand. New
data-access fields had to be wired in both places or ACP-mode extensions
silently got nil function fields.

extbridge.BaseContext now provides the headless half; both call sites
overlay only their UI-specific closures. As a side effect ACP mode gains
previously-missing APIs (state, tree navigation, skills, template
parsing, model resolution) that were nil before. The interactive TUI
keeps its exact SetModel/ReloadExtensions ordering via overrides.
ExecuteTool repeated the OAuth-error/re-auth/retry stanza verbatim twice
(sync and task-augmented paths) and the marshal-and-wrap stanza four
times. Both are now single helpers with identical error strings, so a
fix to OAuth retry or error categorization applies everywhere at once.
handleShareCommand repeated the close/remove/print/return cleanup chain
four times across its temp-file write error paths. File assembly now
lives in buildShareFile with a single deferred cleanup on error.
…uting from runNormalMode

runNormalMode opened with ~150 lines of policy logic (flag-combination
validation, persisted model/thinking-level preference restoration, and
two subtle --provider-url model-rewrite rules). These are now standalone
functions (validateModeFlags, restorePersistedPreferences,
applyProviderURLRouting) so the routing policy is independently readable
and testable. Behaviour unchanged; ordering preserved.
@mark-iii-labs-huly

Copy link
Copy Markdown

Connected to Huly®: KIT-59

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 98b4cf1b-8c31-43a5-9be8-39823fdffe79

📥 Commits

Reviewing files that changed from the base of the PR and between 086d933 and 03d78df.

📒 Files selected for processing (3)
  • internal/tools/mcp.go
  • pkg/kit/events.go
  • pkg/kit/template_bridge.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • internal/tools/mcp.go
  • pkg/kit/template_bridge.go
  • pkg/kit/events.go

📝 Walkthrough

Walkthrough

This PR extracts a headless extension BaseContext, composes TUI and ACP overlays from it, centralizes Anthropic OAuth detection and tool-kind classification, unifies template/argument parsing, consolidates model usage-tracking, and removes several small exported helpers.

Changes

Extension Context Refactoring and API Consolidation

Layer / File(s) Summary
OAuth detection centralization
internal/auth/credentials.go, internal/models/providers.go, pkg/kit/extensions_bridge.go
CredentialSourceOAuth constant and IsAnthropicOAuth() predicate replace scattered string-prefix checks for Anthropic OAuth.
BaseContext foundation and extension state management
internal/extbridge/context.go
New BaseContext() constructs a headless extensions.Context delegating data access, state CRUD, model/tool ops, completions/subagents, tree navigation, skills, templates, and model helpers via *kit.Kit.
TUI-specific extension context overlays
cmd/extension_context.go
buildInteractiveExtensionContext now composes from BaseContext and overlays TUI routing, async widget notifications, channel-backed prompts, model switching delegation, message rendering, themes, and skill injection.
ACP-specific extension context overlays
internal/acpserver/session.go
ACP session context is derived from BaseContext and overrides identifiers/model, sets Interactive=false, and provides ACP-safe no-op or headless behaviors for prompts, UI, and rendering.
Tool kind classification centralization
internal/extensions/toolkinds.go, internal/extensions/wrapper.go, pkg/kit/events.go
Introduces canonical tool-kind constants and ToolKindFor(); wrapper and kit event code delegate to that shared mapping.
Template and argument parsing alignment
internal/skills/templates.go, internal/prompts/template.go, pkg/kit/template_bridge.go
Standardizes {{ variable }} whitespace-tolerant grammar, centralizes argument tokenization via ParseCommandArgs, and delegates template parsing/rendering to skills.PromptTemplate.
Model switching and usage tracking refactoring
internal/ui/factory.go, internal/ui/model.go, cmd/extension_context.go, cmd/root.go
Centralizes model switching in switchModel(), adds UpdateUsageTrackerForModel() to refresh trackers, and updates startup/model callbacks to use the new APIs.
Root command startup code organization
cmd/root.go
Extracts validateModeFlags(), restorePersistedPreferences(), and applyProviderURLRouting() for clearer startup flow.
Message store API simplification
internal/app/messages.go, internal/app/messages_test.go
Removes NewMessageStore() and updates tests to use NewMessageStoreWithMessages(nil).
API surface cleanup and removal
internal/config/substitution.go, internal/config/substitution_test.go, internal/core/bash.go, internal/core/bash_test.go, internal/models/custom.go
Removes HasEnvVars(), ContextWithSudoPassword(), and LoadModelSettingsFromConfig() and updates callsites/tests accordingly.
Kit type simplification
pkg/kit/adapter.go, pkg/kit/kit.go
Aligns NewTreeManagerAdapter parameter and InitTreeSession return type to use *TreeManager.
MCP tool execution deduplication
internal/tools/mcp.go
Adds withOAuthRetry() and marshalToolResult() helpers to unify OAuth-retry and result marshaling across tool call paths.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • mark3labs/kit#24: Introduces and consolidates cmd/extension_context.go and internal/extbridge for extension/subagent event translation; overlaps at extension-context composition.
  • mark3labs/kit#42: Modifies internal/acpserver/session.go session wiring and per-session initialization; likely to touch the same session/context areas.
  • mark3labs/kit#54: Adds extension state primitives (SetState/GetState/DeleteState/ListState) that overlap with BaseContext state wiring.

Poem

🐰 I stitched a base for contexts wide and neat,
Commands and tools now march to one heartbeat,
OAuth known by a single name,
Templates tidy, parsing tame,
Extensions hum — the rabbit hops in beat.

🚥 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 change: refactoring to deduplicate cross-package logic and remove dead code. It aligns with the PR objectives.
Docstring Coverage ✅ Passed Docstring coverage is 86.67% which is sufficient. The required threshold is 80.00%.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 audit/fixes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/ui/model.go (1)

4202-4207: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle fallback thinking-level apply failures before persisting UI state.

At Line 4202, m.thinkingLevel is updated and preference persistence is queued even if m.setThinkingLevel(...) fails at Line 4204. That can leave UI/prefs claiming a level the runtime never accepted.

Suggested fix
-					m.thinkingLevel = string(fallback)
-					if m.setThinkingLevel != nil {
-						_ = m.setThinkingLevel(string(fallback))
-					}
-					go func() { _ = prefs.SaveThinkingLevelPreference(string(fallback)) }()
+					nextLevel := string(fallback)
+					if m.setThinkingLevel != nil {
+						if err := m.setThinkingLevel(nextLevel); err != nil {
+							m.printSystemMessage(fmt.Sprintf("Failed to apply thinking fallback %q: %v", nextLevel, err))
+						} else {
+							m.thinkingLevel = nextLevel
+							go func() { _ = prefs.SaveThinkingLevelPreference(nextLevel) }()
+						}
+					} else {
+						m.thinkingLevel = nextLevel
+						go func() { _ = prefs.SaveThinkingLevelPreference(nextLevel) }()
+					}
🤖 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/ui/model.go` around lines 4202 - 4207, The code updates
m.thinkingLevel and kicks off prefs.SaveThinkingLevelPreference even when
m.setThinkingLevel may fail; instead call m.setThinkingLevel(string(fallback))
first, check its error return, and only on success update m.thinkingLevel and
asynchronously persist via prefs.SaveThinkingLevelPreference; if
m.setThinkingLevel returns an error, do not change m.thinkingLevel or write
prefs (optionally surface the error to the UI). Ensure you reference and adjust
the calls around m.setThinkingLevel, m.thinkingLevel, and
prefs.SaveThinkingLevelPreference so persistence only occurs after a successful
set.
🤖 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/extbridge/context.go`:
- Around line 117-133: The exported TreeNode currently returns the internal
Children slice reference (in GetTreeNode calling kitInstance.GetTreeNode),
allowing extensions to mutate internal state; fix by making a deep copy of the
Children slice (and each child element as needed) before assigning to
extensions.TreeNode.Children so the returned node has its own backing slice;
apply the same deep-copy approach to the other similar exporter that returns
Children (lines 134-150) so neither GetTreeNode nor the other exporter exposes
internal slice backing.

In `@internal/tools/mcp.go`:
- Around line 739-747: marshalToolResult currently dereferences result.IsError
without checking for nil; add a nil guard at the start of marshalToolResult to
avoid panic by returning a clear error when the incoming *mcp.CallToolResult is
nil. Specifically, in marshalToolResult check if result == nil and return nil,
fmt.Errorf("nil *mcp.CallToolResult") (or similar), then proceed to json.Marshal
and build the MCPToolResult (setting Content and IsError) only after the nil
check.

In `@pkg/kit/events.go`:
- Around line 109-110: Update the exported godoc for the ToolKind* constants to
remove the internal package name "internal/extensions" and instead use a generic
description (e.g., "the canonical classification is defined in the internal
implementation; these constants re-export that classification so SDK and
extension events agree"). Edit the comment immediately above the ToolKind*
constants (referencing the ToolKind* symbol names) to avoid mentioning internal
package paths and keep the intent: that these constants mirror the
canonical/internal classification without naming the internal package.

In `@pkg/kit/template_bridge.go`:
- Around line 18-19: The godoc for the exported ParseTemplate function currently
references an internal package path ("internal/skills"); remove that internal
package reference and rephrase the comment to avoid internal paths (e.g.,
"shared with skill prompt templates" or "shared with the project's skill prompt
templates") so the comment remains informative without mentioning internal
packages—update the comment above ParseTemplate in template_bridge.go
accordingly.

---

Outside diff comments:
In `@internal/ui/model.go`:
- Around line 4202-4207: The code updates m.thinkingLevel and kicks off
prefs.SaveThinkingLevelPreference even when m.setThinkingLevel may fail; instead
call m.setThinkingLevel(string(fallback)) first, check its error return, and
only on success update m.thinkingLevel and asynchronously persist via
prefs.SaveThinkingLevelPreference; if m.setThinkingLevel returns an error, do
not change m.thinkingLevel or write prefs (optionally surface the error to the
UI). Ensure you reference and adjust the calls around m.setThinkingLevel,
m.thinkingLevel, and prefs.SaveThinkingLevelPreference so persistence only
occurs after a successful set.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 15dce816-9f39-4a0b-900f-19d8332dfd89

📥 Commits

Reviewing files that changed from the base of the PR and between ef072f6 and 086d933.

📒 Files selected for processing (25)
  • cmd/extension_context.go
  • cmd/root.go
  • internal/acpserver/session.go
  • internal/app/messages.go
  • internal/app/messages_test.go
  • internal/auth/credentials.go
  • internal/config/substitution.go
  • internal/config/substitution_test.go
  • internal/core/bash.go
  • internal/core/bash_test.go
  • internal/extbridge/context.go
  • internal/extensions/toolkinds.go
  • internal/extensions/wrapper.go
  • internal/models/custom.go
  • internal/models/providers.go
  • internal/prompts/template.go
  • internal/skills/templates.go
  • internal/tools/mcp.go
  • internal/ui/factory.go
  • internal/ui/model.go
  • pkg/kit/adapter.go
  • pkg/kit/events.go
  • pkg/kit/extensions_bridge.go
  • pkg/kit/kit.go
  • pkg/kit/template_bridge.go
💤 Files with no reviewable changes (5)
  • internal/config/substitution_test.go
  • internal/core/bash.go
  • internal/config/substitution.go
  • internal/app/messages.go
  • internal/models/custom.go

Comment thread internal/extbridge/context.go
Comment thread internal/tools/mcp.go
Comment thread pkg/kit/events.go Outdated
Comment thread pkg/kit/template_bridge.go Outdated
- pkg/kit: remove internal package paths from exported godoc on
  ParseTemplate and the ToolKind* constants (SDK doc surface must not
  reference internal packages)
- internal/tools: guard marshalToolResult against a nil CallToolResult
  (json.Marshal(nil) succeeds as 'null', then result.IsError panics if
  a client returns nil result with nil error)

Skipped the TreeNode Children deep-copy suggestion: the slice already
comes from TreeManager.GetChildren which returns a fresh copy per call
into a throwaway intermediate, so no internal state is exposed.
@ezynda3 ezynda3 merged commit e8e99b1 into master Jun 11, 2026
3 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.

1 participant