From 6c779188fba30614f458cdd29d4efc6514eee2ef Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Wed, 24 Jun 2026 16:18:29 -0400 Subject: chore: refresh synced agent rules and hooks --- .claude/hooks/validate-el.sh | 1 + .claude/rules/cross-project.md | 2 +- .claude/rules/daily-drivers.md | 49 +++++++++++++++++++++++++++++++++++++ .claude/rules/emacs.md | 6 +++++ .claude/rules/interaction.md | 4 +-- .claude/rules/todo-format.md | 55 ++++++++++++++++++++++++++++++++++++++++-- .claude/rules/triggers.md | 6 ++--- .claude/rules/working-files.md | 2 +- 8 files changed, 116 insertions(+), 9 deletions(-) create mode 100644 .claude/rules/daily-drivers.md diff --git a/.claude/hooks/validate-el.sh b/.claude/hooks/validate-el.sh index 2529fccb8..8e464577a 100755 --- a/.claude/hooks/validate-el.sh +++ b/.claude/hooks/validate-el.sh @@ -104,6 +104,7 @@ if [ "$count" -ge 1 ] && [ "$count" -le "$MAX_AUTO_TEST_FILES" ]; then -L "$PROJECT_ROOT/tests" \ -L "$PROJECT_ROOT/themes" \ --eval '(package-initialize)' \ + --eval "(cd \"$PROJECT_ROOT/tests\")" \ -l ert "${load_args[@]}" \ --eval "(ert-run-tests-batch-and-exit '(not (tag :slow)))" 2>&1)"; then # Terminal gets a compact summary (the run tally + the failing test names); diff --git a/.claude/rules/cross-project.md b/.claude/rules/cross-project.md index ed4a19c5e..caceec9bd 100644 --- a/.claude/rules/cross-project.md +++ b/.claude/rules/cross-project.md @@ -35,7 +35,7 @@ Two acceptable outcomes: ``` Output filenames follow `YYYY-MM-DD-HHMM-from--.` automatically, so the target's next session sees the source + timestamp at a glance without you having to construct the name. Fall back to `Write`/`Edit` only when the script isn't available (e.g. a freshly-cloned project before the first startup-rsync). -2. **"Switch projects"** — stop. Let the user reopen Claude in the right cwd. +2. **"Switch projects"** — stop. Let the user reopen the agent session in the right cwd. Don't assume which one was meant. Either guess is wrong half the time and the cost of asking once is one short turn. diff --git a/.claude/rules/daily-drivers.md b/.claude/rules/daily-drivers.md new file mode 100644 index 000000000..eeda33fd5 --- /dev/null +++ b/.claude/rules/daily-drivers.md @@ -0,0 +1,49 @@ +# Daily-Driver Machines + +Applies to: `**/*` + +Craig runs exactly two daily-driver machines: **ratio** and **velox**. They are +kept in sync, and an important change made on one usually needs to reach the +other. + +## The Rule + +When you make or notice a change that is **machine-level and important** — +dotfiles, installed tooling, a synced repo's clone or timer setup, a global +config, a systemd unit, a credential, a one-time bootstrap step — consider +whether the *other* daily driver needs the same change, and flag it. Don't +assume a change made on the current machine is live everywhere. + +This is a prompt to think, not a script to run. The agent can't reach the other +machine; the point is to surface "the other daily driver may need this too" at +the moment the change lands, so it doesn't silently drift to one box. + +## How the sync actually happens + +The mechanism depends on what changed: + +- **A tracked repo** (rulesets, dotfiles, a project) — the other machine just + needs a `git pull` (and, for rulesets, a `make install` to relink anything + new). Most changes are this. +- **Dotfiles** — ride the dotfiles repo; the other machine picks them up on its + next stow/pull. +- **A one-time setup** — a new repo clone, a new systemd timer, a freshly + installed tool, a credential — has to be done by hand on each machine. These + are the ones that silently drift, because nothing carries them automatically. + +When the change is the one-time kind, say so explicitly: name the manual step +the other machine still needs. + +## Knowing which machine you're on + +`uname -n` returns the hostname (`ratio` or `velox`). Use it when a reminder is +machine-specific ("on ratio, you still need to …") so the note is actionable +rather than abstract. + +## Current open instance + +The org-roam knowledge-base clone — `git@cjennings.net:roam.git` — plus its +`roam-sync` systemd timer is confirmed set up on **velox**. It still needs +verifying (clone + timer) on **ratio**. This is the last piece before the +"memory sync across machines" work closes (tracked in the rulesets `todo.org`). +Clear this line once ratio is confirmed. diff --git a/.claude/rules/emacs.md b/.claude/rules/emacs.md index 702b40e7b..ae4f7cb2b 100644 --- a/.claude/rules/emacs.md +++ b/.claude/rules/emacs.md @@ -27,3 +27,9 @@ This re-evaluates the file and redefines its `defun`s live. For straight functio 3. Verify: for visual changes, screenshot and read it (the `screenshot.py` tool under `.ai/scripts/` can capture an app off-screen on a headless output); for behavior, eval or exercise it. This replaces the quit → relaunch → re-find-and-load-files cycle for most edits. A real restart stays the gold standard for a guaranteed-clean state — anything touching `:config`, load order, or when in doubt. + +## Don't edit on disk a file the daemon is capturing into + +The reload caveats above are about pushing changes *into* the daemon. The inverse hazard: a tool that edits a file *on disk* while the daemon has an indirect buffer cloned from it. org-capture works through such a buffer, and a disk write (a hand edit, a `git pull` that fast-forwards the file, a `sed`/Write) reverts the base buffer underneath the capture. The capture is left on stale state, can no longer finalize with `C-c C-c`, and a freshly-typed item can be lost or written back against post-edit content. Orphaned `CAPTURE-*` buffers piling up as Craig retries is the visible symptom. + +The roam inbox (`~/org/roam/inbox.org`) is the live case — Craig captures into it constantly, and the inbox workflow's roam mode (Phase D) edits it. Before a disk write to a file the daemon may be capturing into, check first: `.ai/scripts/capture-guard ` exits non-zero (and names the buffer) when a live capture is cloned from ``, and exits 0 — safe — when there's no capture or no reachable Emacs. Same principle as the reload rule, one layer out: leave the daemon's live buffers authoritative rather than yanking the file from under them. diff --git a/.claude/rules/interaction.md b/.claude/rules/interaction.md index 1fd0334ff..9148b4ffd 100644 --- a/.claude/rules/interaction.md +++ b/.claude/rules/interaction.md @@ -2,11 +2,11 @@ Applies to: `**/*` -How Claude communicates with the user during a session — choice prompts, status updates, decision points. +How the agent communicates with the user during a session — choice prompts, status updates, decision points. ## No Popup Menus for Choices -When Claude needs the user to pick between options, **do not** use the AskUserQuestion popup. Present the options inline in chat as a numbered list and ask the user to reply with a number. +When the agent needs the user to pick between options, **do not** use the AskUserQuestion popup. Present the options inline in chat as a numbered list and ask the user to reply with a number. **Why:** The popup menu UI sits at the bottom of the chat window and obscures the chat content directly above it — exactly the area the user needs to read to make the choice. Inline numbered options keep the question, the surrounding context, and the proposed text all visible in the same scrollback. diff --git a/.claude/rules/todo-format.md b/.claude/rules/todo-format.md index b9e93bb5a..5c3496690 100644 --- a/.claude/rules/todo-format.md +++ b/.claude/rules/todo-format.md @@ -24,8 +24,8 @@ guessing: The section is mandatory. A `todo.org` without it leaves `[#A]` and the tags undefined, so task-audit can't enforce a vocabulary, task-review can't grade -against agreed semantics, and process-inbox can't file new tasks correctly -(its Phase B.1 already checks for this scheme). Each project defines the +against agreed semantics, and the inbox workflow can't file new tasks correctly +(its priority-scheme check already gates on this scheme). Each project defines the scheme its own way; the floor is that priorities and tags are both spelled out under the header. @@ -263,3 +263,54 @@ are noise that pollute his `cj:` greps. ** DOING [#A] Kostya's contract :admin:kostya: *** 2026-05-15 Fri @ 14:00:00 -0500 Kostya basis — part-time, 20 hr/week Nerses confirmed 5/15 13:30 CDT: Kostya runs at 20 hr/week part-time, mirroring Vrezh's structure. Plugged into Exhibit A § 2 of the contract draft. + +## Cross-Project Dependency Tags + +A task can be blocked by work that has to happen in a *different project* — a rulesets task that can't finish until `.emacs.d` ships a companion function, say. Left unmarked, two things go wrong: the what's-next workflow keeps recommending the blocked task even though it can't move, and the blocker sits at low priority in the other project, so the dependency stalls silently. + +Two plain org tags track it, one on each side, so neither the waiter nor the blocker loses sight of the dependency: `:blocked:` on the task that's waiting, `:blocker:` on the task that owes the work. The cross-project detail — which project, what work — goes in the task *body*, not a property. This applies to *any* project pair; the convention here and the surfacing in `open-tasks.org` live in the shared rule + workflow layer, not in one project. + +### `:blocked:` — the waiting side + +The task that can't proceed carries `:blocked:`. Its body names the project it's waiting on and what that project owes: + +``` +** DOING [#B] Wrap-teardown feature :feature:blocked: +Blocked on emacsd: needs the ai-term companion functions +(cj/ai-term-quit, -live-count) before the manual validation can run. +``` + +`open-tasks.org` reads the `:blocked:` tag to pull the task out of the "do this next" cascade (it can't be worked) and surface it in a dedicated "Blocked on other projects" section, reading the body for which project to name and nudge. + +### Registering with the blocker — the reciprocal handoff (required) + +Setting `:blocked:` is not complete until the blocking project knows it's blocking. The moment you mark a task `:blocked:` on another project's work, send that project a dependency handoff: + +``` +inbox-send --text "Blocking dependency: 's task \"\" is blocked on you — it needs . It stays blocked until this lands. Tag the owning task :blocker: on your side so it surfaces as priority work." +``` + +This is what closes the gap: without it, the blocker only learns it's blocking by accident. The handoff lands in ``'s `inbox/` and its normal inbox processing tags the work (below). A `:blocked:` task with no matching reciprocal handoff is half-done — the dependency is invisible to the one project that can clear it. Skip the send only when the blocker demonstrably already tracks the work (e.g. it's the same handoff that spawned the dependency); it dedups against an existing task either way. + +### `:blocker:` — the blocking side + +When a project processes a blocking-dependency handoff (inbox process mode), it tags the owning task `:blocker:` and names the requesting project in the body: + +``` +** TODO [#B] ai-term wrap-teardown companion :feature:blocker: +Rulesets' wrap-teardown feature is blocked on this — it needs the three +ai-term functions. Surface first so rulesets unblocks. +``` + +The blocking task does *not* carry `:blocked:` — it isn't blocked, it's the blocker. `:blocker:` is a priority signal: `open-tasks.org` surfaces a `:blocker:` task *first*, since clearing it unblocks work in another project, so a dependency that would otherwise stall at low priority gets pulled forward. This is the "surface dependencies first" half of the design. + +### Resolving the dependency + +When the blocker delivers: + +1. The blocking project completes its `:blocker:` task, drops the `:blocker:` tag, and notifies the waiter (`inbox-send --text "Delivered: — you're unblocked."`). +2. The waiting project drops the `:blocked:` tag; the task is workable again. Either side noticing the delivery can lift its own tag — the notification just makes it prompt. + +### Not the same as VERIFY + +`:blocked:` marks "waiting on another *project's* work"; `VERIFY` marks "waiting on Craig's input." If Craig's input is what's needed, it's a VERIFY, not `:blocked:`. And `:blocker:` only ever sits on the project that *owes* the work, never the one waiting. diff --git a/.claude/rules/triggers.md b/.claude/rules/triggers.md index e45e660a2..a8d5e772c 100644 --- a/.claude/rules/triggers.md +++ b/.claude/rules/triggers.md @@ -8,13 +8,13 @@ Trigger phrases the user can say from any session to invoke a cross-project acti Synonyms: "Launch X", "Open project X", "Switch to project X". -**Action:** run the `ai` script (the Claude Code session launcher, installed at `~/.local/bin/ai`) in single-project mode targeting the named project. +**Action:** run the `ai` script (the agent session launcher, installed at `~/.local/bin/ai`) in single-project mode targeting the named project. ``` ai ``` -The `ai` script handles tmux session creation, window placement, and the per-project Claude opening line — see `~/code/rulesets/claude-templates/bin/ai` for the canonical source. +The `ai` script handles tmux session creation, window placement, and the per-project agent opening line — see `~/code/rulesets/claude-templates/bin/ai` for the canonical source. **Resolving X.** Match against project basenames discoverable by `ai` — directories under `~/code/`, `~/projects/`, and `~/.emacs.d` that contain `.ai/protocols.org`. @@ -22,7 +22,7 @@ The `ai` script handles tmux session creation, window placement, and the per-pro - No match → list all available basenames, ask which to launch. - Multiple partial matches (X is a substring of two or more candidates) → list the matching basenames, ask which. -Do not guess. The cost of asking once is one short turn; launching the wrong project is a wrong-context Claude session that has to be killed and restarted. +Do not guess. The cost of asking once is one short turn; launching the wrong project is a wrong-context agent session that has to be killed and restarted. ## Why a separate file diff --git a/.claude/rules/working-files.md b/.claude/rules/working-files.md index 9a7270271..243226866 100644 --- a/.claude/rules/working-files.md +++ b/.claude/rules/working-files.md @@ -120,7 +120,7 @@ When the task is marked done: - *Inbox content* — `inbox/` and `daily-prep/` follow their own conventions (dated filenames, processed and moved on cadence). -## Implementation Note for Claude Sessions +## Implementation Note for Agent Sessions When the user starts a new task that's going to produce file artifacts: -- cgit v1.2.3