jun-claude-code 0.1.2 → 0.1.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.
@@ -292,7 +292,7 @@ function writeJson(filePath, data) {
292
292
  const result = readJson(path.join(destDir, 'settings.json'));
293
293
  (0, vitest_1.expect)(result.statusLine.command).toBe('dest-status');
294
294
  });
295
- (0, vitest_1.it)('should convert ~/.claude/ to ./.claude/ in command fields when project=true', () => {
295
+ (0, vitest_1.it)('should convert ~/.claude/ to .claude/ in command fields when project=true', () => {
296
296
  const sourceSettings = {
297
297
  hooks: {
298
298
  UserPromptSubmit: [
@@ -315,8 +315,8 @@ function writeJson(filePath, data) {
315
315
  writeJson(path.join(sourceDir, 'settings.json'), sourceSettings);
316
316
  (0, copy_1.mergeSettingsJson)(sourceDir, destDir, { project: true });
317
317
  const result = readJson(path.join(destDir, 'settings.json'));
318
- (0, vitest_1.expect)(result.hooks.UserPromptSubmit[0].hooks[0].command).toBe('./.claude/hooks/skill-forced.sh');
319
- (0, vitest_1.expect)(result.hooks.PreToolUse[0].hooks[0].command).toBe('./.claude/hooks/blocker.sh');
318
+ (0, vitest_1.expect)(result.hooks.UserPromptSubmit[0].hooks[0].command).toBe('.claude/hooks/skill-forced.sh');
319
+ (0, vitest_1.expect)(result.hooks.PreToolUse[0].hooks[0].command).toBe('.claude/hooks/blocker.sh');
320
320
  });
321
321
  (0, vitest_1.it)('should keep ~/.claude/ paths unchanged when project option is not set', () => {
322
322
  const sourceSettings = {
package/dist/copy.d.ts CHANGED
@@ -8,7 +8,7 @@ export interface CopyOptions {
8
8
  * Hooks are merged per event key; duplicate hook entries (by deep equality) are skipped.
9
9
  * Non-hook keys are shallow-merged (source wins for new keys, dest preserved for existing).
10
10
  * statusLine is always excluded from merge (personal environment setting).
11
- * When project=true, ~/.claude/ paths in command fields are converted to ./.claude/.
11
+ * When project=true, ~/.claude/ paths in command fields are converted to .claude/.
12
12
  */
13
13
  export declare function mergeSettingsJson(sourceDir: string, destDir: string, options?: {
14
14
  project?: boolean;
package/dist/copy.js CHANGED
@@ -126,13 +126,13 @@ function getDestClaudeDir() {
126
126
  * Non-hook keys are shallow-merged (source wins for new keys, dest preserved for existing).
127
127
  */
128
128
  /**
129
- * Replace ~/.claude/ paths with ./.claude/ in all command fields (deep traverse).
129
+ * Replace ~/.claude/ paths with .claude/ in all command fields (deep traverse).
130
130
  */
131
131
  function replaceClaudePaths(obj) {
132
132
  const result = {};
133
133
  for (const [key, value] of Object.entries(obj)) {
134
134
  if (key === 'command' && typeof value === 'string') {
135
- result[key] = value.replace(/~\/\.claude\//g, './.claude/');
135
+ result[key] = value.replace(/~\/\.claude\//g, '.claude/');
136
136
  }
137
137
  else if (Array.isArray(value)) {
138
138
  result[key] = value.map((item) => typeof item === 'object' && item !== null ? replaceClaudePaths(item) : item);
@@ -151,7 +151,7 @@ function replaceClaudePaths(obj) {
151
151
  * Hooks are merged per event key; duplicate hook entries (by deep equality) are skipped.
152
152
  * Non-hook keys are shallow-merged (source wins for new keys, dest preserved for existing).
153
153
  * statusLine is always excluded from merge (personal environment setting).
154
- * When project=true, ~/.claude/ paths in command fields are converted to ./.claude/.
154
+ * When project=true, ~/.claude/ paths in command fields are converted to .claude/.
155
155
  */
156
156
  function mergeSettingsJson(sourceDir, destDir, options) {
157
157
  const sourcePath = path.join(sourceDir, 'settings.json');
@@ -232,15 +232,23 @@ async function copyClaudeFiles(options = {}) {
232
232
  console.error(chalk_1.default.red('Error:'), 'Source templates/global directory not found');
233
233
  process.exit(1);
234
234
  }
235
- // Files to exclude from global copy (merge-handled separately)
236
- const EXCLUDE_FROM_GLOBAL = [
235
+ // Files to exclude from all copies (merge-handled separately)
236
+ const EXCLUDE_ALWAYS = [
237
237
  'settings.json',
238
238
  'statusline-command.sh',
239
239
  ];
240
+ // Files to exclude only when installing to project
241
+ const EXCLUDE_FROM_PROJECT = [
242
+ 'hooks/_dedup.sh',
243
+ ];
240
244
  // Get all files to copy
241
245
  const allFiles = getAllFiles(sourceDir);
242
246
  const files = allFiles.filter((file) => {
243
- return !EXCLUDE_FROM_GLOBAL.includes(file);
247
+ if (EXCLUDE_ALWAYS.includes(file))
248
+ return false;
249
+ if (project && EXCLUDE_FROM_PROJECT.includes(file))
250
+ return false;
251
+ return true;
244
252
  });
245
253
  if (files.length === 0) {
246
254
  console.log(chalk_1.default.yellow('No files found in templates/global directory'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jun-claude-code",
3
- "version": "0.1.2",
3
+ "version": "0.1.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",
@@ -0,0 +1,32 @@
1
+ #!/bin/bash
2
+ # _dedup.sh - Hook deduplication guard
3
+ # global과 project 양쪽에 hooks가 설치된 경우 중복 실행을 방지합니다.
4
+ # project-level hook이 우선이며, global hook은 project 버전이 존재하면 양보합니다.
5
+ #
6
+ # Usage:
7
+ # source "$(dirname "${BASH_SOURCE[0]}")/_dedup.sh"
8
+ # _hook_dedup_check "${BASH_SOURCE[0]}" || exit 0
9
+
10
+ _hook_dedup_check() {
11
+ local caller_path="$1"
12
+ local hook_name
13
+ hook_name="$(basename "$caller_path")"
14
+
15
+ local self_dir
16
+ self_dir="$(cd "$(dirname "$caller_path")" && pwd)"
17
+
18
+ local global_dir
19
+ global_dir="$(cd "$HOME/.claude/hooks" 2>/dev/null && pwd 2>/dev/null)"
20
+
21
+ # 내가 global 버전인지 확인
22
+ if [ "$self_dir" = "$global_dir" ]; then
23
+ # project-level 동일 hook이 존재하면 → global은 skip
24
+ local project_hook
25
+ project_hook="$(pwd)/.claude/hooks/$hook_name"
26
+ if [ -f "$project_hook" ]; then
27
+ return 1 # global yields to project → skip
28
+ fi
29
+ fi
30
+
31
+ return 0 # project version 또는 global-only → proceed
32
+ }
@@ -4,6 +4,11 @@
4
4
  # Dangerous Command Blocker - PreToolUse Hook
5
5
  # Bash 도구 실행 전 위험한 명령어를 감지하여 차단합니다.
6
6
 
7
+ # Dedup: project-level 복사본이면 global에 양보
8
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+ source "$SCRIPT_DIR/_dedup.sh"
10
+ _hook_dedup_check "${BASH_SOURCE[0]}" || exit 0
11
+
7
12
  INPUT=$(cat)
8
13
 
9
14
  # jq가 있으면 jq로, 없으면 grep/sed로 command 추출
@@ -9,6 +9,10 @@ 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에 양보
13
+ source "$SCRIPT_DIR/_dedup.sh"
14
+ _hook_dedup_check "${BASH_SOURCE[0]}" || exit 0
15
+
12
16
  echo "✅ [Hook] Subagent Skill 평가 프로토콜 실행됨"
13
17
 
14
18
  cat << 'EOF'
@@ -9,6 +9,10 @@ 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에 양보
13
+ source "$SCRIPT_DIR/_dedup.sh"
14
+ _hook_dedup_check "${BASH_SOURCE[0]}" || exit 0
15
+
12
16
  echo "✅ [Hook] Skill/Agent 평가 프로토콜 실행됨"
13
17
 
14
18
  cat << 'EOF'
@@ -9,6 +9,10 @@ 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에 양보
13
+ source "$SCRIPT_DIR/_dedup.sh"
14
+ _hook_dedup_check "${BASH_SOURCE[0]}" || exit 0
15
+
12
16
  echo "✅ [Hook] 워크플로우 순서 강제 프로토콜 실행됨"
13
17
 
14
18
  cat << 'EOF'