aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--todo.org137
1 files changed, 125 insertions, 12 deletions
diff --git a/todo.org b/todo.org
index 5ddf40b..3353745 100644
--- a/todo.org
+++ b/todo.org
@@ -1771,17 +1771,25 @@ The four canonical rules (=commits=, =testing=, =verification=, =subagents=) are
The Elisp pair is the most suspicious — three repos using essentially the same rules. Audit: diff these across the projects, check for drift, then decide whether to canonicalize them under =~/code/rulesets/claude-rules/languages/<lang>/= and symlink, or leave them as project-local.
-** TODO [#B] Fold =claude-templates= into rulesets
+** DOING [#A] Consolidate =.ai/= template infrastructure (fold + audit + install-ai + ratio) :feature:
+
+End-state: one repo (=rulesets=) is the single source of truth for =.ai/= template content. =make audit= verifies and applies drift across every =.ai/=-using project on the machine. =make install-ai= bootstraps new projects. Same setup propagated to ratio so both machines run the same way.
+
+Today (2026-05-15) the canonical-source rule got violated again: rulesets commit =372fb76= added a wrap-up subsection to =rulesets= without going through =claude-templates= first, and the next session's startup rsync was about to silently undo it. Two-repo coordination is the root cause; fold solves it.
+
+Build order: fold first (others depend on the new canonical path), then audit + install-ai in parallel, then test, then propagate to ratio.
+
+*** TODO [#A] Fold =claude-templates= into rulesets
Two repos, one source of truth. =~/projects/claude-templates/= is the canonical =.ai/= template that gets rsync'd into every project at session start. Keeping it standalone means a second =git pull= in startup Phase A.0, a second remote to push to at wrap-up, and a split history any time a change touches both. Folding it into =rulesets/claude-templates/= gives one repo to clone on a fresh machine and one place to edit templates.
-*** Open design choices
+**** Open design choices
- *History.* =git subtree add --prefix=claude-templates ~/projects/claude-templates main= preserves the 84-commit history under the new prefix. Plain content copy (=cp -a= + =git add=) is simpler but loses history. Either is fine since the standalone repo stays archived on =cjennings.net=.
- *Layout.* =rulesets/claude-templates/= mirrors the old repo name and sits next to =claude-rules/= cleanly. Alternative: absorb =.ai/= directly under a different name (=rulesets/.ai-template/= or similar). First option is clearer.
- *bin/ai.* The standalone Makefile symlinks =$HOME/.local/bin/ai → bin/ai=. After the move, fold that into rulesets' Makefile as another install target.
-*** Mechanical steps
+**** Mechanical steps
1. Subtree-merge or copy =~/projects/claude-templates/= into =rulesets/claude-templates/=.
2. Update 3 references in rulesets:
@@ -1792,24 +1800,129 @@ Two repos, one source of truth. =~/projects/claude-templates/= is the canonical
4. Replace =~/projects/claude-templates/= with a symlink to =~/code/rulesets/claude-templates/= for transition continuity.
5. After every active project has had one session start (and rsync'd the new =startup.org=), drop the symlink and archive =cjennings.net:git/claude-templates.git=.
-*** Bootstrap gap
+**** Bootstrap gap
Every project on the machine has a =.ai/workflows/startup.org= that rsyncs from =~/projects/claude-templates/=. Until each project's startup.org gets refreshed (which happens via the rsync itself), the old path needs to keep resolving. The symlink at step 4 is the bridge: old paths resolve into the new location, the rsync delivers the updated startup.org, next session uses the new path directly.
-** TODO [#B] Add =make audit= — drift detector across all =.ai/=-using projects
+*** TODO [#A] Add =make audit= — drift detector across all =.ai/=-using projects
-Companion to =make doctor= (single-machine scope, checks =~/.claude/=). =audit= is cross-project scope: walks every directory on the machine that has a =.ai/=, diffs the synced template files against the canonical source, and reports drift. Catches stale projects without forcing a session start in each one.
+Companion to =make doctor= (single-machine scope, checks =~/.claude/=). =audit= is cross-project scope: walks every directory on the machine that has a =.ai/=, diffs the synced template files against the canonical source, and reports drift. =--apply= flag rsyncs the drift into the project's working tree (no auto-commit). Catches stale projects without forcing a session start in each one.
-*** Open design choices
+**** Open design choices
-- *Scope.* Template-sync drift is the useful flavor: for each project, diff =.ai/protocols.org=, =.ai/workflows/=, =.ai/scripts/= against the canonical source and report =ok= / =behind= / =diverged=. Other interpretations (per-project health check, alias for =doctor=) add less value.
-- *Source path.* Today: =~/projects/claude-templates/.ai/=. After the "Fold claude-templates into rulesets" task lands: =~/code/rulesets/claude-templates/.ai/=. Build =audit= against whichever path is canonical when the work happens.
+- *Scope.* Template-sync drift is the useful flavor: for each project, diff =.ai/protocols.org=, =.ai/workflows/=, =.ai/scripts/= against the canonical source.
+- *Source path.* Post-fold: =~/code/rulesets/claude-templates/.ai/=. Build =audit= against the new path from day one.
- *Project discovery.* Walk =~/code/=, =~/projects/=, =~/.emacs.d/= up to depth 3 for any directory containing =.ai/=. Skip the canonical source itself.
-- *Output and exit code.* Per-project line: =ok=, =behind <N files>= (canonical newer, rsync would update), =diverged= (project has local edits an rsync would overwrite). Exit 0 on all-=ok=, 1 on any =diverged=.
+- *Default mode is report-only.* =--apply= triggers rsync; =--force= overrides the dirty-skip safety.
+
+**** Per-project flow (designed 2026-05-15)
+
+For each discovered project, in order:
+
+1. Verify =.ai/= exists (path probe). If missing → =FAIL=, skip, continue loop.
+2. Detect git tracking via =git check-ignore .ai/= → =tracked= or =gitignored=.
+3. Verify no uncommitted =.ai/= changes (=git status --porcelain .ai/=). Dirty → =WARN=, skip rsync unless =--force=.
+4. Verify content matches canonical via three =rsync -a --dry-run --itemize-changes= calls (=protocols.org=, =workflows/=, =scripts/=). Zero items = clean.
+5. Action (=--apply= only, drift detected): three =rsync -a [--delete]= calls.
+6. Verify rsync converged (re-run the dry-runs; zero now).
+7. Verify working-tree state after rsync (tracked projects). Report deltas. Do not auto-commit.
+8. Verify no unpushed =.ai/= commits (=git log @{u}..HEAD -- .ai/=). Informational only.
+
+**** Output format (mirrors =doctor=)
+
+#+begin_example
+Claude-templates source:
+ ok rulesets/claude-templates is current (origin/main)
+
+Per-project .ai/ drift:
+ ok ~/projects/work
+ applied ~/projects/homelab 3 files changed
+ skipped ~/code/winvm uncommitted .ai/ (use --force)
+ ok ~/projects/clipper
+
+Summary: 18 ok, 3 applied, 1 skipped, 0 failed
+#+end_example
+
+Exit code: =0= if all clean, no skips, no failures. =1= otherwise.
+
+**** Why not extend =make doctor= instead
+
+=doctor= has a clean meaning today: "is this machine's =~/.claude/= consistent with rulesets?" Mixing in cross-project =.ai/= drift muddies the exit code. Keep them separate. =audit= can optionally invoke =doctor= as its last check since both ask "did the symlinks keep up with the source?". A future =make all-checks= can wrap both.
+
+*** TODO [#A] Add =make install-ai PROJECT=<path>= — bootstrap =.ai/= in a fresh project
+
+Separate target from =audit= because operating on projects that lack =.ai/= is a distinct action. The absence might be intentional, so =audit= skips them. Bootstrap is explicit opt-in.
+
+**** Flow
+
+1. Refuse if =.ai/= already exists in =PROJECT=. Message: "already installed; use =make audit --apply= to update."
+2. Verify =PROJECT= is a git checkout (warn if not — works without git, loses some lifecycle benefits).
+3. Create =PROJECT/.ai/= directory.
+4. Rsync canonical content: =protocols.org=, =workflows/=, =scripts/= (same three rsyncs as =audit=).
+5. Seed =PROJECT/.ai/notes.org= from a canonical template with project-name placeholder.
+6. Create empty =PROJECT/.ai/sessions/= (with =.gitkeep= for tracked projects).
+7. Track or gitignore =.ai/=? Default: ask. Flag: =--track= / =--gitignore=.
+8. Print next-steps banner: =make install-lang LANG=<lang> PROJECT=<path>=; open Claude Code in the project.
+
+**** Symmetry with existing install targets
+
+#+begin_example
+make install-lang LANG=python PROJECT=/path # language bundle (existing)
+make install-ai PROJECT=/path # .ai/ template (new)
+make install-lang # no args → fzf-pick
+make install-ai # no args → fzf-pick from
+ # ~/projects/* + ~/code/* dirs
+ # without an existing .ai/
+#+end_example
+
+*** TODO [#A] Test plan for audit + install-ai before propagating to ratio
+
+Test against the current state of this machine before pushing changes to ratio.
+
+**** =make audit= tests
+
+1. Dry-run report only (no =--apply=). Should show: claude-templates current; per-project drift; correct =ok=/=drift= classifications; summary line and exit code match.
+2. After the fold lands, every project should be reported as drift (their =startup.org= still points at the old path). Run =--apply= → rsync converges. Re-run audit → all =ok=.
+3. Manually edit one =.ai/workflows/foo.org= in a tracked project. Re-run audit → should report =skipped: uncommitted .ai/=. Run =--apply --force= → rsync clobbers the edit. Verify the edit is gone.
+4. Manually delete one =.ai/= dir. Re-run audit → =FAIL: .ai/ missing=. Loop continues.
+5. Idempotency: =--apply= twice in a row converges to all =ok= on the second pass.
+
+**** =make install-ai= tests
+
+1. Create =/tmp/test-fresh-project= as a git repo. Run =make install-ai PROJECT=/tmp/test-fresh-project=. Verify =.ai/= structure matches canonical, =notes.org= has placeholder, =sessions/= exists.
+2. Run =make install-ai PROJECT=/tmp/test-fresh-project= again → should refuse (=.ai/= already exists).
+3. Open Claude Code in the new project. Startup workflow runs cleanly (Phase A.0 + Phase A rsync should be a no-op since the install just ran).
+4. fzf form: =make install-ai= with no args. Lists candidate dirs (=~/projects/*=, =~/code/*= without =.ai/=).
+
+**** Pass criteria
+
+- =audit= behavior matches the per-project flow spec for every classification path.
+- =install-ai= produces a project indistinguishable from one that's been running sessions for a while.
+- =make doctor= still passes 36/0/0 after all the work.
+- =make test= (pytest + ERT) passes.
+
+*** TODO [#A] Migrate projects on ratio (second machine)
+
+After local fold + audit + install-ai are working, propagate to ratio.
+
+**** Steps
+
+1. On ratio: =git -C ~/code/rulesets pull= — picks up the folded =claude-templates/= subdir and updated =Makefile= targets.
+2. On ratio: archive or =mv= the standalone =~/projects/claude-templates/= aside, replace with symlink to =~/code/rulesets/claude-templates/= (same bridge mechanic as local).
+3. On ratio: =make audit= → see drift across ratio's projects.
+4. On ratio: =make audit --apply= → rsync into each tracked/gitignored project. Surface projects with uncommitted =.ai/= drift for manual handling.
+5. On ratio: =make doctor= → catch any =~/.claude/= install drift (likely some, since ratio hasn't seen recent rulesets updates).
+6. Verify by opening Claude Code in a few ratio projects. Startup should be a no-op or near-zero rsync.
+
+**** Known unknowns
+
+- Ratio may have its own project list overlapping with this machine's but not identical. =audit= discovers projects via the walk, so this is automatic.
+- Ratio might have uncommitted =.ai/= work in some projects that this machine doesn't. =audit= surfaces them; handle case-by-case.
+- If anything goes wrong, ratio's archived =~/projects/claude-templates/= is the safety net — restore the symlink target and re-run audit.
-*** Why not extend =make doctor= instead
+**** Adjacent: cross-machine memory sync
-=doctor= currently has a clean meaning: "is this machine's =~/.claude/= consistent with rulesets?" Mixing in cross-project =.ai/= drift muddies the exit code. Keep them separate; a future =make all-checks= can wrap both.
+The =[#A] DOING= memory-sync investigation (todo.org:10) is adjacent. Both involve "make my Claude setup portable across machines." Coordinate so the memory-sync stow approach (if approved) doesn't conflict with this fold's symlink mechanics.
** TODO [#C] Refactor =daily-prep.org= to delegate to =triage-intake.org= for the triage section