diff --git a/scripts/codex-git-hooks/pre-commit b/scripts/codex-git-hooks/pre-commit new file mode 100644 index 000000000..98c495fef --- /dev/null +++ b/scripts/codex-git-hooks/pre-commit @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ECC Codex Git Hook: pre-commit +# Blocks commits that add high-signal secrets. + +if [[ "${ECC_SKIP_GIT_HOOKS:-0}" == "1" || "${ECC_SKIP_PRECOMMIT:-0}" == "1" ]]; then + exit 0 +fi + +if [[ -f ".ecc-hooks-disable" || -f ".git/ecc-hooks-disable" ]]; then + exit 0 +fi + +if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + exit 0 +fi + +staged_files="$(git diff --cached --name-only --diff-filter=ACMR || true)" +if [[ -z "$staged_files" ]]; then + exit 0 +fi + +has_findings=0 + +scan_added_lines() { + local file="$1" + local name="$2" + local regex="$3" + local added_lines + local hits + + added_lines="$(git diff --cached -U0 -- "$file" | awk '/^\+\+\+ /{next} /^\+/{print substr($0,2)}')" + if [[ -z "$added_lines" ]]; then + return 0 + fi + + if hits="$(printf '%s\n' "$added_lines" | rg -n --pcre2 "$regex" 2>/dev/null)"; then + printf '\n[ECC pre-commit] Potential secret detected (%s) in %s\n' "$name" "$file" >&2 + printf '%s\n' "$hits" | head -n 3 >&2 + has_findings=1 + fi +} + +while IFS= read -r file; do + [[ -z "$file" ]] && continue + + case "$file" in + *.png|*.jpg|*.jpeg|*.gif|*.svg|*.pdf|*.zip|*.gz|*.lock|pnpm-lock.yaml|package-lock.json|yarn.lock|bun.lockb) + continue + ;; + esac + + scan_added_lines "$file" "OpenAI key" 'sk-[A-Za-z0-9]{20,}' + scan_added_lines "$file" "GitHub classic token" 'ghp_[A-Za-z0-9]{36}' + scan_added_lines "$file" "GitHub fine-grained token" 'github_pat_[A-Za-z0-9_]{20,}' + scan_added_lines "$file" "AWS access key" 'AKIA[0-9A-Z]{16}' + scan_added_lines "$file" "private key block" '-----BEGIN (RSA|EC|OPENSSH|DSA|PRIVATE) KEY-----' + scan_added_lines "$file" "generic credential assignment" "(?i)\\b(api[_-]?key|secret|password|token)\\b\\s*[:=]\\s*['\\\"][^'\\\"]{12,}['\\\"]" +done <<< "$staged_files" + +if [[ "$has_findings" -eq 1 ]]; then + cat >&2 <<'EOF' + +[ECC pre-commit] Commit blocked to prevent secret leakage. +Fix: +1) Remove secrets from staged changes. +2) Move secrets to env vars or secret manager. +3) Re-stage and commit again. + +Temporary bypass (not recommended): + ECC_SKIP_PRECOMMIT=1 git commit ... +EOF + exit 1 +fi + +exit 0 diff --git a/scripts/codex-git-hooks/pre-push b/scripts/codex-git-hooks/pre-push new file mode 100644 index 000000000..2182cbf4a --- /dev/null +++ b/scripts/codex-git-hooks/pre-push @@ -0,0 +1,110 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ECC Codex Git Hook: pre-push +# Runs a lightweight verification flow before pushes. + +if [[ "${ECC_SKIP_GIT_HOOKS:-0}" == "1" || "${ECC_SKIP_PREPUSH:-0}" == "1" ]]; then + exit 0 +fi + +if [[ -f ".ecc-hooks-disable" || -f ".git/ecc-hooks-disable" ]]; then + exit 0 +fi + +if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + exit 0 +fi + +ran_any_check=0 + +log() { + printf '[ECC pre-push] %s\n' "$*" +} + +fail() { + printf '[ECC pre-push] FAILED: %s\n' "$*" >&2 + exit 1 +} + +detect_pm() { + if [[ -f "pnpm-lock.yaml" ]]; then + echo "pnpm" + elif [[ -f "bun.lockb" ]]; then + echo "bun" + elif [[ -f "yarn.lock" ]]; then + echo "yarn" + elif [[ -f "package-lock.json" ]]; then + echo "npm" + else + echo "npm" + fi +} + +has_node_script() { + local script_name="$1" + node -e 'const fs=require("fs"); const p=JSON.parse(fs.readFileSync("package.json","utf8")); process.exit(p.scripts && p.scripts[process.argv[1]] ? 0 : 1)' "$script_name" >/dev/null 2>&1 +} + +run_node_script() { + local pm="$1" + local script_name="$2" + case "$pm" in + pnpm) pnpm run "$script_name" ;; + bun) bun run "$script_name" ;; + yarn) yarn "$script_name" ;; + npm) npm run "$script_name" ;; + *) npm run "$script_name" ;; + esac +} + +if [[ -f "package.json" ]]; then + pm="$(detect_pm)" + log "Node project detected (package manager: $pm)" + + for script_name in lint typecheck test build; do + if has_node_script "$script_name"; then + ran_any_check=1 + log "Running: $script_name" + run_node_script "$pm" "$script_name" || fail "$script_name failed" + else + log "Skipping missing script: $script_name" + fi + done + + if [[ "${ECC_PREPUSH_AUDIT:-0}" == "1" ]]; then + ran_any_check=1 + log "Running dependency audit (ECC_PREPUSH_AUDIT=1)" + case "$pm" in + pnpm) pnpm audit --prod || fail "pnpm audit failed" ;; + bun) bun audit || fail "bun audit failed" ;; + yarn) yarn npm audit --recursive || fail "yarn audit failed" ;; + npm) npm audit --omit=dev || fail "npm audit failed" ;; + *) npm audit --omit=dev || fail "npm audit failed" ;; + esac + fi +fi + +if [[ -f "go.mod" ]] && command -v go >/dev/null 2>&1; then + ran_any_check=1 + log "Go project detected. Running: go test ./..." + go test ./... || fail "go test failed" +fi + +if [[ -f "pyproject.toml" || -f "requirements.txt" ]]; then + if command -v pytest >/dev/null 2>&1; then + ran_any_check=1 + log "Python project detected. Running: pytest -q" + pytest -q || fail "pytest failed" + else + log "Python project detected but pytest is not installed. Skipping." + fi +fi + +if [[ "$ran_any_check" -eq 0 ]]; then + log "No supported checks found in this repository. Skipping." +else + log "Verification checks passed." +fi + +exit 0 diff --git a/scripts/codex/check-codex-global-state.sh b/scripts/codex/check-codex-global-state.sh new file mode 100644 index 000000000..f0c2242c9 --- /dev/null +++ b/scripts/codex/check-codex-global-state.sh @@ -0,0 +1,221 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ECC Codex global regression sanity check. +# Validates that global ~/.codex state matches expected ECC integration. + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +CODEX_HOME="${CODEX_HOME:-$HOME/.codex}" + +CONFIG_FILE="$CODEX_HOME/config.toml" +AGENTS_FILE="$CODEX_HOME/AGENTS.md" +PROMPTS_DIR="$CODEX_HOME/prompts" +SKILLS_DIR="$CODEX_HOME/skills" +HOOKS_DIR_EXPECT="${ECC_GLOBAL_HOOKS_DIR:-$CODEX_HOME/git-hooks}" + +failures=0 +warnings=0 +checks=0 + +ok() { + checks=$((checks + 1)) + printf '[OK] %s\n' "$*" +} + +warn() { + checks=$((checks + 1)) + warnings=$((warnings + 1)) + printf '[WARN] %s\n' "$*" +} + +fail() { + checks=$((checks + 1)) + failures=$((failures + 1)) + printf '[FAIL] %s\n' "$*" +} + +require_file() { + local file="$1" + local label="$2" + if [[ -f "$file" ]]; then + ok "$label exists ($file)" + else + fail "$label missing ($file)" + fi +} + +check_config_pattern() { + local pattern="$1" + local label="$2" + if rg -n "$pattern" "$CONFIG_FILE" >/dev/null 2>&1; then + ok "$label" + else + fail "$label" + fi +} + +check_config_absent() { + local pattern="$1" + local label="$2" + if rg -n "$pattern" "$CONFIG_FILE" >/dev/null 2>&1; then + fail "$label" + else + ok "$label" + fi +} + +printf 'ECC GLOBAL SANITY CHECK\n' +printf 'Repo: %s\n' "$REPO_ROOT" +printf 'Codex home: %s\n\n' "$CODEX_HOME" + +require_file "$CONFIG_FILE" "Global config.toml" +require_file "$AGENTS_FILE" "Global AGENTS.md" + +if [[ -f "$AGENTS_FILE" ]]; then + if rg -n '^# Everything Claude Code \(ECC\) — Agent Instructions' "$AGENTS_FILE" >/dev/null 2>&1; then + ok "AGENTS contains ECC root instructions" + else + fail "AGENTS missing ECC root instructions" + fi + + if rg -n '^# Codex Supplement \(From ECC \.codex/AGENTS\.md\)' "$AGENTS_FILE" >/dev/null 2>&1; then + ok "AGENTS contains ECC Codex supplement" + else + fail "AGENTS missing ECC Codex supplement" + fi +fi + +if [[ -f "$CONFIG_FILE" ]]; then + check_config_pattern '^multi_agent\s*=\s*true' "multi_agent is enabled" + check_config_absent '^\s*collab\s*=' "deprecated collab flag is absent" + check_config_pattern '^persistent_instructions\s*=' "persistent_instructions is configured" + check_config_pattern '^\[profiles\.strict\]' "profiles.strict exists" + check_config_pattern '^\[profiles\.yolo\]' "profiles.yolo exists" + + for section in \ + 'mcp_servers.github' \ + 'mcp_servers.memory' \ + 'mcp_servers.sequential-thinking' \ + 'mcp_servers.context7-mcp' + do + if rg -n "^\[$section\]" "$CONFIG_FILE" >/dev/null 2>&1; then + ok "MCP section [$section] exists" + else + fail "MCP section [$section] missing" + fi + done + + if rg -n '^\[mcp_servers\.context7\]' "$CONFIG_FILE" >/dev/null 2>&1; then + warn "Duplicate [mcp_servers.context7] exists (context7-mcp is preferred)" + else + ok "No duplicate [mcp_servers.context7] section" + fi +fi + +declare -a required_skills=( + api-design + article-writing + backend-patterns + coding-standards + content-engine + e2e-testing + eval-harness + frontend-patterns + frontend-slides + investor-materials + investor-outreach + market-research + security-review + strategic-compact + tdd-workflow + verification-loop +) + +if [[ -d "$SKILLS_DIR" ]]; then + missing_skills=0 + for skill in "${required_skills[@]}"; do + if [[ -d "$SKILLS_DIR/$skill" ]]; then + : + else + printf ' - missing skill: %s\n' "$skill" + missing_skills=$((missing_skills + 1)) + fi + done + + if [[ "$missing_skills" -eq 0 ]]; then + ok "All 16 ECC Codex skills are present" + else + fail "$missing_skills required skills are missing" + fi +else + fail "Skills directory missing ($SKILLS_DIR)" +fi + +if [[ -f "$PROMPTS_DIR/ecc-prompts-manifest.txt" ]]; then + ok "Command prompts manifest exists" +else + fail "Command prompts manifest missing" +fi + +if [[ -f "$PROMPTS_DIR/ecc-extension-prompts-manifest.txt" ]]; then + ok "Extension prompts manifest exists" +else + fail "Extension prompts manifest missing" +fi + +command_prompts_count="$(find "$PROMPTS_DIR" -maxdepth 1 -type f -name 'ecc-*.md' 2>/dev/null | wc -l | tr -d ' ')" +if [[ "$command_prompts_count" -ge 43 ]]; then + ok "ECC prompts count is $command_prompts_count (expected >= 43)" +else + fail "ECC prompts count is $command_prompts_count (expected >= 43)" +fi + +hooks_path="$(git config --global --get core.hooksPath || true)" +if [[ -n "$hooks_path" ]]; then + if [[ "$hooks_path" == "$HOOKS_DIR_EXPECT" ]]; then + ok "Global hooksPath is set to $HOOKS_DIR_EXPECT" + else + warn "Global hooksPath is $hooks_path (expected $HOOKS_DIR_EXPECT)" + fi +else + fail "Global hooksPath is not configured" +fi + +if [[ -x "$HOOKS_DIR_EXPECT/pre-commit" ]]; then + ok "Global pre-commit hook is installed and executable" +else + fail "Global pre-commit hook missing or not executable" +fi + +if [[ -x "$HOOKS_DIR_EXPECT/pre-push" ]]; then + ok "Global pre-push hook is installed and executable" +else + fail "Global pre-push hook missing or not executable" +fi + +if command -v ecc-sync-codex >/dev/null 2>&1; then + ok "ecc-sync-codex command is in PATH" +else + warn "ecc-sync-codex is not in PATH" +fi + +if command -v ecc-install-git-hooks >/dev/null 2>&1; then + ok "ecc-install-git-hooks command is in PATH" +else + warn "ecc-install-git-hooks is not in PATH" +fi + +if command -v ecc-check-codex >/dev/null 2>&1; then + ok "ecc-check-codex command is in PATH" +else + warn "ecc-check-codex is not in PATH (this is expected before alias setup)" +fi + +printf '\nSummary: checks=%d, warnings=%d, failures=%d\n' "$checks" "$warnings" "$failures" +if [[ "$failures" -eq 0 ]]; then + printf 'ECC GLOBAL SANITY: PASS\n' +else + printf 'ECC GLOBAL SANITY: FAIL\n' + exit 1 +fi diff --git a/scripts/codex/install-global-git-hooks.sh b/scripts/codex/install-global-git-hooks.sh new file mode 100644 index 000000000..9919d523e --- /dev/null +++ b/scripts/codex/install-global-git-hooks.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Install ECC git safety hooks globally via core.hooksPath. +# Usage: +# ./scripts/codex/install-global-git-hooks.sh +# ./scripts/codex/install-global-git-hooks.sh --dry-run + +MODE="apply" +if [[ "${1:-}" == "--dry-run" ]]; then + MODE="dry-run" +fi + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +SOURCE_DIR="$REPO_ROOT/scripts/codex-git-hooks" +DEST_DIR="${ECC_GLOBAL_HOOKS_DIR:-$HOME/.codex/git-hooks}" +STAMP="$(date +%Y%m%d-%H%M%S)" +BACKUP_DIR="$HOME/.codex/backups/git-hooks-$STAMP" + +log() { + printf '[ecc-hooks] %s\n' "$*" +} + +run_or_echo() { + if [[ "$MODE" == "dry-run" ]]; then + printf '[dry-run] %s\n' "$*" + else + eval "$*" + fi +} + +if [[ ! -d "$SOURCE_DIR" ]]; then + log "Missing source hooks directory: $SOURCE_DIR" + exit 1 +fi + +log "Mode: $MODE" +log "Source hooks: $SOURCE_DIR" +log "Global hooks destination: $DEST_DIR" + +if [[ -d "$DEST_DIR" ]]; then + log "Backing up existing hooks directory to $BACKUP_DIR" + run_or_echo "mkdir -p \"$BACKUP_DIR\"" + run_or_echo "cp -R \"$DEST_DIR\" \"$BACKUP_DIR/hooks\"" +fi + +run_or_echo "mkdir -p \"$DEST_DIR\"" +run_or_echo "cp \"$SOURCE_DIR/pre-commit\" \"$DEST_DIR/pre-commit\"" +run_or_echo "cp \"$SOURCE_DIR/pre-push\" \"$DEST_DIR/pre-push\"" +run_or_echo "chmod +x \"$DEST_DIR/pre-commit\" \"$DEST_DIR/pre-push\"" + +if [[ "$MODE" == "apply" ]]; then + prev_hooks_path="$(git config --global core.hooksPath || true)" + if [[ -n "$prev_hooks_path" ]]; then + log "Previous global hooksPath: $prev_hooks_path" + fi +fi +run_or_echo "git config --global core.hooksPath \"$DEST_DIR\"" + +log "Installed ECC global git hooks." +log "Disable per repo by creating .ecc-hooks-disable in project root." +log "Temporary bypass: ECC_SKIP_PRECOMMIT=1 or ECC_SKIP_PREPUSH=1" diff --git a/scripts/sync-ecc-to-codex.sh b/scripts/sync-ecc-to-codex.sh new file mode 100644 index 000000000..77c419804 --- /dev/null +++ b/scripts/sync-ecc-to-codex.sh @@ -0,0 +1,466 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Sync Everything Claude Code (ECC) assets into a local Codex CLI setup. +# - Backs up ~/.codex config and AGENTS.md +# - Replaces AGENTS.md with ECC AGENTS.md +# - Syncs Codex-ready skills from .agents/skills +# - Generates prompt files from commands/*.md +# - Generates Codex QA wrappers and optional language rule-pack prompts +# - Installs global git safety hooks (pre-commit and pre-push) +# - Runs a post-sync global regression sanity check +# - Normalizes MCP server entries to pnpm dlx and removes duplicate Context7 block + +MODE="apply" +if [[ "${1:-}" == "--dry-run" ]]; then + MODE="dry-run" +fi + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +CODEX_HOME="${CODEX_HOME:-$HOME/.codex}" + +CONFIG_FILE="$CODEX_HOME/config.toml" +AGENTS_FILE="$CODEX_HOME/AGENTS.md" +AGENTS_ROOT_SRC="$REPO_ROOT/AGENTS.md" +AGENTS_CODEX_SUPP_SRC="$REPO_ROOT/.codex/AGENTS.md" +SKILLS_SRC="$REPO_ROOT/.agents/skills" +SKILLS_DEST="$CODEX_HOME/skills" +PROMPTS_SRC="$REPO_ROOT/commands" +PROMPTS_DEST="$CODEX_HOME/prompts" +HOOKS_INSTALLER="$REPO_ROOT/scripts/codex/install-global-git-hooks.sh" +SANITY_CHECKER="$REPO_ROOT/scripts/codex/check-codex-global-state.sh" +CURSOR_RULES_DIR="$REPO_ROOT/.cursor/rules" + +STAMP="$(date +%Y%m%d-%H%M%S)" +BACKUP_DIR="$CODEX_HOME/backups/ecc-$STAMP" + +log() { printf '[ecc-sync] %s\n' "$*"; } + +run_or_echo() { + if [[ "$MODE" == "dry-run" ]]; then + printf '[dry-run] %s\n' "$*" + else + eval "$@" + fi +} + +require_path() { + local p="$1" + local label="$2" + if [[ ! -e "$p" ]]; then + log "Missing $label: $p" + exit 1 + fi +} + +toml_escape() { + local v="$1" + v="${v//\\/\\\\}" + v="${v//\"/\\\"}" + printf '%s' "$v" +} + +remove_section_inplace() { + local file="$1" + local section="$2" + local tmp + tmp="$(mktemp)" + awk -v section="$section" ' + BEGIN { skip = 0 } + { + if ($0 == "[" section "]") { + skip = 1 + next + } + if (skip && $0 ~ /^\[/) { + skip = 0 + } + if (!skip) { + print + } + } + ' "$file" > "$tmp" + mv "$tmp" "$file" +} + +extract_toml_value() { + local file="$1" + local section="$2" + local key="$3" + awk -v section="$section" -v key="$key" ' + $0 == "[" section "]" { in_section = 1; next } + in_section && /^\[/ { in_section = 0 } + in_section && $1 == key { + line = $0 + sub(/^[^=]*=[[:space:]]*"/, "", line) + sub(/".*$/, "", line) + print line + exit + } + ' "$file" +} + +extract_context7_key() { + local file="$1" + grep -oP -- '--key",[[:space:]]*"\K[^"]+' "$file" | head -n 1 || true +} + +generate_prompt_file() { + local src="$1" + local out="$2" + local cmd_name="$3" + { + printf '# ECC Command Prompt: /%s\n\n' "$cmd_name" + printf 'Source: %s\n\n' "$src" + printf 'Use this prompt to run the ECC `%s` workflow.\n\n' "$cmd_name" + awk ' + NR == 1 && $0 == "---" { fm = 1; next } + fm == 1 && $0 == "---" { fm = 0; next } + fm == 1 { next } + { print } + ' "$src" + } > "$out" +} + +require_path "$REPO_ROOT/AGENTS.md" "ECC AGENTS.md" +require_path "$AGENTS_CODEX_SUPP_SRC" "ECC Codex AGENTS supplement" +require_path "$SKILLS_SRC" "ECC skills directory" +require_path "$PROMPTS_SRC" "ECC commands directory" +require_path "$HOOKS_INSTALLER" "ECC global git hooks installer" +require_path "$SANITY_CHECKER" "ECC global sanity checker" +require_path "$CURSOR_RULES_DIR" "ECC Cursor rules directory" +require_path "$CONFIG_FILE" "Codex config.toml" + +log "Mode: $MODE" +log "Repo root: $REPO_ROOT" +log "Codex home: $CODEX_HOME" + +log "Creating backup folder: $BACKUP_DIR" +run_or_echo "mkdir -p \"$BACKUP_DIR\"" +run_or_echo "cp \"$CONFIG_FILE\" \"$BACKUP_DIR/config.toml\"" +if [[ -f "$AGENTS_FILE" ]]; then + run_or_echo "cp \"$AGENTS_FILE\" \"$BACKUP_DIR/AGENTS.md\"" +fi + +log "Replacing global AGENTS.md with ECC AGENTS + Codex supplement" +if [[ "$MODE" == "dry-run" ]]; then + printf '[dry-run] compose %s from %s + %s\n' "$AGENTS_FILE" "$AGENTS_ROOT_SRC" "$AGENTS_CODEX_SUPP_SRC" +else + { + cat "$AGENTS_ROOT_SRC" + printf '\n\n---\n\n' + printf '# Codex Supplement (From ECC .codex/AGENTS.md)\n\n' + cat "$AGENTS_CODEX_SUPP_SRC" + } > "$AGENTS_FILE" +fi + +log "Syncing ECC Codex skills" +run_or_echo "mkdir -p \"$SKILLS_DEST\"" +skills_count=0 +for skill_dir in "$SKILLS_SRC"/*; do + [[ -d "$skill_dir" ]] || continue + skill_name="$(basename "$skill_dir")" + dest="$SKILLS_DEST/$skill_name" + run_or_echo "rm -rf \"$dest\"" + run_or_echo "cp -R \"$skill_dir\" \"$dest\"" + skills_count=$((skills_count + 1)) +done + +log "Generating prompt files from ECC commands" +run_or_echo "mkdir -p \"$PROMPTS_DEST\"" +manifest="$PROMPTS_DEST/ecc-prompts-manifest.txt" +if [[ "$MODE" == "dry-run" ]]; then + printf '[dry-run] > %s\n' "$manifest" +else + : > "$manifest" +fi + +prompt_count=0 +while IFS= read -r -d '' command_file; do + name="$(basename "$command_file" .md)" + out="$PROMPTS_DEST/ecc-$name.md" + if [[ "$MODE" == "dry-run" ]]; then + printf '[dry-run] generate %s from %s\n' "$out" "$command_file" + else + generate_prompt_file "$command_file" "$out" "$name" + printf 'ecc-%s.md\n' "$name" >> "$manifest" + fi + prompt_count=$((prompt_count + 1)) +done < <(find "$PROMPTS_SRC" -maxdepth 1 -type f -name '*.md' -print0 | sort -z) + +if [[ "$MODE" == "apply" ]]; then + sort -u "$manifest" -o "$manifest" +fi + +log "Generating Codex tool prompts + optional rule-pack prompts" +extension_manifest="$PROMPTS_DEST/ecc-extension-prompts-manifest.txt" +if [[ "$MODE" == "dry-run" ]]; then + printf '[dry-run] > %s\n' "$extension_manifest" +else + : > "$extension_manifest" +fi + +extension_count=0 + +write_extension_prompt() { + local name="$1" + local file="$PROMPTS_DEST/$name" + if [[ "$MODE" == "dry-run" ]]; then + printf '[dry-run] generate %s\n' "$file" + else + cat > "$file" + printf '%s\n' "$name" >> "$extension_manifest" + fi + extension_count=$((extension_count + 1)) +} + +write_extension_prompt "ecc-tool-run-tests.md" < +Summary: +Top failures: +- ... +Suggested next step: +- ... +\`\`\` +EOF + +write_extension_prompt "ecc-tool-check-coverage.md" <% +Total lines: % +Total branches: % (if available) +Worst files: +- path: xx% +Recommended focus: +- ... +\`\`\` +EOF + +write_extension_prompt "ecc-tool-security-audit.md" < +Secrets findings: +Code risk findings: +Critical issues: +- ... +Remediation plan: +1. ... +2. ... +\`\`\` +EOF + +write_extension_prompt "ecc-rules-pack-common.md" <> "$CONFIG_FILE" +else + log "Skipping MCP config normalization in dry-run mode" +fi + +log "Installing global git safety hooks" +if [[ "$MODE" == "dry-run" ]]; then + "$HOOKS_INSTALLER" --dry-run +else + "$HOOKS_INSTALLER" +fi + +log "Running global regression sanity check" +if [[ "$MODE" == "dry-run" ]]; then + printf '[dry-run] %s\n' "$SANITY_CHECKER" +else + "$SANITY_CHECKER" +fi + +log "Sync complete" +log "Backup saved at: $BACKUP_DIR" +log "Skills synced: $skills_count" +log "Prompts generated: $((prompt_count + extension_count)) (commands: $prompt_count, extensions: $extension_count)" + +if [[ "$MODE" == "apply" ]]; then + log "Done. Restart Codex CLI to reload AGENTS, prompts, and MCP servers." +fi