diff options
| author | Craig Jennings <c@cjennings.net> | 2026-05-25 13:16:30 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-05-25 13:16:30 -0500 |
| commit | e0f6b54f41384e8bfdc33810b370ca6eaca9298f (patch) | |
| tree | 282f8c97194662c7d737ec495d38ee9f5a5a9fed | |
| parent | bdbfaa5aec491c89087506508829e9b7a64917bc (diff) | |
| download | dotemacs-e0f6b54f41384e8bfdc33810b370ca6eaca9298f.tar.gz dotemacs-e0f6b54f41384e8bfdc33810b370ca6eaca9298f.zip | |
chore: sync bundled claude rules and git hooks
Routine sync of the .claude/rules and git hooks distributed with the language bundle. Adds the cross-project, emacs, interaction, todo-format, triggers, and working-files rules; refreshes the elisp and elisp-testing rules, the elisp validation hook, and the pre-commit hook.
| -rwxr-xr-x | .claude/hooks/validate-el.sh | 8 | ||||
| -rw-r--r-- | .claude/rules/cross-project.md | 60 | ||||
| -rw-r--r-- | .claude/rules/elisp-testing.md | 44 | ||||
| -rw-r--r-- | .claude/rules/elisp.md | 2 | ||||
| -rw-r--r-- | .claude/rules/emacs.md | 28 | ||||
| -rw-r--r-- | .claude/rules/interaction.md | 31 | ||||
| -rw-r--r-- | .claude/rules/todo-format.md | 223 | ||||
| -rw-r--r-- | .claude/rules/triggers.md | 33 | ||||
| -rw-r--r-- | .claude/rules/working-files.md | 145 | ||||
| -rwxr-xr-x | githooks/pre-commit | 2 |
10 files changed, 568 insertions, 8 deletions
diff --git a/.claude/hooks/validate-el.sh b/.claude/hooks/validate-el.sh index 334578ae..803badf8 100755 --- a/.claude/hooks/validate-el.sh +++ b/.claude/hooks/validate-el.sh @@ -41,16 +41,13 @@ MAX_AUTO_TEST_FILES=20 # skip if more matches than this (large test suites) case "$f" in */init.el|*/early-init.el) # Byte-compile here would load the full package graph. Parens only. - if ! output="$(emacs --batch --no-site-file --no-site-lisp \ - --eval '(setq load-prefer-newer t)' \ - "$f" \ + if ! output="$(emacs --batch --no-site-file --no-site-lisp "$f" \ --eval '(check-parens)' 2>&1)"; then fail_json "PAREN CHECK FAILED" "$f" "$output" fi ;; *.el) if ! output="$(emacs --batch --no-site-file --no-site-lisp \ - --eval '(setq load-prefer-newer t)' \ -L "$PROJECT_ROOT" \ -L "$PROJECT_ROOT/modules" \ -L "$PROJECT_ROOT/tests" \ @@ -92,13 +89,12 @@ if [ "$count" -ge 1 ] && [ "$count" -le "$MAX_AUTO_TEST_FILES" ]; then load_args=() for t in "${tests[@]}"; do load_args+=("-l" "$t"); done if ! output="$(emacs --batch --no-site-file --no-site-lisp \ - --eval '(setq load-prefer-newer t)' \ -L "$PROJECT_ROOT" \ -L "$PROJECT_ROOT/modules" \ -L "$PROJECT_ROOT/tests" \ --eval '(package-initialize)' \ -l ert "${load_args[@]}" \ - --eval "(ert-run-tests-batch-and-exit '(not (or (tag :slow) (tag :perf))))" 2>&1)"; then + --eval "(ert-run-tests-batch-and-exit '(not (tag :slow)))" 2>&1)"; then fail_json "TESTS FAILED ($count test file(s))" "$f" "$output" fi fi diff --git a/.claude/rules/cross-project.md b/.claude/rules/cross-project.md new file mode 100644 index 00000000..50bc34e2 --- /dev/null +++ b/.claude/rules/cross-project.md @@ -0,0 +1,60 @@ +# Cross-Project Boundaries + +Applies to: `**/*` + +How to handle requests that target files or tasks belonging to a different project's `.ai/` scope than the current session. + +## The Rule + +When a request points at a file or task living under a *different* project's `.ai/` scope, stop before doing the work. Surface the boundary crossing in one line and ask: "this looks like it belongs to `<other project>`'s session — confirm you want me to do it from here, or switch projects?" + +Each project's `.ai/` directory is the scope boundary. It carries that project's `protocols.org`, `session-context.org`, `sessions/`, `notes.org`, `todo.org`, `inbox/`, and its own memory dir under `~/.claude/projects/<encoded-cwd>/memory/`. Crossing the boundary without flagging it pollutes the current session's log with the other project's content, drops memories into the wrong dir, and skips the other project's protocols / CLAUDE.md / startup-extras that would otherwise apply. + +## When to Detect + +Trigger the check on any of these: + +- A skill or tool argument names a file under another known project (e.g. cwd is `~/.emacs.d/` and the path is `~/projects/work/todo.org`). +- A file read or write would cross into another project's `.ai/`. +- A user request names another project by topic ("the work todo", "the deepsat repo", "my emacs config") while we're not in that project. + +## How to Apply + +State the mismatch and offer the two acceptable answers. Inline numbered options per `interaction.md` — no popup. + +Two acceptable outcomes: + +1. **"Yes, do it from here"** — proceed. Record the cross-project artifact in a handoff file under the *other* project's `inbox/`, named `YYYY-MM-DD-handoff-from-<this-project>-<topic>.org`, with a top note explaining the crossover. The other project's startup workflow picks it up during inbox processing. + + Prefer the `inbox-send` script (`.ai/scripts/inbox-send.py`, auto-synced into every project) over a manual `Write`/`Edit` for the drop. It handles project discovery, source-project provenance in the filename, slug derivation, and timestamping in one call: + + ``` + inbox-send <target> --text "your message" # text → dated .org file + inbox-send <target> --file <path> # copy a file into target/inbox/ + inbox-send --list # see available projects + ``` + + Output filenames follow `YYYY-MM-DD-HHMM-from-<this-project>-<slug>.<ext>` 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. + +Don't assume which one was meant. Either guess is wrong half the time and the cost of asking once is one short turn. + +## Recovery When It Goes Wrong + +If you do the work first and the boundary issue surfaces afterwards: + +1. Move the cross-project session-log entries out of the current session's `.ai/session-context.org` into `<other-project>/inbox/YYYY-MM-DD-handoff-from-<this-project>-<topic>.org`. Top of that file: a heads-up explaining the crossover so the other project's next session knows what happened. +2. Replace the moved content in `session-context.org` with a brief stub pointing at the handoff file. +3. Move any project-specific memories you saved into the right project's memory dir, or note them in the handoff file if you can't move them. + +## Why + +The user sometimes invokes a skill from whatever shell they happen to be in. The request may be accidental (they meant to be in the other project's terminal) or deliberate (knowing cross-project handoff). The model can't tell from the request alone, and assuming wrong both times costs more than asking once. + +The per-project scope of `.ai/` is the design — protocols, history, memory, inbox, and todo all coupled to one project. Cross-project work breaks every assumption the next session of each project will make. + +## Related + +- `subagents.md` — the per-agent context-isolation discipline. Same principle, smaller scope. +- `interaction.md` — inline numbered options for the "from here / switch?" prompt. +- Per-project `.ai/protocols.org` — the project-scoped instructions this rule protects. diff --git a/.claude/rules/elisp-testing.md b/.claude/rules/elisp-testing.md index b5def788..b727cbd5 100644 --- a/.claude/rules/elisp-testing.md +++ b/.claude/rules/elisp-testing.md @@ -99,6 +99,50 @@ make test-name TEST=pattern # Match by test name pattern A PostToolUse hook runs matching tests automatically after edits to a module, when the match count is small enough to be fast. +## Batch-Mode Reproducibility + +Tests must pass under `emacs --batch` — the headless, scriptable path that CI and the `make` targets use. `--batch` is the source of truth, not an interactive session. + +- Don't depend on interactive-session state: window configuration, frame parameters, `this-command`, minibuffer activity, or anything a running editor accumulates. A test that passes in a live Emacs but fails (or hangs) under `--batch` is broken. +- Don't block on a prompt. `--batch` has no one to answer `y-or-n-p` or `read-string`, so an unmocked prompt either errors or stalls the run. Test the internal directly (see *Interactive vs Internal* above) or `cl-letf` the prompt. +- Keep tests deterministic: no reliance on test execution order, wall-clock time (mock `current-time`), or environment that differs between the developer's machine and CI. + +## Isolating Emacs State + +A test must not read or mutate the developer's real Emacs config. Bind a throwaway environment so the run is hermetic regardless of who runs it. + +- Bind `user-emacs-directory` (and, when relevant, `user-init-file`) to a temp directory so package state, `custom-file` writes, caches, and auto-save files land in the sandbox rather than the developer's `~/.emacs.d`. +- Control `load-path` explicitly. Add only the project's own directories; don't lean on whatever happens to be installed in the developer's session. +- Depend only on the project's declared dependencies. A test that passes because some unrelated package is installed on this machine will fail on a clean checkout or in CI. + +```elisp +(ert-deftest test-foo-writes-to-sandbox () + "Normal: writes under an isolated user-emacs-directory." + (let* ((sandbox (make-temp-file "elisp-test-" t)) + (user-emacs-directory (file-name-as-directory sandbox))) + (unwind-protect + (progn + (cj/--foo) + (should (file-exists-p (expand-file-name "foo.cache" user-emacs-directory)))) + (delete-directory sandbox t)))) +``` + +## Byte-Compile and Native-Comp Warnings + +A clean compile is part of green. Byte-compile warnings (free variables, wrong argument counts, unused lexical bindings, obsolete-function calls) flag real defects, so treat them as failures rather than noise. + +This can be enforced in the test run by binding `byte-compile-error-on-warn` to `t` and compiling the modules under test, optionally extending to native compilation where `native-comp-async-report-warnings-errors` is available. + +Keep the native-comp half conditional. Native compilation exists only on builds with the `native-compile` feature (Emacs 28+ compiled with it); older or non-native builds lack `native-comp-*` variables and `native-compile` entirely. Gate on the feature so the suite still runs everywhere: + +```elisp +(when (and (fboundp 'native-comp-available-p) (native-comp-available-p)) + ;; native-comp-specific checks here + ) +``` + +Make the warnings-as-errors gate opt-in or version-aware rather than absolute — a warning that's clean on the project's pinned Emacs may differ across versions, and a hard failure on every build penalizes contributors on a different Emacs than the maintainer's. + ## Anti-Patterns - Hardcoded timestamps — generate relative to `current-time` or mock diff --git a/.claude/rules/elisp.md b/.claude/rules/elisp.md index e641058a..ea9bdc2d 100644 --- a/.claude/rules/elisp.md +++ b/.claude/rules/elisp.md @@ -72,4 +72,4 @@ Then `(require 'foo-config)` in `init.el` (or a config aggregator). - A PostToolUse hook runs `check-parens` and `byte-compile-file` on every `.el` save - If it blocks, read the error — don't retry blindly -- Prefer Write over repeated Edits for nontrivial new code; incremental edits accumulate subtle paren mismatches +- Edit cohesively, then verify parens/byte-compile right away. For nontrivial Elisp, land a function as one complete, coherent change rather than dribbling it in over many tiny partial edits — incremental fragments accumulate subtle paren mismatches. Run the paren-balance and byte-compile checks immediately after editing, whatever editing mechanism the environment uses. diff --git a/.claude/rules/emacs.md b/.claude/rules/emacs.md new file mode 100644 index 00000000..2e996371 --- /dev/null +++ b/.claude/rules/emacs.md @@ -0,0 +1,28 @@ +# Working With Craig's Running Emacs + +Applies to: `**/*.el` (and any task that edits Craig's Emacs configuration) + +Craig works inside Emacs nearly constantly, with a long-running `emacs --daemon` and `emacsclient` frames. When you edit an Emacs module he's using, **do not make him quit, relaunch, and re-open all his files.** Push the change into the running daemon instead. He explicitly called the restart-and-reload-everything dance out as the thing this avoids. + +## Live-reload a module + +After editing a module, reload it into the running daemon: + + emacsclient -e '(load "/home/cjennings/.emacs.d/modules/foo.el")' + +This re-evaluates the file and redefines its `defun`s live. For straight function redefinitions it is immediate and clean — the edited command is active on the next keypress, no restart. + +## Caveats — when a plain reload isn't enough + +- **`defvar` defaults don't re-apply.** `defvar` only assigns when the variable is unbound, so reloading the file won't change an already-set default. `setq` the value explicitly, or `makunbound` it then reload. +- **`use-package` `:config` / `:init` re-run on a full file `load`.** That re-adds hooks, re-binds keys, re-runs setup — functionally fine, but it stacks state and isn't pristine. For an edit inside a `:config` block, prefer eval-ing just the changed form, or accept the restacking, or restart for a clean state. +- **Faces and themes need re-applying.** Editing a theme/face file does nothing until the theme is re-applied: `(load-theme 'THEME t)`. +- **Baked or rendered state must be regenerated.** Values computed at load time (e.g. a list built from `nerd-icons-*` calls) and already-drawn buffers (e.g. `*dashboard*`) do not update from a var reload alone — regenerate them (e.g. `dashboard-refresh-buffer`). This is the stale-buffer trap: the variable looks correct but the visible buffer is old. It once made a screenshot look right when the live buffer was still wrong. + +## The reload-and-verify loop (default for visible Emacs changes) + +1. Edit the module. +2. Reload into the daemon (`emacsclient -e '(load ...)'`), plus re-apply the theme and/or regenerate the affected buffer where the caveats above apply. +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. diff --git a/.claude/rules/interaction.md b/.claude/rules/interaction.md new file mode 100644 index 00000000..4d9279ba --- /dev/null +++ b/.claude/rules/interaction.md @@ -0,0 +1,31 @@ +# Interaction Style + +Applies to: `**/*` + +How Claude 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. + +**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. + +**How to apply:** + +For approve / changes / cancel flows (commit-message review, PR-description review, plan approval), draft inline: + +``` +1. Approve — commit now +2. Request changes — tell me what to adjust +3. Open in editor — emacsclient -n /tmp/... + +Pick a number. +``` + +For pick-one decisions, same shape: numbered list, one-line prompt at the end. + +For multi-select decisions, say so explicitly: "Pick any combination — reply with the numbers." + +Reserve `AskUserQuestion` only when the user explicitly asks for the popup form ("use the popup for this one") or for genuinely free-form input where numbered options don't fit. + +This rule applies to all three approval gates in the `commits.md` publish flow (commit message, PR description, PR review reply): print the draft inline, then offer numbered approve / changes / edit options inline. Do not switch to the popup form for the gate even though the prior protocol referenced it. diff --git a/.claude/rules/todo-format.md b/.claude/rules/todo-format.md new file mode 100644 index 00000000..a8df76ad --- /dev/null +++ b/.claude/rules/todo-format.md @@ -0,0 +1,223 @@ +# Todo Entry Format + +Applies to: `**/*.org` (org-mode todo and inbox files) + +How task entries are structured in org-mode todo files (`todo.org`, +`inbox.org`, any GTD-style org file). Same shape across every project. + +## The Rule + +A todo entry has two parts: + +1. **Heading** — terse subject naming just the topic. No action verbs, no + sentence-shape, no dates. Tags belong on the heading line. +2. **Body** (optional) — fuller description: action verbs, context, + rationale, source/origin, links, deadlines. Used when the topic alone + isn't enough. + +When the topic alone is enough, skip the body entirely. + +## Format + + ** TODO [#A] Terse topic phrase :tag1:tag2: + Optional body — fuller description, action verbs, context, links. + + Multi-paragraph body is fine when context warrants it. + +## Examples + +Good: + + ** TODO [#A] Blacken + Prettier config from Vrezh + Ask Jason to implement the formatter config Vrezh sends over. + + ** TODO [#B] TAK-server plugin user scenarios :quick: + Develop with Eric, send to Nate Soule for review. + Out of the 2026-05-13 RTX<>DeepSat sync. + +Bad (sentence-shaped heading, details crammed in): + + ** TODO [#B] Develop TAK-server plugin user scenarios with Eric, send to Nate :quick: + +## Why + +The org agenda view shows the heading. A short heading is scannable; a +sentence-shaped one runs off the edge of the agenda buffer, and the +context that mattered ends up in the truncated tail. The body is always +reachable by visiting the entry — push everything beyond the topic there. + +## How to apply + +When adding a new task: + +1. Pick the smallest noun phrase that names the topic. +2. If anything else is worth saying, put it in the body. +3. Tags go on the heading line, not in the body. + +When restructuring an existing entry that's already sentence-shaped, split +it: keep the topic as the heading, move the rest to the body. + +## Completion — depth-based + +How a finished `TODO` / `DOING` task is closed depends on its depth in the outline. The rule is *depth-based*, not keyword-based, because the agenda truncates beyond level-2 and the visibility tradeoff flips at that boundary. + +### Top-level tasks (`*` and `**`) — stay task-shaped + +A completed `*` section or `**` task keeps its `TODO`/`DOING`-shape so it remains visible in the agenda as a record of what shipped: + +1. Change the keyword to `DONE` (or `CANCELLED` if the task was abandoned rather than completed). +2. Add a `CLOSED: [YYYY-MM-DD Day]` line directly under the heading. Use `date "+%Y-%m-%d %a"` to generate. +3. Leave the original heading text, priority cookie, and tags intact. +4. Optionally add a one-line resolution note in the body. + +The entry is then a candidate for `--archive-done` in the wrap-up cleanup, which moves level-2 `DONE`/`CANCELLED` subtrees from the project's "Open Work" section into "Resolved." + +**Example:** + + ** TODO [#B] Convert <cj structure template to universal yasnippet + +becomes + + ** DONE [#B] Convert <cj structure template to universal yasnippet + CLOSED: [2026-05-15 Fri] + +### Sub-tasks (`***` and deeper) — rewrite to a dated event-log entry + +A completed sub-task disappears as a task and becomes an in-place event-log entry under its parent. The parent's subtree organically grows into a chronological history of what landed without the agenda being cluttered by a long tail of nested `DONE` lines. + +1. Replace the heading with `<same depth> YYYY-MM-DD Day @ HH:MM:SS -ZZZZ <past-tense description>`. +2. Generate the timestamp with `date "+%Y-%m-%d %a @ %H:%M:%S %z"`. +3. Reword the original imperative title into the past-tense action that landed. Trim or restate if the original wording doesn't fit the action. +4. Drop the `TODO`/`DOING` keyword, the priority cookie, and the tags. The body stays as the record of what was done (if useful). + +**Example:** + + *** TODO [#B] Wire yasnippet for universal availability :refactor: + +becomes + + *** 2026-05-15 Fri @ 12:58:08 -0500 Wired yasnippet for universal availability + +### Why depth-based + +The agenda view (`org-agenda`) shows entries at the section + top-task level. Letting `**` tasks stay task-shaped preserves their visibility as "things that recently shipped." Letting `***+` sub-tasks flip to dated entries keeps the agenda from being clogged with a long list of completed sub-tasks at every depth — those become history within their parent instead. + +`VERIFY` is the documented exception: it follows the dated-rewrite rule at **all** depths (including `**`), because a resolved VERIFY is an answered question rather than a finished task. See the VERIFY section below. + +## VERIFY tasks + +`VERIFY` is the keyword for "waiting on Craig's input" — open questions, +pending decisions, drafts awaiting approval. The placement and completion +rules below are stricter than normal TODO tasks because VERIFYs are +open-question placeholders, not regular tasks. + +### Placement — top-level or first-level child only + +A VERIFY task lives at exactly one of two depths: + +1. **Top-level** under the relevant section (e.g., `** VERIFY ...` directly + under `* Work Open Work`). +2. **First-level child** of a parent task (e.g., `*** VERIFY ...` under a + `** TODO` / `** DOING` parent). + +Never deeper. A VERIFY at `****` or below is buried — the agenda view +collapses it under its grandparent and Craig stops seeing it. When you +catch a VERIFY at `****+`, flatten it up to one of the two allowed depths. + +The goal is a flat, scannable task list: the parent task is the topic; its +VERIFYs are the open threads under that topic; the dated history sits as +log entries (which *can* be deeper). + +### Creating a new VERIFY — sibling of its trigger + +When you add a new VERIFY task, place it as a **sibling of the heading +that triggered its creation** — not as a child of that heading. + +The trigger is whatever surfaced the open question: + +- A cj annotation that asks something or marks a pending decision → + trigger is the heading the annotation sits under. +- A task body or sub-task that uncovered an unanswered question while you + were working it → trigger is that task. +- A draft awaiting Craig's sign-off → trigger is the draft's parent task. + +Depth-wise, this means: + +- Trigger at `**` → new VERIFY at `**` (top-level sibling under the + section). +- Trigger at `***` → new VERIFY at `***` (first-level-child sibling under + the same `**` parent). +- Trigger at `****` or deeper → VERIFY can't follow it there (Placement + rule above). Climb the VERIFY up to `***` and surface the buried + trigger as a candidate to flatten. +- Trigger at `*` (a top-level section) → VERIFY goes at `**` (top-level + task under the section). + +File placement: insert the new VERIFY *after* the trigger's entire +sub-tree ends — i.e., after any sub-tasks, dated log headers, or draft +sub-headings the trigger already contains. This keeps everything under a +`**` parent flat: sub-tasks, dated logs, and VERIFYs all live as +siblings at `***`, never burrowing deeper. + +The sibling rule is the active force that keeps `todo.org` flat. Without +it, VERIFYs accumulate one level deeper than their trigger every time — +turning a clean parent tree into a long pole of nested sub-headings. + +### Completion — dated rewrite + content replacement + +When a VERIFY resolves, **rewrite the heading and body together** at the +same depth — regardless of whether the VERIFY is at `**` or `***`: + +1. **Replace the heading.** Drop the `VERIFY` keyword (and any priority + cookie / tags) and replace with a timestamp + short description: + + *** 2026-05-15 Fri @ 14:00:00 -0500 <what was answered or done> + + Generate the timestamp with `date "+%Y-%m-%d %a @ %H:%M:%S %z"`. + Match the original depth (a `**` VERIFY becomes `** YYYY-MM-DD ...`; + a `***` VERIFY becomes `*** YYYY-MM-DD ...`). + +2. **Replace the body.** Drop the original question/instruction prose and + replace with either: + - **The information Craig provided** (when the VERIFY was a question + — paste the answer, a quote, a link, whatever resolved it), or + - **A description of the action taken** (when the VERIFY was an + instruction or pending-decision marker — what was done, when, where + the artifact lives). + +The completed VERIFY becomes an in-place event log entry. The original +question is preserved by the dated heading + body shape; anyone scanning +the agenda or `git log` can see what was asked and what landed. + +**Note on the top-level case.** Regular `**` DONE tasks stay task-shaped +with a `DONE` keyword + `CLOSED:` line per *Completion — depth-based* +above. VERIFYs at `**` are the exception — they convert to dated log +entries on completion because a resolved VERIFY isn't a "done task," it's +an answered question. The dated-rewrite rule wins for VERIFYs at all +depths. + +### Don't leave stale placeholders + +A well-named VERIFY heading carries the question on its own. Don't append +`cj: <fill in>` placeholder lines under the heading — Craig adds his own +cj comment (a `#+begin_src cj: ... #+end_src` block) when he's ready to +answer, and that's the trigger to process the response. Empty placeholders +are noise that pollute his `cj:` greps. + +### Examples + +**Top-level VERIFY, before / after:** + + ** VERIFY What's our position on the BBN NDA reciprocal-term length? + + ** 2026-05-15 Fri @ 14:00:00 -0500 BBN NDA — standard 2-year reciprocal terms confirmed + Per Nerses 2026-05-15 (Slack DM D0AAAEW3BS4): DeepSat's standard NDA template uses 2-year reciprocal. Sent to BBN legal for sign-off. + +**First-level child VERIFY, before / after:** + + ** DOING [#A] Kostya's contract :admin:kostya: + *** VERIFY Confirm Kostya's basis — part-time hr/week or full-time? + + ** 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. diff --git a/.claude/rules/triggers.md b/.claude/rules/triggers.md new file mode 100644 index 00000000..e45e660a --- /dev/null +++ b/.claude/rules/triggers.md @@ -0,0 +1,33 @@ +# Cross-Project Trigger Phrases + +Applies to: `**/*` + +Trigger phrases the user can say from any session to invoke a cross-project action. These live in the global rules layer because they cross project boundaries — the user can be sitting in any cwd, including outside a project, and the phrase still means the same thing. + +## "Launch project X" + +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. + +``` +ai <project-path> +``` + +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. + +**Resolving X.** Match against project basenames discoverable by `ai` — directories under `~/code/`, `~/projects/`, and `~/.emacs.d` that contain `.ai/protocols.org`. + +- Exact basename match (case-insensitive) → invoke `ai <path>` directly. +- 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. + +## Why a separate file + +Other claude-rules files cover specific concerns: `commits.md` for the publish flow, `subagents.md` for delegation, `testing.md` for test discipline. None is a natural home for "phrases the user says to trigger global actions." The trigger phrases in `protocols.org` (`Let's run the [X] workflow`, `Wrap it up`) are project-scoped — they assume an active `.ai/` session. Cross-project launchers warrant their own file. + +## Adding new triggers + +Same shape. Each entry: phrase (in quotes), synonyms, the action, ambiguity handling. diff --git a/.claude/rules/working-files.md b/.claude/rules/working-files.md new file mode 100644 index 00000000..9a727027 --- /dev/null +++ b/.claude/rules/working-files.md @@ -0,0 +1,145 @@ +# Working-Files Convention + +Applies to: `**/*` (every project) + +How in-progress task artifacts are organized in the project tree, and how +they migrate to their permanent home when the task ships. + +## The Rule + +Every in-progress task that produces files (drafts, source documents, +diagrams, scripts, sample data, transcripts, sub-deliverables) gets a +dedicated subdirectory under the project's `working/` directory, named +after the task. All artifacts for that task live in that subdirectory — +the org source, every supporting file, every draft revision, every +generated artifact — until the task is marked done. + +When the task is marked done, the files get **renamed individually** and +**moved flat** into the appropriate permanent home (typically `assets/`, +or an area-specific `assets/` like `deepsat/assets/`). The working +subdirectory is then empty and gets deleted. + +**Never rename the directory itself as a substitute for filing.** Renaming +the working subdir to its permanent name keeps every file together as a +bundle, but loses the flat-filing property — future grep for an artifact +hits a nested path instead of a single canonical name. Always rename the +files individually with a shared prefix so they sort together but live as +flat siblings in `assets/`. + +## Directory Layout + + <project-root>/ + working/ + <task-slug>/ + <artifact-1> + <artifact-2> + ... + +Examples: + + work/working/tech-deck-vol2/ + tech-deck-vol2.org + slide-04-platform-at-a-glance.mmd + slide-04-platform-at-a-glance.png + vol1-template.pptx + + finances/working/2026-q2-tax-return/ + schedule-c.org + receipts-import.csv + depreciation-worksheet.ods + + work/working/SE-297-justin-onboarding/ + justin-tasking-options.org + rate-research.org + advisory-agreement-draft.docx + +## Task-Slug Naming + +Keep the slug short, kebab-case, and recognizable on a 1-second scan. + +- Topic-led for narrative tasks: `tech-deck-vol2`, `branching-strategy-spec`, `tampa-trip`. +- Ticket-led for ticket-driven tasks: `SE-297-justin-onboarding`, `DEE-722-vincent-vetting`. +- Date-led for time-boxed work: `2026-q2-tax-return`, `2026-05-sofweek-prep`. + +Avoid: trailing dates on topic-led slugs (`tech-deck-vol2-2026-05-18`) +when the task is open-ended; the slug names *the task*, not a snapshot. + +## Filing on Completion + +When the task is marked done: + +1. **Decide each file's permanent home.** Usually one of: + - `<area>/assets/` (e.g. `deepsat/assets/`) — for area-scoped reference + material (transcripts, PDFs, diagrams that document the area's state). + - Project-root `assets/` — for project-scoped material. + - `<area>/<topic>/` — for material that joins an existing topic + subdirectory (rare; only when the topic dir already exists). + +2. **Rename each file** so it carries the task context and is sortable + alongside its siblings. Standard form: + `<YYYY-MM-DD>-<task-slug>-<descriptor>.<ext>` + + Examples: + - `tech-deck-vol2.org` → `2026-05-18-tech-deck-vol2-source.org` + - `slide-04-platform-at-a-glance.png` → `2026-05-18-tech-deck-vol2-slide-04-diagram.png` + - `vol1-template.pptx` → `2026-05-08-tech-deck-vol1-template.pptx` + (use the date the artifact originated, not the date the task closed, + when it's clearly meaningful — Vol 1 template predates Vol 2 work) + +3. **Move flat into the permanent home.** No nested subdirectory in + `assets/`. The files sort together by date + task-slug prefix. + +4. **Delete the now-empty `working/<task-slug>/` subdirectory.** + +5. **Update any inbound links.** Tasks in `todo.org`, references in + `notes.org`, cross-links from other documents — all need their `file:` + paths updated to the new flat-filed locations. Use grep to find every + occurrence before deleting the working dir. + +## Why This Shape + +- *Discovery during work* — every artifact for the current task is in + one place. No hunting across `assets/`, `drafts/`, scratch dirs. +- *Discovery after completion* — `assets/` stays a flat searchable + store. Every artifact is reachable by a single `find assets/ -name '*<slug>*'`. +- *Atomic completion* — the act of filing forces a review of every + artifact's permanent value. Files that aren't worth keeping get + deleted in the move; the rename forces a meaningful name. +- *No orphan subdirs in `assets/`* — renaming the directory instead of + files would bury artifacts inside a nested path that no flat search + reaches. Three years from now `assets/old-tech-deck-2026/slide.png` + is harder to find than `assets/2026-05-18-tech-deck-vol2-slide-04-diagram.png`. + +## When the Rule Doesn't Apply + +- *Single-file scratch work that lives one day* — a `/tmp/foo.txt` for + a draft is fine. The convention is for tasks producing multiple + artifacts or artifacts worth keeping. +- *Source-controlled subprojects* — code under `<project>/code/<repo>/` + follows the subproject's own conventions, not this rule. +- *Inbox content* — `inbox/` and `daily-prep/` follow their own + conventions (dated filenames, processed and moved on cadence). + +## Implementation Note for Claude Sessions + +When the user starts a new task that's going to produce file artifacts: + +1. Propose the working-dir path before creating any files + (`working/<task-slug>/`). +2. Create the directory and put the first artifact there. +3. Add or update the inbound link in `todo.org` to point at the new + path. +4. Note the working-dir in the task's body so future sessions find it. + +When the user marks the task done: + +1. List every file in the working subdir. +2. Propose renames + permanent homes for each. +3. Move flat after confirmation. +4. Delete the empty working subdir. +5. Update inbound links. + +The directory layout is the same shape across every project — see +per-project `CLAUDE.md` or `notes.org` for project-specific +permanent-home conventions (e.g. `deepsat/assets/` vs project-root +`assets/`). diff --git a/githooks/pre-commit b/githooks/pre-commit index 252921df..909cde22 100755 --- a/githooks/pre-commit +++ b/githooks/pre-commit @@ -9,7 +9,7 @@ cd "$REPO_ROOT" # --- 1. Secret scan --- # Patterns for common credentials. Scans only added lines in the staged diff. -SECRET_PATTERNS='(AKIA[0-9A-Z]{16}|(^|[^a-zA-Z0-9])sk-[a-zA-Z0-9_-]{20,}|-----BEGIN (RSA|DSA|EC|OPENSSH|PGP)( PRIVATE)?( KEY| KEY BLOCK)?-----|(api[_-]?key|api[_-]?secret|auth[_-]?token|secret[_-]?key|bearer[_-]?token|access[_-]?token|password)[[:space:]]*[:=][[:space:]]*["'"'"'][^"'"'"']{16,}["'"'"'])' +SECRET_PATTERNS='(AKIA[0-9A-Z]{16}|sk-[a-zA-Z0-9_-]{20,}|-----BEGIN (RSA|DSA|EC|OPENSSH|PGP)( PRIVATE)?( KEY| KEY BLOCK)?-----|(api[_-]?key|api[_-]?secret|auth[_-]?token|secret[_-]?key|bearer[_-]?token|access[_-]?token|password)[[:space:]]*[:=][[:space:]]*["'"'"'][^"'"'"']{16,}["'"'"'])' secret_hits="$(git diff --cached -U0 --diff-filter=AM \ | grep '^+' | grep -v '^+++' \ |
