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.
Files changed (49) hide show
  1. package/CHANGELOG.md +327 -0
  2. package/LICENSE +21 -0
  3. package/README.md +308 -0
  4. package/VERSION +1 -0
  5. package/agents/warp-browse.md +715 -0
  6. package/agents/warp-build-code.md +1299 -0
  7. package/agents/warp-orchestrator.md +515 -0
  8. package/agents/warp-plan-architect.md +929 -0
  9. package/agents/warp-plan-brainstorm.md +876 -0
  10. package/agents/warp-plan-design.md +1458 -0
  11. package/agents/warp-plan-onboarding.md +732 -0
  12. package/agents/warp-plan-optimize-adversarial.md +81 -0
  13. package/agents/warp-plan-optimize.md +354 -0
  14. package/agents/warp-plan-scope.md +806 -0
  15. package/agents/warp-plan-security.md +1274 -0
  16. package/agents/warp-plan-testdesign.md +1228 -0
  17. package/agents/warp-qa-debug-adversarial.md +90 -0
  18. package/agents/warp-qa-debug.md +793 -0
  19. package/agents/warp-qa-test-adversarial.md +89 -0
  20. package/agents/warp-qa-test.md +1054 -0
  21. package/agents/warp-release-update.md +1189 -0
  22. package/agents/warp-setup.md +1216 -0
  23. package/agents/warp-upgrade.md +334 -0
  24. package/bin/cli.js +44 -0
  25. package/bin/hooks/_warp_html.sh +291 -0
  26. package/bin/hooks/_warp_json.sh +67 -0
  27. package/bin/hooks/consistency-check.sh +92 -0
  28. package/bin/hooks/identity-briefing.sh +89 -0
  29. package/bin/hooks/identity-foundation.sh +37 -0
  30. package/bin/install.js +343 -0
  31. package/dist/warp-browse/SKILL.md +727 -0
  32. package/dist/warp-build-code/SKILL.md +1316 -0
  33. package/dist/warp-orchestrator/SKILL.md +527 -0
  34. package/dist/warp-plan-architect/SKILL.md +943 -0
  35. package/dist/warp-plan-brainstorm/SKILL.md +890 -0
  36. package/dist/warp-plan-design/SKILL.md +1473 -0
  37. package/dist/warp-plan-onboarding/SKILL.md +742 -0
  38. package/dist/warp-plan-optimize/SKILL.md +364 -0
  39. package/dist/warp-plan-scope/SKILL.md +820 -0
  40. package/dist/warp-plan-security/SKILL.md +1286 -0
  41. package/dist/warp-plan-testdesign/SKILL.md +1244 -0
  42. package/dist/warp-qa-debug/SKILL.md +805 -0
  43. package/dist/warp-qa-test/SKILL.md +1070 -0
  44. package/dist/warp-release-update/SKILL.md +1211 -0
  45. package/dist/warp-setup/SKILL.md +1229 -0
  46. package/dist/warp-upgrade/SKILL.md +345 -0
  47. package/package.json +40 -0
  48. package/shared/project-hooks.json +32 -0
  49. 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('');