- Add code-index-mcp and serena sections to AGENTS.md - Update AGENTS.md MCP tools description in obligatory approach - Add graphify hooks (post-checkout, post-commit) for auto graph rebuild - Add serena project config - Add graphify-out knowledge graph artifacts - Ignore dev.db and graphify-out/cache/ in .gitignore
140 lines
5.7 KiB
Bash
Executable File
140 lines
5.7 KiB
Bash
Executable File
#!/bin/sh
|
|
# graphify-checkout-hook-start
|
|
# Auto-rebuilds the knowledge graph (code only) when switching branches.
|
|
# Installed by: graphify hook install
|
|
|
|
# Deterministic clustering: networkx louvain iterates string-keyed sets whose
|
|
# order is randomized per-process by PYTHONHASHSEED, so community assignments
|
|
# churn run-to-run. Pinning it makes graphify-out reproducible.
|
|
export PYTHONHASHSEED=0
|
|
|
|
PREV_HEAD=$1
|
|
NEW_HEAD=$2
|
|
BRANCH_SWITCH=$3
|
|
|
|
# Only run on branch switches, not file checkouts
|
|
if [ "$BRANCH_SWITCH" != "1" ]; then
|
|
exit 0
|
|
fi
|
|
|
|
# Only run if graphify-out/ exists (graph has been built before)
|
|
if [ ! -d "graphify-out" ]; then
|
|
exit 0
|
|
fi
|
|
|
|
# Skip during rebase/merge/cherry-pick
|
|
GIT_DIR=$(git rev-parse --git-dir 2>/dev/null)
|
|
[ -d "$GIT_DIR/rebase-merge" ] && exit 0
|
|
[ -d "$GIT_DIR/rebase-apply" ] && exit 0
|
|
[ -f "$GIT_DIR/MERGE_HEAD" ] && exit 0
|
|
[ -f "$GIT_DIR/CHERRY_PICK_HEAD" ] && exit 0
|
|
|
|
# Detect the correct Python interpreter (handles uv tool, pipx, venv, system installs).
|
|
# _PINNED was recorded at hook-install time; tried first so the hook works even
|
|
# when the graphify launcher is not on PATH (common in GUI clients and CI).
|
|
GRAPHIFY_PYTHON=""
|
|
_PINNED='/Users/ksv741/.local/share/uv/tools/graphifyy/bin/python'
|
|
if [ -n "$_PINNED" ] && [ -x "$_PINNED" ] && "$_PINNED" -c "import graphify" 2>/dev/null; then
|
|
GRAPHIFY_PYTHON="$_PINNED"
|
|
fi
|
|
# Second probe: read graphify-out/.graphify_python (written by the skill and
|
|
# CLI; survives uv-tool reinstalls and is the same source the README documents).
|
|
if [ -z "$GRAPHIFY_PYTHON" ]; then
|
|
_GFY_PYTHON_FILE="graphify-out/.graphify_python"
|
|
if [ -f "$_GFY_PYTHON_FILE" ]; then
|
|
_FROM_FILE=$(cat "$_GFY_PYTHON_FILE" 2>/dev/null | tr -d '[:space:]')
|
|
case "$_FROM_FILE" in
|
|
*[!a-zA-Z0-9/_.@:\-]*) _FROM_FILE="" ;; # allowlist (covers Windows paths)
|
|
esac
|
|
if [ -n "$_FROM_FILE" ] && [ -x "$_FROM_FILE" ] && "$_FROM_FILE" -c "import graphify" 2>/dev/null; then
|
|
GRAPHIFY_PYTHON="$_FROM_FILE"
|
|
fi
|
|
fi
|
|
fi
|
|
# Third probe: resolve via the graphify launcher on PATH (shebang probe).
|
|
if [ -z "$GRAPHIFY_PYTHON" ]; then
|
|
GRAPHIFY_BIN=$(command -v graphify 2>/dev/null)
|
|
if [ -n "$GRAPHIFY_BIN" ]; then
|
|
case "$GRAPHIFY_BIN" in
|
|
*.exe) _SHEBANG="" ;;
|
|
*) _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | sed 's/^#![[:space:]]*//') ;;
|
|
esac
|
|
case "$_SHEBANG" in
|
|
*/env\ *) GRAPHIFY_PYTHON="${_SHEBANG#*/env }" ;;
|
|
*) GRAPHIFY_PYTHON="$_SHEBANG" ;;
|
|
esac
|
|
# Allowlist: only keep characters valid in a filesystem path to prevent
|
|
# injection if the shebang contains shell metacharacters.
|
|
case "$GRAPHIFY_PYTHON" in
|
|
*[!a-zA-Z0-9/_.@-]*) GRAPHIFY_PYTHON="" ;;
|
|
esac
|
|
if [ -n "$GRAPHIFY_PYTHON" ] && ! "$GRAPHIFY_PYTHON" -c "import graphify" 2>/dev/null; then
|
|
GRAPHIFY_PYTHON=""
|
|
fi
|
|
fi
|
|
fi
|
|
# Last resort: try python3 / python (works for system/venv installs on PATH).
|
|
if [ -z "$GRAPHIFY_PYTHON" ]; then
|
|
if command -v python3 >/dev/null 2>&1 && python3 -c "import graphify" 2>/dev/null; then
|
|
GRAPHIFY_PYTHON="python3"
|
|
elif command -v python >/dev/null 2>&1 && python -c "import graphify" 2>/dev/null; then
|
|
GRAPHIFY_PYTHON="python"
|
|
else
|
|
echo "[graphify hook] could not locate a Python with graphify installed. Add the graphify bin dir to PATH or re-run 'graphify hook install' from the env where graphify lives." >&2
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
_GRAPHIFY_LOG="${HOME}/.cache/graphify-rebuild.log"
|
|
mkdir -p "$(dirname "$_GRAPHIFY_LOG")"
|
|
export GRAPHIFY_REBUILD_LOG="$_GRAPHIFY_LOG"
|
|
echo "[graphify] Branch switched - launching background rebuild (log: $_GRAPHIFY_LOG)"
|
|
"$GRAPHIFY_PYTHON" -c "import os, subprocess, sys
|
|
_src = '''
|
|
from graphify.watch import _rebuild_code, _apply_resource_limits
|
|
from pathlib import Path
|
|
import os, signal, sys
|
|
try:
|
|
_apply_resource_limits()
|
|
_timeout = int(os.environ.get('GRAPHIFY_REBUILD_TIMEOUT', '600'))
|
|
if _timeout > 0 and hasattr(signal, 'SIGALRM'):
|
|
signal.signal(signal.SIGALRM, lambda *_: (_ for _ in ()).throw(TimeoutError(f'graphify rebuild exceeded {_timeout}s')))
|
|
signal.alarm(_timeout)
|
|
_force = os.environ.get('GRAPHIFY_FORCE', '').lower() in ('1', 'true', 'yes')
|
|
# post-checkout: branch switch can touch arbitrary files; full rebuild path
|
|
# (no changed_paths) is correct here. The flock inside _rebuild_code still
|
|
# prevents pile-ups when commit + checkout fire back-to-back.
|
|
_root = Path('.')
|
|
_saved = Path('graphify-out/.graphify_root')
|
|
if _saved.exists():
|
|
_txt = _saved.read_text(encoding='utf-8').strip()
|
|
if _txt:
|
|
_root = Path(_txt)
|
|
_rebuild_code(_root, force=_force)
|
|
except TimeoutError as exc:
|
|
print(f'[graphify] {exc}')
|
|
sys.exit(1)
|
|
except Exception as exc:
|
|
print(f'[graphify] Rebuild failed: {exc}')
|
|
sys.exit(1)
|
|
|
|
'''
|
|
_log = os.environ.get('GRAPHIFY_REBUILD_LOG') or os.path.join(os.path.expanduser('~'), '.cache', 'graphify-rebuild.log')
|
|
try:
|
|
os.makedirs(os.path.dirname(_log), exist_ok=True)
|
|
_out = open(_log, 'a', buffering=1, encoding='utf-8', errors='replace')
|
|
except OSError:
|
|
_out = subprocess.DEVNULL
|
|
_kw = dict(stdout=_out, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL, cwd=os.getcwd(), close_fds=True)
|
|
_cmd = [sys.executable, '-c', _src]
|
|
if os.name == 'nt':
|
|
_flags = 0x00000008 | 0x00000200 # DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP
|
|
try:
|
|
subprocess.Popen(_cmd, creationflags=_flags | 0x01000000, **_kw) # + CREATE_BREAKAWAY_FROM_JOB
|
|
except OSError:
|
|
subprocess.Popen(_cmd, creationflags=_flags, **_kw)
|
|
else:
|
|
subprocess.Popen(_cmd, start_new_session=True, **_kw)
|
|
"
|
|
# graphify-checkout-hook-end
|