Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 17:51:59 +08:00
commit 38e80921c8
89 changed files with 20480 additions and 0 deletions

311
skills/nav-init/SKILL.md Normal file
View File

@@ -0,0 +1,311 @@
---
name: nav-init
description: Initialize Navigator documentation structure in a project. Auto-invokes when user says "Initialize Navigator", "Set up Navigator", "Create Navigator structure", or "Bootstrap Navigator".
allowed-tools: Write, Bash, Read, Glob
version: 1.0.0
auto-invoke: true
triggers:
- "initialize navigator"
- "init navigator"
- "set up navigator"
- "setup navigator"
- "create navigator structure"
- "bootstrap navigator"
- "start navigator project"
---
# Navigator Initialization Skill
## Purpose
Creates the Navigator documentation structure (`.agent/`) in a new project, copies templates, and sets up initial configuration.
## When This Skill Auto-Invokes
- "Initialize Navigator in this project"
- "Set up Navigator documentation structure"
- "Create .agent folder for Navigator"
- "Bootstrap Navigator for my project"
## What This Skill Does
1. **Checks if already initialized**: Prevents overwriting existing structure
2. **Creates `.agent/` directory structure**:
```
.agent/
├── DEVELOPMENT-README.md
├── .nav-config.json
├── tasks/
├── system/
├── sops/
│ ├── integrations/
│ ├── debugging/
│ ├── development/
│ └── deployment/
└── grafana/
├── docker-compose.yml
├── prometheus.yml
├── grafana-datasource.yml
├── grafana-dashboards.yml
├── navigator-dashboard.json
└── README.md
```
3. **Copies templates**: DEVELOPMENT-README.md, config, Grafana setup
4. **Auto-detects project info**: Name, tech stack (from package.json if available)
5. **Updates CLAUDE.md**: Adds Navigator-specific instructions to project
6. **Creates .gitignore entries**: Excludes temporary Navigator files
## Execution Steps
### 1. Check if Already Initialized
```bash
if [ -d ".agent" ]; then
echo "✅ Navigator already initialized in this project"
echo ""
echo "To start a session: 'Start my Navigator session'"
echo "To view documentation: Read .agent/DEVELOPMENT-README.md"
exit 0
fi
```
### 2. Detect Project Information
Read `package.json`, `pyproject.toml`, `go.mod`, `Cargo.toml`, or similar to extract:
- Project name
- Tech stack
- Dependencies
**Fallback**: Use current directory name if no config found.
### 3. Create Directory Structure
Use Write tool to create:
```
.agent/
.agent/tasks/
.agent/system/
.agent/sops/integrations/
.agent/sops/debugging/
.agent/sops/development/
.agent/sops/deployment/
.agent/grafana/
```
### 4. Copy Templates
Copy from plugin's `templates/` directory to `.agent/`:
**DEVELOPMENT-README.md**:
- Replace `${PROJECT_NAME}` with detected project name
- Replace `${TECH_STACK}` with detected stack
- Replace `${DATE}` with current date
**`.nav-config.json`**:
```json
{
"version": "4.5.0",
"project_name": "${PROJECT_NAME}",
"tech_stack": "${TECH_STACK}",
"project_management": "none",
"task_prefix": "TASK",
"team_chat": "none",
"auto_load_navigator": true,
"compact_strategy": "conservative"
}
```
**Grafana Setup**:
Copy all Grafana dashboard files to enable metrics visualization:
```bash
# Find plugin installation directory
PLUGIN_DIR="${HOME}/.claude/plugins/marketplaces/jitd-marketplace"
# Copy Grafana files if plugin has them
if [ -d "${PLUGIN_DIR}/.agent/grafana" ]; then
cp -r "${PLUGIN_DIR}/.agent/grafana/"* .agent/grafana/
echo "✓ Grafana dashboard installed"
else
echo "⚠️ Grafana files not found in plugin"
fi
```
Files copied:
- docker-compose.yml (Grafana + Prometheus stack)
- prometheus.yml (scrape config for Claude Code metrics)
- grafana-datasource.yml (Prometheus datasource config)
- grafana-dashboards.yml (dashboard provider config)
- navigator-dashboard.json (10-panel Navigator metrics dashboard)
- README.md (setup instructions)
### 5. Update Project CLAUDE.md
If `CLAUDE.md` exists:
- Append Navigator-specific sections
- Keep existing project customizations
If `CLAUDE.md` doesn't exist:
- Copy `templates/CLAUDE.md` to project root
- Customize with project info
### 6. Create .gitignore Entries
Add to `.gitignore` if not present:
```
# Navigator context markers
.context-markers/
# Navigator temporary files
.agent/.nav-temp/
```
### 7. Success Message
```
✅ Navigator Initialized Successfully!
Created structure:
📁 .agent/ Navigator documentation
📁 .agent/tasks/ Implementation plans
📁 .agent/system/ Architecture docs
📁 .agent/sops/ Standard procedures
📁 .agent/grafana/ Metrics dashboard
📄 .agent/.nav-config.json Configuration
📄 CLAUDE.md Updated with Navigator workflow
Next steps:
1. Start session: "Start my Navigator session"
2. Optional: Enable metrics - see .agent/sops/integrations/opentelemetry-setup.md
3. Optional: Launch Grafana - cd .agent/grafana && docker compose up -d
Documentation: Read .agent/DEVELOPMENT-README.md
```
## Error Handling
**If `.agent/` exists**:
- Don't overwrite
- Show message: "Already initialized"
**If templates not found**:
- Error: "Navigator plugin templates missing. Reinstall plugin."
**If no write permissions**:
- Error: "Cannot create .agent/ directory. Check permissions."
## Predefined Functions
### `project_detector.py`
```python
def detect_project_info(cwd: str) -> dict:
"""
Detect project name and tech stack from config files.
Checks (in order):
1. package.json (Node.js)
2. pyproject.toml (Python)
3. go.mod (Go)
4. Cargo.toml (Rust)
5. composer.json (PHP)
6. Gemfile (Ruby)
Returns:
{
"name": "project-name",
"tech_stack": "Next.js, TypeScript, Prisma",
"detected_from": "package.json"
}
"""
```
### `template_customizer.py`
```python
def customize_template(template_content: str, project_info: dict) -> str:
"""
Replace placeholders in template with project-specific values.
Placeholders:
- ${PROJECT_NAME}
- ${TECH_STACK}
- ${DATE}
- ${YEAR}
Returns customized template content.
"""
```
## Examples
### Example 1: New Next.js Project
**User says**: "Initialize Navigator in this project"
**Skill detects**:
- `package.json` exists
- Name: "my-saas-app"
- Dependencies: next, typescript, prisma
**Result**:
- `.agent/` created
- DEVELOPMENT-README.md shows: "Project: My SaaS App"
- DEVELOPMENT-README.md shows: "Tech Stack: Next.js, TypeScript, Prisma"
- .nav-config.json has project_name: "my-saas-app"
### Example 2: Python Project
**User says**: "Set up Navigator"
**Skill detects**:
- `pyproject.toml` exists
- Name: "ml-pipeline"
- Dependencies: fastapi, pydantic, sqlalchemy
**Result**:
- `.agent/` created
- Tech stack: "FastAPI, Pydantic, SQLAlchemy"
### Example 3: Already Initialized
**User says**: "Initialize Navigator"
**Skill checks**:
- `.agent/` directory exists
**Result**:
```
✅ Navigator already initialized in this project
To start a session: 'Start my Navigator session'
```
## Integration with Other Skills
**nav-start skill**:
- Checks for `.agent/DEVELOPMENT-README.md`
- If missing, suggests: "Initialize Navigator first"
**nav-task skill**:
- Creates tasks in `.agent/tasks/`
- Requires initialization
**nav-sop skill**:
- Creates SOPs in `.agent/sops/`
- Requires initialization
## Version History
- **1.0.0** (2025-01-20): Initial implementation
- Auto-detection of project info
- Template customization
- Grafana setup included
- Error handling for existing installations
## Notes
- This skill replaces the deleted `/nav:init` command from v2.x
- Templates are copied from plugin installation directory
- Project info detection is best-effort (falls back to directory name)
- Safe to run multiple times (won't overwrite existing structure)

View File

@@ -0,0 +1,289 @@
#!/usr/bin/env python3
"""
Project information detection for Navigator initialization.
Detects project name and tech stack from various config files.
"""
import json
import os
import re
from pathlib import Path
from typing import Dict, Optional
def detect_project_info(cwd: str = ".") -> Dict[str, str]:
"""
Detect project name and tech stack from config files.
Args:
cwd: Current working directory (default: ".")
Returns:
Dictionary with keys:
- name: Project name
- tech_stack: Comma-separated technologies
- detected_from: Source file used for detection
"""
cwd_path = Path(cwd).resolve()
# Try detection methods in order
detectors = [
_detect_from_package_json,
_detect_from_pyproject_toml,
_detect_from_go_mod,
_detect_from_cargo_toml,
_detect_from_composer_json,
_detect_from_gemfile,
]
for detector in detectors:
result = detector(cwd_path)
if result:
return result
# Fallback: use directory name
return {
"name": cwd_path.name,
"tech_stack": "Unknown",
"detected_from": "directory_name",
}
def _detect_from_package_json(cwd: Path) -> Optional[Dict[str, str]]:
"""Detect from package.json (Node.js/JavaScript)."""
package_json = cwd / "package.json"
if not package_json.exists():
return None
try:
with open(package_json) as f:
data = json.load(f)
name = data.get("name", cwd.name)
deps = {**data.get("dependencies", {}), **data.get("devDependencies", {})}
# Detect framework/stack
stack_parts = []
if "next" in deps:
stack_parts.append("Next.js")
elif "react" in deps:
stack_parts.append("React")
elif "vue" in deps:
stack_parts.append("Vue")
elif "angular" in deps:
stack_parts.append("Angular")
elif "svelte" in deps:
stack_parts.append("Svelte")
elif "express" in deps:
stack_parts.append("Express")
elif "fastify" in deps:
stack_parts.append("Fastify")
if "typescript" in deps:
stack_parts.append("TypeScript")
if "prisma" in deps:
stack_parts.append("Prisma")
elif "mongoose" in deps:
stack_parts.append("MongoDB")
elif "pg" in deps or "postgres" in deps:
stack_parts.append("PostgreSQL")
tech_stack = ", ".join(stack_parts) if stack_parts else "Node.js"
return {
"name": name,
"tech_stack": tech_stack,
"detected_from": "package.json",
}
except (json.JSONDecodeError, IOError):
return None
def _detect_from_pyproject_toml(cwd: Path) -> Optional[Dict[str, str]]:
"""Detect from pyproject.toml (Python)."""
pyproject = cwd / "pyproject.toml"
if not pyproject.exists():
return None
try:
content = pyproject.read_text()
# Extract name
name_match = re.search(r'name\s*=\s*["\']([^"\']+)["\']', content)
name = name_match.group(1) if name_match else cwd.name
# Detect framework/stack
stack_parts = []
if "fastapi" in content.lower():
stack_parts.append("FastAPI")
elif "django" in content.lower():
stack_parts.append("Django")
elif "flask" in content.lower():
stack_parts.append("Flask")
if "sqlalchemy" in content.lower():
stack_parts.append("SQLAlchemy")
if "pydantic" in content.lower():
stack_parts.append("Pydantic")
if "pytest" in content.lower():
stack_parts.append("Pytest")
tech_stack = ", ".join(stack_parts) if stack_parts else "Python"
return {
"name": name,
"tech_stack": tech_stack,
"detected_from": "pyproject.toml",
}
except IOError:
return None
def _detect_from_go_mod(cwd: Path) -> Optional[Dict[str, str]]:
"""Detect from go.mod (Go)."""
go_mod = cwd / "go.mod"
if not go_mod.exists():
return None
try:
content = go_mod.read_text()
# Extract module name
module_match = re.search(r'module\s+([^\s]+)', content)
name = module_match.group(1).split("/")[-1] if module_match else cwd.name
# Detect framework/stack
stack_parts = ["Go"]
if "gin-gonic/gin" in content:
stack_parts.append("Gin")
elif "gorilla/mux" in content:
stack_parts.append("Gorilla Mux")
elif "fiber" in content:
stack_parts.append("Fiber")
if "gorm" in content:
stack_parts.append("GORM")
tech_stack = ", ".join(stack_parts)
return {
"name": name,
"tech_stack": tech_stack,
"detected_from": "go.mod",
}
except IOError:
return None
def _detect_from_cargo_toml(cwd: Path) -> Optional[Dict[str, str]]:
"""Detect from Cargo.toml (Rust)."""
cargo_toml = cwd / "Cargo.toml"
if not cargo_toml.exists():
return None
try:
content = cargo_toml.read_text()
# Extract name
name_match = re.search(r'name\s*=\s*["\']([^"\']+)["\']', content)
name = name_match.group(1) if name_match else cwd.name
# Detect framework/stack
stack_parts = ["Rust"]
if "actix-web" in content:
stack_parts.append("Actix Web")
elif "rocket" in content:
stack_parts.append("Rocket")
elif "axum" in content:
stack_parts.append("Axum")
if "diesel" in content:
stack_parts.append("Diesel")
elif "sqlx" in content:
stack_parts.append("SQLx")
tech_stack = ", ".join(stack_parts)
return {
"name": name,
"tech_stack": tech_stack,
"detected_from": "Cargo.toml",
}
except IOError:
return None
def _detect_from_composer_json(cwd: Path) -> Optional[Dict[str, str]]:
"""Detect from composer.json (PHP)."""
composer_json = cwd / "composer.json"
if not composer_json.exists():
return None
try:
with open(composer_json) as f:
data = json.load(f)
name = data.get("name", cwd.name).split("/")[-1]
deps = {**data.get("require", {}), **data.get("require-dev", {})}
# Detect framework/stack
stack_parts = []
if any("laravel" in dep for dep in deps):
stack_parts.append("Laravel")
elif any("symfony" in dep for dep in deps):
stack_parts.append("Symfony")
tech_stack = ", ".join(stack_parts) if stack_parts else "PHP"
return {
"name": name,
"tech_stack": tech_stack,
"detected_from": "composer.json",
}
except (json.JSONDecodeError, IOError):
return None
def _detect_from_gemfile(cwd: Path) -> Optional[Dict[str, str]]:
"""Detect from Gemfile (Ruby)."""
gemfile = cwd / "Gemfile"
if not gemfile.exists():
return None
try:
content = gemfile.read_text()
name = cwd.name
# Detect framework/stack
stack_parts = []
if "rails" in content.lower():
stack_parts.append("Ruby on Rails")
elif "sinatra" in content.lower():
stack_parts.append("Sinatra")
else:
stack_parts.append("Ruby")
tech_stack = ", ".join(stack_parts)
return {
"name": name,
"tech_stack": tech_stack,
"detected_from": "Gemfile",
}
except IOError:
return None
if __name__ == "__main__":
# Test detection
info = detect_project_info()
print(json.dumps(info, indent=2))

View File

@@ -0,0 +1,112 @@
#!/usr/bin/env python3
"""
Template customization for Navigator initialization.
Replaces placeholders in templates with project-specific values.
"""
import re
from datetime import datetime
from typing import Dict
def customize_template(template_content: str, project_info: Dict[str, str]) -> str:
"""
Replace placeholders in template with project-specific values.
Args:
template_content: Template file content with placeholders
project_info: Dictionary from project_detector.py
Returns:
Customized template content
Placeholders:
${PROJECT_NAME} - Project name (capitalized)
${project_name} - Project name (lowercase)
${TECH_STACK} - Technology stack
${DATE} - Current date (YYYY-MM-DD)
${YEAR} - Current year
${DETECTED_FROM} - Source of detection
"""
now = datetime.now()
# Prepare replacement values
project_name = project_info.get("name", "My Project")
tech_stack = project_info.get("tech_stack", "Unknown")
detected_from = project_info.get("detected_from", "manual")
# Create title-cased version for display
project_name_title = _title_case(project_name)
replacements = {
"${PROJECT_NAME}": project_name_title,
"${project_name}": project_name.lower(),
"${TECH_STACK}": tech_stack,
"${DATE}": now.strftime("%Y-%m-%d"),
"${YEAR}": str(now.year),
"${DETECTED_FROM}": detected_from,
}
# Apply replacements
result = template_content
for placeholder, value in replacements.items():
result = result.replace(placeholder, value)
return result
def _title_case(text: str) -> str:
"""
Convert kebab-case, snake_case, or camelCase to Title Case.
Examples:
my-saas-app -> My SaaS App
user_management -> User Management
myAwesomeProject -> My Awesome Project
"""
# Replace separators with spaces
text = re.sub(r'[-_]', ' ', text)
# Add spaces before capitals in camelCase
text = re.sub(r'([a-z])([A-Z])', r'\1 \2', text)
# Title case
return text.title()
def validate_customization(content: str) -> bool:
"""
Check if template was properly customized (no placeholders remaining).
Args:
content: Template content after customization
Returns:
True if no placeholders found, False otherwise
"""
placeholder_pattern = r'\$\{[A-Z_]+\}'
return not bool(re.search(placeholder_pattern, content))
if __name__ == "__main__":
# Test customization
template = """
# ${PROJECT_NAME} - Development Documentation
**Project**: ${PROJECT_NAME}
**Tech Stack**: ${TECH_STACK}
**Last Updated**: ${DATE}
Detected from: ${DETECTED_FROM}
"""
project_info = {
"name": "my-saas-app",
"tech_stack": "Next.js, TypeScript, Prisma",
"detected_from": "package.json",
}
result = customize_template(template, project_info)
print(result)
print(f"\nValid: {validate_customization(result)}")