create-battle-plan 1.0.0

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.
package/bin/cli.js ADDED
@@ -0,0 +1,434 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const readline = require('readline');
6
+
7
+ const rl = readline.createInterface({
8
+ input: process.stdin,
9
+ output: process.stdout,
10
+ });
11
+
12
+ function ask(question) {
13
+ return new Promise((resolve) => {
14
+ rl.question(question, (answer) => resolve(answer.trim()));
15
+ });
16
+ }
17
+
18
+ function slugify(text) {
19
+ return text
20
+ .toLowerCase()
21
+ .replace(/[^a-z0-9]+/g, '-')
22
+ .replace(/^-|-$/g, '');
23
+ }
24
+
25
+ function metricKey(text) {
26
+ return text
27
+ .trim()
28
+ .toLowerCase()
29
+ .replace(/[^a-z0-9]+/g, '_')
30
+ .replace(/^_|_$/g, '');
31
+ }
32
+
33
+ function capitalize(text) {
34
+ return text.charAt(0).toUpperCase() + text.slice(1);
35
+ }
36
+
37
+ function copyDir(src, dest) {
38
+ fs.mkdirSync(dest, { recursive: true });
39
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
40
+ const srcPath = path.join(src, entry.name);
41
+ const destPath = path.join(dest, entry.name);
42
+ if (entry.isDirectory()) {
43
+ copyDir(srcPath, destPath);
44
+ } else {
45
+ fs.copyFileSync(srcPath, destPath);
46
+ }
47
+ }
48
+ }
49
+
50
+ const BOLD = '\x1b[1m';
51
+ const DIM = '\x1b[2m';
52
+ const GREEN = '\x1b[32m';
53
+ const CYAN = '\x1b[36m';
54
+ const YELLOW = '\x1b[33m';
55
+ const WHITE = '\x1b[37m';
56
+ const RESET = '\x1b[0m';
57
+
58
+ function banner() {
59
+ console.log('');
60
+ console.log(`${BOLD}${WHITE} ___ ____ ___ ___ _ ____${RESET}`);
61
+ console.log(`${BOLD}${WHITE} |__] |__| | | | |___${RESET}`);
62
+ console.log(`${BOLD}${WHITE} |__] | | | | |___ |___${RESET}`);
63
+ console.log('');
64
+ console.log(`${BOLD}${WHITE} ___ _ ____ _ _${RESET}`);
65
+ console.log(`${BOLD}${WHITE} |__] | |__| |\\ |${RESET}`);
66
+ console.log(`${BOLD}${WHITE} | |___ | | | \\|${RESET}`);
67
+ console.log('');
68
+ console.log(`${DIM} A markdown-based context system${RESET}`);
69
+ console.log(`${DIM} for LLM-powered projects${RESET}`);
70
+ console.log('');
71
+ console.log(`${DIM} ─────────────────────────────${RESET}`);
72
+ console.log('');
73
+ }
74
+
75
+ function cascadeDiagram(domains, metrics) {
76
+ const domainStr = domains.slice(0, 3).join(' ');
77
+ const dots = domains.length > 3 ? ' ...' : '';
78
+ const metricStr = metrics.slice(0, 3).map((m) => metricKey(m)).join(', ');
79
+
80
+ console.log(`${DIM} Your cascade:${RESET}`);
81
+ console.log('');
82
+ console.log(`${CYAN} new info ──→ ${WHITE}metrics.yml${RESET}`);
83
+ console.log(`${DIM} │${RESET}`);
84
+ console.log(`${DIM} ▼${RESET}`);
85
+ console.log(`${CYAN} ${WHITE}battle-plan.md${RESET}`);
86
+ console.log(`${DIM} / | \\${RESET}`);
87
+ console.log(`${CYAN} ${WHITE}${domainStr}${dots}${RESET}`);
88
+ console.log(`${DIM} │${RESET}`);
89
+ console.log(`${DIM} ▼${RESET}`);
90
+ console.log(`${GREEN} verify-cascade.sh ${BOLD}✓${RESET}`);
91
+ console.log('');
92
+ }
93
+
94
+ async function main() {
95
+ banner();
96
+
97
+ // Question 1: Project name
98
+ const projectName = await ask(`${DIM}[1/6]${RESET} ${BOLD}What's your project in one sentence?${RESET}\n> `);
99
+ if (!projectName) {
100
+ console.log('Project name is required.');
101
+ process.exit(1);
102
+ }
103
+ console.log('');
104
+
105
+ // Question 2: Time horizon
106
+ const horizon = await ask(
107
+ `${DIM}[2/6]${RESET} ${BOLD}What's your time horizon?${RESET} ${DIM}(e.g., "3 weeks to demo day", "6 months to launch", "ongoing")${RESET}\n> `
108
+ );
109
+ console.log('');
110
+
111
+ // Question 3: Metrics
112
+ const metricsRaw = await ask(
113
+ `${DIM}[3/6]${RESET} ${BOLD}What are the 3-5 key metrics you want to track?${RESET} ${DIM}(comma-separated, e.g., "outreach sent, calls booked, LOIs signed")${RESET}\n> `
114
+ );
115
+ if (!metricsRaw) {
116
+ console.log('At least one metric is required.');
117
+ process.exit(1);
118
+ }
119
+ const metrics = metricsRaw.split(',').map((m) => m.trim()).filter(Boolean);
120
+ console.log('');
121
+
122
+ // Question 4: Domains
123
+ const suggestedDomains = suggestDomains(projectName);
124
+ const domainsRaw = await ask(
125
+ `${DIM}[4/6]${RESET} ${BOLD}What domains does your work cover?${RESET} ${DIM}(comma-separated)\nSuggested based on your project: ${suggestedDomains}${RESET}\n> `
126
+ );
127
+ if (!domainsRaw) {
128
+ console.log('At least one domain is required.');
129
+ process.exit(1);
130
+ }
131
+ const domains = domainsRaw.split(',').map((d) => d.trim().toLowerCase()).filter(Boolean);
132
+ console.log('');
133
+
134
+ // Question 5: People
135
+ const peopleRaw = await ask(
136
+ `${DIM}[5/6]${RESET} ${BOLD}Who are the key people you'll be working with?${RESET} ${DIM}(format: "Name:Role, Name:Role" — or press enter to skip)${RESET}\n> `
137
+ );
138
+ const people = peopleRaw
139
+ ? peopleRaw.split(',').map((p) => {
140
+ const [name, role] = p.split(':').map((s) => s.trim());
141
+ return { name: name || '', role: role || '' };
142
+ }).filter((p) => p.name)
143
+ : [];
144
+ console.log('');
145
+
146
+ // Question 6: Where to install
147
+ const defaultDir = `./${slugify(projectName) || 'my-battle-plan'}`;
148
+ const dirAnswer = await ask(
149
+ `${DIM}[6/6]${RESET} ${BOLD}Where do you want to install it?${RESET} ${DIM}(default: ${defaultDir})${RESET}\n> `
150
+ );
151
+ const targetDir = path.resolve(dirAnswer || defaultDir);
152
+ console.log('');
153
+
154
+ rl.close();
155
+
156
+ // --- Scaffold ---
157
+ console.log(`${DIM} ─────────────────────────────${RESET}`);
158
+ console.log('');
159
+ console.log(`${CYAN} Scaffolding...${RESET}`);
160
+ console.log('');
161
+
162
+ if (fs.existsSync(targetDir) && fs.readdirSync(targetDir).length > 0) {
163
+ console.log(`${YELLOW}Warning: ${targetDir} already exists and is not empty.${RESET}`);
164
+ process.exit(1);
165
+ }
166
+
167
+ // Copy template
168
+ const templateDir = path.join(__dirname, '..', 'template');
169
+ copyDir(templateDir, targetDir);
170
+ console.log(`${DIM} + CLAUDE.md (system prompt)${RESET}`);
171
+ console.log(`${DIM} + tools/ (verification scripts)${RESET}`);
172
+ console.log(`${DIM} + .claude/commands/ (slash commands)${RESET}`);
173
+ console.log(`${DIM} + .githooks/pre-commit${RESET}`);
174
+
175
+ // Make shell scripts executable
176
+ const toolsDir = path.join(targetDir, 'tools');
177
+ if (fs.existsSync(toolsDir)) {
178
+ for (const f of fs.readdirSync(toolsDir)) {
179
+ if (f.endsWith('.sh')) {
180
+ fs.chmodSync(path.join(toolsDir, f), 0o755);
181
+ }
182
+ }
183
+ }
184
+ const hookFile = path.join(targetDir, '.githooks', 'pre-commit');
185
+ if (fs.existsSync(hookFile)) {
186
+ fs.chmodSync(hookFile, 0o755);
187
+ }
188
+
189
+ const today = new Date().toISOString().split('T')[0];
190
+
191
+ // Create domain directories and docs
192
+ for (const domain of domains) {
193
+ const domainDir = path.join(targetDir, 'docs', domain);
194
+ fs.mkdirSync(domainDir, { recursive: true });
195
+ fs.writeFileSync(
196
+ path.join(domainDir, `${domain}-overview.md`),
197
+ `# ${capitalize(domain)} Overview
198
+
199
+ **Last Updated:** ${today}
200
+ **Status:** Draft
201
+ **Role:** cascade-target
202
+ **Compression:** amended
203
+
204
+ **TL;DR:** Initial ${domain} document for ${projectName}. To be filled in as the project progresses.
205
+
206
+ ---
207
+
208
+ ## Notes
209
+
210
+ _Start adding content here._
211
+ `
212
+ );
213
+ }
214
+
215
+ console.log(`${DIM} + docs/ (${domains.length} domain${domains.length > 1 ? 's' : ''})${RESET}`);
216
+
217
+ // Create metrics.yml
218
+ const metricsContent = [
219
+ `# metrics.yml — project-wide metrics registry for ${projectName}`,
220
+ '# The LLM updates this file FIRST in any cascade, before touching docs.',
221
+ '# Scripts verify all (→ metrics.yml#field) references against these values.',
222
+ '',
223
+ `last_updated: ${today}`,
224
+ '',
225
+ ...metrics.map((m) => `${metricKey(m)}: 0`),
226
+ '',
227
+ ].join('\n');
228
+ fs.writeFileSync(path.join(targetDir, 'metrics.yml'), metricsContent);
229
+ console.log(`${DIM} + metrics.yml (${metrics.length} metric${metrics.length > 1 ? 's' : ''})${RESET}`);
230
+
231
+ // Create battle plan
232
+ const metricsTable = metrics
233
+ .map((m) => `| ${m} | _set target_ | **0** (→ metrics.yml#${metricKey(m)}) |`)
234
+ .join('\n');
235
+
236
+ fs.writeFileSync(
237
+ path.join(targetDir, 'docs', 'battle-plan.md'),
238
+ `# Battle Plan — ${projectName}
239
+
240
+ **Last Updated:** ${today}
241
+ **Status:** Active
242
+ **Role:** source-of-truth
243
+ **Compression:** chronological
244
+
245
+ **TL;DR:** ${projectName} — just initialized. Time horizon: ${horizon || 'not set'}. All metrics at 0. First priority: fill in the battle plan with real tasks and targets.
246
+
247
+ ---
248
+
249
+ ## Rules for This Document
250
+
251
+ 1. Every task has an assigned date — no "sometime this week"
252
+ 2. Tasks move, never disappear — if slipped, add new date + reason
253
+ 3. New info updates the battle plan FIRST, before any other doc
254
+ 4. Everything links — tasks reference the doc they depend on or produce
255
+
256
+ ---
257
+
258
+ ## Key Metrics
259
+
260
+ | Metric | Target | Current |
261
+ |--------|--------|---------|
262
+ ${metricsTable}
263
+
264
+ ---
265
+
266
+ ## Today's Priorities
267
+
268
+ - [ ] Set targets for each metric
269
+ - [ ] Fill in this week's tasks
270
+ - [ ] Record any existing conversations in external-insights.md
271
+
272
+ ---
273
+
274
+ ## This Week
275
+
276
+ _Add day-by-day tasks here._
277
+
278
+ ---
279
+
280
+ ## Daily Log
281
+
282
+ _Append-only. Three lines per day._
283
+ `
284
+ );
285
+
286
+ // Create external-insights.md
287
+ const peopleSections = people.length
288
+ ? people.map((p) => `### ${p.name} — ${p.role}\n_No sessions recorded yet._\n`).join('\n')
289
+ : '_Add key people here as you start conversations._\n';
290
+
291
+ fs.writeFileSync(
292
+ path.join(targetDir, 'docs', 'external-insights.md'),
293
+ `# External Insights
294
+
295
+ **Last Updated:** ${today}
296
+ **Status:** Active
297
+ **Role:** cascade-target
298
+ **Compression:** chronological
299
+
300
+ **TL;DR:** All external conversations, calls, and meetings for ${projectName}. 0 sessions recorded so far.
301
+
302
+ ---
303
+
304
+ ## How to Use This Document
305
+
306
+ Every conversation gets appended as a dated session. Record everything — even "small" chats contain signal.
307
+
308
+ ### Template
309
+
310
+ \`\`\`markdown
311
+ ## Session N (YYYY-MM-DD) — [Person Name], [Role/Company]
312
+
313
+ ### Context
314
+ [Why this conversation happened]
315
+
316
+ ### Key insights
317
+ 1. **Insight title.** Detail. \`Confidence: [level]\`
318
+
319
+ ### Raw quotes (if available)
320
+ > "Quote here"
321
+
322
+ ### Action items
323
+ - [ ] Follow-up X
324
+ \`\`\`
325
+
326
+ ---
327
+
328
+ ## People
329
+
330
+ ${peopleSections}
331
+ `
332
+ );
333
+
334
+ console.log(`${DIM} + docs/battle-plan.md${RESET}`);
335
+ console.log(`${DIM} + docs/external-insights.md${RESET}`);
336
+
337
+ // Save onboarding answers for Claude to read on first /good-morning
338
+ fs.writeFileSync(
339
+ path.join(targetDir, '.battle-plan-onboarding.json'),
340
+ JSON.stringify(
341
+ {
342
+ project_name: projectName,
343
+ horizon,
344
+ metrics,
345
+ domains,
346
+ people,
347
+ installed_at: today,
348
+ },
349
+ null,
350
+ 2
351
+ ) + '\n'
352
+ );
353
+
354
+ // Mark as initialized
355
+ fs.writeFileSync(path.join(targetDir, '.battle-plan-initialized'), `Initialized on ${today}\n`);
356
+
357
+ console.log('');
358
+
359
+ // Initialize git repo
360
+ try {
361
+ const { execSync } = require('child_process');
362
+ execSync('git init', { cwd: targetDir, stdio: 'pipe' });
363
+ execSync('git config core.hooksPath .githooks', { cwd: targetDir, stdio: 'pipe' });
364
+ execSync('git add -A', { cwd: targetDir, stdio: 'pipe' });
365
+ execSync('git commit -m "Initial battle plan scaffold"', {
366
+ cwd: targetDir,
367
+ stdio: 'pipe',
368
+ env: { ...process.env, GIT_AUTHOR_NAME: 'Battle Plan', GIT_AUTHOR_EMAIL: 'noreply@battleplan.dev', GIT_COMMITTER_NAME: 'Battle Plan', GIT_COMMITTER_EMAIL: 'noreply@battleplan.dev' },
369
+ });
370
+ console.log(`${DIM} + git repo initialized${RESET}`);
371
+ } catch {
372
+ // git not available or failed — not critical
373
+ }
374
+
375
+ // --- Done ---
376
+ console.log('');
377
+ console.log(`${DIM} ─────────────────────────────${RESET}`);
378
+ console.log('');
379
+ console.log(`${GREEN}${BOLD} Ready.${RESET}`);
380
+ console.log('');
381
+ console.log(`${BOLD} Project:${RESET} ${projectName}`);
382
+ console.log(`${BOLD} Location:${RESET} ${targetDir}`);
383
+ console.log(`${BOLD} Horizon:${RESET} ${horizon || 'not set'}`);
384
+ console.log(`${BOLD} Metrics:${RESET} ${metrics.join(', ')}`);
385
+ console.log(`${BOLD} Domains:${RESET} ${domains.join(', ')}`);
386
+ if (people.length) {
387
+ console.log(`${BOLD} People:${RESET} ${people.map((p) => `${p.name} (${p.role})`).join(', ')}`);
388
+ }
389
+ console.log('');
390
+
391
+ cascadeDiagram(domains, metrics);
392
+
393
+ console.log(`${DIM} ─────────────────────────────${RESET}`);
394
+ console.log('');
395
+ console.log(`${CYAN}${BOLD} Next steps:${RESET}`);
396
+ console.log('');
397
+ console.log(` ${BOLD}cd ${path.relative(process.cwd(), targetDir)}${RESET}`);
398
+ console.log(` ${BOLD}claude${RESET}`);
399
+ console.log('');
400
+ console.log(` Then type ${GREEN}${BOLD}/good-morning${RESET} to start`);
401
+ console.log(` your first session.`);
402
+ console.log('');
403
+ console.log(`${DIM} Claude already knows your project, metrics,`);
404
+ console.log(` and team. Just dump context into the chat —`);
405
+ console.log(` call transcripts, research, meeting notes,`);
406
+ console.log(` replies — it'll cascade into the right docs.${RESET}`);
407
+ console.log('');
408
+ }
409
+
410
+ function suggestDomains(projectDescription) {
411
+ const desc = projectDescription.toLowerCase();
412
+ const suggestions = [];
413
+
414
+ if (/market|customer|user|audience|segment|icp/.test(desc)) suggestions.push('market');
415
+ if (/valid|test|hypothes|experiment|interview/.test(desc)) suggestions.push('validation');
416
+ if (/strat|position|compete|pricing|business/.test(desc)) suggestions.push('strategy');
417
+ if (/research|learn|study|paper|domain/.test(desc)) suggestions.push('research');
418
+ if (/content|write|blog|newsletter|social/.test(desc)) suggestions.push('content');
419
+ if (/logist|ops|supply|shipping|fulfil/.test(desc)) suggestions.push('logistics');
420
+ if (/product|feature|build|ship|release/.test(desc)) suggestions.push('product');
421
+ if (/sales|outreach|pipeline|deal|close/.test(desc)) suggestions.push('sales');
422
+ if (/fund|invest|pitch|raise|capital/.test(desc)) suggestions.push('fundraising');
423
+
424
+ if (suggestions.length === 0) {
425
+ suggestions.push('market', 'validation', 'strategy', 'research');
426
+ }
427
+
428
+ return suggestions.join(', ');
429
+ }
430
+
431
+ main().catch((err) => {
432
+ console.error(err);
433
+ process.exit(1);
434
+ });
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "create-battle-plan",
3
+ "version": "1.0.0",
4
+ "description": "Scaffold a Battle Plan project — a markdown-based context system for LLM-powered project management",
5
+ "bin": {
6
+ "create-battle-plan": "./bin/cli.js"
7
+ },
8
+ "files": [
9
+ "bin/",
10
+ "template/"
11
+ ],
12
+ "keywords": [
13
+ "claude",
14
+ "claude-code",
15
+ "llm",
16
+ "project-management",
17
+ "markdown",
18
+ "battle-plan",
19
+ "context",
20
+ "ai"
21
+ ],
22
+ "author": "Paul von Kunhardt",
23
+ "license": "MIT",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/paulkunhardt/battle-plan"
27
+ },
28
+ "engines": {
29
+ "node": ">=16"
30
+ }
31
+ }
@@ -0,0 +1,6 @@
1
+ # Battle Plan configuration
2
+ # Copy this file to .cascaderc and edit as needed.
3
+
4
+ # Set to 1 to block commits when verify-cascade.sh finds issues.
5
+ # Default (0): warn but allow the commit.
6
+ CASCADE_STRICT=0
@@ -0,0 +1,186 @@
1
+ ---
2
+ description: Distill a long doc — compress older content into a thorough summary, archive verbatim raw content, lose nothing.
3
+ argument-hint: <path/to/doc.md> [keep:N]
4
+ ---
5
+
6
+ You are running the `/distill` command. The user wants to compress a doc that's grown too long by **distilling** older sections into a thorough summary while archiving the verbatim raw content into `docs/archive/`. **Distilling preserves essence — never lose information.** The full raw content always survives in the archive.
7
+
8
+ ## Arguments
9
+
10
+ - **Doc path** (required): `$1` — relative path from repo root to the doc to distill
11
+ - **Keep count** (optional): `keep:N` — how many of the most recent dated entries to keep verbatim. Default: 2.
12
+
13
+ If `$ARGUMENTS` is empty, ask the user which doc to distill and how many entries to keep.
14
+
15
+ ## Step 0: Read frontmatter — choose mode by `Compression:` field
16
+
17
+ Read the target doc's frontmatter. The `Compression:` field determines how `/distill` operates:
18
+
19
+ | Compression mode | What `/distill` does |
20
+ |---|---|
21
+ | `chronological` | Doc is an append-only log with dated section headings (`## Session N (YYYY-MM-DD)`, `## YYYY-MM-DD`, etc.). Distill keeps the N most recent dated sections verbatim, archives the rest, replaces them with a thorough summary. |
22
+ | `amended` | Doc is a living reference with in-place `> **[UPDATE YYYY-MM-DD]**` amendment blocks above claims. Distill collapses old amendment blocks (older than a cutoff or beyond the most recent N per section) into the body text, archives the raw amendment blocks. |
23
+ | `none` | Doc is a static thesis/reference. **Refuse to run.** Tell the user: "This doc is `Compression: none` — it's not designed for distillation. Edits to static docs are version-controlled by git. If you think this doc should be compressible, change its frontmatter first." |
24
+ | _missing_ | **Refuse to run.** Tell the user: "No `Compression:` field in frontmatter. Add one of `chronological`, `amended`, or `none` per CLAUDE.md before distilling." |
25
+
26
+ Then proceed to the appropriate workflow below.
27
+
28
+ ---
29
+
30
+ ## Workflow A — `Compression: chronological`
31
+
32
+ ### Step 1: Identify dated sections
33
+
34
+ Read the doc. Find headings that contain a date (`## Session N (YYYY-MM-DD)`, `### YYYY-MM-DD`, `## DD Month YYYY`, etc.). List them with their timestamps.
35
+
36
+ **Default split:** Keep the N most recent dated sections verbatim, archive everything before them. N defaults to 2 unless `keep:N` was passed.
37
+
38
+ **Confirm with user before proceeding** — unless they explicitly said "go" or "do it" in the original prompt.
39
+
40
+ ### Step 2: Determine archive path
41
+
42
+ Archive lives at `docs/archive/<same-relative-path>` mirroring the doc's location.
43
+
44
+ Example: `docs/validation/sven-moritz-insights.md` → `docs/archive/validation/sven-moritz-insights.md`
45
+
46
+ If the archive file does NOT exist, create the parent directory (`mkdir -p`) and initialize it with this header (always include the frontmatter so verify-cascade skips it cleanly):
47
+
48
+ ```markdown
49
+ # Archive: <Original Doc Title>
50
+
51
+ **Last Updated:** YYYY-MM-DD
52
+ **Status:** Archived
53
+ **Role:** cascade-target
54
+ **Compression:** none
55
+
56
+ Raw content archived from `<original/path.md>`. Append-only — most recent archives at the top.
57
+
58
+ ---
59
+ ```
60
+
61
+ If the archive file exists, prepend the new dated section above existing archived content (newest at top).
62
+
63
+ ### Step 3: Append the new archive section (verbatim)
64
+
65
+ ```markdown
66
+ ## Archived YYYY-MM-DD — <description of what's being archived>
67
+
68
+ > Sections moved here from `<original/path.md>` on YYYY-MM-DD. These were the verbatim contents at time of archive.
69
+
70
+ <EXACT verbatim content of the older sections — copy them with zero changes>
71
+
72
+ ---
73
+ ```
74
+
75
+ Description should be specific: "Sessions 1-4 (2026-03-26 to 2026-03-30)" or "Daily logs from Jan-Mar 2026".
76
+
77
+ ### Step 4: Replace the archived sections in the original doc
78
+
79
+ In the original doc, remove the older sections you just archived. Replace them with:
80
+
81
+ 1. **Archive notice** at the position where the archived sections used to be:
82
+
83
+ ```markdown
84
+ > **📦 Distilled history:** <Description of what was archived> — full raw content in [archive](../archive/<relative-path>.md). Last distillation: YYYY-MM-DD.
85
+ ```
86
+
87
+ The relative path needs to navigate from the doc's location to `docs/archive/`. Use `../` as needed.
88
+
89
+ 2. **A thorough summary** of the archived sections, immediately after the notice. The summary must:
90
+ - Be substantive — capture key insights, decisions, evidence, quotes future readers (or LLMs) need
91
+ - Preserve all numbers, names, dates, and concrete claims
92
+ - Use clear subheadings if covering multiple sessions/topics
93
+ - Reference the archive for verbatim details: e.g. "_See full transcript in [archive](../archive/...md#session-1-2026-03-26)._"
94
+ - Be marked clearly as a summary, not original content
95
+
96
+ ```markdown
97
+ ## Summary of <description> (distilled YYYY-MM-DD)
98
+
99
+ [Thorough summary here. Preserve key insights, quotes, numbers. Cross-link to archive for verbatim.]
100
+
101
+ ---
102
+ ```
103
+
104
+ Leave the kept verbatim sections completely untouched.
105
+
106
+ ---
107
+
108
+ ## Workflow B — `Compression: amended`
109
+
110
+ ### Step 1: Identify amendment blocks
111
+
112
+ Read the doc. Find all `> **[UPDATE YYYY-MM-DD · Source: ...]**` blocks. Group them by their parent section/claim.
113
+
114
+ Show the user the list and propose which ones to collapse. **Default rule:** within each section, keep the N most recent amendments inline; collapse the older ones into the main body text and archive the raw blocks.
115
+
116
+ **Confirm with user before proceeding.**
117
+
118
+ ### Step 2: Create/update the archive file
119
+
120
+ Same path scheme as Workflow A. Same archive frontmatter.
121
+
122
+ ### Step 3: Append the new archive section (verbatim)
123
+
124
+ ```markdown
125
+ ## Archived YYYY-MM-DD — Collapsed amendments from <doc>
126
+
127
+ > Amendment blocks moved here from `<original/path.md>` on YYYY-MM-DD. These were the verbatim `[UPDATE]` blocks at time of distillation.
128
+
129
+ ### Section: <name of section the amendments belonged to>
130
+
131
+ <EXACT verbatim copy of the old `> **[UPDATE ...]**` blocks, preserving order and source citations>
132
+
133
+ ---
134
+ ```
135
+
136
+ ### Step 4: Rewrite the original doc body
137
+
138
+ For each collapsed amendment, **integrate its content into the main claim text** (not a separate block). The main body should now read as the current consensus state, with the amendment evidence absorbed. Then add a single notice at the section level:
139
+
140
+ ```markdown
141
+ > **📦 Distilled history:** <N> older amendments collapsed into body — see [archive](../archive/<relative-path>.md#section-...) for raw blocks. Last distillation: YYYY-MM-DD.
142
+ ```
143
+
144
+ Keep the most recent N amendment blocks inline as-is.
145
+
146
+ **Critical:** never silently drop a claim. If two old amendments contradict, preserve both as "[date X said A; date Y said B; current view: ...]" in the body.
147
+
148
+ ---
149
+
150
+ ## Step 5 (both workflows): Update dates and verify
151
+
152
+ ```bash
153
+ tools/touch-date.sh <original-doc> <archive-doc>
154
+ tools/verify-cascade.sh
155
+ ```
156
+
157
+ Fix any errors verify-cascade reports.
158
+
159
+ ## Step 6: Report
160
+
161
+ Tell the user:
162
+ - Mode used (`chronological` or `amended`)
163
+ - What was distilled (sections/amendments + date range)
164
+ - Where the archive lives (path)
165
+ - What stayed verbatim
166
+ - Line count of original doc (before vs after)
167
+ - Any verification warnings
168
+
169
+ Offer to commit:
170
+ ```
171
+ git add <original-doc> <archive-doc>
172
+ git commit -m "distill: compress <doc-name> — moved <description> to archive"
173
+ ```
174
+
175
+ ## Important rules
176
+
177
+ - **Never lose data.** Full verbatim content goes to the archive — exactly as it was.
178
+ - **Preserve metric references.** Keep `[**N**](metrics.yml#field)` links intact in both archive and summary.
179
+ - **Don't touch the TL;DR or frontmatter** — those stay on the main doc (the TL;DR may reference distilled summaries, but is current-state, not history).
180
+ - **Don't distill the most recent sections** — they're the active context.
181
+ - **The summary must be useful enough that an LLM reading only the main doc has full context for current decisions.** The archive is for the rare case when something old becomes relevant again.
182
+ - **If `Compression:` is missing or `none`, refuse to run.** Don't guess.
183
+
184
+ ## Arguments passed
185
+
186
+ $ARGUMENTS