From 35ea5c1a494b490978d1a05dc850a4337cb78e4f Mon Sep 17 00:00:00 2001 From: bastien Date: Sun, 12 Apr 2026 13:28:30 +0200 Subject: [PATCH] audit fixes: RTK hook, settings unification, graphifyy, statusline - Add RTK PreToolUse hook (rtk-rewrite.sh) and fix missing config - Unify settings.json: merge hooks, marketplaces, model into project file so link.sh symlink is the single source of truth - Add statusline: model, folder, git branch, context % progress bar - Add graphifyy support: detect, install (pipx), lock, doctor, session-start - Clarify ctx7/ruflo as standalone CLI (not MCP servers) - Fix install-plugins.sh step numbering (duplicate step 6) - Add version check in session-start (local vs origin/master) Co-Authored-By: Claude Opus 4.6 --- doctor.sh | 7 +++ hooks/.rtk-hook.sha256 | 1 + hooks/rtk-rewrite.sh | 98 ++++++++++++++++++++++++++++++++++++++++++ hooks/session-start.sh | 14 +++++- hooks/statusline.sh | 48 +++++++++++++++++++++ install-plugins.sh | 40 ++++++++++++++++- lib/detect-plugins.sh | 5 +++ plugins.lock.json | 10 ++++- settings.json | 36 ++++++++++++++++ 9 files changed, 255 insertions(+), 4 deletions(-) create mode 100644 hooks/.rtk-hook.sha256 create mode 100755 hooks/rtk-rewrite.sh create mode 100755 hooks/statusline.sh diff --git a/doctor.sh b/doctor.sh index 9a4bf78..4d8e251 100644 --- a/doctor.sh +++ b/doctor.sh @@ -180,6 +180,12 @@ else info "Ruflo CLI not installed (optional — enterprise multi-agent: npm install -g ruflo@latest --omit=optional)" fi +if detect_graphifyy; then + pass "Graphifyy installed (graphify CLI)" +else + info "Graphifyy not installed (optional — codebase knowledge graph: pipx install graphifyy)" +fi + echo "" # ──────────────────────────────────────────────────────────── @@ -246,6 +252,7 @@ if detect_frontend_design 2>/dev/null; then PLUGIN_TOKENS=$((PLUGIN_TOKENS + 200 if detect_uiux_pro_max 2>/dev/null; then PLUGIN_TOKENS=$((PLUGIN_TOKENS + 400)); fi if detect_context7 2>/dev/null; then PLUGIN_TOKENS=$((PLUGIN_TOKENS + 200)); fi if detect_ruflo 2>/dev/null; then PLUGIN_TOKENS=$((PLUGIN_TOKENS + 1000)); fi +if detect_graphifyy 2>/dev/null; then PLUGIN_TOKENS=$((PLUGIN_TOKENS + 300)); fi TOTAL_TOKENS=$((CLAUDE_MD_TOKENS + SKILL_DESC_TOKENS + PLUGIN_TOKENS)) SESSION_BUDGET=11000 diff --git a/hooks/.rtk-hook.sha256 b/hooks/.rtk-hook.sha256 new file mode 100644 index 0000000..79741f9 --- /dev/null +++ b/hooks/.rtk-hook.sha256 @@ -0,0 +1 @@ +ef0d630994fd7ef5f2b84fb66cd6249c493bb8736bcacd4734d7c798125018fb rtk-rewrite.sh diff --git a/hooks/rtk-rewrite.sh b/hooks/rtk-rewrite.sh new file mode 100755 index 0000000..f7a42b5 --- /dev/null +++ b/hooks/rtk-rewrite.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash +# rtk-hook-version: 3 +# RTK Claude Code hook — rewrites commands to use rtk for token savings. +# Requires: rtk >= 0.23.0, jq +# +# This is a thin delegating hook: all rewrite logic lives in `rtk rewrite`, +# which is the single source of truth (src/discover/registry.rs). +# To add or change rewrite rules, edit the Rust registry — not this file. +# +# Exit code protocol for `rtk rewrite`: +# 0 + stdout Rewrite found, no deny/ask rule matched → auto-allow +# 1 No RTK equivalent → pass through unchanged +# 2 Deny rule matched → pass through (Claude Code native deny handles it) +# 3 + stdout Ask rule matched → rewrite but let Claude Code prompt the user + +if ! command -v jq &>/dev/null; then + echo "[rtk] WARNING: jq is not installed. Hook cannot rewrite commands. Install jq: https://jqlang.github.io/jq/download/" >&2 + exit 0 +fi + +if ! command -v rtk &>/dev/null; then + echo "[rtk] WARNING: rtk is not installed or not in PATH. Hook cannot rewrite commands. Install: https://github.com/rtk-ai/rtk#installation" >&2 + exit 0 +fi + +# Version guard: rtk rewrite was added in 0.23.0. +# Older binaries: warn once and exit cleanly (no silent failure). +RTK_VERSION=$(rtk --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) +if [ -n "$RTK_VERSION" ]; then + MAJOR=$(echo "$RTK_VERSION" | cut -d. -f1) + MINOR=$(echo "$RTK_VERSION" | cut -d. -f2) + # Require >= 0.23.0 + if [ "$MAJOR" -eq 0 ] && [ "$MINOR" -lt 23 ]; then + echo "[rtk] WARNING: rtk $RTK_VERSION is too old (need >= 0.23.0). Upgrade: cargo install rtk" >&2 + exit 0 + fi +fi + +INPUT=$(cat) +CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty') + +if [ -z "$CMD" ]; then + exit 0 +fi + +# Delegate all rewrite + permission logic to the Rust binary. +REWRITTEN=$(rtk rewrite "$CMD" 2>/dev/null) +EXIT_CODE=$? + +case $EXIT_CODE in + 0) + # Rewrite found, no permission rules matched — safe to auto-allow. + # If the output is identical, the command was already using RTK. + [ "$CMD" = "$REWRITTEN" ] && exit 0 + ;; + 1) + # No RTK equivalent — pass through unchanged. + exit 0 + ;; + 2) + # Deny rule matched — let Claude Code's native deny rule handle it. + exit 0 + ;; + 3) + # Ask rule matched — rewrite the command but do NOT auto-allow so that + # Claude Code prompts the user for confirmation. + ;; + *) + exit 0 + ;; +esac + +ORIGINAL_INPUT=$(echo "$INPUT" | jq -c '.tool_input') +UPDATED_INPUT=$(echo "$ORIGINAL_INPUT" | jq --arg cmd "$REWRITTEN" '.command = $cmd') + +if [ "$EXIT_CODE" -eq 3 ]; then + # Ask: rewrite the command, omit permissionDecision so Claude Code prompts. + jq -n \ + --argjson updated "$UPDATED_INPUT" \ + '{ + "hookSpecificOutput": { + "hookEventName": "PreToolUse", + "updatedInput": $updated + } + }' +else + # Allow: rewrite the command and auto-allow. + jq -n \ + --argjson updated "$UPDATED_INPUT" \ + '{ + "hookSpecificOutput": { + "hookEventName": "PreToolUse", + "permissionDecision": "allow", + "permissionDecisionReason": "RTK auto-rewrite", + "updatedInput": $updated + } + }' +fi diff --git a/hooks/session-start.sh b/hooks/session-start.sh index 93ab504..fcaa777 100644 --- a/hooks/session-start.sh +++ b/hooks/session-start.sh @@ -48,7 +48,7 @@ unset _lib TOGGLE_ACTIVE=() TOGGLE_INACTIVE=() -for plugin in gstack uiux_pro_max frontend_design plugin_dev context7 ruflo; do +for plugin in gstack uiux_pro_max frontend_design plugin_dev context7 ruflo graphifyy; do # Map function name to display name case "$plugin" in uiux_pro_max) display="ui-ux-pro-max" ;; @@ -83,6 +83,7 @@ if [ -n "$_claude_real" ]; then else CONFIG_VERSION="?" fi +REPO_DIR="${_repo_dir:-}" unset _claude_real _repo_dir # Quick passive token cost estimate (Pro session budget = ~11k tokens) @@ -94,6 +95,7 @@ detect_plugin_dev 2>/dev/null && _passive_t=$((_passive_t + 100)) detect_uiux_pro_max 2>/dev/null && _passive_t=$((_passive_t + 400)) detect_context7 2>/dev/null && _passive_t=$((_passive_t + 200)) detect_ruflo 2>/dev/null && _passive_t=$((_passive_t + 1000)) +detect_graphifyy 2>/dev/null && _passive_t=$((_passive_t + 300)) _budget_pct=$((_passive_t * 100 / 11000)) if [ "$_budget_pct" -gt 50 ]; then TOKEN_WARN="⚠️ ~${_passive_t}t passif (${_budget_pct}% budget)" @@ -141,6 +143,16 @@ unset _active_count _inactive_count printf "│ 🖥️ CLI : %-40s│\n" "$GSD_STATUS" [ -n "$TOKEN_WARN" ] && printf "│ 💰 %-44s│\n" "${TOKEN_WARN:0:44}" printf "│ 📦 v%-45s│\n" "$CONFIG_VERSION" +# Version check: compare local vs remote (non-blocking) +_remote_ver="" +if [ -n "$REPO_DIR" ] && [ -d "$REPO_DIR/.git" ]; then + _remote_ver=$(cd "$REPO_DIR" 2>/dev/null && git fetch origin --quiet 2>/dev/null && git show origin/master:version.txt 2>/dev/null || true) +fi +if [ -n "$_remote_ver" ] && [ "$_remote_ver" != "$CONFIG_VERSION" ]; then + printf "│ 🔄 update available: v%-27s│\n" "$_remote_ver" +fi +unset _remote_ver REPO_DIR + echo "│ 💡 /plugin-check before starting a new project │" echo "│ 🩺 /health to run full diagnostic │" echo "└───────────────────────────────────────────────────┘" diff --git a/hooks/statusline.sh b/hooks/statusline.sh new file mode 100755 index 0000000..507917d --- /dev/null +++ b/hooks/statusline.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# Claude Code statusline — folder, git branch, model, context % +# Receives JSON on stdin from Claude Code. + +INPUT=$(cat) + +MODEL=$(echo "$INPUT" | jq -r '.model.display_name // "?"') +DIR=$(echo "$INPUT" | jq -r '.cwd // "?"') +FOLDER="${DIR##*/}" +PCT=$(echo "$INPUT" | jq -r \ + '.context_window.used_percentage // 0' \ + | cut -d. -f1) + +# Git branch (fast, no network) +BRANCH="" +if [ -d "$DIR" ]; then + BRANCH=$(git -C "$DIR" branch --show-current 2>/dev/null) +fi +BRANCH_STR="${BRANCH:+ ($BRANCH)}" + +# Progress bar (20 chars wide) +WIDTH=20 +FILLED=$((PCT * WIDTH / 100)) +EMPTY=$((WIDTH - FILLED)) +if [ "$FILLED" -gt 0 ]; then + printf -v FILL "%${FILLED}s" +else + FILL="" +fi +if [ "$EMPTY" -gt 0 ]; then + printf -v PAD "%${EMPTY}s" +else + PAD="" +fi +BAR="${FILL// /█}${PAD// /░}" + +# Color: green <50%, yellow 50-79%, red >=80% +if [ "$PCT" -ge 80 ]; then + COLOR="\033[31m" +elif [ "$PCT" -ge 50 ]; then + COLOR="\033[33m" +else + COLOR="\033[32m" +fi +RESET="\033[0m" + +# Output: single line +echo -e "$MODEL | $FOLDER${BRANCH_STR} | ${COLOR}${BAR}${RESET} ${PCT}%" diff --git a/install-plugins.sh b/install-plugins.sh index f59fa8f..52539dd 100644 --- a/install-plugins.sh +++ b/install-plugins.sh @@ -147,6 +147,21 @@ else esac fi +# --- pipx (for Graphifyy) --- +if command -v pipx &>/dev/null; then + ok "pipx $(pipx --version 2>/dev/null)" +else + info "Installing pipx..." + case $OS in + macos) brew install pipx ;; + linux-apt) sudo apt-get install -y pipx ;; + linux-dnf) sudo dnf install -y pipx ;; + linux-pacman) sudo pacman -S --noconfirm python-pipx ;; + *) warn "Cannot auto-install pipx on $OS" ;; + esac + pipx ensurepath 2>/dev/null || true +fi + # --- Claude Code CLI --- if command -v claude &>/dev/null; then ok "Claude Code $(claude --version 2>/dev/null | head -1)" @@ -349,7 +364,7 @@ install_plugin "ui-ux-pro-max" "ui-ux-pro-max-skill" echo "" # ============================================================ -# STEP 6 — CONTEXT7 CLI (ctx7) +# STEP 7 — CONTEXT7 CLI (ctx7) # ============================================================ echo "── Step 7: Context7 CLI ─────────────────────────────────────" echo "" @@ -374,6 +389,28 @@ if command -v ctx7 &>/dev/null; then info "Free higher rate limits: ctx7 login (OAuth) or --api-key from context7.com/dashboard" fi +# ============================================================ +# STEP 8 — GRAPHIFYY (codebase knowledge graph) +# ============================================================ +echo "── Step 8: Graphifyy — Knowledge Graph ──────────────────────" +echo "" +if command -v graphify &>/dev/null; then + ok "graphify already installed" +else + info "Installing graphifyy via pipx..." + pipx install graphifyy 2>/dev/null \ + && ok "graphifyy installed" \ + || err "graphifyy install failed — run manually: pipx install graphifyy" +fi +if command -v graphify &>/dev/null; then + info "Running graphify install (dependencies)..." + graphify install 2>/dev/null || warn "graphify install failed — run manually" + info "Configuring Claude Code integration..." + graphify claude install 2>/dev/null || warn "graphify claude install failed — run manually" + ok "Graphifyy configured for Claude Code" +fi +echo "" + # ============================================================ # SUMMARY # ============================================================ @@ -396,6 +433,7 @@ echo " 🔄 frontend-design — UI design skill (~200 tokens) [claude-cod echo " 🔄 ui-ux-pro-max — user scope (~400 tokens)" echo " 🔄 context7 CLI — ctx7 (npm global, standalone or MCP setup)" echo " 🔄 ruflo CLI — enterprise multi-agent orchestration (~500-1500 tokens)" +echo " 🔄 graphifyy — codebase knowledge graph (pipx, PreToolUse hook)" echo "" echo " All plugins installed at: user scope (~/.claude/plugins/)" echo " GStack at: ~/.claude/skills/gstack/ (symlink → submodule)" diff --git a/lib/detect-plugins.sh b/lib/detect-plugins.sh index 4f3cacc..0d92d33 100644 --- a/lib/detect-plugins.sh +++ b/lib/detect-plugins.sh @@ -67,3 +67,8 @@ detect_ruflo() { # Ruflo CLI — installed globally via npm command -v ruflo &>/dev/null } + +detect_graphifyy() { + # Graphifyy — codebase knowledge graph, installed via pipx + command -v graphify &>/dev/null +} diff --git a/plugins.lock.json b/plugins.lock.json index 14db956..fe2baca 100644 --- a/plugins.lock.json +++ b/plugins.lock.json @@ -18,11 +18,17 @@ "ruflo": { "source": "npm:ruflo", "version": "3.5.58", - "note": "Enterprise multi-agent MCP server (formerly claude-flow). Requires manual MCP config after install. Check latest at https://www.npmjs.com/package/ruflo before updating." + "note": "Enterprise multi-agent orchestration CLI (formerly claude-flow). Standalone CLI, not an MCP server. Check latest at https://www.npmjs.com/package/ruflo before updating." }, "ctx7": { "source": "npm:ctx7", "version": "latest", - "note": "Context7 CLI \u2014 doc lookup for fast-evolving libs. Install: npm install -g ctx7. Setup for Claude Code: ctx7 setup --claude. Standalone: ctx7 docs /vercel/next.js \"middleware\"." + "note": "Context7 CLI — doc lookup for fast-evolving libs. Standalone CLI, not an MCP server. Install: npm install -g ctx7. Standalone: ctx7 docs /vercel/next.js \"middleware\"." + }, + "graphifyy": { + "source": "pypi:graphifyy", + "version": "latest", + "managed_by": "pipx", + "note": "Codebase knowledge graph. CLI is 'graphify'. Install: pipx install graphifyy && graphify install && graphify claude install. Adds PreToolUse hook for Glob/Grep." } } diff --git a/settings.json b/settings.json index c7b03e9..eb5eb58 100644 --- a/settings.json +++ b/settings.json @@ -197,6 +197,42 @@ } ] } + ], + "PreToolUse": [ + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": "bash ~/.claude/hooks/rtk-rewrite.sh" + } + ] + } ] + }, + "extraKnownMarketplaces": { + "claude-code-plugins": { + "source": { + "source": "github", + "repo": "anthropics/claude-code" + } + }, + "superpowers-marketplace": { + "source": { + "source": "github", + "repo": "obra/superpowers-marketplace" + } + }, + "ui-ux-pro-max-skill": { + "source": { + "source": "github", + "repo": "nextlevelbuilder/ui-ux-pro-max-skill" + } + } + }, + "model": "opus", + "statusLine": { + "type": "command", + "command": "bash ~/.claude/hooks/statusline.sh" } }