@brickhouse-tech/sync-agents 0.1.10 → 0.1.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brickhouse-tech/sync-agents",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "Simple scripts to DRY up common agent interactions across multiple LLM providers.",
5
5
  "keywords": [
6
6
  "agents",
@@ -25,6 +25,10 @@
25
25
  "files": [
26
26
  "src/**/*"
27
27
  ],
28
+ "overrides": {
29
+ "file-type": ">=22",
30
+ "picomatch": ">=4.0.4"
31
+ },
28
32
  "scripts": {
29
33
  "lint": "shellcheck src/sh/*.sh",
30
34
  "test": "npx bats test/"
@@ -34,6 +38,6 @@
34
38
  "@commitlint/config-conventional": "^20",
35
39
  "commitlint": "20",
36
40
  "shellcheck": "^4.1.0",
37
- "sort-package-json": "3.6"
41
+ "sort-package-json": ">=3"
38
42
  }
39
43
  }
@@ -75,7 +75,9 @@ ${BOLD}COMMANDS${RESET}
75
75
  watch Watch .agents/ for changes and auto-regenerate index
76
76
  import <url> Import a rule/skill/workflow from a URL
77
77
  hook Install a pre-commit git hook for auto-sync
78
- fix [type] Migrate legacy dirs into .agents/ (type: skills, rules, workflows, or all)
78
+ fix [type] Migrate legacy dirs + repair broken symlinks
79
+ type: skills, rules, workflows, or all
80
+ --no-clobber: skip items that already exist in .agents/
79
81
  inherit <label> <path> Add an inheritance link to AGENTS.md (convention-based)
80
82
  inherit --list List current inheritance links
81
83
  inherit --remove <label> Remove an inheritance link by label
@@ -301,7 +303,17 @@ TMPL_EOF
301
303
  cmd_fix() {
302
304
  ensure_agents_dir
303
305
 
304
- local fix_type="${1:-all}"
306
+ # Parse fix-specific flags
307
+ local no_clobber="false"
308
+ local fix_args=()
309
+ for arg in "$@"; do
310
+ case "$arg" in
311
+ --no-clobber) no_clobber="true" ;;
312
+ *) fix_args+=("$arg") ;;
313
+ esac
314
+ done
315
+
316
+ local fix_type="${fix_args[0]:-all}"
305
317
  local subdirs=()
306
318
 
307
319
  case "$fix_type" in
