201 lines
8.1 KiB
Bash
201 lines
8.1 KiB
Bash
#!/usr/bin/env bash
|
|
# tests/e2e-fix-round-4-5a.sh
|
|
# End to end for fix round 4-5a (plan
|
|
# .claude/projects/2026-10-20-fix-round-serious-findings, End-to-end:): the
|
|
# shipped CLIs run the way the commands run them, in temp git repos.
|
|
# 1. The park tool (lib/util/park-attempt-files.mjs), on the diff the
|
|
# "Back to planning" line saves:
|
|
# a. a new file registered --intent-to-add is parked (exit 0, gone from
|
|
# the tree and the index, kept under .git/voyage/parked/);
|
|
# b. a coloured diff (--color=always, as color.diff=always gives it)
|
|
# still parks its new file;
|
|
# c. --cwd a subdirectory of the repo parks the root-relative path.
|
|
# 2. The verdict gate (lib/review/verdict-gate.mjs --json <review> --repo .):
|
|
# a review of HEAD (0), a later lessons-only commit (0), a later source
|
|
# commit (2, reason review-stale).
|
|
# 3. The arg parser (lib/parsers/arg-parser.mjs --command trekendsession --
|
|
# fix-header): no help flag, the slug is the first positional.
|
|
# 4. The gates shim (lib/util/autonomy-gate.mjs): `--gates closed` and a bare
|
|
# `--gates` both give next_state gates_on.
|
|
# Prints "e2e: ok" and exits 0, or names the failed check and exits 1.
|
|
# bash 3.2 compatible, ASCII only.
|
|
|
|
set -u
|
|
|
|
cd "$(dirname "$0")/.." || { echo "e2e: FAIL - cannot cd to the repo root" >&2; exit 1; }
|
|
ROOT="$(pwd)"
|
|
PARK="$ROOT/lib/util/park-attempt-files.mjs"
|
|
VERDICT="$ROOT/lib/review/verdict-gate.mjs"
|
|
ARGS="$ROOT/lib/parsers/arg-parser.mjs"
|
|
SHIM="$ROOT/lib/util/autonomy-gate.mjs"
|
|
|
|
unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE
|
|
|
|
TMP="$(mktemp -d "${TMPDIR:-/tmp}/e2e-fix-round-4-5a.XXXXXX")" || { echo "e2e: FAIL - mktemp" >&2; exit 1; }
|
|
trap 'rm -rf "$TMP"' EXIT
|
|
|
|
fail() {
|
|
echo "e2e: FAIL - $1" >&2
|
|
exit 1
|
|
}
|
|
|
|
mkdir -p "$TMP/hooks-none" || fail "mkdir hooks-none"
|
|
|
|
# newrepo <dir>: git init with no hooks, no signing, one identity.
|
|
newrepo() {
|
|
mkdir -p "$1" || fail "mkdir $1"
|
|
git -C "$1" init -q || fail "git init $1"
|
|
printf '[user]\n\tname = Test\n\temail = test@example.invalid\n[commit]\n\tgpgsign = false\n[core]\n\thooksPath = "%s"\n' \
|
|
"$TMP/hooks-none" >> "$1/.git/config" || fail "git config $1"
|
|
}
|
|
|
|
# jsonok <check> <js expression on j>: stdin is JSON; exit 0 when the expression is true.
|
|
jsonok() {
|
|
node -e 'let s="";process.stdin.on("data",(d)=>{s+=d;}).on("end",()=>{let j;try{j=JSON.parse(s);}catch(e){process.exit(3);}process.exit(Function("j","return ("+process.argv[1]+");")(j)?0:1);});' "$1"
|
|
}
|
|
|
|
# ---- 1. the park tool ----
|
|
|
|
P="$TMP/park"
|
|
newrepo "$P"
|
|
mkdir -p "$P/bin" "$P/tests" || fail "mkdir park dirs"
|
|
printf 'old\n' > "$P/bin/slug.mjs"
|
|
git -C "$P" add bin/slug.mjs || fail "git add bin/slug.mjs"
|
|
git -C "$P" commit -q -m "base" || fail "git commit base"
|
|
START="$(git -C "$P" rev-parse HEAD)" || fail "rev-parse start"
|
|
PARKED="$P/.git/voyage/parked"
|
|
|
|
# savediff <file> [color]: the "Back to planning" save line of commands/trekexecute.md;
|
|
# with "color", the same diff coloured (--color=always in place of --no-color).
|
|
savediff() {
|
|
color=--no-color
|
|
[ "${2:-}" = color ] && color=--color=always
|
|
git -C "$P" -c core.quotepath=off diff --no-ext-diff "$color" --no-renames --no-relative --src-prefix=a/ --dst-prefix=b/ "$START" > "$1" \
|
|
|| fail "git diff for $1"
|
|
}
|
|
|
|
# parked_ok <name> <attempt>: tests/<name> left the tree and the index and sits in the parked dir.
|
|
parked_ok() {
|
|
[ ! -e "$P/tests/$1" ] || fail "park $2: tests/$1 is still in the work tree"
|
|
if git -C "$P" ls-files --error-unmatch -- "tests/$1" >/dev/null 2>&1; then
|
|
fail "park $2: tests/$1 is still in the index"
|
|
fi
|
|
[ -f "$PARKED/step-1-attempt-$2/tests/$1" ] || fail "park $2: tests/$1 is not under the parked directory"
|
|
}
|
|
|
|
# 1a. an intent-to-add file
|
|
printf 'stale | cat\n' > "$P/tests/a.sh"
|
|
git -C "$P" add --intent-to-add -- tests/a.sh || fail "git add --intent-to-add tests/a.sh"
|
|
savediff "$TMP/park-1.diff"
|
|
grep -q '^new file mode' "$TMP/park-1.diff" || fail "park 1: the saved diff names no new file"
|
|
out="$(node "$PARK" --diff "$TMP/park-1.diff" --step 1 --attempt 1 --cwd "$P")" || fail "park 1 (intent-to-add) exited non-zero: $out"
|
|
parked_ok a.sh 1
|
|
|
|
# 1b. a coloured diff
|
|
printf 'stale\n' > "$P/tests/b.sh"
|
|
git -C "$P" add --intent-to-add -- tests/b.sh || fail "git add --intent-to-add tests/b.sh"
|
|
savediff "$TMP/park-2.diff" color
|
|
ESC="$(printf '\033')"
|
|
grep -q "$ESC" "$TMP/park-2.diff" || fail "park 2: control - the diff holds no colour code"
|
|
out="$(node "$PARK" --diff "$TMP/park-2.diff" --step 1 --attempt 2 --cwd "$P")" || fail "park 2 (coloured diff) exited non-zero: $out"
|
|
parked_ok b.sh 2
|
|
|
|
# 1c. from a subdirectory
|
|
printf 'stale\n' > "$P/tests/c.sh"
|
|
git -C "$P" add --intent-to-add -- tests/c.sh || fail "git add --intent-to-add tests/c.sh"
|
|
savediff "$TMP/park-3.diff"
|
|
out="$(node "$PARK" --diff "$TMP/park-3.diff" --step 1 --attempt 3 --cwd "$P/bin")" || fail "park 3 (--cwd bin) exited non-zero: $out"
|
|
parked_ok c.sh 3
|
|
[ "$(cat "$P/bin/slug.mjs")" = "old" ] || fail "park: a committed file was touched"
|
|
|
|
# ---- 2. the verdict gate with --repo ----
|
|
|
|
V="$TMP/verdict"
|
|
newrepo "$V"
|
|
mkdir -p "$TMP/project" || fail "mkdir project"
|
|
printf 'export const a = 1;\n' > "$V/a.mjs"
|
|
git -C "$V" add a.mjs || fail "git add a.mjs"
|
|
git -C "$V" commit -q -m "feat: the reviewed work" || fail "git commit reviewed"
|
|
REVIEWED="$(git -C "$V" rev-parse HEAD)" || fail "rev-parse reviewed"
|
|
REVIEW="$TMP/project/review.md"
|
|
|
|
cat > "$REVIEW" <<EOF
|
|
---
|
|
type: trekreview
|
|
review_version: "1.1"
|
|
task: "t"
|
|
slug: s
|
|
project_dir: .claude/projects/2026-10-23-s/
|
|
brief_path: .claude/projects/2026-10-23-s/brief.md
|
|
scope_sha_end: $REVIEWED
|
|
reviewed_files_count: 1
|
|
verdict: ALLOW
|
|
findings: []
|
|
---
|
|
|
|
# Review: t
|
|
|
|
## Executive Summary
|
|
|
|
x
|
|
|
|
## Coverage
|
|
|
|
| File | Treatment | Reason |
|
|
|---|---|---|
|
|
| a.mjs | summary-only | default |
|
|
|
|
## Remediation Summary
|
|
|
|
x
|
|
|
|
\`\`\`json
|
|
{
|
|
"verdict": "ALLOW",
|
|
"counts": { "BLOCKER": 0, "MAJOR": 0, "MINOR": 0, "SUGGESTION": 0 },
|
|
"allow_blocked_by": [],
|
|
"findings": []
|
|
}
|
|
\`\`\`
|
|
EOF
|
|
|
|
# gate: the merge lock line of commands/trekexecute.md, run in the repo being merged.
|
|
gate() {
|
|
(cd "$V" && node "$VERDICT" --json "$REVIEW" --repo .)
|
|
}
|
|
|
|
out="$(gate)" || fail "verdict 1: a review of HEAD did not exit 0: $out"
|
|
|
|
mkdir -p "$V/.claude/rules" || fail "mkdir .claude/rules"
|
|
# The block holds exactly BLOCK_HEADER (lib/review/recurrence.mjs); each \302\267 is the
|
|
# UTF-8 middle dot, written as octal escapes so this script stays ASCII.
|
|
printf '<!-- voyage:review-lessons:start -->\nEach line: a finding class /trekreview saw in two reviews, approved by the operator. Format: mistake \302\267 fix \302\267 how to check.\n<!-- voyage:review-lessons:end -->\n' \
|
|
> "$V/.claude/rules/voyage-review-lessons.md"
|
|
git -C "$V" add .claude/rules/voyage-review-lessons.md || fail "git add lessons"
|
|
git -C "$V" commit -q -m "docs(voyage): review lessons (round 1)" || fail "git commit lessons"
|
|
out="$(gate)" || fail "verdict 2: a later lessons-only commit did not exit 0: $out"
|
|
|
|
printf 'export const a = 2;\n' > "$V/a.mjs"
|
|
git -C "$V" add a.mjs || fail "git add a.mjs (later)"
|
|
git -C "$V" commit -q -m "feat: a later source change" || fail "git commit later"
|
|
out="$(gate)"
|
|
code=$?
|
|
[ "$code" -eq 2 ] || fail "verdict 3: a review of an older commit exited $code, not 2: $out"
|
|
printf '%s' "$out" | jsonok 'typeof j.reason === "string" && j.reason.indexOf("review-stale") === 0' \
|
|
|| fail "verdict 3: the reason does not start review-stale: $out"
|
|
|
|
# ---- 3. the arg parser for /trekendsession ----
|
|
|
|
out="$(node "$ARGS" --command trekendsession -- fix-header)" || fail "arg-parser on fix-header exited non-zero: $out"
|
|
printf '%s' "$out" | jsonok 'j.flags["--help"] === undefined && j.positional[0] === "fix-header"' \
|
|
|| fail "arg-parser: fix-header asked for help or is not the first positional: $out"
|
|
|
|
# ---- 4. the gates shim ----
|
|
|
|
out="$(node "$SHIM" --state idle --event start --gates closed)" || fail "shim --gates closed exited non-zero: $out"
|
|
printf '%s' "$out" | jsonok 'j.ok === true && j.next_state === "gates_on"' || fail "shim: --gates closed is not gates_on: $out"
|
|
out="$(node "$SHIM" --state idle --event start --gates)" || fail "shim bare --gates exited non-zero: $out"
|
|
printf '%s' "$out" | jsonok 'j.ok === true && j.next_state === "gates_on"' || fail "shim: a bare --gates is not gates_on: $out"
|
|
|
|
echo "e2e: ok"
|
|
exit 0
|