warp-os 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +327 -0
- package/LICENSE +21 -0
- package/README.md +308 -0
- package/VERSION +1 -0
- package/agents/warp-browse.md +715 -0
- package/agents/warp-build-code.md +1299 -0
- package/agents/warp-orchestrator.md +515 -0
- package/agents/warp-plan-architect.md +929 -0
- package/agents/warp-plan-brainstorm.md +876 -0
- package/agents/warp-plan-design.md +1458 -0
- package/agents/warp-plan-onboarding.md +732 -0
- package/agents/warp-plan-optimize-adversarial.md +81 -0
- package/agents/warp-plan-optimize.md +354 -0
- package/agents/warp-plan-scope.md +806 -0
- package/agents/warp-plan-security.md +1274 -0
- package/agents/warp-plan-testdesign.md +1228 -0
- package/agents/warp-qa-debug-adversarial.md +90 -0
- package/agents/warp-qa-debug.md +793 -0
- package/agents/warp-qa-test-adversarial.md +89 -0
- package/agents/warp-qa-test.md +1054 -0
- package/agents/warp-release-update.md +1189 -0
- package/agents/warp-setup.md +1216 -0
- package/agents/warp-upgrade.md +334 -0
- package/bin/cli.js +44 -0
- package/bin/hooks/_warp_html.sh +291 -0
- package/bin/hooks/_warp_json.sh +67 -0
- package/bin/hooks/consistency-check.sh +92 -0
- package/bin/hooks/identity-briefing.sh +89 -0
- package/bin/hooks/identity-foundation.sh +37 -0
- package/bin/install.js +343 -0
- package/dist/warp-browse/SKILL.md +727 -0
- package/dist/warp-build-code/SKILL.md +1316 -0
- package/dist/warp-orchestrator/SKILL.md +527 -0
- package/dist/warp-plan-architect/SKILL.md +943 -0
- package/dist/warp-plan-brainstorm/SKILL.md +890 -0
- package/dist/warp-plan-design/SKILL.md +1473 -0
- package/dist/warp-plan-onboarding/SKILL.md +742 -0
- package/dist/warp-plan-optimize/SKILL.md +364 -0
- package/dist/warp-plan-scope/SKILL.md +820 -0
- package/dist/warp-plan-security/SKILL.md +1286 -0
- package/dist/warp-plan-testdesign/SKILL.md +1244 -0
- package/dist/warp-qa-debug/SKILL.md +805 -0
- package/dist/warp-qa-test/SKILL.md +1070 -0
- package/dist/warp-release-update/SKILL.md +1211 -0
- package/dist/warp-setup/SKILL.md +1229 -0
- package/dist/warp-upgrade/SKILL.md +345 -0
- package/package.json +40 -0
- package/shared/project-hooks.json +32 -0
- package/shared/tier1-engineering-constitution.md +176 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# _warp_json.sh — Shared JSON field extraction for Warp hooks (no jq)
|
|
3
|
+
# Source this file in every hook script. Provides:
|
|
4
|
+
# _warp_read_input — read JSON from stdin into memory
|
|
5
|
+
# _warp_field "key" — extract a string field value
|
|
6
|
+
# _warp_field_raw "key" — extract a non-string field value (bool/number/null)
|
|
7
|
+
# _warp_escape_json "string" — escape a string for embedding in JSON
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# source "$(dirname "$0")/_warp_json.sh"
|
|
11
|
+
# _warp_read_input
|
|
12
|
+
# CWD=$(_warp_field "cwd")
|
|
13
|
+
# SESSION_ID=$(_warp_field "session_id")
|
|
14
|
+
|
|
15
|
+
_WARP_HOOK_INPUT=""
|
|
16
|
+
|
|
17
|
+
# Read all of stdin into _WARP_HOOK_INPUT. Call once per hook.
|
|
18
|
+
_warp_read_input() {
|
|
19
|
+
_WARP_HOOK_INPUT=$(cat)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
# Extract a JSON string field: "key": "value" → value
|
|
23
|
+
# Handles optional whitespace around the colon.
|
|
24
|
+
# Returns empty string if field not found.
|
|
25
|
+
_warp_field() {
|
|
26
|
+
local key="$1"
|
|
27
|
+
if [ -z "$_WARP_HOOK_INPUT" ]; then
|
|
28
|
+
echo ""
|
|
29
|
+
return
|
|
30
|
+
fi
|
|
31
|
+
echo "$_WARP_HOOK_INPUT" | \
|
|
32
|
+
grep -o "\"$key\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" | \
|
|
33
|
+
head -1 | \
|
|
34
|
+
sed "s/\"$key\"[[:space:]]*:[[:space:]]*\"//;s/\"$//"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# Extract a non-string JSON field: "key": true → true, "key": 42 → 42
|
|
38
|
+
# For booleans, numbers, null. Returns empty string if not found.
|
|
39
|
+
_warp_field_raw() {
|
|
40
|
+
local key="$1"
|
|
41
|
+
if [ -z "$_WARP_HOOK_INPUT" ]; then
|
|
42
|
+
echo ""
|
|
43
|
+
return
|
|
44
|
+
fi
|
|
45
|
+
echo "$_WARP_HOOK_INPUT" | \
|
|
46
|
+
grep -o "\"$key\"[[:space:]]*:[[:space:]]*[^,}\"][^,}]*" | \
|
|
47
|
+
head -1 | \
|
|
48
|
+
sed "s/\"$key\"[[:space:]]*:[[:space:]]*//" | \
|
|
49
|
+
tr -d '[:space:]'
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# Escape a string for safe embedding inside a JSON string value.
|
|
53
|
+
# Order matters: backslash must be escaped first.
|
|
54
|
+
_warp_escape_json() {
|
|
55
|
+
local s="$1"
|
|
56
|
+
# Backslash first (doubles all existing backslashes)
|
|
57
|
+
s="${s//\\/\\\\}"
|
|
58
|
+
# Double quotes
|
|
59
|
+
s="${s//\"/\\\"}"
|
|
60
|
+
# Newlines
|
|
61
|
+
s="${s//$'\n'/\\n}"
|
|
62
|
+
# Tabs
|
|
63
|
+
s="${s//$'\t'/\\t}"
|
|
64
|
+
# Carriage returns
|
|
65
|
+
s="${s//$'\r'/}"
|
|
66
|
+
echo "$s"
|
|
67
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# consistency-check.sh — Validate CLAUDE.md and TODOS.md on Stop
|
|
3
|
+
# Fires on Stop event (alongside claude-mem's summarize hook).
|
|
4
|
+
# Reads git diff for session changes, checks for staleness.
|
|
5
|
+
# Outputs warnings via stdout. Never blocks — exits 0 on any error.
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
9
|
+
source "$SCRIPT_DIR/_warp_json.sh"
|
|
10
|
+
_warp_read_input
|
|
11
|
+
|
|
12
|
+
CWD=$(_warp_field "cwd")
|
|
13
|
+
if [ -z "$CWD" ]; then
|
|
14
|
+
exit 0
|
|
15
|
+
fi
|
|
16
|
+
cd "$CWD" 2>/dev/null || exit 0
|
|
17
|
+
|
|
18
|
+
# Need git for diff-based checks
|
|
19
|
+
if ! command -v git >/dev/null 2>&1; then
|
|
20
|
+
exit 0
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
# Get files changed this session (unstaged + staged)
|
|
24
|
+
CHANGED_FILES=$(git diff --name-only HEAD 2>/dev/null || true)
|
|
25
|
+
STAGED_FILES=$(git diff --cached --name-only 2>/dev/null || true)
|
|
26
|
+
ALL_CHANGED=$(echo -e "${CHANGED_FILES}\n${STAGED_FILES}" | sort -u | grep -v '^$' || true)
|
|
27
|
+
|
|
28
|
+
if [ -z "$ALL_CHANGED" ]; then
|
|
29
|
+
exit 0
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
WARNINGS=""
|
|
33
|
+
|
|
34
|
+
# Check A: New files not referenced in nearest CLAUDE.md
|
|
35
|
+
while IFS= read -r filepath; do
|
|
36
|
+
[ -z "$filepath" ] && continue
|
|
37
|
+
# Only check files that are new (not in HEAD)
|
|
38
|
+
if ! git show HEAD:"$filepath" >/dev/null 2>&1; then
|
|
39
|
+
# Find nearest CLAUDE.md
|
|
40
|
+
dir=$(dirname "$filepath")
|
|
41
|
+
claude_md=""
|
|
42
|
+
while [ "$dir" != "." ] && [ "$dir" != "/" ]; do
|
|
43
|
+
if [ -f "$dir/CLAUDE.md" ]; then
|
|
44
|
+
claude_md="$dir/CLAUDE.md"
|
|
45
|
+
break
|
|
46
|
+
fi
|
|
47
|
+
dir=$(dirname "$dir")
|
|
48
|
+
done
|
|
49
|
+
# Fall back to root CLAUDE.md
|
|
50
|
+
if [ -z "$claude_md" ] && [ -f "CLAUDE.md" ]; then
|
|
51
|
+
claude_md="CLAUDE.md"
|
|
52
|
+
fi
|
|
53
|
+
if [ -n "$claude_md" ]; then
|
|
54
|
+
basename_file=$(basename "$filepath")
|
|
55
|
+
if ! grep -q "$basename_file" "$claude_md" 2>/dev/null; then
|
|
56
|
+
WARNINGS="${WARNINGS}MISSING: ${filepath} not referenced in ${claude_md}\n"
|
|
57
|
+
fi
|
|
58
|
+
fi
|
|
59
|
+
fi
|
|
60
|
+
done <<< "$ALL_CHANGED"
|
|
61
|
+
|
|
62
|
+
# Check B: TODOS.md — flag items that may now be done
|
|
63
|
+
if [ -f "TODOS.md" ]; then
|
|
64
|
+
# Get unchecked P1 items
|
|
65
|
+
P1_OPEN=$(sed -n '/^## P1/,/^## P[2-9]/p' TODOS.md 2>/dev/null | grep '^\- \[ \]' || true)
|
|
66
|
+
if [ -n "$P1_OPEN" ]; then
|
|
67
|
+
# Check if any changed files relate to open TODO items
|
|
68
|
+
while IFS= read -r todo_line; do
|
|
69
|
+
[ -z "$todo_line" ] && continue
|
|
70
|
+
# Extract keywords from the TODO (skip common words)
|
|
71
|
+
keywords=$(echo "$todo_line" | sed 's/^- \[ \] //' | tr '[:upper:]' '[:lower:]' | \
|
|
72
|
+
grep -oE '\b[a-z]{4,}\b' | grep -vE '^(that|this|with|from|have|been|they|will|just|also|into|when|after)$' | head -5)
|
|
73
|
+
for kw in $keywords; do
|
|
74
|
+
# Check if any changed file paths or commit messages relate
|
|
75
|
+
if echo "$ALL_CHANGED" | grep -qi "$kw" 2>/dev/null; then
|
|
76
|
+
clean_todo=$(echo "$todo_line" | sed 's/^- \[ \] //')
|
|
77
|
+
WARNINGS="${WARNINGS}TODO-CHECK: '${clean_todo}' may be addressed — changed files match '${kw}'\n"
|
|
78
|
+
break
|
|
79
|
+
fi
|
|
80
|
+
done
|
|
81
|
+
done <<< "$P1_OPEN"
|
|
82
|
+
fi
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
# Output warnings if any
|
|
86
|
+
if [ -n "$WARNINGS" ]; then
|
|
87
|
+
echo "[warp-consistency] Session changes detected:"
|
|
88
|
+
echo -e "$WARNINGS" | grep -v '^$' | head -10
|
|
89
|
+
echo "(Run /warp-release-update before pushing to verify docs are current)"
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
exit 0
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# identity-briefing.sh — Inject pipeline state + priorities into Claude's context
|
|
3
|
+
# Fires on SessionStart (all sources). Bounded to ~100-200 tokens.
|
|
4
|
+
# Outputs partial briefing if some files are missing (graceful degradation).
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
8
|
+
source "$SCRIPT_DIR/_warp_json.sh"
|
|
9
|
+
_warp_read_input
|
|
10
|
+
|
|
11
|
+
CWD=$(_warp_field "cwd")
|
|
12
|
+
if [ -z "$CWD" ]; then
|
|
13
|
+
exit 0
|
|
14
|
+
fi
|
|
15
|
+
cd "$CWD" 2>/dev/null || exit 0
|
|
16
|
+
|
|
17
|
+
# Build welcome banner (48-char width per design spec)
|
|
18
|
+
RULE="━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
19
|
+
|
|
20
|
+
# Branch and git state
|
|
21
|
+
BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown")
|
|
22
|
+
UNCOMMITTED=$(git status --porcelain 2>/dev/null | wc -l | tr -d '[:space:]')
|
|
23
|
+
if [ "$UNCOMMITTED" = "0" ]; then
|
|
24
|
+
GIT_STATE="clean"
|
|
25
|
+
else
|
|
26
|
+
GIT_STATE="${UNCOMMITTED} files"
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
# Pipeline state
|
|
30
|
+
PIPELINE=""
|
|
31
|
+
for artifact in brainstorm onboarding scope architecture design testspec security README; do
|
|
32
|
+
f=".warp/pipeline/${artifact}.md"
|
|
33
|
+
if [ -f "$f" ]; then
|
|
34
|
+
PIPELINE="${PIPELINE} ✓${artifact}"
|
|
35
|
+
else
|
|
36
|
+
PIPELINE="${PIPELINE} ○${artifact}"
|
|
37
|
+
fi
|
|
38
|
+
done
|
|
39
|
+
|
|
40
|
+
# Mode detection
|
|
41
|
+
if [ -f ".warp/pipeline/README.md" ]; then
|
|
42
|
+
WARP_MODE="pipeline"
|
|
43
|
+
elif [ -d ".warp/pipeline" ] && ls .warp/pipeline/*.md >/dev/null 2>&1; then
|
|
44
|
+
WARP_MODE="pipeline-partial"
|
|
45
|
+
else
|
|
46
|
+
WARP_MODE="no-pipeline"
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# Build the banner
|
|
50
|
+
BRIEFING="${RULE}"$'\n'"WARP │ orchestrator"$'\n'"${RULE}"
|
|
51
|
+
BRIEFING="${BRIEFING}"$'\n'" Branch: ${BRANCH} | ${GIT_STATE}"
|
|
52
|
+
if [ -n "$PIPELINE" ]; then
|
|
53
|
+
BRIEFING="${BRIEFING}"$'\n'" Pipeline:${PIPELINE}"
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
# Phase/cycle progress from roadmap
|
|
57
|
+
if [ -f ".warp/pipeline/README.md" ]; then
|
|
58
|
+
CHECKED=$(grep -c '^\- \[x\]' .warp/pipeline/README.md 2>/dev/null || echo "0")
|
|
59
|
+
TOTAL=$(grep -c '^\- \[' .warp/pipeline/README.md 2>/dev/null || echo "0")
|
|
60
|
+
NEXT=$(grep -m1 '^\- \[ \]' .warp/pipeline/README.md 2>/dev/null | sed 's/^- \[ \] //' | head -c 60 || true)
|
|
61
|
+
if [ "$TOTAL" != "0" ]; then
|
|
62
|
+
BRIEFING="${BRIEFING}"$'\n'"Cycles: ${CHECKED}/${TOTAL} complete"
|
|
63
|
+
if [ -n "$NEXT" ]; then
|
|
64
|
+
BRIEFING="${BRIEFING} (next: ${NEXT})"
|
|
65
|
+
fi
|
|
66
|
+
fi
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
# P1 priorities from TODOS.md
|
|
70
|
+
if [ -f "TODOS.md" ]; then
|
|
71
|
+
P1_ITEMS=$(sed -n '/^## P1/,/^## P[2-9]/p' TODOS.md 2>/dev/null | grep '^\- \[ \]' | head -3 | sed 's/^- \[ \] //' | tr '\n' ' | ' || true)
|
|
72
|
+
if [ -n "$P1_ITEMS" ]; then
|
|
73
|
+
BRIEFING="${BRIEFING}"$'\n'"P1: ${P1_ITEMS}"
|
|
74
|
+
fi
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
# Close the banner
|
|
78
|
+
BRIEFING="${BRIEFING}"$'\n'"${RULE}"
|
|
79
|
+
BRIEFING="${BRIEFING}"$'\n'"[Mode: ${WARP_MODE}]"
|
|
80
|
+
|
|
81
|
+
# Output via JSON additionalContext (primary) with stdout fallback
|
|
82
|
+
ESCAPED=$(_warp_escape_json "$BRIEFING")
|
|
83
|
+
if [ -n "$ESCAPED" ]; then
|
|
84
|
+
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"SessionStart\",\"additionalContext\":\"$ESCAPED\"}}"
|
|
85
|
+
else
|
|
86
|
+
echo "$BRIEFING"
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
exit 0
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# identity-foundation.sh — First-run orientation for new users
|
|
3
|
+
# Fires on SessionStart (all sources including compact).
|
|
4
|
+
# Tier 1 is in the orchestrator agent definition — no tier injection needed.
|
|
5
|
+
#
|
|
6
|
+
# Primary: JSON additionalContext (CC-documented mechanism)
|
|
7
|
+
# Fallback: raw stdout (tested, works, but undocumented)
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
|
|
10
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
11
|
+
source "$SCRIPT_DIR/_warp_json.sh"
|
|
12
|
+
_warp_read_input
|
|
13
|
+
|
|
14
|
+
# First-run orientation: welcome new users (one-time)
|
|
15
|
+
CWD=$(_warp_field "cwd")
|
|
16
|
+
INTRO_SEEN="$HOME/.warp/.intro-seen"
|
|
17
|
+
CONTENT=""
|
|
18
|
+
|
|
19
|
+
if [ ! -f "$INTRO_SEEN" ]; then
|
|
20
|
+
CONTENT="Welcome to Warp — a development operating system for Claude Code. 22 deep skills that think through every step of building a product, from first idea to shipped release. Getting started: /warp-plan-brainstorm (new project) or /warp-plan-onboarding (existing project). Settings: ~/.warp/settings.json."
|
|
21
|
+
mkdir -p "$HOME/.warp" 2>/dev/null || true
|
|
22
|
+
touch "$INTRO_SEEN" 2>/dev/null || true
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
if [ -z "$CONTENT" ]; then
|
|
26
|
+
exit 0
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
# Primary: JSON additionalContext (CC-documented for SessionStart)
|
|
30
|
+
ESCAPED=$(_warp_escape_json "$CONTENT")
|
|
31
|
+
if [ -n "$ESCAPED" ]; then
|
|
32
|
+
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"SessionStart\",\"additionalContext\":\"$ESCAPED\"}}"
|
|
33
|
+
else
|
|
34
|
+
echo "$CONTENT"
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
exit 0
|
package/bin/install.js
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// install.js — Warp installer (replaces install.sh)
|
|
3
|
+
// No symlinks. Just file copies. Works on Windows, Mac, Linux.
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
const { execSync } = require('child_process');
|
|
9
|
+
|
|
10
|
+
// ── Paths ────────────────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
const HOME = os.homedir();
|
|
13
|
+
const REPO_ROOT = path.resolve(__dirname, '..');
|
|
14
|
+
const SKILLS_DIR = path.join(HOME, '.claude', 'skills');
|
|
15
|
+
const AGENTS_DIR = path.join(HOME, '.claude', 'agents');
|
|
16
|
+
const WARP_DIR = path.join(HOME, '.warp');
|
|
17
|
+
const HOOKS_DIR = path.join(WARP_DIR, 'hooks');
|
|
18
|
+
const SHARED_DIR = path.join(WARP_DIR, 'shared');
|
|
19
|
+
|
|
20
|
+
// ── Colors ───────────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
const GREEN = '\x1b[32m';
|
|
23
|
+
const YELLOW = '\x1b[33m';
|
|
24
|
+
const RED = '\x1b[31m';
|
|
25
|
+
const CYAN = '\x1b[36m';
|
|
26
|
+
const NC = '\x1b[0m';
|
|
27
|
+
|
|
28
|
+
function ok(msg) { console.log(` ${GREEN}OK${NC} ${msg}`); }
|
|
29
|
+
function warn(msg) { console.log(` ${YELLOW}WARN${NC} ${msg}`); }
|
|
30
|
+
function fail(msg) { console.log(` ${RED}FAIL${NC} ${msg}`); }
|
|
31
|
+
function info(msg) { console.log(` ${CYAN}INFO${NC} ${msg}`); }
|
|
32
|
+
|
|
33
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
function mkdirSafe(dir) {
|
|
36
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function copyFile(src, dest) {
|
|
40
|
+
fs.copyFileSync(src, dest);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function copyDir(src, dest) {
|
|
44
|
+
mkdirSafe(dest);
|
|
45
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
46
|
+
const srcPath = path.join(src, entry.name);
|
|
47
|
+
const destPath = path.join(dest, entry.name);
|
|
48
|
+
if (entry.isDirectory()) {
|
|
49
|
+
copyDir(srcPath, destPath);
|
|
50
|
+
} else {
|
|
51
|
+
copyFile(srcPath, destPath);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function removeSafe(target) {
|
|
57
|
+
try {
|
|
58
|
+
const stat = fs.lstatSync(target);
|
|
59
|
+
if (stat.isDirectory()) {
|
|
60
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
61
|
+
} else {
|
|
62
|
+
fs.unlinkSync(target);
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
} catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ── Version ──────────────────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
const VERSION = fs.readFileSync(path.join(REPO_ROOT, 'VERSION'), 'utf8').trim();
|
|
73
|
+
|
|
74
|
+
console.log('');
|
|
75
|
+
console.log('Warp Installer');
|
|
76
|
+
console.log('\u2501'.repeat(48));
|
|
77
|
+
console.log(` Version: ${CYAN}v${VERSION}${NC}`);
|
|
78
|
+
console.log(` Source: ${REPO_ROOT}`);
|
|
79
|
+
console.log(` Target: ${SKILLS_DIR}`);
|
|
80
|
+
console.log('');
|
|
81
|
+
|
|
82
|
+
// ── Check if this is doctor mode ─────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
const isDoctor = process.argv[2] === 'doctor';
|
|
85
|
+
|
|
86
|
+
// ── Step 1: Install Skills ───────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
console.log('Installing skills...');
|
|
89
|
+
mkdirSafe(SKILLS_DIR);
|
|
90
|
+
|
|
91
|
+
const distDir = path.join(REPO_ROOT, 'dist');
|
|
92
|
+
let skillsInstalled = 0;
|
|
93
|
+
let skillsFailed = 0;
|
|
94
|
+
|
|
95
|
+
if (!fs.existsSync(distDir)) {
|
|
96
|
+
fail('dist/ directory not found. Run build.sh first.');
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Clean up old-style single-directory install
|
|
101
|
+
const oldInstall = path.join(SKILLS_DIR, 'warp');
|
|
102
|
+
if (fs.existsSync(oldInstall)) {
|
|
103
|
+
removeSafe(oldInstall);
|
|
104
|
+
warn('Removed old-style install at ~/.claude/skills/warp');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
for (const entry of fs.readdirSync(distDir, { withFileTypes: true })) {
|
|
108
|
+
if (!entry.isDirectory() || !entry.name.startsWith('warp-')) continue;
|
|
109
|
+
|
|
110
|
+
const srcSkillDir = path.join(distDir, entry.name);
|
|
111
|
+
const srcSkillFile = path.join(srcSkillDir, 'SKILL.md');
|
|
112
|
+
|
|
113
|
+
if (!fs.existsSync(srcSkillFile)) {
|
|
114
|
+
warn(`${entry.name} — no SKILL.md, skipping`);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const targetDir = path.join(SKILLS_DIR, entry.name);
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
// Remove old version (symlink, copy, or directory)
|
|
122
|
+
if (fs.existsSync(targetDir) || fs.lstatSync(targetDir).isSymbolicLink()) {
|
|
123
|
+
removeSafe(targetDir);
|
|
124
|
+
}
|
|
125
|
+
} catch {
|
|
126
|
+
// target doesn't exist, that's fine
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
copyDir(srcSkillDir, targetDir);
|
|
131
|
+
ok(entry.name);
|
|
132
|
+
skillsInstalled++;
|
|
133
|
+
} catch (err) {
|
|
134
|
+
fail(`${entry.name} — ${err.message}`);
|
|
135
|
+
skillsFailed++;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Clean up stale skills (removed in newer versions)
|
|
140
|
+
let staleSkills = 0;
|
|
141
|
+
for (const entry of fs.readdirSync(SKILLS_DIR, { withFileTypes: true })) {
|
|
142
|
+
if (!entry.isDirectory() || !entry.name.startsWith('warp-')) continue;
|
|
143
|
+
const distCounterpart = path.join(distDir, entry.name, 'SKILL.md');
|
|
144
|
+
if (!fs.existsSync(distCounterpart)) {
|
|
145
|
+
removeSafe(path.join(SKILLS_DIR, entry.name));
|
|
146
|
+
warn(`${entry.name} — removed (stale from previous version)`);
|
|
147
|
+
staleSkills++;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
console.log('');
|
|
152
|
+
console.log('\u2501'.repeat(48));
|
|
153
|
+
console.log(`Skills: ${GREEN}${skillsInstalled}${NC} installed ${RED}${skillsFailed}${NC} failed ${YELLOW}${staleSkills}${NC} stale removed`);
|
|
154
|
+
|
|
155
|
+
// ── Step 2: Install Agents ───────────────────────────────────────────────────
|
|
156
|
+
|
|
157
|
+
console.log('');
|
|
158
|
+
console.log('Installing agents...');
|
|
159
|
+
mkdirSafe(AGENTS_DIR);
|
|
160
|
+
|
|
161
|
+
const agentsDir = path.join(REPO_ROOT, 'agents');
|
|
162
|
+
let agentsInstalled = 0;
|
|
163
|
+
let agentsFailed = 0;
|
|
164
|
+
|
|
165
|
+
for (const file of fs.readdirSync(agentsDir)) {
|
|
166
|
+
if (!file.startsWith('warp-') || !file.endsWith('.md')) continue;
|
|
167
|
+
|
|
168
|
+
const srcFile = path.join(agentsDir, file);
|
|
169
|
+
const destFile = path.join(AGENTS_DIR, file);
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
// Remove old version (symlink or file)
|
|
173
|
+
try { fs.lstatSync(destFile); removeSafe(destFile); } catch {}
|
|
174
|
+
copyFile(srcFile, destFile);
|
|
175
|
+
ok(file);
|
|
176
|
+
agentsInstalled++;
|
|
177
|
+
} catch (err) {
|
|
178
|
+
fail(`${file} — ${err.message}`);
|
|
179
|
+
agentsFailed++;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Clean up stale agents
|
|
184
|
+
let staleAgents = 0;
|
|
185
|
+
for (const file of fs.readdirSync(AGENTS_DIR)) {
|
|
186
|
+
if (!file.startsWith('warp-') || !file.endsWith('.md')) continue;
|
|
187
|
+
if (!fs.existsSync(path.join(agentsDir, file))) {
|
|
188
|
+
removeSafe(path.join(AGENTS_DIR, file));
|
|
189
|
+
warn(`${file} — removed (stale)`);
|
|
190
|
+
staleAgents++;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
console.log('');
|
|
195
|
+
console.log('\u2501'.repeat(48));
|
|
196
|
+
console.log(`Agents: ${GREEN}${agentsInstalled}${NC} installed ${RED}${agentsFailed}${NC} failed ${YELLOW}${staleAgents}${NC} stale removed`);
|
|
197
|
+
|
|
198
|
+
// ── Step 3: Install Hooks ────────────────────────────────────────────────────
|
|
199
|
+
|
|
200
|
+
console.log('');
|
|
201
|
+
console.log('Installing hooks...');
|
|
202
|
+
mkdirSafe(HOOKS_DIR);
|
|
203
|
+
|
|
204
|
+
const hooksSource = path.join(REPO_ROOT, 'bin', 'hooks');
|
|
205
|
+
let hooksInstalled = 0;
|
|
206
|
+
|
|
207
|
+
if (fs.existsSync(hooksSource)) {
|
|
208
|
+
// Clean up old hook names from previous versions
|
|
209
|
+
const staleHooks = [
|
|
210
|
+
'session-start.sh', 'session-end.sh', 'pre-compact.sh', 'post-compact.sh',
|
|
211
|
+
'subagent-inject.sh', 'subagent-quicksave.sh', 'orch-guard.sh', 'block-md.sh',
|
|
212
|
+
'orchestrator-foundation.sh', 'orchestrator-briefing.sh', 'qa-gate.sh', 'qa-boundary.sh',
|
|
213
|
+
'phase-boundary-detect.sh', 'test-failure-router.sh', 'lifecycle-session-start.sh',
|
|
214
|
+
'lifecycle-session-end.sh', 'lifecycle-pre-compact.sh', 'lifecycle-post-compact.sh',
|
|
215
|
+
'lifecycle-subagent-inject.sh', 'lifecycle-subagent-quicksave.sh',
|
|
216
|
+
'guard-orch.sh', 'guard-block-md.sh', 'automation-phase-boundary.sh',
|
|
217
|
+
'automation-test-router.sh', 'verify-api-docs.sh', 'verify-qa-boundary.sh', 'verify-qa-gate.sh'
|
|
218
|
+
];
|
|
219
|
+
for (const old of staleHooks) {
|
|
220
|
+
removeSafe(path.join(HOOKS_DIR, old));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
for (const file of fs.readdirSync(hooksSource)) {
|
|
224
|
+
if (!file.endsWith('.sh')) continue;
|
|
225
|
+
const src = path.join(hooksSource, file);
|
|
226
|
+
const dest = path.join(HOOKS_DIR, file);
|
|
227
|
+
try {
|
|
228
|
+
copyFile(src, dest);
|
|
229
|
+
// Make executable on Unix
|
|
230
|
+
if (process.platform !== 'win32') {
|
|
231
|
+
fs.chmodSync(dest, 0o755);
|
|
232
|
+
}
|
|
233
|
+
hooksInstalled++;
|
|
234
|
+
} catch (err) {
|
|
235
|
+
fail(`${file} — ${err.message}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
ok(`${hooksInstalled} hook scripts → ~/.warp/hooks/`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ── Step 4: Install Foundation ───────────────────────────────────────────────
|
|
242
|
+
|
|
243
|
+
console.log('');
|
|
244
|
+
console.log('Installing foundation...');
|
|
245
|
+
mkdirSafe(SHARED_DIR);
|
|
246
|
+
|
|
247
|
+
const tier1Src = path.join(REPO_ROOT, 'shared', 'tier1-engineering-constitution.md');
|
|
248
|
+
if (fs.existsSync(tier1Src)) {
|
|
249
|
+
copyFile(tier1Src, path.join(SHARED_DIR, 'tier1-engineering-constitution.md'));
|
|
250
|
+
ok('Tier 1 → ~/.warp/shared/');
|
|
251
|
+
} else {
|
|
252
|
+
warn('tier1-engineering-constitution.md not found');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Clean up old tier files
|
|
256
|
+
for (const old of ['tier3-ux-patterns.md', 'tier3-ux-infrastructure.md']) {
|
|
257
|
+
removeSafe(path.join(SHARED_DIR, old));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Project hooks config template
|
|
261
|
+
const hooksConfigSrc = path.join(REPO_ROOT, 'shared', 'project-hooks.json');
|
|
262
|
+
if (fs.existsSync(hooksConfigSrc)) {
|
|
263
|
+
copyFile(hooksConfigSrc, path.join(WARP_DIR, 'project-hooks.json'));
|
|
264
|
+
ok('project-hooks.json → ~/.warp/');
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ── Step 5: Install claude-mem ───────────────────────────────────────────────
|
|
268
|
+
|
|
269
|
+
console.log('');
|
|
270
|
+
console.log('Installing claude-mem...');
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
execSync('npx claude-mem install', {
|
|
274
|
+
stdio: 'pipe',
|
|
275
|
+
timeout: 300000
|
|
276
|
+
});
|
|
277
|
+
ok('claude-mem installed (persistent memory)');
|
|
278
|
+
} catch (err) {
|
|
279
|
+
warn('claude-mem install failed — memory features unavailable');
|
|
280
|
+
info('Install manually: npx claude-mem install');
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ── Step 6: Version Tracking ─────────────────────────────────────────────────
|
|
284
|
+
|
|
285
|
+
console.log('');
|
|
286
|
+
console.log('Writing version...');
|
|
287
|
+
|
|
288
|
+
fs.writeFileSync(path.join(WARP_DIR, 'version'), VERSION + '\n');
|
|
289
|
+
ok(`v${VERSION} → ~/.warp/version`);
|
|
290
|
+
|
|
291
|
+
// Write package source path (for upgrade detection)
|
|
292
|
+
fs.writeFileSync(path.join(WARP_DIR, 'repo-path'), REPO_ROOT + '\n');
|
|
293
|
+
ok(`Source path → ~/.warp/repo-path`);
|
|
294
|
+
|
|
295
|
+
// ── Step 7: Clean up old global hooks ────────────────────────────────────────
|
|
296
|
+
|
|
297
|
+
const globalSettings = path.join(HOME, '.claude', 'settings.json');
|
|
298
|
+
if (fs.existsSync(globalSettings)) {
|
|
299
|
+
try {
|
|
300
|
+
const content = fs.readFileSync(globalSettings, 'utf8');
|
|
301
|
+
if (content.includes('.warp/hooks/')) {
|
|
302
|
+
const settings = JSON.parse(content);
|
|
303
|
+
if (settings.hooks) {
|
|
304
|
+
let cleaned = false;
|
|
305
|
+
for (const [event, matchers] of Object.entries(settings.hooks)) {
|
|
306
|
+
if (!Array.isArray(matchers)) continue;
|
|
307
|
+
settings.hooks[event] = matchers.map(m => {
|
|
308
|
+
if (!m.hooks || !Array.isArray(m.hooks)) return m;
|
|
309
|
+
const filtered = m.hooks.filter(h => {
|
|
310
|
+
const isWarp = (h.command && h.command.includes('.warp/hooks/')) ||
|
|
311
|
+
(h.prompt && h.prompt.includes('session is ending'));
|
|
312
|
+
if (isWarp) cleaned = true;
|
|
313
|
+
return !isWarp;
|
|
314
|
+
});
|
|
315
|
+
return { ...m, hooks: filtered };
|
|
316
|
+
}).filter(m => m.hooks && m.hooks.length > 0);
|
|
317
|
+
if (settings.hooks[event].length === 0) delete settings.hooks[event];
|
|
318
|
+
}
|
|
319
|
+
if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
|
|
320
|
+
if (cleaned) {
|
|
321
|
+
fs.writeFileSync(globalSettings, JSON.stringify(settings, null, 2));
|
|
322
|
+
ok('Cleaned old Warp hooks from global settings.json');
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
} catch {
|
|
327
|
+
// Ignore — not critical
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// ── Done ─────────────────────────────────────────────────────────────────────
|
|
332
|
+
|
|
333
|
+
console.log('');
|
|
334
|
+
console.log('\u2501'.repeat(48));
|
|
335
|
+
console.log(`${GREEN}Warp installed successfully.${NC}`);
|
|
336
|
+
console.log(` Version: ${CYAN}v${VERSION}${NC}`);
|
|
337
|
+
console.log(` Skills: ${GREEN}${skillsInstalled}${NC}`);
|
|
338
|
+
console.log(` Agents: ${GREEN}${agentsInstalled}${NC}`);
|
|
339
|
+
console.log(` Hooks: ${GREEN}${hooksInstalled}${NC}`);
|
|
340
|
+
console.log('\u2501'.repeat(48));
|
|
341
|
+
console.log('');
|
|
342
|
+
console.log('Restart Claude Code to discover skills, then try: /warp-setup');
|
|
343
|
+
console.log('');
|