Fix: Ensure all skills are tracked as files, not submodules
This commit is contained in:
178
skills/loki-mode/scripts/export-to-vibe-kanban.sh
Executable file
178
skills/loki-mode/scripts/export-to-vibe-kanban.sh
Executable file
@@ -0,0 +1,178 @@
|
||||
#!/bin/bash
|
||||
# Export Loki Mode tasks to Vibe Kanban format
|
||||
# Usage: ./scripts/export-to-vibe-kanban.sh [export_dir]
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
LOKI_DIR=".loki"
|
||||
EXPORT_DIR="${1:-${VIBE_KANBAN_DIR:-$HOME/.vibe-kanban/loki-tasks}}"
|
||||
|
||||
# Colors
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${GREEN}[INFO]${NC} $*"; }
|
||||
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||||
|
||||
# Check if .loki directory exists
|
||||
if [ ! -d "$LOKI_DIR" ]; then
|
||||
log_warn "No .loki directory found. Run Loki Mode first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$EXPORT_DIR"
|
||||
|
||||
# Get current phase from orchestrator
|
||||
CURRENT_PHASE="UNKNOWN"
|
||||
if [ -f "$LOKI_DIR/state/orchestrator.json" ]; then
|
||||
CURRENT_PHASE=$(python3 -c "import json; print(json.load(open('$LOKI_DIR/state/orchestrator.json')).get('currentPhase', 'UNKNOWN'))" 2>/dev/null || echo "UNKNOWN")
|
||||
fi
|
||||
|
||||
# Map Loki phases to Vibe Kanban columns
|
||||
phase_to_column() {
|
||||
case "$1" in
|
||||
BOOTSTRAP|DISCOVERY|ARCHITECTURE) echo "planning" ;;
|
||||
INFRASTRUCTURE|DEVELOPMENT) echo "in-progress" ;;
|
||||
QA) echo "review" ;;
|
||||
DEPLOYMENT) echo "deploying" ;;
|
||||
BUSINESS_OPS|GROWTH|COMPLETED) echo "done" ;;
|
||||
*) echo "backlog" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Export tasks from all queues
|
||||
export_queue() {
|
||||
local queue_file="$1"
|
||||
local status="$2"
|
||||
|
||||
if [ ! -f "$queue_file" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
python3 << EOF
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
try:
|
||||
with open("$queue_file") as f:
|
||||
content = f.read().strip()
|
||||
if not content or content == "[]":
|
||||
tasks = []
|
||||
else:
|
||||
tasks = json.loads(content)
|
||||
except (json.JSONDecodeError, FileNotFoundError):
|
||||
tasks = []
|
||||
|
||||
export_dir = os.path.expanduser("$EXPORT_DIR")
|
||||
exported = 0
|
||||
|
||||
for task in tasks:
|
||||
task_id = task.get('id', 'unknown')
|
||||
|
||||
# Determine status based on queue and claimed state
|
||||
if "$status" == "pending":
|
||||
vibe_status = "todo"
|
||||
elif "$status" == "in-progress":
|
||||
vibe_status = "doing"
|
||||
elif "$status" == "completed":
|
||||
vibe_status = "done"
|
||||
elif "$status" == "failed":
|
||||
vibe_status = "blocked"
|
||||
else:
|
||||
vibe_status = "todo"
|
||||
|
||||
# Build description from payload
|
||||
payload = task.get('payload', {})
|
||||
if isinstance(payload, dict):
|
||||
desc_parts = []
|
||||
if 'action' in payload:
|
||||
desc_parts.append(f"Action: {payload['action']}")
|
||||
if 'description' in payload:
|
||||
desc_parts.append(payload['description'])
|
||||
if 'command' in payload:
|
||||
desc_parts.append(f"Command: {payload['command']}")
|
||||
description = "\n".join(desc_parts) if desc_parts else json.dumps(payload, indent=2)
|
||||
else:
|
||||
description = str(payload)
|
||||
|
||||
# Get agent type for tagging
|
||||
agent_type = task.get('type', 'unknown')
|
||||
swarm = agent_type.split('-')[0] if '-' in agent_type else 'general'
|
||||
|
||||
# Priority mapping (Loki uses 1-10, higher is more important)
|
||||
priority = task.get('priority', 5)
|
||||
if priority >= 8:
|
||||
priority_tag = "priority-high"
|
||||
elif priority >= 5:
|
||||
priority_tag = "priority-medium"
|
||||
else:
|
||||
priority_tag = "priority-low"
|
||||
|
||||
vibe_task = {
|
||||
"id": f"loki-{task_id}",
|
||||
"title": f"[{agent_type}] {payload.get('action', 'Task')}",
|
||||
"description": description,
|
||||
"status": vibe_status,
|
||||
"agent": "claude-code",
|
||||
"tags": [
|
||||
agent_type,
|
||||
f"swarm-{swarm}",
|
||||
priority_tag,
|
||||
f"phase-$CURRENT_PHASE".lower()
|
||||
],
|
||||
"metadata": {
|
||||
"lokiTaskId": task_id,
|
||||
"lokiType": agent_type,
|
||||
"lokiPriority": priority,
|
||||
"lokiPhase": "$CURRENT_PHASE",
|
||||
"lokiRetries": task.get('retries', 0),
|
||||
"createdAt": task.get('createdAt', datetime.utcnow().isoformat() + 'Z'),
|
||||
"claimedBy": task.get('claimedBy'),
|
||||
"lastError": task.get('lastError')
|
||||
}
|
||||
}
|
||||
|
||||
# Write task file
|
||||
task_file = os.path.join(export_dir, f"{task_id}.json")
|
||||
with open(task_file, 'w') as out:
|
||||
json.dump(vibe_task, out, indent=2)
|
||||
exported += 1
|
||||
|
||||
print(f"EXPORTED:{exported}")
|
||||
EOF
|
||||
}
|
||||
|
||||
log_info "Exporting Loki Mode tasks to Vibe Kanban..."
|
||||
log_info "Export directory: $EXPORT_DIR"
|
||||
log_info "Current phase: $CURRENT_PHASE"
|
||||
|
||||
TOTAL=0
|
||||
|
||||
# Export from each queue
|
||||
for queue in pending in-progress completed failed dead-letter; do
|
||||
queue_file="$LOKI_DIR/queue/${queue}.json"
|
||||
if [ -f "$queue_file" ]; then
|
||||
result=$(export_queue "$queue_file" "$queue")
|
||||
count=$(echo "$result" | grep "EXPORTED:" | cut -d: -f2)
|
||||
if [ -n "$count" ] && [ "$count" -gt 0 ]; then
|
||||
log_info " $queue: $count tasks"
|
||||
TOTAL=$((TOTAL + count))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Create summary file
|
||||
cat > "$EXPORT_DIR/_loki_summary.json" << EOF
|
||||
{
|
||||
"exportedAt": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
||||
"currentPhase": "$CURRENT_PHASE",
|
||||
"totalTasks": $TOTAL,
|
||||
"lokiVersion": "$(cat VERSION 2>/dev/null || echo 'unknown')",
|
||||
"column": "$(phase_to_column "$CURRENT_PHASE")"
|
||||
}
|
||||
EOF
|
||||
|
||||
log_info "Exported $TOTAL tasks total"
|
||||
log_info "Summary written to $EXPORT_DIR/_loki_summary.json"
|
||||
281
skills/loki-mode/scripts/loki-wrapper.sh
Executable file
281
skills/loki-mode/scripts/loki-wrapper.sh
Executable file
@@ -0,0 +1,281 @@
|
||||
#!/bin/bash
|
||||
# Loki Mode Wrapper Script
|
||||
# Provides true autonomy by auto-resuming on rate limits or interruptions
|
||||
#
|
||||
# How it works:
|
||||
# 1. Launches Claude Code with Loki Mode prompt
|
||||
# 2. Monitors the process - when Claude exits, checks exit code
|
||||
# 3. On rate limit (exit code != 0), waits with exponential backoff
|
||||
# 4. Restarts automatically, telling Claude to resume from checkpoint
|
||||
# 5. Continues until successful completion or max retries exceeded
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/loki-wrapper.sh [PRD_PATH]
|
||||
# ./scripts/loki-wrapper.sh ./docs/requirements.md
|
||||
# ./scripts/loki-wrapper.sh # Interactive mode
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
# Configuration
|
||||
MAX_RETRIES=${LOKI_MAX_RETRIES:-50} # Maximum retry attempts
|
||||
BASE_WAIT=${LOKI_BASE_WAIT:-60} # Base wait time in seconds
|
||||
MAX_WAIT=${LOKI_MAX_WAIT:-3600} # Max wait time (1 hour)
|
||||
LOG_FILE=${LOKI_LOG_FILE:-.loki/wrapper.log} # Log file location
|
||||
STATE_FILE=${LOKI_STATE_FILE:-.loki/wrapper-state.json}
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
log() {
|
||||
local level="$1"
|
||||
shift
|
||||
local msg="$*"
|
||||
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
echo -e "[$timestamp] [$level] $msg" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
log_info() { log "INFO" "$*"; }
|
||||
log_warn() { log "${YELLOW}WARN${NC}" "$*"; }
|
||||
log_error() { log "${RED}ERROR${NC}" "$*"; }
|
||||
log_success() { log "${GREEN}SUCCESS${NC}" "$*"; }
|
||||
|
||||
# Ensure .loki directory exists
|
||||
mkdir -p .loki
|
||||
|
||||
# Parse arguments
|
||||
PRD_PATH="${1:-}"
|
||||
INITIAL_PROMPT=""
|
||||
|
||||
if [ -n "$PRD_PATH" ]; then
|
||||
if [ -f "$PRD_PATH" ]; then
|
||||
INITIAL_PROMPT="Loki Mode with PRD at $PRD_PATH"
|
||||
else
|
||||
log_error "PRD file not found: $PRD_PATH"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
INITIAL_PROMPT="Loki Mode"
|
||||
fi
|
||||
|
||||
# Save wrapper state
|
||||
save_state() {
|
||||
local retry_count="$1"
|
||||
local status="$2"
|
||||
local last_exit_code="$3"
|
||||
|
||||
cat > "$STATE_FILE" << EOF
|
||||
{
|
||||
"retryCount": $retry_count,
|
||||
"status": "$status",
|
||||
"lastExitCode": $last_exit_code,
|
||||
"lastRun": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
||||
"prdPath": "$PRD_PATH",
|
||||
"pid": $$
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
# Load wrapper state if resuming
|
||||
load_state() {
|
||||
if [ -f "$STATE_FILE" ]; then
|
||||
if command -v python3 &> /dev/null; then
|
||||
RETRY_COUNT=$(python3 -c "import json; print(json.load(open('$STATE_FILE')).get('retryCount', 0))" 2>/dev/null || echo "0")
|
||||
else
|
||||
RETRY_COUNT=0
|
||||
fi
|
||||
else
|
||||
RETRY_COUNT=0
|
||||
fi
|
||||
}
|
||||
|
||||
# Calculate wait time with exponential backoff and jitter
|
||||
calculate_wait() {
|
||||
local retry="$1"
|
||||
local wait_time=$((BASE_WAIT * (2 ** retry)))
|
||||
|
||||
# Add jitter (0-30 seconds)
|
||||
local jitter=$((RANDOM % 30))
|
||||
wait_time=$((wait_time + jitter))
|
||||
|
||||
# Cap at max wait
|
||||
if [ $wait_time -gt $MAX_WAIT ]; then
|
||||
wait_time=$MAX_WAIT
|
||||
fi
|
||||
|
||||
echo $wait_time
|
||||
}
|
||||
|
||||
# Check if this looks like a rate limit error
|
||||
is_rate_limit() {
|
||||
local exit_code="$1"
|
||||
|
||||
# Exit code 1 with rate limit indicators in log
|
||||
if [ $exit_code -ne 0 ]; then
|
||||
# Check recent .loki logs for rate limit indicators
|
||||
if [ -d ".loki/logs" ]; then
|
||||
if grep -r -l "rate.limit\|429\|too.many.requests\|quota.exceeded" .loki/logs/*.log 2>/dev/null | head -1 | grep -q .; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
# Assume rate limit on non-zero exit (conservative approach)
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Check if Loki Mode completed successfully
|
||||
is_completed() {
|
||||
# Check for completion markers
|
||||
if [ -f ".loki/state/orchestrator.json" ]; then
|
||||
if command -v python3 &> /dev/null; then
|
||||
local phase=$(python3 -c "import json; print(json.load(open('.loki/state/orchestrator.json')).get('currentPhase', ''))" 2>/dev/null || echo "")
|
||||
if [ "$phase" = "COMPLETED" ] || [ "$phase" = "complete" ]; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check for success file
|
||||
if [ -f ".loki/COMPLETED" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# Build the resume prompt
|
||||
build_resume_prompt() {
|
||||
local retry="$1"
|
||||
|
||||
if [ $retry -eq 0 ]; then
|
||||
echo "$INITIAL_PROMPT"
|
||||
else
|
||||
# Resume from checkpoint
|
||||
if [ -n "$PRD_PATH" ]; then
|
||||
echo "Loki Mode - Resume from checkpoint. PRD at $PRD_PATH. This is retry #$retry after rate limit. Check .loki/state/ for current progress and continue from where we left off."
|
||||
else
|
||||
echo "Loki Mode - Resume from checkpoint. This is retry #$retry after rate limit. Check .loki/state/ for current progress and continue from where we left off."
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Main execution loop
|
||||
main() {
|
||||
log_info "=========================================="
|
||||
log_info "Loki Mode Autonomous Wrapper"
|
||||
log_info "=========================================="
|
||||
log_info "PRD: ${PRD_PATH:-Interactive}"
|
||||
log_info "Max retries: $MAX_RETRIES"
|
||||
log_info "Base wait: ${BASE_WAIT}s"
|
||||
log_info ""
|
||||
|
||||
load_state
|
||||
local retry=$RETRY_COUNT
|
||||
|
||||
while [ $retry -lt $MAX_RETRIES ]; do
|
||||
local prompt=$(build_resume_prompt $retry)
|
||||
|
||||
log_info "Attempt $((retry + 1))/$MAX_RETRIES"
|
||||
log_info "Prompt: $prompt"
|
||||
save_state $retry "running" 0
|
||||
|
||||
# Launch Claude Code
|
||||
# The process exits when:
|
||||
# 1. User types /exit or Ctrl+C (exit 0)
|
||||
# 2. Rate limit hit (exit 1 or other non-zero)
|
||||
# 3. Crash or error (non-zero exit)
|
||||
# 4. Session completes naturally (exit 0)
|
||||
|
||||
local start_time=$(date +%s)
|
||||
|
||||
# Run Claude Code with the prompt
|
||||
# Using -p for non-interactive prompt mode
|
||||
set +e
|
||||
claude --dangerously-skip-permissions -p "$prompt" 2>&1 | tee -a "$LOG_FILE"
|
||||
local exit_code=${PIPESTATUS[0]}
|
||||
set -e
|
||||
|
||||
local end_time=$(date +%s)
|
||||
local duration=$((end_time - start_time))
|
||||
|
||||
log_info "Claude exited with code $exit_code after ${duration}s"
|
||||
save_state $retry "exited" $exit_code
|
||||
|
||||
# Check for successful completion
|
||||
if [ $exit_code -eq 0 ]; then
|
||||
if is_completed; then
|
||||
log_success "Loki Mode completed successfully!"
|
||||
save_state $retry "completed" 0
|
||||
exit 0
|
||||
else
|
||||
log_info "Claude exited cleanly but work may not be complete"
|
||||
log_info "Checking if we should continue..."
|
||||
|
||||
# If session was short, might be intentional exit
|
||||
if [ $duration -lt 30 ]; then
|
||||
log_warn "Session was very short (${duration}s). User may have exited intentionally."
|
||||
log_info "Waiting 10 seconds before checking again..."
|
||||
sleep 10
|
||||
|
||||
# Re-check completion
|
||||
if is_completed; then
|
||||
log_success "Loki Mode completed!"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Handle non-zero exit (likely rate limit)
|
||||
if is_rate_limit $exit_code; then
|
||||
local wait_time=$(calculate_wait $retry)
|
||||
log_warn "Rate limit detected. Waiting ${wait_time}s before retry..."
|
||||
|
||||
# Show countdown
|
||||
local remaining=$wait_time
|
||||
while [ $remaining -gt 0 ]; do
|
||||
printf "\r${YELLOW}Resuming in ${remaining}s...${NC} "
|
||||
sleep 10
|
||||
remaining=$((remaining - 10))
|
||||
done
|
||||
echo ""
|
||||
|
||||
((retry++))
|
||||
else
|
||||
# Non-rate-limit error
|
||||
log_error "Non-rate-limit error (exit code: $exit_code)"
|
||||
|
||||
# Still retry, but with shorter wait
|
||||
local wait_time=$((BASE_WAIT / 2))
|
||||
log_info "Retrying in ${wait_time}s..."
|
||||
sleep $wait_time
|
||||
((retry++))
|
||||
fi
|
||||
done
|
||||
|
||||
log_error "Max retries ($MAX_RETRIES) exceeded. Giving up."
|
||||
save_state $retry "failed" 1
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Trap signals for clean shutdown
|
||||
cleanup() {
|
||||
log_warn "Received interrupt signal. Saving state..."
|
||||
save_state $RETRY_COUNT "interrupted" 130
|
||||
exit 130
|
||||
}
|
||||
trap cleanup INT TERM
|
||||
|
||||
# Check for claude command
|
||||
if ! command -v claude &> /dev/null; then
|
||||
log_error "Claude Code CLI not found. Please install it first."
|
||||
log_info "Visit: https://claude.ai/code"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Run main
|
||||
main "$@"
|
||||
55
skills/loki-mode/scripts/take-screenshots.js
Normal file
55
skills/loki-mode/scripts/take-screenshots.js
Normal file
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env node
|
||||
const puppeteer = require('puppeteer');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
async function takeScreenshots() {
|
||||
const dashboardPath = path.resolve(__dirname, '../autonomy/.loki/dashboard/index.html');
|
||||
const screenshotsDir = path.resolve(__dirname, '../docs/screenshots');
|
||||
|
||||
// Ensure screenshots directory exists
|
||||
if (!fs.existsSync(screenshotsDir)) {
|
||||
fs.mkdirSync(screenshotsDir, { recursive: true });
|
||||
}
|
||||
|
||||
console.log('Launching browser...');
|
||||
const browser = await puppeteer.launch({
|
||||
headless: 'new',
|
||||
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
||||
});
|
||||
|
||||
const page = await browser.newPage();
|
||||
|
||||
// Set viewport for consistent screenshots
|
||||
await page.setViewport({ width: 1400, height: 900 });
|
||||
|
||||
console.log('Loading dashboard...');
|
||||
await page.goto(`file://${dashboardPath}`, { waitUntil: 'networkidle0' });
|
||||
|
||||
// Wait for content to render
|
||||
await page.waitForSelector('#agents-grid');
|
||||
await page.waitForSelector('#queue-columns');
|
||||
|
||||
// Screenshot 1: Agents section
|
||||
console.log('Taking agents screenshot...');
|
||||
const agentsSection = await page.$('#agents-section');
|
||||
await agentsSection.screenshot({
|
||||
path: path.join(screenshotsDir, 'dashboard-agents.png'),
|
||||
type: 'png'
|
||||
});
|
||||
console.log('Saved: dashboard-agents.png');
|
||||
|
||||
// Screenshot 2: Task queue section
|
||||
console.log('Taking tasks screenshot...');
|
||||
const queueSection = await page.$('#queue-section');
|
||||
await queueSection.screenshot({
|
||||
path: path.join(screenshotsDir, 'dashboard-tasks.png'),
|
||||
type: 'png'
|
||||
});
|
||||
console.log('Saved: dashboard-tasks.png');
|
||||
|
||||
await browser.close();
|
||||
console.log('Done! Screenshots saved to docs/screenshots/');
|
||||
}
|
||||
|
||||
takeScreenshots().catch(console.error);
|
||||
Reference in New Issue
Block a user