diff --git a/.opencode/commands/check-parens.md b/.opencode/commands/check-parens.md index 54b9cbc..41ab451 100644 --- a/.opencode/commands/check-parens.md +++ b/.opencode/commands/check-parens.md @@ -3,6 +3,9 @@ description: Check paren balance in lisp blocks of .org files --- Run `projects/check-parens/check-parens` on the given .org files to verify all -`#+begin_src lisp` blocks have balanced parentheses. +`#+begin_src lisp` blocks have balanced parentheses. Uses SBCL's reader for +100% accuracy — no false positives from string literals or character literals. + +Exit 0 if all blocks balanced, 1 if any issues found. Usage: /check-parens [ ...] diff --git a/.opencode/commands/check-tangle.md b/.opencode/commands/check-tangle.md index 8b20cef..430ca7d 100644 --- a/.opencode/commands/check-tangle.md +++ b/.opencode/commands/check-tangle.md @@ -3,6 +3,13 @@ description: Tangle an org file and compile the result --- Tangle an .org file to .lisp then compile with SBCL. Reports the first -compile error. Exit 0 = clean, exit 1 = compilation error. +compile error with line numbers. Exit 0 = clean compile, exit 1 = error. + +Prepares code for commit by ensuring the tangled .lisp file is syntactically +valid. Catches missing symbols, undefined functions, and type errors before +they reach the running daemon. Usage: /check-tangle + +Example: + /check-tangle projects/passepartout/org/channel-tui-main.org diff --git a/.opencode/commands/repl-block.md b/.opencode/commands/repl-block.md index b35d8c5..f93ead3 100644 --- a/.opencode/commands/repl-block.md +++ b/.opencode/commands/repl-block.md @@ -2,12 +2,16 @@ description: Send a lisp block from an org file to the REPL --- -Extract a `#+begin_src lisp` block from an .org file and send it to the -running daemon REPL. +Extract a `#+begin_src lisp` block from an .org file and pipe it to the +running daemon REPL. Identify the block by function name or index. + +The `--package` flag wraps the block in `(in-package ...)` so it evaluates +in the right namespace — essential when the block references symbols from +a specific package without the package prefix. Usage: /repl-block --function - /repl-block --block /repl-block --function --package + /repl-block --block -The --package flag wraps the block in an (in-package ...) form. -Use --block to identify by 1-based index, --function to find by defun name. +Example: + /repl-block projects/passepartout/org/channel-tui-view.org --function view-status --package :passepartout.channel-tui diff --git a/projects/check-tangle/README.org b/projects/check-tangle/README.org new file mode 100644 index 0000000..3a88e35 --- /dev/null +++ b/projects/check-tangle/README.org @@ -0,0 +1,22 @@ +#+TITLE: check-tangle + +Tangle an .org file and compile the resulting .lisp with SBCL in one step. + +== Usage + +#+begin_src shell + check-tangle org/file.org +#+end_src + +Exit 0 if compilation succeeds, 1 if tangling or compilation fails. + +== What it checks + +1. Reads the ~:tangle~ header from the org file to find the target .lisp path +2. Runs ~tangle~ to generate the .lisp from the .org source +3. Runs ~sbcl compile-file~ on the result +4. Reports the first compile error if any + +== Requires + +Emacs (for tangling), SBCL (for compilation). diff --git a/projects/check-tangle/check-tangle b/projects/check-tangle/check-tangle index 9ae00ab..0534471 100755 --- a/projects/check-tangle/check-tangle +++ b/projects/check-tangle/check-tangle @@ -28,13 +28,26 @@ fi # Resolve relative tangle path ORG_DIR=$(dirname "$ORG_FILE") -LISP_FILE=$(cd "$ORG_DIR" && realpath -m "$TANGLE" 2>/dev/null || echo "$ORG_DIR/$TANGLE") +if [ "$TANGLE" = "${TANGLE#/}" ]; then + # Relative path + LISP_FILE=$(cd "$ORG_DIR" && realpath -m "$TANGLE" 2>/dev/null || echo "$ORG_DIR/$TANGLE") +else + # Absolute path + LISP_FILE="$TANGLE" +fi echo "Tangling: $ORG_FILE → $LISP_FILE" >&2 -# Tangle using opencode's tangle tool -TANGLE_CMD=$(command -v tangle 2>/dev/null || echo "/home/user/.opencode/bin/tangle") -if ! "$TANGLE_CMD" "$ORG_FILE" 2>/dev/null; then +# Prefer the memex's own tangle tool, then fall back to PATH +if [ -x "projects/tangle-tool/tangle" ]; then + TANGLE_CMD="projects/tangle-tool/tangle" +elif command -v tangle &>/dev/null; then + TANGLE_CMD="tangle" +else + TANGLE_CMD="/home/user/.opencode/bin/tangle" +fi + +if ! "$TANGLE_CMD" "$ORG_FILE"; then echo "FAIL: Tangling $ORG_FILE failed" >&2 exit 1 fi diff --git a/projects/repl-block/README.org b/projects/repl-block/README.org new file mode 100644 index 0000000..8c5a564 --- /dev/null +++ b/projects/repl-block/README.org @@ -0,0 +1,28 @@ +#+TITLE: repl-block + +Extract a ~#+begin_src lisp~ block from an .org file and output it to stdout, +ready to pipe to ~repl~ or inspect. + +== Usage + +#+begin_src shell + repl-block org/file.org --function foo | repl + repl-block org/file.org --block 3 + repl-block org/file.org --function foo --package :my-package + repl-block org/file.org # list all blocks +#+end_src + +== Identifying blocks + +By function name (--function): scans all blocks for (defun ...) and +outputs the containing block. By index (--block): 1-based position in file. + +The --package flag prepends an ~(in-package ...)~ form so the REPL evaluates +in the right namespace. + +Combine with ~repl~ to send a block directly to the daemon: + repl-block org/channel-tui-view.org --function view-status --package :passepartout.channel-tui | repl + +== Requires + +Python 3. No dependencies. diff --git a/projects/repl-block/repl-block b/projects/repl-block/repl-block index 64d646b..ca5019d 100755 --- a/projects/repl-block/repl-block +++ b/projects/repl-block/repl-block @@ -41,7 +41,7 @@ def extract_blocks(lines): return blocks -def find_by_function(blocks, name, lines): +def find_by_function(blocks, name): for line_no, body in blocks: for bline in body: if re.match(rf"\(def(un|macro|method|var|parameter|class|struct|package)\s+{re.escape(name)}\b", bline): @@ -76,7 +76,7 @@ def main(): blocks = extract_blocks(lines) if args.function: - line_no, body = find_by_function(blocks, args.function, lines) + line_no, body = find_by_function(blocks, args.function) if body is None: print(f"No block found containing function '{args.function}'", file=sys.stderr) return 1 @@ -86,12 +86,12 @@ def main(): print(f"Block {args.block} not found (file has {len(blocks)} blocks)", file=sys.stderr) return 1 else: - # Print listing + # Print listing to stderr so piping still works for idx, (line_no, body) in enumerate(blocks, 1): first = (body or [""])[0][:60] - print(f" {idx}: line {line_no}: {first}") + print(f" {idx}: line {line_no}: {first}", file=sys.stderr) print(f"\n{len(blocks)} total blocks", file=sys.stderr) - return 1 + return 0 if args.package: pkg = args.package