tools: add repl-block, check-tangle; move existing tools into memex
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
This commit is contained in:
140
projects/verify-repl-tool/verify-repl
Executable file
140
projects/verify-repl-tool/verify-repl
Executable file
@@ -0,0 +1,140 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user