feat(graphify): threshold signal from 200 tracked code files, the banner informs and the user decides
lib/graphify-gate.sh counts tracked code files (graphify's AST extension set, vendored trees excluded) and, from 200 with no graphify-out/graph.json, prints one banner-sized line; session-start shows it with the /graphify hint. Nothing is built, installed or updated: the rule is the user's (BDR-097), grounded in the LRN-162 measurements (AST build 2.3 s, 0 tokens, a query 2 to 3k tokens). Doctrine section and plugin-advisor thresholds follow the same rule; graphify claude install stays rejected. Test: 11 checks. GRAPHIFY_MIN_CODE_FILES overrides the threshold.
This commit is contained in:
@@ -7,6 +7,14 @@ Format follows [Keep a Changelog](https://keepachangelog.com/).
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- **graphify threshold signal** — `lib/graphify-gate.sh` counts tracked code
|
||||
files (vendored trees excluded) and, from 200 with no
|
||||
`graphify-out/graph.json`, the session-start banner shows one line
|
||||
(`graphify? N code files ≥ 200, no graph`) plus the `/graphify` hint. It
|
||||
informs, the user decides: nothing is built or installed. Doctrine and the
|
||||
plugin-advisor thresholds follow the same rule; measured on a 295-file PHP
|
||||
project: AST build 2.3 s, zero LLM tokens, one query 2 to 3k tokens.
|
||||
Test `lib/tests/graphify-gate.test.sh` (11 checks).
|
||||
- **Branch deletion guard** — `gitflow_delete` (also `gitflow.sh delete
|
||||
<branch>`) is the only path that deletes a branch: it refuses `main` and
|
||||
`develop` (rc 6) and any branch not merged into develop or main (rc 5),
|
||||
|
||||
+4
-2
@@ -343,8 +343,10 @@ generation, not micro-tweaks.
|
||||
|
||||
## graphify
|
||||
|
||||
ALL rules apply only if `graphify-out/graph.json` exists — else read files
|
||||
directly.
|
||||
Threshold: graphify from 200 tracked code files, never below (banner line
|
||||
`graphify? N code files ≥ 200, no graph` informs, the user decides; never
|
||||
build or `graphify claude install` without that go). ALL rules below apply
|
||||
only if `graphify-out/graph.json` exists — else read files directly.
|
||||
- Codebase-wide question → `graphify query`; relationships → `path A B`;
|
||||
concept → `explain`. Scoped subgraph beats raw grep.
|
||||
- Known file / small task → read directly, no graphify.
|
||||
|
||||
@@ -79,9 +79,9 @@ Factors (weighted):
|
||||
**Score thresholds:**
|
||||
- **0-30% (simple)**: superpowers only. No gstack, no gsd, no ctx7, no graphify.
|
||||
_Examples: site vitrine, landing page, script CLI, simple CRUD._
|
||||
- **30-60% (moderate)**: + context7 if fast-libs, + graphify after implementation.
|
||||
- **30-60% (moderate)**: + context7 if fast-libs. graphify only once the codebase passes 200 tracked code files (session-start banner informs, the user decides — BDR-097), never at scaffold.
|
||||
_Examples: blog with auth, dashboard with charts, API with validation._
|
||||
- **60-85% (complex)**: + gstack if browser-QA, + gsd if multi-session, + graphify both passes.
|
||||
- **60-85% (complex)**: + gstack if browser-QA, + gsd if multi-session. graphify: same 200-file rule, likely reached — say so, do not pre-enable.
|
||||
_Examples: SaaS with billing, game with social features, e-commerce._
|
||||
- **85-100% (enterprise)**: all tools justified.
|
||||
_Examples: multi-service platform, real-time collab app, marketplace._
|
||||
|
||||
@@ -55,6 +55,14 @@ if [ -f "$_gf_lib" ] && git rev-parse --is-inside-work-tree >/dev/null 2>&1; the
|
||||
fi
|
||||
unset _gf_lib
|
||||
|
||||
# ── graphify threshold signal (BDR-097) ──
|
||||
# Informs, never acts: one banner line when the repo holds ≥ 200 tracked code
|
||||
# files and no graph. The user decides whether to build one.
|
||||
GRAPHIFY_HINT=""
|
||||
_gg_lib="$(dirname "${BASH_SOURCE[0]}")/../lib/graphify-gate.sh"
|
||||
if [ -f "$_gg_lib" ]; then GRAPHIFY_HINT=$(bash "$_gg_lib" "$PWD" 2>/dev/null); fi
|
||||
unset _gg_lib
|
||||
|
||||
# ── Toggle plugin detection ──
|
||||
|
||||
TOGGLE_ACTIVE=()
|
||||
@@ -215,6 +223,10 @@ if [ -n "$GF_REFRESHED" ]; then
|
||||
printf "│ 🪝 %-44s│\n" "${_gf_line:0:44}"
|
||||
unset _gf_line
|
||||
fi
|
||||
if [ -n "$GRAPHIFY_HINT" ]; then
|
||||
printf "│ 🕸️ %-44s│\n" "${GRAPHIFY_HINT:0:44}"
|
||||
printf "│ %-40s│\n" "→ /graphify (AST, seconds) — you decide"
|
||||
fi
|
||||
# CLAUDE.global.md line-count guard (anti-regression). BDR-062 supersedes
|
||||
# BDR-031's 275 target: 305 is the assumed reality (extraction done at
|
||||
# job1; further compression costs clarity > token gain) — warn past 320.
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env bash
|
||||
# graphify-gate.sh — deterministic "propose graphify" signal (BDR-097).
|
||||
#
|
||||
# Rule (user, 2026-09-24): graphify only from 200 tracked code files. Below,
|
||||
# grep + read is cheaper than a graph. The signal INFORMS, the user DECIDES:
|
||||
# nothing here builds, installs or updates a graph.
|
||||
#
|
||||
# Sourced (functions) or executed: `graphify-gate.sh [dir]` prints one short
|
||||
# line (banner-sized) and exits 0 when <dir>'s repo passes the threshold and has
|
||||
# no graphify-out/graph.json; silent, rc 1 otherwise. GRAPHIFY_MIN_CODE_FILES
|
||||
# overrides the threshold (tests).
|
||||
|
||||
GRAPHIFY_MIN_CODE_FILES="${GRAPHIFY_MIN_CODE_FILES:-200}"
|
||||
# Extensions graphify extracts by AST (tree-sitter): the proxy for "code file".
|
||||
GRAPHIFY_CODE_EXT='py|js|mjs|cjs|ts|tsx|jsx|vue|svelte|astro|php|go|rs|java|kt|c|h|cpp|hpp|cc|cs|rb|swift|scala|sh|bash|lua|sql'
|
||||
# Vendored trees sometimes committed; never the project's own code.
|
||||
GRAPHIFY_VENDOR_DIRS='vendor|node_modules|third_party|dist|build'
|
||||
|
||||
# graphify_code_file_count [dir] → tracked code files, vendored trees excluded.
|
||||
# Tracked only (git ls-files): gitignored deps and build output never count.
|
||||
graphify_code_file_count() {
|
||||
git -C "${1:-.}" ls-files 2>/dev/null \
|
||||
| grep -v -E "(^|/)($GRAPHIFY_VENDOR_DIRS)/" \
|
||||
| grep -E -c "\.($GRAPHIFY_CODE_EXT)$"
|
||||
}
|
||||
|
||||
# graphify_gate [dir] → "graphify? N code files ≥ T, no graph" + rc 0 when the
|
||||
# repo passes the threshold without a graph; silent rc 1 otherwise.
|
||||
graphify_gate() {
|
||||
local root n
|
||||
root=$(git -C "${1:-.}" rev-parse --show-toplevel 2>/dev/null) || return 1
|
||||
[ -f "$root/graphify-out/graph.json" ] && return 1 # graph exists — nothing to propose
|
||||
n=$(graphify_code_file_count "$root")
|
||||
[ "$n" -ge "$GRAPHIFY_MIN_CODE_FILES" ] || return 1
|
||||
printf 'graphify? %s code files ≥ %s, no graph\n' "$n" "$GRAPHIFY_MIN_CODE_FILES"
|
||||
}
|
||||
|
||||
if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
|
||||
set -uo pipefail
|
||||
graphify_gate "${1:-.}"
|
||||
fi
|
||||
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env bash
|
||||
# lib/tests/graphify-gate.test.sh — "propose graphify" threshold signal (BDR-097).
|
||||
set -u
|
||||
LIB="$(cd "$(dirname "$0")/../.." && pwd)/lib/graphify-gate.sh"
|
||||
WORK="$(mktemp -d)"; trap 'rm -rf "$WORK"' EXIT
|
||||
pass=0; fail=0
|
||||
check() { if [ "$2" = "$3" ]; then pass=$((pass+1)); else fail=$((fail+1));
|
||||
printf 'FAIL %s: got[%s] want[%s]\n' "$1" "$2" "$3"; fi; }
|
||||
# gate(dir) -> the signal line, or "silent"
|
||||
gate() { bash "$LIB" "$1" 2>/dev/null || echo silent; }
|
||||
has() { case "$1" in *"$2"*) echo yes ;; *) echo no ;; esac; }
|
||||
# mkrepo <name> <n php files at root> [<n php files under vendor/>]
|
||||
mkrepo() {
|
||||
local d="$WORK/$1" i
|
||||
git init -q "$d"; git -C "$d" config user.email t@t; git -C "$d" config user.name t
|
||||
git -C "$d" config core.hooksPath /dev/null
|
||||
for i in $(seq 1 "$2"); do echo "<?php // $i" > "$d/f$i.php"; done
|
||||
if [ "${3:-0}" -gt 0 ]; then
|
||||
mkdir -p "$d/vendor/lib"
|
||||
for i in $(seq 1 "$3"); do echo "<?php // v$i" > "$d/vendor/lib/v$i.php"; done
|
||||
fi
|
||||
git -C "$d" add -A; git -C "$d" commit -q -m init
|
||||
echo "$d"
|
||||
}
|
||||
|
||||
mkdir -p "$WORK/plain"
|
||||
check T1-not-a-repo "$(gate "$WORK/plain")" silent
|
||||
|
||||
d=$(mkrepo below 199)
|
||||
check T2-199-files-silent "$(gate "$d")" silent
|
||||
|
||||
d=$(mkrepo at 200)
|
||||
check T3-200-files-fires "$(has "$(gate "$d")" "200 code files")" yes
|
||||
check T3b-line-is-banner-sized "$([ "$(gate "$d" | wc -m)" -le 45 ] && echo yes || echo no)" yes
|
||||
mkdir -p "$d/graphify-out"; echo '{}' > "$d/graphify-out/graph.json"
|
||||
check T4-graph-exists-silent "$(gate "$d")" silent
|
||||
|
||||
d=$(mkrepo vendored 190 60)
|
||||
check T5-vendored-not-counted "$(gate "$d")" silent
|
||||
for i in $(seq 191 200); do echo "<?php // $i" > "$d/f$i.php"; done
|
||||
git -C "$d" add -A; git -C "$d" commit -q -m more
|
||||
check T5b-own-files-reach-200 "$(has "$(gate "$d")" "200 code files")" yes
|
||||
|
||||
d=$(mkrepo untracked 199)
|
||||
for i in $(seq 200 210); do echo "<?php // $i" > "$d/f$i.php"; done # left untracked
|
||||
check T6-untracked-not-counted "$(gate "$d")" silent
|
||||
|
||||
d=$(mkrepo tiny 10)
|
||||
check T7-threshold-override "$(has "$(GRAPHIFY_MIN_CODE_FILES=5 bash "$LIB" "$d" 2>/dev/null)" "≥ 5")" yes
|
||||
|
||||
d=$(mkrepo subdir 200); mkdir -p "$d/app/sub"
|
||||
check T8-from-subdirectory "$(has "$(gate "$d/app/sub")" "200 code files")" yes
|
||||
|
||||
d=$(mkrepo docs 10)
|
||||
for i in $(seq 1 300); do echo "# $i" > "$d/doc$i.md"; done
|
||||
git -C "$d" add -A; git -C "$d" commit -q -m docs
|
||||
check T9-non-code-not-counted "$(gate "$d")" silent
|
||||
|
||||
echo "PASS=$pass FAIL=$fail"
|
||||
[ "$fail" -eq 0 ]
|
||||
Reference in New Issue
Block a user