mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-06-12 19:23:07 +08:00
* fix(hooks): fail open on oversized stdin instead of echoing truncated JSON (#2222) run-with-flags.js capped stdin at 1MB but every fallthrough path still echoed the truncated string to stdout. The harness parses hook stdout as JSON, got a document cut mid-stream, and blocked the tool call — so any Edit/Write with a >1MB hook payload was permanently blocked by every registered pre-write hook, before ECC_HOOK_PROFILE / ECC_DISABLED_HOOKS gating could run. - Exit 0 with empty stdout (no opinion) when the stdin cap trips, before any echo or gating logic. - Flush stdout via write callback before process.exit: exiting right after stdout.write() dropped everything past the ~64KB pipe buffer, cutting even sub-cap pass-through payloads mid-JSON. Regression tests cover the enabled, disabled, and missing-arg paths for oversized payloads plus full echo of sub-cap >64KB payloads. * fix(codex): stop emitting invalid exa url entry, align merge with connector policy (#2224) The Codex MCP merge declared exa with a url key, but Codex's [mcp_servers.*] TOML schema is stdio-only — the url key makes the entire config.toml fail to load, bricking both the codex CLI and the desktop app. Every install/update re-injected the line because the urlEntry branch treated the broken entry as present. - ECC_SERVERS now emits only the current default set per docs/MCP-CONNECTOR-POLICY.md: chrome-devtools (stdio, command/args). Retired servers (supabase, playwright, context7, exa, github, memory, sequential-thinking) are never re-emitted; existing user-managed entries are untouched. - The merge now repairs the exact ECC-emitted broken form (url-only exa entry) on every run so re-running the installer fixes broken configs instead of preserving them. User stdio exa entries (command + mcp-remote) are left alone. - check-codex-global-state.sh requires chrome-devtools instead of the retired set, and flags url-only exa entries with a repair hint. Tests cover repair, re-run idempotence, stdio-entry preservation, and no-retired-server emission in add, update, dry-run, and disabled modes. * fix(hooks): never echo truncated stdin from Stop hooks (#2090) Stop hooks follow the ECC pass-through convention (echo stdin on stdout), but every echoing Stop hook capped stdin and echoed the capped string. The Stop payload carries last_assistant_message, so a long final assistant message produced a JSON document cut mid-stream on stdout, which the harness reports as 'Stop hook error: JSON validation failed' across the whole Stop chain. Reproduced: a Stop payload with a >64KB last_assistant_message run through run-with-flags + cost-tracker emitted exactly 65536 bytes of invalid JSON (cost-tracker capped stdin at 64KB — far below realistic Stop payloads). - cost-tracker: raise the cap to 1MB (matching all other hooks) and suppress the pass-through echo when stdin was truncated. - check-console-log, stop-format-typecheck, desktop-notify: suppress the echo when stdin was truncated; flush stdout before process.exit so sub-cap payloads are not cut at the ~64KB pipe buffer. - All hooks keep exiting 0 (fail-open); diagnostics go to stderr. New stop-hooks-stdout test asserts the contract for every registered Stop hook: stdout is empty or valid JSON, exit code 0 — for realistic 100KB payloads and oversized >1MB payloads, via the production runner and via direct invocation. Updated the old hooks.test.js case that codified the truncated-echo behavior. * fix(hooks): dampen GateGuard fact-force repetition in long sessions (#2142) In long autonomous sessions the fact-force gate produced 10+ near-identical 'state facts -> blocked -> restate -> retry' blocks in one context window, which measurably raises the odds of the model collapsing into a degenerate single-token repetition loop. - Track a per-session fact_force_denials counter in GateGuard state (merged max across concurrent writers, reset with the session, robust to malformed on-disk values). - The first GATEGUARD_FACT_FORCE_FULL_DENIALS denials (default 3) keep the full four-fact block; later denials emit a condensed single-line message that carries the denial ordinal, so consecutive denials are structurally different and never textually identical. - True retries of the same target remain allowed without re-prompting (unchanged). Destructive-Bash and routine-Bash gates are unchanged, as are the ECC_GATEGUARD=off / ECC_DISABLED_HOOKS escape hatches. Eight new tests cover budget counting, condensed format, ordinal advancement, retry pass-through, env tuning, malformed state, MultiEdit dampening, and destructive-gate exemption. * fix(hooks): keep security hooks able to block on oversized stdin (#2222) Refine the truncation fail-open: instead of skipping the hook entirely, the runner now suppresses only its own raw-echo when stdin was truncated. The hook still executes and receives the truncated flag (run() context / ECC_HOOK_INPUT_TRUNCATED), so config-protection keeps blocking truncated protected-config payloads (its test requires exit 2) while pass-through hooks fail open with empty stdout as before. * style: apply repo formatter to touched hook files
243 lines
6.8 KiB
Bash
Executable File
243 lines
6.8 KiB
Bash
Executable File
#!/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}"
|
|
|
|
# Use rg if available, otherwise fall back to grep -E.
|
|
# All patterns in this script must be POSIX ERE compatible.
|
|
if command -v rg >/dev/null 2>&1; then
|
|
search_file() { rg -n "$1" "$2" >/dev/null 2>&1; }
|
|
else
|
|
search_file() { grep -En "$1" "$2" >/dev/null 2>&1; }
|
|
fi
|
|
|
|
CONFIG_FILE="$CODEX_HOME/config.toml"
|
|
AGENTS_FILE="$CODEX_HOME/AGENTS.md"
|
|
PROMPTS_DIR="$CODEX_HOME/prompts"
|
|
SKILLS_DIR="${AGENTS_HOME:-$HOME/.agents}/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 search_file "$pattern" "$CONFIG_FILE"; then
|
|
ok "$label"
|
|
else
|
|
fail "$label"
|
|
fi
|
|
}
|
|
|
|
check_config_absent() {
|
|
local pattern="$1"
|
|
local label="$2"
|
|
if search_file "$pattern" "$CONFIG_FILE"; 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 search_file '^# Everything Claude Code \(ECC\)' "$AGENTS_FILE"; then
|
|
ok "AGENTS contains ECC root instructions"
|
|
else
|
|
fail "AGENTS missing ECC root instructions"
|
|
fi
|
|
|
|
if search_file '^# Codex Supplement \(From ECC \.codex/AGENTS\.md\)' "$AGENTS_FILE"; 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[[:space:]]*=[[:space:]]*true' "multi_agent is enabled"
|
|
check_config_absent '^[[:space:]]*collab[[:space:]]*=' "deprecated collab flag is absent"
|
|
# persistent_instructions is recommended but optional; warn instead of fail
|
|
# so users who rely on AGENTS.md alone are not blocked (#967).
|
|
if search_file '^[[:space:]]*persistent_instructions[[:space:]]*=' "$CONFIG_FILE"; then
|
|
ok "persistent_instructions is configured"
|
|
else
|
|
warn "persistent_instructions is not set (recommended but optional)"
|
|
fi
|
|
check_config_pattern '^\[profiles\.strict\]' "profiles.strict exists"
|
|
check_config_pattern '^\[profiles\.yolo\]' "profiles.yolo exists"
|
|
|
|
# Current default connector set (docs/MCP-CONNECTOR-POLICY.md): exactly
|
|
# one connector. Former defaults (github, memory, sequential-thinking,
|
|
# context7, exa, ...) are opt-in user choices, so they are not required.
|
|
for section in \
|
|
'mcp_servers.chrome-devtools'
|
|
do
|
|
if search_file "^\[$section\]" "$CONFIG_FILE"; then
|
|
ok "MCP section [$section] exists"
|
|
else
|
|
fail "MCP section [$section] missing"
|
|
fi
|
|
done
|
|
|
|
# ECC <= 2.0.0 emitted a url-only exa entry that Codex's stdio-only
|
|
# schema rejects, breaking the whole config (#2224). Flag it so users
|
|
# re-run the sync (which repairs it) or remove it manually.
|
|
if search_file '^\[mcp_servers\.exa\]' "$CONFIG_FILE"; then
|
|
exa_block="$(awk '/^\[mcp_servers\.exa\]/{flag=1;next}/^\[/{flag=0}flag' "$CONFIG_FILE")"
|
|
if printf '%s\n' "$exa_block" | grep -Eq '^[[:space:]]*url[[:space:]]*=' \
|
|
&& ! printf '%s\n' "$exa_block" | grep -Eq '^[[:space:]]*command[[:space:]]*='; then
|
|
fail "MCP section [mcp_servers.exa] uses a url key, which Codex rejects for stdio servers — re-run ecc-sync-codex to repair (#2224)"
|
|
else
|
|
ok "MCP section [mcp_servers.exa] uses the stdio form"
|
|
fi
|
|
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 skills are present in $SKILLS_DIR"
|
|
else
|
|
warn "$missing_skills ECC skills missing from $SKILLS_DIR (install via ECC installer or npx skills)"
|
|
fi
|
|
else
|
|
warn "Skills directory missing ($SKILLS_DIR) — install via ECC installer or npx skills"
|
|
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
|