@brickhouse-tech/sync-agents 0.1.5 → 0.1.6

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/README.md CHANGED
@@ -63,6 +63,9 @@ AGENTS.md is also symlinked to CLAUDE.md so that Claude reads the index natively
63
63
  | `watch` | Watch `.agents/` for changes and auto-regenerate `AGENTS.md` |
64
64
  | `import <url>` | Import a rule/skill/workflow from a URL |
65
65
  | `hook` | Install a pre-commit git hook for auto-sync |
66
+ | `inherit <label> <path>` | Add an inheritance link to AGENTS.md |
67
+ | `inherit --list` | List current inheritance links |
68
+ | `inherit --remove <label>` | Remove an inheritance link by label |
66
69
  | `status` | Show the current sync status of all targets and symlinks |
67
70
  | `add <type> <name>` | Add a new rule, skill, or workflow from a template (type is `rule`, `skill`, or `workflow`) |
68
71
  | `index` | Regenerate `AGENTS.md` by scanning the contents of `.agents/` |
@@ -79,6 +82,150 @@ AGENTS.md is also symlinked to CLAUDE.md so that Claude reads the index natively
79
82
  | `--dry-run` | Show what would be done without making changes |
80
83
  | `--force` | Overwrite existing files and symlinks |
81
84
 
85
+ ## Inheritance
86
+
87
+ Projects can inherit agent rules from parent directories (org, team, global) using a convention-based approach. This enables hierarchical rule sharing without duplicating files.
88
+
89
+ ### How It Works
90
+
91
+ Add an `## Inherits` section to your project's `AGENTS.md` that links to parent-level agent configs:
92
+
93
+ ```markdown
94
+ ## Inherits
95
+ - [global](../../AGENTS.md)
96
+ - [team](../AGENTS.md)
97
+ ```
98
+
99
+ AI agents (Claude, Codex, etc.) follow markdown links natively — when they read your project's `AGENTS.md`, they'll traverse the inheritance chain and apply rules from all levels.
100
+
101
+ ### Hierarchy Example
102
+
103
+ ```
104
+ ~/code/ # Global: security norms, universal rules
105
+ ├── .agents/
106
+ ├── AGENTS.md
107
+ └── org/ # Org-level: coding standards, shared workflows
108
+ ├── .agents/
109
+ ├── AGENTS.md
110
+ └── team/ # Team-level: language-specific rules
111
+ ├── .agents/
112
+ ├── AGENTS.md
113
+ └── project/ # Project: project-specific rules + inherits
114
+ ├── .agents/
115
+ └── AGENTS.md → ## Inherits links to team, org, global
116
+ ```
117
+
118
+ **Inheritance is upward-only.** A project declares what it inherits from. Parent directories don't need to know about their children — when an agent works at the org level, it already has access to org-level rules.
119
+
120
+ ### Managing Inheritance
121
+
122
+ ```bash
123
+ # Add an inheritance link
124
+ sync-agents inherit global ../../AGENTS.md
125
+ sync-agents inherit team ../AGENTS.md
126
+
127
+ # List current inheritance links
128
+ sync-agents inherit --list
129
+
130
+ # Remove an inheritance link
131
+ sync-agents inherit --remove global
132
+ ```
133
+
134
+ The `## Inherits` section is preserved across `sync-agents index` regenerations.
135
+
136
+ ### Full Example
137
+
138
+ Set up a three-level hierarchy: global rules → org standards → project config.
139
+
140
+ ```bash
141
+ # 1. Create global rules (e.g. ~/code/.agents/)
142
+ cd ~/code
143
+ sync-agents init
144
+ sync-agents add rule security
145
+ cat > .agents/rules/security.md << 'EOF'
146
+ ---
147
+ trigger: always_on
148
+ ---
149
+ # Security
150
+ - Never commit secrets or API keys
151
+ - Validate all external input
152
+ - Use parameterized queries for database access
153
+ EOF
154
+
155
+ # 2. Create org-level rules (e.g. ~/code/myorg/.agents/)
156
+ cd ~/code/myorg
157
+ sync-agents init
158
+ sync-agents add rule go-standards
159
+ cat > .agents/rules/go-standards.md << 'EOF'
160
+ ---
161
+ trigger: always_on
162
+ ---
163
+ # Go Standards
164
+ - Use `gofmt` and `golangci-lint` on all Go files
165
+ - Prefer table-driven tests
166
+ - Export only what consumers need
167
+ EOF
168
+
169
+ # 3. Create project with inheritance
170
+ cd ~/code/myorg/api-service
171
+ sync-agents init
172
+ sync-agents add rule api-conventions
173
+
174
+ # Link to parent levels
175
+ sync-agents inherit org ../AGENTS.md
176
+ sync-agents inherit global ../../AGENTS.md
177
+
178
+ # Sync to agent directories
179
+ sync-agents sync
180
+ ```
181
+
182
+ The project's `AGENTS.md` now looks like:
183
+
184
+ ```markdown
185
+ ## Inherits
186
+ - [org](../AGENTS.md)
187
+ - [global](../../AGENTS.md)
188
+
189
+ ## Rules
190
+ - [api-conventions](.agents/rules/api-conventions.md)
191
+
192
+ ## Skills
193
+ _No skills defined yet._
194
+
195
+ ## Workflows
196
+ _No workflows defined yet._
197
+ ```
198
+
199
+ When an AI agent reads this file, it follows the `## Inherits` links and applies rules from all three levels — project-specific API conventions, org-wide Go standards, and global security rules.
200
+
201
+ ### Verifying Inheritance
202
+
203
+ ```bash
204
+ # Check what's inherited
205
+ sync-agents inherit --list
206
+ # Output:
207
+ # - [org](../AGENTS.md)
208
+ # - [global](../../AGENTS.md)
209
+
210
+ # Remove a link if no longer needed
211
+ sync-agents inherit --remove global
212
+
213
+ # Re-add with a different path
214
+ sync-agents inherit global ../../AGENTS.md
215
+ ```
216
+
217
+ ## Examples
218
+
219
+ The [`examples/`](examples/) directory contains ready-to-use rules, skills, and workflows. Import them directly:
220
+
221
+ ```bash
222
+ sync-agents import https://raw.githubusercontent.com/brickhouse-tech/sync-agents/main/examples/rules/no-secrets.md
223
+ sync-agents import https://raw.githubusercontent.com/brickhouse-tech/sync-agents/main/examples/skills/code-review.md
224
+ sync-agents import https://raw.githubusercontent.com/brickhouse-tech/sync-agents/main/examples/workflows/pr-checklist.md
225
+ ```
226
+
227
+ See [examples/README.md](examples/README.md) for the full list.
228
+
82
229
  ## Usage
