@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brickhouse-tech/sync-agents",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Simple scripts to DRY up common agent interactions across multiple LLM providers.",
5
5
  "keywords": [
6
6
  "agents",
@@ -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
- local filepath="$PROJECT_ROOT/$AGENTS_DIR/$type/$name.md"
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
- else
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
  ;;