| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
| |
The pager worked but was undocumented and drifting stale: signal-cli warned the account had gone 47 days without a receive, and no runbook existed to hand a future session or a non-Claude runtime.
I added the runbook under docs/design/ and a roam-sync-shaped receive timer that drains the queue every 15 minutes, keeping the account warm and surfacing reply messages. I also generalized agent-page: it now sends directly from any machine holding the pager account and relays to velox only when it doesn't. ratio is linked as a device of the account, so a page lands even when velox is down.
The receive script no-ops cleanly where the account isn't registered, since the units stow onto every machine via the shared common package.
|
| |
|
|
| |
Polyglot: case-by-case, no new machinery. The bundle files that auto-install already compose. The only real collision is the hand-pasted coverage-makefile.txt, and clock-panel (the one polyglot project) proves it's benign. Keep the install-lang guard and document the coverage-target namespacing as a manual step (filed as a follow-up). Subprojects: don't promote. N=1 (only home), and a 282-line always-on rule for one project fails the thin-always-on principle. Keep it home-local. Revisit as a thin pointer if a second case appears.
|
| |
|
|
| |
scouting tasks due
|
| |
|
|
| |
Merged the two no-session /schedule tasks (the sentry variant and the unattended inbox check) into one "Unattended /schedule cron contract". They were the same design problem stated for two consumers. Added a foundational "Cross-host agent coordination" task capturing the shared prerequisite behind the roam-conflict task and the two blocked sentry passes: agent-lock is host-local, so nothing coordinates ratio and velox. Cross-linked both dependents to it.
|
| | |
|
| | |
|
| |
|
|
|
|
| |
The general triage plugins (personal Gmail, cmail, calendar, Telegram, GitHub PRs) are template-synced into every project, so the old "plugins present" probe self-activated triage everywhere. The sentry live trial caught it reaching personal inboxes from a project that isn't a triage target. Now a general plugin activates only when the project names its basename in a :TRIAGE_SOURCES: line in notes.org Workflow State. A project-specific plugin stays active by presence. The gate lives in triage-intake Phase 0, so it fixes the interactive over-pull too, and sentry's pass-3 probe reads the same signal.
Presence is capability, the declaration is activation. A project that declares nothing and owns no project plugin pulls nothing. Migration handoffs went to home and work. rulesets correctly declares nothing.
|
| |
|
|
|
|
| |
Auto triage-intake and auto inbox-zero now collapse an empty check to a single "<workflow> at HH:MM: nothing" line, matching the sentry quiet-fire heartbeat from Phase 1. Detection stays in-session, so the MCP-auth sources are scanned in full and only the output collapses. Inbox already had an acknowledge-only-on-empty rule, now unified to the standard format; triage's three-section sweep output gains the empty-sweep collapse.
Completes the silent-until-signal spec (Phases 2-5): the spec is the shared-policy home the three workflows reference, and a manual-testing entry covers the live-loop verification. Spec flipped to IMPLEMENTED.
|
| |
|
|
|
|
| |
A suspend now ends by detaching the tmux client viewing the aiv-<project> session, so the parked session drops out of the alt-space rotation instead of cluttering it. Detach is not teardown: the session and agent process stay alive in the background and only the view disconnects, so it runs inline as the last action (the handoff text is preserved in the pane and shown on re-attach) rather than deferring to a Stop hook the way the session-killing teardown must. Degrades gracefully when not in tmux.
Originated in archsetup as a local stopgap and reconciled to canonical here.
|
| |
|
|
|
|
| |
A sentry fire whose passes all probe-skip or no-op now writes a single "sentry at HH:MM: nothing" line instead of a full per-pass digest block. A fire that runs or queues anything writes the full digest as before. This is Phase 1 of the silent-until-signal policy. The live trial's fires 3-8 were walls of no-op digests, the noise this removes.
The "no silent skip" discipline is reconciled, not broken: inside a working fire every pass still writes its ran/skipped line, and a quiet fire's heartbeat is the explicit "nothing to do" record rather than a hidden skip.
|
| |
|
|
| |
An in-session monitor loop (sentry, auto triage-intake, auto inbox-zero) fires the model every interval and narrates a full turn even when nothing changed, so a session fills with quiet-fire noise. The spec settles the fix as a policy, not a watcher: detect first, and on an empty check collapse to one labelled heartbeat line, spending a full turn only on a real item. Keeping detection in-session dissolves the MCP-auth split (triage's sources need the session's auth, which a detached watcher can't reach), so it applies uniformly across all three loops. DRAFT, decisions resolved live. Links a [#B] build task.
|
| |
|
|
|
|
| |
research task
Two .emacs.d handoffs. The silent-until-signal monitor pattern (Craig-approved) files as a [#B] spec candidate — it's a cross-workflow shared-asset change across triage-intake, inbox, and sentry, and the sentry live trial just demonstrated the noise problem it solves. The device-location MCP question files as a [#D] research task.
|
| |
|
|
| |
live trial
|
| |
|
|
| |
archsetup routed a Craig-authored suspend.org change (detach the tmux client on every suspend, so a parked session leaves the alt-space rotation). It's a synced shared asset, so it parks for approval rather than landing unattended: reviewed as clean and low-risk, filed as a [#C] task with the exact diff preserved in the processed handoff, replied to archsetup. Applies to canonical on Craig's go.
|
| |
|
|
| |
The autonomous-batch spec's three source artifacts (the Phase E proposal, its diff, the sender note) moved from working/inbox-zero-phase-e/ into docs/design/ under dated names, with the spec's inbound links repointed. The spec is IMPLEMENTED, so the working dir was a filing candidate. Also removed the spent working/sentry-2026-07-19-working-files-ruling/ staging dir, whose proposal shipped in the working/-tracked-from-creation change. working/ is now empty and gone.
|
| |
|
|
| |
Three pointed at artifacts that were folded in or processed away (a spec-review file merged into wrapup-routing-spec, a removed pearl inbox note, a home-side subprojects log with no rulesets file) — delinked to plain text so the reference survives without a dead link. The fourth repoints the autonomous-batch spec at inbox.org, since inbox-zero.org was renamed in the consolidation. Surfaced by the sentry link-integrity pass.
|
| |
|
|
| |
The sentry live trial surfaced that triage-intake self-activates in every project. The general personal-account plugins are template-synced everywhere, so its probe reads present-everywhere as active-everywhere. The spec captures the fix: general plugins gate on a per-project :TRIAGE_SOURCES: declaration, project-specific plugins stay active by presence, and the activation layer lives in triage-intake Phase 0 so it fixes interactive runs too. DRAFT, pending review. Links a [#B] build task.
|
| |
|
|
|
|
|
|
|
|
| |
working/ is the tracked home of in-progress work, version-controlled from creation rather than staged locally until it graduates. Filing on completion reorganizes durable artifacts into permanent homes. It doesn't mark when they became durable. Ephemeral, disposable artifacts go in a gitignored temp/ (or /tmp), never working/.
install-ai.sh emits a temp/ ignore in both track and gitignore modes. Ephemerality is independent of whether a project tracks its .ai/ tooling, so temp/ rides neither the track .gitkeep step nor the gitignore-only tooling block. working/ is never ignored, on purpose. sweep-gitignore-tooling.sh backfills temp/ as a separate mode-independent pass, not an IGNORE_SET member: folding it into that set would skip every track-mode project, the set that most needs it.
While implementing I confirmed the canonical machinery never ignored working/ (the tooling set is only .ai/ .claude/ CLAUDE.md AGENTS.md), so a project that ignored working/ did so as a local deviation.
Documents the convention in working-files.md and mirrors it in protocols.org. 7 new bats cover temp/ in both modes, never working/, and idempotency.
|
| |
|
|
|
|
| |
trial
.emacs.d routed three sentry-workflow design ideas from its own hand-run trial (codebase-gated bug/enhancement logging, a system-health pass, a sibling-machine freshness pass). Filed as a [#C] backlog task rather than applied — they're design input for the sentry Living Document refinements and two of them hinge on an unresolved cross-driver coordination question. Replied to the sender.
|
| |
|
|
|
|
| |
Home's colloquialisms + "the list" before-close-queue handoff was already wired canonically before it arrived (protocols.org Colloquialisms section + wrap-it-up Before-Close Queue drain step); replied to confirm, filed.
The .emacs.d working/ tracked-from-creation ruling is a shared-asset + convention change carrying a temp/-placement design decision, so it parks rather than applying unattended: staged proposal under working/, filed a [#B] VERIFY, replied to .emacs.d with the two findings (canonical never ignored working/; temp/ needs a both-modes ignore call).
|
| |
|
|
| |
Move the three finished tasks into Resolved: the bin/ai characterization-and-refactor hardening, the inbox-boundary Stop hook, and the before-close-queue convention.
|
| |
|
|
| |
Both built, tested, and pushed this session: the task-boundary inbox Stop hook (soft-nudge) and the 'the list' before-close queue plus colloquialisms convention.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
convention
Home proposed two linked cross-project norms and Craig approved rolling them out. Both now ship in the template so every project inherits them.
A new "Colloquialisms and Expansions" section in protocols.org maps Craig's shorthand to a defined action the agent applies without asking. Two seed entries: "put X on the list" appends to a session-scoped Before-Close Queue, and "tell <project> <message>" drops the message in that project's inbox via inbox-send. The set is extensible.
The queue lives under a "* Before-Close Queue" heading in the session anchor, so it resets when the anchor is archived at wrap. Anything that must outlive the session stays a todo.org task. wrap-it-up Step 1 gets a sub-step that works the queue oldest-first before the Summary, so any resulting edits ride the wrap commit, with unfinished items surfaced in the valediction rather than dropped.
I settled three design calls. The reference lives in protocols.org rather than a per-project notes.org (home's local reference), since a synced section is what makes it a shared norm. The queue lives in the session anchor, as home did. The wrap step sits at the front of Step 1 rather than a new half-step, which keeps the "Steps 1 through 5" framing intact. A documentation-integrity bats guards the wiring and the before-the-Summary ordering against a future edit or sync dropping it.
|
| |
|
|
|
|
|
|
| |
The "check inbox/ at every task boundary" rule was prose-only in protocols.org, so it held only as well as the agent remembered it and a handoff could pass a turn unseen. inbox-boundary-check.sh is a Stop hook that backs the rule: when inbox-status -q reports pending items it blocks the yield once and injects the count, so the agent processes them before returning.
It soft-nudges rather than hard-blocks. On the harness re-entry (stop_hook_active: true) the hook steps aside and lets the turn end, so a mid-task pause to ask a question, or an item the agent genuinely can't process, never wedges the session. A hard block would instead push inbox processing ahead of that clarifying question.
The hook self-skips where it doesn't apply: no inbox/ dir, no inbox-status, or a clean inbox each exit 0 silently, so one global hook covers every project with no config. It prefers the project-local .ai/scripts/inbox-status and falls back to one on PATH. It's wired into the Stop array ahead of ai-wrap-teardown in both the tracked settings.json (which the live ~/.claude/settings.json symlinks to) and the documented snippet. bats covers pending, clean, the re-entry step-aside, no-inbox, and the absent-status degrade.
|
| |
|
|
| |
Netted the 17 uncovered functions (launcher tests 9 to 42), extracted four pure decision cores with Normal/Boundary/Error sets, and dispositioned the footgun audit plus refactor pass. shellcheck clean, shfmt consistent, suite green before and after.
|
| |
|
|
|
|
|
|
| |
I moved the decision logic out of the launcher's I/O-coupled functions so it's unit-testable on plain inputs, the hardening the audit called for. Three pure cores now carry logic that was buried in git and tmux calls: _git_prep_action classifies a repo's state into none/pull/report, _order_windows sorts the window listing, and _match_window_id resolves a name to a window id. _git_is_dirty replaces the staged/unstaged/untracked check that was copy-pasted across three functions.
prep_git_single and auto_pull_if_clean both route their pull-or-report choice through _git_prep_action, so the "never auto-pull a dirty repo" rule lives in one place with its own test instead of two parallel branches. sort_windows keeps its two-pass move (park high, then reassign) but hands the ordering to _order_windows, and find_window_id is now a thin tmux wrapper over _match_window_id.
Each core gets a Normal/Boundary/Error set: the git-prep truth table including diverged and behind-while-dirty, the window ordering including whole-line-not-substring matching. The 42 launcher tests stay green and a live black-box run still prints the right launch command for claude and codex, so the extraction is behavior-neutral. I also ran shfmt (-i 2 -ci) to match the house style the other bin/ scripts follow.
|
| |
|
|
|
|
|
|
| |
Brings the session launcher under characterization tests ahead of the hardening refactor, so the pure-core extractions and footgun fixes land against a green net. The tmux/git-coupled functions can't be reached by running the launcher black-box, so I wrapped dispatch in main() behind a run-vs-sourced guard: run directly it dispatches as before, sourced it exposes the functions to the tests.
The net pins current behavior (record-not-spec) with a Normal/Boundary/Error set per unit: git_status_indicator across no-upstream/ahead/behind/dirty/clean, candidate discovery and filtering, selection parsing, and functional tests over create_window, find_window_id, and sort_windows ordering against a throwaway tmux server. Every tmux call runs on a private socket so nothing touches the live ai session, and the throwaway repos disable git auto-maintenance to dodge the teardown race that flaked rename-ai.
Also cleared the two pre-existing shellcheck warnings on the touched paths: quoted the "@{u}" revspecs (SC1083) and marked the intentionally-literal display tilde (SC2088). Both are behavior-neutral, and the existing runtime tests confirm the launch path is unchanged.
|
| |
|
|
|
|
| |
criteria
The task was phrased as an absence ("find bugs until none visible, refactor until nothing worthwhile remains"), which fails the verifiable gate and can't be :solo:. Rescoped it with the four-move pattern just landed in todo-format.md: bound the surface (the 22 functions, split covered/uncovered), net the behavior with characterization tests (pure cores extracted where tmux/git coupling blocks it), disposition every finding (a per-function footgun audit plus a /refactor pass), and gate on an objective floor (shellcheck, shfmt, green suite, per-function coverage, plus ~3 functional tests over the launch pipelines). The qualifying answer is a dispositioned report, not a claim of zero bugs. Split the interactive runtime picker out of the :solo: scope, since it carries a design call rather than hardening.
|
| |
|
|
| |
The two "refuses when..." tests failed intermittently under full-suite load, always in teardown with "rm: cannot remove .git: Directory not empty." A git command in the setup or body armed background auto-maintenance (git runs maintenance run --auto after a commit), which wrote into .git after the test body and raced teardown's rm -rf. Disabling gc.auto and maintenance.auto in the throwaway repo, before any git command can arm them, removes the background writer. Diagnose-not-mask: kill the writer rather than retry the rm. Verified with 30 repeated file runs plus two full-suite runs, all green.
|
| |
|
|
| |
The Justify gate's ticket-quality check now names the absence-phrased open-ended goal as the specific case where acceptance criteria aren't concrete, and routes it to the measurable-acceptance conversion in todo-format.md before work starts rather than into an implementation with no definition of done.
|
| |
|
|
|
|
|
|
|
|
| |
A task phrased as an absence ("find bugs until none remain," "refactor until nothing worthwhile is left," "clean it up") can't be :solo:, because it fails the verifiable-by-the-agent gate. Absence isn't falsifiable, so "done" is a judgment call, which is what :solo: forbids. This kept surfacing (the ai-launcher-hardening task is the live case) with no written recipe for fixing it.
todo-format.md gains a subsection, right after the :solo:/:quick: definitions, on converting a fuzzy goal into a measurable one: bound the surface (enumerate the units, that's the done-set), net the behavior with characterization tests, disposition every finding (fixed / filed / declined-with-reason, never "looked and it's fine"), and gate on an objective floor (static-analysis clean, suite green, coverage of the enumerated surface). The qualifying answer becomes a dispositioned report, not a claim of perfection, and the honest limit stays honest: "no visible bugs" means "every enumerated path passes its characterization set and clears the audit." Once a task carries those criteria it can carry :solo:.
work-the-backlog's keystone defer item now names open-ended goals as a specific, recognizable failure of test-writability, so a mistagged :solo: task with no writable acceptance test is deferred and routed to get criteria rather than guessed at.
start-work has the same Justify/Approach gate and wants the same pointer, but its skill lives outside rulesets, so that edit is a follow-up in its home.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
quality sweep
The rule already said "write a characterization test before refactoring" but not what one is or how many to write per unit. Two additions close that.
testing.md's "Adding Tests to Existing Untested Code" now defines a characterization test as recording what the code actually does, not what it should (Feathers' assert-wrong-read-the-real-value recipe), and requires the same Normal/Boundary/Error set as any unit rather than one happy-path capture. The reason is the useful part: on a characterization test the negative and boundary cases are the bug-finders, because untested legacy code is weakest at the empty input and the missing upstream, and pinning what it currently does there writes the wrong behavior down where it becomes a visible bug. A pinned case that turns out to be a bug graduates from recording current behavior to asserting correct behavior. In-unit bugs fall to this set. Composition bugs need a functional test.
The refactor-for-testability section gains the framing that when the untestable code is legacy you're hardening, extracting the pure core is the hardening, not a detour around it. A function you can't characterize without mocking tmux or git can't be refactored safely, so "needs too much mocking" is the signal to reshape it, never a license to skip the boundary and error cases.
code-quality.org's passes claim to preserve behavior, so it now states the precondition that makes that true: on untested scope, a characterization net comes first, or "behavior-preserving" is an assertion the green suite can't actually verify.
|
| |
|
|
| |
Both shipped this session (a143679 apkg converter, a14e43b multi-tag reconcile). Converted to dated event-log entries per the depth rule.
|
| |
|
|
|
|
|
|
|
|
| |
Craig wanted a curated "DeepSat Fundamentals" deck: the ~100 most fundamental cards out of the 465-card deepsat.org deck, marked with a second org tag (:fundamental:) so they stay grep-able in the source. Both flashcard-to-anki.py and flashcard-stats.py keyed a card on a heading ending in exactly :drill:, so adding any second tag turned the heading into :fundamental:drill:, which that anchor didn't match. The 100 tagged cards would have silently dropped from the full-deck apkg and been undercounted by stats. That's the passing-gate-skips-your-file failure.
CARD_RE now matches a trailing org tag block, and a heading is a card when drill is among its tags. The other tags ride along as Anki tags next to the section tag. Card bodies are bounded by any L1/L2 heading (HEADING_RE) rather than only a section or a drill heading. flashcard-to-anki.py gains --tag-filter <tag> (emit only cards carrying that org tag, the subset deck) and --guid-salt <s> (a distinct GUID space so the subset imports non-empty instead of colliding with the full deck on GUID and importing empty). No salt is unchanged, so the existing deck's GUIDs and SRS state are untouched. flashcard-stats.py gets the same CARD_RE/HEADING_RE broadening plus a drill-membership guard.
This is the rulesets-canonical reconcile of a stopgap the work project ran locally. I re-derived it against the current canonical rather than copying the preserved files, which predate the #+TITLE deck-name fix (060a938) and would have reverted it.
parse()'s third element is now the Anki-tag list rather than a single tag string. Only build() and the tests consumed it. The existing parse tests move to the list contract, and new tests cover multi-tag parsing, --tag-filter, the guid salt, and the stats count. A bats case guards that a :fundamental:drill: card still counts. The full deck's real 465/100 check needs the work deck and isn't runnable here. Verified end to end instead on a synthetic multi-tag deck (2 cards, --tag-filter fundamental returns 1) through the real genanki path.
|
| |
|
|
|
|
|
|
|
|
| |
The flashcard pipeline was one-directional (org-drill to apkg). A deck curated on the phone, or an orphaned apkg whose org source was never saved, had no way back into the org source of truth. apkg-to-orgdrill.py closes the loop.
An apkg is a zip holding an Anki sqlite collection, so reading it needs no third-party library: stdlib zipfile + sqlite3. genanki is only needed to write apkgs, not read them, so the converter has no runtime deps. It mirrors flashcard-to-anki.py inverted: deck name to #+TITLE, note Front to the ** heading, note Back HTML to the body (<br> to newlines, entities unescaped, the answer <hr> stripped), the note tag to a * section, and a fresh :ID: UUID per card so the output is org-drill-valid.
The entity unescape runs in the order that inverts escape_html, which escaped & first, so & is unescaped last here. Otherwise a literally-escaped < in the source would wrongly collapse to <. Only Front/Back note types convert; a cloze or other model is skipped with a warning rather than silently dropped, and a note referencing media is flagged since the org side has no media path.
17 tests cover the HTML-to-org conversion, the apkg read (single and multiple decks, Default-deck exclusion, non-basic skip, .anki2 vs .anki21, media flag), the org emission, and errors. The round-trip test closes the loop through flashcard-to-anki.py's own parse(): a known org source becomes an apkg fixture, converts back, and the recovered (front, back, tag) tuples match the original. Verified end to end against a real genanki-produced apkg too.
|
| |
|
|
|
|
|
|
| |
interaction.md and desktop-capture.md told the agent to launch a viewer with google-chrome-stable ... & or hyprctl dispatch exec "imv ...". Both files already noted the failure mode: a coding-agent shell reaps an ordinary background child after it returns, so the window dies before Craig sees it.
gui-open (dotfiles a34d479, on PATH) closes that. It detaches through systemd-run --user so the shell can't reap it, resolves the current Hyprland instance after a compositor restart, and confirms a mapped, visible client before returning. Both files now launch through it — gui-open <path> auto-detects image vs HTML, with --image/--browser to force. The separate-workspace guidance stays. gui-open replaces only the launch, which was the fragile part.
Proposed by archsetup after shipping the tool. It's dotfiles-owned, not a rulesets script, so this is guidance only; a machine without it needs a dotfiles pull.
|
| |
|
|
|
|
|
|
| |
The sentry live trial surfaced this. A sentry inbox-zero pass running from the work project parked the whole 19-item roam inbox as a cross-project boundary crossing and refused to tidy it unattended, reading knowledge-base.md's work-denylist as "don't touch roam from work."
That over-reads the rule. The denylist is a confidentiality guard on one thing: promoting a durable fact into a new agents/ node, so work-confidential material stays out of the personal cross-machine store. Roam is a shared resource, not another project's product scope. Reading it and tidying the shared roam inbox are housekeeping any project may do, work included.
knowledge-base.md gains a "Scope of the denylist" paragraph stating that and naming the mis-park as the error it closes. sentry.org's inbox-zero pass and inbox.org's roam mode each get a one-line note pointing at the rule, so a future agent doesn't re-derive the refusal. Only durable-node promotion stays work-denylisted.
|
| |
|
|
|
|
| |
Phase 4. All four phases shipped and the suite is green, so the spec keyword moves DOING to IMPLEMENTED with a dated history line and the Metadata Status mirrors it. The build task and its phase sub-tasks close (the sub-tasks become dated event-log entries per the depth rule).
The overnight live trial is the one piece an agent can't run, so it's filed as a structured manual-testing task: entry gates with Craig present, one fire observed end to end, the wrap-up guard and stop-sentry, and the morning branch review. Its findings become follow-up tasks rather than gating the flip.
|
| |
|
|
|
|
|
|
|
|
| |
Phase 3 of the sentry supervisor: reconcile the existing roam writers and wrap-up so sentry's locks actually guard something, and its shutdown has one enforced entry point.
The roam-write lock only helps if every roam writer takes it, so both writers now do. knowledge-base.md's write recipe and inbox.org core §5 acquire the roam-write lock around their edit and trigger roam-sync instead of committing themselves. That closes a gap the one-git-owner rule already implied but the KB recipe still violated: the recipe told agents to run git add -A && commit && push against a tree that's chronically dirty from live captures, which could sweep an in-flight capture into a stray commit. roam-sync stays the roam repo's only committer. Agents edit-plus-trigger under the lock, and roam-sync.sh's header now states that contract instead of the old "agents commit inline" note.
Both writers degrade as the spec settled: an absent agent-lock proceeds unlocked (today's behavior), and only a present helper reporting the lock busy after its bounded wait defers or surfaces.
wrap-it-up.org gains a Step 0 that refuses while sentry is live. It checks the single-runner lock and points at "stop sentry" rather than archiving the anchor and tearing down the buffer under a still-firing loop. Both files derive the lock name the same way (sentry-<repo-basename>), so the guard and the engine agree. triage-intake.org notes that it also runs as sentry's triage pass under the no-approvals contract, with its trigger phrases unchanged.
|
| |
|
|
|
|
|
|
|
|
| |
Phase 2 of the sentry supervisor. sentry.org is the engine: an interval loop (default hourly) that walks a fixed pass list (roam pull, inbox zero, triage, todo cleanup, task audit, working-files hygiene, spec board, link integrity, git health, prep freshness) and commits each writing pass to a throwaway sentry/<date>-<host> branch. Nothing pushes. The morning teardown (review, squash-merge, delete) stays Craig's.
The file owns the whole engine: the :COMMIT_AUTONOMY: entry ticket, the interactive entry gates (clean tree, green suite, prior-branch check, ff-only reconcile) that run with Craig present, the single-runner and roam-write locks via agent-lock, the probe-work-session-context-commit pass contract, the digest and morning-approval queue, the skip-not-degrade safety model, the conditional fire-end suite, the multi-day stall notification, and the stop-sentry shutdown.
Every one of the spec's ten decisions and twelve review findings is reflected: host-suffixed branch so the two daily drivers don't collide, tmpfs lock homes so a lock never rides roam-sync to the other machine, roam-sync kept as the roam repo's only committer, spine-excluded dirty checks so sentry's own bookkeeping can't trip its safety skip, and the queue-don't-execute rule for every judgment or destructive action.
Phase 3 wires the roam writers (inbox.org, knowledge-base.md) to acquire the roam-write lock and adds the wrap-it-up guard. Until then the engine is documented but the companions don't yet cooperate. INDEX.org lists it under Tasks and planning.
|
| |
|
|
|
|
|
|
|
|
| |
Phase 1 of the sentry supervisor workflow. sentry needs two locks: a single-runner lock so overlapping /loop fires don't stack, and a roam-write lock so agent edits to the shared roam tree serialize. flock can't serve either, because every Bash call an agent makes is its own short-lived shell, so an flock dies with the call that took it.
agent-lock persists the lock on disk between calls. An atomic mkdir is the acquire. A metadata file inside records pid, host, ISO timestamp, and TTL. Staleness is age-based on the meta mtime, so a crashed holder's lock expires instead of wedging every later acquire, and every reclaim surfaces a note rather than clearing silently. refresh re-touches the mtime as a heartbeat, release is idempotent, status reports free/held/stale, path prints the resolved dir. Locks live under XDG_RUNTIME_DIR/agent-locks (tmpfs: host-local, out of every repo, cleared on reboot), with a ~/.cache fallback where no runtime dir exists.
Reclaim claims the stale dir with an atomic rename before removing it, so two acquirers that both see a lock stale can't both win. A plain remove-then-mkdir would let the loser delete the winner's fresh lock and double-acquire, defeating the roam-write lock's purpose.
Nothing calls it yet. 18 bats tests cover fresh win, contention, release, stale reclaim, heartbeat, path resolution, and usage errors.
|
| |
|
|
| |
Craig approved the sentry spec after his deep read. Flipped DRAFT → READY → DOING with dated history lines, mirrored the Metadata status, and decomposed the build task into the four implementation phases under a SPEC_ID-bound parent.
|
| |
|
|
|
|
|
|
| |
Two batched changes to the todo hygiene tooling.
Dated-seal archiving: --archive-done aging now keeps one month in-file (retain default 7 → 31, named as tc-archive-retain-days-default) and a new --seal renames the working task-archive.org to resolved-YYYY-MM-DD.org beside it, letting the next aging start a fresh working file. Dating the sealed file by the seal run rather than the calendar quarter is what avoids mislabeling a task closed late in a quarter and archived after the boundary. The unparseable-CLOSED case is now explicit in the contract: a keyword-complete task with no readable close date ages out too. The sealed file inherits the todo file's gitignore status.
Planning-line strip: --convert-subtasks now drops the whole planning line when it rewrites a done sub-task to a dated event-log entry, not just the CLOSED cookie. A dated-log heading carries its date in the heading, so an active SCHEDULED/DEADLINE left on it pins the finished entry to the agenda as weeks-overdue forever (org renders any active planning timestamp, keyword or not). New lint-org checker dated-log-heading-active-timestamp backstops any that predate this. todo-format.md's sub-task and VERIFY completion rules gain the strip step.
|
| |
|
|
| |
Added claude-templates/bin/install-ai, a thin launcher that resolves its own path through the symlink chain and execs scripts/install-ai.sh. make install's bin loop symlinks it into ~/.local/bin/install-ai, the same mechanism that links ai and agent-page, so the fresh-project bootstrapper now runs as install-ai from anywhere. The symlink always points at the canonical, so no separate dotfiles copy is needed. Three launcher bats cover the wrapper, including invocation through a symlink from another directory (the case a naive dirname "$0"/.. would break).
|
| |
|
|
| |
The elisp bundle installs coverage-summary.el into .claude/scripts/, which is gitignored in code projects, so CI can't run make coverage-summary against it. That's intentional (Craig, 2026-06-28): it stays a developer-run check, not a CI gate. Stated the local-only status in the script's commentary header and in elisp-testing.md's coverage section so the gitignored install reads as by-design rather than a gap.
|
| | |
|
| |
|
|
| |
Filed as [#B] work items with their origin proposals preserved under docs/design/: the todo-cleanup dated-seal archiving model, the dated-log planning-line strip plus its lint backstop, a hook to enforce the task-boundary inbox check, and home's colloquialisms / "the list" before-close-queue convention. The first two both touch todo-cleanup.el and are cross-linked to build as one batch. The last two carry the design worked out this session so they start warm.
|
| |
|
|
| |
The roam KB bare repo moved off the publicly-browsable path to a private one. The origin is now cjennings@cjennings.net:git/roam.git, and the old URL in this file was stale.
|
| |
|
|
|
|
|
|
| |
New stdlib-only upcoming_birthdays.py reads ~/sync/org/contacts.org for :BIRTHDAY: properties and prints a daily-prep block: name, date, days-away, and the age turned when the birth year is known (1900 is the org-contacts unknown-year placeholder, rendered date-only). Birthdays inside 7 days come back flagged so a gift or plan gets prompted. 19 pytest cases cover the window boundaries, leap-day fallback to Feb 28, the placeholder year, and the CLI.
daily-prep gains a Phase A source that runs the script and a Heads-Up line that folds in the block, so contact birthdays the calendar doesn't carry still surface.
Built and tested in the home project. Promoted here because the daily-prep hook lives in this synced workflow and the contacts file is present on every machine.
|
| |
|
|
|
|
| |
Phase C now renders a TASKS / FYI / MISC digest with a two-option close offer. The per-source breakdown and the itemized action menu move to on-request. Phase D becomes the close and runs by default after the digest: file the TASKS items, run mail hygiene on every scanned account without itemized confirmation, advance the sentinel, tear down anything the sweep started. Prose sends under my name and destructive non-mail actions stay gated. The reroute modifier delivers outside-project items to their owners via inbox-send. The stay-open-until-confirmed exit loop is retired. I updated daily-prep's Sub-step 3b to match.
I ratified this in the work project on 2026-07-18 and applied it here as sent from that handoff (inbox 2026-07-18-1730-from-work).
|