New tools (projects/<tool>/ — standalone, git-committed): - repl-block: extract and pipe lisp blocks from org files to the REPL - check-tangle: tangle + compile in one step, reports errors Existing tools moved from ~/.opencode/bin/ into memex (survives reinstalls): - repl, tangle, org-eval, verify-repl AGENTS.md updated: - Tool reference table with all 7 tools - Package reference table for passepartout and cl-tty - Updated tangle command to use project-local tools .opencode/commands/ added: check-parens, repl-block, check-tangle commands
141 lines
4.4 KiB
Bash
Executable File
141 lines
4.4 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)
|
|
BLACKLIST=(
|
|
"core-defpackage.org"
|
|
"core-manifest.org"
|
|
"core-skills.org"
|
|
"core-communication.org"
|
|
"package.lisp"
|
|
"setup.org"
|
|
)
|
|
|
|
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)
|
|
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
|