buddy-reroll 0.3.2 → 0.3.3

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/index.js CHANGED
@@ -490,7 +490,22 @@ async function main() {
490
490
  }
491
491
 
492
492
  if (args.doctor) {
493
- console.log(`\n${formatDoctorReport(getDoctorReport(), "buddy-reroll doctor")}\n`);
493
+ const report = getDoctorReport();
494
+ if (report.status === "not-executable" && report.binaryPath) {
495
+ try {
496
+ const { chmodSync, statSync } = await import("fs");
497
+ const mode = statSync(report.binaryPath).mode | 0o111;
498
+ chmodSync(report.binaryPath, mode);
499
+ console.log(`\n ✓ Fixed: restored execute permission on ${report.binaryPath}`);
500
+ const fixed = getDoctorReport();
501
+ console.log(`\n${formatDoctorReport(fixed, "buddy-reroll doctor")}\n`);
502
+ } catch (err) {
503
+ console.log(`\n${formatDoctorReport(report, "buddy-reroll doctor")}\n`);
504
+ console.log(` ⚠ Auto-fix failed: ${err.message}\n Run manually: chmod +x "${report.binaryPath}"\n`);
505
+ }
506
+ } else {
507
+ console.log(`\n${formatDoctorReport(report, "buddy-reroll doctor")}\n`);
508
+ }
494
509
  return;
495
510
  }
496
511
 
package/lib/doctor.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { accessSync, constants, chmodSync, statSync } from "fs";
1
2
  import { getClaudeBinaryOverride, getClaudeConfigDir, findBinaryPath, findConfigPath, getPatchability } from "./runtime.js";
2
3
 
3
4
  function buildStatus(binaryPath, configPath, patchability) {
@@ -5,6 +6,11 @@ function buildStatus(binaryPath, configPath, patchability) {
5
6
  if (!binaryPath) return "missing-binary";
6
7
  if (!configPath) return "missing-config";
7
8
  if (!patchability.ok) return "read-only";
9
+ try {
10
+ accessSync(binaryPath, constants.X_OK);
11
+ } catch {
12
+ return "not-executable";
13
+ }
8
14
  return "ready";
9
15
  }
10
16
 
@@ -18,6 +24,8 @@ function buildNextStep(status) {
18
24
  return "Open Claude Code once so it writes config, or point `CLAUDE_CONFIG_DIR` to the correct config directory.";
19
25
  case "read-only":
20
26
  return "Use a user-writable Claude install, or point `CLAUDE_BINARY_PATH` to a writable Claude binary copy; `--current` can still work with a read-only install.";
27
+ case "not-executable":
28
+ return "Claude binary lost its execute permission (likely from a patch). Run: chmod +x <binary-path>";
21
29
  default:
22
30
  return "`--current` and reroll commands are ready to run on this machine.";
23
31
  }
package/lib/hooks.js CHANGED
@@ -4,14 +4,23 @@ import { homedir } from "os";
4
4
 
5
5
  const HOOK_COMMAND = "npx buddy-reroll --apply-hook";
6
6
 
7
+ const HOOK_ENTRY = {
8
+ matcher: "",
9
+ hooks: [{ type: "command", command: HOOK_COMMAND }],
10
+ };
11
+
12
+ function isOurHook(entry) {
13
+ if (typeof entry === "string") return entry === HOOK_COMMAND;
14
+ if (entry?.hooks) return entry.hooks.some((h) => h.command === HOOK_COMMAND);
15
+ return false;
16
+ }
17
+
7
18
  export function getSettingsPath() {
8
19
  return join(process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), ".claude"), "settings.json");
9
20
  }
10
21
 
