ai-superpower/apply.sh
moilanik 102f593909 feat: configurable docs folders + mandatory instructions rules
config.yaml: new repo-root config file with docs_folders list
(docs, documentation, doc). apply.sh reads this list and picks the
first existing folder per project instead of hardcoding docs/.

Instructions:
- core-principles: add No Vibe Coding and No Touching .ai/ sections
- ai-root-instructions: add mandatory instructions block — rules stay
  active for the whole session, not just at start; AI must stop and
  announce if instructions were not loaded
- project-context, docs: updated to list all docs folder alternatives
  and reference config.yaml as the source of truth

FR-5.0 added to apply-requirements.md. README step 4 updated.
2026-03-06 09:42:48 +02:00

182 lines
7.4 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
set -euo pipefail
REPO_URL="https://gitea.nikos-dev.keskikuja.site/niko/ai-superpower.git"
REPO_NAME="ai-superpower"
RAW_URL="https://gitea.nikos-dev.keskikuja.site/niko/ai-superpower/raw/branch/main/apply.sh"
# ── Bootstrap mode (curl | bash) ─────────────────────────────────────────────
# BASH_SOURCE[0] is empty when piped through bash
if [[ -z "${BASH_SOURCE[0]:-}" ]]; then
DEV_ROOT="$PWD" # capture before exec replaces the process
TARGET="$DEV_ROOT/$REPO_NAME"
EXTRA_FLAGS="${1:-}" # pass through --update if provided
if [[ -d "$TARGET" ]]; then
PREV_COMMIT="$(git -C "$TARGET" rev-parse --short HEAD 2>/dev/null || echo "")"
echo "\u2192 updating $REPO_NAME ..."
git -C "$TARGET" pull || { echo "\u2717 git pull failed"; exit 1; }
else
PREV_COMMIT=""
echo "\u2192 cloning $REPO_NAME into $TARGET ..."
git clone "$REPO_URL" "$TARGET" || { echo "\u2717 git clone failed"; exit 1; }
fi
# Pass DEV_ROOT and PREV_COMMIT explicitly
exec bash "$TARGET/apply.sh" --bootstrapped "$DEV_ROOT" ${EXTRA_FLAGS:+"$EXTRA_FLAGS"} "$PREV_COMMIT"
fi
# ── Guard — block direct invocation ──────────────────────────────────────────
if [[ "${1:-}" != "--bootstrapped" ]]; then
echo "✗ do not run this script directly."
echo " run from your dev root folder:"
echo ""
echo " curl -fsSL $RAW_URL | bash # full setup"
echo " curl -fsSL $RAW_URL | bash -s -- --update # update instructions only"
echo ""
exit 1
fi
# ── Local mode ────────────────────────────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DEV_ROOT="${2:?DEV_ROOT not passed — re-run via curl}"
AI_TARGET="$SCRIPT_DIR/.ai"
TEMPLATE_MARKER="<!-- ai-superpower:template"
# Read docs folder alternatives from config.yaml
DOCS_FOLDERS=()
while IFS= read -r folder; do
DOCS_FOLDERS+=("$folder")
done < <(grep '^\s*-\s' "$SCRIPT_DIR/config.yaml" 2>/dev/null | sed 's/^\s*-\s*//')
[[ ${#DOCS_FOLDERS[@]} -eq 0 ]] && DOCS_FOLDERS=("docs")
# ── --update mode: instructions only, skip project setup ────────────────────
# git pull already ran in bootstrap; compare commits and report changes
if [[ "${3:-}" == "--update" ]]; then
COMMIT="$(git -C "$SCRIPT_DIR" rev-parse --short HEAD 2>/dev/null || echo "unknown")"
PREV_COMMIT="${4:-}"
echo ""
if [[ -z "$PREV_COMMIT" || "$PREV_COMMIT" == "$COMMIT" ]]; then
echo " version: $COMMIT (already up to date)"
else
echo " version: $PREV_COMMIT$COMMIT"
echo " new commits:"
git -C "$SCRIPT_DIR" log --oneline "${PREV_COMMIT}..HEAD" | sed 's/^/ /'
fi
echo ""
exit 0
fi
# Counters
cnt_found=0
cnt_no_docs=0
cnt_templated=0
cnt_refreshed=0
setup_project() {
local project="$1"
local name
name="$(basename "$project")"
echo ""
echo "$name"
# FR-3: symlink
local ai_link="$project/.ai"
if [[ -L "$ai_link" && "$(readlink "$ai_link")" == "$AI_TARGET" ]]; then
: # correct — skip silently
else
ln -sfn "$AI_TARGET" "$ai_link"
echo " ✓ .ai symlinked"
fi
# FR-4: .gitignore
local gitignore="$project/.gitignore"
if ! grep -qxF '.ai' "$gitignore" 2>/dev/null; then
echo '.ai' >> "$gitignore"
echo " ✓ .ai added to .gitignore"
fi
# FR-5 + FR-6: docs — find existing folder or use default
local docs=""
for folder in "${DOCS_FOLDERS[@]}"; do
if [[ -d "$project/$folder" ]]; then
docs="$project/$folder"
break
fi
done
[[ -z "$docs" ]] && docs="$project/${DOCS_FOLDERS[0]}"
if [[ ! -d "$docs" ]]; then
if [[ -t 0 ]] || [[ -e /dev/tty ]]; then
printf " ? no docs/ folder — create it? [y/N] "
read -r answer </dev/tty
if [[ "${answer,,}" == "y" ]]; then
mkdir -p "$docs"
echo " ✓ created docs/"
else
echo " skipped"
(( cnt_no_docs++ )) || true
return
fi
else
echo " ⚠ no docs/ folder — skipping context setup"
(( cnt_no_docs++ )) || true
return
fi
fi
local got_template=0
if [[ ! -f "$docs/ai-context.md" ]]; then
cp "$SCRIPT_DIR/templates/ai-context.md" "$docs/ai-context.md"
echo " ✓ created docs/ai-context.md"
got_template=1
elif head -1 "$docs/ai-context.md" | grep -qF "$TEMPLATE_MARKER"; then
cp "$SCRIPT_DIR/templates/ai-context.md" "$docs/ai-context.md"
echo " ✓ refreshed docs/ai-context.md (was factory template)"
(( cnt_refreshed++ )) || true
fi
if [[ ! -f "$docs/architecture.md" ]]; then
cp "$SCRIPT_DIR/templates/architecture.md" "$docs/architecture.md"
echo " ✓ created docs/architecture.md"
got_template=1
elif head -1 "$docs/architecture.md" | grep -qF "$TEMPLATE_MARKER"; then
cp "$SCRIPT_DIR/templates/architecture.md" "$docs/architecture.md"
echo " ✓ refreshed docs/architecture.md (was factory template)"
(( cnt_refreshed++ )) || true
fi
(( got_template )) && (( cnt_templated++ )) || true
}
# ── Scan and run ──────────────────────────────────────────────────────────────
echo "→ scanning $DEV_ROOT for projects ..."
while IFS= read -r gitdir; do
project="$(cd "$(dirname "$gitdir")" && pwd)"
[[ -f "$project/.ai-superpower" ]] && continue
setup_project "$project"
(( cnt_found++ )) || true
done < <(find "$DEV_ROOT" -mindepth 2 -maxdepth 4 -name ".git" -type d 2>/dev/null || true)
# ── Summary ───────────────────────────────────────────────────────────────────
COMMIT="$(git -C "$SCRIPT_DIR" rev-parse --short HEAD 2>/dev/null || echo "unknown")"
PREV_COMMIT="$(grep '^commit:' "$DEV_ROOT/.ai-superpower.version" 2>/dev/null | awk '{print $2}' || echo "")"
echo ""
echo "────────────────────────────────────────"
if [[ -n "$PREV_COMMIT" && "$PREV_COMMIT" != "$COMMIT" ]]; then
echo " version: $PREV_COMMIT$COMMIT"
elif [[ -n "$PREV_COMMIT" ]]; then
echo " version: $COMMIT (no change)"
else
echo " version: $COMMIT (first run)"
fi
echo " projects: $cnt_found found"
(( cnt_no_docs > 0 )) && echo " no docs/: $cnt_no_docs project(s) skipped (no docs/ folder)" || true
(( cnt_templated > 0 )) && echo " templated: $cnt_templated project(s) received new docs templates" || true
(( cnt_refreshed > 0 )) && echo " refreshed: $cnt_refreshed template file(s) updated (were still factory default)" || true
echo "────────────────────────────────────────"
(( cnt_found > 0 )) && echo "✅ done" || echo "⚠ no projects found — are you in the right directory?"
# FR-8: write version file
printf '# Written by apply.sh on every run — shows when it was last run and which version of ai-superpower was used.\ndate: %s\ncommit: %s\n' \
"$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$COMMIT" > "$DEV_ROOT/.ai-superpower.version"