@caddis/cli 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (21) hide show
  1. package/bundles/antigravity-plugin/plugin.json +1 -1
  2. package/bundles/antigravity-plugin/scripts/check_doc_coverage.py +1 -1
  3. package/bundles/antigravity-plugin/scripts/setup_project_ai.py +34 -5
  4. package/bundles/antigravity-plugin/skills/_registry.md +2 -0
  5. package/bundles/antigravity-plugin/skills/safe-cleanup/SKILL.md +176 -0
  6. package/bundles/antigravity-plugin/skills/tech-explainer/SKILL.md +280 -0
  7. package/bundles/antigravity-plugin/skills/tech-explainer/assets/skeleton.md +255 -0
  8. package/bundles/antigravity-plugin/skills/tech-explainer/evals/evals.json +50 -0
  9. package/bundles/antigravity-plugin/skills/tech-explainer/references/diagrams.md +237 -0
  10. package/bundles/antigravity-plugin/skills/tech-explainer/references/language.md +141 -0
  11. package/bundles/antigravity-plugin-extras/plugin.json +1 -1
  12. package/bundles/antigravity-plugin-extras/skills/_registry.md +1 -0
  13. package/bundles/antigravity-plugin-extras/skills/safe-cleanup/SKILL.md +176 -0
  14. package/bundles/codex/.codex/skills/devops/safe-cleanup/SKILL.md +176 -0
  15. package/bundles/codex/.codex/skills/docs/tech-explainer/SKILL.md +280 -0
  16. package/bundles/codex/.codex/skills/docs/tech-explainer/assets/skeleton.md +255 -0
  17. package/bundles/codex/.codex/skills/docs/tech-explainer/evals/evals.json +50 -0
  18. package/bundles/codex/.codex/skills/docs/tech-explainer/references/diagrams.md +237 -0
  19. package/bundles/codex/.codex/skills/docs/tech-explainer/references/language.md +141 -0
  20. package/bundles/manifest.json +3 -3
  21. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "caddis",
3
- "version": "1.3.83",
3
+ "version": "1.3.87",
4
4
  "description": "caddis — agent-agnostic dev harness as an Antigravity (agy) plugin: TDD skills + commands, single-sourced with the Claude Code plugin."
5
5
  }
@@ -728,7 +728,7 @@ def remove_rows_with_targets(text: str, targets: list[str]) -> str:
728
728
  def _atomic_write(path: Path, text: str) -> bool:
729
729
  """Write ``text`` via a same-dir, per-process temp file + atomic replace. Returns True on success.
730
730
 
731
- A crash mid-write can't leave a truncated DOC-MAP (mirrors dream_memory.save_facts). The temp name
731
+ A crash mid-write can't leave a truncated DOC-MAP. The temp name
732
732
  is PID-tagged so two concurrent runs don't fight over one temp. Returns **False (never raises)** if
733
733
  the target is locked — on Windows an open editor / AV can hold it — so a maintenance run degrades to
734
734
  a clear message instead of a stack trace.
@@ -896,6 +896,7 @@ agent-log.jsonl
896
896
  .last-usage-review
897
897
  relay.md
898
898
  relay/
899
+ session-state.md
899
900
  PROJECT-FACTS.md
900
901
  memory.jsonl
901
902
  """
@@ -1131,12 +1132,13 @@ def scaffold_artifact_dir(target: Path, dry: bool) -> list[str]:
1131
1132
  """Create the harness-owned .caddis/ artifact tree + a default .gitignore + a config example.
1132
1133
 
1133
1134
  Committed subdirs: plans, handoffs, agent-docs, prd, kb, prompts, comms. Transient state
1134
- (reviews/*.html, usage-log.jsonl, .last-usage-review, relay*, PROJECT-FACTS.md, memory.jsonl)
1135
- is gitignored. .caddis/ is the default home for every working-artifact kind (Track A
1135
+ (reviews/*.html, usage-log.jsonl, .last-usage-review, relay*, session-state.md,
1136
+ PROJECT-FACTS.md, memory.jsonl) is gitignored. .caddis/ is the default home for every working-artifact kind (Track A
1136
1137
  Phase A3) — kb/ and prompts/ round out plans/prd/agent-docs/reviews so nothing has to
1137
1138
  scatter to the repo root or .github/. Also drops a documented `config.toml.example`
1138
- (guard/doc_coverage). Idempotent; never clobbers an existing .gitignore or
1139
- config example.
1139
+ (guard/doc_coverage). Idempotent. An existing .caddis/.gitignore is never clobbered or
1140
+ reordered, but MISSING entries are appended — otherwise a repo set up before an entry
1141
+ existed would never receive it. The config example is left alone entirely.
1140
1142
 
1141
1143
  Writes into the repo's `.caddis/`.
1142
1144
  """
