Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions docs/conventions/topic-docs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,94 @@
# Changelog — topic-docs convention

## 2.4.0 — 2026-07-27

- **An Ephemeral row joins the tier table** (additive). The table sorts
documents by one question — does anything downstream enforce against
this? — which cannot express lifetime, so its finest-grained cell, the
memory tier, conflated state a later reader must find with files
nothing downstream ever reads again. With no row naming the second
kind, two plugins answered the same unasked question differently:
`adhd:clarify` reached for the session scratchpad — an undocumented
harness path (zero occurrences in the full docs corpus, keyed by
working directory, and declined three times upstream as a supported
surface) — while `architecture:improve` had independently settled on a
`mktemp` temp file. That divergence, not a shared mistake, is the
trigger: the convention registry calls for an owner doc before a
second plugin adopts, and two incompatible answers were already in
the tree. This row names the tier and moves `adhd:clarify` off the
scratchpad; `architecture:improve` keeps its existing temp behavior
and merely gains the contract's name for it. The row states five rules (one deterministic path; the lifetime
outlives the call, so a producer that returns a path never deletes the
file before returning; never the scratchpad; nothing durable; the FORM
of any temp-root override is a manifest `userConfig` rather than a
tracked key), carries a re-derivation trigger, and records why
git-visibility, promotion-stage, and write-contention each needed no
change.

A sweep of every markdown producer in `plugins/**` for throwaway-file
behavior found seven more surfaces the new row governs, and all seven
are corrected here rather than deferred, because a row that leaves
known contradictions standing in the tree is not yet a contract.
`/planning:interview`'s dense-round decision table moves out of the
memory slice into this tier: the skill's own text names the ledger and
terminal as the record, a resumed session picks up from the first open
ledger checkbox, and the plugin's binding never listed the file — so
it was memory-tier state nothing read. `/education:teach` moves the
other way and is the reason the row is a **classification**, not a
destination: its concept HTML *is* that concept's lesson artifact in a
workspace `resume` reopens, so it is machine state and stays there —
the defect was a bullet titled "Ephemeral placement" offering the
workspace **or** OS temp for one artifact. Its `primer` action, which
creates no workspace at all, is the genuinely ephemeral half and had
no resolvable path before. One further producer is recorded but not
changed here: `/prototype:explore-directions` writes its standalone
HTML mockup to "an OS temp **or** gitignored scratch location", the
same non-deterministic branch rule 1 forbids — but choosing which of
the two wins has to be reconciled against that plugin's own throwaway
discipline, which deliberately locates prototypes next to the
production code they mimic. That is a design question, not a typo, and
it gets its own change. Every other sweep hit is producer-consumed
plumbing (a `mktemp` file the producer itself reads and hands to no
one) or deliberate machine state — `/education:quiz-me`'s report
library is the clearest of the latter, since its `recall` action reads
those reports back weeks later.

Rule 2 is stated because both existing adopters hand their file back
as a path for the user to open — a `finally` cleanup would race the
reader and return a dead path. The row deliberately does **not**
promise the file dies with the session: no documented Claude Code
mechanism prunes that temp tree (`cleanupPeriodDays` is scoped to
`~/.claude/`, a different tree), so the honest guarantee is that the
file OUTLIVES the invocation and is reclaimed only when the platform
reclaims its temp tree. That is why one run writes one file and never
a tree.

Rule 1 also constrains the template's **shape**, not just its root.
The `XXXXXX` placeholders must be trailing, because BSD `mktemp` on
macOS substitutes only trailing Xs — so a template appending an
extension after them (`<prefix>-XXXXXX.html`) cannot create the file
on macOS. Two adopters had independently written exactly that form,
which is the evidence that naming the temp root was not a sufficient
rule; a producer wanting a meaningful filename takes the `-d` form and
writes a fixed name inside the run directory, which is why the row
admits a temp file **or** a directory.

Two claims are stated as constraints rather than capabilities, because
neither capability exists today. Rule 1 does not route
`CLAUDE_CODE_TMPDIR` into the temp primitive: that variable overrides
the temp directory Claude Code uses for its own internal files, and
the env-var reference states that unsandboxed Bash commands inherit
the shell's `$TMPDIR` unchanged, so a plugin shelling out to `mktemp`
never observes it. Rule 5 fixes the form an override takes *if* a
plugin offers one; neither current adopter declares `userConfig`, and
the rule no longer reads as a promise that one is available.