11
22
  export function readSettings(settingsPath) {
12
- if (!existsSync(settingsPath)) {
13
- return {};
14
- }
23
+ if (!existsSync(settingsPath)) return {};
15
24
  try {
16
25
  return JSON.parse(readFileSync(settingsPath, "utf-8"));
17
26
  } catch {
@@ -27,49 +36,46 @@ export function writeSettings(settingsPath, obj) {
27
36
 
28
37
  export function installHook(settingsPath = getSettingsPath()) {
29
38
  const settings = readSettings(settingsPath);
30
-
31
- if (!settings.hooks) {
32
- settings.hooks = {};
33
- }
34
- if (!settings.hooks.SessionStart) {
35
- settings.hooks.SessionStart = [];
36
- }
37
-
38
- if (settings.hooks.SessionStart.includes(HOOK_COMMAND)) {
39
+
40
+ if (!settings.hooks) settings.hooks = {};
41
+ if (!Array.isArray(settings.hooks.SessionStart)) settings.hooks.SessionStart = [];
42
+
43
+ const existing = settings.hooks.SessionStart.find(isOurHook);
44
+ if (existing && typeof existing === "object") {
39
45
  return { installed: false, reason: "already installed" };
40
46
  }
41
-
42
- settings.hooks.SessionStart.push(HOOK_COMMAND);
47
+
48
+ settings.hooks.SessionStart = settings.hooks.SessionStart.filter((e) => !isOurHook(e));
49
+ settings.hooks.SessionStart.push(HOOK_ENTRY);
43
50
  writeSettings(settingsPath, settings);
44
-
51
+
45
52
  return { installed: true, path: settingsPath };
46
53
  }
47
54
 
48
55
  export function removeHook(settingsPath = getSettingsPath()) {
49
56
  const settings = readSettings(settingsPath);
50
-
51
- if (!settings.hooks || !settings.hooks.SessionStart) {
57
+
58
+ if (!settings.hooks || !Array.isArray(settings.hooks.SessionStart)) {
52
59
  return { removed: false, reason: "not installed" };
53
60
  }
54
-
55
- settings.hooks.SessionStart = settings.hooks.SessionStart.filter(cmd => cmd !== HOOK_COMMAND);
56
-
57
- if (settings.hooks.SessionStart.length === 0) {
58
- delete settings.hooks.SessionStart;
59
- }
60
-
61
- if (Object.keys(settings.hooks).length === 0) {
62
- delete settings.hooks;
61
+
62
+ const before = settings.hooks.SessionStart.length;
63
+ settings.hooks.SessionStart = settings.hooks.SessionStart.filter((e) => !isOurHook(e));
64
+
65
+ if (settings.hooks.SessionStart.length === before) {
66
+ return { removed: false, reason: "not installed" };
63
67
  }
64
-
68
+
69
+ if (settings.hooks.SessionStart.length === 0) delete settings.hooks.SessionStart;
70
+ if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
71
+
65
72
  writeSettings(settingsPath, settings);
66
-
67
73
  return { removed: true, path: settingsPath };
68
74
  }
69
75
 
70
76
  export function isHookInstalled(settingsPath = getSettingsPath()) {
71
77
  const settings = readSettings(settingsPath);
72
- return settings.hooks?.SessionStart?.includes(HOOK_COMMAND) ?? false;
78
+ return Array.isArray(settings.hooks?.SessionStart) && settings.hooks.SessionStart.some(isOurHook);
73
79
  }
74
80
 
75
81
  export function getSaltStorePath() {
@@ -85,9 +91,7 @@ export function storeSalt(salt) {
85
91
 
86
92
  export function readStoredSalt() {
87
93
  const path = getSaltStorePath();
88
- if (!existsSync(path)) {
89
- return null;
90
- }
94
+ if (!existsSync(path)) return null;
91
95
  try {
92
96
  return JSON.parse(readFileSync(path, "utf-8"));
93
97
  } catch {
package/lib/hooks.test.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { describe, it, expect, beforeEach, afterEach } from "bun:test";
2
2
  import { mkdtempSync, rmSync, writeFileSync, readFileSync } from "fs";
3
3
  import { join } from "path";
4
- import { tmpdir } from "os";
4
+ import { tmpdir, homedir } from "os";
5
+ import { realpathSync } from "fs";
5
6
  import {
6
7
  readSettings,
7
8
  writeSettings,
@@ -12,10 +13,19 @@ import {
12
13
  readStoredSalt,
13
14
  } from "./hooks.js";
14
15
 
16
+ const HOOK_COMMAND = "npx buddy-reroll --apply-hook";
17
+
18
+ function findOurHook(settings) {
19
+ const entries = settings.hooks?.SessionStart ?? [];
20
+ return entries.find(
21
+ (e) => typeof e === "object" && e.hooks?.some((h) => h.command === HOOK_COMMAND)
22
+ );
23
+ }
24
+
15
25
  let tempDir;
16
26
 
17
27
  beforeEach(() => {
18
- tempDir = mkdtempSync(join(tmpdir(), "buddy-reroll-test-"));
28
+ tempDir = mkdtempSync(join(realpathSync(tmpdir()), "buddy-reroll-test-"));
19
29
  process.env.CLAUDE_CONFIG_DIR = tempDir;
20
30
  });
21
31
 
@@ -26,215 +36,203 @@ afterEach(() => {
26
36
 
27
37
  describe("readSettings", () => {
28
38
  it("returns empty object when file doesn't exist", () => {
29
- const settingsPath = join(tempDir, "settings.json");
30
- expect(readSettings(settingsPath)).toEqual({});
39
+ expect(readSettings(join(tempDir, "settings.json"))).toEqual({});
31
40
  });
32
41
 
33
42
  it("parses valid JSON file", () => {
34
- const settingsPath = join(tempDir, "settings.json");
35
- writeSettings(settingsPath, { permissions: { allow: [] } });
36
- expect(readSettings(settingsPath)).toEqual({ permissions: { allow: [] } });
43
+ const p = join(tempDir, "settings.json");
44
+ writeSettings(p, { permissions: { allow: [] } });
45
+ expect(readSettings(p)).toEqual({ permissions: { allow: [] } });
37
46
  });
38
47
 
39
48
  it("returns empty object for corrupted JSON", () => {
40
- const settingsPath = join(tempDir, "settings.json");
41
- writeFileSync(settingsPath, "{ invalid json");
42
- expect(readSettings(settingsPath)).toEqual({});
49
+ const p = join(tempDir, "settings.json");
50
+ writeFileSync(p, "{ invalid json");
51
+ expect(readSettings(p)).toEqual({});
43
52
  });
44
53
  });
45
54
 
46
55
  describe("writeSettings", () => {
47
56
  it("creates parent directories if needed", () => {
48
- const settingsPath = join(tempDir, "nested", "dir", "settings.json");
49
- writeSettings(settingsPath, { test: true });
50
- expect(readSettings(settingsPath)).toEqual({ test: true });
57
+ const p = join(tempDir, "nested", "dir", "settings.json");
58
+ writeSettings(p, { test: true });
59
+ expect(readSettings(p)).toEqual({ test: true });
51
60
  });
52
61
 
53
- it("writes JSON with 2-space indent and newline", () => {
54
- const settingsPath = join(tempDir, "settings.json");
55
- writeSettings(settingsPath, { a: 1, b: 2 });
56
- const content = readFileSync(settingsPath, "utf-8");
62
+ it("writes JSON with 2-space indent and trailing newline", () => {
63
+ const p = join(tempDir, "settings.json");
64
+ writeSettings(p, { a: 1 });
65
+ const content = readFileSync(p, "utf-8");
57
66
  expect(content).toContain(" ");
58
67
  expect(content.endsWith("\n")).toBe(true);
59
68
  });
60
69
  });
61
70
 
62
71
  describe("installHook", () => {
63
- it("creates settings.json if missing", () => {
64
- const settingsPath = join(tempDir, "settings.json");
65
- const result = installHook(settingsPath);
72
+ it("creates settings.json with correct hook format", () => {
73
+ const p = join(tempDir, "settings.json");
74
+ const result = installHook(p);
66
75
  expect(result.installed).toBe(true);
67
- const settings = readSettings(settingsPath);
68
- expect(settings.hooks.SessionStart).toContain("npx buddy-reroll --apply-hook");
69
- });
70
-
71
- it("adds hook to existing settings", () => {
72
- const settingsPath = join(tempDir, "settings.json");
73
- writeSettings(settingsPath, { permissions: { allow: [] } });
74
- installHook(settingsPath);
75
- const settings = readSettings(settingsPath);
76
- expect(settings.permissions).toEqual({ allow: [] });
77
- expect(settings.hooks.SessionStart).toContain("npx buddy-reroll --apply-hook");
78
- });
79
-
80
- it("is idempotent - doesn't duplicate hook", () => {
81
- const settingsPath = join(tempDir, "settings.json");
82
- installHook(settingsPath);
83
- const result = installHook(settingsPath);
76
+ const settings = readSettings(p);
77
+ const hook = findOurHook(settings);
78
+ expect(hook).toBeDefined();
79
+ expect(hook.matcher).toBe("");
80
+ expect(hook.hooks[0].type).toBe("command");
81
+ expect(hook.hooks[0].command).toBe(HOOK_COMMAND);
82
+ });
83
+
84
+ it("adds hook to existing settings without clobbering", () => {
85
+ const p = join(tempDir, "settings.json");
86
+ writeSettings(p, { permissions: { allow: ["test"] } });
87
+ installHook(p);
88
+ const settings = readSettings(p);
89
+ expect(settings.permissions).toEqual({ allow: ["test"] });
90
+ expect(findOurHook(settings)).toBeDefined();
91
+ });
92
+
93
+ it("is idempotent", () => {
94
+ const p = join(tempDir, "settings.json");
95
+ installHook(p);
96
+ const result = installHook(p);
84
97
  expect(result.installed).toBe(false);
85
98
  expect(result.reason).toBe("already installed");
86
- const settings = readSettings(settingsPath);
87
- const count = settings.hooks.SessionStart.filter(cmd => cmd === "npx buddy-reroll --apply-hook").length;
88
- expect(count).toBe(1);
89
- });
90
-
91
- it("preserves existing hooks in SessionStart array", () => {
92
- const settingsPath = join(tempDir, "settings.json");
93
- writeSettings(settingsPath, {
94
- hooks: { SessionStart: ["existing-hook"] },
95
- });
96
- installHook(settingsPath);
97
- const settings = readSettings(settingsPath);
98
- expect(settings.hooks.SessionStart).toContain("existing-hook");
99
- expect(settings.hooks.SessionStart).toContain("npx buddy-reroll --apply-hook");
99
+ const settings = readSettings(p);
100
+ const hooks = settings.hooks.SessionStart.filter(
101
+ (e) => typeof e === "object" && e.hooks?.some((h) => h.command === HOOK_COMMAND)
102
+ );
103
+ expect(hooks.length).toBe(1);
104
+ });
105
+
106
+ it("migrates old string-format hook to new format", () => {
107
+ const p = join(tempDir, "settings.json");
108
+ writeSettings(p, { hooks: { SessionStart: [HOOK_COMMAND] } });
109
+ installHook(p);
110
+ const settings = readSettings(p);
111
+ const strings = settings.hooks.SessionStart.filter((e) => typeof e === "string");
112
+ expect(strings.length).toBe(0);
113
+ expect(findOurHook(settings)).toBeDefined();
114
+ });
115
+
116
+ it("preserves other hooks in SessionStart array", () => {
117
+ const p = join(tempDir, "settings.json");
118
+ const otherHook = { matcher: "Bash", hooks: [{ type: "command", command: "echo hi" }] };
119
+ writeSettings(p, { hooks: { SessionStart: [otherHook] } });
120
+ installHook(p);
121
+ const settings = readSettings(p);
122
+ expect(settings.hooks.SessionStart.length).toBe(2);
123
+ expect(settings.hooks.SessionStart[0]).toEqual(otherHook);
100
124
  });
101
125
  });
102
126
 
103
127
  describe("removeHook", () => {
104
- it("removes hook from SessionStart", () => {
105
- const settingsPath = join(tempDir, "settings.json");
106
- installHook(settingsPath);
107
- const result = removeHook(settingsPath);
128
+ it("removes hook", () => {
129
+ const p = join(tempDir, "settings.json");
130
+ installHook(p);
131
+ const result = removeHook(p);
108
132
  expect(result.removed).toBe(true);
109
- const settings = readSettings(settingsPath);
110
- expect(settings.hooks?.SessionStart?.includes("npx buddy-reroll --apply-hook") ?? false).toBe(false);
133
+ expect(findOurHook(readSettings(p))).toBeUndefined();
111
134
  });
112
135
 
113
- it("deletes empty SessionStart array", () => {
114
- const settingsPath = join(tempDir, "settings.json");
115
- installHook(settingsPath);
116
- removeHook(settingsPath);
117
- const settings = readSettings(settingsPath);
118
- expect(settings.hooks?.SessionStart).toBeUndefined();
136
+ it("removes old string-format hook too", () => {
137
+ const p = join(tempDir, "settings.json");
138
+ writeSettings(p, { hooks: { SessionStart: [HOOK_COMMAND] } });
139
+ const result = removeHook(p);
140
+ expect(result.removed).toBe(true);
119
141
  });
120
142
 
121
- it("deletes empty hooks object", () => {
122
- const settingsPath = join(tempDir, "settings.json");
123
- installHook(settingsPath);
124
- removeHook(settingsPath);
125
- const settings = readSettings(settingsPath);
143
+ it("deletes empty SessionStart and hooks", () => {
144
+ const p = join(tempDir, "settings.json");
145
+ installHook(p);
146
+ removeHook(p);
147
+ const settings = readSettings(p);
126
148
  expect(settings.hooks).toBeUndefined();
127
149
  });
128
150
 
129
- it("returns false when hook not installed", () => {
130
- const settingsPath = join(tempDir, "settings.json");
131
- const result = removeHook(settingsPath);
132
- expect(result.removed).toBe(false);
133
- expect(result.reason).toBe("not installed");
151
+ it("returns false when not installed", () => {
152
+ const p = join(tempDir, "settings.json");
153
+ expect(removeHook(p).removed).toBe(false);
134
154
  });
135
155
 
136
- it("preserves other hooks in SessionStart", () => {
137
- const settingsPath = join(tempDir, "settings.json");
138
- writeSettings(settingsPath, {
139
- hooks: { SessionStart: ["other-hook", "npx buddy-reroll --apply-hook"] },
140
- });
141
- removeHook(settingsPath);
142
- const settings = readSettings(settingsPath);
143
- expect(settings.hooks.SessionStart).toContain("other-hook");
144
- expect(settings.hooks.SessionStart).not.toContain("npx buddy-reroll --apply-hook");
156
+ it("preserves other hooks", () => {
157
+ const p = join(tempDir, "settings.json");
158
+ const otherHook = { matcher: "Bash", hooks: [{ type: "command", command: "echo hi" }] };
159
+ writeSettings(p, { hooks: { SessionStart: [otherHook, HOOK_COMMAND] } });
160
+ removeHook(p);
161
+ const settings = readSettings(p);
162
+ expect(settings.hooks.SessionStart).toEqual([otherHook]);
145
163
  });
146
164
 
147
165
  it("preserves other hook types", () => {
148
- const settingsPath = join(tempDir, "settings.json");
149
- writeSettings(settingsPath, {
150
- hooks: {
151
- SessionStart: ["npx buddy-reroll --apply-hook"],
152
- OnExit: ["some-command"],
153
- },
154
- });
155
- removeHook(settingsPath);
156
- const settings = readSettings(settingsPath);
157
- expect(settings.hooks.OnExit).toContain("some-command");
166
+ const p = join(tempDir, "settings.json");
167
+ installHook(p);
168
+ const settings = readSettings(p);
169
+ settings.hooks.PostToolUse = [{ matcher: "Edit", hooks: [{ type: "command", command: "lint" }] }];
170
+ writeSettings(p, settings);
171
+ removeHook(p);
172
+ const after = readSettings(p);
173
+ expect(after.hooks.PostToolUse).toBeDefined();
158
174
  });
159
175
  });
160
176
 
161
177
  describe("isHookInstalled", () => {
162
- it("returns true after install", () => {
163
- const settingsPath = join(tempDir, "settings.json");
164
- installHook(settingsPath);
165
- expect(isHookInstalled(settingsPath)).toBe(true);
178
+ it("true after install", () => {
179
+ const p = join(tempDir, "settings.json");
180
+ installHook(p);
181
+ expect(isHookInstalled(p)).toBe(true);
166
182
  });
167
183
 
168
- it("returns false after remove", () => {
169
- const settingsPath = join(tempDir, "settings.json");
170
- installHook(settingsPath);
171
- removeHook(settingsPath);
172
- expect(isHookInstalled(settingsPath)).toBe(false);
184
+ it("false after remove", () => {
185
+ const p = join(tempDir, "settings.json");
186
+ installHook(p);
187
+ removeHook(p);
188
+ expect(isHookInstalled(p)).toBe(false);
173
189
  });
174
190
 
175
- it("returns false when settings don't exist", () => {
176
- const settingsPath = join(tempDir, "settings.json");
177
- expect(isHookInstalled(settingsPath)).toBe(false);
191
+ it("false when no file", () => {
192
+ expect(isHookInstalled(join(tempDir, "settings.json"))).toBe(false);
178
193
  });
179
194
 
180
- it("returns false when hooks don't exist", () => {
181
- const settingsPath = join(tempDir, "settings.json");
182
- writeSettings(settingsPath, { permissions: { allow: [] } });
183
- expect(isHookInstalled(settingsPath)).toBe(false);
195
+ it("detects old string-format as installed", () => {
196
+ const p = join(tempDir, "settings.json");
197
+ writeSettings(p, { hooks: { SessionStart: [HOOK_COMMAND] } });
198
+ expect(isHookInstalled(p)).toBe(true);
184
199
  });
185
200
  });
186
201
 
187
202
  describe("storeSalt and readStoredSalt", () => {
188
- it("roundtrips salt with timestamp", () => {
189
- const salt = "test-salt-value";
190
- storeSalt(salt);
203
+ it("roundtrips salt", () => {
204
+ storeSalt("test-salt");
191
205
  const stored = readStoredSalt();
192
- expect(stored.salt).toBe(salt);
193
- expect(typeof stored.timestamp).toBe("number");
206
+ expect(stored.salt).toBe("test-salt");
194
207
  expect(stored.timestamp).toBeGreaterThan(0);
195
208
  });
196
209
 
197
- it("returns null when file doesn't exist", () => {
210
+ it("returns null when missing", () => {
198
211
  expect(readStoredSalt()).toBeNull();
199
212
  });
200
213
 
201
- it("returns null for corrupted salt file", () => {
202
- const saltPath = join(tempDir, ".buddy-reroll.json");
203
- writeFileSync(saltPath, "{ invalid json");
214
+ it("returns null for corrupted file", () => {
215
+ writeFileSync(join(tempDir, ".buddy-reroll.json"), "broken");
204
216
  expect(readStoredSalt()).toBeNull();
205
217
  });
206
-
207
- it("creates parent directories for salt file", () => {
208
- const salt = "another-test-salt";
209
- storeSalt(salt);
210
- const stored = readStoredSalt();
211
- expect(stored.salt).toBe(salt);
212
- });
213
218
  });
214
219
 
215
220
  describe("integration", () => {
216
- it("preserves existing settings when installing hook", () => {
217
- const settingsPath = join(tempDir, "settings.json");
218
- writeSettings(settingsPath, {
221
+ it("preserves complex settings through install and remove", () => {
222
+ const p = join(tempDir, "settings.json");
223
+ writeSettings(p, {
219
224
  permissions: { allow: ["some-permission"] },
220
225
  other: { nested: { value: 42 } },
221
226
  });
222
- installHook(settingsPath);
223
- const settings = readSettings(settingsPath);
224
- expect(settings.permissions).toEqual({ allow: ["some-permission"] });
225
- expect(settings.other).toEqual({ nested: { value: 42 } });
226
- expect(settings.hooks.SessionStart).toContain("npx buddy-reroll --apply-hook");
227
- });
228
-
229
- it("preserves existing settings when removing hook", () => {
230
- const settingsPath = join(tempDir, "settings.json");
231
- writeSettings(settingsPath, {
232
- permissions: { allow: ["some-permission"] },
233
- hooks: { SessionStart: ["npx buddy-reroll --apply-hook"] },
234
- });
235
- removeHook(settingsPath);
236
- const settings = readSettings(settingsPath);
237
- expect(settings.permissions).toEqual({ allow: ["some-permission"] });
238
- expect(settings.hooks).toBeUndefined();
227
+ installHook(p);
228
+ const after = readSettings(p);
229
+ expect(after.permissions).toEqual({ allow: ["some-permission"] });
230
+ expect(after.other).toEqual({ nested: { value: 42 } });
231
+ expect(findOurHook(after)).toBeDefined();
232
+
233
+ removeHook(p);
234
+ const final = readSettings(p);
235
+ expect(final.permissions).toEqual({ allow: ["some-permission"] });
236
+ expect(final.hooks).toBeUndefined();
239
237
  });
240
238
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "buddy-reroll",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "Reroll your Claude Code buddy companion to any species/rarity/eye/hat/shiny combo",
5
5
  "type": "module",
6
6
  "bin": {