mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-05 00:03:15 +01:00
security: isolate every gstack Bun entry point from the audited checkout's .env and bunfig.toml
Bun auto-loads .env, .env.local and .env.<NODE_ENV> from its cwd and runs a cwd bunfig.toml preload before the program starts. gstack runs inside the repo it audits, so a committed .env could steer state roots, the claude binary or spend caps, and a bunfig preload ran arbitrary code in every bin and hook. Every bun shebang, shell and skill caller, TypeScript spawn and compiled build now disables both (--no-env-file --config=/dev/null, bunIsolationArgs(), --no-compile-autoload-dotenv/bunfig), with a ratchet and hostile-fixture regressions. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
9cf105ccff
commit
660484ee99
267 files changed
+984
-694
No files matched your search
@@ -298,3 +298,4 @@ Agents poll `eval:bg:*` logs for the `### gstack-detach EXIT=<code> ###` sentine
|
||||
- Every Codex run's verdict goes through `lib/outside-review-result.ts`; its reason codes live in `lib/gate-outcomes.ts`, each with a `docs/troubleshooting.md` anchor.
|
||||
- `test/module-size-ratchet.test.ts` keeps refactored owner modules at or under 800 lines (150 per function) and residual files from growing.
|
||||
- The `claude` CLI binary resolves via `lib/claude-bin.ts` (re-exported from `browse/src/claude-bin.ts` for browse internals; `Bun.which()` + `GSTACK_CLAUDE_BIN` override). Set `GSTACK_CLAUDE_BIN=wsl` plus `GSTACK_CLAUDE_BIN_ARGS='["claude"]'` to run Claude through WSL on Windows.
|
||||
- Every Bun gstack starts ignores the working directory's `.env` files and `bunfig.toml`: bun shebangs are `#!/usr/bin/env -S bun --no-env-file --config=/dev/null`, shell and skill callers pass `bun --no-env-file --config=/dev/null`, TypeScript spawns use `bunIsolationArgs()` from `lib/bun-isolation.ts`, and `bun build --compile` adds `--no-compile-autoload-dotenv --no-compile-autoload-bunfig`. `test/bun-entry-isolation.test.ts` enforces all four.
|
||||
@@ -1,5 +1,27 @@
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
**Security: the repository gstack runs in can no longer configure gstack's own programs.** Bun loads `.env`, `.env.local` and `.env.<NODE_ENV>` from the directory it starts in. It also runs any `preload` that a `bunfig.toml` there names, before the program's first line. gstack runs inside the repository it audits, so a committed or branch `.env` could set `GSTACK_STATE_ROOT`, `GSTACK_PROJECT_SLUG`, `GSTACK_CLAUDE_BIN` (an attacker-chosen binary then ran as "claude"), `GSTACK_SPEND_CAP_USD` or a provider base URL for every gstack bin, hook and skill step. A `bunfig.toml` preload ran arbitrary code in all of them. Every Bun that gstack starts now passes `--no-env-file` and an empty `--config`. Your shell environment works exactly as before. Found in review by GSTA-54.
|
||||
|
||||
### Security
|
||||
- **Every bun entry point starts isolated:** all 123 bun shebangs are now `#!/usr/bin/env -S bun --no-env-file --config=/dev/null`. `bun run <file>` ignores the shebang, so the callers carry the flags too: skill templates, generated skill docs, resolvers, hook wrappers, `setup` and the bash bins pass `bun --no-env-file --config=/dev/null` for every `run`, `-e`, `-p` and script path.
|
||||
- **TypeScript spawns use `bunIsolationArgs()`** from `lib/bun-isolation.ts`, which now owns browse's `emptyBunConfig()`. This covers `scriptInvocation` (how TypeScript launches gstack's own bins), restamp, qa-deadline, the timeline hook's repair child, gbrain sync's ingest child, the agents-digest regen and the Claude Code migration. Native Windows has no `/dev/null`, so there the helper passes a private zero-byte bunfig. Under Git Bash, MSYS rewrites the shebang's `/dev/null` to `nul`, which Bun 1.4.2 reads as an empty config. A windows-latest run verified both.
|
||||
- **Compiled builds:** `build:gates` and `scripts/build-app.sh` now pass `--no-compile-autoload-dotenv --no-compile-autoload-bunfig`, as `scripts/build.sh` already did. Without them, compiled binaries load both files.
|
||||
- **A ratchet and regressions:**
|
||||
- `test/bun-entry-isolation.test.ts` fails on any bun shebang, shell or skill caller, TypeScript spawn or `build --compile` that drops the flags.
|
||||
- `test/bun-dotenv-isolation.test.ts` runs the real bins in a fixture repo with a hostile `.env`, `.env.local` and `bunfig.toml`. The state root, slug, claude binary and pregate spend cap still come from the shell.
|
||||
- `test/bun-isolation-launch.test.ts` runs the shebang, bash and TypeScript launch forms on every free lane, Windows included.
|
||||
|
||||
### Changed
|
||||
- **`gstack-evidence run` no longer scrubs dotenv values from the child environment,** and `GSTACK_EVIDENCE_KEEP_DOTENV` is gone. Bun no longer loads those values into the wrapper, so the child sees your shell's environment, as it would if you ran the command by hand. The old value-equality scrub (#2652, @namtrok) also dropped shell-exported variables whose value matched the file.
|
||||
- **Raised SKILL.md size pins:** 80 pins in `test/fixtures/skill-md-size-allowlist.json` grew by 33 to 330 characters each, from the isolation flags alone (8,253 characters in total). For the same reason, ten parity caps in `test/helpers/carve-guards.ts` rose by the measured growth: ship, review, autoplan, spec, land-and-deploy and the three plan reviews (at most 132 skeleton bytes and 0.0016 union ratio).
|
||||
- **Directly executed `./scripts/*.ts` files no longer read gstack's own `.env`.** `bun run <script>` and the package scripts (`bun run test`, `bun run eval:bg:pr`) still load it.
|
||||
|
||||
### If you…
|
||||
- **…add a bin or a hook:** start it with the isolated shebang, and pass `bun --no-env-file --config=/dev/null` (or `bunIsolationArgs()` in TypeScript) wherever you start Bun. If you don't, the ratchet names the line.
|
||||
- **…relied on a repo `.env` to configure gstack:** export the variable in your shell or set it with `gstack-config`.
|
||||
|
||||
## [1.91.149.0] - 2026-10-19
|
||||
|
||||
**Paid eval cases can now run on the Codex host, and the Monday census runs two arms: Claude Opus 5.5 and Codex gpt-6.1-sol.** `EVALS_HOST=codex` runs the 52 adapter-only gate cases through a full Codex runtime per case. On its first pilot gpt-6.1-sol passed 48 of 52 with no retries, at $4.63 for the 52 cases, against $28.93 for the same cases on Fable (0.16×). Claude-only cases are skipped on Codex with a recorded reason and never count as failed. GSTA-49 TM2.
|
||||
|
||||
+1
-1
@@ -676,7 +676,7 @@ Send phase announcements and the final approval request (the brief) in the conve
|
||||
Resolve SNAPSHOT_TOOL once:
|
||||
```bash
|
||||
|
||||
bun -e 'console.log(require("fs").realpathSync(process.argv[1]))' "$HOME/.claude/skills/gstack/bin/gstack-autoplan-snapshot.ts"
|
||||
bun --no-env-file --config=/dev/null -e 'console.log(require("fs").realpathSync(process.argv[1]))' "$HOME/.claude/skills/gstack/bin/gstack-autoplan-snapshot.ts"
|
||||
```
|
||||
|
||||
Fresh RESTORE_PATH, beside its phase artifacts in the git-excluded `.gstack/tmp/autoplan/`:
|
||||
|
||||
@@ -9,7 +9,7 @@ _autoplan_backstop() {
|
||||
}
|
||||
trap _autoplan_backstop EXIT
|
||||
_AUTOPLAN_HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
|
||||
_AUTOPLAN_RESULT="$(bun "$_AUTOPLAN_HOOK_DIR/phase-publication-hook.ts")"
|
||||
_AUTOPLAN_RESULT="$(bun --no-env-file --config=/dev/null "$_AUTOPLAN_HOOK_DIR/phase-publication-hook.ts")"
|
||||
test -n "$_AUTOPLAN_RESULT"
|
||||
printf '%s\n' "$_AUTOPLAN_RESULT"
|
||||
_AUTOPLAN_DECIDED=1
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/** A native parent publication barrier at Autoplan's exact Read boundaries. */
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
|
||||
@@ -126,7 +126,7 @@ if [ "$_OUTSIDE_EXIT" -eq 124 ]; then
|
||||
fi
|
||||
cat "$_OUTSIDE_TMP/stderr" >&2 || { [ "$_OUTSIDE_EXIT" -ne 0 ] || _OUTSIDE_EXIT=1; }
|
||||
_OUTSIDE_RC=0
|
||||
bun "$HOME/.claude/skills/gstack/lib/outside-review-result.ts" --label 'Codex outside review' --exit "$_OUTSIDE_EXIT" --stderr "$_OUTSIDE_TMP/stderr" --events "$_OUTSIDE_TMP/events" review "$_OUTSIDE_TMP/text" || _OUTSIDE_RC=$?
|
||||
bun --no-env-file --config=/dev/null "$HOME/.claude/skills/gstack/lib/outside-review-result.ts" --label 'Codex outside review' --exit "$_OUTSIDE_EXIT" --stderr "$_OUTSIDE_TMP/stderr" --events "$_OUTSIDE_TMP/events" review "$_OUTSIDE_TMP/text" || _OUTSIDE_RC=$?
|
||||
case "$_OUTSIDE_RC" in
|
||||
0|3) ;;
|
||||
4) echo 'OUTSIDE_STATUS: unverified provider=codex host=claude'; exit 4 ;;
|
||||
|
||||
@@ -85,7 +85,7 @@ if [ "$_OUTSIDE_EXIT" -eq 124 ]; then
|
||||
fi
|
||||
cat "$_OUTSIDE_TMP/stderr" >&2 || { [ "$_OUTSIDE_EXIT" -ne 0 ] || _OUTSIDE_EXIT=1; }
|
||||
_OUTSIDE_RC=0
|
||||
bun "$HOME/.claude/skills/gstack/lib/outside-review-result.ts" --label 'Codex outside review' --exit "$_OUTSIDE_EXIT" --stderr "$_OUTSIDE_TMP/stderr" --events "$_OUTSIDE_TMP/events" review "$_OUTSIDE_TMP/text" || _OUTSIDE_RC=$?
|
||||
bun --no-env-file --config=/dev/null "$HOME/.claude/skills/gstack/lib/outside-review-result.ts" --label 'Codex outside review' --exit "$_OUTSIDE_EXIT" --stderr "$_OUTSIDE_TMP/stderr" --events "$_OUTSIDE_TMP/events" review "$_OUTSIDE_TMP/text" || _OUTSIDE_RC=$?
|
||||
case "$_OUTSIDE_RC" in
|
||||
0|3) ;;
|
||||
4) echo 'OUTSIDE_STATUS: unverified provider=codex host=claude'; exit 4 ;;
|
||||
|
||||
@@ -89,7 +89,7 @@ if [ "$_OUTSIDE_EXIT" -eq 124 ]; then
|
||||
fi
|
||||
cat "$_OUTSIDE_TMP/stderr" >&2 || { [ "$_OUTSIDE_EXIT" -ne 0 ] || _OUTSIDE_EXIT=1; }
|
||||
_OUTSIDE_RC=0
|
||||
bun "$HOME/.claude/skills/gstack/lib/outside-review-result.ts" --label 'Codex outside review' --exit "$_OUTSIDE_EXIT" --stderr "$_OUTSIDE_TMP/stderr" --events "$_OUTSIDE_TMP/events" review "$_OUTSIDE_TMP/text" || _OUTSIDE_RC=$?
|
||||
bun --no-env-file --config=/dev/null "$HOME/.claude/skills/gstack/lib/outside-review-result.ts" --label 'Codex outside review' --exit "$_OUTSIDE_EXIT" --stderr "$_OUTSIDE_TMP/stderr" --events "$_OUTSIDE_TMP/events" review "$_OUTSIDE_TMP/text" || _OUTSIDE_RC=$?
|
||||
case "$_OUTSIDE_RC" in
|
||||
0|3) ;;
|
||||
4) echo 'OUTSIDE_STATUS: unverified provider=codex host=claude'; exit 4 ;;
|
||||
|
||||
@@ -78,7 +78,7 @@ if [ "$_OUTSIDE_EXIT" -eq 124 ]; then
|
||||
fi
|
||||
cat "$_OUTSIDE_TMP/stderr" >&2 || { [ "$_OUTSIDE_EXIT" -ne 0 ] || _OUTSIDE_EXIT=1; }
|
||||
_OUTSIDE_RC=0
|
||||
bun "$HOME/.claude/skills/gstack/lib/outside-review-result.ts" --label 'Codex outside review' --exit "$_OUTSIDE_EXIT" --stderr "$_OUTSIDE_TMP/stderr" --events "$_OUTSIDE_TMP/events" review "$_OUTSIDE_TMP/text" || _OUTSIDE_RC=$?
|
||||
bun --no-env-file --config=/dev/null "$HOME/.claude/skills/gstack/lib/outside-review-result.ts" --label 'Codex outside review' --exit "$_OUTSIDE_EXIT" --stderr "$_OUTSIDE_TMP/stderr" --events "$_OUTSIDE_TMP/events" review "$_OUTSIDE_TMP/text" || _OUTSIDE_RC=$?
|
||||
case "$_OUTSIDE_RC" in
|
||||
0|3) ;;
|
||||
4) echo 'OUTSIDE_STATUS: unverified provider=codex host=claude'; exit 4 ;;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-approval-classes — changes that need the owner's approval (field list 2, W2).
|
||||
*
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-artifact — validate, print and acknowledge the unattended artifact contract.
|
||||
*
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-autoplan — the unattended /autoplan runner as an explicit, durable state machine (plan B2).
|
||||
*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/** Autoplan's blind reviewer inputs contain only the current implementation plan. */
|
||||
import { createHash } from 'node:crypto';
|
||||
import { linkSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, renameSync, rmdirSync, rmSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-autoplan-timing — wall time per autoplan phase and the pre-run estimate (plan B10).
|
||||
*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-banned-terms — terms a repository forbids outside named locations (plan E3).
|
||||
*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-brain-cache — three-tier cache for brain-aware planning skills.
|
||||
*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-brain-context-load — V1 retrieval surface (Lane C).
|
||||
*
|
||||
|
||||
@@ -576,7 +576,7 @@ PYEOF
|
||||
CONSENT_TS="$SCRIPT_DIR/../lib/transcript-consent.ts"
|
||||
transcript_policy() {
|
||||
command -v bun >/dev/null 2>&1 || return 2
|
||||
GSTACK_STATE_ROOT="$GSTACK_HOME" bun run "$CONSENT_TS" "$@"
|
||||
GSTACK_STATE_ROOT="$GSTACK_HOME" bun --no-env-file --config=/dev/null run "$CONSENT_TS" "$@"
|
||||
}
|
||||
|
||||
# 0 when staged transcript pages may sync at all.
|
||||
|
||||
@@ -58,7 +58,7 @@ probe() {
|
||||
else PROBE_OUT=$(cd "$ROOT" && node -e "$PROBE" </dev/null 2>&1); PROBE_RC=$?; fi
|
||||
elif command -v bun >/dev/null 2>&1; then
|
||||
ENGINE=bun
|
||||
PROBE_OUT=$(cd "$ROOT" && bun -e "$PROBE" </dev/null 2>&1); PROBE_RC=$?
|
||||
PROBE_OUT=$(cd "$ROOT" && bun --no-env-file --config=/dev/null -e "$PROBE" </dev/null 2>&1); PROBE_RC=$?
|
||||
else
|
||||
fail "neither node nor bun is on PATH to launch Chromium" "install Node.js (https://nodejs.org) or Bun, then re-run $0"
|
||||
fi
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
import { ciGateMain } from '../lib/ci-gate';
|
||||
|
||||
process.exit(await ciGateMain(process.argv.slice(2)));
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
// Claude-only outside-review runner. Prompts arrive on stdin, never argv.
|
||||
// codex_reviews gates the send (lib/outside-consent.ts); --user-invoked marks
|
||||
// a review the user asked for by name (/claude-code) and runs regardless.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-code-intelligence — pick a code-intelligence provider and use it to
|
||||
* index and search this repo. OPTIONAL: with nothing selected, gstack works
|
||||
|
||||
@@ -168,7 +168,7 @@ _gstack_codex_select_model() {
|
||||
[ -n "$_role" ] && set -- "$@" --role "$_role"
|
||||
[ -n "$_explicit" ] && set -- "$@" --explicit "$_explicit"
|
||||
[ -n "$_cwd" ] && set -- "$@" --cwd "$_cwd"
|
||||
_line=$(bun run "$_GSTACK_CODEX_BIN/../scripts/resolve-codex-generation-model.ts" "$@")
|
||||
_line=$(bun --no-env-file --config=/dev/null run "$_GSTACK_CODEX_BIN/../scripts/resolve-codex-generation-model.ts" "$@")
|
||||
_rc=$?
|
||||
_GSTACK_CODEX_SEL="${_line%%$'\t'*}"
|
||||
_rest="${_line#*$'\t'}"
|
||||
|
||||
@@ -101,7 +101,7 @@ IMPORTED=0
|
||||
SKIPPED_NO_ANSWER=0
|
||||
|
||||
for SESSION_FILE in "${SESSION_FILES[@]}"; do
|
||||
COUNT_LINE=$(SESSION_FILE_PATH="$SESSION_FILE" QLOG_BIN="$SCRIPT_DIR/gstack-question-log" bun -e '
|
||||
COUNT_LINE=$(SESSION_FILE_PATH="$SESSION_FILE" QLOG_BIN="$SCRIPT_DIR/gstack-question-log" bun --no-env-file --config=/dev/null -e '
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { spawnSync } = require("child_process");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
// gstack-context-bill — token bill-of-materials for an installed skills tree.
|
||||
// All behavior lives in lib/context-bill.ts; this is the CLI shim.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-contributor-mode — contributor-PR mode detection for /review (plan E3, decision D10).
|
||||
*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-decision-log — append a durable decision (or supersede/redact/compact it).
|
||||
*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-decision-search — read active decisions (the curated "what did we decide" view).
|
||||
*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
// gstack-design-approved — print the absolute path of the image an
|
||||
// approved.json names.
|
||||
//
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-design-detect — find, and run, an impeccable engine the USER installed.
|
||||
*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-design-md — inspect, convert, and read DESIGN.md in the open format.
|
||||
*
|
||||
|
||||
@@ -70,7 +70,7 @@ do_migrate() {
|
||||
TMPOUT=$(mktemp "$GSTACK_HOME/developer-profile.json.tmp.XXXXXX")
|
||||
trap 'rm -f "$TMPOUT"' EXIT
|
||||
|
||||
cat "$LEGACY_FILE" | bun -e "
|
||||
cat "$LEGACY_FILE" | bun --no-env-file --config=/dev/null -e "
|
||||
const lines = (await Bun.stdin.text()).trim().split('\n').filter(Boolean);
|
||||
const sessions = [];
|
||||
const signalsAcc = {};
|
||||
@@ -127,7 +127,7 @@ do_migrate() {
|
||||
mv "$LEGACY_FILE" "$LEGACY_FILE.migrated-$TS"
|
||||
|
||||
local COUNT
|
||||
COUNT=$(PROFILE_FILE_PATH="$PROFILE_FILE" bun -e "console.log(JSON.parse(require('fs').readFileSync(process.env.PROFILE_FILE_PATH,'utf-8')).sessions.length)" 2>/dev/null || echo "?")
|
||||
COUNT=$(PROFILE_FILE_PATH="$PROFILE_FILE" bun --no-env-file --config=/dev/null -e "console.log(JSON.parse(require('fs').readFileSync(process.env.PROFILE_FILE_PATH,'utf-8')).sessions.length)" 2>/dev/null || echo "?")
|
||||
echo "MIGRATE: ok — migrated $COUNT sessions from builder-profile.jsonl"
|
||||
}
|
||||
|
||||
@@ -184,7 +184,7 @@ do_log_session() {
|
||||
fi
|
||||
|
||||
# Validate: input must be parseable JSON with required fields (date, mode).
|
||||
if ! printf '%s' "$INPUT" | bun -e "
|
||||
if ! printf '%s' "$INPUT" | bun --no-env-file --config=/dev/null -e "
|
||||
const j = JSON.parse(await Bun.stdin.text());
|
||||
if (!j.date || !j.mode) process.exit(1);
|
||||
" 2>/dev/null; then
|
||||
@@ -197,7 +197,7 @@ do_log_session() {
|
||||
TMPOUT=$(mktemp "$GSTACK_HOME/developer-profile.json.tmp.XXXXXX")
|
||||
trap 'rm -f "$TMPOUT"' EXIT
|
||||
|
||||
PROFILE_FILE_PATH="$PROFILE_FILE" RECORD_INPUT="$INPUT" TMPOUT_PATH="$TMPOUT" bun -e "
|
||||
PROFILE_FILE_PATH="$PROFILE_FILE" RECORD_INPUT="$INPUT" TMPOUT_PATH="$TMPOUT" bun --no-env-file --config=/dev/null -e "
|
||||
const fs = require('fs');
|
||||
const entry = JSON.parse(process.env.RECORD_INPUT);
|
||||
if (!entry.ts) entry.ts = new Date().toISOString();
|
||||
@@ -244,7 +244,7 @@ do_reconcile() {
|
||||
TMPOUT=$(mktemp "$GSTACK_HOME/developer-profile.json.tmp.XXXXXX")
|
||||
trap 'rm -f "$TMPOUT"' EXIT
|
||||
|
||||
PROFILE_FILE_PATH="$PROFILE_FILE" PROJECTS_DIR="$GSTACK_HOME/projects" TMPOUT_PATH="$TMPOUT" bun -e "
|
||||
PROFILE_FILE_PATH="$PROFILE_FILE" PROJECTS_DIR="$GSTACK_HOME/projects" TMPOUT_PATH="$TMPOUT" bun --no-env-file --config=/dev/null -e "
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const profile = JSON.parse(fs.readFileSync(process.env.PROFILE_FILE_PATH, 'utf-8'));
|
||||
@@ -314,7 +314,7 @@ do_reconcile() {
|
||||
# -----------------------------------------------------------------------
|
||||
do_read() {
|
||||
ensure_profile
|
||||
cat "$PROFILE_FILE" | bun -e "
|
||||
cat "$PROFILE_FILE" | bun --no-env-file --config=/dev/null -e "
|
||||
const p = JSON.parse(await Bun.stdin.text());
|
||||
const sessions = p.sessions || [];
|
||||
|
||||
@@ -388,7 +388,7 @@ do_profile() {
|
||||
# -----------------------------------------------------------------------
|
||||
do_gap() {
|
||||
ensure_profile
|
||||
cat "$PROFILE_FILE" | bun -e "
|
||||
cat "$PROFILE_FILE" | bun --no-env-file --config=/dev/null -e "
|
||||
const p = JSON.parse(await Bun.stdin.text());
|
||||
const declared = p.declared || {};
|
||||
const inferred = (p.inferred && p.inferred.values) || {};
|
||||
@@ -417,7 +417,7 @@ do_derive() {
|
||||
fi
|
||||
|
||||
cd "$ROOT_DIR"
|
||||
PROFILE_FILE_PATH="$PROFILE_FILE" EVENTS_PATH="$EVENTS" bun -e "
|
||||
PROFILE_FILE_PATH="$PROFILE_FILE" EVENTS_PATH="$EVENTS" bun --no-env-file --config=/dev/null -e "
|
||||
import('./scripts/question-registry.ts').then(async (regmod) => {
|
||||
const sigmod = await import('./scripts/psychographic-signals.ts');
|
||||
const fs = require('fs');
|
||||
@@ -504,7 +504,7 @@ do_trace() {
|
||||
return 0
|
||||
fi
|
||||
cd "$ROOT_DIR"
|
||||
EVENTS_PATH="$EVENTS" TRACE_DIM="$DIM" bun -e "
|
||||
EVENTS_PATH="$EVENTS" TRACE_DIM="$DIM" bun --no-env-file --config=/dev/null -e "
|
||||
import('./scripts/question-registry.ts').then(async (regmod) => {
|
||||
const sigmod = await import('./scripts/psychographic-signals.ts');
|
||||
const fs = require('fs');
|
||||
@@ -542,7 +542,7 @@ do_trace() {
|
||||
# -----------------------------------------------------------------------
|
||||
do_check_mismatch() {
|
||||
ensure_profile
|
||||
cat "$PROFILE_FILE" | bun -e "
|
||||
cat "$PROFILE_FILE" | bun --no-env-file --config=/dev/null -e "
|
||||
const p = JSON.parse(await Bun.stdin.text());
|
||||
const declared = p.declared || {};
|
||||
const inferred = (p.inferred && p.inferred.values) || {};
|
||||
@@ -586,7 +586,7 @@ do_narrative() {
|
||||
do_vibe() {
|
||||
ensure_profile
|
||||
cd "$ROOT_DIR"
|
||||
cat "$PROFILE_FILE" | PROFILE_DATA="$(cat "$PROFILE_FILE")" bun -e "
|
||||
cat "$PROFILE_FILE" | PROFILE_DATA="$(cat "$PROFILE_FILE")" bun --no-env-file --config=/dev/null -e "
|
||||
import('./scripts/archetypes.ts').then(async (mod) => {
|
||||
const p = JSON.parse(process.env.PROFILE_DATA);
|
||||
const dims = (p.inferred && p.inferred.values) || {
|
||||
|
||||
@@ -57,7 +57,7 @@ if [ ! -f "$PROPOSAL_FILE" ]; then
|
||||
fi
|
||||
|
||||
if [ "$ACTION" = "list" ]; then
|
||||
PROPOSAL_FILE_PATH="$PROPOSAL_FILE" bun -e '
|
||||
PROPOSAL_FILE_PATH="$PROPOSAL_FILE" bun --no-env-file --config=/dev/null -e '
|
||||
const fs = require("fs");
|
||||
const p = JSON.parse(fs.readFileSync(process.env.PROPOSAL_FILE_PATH, "utf-8"));
|
||||
const proposals = p.proposals || [];
|
||||
@@ -100,7 +100,7 @@ MEMORY_FILE_PATH="$MEMORY_FILE" \
|
||||
PROFILE_FILE_PATH="$PROFILE_FILE" \
|
||||
PREF_BIN="$SCRIPT_DIR/gstack-question-preference" \
|
||||
GBRAIN_PUBLISHED="$GBRAIN_PUBLISHED" \
|
||||
bun -e '
|
||||
bun --no-env-file --config=/dev/null -e '
|
||||
const fs = require("fs");
|
||||
const { spawnSync } = require("child_process");
|
||||
const idx = parseInt(process.env.PROPOSAL_IDX, 10);
|
||||
|
||||
@@ -59,7 +59,7 @@ esac
|
||||
# --- Status subcommand --------------------------------------------------
|
||||
|
||||
if [ "$MODE" = "status" ]; then
|
||||
COST_LOG_PATH="$COST_LOG" SLUG_PATH="$SLUG" bun -e '
|
||||
COST_LOG_PATH="$COST_LOG" SLUG_PATH="$SLUG" bun --no-env-file --config=/dev/null -e '
|
||||
const fs = require("fs");
|
||||
const slug = process.env.SLUG_PATH;
|
||||
const path = process.env.COST_LOG_PATH;
|
||||
@@ -98,7 +98,7 @@ if [ ! -f "$LOG_FILE" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
EVENTS_JSON=$(LOG_FILE_PATH="$LOG_FILE" bun -e '
|
||||
EVENTS_JSON=$(LOG_FILE_PATH="$LOG_FILE" bun --no-env-file --config=/dev/null -e '
|
||||
const fs = require("fs");
|
||||
const lines = fs.readFileSync(process.env.LOG_FILE_PATH, "utf-8").trim().split("\n").filter(Boolean);
|
||||
const out = [];
|
||||
@@ -119,7 +119,7 @@ EVENTS_JSON=$(LOG_FILE_PATH="$LOG_FILE" bun -e '
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
')
|
||||
|
||||
EVENT_COUNT=$(printf '%s' "$EVENTS_JSON" | bun -e 'const a = JSON.parse(await Bun.stdin.text()); console.log(a.length);')
|
||||
EVENT_COUNT=$(printf '%s' "$EVENTS_JSON" | bun --no-env-file --config=/dev/null -e 'const a = JSON.parse(await Bun.stdin.text()); console.log(a.length);')
|
||||
if [ "$EVENT_COUNT" -eq 0 ]; then
|
||||
echo "NO_FREE_TEXT: nothing to distill"
|
||||
exit 0
|
||||
@@ -172,7 +172,7 @@ if [ "$MODE" = "dry-run" ]; then
|
||||
echo "$DISTILL_PROMPT"
|
||||
echo
|
||||
echo "=== EVENTS ($EVENT_COUNT) ==="
|
||||
echo "$EVENTS_JSON" | bun -e 'console.log(JSON.stringify(JSON.parse(await Bun.stdin.text()), null, 2));'
|
||||
echo "$EVENTS_JSON" | bun --no-env-file --config=/dev/null -e 'console.log(JSON.stringify(JSON.parse(await Bun.stdin.text()), null, 2));'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -196,7 +196,7 @@ fi
|
||||
RESULT=$(EVENTS_JSON="$EVENTS_JSON" DISTILL_PROMPT="$DISTILL_PROMPT" \
|
||||
PROPOSAL_FILE_PATH="$PROPOSAL_FILE" LOG_FILE_PATH="$LOG_FILE" \
|
||||
ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
|
||||
bun --cwd "$ROOT_DIR" -e '
|
||||
bun --no-env-file --config=/dev/null --cwd "$ROOT_DIR" -e '
|
||||
const fs = require("fs");
|
||||
const Anthropic = require("@anthropic-ai/sdk").default;
|
||||
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env -S bun --no-env-file run
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-docs-candidate — /ship's documentation-audit snapshot and its later check.
|
||||
*
|
||||
|
||||
+1
-1
@@ -512,7 +512,7 @@ check_windows_bash() {
|
||||
row warn "windows bash" "not checked: bun not found" "install Bun (see the bun row), then rerun $BIN/gstack-doctor"
|
||||
return
|
||||
fi
|
||||
out=$(bun "$BIN/gstack-windows-bash.ts" ${GSTACK_DOCTOR_BASH_PROBE:+--probe "$GSTACK_DOCTOR_BASH_PROBE"} </dev/null 2>&1)
|
||||
out=$(bun --no-env-file --config=/dev/null "$BIN/gstack-windows-bash.ts" ${GSTACK_DOCTOR_BASH_PROBE:+--probe "$GSTACK_DOCTOR_BASH_PROBE"} </dev/null 2>&1)
|
||||
rc=$?
|
||||
out=$(printf '%s\n' "$out" | tr -d '\000-\010\013-\037')
|
||||
bash=$(printf '%s\n' "$out" | sed -n 's/^WINDOWS_BASH: //p' | head -1)
|
||||
|
||||
@@ -213,7 +213,7 @@ _dc_check_browser() {
|
||||
out=$(cd "$ROOT" && { command -v timeout >/dev/null 2>&1 && timeout 90 node -e "$script" </dev/null 2>&1 || node -e "$script" </dev/null 2>&1; }); rc=$?
|
||||
elif command -v bun >/dev/null 2>&1; then
|
||||
engine=bun
|
||||
out=$(cd "$ROOT" && bun -e "$script" </dev/null 2>&1); rc=$?
|
||||
out=$(cd "$ROOT" && bun --no-env-file --config=/dev/null -e "$script" </dev/null 2>&1); rc=$?
|
||||
else
|
||||
_dc_row FAIL browser "neither node nor bun is on PATH to launch Chromium" "install Node.js (https://nodejs.org) or Bun, then $fix" BROWSER_UNAVAILABLE
|
||||
return
|
||||
@@ -329,7 +329,7 @@ _dc_check_pins() {
|
||||
return
|
||||
fi
|
||||
node=$(node --version </dev/null 2>/dev/null | clamp 'v0-9.' 20); node="${node#v}"
|
||||
out=$(bun "$BIN/gstack-runtime-pins.ts" --project "$project" ${_dc_bun:+--bun "$_dc_bun"} ${node:+--node "$node"} </dev/null 2>&1); rc=$?
|
||||
out=$(bun --no-env-file --config=/dev/null "$BIN/gstack-runtime-pins.ts" --project "$project" ${_dc_bun:+--bun "$_dc_bun"} ${node:+--node "$node"} </dev/null 2>&1); rc=$?
|
||||
verdict=$(printf '%s\n' "$out" | sed -n 's/^PINS_VERDICT: //p' | tail -1 | clamp 'a-z' 8)
|
||||
summary=$(printf '%s\n' "$out" | sed -n 's/^PIN_SUMMARY: //p' | tr '\n' '|' | sed 's/|$//; s/|/; /g')
|
||||
case "$verdict" in
|
||||
@@ -357,7 +357,7 @@ _dc_check_pins() {
|
||||
fi
|
||||
fi
|
||||
if [ -n "$_dc_ln_dir" ] && [ "$_dc_ln_ver" != "$_dc_bun" ] \
|
||||
&& bun "$BIN/gstack-runtime-pins.ts" --project "$project" --bun "$_dc_ln_ver" ${node:+--node "$node"} </dev/null 2>/dev/null | grep -q '^PINS_VERDICT: pass'; then
|
||||
&& bun --no-env-file --config=/dev/null "$BIN/gstack-runtime-pins.ts" --project "$project" --bun "$_dc_ln_ver" ${node:+--node "$node"} </dev/null 2>/dev/null | grep -q '^PINS_VERDICT: pass'; then
|
||||
note="; bun $_dc_ln_ver found at $_dc_ln_dir (not on PATH) satisfies them" fix="$_dc_ln_fix"
|
||||
fi
|
||||
_dc_row FAIL pins "$failline$note" "$fix" PROJECT_PIN_MISMATCH ;;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-egress — the auditor's view of the receipts ledger.
|
||||
*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
// gstack-egress-receipt — bun script that BRIDGES shell callers (the bash
|
||||
// egress sinks: gstack-telemetry-sync, gstack-update-check, gstack-brain-sync,
|
||||
// and the sourced helpers in gstack-egress-lib.sh) into lib/egress-receipt.ts.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
import { dxErrorCaptureMain } from '../lib/dx-error-capture';
|
||||
|
||||
process.exit(await dxErrorCaptureMain(process.argv.slice(2)));
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-eval-plan — the tier-1 machinery behind /eval-plan (plan E1).
|
||||
*
|
||||
|
||||
+2
-99
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-evidence — verification-evidence ledger: the mechanical arm of /ship's
|
||||
* IRON LAW ("no completion claims without fresh verification evidence").
|
||||
@@ -269,97 +269,6 @@ function openLog(logsDir: string, label: string, cmdSha: string): { fd: number;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bun AUTO-LOADS `.env`, `.env.<NODE_ENV>` and `.env.local` from the working
|
||||
* directory into `process.env`. This file has a `#!/usr/bin/env bun` shebang, so
|
||||
* every command it spawns inherits those variables — and a repo `.env.local`
|
||||
* routinely holds PRODUCTION credentials.
|
||||
*
|
||||
* Two things go wrong, and the second one is worse than the leak:
|
||||
*
|
||||
* 1. Secrets reach a child process that would not have had them. `npm test` run
|
||||
* by hand in the same shell sees none of this; run through the wrapper it sees
|
||||
* all of it.
|
||||
* 2. THE COMMAND UNDER TEST BEHAVES DIFFERENTLY, so the evidence ledger
|
||||
* certifies a run that is not the run CI performs. Observed in a Next.js repo
|
||||
* 2026-08-20: four tests failed 4/4 through the wrapper and passed 5/5 without
|
||||
* it, because app code branched on env vars only the wrapper supplied. The
|
||||
* wrapper exists to record trustworthy evidence, so silently changing the
|
||||
* environment defeats its whole purpose.
|
||||
*
|
||||
* Verified against bun 1.3.11: a dotenv file does NOT override a variable the
|
||||
* shell already exported (the shell's value wins). So a key whose live value is
|
||||
* exactly the dotenv file's value was injected by bun, and dropping it restores
|
||||
* the environment the user's own shell would have given the command.
|
||||
*
|
||||
* Escape hatch: GSTACK_EVIDENCE_KEEP_DOTENV=1 keeps the old behaviour for anyone
|
||||
* who really does want the wrapper to supply .env values.
|
||||
*/
|
||||
const BUN_DOTENV_FILES = (): string[] => {
|
||||
const nodeEnv = process.env.NODE_ENV;
|
||||
// bun's documented precedence, lowest first. `.env.local` is skipped by bun
|
||||
// when NODE_ENV is "test"; mirror that rather than guessing.
|
||||
const files = [".env"];
|
||||
if (nodeEnv) files.push(`.env.${nodeEnv}`);
|
||||
if (nodeEnv !== "test") files.push(".env.local");
|
||||
return files;
|
||||
};
|
||||
|
||||
/** Minimal dotenv reader: KEY=VALUE, one per line. Quotes stripped, comments and
|
||||
* `export ` prefixes tolerated. Multi-line values are not parsed — a key we fail
|
||||
* to parse is simply left in the child env, which is the safe direction.
|
||||
* Known limitation: bun EXPANDS ${VAR} references inside dotenv values, but this
|
||||
* reader compares the raw file text, so an expanded live value never matches and
|
||||
* that key is left in the child env — the pre-scrub behavior persists for those
|
||||
* keys (fails open, same safe direction as above). */
|
||||
function parseDotenv(text: string): Map<string, string> {
|
||||
const out = new Map<string, string>();
|
||||
for (const raw of text.split(/\r?\n/)) {
|
||||
const line = raw.trim();
|
||||
if (!line || line.startsWith("#")) continue;
|
||||
const eq = line.indexOf("=");
|
||||
if (eq <= 0) continue;
|
||||
let key = line.slice(0, eq).trim();
|
||||
if (key.startsWith("export ")) key = key.slice(7).trim();
|
||||
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue;
|
||||
let val = line.slice(eq + 1).trim();
|
||||
if ((val.startsWith('"') && val.endsWith('"') && val.length > 1) ||
|
||||
(val.startsWith("'") && val.endsWith("'") && val.length > 1)) {
|
||||
val = val.slice(1, -1);
|
||||
}
|
||||
out.set(key, val);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** process.env minus the variables bun injected from the repo's dotenv files.
|
||||
* Returns the scrubbed env and the KEY NAMES removed (never the values). */
|
||||
function childEnv(cwd: string): { env: Record<string, string>, scrubbed: string[] } {
|
||||
const env: Record<string, string> = {};
|
||||
for (const [k, v] of Object.entries(process.env)) if (v !== undefined) env[k] = v;
|
||||
if (process.env.GSTACK_EVIDENCE_KEEP_DOTENV === "1") return { env, scrubbed: [] };
|
||||
|
||||
const scrubbed: string[] = [];
|
||||
for (const file of BUN_DOTENV_FILES()) {
|
||||
let text: string;
|
||||
try {
|
||||
text = readFileSync(join(cwd, file), "utf-8");
|
||||
} catch {
|
||||
continue; // absent or unreadable — nothing to scrub from it
|
||||
}
|
||||
for (const [k, v] of parseDotenv(text)) {
|
||||
// Only when the live value IS the file's value. A different live value means
|
||||
// the shell exported its own and bun left it alone, so it is genuinely the
|
||||
// user's environment and must survive.
|
||||
if (env[k] !== undefined && env[k] === v) {
|
||||
delete env[k];
|
||||
if (!scrubbed.includes(k)) scrubbed.push(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
return { env, scrubbed };
|
||||
}
|
||||
|
||||
async function cmdRun(argv: string[]): Promise<number> {
|
||||
let label = "default";
|
||||
const li = argv.indexOf("--label");
|
||||
@@ -395,15 +304,9 @@ async function cmdRun(argv: string[]): Promise<number> {
|
||||
let exitCode: number;
|
||||
let proc: ReturnType<typeof Bun.spawn> | undefined;
|
||||
try {
|
||||
const { env: spawnEnv, scrubbed } = childEnv(process.cwd());
|
||||
if (scrubbed.length > 0) {
|
||||
// Names only. Printing values here would defeat the point.
|
||||
warn(`scrubbed ${scrubbed.length} bun-injected dotenv var(s) from the child env: ${scrubbed.join(", ")} `
|
||||
+ `(GSTACK_EVIDENCE_KEEP_DOTENV=1 to keep them)`);
|
||||
}
|
||||
if (shell && !shell.ok) throw new Error(shell.error);
|
||||
const spawnArgv = shell?.ok ? [shell.cmd, ...shell.argv] : cmdArgv;
|
||||
proc = Bun.spawn(spawnArgv, { stdin: "inherit", stdout: "pipe", stderr: "pipe", env: spawnEnv });
|
||||
proc = Bun.spawn(spawnArgv, { stdin: "inherit", stdout: "pipe", stderr: "pipe" });
|
||||
} catch (e: any) {
|
||||
// Spawn failure (ENOENT on argv-direct form): record exit 127, propagate 127.
|
||||
exitCode = 127;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-gate — render and parse the one gate list (plan B9).
|
||||
*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-gate-diff — list the gate edits in a candidate so a human can
|
||||
* disposition each one (W1 of docs/designs/HONEST_WORK_GATE_INTEGRITY.md).
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env -S bun run
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-gbrain-detect — emit current gbrain/gstack-brain state as JSON.
|
||||
*
|
||||
|
||||
@@ -100,7 +100,7 @@ fi
|
||||
# bun, which this installer already requires. It used to need jq, which it
|
||||
# never checked for, so a successful install reported failure (#1675).
|
||||
pkg_field() {
|
||||
GSTACK_PKG_JSON="$1" GSTACK_PKG_FIELD="$2" bun -e '
|
||||
GSTACK_PKG_JSON="$1" GSTACK_PKG_FIELD="$2" bun --no-env-file --config=/dev/null -e '
|
||||
try {
|
||||
const pkg = JSON.parse(require("fs").readFileSync(process.env.GSTACK_PKG_JSON, "utf8"));
|
||||
const v = process.env.GSTACK_PKG_FIELD.split(".").reduce((o, k) => (o == null ? o : o[k]), pkg);
|
||||
@@ -198,7 +198,7 @@ _bun_exe_hint() {
|
||||
[ "$IS_WINDOWS" -eq 1 ] || return 0
|
||||
command -v bun.exe >/dev/null 2>&1 && return 0
|
||||
local real_bun
|
||||
real_bun=$(bun -e 'console.log(process.execPath)' 2>/dev/null | tr -d '\r' || true)
|
||||
real_bun=$(bun --no-env-file --config=/dev/null -e 'console.log(process.execPath)' 2>/dev/null | tr -d '\r' || true)
|
||||
echo " detected: bun was installed via npm — bun.exe is NOT on %PATH%, and the gbrain.exe shim needs it." >&2
|
||||
if [ -n "$real_bun" ]; then
|
||||
echo " fix: add bun.exe's directory to PATH (persist it in your shell profile):" >&2
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
import { readFileSync, realpathSync, statSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
|
||||
@@ -222,7 +222,7 @@ ensure_worktree() {
|
||||
# is not JSON.
|
||||
source_local_path() {
|
||||
gbrain sources list --json 2>/dev/null \
|
||||
| GB_SOURCE_ID="$1" bun -e '
|
||||
| GB_SOURCE_ID="$1" bun --no-env-file --config=/dev/null -e '
|
||||
const raw = await Bun.stdin.text();
|
||||
try {
|
||||
const s = (JSON.parse(raw).sources || []).find((x) => x.id === process.env.GB_SOURCE_ID);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env -S bun run
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-gbrain-supabase-provision — Supabase Management API wrapper for
|
||||
* /setup-gbrain path 2a (auto-provision). Thin entry: all logic lives in
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-gbrain-sync — V1 unified sync verb.
|
||||
*
|
||||
@@ -51,6 +51,7 @@ import {
|
||||
} from "../lib/gbrain-sources";
|
||||
import { detectAutopilot, decideSourceRemove, decideCodeSync } from "../lib/gbrain-guards";
|
||||
import { writeReceipt } from "../lib/egress-receipt";
|
||||
import { bunIsolationArgs } from "../lib/bun-isolation";
|
||||
import { dbUnreachableReason, localEngineStatus, localEngineStatusDetail, type LocalEngineStatus } from "../lib/gbrain-local-status";
|
||||
import { buildGbrainEnv, spawnGbrain, spawnGbrainAsync, execGbrainJson, NEEDS_SHELL_ON_WINDOWS, bashScriptInvocation } from "../lib/gbrain-exec";
|
||||
import { repoPolicyTier as sharedRepoPolicyTier } from "../lib/gbrain-repo-policy-client";
|
||||
@@ -1611,7 +1612,7 @@ function runMemoryIngest(args: CliArgs, previous: SyncState, consent: Transcript
|
||||
}
|
||||
|
||||
const ingestPath = join(import.meta.dir, "gstack-memory-ingest.ts");
|
||||
const ingestArgs = ["run", ingestPath];
|
||||
const ingestArgs = [...bunIsolationArgs(), "run", ingestPath];
|
||||
if (args.mode === "full") ingestArgs.push("--bulk");
|
||||
else ingestArgs.push("--incremental");
|
||||
if (args.mode === "full" && consent.affirmative && consent.window !== "recent") ingestArgs.push("--all-history");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-global-discover — Discover AI coding sessions across Claude Code, Codex CLI, and Gemini CLI.
|
||||
* Resolves each session's working directory to a git repo, deduplicates by normalized remote URL,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-greptile-early — /ship's early Greptile review (detect, consent, open, wait).
|
||||
*
|
||||
|
||||
@@ -173,7 +173,7 @@ if [ -n "$ENTRIES" ]; then
|
||||
# up below (lib/path-spelling.ts shellPath; test/path-spelling.test.ts keeps
|
||||
# the two equal).
|
||||
# shellcheck disable=SC2086 # newline-split list (IFS is set to a newline below)
|
||||
(cd "$ROOT" && IFS=$NL && "$BUN" -e '
|
||||
(cd "$ROOT" && IFS=$NL && "$BUN" --no-env-file --config=/dev/null -e '
|
||||
const shellPath = (value) => process.platform === "win32"
|
||||
? String(value).replace(/\\/g, "/").replace(/^([A-Za-z]):(?=\/|$)/, (_, drive) => `/${drive.toLowerCase()}`)
|
||||
: String(value);
|
||||
|
||||
@@ -159,7 +159,7 @@ gstack_install_admission() {
|
||||
v_bun=$(gstack_install_verdict_field "$file" bun_path)
|
||||
if [ -z "$detail" ] && [ -n "$v_bun" ]; then
|
||||
running=$(gstack_realpath "$where" 2>/dev/null)
|
||||
_gstack_bun_is_shim "$where" && running=$(bun -p process.execPath </dev/null 2>/dev/null | head -1)
|
||||
_gstack_bun_is_shim "$where" && running=$(bun --no-env-file --config=/dev/null -p process.execPath </dev/null 2>/dev/null | head -1)
|
||||
if [ -n "$running" ] && [ "$running" != "$v_bun" ]; then
|
||||
_ia_status=runtime code=BUN_RESOLUTION_DRIFT
|
||||
detail="bun $found at $running is running, but the install verdict recorded bun $(gstack_install_verdict_field "$file" bun_version) at $v_bun"
|
||||
|
||||
@@ -67,4 +67,4 @@ if ! command -v bun >/dev/null 2>&1; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exec bun run "$ENTRY" "$@"
|
||||
exec bun --no-env-file --config=/dev/null run "$ENTRY" "$@"
|
||||
@@ -25,4 +25,4 @@ if ! command -v bun >/dev/null 2>&1; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exec bun run "$ENTRY" "$@"
|
||||
exec bun --no-env-file --config=/dev/null run "$ENTRY" "$@"
|
||||
@@ -144,7 +144,7 @@ do
|
||||
fi
|
||||
done
|
||||
|
||||
bun run "$GENERATOR" --input "$APP_SOURCE" --output "$GENERATED_DIR"
|
||||
bun --no-env-file --config=/dev/null 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.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-issue-guard — fetch tracker text and emit it inside the untrusted
|
||||
* trust envelope (lib/tracker-guard.ts). The ONLY sanctioned path for reading
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-issue-links — issue links by intent for /ship Step 18 (plan C6).
|
||||
*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-lane-check — planned files versus in-flight lanes (plan E2).
|
||||
*
|
||||
|
||||
@@ -27,7 +27,7 @@ INPUT="$1"
|
||||
TMPERR=$(mktemp "${TMPDIR:-/tmp}/gstack-learnings-log.XXXXXX") || { echo "gstack-learnings-log: could not create a temp file in ${TMPDIR:-/tmp}, so the learning was not recorded. Fix: point TMPDIR at a writable directory." >&2; exit 1; }
|
||||
trap 'rm -f "$TMPERR"' EXIT
|
||||
set +e
|
||||
VALIDATED=$(printf '%s' "$INPUT" | GSTACK_LIB_DIR="$SCRIPT_DIR/../lib" bun -e "
|
||||
VALIDATED=$(printf '%s' "$INPUT" | GSTACK_LIB_DIR="$SCRIPT_DIR/../lib" bun --no-env-file --config=/dev/null -e "
|
||||
const { hasInjection } = await import(process.env.GSTACK_LIB_DIR + '/jsonl-store.ts');
|
||||
const raw = await Bun.stdin.text();
|
||||
let j;
|
||||
|
||||
@@ -69,7 +69,7 @@ emit_tagged_file() {
|
||||
emit_tagged_file cross "$f"
|
||||
done
|
||||
fi
|
||||
} | GSTACK_SEARCH_TYPE="$TYPE" GSTACK_SEARCH_QUERY="$QUERY" GSTACK_SEARCH_LIMIT="$LIMIT" GSTACK_SEARCH_CROSS="$CROSS_PROJECT" bun -e "
|
||||
} | GSTACK_SEARCH_TYPE="$TYPE" GSTACK_SEARCH_QUERY="$QUERY" GSTACK_SEARCH_LIMIT="$LIMIT" GSTACK_SEARCH_CROSS="$CROSS_PROJECT" bun --no-env-file --config=/dev/null -e "
|
||||
const lines = (await Bun.stdin.text()).trim().split('\n').filter(Boolean);
|
||||
const now = Date.now();
|
||||
const type = process.env.GSTACK_SEARCH_TYPE || '';
|
||||
|
||||
@@ -374,7 +374,7 @@ status_bridge() {
|
||||
# JSON; a failed query is reported as unknown, never as an empty history.
|
||||
local egress_json
|
||||
if egress_json="$("$EGRESS_BIN" list --sink "$SINK" --json 2>/dev/null)"; then
|
||||
n="$(printf '%s' "$egress_json" | bun -e 'const a=JSON.parse(require("fs").readFileSync(0,"utf8")||"[]");console.log(Array.isArray(a)?a.length:0)' 2>/dev/null)"
|
||||
n="$(printf '%s' "$egress_json" | bun --no-env-file --config=/dev/null -e 'const a=JSON.parse(require("fs").readFileSync(0,"utf8")||"[]");console.log(Array.isArray(a)?a.length:0)' 2>/dev/null)"
|
||||
case "$n" in *[!0-9]*|"") n="unknown (could not parse gstack-egress output)" ;; esac
|
||||
else
|
||||
n="unknown (gstack-egress list failed; run it yourself)"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-memory-ingest — V1 memory ingest helper.
|
||||
*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
// Repair existing installs only; never install a new host or invoke an AI CLI.
|
||||
import * as path from 'node:path';
|
||||
import { migrateClaudeCodeSkills } from '../lib/claude-code-migration';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-model-benchmark — run the same prompt across multiple providers
|
||||
* and compare latency, tokens, cost, quality, and tool-call count.
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
import { modelsMain } from '../lib/model-policy-cli.ts';
|
||||
|
||||
const result = modelsMain(process.argv.slice(2));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
// gstack-next-version — host-aware VERSION allocator for /ship.
|
||||
//
|
||||
// Queries the PR queue (GitHub or GitLab), fetches each open PR's VERSION,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { atomicWriteSync } from '../lib/fs-atomic.ts';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-outside-voice — run the outside voice through one pluggable runner (plan B3).
|
||||
*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-owner-brief — the one-page owner brief at the autoplan gate (plan E3).
|
||||
*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-plan-reality — the reality rows of a plan review (plan D1).
|
||||
*
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-post — post PR/MR and issue text from agent-written files.
|
||||
*
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-pregate — cheap checks before anything paid or remote (plan C8).
|
||||
*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
import { qaDeadlineMain } from '../lib/qa-deadline';
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
import { qaEvidenceMain } from '../lib/qa-evidence';
|
||||
|
||||
process.exit(await qaEvidenceMain(process.argv.slice(2)));
|
||||
@@ -46,7 +46,7 @@ INPUT="$1"
|
||||
TMPERR=$(mktemp "${TMPDIR:-/tmp}/gstack-question-log.XXXXXX") || { echo "gstack-question-log: could not create a temp file in ${TMPDIR:-/tmp}, so the question was not logged. Fix: point TMPDIR at a writable directory." >&2; exit 1; }
|
||||
trap 'rm -f "$TMPERR"' EXIT
|
||||
set +e
|
||||
VALIDATED=$(printf '%s' "$INPUT" | GSTACK_LIB_DIR="$SCRIPT_DIR/../lib" bun -e "
|
||||
VALIDATED=$(printf '%s' "$INPUT" | GSTACK_LIB_DIR="$SCRIPT_DIR/../lib" bun --no-env-file --config=/dev/null -e "
|
||||
const { hasInjection } = await import(process.env.GSTACK_LIB_DIR + '/jsonl-store.ts');
|
||||
const path = require('path');
|
||||
const raw = await Bun.stdin.text();
|
||||
@@ -218,7 +218,7 @@ LOG_FILE="$GSTACK_HOME/projects/$SLUG/question-log.jsonl"
|
||||
# Lookup is bounded so the bin stays cheap on hot paths.
|
||||
DEDUP_SKIP=""
|
||||
if [ -f "$LOG_FILE" ]; then
|
||||
DEDUP_SKIP=$(VALIDATED_JSON="$VALIDATED" LOG_FILE_PATH="$LOG_FILE" bun -e '
|
||||
DEDUP_SKIP=$(VALIDATED_JSON="$VALIDATED" LOG_FILE_PATH="$LOG_FILE" bun --no-env-file --config=/dev/null -e '
|
||||
const fs = require("fs");
|
||||
const j = JSON.parse(process.env.VALIDATED_JSON);
|
||||
if (!j.tool_use_id) { console.log(""); process.exit(0); }
|
||||
@@ -238,7 +238,7 @@ if [ -f "$LOG_FILE" ]; then
|
||||
fi
|
||||
|
||||
if [ "$DEDUP_SKIP" = "dup" ]; then
|
||||
echo "DEDUP: skipped (source=$(echo "$VALIDATED" | bun -e 'const j=JSON.parse(await Bun.stdin.text()); console.log(j.source);'), tool_use_id duplicate)"
|
||||
echo "DEDUP: skipped (source=$(echo "$VALIDATED" | bun --no-env-file --config=/dev/null -e 'const j=JSON.parse(await Bun.stdin.text()); console.log(j.source);'), tool_use_id duplicate)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ do_check() {
|
||||
fi
|
||||
ensure_file
|
||||
cd "$ROOT_DIR"
|
||||
PREF_FILE_PATH="$PREF_FILE" QID="$QID" QSUMMARY="$QSUMMARY" bun -e "
|
||||
PREF_FILE_PATH="$PREF_FILE" QID="$QID" QSUMMARY="$QSUMMARY" bun --no-env-file --config=/dev/null -e "
|
||||
import('./scripts/one-way-doors.ts').then((oneway) => {
|
||||
const fs = require('fs');
|
||||
const qid = process.env.QID;
|
||||
@@ -187,7 +187,7 @@ do_write() {
|
||||
|
||||
set +e
|
||||
local RESULT
|
||||
RESULT=$(cd "$ROOT_DIR" && printf '%s' "$INPUT" | PREF_FILE_PATH="$PREF_FILE" EVENT_FILE_PATH="$EVENT_FILE" FREE_TEXT="$FREE_TEXT" HAS_FREE_TEXT="$HAS_FREE_TEXT" bun -e "
|
||||
RESULT=$(cd "$ROOT_DIR" && printf '%s' "$INPUT" | PREF_FILE_PATH="$PREF_FILE" EVENT_FILE_PATH="$EVENT_FILE" FREE_TEXT="$FREE_TEXT" HAS_FREE_TEXT="$HAS_FREE_TEXT" bun --no-env-file --config=/dev/null -e "
|
||||
const fs = require('fs');
|
||||
const raw = await Bun.stdin.text();
|
||||
let j;
|
||||
@@ -306,7 +306,7 @@ do_clear() {
|
||||
echo '{}' > "$PREF_FILE"
|
||||
echo "OK: cleared all preferences"
|
||||
else
|
||||
PREF_FILE_PATH="$PREF_FILE" QID="$QID" bun -e "
|
||||
PREF_FILE_PATH="$PREF_FILE" QID="$QID" bun --no-env-file --config=/dev/null -e "
|
||||
const fs = require('fs');
|
||||
const prefs = JSON.parse(fs.readFileSync(process.env.PREF_FILE_PATH, 'utf-8'));
|
||||
if (prefs[process.env.QID] !== undefined) {
|
||||
@@ -325,7 +325,7 @@ do_clear() {
|
||||
# -----------------------------------------------------------------------
|
||||
do_stats() {
|
||||
ensure_file
|
||||
(cd "$ROOT_DIR" && PREF_FILE_PATH="$PREF_FILE" bun -e "
|
||||
(cd "$ROOT_DIR" && PREF_FILE_PATH="$PREF_FILE" bun --no-env-file --config=/dev/null -e "
|
||||
import('./scripts/one-way-doors.ts').then((oneway) => {
|
||||
const fs = require('fs');
|
||||
const prefs = JSON.parse(fs.readFileSync(process.env.PREF_FILE_PATH, 'utf-8'));
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-redact — scan text for secrets/PII/legal content via the shared engine.
|
||||
*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-redact-prepush — git pre-push hook that scans the diff being pushed for
|
||||
* HIGH-severity credentials and blocks the push on a hit.
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-regen — the generated-file registry `.gstack/generated.json` (plan C4).
|
||||
*
|
||||
|
||||
+1
-1
@@ -468,7 +468,7 @@ if { [ "$DISABLED" != " " ] || grep -q '^Disabled on this install' "$_ROUTER" 2>
|
||||
_ROUTER_TMP="$(mktemp -d "${TMPDIR:-/tmp}/gstack-router.XXXXXX")" || _ROUTER_TMP=""
|
||||
_DISABLED_CSV="$(printf '%s' "$DISABLED" | xargs | tr ' ' ',')"
|
||||
if [ -n "$_ROUTER_TMP" ] && command -v bun >/dev/null 2>&1 \
|
||||
&& (cd "$_GEN_SRC" && bun run scripts/gen-skill-docs.ts --host claude --out-dir "$_ROUTER_TMP" ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"} >/dev/null 2>&1) \
|
||||
&& (cd "$_GEN_SRC" && bun --no-env-file --config=/dev/null run scripts/gen-skill-docs.ts --host claude --out-dir "$_ROUTER_TMP" ${_DISABLED_CSV:+"--disabled-skills=$_DISABLED_CSV"} >/dev/null 2>&1) \
|
||||
&& [ -s "$_ROUTER_TMP/SKILL.md" ] && cp "$_ROUTER_TMP/SKILL.md" "$_ROUTER.tmp.$$" && mv "$_ROUTER.tmp.$$" "$_ROUTER"; then
|
||||
[ -n "$_DISABLED_CSV" ] && echo "Router: disabled skills left out of $_ROUTER"
|
||||
else
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-render — render a local HTML file through a browser: Aside when it
|
||||
* is running, otherwise gstack's own headless browser (the browse daemon).
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-restamp — the whole stamp in one step (plan C2).
|
||||
*
|
||||
|
||||
@@ -72,14 +72,14 @@ fi
|
||||
SESSION_KIND=$("$SCRIPT_DIR/gstack-session-kind" 2>/dev/null || echo interactive)
|
||||
export GSTACK_STAMP_COMMIT_FULL="$COMMIT_FULL" GSTACK_STAMP_TREE="$TREE" GSTACK_STAMP_WTREE="$WTREE" GSTACK_STAMP_DIRTY="$DIRTY" GSTACK_STAMP_SESSION_KIND="$SESSION_KIND"
|
||||
if [ "$INPUT" = --start ]; then
|
||||
GSTACK_REVIEW_SKILL="${2:-}" bun -e '
|
||||
GSTACK_REVIEW_SKILL="${2:-}" bun --no-env-file --config=/dev/null -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 '
|
||||
GSTACK_REVIEW_TOKEN="$2" bun --no-env-file --config=/dev/null -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)));
|
||||
'
|
||||
@@ -107,7 +107,7 @@ case "$INPUT" in --*) usage ;; esac
|
||||
# Caller values for the binding keys are dropped before stamping. A binding or
|
||||
# validation failure reaches stderr as its own line (never folded into
|
||||
# "invalid JSON"), and exits 1 with nothing written.
|
||||
STAMPED=$(printf '%s' "$INPUT" | GSTACK_REVIEW_TOKEN="$TOKEN" GSTACK_REVIEW_FINDINGS="$FINDINGS" bun -e "
|
||||
STAMPED=$(printf '%s' "$INPUT" | GSTACK_REVIEW_TOKEN="$TOKEN" GSTACK_REVIEW_FINDINGS="$FINDINGS" bun --no-env-file --config=/dev/null -e "
|
||||
const { bindReview } = await import(process.env.GSTACK_REVIEW_LIB);
|
||||
let rec;
|
||||
try { rec = JSON.parse(await Bun.stdin.text()); } catch (e) { console.error('gstack-review-log: invalid JSON, skipping (' + e.message + ')'); process.exit(1); }
|
||||
|
||||
@@ -19,7 +19,7 @@ gstack_state_root_select
|
||||
GSTACK_HOME="$_gstack_sr_root"
|
||||
WTREE=$("$SCRIPT_DIR/gstack-wtree" 2>/dev/null || echo "unknown")
|
||||
if [ -f "$GSTACK_HOME/projects/$SLUG/$BRANCH-reviews.jsonl" ]; then
|
||||
GSTACK_REVIEW_LIB="$SCRIPT_DIR/../lib/review-evidence.ts" GSTACK_REVIEW_WTREE="$WTREE" bun -e '
|
||||
GSTACK_REVIEW_LIB="$SCRIPT_DIR/../lib/review-evidence.ts" GSTACK_REVIEW_WTREE="$WTREE" bun --no-env-file --config=/dev/null -e '
|
||||
const { reviewFreshness } = await import(process.env.GSTACK_REVIEW_LIB);
|
||||
for (const line of (await Bun.stdin.text()).split("\n").filter(Boolean)) {
|
||||
try {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-runtime-pins — the doctor's `project pins` row (lib/runtime-pins.ts).
|
||||
*
|
||||
|
||||
@@ -307,7 +307,7 @@ function gsWriteIfChanged(path, beforeText, settings, existed) {
|
||||
|
||||
GSTACK_SETTINGS_INPUT="$SETTINGS_FILE"
|
||||
export GSTACK_SETTINGS_INPUT
|
||||
SETTINGS_FILE=$(bun -e "$_HOOK_JS_PRELUDE"'gsMain(function () {
|
||||
SETTINGS_FILE=$(bun --no-env-file --config=/dev/null -e "$_HOOK_JS_PRELUDE"'gsMain(function () {
|
||||
process.stdout.write(gsResolveSettingsPath(process.env.GSTACK_SETTINGS_INPUT));
|
||||
});') || exit $?
|
||||
|
||||
@@ -405,7 +405,7 @@ case "$ACTION" in
|
||||
fi
|
||||
_acquire_lock || exit 5
|
||||
_mutation_env
|
||||
GSTACK_SETTINGS_PATH="$SETTINGS_FILE" GSTACK_HOOK_CMD="$HOOK_CMD" bun -e "$_HOOK_JS_PRELUDE"'gsMain(function () {
|
||||
GSTACK_SETTINGS_PATH="$SETTINGS_FILE" GSTACK_HOOK_CMD="$HOOK_CMD" bun --no-env-file --config=/dev/null -e "$_HOOK_JS_PRELUDE"'gsMain(function () {
|
||||
const settingsPath = process.env.GSTACK_SETTINGS_PATH;
|
||||
const hookCmd = process.env.GSTACK_HOOK_CMD;
|
||||
const loaded = gsLoadSettings(settingsPath);
|
||||
@@ -435,7 +435,7 @@ case "$ACTION" in
|
||||
[ -f "$SETTINGS_FILE" ] || exit 1
|
||||
_acquire_lock || exit 5
|
||||
_mutation_env
|
||||
GSTACK_SETTINGS_PATH="$SETTINGS_FILE" bun -e "$_HOOK_JS_PRELUDE"'gsMain(function () {
|
||||
GSTACK_SETTINGS_PATH="$SETTINGS_FILE" bun --no-env-file --config=/dev/null -e "$_HOOK_JS_PRELUDE"'gsMain(function () {
|
||||
const settingsPath = process.env.GSTACK_SETTINGS_PATH;
|
||||
const loaded = gsLoadSettings(settingsPath);
|
||||
const settings = loaded.settings;
|
||||
@@ -505,7 +505,7 @@ case "$ACTION" in
|
||||
GSTACK_TIMEOUT="$TIMEOUT" \
|
||||
GSTACK_DIFF_ONLY="$DIFF_ONLY" \
|
||||
GSTACK_ENSURE="$ENSURE" \
|
||||
bun -e "$_HOOK_JS_PRELUDE"'gsMain(function () {
|
||||
bun --no-env-file --config=/dev/null -e "$_HOOK_JS_PRELUDE"'gsMain(function () {
|
||||
const settingsPath = process.env.GSTACK_SETTINGS_PATH;
|
||||
const event = process.env.GSTACK_EVENT;
|
||||
const cmd = process.env.GSTACK_COMMAND;
|
||||
@@ -660,7 +660,7 @@ case "$ACTION" in
|
||||
[ -f "$SETTINGS_FILE" ] || exit 0
|
||||
_acquire_lock || exit 5
|
||||
_mutation_env
|
||||
GSTACK_SETTINGS_PATH="$SETTINGS_FILE" GSTACK_SOURCE="$SOURCE" GSTACK_RS_EVENT="$RS_EVENT" bun -e "$_HOOK_JS_PRELUDE"'gsMain(function () {
|
||||
GSTACK_SETTINGS_PATH="$SETTINGS_FILE" GSTACK_SOURCE="$SOURCE" GSTACK_RS_EVENT="$RS_EVENT" bun --no-env-file --config=/dev/null -e "$_HOOK_JS_PRELUDE"'gsMain(function () {
|
||||
const settingsPath = process.env.GSTACK_SETTINGS_PATH;
|
||||
const source = process.env.GSTACK_SOURCE;
|
||||
const onlyEvent = process.env.GSTACK_RS_EVENT || "";
|
||||
@@ -763,7 +763,7 @@ case "$ACTION" in
|
||||
GSTACK_PRUNE_ALL="$PRUNE_ALL" \
|
||||
GSTACK_PT_OPTOUT="$GSTACK_PT_OPTOUT" \
|
||||
GSTACK_SWEEP_EXCLUDE_SOURCES="${GSTACK_SWEEP_EXCLUDE_SOURCES:-}" \
|
||||
bun -e "$_HOOK_JS_PRELUDE"'gsMain(function () {
|
||||
bun --no-env-file --config=/dev/null -e "$_HOOK_JS_PRELUDE"'gsMain(function () {
|
||||
const settingsPath = process.env.GSTACK_SETTINGS_PATH;
|
||||
const root = (process.env.GSTACK_REPOINT_ROOT || "").replace(/\/+$/, "");
|
||||
const all = process.env.GSTACK_PRUNE_ALL === "1";
|
||||
@@ -902,7 +902,7 @@ case "$ACTION" in
|
||||
fi
|
||||
_mutation_env
|
||||
GSTACK_SETTINGS_PATH="$SETTINGS_FILE" GSTACK_RESTORE_PATH="$LATEST" \
|
||||
bun -e "$_HOOK_JS_PRELUDE"'gsMain(function () {
|
||||
bun --no-env-file --config=/dev/null -e "$_HOOK_JS_PRELUDE"'gsMain(function () {
|
||||
var fs = require("fs");
|
||||
var target = process.env.GSTACK_SETTINGS_PATH;
|
||||
var backup = process.env.GSTACK_RESTORE_PATH;
|
||||
@@ -944,7 +944,7 @@ case "$ACTION" in
|
||||
exit 1
|
||||
fi
|
||||
[ -f "$SETTINGS_FILE" ] || exit 0
|
||||
GSTACK_SETTINGS_PATH="$SETTINGS_FILE" GSTACK_LI_EVENT="$LI_EVENT" GSTACK_LI_OWNED_BY="$LI_OWNED_BY" GSTACK_LI_CMD_RE="$LI_CMD_RE" bun -e "$_HOOK_JS_PRELUDE"'gsMain(function () {
|
||||
GSTACK_SETTINGS_PATH="$SETTINGS_FILE" GSTACK_LI_EVENT="$LI_EVENT" GSTACK_LI_OWNED_BY="$LI_OWNED_BY" GSTACK_LI_CMD_RE="$LI_CMD_RE" bun --no-env-file --config=/dev/null -e "$_HOOK_JS_PRELUDE"'gsMain(function () {
|
||||
const event = process.env.GSTACK_LI_EVENT;
|
||||
const ownedBy = process.env.GSTACK_LI_OWNED_BY || "";
|
||||
const reSrc = process.env.GSTACK_LI_CMD_RE || "";
|
||||
@@ -982,7 +982,7 @@ case "$ACTION" in
|
||||
|
||||
list-sources)
|
||||
[ -f "$SETTINGS_FILE" ] || { echo "(no settings file)"; exit 0; }
|
||||
GSTACK_SETTINGS_PATH="$SETTINGS_FILE" bun -e "$_HOOK_JS_PRELUDE"'gsMain(function () {
|
||||
GSTACK_SETTINGS_PATH="$SETTINGS_FILE" bun --no-env-file --config=/dev/null -e "$_HOOK_JS_PRELUDE"'gsMain(function () {
|
||||
const fs = require("fs");
|
||||
let settings = {};
|
||||
try { settings = JSON.parse(fs.readFileSync(process.env.GSTACK_SETTINGS_PATH, "utf8")); }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-ship-policy — read, validate and explain `.gstack/ship-policy.json` (plan C1).
|
||||
*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-ship-queue — the shared merge queue, one subcommand per protocol
|
||||
* step (field list 2, W1; docs/ship-policy.md#run-a-shared-merge-queue).
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-ship-receipt — the handoff record, identifiers first (plan C5).
|
||||
*
|
||||
|
||||
@@ -31,7 +31,7 @@ if [ -z "$COMBINED" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
printf '%s' "$COMBINED" | bun -e "
|
||||
printf '%s' "$COMBINED" | bun --no-env-file --config=/dev/null -e "
|
||||
const lines = (await Bun.stdin.text()).trim().split('\n').filter(Boolean);
|
||||
const NEVER_GATE = new Set(['security', 'data-migration', 'state-machines']);
|
||||
const stats = {};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-state-matrix — the round-trip matrix for proposal, hold and approval state machines (field list 2, W3).
|
||||
*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
// gstack-taste-update — update the persistent taste profile at
|
||||
// ~/.gstack/projects/$SLUG/taste-profile.json
|
||||
//
|
||||
|
||||
@@ -181,7 +181,7 @@ HOOK_EOF
|
||||
if ! command -v bun >/dev/null 2>&1; then
|
||||
echo " ! bun not found — manually add this PreToolUse hook (matcher Skill|skill) to .claude/settings.json:"
|
||||
echo " $HOOK_COMMAND"
|
||||
elif GSTACK_SETTINGS_PATH="$SETTINGS" GSTACK_HOOK_COMMAND="$HOOK_COMMAND" bun -e "
|
||||
elif GSTACK_SETTINGS_PATH="$SETTINGS" GSTACK_HOOK_COMMAND="$HOOK_COMMAND" bun --no-env-file --config=/dev/null -e "
|
||||
const fs = require('fs');
|
||||
const settingsPath = process.env.GSTACK_SETTINGS_PATH;
|
||||
const command = process.env.GSTACK_HOOK_COMMAND;
|
||||
|
||||
@@ -205,7 +205,7 @@ ERR_FIELD="null"
|
||||
# look like a JSON string, the whole message becomes null — never raw.
|
||||
ERR_MSG_FIELD="null"
|
||||
if [ -n "$ERROR_MESSAGE" ]; then
|
||||
ERR_MSG_FIELD="$(printf '%s' "$ERROR_MESSAGE" | GSTACK_LIB_DIR="$SCRIPT_DIR/../lib" bun -e "
|
||||
ERR_MSG_FIELD="$(printf '%s' "$ERROR_MESSAGE" | GSTACK_LIB_DIR="$SCRIPT_DIR/../lib" bun --no-env-file --config=/dev/null -e "
|
||||
const { redactFindingSpans } = await import(process.env.GSTACK_LIB_DIR + '/redact-engine.ts');
|
||||
const input = await Bun.stdin.text();
|
||||
const out = redactFindingSpans(input, { repoVisibility: 'private' });
|
||||
|
||||
@@ -20,7 +20,7 @@ mkdir -p "$GSTACK_HOME/projects/$SLUG"
|
||||
INPUT="$1"
|
||||
|
||||
# Validate: input must be parseable JSON with required fields
|
||||
if ! printf '%s' "$INPUT" | bun -e "
|
||||
if ! printf '%s' "$INPUT" | bun --no-env-file --config=/dev/null -e "
|
||||
const j = JSON.parse(await Bun.stdin.text());
|
||||
if (!j.skill || !j.event) process.exit(1);
|
||||
" 2>/dev/null; then
|
||||
@@ -28,8 +28,8 @@ if ! printf '%s' "$INPUT" | bun -e "
|
||||
fi
|
||||
|
||||
# Inject timestamp if not present
|
||||
if ! printf '%s' "$INPUT" | bun -e "const j=JSON.parse(await Bun.stdin.text()); if(!j.ts) process.exit(1)" 2>/dev/null; then
|
||||
INPUT=$(printf '%s' "$INPUT" | bun -e "
|
||||
if ! printf '%s' "$INPUT" | bun --no-env-file --config=/dev/null -e "const j=JSON.parse(await Bun.stdin.text()); if(!j.ts) process.exit(1)" 2>/dev/null; then
|
||||
INPUT=$(printf '%s' "$INPUT" | bun --no-env-file --config=/dev/null -e "
|
||||
const j = JSON.parse(await Bun.stdin.text());
|
||||
j.ts = new Date().toISOString();
|
||||
console.log(JSON.stringify(j));
|
||||
|
||||
@@ -108,7 +108,7 @@ command -v bun >/dev/null 2>&1 || {
|
||||
exit 127
|
||||
}
|
||||
|
||||
cat "$TIMELINE_FILE" | GSTACK_TIMELINE_SINCE_MS="$SINCE_MS" GSTACK_TIMELINE_BRANCH="$BRANCH" GSTACK_TIMELINE_LIMIT="$LIMIT" bun -e "
|
||||
cat "$TIMELINE_FILE" | GSTACK_TIMELINE_SINCE_MS="$SINCE_MS" GSTACK_TIMELINE_BRANCH="$BRANCH" GSTACK_TIMELINE_LIMIT="$LIMIT" bun --no-env-file --config=/dev/null -e "
|
||||
const lines = (await Bun.stdin.text()).trim().split('\n').filter(Boolean);
|
||||
const branch = process.env.GSTACK_TIMELINE_BRANCH || '';
|
||||
const limit = Number(process.env.GSTACK_TIMELINE_LIMIT);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
/**
|
||||
* gstack-tree-receipt — the tree-equality receipt (plan C3).
|
||||
*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bun
|
||||
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
||||
// gstack-version-bump — deterministic version-state classifier + writer for /ship.
|
||||
//
|
||||
// Extracted from ship Step 12 prose (v2 plan T9, hybrid CLI extraction). The
|
||||
@@ -66,6 +66,7 @@
|
||||
import { existsSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
||||
import { bunIsolationArgs } from "../lib/bun-isolation";
|
||||
import {
|
||||
extractVersion,
|
||||
isJsonVersionPath,
|
||||
@@ -391,7 +392,7 @@ function regenAgentsDigest(versionPath: string, optedIn: boolean): boolean | nul
|
||||
const gen = join(root, "scripts", "gen-agents-digest.ts");
|
||||
if (!existsSync(gen) || !existsSync(join(root, "agents-digest", "gstack-AGENTS.md"))) return null;
|
||||
try {
|
||||
execFileSync("bun", [gen], { cwd: root, stdio: "pipe", timeout: 30_000 });
|
||||
execFileSync("bun", [...bunIsolationArgs(), gen], { cwd: root, stdio: "pipe", timeout: 30_000 });
|
||||
return true;
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
|
||||
Loaded 100 of 267 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user