jekyll-theme-zer0 0.22.21 โ 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +55 -0
- data/README.md +294 -23
- data/scripts/bin/install +717 -0
- data/scripts/bin/test +45 -2
- data/scripts/lib/install/README.md +63 -0
- data/scripts/lib/install/agents.sh +166 -0
- data/scripts/lib/install/ai/diagnose.sh +199 -0
- data/scripts/lib/install/ai/openai.sh +233 -0
- data/scripts/lib/install/ai/suggest.sh +182 -0
- data/scripts/lib/install/ai/wizard.sh +160 -0
- data/scripts/lib/install/config.sh +56 -0
- data/scripts/lib/install/deploy/README.md +52 -0
- data/scripts/lib/install/deploy/azure-swa.sh +50 -0
- data/scripts/lib/install/deploy/docker-prod.sh +71 -0
- data/scripts/lib/install/deploy/github-pages.sh +44 -0
- data/scripts/lib/install/deploy/registry.sh +190 -0
- data/scripts/lib/install/doctor.sh +301 -0
- data/scripts/lib/install/fs.sh +52 -0
- data/scripts/lib/install/logging.sh +33 -0
- data/scripts/lib/install/pages.sh +255 -0
- data/scripts/lib/install/platform.sh +71 -0
- data/scripts/lib/install/profile.sh +113 -0
- data/scripts/lib/install/template.sh +137 -0
- data/scripts/lib/install/upgrade.sh +184 -0
- data/scripts/lib/install/wizard_interactive.sh +189 -0
- metadata +24 -2
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
|
-
|
|
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,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
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# scripts/lib/install/ai/diagnose.sh
|
|
3
|
+
#
|
|
4
|
+
# `install diagnose [--ai]` โ Jekyll build / runtime error analysis.
|
|
5
|
+
#
|
|
6
|
+
# Two modes:
|
|
7
|
+
#
|
|
8
|
+
# 1. Rule-based (default, no network). Pattern-matches a curated list of
|
|
9
|
+
# known errors against a build log and prints structured fixes.
|
|
10
|
+
#
|
|
11
|
+
# 2. AI-assisted (`--ai`). Sends a sanitized error log + the most relevant
|
|
12
|
+
# config files to OpenAI, returns a unified diff for user review.
|
|
13
|
+
#
|
|
14
|
+
# Public API:
|
|
15
|
+
# diagnose_run <target_dir> <repo_root> [--log <file>] [--ai] [--auto-accept]
|
|
16
|
+
#
|
|
17
|
+
# If --log is not provided, runs `jekyll build` once and captures output.
|
|
18
|
+
|
|
19
|
+
# shellcheck disable=SC2034
|
|
20
|
+
AI_DIAGNOSE_LIB_VERSION="1.0.0"
|
|
21
|
+
|
|
22
|
+
# ----- Rule-based pattern table --------------------------------------------
|
|
23
|
+
# Each rule: [pattern_regex] [short_label] [explanation+fix]
|
|
24
|
+
# Order matters โ first match wins. Keep patterns specific.
|
|
25
|
+
_diagnose_rules() {
|
|
26
|
+
cat <<'EOF'
|
|
27
|
+
theme could not be found|MISSING_THEME|The Jekyll theme gem is not installed. Run `bundle install` (or `bundle update jekyll-theme-zer0`) and confirm the gem name in _config.yml matches your Gemfile entry.
|
|
28
|
+
Address already in use|PORT_IN_USE|Port 4000 is already bound. Either stop the existing process (`lsof -ti :4000 | xargs kill`) or run on a different port (`bundle exec jekyll serve --port 4001`).
|
|
29
|
+
You have requested:.*Could not find compatible versions|GEM_VERSION_CONFLICT|A gem version constraint cannot be satisfied. Run `bundle update` to refresh the lockfile, or pin to compatible versions in your Gemfile.
|
|
30
|
+
Liquid Exception:.*Could not locate the included file|MISSING_INCLUDE|A `{% include %}` tag references a file that doesn't exist. Verify the path inside `_includes/` and check for typos.
|
|
31
|
+
Liquid Exception|LIQUID_ERROR|A Liquid template raised an error. Check the file path printed above the exception and look for unmatched tags ({% if %} without {% endif %}, etc.).
|
|
32
|
+
SassC::SyntaxError|SASS_SYNTAX|Sass compilation failed. Review the file/line printed in the error and check for missing semicolons, unclosed braces, or invalid @import paths.
|
|
33
|
+
No such file or directory @ rb_sysopen|MISSING_FILE|Jekyll tried to open a file that doesn't exist. Common causes: deleted but still referenced in _config.yml or front matter; typo in include path.
|
|
34
|
+
incompatible character encodings|ENCODING_ISSUE|A file has mixed encodings. Re-save the offending file as UTF-8 without BOM.
|
|
35
|
+
EOF
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
_diagnose_rule_based() {
|
|
39
|
+
local log_file="$1"
|
|
40
|
+
local matched=0 line key label fix
|
|
41
|
+
while IFS='|' read -r pattern label fix; do
|
|
42
|
+
[[ -z "$pattern" ]] && continue
|
|
43
|
+
if grep -qE "$pattern" "$log_file" 2>/dev/null; then
|
|
44
|
+
log_info "Matched rule: $label"
|
|
45
|
+
echo " โณ $fix"
|
|
46
|
+
echo
|
|
47
|
+
matched=$((matched+1))
|
|
48
|
+
fi
|
|
49
|
+
done < <(_diagnose_rules)
|
|
50
|
+
|
|
51
|
+
if [[ "$matched" = "0" ]]; then
|
|
52
|
+
log_warning "No known patterns matched. Re-run with --ai for AI analysis (requires OPENAI_API_KEY)."
|
|
53
|
+
return 1
|
|
54
|
+
fi
|
|
55
|
+
log_success "Matched $matched rule(s)."
|
|
56
|
+
return 0
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
_diagnose_capture_build_log() {
|
|
60
|
+
local target_dir="$1" log_file="$2"
|
|
61
|
+
log_info "Running 'jekyll build' to capture errors ..."
|
|
62
|
+
(
|
|
63
|
+
cd "$target_dir" || exit 1
|
|
64
|
+
if [[ -f Gemfile ]] && command -v bundle >/dev/null 2>&1; then
|
|
65
|
+
bundle exec jekyll build 2>&1 | tee "$log_file"
|
|
66
|
+
elif command -v jekyll >/dev/null 2>&1; then
|
|
67
|
+
jekyll build 2>&1 | tee "$log_file"
|
|
68
|
+
else
|
|
69
|
+
echo "neither bundler nor jekyll is installed" > "$log_file"
|
|
70
|
+
return 1
|
|
71
|
+
fi
|
|
72
|
+
) || true
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
_diagnose_ai() {
|
|
76
|
+
local log_file="$1" target_dir="$2" repo_root="$3" auto_accept="$4"
|
|
77
|
+
|
|
78
|
+
if ! ai_enabled; then
|
|
79
|
+
log_warning "AI is disabled (ZER0_NO_AI=1) โ using rule-based mode only."
|
|
80
|
+
return 1
|
|
81
|
+
fi
|
|
82
|
+
if ! ai_require_key; then
|
|
83
|
+
return 1
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
local sys_prompt_file="$repo_root/templates/ai/prompts/diagnose-system.md"
|
|
87
|
+
if [[ ! -f "$sys_prompt_file" ]]; then
|
|
88
|
+
log_error "System prompt missing: $sys_prompt_file"
|
|
89
|
+
return 1
|
|
90
|
+
fi
|
|
91
|
+
local system_prompt
|
|
92
|
+
system_prompt="$(cat "$sys_prompt_file")"
|
|
93
|
+
|
|
94
|
+
# Build sanitized context (last 80 lines of log + _config.yml + Gemfile)
|
|
95
|
+
local sanitized_log sanitized_cfg sanitized_gem
|
|
96
|
+
sanitized_log="$(tail -n 80 "$log_file" | ai_sanitize_text)"
|
|
97
|
+
if [[ -f "$target_dir/_config.yml" ]]; then
|
|
98
|
+
sanitized_cfg="$(ai_sanitize_text < "$target_dir/_config.yml")"
|
|
99
|
+
else
|
|
100
|
+
sanitized_cfg="(missing)"
|
|
101
|
+
fi
|
|
102
|
+
if [[ -f "$target_dir/Gemfile" ]]; then
|
|
103
|
+
sanitized_gem="$(ai_sanitize_text < "$target_dir/Gemfile")"
|
|
104
|
+
else
|
|
105
|
+
sanitized_gem="(missing)"
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
local user_prompt="===== BUILD LOG (last 80 lines) =====
|
|
109
|
+
${sanitized_log}
|
|
110
|
+
|
|
111
|
+
===== _config.yml =====
|
|
112
|
+
${sanitized_cfg}
|
|
113
|
+
|
|
114
|
+
===== Gemfile =====
|
|
115
|
+
${sanitized_gem}
|
|
116
|
+
|
|
117
|
+
Diagnose the failure and propose a minimal fix. If a file change is needed, output a unified diff. Be concise."
|
|
118
|
+
|
|
119
|
+
local model
|
|
120
|
+
model="$(ai_default_model diagnose)"
|
|
121
|
+
local in_chars=$(( ${#system_prompt} + ${#user_prompt} ))
|
|
122
|
+
log_info "About to call OpenAI:"
|
|
123
|
+
ai_estimate_cost "$model" "$in_chars" 600
|
|
124
|
+
|
|
125
|
+
if [[ "$auto_accept" != "1" ]]; then
|
|
126
|
+
printf "Proceed with API call? [y/N] "
|
|
127
|
+
local go
|
|
128
|
+
read -r go
|
|
129
|
+
if [[ ! "$go" =~ ^[Yy]$ ]]; then
|
|
130
|
+
log_warning "Aborted by user."
|
|
131
|
+
return 1
|
|
132
|
+
fi
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
log_info "Calling $model ..."
|
|
136
|
+
local resp
|
|
137
|
+
if ! resp="$(ai_call_chat "$model" "$system_prompt" "$user_prompt" 800 0.2)"; then
|
|
138
|
+
log_error "OpenAI call failed."
|
|
139
|
+
return 1
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
echo
|
|
143
|
+
log_info "AI diagnosis:"
|
|
144
|
+
echo "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
|
|
145
|
+
printf '%s\n' "$resp"
|
|
146
|
+
echo "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
|
|
147
|
+
log_info "If the response includes a unified diff, save it to a file and apply with: patch -p0 < fix.diff"
|
|
148
|
+
return 0
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
diagnose_run() {
|
|
152
|
+
local target_dir="$1" repo_root="$2"
|
|
153
|
+
shift 2 || true
|
|
154
|
+
|
|
155
|
+
local log_file="" use_ai=0 auto_accept=0
|
|
156
|
+
while [[ $# -gt 0 ]]; do
|
|
157
|
+
case "$1" in
|
|
158
|
+
--log) log_file="${2:-}"; shift ;;
|
|
159
|
+
--ai) use_ai=1 ;;
|
|
160
|
+
--auto-accept) auto_accept=1 ;;
|
|
161
|
+
*) log_warning "diagnose_run: ignoring unknown flag: $1" ;;
|
|
162
|
+
esac
|
|
163
|
+
shift
|
|
164
|
+
done
|
|
165
|
+
|
|
166
|
+
if [[ ! -d "$target_dir" ]]; then
|
|
167
|
+
log_error "Target directory does not exist: $target_dir"
|
|
168
|
+
return 1
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
local cleanup_log=0
|
|
172
|
+
if [[ -z "$log_file" ]]; then
|
|
173
|
+
log_file="$(mktemp)"
|
|
174
|
+
cleanup_log=1
|
|
175
|
+
_diagnose_capture_build_log "$target_dir" "$log_file"
|
|
176
|
+
fi
|
|
177
|
+
|
|
178
|
+
if [[ ! -f "$log_file" ]]; then
|
|
179
|
+
log_error "Log file not found: $log_file"
|
|
180
|
+
[[ "$cleanup_log" = "1" ]] && rm -f "$log_file"
|
|
181
|
+
return 1
|
|
182
|
+
fi
|
|
183
|
+
|
|
184
|
+
log_info "Diagnosing build log ($(wc -l < "$log_file" | tr -d ' ') lines) ..."
|
|
185
|
+
echo
|
|
186
|
+
|
|
187
|
+
# Always run rule-based first
|
|
188
|
+
local rule_result=0
|
|
189
|
+
_diagnose_rule_based "$log_file" || rule_result=1
|
|
190
|
+
|
|
191
|
+
# AI if requested
|
|
192
|
+
if [[ "$use_ai" = "1" ]]; then
|
|
193
|
+
echo
|
|
194
|
+
_diagnose_ai "$log_file" "$target_dir" "$repo_root" "$auto_accept" || true
|
|
195
|
+
fi
|
|
196
|
+
|
|
197
|
+
[[ "$cleanup_log" = "1" ]] && rm -f "$log_file"
|
|
198
|
+
return $rule_result
|
|
199
|
+
}
|