@soleri/cli 9.0.2 → 9.3.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/dist/commands/agent.js +116 -3
  2. package/dist/commands/agent.js.map +1 -1
  3. package/dist/commands/create.js +6 -2
  4. package/dist/commands/create.js.map +1 -1
  5. package/dist/commands/hooks.js +36 -13
  6. package/dist/commands/hooks.js.map +1 -1
  7. package/dist/commands/install.d.ts +1 -0
  8. package/dist/commands/install.js +61 -12
  9. package/dist/commands/install.js.map +1 -1
  10. package/dist/commands/pack.js +0 -1
  11. package/dist/commands/pack.js.map +1 -1
  12. package/dist/commands/staging.d.ts +2 -0
  13. package/dist/commands/staging.js +175 -0
  14. package/dist/commands/staging.js.map +1 -0
  15. package/dist/hook-packs/full/manifest.json +2 -2
  16. package/dist/hook-packs/installer.d.ts +4 -11
  17. package/dist/hook-packs/installer.js +197 -23
  18. package/dist/hook-packs/installer.js.map +1 -1
  19. package/dist/hook-packs/installer.ts +223 -38
  20. package/dist/hook-packs/registry.d.ts +16 -13
  21. package/dist/hook-packs/registry.js +11 -18
  22. package/dist/hook-packs/registry.js.map +1 -1
  23. package/dist/hook-packs/registry.ts +31 -30
  24. package/dist/hook-packs/yolo-safety/manifest.json +23 -0
  25. package/dist/hook-packs/yolo-safety/scripts/anti-deletion.sh +214 -0
  26. package/dist/hooks/templates.js +1 -1
  27. package/dist/hooks/templates.js.map +1 -1
  28. package/dist/main.js +2 -0
  29. package/dist/main.js.map +1 -1
  30. package/package.json +1 -1
  31. package/src/__tests__/create.test.ts +6 -2
  32. package/src/__tests__/hook-packs.test.ts +67 -25
  33. package/src/__tests__/wizard-e2e.mjs +153 -58
  34. package/src/commands/agent.ts +146 -3
  35. package/src/commands/create.ts +8 -2
  36. package/src/commands/hooks.ts +36 -31
  37. package/src/commands/install.ts +65 -22
  38. package/src/commands/pack.ts +0 -1
  39. package/src/commands/staging.ts +208 -0
  40. package/src/hook-packs/full/manifest.json +2 -2
  41. package/src/hook-packs/installer.ts +223 -38
  42. package/src/hook-packs/registry.ts +31 -30
  43. package/src/hook-packs/yolo-safety/manifest.json +23 -0
  44. package/src/hook-packs/yolo-safety/scripts/anti-deletion.sh +214 -0
  45. package/src/hooks/templates.ts +1 -1
  46. package/src/main.ts +2 -0
  47. package/dist/commands/cognee.d.ts +0 -10
  48. package/dist/commands/cognee.js +0 -364
  49. package/dist/commands/cognee.js.map +0 -1
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "yolo-safety",
3
+ "version": "1.0.0",
4
+ "description": "Anti-deletion guardrail for YOLO mode — intercepts destructive commands, stages files for review",
5
+ "hooks": [],
6
+ "scripts": [
7
+ {
8
+ "name": "anti-deletion",
9
+ "file": "anti-deletion.sh",
10
+ "targetDir": "hooks"
11
+ }
12
+ ],
13
+ "lifecycleHooks": [
14
+ {
15
+ "event": "PreToolUse",
16
+ "matcher": "Bash",
17
+ "type": "command",
18
+ "command": "bash ~/.claude/hooks/anti-deletion.sh",
19
+ "timeout": 10,
20
+ "statusMessage": "Checking for destructive commands..."
21
+ }
22
+ ]
23
+ }
@@ -0,0 +1,214 @@
1
+ #!/usr/bin/env bash
2
+ # Anti-Deletion Staging Hook for Claude Code (Soleri Hook Pack: yolo-safety)
3
+ # PreToolUse -> Bash: intercepts rm, rmdir, mv (of project dirs), git clean, reset --hard
4
+ # Copies target files to ~/.soleri/staging/<timestamp>/ then blocks the command.
5
+ #
6
+ # Catastrophic commands (rm -rf /, rm -rf ~) should stay in deny rules —
7
+ # this hook handles targeted deletes only.
8
+ #
9
+ # Dependencies: jq (required), perl (optional, for heredoc stripping)
10
+
11
+ set -euo pipefail
12
+
13
+ STAGING_ROOT="$HOME/.soleri/staging"
14
+ PROJECTS_DIR="$HOME/projects"
15
+ INPUT=$(cat)
16
+
17
+ # Extract the command from stdin JSON
18
+ CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
19
+
20
+ # No command found — let it through
21
+ if [ -z "$CMD" ]; then
22
+ exit 0
23
+ fi
24
+
25
+ # --- Strip heredocs and quoted strings to avoid false positives ---
26
+ # Commands like: gh issue comment --body "$(cat <<'EOF' ... rmdir ... EOF)"
27
+ # contain destructive keywords in text, not as actual commands.
28
+
29
+ # Remove heredoc blocks: <<'EOF'...EOF and <<EOF...EOF (multiline)
30
+ STRIPPED=$(echo "$CMD" | perl -0777 -pe "s/<<'?\\w+'?.*?^\\w+$//gms" 2>/dev/null || echo "$CMD")
31
+ # Remove double-quoted strings (greedy but good enough for this check)
32
+ STRIPPED=$(echo "$STRIPPED" | sed -E 's/"[^"]*"//g' 2>/dev/null || echo "$STRIPPED")
33
+ # Remove single-quoted strings
34
+ STRIPPED=$(echo "$STRIPPED" | sed -E "s/'[^']*'//g" 2>/dev/null || echo "$STRIPPED")
35
+
36
+ # --- Detect destructive commands (on stripped command only) ---
37
+
38
+ IS_RM=false
39
+ IS_RMDIR=false
40
+ IS_MV_PROJECT=false
41
+ IS_GIT_CLEAN=false
42
+ IS_RESET_HARD=false
43
+ IS_GIT_CHECKOUT_DOT=false
44
+ IS_GIT_RESTORE_DOT=false
45
+
46
+ # Check for rm commands (but not git rm which is safe — it stages, doesn't destroy)
47
+ if echo "$STRIPPED" | grep -qE '(^|\s|;|&&|\|\|)rm\s'; then
48
+ if ! echo "$STRIPPED" | grep -qE '(^|\s)git\s+rm\s'; then
49
+ IS_RM=true
50
+ fi
51
+ fi
52
+
53
+ # Check for rmdir commands
54
+ if echo "$STRIPPED" | grep -qE '(^|\s|;|&&|\|\|)rmdir\s'; then
55
+ IS_RMDIR=true
56
+ fi
57
+
58
+ # Check for mv commands that move project directories or git repos
59
+ if echo "$STRIPPED" | grep -qE '(^|\s|;|&&|\|\|)mv\s'; then
60
+ MV_SOURCES=$(echo "$STRIPPED" | sed -E 's/^.*\bmv\s+//' | sed -E 's/-(f|i|n|v)\s+//g')
61
+ if echo "$MV_SOURCES" | grep -qE "(~/projects|$HOME/projects|\\\$HOME/projects|\\.git)"; then
62
+ IS_MV_PROJECT=true
63
+ fi
64
+ fi
65
+
66
+ # Check for git clean
67
+ if echo "$STRIPPED" | grep -qE '(^|\s|;|&&|\|\|)git\s+clean\b'; then
68
+ IS_GIT_CLEAN=true
69
+ fi
70
+
71
+ # Check for git reset --hard
72
+ if echo "$STRIPPED" | grep -qE '(^|\s|;|&&|\|\|)git\s+reset\s+--hard'; then
73
+ IS_RESET_HARD=true
74
+ fi
75
+
76
+ # Check for git checkout -- . (restores all files, discards changes)
77
+ if echo "$STRIPPED" | grep -qE '(^|\s|;|&&|\|\|)git\s+checkout\s+--\s+\.'; then
78
+ IS_GIT_CHECKOUT_DOT=true
79
+ fi
80
+
81
+ # Check for git restore . (restores all files, discards changes)
82
+ if echo "$STRIPPED" | grep -qE '(^|\s|;|&&|\|\|)git\s+restore\s+\.'; then
83
+ IS_GIT_RESTORE_DOT=true
84
+ fi
85
+
86
+ # Not a destructive command — let it through
87
+ if [ "$IS_RM" = false ] && [ "$IS_RMDIR" = false ] && [ "$IS_MV_PROJECT" = false ] && \
88
+ [ "$IS_GIT_CLEAN" = false ] && [ "$IS_RESET_HARD" = false ] && \
89
+ [ "$IS_GIT_CHECKOUT_DOT" = false ] && [ "$IS_GIT_RESTORE_DOT" = false ]; then
90
+ exit 0
91
+ fi
92
+
93
+ # --- Handle git clean (block outright) ---
94
+
95
+ if [ "$IS_GIT_CLEAN" = true ]; then
96
+ jq -n '{
97
+ continue: false,
98
+ stopReason: "BLOCKED: git clean would remove untracked files. Use git stash --include-untracked to save them first, or ask the user to run git clean manually."
99
+ }'
100
+ exit 0
101
+ fi
102
+
103
+ # --- Handle git reset --hard (block outright) ---
104
+
105
+ if [ "$IS_RESET_HARD" = true ]; then
106
+ jq -n '{
107
+ continue: false,
108
+ stopReason: "BLOCKED: git reset --hard would discard uncommitted changes. Use git stash to save them first, or ask the user to run this manually."
109
+ }'
110
+ exit 0
111
+ fi
112
+
113
+ # --- Handle git checkout -- . (block outright) ---
114
+
115
+ if [ "$IS_GIT_CHECKOUT_DOT" = true ]; then
116
+ jq -n '{
117
+ continue: false,
118
+ stopReason: "BLOCKED: git checkout -- . would discard all uncommitted changes. Use git stash to save them first, or ask the user to run this manually."
119
+ }'
120
+ exit 0
121
+ fi
122
+
123
+ # --- Handle git restore . (block outright) ---
124
+
125
+ if [ "$IS_GIT_RESTORE_DOT" = true ]; then
126
+ jq -n '{
127
+ continue: false,
128
+ stopReason: "BLOCKED: git restore . would discard all uncommitted changes. Use git stash to save them first, or ask the user to run this manually."
129
+ }'
130
+ exit 0
131
+ fi
132
+
133
+ # --- Handle mv of project directories (block outright) ---
134
+
135
+ if [ "$IS_MV_PROJECT" = true ]; then
136
+ jq -n '{
137
+ continue: false,
138
+ stopReason: "BLOCKED: mv of a project directory or git repo detected. Moving project directories can cause data loss if the operation fails midway. Ask the user to run this manually, or use cp + verify + rm instead."
139
+ }'
140
+ exit 0
141
+ fi
142
+
143
+ # --- Handle rmdir (block outright) ---
144
+
145
+ if [ "$IS_RMDIR" = true ]; then
146
+ jq -n '{
147
+ continue: false,
148
+ stopReason: "BLOCKED: rmdir detected. Removing directories can break project structure. Ask the user to confirm this operation manually."
149
+ }'
150
+ exit 0
151
+ fi
152
+
153
+ # --- Handle rm commands — copy to staging, then block ---
154
+
155
+ # Create timestamped staging directory
156
+ TIMESTAMP=$(date +%Y-%m-%d_%H%M%S)
157
+ STAGE_DIR="$STAGING_ROOT/$TIMESTAMP"
158
+
159
+ # Extract file paths from the rm command
160
+ # Strip rm and its flags, keeping only the file arguments
161
+ FILES=$(echo "$CMD" | sed -E 's/^.*\brm\s+//' | sed -E 's/-(r|f|rf|fr|v|i|rv|fv|rfv|frv)\s+//g' | tr ' ' '\n' | grep -v '^-' | grep -v '^$')
162
+
163
+ if [ -z "$FILES" ]; then
164
+ jq -n '{
165
+ continue: false,
166
+ stopReason: "BLOCKED: rm command detected but could not parse file targets. Please specify files explicitly."
167
+ }'
168
+ exit 0
169
+ fi
170
+
171
+ STAGED=()
172
+ MISSING=()
173
+
174
+ mkdir -p "$STAGE_DIR"
175
+
176
+ while IFS= read -r filepath; do
177
+ # Expand path (handle ~, relative paths)
178
+ expanded=$(eval echo "$filepath" 2>/dev/null || echo "$filepath")
179
+
180
+ if [ -e "$expanded" ]; then
181
+ # Preserve directory structure in staging
182
+ target_dir="$STAGE_DIR/$(dirname "$expanded")"
183
+ mkdir -p "$target_dir"
184
+ # COPY instead of MOVE — originals stay intact, staging is a backup
185
+ if [ -d "$expanded" ]; then
186
+ cp -R "$expanded" "$target_dir/" 2>/dev/null && STAGED+=("$expanded") || MISSING+=("$expanded")
187
+ else
188
+ cp "$expanded" "$target_dir/" 2>/dev/null && STAGED+=("$expanded") || MISSING+=("$expanded")
189
+ fi
190
+ else
191
+ MISSING+=("$expanded")
192
+ fi
193
+ done <<< "$FILES"
194
+
195
+ # Build response
196
+ STAGED_COUNT=${#STAGED[@]}
197
+ MISSING_COUNT=${#MISSING[@]}
198
+
199
+ if [ "$STAGED_COUNT" -eq 0 ] && [ "$MISSING_COUNT" -gt 0 ]; then
200
+ # All files were missing — let the rm fail naturally
201
+ rmdir "$STAGE_DIR" 2>/dev/null || true
202
+ exit 0
203
+ fi
204
+
205
+ STAGED_LIST=$(printf '%s, ' "${STAGED[@]}" | sed 's/, $//')
206
+
207
+ jq -n \
208
+ --arg staged "$STAGED_LIST" \
209
+ --arg dir "$STAGE_DIR" \
210
+ --argjson count "$STAGED_COUNT" \
211
+ '{
212
+ continue: false,
213
+ stopReason: ("BLOCKED & BACKED UP: " + ($count | tostring) + " item(s) copied to " + $dir + " — files: " + $staged + ". The originals are untouched. To proceed with deletion, ask the user to run the rm command manually.")
214
+ }'
@@ -105,7 +105,7 @@ function generateClaudeCodeSettings(dir) {
105
105
  hooks: [
106
106
  {
107
107
  type: 'command',
108
- command: `echo "[${agentId}] session started — register project: ${agentId}_core op:register params:{ projectPath: \\".\\" }" && echo "Check for active plans: ${agentId}_core op:get_plan"`,
108
+ command: `echo "[${agentId}] session started — register project: ${agentId}_core op:session_start params:{ projectPath: \\".\\" }" && echo "Check for active plans: ${agentId}_core op:get_plan"`,
109
109
  },
110
110
  ],
111
111
  },
@@ -1 +1 @@
1
- {"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/hooks/templates.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/hooks/templates.ts"],"names":[],"mappings":"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"}
package/dist/main.js CHANGED
@@ -19,6 +19,7 @@ import { registerPack } from './commands/pack.js';
19
19
  import { registerSkills } from './commands/skills.js';
