diff options
Diffstat (limited to 'claude-templates/.ai/protocols.org')
| -rw-r--r-- | claude-templates/.ai/protocols.org | 89 |
1 files changed, 78 insertions, 11 deletions
diff --git a/claude-templates/.ai/protocols.org b/claude-templates/.ai/protocols.org index 05f889b..bf9f420 100644 --- a/claude-templates/.ai/protocols.org +++ b/claude-templates/.ai/protocols.org @@ -1,5 +1,5 @@ #+TITLE: Claude Code Protocols -#+AUTHOR: Craig Jennings & Claude +#+AUTHOR: Craig Jennings #+DATE: 2025-11-05 * About This File @@ -84,7 +84,7 @@ Do NOT estimate, guess, or rely on memory. Just run the command. It takes one se Every session pulls rulesets first, then the local project repo. Rulesets carries the canonical behavioral rules and =.ai/= templates (the old =claude-templates= repo is folded in as a subtree at =rulesets/claude-templates/=); the project pull lands commits pushed from other machines or teammates since the last session. -Resolve any dirty-tree or merge issue at each step before moving on. Both pulls run as =git pull --ff-only= (or =git merge --ff-only= against the fetched upstream), so anything non-trivial — non-fast-forward history, dirty working tree, diverged branches — aborts. Surface the state and stop. Never auto-stash, auto-merge, or auto-rebase; the user resolves the conflict before further work. +Resolve any sync-blocking tree or merge issue at each step before moving on. The shared =git-worktree-gate sync-safe= policy permits untracked deliveries beneath =inbox/= so receiving a handoff never prevents another project from refreshing rulesets; every staged or tracked change, dirty submodule, Git operation in progress, or untracked path outside =inbox/= blocks. Both pulls run as =git pull --ff-only= (or =git merge --ff-only= against the fetched upstream), so non-fast-forward history and diverged branches also abort. Surface the state and stop. Never auto-stash, auto-merge, or auto-rebase; the user resolves the conflict before further work. Mechanics live in =startup.org= Phase A.0. The rule lives here because it governs the very first action of every session: load the freshest behavioral rules and templates before anything else runs. @@ -187,6 +187,8 @@ Canonical rule: =~/code/rulesets/claude-rules/cross-project.md=. Every in-progress task that produces files (drafts, source documents, diagrams, scripts, sub-deliverables) gets a dedicated subdirectory under =<project-root>/working/=, named after the task. All artifacts for that task live in that subdirectory until the task is marked done. +=working/= is version-controlled from creation — it's the tracked home of in-progress work, never gitignored. Filing on completion *reorganizes* durable artifacts into permanent homes; it doesn't mark when they became durable (they were durable, and tracked, from the start). Genuinely disposable artifacts go in a gitignored =temp/= (or =/tmp=), never =working/=; the install tooling ignores =temp/= in both track and gitignore modes. + When the task ships, files are **renamed individually** (standard form: =YYYY-MM-DD-<task-slug>-<descriptor>.<ext>=) and **moved flat** into the appropriate permanent home (typically =assets/= or an area-specific =<area>/assets/=). The working subdirectory is then empty and gets deleted. ***Never rename the directory itself as a substitute for filing.*** The point is to keep =assets/= flat-searchable — a nested =assets/old-tech-deck-2026/slide.png= is harder to find than =assets/2026-05-18-tech-deck-vol2-slide-04-diagram.png=. @@ -203,7 +205,9 @@ Check =inbox/= at every task boundary (after finishing a unit of work, before re .ai/scripts/inbox-status -q #+end_src -Exit 1 means handoffs are pending — process them per =process-inbox.org=. For each accepted handoff, the act-vs-file rule: *act now* when it's clear, bounded, low-risk, in-scope, and cheaper than deferring — just do it, no asking; *file* otherwise — ask first, with filing as option 1 and "do it now" as option 2; *ask* if unsure. Exception: a proposal to change a shared asset (template workflow, rule, skill, synced script) or a substantive convention never silently acts now — it goes through process-inbox's Skeptical Review and its approval (or park) step. Always reply to a handoff's sender (confirm on accept, the why on reject). Full process, the reply discipline, and the opt-in background-monitor =/loop= recipe live in =monitor-inbox.org=. +Exit 1 means handoffs are pending — process them per =inbox.org= process mode. For each accepted handoff, the act-vs-file rule: *act now* when it's clear, bounded, low-risk, in-scope, and cheaper than deferring — just do it, no asking; *file* otherwise — ask first, with filing as option 1 and "do it now" as option 2; *ask* if unsure. Exception: a proposal to change a shared asset (template workflow, rule, skill, synced script) or a substantive convention never silently acts now — it goes through the inbox engine's skeptical review and its approval (or park) step. Always reply to a handoff's sender (confirm on accept, the why on reject). Full process, the reply discipline, and the opt-in background-monitor =/loop= recipe live in =inbox.org= monitor mode. + +A machine-global =Stop= hook (=inbox-boundary-check.sh=) backs this rule so it isn't prose-only. When handoffs are pending it blocks the turn once and injects the count, so a task boundary can't pass with items unseen. It soft-nudges rather than hard-blocks: on the harness re-entry it steps aside, so a mid-task pause to ask "what's next" is never hijacked into inbox processing. The rule above still governs what to do with the items; the hook only makes sure you look. ** Recursive Reads — Honor =.aiignore= @@ -242,13 +246,33 @@ Execute the wrap-up workflow (details in Session Protocols section below): 2. Git commit and push all changes 3. Valediction summary +** "Suspend the session" / "Suspend" / "I need to go" / "Stick a pin in everything" + +Execute the suspend workflow ([[file:workflows/suspend.org][suspend.org]]): a capture-only mid-session pause for an abrupt departure. It appends a resume-weighted =SUSPENDED= entry to the Session Log, notes uncommitted work, and LEAVES =.ai/session-context.org= in place so the next startup resumes from it — no archive, no teardown, no valediction. The capture-only counterpart to "wrap it up" (which ends + archives + tears down) and to =/flush= (which prompts =/clear= and resumes the same session). "I need to go" is broad — if it reads as a conversational aside, confirm before suspending. + +* Colloquialisms and Expansions + +Shorthand phrases Craig uses that expand to a defined action the agent applies without asking. The set is extensible: a project may add its own entries, and new shared shorthands land here. + +** "the list": the Before-Close Queue + +"Put X on the list" or "add X to the list" appends X to the Before-Close Queue, a FIFO queue of tasks and actions to finish before the session closes. Work it oldest-first at wrap-up, before teardown (=wrap-it-up.org= Step 1 works it before finalizing the Summary), and surface anything unfinished in the valediction rather than dropping it. + +The queue lives in the session anchor (=.ai/session-context.org=) under a =* Before-Close Queue= heading. Create the heading on the first "put it on the list" if it's absent, then append one line per item. It's session-scoped: it resets when the anchor is archived at wrap. Anything that must outlive the session is a =todo.org= task instead, not a list item. + +** "tell <project> <message>": cross-project handoff + +"Tell <project> <message>" drops the message in that project's =inbox/= via =inbox-send= (=python3 .ai/scripts/inbox-send.py <project> --text "<message>"=), the sanctioned cross-project handoff. Never write another project's =todo.org= or =inbox/= directly. Resolve =<project>= the way =inbox-send= does (basename match, dots stripped); if it's ambiguous, ask which project rather than guessing. + * User Information ** Calendar Management Three ways to access Craig's calendars: Google Calendar MCP (preferred, both personal + work accounts), gcalcli (fallback, personal only), Emacs org files (read-only viewer). -For tool recipes, authentication details, and credentials, see [[file:references/calendar-reference.org][calendar-reference.org]]. +For tool recipes and account details, read the calendar workflows in =.ai/workflows/=: =add-calendar-event.org=, =edit-calendar-event.org=, =delete-calendar-event.org=, =read-calendar-events.org=. They carry the MCP tool names, both account ids, the gcalcli fallback, and the conflict-check discipline. + +Credentials are needed only for a re-auth Craig performs himself. The MCP bundle's =mcp/README.org= in the rulesets repo is the authority: =gcp-oauth.keys.json= is gitignored and regenerated at install from a base64 var in the bundle, never committed. Named in prose rather than linked, because that path isn't synced into consuming projects. ** GPG Keys @@ -352,9 +376,19 @@ Craig runs a pure Wayland setup (Hyprland) and avoids XWayland/Xorg apps. - Clipboard: Use =wl-copy= and =wl-paste= (NOT =xclip= or =xsel=) - Window management: Use Hyprland commands (NOT =xkill=, =xdotool=, etc.) - Prefer Wayland-native tools over X11 equivalents -- Open URLs in browser: Use =google-chrome-stable "URL" &>/dev/null &= - - The =&>/dev/null &= is required to detach the process and suppress output - - Without it, the command may appear to hang or produce no result +- Open URLs in browser: invoke Chrome directly — never =xdg-open=, which returned success in a home session on 2026-07-26 while no tab appeared. + + Chrome is normally already running, and in that case it hands the URL to the live session and exits immediately (rc 0), printing =Opening in existing browser session.= on *stdout*. So run it in the foreground and read that line as the confirmation the tab actually opened: + + #+begin_src bash + google-chrome-stable --new-tab "URL" + #+end_src + + Don't redirect stdout away while checking for that line — verified 2026-07-27 on ratio: with =2>/dev/null= the message still appears (it isn't stderr), and with =>/dev/null= it vanishes. + + Several URLs in one invocation open as separate tabs (=google-chrome-stable --new-tab "URL1" "URL2"=). Pass them as separate words or an array — the Bash tool runs zsh, which does not word-split an unquoted =$urls= variable, so a space-joined string arrives as one malformed argument (see the zsh note below). + + *Cold start.* If Chrome is *not* already running, the command becomes the browser process and blocks. Detach that case with =&>/dev/null &=, accepting that the confirmation line is discarded — there is no session to confirm into. Don't apply the detach form unconditionally: it suppresses the very output the warm path is verified by. *** Shell aliases (=ls= → =exa=) Craig's shell aliases =ls= to =exa=, which prints nothing to non-TTY pipes (e.g. when capturing =ls= output in a Bash tool call). The result looks like the directory is empty when it isn't. @@ -363,6 +397,12 @@ Craig's shell aliases =ls= to =exa=, which prints nothing to non-TTY pipes (e.g. - Applies to =ls -la=, =ls -t=, glob expansions piped through =ls=, and any =ls= invocation whose output gets read programmatically. - Symptom if forgotten: the Bash tool returns empty output and you mistakenly conclude the directory is empty. +*** zsh does not word-split unquoted variables +The Bash tool runs zsh, which (unlike bash) does not split an unquoted =$var= on whitespace. =chrome $urls= passes all the space-joined URLs as one malformed argument. + +- Loop over the values, use an array, or force the split with =${=var}=. +- Symptom if forgotten: a command that "works in bash" gets one garbled argument and fails, often silently (from the takuzu session, 2026-07-11). + ** Miscellaneous Information - Craig currently lives in New Orleans, LA - Craig's phone number: 510-316-9357 @@ -402,6 +442,30 @@ Full usage: =notify --help= or see =~/.local/bin/notify= - =atq= - list all scheduled alarms - =atrm [number]= - remove an alarm by its queue number +** Reaching Craig — the notification vocabulary + +Two channels, two trigger words. "page me" is the desktop, "text me" is the phone, "text and page me" is both. Pick by where Craig is, and default to both when a run can't tell. Both work from any agent runtime (nothing here is Claude-specific). The words are what Craig says; a run deciding on its own maps the same way (away run texts, at-desk run pages, unsure does both). + +- *"page me" — at his laptop/desktop.* A desktop =notify ... --persist= that reaches him on the machine and stays up until dismissed. + + #+begin_src bash + notify info "Title" "Message" --persist + #+end_src + +- *"text me" — away from his machine.* A Signal push to his phone via =agent-text=: + + #+begin_src bash + agent-text "Message for Craig's phone" + #+end_src + + =agent-text= (in =~/.local/bin= via the rulesets install) sends from the dedicated Signal identity (+15045173983) to Craig's Signal account UUID, firing a normal mobile push. The account is registered on velox (primary) and ratio (linked device), so either sends directly; a machine without it ssh-relays to velox. Verified end to end 2026-07-13 (velox) and 2026-07-20 (ratio). Never target Craig's phone *number* (it reads as unregistered in Signal's directory); the script targets the UUID. + + Caveats: a relay from a non-linked machine needs velox up on the tailnet, and each device holding the account wants a periodic =receive= (the signal-receive timer handles that). The full runbook lives in rulesets =docs/design/=. + +- *"text and page me" — both.* Fire =agent-text= and =notify= together. The phone reaches him now, the desktop note waits for his return. This is the default when a run can't tell whether he's away. + +On velox, Claude sessions may also have the *signal-mcp* tool (=send_message_to_user=, same identity), fine to use there, but it exists only in velox's local MCP config, so =agent-text= is the portable habit. The tool was named =agent-page= before 2026-07-20; a deprecated =agent-page= shim still delegates to =agent-text=. Do *not* use the old =page-signal= shell script (removed 2026-06-12). + * Session Protocols ** CRITICAL: Git Commit Requirements @@ -427,7 +491,7 @@ When creating commits: - Keep messages clear and informative 3. **No Claude-tooling artifacts**: Commit messages describe project changes only — the meta-process of how work got shipped stays out of public git history. - - **ABSOLUTELY NO** mentions of =notes.org=, =session-context.org=, =.ai/sessions/=, =todo.org=, "session wrap-up", or session timestamps (e.g., "Session YYYY-MM-DD HH:MM → ...") + - **ABSOLUTELY NO** mentions of =notes.org=, =session-context.org=, =.ai/= (including =.ai/sessions/=), =.claude/=, =CLAUDE.md=, =todo.org=, "session wrap-up", or session timestamps (e.g., "Session YYYY-MM-DD HH:MM → ..."), except when one of those files is itself the change — then name what changed by category, not the surrounding tooling layer - Subject lines must NEVER start with =session:= as a conventional-commit type — use =docs:=, =refactor:=, =fix:=, =feat:=, =chore:=, etc. (real change categories) - When a wrap-up commit bundles many changes from a session, describe what /shipped/ (e.g., =refactor: extract RAID logic + add bats testing infrastructure=), not that a session happened - Same spirit as the no-Claude-attribution rule: the tooling stays invisible in =git log= @@ -460,7 +524,7 @@ When Craig says this phrase: - If exact match found: Read and guide through process 3. **Fuzzy match across both directories:** Ask for clarification - - Example: User says "empty inbox" but we have "inbox-zero.org" + - Example: User says "empty inbox" but we have "inbox.org" (roam mode) - Ask: "Did you mean the 'inbox zero' workflow, or create new 'empty inbox'?" 4. **No match at all:** Offer to create it @@ -515,12 +579,13 @@ When monitoring a long-running process (rsync, large downloads, builds, VM tests ** "Wrap it up" / "That's a wrap" / "Let's call it a wrap" -When Craig says any of these phrases (or variations), execute the wrap-up workflow: [[file:workflows/wrap-it-up.org][wrap-it-up.org]]. Four steps: +When Craig says any of these phrases (or variations), execute the wrap-up workflow: [[file:workflows/wrap-it-up.org][wrap-it-up.org]]. Five load-bearing steps: 1. *Finalize the Summary* in =.ai/session-context.org= (populate the 5 subsections from the Session Log) 2. *Rename* =.ai/session-context.org= → =.ai/sessions/YYYY-MM-DD-HH-MM-description.org= 3. *Git commit + push* to all remotes (see Git Commit Requirements) -4. *Valediction* — brief, warm, specific closing +4. *Certify the clean tree* with =git-worktree-gate certify=. Any remaining staged, unstaged, untracked, submodule, or in-progress-operation state blocks wrap entirely; report each path and the exact decision needed. There is no dirty-file deferral. +5. *Valediction* — brief, warm, specific closing, reachable only after certification The absence of =.ai/session-context.org= after wrap-up is the signal that the session ended cleanly. If the file is still there at the next session start, the previous session was interrupted. @@ -539,6 +604,8 @@ Claude needs to add information to =.ai/notes.org=. For large amounts of informa **The gitignore set follows that same decision.** A project that gitignores =.ai/= (the code-project case) gitignores the whole personal-tooling set: =.ai/=, =.claude/=, =CLAUDE.md=, =AGENTS.md=. =.claude/= is rulesets-owned — copies of =claude-rules/*.md= plus the language bundle's rules, hooks, and settings — and re-synced from rulesets on every startup, so git isn't how it travels between machines; ignoring it also keeps those private rule copies out of the repo, which ignoring =CLAUDE.md= alone would miss. A track-mode project (personal/doc repos, or a team repo that shares config with teammates who don't run rulesets) tracks the set instead. =install-ai.sh= writes the full set at bootstrap in gitignore mode; =scripts/sweep-gitignore-tooling.sh= backfills it idempotently across existing gitignore-mode projects when the set grows. +**Public reachability decides harder than project type.** Any repo whose remotes include a non-cjennings.net host gitignores the tooling set, whatever kind of project it is — the only exception is a team repo that deliberately shares the config, decided explicitly, never by default. And a private remote is not proof of privacy: a server-side =post-receive --mirror= hook republishes invisibly from the client (the 2026-06-30 =.emacs.d= exposure rode exactly that — a cjennings.net remote mirroring to public GitHub). The sweep recognizes both the anchored (=/.ai/=) and unanchored (=.ai/=) ignore styles — an anchored-style project used to be misread as track-mode and silently skipped — and warns when tracked tooling can reach a non-cjennings.net remote. + **Credential-leak concern: gate it on project type, not on the credential itself.** A tracked secret, token, or credentials doc is only a public-leak risk where the repo can reach a public remote — that is, *code projects pushed to public GitHub*, which is exactly why those gitignore =.ai/= and =.claude/=. For *personal / documentation projects* (the =~/projects/= set: elibrary, home, finances, health, philosophy, etc.), the git remote is a private single-user repo on =cjennings.net=, so tracked credentials inside =.ai/= files are fine — that's the design, the project history IS the project. Do NOT raise a leak warning or suggest gitignoring a secret for these. When the question "is this a leak / should we gitignore this secret?" comes up, decide it on *which kind of project and remote* this is, never on the mere presence of a credential in a tracked file. **When to break out documents:** |
