@brickhouse-tech/sync-agents 0.1.8 → 0.1.10
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 +1 -1
- package/src/sh/sync-agents.sh +286 -6
package/package.json
CHANGED
package/src/sh/sync-agents.sh
CHANGED
|
@@ -75,6 +75,7 @@ ${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
79
|
inherit <label> <path> Add an inheritance link to AGENTS.md (convention-based)
|
|
79
80
|
inherit --list List current inheritance links
|
|
80
81
|
inherit --remove <label> Remove an inheritance link by label
|
|
@@ -184,7 +185,6 @@ cmd_init() {
|
|
|
184
185
|
else
|
|
185
186
|
# Inline fallback if template not found
|
|
186
187
|
cat > "$PROJECT_ROOT/$AGENTS_DIR/STATE.md" <<'STATE_EOF'
|
|
187
|
-
|
|
188
188
|
---
|
|
189
189
|
trigger: always_on
|
|
190
190
|
---
|
|
@@ -222,6 +222,9 @@ CONFIG_EOF
|
|
|
222
222
|
warn "$AGENTS_MD already exists, skipping (run 'sync-agents index' to regenerate)"
|
|
223
223
|
fi
|
|
224
224
|
|
|
225
|
+
# Add default .gitignore entries for agent tool directories
|
|
226
|
+
add_default_gitignore_entries
|
|
227
|
+
|
|
225
228
|
info "Initialization complete. Directory structure:"
|
|
226
229
|
print_tree "$PROJECT_ROOT/$AGENTS_DIR"
|
|
227
230
|
}
|
|
@@ -247,7 +250,14 @@ cmd_add() {
|
|
|
247
250
|
|
|
248
251
|
ensure_agents_dir
|
|
249
252
|
|
|
250
|
-
|
|
253
|
+
# Skills use directory layout: skills/name/SKILL.md
|
|
254
|
+
# Rules and workflows use flat files: rules/name.md, workflows/name.md
|
|
255
|
+
local filepath
|
|
256
|
+
if [[ "$type" == "skills" ]]; then
|
|
257
|
+
filepath="$PROJECT_ROOT/$AGENTS_DIR/$type/$name/SKILL.md"
|
|
258
|
+
else
|
|
259
|
+
filepath="$PROJECT_ROOT/$AGENTS_DIR/$type/$name.md"
|
|
260
|
+
fi
|
|
251
261
|
|
|
252
262
|
if [[ -f "$filepath" ]] && [[ "$FORCE" != "true" ]]; then
|
|
253
263
|
error "File already exists: $filepath (use --force to overwrite)"
|
|
@@ -263,6 +273,9 @@ cmd_add() {
|
|
|
263
273
|
*) template_name="RULE_TEMPLATE.md" ;;
|
|
264
274
|
esac
|
|
265
275
|
|
|
276
|
+
# Create parent directory for skills
|
|
277
|
+
mkdir -p "$(dirname "$filepath")"
|
|
278
|
+
|
|
266
279
|
if [[ -f "$TEMPLATES_DIR/$template_name" ]]; then
|
|
267
280
|
sed "s/\${NAME}/$name/g" "$TEMPLATES_DIR/$template_name" > "$filepath"
|
|
268
281
|
elif [[ -f "$TEMPLATES_DIR/RULE_TEMPLATE.md" ]]; then
|
|
@@ -270,7 +283,6 @@ cmd_add() {
|
|
|
270
283
|
sed "s/\${NAME}/$name/g" "$TEMPLATES_DIR/RULE_TEMPLATE.md" > "$filepath"
|
|
271
284
|
else
|
|
272
285
|
cat > "$filepath" <<TMPL_EOF
|
|
273
|
-
|
|
274
286
|
---
|
|
275
287
|
trigger: always_on
|
|
276
288
|
---
|
|
@@ -286,6 +298,105 @@ TMPL_EOF
|
|
|
286
298
|
info "Updated $AGENTS_MD index"
|
|
287
299
|
}
|
|
288
300
|
|
|
301
|
+
cmd_fix() {
|
|
302
|
+
ensure_agents_dir
|
|
303
|
+
|
|
304
|
+
local fix_type="${1:-all}"
|
|
305
|
+
local subdirs=()
|
|
306
|
+
|
|
307
|
+
case "$fix_type" in
|
|
308
|
+
skills|rules|workflows)
|
|
309
|
+
subdirs=("$fix_type")
|
|
310
|
+
;;
|
|
311
|
+
all)
|
|
312
|
+
subdirs=(skills rules workflows)
|
|
313
|
+
;;
|
|
314
|
+
*)
|
|
315
|
+
error "Unknown type: $fix_type (expected: skills, rules, workflows, or all)"
|
|
316
|
+
exit 1
|
|
317
|
+
;;
|
|
318
|
+
esac
|
|
319
|
+
|
|
320
|
+
local agents_abs
|
|
321
|
+
agents_abs="$(cd "$PROJECT_ROOT/$AGENTS_DIR" && pwd)"
|
|
322
|
+
local fixed=0
|
|
323
|
+
|
|
324
|
+
for subdir in "${subdirs[@]}"; do
|
|
325
|
+
local legacy_dir="$PROJECT_ROOT/$subdir"
|
|
326
|
+
local agents_subdir="$agents_abs/$subdir"
|
|
327
|
+
|
|
328
|
+
# Skip if legacy dir doesn't exist or is already a symlink
|
|
329
|
+
if [[ ! -d "$legacy_dir" ]] || [[ -L "$legacy_dir" ]]; then
|
|
330
|
+
continue
|
|
331
|
+
fi
|
|
332
|
+
|
|
333
|
+
info "Found legacy directory: $subdir/"
|
|
334
|
+
mkdir -p "$agents_subdir"
|
|
335
|
+
|
|
336
|
+
# Move each item from legacy dir into .agents/subdir
|
|
337
|
+
for item in "$legacy_dir"/*/; do
|
|
338
|
+
[[ -d "$item" ]] || continue
|
|
339
|
+
local name
|
|
340
|
+
name="$(basename "$item")"
|
|
341
|
+
|
|
342
|
+
if [[ -d "$agents_subdir/$name" ]]; then
|
|
343
|
+
warn "Skipping $subdir/$name — already exists in $AGENTS_DIR/$subdir/"
|
|
344
|
+
continue
|
|
345
|
+
fi
|
|
346
|
+
|
|
347
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
348
|
+
echo " would move: $subdir/$name -> $AGENTS_DIR/$subdir/$name"
|
|
349
|
+
else
|
|
350
|
+
mv "$item" "$agents_subdir/$name"
|
|
351
|
+
info "Moved: $subdir/$name -> $AGENTS_DIR/$subdir/$name"
|
|
352
|
+
fi
|
|
353
|
+
fixed=$((fixed + 1))
|
|
354
|
+
done
|
|
355
|
+
|
|
356
|
+
# Also move any top-level files (e.g. loose .md rules)
|
|
357
|
+
for item in "$legacy_dir"/*; do
|
|
358
|
+
[[ -f "$item" ]] || continue
|
|
359
|
+
local name
|
|
360
|
+
name="$(basename "$item")"
|
|
361
|
+
|
|
362
|
+
if [[ -f "$agents_subdir/$name" ]]; then
|
|
363
|
+
warn "Skipping $subdir/$name — already exists in $AGENTS_DIR/$subdir/"
|
|
364
|
+
continue
|
|
365
|
+
fi
|
|
366
|
+
|
|
367
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
368
|
+
echo " would move: $subdir/$name -> $AGENTS_DIR/$subdir/$name"
|
|
369
|
+
else
|
|
370
|
+
mv "$item" "$agents_subdir/$name"
|
|
371
|
+
info "Moved: $subdir/$name -> $AGENTS_DIR/$subdir/$name"
|
|
372
|
+
fi
|
|
373
|
+
fixed=$((fixed + 1))
|
|
374
|
+
done
|
|
375
|
+
|
|
376
|
+
# Remove the now-empty legacy dir and replace with symlink
|
|
377
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
378
|
+
echo " would replace $subdir/ with symlink -> $AGENTS_DIR/$subdir"
|
|
379
|
+
else
|
|
380
|
+
# Check if dir is empty (only . and .. remain)
|
|
381
|
+
if [[ -z "$(ls -A "$legacy_dir" 2>/dev/null)" ]]; then
|
|
382
|
+
rmdir "$legacy_dir"
|
|
383
|
+
ln -s "$AGENTS_DIR/$subdir" "$legacy_dir"
|
|
384
|
+
info "Replaced $subdir/ with symlink -> $AGENTS_DIR/$subdir"
|
|
385
|
+
else
|
|
386
|
+
warn "$subdir/ is not empty after migration — skipping symlink replacement"
|
|
387
|
+
warn "Remaining items:"
|
|
388
|
+
find "$legacy_dir" -mindepth 1 -maxdepth 1 -exec basename {} \; | sed 's/^/ /'
|
|
389
|
+
fi
|
|
390
|
+
fi
|
|
391
|
+
done
|
|
392
|
+
|
|
393
|
+
if [[ "$fixed" -eq 0 ]]; then
|
|
394
|
+
info "Nothing to fix — all directories are already in $AGENTS_DIR/ or symlinked."
|
|
395
|
+
else
|
|
396
|
+
info "Fixed $fixed item(s). Run 'sync-agents sync' to update agent target symlinks."
|
|
397
|
+
fi
|
|
398
|
+
}
|
|
399
|
+
|
|
289
400
|
cmd_sync() {
|
|
290
401
|
ensure_agents_dir
|
|
291
402
|
|
|
@@ -315,9 +426,162 @@ cmd_sync() {
|
|
|
315
426
|
create_symlink "$AGENTS_MD" "$PROJECT_ROOT/CLAUDE.md" "$DRY_RUN"
|
|
316
427
|
fi
|
|
317
428
|
|
|
429
|
+
# Update .gitignore with synced symlink entries
|
|
430
|
+
update_gitignore
|
|
431
|
+
|
|
318
432
|
info "Sync complete."
|
|
319
433
|
}
|
|
320
434
|
|
|
435
|
+
# --------------------------------------------------------------------------
|
|
436
|
+
# .gitignore management
|
|
437
|
+
# --------------------------------------------------------------------------
|
|
438
|
+
|
|
439
|
+
# Add default .gitignore entries for agent tool directories (called during init)
|
|
440
|
+
add_default_gitignore_entries() {
|
|
441
|
+
local gitignore="$PROJECT_ROOT/.gitignore"
|
|
442
|
+
|
|
443
|
+
# Create .gitignore if it doesn't exist
|
|
444
|
+
if [[ ! -f "$gitignore" ]]; then
|
|
445
|
+
touch "$gitignore"
|
|
446
|
+
info "Created .gitignore"
|
|
447
|
+
fi
|
|
448
|
+
|
|
449
|
+
# Check if .DS_Store is already present (case-insensitive check)
|
|
450
|
+
if ! grep -qiE "^\.DS_Store$" "$gitignore" 2>/dev/null; then
|
|
451
|
+
# Add .DS_Store if not present
|
|
452
|
+
if [[ -s "$gitignore" ]] && ! tail -c1 "$gitignore" | grep -q '^$'; then
|
|
453
|
+
echo "" >> "$gitignore"
|
|
454
|
+
fi
|
|
455
|
+
echo ".DS_Store" >> "$gitignore"
|
|
456
|
+
info "Added .DS_Store to .gitignore"
|
|
457
|
+
fi
|
|
458
|
+
|
|
459
|
+
# Define default entries (tool artifacts, not symlinks)
|
|
460
|
+
# Using pattern: ignore everything in dir, except specific files we want to track
|
|
461
|
+
local marker="# sync-agents — ignore tool artifacts, keep symlinks"
|
|
462
|
+
|
|
463
|
+
# Check if sync-agents section already exists
|
|
464
|
+
if grep -qF "$marker" "$gitignore"; then
|
|
465
|
+
# Section exists - check if we need to add any missing entries
|
|
466
|
+
local needs_update=false
|
|
467
|
+
|
|
468
|
+
# Check for each pattern
|
|
469
|
+
if ! grep -qF ".cursor/*" "$gitignore"; then needs_update=true; fi
|
|
470
|
+
if ! grep -qF "!.cursor/rules" "$gitignore"; then needs_update=true; fi
|
|
471
|
+
if ! grep -qF ".codex/*" "$gitignore"; then needs_update=true; fi
|
|
472
|
+
if ! grep -qF "!.codex/instructions.md" "$gitignore"; then needs_update=true; fi
|
|
473
|
+
if ! grep -qF ".github/copilot/*" "$gitignore"; then needs_update=true; fi
|
|
474
|
+
if ! grep -qF "!.github/copilot/instructions.md" "$gitignore"; then needs_update=true; fi
|
|
475
|
+
|
|
476
|
+
if [[ "$needs_update" == "true" ]]; then
|
|
477
|
+
# Rebuild section by reading the file, preserving everything else
|
|
478
|
+
local tmp
|
|
479
|
+
tmp="$(mktemp)"
|
|
480
|
+
local in_section=false
|
|
481
|
+
|
|
482
|
+
while IFS= read -r line; do
|
|
483
|
+
if [[ "$line" == "$marker" ]]; then
|
|
484
|
+
in_section=true
|
|
485
|
+
# Output the marker
|
|
486
|
+
{
|
|
487
|
+
echo "$line"
|
|
488
|
+
echo ".cursor/*"
|
|
489
|
+
echo "!.cursor/rules"
|
|
490
|
+
echo ".codex/*"
|
|
491
|
+
echo "!.codex/instructions.md"
|
|
492
|
+
echo ".github/copilot/*"
|
|
493
|
+
echo "!.github/copilot/instructions.md"
|
|
494
|
+
} >> "$tmp"
|
|
495
|
+
continue
|
|
496
|
+
fi
|
|
497
|
+
|
|
498
|
+
# Skip old entries in the sync-agents section (until we hit empty line or new section)
|
|
499
|
+
if [[ "$in_section" == "true" ]]; then
|
|
500
|
+
if [[ -z "$line" ]] || [[ "$line" == "#"* ]]; then
|
|
501
|
+
in_section=false
|
|
502
|
+
echo "$line" >> "$tmp"
|
|
503
|
+
fi
|
|
504
|
+
# Skip old entry lines (they're replaced above)
|
|
505
|
+
continue
|
|
506
|
+
fi
|
|
507
|
+
|
|
508
|
+
echo "$line" >> "$tmp"
|
|
509
|
+
done < "$gitignore"
|
|
510
|
+
|
|
511
|
+
mv "$tmp" "$gitignore"
|
|
512
|
+
info "Updated sync-agents section in .gitignore"
|
|
513
|
+
fi
|
|
514
|
+
else
|
|
515
|
+
# Section doesn't exist, add entire block
|
|
516
|
+
# Add separator if file is non-empty
|
|
517
|
+
if [[ -s "$gitignore" ]] && ! tail -c1 "$gitignore" | grep -q '^$'; then
|
|
518
|
+
echo "" >> "$gitignore"
|
|
519
|
+
fi
|
|
520
|
+
|
|
521
|
+
# Add all entries
|
|
522
|
+
{
|
|
523
|
+
echo "$marker"
|
|
524
|
+
echo ".cursor/*"
|
|
525
|
+
echo "!.cursor/rules"
|
|
526
|
+
echo ".codex/*"
|
|
527
|
+
echo "!.codex/instructions.md"
|
|
528
|
+
echo ".github/copilot/*"
|
|
529
|
+
echo "!.github/copilot/instructions.md"
|
|
530
|
+
} >> "$gitignore"
|
|
531
|
+
|
|
532
|
+
info "Added sync-agents section to .gitignore with 7 entries"
|
|
533
|
+
fi
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
update_gitignore() {
|
|
537
|
+
local gitignore="$PROJECT_ROOT/.gitignore"
|
|
538
|
+
|
|
539
|
+
# Build list of entries that should be ignored (synced symlinks)
|
|
540
|
+
local entries=()
|
|
541
|
+
for target in "${ACTIVE_TARGETS[@]}"; do
|
|
542
|
+
local target_dir
|
|
543
|
+
target_dir="$(resolve_target_dir "$target" "$PROJECT_ROOT")"
|
|
544
|
+
local rel_path="${target_dir#"$PROJECT_ROOT"/}/"
|
|
545
|
+
entries+=("$rel_path")
|
|
546
|
+
done
|
|
547
|
+
entries+=("CLAUDE.md")
|
|
548
|
+
|
|
549
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
550
|
+
for entry in "${entries[@]}"; do
|
|
551
|
+
if [[ ! -f "$gitignore" ]] || ! grep -qxF "$entry" "$gitignore"; then
|
|
552
|
+
echo " would add to .gitignore: $entry"
|
|
553
|
+
fi
|
|
554
|
+
done
|
|
555
|
+
return 0
|
|
556
|
+
fi
|
|
557
|
+
|
|
558
|
+
# Create .gitignore if it doesn't exist
|
|
559
|
+
[[ -f "$gitignore" ]] || touch "$gitignore"
|
|
560
|
+
|
|
561
|
+
local added=0
|
|
562
|
+
for entry in "${entries[@]}"; do
|
|
563
|
+
if ! grep -qxF "$entry" "$gitignore"; then
|
|
564
|
+
# Add sync-agents header on first addition
|
|
565
|
+
if [[ "$added" -eq 0 ]]; then
|
|
566
|
+
# Check if header already exists
|
|
567
|
+
if ! grep -qF "# sync-agents" "$gitignore"; then
|
|
568
|
+
# Add a blank line separator if file is non-empty
|
|
569
|
+
if [[ -s "$gitignore" ]]; then
|
|
570
|
+
echo "" >> "$gitignore"
|
|
571
|
+
fi
|
|
572
|
+
echo "# sync-agents (generated symlinks)" >> "$gitignore"
|
|
573
|
+
fi
|
|
574
|
+
fi
|
|
575
|
+
echo "$entry" >> "$gitignore"
|
|
576
|
+
added=$((added + 1))
|
|
577
|
+
fi
|
|
578
|
+
done
|
|
579
|
+
|
|
580
|
+
if [[ "$added" -gt 0 ]]; then
|
|
581
|
+
info "Added $added entries to .gitignore"
|
|
582
|
+
fi
|
|
583
|
+
}
|
|
584
|
+
|
|
321
585
|
cmd_status() {
|
|
322
586
|
echo -e "${BOLD}sync-agents${RESET} v${VERSION}"
|
|
323
587
|
echo ""
|
|
@@ -749,7 +1013,6 @@ generate_agents_md() {
|
|
|
749
1013
|
fi
|
|
750
1014
|
|
|
751
1015
|
cat > "$outfile" <<'HEADER'
|
|
752
|
-
|
|
753
1016
|
---
|
|
754
1017
|
trigger: always_on
|
|
755
1018
|
---
|
|
@@ -783,16 +1046,30 @@ HEADER
|
|
|
783
1046
|
fi
|
|
784
1047
|
echo ""
|
|
785
1048
|
|
|
786
|
-
# Skills
|
|
1049
|
+
# Skills (directory layout: skills/name/SKILL.md, or legacy flat: skills/name.md)
|
|
787
1050
|
echo "## Skills"
|
|
788
1051
|
echo ""
|
|
1052
|
+
local has_skills="false"
|
|
1053
|
+
# Directory skills: skills/name/SKILL.md
|
|
1054
|
+
for d in "$agents_dir/skills/"*/; do
|
|
1055
|
+
[[ -d "$d" ]] || continue
|
|
1056
|
+
local name
|
|
1057
|
+
name="$(basename "$d")"
|
|
1058
|
+
if [[ -f "$d/SKILL.md" ]]; then
|
|
1059
|
+
echo "- [$name](.agents/skills/$name/SKILL.md)"
|
|
1060
|
+
has_skills="true"
|
|
1061
|
+
fi
|
|
1062
|
+
done
|
|
1063
|
+
# Legacy flat skills: skills/name.md
|
|
789
1064
|
if compgen -G "$agents_dir/skills/*.md" > /dev/null 2>&1; then
|
|
790
1065
|
for f in "$agents_dir/skills/"*.md; do
|
|
791
1066
|
local name
|
|
792
1067
|
name="$(basename "$f" .md)"
|
|
793
1068
|
echo "- [$name](.agents/skills/$name.md)"
|
|
1069
|
+
has_skills="true"
|
|
794
1070
|
done
|
|
795
|
-
|
|
1071
|
+
fi
|
|
1072
|
+
if [[ "$has_skills" == "false" ]]; then
|
|
796
1073
|
echo "_No skills defined yet. Add one with \`sync-agents add skill <name>\`._"
|
|
797
1074
|
fi
|
|
798
1075
|
echo ""
|
|
@@ -971,6 +1248,9 @@ main() {
|
|
|
971
1248
|
import)
|
|
972
1249
|
cmd_import "$@"
|
|
973
1250
|
;;
|
|
1251
|
+
fix)
|
|
1252
|
+
cmd_fix "$@"
|
|
1253
|
+
;;
|
|
974
1254
|
hook)
|
|
975
1255
|
cmd_hook
|
|
976
1256
|
;;
|