mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-03-30 21:53:28 +08:00
feat: add Codex CLI customization scripts (#336)
* chore(codex): add global ecc sync script and pnpm mcp config * chore(codex): include codex supplement when syncing agents * feat(codex): add global git safety hooks and QA/rule prompt packs * feat(codex): add global regression sanity check command --------- Co-authored-by: TGreen87 <your-email@example.com>
This commit is contained in:
77
scripts/codex-git-hooks/pre-commit
Normal file
77
scripts/codex-git-hooks/pre-commit
Normal file
@@ -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
|
||||
110
scripts/codex-git-hooks/pre-push
Normal file
110
scripts/codex-git-hooks/pre-push
Normal file
@@ -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
|
||||
221
scripts/codex/check-codex-global-state.sh
Normal file
221
scripts/codex/check-codex-global-state.sh
Normal file
@@ -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
|
||||
63
scripts/codex/install-global-git-hooks.sh
Normal file
63
scripts/codex/install-global-git-hooks.sh
Normal file
@@ -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"
|
||||
466
scripts/sync-ecc-to-codex.sh
Normal file
466
scripts/sync-ecc-to-codex.sh
Normal file
@@ -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" <<EOF
|
||||
# ECC Tool Prompt: run-tests
|
||||
|
||||
Run the repository test suite with package-manager autodetection and concise reporting.
|
||||
|
||||
## Instructions
|
||||
1. Detect package manager from lock files in this order: \`pnpm-lock.yaml\`, \`bun.lockb\`, \`yarn.lock\`, \`package-lock.json\`.
|
||||
2. Detect available scripts or test commands for this repo.
|
||||
3. Execute tests with the best project-native command.
|
||||
4. If tests fail, report failing files/tests first, then the smallest likely fix list.
|
||||
5. Do not change code unless explicitly asked.
|
||||
|
||||
## Output Format
|
||||
\`\`\`
|
||||
RUN TESTS: [PASS/FAIL]
|
||||
Command used: <command>
|
||||
Summary: <x passed / y failed>
|
||||
Top failures:
|
||||
- ...
|
||||
Suggested next step:
|
||||
- ...
|
||||
\`\`\`
|
||||
EOF
|
||||
|
||||
write_extension_prompt "ecc-tool-check-coverage.md" <<EOF
|
||||
# ECC Tool Prompt: check-coverage
|
||||
|
||||
Analyze coverage and compare it to an 80% threshold (or a threshold I specify).
|
||||
|
||||
## Instructions
|
||||
1. Find existing coverage artifacts first (\`coverage/coverage-summary.json\`, \`coverage/coverage-final.json\`, \`.nyc_output/coverage.json\`).
|
||||
2. If missing, run the project's coverage command using the detected package manager.
|
||||
3. Report total coverage and top under-covered files.
|
||||
4. Fail the report if coverage is below threshold.
|
||||
|
||||
## Output Format
|
||||
\`\`\`
|
||||
COVERAGE: [PASS/FAIL]
|
||||
Threshold: <n>%
|
||||
Total lines: <n>%
|
||||
Total branches: <n>% (if available)
|
||||
Worst files:
|
||||
- path: xx%
|
||||
Recommended focus:
|
||||
- ...
|
||||
\`\`\`
|
||||
EOF
|
||||
|
||||
write_extension_prompt "ecc-tool-security-audit.md" <<EOF
|
||||
# ECC Tool Prompt: security-audit
|
||||
|
||||
Run a practical security audit: dependency vulnerabilities + secret scan + high-risk code patterns.
|
||||
|
||||
## Instructions
|
||||
1. Run dependency audit command for this repo/package manager.
|
||||
2. Scan source and staged changes for high-signal secrets (OpenAI keys, GitHub tokens, AWS keys, private keys).
|
||||
3. Scan for risky patterns (\`eval(\`, \`dangerouslySetInnerHTML\`, unsanitized \`innerHTML\`, obvious SQL string interpolation).
|
||||
4. Prioritize findings by severity: CRITICAL, HIGH, MEDIUM, LOW.
|
||||
5. Do not auto-fix unless I explicitly ask.
|
||||
|
||||
## Output Format
|
||||
\`\`\`
|
||||
SECURITY AUDIT: [PASS/FAIL]
|
||||
Dependency vulnerabilities: <summary>
|
||||
Secrets findings: <count>
|
||||
Code risk findings: <count>
|
||||
Critical issues:
|
||||
- ...
|
||||
Remediation plan:
|
||||
1. ...
|
||||
2. ...
|
||||
\`\`\`
|
||||
EOF
|
||||
|
||||
write_extension_prompt "ecc-rules-pack-common.md" <<EOF
|
||||
# ECC Rule Pack: common (optional)
|
||||
|
||||
Apply ECC common engineering rules for this session. Use these files as the source of truth:
|
||||
|
||||
- \`$CURSOR_RULES_DIR/common-agents.md\`
|
||||
- \`$CURSOR_RULES_DIR/common-coding-style.md\`
|
||||
- \`$CURSOR_RULES_DIR/common-development-workflow.md\`
|
||||
- \`$CURSOR_RULES_DIR/common-git-workflow.md\`
|
||||
- \`$CURSOR_RULES_DIR/common-hooks.md\`
|
||||
- \`$CURSOR_RULES_DIR/common-patterns.md\`
|
||||
- \`$CURSOR_RULES_DIR/common-performance.md\`
|
||||
- \`$CURSOR_RULES_DIR/common-security.md\`
|
||||
- \`$CURSOR_RULES_DIR/common-testing.md\`
|
||||
|
||||
Treat these as strict defaults for planning, implementation, review, and verification in this repo.
|
||||
EOF
|
||||
|
||||
write_extension_prompt "ecc-rules-pack-typescript.md" <<EOF
|
||||
# ECC Rule Pack: typescript (optional)
|
||||
|
||||
Apply ECC common rules plus TypeScript-specific rules for this session.
|
||||
|
||||
## Common
|
||||
Use \`$PROMPTS_DEST/ecc-rules-pack-common.md\`.
|
||||
|
||||
## TypeScript Extensions
|
||||
- \`$CURSOR_RULES_DIR/typescript-coding-style.md\`
|
||||
- \`$CURSOR_RULES_DIR/typescript-hooks.md\`
|
||||
- \`$CURSOR_RULES_DIR/typescript-patterns.md\`
|
||||
- \`$CURSOR_RULES_DIR/typescript-security.md\`
|
||||
- \`$CURSOR_RULES_DIR/typescript-testing.md\`
|
||||
|
||||
Language-specific guidance overrides common rules when they conflict.
|
||||
EOF
|
||||
|
||||
write_extension_prompt "ecc-rules-pack-python.md" <<EOF
|
||||
# ECC Rule Pack: python (optional)
|
||||
|
||||
Apply ECC common rules plus Python-specific rules for this session.
|
||||
|
||||
## Common
|
||||
Use \`$PROMPTS_DEST/ecc-rules-pack-common.md\`.
|
||||
|
||||
## Python Extensions
|
||||
- \`$CURSOR_RULES_DIR/python-coding-style.md\`
|
||||
- \`$CURSOR_RULES_DIR/python-hooks.md\`
|
||||
- \`$CURSOR_RULES_DIR/python-patterns.md\`
|
||||
- \`$CURSOR_RULES_DIR/python-security.md\`
|
||||
- \`$CURSOR_RULES_DIR/python-testing.md\`
|
||||
|
||||
Language-specific guidance overrides common rules when they conflict.
|
||||
EOF
|
||||
|
||||
write_extension_prompt "ecc-rules-pack-golang.md" <<EOF
|
||||
# ECC Rule Pack: golang (optional)
|
||||
|
||||
Apply ECC common rules plus Go-specific rules for this session.
|
||||
|
||||
## Common
|
||||
Use \`$PROMPTS_DEST/ecc-rules-pack-common.md\`.
|
||||
|
||||
## Go Extensions
|
||||
- \`$CURSOR_RULES_DIR/golang-coding-style.md\`
|
||||
- \`$CURSOR_RULES_DIR/golang-hooks.md\`
|
||||
- \`$CURSOR_RULES_DIR/golang-patterns.md\`
|
||||
- \`$CURSOR_RULES_DIR/golang-security.md\`
|
||||
- \`$CURSOR_RULES_DIR/golang-testing.md\`
|
||||
|
||||
Language-specific guidance overrides common rules when they conflict.
|
||||
EOF
|
||||
|
||||
write_extension_prompt "ecc-rules-pack-swift.md" <<EOF
|
||||
# ECC Rule Pack: swift (optional)
|
||||
|
||||
Apply ECC common rules plus Swift-specific rules for this session.
|
||||
|
||||
## Common
|
||||
Use \`$PROMPTS_DEST/ecc-rules-pack-common.md\`.
|
||||
|
||||
## Swift Extensions
|
||||
- \`$CURSOR_RULES_DIR/swift-coding-style.md\`
|
||||
- \`$CURSOR_RULES_DIR/swift-hooks.md\`
|
||||
- \`$CURSOR_RULES_DIR/swift-patterns.md\`
|
||||
- \`$CURSOR_RULES_DIR/swift-security.md\`
|
||||
- \`$CURSOR_RULES_DIR/swift-testing.md\`
|
||||
|
||||
Language-specific guidance overrides common rules when they conflict.
|
||||
EOF
|
||||
|
||||
if [[ "$MODE" == "apply" ]]; then
|
||||
sort -u "$extension_manifest" -o "$extension_manifest"
|
||||
fi
|
||||
|
||||
if [[ "$MODE" == "apply" ]]; then
|
||||
log "Normalizing MCP server config to pnpm"
|
||||
|
||||
supabase_token="$(extract_toml_value "$CONFIG_FILE" "mcp_servers.supabase.env" "SUPABASE_ACCESS_TOKEN")"
|
||||
context7_key="$(extract_context7_key "$CONFIG_FILE")"
|
||||
github_bootstrap='token=$(gh auth token 2>/dev/null || true); if [ -n "$token" ]; then export GITHUB_PERSONAL_ACCESS_TOKEN="$token"; fi; exec pnpm dlx @modelcontextprotocol/server-github'
|
||||
|
||||
remove_section_inplace "$CONFIG_FILE" "mcp_servers.github.env"
|
||||
remove_section_inplace "$CONFIG_FILE" "mcp_servers.github"
|
||||
remove_section_inplace "$CONFIG_FILE" "mcp_servers.memory"
|
||||
remove_section_inplace "$CONFIG_FILE" "mcp_servers.sequential-thinking"
|
||||
remove_section_inplace "$CONFIG_FILE" "mcp_servers.context7"
|
||||
remove_section_inplace "$CONFIG_FILE" "mcp_servers.context7-mcp"
|
||||
remove_section_inplace "$CONFIG_FILE" "mcp_servers.playwright"
|
||||
remove_section_inplace "$CONFIG_FILE" "mcp_servers.supabase.env"
|
||||
remove_section_inplace "$CONFIG_FILE" "mcp_servers.supabase"
|
||||
|
||||
{
|
||||
printf '\n[mcp_servers.supabase]\n'
|
||||
printf 'command = "pnpm"\n'
|
||||
printf 'args = ["dlx", "@supabase/mcp-server-supabase@latest", "--features=account,docs,database,debugging,development,functions,storage,branching"]\n'
|
||||
printf 'startup_timeout_sec = 20.0\n'
|
||||
printf 'tool_timeout_sec = 120.0\n'
|
||||
|
||||
if [[ -n "$supabase_token" ]]; then
|
||||
printf '\n[mcp_servers.supabase.env]\n'
|
||||
printf 'SUPABASE_ACCESS_TOKEN = "%s"\n' "$(toml_escape "$supabase_token")"
|
||||
fi
|
||||
|
||||
printf '\n[mcp_servers.playwright]\n'
|
||||
printf 'command = "pnpm"\n'
|
||||
printf 'args = ["dlx", "@playwright/mcp@latest"]\n'
|
||||
|
||||
if [[ -n "$context7_key" ]]; then
|
||||
printf '\n[mcp_servers.context7-mcp]\n'
|
||||
printf 'command = "pnpm"\n'
|
||||
printf 'args = ["dlx", "@smithery/cli@latest", "run", "@upstash/context7-mcp", "--key", "%s"]\n' "$(toml_escape "$context7_key")"
|
||||
else
|
||||
printf '\n[mcp_servers.context7-mcp]\n'
|
||||
printf 'command = "pnpm"\n'
|
||||
printf 'args = ["dlx", "@upstash/context7-mcp"]\n'
|
||||
fi
|
||||
|
||||
printf '\n[mcp_servers.github]\n'
|
||||
printf 'command = "bash"\n'
|
||||
printf 'args = ["-lc", "%s"]\n' "$(toml_escape "$github_bootstrap")"
|
||||
|
||||
printf '\n[mcp_servers.memory]\n'
|
||||
printf 'command = "pnpm"\n'
|
||||
printf 'args = ["dlx", "@modelcontextprotocol/server-memory"]\n'
|
||||
|
||||
printf '\n[mcp_servers.sequential-thinking]\n'
|
||||
printf 'command = "pnpm"\n'
|
||||
printf 'args = ["dlx", "@modelcontextprotocol/server-sequential-thinking"]\n'
|
||||
} >> "$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
|
||||
Reference in New Issue
Block a user