jun-claude-code 0.2.1 → 0.2.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.
@@ -335,6 +335,104 @@ function writeJson(filePath, data) {
335
335
  const result = readJson(path.join(destDir, 'settings.json'));
336
336
  (0, vitest_1.expect)(result.hooks.UserPromptSubmit[0].hooks[0].command).toBe('~/.claude/hooks/skill-forced.sh');
337
337
  });
338
+ (0, vitest_1.it)('should not duplicate hooks when project=true and run 3 times (idempotency)', () => {
339
+ const sourceSettings = {
340
+ hooks: {
341
+ UserPromptSubmit: [
342
+ {
343
+ hooks: [
344
+ { type: 'command', command: '~/.claude/hooks/skill-forced.sh' },
345
+ ],
346
+ },
347
+ ],
348
+ },
349
+ };
350
+ writeJson(path.join(sourceDir, 'settings.json'), sourceSettings);
351
+ (0, copy_1.mergeSettingsJson)(sourceDir, destDir, { project: true });
352
+ (0, copy_1.mergeSettingsJson)(sourceDir, destDir, { project: true });
353
+ (0, copy_1.mergeSettingsJson)(sourceDir, destDir, { project: true });
354
+ const result = readJson(path.join(destDir, 'settings.json'));
355
+ (0, vitest_1.expect)(result.hooks.UserPromptSubmit).toHaveLength(1);
356
+ (0, vitest_1.expect)(result.hooks.UserPromptSubmit[0].hooks[0].command).toBe('.claude/hooks/skill-forced.sh');
357
+ });
358
+ (0, vitest_1.it)('should not duplicate hooks across multiple events in project mode', () => {
359
+ const sourceSettings = {
360
+ hooks: {
361
+ SubagentStart: [
362
+ { hooks: [{ type: 'command', command: '~/.claude/hooks/subagent.sh' }] },
363
+ ],
364
+ UserPromptSubmit: [
365
+ { hooks: [{ type: 'command', command: '~/.claude/hooks/skill-forced.sh' }] },
366
+ ],
367
+ PreToolUse: [
368
+ {
369
+ matcher: 'Bash',
370
+ hooks: [{ type: 'command', command: '~/.claude/hooks/blocker.sh' }],
371
+ },
372
+ ],
373
+ },
374
+ };
375
+ writeJson(path.join(sourceDir, 'settings.json'), sourceSettings);
376
+ (0, copy_1.mergeSettingsJson)(sourceDir, destDir, { project: true });
377
+ (0, copy_1.mergeSettingsJson)(sourceDir, destDir, { project: true });
378
+ const result = readJson(path.join(destDir, 'settings.json'));
379
+ (0, vitest_1.expect)(result.hooks.SubagentStart).toHaveLength(1);
380
+ (0, vitest_1.expect)(result.hooks.UserPromptSubmit).toHaveLength(1);
381
+ (0, vitest_1.expect)(result.hooks.PreToolUse).toHaveLength(1);
382
+ });
383
+ (0, vitest_1.it)('should not duplicate matcher hooks in project mode on re-run', () => {
384
+ const sourceSettings = {
385
+ hooks: {
386
+ PreToolUse: [
387
+ {
388
+ matcher: 'Bash',
389
+ hooks: [{ type: 'command', command: '~/.claude/hooks/blocker.sh' }],
390
+ },
391
+ {
392
+ matcher: 'Write',
393
+ hooks: [{ type: 'command', command: '~/.claude/hooks/blocker.sh' }],
394
+ },
395
+ ],
396
+ },
397
+ };
398
+ writeJson(path.join(sourceDir, 'settings.json'), sourceSettings);
399
+ (0, copy_1.mergeSettingsJson)(sourceDir, destDir, { project: true });
400
+ (0, copy_1.mergeSettingsJson)(sourceDir, destDir, { project: true });
401
+ const result = readJson(path.join(destDir, 'settings.json'));
402
+ (0, vitest_1.expect)(result.hooks.PreToolUse).toHaveLength(2);
403
+ (0, vitest_1.expect)(result.hooks.PreToolUse[0].matcher).toBe('Bash');
404
+ (0, vitest_1.expect)(result.hooks.PreToolUse[1].matcher).toBe('Write');
405
+ });
406
+ (0, vitest_1.it)('should dedup when dest has .claude/ paths and source has ~/.claude/ paths', () => {
407
+ const sourceSettings = {
408
+ hooks: {
409
+ UserPromptSubmit: [
410
+ {
411
+ hooks: [
412
+ { type: 'command', command: '~/.claude/hooks/skill-forced.sh' },
413
+ ],
414
+ },
415
+ ],
416
+ },
417
+ };
418
+ writeJson(path.join(sourceDir, 'settings.json'), sourceSettings);
419
+ // Simulate dest from a previous project install (already converted)
420
+ const destSettings = {
421
+ hooks: {
422
+ UserPromptSubmit: [
423
+ {
424
+ hooks: [
425
+ { type: 'command', command: '.claude/hooks/skill-forced.sh' },
426
+ ],
427
+ },
428
+ ],
429
+ },
430
+ };
431
+ writeJson(path.join(destDir, 'settings.json'), destSettings);
432
+ (0, copy_1.mergeSettingsJson)(sourceDir, destDir, { project: true });
433
+ const result = readJson(path.join(destDir, 'settings.json'));
434
+ (0, vitest_1.expect)(result.hooks.UserPromptSubmit).toHaveLength(1);
435
+ });
338
436
  });
