mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-15 17:35:29 +02:00
Merge origin/main (v1.60.1.0) into garrytan/gstack-fix-wave
Semantic reconciliation with the parallel time-attack wave (#2264):
- careful: adopt main's anchored full-command whitelist (stricter — also
catches comment-hiding), re-apply this wave's two hardenings on top
(capital -[rR] in the flag cluster; exclude `(` and backtick from safe
targets so $()/backtick substitution cannot ride the whitelist). Union
of both waves' test batteries passes (main's test.each incl. comment
case + this wave's substitution/capital-R/FP-pin cases).
- one-way-doors: main landed the singular noun unification (a2a447a1);
keep this wave's superset (plural s? + --summary-stdin runtime wiring).
- gbrain-local-status: union of states — main's engine-locked (#2194,
exit 124 PGLite lock) + this wave's thin-client (#2051). --is-ok keeps
main's intent (engine-locked = STOP) and this wave's (thin-client =
usable). Test harness unions both fake behaviors.
- sync-gbrain/setup-gbrain tmpls: both Step 1.5 branches kept; generated
SKILL.md resolved via bun run gen:skill-docs (never hand-edited).
- VERSION/package.json -> 1.61.0.0 per bin/gstack-next-version (main took
1.60.1.0; PR #2470 claims 1.60.2.0). CHANGELOG: wave entry renumbered
1.61.0.0 on top of main's 1.60.1.0; careful/#2024 bullets updated to
describe the delta vs current main. TODOS: union.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+14
-11
@@ -134,12 +134,12 @@ lookup_default() {
|
||||
redact_repo_visibility) echo "" ;; # empty → fall through to gh/glab detection
|
||||
redact_prepush_hook) echo "false" ;;
|
||||
# Brain-aware planning (v1.48 / T5+T10+T16). Defaults documented inline:
|
||||
# brain_trust_policy@<hash> — unset on fresh install; setup-gbrain
|
||||
# brain_trust_policy@<endpoint-id> — unset on fresh install; setup-gbrain
|
||||
# writes 'personal' for local engines,
|
||||
# asks the user for remote-ambiguous.
|
||||
# salience_allowlist — empty falls through to
|
||||
# SALIENCE_DEFAULT_ALLOWLIST (D9).
|
||||
# user_slug_at_<hash> — empty triggers resolve-user-slug
|
||||
# user_slug_at_<endpoint-id> — empty triggers resolve-user-slug
|
||||
# fallback chain (D4 A3) on first call.
|
||||
brain_trust_policy*) echo "unset" ;;
|
||||
salience_allowlist) echo "" ;;
|
||||
@@ -260,14 +260,16 @@ resolve_user_slug() {
|
||||
case "${1:-}" in
|
||||
get)
|
||||
KEY="${2:?Usage: gstack-config get <key>}"
|
||||
# Validate key (alphanumeric + underscore + optional @<hash> suffix for
|
||||
# endpoint-namespaced keys introduced by the brain-aware planning layer)
|
||||
if ! printf '%s' "$KEY" | grep -qE '^[a-zA-Z0-9_]+(@[a-f0-9]+)?$'; then
|
||||
echo "Error: key must contain only alphanumeric characters, underscores, and an optional @<hex-hash> suffix" >&2
|
||||
# Validate key (alphanumeric + underscore + optional @<endpoint-id> suffix for
|
||||
# endpoint-namespaced keys introduced by the brain-aware planning layer).
|
||||
# Endpoint ids are sha8/sha16 hex for remote MCP URLs, or the literal
|
||||
# "local" for stdio/PGLite engines (see endpoint_hash).
|
||||
if ! printf '%s' "$KEY" | grep -qE '^[a-zA-Z0-9_]+(@[a-zA-Z0-9]+)?$'; then
|
||||
echo "Error: key must contain only alphanumeric characters, underscores, and an optional @<endpoint-id> suffix" >&2
|
||||
exit 1
|
||||
fi
|
||||
# Use literal match for keys containing @ (sha hashes), regex otherwise
|
||||
VALUE=$(grep -F "${KEY}:" "$CONFIG_FILE" 2>/dev/null | grep -E "^${KEY%@*}(@[a-f0-9]+)?:" | grep -F "${KEY}:" | tail -1 | awk '{print $2}' | tr -d '[:space:]' || true)
|
||||
# Use literal match for keys containing @ (endpoint ids), regex otherwise
|
||||
VALUE=$(grep -F "${KEY}:" "$CONFIG_FILE" 2>/dev/null | grep -E "^${KEY%@*}(@[a-zA-Z0-9]+)?:" | grep -F "${KEY}:" | tail -1 | awk '{print $2}' | tr -d '[:space:]' || true)
|
||||
if [ -z "$VALUE" ]; then
|
||||
VALUE=$(lookup_default "$KEY")
|
||||
fi
|
||||
@@ -276,9 +278,10 @@ case "${1:-}" in
|
||||
set)
|
||||
KEY="${2:?Usage: gstack-config set <key> <value>}"
|
||||
VALUE="${3:?Usage: gstack-config set <key> <value>}"
|
||||
# Validate key (alphanumeric + underscore + optional @<hash> suffix)
|
||||
if ! printf '%s' "$KEY" | grep -qE '^[a-zA-Z0-9_]+(@[a-f0-9]+)?$'; then
|
||||
echo "Error: key must contain only alphanumeric characters, underscores, and an optional @<hex-hash> suffix" >&2
|
||||
# Validate key (alphanumeric + underscore + optional @<endpoint-id> suffix).
|
||||
# Accepts hex hashes and the literal "local" from endpoint_hash.
|
||||
if ! printf '%s' "$KEY" | grep -qE '^[a-zA-Z0-9_]+(@[a-zA-Z0-9]+)?$'; then
|
||||
echo "Error: key must contain only alphanumeric characters, underscores, and an optional @<endpoint-id> suffix" >&2
|
||||
exit 1
|
||||
fi
|
||||
# Validate brain_trust_policy value domain (D4 / D11)
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
* "gstack_brain_sync_mode": "off"|"artifacts-only"|"full",
|
||||
* "gstack_brain_git": true|false,
|
||||
* "gstack_artifacts_remote": "https://..." | "",
|
||||
* "gbrain_local_status": "ok"|"no-cli"|"missing-config"|"broken-config"|"broken-db"|"timeout",
|
||||
* "gbrain_local_status": "ok"|"no-cli"|"missing-config"|"broken-config"|"broken-db"|"engine-locked"|"timeout"|"thin-client",
|
||||
* "gbrain_pooler_mode": "transaction"|"session"|null
|
||||
* }
|
||||
*
|
||||
|
||||
@@ -717,6 +717,7 @@ function dreamMarkerPid(): number | null {
|
||||
* missing-config → "no local engine; run /setup-gbrain to add local PGLite"
|
||||
* broken-config → "config file at ~/.gbrain/config.json is malformed; see /setup-gbrain Step 1.5"
|
||||
* broken-db → "config points at unreachable DB; see /setup-gbrain Step 1.5"
|
||||
* engine-locked → PGLite is busy; stop its holder or sync outside the live session
|
||||
* timeout → kept for Record totality; stages PROCEED on timeout (#1964)
|
||||
* via the gate's warnProbeTimeout path, never this skip.
|
||||
* thin-client → remote-HTTP MCP brain, no local engine by design (#2051);
|
||||
@@ -736,6 +737,8 @@ function skipStageForLocalStatus(
|
||||
"config at ~/.gbrain/config.json is malformed; see /setup-gbrain Step 1.5",
|
||||
"broken-db":
|
||||
"config points at unreachable DB; see /setup-gbrain Step 1.5",
|
||||
"engine-locked":
|
||||
"PGLite is busy (often held by gbrain serve); stop the holding process or run /sync-gbrain outside the live Claude session, then retry",
|
||||
"timeout":
|
||||
"engine probe timed out; raise GSTACK_GBRAIN_PROBE_TIMEOUT_MS if your pooler is slow",
|
||||
"thin-client":
|
||||
|
||||
Executable
+154
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env bash
|
||||
# gstack-ios-qa-regen — deterministically regenerate the iOS DebugBridge
|
||||
# package and the app-owned typed state accessors.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: gstack-ios-qa-regen --app-source <dir> --bridge-dir <dir>
|
||||
|
||||
--app-source Swift source tree to scan for @Observable state
|
||||
--bridge-dir Destination for the generated local DebugBridge package
|
||||
EOF
|
||||
}
|
||||
|
||||
APP_SOURCE=""
|
||||
BRIDGE_DIR=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--app-source)
|
||||
[[ $# -ge 2 ]] || { echo "gstack-ios-qa-regen: --app-source requires a value" >&2; exit 2; }
|
||||
APP_SOURCE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--bridge-dir)
|
||||
[[ $# -ge 2 ]] || { echo "gstack-ios-qa-regen: --bridge-dir requires a value" >&2; exit 2; }
|
||||
BRIDGE_DIR="$2"
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "gstack-ios-qa-regen: unknown argument: $1" >&2
|
||||
usage >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$APP_SOURCE" || -z "$BRIDGE_DIR" ]]; then
|
||||
echo "gstack-ios-qa-regen: both --app-source and --bridge-dir are required" >&2
|
||||
usage >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [[ ! -d "$APP_SOURCE" ]]; then
|
||||
echo "gstack-ios-qa-regen: app source directory not found: $APP_SOURCE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v bun >/dev/null 2>&1; then
|
||||
echo "gstack-ios-qa-regen: bun runtime not on PATH — install from https://bun.sh" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
GSTACK_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
TEMPLATE_DIR="$GSTACK_ROOT/ios-qa/templates"
|
||||
GENERATOR="$GSTACK_ROOT/ios-qa/scripts/gen-accessors.ts"
|
||||
VERSION_FILE="$GSTACK_ROOT/VERSION"
|
||||
GENERATED_DIR="$APP_SOURCE/DebugBridgeGenerated"
|
||||
|
||||
for required in "$GENERATOR" "$VERSION_FILE"; do
|
||||
if [[ ! -f "$required" ]]; then
|
||||
echo "gstack-ios-qa-regen: missing required gstack file: $required" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
TMP_FILE=""
|
||||
cleanup() {
|
||||
if [[ -n "$TMP_FILE" ]]; then
|
||||
rm -f "$TMP_FILE"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# Copy through a sibling temporary file so interruption never leaves a
|
||||
# truncated generated source. Preserve an unchanged destination byte-for-byte
|
||||
# and metadata-for-metadata on repeated runs.
|
||||
install_file() {
|
||||
local source="$1"
|
||||
local destination="$2"
|
||||
|
||||
if [[ ! -f "$source" ]]; then
|
||||
echo "gstack-ios-qa-regen: missing template: $source" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -f "$destination" ]] && cmp -s "$source" "$destination"; then
|
||||
return
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "$destination")"
|
||||
TMP_FILE="${destination}.tmp.$$"
|
||||
cp "$source" "$TMP_FILE"
|
||||
mv "$TMP_FILE" "$destination"
|
||||
TMP_FILE=""
|
||||
}
|
||||
|
||||
# Invalidate the completion marker before changing any package source. A
|
||||
# failed or interrupted regeneration must never look current to ios-sync.
|
||||
mkdir -p "$GENERATED_DIR"
|
||||
rm -f -- "$GENERATED_DIR/.gstack-version"
|
||||
|
||||
# This is intentionally an allowlist, not a template glob. Wiring belongs to
|
||||
# the consuming app and StateAccessor.swift is emitted by the parser below.
|
||||
install_file "$TEMPLATE_DIR/Package.swift.template" \
|
||||
"$BRIDGE_DIR/Package.swift"
|
||||
install_file "$TEMPLATE_DIR/StateServer.swift.template" \
|
||||
"$BRIDGE_DIR/Sources/DebugBridgeCore/StateServer.swift"
|
||||
install_file "$TEMPLATE_DIR/DebugBridgeManager.swift.template" \
|
||||
"$BRIDGE_DIR/Sources/DebugBridgeCore/DebugBridgeManager.swift"
|
||||
install_file "$TEMPLATE_DIR/Bridges.swift.template" \
|
||||
"$BRIDGE_DIR/Sources/DebugBridgeUI/Bridges.swift"
|
||||
install_file "$TEMPLATE_DIR/DebugOverlay.swift.template" \
|
||||
"$BRIDGE_DIR/Sources/DebugBridgeUI/DebugOverlay.swift"
|
||||
install_file "$TEMPLATE_DIR/DebugBridgeTouch.m.template" \
|
||||
"$BRIDGE_DIR/Sources/DebugBridgeTouch/DebugBridgeTouch.m"
|
||||
install_file "$TEMPLATE_DIR/DebugBridgeTouch.h.template" \
|
||||
"$BRIDGE_DIR/Sources/DebugBridgeTouch/include/DebugBridgeTouch.h"
|
||||
|
||||
# Older ios-sync versions copied the entire template set flat into the app's
|
||||
# generated-source directory. Those files can shadow the package modules or
|
||||
# make Xcode compile two harness implementations. Remove only the explicit
|
||||
# obsolete generated paths; handwritten app sources are never touched.
|
||||
for obsolete in \
|
||||
"$BRIDGE_DIR/DebugBridgeWiring.swift" \
|
||||
"$BRIDGE_DIR/StateAccessor.swift" \
|
||||
"$GENERATED_DIR/Package.swift" \
|
||||
"$GENERATED_DIR/StateServer.swift" \
|
||||
"$GENERATED_DIR/DebugBridgeManager.swift" \
|
||||
"$GENERATED_DIR/Bridges.swift" \
|
||||
"$GENERATED_DIR/DebugOverlay.swift" \
|
||||
"$GENERATED_DIR/DebugBridgeTouch.m" \
|
||||
"$GENERATED_DIR/DebugBridgeTouch.h" \
|
||||
"$GENERATED_DIR/DebugBridgeWiring.swift"
|
||||
do
|
||||
if [[ -f "$obsolete" || -L "$obsolete" ]]; then
|
||||
rm -f -- "$obsolete"
|
||||
echo "gstack-ios-qa-regen: removed obsolete generated file $obsolete"
|
||||
fi
|
||||
done
|
||||
|
||||
bun run "$GENERATOR" --input "$APP_SOURCE" --output "$GENERATED_DIR"
|
||||
|
||||
# Stamp only after successful accessor generation. ios-sync uses this marker
|
||||
# to distinguish a complete current install from an interrupted regeneration.
|
||||
install_file "$VERSION_FILE" "$GENERATED_DIR/.gstack-version"
|
||||
|
||||
echo "gstack-ios-qa-regen: bridge package ready at $BRIDGE_DIR"
|
||||
echo "gstack-ios-qa-regen: accessors ready at $GENERATED_DIR/StateAccessor.swift"
|
||||
@@ -12,13 +12,20 @@
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
# Compute SLUG directly (avoid eval of gstack-slug — branch names can contain shell metacharacters)
|
||||
# Keep the no-remote early exit before consulting gstack-slug. gstack-slug falls
|
||||
# back to the directory basename when origin is missing, which would incorrectly
|
||||
# make local-only repos eligible for classification.
|
||||
REMOTE_URL=$(git remote get-url origin 2>/dev/null || true)
|
||||
if [ -z "$REMOTE_URL" ]; then
|
||||
echo "REPO_MODE=unknown"
|
||||
exit 0
|
||||
fi
|
||||
SLUG=$(echo "$REMOTE_URL" | sed 's|.*[:/]\([^/]*/[^/]*\)\.git$|\1|;s|.*[:/]\([^/]*/[^/]*\)$|\1|' | tr '/' '-')
|
||||
# Consume the canonical cached slug from gstack-slug (sed, never eval — branch
|
||||
# names can contain shell metacharacters). Invoke from the git root so the
|
||||
# PWD-keyed slug cache matches other gstack project-state writers.
|
||||
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || true)
|
||||
[ -z "${REPO_ROOT:-}" ] && { echo "REPO_MODE=unknown"; exit 0; }
|
||||
SLUG=$(cd "$REPO_ROOT" && "$SCRIPT_DIR/gstack-slug" | sed -n 's/^SLUG=//p' | head -1)
|
||||
[ -z "${SLUG:-}" ] && { echo "REPO_MODE=unknown"; exit 0; }
|
||||
|
||||
# Validate: only allow known values (prevent shell injection via source <(...))
|
||||
|
||||
Reference in New Issue
Block a user