From 10ea44b6de3be1872f7f0bd4501ccf3878105bc4 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Thu, 23 Jul 2026 08:41:44 -0500 Subject: feat(workflows): clear temp/ at wrap, plus three rule and template fixes Four proposals from other projects, reviewed and applied together. wrap-it-up gains a Clear temp/ step in Step 3. temp/ is gitignored everywhere, so nothing there survives deletion. The step confirms before removing anything that reads as in-progress rather than throwaway, and skips where temp/ isn't gitignored. This closes the last open clause of the working/temp work from 2026-07-20. interaction.md drops the fenced-code-block carve-out. Chat output carries no markup at all now, fences included. Fences don't invert the way inline spans do, but the carve-out kept pulling them back in. sentry.org folds in four notes from its first two live runs elsewhere. Two are traps for anyone reviewing a night by git log: archive-done touches .gitignore on its first run, and a mirror-only project's quiet fires leave no commits at all. The others make property sweeps sanctioned quiet-fire work and split the task audit into a mechanical hourly subset plus a nightly judgment half. The notes.org template now lints clean, down from four flags in every project on every sweep. Two of those were mechanical, so lint-org --fix anywhere would have rewritten the template and drifted it from canonical. --- .ai/workflows/sentry.org | 8 +- .ai/workflows/wrap-it-up.org | 10 + claude-rules/interaction.md | 2 +- claude-templates/.ai/notes.org | 12 +- claude-templates/.ai/workflows/sentry.org | 8 +- claude-templates/.ai/workflows/wrap-it-up.org | 10 + todo.org | 38 +- .../interaction-no-markup/interaction.md.proposed | 70 --- .../proposal-from-org-drill.org | 5 - working/interaction-no-markup/proposed.diff | 11 - working/notes-template-lint/notes.org.proposed | 86 --- .../notes-template-lint/proposal-from-smoke.org | 5 - working/notes-template-lint/proposed.diff | 38 -- .../feedback-from-archangel.org | 5 - .../feedback-from-takuzu.org | 5 - working/sentry-living-document/proposed.diff | 32 -- working/sentry-living-document/sentry.org.proposed | 219 ------- working/wrapup-temp-cleanup/proposed.diff | 19 - .../wrapup-temp-cleanup/wrap-it-up.org.proposed | 636 --------------------- 19 files changed, 52 insertions(+), 1167 deletions(-) delete mode 100644 working/interaction-no-markup/interaction.md.proposed delete mode 100644 working/interaction-no-markup/proposal-from-org-drill.org delete mode 100644 working/interaction-no-markup/proposed.diff delete mode 100644 working/notes-template-lint/notes.org.proposed delete mode 100644 working/notes-template-lint/proposal-from-smoke.org delete mode 100644 working/notes-template-lint/proposed.diff delete mode 100644 working/sentry-living-document/feedback-from-archangel.org delete mode 100644 working/sentry-living-document/feedback-from-takuzu.org delete mode 100644 working/sentry-living-document/proposed.diff delete mode 100644 working/sentry-living-document/sentry.org.proposed delete mode 100644 working/wrapup-temp-cleanup/proposed.diff delete mode 100644 working/wrapup-temp-cleanup/wrap-it-up.org.proposed diff --git a/.ai/workflows/sentry.org b/.ai/workflows/sentry.org index a502785..62eac12 100644 --- a/.ai/workflows/sentry.org +++ b/.ai/workflows/sentry.org @@ -131,9 +131,9 @@ In order. Each names its detection probe. A pass whose probe fails is one skip l 3. *Triage intake — mail and messenger sources excluded.* Run =triage-intake.org=, loading only its non-mail, non-messenger source plugins (calendar, PR/ticketing). The mail and messenger plugins — cmail, any Gmail variant, Telegram, Signal, chat DMs — are never loaded by a sentry fire: Craig ruled 2026-07-21 that sentry doesn't check email or messengers. A manual "triage intake" still scans everything. Probe: the project has at least one *active* triage source that survives that exclusion — a project-specific plugin (=.ai/project-workflows/triage-intake.*.org=), or a non-empty =:TRIAGE_SOURCES:= declaration naming general plugins that exist. Mere presence of the template-synced general plugins does *not* activate the pass; a project that declares no sources, or whose only declared sources are mail or messengers, probe-skips (see =docs/specs/2026-07-20-triage-source-activation-spec.org=). Destructive actions (deleting, archiving, sending) queue; they never fire unattended. -4. *Todo cleanup* — the =clean-todo.org= mechanics (hygiene pass + =--archive-done= + =--convert-subtasks=). Probe: a root =todo.org=. +4. *Todo cleanup* — the =clean-todo.org= mechanics (hygiene pass + =--archive-done= + =--convert-subtasks=). Probe: a root =todo.org=. Note that =--archive-done= is not purely an org-file pass on its first run in a project: it creates =archive/task-archive.org= and appends a =.gitignore= entry, so it produces a real tracked-file commit and correctly trips the fire-end conditional suite. (archangel, first live run 2026-07-21.) -5. *Task audit* — =task-audit.org=. Probe: a root =todo.org=. Priority regrades and consolidations queue for review; factual staleness fixes that are unambiguous execute. +5. *Task audit* — the *mechanical subset* of =task-audit.org= hourly (staleness counts, structural checks, cookie recomputation); the judgment half (priority regrades, consolidations, merge candidates) runs *once per night* and queues its findings rather than repeating them every fire. Probe: a root =todo.org=. A full audit every hour is too heavy and re-surfaces the same judgment calls all night. (takuzu, first live run 2026-07-21.) Factual staleness fixes that are unambiguous still execute. 6. *Working-files hygiene* — flag =working//= directories whose backing task is closed (a filing candidate per =working-files.md=). Probe: a =working/= directory exists. The filing itself queues (it's a judgment move). @@ -145,7 +145,7 @@ In order. Each names its detection probe. A pass whose probe fails is one skip l 10. *Prep + symlink freshness* — stale daily-prep docs, broken symlinks. Probe: the prep dir / symlinks exist (work and home only, in practice). -11. *Bug finding* — hunt for real bugs in the project's codebase: static analysis (=shellcheck= for shell, the project's own linters for its languages), config sanity checks, plus one targeted code-reading area per fire. Rotate the area across fires and name it in the digest, so coverage accumulates over a night instead of re-reading the same corner. This pass does *not* run the test suite — the entry baseline already ran it, and re-running it hourly is anti-pattern 5; read the entry result instead. Probe: the project carries a codebase — source under version control beyond its org and tooling files. File each verified finding as a graded bug task in =todo.org= per the severity × frequency matrix (=todo-format.md=), deduped against existing tasks; an unverifiable suspicion is a digest line, not a task. Find, never fix unattended — a proposed fix, or any remediation beyond the task filing, queues for morning approval with its exact edit. (Added at Craig's order 2026-07-21, first dogfooded in dotfiles.) +11. *Bug finding* — hunt for real bugs in the project's codebase: static analysis (=shellcheck= for shell, the project's own linters for its languages), config sanity checks, plus one targeted code-reading area per fire. Rotate the area across fires and name it in the digest, so coverage accumulates over a night instead of re-reading the same corner. Randomized property sweeps (generate-and-verify against an engine's own invariants) are good quiet-fire work here, reaching past a frozen test corpus. Expect the pass to go honestly quiet after the first few fires find the standing defects; a quiet bug hunt is a result, not a failure. (takuzu, first live run 2026-07-21: three real fixes in the first four fires, then quiet.) This pass does *not* run the test suite — the entry baseline already ran it, and re-running it hourly is anti-pattern 5; read the entry result instead. Probe: the project carries a codebase — source under version control beyond its org and tooling files. File each verified finding as a graded bug task in =todo.org= per the severity × frequency matrix (=todo-format.md=), deduped against existing tasks; an unverifiable suspicion is a digest line, not a task. Find, never fix unattended — a proposed fix, or any remediation beyond the task filing, queues for morning approval with its exact edit. (Added at Craig's order 2026-07-21, first dogfooded in dotfiles.) (KB lesson promotion — the pass the original proposal listed eleventh — is deferred to vNext. An unattended judgment pass writing to the shared knowledge base waits until sentry has quiet weeks behind it and a designed detection heuristic. See the filed lesson-detection-heuristic task.) @@ -178,6 +178,8 @@ Sentry never merges its own branch. In the morning Craig: A bad night is discarded by deleting one branch — nothing reached main, nothing was pushed. +In a project that gitignores =.ai/=, the whole spine is untracked, so quiet fires produce no commits at all and =git log main..sentry/-= understates the night's activity. There the anchor's heartbeat list is the only record of what fired. Read the anchor, not just the log. (archangel, first live run 2026-07-21.) + * Stop Sentry Trigger: "stop sentry" (and synonyms above). Sentry owns its own shutdown: diff --git a/.ai/workflows/wrap-it-up.org b/.ai/workflows/wrap-it-up.org index 41f2e54..103e225 100644 --- a/.ai/workflows/wrap-it-up.org +++ b/.ai/workflows/wrap-it-up.org @@ -189,6 +189,16 @@ Preview the moves without writing: emacs --batch -q -l .ai/scripts/todo-cleanup.el --archive-done --check todo.org #+end_src +*** Clear temp/ + +#+begin_src bash +[ -d temp ] && find temp -mindepth 1 -delete && echo "temp/ cleared" +#+end_src + +=temp/= holds throwaway artifacts — discarded prototypes, scratch output, intermediate data (see =working-files.md=). It's gitignored in every project, so nothing here rides a commit and nothing is recoverable from git once deleted. Clearing it at wrap is what keeps ephemeral work from silting up across sessions, and it's the counterpart to =working/=, which is tracked and *never* cleared here. + +Two guards. Confirm before deleting if =temp/= holds anything a reasonable reader would call in-progress rather than throwaway — misfiled work belongs in =working/=, so move it there instead of deleting it. And skip the step entirely in a project where =temp/= is not gitignored, since that means the project is using the directory for something else. + *** Sync child priorities #+begin_src bash diff --git a/claude-rules/interaction.md b/claude-rules/interaction.md index dfd73bf..caa4d49 100644 --- a/claude-rules/interaction.md +++ b/claude-rules/interaction.md @@ -54,7 +54,7 @@ In conversational output to the user, do not use Markdown bold (`**...**`) or in - Write command names, file paths, key chords, and code identifiers as plain text — `pearl-save-issue` becomes pearl-save-issue, `C-; L s s` becomes C-; L s s. - Use structure that doesn't invert colors: headers, numbered lists, dashes, parentheses, and double quotes for labels are all fine. -- Fenced code blocks (triple backtick) are acceptable when the user explicitly wants a block to copy — they don't invert the way inline spans do. Default to plain text otherwise. +- Fenced code blocks (triple backtick) are not an exception. Craig's direction is zero markup in chat output, always: "always always list it out without markup" (2026-05-30). Fences don't invert the way inline spans do, but they still read as markup he didn't ask for, and the carve-out kept reintroducing them. When he needs something to copy, give it as plain indented text, or write it to a file and name the path. This governs **chat output**, not the Markdown source of rule files, specs, or docs the user reads in an editor — those keep normal Markdown formatting. The constraint is the terminal rendering of the live conversation. diff --git a/claude-templates/.ai/notes.org b/claude-templates/.ai/notes.org index c16863b..311a86f 100644 --- a/claude-templates/.ai/notes.org +++ b/claude-templates/.ai/notes.org @@ -6,19 +6,19 @@ This file contains project-specific information for this project. -**When to read this:** +- When to read this: - At the start of EVERY session (after reading protocols.org) - When needing project context or history - When checking reminders or pending decisions -**What's in this file:** +- What's in this file: - Project-specific context and goals - Pending decisions - Active reminders -**Session history is NOT in this file.** Each session's record lives in =.ai/sessions/YYYY-MM-DD-HH-MM-description.org= — one file per session. Catch-up reads the Summary sections of the most recent 5. +- Session history is NOT in this file. Each session's record lives in =.ai/sessions/YYYY-MM-DD-HH-MM-description.org= — one file per session. Catch-up reads the Summary sections of the most recent 5. -**For protocols and conventions, see:** [[file:protocols.org][protocols.org]] +- For protocols and conventions, see [[file:protocols.org][protocols.org]]. * Project-Specific Context @@ -48,9 +48,9 @@ This section tracks decisions that need Craig's input before work can proceed. - Include: What needs to be decided, options available, why it matters - Remove decisions once resolved (the resolution is captured in the Session Log of the session where it was resolved) -**Example format:** +- Example format: #+begin_example -** Feature Name or Topic +,** Feature Name or Topic Craig needs to decide on [specific question]. diff --git a/claude-templates/.ai/workflows/sentry.org b/claude-templates/.ai/workflows/sentry.org index a502785..62eac12 100644 --- a/claude-templates/.ai/workflows/sentry.org +++ b/claude-templates/.ai/workflows/sentry.org @@ -131,9 +131,9 @@ In order. Each names its detection probe. A pass whose probe fails is one skip l 3. *Triage intake — mail and messenger sources excluded.* Run =triage-intake.org=, loading only its non-mail, non-messenger source plugins (calendar, PR/ticketing). The mail and messenger plugins — cmail, any Gmail variant, Telegram, Signal, chat DMs — are never loaded by a sentry fire: Craig ruled 2026-07-21 that sentry doesn't check email or messengers. A manual "triage intake" still scans everything. Probe: the project has at least one *active* triage source that survives that exclusion — a project-specific plugin (=.ai/project-workflows/triage-intake.*.org=), or a non-empty =:TRIAGE_SOURCES:= declaration naming general plugins that exist. Mere presence of the template-synced general plugins does *not* activate the pass; a project that declares no sources, or whose only declared sources are mail or messengers, probe-skips (see =docs/specs/2026-07-20-triage-source-activation-spec.org=). Destructive actions (deleting, archiving, sending) queue; they never fire unattended. -4. *Todo cleanup* — the =clean-todo.org= mechanics (hygiene pass + =--archive-done= + =--convert-subtasks=). Probe: a root =todo.org=. +4. *Todo cleanup* — the =clean-todo.org= mechanics (hygiene pass + =--archive-done= + =--convert-subtasks=). Probe: a root =todo.org=. Note that =--archive-done= is not purely an org-file pass on its first run in a project: it creates =archive/task-archive.org= and appends a =.gitignore= entry, so it produces a real tracked-file commit and correctly trips the fire-end conditional suite. (archangel, first live run 2026-07-21.) -5. *Task audit* — =task-audit.org=. Probe: a root =todo.org=. Priority regrades and consolidations queue for review; factual staleness fixes that are unambiguous execute. +5. *Task audit* — the *mechanical subset* of =task-audit.org= hourly (staleness counts, structural checks, cookie recomputation); the judgment half (priority regrades, consolidations, merge candidates) runs *once per night* and queues its findings rather than repeating them every fire. Probe: a root =todo.org=. A full audit every hour is too heavy and re-surfaces the same judgment calls all night. (takuzu, first live run 2026-07-21.) Factual staleness fixes that are unambiguous still execute. 6. *Working-files hygiene* — flag =working//= directories whose backing task is closed (a filing candidate per =working-files.md=). Probe: a =working/= directory exists. The filing itself queues (it's a judgment move). @@ -145,7 +145,7 @@ In order. Each names its detection probe. A pass whose probe fails is one skip l 10. *Prep + symlink freshness* — stale daily-prep docs, broken symlinks. Probe: the prep dir / symlinks exist (work and home only, in practice). -11. *Bug finding* — hunt for real bugs in the project's codebase: static analysis (=shellcheck= for shell, the project's own linters for its languages), config sanity checks, plus one targeted code-reading area per fire. Rotate the area across fires and name it in the digest, so coverage accumulates over a night instead of re-reading the same corner. This pass does *not* run the test suite — the entry baseline already ran it, and re-running it hourly is anti-pattern 5; read the entry result instead. Probe: the project carries a codebase — source under version control beyond its org and tooling files. File each verified finding as a graded bug task in =todo.org= per the severity × frequency matrix (=todo-format.md=), deduped against existing tasks; an unverifiable suspicion is a digest line, not a task. Find, never fix unattended — a proposed fix, or any remediation beyond the task filing, queues for morning approval with its exact edit. (Added at Craig's order 2026-07-21, first dogfooded in dotfiles.) +11. *Bug finding* — hunt for real bugs in the project's codebase: static analysis (=shellcheck= for shell, the project's own linters for its languages), config sanity checks, plus one targeted code-reading area per fire. Rotate the area across fires and name it in the digest, so coverage accumulates over a night instead of re-reading the same corner. Randomized property sweeps (generate-and-verify against an engine's own invariants) are good quiet-fire work here, reaching past a frozen test corpus. Expect the pass to go honestly quiet after the first few fires find the standing defects; a quiet bug hunt is a result, not a failure. (takuzu, first live run 2026-07-21: three real fixes in the first four fires, then quiet.) This pass does *not* run the test suite — the entry baseline already ran it, and re-running it hourly is anti-pattern 5; read the entry result instead. Probe: the project carries a codebase — source under version control beyond its org and tooling files. File each verified finding as a graded bug task in =todo.org= per the severity × frequency matrix (=todo-format.md=), deduped against existing tasks; an unverifiable suspicion is a digest line, not a task. Find, never fix unattended — a proposed fix, or any remediation beyond the task filing, queues for morning approval with its exact edit. (Added at Craig's order 2026-07-21, first dogfooded in dotfiles.) (KB lesson promotion — the pass the original proposal listed eleventh — is deferred to vNext. An unattended judgment pass writing to the shared knowledge base waits until sentry has quiet weeks behind it and a designed detection heuristic. See the filed lesson-detection-heuristic task.) @@ -178,6 +178,8 @@ Sentry never merges its own branch. In the morning Craig: A bad night is discarded by deleting one branch — nothing reached main, nothing was pushed. +In a project that gitignores =.ai/=, the whole spine is untracked, so quiet fires produce no commits at all and =git log main..sentry/-= understates the night's activity. There the anchor's heartbeat list is the only record of what fired. Read the anchor, not just the log. (archangel, first live run 2026-07-21.) + * Stop Sentry Trigger: "stop sentry" (and synonyms above). Sentry owns its own shutdown: diff --git a/claude-templates/.ai/workflows/wrap-it-up.org b/claude-templates/.ai/workflows/wrap-it-up.org index 41f2e54..103e225 100644 --- a/claude-templates/.ai/workflows/wrap-it-up.org +++ b/claude-templates/.ai/workflows/wrap-it-up.org @@ -189,6 +189,16 @@ Preview the moves without writing: emacs --batch -q -l .ai/scripts/todo-cleanup.el --archive-done --check todo.org #+end_src +*** Clear temp/ + +#+begin_src bash +[ -d temp ] && find temp -mindepth 1 -delete && echo "temp/ cleared" +#+end_src + +=temp/= holds throwaway artifacts — discarded prototypes, scratch output, intermediate data (see =working-files.md=). It's gitignored in every project, so nothing here rides a commit and nothing is recoverable from git once deleted. Clearing it at wrap is what keeps ephemeral work from silting up across sessions, and it's the counterpart to =working/=, which is tracked and *never* cleared here. + +Two guards. Confirm before deleting if =temp/= holds anything a reasonable reader would call in-progress rather than throwaway — misfiled work belongs in =working/=, so move it there instead of deleting it. And skip the step entirely in a project where =temp/= is not gitignored, since that means the project is using the directory for something else. + *** Sync child priorities #+begin_src bash diff --git a/todo.org b/todo.org index 2b299c9..2e613b9 100644 --- a/todo.org +++ b/todo.org @@ -60,29 +60,21 @@ Low impact, stated honestly: git chdirs to the working-tree root before running Grading: Minor severity (belt-and-braces guard, no silent-pass path found — the checks run regardless) x rare edge case (needs a git configuration that makes toplevel differ from the hook's cwd) = P4... graded [#C] rather than [#D] because it's a two-character fix in a security-relevant gate and the drift pattern is the real signal: a fix landed in one bundle copy and stopped there, which is the same shape as the [#A] above. -** VERIFY [#B] Parked: zero markup in chat output, fences included (from org-drill) -What arrived: interaction.md's no-reverse-video rule carves out fenced code blocks as acceptable "when the user explicitly wants a block to copy"; org-drill relays your 2026-05-30 direction that chat output carries no markup at all, fences included. -Recommendation: accept as-is. The carve-out contradicts the direction, and the rule is where it belongs so every project inherits it instead of it living as one project's memory. I kept the rule's factual claim honest — fences genuinely don't invert the way inline spans do, so the new text says they read as markup you didn't ask for rather than inventing a rendering problem. Checked: this is the only copy of the carve-out. -Prepared diff: [[file:working/interaction-no-markup/proposed.diff]] — apply is mechanical on your go. -Say "approve the parked no-markup rule" (or adjust / reject) and it gets applied. - -** VERIFY [#B] Parked: sentry Living Document updates from two dogfood runs (from takuzu + archangel) -What arrived: both projects ran sentry live overnight 2026-07-20/21 and reported back — takuzu 8 fires, archangel 7. No engine defects in either. -Recommendation: accept with changes. Four of the five findings fold in cleanly: the archive-done .gitignore note, the mirror-only "git log understates the night" note, property sweeps as quiet-fire work, and splitting the task audit into a mechanical hourly subset plus a nightly judgment half. The fifth (make the bug hunt an official pass) already landed tonight as pass 11, so it reduces to recording their corroboration. Takuzu's lint-org finding I handled separately as a filed bug task rather than a workflow edit, since it's a defect not a design change. -Prepared diff: [[file:working/sentry-living-document/proposed.diff]] — apply is mechanical on your go. -Say "approve the parked sentry updates" (or adjust / reject) and it gets applied. - -** VERIFY [#B] Parked: clear four lint flags in the notes.org template (from smoke) -What arrived: the synced notes.org template trips misplaced-heading twice on column-0 bold lines and invalid-block twice around the Pending Decisions example, so every project inherits the noise on every sweep. -Recommendation: accept as-is, and I found two more instances smoke didn't list plus the actual cause of the block flags (the example contains a literal =** Feature Name= line that org reads as a heading, breaking block tracking). Verified: the proposed file lints with zero mechanical and zero heading/block findings. Worth your attention separately — those two flags are mechanical, not judgment, so =lint-org --fix= run in any project would rewrite the template and drift it from canonical. -Prepared diff: [[file:working/notes-template-lint/proposed.diff]] — apply is mechanical on your go. -Say "approve the parked template fix" (or adjust / reject) and it gets applied. - -** VERIFY [#B] Parked: clear temp/ during wrap-up teardown (from your roam capture) -What arrived: your roam inbox item asked that every project carry working/ and temp/, and that temp/ be "deleted as a part of the wrap up sequence". The rest shipped 2026-07-20; wrap-it-up.org never got the temp/ clause and has no mention of the directory. -Recommendation: accept as-is. Added as a Step 3 sub-step after the archive pass, with two guards — confirm before deleting anything that reads as in-progress rather than throwaway (that belongs in working/, so move it), and skip in a project where temp/ isn't gitignored. It parks rather than lands because it's both a shared-asset edit and a destructive one. -Prepared diff: [[file:working/wrapup-temp-cleanup/proposed.diff]] — apply is mechanical on your go. -Say "approve the parked temp cleanup" (or adjust / reject) and it gets applied. +** DONE [#B] Parked: zero markup in chat output, fences included (from org-drill) +CLOSED: [2026-07-23 Thu] +Craig approved 2026-07-23. Applied to =claude-rules/interaction.md=: the fenced-code-block carve-out is gone, replaced with a zero-markup-always statement citing his 2026-05-30 direction. The rule's factual claim stays honest — fences don't invert the way inline spans do, so the text says they read as markup he didn't ask for rather than inventing a rendering problem, and points at plain indented text or a named file path as the way to hand over something copyable. It was the only copy of the carve-out. + +** DONE [#B] Parked: sentry Living Document updates from two dogfood runs (from takuzu + archangel) +CLOSED: [2026-07-23 Thu] +Craig approved 2026-07-23. Four folds applied to =sentry.org=: =--archive-done= touches =.gitignore= on its first run so an "org-only" pass can still produce a commit, a mirror-only project's quiet fires leave no commits so the anchor's heartbeat list is the only record, randomized property sweeps are good quiet-fire work, and the task-audit pass splits into a mechanical hourly subset plus a nightly judgment half. The fifth finding (make the bug hunt official) had already landed as pass 11, so it became a corroboration note. Takuzu's lint-org finding stayed a filed bug task rather than a workflow edit, since it's a defect not a design change. + +** DONE [#B] Parked: clear four lint flags in the notes.org template (from smoke) +CLOSED: [2026-07-23 Thu] +Craig approved 2026-07-23. Applied to =claude-templates/.ai/notes.org= and synced to the mirror. The template now lints with zero mechanical and zero judgment findings, down from four. Two fixes beyond what smoke reported: two more column-0 bold lines, and the real cause of the block flags — a literal =** Feature Name= inside the example block that org parses as a heading, cleared by comma-escaping that one line. Worth remembering: those heading flags were mechanical, not judgment, so =lint-org --fix= in any project would have rewritten the template locally and drifted it from canonical. + +** DONE [#B] Parked: clear temp/ during wrap-up teardown (from your roam capture) +CLOSED: [2026-07-23 Thu] +Craig approved 2026-07-23. Added =*** Clear temp/= to =wrap-it-up.org= Step 3, after the archive pass, and synced to the mirror. Two guards: confirm before deleting anything that reads as in-progress rather than throwaway (that belongs in =working/=, so move it there), and skip entirely where =temp/= isn't gitignored, since that means the project uses the directory for something else. This closes the last open clause of his 2026-07-20 working/temp capture. ** TODO [#B] inbox-send leaves a phantom empty handoff in the target inbox :bug:solo: =inbox-send.py= writes straight to the destination path in the target project's =inbox/=. =Path.write_text= opens with mode =w=, which creates and truncates before any content is written, so *any* failure between opening and finishing leaves a zero-byte =.org= file sitting in another project's inbox. diff --git a/working/interaction-no-markup/interaction.md.proposed b/working/interaction-no-markup/interaction.md.proposed deleted file mode 100644 index caa4d49..0000000 --- a/working/interaction-no-markup/interaction.md.proposed +++ /dev/null @@ -1,70 +0,0 @@ -# Interaction Style - -Applies to: `**/*` - -How the agent communicates with the user during a session — choice prompts, status updates, decision points. - -## No Popup Menus for Choices - -When the agent needs the user to pick between options, **do not** use the AskUserQuestion popup. Present the options inline in chat as a numbered list and ask the user to reply with a number. - -**Why:** The popup menu UI sits at the bottom of the chat window and obscures the chat content directly above it — exactly the area the user needs to read to make the choice. Inline numbered options keep the question, the surrounding context, and the proposed text all visible in the same scrollback. - -**How to apply:** - -For approve / changes / cancel flows (commit-message review, PR-description review, plan approval), draft inline: - -``` -1. Approve — commit now -2. Request changes — tell me what to adjust -3. Open in editor — emacsclient -n /tmp/... - -Pick a number. -``` - -For pick-one decisions, same shape: numbered list, one-line prompt at the end. - -For multi-select decisions, say so explicitly: "Pick any combination — reply with the numbers." - -**Render-merge guard.** GFM renderers merge adjacent ordered lists and renumber them — a message with a 1-3 content list followed by a 1-3 options list rendered as options 4-6 on the user's screen, and his pick ("4") didn't exist in the author's numbering (work session, 2026-06-10). Numerals belong to the options list only: any other enumeration in the same message uses dashes or prose, and a short prose lead-in line sits directly above the options so no renderer can merge them. When the user picks a number outside the offered range, suspect the render-merge and ask which item text they meant rather than treating it as an error. - -Reserve `AskUserQuestion` only when the user explicitly asks for the popup form ("use the popup for this one") or for genuinely free-form input where numbered options don't fit. - -This rule applies to all three approval gates in the `commits.md` publish flow (commit message, PR description, PR review reply): print the draft inline, then offer numbered approve / changes / edit options inline. Do not switch to the popup form for the gate even though the prior protocol referenced it. - -### Order options with the recommendation at item 1 - -When the question has a clear recommended option, put it at item 1. The other paths stay visible so the user can override without having to type a custom answer, but the common case collapses to a single keystroke. - -Default to this pattern whenever the user needs to weigh a genuine choice — task-review actions, brainstorm decisions, judgment calls, approve/edit/cancel gates, "what should I do next" prompts. Skip it only when the question is free-form (open prose, names, dates), when the user has already issued a directive and a single confirmation suffices, or when there is no clearly-recommended option (in which case state that plainly: "I don't have a recommendation here — what's your read?"). - -Include a "Skip" / "Defer" / "Tell me how" as the last option when the user's answer might be "none of these." - -The convention reinforces the popup-denial rule above by giving every inline choice list a single canonical shape, and it forces the model to actually pick a recommendation rather than offering a neutrally-ordered enumerate-and-defer list. A neutrally-ordered list is a covert way to push the decision back; recommendation-first puts skin in the game. - -**Enforcement:** a global `PreToolUse` hook (matcher `AskUserQuestion`) in `~/.claude/settings.json` hard-denies the popup and returns this rule as the reason — the prose alone proved too easy to forget. Because the deny is unconditional, the "use the popup for this one" exception above can't be honored in-turn; to get the popup, disable the hook via `/hooks` (or edit settings) first. - -## No Reverse-Video Highlighting in Chat Output - -In conversational output to the user, do not use Markdown bold (`**...**`) or inline-code spans (backtick `` `...` ``). The user's terminal renders both as reverse video, which is hard to read on the display. - -**Why:** The styling that looks like emphasis in most renderers comes through as inverted foreground/background here. Plain prose, plain identifiers, and plain key chords read cleanly; the "highlight" actively hurts legibility. - -**How to apply:** - -- Write command names, file paths, key chords, and code identifiers as plain text — `pearl-save-issue` becomes pearl-save-issue, `C-; L s s` becomes C-; L s s. -- Use structure that doesn't invert colors: headers, numbered lists, dashes, parentheses, and double quotes for labels are all fine. -- Fenced code blocks (triple backtick) are not an exception. Craig's direction is zero markup in chat output, always: "always always list it out without markup" (2026-05-30). Fences don't invert the way inline spans do, but they still read as markup he didn't ask for, and the carve-out kept reintroducing them. When he needs something to copy, give it as plain indented text, or write it to a file and name the path. - -This governs **chat output**, not the Markdown source of rule files, specs, or docs the user reads in an editor — those keep normal Markdown formatting. The constraint is the terminal rendering of the live conversation. - -## Showing Craig Visuals - -Craig runs Claude Code inside Emacs EAT (through tmux). EAT renders SendUserFile and inline terminal images as an `[image] path.png` text line — the visual itself never appears. In one session ~20 renders went out that way and Craig approved UI he had never seen (takuzu, 2026-07-11). Never rely on SendUserFile or inline image display to show a visual. SendUserFile stays fine for *delivering* a file; it just doesn't display one. - -Two display lanes, by what the visual is for: - -- **Durable or interactive visuals** — HTML prototypes, full renders, anything Craig will study or click: open with `gui-open ` (dotfiles-shipped; detaches through `systemd-run --user` so the agent shell can't reap it, resolves the Hyprland instance after a compositor restart, and confirms the window is actually visible before returning). It auto-detects image vs HTML; `--image` / `--browser` force. Place it off Craig's active workspace and name it, per `desktop-capture.md`. -- **Quick inline glances** — a chart, a did-it-render check: sixel in the terminal. Encode with ImageMagick (`magick sixel:`; img2sixel silently emits zero bytes on some builds) and display in a separate tmux window (`tmux new-window -d -n `, then `tmux send-keys -t "clear; cat " Enter`, tell Craig the window name) rather than the Claude Code pane, whose TUI repaints over anything drawn into it. With native tmux sixel active, the image lives in tmux's grid and survives window switches, scrolling, and resizing. - -**Capability gate.** Native sixel needs two config pieces: EAT answering the XTWINOPS cell-size query (patched eat.el, owned by .emacs.d) and `terminal-features 'xterm*:sixel'` in tmux.conf (owned by dotfiles). Check before relying on it: `tmux display -p '#{client_cell_width}'` — nonzero means go; 0 means the chain is missing a piece, so fall back to the browser lane. (Diagnosed 2026-07-13: tmux won't transmit sixel until it knows the client's cell pixel size, and stock EAT 0.9.4 silently ignores the CSI 14 t query tmux uses to ask.) diff --git a/working/interaction-no-markup/proposal-from-org-drill.org b/working/interaction-no-markup/proposal-from-org-drill.org deleted file mode 100644 index 09f0845..0000000 --- a/working/interaction-no-markup/proposal-from-org-drill.org +++ /dev/null @@ -1,5 +0,0 @@ -#+TITLE: Proposal: sharpen interaction.md's 'No Reverse-Video Highlig -#+SOURCE: from org-drill -#+DATE: 2026-07-21 00:06:48 -0500 - -Proposal: sharpen interaction.md's 'No Reverse-Video Highlighting in Chat Output' rule. Craig's feedback in org-drill (2026-05-30, saved as a harness memory there): chat output must have zero markup — no code fences, no bold, no backtick spans, ever ('always always list it out without markup'). The current rule bans bold and inline-code spans but says fenced code blocks 'are acceptable when the user explicitly wants a block to copy'. Craig's directive tightens that: default to plain text always; no fences either. Suggest updating the rule text so every project inherits it instead of it living as one project's memory. Evidence: org-drill memory file no-markup-chat-output.md and the 2026-05-30 session log there. diff --git a/working/interaction-no-markup/proposed.diff b/working/interaction-no-markup/proposed.diff deleted file mode 100644 index f5d1830..0000000 --- a/working/interaction-no-markup/proposed.diff +++ /dev/null @@ -1,11 +0,0 @@ ---- claude-rules/interaction.md 2026-07-19 15:41:37.382769288 -0500 -+++ /tmp/im.md 2026-07-23 02:47:43.771850521 -0500 -@@ -54,7 +54,7 @@ - - - Write command names, file paths, key chords, and code identifiers as plain text — `pearl-save-issue` becomes pearl-save-issue, `C-; L s s` becomes C-; L s s. - - Use structure that doesn't invert colors: headers, numbered lists, dashes, parentheses, and double quotes for labels are all fine. --- Fenced code blocks (triple backtick) are acceptable when the user explicitly wants a block to copy — they don't invert the way inline spans do. Default to plain text otherwise. -+- Fenced code blocks (triple backtick) are not an exception. Craig's direction is zero markup in chat output, always: "always always list it out without markup" (2026-05-30). Fences don't invert the way inline spans do, but they still read as markup he didn't ask for, and the carve-out kept reintroducing them. When he needs something to copy, give it as plain indented text, or write it to a file and name the path. - - This governs **chat output**, not the Markdown source of rule files, specs, or docs the user reads in an editor — those keep normal Markdown formatting. The constraint is the terminal rendering of the live conversation. - diff --git a/working/notes-template-lint/notes.org.proposed b/working/notes-template-lint/notes.org.proposed deleted file mode 100644 index 311a86f..0000000 --- a/working/notes-template-lint/notes.org.proposed +++ /dev/null @@ -1,86 +0,0 @@ -#+TITLE: Claude Code Notes - [Project Name] -#+AUTHOR: Craig Jennings -#+DATE: [Date] - -* About This File - -This file contains project-specific information for this project. - -- When to read this: -- At the start of EVERY session (after reading protocols.org) -- When needing project context or history -- When checking reminders or pending decisions - -- What's in this file: -- Project-specific context and goals -- Pending decisions -- Active reminders - -- Session history is NOT in this file. Each session's record lives in =.ai/sessions/YYYY-MM-DD-HH-MM-description.org= — one file per session. Catch-up reads the Summary sections of the most recent 5. - -- For protocols and conventions, see [[file:protocols.org][protocols.org]]. - -* Project-Specific Context - -This is where context regarding this project is placed. - -Examples of what goes here: -- Project overview and goals -- People and their relationships to the user -- Contact information for companies and people -- Current state of the project -- Status report -- Links to important documents -- Technical architecture notes -- Key decisions and rationale - -** If this is the first session - -Run the first-session workflow: [[file:workflows/first-session.org][first-session.org]]. It walks through git/.ai policy, project orientation, and initializing this notes.org. Remove this heading once done. - -* PENDING DECISIONS - -This section tracks decisions that need Craig's input before work can proceed. - -**Instructions:** -- Add pending decisions as they arise during sessions -- Format: =** [Topic/Feature Name]= -- Include: What needs to be decided, options available, why it matters -- Remove decisions once resolved (the resolution is captured in the Session Log of the session where it was resolved) - -- Example format: -#+begin_example -,** Feature Name or Topic - -Craig needs to decide on [specific question]. - -Options: -1. Option A - [brief description, pros/cons] -2. Option B - [brief description, pros/cons] - -Why this matters: [impact on project] - -Implementation is ready - just need Craig's preference. -#+end_example - -** Current Pending Decisions - -(None currently - will be added as they arise) - -* Active Reminders - -** Current Reminders - -(None currently - will be added as needed) - -** Instructions for This Section - -When Craig says "remind me" about something: -1. Add it here with timestamp and description -2. If it's a TODO, also add to =/home/cjennings/org/roam/inbox.org= scheduled for today -3. Check this section at start of every session -4. Remove reminders once addressed - -Format: -- =[YYYY-MM-DD]= Description of what to remind Craig about - diff --git a/working/notes-template-lint/proposal-from-smoke.org b/working/notes-template-lint/proposal-from-smoke.org deleted file mode 100644 index 26b458a..0000000 --- a/working/notes-template-lint/proposal-from-smoke.org +++ /dev/null @@ -1,5 +0,0 @@ -#+TITLE: Proposal: clean four lint-org flags in the notes.org templat -#+SOURCE: from smoke -#+DATE: 2026-07-21 06:49:22 -0500 - -Proposal: clean four lint-org flags in the notes.org template (claude-templates/.ai/notes.org). A sentry pass over smoke's synced copy flagged: (1) two 'possibly misplaced heading' judgments on the boilerplate lines that open with org bold at column 0 ('**When to read this:**' and '**What's in this file:**' in the About This File section) — org can parse a line-initial ** as a level-2 heading; rephrasing (a list dash, or moving the bold off line start) would clear it everywhere. (2) two 'possible incomplete block' judgments around the Pending Decisions instructions' #+begin_example block that shows the entry format — likely the example content confusing block parsing; worth a look at whether the example should escape its own markers (e.g. a comma-escaped ,** or ,#+ inside the block). Both are cosmetic — no behavior breaks — but every project inherits the lint noise on each sweep, so one canonical fix beats N local ones. No local edit made in smoke; its copy stays template-faithful. diff --git a/working/notes-template-lint/proposed.diff b/working/notes-template-lint/proposed.diff deleted file mode 100644 index 627741f..0000000 --- a/working/notes-template-lint/proposed.diff +++ /dev/null @@ -1,38 +0,0 @@ ---- claude-templates/.ai/notes.org 2026-07-09 13:57:29.814830969 -0500 -+++ /tmp/nt.org 2026-07-23 02:48:03.165968767 -0500 -@@ -6,19 +6,19 @@ - - This file contains project-specific information for this project. - --**When to read this:** -+- When to read this: - - At the start of EVERY session (after reading protocols.org) - - When needing project context or history - - When checking reminders or pending decisions - --**What's in this file:** -+- What's in this file: - - Project-specific context and goals - - Pending decisions - - Active reminders - --**Session history is NOT in this file.** Each session's record lives in =.ai/sessions/YYYY-MM-DD-HH-MM-description.org= — one file per session. Catch-up reads the Summary sections of the most recent 5. -+- Session history is NOT in this file. Each session's record lives in =.ai/sessions/YYYY-MM-DD-HH-MM-description.org= — one file per session. Catch-up reads the Summary sections of the most recent 5. - --**For protocols and conventions, see:** [[file:protocols.org][protocols.org]] -+- For protocols and conventions, see [[file:protocols.org][protocols.org]]. - - * Project-Specific Context - -@@ -48,9 +48,9 @@ - - Include: What needs to be decided, options available, why it matters - - Remove decisions once resolved (the resolution is captured in the Session Log of the session where it was resolved) - --**Example format:** -+- Example format: - #+begin_example --** Feature Name or Topic -+,** Feature Name or Topic - - Craig needs to decide on [specific question]. - diff --git a/working/sentry-living-document/feedback-from-archangel.org b/working/sentry-living-document/feedback-from-archangel.org deleted file mode 100644 index b59ce20..0000000 --- a/working/sentry-living-document/feedback-from-archangel.org +++ /dev/null @@ -1,5 +0,0 @@ -#+TITLE: Sentry dogfood feedback (first live run, archangel, night of -#+SOURCE: from archangel -#+DATE: 2026-07-21 07:22:17 -0500 - -Sentry dogfood feedback (first live run, archangel, night of 2026-07-20→21): ran clean end to end — 7 fires (1 working + 6 quiet heartbeats), morning teardown + 8-item approval-queue walk all worked as designed. Two findings for the Living Document: (1) todo-cleanup --archive-done can write a TRACKED file from an 'org-only' pass — on first archive it creates archive/task-archive.org and appends a .gitignore entry, so the pass produced a real commit and correctly triggered the fire-end conditional suite; worth a note in the pass list that todo-cleanup's first archive touches .gitignore. (2) In a project that gitignores .ai/, the whole spine is the mirror-only case — quiet fires produce zero commits, so 'git log main..sentry/*' understates the night's activity; the anchor's heartbeat list is the only record. Both are notes, not defects. diff --git a/working/sentry-living-document/feedback-from-takuzu.org b/working/sentry-living-document/feedback-from-takuzu.org deleted file mode 100644 index fe3ddba..0000000 --- a/working/sentry-living-document/feedback-from-takuzu.org +++ /dev/null @@ -1,5 +0,0 @@ -#+TITLE: Sentry dogfood feedback from its first live overnight run (t -#+SOURCE: from takuzu -#+DATE: 2026-07-21 06:43:55 -0500 - -Sentry dogfood feedback from its first live overnight run (takuzu, 2026-07-20/21, 8 hourly fires): the engine worked as designed end to end — entry gates, per-fire lock acquire/release, branch isolation, digest-vs-heartbeat split, approval queue, morning squash-merge teardown. Findings for the Living Document: (1) a Craig-granted rotating-angle bug-hunt pass found 3 real fixes in the first 4 fires then went honestly quiet — worth considering as an official opt-in pass; (2) randomized property sweeps (generate-and-verify in both engines) made good quiet-fire work beyond the frozen parity corpus; (3) lint-org's todo-format checkers (level2-done-without-closed, level-2-dated-header) fire on spec files' Decisions sections under docs/specs/ — consider scoping those checkers away from spec files or blessing CLOSED dates in spec-create; (4) the per-fire full task-audit pass is too heavy hourly — a mechanical-subset audit with judgment items queued once per night fit better. No engine defects observed; skip semantics and the quiet-fire heartbeat behaved per spec. diff --git a/working/sentry-living-document/proposed.diff b/working/sentry-living-document/proposed.diff deleted file mode 100644 index 90c0db4..0000000 --- a/working/sentry-living-document/proposed.diff +++ /dev/null @@ -1,32 +0,0 @@ ---- claude-templates/.ai/workflows/sentry.org 2026-07-23 02:39:42.349838885 -0500 -+++ /tmp/st.org 2026-07-23 02:48:24.104591775 -0500 -@@ -131,9 +131,9 @@ - - 3. *Triage intake — mail and messenger sources excluded.* Run =triage-intake.org=, loading only its non-mail, non-messenger source plugins (calendar, PR/ticketing). The mail and messenger plugins — cmail, any Gmail variant, Telegram, Signal, chat DMs — are never loaded by a sentry fire: Craig ruled 2026-07-21 that sentry doesn't check email or messengers. A manual "triage intake" still scans everything. Probe: the project has at least one *active* triage source that survives that exclusion — a project-specific plugin (=.ai/project-workflows/triage-intake.*.org=), or a non-empty =:TRIAGE_SOURCES:= declaration naming general plugins that exist. Mere presence of the template-synced general plugins does *not* activate the pass; a project that declares no sources, or whose only declared sources are mail or messengers, probe-skips (see =docs/specs/2026-07-20-triage-source-activation-spec.org=). Destructive actions (deleting, archiving, sending) queue; they never fire unattended. - --4. *Todo cleanup* — the =clean-todo.org= mechanics (hygiene pass + =--archive-done= + =--convert-subtasks=). Probe: a root =todo.org=. -+4. *Todo cleanup* — the =clean-todo.org= mechanics (hygiene pass + =--archive-done= + =--convert-subtasks=). Probe: a root =todo.org=. Note that =--archive-done= is not purely an org-file pass on its first run in a project: it creates =archive/task-archive.org= and appends a =.gitignore= entry, so it produces a real tracked-file commit and correctly trips the fire-end conditional suite. (archangel, first live run 2026-07-21.) - --5. *Task audit* — =task-audit.org=. Probe: a root =todo.org=. Priority regrades and consolidations queue for review; factual staleness fixes that are unambiguous execute. -+5. *Task audit* — the *mechanical subset* of =task-audit.org= hourly (staleness counts, structural checks, cookie recomputation); the judgment half (priority regrades, consolidations, merge candidates) runs *once per night* and queues its findings rather than repeating them every fire. Probe: a root =todo.org=. A full audit every hour is too heavy and re-surfaces the same judgment calls all night. (takuzu, first live run 2026-07-21.) Factual staleness fixes that are unambiguous still execute. - - 6. *Working-files hygiene* — flag =working//= directories whose backing task is closed (a filing candidate per =working-files.md=). Probe: a =working/= directory exists. The filing itself queues (it's a judgment move). - -@@ -145,7 +145,7 @@ - - 10. *Prep + symlink freshness* — stale daily-prep docs, broken symlinks. Probe: the prep dir / symlinks exist (work and home only, in practice). - --11. *Bug finding* — hunt for real bugs in the project's codebase: static analysis (=shellcheck= for shell, the project's own linters for its languages), config sanity checks, plus one targeted code-reading area per fire. Rotate the area across fires and name it in the digest, so coverage accumulates over a night instead of re-reading the same corner. This pass does *not* run the test suite — the entry baseline already ran it, and re-running it hourly is anti-pattern 5; read the entry result instead. Probe: the project carries a codebase — source under version control beyond its org and tooling files. File each verified finding as a graded bug task in =todo.org= per the severity × frequency matrix (=todo-format.md=), deduped against existing tasks; an unverifiable suspicion is a digest line, not a task. Find, never fix unattended — a proposed fix, or any remediation beyond the task filing, queues for morning approval with its exact edit. (Added at Craig's order 2026-07-21, first dogfooded in dotfiles.) -+11. *Bug finding* — hunt for real bugs in the project's codebase: static analysis (=shellcheck= for shell, the project's own linters for its languages), config sanity checks, plus one targeted code-reading area per fire. Rotate the area across fires and name it in the digest, so coverage accumulates over a night instead of re-reading the same corner. Randomized property sweeps (generate-and-verify against an engine's own invariants) are good quiet-fire work here, reaching past a frozen test corpus. Expect the pass to go honestly quiet after the first few fires find the standing defects; a quiet bug hunt is a result, not a failure. (takuzu, first live run 2026-07-21: three real fixes in the first four fires, then quiet.) This pass does *not* run the test suite — the entry baseline already ran it, and re-running it hourly is anti-pattern 5; read the entry result instead. Probe: the project carries a codebase — source under version control beyond its org and tooling files. File each verified finding as a graded bug task in =todo.org= per the severity × frequency matrix (=todo-format.md=), deduped against existing tasks; an unverifiable suspicion is a digest line, not a task. Find, never fix unattended — a proposed fix, or any remediation beyond the task filing, queues for morning approval with its exact edit. (Added at Craig's order 2026-07-21, first dogfooded in dotfiles.) - - (KB lesson promotion — the pass the original proposal listed eleventh — is deferred to vNext. An unattended judgment pass writing to the shared knowledge base waits until sentry has quiet weeks behind it and a designed detection heuristic. See the filed lesson-detection-heuristic task.) - -@@ -178,6 +178,8 @@ - - A bad night is discarded by deleting one branch — nothing reached main, nothing was pushed. - -+In a project that gitignores =.ai/=, the whole spine is untracked, so quiet fires produce no commits at all and =git log main..sentry/-= understates the night's activity. There the anchor's heartbeat list is the only record of what fired. Read the anchor, not just the log. (archangel, first live run 2026-07-21.) -+ - * Stop Sentry - - Trigger: "stop sentry" (and synonyms above). Sentry owns its own shutdown: diff --git a/working/sentry-living-document/sentry.org.proposed b/working/sentry-living-document/sentry.org.proposed deleted file mode 100644 index 62eac12..0000000 --- a/working/sentry-living-document/sentry.org.proposed +++ /dev/null @@ -1,219 +0,0 @@ -#+TITLE: Sentry — Overnight Hygiene Supervisor -#+AUTHOR: Craig Jennings -#+DATE: 2026-07-19 - -* Overview - -Sentry is an interval loop that keeps a project's hygiene current while Craig is away. Each fire walks a fixed list of hygiene passes — roam pull, inbox zero, triage (no mail or messengers), todo cleanup, task audit, working-files hygiene, spec board, link integrity, git health, prep freshness, bug finding — and commits each pass's writing to a throwaway daily branch. Nothing pushes. In the morning Craig reviews the branch, squash-merges what he wants, and deletes it. - -The design goal is a project that greets the morning already tidy, with every judgment call and every destructive action parked in an approval queue rather than executed unattended. Sentry does the mechanical sweeping; Craig does the deciding. - -This file is the engine. It owns the entry gates, the branch mechanics, the lock model, the per-fire pass runner, the digest and approval queue, the skip semantics, and the stop-sentry shutdown. The =agent-lock= helper (=.ai/scripts/agent-lock=) provides the locks. The passes reuse existing workflows (=inbox.org=, =triage-intake.org=, =clean-todo.org=, =task-audit.org=) under sentry's unattended contract. - -* When to Use This Workflow - -Craig arms sentry at the end of a session, with the machine left running, to have overnight hygiene done by morning. - -Triggers: - -- "start sentry", "run sentry", "arm sentry", "sentry mode" -- "let sentry watch this overnight", "keep this tidy overnight" -- "start sentry hourly", "start sentry every " (sets the loop interval) - -Stop trigger (see Stop Sentry below): - -- "stop sentry", "stand down sentry", "sentry off" - -Sentry is deliberately *not* auto-armed. Running it in a project is a per-project grant (the =:COMMIT_AUTONOMY:= marker) plus a deliberate launch with Craig at the terminal for the entry gates. - -* Prerequisite — the autonomy ticket - -Sentry commits unattended. =commits.md= gates commits on Craig's approval, so sentry needs standing, per-project authorization to run at all. Before anything else, read the project's =.ai/notes.org= Workflow State block for: - -: :COMMIT_AUTONOMY: yes - -If the marker is absent or not =yes=, decline to start and name the marker: - -: Sentry needs ":COMMIT_AUTONOMY: yes" in .ai/notes.org Workflow State to run — it commits unattended. Add it to grant, or run the hygiene passes by hand. - -No half-running mode: a project without the grant doesn't run sentry's read-only passes either. The grant is one line away, so this is a deliberate opt-in, not a barrier. - -* Entry — interactive, with Craig present - -Craig types the sentry trigger, so the first moves run with him at the terminal. Do them in order; each gate that fails stops entry until Craig answers. - -1. *Autonomy ticket* — the prerequisite above. Absent → decline and stop. - -2. *Dirty-tree gate.* =git diff --quiet HEAD= (tracked modifications only; untracked and gitignored files never block — an inbox drop or scratch file is not in-progress work). If the tracked tree is dirty, describe what's dirty and offer, inline-numbered per =interaction.md=: - - 1. Finish the job — commit the in-progress work first (recommended if it's a coherent unit) - 2. Stash it — =git stash= and start sentry on a clean tree - 3. Roll back named changes — discard specific files (names them) - - Wait for an answer. Sentry can't start unattended from a dirty state; that's the point. - -3. *Green-suite gate.* Run the project's full suite (=make test=, or the project's equivalent — detect it). Read the output. If anything is red, describe the failures and offer to investigate before arming. The loop starts only on a green baseline, because every unattended fire measures itself against "did I break this?" and a pre-existing red poisons that check. - -4. *Prior sentry branch.* =git branch --list 'sentry/*'=. An unmerged =sentry/*= branch from a previous night means the morning review didn't happen. Surface it and offer to squash-merge or delete it now (Craig is present); don't stack a second sentry branch on the first. - -5. *Reconcile the project branch.* Fetch and fast-forward-only against upstream — the same reconcile =startup= runs: - - : git fetch --all --prune - : git rev-list --left-right --count @{u}...HEAD - - Zero-behind → continue. Behind-only and clean → =git merge --ff-only @{u}=. Diverged → surface to Craig (he's present); don't auto-resolve. - -6. *Create the daily branch.* From HEAD: - - : git switch -c "sentry/$(date +%F)-$(uname -n)" - - The host suffix (=uname -n=) stops a same-date collision between the two daily drivers. The working tree now sits on this branch overnight — the launch hands the repo to sentry until the morning merge. Reclaiming it mid-night means stopping sentry first (see Stop Sentry). Note the Emacs buffer-revert caveat to Craig if he has the repo open: files change on disk under him overnight, so buffers want reverting after the morning merge (see =emacs.md=). - -7. *Arm the loop.* Start =/loop= at the interval (default hourly; Craig's "every " phrase overrides) with the per-fire body being one sentry fire (the Pass Runner below). Confirm the arming in one line: interval, branch name, project. - -* The lock model - -Two locks, both served by =.ai/scripts/agent-lock= (names only; the helper owns the paths, which live on tmpfs under =$XDG_RUNTIME_DIR/agent-locks/=, host-local and cleared on reboot). - -*Single-runner lock* (=sentry-=, where == is the repo-root basename: =basename "$(git rev-parse --show-toplevel)"= — the same derivation =wrap-it-up.org='s guard uses, so the two agree on the lock name). Each fire acquires it at fire start and releases it at fire end, and refreshes it between passes (the heartbeat, so a live fire's lock never ages past one pass). If =/loop= fires again while a previous fire still holds it, the new fire's acquire fails and the fire skips with one digest line — no two fires run at once. The bounded wait is short (a few seconds); a live fire means defer, not queue. - -*Roam-write lock* (=roam-write=). A pass that edits a file under =~/org/roam= acquires it, runs =capture-guard --wait= (the human-capture layer stays underneath), edits the working tree, triggers =systemctl --user start roam-sync.service=, and releases. The lock spans only edit-plus-trigger. Sentry never runs =git= against =~/org/roam= — roam-sync stays the repo's only committer (the 2026-06-24 one-git-owner rule). Pass 1's =pull --ff-only= is the sole, read-only exception. - -Every reclaim of a stale lock surfaces in the digest — the helper prints the reclaim note, and the fire records it. A reclaim during a genuinely slow pass is possible, so it's never silent. - -* The Pass Runner — one contract per pass - -Each fire, after acquiring the single-runner lock and verifying branch state (below), walks the pass list in order. Every pass follows the same four-step contract: - -1. *Probe* — a cheap existence check for the pass's target (named per pass below). Absent → the pass is one skip line in the digest and nothing more. This is what makes the pass list portable: passes self-activate where their target exists and stay silent elsewhere, with zero per-project configuration. - -2. *Work* — run the pass under the unattended contract. Quick, solo, already-agreed mechanical actions execute. Anything destructive or requiring judgment does *not* execute — it appends to the morning-approval queue (what, why, the exact command or edit that fires on approval). A pass runs fully or not at all; there is no reduced-form pass. - -3. *Session-context entry* — a pass that does or queues work appends its digest line to the =session-context.org= Session Log (path resolved via =.ai/scripts/session-context-path=) before its commit, so a crash between them still leaves the trail. Per-pass lines for an all-quiet fire (every pass probe-skipped or no-op) are not written one by one — the fire collapses to a single heartbeat at fire-end (below), so an idle fire doesn't spray one skip line per pass. - -4. *Commit* — if the pass wrote to disk, commit it: =chore(sentry): =. One commit per writing pass. A probe-skip or a no-op pass writes nothing and commits nothing. - -Between passes, refresh the single-runner lock (=agent-lock refresh sentry-=) — the heartbeat. - -** Branch-state verification (fire start, before the passes) - -After acquiring the lock, confirm the fire is safe to run: - -- *On the right branch* — HEAD is =sentry/-=. If the loop was armed on a prior day and crossed midnight, the branch keeps the arming date; that's fine, morning teardown handles it. If HEAD is somehow *not* a sentry branch (an interrupted stop, a manual checkout), skip the whole fire with a digest line rather than committing onto main. -- *Clean of foreign changes* — =git diff --quiet HEAD= excluding the spine set (=session-context.org= / =session-context.d/=, resolved via =session-context-path=). Sentry's own spine writes must not trip this; a genuinely unexpected dirty tree (something outside the spine changed and wasn't committed by a prior pass) poisons the fire — skip it with a digest line, the next fire retries. - -* Unattended safety — skip, never degrade - -With no one at the terminal, any unsafe state makes the affected scope skip with one digest line, and the next fire retries. Unsafe states and their scope: - -- *Unexpected dirty tree* (non-spine) → skip the whole fire. -- *Lost or un-acquirable single-runner lock* → skip the fire (another fire holds it, or the helper is missing). -- *A pass's own precondition unmet* (its probe fails, or a dependency is dirty) → skip that pass only. -- *Red suite at fire-end* (see below) → the commits stay on the branch, flagged in the digest for morning review; the fire doesn't roll back. - -Skips are never silent and never partial. Inside a *working* fire, a pass line means the pass fully ran and a skip line names why it didn't. An *all-quiet* fire is not a silent skip either: its single =sentry at HH:MM: nothing= heartbeat is the explicit record that every pass found nothing, standing in for a wall of identical skip lines. The anti-silence rule targets a pass that hides work it should have surfaced; a quiet fire has surfaced that there was none. - -** Multi-day stall notification - -An unmerged prior =sentry/*= branch at fire start (the morning review never happened) skips the fire. After the *second consecutive* fire skipped for this reason, send one persistent desktop notification naming the project and branch: - -: sentry stalled: unmerged — merge or delete to resume - -Then repeat at most daily. Persistent notify matches the paging convention — it stays on screen until dismissed. A multi-day stall never stays silent. - -* The pass list (v1) - -In order. Each names its detection probe. A pass whose probe fails is one skip line. - -1. *Roam pull* — =git -C ~/org/roam pull --ff-only=. Probe: =~/org/roam= is a git clone. Skipped when the roam tree is dirty (roam-sync owns that case) or the clone is absent. Read-only and ff-only — the one narrow exception to "don't touch roam git," so later passes read a fresh tree. - -2. *Inbox zero* — run =inbox.org= roam mode under the no-approvals contract: quick+solo+agreed items execute, shared-asset and convention proposals park (prepared diff, =VERIFY= task, sender reply) in the approval queue. Edits to =~/org/roam/inbox.org= take the roam-write lock + =capture-guard=. Probe: the roam clone or a project =inbox/= exists. Tidying the shared roam inbox is allowed from *any* project session, work included — it's housekeeping on a shared resource, not a durable KB-node write, so the work-denylist doesn't gate it (=knowledge-base.md=). Never park it as a cross-project boundary crossing. - -3. *Triage intake — mail and messenger sources excluded.* Run =triage-intake.org=, loading only its non-mail, non-messenger source plugins (calendar, PR/ticketing). The mail and messenger plugins — cmail, any Gmail variant, Telegram, Signal, chat DMs — are never loaded by a sentry fire: Craig ruled 2026-07-21 that sentry doesn't check email or messengers. A manual "triage intake" still scans everything. Probe: the project has at least one *active* triage source that survives that exclusion — a project-specific plugin (=.ai/project-workflows/triage-intake.*.org=), or a non-empty =:TRIAGE_SOURCES:= declaration naming general plugins that exist. Mere presence of the template-synced general plugins does *not* activate the pass; a project that declares no sources, or whose only declared sources are mail or messengers, probe-skips (see =docs/specs/2026-07-20-triage-source-activation-spec.org=). Destructive actions (deleting, archiving, sending) queue; they never fire unattended. - -4. *Todo cleanup* — the =clean-todo.org= mechanics (hygiene pass + =--archive-done= + =--convert-subtasks=). Probe: a root =todo.org=. Note that =--archive-done= is not purely an org-file pass on its first run in a project: it creates =archive/task-archive.org= and appends a =.gitignore= entry, so it produces a real tracked-file commit and correctly trips the fire-end conditional suite. (archangel, first live run 2026-07-21.) - -5. *Task audit* — the *mechanical subset* of =task-audit.org= hourly (staleness counts, structural checks, cookie recomputation); the judgment half (priority regrades, consolidations, merge candidates) runs *once per night* and queues its findings rather than repeating them every fire. Probe: a root =todo.org=. A full audit every hour is too heavy and re-surfaces the same judgment calls all night. (takuzu, first live run 2026-07-21.) Factual staleness fixes that are unambiguous still execute. - -6. *Working-files hygiene* — flag =working//= directories whose backing task is closed (a filing candidate per =working-files.md=). Probe: a =working/= directory exists. The filing itself queues (it's a judgment move). - -7. *Spec status board* — the =docs-lifecycle= grep for spec keywords, surfacing any =DOING= spec whose bound build parent is closed. Probe: =docs/specs/= exists. - -8. *Link integrity* — broken =file:= links in the project's org files, via =lint-org.el=. Probe: =lint-org.el= present. Report-only into the digest; no unattended rewrites. - -9. *Git health* — uncommitted drift, unpushed commits on other branches, stale branches, main-behind-origin. Probe: =.git=. Report into the digest. - -10. *Prep + symlink freshness* — stale daily-prep docs, broken symlinks. Probe: the prep dir / symlinks exist (work and home only, in practice). - -11. *Bug finding* — hunt for real bugs in the project's codebase: static analysis (=shellcheck= for shell, the project's own linters for its languages), config sanity checks, plus one targeted code-reading area per fire. Rotate the area across fires and name it in the digest, so coverage accumulates over a night instead of re-reading the same corner. Randomized property sweeps (generate-and-verify against an engine's own invariants) are good quiet-fire work here, reaching past a frozen test corpus. Expect the pass to go honestly quiet after the first few fires find the standing defects; a quiet bug hunt is a result, not a failure. (takuzu, first live run 2026-07-21: three real fixes in the first four fires, then quiet.) This pass does *not* run the test suite — the entry baseline already ran it, and re-running it hourly is anti-pattern 5; read the entry result instead. Probe: the project carries a codebase — source under version control beyond its org and tooling files. File each verified finding as a graded bug task in =todo.org= per the severity × frequency matrix (=todo-format.md=), deduped against existing tasks; an unverifiable suspicion is a digest line, not a task. Find, never fix unattended — a proposed fix, or any remediation beyond the task filing, queues for morning approval with its exact edit. (Added at Craig's order 2026-07-21, first dogfooded in dotfiles.) - -(KB lesson promotion — the pass the original proposal listed eleventh — is deferred to vNext. An unattended judgment pass writing to the shared knowledge base waits until sentry has quiet weeks behind it and a designed detection heuristic. See the filed lesson-detection-heuristic task.) - -* Fire-end — conditional suite, then the digest commit - -After the passes: - -1. *Conditional suite run.* If any pass this fire modified files *outside* the org/spine set (a code-touching pass, rare but possible via fixtures), run the full suite once. A green run confirms the fire's commits are safe; a red run flags the digest for morning review — the commits stay on the branch (nothing is pushed, so the morning gate catches it). No per-pass suite runs: the entry run is the green baseline, and hourly per-commit runs would turn a seconds-long fire into minutes all night. Fires that only touched org/spine files skip this. - -2. *Heartbeat or digest, then commit.* Decide quiet vs working. A *quiet* fire — every pass probe-skipped or no-op, nothing added to the approval queue — writes a single heartbeat line to the Session Log, =sentry at HH:MM: nothing= (HH:MM local, from =date=), and no per-pass digest block. A *working* fire — any pass ran, wrote, or queued — writes its full per-pass digest block. Then commit any accumulated spine writes in one sweep: =chore(sentry): digest —