repl: env var was dead code (hardcoded 2s), empty frame gave misleading error check-tangle: show full SBCL output on compile failure, not filtered verify-repl: blacklist now configurable via VERIFY_REPL_EXCLUDE env var; regex tightened to ';; +REPL-VERIFIED:' from ';;.REPL.VERIFIED:' (ambiguous) org-eval: 1-based indexing to match repl-block; errors on out-of-range; errors on <1
150 lines
4.7 KiB
Bash
Executable File
150 lines
4.7 KiB
Bash
Executable File
#!/bin/bash
|
|
# verify-repl — compliance checker for the OpenCode Engineering Discipline
|
|
#
|
|
# Usage: verify-repl <org-directory>
|
|
# Scans all .org files in the given directory for violations of:
|
|
# 1. REPL-First: every (defun|defmacro|defvar|defparameter|defstruct|defmethod|defclass)
|
|
# block must have ";; REPL-VERIFIED:" on the line above #+begin_src lisp
|
|
# 2. One-per-block: each #+begin_src lisp block must contain exactly one top-level form
|
|
# 3. Prose-before-code: each code block must be preceded by an Org headline
|
|
#
|
|
# Returns 0 if all checks pass, 1 if violations found.
|
|
|
|
set -euo pipefail
|
|
|
|
ORG_DIR="${1:-}"
|
|
if [ -z "$ORG_DIR" ] || [ ! -d "$ORG_DIR" ]; then
|
|
echo "Usage: verify-repl <org-directory>"
|
|
exit 1
|
|
fi
|
|
|
|
VIOLATIONS=0
|
|
FILES_CHECKED=0
|
|
|
|
# Blacklist: files exempt from REPL verification (core infrastructure, tests)
|
|
# Override with $VERIFY_REPL_EXCLUDE (space-separated filenames).
|
|
# Example: VERIFY_REPL_EXCLUDE="setup.org package.lisp" verify-repl org/
|
|
DEFAULT_BLACKLIST=(
|
|
"core-defpackage.org"
|
|
"core-manifest.org"
|
|
"core-skills.org"
|
|
"core-communication.org"
|
|
"package.lisp"
|
|
"setup.org"
|
|
)
|
|
|
|
if [ -n "${VERIFY_REPL_EXCLUDE:-}" ]; then
|
|
IFS=' ' read -ra BLACKLIST <<< "$VERIFY_REPL_EXCLUDE"
|
|
else
|
|
BLACKLIST=("${DEFAULT_BLACKLIST[@]}")
|
|
fi
|
|
|
|
is_blacklisted() {
|
|
local fname
|
|
fname=$(basename "$1")
|
|
for bl in "${BLACKLIST[@]}"; do
|
|
[ "$fname" = "$bl" ] && return 0
|
|
done
|
|
return 1
|
|
}
|
|
|
|
check_file() {
|
|
local file="$1"
|
|
local fname
|
|
fname=$(basename "$file")
|
|
local in_block=0
|
|
local block_start=0
|
|
local prev_line=""
|
|
local prev_was_headline=0
|
|
local block_content=""
|
|
local line_no=0
|
|
local has_repl_verify=0
|
|
local def_count=0
|
|
local violations_in_file=0
|
|
|
|
is_blacklisted "$file" && return 0
|
|
|
|
FILES_CHECKED=$((FILES_CHECKED + 1))
|
|
|
|
while IFS= read -r line || [ -n "$line" ]; do
|
|
line_no=$((line_no + 1))
|
|
local trimmed="${line#"${line%%[![:space:]]*}"}"
|
|
|
|
# Track headlines
|
|
if echo "$trimmed" | grep -qE '^\*+[[:space:]]'; then
|
|
prev_was_headline=1
|
|
fi
|
|
|
|
# Enter code block
|
|
if echo "$trimmed" | grep -qE '^#\+begin_src[[:space:]]+lisp'; then
|
|
in_block=1
|
|
block_start=$line_no
|
|
block_content=""
|
|
def_count=0
|
|
has_repl_verify=0
|
|
# Check for REPL-VERIFIED comment on previous line(s)
|
|
# Matches ";; REPL-VERIFIED:" or ";; REPL-VERIFIED:" etc.
|
|
if echo "$prev_line" | grep -qE ';; +REPL[-_]VERIFIED:'; then
|
|
has_repl_verify=1
|
|
fi
|
|
# Check for prose requirement: was there a headline before this block?
|
|
if [ "$prev_was_headline" -eq 0 ]; then
|
|
echo " $fname:$block_start: PROSE-BEFORE-CODE: no Org headline before code block"
|
|
violations_in_file=$((violations_in_file + 1))
|
|
fi
|
|
fi
|
|
|
|
# Inside code block: collect content
|
|
if [ "$in_block" -eq 1 ]; then
|
|
if echo "$trimmed" | grep -qE '^#\+end_src'; then
|
|
in_block=0
|
|
# Check: REPL verification for definition blocks
|
|
if [ "$def_count" -gt 0 ] && [ "$has_repl_verify" -eq 0 ]; then
|
|
echo " $fname:$block_start: REPL-FIRST: $(if [ "$def_count" -gt 1 ]; then echo "$def_count definitions"; else echo "defun/defmacro"; fi) without REPL-VERIFIED comment"
|
|
violations_in_file=$((violations_in_file + 1))
|
|
fi
|
|
# Check: one-per-block
|
|
if [ "$def_count" -gt 1 ]; then
|
|
echo " $fname:$block_start: ONE-PER-BLOCK: $def_count definitions in a single block (must be exactly 1)"
|
|
violations_in_file=$((violations_in_file + 1))
|
|
fi
|
|
prev_was_headline=0
|
|
block_content=""
|
|
else
|
|
# Count definitions in block
|
|
if echo "$trimmed" | grep -qE '^\((defun|defmacro|defvar|defparameter|defstruct|defmethod|defclass)[[:space:](]'; then
|
|
def_count=$((def_count + 1))
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
prev_line="$line"
|
|
done < "$file"
|
|
|
|
if [ "$violations_in_file" -gt 0 ]; then
|
|
echo " ✗ $fname: $violations_in_file violation(s)"
|
|
VIOLATIONS=$((VIOLATIONS + violations_in_file))
|
|
fi
|
|
}
|
|
|
|
echo "=== REPL Compliance Check ==="
|
|
echo "Directory: $ORG_DIR"
|
|
echo ""
|
|
|
|
for file in "$ORG_DIR"/*.org; do
|
|
[ -f "$file" ] || continue
|
|
check_file "$file"
|
|
done
|
|
|
|
echo ""
|
|
echo "Files checked: $FILES_CHECKED"
|
|
echo "Violations: $VIOLATIONS"
|
|
|
|
if [ "$VIOLATIONS" -eq 0 ]; then
|
|
echo "Status: ✓ PASS — all checks satisfied"
|
|
exit 0
|
|
else
|
|
echo "Status: ✗ FAIL — fix violations before committing"
|
|
exit 1
|
|
fi
|