@@ -320,13 +332,50 @@ cmd_fix() {
320
332
  local agents_abs
321
333
  agents_abs="$(cd "$PROJECT_ROOT/$AGENTS_DIR" && pwd)"
322
334
  local fixed=0
335
+ local skipped=0
336
+ local merged=0
337
+
338
+ # Helper: compare inodes of two directories (portable across macOS/Linux)
339
+ same_inode() {
340
+ local inode_a inode_b
341
+ if stat --version >/dev/null 2>&1; then
342
+ # GNU stat (Linux)
343
+ inode_a="$(stat -c '%i' "$1" 2>/dev/null)"
344
+ inode_b="$(stat -c '%i' "$2" 2>/dev/null)"
345
+ else
346
+ # BSD stat (macOS)
347
+ inode_a="$(stat -f '%i' "$1" 2>/dev/null)"
348
+ inode_b="$(stat -f '%i' "$2" 2>/dev/null)"
349
+ fi
350
+ [[ -n "$inode_a" ]] && [[ "$inode_a" == "$inode_b" ]]
351
+ }
323
352
 
324
353
  for subdir in "${subdirs[@]}"; do
325
354
  local legacy_dir="$PROJECT_ROOT/$subdir"
326
355
  local agents_subdir="$agents_abs/$subdir"
327
356
 
328
357
  # Skip if legacy dir doesn't exist or is already a symlink
329
- if [[ ! -d "$legacy_dir" ]] || [[ -L "$legacy_dir" ]]; then
358
+ if [[ ! -d "$legacy_dir" ]]; then
359
+ continue
360
+ fi
361
+ if [[ -L "$legacy_dir" ]]; then
362
+ info "$subdir/ is already a symlink — nothing to do."
363
+ continue
364
+ fi
365
+
366
+ # Detect same-inode (legacy dir IS .agents/subdir — e.g. hardlink or bind mount)
367
+ if [[ -d "$agents_subdir" ]] && same_inode "$legacy_dir" "$agents_subdir"; then
368
+ warn "$subdir/ and $AGENTS_DIR/$subdir/ are the same directory (same inode)."
369
+ warn "Replacing $subdir/ with a symlink to $AGENTS_DIR/$subdir/."
370
+ if [[ "$DRY_RUN" == "true" ]]; then
371
+ echo " would remove $subdir/ (same inode as $AGENTS_DIR/$subdir/)"
372
+ echo " would create symlink $subdir/ -> $AGENTS_DIR/$subdir"
373
+ else
374
+ rm -rf "$legacy_dir"
375
+ ln -s "$AGENTS_DIR/$subdir" "$legacy_dir"
376
+ info "Replaced $subdir/ with symlink -> $AGENTS_DIR/$subdir"
377
+ fi
378
+ fixed=$((fixed + 1))
330
379
  continue
331
380
  fi
332
381
 
@@ -340,7 +389,21 @@ cmd_fix() {
340
389
  name="$(basename "$item")"
341
390
 
342
391
  if [[ -d "$agents_subdir/$name" ]]; then
343
- warn "Skipping $subdir/$name already exists in $AGENTS_DIR/$subdir/"
392
+ if [[ "$no_clobber" == "true" ]]; then
393
+ warn "Skipping $subdir/$name — already exists in $AGENTS_DIR/$subdir/ (--no-clobber)"
394
+ skipped=$((skipped + 1))
395
+ continue
396
+ fi
397
+ # Merge: legacy content wins (overwrite into .agents/)
398
+ if [[ "$DRY_RUN" == "true" ]]; then
399
+ echo " would merge: $subdir/$name -> $AGENTS_DIR/$subdir/$name (overwrite)"
400
+ else
401
+ rm -rf "${agents_subdir:?}/${name:?}"
402
+ mv "$item" "$agents_subdir/$name"
403
+ info "Merged: $subdir/$name -> $AGENTS_DIR/$subdir/$name (overwrote existing)"
404
+ fi
405
+ merged=$((merged + 1))
406
+ fixed=$((fixed + 1))
344
407
  continue
345
408
  fi
346
409
 
@@ -360,7 +423,20 @@ cmd_fix() {
360
423
  name="$(basename "$item")"
361
424
 
362
425
  if [[ -f "$agents_subdir/$name" ]]; then
363
- warn "Skipping $subdir/$name already exists in $AGENTS_DIR/$subdir/"
426
+ if [[ "$no_clobber" == "true" ]]; then
427
+ warn "Skipping $subdir/$name — already exists in $AGENTS_DIR/$subdir/ (--no-clobber)"
428
+ skipped=$((skipped + 1))
429
+ continue
430
+ fi
431
+ # Merge: legacy content wins
432
+ if [[ "$DRY_RUN" == "true" ]]; then
433
+ echo " would merge: $subdir/$name -> $AGENTS_DIR/$subdir/$name (overwrite)"
434
+ else
435
+ mv "$item" "$agents_subdir/$name"
436
+ info "Merged: $subdir/$name -> $AGENTS_DIR/$subdir/$name (overwrote existing)"
437
+ fi
438
+ merged=$((merged + 1))
439
+ fixed=$((fixed + 1))
364
440
  continue
365
441
  fi
366
442
 
@@ -390,10 +466,139 @@ cmd_fix() {
390
466
  fi
391
467
  done
392
468
 
393
- if [[ "$fixed" -eq 0 ]]; then
394
- info "Nothing to fix — all directories are already in $AGENTS_DIR/ or symlinked."
469
+ # --- Phase 1b: Convert flat skill files to directory layout ---
470
+ # e.g. .agents/skills/foo.md -> .agents/skills/foo/SKILL.md
471
+ for subdir in "${subdirs[@]}"; do
472
+ [[ "$subdir" == "skills" ]] || continue
473
+ local skills_dir="$agents_abs/skills"
474
+ [[ -d "$skills_dir" ]] || continue
475
+
476
+ for flat_file in "$skills_dir"/*.md; do
477
+ [[ -f "$flat_file" ]] || continue
478
+ local name
479
+ name="$(basename "$flat_file" .md)"
480
+ local target_dir="$skills_dir/$name"
481
+ local target_file="$target_dir/SKILL.md"
482
+
483
+ if [[ -d "$target_dir" ]] && [[ -f "$target_file" ]]; then
484
+ if [[ "$no_clobber" == "true" ]]; then
485
+ warn "Skipping flat skill $name.md — $name/SKILL.md already exists (--no-clobber)"
486
+ skipped=$((skipped + 1))
487
+ continue
488
+ fi
489
+ # Flat file wins (same merge behavior as legacy migration)
490
+ if [[ "$DRY_RUN" == "true" ]]; then
491
+ echo " would convert: skills/$name.md -> skills/$name/SKILL.md (overwrite)"
492
+ else
493
+ mv "$flat_file" "$target_file"
494
+ info "Converted: skills/$name.md -> skills/$name/SKILL.md (overwrote existing)"
495
+ fi
496
+ merged=$((merged + 1))
497
+ fixed=$((fixed + 1))
498
+ continue
499
+ fi
500
+
501
+ if [[ "$DRY_RUN" == "true" ]]; then
502
+ echo " would convert: skills/$name.md -> skills/$name/SKILL.md"
503
+ else
504
+ mkdir -p "$target_dir"
505
+ mv "$flat_file" "$target_file"
506
+ info "Converted: skills/$name.md -> skills/$name/SKILL.md"
507
+ fi
508
+ fixed=$((fixed + 1))
509
+ done
510
+ done
511
+
512
+ # --- Phase 2: Repair broken/missing symlinks ---
513
+ local repaired=0
514
+
515
+ for target in "${ACTIVE_TARGETS[@]}"; do
516
+ local target_dir
517
+ target_dir="$(resolve_target_dir "$target" "$PROJECT_ROOT")"
518
+ local agents_rel
519
+ agents_rel="$(resolve_agents_rel "$target")"
520
+
521
+ for subdir in "${subdirs[@]}"; do
522
+ if [[ ! -d "$agents_abs/$subdir" ]]; then
523
+ continue
524
+ fi
525
+ local expected_link="$target_dir/$subdir"
526
+ local expected_source="$agents_rel/$subdir"
527
+
528
+ if [[ -L "$expected_link" ]]; then
529
+ local current_target
530
+ current_target="$(readlink "$expected_link")"
531
+ if [[ "$current_target" == "$expected_source" ]]; then
532
+ continue # Already correct
533
+ fi
534
+ # Symlink exists but points to wrong target
535
+ if [[ "$DRY_RUN" == "true" ]]; then
536
+ echo " would relink: $expected_link -> $expected_source (was $current_target)"
537
+ else
538
+ rm "$expected_link"
539
+ create_symlink "$expected_source" "$expected_link" "false"
540
+ info "Repaired: $expected_link -> $expected_source (was $current_target)"
541
+ fi
542
+ repaired=$((repaired + 1))
543
+ elif [[ -e "$expected_link" ]]; then
544
+ # Something exists but isn't a symlink — skip unless --force
545
+ if [[ "$FORCE" == "true" ]]; then
546
+ if [[ "$DRY_RUN" == "true" ]]; then
547
+ echo " would replace: $expected_link with symlink -> $expected_source"
548
+ else
549
+ rm -rf "$expected_link"
550
+ create_symlink "$expected_source" "$expected_link" "false"
551
+ info "Repaired: replaced $expected_link with symlink -> $expected_source"
552
+ fi
553
+ repaired=$((repaired + 1))
554
+ else
555
+ warn "$expected_link exists but is not a symlink (use --force to replace)"
556
+ fi
557
+ else
558
+ # Missing entirely
559
+ if [[ "$DRY_RUN" == "true" ]]; then
560
+ echo " would create: $expected_link -> $expected_source"
561
+ else
562
+ create_symlink "$expected_source" "$expected_link" "false"
563
+ fi
564
+ repaired=$((repaired + 1))
565
+ fi
566
+ done
567
+ done
568
+
569
+ # Repair CLAUDE.md symlink
570
+ if [[ -f "$PROJECT_ROOT/$AGENTS_MD" ]]; then
571
+ if [[ -L "$PROJECT_ROOT/CLAUDE.md" ]]; then
572
+ local current_target
573
+ current_target="$(readlink "$PROJECT_ROOT/CLAUDE.md")"
574
+ if [[ "$current_target" != "$AGENTS_MD" ]]; then
575
+ if [[ "$DRY_RUN" == "true" ]]; then
576
+ echo " would relink: CLAUDE.md -> $AGENTS_MD (was $current_target)"
577
+ else
578
+ rm "$PROJECT_ROOT/CLAUDE.md"
579
+ create_symlink "$AGENTS_MD" "$PROJECT_ROOT/CLAUDE.md" "false"
580
+ fi
581
+ repaired=$((repaired + 1))
582
+ fi
583
+ elif [[ ! -e "$PROJECT_ROOT/CLAUDE.md" ]]; then
584
+ if [[ "$DRY_RUN" == "true" ]]; then
585
+ echo " would create: CLAUDE.md -> $AGENTS_MD"
586
+ else
587
+ create_symlink "$AGENTS_MD" "$PROJECT_ROOT/CLAUDE.md" "false"
588
+ fi
589
+ repaired=$((repaired + 1))
590
+ fi
591
+ fi
592
+
593
+ # Summary
594
+ if [[ "$fixed" -eq 0 ]] && [[ "$skipped" -eq 0 ]] && [[ "$repaired" -eq 0 ]]; then
595
+ info "Nothing to fix — all directories and symlinks are correct."
395
596
  else
396
- info "Fixed $fixed item(s). Run 'sync-agents sync' to update agent target symlinks."
597
+ if [[ "$fixed" -gt 0 ]]; then info "Fixed $fixed item(s)."; fi
598
+ if [[ "$merged" -gt 0 ]]; then info "Merged $merged item(s) (legacy overwrote existing)."; fi
599
+ if [[ "$skipped" -gt 0 ]]; then warn "Skipped $skipped item(s) (use without --no-clobber to merge)."; fi
600
+ if [[ "$repaired" -gt 0 ]]; then info "Repaired $repaired symlink(s)."; fi
601
+ if [[ "$fixed" -gt 0 ]]; then info "Run 'sync-agents sync' to update agent target symlinks."; fi
397
602
  fi
398
603
  }
399
604