20
20
  import { registerAgent } from './commands/agent.js';
21
21
  import { registerTelegram } from './commands/telegram.js';
22
+ import { registerStaging } from './commands/staging.js';
22
23
  const require = createRequire(import.meta.url);
23
24
  const { version } = require('../package.json');
24
25
  const RESET = '\x1b[0m';
@@ -73,5 +74,6 @@ registerPack(program);
73
74
  registerSkills(program);
74
75
  registerAgent(program);
75
76
  registerTelegram(program);
77
+ registerStaging(program);
76
78
  program.parse();
77
79
  //# sourceMappingURL=main.js.map
package/dist/main.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soleri/cli",
3
- "version": "9.0.2",
3
+ "version": "9.3.0",
4
4
  "description": "Developer CLI for creating and managing Soleri AI agents.",
5
5
  "keywords": [
6
6
  "agent",
@@ -6,7 +6,7 @@ import { previewScaffold, scaffold } from '@soleri/forge/lib';
6
6
  import type { AgentConfig } from '@soleri/forge/lib';
7
7
  import { installPack } from '../hook-packs/installer.js';
8
8
 
9
- describe('create command', () => {
9
+ describe('create command', { timeout: 30_000 }, () => {
10
10
  let tempDir: string;
11
11
 
12
12
  const testConfig: AgentConfig = {
@@ -81,7 +81,11 @@ describe('create command', () => {
81
81
  ),
82
82
  );
83
83
  expect(testingBundle.domain).toBe('testing');
84
- expect(testingBundle.entries).toEqual([]);
84
+ expect(testingBundle.entries.length).toBeGreaterThanOrEqual(0);
85
+ if (testingBundle.entries.length > 0) {
86
+ expect(testingBundle.entries[0].id).toBe('testing-seed');
87
+ expect(testingBundle.entries[0].tags).toContain('seed');
88
+ }
85
89
  });
86
90
 
87
91
  it('should read config from file for non-interactive mode', () => {
@@ -3,7 +3,6 @@ import { mkdirSync, rmSync, existsSync, readFileSync, writeFileSync } from 'node
3
3
  import { join } from 'node:path';
4
4
  import { tmpdir } from 'node:os';
5
5
 
6
- // Mock homedir to use a temp directory instead of real ~/.claude/
7
6
  const tempHome = join(tmpdir(), `cli-hookpacks-test-${Date.now()}`);
8
7
 
9
8
  vi.mock('node:os', async () => {
@@ -24,10 +23,9 @@ describe('hook-packs', () => {
24
23
  });
25
24
 
26
25
  describe('registry', () => {
27
- it('should list all 5 built-in packs', () => {
26
+ it('should list all 6 built-in packs', () => {
28
27
  const packs = listPacks();
29
- expect(packs.length).toBe(5);
30
-
28
+ expect(packs.length).toBe(6);
31
29
  const names = packs.map((p) => p.name).sort();
32
30
  expect(names).toEqual([
33
31
  'a11y',
@@ -35,6 +33,7 @@ describe('hook-packs', () => {
35
33
  'css-discipline',
36
34
  'full',
37
35
  'typescript-safety',
36
+ 'yolo-safety',
38
37
  ]);
39
38
  });
40
39
 
@@ -50,7 +49,7 @@ describe('hook-packs', () => {
50
49
  expect(getPack('nonexistent')).toBeNull();
51
50
  });
52
51
 
53
- it('should return full pack with composedFrom', () => {
52
+ it('should return full pack with composedFrom including yolo-safety', () => {
54
53
  const pack = getPack('full');
55
54
  expect(pack).not.toBeNull();
56
55
  expect(pack!.manifest.composedFrom).toEqual([
@@ -58,27 +57,36 @@ describe('hook-packs', () => {
58
57
  'a11y',
59
58
  'css-discipline',
60
59
  'clean-commits',
60
+ 'yolo-safety',
61
61
  ]);
62
62
  expect(pack!.manifest.hooks).toHaveLength(8);
63
63
  });
64
64
 
65
65
  it('should return empty installed packs when none installed', () => {
66
- expect(getInstalledPacks()).toEqual([]);
66
+ const installed = getInstalledPacks();
67
+ expect(installed.filter((p) => p !== 'yolo-safety')).toEqual([]);
68
+ });
69
+
70
+ it('should get yolo-safety pack with scripts and lifecycleHooks', () => {
71
+ const pack = getPack('yolo-safety');
72
+ expect(pack).not.toBeNull();
73
+ expect(pack!.manifest.name).toBe('yolo-safety');
74
+ expect(pack!.manifest.hooks).toEqual([]);
75
+ expect(pack!.manifest.scripts).toHaveLength(1);
76
+ expect(pack!.manifest.scripts![0].name).toBe('anti-deletion');
77
+ expect(pack!.manifest.lifecycleHooks).toHaveLength(1);
78
+ expect(pack!.manifest.lifecycleHooks![0].event).toBe('PreToolUse');
67
79
  });
68
80
  });
69
81
 
70
82
  describe('installer', () => {
71
83
  it('should install a simple pack', () => {
72
84
  const result = installPack('typescript-safety');
73
-
74
85
  expect(result.installed).toEqual(['no-any-types', 'no-console-log']);
75
86
  expect(result.skipped).toEqual([]);
76
-
77
87
  const claudeDir = join(tempHome, '.claude');
78
88
  expect(existsSync(join(claudeDir, 'hookify.no-any-types.local.md'))).toBe(true);
79
89
  expect(existsSync(join(claudeDir, 'hookify.no-console-log.local.md'))).toBe(true);
80
-
81
- // Verify content was copied correctly
82
90
  const content = readFileSync(join(claudeDir, 'hookify.no-any-types.local.md'), 'utf-8');
83
91
  expect(content).toContain('name: no-any-types');
84
92
  expect(content).toContain('Soleri Hook Pack: typescript-safety');
@@ -87,19 +95,16 @@ describe('hook-packs', () => {
87
95
  it('should be idempotent — skip existing files', () => {
88
96
  installPack('typescript-safety');
89
97
  const result = installPack('typescript-safety');
90
-
91
98
  expect(result.installed).toEqual([]);
92
99
  expect(result.skipped).toEqual(['no-any-types', 'no-console-log']);
93
100
  });
94
101
 
95
102
  it('should install composed pack (full)', () => {
96
103
  const result = installPack('full');
97
-
98
104
  expect(result.installed).toHaveLength(8);
99
105
  expect(result.skipped).toEqual([]);
100
-
101
106
  const claudeDir = join(tempHome, '.claude');
102
- const expectedHooks = [
107
+ for (const hook of [
103
108
  'no-any-types',
104
109
  'no-console-log',
105
110
  'no-important',
@@ -108,19 +113,20 @@ describe('hook-packs', () => {
108
113
  'focus-ring-required',
109
114
  'ux-touch-targets',
110
115
  'no-ai-attribution',
111
- ];
112
- for (const hook of expectedHooks) {
116
+ ]) {
113
117
  expect(existsSync(join(claudeDir, `hookify.${hook}.local.md`))).toBe(true);
114
118
  }
119
+ expect(result.scripts).toHaveLength(1);
120
+ expect(result.scripts[0]).toBe('hooks/anti-deletion.sh');
121
+ expect(existsSync(join(claudeDir, 'hooks', 'anti-deletion.sh'))).toBe(true);
115
122
  });
116
123
 
117
124
  it('should skip already-installed hooks when installing full after partial', () => {
118
125
  installPack('typescript-safety');
119
126
  const result = installPack('full');
120
-
121
127
  expect(result.skipped).toContain('no-any-types');
122
128
  expect(result.skipped).toContain('no-console-log');
123
- expect(result.installed).toHaveLength(6); // 8 total - 2 already installed
129
+ expect(result.installed).toHaveLength(6);
124
130
  });
125
131
 
126
132
  it('should throw for unknown pack', () => {
@@ -130,9 +136,7 @@ describe('hook-packs', () => {
130
136
  it('should remove a pack', () => {
131
137
  installPack('a11y');
132
138
  const result = removePack('a11y');
133
-
134
139
  expect(result.removed).toEqual(['semantic-html', 'focus-ring-required', 'ux-touch-targets']);
135
-
136
140
  const claudeDir = join(tempHome, '.claude');
137
141
  expect(existsSync(join(claudeDir, 'hookify.semantic-html.local.md'))).toBe(false);
138
142
  });
@@ -145,6 +149,42 @@ describe('hook-packs', () => {
145
149
  it('should throw for unknown pack on remove', () => {
146
150
  expect(() => removePack('nonexistent')).toThrow('Unknown hook pack: "nonexistent"');
147
151
  });
152
+
153
+ it('should install yolo-safety pack with scripts and lifecycle hooks', () => {
154
+ const result = installPack('yolo-safety');
155
+ expect(result.installed).toEqual([]);
156
+ expect(result.scripts).toHaveLength(1);
157
+ expect(result.scripts[0]).toBe('hooks/anti-deletion.sh');
158
+ expect(result.lifecycleHooks).toHaveLength(1);
159
+ expect(result.lifecycleHooks[0]).toBe('PreToolUse:Bash');
160
+ const claudeDir = join(tempHome, '.claude');
161
+ expect(existsSync(join(claudeDir, 'hooks', 'anti-deletion.sh'))).toBe(true);
162
+ const settings = JSON.parse(readFileSync(join(claudeDir, 'settings.json'), 'utf-8'));
163
+ expect(settings.hooks).toBeDefined();
164
+ expect(settings.hooks.PreToolUse).toHaveLength(1);
165
+ expect(settings.hooks.PreToolUse[0].command).toBe('bash ~/.claude/hooks/anti-deletion.sh');
166
+ expect(settings.hooks.PreToolUse[0]._soleriPack).toBe('yolo-safety');
167
+ });
168
+
169
+ it('should remove yolo-safety pack including scripts and lifecycle hooks', () => {
170
+ installPack('yolo-safety');
171
+ const result = removePack('yolo-safety');
172
+ expect(result.scripts).toHaveLength(1);
173
+ expect(result.lifecycleHooks).toHaveLength(1);
174
+ const claudeDir = join(tempHome, '.claude');
175
+ expect(existsSync(join(claudeDir, 'hooks', 'anti-deletion.sh'))).toBe(false);
176
+ const settings = JSON.parse(readFileSync(join(claudeDir, 'settings.json'), 'utf-8'));
177
+ expect(settings.hooks.PreToolUse).toBeUndefined();
178
+ });
179
+
180
+ it('should be idempotent for yolo-safety lifecycle hooks', () => {
181
+ installPack('yolo-safety');
182
+ const result2 = installPack('yolo-safety');
183
+ expect(result2.lifecycleHooks).toEqual([]);
184
+ const claudeDir = join(tempHome, '.claude');
185
+ const settings = JSON.parse(readFileSync(join(claudeDir, 'settings.json'), 'utf-8'));
186
+ expect(settings.hooks.PreToolUse).toHaveLength(1);
187
+ });
148
188
  });
149
189
 
150
190
  describe('isPackInstalled', () => {
@@ -158,7 +198,6 @@ describe('hook-packs', () => {
158
198
  });
159
199
 
160
200
  it('should return partial when some hooks present', () => {
161
- // Install just one of the two hooks
162
201
  const claudeDir = join(tempHome, '.claude');
163
202
  writeFileSync(join(claudeDir, 'hookify.no-any-types.local.md'), 'test');
164
203
  expect(isPackInstalled('typescript-safety')).toBe('partial');
@@ -167,29 +206,32 @@ describe('hook-packs', () => {
167
206
  it('should return false for unknown pack', () => {
168
207
  expect(isPackInstalled('nonexistent')).toBe(false);
169
208
  });
209
+
210
+ it('should detect yolo-safety as installed when script is present', () => {
211
+ installPack('yolo-safety');
212
+ expect(isPackInstalled('yolo-safety')).toBe(true);
213
+ });
170
214
  });
171
215
 
172
216
  describe('getInstalledPacks', () => {
173
217
  it('should list installed packs', () => {
174
218
  installPack('typescript-safety');
175
219
  installPack('a11y');
176
-
177
220
  const installed = getInstalledPacks();
178
221
  expect(installed).toContain('typescript-safety');
179
222
  expect(installed).toContain('a11y');
180
223
  expect(installed).not.toContain('css-discipline');
181
224
  });
182
225
 
183
- it('should include full when all 8 hooks are present', () => {
226
+ it('should include full when all hooks and scripts are present', () => {
184
227
  installPack('full');
185
-
186
228
  const installed = getInstalledPacks();
187
- // All packs should show as installed since full installs all hooks
188
229
  expect(installed).toContain('full');
189
230
  expect(installed).toContain('typescript-safety');
190
231
  expect(installed).toContain('a11y');
191
232
  expect(installed).toContain('css-discipline');
192
233
  expect(installed).toContain('clean-commits');
234
+ expect(installed).toContain('yolo-safety');
193
235
  });
194
236
  });
195
237
  });