diff options
| -rw-r--r-- | .ai/notes.org | 4 | ||||
| -rw-r--r-- | .ai/sessions/2026-06-23-22-36-inbox-guard-bash-bundle-consolidation-spec.org | 164 | ||||
| -rw-r--r-- | docs/design/2026-06-23-wrap-teardown-shutdown-proposal.org | 124 | ||||
| -rw-r--r-- | docs/inbox-workflow-consolidation-spec.org | 192 | ||||
| -rw-r--r-- | todo.org | 37 |
5 files changed, 515 insertions, 6 deletions
diff --git a/.ai/notes.org b/.ai/notes.org index af1d3c5..79b158f 100644 --- a/.ai/notes.org +++ b/.ai/notes.org @@ -32,7 +32,7 @@ See [[file:../README.org][README.org]] for the full layout, install modes, and l - =claude-rules/= — generic rules (=commits.md=, =testing.md=, =verification.md=, =subagents.md=) symlinked into =~/.claude/rules/= and applied to every Claude Code session on the machine. - Top-level skill directories (=add-tests/=, =debug/=, =five-whys/=, =frontend-design/=, =pairwise-tests/=, =playwright-js/=, =playwright-py/=, =root-cause-trace/=, =voice/=) — each a Claude Code skill, symlinked into =~/.claude/skills/= by =make install=. -- =languages/= — per-language bundles (rules + hooks + settings) copied into target projects via =make install-lang LANG=<name> PROJECT=<path>=. Both =LANG= and =PROJECT= are optional — fzf picks them interactively when omitted. Bundles currently shipping: =elisp=, =python=. +- =languages/= — per-language bundles (rules + hooks + settings) copied into target projects via =make install-lang LANG=<name> PROJECT=<path>=. Both =LANG= and =PROJECT= are optional — fzf picks them interactively when omitted. Bundles currently shipping: =bash=, =elisp=, =go=, =python=, =typescript=. - =.claude/= — repo-local Claude Code config: =settings.json= and =commands/=. - =hooks/=, =scripts/= — install helpers and PostToolUse validators that ride along with bundles. - =Makefile= — install / uninstall / list entry points. @@ -79,6 +79,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-06-15 -:LAST_INBOX_PROCESS: 2026-06-15 (3 items: fix-speedrun proposal filed [#C], 2 pearl acks cleared) +:LAST_INBOX_PROCESS: 2026-06-23 (2 handoffs implemented: inbox-zero capture-guard, install-lang neutral-default CLAUDE.md; bash bundle filed [#C]) Format: one =:MARKER: YYYY-MM-DD= line per workflow. Workflows overwrite their own marker on completion. diff --git a/.ai/sessions/2026-06-23-22-36-inbox-guard-bash-bundle-consolidation-spec.org b/.ai/sessions/2026-06-23-22-36-inbox-guard-bash-bundle-consolidation-spec.org new file mode 100644 index 0000000..19e9a2c --- /dev/null +++ b/.ai/sessions/2026-06-23-22-36-inbox-guard-bash-bundle-consolidation-spec.org @@ -0,0 +1,164 @@ +#+TITLE: Session — inbox guard, bash bundle, agent-neutral rules, inbox-consolidation spec + +* Summary + +** Active Goal + +Started by processing two inbox handoffs, then ran long across a queue of work: +two handoff fixes, a new bash bundle, item 1-2-3 from the roam inbox, and a spec +for consolidating the inbox workflows. Five commits landed; the inbox-consolidation +spec reached Ready for a next-session build. + +** Decisions + +- Item 1: build the guard as a shared, testable script (.ai/scripts/), not + inline in the workflow. Fix the handoff's malformed lisp (#(quote …) → + #'buffer-name). Add an emacs.md cross-reference. +- Item 2 (Craig's design answers, all recommendations): (Q1) language-neutral + default *fallback* template install-lang seeds when the bundle ships none — + per-bundle templates still win where present; (Q2) the default names *no* + language — Craig fills it in; (Q3) inbox-zero's wrap-up roam sub-step, on a + live-capture collision, *skips that sub-step non-blocking* and finishes the + rest of wrap-up. +- Bash/shell language bundle: built this session (shellcheck the gate, shfmt left + advisory since shell has no canonical style). Confirmed the five bundles cover + Craig's ecosystem — no further bundles warranted. +- Item 1 (inbox-zero empty sweep): built. Item 2 (agent-source): claude-rules/ + neutralized + .emacs.d note sent; the workflow sweep parked behind consolidation + (don't neutralize files about to be merged). Item 3 (wrap-teardown): filed, not + built (open decisions). +- Inbox consolidation: spec it before building (load-bearing 3→1 merge of synced + workflows). Engine shape = Option A (one inbox.org, process/monitor/roam modes). + "auto inbox zero" = interactive /loop only in v1; fully-unattended /schedule + cron pass deferred to vNext (Codex finding, narrowed). + +** Data Collected / Findings + +- Verified handoff #2 against the tree: claim "only elisp ships CLAUDE.md" is + stale — go ships one too; python + typescript ship none; install-lang guards + on [ -f "$SRC/CLAUDE.md" ]. Core gap holds. +- Only inbox-zero.org Phase D writes the roam inbox on disk (startup reads, + wrap-up delegates to inbox-zero). So one guard call covers it. +- Canonical/mirror: claude-templates/.ai/ is canonical; .ai/ root is the mirror + kept in sync by scripts/sync-check.sh (pre-commit enforced). Edit canonical, + then sync --fix. + +** Files Modified + +Item 1 (capture guard): NEW claude-templates/.ai/scripts/capture-guard + its +bats test; edited claude-templates/.ai/workflows/inbox-zero.org (Phase D guard +step, before the pull); edited claude-rules/emacs.md (new "don't edit on disk a +file the daemon is capturing into" section). Mirror synced to .ai/. + +Item 2 (CLAUDE.md fallback): NEW languages/default-CLAUDE.md (neutral, names no +language); edited scripts/install-lang.sh (fall back to default when bundle +ships none); Makefile LANGUAGES glob hardened to dirs-only; scripts/lint.sh +lints the default; scripts/tests/install-lang.bats +3 tests. + +Housekeeping: bash bundle filed todo.org [#C]; both handoffs preserved to +docs/design/ (2026-06-22-inbox-zero-capture-hardening.org, +2026-06-23-install-lang-claude-md-gap.org); TODO link repointed; replies sent to +home + archangel; notes.org :LAST_INBOX_PROCESS: stamped 2026-06-23. + +Second half: NEW languages/bash/ bundle (rules, validate-bash.sh shellcheck hook ++ 8 bats tests, pre-commit githook, settings, CLAUDE.md), Makefile glob for +languages/*/tests/*.bats, README + notes bundle-list fixes. inbox-zero.org Phase +B/D empty-sweep. claude-rules/ (interaction, cross-project, working-files, +triggers) agent-neutralized. NEW docs/inbox-workflow-consolidation-spec.org +(Ready). todo.org: bash bundle DONE, + filed wrap-teardown [#B], inbox-empty +[#C], agent-source [#C], consolidation [#B], unattended-cron [#D]. + +Commits this session: 603abc4 (capture-guard), 71db71b (install-lang fallback), +3626285 (bash bundle), 3da2725 (inbox empty sweep), 6ad0442 (agent-neutral rules). +Roam repo: 436d646 (route rulesets tasks). All un-pushed until this wrap. + +Verification: full make test exit 0 (200 ok, 0 not-ok); lint clean except a +pre-existing remove.sh chmod warning (untouched); capture-guard lisp eval'd in the +live daemon; bash hook + githook dogfood shellcheck-clean. + +** Next Steps + +Build the inbox consolidation from the Ready spec (docs/inbox-workflow-consolidation-spec.org): +Phase 1 author inbox.org, Phase 2 reconcile callers + retire the 3 old files + +stale-ref grep, Phase 3 auto-inbox-zero, Phase 4 verify. Then the parked agent- +neutrality workflow sweep (over the consolidated file), then item 3 (wrap-teardown, +needs Craig's 3 open decisions). Queue tasks #5, #6, #7 carry these. + +KB: promoted 0 / consulted no + +* Session Log + +** Committed both fixes (603abc4, 71db71b) + +Two feat commits landed as Craig: capture-guard (603abc4) and install-lang +neutral CLAUDE.md (71db71b). Author verified c@cjennings.net. Push deferred to +wrap-up. notes.org marker + session-context.org stay for the wrap commit. + +Built the bash/shell bundle (languages/bash/): rules (bash.md, bash-testing.md), +shellcheck validate hook (8 bats tests, handles extensionless shell via shebang, +caught a real path-dot bug during TDD), shellcheck pre-commit githook, +settings.json, gitignore-add.txt, "Bash/shell project" CLAUDE.md. Makefile test +target extended to discover languages/*/tests/*.bats. README bundle table + notes +bundle list corrected (were stale at elisp-only / elisp+python). bash-bundle TODO +closed DONE. Full make test exit 0 (200 ok), lint clean, hook+githook dogfood +shellcheck-clean, installs + fingerprint-detects correctly. + +Committed bash bundle (3626285). Awaiting Craig's call on further bundles (I +recommended none — the five cover his visible ecosystem). + +** Inbox zero (local + roam) + +Local inbox: a new home handoff arrived mid-session — wrap-it-up teardown + +"wrap it up and shutdown" (Craig's own design). Filed [#B] :feature:, preserved +to docs/design/2026-06-23-wrap-teardown-shutdown-proposal.org, replied to home, +NOT built (shared-asset + open decisions). Companion cj/ai-term-quit routes to +.emacs.d when built. + +Roam inbox (11 items, 2 rulesets-claimed): imported both to rulesets todo.org — +"inbox-zero: delete empty roam entries on triage" [#C], and "Multiple +agent-source improvements" [#C] :spec: (naming the agent, agent-neutral rule +source / Codex review, multi-LLM ai-term note to .emacs.d). Removed both from the +roam inbox via the new capture-guard (exit 0, safe) + pull, committed the roam +repo (436d646). 9 foreign items left untouched (emacs, pocketbook, archsetup x4, +chime, emacs-wttrin, pearl). No empty entries existed to delete this pass. + +Pending push: rulesets 4 commits ahead (603abc4, 71db71b, 3626285 + the inbox +filing), roam 1 commit (436d646, roam-sync timer will push). Hold for wrap-up. + +** Startup + inbox triage + +Ran startup. Clean prior wrap-up (no session-context). 16 stale tasks, 11 roam +inbox items (2 rulesets-related). Two pending inbox handoffs, both shared-asset +change proposals → skeptical review, surfaced to Craig. Craig chose to implement +both now (bash bundle filed separately) and answered the three design questions +with all recommendations. + +** Items 1-2 + the consolidation pivot to a spec + +Craig set up a visible task queue (TaskCreate #1-#7) and worked items 1, 2, 3 in +order. Item 1 (inbox-zero empty-entry sweep): added a Phase B "empty" bucket + +Phase D removal so aborted/blank roam headings get swept every triage; committed +3da2725. Item 2 (agent-source): thread 3 (.emacs.d multi-LLM ai-term note) sent; +claude-rules/ neutralized (7 agent-as-actor "Claude" → "the agent" edits, commits.md +correctly untouched) committed 6ad0442; the 45-file workflow-neutralization sweep +PARKED. + +Mid-flow Craig dropped a roam item — "consolidate inbox workflows, there's too +many" + how to schedule inbox checks. Routing a 45-file neutrality sweep over +workflows about to be consolidated would be wasted, so parked #5 behind a new +consolidation task #7, routed the roam item to todo [#B], and mapped the inbox +landscape (3 overloaded surfaces: local inbox/ dir → process-inbox+monitor-inbox; +roam → inbox-zero; external → triage-intake). Proposed 3→1 engine; Craig chose to +SPEC it. + +Ran the spec trio: spec-create authored docs/inbox-workflow-consolidation-spec.org +(Option A: one inbox.org engine, process/monitor/roam modes, shared core); +Craig resolved all 4 decisions and specified "auto inbox zero" (interactive /loop, +ask-interval, acknowledge-only-on-empty, find → summarize → file → displayed queue +→ ask-to-execute, cross-cycle dedup). Codex reviewed (2 findings, 1 blocking). +spec-response folded both: finding 1 (unattended behavior unspecified) accepted via +narrow — v1 = interactive auto inbox zero only, fully-unattended /schedule cron pass +deferred to vNext [#D] with its open contract questions; finding 2 (checker doesn't +validate workflow links) accepted — added an explicit stale-reference grep as an +acceptance item, dropped the over-claim. Spec Status → Ready, Decisions [4/4], +Findings [2/2]. The build (Phase 1 author inbox.org) is the next session's work. diff --git a/docs/design/2026-06-23-wrap-teardown-shutdown-proposal.org b/docs/design/2026-06-23-wrap-teardown-shutdown-proposal.org new file mode 100644 index 0000000..a47aa2d --- /dev/null +++ b/docs/design/2026-06-23-wrap-teardown-shutdown-proposal.org @@ -0,0 +1,124 @@ +#+TITLE: Proposal — wrap-it-up teardown + "wrap it up and shutdown" variant + +* Source + +Raised by Craig in a home-project session, 2026-06-23, after talking the +design through. Two related additions to =wrap-it-up.org=. Both touch the +Claude-session lifecycle (workflow + hook + the =ai-term= buffer/tmux pair), +so they're rulesets — with one companion piece that has to live in +=.emacs.d/modules/ai-term.el= (flagged below). Originally floated as an +archsetup task; archsetup owns the Hyprland/waybar layer, not the +Claude-session lifecycle, so it was re-routed here. + +* Architecture this depends on (so the design is grounded) + +- =ai-term.el= (=.emacs.d=) is the in-Emacs launcher: a vertical-split vterm + buffer running a tmux session named =aiv-<project-basename>= (prefix + =aiv-=). Layering: =claude= process → tmux session =aiv-<proj>= → Emacs + vterm buffer. +- Killing the tmux session takes the =claude= process with it, so "quit + Claude Code" is a *consequence* of killing =aiv-<proj>=, not a separate + step. +- Hooks already exist under =~/.claude/hooks/= (e.g. =session-clear-resume.sh=, + =precompact-priorities.sh=) — the teardown trigger fits that pattern. +- =sudo= is =NOPASSWD: ALL= on Craig's machines, so =sudo shutdown now= runs + unattended. + +* Item 1 — wrap-up also removes the buffer, quits Claude, removes the tmux session + +Recommend: yes, with one structural rule — the wrap-up runs *inside* the +things it tears down, so teardown is self-terminating and must be the last, +decoupled action, or the valediction may not flush before the session dies. + +Design: +1. *Teardown lives in =ai-term.el=* (companion, see below): one function + =cj/ai-term-quit= that kills the =aiv-<proj>= tmux session (takes =claude= + with it), kills the vterm buffer, and restores the saved window geometry — + =ai-term.el= already owns the buffer↔session pair and the geometry logic. +2. *Trigger from a Stop / SessionEnd hook, not inline.* Wrap-up does all its + git/archive work, delivers the valediction, then drops a sentinel (flag + file, e.g. =/tmp/ai-wrap-teardown-<session>=). The hook fires when Claude + finishes, sees the sentinel, and runs =cj/ai-term-quit= via =emacsclient=. + Decoupling guarantees the valediction lands before the session dies. +3. *Gate on commit+push verified* — never tear down before the session record + is pushed (wrap-up's existing Step 4 / validation checklist already + enforces push; teardown is strictly after it). +4. *Phrase split — teardown IS the default* (Craig's decision 2026-06-23). + Bare "wrap it up" does the full wrap AND removes the buffer/session/quits — + that's his typical case. The non-destructive variant gets the explicit + qualifier: "wrap it up with summary" summarizes + commits + pushes + + archives but keeps the buffer (no teardown), so the summary stays readable. + So: "wrap it up" → teardown; "wrap it up with summary" → no teardown; + "wrap it up and shutdown" → wrap + poweroff (supersedes teardown, Item 2). + +* Item 2 — "wrap it up and shutdown": 10-count then =sudo shutdown now= + +Recommend: yes, but the safety gate is load-bearing and the countdown has a +rendering gotcha. + +Design: +1. *"Only ai-term left" = hard blocking precondition*, evaluated BEFORE the + countdown. Count live sessions (=tmux ls | grep '^aiv-'= or + =pgrep -fc claude=). If more than this one is alive, ABORT the shutdown, + list what's running, and fall back to a normal wrap. Never power the box + off out from under another active Claude session. This is the most + important part of the item. +2. *The live countdown can't run through Claude's tool output.* The Bash tool + buffers stdout until the command returns, so a =for i in $(seq 10 -1 1); + sleep 1= prints all ten at once at the end, not one per second. It has to + run detached or in Emacs: + - tty writer: =for i in $(seq 10 -1 1); do printf '\rShutting down in %2d…' + "$i" > /dev/tty; sleep 1; done; sudo shutdown now= (backgrounded), or + - an Emacs =run-at-time= timer printing 10→1 in the echo area, then + =(shell-command "sudo shutdown now")=. +3. *Make it abort-able* (Ctrl-C / keypress cancels). A 10-second countdown's + whole purpose is a last-chance window; a non-cancellable one is just a + delay. +4. *Sequencing.* "...and shutdown" supersedes Item 1's teardown — if the box + is powering off, killing the buffer/session first is moot. Wrap (commit + + push + archive) → session-count gate → countdown → =shutdown=. + +Packaging: a small rulesets bin script (e.g. =ai-wrap-shutdown=) doing the +gate → abort-able countdown → shutdown, invoked by the workflow after the wrap +commit/push. Countdown either in that script (tty) or handed to Emacs. + +* Companion — required change in =.emacs.d/modules/ai-term.el= + +Item 1's teardown function =cj/ai-term-quit= must live in =ai-term.el= (it +owns =aiv-<proj>= session naming, the vterm buffer, and geometry restore). +rulesets owns the workflow + hook + bin script that *call* it; =.emacs.d= owns +the function itself. Spec for the =.emacs.d= side: + +- =cj/ai-term-quit (&optional project)= — resolve the =aiv-<basename>= session + for the current/!named project, =tmux kill-session= it, =kill-buffer= the + associated vterm buffer, restore saved geometry. Idempotent / no-op if the + session or buffer is already gone. Callable from =emacsclient -e= so the + Stop hook can invoke it headlessly. +- (Optional) a count helper =cj/ai-term-live-count= so the Item-2 gate can ask + Emacs how many ai-term sessions are live, as an alternative to =tmux ls= / + =pgrep=. + +When rulesets builds the workflow/hook side, route this companion to +=.emacs.d= (inbox-send) so the two land together. + +* Open decisions for Craig + +- Phrase set: DECIDED (2026-06-23) — "wrap it up" tears down (default); + "wrap it up with summary" wraps without teardown; "wrap it up and shutdown" + is the poweroff variant. Remaining nuance: confirm the exact non-destructive + qualifier wording is "with summary" (vs e.g. "and summarize"). +- Countdown home: tty-writer bin script vs Emacs timer. (Emacs timer reads + cleaner inside the vterm and is trivially abort-able.) +- Session-count mechanism for the gate: =tmux ls=, =pgrep claude=, or + =cj/ai-term-live-count=. + +* Verify + +- Item 1: bare "wrap it up" → valediction renders fully, THEN buffer + + =aiv-<proj>= session + claude all gone, geometry restored; "wrap it up with + summary" → wrap completes but the buffer stays intact (no teardown). +- Item 2 gate: with a second =aiv-*= session alive, "wrap it up and shutdown" + refuses, lists the other session, and does a normal wrap (no poweroff). +- Item 2 happy path: sole session → 10→1 renders one-per-second, is + cancellable, then =shutdown= fires. +- Teardown never runs before commit+push is verified. diff --git a/docs/inbox-workflow-consolidation-spec.org b/docs/inbox-workflow-consolidation-spec.org new file mode 100644 index 0000000..2e158b6 --- /dev/null +++ b/docs/inbox-workflow-consolidation-spec.org @@ -0,0 +1,192 @@ +#+TITLE: Inbox Workflow Consolidation — Spec +#+AUTHOR: Craig Jennings & Claude +#+DATE: 2026-06-23 +#+TODO: TODO | DONE SUPERSEDED CANCELLED + +* Metadata +| Status | Ready — review incorporated (Codex, 2026-06-23) | +|----------+-------------------------------------------------------------| +| Owner | Craig | +|----------+-------------------------------------------------------------| +| Reviewer | Craig | +|----------+-------------------------------------------------------------| +| Related | [[file:../todo.org][Consolidate inbox/triage workflows + scheduled inbox check]] | +|----------+-------------------------------------------------------------| + +* Summary + +Four inbox-named workflows (=inbox-zero=, =process-inbox=, =monitor-inbox=, plus the startup/wrap-up nudges) circle the same disposition logic across three different surfaces. This spec consolidates them into one =inbox= engine with explicit modes, keeps =triage-intake= (external accounts) and =no-approvals= (session mode) separate, and adds an interactive recurring roam check (=auto inbox zero=). The fully-unattended cron pass is named but deferred to vNext. + +* Problem / Context + +"Too many inbox related workflows" (Craig, roam capture 2026-06-23). The word "inbox" is overloaded onto three genuinely different surfaces, and the workflows that serve them have grown to circle the same logic: + +- *Project-local =inbox/= dir* (handoffs from other projects/scripts/Craig) → =process-inbox.org= owns the value gate and disposition; =monitor-inbox.org= is a thin cadence layer on top of it ("loop process-inbox every 15 min + act-vs-file + reply discipline"). +- *Global roam inbox* (=~/org/roam/inbox.org=, GTD capture) → =inbox-zero.org=, whose Phase A already *calls* =process-inbox= for the local dir before doing the roam-routing part. +- *External accounts* (email / calendar / PRs) → =triage-intake.org= + six source plugins. + +So a reader (or a non-Claude agent) facing "deal with my inbox" has to know which of four files to invoke, and the shared concepts — the three-question value gate, the skeptical review, the implement/fold/file/defer/reject disposition, the reply-to-sender discipline, the capture-guard before a roam write, the priority-scheme check before filing — are spread across and cross-referenced between them. The duplication is real (=monitor-inbox= and =inbox-zero= both lean on =process-inbox='s machinery) and the count is the symptom Craig named. + +A second gap surfaced in the same capture: there's no documented way to run a *recurring* inbox check, and Craig wants a keyword trigger for it. v1 answers this with an interactive in-session loop (=auto inbox zero=); a fully-unattended cron pass that fires while Craig is away is a larger contract (mutation safety, surfacing-when-away, cross-run state) and is deferred. + +* Goals and Non-Goals + +** Goals +- One engine is the single entry point for the inbox surfaces, with the shared value-gate / disposition / reply / capture-guard / priority-scheme logic living in exactly one place. +- Mode selection is unambiguous from the trigger phrase and the caller (startup, wrap-up, on-demand). +- Every existing trigger phrase still works, routing to the right mode — no relearning. +- A documented interactive recurring check (=auto inbox zero=, =/loop=-based). The fully-unattended cron pass (=/schedule=) is vNext, not v1. +- INDEX.org, protocols.org, and the startup/wrap-up callers reconciled to the new shape with no dangling references. +- No behavior regression: the value gate, disposition rules, capture-guard, and reply discipline behave exactly as today. + +** Non-Goals +- *Not* merging =triage-intake.org=. External-account triage ("what's new across my email/cal/PRs") is a different domain from "my inbox dirs"; keeping it distinct is correct, not redundancy. +- *Not* merging =no-approvals.org=. It's a session mode, not an inbox workflow (it's referenced by the monitor cadence, not part of it). +- *Not* changing value-gate semantics or disposition rules. This is a structural merge, behavior-preserving. +- *Not* the domain-aware whole-roam-inbox routing (still deferred, unchanged). +- *Not* the agent-neutral language sweep over these files — that is the parked half of the agent-source task and runs *after* this merge, over fewer files. +- *Not* renaming =CLAUDE.md=, =.claude/=, or other structural paths. + +** Scope tiers +- v1: merge =process-inbox= + =monitor-inbox= + =inbox-zero= into one =inbox.org= engine with =process= / =monitor= / =roam= modes; preserve all trigger phrases; reconcile INDEX + protocols + startup + wrap-up; add the interactive =auto inbox zero= recurring check (=/loop=). +- Out of scope: =triage-intake= merge, =no-approvals= merge, domain-aware roam routing, the agent-neutrality sweep. +- vNext (log to todo.org): the fully-unattended =/schedule= cron pass — needs its own contract (read-only vs may-mutate =todo.org= / =~/org/roam/inbox.org=, how a find surfaces when Craig is away, how dedup state survives across runs, auth/session constraints); a later umbrella unifying =triage-intake='s "what's new" with the inbox engine; the agent-neutrality pass over the consolidated =inbox.org=. + +* Design + +The consolidation produces one engine file, =inbox.org=, structured as a shared core plus three thin modes. The core holds every concept that today is duplicated or cross-referenced: the three-question value gate, the skeptical review (with the cross-project battery for shared-asset proposals), the disposition ladder (implement-now / fold / file / defer / reject-by-source / park), the reply-to-sender discipline, the capture-guard before any roam-inbox disk write, and the priority-scheme check before filing. A mode is a short front section that says which surface it reads, how it enters and exits, and which core steps it runs. + +*Two altitudes.* + +For the *user*: the trigger phrase picks the mode, and the phrases are unchanged. "process inbox" / "handle the inbox" → process mode (the local =inbox/= dir). "monitor the inbox" / "watch the inbox" → monitor mode (process mode on a loop, with the act-vs-file and reply discipline and the clean-tree/green-suite gates). "inbox zero" / "process the roam inbox" → roam mode (route the global roam inbox by =<project>:= prefix, sweep empties, capture-guard the write). Startup calls process mode for the local dir and the read-only roam nudge; wrap-up calls process mode then the roam sweep. + +For the *implementer*: =inbox.org= is one file. The core sections are written once. Each mode is a section that references core steps by name rather than restating them ("run the value gate (core §X) on each item", "guard and reconcile the roam write (core §Y)"). The old three files are deleted; their content is absorbed, not copied. The =triage-intake= engine and its plugins are untouched and keep their own namespace. + +*Routing and callers.* protocols.org's terminology section and the startup workflow's INDEX-driven routing both key off trigger phrases, so the phrase→mode map is the contract. Each caller that today names =process-inbox.org= / =monitor-inbox.org= / =inbox-zero.org= (startup Phase C, wrap-up Step 3, protocols, INDEX) is repointed at =inbox.org= and the relevant mode. INDEX gets one entry for =inbox.org= listing every trigger phrase, grouped by mode. + +*Auto inbox zero (the scheduled mode).* The trigger phrase =auto inbox zero= starts a recurring roam-mode pass. On invocation the engine *asks Craig for the interval* (e.g. 30 min, 2 hours), then drives the loop with =/loop <interval>= running roam mode. It's in-session and interactive by design — each cycle reports, and a find waits for Craig's go before any work happens. Per cycle: + +- *Nothing found* → no inbox summary. A single acknowledgement line: ran at =HH:MM=, nothing found. Nothing else. +- *Items found* → summarize the found items, file them as tasks, and *append them to a displayed queue* (the harness task list, =TaskCreate=) so the queue accumulates across cycles. Then ask: "run this batch next?" If Craig says yes, the engine launches into implementing the found items (each through the normal disposition + verify flow); if no, they stay queued for a later go. Subsequent cycles add only newly-found items to the same displayed queue, never re-surfacing what's already there. + +The acknowledge-only-on-empty rule keeps a quiet inbox quiet — no noise when there's nothing to do — while a find is always surfaced and gated on Craig's yes. =auto inbox zero= is the interactive =/loop= shape because its execute step waits for a yes, so it is inherently in-session. + +A fully-unattended =/schedule= cron pass (firing while Craig is away) is a different contract and is *vNext, not v1*: it can't wait for a yes, so it has to decide up front whether it may mutate =todo.org= and the roam inbox or stays read-only, how a find reaches Craig asynchronously, how dedup state persists between runs that don't share a session, and what session/auth context a cron run carries. v1 ships only the interactive loop; the unattended contract is logged to =todo.org= for its own design pass. + +* Alternatives Considered + +** Option A — One engine with modes (chosen) +- Good, because it cuts four inbox-named files to one and puts the shared logic in a single authoritative place, which is exactly the "too many" complaint. +- Good, because every trigger phrase can re-home to a mode with no user relearning. +- Bad, because =inbox.org= becomes a larger file with internal mode branching. +- Neutral, because =triage-intake= and =no-approvals= stay separate either way. + +** Option B — Keep three files, extract a shared include +- Good, because the diffs are smaller and the per-surface entry points stay familiar. +- Bad, because it does not reduce the file count — Craig's actual complaint is the number of files, and this keeps three plus adds an include. +- Neutral, because the dedup of logic happens, just without the count reduction. + +** Option C — Merge only process-inbox + monitor-inbox, leave inbox-zero +- Good, because it fixes the tightest, least-ambiguous redundancy (monitor is literally a loop over process) at the lowest risk. +- Bad, because roam vs local stays two files; the consolidation is partial (4→3, not 4→2). +- Neutral, because it could be a first phase of Option A rather than a competing end state. + +** Option D — Do nothing, just document which file is which +- Good, because zero risk to load-bearing synced workflows. +- Bad, because it doesn't reduce the count at all; the complaint stands. + +* Decisions [4/4] + +** DONE Engine shape — one file with modes vs partial merge +- Context: Option A (one =inbox.org=, 4→1) maximally addresses "too many" but is the biggest single change to load-bearing synced files. Option C (merge the process/monitor pair only, 4→3) is lower-risk and could be A's first phase. +- Decision: We will build Option A — one =inbox.org= engine with =process= / =monitor= / =roam= modes. (Craig, 2026-06-23.) +- Consequences: easier discovery and one home for the logic; harder single-file size and a bigger, higher-blast-radius diff, mitigated by the shared-core + thin-mode structure and the plugin-namespace escape hatch for a mode that wants depth. + +** DONE Trigger-phrase routing — preserve all existing phrases +- Context: protocols + startup route by phrase; users have these in muscle memory. +- Decision: We will keep every existing trigger phrase, re-homing each to its mode on the one engine, adding only the new =auto inbox zero= phrase. (Craig, 2026-06-23 — accepted as recommended.) +- Consequences: easier — no relearning, no broken muscle memory; harder — the engine must document a longer phrase→mode table and guard against collisions. + +** DONE triage-intake stays separate +- Context: external-account triage is a different surface; folding it in would re-bloat the engine. +- Decision: We will leave =triage-intake.org= and its plugins untouched, out of this consolidation. (Craig, 2026-06-23 — accepted as recommended.) +- Consequences: easier — smaller, coherent inbox engine; harder — two "what's arriving" entry points remain (inbox engine vs triage-intake), documented so the boundary is clear. + +** DONE Scheduled-check mechanism + behavior + keyword +- Context: Craig wants a recurring inbox check with a keyword, an interactive find-then-execute flow, and a running queue. +- Decision: The trigger phrase is =auto inbox zero=. On invocation it asks Craig for the interval, then runs roam-mode on =/loop <interval>=. Empty cycle → one acknowledgement line (ran at HH:MM, nothing found), no inbox summary. Find → summarize, file as tasks, append to the displayed task queue (=TaskCreate=), and ask "run this batch next?"; on yes, implement the found items; subsequent cycles append only new finds to the same queue. =/schedule= stays available for a fully-unattended pass. (Craig, 2026-06-23.) +- Consequences: easier — a quiet inbox stays quiet, a find is always gated on a yes, and the queue is one accumulating view; harder — the loop must dedup against already-queued items so it doesn't re-surface them, and the in-session =/loop= shape means the unattended case still needs =/schedule=. + +* Review findings [2/2] + +** DONE Fully unattended scheduled behavior is not specified :blocking: +Disposition: accepted via the narrow option. v1 ships only the interactive =auto inbox zero= (=/loop=); the fully-unattended =/schedule= pass is deferred to vNext with its open contract questions named (read-only vs may-mutate, surface-when-away, cross-run dedup state, auth/session). Folded into Summary, Goals, Problem/Context, Scope tiers (vNext), and the Design "Auto inbox zero" subsection; the vNext contract is logged to todo.org. This sequences the unattended pass rather than dropping it, preserving Decision 4's intent. +The Summary and Goals promise a scheduled unattended inbox check with trigger keywords, but the concrete =auto inbox zero= design is intentionally interactive: it asks for an interval, runs =/loop <interval>= in the live session, and waits for Craig before executing found work. The only unattended behavior is the sentence that =/schedule= remains available for a fully-unattended cloud-cron pass. That leaves an implementer to invent the actual scheduled contract: trigger phrase(s), whether the pass is read-only or may mutate =todo.org= / =~/org/roam/inbox.org=, how findings are surfaced when Craig is away, how dedup state survives across runs, and what auth/session constraints apply. Add a distinct =/schedule= subsection and acceptance criteria for the fully unattended mode, or narrow the Summary/Goals to say v1 ships only the interactive =/loop= mode and log the unattended cron shape as vNext. (blocking) + +** DONE Stale-reference verification relies on a checker that does not check workflow links +Phase 2 and the Risks section rely on the workflow-integrity / INDEX-drift check as the backstop for missed references to =process-inbox.org=, =monitor-inbox.org=, and =inbox-zero.org=. Current =scripts/workflow-integrity.py= checks INDEX coverage, script references, plugin parentage, orientation sections, and duplicate trigger phrases; it does not validate arbitrary =[[file:...org]]= workflow links or prose references in workflows/protocols/rules. That means a deleted-workflow link in =startup.org=, =wrap-it-up.org=, or =protocols.org= can survive the named checker. Keep the grep requirement, but make it an explicit acceptance item with the exact scope: at minimum =rg 'process-inbox|monitor-inbox|inbox-zero' claude-templates/.ai .ai claude-rules= after caller rewrites, allowing only intentional historical/spec/todo mentions. Optionally extend =workflow-integrity.py= to validate local workflow links, but do not imply it already catches this class. (non-blocking) + +Disposition: accepted. Added the exact grep as an acceptance item and a Phase 2 step, reworded the Risks "missed caller reference" dodge and the Dev-tooling readiness line so the integrity checker is no longer implied to validate workflow links, and noted the optional =workflow-integrity.py= extension as not-required-for-v1. + +* Implementation phases + +** Phase 1 — Author the inbox engine +Write =inbox.org= (canonical =claude-templates/.ai/workflows/=): the shared core (value gate, skeptical review, disposition ladder, reply discipline, capture-guard, priority-scheme check) plus the three mode sections, absorbing the content of the three source files. No caller changes and no deletions yet — the tree still works with the old files in place, the new engine sits alongside for review. + +** Phase 2 — Reconcile callers and retire the old files +Repoint INDEX.org (one =inbox.org= entry, phrases grouped by mode), protocols.org terminology, startup.org Phase C, and wrap-it-up.org Step 3 at =inbox.org= + mode. Delete =process-inbox.org=, =monitor-inbox.org=, =inbox-zero.org=. Grep for stale references — =rg 'process-inbox|monitor-inbox|inbox-zero' claude-templates/.ai .ai claude-rules= — and clear every live caller (the integrity checker covers INDEX coverage and trigger duplication, not workflow links). Run the workflow-integrity / INDEX-drift check. Sync the mirror. + +** Phase 3 — Auto inbox zero + scheduled check +Add the =auto inbox zero= mode to =inbox.org=: ask-for-interval, =/loop <interval>= over roam mode, the empty-cycle acknowledgement, and the find → summarize → file → queue → ask-to-execute flow with cross-cycle dedup against the displayed queue. Document the =/schedule= recipe for the fully-unattended pass alongside it. + +** Phase 4 — Verify +Trigger-phrase coverage (every old phrase resolves to a mode), startup + wrap-up dry-run against the new engine, capture-guard still gates the roam write, INDEX drift clean, mirror in sync. + +* Acceptance criteria +- [ ] Every trigger phrase that today routes to =process-inbox= / =monitor-inbox= / =inbox-zero= resolves to a mode of =inbox.org=. +- [ ] The three old workflow files are deleted and absent from INDEX; the integrity check reports no orphan or stale entry. +- [ ] After caller rewrites, =rg 'process-inbox|monitor-inbox|inbox-zero' claude-templates/.ai .ai claude-rules= returns only intentional historical / spec / todo mentions — no live caller reference to a deleted file. (The integrity checker validates INDEX coverage, not arbitrary workflow links, so this grep is the real backstop.) +- [ ] Startup still processes the local inbox and produces the read-only roam nudge; wrap-up still sweeps the project's roam items. +- [ ] The capture-guard runs before any roam-inbox disk write in the consolidated engine. +- [ ] The value gate, disposition ladder, and reply-to-sender discipline are present once and unchanged in behavior. +- [ ] =auto inbox zero= asks for an interval, then runs roam mode on =/loop <interval>=. +- [ ] An empty auto cycle emits only a timestamped acknowledgement (ran at HH:MM, nothing found) — no inbox summary. +- [ ] A find summarizes the items, files them as tasks, appends them to the displayed queue, and asks before executing; "yes" runs the batch; later cycles append only newly-found items, never re-surfacing queued ones. +- [ ] Canonical and mirror copies are in sync (=sync-check.sh=). + +* Readiness dimensions +- Data model & ownership: the engine reads two files it doesn't own (project =inbox/= dir contents, =~/org/roam/inbox.org=) and writes =todo.org= + the roam file. Ownership unchanged from today; the merge moves no data. +- Errors, empty states & failure: empty inbox → report and stop (preserved per surface); roam pull blocked or dirty → surface and stop, never auto-stash (preserved); live org-capture on the roam file → capture-guard blocks the write (preserved). +- Security & privacy: N/A because no credentials or sensitive data; the engine moves task text between local files. +- Observability: the user sees which mode ran and its disposition summary; INDEX drift check surfaces a mis-wired routing. +- Performance & scale: N/A because the inputs are small text files triaged by hand-scale counts. +- Reuse & lost opportunities: the whole point — the shared core is written once instead of three times; =triage-intake='s plugin pattern is intentionally not reused here (different surface). +- Architecture fit & weak points: integration points are INDEX.org, protocols.org terminology, startup Phase C, wrap-up Step 3. Weak point: a missed caller reference to an old filename breaks routing — mitigated by the explicit stale-reference grep (acceptance item), since the integrity check covers INDEX coverage, not workflow links. +- Config surface: trigger phrases (the phrase→mode table) and the scheduled-check keyword set + cron expression. +- Documentation plan: the engine file is the doc; INDEX entry updated; protocols terminology updated. No separate user doc needed. +- Dev tooling: the workflow-integrity check + startup INDEX-drift check cover INDEX coverage and trigger-phrase duplication; they do *not* validate workflow file links, so the stale-reference grep (acceptance item) is a manual step. Optionally extend =workflow-integrity.py= to validate local =[[file:...org]]= workflow links — not required for v1. +- Rollout, compatibility & rollback: the merge lands via the template sync; =rsync --delete= removes the three retired files from every consuming project on its next startup, and the new =inbox.org= arrives the same pass. Rollback = git revert of the rulesets commit, then the next sync restores the old files. Trigger-phrase preservation is the compatibility guarantee. +- External APIs & deps: N/A because no external API; =/schedule= and =/loop= are harness features, not deps. + +* Risks, Rabbit Holes, and Drawbacks +- *Missed caller reference.* A lingering mention of =process-inbox.org= / =monitor-inbox.org= / =inbox-zero.org= in a workflow, protocol, skill, or the INDEX would break routing after the files are deleted. Dodge: =rg 'process-inbox|monitor-inbox|inbox-zero' claude-templates/.ai .ai claude-rules= and clear every live caller before deleting. The workflow-integrity checker validates INDEX coverage and trigger-phrase duplication, *not* arbitrary =[[file:...]]= links, so the grep — not the checker — is the real backstop here. +- *Single-file sprawl.* One engine with three modes risks becoming the wall-of-text the workflows were split to avoid. Dodge: the shared-core + thin-mode structure and the terseness pass; if a mode wants real depth, it can become a =inbox.<mode>.org= plugin under the engine namespace (the same pattern =triage-intake= uses) rather than bloating the core. +- *Sequencing with the agent-neutrality sweep.* If the neutrality sweep runs first, it edits three files about to be deleted. Dodge: this consolidation lands first by construction (it's why the sweep was parked). + +* Review and iteration history +** 2026-06-23 Tue @ 21:51:51 -0400 — Claude — author +- What: initial draft. +- Why: Craig chose to spec the inbox-workflow consolidation before building a load-bearing 3-to-1 merge of synced workflows. +- Artifacts: docs/inbox-workflow-consolidation-spec.org; todo.org "Consolidate inbox/triage workflows + scheduled inbox check". +** 2026-06-23 Tue @ 22:05:00 -0400 — Craig — decision-maker +- What: resolved all four decisions; added the =auto inbox zero= scheduled mode (ask-for-interval, empty-cycle acknowledgement only, find → summarize → file → displayed queue → ask-to-execute, cross-cycle dedup). Status → ready for review. +- Why: chose Option A (4→1 engine) and specified the recurring-check behavior in full. +- Artifacts: Decisions [4/4]; Design "Auto inbox zero" subsection. +** 2026-06-23 Tue @ 22:15:58 -0400 — Codex — reviewer +- What: spec-review pass rated the spec =Not ready= and added two findings: the fully unattended =/schedule= behavior is not specified, and stale-reference verification leans on a checker that does not validate workflow links. +- Why: the current design is strong enough for the consolidation and interactive =/loop= mode, but the stated scheduled/unattended goal would force implementers to invent behavior before shipping. +- Artifacts: Review findings [0/2]. +** 2026-06-23 Tue @ 22:28:00 -0400 — Claude — responder +- What: both findings accepted and folded. Finding 1 (blocking) resolved by narrowing v1 to the interactive =auto inbox zero= (=/loop=) and deferring the fully-unattended =/schedule= contract to vNext with its open questions named — Summary, Goals, Problem/Context, Scope tiers, and Design updated. Finding 2 resolved by adding the exact stale-reference grep as an acceptance item + Phase 2 step and dropping the over-claim that the integrity checker validates workflow links. Findings [2/2], Decisions [4/4]; scope narrowed (not expanded), so no readiness-rubric rerun needed. Status → Ready. +- Why: the scheduled/unattended promise outran what Craig actually specced (he detailed the interactive loop); sequencing the cron pass to vNext keeps v1 honest. The checker genuinely doesn't catch stale workflow links. +- Artifacts: Decisions [4/4]; Review findings [2/2]; vNext task to be logged for the unattended cron contract. @@ -34,14 +34,35 @@ Tags are assigned and refreshed by =task-audit=; =task-review= keeps them honest * Rulesets Open Work -** DONE [#C] Bash/shell language bundle :feature: -CLOSED: [2026-06-23 Tue] +** TODO [#D] Fully-unattended scheduled inbox check (/schedule cron pass) :feature: :PROPERTIES: :CREATED: [2026-06-23 Tue] :END: -Built =languages/bash/= the same session it was filed: bash.md + bash-testing.md rules, a shellcheck PostToolUse validate hook (covers =.sh=, =.bash=, and extensionless shell scripts by shebang; 8 bats tests), a shellcheck pre-commit githook, settings.json wiring, gitignore-add.txt, and a "Bash/shell project" CLAUDE.md. shfmt left out of the blocking path on purpose (shell has no canonical style). Makefile test target now discovers =languages/*/tests/*.bats=. +vNext from the inbox-consolidation spec. =auto inbox zero= (v1) is the interactive =/loop= recurring check that waits for Craig's yes before executing. A fully-unattended =/schedule= cron pass that fires while Craig is away needs its own contract before it can ship: read-only vs may-mutate =todo.org= / =~/org/roam/inbox.org=, how a find surfaces asynchronously when Craig isn't at the session, how dedup state persists across runs that don't share a session, and what session/auth context a cron run carries. Design it after v1 consolidation lands. From the inbox-consolidation spec-review (Codex finding 1). See [[file:docs/inbox-workflow-consolidation-spec.org][spec]]. -No =languages/= bundle fits a shell-heavy project. archangel (437 =.sh= files) and archsetup are bash projects with nothing that matches; installing elisp/python gives them the wrong language rules. Build a =languages/bash/= bundle on the elisp/go pattern: =claude/rules/bash.md= (style — =set -euo pipefail=, quoting, =[[ ]]=, trap/cleanup) + =bash-testing.md= (bats conventions), a PostToolUse validate hook (=shellcheck= on edited =.sh=), a =githooks/pre-commit= running shellcheck on staged shell files, =settings.json= wiring, =gitignore-add.txt=, and its own =CLAUDE.md= headed "Bash/shell project." Urgency dropped 2026-06-23: install-lang now seeds the language-neutral default CLAUDE.md when a bundle ships none, so a bash project no longer gets a mislabeled "Elisp project" header — the bundle is now the accurate-rules win, not a mislabel fix. From archangel 2026-06-23 ([[file:docs/design/2026-06-23-install-lang-claude-md-gap.org][handoff]]). +** TODO [#B] Consolidate inbox/triage workflows + scheduled inbox check :chore: +:PROPERTIES: +:CREATED: [2026-06-23 Tue] +:END: +Consolidate the four inbox-named workflows (=process-inbox=, =monitor-inbox=, =inbox-zero=, + nudges) into one =inbox.org= engine with process/monitor/roam modes; =triage-intake= and =no-approvals= stay separate. Plus a documented scheduled inbox check (=/schedule= / =/loop=) with keywords. Spec drafted, awaiting Craig's 4 decisions (engine shape A vs C, phrase preservation, triage-intake-separate, scheduled-check keywords): [[file:docs/inbox-workflow-consolidation-spec.org][spec]]. Then spec-review → spec-response → build. Sequenced before the agent-neutrality workflow sweep. From the roam inbox 2026-06-23. + +** TODO [#B] wrap-it-up teardown + "wrap it up and shutdown" :feature: +:PROPERTIES: +:CREATED: [2026-06-23 Tue] +: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. + +** TODO [#C] inbox-zero: delete empty roam entries on triage :feature: +:PROPERTIES: +:CREATED: [2026-06-23 Tue] +:END: +=inbox-zero.org= should drop empty/blank entries from =~/org/roam/inbox.org= whenever it triages the file (a heading with no body and no actionable content is noise). Fold into Phase D's reconcile, after the capture-guard and pull, before/with the claimed-item removal. Shared-asset, review-gated. From the roam inbox 2026-06-23. + +** TODO [#C] Multiple agent-source improvements :spec: +:PROPERTIES: +:CREATED: [2026-06-23 Tue] +: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). ** TODO [#B] Anki deck name from #+TITLE :bug: :PROPERTIES: @@ -2880,3 +2901,11 @@ Touches four synced template workflows and needs a curation pass on the best-pra Drafted [[file:docs/design/2026-06-16-encourage-kb-contribution-spec.org][the KB-contribution spec]]: four light workflow prompts (startup nudge, triage-intake + inbox-zero end-of-flow reminders, an early wrap-up reflection feeding the existing KB receipt) plus one Craig-authored best-practices node curated from Ahrens / Matuschak / org-roam guidance. Five open sub-decisions filed as decisions-as-TODO in the spec. *** 2026-06-20 Sat @ 23:29:10 -0400 Spec ratified + built Craig ratified all five decisions (2026-06-20) and added D6 — a read-side startup consult-nudge surfacing project-relevant KB node titles, the counterpart the original write-only design lacked. Built all of it: the best-practices node (=~/org/roam/agents/20260620232112-agent-kb-best-practices.org=), startup's two Phase C nudges (consult + contribute, gated on the roam clone), the conditional capture reminders in triage-intake + inbox-zero, and the early wrap-up reflection feeding the existing receipt. Commits 76e5559 (workflows + spec) and the related lint checker f6dde4e. Trigger for the build: receipt data showed "promoted 0 / consulted no" across recent sessions. +** DONE [#C] Bash/shell language bundle :feature: +CLOSED: [2026-06-23 Tue] +:PROPERTIES: +:CREATED: [2026-06-23 Tue] +:END: +Built =languages/bash/= the same session it was filed: bash.md + bash-testing.md rules, a shellcheck PostToolUse validate hook (covers =.sh=, =.bash=, and extensionless shell scripts by shebang; 8 bats tests), a shellcheck pre-commit githook, settings.json wiring, gitignore-add.txt, and a "Bash/shell project" CLAUDE.md. shfmt left out of the blocking path on purpose (shell has no canonical style). Makefile test target now discovers =languages/*/tests/*.bats=. + +No =languages/= bundle fits a shell-heavy project. archangel (437 =.sh= files) and archsetup are bash projects with nothing that matches; installing elisp/python gives them the wrong language rules. Build a =languages/bash/= bundle on the elisp/go pattern: =claude/rules/bash.md= (style — =set -euo pipefail=, quoting, =[[ ]]=, trap/cleanup) + =bash-testing.md= (bats conventions), a PostToolUse validate hook (=shellcheck= on edited =.sh=), a =githooks/pre-commit= running shellcheck on staged shell files, =settings.json= wiring, =gitignore-add.txt=, and its own =CLAUDE.md= headed "Bash/shell project." Urgency dropped 2026-06-23: install-lang now seeds the language-neutral default CLAUDE.md when a bundle ships none, so a bash project no longer gets a mislabeled "Elisp project" header — the bundle is now the accurate-rules win, not a mislabel fix. From archangel 2026-06-23 ([[file:docs/design/2026-06-23-install-lang-claude-md-gap.org][handoff]]). |
