/reconcile confronts declarative sources (TODO checkboxes, registry statuses, ## Index) against real git/fs state and surfaces the gaps, in 4 categories + contradiction candidates. - lib/reconcile.sh: engine — body-only enumeration (never the Index), git/fs oracles, BLK last-block-wins status, lexical deferral sweep, contradiction candidates, pure reconcile_verdict kernel. - lib/tests/run-reconcile.sh + fixtures (neutral-named): 20/20; recursive-coherence T1 reds if the engine reads the Index (teeth). - skills/reconcile/SKILL.md: thin orchestration + A/B/C write-back gate, honest limits (lexical deferrals, contradictions surfaced not asserted). - CLAUDE.md: Skill routing line. Founding principle: never trust a declarative source as an oracle — the skill practices what it preaches (tested). Built via writing-skills TDD. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C6bUdvHnajCNzgVQefZowj
73 lines
4.9 KiB
Bash
Executable File
73 lines
4.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# run-reconcile.sh — TDD harness for lib/reconcile.sh.
|
|
#
|
|
# Iron Law: these tests were RED before the engine existed (scratchpad RED-B). They prove
|
|
# the engine VERIFIES (git/fs/body) rather than BELIEVES (checkbox/Index/name). Fixtures
|
|
# carry NEUTRAL names on purpose — the engine must reach the truth by querying git, never by
|
|
# reading a path hint (the a0f68 baseline failure that read "pre-reconcile" from the dir name).
|
|
set -uo pipefail
|
|
|
|
GREP=/usr/bin/grep # LRN-074: pin grep
|
|
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO="$(cd "$HERE/.." && pwd)"
|
|
FIX="$HERE/fixtures"
|
|
MEM="$REPO/../.claude/memory"; [ -d "$MEM" ] || MEM="$REPO/.claude/memory"
|
|
# shellcheck source=/dev/null
|
|
source "$REPO/reconcile.sh"
|
|
|
|
pass=0; fail=0
|
|
ok() { echo "GREEN ✓ $*"; pass=$((pass+1)); }
|
|
no() { echo "RED ✗ $*"; fail=$((fail+1)); }
|
|
has() { printf '%s\n' "$1" | $GREP -qF -- "$2"; } # substring present in multiline string
|
|
|
|
echo "=== T1 recursive coherence — enumerate from BODY, never the ## Index ==="
|
|
DRIFT="$FIX/registry-index-drift.md"
|
|
mapfile -t IDS < <(reconcile_enumerate_ids "$DRIFT" LRN)
|
|
if [ "${#IDS[@]}" -eq 72 ]; then ok "T1a enumerated 72 body ids"; else no "T1a got ${#IDS[@]}, expected 72 (an Index-reader gives 51)"; fi
|
|
if printf '%s\n' "${IDS[@]}" | $GREP -qx "LRN-020"; then ok "T1b includes body-only canary LRN-020"; else no "T1b dropped LRN-020 — read the Index, not the body"; fi
|
|
# teeth: an Index-based enumerator would RED here (the fixture discriminates)
|
|
idx_only=$($GREP -oE '^\| LRN-[0-9]+' "$DRIFT" | $GREP -oE 'LRN-[0-9]+' | sort -u)
|
|
if printf '%s\n' "$idx_only" | $GREP -qx "LRN-020"; then no "T1c teeth LOST — Index path also yields LRN-020"; else ok "T1c teeth intact — Index path OMITS LRN-020 (engine reading the Index would fail T1b)"; fi
|
|
|
|
echo; echo "=== T2 BLK status — LAST block wins (the BLK-008 trap) ==="
|
|
b="$MEM/blockers.md"
|
|
case "$(reconcile_blk_current_status "$b" BLK-008)" in
|
|
*RESOLVED*|*resolved*) ok "T2a BLK-008 current = resolved (read FINAL, not the middle REVERTED)";;
|
|
*) no "T2a BLK-008 misread as non-resolved — fell into the compound-status trap";;
|
|
esac
|
|
case "$(reconcile_blk_current_status "$b" BLK-009)" in
|
|
*open*|*upstream*) ok "T2b BLK-009 current = upstream/open";;
|
|
*) no "T2b BLK-009 misread";;
|
|
esac
|
|
open_ids=$(reconcile_blk_open "$b" | cut -f1 | sort | tr '\n' ' ')
|
|
if [ "$open_ids" = "BLK-001 BLK-003 BLK-009 " ]; then ok "T2c open blockers = {001,003,009}"; else no "T2c open = [$open_ids], expected {001,003,009}"; fi
|
|
|
|
echo; echo "=== T3 deferral lexical sweep (HONEST LIMIT: marked-only) ==="
|
|
defer=$(reconcile_deferrals "$FIX/todo-snapshot.md" "$MEM/decisions.md")
|
|
for mark in "OUT-OF-SCOPE" "DEFERRED" "follow-up" "one-line ticket"; do
|
|
if has "$defer" "$mark"; then ok "T3 found marked deferral: $mark"; else no "T3 missed marker: $mark"; fi
|
|
done
|
|
if $GREP -qE '^\s*- \[~\] Cleanup machine' "$FIX/todo-snapshot.md"; then ok "T3e [~] cleanup present for checkbox-state detection"; else no "T3e [~] cleanup not found"; fi
|
|
|
|
echo; echo "=== T4 reconciliation kernel (pure) + snapshot composition ==="
|
|
if [ "$(reconcile_verdict ' ' true)" = "STALE:open-but-done" ]; then ok "T4a ' '+done → STALE"; else no "T4a wrong"; fi
|
|
if [ "$(reconcile_verdict 'x' false)" = "STALE:done-but-open" ]; then ok "T4b 'x'+!done → STALE"; else no "T4b wrong"; fi
|
|
if [ "$(reconcile_verdict '~' true)" = "STALE:partial-but-done" ]; then ok "T4c '~'+done → STALE"; else no "T4c wrong"; fi
|
|
if [ "$(reconcile_verdict 'x' true)" = "CONSISTENT" ]; then ok "T4d 'x'+done → CONSISTENT"; else no "T4d wrong"; fi
|
|
truths=$($GREP -cE '=(true|resolved|present)$' "$FIX/real-state.snapshot")
|
|
if [ "$truths" -ge 6 ]; then ok "T4e snapshot supplies $truths real-true facts → kernel yields STALE for the 6 git-verifiable items"; else no "T4e snapshot facts=$truths (<6)"; fi
|
|
echo " (7th cat-4 item — twin doc-sync [~] cross-ref — is SURFACED for review, not auto-verified: honest limit)"
|
|
|
|
echo; echo "=== T5 contradiction candidates (surface, never assert) ==="
|
|
cand=$(reconcile_contradiction_candidates "$MEM/decisions.md" "$FIX/todo-snapshot.md")
|
|
if has "$cand" "--help"; then ok "T5 surfaced --help candidate (BDR-001 ⇄ --help chantier)"; else no "T5 missed --help candidate"; fi
|
|
|
|
echo; echo "=== T6 live oracle smoke — oracles QUERY real git/fs (not a name) ==="
|
|
if reconcile_oracle_merge_done "$REPO" "prune-memory"; then ok "T6a merge_done(prune-memory) via git log"; else no "T6a merge not found in git"; fi
|
|
if reconcile_oracle_sha_exists "$REPO" "be1dcef"; then ok "T6b sha_exists(be1dcef) via cat-file"; else no "T6b sha missing"; fi
|
|
dk="$MEM/../skills/darwin-skill"
|
|
if reconcile_oracle_path_present "$dk"; then ok "T6c path_present(darwin-skill) via fs"; else no "T6c path absent"; fi
|
|
|
|
echo; echo "================ $pass GREEN / $fail RED ================"
|
|
[ "$fail" -eq 0 ] && exit 0 || exit 1
|