feat(plan): the plan gate keeps each reviewed text in the git directory and prints the diff for round 2+ [skip-docs]
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
7b46cdd788
commit
fb9a551ed8
3 changed files with 567 additions and 5 deletions
148
tests/e2e-critic-diff-mode.sh
Normal file
148
tests/e2e-critic-diff-mode.sh
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
#!/usr/bin/env bash
|
||||
# tests/e2e-critic-diff-mode.sh
|
||||
# End to end for the critic loop's diff mode (decision 43): the plan gate CLI
|
||||
# run the way /trekplan Phase 9 runs it, in a temp git repo.
|
||||
# 1. git init a temp repo and build a 1.8 plan with
|
||||
# tests/helpers/plan-v18-builder.mjs.
|
||||
# 2. --hash, a round-1 reviewer payload (one major) through
|
||||
# lib/review/plan-review-dedup.mjs --stdin into a file, then
|
||||
# --record < file (exit 0, REVISE).
|
||||
# 3. Edit one step line with node -e.
|
||||
# 4. --diff names the edited line (its number found with grep -n).
|
||||
# 5. Save a copy of the plan.
|
||||
# 6. A: round 2 marks the round-1 major resolved and adds a new major on a
|
||||
# line outside the edit (exit 0, PASS); the bare gate exits 0.
|
||||
# 7. Restore the copy. B: the same, with the new major on the edited line
|
||||
# (exit 0, REVISE); the bare gate exits 1 with PLAN_GATE_OPEN_MAJOR.
|
||||
# 8. git status --porcelain --untracked-files=all lists no snapshot; the
|
||||
# round-1 snapshot file exists under .git/voyage/plan-snapshots/.
|
||||
# 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)"
|
||||
GATE="$ROOT/lib/validators/plan-gate.mjs"
|
||||
DEDUP="$ROOT/lib/review/plan-review-dedup.mjs"
|
||||
|
||||
unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE
|
||||
|
||||
TMP="$(mktemp -d "${TMPDIR:-/tmp}/e2e-critic-diff-mode.XXXXXX")" || { echo "e2e: FAIL - mktemp" >&2; exit 1; }
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
|
||||
fail() {
|
||||
echo "e2e: FAIL - $1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
REPO="$TMP/repo"
|
||||
PLAN="$REPO/plan.md"
|
||||
|
||||
# gate <args...>: the plan gate run from inside the temp repo (findings cite plan.md).
|
||||
gate() {
|
||||
(cd "$REPO" && node "$GATE" "$@")
|
||||
}
|
||||
|
||||
# round <payload-file>: --hash, dedup --stdin, then --record; prints the record line.
|
||||
round() {
|
||||
h="$(gate --hash plan.md)" || fail "--hash exited non-zero"
|
||||
node "$DEDUP" --stdin < "$1" > "$TMP/dedup.json" || fail "plan-review-dedup --stdin on $1"
|
||||
gate --record plan.md --reviewed "$h" < "$TMP/dedup.json"
|
||||
}
|
||||
|
||||
# ---- 1. a temp git repo and a 1.8 plan ----
|
||||
|
||||
mkdir -p "$REPO" || fail "mkdir $REPO"
|
||||
git -C "$REPO" init -q || fail "git init $REPO"
|
||||
node --input-type=module -e '
|
||||
import { writeFileSync } from "node:fs";
|
||||
import { buildPlan } from "./tests/helpers/plan-v18-builder.mjs";
|
||||
writeFileSync(process.argv[1], buildPlan());
|
||||
' "$PLAN" || fail "could not write the plan with tests/helpers/plan-v18-builder.mjs"
|
||||
|
||||
# ---- 2. round 1: one major ----
|
||||
|
||||
L1="$(grep -n 'Verifies: run1()' "$PLAN" | head -n 1 | cut -d: -f1)"
|
||||
[ -n "$L1" ] || fail "no Verifies line for step 1 in the plan"
|
||||
M1='Step 1 verification never states the expected return value'
|
||||
printf '{"plan_critic":{"findings":[{"file":"plan.md","line":%s,"rule_key":"step-quality","severity":"major","text":"%s"}]},"scope_guardian":{"findings":[]}}\n' \
|
||||
"$L1" "$M1" > "$TMP/r1.json"
|
||||
H1="$(gate --hash plan.md)" || fail "--hash before round 1"
|
||||
out="$(round "$TMP/r1.json")"
|
||||
rc=$?
|
||||
[ "$rc" -eq 0 ] || fail "round 1 --record: exit $rc, expected 0: $out"
|
||||
echo "$out" | grep -q '"verdict":"REVISE"' || fail "round 1: no REVISE in: $out"
|
||||
ID1="$(node -e '
|
||||
const t = require("node:fs").readFileSync(process.argv[1], "utf8");
|
||||
const v = JSON.parse(t.match(/```json\n([\s\S]*?)\n```/)[1]);
|
||||
process.stdout.write(v.findings[0].id);
|
||||
' "$PLAN")" || fail "could not read the round-1 finding id"
|
||||
echo "$ID1" | grep -Eq '^[0-9a-f]{40}$' || fail "round-1 finding id is not 40 hex: $ID1"
|
||||
|
||||
# ---- 3. edit one step line ----
|
||||
|
||||
node -e '
|
||||
const fs = require("node:fs");
|
||||
const p = process.argv[1];
|
||||
const t = fs.readFileSync(p, "utf8");
|
||||
const from = "Add `run2()` to `src/mod2.mjs`.";
|
||||
if (!t.includes(from)) process.exit(1);
|
||||
fs.writeFileSync(p, t.replace(from, "Add `run2()` and its error path to `src/mod2.mjs`."));
|
||||
' "$PLAN" || fail "could not edit the Step 2 Changes line"
|
||||
LE="$(grep -n 'its error path' "$PLAN" | head -n 1 | cut -d: -f1)"
|
||||
[ -n "$LE" ] || fail "the edited line is not in the plan"
|
||||
|
||||
# ---- 4. --diff names the edited line ----
|
||||
|
||||
out="$(gate --diff plan.md)"
|
||||
rc=$?
|
||||
[ "$rc" -eq 0 ] || fail "--diff: exit $rc, expected 0: $out"
|
||||
echo "$out" | head -n 1 | grep -q "^diff-base: $H1 (round 1)\$" || fail "--diff: first line is not the round-1 base: $out"
|
||||
echo "$out" | grep -q "^@@ lines $LE-$LE @@\$" || fail "--diff: no hunk header for line $LE in: $out"
|
||||
echo "$out" | grep -q "^+$LE .*its error path" || fail "--diff: line $LE is not named as a new line in: $out"
|
||||
|
||||
# ---- 5. save a copy ----
|
||||
|
||||
cp "$PLAN" "$TMP/plan-after-edit.md" || fail "could not copy the plan"
|
||||
|
||||
# ---- 6. A: the new major is outside the edit ----
|
||||
|
||||
LO="$(grep -n 'checkout -- src/mod1.mjs' "$PLAN" | head -n 1 | cut -d: -f1)"
|
||||
[ -n "$LO" ] || fail "no On failure line for step 1 in the plan"
|
||||
MA='The rollback named for this step gives no check that the revert actually worked'
|
||||
printf '{"plan_critic":{"previous":[{"id":"%s","resolved":true}],"findings":[{"file":"plan.md","line":%s,"rule_key":"step-quality","severity":"major","text":"%s"}]},"scope_guardian":{"previous":[{"id":"%s","resolved":true}],"findings":[]}}\n' \
|
||||
"$ID1" "$LO" "$MA" "$ID1" > "$TMP/r2a.json"
|
||||
out="$(round "$TMP/r2a.json")"
|
||||
rc=$?
|
||||
[ "$rc" -eq 0 ] || fail "A: round 2 --record: exit $rc, expected 0: $out"
|
||||
echo "$out" | grep -q '"verdict":"PASS"' || fail "A: round 2 outside the diff is not PASS: $out"
|
||||
echo "$out" | grep -q "\"diff_base\":\"$H1\"" || fail "A: round 2 did not diff against round 1: $out"
|
||||
out="$(gate plan.md)"
|
||||
rc=$?
|
||||
[ "$rc" -eq 0 ] || fail "A: the bare gate exit $rc, expected 0: $out"
|
||||
|
||||
# ---- 7. B: the new major is on the edited line ----
|
||||
|
||||
cp "$TMP/plan-after-edit.md" "$PLAN" || fail "could not restore the plan"
|
||||
MB='The error path named here gives callers no thrown type to match on'
|
||||
printf '{"plan_critic":{"previous":[{"id":"%s","resolved":true}],"findings":[{"file":"plan.md","line":%s,"rule_key":"step-quality","severity":"major","text":"%s"}]},"scope_guardian":{"previous":[{"id":"%s","resolved":true}],"findings":[]}}\n' \
|
||||
"$ID1" "$LE" "$MB" "$ID1" > "$TMP/r2b.json"
|
||||
out="$(round "$TMP/r2b.json")"
|
||||
rc=$?
|
||||
[ "$rc" -eq 0 ] || fail "B: round 2 --record: exit $rc, expected 0: $out"
|
||||
echo "$out" | grep -q '"verdict":"REVISE"' || fail "B: round 2 inside the diff is not REVISE: $out"
|
||||
out="$(gate plan.md)"
|
||||
rc=$?
|
||||
[ "$rc" -eq 1 ] || fail "B: the bare gate exit $rc, expected 1: $out"
|
||||
echo "$out" | grep -q 'PLAN_GATE_OPEN_MAJOR' || fail "B: no PLAN_GATE_OPEN_MAJOR in: $out"
|
||||
|
||||
# ---- 8. snapshots stay in the git directory ----
|
||||
|
||||
st="$(git -C "$REPO" status --porcelain --untracked-files=all)" || fail "git status in the temp repo"
|
||||
echo "$st" | grep -q 'plan-snapshots' && fail "git status lists a snapshot: $st"
|
||||
[ "$st" = '?? plan.md' ] || fail "git status lists more than the plan: $st"
|
||||
[ -f "$REPO/.git/voyage/plan-snapshots/$H1.md" ] || fail "no round-1 snapshot under .git/voyage/plan-snapshots/"
|
||||
|
||||
echo "e2e: ok"
|
||||
exit 0
|
||||
Loading…
Add table
Add a link
Reference in a new issue