aboutsummaryrefslogtreecommitdiff
Commit message (Collapse)AuthorAgeFilesLines
* docs(start-work): catch open-ended tasks at the ticket-quality gateCraig Jennings4 days1-1/+1
| | | | 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.
* docs(todo-format): add the measurable-acceptance pattern for open-ended tasksCraig Jennings4 days3-2/+44
| | | | | | | | | | 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.
* docs(testing): sharpen characterization-test guidance, wire it into the ↵Craig Jennings4 days3-0/+51
| | | | | | | | | | | | 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.
* chore(todo): close the two flashcard speedrun tasksCraig Jennings4 days1-14/+4
| | | | Both shipped this session (a143679 apkg converter, a14e43b multi-tag reconcile). Converted to dated event-log entries per the depth rule.
* feat(flashcard): support multi-tag drill headings + --tag-filter / --guid-saltCraig Jennings4 days12-60/+408
| | | | | | | | | | 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.
* feat(flashcard): add apkg-to-orgdrill.py, the inverse of flashcard-to-ankiCraig Jennings4 days4-0/+1100
| | | | | | | | | | 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 &lt; 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.
* docs(rules): use gui-open to show Craig visualsCraig Jennings4 days2-4/+7
| | | | | | | | 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.
* fix(roam): scope the work-denylist to durable KB-node writes, not roam tidyingCraig Jennings4 days5-2/+8
| | | | | | | | 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.
* chore(sentry): flip spec to IMPLEMENTED and close the build taskCraig Jennings4 days2-35/+68
| | | | | | 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.
* feat(sentry): wire the roam writers and wrap-up guard for sentryCraig Jennings4 days10-5/+89
| | | | | | | | | | 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.
* feat(sentry): add the sentry overnight-hygiene engine workflowCraig Jennings4 days4-0/+436
| | | | | | | | | | 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.
* feat(sentry): add agent-lock mkdir-atomic advisory lock helperCraig Jennings4 days4-0/+924
| | | | | | | | | | 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.
* chore(spec): flip sentry to DOING and decompose the buildCraig Jennings4 days2-9/+39
| | | | 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.
* feat(todo-cleanup): dated-seal archiving and planning-line strip on convertCraig Jennings5 days10-79/+746
| | | | | | | | 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.
* feat(bin): launch install-ai from PATHCraig Jennings5 days3-1/+55
| | | | 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).
* docs(elisp): mark coverage-summary.el as a local-only helperCraig Jennings5 days3-3/+15
| | | | 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.
* chore(notes): stamp inbox-process marker for the 2026-07-18 passCraig Jennings5 days1-1/+1
|
* chore(todo): file four backlog tasks from the inbox passCraig Jennings5 days4-0/+224
| | | | 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.
* docs(knowledge-base): update roam origin URL after the repo movedCraig Jennings5 days1-1/+1
| | | | 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.
* feat(daily-prep): add upcoming-birthdays block from contacts.orgCraig Jennings5 days6-2/+704
| | | | | | | | 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.
* feat(triage-intake): adopt three-section digest and close-by-defaultCraig Jennings5 days4-106/+148
| | | | | | 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).
* chore: archive session recordCraig Jennings6 days1-0/+238
|
* fix(spec): file the suite-policy decision under Decisions, not findingsCraig Jennings7 days1-6/+6
| | | | | | | | Decision 10 was sitting at the bottom of Review findings. It's written in Context/Decision/Consequences shape, unlike the Resolved-notes around it, and the finding at "Per-pass commits skip the pre-commit suite run" points at it by name, so the pointer was there and the target was in the wrong section. That one misfiling made both cookies lie. Decisions read [10/10] by hand while org would have computed [9/9]. Review findings read [/], uncomputed, and would have resolved to 13, which was 12 findings plus the stray decision. Moving it and letting org recompute gives [10/10] and [12/12], both live. Nothing was lost. Every decision and finding was present and resolved. But the readiness gate reads those cookies, and a spec about to be graded on them shouldn't carry one that's stale and one that's dead.
* chore(settings): switch the session model to opusCraig Jennings7 days1-1/+1
| | | | | | Craig's call, 2026-07-16. This reverses the fable pin from e91073d rather than drifting into it — the field has flip-flopped four times now, so noting that this one was chosen.
* fix(install-lang): refuse a colliding second bundle instead of clobberingCraig Jennings7 days4-0/+252
| | | | | | | | | | | | Installing a second language bundle into a project silently replaced the first one's config. settings.json and githooks are copied with cp -rT (always overwrite), so installing bash over elisp rewired the validate hook to validate-bash.sh and dropped check-parens from pre-commit, leaving validate-el.sh orphaned on disk. The output said [ok] for both. A project could lose its paren check or secret scan and read the install as success. The guard detects which bundles a project already has, by the same rule fingerprint sync-language-bundle uses, and refuses when the incoming bundle would overwrite a file another one ships. It names each file at risk. FORCE=1 still overrides, and the message says that also re-seeds CLAUDE.md, which is destructive on a customized project. Only three of the five shared filenames actually collide. gitignore-add.txt is appended and deduped, and CLAUDE.md is seed-only, so both compose across bundles already. This doesn't decide whether polyglot projects are supported, and the guard shouldn't be read as "no". A non-overlapping pair still installs: bash ships settings.json and githooks with no coverage fragment, python ships only a coverage fragment, so the two compose today. The real line is overlap, not polyglot, and nothing chose it. The open question, along with the identical coverage target names both fragments define, is filed. home reported this after scaffolding clock-panel with python and typescript, which hit the coverage fragment. The settings.json and githooks cases are worse and hadn't been noticed. No project was damaged: the three bundles that collide aren't doubled up anywhere.
* docs(design): record the subproject-pattern proposal, defer promotionCraig Jennings7 days3-0/+343
| | | | | | | | | | home proposed promoting its subproject pattern (a folded-in standalone project living as a self-contained subdir under a parent's scope) into the rules layer. The pattern reads as sound. I deferred it on evidence rather than merit, and preserved both the proposal and home's worked instance here so the decision has its source. Two findings drove the deferral. It's N=1: across all 27 agent scopes, home is the only project with subprojects, and all nine came from one fold. There's no second instance to say which parts are general and which are home-shaped. The placement also contradicts the proposal's own argument. Rules load into every session of every project, and the doc argues that layer is a tax paid whether or not it's relevant, with depth belonging one open away. At 282 lines it would be the third-largest rule and add ~11% to the layer, so every unrelated project would carry a one-project convention. I want to scout which projects would actually get subprojects, and why, before shaping anything. That evidence decides whether the pattern gets dropped or adjusted. Two things to fix if it ever promotes: the doc cites a privacy-model rule that doesn't exist (the real content is the gitignore-vs-track and public-reachability decision), and about half of it is home's instance rather than rule content.
* fix(githooks): match fixed-case secret tokens case-sensitivelyCraig Jennings7 days4-15/+195
| | | | | | | | | | The secret scan ran every pattern through grep -iE, so AKIA[0-9A-Z]{16} matched any mixed-case 20-char run. Random base64 in an embedded image blob hits that about 6% of the time per 100KB, enough to block a real commit and force --no-verify. takuzu hit it on a PNG sprite. AWS keys are uppercase, sk- keys lowercase, and PEM headers fixed, so those three now match case-sensitively. Only the keyword=value patterns still need -i. I measured both causes first. Across ~10MB of random base64 the old pattern produced 7 matches and would have false-positived 6 of 100 sprite-sized blobs. Case-sensitive matching produced none. takuzu's report also proposed skipping any added line containing ";base64,". I left that out. It guards against an uppercase AKIA run surviving the case fix, which never occurred in the sample and runs about one in a million per blob. The cost is real: minified bundles put a whole file on one line, so a data URI and a live key can share it, and skipping the line hides the key. A test covers that. The three variants share one scan block, so elisp, bash, and go all carry the change. Splitting one grep into two meant a line matching both passes got reported twice, which reads as two separate leaks. awk dedupes it.
* fix(triage-intake): anchor the gmail residue probe to the epochCraig Jennings7 days2-6/+14
| | | | | | | | The residue probe cut off at before:<anchor-YYYY/MM/DD> while the scan started at after:<anchor-epoch>. Gmail's before:<date> excludes the named day, so the anchor day fell between the two queries. Each sweep then advanced the anchor past that window and never looked back, so an evening anchor hid most of a day for good. That's the exact failure the probe was added on 2026-07-08 to prevent. The probe now cuts off at the epoch, matching the scan. I hoisted the epoch rule to cover both anchored queries once rather than twice. I scoped it to the anchor windows so the date-slice walk's deliberate day resolution doesn't read as a bug. home caught it: after acting on 38 messages the probe reported zero while a plain is:unread in:inbox still returned two messages from the anchor day. personal-gmail is the only source pairing an anchored scan with a residue probe, so nothing else needed the fix.
* feat(spec): sentry supervisor workflow spec, review-completeCraig Jennings9 days6-20/+549
| | | | Spec for the overnight hygiene supervisor: ten serialized passes, mkdir-atomic locks with heartbeat under the runtime dir, a host-suffixed unpushed branch, interactive entry gates with no report-only mode, and roam-sync kept as the roam repo's only committer. All 10 decisions and 12 adversarial-review findings are resolved in the file; status stays DRAFT pending a deep read. The build task and three deferrals are filed.
* chore(workflows): drop retired Signal-client name from triage history noteCraig Jennings10 days2-2/+2
|
* fix(workflows): find and transcribe .flac recordingsCraig Jennings10 days2-24/+24
| | | | | | The meeting recorder switched to lossless FLAC on 2026-07-01, and the transcript workflow's scan globbed only .mkv and .m4a, so a .flac recording was silently never found. FLAC now threads through the audio-only path: found by the scan, transcribed natively by AssemblyAI with no ffmpeg extraction, kept on cleanup, and archived with its own gitignore pattern. Adopted verbatim from work's handoff (2026-07-14-0105), diff-verified against the canonical.
* fix(scripts): stop org table helpers rewriting block contentCraig Jennings10 days13-74/+370
| | | | | | Both helpers treated any pipe-led line as a table row, so ASCII art in an example block got mangled into a bordered table (a work file took a 1949-line reformat on 2026-07-09). The scanners now track the open block's type and skip its content. Only the matching end marker closes a block, so a literal end_src quoted inside an example block can't re-expose it. The deeper cause was wrap-org-table's load-time dispatch: it fired when lint-org merely required the library, running the reformatter over files lint-org was only asked to report on. It now dispatches only when named as the entry script. lint-org's CLI is also report-only by default now. Writes require --fix, and the wrap-up workflow and lint command pass it.
* chore: archive completed tasks and close out the day's recordsCraig Jennings10 days4-186/+377
| | | | The archive sweep moves the finished routing feature and the cancelled ntfy proposal into Resolved, todo.org carries the day's review stamps and dated build entries, and the session record lands under its dated name.
* chore(todo): finish the task review — full backlog now currentCraig Jennings10 days1-9/+21
| | | | Eleven tasks reviewed: the ntfy two-way-comms proposal is CANCELLED (home tore that channel down on 07-04; the Signal pager task and agent-page are its living successors), the triage-intake phone-push task is re-pointed at agent-page so its send half is buildable now, coverage-summary's docs-only remainder gained quick and solo tags, and the rest were confirmed as-is. Zero top-level tasks remain unreviewed.
* fix(elisp): validate-el.sh prefers source over stale bytecodeCraig Jennings10 days1-0/+2
| | | | Both emacs invocations in the hook now set load-prefer-newer, matching the project-side Makefile and early-init guards. Without it, the PostToolUse hook after a pull that changed a module ran tests against the module's stale .elc and every test failed void-function on the newly-added symbols. From .emacs.d's 2026-07-13 handoff, repro-verified there; inbox/2026-07-13-1712-from-.emacs.d.
* feat(launcher): bare ai picks the agent before the projectsCraig Jennings10 days3-8/+88
| | | | The interactive flow now opens with a runtime picker: claude first (Enter-Enter preserves the old two-keystroke habit), codex labeled as ChatGPT, and one local:<model> line per ollama model, queried live with a short timeout so a dead server just drops those lines. The pick feeds the existing runtime map, then the annotated project multi-select runs as before. --runtime and AI_RUNTIME skip the question, single-directory mode is unchanged, and --print-runtimes exposes the choice list for the two new bats tests.
* feat(launcher): wire the local runtime — codex --oss over ollamaCraig Jennings10 days3-18/+30
| | | | ai --runtime local now launches codex against the machine's ollama (explicit --local-provider=ollama, model from AI_LOCAL_MODEL, default gpt-oss:120b). Verified end to end with a codex exec completion through the local model. The dependency check probes AGENT_BIN now that AGENT_CMD carries flags. This makes codex-over-ollama the de-facto answer to the runtime spec's first-supported-local-CLI blocker.
* chore(todo): record the verified local-inference stack on the model-floor taskCraig Jennings10 days1-1/+3
| | | | ollama 0.31.2 with the Vulkan backend forced by systemd override (ROCm first-touch loads were unusable at under 25 MB/s), gpt-oss:120b and qwen3-coder:30b verified at 37.9 and 76 tok/s on 100% GPU, four legacy dense models dropped, the orphaned user store purged. The child's remaining work is purely the instruction-following evaluation.
* feat(pager): add agent-page, the runtime-neutral phone pagerCraig Jennings10 days11-14/+188
| | | | The Signal pager reconcile found one pager identity, registered in velox's signal-cli, reachable from other machines only by hand-rolled ssh. agent-page wraps that: direct signal-cli on velox, ssh relay over the tailnet from everywhere else, a desktop-fallback hint when the relay fails, and the UUID target baked in so nobody pages the unregistered phone number again. Four bats tests stub ssh/uname/signal-cli to verify command construction; the real path was verified with a live phone push today. protocols.org's Paging Craig section now teaches both channels (notify for the desk, agent-page for the phone) and demotes signal-mcp to a velox-local nicety; page-me.org, work-the-backlog, and the INDEX carry the same two-channel story. Every project inherits on its next startup sync and make install.
* chore(todo): task-review batch of seven, plus the roam-capture filingsCraig Jennings10 days1-2/+20
| | | | Signal pager re-graded C to B: the runtime-portability inventory showed the claude.ai-side signal-mcp is the only paging path, home currently has no live channel at all, and off-Claude agents have none, so the doc-and-reconcile task now gates real capability rather than tidiness. install-ai-on-PATH tagged quick and solo. The launcher-hardening task and the agent-switching fold-in came from today's roam capture. The other five reviewed tasks were confirmed as-is.
* docs(runtime): session plumbing assessed, no build neededCraig Jennings10 days2-2/+13
| | | | session-context-path already carries a runtime segment, the anchor cycle is plain files, and self-inject is harness-agnostic with only its payload Claude-shaped. A codex auto-flush is a documented payload variant whose second injected line carries the resume instruction, replacing the SessionStart hook. Wire it when a codex session first wants it.
* feat(launcher): add ai --runtime for choosing the agent CLICraig Jennings10 days3-16/+146
| | | | The ai launcher can now start codex sessions: --runtime claude|codex (or AI_RUNTIME), a runtime-to-CLI map with local reserved behind a clear error until the local-model evaluation picks a CLI, and a runtime-aware dependency check. A new --print-launch mode prints the pane launch command without touching tmux or fzf, and is the seam the six new bats tests drive. Both current CLIs take the opening line as a positional prompt, so only the command name varies.
* feat(runtime): add the thin-pointer AGENTS.md agent entry fileCraig Jennings10 days8-4/+119
| | | | claude-templates/AGENTS.md points any harness at protocols.org, the rules locations, and the /name-to-file resolution rule, and tells it to degrade per each rule's documented fallback instead of skipping gates. make install links it to ~/.codex/AGENTS.md (new CODEX_DIR stanza) and install-ai.sh seeds a project-owned copy at bootstrap, never overwriting an existing one. The rulesets root carries a tracked symlink as dogfood. Five new bats tests cover the link idiom and the seeding. This resolves the generic-instruction-file blocker from the runtime spec, and velox picks up the global link on its next session's make install.
* docs(runtime): skill parity resolves to one bootstrap resolution ruleCraig Jennings10 days2-2/+17
| | | | All 29 skill and command bodies are markdown, so a single sentence in the future non-Claude entry file (a /name reference resolves to the skill or command file, read and follow it) carries the whole library. Auto-invocation degrades to by-name, which the publish flow already uses. flush stays Claude-only, owned by the session-plumbing child.
* feat(runtime): complete the hook, MCP, and memory portability inventoriesCraig Jennings10 days3-7/+71
| | | | The new inventories doc maps what's actually Claude-bound. Hooks: only PreCompact and Stop-teardown carry porting decisions, both now decided (prose downgrade, Codex notify or manual). MCP: everything ports mechanically except paging, since signal-mcp exists only claude.ai-side, so the Signal-pager task gains runtime portability as a second motivation. Memory: the KB is already the cross-agent store, and knowledge-base.md now tells agents without harness memory to capture into the session log. Three inventory child tasks closed as dated entries under the generic-agent-runtime parent.
* fix: pin the session model to fable as intendedCraig Jennings10 days1-1/+1
| | | | d5bc9b3 was meant to keep the opus-to-fable flip but recorded only a case change (opus to Opus) — the harness had rewritten the file before staging. This lands the value Craig actually chose.
* chore(todo): file runtime-portability child tasks and stamp a reviewCraig Jennings10 days1-0/+28
| | | | Eight child tasks under the generic-agent-runtime parent decompose what's left to run a non-Claude agent (Codex-style or local LLM) in this system: instruction bootstrap, skill parity, hook parity, launcher flag, session plumbing, memory story, MCP portability, local model floor. Each names the spec blocker decision it feeds. Also stamps the build-to-prototype task's review.
* chore(todo): fold home's Signal-pager status into the pager doc taskCraig Jennings10 days1-0/+3
|
* chore: switch session model from opus to fableCraig Jennings10 days1-1/+1
|
* feat(rules): add visuals-display rule for EAT and a zsh word-split noteCraig Jennings10 days3-0/+23
| | | | interaction.md gains a Showing Craig Visuals section: EAT renders SendUserFile and inline images as a text line, so visuals open in the browser or imv (per desktop-capture.md), with inline sixel as the quick-glance lane behind a capability gate (patched EAT answering CSI 14 t plus tmux terminal-features sixel, diagnosed 2026-07-13). protocols.org gains the zsh no-word-split gotcha next to the exa alias note. takuzu sent the proposal as inbox handoff 2026-07-11-2137; the sixel findings correct its capability claim.