Repository navigation
fix(cli): fix various issues found during bugbash - #112
Conversation
The createFlag MCP call was sending variants without the required `value` field, causing the API to reject with "does not exist in schema". Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Prevents tests from crating real `mcp_preference` file
The MCP server's resolveFlag tool requires clientName, but the CLI treated it as optional, causing a server-side NPE when omitted. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ual-testing-bug-bash
The defaults callback in resolveInput threw before file data could be merged, so --from-file alone always failed. Move validation after resolveInput so the error only fires when the final schema is empty. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
resolveBin now returns shell: true when no .js shim is found on Windows, letting cmd.exe resolve npm-installed .cmd wrappers. Moved isWindows to shared-kernel so all packages share one constant. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
`claude plugin update` requires the full plugin ID (e.g. `confidence@claude-plugins-official`), unlike install/uninstall which accept the short name. Resolve the ID from `claude plugin list` first so the update succeeds via CLI instead of silently falling back to download. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…absolute path Plugin skill file resolution broke when the CLI plugin registered as `confidence@claude-plugins-official` — command prompts inside the plugin couldn't resolve sibling skill files. Instead of depending on the plugin's internal resolution, skills are now always downloaded to `~/.config/confidence/skills/` and referenced via absolute path in prompts. Key changes: - installPlugin/updatePlugin always download skills to the shared dir, regardless of whether the CLI plugin install succeeds - referenceInstruction() produces an absolute file path; followInstruction() does the same for commands that should follow (not just reference) the skill - detectPlugin() returns boolean (is the CLI plugin installed?) - Removed PluginInstallationMethod, InstalledPlugin, skillsDir from IdeIntegration, and pluginInstallMethod from WizardSession - Moved getConfigDir() from auth to config module
filipmyllari
left a comment
There was a problem hiding this comment.
Review notes (AI-assisted), re-reviewed at the latest head. All findings are inline. I read all source changes in full and skimmed the tests; pnpm-lock.yaml was skipped. Nothing was tested on Windows.
| } | ||
|
|
||
| return { command, args: [...args] }; | ||
| return { command, args: [...args], shell: true }; |
There was a problem hiding this comment.
Bug: On Windows, any command without a .js shim now returns shell: true. Node joins the args into a cmd.exe line without quoting them, so multi-line prompts passed to claude --append-system-prompt and to codex/cursor agent (in */chat.ts and */onboarding.ts) get split on spaces and newlines, and chat launch breaks on Windows. Fix: quote each arg for cmd, or run the resolved .cmd through cmd.exe /d /s /c with proper escaping.
There was a problem hiding this comment.
Will remove shell and use cross-spawn instead.
| ): ChildProcess { | ||
| const resolved = resolveBin(command, args, { pathEnv: pathFromEnv(options?.env) }); | ||
| return cpSpawn(resolved.command, resolved.args, options ?? {}); | ||
| return cpSpawn(resolved.command, resolved.args, { ...options, shell: resolved.shell }); |
There was a problem hiding this comment.
Security: This is the same shell: true path as the bug in resolve-bin.ts. On Windows, cmd metacharacters (&, |, >, ^) in arguments now run as shell syntax. Prompts include the project path and user-supplied userPrompt, so a directory named like foo&calc runs a command. The spread also silently overrides a caller's explicit shell option. Fix: don't use shell: true with untrusted args; escape them or call cmd.exe explicitly.
There was a problem hiding this comment.
Will remove shell and use cross-spawn instead.
| } | ||
| await Promise.all([ | ||
| integration.updatePlugin(projectDir, scope).catch(() => cliFailed('update', ide)), | ||
| downloadSkills(getSkillsDir()), |
There was a problem hiding this comment.
Bug: downloadSkills is called without force, so it returns early when SKILL.md already exists. That means confidence plugin update never refreshes skills that are already downloaded. Fix: pass true as before.
| if (method === 'download' || !scope || scope === 'project') { | ||
| await removeSkills(integration.skillsDir(projectDir)); | ||
| } | ||
| await removeSkills(getSkillsDir()); |
There was a problem hiding this comment.
Bug: The skills directory is now one global location under ~/.config/confidence shared by all IDEs. Uninstalling for one IDE (for example --ide cursor) still deletes all of it, which breaks migrate and warehouse setup for the other IDEs. scope is also ignored, so uninstalling at local scope wipes the global skills. Fix: only remove skills when no IDE still has the plugin, or stop removing them on per-IDE uninstall.
| await Promise.all([ | ||
| integration.installPlugin(projectDir, scope).catch(() => cliFailed('install', ide)), | ||
| downloadSkills(getSkillsDir()), |
There was a problem hiding this comment.
Bug: CLI install errors are swallowed into telemetry, and downloadSkills skips any non-OK fetch silently. So install reports success even when the CLI failed and every download returned 404/500. Going the other way, one network error in fetch rejects the whole Promise.all even when the CLI install worked. Fix: fail only when both paths fail, and report which path succeeded.
|
|
||
| skills_dirs=("$PROJECT_DIR/.claude/skills" "$PROJECT_DIR/.cursor/skills" "$PROJECT_DIR/.agents/skills") | ||
| skills_dirs=("$config_dir/skills" "$PROJECT_DIR/.claude/skills" "$PROJECT_DIR/.cursor/skills" "$PROJECT_DIR/.agents/skills") | ||
| confidence_skills=( |
There was a problem hiding this comment.
Minor: confidence_skills doesn't include explore-metric, onboard-confidence-dry-run or setup-session-recording, which are now in SKILL_NAMES, so the clean script leaves them behind.
| ); | ||
| } | ||
|
|
||
| function readProjectMcpServers(projectDir: string): Record<string, McpServerEntry> { |
There was a problem hiding this comment.
Minor: reduce with an object spread on each iteration is O(n²) over projects in ~/.claude.json. Use Object.fromEntries. Also, resolveGitRoot (line 37) runs git synchronously on every detect, even when the direct key already matches. Compute it lazily.
| } | ||
|
|
||
| async function resolvePluginId(projectDir: string): Promise<string> { | ||
| const { stdout } = await execFile('claude', ['plugin', 'list', '--json'], { cwd: projectDir }); |
There was a problem hiding this comment.
Minor: resolvePluginId takes the first confidence@* entry and ignores enabled and the project path. With several marketplaces or scopes, it can update the wrong entry.
…ution The shell: true fallback passed untrusted args (multi-line prompts, project paths) through cmd.exe, breaking on spaces/newlines and exposing cmd metacharacter injection. cross-spawn handles .cmd resolution and argument escaping without a shell. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…pdate - downloadSkills uses Promise.allSettled and returns boolean so a single network error no longer rejects the whole install - install/update throw only when both CLI and download fail - updatePlugin passes force: true so existing skills get refreshed - uninstallPlugin only removes shared skills when no other IDE has the plugin installed Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use Object.fromEntries instead of O(n²) reduce with spread - Defer resolveGitRoot until direct key lookups miss - resolvePluginId reuses isAvailable to check enabled and projectPath Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
~/.config/confidence/skills/— decouples skill availability from plugin installation, fixing broken skill file resolution when the plugin registers asconfidence@claude-plugins-officialdetectPlugin()returnsboolean, removedPluginInstallationMethod,InstalledPlugin,skillsDirfromIdeIntegration,pluginInstallMethodfromWizardSessiongetConfigDir()fromauth/credentialstoconfig/modulereferenceInstruction()/followInstruction()— onboarding uses skills as methodology references, CLI commands (migrate, warehouse) follow the skill instructionsconfidence warehouse create|validate|setup) for managing data warehouse connectionsclaude plugin update,.cmdshim resolution on Windows, git root lookup for MCP status,--from-filewithout--fieldfor events, flag enablement schema complianceNotes for reviewers
removeSkills/downloadSkillsstill take askillsDirparameter — callers always passgetSkillsDir()now, so these could be simplified to use it internally. Left as-is to keep the diff focused; happy to clean up if you prefer.detectPluginon Cursor always returnsfalse— Cursor doesn't support CLI plugin management yet, so its implementation is a no-op stub. Previously it checked for downloaded skills in.cursor/skills/, which was the only way it could return a truthy value.Skills are downloaded on every install/update, even when the CLI plugin succeeds — this is intentional. The prompts reference the local files regardless of plugin status, so they must always exist. The download is cheap (12 small markdown files, skipped if already present).
PluginInstallationMethodremoved entirely — the'cli' | 'download'distinction no longer drives any behavior.detectPlugin()returningbooleanis sufficient for the uninstall path. Telemetry for CLI failures now usestrack()directly inplugin.tsinstead of propagating the method to callers.getConfigDir()moved fromauth/credentials/paths.tstoconfig/paths.ts— it was imported by config, integrations, and MCP modules, none of which are auth-related. The auth module now imports it from config. Added@configpath alias to core's tsconfig and vitest config.