| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
| |
Craig proposed a new pattern from the work session: a sentence that makes the reader translate more than one specialized term is too dense and gets rewritten. He supplied the rule and a worked before/after and left number, placement, and mode to me.
Prepared as #48 in general mode, since it's a universal clarity rule rather than a Craig-voice trait. The one structural call is in the VERIFY: general mode was a clean "patterns #1-31," and a general pattern at #48 breaks that, so the diff updates every enumeration to "#1-31 and #48." Both files and the diffs are staged under working/, ready to apply on his word.
|
| |
|
|
|
|
|
|
| |
Adds pattern #47: in a reply, lead with what matters most to the recipient, not the easiest answer or the order they wrote it. Correspondence-scoped, so it fires on email, Signal, and letters and nowhere else. It's the one pattern that doesn't carry into personal mode, since a commit or PR review isn't a reply to someone's news.
Recalibrates #43 in the same edit. "Shifts angle" now means shifts topic: break between topics, consolidate within one, up to a five-or-six-sentence ceiling. The never-merge line was being read at sentence granularity, which fragmented single topics across paragraphs. #43 and #47 are one rule seen from both sides, the break between topics and the order of them, not a rule in tension with its neighbor.
Both land in SKILL.md and the profile per the pairing rule, with the worked before/after in the profile. From a home session drafting a Signal reply; the design is Craig's.
|
| |
|
|
|
|
|
|
| |
Craig's roam capture reached me a second time, relayed from another project, and the sender asked what already existed before treating it as new. Three of its four asks had already shipped, one of them earlier today from the same capture.
The remaining ask is an automatic sweep of a closed task's working/ files into temp/. I'm recommending against it. temp/ is cleared at the next wrap, so the sweep would destroy the artifacts that working-files.md says to rename and file into assets/. Filing is also the moment you decide what each artifact is worth keeping as, and automating it skips that.
The real gap is smaller: orphan detection only runs during a sentry fire, so a project that never runs sentry never hears about it. The parked diff adds report-only detection to wrap-up.
|
| |
|
|
|
|
|
|
|
|
| |
The python and typescript bundles carried rules and a coverage script but no pre-commit hook, so any project installing one got no credential scan on commit. Both now ship all four components the README documents: the shared secret scan, a validate-on-edit hook, settings wiring, and a seed CLAUDE.md. Verified against a real repo: a commit carrying an AWS key is refused.
install-lang now warns when a bundle is missing a documented component. That's the half that keeps this from recurring. Whoever adds the sixth bundle will forget something too, and today the installer prints success either way.
Two things fell out of the build. node --check is unusable on TypeScript: it ignores --experimental-strip-types, so it rejects valid TS and accepts broken TS. The hook uses tsc filtered to syntactic diagnostics instead. And completing the bundles means every pair now collides on settings.json and pre-commit, so no two compose without FORCE=1. The earlier "bundles already compose" reading rested on these two being incomplete. Filed for a real decision; clock-panel is the project that wants it.
Also stamps :LAST_REVIEWED: at task creation, with a lint checker to catch misses. Writing a task is reviewing it, so leaving the stamp off pushed every fresh task to the top of the next review batch to be re-derived by someone with less context than its author had. Tonight's sweep sent the staleness count from 13 to 22 while the list got more accurate.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
| |
Overnight hygiene sweep of this repo. Everything here is a finding or a staged proposal. No rule, workflow, script, or bundle file was modified.
The one that matters: the python and typescript language bundles ship no pre-commit hook, so a project installing either gets no credential scan on commit. Both bundles predate the hook rollout that swept go and bash, and install-lang skips missing components without a word, so it went unnoticed for two months. Live on two projects, one of them work. Filed [#A].
Seven more findings landed. The largest is a non-atomic write in the cross-project inbox tool, which can strand an empty handoff in another project's inbox and block a turn there. The rest are lint noise and two gaps in the Signal channel. An eighth was filed and then retracted the same night, once a retest showed I had compared two different files and read the difference as a bug.
Four proposals from other projects are staged under working/ with verified diffs, each behind a VERIFY task, waiting on a decision.
|
| |
|
|
|
|
|
|
|
|
| |
Sentry no longer checks email or messengers. Pass 3 loads only the calendar and PR/ticketing triage plugins. The mail and messenger ones stay out: cmail, Gmail, Telegram, Signal, chat DMs. A manual triage intake still scans everything. The probe follows, so a project whose only declared sources are mail or messengers now skips the pass instead of running it empty.
New pass 11 hunts bugs. It runs linters and static analysis, checks config sanity, and reads one rotating code area per fire so coverage builds over a night. Verified findings get filed as graded bug tasks. It never fixes unattended.
I dropped the suite run the order called for. Running the suite every fire is the per-pass anti-pattern this same file forbids, and the entry baseline already has the result.
The KB personal-project roots now cover ~/.dotfiles, which was classifying Unknown and blocking writes from there.
|
| |
|
|
| |
Moved 5 completed level-2 tasks into Resolved. Logged one review-health followup: a top-level task has gone unreviewed past 30 days.
|
| |
|
|
|
|
|
|
| |
Auto mode is the away-from-desk mode. A sweep worth seeing should reach Craig's phone, not just print into a session he isn't watching. I folded a Phone delivery subsection into canonical triage-intake.org: a full-three-section sweep now pushes to the phone over Signal via agent-text. A pointer from End-of-sweep output and a Living Document note tie it in.
Signal-only, by Craig's ruling: a quiet sweep's "nothing" heartbeat never reaches the phone. Silent-until-signal governs the phone channel too, so it only fires on real signal. The in-session heartbeat stays as proof the loop ran.
Only the send half ships. Reply polling (the retired ntfy design's phone-recv) waits on the reply-correlation follow-up: a Signal reply fans out to every linked device and neither knows which page it answers. That spec owns the recv wiring. I re-pointed the transport off dead ntfy onto agent-text (the renamed pager). The mirror is synced and the DONE task records the deferral.
|
| | |
|
| |
|
|
|
|
| |
Craig's directive from the 2026-07-20 archsetup session: no sentence in a publish artifact carries more than two commas, enforced in the /voice personal pass. The third comma almost always marks stacked clauses or an inline list doing a paragraph's work. It's a mechanical count the walk can enforce where "don't stack clauses" gets skipped.
Scoped to publish artifacts, so the personal-only set goes four to five. Added to the attestation high-recurrence set at birth: a comma count is cheap to receipt, and new discipline fails silently without one. The profile section carries the before/after from the Hyprland issue draft that prompted the rule.
|
| |
|
|
|
|
| |
"page me" and "text me" now name distinct channels: page is the desktop notification, text is the Signal phone push, and "text and page me" fires both. Before, "page me" defaulted to desktop and the phone was an unnamed "on my phone" variant, so you couldn't name the phone without naming the machine.
I renamed the Signal tool agent-page to agent-text to match, with a deprecated agent-page shim delegating to it so callers and other machines don't break before their next install. The dispatch and tests are otherwise unchanged. I rewrote protocols.org "Reaching Craig", page-me.org, and work-the-backlog's away-run logic around the three phrasings, and updated the runbook.
|
| |
|
|
|
|
|
|
| |
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.
|