jun-claude-code 0.5.0 → 0.5.1

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.
@@ -403,6 +403,98 @@ function writeJson(filePath, data) {
403
403
  (0, vitest_1.expect)(result.hooks.PreToolUse[0].matcher).toBe('Bash');
404
404
  (0, vitest_1.expect)(result.hooks.PreToolUse[1].matcher).toBe('Write');
405
405
  });
406
+ (0, vitest_1.it)('should skip source group when dest group contains all source commands plus extras', () => {
407
+ const sourceSettings = {
408
+ hooks: {
409
+ UserPromptSubmit: [
410
+ {
411
+ hooks: [
412
+ { type: 'command', command: 'skill-forced.sh' },
413
+ { type: 'command', command: 'workflow-enforced.sh' },
414
+ ],
415
+ },
416
+ ],
417
+ },
418
+ };
419
+ writeJson(path.join(sourceDir, 'settings.json'), sourceSettings);
420
+ // Dest has the same commands PLUS an extra custom hook (peon-ping)
421
+ const destSettings = {
422
+ hooks: {
423
+ UserPromptSubmit: [
424
+ {
425
+ hooks: [
426
+ { type: 'command', command: 'peon.sh' },
427
+ { type: 'command', command: 'skill-forced.sh' },
428
+ { type: 'command', command: 'workflow-enforced.sh' },
429
+ ],
430
+ },
431
+ ],
432
+ },
433
+ };
434
+ writeJson(path.join(destDir, 'settings.json'), destSettings);
435
+ (0, copy_1.mergeSettingsJson)(sourceDir, destDir);
436
+ const result = readJson(path.join(destDir, 'settings.json'));
437
+ // Should remain 1 group, not duplicate
438
+ (0, vitest_1.expect)(result.hooks.UserPromptSubmit).toHaveLength(1);
439
+ (0, vitest_1.expect)(result.hooks.UserPromptSubmit[0].hooks).toHaveLength(3);
440
+ });
441
+ (0, vitest_1.it)('should skip source group when dest has commands split across multiple groups', () => {
442
+ const sourceSettings = {
443
+ hooks: {
444
+ UserPromptSubmit: [
445
+ {
446
+ hooks: [
447
+ { type: 'command', command: 'skill-forced.sh' },
448
+ { type: 'command', command: 'workflow-enforced.sh' },
449
+ ],
450
+ },
451
+ ],
452
+ },
453
+ };
454
+ writeJson(path.join(sourceDir, 'settings.json'), sourceSettings);
455
+ // Dest has the same commands spread across separate groups
456
+ const destSettings = {
457
+ hooks: {
458
+ UserPromptSubmit: [
459
+ { hooks: [{ type: 'command', command: 'skill-forced.sh' }] },
460
+ { hooks: [{ type: 'command', command: 'workflow-enforced.sh' }] },
461
+ ],
462
+ },
463
+ };
464
+ writeJson(path.join(destDir, 'settings.json'), destSettings);
465
+ (0, copy_1.mergeSettingsJson)(sourceDir, destDir);
466
+ const result = readJson(path.join(destDir, 'settings.json'));
467
+ // Should remain 2 groups, source group not duplicated
468
+ (0, vitest_1.expect)(result.hooks.UserPromptSubmit).toHaveLength(2);
469
+ });
470
+ (0, vitest_1.it)('should add source group when dest only has some of its commands', () => {
471
+ const sourceSettings = {
472
+ hooks: {
473
+ UserPromptSubmit: [
474
+ {
475
+ hooks: [
476
+ { type: 'command', command: 'skill-forced.sh' },
477
+ { type: 'command', command: 'new-hook.sh' },
478
+ ],
479
+ },
480
+ ],
481
+ },
482
+ };
483
+ writeJson(path.join(sourceDir, 'settings.json'), sourceSettings);
484
+ // Dest has only one of the two source commands
485
+ const destSettings = {
486
+ hooks: {
487
+ UserPromptSubmit: [
488
+ { hooks: [{ type: 'command', command: 'skill-forced.sh' }] },
489
+ ],
490
+ },
491
+ };
492
+ writeJson(path.join(destDir, 'settings.json'), destSettings);
493
+ (0, copy_1.mergeSettingsJson)(sourceDir, destDir);
494
+ const result = readJson(path.join(destDir, 'settings.json'));
495
+ // Source group should be added since new-hook.sh is missing
496
+ (0, vitest_1.expect)(result.hooks.UserPromptSubmit).toHaveLength(2);
497
+ });
406
498
  (0, vitest_1.it)('should dedup when dest has .claude/ paths and source has ~/.claude/ paths', () => {
407
499
  const sourceSettings = {
408
500
  hooks: {
package/dist/copy.js CHANGED
@@ -43,7 +43,6 @@ const path = __importStar(require("path"));
43
43
  const readline = __importStar(require("readline"));
44
44
  const crypto = __importStar(require("crypto"));
45
45
  const chalk_1 = __importDefault(require("chalk"));
46
- const utils_1 = require("./utils");
47
46
  /**
48
47
  * Prompt user for confirmation using readline
49
48
  */
@@ -202,13 +201,49 @@ function mergeSettingsJson(sourceDir, destDir, options) {
202
201
  destHooks[event] = [];
203
202
  }
204
203
  const destEntries = destHooks[event];
205
- // Build a Set of command keys from existing entries for fast duplicate detection
206
- const existingKeys = new Set(destEntries.map((entry) => (0, utils_1.getHookKey)(entry)));
204
+ // Collect all individual commands from dest entries, grouped by matcher
205
+ const destCommandsByMatcher = new Map();
206
+ for (const entry of destEntries) {
207
+ const matcher = entry.matcher || '';
208
+ if (!destCommandsByMatcher.has(matcher)) {
209
+ destCommandsByMatcher.set(matcher, new Set());
210
+ }
211
+ const cmds = destCommandsByMatcher.get(matcher);
212
+ if (entry.hooks && Array.isArray(entry.hooks)) {
213
+ for (const h of entry.hooks) {
214
+ if (h.command)
215
+ cmds.add(h.command);
216
+ }
217
+ }
218
+ else if (entry.command) {
219
+ cmds.add(entry.command);
220
+ }
221
+ }
207
222
  for (const entry of sourceEntries) {
208
- const key = (0, utils_1.getHookKey)(entry);
209
- if (!existingKeys.has(key)) {
223
+ const matcher = entry.matcher || '';
224
+ const existingCmds = destCommandsByMatcher.get(matcher) || new Set();
225
+ // Extract all commands from this source entry
226
+ const sourceCommands = [];
227
+ if (entry.hooks && Array.isArray(entry.hooks)) {
228
+ for (const h of entry.hooks) {
229
+ if (h.command)
230
+ sourceCommands.push(h.command);
231
+ }
232
+ }
233
+ else if (entry.command) {
234
+ sourceCommands.push(entry.command);
235
+ }
236
+ // Skip if all source commands already exist in dest for the same matcher
237
+ const allExist = sourceCommands.length > 0 && sourceCommands.every((cmd) => existingCmds.has(cmd));
238
+ if (!allExist) {
210
239
  destEntries.push(entry);
211
- existingKeys.add(key);
240
+ // Track newly added commands for subsequent source entries
241
+ if (!destCommandsByMatcher.has(matcher)) {
242
+ destCommandsByMatcher.set(matcher, existingCmds);
243
+ }
244
+ for (const cmd of sourceCommands) {
245
+ existingCmds.add(cmd);
246
+ }
212
247
  }
213
248
  }
214
249
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jun-claude-code",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
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",