diff options
174 files changed, 4037 insertions, 578 deletions
diff --git a/.ai/notes.org b/.ai/notes.org index c49c9ee..5ce7047 100644 --- a/.ai/notes.org +++ b/.ai/notes.org @@ -1,5 +1,5 @@ #+TITLE: Claude Code Notes - Rulesets -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-05-06 * About This File @@ -61,7 +61,7 @@ This section tracks decisions that need Craig's input before work can proceed. ** Current Reminders -(None currently — will be added as needed) +- =[2026-07-14]= Review the sentry spec (docs/specs/2026-07-14-sentry-workflow-spec.org) — Craig's explicit ask at wrap: strongly suggest he reviews it before ending the next session. All 12 review findings and 10 decisions are resolved and folded in; the spec is open in his Emacs; the READY flip and the [#B] build task both wait on his deep read. ** Instructions for This Section @@ -82,6 +82,6 @@ Format: Markers maintained by workflows to record when they last ran. Read by other workflows that gate their behavior on freshness. :LAST_AUDIT: 2026-07-04 -:LAST_INBOX_PROCESS: 2026-07-04 (1 handoff: leftover kb-hygiene script report — already filed as todo.org [#C] KB orphan-node review pass; deleted the duplicate) +:LAST_INBOX_PROCESS: 2026-07-14 (3 handoffs: sentry proposal from work accepted + worked in-session; archsetup FYI acknowledged; lint-followups.org deleted per Craig) Format: one =:MARKER: YYYY-MM-DD= line per workflow. Workflows overwrite their own marker on completion. diff --git a/.ai/protocols.org b/.ai/protocols.org index 5e18ab9..5cd69d4 100644 --- a/.ai/protocols.org +++ b/.ai/protocols.org @@ -1,5 +1,5 @@ #+TITLE: Claude Code Protocols -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2025-11-05 * About This File @@ -367,6 +367,12 @@ Craig's shell aliases =ls= to =exa=, which prints nothing to non-TTY pipes (e.g. - Applies to =ls -la=, =ls -t=, glob expansions piped through =ls=, and any =ls= invocation whose output gets read programmatically. - Symptom if forgotten: the Bash tool returns empty output and you mistakenly conclude the directory is empty. +*** zsh does not word-split unquoted variables +The Bash tool runs zsh, which (unlike bash) does not split an unquoted =$var= on whitespace. =chrome $urls= passes all the space-joined URLs as one malformed argument. + +- Loop over the values, use an array, or force the split with =${=var}=. +- Symptom if forgotten: a command that "works in bash" gets one garbled argument and fails, often silently (from the takuzu session, 2026-07-11). + ** Miscellaneous Information - Craig currently lives in New Orleans, LA - Craig's phone number: 510-316-9357 @@ -406,14 +412,27 @@ Full usage: =notify --help= or see =~/.local/bin/notify= - =atq= - list all scheduled alarms - =atrm [number]= - remove an alarm by its queue number -** Paging Craig — desktop vs. away from the machine +** Paging Craig — the agent pager -"Page me" has two channels; pick by where Craig is. +"Page me" has two channels; pick by where Craig is. Both work from any agent runtime — nothing here is Claude-specific. - *At his laptop/desktop* — desktop =notify ... --persist= (above). It reaches him on the machine and stays up until dismissed. -- *Away from his laptop/desktop* — page his phone over Signal via the *signal-mcp* tool =send_message_to_user=, addressed to Craig's account UUID =b1b5601e-6126-47f8-afaa-0a59f5188fde= (his primary number reads as unregistered in Signal's directory — never page a phone number). The message goes out from the dedicated pager account (+15045173983) and fires a normal mobile push. This is the live cross-device path, verified working 2026-06-30. -Do *not* use the old =page-signal= shell script — it was removed from the rulesets canonical 2026-06-12 and its =~/.local/bin/page-signal= symlink no longer exists. The signal-mcp tool is the only supported Signal path; =notify --persist= is the only supported desktop path. + #+begin_src bash + notify info "Title" "Message" --persist + #+end_src + +- *Away from his laptop/desktop* — page his phone over Signal with the *agent pager*: + + #+begin_src bash + agent-page "Message for Craig's phone" + #+end_src + + =agent-page= (in =~/.local/bin= via the rulesets install) sends from the dedicated pager identity (+15045173983, registered in velox's signal-cli) to Craig's Signal account UUID, firing a normal mobile push. On velox it sends directly; on any other tailnet machine it ssh-relays the send to velox. Verified end to end 2026-07-13. Never page Craig's phone *number* — it reads as unregistered in Signal's directory; the script already targets the UUID. + + Caveats: velox must be up and on the tailnet (the script says so and names the desktop fallback when the relay fails), and the signal-cli account wants a periodic =receive= — both tracked on the rulesets Signal-pager task, which owns the full runbook. + +On velox, Claude sessions may also have the *signal-mcp* tool (=send_message_to_user=, same pager identity) — fine to use there, but it exists only in velox's local MCP config, so =agent-page= is the portable habit. Do *not* use the old =page-signal= shell script (removed 2026-06-12). * Session Protocols diff --git a/.ai/references/calendar-reference.org b/.ai/references/calendar-reference.org index b44c0f1..5791b08 100644 --- a/.ai/references/calendar-reference.org +++ b/.ai/references/calendar-reference.org @@ -1,5 +1,5 @@ #+TITLE: Calendar Reference -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings Tool recipes, authentication, and credentials for Craig's calendar setup. Three access methods, in order of preference. diff --git a/.ai/scripts/lint-org.el b/.ai/scripts/lint-org.el index 90b1b1d..55727ef 100644 --- a/.ai/scripts/lint-org.el +++ b/.ai/scripts/lint-org.el @@ -2,16 +2,19 @@ ;; ;; Usage: ;; emacs --batch -q -l lint-org.el FILE.org [FILE.org ...] +;; report only (the default) — categorize without modifying the file. +;; A linter reports, it doesn't write; mutation requires --fix. +;; --check is accepted as an explicit alias of this default. +;; +;; emacs --batch -q -l lint-org.el --fix FILE.org [FILE.org ...] ;; apply mechanical fixes in place, emit judgment items on stdout for the ;; command layer to walk ;; -;; emacs --batch -q -l lint-org.el --check FILE.org [FILE.org ...] -;; report only — categorize without modifying the file -;; -;; emacs --batch -q -l lint-org.el --followups-file=PATH FILE.org +;; emacs --batch -q -l lint-org.el --fix --followups-file=PATH FILE.org ;; apply mechanical fixes; if any judgment items remain, append them to ;; PATH as an org section dated today. Used by wrap-it-up to defer the ;; judgment walk to the next morning's review without blocking the wrap. +;; (--followups-file only writes in --fix mode.) ;; ;; Mechanical categories (auto-fixed): ;; item-number add [@N] directive to drifted bullets @@ -66,7 +69,9 @@ Each plist has :kind (mechanical-fixed | judgment), :line, :checker, :msg. Mechanical entries from --check mode also carry :preview t.") (defvar lo-check-only nil - "Non-nil means run in report-only mode — no buffer writes.") + "Non-nil means run in report-only mode — no buffer writes. +The CLI defaults this to t (a linter reports, it doesn't write); +`--fix' is what enables writes on a command-line run.") (defvar lo-current-file nil "Path of the file currently being processed.") (defvar lo-followups-file nil @@ -355,24 +360,40 @@ logical row, matching wrap-org-table.el's grouping." (defun lo--check-tables () "Scan the current buffer for org tables violating the table standard. -Emits one judgment item per violating table." +Emits one judgment item per violating table. Pipe-led lines inside +#+begin_/#+end_ blocks are content (ASCII art, shell pipes), not tables, +and are skipped — the same block rule `wot-process-file' applies." (save-excursion (goto-char (point-min)) - (while (re-search-forward "^[ \t]*|" nil t) - (let ((start-line (line-number-at-pos)) - (lines nil)) - (beginning-of-line) - (while (and (not (eobp)) (looking-at "[ \t]*|")) - (push (buffer-substring-no-properties (line-beginning-position) - (line-end-position)) - lines) + (let ((in-block nil)) ; the open block's type, e.g. "example" — nil outside + (while (not (eobp)) + (cond + ;; Type-matched close only: literal #+end_src quoted inside an + ;; example block must not clear the flag (see wot-process-file). + ((and (not in-block) + (looking-at "^[ \t]*#\\+begin_\\([^ \t\n]+\\)")) + (setq in-block (downcase (match-string 1))) + (forward-line 1)) + ((and in-block + (looking-at-p (format "^[ \t]*#\\+end_%s\\([ \t]\\|$\\)" + (regexp-quote in-block)))) + (setq in-block nil) (forward-line 1)) - (let ((violations (lo--table-violations (nreverse lines)))) - (when violations - (lo--emit-judgment - 'org-table-standard start-line - (format "table violates the org-table standard: %s — wrap-org-table.el reflows it" - (string-join violations "; "))))))))) + ((and (not in-block) (looking-at-p "^[ \t]*|")) + (let ((start-line (line-number-at-pos)) + (lines nil)) + (while (and (not (eobp)) (looking-at "[ \t]*|")) + (push (buffer-substring-no-properties (line-beginning-position) + (line-end-position)) + lines) + (forward-line 1)) + (let ((violations (lo--table-violations (nreverse lines)))) + (when violations + (lo--emit-judgment + 'org-table-standard start-line + (format "table violates the org-table standard: %s — wrap-org-table.el reflows it" + (string-join violations "; "))))))) + (t (forward-line 1))))))) ;;; --------------------------------------------------------------------------- ;;; level-2 dated-header check (claude-rules/todo-format.md) @@ -677,6 +698,13 @@ After printing, also append judgments to `lo-followups-file' when set." ;;; CLI (defun lo-main () + ;; Report-only is the CLI default; --fix is the only way a command-line run + ;; writes to disk. The old mutate-by-default reformatted five files in one + ;; pass before anyone confirmed anything (work project, 2026-07-09). + (setq lo-check-only t) + (when (member "--fix" command-line-args-left) + (setq lo-check-only nil) + (setq command-line-args-left (delete "--fix" command-line-args-left))) (when (member "--check" command-line-args-left) (setq lo-check-only t) (setq command-line-args-left (delete "--check" command-line-args-left))) @@ -688,7 +716,7 @@ After printing, also append judgments to `lo-followups-file' when set." (setq command-line-args-left (delete followups command-line-args-left)))) (if (null command-line-args-left) (progn - (princ "Usage: emacs --batch -q -l lint-org.el [--check] [--followups-file=PATH] FILE.org ...\n") + (princ "Usage: emacs --batch -q -l lint-org.el [--fix] [--check] [--followups-file=PATH] FILE.org ...\n") (kill-emacs 1)) (let ((files command-line-args-left)) (setq command-line-args-left nil) @@ -707,7 +735,7 @@ this file without firing the CLI dispatch — under `ert-run-tests-batch-and-exi the trailing args are things like `-f ert-run-tests-batch-and-exit'." (and command-line-args-left (cl-every (lambda (a) - (cond ((member a '("--check")) t) + (cond ((member a '("--check" "--fix")) t) ((string-prefix-p "--followups-file=" a) t) ((string-prefix-p "-" a) nil) (t (file-readable-p a)))) diff --git a/.ai/scripts/task-review-staleness.sh b/.ai/scripts/task-review-staleness.sh index ed43712..50e0257 100755 --- a/.ai/scripts/task-review-staleness.sh +++ b/.ai/scripts/task-review-staleness.sh @@ -19,9 +19,14 @@ # deeper headings, and cookie-less headings are not review units. # # A task is stale (count mode), and sorts oldest (list mode), when its -# :LAST_REVIEWED: property is missing or unparseable (NIL sorts first), or -# when its age strictly exceeds the threshold (age > N days; age == N is -# still fresh). +# :LAST_REVIEWED: property is missing (NIL sorts first) or when its age +# strictly exceeds the threshold (age > N days; age == N is still fresh). +# +# :LAST_REVIEWED: accepts a bare date (2026-07-09) or an org-native +# timestamp ([2026-07-09 Thu] or <2026-07-09 Thu ...>); both normalize to +# the ISO date. A value that is present but parses to neither is a data +# error: the script warns loudly to stderr (file:line:value) and leaves it +# out of the stale count rather than silently treating it as never-reviewed. set -euo pipefail @@ -46,7 +51,7 @@ num="$2" # only the property drawer between a qualifying heading and the next heading # is scanned. extract_tasks() { - awk ' + awk -v fname="$todo_file" ' function flush() { if (in_task) printf "%s\t%s\t%s\n", hline, (have_lr ? lr : "NONE"), heading } @@ -65,7 +70,21 @@ extract_tasks() { v = $0 sub(/^[ \t]*:LAST_REVIEWED:[ \t]*/, "", v) sub(/[ \t]*$/, "", v) - lr = v; have_lr = 1 + raw = v + # Accept org-native stamps: strip a leading [ or < (inactive/active + # timestamp bracket) and take the leading ISO date, so [2026-07-09 Thu] + # and 2026-07-09 both normalize to 2026-07-09. + sub(/^[[<]/, "", v) + if (match(v, /^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/)) { + lr = substr(v, RSTART, RLENGTH); have_lr = 1 + } else { + # Present but unparseable — a data error, not "never reviewed". Warn + # loudly (file:line:value) instead of silently folding it into the + # stale count, where a re-review in the same bad format would never + # drop the number and nothing would explain why. + printf "task-review-staleness: %s:%d: unparseable :LAST_REVIEWED: %s (expected YYYY-MM-DD or [YYYY-MM-DD Day])\n", fname, NR, raw > "/dev/stderr" + lr = "INVALID"; have_lr = 1 + } next } END { flush() } @@ -98,9 +117,16 @@ while IFS=$'\t' read -r hline value heading; do continue fi - # Unparseable date → treat as NIL (stale). + # Malformed stamp: extract_tasks already warned. A data error is not + # "never reviewed", so it stays out of the stale count. + if [ "$value" = "INVALID" ]; then + continue + fi + + # A shape-valid but impossible date (e.g. 2026-13-45) slips past the awk + # regex; warn and skip rather than silently counting it. if ! rev_epoch=$(date -d "$value" +%s 2>/dev/null); then - count=$((count + 1)) + echo "task-review-staleness: $todo_file: unparseable :LAST_REVIEWED: $value" >&2 continue fi diff --git a/.ai/scripts/tests/lint-org-cli.bats b/.ai/scripts/tests/lint-org-cli.bats index d457696..b9faef6 100644 --- a/.ai/scripts/tests/lint-org-cli.bats +++ b/.ai/scripts/tests/lint-org-cli.bats @@ -20,6 +20,24 @@ teardown() { [[ "$output" == *"lint-org: file="* ]] } +@test "lint-org.el default invocation is report-only — file untouched" { + # bare #+begin_src is a mechanical fix (→ #+begin_example) that the old + # default applied on disk; a linter reports, it doesn't write + printf '* H\n\n#+begin_src\nx\n#+end_src\n' > "$TMPFILE" + before="$(cat "$TMPFILE")" + run emacs --batch -q -l "$SCRIPTS_DIR/lint-org.el" "$TMPFILE" + [ "$status" -eq 0 ] + [[ "$output" == *"would-fix"* ]] + [ "$(cat "$TMPFILE")" = "$before" ] +} + +@test "lint-org.el --fix applies mechanical fixes on disk" { + printf '* H\n\n#+begin_src\nx\n#+end_src\n' > "$TMPFILE" + run emacs --batch -q -l "$SCRIPTS_DIR/lint-org.el" --fix "$TMPFILE" + [ "$status" -eq 0 ] + grep -q '#+begin_example' "$TMPFILE" +} + @test "wrap-org-table.el loads and runs without -L on the load path" { run emacs --batch -q -l "$SCRIPTS_DIR/wrap-org-table.el" --width=120 "$TMPFILE" [ "$status" -eq 0 ] diff --git a/.ai/scripts/tests/task-review-staleness.bats b/.ai/scripts/tests/task-review-staleness.bats index 488b023..79aad79 100644 --- a/.ai/scripts/tests/task-review-staleness.bats +++ b/.ai/scripts/tests/task-review-staleness.bats @@ -49,6 +49,16 @@ task_unreviewed() { printf '** %s [#%s] %s\nBody.\n\n' "$keyword" "$prio" "$title" >> "$TODO" } +# Emit a qualifying task whose LAST_REVIEWED is an org-native inactive +# timestamp — [YYYY-MM-DD Day] — matching the CREATED:/CLOSED: cookies that +# sit in the same drawer. The date is derived from an ISO date via `date`. +task_reviewed_org() { + local keyword="$1" prio="$2" title="$3" isodate="$4" + local org="[$(date -d "$isodate" '+%F %a')]" + printf '** %s [#%s] %s\n:PROPERTIES:\n:LAST_REVIEWED: %s\n:END:\nBody.\n\n' \ + "$keyword" "$prio" "$title" "$org" >> "$TODO" +} + # ---- Normal cases ---------------------------------------------------- @test "staleness: empty file reports zero" { @@ -85,6 +95,20 @@ task_unreviewed() { [ "$output" = "2" ] } +@test "staleness: org-native bracketed LAST_REVIEWED parses — recent is fresh" { + task_reviewed_org TODO A "Reviewed five days ago, org stamp" "$D5" + run bash "$SCRIPT" "$TODO" 30 + [ "$status" -eq 0 ] + [ "$output" = "0" ] +} + +@test "staleness: org-native bracketed LAST_REVIEWED parses — old is stale" { + task_reviewed_org TODO A "Reviewed forty days ago, org stamp" "$D40" + run bash "$SCRIPT" "$TODO" 30 + [ "$status" -eq 0 ] + [ "$output" = "1" ] +} + # ---- Boundary cases -------------------------------------------------- @test "staleness: age exactly equal to threshold is fresh" { @@ -136,9 +160,23 @@ task_unreviewed() { [ "$output" = "0" ] } -@test "staleness: malformed LAST_REVIEWED is treated as stale" { +@test "staleness: malformed LAST_REVIEWED warns to stderr and is not counted" { task_reviewed TODO A "Bad date" "not-a-date" - run bash "$SCRIPT" "$TODO" 30 + # stdout carries only the count — the malformed stamp is not folded in. + run bash -c "bash '$SCRIPT' '$TODO' 30 2>/dev/null" + [ "$status" -eq 0 ] + [ "$output" = "0" ] + # stderr carries the loud warning naming the offending value. + run bash -c "bash '$SCRIPT' '$TODO' 30 2>&1 1>/dev/null" + [ "$status" -eq 0 ] + [[ "$output" == *"not-a-date"* ]] + [[ "$output" == *"LAST_REVIEWED"* ]] +} + +@test "staleness: malformed stamp is excluded while real stale tasks still count" { + task_reviewed TODO A "Real stale" "$D40" + task_reviewed TODO B "Broken stamp" "garbage" + run bash -c "bash '$SCRIPT' '$TODO' 30 2>/dev/null" [ "$status" -eq 0 ] [ "$output" = "1" ] } @@ -161,6 +199,17 @@ task_unreviewed() { [[ "${lines[2]}" == *"Reviewed recently"* ]] } +@test "staleness --list: org-native bracketed stamp sorts by its real date" { + task_reviewed TODO A "Bare recent" "$D5" + task_reviewed_org TODO B "Org-stamped old" "$D40" + run bash "$SCRIPT" --list "$TODO" 10 + [ "$status" -eq 0 ] + # The org-bracketed old stamp must sort ahead of the bare recent one — + # proof it parsed to a real date rather than falling to 0000-00-00. + [[ "${lines[0]}" == *"Org-stamped old"* ]] + [[ "${lines[1]}" == *"Bare recent"* ]] +} + @test "staleness --list: takes only the requested count" { task_unreviewed TODO A "First" task_reviewed TODO B "Second" "$D40" diff --git a/.ai/scripts/tests/test-lint-org.el b/.ai/scripts/tests/test-lint-org.el index d14879f..8e3e190 100644 --- a/.ai/scripts/tests/test-lint-org.el +++ b/.ai/scripts/tests/test-lint-org.el @@ -620,6 +620,29 @@ followups file on the next run." ;;; --------------------------------------------------------------------------- ;;; org-table-standard check (width budget + rules between rows) +(ert-deftest lo-table-inside-example-block-not-flagged () + "Pipe-led ASCII art inside an example block is not a table; no judgment." + (let* ((run (lo-test--run + "* H\n\n#+begin_example\n| client |----->| server |\n| box | | box |\n#+end_example\n" + 1 t)) + (judgments (lo-test--judgments (plist-get run :issues)))) + (should-not (memq 'org-table-standard (lo-test--checkers judgments))))) + +(ert-deftest lo-table-inside-src-block-not-flagged () + "Shell pipes inside a src block are not a table; no judgment." + (let* ((run (lo-test--run + "* H\n\n#+begin_src sh\n| sort\n| uniq -c\n#+end_src\n" 1 t)) + (judgments (lo-test--judgments (plist-get run :issues)))) + (should-not (memq 'org-table-standard (lo-test--checkers judgments))))) + +(ert-deftest lo-real-table-after-block-still-flagged () + "Block safety must not mask a genuine violation later in the file." + (let* ((run (lo-test--run + "* H\n\n#+begin_example\n| art |\n#+end_example\n\n| a | b |\n| 1 | 2 |\n" + 1 t)) + (judgments (lo-test--judgments (plist-get run :issues)))) + (should (memq 'org-table-standard (lo-test--checkers judgments))))) + (ert-deftest lo-table-over-budget-emits-judgment () "A table line rendering wider than 120 surfaces as an org-table-standard judgment." (let* ((wide (make-string 130 ?x)) diff --git a/.ai/scripts/tests/test-wrap-org-table.el b/.ai/scripts/tests/test-wrap-org-table.el index 8d1ecb6..0b3b375 100644 --- a/.ai/scripts/tests/test-wrap-org-table.el +++ b/.ai/scripts/tests/test-wrap-org-table.el @@ -186,3 +186,45 @@ (should (string-match-p "Prose before\\." content)) (should (string-match-p "Prose after\\." content)))) (delete-file file)))) + +;;; --------------------------------------------------------------------------- +;;; block safety — pipe lines inside #+begin_/#+end_ blocks are never tables + +(defconst wot-test--block-content + "#+begin_example +| client |----->| server | +| box | | box | +#+end_example +" + "An example block whose ASCII-art lines start with pipes.") + +(defun wot-test--process-content (content budget) + "Write CONTENT to a temp file, run `wot-process-file' at BUDGET, return result." + (let ((file (make-temp-file "wot-test" nil ".org"))) + (unwind-protect + (progn + (with-temp-file file (insert content)) + (wot-process-file file budget) + (with-temp-buffer (insert-file-contents file) (buffer-string))) + (delete-file file)))) + +(ert-deftest wot-process-file-leaves-example-block-byte-identical () + (let ((content (concat "* Diagram\n\n" wot-test--block-content))) + (should (equal (wot-test--process-content content 120) content)))) + +(ert-deftest wot-process-file-reformats-table-but-not-block () + (let* ((content (concat "* Doc\n\n" wot-test--block-content "\n" + wot-test--wide-input)) + (result (wot-test--process-content content 40))) + (should (string-match-p (regexp-quote wot-test--block-content) result)) + (should (string-match-p (regexp-quote wot-test--wide-expected) result)))) + +(ert-deftest wot-process-file-skips-pipes-in-src-block () + (let ((content "* Pipeline\n\n#+begin_src sh\n| sort\n| uniq -c\n#+end_src\n")) + (should (equal (wot-test--process-content content 120) content)))) + +(ert-deftest wot-process-file-literal-inner-end-marker-stays-in-block () + "A literal #+end_src quoted inside an example block must not close it." + (let ((content (concat "* Doc\n\n#+begin_example\n#+begin_src sh\nx\n" + "#+end_src\n| art |----| art |\n#+end_example\n"))) + (should (equal (wot-test--process-content content 120) content)))) diff --git a/.ai/scripts/wrap-org-table.el b/.ai/scripts/wrap-org-table.el index ddbea65..173e44d 100644 --- a/.ai/scripts/wrap-org-table.el +++ b/.ai/scripts/wrap-org-table.el @@ -228,22 +228,40 @@ continuation lines merge back into their logical row before re-wrapping." ;;; file layer (defun wot-process-file (file &optional budget) - "Reformat every org table in FILE in place to BUDGET width." + "Reformat every org table in FILE in place to BUDGET width. +Pipe-led lines inside #+begin_/#+end_ blocks (example, src, quote, …) are +content, not tables — ASCII art in an example block once got mangled into a +bordered table — so block regions are skipped verbatim." (with-temp-buffer (insert-file-contents file) (goto-char (point-min)) - (while (re-search-forward "^[ \t]*|" nil t) - (let ((start (line-beginning-position))) - (while (and (not (eobp)) - (save-excursion (beginning-of-line) - (looking-at "[ \t]*|"))) + (let ((in-block nil)) ; the open block's type, e.g. "example" — nil outside + (while (not (eobp)) + (cond + ;; Only the matching #+end_<type> closes a block: an example block + ;; often quotes literal #+begin_src/#+end_src lines, and a boolean + ;; flag would let that inner literal end-marker re-expose the rest + ;; of the block to reformatting. + ((and (not in-block) + (looking-at "^[ \t]*#\\+begin_\\([^ \t\n]+\\)")) + (setq in-block (downcase (match-string 1))) (forward-line 1)) - (let* ((end (point)) - (table (buffer-substring-no-properties start end)) - (reformatted (wot-reformat-table-string table budget))) - (delete-region start end) - (goto-char start) - (insert reformatted)))) + ((and in-block + (looking-at-p (format "^[ \t]*#\\+end_%s\\([ \t]\\|$\\)" + (regexp-quote in-block)))) + (setq in-block nil) + (forward-line 1)) + ((and (not in-block) (looking-at-p "^[ \t]*|")) + (let ((start (point))) + (while (and (not (eobp)) (looking-at-p "^[ \t]*|")) + (forward-line 1)) + (let* ((end (point)) + (table (buffer-substring-no-properties start end)) + (reformatted (wot-reformat-table-string table budget))) + (delete-region start end) + (goto-char start) + (insert reformatted)))) + (t (forward-line 1))))) (write-region (point-min) (point-max) file))) ;;; --------------------------------------------------------------------------- @@ -289,7 +307,21 @@ so the ERT suite can `require' this file without firing the CLI dispatch." (t (file-readable-p a)))) command-line-args-left))) -(when (and noninteractive (wot--cli-invocation-p)) +(defun wot--entry-script-p () + "Non-nil when wrap-org-table.el itself was named on the command line. +lint-org.el `require's this file, and a load-triggered dispatch would run +the table reformatter over lint-org's file arguments — that's how a lint +invocation once reformatted the files it was only supposed to report on. +Only dispatch when a -l/--load argument names this very file." + (and load-file-name + (cl-loop for (flag arg) on command-line-args + thereis (and (member flag '("-l" "--load")) + (stringp arg) + (file-exists-p arg) + (string= (file-truename (expand-file-name arg)) + (file-truename load-file-name)))))) + +(when (and noninteractive (wot--entry-script-p) (wot--cli-invocation-p)) (wot-main)) (provide 'wrap-org-table) diff --git a/.ai/sessions/2026-07-04-12-57-audit-closeouts-and-startup-sync-guard.org b/.ai/sessions/2026-07-04-12-57-audit-closeouts-and-startup-sync-guard.org new file mode 100644 index 0000000..c0d569b --- /dev/null +++ b/.ai/sessions/2026-07-04-12-57-audit-closeouts-and-startup-sync-guard.org @@ -0,0 +1,68 @@ +#+TITLE: Session Context +#+DATE: 2026-07-04 + +* Summary + +** Active Goal + +Work a short list: commit the fable→opus model switch, run a task review, double-check the speedrun and finish it, then the routing feature. The review turned into a full task audit ("how many are actually completed"), which drove the rest. + +** Decisions + +- Craig redirected task-review → full task audit, scoped to completion. +- Closed out all three code-complete tasks (docs-lifecycle, wrap-up routing, memory-sync): flip spec to IMPLEMENTED, close the parent, promote pending manual validation to standalone tasks. Same shape each time. +- Accepted + applied home's startup skip-sync-when-behind proposal (shared-asset change, skeptical-reviewed, Craig-approved). +- Pushed all three session commits to origin. + +** Data Collected / Findings + +- Of 18 open tasks, none were sitting done-and-verified; three were code-complete pending validation. The speedrun (autonomous-batch) was already fully done — IMPLEMENTED, live-trialed, archived in 5dc7da3. +- Cross-machine memory sync verified as a live round-trip: on ratio (this machine) the velox-created probe was present with no conflicts; then over tailscale to velox, the probe deletion had propagated, both clones at HEAD 8c5ee01, both roam-sync timers active. Probe cleaned up. +- Spec board now carries no DOING specs. +- The commit no-attribution rule caught an accidental Claude-Session trailer on the first model-switch commit (amended out before it went anywhere). + +** Files Modified + +- .claude/settings.json — model fable→opus (73835a2). +- todo.org + docs/specs/{docs-lifecycle,wrapup-routing}-spec.org + notes.org — three tasks closed, two specs flipped IMPLEMENTED, :LAST_AUDIT: stamped (44af1b2). +- claude-templates/.ai/workflows/startup.org + mirror — Phase A proj_behind guard skips the template rsync when the project branch is behind upstream (bc4befa). + +** Next Steps + +- The three promoted validation tasks (docs-lifecycle manual checks; wrap-routing manual e2e; work/unknown-project KB write-refusal) need live cross-project or in-Emacs sessions to close. +- home is applying the startup fix as a local stopgap; it can drop the stopgap after pulling rulesets. + +KB: promoted 0 / consulted no + +* Session Log + +** 2026-07-04 Sat @ 11:06 CDT — Startup + inbox + +Ran startup. Session-context was absent — previous session wrapped cleanly. Project repo clean/current (no ff needed); rulesets skipped its pull on a dirty tree (uncommitted =.claude/settings.json= model switch fable→opus, plus this session's own drops). =.ai/= synced from templates clean. + +One pending inbox handoff: =inbox/2026-07-01-0041-kb-hygiene-report.org=, a report from the automated =kb-hygiene.sh= script listing 41 orphan =:agent:= KB nodes. Checked todo.org — already filed as =** TODO [#C] KB orphan-node review pass :chore:= (line 42), whose body cites this exact report and instructs regenerating the list rather than trusting the snapshot. So the inbox file is a leftover; deleted it (script source, already captured, no reply owed). Stamped =:LAST_INBOX_PROCESS:=. + +Noted but not touched: =.claude/settings.json= carries an uncommitted model switch (fable→opus) — Craig's local config, left as-is. =inbox/lint-followups.org= is a task-review-health nudge (2 top-level tasks unreviewed >30 days), correctly excluded by inbox-status. + +** 2026-07-04 Sat @ 11:xx CDT — Model commit, task audit, three closeouts + +Committed the fable→opus switch (73835a2; first amend removed an accidental Claude-Session trailer that violates the no-attribution rule). Then ran a full task audit (Craig redirected from task-review to "how many are actually completed"). + +Audit finding: of 18 open tasks, zero were sitting done-and-verified, but three were code-complete with only validation pending. Also surfaced that the no-approvals speedrun (Craig's item 3) was already fully done — autonomous-batch spec IMPLEMENTED, live-trialed (metrics commits), task archived in 5dc7da3. + +Closed out all three code-complete tasks (Craig approved A, then walked B and C): +- A: docs-lifecycle convention — flipped spec to IMPLEMENTED, closed parent, promoted manual-validation + vNext to standalone tasks. +- B: wrap-up routing (Craig's item 4) — same shape; build was fully shipped + green, flipped spec IMPLEMENTED, closed parent, promoted manual e2e + transcript vNext. +- C: memories-sync VERIFY — implementation was IMPLEMENTED already; verified check 4 live this session. We're on ratio, so the ratio half was a local check (probe from velox present, zero conflicts). Then velox came back reachable — did the full round-trip over tailscale: velox pulled ratio's probe deletion, both clones at the same HEAD (8c5ee01), zero conflicts, both timers active. Deleted the spent probe. Closed the VERIFY; promoted the residual work/unknown-project write-refusal checks to a standalone task. + +Spec board now has no DOING specs. Stamped :LAST_AUDIT: 2026-07-04. + +Committed the audit closeouts as 44af1b2 (todo.org + both specs + notes.org), voice-passed. Two commits now sit unpushed ahead of origin (73835a2 model switch, 44af1b2 audit). + +** 2026-07-04 Sat @ ~12:53 CDT — Inbox: home's startup behind-guard fix applied + +Two inbox handoffs from home arrived mid-session (intro + proposal): startup Phase A step 3 rsyncs templates onto a project's stale committed .ai/ baseline when the project branch is behind/diverged, producing phantom drift that conflicts on reconcile. Ran the skeptical review (correct, complete, safe, universal); recommended accept; Craig approved applying now. + +Applied in canonical claude-templates/.ai/workflows/startup.org: a second guard computes proj_behind from git rev-list @{u}...HEAD and skips the rsync when behind>0, composing with the rulesets-clean guard. Added companion prose (step-3 heading + a rsync-notes bullet, incl. the no-auto-discard reasoning and the 6/22 flashcard-revert precedent). sync-check --fix propagated to the mirror; make test green (all ERT 0-unexpected, bats ok). Committed bc4befa. Replied to home (delivered to their inbox), deleted both inbox files, updated :LAST_INBOX_PROCESS:. Inbox clean. + +Three unpushed commits now: 73835a2, 44af1b2, bc4befa. diff --git a/.ai/sessions/2026-07-11-02-30-inbox-clearout-six-proposals-shipped.org b/.ai/sessions/2026-07-11-02-30-inbox-clearout-six-proposals-shipped.org new file mode 100644 index 0000000..3555f8f --- /dev/null +++ b/.ai/sessions/2026-07-11-02-30-inbox-clearout-six-proposals-shipped.org @@ -0,0 +1,74 @@ +#+TITLE: Session Context +#+AUTHOR: Craig Jennings +#+DATE: 2026-07-11 + +* Summary + +** Active Goal + +Process the full project inbox (11 handoffs) and then the roam inbox. Craig chose to work the shared-asset proposals in order rather than deferring them, so the session became a full inbox clear-out with six proposals shipped. + +** Decisions + +- Craig approved all six shared-asset proposals after skeptical review, one batch at a time: triage-intake gmail cap, PR-review drop-praise, ui-prototyping rule, desktop-capture rule, bug-matrix double-count, staleness org-date parsing. +- Placement calls: ui-prototyping and desktop-capture each got a standalone claude-rules/ file (not folded into existing workflows), matching the docs-lifecycle.md pattern spec-create already points at. +- chime's staleness fix used the "both" approach: accept the org-native bracketed stamp AND warn loudly on genuinely-unparseable values, rather than silently counting them stale. +- settings.json model flip (opus→fable, harness-written at 02:12) reverted to opus per Craig — matches the deliberate 2026-07-04 switch (73835a2). +- Bare "wrap it up" → teardown mode. + +** Data Collected / Findings + +- The AI-attribution handoff's work had already landed as commit 6def7c4 (tree was clean); the 15 remaining "& Claude" files are all in .ai/sessions/ (historical, correctly held back). Confirmed agreement with everything the work session held back. +- personal-gmail is the only gmail-family plugin rulesets owns, so the 100-cap fix touched one plugin. +- The sent triage-intake copy carried the old "& Claude" author line; applied only the two Scan blocks, did not reintroduce it. +- ~/.claude/settings.json is a symlink to the repo file, so a git checkout reverts both at once (not a hardlink). +- Roam inbox churns fast (12 items at 01:01 startup, 6 at 02:16 process). Git history confirmed exactly 3 rulesets-owned items ever passed through and all 3 were filed — no rulesets item was missed; the count drop was foreign-item/other-machine churn. + +** Files Modified + +- Six proposal commits (see Next Steps for shas). Rules/skills: review-code/SKILL.md, voice/SKILL.md, voice/references/voice-profile.org, claude-rules/{commits,todo-format,verification}.md; new claude-rules/{ui-prototyping,desktop-capture}.md. Workflows: triage-intake.personal-gmail.org, spec-create.org, spec-review.org, task-review.org (+ mirrors). Script: task-review-staleness.sh + its bats (+4 tests) + mirrors. +- todo.org: 5 tasks filed (org-table bug [#B], Signal pager [#C], 3 roam ideas [#C]). notes.org: :LAST_INBOX_PROCESS: stamped 2026-07-11. 11 inbox handoffs renamed PROCESSED-*. + +** Next Steps + +- All inbox work done; nothing carried forward. Filed backlog tasks await future sessions: org-table helpers data-loss bug ([#B] :bug:solo:, has a repro test), Signal pager doc ([#C], reconcile signal-mcp vs signal-cli first), and the 3 roam ideas. +- Shipped shas: 81ca16f, 99bd213, 53f6ce6, 8ddd3d3, 06ca6c7, cfbabf1 (+ housekeeping b2aeaf6, 95dd661). + +KB: promoted 0 / consulted no + +* Session Log + +** Startup (2026-07-11 01:01 CDT) + +Ran startup.org Phases A.0/A/B. rulesets pull clean, make install nothing new, project repo current. .ai/ synced from templates. No session-context anchor (prior session wrapped cleanly). Read the 5 most recent session summaries and all 11 inbox handoffs. Verified the AI-attribution handoff's work already landed as commit 6def7c4 (tree clean). Created this anchor. + +** Inbox processing (Craig chose: file the 2 tasks, work proposals in order) + +Filed two todo.org tasks: [#B] :bug:solo: org-table helpers corrupt example blocks (graded Critical×rare = P2 = [#B]); [#C] :feature:spec: document/own the Signal pager (flagged the signal-mcp vs signal-cli/account-404211 reconcile). Replied to work (bug + attribution) and home (signal), marked those 3 inbox files PROCESSED. + +Skeptical-reviewed the two work proposals: +- triage-intake.personal-gmail.org: clean 2-block Scan addition (100-cap date-slice walk + mandatory backlog-residue probe). Only other diff is the "& Claude" author-line regression, which I will NOT reintroduce. Low-risk correctness fix. Verdict: approve. personal-gmail is the only gmail-family plugin in rulesets. +- PR-review tightening: (1) no praise at all on approvals — drop the "bare positive" carve-out at review-code/SKILL.md:453, :278, examples :458-465, and /voice pattern #40; lead with substantive pointer then verdict. (2) always print full inline prose at the gate — tighten review-code Phase 5 (:276) + commits.md Shape 1. Craig's own ruling from a real session. Verdict: approve. + +Both awaiting Craig's approve/park. + +** Work proposals shipped (Craig approved both) + +- 81ca16f fix(triage-intake): personal-gmail 100-cap date-slice walk + backlog-residue probe (canonical + mirror). Did NOT reintroduce the "& Claude" author line from the sent copy. +- 99bd213 docs(review): drop praise from approve summaries (removed bare-positive carve-out from review-code Posted Summary Voice + voice #40 + voice-profile §40 examples/history + quick-ref table), require full inline prose at the gate (review-code Phase 5 + commits.md Shape 1). +- make test green (269 ok, pytest 12), both authored as Craig, not pushed yet. +- Replied to work on both; marked the 3 work inbox files PROCESSED. + +Remaining inbox proposals to work (Craig's chosen order): archsetup ui-prototyping (2 files), archsetup off-workspace-captures rule, chime staleness-parse, chime bug-matrix double-count. + +** Full inbox cleared — 6 proposals shipped, all 11 handoffs processed + +Shipped commits: 81ca16f (triage 100-cap), 99bd213 (drop-praise), 53f6ce6 (ui-prototyping rule), 8ddd3d3 (desktop-capture rule), 06ca6c7 (bug-matrix), cfbabf1 (staleness org-date TDD, +4 tests, suite 273 ok), b2aeaf6 (housekeeping: 2 tasks filed + processed markers + inbox marker). Replied to every sender (work, home, archsetup, chime). notes.org :LAST_INBOX_PROCESS: stamped 2026-07-11. Nothing pushed yet. + +FLAG: .claude/settings.json model flipped opus→fable at 02:12 (hardlinked to ~/.claude/settings.json, harness-written, not my edit). Reverts the deliberate 2026-07-04 opus switch (73835a2). Left unstaged — needs Craig's call (revert to opus, or accept fable). + +** Roam inbox-zero (Craig: "then inbox-zero again") + +Roam inbox had 6 items at process time (roam-sync churns fast). Bucketed: 3 claimed (rulesets:), 3 foreign (emacs.d ×1, archsetup ×2), 0 unowned, 0 empty. Filed the 3 rulesets items into todo.org (all [#C]: roam-only-startup investigation :spec:, keep-WIP-from-blocking-sync-gate :feature:, install-ai-on-PATH :chore:) — committed 95dd661. Removed the 3 rulesets items from the roam inbox (foreign left untouched), handed git to roam-sync → adeb011, pushed clean. Phase E done. + +Session state: 8 rulesets commits this session (6 inbox proposals + 2 housekeeping), roam inbox reconciled. Nothing pushed to rulesets origin yet. settings.json model-flip still unstaged/unresolved. diff --git a/.ai/sessions/2026-07-13-23-48-runtime-portability-pager-local-llm.org b/.ai/sessions/2026-07-13-23-48-runtime-portability-pager-local-llm.org new file mode 100644 index 0000000..8e55623 --- /dev/null +++ b/.ai/sessions/2026-07-13-23-48-runtime-portability-pager-local-llm.org @@ -0,0 +1,191 @@ +#+TITLE: Session Context — 2026-07-13 +#+AUTHOR: Craig Jennings + +* Summary + +** Active Goal + +A marathon (05:14–23:59) that became four arcs: (1) the runtime-portability children — all eight resolved, ending with a working local-LLM agent lane; (2) the EAT sixel investigation — persistent terminal images plus two dead .emacs.d agents root-caused (OOM from an EXDATE infinite loop, fixed cross-project); (3) the agent pager — Signal paging reconciled, verified, wrapped in a universal agent-page command; (4) full backlog hygiene — inbox zero on both surfaces repeatedly, task-review staleness driven to zero. + +** Decisions + +- Craig kept the fable model pin (d5bc9b3 accidentally committed only a case change — harness rewrote the file pre-staging; corrected in e91073d). +- Thin-pointer AGENTS.md over a generated monolith for non-Claude bootstrap ("1 is the way to go. definitely"). +- All four inventory decisions approved (PreCompact prose downgrade, Stop-teardown via codex notify/manual, Signal-pager portability note, knowledge-base.md capture-layer sentence). +- Signal pager re-graded [#C]→[#B]; ntfy two-way-comms task KILLED (infrastructure torn down 07-04; agent-page + Signal runbook are successors). +- Drop all four legacy ollama models after verification; MoE over dense for Strix Halo; Vulkan backend forced over ROCm (systemd override). +- "Agent pager" naming (the Slough House of pagers); paging documented as two channels everywhere. +- EXDATE fix applied from here (cross-project, Craig-directed); jotto SVG rule and takuzu EAT-visuals rule both accepted with changes; .emacs.d's validate-el.sh load-prefer-newer fix accepted verbatim. + +** Data Collected / Findings + +- tmux transmits sixel only when TERM_SIXEL is set AND the client's cell pixel size is known; stock EAT 0.9.4 silently drops the CSI 14 t query (no t case in its dispatch). EAT persists sixel in scrollback natively; through tmux, passthrough images are write-once but native-path images survive redraws. +- Both .emacs.d agent deaths were kernel OOM kills (111GB emacs) from calendar-sync--get-exdates reading (match-end 0) after split-string clobbered the match data — infinite loop only on comma-separated EXDATE lines far enough into the string. My first two diagnoses (stale sentinel; double-reply keystroke leak) were wrong; the double-reply was real but non-lethal. +- signal-mcp is a velox-local MCP server, not claude.ai-side; the pager identity +15045173983 lives in velox's signal-cli; ratio's signal-cli is Craig's own number (no push). Receive-staleness warnings on both accounts. +- Strix Halo: ROCm allocates correctly but first-touch loads at <25MB/s (unusable for 61GB); Vulkan loads the same model in 44s. gpt-oss:120b: 37.9 tok/s; qwen3-coder:30b: 76 tok/s; both 100% GPU. num_ctx matters (native 128K balloons loads). codex --oss needs --local-provider=ollama explicitly (config.toml append lands in the last TOML table, silently inert). +- The remembered old launcher was aix + hey, removed from dotfiles 2026-04-20 (dab0d5a); bin/ai is their unified successor. lint-org.el's mutate-on-lint defect self-demonstrated during this wrap (no damage — no pipes in example blocks). + +** Files Modified + +Rulesets (16 commits, all pushed through 3984392): SVG rule (cac1aa1), visuals rule + zsh note (8703150), model pin ×2 (d5bc9b3, e91073d), runtime child tasks (1b638e6), inventories doc + decisions + skill parity (44bc344, 0ee94ab), AGENTS.md entry + install paths (6cd3aa3), launcher --runtime / local lane / agent picker (04c3b29, 1b98a9e, 3eed2e1), agent-page + two-channel paging docs (49be354), validate-el.sh fix (7e61dc0), model-floor status (21d14e0), review close-outs (0de6e09, 3984392). Plus dotfiles 5055ef1 (tmux sixel conf, pushed) and .emacs.d 56a3ed86 (EXDATE fix, committed there). System: ollama 0.31.2 + Vulkan override; ~/.codex/AGENTS.md link; two KB node writes (roam-synced). + +** Next Steps + +- Craig's maiden voyage: ai → pick local:gpt-oss:120b → takuzu; watch for skipped startup/gates/session-log/commit-rules — that's the model-floor eval running live. The formal scripted eval remains on the child. +- Signal-pager [#B]: runbook, receive timer on velox, ssh-vs-linked-device decision. +- Watch for .emacs.d's ai-term multi-LLM work (they're shelling out to ai --print-runtimes); upstream EAT PR is their [#D]. +- Backlog is fully current; org-table [#B] bug and launcher-hardening [#C] are the natural next builds. + +* Session Log + +** 2026-07-13 Mon 15:25 — SECOND .emacs.d death root-caused: OOM from an EXDATE infinite loop (my first diagnosis was wrong) + +Craig reported death #2 (launch 14:32, [exited] 14:55:36) — AFTER my double-reply fix, so that diagnosis fell. Corrections to the record: the transcript "queue-operations" are harness task-notification bookkeeping, NOT leaked keystrokes (my death-1 keystroke story loses its evidence — the double-reply leak was real and measured but likely never killed anything); the final "background task killed" entries are claude's SIGHUP handling. Hard evidence chain: EAT scrollback shows no bash prompt before [exited] (session destroyed under a live claude); journalctl nails BOTH deaths to the second — kernel OOM killer at 13:41:57 (emacs, 111GB anon-rss!) and 14:55:36, each inside the session's tmux-spawn scope, which systemd then failed, killing the pane → single-window session gone. The bomb: tests/test-calendar-sync--get-exdates.el's two comma-separated tests (NEW in a9e61207, the off-anchor EXDATE fix) hang forever: calendar-sync--get-exdates sets pos from (match-end 0) AFTER split-string, whose internal string-match clobbers match data → pos becomes the last comma's offset within the VALUE; when that's smaller than the EXDATE line's position, the loop re-matches the same line forever, growing the list to OOM. Verified: exact test string hangs the loaded .elc; bytecode disassembly confirms match-end after split-string; comma-less values don't clobber (failed string-match preserves match data), which is why only the two new tests bomb. Fix shape: capture (match-end 0) into a local BEFORE split-string (or save-match-data). Daemon check: calendar-sync loaded but NO timer → no live-daemon exposure right now. Side finding for the handoff: test-ai-term--show-or-create.el leaks one real tmux list-sessions call past its mocks (read-only, benign, but a mock gap; caught with a tmux PATH shim). Suite-under-shim full run unnecessary — OOM evidence is conclusive. Instruction-bootstrap build parked mid-scoping (no edits yet). Next: Craig decides fix-from-here vs handoff to the .emacs.d agent. + +** 2026-07-13 Mon 23:50 — inbox cleared again + task review COMPLETE (staleness zero) + +Handoff round: .emacs.d's validate-el.sh load-prefer-newer fix accepted verbatim (diff = exactly the two claimed evals; suite green; commit 7e61dc0 PUSHED; reply sent); takuzu ack deleted (all five routed captures filed there as three [#B] tasks); two more acks arrived mid-work and were processed (.emacs.d multi-LLM work underway, will shell out to ai --print-runtimes — interop achieved; home recorded the pager recipe) — sent home a one-line addendum pointing at agent-page so its memory doesn't fossilize the raw ssh recipe. Then the remaining 11 reviews, Craig-delegated: ntfy two-way-comms KILLED (CANCELLED — home tore down ntfy 07-04; Signal pager + agent-page are the successors); triage-intake phone-push re-pointed at agent-page (send half buildable now, recv waits on the Signal runbook); coverage-summary docs task tagged :quick:solo:; three tasks gained missing drawers; the rest confirmed. STALENESS NOW ZERO — the whole backlog is current. Commit 3984392 PUSHED. Emacsd handoff sent earlier re ai-term alignment. Everything on origin through 3984392. Remaining: Craig's takuzu local trial (his keyboard, likely tomorrow) and the wrap. + +** 2026-07-13 Mon 23:22 — agent picker shipped: bare ai asks which brain first + +Craig asked where fzf went and wanted the flow to offer LLMs before projects. Built TDD (9/9 launcher bats, suite 441/0): build_runtime_choices (claude first, codex-as-ChatGPT, one local:<model> line per live ollama model, 3s-timeout query, dead server drops lines), pick_runtime feeding the runtime map, dispatch restructured (picker only on bare ai without --runtime/AI_RUNTIME; check_deps moved after final runtime known), --print-runtimes test seam, usage updated (sed range widened to 2,23 for the grown header). Live print verified: all four lines. Commit 3eed2e1, PUSHED. Also answered: the remembered old launcher was aix + hey in dotfiles, removed 2026-04-20 (dab0d5a) when bin/ai unified them — no shadowed script exists; and the rofi/zenity UI-picker exploration stands offered but unbuilt (rofi -dmenu -multi-select = nearest drop-in; zenity = real GTK checklist; fuzzel single-select only). Roam-ask "agentically democratic" now substantially delivered. Still open: Craig's takuzu local trial, 11 task reviews, wrap. 23:24: sent emacsd the ai-term alignment handoff (runtime map strings incl. the --local-provider gotcha, picker shape + --print-runtimes as a shell-out option, verified perf numbers + Vulkan/num_ctx caveats, AGENTS.md bootstrap note) — Craig is actively working ai-term multi-LLM with that project. + +** 2026-07-13 Mon 23:06 — local runtime LIVE: ai --runtime local = codex --oss over ollama + +Craig pushed (e91073d..21d14e0 landed) and chose to wire the local lane. Shipped TDD (7/7 launcher bats, suite 439/0): local → codex --oss --local-provider=ollama -m $AI_LOCAL_MODEL (default gpt-oss:120b), AGENT_BIN/AGENT_CMD split for the deps check. End-to-end verified: codex exec --oss through local gpt-oss returned a correct completion (~7K tokens). Gotchas learned: codex needs the provider named (flag beats config — appending oss_provider to config.toml lands in the last TOML table and silently does nothing; a stray append was made and removed, ~/.codex/config.toml back to original). Also: upstream-patch question answered — .emacs.d shipped its durable home as c53671b0 (DONE, pushed) and filed the upstream PR as its own [#D] with working/eat-sixel-patch/; nothing needed from rulesets. Recommendation given for the live local trial: takuzu (well-traveled, 78+ tests, 5 fresh small tasks, known Claude baseline); watch for skipped startup/gates/session-log/commit-rules + practical tok/s. Commit 1b98a9e PUSHED. During the edit I briefly clobbered the 16:39 dated heading in todo.org (Edit replaced a heading instead of inserting above it) — caught and repaired in the same minute. Remaining for the session: Craig's live takuzu trial (his keyboard), the leftover 11 task reviews, wrap. + +** 2026-07-13 Mon 20:20 — local inference stack verified end to end; model thread CLOSED + +The gpt-oss saga resolved: ROCm on gfx1151 allocates/offloads correctly but first-touch loads at <25MB/s — two 40-min load attempts died to client timeouts (one mine, one the ceiling; a third was killed externally ~19:00, unexplained). qwen3-coder verified first on ROCm (5s load, 76 tok/s, 100% GPU) proving the generate path, isolating the problem to big-model load speed. Fix: systemd override /etc/systemd/system/ollama.service.d/strix-halo-vulkan.conf (OLLAMA_IGPU_ENABLE=1 + ROCR_VISIBLE_DEVICES="") → Vulkan/RADV as sole inference device → gpt-oss:120b loads in 44 SECONDS, "verified", 37.9 tok/s, 100% GPU. Craig's approved drops executed: tinyllama + llama3 rm'd from the service store, the orphaned 45GB ~/.ollama user store purged. KB node rewritten with the end-state (backend, override path + revert note, per-model numbers, num_ctx gotcha, MoE-not-dense lesson) and roam-synced. Model-floor child updated: remaining work is purely the instruction-following eval (gpt-oss as target, qwen as comparator). Commit 21d14e0. Unpushed now 9: 44bc344..21d14e0. All threads from today are closed or cleanly parked; ready to wrap whenever Craig calls it. + +** 2026-07-13 Mon 18:32 — agent pager shipped; pager thread effectively closed for today + +Full sequence since 17:15: routed all 10 foreign roam items to owners via inbox-send (roam inbox at true zero, takuku typo flagged); Signal reconcile — pager identity +15045173983 lives in velox's signal-cli (signal-mcp = velox-local MCP config, NOT claude.ai-side; corrected the 14:40 inference), ratio's signal-cli is Craig's own number; live page from velox buzzed Craig's phone (confirmed). Then Craig directed universal paging + the "agent pager" rename: NEW claude-templates/bin/agent-page (velox-direct/ssh-relay, UUID baked in, fallback hint; 4 bats, PATH-stubbed; live-verified through the real script — second buzz), protocols.org Paging-Craig rewritten (two channels, signal-mcp demoted), page-me.org + work-the-backlog + INDEX updated, mirror synced, make install linked it (velox inherits next session). Suite 438/0. Commits: 0de6e09 (review+filings), 49be354 (agent pager, incl. the Signal task's dated entries + interim-recipe-to-home note). home got the recipe handoff at 18:14. Signal task remains [#B] with sharpened deliverables: runbook, receive timer, ssh-vs-linked-device. Unpushed count now 8 (44bc344..49be354). Model verification still pending: gpt-oss smoke test backgrounded (bkpjmp2iu), then qwen3-coder smoke, then the four legacy drops + KB update. + +** 2026-07-13 Mon 17:15 — task review finished (Craig delegated the close) + +Craig said "finish the task review" — applied my judgments across the remaining 6 (+1 from the morning = 8 of 18 total; 11 remain for future daily rotations). Changes: Signal pager re-graded [#C]→[#B] (paging is a real capability gap post-inventory — flagged for veto); install-ai-on-PATH tagged :quick:solo:; wrap-up-routing validation gained its missing PROPERTIES drawer; the rest confirmed as-is, all stamped 2026-07-13. Committed 0de6e09 together with the inbox-zero filings. Next per Craig: "handle some of those roam inbox items" — all 10 remaining are foreign, so the sanctioned shapes are inbox-send routing or launching owner agents; presenting options. + +** 2026-07-13 Mon 17:08 — inbox zero (roam mode) run + +Craig called inbox zero mid task-review (review paused at item 2 of 7, roam-only startup, recommendation pending). Local inbox clear. Roam scan: 12 total, 2 rulesets-claimed, 9 foreign (4 archsetup, 4 takuzu, 1 emacsd), 1 mis-prefixed "takuku:" (plainly takuzu's — left in place, typo flagged to Craig), 0 unowned, 0 empty. Claimed items deduped against today's work: the agent-switching ask is partly shipped (--runtime claude|codex 04c3b29; codex = the ChatGPT CLI; ollama/qwen = the reserved local runtime on the model-floor child) — folded as a dated entry on the generic-agent-runtime parent. The new work filed as [#C] "ai launcher hardening — bug hunt + refactor pass" :refactor:solo: (interactive runtime picker rides it). Roam edit: both claimed items removed under a clean capture-guard, roam-sync triggered (10 items remain, all foreign). todo.org edits uncommitted. Monitor for pulls retired at Craig's cue earlier; pull ETA ~18:30 at 12MB/s. + +** 2026-07-13 Mon 16:45 — session plumbing closed with a no-build verdict (8 of 8 children resolved; model-floor eval pending pulls) + +Assessed the four plumbing pieces: session-context-path runtime-aware by design; anchor cycle plain-files-portable; self-inject harness-agnostic (payload is the only Claude-shaped part — codex auto-flush = payload variant, second injected line carries the resume instruction, no hook needed); session-clear-resume.sh stays Claude-only. Section added to the inventories doc, child closed, commit 3ef5580 (suite 434/0). ALL EIGHT runtime children are now resolved or reduced: 6 shipped/closed, launcher shipped, model-floor's remaining work is the evaluation itself, blocked only on the pulls (~19:00 ETA). Unpushed: 44bc344, 0ee94ab, 6cd3aa3, 04c3b29, 3ef5580. + +** 2026-07-13 Mon 16:42 — launcher runtime flag shipped (7 of 8 children done) + +While the pulls run (rate settled ~0.5GB/min, ETA ~19:00): built ai --runtime claude|codex TDD — 6-test bats file driving a new --print-launch seam (prints the pane command, no tmux), runtime→CLI map with local reserved (clear error naming the model-eval dependency), AI_RUNTIME env support, runtime-aware deps check, CLAUDE_CMD→AGENT_CMD at all three launch sites. Verified: codex --help confirms positional-prompt parity with claude; live smoke prints correct commands both runtimes. Suite 434/0. Commit 04c3b29; child closed as dated entry. NOTE: Craig's real sessions launch via .emacs.d's ai-term (aiv-<proj> per-project sessions), NOT bin/ai (shared "ai" session, window per project) — the Emacs-side multi-LLM support is .emacs.d's June handoff, unchanged here. Craig also approved dropping tinyllama + llama3:8b ("annoying little pest") — all four legacy models go after verification. Remaining child: session plumbing, local model floor (eval blocked on pulls). Unpushed: 44bc344, 0ee94ab, 6cd3aa3, 04c3b29. + +** 2026-07-13 Mon 16:40 — ollama upgraded 0.17.7→0.31.2, model pulls running + +Craig picked option 1 (drop both llamas after the new models verify) and directed the full sequence. Research (WebSearch): Strix Halo wants MoE models (bandwidth-bound decode; dense 70B ≈ single-digit t/s) — gpt-oss:120b (~55 t/s reported on this chip) + qwen3-coder:30b (~100 t/s, 256K ctx) are the picks; ollama 0.17.7 was far behind. Upgraded via the official installer (sudo, NOPASSWD): 0.31.2, systemd unit recreated, service active. GPU discovery healthy out of the box: ROCm sees gfx1151 iGPU, 107.4GiB available; Vulkan RADV device present but gated behind OLLAMA_IGPU_ENABLE=1 (community says Vulkan outperforms ROCm on gfx1151 — A/B during the model-floor eval, not now). DISCOVERY: the service uses /usr/share/ollama/.ollama (system store) — holds two MORE models my earlier "complete" inventory missed (tinyllama 637MB, llama3:8b 4.7GB); the 45GB ~/.ollama store is separate and invisible to the service. KB node needs both corrections (stores + new versions) after pulls complete. Pulls running in background (btkrc1y0n, sequential: gpt-oss:120b 65GB then qwen3-coder:30b 19GB) into the system store; du-based Monitor armed (be6nrzl5d). Disk: 3.0T free, no constraint (df is aliased to dfc — use command df). Pending Craig: extend the drop list to tinyllama + llama3? Pending me after pulls: smoke-test both models w/ GPU offload check, ollama rm the drops, purge the orphaned ~/.ollama user store, update KB node + model-floor child, session log. + +** 2026-07-13 Mon 16:15 — local-runtime inventory gathered and promoted to the KB + +Craig asked about ollama; gathered the full local-inference picture. ratio: ollama 0.17.7 (/usr/local/bin, no systemd unit, on-demand), 45GB store with llama3.1:8b + llama3.3 70B, Strix Halo Radeon 8060S iGPU + 125GiB unified RAM — a real inference box, and the on-disk 70B matches the model-floor estimate. velox: no ollama, Iris Xe, 60GiB — out of scope for local inference. codex CLI installed + authed on ratio. Promoted to the KB as agents/20260713161222-local-llm-inference-inventory-daily-drivers.org (committed, roam-sync triggered — first KB write in a long while, the contribute nudge finally earned). Folded the inventory into the model-floor child's body: the evaluation can run TODAY on ratio against the on-disk 70B (scripted startup + publish-flow transcript, grade instruction-following) — no procurement needed. todo.org edit uncommitted. + +** 2026-07-13 Mon 16:10 — instruction bootstrap shipped (6 of 8 children done) + +Memory sentry ran clean during Craig's relaunch (zero alerts, daemon flat 1.97GB, third .emacs.d session alive since 15:44 and healthy). Queue resumed: built the thin-pointer AGENTS.md TDD (bats red→green): canonical claude-templates/AGENTS.md; make install CODEX_DIR stanza (→ ~/.codex/AGENTS.md, live on ratio now; velox auto-picks-up via startup's make install); install-ai.sh seed-only copy at bootstrap; rulesets root tracked symlink as dogfood. NEW scripts/tests/install-agents-entry.bats (3 tests) + 2 install-ai.bats tests. Riders: todo.org line-1357 prose reworded (lint misread "file:→id:" as a dead link), lint-followups.org cleared. Suite 428/0. Commit 6cd3aa3 (also closes the child + adds a dated entry on the agent-source task's thread 2). Inbox: two .emacs.d acks processed + deleted (the dead 14:34 one, and the live session's 15:47 — HEAD 56a3ed86 confirmed, staged eat work intact, mock-leak filed [#C] there, no action needed). Unpushed rulesets commits: 44bc344, 0ee94ab, 6cd3aa3. Remaining children: launcher runtime flag (scoping next — codex mapping clear, local mapping blocked on the model-floor/default-runtime decision), session plumbing, local model floor. Then the org-table bug + 6 task reviews. + +** 2026-07-13 Mon 15:32 — EXDATE bomb fixed, .emacs.d suite green end-to-end, agent cleared to relaunch + +Craig chose fix-from-here. Applied the capture-line-end-before-split fix to calendar-sync-recurrence.el (with a why-comment naming the clobber mechanism), deleted + recompiled the .elc, reloaded into the daemon (live Emacs calendar-safe again). Verification: the two previously-infinite tests pass in microseconds; all 12 exdates tests green; all 56 calendar-sync files green; then the FULL suite ran to completion for the first time today — all 599 elisp files + 18 bats green, including the dead agent's uncommitted eat-config guard tests. Committed 56a3ed86 in .emacs.d with a pathspec so the agent's staged eat work stayed untouched; not pushed. Superseding handoff sent to .emacs.d inbox (2026-07-13-1530): corrected diagnosis (journalctl OOM evidence), fix details, the show-or-create mock-leak finding, the double-reply note downgraded to latent-bug status, explicit safe-to-relaunch incl. the suite. Craig can relaunch the .emacs.d agent now. Resume thread: instruction-bootstrap build (thin-pointer AGENTS.md, Craig approved shape) was mid-scoping when death #2 interrupted — Makefile install stanza + install-ai.sh seeding + canonical claude-templates/AGENTS.md + bats, TDD. + +** 2026-07-13 Mon 14:55 — pin pulled: four decisions applied, skill parity done + +Craig pulled the pin and approved all four inventory decisions. Applied: knowledge-base.md capture-layer sentence (non-Claude runtimes capture into the session log), Signal-pager task gained the runtime-portability motivation entry, decisions recorded in the doc, VERIFY closed as a dated entry. Commit 44bc344. Then the skill-parity child: inventoried 11 skills + 18 commands, key finding is that one resolution sentence in the bootstrap entry file makes all 29 portable to any file-reading harness (auto-invocation degrades to by-name, which the publish flow already uses; flush excluded, session-plumbing owns it; native registration optional nicety). Section appended to the inventories doc, child closed as dated entry, commit 0ee94ab. Both commits local/unpushed (with 44bc344). 5 of 8 children done. Remaining: instruction bootstrap (needs Craig's design input, overlaps the Multiple agent-source improvements task), launcher runtime flag, session plumbing, local model floor. + +** 2026-07-13 Mon 14:30 — PIVOT RESOLVED: .emacs.d agent death root-caused and fixed + +Craig's urgent pivot: the .emacs.d agent session (launched 13:20) died — tmux session aiv-_emacs_d gone, [exited] in the launcher shell. Investigation chain: stale-sentinel theory disproved (multiple turns survived; no sentinels; hook tests clean). Real cause: DOUBLE XTWINOPS REPLIES. The .emacs.d agent had picked up our eat-xtwinops handoff, designed an advice-based fix (cj/--eat-answer-xtwinops :before eat--t-handle-output, written to modules/eat-config.el 13:33, test file 13:32), and evaluated it into the live daemon — where MY morning spike (patched parser with its own CSI t clause, loaded 10:52) was still active. Two answerers → tmux consumes reply #1, forwards reply #2 to the active pane as raw keystrokes (ESC + junk) — and Emacs ptys report 0 pixel size so EVERY frame resize re-queries. Its transcript (83070ecd, ends 13:41:57) shows the signature: four empty queue-operations, then background make test killed as claude exited. Exact final exit trigger unreconstructable from the transcript; the leak itself confirmed empirically: probe showed 2 replies per query. Fix: reloaded stock eat.elc into the daemon 14:27 (advice survives redefinition and is now sole answerer); re-probe = 1 reply. Probe buffer cleaned up. Handoff sent to .emacs.d (2026-07-13-1427): root cause, safe-to-resume, the latent no-op-claim bug in its advice comment (must guard against upstream shipping the clause or the double-reply returns), rerun its killed ERT, and the coordination lesson (our 10:58 handoff should have disclosed the live spike — our omission). Craig can relaunch the .emacs.d agent now; it resumes from its own anchor. + +** 2026-07-13 Mon @ 14:16:49 -0500 — SUSPENDED (urgent pivot; supersedes the 13:22 entry) + +Open threads, most active first: +- ACTIVE: runtime-portability children under the generic-agent-runtime parent (todo.org ~line 231). Done: hook parity, MCP portability, memory story — all three now dated entries pointing at docs/design/2026-07-13-runtime-portability-inventories.org. AWAITING CRAIG: the *** VERIFY with four decisions (PreCompact prose downgrade; Stop-teardown via Codex notify/manual; Signal-pager portability note; knowledge-base.md capture-layer sentence) — all recommended yes, he was about to answer when the pivot hit. Next child queued: skill parity (decision matrix over voice/review-code/flush etc.: re-register per harness vs fold into .ai/workflows/), then instruction bootstrap, launcher flag, session plumbing, local model floor. +- PINNED (from 13:22, still queued): org-table helpers bug ([#B] :bug:solo:, todo.org ~line 66) + the todo.org "→id" dead-link rider from lint-followups.org. +- PINNED: task review, 6 of 7 unwalked; they stay at the head of the next batch. +- SET ASIDE: sixel/EAT — .emacs.d has the patch filed [#B] with the durable-home design call coming to Craig; after it lands + velox pulls dotfiles and .emacs.d, run the velox test (client_cell_width nonzero, then magick sixel in a tmux window, Craig's eyes). + +Pending decisions: the four VERIFY items above. Nothing else blocks. + +Shipped since the 13:22 suspend (all pushed): 828f70a..1b638e6 (SVG rule cac1aa1, visuals rule 8703150, model d5bc9b3, todo folds dc13bce, child-task filing 1b638e6) and e91073d (model fix — d5bc9b3 had committed opus→Opus from a harness rewrite, not the fable flip; e91073d lands fable as Craig chose). + +Uncommitted work: todo.org (three children converted to dated entries + the new *** VERIFY), docs/design/2026-07-13-runtime-portability-inventories.org (new file, untracked), this anchor. Commit shape when resumed: docs+todo together as the inventories chore/feat commit. + +Key findings not recorded elsewhere: none — everything is in the inventories doc, the todo entries, or this log. Inbox is clear (the 13:23 .emacs.d ack was read and deleted). + +Background work: none running. + +Resume hint: read the *** VERIFY under the generic-agent-runtime parent, get Craig's four answers, apply the two tiny approved edits (Signal-pager note + knowledge-base.md sentence), commit todo.org + the inventories doc, then start the skill-parity matrix. + +** 2026-07-13 Mon @ 13:22:43 -0500 — SUSPENDED + +Craig stuck a pin in everything mid task-review walk. Resume-weighted state: + +Open threads, most active first: +- PINNED: org-table helpers bug ([#B] :bug:solo:, todo.org line 66). Craig queued it as the next work item ("2 then 1" — review done-ish, this is the 1). Fully specified in the task body: two defects (line-based table detection mangles example/src blocks; lint-org.el writes without --fix), repro from work 2026-07-09, regression test named. TDD it: canonical scripts live in claude-templates/.ai/scripts/, mirror-synced; tests are ERT via make test. Start here. +- PINNED: task review, stopped after 1 of 7. Batch was the staleness --list top 7; task 1 (build-to-prototype [#B]) kept + stamped 2026-07-13. Items 2-7 (roam-only startup, WIP sync gate, install-ai PATH, org-table bug, Signal pager, KB orphan pass) unreviewed — they stay at the head of the next run's batch automatically. +- SET ASIDE: sixel/EAT thread — rulesets + dotfiles halves are DONE and shipped; waiting on Craig to fast-track the eat-xtwinops.patch handoff sitting in .emacs.d/inbox/ (2026-07-13-1058-from-rulesets-*). After .emacs.d lands it and velox pulls dotfiles + .emacs.d, run the velox test: restart EAT/tmux server on velox, tmux display -p '#{client_cell_width}' nonzero, then magick logo: sixel:- into a tmux window, Craig eyeballs. Note ratio's working state is runtime-only until the .emacs.d patch lands (Emacs restart loses it; tmux server restart loses the feature flags — the dotfiles conf now covers that half). +- DEFERRED: lint-followups.org rider — dead file link at todo.org (was line 1314 pre-edit; re-grep for "→id"), promised as a rider on the next work item. +- POST-SUSPEND ADDITION (13:27): Craig asked for the ChatGPT/local-LLM gap assessment to be filed. Found the existing parent (** TODO [#D] Generic agent runtime support — Codex spec v0) and added 8 child tasks under it (instruction bootstrap, skill parity, hook parity, launcher flag, session plumbing, memory story, MCP portability, local model floor) plus a dated decomposition entry. todo.org edit uncommitted, riding with the review stamp. + +Pending decisions / open questions for Craig: none blocking. (Model flip resolved: keep fable, committed.) + +Shipped this session (all local, UNPUSHED except dotfiles): +- rulesets cac1aa1 — SVG-first rendering rule (emacs.md + ui-prototyping.md cross-link), jotto proposal accepted, reply sent. +- rulesets 8703150 — Showing Craig Visuals rule (interaction.md) + zsh word-split note (protocols.org canonical+mirror), takuzu proposal accepted with corrections, reply sent. +- rulesets d5bc9b3 — model opus→fable (Craig: keep). rulesets dc13bce — todo.org Signal-pager fold. +- dotfiles 5055ef1 — tmux terminal-features sixel lines, COMMITTED AND PUSHED to origin; inbox handoff there rewritten as applied-FYI. +- Handoffs delivered: jotto (acceptance), takuzu (acceptance + capability correction), emacsd (eat-xtwinops.patch + intro), dotfiles (superseded by direct apply). + +Uncommitted work: todo.org (the :LAST_REVIEWED: 2026-07-13 stamp on the build-to-prototype task — one drawer line, safe to commit with the next housekeeping/wrap). .ai/session-context.org is this live anchor (untracked, stays). + +Key findings not recorded elsewhere: none — the sixel investigation's durable facts live in the two rule commits, the emacsd/dotfiles handoffs, and this log. The four unpushed rulesets commits are the main crash exposure; push happens at wrap. + +Background work: none running. tmux sixel test windows killed; *sixel-probe* buffer killed; tmux server carries runtime-only settings (terminal-features sixel entries, allow-passthrough all) that are now redundant with the pushed dotfiles conf (passthrough) or pending the .emacs.d patch (features). + +Resume hint: start the org-table helpers bug via TDD (regression test first: a #+begin_example block of pipe-prefixed lines must survive wrap-org-table.el byte-identical), and fold in the todo.org:1314 link fix as the rider. + +** 2026-07-13 Mon 05:14 — Startup + inbox processing begins + +Ran startup: rulesets pull skipped (dirty tree — .claude/settings.json shows model opus→fable, same harness flip Craig reverted on 2026-07-11), nothing new to link, no branches behind, .ai/ synced from templates. No crashed session. Notes.org: no reminders, no pending decisions. Staleness: 18 top-level tasks unreviewed >7 days. Roam inbox: 8 items, none rulesets-owned (4 takuzu, 3 archsetup, 1 emacs.d). + +Inbox had 3 handoffs. Processed the home FYI (2026-07-11-1208, Signal pager ack): folded as a dated sub-entry into the [#C] "Document (and own) the Signal pager" task body — home confirms rulesets owns it, and reports signal-mcp wasn't connected in its 2026-07-09 session and its signal-cli is registered as Craig's own number (note-to-self pushes nothing), so home currently has no live page channel. Deleted the inbox file. No reply sent — the handoff was itself home's ack of our earlier reply; nothing to close. + +Remaining: two shared-asset proposals (jotto SVG-rendering rule, takuzu EAT-image-display rule) — skeptical reviews done, surfacing to Craig for approval next. + +** 2026-07-13 Mon 05:25 — jotto SVG rule shipped + +Craig approved option 1 (accept with changes). Added the SVG Rendering section to claude-rules/emacs.md (consider-by-default phrasing, full constraint sheet, hybrid guidance verbatim) and the svg.el port-target paragraph to claude-rules/ui-prototyping.md section 3. /review-code --staged: approve, one Minor wording nit self-fixed (1:1 repetition). make test green (273 ok, ERT clean — the 9 grep hits were test names containing "error"). Commit cac1aa1 as Craig. Replied to jotto via inbox-send.py (landed in jotto's inbox), deleted the inbox file. Note: bare inbox-send isn't on PATH in this shell — used .ai/scripts/inbox-send.py directly. Settings.json model flip still undecided (Craig hasn't answered keep-or-revert). Next: takuzu EAT-image proposal. + +** 2026-07-13 Mon 09:40 — EAT sixel capability verified live (takuzu claim corrected) + +Craig challenged takuzu's "EAT can't display images" claim; verified it's wrong on capability. Findings: EAT 0.9.4 fully implements Sixel — advertises it in DA (\e[?12;4c, feature 4 = sixel), answers XTSMGRAPHICS, renders via eat-sixel-render-formats (xpm first; daemon has xpm+svg). Live test: magick logo → sixel, wrapped in tmux passthrough DCS (ESC Ptmux; with doubled ESCs), cat from a VISIBLE pane → full-color 640px image rendered in the EAT buffer (Craig's screenshot 09:36:42). Caveats found: (1) image is a transient screen-anchored overlay — any scroll/redraw drops it; (2) tmux allow-passthrough "on" drops passthrough from invisible panes — set allow-passthrough "all" globally at RUNTIME ONLY (needs tmux.conf for persistence, both daily drivers); (3) img2sixel/libsixel 1.10.5 on ratio is broken — exit 0, zero bytes, all formats; use magick ... sixel: instead; (4) writes into the Claude Code TUI pane get instantly repainted over — draw in a separate visible pane. Test window killed, cleanup done. Next: re-present takuzu disposition with corrected facts. + +** 2026-07-13 Mon 09:55 — Root cause of sixel vanishing: tmux grid, not EAT + +Craig asked why the image is irretrievable once offscreen. Isolated it: created a background plain-EAT buffer (*sixel-probe*, eat-make, no window shown) in the daemon, fed it the RAW sixel (no tmux), scrolled 60 lines, re-scanned: all 60 image cells persist at pos 163, before display-begin 654 — EAT keeps sixel in scrollback natively (each cell is a buffer char with an XPM-tile display property, per eat--t-sixel-flush-line). So tmux is the killer: passthrough bypasses tmux's grid, tmux's model of those lines is text-only, and every repaint (scroll/window-switch/output) rewrites them, destroying EAT's image cells. Nothing upstream stores the pixels → irretrievable. Follow-up lead: tmux 3.7b binary has sixel compiled in, but client_termname is xterm-256color (TERM override somewhere in the launch chain — EAT would normally set eat-truecolor), whose terminfo doesn't advertise sixel, so tmux's native sixel path never engages. Native path would need terminal-features 'xterm*:sixel' + client reattach to test. *sixel-probe* buffer left alive so Craig can see the scrollback image; kill at wrap. + +** 2026-07-13 Mon 11:00 — EAT XTWINOPS spike: full native-sixel fix, verified, handed off + +Craig chose the spike (option 1). Found the final gate in tmux source (cloned 3.7b shallow to scratchpad): tty_cmd_sixelimage falls back to the text placeholder when TERM_SIXEL is unset OR tty xpixel/ypixel are 0. First was cleared by terminal-features 'xterm*:sixel'; second was the blocker — Emacs never fills pty pixel fields, and tmux's fallback query (CSI 18t + CSI 14t at attach) is silently swallowed by EAT (no 't' case in its CSI dispatch; confirmed client_cell_width 0x0). Spike: copied eat.el to scratchpad, added eat--t-send-window-size-report (answers CSI 14/16/18 t from char-width/height + disp dims, mirroring the XTSMGRAPHICS reply) + one dispatch case. Byte-compiled clean, loaded into the live daemon (survived — this session runs in EAT), probe confirmed replies (R14 4;576;880, R16 6;24;11, R18 8;24;80). After Craig's reattach: client_cell_width 11x24, and the invisible-window grid-redraw test PASSED — wizard image drawn from tmux's grid, survives window switches, scrolling, AND resizing (Craig verified visually). Note: first probe buffer vanished mid-session (unexplained; daemon uptime 1.5d rules out restart), recreated fine. img2sixel (libsixel 1.10.5) broken on ratio — magick sixel: is the working encoder. Velox verified over tailscale: same sixel-enabled tmux 3.7b + imagemagick — ready once configs land. Handoffs sent: emacsd got the eat-xtwinops.patch + intro (durable home, TDD productionization, upstream to codeberg akib/emacs-eat); dotfiles got the two tmux.conf lines. Everything on ratio is RUNTIME-ONLY until those land (Emacs restart loses the patch, tmux server restart loses the flags). Remaining: takuzu disposition (rule now reflects working persistent sixel), model-flip question, cleanup (sixel-final window, *sixel-probe* buffer). + +** 2026-07-13 Mon 12:00 — takuzu rule shipped, dotfiles committed + pushed + +Craig approved option 1 and directed the dotfiles commit+push. Applied: (1) rulesets 8703150 — Showing Craig Visuals section in interaction.md (never rely on SendUserFile display; browser/imv lane per desktop-capture.md; sixel quick-glance lane behind the client_cell_width capability gate) + zsh no-word-split note in protocols.org canonical + mirror (sync-check --fix). Suite green 423/0. Corrective acceptance reply sent to takuzu (its capability claim corrected with the CSI 14 t findings); inbox file deleted — local inbox now clear. (2) dotfiles 5055ef1 committed as Craig and pushed to origin (cjennings.net): two terminal-features sixel lines in common/.tmux.conf (allow-passthrough already on); dotfiles suite 10/10 OK; the pending rulesets handoff in dotfiles/inbox rewritten as an already-applied FYI. Cleanup done earlier this turn-series: sixel-final tmux window killed, *sixel-probe* buffer killed. Plan: Craig fast-tracks the emacsd patch, then a velox end-to-end test after velox pulls both repos. Still open: model flip (settings.json, unanswered), todo.org Signal-pager fold uncommitted. + +** 2026-07-13 Mon 13:35 — resumed: runtime-portability work begun, three inventories done + +Craig pulled the pin ("commit and push that, then let's work on all those remaining tasks"). Pushed 828f70a..1b638e6 (5 commits incl. the child-task filing), then found and fixed a defect: d5bc9b3 had committed opus→Opus (harness rewrote the file to "Opus" before staging), NOT the fable flip Craig chose — corrected + pushed as e91073d. Mid-work inbox arrival: .emacs.d ack of the EAT patch (filed [#B] there, design call going to Craig) — pure FYI, deleted. Then the three inventory children: wrote docs/design/2026-07-13-runtime-portability-inventories.org (hooks: only PreCompact + Stop carry porting work, AskUserQuestion moot, validators ride githooks; MCP: nine local servers portable, signal-mcp is claude.ai-side only so paging has NO off-Claude path — Signal-pager task is the fix; memory: KB already cross-agent, one sentence gap in knowledge-base.md). Converted the three children to dated entries, added a *** VERIFY carrying the four decisions (all recommended yes). todo.org + new doc uncommitted. Next children: skill parity (decision matrix), instruction bootstrap, launcher flag, session plumbing, local model floor. + +** 2026-07-13 Mon 12:15 — task review stopped early at Craig's call + +Craig picked "2 then 1" (task review, then org-table bug). Review batch was the 7 oldest-unreviewed; task 1 (build-to-prototype rule extension [#B]) kept as-is and stamped :LAST_REVIEWED: 2026-07-13 (assessed not-quick, not-solo — placement decision pending). Craig then said "let's stop here" — review closed early, 6 tasks untouched for the next run. The org-table bug (queued item 1) not started. todo.org edit uncommitted; ambiguity noted: "stop here" might mean stop-review or end-session — asked Craig which. + +** 2026-07-13 Mon 12:05 — model flip kept, housekeeping committed + +Craig chose to keep the fable model flip (deliberate this time, unlike the 2026-07-11 revert). Committed d5bc9b3 (settings model opus→fable) and dc13bce (todo.org Signal-pager fold). Both mechanical, subject-only messages; the 11:45 green suite run covers this tree state. Working tree now clean except the live session-context file. Rulesets has 4 unpushed commits (cac1aa1, 8703150, d5bc9b3, dc13bce); push at wrap per usual flow. Session remaining: nothing pending from the inbox; awaiting Craig's next move (emacsd fast-track, then velox test). diff --git a/.ai/sessions/2026-07-14-02-50-sentry-spec-review-lint-org-fix.org b/.ai/sessions/2026-07-14-02-50-sentry-spec-review-lint-org-fix.org new file mode 100644 index 0000000..280ab5b --- /dev/null +++ b/.ai/sessions/2026-07-14-02-50-sentry-spec-review-lint-org-fix.org @@ -0,0 +1,84 @@ +#+TITLE: Session Context — 2026-07-14 +#+DATE: 2026-07-14 + +* Summary + +** Active Goal + +Take the work project's sentry proposal (one supervisor loop running a project's hygiene passes overnight, with locking) from inbox arrival to a fully-dispositioned spec: decision walk, spec-create, adversarial review, findings walk. Mid-walk, fix the lint-org data-corruption bug the review surfaced as a sentry prerequisite. Plus two inbox waves (8 handoffs total) processed along the way. + +** Decisions + +- Sentry design, all resolved live with Craig (recorded as 10 DONE decisions + 12 DONE findings in the spec): host-suffixed sentry branch (Craig's call — unpushed commits on main diverge across two daily drivers); host-local locks under /run/user/<uid>/agent-locks/ (a lock inside the roam repo would ride roam-sync's git add -A to the other machine); mkdir-atomic lock helper with heartbeat refresh + bounded-wait-then-defer (flock can't span tool calls); interactive entry gates, NO report-only mode anywhere (Craig's explicit direction — passes run fully or skip); roam-sync stays the roam repo's only committer; :COMMIT_AUTONOMY: yes gates sentry entirely; detection-over-configuration pass portability; launch contract with in-place checkout (worktrees rejected: untracked inbox drops invisible there); inbox pass under the no-approvals contract; suite at entry + conditional fire-end run only; KB promotion pass cut to vNext (heuristic task filed); wrap-up refuses during a live loop, "stop sentry" owns shutdown; entry ff-only reconcile; persistent stall notify after 2 skips. +- Craig corrected a misread mid-turn: the .emacs.d signel request meant "remove signel mentions from live workflows," not "run a broadcast + question smoke's scope." No broadcast ran; smoke's scope stands; nothing had been sent. +- Spec stays DRAFT deliberately — Craig reviews deeply before the READY flip. + +** Data Collected / Findings + +- The 2026-07-09 org-file corruption's true mechanism: wrap-org-table.el's load-time CLI dispatch fired when lint-org.el merely required it, running the table reformatter over lint-org's file arguments (--check runs were immune because the flag made wot's dispatch decline). The filed defects (block-blind scanning, mutate-by-default) were real but secondary. +- flock binds to a living process; agent Bash calls are short-lived shells, so locks for agent workflows need mkdir-atomicity + staleness semantics, never flock. +- rg skips hidden directories by default — a grep over .ai/ paths silently misses everything without --hidden (bit this session during the signel audit). +- No locking existed anywhere in .ai/scripts/ (verified); the roam sync-conflict forks are the observed cost. + +** Files Modified + +- docs/specs/2026-07-14-sentry-workflow-spec.org — NEW: the sentry spec, DRAFT, 10/10 decisions, 12/12 findings resolved, voice-passed. docs/design/2026-07-14-sentry-workflow-proposal.org — NEW: preserved origin proposal + cover note. +- Commit 951b6fc — lint-org/wrap-org-table corruption fix, test-first (6 new tests): block-type-aware scanning, lint-org CLI report-only by default (--fix required; wrap-it-up + /lint-org command updated), wot entry-script dispatch guard. Commit 32afa41 — work's .flac threading adopted into process-meeting-transcript.org. Plus the signel history-line chore. All pushed (ad5bb07). +- todo.org — sentry build task [#B] + cross-host [#D] + /schedule [#D] + KB-heuristic [#D] filed; org-table bug closed DONE and archived to Resolved. +- notes.org — :LAST_INBOX_PROCESS: 2026-07-14; Active Reminder: Craig reviews the sentry spec before ending the next session. + +** Next Steps + +- FIRST: Craig's deep read of the sentry spec (his explicit ask — surface the reminder at startup). READY flip, then spec-response decomposes the build. +- The paused task review: batch of 7 selected, task 1 (sentry build) walked but no action picked; 6 untouched. The sentry build task is also the one 30-day-staleness hit (never-reviewed, filed tonight) — the next review cycle covers it. +- Sentry build follows the spec's 4 phases once READY (agent-lock helper first). + +KB: promoted 2 / consulted no + +* Session Log + +** Startup + inbox processing + +Session started 2026-07-14 00:11 CDT on ratio. Fresh session, templates synced, no reminders, staleness 0, roam inbox 0. + +Inbox: three items. (1+2) Sentry workflow proposal from work (intro + full proposal) — passed value gate (Q2), skeptical review found: real problem (verified no flock anywhere in .ai/scripts/), sound invariants, but four issues: roam lock held across commit-push conflicts with inbox.org's edit-then-roam-sync discipline; flock can't span short-lived tool-call shells (needs PID/staleness lockfile or mkdir semantics); unattended per-pass commits need gating on :COMMIT_AUTONOMY:; passes 3 and 10 are sender-specific (need auto-skip/config). Craig picked option 2: work it now, walk decisions, spec/build tonight. Then a task review. (3) archsetup FYI (maint update-queue CVE item done, dotfiles 6757b51) — acknowledged, deleted. Craig also directed deleting the standing inbox/lint-followups.org. + +Proposal preserved to docs/design/2026-07-14-sentry-workflow-proposal.org (cover note merged in). Reply sent to work: accepted, being worked now. + +** Sentry decision walk (all decided with Craig, one by one) + +- D1 commit target: dedicated sentry branch, host-suffixed (sentry/YYYY-MM-DD-<host>). Craig's call, overriding my current-branch rec: unpushed commits on main across two daily drivers = diverged main, breaks startup ff + template sync. Morning flow: review, squash-merge, delete branch. Prior unmerged sentry branch at entry → skip fire. +- D2 roam-write lock scope: host-local only; cross-host conflict-surfacing filed as follow-up. +- D3 interval: hourly default, config knob. +- D4 gates: interactive at entry (Craig is at terminal when he launches) — dirty tree stops and surfaces what's dirty + options (finish the job / stash / rollback which changes); red suite same shape. NO report-only mode anywhere (Craig's explicit direction): unattended fire hitting unsafe state skips with one digest line; next fire retries. Morning-approval queue for destructive/judgment actions stays (permanent division of labor, not a degraded mode). +- D5 roam git: roam-sync stays the only committer; lock wraps edit + trigger. Reconciliation surfaced: knowledge-base.md + roam-sync.sh header still say agents pull/commit/push inline — join companion-file list. +- D6 locks: shared .ai/scripts/ lock helper, mkdir atomicity, PID/host/timestamp metadata, age-based staleness reclaim, acquire/release/status, bats-tested. (flock can't span tool calls.) +- D7 autonomy: :COMMIT_AUTONOMY: yes gates sentry entirely — absent/no → declines to start, names the marker. +- D8 portability: auto-skip by detection (existence probes per pass), no config. +- D9 path: spec-first (spec-create → docs/specs/2026-07-14-sentry-workflow-spec.org). + +** Sentry spec authored + +Wrote docs/specs/2026-07-14-sentry-workflow-spec.org (DRAFT, ID f6c51f27-d7a2-4b63-9ff9-5ba005a66dfb): full spine — summary, problem, goals/non-goals/scope, two-altitude design (user + implementer), 5 alternatives with why-nots, 9 DONE decisions, 4 implementation phases, acceptance criteria, readiness dimensions, risks. Voice pass ran (/voice general per spec-create Phase 4): ~20 em-dashes reduced to structural-only, 2 prose fragments completed, passives activated, one correctness catch (design said "never pulls" roam but pass 1 is an ff-only pull — reconciled). Self-rubric: Ready. Cross-linked: todo.org gets the [#B] build task + two [#D] vNext deferrals (cross-host conflict surfacing, unattended /schedule variant). Next: spec-review gate, then Craig's queued task review. + +** Adversarial spec review (Craig's weakness-loop method) + +Task review started (batch of 7, task 1 = sentry build) but Craig redirected: run the spec through review now via a weakness loop — 20 weaknesses, check each against the spec, loop until <5 new, then notify him for deep review. Ran 3 rounds (20 → 7 → <5). Recorded 10 blocking + 2 non-blocking findings in the spec's Review findings section. Heavy hitters: overnight working-tree ownership (sentry checkout vs Craig's live session — biggest open design question); roam-write lock is sentry-only unless inbox.org/knowledge-base.md writers also acquire it; lock dir inside roam repo would be committed by roam-sync's git add -A (relocate to /run/user); sentry's session-context writes trip its own dirty-tree skip; pass 8 depends on the filed lint-org mutate-on-lint [#B] bug; unattended inbox semantics; per-pass commits vs verification.md suite rule (needs a recorded decision); KB lesson source undefined; lock wait/heartbeat mechanics; wrap-up-during-loop undefined. Rubric: Not ready; status stays DRAFT. Iteration history entry added. Craig notified for his deeper review. Task review batch (7 tasks) still pending — resumes after. + +** Findings walk + lint-org fix shipped mid-walk + +Walking the 12 findings with Craig one by one. Decisions so far: F1 = launch contract (in-place checkout, sentry owns the repo overnight, dirty-skip backstop, Emacs revert caveat documented). F2 = all roam writers acquire the lock (inbox.org core §5 + knowledge-base.md write recipe gain acquire/release; graceful degradation when helper absent; bounded-wait-then-surface for interactive callers). F3 = both locks under /run/user/<uid>/agent-locks/, helper owns path scheme, ~/.cache fallback. F4 = spine-set exclusion in dirty check + fire-end digest commit + path via session-context-path. + +At F5 Craig redirected: fix the lint-org bug now. Done, TDD (red shown first): block-type-aware scanning in wrap-org-table.el wot-process-file + lint-org.el lo--check-tables (type-matched end marker so literal inner markers can't re-expose a block); lint-org CLI report-only by default, writes behind --fix (--check kept as alias; wrap-it-up + /lint-org command updated to pass --fix). Root cause discovered during red-phase debugging: wrap-org-table's load-time CLI dispatch fired on lint-org's require and ran the reformatter over lint-org's file args — THE 2026-07-09 corruption mechanism (--check runs were immune because the flag made wot's dispatch decline). Entry-script guard added (dispatch only when -l names wrap-org-table.el itself). /review-code --staged ran: one Important (boolean in-block flag cleared by literal inner end-marker) fixed in-review + regression test; verdict Approve. Suite 0 failures. Committed 951b6fc (unpushed). todo.org bug task closed DONE; spec finding F5 completed DONE. Minor follow-up noted (not filed): lo--check-indented-headings still has the naive boolean flag, benign (suppression-only). + +Second archsetup FYI (tailscale-amber fixed, dotfiles 4840463 + archsetup aed151b, velox TOML hand-refresh is archsetup's item) acknowledged + deleted. Inbox clean. + +Next: resume walk at F6 (unattended inbox semantics), then F7-F12, then Craig's deep spec review, then the paused task review (task 1 of 7 pending — sentry build task, now also needs the F5 resolution reflected... its body references the spec; fine as-is). + +** Findings walk completed — all 12 dispositioned, spec updated + +F6 = inbox pass under the no-approvals contract, everything to the morning queue. F7 = suite policy recorded as Decision 10 (entry run + conditional fire-end run when non-org files changed, no per-pass runs). F8 = KB promotion pass CUT to vNext (Craig), lesson-detection-heuristic task filed [#D] :spec: (blocks re-adding). F9 = bounded-wait-then-defer + heartbeat refresh, reclaims surfaced. F10 = wrap-up REFUSES during live sentry (Craig picked simple over shutdown-pre-step); "stop sentry" operation owns loop-cancel + branch/queue disposition. F11 = entry ff-only reconcile, diverged joins interactive gate. F12 = persistent notify after 2nd consecutive unmerged-branch skip, then at most daily. All 12 findings flipped DONE in place; design/scope/decisions/acceptance/risks updated to match (pass list now 10, Decisions [10/10], locks at /run/user/<uid>/agent-locks/, launch-contract paragraph in user altitude). Status stays DRAFT for Craig's deep read; responder entry added to iteration history. + +** Second inbox wave (5 handoffs) + Craig's broadcast correction + +Two more archsetup FYI closures (memory-killer = live-refresh hairline artifact, filed [#C] their side; net-panel speedtest history filed [#C] their side) deleted. work's process-meeting-transcript .flac update: diff-verified verbatim (12 hunks, pure .flac threading), adopted into canonical, committed 32afa41, confirmation sent to work. .emacs.d's signel-deprecation broadcast request: I misread it as run-a-broadcast + flag smoke's scope; Craig corrected mid-turn — NO broadcast, smoke's scope is well defined and needed, and the actual ask was removing signel mentions from live workflows. Broadcast draft deleted from /tmp before anything sent; nothing reached smoke. Grep gotcha: rg skips hidden dirs by default, so .ai/ paths need --hidden. One live-workflow mention found (triage-intake.org:417 incident history line) — rewritten to keep the lesson, drop the client name (commit next to 32afa41: chore(workflows): drop retired Signal-client name). Session archives + frozen design doc keep theirs as history. Reply sent to .emacs.d. Inbox clean (0 pending). diff --git a/.ai/workflows/INDEX.org b/.ai/workflows/INDEX.org index 88721ed..b031dbe 100644 --- a/.ai/workflows/INDEX.org +++ b/.ai/workflows/INDEX.org @@ -1,5 +1,5 @@ #+TITLE: Workflow Index -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-04-25 * Purpose @@ -110,8 +110,8 @@ This index must list every =.org= file in =.ai/workflows/= except this one and e - Situational triggers: "broadcast the <event> to all projects", "broadcast that <situation>", "let every project know I'll be away ..." - =flashcard-review.org= — review an org-drill flashcard file, restructure cards to question-form headings (no answer hints), audit content accuracy against project source-of-truth via subagent, rewrite source preserving SRS state, regenerate the Anki =.apkg= to =~/sync/phone/anki/=. Person cards use "Who is X? Tell me about their Y."; talking-points cards stay as-is. Script behavior: =flashcard-to-anki.py= strips =:PROPERTIES:= drawers + =SCHEDULED:= / =DEADLINE:= planning lines from Anki output. - Triggers: "review the flashcards", "update the flashcards", "review the drill deck", "update the drill deck", "refresh the Anki cards", "let's run the flashcard-review workflow" -- =page-me.org= — set a timed notification. - - Triggers: anything containing the word "page" used as a verb ("page me", "page me in 10 minutes", "page me at 3pm") +- =page-me.org= — set a timed notification (desktop =notify=; phone via =agent-page= when Craig is away). + - Triggers: anything containing the word "page" used as a verb ("page me", "page me in 10 minutes", "page me at 3pm", "page my phone") - =status-check.org= — proactive long-running-job updates. - Triggers: "keep me posted on this", "provide status checks on this job", "let me know when it's done", "monitor this for me". Auto: any job estimated 10+ min. - =create-workflow.org= — define a new workflow. diff --git a/.ai/workflows/add-calendar-event.org b/.ai/workflows/add-calendar-event.org index 2650fb7..5dd6c42 100644 --- a/.ai/workflows/add-calendar-event.org +++ b/.ai/workflows/add-calendar-event.org @@ -1,5 +1,5 @@ #+TITLE: Add Calendar Event Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-02-01 * Overview diff --git a/.ai/workflows/broadcast.org b/.ai/workflows/broadcast.org index 60e9ed1..cc14f00 100644 --- a/.ai/workflows/broadcast.org +++ b/.ai/workflows/broadcast.org @@ -1,5 +1,5 @@ #+TITLE: Broadcast Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-05-29 * Overview diff --git a/.ai/workflows/clean-todo.org b/.ai/workflows/clean-todo.org index a1b2af5..48d3084 100644 --- a/.ai/workflows/clean-todo.org +++ b/.ai/workflows/clean-todo.org @@ -1,5 +1,5 @@ #+TITLE: Clean-Todo Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-05-11 * Overview diff --git a/.ai/workflows/code-quality.org b/.ai/workflows/code-quality.org index 2406f4c..3ac3e9d 100644 --- a/.ai/workflows/code-quality.org +++ b/.ai/workflows/code-quality.org @@ -1,5 +1,5 @@ #+TITLE: Code-Quality Sweep Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-06-28 * Overview diff --git a/.ai/workflows/daily-prep.org b/.ai/workflows/daily-prep.org index b6989e7..3103bc7 100644 --- a/.ai/workflows/daily-prep.org +++ b/.ai/workflows/daily-prep.org @@ -1,5 +1,5 @@ #+TITLE: Daily Prep Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-06-11 * Overview diff --git a/.ai/workflows/delete-calendar-event.org b/.ai/workflows/delete-calendar-event.org index 5bb92a1..7de0086 100644 --- a/.ai/workflows/delete-calendar-event.org +++ b/.ai/workflows/delete-calendar-event.org @@ -1,5 +1,5 @@ #+TITLE: Delete Calendar Event Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-02-01 * Overview diff --git a/.ai/workflows/edit-calendar-event.org b/.ai/workflows/edit-calendar-event.org index 662f0b4..27a9dd3 100644 --- a/.ai/workflows/edit-calendar-event.org +++ b/.ai/workflows/edit-calendar-event.org @@ -1,5 +1,5 @@ #+TITLE: Edit Calendar Event Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-02-01 * Overview diff --git a/.ai/workflows/email-assembly.org b/.ai/workflows/email-assembly.org index 003459c..699dbc0 100644 --- a/.ai/workflows/email-assembly.org +++ b/.ai/workflows/email-assembly.org @@ -1,5 +1,5 @@ #+TITLE: Email Assembly Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-01-29 * Overview diff --git a/.ai/workflows/extract-email.org b/.ai/workflows/extract-email.org index 3a70bea..c68bafe 100644 --- a/.ai/workflows/extract-email.org +++ b/.ai/workflows/extract-email.org @@ -1,5 +1,5 @@ #+TITLE: Extract Email Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-02-06 * Overview diff --git a/.ai/workflows/find-email.org b/.ai/workflows/find-email.org index 0ef9615..d71ed3e 100644 --- a/.ai/workflows/find-email.org +++ b/.ai/workflows/find-email.org @@ -1,5 +1,5 @@ #+TITLE: Find Email Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-02-01 * Overview diff --git a/.ai/workflows/first-session.org b/.ai/workflows/first-session.org index 60118a2..147026f 100644 --- a/.ai/workflows/first-session.org +++ b/.ai/workflows/first-session.org @@ -1,5 +1,5 @@ #+TITLE: First Session Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings Run this workflow on the first Claude Code session for a new project. It establishes the git/.ai policy, orients Claude to the diff --git a/.ai/workflows/flashcard-review.org b/.ai/workflows/flashcard-review.org index 31027b3..09af348 100644 --- a/.ai/workflows/flashcard-review.org +++ b/.ai/workflows/flashcard-review.org @@ -1,5 +1,5 @@ #+TITLE: Drill Deck Review Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-05-30 * Overview diff --git a/.ai/workflows/helper-mode.org b/.ai/workflows/helper-mode.org index cdec200..a6acfa7 100644 --- a/.ai/workflows/helper-mode.org +++ b/.ai/workflows/helper-mode.org @@ -1,5 +1,5 @@ #+TITLE: Helper Mode Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-06-15 * Overview diff --git a/.ai/workflows/inbox.org b/.ai/workflows/inbox.org index acfd11d..3bd9335 100644 --- a/.ai/workflows/inbox.org +++ b/.ai/workflows/inbox.org @@ -1,5 +1,5 @@ #+TITLE: Inbox Workflow (Engine) -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-06-23 * Overview diff --git a/.ai/workflows/journal-entry.org b/.ai/workflows/journal-entry.org index 3f476a7..c70dfe8 100644 --- a/.ai/workflows/journal-entry.org +++ b/.ai/workflows/journal-entry.org @@ -1,5 +1,5 @@ #+TITLE: Journal Entry Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2025-11-07 * Overview diff --git a/.ai/workflows/meeting-prep.org b/.ai/workflows/meeting-prep.org index 162ae30..563328b 100644 --- a/.ai/workflows/meeting-prep.org +++ b/.ai/workflows/meeting-prep.org @@ -1,5 +1,5 @@ #+TITLE: Meeting-Prep Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-06-10 * Overview diff --git a/.ai/workflows/meeting-prep.pre-wire.org b/.ai/workflows/meeting-prep.pre-wire.org index 6a156c0..3e27c2a 100644 --- a/.ai/workflows/meeting-prep.pre-wire.org +++ b/.ai/workflows/meeting-prep.pre-wire.org @@ -1,5 +1,5 @@ #+TITLE: Meeting-Prep — Pre-Wire Method (supporting doc) -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-06-10 Supporting document for the [[file:meeting-prep.org][meeting-prep workflow]]'s Phase 3.5. The workflow carries the condensed, in-flow version of pre-wiring; this file is the full Manager Tools method, kept beside the workflow (same name + =.pre-wire= suffix) so it travels with the workflow. Source casts: "How to Prewire a Meeting" (2007) and "Peer Prewire" (2015). diff --git a/.ai/workflows/no-approvals.org b/.ai/workflows/no-approvals.org index 9e1c894..5f54b96 100644 --- a/.ai/workflows/no-approvals.org +++ b/.ai/workflows/no-approvals.org @@ -1,5 +1,5 @@ #+TITLE: No-Approvals Mode -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-05-28 * Overview diff --git a/.ai/workflows/open-tasks.org b/.ai/workflows/open-tasks.org index 02a0847..205d95c 100644 --- a/.ai/workflows/open-tasks.org +++ b/.ai/workflows/open-tasks.org @@ -1,5 +1,5 @@ #+TITLE: Open Tasks Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-04-25 * Overview diff --git a/.ai/workflows/page-me.org b/.ai/workflows/page-me.org index 8069830..bfa92c6 100644 --- a/.ai/workflows/page-me.org +++ b/.ai/workflows/page-me.org @@ -1,5 +1,5 @@ #+TITLE: Page Me Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-01-31 #+UPDATED: 2026-02-27 @@ -113,6 +113,19 @@ notify info "Page" "Your message here" --persist The =--persist= flag keeps the notification on screen until manually dismissed. All page-me notifications should use =--persist= by default. +** Paging Craig's phone (away from the machine) + +The timed =notify= alarm above is the desktop channel. When Craig is away from the machine (or asks to be paged "on my phone"), use the agent pager instead — a Signal push to his phone from any machine or agent runtime: + +#+begin_src bash +agent-page "Build finished — ready for your eyes" + +# Timed phone page: same at-daemon pattern, different channel +echo "agent-page 'Meeting starts in 5'" | at 3:25pm +#+end_src + +Channel selection and the pager's mechanics live in protocols.org "Paging Craig — the agent pager". When in doubt, fire both: the desktop notification persists for whenever he returns, the phone push reaches him now. + ** Managing Alarms #+begin_src bash diff --git a/.ai/workflows/process-meeting-transcript.org b/.ai/workflows/process-meeting-transcript.org index 4dd340f..d0806ad 100644 --- a/.ai/workflows/process-meeting-transcript.org +++ b/.ai/workflows/process-meeting-transcript.org @@ -1,5 +1,5 @@ #+TITLE: Process Meeting Transcript Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-02-03 * Overview @@ -10,16 +10,16 @@ This workflow defines the process for processing meeting recordings from start t Trigger this workflow when: - Craig says "process the transcript" or "process the recording" or similar -- New recording files (.mkv) appear in ~/sync/recordings/ after meetings +- New recording files (.mkv, .m4a, or .flac) appear in ~/sync/recordings/ after meetings - Craig wants to process meeting recordings into labeled transcripts * Prerequisites -- Recording file(s) exist in ~/sync/recordings/ (*.mkv) +- Recording file(s) exist in ~/sync/recordings/ (*.mkv, *.m4a, or *.flac) - Calendar files available at ~/.emacs.d/data/*cal.org for meeting titles - AssemblyAI transcription script at ~/.emacs.d/scripts/assemblyai-transcribe - AssemblyAI API key stored in ~/.authinfo.gpg (machine api.assemblyai.com) -- ffmpeg available for audio extraction +- ffmpeg available for audio extraction (video .mkv only; .m4a and .flac skip extraction) * The Workflow @@ -43,13 +43,13 @@ Classification is per recording, not per session — a single run can carry this Find and match recording files with calendar events. *Run sub-steps 1 and 3 (recording list + calendar dump) as a single parallel batch* — they're independent. Sub-step 2 (parse timestamps) and sub-step 4 (matching) work from those two outputs in-memory, so they're sequential after the batch. -1. **List recordings:** Find all recording files in ~/sync/recordings/ (video .mkv or audio-only .m4a) +1. **List recordings:** Find all recording files in ~/sync/recordings/ (video .mkv or audio-only .m4a / .flac) #+begin_src bash - ls -la ~/sync/recordings/*.mkv ~/sync/recordings/*.m4a 2>/dev/null + ls -la ~/sync/recordings/*.mkv ~/sync/recordings/*.m4a ~/sync/recordings/*.flac 2>/dev/null #+end_src - Audio-only recordings (.m4a) are used when no screen content is expected. These skip Step 3 (audio extraction) since they're already in a transcribable format. + Audio-only recordings (.m4a or lossless .flac) are used when no screen content is expected. These skip Step 3 (audio extraction) since they're already in a transcribable format. FLAC is the recorder's current audio format — its frames are self-contained, so an interrupted recording still decodes. -2. **Extract timestamps:** Parse date/time from each filename (format: YYYY-MM-DD-HH-MM-SS.mkv or .m4a) +2. **Extract timestamps:** Parse date/time from each filename (format: YYYY-MM-DD-HH-MM-SS.mkv, .m4a, or .flac) 3. **Match with calendar:** Check ~/.emacs.d/data/*cal.org for meetings at those times #+begin_src bash @@ -74,7 +74,7 @@ Per =cross-project.md=: transcribe and label here, then deliver the *labeled tra ** Step 3: Extract Audio (video recordings only) -*Skip this step for .m4a files* — they are already audio and can go directly to transcription. +*Skip this step for .m4a and .flac files* — they are already audio and can go directly to transcription. For .mkv video recordings, extract audio for transcription: @@ -96,11 +96,11 @@ Output: /tmp/FILENAME.m4a (temporary, deleted after transcription) #+begin_src bash # For .mkv files (audio was extracted to /tmp/): ~/.emacs.d/scripts/assemblyai-transcribe /tmp/FILENAME.m4a > ~/sync/recordings/FILENAME.txt - # For .m4a files (transcribe directly): + # For .m4a or .flac files (transcribe directly — AssemblyAI accepts both natively): ~/.emacs.d/scripts/assemblyai-transcribe ~/sync/recordings/FILENAME.m4a > ~/sync/recordings/FILENAME.txt #+end_src -2. **Clean up:** Delete intermediate .m4a file after successful transcription (only for .mkv extractions — do NOT delete original .m4a recordings) +2. **Clean up:** Delete intermediate .m4a file after successful transcription (only for .mkv extractions — do NOT delete original .m4a or .flac recordings) #+begin_src bash rm /tmp/FILENAME.m4a #+end_src @@ -181,13 +181,13 @@ Present the speaker identification table to Craig for confirmation: ** Step 9: Copy Recording to Meetings Folder -1. Ensure engagement meetings folder exists and patterns are in .gitignore (~*/meetings/*.mkv~ and ~*/meetings/*.m4a~) +1. Ensure engagement meetings folder exists and patterns are in .gitignore (~*/meetings/*.mkv~, ~*/meetings/*.m4a~, and ~*/meetings/*.flac~) 2. Copy the recording file with descriptive name: #+begin_src bash # Video recordings: cp ~/sync/recordings/YYYY-MM-DD-HH-MM-SS.mkv {engagement}/meetings/YYYY-MM-DD_HH-MM-meeting-name.mkv - # Audio-only recordings: + # Audio-only recordings (.m4a or .flac — copy with the original extension): cp ~/sync/recordings/YYYY-MM-DD-HH-MM-SS.m4a {engagement}/meetings/YYYY-MM-DD_HH-MM-meeting-name.m4a #+end_src Example: ~deepsat/meetings/2026-02-03_11-02-standup-ipm-grooming.mkv~ diff --git a/.ai/workflows/read-calendar-events.org b/.ai/workflows/read-calendar-events.org index be66bf4..5eac529 100644 --- a/.ai/workflows/read-calendar-events.org +++ b/.ai/workflows/read-calendar-events.org @@ -1,5 +1,5 @@ #+TITLE: Read Calendar Events Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-02-01 * Overview diff --git a/.ai/workflows/readability-audit.org b/.ai/workflows/readability-audit.org index 8223a03..90ad366 100644 --- a/.ai/workflows/readability-audit.org +++ b/.ai/workflows/readability-audit.org @@ -1,5 +1,5 @@ #+TITLE: Readability Audit Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-06-28 * Overview diff --git a/.ai/workflows/rename-artifact.org b/.ai/workflows/rename-artifact.org index 7b9f15b..a8d1246 100644 --- a/.ai/workflows/rename-artifact.org +++ b/.ai/workflows/rename-artifact.org @@ -1,5 +1,5 @@ #+TITLE: Rename an .ai Artifact -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-05-31 * Summary diff --git a/.ai/workflows/send-email.org b/.ai/workflows/send-email.org index 065f925..82d2286 100644 --- a/.ai/workflows/send-email.org +++ b/.ai/workflows/send-email.org @@ -1,5 +1,5 @@ #+TITLE: Email Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-01-26 * Overview diff --git a/.ai/workflows/session-harvest.org b/.ai/workflows/session-harvest.org index c48d689..54a7c09 100644 --- a/.ai/workflows/session-harvest.org +++ b/.ai/workflows/session-harvest.org @@ -1,5 +1,5 @@ #+TITLE: Session-Harvest Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-06-11 * Overview diff --git a/.ai/workflows/spec-create.org b/.ai/workflows/spec-create.org index 1249181..39758a0 100644 --- a/.ai/workflows/spec-create.org +++ b/.ai/workflows/spec-create.org @@ -1,5 +1,5 @@ #+TITLE: Spec-Create Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-06-09 * Overview @@ -47,6 +47,8 @@ Capture, in this order: ** Phase 2 — Design, alternatives, decisions 1. *Design* — overview first, then detail. Write the reasoning as *prose, not bullet dumps* — prose exposes weak logic that bullets let you hide. Use bullets only for genuinely enumerable lists. When the thing has an interface, use the *two-altitude* split (Rust RFC): explain it once for a user/caller, once for an implementer. + + *Non-trivial UI.* When the deliverable is a real UI (a panel, a multi-control surface, an interacting visual layout — not a single dialog, a CLI flag, or a one-off prompt), the design isn't settled on the page. Run the research → ~5 distinct working-prototype directions → iterate-one-to-final process in =claude-rules/ui-prototyping.md= before treating the UI design as done, and add a =Prototype iterations= subsection under the spec's status heading linking every iteration (final linked in the design section). A UI design decision moves to =DONE= only once it's been seen working in a prototype. 2. *Alternatives considered* — the load-bearing section authors skip and reviewers need most. For each option, force a why-not with the MADR grammar: "Good, because… / Bad, because… / Neutral, because…". Even one rejected option, with the reason, beats presenting one path as inevitable. 3. *Decisions* — capture each real choice as an org =TODO= task carrying an inline mini-ADR (Nygard's spine): - The heading is =** TODO <Decision name>=. It flips to =DONE= when the decision-maker agrees with the call; until then it stays =TODO=. diff --git a/.ai/workflows/spec-review.org b/.ai/workflows/spec-review.org index d4998eb..0da8e65 100644 --- a/.ai/workflows/spec-review.org +++ b/.ai/workflows/spec-review.org @@ -134,6 +134,7 @@ Work the spec against these. Each is a source of concrete findings, not a box to - *Security & privacy.* API keys safe? Debug logs leaking secrets or private issue text? Confirmations before mutating shared workspace objects? Personal vs shared distinguished? Local files holding sensitive descriptions/comments? Anything to redact from messages/logs? Any work-tracker integration may handle private company data. - *UX & accessibility.* Discoverable commands? Recoverable mistakes? Prompts ordered to the task? Safe, useful defaults? Informative-not-noisy status messages? Does the UI avoid implying unsupported actions are supported? Match the upstream product's permissions/concepts? Are customizations named in user language, with clear defaults and docstrings? For Emacs packages, command names, completion candidates, buffer layout, defcustom names, and message wording *are* the UX. - *Operational-panel UI traps.* Applies when the spec covers a user-facing panel, dialog, or control surface; skip otherwise. Lists that mix saved, current, and generated items must name each item's source. Refresh or scan actions must not gate data that could be shown immediately. Add-forms must not ask the user to retype values the system already discovered. Destructive confirmations read in future tense before the action and verified-result tense after it. Diagnostics, performance, logging, and repair affordances are reviewed as one coherent flow before extra pages or buttons are added. A popup launched from a bar, tray, or tool surface should visually belong to that launcher. (Promoted from archsetup's Waybar network-panel review, 2026-06-30.) +- *Prototype process for non-trivial UI.* Applies when the deliverable is a real UI (a panel, a multi-control surface, an interacting visual layout — not a single dialog or CLI flag); skip otherwise. Verify the =claude-rules/ui-prototyping.md= process ran: category research is cited in Goals/Design, the final prototype is linked in the design section, a =Prototype iterations= subsection under the status heading lists every pass, and each UI design decision is backed by a prototype it was seen working in rather than asserted on the page. A non-trivial-UI spec with decisions but no prototype evidence is a =:blocking:= finding. - *Test strategy and coverage.* Characterization tests before behavior changes? Pure functions to unit-test? API responses needing fixtures? Command flows needing stubs? Regression tests for prior bugs? Boundary/error cases? What's covered elsewhere and shouldn't be re-tested? Which existing tests must change? How is coverage generated, summarized, and used to find untested/refactor-worthy code? Prefer tests that lock contracts: representation shape, query compilation, sync no-op, conflict refusal, pagination, dirty-buffer protection, log redaction, and long-running/slow-operation behavior via fakes rather than flaky live dependencies. - *Observability & operations.* How does a user see what the package is doing? Progress messages for long ops? Useful, safe debug logging? Are logs structured enough to isolate issues from a bug report? Are commands provided to inspect/clear caches, test connectivity, diagnose backends/tools, copy redacted debug info, or reproduce command invocations? How are terminal states discovered: completion, failure, partial success, stalled/hung, cancelled, cleanup-unverified, and "needs user action"? Does the product notify only when useful, avoid noisy success spam, and keep non-success states visible until acknowledged? For generated org files, headers should often carry source, filter/view name, refresh time, count, truncation state. - *Comparable-product sentiment.* When there are obvious adjacent products, research what users love and hate about them from official docs plus current community reports. Do not cargo-cult their feature set; translate findings into the spec's scope. For each loved behavior, say whether the spec provides it, intentionally omits it, or defers it. For each hated behavior, say whether the spec avoids, resolves, inherits, or accepts it. diff --git a/.ai/workflows/startup.org b/.ai/workflows/startup.org index 4e3d417..929d482 100644 --- a/.ai/workflows/startup.org +++ b/.ai/workflows/startup.org @@ -1,5 +1,5 @@ #+TITLE: Startup Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-04-25 * Summary diff --git a/.ai/workflows/status-check.org b/.ai/workflows/status-check.org index efff16d..4a9972c 100644 --- a/.ai/workflows/status-check.org +++ b/.ai/workflows/status-check.org @@ -1,5 +1,5 @@ #+TITLE: Status Check Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-02-02 * Overview diff --git a/.ai/workflows/summarize-emails.org b/.ai/workflows/summarize-emails.org index 6ac5e6f..c9c7001 100644 --- a/.ai/workflows/summarize-emails.org +++ b/.ai/workflows/summarize-emails.org @@ -1,5 +1,5 @@ #+TITLE: Summarize Emails Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-02-14 * Overview diff --git a/.ai/workflows/suspend.org b/.ai/workflows/suspend.org index 1c16bb9..3691f60 100644 --- a/.ai/workflows/suspend.org +++ b/.ai/workflows/suspend.org @@ -1,5 +1,5 @@ #+TITLE: Session Suspend Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-06-28 * Overview diff --git a/.ai/workflows/sync-email.org b/.ai/workflows/sync-email.org index 52a7caf..863b400 100644 --- a/.ai/workflows/sync-email.org +++ b/.ai/workflows/sync-email.org @@ -1,5 +1,5 @@ #+TITLE: Sync Email Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-02-01 * Overview diff --git a/.ai/workflows/task-audit.org b/.ai/workflows/task-audit.org index 7d2b758..aa50176 100644 --- a/.ai/workflows/task-audit.org +++ b/.ai/workflows/task-audit.org @@ -1,5 +1,5 @@ #+TITLE: Task Audit Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-05-22 * Overview diff --git a/.ai/workflows/task-review.org b/.ai/workflows/task-review.org index ba1571a..7ea2e8e 100644 --- a/.ai/workflows/task-review.org +++ b/.ai/workflows/task-review.org @@ -1,5 +1,5 @@ #+TITLE: Task Review Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-05-20 * Overview @@ -92,7 +92,7 @@ Set =:LAST_REVIEWED:= to today's date (from above) in the task's =:PROPERTIES:= Body... #+end_example -The exact date string matters: =task-review-staleness.sh= and the wrap-up health check both parse =:LAST_REVIEWED: YYYY-MM-DD=. +Format: =:LAST_REVIEWED:= takes a bare ISO date (=2026-05-20=) or an org-native inactive timestamp (=[2026-05-20 Tue]=, matching the =CREATED:=/=CLOSED:= cookies beside it); =task-review-staleness.sh= and the wrap-up health check normalize both to the date. A value that is neither is a data error — the staleness script warns loudly to stderr (naming the file, line, and value) and leaves the task out of the stale count rather than silently reporting a freshly-reviewed task as never-reviewed. Stamp a clean date and the warning never fires. *** Killing a task diff --git a/.ai/workflows/triage-intake.cmail.org b/.ai/workflows/triage-intake.cmail.org index d818c72..8d8abfb 100644 --- a/.ai/workflows/triage-intake.cmail.org +++ b/.ai/workflows/triage-intake.cmail.org @@ -1,5 +1,5 @@ #+TITLE: Triage Intake — cmail (Proton) Source -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-05-26 # Source plugin for the triage-intake engine. See triage-intake.org for the diff --git a/.ai/workflows/triage-intake.github-prs.org b/.ai/workflows/triage-intake.github-prs.org index c1bc796..644421c 100644 --- a/.ai/workflows/triage-intake.github-prs.org +++ b/.ai/workflows/triage-intake.github-prs.org @@ -1,5 +1,5 @@ #+TITLE: Triage Intake — Personal GitHub PRs Source -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-05-26 # Source plugin for the triage-intake engine. See triage-intake.org for the diff --git a/.ai/workflows/triage-intake.org b/.ai/workflows/triage-intake.org index a5a3bda..fde67a7 100644 --- a/.ai/workflows/triage-intake.org +++ b/.ai/workflows/triage-intake.org @@ -1,5 +1,5 @@ #+TITLE: Triage Intake Workflow (Engine) -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-05-01 * Summary @@ -414,7 +414,7 @@ The sentinel is checked into git, but git tracks content, not mtime — so an mt Craig, via the work project's same-day handoff: "we only need to report if anything's changed when we do triage intake." Sweep summaries report deltas only — a new invite, a new/moved/cancelled event, a new message needing attention. Unchanged sources get no block (the "Calendar — quiet" roll-call is retired), and an all-quiet sweep renders as a single "HH:MM sweep: no changes" line. Failures keep their loud banner (never folded into the no-change line) and the suggested-actions line stays when actions are queued. Same ruling: the telegram plugin's dev-community group traffic is dropped from reports entirely unless Craig asks (see that plugin's 2026-06-11 note). **** 2026-06-10: Loud failure surfacing (Phase C item 0 + Common Mistake 9) -Craig: "highlight any failures in daily triage loudly. I get important communication from all these channels." Trigger: the 2026-06-10 sweep shipped with Signal silently missing — a standalone receive hung on the account lock while the signel daemon owned it, and the failure looked identical to a quiet source. Failures now lead the summary in a ⚠ SCAN FAILED banner; the telegram plugin's failure path points at this rule. +Craig: "highlight any failures in daily triage loudly. I get important communication from all these channels." Trigger: the 2026-06-10 sweep shipped with Signal silently missing — a standalone receive hung on the signal-cli account lock while another client held it, and the failure looked identical to a quiet source. Failures now lead the summary in a ⚠ SCAN FAILED banner; the telegram plugin's failure path points at this rule. **** 2026-05-26: Refactor into engine + source plugins Split the monolithic workflow into a source-agnostic engine (this file) and per-source plugins named =triage-intake.<source>.org=. The engine carries the anchor/sentinel logic, the four-bucket model, the Phase A-D orchestration, the todo.org persistence convention, and the exit criteria. Each source's scan/classify/render/action knowledge moved to its own plugin. General plugins (personal-gmail, personal-calendar, cmail, github-prs) live in =.ai/workflows/= and are template-synced; project-specific plugins (a work project's Linear, work Gmail, work Slack, enterprise PRs) live in the project's =.ai/project-workflows/= and are never synced. Phase 0 globs *both* directories — the loud requirement, because missing the project dir silently halves the sweep. Naming convention: first dot is the engine/plugin boundary, deeper dots reserved for sub-adapters. This removed all DeepSat/Linear specifics from the engine; they become work-project plugins. diff --git a/.ai/workflows/triage-intake.personal-calendar.org b/.ai/workflows/triage-intake.personal-calendar.org index bf7d543..b5ee67a 100644 --- a/.ai/workflows/triage-intake.personal-calendar.org +++ b/.ai/workflows/triage-intake.personal-calendar.org @@ -1,5 +1,5 @@ #+TITLE: Triage Intake — Personal Calendar Source -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-05-26 # Source plugin for the triage-intake engine. See triage-intake.org for the diff --git a/.ai/workflows/triage-intake.personal-gmail.org b/.ai/workflows/triage-intake.personal-gmail.org index aa0554d..7fb1231 100644 --- a/.ai/workflows/triage-intake.personal-gmail.org +++ b/.ai/workflows/triage-intake.personal-gmail.org @@ -1,5 +1,5 @@ #+TITLE: Triage Intake — Personal Gmail Source -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-05-26 # Source plugin for the triage-intake engine. See triage-intake.org for the @@ -21,10 +21,29 @@ Personal Gmail unread in the inbox since the anchor: mcp__google-docs-personal__listMessages q="is:unread in:inbox after:<anchor-epoch>" maxResults=100 #+end_src -⚠ *Express the cutoff as the literal UNIX epoch* — =after:1778856990=, not =after:YYYY/MM/DD=. Gmail's =after:YYYY/MM/DD= operator only supports day resolution; the =YYYY/MM/DD HH:MM:SS= form is NOT valid syntax — Gmail parses the space as a term separator, treats =HH:MM:SS= as a search term that never matches, and returns 0 results, silently masking unread mail. The engine supplies =<anchor-epoch>= because this source declares =ANCHOR: epoch=. +⚠ *Express every anchor cutoff as the literal UNIX epoch* — =after:1784177122= and =before:1784177122= for the same anchor, never the =YYYY/MM/DD= form. This governs *both* anchored queries: the scan above and the backlog-residue probe below. They must meet at the same instant or mail falls between them permanently. Gmail's day-resolution operators fail two different ways: =after:YYYY/MM/DD HH:MM:SS= is not valid syntax at all — Gmail parses the space as a term separator, treats =HH:MM:SS= as a search term that never matches, and returns 0 results, silently masking unread mail — while =before:YYYY/MM/DD= is valid but excludes the named day entirely, so pairing it with a second-resolution scan leaves the whole anchor day covered by neither query. The engine supplies =<anchor-epoch>= because this source declares =ANCHOR: epoch=. + +The rule binds the *anchor* windows only. The date-slice walk below deliberately uses =before:<oldest-full-day-seen>= at day resolution — safe there because consecutive slices overlap and get deduped by message id. ⚠ *Do NOT add =-category:promotions -category:social=.* That filter masked 67 promo+social messages across two runs (2026-05-04, 2026-05-06), both needing a follow-up sweep. Pull the full unfiltered set; the trash-leaning bias in Classify handles promotions and social directly. +⚠ *The MCP caps at =maxResults=100= and exposes NO =pageToken= parameter.* The response carries a =nextPageToken=, but the tool can't consume it, so a pile over 100 is silently truncated — the tail below the cap never gets classified, and every later anchored sweep skips it (it predates the new anchor). This is exactly how a 300+ backlog accumulated invisibly by 2026-07-08. Two consequences: + +- *Never treat a 100-row result as complete.* When a scan returns exactly 100, walk the tail in *date slices*: re-query with =before:<oldest-full-day-seen>= (day resolution), repeat until a page returns fewer than 100, dedupe by message id across slices (the day-resolution boundary overlaps). +- *Never report =resultSizeEstimate= as a count.* It's unreliable — observed stuck at "201" across three different queries whose real union exceeded 300. + +*** Backlog-residue check (every sweep — cheap, mandatory) + +The anchored scan is blind to anything unread from *before* the anchor. After it, run one probe for pre-anchor residue: + +#+begin_src text +mcp__google-docs-personal__listMessages q="is:unread in:inbox before:<anchor-epoch>" maxResults=5 +#+end_src + +The cutoff is the epoch, matching the scan's =after:<anchor-epoch>= — see the epoch rule above. + +If it returns any messages, surface one loud line in the sweep summary: "Backlog: unread predating the anchor exists (N+ shown; date-slice to inventory)" and offer a backlog sweep. Never fold the residue into a quiet sweep — an anchored "no changes" claim is only true for the window the scan saw. (Added 2026-07-08 after ~300 pre-anchor unread accumulated unseen; the probe returns actual messages, so it works where the estimate lies. Shipped with a day-resolution cutoff that hid the entire anchor day; fixed to epoch 2026-07-16 after a home sweep reported the backlog clear while two July-15 messages sat unread.) + ** Classify Bias: *trash-leaning* — personal Gmail is high noise volume. diff --git a/.ai/workflows/triage-intake.telegram.org b/.ai/workflows/triage-intake.telegram.org index 9caa4e1..5039a8b 100644 --- a/.ai/workflows/triage-intake.telegram.org +++ b/.ai/workflows/triage-intake.telegram.org @@ -1,5 +1,5 @@ #+TITLE: Triage Intake — Telegram Source -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-06-09 # Source plugin for the triage-intake engine. See triage-intake.org for the diff --git a/.ai/workflows/work-the-backlog.org b/.ai/workflows/work-the-backlog.org index b0666e7..090841d 100644 --- a/.ai/workflows/work-the-backlog.org +++ b/.ai/workflows/work-the-backlog.org @@ -1,5 +1,5 @@ #+TITLE: Work the Backlog -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-07-02 * Overview @@ -152,7 +152,7 @@ With paging on, fire one page when the set is done or the cap is hit — end-of- notify info "Page" "<project>: <N> done, <M> remaining — <one-line summary>" --persist #+end_src -=--persist= keeps it on screen until dismissed, and =info= is the page-me urgency convention (persistent but never crash-scary). The page fires when the set completes *or* the cap stops the run — either way exactly once. The message carries the project name, the completed count, and the remaining count (with skipped tasks noted in the run summary) so Craig can confirm ready and name the next project in one reply. There is no separate page-signal call — =notify= is the paging surface. +=--persist= keeps it on screen until dismissed, and =info= is the page-me urgency convention (persistent but never crash-scary). The page fires when the set completes *or* the cap stops the run — either way exactly once. The message carries the project name, the completed count, and the remaining count (with skipped tasks noted in the run summary) so Craig can confirm ready and name the next project in one reply. =notify= is the desktop paging surface; a run that expects Craig to be away also fires =agent-page= with the same message (the Signal phone channel — protocols.org "Paging Craig — the agent pager"). * Metrics diff --git a/.ai/workflows/wrap-it-up.org b/.ai/workflows/wrap-it-up.org index d0c4e75..5ce88a5 100644 --- a/.ai/workflows/wrap-it-up.org +++ b/.ai/workflows/wrap-it-up.org @@ -1,5 +1,5 @@ #+TITLE: Session Wrap-Up Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-04-20 * Overview @@ -202,9 +202,14 @@ else followups=".ai/lint-followups.org" fi [ -f todo.org ] && emacs --batch -q -l .ai/scripts/lint-org.el \ - --followups-file="$followups" todo.org + --fix --followups-file="$followups" todo.org #+end_src +The =--fix= flag is required for the writes: lint-org's CLI default is +report-only (a linter reports, it doesn't write), and this wrap-up pass is +the deliberate exception that applies fixes — its diff rides the wrap-up +commit for review. + =lint-org= runs =org-lint= over =todo.org=, auto-applies four mechanical categories (=item-number= counters, bare =#+begin_src= → =#+begin_example=, multi-line planning-info merged onto one line, =**X.**= → =*X.*=), and diff --git a/.claude/commands/lint-org.md b/.claude/commands/lint-org.md index 64ec967..d9719fa 100644 --- a/.claude/commands/lint-org.md +++ b/.claude/commands/lint-org.md @@ -60,10 +60,10 @@ Out of scope (refuse, don't try to lint): 3. Invoke the script: ```bash - emacs --batch -q -l .ai/scripts/lint-org.el FILE + emacs --batch -q -l .ai/scripts/lint-org.el --fix FILE ``` - The script applies every mechanical fix, then emits structured stdout. First line is a summary; each subsequent line is a plist describing one issue: + `--fix` is required for the writes — the script's default invocation is report-only (a linter reports, it doesn't write). With the flag, the script applies every mechanical fix, then emits structured stdout. First line is a summary; each subsequent line is a plist describing one issue: ``` ;; lint-org: file=todo.org mechanical=4 judgment=11 diff --git a/AGENTS.md b/AGENTS.md new file mode 120000 index 0000000..1e43f54 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1 @@ +claude-templates/AGENTS.md
\ No newline at end of file @@ -5,6 +5,7 @@ SKILLS_DIR := $(HOME)/.claude/skills RULES_DIR := $(HOME)/.claude/rules HOOKS_DIR := $(HOME)/.claude/hooks CLAUDE_DIR := $(HOME)/.claude +CODEX_DIR := $(HOME)/.codex LOCAL_BIN := $(HOME)/.local/bin AI_LAUNCHER := $(CURDIR)/claude-templates/bin/ai SKILLS := $(patsubst %/SKILL.md,%,$(wildcard */SKILL.md)) @@ -220,6 +221,17 @@ install: ## Symlink skills, rules, config, hooks, and bin scripts into place fi \ fi @echo "" + @echo "Agent entry (codex):" + @mkdir -p "$(CODEX_DIR)" + @if [ -L "$(CODEX_DIR)/AGENTS.md" ]; then \ + echo " skip AGENTS.md (already linked)"; \ + elif [ -e "$(CODEX_DIR)/AGENTS.md" ]; then \ + echo " WARN AGENTS.md exists and is not a symlink — skipping"; \ + else \ + ln -s "$(CURDIR)/claude-templates/AGENTS.md" "$(CODEX_DIR)/AGENTS.md"; \ + echo " link AGENTS.md → $(CODEX_DIR)/AGENTS.md"; \ + fi + @echo "" @echo "Hooks (default):" @for hook in $(DEFAULT_HOOKS); do \ name=$$(basename $$hook); \ diff --git a/archive/task-archive.org b/archive/task-archive.org index e52563d..9e053b4 100644 --- a/archive/task-archive.org +++ b/archive/task-archive.org @@ -1718,3 +1718,292 @@ CLOSED: [2026-06-24 Wed 05:46] A scheduled headless morning run chaining the existing pieces: startup checks, the triage-intake scan, a system health check — producing the prep doc plus a report and a notify ping, with all remediation propose-only. Staged adoption from the 2026-06-11 insights report's "Self-Healing Daily Ops Orchestrator": read-only first; promote individual routine remediations to auto only after each has a track record. Known blockers to design around: headless MCP auth (interactively-authenticated servers are absent in cron runs) and the consent boundary (triage Phase D, anything destructive). The triage limb can reuse triage-intake's *auto mode* (added 2026-06-15, see [[file:.ai/workflows/triage-intake.org]]) — its accumulate-don't-mutate sweep is the propose-only behavior this orchestrator wants. Auto mode itself runs in-session (inherited MCP auth); the orchestrator is the durable headless schedule, so the headless-auth blocker above is the part still on this task to solve. +** DONE [#B] wrap-it-up teardown + "wrap it up and shutdown" :feature: +CLOSED: [2026-07-01 Wed] +:PROPERTIES: +:CREATED: [2026-06-23 Tue] +:LAST_REVIEWED: 2026-06-24 +:END: +Two additions to =wrap-it-up.org=, designed by Craig (home, 2026-06-23). Item 1: bare "wrap it up" also tears down the session — kill the =aiv-<proj>= tmux session (takes =claude= with it), kill the vterm buffer, restore geometry. Teardown is the default; "wrap it up with summary" wraps without teardown (keeps the buffer readable). Must fire from a Stop/SessionEnd hook via a sentinel file, decoupled and last, so the valediction flushes before the session dies, and strictly after commit+push is verified. Item 2: "wrap it up and shutdown" → wrap, then a hard blocking gate (abort unless this is the only live =aiv-*= session), then an abort-able 10→1 countdown, then =sudo shutdown now=. Countdown can't run through the Bash tool (stdout buffers — prints all ten at once); needs a detached tty writer or an Emacs =run-at-time= timer. Companion: =cj/ai-term-quit= (and optional =cj/ai-term-live-count=) must live in =.emacs.d/modules/ai-term.el= — route there via inbox-send when building so both sides land together. Open decisions for Craig first: qualifier wording ("with summary" vs "and summarize"), countdown home (tty script vs Emacs timer), session-count mechanism (=tmux ls= / =pgrep claude= / helper). Shared-asset, review-gated. Proposal: [[file:docs/design/2026-06-23-wrap-teardown-shutdown-proposal.org][proposal]]. From home 2026-06-23. + +*** 2026-06-23 Tue @ 23:31:59 -0400 Built the rulesets side; companion routed to .emacs.d +Craig's three decisions (2026-06-23): non-destructive qualifier is *both* "with summary" and "and summarize"; countdown is an Emacs =run-at-time= timer; the gate uses =cj/ai-term-live-count=. Built and pushed: =hooks/ai-wrap-teardown.sh= (Stop hook, sentinel-gated, 8 bats tests green), =hooks/settings-snippet.json= Stop wiring, =wrap-it-up.org= Teardown-mode section + Step 6 + checklist, INDEX trigger update. Architecture: both teardown and shutdown fire from the Stop hook via a basename-keyed sentinel (=/tmp/ai-wrap-teardown-<proj>=, =/tmp/ai-wrap-shutdown-<proj>=) dropped only at the end of Step 6 after commit+push, so the valediction flushes first. No bin script — the gate/countdown/teardown are all =emacsclient= calls into the companion. Companion spec (=cj/ai-term-quit=, =cj/ai-term-live-count=, =cj/ai-term-shutdown-countdown=) routed to .emacs.d via inbox-send. Remaining: .emacs.d lands the three functions, then the manual end-to-end validation below. Task stays DOING until both sides verify. + +*** 2026-06-23 Tue @ 23:40 .emacs.d received + filed the companion spec +Per a roam-inbox FYI from .emacs.d (2026-06-23 23:38): both ai-term handoffs (multi-LLM support + this wrap-teardown companion spec) landed and are filed as .emacs.d tasks. The teardown one is flagged for its own focused session to land alongside the rulesets half. Part (c) is now in progress on the .emacs.d side. + +*** 2026-06-24 Wed @ 06:51:13 -0400 Unblocked — .emacs.d companion landed; feature now live +The three companion functions are in =.emacs.d/modules/ai-term.el= (=cj/ai-term-quit= 1068, =cj/ai-term-live-count= 1087, =cj/ai-term-shutdown-countdown= 1109), matching the contract — double-checked the bodies: quit kills session+buffer+restores layout idempotently, live-count returns the gate integer, shutdown-countdown re-checks the gate (TOCTOU guard), runs an abort-able =run-at-time= countdown (C-g cancels), then a configurable =cj/ai-term-shutdown-command=. 13 ERT tests, headless-verified live (.emacs.d FYI 2026-06-24 06:44). Dropped =:blocked:= / =:BLOCKED_BY:= — the build dependency is resolved; only the manual end-to-end validation below remains. NOTE: with the Stop hook wired and the companion present, the feature is now functional — the next bare "wrap it up" will actually tear the session down. Run the validation below before relying on it. + +*** 2026-07-01 Wed @ 21:52:15 -0400 Plumbing pre-flight re-verified; only the eyes-on tests remain +Fresh pre-flight, all green: Stop hook block in =~/.claude/settings.json= points at =~/.claude/hooks/ai-wrap-teardown.sh=, the symlink resolves to the rulesets canonical, no stale =/tmp/ai-wrap-teardown-*= sentinel, and all three companion functions are live in the daemon (=(t t t)=). Three =aiv-*= sessions live right now (=_emacs_d=, =archsetup=, =rulesets=), so the shutdown-gate refusal test has its multi-session condition available. Everything left in the checklist below needs Craig's eyes on a scratch session: buffer teardown + geometry restore, the qualifier opt-outs, the countdown render + C-g, and the push-failure guard. + +*** 2026-07-01 Wed @ 21:59:43 -0400 Manual end-to-end validation passed — all five tests, Craig's live run +Craig ran the full checklist in a live Emacs/tmux ai-term setup: (1) bare "wrap it up" tore down after the valediction rendered, geometry restored, no lingering sentinel; (2) "with summary" / "and summarize" both wrapped without teardown, buffer stayed readable; (3) "wrap it up and shutdown" with another aiv-* session live refused the shutdown, named the other session, fell back to a normal wrap; (4) as the sole session, the 10→1 echo-area countdown rendered one-per-second, C-g cancelled cleanly, and a full run fired the (stubbed) shutdown command; (5) with the push made to fail, the wrap stopped at the failure and no sentinel was dropped. Works great — feature validated and live. Both sides complete: rulesets Stop hook + wrap-it-up Teardown mode, .emacs.d companion functions. +** DONE [#C] Guard against hardcoded host identity in synced files :feature:solo: +CLOSED: [2026-07-02 Thu] +:PROPERTIES: +:CREATED: [2026-06-22 Mon] +:LAST_REVIEWED: 2026-06-24 +:END: +A =CLAUDE.md= / notes file that asserts mutable environment identity as a fixed fact ("This machine is ratio", a current OS, an IP, "the laptop") is false on every machine the synced/tracked file lands on but one. It bit a real archsetup session: a stale "this machine is ratio" line made the agent reason backwards all session while on velox. Proposal: a claude-rule — don't assert mutable host/env identity as a fixed fact in a tracked/synced project file; derive it at runtime and name the command (=uname -n= for host; the =hostname= binary is often absent). Optionally a codify- or startup-time lint flagging "this machine is <name>" / "the current host is" style claims. Proposal: [[file:docs/design/2026-06-21-host-identity-guard-proposal.org][proposal]]. From archsetup 2026-06-21. + +2026-07-02 Thu @ 05:09:58 -0400 — Craig (speedrun pre-flight): rule + startup lint. A new claude-rules file plus a cheap grep probe in startup flagging host-identity claims in CLAUDE.md / notes.org fleet-wide. + +Resolution 2026-07-02: claude-rules/host-identity.md written (fixed-identity claims banned in tracked/synced docs, runtime derivation via uname -n, fleet-description carve-out, the archsetup worked failure) and linked machine-wide by make install. startup.org gained Phase A probe 13 (grep for "this machine/host/box is" claims in CLAUDE.md + notes.org, fixture-verified bash+zsh) and the Phase C host-identity flag line. Flags for judgment, never blocks. +** DONE [#C] No-approvals speedrun — cross-project autonomous-batch mode :feature:spec: +CLOSED: [2026-07-02 Thu] +:PROPERTIES: +:CREATED: [2026-06-15 Mon] +:LAST_REVIEWED: 2026-06-24 +:SPEC_ID: 90f623cd-fdbe-4f5c-b63d-b2f84d9151cf +:END: +A named mode for coding projects: Craig names an ordered task set and says "speedrun" / "no approvals speedrun"; the set is worked autonomously, each task held to the full quality bar (TDD red→green, =/review-code=, =/voice= on the commit) and committed + pushed as its own logical commit, with all needed quick decisions gathered in one pre-flight Q&A (answer or "skip this") and a VERIFY filed for anything underspecified or needing deliberation, plus an end-of-set page listing completed + remaining + skipped tasks. Task size is not a gate — large tasks decompose into per-commit chunks. Surfaced by .emacs.d from a 2026-06-15 theme-studio session where the shape worked. Source proposal: [[file:docs/design/2026-06-15-fix-speedrun-workflow-proposal.org]] (.emacs.d handoff 2026-06-15). Build via =spec-create= when worked; we handle the task in priority order. + +Skeptical-review read (open design questions to resolve in the spec, not settled here): +- *Is it a new workflow or a documented preset?* The proposal frames it as no-approvals + always-push session modes plus an end page. Decide whether it needs its own workflow file or is mostly documentation of a preset over the two existing modes. +- *Where/how the page fires* — every task vs end-of-set, and via what. The paging surface is in flux (=page-signal= removed 2026-06-12), so reconcile against =notify --persist= or whatever paging stands now. +- *Auto-pull vs explicit list* — whether the set comes from an explicit ordered list or a tag/priority query. +- *Guardrails* — must refuse to speedrun tasks needing design decisions or carrying data-loss risk without a checkpoint (the sender's biased-safe unused-tile flag is the worked example). + +*** 2026-07-01 Wed @ 22:10:35 -0400 Phase 0 landed — hard tag definitions + review/audit enforcement +todo-format.md gained the "Hard definitions: :solo: and :quick:" subsection under the scheme header (fixed across projects: :solo: = buildable + agent-verifiable + no deliberation, with one-or-two upfront-answerable quick decisions allowed per the ratified spec; :quick: = ≤30-min effort hint, never a gate). task-review.org: the two tagging sections are now explicitly mandatory ("a review that skips them is incomplete") and gate 3 was realigned from "no upfront decision" to the spec's no-deliberation form — the stricter old wording predated the pre-flight-Q&A decision and would have wrongly excluded quick-question tasks. task-audit.org: the re-assess bullet is marked mandatory and points at the todo-format hard definitions as canonical. Phases 1-6 (work-the-backlog extraction, callers, commit gate, checklist/Q&A/page, metrics, synthesis) remain. + +*** 2026-06-16 Tue @ 00:53:36 -0500 Spec written; design questions answered +Craig's "your call" (2026-06-16) answered in [[id:90f623cd-fdbe-4f5c-b63d-b2f84d9151cf][the autonomous-batch execution spec]], which reconciles this with Phase E into one feature: +- *Most effective / workflow-vs-preset:* one dedicated =work-the-backlog.org= workflow holds the execution loop; "fix speedrun" is a thin named preset (no-approvals + always-push + end page) feeding it an explicit list, and the inbox-zero loop feeds it a tag query. Pros of the shared workflow: one execution loop to audit, inbox-zero's three callers stay clean, both input shapes reuse one guardrail set. Cons: one more workflow file and a caller-to-workflow indirection. The con list is shorter and lighter than the duplication cost of two separate features, which is why the shared workflow wins. The pros carry the more important entries (single audit surface, clean seam). +- *Paging:* end-of-set only, via =notify ... --persist= (reconciled past the removed page-signal wrapper). +- *Auto-pull vs explicit list:* both — explicit list for the preset, tag/priority query for the loop. +- *Effectiveness measurement (the trial Craig asked for):* the spec designs a per-task JSONL metrics log (=.ai/metrics/work-the-backlog.jsonl=), a corrections-in-next-session signal, and a periodic synthesis step that writes =:agent:metrics:= org-roam articles for later review — the "gather data + create org-roam articles" loop. +*** 2026-06-29 Mon @ 03:48:09 -0400 Ratified the autonomous-batch execution spec +Craig ratified all eight decisions in [[id:90f623cd-fdbe-4f5c-b63d-b2f84d9151cf][2026-06-16-autonomous-batch-execution-spec.org]] (revised this session — size gate removed, crisp four-item defer checklist, =:solo:= / =:quick:= definitions + task-review/audit enforcement, speedrun pre-flight Q&A). Spec Status → ready; implementation-ready across Phase 0–6. Decisions grew from six to eight during the revision. + +*** 2026-07-02 Thu @ 00:44:59 -0400 spec-response decomposition — :SPEC_ID: bound, spec DOING +Stamped the spec's UUID on this parent, broke Phases 1-6 into the build tasks below (plus the flip task and a live-trial validation child), and flipped the spec's status heading READY → DOING per the transition-ownership table. + +*** 2026-07-02 Thu @ 01:07:29 -0400 Phase 1 landed — execution loop extracted into work-the-backlog.org +work-the-backlog.org written (canonical + mirror): caller contract (task set + session mode + cap), five-outcome vocabulary, the loop, mechanical eligibility gate (TODO + :solo: per scheme header, safe-by-omission, no-scheme-header → don't run), four-item defer checklist, per-task quality bar, cap/kill-switch semantics, page + metrics stubs pointing at Phases 4-5. inbox.org's auto-mode per-cycle item 3 reverted to routing-only (yes-path execution removed; mode intro + closing line updated to match). INDEX.org entry added. make test green, sync clean; nothing invokes the new workflow yet. + +*** 2026-07-02 Thu @ 01:13:33 -0400 Phase 2 landed — both callers wired +inbox.org auto-mode item 3 regained its "run this batch next?" ask, now chaining into work-the-backlog as an explicit second step after routing (eligibility query + file-only + paging off + cap 1). work-the-backlog.org gained the two caller sections: the auto-loop contract and the no-approvals speedrun preset (seven-step pre-flight → autonomous-commit + always-push + paging-on over an explicit list; finer Q&A mechanics deferred to Phase 4). Speedrun trigger phrases live in the workflow + INDEX; "speedrun" always routes to the preset, with a disambiguation note in no-approvals.org and its INDEX entry. Each caller independently exercisable. + +*** 2026-07-02 Thu @ 01:18:07 -0400 Phase 3 landed — waiver-gated commit autonomy +Pinned the waiver format per D5: two marker lines in .ai/notes.org Workflow State — :COMMIT_AUTONOMY: yes (has the waiver) and :LOOP_MAY_COMMIT: yes (the unattended loop may also commit; requires the first). Absent or non-yes reads as no; the read is a fresh grep each run, never memory. Degrade contract written into work-the-backlog.org (surface in run intro + summary, never honor without the marker, never degrade silently); caller sections + Common Mistakes updated. Stamped rulesets' own :COMMIT_AUTONOMY: yes; :LOOP_MAY_COMMIT: deliberately not granted — Craig's call. .emacs.d holds the waiver too but its notes.org is its own scope; told via inbox-send to stamp its marker. + +*** 2026-07-02 Thu @ 01:21:47 -0400 Phase 4 landed — checklist mechanics, pre-flight Q&A contract, page +The four-item checklist (in since Phase 1) gained its mechanics: a VERIFY-filing subsection (dedup against an existing sibling first — the deferred task stays TODO, so without the check every run re-files; placement/heading/body per todo-format.md) and a quick-question routing subsection (discriminator: one-line factual/preference pick vs tradeoff-weighing; three-plus questions = underspecified = file; item 2 data-loss never routes to Q&A). Preset section gained the batch-ask contract (one message, recommendation-first numbered options per interaction.md, answers recorded as dated lines in the task bodies before the run). Page section finalized (fires once on set-done or cap-hit; notify --persist is the paging surface). Common Mistakes 12-13 added. Checklist only ever reduces what runs; pre-flight fires only under the preset. + +*** 2026-07-02 Thu @ 01:24:50 -0400 Phase 5 landed — per-task JSONL metrics log +Metrics section written into work-the-backlog.org: one record per task at outcome time, appended to the project's .ai/metrics/work-the-backlog.jsonl (git-tracked, append-only, dir+file created on first append). Full field table per the spec (ts, run_id, project, caller, task, outcome, defer_reason, upfront_decision, wall_clock_s, commit_sha, review_findings), outcome slugs mapped to the prose vocabulary, commit_sha flagged as the corrections-signal key (comma-separated when a task decomposed into several commits). Added the sixth outcome the spec's readiness section demanded but the enum missed: failed (tree left working, surfaced, run continues) — wired into the Outcomes vocabulary and loop step 4. A failed append warns in the run summary but never blocks, reorders, or aborts execution. + +*** 2026-07-02 Thu @ 01:27:43 -0400 Phase 6 landed — synthesis step to org-roam +Synthesis section written into work-the-backlog.org (trigger "synthesize backlog metrics", INDEX row added): discover the JSONL union across project roots, classify each project per knowledge-base.md's denylist before reading, exclude work/unknown projects with the refusal contract, compute per-run rollups + trends, compute the corrections signal (later revert/fix commit touching the same files within ~14 days — a flag for human review, not a conviction), write one :agent:metrics: KB node under ~/org/roam/agents/ with [[id:...]] links to prior synthesis nodes, pull-before/commit-push-after. Read-only over the logs plus the single KB write; never mutates JSONL, todo.org, or any tree. + +*** 2026-07-02 Thu @ 05:26:07 -0400 Live trial passed — first speedrun ran 3/3, every loop part exercised +Craig named the ordered set (id-link conversion, host-identity guard, template-sync policy) and said it was the validation run. Pre-flight Q&A fired once (two questions, both answered, answers stamped as dated lines before the run); each task landed as its own reviewed commit under the waiver (78bbaae, b6a977c, ed75d3c); metrics JSONL carries one record per task (run c726f526); the end-of-set page arrived via notify --persist. Nothing needed a VERIFY this run (all three cleared the checklist). Craig's read: granted :LOOP_MAY_COMMIT: on the strength of the run. + +*** 2026-07-02 Thu @ 05:26:07 -0400 Flipped the spec DOING → IMPLEMENTED +All six phases built and the live trial validated. Keyword, dated history line, and Metadata mirror all flipped per the transition-ownership table. +** DONE [#B] inbox-send filename collision silently overwrote a message :bug:solo: +CLOSED: [2026-07-02 Thu] +:PROPERTIES: +:CREATED: [2026-07-02 Thu] +:END: +From archsetup (2026-07-02 0543, found in the wild): two --text sends in the same minute whose text starts with the same phrase derive identical filenames, and the second silently overwrites the first — archsetup lost a message at 05:42 and had to resend. Severity data-loss x rare-edge = P2 = [#B]. + +Resolution 2026-07-02 (auto-inbox-zero loop, standing yes): uniquify() guard in inbox-send.py — an existing target gets a -2/-3/... stem suffix, both send_text and send_file paths, extension preserved. Four red-first tests reproduce the loss (module-level with a fixed timestamp so the same-minute collision is deterministic, plus a CLI loss-proof check); 30/30 green. +** DONE [#C] page-me notify styling — all-red too alarming :bug:solo: +CLOSED: [2026-07-02 Thu] +:PROPERTIES: +:CREATED: [2026-07-02 Thu] +:END: +From Craig via the roam inbox (2026-07-02, routed by archsetup): the page notify's all-red styling "makes me feel like somehow the system is about to crash" — should be a persistent info-level notification. + +Resolution 2026-07-02 (auto-inbox-zero loop, standing yes): pages now use notify info --persist instead of notify alarm — page-me.org (all examples + prose, with Craig's verdict recorded) and work-the-backlog.org's end-of-set page. status-check's success/fail types untouched (job outcomes, not pages). +** DONE [#C] Template sync with gitignored-only local changes :feature: +CLOSED: [2026-07-02 Thu] +From Craig via the roam inbox (2026-07-02, routed by archsetup): downstream projects should still pull template updates when their local changes sit entirely in gitignored files or directories — an inbox drop or a file left to read doesn't affect the templates, yet it currently holds the sync back and projects fall behind. When worked: verify how the sync gate actually detects dirtiness today, then let gitignored-only changes pass it. + +2026-07-02 Thu @ 05:09:58 -0400 — Craig (speedrun pre-flight): policy + audit. Scope read found startup's git gates already ignore untracked/ignored files; state the policy in startup.org and audit every dirty-check in the synced workflows to match (monitor-inbox's bare porcelain check is the known offender; tracked-modification blocking stays). + +Resolution 2026-07-02: template-freshness policy stated in startup.org Phase A.0 (dirty = tracked modifications only; untracked/gitignored never block pulls, ffs, or monitoring gates; the rsync WIP-guard named as the one deliberate exception — it holds back rulesets' own outbound WIP). Full audit of dirty-checks across synced workflows: startup's two git gates already complied; inbox.org monitor mode was the one offender — its precondition now uses --untracked-files=no with the explicit-staging rationale, and its close-out sweeps tracked changes only. triage-intake auto mode borrows monitor's gates, so it inherits the fix by reference. +** DONE [#B] Wrap-up inbox/transcript routing to destination projects :feature:spec: +CLOSED: [2026-07-04 Sat] +:PROPERTIES: +:CREATED: [2026-06-13 Sat] +:LAST_REVIEWED: 2026-06-24 +:END: +Optional wrap-up step that surfaces filed keepers belonging to another project, recommends a destination, and routes each to that project's =inbox/= via =inbox-send= (the destination's own =process-inbox= files it; transcript filing deferred to vNext). Spec: [[id:00b47414-2213-4a99-be35-48ceb266fc08][wrapup-routing-spec.org]] — Ready, [9/9] decisions. Source proposal: [[file:docs/design/2026-06-13-wrapup-inbox-transcript-routing-proposal.org]]. + +*** 2026-06-21 Sun @ 02:06:37 -0400 Spec-review + spec-response complete — Ready +Craig's review challenge reshaped the design from a direct cross-repo =todo.org= move to =inbox-send= delivery into the destination's inbox (safer: reuses the sanctioned cross-project path, gets provenance + per-project filing for free, degrades gracefully where a destination has an =inbox/= but no =todo.org=). D2/D3 superseded; D7 (inbox-send delivery), D8 (=:ROUTE_CANDIDATE:= marker at file time), D9 (local source removal + reject-flow recovery) added. Spec-review file consumed and deleted. Implementation-task breakdown filed below (spec-response Phase 6). + +*** 2026-06-24 Wed @ 00:21:20 -0400 Reconcile — marker sub-task repointed at inbox.org +The 2026-06-23 inbox consolidation (24ca58d) merged =process-inbox= + =monitor-inbox= + =inbox-zero= into one =inbox.org= engine (process/monitor/roam modes) and deleted the three old files. The =:ROUTE_CANDIDATE:= marker sub-task targeted =process-inbox.org='s Phase D — repointed it to =inbox.org= process mode (core §3 "File as TODO"). No build has started, so this is a target-rename only; the spec design is unaffected. + +*** 2026-06-28 Sun @ 13:02:42 -0400 Built the recommendation engine + destination discovery +Added =.ai/scripts/route_recommend.py= (canonical + mirror): pure =recommend(item, projects) → (destination, confidence)= with strong (name/path literal, word-boundary matched, dot-stripped alias aware), weak (distinctive name-token overlap), and none tiers; a multi-way top-tier tie downgrades to weak with a deterministic pick (most overlap, then alphabetical); empty list → none. The CLI (=--item=, =--exclude=) reuses =inbox-send.py='s =discover_projects= via importlib so the candidate set matches inbox-send's project universe. 13 tests (the five spec'd cases + boundary/path/strong-beats-weak + 3 sandboxed CLI integration tests), full =make test= green. Covers spec Phases 1 + 3. Next sub-tasks (=:ROUTE_CANDIDATE:= marker, wrap-up router) call this engine. + +*** 2026-07-02 Thu @ 00:36:12 -0400 Phases 2 + 4 + test surface landed — marker, router, route-batch helper +inbox.org's "File as TODO" disposition now runs route_recommend on each keeper and stamps =:ROUTE_CANDIDATE: <destination>= on strong/weak matches (none stamps nothing; local keepers stay unstamped) — spec Phase 2 / D8. wrap-it-up.org Step 3 gained the optional router directly after the inbox sanity check, with the gate-vs-optional split named in the prose: surface the batch (task / destination / delivery mode / confidence, weak visibly labeled), go/skip, empty set = zero interaction — spec Phase 4 / D7 / D9. The go path is mechanical: new =.ai/scripts/route-batch= (--list read-only, --go extracts the subtree minus the marker with children riding along and headings promoted, delivers via inbox-send for provenance, removes the local subtree only after a successful send; a failed send leaves the task in place and exits non-zero). Test surface: engine unit tests existed (13); route-batch adds a 9-test bats suite (list/backlog-exclusion, empty-set silence, list-modifies-nothing = skip semantics, delivery + provenance + children, local-task survival, drawer-minus-marker, inbox-without-todo.org delivery, empty go, failed-send recovery). cross-project.md notes the router as a sanctioned cross-project write path. make test green, sync clean. + +*** 2026-07-04 Sat @ 11:49:59 -0500 Flipped the spec to IMPLEMENTED; parent closed +The routing build shipped green: route_recommend.py (destination discovery + recommendation, 13 unit tests), the =:ROUTE_CANDIDATE:= marker in inbox process mode, the wrap-it-up router sub-step, and route-batch (9 bats tests). Spec [[id:00b47414-2213-4a99-be35-48ceb266fc08][wrapup-routing]] flipped DOING → IMPLEMENTED. Manual end-to-end validation and the transcript vNext promoted to their own tasks. +** DONE [#C] Check that memories are sync'd across machines via git :spec: +CLOSED: [2026-07-04 Sat] +:PROPERTIES: +:LAST_REVIEWED: 2026-06-28 +:END: +v1 implemented end-to-end 2026-06-10 (Phases 0-4, all shipped + pushed) and the agent-KB spec is IMPLEMENTED. Both daily drivers (velox + ratio) carry the =roam.git= clone + sync-timer. Cross-machine sync verified as a full round-trip 2026-07-04: velox created a probe node 2026-07-01 and ratio held it; ratio then deleted it and velox pulled the deletion. Both clones sit at the same HEAD with zero sync-conflict files and both roam-sync timers active. The work/unknown-project write-refusal checks need live sessions in those projects and are tracked as a standalone manual-validation task below. +*** 2026-05-14 Thu @ 19:14:11 -0500 Investigate current memory storage + +Memory files live at +[[file:/home/cjennings/.claude/projects/-home-cjennings-code-rulesets/memory/][~/.claude/projects/-home-cjennings-code-rulesets/memory/]] +— four files including =MEMORY.md= and three individual entries +(=feedback_never_guess.md=, =project_ai_scripts_canonical_source.md=, +=reference_pdftools_venv.md=). The directory is a plain unmanaged dir +(no symlink, no enclosing git checkout). Neither +[[file:/home/cjennings/.claude/][~/.claude/]] itself nor any subtree +containing the project-memory dirs is tracked in +[[file:/home/cjennings/code/archsetup/][archsetup]] or +[[file:/home/cjennings/code/rulesets/][rulesets]]. Without a symlink +into a stowed or tracked location, memory files don't survive a new +machine setup or a dotfiles restore. + +Proposed setup: stow =~/.claude/projects= → +=archsetup/dotfiles/common/.claude/projects/= (path doesn't exist yet +— it's the target location pending VERIFY). +Create the destination in archsetup, move existing per-project +=projects/<encoded-cwd>/memory/= dirs there, run =stow= to link, then +commit + push archsetup. After that, every machine running =stow= +picks up the same memory tree. + +*** 2026-05-23 Sat @ 16:12:48 -0500 Decided: dedicated private repo, not stow +Worked through dotfiles → rulesets → dedicated repo. Dropped stow/dotfiles (machine config, wrong cadence) and rulesets (it's pulled first in every session, so memory edits would dirty its tree and skip the startup =git pull --ff-only=). Chose a dedicated private repo on cjennings.net: storage is unified there while recall stays per-project (the encoded-cwd subdirs), since pooling recall would hurt relevance and risk work-private facts surfacing in personal-project artifacts. + +*** 2026-05-23 Sat @ 16:12:48 -0500 Shipped: claude-memory.git + folded symlinks +Created bare =git@cjennings.net:claude-memory.git=, cloned to =~/.claude-memory= (later deleted in the reversal below), moved all 7 per-project =memory/= dirs in (54 files; work has 40) and replaced each live =~/.claude/projects/<enc>/memory= with a folded dir-symlink so new memory lands in the clone and a push syncs it. Added =link-claude-memory.sh= (idempotent — recreates the symlinks on a new machine after clone) + README. Private repo, never GitHub (carries work/DeepSat memory). Initial import pushed (=f496370=). + +*** 2026-05-24 Sun @ 01:53:35 -0500 Reversed the migration — back to unmanaged per-project memory +Cancelled the follow-up brainstorm and undid the dedicated-repo migration at Craig's call. Moved all 7 memory dirs back to =~/.claude/projects/<enc>/memory/= (content preserved), deleted the =~/.claude-memory= clone, and deleted the bare =claude-memory.git= on the server. Memory is back to its original at-risk state, so the task reopens at [#C] pending a direction. The brainstorm landed on a two-tier idea for whenever this resumes: promote general lessons into a rulesets-tracked file symlinked into =~/.claude/rules/= (loaded into every project natively, one repo), and keep project-specific memory under each project's own =.ai/memory/= (committed where =.ai/= is tracked, at-risk where it's gitignored). Not implemented. + +*** 2026-06-05 Fri @ 05:57:35 -0500 Pivot: adopt the existing org-roam KB as the shared agent substrate +Pressure-tested the two-tier idea, then Craig redirected: a shared org-roam knowledge base any project can read and write makes this simpler. Ground truth verified: =~/sync/org/roam/= already exists (484 org files, curated since 2023, Syncthing-synced, not git). So cross-machine sync is already solved, and the task stops being "build a memory-sync system" and becomes "point agents at the KB that already syncs." The dedicated-repo and two-tier approaches are both superseded for the storage+sync half. + +Wrote a one-page spec: [[id:08a5ec99-9e1e-40e4-8241-e8a41e9de49f][agent-knowledge-base-spec.org]] (originally docs/design/2026-06-05-org-roam-knowledge-base-spec.org; superseded by the 2026-06-10 spec-create rewrite at the new path). Five decisions, mechanics recommended: (1) KB is a queried substrate accessed as files (ripgrep + follow =[[id:]]= by grep), not via the org-roam package; (2) capture in harness memory, promote durable facts into the KB (same cadence as the pattern catalog) — resolves the at-risk problem since the valuable knowledge moves to the synced KB; (3) a =claude-rules/knowledge-base.md= pointer rule carries path/query/write-schema/boundary; (4) write schema = roam-valid node + =:agent:= filetag so agent notes stay distinguishable and index on the next =org-roam-db-sync=. The rules layer (=claude-rules/=, =CLAUDE.md=) is untouched — the KB replaces the memory tier, not the rules tier. + +*** 2026-06-10 Wed @ 14:29:20 -0500 Spec ratified — write boundary is option C; rewritten to spec-create format +Craig answered via cj annotations in the spec (2026-06-10): DECISION 5 is option C (read-shared, write-scoped — work agents never write the KB). Syncthing does replicate ~/sync/ to a work machine and Craig is fine with how C handles it. Node granularity: per-fact nodes. Write review: agent writes land freely in the KB only — explicitly not permission to post to email, Linear, or any public channel without review and consent. The spec was rewritten into the spec-create format at [[id:08a5ec99-9e1e-40e4-8241-e8a41e9de49f][agent-knowledge-base-spec.org]] (old draft removed). Implementation explicitly held pending Craig's go-ahead; one decision still open (D7, next VERIFY). + +*** 2026-06-10 Wed @ 14:35:40 -0500 Spec review — not ready +Review written at docs/agent-knowledge-base-spec-review.org (deleted on disposition completion; content summarized in the spec's Review dispositions). Rubric: =Not ready=. Blockers: resolve D7 (keep vs retire harness memory) and define the executable personal/work/unknown write-boundary classifier plus work-side write/refusal destination. Medium notes: use concrete ripgrep commands that exclude =*.sync-conflict-*= files, and define seed-node approval/rollback. + +*** 2026-06-10 Wed @ 14:44:00 -0500 D7 resolved — keep harness memory as the capture layer +Craig ratified "keep" in chat (2026-06-10). Harness memory stays the ephemeral, auto-recalled capture layer; the KB holds promoted durable facts; Phase 3's wrap-up promotion cadence is mandatory. Spec D7 flipped to accepted; D2 stands as written. + +*** 2026-06-10 Wed @ 14:44:00 -0500 Project classification defined — work-root denylist, unknown refuses +Resolved in the spec-response pass: =knowledge-base.md= carries an explicit work-root denylist (initially =~/projects/work=) as the source of truth. Personal = under a known project parent (=~/code/=, =~/projects/=, =~/.emacs.d=) and not denylisted → KB writes allowed. Work or unknown → no KB write; the agent reports the refusal with a one-line redacted summary of the fact. v1 adds no new work-side store — work projects keep their existing project-tree conventions. See the "Project classification and write routing" section of [[id:08a5ec99-9e1e-40e4-8241-e8a41e9de49f][the spec]]. Denylist completeness is the one open caveat (next VERIFY). + +*** 2026-06-10 Wed @ 14:44:00 -0500 Codex review incorporated — spec ready with caveats +Spec-response pass processed the 2026-06-10 Codex review with D7 = keep as a pre-agreed input. Both blockers cleared (D7 accepted; classification/write-routing section added). Mediums accepted: canonical rg commands with conflict-file exclusion, Phase 2 seed-node approval/rollback mechanics, Makefile no-change note, Testing/Verification section. Three recommendations modified, none rejected — see the spec's Review dispositions. Review file deleted per the workflow. Rubric: ready with caveats (denylist confirmation). Implementation tasks broken out below; implementation itself awaits Craig's go. + +*** 2026-06-10 Wed @ 17:29:37 -0500 Work-root denylist confirmed — ~/projects/work only +Craig confirmed (2026-06-10, in chat): the denylist is just =~/projects/work=. Archangel is not work-scoped. The spec's one caveat clears — status now ready. Phase 1 is unblocked, but implementation still awaits Craig's explicit go. + +*** 2026-06-10 Wed @ 17:57:08 -0500 Spec amended — D8 git transport + migration/metrics/docs/maintenance folds +Craig's five design questions answered and folded into the spec, and D8 ratified (Shape A): the KB moves out of the =~/sync/org= Syncthing share into its own git repo on cjennings.net, with an =agents/= subdirectory for agent writes, a systemd auto-sync timer for Craig's edits, opt-in-by-clone replication (work machine doesn't clone), and the phone staying on the on-demand =~/sync/phone= pattern. Folded in: inclusion criteria + a Phase 1.5 guided memory sweep, a Success metrics section with a 30-day checkpoint, the seed node redefined as the KB's own documentation, and Phase 4 maintenance automation. Phases renumbered 0-4; tasks below updated. Implementation still held. + +*** 2026-06-10 Wed @ 18:21:33 -0500 Phase 0 done — roam migrated to git +Backed up (~/roam-backup-2026-06-10.tar.gz), copied to =~/org/roam=, 63 conflict files deleted (424 org files), git repo with origin =git@cjennings.net:roam.git= (initial commit 515693d), old location replaced with a transition symlink. Emacs =roam-dir= updated in user-constants.el + live-reloaded (db rebuilt, 416 nodes); handoff to .emacs.d for the commit. =roam-sync.sh= (6 bats green) on a 15-min systemd user timer, installed + enabled + round-trip verified. Old-path references repointed (protocols task-list pointer, journal workflow, notes template). archsetup handoff covers dotfiles adoption + other-machine clones. rulesets commit fcf554a. + +*** 2026-06-10 Wed @ 18:21:33 -0500 Phase 1 done — knowledge-base.md rule live +=claude-rules/knowledge-base.md= written (path, git discipline, query commands, agents/ write schema, denylist + refusal contract, inclusion criteria, capture-then-promote). =make install= linked it machine-wide; verified the link, a known-note query, and conflict-glob exclusion with a planted file. Commit d071f1f. + +*** 2026-06-10 Wed @ 18:21:33 -0500 Phase 1.5 done — rulesets swept, 10 projects broadcast +Rulesets' 6 memories classified: 3 promoted as =agents/= nodes (notify-attention pattern, pdftools venv, gpg-agent SSH TTL trap), 2 kept local (rule-encoded in verification.md / interaction.md), 1 kept + de-staled (ai-scripts-canonical updated for the claude-templates subtree fold). Sweep handoff broadcast to the 10 other memory-bearing projects (archsetup, org-drill, pearl, .emacs.d, elibrary, finances, health, home, jr-estate, kit); work skipped by the boundary; the orphaned =linear-emacs= memory dir (project retired, likely pearl's predecessor) noted for Craig. + +*** 2026-06-10 Wed @ 18:21:33 -0500 Phase 2 done — seed/doc node written and indexed +=agents/20260610181640-how-the-agent-knowledge-base-works.org= written: the KB's user-facing guide (what agents do, how it syncs, finding/pruning agent content, the rule pointer). Index verified programmatically: =org-roam-node-from-title-or-alias= resolves it with tags (agent reference); node count 416 → 420. Craig's visual check remains in the manual-testing child. + +*** 2026-06-10 Wed @ 18:21:33 -0500 Phase 3 done — wrap-up promotes + records the KB receipt +wrap-it-up.org Step 1 gains the promotion check (inclusion-criteria bar) and the mandatory "KB: promoted N / consulted yes-no" Summary line; validation checklist enforces it. Mirror synced, integrity OK (44), parse OK. Commit 242b95e. + +*** 2026-06-11 Thu @ 19:26:26 -0500 .emacs.d memory sweep complete (first broadcast response) +First of the 10 broadcast projects to report Phase 1.5 done (handoff 18:23). Inventory 7: promoted 3 to KB (no-make-frame-in-live-daemon, proton-bridge-headless-cert-mismatch, open-images-with-imv — roam commit a915760), kept 3 local at Craig's call (commit-flow-no-approval-gate per-project-scoped; two theme-scoped ones possibly superseded by the palette-columns spec), deleted 1 (superseded by canonical interaction.md rule). 9 projects' sweeps outstanding. + +*** 2026-06-12 Fri @ 02:25:12 -0500 Five more sweeps complete via the home folds +Overnight handoffs from home closed five more broadcast targets, each swept at fold-time triage with Craig's approval: jr-estate 2 promoted (forms name-with-number, PDF-editing tooling split; roam 45d8e6c) / 3 kept with area attribution / 2 deleted as rule-encoded or duplicate; finances 0/1/0 (rosalea-daly contact fact kept local); elibrary 0/0/2, health 0/0/1, kit 1/0/2 (hand-prep-items-to-work-inbox promoted into home's memory; the rest duplicated rules or home memories). Nothing from these five met the KB bar that wasn't already encoded. All folded projects' session archives merged area-prefixed into home's .ai/sessions/, so session-harvest's first run sees them. Home covers its own and remaining areas' sweeps through ongoing discipline; still pending from the broadcast: archsetup and work. + +*** 2026-07-04 Sat @ 11:52:00 -0500 Manual validation — checks 1 + 4 verified; refusal checks split out +Check 1 (seed node in org-roam + rg inventory) verified on velox 2026-07-01 (55 =:agent:= nodes matched the live org-roam DB). Check 4 (cross-machine sync) verified as a full round-trip 2026-07-04: velox pushed a probe 2026-07-01, ratio held it, ratio deleted it, velox pulled the deletion; both clones at the same HEAD, zero sync-conflict files, both timers active. Checks 2 + 3 (work / unknown-project write refusal) need live sessions in those projects — promoted to a standalone manual-validation task. + +*** 2026-06-10 Wed @ 18:21:33 -0500 Phase 4 done — monthly hygiene automation live +=scripts/kb-hygiene.sh= (6 bats green, shellcheck clean, read-only by design) inventories =:agent:= nodes, flags orphans / duplicate titles / conflict files, and writes an org report into the rulesets inbox; =roam-hygiene.timer= (monthly, Persistent) installed + enabled. Live run against the real KB verified (4 agent nodes, 428 files, 0 conflicts). Conditional vNext stays in the spec's scope tiers: a =/promote= command if the wrap-up prompt proves insufficient, an =:agent:inbox:= staging tag if free writes prove too noisy. Commit b014095. + +*** 2026-06-30 Tue @ 13:53:34 -0400 ratio roam clone + sync-timer confirmed (cross-machine half done) +Verified ratio over tailscale ssh: =~/org/roam= is a clone of =git@cjennings.net:roam.git= (HEAD auto-synced 13:11 today), and =roam-sync.timer= is enabled and actively firing (last run 5 min prior, next in 10). Both unit files present. velox was already confirmed, so the one-time clone+timer setup is now done on both daily drivers — the (b) half of this VERIFY's remaining work. Only the manual-validation child (work/unknown-project refusal checks needing Craig's eyes) is left before DONE. Cleared the matching "Current open instance" line in =daily-drivers.md=. + +*** 2026-07-01 Wed @ 21:52:15 -0400 Manual-validation checks 1 + 4 (velox half) verified; ratio unreachable +Check 1 (seed node in org-roam + rg inventory): 55 =:agent:= nodes found by the rg inventory AND 55 nodes under =agents/= indexed in the live org-roam DB (emacsclient query) — match. Check 4 (cross-machine edit): created a temporary probe node =agents/20260701214910-kb-sync-validation-probe.org=, triggered =roam-sync= — committed and pushed to origin within seconds (f0252bb), zero =sync-conflict= files. The ratio half could NOT be verified tonight: =tailscale ping ratio= pongs via DERP, but ssh to 100.71.182.1:22 times out (machine likely suspended). Probe left in place; when ratio is back, confirm with: ssh cjennings@100.71.182.1 'ls ~/org/roam/agents/ | grep kb-sync-validation-probe' — then delete the probe node. Checks 2 + 3 (work/unknown-project refusal) still need live sessions in those projects, and "work machine has no KB clone" needs the work machine named + checked. +** DONE [#C] Spec storage location + lifecycle-status convention :spec: +CLOSED: [2026-07-04 Sat] +:PROPERTIES: +:CREATED: [2026-06-15 Mon] +:LAST_REVIEWED: 2026-06-24 +:SPEC_ID: 80b0787b-4a60-4c82-8a16-b383d3e3c8f2 +:END: +Two coupled documentation conventions for rulesets to adopt, surfaced by .emacs.d while triaging ~28 design docs. Both land in =spec-create= ([[file:.ai/workflows/spec-create.org]]) and likely a new =docs-lifecycle= rule under =claude-rules/=. Source proposal: [[file:docs/design/2026-06-15-spec-storage-lifecycle-proposal.org]] (.emacs.d handoff 2026-06-15). + +The two conventions: +- *Location split* — formal specs live in =docs/specs/=; =docs/design/= keeps working notes, brainstorms, inventories, reviews. A spec is a doc proposing a buildable change with a Decisions section and phases; everything else is a note. +- *Glanceable lifecycle status* — a spec's state (draft / doing / implemented / superseded / cancelled) is visible without opening the file, plus an authoritative in-file record. + +We handle the task in priority order. Mechanism decided 2026-06-28; migrates into the spec when built. + +*** Decisions (settled 2026-06-28 — migrate into the spec when built) +1. *Location split — adopt.* =docs/specs/= for formal specs, =docs/design/= for notes (brainstorms, inventories, reviews). A spec is a doc proposing a buildable change with a Decisions section and phases; everything else is a note. Document in spec-create and the docs-lifecycle rule. +2. *Status mechanism — org-keyword authoritative.* The spec's =#+TODO:= state on its top heading is authoritative (specs already carry =#+TODO: TODO | DONE SUPERSEDED CANCELLED=), mirrored in a =Status= field in the Metadata table. Drop the filename suffix entirely — it's redundant with the Status field and adds rename churn across a cross-linked, template-synced doc set. (Craig 2026-06-28, choosing org-keyword over his earlier filename-suffix lean.) +3. *Link safety — adopt =org-id= ([[id:...]]) for cross-doc spec links.* Decouples link stability from the status mechanism; good hygiene regardless. +4. *Generalize.* Capture the shape (lifecycle state authoritative-in-artifact, formal-vs-notes split, rename-safe links) as a general =docs-lifecycle= convention in =claude-rules/=, with spec-create as the first instance. +5. *Retrofit existing files across ALL projects* (Craig 2026-06-28). The convention is worthless if legacy docs stay misfiled — every project's existing =docs/design/= pile (the ~28 in .emacs.d that surfaced this) must be sorted: formal specs move to =docs/specs/=, notes stay in =docs/design/=, inbound =file:= links updated. This is a one-time per-project migration that template sync can't perform, so the spec must design the reach mechanism. Proposed shape: a synced classify-and-move helper under =.ai/scripts/= (heuristic: a doc with a Decisions section + phases/Metadata is a spec) that proposes moves for confirmation and relinks, plus a startup nudge gated on a per-project =:LAST_SPEC_SORT:= marker so each project runs it once. Classification is a judgment call — the helper proposes, a human confirms. + +Follow-up once built: update spec-create to emit into =docs/specs/= with the org-keyword status; write the =docs-lifecycle= rule; ship the retrofit helper + startup nudge; retrofit rulesets' own =docs/design/= first as the pilot; send a note if .emacs.d should pilot before generalizing. + +*** 2026-07-01 Wed @ 22:13:00 -0400 Spec drafted — first resident of docs/specs/, awaiting review +Wrote [[id:80b0787b-4a60-4c82-8a16-b383d3e3c8f2][the spec]] from the five settled decisions, dogfooding its own conventions: it lives in the new =docs/specs/=, opens with the =* DRAFT Docs lifecycle= status heading (org keyword authoritative, =:ID:= for id-links, dated history in the body), and drops the status filename suffix. It pins the one mechanism the decisions left open — where the keyword lives: a prepended top-level status heading with vocabulary =DRAFT READY DOING | IMPLEMENTED SUPERSEDED CANCELLED=, additive and retrofittable, giving both the one-line =rg= board and free org-agenda scanning. Four build phases: rule + template updates → =spec-sort= helper (classify/confirm/move/relink, bats) → rulesets pilot (41 design files, 3 spec-spine candidates, 2 stray root specs) → startup nudge gated on =:LAST_SPEC_SORT:= + .emacs.d note. Status DRAFT until Craig's review flips it READY. + +*** 2026-07-01 Wed @ 22:22:34 -0400 Codex spec-review complete — Not ready +Review findings live in [[id:cc77a7f6-e4c3-488a-ac3b-e739420a5c2b][the spec]]. Four blockers before implementation: the proposed lifecycle =#+TODO:= line drops the =TODO=/=DONE= states needed by Decisions and Review findings cookies; =spec-sort= needs an exact relink/unsupported-residue contract; the sort marker/startup nudge must name the actual =.ai/notes.org= state surface and detection flow; and the stricter =docs/specs/= review precondition must not strand legacy specs before retrofit. + +*** 2026-07-01 Wed @ 22:30:06 -0400 Second review merged + responder pass — all nine findings fixed +A fresh-context Claude reviewer independently rated the draft Not ready, converging on Codex's keyword-vocabulary blocker (adding the cross-sequence uniqueness wrinkle) and contributing five unique findings — the biggest: nobody owned the DOING→IMPLEMENTED flip, the exact mechanism whose failure produced this spec. Craig approved fixing all nine. Fixed in place: two-sequence collision-free keyword header (dogfooded in the spec's own header; org now computes the [5/5] and [9/9] cookies — verified in batch), transition-ownership table incl. spec-response's mandatory flip-to-IMPLEMENTED task + task-audit safety net, single classification predicate (Decisions AND Implementation phases), the -spec.org rename step, the full relink data-safety contract (rewritten roots / report-only surfaces / dry-run default / residue-grep gate), the =.ai/notes.org= marker + Phase A probe + Phase C nudge contract, the legacy-location compatibility rule, the org-id Emacs-resolution prerequisite for .emacs.d, and the three-line transition definition. Ledger + per-finding responses in the spec's Review findings section. Status stays DRAFT pending Craig's READY flip. + +*** 2026-07-01 Wed @ 22:41:33 -0400 Codex spec-review rerun — Not ready +Fresh review after the response/READY flip added five new blocking findings in [[id:cc77a7f6-e4c3-488a-ac3b-e739420a5c2b][the spec]] and demoted the spec back to DRAFT. Remaining blockers: shared helper/workflow edits must name the canonical =claude-templates/.ai/= + mirror sync contract; task-audit needs an explicit spec-to-task binding before it can police =DOING= specs; =spec-sort --apply= needs a failure-safe/rollback contract; the org-id Emacs prerequisite must be executable before link conversion; and lifecycle status confirmation must be evidence-based so the retrofit does not encode stale reality. + +*** 2026-07-01 Wed @ 22:46:52 -0400 Second responder pass — all fourteen findings closed +Fixed Codex's five re-review findings: the canonical-placement contract now opens the retrofit section (helper + tests + workflow edits land in claude-templates first, sync-check --fix propagates, sync-check-clean is an acceptance criterion); spec-response stamps a =:SPEC_ID:= property on the build parent, and task-audit's query checks that parent's keyword — which dissolves the flip-task chicken-and-egg; =--apply= got the fail-safe contract (clean-tree preflight, validate-then-write from a recorded plan, named recovery recipe); id-link conversion is staged (pilot rewrites =file:= links only; =id:= conversion is a follow-up gated on the concrete .emacs.d id-index mechanism — Craig picked this fork); and status confirmation is evidence-based (evidence panel, conservative non-terminal default, terminal states need a stated reason). Also de-cookified bracket tokens in prose that org's cookie updater would mangle. Status stays DRAFT; the READY flip belongs to the reviewers this round — verify pass dispatched. + +*** 2026-07-01 Wed @ 23:22:50 -0400 Codex spec-review rerun — Ready +Codex re-read the revised [[id:80b0787b-4a60-4c82-8a16-b383d3e3c8f2][docs lifecycle spec]] after the second responder pass. All fourteen findings are closed, decisions remain [5/5], and the remaining implementation contracts are concrete enough to build and test. Status flipped to READY in the spec; implementation can proceed. + +*** 2026-07-01 Wed @ 23:34:15 -0400 Decomposed into build tasks; spec flipped READY → DOING +spec-response Phase 6 run: this parent now carries the =:SPEC_ID:= binding (the spec's status-heading UUID), the phase tasks below track the build, and the spec's status heading is DOING. Completeness pass done: all ten acceptance criteria have homes across the phase tasks; vNext (org-agenda view) was already filed as the [#D] task below. + +*** 2026-07-01 Wed @ 23:39:10 -0400 Phase 1 landed — docs-lifecycle rule + four spec-workflow updates +claude-rules/docs-lifecycle.md written and linked machine-wide (make install). Canonical-side updates: spec-create Phase 5 + template (docs/specs/ location, two-sequence keyword header, DRAFT status heading with :ID:, transition mechanics), spec-review (location expectation with the legacy compatibility rule keyed on :LAST_SPEC_SORT:, plus the DRAFT→READY flip — and the demote-back-to-DRAFT path a failed re-review takes), spec-response Phase 6 (owns READY→DOING, stamps :SPEC_ID: on the build parent, always emits the flip-to-IMPLEMENTED task), task-audit Phase B (the :SPEC_ID: reconcile query, checking the parent's keyword rather than counting children). Mirror synced; make test green end to end. + +*** 2026-07-01 Wed @ 23:57:44 -0400 Phase 2 landed — spec-sort helper + 30-test bats suite +Built claude-templates/.ai/scripts/spec-sort (Python, TDD — the 30-test bats suite written red-first in claude-templates/.ai/scripts/tests/spec-sort.bats) covering the full retrofit contract: spine classification with the -spec.org-name-without-spine anomaly case, evidence panel (Status field, cookies, linking todo.org task, dated history, artifact existence) with conservative non-terminal proposals, per-candidate --confirm/--skip gate with --reason required on terminal keywords, clean-worktree preflight (--allow-dirty prints what recovery loses), validate-then-write from a recorded plan file, relink across the rewritten roots (inbound AND the moved doc's own outbound relative links) with report-only for sessions + synced templates (naming the canonical claude-templates file), bare-path mentions blocking until --acknowledge-bare, named recovery on injected mid-apply failure, post-apply residue gate, idempotent :LAST_SPEC_SORT: stamp. Real-data dry run against rulesets' pile matched predictions: 5 candidates, 4 anomalies, 30 notes, 0 bare, 10 report-only (incl. the startup.org synced-template case Codex flagged). make test green; sync-check clean. + +*** 2026-07-02 Thu @ 00:18:28 -0400 Phase 3 pilot ran — rulesets' pile sorted, board live +Craig confirmed all five proposed keywords as-is plus the IMPLEMENTED reason; spec-sort --apply moved the five specs to docs/specs/ (agent-knowledge-base IMPLEMENTED, inbox-workflow-consolidation READY, autonomous-batch-execution READY, encourage-kb-contribution READY, wrapup-routing DOING — joining the docs-lifecycle spec's DOING on the board), rewrote 12 todo.org links plus the moved specs' own outbound links, and stamped :LAST_SPEC_SORT: 2026-07-02. Acceptance verified: status board matches reality, all re-homed specs carry -spec.org, residue zero in the rewritten roots (one acknowledged self bare mention rode along inside inbox-workflow-consolidation-spec), no id: links emitted, make test green. Surfaced and left in place: the four -spec.org-named files in docs/design without a spec spine (generic-agent-runtime, pattern-catalog, daily-prep-template, auto-triage-intake) — notes by predicate, misleading names; rename or leave is a Craig call. Report-only references: 9 frozen session archives + the synced startup.org (canonical edit lands with Phase 4's nudge work). + +*** 2026-07-02 Thu @ 00:23:32 -0400 Phase 4 landed — startup nudge live, .emacs.d notified +Added the spec-sort probe to startup.org Phase A (item 12) and the one-line nudge to Phase C's findings list, canonical-side, mirror synced. One refinement over the spec's sketch: the stray-root check uses find instead of compgen, because compgen is bash-only and zsh aborts on an unmatched glob — the original snippet false-negatived on stray root specs under zsh (spec snippet updated with a note). Fixture-verified in both shells: fires on an unsorted docs/design and on a stray docs/*-spec.org, silent with the marker stamped, silent with no docs at all. Also fixed startup.org's own stale reference to the moved encourage-kb-contribution spec (the pilot's report-only finding). Sent .emacs.d the convention-live note with its ~28-doc pile nudge and the id-index ask (org-id-extra-files enumeration or periodic org-id-update-id-locations, verify by clicking the docs-lifecycle spec's :ID:), asking it to tag the owning task :blocker: since rulesets' id-conversion task waits on it. + +*** 2026-07-02 Thu @ 05:12:33 -0400 Converted spec-target file: links to id: form (rulesets) +All 13 file:docs/specs/ links lived in todo.org (zero in .ai/ or docs/ outside specs). 11 converted straight to [[id:UUID][label]] (bare links labeled with the spec filename); the 2 links carrying a ::*Review findings search target got full fidelity by minting an :ID: on that heading in the docs-lifecycle spec (cc77a7f6-e4c3-488a-ac3b-e739420a5c2b) — the id index scans whole files, so heading-level ids resolve. Residue grep zero; every id verified against its target's :ID:. Gate had cleared earlier tonight via .emacs.d's org-spec-links.el delivery (verified org-id-find on their side); M-x cj/org-id-refresh-spec-locations is the fix if a fresh id doesn't resolve on click. + +*** 2026-07-04 Sat @ 11:46:31 -0500 Flipped the spec to IMPLEMENTED +All four build phases had shipped (docs-lifecycle rule + spec-workflow updates, spec-sort helper + 30-test bats suite, rulesets pilot + status board, startup nudge) plus the conversion of file-style links to id-style links, so the spec's status heading went DOING → IMPLEMENTED with a dated history line and the Metadata mirror, per the transition-ownership table. Parent closed; manual validation promoted to its own task. diff --git a/claude-rules/commits.md b/claude-rules/commits.md index 4509cee..c4eb2cd 100644 --- a/claude-rules/commits.md +++ b/claude-rules/commits.md @@ -19,6 +19,7 @@ Absolutely no AI/LLM/Claude/Anthropic attribution in: - Code comments - Commit trailers - Release notes, changelogs, and any public-facing artifact +- Document author metadata — an org `#+AUTHOR:` line, YAML frontmatter `author:`, a docx or PDF author property, a byline This means: @@ -32,6 +33,34 @@ If a tool, template, or default config inserts attribution, remove it. If settings.json needs it, set `attribution.commit: ""` and `attribution.pr: ""` to suppress the defaults. +### Generated documents carry the human author only + +Every document an agent writes or updates — a daily prep, a session log, a +spec, an explainer, a meeting summary — names the human as its sole author. +`#+AUTHOR: Craig Jennings`, never `Craig Jennings & Claude`, and never an +agent's name standing alone. + +The failure mode is imitation, not intent. No template stamps this line; +agents copy the author header from whatever sits nearby — yesterday's prep, +the workflow file they're reading, the doc next to the one they're writing. +So a single stray `& Claude` propagates through everything generated +afterward, and no amount of fixing individual files stops it. Fix the files +an agent reads, and state the rule here. + +The stakes are highest where the rule is least visible. A private notes repo +tolerates a co-author line; many employers do not, and their policy is that +work product carries employee names alone. An `#+AUTHOR:` line survives +conversion into docx, a published wiki page, or a PDF handed to a customer, +so a header written in a scratch document three months ago can surface inside +a deliverable. Write the header correctly at creation. + +Two things this rule does not ask for. Don't rewrite historical records — +archived session logs and past dated documents stay as they are, because +they're a record of what happened rather than a live artifact. And don't +relabel a document another agent genuinely authored: if Codex wrote it, the +byline stays Codex. The rule removes false co-authorship, not true +authorship. + ## Commit Message Format Commit messages follow the [Conventional Commits](https://www.conventionalcommits.org/) spec. @@ -357,7 +386,7 @@ Pick the shape first. Most reviews are Shape 1. 3. Run `/voice personal` on the file once. The skill walks its full pattern list across every block at the same time. The separators stay intact because they aren't prose. -4. Print the final draft inline in the terminal. Every block, exactly as it'll be posted, with its separator header. State that the skill ran (e.g. "/voice personal — full pattern walk across summary + 3 inline"). Surface any pattern #39 warnings. +4. Print the final draft inline in the terminal. Every block — the summary body AND the full prose of every inline comment — exactly as it'll be posted, with its separator header. Print the inline in full; never describe it in place of printing it ("I'd pair it with one inline on…"). Craig approves the exact words that post under his name, so the exact words must be on screen. State that the skill ran (e.g. "/voice personal — full pattern walk across summary + 3 inline"). Surface any pattern #39 warnings. 5. Ask: approve, request changes, or open in editor. Wait for an explicit answer. Do not open the file in `emacsclient` (or any editor) by default. - **Approve** → continue to step 6. diff --git a/claude-rules/desktop-capture.md b/claude-rules/desktop-capture.md new file mode 100644 index 0000000..0051c4d --- /dev/null +++ b/claude-rules/desktop-capture.md @@ -0,0 +1,57 @@ +# Off-Workspace Windows and Captures + +Applies to: `**/*` (any task that opens a window or takes a screenshot on the user's live desktop) + +Never open a window or take a screenshot on the user's active workspace. When +visual verification needs a real window on the user's live desktop, keep it off +the workspace they're working in. An agent doing its own visual verification +shouldn't hijack the desktop the user is actively using. + +The principle is environment-general: don't commandeer the user's active +workspace for agent-side visual work. The recipe below is the Hyprland/Wayland +implementation; other environments implement the same principle with their own +off-screen mechanism. + +## Captures for your own verification + +Render and grab the window off the user's physical screen, then tear it down. On +Hyprland this is a virtual headless output, verified non-disruptive on ratio +2026-07-06 — the physical monitor stayed on its workspace, focused, throughout: + +```sh +hyprctl output create headless # virtual output on its own workspace +setsid <app> >/tmp/x.log 2>&1 </dev/null & +addr=$(hyprctl -j clients | python3 -c 'import json,sys; print(next((c["address"] for c in json.load(sys.stdin) if c.get("class")=="<CLASS>"), ""))') +hyprctl dispatch movetoworkspacesilent "<ws-on-headless>,address:$addr" # silent = keeps the user's focus +grim -o HEADLESS-<n> /tmp/shot.png # capture the virtual output only +pkill -f '<app>$'; hyprctl output remove HEADLESS-<n> # tear down, restore the display +``` + +Key constraint: `grim` captures a *visible output*, so a window merely parked on +another Hyprland workspace can't be screenshotted — it must render on the +headless (or another real) output. That's why a headless output, not just +"another workspace," is the tool for self-captures. A nested compositor +(weston/cage/sway) is the alternative on non-Hyprland Wayland or when a headless +output isn't available; it needs the compositor installed. + +## Showing the user something + +Open it on a *separate* real workspace and tell them which one, so it never +grabs their active workspace. They switch when ready. Craig's viewer preference +is `imv`; launch it through the compositor (`hyprctl dispatch exec "imv +<files>"`) so it survives the agent's shell rather than a bare `&` job that gets +reaped. + +## Always clean up + +Close the window and remove any headless output afterward. Verify the user's +display is restored: physical monitor back to its workspace, no orphan +processes. + +## Related + +- `verification.md` — this is part of how visual verification is done without + disrupting the user. +- `interaction.md` — the broader "don't disrupt the user's active work" concern. +- `emacs.md` — the screenshot note for Emacs changes uses the same off-screen + capture approach. diff --git a/claude-rules/emacs.md b/claude-rules/emacs.md index ae4f7cb..2c3b729 100644 --- a/claude-rules/emacs.md +++ b/claude-rules/emacs.md @@ -33,3 +33,21 @@ This replaces the quit → relaunch → re-find-and-load-files cycle for most ed The reload caveats above are about pushing changes *into* the daemon. The inverse hazard: a tool that edits a file *on disk* while the daemon has an indirect buffer cloned from it. org-capture works through such a buffer, and a disk write (a hand edit, a `git pull` that fast-forwards the file, a `sed`/Write) reverts the base buffer underneath the capture. The capture is left on stale state, can no longer finalize with `C-c C-c`, and a freshly-typed item can be lost or written back against post-edit content. Orphaned `CAPTURE-*` buffers piling up as Craig retries is the visible symptom. The roam inbox (`~/org/roam/inbox.org`) is the live case — Craig captures into it constantly, and the inbox workflow's roam mode (Phase D) edits it. Before a disk write to a file the daemon may be capturing into, check first: `.ai/scripts/capture-guard <file>` exits non-zero (and names the buffer) when a live capture is cloned from `<file>`, and exits 0 — safe — when there's no capture or no reachable Emacs. Same principle as the reload rule, one layer out: leave the daemon's live buffers authoritative rather than yanking the file from under them. + +## SVG Rendering for Emacs App UIs + +Consider SVG rendering (`svg.el`) as the default for any Emacs app UI with real visual structure — boards, meters, gauges, status panels (Craig's directive, 2026-07-11). Verified on Emacs 30.2 pgtk via jotto's librsvg spike: `svg.el` cleanly renders rounded rects, linear gradients, fill-opacity, bold/italic/letter-spaced text, stroked shapes, and theme-derived colors. + +The payoff is the prototyping pipeline: design the UI as an HTML prototype first (per `ui-prototyping.md` — browser iteration), then port near-1:1 to `svg.el`, since browser SVG and librsvg share primitives. + +Constraints to design around: + +- **GUI-only.** No tty rendering — ship a second text renderer or accept GUI-only. +- **The SVG region is an image, not text.** Input stays minibuffer/keymaps; no point-and-click into the drawing. +- **Theme colors are pulled by hand.** Read them from faces at render time (`face-attribute`); the image inherits nothing. +- **Whole-image regeneration per state change.** Design the render as a pure state → SVG function so regeneration is cheap to reason about. +- **Stick to a librsvg-safe subset.** No SMIL, no JS-in-SVG, conservative CSS and filters. `feDropShadow` renders without error but is visually unverified — don't lean on it. + +Recommended shape: hybrid layouts — SVG for boards, meters, and visual state; text buffers for prose and transcripts. + +Prior art: archsetup's instrument-console widget gallery (`docs/prototypes/2026-07-03-panel-widget-gallery-prototype.html` in archsetup); jotto's spec records the full pipeline and constraint sheet. diff --git a/claude-rules/interaction.md b/claude-rules/interaction.md index 9148b4f..8d65799 100644 --- a/claude-rules/interaction.md +++ b/claude-rules/interaction.md @@ -57,3 +57,14 @@ In conversational output to the user, do not use Markdown bold (`**...**`) or in - Fenced code blocks (triple backtick) are acceptable when the user explicitly wants a block to copy — they don't invert the way inline spans do. Default to plain text otherwise. This governs **chat output**, not the Markdown source of rule files, specs, or docs the user reads in an editor — those keep normal Markdown formatting. The constraint is the terminal rendering of the live conversation. + +## Showing Craig Visuals + +Craig runs Claude Code inside Emacs EAT (through tmux). EAT renders SendUserFile and inline terminal images as an `[image] path.png` text line — the visual itself never appears. In one session ~20 renders went out that way and Craig approved UI he had never seen (takuzu, 2026-07-11). Never rely on SendUserFile or inline image display to show a visual. SendUserFile stays fine for *delivering* a file; it just doesn't display one. + +Two display lanes, by what the visual is for: + +- **Durable or interactive visuals** — HTML prototypes, full renders, anything Craig will study or click: open in the browser (`google-chrome-stable "file://<abs-path>" &>/dev/null &`) or imv, on a separate workspace per `desktop-capture.md`. +- **Quick inline glances** — a chart, a did-it-render check: sixel in the terminal. Encode with ImageMagick (`magick <img> sixel:<out>`; img2sixel silently emits zero bytes on some builds) and display in a separate tmux window (`tmux new-window -d -n <name>`, then `tmux send-keys -t <name> "clear; cat <out>" Enter`, tell Craig the window name) rather than the Claude Code pane, whose TUI repaints over anything drawn into it. With native tmux sixel active, the image lives in tmux's grid and survives window switches, scrolling, and resizing. + +**Capability gate.** Native sixel needs two config pieces: EAT answering the XTWINOPS cell-size query (patched eat.el, owned by .emacs.d) and `terminal-features 'xterm*:sixel'` in tmux.conf (owned by dotfiles). Check before relying on it: `tmux display -p '#{client_cell_width}'` — nonzero means go; 0 means the chain is missing a piece, so fall back to the browser lane. (Diagnosed 2026-07-13: tmux won't transmit sixel until it knows the client's cell pixel size, and stock EAT 0.9.4 silently ignores the CSI 14 t query tmux uses to ask.) diff --git a/claude-rules/knowledge-base.md b/claude-rules/knowledge-base.md index 5658498..d8aa6a6 100644 --- a/claude-rules/knowledge-base.md +++ b/claude-rules/knowledge-base.md @@ -53,7 +53,7 @@ Pull before writing, commit and push after (`git -C ~/org/roam add -A && git com ## Capture, then promote -Harness memory (`~/.claude/projects/<enc>/memory/`) remains the per-project capture layer: fast, automatic, allowed to be at-risk. At wrap-up (or a task audit, or an explicit prompt), promote facts that meet the inclusion bar into the KB as nodes. The wrap-up workflow asks; answer it honestly — promotion discipline is what keeps the capture layer from silting up. +Harness memory (`~/.claude/projects/<enc>/memory/`) remains the per-project capture layer: fast, automatic, allowed to be at-risk. An agent running without harness memory (a non-Claude runtime) captures into its session log instead and promotes from there — the promotion discipline is the same. At wrap-up (or a task audit, or an explicit prompt), promote facts that meet the inclusion bar into the KB as nodes. The wrap-up workflow asks; answer it honestly — promotion discipline is what keeps the capture layer from silting up. ## Inventory diff --git a/claude-rules/todo-format.md b/claude-rules/todo-format.md index 5e9ca32..2cdc76c 100644 --- a/claude-rules/todo-format.md +++ b/claude-rules/todo-format.md @@ -110,6 +110,29 @@ the letter mapping are fixed. and safety issues are graded on severity alone — one occurrence with the right consequences is a showstopper no matter how rarely it would be hit. +**Don't double-count rarity.** Grade severity by the rate of harm once the +failure state is entered, not by how rare it is to enter. Frequency already +carries the rarity; letting it discount severity too grades the same fact twice, +and that buries exactly the bugs that compound — the ones where a rare trigger +produces unbounded harm. A leak that repeats every timeout period until the +process restarts is Major even when reaching that state is a rare edge case +("accumulates slowly" describes a bounded trickle, not a fixed-rate leak with no +workaround). Grade the *being-in-it*, and let the frequency row carry the +*getting-into-it*. + +**Record the grading in the task body.** State the severity band, the frequency +row, and the arithmetic (e.g. "Major severity × rare edge case = P3 = [#C]"). A +bare priority cookie can't be argued with; a stated read can be re-checked +against the source and corrected. This is what lets a misgrade move — a chime +watchdog bug went [#D] → [#C] an hour after grading precisely because the read +was written down and re-checked. + +**Disagreeing with a grade means fixing an input.** If a letter looks wrong, +don't override the letter — re-read the severity band and the frequency row +against the source and correct whichever input is wrong. Overriding the cookie +directly turns the matrix into a formality and puts you back to grading by +instinct, which is the thing it exists to replace. + ## The Rule A todo entry has two parts: diff --git a/claude-rules/ui-prototyping.md b/claude-rules/ui-prototyping.md new file mode 100644 index 0000000..c453258 --- /dev/null +++ b/claude-rules/ui-prototyping.md @@ -0,0 +1,93 @@ +# UI Prototyping for Specs + +Applies to: `**/*` (any spec whose deliverable has a non-trivial UI) + +How to settle a UI design before committing implementation code: research the +category, brainstorm the UX in the spec, build a handful of full working +prototypes, then iterate one to a final. The prototype is the evidence a design +decision is recorded against. Discovered building archsetup's timer-panel spec, +promoted here so any UI-bearing spec follows the same shape. + +## When this applies — non-trivial UI only + +The process fires when a spec's deliverable is a real UI: a panel, a +multi-control surface, a visual layout with interacting parts. Not a single +dialog, a CLI flag, or a one-off prompt. The test: if "which of these layouts +is right?" can't be answered from a sentence, it qualifies. + +A spec with no UI, or a trivial one, skips this rule entirely. + +## The process + +### 1. Research first — during brainstorming, before any prototype + +Before building anything, survey how existing and best-in-class tools solve the +same UX: the category's well-regarded apps, prior art, the conventions users +already expect. Feed the findings into the spec's Goals and Design so the UX is +understood before a single prototype exists. Prototyping blind wastes iterations +re-deriving what a 20-minute survey would have told you. Cite the sources in the +spec. + +### 2. Brainstorm the UX in the spec + +Informed by the research, write the goals, the interactions, and the functional +surface into the spec. This is the "what and why" the prototypes make real. + +### 3. Prototype — ~5 distinct directions, then iterate one to final + +Build about five genuinely different directions — distinct layouts and +interaction models, not variations of one — as full working prototypes over one +shared engine, in the project's design language. Pick a direction, then iterate +that one across numbered passes to the final. Save each meaningful pass as its +own numbered prototype so the design history is walkable. + +For an Emacs app UI, the production port target is `svg.el` — browser SVG and +librsvg share primitives, so the winning prototype ports near-1:1. Keep every +prototype within the librsvg-safe constraint sheet in `emacs.md` (SVG Rendering +section) so the port stays mechanical. + +### 4. Full working prototypes, not mockups + +The prototypes must be functional: real state, real controls, real behavior, so +decisions are made against how it feels to use rather than against a picture. A +static mockup hides the interaction problems that only surface when you drive it. + +### 5. Naming and location + +`docs/prototypes/<spec-name>-prototype-<N>.html`, where `<spec-name>` is the +spec's dated slug with the `-spec` suffix dropped, and `N` is the iteration +number. For `docs/specs/2026-07-02-timer-panel-spec.org`, the prototypes are +`docs/prototypes/2026-07-02-timer-panel-prototype-1.html`, `-2.html`, `-3.html`. + +### 6. Link from the spec; keep every iteration in history + +The spec links the final prototype in its design section, and keeps links to +every prior iteration in a "Prototype iterations" subsection under the status +heading — newest last — so the design's evolution is walkable from the spec. + +### 7. Decisions get recorded once seen working + +A design decision moves into the spec's Decisions only after it has been seen +working in a prototype. "Resolved live through the prototype iteration" — the +prototype is the evidence, not an argument on the page. + +## How the spec workflows hook in + +- **spec-create**: for a non-trivial-UI spec, add the "research → brainstorm → + prototype (5 directions → iterate)" step before the design is treated as + settled, and require the "Prototype iterations" subsection under the status + heading. +- **spec-review**: for a non-trivial-UI spec, verify the process ran — research + cited, final prototype linked, iterations present in history, and each UI + design decision backed by a prototype rather than asserted. + +Both workflows point here rather than restating the process, so the rule stays +the single source of truth. + +## Why + +A UI design argued on paper is a guess. Five working directions surface the +interaction problems a mockup hides, and iterating one of them to a final makes +the tradeoffs concrete before any production code is written. Recording each +decision only once it's been seen working keeps the spec honest: the Decisions +section documents what the prototype proved, not what the author hoped. diff --git a/claude-rules/verification.md b/claude-rules/verification.md index 26d05b5..2c2088e 100644 --- a/claude-rules/verification.md +++ b/claude-rules/verification.md @@ -68,6 +68,8 @@ Do not let an unverifiable check vanish into a confident summary. State it plain Some checks can only be run by the user: interactive UI a script can't drive, a live external service, visual rendering (colors, layout, faces), a real device, or anything where the verification *is* a human looking at the result. When the gap needs the user's hands or eyes — not just a command they could paste — don't bury the steps in prose. Write them as a structured, runnable checklist in the project's task file. +When *you* need a window on the user's live desktop to verify something visually — render a panel, grab a screenshot — keep it off the workspace they're actively using. Capture on an off-screen output and tear it down; when showing the user something, open it on a separate workspace and name it. See `desktop-capture.md` for the rule and the Hyprland recipe. + Create (or append to) a single parent task named **"Manual testing and validation"** in the project's todo file (`todo.org`, or the project's equivalent). Under it, write **one org sub-header per test**: - **Title** — descriptive, naming the behavior under test (not "test 1"). diff --git a/claude-templates/.ai/notes.org b/claude-templates/.ai/notes.org index 42ea8df..c16863b 100644 --- a/claude-templates/.ai/notes.org +++ b/claude-templates/.ai/notes.org @@ -1,5 +1,5 @@ #+TITLE: Claude Code Notes - [Project Name] -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: [Date] * About This File diff --git a/claude-templates/.ai/protocols.org b/claude-templates/.ai/protocols.org index 5e18ab9..5cd69d4 100644 --- a/claude-templates/.ai/protocols.org +++ b/claude-templates/.ai/protocols.org @@ -1,5 +1,5 @@ #+TITLE: Claude Code Protocols -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2025-11-05 * About This File @@ -367,6 +367,12 @@ Craig's shell aliases =ls= to =exa=, which prints nothing to non-TTY pipes (e.g. - Applies to =ls -la=, =ls -t=, glob expansions piped through =ls=, and any =ls= invocation whose output gets read programmatically. - Symptom if forgotten: the Bash tool returns empty output and you mistakenly conclude the directory is empty. +*** zsh does not word-split unquoted variables +The Bash tool runs zsh, which (unlike bash) does not split an unquoted =$var= on whitespace. =chrome $urls= passes all the space-joined URLs as one malformed argument. + +- Loop over the values, use an array, or force the split with =${=var}=. +- Symptom if forgotten: a command that "works in bash" gets one garbled argument and fails, often silently (from the takuzu session, 2026-07-11). + ** Miscellaneous Information - Craig currently lives in New Orleans, LA - Craig's phone number: 510-316-9357 @@ -406,14 +412,27 @@ Full usage: =notify --help= or see =~/.local/bin/notify= - =atq= - list all scheduled alarms - =atrm [number]= - remove an alarm by its queue number -** Paging Craig — desktop vs. away from the machine +** Paging Craig — the agent pager -"Page me" has two channels; pick by where Craig is. +"Page me" has two channels; pick by where Craig is. Both work from any agent runtime — nothing here is Claude-specific. - *At his laptop/desktop* — desktop =notify ... --persist= (above). It reaches him on the machine and stays up until dismissed. -- *Away from his laptop/desktop* — page his phone over Signal via the *signal-mcp* tool =send_message_to_user=, addressed to Craig's account UUID =b1b5601e-6126-47f8-afaa-0a59f5188fde= (his primary number reads as unregistered in Signal's directory — never page a phone number). The message goes out from the dedicated pager account (+15045173983) and fires a normal mobile push. This is the live cross-device path, verified working 2026-06-30. -Do *not* use the old =page-signal= shell script — it was removed from the rulesets canonical 2026-06-12 and its =~/.local/bin/page-signal= symlink no longer exists. The signal-mcp tool is the only supported Signal path; =notify --persist= is the only supported desktop path. + #+begin_src bash + notify info "Title" "Message" --persist + #+end_src + +- *Away from his laptop/desktop* — page his phone over Signal with the *agent pager*: + + #+begin_src bash + agent-page "Message for Craig's phone" + #+end_src + + =agent-page= (in =~/.local/bin= via the rulesets install) sends from the dedicated pager identity (+15045173983, registered in velox's signal-cli) to Craig's Signal account UUID, firing a normal mobile push. On velox it sends directly; on any other tailnet machine it ssh-relays the send to velox. Verified end to end 2026-07-13. Never page Craig's phone *number* — it reads as unregistered in Signal's directory; the script already targets the UUID. + + Caveats: velox must be up and on the tailnet (the script says so and names the desktop fallback when the relay fails), and the signal-cli account wants a periodic =receive= — both tracked on the rulesets Signal-pager task, which owns the full runbook. + +On velox, Claude sessions may also have the *signal-mcp* tool (=send_message_to_user=, same pager identity) — fine to use there, but it exists only in velox's local MCP config, so =agent-page= is the portable habit. Do *not* use the old =page-signal= shell script (removed 2026-06-12). * Session Protocols diff --git a/claude-templates/.ai/references/calendar-reference.org b/claude-templates/.ai/references/calendar-reference.org index b44c0f1..5791b08 100644 --- a/claude-templates/.ai/references/calendar-reference.org +++ b/claude-templates/.ai/references/calendar-reference.org @@ -1,5 +1,5 @@ #+TITLE: Calendar Reference -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings Tool recipes, authentication, and credentials for Craig's calendar setup. Three access methods, in order of preference. diff --git a/claude-templates/.ai/scripts/lint-org.el b/claude-templates/.ai/scripts/lint-org.el index 90b1b1d..55727ef 100644 --- a/claude-templates/.ai/scripts/lint-org.el +++ b/claude-templates/.ai/scripts/lint-org.el @@ -2,16 +2,19 @@ ;; ;; Usage: ;; emacs --batch -q -l lint-org.el FILE.org [FILE.org ...] +;; report only (the default) — categorize without modifying the file. +;; A linter reports, it doesn't write; mutation requires --fix. +;; --check is accepted as an explicit alias of this default. +;; +;; emacs --batch -q -l lint-org.el --fix FILE.org [FILE.org ...] ;; apply mechanical fixes in place, emit judgment items on stdout for the ;; command layer to walk ;; -;; emacs --batch -q -l lint-org.el --check FILE.org [FILE.org ...] -;; report only — categorize without modifying the file -;; -;; emacs --batch -q -l lint-org.el --followups-file=PATH FILE.org +;; emacs --batch -q -l lint-org.el --fix --followups-file=PATH FILE.org ;; apply mechanical fixes; if any judgment items remain, append them to ;; PATH as an org section dated today. Used by wrap-it-up to defer the ;; judgment walk to the next morning's review without blocking the wrap. +;; (--followups-file only writes in --fix mode.) ;; ;; Mechanical categories (auto-fixed): ;; item-number add [@N] directive to drifted bullets @@ -66,7 +69,9 @@ Each plist has :kind (mechanical-fixed | judgment), :line, :checker, :msg. Mechanical entries from --check mode also carry :preview t.") (defvar lo-check-only nil - "Non-nil means run in report-only mode — no buffer writes.") + "Non-nil means run in report-only mode — no buffer writes. +The CLI defaults this to t (a linter reports, it doesn't write); +`--fix' is what enables writes on a command-line run.") (defvar lo-current-file nil "Path of the file currently being processed.") (defvar lo-followups-file nil @@ -355,24 +360,40 @@ logical row, matching wrap-org-table.el's grouping." (defun lo--check-tables () "Scan the current buffer for org tables violating the table standard. -Emits one judgment item per violating table." +Emits one judgment item per violating table. Pipe-led lines inside +#+begin_/#+end_ blocks are content (ASCII art, shell pipes), not tables, +and are skipped — the same block rule `wot-process-file' applies." (save-excursion (goto-char (point-min)) - (while (re-search-forward "^[ \t]*|" nil t) - (let ((start-line (line-number-at-pos)) - (lines nil)) - (beginning-of-line) - (while (and (not (eobp)) (looking-at "[ \t]*|")) - (push (buffer-substring-no-properties (line-beginning-position) - (line-end-position)) - lines) + (let ((in-block nil)) ; the open block's type, e.g. "example" — nil outside + (while (not (eobp)) + (cond + ;; Type-matched close only: literal #+end_src quoted inside an + ;; example block must not clear the flag (see wot-process-file). + ((and (not in-block) + (looking-at "^[ \t]*#\\+begin_\\([^ \t\n]+\\)")) + (setq in-block (downcase (match-string 1))) + (forward-line 1)) + ((and in-block + (looking-at-p (format "^[ \t]*#\\+end_%s\\([ \t]\\|$\\)" + (regexp-quote in-block)))) + (setq in-block nil) (forward-line 1)) - (let ((violations (lo--table-violations (nreverse lines)))) - (when violations - (lo--emit-judgment - 'org-table-standard start-line - (format "table violates the org-table standard: %s — wrap-org-table.el reflows it" - (string-join violations "; "))))))))) + ((and (not in-block) (looking-at-p "^[ \t]*|")) + (let ((start-line (line-number-at-pos)) + (lines nil)) + (while (and (not (eobp)) (looking-at "[ \t]*|")) + (push (buffer-substring-no-properties (line-beginning-position) + (line-end-position)) + lines) + (forward-line 1)) + (let ((violations (lo--table-violations (nreverse lines)))) + (when violations + (lo--emit-judgment + 'org-table-standard start-line + (format "table violates the org-table standard: %s — wrap-org-table.el reflows it" + (string-join violations "; "))))))) + (t (forward-line 1))))))) ;;; --------------------------------------------------------------------------- ;;; level-2 dated-header check (claude-rules/todo-format.md) @@ -677,6 +698,13 @@ After printing, also append judgments to `lo-followups-file' when set." ;;; CLI (defun lo-main () + ;; Report-only is the CLI default; --fix is the only way a command-line run + ;; writes to disk. The old mutate-by-default reformatted five files in one + ;; pass before anyone confirmed anything (work project, 2026-07-09). + (setq lo-check-only t) + (when (member "--fix" command-line-args-left) + (setq lo-check-only nil) + (setq command-line-args-left (delete "--fix" command-line-args-left))) (when (member "--check" command-line-args-left) (setq lo-check-only t) (setq command-line-args-left (delete "--check" command-line-args-left))) @@ -688,7 +716,7 @@ After printing, also append judgments to `lo-followups-file' when set." (setq command-line-args-left (delete followups command-line-args-left)))) (if (null command-line-args-left) (progn - (princ "Usage: emacs --batch -q -l lint-org.el [--check] [--followups-file=PATH] FILE.org ...\n") + (princ "Usage: emacs --batch -q -l lint-org.el [--fix] [--check] [--followups-file=PATH] FILE.org ...\n") (kill-emacs 1)) (let ((files command-line-args-left)) (setq command-line-args-left nil) @@ -707,7 +735,7 @@ this file without firing the CLI dispatch — under `ert-run-tests-batch-and-exi the trailing args are things like `-f ert-run-tests-batch-and-exit'." (and command-line-args-left (cl-every (lambda (a) - (cond ((member a '("--check")) t) + (cond ((member a '("--check" "--fix")) t) ((string-prefix-p "--followups-file=" a) t) ((string-prefix-p "-" a) nil) (t (file-readable-p a)))) diff --git a/claude-templates/.ai/scripts/task-review-staleness.sh b/claude-templates/.ai/scripts/task-review-staleness.sh index ed43712..50e0257 100755 --- a/claude-templates/.ai/scripts/task-review-staleness.sh +++ b/claude-templates/.ai/scripts/task-review-staleness.sh @@ -19,9 +19,14 @@ # deeper headings, and cookie-less headings are not review units. # # A task is stale (count mode), and sorts oldest (list mode), when its -# :LAST_REVIEWED: property is missing or unparseable (NIL sorts first), or -# when its age strictly exceeds the threshold (age > N days; age == N is -# still fresh). +# :LAST_REVIEWED: property is missing (NIL sorts first) or when its age +# strictly exceeds the threshold (age > N days; age == N is still fresh). +# +# :LAST_REVIEWED: accepts a bare date (2026-07-09) or an org-native +# timestamp ([2026-07-09 Thu] or <2026-07-09 Thu ...>); both normalize to +# the ISO date. A value that is present but parses to neither is a data +# error: the script warns loudly to stderr (file:line:value) and leaves it +# out of the stale count rather than silently treating it as never-reviewed. set -euo pipefail @@ -46,7 +51,7 @@ num="$2" # only the property drawer between a qualifying heading and the next heading # is scanned. extract_tasks() { - awk ' + awk -v fname="$todo_file" ' function flush() { if (in_task) printf "%s\t%s\t%s\n", hline, (have_lr ? lr : "NONE"), heading } @@ -65,7 +70,21 @@ extract_tasks() { v = $0 sub(/^[ \t]*:LAST_REVIEWED:[ \t]*/, "", v) sub(/[ \t]*$/, "", v) - lr = v; have_lr = 1 + raw = v + # Accept org-native stamps: strip a leading [ or < (inactive/active + # timestamp bracket) and take the leading ISO date, so [2026-07-09 Thu] + # and 2026-07-09 both normalize to 2026-07-09. + sub(/^[[<]/, "", v) + if (match(v, /^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/)) { + lr = substr(v, RSTART, RLENGTH); have_lr = 1 + } else { + # Present but unparseable — a data error, not "never reviewed". Warn + # loudly (file:line:value) instead of silently folding it into the + # stale count, where a re-review in the same bad format would never + # drop the number and nothing would explain why. + printf "task-review-staleness: %s:%d: unparseable :LAST_REVIEWED: %s (expected YYYY-MM-DD or [YYYY-MM-DD Day])\n", fname, NR, raw > "/dev/stderr" + lr = "INVALID"; have_lr = 1 + } next } END { flush() } @@ -98,9 +117,16 @@ while IFS=$'\t' read -r hline value heading; do continue fi - # Unparseable date → treat as NIL (stale). + # Malformed stamp: extract_tasks already warned. A data error is not + # "never reviewed", so it stays out of the stale count. + if [ "$value" = "INVALID" ]; then + continue + fi + + # A shape-valid but impossible date (e.g. 2026-13-45) slips past the awk + # regex; warn and skip rather than silently counting it. if ! rev_epoch=$(date -d "$value" +%s 2>/dev/null); then - count=$((count + 1)) + echo "task-review-staleness: $todo_file: unparseable :LAST_REVIEWED: $value" >&2 continue fi diff --git a/claude-templates/.ai/scripts/tests/lint-org-cli.bats b/claude-templates/.ai/scripts/tests/lint-org-cli.bats index d457696..b9faef6 100644 --- a/claude-templates/.ai/scripts/tests/lint-org-cli.bats +++ b/claude-templates/.ai/scripts/tests/lint-org-cli.bats @@ -20,6 +20,24 @@ teardown() { [[ "$output" == *"lint-org: file="* ]] } +@test "lint-org.el default invocation is report-only — file untouched" { + # bare #+begin_src is a mechanical fix (→ #+begin_example) that the old + # default applied on disk; a linter reports, it doesn't write + printf '* H\n\n#+begin_src\nx\n#+end_src\n' > "$TMPFILE" + before="$(cat "$TMPFILE")" + run emacs --batch -q -l "$SCRIPTS_DIR/lint-org.el" "$TMPFILE" + [ "$status" -eq 0 ] + [[ "$output" == *"would-fix"* ]] + [ "$(cat "$TMPFILE")" = "$before" ] +} + +@test "lint-org.el --fix applies mechanical fixes on disk" { + printf '* H\n\n#+begin_src\nx\n#+end_src\n' > "$TMPFILE" + run emacs --batch -q -l "$SCRIPTS_DIR/lint-org.el" --fix "$TMPFILE" + [ "$status" -eq 0 ] + grep -q '#+begin_example' "$TMPFILE" +} + @test "wrap-org-table.el loads and runs without -L on the load path" { run emacs --batch -q -l "$SCRIPTS_DIR/wrap-org-table.el" --width=120 "$TMPFILE" [ "$status" -eq 0 ] diff --git a/claude-templates/.ai/scripts/tests/task-review-staleness.bats b/claude-templates/.ai/scripts/tests/task-review-staleness.bats index 488b023..79aad79 100644 --- a/claude-templates/.ai/scripts/tests/task-review-staleness.bats +++ b/claude-templates/.ai/scripts/tests/task-review-staleness.bats @@ -49,6 +49,16 @@ task_unreviewed() { printf '** %s [#%s] %s\nBody.\n\n' "$keyword" "$prio" "$title" >> "$TODO" } +# Emit a qualifying task whose LAST_REVIEWED is an org-native inactive +# timestamp — [YYYY-MM-DD Day] — matching the CREATED:/CLOSED: cookies that +# sit in the same drawer. The date is derived from an ISO date via `date`. +task_reviewed_org() { + local keyword="$1" prio="$2" title="$3" isodate="$4" + local org="[$(date -d "$isodate" '+%F %a')]" + printf '** %s [#%s] %s\n:PROPERTIES:\n:LAST_REVIEWED: %s\n:END:\nBody.\n\n' \ + "$keyword" "$prio" "$title" "$org" >> "$TODO" +} + # ---- Normal cases ---------------------------------------------------- @test "staleness: empty file reports zero" { @@ -85,6 +95,20 @@ task_unreviewed() { [ "$output" = "2" ] } +@test "staleness: org-native bracketed LAST_REVIEWED parses — recent is fresh" { + task_reviewed_org TODO A "Reviewed five days ago, org stamp" "$D5" + run bash "$SCRIPT" "$TODO" 30 + [ "$status" -eq 0 ] + [ "$output" = "0" ] +} + +@test "staleness: org-native bracketed LAST_REVIEWED parses — old is stale" { + task_reviewed_org TODO A "Reviewed forty days ago, org stamp" "$D40" + run bash "$SCRIPT" "$TODO" 30 + [ "$status" -eq 0 ] + [ "$output" = "1" ] +} + # ---- Boundary cases -------------------------------------------------- @test "staleness: age exactly equal to threshold is fresh" { @@ -136,9 +160,23 @@ task_unreviewed() { [ "$output" = "0" ] } -@test "staleness: malformed LAST_REVIEWED is treated as stale" { +@test "staleness: malformed LAST_REVIEWED warns to stderr and is not counted" { task_reviewed TODO A "Bad date" "not-a-date" - run bash "$SCRIPT" "$TODO" 30 + # stdout carries only the count — the malformed stamp is not folded in. + run bash -c "bash '$SCRIPT' '$TODO' 30 2>/dev/null" + [ "$status" -eq 0 ] + [ "$output" = "0" ] + # stderr carries the loud warning naming the offending value. + run bash -c "bash '$SCRIPT' '$TODO' 30 2>&1 1>/dev/null" + [ "$status" -eq 0 ] + [[ "$output" == *"not-a-date"* ]] + [[ "$output" == *"LAST_REVIEWED"* ]] +} + +@test "staleness: malformed stamp is excluded while real stale tasks still count" { + task_reviewed TODO A "Real stale" "$D40" + task_reviewed TODO B "Broken stamp" "garbage" + run bash -c "bash '$SCRIPT' '$TODO' 30 2>/dev/null" [ "$status" -eq 0 ] [ "$output" = "1" ] } @@ -161,6 +199,17 @@ task_unreviewed() { [[ "${lines[2]}" == *"Reviewed recently"* ]] } +@test "staleness --list: org-native bracketed stamp sorts by its real date" { + task_reviewed TODO A "Bare recent" "$D5" + task_reviewed_org TODO B "Org-stamped old" "$D40" + run bash "$SCRIPT" --list "$TODO" 10 + [ "$status" -eq 0 ] + # The org-bracketed old stamp must sort ahead of the bare recent one — + # proof it parsed to a real date rather than falling to 0000-00-00. + [[ "${lines[0]}" == *"Org-stamped old"* ]] + [[ "${lines[1]}" == *"Bare recent"* ]] +} + @test "staleness --list: takes only the requested count" { task_unreviewed TODO A "First" task_reviewed TODO B "Second" "$D40" diff --git a/claude-templates/.ai/scripts/tests/test-lint-org.el b/claude-templates/.ai/scripts/tests/test-lint-org.el index d14879f..8e3e190 100644 --- a/claude-templates/.ai/scripts/tests/test-lint-org.el +++ b/claude-templates/.ai/scripts/tests/test-lint-org.el @@ -620,6 +620,29 @@ followups file on the next run." ;;; --------------------------------------------------------------------------- ;;; org-table-standard check (width budget + rules between rows) +(ert-deftest lo-table-inside-example-block-not-flagged () + "Pipe-led ASCII art inside an example block is not a table; no judgment." + (let* ((run (lo-test--run + "* H\n\n#+begin_example\n| client |----->| server |\n| box | | box |\n#+end_example\n" + 1 t)) + (judgments (lo-test--judgments (plist-get run :issues)))) + (should-not (memq 'org-table-standard (lo-test--checkers judgments))))) + +(ert-deftest lo-table-inside-src-block-not-flagged () + "Shell pipes inside a src block are not a table; no judgment." + (let* ((run (lo-test--run + "* H\n\n#+begin_src sh\n| sort\n| uniq -c\n#+end_src\n" 1 t)) + (judgments (lo-test--judgments (plist-get run :issues)))) + (should-not (memq 'org-table-standard (lo-test--checkers judgments))))) + +(ert-deftest lo-real-table-after-block-still-flagged () + "Block safety must not mask a genuine violation later in the file." + (let* ((run (lo-test--run + "* H\n\n#+begin_example\n| art |\n#+end_example\n\n| a | b |\n| 1 | 2 |\n" + 1 t)) + (judgments (lo-test--judgments (plist-get run :issues)))) + (should (memq 'org-table-standard (lo-test--checkers judgments))))) + (ert-deftest lo-table-over-budget-emits-judgment () "A table line rendering wider than 120 surfaces as an org-table-standard judgment." (let* ((wide (make-string 130 ?x)) diff --git a/claude-templates/.ai/scripts/tests/test-wrap-org-table.el b/claude-templates/.ai/scripts/tests/test-wrap-org-table.el index 8d1ecb6..0b3b375 100644 --- a/claude-templates/.ai/scripts/tests/test-wrap-org-table.el +++ b/claude-templates/.ai/scripts/tests/test-wrap-org-table.el @@ -186,3 +186,45 @@ (should (string-match-p "Prose before\\." content)) (should (string-match-p "Prose after\\." content)))) (delete-file file)))) + +;;; --------------------------------------------------------------------------- +;;; block safety — pipe lines inside #+begin_/#+end_ blocks are never tables + +(defconst wot-test--block-content + "#+begin_example +| client |----->| server | +| box | | box | +#+end_example +" + "An example block whose ASCII-art lines start with pipes.") + +(defun wot-test--process-content (content budget) + "Write CONTENT to a temp file, run `wot-process-file' at BUDGET, return result." + (let ((file (make-temp-file "wot-test" nil ".org"))) + (unwind-protect + (progn + (with-temp-file file (insert content)) + (wot-process-file file budget) + (with-temp-buffer (insert-file-contents file) (buffer-string))) + (delete-file file)))) + +(ert-deftest wot-process-file-leaves-example-block-byte-identical () + (let ((content (concat "* Diagram\n\n" wot-test--block-content))) + (should (equal (wot-test--process-content content 120) content)))) + +(ert-deftest wot-process-file-reformats-table-but-not-block () + (let* ((content (concat "* Doc\n\n" wot-test--block-content "\n" + wot-test--wide-input)) + (result (wot-test--process-content content 40))) + (should (string-match-p (regexp-quote wot-test--block-content) result)) + (should (string-match-p (regexp-quote wot-test--wide-expected) result)))) + +(ert-deftest wot-process-file-skips-pipes-in-src-block () + (let ((content "* Pipeline\n\n#+begin_src sh\n| sort\n| uniq -c\n#+end_src\n")) + (should (equal (wot-test--process-content content 120) content)))) + +(ert-deftest wot-process-file-literal-inner-end-marker-stays-in-block () + "A literal #+end_src quoted inside an example block must not close it." + (let ((content (concat "* Doc\n\n#+begin_example\n#+begin_src sh\nx\n" + "#+end_src\n| art |----| art |\n#+end_example\n"))) + (should (equal (wot-test--process-content content 120) content)))) diff --git a/claude-templates/.ai/scripts/wrap-org-table.el b/claude-templates/.ai/scripts/wrap-org-table.el index ddbea65..173e44d 100644 --- a/claude-templates/.ai/scripts/wrap-org-table.el +++ b/claude-templates/.ai/scripts/wrap-org-table.el @@ -228,22 +228,40 @@ continuation lines merge back into their logical row before re-wrapping." ;;; file layer (defun wot-process-file (file &optional budget) - "Reformat every org table in FILE in place to BUDGET width." + "Reformat every org table in FILE in place to BUDGET width. +Pipe-led lines inside #+begin_/#+end_ blocks (example, src, quote, …) are +content, not tables — ASCII art in an example block once got mangled into a +bordered table — so block regions are skipped verbatim." (with-temp-buffer (insert-file-contents file) (goto-char (point-min)) - (while (re-search-forward "^[ \t]*|" nil t) - (let ((start (line-beginning-position))) - (while (and (not (eobp)) - (save-excursion (beginning-of-line) - (looking-at "[ \t]*|"))) + (let ((in-block nil)) ; the open block's type, e.g. "example" — nil outside + (while (not (eobp)) + (cond + ;; Only the matching #+end_<type> closes a block: an example block + ;; often quotes literal #+begin_src/#+end_src lines, and a boolean + ;; flag would let that inner literal end-marker re-expose the rest + ;; of the block to reformatting. + ((and (not in-block) + (looking-at "^[ \t]*#\\+begin_\\([^ \t\n]+\\)")) + (setq in-block (downcase (match-string 1))) (forward-line 1)) - (let* ((end (point)) - (table (buffer-substring-no-properties start end)) - (reformatted (wot-reformat-table-string table budget))) - (delete-region start end) - (goto-char start) - (insert reformatted)))) + ((and in-block + (looking-at-p (format "^[ \t]*#\\+end_%s\\([ \t]\\|$\\)" + (regexp-quote in-block)))) + (setq in-block nil) + (forward-line 1)) + ((and (not in-block) (looking-at-p "^[ \t]*|")) + (let ((start (point))) + (while (and (not (eobp)) (looking-at-p "^[ \t]*|")) + (forward-line 1)) + (let* ((end (point)) + (table (buffer-substring-no-properties start end)) + (reformatted (wot-reformat-table-string table budget))) + (delete-region start end) + (goto-char start) + (insert reformatted)))) + (t (forward-line 1))))) (write-region (point-min) (point-max) file))) ;;; --------------------------------------------------------------------------- @@ -289,7 +307,21 @@ so the ERT suite can `require' this file without firing the CLI dispatch." (t (file-readable-p a)))) command-line-args-left))) -(when (and noninteractive (wot--cli-invocation-p)) +(defun wot--entry-script-p () + "Non-nil when wrap-org-table.el itself was named on the command line. +lint-org.el `require's this file, and a load-triggered dispatch would run +the table reformatter over lint-org's file arguments — that's how a lint +invocation once reformatted the files it was only supposed to report on. +Only dispatch when a -l/--load argument names this very file." + (and load-file-name + (cl-loop for (flag arg) on command-line-args + thereis (and (member flag '("-l" "--load")) + (stringp arg) + (file-exists-p arg) + (string= (file-truename (expand-file-name arg)) + (file-truename load-file-name)))))) + +(when (and noninteractive (wot--entry-script-p) (wot--cli-invocation-p)) (wot-main)) (provide 'wrap-org-table) diff --git a/claude-templates/.ai/workflows/INDEX.org b/claude-templates/.ai/workflows/INDEX.org index 88721ed..b031dbe 100644 --- a/claude-templates/.ai/workflows/INDEX.org +++ b/claude-templates/.ai/workflows/INDEX.org @@ -1,5 +1,5 @@ #+TITLE: Workflow Index -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-04-25 * Purpose @@ -110,8 +110,8 @@ This index must list every =.org= file in =.ai/workflows/= except this one and e - Situational triggers: "broadcast the <event> to all projects", "broadcast that <situation>", "let every project know I'll be away ..." - =flashcard-review.org= — review an org-drill flashcard file, restructure cards to question-form headings (no answer hints), audit content accuracy against project source-of-truth via subagent, rewrite source preserving SRS state, regenerate the Anki =.apkg= to =~/sync/phone/anki/=. Person cards use "Who is X? Tell me about their Y."; talking-points cards stay as-is. Script behavior: =flashcard-to-anki.py= strips =:PROPERTIES:= drawers + =SCHEDULED:= / =DEADLINE:= planning lines from Anki output. - Triggers: "review the flashcards", "update the flashcards", "review the drill deck", "update the drill deck", "refresh the Anki cards", "let's run the flashcard-review workflow" -- =page-me.org= — set a timed notification. - - Triggers: anything containing the word "page" used as a verb ("page me", "page me in 10 minutes", "page me at 3pm") +- =page-me.org= — set a timed notification (desktop =notify=; phone via =agent-page= when Craig is away). + - Triggers: anything containing the word "page" used as a verb ("page me", "page me in 10 minutes", "page me at 3pm", "page my phone") - =status-check.org= — proactive long-running-job updates. - Triggers: "keep me posted on this", "provide status checks on this job", "let me know when it's done", "monitor this for me". Auto: any job estimated 10+ min. - =create-workflow.org= — define a new workflow. diff --git a/claude-templates/.ai/workflows/add-calendar-event.org b/claude-templates/.ai/workflows/add-calendar-event.org index 2650fb7..5dd6c42 100644 --- a/claude-templates/.ai/workflows/add-calendar-event.org +++ b/claude-templates/.ai/workflows/add-calendar-event.org @@ -1,5 +1,5 @@ #+TITLE: Add Calendar Event Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-02-01 * Overview diff --git a/claude-templates/.ai/workflows/broadcast.org b/claude-templates/.ai/workflows/broadcast.org index 60e9ed1..cc14f00 100644 --- a/claude-templates/.ai/workflows/broadcast.org +++ b/claude-templates/.ai/workflows/broadcast.org @@ -1,5 +1,5 @@ #+TITLE: Broadcast Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-05-29 * Overview diff --git a/claude-templates/.ai/workflows/clean-todo.org b/claude-templates/.ai/workflows/clean-todo.org index a1b2af5..48d3084 100644 --- a/claude-templates/.ai/workflows/clean-todo.org +++ b/claude-templates/.ai/workflows/clean-todo.org @@ -1,5 +1,5 @@ #+TITLE: Clean-Todo Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-05-11 * Overview diff --git a/claude-templates/.ai/workflows/code-quality.org b/claude-templates/.ai/workflows/code-quality.org index 2406f4c..3ac3e9d 100644 --- a/claude-templates/.ai/workflows/code-quality.org +++ b/claude-templates/.ai/workflows/code-quality.org @@ -1,5 +1,5 @@ #+TITLE: Code-Quality Sweep Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-06-28 * Overview diff --git a/claude-templates/.ai/workflows/daily-prep.org b/claude-templates/.ai/workflows/daily-prep.org index b6989e7..3103bc7 100644 --- a/claude-templates/.ai/workflows/daily-prep.org +++ b/claude-templates/.ai/workflows/daily-prep.org @@ -1,5 +1,5 @@ #+TITLE: Daily Prep Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-06-11 * Overview diff --git a/claude-templates/.ai/workflows/delete-calendar-event.org b/claude-templates/.ai/workflows/delete-calendar-event.org index 5bb92a1..7de0086 100644 --- a/claude-templates/.ai/workflows/delete-calendar-event.org +++ b/claude-templates/.ai/workflows/delete-calendar-event.org @@ -1,5 +1,5 @@ #+TITLE: Delete Calendar Event Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-02-01 * Overview diff --git a/claude-templates/.ai/workflows/edit-calendar-event.org b/claude-templates/.ai/workflows/edit-calendar-event.org index 662f0b4..27a9dd3 100644 --- a/claude-templates/.ai/workflows/edit-calendar-event.org +++ b/claude-templates/.ai/workflows/edit-calendar-event.org @@ -1,5 +1,5 @@ #+TITLE: Edit Calendar Event Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-02-01 * Overview diff --git a/claude-templates/.ai/workflows/email-assembly.org b/claude-templates/.ai/workflows/email-assembly.org index 003459c..699dbc0 100644 --- a/claude-templates/.ai/workflows/email-assembly.org +++ b/claude-templates/.ai/workflows/email-assembly.org @@ -1,5 +1,5 @@ #+TITLE: Email Assembly Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-01-29 * Overview diff --git a/claude-templates/.ai/workflows/extract-email.org b/claude-templates/.ai/workflows/extract-email.org index 3a70bea..c68bafe 100644 --- a/claude-templates/.ai/workflows/extract-email.org +++ b/claude-templates/.ai/workflows/extract-email.org @@ -1,5 +1,5 @@ #+TITLE: Extract Email Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-02-06 * Overview diff --git a/claude-templates/.ai/workflows/find-email.org b/claude-templates/.ai/workflows/find-email.org index 0ef9615..d71ed3e 100644 --- a/claude-templates/.ai/workflows/find-email.org +++ b/claude-templates/.ai/workflows/find-email.org @@ -1,5 +1,5 @@ #+TITLE: Find Email Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-02-01 * Overview diff --git a/claude-templates/.ai/workflows/first-session.org b/claude-templates/.ai/workflows/first-session.org index 60118a2..147026f 100644 --- a/claude-templates/.ai/workflows/first-session.org +++ b/claude-templates/.ai/workflows/first-session.org @@ -1,5 +1,5 @@ #+TITLE: First Session Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings Run this workflow on the first Claude Code session for a new project. It establishes the git/.ai policy, orients Claude to the diff --git a/claude-templates/.ai/workflows/flashcard-review.org b/claude-templates/.ai/workflows/flashcard-review.org index 31027b3..09af348 100644 --- a/claude-templates/.ai/workflows/flashcard-review.org +++ b/claude-templates/.ai/workflows/flashcard-review.org @@ -1,5 +1,5 @@ #+TITLE: Drill Deck Review Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-05-30 * Overview diff --git a/claude-templates/.ai/workflows/helper-mode.org b/claude-templates/.ai/workflows/helper-mode.org index cdec200..a6acfa7 100644 --- a/claude-templates/.ai/workflows/helper-mode.org +++ b/claude-templates/.ai/workflows/helper-mode.org @@ -1,5 +1,5 @@ #+TITLE: Helper Mode Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-06-15 * Overview diff --git a/claude-templates/.ai/workflows/inbox.org b/claude-templates/.ai/workflows/inbox.org index acfd11d..3bd9335 100644 --- a/claude-templates/.ai/workflows/inbox.org +++ b/claude-templates/.ai/workflows/inbox.org @@ -1,5 +1,5 @@ #+TITLE: Inbox Workflow (Engine) -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-06-23 * Overview diff --git a/claude-templates/.ai/workflows/journal-entry.org b/claude-templates/.ai/workflows/journal-entry.org index 3f476a7..c70dfe8 100644 --- a/claude-templates/.ai/workflows/journal-entry.org +++ b/claude-templates/.ai/workflows/journal-entry.org @@ -1,5 +1,5 @@ #+TITLE: Journal Entry Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2025-11-07 * Overview diff --git a/claude-templates/.ai/workflows/meeting-prep.org b/claude-templates/.ai/workflows/meeting-prep.org index 162ae30..563328b 100644 --- a/claude-templates/.ai/workflows/meeting-prep.org +++ b/claude-templates/.ai/workflows/meeting-prep.org @@ -1,5 +1,5 @@ #+TITLE: Meeting-Prep Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-06-10 * Overview diff --git a/claude-templates/.ai/workflows/meeting-prep.pre-wire.org b/claude-templates/.ai/workflows/meeting-prep.pre-wire.org index 6a156c0..3e27c2a 100644 --- a/claude-templates/.ai/workflows/meeting-prep.pre-wire.org +++ b/claude-templates/.ai/workflows/meeting-prep.pre-wire.org @@ -1,5 +1,5 @@ #+TITLE: Meeting-Prep — Pre-Wire Method (supporting doc) -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-06-10 Supporting document for the [[file:meeting-prep.org][meeting-prep workflow]]'s Phase 3.5. The workflow carries the condensed, in-flow version of pre-wiring; this file is the full Manager Tools method, kept beside the workflow (same name + =.pre-wire= suffix) so it travels with the workflow. Source casts: "How to Prewire a Meeting" (2007) and "Peer Prewire" (2015). diff --git a/claude-templates/.ai/workflows/no-approvals.org b/claude-templates/.ai/workflows/no-approvals.org index 9e1c894..5f54b96 100644 --- a/claude-templates/.ai/workflows/no-approvals.org +++ b/claude-templates/.ai/workflows/no-approvals.org @@ -1,5 +1,5 @@ #+TITLE: No-Approvals Mode -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-05-28 * Overview diff --git a/claude-templates/.ai/workflows/open-tasks.org b/claude-templates/.ai/workflows/open-tasks.org index 02a0847..205d95c 100644 --- a/claude-templates/.ai/workflows/open-tasks.org +++ b/claude-templates/.ai/workflows/open-tasks.org @@ -1,5 +1,5 @@ #+TITLE: Open Tasks Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-04-25 * Overview diff --git a/claude-templates/.ai/workflows/page-me.org b/claude-templates/.ai/workflows/page-me.org index 8069830..bfa92c6 100644 --- a/claude-templates/.ai/workflows/page-me.org +++ b/claude-templates/.ai/workflows/page-me.org @@ -1,5 +1,5 @@ #+TITLE: Page Me Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-01-31 #+UPDATED: 2026-02-27 @@ -113,6 +113,19 @@ notify info "Page" "Your message here" --persist The =--persist= flag keeps the notification on screen until manually dismissed. All page-me notifications should use =--persist= by default. +** Paging Craig's phone (away from the machine) + +The timed =notify= alarm above is the desktop channel. When Craig is away from the machine (or asks to be paged "on my phone"), use the agent pager instead — a Signal push to his phone from any machine or agent runtime: + +#+begin_src bash +agent-page "Build finished — ready for your eyes" + +# Timed phone page: same at-daemon pattern, different channel +echo "agent-page 'Meeting starts in 5'" | at 3:25pm +#+end_src + +Channel selection and the pager's mechanics live in protocols.org "Paging Craig — the agent pager". When in doubt, fire both: the desktop notification persists for whenever he returns, the phone push reaches him now. + ** Managing Alarms #+begin_src bash diff --git a/claude-templates/.ai/workflows/process-meeting-transcript.org b/claude-templates/.ai/workflows/process-meeting-transcript.org index 4dd340f..d0806ad 100644 --- a/claude-templates/.ai/workflows/process-meeting-transcript.org +++ b/claude-templates/.ai/workflows/process-meeting-transcript.org @@ -1,5 +1,5 @@ #+TITLE: Process Meeting Transcript Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-02-03 * Overview @@ -10,16 +10,16 @@ This workflow defines the process for processing meeting recordings from start t Trigger this workflow when: - Craig says "process the transcript" or "process the recording" or similar -- New recording files (.mkv) appear in ~/sync/recordings/ after meetings +- New recording files (.mkv, .m4a, or .flac) appear in ~/sync/recordings/ after meetings - Craig wants to process meeting recordings into labeled transcripts * Prerequisites -- Recording file(s) exist in ~/sync/recordings/ (*.mkv) +- Recording file(s) exist in ~/sync/recordings/ (*.mkv, *.m4a, or *.flac) - Calendar files available at ~/.emacs.d/data/*cal.org for meeting titles - AssemblyAI transcription script at ~/.emacs.d/scripts/assemblyai-transcribe - AssemblyAI API key stored in ~/.authinfo.gpg (machine api.assemblyai.com) -- ffmpeg available for audio extraction +- ffmpeg available for audio extraction (video .mkv only; .m4a and .flac skip extraction) * The Workflow @@ -43,13 +43,13 @@ Classification is per recording, not per session — a single run can carry this Find and match recording files with calendar events. *Run sub-steps 1 and 3 (recording list + calendar dump) as a single parallel batch* — they're independent. Sub-step 2 (parse timestamps) and sub-step 4 (matching) work from those two outputs in-memory, so they're sequential after the batch. -1. **List recordings:** Find all recording files in ~/sync/recordings/ (video .mkv or audio-only .m4a) +1. **List recordings:** Find all recording files in ~/sync/recordings/ (video .mkv or audio-only .m4a / .flac) #+begin_src bash - ls -la ~/sync/recordings/*.mkv ~/sync/recordings/*.m4a 2>/dev/null + ls -la ~/sync/recordings/*.mkv ~/sync/recordings/*.m4a ~/sync/recordings/*.flac 2>/dev/null #+end_src - Audio-only recordings (.m4a) are used when no screen content is expected. These skip Step 3 (audio extraction) since they're already in a transcribable format. + Audio-only recordings (.m4a or lossless .flac) are used when no screen content is expected. These skip Step 3 (audio extraction) since they're already in a transcribable format. FLAC is the recorder's current audio format — its frames are self-contained, so an interrupted recording still decodes. -2. **Extract timestamps:** Parse date/time from each filename (format: YYYY-MM-DD-HH-MM-SS.mkv or .m4a) +2. **Extract timestamps:** Parse date/time from each filename (format: YYYY-MM-DD-HH-MM-SS.mkv, .m4a, or .flac) 3. **Match with calendar:** Check ~/.emacs.d/data/*cal.org for meetings at those times #+begin_src bash @@ -74,7 +74,7 @@ Per =cross-project.md=: transcribe and label here, then deliver the *labeled tra ** Step 3: Extract Audio (video recordings only) -*Skip this step for .m4a files* — they are already audio and can go directly to transcription. +*Skip this step for .m4a and .flac files* — they are already audio and can go directly to transcription. For .mkv video recordings, extract audio for transcription: @@ -96,11 +96,11 @@ Output: /tmp/FILENAME.m4a (temporary, deleted after transcription) #+begin_src bash # For .mkv files (audio was extracted to /tmp/): ~/.emacs.d/scripts/assemblyai-transcribe /tmp/FILENAME.m4a > ~/sync/recordings/FILENAME.txt - # For .m4a files (transcribe directly): + # For .m4a or .flac files (transcribe directly — AssemblyAI accepts both natively): ~/.emacs.d/scripts/assemblyai-transcribe ~/sync/recordings/FILENAME.m4a > ~/sync/recordings/FILENAME.txt #+end_src -2. **Clean up:** Delete intermediate .m4a file after successful transcription (only for .mkv extractions — do NOT delete original .m4a recordings) +2. **Clean up:** Delete intermediate .m4a file after successful transcription (only for .mkv extractions — do NOT delete original .m4a or .flac recordings) #+begin_src bash rm /tmp/FILENAME.m4a #+end_src @@ -181,13 +181,13 @@ Present the speaker identification table to Craig for confirmation: ** Step 9: Copy Recording to Meetings Folder -1. Ensure engagement meetings folder exists and patterns are in .gitignore (~*/meetings/*.mkv~ and ~*/meetings/*.m4a~) +1. Ensure engagement meetings folder exists and patterns are in .gitignore (~*/meetings/*.mkv~, ~*/meetings/*.m4a~, and ~*/meetings/*.flac~) 2. Copy the recording file with descriptive name: #+begin_src bash # Video recordings: cp ~/sync/recordings/YYYY-MM-DD-HH-MM-SS.mkv {engagement}/meetings/YYYY-MM-DD_HH-MM-meeting-name.mkv - # Audio-only recordings: + # Audio-only recordings (.m4a or .flac — copy with the original extension): cp ~/sync/recordings/YYYY-MM-DD-HH-MM-SS.m4a {engagement}/meetings/YYYY-MM-DD_HH-MM-meeting-name.m4a #+end_src Example: ~deepsat/meetings/2026-02-03_11-02-standup-ipm-grooming.mkv~ diff --git a/claude-templates/.ai/workflows/read-calendar-events.org b/claude-templates/.ai/workflows/read-calendar-events.org index be66bf4..5eac529 100644 --- a/claude-templates/.ai/workflows/read-calendar-events.org +++ b/claude-templates/.ai/workflows/read-calendar-events.org @@ -1,5 +1,5 @@ #+TITLE: Read Calendar Events Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-02-01 * Overview diff --git a/claude-templates/.ai/workflows/readability-audit.org b/claude-templates/.ai/workflows/readability-audit.org index 8223a03..90ad366 100644 --- a/claude-templates/.ai/workflows/readability-audit.org +++ b/claude-templates/.ai/workflows/readability-audit.org @@ -1,5 +1,5 @@ #+TITLE: Readability Audit Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-06-28 * Overview diff --git a/claude-templates/.ai/workflows/rename-artifact.org b/claude-templates/.ai/workflows/rename-artifact.org index 7b9f15b..a8d1246 100644 --- a/claude-templates/.ai/workflows/rename-artifact.org +++ b/claude-templates/.ai/workflows/rename-artifact.org @@ -1,5 +1,5 @@ #+TITLE: Rename an .ai Artifact -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-05-31 * Summary diff --git a/claude-templates/.ai/workflows/send-email.org b/claude-templates/.ai/workflows/send-email.org index 065f925..82d2286 100644 --- a/claude-templates/.ai/workflows/send-email.org +++ b/claude-templates/.ai/workflows/send-email.org @@ -1,5 +1,5 @@ #+TITLE: Email Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-01-26 * Overview diff --git a/claude-templates/.ai/workflows/session-harvest.org b/claude-templates/.ai/workflows/session-harvest.org index c48d689..54a7c09 100644 --- a/claude-templates/.ai/workflows/session-harvest.org +++ b/claude-templates/.ai/workflows/session-harvest.org @@ -1,5 +1,5 @@ #+TITLE: Session-Harvest Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-06-11 * Overview diff --git a/claude-templates/.ai/workflows/spec-create.org b/claude-templates/.ai/workflows/spec-create.org index 1249181..39758a0 100644 --- a/claude-templates/.ai/workflows/spec-create.org +++ b/claude-templates/.ai/workflows/spec-create.org @@ -1,5 +1,5 @@ #+TITLE: Spec-Create Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-06-09 * Overview @@ -47,6 +47,8 @@ Capture, in this order: ** Phase 2 — Design, alternatives, decisions 1. *Design* — overview first, then detail. Write the reasoning as *prose, not bullet dumps* — prose exposes weak logic that bullets let you hide. Use bullets only for genuinely enumerable lists. When the thing has an interface, use the *two-altitude* split (Rust RFC): explain it once for a user/caller, once for an implementer. + + *Non-trivial UI.* When the deliverable is a real UI (a panel, a multi-control surface, an interacting visual layout — not a single dialog, a CLI flag, or a one-off prompt), the design isn't settled on the page. Run the research → ~5 distinct working-prototype directions → iterate-one-to-final process in =claude-rules/ui-prototyping.md= before treating the UI design as done, and add a =Prototype iterations= subsection under the spec's status heading linking every iteration (final linked in the design section). A UI design decision moves to =DONE= only once it's been seen working in a prototype. 2. *Alternatives considered* — the load-bearing section authors skip and reviewers need most. For each option, force a why-not with the MADR grammar: "Good, because… / Bad, because… / Neutral, because…". Even one rejected option, with the reason, beats presenting one path as inevitable. 3. *Decisions* — capture each real choice as an org =TODO= task carrying an inline mini-ADR (Nygard's spine): - The heading is =** TODO <Decision name>=. It flips to =DONE= when the decision-maker agrees with the call; until then it stays =TODO=. diff --git a/claude-templates/.ai/workflows/spec-review.org b/claude-templates/.ai/workflows/spec-review.org index d4998eb..0da8e65 100644 --- a/claude-templates/.ai/workflows/spec-review.org +++ b/claude-templates/.ai/workflows/spec-review.org @@ -134,6 +134,7 @@ Work the spec against these. Each is a source of concrete findings, not a box to - *Security & privacy.* API keys safe? Debug logs leaking secrets or private issue text? Confirmations before mutating shared workspace objects? Personal vs shared distinguished? Local files holding sensitive descriptions/comments? Anything to redact from messages/logs? Any work-tracker integration may handle private company data. - *UX & accessibility.* Discoverable commands? Recoverable mistakes? Prompts ordered to the task? Safe, useful defaults? Informative-not-noisy status messages? Does the UI avoid implying unsupported actions are supported? Match the upstream product's permissions/concepts? Are customizations named in user language, with clear defaults and docstrings? For Emacs packages, command names, completion candidates, buffer layout, defcustom names, and message wording *are* the UX. - *Operational-panel UI traps.* Applies when the spec covers a user-facing panel, dialog, or control surface; skip otherwise. Lists that mix saved, current, and generated items must name each item's source. Refresh or scan actions must not gate data that could be shown immediately. Add-forms must not ask the user to retype values the system already discovered. Destructive confirmations read in future tense before the action and verified-result tense after it. Diagnostics, performance, logging, and repair affordances are reviewed as one coherent flow before extra pages or buttons are added. A popup launched from a bar, tray, or tool surface should visually belong to that launcher. (Promoted from archsetup's Waybar network-panel review, 2026-06-30.) +- *Prototype process for non-trivial UI.* Applies when the deliverable is a real UI (a panel, a multi-control surface, an interacting visual layout — not a single dialog or CLI flag); skip otherwise. Verify the =claude-rules/ui-prototyping.md= process ran: category research is cited in Goals/Design, the final prototype is linked in the design section, a =Prototype iterations= subsection under the status heading lists every pass, and each UI design decision is backed by a prototype it was seen working in rather than asserted on the page. A non-trivial-UI spec with decisions but no prototype evidence is a =:blocking:= finding. - *Test strategy and coverage.* Characterization tests before behavior changes? Pure functions to unit-test? API responses needing fixtures? Command flows needing stubs? Regression tests for prior bugs? Boundary/error cases? What's covered elsewhere and shouldn't be re-tested? Which existing tests must change? How is coverage generated, summarized, and used to find untested/refactor-worthy code? Prefer tests that lock contracts: representation shape, query compilation, sync no-op, conflict refusal, pagination, dirty-buffer protection, log redaction, and long-running/slow-operation behavior via fakes rather than flaky live dependencies. - *Observability & operations.* How does a user see what the package is doing? Progress messages for long ops? Useful, safe debug logging? Are logs structured enough to isolate issues from a bug report? Are commands provided to inspect/clear caches, test connectivity, diagnose backends/tools, copy redacted debug info, or reproduce command invocations? How are terminal states discovered: completion, failure, partial success, stalled/hung, cancelled, cleanup-unverified, and "needs user action"? Does the product notify only when useful, avoid noisy success spam, and keep non-success states visible until acknowledged? For generated org files, headers should often carry source, filter/view name, refresh time, count, truncation state. - *Comparable-product sentiment.* When there are obvious adjacent products, research what users love and hate about them from official docs plus current community reports. Do not cargo-cult their feature set; translate findings into the spec's scope. For each loved behavior, say whether the spec provides it, intentionally omits it, or defers it. For each hated behavior, say whether the spec avoids, resolves, inherits, or accepts it. diff --git a/claude-templates/.ai/workflows/startup.org b/claude-templates/.ai/workflows/startup.org index 4e3d417..929d482 100644 --- a/claude-templates/.ai/workflows/startup.org +++ b/claude-templates/.ai/workflows/startup.org @@ -1,5 +1,5 @@ #+TITLE: Startup Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-04-25 * Summary diff --git a/claude-templates/.ai/workflows/status-check.org b/claude-templates/.ai/workflows/status-check.org index efff16d..4a9972c 100644 --- a/claude-templates/.ai/workflows/status-check.org +++ b/claude-templates/.ai/workflows/status-check.org @@ -1,5 +1,5 @@ #+TITLE: Status Check Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-02-02 * Overview diff --git a/claude-templates/.ai/workflows/summarize-emails.org b/claude-templates/.ai/workflows/summarize-emails.org index 6ac5e6f..c9c7001 100644 --- a/claude-templates/.ai/workflows/summarize-emails.org +++ b/claude-templates/.ai/workflows/summarize-emails.org @@ -1,5 +1,5 @@ #+TITLE: Summarize Emails Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-02-14 * Overview diff --git a/claude-templates/.ai/workflows/suspend.org b/claude-templates/.ai/workflows/suspend.org index 1c16bb9..3691f60 100644 --- a/claude-templates/.ai/workflows/suspend.org +++ b/claude-templates/.ai/workflows/suspend.org @@ -1,5 +1,5 @@ #+TITLE: Session Suspend Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-06-28 * Overview diff --git a/claude-templates/.ai/workflows/sync-email.org b/claude-templates/.ai/workflows/sync-email.org index 52a7caf..863b400 100644 --- a/claude-templates/.ai/workflows/sync-email.org +++ b/claude-templates/.ai/workflows/sync-email.org @@ -1,5 +1,5 @@ #+TITLE: Sync Email Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-02-01 * Overview diff --git a/claude-templates/.ai/workflows/task-audit.org b/claude-templates/.ai/workflows/task-audit.org index 7d2b758..aa50176 100644 --- a/claude-templates/.ai/workflows/task-audit.org +++ b/claude-templates/.ai/workflows/task-audit.org @@ -1,5 +1,5 @@ #+TITLE: Task Audit Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-05-22 * Overview diff --git a/claude-templates/.ai/workflows/task-review.org b/claude-templates/.ai/workflows/task-review.org index ba1571a..7ea2e8e 100644 --- a/claude-templates/.ai/workflows/task-review.org +++ b/claude-templates/.ai/workflows/task-review.org @@ -1,5 +1,5 @@ #+TITLE: Task Review Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-05-20 * Overview @@ -92,7 +92,7 @@ Set =:LAST_REVIEWED:= to today's date (from above) in the task's =:PROPERTIES:= Body... #+end_example -The exact date string matters: =task-review-staleness.sh= and the wrap-up health check both parse =:LAST_REVIEWED: YYYY-MM-DD=. +Format: =:LAST_REVIEWED:= takes a bare ISO date (=2026-05-20=) or an org-native inactive timestamp (=[2026-05-20 Tue]=, matching the =CREATED:=/=CLOSED:= cookies beside it); =task-review-staleness.sh= and the wrap-up health check normalize both to the date. A value that is neither is a data error — the staleness script warns loudly to stderr (naming the file, line, and value) and leaves the task out of the stale count rather than silently reporting a freshly-reviewed task as never-reviewed. Stamp a clean date and the warning never fires. *** Killing a task diff --git a/claude-templates/.ai/workflows/triage-intake.cmail.org b/claude-templates/.ai/workflows/triage-intake.cmail.org index d818c72..8d8abfb 100644 --- a/claude-templates/.ai/workflows/triage-intake.cmail.org +++ b/claude-templates/.ai/workflows/triage-intake.cmail.org @@ -1,5 +1,5 @@ #+TITLE: Triage Intake — cmail (Proton) Source -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-05-26 # Source plugin for the triage-intake engine. See triage-intake.org for the diff --git a/claude-templates/.ai/workflows/triage-intake.github-prs.org b/claude-templates/.ai/workflows/triage-intake.github-prs.org index c1bc796..644421c 100644 --- a/claude-templates/.ai/workflows/triage-intake.github-prs.org +++ b/claude-templates/.ai/workflows/triage-intake.github-prs.org @@ -1,5 +1,5 @@ #+TITLE: Triage Intake — Personal GitHub PRs Source -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-05-26 # Source plugin for the triage-intake engine. See triage-intake.org for the diff --git a/claude-templates/.ai/workflows/triage-intake.org b/claude-templates/.ai/workflows/triage-intake.org index a5a3bda..fde67a7 100644 --- a/claude-templates/.ai/workflows/triage-intake.org +++ b/claude-templates/.ai/workflows/triage-intake.org @@ -1,5 +1,5 @@ #+TITLE: Triage Intake Workflow (Engine) -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-05-01 * Summary @@ -414,7 +414,7 @@ The sentinel is checked into git, but git tracks content, not mtime — so an mt Craig, via the work project's same-day handoff: "we only need to report if anything's changed when we do triage intake." Sweep summaries report deltas only — a new invite, a new/moved/cancelled event, a new message needing attention. Unchanged sources get no block (the "Calendar — quiet" roll-call is retired), and an all-quiet sweep renders as a single "HH:MM sweep: no changes" line. Failures keep their loud banner (never folded into the no-change line) and the suggested-actions line stays when actions are queued. Same ruling: the telegram plugin's dev-community group traffic is dropped from reports entirely unless Craig asks (see that plugin's 2026-06-11 note). **** 2026-06-10: Loud failure surfacing (Phase C item 0 + Common Mistake 9) -Craig: "highlight any failures in daily triage loudly. I get important communication from all these channels." Trigger: the 2026-06-10 sweep shipped with Signal silently missing — a standalone receive hung on the account lock while the signel daemon owned it, and the failure looked identical to a quiet source. Failures now lead the summary in a ⚠ SCAN FAILED banner; the telegram plugin's failure path points at this rule. +Craig: "highlight any failures in daily triage loudly. I get important communication from all these channels." Trigger: the 2026-06-10 sweep shipped with Signal silently missing — a standalone receive hung on the signal-cli account lock while another client held it, and the failure looked identical to a quiet source. Failures now lead the summary in a ⚠ SCAN FAILED banner; the telegram plugin's failure path points at this rule. **** 2026-05-26: Refactor into engine + source plugins Split the monolithic workflow into a source-agnostic engine (this file) and per-source plugins named =triage-intake.<source>.org=. The engine carries the anchor/sentinel logic, the four-bucket model, the Phase A-D orchestration, the todo.org persistence convention, and the exit criteria. Each source's scan/classify/render/action knowledge moved to its own plugin. General plugins (personal-gmail, personal-calendar, cmail, github-prs) live in =.ai/workflows/= and are template-synced; project-specific plugins (a work project's Linear, work Gmail, work Slack, enterprise PRs) live in the project's =.ai/project-workflows/= and are never synced. Phase 0 globs *both* directories — the loud requirement, because missing the project dir silently halves the sweep. Naming convention: first dot is the engine/plugin boundary, deeper dots reserved for sub-adapters. This removed all DeepSat/Linear specifics from the engine; they become work-project plugins. diff --git a/claude-templates/.ai/workflows/triage-intake.personal-calendar.org b/claude-templates/.ai/workflows/triage-intake.personal-calendar.org index bf7d543..b5ee67a 100644 --- a/claude-templates/.ai/workflows/triage-intake.personal-calendar.org +++ b/claude-templates/.ai/workflows/triage-intake.personal-calendar.org @@ -1,5 +1,5 @@ #+TITLE: Triage Intake — Personal Calendar Source -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-05-26 # Source plugin for the triage-intake engine. See triage-intake.org for the diff --git a/claude-templates/.ai/workflows/triage-intake.personal-gmail.org b/claude-templates/.ai/workflows/triage-intake.personal-gmail.org index aa0554d..7fb1231 100644 --- a/claude-templates/.ai/workflows/triage-intake.personal-gmail.org +++ b/claude-templates/.ai/workflows/triage-intake.personal-gmail.org @@ -1,5 +1,5 @@ #+TITLE: Triage Intake — Personal Gmail Source -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-05-26 # Source plugin for the triage-intake engine. See triage-intake.org for the @@ -21,10 +21,29 @@ Personal Gmail unread in the inbox since the anchor: mcp__google-docs-personal__listMessages q="is:unread in:inbox after:<anchor-epoch>" maxResults=100 #+end_src -⚠ *Express the cutoff as the literal UNIX epoch* — =after:1778856990=, not =after:YYYY/MM/DD=. Gmail's =after:YYYY/MM/DD= operator only supports day resolution; the =YYYY/MM/DD HH:MM:SS= form is NOT valid syntax — Gmail parses the space as a term separator, treats =HH:MM:SS= as a search term that never matches, and returns 0 results, silently masking unread mail. The engine supplies =<anchor-epoch>= because this source declares =ANCHOR: epoch=. +⚠ *Express every anchor cutoff as the literal UNIX epoch* — =after:1784177122= and =before:1784177122= for the same anchor, never the =YYYY/MM/DD= form. This governs *both* anchored queries: the scan above and the backlog-residue probe below. They must meet at the same instant or mail falls between them permanently. Gmail's day-resolution operators fail two different ways: =after:YYYY/MM/DD HH:MM:SS= is not valid syntax at all — Gmail parses the space as a term separator, treats =HH:MM:SS= as a search term that never matches, and returns 0 results, silently masking unread mail — while =before:YYYY/MM/DD= is valid but excludes the named day entirely, so pairing it with a second-resolution scan leaves the whole anchor day covered by neither query. The engine supplies =<anchor-epoch>= because this source declares =ANCHOR: epoch=. + +The rule binds the *anchor* windows only. The date-slice walk below deliberately uses =before:<oldest-full-day-seen>= at day resolution — safe there because consecutive slices overlap and get deduped by message id. ⚠ *Do NOT add =-category:promotions -category:social=.* That filter masked 67 promo+social messages across two runs (2026-05-04, 2026-05-06), both needing a follow-up sweep. Pull the full unfiltered set; the trash-leaning bias in Classify handles promotions and social directly. +⚠ *The MCP caps at =maxResults=100= and exposes NO =pageToken= parameter.* The response carries a =nextPageToken=, but the tool can't consume it, so a pile over 100 is silently truncated — the tail below the cap never gets classified, and every later anchored sweep skips it (it predates the new anchor). This is exactly how a 300+ backlog accumulated invisibly by 2026-07-08. Two consequences: + +- *Never treat a 100-row result as complete.* When a scan returns exactly 100, walk the tail in *date slices*: re-query with =before:<oldest-full-day-seen>= (day resolution), repeat until a page returns fewer than 100, dedupe by message id across slices (the day-resolution boundary overlaps). +- *Never report =resultSizeEstimate= as a count.* It's unreliable — observed stuck at "201" across three different queries whose real union exceeded 300. + +*** Backlog-residue check (every sweep — cheap, mandatory) + +The anchored scan is blind to anything unread from *before* the anchor. After it, run one probe for pre-anchor residue: + +#+begin_src text +mcp__google-docs-personal__listMessages q="is:unread in:inbox before:<anchor-epoch>" maxResults=5 +#+end_src + +The cutoff is the epoch, matching the scan's =after:<anchor-epoch>= — see the epoch rule above. + +If it returns any messages, surface one loud line in the sweep summary: "Backlog: unread predating the anchor exists (N+ shown; date-slice to inventory)" and offer a backlog sweep. Never fold the residue into a quiet sweep — an anchored "no changes" claim is only true for the window the scan saw. (Added 2026-07-08 after ~300 pre-anchor unread accumulated unseen; the probe returns actual messages, so it works where the estimate lies. Shipped with a day-resolution cutoff that hid the entire anchor day; fixed to epoch 2026-07-16 after a home sweep reported the backlog clear while two July-15 messages sat unread.) + ** Classify Bias: *trash-leaning* — personal Gmail is high noise volume. diff --git a/claude-templates/.ai/workflows/triage-intake.telegram.org b/claude-templates/.ai/workflows/triage-intake.telegram.org index 9caa4e1..5039a8b 100644 --- a/claude-templates/.ai/workflows/triage-intake.telegram.org +++ b/claude-templates/.ai/workflows/triage-intake.telegram.org @@ -1,5 +1,5 @@ #+TITLE: Triage Intake — Telegram Source -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-06-09 # Source plugin for the triage-intake engine. See triage-intake.org for the diff --git a/claude-templates/.ai/workflows/work-the-backlog.org b/claude-templates/.ai/workflows/work-the-backlog.org index b0666e7..090841d 100644 --- a/claude-templates/.ai/workflows/work-the-backlog.org +++ b/claude-templates/.ai/workflows/work-the-backlog.org @@ -1,5 +1,5 @@ #+TITLE: Work the Backlog -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-07-02 * Overview @@ -152,7 +152,7 @@ With paging on, fire one page when the set is done or the cap is hit — end-of- notify info "Page" "<project>: <N> done, <M> remaining — <one-line summary>" --persist #+end_src -=--persist= keeps it on screen until dismissed, and =info= is the page-me urgency convention (persistent but never crash-scary). The page fires when the set completes *or* the cap stops the run — either way exactly once. The message carries the project name, the completed count, and the remaining count (with skipped tasks noted in the run summary) so Craig can confirm ready and name the next project in one reply. There is no separate page-signal call — =notify= is the paging surface. +=--persist= keeps it on screen until dismissed, and =info= is the page-me urgency convention (persistent but never crash-scary). The page fires when the set completes *or* the cap stops the run — either way exactly once. The message carries the project name, the completed count, and the remaining count (with skipped tasks noted in the run summary) so Craig can confirm ready and name the next project in one reply. =notify= is the desktop paging surface; a run that expects Craig to be away also fires =agent-page= with the same message (the Signal phone channel — protocols.org "Paging Craig — the agent pager"). * Metrics diff --git a/claude-templates/.ai/workflows/wrap-it-up.org b/claude-templates/.ai/workflows/wrap-it-up.org index d0c4e75..5ce88a5 100644 --- a/claude-templates/.ai/workflows/wrap-it-up.org +++ b/claude-templates/.ai/workflows/wrap-it-up.org @@ -1,5 +1,5 @@ #+TITLE: Session Wrap-Up Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-04-20 * Overview @@ -202,9 +202,14 @@ else followups=".ai/lint-followups.org" fi [ -f todo.org ] && emacs --batch -q -l .ai/scripts/lint-org.el \ - --followups-file="$followups" todo.org + --fix --followups-file="$followups" todo.org #+end_src +The =--fix= flag is required for the writes: lint-org's CLI default is +report-only (a linter reports, it doesn't write), and this wrap-up pass is +the deliberate exception that applies fixes — its diff rides the wrap-up +commit for review. + =lint-org= runs =org-lint= over =todo.org=, auto-applies four mechanical categories (=item-number= counters, bare =#+begin_src= → =#+begin_example=, multi-line planning-info merged onto one line, =**X.**= → =*X.*=), and diff --git a/claude-templates/AGENTS.md b/claude-templates/AGENTS.md new file mode 100644 index 0000000..97fd001 --- /dev/null +++ b/claude-templates/AGENTS.md @@ -0,0 +1,19 @@ +# Agent Entry Point + +You are this project's agent, whichever model or harness is running you. + +1. If the project has `.ai/protocols.org`, read it now and follow all + instructions. It is the single entry point for session behavior: + startup, session logging, inbox processing, wrap-up. +2. Behavioral rules live in `.claude/rules/*.md` in the project (when + present) and `~/.claude/rules/*.md` globally. Read them; they bind + every session regardless of harness. +3. A `/name` reference in any rule or workflow (`/voice`, + `/review-code`, `/brainstorm`, ...) resolves to a file: + `~/.claude/skills/<name>/SKILL.md` or `~/.claude/commands/<name>.md`. + Read that file and follow it. If it is absent, say so and apply the + rule's documented fallback rather than skipping the gate. +4. Harness mechanics these files assume (hooks, `/clear`, popup tools) + may not exist in your harness. Degrade per each rule's own fallback + language. Never silently skip a verification or approval gate because + the tooling that enforces it is missing. diff --git a/claude-templates/bin/agent-page b/claude-templates/bin/agent-page new file mode 100755 index 0000000..11a5264 --- /dev/null +++ b/claude-templates/bin/agent-page @@ -0,0 +1,48 @@ +#!/bin/bash +# agent-page — page Craig's phone over Signal, from any machine or agent runtime. +# +# Usage: agent-page <message...> +# +# The pager identity (+15045173983) is registered in velox's signal-cli, so +# velox sends directly and every other machine ssh-relays the send over the +# tailnet. The recipient is Craig's Signal account UUID — his phone number +# reads as unregistered in Signal's directory, so never page the number. +# Verified end to end 2026-07-13 (phone push confirmed). +# +# This is the AWAY channel. At his desk, use the desktop channel instead: +# notify info "Title" "Message" --persist +# See protocols.org "Paging Craig" for choosing between them. +# +# Known caveats (tracked on the rulesets Signal-pager task): velox must be +# up and reachable on the tailnet, and the signal-cli account wants a +# periodic `receive` (staleness warnings appear otherwise). +# +# Source: ~/code/rulesets/claude-templates/bin/agent-page +# Install: make -C ~/code/rulesets install + +PAGER_ACCOUNT="+15045173983" +CRAIG_UUID="b1b5601e-6126-47f8-afaa-0a59f5188fde" +VELOX_HOST="velox.tailf3bb8c.ts.net" + +if [ $# -eq 0 ]; then + echo "usage: agent-page <message...>" >&2 + exit 2 +fi + +msg="$*" + +if [ "$(uname -n)" = "velox" ]; then + signal-cli -a "$PAGER_ACCOUNT" send -m "$msg" "$CRAIG_UUID" + rc=$? +else + # printf %q hardens the message for the remote shell. + ssh -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new \ + "$VELOX_HOST" \ + "signal-cli -a $PAGER_ACCOUNT send -m $(printf '%q' "$msg") $CRAIG_UUID" + rc=$? +fi + +if [ "$rc" -ne 0 ]; then + echo "agent-page: phone page failed (velox down or unreachable?) — fall back to the desktop channel: notify info 'Page' '<message>' --persist" >&2 +fi +exit "$rc" diff --git a/claude-templates/bin/ai b/claude-templates/bin/ai index 994dc1f..cf17875 100755 --- a/claude-templates/bin/ai +++ b/claude-templates/bin/ai @@ -1,16 +1,23 @@ #!/bin/bash -# ai — Claude Code session launcher (unified aix + hey) +# ai — agent session launcher (unified aix + hey) # # Usage: -# ai Select one or more projects via fzf and open each in -# an 'ai' tmux session window (creates session if needed). +# ai Pick the agent first (claude, codex/ChatGPT, or any +# local ollama model), then select one or more projects +# via fzf; each opens in an 'ai' tmux session window. # Git-aware: fetches, annotates with ↑/↓/dirty, auto-pulls -# clean-and-behind repos before opening. +# clean-and-behind repos before opening. --runtime or +# AI_RUNTIME skips the agent pick. # # ai <dir>... Single-project mode. Opens each given directory directly # in the 'ai' session (new window or switch to existing). # Use '.' for current directory. Git prep per dir. # +# ai --runtime <rt> Launch with a different agent CLI: claude (default), +# codex, or local (codex --oss against this machine's +# ollama; model per AI_LOCAL_MODEL, default gpt-oss:120b). +# Also settable via AI_RUNTIME. +# # ai --attach Attach to the existing 'ai' session without changes. # # ai -h | --help Show this help. @@ -25,7 +32,56 @@ # would kill the script. SESSION="ai" -CLAUDE_CMD="claude" +RUNTIME="${AI_RUNTIME:-claude}" +LOCAL_MODEL="${AI_LOCAL_MODEL:-gpt-oss:120b}" + +# Map the runtime name to the agent CLI a pane launches. All three take the +# opening instructions as a positional prompt. AGENT_BIN is the binary the +# dependency check probes; AGENT_CMD is the full launch command (the local +# runtime rides codex's open-source provider against the machine's ollama — +# model per AI_LOCAL_MODEL, default gpt-oss:120b, verified on ratio's +# Strix Halo 2026-07-13). +resolve_agent_cmd() { + case "$RUNTIME" in + claude) AGENT_BIN="claude"; AGENT_CMD="claude" ;; + codex) AGENT_BIN="codex"; AGENT_CMD="codex" ;; + local) AGENT_BIN="codex"; AGENT_CMD="codex --oss --local-provider=ollama -m $LOCAL_MODEL" ;; + *) + echo "ai: unknown runtime '$RUNTIME' — valid runtimes: claude, codex, local" >&2 + exit 2 + ;; + esac +} + +# One line per launchable agent, claude first (Enter-Enter keeps the old +# muscle memory). Local models appear only when both codex (the CLI that +# drives them) and a live ollama answer; a dead server just drops the lines. +build_runtime_choices() { + command -v claude >/dev/null 2>&1 && echo "claude — Claude Code" + command -v codex >/dev/null 2>&1 && echo "codex — ChatGPT (Codex CLI)" + if command -v codex >/dev/null 2>&1 && command -v ollama >/dev/null 2>&1; then + timeout 3 ollama list 2>/dev/null | tail -n +2 | awk 'NF {print "local:" $1 " — ollama"}' + fi +} + +# Interactive runtime pick for the bare-`ai` flow. Sets RUNTIME (and +# LOCAL_MODEL for a local pick) and re-resolves the agent command. +# Returns 1 when the pick is cancelled. +pick_runtime() { + local choice + choice=$(build_runtime_choices | fzf --height=30% --reverse --prompt='agent> ') || return 1 + [ -z "$choice" ] && return 1 + case "$choice" in + claude*) RUNTIME="claude" ;; + codex*) RUNTIME="codex" ;; + local:*) + RUNTIME="local" + LOCAL_MODEL="${choice#local:}" + LOCAL_MODEL="${LOCAL_MODEL%% *}" + ;; + esac + resolve_agent_cmd +} # Run in the pane's shell just before Claude launches. `stty susp undef` clears # the tty's SIGTSTP (C-z) character for this pane only, so an accidental C-z is @@ -46,16 +102,18 @@ build_instructions() { } usage() { - sed -n '2,20p' "$0" | sed 's|^# \?||' + sed -n '2,23p' "$0" | sed 's|^# \?||' exit 0 } -for cmd in fzf tmux claude; do - if ! command -v "$cmd" &>/dev/null; then - echo "ai: $cmd is not installed" >&2 - exit 1 - fi -done +check_deps() { + for cmd in fzf tmux "$AGENT_BIN"; do + if ! command -v "$cmd" &>/dev/null; then + echo "ai: $cmd is not installed" >&2 + exit 1 + fi + done +} # ---------- shared helpers ---------- @@ -73,7 +131,7 @@ create_window() { wid=$(tmux new-window -a -t "$SESSION:{end}" -n "$name" -c "$dir" -P -F '#{window_id}') sleep 0.1 instructions=$(build_instructions "$name") - tmux send-keys -t "$wid" "${LAUNCH_PREFIX}$CLAUDE_CMD \"$instructions\"" Enter + tmux send-keys -t "$wid" "${LAUNCH_PREFIX}$AGENT_CMD \"$instructions\"" Enter echo "$wid" } @@ -319,7 +377,7 @@ single_mode() { local instructions wid=$(tmux new-session -d -s "$SESSION" -n "$name" -c "$dir" -P -F '#{window_id}') instructions=$(build_instructions "$name") - tmux send-keys -t "$wid" "${LAUNCH_PREFIX}$CLAUDE_CMD \"$instructions\"" Enter + tmux send-keys -t "$wid" "${LAUNCH_PREFIX}$AGENT_CMD \"$instructions\"" Enter fi sort_windows @@ -379,7 +437,7 @@ multi_mode() { local instructions first_wid=$(tmux new-session -d -s "$SESSION" -n "$name" -c "$dir" -P -F '#{window_id}') instructions=$(build_instructions "$name") - tmux send-keys -t "$first_wid" "${LAUNCH_PREFIX}$CLAUDE_CMD \"$instructions\"" Enter + tmux send-keys -t "$first_wid" "${LAUNCH_PREFIX}$AGENT_CMD \"$instructions\"" Enter for entry in "${selected[@]:1}"; do dir="${entry/#\~/$HOME}" name="$(basename "$dir")" @@ -403,19 +461,78 @@ multi_mode() { attach_session } +# Print the launch command a real run would send to the pane, then exit. +# Exists for the launcher's bats tests: exercises runtime resolution and the +# opening line with no tmux or fzf involved. +print_launch_mode() { + local arg="$1" dir name + dir="$(cd "$arg" 2>/dev/null && pwd)" || { echo "ai: cannot access '$arg'" >&2; exit 1; } + if [ ! -f "$dir/.ai/protocols.org" ]; then + echo "ai: $dir has no .ai/protocols.org — not an agent-template project" >&2 + exit 1 + fi + name="$(basename "$dir")" + printf '%s "%s"\n' "$AGENT_CMD" "$(build_instructions "$name")" + exit 0 +} + # ---------- dispatch ---------- +print_launch="" +runtime_explicit="${AI_RUNTIME:+1}" +while [ $# -gt 0 ]; do + case "$1" in + -h|--help) + usage + ;; + --runtime) + [ -z "${2:-}" ] && { echo "ai: --runtime needs a value — valid runtimes: claude, codex, local" >&2; exit 2; } + RUNTIME="$2" + runtime_explicit=1 + shift 2 + ;; + --runtime=*) + RUNTIME="${1#--runtime=}" + runtime_explicit=1 + shift + ;; + --print-launch) + print_launch=1 + shift + ;; + --print-runtimes) + build_runtime_choices + exit 0 + ;; + *) + break + ;; + esac +done + +resolve_agent_cmd + +if [ -n "$print_launch" ]; then + [ $# -eq 0 ] && { echo "ai: --print-launch needs a project directory" >&2; exit 2; } + print_launch_mode "$1" +fi + case "${1:-}" in - -h|--help) - usage - ;; --attach) + check_deps attach_mode ;; "") + # Bare `ai`: pick the agent first (skipped when --runtime or AI_RUNTIME + # already chose), then the familiar project multi-select. + if [ -z "$runtime_explicit" ]; then + pick_runtime || exit 0 + fi + check_deps multi_mode ;; *) + check_deps for arg in "$@"; do single_mode "$arg" done diff --git a/docs/design/2026-06-02-flush-promotion.org b/docs/design/2026-06-02-flush-promotion.org index 9d9d8a3..ff4e579 100644 --- a/docs/design/2026-06-02-flush-promotion.org +++ b/docs/design/2026-06-02-flush-promotion.org @@ -1,5 +1,5 @@ #+TITLE: Flush Promotion — Handoff Bundle (from work) -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-06-02 * Provenance diff --git a/docs/design/2026-06-02-pattern-catalog-spec.org b/docs/design/2026-06-02-pattern-catalog-spec.org index e74b8ae..6bb105a 100644 --- a/docs/design/2026-06-02-pattern-catalog-spec.org +++ b/docs/design/2026-06-02-pattern-catalog-spec.org @@ -1,5 +1,5 @@ #+TITLE: Cross-Project Pattern Catalog — Spec -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-06-02 One-page spec for capturing reusable design patterns so they travel from one project to the next instead of being re-derived. Drafted for Craig's spec-review; the five open questions from the [[file:../../todo.org][todo.org]] task carry a recommended call each, marked DECISION. diff --git a/docs/design/2026-06-17-ntfy-agent-comms-proposal.org b/docs/design/2026-06-17-ntfy-agent-comms-proposal.org index ce17138..0961b47 100644 --- a/docs/design/2026-06-17-ntfy-agent-comms-proposal.org +++ b/docs/design/2026-06-17-ntfy-agent-comms-proposal.org @@ -1,5 +1,5 @@ #+TITLE: Proposal — Promote the ntfy phone channel into a general agent-comms tool -#+AUTHOR: Craig Jennings & Claude (home project) +#+AUTHOR: Craig Jennings #+DATE: 2026-06-17 * Why this is in rulesets' inbox diff --git a/docs/design/2026-06-18-triage-intake-phone-push-workflow.org b/docs/design/2026-06-18-triage-intake-phone-push-workflow.org index cd830fb..a0bb416 100644 --- a/docs/design/2026-06-18-triage-intake-phone-push-workflow.org +++ b/docs/design/2026-06-18-triage-intake-phone-push-workflow.org @@ -1,5 +1,5 @@ #+TITLE: Triage Intake Workflow (Engine) -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-05-01 * Summary diff --git a/docs/design/2026-07-13-runtime-portability-inventories.org b/docs/design/2026-07-13-runtime-portability-inventories.org new file mode 100644 index 0000000..b9e8244 --- /dev/null +++ b/docs/design/2026-07-13-runtime-portability-inventories.org @@ -0,0 +1,84 @@ +#+TITLE: Runtime Portability — Hook, MCP, and Memory Inventories +#+AUTHOR: Craig Jennings +#+DATE: 2026-07-13 + +Decision-prep for the generic-agent-runtime arc (spec: [[file:2026-05-28-generic-agent-runtime-spec.org]]). Three of the eight gap child tasks are inventories; this doc is their deliverable. Each section ends with a verdict and any decision left for Craig. + +* Hook parity inventory + +What's wired today (global =.claude/settings.json=, symlinked to =~/.claude/settings.json=): + +- PreToolUse AskUserQuestion hard-deny — blocks the popup-choice tool, forcing inline numbered options per =interaction.md=. Exists because prose alone failed. +- PreCompact =precompact-priorities.sh= — saves priorities before context compaction. +- SessionStart =session-title.sh= (cosmetic title) and =session-clear-resume.sh= (resume-after-/clear plumbing). +- Stop =ai-wrap-teardown.sh= — sentinel-gated session teardown. + +Shipped but unwired (reference copies in =hooks/= + =settings-snippet.json=): =git-commit-confirm.py=, =gh-pr-create-confirm.py=, =destructive-bash-confirm.py=. Not active under Claude either, so they impose no parity requirement today. + +Language bundles wire PostToolUse validators per edit (=validate-bash.sh=, =validate-el.sh=, =validate-go.sh=, plus python/typescript equivalents) — and every bundle also ships the same validation as a =githooks/pre-commit=. + +Per-hook portability verdicts: + +- AskUserQuestion deny: moot off Claude. The popup tool is Claude Code's; Codex-style and local harnesses have no equivalent tool to deny. No port needed. +- PostToolUse validators: covered off Claude. The bundles' git pre-commit hooks run the same validators, so enforcement survives at commit time instead of edit time. Acceptable downgrade, no work needed. +- SessionStart clear-resume: not portable as-is; /clear semantics are Claude's. This is the same surface as the "session plumbing per runtime" child task — resolve it there, not as a hook port. +- session-title: cosmetic, skip on other runtimes. +- PreCompact priority-save: genuine gap. Codex-style harnesses compact without a hook point. Downgrade is prose (the session-context write triggers in protocols.org already say "save before compaction"), which is weaker but is also the pre-2026 Claude behavior. Accept prose, or shorten sessions on those runtimes. +- Stop wrap-teardown: partial gap. Codex has a turn-completion notify command that could run the same sentinel-gated script; local harnesses vary. Port the script trigger where a hook point exists; where none does, teardown becomes a manual step in the wrap phrase. + +Verdict: only PreCompact and Stop carry real porting work, and both have acceptable degraded modes. Nothing here blocks a pilot. + +* MCP portability check + +Locally-configured servers (user scope, =~/.claude.json=): linear, notion, figma, slack-deepsat, google-calendar, google-docs-personal, google-docs-work, drawio, google-keep. No project-scope servers anywhere. All are ordinary MCP server definitions, portable to any MCP-speaking harness (Codex CLI reads them from its config; most local harnesses that matter speak MCP now). Port = translate the server blocks + carry the auth material. Mechanical. + +claude.ai-managed connectors (session-injected, not in local config): Gmail, and the claude.ai variants of Calendar/Drive. These do not travel. Overlap analysis: calendar and docs are already covered by the locally-configured servers; Gmail's workflow-preferred path is already =cmail-action= (local CLI), so the managed Gmail connector is convenience, not dependency. + +Named gap: signal-mcp. protocols.org "Paging Craig" names it as the only supported away-from-desk page path, and it is not in any local config — it lives claude.ai-side. Off Claude there is currently no page channel at all. This lands squarely on the open Signal-pager task ([#C], todo.org): the signal-cli runbook that task produces would be the runtime-neutral paging path. Recommend noting runtime-portability as an added motivation on that task. + +Verdict: portable except paging; paging's fix is already a filed task. + +* Memory story for non-Claude agents + +Claude auto-memory (=~/.claude/projects/<enc>/memory/=, MEMORY.md index) is harness-owned: written by Claude's memory tooling, loaded by its session start. A non-Claude agent should neither read nor write it. + +The designed cross-agent store already exists: the org-roam KB (=knowledge-base.md= — reading is plain rg over files, writing is one node per fact, both runtime-neutral). Project state lives in file artifacts every agent reads anyway: todo.org, notes.org, session anchors, docs/. + +One wording gap: knowledge-base.md's "Capture, then promote" section names harness memory as the capture layer. For a non-Claude agent the capture layer is the session log itself. Recommended one-sentence addition there: an agent without harness memory captures into its session log and promotes from it at wrap-up. Shared-asset edit, needs approval. + +Verdict: no build. One approved sentence in knowledge-base.md closes it. + +* Skill and command parity + +What rulesets ships: 11 skills (SKILL.md, model-invokable — add-tests, debug, five-whys, flush, frontend-design, pairwise-tests, playwright-js, playwright-py, review-code, root-cause-trace, voice) and 18 commands (plain .md prompts under =.claude/commands/=, user-invoked). Every body is markdown instructions; nothing executable lives in the registration layer. + +The load-bearing observation: a =/name= reference is just a pointer to a file on disk. Any harness that reads files can execute all 29 artifacts through one resolution rule in its bootstrap entry file: "a skill or command reference (=/voice=, =/review-code=, =/brainstorm=) resolves to =~/.claude/skills/<name>/SKILL.md= or =~/.claude/commands/<name>.md= — read the file and follow it." That single sentence, emitted by the instruction-bootstrap install target, makes the whole library portable with zero per-skill work. commits.md's existing "/voice unavailable — walk the patterns inline" fallback generalizes the same way. + +What that rule does not carry: + +- Auto-invocation. Claude Code triggers skills from their descriptions; other harnesses won't. The high-value auto-triggers (voice and review-code inside the publish flow) don't actually need it — commits.md invokes them by name at fixed flow points, and the resolution rule covers a by-name invocation. The convenience triggers (debug, frontend-design firing on topic match) degrade to on-request. Acceptable. +- Native slash registration. Codex-style harnesses have their own custom-prompt mechanism; the 18 commands could also be registered there for ergonomics (an optional install nicety, not a requirement — the resolution rule already works). +- flush. Its mechanics are Claude Code's (/clear, the self-inject resume hook). Explicitly not ported; the session-plumbing child owns that surface. +- Harness built-ins (code-review ultra, plan mode, artifacts). Not rulesets' to port; flows that name them need per-runtime alternatives or graceful absence, same as today when they're unavailable. + +Verdict: no per-skill porting matrix needed. One resolution sentence in the bootstrap entry file (instruction-bootstrap child), optional native registration as an install nicety, flush explicitly excluded. + +* Session plumbing per runtime + +Four pieces, examined: + +- =session-context-path= is already runtime-aware by design — the =AI_AGENT_ID= shape carries a runtime segment (=host.project.runtime.epoch=) and the resolver is pure bash. Nothing to do. +- The anchor cycle (suspend entry, startup's interrupted-session recovery, wrap-up's archive rename) is plain files driven by workflow prose. Any agent that reads protocols.org executes it identically. Nothing to do. +- =self-inject.sh= is harness-agnostic: it types arbitrary strings into a tmux pane. The Claude-bound part is only the *payload* — "/clear" plus a resume line that leans on the SessionStart hook. A Codex auto-flush is a payload variant: the clear command differs (confirm the exact one when wiring) and the second injected line must itself carry the resume instruction ("read .ai/session-context.org and resume") since no hook fires. Arguably simpler than the Claude path. +- =session-clear-resume.sh= (the SessionStart hook) is Claude-only, and the payload variant above makes it unnecessary off Claude. + +Verdict: no build. The flush *skill* stays Claude-official; a codex auto-flush is a documented payload variant to wire the day a codex session actually wants it, not before. + +* Decisions for Craig + +All four approved by Craig, 2026-07-13: + +1. PreCompact downgrade off Claude: prose-only "save before compaction" accepted. +2. Stop-teardown off Claude: port via Codex notify where available, manual teardown elsewhere. +3. Runtime-portability motivation note added to the Signal-pager task. +4. knowledge-base.md capture-layer sentence added (non-Claude runtimes capture into the session log and promote from there). diff --git a/docs/design/2026-07-14-sentry-workflow-proposal.org b/docs/design/2026-07-14-sentry-workflow-proposal.org new file mode 100644 index 0000000..d29ceb8 --- /dev/null +++ b/docs/design/2026-07-14-sentry-workflow-proposal.org @@ -0,0 +1,150 @@ +#+TITLE: Sentry Workflow — Proposal for rulesets +#+AUTHOR: Craig Jennings +#+DATE: 2026-07-13 + +* Status +Proposal from the work project to the rulesets session, for implementation as a +general (shared-layer) workflow: =.ai/workflows/sentry.org= plus a small +supporting script or two under =.ai/scripts/=. + +* Problem + +An agent session left running overnight can keep a project clean and healthy on +a cadence, but the current pieces don't coordinate. Two gaps: + +1. *No agent-vs-agent collision guard on shared public files.* =capture-guard= + only detects Emacs org-capture buffers (the human case) on one target file, + and it's advisory, not a lock. There is no flock anywhere in =.ai/scripts/=. + Multiple sessions (several projects, sometimes both daily drivers) can + read-modify-write =~/org/roam/inbox.org= and the =agents/= KB nodes at once. + The only coordination is the git pull/commit/push discipline in + knowledge-base.md, which is optimistic — a pull/push race forks a + =*sync-conflict*= file, and the KB search already globs those out, so we know + the forks happen and get dropped. + +2. *No single serialized runner.* Firing inbox-zero and triage-intake as + separate hourly crons means they can overlap each other and other agents. + +Sentry is one supervisor loop that runs a fixed sequence of hygiene passes +serially, holds locks so it never collides with itself or other agents, and +checkpoints aggressively for crash recovery. + +* What sentry is + +A single workflow, driven by one interactive =/loop= (one cron), that on each +fire walks an ordered list of passes inside a single turn. It replaces the +per-task crons. Design invariants: + +- *Serial.* Passes run one at a time, in order. They never overlap each other. +- *Single-runner lock.* Sentry takes a project-local flock + (=.ai/.sentry.lock=) at entry. If a prior fire is still running, this fire + skips instead of doubling up. +- *Shared-file lock.* Any pass that writes a public roam file first acquires a + roam-write lock (flock on =~/org/roam/.roam-write.lock=) held across the whole + read-modify-write-commit-push cycle, so it serializes against other agents on + the same host. =capture-guard= still runs underneath for the human case. (The + roam-write lock is new; it's the general guard the dropped KB-guard note would + have proposed, folded in here.) +- *Non-destructive by default.* Read-only and idempotent-filing passes run + fully. Anything destructive or judgment-heavy (triage trash/mark-read, task + regrades, spec flips) queues its proposal into the digest for morning + approval, never fires unattended. +- *Idempotent + isolated.* Every pass is safe to repeat. A pass that errors + doesn't abort the rest; its failure is a loud banner in the digest. + +* Crash-recovery spine (applies to every pass) + +Crash recovery has been a live consideration. Two enforced steps wrap each pass: + +1. *Session-context between passes.* Before the next pass starts, the current + pass appends a dated entry to =.ai/session-context.org= recording what it + did. A crash mid-sequence leaves a readable "got this far" trail, so the next + session resumes without re-deriving state. +2. *Local commit, no push, on any disk change.* Every pass that writes to disk + gets its own =chore(sentry): <pass> — <what changed>= commit on the current + branch, unpushed. A crash loses at most one pass. Craig reviews the stack in + the morning and pushes deliberately. Conventional messages, no AI + attribution. (Open decision: commit on the current branch vs a dedicated + =sentry/<date>= branch — see Open decisions.) + +The unit per pass is therefore: do the work → log to session-context → commit. +That triplet is the recovery boundary. + +* Passes (v1 — all ten, plus the first-run KB promotion) + +Ordered so read-only/pull passes precede writes, and the heaviest external +sweep sits mid-run: + +1. *Roam pull* — =git -C ~/org/roam pull --ff-only= so later reads are fresh. + Read-only. +2. *Inbox zero* — inbox.org roam mode. Route roam-inbox items this project + owns. Acquires the roam-write lock for the inbox edit. +3. *Triage intake* — triage-intake.org. External-accounts sweep, classify, + file Action tasks. Destructive actions (trash/mark-read) queue for morning; + =mbsync -a= and the sentinel advance run. +4. *Todo cleanup* — clean-todo.org + todo-cleanup.el + lint-org.el. Convert + level-3 done subtasks to dated logs, archive done subtrees, flag lint. +5. *Task audit* — task-audit.org. Re-check =:solo:=/=:quick:= tags, + priority-scheme conformance, bug severity×frequency grades, stale =DOING= + specs with closed parents, blocked/blocker reciprocity. Regrades queue for + morning. +6. *Working-files hygiene* — flag =working/<slug>/= dirs whose task is DONE but + not filed to =assets/=. Report only; filing is a morning decision. +7. *Spec status board* — the docs-lifecycle grep. Flag =DOING= specs with + closed parents, or specs stuck in =DRAFT=. +8. *Link integrity* — lint-org broken =file:= links across todo/notes/prep. +9. *Git health* — uncommitted drift, unpushed commits, stale local branches, + whether main is behind origin. +10. *Prep + symlink freshness* — verify tomorrow's prep and both symlinks + resolve. +11. *KB lesson promotion (first run of the session, and thereafter as new + lessons accrue)* — promote recent durable lessons out of the fast capture + layer. CLASSIFICATION-GATED: + - Personal project → write to the roam KB as =agents/= nodes (pull, lock, + write, commit, push), per knowledge-base.md. + - Work / denylisted project → NEVER touch roam. Promote to the project's own + durable store (e.g. =deepsat/knowledge.org=) instead, and emit the + one-line refusal note from the knowledge-base.md refusal contract so + nothing is lost silently. + +* Digest output + +One consolidated, timestamped summary per fire: run time first, then per-pass +one-liners (what it found / did), the morning-approval queue (proposed +trash/mark-read, regrades, files-to-move), and any failures as loud banners at +the top. Delta-only where a pass supports it (triage): a pass with nothing to +report is one line. + +* Cadence + +One hourly sentry to start (passes short-circuit fast when there's nothing to +do). A slower evening interval (every 2–3 hours) is a config knob if hourly +proves noisy. + +* Open decisions for rulesets + +1. *Commit target* — per-pass commits on the current branch (simplest, matches + "commit but don't push") vs a dedicated =sentry/<date>= branch (keeps the + working branch clean, costs a branch dance). Proposal leans current branch. +2. *Roam-write lock scope* — host-local flock only (solves same-host + multi-agent, the common case) vs also hardening roam-sync to surface + cross-host conflicts loudly. Proposal: ship the flock now, note the + cross-host limit, treat roam-sync conflict-surfacing as a separate follow-up. +3. *Interval default* — hourly vs evening-only. Proposal: hourly, config knob. +4. *Which passes gate on a green tree* — e.g. skip todo-cleanup commits if the + project suite is red, matching inbox monitor-mode's clean-tree gate. + +* Cover note from sender (work, 2026-07-14 00:02) + +Intro for the sentry-proposal.org file just sent. This is a NEW general workflow proposal (.ai/workflows/sentry.org + supporting scripts under .ai/scripts/), not an edit to an existing synced file. + +What it is: one serialized supervisor loop that runs a project's evening hygiene passes on a cadence, with locks so it never collides with itself or with other agents. It replaces firing inbox-zero + triage-intake as separate crons. + +Why now: an agent left running overnight can keep a project clean, but the current pieces don't coordinate. capture-guard only handles the Emacs-capture case on one file and holds no lock; there is no flock anywhere in .ai/scripts/; and separate crons let inbox-zero and triage overlap each other and other agents on shared roam files, which already fork *sync-conflict* files that the KB search globs out. + +Three design points worth your judgment: +1. Crash-recovery spine on every pass (crash recovery has been a live concern): enforce a session-context.org update BETWEEN passes, and a local commit (no push) after any disk change, so a crash loses at most one pass. +2. A new roam-write flock (~/org/roam/.roam-write.lock) as the shared-file guard. This absorbs a separate 'guard all public KB files' note we considered and dropped. The lock is the better home for that concern than a bespoke per-file guard. +3. The KB lesson-promotion pass is classification-gated: personal projects promote to roam agents/ nodes; a denylisted work project promotes to its own local store (deepsat/knowledge.org) and never touches roam, per knowledge-base.md's refusal contract. + +Companion files to reconcile: knowledge-base.md (the gated promotion + refusal contract), inbox.org (roam mode + the new lock), triage-intake.org (runs as a sentry pass; keep its own triggers), capture-guard (becomes one layer under the roam-write lock, not the whole guard), and clean-todo.org / task-audit.org / lint-org.el (invoked as passes). Open decisions for you are listed at the bottom of the proposal file: commit target, lock scope, interval default, and which passes gate on a green tree. diff --git a/docs/design/2026-07-15-subproject-pattern-proposal.org b/docs/design/2026-07-15-subproject-pattern-proposal.org new file mode 100644 index 0000000..61ade18 --- /dev/null +++ b/docs/design/2026-07-15-subproject-pattern-proposal.org @@ -0,0 +1,22 @@ +#+TITLE: Proposal: promote the "subproject" pattern into the claude-r +#+SOURCE: from home +#+DATE: 2026-07-15 23:24:07 -0500 + +Proposal: promote the "subproject" pattern into the claude-rules layer. + +WHAT WE DID (in home, 2026-07-15) +We named and built a pattern for what happens when a standalone project is consolidated into another project. home absorbed nine former projects on 2026-06-11 (danneel, finances, jr-estate, kit, health, documents, clipper, elibrary, philosophy). We now call each folded-in unit a "subproject": a former standalone project that lives as a subdirectory, shares the parent's .ai/ scope (one session, one toolchain), but keeps its own files, assets, and history self-contained under its subdir. + +The pattern has four parts: +1. Vocabulary — parent project, subproject, consolidation (the process), subproject archiving. Terminology so we can talk precisely about where a subproject's documents/assets live and archive them cleanly. +2. Subproject brief — a read-first orientation doc at <subproject>/<subproject>-brief.org (situation, current state, reading list, and for deep ones cast/timeline/key-facts-with-verify-flags). The parent's notes.org holds a thin index + a read-first rule + pointers to urgent items only, never a copy of subproject substance ("one fact, one home"). +3. Lifecycle criteria — when to CREATE a subproject (consolidate: domain fit, low independent cadence, shared-scope safety re: privacy/remote posture, no independent collaborators), and when/how to ARCHIVE one cleanly (terminal state -> move the self-contained subdir to <parent>/archive/, reconcile pointers/links/tags, manifest note). +4. Instrumentation — a use/miss log, metrics weighted toward Craig-caught errors over self-report, review folded into task-audit, and a falsifiable kill criterion. + +WHY IT'S GENERAL (not just a home thing) +Consolidating a project into another drops what the former project's own startup used to auto-load (its full depth), so sessions start cold and re-read files. That failure recurs whenever ANY project folds into another. The pattern also gives consolidation and archiving a named, repeatable shape, and the self-contained-subdir rule is what makes archiving a move-plus-pointer-reconcile instead of an excavation. + +THE PROPOSAL +Promote this to a rulesets claude-rule (e.g. claude-rules/subprojects.md): the vocabulary, the brief convention, the parent-vs-subproject content criterion, and the create/archive criteria. Any parent/hub project would then inherit it. home's .ai/subprojects.org (attached next) becomes the local instance/index under the general rule. It's a companion to working-files.md (in-progress artifact layout) and docs-lifecycle.md (formal-doc lifecycle) — same family, different scope: this one governs whole-subproject orientation and lifecycle. + +Attached: the home instance .ai/subprojects.org as the reference draft to adapt. Apply your value gate; this is a proposal, not a synced-file edit. diff --git a/docs/design/2026-07-15-subprojects-convention-home-instance.org b/docs/design/2026-07-15-subprojects-convention-home-instance.org new file mode 100644 index 0000000..a1c4397 --- /dev/null +++ b/docs/design/2026-07-15-subprojects-convention-home-instance.org @@ -0,0 +1,282 @@ +#+TITLE: Subprojects — Convention +#+AUTHOR: Craig Jennings +#+DATE: 2026-07-15 + +# The reference spec for the subproject pattern: what a subproject is, when to +# create one (consolidation), how to keep it legible (the brief + content +# split), and how to archive one cleanly. Not read every session — the +# always-on parts (the subproject index + the read-first rule) live in +# notes.org and point here. Read this when consolidating a project, creating +# or maintaining a subproject brief, or archiving a subproject. + +* Vocabulary (say it the same way every time) + +The terminology is the point — it's what lets us talk precisely about where a +subproject's documents and assets live, and archive them cleanly. + +- *Parent project* (a.k.a. hub) — a top-level project that owns an =.ai/= + scope: its own protocols, session cadence, memory, inbox, todo. home, work, + rulesets, .emacs.d are parent projects. Reached by launching an agent + session in its directory. +- *Subproject* — a formerly-standalone project that has been *consolidated* + into a parent and now lives as a subdirectory of it (=danneel/=, + =finances/=, =kit/= …). A subproject shares the parent's =.ai/= tooling and + session — it is not a separate agent scope — but keeps its own working + files, assets, notes, and history self-contained under its one subdirectory. +- *Consolidation* — the process of turning a standalone project into a + subproject of a parent. (home's nine subprojects were consolidated on + 2026-06-11.) The inverse — promoting a subproject back to a standalone + project — is rare but allowed; it's just consolidation run backward. +- *Subproject archiving* — retiring a completed or abandoned subproject + cleanly. Because a subproject is self-contained under one subdirectory, + archiving is a move plus a pointer reconcile, not an excavation. +- *Subproject brief* — the read-first orientation doc for one subproject, at + =<subproject>/<subproject>-brief.org=. + +The cross-project boundary rule (=claude-rules/cross-project.md=) governs +*parent* projects, which are separate =.ai/= scopes. It does *not* apply +between a parent and its subprojects — they share one scope. + +* Why the pattern exists + +As a standalone project, each of these units auto-loaded its own notes.org — +its full depth — at every startup, so recall felt instant. Consolidated into a +parent, a subproject is one compressed section competing with the others, and +the parent's startup surfaces hub-wide state, not any one subproject's depth. +So a session starts cold on a given subproject and re-reads several files on +demand. This convention restores fast, accurate orientation without +re-bloating what loads every session — and gives consolidation and archiving a +named, repeatable shape. + +* Document & asset location (what makes clean archiving possible) + +Everything for a subproject lives under =<subproject>/= — its =notes.org=, +=assets/=, working files, event log, briefs. Links *within* a subproject are +relative to the subproject dir, so the whole tree can move without breaking +them. The parent's own top-level =assets/= is for parent-level material only, +never a subproject's. This self-containment is exactly what lets archiving be +a single =mv= plus a pointer reconcile. Keep it strict: a subproject's asset +in the parent's shared =assets/= is a future broken link. + +* The two layers (parent vs subproject) + +- *Always-on layer* — the parent's notes.org. Read at every startup, so every + line is a tax paid whether or not it's relevant today. Holds only what must + be seen session-independently. +- *On-demand layer* — the subproject brief + the subproject's notes.org. + Loaded only when working that subproject. Holds the depth. + +The whole design is: sort content by *when it's needed*, keep the always-on +layer thin, put the depth one open away. + +* Creating a subproject (consolidation criteria) + +Consolidate a standalone project into a parent as a subproject when *all* hold: + +1. *Domain fit* — the project is a facet of a domain the parent already owns + (danneel is home life-admin; finances is home). +2. *Low independent cadence* — it no longer needs its own session rhythm. + You'd rather reach it from the parent than launch it standalone, and its + standalone startup/sync/session overhead now outweighs the isolation. +3. *Shared-scope safety* — it does not need a git/remote/visibility posture + distinct from the parent. A public code project, a team repo, or anything + with its own CI or gitignore-privacy posture *stays standalone* — folding + it would break the parent's privacy model (see + =claude-rules/git-hosting-privacy-model= and the gitignore-vs-track + decision). Personal/documentation units fold; code-with-a-public-remote + does not. +4. *No independent collaborators* depend on it as a standalone deliverable. + +Keep it standalone when any of those fail: active independent development, its +own team or remote, a distinct visibility/security posture, or activity high +enough that competing in the parent's hub would bury it. + +*The consolidation act itself* (mechanics, mirroring home's 2026-06-11 fold): +move the project tree under the parent as =<parent>/<subproject>/=; import its +tasks into the parent's todo with an area =:tag:= and a =:MIGRATED_FROM:= +property; fold its live reminders/decisions into the parent's notes as +pointers (see the content split); build the subproject brief; record the fold +in a consolidation manifest (=docs/consolidation-manifest-<subproject>.org=). +Retire the source to =~/projects/.retired/<name>= once parity is verified. + +* The subproject brief + +** Path (fixed — discovery is mechanical) + +=<subproject>/<subproject>-brief.org= (=danneel/danneel-brief.org=, +=finances/finances-brief.org=). The subproject name is the directory name, so +the brief name follows for free. One predictable name means the read-first +rule never has to hunt; put descriptive richness *inside*, not in the filename. + +** Spine (scales from light to deep) + +Required in every brief, even a fifteen-line one: +- *One-paragraph situation* — what this subproject is, in plain language. +- *Current state / open threads* — the live stuff. The only high-churn + section; everything else is stable. +- *Reading list* — the subproject's deeper files and what each holds. The + brief *points into* notes.org and the rest; it does not copy them. + +Add only where the subproject warrants it (danneel, jr-estate, finances earn +all of these; elibrary or documents may earn none): +- Cast / contacts. +- Timeline (for matters with history). +- Key facts / numbers, with explicit *verify* flags anywhere the underlying + record disagrees with itself. A brief that asserts a wrong number is worse + than no brief — flag, don't guess. + +** Read-first rule (the behavior) + +When a session's work targets a subproject — a file under =<subproject>/=, its +=:tag:=, or Craig naming the topic — open =<subproject>/<subproject>-brief.org= +*first*, before touching anything else in it. If the subproject has no brief +yet, creating it is the first step of the work (lazy backfill), and the same +pass reconciles that subproject's parent-vs-subproject content. + +* Parent-vs-subproject content criterion + +The test for any piece of content: *if next session I'm working a different +subproject, do I still need to see this?* Three bins: + +1. *Yes, and it's a fact or policy* → hub-level → the parent's notes.org. + Machine names, mail policy, calendar access, the hub map, the subproject + index. +2. *Yes, but only because it's urgent* — a deadline or blocking status that + would be missed if it only lived where I might not look this week → the + parent holds a *one-line pointer* (item + date + "see the subproject + brief"); the substance stays in the subproject. +3. *No* → the subproject. Working depth: contacts, contract facts, case + theory, evidence, document index, and any reminder that isn't time-critical. + +One hard rule over both axes: *one fact, one home.* The parent never copies +subproject substance — it points. The parent holds the trigger; the subproject +holds the truth. Every parent↔subproject duplicate is a second surface that +goes stale. + +* Archiving a subproject (archiving criteria + clean process) + +Archive a subproject when it reaches a *terminal* state: +- *Complete* — the work is done and closed (a settlement signed, a probate + closed, a trip finished): no live threads, no open tasks, nothing + time-sensitive in the parent still pointing at it; or +- *Abandoned / superseded* — dropped, or replaced by other work. + +Clean archive process (self-containment is what makes each step a one-liner): +1. Close or settle its tasks in the parent's todo (DONE / CANCELLED), per the + todo completion rules. +2. Move the whole subproject tree to the parent's archive: + =<parent>/archive/<subproject>/= (keep it intact — archiving is reversible, + not deletion). +3. Update inbound =file:= links that pointed into it (grep first, per the + keep-links-current rule). +4. Move or retire its brief with it, and drop its row from the active + subproject index (or mark it =archived= there). +5. Strip its pointers/reminders from the parent's always-on layer, and retire + its =:tag:= from active use. +6. Record the archive in a manifest note + (=docs/archive-manifest-<subproject>.org=): why, when, where it moved, + what links were repointed. Mirrors the consolidation manifest. + +The =:LAST_UPDATED:= on a brief plus "no open tasks under the tag" is the +signal a subproject is a candidate for archiving — surfaced at the review +cadence, never auto-archived. + +* Freshness + +- *Wrap-up hook*: at wrap-up, for each subproject touched this session, refresh + its brief's Current-state section and bump its =:LAST_UPDATED:= date. Stable + sections rarely change, so this is cheap. +- *Staleness nudge*: a brief whose =:LAST_UPDATED:= is old while its subproject + was recently active is worth surfacing at startup. + +* Self-improvement loop + +Every real use emits a signal, and the signal is *captured*, not silently +acted on: +- Brief answers the question → *hit*. +- Brief is missing something, stale, or wrong → *miss*, logged in + [[file:subprojects-log.org][subprojects-log.org]]. +- Each miss drives *two* updates: *local* (fix that brief) and, if the same + kind of miss recurs across subprojects, *structural* (fix this spine/spec, + not just the one file). The structural half is the actual self-improvement — + grow-and-refine from real use rather than rewriting from scratch (the ACE + idea behind the =codify= skill). + +* Metrics + +Split by who can trust them. + +*Process signals (I observe, cheaply):* +- *Orientation cost* — files/tool-calls before my first correct action in a + subproject. Target: ~1 (the brief), occasionally a second deep dive. +- *Brief hit rate* — fraction of subproject questions answered from the brief + alone. Should climb. +- *Freshness gap* — days between =:LAST_UPDATED:= and last subproject activity. + Target ~0. + +*Outcome signals (Craig is ground truth — weight these over the above):* +- *Craig-caught errors* — the real quality bar. Target: trend to zero. If + briefs work, Craig stops catching me. +- *Felt speed* — did dropping into the subproject feel fast and right. + +*Honesty caveat.* Most process signals are self-observed, and self-report is +biased toward "hit." So the external signals (Craig-caught errors, felt speed) +outrank mine, and the log is auditable so Craig can spot-check my self-grades. +The instrumentation must stay lighter than the briefs, or it rots like an +unmaintained brief. Small N, no clean A/B — this is directional evidence, not +proof. + +* Review cadence + +Fold a "subprojects health" check into the existing task-audit cadence: read +the log, look at the hit-rate / correction / freshness trends, flag any +subproject that's gone terminal (archive candidate), and check the +parent-vs-subproject criterion — anywhere the parent holds a copy where it +should hold a pointer. Decide continue / adjust / kill for the pattern itself. + +* Kill criteria (falsifiable) + +After ~6-8 subproject-sessions across ≥3 subprojects, drop or rework the +pattern if: +- the correction rate isn't falling, or Craig is still catching errors; or +- the freshness gap keeps reopening despite the wrap-up hook; or +- maintenance costs more time than the orientation it saves. + +Success is the mirror: orientation down to ~1 read, Craig catching ~nothing, +briefs current, and I'm right fast in any subproject cold. + +* Rollout + +- Define the convention (this file) + the index scaffold in notes.org. Done + 2026-07-15. +- *Lazy backfill*: the next session that touches a subproject with no brief + creates the brief as its first step — cost paid exactly when it's used. +- *Reconcile in the same pass*: building a subproject's brief includes + reconciling its parent-vs-subproject content — push substance down, leave + pointers up, drop dead reminders. +- *Promotion*: this pattern is home-instanced but general. Consolidation into + subprojects will recur whenever any project folds into another, so this is a + candidate to promote into the rulesets rules layer (proposed to rulesets + 2026-07-15). + +* Adoption status + +| Subproject | Brief exists | Reconciled | Notes | +|------------+--------------+------------+---------------------------------------| +| danneel | yes | yes | First worked instance, 2026-07-15. | +|------------+--------------+------------+---------------------------------------| +| clipper | no | no | Lazy backfill on next touch. | +|------------+--------------+------------+---------------------------------------| +| documents | no | no | Lazy backfill on next touch. | +|------------+--------------+------------+---------------------------------------| +| elibrary | no | no | No notes.org yet; lightest subproject.| +|------------+--------------+------------+---------------------------------------| +| finances | no | no | Deep; earns the full spine. | +|------------+--------------+------------+---------------------------------------| +| health | no | no | Has its own notes.org. | +|------------+--------------+------------+---------------------------------------| +| jr-estate | no | no | Deep; earns the full spine. | +|------------+--------------+------------+---------------------------------------| +| kit | no | no | Lazy backfill on next touch. | +|------------+--------------+------------+---------------------------------------| +| philosophy | no | no | Lazy backfill on next touch. | diff --git a/docs/design/2026-07-16-polyglot-bundle-collision.txt b/docs/design/2026-07-16-polyglot-bundle-collision.txt new file mode 100644 index 0000000..b7e1257 --- /dev/null +++ b/docs/design/2026-07-16-polyglot-bundle-collision.txt @@ -0,0 +1,30 @@ +Proposal: language bundles collide on coverage-makefile.txt in a polyglot project, and the second install loses silently. + +Context: scaffolded a new project (clock-panel) on 2026-07-16 with both the python and typescript bundles installed into the same project. + +What happened. The second install printed: + + [skip] coverage-makefile.txt already exists (use FORCE=1 to overwrite) + +Both bundles ship a file at that same path. Python installed first and won, so the project ended up with Python's coverage fragment only. The TypeScript one (c8, Istanbul json-summary) never landed. The line reads like routine idempotence, not a dropped deliverable, so it's easy to skim past. I only caught it by checking which targets were actually in the file. + +The workaround is bad. FORCE=1 gets the TypeScript fragment but overwrites the Python one, so you can't have both without renaming by hand between installs. FORCE=1 also re-seeds CLAUDE.md, which is fine on a fresh project and destructive on a customized one. What I did in clock-panel: install python, mv coverage-makefile.txt coverage-makefile-python.txt, install typescript FORCE=1, mv coverage-makefile.txt coverage-makefile-typescript.txt. Both fragments now coexist under language-suffixed names. + +The deeper problem, which the filename collision only hints at: both fragments define targets with the SAME names. + + coverage: + coverage-summary: + +So even with both files present, a polyglot project can't copy both into one Makefile. It gets duplicate targets. The fragments assume they're the only language in the project. Renaming the files doesn't fix that, it just makes both sets of instructions visible while leaving the conflict for whoever pastes them. + +Options, in the order I'd weigh them: + +Suffix the shipped filename per bundle (coverage-makefile-python.txt, coverage-makefile-typescript.txt) so installs never collide. Cheap, and it makes the multi-bundle case work by default. It leaves the target-name conflict. + +Namespace the targets too (coverage-python / coverage-typescript, with a coverage target that runs both). Fixes the real problem for polyglot projects. Bigger change, and it makes single-language projects type a longer target name unless there's an alias. + +At minimum, make the skip line loud when the skipped file came from a DIFFERENT bundle than the one that wrote it. A skip that means "already yours" and a skip that means "another language's file is here and yours is being dropped" currently look identical. + +Worth deciding whether polyglot projects are a supported case at all. If they are, the bundles need a collision story. If they aren't, install-lang could say so when it detects a second bundle going into a project that already has one, rather than half-installing. + +Also flagged separately in a note from home today: the same install-lang run is where I'd look for other per-bundle files that could collide on a shared filename. I only checked coverage-makefile.txt. diff --git a/docs/design/task-review.org b/docs/design/task-review.org index 6c6dac7..c9ae023 100644 --- a/docs/design/task-review.org +++ b/docs/design/task-review.org @@ -1,5 +1,5 @@ #+TITLE: Design: Daily Task-Review Habit -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-05-16 #+OPTIONS: toc:nil num:nil diff --git a/docs/specs/2026-06-16-autonomous-batch-execution-spec.org b/docs/specs/2026-06-16-autonomous-batch-execution-spec.org index 23fc574..fe3458b 100644 --- a/docs/specs/2026-06-16-autonomous-batch-execution-spec.org +++ b/docs/specs/2026-06-16-autonomous-batch-execution-spec.org @@ -1,5 +1,5 @@ #+TITLE: Autonomous-Batch Task Execution — Spec -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-06-16 #+TODO: TODO | DONE #+TODO: DRAFT READY DOING | IMPLEMENTED SUPERSEDED CANCELLED diff --git a/docs/specs/2026-06-16-encourage-kb-contribution-spec.org b/docs/specs/2026-06-16-encourage-kb-contribution-spec.org index cfbfe79..3d08ed5 100644 --- a/docs/specs/2026-06-16-encourage-kb-contribution-spec.org +++ b/docs/specs/2026-06-16-encourage-kb-contribution-spec.org @@ -1,5 +1,5 @@ #+TITLE: Encourage Org-Roam KB Contribution Across Workflows — Spec -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-06-16 #+TODO: TODO | DONE #+TODO: DRAFT READY DOING | IMPLEMENTED SUPERSEDED CANCELLED diff --git a/docs/specs/2026-07-01-docs-lifecycle-spec.org b/docs/specs/2026-07-01-docs-lifecycle-spec.org index f5bfe63..91c1603 100644 --- a/docs/specs/2026-07-01-docs-lifecycle-spec.org +++ b/docs/specs/2026-07-01-docs-lifecycle-spec.org @@ -1,5 +1,5 @@ #+TITLE: Docs Lifecycle — Spec -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-07-01 #+TODO: TODO | DONE #+TODO: DRAFT READY DOING | IMPLEMENTED SUPERSEDED CANCELLED diff --git a/docs/specs/2026-07-14-sentry-workflow-spec.org b/docs/specs/2026-07-14-sentry-workflow-spec.org new file mode 100644 index 0000000..fe1c155 --- /dev/null +++ b/docs/specs/2026-07-14-sentry-workflow-spec.org @@ -0,0 +1,275 @@ +#+TITLE: Sentry Workflow — Spec +#+AUTHOR: Craig Jennings +#+DATE: 2026-07-14 +#+TODO: TODO | DONE +#+TODO: DRAFT READY DOING | IMPLEMENTED SUPERSEDED CANCELLED + +* DRAFT sentry workflow +:PROPERTIES: +:ID: f6c51f27-d7a2-4b63-9ff9-5ba005a66dfb +:END: +- 2026-07-14 Tue @ 02:03:28 -0500 — all 12 review findings dispositioned live with Craig and folded into the design; decisions now 10/10; still DRAFT pending Craig's deep read. +- 2026-07-14 Tue @ 00:52:03 -0500 — drafted, all nine design decisions resolved live with Craig during the authoring session. + +* Metadata +| Status | draft | +|----------+------------------------------------------| +| Owner | Craig Jennings | +|----------+------------------------------------------| +| Reviewer | (spec-review, next session) | +|----------+------------------------------------------| +| Related | [[file:../../todo.org][todo.org — sentry build task]] | +|----------+------------------------------------------| + +* Summary + +Sentry is one supervisor workflow that runs a project's hygiene passes serially on a cadence, holds locks so it never collides with itself or other agents, commits its work to a host-suffixed side branch for morning review, and checkpoints between passes so a crash loses at most one pass. It replaces the idea of firing inbox-zero and triage-intake as separate uncoordinated crons. + +* Problem / Context + +An agent session left running overnight can keep a project clean, but the pieces don't coordinate. There is no lock anywhere in .ai/scripts/ (verified 2026-07-14): capture-guard covers only the Emacs org-capture case on one file, and it's advisory. Multiple sessions (several projects, sometimes both daily drivers) can read-modify-write ~/org/roam/inbox.org and the agents/ KB nodes at once. The only coordination is git optimism, and we know it loses: the KB search already globs out *sync-conflict* files because pull/push races fork them. + +Separate crons compound it: inbox-zero and triage-intake fired independently can overlap each other and any interactive session on the same shared files. + +Proposal origin: the work project, 2026-07-13 (preserved at [[file:../design/2026-07-14-sentry-workflow-proposal.org][docs/design/2026-07-14-sentry-workflow-proposal.org]]). This spec supersedes the proposal where they differ: the skeptical review found four conflicts with existing discipline, each resolved as a Decision below. + +* Goals and Non-Goals + +** Goals +- One serialized runner per project: passes never overlap each other, and a fire that finds a prior fire still running skips. +- A shared-file lock that actually serializes same-host agents writing public roam files. +- Crash-lossless to one-pass granularity: session-context entry plus a commit after every disk-writing pass. +- Main stays clean: overnight commits land on a host-suffixed sentry branch so cross-machine pulls and the template sync keep working. +- Nothing destructive fires unattended: trash/mark-read, regrades, and file moves queue for morning approval. +- Portable across projects with zero configuration: passes detect their targets and skip where not applicable. + +** Non-Goals +- No report-only or degraded pass mode. A pass runs fully or it doesn't run (Craig's explicit direction). The morning-approval queue is a permanent division of labor, not a degraded mode. +- No cross-host locking. The lock is host-local; cross-host races stay with roam-sync's abort-loudly rebase. +- No unattended /schedule contract. Sentry runs inside a live session Craig starts, driven by /loop, the same boundary inbox.org draws for auto inbox zero. +- Sentry never runs git against the roam repo. roam-sync remains that repo's only committer. + +** Scope tiers +- v1: the sentry.org engine (ten mechanical passes, entry gates, branch mechanics, digest, the stop-sentry operation), the agent-lock helper script with bats tests, the roam-write lock adopted by every roam write path, companion-file reconciliations (knowledge-base.md write recipe, inbox.org core §5, roam-sync.sh header, triage-intake.org note, wrap-it-up.org active-sentry guard), INDEX.org entry. +- Out of scope: changes to roam-sync.sh behavior; changes to the wrap-up teardown feature. +- vNext (logged to todo.org): cross-host roam conflict surfacing; the KB lesson-promotion pass (blocked on the lesson-detection heuristic task); a fully-unattended /schedule variant if the interactive shape proves too narrow. + +* Design + +** For Craig (the user altitude) + +You type the trigger in a project session ("run sentry", or the /loop line it expands to). Sentry first checks its entry ticket: the project's notes.org must carry :COMMIT_AUTONOMY: yes, or sentry declines to start and names the marker. Then the entry gates run while you're still at the terminal: a dirty tracked tree stops and describes what's dirty, file by file, and offers numbered options (finish the job, stash it, or name changes to roll back); a red test suite stops the same way with an offer to investigate. The loop starts only once both are answered. + +Launching is a handoff — the launch contract: sentry owns the repo until the morning merge. Working in that repo mid-night means saying "stop sentry" first (it cancels the loop and walks the branch and queue disposition), and open Emacs buffers on project files want a revert after launch and again after the merge. "Wrap it up" while sentry is active refuses and points at "stop sentry". + +Each fire (hourly by default) walks the pass list serially and appends one timestamped digest to the session log: failures as banners on top, then one line per pass, then the morning-approval queue. A pass with nothing to do is one line. Anything destructive or judgment-shaped waits in that queue for you. + +In the morning you review the sentry/<date>-<host> branch (a stack of small unpushed chore(sentry) commits), squash-merge it into your working branch, delete it, and answer the approval queue. If a night's branch is still unmerged when sentry next starts, that fire skips and says so. + +** For the implementer + +*The engine.* A synced template workflow, .ai/workflows/sentry.org, driven by /loop <interval> (default hourly). Each fire acquires the single-runner lock, verifies branch state, walks the pass list, writes the digest, commits the spine residue, and releases the lock. The runner re-touches the lock between passes (the heartbeat), so a live run's lock is never older than one pass. Every pass follows one contract: probe, work, session-context entry, then a commit when the pass wrote to disk. The probe asks whether the pass's target exists in this project; when it doesn't, the pass is one skip line and nothing more. The session-context entry precedes the commit so a crash between them still leaves the trail. + +*Branch mechanics.* At loop start (after the entry gates and a fetch-and-ff-only reconcile of the project branch — a diverged branch joins the interactive gate), sentry creates sentry/YYYY-MM-DD-<host> from HEAD and checks it out; the host suffix prevents a same-date collision between the daily drivers. Commits are conventional, one per writing pass: chore(sentry): <pass> — <what changed>. Nothing pushes. At entry, an existing unmerged sentry/* branch means skip-and-note. No second branch stacks on the first. Morning teardown (review, squash-merge, delete) is Craig's, documented in the workflow, never automated. + +*Locks.* One helper script, .ai/scripts/agent-lock, serves both locks. flock is unusable here: every Bash call an agent makes is its own short-lived shell, so a flock dies with the call that acquired it. Instead: atomic mkdir as acquire (create-and-win or fail-and-lose), a metadata file inside recording PID, hostname, and ISO timestamp, and age-based staleness reclaim so a crashed run's lock expires instead of wedging every later fire. Subcommands: acquire <name> [--ttl], release <name>, status <name>. Lock homes: /run/user/<uid>/agent-locks/<name>/ for both locks — the helper owns the path scheme, callers pass names — with ~/.cache/agent-locks/ as the fallback where no runtime dir exists. tmpfs makes a lock host-local by construction, keeps it out of every repo (a lock inside ~/org/roam would ride roam-sync's git add -A to the other machine as a phantom hold), and clears it on reboot. Contention is a bounded wait (~30s), then defer-and-note. With the heartbeat keeping a live lock young, TTLs size to the longest single pass; every reclaim surfaces in the digest. + +*Roam writes.* A pass touching a public roam file acquires the roam-write lock, runs capture-guard --wait (the human-capture layer stays underneath), edits the working tree, triggers roam-sync with systemctl --user start roam-sync.service, and releases the lock. Sentry never runs a git write against ~/org/roam, and pass 1's ff-only pull is its only git read of that repo. The 06-24 one-git-owner rule holds. The lock spans only edit-plus-trigger; roam-sync itself is serialized by systemd (a oneshot unit never runs concurrently with itself). + +*Unattended safety.* With no one at the terminal, any unsafe state (unexpected dirty tree, red suite, lost lock, unmerged prior branch) makes the affected scope (the pass, or the whole fire, whichever the state poisons) skip with one digest line. The next fire retries; if the state persists to morning, the interactive entry gate handles it with Craig present. Skips are never silent and never partial: no pass runs in a reduced form. Two refinements keep the machinery honest: the dirty check excludes the spine set (session-context files, path resolved via session-context-path) so sentry's own bookkeeping can't trip it, and after the second consecutive fire skipped on an unmerged prior branch, sentry sends one persistent desktop notification naming the project and branch, then repeats at most daily — a multi-day stall never stays silent. + +*Pass list (v1), in order, each with its detection probe:* + +1. Roam pull — git -C ~/org/roam pull --ff-only, skipped when the tree is dirty (roam-sync owns that case) or the clone is absent. Read-only; the one narrow exception to "don't touch roam git," pull-only and ff-only, so later reads are fresh. +2. Inbox zero — inbox.org roam mode, run under the engine's no-approvals contract (quick+solo+agreed items execute; shared-asset proposals park; everything lands in the morning queue). Probe: roam clone or project inbox/ exists. +3. Triage intake — triage-intake.org. Probe: triage plugins present. Destructive actions queue. +4. Todo cleanup — clean-todo.org mechanics. Probe: root todo.org. +5. Task audit — task-audit.org. Probe: root todo.org. Regrades queue. +6. Working-files hygiene — flag working/<slug>/ dirs whose task is closed. Probe: working/ exists. Filing queues. +7. Spec status board — the docs-lifecycle grep. Probe: docs/specs/ exists. +8. Link integrity — broken file: links in the project's org files. Probe: lint-org.el present. +9. Git health — drift, unpushed commits, stale branches, main-behind-origin. Probe: .git. +10. Prep + symlink freshness. Probe: the prep dir / symlinks exist (work and home only, in practice). +(KB lesson promotion, the proposal's eleventh pass, is deferred to vNext — see the KB finding and the filed lesson-detection-heuristic task. v1 ships the ten mechanical passes above.) + +*Digest.* Appended per fire to the session-context.org Session Log (which the spine already writes), so it survives a crash, rides the session archive, and is on screen in the running session. The morning-approval queue accumulates under one heading in the same file — each item carries what, why, and the exact command or edit that fires on approval. + +* Alternatives Considered + +** Separate crons per task (status quo direction) +- Good, because each piece stays independently simple. +- Bad, because nothing serializes them: the observed sync-conflict forks are this cost, already paid. +- Bad, because N crons means N session lifecycles to manage instead of one. + +** flock for both locks (as proposed) +- Good, because it's the standard tool and kernel-enforced. +- Bad, because it binds to a living process; agent tool calls are short-lived shells and /loop turns share none, so the lock evaporates on return. Disqualifying. + +** Per-pass commits on the current branch (as proposed) +- Good, because commits sit where the edits apply; no morning merge step. +- Bad, because unpushed commits on main on two daily drivers diverge main by morning, the exact state startup's fast-forward refuses and the template-sync guard blocks on. Disqualifying with two machines. + +** Degraded report-only mode for unsafe states +- Good, because some findings still land when committing is unsafe. +- Bad, because it blurs the contract: a pass that half-ran reads as having run. Craig rejected it outright; skip-and-note is the replacement. + +** Per-project pass manifest instead of detection +- Good, because explicit control over what runs where. +- Bad, because it's one more thing to maintain and forget; detection activates a pass the day its target appears. Rejected for v1. + +* Decisions [10/10] + +** DONE Commit target: host-suffixed sentry branch +- Context: overnight commits must survive two daily drivers that both pull main; unpushed commits on main diverge it across machines, breaking startup fast-forward and the template-sync guard. +- Decision: We will commit each writing pass to sentry/YYYY-MM-DD-<host>, created from HEAD at loop start, never pushed. Morning flow: review, squash-merge, delete. An unmerged prior sentry branch at entry skips the fire. +- Consequences: easier — main's ref stays clean for cross-machine sync; a bad night is discarded by deleting one branch. Harder — a daily merge step; the working tree sits on a non-main branch overnight. + +** DONE Roam-write lock scope: host-local only +- Context: observed conflicts are same-host multi-agent; cross-host races go through roam-sync's rebase, which aborts loudly. +- Decision: We will ship the host-local lock and file cross-host conflict-surfacing as a separate task. +- Consequences: easier — small, testable v1. Harder — a true cross-host race still forks; we accept the rarity. + +** DONE Interval default: hourly, with a knob +- Context: passes short-circuit in seconds when idle; the real cost is digest noise, not compute. +- Decision: We will default to hourly and expose the interval as the /loop argument. +- Consequences: easier — projects stay clean through the night. Harder — a capture-heavy evening produces several small commits; the knob is the escape hatch. + +** DONE Entry gates are interactive, not silent +- Context: Craig types the sentry command in, so the first fire runs with him at the terminal. +- Decision: We will stop at entry on a dirty tracked tree (describe what's dirty; offer finish-the-job / stash / rollback-named-changes) and on a red suite (describe failures; offer to investigate). The loop starts only after both are answered. Untracked files never block. +- Consequences: easier — no guessing about his in-progress work. Harder — sentry can't start unattended from a dirty state; that's the point. + +** DONE No report-only mode; unsafe unattended states skip +- Context: a degraded pass mode blurs whether a pass ran; Craig rejected it explicitly. +- Decision: We will make every pass run fully or not at all. Unattended unsafe states (dirty tree, red suite, lost lock, unmerged prior branch) skip the poisoned scope with one digest line; the next fire retries. The morning-approval queue for destructive/judgment actions stays — it's a permanent contract, not degradation. +- Consequences: easier — a pass line in the digest means it fully ran. Harder — a persistent unsafe state means zero hygiene until morning; accepted. + +** DONE Roam git discipline: roam-sync stays the only committer +- Context: the proposal had sentry commit-and-push roam under the lock; the 06-24 fix made roam-sync the repo's single git owner because the tree is chronically dirty from live captures. +- Decision: We will wrap only edit-plus-trigger in the roam-write lock; sentry never runs git against ~/org/roam (pass 1's ff-only pull is the sole, read-only exception). +- Consequences: easier — one git owner, no mid-rebase states from agents. Harder — an edit lands remotely only when roam-sync fires; the manual trigger closes most of that gap. + +** DONE Lock mechanics: mkdir-atomic helper with staleness reclaim +- Context: flock can't span tool calls (short-lived shells, no shared process across /loop turns). +- Decision: We will build .ai/scripts/agent-lock — atomic mkdir acquire, PID/host/timestamp metadata, age-based reclaim, acquire/release/status subcommands, bats-tested — and use it for both locks. +- Consequences: easier — locks survive between calls and self-clear after crashes. Harder — TTL tuning; a reclaim during a genuinely slow pass is possible, so the helper surfaces every reclaim rather than reclaiming silently. + +** DONE Autonomy gate: :COMMIT_AUTONOMY: yes is the entry ticket +- Context: commits.md gates commits on approval; sentry commits unattended, so it needs standing, per-project authorization. +- Decision: We will have sentry decline to start in any project whose notes.org lacks :COMMIT_AUTONOMY: yes, naming the marker. +- Consequences: easier — running sentry somewhere is a deliberate grant; no half-running mode to reason about. Harder — read-only passes don't run in ungranted projects either; that's acceptable, since launch is one marker away. + +** DONE Pass portability: detection over configuration +- Context: several proposed passes are work/home-specific; run verbatim elsewhere they're vacuous or error. +- Decision: We will open every pass with a cheap existence probe and skip with one digest line when the target is absent. +- Consequences: easier — zero config, passes self-activate when targets appear. Harder — an intentionally-unwanted pass needs a vNext exclusion marker if that ever becomes real. + +** DONE Suite policy: entry run plus conditional fire-end run, no per-pass runs +- Context: the verification discipline requires a full suite run before every commit, but sentry commits per pass, hourly, mostly touching org files the suite doesn't exercise; a per-commit run would turn a seconds-long fire into minutes, all night. +- Decision: We will run the suite once at entry (the green baseline the gates require) and again at fire-end only when a pass modified files outside the org/spine set. No per-pass runs. The deviation is justified by the unpushed branch and the morning review gating everything before push. +- Consequences: easier — idle fires stay cheap and the rare code-touching pass is still caught before its commits age. Harder — a suite break introduced by an org-only edit (possible via fixtures) surfaces at morning review rather than at the offending commit. + +* Review findings [12/12] + +** DONE Overnight working-tree ownership is undefined +Resolved 2026-07-14 (Craig): launch contract with in-place checkout. Launching sentry hands the repo to sentry until the morning merge; reclaiming it mid-night means stopping the loop first. The entry gate (clean tree, Craig present) fronts the handoff, the unattended dirty-skip backstops anything that slips, and the workflow documents the Emacs buffer-revert caveat at launch and after the morning merge. Worktree isolation was rejected because untracked inbox drops exist only in the main tree, which blinds the inbox pass; plumbing commits were rejected as fragile. + +** DONE Roam-write lock guards nothing unless every roam writer acquires it +Resolved 2026-07-14 (Craig): Phase 3 strengthened from notes to mandatory write-path changes. inbox.org core §5 gains acquire/release around the Phase D edit; knowledge-base.md's write recipe gains the same around its write block. Both degrade gracefully when the helper isn't installed (proceed unlocked — today's behavior); an interactive caller finding the lock busy does a bounded wait (~30s) then surfaces to the user rather than proceeding unlocked. + +** DONE Roam lock location gets committed by roam-sync +Resolved 2026-07-14 (Craig): both locks live under /run/user/<uid>/agent-locks/<name>/, with ~/.cache/agent-locks/ as the fallback where no runtime dir exists. The agent-lock helper owns the path scheme; callers pass names, never paths. tmpfs gives host-locality by construction and reboot self-clearing for free. + +** DONE Sentry's own spine writes trip its dirty-tree skip +Resolved 2026-07-14 (Craig), all three parts: the unsafe-state dirty check excludes the spine set (session-context.org / session-context.d/), each fire ends with one digest commit that sweeps accumulated spine writes so read-only fires still leave a clean tree, and the spine path resolves through .ai/scripts/session-context-path so a concurrent agent's anchor is never clobbered. + +** DONE Pass 8 depends on a known data-corrupting bug +Resolved 2026-07-14, mid-review: the lint-org fix landed in 951b6fc — block-type-aware scanning in both helpers, CLI report-only by default with writes behind --fix, and the true corruption path (wrap-org-table's load-time dispatch firing on lint-org's require) guarded to entry-script-only. Pass 8 runs against the fixed linter; the prerequisite is satisfied and no Implementation-phases dependency is needed. + +** DONE Unattended inbox-pass semantics are undefined +Resolved 2026-07-14 (Craig): sentry runs inbox processing under inbox.org's no-approvals contract verbatim — quick+solo+agreed items execute, shared-asset and convention proposals park (prepared diff, VERIFY task, sender reply). Everything the pass did lands in the morning queue, and anything the engine would ask interactively defers to the queue instead of blocking the fire. + +** DONE Per-pass commits skip the pre-commit suite run — undecided convention conflict +Resolved 2026-07-14 (Craig): recorded as a Decision (see Decisions — suite policy). Entry run establishes the green baseline; no per-pass runs; a fire-end suite run fires only when a pass modified files outside the org/spine set, so the rare code-touching pass is caught before its commits age overnight. Justification recorded with both halves of the consequences. + +** DONE KB promotion pass has no defined lesson source +Resolved 2026-07-14 (Craig): pass 11 is cut from v1 and deferred to vNext. KB promotion stays a wrap-up concern — the editorial moment where Craig answers the promotion prompt. An unattended judgment pass writing to the shared KB waits until sentry has quiet weeks behind it AND a designed detection heuristic; the heuristic design is filed in todo.org ([#D] "KB lesson-detection heuristic", which blocks re-adding the pass). + +** DONE Lock contention and reclaim mechanics are half-specified +Resolved 2026-07-14 (Craig): both mechanics adopted. Contention: bounded wait (~30s, capture-guard's --wait shape), then defer — the pass skips with a digest line and the next fire retries. Reclaim: heartbeat refresh — the runner re-touches the lock timestamp between passes, so a live run's lock is never older than one pass and the TTL sizes to the longest single pass (minutes). Every reclaim surfaces in the digest. + +** DONE Wrap-up while the loop is live is undefined +Resolved 2026-07-14 (Craig): wrap-up refuses while sentry is active. It detects the live single-runner lock and stops with "sentry is active — say 'stop sentry' first." The stop-sentry operation is defined in sentry.org and owns the shutdown: cancel the loop, walk the branch disposition (squash-merge now or leave named), walk-or-carry the approval queue. Wrap-up itself gains only the one guard; the shutdown logic lives with sentry. + +** DONE Entry should reconcile the project branch before branching +Resolved 2026-07-14 (Craig): adopted. After the entry gates pass, sentry runs the same fetch-and-ff-only reconcile startup uses, then creates the branch. A diverged branch joins the interactive entry gate (Craig is present at launch) rather than being auto-resolved. + +** DONE Multi-day unmerged branch stalls hygiene silently +Resolved 2026-07-14 (Craig): adopted. After the second consecutive fire skipped for the unmerged-branch reason, sentry sends one persistent desktop notification naming the project and branch ("sentry stalled: <branch> unmerged — merge or delete to resume"), then repeats at most daily. Persistent notify matches the paging convention: it stays on screen until dismissed. + +* Implementation phases + +** Phase 1 — agent-lock helper +.ai/scripts/agent-lock (canonical: claude-templates/.ai/scripts/) with bats tests: acquire/release/status, contention, staleness reclaim, metadata. Tree stays working; nothing calls it yet. + +** Phase 2 — sentry.org engine +The workflow file: entry ticket, interactive gates, branch mechanics, pass runner with the probe→work→log→commit contract, digest + approval queue, skip semantics. INDEX.org entry. Mirror synced. + +** Phase 3 — companion reconciliations +knowledge-base.md write recipe (acquire/release the roam-write lock around the write block; edit-plus-trigger replaces inline pull/commit/push); inbox.org core §5 (acquire/release around the Phase D edit, capture-guard staying as the human layer underneath; graceful degradation when the helper is absent); roam-sync.sh header comment; triage-intake.org note (runs as a sentry pass; own triggers kept); wrap-it-up.org active-sentry guard (refuse and point at "stop sentry"). + +** Phase 4 — verification +make test green; a live trial night on rulesets (ratio): entry gates exercised, one fire observed end to end, morning branch review performed; follow-up tasks filed from what the trial surfaces. + +* Acceptance criteria +- [ ] agent-lock: two concurrent acquires produce exactly one winner; release frees; a stale lock (aged past TTL) is reclaimed with a surfaced note; a busy lock is a bounded wait then defer; heartbeat refresh keeps a live lock young; locks live under the runtime dir (cache fallback), never inside a repo; bats suite green. +- [ ] Sentry declines to start without :COMMIT_AUTONOMY: yes, naming the marker. +- [ ] Entry on a dirty tracked tree stops with the file-by-file description and the three options; untracked files don't trigger it. +- [ ] A fire on a clean project produces one digest with every pass either run or skipped-with-reason; no other output shape exists. +- [ ] Writing passes commit individually to sentry/<date>-<host>; main's ref is untouched; nothing is pushed. +- [ ] A roam-writing pass acquires the roam-write lock, passes capture-guard, edits, triggers roam-sync, releases, and never runs git write commands against ~/org/roam. +- [ ] An unmerged prior sentry branch at entry causes a skip-and-note, not a second branch. +- [ ] Destructive/judgment actions appear only in the approval queue, never executed unattended. +- [ ] On a project missing a pass target (no todo.org, no triage plugins), the pass skips with one line and the rest run. +- [ ] Entry reconciles the project branch ff-only before creating the sentry branch; a diverged branch stops at the interactive gate. +- [ ] Sentry's own spine writes never trip the dirty-tree skip (a full fire of read-only passes ends with a clean tree via the digest commit). +- [ ] "Wrap it up" during an active loop refuses and names "stop sentry"; "stop sentry" cancels the loop and walks the branch and queue disposition. +- [ ] The second consecutive unmerged-branch skip produces one persistent desktop notification; repeats are at most daily. +- [ ] The inbox pass parks a shared-asset proposal (VERIFY + prepared diff + sender reply) rather than applying it, and the morning queue lists everything the pass did. + +* Readiness dimensions +- Data model & ownership: locks own their directories under /run/user/<uid>/agent-locks/ (tmpfs, outside every repo); digest and queue live in session-context.org (session-owned, path via session-context-path); commits on the sentry branch are Craig-authored per commits.md. +- Errors, empty states & failure: every skip names its reason in the digest; a failed pass banners and the rest continue; the digest surfaces lock-reclaim events. No silent outcomes. +- Security & privacy: no credentials touched; KB promotion honors the knowledge-base.md work-project refusal contract; commit messages follow the no-tooling-enumeration rule where applicable. +- Observability: the digest is the observability surface (per-fire, timestamped, failures on top); session-context.org persists it across crashes. +- Performance & scale: idle fire is seconds (probes + short-circuits); heaviest pass is triage (network); hourly cadence assumed fine; the knob covers it. +- Reuse & lost opportunities: reuses inbox.org, triage-intake.org, clean-todo, task-audit, lint-org.el, capture-guard, roam-sync wholesale; the only new code is agent-lock and the engine file. +- Architecture fit & weak points: engine+passes mirrors the engine+plugin convention; weak points are lock TTL tuning (reclaim vs slow pass) and /loop session lifetime (a killed session ends sentry; the lock's TTL clears the residue). +- Config surface: /loop interval; :COMMIT_AUTONOMY: marker; lock TTLs as script flags with defaults. Nothing else. +- Documentation plan: the workflow file is the doc (When to Use + the morning-review section); INDEX.org entry; no README change needed. +- Dev tooling: bats for agent-lock via the existing glob-discovered suites; make test covers it with no Makefile edit. +- Rollout, compatibility & rollback: additive, new files plus comment-level reconciliations; no consuming project changes behavior until Craig launches sentry there. Rollback is deleting the branch and not launching. +- External APIs & deps: none beyond tools already in use (git, systemctl, bats). N/A for schema verification. + +* Risks, Rabbit Holes, and Drawbacks +- The heartbeat bounds the TTL question to a single pass, but a pass that legitimately outruns its TTL (a slow triage sweep on a bad network) can still be reclaimed under itself. Start generous, surface every reclaim, tune from the digest. +- /loop lifetime: sentry dies with its session (harness restart, machine sleep). Acceptable for v1: the morning state is a readable branch plus session-context, and nothing corrupts. +- Digest fatigue: hourly fires on an active evening could stack noise. The interval knob and the one-line-when-idle rule are the mitigations; revisit after a week of use. +- The sentry branch holds todo.org/notes.org edits overnight; Craig editing those files on main before the morning merge makes the squash-merge conflict. That conflict is small, self-inflicted, and resolvable by hand; the morning-review section documents it. + +* Review and iteration history + +** 2026-07-14 Tue @ 02:03:28 -0500 — Claude Code (rulesets) — responder +- What: all 12 findings walked with Craig one by one and dispositioned; each completed in place and folded into the body. Load-bearing calls: launch contract with in-place checkout (worktrees rejected — untracked inbox drops are invisible there); every roam writer adopts the lock; locks relocate to the XDG runtime dir; spine-set exclusion + fire-end digest commit; inbox pass runs the no-approvals contract; suite policy recorded as Decision 10; KB promotion pass cut to vNext (heuristic task filed); heartbeat + bounded-wait lock mechanics; wrap-up refuses during a live loop ("stop sentry" owns shutdown); entry ff-only reconcile; stall notify after two skips. Mid-walk, Craig redirected the pass-8 finding into fixing the lint-org bug immediately — landed as 951b6fc, which also found and closed the true 2026-07-09 corruption path (wrap-org-table's load-time dispatch firing on lint-org's require). +- Why: Craig chose to resolve every open question together rather than batch them into a written response round. +- Artifacts: findings section above (12/12 done); todo.org tasks (KB heuristic [#D], bug task closed); commit 951b6fc. + +** 2026-07-14 Tue @ 01:15:00 -0500 — Claude Code (rulesets) — reviewer +- What: adversarial weakness review at Craig's direction — batches of candidate weaknesses checked against the spec, looped until a round produced fewer than five substantive new ones. Round 1: 20 candidates, 11 answered or mitigated by the spec, 9 became findings. Round 2: 7 candidates, 3 answered, 4 folded into findings. Round 3 produced fewer than five substantive candidates; loop terminated. Result: 10 blocking + 2 non-blocking findings recorded above. Rubric: Not ready until the blocking findings are dispositioned. Status stays DRAFT for Craig's deeper review. +- Why: Craig asked for the weakness loop before his own deep review, in place of a standard first-pass spec-review. +- Artifacts: findings grounded in code reads — roam-sync.sh:28 (git add -A commits anything inside the roam repo), .ai/scripts/session-context-path (AI_AGENT_ID scoping), todo.org [#B] "Org-table helpers corrupt example blocks" (lint-org mutate-on-lint), inbox.org core §2 no-approvals park path, verification.md pre-commit suite rule. + +** 2026-07-14 Tue @ 00:52:03 -0500 — Craig Jennings — author +- What: initial draft. +- Why: work project's sentry proposal accepted after skeptical review; nine design decisions resolved live in-session, recorded under Decisions. +- Artifacts: [[file:../design/2026-07-14-sentry-workflow-proposal.org][origin proposal]]; todo.org sentry build task. diff --git a/docs/specs/agent-knowledge-base-spec.org b/docs/specs/agent-knowledge-base-spec.org index e36d897..d5c0ce7 100644 --- a/docs/specs/agent-knowledge-base-spec.org +++ b/docs/specs/agent-knowledge-base-spec.org @@ -1,5 +1,5 @@ #+TITLE: Agent Knowledge Base on Org-roam — Spec -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-06-10 #+TODO: TODO | DONE #+TODO: DRAFT READY DOING | IMPLEMENTED SUPERSEDED CANCELLED diff --git a/docs/specs/inbox-workflow-consolidation-spec.org b/docs/specs/inbox-workflow-consolidation-spec.org index 4543e77..24ce30a 100644 --- a/docs/specs/inbox-workflow-consolidation-spec.org +++ b/docs/specs/inbox-workflow-consolidation-spec.org @@ -1,5 +1,5 @@ #+TITLE: Inbox Workflow Consolidation — Spec -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-06-23 #+TODO: TODO | DONE #+TODO: DRAFT READY DOING | IMPLEMENTED SUPERSEDED CANCELLED diff --git a/inbox/PROCESSED-2026-07-04-1302-from-home-task-for-rulesets-document-and-decide.org b/inbox/PROCESSED-2026-07-04-1302-from-home-task-for-rulesets-document-and-decide.org new file mode 100644 index 0000000..1abc1a6 --- /dev/null +++ b/inbox/PROCESSED-2026-07-04-1302-from-home-task-for-rulesets-document-and-decide.org @@ -0,0 +1,5 @@ +#+TITLE: Task for rulesets: document (and decide ownership of) the Si +#+SOURCE: from home +#+DATE: 2026-07-04 13:02:07 -0500 + +Task for rulesets: document (and decide ownership of) the Signal pager. Context: home retired the ntfy phone-notification channel (phone-notify/phone-recv, self-hosted ntfy on ratio) on 2026-07-04 in favor of paging over Signal, and tore the ntfy system down. But the Signal pager isn't documented anywhere — no pager script in ~/.local/bin, and the notify script doesn't reference Signal. What exists on ratio: signal-cli 0.14.5, configured with account 404211. The interface (a send wrapper, how a workflow pages Craig, how replies are read) is uncaptured, so no session can actually use the channel yet. This is the successor to the ntfy tooling's rulesets-ownership question (the 6/17 two-way-comms proposal): a phone paging channel is cross-machine tooling, so its canonical home + docs belong in rulesets. Suggested deliverable: a documented Signal pager (send + read-replies), the signal-cli setup/account notes, and the sync path — the Signal equivalent of what the retired ntfy runbook covered. Craig flagged this as a task for rulesets to finish. diff --git a/inbox/PROCESSED-2026-07-05-0420-from-archsetup-proposal-incoming-ui-prototyping.org b/inbox/PROCESSED-2026-07-05-0420-from-archsetup-proposal-incoming-ui-prototyping.org new file mode 100644 index 0000000..480d006 --- /dev/null +++ b/inbox/PROCESSED-2026-07-05-0420-from-archsetup-proposal-incoming-ui-prototyping.org @@ -0,0 +1,5 @@ +#+TITLE: Proposal incoming (ui-prototyping-process-proposal.org): a U +#+SOURCE: from archsetup +#+DATE: 2026-07-05 04:20:42 -0500 + +Proposal incoming (ui-prototyping-process-proposal.org): a UI/UX prototype process for specs with a non-trivial UI — research-first during brainstorming, then ~5 full working prototype directions, iterate one to final, name <spec-name>-prototype-<N>.html, link final in the spec + keep old iterations in the spec history. Worked example is archsetup's timer-panel spec + its 3 prototypes. Suggested it fold into spec-create/spec-review or a new ui-prototyping rule — your value gate decides placement. diff --git a/inbox/PROCESSED-2026-07-05-0420-from-archsetup-ui-prototyping-process-proposal.org b/inbox/PROCESSED-2026-07-05-0420-from-archsetup-ui-prototyping-process-proposal.org new file mode 100644 index 0000000..e0677eb --- /dev/null +++ b/inbox/PROCESSED-2026-07-05-0420-from-archsetup-ui-prototyping-process-proposal.org @@ -0,0 +1,79 @@ +#+TITLE: Proposal — UI/UX prototype process for specs with a non-trivial UI +#+AUTHOR: Craig Jennings (via archsetup session) +#+DATE: 2026-07-05 + +* Intro / why this is coming to you + +Working the timer-panel spec in archsetup, we found the right way to settle a +UI design: research first, then build a handful of full working prototypes, +then iterate one to a final — all before committing GTK code. It worked well +enough that Craig wants it as a standing part of the spec process for any spec +whose deliverable has a non-trivial UI. This is the write-up, proposed for the +rulesets layer (spec-create / spec-review, or a new =ui-prototyping= rule — +your call on placement). + +Worked example living now in archsetup: =docs/specs/2026-07-02-timer-panel-spec.org= +plus =docs/prototypes/2026-07-02-timer-panel-prototype-{1,2,3}.html=. The spec's +"Prototype iterations" subsection and its new design decisions show the shape in +practice. + +* The process + +** 1. Trigger — non-trivial UI only +Applies when a spec's deliverable is a real UI: a panel, a multi-control +surface, a visual layout with interacting parts. Not a single dialog, a CLI +flag, or a one-off prompt. If "which of these layouts is right?" can't be +answered from a sentence, it qualifies. + +** 2. Research first — during brainstorming, before prototyping +Before any prototype, survey how existing and best-in-class tools solve the same +UX and functionality (the category's well-regarded apps, prior art, conventions +users already expect). Feed the findings into the spec's Goals and Design so the +UX and functionality are understood *before* a single prototype. Prototyping +blind wastes iterations re-deriving what a 20-minute survey would have told you. +Cite the sources in the spec. + +** 3. Brainstorm the UX + functionality in the spec +Informed by the research, write the goals, the interactions, and the functional +surface into the spec. This is the "what and why" the prototypes will make real. + +** 4. Prototype — ~5 initial directions, then iterate to a final +Build about five *distinct directions* (genuinely different layouts / +interaction models, not variations of one) as full working prototypes over one +shared engine, in the project's design language. Pick a direction, then iterate +*that one* across numbered passes to the final design. Each meaningful pass is +saved as its own numbered prototype so the design history is walkable. + +** 5. Full working prototypes, not mockups +The prototypes must be *functional* — real state, real controls, real behavior — +so decisions are made against how it feels to use, not against a picture. A +static mockup hides the interaction problems that only surface when you drive it. + +** 6. Naming + location +=docs/prototypes/<spec-name>-prototype-<N>.html=, where =<spec-name>= is the +spec's dated slug (dropping the =-spec= suffix) and =N= is the iteration number. +E.g. for =2026-07-02-timer-panel-spec.org= → +=2026-07-02-timer-panel-prototype-1.html=, =-2.html=, =-3.html=. + +** 7. Link from the spec; keep old iterations in history +The spec links the *final* prototype in its design section, and keeps links to +*every* prior iteration in a "Prototype iterations" subsection under the status +heading — newest last — so the design's evolution is walkable from the spec. + +** 8. Decisions get written down once seen working +A design decision is recorded in the spec's Decisions only after it's been seen +working in a prototype. "Resolved live through the prototype iteration" — the +prototype is the evidence. + +* Suggested placement (your value gate decides) + +- =spec-create=: for a non-trivial-UI spec, add a "research → brainstorm → + prototype (5 directions → iterate)" step, and require the "Prototype + iterations" subsection. +- =spec-review=: for a non-trivial-UI spec, verify the prototype process ran — + research cited, final prototype linked, iterations in history, decisions + backed by a prototype. +- Or a standalone =claude-rules/ui-prototyping.md= that both workflows point at. + +Not prescribing which — sending the content and the worked example; apply the +rulesets value gate and place it where it fits. diff --git a/inbox/PROCESSED-2026-07-06-1054-from-archsetup-off-workspace-captures-rule.md b/inbox/PROCESSED-2026-07-06-1054-from-archsetup-off-workspace-captures-rule.md new file mode 100644 index 0000000..03538af --- /dev/null +++ b/inbox/PROCESSED-2026-07-06-1054-from-archsetup-off-workspace-captures-rule.md @@ -0,0 +1,38 @@ +# Proposal: never use the user's active workspace for agent windows/captures + +From an archsetup session (2026-07-06). Craig's request, verbatim intent: when I open an app or take a screenshot for my own verification, don't do it on his active workspace — put it somewhere that doesn't interrupt what he's doing. He then asked to make this a rule for everyone and send it to rulesets. + +## Why + +During the audio-panel work I repeatedly launched the GTK panel and `imv` on Craig's live desktop to screenshot and verify. Each one popped onto his current workspace and stole focus/attention mid-task. Agents doing visual verification on a user's live session shouldn't hijack the workspace the user is actively working in. + +## The rule (proposed text, ready to place) + +**Never open a window or take a screenshot on the user's active workspace.** When visual verification needs a real window on the user's live desktop, keep it off the workspace they're working in: + +- **Captures for your own verification** — render and grab the window off the user's physical screen, then tear it down. On Hyprland this is a virtual headless output (verified non-disruptive on ratio 2026-07-06 — the physical monitor stayed on its workspace, focused, throughout): + + ```sh + hyprctl output create headless # virtual output on its own workspace + setsid <app> >/tmp/x.log 2>&1 </dev/null & + addr=$(hyprctl -j clients | python3 -c 'import json,sys; print(next((c["address"] for c in json.load(sys.stdin) if c.get("class")=="<CLASS>"), ""))') + hyprctl dispatch movetoworkspacesilent "<ws-on-headless>,address:$addr" # silent = keeps the user's focus + grim -o HEADLESS-<n> /tmp/shot.png # capture the virtual output only + pkill -f '<app>$'; hyprctl output remove HEADLESS-<n> # tear down, restore the display + ``` + + Key constraint: `grim` captures a *visible output*, so a window merely parked on another Hyprland workspace can't be screenshotted — it must render on the headless (or another real) output. That's why a headless output, not just "another workspace," is the tool for self-captures. (A nested compositor — weston/cage/sway — is the alternative on non-Hyprland Wayland or when a headless output isn't available; it needs the compositor installed.) + +- **Showing the user something** — open it on a *separate* real workspace and tell them which one, so it never grabs their active workspace. They switch when ready. (Craig's viewer preference is `imv`; launch it through the compositor — `hyprctl dispatch exec "imv <files>"` — so it survives the agent's shell, not a bare `&` job that gets reaped.) + +- **Always clean up** — close the window and remove any headless output afterward; verify the user's display is restored (physical monitor back to its workspace, no orphan processes). + +The principle is environment-general (don't commandeer the user's active workspace for agent-side visual work); the recipe above is the Hyprland/Wayland implementation. Other environments implement the same principle with their own off-screen mechanism. + +## Placement suggestion (your call — "appropriate places") + +I'd lean toward a short standalone rule file (e.g. `claude-rules/desktop-capture.md`) since it's a distinct concern, cross-referenced from `verification.md` (it's part of how visual verification is done) and `interaction.md` (it's about not disrupting the user). It could instead be a section in `verification.md`. The `imv`/viewer preference and the "launch through the compositor" mechanic could also land wherever `emacs.md`'s screenshot note lives. Pick whatever fits the layer best. + +## Companion (local, already applied) + +Captured as archsetup auto-memory (`display-images-via-imv.md`) as the stopgap; this inbox note is the propagation to canonical per the cross-project rule for rulesets-owned changes. diff --git a/inbox/PROCESSED-2026-07-08-1124-from-work-proposal-triage-intake-personal-gmail.org b/inbox/PROCESSED-2026-07-08-1124-from-work-proposal-triage-intake-personal-gmail.org new file mode 100644 index 0000000..6fb315b --- /dev/null +++ b/inbox/PROCESSED-2026-07-08-1124-from-work-proposal-triage-intake-personal-gmail.org @@ -0,0 +1,5 @@ +#+TITLE: Proposal: triage-intake.personal-gmail.org edit (copy sent a +#+SOURCE: from work +#+DATE: 2026-07-08 11:24:19 -0500 + +Proposal: triage-intake.personal-gmail.org edit (copy sent alongside this note). Two additions to the Scan section, both from a 2026-07-08 work-session investigation: (1) a warning that the Gmail MCP listMessages tool caps at maxResults=100 and exposes no pageToken parameter, so >100 unread piles silently truncate — with the date-slice walk (before:<oldest-day>, dedupe by id) as the recipe, and a note that resultSizeEstimate is unreliable (stuck at 201 while the real union exceeded 300); (2) a mandatory cheap backlog-residue probe each sweep (q="is:unread in:inbox before:<anchor-date>" maxResults=5) that loudly surfaces any pre-anchor unread instead of letting anchored sweeps claim 'no changes' over a window they never saw. Root cause this fixes: ~300 unread accumulated invisibly Jun 4 - Jul 4 because anchored scans never look behind the anchor and the 7/4 catch-up hit the 100 cap. Companion: the work project's project-owned triage-intake.deepsat-gmail.org got the same two additions directly (work tool names); no other plugins in the gmail family. The engine file needs no change. diff --git a/inbox/PROCESSED-2026-07-08-1124-from-work-triage-intake.personal-gmail.org b/inbox/PROCESSED-2026-07-08-1124-from-work-triage-intake.personal-gmail.org new file mode 100644 index 0000000..ca81f5d --- /dev/null +++ b/inbox/PROCESSED-2026-07-08-1124-from-work-triage-intake.personal-gmail.org @@ -0,0 +1,68 @@ +#+TITLE: Triage Intake — Personal Gmail Source +#+AUTHOR: Craig Jennings & Claude +#+DATE: 2026-05-26 + +# Source plugin for the triage-intake engine. See triage-intake.org for the +# contract and the Phase A-D orchestration. This file declares ONE source. + +* Source: personal-gmail +:PROPERTIES: +:ORDER: 20 +:ENABLED: mcp google-docs-personal present +:ANCHOR: epoch +:SUBAGENT_OVER: 50 +:END: + +** Scan + +Personal Gmail unread in the inbox since the anchor: + +#+begin_src text +mcp__google-docs-personal__listMessages q="is:unread in:inbox after:<anchor-epoch>" maxResults=100 +#+end_src + +⚠ *Express the cutoff as the literal UNIX epoch* — =after:1778856990=, not =after:YYYY/MM/DD=. Gmail's =after:YYYY/MM/DD= operator only supports day resolution; the =YYYY/MM/DD HH:MM:SS= form is NOT valid syntax — Gmail parses the space as a term separator, treats =HH:MM:SS= as a search term that never matches, and returns 0 results, silently masking unread mail. The engine supplies =<anchor-epoch>= because this source declares =ANCHOR: epoch=. + +⚠ *Do NOT add =-category:promotions -category:social=.* That filter masked 67 promo+social messages across two runs (2026-05-04, 2026-05-06), both needing a follow-up sweep. Pull the full unfiltered set; the trash-leaning bias in Classify handles promotions and social directly. + +⚠ *The MCP caps at =maxResults=100= and exposes NO =pageToken= parameter.* The response carries a =nextPageToken=, but the tool can't consume it, so a pile over 100 is silently truncated — the tail below the cap never gets classified, and every later anchored sweep skips it (it predates the new anchor). This is exactly how a 300+ backlog accumulated invisibly by 2026-07-08. Two consequences: + +- *Never treat a 100-row result as complete.* When a scan returns exactly 100, walk the tail in *date slices*: re-query with =before:<oldest-full-day-seen>= (day resolution), repeat until a page returns fewer than 100, dedupe by message id across slices (the day-resolution boundary overlaps). +- *Never report =resultSizeEstimate= as a count.* It's unreliable — observed stuck at "201" across three different queries whose real union exceeded 300. + +*** Backlog-residue check (every sweep — cheap, mandatory) + +The anchored scan is blind to anything unread from *before* the anchor. After it, run one probe for pre-anchor residue: + +#+begin_src text +mcp__google-docs-personal__listMessages q="is:unread in:inbox before:<anchor-YYYY/MM/DD>" maxResults=5 +#+end_src + +If it returns any messages, surface one loud line in the sweep summary: "Backlog: unread predating the anchor exists (N+ shown; date-slice to inventory)" and offer a backlog sweep. Never fold the residue into a quiet sweep — an anchored "no changes" claim is only true for the window the scan saw. (Added 2026-07-08 after ~300 pre-anchor unread accumulated unseen; the probe returns actual messages, so it works where the estimate lies.) + +** Classify + +Bias: *trash-leaning* — personal Gmail is high noise volume. + +- *Noise-trash:* newsletters, Substacks, retail/SaaS marketing, social digests, redundant aggregator digests (Notion/Miro daily), wrong-recipient mail, past-event calendar artifacts. +- *Noise-keep:* receipts, order confirmations, statements — low value but worth the audit trail. +- *FYI:* substantive personal mail with no action owed. +- *Action:* an explicit ask, a reply owed, a time-sensitive personal matter. + +** Render + +#+begin_example +**Personal Gmail — N unread.** <one-line classification summary> +- Action: <items, if any, with thread links> +- FYI: <items, if any> +- Noise: N trash candidates, M keep +#+end_example + +Omit the block if zero unread. + +** Actions + +- trash :: =mcp__google-docs-personal__trashMessage= id=<message-id> (recoverable from Gmail Trash for 30 days) +- mark-read :: =mcp__google-docs-personal__modifyMessageLabels= id=<message-id> removeLabelIds=["UNREAD"] +- star+read :: =mcp__google-docs-personal__modifyMessageLabels= id=<message-id> addLabelIds=["STARRED"] removeLabelIds=["UNREAD"] +- attach-fetch:: =.ai/scripts/gmail-fetch-attachments.py --profile personal --message-id <message-id> --output-dir <PATH>= diff --git a/inbox/PROCESSED-2026-07-09-0649-from-work-pr-review-rule-tightening-from-a.org b/inbox/PROCESSED-2026-07-09-0649-from-work-pr-review-rule-tightening-from-a.org new file mode 100644 index 0000000..80dc4bc --- /dev/null +++ b/inbox/PROCESSED-2026-07-09-0649-from-work-pr-review-rule-tightening-from-a.org @@ -0,0 +1,11 @@ +#+TITLE: PR-review rule tightening from a DeepSat review session (Cra +#+SOURCE: from work +#+DATE: 2026-07-09 06:49:14 -0500 + +PR-review rule tightening from a DeepSat review session (Craig, 2026-07-09). Two changes to the review-code skill: + +1. No praise on approvals — stronger than the current 'Posted Summary Voice' rule. That section currently permits 'the verdict plus at most a bare positive ("Clean.", "Solid fix.")'. Craig's ruling: an approve summary carries NO praise at all, not even the bare positive. Lead the summary with the substantive pointer (the inline design note), then the verdict. Example he approved: 'One design note inline, not a blocker. Approving.' The praise-strips / correction-explains split still holds for findings; approvals just drop the praise clause entirely. Suggest editing the 'Posted Summary Voice' section (and /voice personal pattern #40 if it encodes the 'bare positive allowed' carve-out) to remove the bare-positive permission. + +2. Always show inline comment text at the review gate — Phase 5 / the publish-flow gate should require printing the FULL inline prose that will post, alongside the summary body, never the summary alone with the inline merely described ('I'd pair it with one inline on...'). Craig approves the exact words that post under his name, so the exact words must be on screen. Suggest making this explicit in review-code Phase 5 (Terminal display) and in commits.md Step 2 Shape 1 (the print-the-draft step). + +Both are cross-project (any PR review), so they belong in the rulesets layer, not just the DeepSat project. Also captured in the work project's harness memory as feedback_no_praise_on_approvals_show_inline for immediate use. diff --git a/inbox/PROCESSED-2026-07-09-1341-from-work-bug-data-loss-wrap-org-table-el-and.org b/inbox/PROCESSED-2026-07-09-1341-from-work-bug-data-loss-wrap-org-table-el-and.org new file mode 100644 index 0000000..a128d0a --- /dev/null +++ b/inbox/PROCESSED-2026-07-09-1341-from-work-bug-data-loss-wrap-org-table-el-and.org @@ -0,0 +1,28 @@ +#+TITLE: BUG (data loss): wrap-org-table.el and lint-org.el corrupt o +#+SOURCE: from work +#+DATE: 2026-07-09 13:41:56 -0500 + +BUG (data loss): wrap-org-table.el and lint-org.el corrupt org example blocks. + +Both scripts scan for lines beginning with "|" and rewrite them as org tables. They do not skip #+begin_example / #+begin_src / #+begin_quote regions, so ASCII art using pipe characters gets mangled into tables. Both write to disk with no confirmation. + +Reproduced 2026-07-09 in the work project against an architecture doc containing an ASCII pipeline diagram that uses | and v as flow arrows: + + Before: After: + | | | + v |---| + v + +and a plain indented block became a bordered org table with |---| rules inserted between every line. + +Two separable defects: + +1. Table detection is line-based. Both helpers should use org-element-at-point (or org-in-block-p) to skip example/src/quote/verse blocks rather than matching /^\s*|/. + +2. lint-org.el mutates its input. Passing five files to it reformatted all five on disk -- one of them by 1949 lines. Its documented job is to report judgment items. A linter must not write. If the reformat is wanted, it belongs behind an explicit --fix flag. + +Impact: silent data loss on any org file that mixes tables and example blocks, which is most architecture docs. In this case the good content was already staged in git and was recoverable. It would not have been otherwise. + +No local fix attempted: .ai/scripts/ is rulesets-owned and the startup rsync would revert it. Filed as a task on the work side (Org-table helpers corrupt example blocks) so it is tracked there until the canonical fix lands. + +Suggested test: run wrap-org-table.el against a file containing a #+begin_example block whose lines start with "|" and assert the block is byte-identical afterward. diff --git a/inbox/PROCESSED-2026-07-09-1400-from-work-handoff-ai-attribution-cleanup-done.org b/inbox/PROCESSED-2026-07-09-1400-from-work-handoff-ai-attribution-cleanup-done.org new file mode 100644 index 0000000..a37a54a --- /dev/null +++ b/inbox/PROCESSED-2026-07-09-1400-from-work-handoff-ai-attribution-cleanup-done.org @@ -0,0 +1,22 @@ +#+TITLE: HANDOFF: AI-attribution cleanup done from the work session ( +#+SOURCE: from work +#+DATE: 2026-07-09 14:00:05 -0500 + +HANDOFF: AI-attribution cleanup done from the work session (Craig approved doing it from here). + +What changed in rulesets, uncommitted in your working tree: + +1. 103 files: rewrote the org header "#+AUTHOR: Craig Jennings & Claude" to "#+AUTHOR: Craig Jennings". Breakdown: 43 .ai/workflows, 43 claude-templates/.ai/workflows, 5 docs/specs, 5 docs/design, 2 .ai (notes, protocols), 2 claude-templates/.ai, 2 references, 1 working/. Exactly one line changed per file (103 insertions, 103 deletions, zero non-AUTHOR lines). Prose mentions of Claude were not touched. + +2. claude-rules/commits.md: added "Document author metadata" to the No-AI-Attribution list, plus a new subsection "Generated documents carry the human author only". It explains that the propagation mechanism is imitation (no template stamps the line; agents copy it from neighbouring files), names the employer-policy stakes, and carves out two exceptions. + +Held back deliberately, please confirm you agree: +- .ai/sessions/ (14 files) left as-is. They are historical records of what happened, not live artifacts. +- docs/design/2026-05-28-generic-agent-runtime-spec.org and its -review sibling keep "#+AUTHOR: Codex". Codex actually wrote them, so renaming would be a false attribution rather than removing one. +- .ai/scripts/tests/fixtures/todo-sample.org keeps "#+AUTHOR: synthetic fixture" (test data). + +Not committed. The tracked tree was clean at 0/0 against origin/main before these edits, so the diff is exactly this change and nothing else. Review and commit on your side. + +Why it came up: the work project noticed its generated daily-prep docs carry the co-author line. Craig pointed out that his own repo tolerates it, but employers whose policy is that work product carries employee names alone would not. Nothing has leaked yet: the arch docs pushed to the company GHE lost the #+AUTHOR line in the pandoc conversion. The exposure was prospective, via a planned Markdown-to-Notion publisher. + +Companion item already in your inbox: the wrap-org-table.el / lint-org.el data-loss bug (2026-07-09-1341). diff --git a/inbox/PROCESSED-2026-07-09-1636-from-chime-staleness-proposal.txt b/inbox/PROCESSED-2026-07-09-1636-from-chime-staleness-proposal.txt new file mode 100644 index 0000000..cc741b2 --- /dev/null +++ b/inbox/PROCESSED-2026-07-09-1636-from-chime-staleness-proposal.txt @@ -0,0 +1,17 @@ +Proposal: task-review-staleness.sh should accept org-style :LAST_REVIEWED: values, or fail loudly. + +Hit this in chime today. I stamped :LAST_REVIEWED: [2026-07-09 Thu] — an org inactive timestamp, matching the CREATED: and CLOSED: cookies sitting right next to it in the same drawer. The script expects a bare 2026-07-09. + +The failure is silent and inverted. In count mode, `date -d "[2026-07-09 Thu]"` fails, and the unparseable branch counts the task as STALE. So a freshly-reviewed task reports as never-reviewed, and a full review pass leaves the startup nudge saying exactly what it said before. In list mode the sort-key regex also rejects it, so the task sorts as 0000-00-00 (oldest) and gets re-walked first. Both modes punish the stamp for being in the wrong format, and neither says so. + +I fixed my side (bare dates, matching the precedent in home/todo.org). The trap is worth closing, because the bracketed form is the plausible guess: every other date in an org PROPERTIES drawer is bracketed, and nothing in task-review.org or todo-format.md says LAST_REVIEWED is different. + +Two options, either fine: + +1. Accept both. Strip a leading bracket and a trailing weekday + bracket before parsing, inside extract_tasks. Org-native stamps then work and existing bare stamps keep working. + +2. Fail loudly. When a LAST_REVIEWED value is present but unparseable, print a warning naming the file, line, and value rather than folding it into the stale count. A malformed stamp is a data error, and treating it as "never reviewed" hides it forever. + +I lean toward both: accept the org form, warn on anything still unparseable. Today a project can run task reviews for months while the staleness count never drops, and nothing ever explains why. + +Also worth a line in todo-format.md or task-review.org stating the expected format. The script is currently the only place it's written down, and you have to read its awk to find it. diff --git a/inbox/PROCESSED-2026-07-09-1745-from-chime-matrix-proposal.txt b/inbox/PROCESSED-2026-07-09-1745-from-chime-matrix-proposal.txt new file mode 100644 index 0000000..53c20c8 --- /dev/null +++ b/inbox/PROCESSED-2026-07-09-1745-from-chime-matrix-proposal.txt @@ -0,0 +1,35 @@ +Proposal: todo-format.md's bug matrix should warn against double-counting rarity. + +Adding the severity x frequency matrix to chime today, I mis-graded a bug by exactly one mistake, and I think the rule invites it. + +The task: chime's async watchdog interrupts a child that outlives its timeout, but never escalates to kill. I graded it Minor severity ("a zombie child and a leaked process buffer accumulate slowly") x rare edge case ("the watchdog must fire AND the child must survive SIGINT") = P4 = [#D]. + +Then I read async.el. Its cleanup guards on (eq 'exit (process-status proc)) and only kills the process buffer in the zero-exit branch, so a signal-killed child (status 'signal) skips it entirely. Every watchdog interrupt leaks a buffer; the surviving-SIGINT zombie is the rare sub-case, not the leak. And the watchdog nils the process handle, so the same tick spawns a replacement — if the hang cause persists, another child is abandoned every timeout period. Roughly 30 leaked buffers an hour, indefinitely, invisibly. A real incident had a child stuck 15+ hours. + +Severity was the wrong input, not frequency. "Accumulates slowly" describes a bounded trickle. This accumulates at a fixed rate forever once entered, with no workaround short of restarting Emacs. That's Major. Major x rare edge = P3 = [#C], which is where it landed. + +The generalizable error: I let the rarity of *entering* the failure state discount the *severity* of being in it. But frequency already carries that rarity. Grading it twice buries exactly the bugs that compound — the ones where a rare trigger produces unbounded harm. + +Suggested addition to the matrix section: + + Don't double-count rarity. Grade severity by the rate of harm once the + failure state is entered, not by how rare it is to enter. Frequency + already carries the rarity; letting it discount severity too grades the + same fact twice, and that buries compounding bugs. A leak that repeats + every timeout period until the process restarts is Major even when + reaching that state is a rare edge case. + +Two smaller additions I made to chime's scheme, both of which I'd put in the global rule: + + Record the grading in the task body — the severity band, the frequency + row, and the arithmetic. A bare priority cookie can't be argued with; a + stated read can be re-checked against the source and corrected. That's + how this task moved [#D] -> [#C] an hour after I graded it. + + Disagreeing with a grade means fixing an input. If a letter looks wrong, + re-read the severity band and the frequency row against the source and + correct whichever is wrong. Don't override the letter directly — that + turns the matrix into a formality and puts you back to grading by + instinct. + +The last one is the one I care about. Craig's first instinct on seeing the [#D] was to restore the [#B] it had before. The matrix earns its keep only if a disagreement forces a re-read of the inputs rather than a manual override, and the rule text currently doesn't say so. diff --git a/inbox/PROCESSED-2026-07-11-0222-from-.emacs.d-ui-prototype-rule-proposal.org b/inbox/PROCESSED-2026-07-11-0222-from-.emacs.d-ui-prototype-rule-proposal.org new file mode 100644 index 0000000..eeb60e7 --- /dev/null +++ b/inbox/PROCESSED-2026-07-11-0222-from-.emacs.d-ui-prototype-rule-proposal.org @@ -0,0 +1,55 @@ +#+TITLE: Proposal: UI features require a prototype phase before build-to-spec +#+AUTHOR: Craig Jennings +#+DATE: 2026-07-11 + +* The rule (Craig approved promotion 2026-07-11) + +When a spec'd feature has a UI, don't build straight from the spec. After the +initial spec, run several UI/UX prototype feedback loops — iteratively driving +the remaining functionality alongside the look and feel — then fold what settled +back into the spec. Only then "build to the prototype": the built feature should +look and behave as close to the prototype as possible, and any deviation is +documented in an addendum section of the prototype/spec itself. + +* Where it should live (rulesets session decides exact home) + +Two natural homes; probably both: + +1. The =brainstorm= skill's Phase 3. Today Phase 3 presents the design in chunks + and stops. Add: if the feature has a UI, the design isn't "accepted" until it + has been through a prototype UI/UX phase (several feedback loops), and the + spec records what the prototype settled. The spec's Next Steps then say + "build to the prototype," not "build to the spec." + +2. A spec-lifecycle rule (=docs-lifecycle.md=, or a small new workflow rule). + The lifecycle for a UI feature gains a prototype stage between DRAFT/READY and + build, and the spec carries a "Prototype & deviations" addendum section that + the build keeps current. + +Companion touch points to reconcile: =spec-create= (emit the prototype stage + +the deviations-addendum section for UI specs), =spec-response= (a UI spec +decomposes into a prototype loop first, then build-to-prototype tasks), +=start-work= (its verify phase already drives the UI end-to-end; here the bar is +"matches the prototype," with deviations logged). + +* Why — worked example (takuzu, 2026-07-11) + +Building the takuzu (Binairo) Emacs game. The spec chose "colored tiles, glyph +overlay optional." Built straight to that and the first launch in Craig's actual +terminal frame was all black — the dark background-color faces read as black and +the cursor was a GUI-only =:box=, so the whole grid was invisible. The +colored-tiles-are-readable assumption was false in the real environment. A +prototype loop caught it on the first screenshot; had we "built to spec" and +called it done, we'd have shipped an unusable board. The fix (glyphs as the +primary signal, inverse-video cursor) is exactly the kind of adjustment that only +surfaces by looking at the running UI, and it now needs to flow back into the +spec so the build target is the prototype, not the original spec text. + +Jotto (the other game spec'd the same day) also has a UI and will follow the same +path. + +* Requested action + +Fold the rule into the brainstorm skill and the spec-lifecycle rules so it +governs every project's UI features, then re-sync. This proposal is the durable +channel; the two game projects apply it locally in the meantime. diff --git a/inbox/lint-followups.org b/inbox/lint-followups.org deleted file mode 100644 index a81b1ce..0000000 --- a/inbox/lint-followups.org +++ /dev/null @@ -1,2 +0,0 @@ - -* 2026-07-02 Thu — Task-review health: 2 top-level [#A]/[#B]/[#C] tasks unreviewed for >30 days (daily review may have slipped) diff --git a/languages/bash/githooks/pre-commit b/languages/bash/githooks/pre-commit index e41c41c..880d5cf 100755 --- a/languages/bash/githooks/pre-commit +++ b/languages/bash/githooks/pre-commit @@ -9,11 +9,23 @@ cd "$REPO_ROOT" || exit 1 # --- 1. Secret scan --- # Patterns for common credentials. Scans only added lines in the staged diff. -SECRET_PATTERNS='(AKIA[0-9A-Z]{16}|sk-[a-zA-Z0-9_-]{20,}|-----BEGIN (RSA|DSA|EC|OPENSSH|PGP)( PRIVATE)?( KEY| KEY BLOCK)?-----|(api[_-]?key|api[_-]?secret|auth[_-]?token|secret[_-]?key|bearer[_-]?token|access[_-]?token|password)[[:space:]]*[:=][[:space:]]*["'"'"'][^"'"'"']{16,}["'"'"'])' - -secret_hits="$(git diff --cached -U0 --diff-filter=AM \ - | grep '^+' | grep -v '^+++' \ - | grep -iEn "$SECRET_PATTERNS" || true)" +# +# Two passes because case-sensitivity differs. AWS keys are uppercase, sk- keys +# lowercase, PEM headers fixed, so those match case-SENSITIVELY: under -i, +# AKIA[0-9A-Z]{16} matches any mixed-case 20-char run, which random base64 in an +# embedded image blob hits ~6% of the time per 100KB and blocks real commits. +# Only the keyword=value patterns need -i. +SECRET_PATTERNS_CS='(AKIA[0-9A-Z]{16}|sk-[a-zA-Z0-9_-]{20,}|-----BEGIN (RSA|DSA|EC|OPENSSH|PGP)( PRIVATE)?( KEY| KEY BLOCK)?-----)' +SECRET_PATTERNS_CI='(api[_-]?key|api[_-]?secret|auth[_-]?token|secret[_-]?key|bearer[_-]?token|access[_-]?token|password)[[:space:]]*[:=][[:space:]]*["'"'"'][^"'"'"']{16,}["'"'"']' + +added_lines="$(git diff --cached -U0 --diff-filter=AM \ + | grep '^+' | grep -v '^+++' || true)" + +cs_hits="$(printf '%s\n' "$added_lines" | grep -nE "$SECRET_PATTERNS_CS" || true)" +ci_hits="$(printf '%s\n' "$added_lines" | grep -niE "$SECRET_PATTERNS_CI" || true)" +# awk dedupes lines both passes matched, keeping first-seen order. +secret_hits="$(printf '%s\n%s' "$cs_hits" "$ci_hits" \ + | grep -v '^[[:space:]]*$' | awk '!seen[$0]++' || true)" if [ -n "$secret_hits" ]; then echo "pre-commit: potential secret in staged changes:" >&2 diff --git a/languages/elisp/claude/hooks/validate-el.sh b/languages/elisp/claude/hooks/validate-el.sh index 8e46457..d028789 100755 --- a/languages/elisp/claude/hooks/validate-el.sh +++ b/languages/elisp/claude/hooks/validate-el.sh @@ -55,6 +55,7 @@ case "$f" in # under a tests/ subdir) so cross-project edits compile against their # own modules, not just this project's. if ! output="$(emacs --batch --no-site-file --no-site-lisp \ + --eval '(setq load-prefer-newer t)' \ -L "$(dirname "$f")" \ -L "$(dirname "$f")/.." \ -L "$PROJECT_ROOT" \ @@ -99,6 +100,7 @@ if [ "$count" -ge 1 ] && [ "$count" -le "$MAX_AUTO_TEST_FILES" ]; then load_args=() for t in "${tests[@]}"; do load_args+=("-l" "$t"); done if ! output="$(emacs --batch --no-site-file --no-site-lisp \ + --eval '(setq load-prefer-newer t)' \ -L "$PROJECT_ROOT" \ -L "$PROJECT_ROOT/modules" \ -L "$PROJECT_ROOT/tests" \ diff --git a/languages/elisp/githooks/pre-commit b/languages/elisp/githooks/pre-commit index 909cde2..27f280c 100755 --- a/languages/elisp/githooks/pre-commit +++ b/languages/elisp/githooks/pre-commit @@ -9,11 +9,23 @@ cd "$REPO_ROOT" # --- 1. Secret scan --- # Patterns for common credentials. Scans only added lines in the staged diff. -SECRET_PATTERNS='(AKIA[0-9A-Z]{16}|sk-[a-zA-Z0-9_-]{20,}|-----BEGIN (RSA|DSA|EC|OPENSSH|PGP)( PRIVATE)?( KEY| KEY BLOCK)?-----|(api[_-]?key|api[_-]?secret|auth[_-]?token|secret[_-]?key|bearer[_-]?token|access[_-]?token|password)[[:space:]]*[:=][[:space:]]*["'"'"'][^"'"'"']{16,}["'"'"'])' - -secret_hits="$(git diff --cached -U0 --diff-filter=AM \ - | grep '^+' | grep -v '^+++' \ - | grep -iEn "$SECRET_PATTERNS" || true)" +# +# Two passes because case-sensitivity differs. AWS keys are uppercase, sk- keys +# lowercase, PEM headers fixed, so those match case-SENSITIVELY: under -i, +# AKIA[0-9A-Z]{16} matches any mixed-case 20-char run, which random base64 in an +# embedded image blob hits ~6% of the time per 100KB and blocks real commits. +# Only the keyword=value patterns need -i. +SECRET_PATTERNS_CS='(AKIA[0-9A-Z]{16}|sk-[a-zA-Z0-9_-]{20,}|-----BEGIN (RSA|DSA|EC|OPENSSH|PGP)( PRIVATE)?( KEY| KEY BLOCK)?-----)' +SECRET_PATTERNS_CI='(api[_-]?key|api[_-]?secret|auth[_-]?token|secret[_-]?key|bearer[_-]?token|access[_-]?token|password)[[:space:]]*[:=][[:space:]]*["'"'"'][^"'"'"']{16,}["'"'"']' + +added_lines="$(git diff --cached -U0 --diff-filter=AM \ + | grep '^+' | grep -v '^+++' || true)" + +cs_hits="$(printf '%s\n' "$added_lines" | grep -nE "$SECRET_PATTERNS_CS" || true)" +ci_hits="$(printf '%s\n' "$added_lines" | grep -niE "$SECRET_PATTERNS_CI" || true)" +# awk dedupes lines both passes matched, keeping first-seen order. +secret_hits="$(printf '%s\n%s' "$cs_hits" "$ci_hits" \ + | grep -v '^[[:space:]]*$' | awk '!seen[$0]++' || true)" if [ -n "$secret_hits" ]; then echo "pre-commit: potential secret in staged changes:" >&2 diff --git a/languages/go/githooks/pre-commit b/languages/go/githooks/pre-commit index a3d6f3f..a6297c8 100755 --- a/languages/go/githooks/pre-commit +++ b/languages/go/githooks/pre-commit @@ -9,11 +9,23 @@ cd "$REPO_ROOT" # --- 1. Secret scan --- # Patterns for common credentials. Scans only added lines in the staged diff. -SECRET_PATTERNS='(AKIA[0-9A-Z]{16}|sk-[a-zA-Z0-9_-]{20,}|-----BEGIN (RSA|DSA|EC|OPENSSH|PGP)( PRIVATE)?( KEY| KEY BLOCK)?-----|(api[_-]?key|api[_-]?secret|auth[_-]?token|secret[_-]?key|bearer[_-]?token|access[_-]?token|password)[[:space:]]*[:=][[:space:]]*["'"'"'][^"'"'"']{16,}["'"'"'])' - -secret_hits="$(git diff --cached -U0 --diff-filter=AM \ - | grep '^+' | grep -v '^+++' \ - | grep -iEn "$SECRET_PATTERNS" || true)" +# +# Two passes because case-sensitivity differs. AWS keys are uppercase, sk- keys +# lowercase, PEM headers fixed, so those match case-SENSITIVELY: under -i, +# AKIA[0-9A-Z]{16} matches any mixed-case 20-char run, which random base64 in an +# embedded image blob hits ~6% of the time per 100KB and blocks real commits. +# Only the keyword=value patterns need -i. +SECRET_PATTERNS_CS='(AKIA[0-9A-Z]{16}|sk-[a-zA-Z0-9_-]{20,}|-----BEGIN (RSA|DSA|EC|OPENSSH|PGP)( PRIVATE)?( KEY| KEY BLOCK)?-----)' +SECRET_PATTERNS_CI='(api[_-]?key|api[_-]?secret|auth[_-]?token|secret[_-]?key|bearer[_-]?token|access[_-]?token|password)[[:space:]]*[:=][[:space:]]*["'"'"'][^"'"'"']{16,}["'"'"']' + +added_lines="$(git diff --cached -U0 --diff-filter=AM \ + | grep '^+' | grep -v '^+++' || true)" + +cs_hits="$(printf '%s\n' "$added_lines" | grep -nE "$SECRET_PATTERNS_CS" || true)" +ci_hits="$(printf '%s\n' "$added_lines" | grep -niE "$SECRET_PATTERNS_CI" || true)" +# awk dedupes lines both passes matched, keeping first-seen order. +secret_hits="$(printf '%s\n%s' "$cs_hits" "$ci_hits" \ + | grep -v '^[[:space:]]*$' | awk '!seen[$0]++' || true)" if [ -n "$secret_hits" ]; then echo "pre-commit: potential secret in staged changes:" >&2 diff --git a/review-code/SKILL.md b/review-code/SKILL.md index ec08a9d..3757404 100644 --- a/review-code/SKILL.md +++ b/review-code/SKILL.md @@ -273,9 +273,9 @@ Remaining issues get tagged: ## Phase 5 — Output -**Terminal display — plain text only.** Everything this skill echoes to Craig in the chat terminal — the structured report, the per-criterion table, the verdict, and any draft summary or inline awaiting approval — must be rendered as plain text. No bold (`**`), no backtick code spans, no markdown tables, no headings-as-markup. They render as reverse video in his terminal and are hard to read. Write `file.py:42` as bare text, identifiers and `test:`-style prefixes unquoted, the criterion audit as a plain dashed list rather than a table, severity tiers as plain labels. This applies only to the terminal echo. The review actually posted to GitHub (the `gh api` body and inline blocks) keeps normal markdown — GitHub renders it correctly, so draft the posted artifact in markdown and strip the markup only when mirroring it into the chat. (Same rule as `interaction.md`'s no-reverse-video constraint; repeated here because the violation happens at exactly this print step.) +**Terminal display — plain text only.** Everything this skill echoes to Craig in the chat terminal — the structured report, the per-criterion table, the verdict, the draft summary, and the full prose of every inline pin awaiting approval (the exact words, never a description of them) — must be rendered as plain text. No bold (`**`), no backtick code spans, no markdown tables, no headings-as-markup. They render as reverse video in his terminal and are hard to read. Write `file.py:42` as bare text, identifiers and `test:`-style prefixes unquoted, the criterion audit as a plain dashed list rather than a table, severity tiers as plain labels. This applies only to the terminal echo. The review actually posted to GitHub (the `gh api` body and inline blocks) keeps normal markdown — GitHub renders it correctly, so draft the posted artifact in markdown and strip the markup only when mirroring it into the chat. (Same rule as `interaction.md`'s no-reverse-video constraint; repeated here because the violation happens at exactly this print step.) -Before printing any approve/request-changes summary for posting, run the praise/correction gate (see Posted Summary Voice, and `/voice` personal #40): scan the summary and cut every clause that describes or justifies a good change — keep praise plus verdict only. Then confirm each finding and change-request states its why, gently and briefly. This is a mechanical pass, not a judgment call. +Before posting, print the full summary body AND every inline comment exactly as it will post — never the summary alone with an inline merely described ("I'd pair it with one inline on..."). Craig approves the exact words that post under his name, so the exact words must be on screen. Then run the praise/correction gate (see Posted Summary Voice, and `/voice` personal #40): scan the summary and cut every praise clause and every clause that describes or justifies a good change — an approve summary is the substantive pointer plus the verdict, no praise, not even a bare positive. Then confirm each finding and change-request states its why, gently and briefly. This is a mechanical pass, not a judgment call. ```markdown # Code Review — <PR title / branch name / SHA range> @@ -446,21 +446,24 @@ None. The summary body and the inline pins work as a pair: scannable verdict on top, full coaching conversation in the pins. Read this section paired with Inline Comment Voice below — the summary is terse precisely because the inlines carry the teaching weight. -The structured report above stays local. When the verdict is posted as a GitHub review (per `commits.md` Step 2 Shape 1), keep the summary body terse — one long sentence or a few short ones is plenty. Vary the phrasing run-to-run so consecutive reviews don't read templated. Voice: an encouraging senior dev who doesn't like to talk; positive feedback is short, blunt, and lands cleanly. +The structured report above stays local. When the verdict is posted as a GitHub review (per `commits.md` Step 2 Shape 1), keep the summary body terse — one long sentence or a few short ones is plenty. Vary the phrasing run-to-run so consecutive reviews don't read templated. Voice: an encouraging senior dev who doesn't like to talk. Lead with the substantive pointer — the design note or blocker that's pinned inline — and close with the verdict; the summary carries no praise clause. -Name the good thing and stop: do not explain *why* it's good. The author made the change and already knows the rationale, so justifying the praise reads as sycophantic. "Clean migration off the window globals, tests cover the right edges" lands; appending "...because there are no stragglers and the provider, mocks, and Normal/Boundary/Error cases are all covered" turns a compliment into padding. Elaboration is for findings (something is wrong, here's the failure mode and the fix), never for compliments. +The summary body carries no praise — not a named good thing, not a bare positive. The author made the change and already knows its merits, so a compliment in a terse summary reads as filler or sycophancy. If a genuine positive is worth surfacing, it goes as a single inline pin on the relevant line (see below), never the summary body. Elaboration in the summary is for the substantive pointer and for findings — what's wrong, the failure mode, the fix — never for compliments. -This holds for re-review approvals too. A re-review confirming requested changes is just "Approving." Mechanical rule: an approve summary is the verdict plus at most a bare positive ("Clean.", "Solid fix."). It must contain no clause that says what the change does or why it works. "The hoist to App fixes the crash, and the new test locks it in" is the banned pattern — it describes and justifies the change on an approve. If a clause references the code's behavior, cut it. +This holds for re-review approvals too. A re-review confirming requested changes is just "Approving." Mechanical rule: an approve summary is the substantive pointer (the inline design note, if any) followed by the verdict — no praise, not even a bare positive. Lead with the pointer, close with the verdict: "One design note inline, not a blocker. Approving." An approve with nothing to flag is just "Approving." No clause may say what the change does or why it works, and none may compliment it — "Clean.", "Solid fix.", "The hoist to App fixes the crash and the new test locks it in" are all cut. If a clause references the code's behavior or praises it, cut it. -The asymmetry: praise gets no why, but a finding, change-request, or inline coaching note *always* gets the why. Behavior only changes when the reason lands, so a correction that just says what to fix without saying why teaches nothing. Deliver that why gently and briefly, the way a kind coach would, never as a verdict from on high. The praise-strips / correction-explains split is enforced as `/voice` personal pattern #40, which every posted review summary passes through. +The asymmetry: the summary drops praise entirely, but a finding, change-request, or inline coaching note *always* gets the why. Behavior only changes when the reason lands, so a correction that just says what to fix without saying why teaches nothing. Deliver that why gently and briefly, the way a kind coach would, never as a verdict from on high. The drop-praise / correction-explains split is enforced as `/voice` personal pattern #40, which every posted review summary passes through. Good: -- "Nice, clean, good coverage. One small naming point inline. Approving." -- "Clean shape, tests cover the right edges. Approving." -- "Solid. One blocker inline — see the auth gap. Request changes." +- "One small naming point inline, not a blocker. Approving." +- "One edge-case gap noted inline, minor. Approving." +- "One blocker inline — see the auth gap. Request changes." +- "Approving." (nothing to flag) -Bad (chatty, padded, marketing-adjacent): +Bad (chatty, padded, or any praise on an approve): - "Great work overall! This is a really clean addition. The OneToOne relationship behaves as expected, the migration is correctly dependent on 0028, CI is green across all backend/frontend checks, and the tests cover Normal/Boundary/Error cases. One small naming nit inline — fine to roll into a follow-up." +- "Clean, good coverage. One naming point inline. Approving." — the leading praise is now cut; lead with the pointer instead. +- "Solid fix. Approving." — a bare positive is still praise; drop it. If specific praise lands somewhere, surface it as a single inline comment on the relevant line, not in the summary body. The summary stays scannable; the inline pins carry the specifics. diff --git a/scripts/install-ai.sh b/scripts/install-ai.sh index 7007eed..0c90f64 100755 --- a/scripts/install-ai.sh +++ b/scripts/install-ai.sh @@ -141,6 +141,13 @@ today="$(date +%Y-%m-%d)" sed "s|\[Project Name\]|$project_name|g; s|\[Date\]|$today|g" \ "$CANONICAL/notes.org" > "$project/.ai/notes.org" +# Seed AGENTS.md — the runtime-neutral agent entry file (thin pointer at +# protocols.org, rules, and /name resolution) for Codex-style harnesses. +# Seed-only, like CLAUDE.md: project-owned after bootstrap, never overwritten. +if [ ! -e "$project/AGENTS.md" ]; then + cp "$REPO/claude-templates/AGENTS.md" "$project/AGENTS.md" +fi + # Tracking setup. case "$track_mode" in track) diff --git a/scripts/install-lang.sh b/scripts/install-lang.sh index 2f38fcd..6e8d806 100755 --- a/scripts/install-lang.sh +++ b/scripts/install-lang.sh @@ -33,6 +33,75 @@ fi # Resolve to absolute path PROJECT="$(cd "$PROJECT" && pwd)" +# 0. Cross-bundle collision guard. +# +# Several bundles ship a file at the same path. gitignore-add.txt is appended +# and deduped, and CLAUDE.md is seed-only, so both compose across bundles. Three +# do not: claude/settings.json and githooks/* are installed with `cp -rT` +# (always overwrite), and coverage-makefile.txt is seeded under a fixed name. So +# installing a second bundle replaced the first's hook wiring and pre-commit +# while printing [ok], so a project could lose its paren check or secret scan +# and read the output as success. Refuse instead, naming what would go. +# +# Detection is by rule fingerprint, matching sync-language-bundle.sh: a project +# has bundle X iff one of X's own rule files is in .claude/rules/. No marker +# file, so this works on installs that predate the guard. +project_has_bundle() { + local b="$1" rf + for rf in "$REPO_ROOT/languages/$b/claude/rules"/*.md; do + [ -f "$rf" ] || continue + [ -f "$PROJECT/.claude/rules/$(basename "$rf")" ] && return 0 + done + return 1 +} + +# Files $1's bundle and $LANG both ship, and that install would overwrite. +shared_overwritten_files() { + local other="$1" rel + for rel in claude/settings.json coverage-makefile.txt; do + [ -f "$SRC/$rel" ] && [ -f "$REPO_ROOT/languages/$other/$rel" ] \ + && echo " $(printf '%s' "$rel" | sed 's|^claude/|.claude/|')" + done + if [ -d "$SRC/githooks" ] && [ -d "$REPO_ROOT/languages/$other/githooks" ]; then + for rel in "$SRC/githooks"/*; do + [ -f "$rel" ] || continue + [ -f "$REPO_ROOT/languages/$other/githooks/$(basename "$rel")" ] \ + && echo " githooks/$(basename "$rel")" + done + fi +} + +if [ "$FORCE" != "1" ]; then + collisions="" + for other_dir in "$REPO_ROOT/languages"/*/; do + other="$(basename "$other_dir")" + [ "$other" = "$LANG" ] && continue + [ -d "$other_dir/claude/rules" ] || continue + project_has_bundle "$other" || continue + files="$(shared_overwritten_files "$other")" + [ -n "$files" ] && collisions="${collisions}The '$other' bundle is already installed here. Installing '$LANG' would replace: +${files} +" + done + + if [ -n "$collisions" ]; then + { + echo "ERROR: bundle collision. Refusing to install '$LANG' into $PROJECT" + echo + printf '%s' "$collisions" + echo "Both bundles ship these files, and installing overwrites rather than merges," + echo "so the bundle already here would silently lose them." + echo + echo "Whether a project can carry two bundles at once is an open question." + echo "Until it's settled, install one bundle per project." + echo + echo "To override: re-run with FORCE=1. That also re-seeds CLAUDE.md from the" + echo "bundle template, which overwrites any project-specific edits to it." + } >&2 + exit 1 + fi +fi + echo "Installing '$LANG' ruleset into $PROJECT" # 1. Generic rules from claude-rules/ (shared across all languages) diff --git a/scripts/remove.sh b/scripts/remove.sh index 3d8b7e4..3d8b7e4 100644..100755 --- a/scripts/remove.sh +++ b/scripts/remove.sh diff --git a/scripts/tests/agent-page.bats b/scripts/tests/agent-page.bats new file mode 100644 index 0000000..071e4b9 --- /dev/null +++ b/scripts/tests/agent-page.bats @@ -0,0 +1,68 @@ +#!/usr/bin/env bats +# agent-page — the runtime-neutral phone pager. Pages Craig over Signal from +# any machine on the tailnet: runs signal-cli directly on velox (where the +# pager identity lives), ssh-relays to velox from everywhere else. These tests +# stub ssh/uname/signal-cli on PATH to verify command construction without a +# network or a phone. + +setup() { + REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../.." && pwd)" + PAGE="$REPO_ROOT/claude-templates/bin/agent-page" + STUBS="$(mktemp -d)" + LOG="$STUBS/calls.log" + cat > "$STUBS/ssh" <<EOF +#!/bin/bash +echo "ssh \$*" >> "$LOG" +exit 0 +EOF + cat > "$STUBS/signal-cli" <<EOF +#!/bin/bash +echo "signal-cli \$*" >> "$LOG" +exit 0 +EOF + chmod +x "$STUBS/ssh" "$STUBS/signal-cli" +} + +teardown() { + rm -rf "$STUBS" +} + +@test "no message exits 2 with usage" { + run bash "$PAGE" + [ "$status" -eq 2 ] + [[ "$output" == *"usage"* ]] +} + +@test "relays through ssh to velox with the pager account and Craig's UUID" { + PATH="$STUBS:$PATH" run bash "$PAGE" build finished + [ "$status" -eq 0 ] + grep -q "^ssh .*velox" "$LOG" + grep -q "15045173983" "$LOG" + grep -q "b1b5601e-6126-47f8-afaa-0a59f5188fde" "$LOG" + # printf %q escapes the space, so the relayed message reads build\ finished. + grep -qF 'build\ finished' "$LOG" +} + +@test "on velox itself, calls signal-cli directly (no ssh)" { + cat > "$STUBS/uname" <<'EOF' +#!/bin/bash +[ "$1" = "-n" ] && { echo velox; exit 0; } +exec /usr/bin/uname "$@" +EOF + chmod +x "$STUBS/uname" + PATH="$STUBS:$PATH" run bash "$PAGE" hello + [ "$status" -eq 0 ] + grep -q "^signal-cli " "$LOG" + ! grep -q "^ssh " "$LOG" +} + +@test "a failed relay reports the desktop fallback and propagates failure" { + cat > "$STUBS/ssh" <<'EOF' +#!/bin/bash +exit 255 +EOF + chmod +x "$STUBS/ssh" + PATH="$STUBS:$PATH" run bash "$PAGE" urgent thing + [ "$status" -ne 0 ] + [[ "$output" == *"notify"* ]] +} diff --git a/scripts/tests/ai-launcher-runtime.bats b/scripts/tests/ai-launcher-runtime.bats new file mode 100644 index 0000000..3f0ad69 --- /dev/null +++ b/scripts/tests/ai-launcher-runtime.bats @@ -0,0 +1,97 @@ +#!/usr/bin/env bats +# The ai launcher's --runtime flag: pick the agent CLI (claude, codex) that +# a project window launches. Part of the generic-agent-runtime arc — the +# tmux-side counterpart of .emacs.d's ai-term multi-LLM handoff. The +# --print-launch mode exists for exactly these tests: it prints the launch +# command a real run would send to the pane, without touching tmux or fzf. + +setup() { + REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../.." && pwd)" + AI="$REPO_ROOT/claude-templates/bin/ai" + PROJ="$(mktemp -d)" + mkdir -p "$PROJ/.ai" + touch "$PROJ/.ai/protocols.org" + STUB_BIN="$(mktemp -d)" +} + +teardown() { + rm -rf "$PROJ" "$STUB_BIN" +} + +@test "default runtime launches claude" { + run bash "$AI" --print-launch "$PROJ" + [ "$status" -eq 0 ] + [[ "$output" == claude\ * ]] + [[ "$output" == *"protocols.org"* ]] +} + +@test "--runtime codex launches codex with the same opening line" { + run bash "$AI" --runtime codex --print-launch "$PROJ" + [ "$status" -eq 0 ] + [[ "$output" == codex\ * ]] + [[ "$output" == *"protocols.org"* ]] + [[ "$output" == *"$(basename "$PROJ")"* ]] +} + +@test "AI_RUNTIME env selects the runtime without the flag" { + AI_RUNTIME=codex run bash "$AI" --print-launch "$PROJ" + [ "$status" -eq 0 ] + [[ "$output" == codex\ * ]] +} + +@test "--runtime local launches codex --oss over ollama with the default local model" { + run bash "$AI" --runtime local --print-launch "$PROJ" + [ "$status" -eq 0 ] + [[ "$output" == "codex --oss --local-provider=ollama -m gpt-oss:120b "* ]] + [[ "$output" == *"protocols.org"* ]] +} + +@test "AI_LOCAL_MODEL overrides the local runtime's model" { + AI_LOCAL_MODEL=qwen3-coder:30b run bash "$AI" --runtime local --print-launch "$PROJ" + [ "$status" -eq 0 ] + [[ "$output" == "codex --oss --local-provider=ollama -m qwen3-coder:30b "* ]] +} + +@test "an unknown runtime errors and names the valid ones" { + run bash "$AI" --runtime frobnitz --print-launch "$PROJ" + [ "$status" -eq 2 ] + [[ "$output" == *"claude"* ]] + [[ "$output" == *"codex"* ]] +} + +@test "--print-launch refuses a non-project directory" { + run bash "$AI" --print-launch "$BATS_TEST_TMPDIR" + [ "$status" -eq 1 ] + [[ "$output" == *"protocols.org"* ]] +} + +@test "--print-runtimes lists claude, codex, and one line per ollama model" { + cat > "$STUB_BIN/ollama" <<'STUB' +#!/bin/bash +if [ "$1" = "list" ]; then + printf 'NAME ID SIZE MODIFIED\n' + printf 'gpt-oss:120b aaa 65 GB 1 hour ago\n' + printf 'qwen3-coder:30b bbb 18 GB 1 hour ago\n' +fi +STUB + chmod +x "$STUB_BIN/ollama" + PATH="$STUB_BIN:$PATH" run bash "$AI" --print-runtimes + [ "$status" -eq 0 ] + [ "${lines[0]%% *}" = "claude" ] + [[ "$output" == *"codex"* ]] + [[ "$output" == *"local:gpt-oss:120b"* ]] + [[ "$output" == *"local:qwen3-coder:30b"* ]] +} + +@test "a dead ollama server just drops the local lines" { + cat > "$STUB_BIN/ollama" <<'STUB' +#!/bin/bash +echo "Error: could not connect to a running Ollama instance" >&2 +exit 1 +STUB + chmod +x "$STUB_BIN/ollama" + PATH="$STUB_BIN:$PATH" run bash "$AI" --print-runtimes + [ "$status" -eq 0 ] + [[ "$output" == *"claude"* ]] + [[ "$output" != *"local:"* ]] +} diff --git a/scripts/tests/install-agents-entry.bats b/scripts/tests/install-agents-entry.bats new file mode 100644 index 0000000..03343a6 --- /dev/null +++ b/scripts/tests/install-agents-entry.bats @@ -0,0 +1,51 @@ +#!/usr/bin/env bats +# make install must link the runtime-neutral agent entry file (AGENTS.md) +# into CODEX_DIR so Codex-style harnesses bootstrap from the same +# protocols/rules/skills the Claude side reads. The thin-pointer shape and +# the decision trail live in docs/design/2026-07-13-runtime-portability- +# inventories.org and the generic-agent-runtime task. + +setup() { + REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../.." && pwd)" + TMPHOME="$(mktemp -d)" +} + +teardown() { + rm -rf "$TMPHOME" +} + +run_install() { + make -C "$REPO_ROOT" install \ + SKILLS_DIR="$TMPHOME/skills" \ + RULES_DIR="$TMPHOME/rules" \ + HOOKS_DIR="$TMPHOME/hooks" \ + CLAUDE_DIR="$TMPHOME/claude" \ + CODEX_DIR="$TMPHOME/codex" \ + LOCAL_BIN="$TMPHOME/bin" +} + +@test "install links AGENTS.md into CODEX_DIR" { + run run_install + [ "$status" -eq 0 ] + [ -L "$TMPHOME/codex/AGENTS.md" ] + grep -q "protocols.org" "$TMPHOME/codex/AGENTS.md" +} + +@test "install is idempotent on the agent entry (second run skips)" { + run run_install + [ "$status" -eq 0 ] + run run_install + [ "$status" -eq 0 ] + [[ "$output" == *"skip AGENTS.md (already linked)"* ]] + [ -L "$TMPHOME/codex/AGENTS.md" ] +} + +@test "install warns on a non-symlink AGENTS.md collision and leaves it alone" { + mkdir -p "$TMPHOME/codex" + echo "hand-written entry" > "$TMPHOME/codex/AGENTS.md" + run run_install + [ "$status" -eq 0 ] + [[ "$output" == *"WARN AGENTS.md exists and is not a symlink"* ]] + [ ! -L "$TMPHOME/codex/AGENTS.md" ] + grep -q "hand-written entry" "$TMPHOME/codex/AGENTS.md" +} diff --git a/scripts/tests/install-ai.bats b/scripts/tests/install-ai.bats index 8e91770..a7eb3c0 100644 --- a/scripts/tests/install-ai.bats +++ b/scripts/tests/install-ai.bats @@ -149,3 +149,26 @@ EOF [ -d "$TEST_HOME/code/pickme/.ai" ] [ ! -d "$TEST_HOME/code/skipme/.ai" ] } + +@test "install-ai: seeds AGENTS.md at the project root" { + mkdir -p "$TEST_HOME/code/fresh" + (cd "$TEST_HOME/code/fresh" && git init -q) + + run bash "$INSTALL_AI" --gitignore "$TEST_HOME/code/fresh" + + [ "$status" -eq 0 ] + [ -f "$TEST_HOME/code/fresh/AGENTS.md" ] + grep -q "protocols.org" "$TEST_HOME/code/fresh/AGENTS.md" +} + +@test "install-ai: never overwrites an existing AGENTS.md" { + mkdir -p "$TEST_HOME/code/fresh" + (cd "$TEST_HOME/code/fresh" && git init -q) + echo "project-owned entry file" > "$TEST_HOME/code/fresh/AGENTS.md" + + run bash "$INSTALL_AI" --gitignore "$TEST_HOME/code/fresh" + + [ "$status" -eq 0 ] + grep -q "project-owned entry file" "$TEST_HOME/code/fresh/AGENTS.md" + ! grep -q "protocols.org" "$TEST_HOME/code/fresh/AGENTS.md" +} diff --git a/scripts/tests/install-lang-collision.bats b/scripts/tests/install-lang-collision.bats new file mode 100644 index 0000000..36abb5b --- /dev/null +++ b/scripts/tests/install-lang-collision.bats @@ -0,0 +1,112 @@ +#!/usr/bin/env bats +# Tests for install-lang's cross-bundle collision guard. +# +# Several bundles ship files at the same path. gitignore-add.txt merges +# (appended, deduped) and CLAUDE.md is seed-only, so both compose across +# bundles. Three do not: +# +# claude/settings.json elisp, bash, go — cp -rT, silently overwritten +# githooks/pre-commit elisp, bash, go — cp -rT, silently overwritten +# coverage-makefile.txt 4 bundles — [skip]ped, fragment dropped +# +# Installing a second bundle used to replace the first's settings.json and +# pre-commit while printing [ok], so a project could lose its paren check or +# secret scan and read the output as success. The guard refuses instead, naming +# what would be replaced. FORCE=1 still overrides. + +INSTALL="${BATS_TEST_DIRNAME}/../install-lang.sh" + +setup() { + PROJ="$(mktemp -d)" + git init -q "$PROJ" +} + +teardown() { + [ -n "${PROJ:-}" ] && rm -rf "$PROJ" +} + +# ---- Normal: single-bundle installs are unaffected ---- + +@test "install-lang: a fresh single-bundle install succeeds" { + run bash "$INSTALL" elisp "$PROJ" + [ "$status" -eq 0 ] + [ -f "$PROJ/.claude/settings.json" ] + grep -q 'validate-el.sh' "$PROJ/.claude/settings.json" +} + +@test "install-lang: reinstalling the SAME bundle is idempotent, not a collision" { + bash "$INSTALL" elisp "$PROJ" + run bash "$INSTALL" elisp "$PROJ" + [ "$status" -eq 0 ] + [[ "$output" != *"collision"* ]] + grep -q 'validate-el.sh' "$PROJ/.claude/settings.json" +} + +# ---- The guard: a second, different bundle must not silently replace ---- + +@test "install-lang: a second bundle sharing settings.json and githooks is refused" { + bash "$INSTALL" elisp "$PROJ" + run bash "$INSTALL" bash "$PROJ" + [ "$status" -ne 0 ] || { echo "second bundle installed without refusal"; return 1; } + [[ "$output" == *"elisp"* ]] || { echo "refusal does not name the existing bundle"; return 1; } +} + +@test "install-lang: the refusal names each file that would be replaced" { + bash "$INSTALL" elisp "$PROJ" + run bash "$INSTALL" bash "$PROJ" + [[ "$output" == *"settings.json"* ]] || { echo "refusal omits settings.json"; return 1; } + [[ "$output" == *"pre-commit"* ]] || { echo "refusal omits githooks/pre-commit"; return 1; } +} + +@test "install-lang: a refused install leaves the first bundle intact" { + bash "$INSTALL" elisp "$PROJ" + bash "$INSTALL" bash "$PROJ" || true + grep -q 'validate-el.sh' "$PROJ/.claude/settings.json" \ + || { echo "elisp settings.json was replaced despite refusal"; return 1; } + grep -q 'check-parens' "$PROJ/githooks/pre-commit" \ + || { echo "elisp pre-commit was replaced despite refusal"; return 1; } +} + +@test "install-lang: bundles colliding only on coverage-makefile.txt are refused too" { + # python and typescript ship no settings.json or githooks, but both ship a + # coverage fragment. The second one's used to be silently dropped. + bash "$INSTALL" python "$PROJ" + run bash "$INSTALL" typescript "$PROJ" + [ "$status" -ne 0 ] || { echo "typescript installed over python's coverage fragment"; return 1; } + [[ "$output" == *"coverage-makefile.txt"* ]] +} + +@test "install-lang: two bundles that share no overwritten file install together" { + # bash ships settings.json + githooks and no coverage fragment; python ships + # only the coverage fragment. Nothing overlaps, so the guard must stay out of + # the way. This is the path where a false refusal would be easiest to + # introduce: the bundle IS detected, and only the empty file-list stops it. + bash "$INSTALL" bash "$PROJ" + run bash "$INSTALL" python "$PROJ" + [ "$status" -eq 0 ] || { echo "guard falsely refused a non-colliding pair: $output"; return 1; } + [ -f "$PROJ/coverage-makefile.txt" ] || { echo "python's coverage fragment did not land"; return 1; } + # bash's config survives untouched. + grep -q 'validate-bash.sh' "$PROJ/.claude/settings.json" + [ -f "$PROJ/.claude/rules/bash.md" ] && [ -f "$PROJ/.claude/rules/python-testing.md" ] +} + +# ---- The escape hatch ---- + +@test "install-lang: FORCE=1 overrides the collision guard" { + bash "$INSTALL" elisp "$PROJ" + run bash "$INSTALL" bash "$PROJ" 1 + [ "$status" -eq 0 ] || { echo "FORCE=1 did not override: $output"; return 1; } + grep -q 'validate-bash.sh' "$PROJ/.claude/settings.json" +} + +@test "install-lang: the refusal points at FORCE=1 and warns it re-seeds CLAUDE.md" { + bash "$INSTALL" elisp "$PROJ" + run bash "$INSTALL" bash "$PROJ" + # Assert the refusal fired first: the pre-existing "[skip] CLAUDE.md already + # exists (use FORCE=1 to overwrite)" line mentions both strings on its own, so + # without this the test passes against an unguarded install. + [ "$status" -ne 0 ] || { echo "no refusal fired"; return 1; } + refusal="$(printf '%s\n' "$output" | grep -v '^ \[skip\]')" + [[ "$refusal" == *"FORCE=1"* ]] || { echo "refusal does not name the override"; return 1; } + [[ "$refusal" == *"CLAUDE.md"* ]] || { echo "refusal does not warn about the CLAUDE.md re-seed"; return 1; } +} diff --git a/scripts/tests/pre-commit-secret-scan.bats b/scripts/tests/pre-commit-secret-scan.bats new file mode 100644 index 0000000..013129e --- /dev/null +++ b/scripts/tests/pre-commit-secret-scan.bats @@ -0,0 +1,144 @@ +#!/usr/bin/env bats +# Tests for the secret-scan block shared by the elisp, bash, and go pre-commit +# hooks. The block greps added lines in the staged diff for credential +# patterns; a hit blocks the commit (exit 1), a clean scan falls through to the +# variant's language check (exit 0). +# +# Every case stages a .txt file, so the language checks that follow the scan +# (check-parens, shellcheck, gofmt) all skip and the scan is what's under test. +# +# The two boundary cases exist because of a live false-positive in a downstream +# project: an embedded PNG sprite data URI blocked a real commit and forced +# --no-verify. Root cause was `grep -iE` applying case-insensitivity to the +# fixed-case AWS token AKIA[0-9A-Z]{16}, so any mixed-case 20-char run inside a +# random base64 blob matched. Measured at ~6% of 100KB blobs; case-sensitive +# matching drops it to 0 across ~10MB. + +VARIANTS="elisp bash go" + +setup() { + REPO="$(mktemp -d)" + cd "$REPO" || return 1 + git init -q . + git config user.email t@example.com + git config user.name Test +} + +teardown() { + cd /tmp || true + [ -n "${REPO:-}" ] && rm -rf "$REPO" +} + +# Stage $2 as the content of file $1 (default staged.txt). +stage() { + local file="${2:-staged.txt}" + printf '%s\n' "$1" > "$file" + git add "$file" +} + +# Run a variant's hook in the temp repo. $1 = variant name. +run_hook() { + bash "${BATS_TEST_DIRNAME}/../../languages/$1/githooks/pre-commit" +} + +# ---- Normal: real secrets still block, clean content still passes ---- + +@test "secret-scan: clean content passes in every variant" { + stage 'const greeting = "hello world";' + for v in $VARIANTS; do + run run_hook "$v" + [ "$status" -eq 0 ] || { echo "$v blocked clean content: $output"; return 1; } + done +} + +@test "secret-scan: a real uppercase AWS access key blocks in every variant" { + stage 'aws_key = "AKIAIOSFODNN7EXAMPLE"' + for v in $VARIANTS; do + run run_hook "$v" + [ "$status" -eq 1 ] || { echo "$v missed an AWS key"; return 1; } + [[ "$output" == *"potential secret"* ]] + done +} + +@test "secret-scan: a keyword=value credential blocks in every variant" { + stage 'api_key: "sk_live_9f3b2a7c1e4d8f0a6b5"' + for v in $VARIANTS; do + run run_hook "$v" + [ "$status" -eq 1 ] || { echo "$v missed an api_key assignment"; return 1; } + done +} + +@test "secret-scan: a PEM private-key header blocks in every variant" { + stage '-----BEGIN RSA PRIVATE KEY-----' + for v in $VARIANTS; do + run run_hook "$v" + [ "$status" -eq 1 ] || { echo "$v missed a PEM header"; return 1; } + done +} + +# ---- Boundary: the case-sensitivity fix ---- + +@test "secret-scan: a lowercase akia-like run does not block (AWS keys are uppercase)" { + # Under `grep -iE` this matched the AKIA token and blocked a legitimate commit. + stage 'const blob = "akiaiosfodnn7examplexyz";' + for v in $VARIANTS; do + run run_hook "$v" + [ "$status" -eq 0 ] || { echo "$v false-positived on a lowercase run: $output"; return 1; } + done +} + +@test "secret-scan: an embedded base64 data URI carrying a mixed-case akia run does not block" { + # The live failure: a sprite blob whose random base64 contained a mixed-case + # 20-char run. Case-sensitive matching is what clears it. + stage 'const SPRITE = "data:image/png;base64,iVBORw0KGgoAkIaIOSFODNN7ExAMPLEqQmCC";' + for v in $VARIANTS; do + run run_hook "$v" + [ "$status" -eq 0 ] || { echo "$v false-positived on a sprite data URI: $output"; return 1; } + done +} + +# ---- Boundary: the scan must not go blind on data-URI lines ---- + +@test "secret-scan: a real credential sharing a line with a base64 data URI still blocks" { + # Minified bundles put a whole file on one line, so a data URI and a real key + # can share it. Skipping any line containing ';base64,' would hide the key. + stage 'const S="data:image/png;base64,iVBORw0KGgoAAAANS";const c={api_key:"sk_live_9f3b2a7c1e4d8f0a6b5"};' + for v in $VARIANTS; do + run run_hook "$v" + [ "$status" -eq 1 ] || { echo "$v went blind on a data-URI line and missed the key"; return 1; } + done +} + +@test "secret-scan: a line matching both passes is reported once, not twice" { + # The scan runs a case-sensitive and a case-insensitive pass. A line carrying + # both an AWS key and a keyword=value credential hits both; reporting it twice + # reads as two separate leaks. + stage 'api_key = "AKIAIOSFODNN7EXAMPLE_padding"' + for v in $VARIANTS; do + run run_hook "$v" + [ "$status" -eq 1 ] + hits="$(printf '%s\n' "$output" | grep -c 'AKIAIOSFODNN7EXAMPLE_padding')" + [ "$hits" -eq 1 ] || { echo "$v reported the line $hits times, want 1"; return 1; } + done +} + +# ---- Error / edge ---- + +@test "secret-scan: an empty staged diff passes" { + for v in $VARIANTS; do + run run_hook "$v" + [ "$status" -eq 0 ] || { echo "$v failed on an empty diff: $output"; return 1; } + done +} + +@test "secret-scan: a secret only on a removed line does not block" { + # The scan reads added lines. Deleting a key should never block the deletion. + stage 'aws_key = "AKIAIOSFODNN7EXAMPLE"' + git commit -qm "seed" --no-verify + printf 'clean\n' > staged.txt + git add staged.txt + for v in $VARIANTS; do + run run_hook "$v" + [ "$status" -eq 0 ] || { echo "$v blocked a removal: $output"; return 1; } + done +} @@ -39,16 +39,179 @@ Tags are assigned and refreshed by =task-audit=; =task-review= keeps them honest * Rulesets Open Work +** TODO [#B] Sentry workflow — build from spec :feature: +Build the sentry supervisor workflow from the READY-gated spec: +[[file:docs/specs/2026-07-14-sentry-workflow-spec.org][sentry workflow spec]] (DRAFT as of 2026-07-14; needs spec-review first). +Four phases: agent-lock helper + bats, sentry.org engine, companion +reconciliations (knowledge-base.md, roam-sync.sh header, inbox.org, +triage-intake.org), live trial night on rulesets. Origin: work project's +proposal, [[file:docs/design/2026-07-14-sentry-workflow-proposal.org][docs/design/2026-07-14-sentry-workflow-proposal.org]]. All nine +design decisions resolved with Craig 2026-07-14 (recorded in the spec). + +** TODO [#C] Polyglot projects — supported, or refused? :spec: +SCHEDULED: <2026-07-20 Mon> +Do we support more than one language bundle per project? The honest answer today +is "partly, by accident." The collision guard added 2026-07-16 refuses a +*colliding* second bundle rather than silently replacing the first's config, but +a non-overlapping pair still installs fine: bash ships =settings.json= + +githooks and no coverage fragment, python ships only a coverage fragment, so +=bash= + =python= composes cleanly today and yields a real polyglot project with +both rule sets. So the line isn't polyglot-vs-not, it's overlap-vs-not — and +nobody chose that line, it fell out of which bundle happens to ship what. Origin: +home's report after scaffolding clock-panel with python + typescript, +[[file:docs/design/2026-07-16-polyglot-bundle-collision.txt][docs/design/2026-07-16-polyglot-bundle-collision.txt]]. + +Pair this with the subproject scouting below — it's the same question in a +different costume ("which projects would actually be polyglot, and why"), so +they should be one conversation. + +The three options, in the order they'd be weighed: + +1. *Unsupported, explicitly.* Keep the guard as the answer. Cheapest, and + matches how little polyglot exists (one project, clock-panel). +2. *Supported.* Needs per-bundle filenames, a merged =settings.json= (the hooks + arrays compose rather than clobber), composed githooks, and namespaced + Makefile targets with a =coverage= aggregate. This is the real work. +3. *Case-by-case.* Support the pairs that come up, refuse the rest. + +What the decision needs to know: + +- *The target-name collision is the deeper half* (home's point, and it's right). + Every bundle's fragment defines =coverage:= and =coverage-summary:=, so even + with both files present a polyglot project can't paste both into one Makefile. + Renaming files doesn't fix it. +- *Only three of five shared filenames actually collide.* =gitignore-add.txt= + (5 bundles) appends deduped and composes. =CLAUDE.md= (3) is seed-only, and + its fallback comment shows multi-bundle was already considered there. + =claude/settings.json= (3), =githooks/*= (3), and =coverage-makefile.txt= (4) + are the real ones. +- *=FORCE=1= is a poor escape hatch* (home's catch): it also re-seeds + =CLAUDE.md=, which is destructive on a customized project. If polyglot + becomes supported, the override wants to be its own flag. + +** TODO [#C] Subproject pattern — promote to claude-rules? :spec: +SCHEDULED: <2026-07-20 Mon> +home proposes promoting its subproject pattern (a former standalone project +folded into a parent, living as a self-contained subdir sharing the parent's +=.ai/= scope) into the rules layer: vocabulary, the read-first +=<subproject>/<subproject>-brief.org= convention, the parent-vs-subproject +content criterion ("one fact, one home"), and create/archive criteria. +Proposal + home's full instance: +[[file:docs/design/2026-07-15-subproject-pattern-proposal.org][proposal]], +[[file:docs/design/2026-07-15-subprojects-convention-home-instance.org][home's convention doc]]. + +Deferred 2026-07-16 rather than promoted. *Craig's framing:* he wants to scout +which projects would actually get subprojects, and why, before we shape a rule. +If he hasn't done that scouting by the time this comes up, offer to do it +together — brainstorm the candidates, then explore the reasons behind each. That +evidence decides it: either we drop the pattern, or we know enough to adjust it +so it's effective. Don't shape the rule before the scouting. + +*Review findings from the 2026-07-16 pass* (the inputs the decision needs): + +- *N=1.* home is the only project with subprojects, across all 27 =.ai= scopes; + its nine all came from the single 2026-06-11 fold. This is the fact the + scouting tests. +- *Placement contradicts the proposal's own principle.* =claude-rules/*.md= + loads into every session of every project. home's doc argues the always-on + layer is "a tax paid whether or not it's relevant today" and depth belongs + "one open away". At 282 lines the doc would be the third-largest rule and add + ~11% to the always-on layer, so every .emacs.d / takuzu / chime session would + carry a one-project convention. +- *Precedent for the shape:* =patterns.md= (29 lines, explicit "don't carry the + catalog in context") and =docs-lifecycle.md= (75 lines, depth in a spec). + Thin rule + on-demand depth is the established answer. +- *Dangling reference:* the doc cites =claude-rules/git-hosting-privacy-model= + as authority for its shared-scope-safety criterion. No such file exists — the + real content is the gitignore-vs-track and public-reachability decision in + =protocols.org=. Fix before any promotion. +- *Instance vs rule:* the metrics, self-improvement log, kill criteria, rollout + dates, and adoption table are home's instance, not rule content. + +** TODO [#D] Cross-host roam conflict surfacing :feature: +Deferred from the sentry spec (Decision: host-local lock only). The +roam-write lock serializes same-host agents; a true cross-host race still +forks a sync-conflict file, surfaced only by roam-sync's rebase abort. +Investigate making the conflict loud (roam-sync retry-once, or a +cross-host lock node in the repo) when it proves frequent in practice. + +** TODO [#D] KB lesson-detection heuristic :spec: +Deferred from the sentry spec review (2026-07-14): sentry's KB promotion pass was cut from v1 because "promote recent durable lessons" had no defined lesson source, and an unattended judgment pass writing to the shared KB needs one that doesn't spam or go silent. Design the detection heuristic — candidate source: Session Log entries and memory-dir changes since the last fire, judged against knowledge-base.md's inclusion bar — and define how precision gets verified before the pass ships in a sentry vNext. Blocks re-adding the pass. + +** TODO [#D] Sentry unattended /schedule variant :feature: +Deferred from the sentry spec (Non-Goal). The v1 contract is interactive: +a live session Craig starts, /loop-driven. A cron variant firing with no +session needs its own contract (mutation rights, async surfacing, dedup +state across runs) — same open questions inbox.org logs for unattended +auto inbox zero. Take up only if the interactive shape proves too narrow. + +** TODO [#C] ai launcher hardening — bug hunt + refactor pass :refactor:solo: +:PROPERTIES: +:CREATED: [2026-07-13 Mon] +:LAST_REVIEWED: 2026-07-13 +:END: +From the roam inbox (2026-07-13): find bugs in =bin/ai= until no more are visible, then refactor until no worthwhile opportunities remain. The launcher gained =--runtime= + =--print-launch= + 6 bats tests today (04c3b29); this pass extends coverage over the legacy paths (multi_mode, sort_windows, git prep, window sorting) and cleans as it goes. The runtime-selection asks from the same capture are tracked on the generic-agent-runtime parent (claude/codex shipped; ollama/qwen pending the model-floor eval); an interactive runtime picker is fair game here if it stays lightweight. + +** TODO [#B] Extend ui-prototyping rule with build-to-prototype :feature: +:PROPERTIES: +:CREATED: [2026-07-11 Sat] +:LAST_REVIEWED: 2026-07-13 +:END: +.emacs.d proposal (2026-07-11, Craig-approved promotion), extending the ui-prototyping rule shipped tonight (=claude-rules/ui-prototyping.md=, 53f6ce6). The base rule covers research → prototypes → iterate → decisions-backed-by-a-prototype, but not the "build to the prototype" half. Add: after prototyping, fold what settled back into the spec and make the *build target* the prototype rather than the original spec text — the built feature should match the prototype, and any deviation is documented in a "Prototype & deviations" addendum section the build keeps current. Wire into four touch points: =brainstorm= Phase 3 (a UI design isn't "accepted" until it's been through the prototype loop; Next Steps say "build to the prototype"), =spec-create= (emit the deviations-addendum section for UI specs), =spec-response= (a UI spec decomposes into a prototype loop first, then build-to-prototype tasks), =start-work= (its verify phase drives the UI end-to-end; the bar becomes "matches the prototype," deviations logged). Worked example: the takuzu Emacs game — colored tiles read as all-black in the real terminal frame (dark faces + GUI-only box cursor), caught by a prototype loop on the first screenshot where build-to-spec would have shipped an unusable board. Multi-asset synced-rule change, so review-gated and needs a focused session; decide brainstorm-vs-lifecycle placement with Craig. Source: =inbox/PROCESSED-2026-07-11-0222-from-.emacs.d-ui-prototype-rule-proposal.org=. + +** TODO [#C] Roam-only startup for .ai projects — investigate :spec: +:PROPERTIES: +:CREATED: [2026-07-11 Sat] +:LAST_REVIEWED: 2026-07-13 +:END: +Open question from the roam inbox (2026-07-11): could the startup sequence for .ai projects use org-roam as the single store instead of local files? Potential gain is near-guaranteed shared information across projects — lessons and proven techniques on a common thread, scannable across similar projects. It would need a way to isolate a project from the rest. Weigh the pros/cons, the risk, and whether it's worth it before any build. Exploratory, no commitment yet. + +** TODO [#C] Keep WIP from blocking the template sync gate :feature: +:PROPERTIES: +:CREATED: [2026-07-11 Sat] +:LAST_REVIEWED: 2026-07-13 +:END: +From the roam inbox (2026-07-11): work in progress in one project shouldn't stop the sync gate. Idea: keep all diffs/changes in a =working/= directory and exclude it (and its subdirectories) from the sync gate. Many projects run at once, so their WIP files need to be grouped. Also add a per-project count of when the gate tripped, tracked as a metric to investigate. Distinct from the 2026-07-02 policy (untracked/gitignored changes already pass — this is about *tracked* WIP under =working/=). Verify how the gate detects dirtiness today before designing. + +** TODO [#C] Put install-ai on PATH, launchable as =install-ai= :chore:quick:solo: +:PROPERTIES: +:CREATED: [2026-07-11 Sat] +:LAST_REVIEWED: 2026-07-13 +:END: +From the roam inbox (2026-07-11): make install-ai launchable as =install-ai= (no =.sh=) from PATH. dotfiles needs a copy that stays in sync with the rulesets canonical — decide whether the startup script-sync already covers it or a dedicated mechanism is needed. + +** TODO [#B] Document (and own) the Signal pager :feature:spec: +:PROPERTIES: +:CREATED: [2026-07-11 Sat] +:LAST_REVIEWED: 2026-07-13 +:END: +home retired the ntfy phone-notification channel (phone-notify/phone-recv, self-hosted ntfy on ratio) on 2026-07-04 in favor of paging over Signal, and tore ntfy down. The Signal pager it replaced ntfy with is undocumented: no pager script in =~/.local/bin=, and =notify= doesn't reference Signal. What exists on ratio: signal-cli 0.14.5, account 404211. Deliverable: a documented Signal pager (send + read-replies), the signal-cli setup/account notes, and the sync path — the Signal equivalent of the retired ntfy runbook. Cross-machine tooling, so canonical home + docs belong in rulesets. + +RECONCILE FIRST: =protocols.org= "Paging Craig" already documents an agent-paging path via the *signal-mcp* tool (=send_message_to_user=, pager account +15045173983, Craig's UUID =b1b5601e-…=, verified 2026-06-30). home's handoff is about a *different* mechanism (signal-cli / account 404211, home's own paging). Decide whether these are one channel or two, and whether the signal-cli side needs its own runbook or should route through signal-mcp. Source: home handoff 2026-07-04 (=inbox/2026-07-04-1302-from-home-task-for-rulesets-document-and-decide.org=). Successor to the 2026-06-17 two-way-comms proposal. + +*** 2026-07-13 Mon @ 05:16:37 -0500 Folded home's ownership ack + current-state report into the reconcile scope +home confirmed (2026-07-11 reply) rulesets owns this task and the two-paths reconciliation is the right first step. New facts for the reconcile: from a home session on 2026-07-09, signal-mcp was NOT connected, and the local signal-cli is registered as Craig's own number — =send --note-to-self= returns a message id but produces no phone push. So home currently has no working ad-hoc page channel at all; whatever the runbook lands on must give home a live path. + +*** 2026-07-13 Mon @ 14:40:00 -0500 Added runtime-portability as a second motivation (Craig approved) +The MCP portability inventory ([[file:docs/design/2026-07-13-runtime-portability-inventories.org]]) found signal-mcp exists only claude.ai-side — no local config anywhere — so a non-Claude agent (Codex-style or local LLM) has no paging path at all. The signal-cli runbook this task produces is therefore also the runtime-neutral page channel, not just home's replacement for ntfy. + +*** 2026-07-13 Mon @ 18:30:19 -0500 agent-page shipped — every project now knows both channels +Craig's call: make paging universal and rename it the agent pager. NEW =claude-templates/bin/agent-page= (runs signal-cli directly on velox, ssh-relays from anywhere else, desktop-fallback hint on failure; 4 bats tests; live-verified from ratio through the real script). protocols.org "Paging Craig" rewritten around the two channels (notify desktop + agent-page phone; signal-mcp demoted to a velox-local nicety); page-me.org gained the phone section + fire-both guidance; work-the-backlog's end-of-set page names both surfaces; INDEX updated. Every project inherits via the startup sync + make install. Remaining here: the runbook proper, the receive timer, ssh-only vs linked-device. + +*** 2026-07-13 Mon @ 18:13:07 -0500 RECONCILED — one channel, on velox; live page verified end to end +The two-paths question is answered: there is ONE pager identity, +15045173983, registered in velox's signal-cli (account file 465310) — and signal-mcp is a locally-configured MCP server in velox's global ~/.claude.json (not claude.ai-side as the 14:40 entry inferred; it's just invisible from ratio, which is why home and this session couldn't find it). ratio's signal-cli holds only Craig's personal number (note-to-self, no push). Verified live today: =ssh velox 'signal-cli -a +15045173983 send -m … b1b5601e-6126-47f8-afaa-0a59f5188fde'= buzzed Craig's phone — his remembered CLI page was this same account on velox. Reliability findings for the runbook: both accounts throw receive-staleness warnings (velox 40 days, ratio 26; the signal protocol wants regular receives — a systemd receive timer on velox is the roam-sync-shaped fix), and the channel requires velox to be up. Remaining deliverables sharpened: the runbook (send + read-replies + receive timer + account notes); decide ssh-over-tailnet-only vs registering ratio as a linked device of the pager account; update protocols.org "Paging Craig" (it names signal-mcp as the only supported path — true only on velox; the ssh recipe is the cross-machine path) — shared-asset edit, own review pass. Interim recipe sent to home so it's unblocked today. + ** TODO [#C] KB orphan-node review pass :chore: :PROPERTIES: :CREATED: [2026-07-01 Wed] +:LAST_REVIEWED: 2026-07-13 :END: The 2026-07-01 kb-hygiene report listed 42 agent KB nodes with no inbound id: links (of 53 agent nodes; 0 conflicts, no duplicate titles). Orphan-ness alone isn't a defect — agent nodes are found by rg, not only by links — but a periodic pass is worth doing: prune nodes that aged out, merge near-duplicates, add id: links where clusters exist. Regenerate the list with the kb-hygiene script rather than trusting the snapshot. Propose deletions/merges to Craig before applying (auto-cleanup allowed only for :agent:-tagged nodes after approval, per knowledge-base.md). ** TODO [#B] Helper-agent instance support — concurrent same-project Claude :feature:spec: :PROPERTIES: :CREATED: [2026-06-11 Thu] -:LAST_REVIEWED: 2026-06-24 +:LAST_REVIEWED: 2026-07-13 :END: SPEC REVIEWED 2026-06-12: [[file:docs/design/2026-05-28-generic-agent-runtime-spec-review.org][Codex review]] now rates Phase 1.5 =Ready with caveats=. Before any build, keep the Emacs integration as a cross-project handoff to =~/.emacs.d=, preserve the three-ring gate (bats → sandbox drills → pilot project), and do not let startup/helper changes reach synced template paths until the live drills pass. @@ -89,30 +252,10 @@ DEPENDENCY QUESTION (Craig, 2026-06-15, resolved 2026-06-24 — see below): does *** 2026-06-24 Wed @ 00:30:32 -0400 RESOLVED — independent, unblocked (keyword VERIFY → TODO) Craig's call (2026-06-24): helper-instance is independent of the generic-runtime refactor and builds on its own. The shipped pieces and the remaining wiring are all shared-file concurrency-safety (two Edit writers, one file), orthogonal to which LLM runtime launches — none of it assumes the runtime-manifest / multi-runtime machinery of phases 2-6. One caveat: =ai --helper= overlaps the launcher refactor the generic-runtime arc plans, so keep that launcher change small and contained so the later refactor doesn't fight it. Now a buildable [#B] task behind its own three-ring gate (bats → sandbox drills → live pilot). -** DONE [#B] Wrap-up inbox/transcript routing to destination projects :feature:spec: -CLOSED: [2026-07-04 Sat] +** TODO [#B] Wrap-up routing — manual end-to-end validation :test: :PROPERTIES: -:CREATED: [2026-06-13 Sat] -:LAST_REVIEWED: 2026-06-24 +:LAST_REVIEWED: 2026-07-13 :END: -Optional wrap-up step that surfaces filed keepers belonging to another project, recommends a destination, and routes each to that project's =inbox/= via =inbox-send= (the destination's own =process-inbox= files it; transcript filing deferred to vNext). Spec: [[id:00b47414-2213-4a99-be35-48ceb266fc08][wrapup-routing-spec.org]] — Ready, [9/9] decisions. Source proposal: [[file:docs/design/2026-06-13-wrapup-inbox-transcript-routing-proposal.org]]. - -*** 2026-06-21 Sun @ 02:06:37 -0400 Spec-review + spec-response complete — Ready -Craig's review challenge reshaped the design from a direct cross-repo =todo.org= move to =inbox-send= delivery into the destination's inbox (safer: reuses the sanctioned cross-project path, gets provenance + per-project filing for free, degrades gracefully where a destination has an =inbox/= but no =todo.org=). D2/D3 superseded; D7 (inbox-send delivery), D8 (=:ROUTE_CANDIDATE:= marker at file time), D9 (local source removal + reject-flow recovery) added. Spec-review file consumed and deleted. Implementation-task breakdown filed below (spec-response Phase 6). - -*** 2026-06-24 Wed @ 00:21:20 -0400 Reconcile — marker sub-task repointed at inbox.org -The 2026-06-23 inbox consolidation (24ca58d) merged =process-inbox= + =monitor-inbox= + =inbox-zero= into one =inbox.org= engine (process/monitor/roam modes) and deleted the three old files. The =:ROUTE_CANDIDATE:= marker sub-task targeted =process-inbox.org='s Phase D — repointed it to =inbox.org= process mode (core §3 "File as TODO"). No build has started, so this is a target-rename only; the spec design is unaffected. - -*** 2026-06-28 Sun @ 13:02:42 -0400 Built the recommendation engine + destination discovery -Added =.ai/scripts/route_recommend.py= (canonical + mirror): pure =recommend(item, projects) → (destination, confidence)= with strong (name/path literal, word-boundary matched, dot-stripped alias aware), weak (distinctive name-token overlap), and none tiers; a multi-way top-tier tie downgrades to weak with a deterministic pick (most overlap, then alphabetical); empty list → none. The CLI (=--item=, =--exclude=) reuses =inbox-send.py='s =discover_projects= via importlib so the candidate set matches inbox-send's project universe. 13 tests (the five spec'd cases + boundary/path/strong-beats-weak + 3 sandboxed CLI integration tests), full =make test= green. Covers spec Phases 1 + 3. Next sub-tasks (=:ROUTE_CANDIDATE:= marker, wrap-up router) call this engine. - -*** 2026-07-02 Thu @ 00:36:12 -0400 Phases 2 + 4 + test surface landed — marker, router, route-batch helper -inbox.org's "File as TODO" disposition now runs route_recommend on each keeper and stamps =:ROUTE_CANDIDATE: <destination>= on strong/weak matches (none stamps nothing; local keepers stay unstamped) — spec Phase 2 / D8. wrap-it-up.org Step 3 gained the optional router directly after the inbox sanity check, with the gate-vs-optional split named in the prose: surface the batch (task / destination / delivery mode / confidence, weak visibly labeled), go/skip, empty set = zero interaction — spec Phase 4 / D7 / D9. The go path is mechanical: new =.ai/scripts/route-batch= (--list read-only, --go extracts the subtree minus the marker with children riding along and headings promoted, delivers via inbox-send for provenance, removes the local subtree only after a successful send; a failed send leaves the task in place and exits non-zero). Test surface: engine unit tests existed (13); route-batch adds a 9-test bats suite (list/backlog-exclusion, empty-set silence, list-modifies-nothing = skip semantics, delivery + provenance + children, local-task survival, drawer-minus-marker, inbox-without-todo.org delivery, empty go, failed-send recovery). cross-project.md notes the router as a sanctioned cross-project write path. make test green, sync clean. - -*** 2026-07-04 Sat @ 11:49:59 -0500 Flipped the spec to IMPLEMENTED; parent closed -The routing build shipped green: route_recommend.py (destination discovery + recommendation, 13 unit tests), the =:ROUTE_CANDIDATE:= marker in inbox process mode, the wrap-it-up router sub-step, and route-batch (9 bats tests). Spec [[id:00b47414-2213-4a99-be35-48ceb266fc08][wrapup-routing]] flipped DOING → IMPLEMENTED. Manual end-to-end validation and the transcript vNext promoted to their own tasks. - -** TODO [#B] Wrap-up routing — manual end-to-end validation :test: What we're verifying: a real keeper routes through a live wrap and the destination actually files it. The task-routing build shipped IMPLEMENTED 2026-07-04 (spec [[id:00b47414-2213-4a99-be35-48ceb266fc08][wrapup-routing]]); this confirms it works end to end across a real cross-project wrap. A failed check promotes to a bug. - In a project session, let process-inbox file a handoff whose home is a different project; confirm the local task carries =:ROUTE_CANDIDATE: <dest>=. - Run wrap-it-up; at the router sub-step, confirm the candidate is surfaced with the right destination + confidence, then choose "go". @@ -126,17 +269,20 @@ File a meeting recording into the destination =assets/= per =working-files.md=, ** TODO [#C] Multiple agent-source improvements :spec: :PROPERTIES: :CREATED: [2026-06-23 Tue] -:LAST_REVIEWED: 2026-06-24 +:LAST_REVIEWED: 2026-07-13 :END: Make the tooling agent-agnostic instead of Claude-specific. Three threads from Craig (roam 2026-06-23): (1) give the agent a name so workflows don't say "Claude" everywhere — a non-Claude agent (Codex) reading "you are Claude" gets confused; evaluate whether naming resolves the confusion or whether other spots also leak Claude-specificity. (2) Pull agent-neutral content out of Anthropic-specific files (=CLAUDE.md=) into a shared source that each agent's own entry file points to, so Codex (which runs more literal) reads the same rules; or link =CLAUDE.md= and the Codex equivalent to one source. Have Codex review the workflows for literal-reading wording gaps. (3) Send =.emacs.d= a note (inbox-send) to let =ai-term= launch Claude / Codex / a local ollama LLM, switchable seamlessly at session start. Spec-shaped — needs design before build. From the roam inbox 2026-06-23 (deferred from the 2026-06-21 session). *** 2026-06-24 Wed @ 00:21:20 -0400 Partial — agent-neutral wording sweep + thread-3 note landed Thread 2's wording half shipped in 6ad0442 (=refactor(rules): use agent-neutral language in shared rules=): agent-as-actor phrasing replaced with "the agent" across interaction.md, cross-project.md, triggers.md, working-files.md. Thread 3's note reached =.emacs.d=, whose 2026-06-23 inbox FYI confirms it received and filed the "multi-LLM support" ai-term handoff. Remaining and still TODO: thread 1 (give the agent a name), and thread 2's structural half (extract agent-neutral content into a shared source with a Codex entry-file pointer, then have Codex review the workflows for literal-reading gaps). +*** 2026-07-13 Mon @ 16:04:04 -0500 Thread 2's entry-file half landed via the runtime-portability build +The Codex entry-file pointer now exists: =claude-templates/AGENTS.md= (thin pointer at protocols.org + rules + /name resolution), linked to =~/.codex/AGENTS.md= by =make install= and seeded per-project by =install-ai.sh= (see the generic-agent-runtime parent's 2026-07-13 children). Remaining here: thread 1 (agent naming — the entry file's "you are this project's agent" phrasing is a start, not the whole answer) and the Codex literal-reading review of workflows. + ** TODO [#C] Flashcard tooling improvements :feature: :PROPERTIES: :CREATED: [2026-06-28 Sun] -:LAST_REVIEWED: 2026-06-28 +:LAST_REVIEWED: 2026-07-13 :END: Three flashcard-tooling tasks that all edit =flashcard-to-anki.py= and/or =flashcard-stats.py=, grouped so they get built together instead of colliding on the same files (prior sessions flagged the conflict risk). The Anki =#+TITLE= deck-name fix already landed (commit 060a938), so any preserved pre-fix script copy gets re-derived against the current canonical, never copied wholesale. The three children each ship independently. @@ -165,111 +311,17 @@ The work project edited two synced scripts locally as a stopgap (2026-06-17) and Note (2026-06-24): the Anki =#+TITLE= deck-name fix landed (commit 060a938) — =default_deck_name= is now =default_deck_name(input_path, org_text)= with a new docstring. The preserved 2026-06-17 =to-anki.py= predates that, so *don't* copy it wholesale (it would revert the title-fix). Re-derive the multi-tag changes against the current canonical =flashcard-to-anki.py= and keep the =#+TITLE= behavior. -** TODO [#C] coverage-summary.el documented as a local-only helper :chore: +** TODO [#C] coverage-summary.el documented as a local-only helper :chore:quick:solo: :PROPERTIES: :CREATED: [2026-06-22 Mon] -:LAST_REVIEWED: 2026-06-28 +:LAST_REVIEWED: 2026-07-13 :END: The elisp bundle installs =coverage-summary.el= into =.claude/scripts/=, gitignored in code projects, so CI can't run =make coverage-summary= against it. Decision (Craig, 2026-06-28): keep it in =.claude/scripts/= and document it as a local-only helper — don't ship it to a tracked =scripts/= dir, don't expect CI to run it. Remaining work (docs only, no move): state the local-only status in the script's header comment and wherever =make coverage-summary= is described, so the gitignored install reads as intentional rather than a gap. Note: emacs-wttrin rewrote its copy's header to claim a tracked =scripts/= home, which now contradicts this decision and should be reverted on their side. Surfaced 2026-06-21 during the coverage-summary autoloads bugfix (commit fb86736). -** DONE [#C] Check that memories are sync'd across machines via git :spec: -CLOSED: [2026-07-04 Sat] +** TODO [#C] Agent-KB / memory-sync — work + unknown-project write refusal :test: :PROPERTIES: -:LAST_REVIEWED: 2026-06-28 +:LAST_REVIEWED: 2026-07-13 :END: -v1 implemented end-to-end 2026-06-10 (Phases 0-4, all shipped + pushed) and the agent-KB spec is IMPLEMENTED. Both daily drivers (velox + ratio) carry the =roam.git= clone + sync-timer. Cross-machine sync verified as a full round-trip 2026-07-04: velox created a probe node 2026-07-01 and ratio held it; ratio then deleted it and velox pulled the deletion. Both clones sit at the same HEAD with zero sync-conflict files and both roam-sync timers active. The work/unknown-project write-refusal checks need live sessions in those projects and are tracked as a standalone manual-validation task below. -*** 2026-05-14 Thu @ 19:14:11 -0500 Investigate current memory storage - -Memory files live at -[[file:/home/cjennings/.claude/projects/-home-cjennings-code-rulesets/memory/][~/.claude/projects/-home-cjennings-code-rulesets/memory/]] -— four files including =MEMORY.md= and three individual entries -(=feedback_never_guess.md=, =project_ai_scripts_canonical_source.md=, -=reference_pdftools_venv.md=). The directory is a plain unmanaged dir -(no symlink, no enclosing git checkout). Neither -[[file:/home/cjennings/.claude/][~/.claude/]] itself nor any subtree -containing the project-memory dirs is tracked in -[[file:/home/cjennings/code/archsetup/][archsetup]] or -[[file:/home/cjennings/code/rulesets/][rulesets]]. Without a symlink -into a stowed or tracked location, memory files don't survive a new -machine setup or a dotfiles restore. - -Proposed setup: stow =~/.claude/projects= → -=archsetup/dotfiles/common/.claude/projects/= (path doesn't exist yet -— it's the target location pending VERIFY). -Create the destination in archsetup, move existing per-project -=projects/<encoded-cwd>/memory/= dirs there, run =stow= to link, then -commit + push archsetup. After that, every machine running =stow= -picks up the same memory tree. - -*** 2026-05-23 Sat @ 16:12:48 -0500 Decided: dedicated private repo, not stow -Worked through dotfiles → rulesets → dedicated repo. Dropped stow/dotfiles (machine config, wrong cadence) and rulesets (it's pulled first in every session, so memory edits would dirty its tree and skip the startup =git pull --ff-only=). Chose a dedicated private repo on cjennings.net: storage is unified there while recall stays per-project (the encoded-cwd subdirs), since pooling recall would hurt relevance and risk work-private facts surfacing in personal-project artifacts. - -*** 2026-05-23 Sat @ 16:12:48 -0500 Shipped: claude-memory.git + folded symlinks -Created bare =git@cjennings.net:claude-memory.git=, cloned to =~/.claude-memory= (later deleted in the reversal below), moved all 7 per-project =memory/= dirs in (54 files; work has 40) and replaced each live =~/.claude/projects/<enc>/memory= with a folded dir-symlink so new memory lands in the clone and a push syncs it. Added =link-claude-memory.sh= (idempotent — recreates the symlinks on a new machine after clone) + README. Private repo, never GitHub (carries work/DeepSat memory). Initial import pushed (=f496370=). - -*** 2026-05-24 Sun @ 01:53:35 -0500 Reversed the migration — back to unmanaged per-project memory -Cancelled the follow-up brainstorm and undid the dedicated-repo migration at Craig's call. Moved all 7 memory dirs back to =~/.claude/projects/<enc>/memory/= (content preserved), deleted the =~/.claude-memory= clone, and deleted the bare =claude-memory.git= on the server. Memory is back to its original at-risk state, so the task reopens at [#C] pending a direction. The brainstorm landed on a two-tier idea for whenever this resumes: promote general lessons into a rulesets-tracked file symlinked into =~/.claude/rules/= (loaded into every project natively, one repo), and keep project-specific memory under each project's own =.ai/memory/= (committed where =.ai/= is tracked, at-risk where it's gitignored). Not implemented. - -*** 2026-06-05 Fri @ 05:57:35 -0500 Pivot: adopt the existing org-roam KB as the shared agent substrate -Pressure-tested the two-tier idea, then Craig redirected: a shared org-roam knowledge base any project can read and write makes this simpler. Ground truth verified: =~/sync/org/roam/= already exists (484 org files, curated since 2023, Syncthing-synced, not git). So cross-machine sync is already solved, and the task stops being "build a memory-sync system" and becomes "point agents at the KB that already syncs." The dedicated-repo and two-tier approaches are both superseded for the storage+sync half. - -Wrote a one-page spec: [[id:08a5ec99-9e1e-40e4-8241-e8a41e9de49f][agent-knowledge-base-spec.org]] (originally docs/design/2026-06-05-org-roam-knowledge-base-spec.org; superseded by the 2026-06-10 spec-create rewrite at the new path). Five decisions, mechanics recommended: (1) KB is a queried substrate accessed as files (ripgrep + follow =[[id:]]= by grep), not via the org-roam package; (2) capture in harness memory, promote durable facts into the KB (same cadence as the pattern catalog) — resolves the at-risk problem since the valuable knowledge moves to the synced KB; (3) a =claude-rules/knowledge-base.md= pointer rule carries path/query/write-schema/boundary; (4) write schema = roam-valid node + =:agent:= filetag so agent notes stay distinguishable and index on the next =org-roam-db-sync=. The rules layer (=claude-rules/=, =CLAUDE.md=) is untouched — the KB replaces the memory tier, not the rules tier. - -*** 2026-06-10 Wed @ 14:29:20 -0500 Spec ratified — write boundary is option C; rewritten to spec-create format -Craig answered via cj annotations in the spec (2026-06-10): DECISION 5 is option C (read-shared, write-scoped — work agents never write the KB). Syncthing does replicate ~/sync/ to a work machine and Craig is fine with how C handles it. Node granularity: per-fact nodes. Write review: agent writes land freely in the KB only — explicitly not permission to post to email, Linear, or any public channel without review and consent. The spec was rewritten into the spec-create format at [[id:08a5ec99-9e1e-40e4-8241-e8a41e9de49f][agent-knowledge-base-spec.org]] (old draft removed). Implementation explicitly held pending Craig's go-ahead; one decision still open (D7, next VERIFY). - -*** 2026-06-10 Wed @ 14:35:40 -0500 Spec review — not ready -Review written at docs/agent-knowledge-base-spec-review.org (deleted on disposition completion; content summarized in the spec's Review dispositions). Rubric: =Not ready=. Blockers: resolve D7 (keep vs retire harness memory) and define the executable personal/work/unknown write-boundary classifier plus work-side write/refusal destination. Medium notes: use concrete ripgrep commands that exclude =*.sync-conflict-*= files, and define seed-node approval/rollback. - -*** 2026-06-10 Wed @ 14:44:00 -0500 D7 resolved — keep harness memory as the capture layer -Craig ratified "keep" in chat (2026-06-10). Harness memory stays the ephemeral, auto-recalled capture layer; the KB holds promoted durable facts; Phase 3's wrap-up promotion cadence is mandatory. Spec D7 flipped to accepted; D2 stands as written. - -*** 2026-06-10 Wed @ 14:44:00 -0500 Project classification defined — work-root denylist, unknown refuses -Resolved in the spec-response pass: =knowledge-base.md= carries an explicit work-root denylist (initially =~/projects/work=) as the source of truth. Personal = under a known project parent (=~/code/=, =~/projects/=, =~/.emacs.d=) and not denylisted → KB writes allowed. Work or unknown → no KB write; the agent reports the refusal with a one-line redacted summary of the fact. v1 adds no new work-side store — work projects keep their existing project-tree conventions. See the "Project classification and write routing" section of [[id:08a5ec99-9e1e-40e4-8241-e8a41e9de49f][the spec]]. Denylist completeness is the one open caveat (next VERIFY). - -*** 2026-06-10 Wed @ 14:44:00 -0500 Codex review incorporated — spec ready with caveats -Spec-response pass processed the 2026-06-10 Codex review with D7 = keep as a pre-agreed input. Both blockers cleared (D7 accepted; classification/write-routing section added). Mediums accepted: canonical rg commands with conflict-file exclusion, Phase 2 seed-node approval/rollback mechanics, Makefile no-change note, Testing/Verification section. Three recommendations modified, none rejected — see the spec's Review dispositions. Review file deleted per the workflow. Rubric: ready with caveats (denylist confirmation). Implementation tasks broken out below; implementation itself awaits Craig's go. - -*** 2026-06-10 Wed @ 17:29:37 -0500 Work-root denylist confirmed — ~/projects/work only -Craig confirmed (2026-06-10, in chat): the denylist is just =~/projects/work=. Archangel is not work-scoped. The spec's one caveat clears — status now ready. Phase 1 is unblocked, but implementation still awaits Craig's explicit go. - -*** 2026-06-10 Wed @ 17:57:08 -0500 Spec amended — D8 git transport + migration/metrics/docs/maintenance folds -Craig's five design questions answered and folded into the spec, and D8 ratified (Shape A): the KB moves out of the =~/sync/org= Syncthing share into its own git repo on cjennings.net, with an =agents/= subdirectory for agent writes, a systemd auto-sync timer for Craig's edits, opt-in-by-clone replication (work machine doesn't clone), and the phone staying on the on-demand =~/sync/phone= pattern. Folded in: inclusion criteria + a Phase 1.5 guided memory sweep, a Success metrics section with a 30-day checkpoint, the seed node redefined as the KB's own documentation, and Phase 4 maintenance automation. Phases renumbered 0-4; tasks below updated. Implementation still held. - -*** 2026-06-10 Wed @ 18:21:33 -0500 Phase 0 done — roam migrated to git -Backed up (~/roam-backup-2026-06-10.tar.gz), copied to =~/org/roam=, 63 conflict files deleted (424 org files), git repo with origin =git@cjennings.net:roam.git= (initial commit 515693d), old location replaced with a transition symlink. Emacs =roam-dir= updated in user-constants.el + live-reloaded (db rebuilt, 416 nodes); handoff to .emacs.d for the commit. =roam-sync.sh= (6 bats green) on a 15-min systemd user timer, installed + enabled + round-trip verified. Old-path references repointed (protocols task-list pointer, journal workflow, notes template). archsetup handoff covers dotfiles adoption + other-machine clones. rulesets commit fcf554a. - -*** 2026-06-10 Wed @ 18:21:33 -0500 Phase 1 done — knowledge-base.md rule live -=claude-rules/knowledge-base.md= written (path, git discipline, query commands, agents/ write schema, denylist + refusal contract, inclusion criteria, capture-then-promote). =make install= linked it machine-wide; verified the link, a known-note query, and conflict-glob exclusion with a planted file. Commit d071f1f. - -*** 2026-06-10 Wed @ 18:21:33 -0500 Phase 1.5 done — rulesets swept, 10 projects broadcast -Rulesets' 6 memories classified: 3 promoted as =agents/= nodes (notify-attention pattern, pdftools venv, gpg-agent SSH TTL trap), 2 kept local (rule-encoded in verification.md / interaction.md), 1 kept + de-staled (ai-scripts-canonical updated for the claude-templates subtree fold). Sweep handoff broadcast to the 10 other memory-bearing projects (archsetup, org-drill, pearl, .emacs.d, elibrary, finances, health, home, jr-estate, kit); work skipped by the boundary; the orphaned =linear-emacs= memory dir (project retired, likely pearl's predecessor) noted for Craig. - -*** 2026-06-10 Wed @ 18:21:33 -0500 Phase 2 done — seed/doc node written and indexed -=agents/20260610181640-how-the-agent-knowledge-base-works.org= written: the KB's user-facing guide (what agents do, how it syncs, finding/pruning agent content, the rule pointer). Index verified programmatically: =org-roam-node-from-title-or-alias= resolves it with tags (agent reference); node count 416 → 420. Craig's visual check remains in the manual-testing child. - -*** 2026-06-10 Wed @ 18:21:33 -0500 Phase 3 done — wrap-up promotes + records the KB receipt -wrap-it-up.org Step 1 gains the promotion check (inclusion-criteria bar) and the mandatory "KB: promoted N / consulted yes-no" Summary line; validation checklist enforces it. Mirror synced, integrity OK (44), parse OK. Commit 242b95e. - -*** 2026-06-11 Thu @ 19:26:26 -0500 .emacs.d memory sweep complete (first broadcast response) -First of the 10 broadcast projects to report Phase 1.5 done (handoff 18:23). Inventory 7: promoted 3 to KB (no-make-frame-in-live-daemon, proton-bridge-headless-cert-mismatch, open-images-with-imv — roam commit a915760), kept 3 local at Craig's call (commit-flow-no-approval-gate per-project-scoped; two theme-scoped ones possibly superseded by the palette-columns spec), deleted 1 (superseded by canonical interaction.md rule). 9 projects' sweeps outstanding. - -*** 2026-06-12 Fri @ 02:25:12 -0500 Five more sweeps complete via the home folds -Overnight handoffs from home closed five more broadcast targets, each swept at fold-time triage with Craig's approval: jr-estate 2 promoted (forms name-with-number, PDF-editing tooling split; roam 45d8e6c) / 3 kept with area attribution / 2 deleted as rule-encoded or duplicate; finances 0/1/0 (rosalea-daly contact fact kept local); elibrary 0/0/2, health 0/0/1, kit 1/0/2 (hand-prep-items-to-work-inbox promoted into home's memory; the rest duplicated rules or home memories). Nothing from these five met the KB bar that wasn't already encoded. All folded projects' session archives merged area-prefixed into home's .ai/sessions/, so session-harvest's first run sees them. Home covers its own and remaining areas' sweeps through ongoing discipline; still pending from the broadcast: archsetup and work. - -*** 2026-07-04 Sat @ 11:52:00 -0500 Manual validation — checks 1 + 4 verified; refusal checks split out -Check 1 (seed node in org-roam + rg inventory) verified on velox 2026-07-01 (55 =:agent:= nodes matched the live org-roam DB). Check 4 (cross-machine sync) verified as a full round-trip 2026-07-04: velox pushed a probe 2026-07-01, ratio held it, ratio deleted it, velox pulled the deletion; both clones at the same HEAD, zero sync-conflict files, both timers active. Checks 2 + 3 (work / unknown-project write refusal) need live sessions in those projects — promoted to a standalone manual-validation task. - -*** 2026-06-10 Wed @ 18:21:33 -0500 Phase 4 done — monthly hygiene automation live -=scripts/kb-hygiene.sh= (6 bats green, shellcheck clean, read-only by design) inventories =:agent:= nodes, flags orphans / duplicate titles / conflict files, and writes an org report into the rulesets inbox; =roam-hygiene.timer= (monthly, Persistent) installed + enabled. Live run against the real KB verified (4 agent nodes, 428 files, 0 conflicts). Conditional vNext stays in the spec's scope tiers: a =/promote= command if the wrap-up prompt proves insufficient, an =:agent:inbox:= staging tag if free writes prove too noisy. Commit b014095. - -*** 2026-06-30 Tue @ 13:53:34 -0400 ratio roam clone + sync-timer confirmed (cross-machine half done) -Verified ratio over tailscale ssh: =~/org/roam= is a clone of =git@cjennings.net:roam.git= (HEAD auto-synced 13:11 today), and =roam-sync.timer= is enabled and actively firing (last run 5 min prior, next in 10). Both unit files present. velox was already confirmed, so the one-time clone+timer setup is now done on both daily drivers — the (b) half of this VERIFY's remaining work. Only the manual-validation child (work/unknown-project refusal checks needing Craig's eyes) is left before DONE. Cleared the matching "Current open instance" line in =daily-drivers.md=. - -*** 2026-07-01 Wed @ 21:52:15 -0400 Manual-validation checks 1 + 4 (velox half) verified; ratio unreachable -Check 1 (seed node in org-roam + rg inventory): 55 =:agent:= nodes found by the rg inventory AND 55 nodes under =agents/= indexed in the live org-roam DB (emacsclient query) — match. Check 4 (cross-machine edit): created a temporary probe node =agents/20260701214910-kb-sync-validation-probe.org=, triggered =roam-sync= — committed and pushed to origin within seconds (f0252bb), zero =sync-conflict= files. The ratio half could NOT be verified tonight: =tailscale ping ratio= pongs via DERP, but ssh to 100.71.182.1:22 times out (machine likely suspended). Probe left in place; when ratio is back, confirm with: ssh cjennings@100.71.182.1 'ls ~/org/roam/agents/ | grep kb-sync-validation-probe' — then delete the probe node. Checks 2 + 3 (work/unknown-project refusal) still need live sessions in those projects, and "work machine has no KB clone" needs the work machine named + checked. - -** TODO [#C] Agent-KB / memory-sync — work + unknown-project write refusal :test: Residual manual validation from the memory-sync task (closed 2026-07-04, implementation IMPLEMENTED). Two checks need live sessions in other project contexts: - In the work project, a durable-storage request produces no KB write and the refusal report names the fact. - In an unknown project (outside =~/code/=, =~/projects/=, =~/.emacs.d=), the agent refuses or asks rather than guessing. @@ -277,7 +329,7 @@ Expected: both refusal checks behave per the spec; any miss promotes to a bug. N ** TODO [#C] Token-rotation helper for =@a-bonus/google-docs-mcp= OAuth refresh :feature:quick: :PROPERTIES: -:LAST_REVIEWED: 2026-06-28 +:LAST_REVIEWED: 2026-07-13 :END: When a Google refresh token gets revoked (re-grant scopes, removed Connected App, account password reset), recovery is currently manual: run =npx -y @a-bonus/google-docs-mcp= with the right env, follow the URL in a browser, kill the process, base64-encode the new =token.json=, decrypt =secrets.env.gpg=, replace the var, re-encrypt. A small =mcp/refresh-google-docs-token.sh <profile>= would chain that into one command. @@ -328,70 +380,53 @@ Codex ran the spec-review workflow. Outcome: the combined spec is =Not ready= be *** 2026-06-12 Fri @ 02:39:38 -0500 Second review after response pass Codex re-ran spec-review after the dispositions were folded in. Outcome by arc: Phase 1.5 helper instances =Ready with caveats=; phases 2-5 remain =Not ready= behind the explicit decisions/reverification gate. No new blocking findings for the helper slice. Review file updated in place: [[file:docs/design/2026-05-28-generic-agent-runtime-spec-review.org]]. -** DONE [#C] Spec storage location + lifecycle-status convention :spec: -CLOSED: [2026-07-04 Sat] -:PROPERTIES: -:CREATED: [2026-06-15 Mon] -:LAST_REVIEWED: 2026-06-24 -:SPEC_ID: 80b0787b-4a60-4c82-8a16-b383d3e3c8f2 -:END: -Two coupled documentation conventions for rulesets to adopt, surfaced by .emacs.d while triaging ~28 design docs. Both land in =spec-create= ([[file:.ai/workflows/spec-create.org]]) and likely a new =docs-lifecycle= rule under =claude-rules/=. Source proposal: [[file:docs/design/2026-06-15-spec-storage-lifecycle-proposal.org]] (.emacs.d handoff 2026-06-15). - -The two conventions: -- *Location split* — formal specs live in =docs/specs/=; =docs/design/= keeps working notes, brainstorms, inventories, reviews. A spec is a doc proposing a buildable change with a Decisions section and phases; everything else is a note. -- *Glanceable lifecycle status* — a spec's state (draft / doing / implemented / superseded / cancelled) is visible without opening the file, plus an authoritative in-file record. - -We handle the task in priority order. Mechanism decided 2026-06-28; migrates into the spec when built. - -*** Decisions (settled 2026-06-28 — migrate into the spec when built) -1. *Location split — adopt.* =docs/specs/= for formal specs, =docs/design/= for notes (brainstorms, inventories, reviews). A spec is a doc proposing a buildable change with a Decisions section and phases; everything else is a note. Document in spec-create and the docs-lifecycle rule. -2. *Status mechanism — org-keyword authoritative.* The spec's =#+TODO:= state on its top heading is authoritative (specs already carry =#+TODO: TODO | DONE SUPERSEDED CANCELLED=), mirrored in a =Status= field in the Metadata table. Drop the filename suffix entirely — it's redundant with the Status field and adds rename churn across a cross-linked, template-synced doc set. (Craig 2026-06-28, choosing org-keyword over his earlier filename-suffix lean.) -3. *Link safety — adopt =org-id= ([[id:...]]) for cross-doc spec links.* Decouples link stability from the status mechanism; good hygiene regardless. -4. *Generalize.* Capture the shape (lifecycle state authoritative-in-artifact, formal-vs-notes split, rename-safe links) as a general =docs-lifecycle= convention in =claude-rules/=, with spec-create as the first instance. -5. *Retrofit existing files across ALL projects* (Craig 2026-06-28). The convention is worthless if legacy docs stay misfiled — every project's existing =docs/design/= pile (the ~28 in .emacs.d that surfaced this) must be sorted: formal specs move to =docs/specs/=, notes stay in =docs/design/=, inbound =file:= links updated. This is a one-time per-project migration that template sync can't perform, so the spec must design the reach mechanism. Proposed shape: a synced classify-and-move helper under =.ai/scripts/= (heuristic: a doc with a Decisions section + phases/Metadata is a spec) that proposes moves for confirmation and relinks, plus a startup nudge gated on a per-project =:LAST_SPEC_SORT:= marker so each project runs it once. Classification is a judgment call — the helper proposes, a human confirms. +*** 2026-07-13 Mon @ 13:26:57 -0500 Gap assessment decomposed into child tasks +Craig asked what's left to run ChatGPT or a local LLM as the agent. Assessment: the =.ai/= layer (protocols, workflows, scripts, inbox, todo, session anchors) is already runtime-neutral — plain org + bash, and a Codex session has run in it (2026-06-13). The Claude-bound remainder decomposed into the child tasks below; each overlapping spec-blocker decision is named in its body. The phases 2-5 go/no-go above still gates any big build, but several children are useful standalone. -Follow-up once built: update spec-create to emit into =docs/specs/= with the org-keyword status; write the =docs-lifecycle= rule; ship the retrofit helper + startup nudge; retrofit rulesets' own =docs/design/= first as the pilot; send a note if .emacs.d should pilot before generalizing. +*** 2026-07-13 Mon @ 16:04:04 -0500 Instruction bootstrap built — thin-pointer AGENTS.md, both install paths +Craig picked the thin-pointer shape (Decisions: option 1, definitively). Shipped TDD (bats red → green): canonical =claude-templates/AGENTS.md= (you-are-this-project's-agent + read protocols.org + rules locations + the /name resolution rule from the skill-parity finding + degrade-per-fallback, never skip gates); =make install= links it to =~/.codex/AGENTS.md= (new CODEX_DIR stanza, house skip/WARN/link idiom, covered by NEW =scripts/tests/install-agents-entry.bats=, 3 tests); =install-ai.sh= seeds a project-owned copy at bootstrap, never overwriting (+2 tests in install-ai.bats); rulesets root gets a tracked symlink as dogfood. Resolves the spec's "generic instruction-file strategy" blocker. velox picks up the global link automatically — startup Phase A.0 runs =make install= every session. Existing projects get seeded on demand (no auto-sweep in v1). -*** 2026-07-01 Wed @ 22:13:00 -0400 Spec drafted — first resident of docs/specs/, awaiting review -Wrote [[id:80b0787b-4a60-4c82-8a16-b383d3e3c8f2][the spec]] from the five settled decisions, dogfooding its own conventions: it lives in the new =docs/specs/=, opens with the =* DRAFT Docs lifecycle= status heading (org keyword authoritative, =:ID:= for id-links, dated history in the body), and drops the status filename suffix. It pins the one mechanism the decisions left open — where the keyword lives: a prepended top-level status heading with vocabulary =DRAFT READY DOING | IMPLEMENTED SUPERSEDED CANCELLED=, additive and retrofittable, giving both the one-line =rg= board and free org-agenda scanning. Four build phases: rule + template updates → =spec-sort= helper (classify/confirm/move/relink, bats) → rulesets pilot (41 design files, 3 spec-spine candidates, 2 stray root specs) → startup nudge gated on =:LAST_SPEC_SORT:= + .emacs.d note. Status DRAFT until Craig's review flips it READY. +*** 2026-07-13 Mon @ 14:52:38 -0500 Skill parity resolved — one resolution rule, no per-skill matrix +Analysis in [[file:docs/design/2026-07-13-runtime-portability-inventories.org]] (Skill and command parity section). All 29 artifacts (11 skills + 18 commands) are markdown bodies; a single resolution sentence in the bootstrap entry file ("a /name reference resolves to the skill/command file — read and follow it") makes the library portable to any file-reading harness. Auto-invocation degrades to by-name invocation (the publish flow already invokes by name), native slash registration is an optional install nicety, flush is explicitly excluded (session-plumbing child owns it). Folds into the instruction-bootstrap child's build. -*** 2026-07-01 Wed @ 22:22:34 -0400 Codex spec-review complete — Not ready -Review findings live in [[id:cc77a7f6-e4c3-488a-ac3b-e739420a5c2b][the spec]]. Four blockers before implementation: the proposed lifecycle =#+TODO:= line drops the =TODO=/=DONE= states needed by Decisions and Review findings cookies; =spec-sort= needs an exact relink/unsupported-residue contract; the sort marker/startup nudge must name the actual =.ai/notes.org= state surface and detection flow; and the stricter =docs/specs/= review precondition must not strand legacy specs before retrofit. +*** 2026-07-13 Mon @ 13:34:17 -0500 Hook parity inventoried — only two hooks carry real porting work +Full mapping in [[file:docs/design/2026-07-13-runtime-portability-inventories.org]]. AskUserQuestion deny is moot off Claude (no popup tool to deny); PostToolUse validators survive via the bundles' git pre-commit hooks; clear-resume folds into the session-plumbing child; session-title is cosmetic. Real gaps: PreCompact priority-save (prose downgrade) and Stop wrap-teardown (Codex notify / manual elsewhere) — decisions in the VERIFY below. -*** 2026-07-01 Wed @ 22:30:06 -0400 Second review merged + responder pass — all nine findings fixed -A fresh-context Claude reviewer independently rated the draft Not ready, converging on Codex's keyword-vocabulary blocker (adding the cross-sequence uniqueness wrinkle) and contributing five unique findings — the biggest: nobody owned the DOING→IMPLEMENTED flip, the exact mechanism whose failure produced this spec. Craig approved fixing all nine. Fixed in place: two-sequence collision-free keyword header (dogfooded in the spec's own header; org now computes the [5/5] and [9/9] cookies — verified in batch), transition-ownership table incl. spec-response's mandatory flip-to-IMPLEMENTED task + task-audit safety net, single classification predicate (Decisions AND Implementation phases), the -spec.org rename step, the full relink data-safety contract (rewritten roots / report-only surfaces / dry-run default / residue-grep gate), the =.ai/notes.org= marker + Phase A probe + Phase C nudge contract, the legacy-location compatibility rule, the org-id Emacs-resolution prerequisite for .emacs.d, and the three-line transition definition. Ledger + per-finding responses in the spec's Review findings section. Status stays DRAFT pending Craig's READY flip. +*** 2026-07-13 Mon @ 23:21:08 -0500 Interactive agent picker — bare =ai= asks which brain first +Craig's ask: the fzf flow should offer the LLMs before the projects. Bare =ai= now runs a runtime picker (claude first so Enter-Enter keeps the old muscle memory; codex labeled ChatGPT; one =local:<model>= line per ollama model, live-queried with a 3s timeout — a dead server just drops the local lines), then the familiar annotated project multi-select. =--runtime= / =AI_RUNTIME= skip the pick; single-dir mode unchanged. New =--print-runtimes= test seam; 9/9 launcher bats, suite 441/0. This substantially delivers the "easy lightweight way to change agents / agentically democratic" roam ask — the launcher-hardening task keeps the deeper refactor. -*** 2026-07-01 Wed @ 22:41:33 -0400 Codex spec-review rerun — Not ready -Fresh review after the response/READY flip added five new blocking findings in [[id:cc77a7f6-e4c3-488a-ac3b-e739420a5c2b][the spec]] and demoted the spec back to DRAFT. Remaining blockers: shared helper/workflow edits must name the canonical =claude-templates/.ai/= + mirror sync contract; task-audit needs an explicit spec-to-task binding before it can police =DOING= specs; =spec-sort --apply= needs a failure-safe/rollback contract; the org-id Emacs prerequisite must be executable before link conversion; and lifecycle status confirmation must be evidence-based so the retrofit does not encode stale reality. +*** 2026-07-13 Mon @ 23:04:21 -0500 Local runtime wired — ai --runtime local runs codex --oss over ollama +The reserved lane went live the same day: =local= maps to =codex --oss --local-provider=ollama -m $AI_LOCAL_MODEL= (default gpt-oss:120b; qwen3-coder:30b via the env var). Verified end to end: =codex exec --oss --local-provider=ollama -m gpt-oss:120b= returned a correct completion through the local model (~7K tokens). The explicit provider flag avoids config.toml dependence (a root-level =oss_provider= key would also work; appending to the file lands in the last TOML table and silently does nothing — learned live). Dependency check keys on AGENT_BIN (codex) now that AGENT_CMD carries flags. This partially answers the spec's "first supported local editing CLI" blocker: codex-over-ollama is the de-facto v1. The model-floor child's live trial (a takuzu session under =ai --runtime local=) is the next step. -*** 2026-07-01 Wed @ 22:46:52 -0400 Second responder pass — all fourteen findings closed -Fixed Codex's five re-review findings: the canonical-placement contract now opens the retrofit section (helper + tests + workflow edits land in claude-templates first, sync-check --fix propagates, sync-check-clean is an acceptance criterion); spec-response stamps a =:SPEC_ID:= property on the build parent, and task-audit's query checks that parent's keyword — which dissolves the flip-task chicken-and-egg; =--apply= got the fail-safe contract (clean-tree preflight, validate-then-write from a recorded plan, named recovery recipe); id-link conversion is staged (pilot rewrites =file:= links only; =id:= conversion is a follow-up gated on the concrete .emacs.d id-index mechanism — Craig picked this fork); and status confirmation is evidence-based (evidence panel, conservative non-terminal default, terminal states need a stated reason). Also de-cookified bracket tokens in prose that org's cookie updater would mangle. Status stays DRAFT; the READY flip belongs to the reviewers this round — verify pass dispatched. +*** 2026-07-13 Mon @ 16:39:41 -0500 Launcher runtime flag built — ai --runtime claude|codex +Shipped TDD (new =scripts/tests/ai-launcher-runtime.bats=, 6 tests red→green): =--runtime= flag + =AI_RUNTIME= env on =bin/ai=, runtime→CLI mapping (claude default, codex 1:1 — both take the opening line as a positional prompt), runtime-aware dependency check, =local= reserved with a clear not-wired-yet error (pending the model-floor evaluation), and a =--print-launch= mode as the test seam (prints the pane launch command, no tmux/fzf). Kept small per the 2026-06-24 helper-task caveat — dispatch pre-parse only, no launcher restructure. The Emacs-side equivalent (ai-term multi-LLM) remains .emacs.d's June handoff. Live smoke: correct commands for both runtimes against real projects. -*** 2026-07-01 Wed @ 23:22:50 -0400 Codex spec-review rerun — Ready -Codex re-read the revised [[id:80b0787b-4a60-4c82-8a16-b383d3e3c8f2][docs lifecycle spec]] after the second responder pass. All fourteen findings are closed, decisions remain [5/5], and the remaining implementation contracts are concrete enough to build and test. Status flipped to READY in the spec; implementation can proceed. +*** 2026-07-13 Mon @ 17:05:00 -0500 Roam capture folded — the agent-switching ask +Two roam items (2026-07-13) asked for lightweight agent selection (claude, qwen, chatgpt, ollama — "agentically democratic"). Partly shipped the same day: =--runtime claude|codex= (04c3b29; codex is the ChatGPT-side CLI). The ollama/qwen choice is the reserved =local= runtime, landing with the model-floor eval below. An interactive runtime picker rides the new launcher-hardening task ([#C] :refactor:solo:, filed from the same capture). -*** 2026-07-01 Wed @ 23:34:15 -0400 Decomposed into build tasks; spec flipped READY → DOING -spec-response Phase 6 run: this parent now carries the =:SPEC_ID:= binding (the spec's status-heading UUID), the phase tasks below track the build, and the spec's status heading is DOING. Completeness pass done: all ten acceptance criteria have homes across the phase tasks; vNext (org-agenda view) was already filed as the [#D] task below. +*** 2026-07-13 Mon @ 16:41:14 -0500 Session plumbing assessed — no build needed +Analysis in [[file:docs/design/2026-07-13-runtime-portability-inventories.org]] (Session plumbing section). session-context-path is runtime-aware by design; the anchor cycle is plain files any agent drives identically; self-inject.sh is harness-agnostic (only its PAYLOAD is Claude-shaped — a codex auto-flush is a payload variant whose second injected line carries the resume instruction itself, no hook needed); session-clear-resume.sh stays Claude-only and the payload variant obsoletes it off Claude. Wire the codex variant the day a codex session wants it. -*** 2026-07-01 Wed @ 23:39:10 -0400 Phase 1 landed — docs-lifecycle rule + four spec-workflow updates -claude-rules/docs-lifecycle.md written and linked machine-wide (make install). Canonical-side updates: spec-create Phase 5 + template (docs/specs/ location, two-sequence keyword header, DRAFT status heading with :ID:, transition mechanics), spec-review (location expectation with the legacy compatibility rule keyed on :LAST_SPEC_SORT:, plus the DRAFT→READY flip — and the demote-back-to-DRAFT path a failed re-review takes), spec-response Phase 6 (owns READY→DOING, stamps :SPEC_ID: on the build parent, always emits the flip-to-IMPLEMENTED task), task-audit Phase B (the :SPEC_ID: reconcile query, checking the parent's keyword rather than counting children). Mirror synced; make test green end to end. +*** 2026-07-13 Mon @ 13:34:17 -0500 Memory story confirmed — KB is already the cross-agent store +Details in [[file:docs/design/2026-07-13-runtime-portability-inventories.org]]. Auto-memory stays Claude-owned; non-Claude agents use the org-roam KB + file artifacts (todo/notes/session anchors), all runtime-neutral already. One wording gap: knowledge-base.md's capture-then-promote names harness memory as the capture layer — a one-sentence addition (session log as the capture layer for agents without harness memory) closes it, pending approval in the VERIFY below. -*** 2026-07-01 Wed @ 23:57:44 -0400 Phase 2 landed — spec-sort helper + 30-test bats suite -Built claude-templates/.ai/scripts/spec-sort (Python, TDD — the 30-test bats suite written red-first in claude-templates/.ai/scripts/tests/spec-sort.bats) covering the full retrofit contract: spine classification with the -spec.org-name-without-spine anomaly case, evidence panel (Status field, cookies, linking todo.org task, dated history, artifact existence) with conservative non-terminal proposals, per-candidate --confirm/--skip gate with --reason required on terminal keywords, clean-worktree preflight (--allow-dirty prints what recovery loses), validate-then-write from a recorded plan file, relink across the rewritten roots (inbound AND the moved doc's own outbound relative links) with report-only for sessions + synced templates (naming the canonical claude-templates file), bare-path mentions blocking until --acknowledge-bare, named recovery on injected mid-apply failure, post-apply residue gate, idempotent :LAST_SPEC_SORT: stamp. Real-data dry run against rulesets' pile matched predictions: 5 candidates, 4 anomalies, 30 notes, 0 bare, 10 report-only (incl. the startup.org synced-template case Codex flagged). make test green; sync-check clean. +*** 2026-07-13 Mon @ 13:34:17 -0500 MCP portability checked — portable except paging +Details in [[file:docs/design/2026-07-13-runtime-portability-inventories.org]]. Nine locally-configured servers (linear, notion, figma, slack-deepsat, google-calendar, google-docs x2, drawio, google-keep) port mechanically to any MCP-speaking harness. claude.ai-managed connectors (Gmail + claude.ai Calendar/Drive) don't travel but are redundant with local servers / cmail-action. The real finding: signal-mcp is NOT locally configured anywhere — the protocols.org paging path is claude.ai-side only, so off Claude there is no page channel. The Signal-pager [#C] task's signal-cli runbook is the fix; add runtime-portability as motivation there (VERIFY below). -*** 2026-07-02 Thu @ 00:18:28 -0400 Phase 3 pilot ran — rulesets' pile sorted, board live -Craig confirmed all five proposed keywords as-is plus the IMPLEMENTED reason; spec-sort --apply moved the five specs to docs/specs/ (agent-knowledge-base IMPLEMENTED, inbox-workflow-consolidation READY, autonomous-batch-execution READY, encourage-kb-contribution READY, wrapup-routing DOING — joining the docs-lifecycle spec's DOING on the board), rewrote 12 todo.org links plus the moved specs' own outbound links, and stamped :LAST_SPEC_SORT: 2026-07-02. Acceptance verified: status board matches reality, all re-homed specs carry -spec.org, residue zero in the rewritten roots (one acknowledged self bare mention rode along inside inbox-workflow-consolidation-spec), no id: links emitted, make test green. Surfaced and left in place: the four -spec.org-named files in docs/design without a spec spine (generic-agent-runtime, pattern-catalog, daily-prep-template, auto-triage-intake) — notes by predicate, misleading names; rename or leave is a Craig call. Report-only references: 9 frozen session archives + the synced startup.org (canonical edit lands with Phase 4's nudge work). +*** 2026-07-13 Mon @ 14:40:00 -0500 Four inventory decisions — Craig approved all four +Recorded in [[file:docs/design/2026-07-13-runtime-portability-inventories.org][the inventories doc]]: prose-only PreCompact downgrade off Claude; Stop-teardown via Codex notify where available, manual elsewhere; runtime-portability note added to the Signal-pager task; capture-layer sentence added to knowledge-base.md (non-Claude runtimes capture into the session log and promote from there). -*** 2026-07-02 Thu @ 00:23:32 -0400 Phase 4 landed — startup nudge live, .emacs.d notified -Added the spec-sort probe to startup.org Phase A (item 12) and the one-line nudge to Phase C's findings list, canonical-side, mirror synced. One refinement over the spec's sketch: the stray-root check uses find instead of compgen, because compgen is bash-only and zsh aborts on an unmatched glob — the original snippet false-negatived on stray root specs under zsh (spec snippet updated with a note). Fixture-verified in both shells: fires on an unsorted docs/design and on a stray docs/*-spec.org, silent with the marker stamped, silent with no docs at all. Also fixed startup.org's own stale reference to the moved encourage-kb-contribution spec (the pilot's report-only finding). Sent .emacs.d the convention-live note with its ~28-doc pile nudge and the id-index ask (org-id-extra-files enumeration or periodic org-id-update-id-locations, verify by clicking the docs-lifecycle spec's :ID:), asking it to tag the owning task :blocker: since rulesets' id-conversion task waits on it. +*** TODO Local model floor evaluation +The workflows assume long-context instruction-following (startup's multi-file read; the commits.md publish chain). Establish the minimum viable local tier (likely strong-70B+/MoE, 100k+ context), and what compensations a weaker model needs: shortened protocols, more checklist gates, more hook-level enforcement. Feeds the spec's "default local runtime/server" and "first supported local editing CLI" blocker decisions. -*** 2026-07-02 Thu @ 05:12:33 -0400 Converted spec-target file: links to id: form (rulesets) -All 13 file:docs/specs/ links lived in todo.org (zero in .ai/ or docs/ outside specs). 11 converted straight to [[id:UUID][label]] (bare links labeled with the spec filename); the 2 links carrying a ::*Review findings search target got full fidelity by minting an :ID: on that heading in the docs-lifecycle spec (cc77a7f6-e4c3-488a-ac3b-e739420a5c2b) — the id index scans whole files, so heading-level ids resolve. Residue grep zero; every id verified against its target's :ID:. Gate had cleared earlier tonight via .emacs.d's org-spec-links.el delivery (verified org-id-find on their side); M-x cj/org-id-refresh-spec-locations is the fix if a fresh id doesn't resolve on click. +Environment inventory done 2026-07-13 (KB node "Local LLM inference inventory — daily drivers"): ratio is the inference box — Strix Halo iGPU + 125 GiB unified RAM. velox is out of scope (Iris Xe, 60 GiB, no ollama). -*** 2026-07-04 Sat @ 11:46:31 -0500 Flipped the spec to IMPLEMENTED -All four build phases had shipped (docs-lifecycle rule + spec-workflow updates, spec-sort helper + 30-test bats suite, rulesets pilot + status board, startup nudge) plus the file:→id: link conversion, so the spec's status heading went DOING → IMPLEMENTED with a dated history line and the Metadata mirror, per the transition-ownership table. Parent closed; manual validation promoted to its own task. +Environment BUILT and verified, evening of 2026-07-13: ollama 0.31.2 as a systemd service, Vulkan/RADV backend forced via systemd override (ROCm loaded a 61 GB model at <25 MB/s — 40+ min, unusable; Vulkan loads it in 44 s), models swapped to the MoE shape this platform wants: gpt-oss:120b (44 s load, 37.9 tok/s, 100% GPU) and qwen3-coder:30b (5 s load, 76 tok/s). All four legacy dense models dropped; the orphaned ~/.ollama user store purged. Gotcha for the eval harness: pass num_ctx 8-32K — gpt-oss's native 128K default balloons the load. Remaining work is now purely the evaluation: drive a scripted startup read + a publish-flow transcript through gpt-oss:120b (and qwen3-coder as the fast comparator), grade instruction-following against the protocol stack, and answer the spec's default-local-runtime + first-supported-CLI blockers. ** TODO [#C] Docs-lifecycle convention — manual validation :test: +:PROPERTIES: +:LAST_REVIEWED: 2026-07-13 +:END: The human-eyes half of the docs-lifecycle acceptance surface. The convention shipped IMPLEMENTED 2026-07-04 (spec [[id:80b0787b-4a60-4c82-8a16-b383d3e3c8f2][docs-lifecycle]]); these checks confirm the human-facing behavior. A failed check promotes to a bug. *** Startup nudge appears and clears @@ -408,20 +443,17 @@ Expected: each opens the spec at its new docs/specs/ path. Once specs carry lifecycle TODO keywords under =docs/specs/=, add a custom org-agenda view that lists =DRAFT= / =READY= / =DOING= / terminal specs by status. Deferred from [[id:80b0787b-4a60-4c82-8a16-b383d3e3c8f2][the docs-lifecycle spec]]; not part of v1 because the grep board is sufficient until the status headings exist. ** TODO [#C] Wrap-it-up summary mode — keep or cut :feature: -From Craig via the roam inbox (2026-07-02, routed by archsetup). Teardown-by-default already shipped (bare "wrap it up" closes the window; "with summary" keeps it). Craig's follow-on: "maybe we cut the summary altogether. help me think through when I'd want a summary and how I would recognize it before confirming and then having it close." Run that think-through with him (brainstorm-shaped, not solo), then adjust wrap-it-up.org's Step 6 + trigger phrases to the outcome. - -** TODO [#C] ntfy phone channel as general two-way agent-comms :feature:spec: :PROPERTIES: -:CREATED: [2026-06-20 Sat] -:LAST_REVIEWED: 2026-06-24 +:LAST_REVIEWED: 2026-07-13 :END: -Proposal from the home project (2026-06-17): promote the self-hosted ntfy-over-Tailscale phone channel it built and verified on ratio into a general two-way agent-comms tool rulesets owns. Full proposal: [[file:docs/design/2026-06-17-ntfy-agent-comms-proposal.org]] (as-built runbook stays in the home project at =working/phone-notifications/spec.org=). What rulesets would decide: canonicalize =phone-notify= (send) plus a new =phone-recv= (check-since) as synced bin scripts; the per-machine config/secret convention (token in =~/.config/phone-notify/config= chmod 600 today, vs GPG-encrypted in dotfiles); a reference =ntfy-inbound-handler= plus systemd user-unit for event-driven delivery (Tier A subscriber routes inbound to inbox/notify, Tier B inbound spawns an agent session, Tier C notify a live session — harness research); approval-button workflows for the commits.md gates when Craig is away from the desk (tap-to-approve, the high-value concrete use); and the relationship to the retired cross-agent-comms scripts (ntfy may be the transport they lacked). Worked via =spec-create=. Blocks the triage-intake phone-push task below. +From Craig via the roam inbox (2026-07-02, routed by archsetup). Teardown-by-default already shipped (bare "wrap it up" closes the window; "with summary" keeps it). Craig's follow-on: "maybe we cut the summary altogether. help me think through when I'd want a summary and how I would recognize it before confirming and then having it close." Run that think-through with him (brainstorm-shaped, not solo), then adjust wrap-it-up.org's Step 6 + trigger phrases to the outcome. + ** TODO [#C] triage-intake.org auto mode — push each sweep to phone (ntfy) :feature:solo: :PROPERTIES: :CREATED: [2026-06-20 Sat] -:LAST_REVIEWED: 2026-06-24 +:LAST_REVIEWED: 2026-07-13 :END: -The work project (2026-06-18) added a "Push each sweep to Craig's phone (ntfy) — the primary delivery" subsection under "Trigger and delivery" in triage-intake.org auto mode, and asks to fold it into the canonical engine plus re-sync. Preserved bundle: [[file:docs/design/2026-06-18-triage-intake-phone-push-note.org][note]] + [[file:docs/design/2026-06-18-triage-intake-phone-push-workflow.org][edited workflow]]. Auto mode is the away-from-desk / vacation mode, so phone-notify becomes the primary delivery each sweep (fuller end-of-sweep output: per-source deltas, open-PR/Linear state, awaiting-ack list, one-line verdict, timestamp; SCAN FAILED banner on any source failure), plus phone-recv polling each sweep for Craig's replies. Falls back to inline when phone-notify is absent. Depends on the ntfy agent-comms task above (phone-notify/phone-recv must be canonicalized first). Shared template-workflow change, so review-gated. +The work project (2026-06-18) added a "Push each sweep to Craig's phone (ntfy) — the primary delivery" subsection under "Trigger and delivery" in triage-intake.org auto mode, and asks to fold it into the canonical engine plus re-sync. Preserved bundle: [[file:docs/design/2026-06-18-triage-intake-phone-push-note.org][note]] + [[file:docs/design/2026-06-18-triage-intake-phone-push-workflow.org][edited workflow]]. Auto mode is the away-from-desk / vacation mode, so phone-notify becomes the primary delivery each sweep (fuller end-of-sweep output: per-source deltas, open-PR/Linear state, awaiting-ack list, one-line verdict, timestamp; SCAN FAILED banner on any source failure), plus phone-recv polling each sweep for Craig's replies. Falls back to inline when phone-notify is absent. Transport re-pointed 2026-07-13: ntfy is retired; agent-page (shipped today) is the send channel, so the push half is buildable now — substitute agent-page for phone-notify throughout. The reply-polling half (phone-recv) waits on the Signal-pager runbook's read-replies deliverable. Shared template-workflow change, so review-gated. ** TODO [#D] Fully-unattended scheduled inbox check (/schedule cron pass) :feature: :PROPERTIES: @@ -1279,112 +1311,23 @@ speculatively — defense-specific notations are narrow enough that each skill should be driven by a concrete contract need, not aspiration. * Rulesets Resolved -** DONE [#B] wrap-it-up teardown + "wrap it up and shutdown" :feature: -CLOSED: [2026-07-01 Wed] -:PROPERTIES: -:CREATED: [2026-06-23 Tue] -:LAST_REVIEWED: 2026-06-24 -:END: -Two additions to =wrap-it-up.org=, designed by Craig (home, 2026-06-23). Item 1: bare "wrap it up" also tears down the session — kill the =aiv-<proj>= tmux session (takes =claude= with it), kill the vterm buffer, restore geometry. Teardown is the default; "wrap it up with summary" wraps without teardown (keeps the buffer readable). Must fire from a Stop/SessionEnd hook via a sentinel file, decoupled and last, so the valediction flushes before the session dies, and strictly after commit+push is verified. Item 2: "wrap it up and shutdown" → wrap, then a hard blocking gate (abort unless this is the only live =aiv-*= session), then an abort-able 10→1 countdown, then =sudo shutdown now=. Countdown can't run through the Bash tool (stdout buffers — prints all ten at once); needs a detached tty writer or an Emacs =run-at-time= timer. Companion: =cj/ai-term-quit= (and optional =cj/ai-term-live-count=) must live in =.emacs.d/modules/ai-term.el= — route there via inbox-send when building so both sides land together. Open decisions for Craig first: qualifier wording ("with summary" vs "and summarize"), countdown home (tty script vs Emacs timer), session-count mechanism (=tmux ls= / =pgrep claude= / helper). Shared-asset, review-gated. Proposal: [[file:docs/design/2026-06-23-wrap-teardown-shutdown-proposal.org][proposal]]. From home 2026-06-23. - -*** 2026-06-23 Tue @ 23:31:59 -0400 Built the rulesets side; companion routed to .emacs.d -Craig's three decisions (2026-06-23): non-destructive qualifier is *both* "with summary" and "and summarize"; countdown is an Emacs =run-at-time= timer; the gate uses =cj/ai-term-live-count=. Built and pushed: =hooks/ai-wrap-teardown.sh= (Stop hook, sentinel-gated, 8 bats tests green), =hooks/settings-snippet.json= Stop wiring, =wrap-it-up.org= Teardown-mode section + Step 6 + checklist, INDEX trigger update. Architecture: both teardown and shutdown fire from the Stop hook via a basename-keyed sentinel (=/tmp/ai-wrap-teardown-<proj>=, =/tmp/ai-wrap-shutdown-<proj>=) dropped only at the end of Step 6 after commit+push, so the valediction flushes first. No bin script — the gate/countdown/teardown are all =emacsclient= calls into the companion. Companion spec (=cj/ai-term-quit=, =cj/ai-term-live-count=, =cj/ai-term-shutdown-countdown=) routed to .emacs.d via inbox-send. Remaining: .emacs.d lands the three functions, then the manual end-to-end validation below. Task stays DOING until both sides verify. - -*** 2026-06-23 Tue @ 23:40 .emacs.d received + filed the companion spec -Per a roam-inbox FYI from .emacs.d (2026-06-23 23:38): both ai-term handoffs (multi-LLM support + this wrap-teardown companion spec) landed and are filed as .emacs.d tasks. The teardown one is flagged for its own focused session to land alongside the rulesets half. Part (c) is now in progress on the .emacs.d side. - -*** 2026-06-24 Wed @ 06:51:13 -0400 Unblocked — .emacs.d companion landed; feature now live -The three companion functions are in =.emacs.d/modules/ai-term.el= (=cj/ai-term-quit= 1068, =cj/ai-term-live-count= 1087, =cj/ai-term-shutdown-countdown= 1109), matching the contract — double-checked the bodies: quit kills session+buffer+restores layout idempotently, live-count returns the gate integer, shutdown-countdown re-checks the gate (TOCTOU guard), runs an abort-able =run-at-time= countdown (C-g cancels), then a configurable =cj/ai-term-shutdown-command=. 13 ERT tests, headless-verified live (.emacs.d FYI 2026-06-24 06:44). Dropped =:blocked:= / =:BLOCKED_BY:= — the build dependency is resolved; only the manual end-to-end validation below remains. NOTE: with the Stop hook wired and the companion present, the feature is now functional — the next bare "wrap it up" will actually tear the session down. Run the validation below before relying on it. - -*** 2026-07-01 Wed @ 21:52:15 -0400 Plumbing pre-flight re-verified; only the eyes-on tests remain -Fresh pre-flight, all green: Stop hook block in =~/.claude/settings.json= points at =~/.claude/hooks/ai-wrap-teardown.sh=, the symlink resolves to the rulesets canonical, no stale =/tmp/ai-wrap-teardown-*= sentinel, and all three companion functions are live in the daemon (=(t t t)=). Three =aiv-*= sessions live right now (=_emacs_d=, =archsetup=, =rulesets=), so the shutdown-gate refusal test has its multi-session condition available. Everything left in the checklist below needs Craig's eyes on a scratch session: buffer teardown + geometry restore, the qualifier opt-outs, the countdown render + C-g, and the push-failure guard. - -*** 2026-07-01 Wed @ 21:59:43 -0400 Manual end-to-end validation passed — all five tests, Craig's live run -Craig ran the full checklist in a live Emacs/tmux ai-term setup: (1) bare "wrap it up" tore down after the valediction rendered, geometry restored, no lingering sentinel; (2) "with summary" / "and summarize" both wrapped without teardown, buffer stayed readable; (3) "wrap it up and shutdown" with another aiv-* session live refused the shutdown, named the other session, fell back to a normal wrap; (4) as the sole session, the 10→1 echo-area countdown rendered one-per-second, C-g cancelled cleanly, and a full run fired the (stubbed) shutdown command; (5) with the push made to fail, the wrap stopped at the failure and no sentinel was dropped. Works great — feature validated and live. Both sides complete: rulesets Stop hook + wrap-it-up Teardown mode, .emacs.d companion functions. -** DONE [#C] Guard against hardcoded host identity in synced files :feature:solo: -CLOSED: [2026-07-02 Thu] +** CANCELLED [#C] ntfy phone channel as general two-way agent-comms :feature:spec: +CLOSED: [2026-07-13 Mon] :PROPERTIES: -:CREATED: [2026-06-22 Mon] -:LAST_REVIEWED: 2026-06-24 -:END: -A =CLAUDE.md= / notes file that asserts mutable environment identity as a fixed fact ("This machine is ratio", a current OS, an IP, "the laptop") is false on every machine the synced/tracked file lands on but one. It bit a real archsetup session: a stale "this machine is ratio" line made the agent reason backwards all session while on velox. Proposal: a claude-rule — don't assert mutable host/env identity as a fixed fact in a tracked/synced project file; derive it at runtime and name the command (=uname -n= for host; the =hostname= binary is often absent). Optionally a codify- or startup-time lint flagging "this machine is <name>" / "the current host is" style claims. Proposal: [[file:docs/design/2026-06-21-host-identity-guard-proposal.org][proposal]]. From archsetup 2026-06-21. - -2026-07-02 Thu @ 05:09:58 -0400 — Craig (speedrun pre-flight): rule + startup lint. A new claude-rules file plus a cheap grep probe in startup flagging host-identity claims in CLAUDE.md / notes.org fleet-wide. - -Resolution 2026-07-02: claude-rules/host-identity.md written (fixed-identity claims banned in tracked/synced docs, runtime derivation via uname -n, fleet-description carve-out, the archsetup worked failure) and linked machine-wide by make install. startup.org gained Phase A probe 13 (grep for "this machine/host/box is" claims in CLAUDE.md + notes.org, fixture-verified bash+zsh) and the Phase C host-identity flag line. Flags for judgment, never blocks. -** DONE [#C] No-approvals speedrun — cross-project autonomous-batch mode :feature:spec: -CLOSED: [2026-07-02 Thu] -:PROPERTIES: -:CREATED: [2026-06-15 Mon] +:CREATED: [2026-06-20 Sat] :LAST_REVIEWED: 2026-06-24 -:SPEC_ID: 90f623cd-fdbe-4f5c-b63d-b2f84d9151cf :END: -A named mode for coding projects: Craig names an ordered task set and says "speedrun" / "no approvals speedrun"; the set is worked autonomously, each task held to the full quality bar (TDD red→green, =/review-code=, =/voice= on the commit) and committed + pushed as its own logical commit, with all needed quick decisions gathered in one pre-flight Q&A (answer or "skip this") and a VERIFY filed for anything underspecified or needing deliberation, plus an end-of-set page listing completed + remaining + skipped tasks. Task size is not a gate — large tasks decompose into per-commit chunks. Surfaced by .emacs.d from a 2026-06-15 theme-studio session where the shape worked. Source proposal: [[file:docs/design/2026-06-15-fix-speedrun-workflow-proposal.org]] (.emacs.d handoff 2026-06-15). Build via =spec-create= when worked; we handle the task in priority order. - -Skeptical-review read (open design questions to resolve in the spec, not settled here): -- *Is it a new workflow or a documented preset?* The proposal frames it as no-approvals + always-push session modes plus an end page. Decide whether it needs its own workflow file or is mostly documentation of a preset over the two existing modes. -- *Where/how the page fires* — every task vs end-of-set, and via what. The paging surface is in flux (=page-signal= removed 2026-06-12), so reconcile against =notify --persist= or whatever paging stands now. -- *Auto-pull vs explicit list* — whether the set comes from an explicit ordered list or a tag/priority query. -- *Guardrails* — must refuse to speedrun tasks needing design decisions or carrying data-loss risk without a checkpoint (the sender's biased-safe unused-tile flag is the worked example). - -*** 2026-07-01 Wed @ 22:10:35 -0400 Phase 0 landed — hard tag definitions + review/audit enforcement -todo-format.md gained the "Hard definitions: :solo: and :quick:" subsection under the scheme header (fixed across projects: :solo: = buildable + agent-verifiable + no deliberation, with one-or-two upfront-answerable quick decisions allowed per the ratified spec; :quick: = ≤30-min effort hint, never a gate). task-review.org: the two tagging sections are now explicitly mandatory ("a review that skips them is incomplete") and gate 3 was realigned from "no upfront decision" to the spec's no-deliberation form — the stricter old wording predated the pre-flight-Q&A decision and would have wrongly excluded quick-question tasks. task-audit.org: the re-assess bullet is marked mandatory and points at the todo-format hard definitions as canonical. Phases 1-6 (work-the-backlog extraction, callers, commit gate, checklist/Q&A/page, metrics, synthesis) remain. - -*** 2026-06-16 Tue @ 00:53:36 -0500 Spec written; design questions answered -Craig's "your call" (2026-06-16) answered in [[id:90f623cd-fdbe-4f5c-b63d-b2f84d9151cf][the autonomous-batch execution spec]], which reconciles this with Phase E into one feature: -- *Most effective / workflow-vs-preset:* one dedicated =work-the-backlog.org= workflow holds the execution loop; "fix speedrun" is a thin named preset (no-approvals + always-push + end page) feeding it an explicit list, and the inbox-zero loop feeds it a tag query. Pros of the shared workflow: one execution loop to audit, inbox-zero's three callers stay clean, both input shapes reuse one guardrail set. Cons: one more workflow file and a caller-to-workflow indirection. The con list is shorter and lighter than the duplication cost of two separate features, which is why the shared workflow wins. The pros carry the more important entries (single audit surface, clean seam). -- *Paging:* end-of-set only, via =notify ... --persist= (reconciled past the removed page-signal wrapper). -- *Auto-pull vs explicit list:* both — explicit list for the preset, tag/priority query for the loop. -- *Effectiveness measurement (the trial Craig asked for):* the spec designs a per-task JSONL metrics log (=.ai/metrics/work-the-backlog.jsonl=), a corrections-in-next-session signal, and a periodic synthesis step that writes =:agent:metrics:= org-roam articles for later review — the "gather data + create org-roam articles" loop. -*** 2026-06-29 Mon @ 03:48:09 -0400 Ratified the autonomous-batch execution spec -Craig ratified all eight decisions in [[id:90f623cd-fdbe-4f5c-b63d-b2f84d9151cf][2026-06-16-autonomous-batch-execution-spec.org]] (revised this session — size gate removed, crisp four-item defer checklist, =:solo:= / =:quick:= definitions + task-review/audit enforcement, speedrun pre-flight Q&A). Spec Status → ready; implementation-ready across Phase 0–6. Decisions grew from six to eight during the revision. - -*** 2026-07-02 Thu @ 00:44:59 -0400 spec-response decomposition — :SPEC_ID: bound, spec DOING -Stamped the spec's UUID on this parent, broke Phases 1-6 into the build tasks below (plus the flip task and a live-trial validation child), and flipped the spec's status heading READY → DOING per the transition-ownership table. - -*** 2026-07-02 Thu @ 01:07:29 -0400 Phase 1 landed — execution loop extracted into work-the-backlog.org -work-the-backlog.org written (canonical + mirror): caller contract (task set + session mode + cap), five-outcome vocabulary, the loop, mechanical eligibility gate (TODO + :solo: per scheme header, safe-by-omission, no-scheme-header → don't run), four-item defer checklist, per-task quality bar, cap/kill-switch semantics, page + metrics stubs pointing at Phases 4-5. inbox.org's auto-mode per-cycle item 3 reverted to routing-only (yes-path execution removed; mode intro + closing line updated to match). INDEX.org entry added. make test green, sync clean; nothing invokes the new workflow yet. - -*** 2026-07-02 Thu @ 01:13:33 -0400 Phase 2 landed — both callers wired -inbox.org auto-mode item 3 regained its "run this batch next?" ask, now chaining into work-the-backlog as an explicit second step after routing (eligibility query + file-only + paging off + cap 1). work-the-backlog.org gained the two caller sections: the auto-loop contract and the no-approvals speedrun preset (seven-step pre-flight → autonomous-commit + always-push + paging-on over an explicit list; finer Q&A mechanics deferred to Phase 4). Speedrun trigger phrases live in the workflow + INDEX; "speedrun" always routes to the preset, with a disambiguation note in no-approvals.org and its INDEX entry. Each caller independently exercisable. - -*** 2026-07-02 Thu @ 01:18:07 -0400 Phase 3 landed — waiver-gated commit autonomy -Pinned the waiver format per D5: two marker lines in .ai/notes.org Workflow State — :COMMIT_AUTONOMY: yes (has the waiver) and :LOOP_MAY_COMMIT: yes (the unattended loop may also commit; requires the first). Absent or non-yes reads as no; the read is a fresh grep each run, never memory. Degrade contract written into work-the-backlog.org (surface in run intro + summary, never honor without the marker, never degrade silently); caller sections + Common Mistakes updated. Stamped rulesets' own :COMMIT_AUTONOMY: yes; :LOOP_MAY_COMMIT: deliberately not granted — Craig's call. .emacs.d holds the waiver too but its notes.org is its own scope; told via inbox-send to stamp its marker. - -*** 2026-07-02 Thu @ 01:21:47 -0400 Phase 4 landed — checklist mechanics, pre-flight Q&A contract, page -The four-item checklist (in since Phase 1) gained its mechanics: a VERIFY-filing subsection (dedup against an existing sibling first — the deferred task stays TODO, so without the check every run re-files; placement/heading/body per todo-format.md) and a quick-question routing subsection (discriminator: one-line factual/preference pick vs tradeoff-weighing; three-plus questions = underspecified = file; item 2 data-loss never routes to Q&A). Preset section gained the batch-ask contract (one message, recommendation-first numbered options per interaction.md, answers recorded as dated lines in the task bodies before the run). Page section finalized (fires once on set-done or cap-hit; notify --persist is the paging surface). Common Mistakes 12-13 added. Checklist only ever reduces what runs; pre-flight fires only under the preset. - -*** 2026-07-02 Thu @ 01:24:50 -0400 Phase 5 landed — per-task JSONL metrics log -Metrics section written into work-the-backlog.org: one record per task at outcome time, appended to the project's .ai/metrics/work-the-backlog.jsonl (git-tracked, append-only, dir+file created on first append). Full field table per the spec (ts, run_id, project, caller, task, outcome, defer_reason, upfront_decision, wall_clock_s, commit_sha, review_findings), outcome slugs mapped to the prose vocabulary, commit_sha flagged as the corrections-signal key (comma-separated when a task decomposed into several commits). Added the sixth outcome the spec's readiness section demanded but the enum missed: failed (tree left working, surfaced, run continues) — wired into the Outcomes vocabulary and loop step 4. A failed append warns in the run summary but never blocks, reorders, or aborts execution. - -*** 2026-07-02 Thu @ 01:27:43 -0400 Phase 6 landed — synthesis step to org-roam -Synthesis section written into work-the-backlog.org (trigger "synthesize backlog metrics", INDEX row added): discover the JSONL union across project roots, classify each project per knowledge-base.md's denylist before reading, exclude work/unknown projects with the refusal contract, compute per-run rollups + trends, compute the corrections signal (later revert/fix commit touching the same files within ~14 days — a flag for human review, not a conviction), write one :agent:metrics: KB node under ~/org/roam/agents/ with [[id:...]] links to prior synthesis nodes, pull-before/commit-push-after. Read-only over the logs plus the single KB write; never mutates JSONL, todo.org, or any tree. - -*** 2026-07-02 Thu @ 05:26:07 -0400 Live trial passed — first speedrun ran 3/3, every loop part exercised -Craig named the ordered set (id-link conversion, host-identity guard, template-sync policy) and said it was the validation run. Pre-flight Q&A fired once (two questions, both answered, answers stamped as dated lines before the run); each task landed as its own reviewed commit under the waiver (78bbaae, b6a977c, ed75d3c); metrics JSONL carries one record per task (run c726f526); the end-of-set page arrived via notify --persist. Nothing needed a VERIFY this run (all three cleared the checklist). Craig's read: granted :LOOP_MAY_COMMIT: on the strength of the run. - -*** 2026-07-02 Thu @ 05:26:07 -0400 Flipped the spec DOING → IMPLEMENTED -All six phases built and the live trial validated. Keyword, dated history line, and Metadata mirror all flipped per the transition-ownership table. -** DONE [#B] inbox-send filename collision silently overwrote a message :bug:solo: -CLOSED: [2026-07-02 Thu] -:PROPERTIES: -:CREATED: [2026-07-02 Thu] -:END: -From archsetup (2026-07-02 0543, found in the wild): two --text sends in the same minute whose text starts with the same phrase derive identical filenames, and the second silently overwrites the first — archsetup lost a message at 05:42 and had to resend. Severity data-loss x rare-edge = P2 = [#B]. - -Resolution 2026-07-02 (auto-inbox-zero loop, standing yes): uniquify() guard in inbox-send.py — an existing target gets a -2/-3/... stem suffix, both send_text and send_file paths, extension preserved. Four red-first tests reproduce the loss (module-level with a fixed timestamp so the same-minute collision is deterministic, plus a CLI loss-proof check); 30/30 green. -** DONE [#C] page-me notify styling — all-red too alarming :bug:solo: -CLOSED: [2026-07-02 Thu] +Killed at the 2026-07-13 task review: home retired and tore down the ntfy channel on 2026-07-04, so this proposal's transport no longer exists. Its living successors are the Signal pager ([#B] task above — one identity on velox, runbook pending) and agent-page (shipped 2026-07-13), which cover the send half; two-way (read-replies) rides the Signal runbook. +Proposal from the home project (2026-06-17): promote the self-hosted ntfy-over-Tailscale phone channel it built and verified on ratio into a general two-way agent-comms tool rulesets owns. Full proposal: [[file:docs/design/2026-06-17-ntfy-agent-comms-proposal.org]] (as-built runbook stays in the home project at =working/phone-notifications/spec.org=). What rulesets would decide: canonicalize =phone-notify= (send) plus a new =phone-recv= (check-since) as synced bin scripts; the per-machine config/secret convention (token in =~/.config/phone-notify/config= chmod 600 today, vs GPG-encrypted in dotfiles); a reference =ntfy-inbound-handler= plus systemd user-unit for event-driven delivery (Tier A subscriber routes inbound to inbox/notify, Tier B inbound spawns an agent session, Tier C notify a live session — harness research); approval-button workflows for the commits.md gates when Craig is away from the desk (tap-to-approve, the high-value concrete use); and the relationship to the retired cross-agent-comms scripts (ntfy may be the transport they lacked). Worked via =spec-create=. Blocks the triage-intake phone-push task below. +** DONE [#B] Org-table helpers corrupt example blocks :bug:solo: +CLOSED: [2026-07-14 Tue] :PROPERTIES: -:CREATED: [2026-07-02 Thu] +:CREATED: [2026-07-11 Sat] +:LAST_REVIEWED: 2026-07-13 :END: -From Craig via the roam inbox (2026-07-02, routed by archsetup): the page notify's all-red styling "makes me feel like somehow the system is about to crash" — should be a persistent info-level notification. - -Resolution 2026-07-02 (auto-inbox-zero loop, standing yes): pages now use notify info --persist instead of notify alarm — page-me.org (all examples + prose, with Craig's verdict recorded) and work-the-backlog.org's end-of-set page. status-check's success/fail types untouched (job outcomes, not pages). -** DONE [#C] Template sync with gitignored-only local changes :feature: -CLOSED: [2026-07-02 Thu] -From Craig via the roam inbox (2026-07-02, routed by archsetup): downstream projects should still pull template updates when their local changes sit entirely in gitignored files or directories — an inbox drop or a file left to read doesn't affect the templates, yet it currently holds the sync back and projects fall behind. When worked: verify how the sync gate actually detects dirtiness today, then let gitignored-only changes pass it. +Fixed in 951b6fc, test-first. Both defects landed as filed (block-type-aware scanning in both helpers; lint-org CLI report-only by default, writes behind --fix), plus the true corruption path found during the work: wrap-org-table's load-time CLI dispatch fired on lint-org's require and reformatted lint-org's file arguments. Entry-script guard added. The named regression test (example block byte-identical) is in the suite. +=wrap-org-table.el= and =lint-org.el= both scan for =/^\s*|/= lines and rewrite them as org tables without skipping =#+begin_example=/=src=/=quote= regions, so ASCII art using pipe characters gets mangled into bordered tables. Reproduced 2026-07-09 in the work project against an architecture doc with a pipe/=v= flow diagram; a plain indented block became a table with =|---|= rules between every line. Two separable defects: (1) table detection is line-based — both helpers should use =org-element-at-point= / =org-in-block-p= to skip example/src/quote/verse blocks; (2) =lint-org.el= mutates its input on disk with no confirmation — passing five files reformatted all five (one by 1949 lines). A linter must report, not write; put the reformat behind an explicit =--fix= flag. -2026-07-02 Thu @ 05:09:58 -0400 — Craig (speedrun pre-flight): policy + audit. Scope read found startup's git gates already ignore untracked/ignored files; state the policy in startup.org and audit every dirty-check in the synced workflows to match (monitor-inbox's bare porcelain check is the known offender; tracked-modification blocking stays). +Grading (severity × frequency, per todo-format.md): Critical severity (silent org data loss; recoverable here only because the content was git-staged) × rare-edge-case frequency (fires only when a mixed table+example file is passed to the helper) = P2 = [#B]. -Resolution 2026-07-02: template-freshness policy stated in startup.org Phase A.0 (dirty = tracked modifications only; untracked/gitignored never block pulls, ffs, or monitoring gates; the rsync WIP-guard named as the one deliberate exception — it holds back rulesets' own outbound WIP). Full audit of dirty-checks across synced workflows: startup's two git gates already complied; inbox.org monitor mode was the one offender — its precondition now uses --untracked-files=no with the explicit-staging rationale, and its close-out sweeps tracked changes only. triage-intake auto mode borrows monitor's gates, so it inherits the fix by reference. +Regression test: run =wrap-org-table.el= against a file containing a =#+begin_example= block whose lines start with =|= and assert the block is byte-identical afterward. Source: work handoff 2026-07-09 (=inbox/2026-07-09-1341-from-work-bug-data-loss-wrap-org-table-el-and.org=). diff --git a/voice/SKILL.md b/voice/SKILL.md index 4690b97..19eb38b 100644 --- a/voice/SKILL.md +++ b/voice/SKILL.md @@ -44,9 +44,9 @@ Terse is a budget, not an adjective. Each publish-artifact type has a target sha |----------|--------| | Commit body | Skip entirely when the subject line carries the change. Otherwise short paragraphs: the constraint, bug, or tradeoff. No play-by-play. | | PR description | Problem / Fix / Why / Testing, each section tight. | -| PR review summary | One long sentence or a few short ones. Verdict closes it. Verdict formulas ("Approving.", "Requesting changes.") are valid sentences here. | +| PR review summary | Lead with the substantive pointer, verdict closes it. No praise, not even a bare positive (#40). Verdict formulas ("Approving.", "Requesting changes.") are valid sentences here. | | Inline pin (finding) | ~4 sentences in stems shape (#42): where the bug is, the fix, why it's better. | -| Praise comment | One sentence naming what's good. Nothing else (#40). | +| Praise comment (inline only) | One sentence naming what's good. Nothing else (#40). Never in the summary body. | | Follow-up approval after prior feedback was addressed | Exactly "Approved." | ## Your Task @@ -348,7 +348,7 @@ See `voice/references/voice-profile.org` §39 for problem, basis, examples, and ### 40. Praise vs Correction Asymmetry [personal] -**Rule.** Praise on a PR review is short and unjustified (the author knows why their good change is good). Correction always explains the why, gently and briefly, the way a mentor would. Never as a verdict from on high. **Verification narration is the same defect as justified praise:** "I traced X and it's safe because..." pads the compliment with the reviewer's homework. Tracing the code is the reviewer's job, not content for the comment — if verification found a problem, the problem gets the words; if it found nothing, it gets zero words. +**Rule.** Praise on a PR review is short and unjustified (the author knows why their good change is good). Correction always explains the why, gently and briefly, the way a mentor would. Never as a verdict from on high. **Verification narration is the same defect as justified praise:** "I traced X and it's safe because..." pads the compliment with the reviewer's homework. Tracing the code is the reviewer's job, not content for the comment — if verification found a problem, the problem gets the words; if it found nothing, it gets zero words. **An approve summary carries no praise at all** — not even a bare positive ("Clean.", "Solid fix."). Lead the summary with the substantive pointer (the design note pinned inline) and close with the verdict: "One design note inline, not a blocker. Approving." An approve with nothing to flag is just "Approving." Short unjustified praise survives only as an inline pin on the line it refers to, never in the summary body. See `voice/references/voice-profile.org` §40 for problem, basis, examples, and history. diff --git a/voice/references/voice-profile.org b/voice/references/voice-profile.org index 088f0eb..765d513 100644 --- a/voice/references/voice-profile.org +++ b/voice/references/voice-profile.org @@ -1308,9 +1308,9 @@ Local absolute paths (=/home/<user>/...=, =/Users/<user>/...=), private repo nam Personal mode only. General and prose skip because the rule assumes a PR review context. *** Rule -Praise on a PR review is short and unjustified (the author knows why their good change is good). Correction always explains the why, gently and briefly, the way a mentor would, never as a verdict from on high. Keep it brief either way. +Praise on a PR review is short and unjustified (the author knows why their good change is good), and it survives only as an inline pin on the line it refers to. Correction always explains the why, gently and briefly, the way a mentor would, never as a verdict from on high. Keep it brief either way. -On an approve summary: praise plus verdict, nothing else. Cut any clause that describes or justifies the change. "Clean fix on the stacking bug, the tri-state is the right level to solve it at, and the tests cover the edges. Approving." becomes "Clean fix on the stacking bug. Approving." If a clause references what the code does or why it works, delete it. +On an approve summary: no praise at all, not even a bare positive ("Clean.", "Solid fix."). Lead with the substantive pointer — the design note pinned inline — and close with the verdict; an approve with nothing to flag is just "Approving." "Clean fix on the stacking bug, the tri-state is the right level to solve it at, and the tests cover the edges. Approving." becomes "One design note inline, not a blocker. Approving." (or just "Approving." with nothing to flag). Cut any clause that describes, justifies, or compliments the change — if a clause references what the code does, why it works, or how good it is, delete it. On a finding or change-request: always give the why, gently and briefly. Not "Move this to a helper." but "I'd pull this into one helper — three copies of the same rule means the next change has to touch all three, and missing one brings the bug back." @@ -1329,9 +1329,11 @@ Nice clean migration, the provider mocks and the Normal/Boundary/Error cases are *** After #+begin_example -Clean migration. Approving. One note inline: I'd rename `x` to `provider` — it reads as a generic placeholder and the next person won't know it's the resolved provider without tracing it. +One naming note inline, not a blocker. Approving. #+end_example +The rename rationale (`x` reads as a generic placeholder; the next person won't know it's the resolved provider without tracing it) lives in the inline pin, not the summary — the summary points, the pin teaches. + *** Before (verification narration) #+begin_example All three fixes look right. I traced useMapActions and the unmount cleanup is safe because the hook returns a memoized object, and the provider wraps the whole app so neither call site lands on the no-op path. @@ -1339,16 +1341,19 @@ All three fixes look right. I traced useMapActions and the unmount cleanup is sa *** After #+begin_example -All three fixes are clean and well-aimed. +Approving. #+end_example +Nothing to flag, so the summary is the bare verdict. The old "All three fixes are clean and well-aimed" is itself praise, and praise is now cut from the approve summary entirely. + *** Detection -In a PR review summary or comment: a praise clause that explains why the good thing is good, a praise clause followed by the verification work that supports it, or a finding or change-request that states what to fix without saying why. +In a PR review summary or comment: any praise on an approve summary (including a bare positive), a praise clause that explains why the good thing is good, a praise clause followed by the verification work that supports it, or a finding or change-request that states what to fix without saying why. *** History - Original SKILL.md entry: praise-versus-correction asymmetry for PR review. - 2026-05-29: migrated to this file as the canonical home per the pairing rule. - 2026-06-10: verification-narration variant added after the third recurrence — a review draft praised a fix and then narrated the verification supporting the praise (the #236 draft). Added to the SKILL.md rule line and the high-recurrence attestation set. Craig's call, from the work-project session. +- 2026-07-11: bare-positive carve-out removed. An approve summary now carries no praise at all, not even "Clean." / "Solid fix." — lead with the substantive pointer, close with the verdict. Craig's ruling from a DeepSat review session (approved "One design note inline, not a blocker. Approving."). Same change applied to review-code's Posted Summary Voice and commits.md Shape 1. ** §41 No Emphasis Formatting diff --git a/working/inbox-zero-phase-e/proposed-inbox-zero.org b/working/inbox-zero-phase-e/proposed-inbox-zero.org index a3fd040..5fa7e12 100644 --- a/working/inbox-zero-phase-e/proposed-inbox-zero.org +++ b/working/inbox-zero-phase-e/proposed-inbox-zero.org @@ -1,5 +1,5 @@ #+TITLE: Inbox Zero Workflow -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2026-06-13 * Overview |
