Files
gstack/.github/workflows/windows-pregate-e2e.yml
82530ac143 v1.91.117.0 fix: pregate bats lanes, WTREE_UNAVAILABLE, doctor rows, SKILL.md size guard, planMode hosts (fix wave PR 5) (#3236)
* fix(pregate): recognise bats test files so lanes does not fail in bats repos (#3161)

gstack-pregate never treated a .bats file as a test, so in a repository whose
tests are bats files the lanes check failed every run with PREGATE_NO_LANE: a
.gstack/pregate.json dependency pointing at tests/foo.bats selected nothing, a
selection manifest printing .bats paths yielded 0 files, and a bats run that
executed no tests could not trip the zero-run rule.

Recognise .bats in the test-file pattern and in the manifest-output parser, and
read the bats TAP plan and pretty summary (as a last resort, after every
existing runner's summary) so a run that reports zero tests is a failure.

Fixes #3161.

Cherry-picked from #3177 (d355c116b). Conflicts resolved as in the author's own
merges (b09476beb, 444f4e768): lane-selection.ts keeps main's isTestFile-based
manifest parser, and lanes.ts carries the bats summary on main's supervised runner.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(pregate): a nested .bats file is selected and run end to end (#3161)

Reviewer test for #3177: touched itself with no declaration, and through a
declared dependency; a failing nested bats run reports the bats counts.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(pregate): a wtree failure is WTREE_UNAVAILABLE, not an unresolvable base (#3197)

The pregate consumers of the working-tree fingerprint (buildContext,
withFingerprint, verifyPregate) move to lib/script-invocation.ts's
{tree}|{error} form, and lib/regen.ts drops its string-returning wrapper.

- A fingerprint failure was reported as "policy source unavailable:
  origin/<base> is not a resolvable commit" with git fetch as the fix. It is
  now WTREE_UNAVAILABLE: a launch failure lists each Windows bash candidate
  with its rejection reason and names GSTACK_GIT_BASH; a gstack-wtree exit
  keeps the repair it printed (set TMPDIR to a writable directory).
- A fingerprint failing after a check is incomplete with that cause, not
  "working tree changed"; verify says the tree cannot be fingerprinted.
- #3190's gstack-config read (test_backend) goes through runScriptSync, so
  Windows reaches it through Git Bash. The tripwire learns the bin-constant
  shape that let it through.
- Under --json every context refusal is one envelope on stdout
  ({ok:false,status:'refused',code,detail,fix,docs}, exit 3); the run object
  carries ok and status too, and GSTACK_RESULT goes to stderr.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ci(pregate): gstack-pregate joins the Windows pregate job; pregate tests return to the Windows lane (#3197)

- windows-pregate-e2e runs gstack-pregate run (preflight: regen, secrets
  through the gstack-redact bun bin, literals, guards) in both journeys, and
  asserts a missing GSTACK_GIT_BASH refuses with one WTREE_UNAVAILABLE --json
  envelope.
- test/helpers/pregate-fixture.ts and restamp-fixture.ts runBin launch bins
  through lib/script-invocation.ts (bun bins as Bun argv, bash bins through
  Git Bash on Windows), which removes the "shebang via CreateProcess" reason
  for the pregate tests; they move from KNOWN_WINDOWS_INCOMPATIBLE to
  KNOWN_WINDOWS_SAFE.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* doctor: list rescue refs and warn about CLAUDE.md skill lists

gstack-doctor's plain report gains two rows. 'rescue refs' lists each
refs/gstack/rescue/* commit in the install checkout, newest first, with its
date, a needs-manual-resolution label when the rescue held unmerged files, and
the shell-quoted inspect and delete commands bin/gstack-ff-update's banner
prints. 'CLAUDE.md' warns when a gstack section in ~/.claude/CLAUDE.md or the
project's CLAUDE.md enumerates skills (#3169) and prints the short block to
paste instead; docs/troubleshooting.md#claude-md-skill-list explains it.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* doctor: name the Windows bash gstack resolves and what chose it

On Windows (OSTYPE msys/cygwin) gstack-doctor adds a 'windows bash' row from
lib/script-invocation.ts resolveWindowsBash, run through the new thin
bin/gstack-windows-bash.ts with the bun on PATH: the resolved bash and the
override or discovery step that chose it, a warn when GSTACK_GIT_BASH and
GSTACK_BASH differ, and a fail carrying the BASH_UNAVAILABLE line (every
candidate with its rejection reason) when none qualifies. Tests inject the
platform (GSTACK_DOCTOR_PLATFORM) and the discovery inputs
(GSTACK_DOCTOR_BASH_PROBE) so the row runs on every OS.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* doctor --check: name the held-install recovery on Windows, not bin/gstack-upgrade

bin/gstack-upgrade compares a pwd -P path (/c/...) with git's toplevel
(C:/...) and refuses on every Git Bash install, so on Windows the revision
row's upgrade fix (REVISION_BELOW_MINIMUM, the Capy-wrapper variant and
REVISION_UNMET) names the held-install recovery forms from
docs/troubleshooting.md#auto-update-held-install-recovery instead: the Git
Bash form and the Windows PowerShell 5.1 form, each labelled by shell with the
install path quoted for that shell. Other platforms keep bin/gstack-upgrade.
A test pins both printed forms to the documented ones.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(skills): omit the plan-mode section on hosts without plan mode (#3168)

generatePlanModeInfo now honours capabilities.planMode. Hosts that declare
planMode:false (codex, agy, hermes, openclaw, and define-host's default:
factory, kiro, slate, cursor, gbrain) no longer render the
'## Plan Mode Safe Operations' and '## Skill Invocation During Plan Mode'
preamble sections. Claude, copilot and opencode are unchanged.

The codex, factory and agy ship goldens are regenerated. The clarity test that
pinned the plan-mode entry-order prose on every host now pins it on planMode
hosts and asserts its absence elsewhere (intentional contract change).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(skills): guard generated SKILL.md size at 100,000 chars on every host (#3199)

A free test renders every host and fails when a generated SKILL.md exceeds
100,000 characters, naming host, skill, actual size, limit and the fix. The 89
current offenders are pinned at their exact size in
test/fixtures/skill-md-size-allowlist.json: growth past a pin fails, and a
shrink fails until the pin is lowered. The shared-preamble hoist is deferred
(owner Garry, revisit 2026-11-15).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(pregate): keep #3177's bats tests beside the nested-directory test

Moves #3177's bats describe block verbatim from test/pregate-selection.test.ts
into test/pregate-bats-nested.test.ts, so pregate-selection.test.ts is
unchanged on this branch and the in-flight field-list-2 pregate lane
(capy/fl2-L6-pregate, which edits that file's imports and adds cases) merges
without a conflict. No assertion changes.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* doctor --check: keep bin/gstack-upgrade as the Windows fix (path spelling fixed on main)

Reverts 37bac07f8. Plan item D named the held-install recovery forms on
Windows only until bin/gstack-upgrade stopped refusing Git Bash installs.
bin/gstack-upgrade:63-67 now compares with gstack_path_same from
bin/gstack-path-spelling.sh, pinned by test/gstack-upgrade-path-spelling.test.ts,
so bin/gstack-upgrade is the right fix on every platform again.

* test(skills): measure the size-guard pins on v1.91.109.0

#3225 (merged into this branch) grew 53 pinned skills: 3 characters each from
the Aside driver resolver, and about 340 more in plan-design-review and
plan-devex-review from the #3219 summary sections. The pins are this PR's
starting measurement, so they are taken on the merged base; no skill this PR
renders grew.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ci(windows): only the pregate tests that pass on windows-latest join the lane

windows-free-tests run 38984824731 (this branch at 8ed6da402) ran the moved
files. gstack-regen and the new pregate-wtree-refusal passed. pregate-guards,
-remote, -regate, -invalidation, -selection and -bats-nested launched
gstack-pregate fine but fail on Linux-host expectations: their fixture lanes are
Linux workflow jobs, which a Windows host records as requires-remote
obligations. They go back to KNOWN_WINDOWS_INCOMPATIBLE with that reason
instead of "shebang via CreateProcess".

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* windows curation: keep CI run ids out of reason prose (pii.phone false positive)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* skill-md size pins: follow #3231's review-army line (+21 chars on five inlining hosts)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* doctor test: drop the reverted Windows upgrade-fix wording from item D's describe

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* skill-md size pins: follow #3226 (plan-devex-review +3324) and #3234 (autoplan +59)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* WTREE_UNAVAILABLE moves to lib/result-codes-release.ts (#3235 split; result-codes.ts stays at 796 of 800 lines); doctor fixture copies the release table; size pins follow #3235's review +1

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(changelog): fix wave PR 5 release notes

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* v1.91.117.0 fix: fix wave PR 5 (pregate bats + WTREE_UNAVAILABLE, doctor rows, SKILL.md size guard, planMode hosts)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(qa): an invalid-input probe breaks only the rule it tests

The paid gate's qa-functional-cli-report failed again on #3236 (run
39001107574): the agent probed the digits-only amount rule with -5, which
the positivity check rejected first, so the planted parseInt defect stayed
hidden and it reported no defect. The contract map's invalid-input row now
asks for each input to break only the rule it tests, so another check
cannot reject it first.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: harjothkhara <48686985+harjothkhara@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-17 04:36:00 +00:00

246 lines
14 KiB
YAML

name: Windows Pregate E2E
# Native proof for #3197 on windows-latest: Bun on Windows cannot execute an
# extensionless shebang bin (bin/gstack-wtree, gstack-slug, gstack-redact),
# so every TypeScript caller goes through lib/script-invocation.ts, which
# hands bash bins to Git Bash and bun bins to Bun. This job drives the real
# CLIs from TypeScript entry points against a fixture repository:
# - the resolver finds Git Bash from `git --exec-path`;
# - gstack-restamp --dry-run fingerprints the tree and stamps;
# - gstack-regen check (scratch worktree from the fingerprint, the entry's
# command through `bash -c`) and write (the no-scratch path);
# - gstack-evidence records a content fingerprint, its command through bash -c;
# - gstack-gate-diff records a candidate with a tree id, not `+nowtree`;
# - gstack-pregate run fingerprints the tree and runs its preflight checks
# (secrets through the gstack-redact bun bin, regen and guards);
# - a hook invocation is timed cold and warm (the cached resolution);
# - a missing GSTACK_GIT_BASH refuses with BASH_UNAVAILABLE, not `()`, and
# gstack-pregate --json refuses with one WTREE_UNAVAILABLE envelope;
# - the same journey under a pinned, checksum-verified PortableGit found
# only through `git --exec-path`.
#
# Runner: GitHub-hosted free windows-latest. ~4-6 min.
on:
pull_request:
branches: [main]
paths:
- 'lib/script-invocation.ts'
- 'lib/regen.ts'
- 'lib/restamp.ts'
- 'lib/gbrain-exec.ts'
- 'lib/gate-diff/**'
- 'lib/pregate/**'
- 'lib/result-codes.ts'
- 'lib/result-codes-release.ts'
- 'bin/gstack-wtree'
- 'bin/gstack-slug'
- 'bin/gstack-path-spelling.sh'
- 'bin/gstack-restamp'
- 'bin/gstack-regen'
- 'bin/gstack-evidence'
- 'bin/gstack-gate-diff'
- 'bin/gstack-redact'
- 'bin/gstack-pregate'
- 'hosts/claude/hooks/**'
- '.github/workflows/windows-pregate-e2e.yml'
workflow_dispatch:
concurrency:
group: windows-pregate-e2e-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
permissions:
contents: read
env:
PORTABLE_GIT_VERSION: 2.56.0.windows.2
PORTABLE_GIT_FILE: PortableGit-2.56.0.2-64-bit.7z.exe
PORTABLE_GIT_SHA256: 075e158ef8e1f0ab80b347e245405d3eca735c2dc88fd8e032e137d0ca61f61b
jobs:
windows-pregate:
runs-on: windows-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.2
- name: Configure git identity
run: |
git config --global user.email "windows-pregate-e2e@gstack.test"
git config --global user.name "Windows Pregate E2E"
git config --global init.defaultBranch main
git config --global commit.gpgsign false
shell: bash
- name: Install dependencies
run: bun install --frozen-lockfile
shell: bash
- name: Build the fixture repository
# A VERSION + CHANGELOG + ship policy + generated-file registry, a
# bare origin and a feature branch with an [Unreleased] entry.
run: |
set -euo pipefail
T="$RUNNER_TEMP/pregate-e2e"
rm -rf "$T"; mkdir -p "$T/home"
git init -q --bare -b main "$T/origin.git"
git init -q -b main "$T/work"
cd "$T/work"
mkdir -p generated .gstack
echo 1.2.3.0 > VERSION
printf '{ "name": "fixture", "version": "1.2.3" }\n' > package.json
printf '%s\n' "const fs=require('fs');const v=fs.readFileSync('VERSION','utf8').trim();fs.writeFileSync('generated/digest.md','# digest v'+v+'\\n');" > gen.js
node gen.js
printf '# Changelog\n\n## [1.2.3.0] - 2026-10-01\n\n- base release\n' > CHANGELOG.md
cat > .gstack/ship-policy.json <<'JSON'
{ "$schema": "https://github.com/garrytan/gstack/docs/ship-policy.md#schema-v1", "bump": "patch", "queue_mode": "stamp-at-merge", "release_tool": "node gen.js", "release_outputs": ["generated/**"], "stamp_paths": [], "changelog": "CHANGELOG.md", "history": "squash-ok", "stamp_sensitive_gates": [] }
JSON
cat > .gstack/generated.json <<'JSON'
{ "$schema": "https://github.com/garrytan/gstack/docs/pregate.md#generated-v1", "entries": [ { "id": "digest", "command": "node gen.js", "outputs": ["generated/**"], "inputs": ["VERSION", "gen.js"] } ] }
JSON
git add -A && git commit -qm base
git remote add origin "$T/origin.git" && git push -q origin main && git remote set-head origin main
git checkout -qb feature
echo feature > feature.txt
printf '# Changelog\n\n## [Unreleased]\n\n- feature change\n\n## [1.2.3.0] - 2026-10-01\n\n- base release\n' > CHANGELOG.md
git add -A && git commit -qm feature
shell: bash
- name: The resolver finds the runner's Git Bash from git --exec-path
run: |
set -euo pipefail
git --version
OUT=$(bun -e 'import { resolveWindowsBash } from "./lib/script-invocation"; const r = resolveWindowsBash(); console.log(JSON.stringify(r)); process.exit(r.ok ? 0 : 1);')
echo "$OUT"
echo "$OUT" | grep -q '"source":"git --exec-path"' || (echo "Git Bash was not found through git --exec-path" && exit 1)
echo "$OUT" | grep -qiE '"bash":"[^"]*/(usr/)?bin/bash\.exe"' || (echo "the resolved bash is not a Git Bash bash.exe" && exit 1)
shell: bash
- name: A hook invocation, timed cold and warm (cached Git Bash resolution)
run: |
set -euo pipefail
T="$RUNNER_TEMP/hook-timing"
rm -rf "$T"; mkdir -p "$T"
PAYLOAD='{"session_id":"e2e","hook_event_name":"PostToolUse","tool_name":"AskUserQuestion","tool_use_id":"TOOL_ID","tool_input":{"questions":[{"question":"Ship it?","options":[{"label":"Ship now (recommended)"},{"label":"Hold"}]}]},"tool_response":{"answers":[{"option_label":"Ship now"}]}}'
run_hook() {
local start end
start=$(date +%s%3N)
printf '%s' "${PAYLOAD/TOOL_ID/$1}" | (cd "$T" && GSTACK_STATE_ROOT="$T" GSTACK_QUESTION_LOG_NO_DERIVE=1 bun "$GITHUB_WORKSPACE/hosts/claude/hooks/question-log-hook.ts")
end=$(date +%s%3N)
echo $((end - start))
}
COLD=$(run_hook toolu_cold)
test -f "$T/cache/windows-bash.json" || (echo "the hook did not cache its Git Bash resolution" && exit 1)
cat "$T/cache/windows-bash.json"
WARM=$(run_hook toolu_warm)
echo "question-log-hook: cold ${COLD} ms, warm ${WARM} ms"
echo "### question-log-hook on windows-latest: cold ${COLD} ms, warm ${WARM} ms" >> "$GITHUB_STEP_SUMMARY"
LOGS=$(find "$T/projects" -name question-log.jsonl | head -n 1)
test -n "$LOGS" || (echo "the hook wrote no question-log.jsonl" && exit 1)
test "$(wc -l < "$LOGS")" -eq 2 || (echo "expected two logged events" && exit 1)
test ! -s "$T/hook-errors.log" || { cat "$T/hook-errors.log"; echo "the hook logged errors"; exit 1; }
shell: bash
- name: A missing GSTACK_GIT_BASH refuses with BASH_UNAVAILABLE and names every candidate
run: |
set -uo pipefail
T="$RUNNER_TEMP/pregate-e2e"
cd "$T/work"
OUT=$(GSTACK_HOME="$T/home" GSTACK_GIT_BASH='C:\no-such-git\bin\bash.exe' bun "$GITHUB_WORKSPACE/bin/gstack-restamp" --base main --allow-repo-commands --next --dry-run 2>&1) && CODE=0 || CODE=$?
echo "$OUT" | tail -n 3
test "$CODE" -ne 0 || (echo "restamp passed without a bash" && exit 1)
echo "$OUT" | grep -q 'cannot fingerprint the working tree (gstack-wtree could not start: gstack: no Git Bash found' || (echo "the refusal does not say why" && exit 1)
echo "$OUT" | grep -q 'GSTACK_GIT_BASH C:/no-such-git/bin/bash.exe: does not exist' || (echo "the refusal does not list the override" && exit 1)
echo "$OUT" | grep -q '(BASH_UNAVAILABLE)' || (echo "the refusal has no result code" && exit 1)
OUT=$(GSTACK_HOME="$T/home" GSTACK_GIT_BASH='C:\no-such-git\bin\bash.exe' bun "$GITHUB_WORKSPACE/bin/gstack-pregate" run --base main --stage preflight --json 2>/dev/null) && CODE=0 || CODE=$?
echo "$OUT"
test "$CODE" -eq 3 || (echo "gstack-pregate exited $CODE, expected 3 (refused)" && exit 1)
echo "$OUT" | bun -e 'const j = JSON.parse(await Bun.stdin.text()); const ok = j.ok === false && j.status === "refused" && j.code === "WTREE_UNAVAILABLE" && j.detail.includes("GSTACK_GIT_BASH C:/no-such-git/bin/bash.exe: does not exist") && j.fix.includes("GSTACK_GIT_BASH"); process.exit(ok ? 0 : 1);' || (echo "gstack-pregate --json did not refuse with one WTREE_UNAVAILABLE envelope" && exit 1)
shell: bash
- name: Download the pinned PortableGit and verify its checksum
run: |
set -euo pipefail
PG="$RUNNER_TEMP/PortableGit"
curl -fsSL -o "$RUNNER_TEMP/$PORTABLE_GIT_FILE" "https://github.com/git-for-windows/git/releases/download/v${PORTABLE_GIT_VERSION}/${PORTABLE_GIT_FILE}"
echo "$PORTABLE_GIT_SHA256 $RUNNER_TEMP/$PORTABLE_GIT_FILE" | sha256sum -c -
"$RUNNER_TEMP/$PORTABLE_GIT_FILE" -o"$(cygpath -w "$PG")" -y
test -f "$PG/bin/bash.exe" || (echo "PortableGit has no bin/bash.exe" && exit 1)
"$PG/cmd/git.exe" --version
shell: bash
- name: The resolver finds the PortableGit bash through git --exec-path alone
run: |
set -euo pipefail
PG=$(cygpath -u "$RUNNER_TEMP/PortableGit")
export PATH="$PG/cmd:$PATH"
git --version
WANT=$(cygpath -m "$PG/bin/bash.exe")
OUT=$(bun -e 'import { resolveWindowsBash } from "./lib/script-invocation"; const r = resolveWindowsBash(); console.log(JSON.stringify(r)); process.exit(r.ok ? 0 : 1);')
echo "$OUT"
echo "$OUT" | grep -qiF "\"bash\":\"$WANT\"" || (echo "expected $WANT" && exit 1)
echo "$OUT" | grep -q '"source":"git --exec-path"' || (echo "PortableGit was not found through git --exec-path" && exit 1)
shell: bash
- name: The fixture journey (restamp, regen, evidence, gate-diff) under the runner's Git and the pinned PortableGit
run: |
set -euo pipefail
journey() {
G="$PWD"
T="$RUNNER_TEMP/pregate-e2e"
export GSTACK_HOME="$T/home" GSTACK_STATE_ROOT="$T/home"
cd "$T/work"
git reset -q --hard && git clean -fdq
rm -rf "$T/home/projects"
OUT=$(bun "$G/bin/gstack-restamp" --base main --allow-repo-commands --next --dry-run 2>&1) || { echo "$OUT"; echo "gstack-restamp --dry-run failed"; exit 1; }
echo "$OUT" | tail -n 8
echo "$OUT" | grep -q '^+1\.2\.4\.0$' || (echo "restamp did not stamp VERSION" && exit 1)
echo "$OUT" | grep -q '^+# digest v1\.2\.4\.0$' || (echo "restamp did not run the release tool" && exit 1)
echo "$OUT" | grep -q 'GSTACK_RESULT: skill=restamp status=complete' || (echo "restamp did not complete" && exit 1)
test -z "$(git status --porcelain)" || (echo "--dry-run wrote to the tree" && exit 1)
OUT=$(bun "$G/bin/gstack-regen" check --allow-repo-commands 2>&1) || { echo "$OUT"; echo "gstack-regen check failed"; exit 1; }
echo "$OUT"
echo "$OUT" | grep -q '^regen digest: fresh' || (echo "the regen command did not run fresh in the scratch worktree" && exit 1)
echo "$OUT" | grep -qE 'GSTACK_RESULT: skill=regen status=complete run=[0-9a-f]{40}$' || (echo "regen check has no tree fingerprint" && exit 1)
OUT=$(bun "$G/bin/gstack-regen" write --allow-repo-commands 2>&1) || { echo "$OUT"; echo "gstack-regen write failed"; exit 1; }
echo "$OUT" | grep -qE 'GSTACK_RESULT: skill=regen status=complete run=[0-9a-f]{40}$' || (echo "regen write has no tree fingerprint" && exit 1)
bun "$G/bin/gstack-evidence" run --label tests -- 'echo evidence-ok && test -f VERSION'
LEDGER=$(find "$T/home/projects" -name '*-evidence.jsonl' | head -n 1)
test -n "$LEDGER" || (echo "no evidence ledger written" && exit 1)
tail -n 1 "$LEDGER"
tail -n 1 "$LEDGER" | grep -qE '"exit":0,' || (echo "the evidence command did not exit 0 through bash -c" && exit 1)
tail -n 1 "$LEDGER" | grep -qE '"wtree":"[0-9a-f]{40}"' || (echo "evidence recorded no content fingerprint" && exit 1)
set +e
OUT=$(bun "$G/bin/gstack-gate-diff" main 2>&1)
CODE=$?
set -e
echo "$OUT" | tail -n 3
test "$CODE" -eq 0 || (echo "gstack-gate-diff exited $CODE" && exit 1)
! echo "$OUT" | grep -q 'fingerprint unavailable' || (echo "gate-diff could not fingerprint the tree" && exit 1)
echo "$OUT" | grep -qE 'candidate=[0-9a-f]{12}\+[0-9a-f]{12} ' || (echo "gate-diff candidate has no tree id" && exit 1)
OUT=$(bun "$G/bin/gstack-pregate" run --base main --stage preflight --only regen,secrets,literals,guards --allow-repo-commands 2>&1) || { echo "$OUT"; echo "gstack-pregate run failed"; exit 1; }
echo "$OUT" | tail -n 10
echo "$OUT" | grep -qE '^preflight secrets pass .* added lines clean' || (echo "pregate secrets did not scan through gstack-redact" && exit 1)
echo "$OUT" | grep -q 'GSTACK_RESULT: skill=pregate status=complete run=' || (echo "pregate did not complete" && exit 1)
grep -qE '"tree": "[0-9a-f]{40}"' .gstack/tmp/pregate.json || (echo "pregate.json has no tree fingerprint" && exit 1)
}
echo "::group::runner Git for Windows ($(git --version))"
(journey)
echo "::endgroup::"
echo "::group::pinned PortableGit $PORTABLE_GIT_VERSION"
PG_CMD=$(cygpath -u "$RUNNER_TEMP/PortableGit/cmd")
(export PATH="$PG_CMD:$PATH"; git --version; journey)
echo "::endgroup::"
shell: bash