Files
gstack/bin/gstack-review-log
garrytan cd7485b0b4 W1: guarded migration of every executable state-root site; uninstall deletes only ~/.gstack
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.
2026-09-30 06:38:50 +00:00

88 lines
3.9 KiB
Bash
Executable File

#!/usr/bin/env bash
# gstack-review-log — atomically log a review result
# Usage: gstack-review-log '{"skill":"...","timestamp":"...","status":"..."}'
#
# Before reading a diff: gstack-review-log --start review
# After that pass: gstack-review-log '{...,"completed":true,"converged":true}' --finish TOKEN
# Diff reviews get wtree only from a consumed, matching start capture on an
# unchanged tree. Completion/convergence are reviewer-reported, not proof that
# a model read the code. Caller-supplied binding fields are always discarded.
# Plan-tier rows retain their legacy binding behavior.
set -euo pipefail
export GIT_OPTIONAL_LOCKS=0
export GIT_CONFIG_PARAMETERS="${GIT_CONFIG_PARAMETERS:+$GIT_CONFIG_PARAMETERS }'core.fsmonitor=false' 'core.untrackedCache=false'"
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"
mkdir -p "$GSTACK_HOME/projects/$SLUG"
INPUT="${1:-}"
export GSTACK_REVIEW_DIR="$GSTACK_HOME/projects/$SLUG"
export GSTACK_REVIEW_REPO="$(git rev-parse --show-toplevel 2>/dev/null || true)"
export GSTACK_REVIEW_BRANCH="$(git symbolic-ref --short HEAD 2>/dev/null || git rev-parse HEAD 2>/dev/null || true)"
export GSTACK_REVIEW_LIB="$SCRIPT_DIR/../lib/review-evidence.ts"
case "$(uname -s)" in
MINGW*|MSYS*|CYGWIN*)
if command -v cygpath >/dev/null 2>&1; then
GSTACK_REVIEW_LIB="$(cygpath -m "$GSTACK_REVIEW_LIB")"
GSTACK_REVIEW_DIR="$(cygpath -m "$GSTACK_REVIEW_DIR")"
fi
;;
esac
export GSTACK_REVIEW_LOG="$GSTACK_REVIEW_DIR/$BRANCH-reviews.jsonl"
# Compute binding fields (best-effort; empty outside a git repo).
COMMIT_FULL=$(git rev-parse HEAD 2>/dev/null || true)
TREE=""
WTREE=""
DIRTY=""
if [ -n "$COMMIT_FULL" ]; then
TREE=$(git rev-parse 'HEAD^{tree}' 2>/dev/null || true)
WTREE=$("$SCRIPT_DIR/gstack-wtree" 2>/dev/null || true)
if [ -n "$(git status --porcelain -uno 2>/dev/null | head -1)" ]; then
DIRTY="true"
else
DIRTY="false"
fi
fi
export GSTACK_STAMP_COMMIT_FULL="$COMMIT_FULL" GSTACK_STAMP_TREE="$TREE" GSTACK_STAMP_WTREE="$WTREE" GSTACK_STAMP_DIRTY="$DIRTY"
if [ "$INPUT" = --start ]; then
GSTACK_REVIEW_SKILL="${2:-}" bun -e '
const { captureReviewStart } = await import(process.env.GSTACK_REVIEW_LIB);
console.log(captureReviewStart(process.env.GSTACK_REVIEW_SKILL));
'
exit $?
fi
if [ "$INPUT" = --check-shared-libs ] && [ "$#" -eq 2 ]; then
GSTACK_REVIEW_TOKEN="$2" bun -e '
const { checkSharedLibsReuse } = await import(process.env.GSTACK_REVIEW_LIB);
console.log(JSON.stringify(checkSharedLibsReuse(JSON.parse(await Bun.stdin.text()), process.env.GSTACK_REVIEW_TOKEN)));
'
exit $?
fi
if [ "$#" -ne 1 ] && { [ "$#" -ne 3 ] || [ "${2:-}" != --finish ]; }; then
echo 'Usage: gstack-review-log JSON [--finish TOKEN] | --start SKILL | --check-shared-libs TOKEN < finding.json' >&2
exit 1
fi
# Validate (reject malformed or injection attempts) AND stamp in one pass.
# Caller values for the binding keys are dropped before stamping.
STAMPED=$(printf '%s' "$INPUT" | GSTACK_REVIEW_TOKEN="${3:-}" bun -e "
const { bindReview } = await import(process.env.GSTACK_REVIEW_LIB);
const rec = JSON.parse(await Bun.stdin.text());
if (!rec || Array.isArray(rec) || typeof rec !== 'object') throw new Error('expected object');
console.log(JSON.stringify(bindReview(rec, process.env.GSTACK_REVIEW_TOKEN)));
" 2>/dev/null) || {
# Not valid JSON — refuse to append
echo "gstack-review-log: invalid JSON, skipping" >&2
exit 1
}
echo "$STAMPED" >> "$GSTACK_HOME/projects/$SLUG/$BRANCH-reviews.jsonl"
# gbrain-sync: enqueue for cross-machine sync (no-op if sync is off).
"$SCRIPT_DIR/gstack-brain-enqueue" "projects/$SLUG/$BRANCH-reviews.jsonl" 2>/dev/null &