diff options
| -rw-r--r-- | .ai/sessions/2026-07-19-21-15-launcher-hardening-inbox-hook-colloquialisms.org | 238 | ||||
| -rw-r--r-- | todo.org | 190 |
2 files changed, 329 insertions, 99 deletions
diff --git a/.ai/sessions/2026-07-19-21-15-launcher-hardening-inbox-hook-colloquialisms.org b/.ai/sessions/2026-07-19-21-15-launcher-hardening-inbox-hook-colloquialisms.org new file mode 100644 index 0000000..8f5207c --- /dev/null +++ b/.ai/sessions/2026-07-19-21-15-launcher-hardening-inbox-hook-colloquialisms.org @@ -0,0 +1,238 @@ +#+TITLE: Session Context +#+AUTHOR: Craig Jennings +#+DATE: 2026-07-18 + +* Summary + +** Active Goal + +Three tasks shipped this session, all closed, pushed, velox synced (HEAD f6a2701). No active goal remaining; next is a fresh backlog pick. + +1. ai-launcher-hardening [#C] (113e8d8, 2b619f1, closed 33c6d7b): hardened =claude-templates/bin/ai= per its measurable acceptance criteria. Launcher tests 9 → 42, four pure decision cores extracted (=_git_prep_action=, =_order_windows=, =_match_window_id=, =_git_is_dirty=) each N/B/E, footgun audit + /refactor pass fully dispositioned, shellcheck clean + shfmt -i2 -ci + suite green before/after + live smoke correct. +2. inbox-boundary-check hook [#B] (94e54f6, closed f6a2701): soft-nudge Stop hook enforcing the task-boundary inbox check. 6 bats, wired in settings.json + snippet, protocols note. Takes effect next session. +3. colloquialisms / "the list" convention [#B] (8fd9e39, closed f6a2701): protocols.org Colloquialisms section + wrap-it-up Step 1 Before-Close Queue sub-step. 4 bats. Live now. + +KB: promoted 0 / consulted no + +** Decisions + +- The task's acceptance criteria are FIXED and live in the task body (todo.org, the 4 moves per todo-format.md's "Making an open-ended task measurable"). Follow them; don't re-derive scope. +- Interactive runtime picker is OUT of this :solo: scope (design call) — file separately if wanted. +- Characterization discipline per testing.md (refined this session, commit 179c495): record-not-spec, full Normal/Boundary/Error set per unit, negative/boundary cases are the bug-finders, extract-pure-core-when-IO-blocks IS the hardening. + +** Data Collected / Findings + +- Surface (22 fns). COVERED (via scripts/tests/ai-launcher-runtime.bats, 9 tests, runtime path): resolve_agent_cmd, build_runtime_choices, pick_runtime, build_instructions, print modes. UNCOVERED (17 to net): usage, check_deps, attach_session, create_window, maybe_add_candidate, build_candidates, fetch_candidates, git_status_indicator, annotate_candidates, auto_pull_if_clean, read_selections, sort_windows, find_window_id, prep_git_single, attach_mode, single_mode, multi_mode, print_launch_mode. +- Pure/near-pure (characterize directly, N/B/E): git_status_indicator, maybe_add_candidate (dedup), annotate_candidates (format), read_selections (parse), usage. tmux/git-coupled (extract pure core + thin wrapper): sort_windows (ordering), create_window, attach_session, find_window_id, prep_git_single, auto_pull_if_clean. ~3 functional tests over single_mode/multi_mode/attach_mode against a throwaway tmux session. +- Canonical bin/ai is claude-templates/bin/ai; installed via make install's bin loop (symlink to ~/.local/bin/ai). Tests live in scripts/tests/ (not .ai mirror). shellcheck + shfmt present; kcov NOT installed. + +** Files Modified + +This session (all pushed to origin/main, velox synced to d49be09): sentry build a8b6cf4/ccc9c26/c6383e9/8c0a56b, trial fix beb7f0b, gui-open b3195e9, flashcard apkg converter a143679 + multi-tag a14e43b, task closes a760d8e, knowledge-arch landing 179c495/2e19048/94df71e, flake fix 94015e6, launcher scoping d49be09. Nothing in flight — tree clean at the flush. + +** Next Steps + +Launcher hardening is done and pushed. Pick the next backlog task. Strong candidates surfaced this session: the two [#B] :feature: shared-asset proposals (todo-cleanup dated-seal already shipped ddbd47f; remaining backlog includes the inbox-boundary-check hook, the colloquialisms/the-list convention, and the build-to-prototype ui-prototyping extension). Manual: the sentry overnight live-trial on ratio (4-part task) stays Craig's to run. + +STANDING (still in force this session): run the suite as its OWN step and read it green before each commit; sync velox (git pull + make install, ssh 100.127.238.103) after each push; /review-code + /voice personal per commit; keep velox current. + +* Session Log + +** 2026-07-18 Sat 17:52 CDT — Startup + inbox inventory + +Ran startup.org. Phase A.0: rulesets pull skipped (dirty tree — =.claude/settings.json= carries a harness-written model flip opus → claude-fable-5[1m] against the committed opus pin bd76d98; needs Craig's call). =make install= nothing new. Project fetch clean. Phase A: no crash anchor (clean prior wrap), =.ai/= synced from templates, staleness 3 tasks >7 days, roam inbox 18 items (all foreign — archsetup/takuzu/home/clock-panel, none rulesets-claimed), KB 97 nodes / no relevant titles / best-practices path unresolved, spec-sort + host-identity probes silent. + +Inbox: 7 pending. Acted on the two trivial ones immediately: +- website priority-scheme FYI — deleted (pure FYI, loop already closed by their "done"). +- website roam-KB-hosting-moved — applied the factual origin-URL fix in =claude-rules/knowledge-base.md= (git@cjennings.net:roam.git → cjennings@cjennings.net:git/roam.git), verified ratio's =~/org/roam= remote already points at the new URL. Deleted the inbox file. Commit + reply to website pending. Surfaced to Craig: rulesets.git itself is still publicly browsable on cgit (his open decision, per website's note). + +** 2026-07-18 Sat 18:02 CDT — Triage-intake redesign applied (item 1 of 3) + +Craig approved option 1 (apply as sent). Copied both sent canonicals over =claude-templates/.ai/workflows/{triage-intake,daily-prep}.org=, synced the mirror via =sync-check.sh --fix=, full suite green (374 + 67 pytest, all ERT expected, all bats ok, make exit 0). Review ran (/review-code --staged, inline): verdict Approve — the two remaining "suggested-actions" mentions are dated history entries (correct to keep), ORDER still governs the on-request long form, daily-prep line 58 keeps old "Action items" wording but is behaviorally accurate (Minor, not fixed). Commit 6e48714 (voice-passed, gate skipped — .ai/ tracked). Deleted the 3 inbox files, replied to work (delivered to their inbox 18:02). + +Three substantive shared-asset proposals queued for skeptical-review surfacing: +1. work 07-17: todo-cleanup.el --archive-done dated-seal model (retain 7→31, unparseable-CLOSED archiving, --seal rename). Craig ratified the design at work. Verified: todo-cleanup.el still has retain default 7, no seal — proposal premise current. +2. home 07-17: strip SCHEDULED/DEADLINE on dated-rewrite completion (todo-format.md + todo-cleanup --convert-subtasks) + new lint-org checker dated-log-heading-active-timestamp. +3. work 07-18: triage-intake Phase C/D redesign (three-section digest TASKS/FYI/MISC, close-by-default, reroute modifier) + daily-prep 3b one-liner — Craig's 2026-07-18 ruling, edited canonicals attached. Verified: diffs coherent, author lines clean (no "& Claude" regression), no other template references the retired format, plugins untouched by design. + +** 2026-07-18 Sat 18:11 CDT — Items 2 & 3 filed; 2 new home handoffs arrived + +Craig said "proceed" → filed both items 2 and 3 as [#B] :feature:solo: in todo.org, each with the design preserved to docs/design/ and cross-linked (both touch todo-cleanup.el, build as one batch). Replied to work (filed) for item 2. Route checks: both none (local keepers). settings.json resolved itself — Craig's /model opus set it back to match the committed opus pin (bd76d98), tree now clean on that file. + +Task-boundary inbox check caught 5 NEW home handoffs (17:53 + 18:04), two distinct proposals: +- Item 4: upcoming-birthdays feature promotion (cover + upcoming_birthdays.py + test + full daily-prep copy). Reviewed the code — clean, stdlib-only, 19 pytest cases (Normal/Boundary/Error, leap-day, placeholder-year 1900→None, window boundaries, CLI). Isolated the daily-prep reconcile: home's copy PREDATES my triage change, so only two birthday hunks apply (Heads-Up item 2 + Phase A source 8) — NOT a wholesale overwrite, which would revert my 3b triage edit. Home flagged this correctly. +- Item 5: "Colloquialisms and Expansions" + "the list" before-close-queue convention. Touches protocols.org + wrap-it-up.org (both synced) — a norm-adoption design call. + +** 2026-07-18 Sat 18:22 CDT — Item 4 (birthdays) applied + +Craig approved. Copied upcoming_birthdays.py + test into claude-templates/.ai/scripts/, applied ONLY the two birthday hunks to canonical daily-prep (verified my 3b triage edit survived — both copies still carry "three-section digest"), synced mirror. Suite green pytest 374→393 (19 new, all pass). Commit 80ebb74. Deleted 4 inbox files, replied to home (delivered 18:22). Also answered Craig's side question: all 27 projects carry the task-boundary inbox-check instruction (protocols.org synced), but it's a prose behavioral rule, not an enforced hook. + +Remaining: item 5 (colloquialisms/the-list convention). Still uncommitted from earlier: knowledge-base.md roam-URL fix + todo.org filings (items 2,3) + 2 docs/design proposals — batch at close-out. Craig also asked to explore a hook design for the inbox check after item 4. + +** 2026-07-18 Sat 19:56 CDT — Hook exploration, item 5 filed, inbox closed, all committed + +Explored the inbox-check hook with Craig. Key framing that emerged: two rails split by "must fire at a wall-clock instant, or just needs to be seen soon?" — boundary rail (Stop hook / UserPromptSubmit, fires when the agent yields, never interrupts mid-task) vs cron/at (must-fire-now: meeting alarms, meds, deadlines). The Stop event maps onto the rule's own boundary definition ("before reporting back"). Recommended soft-nudge Stop hook (stop_hook_active guard, once per turn) + UserPromptSubmit visibility injector. Filed as [#B] :feature: with full design. Craig extended the idea to a general boundary-check reminder rail (session-save nudge, uncommitted-drift nudge, soft reminders coming due) — offered a design note, he declined (not restarting, so no cold-start concern). + +Craig aborted the wrap (not restarting after all) and said complete the tasks. Actions: +- Filed item 5 (colloquialisms / "the list" convention) as [#B] :feature: — a cross-project convention adoption touching protocols.org + wrap-it-up.org, so filed not applied; needs Craig's adoption decision + a short design pass. Replied to home, design preserved to docs/design/. +- New archsetup FYI arrived mid-work (package-drift audit accepted on their side) — pure loop-close, deleted, no reply owed. +- Committed everything: 6523ed5 (knowledge-base roam URL fix), bc1d81d (4 backlog task filings + 3 docs/design proposals). Plus earlier 6e48714 (triage redesign), 80ebb74 (birthdays). + +STATE: inbox at zero, tree clean, suite green. 4 commits ahead of origin/main (6e48714, 80ebb74, 6523ed5, bc1d81d), 0 behind — UNPUSHED, awaiting Craig's push call. :LAST_INBOX_PROCESS: stamped 2026-07-18. + +Backlog filed this session (all [#B]): todo-cleanup dated-seal, dated-log planning-line strip + lint checker (batches with the seal), inbox-boundary-check hook, colloquialisms/the-list convention. + +** 2026-07-18 Sat 20:06 CDT — Two [#C] :quick:solo: closeouts (power-through) + +Craig picked the quick wins first. Both DONE + CLOSED, task-shaped (top-level): +- coverage-summary.el local-only doc (2cb7c1b, pushed after this): stated local-only status in the .el commentary header + elisp-testing.md "Measuring it" section (the gitignored .claude/scripts/ install is by-design, not a CI gap — Craig's 2026-06-28 decision). Sent emacs-wttrin a handoff to revert its contradicting header claim. +- install-ai on PATH (d2b1bef): new claude-templates/bin/install-ai thin launcher, resolves its own symlink chain and execs scripts/install-ai.sh. make install's existing bin loop links it to ~/.local/bin/install-ai (same as ai/agent-page) — resolves the task's open question (no dedicated sync, no dotfiles copy; the symlink is the canonical). 3 launcher bats incl. symlink-invocation. Verified live: install-ai --help runs from PATH. Suite green pytest 393, all bats/ERT pass. + +Remaining top solo work: the two batched todo-cleanup [#B] :feature:solo: tasks (dated-seal + planning-line strip) — the strongest next power-through target. + +** 2026-07-18 Sat 21:04 CDT — Batched todo-cleanup build (both [#B] :feature:solo: DONE) + +Craig said yes to the batch. Built both TDD in one commit ddbd47f (10 files, +746/-79), both DONE+CLOSED. + +Task A (dated-seal): retain default 7→31 via new defconst tc-archive-retain-days-default; unparseable-CLOSED archiving made explicit in the contract/commentary; new --seal mode (tc-seal-archive-file) renames task-archive.org → resolved-YYYY-MM-DD.org beside it, dated by seal run (not quarter — avoids late-quarter mislabeling), next --archive-done recreates fresh working file; sealed file inherits gitignore status; --seal flag + dispatch + report + CLI recognizer. 5 ERT tests. + +Task B (planning strip + lint): --convert-subtasks now strips the WHOLE planning line (CLOSED+SCHEDULED+DEADLINE) via tc--strip-planning-lines-in-entry, reversing the old CLOSED-only behavior (the enshrining test tc-convert-preserves-deadline... was rewritten to assert stripping). Stops at first non-planning line so body prose survives. New lint-org checker dated-log-heading-active-timestamp (flags active <..> SCHEDULED/DEADLINE on a keyword-less dated heading, ignores inactive [..]). todo-format.md sub-task rule step 5 + VERIFY path. 5 convert + 5 lint tests. + +Debugging note: seal tests failed only in the FULL suite — root cause was a latent bug, tc-test--reset never cleared tc-convert-subtasks, so a convert test's mode flag leaked and won the tc-process-file cond over seal. Fixed the reset + made the seal harness set all mode flags explicitly. Full suite green (todo-cleanup 52, lint-org 57, pytest 393, all bats). Replied to work + home (delivered). + +STATE at 21:04: ddbd47f committed, NOT yet pushed (2 quick-win commits 2cb7c1b/d2b1bef already pushed earlier). Reconcile clean before commit (0/0). + +** 2026-07-19 Sun 04:35 CDT — SENTRY BUILD STARTED (no-approvals + auto-flush) + +Craig approved the sentry spec after his deep read and told me to build it in no-approvals + auto-flush mode. Big pivot from the launcher-hardening [#C] (that was read-only, no edits to bin/ai — left as-is, still TODO). + +Spec flipped DRAFT → READY → DOING (docs/specs/2026-07-14-sentry-workflow-spec.org, ID f6c51f27-d7a2-4b63-9ff9-5ba005a66dfb). Metadata Status → doing. Build task decomposed into 4 phase sub-tasks under "** DOING [#B] Sentry workflow" with :SPEC_ID: binding. + +BUILD PLAN (resume anchor — re-read the spec's Design + Decisions + Implementation phases if context was cleared): +- Phase 1: =agent-lock= helper (canonical claude-templates/.ai/scripts/) — mkdir-atomic acquire, PID/host/ISO metadata, age-staleness reclaim (surfaced), bounded-wait contention (~30s), heartbeat refresh, acquire/release/status subcommands. Home: /run/user/<uid>/agent-locks/<name>/ with ~/.cache/agent-locks/ fallback. bats-tested. TDD. NOTHING calls it yet. +- Phase 2: =sentry.org= engine (.ai/workflows/, mirror synced) — entry ticket :COMMIT_AUTONOMY:, interactive entry gates, ff-only reconcile, sentry/YYYY-MM-DD-<host> branch, 10-pass runner (probe→work→session-context→commit), digest + approval queue, skip-and-note, spine-exclusion + fire-end digest commit, stall-notify after 2 skips, stop-sentry op. INDEX.org. +- Phase 3: companion reconciliations — knowledge-base.md, inbox.org core §5, roam-sync.sh header, triage-intake.org, wrap-it-up.org active-sentry guard. +- Phase 4: make test green; file live-trial follow-ups (Craig runs the trial); flip spec → IMPLEMENTED, close parent. + +Mode: no-approvals (commit+push per logical unit, Step 0 reconcile + pre-push check still run), auto-flush armed. COMMIT_AUTONOMY: yes + LOOP_MAY_COMMIT: yes in notes.org. + +Starting Phase 1 (agent-lock, TDD). + +** 2026-07-19 Sun @ 04:38:35 -0500 — flushed +Auto-flush before the sentry build. Clean boundary: spec flipped DRAFT→READY→DOING and decomposed (495e36b, pushed), nothing half-edited. Resuming into Phase 1 (agent-lock helper, TDD) per Summary Next Steps. + +** 2026-07-19 Sun @ 04:52 CDT — PHASE 1 DONE (agent-lock), pushed + +Built the agent-lock helper TDD (canonical claude-templates/.ai/scripts/agent-lock + mirror). Subcommands acquire/refresh/release/status/path. mkdir-atomic acquire, PID/host/ISO/TTL metadata, mtime-based staleness reclaim (surfaced note), heartbeat refresh, XDG_RUNTIME_DIR/agent-locks home with ~/.cache fallback. 18 bats tests. + +Self-review (review-code --staged, inline) caught one real concurrency bug pre-commit: the stale-reclaim path was rm -rf + mkdir in two steps, letting two acquirers who both see a lock stale double-acquire (loser deletes winner's fresh dir). Fixed with atomic-rename claim (mv wins-or-fails, then mkdir stays sole grant); strengthened test 7 to assert fresh metadata after reclaim. Verdict cleared to Approve. + +Full suite green (make test exit 0, 0 not-ok; agent-lock 18/18). Commit a8b6cf4, pushed to origin/main (0 behind, pre-push reconcile clean). /voice personal ran on the message. + +NEXT: Phase 2 — sentry.org engine (.ai/workflows/, mirror synced). Entry ticket :COMMIT_AUTONOMY:, interactive entry gates (dirty-tree / red-suite), ff-only reconcile, sentry/YYYY-MM-DD-<host> branch, pass runner (probe→work→session-context→commit contract), digest + morning-approval queue, skip-and-note semantics, spine-exclusion + fire-end digest commit, stall-notify after 2 unmerged-branch skips, stop-sentry op, INDEX.org entry. Re-read spec Design paragraphs (branch mechanics, locks, roam writes, unattended safety, pass list, digest) + the 10 decisions. + +** 2026-07-19 Sun @ 05:06 CDT — SENTRY BUILD COMPLETE (all 4 phases, pushed) + +All four sentry phases shipped, committed, pushed to origin/main, suite green throughout: +- Phase 1 a8b6cf4 — agent-lock helper (18 bats). Pre-commit review caught + fixed a reclaim double-acquire race (atomic-rename claim). +- Phase 2 ccc9c26 — sentry.org engine + INDEX entry. All 10 decisions / 12 findings reflected. +- Phase 3 c6383e9 — roam writers (knowledge-base.md, inbox.org §5) acquire roam-write lock + edit-plus-trigger (roam-sync sole committer); roam-sync.sh header; triage-intake note; wrap-it-up Step 0 active-sentry guard. Lock-name derivation pinned identically in sentry.org + wrap-it-up (sentry-<repo-basename>). +- Phase 4 8c0a56b — make test green at HEAD; spec DOING→IMPLEMENTED (dated history + Metadata mirror); build task + phase sub-tasks closed (sub-tasks → dated event-log entries); live trial filed as structured "Manual testing and validation" task. + +FINAL STATE: tree clean (only untracked spine), 0/0 vs origin/main, spec IMPLEMENTED, sync-check clean. Live agent-lock smoke test on the real runtime dir passed (acquire→held on ratio→release). Each commit ran /review-code (inline for docs) + /voice personal. + +REMAINING (Craig's, not agent-buildable): the overnight live-trial night on ratio — arm sentry, exercise the entry gates, observe one fire end to end, run the morning branch review. Filed as the 4-part manual-testing task in todo.org. Its findings become follow-up tasks. The launcher-hardening [#C] (read-only bin/ai) is still TODO, untouched (was pre-empted by the sentry build). + +** 2026-07-19 Sun @ 15:38 CDT — Live trial feedback #1 processed (roam-denylist mis-park) + +Craig ran a full sentry session from the WORK project. First trial finding: the inbox-zero pass (P2), running from ~/projects/work, parked the whole 19-item roam inbox as a cross-project boundary crossing and refused to tidy it — over-reading knowledge-base.md's work-denylist as "don't touch roam from work." Craig ruled: roam is a shared resource; the denylist only ever gated durable agents/ KB-node writes (a confidentiality guard). Reading roam + tidying the roam inbox are allowed from any project, work included. + +Fix (commit beb7f0b, pushed): applied work's prepared knowledge-base.md "Scope of the denylist — durable KB-node writes only" paragraph (naming the 2026-07-19 mis-park), plus one-line companion notes in sentry.org P2 and inbox.org roam mode pointing at the rule. Suite green. Replied to work (inbox-send, delivered 15:35), deleted the two work inbox items. + +Commits so far: a8b6cf4, ccc9c26, c6383e9, 8c0a56b (build), beb7f0b (trial fix #1). All pushed, tree clean. + +PENDING: 2 untracked archsetup items in inbox/ (11:47, gui-open proposal — unrelated to sentry, separate pass). Awaiting any further trial findings from Craig. + +** 2026-07-19 Sun @ 15:44 CDT — Inbox cleared: archsetup gui-open proposal applied + +Processed the two archsetup items (gui-open proposal). gui-open is a dotfiles-shipped launcher (a34d479, on PATH via ~/.dotfiles) that shows a file from a short-lived agent shell reliably — detaches through systemd-run --user (no shell reap), resolves the Hyprland instance after a restart, verifies a visible client. Cleared the value gate (fixes documented fragility both rules already flagged). + +Fix (commit b3195e9, pushed): interaction.md "Showing Craig Visuals" + desktop-capture.md "Showing the user something" now launch via gui-open instead of google-chrome-stable ... & / hyprctl dispatch exec imv. Guidance-only (tool is dotfiles-owned, not a rulesets script); added an "if not on PATH, needs a dotfiles pull" note. Replied to archsetup (delivered 15:42), deleted both inbox files. Inbox now clear. + +DAILY-DRIVER NOTE: gui-open ships via dotfiles a34d479 — confirmed on ratio (symlinked ~/.local/bin/gui-open → ~/.dotfiles). velox needs a dotfiles pull to have it, or the new rule guidance references a missing tool there. Flag to Craig. + +Session commits total: a8b6cf4, ccc9c26, c6383e9, 8c0a56b (sentry build) + beb7f0b (trial fix #1) + b3195e9 (gui-open guidance). All pushed, tree clean, inbox zero. + +** 2026-07-19 Sun @ 15:52 CDT — velox brought current (standing: keep it synced this session) + +Craig: do the dotfiles pull on velox now, and keep velox up to date with our changes for the rest of the session. Reached velox over tailscale (100.127.238.103, cjennings@). +- dotfiles: git pull → c3ef604 (was 9914a25), make restow hyprland (clean, no conflicts). gui-open symlinked ~/.local/bin/gui-open → dotfiles common tier; verified: direct exec prints usage, ~/.local/bin on interactive-login PATH (positions 1-2), interactive shell resolves it. Earlier "MISSING" was only the non-interactive SSH PATH. +- rulesets: git fetch + merge --ff-only → b3195e9 (was 3ae71be; also caught up on upcoming_birthdays, install-ai, docs/design proposals). make install relinked install-ai + agent-page, rest skipped. + +STANDING INSTRUCTION (rest of session): after each push, also update velox — git pull + make install on ~/code/rulesets, and a dotfiles pull + restow if dotfiles changed. ssh via 100.127.238.103. zsh gotcha: don't word-split an unquoted $VAR for the ssh command; run ssh inline. Non-interactive SSH PATH lacks ~/.local/bin — test tools via resolved path or zsh -lic. + +** 2026-07-19 Sun @ 18:42 CDT — Speedrun complete (2 flashcard tasks shipped) + +No-approvals speedrun over the 2 flashcard :solo: tasks, both TDD + review + voice, each its own commit, pushed, velox synced after each. +- apkg-to-orgdrill.py (a143679): inverse converter, stdlib zipfile+sqlite3, 17 tests + real-genanki round-trip. Grounded the apkg schema by generating/inspecting a real one first. +- flashcard multi-tag reconcile (a14e43b): broadened CARD_RE in to-anki + stats for :fundamental:drill:, added --tag-filter + --guid-salt + drill-membership guard; re-derived against current canonical (kept the #+TITLE fix); parse() 3rd element now anki-tag list. End-to-end verified (2→1 with --tag-filter). 465/100 real-deck check needs the work deck (not runnable here). +- Closed both as dated event-log entries (a760d8e). + +Suite green throughout (419 pytest). Session commits now: sentry build (4) + trial fix + gui-open + flashcard (2) + closes. All pushed, velox current at a760d8e. + +NEXT (Craig asked): discuss the ai launcher hardening [#C] task (todo.org line ~208). Remind him what it is. + +** 2026-07-19 Sun @ 19:04 CDT — Landed the testing/acceptance knowledge-architecture change + +Craig's directive: build the characterization-test + measurable-acceptance discipline into the workflows, and decide the KB-vs-rule boundary. Answer settled in discussion: apply-every-time discipline → rules (single source, auto-loaded, review-gated contributions via inbox); pull-when-relevant cross-project facts → KB. Almost none of this is KB material (it has a rule home); the KB is the capture/holding-pen, promotion moves it OUT into rules. + +Two commits: +- 179c495 (characterization discipline): testing.md — defined a characterization test (record-not-spec, Feathers recipe), required the full Normal/Boundary/Error set per unit, and the key lens that negative/boundary cases are the bug-finders; + the "extracting the pure core IS the hardening" framing in the refactor-for-testability section. code-quality.org gained the precondition that behavior-preserving rests on a characterization net. +- 2e19048 (measurable acceptance): todo-format.md new subsection "Making an open-ended task measurable (so it can be :solo:)" — bound surface / characterization net / disposition findings / objective floor; qualifying answer = dispositioned report. work-the-backlog's keystone defer item now recognizes absence-phrased open-ended goals and routes them to get criteria. + +Scope note: start-work and /refactor are NOT rulesets files (not installed at ~/.claude/skills/; registered elsewhere). Their pointers are a follow-up in whatever repo defines them — flagged to Craig. add-tests + review-code (rulesets skills) already well-wired to testing.md. + +Suite green, lint 0/0, velox synced to 2e19048. + +** 2026-07-19 Sun @ 19:14 CDT — Flaky-test fix + a discipline note + +While landing the start-work pointer (94df71e), chained make test into the commit command and the commit went through on a RED run — the bundling verification.md warns against. The red turned out flaky (rename-ai-artifact.bats teardown race, unrelated to the md edit; suite green on re-run, change sound), but the gate failed open. Correction going forward: run the suite as its own step, read it, then commit. + +Root-caused + fixed the flake (94015e6): rename-ai-artifact.bats teardown hit "rm: cannot remove .git: Directory not empty" because git background auto-maintenance (maintenance run --auto after commit) wrote into .git after the body, racing rm -rf. Fix: gc.auto 0 + maintenance.auto false in the throwaway repo before any git command arms them. Diagnose-not-mask (no rm retry). Verified 30 file runs + 2 full-suite runs green. + +Also corrected an earlier wrong claim: start-work + refactor ARE rulesets files (.claude/commands/, symlinked to ~/.claude/commands), already wired to testing.md for characterization. The acceptance-pattern landing is fully complete — no out-of-repo follow-up. + +** 2026-07-19 Sun @ 19:20 CDT — Scoped the ai-launcher-hardening task (dogfooded the pattern) + +Applied the measurable-acceptance pattern to the launcher task (d49be09). Rewrote the open-ended body with the 4 moves: bounded surface (22 fns, 5 covered via ai-launcher-runtime.bats / 17 uncovered, all named), characterization-net plan (pure-core extraction called out for the tmux/git-coupled fns), dispositioned audit (per-fn footgun matrix + /refactor pass), objective floor (shellcheck/shfmt/green suite/per-fn coverage + ~3 functional tests over single/multi/attach_mode). Split the interactive runtime picker OUT of :solo: (design call). Task is now genuinely :solo:-ready. :LAST_REVIEWED: bumped to 2026-07-19. + +The actual hardening work (bring 17 fns under characterization tests, extract pure cores, run the audit + refactor) is a substantial next-session speedrun candidate — now bounded. + +** 2026-07-19 Sun @ 20:19:15 -0500 — flushed +Interactive flush at a clean boundary (launcher task scoped + committed d49be09, tree clean, nothing half-edited). Resuming into option 2: work the ai-launcher-hardening characterization sweep per the refreshed Summary Next Steps. + +** 2026-07-19 Sun @ ~21:30 CDT — LAUNCHER HARDENING COMPLETE (2 commits + close, pushed, velox synced) + +Worked the ai-launcher-hardening [#C] :refactor:solo: end to end per its measurable acceptance criteria. Craig present, chose push+sync+close. + +Sequence: +- Grounded in bin/ai (540 lines), confirmed green baseline (make test exit 0), pinned the gate targets: shellcheck-clean (the bash hook enforces shellcheck only; shfmt is deliberately NOT hook-gated), shfmt house style = -i 2 -ci (agent-page conforms, bin/ai did not). +- Commit 113e8d8 (net + source-testable): wrapped dispatch in main() behind a run-vs-sourced guard so the file is sourceable for unit tests; cleared 4 pre-existing shellcheck warnings on touched paths (@{u} quoted ×3 = SC1083, literal display tilde disabled = SC2088). New scripts/tests/ai-launcher-characterization.bats, 20 tests: pure/near-pure N/B/E (git_status_indicator, maybe_add_candidate, annotate_candidates, read_selections, build_candidates, usage) + 4 functional (create_window, find_window_id, sort_windows, attach_mode) against a PRIVATE tmux socket (TMUX_TMPDIR + unset TMUX) so nothing touches Craig's live ai session; throwaway repos disable gc.auto/maintenance.auto (the rename-ai flake). +- Commit 2b619f1 (refactor): extracted four pure cores — _git_prep_action (git-prep none/pull/report classifier, shared by prep_git_single + auto_pull_if_clean), _order_windows (sort_windows ordering), _match_window_id (find_window_id), _git_is_dirty (DRY the dirty triad ×3). Added 13 unit tests for the cores incl. the safety case (dirty repo never auto-pulled). shfmt -i2 -ci applied. 42 launcher tests green, live black-box smoke correct for claude+codex. +- Commit 33c6d7b: closed the task DONE+CLOSED (top-level ** → task-shaped) with the dispositioned resolution note. + +Footgun audit fully dispositioned (report delivered inline to Craig): unquoted-expansion/word-split — clean; errexit-off — intentional/documented, kept; subshell state loss — none (globals set in main shell); exit-code propagation — intentional; sort_windows race — none (two-pass park-then-reassign is correct); create_window sleep 0.1 — declined (intentional); git-prep error paths (dirty/detached/no-upstream) — all correct, dirty-safety now a named test. /refactor: 4 extractions + shfmt applied; build_candidates exit-status + create_window sleep declined. + +Honest coverage limit stated: attach_session and full end-to-end of single/multi/fetch stay partially covered (terminal step attaches/blocks on fzf, can't run headless); their decision logic was extracted into the netted cores. + +STATE: 33c6d7b pushed, 0/0 vs origin/main, velox synced to 33c6d7b (make install: ai linked), tree clean (only untracked session anchor), suite 367 ok / 0 not-ok. Interactive runtime picker stayed OUT of scope (design call, on the generic-agent-runtime parent). No memory promotion needed (nothing durable/cross-project beyond the repo record). + +** 2026-07-19 Sun @ ~22:00 CDT — No-approvals speedrun: inbox hook + colloquialisms (both [#B] DONE) + +Craig said "no approvals speedrun time" after the launcher task. Built two [#B] backlog tasks, each TDD + inline review + /voice personal, committed + pushed + velox-synced. + +Task 1 — inbox-boundary-check hook (94e54f6): soft-nudge Stop hook backing the "check inbox/ at every task boundary" rule (was prose-only). Blocks the yield once + injects the pending count when =inbox-status -q= exits 1, steps aside on =stop_hook_active= re-entry (soft, not hard-block, so a mid-task pause never wedges), self-skips on no-inbox/no-inbox-status/clean. Prefers project-local =.ai/scripts/inbox-status=, PATH fallback. Wired ahead of =ai-wrap-teardown= in =.claude/settings.json= (which the live =~/.claude/settings.json= symlinks to → live global on both boxes) + =settings-snippet.json= + README row; glob-installed by =make install-hooks=. protocols.org Inbox Monitoring Cadence notes the enforcement. 6 bats. Live-verified on ratio (clean no-op, pending fixture blocks). Loads next session (hooks read at session start), so it didn't interfere with this wrap. + +Task 2 — colloquialisms / "the list" (8fd9e39): new =* Colloquialisms and Expansions= section in protocols.org. "put X on the list" → session-scoped Before-Close Queue (=* Before-Close Queue= heading in the session anchor, resets on archive, todo.org for must-outlive); "tell <project> <msg>" → =inbox-send=. wrap-it-up Step 1 gained a "Work the Before-Close Queue (before the Summary)" sub-step so queued work rides the wrap commit. Design calls: reference in protocols.org not per-project notes.org (synced = shared norm); queue in the session anchor as home did; wrap step at front of Step 1 not a half-step (keeps "Steps 1-5" framing). 4 documentation-integrity bats. Canonical+mirror synced. + +Both closes in f6a2701. Full suite green throughout (0 not-ok). velox synced to f6a2701 (make install ok). Then Craig said wrap it up. @@ -39,39 +39,6 @@ Tags are assigned and refreshed by =task-audit=; =task-review= keeps them honest * Rulesets Open Work -** DONE [#B] Sentry workflow — build from spec :feature: -CLOSED: [2026-07-19 Sun] -:PROPERTIES: -:SPEC_ID: f6c51f27-d7a2-4b63-9ff9-5ba005a66dfb -:END: -Built the sentry supervisor workflow from the spec -([[file:docs/specs/2026-07-14-sentry-workflow-spec.org][sentry workflow spec]], now IMPLEMENTED). Four phases, each committed and -pushed in no-approvals + auto-flush mode; full suite green throughout. The overnight -live trial is handed to Craig as a manual-testing task (below); its findings file as -follow-ups. -*** 2026-07-19 Sun @ 04:52:00 -0500 Built the agent-lock helper + 18 bats tests -=.ai/scripts/agent-lock= (canonical =claude-templates/.ai/scripts/=, mirror synced): -mkdir-atomic acquire, PID/host/ISO-timestamp metadata, mtime-based staleness reclaim -(atomic-rename claim so two acquirers can't double-acquire — caught by the pre-commit -review), heartbeat refresh, acquire/release/status/path subcommands, XDG_RUNTIME_DIR -home with =~/.cache= fallback. Commit a8b6cf4. -*** 2026-07-19 Sun @ 04:56:00 -0500 Built the sentry.org engine + INDEX entry -=.ai/workflows/sentry.org= (mirror synced): :COMMIT_AUTONOMY: entry ticket, the -interactive entry gates, ff-only reconcile, =sentry/<date>-<host>= branch mechanics, -the ten-pass probe→work→session-context→commit runner, digest + morning-approval -queue, skip-not-degrade safety, spine-excluded dirty checks + fire-end digest commit, -multi-day stall notify, the stop-sentry operation. All 10 decisions and 12 findings -reflected. Commit ccc9c26. -*** 2026-07-19 Sun @ 05:00:00 -0500 Wired the roam writers + wrap-up guard -knowledge-base.md and inbox.org core §5 acquire the roam-write lock and edit-plus- -trigger (roam-sync stays sole committer); roam-sync.sh header updated to match; -wrap-it-up.org gained a Step 0 active-sentry guard; triage-intake.org notes its -sentry-pass role. Graceful degradation when agent-lock is absent. Commit c6383e9. -*** 2026-07-19 Sun @ 05:04:00 -0500 Verified suite green + flipped spec to IMPLEMENTED -=make test= green at HEAD (pytest 393, ERT + bats all pass, exit 0). Flipped the spec -keyword DOING → IMPLEMENTED with a dated history line and mirrored the Metadata Status. -Filed the overnight live trial as a structured manual-testing task. - ** TODO Manual testing and validation *** Sentry — entry gates fire with Craig present What we're verifying: the interactive entry gates stop for the right states and start the loop only on a clean, green baseline. @@ -205,27 +172,6 @@ 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. -** DONE [#C] ai launcher hardening — bug hunt + refactor pass :refactor:solo: -CLOSED: [2026-07-19 Sun] -:PROPERTIES: -:CREATED: [2026-07-13 Mon] -:LAST_REVIEWED: 2026-07-19 -:END: -Resolved across two commits (113e8d8 net, 2b619f1 refactor). Brought the 17 uncovered functions under characterization tests (launcher tests 9 → 42), extracted the git/tmux decision logic into four pure cores (_git_prep_action, _order_windows, _match_window_id, _git_is_dirty) each with a Normal/Boundary/Error set, and dispositioned the footgun audit + /refactor pass. Objective floor met: shellcheck clean, shfmt -i 2 -ci consistent, make test green before and after, live black-box smoke correct for claude and codex. Honest limit: attach_session and the full end-to-end of single/multi/fetch stay partially covered (their terminal step attaches to tmux or blocks on fzf, can't run headless). Their decision logic was extracted into the netted cores. The interactive runtime picker stayed out of scope (a design call, filed on the generic-agent-runtime parent). -Harden =claude-templates/bin/ai= (the agent-session launcher, 540 lines, 22 functions). Origin: roam inbox 2026-07-13, phrased open-endedly ("find bugs until none visible, refactor until nothing worthwhile remains"). Rescoped 2026-07-19 with measurable acceptance criteria per =todo-format.md='s "Making an open-ended task measurable," which is what makes it =:solo:=. The four moves: - -1. *Bound the surface.* The 22 functions are the done-set. *Covered* (behaviorally, via =scripts/tests/ai-launcher-runtime.bats=, 9 tests over the runtime path): =resolve_agent_cmd=, =build_runtime_choices=, =pick_runtime=, =build_instructions=, the print modes. *Uncovered* (the 17 to bring under test): =usage=, =check_deps=, =attach_session=, =create_window=, =maybe_add_candidate=, =build_candidates=, =fetch_candidates=, =git_status_indicator=, =annotate_candidates=, =auto_pull_if_clean=, =read_selections=, =sort_windows=, =find_window_id=, =prep_git_single=, =attach_mode=, =single_mode=, =multi_mode=, =print_launch_mode=. - -2. *Net the behavior.* Characterization tests (Normal/Boundary/Error per unit, per =testing.md=) over the uncovered surface. The pure/near-pure ones take the category set directly: =git_status_indicator=, =maybe_add_candidate= (dedup), =annotate_candidates= (formatting), =read_selections= (selection parse), =usage=. The =tmux=/=git=-coupled ones (=sort_windows='s ordering, =create_window=, =attach_session=, =find_window_id=, =prep_git_single=, =auto_pull_if_clean=) get their pure decision logic extracted into helpers that take plain inputs and return plain results — that extraction *is* the hardening — with the I/O calls left as thin wrappers. - -3. *Disposition every finding.* (a) A bash-footgun audit per function, each cell fixed / n-a / filed: unquoted expansions + word-splitting, =set -euo pipefail= gaps and where errexit is intentionally off, subshell state loss, exit-code propagation, ordering/races in =sort_windows= + window creation, and the git-prep error paths (=prep_git_single= / =auto_pull_if_clean= on a dirty tree, detached HEAD, no upstream). (b) A =/refactor= pass, each finding applied (tests green) or declined with a one-line reason. - -4. *Objective floor.* =shellcheck= clean, =shfmt=-consistent, =make test= green before and after, and every uncovered function above has its characterization set (a per-function checklist — =kcov= isn't installed; install it if a single coverage number is wanted). Plus ~3 functional tests over the launch pipelines (=single_mode=, =multi_mode=, =attach_mode=) against a throwaway =tmux= session, for the composition bugs no per-function unit can see. - -*Qualifying answer:* a dispositioned report — surface split covered/uncovered, tests before → after, =shellcheck=/=shfmt= result, the footgun matrix fully dispositioned, the =/refactor= findings fully dispositioned, all green. Not "no bugs remain" (unprovable) — "every enumerated path passes its characterization set and clears the audit." - -*Out of this task's =:solo:= scope:* an interactive runtime picker. It's a feature carrying a design/preference call (does Craig want it, what shape), which is deliberation, not hardening — file it separately if wanted. The runtime-selection arc (claude/codex shipped; ollama/qwen pending the model-floor eval) stays on the generic-agent-runtime parent. - ** TODO [#B] Extend ui-prototyping rule with build-to-prototype :feature: :PROPERTIES: :CREATED: [2026-07-11 Sat] @@ -247,16 +193,6 @@ Open question from the roam inbox (2026-07-11): could the startup sequence for . :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. -** DONE [#C] Put install-ai on PATH, launchable as =install-ai= :chore:quick:solo: -CLOSED: [2026-07-18 Sat] -: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. - -Resolved: added =claude-templates/bin/install-ai=, a thin launcher that resolves its own path through the symlink chain and execs =scripts/install-ai.sh=. =make install='s existing bin loop symlinks it into =~/.local/bin/install-ai= (same mechanism as =ai= and =agent-page=), so no dedicated sync and no dotfiles copy — the symlink always points at the canonical. 3 launcher bats added (incl. symlink-invocation resolution). Verified live: =install-ai --help= runs from PATH. - ** TODO [#B] Document (and own) the Signal pager :feature:spec: :PROPERTIES: :CREATED: [2026-07-11 Sat] @@ -378,16 +314,6 @@ Design (Craig, 2026-06-28, supersedes the two proposed options): make the exempt *** 2026-07-19 Sun @ 18:41:00 -0500 Reconciled the flashcard multi-tag tooling into canonical Commit a14e43b. Broadened =CARD_RE= in both =flashcard-to-anki.py= and =flashcard-stats.py= so a heading is a card when =drill= is among its tag block (=:fundamental:drill:=), bounded card bodies by any L1/L2 heading (=HEADING_RE=), and added =--tag-filter= (subset emit) + =--guid-salt= (distinct GUID space) to to-anki, plus a drill-membership guard to stats. Re-derived against the current canonical rather than copying the preserved 2026-06-17 files, which predated the =#+TITLE= fix (060a938) and would have reverted it. parse()'s 3rd element is now the Anki-tag list; existing parse tests moved to that contract, new tests cover multi-tag / tag-filter / guid-salt / stats count, and a =flashcard-sync.bats= case guards the count. The real 465/100 check needs the work deck; verified end-to-end on a synthetic deck (2 cards → 1 with =--tag-filter fundamental=) through real genanki. Speedrun task 2 of 2. -** DONE [#C] coverage-summary.el documented as a local-only helper :chore:quick:solo: -CLOSED: [2026-07-18 Sat] -:PROPERTIES: -:CREATED: [2026-06-22 Mon] -: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). - -Resolved: documented the local-only status in the =coverage-summary.el= commentary header and in =elisp-testing.md='s "Measuring it" section — the gitignored install now reads as intentional, not a coverage gap. Sent emacs-wttrin a handoff to revert its contradicting header claim. - ** TODO [#C] Agent-KB / memory-sync — work + unknown-project write refusal :test: :PROPERTIES: :LAST_REVIEWED: 2026-07-13 @@ -1380,6 +1306,97 @@ having a skill to generate or check OV-1-shaped artifacts. Don't build speculatively — defense-specific notations are narrow enough that each skill should be driven by a concrete contract need, not aspiration. +* Rulesets Resolved +** CANCELLED [#C] ntfy phone channel as general two-way agent-comms :feature:spec: +CLOSED: [2026-07-13 Mon] +:PROPERTIES: +:CREATED: [2026-06-20 Sat] +:LAST_REVIEWED: 2026-06-24 +:END: +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-11 Sat] +:LAST_REVIEWED: 2026-07-13 +:END: +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. + +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]. + +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=). +** DONE [#B] Sentry workflow — build from spec :feature: +CLOSED: [2026-07-19 Sun] +:PROPERTIES: +:SPEC_ID: f6c51f27-d7a2-4b63-9ff9-5ba005a66dfb +:END: +Built the sentry supervisor workflow from the spec +([[file:docs/specs/2026-07-14-sentry-workflow-spec.org][sentry workflow spec]], now IMPLEMENTED). Four phases, each committed and +pushed in no-approvals + auto-flush mode; full suite green throughout. The overnight +live trial is handed to Craig as a manual-testing task (below); its findings file as +follow-ups. +*** 2026-07-19 Sun @ 04:52:00 -0500 Built the agent-lock helper + 18 bats tests +=.ai/scripts/agent-lock= (canonical =claude-templates/.ai/scripts/=, mirror synced): +mkdir-atomic acquire, PID/host/ISO-timestamp metadata, mtime-based staleness reclaim +(atomic-rename claim so two acquirers can't double-acquire — caught by the pre-commit +review), heartbeat refresh, acquire/release/status/path subcommands, XDG_RUNTIME_DIR +home with =~/.cache= fallback. Commit a8b6cf4. +*** 2026-07-19 Sun @ 04:56:00 -0500 Built the sentry.org engine + INDEX entry +=.ai/workflows/sentry.org= (mirror synced): :COMMIT_AUTONOMY: entry ticket, the +interactive entry gates, ff-only reconcile, =sentry/<date>-<host>= branch mechanics, +the ten-pass probe→work→session-context→commit runner, digest + morning-approval +queue, skip-not-degrade safety, spine-excluded dirty checks + fire-end digest commit, +multi-day stall notify, the stop-sentry operation. All 10 decisions and 12 findings +reflected. Commit ccc9c26. +*** 2026-07-19 Sun @ 05:00:00 -0500 Wired the roam writers + wrap-up guard +knowledge-base.md and inbox.org core §5 acquire the roam-write lock and edit-plus- +trigger (roam-sync stays sole committer); roam-sync.sh header updated to match; +wrap-it-up.org gained a Step 0 active-sentry guard; triage-intake.org notes its +sentry-pass role. Graceful degradation when agent-lock is absent. Commit c6383e9. +*** 2026-07-19 Sun @ 05:04:00 -0500 Verified suite green + flipped spec to IMPLEMENTED +=make test= green at HEAD (pytest 393, ERT + bats all pass, exit 0). Flipped the spec +keyword DOING → IMPLEMENTED with a dated history line and mirrored the Metadata Status. +Filed the overnight live trial as a structured manual-testing task. +** DONE [#C] ai launcher hardening — bug hunt + refactor pass :refactor:solo: +CLOSED: [2026-07-19 Sun] +:PROPERTIES: +:CREATED: [2026-07-13 Mon] +:LAST_REVIEWED: 2026-07-19 +:END: +Resolved across two commits (113e8d8 net, 2b619f1 refactor). Brought the 17 uncovered functions under characterization tests (launcher tests 9 → 42), extracted the git/tmux decision logic into four pure cores (_git_prep_action, _order_windows, _match_window_id, _git_is_dirty) each with a Normal/Boundary/Error set, and dispositioned the footgun audit + /refactor pass. Objective floor met: shellcheck clean, shfmt -i 2 -ci consistent, make test green before and after, live black-box smoke correct for claude and codex. Honest limit: attach_session and the full end-to-end of single/multi/fetch stay partially covered (their terminal step attaches to tmux or blocks on fzf, can't run headless). Their decision logic was extracted into the netted cores. The interactive runtime picker stayed out of scope (a design call, filed on the generic-agent-runtime parent). +Harden =claude-templates/bin/ai= (the agent-session launcher, 540 lines, 22 functions). Origin: roam inbox 2026-07-13, phrased open-endedly ("find bugs until none visible, refactor until nothing worthwhile remains"). Rescoped 2026-07-19 with measurable acceptance criteria per =todo-format.md='s "Making an open-ended task measurable," which is what makes it =:solo:=. The four moves: + +1. *Bound the surface.* The 22 functions are the done-set. *Covered* (behaviorally, via =scripts/tests/ai-launcher-runtime.bats=, 9 tests over the runtime path): =resolve_agent_cmd=, =build_runtime_choices=, =pick_runtime=, =build_instructions=, the print modes. *Uncovered* (the 17 to bring under test): =usage=, =check_deps=, =attach_session=, =create_window=, =maybe_add_candidate=, =build_candidates=, =fetch_candidates=, =git_status_indicator=, =annotate_candidates=, =auto_pull_if_clean=, =read_selections=, =sort_windows=, =find_window_id=, =prep_git_single=, =attach_mode=, =single_mode=, =multi_mode=, =print_launch_mode=. + +2. *Net the behavior.* Characterization tests (Normal/Boundary/Error per unit, per =testing.md=) over the uncovered surface. The pure/near-pure ones take the category set directly: =git_status_indicator=, =maybe_add_candidate= (dedup), =annotate_candidates= (formatting), =read_selections= (selection parse), =usage=. The =tmux=/=git=-coupled ones (=sort_windows='s ordering, =create_window=, =attach_session=, =find_window_id=, =prep_git_single=, =auto_pull_if_clean=) get their pure decision logic extracted into helpers that take plain inputs and return plain results — that extraction *is* the hardening — with the I/O calls left as thin wrappers. + +3. *Disposition every finding.* (a) A bash-footgun audit per function, each cell fixed / n-a / filed: unquoted expansions + word-splitting, =set -euo pipefail= gaps and where errexit is intentionally off, subshell state loss, exit-code propagation, ordering/races in =sort_windows= + window creation, and the git-prep error paths (=prep_git_single= / =auto_pull_if_clean= on a dirty tree, detached HEAD, no upstream). (b) A =/refactor= pass, each finding applied (tests green) or declined with a one-line reason. + +4. *Objective floor.* =shellcheck= clean, =shfmt=-consistent, =make test= green before and after, and every uncovered function above has its characterization set (a per-function checklist — =kcov= isn't installed; install it if a single coverage number is wanted). Plus ~3 functional tests over the launch pipelines (=single_mode=, =multi_mode=, =attach_mode=) against a throwaway =tmux= session, for the composition bugs no per-function unit can see. + +*Qualifying answer:* a dispositioned report — surface split covered/uncovered, tests before → after, =shellcheck=/=shfmt= result, the footgun matrix fully dispositioned, the =/refactor= findings fully dispositioned, all green. Not "no bugs remain" (unprovable) — "every enumerated path passes its characterization set and clears the audit." + +*Out of this task's =:solo:= scope:* an interactive runtime picker. It's a feature carrying a design/preference call (does Craig want it, what shape), which is deliberation, not hardening — file it separately if wanted. The runtime-selection arc (claude/codex shipped; ollama/qwen pending the model-floor eval) stays on the generic-agent-runtime parent. +** DONE [#C] Put install-ai on PATH, launchable as =install-ai= :chore:quick:solo: +CLOSED: [2026-07-18 Sat] +: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. + +Resolved: added =claude-templates/bin/install-ai=, a thin launcher that resolves its own path through the symlink chain and execs =scripts/install-ai.sh=. =make install='s existing bin loop symlinks it into =~/.local/bin/install-ai= (same mechanism as =ai= and =agent-page=), so no dedicated sync and no dotfiles copy — the symlink always points at the canonical. 3 launcher bats added (incl. symlink-invocation resolution). Verified live: =install-ai --help= runs from PATH. +** DONE [#C] coverage-summary.el documented as a local-only helper :chore:quick:solo: +CLOSED: [2026-07-18 Sat] +:PROPERTIES: +:CREATED: [2026-06-22 Mon] +: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). + +Resolved: documented the local-only status in the =coverage-summary.el= commentary header and in =elisp-testing.md='s "Measuring it" section — the gitignored install now reads as intentional, not a coverage gap. Sent emacs-wttrin a handoff to revert its contradicting header claim. ** DONE [#B] todo-cleanup.el dated-seal archiving :feature:solo: CLOSED: [2026-07-18 Sat] Redefine =--archive-done= aging from a 7-day roll-into-one-file model to a @@ -1405,7 +1422,6 @@ Changes to =.ai/scripts/todo-cleanup.el= (canonical in TDD, canonical-then-mirror per the sync-check invariant. Home's planning-line strip proposal (filed alongside) also touches todo-cleanup.el =--convert-subtasks= — the two can be built as one batch. - ** DONE [#B] Strip stale planning lines on dated completion + lint backstop :feature:solo: CLOSED: [2026-07-18 Sat] Two linked fixes so a closed sub-task can't keep polluting the org agenda. Origin: @@ -1432,7 +1448,6 @@ but nothing strips the planning line, and an interactive org close only stamps Build notes: the checker's regex must key on "no TODO keyword" so it never flags a live TODO that legitimately carries a SCHEDULED. TDD, canonical-then-mirror. Batches with the dated-seal task above (both touch todo-cleanup.el). - ** DONE [#B] Enforce the task-boundary inbox check via a hook :feature: CLOSED: [2026-07-19 Sun] Resolved with the soft-nudge design (94e54f6). New =hooks/inbox-boundary-check.sh= Stop hook blocks the yield once + injects the pending count when =inbox-status -q= exits 1, steps aside on the harness re-entry (=stop_hook_active=) so a mid-task pause never wedges, self-skips on no-inbox/no-inbox-status/clean. Wired ahead of =ai-wrap-teardown= in =.claude/settings.json= (which the live =~/.claude/settings.json= symlinks to) + the snippet; glob-installed by =make install-hooks=. 6 bats (pending/clean/re-entry/no-inbox/absent-status/project-name). protocols.org Inbox Monitoring Cadence now notes the enforcement. Live-verified on ratio (clean inbox no-ops, pending fixture blocks); velox synced + hook linked. The UserPromptSubmit visibility complement stayed unbuilt (optional in the design) — file separately if wanted. @@ -1500,7 +1515,6 @@ No hook can perfectly tell "a unit of work finished" from "the agent paused for other reason" — the harness exposes only "the turn is ending," not "a task is ending." The design leans on those two being usually the same in Craig's workflow. That's the tradeoff hard-block vs soft-nudge is really about. - ** DONE [#B] "Colloquialisms and Expansions" + "the list" before-close-queue convention :feature: CLOSED: [2026-07-19 Sun] Resolved (approved in the speedrun). New =* Colloquialisms and Expansions= section in =protocols.org= documents both shorthands: "put X on the list" → append to a session-scoped Before-Close Queue (=* Before-Close Queue= heading in the session anchor, resets on archive, todo.org for must-outlive items); "tell <project> <msg>" → =inbox-send=. =wrap-it-up.org= Step 1 gained a "Work the Before-Close Queue (before the Summary)" sub-step so queued work rides the wrap commit, unfinished items surfaced in the valediction. Design calls: reference in protocols.org not per-project notes.org (synced = shared norm); queue in the session anchor as home did; wrap step at the front of Step 1, not a new half-step (keeps the "Steps 1-5" framing). 4 documentation-integrity bats (=before-close-queue.bats=). Canonical + mirror synced. The UserPromptSubmit-style variant wasn't in scope. @@ -1534,25 +1548,3 @@ section vs a new shipped reference file); where the queue itself lives (home use insertion point (before the teardown/valediction, alongside the roam-inbox sub-step). Then it's a synced-file change (canonical-then-mirror) + a test that the wrap step drains the queue. - -* Rulesets Resolved -** CANCELLED [#C] ntfy phone channel as general two-way agent-comms :feature:spec: -CLOSED: [2026-07-13 Mon] -:PROPERTIES: -:CREATED: [2026-06-20 Sat] -:LAST_REVIEWED: 2026-06-24 -:END: -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-11 Sat] -:LAST_REVIEWED: 2026-07-13 -:END: -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. - -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]. - -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=). |
