aboutsummaryrefslogtreecommitdiff
path: root/docs/design
diff options
context:
space:
mode:
Diffstat (limited to 'docs/design')
-rw-r--r--docs/design/2026-06-02-flush-promotion.org2
-rw-r--r--docs/design/2026-06-02-pattern-catalog-spec.org2
-rw-r--r--docs/design/2026-06-17-ntfy-agent-comms-proposal.org2
-rw-r--r--docs/design/2026-06-18-triage-intake-phone-push-workflow.org2
-rw-r--r--docs/design/2026-07-13-runtime-portability-inventories.org84
-rw-r--r--docs/design/2026-07-14-sentry-workflow-proposal.org150
-rw-r--r--docs/design/2026-07-15-subproject-pattern-proposal.org22
-rw-r--r--docs/design/2026-07-15-subprojects-convention-home-instance.org282
-rw-r--r--docs/design/2026-07-16-polyglot-bundle-collision.txt30
-rw-r--r--docs/design/task-review.org2
10 files changed, 573 insertions, 5 deletions
diff --git a/docs/design/2026-06-02-flush-promotion.org b/docs/design/2026-06-02-flush-promotion.org
index 9d9d8a3..ff4e579 100644
--- a/docs/design/2026-06-02-flush-promotion.org
+++ b/docs/design/2026-06-02-flush-promotion.org
@@ -1,5 +1,5 @@
#+TITLE: Flush Promotion — Handoff Bundle (from work)
-#+AUTHOR: Craig Jennings & Claude
+#+AUTHOR: Craig Jennings
#+DATE: 2026-06-02
* Provenance
diff --git a/docs/design/2026-06-02-pattern-catalog-spec.org b/docs/design/2026-06-02-pattern-catalog-spec.org
index e74b8ae..6bb105a 100644
--- a/docs/design/2026-06-02-pattern-catalog-spec.org
+++ b/docs/design/2026-06-02-pattern-catalog-spec.org
@@ -1,5 +1,5 @@
#+TITLE: Cross-Project Pattern Catalog — Spec
-#+AUTHOR: Craig Jennings & Claude
+#+AUTHOR: Craig Jennings
#+DATE: 2026-06-02
One-page spec for capturing reusable design patterns so they travel from one project to the next instead of being re-derived. Drafted for Craig's spec-review; the five open questions from the [[file:../../todo.org][todo.org]] task carry a recommended call each, marked DECISION.
diff --git a/docs/design/2026-06-17-ntfy-agent-comms-proposal.org b/docs/design/2026-06-17-ntfy-agent-comms-proposal.org
index ce17138..0961b47 100644
--- a/docs/design/2026-06-17-ntfy-agent-comms-proposal.org
+++ b/docs/design/2026-06-17-ntfy-agent-comms-proposal.org
@@ -1,5 +1,5 @@
#+TITLE: Proposal — Promote the ntfy phone channel into a general agent-comms tool
-#+AUTHOR: Craig Jennings & Claude (home project)
+#+AUTHOR: Craig Jennings
#+DATE: 2026-06-17
* Why this is in rulesets' inbox
diff --git a/docs/design/2026-06-18-triage-intake-phone-push-workflow.org b/docs/design/2026-06-18-triage-intake-phone-push-workflow.org
index cd830fb..a0bb416 100644
--- a/docs/design/2026-06-18-triage-intake-phone-push-workflow.org
+++ b/docs/design/2026-06-18-triage-intake-phone-push-workflow.org
@@ -1,5 +1,5 @@
#+TITLE: Triage Intake Workflow (Engine)
-#+AUTHOR: Craig Jennings & Claude
+#+AUTHOR: Craig Jennings
#+DATE: 2026-05-01
* Summary
diff --git a/docs/design/2026-07-13-runtime-portability-inventories.org b/docs/design/2026-07-13-runtime-portability-inventories.org
new file mode 100644
index 0000000..b9e8244
--- /dev/null
+++ b/docs/design/2026-07-13-runtime-portability-inventories.org
@@ -0,0 +1,84 @@
+#+TITLE: Runtime Portability — Hook, MCP, and Memory Inventories
+#+AUTHOR: Craig Jennings
+#+DATE: 2026-07-13
+
+Decision-prep for the generic-agent-runtime arc (spec: [[file:2026-05-28-generic-agent-runtime-spec.org]]). Three of the eight gap child tasks are inventories; this doc is their deliverable. Each section ends with a verdict and any decision left for Craig.
+
+* Hook parity inventory
+
+What's wired today (global =.claude/settings.json=, symlinked to =~/.claude/settings.json=):
+
+- PreToolUse AskUserQuestion hard-deny — blocks the popup-choice tool, forcing inline numbered options per =interaction.md=. Exists because prose alone failed.
+- PreCompact =precompact-priorities.sh= — saves priorities before context compaction.
+- SessionStart =session-title.sh= (cosmetic title) and =session-clear-resume.sh= (resume-after-/clear plumbing).
+- Stop =ai-wrap-teardown.sh= — sentinel-gated session teardown.
+
+Shipped but unwired (reference copies in =hooks/= + =settings-snippet.json=): =git-commit-confirm.py=, =gh-pr-create-confirm.py=, =destructive-bash-confirm.py=. Not active under Claude either, so they impose no parity requirement today.
+
+Language bundles wire PostToolUse validators per edit (=validate-bash.sh=, =validate-el.sh=, =validate-go.sh=, plus python/typescript equivalents) — and every bundle also ships the same validation as a =githooks/pre-commit=.
+
+Per-hook portability verdicts:
+
+- AskUserQuestion deny: moot off Claude. The popup tool is Claude Code's; Codex-style and local harnesses have no equivalent tool to deny. No port needed.
+- PostToolUse validators: covered off Claude. The bundles' git pre-commit hooks run the same validators, so enforcement survives at commit time instead of edit time. Acceptable downgrade, no work needed.
+- SessionStart clear-resume: not portable as-is; /clear semantics are Claude's. This is the same surface as the "session plumbing per runtime" child task — resolve it there, not as a hook port.
+- session-title: cosmetic, skip on other runtimes.
+- PreCompact priority-save: genuine gap. Codex-style harnesses compact without a hook point. Downgrade is prose (the session-context write triggers in protocols.org already say "save before compaction"), which is weaker but is also the pre-2026 Claude behavior. Accept prose, or shorten sessions on those runtimes.
+- Stop wrap-teardown: partial gap. Codex has a turn-completion notify command that could run the same sentinel-gated script; local harnesses vary. Port the script trigger where a hook point exists; where none does, teardown becomes a manual step in the wrap phrase.
+
+Verdict: only PreCompact and Stop carry real porting work, and both have acceptable degraded modes. Nothing here blocks a pilot.
+
+* MCP portability check
+
+Locally-configured servers (user scope, =~/.claude.json=): linear, notion, figma, slack-deepsat, google-calendar, google-docs-personal, google-docs-work, drawio, google-keep. No project-scope servers anywhere. All are ordinary MCP server definitions, portable to any MCP-speaking harness (Codex CLI reads them from its config; most local harnesses that matter speak MCP now). Port = translate the server blocks + carry the auth material. Mechanical.
+
+claude.ai-managed connectors (session-injected, not in local config): Gmail, and the claude.ai variants of Calendar/Drive. These do not travel. Overlap analysis: calendar and docs are already covered by the locally-configured servers; Gmail's workflow-preferred path is already =cmail-action= (local CLI), so the managed Gmail connector is convenience, not dependency.
+
+Named gap: signal-mcp. protocols.org "Paging Craig" names it as the only supported away-from-desk page path, and it is not in any local config — it lives claude.ai-side. Off Claude there is currently no page channel at all. This lands squarely on the open Signal-pager task ([#C], todo.org): the signal-cli runbook that task produces would be the runtime-neutral paging path. Recommend noting runtime-portability as an added motivation on that task.
+
+Verdict: portable except paging; paging's fix is already a filed task.
+
+* Memory story for non-Claude agents
+
+Claude auto-memory (=~/.claude/projects/<enc>/memory/=, MEMORY.md index) is harness-owned: written by Claude's memory tooling, loaded by its session start. A non-Claude agent should neither read nor write it.
+
+The designed cross-agent store already exists: the org-roam KB (=knowledge-base.md= — reading is plain rg over files, writing is one node per fact, both runtime-neutral). Project state lives in file artifacts every agent reads anyway: todo.org, notes.org, session anchors, docs/.
+
+One wording gap: knowledge-base.md's "Capture, then promote" section names harness memory as the capture layer. For a non-Claude agent the capture layer is the session log itself. Recommended one-sentence addition there: an agent without harness memory captures into its session log and promotes from it at wrap-up. Shared-asset edit, needs approval.
+
+Verdict: no build. One approved sentence in knowledge-base.md closes it.
+
+* Skill and command parity
+
+What rulesets ships: 11 skills (SKILL.md, model-invokable — add-tests, debug, five-whys, flush, frontend-design, pairwise-tests, playwright-js, playwright-py, review-code, root-cause-trace, voice) and 18 commands (plain .md prompts under =.claude/commands/=, user-invoked). Every body is markdown instructions; nothing executable lives in the registration layer.
+
+The load-bearing observation: a =/name= reference is just a pointer to a file on disk. Any harness that reads files can execute all 29 artifacts through one resolution rule in its bootstrap entry file: "a skill or command reference (=/voice=, =/review-code=, =/brainstorm=) resolves to =~/.claude/skills/<name>/SKILL.md= or =~/.claude/commands/<name>.md= — read the file and follow it." That single sentence, emitted by the instruction-bootstrap install target, makes the whole library portable with zero per-skill work. commits.md's existing "/voice unavailable — walk the patterns inline" fallback generalizes the same way.
+
+What that rule does not carry:
+
+- Auto-invocation. Claude Code triggers skills from their descriptions; other harnesses won't. The high-value auto-triggers (voice and review-code inside the publish flow) don't actually need it — commits.md invokes them by name at fixed flow points, and the resolution rule covers a by-name invocation. The convenience triggers (debug, frontend-design firing on topic match) degrade to on-request. Acceptable.
+- Native slash registration. Codex-style harnesses have their own custom-prompt mechanism; the 18 commands could also be registered there for ergonomics (an optional install nicety, not a requirement — the resolution rule already works).
+- flush. Its mechanics are Claude Code's (/clear, the self-inject resume hook). Explicitly not ported; the session-plumbing child owns that surface.
+- Harness built-ins (code-review ultra, plan mode, artifacts). Not rulesets' to port; flows that name them need per-runtime alternatives or graceful absence, same as today when they're unavailable.
+
+Verdict: no per-skill porting matrix needed. One resolution sentence in the bootstrap entry file (instruction-bootstrap child), optional native registration as an install nicety, flush explicitly excluded.
+
+* Session plumbing per runtime
+
+Four pieces, examined:
+
+- =session-context-path= is already runtime-aware by design — the =AI_AGENT_ID= shape carries a runtime segment (=host.project.runtime.epoch=) and the resolver is pure bash. Nothing to do.
+- The anchor cycle (suspend entry, startup's interrupted-session recovery, wrap-up's archive rename) is plain files driven by workflow prose. Any agent that reads protocols.org executes it identically. Nothing to do.
+- =self-inject.sh= is harness-agnostic: it types arbitrary strings into a tmux pane. The Claude-bound part is only the *payload* — "/clear" plus a resume line that leans on the SessionStart hook. A Codex auto-flush is a payload variant: the clear command differs (confirm the exact one when wiring) and the second injected line must itself carry the resume instruction ("read .ai/session-context.org and resume") since no hook fires. Arguably simpler than the Claude path.
+- =session-clear-resume.sh= (the SessionStart hook) is Claude-only, and the payload variant above makes it unnecessary off Claude.
+
+Verdict: no build. The flush *skill* stays Claude-official; a codex auto-flush is a documented payload variant to wire the day a codex session actually wants it, not before.
+
+* Decisions for Craig
+
+All four approved by Craig, 2026-07-13:
+
+1. PreCompact downgrade off Claude: prose-only "save before compaction" accepted.
+2. Stop-teardown off Claude: port via Codex notify where available, manual teardown elsewhere.
+3. Runtime-portability motivation note added to the Signal-pager task.
+4. knowledge-base.md capture-layer sentence added (non-Claude runtimes capture into the session log and promote from there).
diff --git a/docs/design/2026-07-14-sentry-workflow-proposal.org b/docs/design/2026-07-14-sentry-workflow-proposal.org
new file mode 100644
index 0000000..d29ceb8
--- /dev/null
+++ b/docs/design/2026-07-14-sentry-workflow-proposal.org
@@ -0,0 +1,150 @@
+#+TITLE: Sentry Workflow — Proposal for rulesets
+#+AUTHOR: Craig Jennings
+#+DATE: 2026-07-13
+
+* Status
+Proposal from the work project to the rulesets session, for implementation as a
+general (shared-layer) workflow: =.ai/workflows/sentry.org= plus a small
+supporting script or two under =.ai/scripts/=.
+
+* Problem
+
+An agent session left running overnight can keep a project clean and healthy on
+a cadence, but the current pieces don't coordinate. Two gaps:
+
+1. *No agent-vs-agent collision guard on shared public files.* =capture-guard=
+ only detects Emacs org-capture buffers (the human case) on one target file,
+ and it's advisory, not a lock. There is no flock anywhere in =.ai/scripts/=.
+ Multiple sessions (several projects, sometimes both daily drivers) can
+ read-modify-write =~/org/roam/inbox.org= and the =agents/= KB nodes at once.
+ The only coordination is the git pull/commit/push discipline in
+ knowledge-base.md, which is optimistic — a pull/push race forks a
+ =*sync-conflict*= file, and the KB search already globs those out, so we know
+ the forks happen and get dropped.
+
+2. *No single serialized runner.* Firing inbox-zero and triage-intake as
+ separate hourly crons means they can overlap each other and other agents.
+
+Sentry is one supervisor loop that runs a fixed sequence of hygiene passes
+serially, holds locks so it never collides with itself or other agents, and
+checkpoints aggressively for crash recovery.
+
+* What sentry is
+
+A single workflow, driven by one interactive =/loop= (one cron), that on each
+fire walks an ordered list of passes inside a single turn. It replaces the
+per-task crons. Design invariants:
+
+- *Serial.* Passes run one at a time, in order. They never overlap each other.
+- *Single-runner lock.* Sentry takes a project-local flock
+ (=.ai/.sentry.lock=) at entry. If a prior fire is still running, this fire
+ skips instead of doubling up.
+- *Shared-file lock.* Any pass that writes a public roam file first acquires a
+ roam-write lock (flock on =~/org/roam/.roam-write.lock=) held across the whole
+ read-modify-write-commit-push cycle, so it serializes against other agents on
+ the same host. =capture-guard= still runs underneath for the human case. (The
+ roam-write lock is new; it's the general guard the dropped KB-guard note would
+ have proposed, folded in here.)
+- *Non-destructive by default.* Read-only and idempotent-filing passes run
+ fully. Anything destructive or judgment-heavy (triage trash/mark-read, task
+ regrades, spec flips) queues its proposal into the digest for morning
+ approval, never fires unattended.
+- *Idempotent + isolated.* Every pass is safe to repeat. A pass that errors
+ doesn't abort the rest; its failure is a loud banner in the digest.
+
+* Crash-recovery spine (applies to every pass)
+
+Crash recovery has been a live consideration. Two enforced steps wrap each pass:
+
+1. *Session-context between passes.* Before the next pass starts, the current
+ pass appends a dated entry to =.ai/session-context.org= recording what it
+ did. A crash mid-sequence leaves a readable "got this far" trail, so the next
+ session resumes without re-deriving state.
+2. *Local commit, no push, on any disk change.* Every pass that writes to disk
+ gets its own =chore(sentry): <pass> — <what changed>= commit on the current
+ branch, unpushed. A crash loses at most one pass. Craig reviews the stack in
+ the morning and pushes deliberately. Conventional messages, no AI
+ attribution. (Open decision: commit on the current branch vs a dedicated
+ =sentry/<date>= branch — see Open decisions.)
+
+The unit per pass is therefore: do the work → log to session-context → commit.
+That triplet is the recovery boundary.
+
+* Passes (v1 — all ten, plus the first-run KB promotion)
+
+Ordered so read-only/pull passes precede writes, and the heaviest external
+sweep sits mid-run:
+
+1. *Roam pull* — =git -C ~/org/roam pull --ff-only= so later reads are fresh.
+ Read-only.
+2. *Inbox zero* — inbox.org roam mode. Route roam-inbox items this project
+ owns. Acquires the roam-write lock for the inbox edit.
+3. *Triage intake* — triage-intake.org. External-accounts sweep, classify,
+ file Action tasks. Destructive actions (trash/mark-read) queue for morning;
+ =mbsync -a= and the sentinel advance run.
+4. *Todo cleanup* — clean-todo.org + todo-cleanup.el + lint-org.el. Convert
+ level-3 done subtasks to dated logs, archive done subtrees, flag lint.
+5. *Task audit* — task-audit.org. Re-check =:solo:=/=:quick:= tags,
+ priority-scheme conformance, bug severity×frequency grades, stale =DOING=
+ specs with closed parents, blocked/blocker reciprocity. Regrades queue for
+ morning.
+6. *Working-files hygiene* — flag =working/<slug>/= dirs whose task is DONE but
+ not filed to =assets/=. Report only; filing is a morning decision.
+7. *Spec status board* — the docs-lifecycle grep. Flag =DOING= specs with
+ closed parents, or specs stuck in =DRAFT=.
+8. *Link integrity* — lint-org broken =file:= links across todo/notes/prep.
+9. *Git health* — uncommitted drift, unpushed commits, stale local branches,
+ whether main is behind origin.
+10. *Prep + symlink freshness* — verify tomorrow's prep and both symlinks
+ resolve.
+11. *KB lesson promotion (first run of the session, and thereafter as new
+ lessons accrue)* — promote recent durable lessons out of the fast capture
+ layer. CLASSIFICATION-GATED:
+ - Personal project → write to the roam KB as =agents/= nodes (pull, lock,
+ write, commit, push), per knowledge-base.md.
+ - Work / denylisted project → NEVER touch roam. Promote to the project's own
+ durable store (e.g. =deepsat/knowledge.org=) instead, and emit the
+ one-line refusal note from the knowledge-base.md refusal contract so
+ nothing is lost silently.
+
+* Digest output
+
+One consolidated, timestamped summary per fire: run time first, then per-pass
+one-liners (what it found / did), the morning-approval queue (proposed
+trash/mark-read, regrades, files-to-move), and any failures as loud banners at
+the top. Delta-only where a pass supports it (triage): a pass with nothing to
+report is one line.
+
+* Cadence
+
+One hourly sentry to start (passes short-circuit fast when there's nothing to
+do). A slower evening interval (every 2–3 hours) is a config knob if hourly
+proves noisy.
+
+* Open decisions for rulesets
+
+1. *Commit target* — per-pass commits on the current branch (simplest, matches
+ "commit but don't push") vs a dedicated =sentry/<date>= branch (keeps the
+ working branch clean, costs a branch dance). Proposal leans current branch.
+2. *Roam-write lock scope* — host-local flock only (solves same-host
+ multi-agent, the common case) vs also hardening roam-sync to surface
+ cross-host conflicts loudly. Proposal: ship the flock now, note the
+ cross-host limit, treat roam-sync conflict-surfacing as a separate follow-up.
+3. *Interval default* — hourly vs evening-only. Proposal: hourly, config knob.
+4. *Which passes gate on a green tree* — e.g. skip todo-cleanup commits if the
+ project suite is red, matching inbox monitor-mode's clean-tree gate.
+
+* Cover note from sender (work, 2026-07-14 00:02)
+
+Intro for the sentry-proposal.org file just sent. This is a NEW general workflow proposal (.ai/workflows/sentry.org + supporting scripts under .ai/scripts/), not an edit to an existing synced file.
+
+What it is: one serialized supervisor loop that runs a project's evening hygiene passes on a cadence, with locks so it never collides with itself or with other agents. It replaces firing inbox-zero + triage-intake as separate crons.
+
+Why now: an agent left running overnight can keep a project clean, but the current pieces don't coordinate. capture-guard only handles the Emacs-capture case on one file and holds no lock; there is no flock anywhere in .ai/scripts/; and separate crons let inbox-zero and triage overlap each other and other agents on shared roam files, which already fork *sync-conflict* files that the KB search globs out.
+
+Three design points worth your judgment:
+1. Crash-recovery spine on every pass (crash recovery has been a live concern): enforce a session-context.org update BETWEEN passes, and a local commit (no push) after any disk change, so a crash loses at most one pass.
+2. A new roam-write flock (~/org/roam/.roam-write.lock) as the shared-file guard. This absorbs a separate 'guard all public KB files' note we considered and dropped. The lock is the better home for that concern than a bespoke per-file guard.
+3. The KB lesson-promotion pass is classification-gated: personal projects promote to roam agents/ nodes; a denylisted work project promotes to its own local store (deepsat/knowledge.org) and never touches roam, per knowledge-base.md's refusal contract.
+
+Companion files to reconcile: knowledge-base.md (the gated promotion + refusal contract), inbox.org (roam mode + the new lock), triage-intake.org (runs as a sentry pass; keep its own triggers), capture-guard (becomes one layer under the roam-write lock, not the whole guard), and clean-todo.org / task-audit.org / lint-org.el (invoked as passes). Open decisions for you are listed at the bottom of the proposal file: commit target, lock scope, interval default, and which passes gate on a green tree.
diff --git a/docs/design/2026-07-15-subproject-pattern-proposal.org b/docs/design/2026-07-15-subproject-pattern-proposal.org
new file mode 100644
index 0000000..61ade18
--- /dev/null
+++ b/docs/design/2026-07-15-subproject-pattern-proposal.org
@@ -0,0 +1,22 @@
+#+TITLE: Proposal: promote the "subproject" pattern into the claude-r
+#+SOURCE: from home
+#+DATE: 2026-07-15 23:24:07 -0500
+
+Proposal: promote the "subproject" pattern into the claude-rules layer.
+
+WHAT WE DID (in home, 2026-07-15)
+We named and built a pattern for what happens when a standalone project is consolidated into another project. home absorbed nine former projects on 2026-06-11 (danneel, finances, jr-estate, kit, health, documents, clipper, elibrary, philosophy). We now call each folded-in unit a "subproject": a former standalone project that lives as a subdirectory, shares the parent's .ai/ scope (one session, one toolchain), but keeps its own files, assets, and history self-contained under its subdir.
+
+The pattern has four parts:
+1. Vocabulary — parent project, subproject, consolidation (the process), subproject archiving. Terminology so we can talk precisely about where a subproject's documents/assets live and archive them cleanly.
+2. Subproject brief — a read-first orientation doc at <subproject>/<subproject>-brief.org (situation, current state, reading list, and for deep ones cast/timeline/key-facts-with-verify-flags). The parent's notes.org holds a thin index + a read-first rule + pointers to urgent items only, never a copy of subproject substance ("one fact, one home").
+3. Lifecycle criteria — when to CREATE a subproject (consolidate: domain fit, low independent cadence, shared-scope safety re: privacy/remote posture, no independent collaborators), and when/how to ARCHIVE one cleanly (terminal state -> move the self-contained subdir to <parent>/archive/, reconcile pointers/links/tags, manifest note).
+4. Instrumentation — a use/miss log, metrics weighted toward Craig-caught errors over self-report, review folded into task-audit, and a falsifiable kill criterion.
+
+WHY IT'S GENERAL (not just a home thing)
+Consolidating a project into another drops what the former project's own startup used to auto-load (its full depth), so sessions start cold and re-read files. That failure recurs whenever ANY project folds into another. The pattern also gives consolidation and archiving a named, repeatable shape, and the self-contained-subdir rule is what makes archiving a move-plus-pointer-reconcile instead of an excavation.
+
+THE PROPOSAL
+Promote this to a rulesets claude-rule (e.g. claude-rules/subprojects.md): the vocabulary, the brief convention, the parent-vs-subproject content criterion, and the create/archive criteria. Any parent/hub project would then inherit it. home's .ai/subprojects.org (attached next) becomes the local instance/index under the general rule. It's a companion to working-files.md (in-progress artifact layout) and docs-lifecycle.md (formal-doc lifecycle) — same family, different scope: this one governs whole-subproject orientation and lifecycle.
+
+Attached: the home instance .ai/subprojects.org as the reference draft to adapt. Apply your value gate; this is a proposal, not a synced-file edit.
diff --git a/docs/design/2026-07-15-subprojects-convention-home-instance.org b/docs/design/2026-07-15-subprojects-convention-home-instance.org
new file mode 100644
index 0000000..a1c4397
--- /dev/null
+++ b/docs/design/2026-07-15-subprojects-convention-home-instance.org
@@ -0,0 +1,282 @@
+#+TITLE: Subprojects — Convention
+#+AUTHOR: Craig Jennings
+#+DATE: 2026-07-15
+
+# The reference spec for the subproject pattern: what a subproject is, when to
+# create one (consolidation), how to keep it legible (the brief + content
+# split), and how to archive one cleanly. Not read every session — the
+# always-on parts (the subproject index + the read-first rule) live in
+# notes.org and point here. Read this when consolidating a project, creating
+# or maintaining a subproject brief, or archiving a subproject.
+
+* Vocabulary (say it the same way every time)
+
+The terminology is the point — it's what lets us talk precisely about where a
+subproject's documents and assets live, and archive them cleanly.
+
+- *Parent project* (a.k.a. hub) — a top-level project that owns an =.ai/=
+ scope: its own protocols, session cadence, memory, inbox, todo. home, work,
+ rulesets, .emacs.d are parent projects. Reached by launching an agent
+ session in its directory.
+- *Subproject* — a formerly-standalone project that has been *consolidated*
+ into a parent and now lives as a subdirectory of it (=danneel/=,
+ =finances/=, =kit/= …). A subproject shares the parent's =.ai/= tooling and
+ session — it is not a separate agent scope — but keeps its own working
+ files, assets, notes, and history self-contained under its one subdirectory.
+- *Consolidation* — the process of turning a standalone project into a
+ subproject of a parent. (home's nine subprojects were consolidated on
+ 2026-06-11.) The inverse — promoting a subproject back to a standalone
+ project — is rare but allowed; it's just consolidation run backward.
+- *Subproject archiving* — retiring a completed or abandoned subproject
+ cleanly. Because a subproject is self-contained under one subdirectory,
+ archiving is a move plus a pointer reconcile, not an excavation.
+- *Subproject brief* — the read-first orientation doc for one subproject, at
+ =<subproject>/<subproject>-brief.org=.
+
+The cross-project boundary rule (=claude-rules/cross-project.md=) governs
+*parent* projects, which are separate =.ai/= scopes. It does *not* apply
+between a parent and its subprojects — they share one scope.
+
+* Why the pattern exists
+
+As a standalone project, each of these units auto-loaded its own notes.org —
+its full depth — at every startup, so recall felt instant. Consolidated into a
+parent, a subproject is one compressed section competing with the others, and
+the parent's startup surfaces hub-wide state, not any one subproject's depth.
+So a session starts cold on a given subproject and re-reads several files on
+demand. This convention restores fast, accurate orientation without
+re-bloating what loads every session — and gives consolidation and archiving a
+named, repeatable shape.
+
+* Document & asset location (what makes clean archiving possible)
+
+Everything for a subproject lives under =<subproject>/= — its =notes.org=,
+=assets/=, working files, event log, briefs. Links *within* a subproject are
+relative to the subproject dir, so the whole tree can move without breaking
+them. The parent's own top-level =assets/= is for parent-level material only,
+never a subproject's. This self-containment is exactly what lets archiving be
+a single =mv= plus a pointer reconcile. Keep it strict: a subproject's asset
+in the parent's shared =assets/= is a future broken link.
+
+* The two layers (parent vs subproject)
+
+- *Always-on layer* — the parent's notes.org. Read at every startup, so every
+ line is a tax paid whether or not it's relevant today. Holds only what must
+ be seen session-independently.
+- *On-demand layer* — the subproject brief + the subproject's notes.org.
+ Loaded only when working that subproject. Holds the depth.
+
+The whole design is: sort content by *when it's needed*, keep the always-on
+layer thin, put the depth one open away.
+
+* Creating a subproject (consolidation criteria)
+
+Consolidate a standalone project into a parent as a subproject when *all* hold:
+
+1. *Domain fit* — the project is a facet of a domain the parent already owns
+ (danneel is home life-admin; finances is home).
+2. *Low independent cadence* — it no longer needs its own session rhythm.
+ You'd rather reach it from the parent than launch it standalone, and its
+ standalone startup/sync/session overhead now outweighs the isolation.
+3. *Shared-scope safety* — it does not need a git/remote/visibility posture
+ distinct from the parent. A public code project, a team repo, or anything
+ with its own CI or gitignore-privacy posture *stays standalone* — folding
+ it would break the parent's privacy model (see
+ =claude-rules/git-hosting-privacy-model= and the gitignore-vs-track
+ decision). Personal/documentation units fold; code-with-a-public-remote
+ does not.
+4. *No independent collaborators* depend on it as a standalone deliverable.
+
+Keep it standalone when any of those fail: active independent development, its
+own team or remote, a distinct visibility/security posture, or activity high
+enough that competing in the parent's hub would bury it.
+
+*The consolidation act itself* (mechanics, mirroring home's 2026-06-11 fold):
+move the project tree under the parent as =<parent>/<subproject>/=; import its
+tasks into the parent's todo with an area =:tag:= and a =:MIGRATED_FROM:=
+property; fold its live reminders/decisions into the parent's notes as
+pointers (see the content split); build the subproject brief; record the fold
+in a consolidation manifest (=docs/consolidation-manifest-<subproject>.org=).
+Retire the source to =~/projects/.retired/<name>= once parity is verified.
+
+* The subproject brief
+
+** Path (fixed — discovery is mechanical)
+
+=<subproject>/<subproject>-brief.org= (=danneel/danneel-brief.org=,
+=finances/finances-brief.org=). The subproject name is the directory name, so
+the brief name follows for free. One predictable name means the read-first
+rule never has to hunt; put descriptive richness *inside*, not in the filename.
+
+** Spine (scales from light to deep)
+
+Required in every brief, even a fifteen-line one:
+- *One-paragraph situation* — what this subproject is, in plain language.
+- *Current state / open threads* — the live stuff. The only high-churn
+ section; everything else is stable.
+- *Reading list* — the subproject's deeper files and what each holds. The
+ brief *points into* notes.org and the rest; it does not copy them.
+
+Add only where the subproject warrants it (danneel, jr-estate, finances earn
+all of these; elibrary or documents may earn none):
+- Cast / contacts.
+- Timeline (for matters with history).
+- Key facts / numbers, with explicit *verify* flags anywhere the underlying
+ record disagrees with itself. A brief that asserts a wrong number is worse
+ than no brief — flag, don't guess.
+
+** Read-first rule (the behavior)
+
+When a session's work targets a subproject — a file under =<subproject>/=, its
+=:tag:=, or Craig naming the topic — open =<subproject>/<subproject>-brief.org=
+*first*, before touching anything else in it. If the subproject has no brief
+yet, creating it is the first step of the work (lazy backfill), and the same
+pass reconciles that subproject's parent-vs-subproject content.
+
+* Parent-vs-subproject content criterion
+
+The test for any piece of content: *if next session I'm working a different
+subproject, do I still need to see this?* Three bins:
+
+1. *Yes, and it's a fact or policy* → hub-level → the parent's notes.org.
+ Machine names, mail policy, calendar access, the hub map, the subproject
+ index.
+2. *Yes, but only because it's urgent* — a deadline or blocking status that
+ would be missed if it only lived where I might not look this week → the
+ parent holds a *one-line pointer* (item + date + "see the subproject
+ brief"); the substance stays in the subproject.
+3. *No* → the subproject. Working depth: contacts, contract facts, case
+ theory, evidence, document index, and any reminder that isn't time-critical.
+
+One hard rule over both axes: *one fact, one home.* The parent never copies
+subproject substance — it points. The parent holds the trigger; the subproject
+holds the truth. Every parent↔subproject duplicate is a second surface that
+goes stale.
+
+* Archiving a subproject (archiving criteria + clean process)
+
+Archive a subproject when it reaches a *terminal* state:
+- *Complete* — the work is done and closed (a settlement signed, a probate
+ closed, a trip finished): no live threads, no open tasks, nothing
+ time-sensitive in the parent still pointing at it; or
+- *Abandoned / superseded* — dropped, or replaced by other work.
+
+Clean archive process (self-containment is what makes each step a one-liner):
+1. Close or settle its tasks in the parent's todo (DONE / CANCELLED), per the
+ todo completion rules.
+2. Move the whole subproject tree to the parent's archive:
+ =<parent>/archive/<subproject>/= (keep it intact — archiving is reversible,
+ not deletion).
+3. Update inbound =file:= links that pointed into it (grep first, per the
+ keep-links-current rule).
+4. Move or retire its brief with it, and drop its row from the active
+ subproject index (or mark it =archived= there).
+5. Strip its pointers/reminders from the parent's always-on layer, and retire
+ its =:tag:= from active use.
+6. Record the archive in a manifest note
+ (=docs/archive-manifest-<subproject>.org=): why, when, where it moved,
+ what links were repointed. Mirrors the consolidation manifest.
+
+The =:LAST_UPDATED:= on a brief plus "no open tasks under the tag" is the
+signal a subproject is a candidate for archiving — surfaced at the review
+cadence, never auto-archived.
+
+* Freshness
+
+- *Wrap-up hook*: at wrap-up, for each subproject touched this session, refresh
+ its brief's Current-state section and bump its =:LAST_UPDATED:= date. Stable
+ sections rarely change, so this is cheap.
+- *Staleness nudge*: a brief whose =:LAST_UPDATED:= is old while its subproject
+ was recently active is worth surfacing at startup.
+
+* Self-improvement loop
+
+Every real use emits a signal, and the signal is *captured*, not silently
+acted on:
+- Brief answers the question → *hit*.
+- Brief is missing something, stale, or wrong → *miss*, logged in
+ [[file:subprojects-log.org][subprojects-log.org]].
+- Each miss drives *two* updates: *local* (fix that brief) and, if the same
+ kind of miss recurs across subprojects, *structural* (fix this spine/spec,
+ not just the one file). The structural half is the actual self-improvement —
+ grow-and-refine from real use rather than rewriting from scratch (the ACE
+ idea behind the =codify= skill).
+
+* Metrics
+
+Split by who can trust them.
+
+*Process signals (I observe, cheaply):*
+- *Orientation cost* — files/tool-calls before my first correct action in a
+ subproject. Target: ~1 (the brief), occasionally a second deep dive.
+- *Brief hit rate* — fraction of subproject questions answered from the brief
+ alone. Should climb.
+- *Freshness gap* — days between =:LAST_UPDATED:= and last subproject activity.
+ Target ~0.
+
+*Outcome signals (Craig is ground truth — weight these over the above):*
+- *Craig-caught errors* — the real quality bar. Target: trend to zero. If
+ briefs work, Craig stops catching me.
+- *Felt speed* — did dropping into the subproject feel fast and right.
+
+*Honesty caveat.* Most process signals are self-observed, and self-report is
+biased toward "hit." So the external signals (Craig-caught errors, felt speed)
+outrank mine, and the log is auditable so Craig can spot-check my self-grades.
+The instrumentation must stay lighter than the briefs, or it rots like an
+unmaintained brief. Small N, no clean A/B — this is directional evidence, not
+proof.
+
+* Review cadence
+
+Fold a "subprojects health" check into the existing task-audit cadence: read
+the log, look at the hit-rate / correction / freshness trends, flag any
+subproject that's gone terminal (archive candidate), and check the
+parent-vs-subproject criterion — anywhere the parent holds a copy where it
+should hold a pointer. Decide continue / adjust / kill for the pattern itself.
+
+* Kill criteria (falsifiable)
+
+After ~6-8 subproject-sessions across ≥3 subprojects, drop or rework the
+pattern if:
+- the correction rate isn't falling, or Craig is still catching errors; or
+- the freshness gap keeps reopening despite the wrap-up hook; or
+- maintenance costs more time than the orientation it saves.
+
+Success is the mirror: orientation down to ~1 read, Craig catching ~nothing,
+briefs current, and I'm right fast in any subproject cold.
+
+* Rollout
+
+- Define the convention (this file) + the index scaffold in notes.org. Done
+ 2026-07-15.
+- *Lazy backfill*: the next session that touches a subproject with no brief
+ creates the brief as its first step — cost paid exactly when it's used.
+- *Reconcile in the same pass*: building a subproject's brief includes
+ reconciling its parent-vs-subproject content — push substance down, leave
+ pointers up, drop dead reminders.
+- *Promotion*: this pattern is home-instanced but general. Consolidation into
+ subprojects will recur whenever any project folds into another, so this is a
+ candidate to promote into the rulesets rules layer (proposed to rulesets
+ 2026-07-15).
+
+* Adoption status
+
+| Subproject | Brief exists | Reconciled | Notes |
+|------------+--------------+------------+---------------------------------------|
+| danneel | yes | yes | First worked instance, 2026-07-15. |
+|------------+--------------+------------+---------------------------------------|
+| clipper | no | no | Lazy backfill on next touch. |
+|------------+--------------+------------+---------------------------------------|
+| documents | no | no | Lazy backfill on next touch. |
+|------------+--------------+------------+---------------------------------------|
+| elibrary | no | no | No notes.org yet; lightest subproject.|
+|------------+--------------+------------+---------------------------------------|
+| finances | no | no | Deep; earns the full spine. |
+|------------+--------------+------------+---------------------------------------|
+| health | no | no | Has its own notes.org. |
+|------------+--------------+------------+---------------------------------------|
+| jr-estate | no | no | Deep; earns the full spine. |
+|------------+--------------+------------+---------------------------------------|
+| kit | no | no | Lazy backfill on next touch. |
+|------------+--------------+------------+---------------------------------------|
+| philosophy | no | no | Lazy backfill on next touch. |
diff --git a/docs/design/2026-07-16-polyglot-bundle-collision.txt b/docs/design/2026-07-16-polyglot-bundle-collision.txt
new file mode 100644
index 0000000..b7e1257
--- /dev/null
+++ b/docs/design/2026-07-16-polyglot-bundle-collision.txt
@@ -0,0 +1,30 @@
+Proposal: language bundles collide on coverage-makefile.txt in a polyglot project, and the second install loses silently.
+
+Context: scaffolded a new project (clock-panel) on 2026-07-16 with both the python and typescript bundles installed into the same project.
+
+What happened. The second install printed:
+
+ [skip] coverage-makefile.txt already exists (use FORCE=1 to overwrite)
+
+Both bundles ship a file at that same path. Python installed first and won, so the project ended up with Python's coverage fragment only. The TypeScript one (c8, Istanbul json-summary) never landed. The line reads like routine idempotence, not a dropped deliverable, so it's easy to skim past. I only caught it by checking which targets were actually in the file.
+
+The workaround is bad. FORCE=1 gets the TypeScript fragment but overwrites the Python one, so you can't have both without renaming by hand between installs. FORCE=1 also re-seeds CLAUDE.md, which is fine on a fresh project and destructive on a customized one. What I did in clock-panel: install python, mv coverage-makefile.txt coverage-makefile-python.txt, install typescript FORCE=1, mv coverage-makefile.txt coverage-makefile-typescript.txt. Both fragments now coexist under language-suffixed names.
+
+The deeper problem, which the filename collision only hints at: both fragments define targets with the SAME names.
+
+ coverage:
+ coverage-summary:
+
+So even with both files present, a polyglot project can't copy both into one Makefile. It gets duplicate targets. The fragments assume they're the only language in the project. Renaming the files doesn't fix that, it just makes both sets of instructions visible while leaving the conflict for whoever pastes them.
+
+Options, in the order I'd weigh them:
+
+Suffix the shipped filename per bundle (coverage-makefile-python.txt, coverage-makefile-typescript.txt) so installs never collide. Cheap, and it makes the multi-bundle case work by default. It leaves the target-name conflict.
+
+Namespace the targets too (coverage-python / coverage-typescript, with a coverage target that runs both). Fixes the real problem for polyglot projects. Bigger change, and it makes single-language projects type a longer target name unless there's an alias.
+
+At minimum, make the skip line loud when the skipped file came from a DIFFERENT bundle than the one that wrote it. A skip that means "already yours" and a skip that means "another language's file is here and yours is being dropped" currently look identical.
+
+Worth deciding whether polyglot projects are a supported case at all. If they are, the bundles need a collision story. If they aren't, install-lang could say so when it detects a second bundle going into a project that already has one, rather than half-installing.
+
+Also flagged separately in a note from home today: the same install-lang run is where I'd look for other per-bundle files that could collide on a shared filename. I only checked coverage-makefile.txt.
diff --git a/docs/design/task-review.org b/docs/design/task-review.org
index 6c6dac7..c9ae023 100644
--- a/docs/design/task-review.org
+++ b/docs/design/task-review.org
@@ -1,5 +1,5 @@
#+TITLE: Design: Daily Task-Review Habit
-#+AUTHOR: Craig Jennings & Claude
+#+AUTHOR: Craig Jennings
#+DATE: 2026-05-16
#+OPTIONS: toc:nil num:nil