jekyll-theme-zer0 0.22.20 โ†’ 0.22.22

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.
data/scripts/bin/test CHANGED
@@ -30,11 +30,12 @@ show_usage() {
30
30
  USAGE:
31
31
  ./scripts/bin/test [OPTIONS] [TEST_SUITE]
32
32
 
33
- TEST SUITES:
33
+ TEST_SUITES:
34
34
  all Run all tests (default)
35
35
  lib Run library unit tests only
36
36
  theme Run theme validation tests only
37
37
  integration Run integration tests only
38
+ install Run installer e2e suites only (test/test_install_*.sh)
38
39
 
39
40
  OPTIONS:
40
41
  --verbose, -v Show detailed test output
@@ -67,7 +68,7 @@ for arg in "$@"; do
67
68
  --dry-run)
68
69
  export DRY_RUN=true
69
70
  ;;
70
- all|lib|theme|integration)
71
+ all|lib|theme|integration|install)
71
72
  TEST_SUITE="$arg"
72
73
  ;;
73
74
  *)
@@ -161,6 +162,43 @@ run_integration_tests() {
161
162
  fi
162
163
  }
163
164
 
165
+ # Run installer e2e tests (test/test_install_*.sh in repo root)
166
+ run_install_tests() {
167
+ log_info "Running installer e2e tests..."
168
+
169
+ # Repo root is two levels up from scripts/bin/
170
+ local repo_root
171
+ repo_root="$(cd "$SCRIPTS_ROOT/.." && pwd)"
172
+ local install_test_dir="$repo_root/test"
173
+
174
+ if [[ ! -d "$install_test_dir" ]]; then
175
+ log_warning "Installer test dir not found: $install_test_dir"
176
+ return
177
+ fi
178
+
179
+ local found=false
180
+ for test_file in "$install_test_dir"/test_install_*.sh; do
181
+ [[ -f "$test_file" ]] || continue
182
+ found=true
183
+ local test_name
184
+ test_name=$(basename "$test_file")
185
+ log_info "Running installer suite: $test_name"
186
+
187
+ if bash "$test_file"; then
188
+ log_success "$test_name passed"
189
+ ((TOTAL_PASSED++)) || true
190
+ else
191
+ log_error "$test_name failed"
192
+ ((TOTAL_FAILED++)) || true
193
+ fi
194
+ ((TOTAL_TESTS++)) || true
195
+ done
196
+
197
+ if [[ "$found" == "false" ]]; then
198
+ log_warning "No installer test suites found (test_install_*.sh)"
199
+ fi
200
+ }
201
+
164
202
  # Main execution
165
203
  log_info "๐Ÿงช Starting test suite: $TEST_SUITE"
166
204
  echo ""
@@ -172,6 +210,8 @@ case $TEST_SUITE in
172
210
  run_theme_tests
173
211
  echo ""
174
212
  run_integration_tests
213
+ echo ""
214
+ run_install_tests
175
215
  ;;
176
216
  lib)
177
217
  run_lib_tests
@@ -182,6 +222,9 @@ case $TEST_SUITE in
182
222
  integration)
183
223
  run_integration_tests
184
224
  ;;
225
+ install)
226
+ run_install_tests
227
+ ;;
185
228
  esac
186
229
 
187
230
  # Summary
