jun-claude-code 0.2.2 → 0.2.4

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.2",
3
+ "version": "0.2.4",
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",
@@ -18,7 +18,8 @@ echo "✅ [Hook] Skill/Agent 평가 프로토콜 실행됨"
18
18
  cat << 'EOF'
19
19
  MANDATORY SKILL & AGENT EVALUATION PROTOCOL
20
20
 
21
- 작업을 시작하기 전에 아래 단계를 순서대로 완료하세요:
21
+ BLOCKING REQUIREMENT: 응답 시작 시 아래 Skill/Agent 평가를 반드시 출력하세요.
22
+ 평가 없이 작업을 시작하면 프로토콜 위반입니다.
22
23
 
23
24
  <delegation_rules>
24
25
 
@@ -60,12 +61,12 @@ Main Agent의 Context Window는 제한적입니다.
60
61
 
61
62
  ## PART 1: SKILL 평가
62
63
 
63
- Step 1 - Skill 평가: 각 Skill에 대해 다음을 명시하세요:
64
- - Skill 이름
65
- - YES 또는 NO (이 요청에 해당 Skill이 필요한가?)
66
- - 한 줄 이유
64
+ 응답에 아래 형식으로 Skill 평가를 출력하세요:
67
65
 
68
- Step 2 - Skill 활성화: YES 표시된 모든 Skill의 SKILL.md를 읽으세요.
66
+ - [Skill이름]: YES/NO - 이유
67
+ - 예: `Git: YES - 커밋 작업 필요`
68
+
69
+ YES인 Skill의 SKILL.md를 읽으세요.
69
70
 
70
71
  ---
71
72
 
@@ -180,13 +181,12 @@ cat << 'EOF'
180
181
 
181
182
  ## PART 2: AGENT 평가 (Context 절약 핵심)
182
183
 
183
- Step 3 - Agent 평가: 각 Agent에 대해 다음을 명시하세요:
184
- - Agent 이름
185
- - YES 또는 NO (이 요청에 해당 Agent 활용이 필요한가?)
186
- - 이유
184
+ 응답에 아래 형식으로 Agent 평가를 출력하세요:
185
+
186
+ - [Agent이름]: YES/NO - 이유
187
+ - 예: `git-manager: YES - 커밋 작업 위임`
187
188
 
188
- Step 4 - Agent 활용: YES 표시된 Agent는 Task 도구로 호출하세요.
189
- 예: Task(subagent_type="task-planner", prompt="...")
189
+ YES Agent는 Task 도구로 호출하세요.
190
190
 
191
191
  ---
192
192
 
@@ -237,13 +237,11 @@ cat << 'EOF'
237
237
 
238
238
  ## PART 3: 구현
239
239
 
240
- Step 5 - 구현: 모든 관련 Skill 확인 Agent 호출 후에 구현을 시작하세요.
240
+ Skill/Agent 평가를 출력한 후에 구현을 시작하세요.
241
241
 
242
242
  ---
243
243
 
244
244
  탐색은 Subagent 전담, 구현 후 검증(code-reviewer + qa-tester), 단순 작업은 직접 처리 가능
245
245
 
246
- 지금 바로 모든 사용 가능한 Skill과 Agent를 평가하세요.
247
-
248
246
  </phase>
249
247
  EOF