Minor, not major: no tier moves, no `topic-docs.yaml` key is renamed,
the slug spec is untouched, and no visibility guarantee changes — the
ephemeral row is slug-less and invisible to every other execution
context by construction, so it takes no row in the visibility matrix.
The eight bindings need no synchronized adoption wave.

## 2.3.0 — 2026-07-26

- **The `.worktreeinclude` template carries sub-slices** (additive). Its
Expand Down
126 changes: 121 additions & 5 deletions docs/conventions/topic-docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,26 @@ were persisted forever.

## The two tiers (and their neighbors)

Placement follows document **nature**, decided by one question: does
anything downstream *enforce against* this document?
Placement follows document **nature**, decided by two questions in order.
First: does anything downstream *enforce against* this document? Yes puts
it in the contract tier while the task runs, and the durable tier once it
outlives the task. Second, for everything else: once this run ends, does
anything read the document again — a later session, another checkout, a
reviewer, or the producer itself on resume? **No** is the ephemeral row,
and it is the only row that answers no. **Yes** is the memory tier when
that reader is scoped to this checkout, and machine state when it is
scoped to the machine across projects. Membership answers the second
question, not frequency: a file inside a slice a later session reopens is
read again even if that session rarely looks at the file itself.

| Tier | Location (default) | Git | Holds |
|---|---|---|---|
| Ephemeral | An OS-API-created temp file or directory, one per run | Never in the repo | Files nothing downstream reads: a rendered HTML view, a spill file, a throwaway |
Comment thread
kyle-sexton marked this conversation as resolved.
Comment thread
kyle-sexton marked this conversation as resolved.
Comment thread
kyle-sexton marked this conversation as resolved.
| Memory | `.work/<slug>/` | Never committed (self-ignoring) | `EXPLORE.md`, `RESEARCH.md`, `<stage>-checklist.md`, `baselines/`, raw captures and scratch |
| Memory, concern-scoped | `.work/handoffs/`, `.work/reviews/<branch-slug>/` | Never committed | session handoffs; review reports — their axes are session and branch, so they sit outside topic slices |
| Contract | `docs/topics/<slug>/` | Committed **on the task branch only**; pruned before merge | `PLAN.md` (Brief + Plan), `PRD.md`, `design/` (incl. the `design-threads.md` / `design-resolution.md` gate files), `verification/` (the distilled manifest) |
| Durable | knowledge-vault seam — default backend `docs/adr/`, `docs/specs/` | Committed, permanent | promotion targets |
| Machine state | `${CLAUDE_PLUGIN_DATA}`; `.claude/observability/` | Never committed | telemetry, caches |
| Machine state | `${CLAUDE_PLUGIN_DATA}`; `.claude/observability/` | Never committed | telemetry; caches; durable machine-scoped state a later session reopens across projects |

Locations are the documented defaults; the tracked concern file's
`contract_dir` / `memory_dir` keys override the memory and contract
Expand All @@ -53,6 +63,110 @@ log — git log, PR threads, and tracker comments provide this natively for
tracked contracts) and a default-persisted `brainstorm.md` (ideation is
conversation output; persisting is opt-in, into the memory tier).

### The ephemeral tier

The memory tier's one cell conflated two kinds with opposite
requirements: state that must SURVIVE the session as a read input
(resume artifacts, ledgers, captures) and files nothing downstream ever
reads again. The ephemeral row names the second. It is slug-less and
path-less by design — a run creates its own file or directory through
the platform's temp primitive — so it is invisible to every other
execution context by construction and takes no row in the visibility
matrix.

Five rules hold at this row:

1. **Resolve one deterministic path.** Never branch on whether a harness
injected a scratchpad path or set `CLAUDE_JOB_DIR`: those surfaces
are disjoint by session kind (`CLAUDE_JOB_DIR` is set for background
sessions only), so branching makes file placement depend on how the
session was launched, which is invisible from inside the plugin. Use
the platform's standard temp primitive and **name the temp root in the
template**: on Unix `mktemp "${TMPDIR:-/tmp}/<prefix>-XXXXXX"` (add
`-d` for a directory), the positional-template form both GNU and BSD
`mktemp` accept identically; on Windows a user-scoped temp under
`%LOCALAPPDATA%\Temp`. The `XXXXXX` placeholders must be **trailing**:
BSD `mktemp` (macOS) substitutes only trailing Xs, so a template that
appends an extension after them — `<prefix>-XXXXXX.html` — is not
portable. A producer that wants a meaningful filename takes the `-d`
form and writes a fixed name inside the run directory, which is why
the row above admits a temp file **or** a directory. A bare relative
template does **not** reach the temp tree — `mktemp report-XXXXXX`
creates the file in the current working directory, which is the
consumer's repository (reproduced against GNU coreutils 8.32,
2026-07-27) — and the flags
that would fix it are not portable (`--tmpdir` is GNU-only, `-t` is
deprecated there). That root is the ambient `$TMPDIR` or system
default — **not** `CLAUDE_CODE_TMPDIR`, which overrides the temp
directory Claude Code uses for its *own internal* files: the env-var
reference states that "Unsandboxed Bash commands inherit your shell's
`$TMPDIR` unchanged" (verified 2026-07-27). A plugin shelling out to
`mktemp` therefore never observes that override, and no plugin should
claim it does.
2. **The lifetime outlives the call.** A path handed back to the user
must still be readable when they open it, so a producer that RETURNS
a path does not delete the file in a `finally` — that races the
reader and hands back a dead path. `finally` cleanup is correct only
for a file the producer itself consumes and hands to no one. How long
a returned file actually lives is the platform's decision, not this
contract's: it sits in the OS temp tree until something reclaims that
tree, and nothing documented does (see below). Size the footprint for
a file that OUTLIVES the session, not one that vanishes with it.
3. **Never the session scratchpad.** Plugins never require it, publish
pointers to it, or change semantics based on its presence.
4. **Nothing durable lands here.** If a later session, another checkout,
or a reviewer must read the file, it belongs in the memory or
contract tier — this row is not a shortcut past their rules.
5. **If a plugin exposes a temp-root override, its form is a manifest
`userConfig` typed `directory`, defaulting to empty** — never a
`.claude/topic-docs.yaml` key. A temp root is machine scope; a
tracked key would imply a team decision about a location no teammate
can observe. This constrains the FORM of an override, and does not
oblige any plugin to offer one — no implementer declares one today, so
the ambient temp root is currently the only root in play. Per the
configuration ownership table in `docs/PLUGIN-PHILOSOPHY.md`.

**Keep the footprint small.** Nothing reclaims this tree on a schedule:
verified 2026-07-26 against the full Claude Code docs corpus, no
documented cleanup, retention, TTL, or pruning mechanism covers the temp
tree Claude Code writes under, and the one documented retention setting,
`cleanupPeriodDays`, is scoped to `~/.claude/` application data — a
different tree. That is precisely why rule 2 refuses to promise the file
dies with the session, and why the footprint rule is load-bearing rather
than tidy-minded: a producer writes one file, or one directory, per run
— never an accumulating tree — and rule 4 does real work, since anything
worth keeping belongs in a tier that is actually managed.