@@ -0,0 +1,200 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # =============================================================================
5
+ # generate-roadmap.rb
6
+ # =============================================================================
7
+ #
8
+ # Reads `_data/roadmap.yml` and updates the README.md roadmap section in-place.
9
+ #
10
+ # It rewrites two regions delimited by HTML comment markers:
11
+ #
12
+ # <!-- ROADMAP_MERMAID:START --> ... <!-- ROADMAP_MERMAID:END -->
13
+ # <!-- ROADMAP_TABLE:START --> ... <!-- ROADMAP_TABLE:END -->
14
+ #
15
+ # Usage:
16
+ # ruby scripts/generate-roadmap.rb # update README.md in place
17
+ # ruby scripts/generate-roadmap.rb --check # exit non-zero if README is stale
18
+ # ruby scripts/generate-roadmap.rb --stdout # print regenerated sections only
19
+ #
20
+ # This script has no gem dependencies beyond the Ruby stdlib.
21
+ # =============================================================================
22
+
23
+ require 'yaml'
24
+ require 'date'
25
+ require 'optparse'
26
+
27
+ ROOT = File.expand_path('..', __dir__)
28
+ DATA_FILE = File.join(ROOT, '_data', 'roadmap.yml')
29
+ README = File.join(ROOT, 'README.md')
30
+
31
+ MERMAID_START = '<!-- ROADMAP_MERMAID:START -->'
32
+ MERMAID_END = '<!-- ROADMAP_MERMAID:END -->'
33
+ TABLE_START = '<!-- ROADMAP_TABLE:START -->'
34
+ TABLE_END = '<!-- ROADMAP_TABLE:END -->'
35
+
36
+ # Width used to right-pad the gantt label column so the `:status, start, end`
37
+ # tail aligns vertically. Adjust if very long version/title combinations show up.
38
+ GANTT_LABEL_WIDTH = 28
39
+
40
+ # ---------------------------------------------------------------------------
41
+ # Rendering helpers
42
+ # ---------------------------------------------------------------------------
43
+
44
+ # Mermaid gantt task line for a single milestone.
45
+ #
46
+ # v0.22 AIEO Optimization :active, 2026-03, 2026-04
47
+ # v1.0 Stable Release :milestone, 2027-01, 1d
48
+ #
49
+ def gantt_task(milestone)
50
+ label = "v#{milestone['version']} #{milestone['title']}"
51
+ status = milestone['status'].to_s
52
+ start = milestone['start']
53
+ finish = milestone['end'] || milestone['start']
54
+
55
+ prefix =
56
+ case status
57
+ when 'completed' then 'done, '
58
+ when 'active' then 'active, '
59
+ when 'milestone' then 'milestone, '
60
+ else ''
61
+ end
62
+
63
+ range = status == 'milestone' ? "#{start}, 1d" : "#{start}, #{finish}"
64
+ " #{label.ljust(GANTT_LABEL_WIDTH)} :#{prefix}#{range}"
65
+ end
66
+
67
+ def render_mermaid(data)
68
+ title = data.dig('meta', 'title') || 'zer0-mistakes Roadmap'
69
+ milestones = data['milestones'] || []
70
+
71
+ # Group by section while preserving the order in which sections first appear.
72
+ sections = milestones.group_by { |m| m['section'] || 'Roadmap' }
73
+ ordered = milestones.map { |m| m['section'] }.uniq
74
+
75
+ lines = []
76
+ lines << '```mermaid'
77
+ lines << 'gantt'
78
+ lines << " title #{title}"
79
+ lines << ' dateFormat YYYY-MM'
80
+ ordered.each do |section|
81
+ lines << " section #{section}"
82
+ sections[section].each { |m| lines << gantt_task(m) }
83
+ end
84
+ lines << '```'
85
+ lines.join("\n")
86
+ end
87
+
88
+ # Status โ†’ human-readable target column for the summary table.
89
+ def target_label(milestone)
90
+ case milestone['status']
91
+ when 'completed'
92
+ if (released = milestone['released'])
93
+ Date.parse(released.to_s).strftime('%b %Y')
94
+ else
95
+ 'Completed'
96
+ end
97
+ when 'active'
98
+ milestone['target'] || 'In progress'
99
+ when 'milestone', 'planned'
100
+ milestone['target'] || milestone['start']
101
+ else
102
+ milestone['target'] || ''
103
+ end
104
+ end
105
+
106
+ def render_table(data)
107
+ rows = (data['milestones'] || []).map do |m|
108
+ version = "**v#{m['version']}**"
109
+ target = target_label(m)
110
+ summary = m['summary'] || ''
111
+ status_emoji =
112
+ case m['status']
113
+ when 'completed' then 'โœ… Completed'
114
+ when 'active' then '๐Ÿšง In Progress'
115
+ when 'milestone' then '๐ŸŽฏ Milestone'
116
+ else '๐Ÿ—“ Planned'
117
+ end
118
+ "| #{version} | #{status_emoji} | #{target} | #{summary} |"
119
+ end
120
+
121
+ header = [
122
+ '| Version | Status | Target | Highlights |',
123
+ '|---------|--------|--------|------------|'
124
+ ]
125
+
126
+ (header + rows).join("\n")
127
+ end
128
+
129
+ def replace_block(content, marker_start, marker_end, replacement)
130
+ pattern = /(#{Regexp.escape(marker_start)})(.*?)(#{Regexp.escape(marker_end)})/m
131
+ unless content.match?(pattern)
132
+ raise "Markers not found in README: #{marker_start} ... #{marker_end}"
133
+ end
134
+
135
+ # Surround the replacement with blank lines so kramdown parses the following
136
+ # markdown (especially tables) instead of treating it as part of the HTML
137
+ # comment block. Without the blank line after `<!-- ... -->`, GFM tables
138
+ # collapse into a single paragraph.
139
+ content.sub(pattern, "\\1\n\n#{replacement}\n\n\\3")
140
+ end
141
+
142
+ # ---------------------------------------------------------------------------
143
+ # Main
144
+ # ---------------------------------------------------------------------------
145
+
146
+ def main
147
+ options = { mode: :write }
148
+ OptionParser.new do |opts|
149
+ opts.banner = 'Usage: generate-roadmap.rb [--check|--stdout]'
150
+ opts.on('--check', 'Exit non-zero if README would change') { options[:mode] = :check }
151
+ opts.on('--stdout', 'Print regenerated sections to stdout') { options[:mode] = :stdout }
152
+ end.parse!
153
+
154
+ # The roadmap data only contains scalars and Date values; Symbol is not used,
155
+ # but Date/Time must be permitted because YAML's safe loader rejects them by default.
156
+ # Ruby >= 3.1 supports `permitted_classes:` on `YAML.load_file`. On older Rubies
157
+ # (e.g. macOS system Ruby 2.6), fall back to `safe_load` which accepted the
158
+ # keyword earlier, so the generator works for contributors without rbenv/rvm.
159
+ data =
160
+ begin
161
+ YAML.load_file(DATA_FILE, permitted_classes: [Date, Time])
162
+ rescue ArgumentError
163
+ YAML.safe_load(File.read(DATA_FILE), permitted_classes: [Date, Time], aliases: false)
164
+ end
165
+ mermaid = render_mermaid(data)
166
+ table = render_table(data)
167
+
168
+ if options[:mode] == :stdout
169
+ puts mermaid
170
+ puts
171
+ puts table
172
+ return 0
173
+ end
174
+
175
+ original = File.read(README)
176
+ updated = original.dup
177
+ updated = replace_block(updated, MERMAID_START, MERMAID_END, mermaid)
178
+ updated = replace_block(updated, TABLE_START, TABLE_END, table)
179
+
180
+ if options[:mode] == :check
181
+ if original == updated
182
+ puts 'โœ“ README.md roadmap section is up to date with _data/roadmap.yml'
183
+ return 0
184
+ else
185
+ warn 'โœ— README.md roadmap section is out of date with _data/roadmap.yml'
186
+ warn ' Run: ./scripts/generate-roadmap.sh'
187
+ return 1
188
+ end
189
+ end
190
+
191
+ if original == updated
192
+ puts 'README.md roadmap section already up to date.'
193
+ else
194
+ File.write(README, updated)
195
+ puts "Updated README.md roadmap section from #{File.basename(DATA_FILE)}."
196
+ end
197
+ 0
198
+ end
199
+
200
+ exit main if $PROGRAM_NAME == __FILE__
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env bash
2
+ # =============================================================================
3
+ # generate-roadmap.sh
4
+ # =============================================================================
5
+ #
6
+ # Thin wrapper around scripts/generate-roadmap.rb.
7
+ #
8
+ # Reads `_data/roadmap.yml` and rewrites the auto-generated roadmap regions
9
+ # of README.md (Mermaid gantt diagram + summary table) in place.
10
+ #
11
+ # Usage:
12
+ # ./scripts/generate-roadmap.sh # update README.md
13
+ # ./scripts/generate-roadmap.sh --check # CI-friendly drift detection
14
+ # ./scripts/generate-roadmap.sh --stdout # print regenerated sections only
15
+ #
16
+ # =============================================================================
17
+
18
+ set -euo pipefail
19
+
20
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
21
+ exec ruby "${SCRIPT_DIR}/generate-roadmap.rb" "$@"
@@ -0,0 +1,63 @@
1
+ # `scripts/lib/install/` โ€” Installer Library Modules
2
+
3
+ Focused modules sourced by [`install.sh`](../../../install.sh) at the repository root. Each module is self-contained and โ‰ค 200 lines for readability and reuse.
4
+
5
+ ## Modules
6
+
7
+ | File | Purpose | Key Functions |
8
+ |------|---------|----------------|
9
+ | [`logging.sh`](logging.sh) | `log_info` / `log_success` / `log_warning` / `log_error` shim used throughout `install.sh`. | `log_info`, `log_success`, `log_warning`, `log_error` |
10
+ | [`platform.sh`](platform.sh) | OS, Ruby version, and platform detection (bash 3.2-compatible). | `detect_os`, `detect_ruby_version`, `ruby_version_lt_27`, `needs_macos_gemfile`, `detect_platform` |
11
+ | [`fs.sh`](fs.sh) | Idempotent file/directory copy with timestamped backups. | `copy_file_with_backup`, `copy_directory_with_backup` |
12
+ | [`template.sh`](template.sh) | `{{VAR}}` placeholder substitution + local/remote/fallback resolution. | `render_template`, `create_from_template`, `templates_available` |
13
+ | [`config.sh`](config.sh) | Loads `templates/config/install.conf` with hard-coded defaults as fallback. | `load_install_config` |
14
+ | [`pages.sh`](pages.sh) | Manifest-driven starter-page renderer. Replaces 8 legacy `create_*_page` heredoc functions with one driver. | `render_starter_pages`, `render_admin_settings_pages` (+ `create_starter_pages`/`create_admin_pages` aliases) |
15
+ | [`profile.sh`](profile.sh) | Pure-bash YAML reader for [`templates/profiles/*.yml`](../../../templates/profiles/). Bash 3.2 compatible (no yq/python). | `list_profile_names`, `profile_path`, `profile_get_scalar`, `profile_get_list`, `profile_print_summary` |
16
+ | [`deploy/`](deploy/) | Pluggable deployment-target modules (`github-pages`, `azure-swa`, `docker-prod`) with a uniform `check_prereqs`/`install`/`verify`/`doc_url` contract. | `deploy_run_target`, `deploy_print_summary`, `deploy_render`, `deploy_copy` (see [`deploy/README.md`](deploy/README.md)) |
17
+
18
+ ## Loading
19
+
20
+ `install.sh` sources these modules when `scripts/lib/install/` is present. When it isn't (a stripped distribution or a `curl | bash` one-liner that didn't bundle the libraries), `install.sh` falls back to inlined copies of the same functions defined at the top of the script.
21
+
22
+ ## CLI Entrypoint
23
+
24
+ For day-to-day use prefer the canonical dispatcher:
25
+
26
+ ```bash
27
+ ./scripts/bin/install help # subcommand index
28
+ ./scripts/bin/install init # full install in CWD
29
+ ./scripts/bin/install init --profile minimal /tmp/demo
30
+ ./scripts/bin/install list-profiles # available profiles
31
+ ./scripts/bin/install list-targets # available deploy targets
32
+ ./scripts/bin/install deploy github-pages /tmp/demo
33
+ ./scripts/bin/install deploy azure-swa,docker-prod /tmp/demo
34
+ ./scripts/bin/install agents # AGENTS.md / instructions index
35
+ ./scripts/bin/install version # theme version from version.rb
36
+ ```
37
+
38
+ `init` translates `--profile` into the appropriate legacy flag and execs `install.sh`. `deploy` dispatches to the modules under `deploy/` (Phase 4). The remaining subcommands (`wizard`, `diagnose`, `doctor`, `upgrade`) are stubs that print a clear notice until their backing modules land in Phases 5-6.
39
+
40
+ ## Roadmap
41
+
42
+ Phases 1, 1.5, 2, 3 (declarative profiles), and 4 (deploy modules) are complete. Future phases will add:
43
+
44
+ - `bootstrap.sh` โ€” remote install (`download_theme_files`, `cleanup_temp_dir`)
45
+ - `wizard.sh` โ€” interactive prompts (`gather_user_input`, `prompt_with_default`)
46
+ - `ai/{wizard,diagnose,suggest}.sh` โ€” opt-in AI integration
47
+ - `doctor.sh` โ€” pre-flight environment + site health checks
48
+
49
+ See the session refactor plan for the full sequence.
50
+
51
+ ## Compatibility
52
+
53
+ All modules target **bash 3.2** (the macOS default `/bin/bash`). No `declare -A`, no `=~` capture groups, no `mapfile`/`readarray`.
54
+
55
+ ## Conventions
56
+
57
+ - Every function documents its required globals at the top of the file
58
+ - No module calls `exit` โ€” caller decides; modules return non-zero on recoverable failure
59
+ - Modules don't `set -euo pipefail` themselves โ€” they inherit from the caller (`install.sh` already sets it)
60
+
61
+ ---
62
+
63
+ **Last updated**: 2026-04-20 โ€” Phase 4 (`deploy/registry.sh` + `deploy/{github-pages,azure-swa,docker-prod}.sh` + `templates/deploy/`).
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env bash
2
+ # scripts/lib/install/agents.sh
3
+ #
4
+ # `install agents` โ€” copy AI agent guidance files into a target site.
5
+ #
6
+ # Pure file copy. No network. No AI calls. Source of truth is the theme
7
+ # repo's own .github/ + AGENTS.md, so the theme is self-canonical:
8
+ # whatever the theme dogfoods is what consumers receive.
9
+ #
10
+ # Public API:
11
+ # agents_install <target_dir> <repo_root> [--cursor] [--claude] [--aider] [--force]
12
+ #
13
+ # Always installs the core set (AGENTS.md + .github/copilot-instructions.md
14
+ # + .github/instructions/ + .github/prompts/). Optional flags add:
15
+ # --cursor .cursor/commands/*.md (mirrors prompts as slash commands)
16
+ # --claude CLAUDE.md stub pointing to AGENTS.md
17
+ # --aider .aider.conf.yml referencing AGENTS.md as read-only context
18
+ #
19
+ # Idempotent: skips files that already exist unless --force.
20
+
21
+ # shellcheck disable=SC2034 # script intended to be sourced
22
+ AGENTS_LIB_VERSION="1.0.0"
23
+
24
+ # Copy a single file with skip/force semantics. Returns 0 on copy, 1 on skip.
25
+ _agents_copy_file() {
26
+ local src="$1" dst="$2" force="$3"
27
+ if [[ ! -f "$src" ]]; then
28
+ log_warning "Source not found, skipping: ${src#$REPO_ROOT/}"
29
+ return 1
30
+ fi
31
+ if [[ -f "$dst" ]] && [[ "$force" != "1" ]]; then
32
+ log_warning "Exists, skipping: ${dst#$PWD/} (use --force to overwrite)"
33
+ return 1
34
+ fi
35
+ mkdir -p "$(dirname "$dst")"
36
+ cp "$src" "$dst"
37
+ log_success "Wrote ${dst#$PWD/}"
38
+ return 0
39
+ }
40
+
41
+ # Copy every file in a source dir matching a glob into a dest dir.
42
+ _agents_copy_glob() {
43
+ local src_dir="$1" pattern="$2" dst_dir="$3" force="$4"
44
+ local copied=0 skipped=0
45
+ if [[ ! -d "$src_dir" ]]; then
46
+ log_warning "Source dir not found: ${src_dir#$REPO_ROOT/}"
47
+ return 0
48
+ fi
49
+ local f base
50
+ for f in "$src_dir"/$pattern; do
51
+ [[ -f "$f" ]] || continue
52
+ base="$(basename "$f")"
53
+ if _agents_copy_file "$f" "$dst_dir/$base" "$force"; then
54
+ copied=$((copied+1))
55
+ else
56
+ skipped=$((skipped+1))
57
+ fi
58
+ done
59
+ log_info " โ†’ $copied copied, $skipped skipped in ${dst_dir#$PWD/}"
60
+ }
61
+
62
+ agents_install() {
63
+ local target_dir="$1" repo_root="$2"
64
+ shift 2 || true
65
+
66
+ local with_cursor=0 with_claude=0 with_aider=0 force=0
67
+ while [[ $# -gt 0 ]]; do
68
+ case "$1" in
69
+ --cursor) with_cursor=1 ;;
70
+ --claude) with_claude=1 ;;
71
+ --aider) with_aider=1 ;;
72
+ --all) with_cursor=1; with_claude=1; with_aider=1 ;;
73
+ -f|--force) force=1 ;;
74
+ *) log_warning "agents_install: ignoring unknown flag: $1" ;;
75
+ esac
76
+ shift
77
+ done
78
+
79
+ if [[ ! -d "$target_dir" ]]; then
80
+ log_error "Target directory does not exist: $target_dir"
81
+ return 1
82
+ fi
83
+
84
+ log_info "Installing AI agent guidance into: $target_dir"
85
+ [[ "$force" = "1" ]] && log_info " (--force: overwrite enabled)"
86
+
87
+ # 1. Core: AGENTS.md (cross-tool entry point)
88
+ _agents_copy_file "$repo_root/AGENTS.md" "$target_dir/AGENTS.md" "$force" || true
89
+
90
+ # 2. Copilot main instructions
91
+ _agents_copy_file \
92
+ "$repo_root/.github/copilot-instructions.md" \
93
+ "$target_dir/.github/copilot-instructions.md" \
94
+ "$force" || true
95
+
96
+ # 3. File-scoped instructions
97
+ log_info "Copying .github/instructions/*.md ..."
98
+ _agents_copy_glob \
99
+ "$repo_root/.github/instructions" "*.md" \
100
+ "$target_dir/.github/instructions" "$force"
101
+
102
+ # 4. Reusable prompts
103
+ log_info "Copying .github/prompts/*.md ..."
104
+ _agents_copy_glob \
105
+ "$repo_root/.github/prompts" "*.md" \
106
+ "$target_dir/.github/prompts" "$force"
107
+
108
+ # 5. Optional: Cursor slash-commands
109
+ if [[ "$with_cursor" = "1" ]]; then
110
+ log_info "Copying .cursor/commands/*.md ..."
111
+ _agents_copy_glob \
112
+ "$repo_root/.cursor/commands" "*.md" \
113
+ "$target_dir/.cursor/commands" "$force"
114
+ fi
115
+
116
+ # 6. Optional: Claude stub
117
+ if [[ "$with_claude" = "1" ]]; then
118
+ local claude_tpl="$repo_root/templates/agents/CLAUDE.md.template"
119
+ if [[ -f "$claude_tpl" ]]; then
120
+ _agents_copy_file "$claude_tpl" "$target_dir/CLAUDE.md" "$force" || true
121
+ else
122
+ cat > "$target_dir/CLAUDE.md.tmp" <<'EOF'
123
+ # Claude Code Instructions
124
+
125
+ This project uses [`AGENTS.md`](./AGENTS.md) as the single source of truth for
126
+ AI agent guidance. Please read it first.
127
+
128
+ For detailed conventions, see:
129
+ - `.github/copilot-instructions.md`
130
+ - `.github/instructions/*.instructions.md`
131
+ - `.github/prompts/*.prompt.md`
132
+ EOF
133
+ if [[ -f "$target_dir/CLAUDE.md" ]] && [[ "$force" != "1" ]]; then
134
+ rm -f "$target_dir/CLAUDE.md.tmp"
135
+ log_warning "Exists, skipping: CLAUDE.md (use --force to overwrite)"
136
+ else
137
+ mv "$target_dir/CLAUDE.md.tmp" "$target_dir/CLAUDE.md"
138
+ log_success "Wrote CLAUDE.md"
139
+ fi
140
+ fi
141
+ fi
142
+
143
+ # 7. Optional: Aider config
144
+ if [[ "$with_aider" = "1" ]]; then
145
+ local aider_tpl="$repo_root/templates/agents/aider.conf.yml.template"
146
+ if [[ -f "$aider_tpl" ]]; then
147
+ _agents_copy_file "$aider_tpl" "$target_dir/.aider.conf.yml" "$force" || true
148
+ else
149
+ if [[ -f "$target_dir/.aider.conf.yml" ]] && [[ "$force" != "1" ]]; then
150
+ log_warning "Exists, skipping: .aider.conf.yml (use --force to overwrite)"
151
+ else
152
+ cat > "$target_dir/.aider.conf.yml" <<'EOF'
153
+ # Aider configuration โ€” see https://aider.chat
154
+ # Loads project agent guidance as read-only context for every session.
155
+ read:
156
+ - AGENTS.md
157
+ - .github/copilot-instructions.md
158
+ EOF
159
+ log_success "Wrote .aider.conf.yml"
160
+ fi
161
+ fi
162
+ fi
163
+
164
+ log_success "Agent guidance installation complete."
165
+ return 0
166
+ }