From efbd7732afcc98db8182cdd33c86ff07074d1778 Mon Sep 17 00:00:00 2001 From: jinon86 <247078695+jinon86@users.noreply.github.com> Date: Wed, 5 Aug 2026 11:05:53 +0000 Subject: [PATCH] feat(memory): add ccc-piri node-global Piri memory launcher Node-global counterpart of ccc-codex: materializes the shared ccc-node snapshot into the Piri global context file (/AGENTS.md, via CODEX_HOME indirection + CCC_MEMORY_MATERIALIZER_PROVIDER=piri) before exec'ing the real Piri CLI. Closes the injection gap where node-global Piri sessions (bridge RPC, print-mode, interactive) started with no memory while audience-scoped sessions already had a runtime bootstrap. Guards keep non-user runs memory-free: the nunchi piri-feed extractor session dir, CCC_MEMORY_AUDIENCE_SCOPED (runtime-owned bootstrap), and the CCC_PIRI_MEMORY_SKIP kill-switch. Shares the ccc-codex fail-closed contract (exit 78, last-valid-snapshot fallback, argv/cwd/stdio/signal preservation via final exec). Deployed and live-verified on soonwook (vps6): new Piri sessions through the wrapper confirm the injected snapshot (MEMORY_OK round-trip). - setup.sh installs the launcher beside ccc-codex; summary prints CCC_PIRI_CLI_PATH - scripts/ccc-piri.test.sh: 13 hermetic launch-surface + guard tests - docs/memory.md: Piri global snapshot materializer section --- docs/memory.md | 35 +++++++++++ scripts/ccc-piri | 60 +++++++++++++++++++ scripts/ccc-piri.test.sh | 113 ++++++++++++++++++++++++++++++++++++ scripts/setup.test.sh | 4 +- scripts/validate-harness.sh | 2 +- setup.sh | 9 +++ 6 files changed, 221 insertions(+), 2 deletions(-) create mode 100755 scripts/ccc-piri create mode 100755 scripts/ccc-piri.test.sh diff --git a/docs/memory.md b/docs/memory.md index 54c47965..30809cf0 100644 --- a/docs/memory.md +++ b/docs/memory.md @@ -96,6 +96,41 @@ cron entries. The nunchi code and local database remain in place, while the next Codex materialization falls back to canonical `load-memory.sh`. This wiring does not claim completion of pilot or gate-3 observation. +### Piri global snapshot materializer (ccc-piri) + +`scripts/ccc-piri` is the node-global Piri counterpart of `scripts/ccc-codex`. +Piri auto-loads `/AGENTS.md` (`${PIRI_CODING_AGENT_DIR:-~/.piri/agent}`) +as its global context file, and the shared materializer resolves its output as +`/AGENTS.md`, so the launcher runs the materializer with +`CODEX_HOME` pointed at the Piri agent dir and +`CCC_MEMORY_MATERIALIZER_PROVIDER=piri` before exec'ing the real CLI. Every +node-global Piri launch — interactive, print-mode, or the bridge RPC runtime — +then starts from the same bounded snapshot policy as Claude SessionStart and +the Codex global block, including the managed nunchi merge when nunchi mode is +`on`. + +Point `CCC_PIRI_CLI_PATH` at the installed `~/.claude/hooks/ccc-piri` and +`CCC_PIRI_REAL_CLI_PATH` at the real Piri CLI (e.g. a model-selecting shim). +`CCC_PIRI_MEMORY_MATERIALIZER_PATH` and `CCC_PIRI_MEMORY_HOME` override the +materializer and target agent dir. The launcher preserves argv, cwd, stdio, +exit status, and signals via a final `exec`, and shares the ccc-codex +fail-closed contract: a refresh failure may proceed on a structurally valid +private last snapshot; otherwise launch exits 78. + +Three bypass guards keep non-user runs memory-free: + +- `PIRI_CODING_AGENT_SESSION_DIR` under `.piri-feed-extractor-sessions` + (the nunchi piri-feed extractor) routes straight to the real CLI, so the + tool-free extractor never receives user memory. +- `CCC_MEMORY_AUDIENCE_SCOPED` truthy skips the node-global bootstrap; + audience-scoped sessions are bootstrapped by the bridge runtime itself via + `--no-context-files --append-system-prompt` and must not touch global memory. +- `CCC_PIRI_MEMORY_SKIP=1` is the explicit operator kill-switch. + +`setup.sh` installs the launcher beside `ccc-codex` under +`${CCC_CLAUDE_DIR:-$HOME/.claude}/hooks`, and `scripts/ccc-piri.test.sh` +covers the launch surface and all three guards hermetically. + ### Managed MemPalace refresh The nunchi installer schedules `hooks/nunchi/mempalace-refresh.sh` once per diff --git a/scripts/ccc-piri b/scripts/ccc-piri new file mode 100755 index 00000000..4c816805 --- /dev/null +++ b/scripts/ccc-piri @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +# ccc-piri — materialize the ccc-node snapshot into the Piri global context +# file before every Piri run. Node-global counterpart of ccc-codex: the +# materializer writes /AGENTS.md, so CODEX_HOME is pointed at the +# Piri agent dir, whose AGENTS.md Piri auto-loads as its global context file. +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +materializer="${CCC_PIRI_MEMORY_MATERIALIZER_PATH:-$SCRIPT_DIR/ccc_codex_memory.py}" +real_candidate="${CCC_PIRI_REAL_CLI_PATH:-piri}" +memory_home="${CCC_PIRI_MEMORY_HOME:-$HOME/.piri/agent}" + +skip=0 +# Isolated tool-free extractor runs (nunchi piri-feed) must not receive user +# memory; route them straight to the real CLI. +case "${PIRI_CODING_AGENT_SESSION_DIR:-}" in + */.piri-feed-extractor-sessions|*/.piri-feed-extractor-sessions/*) skip=1 ;; +esac +# Audience-scoped sessions are bootstrapped by the bridge runtime itself +# (--no-context-files --append-system-prompt); never touch global memory here. +case "${CCC_MEMORY_AUDIENCE_SCOPED:-0}" in + 1|true|TRUE|on|ON|yes|YES) skip=1 ;; +esac +[ "${CCC_PIRI_MEMORY_SKIP:-0}" = "1" ] && skip=1 + +if [ "$skip" != "1" ]; then + if [ ! -f "$materializer" ] || [ ! -x "$materializer" ] || [ -L "$materializer" ]; then + printf "%s\n" "ccc-piri: memory bootstrap unavailable" >&2 + exit 78 + fi + if ! env CODEX_HOME="$memory_home" CCC_MEMORY_MATERIALIZER_PROVIDER="${CCC_MEMORY_MATERIALIZER_PROVIDER:-piri}" \ + "$materializer" materialize --json >/dev/null 2>&1; then + # A refresh failure may proceed only when a previously materialized block + # is still structurally valid and private. status is read-only/body-free. + if ! env CODEX_HOME="$memory_home" "$materializer" status --json >/dev/null 2>&1; then + printf "%s\n" "ccc-piri: memory bootstrap unavailable" >&2 + exit 78 + fi + fi +fi + +case "$real_candidate" in + ""|-*) + printf "%s\n" "ccc-piri: real CLI unavailable" >&2 + exit 127 + ;; + */*) real_cli="$real_candidate" ;; + *) real_cli="$(command -v "$real_candidate" 2>/dev/null || true)" ;; +esac + +if [ -z "${real_cli:-}" ] || [ ! -x "$real_cli" ] || [ ! -f "$real_cli" ]; then + printf "%s\n" "ccc-piri: real CLI unavailable" >&2 + exit 127 +fi +if [ "$real_cli" -ef "$0" ]; then + printf "%s\n" "ccc-piri: recursive real CLI configuration" >&2 + exit 127 +fi + +exec "$real_cli" "$@" diff --git a/scripts/ccc-piri.test.sh b/scripts/ccc-piri.test.sh new file mode 100755 index 00000000..0aa940e6 --- /dev/null +++ b/scripts/ccc-piri.test.sh @@ -0,0 +1,113 @@ +#!/usr/bin/env bash +# Hermetic launch-surface tests for scripts/ccc-piri (node-global Piri +# counterpart of the ccc-codex launcher tests in ccc-codex.test.sh). +set -uo pipefail +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +LAUNCHER="$ROOT/scripts/ccc-piri" +pass=0; fail=0 +TMP="$(mktemp -d)" +trap 'rm -rf "$TMP"' EXIT +ok() { if eval "$2"; then pass=$((pass+1)); else fail=$((fail+1)); echo "FAIL: $1"; fi; } + +mat="$TMP/materializer" +real="$TMP/real-piri" +cat > "$mat" <<'SH' +#!/usr/bin/env bash +case "${1:-}" in + materialize) + printf 'materialize\n' >> "${ORDER_FILE:?}" + printf 'CODEX_HOME=%s\nPROVIDER=%s\n' "${CODEX_HOME:-}" "${CCC_MEMORY_MATERIALIZER_PROVIDER:-}" >> "${ENV_FILE:?}" + printf 'MATERIALIZER_BODY_SENTINEL\n' + printf 'MATERIALIZER_ERROR_SENTINEL\n' >&2 + exit "${MAT_RC:-0}" + ;; + status) + printf 'status\n' >> "${ORDER_FILE:?}" + exit "${STATUS_RC:-1}" + ;; + *) exit 64 ;; +esac +SH +cat > "$real" <<'SH' +#!/usr/bin/env bash +printf 'real\n' >> "${ORDER_FILE:?}" +pwd > "${CWD_FILE:?}" +python3 - "$ARGV_FILE" "$@" <<'PY' +import json,sys +with open(sys.argv[1], "w", encoding="utf-8") as fh: + json.dump(sys.argv[2:], fh) +PY +input="$(cat)" +printf 'REAL:%s' "$input" +exit "${REAL_RC:-0}" +SH +chmod 0700 "$mat" "$real" + +piri_home="$TMP/piri-agent"; mkdir -p "$piri_home" +work="$TMP/work dir"; mkdir -p "$work" +order="$TMP/order"; argv="$TMP/argv.json"; cwd_file="$TMP/cwd"; err="$TMP/err"; env_file="$TMP/env" +set +e +out="$(cd "$work" && printf 'stdin data' | ORDER_FILE="$order" ARGV_FILE="$argv" CWD_FILE="$cwd_file" ENV_FILE="$env_file" MAT_RC=0 STATUS_RC=1 REAL_RC=23 CCC_PIRI_MEMORY_HOME="$piri_home" CCC_PIRI_MEMORY_MATERIALIZER_PATH="$mat" CCC_PIRI_REAL_CLI_PATH="$real" "$LAUNCHER" --mode rpc --approve 2>"$err")" +rc=$? +set -e +ok "launcher preserves real Piri exit code and stdio" '[ "$rc" = 23 ] && [ "$out" = "REAL:stdin data" ]' +ok "launcher suppresses materializer body and error output" '! grep -q "MATERIALIZER_" "$err" && [[ "$out" != *MATERIALIZER_* ]]' +ok "launcher materializes before exec and preserves cwd" '[ "$(cat "$order")" = $'"'"'materialize\nreal'"'"' ] && [ "$(cat "$cwd_file")" = "$work" ]' +ok "launcher preserves argv boundaries" 'python3 - "$argv" <<'"'"'PY'"'"' +import json,sys +raise SystemExit(0 if json.load(open(sys.argv[1])) == ["--mode", "rpc", "--approve"] else 1) +PY' +ok "launcher points the materializer at the Piri agent dir with the piri provider" \ + 'grep -Fx "CODEX_HOME='"$piri_home"'" "$env_file" >/dev/null && grep -Fx "PROVIDER=piri" "$env_file" >/dev/null' + +: > "$order"; rm -f "$argv" +set +e +out="$(ORDER_FILE="$order" ARGV_FILE="$argv" CWD_FILE="$cwd_file" ENV_FILE="$env_file" MAT_RC=9 STATUS_RC=0 REAL_RC=0 CCC_PIRI_MEMORY_HOME="$piri_home" CCC_PIRI_MEMORY_MATERIALIZER_PATH="$mat" CCC_PIRI_REAL_CLI_PATH="$real" "$LAUNCHER" ready 2>"$err")" +rc=$? +set -e +ok "launcher uses last valid snapshot when refresh fails" '[ "$rc" = 0 ] && [ "$out" = "REAL:" ] && [ "$(cat "$order")" = $'"'"'materialize\nstatus\nreal'"'"' ]' + +: > "$order"; rm -f "$argv" +set +e +ORDER_FILE="$order" ARGV_FILE="$argv" CWD_FILE="$cwd_file" ENV_FILE="$env_file" MAT_RC=9 STATUS_RC=7 REAL_RC=0 CCC_PIRI_MEMORY_HOME="$piri_home" CCC_PIRI_MEMORY_MATERIALIZER_PATH="$mat" CCC_PIRI_REAL_CLI_PATH="$real" "$LAUNCHER" blocked >"$TMP/out" 2>"$err" +rc=$? +set -e +ok "launcher fails closed when no current or last-valid snapshot exists" '[ "$rc" = 78 ] && [ ! -e "$argv" ] && [ "$(cat "$order")" = $'"'"'materialize\nstatus'"'"' ]' +ok "fail-closed diagnostic is bounded and body-free" '[ "$(wc -c < "$err")" -lt 256 ] && ! grep -q "MATERIALIZER_\|SECRET" "$err"' + +: > "$order" +set +e +ORDER_FILE="$order" ARGV_FILE="$argv" CWD_FILE="$cwd_file" ENV_FILE="$env_file" MAT_RC=0 STATUS_RC=0 CCC_PIRI_MEMORY_HOME="$piri_home" CCC_PIRI_MEMORY_MATERIALIZER_PATH="$mat" CCC_PIRI_REAL_CLI_PATH="$LAUNCHER" "$LAUNCHER" >"$TMP/out" 2>"$err" +rc=$? +set -e +ok "launcher rejects recursive real-cli configuration" '[ "$rc" = 127 ]' +ok "launcher uses final exec rather than a child Piri process" 'grep -Fq '"'"'exec "$real_cli" "$@"'"'"' "$LAUNCHER"' + +# Guard: the nunchi piri-feed extractor runs tool-free and must never receive +# user memory — it is routed straight to the real CLI with no materialize. +: > "$order"; rm -f "$argv" +set +e +out="$(ORDER_FILE="$order" ARGV_FILE="$argv" CWD_FILE="$cwd_file" ENV_FILE="$env_file" MAT_RC=0 STATUS_RC=0 REAL_RC=0 PIRI_CODING_AGENT_SESSION_DIR="$TMP/nunchi/.piri-feed-extractor-sessions" CCC_PIRI_MEMORY_HOME="$piri_home" CCC_PIRI_MEMORY_MATERIALIZER_PATH="$mat" CCC_PIRI_REAL_CLI_PATH="$real" "$LAUNCHER" --mode text --print x 2>"$err")" +rc=$? +set -e +ok "extractor-session guard bypasses the memory bootstrap entirely" '[ "$rc" = 0 ] && [ "$out" = "REAL:" ] && [ "$(cat "$order")" = "real" ]' + +# Guard: audience-scoped sessions are bootstrapped by the bridge runtime +# itself; the node-global launcher must not touch global memory for them. +: > "$order" +set +e +out="$(ORDER_FILE="$order" ARGV_FILE="$argv" CWD_FILE="$cwd_file" ENV_FILE="$env_file" MAT_RC=0 STATUS_RC=0 REAL_RC=0 CCC_MEMORY_AUDIENCE_SCOPED=1 CCC_PIRI_MEMORY_HOME="$piri_home" CCC_PIRI_MEMORY_MATERIALIZER_PATH="$mat" CCC_PIRI_REAL_CLI_PATH="$real" "$LAUNCHER" 2>"$err")" +rc=$? +set -e +ok "audience-scoped guard bypasses the node-global memory bootstrap" '[ "$rc" = 0 ] && [ "$out" = "REAL:" ] && [ "$(cat "$order")" = "real" ]' + +# Guard: explicit operator kill-switch. +: > "$order" +set +e +out="$(ORDER_FILE="$order" ARGV_FILE="$argv" CWD_FILE="$cwd_file" ENV_FILE="$env_file" MAT_RC=0 STATUS_RC=0 REAL_RC=0 CCC_PIRI_MEMORY_SKIP=1 CCC_PIRI_MEMORY_HOME="$piri_home" CCC_PIRI_MEMORY_MATERIALIZER_PATH="$mat" CCC_PIRI_REAL_CLI_PATH="$real" "$LAUNCHER" 2>"$err")" +rc=$? +set -e +ok "CCC_PIRI_MEMORY_SKIP=1 bypasses the memory bootstrap" '[ "$rc" = 0 ] && [ "$out" = "REAL:" ] && [ "$(cat "$order")" = "real" ]' + +echo "----"; echo "PASS=$pass FAIL=$fail" +[ "$fail" = 0 ] diff --git a/scripts/setup.test.sh b/scripts/setup.test.sh index 14b96eed..601c5e44 100644 --- a/scripts/setup.test.sh +++ b/scripts/setup.test.sh @@ -267,6 +267,8 @@ ok "setup deploys the mtime-prune library the pruning hooks source" \ '[ -x "$rewrite_claude/hooks/lib/mtime-prune.sh" ] && cmp -s "$ROOT/claude/hooks/lib/mtime-prune.sh" "$rewrite_claude/hooks/lib/mtime-prune.sh"' ok "setup installs the Codex launcher and materializer as executable managed hooks" \ '[ -x "$rewrite_claude/hooks/ccc-codex" ] && [ -x "$rewrite_claude/hooks/ccc_codex_memory.py" ] && cmp -s "$ROOT/scripts/ccc-codex" "$rewrite_claude/hooks/ccc-codex" && cmp -s "$ROOT/scripts/ccc_codex_memory.py" "$rewrite_claude/hooks/ccc_codex_memory.py"' +ok "setup installs the Piri launcher as an executable managed hook" \ + '[ -x "$rewrite_claude/hooks/ccc-piri" ] && cmp -s "$ROOT/scripts/ccc-piri" "$rewrite_claude/hooks/ccc-piri"' ok "setup installs the managed nunchi Codex loader" \ '[ -x "$rewrite_claude/hooks/nunchi/codex-loader.py" ] && cmp -s "$ROOT/claude/hooks/nunchi/codex-loader.py" "$rewrite_claude/hooks/nunchi/codex-loader.py"' ok "setup installs the body-free memory readiness probe beside memory-check" \ @@ -285,7 +287,7 @@ ok "source-checkout local-memory transaction imports canonical secure-fs directl 'PYTHONDONTWRITEBYTECODE=1 PYTHONPATH= python3 -S "$ROOT/bridge/memory/local_memory_transaction.py" --help >/dev/null 2>&1' codex_dry_out="$(HOME="$nonroot_home" CCC_CLAUDE_DIR="$nonroot_claude" CCC_HERMES_DIR="$nonroot_hermes" CCC_WIKI_AGENT_BIN="$nonroot_wiki" CCC_BRIDGE_DEFAULT_PATH="$nonroot_bridge" bash "$SETUP" --dry-run 2>&1)"; codex_dry_rc=$? ok "setup non-root dry-run includes all Codex managed launch artifacts" \ - '[ "$codex_dry_rc" = 0 ] && grep -Fq "$nonroot_claude/hooks/ccc-codex" <<<"$codex_dry_out" && grep -Fq "$nonroot_claude/hooks/ccc_codex_memory.py" <<<"$codex_dry_out" && grep -Fq "$nonroot_claude/hooks/ccc_secure_fs.py" <<<"$codex_dry_out" && grep -Fq "$nonroot_claude/hooks/ccc_local_memory_transaction.py" <<<"$codex_dry_out"' + '[ "$codex_dry_rc" = 0 ] && grep -Fq "$nonroot_claude/hooks/ccc-codex" <<<"$codex_dry_out" && grep -Fq "$nonroot_claude/hooks/ccc-piri" <<<"$codex_dry_out" && grep -Fq "$nonroot_claude/hooks/ccc_codex_memory.py" <<<"$codex_dry_out" && grep -Fq "$nonroot_claude/hooks/ccc_secure_fs.py" <<<"$codex_dry_out" && grep -Fq "$nonroot_claude/hooks/ccc_local_memory_transaction.py" <<<"$codex_dry_out"' # --- #569: hook-tree walk — deploys recursively, excludes tests/bytecode/wiring, # and dry-run only RENDERS the walk (no copies). rewrite_claude is a real install. diff --git a/scripts/validate-harness.sh b/scripts/validate-harness.sh index fa4c4a3f..a22e119a 100755 --- a/scripts/validate-harness.sh +++ b/scripts/validate-harness.sh @@ -268,7 +268,7 @@ for t in claude/hooks/observability.test.sh claude/hooks/security-scan.test.sh \ claude/hooks/distill/local-facts.test.sh claude/hooks/memory-hooks.test.sh \ claude/hooks/refresh-memory-freshness.test.sh \ claude/hooks/nunchi/nunchi.test.sh claude/hooks/nunchi/bench.test.sh \ - scripts/ccc-doctor.test.sh scripts/ccc-memory.test.sh scripts/ccc-codex-memory.test.sh scripts/ccc-codex.test.sh scripts/ccc-codex-github-policy.test.sh scripts/ccc-distill-check.test.sh scripts/ccc-security-audit.test.sh \ + scripts/ccc-doctor.test.sh scripts/ccc-memory.test.sh scripts/ccc-codex-memory.test.sh scripts/ccc-codex.test.sh scripts/ccc-piri.test.sh scripts/ccc-codex-github-policy.test.sh scripts/ccc-distill-check.test.sh scripts/ccc-security-audit.test.sh \ scripts/ccc-fleet-matrix.test.sh scripts/ccc-wiki-triage.test.sh scripts/setup.test.sh \ scripts/harness-paths.test.sh scripts/canonical-paths.test.sh \ scripts/agent-cron.test.sh scripts/agent-cron-lib.test.sh scripts/a2a-termux-native-worker.test.sh \ diff --git a/setup.sh b/setup.sh index dd65ac69..db37235c 100755 --- a/setup.sh +++ b/setup.sh @@ -367,6 +367,9 @@ run cp "$SRC/scripts/lib/harness_paths.py" "$CLAUDE_DIR/hooks/lib/harness_paths. # load-memory.sh so every direct/app-server run reuses the same snapshot policy. run cp "$SRC/scripts/ccc-codex" "$CLAUDE_DIR/hooks/ccc-codex" run cp "$SRC/scripts/ccc_codex_memory.py" "$CLAUDE_DIR/hooks/ccc_codex_memory.py" +# Piri launch boundary: node-global counterpart of ccc-codex — materializes the +# same snapshot into the Piri global context file (/AGENTS.md). +run cp "$SRC/scripts/ccc-piri" "$CLAUDE_DIR/hooks/ccc-piri" # Claude's distill committer imports the same crash-recoverable transaction # implementation as the Codex bridge. Install the canonical module beside the # existing standalone secure-fs copy instead of forking provider logic. @@ -402,6 +405,7 @@ installed_hook_scripts=( "$CLAUDE_DIR/hooks/lib/harness_paths.py" "$CLAUDE_DIR/hooks/ccc-codex" "$CLAUDE_DIR/hooks/ccc_codex_memory.py" + "$CLAUDE_DIR/hooks/ccc-piri" "$CLAUDE_DIR/hooks/ccc-memory-index.sh" "$CLAUDE_DIR/hooks/ccc-memory-search.sh" "$CLAUDE_DIR/hooks/ccc-memory-consolidate.sh" @@ -693,6 +697,10 @@ cat <<'EOF' 11. (Optional Codex) Keep CCC_CODEX_CLI_PATH on ~/.claude/hooks/ccc-codex, set CCC_CODEX_REAL_CLI_PATH only for a non-PATH binary, and require `ccc-memory-check.sh --json` to report `.codex.status == "ready"`. + 12. (Optional Piri) Point CCC_PIRI_CLI_PATH at ~/.claude/hooks/ccc-piri and + CCC_PIRI_REAL_CLI_PATH at the real Piri CLI so every node-global Piri + launch materializes the same memory snapshot into the Piri global + context file first. Secrets that are intentionally NOT installed by this script: - ~/.claude/.credentials.json (Claude OAuth — created on `claude` login) @@ -709,6 +717,7 @@ printf ' - CCC_WIKI_AGENT_BIN=%s\n' "$WIKI_AGENT_BIN" printf ' - CCC_BRIDGE_DEFAULT_PATH=%s\n' "$BRIDGE_DEFAULT_PATH" printf ' - CCC_CODEX_CLI_PATH=%s/hooks/ccc-codex\n' "$CLAUDE_DIR" printf ' - CCC_CODEX_MEMORY_MATERIALIZER_PATH=%s/hooks/ccc_codex_memory.py\n' "$CLAUDE_DIR" +printf ' - CCC_PIRI_CLI_PATH=%s/hooks/ccc-piri\n' "$CLAUDE_DIR" printf ' - CODEX_HOME=%s (GitHub plugin disabled; gh CLI-first)\n' "$CODEX_DIR" printf ' - Codex managed skills=%s/skills (catalog: codex/compatibility.json)\n' "$CODEX_DIR" printf ' - bridge command=./start.sh --path %s -d\n' "$BRIDGE_DEFAULT_PATH"