/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
109 lines
5.5 KiB
Bash
109 lines
5.5 KiB
Bash
#!/usr/bin/env bash
|
||
# reconcile.sh — deterministic engine for /reconcile.
|
||
#
|
||
# Confronts DECLARATIVE sources (TODO checkboxes, registry statuses) against REAL
|
||
# state (git, fs, registry BODY). It is the engine behind the /reconcile skill;
|
||
# the skill orchestrates + gates, this file holds the mechanical truth-probes.
|
||
#
|
||
# FOUNDING PRINCIPLE — recursive coherence (LRN-055): a reconciler must NEVER trust
|
||
# a declarative source as an oracle. So this engine NEVER reads the `## Index` table
|
||
# nor believes a `[x]`/`[ ]` checkbox; it enumerates registry entries from the BODY
|
||
# `## ID —` headings and decides "done/stale" from git/fs only. It practices what it
|
||
# preaches — the recursive-coherence test (run-reconcile.sh T1) reds if this is broken.
|
||
#
|
||
# HONEST LIMITS (graven, do not over-read):
|
||
# - Deferral detection is LEXICAL (reconcile_deferrals): it catches deferrals MARKED
|
||
# by a keyword, misses ones phrased without one ("à reprendre quand X"). Deterministic
|
||
# on the detectable; the skill SURFACES the ambiguous for human review, never asserts.
|
||
# - Contradiction detection surfaces CANDIDATES (token overlap), never asserts a verdict.
|
||
#
|
||
# Layers:
|
||
# reconcile_enumerate_ids — body-only ID enumeration (recursive-coherence core)
|
||
# reconcile_oracle_* — live git/fs truth probes
|
||
# reconcile_blk_current_status — registry status, LAST block wins (compound/UPDATE/FINAL)
|
||
# reconcile_blk_open — blockers whose CURRENT status is not resolved
|
||
# reconcile_verdict — pure kernel: declared checkbox × real fact → verdict
|
||
# reconcile_deferrals — lexical deferral sweep (honest limit)
|
||
# reconcile_contradiction_candidates — accepted-BDR ⇄ open-chantier token overlap (surface)
|
||
set -uo pipefail
|
||
|
||
RECONCILE_GREP="${RECONCILE_GREP:-/usr/bin/grep}" # LRN-074: pin grep, never assume GNU flags
|
||
# markers that LEXICALLY signal a deferral/follow-up (honest limit: marked-only)
|
||
RECONCILE_DEFER_RE='[Dd]efer|[Ff]ollow-?up|[Oo]ut.of.scope|OUT-OF-SCOPE|[Rr]econsider|[Rr]evisit|2e passage|won.t.do|optional\)|one-line ticket'
|
||
|
||
# --- recursive coherence: enumerate IDs from the BODY, never the ## Index ---
|
||
# $1 registry file, $2 prefix (BDR|LRN|BLK|EVAL). Emits one id per line, unique.
|
||
reconcile_enumerate_ids() {
|
||
"$RECONCILE_GREP" -oE "^## ${2}-[0-9]+" "$1" | "$RECONCILE_GREP" -oE "${2}-[0-9]+" | sort -u
|
||
}
|
||
|
||
# --- live truth probes (query the REAL repo/fs; this is the "verify, don't believe" core) ---
|
||
reconcile_oracle_tree_clean() { # rc 0 = working tree clean
|
||
[ -z "$(git -C "${1:-.}" status --porcelain 2>/dev/null)" ]
|
||
}
|
||
reconcile_oracle_merge_done() { # $1 repo, $2 branch fragment → rc 0 if a merge commit exists
|
||
[ -n "$(git -C "${1:-.}" log --oneline --grep "Merge .*$2" -1 2>/dev/null)" ]
|
||
}
|
||
reconcile_oracle_pushed() { # $1 repo, $2 branch → rc 0 if nothing unpushed vs origin
|
||
[ -z "$(git -C "${1:-.}" rev-list "origin/$2..$2" 2>/dev/null)" ]
|
||
}
|
||
reconcile_oracle_sha_exists() { # $1 repo, $2 sha → rc 0 if the commit object exists
|
||
git -C "${1:-.}" cat-file -e "${2}^{commit}" 2>/dev/null
|
||
}
|
||
reconcile_oracle_msg_committed() { # $1 repo, $2 grep → rc 0 if a commit message matches
|
||
[ -n "$(git -C "${1:-.}" log --oneline --grep "$2" -1 2>/dev/null)" ]
|
||
}
|
||
reconcile_oracle_path_present() { [ -e "${1:?}" ]; } # $1 path → rc 0 if it still exists on disk
|
||
|
||
# --- registry status: LAST status-bearing line wins (the BLK-008 trap A fell into) ---
|
||
# $1 blockers file, $2 id. Echoes the current status line (compound/UPDATE/FINAL aware).
|
||
reconcile_blk_current_status() {
|
||
# drop ALL `## BLK-` header lines first: the range is inclusive of the NEXT entry's
|
||
# header, and a sibling header may carry a status word (e.g. BLK-005 "...upstream rename")
|
||
# → cross-entry bleed. The entry's own header carries no status, so dropping it is safe.
|
||
sed -n "/^## ${2} /,/^## BLK-/p" "$1" \
|
||
| "$RECONCILE_GREP" -v '^## BLK-' \
|
||
| "$RECONCILE_GREP" -iE 'status|RESOLVED|REVERTED|upstream|resolved|[^a-z]open' \
|
||
| tail -1
|
||
}
|
||
# blockers whose CURRENT status is not resolved → emits "id<TAB>status"
|
||
reconcile_blk_open() {
|
||
local id st
|
||
for id in $(reconcile_enumerate_ids "$1" BLK); do
|
||
st=$(reconcile_blk_current_status "$1" "$id")
|
||
case "$st" in
|
||
*RESOLVED*|*resolved*) : ;;
|
||
*) printf '%s\t%s\n' "$id" "$st" ;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
# --- pure reconciliation kernel: declared checkbox × real fact → verdict (no git, fully testable) ---
|
||
# $1 checkbox char (x| |~), $2 real_done (true|false).
|
||
reconcile_verdict() {
|
||
case "$1:$2" in
|
||
" :true") echo "STALE:open-but-done" ;;
|
||
"x:false") echo "STALE:done-but-open" ;;
|
||
"~:true") echo "STALE:partial-but-done" ;;
|
||
*) echo "CONSISTENT" ;;
|
||
esac
|
||
}
|
||
|
||
# --- lexical deferral sweep (HONEST LIMIT: marked-only) → "src<TAB>line<TAB>text" ---
|
||
reconcile_deferrals() {
|
||
[ -f "$1" ] && "$RECONCILE_GREP" -nE "$RECONCILE_DEFER_RE" "$1" 2>/dev/null | sed 's/^/TODO\t/'
|
||
[ -f "$2" ] && "$RECONCILE_GREP" -nE "$RECONCILE_DEFER_RE" "$2" 2>/dev/null | sed 's/^/BDR\t/'
|
||
return 0
|
||
}
|
||
|
||
# --- contradiction CANDIDATES (surface, never assert): CLI-flag token shared by a BDR + the TODO ---
|
||
# $1 decisions file, $2 todo file. CLI-flag-like tokens are distinctive enough to flag for review.
|
||
reconcile_contradiction_candidates() {
|
||
local tok
|
||
for tok in $("$RECONCILE_GREP" -oE '\-\-[a-z][a-z-]+' "$1" 2>/dev/null | sort -u); do
|
||
"$RECONCILE_GREP" -qF -- "$tok" "$2" 2>/dev/null \
|
||
&& printf 'CANDIDATE\t%s\tflag "%s" in a BDR title and an open TODO chantier — review for contradiction\n' "$tok" "$tok"
|
||
done
|
||
return 0
|
||
}
|