@@ -1198,7 +1200,34 @@ def scaffold_artifact_dir(target: Path, dry: bool) -> list[str]:
1198
1200
  notes.append(f"scaffold: wrote {label}/kb/environment-map.md")
1199
1201
  gi = root / ".gitignore"
1200
1202
  if gi.exists():
1201
- notes.append(f"scaffold: {label}/.gitignore present — kept")
1203
+ # APPEND what is missing rather than keeping the file frozen. The old behaviour was
1204
+ # "present -> kept", so a repo scaffolded before an entry was added NEVER received it.
1205
+ # That is not hypothetical: `session-state.md` is rewritten by the Stop hook on every
1206
+ # turn and contains the operator's verbatim words, and an already-scaffolded repo was
1207
+ # sitting one hook-fire away from committing it.
1208
+ #
1209
+ # Only ever adds; a line the user wrote themselves is never touched or reordered.
1210
+ try:
1211
+ existing = gi.read_text(encoding="utf-8")
1212
+ except OSError:
1213
+ existing = ""
1214
+ have = {ln.strip() for ln in existing.splitlines()}
1215
+ missing = [
1216
+ ln for ln in ARTIFACT_GITIGNORE.splitlines()
1217
+ if ln.strip() and not ln.startswith("#") and ln.strip() not in have
1218
+ ]
1219
+ if not missing:
1220
+ notes.append(f"scaffold: {label}/.gitignore present — up to date")
1221
+ else:
1222
+ if not dry:
1223
+ block = "" if existing.endswith("\n") or not existing else "\n"
1224
+ block += "\n# added by caddis — newer transient artifacts\n"
1225
+ block += "\n".join(missing) + "\n"
1226
+ gi.write_text(existing + block, encoding="utf-8")
1227
+ notes.append(
1228
+ f"scaffold: {label}/.gitignore — added {len(missing)} missing entry(ies): "
1229
+ + ", ".join(missing)
1230
+ )
1202
1231
  else:
1203
1232
  if not dry:
1204
1233
  root.mkdir(parents=True, exist_ok=True)
@@ -39,6 +39,7 @@
39
39
  | Ci Cd Pipeline | `ci-cd-pipeline/` | CI/CD pipeline design and implementation for GitHub Actions, Azure DevOps, and general pipeline architecture. Use when creating build pipelines, deployment workflows, quality gates, environment promotion strategies, or automating release processes. |
40
40
  | Gh Cli | `gh-cli/` | GitHub CLI operations — issues, PRs, releases, and repo management |
41
41
  | Git Commit | `git-commit/` | Create well-structured conventional commit messages following Conventional Commits standard. Use when committing changes, preparing PRs, or generating changelogs. |
42
+ | Safe Cleanup | `safe-cleanup/` | Use BEFORE deleting any directory tree, clearing a scratchpad, removing git worktrees, or "tidying up" generated folders - especially on Windows, where a recursive delete that meets a junction or symlink destroys the link's TARGET. Also use when verifying that a Python venv or node_modules survived a cleanup, because pip check and file counts both report healthy on a half-deleted environment. |
42
43
  | Using Git Worktrees | `using-git-worktrees/` | Use when starting feature work that needs isolation from current workspace or before executing implementation plans - creates isolated git worktrees with smart directory selection and safety verification |
43
44
  | Windows Deployment | `windows-deployment/` | Deploy FastAPI + React/Vite apps to Windows Server with NSSM services, reverse proxy (IIS or nginx), and git-pull workflow. Use when deploying any web app to a Windows prod server, setting up NSSM services, configuring IIS or nginx reverse proxy, making code environment-aware for dev/prod, or troubleshooting prod deployment issues. |
44
45
 
@@ -48,6 +49,7 @@
48
49
  |-------|------|-------------|
49
50
  | Code Documentation | `code-documentation/` | Writing effective code documentation - API docs, README files, inline comments, and technical guides. Use for documenting codebases, APIs, or writing developer guides. |
50
51
  | Explainer Doc | `explainer-doc/` | Write a document that hands off work to a team who did NOT do the investigation — an external vendor, another team, a contractor — and who must be able to act on it alone. Use when the user says "write a spec for the X team", "document this for someone outside the team", "explainer doc", "implementation spec for a vendor", "hand this off to Y", or is about to write a design doc whose primary reader did not live through the debugging/design process. NOT for internal specs where the reader already has full context (use technical-writing or writing-plans instead) — this is specifically for the "stranger must follow it alone" case. |
