mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-04 07:47:27 +01:00
Bins, careful/freeze hooks, setup, upgrade migrations, browse/src, design, ios-qa daemon, lib and scripts resolve the state root through bin/gstack-state-root.sh (bash) or lib/state-root.ts (TS). Bins source the twin and stop with a reinstall message when it is missing; hooks source it and never spawn gstack-paths. browse/src/config.ts and lib/cso/state.ts delegate to resolveStateRoot. Analytics writers and readers move together so the usage log stays one file. gstack-uninstall deletes state only at ~/.gstack, refuses (exit 2) when it resolves to /, $HOME or an ancestor, the checkout or the git root, and leaves any other resolved root in place with the removal command. Fixtures that copy single bins now copy the twin.
68 lines
2.4 KiB
Bash
Executable File
68 lines
2.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# gstack-specialist-stats — compute per-specialist hit rates from review history
|
|
# Usage: gstack-specialist-stats
|
|
#
|
|
# Reads all *-reviews.jsonl files across branches, parses specialist fields,
|
|
# and outputs hit rates. Tags specialists as GATE_CANDIDATE (0 findings in 10+
|
|
# dispatches) or NEVER_GATE (security, data-migration — insurance policy).
|
|
set -euo pipefail
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
eval "$("$SCRIPT_DIR/gstack-slug" 2>/dev/null)"
|
|
. "$(dirname "$0")/gstack-state-root.sh" 2>/dev/null || { echo "$0: cannot resolve the gstack state root: $(dirname "$0")/gstack-state-root.sh is missing. fix: reinstall with ./setup or /gstack-upgrade (docs/state-root.md)" >&2; exit 1; }
|
|
gstack_state_root_select
|
|
GSTACK_HOME="$_gstack_sr_root"
|
|
PROJECT_DIR="$GSTACK_HOME/projects/$SLUG"
|
|
|
|
if [ ! -d "$PROJECT_DIR" ]; then
|
|
echo "SPECIALIST_STATS: 0 reviews analyzed"
|
|
exit 0
|
|
fi
|
|
|
|
# Collect all review JSONL files (strip ---CONFIG--- and ---HEAD--- footers)
|
|
COMBINED=""
|
|
for f in "$PROJECT_DIR"/*-reviews.jsonl; do
|
|
[ -f "$f" ] || continue
|
|
COMBINED="$COMBINED$(sed '/^---/,$d' "$f" 2>/dev/null)
|
|
"
|
|
done
|
|
|
|
if [ -z "$COMBINED" ]; then
|
|
echo "SPECIALIST_STATS: 0 reviews analyzed"
|
|
exit 0
|
|
fi
|
|
|
|
printf '%s' "$COMBINED" | bun -e "
|
|
const lines = (await Bun.stdin.text()).trim().split('\n').filter(Boolean);
|
|
const NEVER_GATE = new Set(['security', 'data-migration']);
|
|
const stats = {};
|
|
let reviewed = 0;
|
|
|
|
for (const line of lines) {
|
|
try {
|
|
const e = JSON.parse(line);
|
|
if (!e.specialists) continue;
|
|
reviewed++;
|
|
for (const [name, info] of Object.entries(e.specialists)) {
|
|
if (!stats[name]) stats[name] = { dispatched: 0, findings: 0 };
|
|
if (info.dispatched) {
|
|
stats[name].dispatched++;
|
|
stats[name].findings += (info.findings || 0);
|
|
}
|
|
}
|
|
} catch {}
|
|
}
|
|
|
|
console.log('SPECIALIST_STATS: ' + reviewed + ' reviews analyzed');
|
|
const sorted = Object.entries(stats).sort((a, b) => a[0].localeCompare(b[0]));
|
|
for (const [name, s] of sorted) {
|
|
const pct = s.dispatched > 0 ? Math.round(100 * s.findings / s.dispatched) : 0;
|
|
let tag = '';
|
|
if (NEVER_GATE.has(name)) {
|
|
tag = ' [NEVER_GATE]';
|
|
} else if (s.dispatched >= 10 && s.findings === 0) {
|
|
tag = ' [GATE_CANDIDATE]';
|
|
}
|
|
console.log(name + ': ' + s.dispatched + '/' + reviewed + ' dispatched, ' + s.findings + ' findings (' + pct + '%)' + tag);
|
|
}
|
|
" 2>/dev/null || { echo "SPECIALIST_STATS: 0 reviews analyzed"; exit 0; }
|