339
437
  // ─── init-project.ts mergeSettingsJson (project) ───
340
438
  (0, vitest_1.describe)('init-project.ts mergeSettingsJson', () => {
package/dist/copy.js CHANGED
@@ -177,6 +177,10 @@ function mergeSettingsJson(sourceDir, destDir, options) {
177
177
  destSettings = {};
178
178
  }
179
179
  }
180
+ // Convert ~/.claude/ → .claude/ paths in source BEFORE merge (for dedup key matching)
181
+ if (options?.project) {
182
+ sourceSettings = replaceClaudePaths(sourceSettings);
183
+ }
180
184
  // Merge top-level keys (source fills in missing keys, dest's existing keys preserved)
181
185
  for (const key of Object.keys(sourceSettings)) {
182
186
  if (key === 'hooks' || key === 'statusLine') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jun-claude-code",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Claude Code configuration template - copy .claude settings to your project",
5
5
  "main": "dist/index.js",
6
6
  "bin": "dist/cli.js",
@@ -4,7 +4,7 @@
4
4
  # Dangerous Command Blocker - PreToolUse Hook
5
5
  # Bash 도구 실행 전 위험한 명령어를 감지하여 차단합니다.
6
6
 
7
- # Dedup: project-level 복사본이면 global 양보
7
+ # Dedup: project 우선, global project 버전 존재 시 양보
8
8
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
9
  source "$SCRIPT_DIR/_dedup.sh"
10
10
  _hook_dedup_check "${BASH_SOURCE[0]}" || exit 0
@@ -9,7 +9,7 @@ CLAUDE_DIR="$(dirname "$SCRIPT_DIR")"
9
9
  GLOBAL_CLAUDE_DIR="$HOME/.claude"
10
10
  PROJECT_CLAUDE_DIR="$(pwd)/.claude"
11
11
 
12
- # Dedup: project-level 복사본이면 global 양보
12
+ # Dedup: project 우선, global project 버전 존재 시 양보
13
13
  source "$SCRIPT_DIR/_dedup.sh"
14
14
  _hook_dedup_check "${BASH_SOURCE[0]}" || exit 0
15
15
 
@@ -9,7 +9,7 @@ CLAUDE_DIR="$(dirname "$SCRIPT_DIR")"
9
9
  GLOBAL_CLAUDE_DIR="$HOME/.claude"
10
10
  PROJECT_CLAUDE_DIR="$(pwd)/.claude"
11
11
 
12
- # Dedup: project-level 복사본이면 global 양보
12
+ # Dedup: project 우선, global project 버전 존재 시 양보
13
13
  source "$SCRIPT_DIR/_dedup.sh"
14
14
  _hook_dedup_check "${BASH_SOURCE[0]}" || exit 0
15
15
 
@@ -9,7 +9,7 @@ CLAUDE_DIR="$(dirname "$SCRIPT_DIR")"
9
9
  GLOBAL_CLAUDE_DIR="$HOME/.claude"
10
10
  PROJECT_CLAUDE_DIR="$(pwd)/.claude"
11
11
 
12
- # Dedup: project-level 복사본이면 global 양보
12
+ # Dedup: project 우선, global project 버전 존재 시 양보
13
13
  source "$SCRIPT_DIR/_dedup.sh"
14
14
  _hook_dedup_check "${BASH_SOURCE[0]}" || exit 0
15
15