Initial commit
This commit is contained in:
667
skills/nav-upgrade/SKILL.md
Normal file
667
skills/nav-upgrade/SKILL.md
Normal file
@@ -0,0 +1,667 @@
|
||||
---
|
||||
name: nav-upgrade
|
||||
description: Automates Navigator plugin updates. Detects current version, updates plugin, verifies installation, updates project CLAUDE.md, and validates new features. Auto-invoke when user mentions upgrading Navigator or getting new features.
|
||||
allowed-tools: Bash, Read, Write, Edit, TodoWrite
|
||||
version: 1.0.0
|
||||
---
|
||||
|
||||
# Navigator Upgrade Skill
|
||||
|
||||
Automate Navigator plugin updates with version detection, conflict resolution, and post-update validation.
|
||||
|
||||
## When to Invoke
|
||||
|
||||
Auto-invoke when user says:
|
||||
- "Update Navigator"
|
||||
- "Upgrade Navigator plugin"
|
||||
- "Get latest Navigator version"
|
||||
- "Update to Navigator v3.3.0"
|
||||
- "Install new Navigator features"
|
||||
- "Check for Navigator updates"
|
||||
|
||||
## What This Does
|
||||
|
||||
**5-Step Workflow**:
|
||||
1. **Version Detection**: Check current Navigator version vs latest
|
||||
2. **Plugin Update**: Execute `/plugin update navigator`
|
||||
3. **Verification**: Confirm update succeeded
|
||||
4. **CLAUDE.md Update**: Update project configuration (via nav-update-claude)
|
||||
5. **Feature Discovery**: Show new features available
|
||||
|
||||
**Time Savings**: Manual update (10-15 min) → Automated (2 min)
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Navigator plugin installed
|
||||
- Project initialized with Navigator
|
||||
- Internet connection for plugin update
|
||||
|
||||
---
|
||||
|
||||
## Workflow Protocol
|
||||
|
||||
### Step 1: Version Detection
|
||||
|
||||
**Execute**: `version_detector.py`
|
||||
|
||||
**Check both stable and pre-release versions**:
|
||||
```bash
|
||||
# Current installed version
|
||||
grep '"version"' .claude-plugin/plugin.json
|
||||
|
||||
# Get all releases (including pre-releases)
|
||||
curl -s https://api.github.com/repos/alekspetrov/navigator/releases
|
||||
|
||||
# Parse:
|
||||
# - Latest stable (prerelease: false)
|
||||
# - Latest pre-release (prerelease: true)
|
||||
# - Compare with current version
|
||||
```
|
||||
|
||||
**Output scenarios**:
|
||||
|
||||
**Scenario 1: Stable update available**
|
||||
```json
|
||||
{
|
||||
"current_version": "4.0.0",
|
||||
"latest_stable": "4.2.0",
|
||||
"latest_prerelease": null,
|
||||
"recommendation": "update_to_stable"
|
||||
}
|
||||
```
|
||||
|
||||
**Scenario 2: Pre-release available (user on stable)**
|
||||
```json
|
||||
{
|
||||
"current_version": "4.0.0",
|
||||
"latest_stable": "4.0.0",
|
||||
"latest_prerelease": "4.3.0",
|
||||
"recommendation": "offer_prerelease_option"
|
||||
}
|
||||
```
|
||||
|
||||
**Present choice**:
|
||||
```
|
||||
✅ You're on the latest stable version (v4.0.0)
|
||||
|
||||
⚡ Experimental version available: v4.3.0
|
||||
|
||||
New in v4.3.0 (Experimental):
|
||||
• Multi-Claude agentic workflows
|
||||
• 30% success rate (use for simple features)
|
||||
• PM integration with ticket closing
|
||||
|
||||
Options:
|
||||
[1] Stay on stable v4.0.0 (recommended)
|
||||
[2] Try experimental v4.3.0 (early adopter)
|
||||
|
||||
Your choice [1-2]:
|
||||
```
|
||||
|
||||
**Scenario 3: Already on latest (stable or pre-release)**
|
||||
```
|
||||
✅ You're on v4.3.0 (latest experimental)
|
||||
|
||||
Latest stable: v4.0.0
|
||||
Status: You're ahead of stable (testing experimental features)
|
||||
|
||||
New features in your version:
|
||||
- Multi-Claude workflows
|
||||
- Task agents in sub-Claude phases
|
||||
```
|
||||
|
||||
Skip to Step 5 (Feature Discovery).
|
||||
|
||||
**Scenario 4: On pre-release, newer stable available**
|
||||
```
|
||||
⚠️ You're on v4.3.0 (experimental)
|
||||
Latest stable: v4.5.0
|
||||
|
||||
Recommendation: Update to stable v4.5.0
|
||||
Experimental features from v4.3.0 are now stable.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 2: Plugin Update
|
||||
|
||||
**Scenario-based update strategy**:
|
||||
|
||||
#### Scenario 2: Pre-release Available (User on Stable)
|
||||
|
||||
When pre-release detected, present choice using AskUserQuestion tool:
|
||||
|
||||
```markdown
|
||||
✅ You're on latest stable version (v4.0.0)
|
||||
|
||||
⚡ Experimental version available: v4.3.0
|
||||
|
||||
New in v4.3.0 (Experimental):
|
||||
• Multi-Claude agentic workflows
|
||||
• 30% success rate (use for simple features)
|
||||
• PM integration with ticket closing
|
||||
|
||||
**Question**: Which version would you like?
|
||||
|
||||
**Options**:
|
||||
[1] **Stay on stable v4.0.0** (recommended)
|
||||
- Production-ready
|
||||
- No experimental features
|
||||
- Most reliable
|
||||
|
||||
[2] **Try experimental v4.3.0** (early adopter)
|
||||
- Multi-Claude workflows
|
||||
- Latest features
|
||||
- 30% completion rate
|
||||
- Help test new functionality
|
||||
|
||||
Your choice?
|
||||
```
|
||||
|
||||
**If user chooses [1] (Stay stable)**:
|
||||
```
|
||||
✓ Staying on v4.0.0 (latest stable)
|
||||
|
||||
No action needed. Run nav-upgrade again when you're ready to try experimental features.
|
||||
```
|
||||
|
||||
**If user chooses [2] (Try experimental)**:
|
||||
```bash
|
||||
# Uninstall current version
|
||||
/plugin uninstall navigator
|
||||
|
||||
# Add marketplace (if not already added)
|
||||
/plugin marketplace add alekspetrov/navigator
|
||||
|
||||
# Install specific pre-release version
|
||||
# Note: /plugin update only fetches stable, must install specific version
|
||||
git clone https://github.com/alekspetrov/navigator.git /tmp/navigator-v4.3.0
|
||||
cd /tmp/navigator-v4.3.0
|
||||
git checkout v4.3.0
|
||||
|
||||
# Install from local checkout
|
||||
/plugin install /tmp/navigator-v4.3.0
|
||||
```
|
||||
|
||||
**Then verify installation**:
|
||||
```bash
|
||||
/plugin list | grep navigator
|
||||
# Should show: navigator (v4.3.0)
|
||||
```
|
||||
|
||||
#### Scenario 1: Stable Update Available
|
||||
|
||||
**Execute**: `/plugin update navigator`
|
||||
|
||||
**Monitor output**:
|
||||
```
|
||||
Updating navigator...
|
||||
✅ Navigator updated to v4.2.0
|
||||
```
|
||||
|
||||
**If update fails**:
|
||||
```
|
||||
❌ Update failed: [error message]
|
||||
|
||||
Troubleshooting:
|
||||
1. Restart Claude Code
|
||||
2. Try: /plugin uninstall navigator && /plugin install navigator
|
||||
3. Check internet connection
|
||||
4. Report issue: https://github.com/alekspetrov/navigator/issues
|
||||
```
|
||||
|
||||
**Automatic retry** (once):
|
||||
If update fails, try uninstall/reinstall automatically:
|
||||
```bash
|
||||
/plugin uninstall navigator
|
||||
/plugin marketplace add alekspetrov/navigator
|
||||
/plugin install navigator
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 3: Verification
|
||||
|
||||
**Execute**: `plugin_verifier.py`
|
||||
|
||||
**Verify**:
|
||||
1. Plugin version matches latest
|
||||
2. New skills registered in plugin.json
|
||||
3. Skills are invokable
|
||||
|
||||
**Test new skills** (v3.3.0 example):
|
||||
```bash
|
||||
# Test that visual-regression skill exists
|
||||
ls ~/.config/claude/plugins/navigator/skills/visual-regression/SKILL.md 2>/dev/null || echo "Skill not found"
|
||||
```
|
||||
|
||||
**Output**:
|
||||
```
|
||||
✅ Update Verification
|
||||
|
||||
Version: v3.3.0 ✅
|
||||
New Skills Registered: visual-regression ✅
|
||||
Skills Invokable: ✅
|
||||
|
||||
Update successful!
|
||||
```
|
||||
|
||||
**If verification fails**:
|
||||
```
|
||||
⚠️ Update completed but verification failed
|
||||
|
||||
Issue: visual-regression skill not found
|
||||
Fix: Restart Claude Code to reload skills
|
||||
|
||||
After restarting, verify:
|
||||
"Set up visual regression for Button"
|
||||
```
|
||||
|
||||
Prompt user to restart Claude Code.
|
||||
|
||||
---
|
||||
|
||||
### Step 4: Update Project CLAUDE.md (Automatic)
|
||||
|
||||
**After plugin update, automatically invoke**: `nav-update-claude` skill
|
||||
|
||||
```
|
||||
🔄 Syncing project CLAUDE.md with updated plugin...
|
||||
|
||||
✓ Using template from GitHub (v4.3.0)
|
||||
✓ Extracted customizations
|
||||
✓ Generated updated CLAUDE.md
|
||||
```
|
||||
|
||||
**What happens automatically**:
|
||||
1. Detects new plugin version (e.g., v4.3.0)
|
||||
2. Fetches matching template from GitHub
|
||||
3. Preserves project customizations
|
||||
4. Updates CLAUDE.md in current project
|
||||
5. Shows diff for review
|
||||
|
||||
**Template sync benefits**:
|
||||
- ✅ CLAUDE.md always matches installed plugin version
|
||||
- ✅ No template drift (v4.0 templates with v4.3 plugin)
|
||||
- ✅ Pre-release templates accessible
|
||||
- ✅ Offline fallback to bundled templates
|
||||
|
||||
**User action required**:
|
||||
```
|
||||
Review changes and commit:
|
||||
|
||||
git add CLAUDE.md
|
||||
git commit -m "chore: update CLAUDE.md to Navigator v4.3.0"
|
||||
```
|
||||
|
||||
**See**: `nav-update-claude` skill for details.
|
||||
|
||||
---
|
||||
|
||||
### Step 5: Post-Upgrade Setup Check
|
||||
|
||||
**Check if new features require setup**:
|
||||
|
||||
```bash
|
||||
# Check for skills with setup requirements
|
||||
if [ -f "$NAVIGATOR_PATH/skills/product-design/setup.sh" ]; then
|
||||
# Check if venv exists
|
||||
if [ ! -d "$NAVIGATOR_PATH/skills/product-design/venv" ]; then
|
||||
echo "⚠️ product-design skill requires setup"
|
||||
NEEDS_SETUP=true
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
**If setup needed, show instructions**:
|
||||
|
||||
```markdown
|
||||
⚠️ New Feature Requires Setup
|
||||
|
||||
The product-design skill (v3.4.0+) requires Python dependencies:
|
||||
|
||||
**One-time setup** (30 seconds):
|
||||
```bash
|
||||
cd ~/.claude/plugins/marketplaces/jitd-marketplace/skills/product-design
|
||||
./setup.sh
|
||||
```
|
||||
|
||||
**What this installs**:
|
||||
- Python MCP SDK for direct Figma connection
|
||||
- 95% orchestration reduction
|
||||
- 92% token savings
|
||||
|
||||
**After setup, use**:
|
||||
"Review this Figma design: [URL]"
|
||||
```
|
||||
|
||||
**Record setup needed in TodoWrite** for tracking.
|
||||
|
||||
---
|
||||
|
||||
### Step 6: Feature Discovery
|
||||
|
||||
**Show new features** available in updated version.
|
||||
|
||||
**For v3.3.0 update**:
|
||||
````markdown
|
||||
🎉 Navigator v3.3.0 Update Complete!
|
||||
|
||||
## New Features Available
|
||||
|
||||
### visual-regression Skill (NEW)
|
||||
Set up Storybook + Chromatic in 5 minutes instead of 2-3 hours.
|
||||
|
||||
**Usage**:
|
||||
```
|
||||
"Set up visual regression for ProfileCard"
|
||||
"Add Chromatic to Button component"
|
||||
"Configure visual tests for Input, Card, Modal"
|
||||
```
|
||||
|
||||
**What it does**:
|
||||
✅ Generates Storybook stories with all variants
|
||||
✅ Configures Chromatic/Percy/BackstopJS
|
||||
✅ Creates CI workflows (GitHub Actions, GitLab CI)
|
||||
✅ Adds accessibility tests
|
||||
|
||||
**Complete Design Pipeline** (v3.2 + v3.3):
|
||||
1. "Review this design from Figma" (v3.2)
|
||||
2. Implement components
|
||||
3. "Set up visual regression" (v3.3 NEW)
|
||||
4. Automated visual testing in CI
|
||||
|
||||
### Updated Skills Count
|
||||
- **17 total skills** (was 16)
|
||||
- 10 core Navigator skills
|
||||
- 7 development skills
|
||||
|
||||
### Integration
|
||||
visual-regression integrates with product-design skill for complete design→code→testing workflow.
|
||||
|
||||
## Try It Now
|
||||
|
||||
If you have Storybook in this project:
|
||||
```
|
||||
"Set up visual regression for [ComponentName]"
|
||||
```
|
||||
|
||||
If you don't have Storybook:
|
||||
```bash
|
||||
npx storybook init
|
||||
```
|
||||
|
||||
Then:
|
||||
```
|
||||
"Set up visual regression for [ComponentName]"
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
- Release Notes: https://github.com/alekspetrov/navigator/releases/tag/v3.3.0
|
||||
- Skill Docs: skills/visual-regression/SKILL.md
|
||||
- Examples: skills/visual-regression/examples/
|
||||
- SOP: .agent/sops/testing/visual-regression-setup.md (created in your project)
|
||||
````
|
||||
|
||||
---
|
||||
|
||||
## Predefined Functions
|
||||
|
||||
### functions/version_detector.py
|
||||
|
||||
**Purpose**: Detect current and latest Navigator versions
|
||||
|
||||
**Usage**:
|
||||
```bash
|
||||
python3 functions/version_detector.py
|
||||
```
|
||||
|
||||
**Output**:
|
||||
```json
|
||||
{
|
||||
"current_version": "3.2.0",
|
||||
"latest_version": "3.3.0",
|
||||
"update_available": true,
|
||||
"release_url": "https://github.com/alekspetrov/navigator/releases/tag/v3.3.0",
|
||||
"changes": {
|
||||
"new_skills": ["visual-regression"],
|
||||
"updated_skills": ["product-design"],
|
||||
"new_features": ["Multi-tool VR support", "CI workflows"],
|
||||
"breaking_changes": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### functions/plugin_updater.py
|
||||
|
||||
**Purpose**: Execute plugin update with retry logic
|
||||
|
||||
**Usage**:
|
||||
```bash
|
||||
python3 functions/plugin_updater.py --target-version 3.3.0
|
||||
```
|
||||
|
||||
**Actions**:
|
||||
1. Execute `/plugin update navigator`
|
||||
2. If fails, retry with uninstall/reinstall
|
||||
3. Verify update succeeded
|
||||
4. Return status
|
||||
|
||||
### functions/plugin_verifier.py
|
||||
|
||||
**Purpose**: Verify update completed successfully
|
||||
|
||||
**Usage**:
|
||||
```bash
|
||||
python3 functions/plugin_verifier.py --expected-version 3.3.0
|
||||
```
|
||||
|
||||
**Checks**:
|
||||
- Plugin version matches expected
|
||||
- New skills exist in filesystem
|
||||
- Skills registered in plugin.json
|
||||
- Skills are invokable (test invocation)
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Update Failed: Network Error
|
||||
|
||||
```
|
||||
❌ Update failed: Could not connect to plugin marketplace
|
||||
|
||||
Fix:
|
||||
1. Check internet connection
|
||||
2. Try again in a few minutes
|
||||
3. Manual update: /plugin uninstall navigator && /plugin install navigator
|
||||
```
|
||||
|
||||
### Update Failed: Permission Denied
|
||||
|
||||
```
|
||||
❌ Update failed: Permission denied
|
||||
|
||||
Fix:
|
||||
1. Close Claude Code
|
||||
2. Check ~/.config/claude/plugins/ permissions
|
||||
3. Restart Claude Code
|
||||
4. Try update again
|
||||
```
|
||||
|
||||
### Verification Failed: Skills Not Found
|
||||
|
||||
```
|
||||
⚠️ Update completed but new skills not found
|
||||
|
||||
Fix:
|
||||
1. Restart Claude Code (required for skill reload)
|
||||
2. Verify: /plugin list
|
||||
3. Test: "Set up visual regression for Button"
|
||||
```
|
||||
|
||||
Automatically prompt user to restart.
|
||||
|
||||
### CLAUDE.md Update Conflicts
|
||||
|
||||
```
|
||||
⚠️ CLAUDE.md update has conflicts with your customizations
|
||||
|
||||
Options:
|
||||
[1] Keep my customizations (merge new features)
|
||||
[2] Use new template (lose customizations)
|
||||
[3] Show me the diff first
|
||||
|
||||
Reply with choice
|
||||
```
|
||||
|
||||
Let user decide how to handle conflicts.
|
||||
|
||||
---
|
||||
|
||||
## Upgrade Paths
|
||||
|
||||
### From v3.0.x to v3.3.0
|
||||
|
||||
**Changes**:
|
||||
- +2 skills (nav-markers in v3.1, visual-regression in v3.3)
|
||||
- OpenTelemetry integration (v3.1)
|
||||
- Product design skill (v3.2)
|
||||
- Visual regression skill (v3.3)
|
||||
|
||||
**Breaking changes**: None (fully backward compatible)
|
||||
|
||||
### From v3.1.x to v3.3.0
|
||||
|
||||
**Changes**:
|
||||
- Product design skill (v3.2)
|
||||
- Visual regression skill (v3.3)
|
||||
- Updated skills count (17 total)
|
||||
|
||||
**Breaking changes**: None
|
||||
|
||||
### From v3.2.x to v3.3.0
|
||||
|
||||
**Changes**:
|
||||
- Visual regression skill
|
||||
- Integration with product-design workflow
|
||||
- Updated skills count (17 total)
|
||||
|
||||
**Breaking changes**: None
|
||||
|
||||
---
|
||||
|
||||
## Post-Update Checklist
|
||||
|
||||
After upgrade, verify:
|
||||
|
||||
- ✅ `/plugin list` shows new version
|
||||
- ✅ CLAUDE.md updated with new patterns
|
||||
- ✅ New skills auto-invoke on natural language
|
||||
- ✅ Existing skills still work
|
||||
- ✅ No conflicts in project configuration
|
||||
|
||||
**If all checked**: Update successful!
|
||||
|
||||
---
|
||||
|
||||
## Rollback
|
||||
|
||||
If update causes issues:
|
||||
|
||||
```
|
||||
"Rollback Navigator to v3.2.0"
|
||||
```
|
||||
|
||||
This will:
|
||||
1. Uninstall current version
|
||||
2. Install specific version from marketplace
|
||||
3. Update CLAUDE.md to match
|
||||
4. Verify rollback succeeded
|
||||
|
||||
---
|
||||
|
||||
## Integration Points
|
||||
|
||||
### With nav-update-claude
|
||||
|
||||
After plugin update, automatically invokes `nav-update-claude` to sync project configuration.
|
||||
|
||||
### With nav-start
|
||||
|
||||
After update, `nav-start` shows new features available in session statistics.
|
||||
|
||||
### With nav-init
|
||||
|
||||
If upgrading before project initialization, suggests running `nav-init` with latest features.
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
### Example 1: Simple Update
|
||||
|
||||
```
|
||||
User: "Update Navigator"
|
||||
|
||||
→ Detects: v3.2.0 → v3.3.0 available
|
||||
→ Updates plugin
|
||||
→ Updates CLAUDE.md
|
||||
→ Shows: "visual-regression skill now available"
|
||||
→ Suggests: "Set up visual regression for [Component]"
|
||||
```
|
||||
|
||||
### Example 2: Already on Latest
|
||||
|
||||
```
|
||||
User: "Update Navigator"
|
||||
|
||||
→ Detects: Already on v3.3.0
|
||||
→ Shows new features available
|
||||
→ Suggests trying visual-regression if not used yet
|
||||
```
|
||||
|
||||
### Example 3: Update with Restart Required
|
||||
|
||||
```
|
||||
User: "Update Navigator"
|
||||
|
||||
→ Updates plugin
|
||||
→ Verification: Skills not found (needs restart)
|
||||
→ Prompts: "Please restart Claude Code to complete update"
|
||||
→ After restart: Verification succeeds
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Update regularly**: Check for updates monthly
|
||||
2. **Read release notes**: Understand new features before using
|
||||
3. **Test new skills**: Try new features in test project first
|
||||
4. **Report issues**: File GitHub issues for update problems
|
||||
5. **Backup CLAUDE.md**: Keep backup before update (auto-created)
|
||||
|
||||
---
|
||||
|
||||
## Version History
|
||||
|
||||
- **v1.0.0**: Initial nav-upgrade skill (Navigator v3.3.1)
|
||||
|
||||
---
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- Auto-update check on `nav-start` (opt-in)
|
||||
- Changelog display in CLI
|
||||
- Update notifications for major versions
|
||||
- Automated migration scripts for breaking changes
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2025-10-21
|
||||
**Skill Type**: Core Navigator
|
||||
**Auto-Invocation**: Yes
|
||||
227
skills/nav-upgrade/functions/plugin_updater.py
Normal file
227
skills/nav-upgrade/functions/plugin_updater.py
Normal file
@@ -0,0 +1,227 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Navigator Plugin Updater
|
||||
|
||||
Executes plugin update with retry logic and verification.
|
||||
|
||||
Usage:
|
||||
python plugin_updater.py [--target-version VERSION]
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from typing import Dict
|
||||
|
||||
|
||||
def update_plugin_via_claude() -> Dict:
|
||||
"""
|
||||
Execute /plugin update navigator command.
|
||||
|
||||
Returns:
|
||||
Dict with success status and output
|
||||
"""
|
||||
try:
|
||||
# Execute update command
|
||||
result = subprocess.run(
|
||||
['claude', 'plugin', 'update', 'navigator'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=60
|
||||
)
|
||||
|
||||
success = result.returncode == 0
|
||||
|
||||
return {
|
||||
'success': success,
|
||||
'output': result.stdout,
|
||||
'error': result.stderr,
|
||||
'method': 'update'
|
||||
}
|
||||
except subprocess.TimeoutExpired:
|
||||
return {
|
||||
'success': False,
|
||||
'error': 'Update timed out after 60 seconds',
|
||||
'method': 'update'
|
||||
}
|
||||
except FileNotFoundError:
|
||||
return {
|
||||
'success': False,
|
||||
'error': 'claude command not found. Is Claude Code installed?',
|
||||
'method': 'update'
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
'success': False,
|
||||
'error': str(e),
|
||||
'method': 'update'
|
||||
}
|
||||
|
||||
|
||||
def reinstall_plugin() -> Dict:
|
||||
"""
|
||||
Uninstall and reinstall Navigator plugin.
|
||||
|
||||
Returns:
|
||||
Dict with success status
|
||||
"""
|
||||
try:
|
||||
# Uninstall
|
||||
uninstall_result = subprocess.run(
|
||||
['claude', 'plugin', 'uninstall', 'navigator'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if uninstall_result.returncode != 0:
|
||||
return {
|
||||
'success': False,
|
||||
'error': f'Uninstall failed: {uninstall_result.stderr}',
|
||||
'method': 'reinstall'
|
||||
}
|
||||
|
||||
# Wait a moment
|
||||
time.sleep(2)
|
||||
|
||||
# Add from marketplace
|
||||
add_result = subprocess.run(
|
||||
['claude', 'plugin', 'marketplace', 'add', 'alekspetrov/navigator'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if add_result.returncode != 0:
|
||||
return {
|
||||
'success': False,
|
||||
'error': f'Marketplace add failed: {add_result.stderr}',
|
||||
'method': 'reinstall'
|
||||
}
|
||||
|
||||
# Wait a moment
|
||||
time.sleep(2)
|
||||
|
||||
# Install
|
||||
install_result = subprocess.run(
|
||||
['claude', 'plugin', 'install', 'navigator'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=60
|
||||
)
|
||||
|
||||
success = install_result.returncode == 0
|
||||
|
||||
return {
|
||||
'success': success,
|
||||
'output': install_result.stdout,
|
||||
'error': install_result.stderr if not success else None,
|
||||
'method': 'reinstall'
|
||||
}
|
||||
except subprocess.TimeoutExpired:
|
||||
return {
|
||||
'success': False,
|
||||
'error': 'Reinstall timed out',
|
||||
'method': 'reinstall'
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
'success': False,
|
||||
'error': str(e),
|
||||
'method': 'reinstall'
|
||||
}
|
||||
|
||||
|
||||
def update_with_retry(target_version: str = None) -> Dict:
|
||||
"""
|
||||
Update Navigator plugin with automatic retry on failure.
|
||||
|
||||
Args:
|
||||
target_version: Optional specific version to install
|
||||
|
||||
Returns:
|
||||
Dict with final update status
|
||||
"""
|
||||
report = {
|
||||
'attempts': [],
|
||||
'final_success': False,
|
||||
'target_version': target_version
|
||||
}
|
||||
|
||||
# Attempt 1: Normal update
|
||||
print("Attempting plugin update...", file=sys.stderr)
|
||||
attempt1 = update_plugin_via_claude()
|
||||
report['attempts'].append(attempt1)
|
||||
|
||||
if attempt1['success']:
|
||||
report['final_success'] = True
|
||||
return report
|
||||
|
||||
# Attempt 2: Reinstall
|
||||
print("Update failed. Attempting reinstall...", file=sys.stderr)
|
||||
time.sleep(2)
|
||||
|
||||
attempt2 = reinstall_plugin()
|
||||
report['attempts'].append(attempt2)
|
||||
|
||||
if attempt2['success']:
|
||||
report['final_success'] = True
|
||||
return report
|
||||
|
||||
# Both failed
|
||||
return report
|
||||
|
||||
|
||||
def get_post_update_instructions(success: bool, method: str) -> str:
|
||||
"""Generate post-update instructions."""
|
||||
if success:
|
||||
return """
|
||||
✅ Update Successful
|
||||
|
||||
Next steps:
|
||||
1. Restart Claude Code to reload skills
|
||||
2. Verify version: /plugin list
|
||||
3. Update project CLAUDE.md: "Update my CLAUDE.md to latest Navigator version"
|
||||
4. Try new features (if any)
|
||||
"""
|
||||
else:
|
||||
return f"""
|
||||
❌ Update Failed (method: {method})
|
||||
|
||||
Troubleshooting:
|
||||
1. Restart Claude Code
|
||||
2. Try manual update:
|
||||
/plugin uninstall navigator
|
||||
/plugin marketplace add alekspetrov/navigator
|
||||
/plugin install navigator
|
||||
|
||||
3. Check internet connection
|
||||
4. Report issue: https://github.com/alekspetrov/navigator/issues
|
||||
"""
|
||||
|
||||
|
||||
def main():
|
||||
"""CLI entry point."""
|
||||
parser = argparse.ArgumentParser(description='Update Navigator plugin')
|
||||
parser.add_argument('--target-version', help='Target version to install', default=None)
|
||||
args = parser.parse_args()
|
||||
|
||||
# Run update with retry
|
||||
report = update_with_retry(args.target_version)
|
||||
|
||||
# Add instructions
|
||||
final_attempt = report['attempts'][-1] if report['attempts'] else {}
|
||||
method = final_attempt.get('method', 'unknown')
|
||||
report['instructions'] = get_post_update_instructions(report['final_success'], method)
|
||||
|
||||
# Output as JSON
|
||||
print(json.dumps(report, indent=2))
|
||||
|
||||
# Exit code
|
||||
sys.exit(0 if report['final_success'] else 1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
238
skills/nav-upgrade/functions/plugin_verifier.py
Normal file
238
skills/nav-upgrade/functions/plugin_verifier.py
Normal file
@@ -0,0 +1,238 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Navigator Plugin Verifier
|
||||
|
||||
Verifies that Navigator plugin update completed successfully.
|
||||
|
||||
Usage:
|
||||
python plugin_verifier.py --expected-version 3.3.0
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Dict, List
|
||||
|
||||
|
||||
def get_installed_version() -> str:
|
||||
"""
|
||||
Get installed Navigator version from /plugin list.
|
||||
|
||||
Returns:
|
||||
Version string or None
|
||||
"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['claude', 'plugin', 'list'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10
|
||||
)
|
||||
|
||||
for line in result.stdout.split('\n'):
|
||||
if 'navigator' in line.lower():
|
||||
match = re.search(r'v?(\d+\.\d+\.\d+)', line)
|
||||
if match:
|
||||
return match.group(1)
|
||||
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def find_plugin_directory() -> Path:
|
||||
"""
|
||||
Find Navigator plugin installation directory.
|
||||
|
||||
Returns:
|
||||
Path to plugin directory or None
|
||||
"""
|
||||
possible_paths = [
|
||||
Path.home() / '.config' / 'claude' / 'plugins' / 'navigator',
|
||||
Path.home() / '.claude' / 'plugins' / 'navigator',
|
||||
Path.home() / 'Library' / 'Application Support' / 'Claude' / 'plugins' / 'navigator',
|
||||
]
|
||||
|
||||
for path in possible_paths:
|
||||
if path.exists() and path.is_dir():
|
||||
return path
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def verify_skills_exist(plugin_dir: Path, expected_skills: List[str]) -> Dict:
|
||||
"""
|
||||
Verify that expected skills exist in plugin directory.
|
||||
|
||||
Args:
|
||||
plugin_dir: Path to plugin directory
|
||||
expected_skills: List of skill names to check
|
||||
|
||||
Returns:
|
||||
Dict with verification results
|
||||
"""
|
||||
skills_dir = plugin_dir / 'skills'
|
||||
|
||||
if not skills_dir.exists():
|
||||
return {
|
||||
'success': False,
|
||||
'error': 'Skills directory not found'
|
||||
}
|
||||
|
||||
results = {}
|
||||
for skill_name in expected_skills:
|
||||
skill_path = skills_dir / skill_name / 'SKILL.md'
|
||||
results[skill_name] = skill_path.exists()
|
||||
|
||||
all_exist = all(results.values())
|
||||
|
||||
return {
|
||||
'success': all_exist,
|
||||
'skills_checked': results,
|
||||
'missing_skills': [name for name, exists in results.items() if not exists]
|
||||
}
|
||||
|
||||
|
||||
def verify_plugin_json(plugin_dir: Path, expected_skills: List[str]) -> Dict:
|
||||
"""
|
||||
Verify that skills are registered in plugin.json.
|
||||
|
||||
Args:
|
||||
plugin_dir: Path to plugin directory
|
||||
expected_skills: List of skill names to check
|
||||
|
||||
Returns:
|
||||
Dict with verification results
|
||||
"""
|
||||
plugin_json_path = plugin_dir / '.claude-plugin' / 'plugin.json'
|
||||
|
||||
if not plugin_json_path.exists():
|
||||
return {
|
||||
'success': False,
|
||||
'error': 'plugin.json not found'
|
||||
}
|
||||
|
||||
try:
|
||||
with open(plugin_json_path, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
registered_skills = data.get('skills', [])
|
||||
|
||||
# Check each expected skill
|
||||
results = {}
|
||||
for skill_name in expected_skills:
|
||||
skill_path = f'./skills/{skill_name}'
|
||||
results[skill_name] = skill_path in registered_skills
|
||||
|
||||
all_registered = all(results.values())
|
||||
|
||||
return {
|
||||
'success': all_registered,
|
||||
'skills_checked': results,
|
||||
'unregistered_skills': [name for name, registered in results.items() if not registered]
|
||||
}
|
||||
except (json.JSONDecodeError, FileNotFoundError) as e:
|
||||
return {
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
|
||||
def verify_update(expected_version: str, expected_new_skills: List[str] = None) -> Dict:
|
||||
"""
|
||||
Comprehensive verification of Navigator plugin update.
|
||||
|
||||
Args:
|
||||
expected_version: Expected version after update (e.g., "3.3.0")
|
||||
expected_new_skills: List of new skills expected in this version
|
||||
|
||||
Returns:
|
||||
Complete verification report
|
||||
"""
|
||||
report = {
|
||||
'expected_version': expected_version,
|
||||
'checks': {},
|
||||
'overall_success': False,
|
||||
'needs_restart': False
|
||||
}
|
||||
|
||||
# Check 1: Version matches
|
||||
installed_version = get_installed_version()
|
||||
report['checks']['version'] = {
|
||||
'expected': expected_version,
|
||||
'actual': installed_version,
|
||||
'success': installed_version == expected_version
|
||||
}
|
||||
|
||||
# Check 2: Plugin directory exists
|
||||
plugin_dir = find_plugin_directory()
|
||||
report['checks']['plugin_directory'] = {
|
||||
'success': plugin_dir is not None,
|
||||
'path': str(plugin_dir) if plugin_dir else None
|
||||
}
|
||||
|
||||
if not plugin_dir:
|
||||
report['recommendation'] = 'Plugin directory not found. Reinstall Navigator.'
|
||||
return report
|
||||
|
||||
# Check 3: New skills exist (if specified)
|
||||
if expected_new_skills:
|
||||
skills_check = verify_skills_exist(plugin_dir, expected_new_skills)
|
||||
report['checks']['skills_exist'] = skills_check
|
||||
|
||||
# Check 4: Skills registered in plugin.json
|
||||
registration_check = verify_plugin_json(plugin_dir, expected_new_skills)
|
||||
report['checks']['skills_registered'] = registration_check
|
||||
|
||||
# If skills exist but verification shows they're not accessible, needs restart
|
||||
if skills_check['success'] and not registration_check['success']:
|
||||
report['needs_restart'] = True
|
||||
|
||||
# Overall success
|
||||
all_checks_passed = all(
|
||||
check.get('success', False)
|
||||
for check in report['checks'].values()
|
||||
)
|
||||
|
||||
report['overall_success'] = all_checks_passed
|
||||
|
||||
# Generate recommendation
|
||||
if all_checks_passed:
|
||||
report['recommendation'] = 'Update verified successfully!'
|
||||
elif report['needs_restart']:
|
||||
report['recommendation'] = 'Update completed. Restart Claude Code to reload skills.'
|
||||
else:
|
||||
failed_checks = [name for name, check in report['checks'].items() if not check.get('success')]
|
||||
report['recommendation'] = f"Verification failed: {', '.join(failed_checks)}"
|
||||
|
||||
return report
|
||||
|
||||
|
||||
def main():
|
||||
"""CLI entry point."""
|
||||
parser = argparse.ArgumentParser(description='Verify Navigator plugin update')
|
||||
parser.add_argument('--expected-version', required=True, help='Expected version (e.g., 3.3.0)')
|
||||
parser.add_argument('--new-skills', nargs='*', help='New skills to verify', default=[])
|
||||
args = parser.parse_args()
|
||||
|
||||
# Run verification
|
||||
report = verify_update(args.expected_version, args.new_skills or None)
|
||||
|
||||
# Output as JSON
|
||||
print(json.dumps(report, indent=2))
|
||||
|
||||
# Exit code
|
||||
if report['overall_success']:
|
||||
sys.exit(0)
|
||||
elif report['needs_restart']:
|
||||
sys.exit(2) # Special exit code for restart needed
|
||||
else:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
266
skills/nav-upgrade/functions/version_detector.py
Normal file
266
skills/nav-upgrade/functions/version_detector.py
Normal file
@@ -0,0 +1,266 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Navigator Version Detector
|
||||
|
||||
Detects current Navigator version and checks for updates from GitHub releases.
|
||||
|
||||
Usage:
|
||||
python version_detector.py
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Dict, Optional
|
||||
from urllib import request
|
||||
|
||||
|
||||
def get_current_version() -> Optional[str]:
|
||||
"""
|
||||
Get currently installed Navigator version from /plugin list.
|
||||
|
||||
Returns:
|
||||
Version string (e.g., "3.3.0") or None if not found
|
||||
"""
|
||||
try:
|
||||
# Try to run claude plugin list command
|
||||
result = subprocess.run(
|
||||
['claude', 'plugin', 'list'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10
|
||||
)
|
||||
|
||||
# Parse output for navigator version
|
||||
for line in result.stdout.split('\n'):
|
||||
if 'navigator' in line.lower():
|
||||
# Extract version (e.g., "navigator (v3.3.0)" or "navigator (3.3.0)")
|
||||
match = re.search(r'v?(\d+\.\d+\.\d+)', line)
|
||||
if match:
|
||||
return match.group(1)
|
||||
|
||||
return None
|
||||
except (subprocess.TimeoutExpired, FileNotFoundError, subprocess.SubprocessError):
|
||||
return None
|
||||
|
||||
|
||||
def get_plugin_json_version() -> Optional[str]:
|
||||
"""
|
||||
Fallback: Get version from plugin.json in Navigator plugin directory.
|
||||
|
||||
Returns:
|
||||
Version string or None
|
||||
"""
|
||||
# Common plugin installation paths
|
||||
possible_paths = [
|
||||
Path.home() / '.config' / 'claude' / 'plugins' / 'navigator' / '.claude-plugin' / 'plugin.json',
|
||||
Path.home() / '.claude' / 'plugins' / 'navigator' / '.claude-plugin' / 'plugin.json',
|
||||
Path.home() / 'Library' / 'Application Support' / 'Claude' / 'plugins' / 'navigator' / '.claude-plugin' / 'plugin.json',
|
||||
]
|
||||
|
||||
for path in possible_paths:
|
||||
if path.exists():
|
||||
try:
|
||||
with open(path, 'r') as f:
|
||||
data = json.load(f)
|
||||
return data.get('version')
|
||||
except (json.JSONDecodeError, FileNotFoundError, PermissionError):
|
||||
continue
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_latest_version_from_github() -> Dict:
|
||||
"""
|
||||
Get latest Navigator version from GitHub releases API.
|
||||
|
||||
Returns:
|
||||
Dict with version, release_url, and changes
|
||||
"""
|
||||
try:
|
||||
url = 'https://api.github.com/repos/alekspetrov/navigator/releases/latest'
|
||||
|
||||
req = request.Request(url)
|
||||
req.add_header('User-Agent', 'Navigator-Version-Detector')
|
||||
|
||||
with request.urlopen(req, timeout=10) as response:
|
||||
data = json.load(response)
|
||||
|
||||
# Extract version from tag_name (e.g., "v3.3.0" → "3.3.0")
|
||||
tag_name = data.get('tag_name', '')
|
||||
version = tag_name.lstrip('v')
|
||||
|
||||
# Parse release notes for key changes
|
||||
body = data.get('body', '')
|
||||
changes = parse_release_notes(body)
|
||||
|
||||
return {
|
||||
'version': version,
|
||||
'release_url': data.get('html_url', ''),
|
||||
'release_date': data.get('published_at', '').split('T')[0],
|
||||
'changes': changes
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
'version': None,
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
|
||||
def parse_release_notes(body: str) -> Dict:
|
||||
"""
|
||||
Parse release notes to extract key changes.
|
||||
|
||||
Args:
|
||||
body: Release notes markdown
|
||||
|
||||
Returns:
|
||||
Dict with new_skills, updated_skills, new_features, breaking_changes
|
||||
"""
|
||||
changes = {
|
||||
'new_skills': [],
|
||||
'updated_skills': [],
|
||||
'new_features': [],
|
||||
'breaking_changes': []
|
||||
}
|
||||
|
||||
# Extract new skills
|
||||
skill_pattern = r'-\s+\*\*(\w+-[\w-]+)\*\*:.*\(NEW\)'
|
||||
for match in re.finditer(skill_pattern, body):
|
||||
changes['new_skills'].append(match.group(1))
|
||||
|
||||
# Extract features from "What's New" section
|
||||
features_section = re.search(r'##\s+.*What.*s New(.*?)(?=##|\Z)', body, re.DOTALL | re.IGNORECASE)
|
||||
if features_section:
|
||||
# Find bullet points
|
||||
for line in features_section.group(1).split('\n'):
|
||||
if line.strip().startswith('-') or line.strip().startswith('*'):
|
||||
feature = line.strip().lstrip('-*').strip()
|
||||
if feature and len(feature) < 100: # Reasonable feature description
|
||||
changes['new_features'].append(feature)
|
||||
|
||||
# Check for breaking changes
|
||||
if 'breaking change' in body.lower() or '⚠️' in body:
|
||||
breaking_section = re.search(r'##\s+.*Breaking.*Changes(.*?)(?=##|\Z)', body, re.DOTALL | re.IGNORECASE)
|
||||
if breaking_section:
|
||||
for line in breaking_section.group(1).split('\n'):
|
||||
if line.strip().startswith('-') or line.strip().startswith('*'):
|
||||
change = line.strip().lstrip('-*').strip()
|
||||
if change:
|
||||
changes['breaking_changes'].append(change)
|
||||
|
||||
return changes
|
||||
|
||||
|
||||
def compare_versions(current: str, latest: str) -> int:
|
||||
"""
|
||||
Compare two semantic versions.
|
||||
|
||||
Args:
|
||||
current: Current version (e.g., "3.2.0")
|
||||
latest: Latest version (e.g., "3.3.0")
|
||||
|
||||
Returns:
|
||||
-1 if current < latest (update available)
|
||||
0 if current == latest (up to date)
|
||||
1 if current > latest (ahead of latest, e.g., dev version)
|
||||
"""
|
||||
try:
|
||||
current_parts = [int(x) for x in current.split('.')]
|
||||
latest_parts = [int(x) for x in latest.split('.')]
|
||||
|
||||
# Pad to same length
|
||||
while len(current_parts) < len(latest_parts):
|
||||
current_parts.append(0)
|
||||
while len(latest_parts) < len(current_parts):
|
||||
latest_parts.append(0)
|
||||
|
||||
# Compare
|
||||
for c, l in zip(current_parts, latest_parts):
|
||||
if c < l:
|
||||
return -1
|
||||
elif c > l:
|
||||
return 1
|
||||
|
||||
return 0
|
||||
except (ValueError, AttributeError):
|
||||
return 0 # Can't compare, assume equal
|
||||
|
||||
|
||||
def detect_version() -> Dict:
|
||||
"""
|
||||
Detect current and latest Navigator versions.
|
||||
|
||||
Returns:
|
||||
Complete version detection report
|
||||
"""
|
||||
# Get current version
|
||||
current_version = get_current_version()
|
||||
|
||||
if not current_version:
|
||||
# Fallback to plugin.json
|
||||
current_version = get_plugin_json_version()
|
||||
|
||||
# Get latest version from GitHub
|
||||
latest_info = get_latest_version_from_github()
|
||||
latest_version = latest_info.get('version')
|
||||
|
||||
# Determine if update available
|
||||
update_available = False
|
||||
if current_version and latest_version:
|
||||
comparison = compare_versions(current_version, latest_version)
|
||||
update_available = (comparison == -1)
|
||||
|
||||
# Build report
|
||||
report = {
|
||||
'current_version': current_version,
|
||||
'latest_version': latest_version,
|
||||
'update_available': update_available,
|
||||
'release_url': latest_info.get('release_url', ''),
|
||||
'release_date': latest_info.get('release_date', ''),
|
||||
'changes': latest_info.get('changes', {}),
|
||||
'error': latest_info.get('error'),
|
||||
'recommendation': get_recommendation(current_version, latest_version, update_available)
|
||||
}
|
||||
|
||||
return report
|
||||
|
||||
|
||||
def get_recommendation(current: Optional[str], latest: Optional[str], update_available: bool) -> str:
|
||||
"""Generate recommendation based on version status."""
|
||||
if not current:
|
||||
return "Navigator not detected. Install: /plugin marketplace add alekspetrov/navigator && /plugin install navigator"
|
||||
|
||||
if not latest:
|
||||
return "Could not check for updates. Try again later or check GitHub releases manually."
|
||||
|
||||
if update_available:
|
||||
return f"Update recommended: v{current} → v{latest}. Run: /plugin update navigator"
|
||||
|
||||
return f"You're on the latest version (v{current}). No update needed."
|
||||
|
||||
|
||||
def main():
|
||||
"""CLI entry point."""
|
||||
report = detect_version()
|
||||
|
||||
# Output as JSON
|
||||
print(json.dumps(report, indent=2))
|
||||
|
||||
# Exit with code
|
||||
# 0 = up to date
|
||||
# 1 = update available
|
||||
# 2 = error
|
||||
if report.get('error'):
|
||||
sys.exit(2)
|
||||
elif report.get('update_available'):
|
||||
sys.exit(1)
|
||||
else:
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user