@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 +6 -2
- package/src/sh/sync-agents.sh +213 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brickhouse-tech/sync-agents",
|
|
3
|
-
"version": "0.1.
|
|
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
|
|
41
|
+
"sort-package-json": ">=3"
|
|
38
42
|
}
|
|
39
43
|
}
|
package/src/sh/sync-agents.sh
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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" ]]
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
394
|
-
|
|
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).
|
|
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
|
|