fix(cli): suppress OpenSkills fallback when Tier-1 already has the skill#118
Conversation
The fallback hint introduced in #116 fired whenever no install happened in this run, including the re-run case where a Tier-1 client was detected and already had the skill. That produced a self-contradicting output: "claude-code skipped already installed" followed by "No Tier-1 client received the skill." Extract the gate into a pure `shouldPrintOpenSkillsFallback(results)` predicate that returns true only when no Tier-1 client has the skill — i.e., every non-advisory result is `skipped: not detected`. Treat `skipped: already installed`, fresh installs, and would-install (`--dry-run`) as "Tier-1 received the skill" so the hint stays quiet on re-runs. Test the predicate directly against InstallResult vectors. Cover: no detection, advisory-only, already-installed re-run, fresh install, dry-run. Closes the CodeRabbit feedback on #116. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
WalkthroughThis PR extracts the logic for determining when to print the OpenSkills fallback message into a reusable, exported predicate function. The new ChangesOpenSkills Fallback Predicate
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/cli/src/init.ts (1)
273-276: ⚡ Quick winDecouple fallback gating from user-facing reason text.
Line 275 keys behavior off the literal
'not detected'; wording changes can silently break fallback decisions. Prefer a shared internal reason constant/code for machine checks.Proposed refactor
+const REASON_NOT_DETECTED = 'not detected' as const + export function runInit( opts: InitOptions, clients: ClientSpec[], sourceSkill: string | null, fs: FsLike = realFs ): { results: InstallResult[]; sourceMissing: boolean } { @@ if (!fs.existsSync(c.detectDir())) { - results.push({ client: c.id, status: 'skipped', reason: 'not detected' }) + results.push({ client: c.id, status: 'skipped', reason: REASON_NOT_DETECTED }) continue } @@ export function shouldPrintOpenSkillsFallback(results: InstallResult[]): boolean { const hasTier1ReceivedSkill = results.some( - (r) => r.status !== 'advisory' && !(r.status === 'skipped' && r.reason === 'not detected') + (r) => + r.status !== 'advisory' && + !(r.status === 'skipped' && r.reason === REASON_NOT_DETECTED) ) return !hasTier1ReceivedSkill } @@ const detectedCount = results.filter( - (r) => !(r.status === 'skipped' && r.reason === 'not detected') + (r) => !(r.status === 'skipped' && r.reason === REASON_NOT_DETECTED) ).length🤖 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 `@packages/cli/src/init.ts` around lines 273 - 276, The fallback gating in shouldPrintOpenSkillsFallback uses the literal string 'not detected' from InstallResult.reason which couples logic to user-facing text; introduce a machine-only enum or constant (e.g., InternalSkipReason { NotDetected = 'NOT_DETECTED' }) and update shouldPrintOpenSkillsFallback to check r.reason === InternalSkipReason.NotDetected (or the new constant) instead of the literal, and then change all code sites that set InstallResult.reason for machine decisions (e.g., where skips are created) to assign the new internal code while keeping or mapping to the existing user-facing message for display.
🤖 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.
Nitpick comments:
In `@packages/cli/src/init.ts`:
- Around line 273-276: The fallback gating in shouldPrintOpenSkillsFallback uses
the literal string 'not detected' from InstallResult.reason which couples logic
to user-facing text; introduce a machine-only enum or constant (e.g.,
InternalSkipReason { NotDetected = 'NOT_DETECTED' }) and update
shouldPrintOpenSkillsFallback to check r.reason ===
InternalSkipReason.NotDetected (or the new constant) instead of the literal, and
then change all code sites that set InstallResult.reason for machine decisions
(e.g., where skips are created) to assign the new internal code while keeping or
mapping to the existing user-facing message for display.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: ed71e01a-ccf3-4239-a13d-fda17ed4153c
📒 Files selected for processing (2)
packages/cli/__tests__/init.test.tspackages/cli/src/init.ts
…test - @openhop/server: 0.1.0-beta.2 → 0.1.0 - @openhop/web: 0.1.0-beta.4 → 0.1.0 - openhop CLI: 0.1.0-beta.6 → 0.1.0 + bump pinned @openhop/server, @openhop/web deps + hardcoded --version publish.yml: drop `--tag beta` from all three publish steps. With no --tag, `npm publish` sets the `latest` dist-tag — so npmjs.com pages and `npm install <pkg>` (no @beta suffix) automatically reflect the new version on every successful publish. No more manual `npm dist-tag add latest` step. Bundles 13 PRs since v0.1.0-beta.6: Web (heavy): - #106 sprite URLs use Vite BASE_URL (Pages 404 fix) - #107 example flow cards on empty state - #108 sidebar + carrot-click highlight for string-data steps - #109 all 5 examples grouped under examples/ + first-visit autoload - #112 per-step auto-zoom + playback speed button - #113 collapse FLOWS / INSPECT by default on mobile - #114 lazy editor + vendor-split chunks + meta description - #115 filter codemirror from <modulepreload> - #117 memo FlowNode + GPU-layer sprites for smoother pan/zoom CLI: - #116 OpenSkills fallback hint when no Tier-1 client gets the skill - #118 suppress the hint when Tier-1 already has the skill Server: untouched code; bumped to keep the 0.1.0 set consistent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…test (#119) - @openhop/server: 0.1.0-beta.2 → 0.1.0 - @openhop/web: 0.1.0-beta.4 → 0.1.0 - openhop CLI: 0.1.0-beta.6 → 0.1.0 + bump pinned @openhop/server, @openhop/web deps + hardcoded --version publish.yml: drop `--tag beta` from all three publish steps. With no --tag, `npm publish` sets the `latest` dist-tag — so npmjs.com pages and `npm install <pkg>` (no @beta suffix) automatically reflect the new version on every successful publish. No more manual `npm dist-tag add latest` step. Bundles 13 PRs since v0.1.0-beta.6: Web (heavy): - #106 sprite URLs use Vite BASE_URL (Pages 404 fix) - #107 example flow cards on empty state - #108 sidebar + carrot-click highlight for string-data steps - #109 all 5 examples grouped under examples/ + first-visit autoload - #112 per-step auto-zoom + playback speed button - #113 collapse FLOWS / INSPECT by default on mobile - #114 lazy editor + vendor-split chunks + meta description - #115 filter codemirror from <modulepreload> - #117 memo FlowNode + GPU-layer sprites for smoother pan/zoom CLI: - #116 OpenSkills fallback hint when no Tier-1 client gets the skill - #118 suppress the hint when Tier-1 already has the skill Server: untouched code; bumped to keep the 0.1.0 set consistent. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
shouldPrintOpenSkillsFallback(results)predicate; 5 new direct unit tests cover the matrix.Why
With the merged gate
installed === 0 && wouldInstall === 0, a user re-runninginitafter a prior successful install saw self-contradicting output:The skill is on their machine — just not from this invocation.
Behavior matrix
EXIT_CONFLICTbefore this check)Test plan
npx vitest run __tests__/init.test.ts→ 25/25 pass (20 prior + 5 new on the predicate).$HOME/.claude/skills/openhop/→claude-code skipped (already installed)and no fallback line.$HOME→ fallback line still printed; exit 4.🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
Bug Fixes
initcommand to more accurately determine when to display the OpenSkills fallback hint, ensuring it only appears when no Tier-1 client receives a skill installation.Tests