**Why not the session scratchpad.** Verified 2026-07-26 against primary
sources: zero occurrences of "scratchpad" in the full Claude Code docs
corpus (`https://code.claude.com/docs/llms-full.txt`) — it is
system-prompt-injected only. It is keyed by working directory, so every
worktree gets a distinct root, and scoped by session UUID. Measured on
one machine: 230 directories, 31,260 files, 2.96 GB accumulated in ten
days with no pruning observed. Three upstream requests to make it a
supported surface are all closed as not-planned
([#45745](https://github.com/anthropics/claude-code/issues/45745),
[#17936](https://github.com/anthropics/claude-code/issues/17936),
[#21248](https://github.com/anthropics/claude-code/issues/21248)) —
upstream has not merely failed to document it, it has declined three
times to support it.

**Re-derivation trigger.** An upstream versioned interface for the
scratchpad that guarantees injection, lifecycle, ownership, quota, and
cleanup semantics reopens rule 2, and the change lands here as a
recorded changelog entry. The dated verification above is an as-of
record, never standing authority.

**Why the other three axes needed no change.** The placement question
was re-derived across four axes and only lifetime was uncovered:
git-visibility is already the tier table's own organizing question;
promotion-stage is already carried by the contract-slice lifecycle and
the two graduation edges; and write-contention is already solved at the
work-item tracker seam
([`plugins/work-items/reference/tracker-seam.md`](../../../plugins/work-items/reference/tracker-seam.md)),
whose race-safe claim-and-lease is provider-neutral. Recorded so the
analysis is not re-run.

### The single-home rule

Every fact has exactly one home. Any other surface — a handoff, a
Expand Down Expand Up @@ -368,9 +482,10 @@ relationship to the contract is fully stated by their table row.

| Plugin | Writes | Tier(s) | Binding |
|---|---|---|---|
| adhd | rendered decision-table HTML view | ephemeral | by reference — the ephemeral row's five rules are its entire relationship |
| discovery | `EXPLORE.md`, `RESEARCH.md` | memory | delta doc |
| architecture | `deepening-candidates-<timestamp>.md` (per-lens candidate ledgers) | memory | delta doc |
| planning | `PRD.md`, `PLAN.md` (Brief), `design/`, opt-in brainstorm persist | contract + memory | delta doc |
| architecture | `deepening-candidates-<timestamp>.md` (per-lens candidate ledgers); deepening HTML report | memory + ephemeral | delta doc |
| planning | `PRD.md`, `PLAN.md` (Brief), `design/`, opt-in brainstorm persist; five optional rendered HTML views (dense-round decision table, PRD pitch, brainstorm reaction page, plan view, design topology) | contract + memory + ephemeral | delta doc |
| implementation | `PLAN.md` (Plan/progress), `DEVIATIONS.md`, status summaries | contract + memory | delta doc |
| verification | `verification/` manifest; baselines, raw captures | contract + memory | delta doc |
| session-flow | handoffs; running-retro ledgers | memory (`handoffs/`, `running-retros/`) | delta doc |
Expand All @@ -379,6 +494,7 @@ relationship to the contract is fully stated by their table row.
| toolchain | nothing of its own — its setup skill offers the concern file | — | delta doc |
| knowledge | ingest trees — **formal carve-out**: its work root resolves through its own `library_dir` seam, not `memory_dir`; slug conformance is form-only (charset/reserved names), and its nested `<epic>/<slug>/` sub-slices are sanctioned | memory (carved out) | by reference — the carve-out above is its entire delta |
| claude-ops | telemetry | machine state | by reference — machine state resolves no contract paths |
| education | per-concept `lesson` / `reference` / `exercise` slices; `quiz-me` report library (`recall` reads it back); `primer` vocabulary-ladder HTML | machine state + ephemeral | by reference — its workspace and report library are its own `${CLAUDE_PLUGIN_DATA}` layouts, and only the workspace-less `primer` render resolves a path this contract owns |
| docs-hygiene | (reader) audit-noise detector recognizes these shapes | — | by reference — reads shapes, writes nothing |

### Implementers restate the rules; they do not share a source
Expand Down
2 changes: 1 addition & 1 deletion plugins/adhd/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "adhd",
"version": "0.3.0",
"version": "0.3.1",
"description": "Shape and restructure the assistant's output for a reader with ADHD — action-first, low-friction, and digestible. adhd:shape is a standing session posture: lead with the concrete next action, number multi-step work, restate state across turns, cap and rank lists, give concrete time estimates, make wins visible, and cut preamble, recap, and closers. adhd:clarify is a one-shot reshape of a dense, decision-heavy artifact already on screen — chunk it one-decision-at-a-time, define the session's own jargon, and surface exactly what you must decide, faithfully (operative terms quoted verbatim, no altitude loss), rendered as an HTML decision table for big content. Reauthored in part from ayghri/i-have-adhd (MIT). Deliberately mutually exclusive with terse-for-tokens output shapers like caveman — opposite objectives.",
"author": {
"name": "Melodic Software",
Expand Down
14 changes: 14 additions & 0 deletions plugins/adhd/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,20 @@
All notable changes to the `adhd` plugin are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning.

## [0.3.1]

### Changed

- `clarify` places its local HTML file in the topic-docs **ephemeral tier**
instead of preferring the session scratchpad. The old wording branched on
whether the harness injected a scratchpad path, which made placement depend
on how the session was launched — invisible from inside the skill — and
depended on an undocumented surface upstream has declined three times to
support. The skill now resolves one temp path deterministically, and states
explicitly that the file is not deleted before returning — the path is the
delivery mechanism, so it must stay readable when the reader opens it. See
`docs/conventions/topic-docs/README.md` §"The ephemeral tier".

## [0.3.0]

Fixes every finding from the 2026-07-23 live audit (handoff item
Expand Down
Loading