83
230
 
84
231
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brickhouse-tech/sync-agents",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Simple scripts to DRY up common agent interactions across multiple LLM providers.",
5
5
  "keywords": [
6
6
  "agents",
@@ -75,6 +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
+ inherit <label> <path> Add an inheritance link to AGENTS.md (convention-based)
79
+ inherit --list List current inheritance links
80
+ inherit --remove <label> Remove an inheritance link by label
78
81
 
79
82
  ${BOLD}OPTIONS${RESET}
80
83
  -h, --help Show this help message
@@ -526,6 +529,198 @@ cmd_clean() {
526
529
  info "Clean complete."
527
530
  }
528
531
 
532
+ # --------------------------------------------------------------------------
533
+ # Inherit
534
+ # --------------------------------------------------------------------------
535
+
536
+ cmd_inherit() {
537
+ local action="${1:-}"
538
+
539
+ # --list: show current inherits
540
+ if [[ "$action" == "--list" ]]; then
541
+ if [[ ! -f "$PROJECT_ROOT/$AGENTS_MD" ]]; then
542
+ info "No AGENTS.md found."
543
+ return 0
544
+ fi
545
+ local in_section="false"
546
+ while IFS= read -r line; do
547
+ if [[ "$line" =~ ^##[[:space:]]+Inherits ]]; then
548
+ in_section="true"
549
+ continue
550
+ fi
551
+ if [[ "$in_section" == "true" ]] && [[ "$line" =~ ^## ]]; then
552
+ break
553
+ fi
554
+ if [[ "$in_section" == "true" ]] && [[ "$line" =~ ^-[[:space:]]+\[ ]]; then
555
+ echo "$line"
556
+ fi
557
+ done < "$PROJECT_ROOT/$AGENTS_MD"
558
+ return 0
559
+ fi
560
+
561
+ # --remove <label>: remove an inherit entry
562
+ if [[ "$action" == "--remove" ]]; then
563
+ local label="${2:-}"
564
+ if [[ -z "$label" ]]; then
565
+ error "Usage: sync-agents inherit --remove <label>"
566
+ exit 1
567
+ fi
568
+ if [[ ! -f "$PROJECT_ROOT/$AGENTS_MD" ]]; then
569
+ error "No AGENTS.md found."
570
+ exit 1
571
+ fi
572
+ # Remove the line matching [label](...) from the Inherits section
573
+ local tmp
574
+ tmp="$(mktemp)"
575
+ local in_section="false"
576
+ local removed="false"
577
+ while IFS= read -r line; do
578
+ if [[ "$line" =~ ^##[[:space:]]+Inherits ]]; then
579
+ in_section="true"
580
+ echo "$line" >> "$tmp"
581
+ continue
582
+ fi
583
+ if [[ "$in_section" == "true" ]] && [[ "$line" =~ ^## ]]; then
584
+ in_section="false"
585
+ fi
586
+ if [[ "$in_section" == "true" ]] && [[ "$line" == *"[$label]("* ]]; then
587
+ removed="true"
588
+ continue
589
+ fi
590
+ echo "$line" >> "$tmp"
591
+ done < "$PROJECT_ROOT/$AGENTS_MD"
592
+ mv "$tmp" "$PROJECT_ROOT/$AGENTS_MD"
593
+ if [[ "$removed" == "true" ]]; then
594
+ info "Removed inherit: $label"
595
+ else
596
+ warn "No inherit found with label: $label"
597
+ fi
598
+ return 0
599
+ fi
600
+
601
+ # Default: add <label> <path>
602
+ local label="$action"
603
+ local path="${2:-}"
604
+
605
+ if [[ -z "$label" ]] || [[ -z "$path" ]]; then
606
+ error "Usage: sync-agents inherit <label> <path>"
607
+ error " sync-agents inherit --list"
608
+ error " sync-agents inherit --remove <label>"
609
+ exit 1
610
+ fi
611
+
612
+ # Validate the path exists (resolve relative to PROJECT_ROOT)
613
+ local resolved_path
614
+ if [[ "$path" == /* ]] || [[ "$path" == ~* ]]; then
615
+ resolved_path="${path/#\~/$HOME}"
616
+ else
617
+ resolved_path="$PROJECT_ROOT/$path"
618
+ fi
619
+
620
+ if [[ ! -f "$resolved_path" ]] && [[ ! -d "$resolved_path" ]]; then
621
+ warn "Path does not exist: $path (link will be added anyway)"
622
+ fi
623
+
624
+ # Check if AGENTS.md exists
625
+ if [[ ! -f "$PROJECT_ROOT/$AGENTS_MD" ]]; then
626
+ error "No AGENTS.md found. Run 'sync-agents init' first."
627
+ exit 1
628
+ fi
629
+
630
+ # Check if Inherits section exists; if not, add it after the header
631
+ if ! grep -q "^## Inherits" "$PROJECT_ROOT/$AGENTS_MD"; then
632
+ # Insert Inherits section right after the header block (after first blank line following description)
633
+ local tmp
634
+ tmp="$(mktemp)"
635
+ local header_done="false"
636
+ local inherits_written="false"
637
+ while IFS= read -r line; do
638
+ echo "$line" >> "$tmp"
639
+ # Write inherits section after the description paragraph (first line starting with "This file")
640
+ if [[ "$header_done" == "false" ]] && [[ "$line" == "This file indexes"* ]]; then
641
+ header_done="true"
642
+ {
643
+ echo ""
644
+ echo "## Inherits"
645
+ echo ""
646
+ echo "- [$label]($path)"
647
+ } >> "$tmp"
648
+ inherits_written="true"
649
+ fi
650
+ done < "$PROJECT_ROOT/$AGENTS_MD"
651
+ # Fallback: if header pattern wasn't found, append at the end before ## Rules
652
+ if [[ "$inherits_written" == "false" ]]; then
653
+ rm "$tmp"
654
+ tmp="$(mktemp)"
655
+ while IFS= read -r line; do
656
+ if [[ "$line" == "## Rules" ]] && [[ "$inherits_written" == "false" ]]; then
657
+ {
658
+ echo "## Inherits"
659
+ echo ""
660
+ echo "- [$label]($path)"
661
+ echo ""
662
+ } >> "$tmp"
663
+ inherits_written="true"
664
+ fi
665
+ echo "$line" >> "$tmp"
666
+ done < "$PROJECT_ROOT/$AGENTS_MD"
667
+ fi
668
+ mv "$tmp" "$PROJECT_ROOT/$AGENTS_MD"
669
+ else
670
+ # Inherits section exists — check for duplicate label
671
+ if grep -q "\[$label\](" "$PROJECT_ROOT/$AGENTS_MD"; then
672
+ warn "Inherit with label '$label' already exists. Use --remove first to update."
673
+ return 1
674
+ fi
675
+ # Append to existing Inherits section (after last inherit entry or section header)
676
+ local tmp
677
+ tmp="$(mktemp)"
678
+ local in_section="false"
679
+ local added="false"
680
+ while IFS= read -r line; do
681
+ if [[ "$line" =~ ^##[[:space:]]+Inherits ]]; then
682
+ in_section="true"
683
+ echo "$line" >> "$tmp"
684
+ continue
685
+ fi
686
+ # When we hit the next section or blank line after entries, insert
687
+ if [[ "$in_section" == "true" ]] && [[ "$added" == "false" ]]; then
688
+ if [[ "$line" =~ ^## ]] || [[ -z "$line" ]]; then
689
+ # Check if previous content had entries; add after them
690
+ if [[ "$line" =~ ^## ]]; then
691
+ {
692
+ echo "- [$label]($path)"
693
+ echo ""
694
+ } >> "$tmp"
695
+ added="true"
696
+ in_section="false"
697
+ fi
698
+ fi
699
+ if [[ "$line" =~ ^-[[:space:]]+\[ ]]; then
700
+ echo "$line" >> "$tmp"
701
+ continue
702
+ fi
703
+ if [[ -z "$line" ]] && [[ "$added" == "false" ]]; then
704
+ echo "- [$label]($path)" >> "$tmp"
705
+ added="true"
706
+ in_section="false"
707
+ echo "$line" >> "$tmp"
708
+ continue
709
+ fi
710
+ fi
711
+ echo "$line" >> "$tmp"
712
+ done < "$PROJECT_ROOT/$AGENTS_MD"
713
+ # If we never added (section was at end of file)
714
+ if [[ "$added" == "false" ]]; then
715
+ echo "- [$label]($path)" >> "$tmp"
716
+ echo "" >> "$tmp"
717
+ fi
718
+ mv "$tmp" "$PROJECT_ROOT/$AGENTS_MD"
719
+ fi
720
+
721
+ info "Added inherit: [$label]($path)"
722
+ }
723
+
529
724
  # --------------------------------------------------------------------------
530
725
  # Index generator
531
726
  # --------------------------------------------------------------------------
@@ -534,6 +729,25 @@ generate_agents_md() {
534
729
  local outfile="$PROJECT_ROOT/$AGENTS_MD"
535
730
  local agents_dir="$PROJECT_ROOT/$AGENTS_DIR"
536
731
 
732
+ # Preserve existing Inherits section before regenerating
733
+ local inherits_block=""
734
+ if [[ -f "$outfile" ]]; then
735
+ local in_section="false"
736
+ while IFS= read -r line; do
737
+ if [[ "$line" =~ ^##[[:space:]]+Inherits ]]; then
738
+ in_section="true"
739
+ inherits_block+="$line"$'\n'
740
+ continue
741
+ fi
742
+ if [[ "$in_section" == "true" ]] && [[ "$line" =~ ^## ]]; then
743
+ break
744
+ fi
745
+ if [[ "$in_section" == "true" ]]; then
746
+ inherits_block+="$line"$'\n'
747
+ fi
748
+ done < "$outfile"
749
+ fi
750
+
537
751
  cat > "$outfile" <<'HEADER'
538
752
 
539
753
  ---
@@ -550,6 +764,11 @@ This file indexes all rules, skills, and workflows defined in `.agents/`.
550
764
  HEADER
551
765
 
552
766
  {
767
+ # Inherits (preserved from previous AGENTS.md)
768
+ if [[ -n "$inherits_block" ]]; then
769
+ printf '%s\n' "$inherits_block"
770
+ fi
771
+
553
772
  # Rules
554
773
  echo "## Rules"
555
774
  echo ""
@@ -681,6 +900,10 @@ main() {
681
900
  shift
682
901
  ;;
683
902
  -*)
903
+ if [[ -n "$command" ]]; then
904
+ # Unknown flag after command — pass to subcommand
905
+ break
906
+ fi
684
907
  error "Unknown option: $1"
685
908
  usage
686
909
  exit 1
@@ -751,6 +974,9 @@ main() {
751
974
  hook)
752
975
  cmd_hook
753
976
  ;;
977
+ inherit)
978
+ cmd_inherit "$@"
979
+ ;;
754
980
  "")
755
981
  usage
756
982
  exit 0