52
+ | Tech Explainer | `tech-explainer/` | >- |
51
53
  | Technical Writing | `technical-writing/` | Technical documentation best practices for READMEs, API docs, architecture docs, runbooks, and developer guides. Use when writing or reviewing documentation, creating onboarding guides, or establishing documentation standards. |
52
54
  | Writing Plans | `writing-plans/` | Use when you have a spec or requirements for a multi-step task, before touching code |
53
55
 
@@ -0,0 +1,176 @@
1
+ ---
2
+ name: safe-cleanup
3
+ description: Use BEFORE deleting any directory tree, clearing a scratchpad, removing git worktrees, or "tidying up" generated folders - especially on Windows, where a recursive delete that meets a junction or symlink destroys the link's TARGET. Also use when verifying that a Python venv or node_modules survived a cleanup, because pip check and file counts both report healthy on a half-deleted environment.
4
+ ---
5
+
6
+ # Safe Cleanup
7
+
8
+ ## Overview
9
+
10
+ Cleanup looks like the safest thing you do all day. It is the most destructive.
11
+
12
+ This skill exists because of a real incident: an agent created 31 directory
13
+ junctions from throwaway worktrees into live repositories, unlinked them, saw
14
+ "31 unlinked, 0 failed", then recursively deleted the parent. A junction it had
15
+ never seen survived the scan, the delete followed it, and **five repositories'
16
+ development environments were destroyed**. Every verification afterwards said
17
+ "intact", because the verification was wrong too.
18
+
19
+ Two hours of repair. None of it would have happened with the rules below.
20
+
21
+ ## The one-paragraph version
22
+
23
+ On Windows, `Remove-Item -Recurse` **follows junctions and deletes their
24
+ targets**. Enumerating with `-ErrorAction SilentlyContinue` hides the ones you
25
+ cannot read. And after the damage, `pip check`, `pip list` and file counts all
26
+ report success, because metadata survives while package files do not. So: never
27
+ link into somewhere real, never recurse through a link, prefer rename over
28
+ delete, and verify by *using* the thing rather than counting it.
29
+
30
+ ---
31
+
32
+ ## RULE 1 - Prefer rename over delete
33
+
34
+ Rename is atomic. Recursive delete is not: it can fail half way and leave a
35
+ directory that looks present and is unusable.
36
+
37
+ ```powershell
38
+ Rename-Item -Path $target -NewName ".broken-$(Get-Date -Format 'yyyyMMdd-HHmmss')"
39
+ ```
40
+
41
+ In the incident, a venv holding `_greenlet.cp314-win_amd64.pyd` open **refused to
42
+ delete** but **renamed fine**. Had the delete been retried or forced, it would
43
+ have half-emptied the directory.
44
+
45
+ Rename first, build fresh alongside, delete the old copy later when nothing holds
46
+ it. Disk is cheap. An environment nobody can recover is not.
47
+
48
+ ## RULE 2 - Never recurse a delete through a reparse point
49
+
50
+ Junctions and symlinks are transparent to `Remove-Item -Recurse`. Deleting a
51
+ folder that contains a link to `C:\live\thing` deletes **the contents of
52
+ `C:\live\thing`**.
53
+
54
+ Check first, and **fail if the check itself cannot complete**:
55
+
56
+ ```powershell
57
+ $links = @(Get-ChildItem $path -Recurse -Force -Directory -ErrorAction Stop |
58
+ Where-Object { $_.Attributes -band [IO.FileAttributes]::ReparsePoint })
59
+ if ($links.Count) {
60
+ $links | ForEach-Object { Write-Host " $($_.FullName) -> $($_.Target)" }
61
+ throw "refusing: $($links.Count) link(s) inside $path"
62
+ }
63
+ ```
64
+
65
+ `-ErrorAction SilentlyContinue` on that scan is precisely what caused the
66
+ incident. A link inside a directory the scanner could not read was skipped in
67
+ silence and never appeared in the count. **A scan that cannot see everything must
68
+ fail, not shrug.**
69
+
70
+ To remove a link itself, unlink it - never delete through it:
71
+
72
+ ```powershell
73
+ [System.IO.Directory]::Delete($linkPath, $false) # link only, never the target
74
+ cmd /c rmdir "$linkPath" # same effect
75
+ ```
76
+
77
+ ## RULE 3 - Do not create links into live directories
78
+
79
+ The safest delete is the one with nothing to follow.
80
+
81
+ If a throwaway workspace needs dependencies, **build them there**, or point the
82
+ tool at an absolute interpreter path. Do not junction `<scratch>\.venv` to
83
+ `<live-repo>\.venv` for convenience - that is a loaded gun aimed at the repo, and
84
+ any cleanup above the link can fire it.
85
+
86
+ If you must link, remove the link the moment you are done, and never leave one
87
+ inside a directory that something else will clean up.
88
+
89
+ ## RULE 4 - Verify by using, not by counting
90
+
91
+ After any cleanup near an environment, prove it still works. These all LIE on a
92
+ half-deleted Python venv, because dist-info metadata survives while package files
93
+ are removed:
94
+
95
+ | Check | Why it lies |
96
+ |---|---|
97
+ | `Test-Path .venv\Scripts\python.exe` | Proves the interpreter exists. Says nothing about packages. |
98
+ | file or directory counts | Metadata directories inflate the count. |
99
+ | `pip list` | Reads metadata. |
100
+ | `pip check` | Reads metadata. Reported "No broken requirements found" on a venv that could not import fastapi. |
101
+ | `pip install -e .` | Reads metadata, calls the package satisfied, **skips it, and reports success**. |
102
+
103
+ The honest check is whether the interpreter can import what the project declares:
104
+
105
+ ```python
106
+ import importlib.util
107
+ importlib.util.find_spec("fastapi") is None # -> True means genuinely missing
108
+ ```
109
+
110
+ For node, check that a real entry point resolves, not that `node_modules` exists.
111
+
112
+ **Map distribution names to import names.** `pyjwt` imports as `jwt`,
113
+ `python-ulid` as `ulid`, `python-dotenv` as `dotenv`. A naive audit produces false
114
+ positives and will have you rebuilding healthy environments - which is its own way
115
+ of causing an outage.
116
+
117
+ ## RULE 5 - Stop what holds the files, or refuse
118
+
119
+ A running service turns a delete into a **partial** delete - the worst outcome.
120
+
121
+ Stop named services first. Then check for surviving processes and **refuse rather
122
+ than force**:
123
+
124
+ ```powershell
125
+ $held = @(Get-CimInstance Win32_Process -Filter "Name='python.exe'" |
126
+ Where-Object { $_.ExecutablePath -like "$venv\*" })
127
+ if ($held.Count) { throw "refusing: still held by PID $($held[0].ProcessId)" }
128
+ ```
129
+
130
+ In the incident's repair, this refused on one repo because a **VS Code language
131
+ server** had held that venv since the previous day. Not a service, not safe to
132
+ kill, not the script's business. That repo was fixed a different way. **Refusing
133
+ was correct.**
134
+
135
+ ## RULE 6 - One agent at a time on a shared resource
136
+
137
+ Two agents installing into the same environment concurrently can produce exactly
138
+ this partial state. Three sessions were live in one repository during the
139
+ incident. Whoever is repairing says so; everyone else keeps out.
140
+
141
+ ## RULE 7 - Verify against the source of truth, not the working copy
142
+
143
+ Checkouts go stale. During the same incident a subagent reported a file as absent
144
+ from a configuration, contradicting a change made hours earlier. The file was
145
+ present on `origin/main`; the working copy was **10 commits behind**.
146
+
147
+ Before asserting something is missing, check `git show origin/main:<path>`, not
148
+ the file on disk.
149
+
150
+ ---
151
+
152
+ ## Before you delete anything - the checklist
153
+
154
+ 1. Can I **rename** instead? Do that.
155
+ 2. Does the tree contain **reparse points**? Scan with `-ErrorAction Stop`. Refuse if any, or if the scan fails.
156
+ 3. Does anything **hold files open**? Stop services; refuse on surviving processes.
157
+ 4. Did I create any **links into live directories** earlier in this session? Unlink them explicitly, by name.
158
+ 5. After: **verify by using**, not by counting.
159
+ 6. If something looks wrong: check `origin/main`, not the working copy.
160
+
161
+ ## What good verification output looks like
162
+
163
+ Not this:
164
+
165
+ ```
166
+ .venv intact: True <- only proves python.exe exists
167
+ 31 unlinked, 0 failed <- says nothing about what the scan missed
168
+ No broken requirements found <- reads metadata, not reality
169
+ ```
170
+
171
+ This:
172
+
173
+ ```
174
+ service-a OK declared=24 missing=0 <- every declared dep imported
175
+ service-b OK declared=19 missing=0
176
+ ```
@@ -0,0 +1,280 @@
1
+ ---
2
+ name: tech-explainer
3
+ context: fork
4
+ description: >-
5
+ Write a plain-language technical explainer for a smart reader who did NOT do the investigation — a
6
+ manager, an infra colleague, a stakeholder, the person whose recommendation you are answering.
7
+ Layered Mermaid diagrams that rank causes by SIZE OF EFFECT, a controlled-vocabulary table,
8
+ Simplified Technical English, published withdrawn results, and questions back to the reader. Use
9
+ this skill whenever the user says "explain this to X", "explainer", "write this up for someone
10
+ non-technical", "plain English", "simplified technical english", "STE", "explain our findings",
11
+ "write up the benchmark results", "answer my manager's recommendations", "why is it slow — document
12
+ it", "make a doc with diagrams explaining the stack", or is about to summarise an investigation, a
13
+ benchmark, a postmortem, an architecture, or a disagreement for a reader who will not read the raw
14
+ data. NOT for handing off work to be BUILT by a vendor or another team (use explainer-doc for that —
15
+ it produces an actionable spec); NOT for READMEs or API docs (use technical-writing). This one
16
+ produces UNDERSTANDING: the reader must finish able to argue with you on the merits.
17
+ ---
18
+
19
+ # tech-explainer — make a stranger understand, and able to push back
20
+
21
+ The reader is intelligent but was not in the room. They did not watch the tests, they will not read
22
+ the raw report, and they have their own opinion about what you should do. If you hand them the
23
+ findings compressed, every conclusion looks like an assertion, and the conversation becomes
24
+ credentials against credentials.
25
+
26
+ **The job is not to summarise. The job is to transfer enough of the reasoning that the reader can
27
+ disagree with you correctly.** A reader who finishes and says "I still think it's the wrapper, and
28
+ here's why your 3x number is wrong" is a success. A reader who nods and remembers nothing is a
29
+ failure, even if they agreed.
30
+
31
+ ## What this skill gives you
32
+
33
+ | File | Use it for |
34
+ | --------------------------- | --------------------------------------------------------------------------- |
35
+ | `assets/skeleton.md` | **Start here.** The fill-in document. Copy it, then delete what has nothing to say. |
36
+ | `references/diagrams.md` | Copy-paste Mermaid for all six patterns, plus the syntax that bites. |
37
+ | `references/language.md` | Full STE rules, the rewrite table, and the mechanical rewrite pass. |
38
+
39
+ ## When this is NOT the right skill
40
+
41
+ - The reader must **build** something from the document → `explainer-doc`. That skill produces an
42
+ actionable spec (DDL, queries, acceptance criteria). This one produces understanding.
43
+ - The reader already lived through the investigation → `technical-writing` or a plain summary. The
44
+ vocabulary table and STE discipline are overhead they don't need.
45
+ - It's a README, API reference, or runbook → `technical-writing`.
46
+
47
+ The tell for *this* skill: **the reader will form or change an opinion**, and you need that opinion
48
+ to be based on the numbers rather than on trust.
49
+
50
+ ## The central move — rank the layers by size of effect
51
+
52
+ This is the single technique that makes the document work, and it is what most explainers get wrong.
53
+
54
+ An architecture diagram shows **what exists**. It is the natural thing to draw and it is nearly
55
+ useless here, because the reader's real question is not "what is the shape of this system" — it is
56
+ **"what should we change?"** So draw the same stack, but label every layer with **the magnitude of
57
+ its effect on the outcome**:
58
+
59
+ ```
60
+ LAYER 1 — The model — LARGEST EFFECT — about 3x
61
+ LAYER 2 — The engine — MEDIUM EFFECT — tens of percent
62
+ LAYER 3 — The program — SMALL EFFECT — under 5%
63
+ LAYER 4 — The operating system — NO EFFECT
64
+ ```
65
+
66
+ Now the reader who came in convinced that swapping the program would fix everything can see, without
67
+ being contradicted, that they were aiming at the 5% layer. **The disagreement becomes arithmetic.**
68
+ That is worth more than any paragraph of argument, because arithmetic is something they can check
69
+ and you cannot fake.
70
+
71
+ Rules for this diagram:
72
+
73
+ - **Order the layers by effect size, largest first** — not by where they sit in the stack, and not by
74
+ the order you investigated them.
75
+ - **Every layer carries a number or an honest "no effect"**, plus the one-line reason. A layer with no
76
+ measurement is a layer you are guessing about; say "not measured" rather than inventing a rank.
77
+ - **Include the layer the reader is about to optimise, even if it ranks last.** Leaving it out looks
78
+ evasive. Ranking it last with a measured number is the whole argument.
79
+ - **Say how to read it.** One line under the diagram: *"If we change the program, we gain a few
80
+ percent. If we change the model, we gain about 3 times. Layer 1 is where the work must go."*
81
+
82
+ If your subject has no natural layers, the same move still applies: rank the candidate causes by
83
+ their measured contribution, and call them causes rather than layers. The stack is a convenience; the
84
+ ranking is the point. It transfers to any "why is it slow / expensive / wrong" question — the causes
85
+ of an ETL's runtime, the sources of a cloud bill, the contributors to a defect rate.
86
+
87
+ **An attribution number must carry its method.** "The N+1 query is 85% of the p99" is the sentence
88
+ the whole document rests on, and share-of-total figures are where this kind of analysis rots: they
89
+ get produced by one tool, quoted onward, and nobody remembers whether it was mean or p99, warm or
90
+ cold, one endpoint or all of them. Say how the share was obtained, in the same breath — *"85% of p99,
91
+ from 12,000 spans over 24 hours, attributed by span duration"*. If you cannot say how, rank it
92
+ qualitatively and mark it unmeasured rather than putting a false number on the diagram.
93
+
94
+ ## Simplified Technical English — the language discipline
95
+
96
+ STE is not dumbing down. It is removing every sentence that can be read two ways, so a reader who is
97
+ expert in a *different* field never has to guess. Announce it in the header so the terseness reads as
98
+ courtesy rather than curtness:
99
+
100
+ > **Written in Simplified Technical English.** Short sentences. One idea per sentence.
101
+
102
+ Six rules cover most of it. Full rules and before/after rewrites: `references/language.md`.
103
+
104
+ 1. **One idea per sentence.** Aim under 20 words. Split on "and", "but", "which", ";".
105
+ 2. **One word, one meaning.** Fix the term in the vocabulary table (§2), then never use a synonym for
106
+ it. "Engine" stays "engine" — never "backend", "runtime", or "inference layer".
107
+ 3. **Active voice, and name the actor.** "We withdrew the result", not "the result was withdrawn".
108
+ 4. **No idiom, no metaphor, no hedging.** Not "the low-hanging fruit", not "it seems likely that".
109
+ If you are unsure, say the size of your uncertainty: "we measured this once, on a test prompt".
110
+ 5. **Every number carries its unit and its basis.** "2,373 output tokens per summary, measured from
111
+ `gpt-4o`" — not "about 2.4k tokens".
112
+ 6. **Never open a paragraph with a pronoun standing in for the previous paragraph.** The reader
113
+ skims and lands mid-document; name the subject again. ("This is the most important diagram in
114
+ this document" is fine — "this" points at the thing directly below it, not at prose above.)
115
+
116
+ ## The shape — sections in this order
117
+
118
+ The order is load-bearing: the reader must be able to stop at any section and have gained something
119
+ complete. Not every document needs all fifteen. The **bold** slots are the ones that make it *this*
120
+ kind of document; drop the others when they have nothing to say.
121
+
122
+ | § | Slot | What it does |
123
+ | --- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
124
+ | — | **Header** | Title, `**Audience:**` by name, and the STE announcement. Naming the reader changes how the whole document reads. |
125
+ | 1 | **Why this document exists** | Numbered list of the questions this answers. Then route the reader's own question: *"Section 5 answers the LM Studio question."* |
126
+ | 2 | **Words used in this document** | Controlled vocabulary table. One meaning only. This is what lets the rest be short. |
127
+ | 3 | What we are building | One left-to-right flowchart of the real pipeline, plus the physical facts (hardware, OS, versions). |
128
+ | 4 | **The goal, in numbers** | Target / measured / gap, in a table, with the three key rows in bold. If there is no gap, say what "good" is. |
129
+ | 5 | The layer stack | What sits on what, and which layers are cheap to change. Answer the reader's specific question here if they asked one. |
130
+ | 6 | Why the work took so long | The results you **withdrew**, and what caught each one. See below — this section buys more credibility than any other. |
131
+ | 7 | Where the time (or cost) goes | A proportion chart with the sample count. Redirects effort before anyone argues about it. |
132
+ | 8 | **What limits the result** | The ranked-layer diagram. The most important diagram in the document. Say so, and say how to read it. |
133
+ | 9 | The recommendations we checked | One row per claim: recommendation / verdict / evidence. Then a **"Where the advice is right"** subsection. |
134
+ | 10 | What the constraint costs | The forced choice (a platform, a vendor, a deadline) — what it costs, and what it genuinely gives back. |
135
+ | 11 | **What we do next** | Ordered steps as a flowchart. Name which step matters most. Then list the measurement gaps still open. |
136
+ | 12 | Questions for \<reader\> | Real questions only. This converts a verdict into a conversation. |
137
+ | 13 | **Summary in five lines** | Five numbered sentences. Someone will read only this. Make it survivable alone. |
138
+ | — | **Related documents** | Table: document → what it holds. This is what lets §1–13 stay short without hiding anything. |
139
+
140
+ Rename the slots to fit the domain — a security review, a cost analysis, and a latency investigation
141
+ will all name §3–§10 differently. Keep the order and the intent.
142
+
143
+ ### When a slot does not fit
144
+
145
+ Most explainers hit two or three of these. Substituting is right; padding the slot with invented
146
+ content is not, and forcing a section you have nothing for is how the document starts sounding
147
+ false.
148
+
149
+ | Situation | What to do |
150
+ | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
151
+ | **§4 — no target exists** | Retitle it "The result, in numbers" and make the missing target the finding: *"We have no agreed p99 target. That is the first thing to fix."* A gap you cannot size is still worth naming. |
152
+ | **§5 duplicates §8** (few causes, one stack) | Collapse. Make §5 a table with a column §8 lacks — usually **cost to change** or **who owns it** — and let §8 carry the only stack diagram. Two near-identical diagrams teach the reader nothing twice. |
153
+ | **§6 — nothing was withdrawn** | Do not invent a retraction. Substitute the assumptions the argument rests on, plus what would falsify each. Same function: it shows the reader where to attack. |
154
+ | **§9 — no formal list of recommendations** | The reader still has a belief. Turn it into a claims-checked table: *the claim as they would state it* / verdict / evidence. "It's just copying data" is a claim; check it like one. |
155
+ | **No independent external number to anchor against** | Say so once and move on. A stated absence costs nothing; a fabricated comparison costs the document. |
156
+ | **The reader has no name** ("my manager") | Use their role in the audience line — *"Audience: the engineering manager"* — and leave a visible placeholder. Never invent a name. |
157
+ | **The prompt is the only source** | Say so in §1 and label every number by origin: measured / calculated / assumed. See process step 2. |
158
+
159
+ ### When to stop
160
+
161
+ Section count follows the evidence, not the template. Four numbers and one conclusion is a six-section
162
+ document; a three-week investigation with retractions is a fifteen-section document. If a section
163
+ would exist only because the table lists it, cut it — a short explainer that a manager finishes beats
164
+ a complete one they abandon at §7.
165
+
166
+ The load-bearing minimum is: the audience line, §2, §8, §13, and the related-documents table.
167
+
168
+ ## The moves that carry the document
169
+
170
+ **Lead every section with the bolded short answer.** The reader is looking for the answer to one
171
+ question. Give it in the first line, in bold, then spend the section earning it:
172
+
173
+ > **Short answer: we do not have LM Studio installed. But we already run the engine that is inside
174
+ > LM Studio.**
175
+
176
+ **Publish what you withdrew.** List the results you reported and later retracted, each with the cause
177
+ and how it was caught. This feels like confessing and it is actually the strongest section in the
178
+ document: it explains why the work took three weeks, it proves the surviving numbers were checked the
179
+ same way, and it inoculates you against the next person who finds an error. End with the lesson in
180
+ one line — *"a benchmark that gives a believable number is not therefore correct."*
181
+
182
+ **Steelman the person you are answering.** Before refuting a recommendation, write the subsection
183
+ where they are right, and mean it. *"The review is correct that the GPU is not fully used. We confirmed
184
+ this with arithmetic."* Then: *"The instinct behind the advice is right. The cause is different from
185
+ the cause given."* Nobody accepts a correction from someone who did not first understand them.
186
+
187
+ **Route each wrong assumption to its real cause, and it is usually not the reader.** When someone
188
+ recommends a fix you have already applied, the honest framing is almost never "they were wrong" — it
189
+ is that nobody told them. Write it that way: *"The database has been on NVMe since March. That is not
190
+ recorded anywhere the infra team can see, which is our gap to close, not a wrong call."* This is the
191
+ same instinct as the steelman, applied to facts rather than judgement, and it does real work in a
192
+ document that answers someone senior: it separates **the recommendation was reasonable given what
193
+ they knew** from **the recommendation is what we should do**, which is the only distinction that
194
+ lets a senior reader change position without losing anything. Look for it every time a verdict comes
195
+ out "already done" or "does not apply here".
196
+
197
+ **Show the arithmetic in a bare code block.** When a conclusion rests on a calculation, print it so
198
+ the reader can redo it in their head:
199
+
200
+ ```
201
+ GPU memory speed 3,900 GB per second
202
+ Data read per token 2.65 GB
203
+ Theoretical maximum 1,470 tokens per second
204
+ Measured 114 to 316 tokens per second (8% to 21%)
205
+ ```
206
+
207
+ A number the reader can re-derive is a number they own. A number they must accept is one they will
208
+ challenge later, at a worse time.
209
+
210
+ **Anchor against an independent external result, when one exists.** "Are we misconfigured?" is
211
+ answerable only by comparison. Find someone else's published number on comparable kit, put it in a
212
+ two-row table beside yours, and state the conclusion plainly. This turns a defensive claim into a
213
+ measurement. If no comparable number exists, say that in one line and move on — never assemble a
214
+ lookalike from a different workload to fill the slot.
215
+
216
+ **Admit the gaps you did not close.** Name what you never measured directly and what you would need
217
+ to do it. Two or three lines. A document with no admitted gaps reads as marketing.
218
+
219
+ **Diagrams for shape, tables for detail, prose only for judgement.** If a table says it better, delete
220
+ the diagram. The count is not the test — the test is that each diagram answers a question no other
221
+ one answers. The source explainer carries eight across thirteen sections and none is redundant; a
222
+ six-section explainer with four near-identical stack diagrams is over-drawn.
223
+
224
+ ## Diagram vocabulary
225
+
226
+ Keep to a small, repeated set of Mermaid shapes — the document reads as one voice because the same
227
+ four patterns recur. Copy-paste versions with the syntax details (bold inside nodes, dotted
228
+ "hypothetical" arrows, line breaks, pie charts): `references/diagrams.md`.
229
+
230
+ | Pattern | Use for |
231
+ | ---------------------------------- | ---------------------------------------------- |
232
+ | `flowchart LR` | The pipeline. Input → work → output. |
233
+ | `flowchart TB` with `subgraph`s | The layer stack — §5, and the ranked §8. |
234
+ | `flowchart LR` with a decision node | A loop or a check procedure. |
235
+ | `pie showData` | Where the time/cost goes. Give the run or N. |
236
+
237
+ Check every diagram before shipping: paste it into `mermaid.live`, or read it against the syntax
238
+ table at the end of `references/diagrams.md`. A broken Mermaid block renders as raw source and costs
239
+ more credibility than the diagram was worth.
240
+
241
+ ## Process
242
+
243
+ 1. **Name the reader and their question.** Ask if it is not obvious. "Explain the benchmark" is not
244
+ enough — you need *who* and *what they currently believe*, because §8's ranking exists to meet a
245
+ specific wrong (or right) belief. If they already have full context, stop and use a plain summary.
246
+ 2. **Read the source material yourself.** The raw report, the measurement logs, the plan, the thread
247
+ you are answering. Do not write an explainer from a summary — the explainer is the place where
248
+ soft numbers get caught, and you cannot catch them at second hand. **If no source exists and the
249
+ request itself is all you have**, that is workable but must be visible: say in §1 that the numbers
250
+ come from the requester, and tag every figure in the document `(measured)`, `(calculated)` — with
251
+ the arithmetic shown — or `(assumed)`. The tags cost a few words and they are what stop your
252
+ inference being quoted back later as someone's measurement.
253
+ 3. **Build §8 first.** List every candidate cause, attach its measured effect size, and sort. If you
254
+ cannot rank them, you do not yet understand the system well enough to explain it — go measure, or
255
+ say explicitly which layers are unranked and why.
256
+ 4. **Write §2, the vocabulary, second.** Every term you fix here makes the following sections shorter.
257
+ 5. **Copy `assets/skeleton.md` and fill it top to bottom**, deleting the slots that have nothing to
258
+ say (see "When a slot does not fit"). Keep evidence beside each claim rather than in an appendix.
259
+ 6. **Rewrite in STE last, as its own pass.** Split long sentences, hunt synonyms for defined terms,
260
+ convert passive to active, attach units to bare numbers. It is a mechanical pass; do it
261
+ mechanically. `references/language.md` has the rewrite table.
262
+ 7. **Run the exit gate.**
263
+
264
+ ## The exit gate
265
+
266
+ Ask all four before shipping. Each one has caught a real defect in this document type.
267
+
268
+ - **"Can the reader disagree with me on the merits?"** If every conclusion rests on "we measured it"
269
+ with no visible number or method, they can only agree or distrust. Neither is useful.
270
+ - **"Does §8 rank the layer the reader cares about?"** If their favoured fix is missing from the
271
+ ranked diagram, the document dodges instead of answering.
272
+ - **"Would the five-line summary survive alone?"** Someone will forward only that. Read it cold.
273
+ - **"What did I state without a basis?"** Every number needs a unit, a sample size or a measurement
274
+ date, and a way to reproduce it. Bare numbers are where explainers rot first — they get quoted
275
+ onward, and six weeks later nobody knows what they measured.
276
+ - **"Which section is here only because the template listed it?"** Cut it. Padding reads as padding,
277
+ and it spends the attention you need for §8.
278
+
279
+ If the repository has a document-frontmatter convention, apply it (`type: analysis` fits this
280
+ document kind). Then add the document to the doc index if one exists.