bossbuild 0.97.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.
Files changed (128) hide show
  1. package/LICENSE +21 -0
  2. package/PRINCIPLES.md +70 -0
  3. package/README.md +213 -0
  4. package/VERSION +1 -0
  5. package/bin/boss +3 -0
  6. package/library/README.md +19 -0
  7. package/library/agents/.gitkeep +0 -0
  8. package/library/agents/mentor-venture.md +57 -0
  9. package/library/hooks/.gitkeep +0 -0
  10. package/library/hooks/auto-log.js +133 -0
  11. package/library/hooks/memory-cue.js +82 -0
  12. package/library/hooks/secrets-guard.js +87 -0
  13. package/library/memory-seed/README.md +29 -0
  14. package/library/memory-seed/durable-facts-example.md +16 -0
  15. package/library/practices/.gitkeep +0 -0
  16. package/library/practices/agent-security.md +111 -0
  17. package/library/practices/ai-adoption-culture.md +104 -0
  18. package/library/practices/ai-ux-patterns.md +246 -0
  19. package/library/practices/celebration-of-done.md +100 -0
  20. package/library/practices/conscience-voicing.md +121 -0
  21. package/library/practices/context-discipline.md +116 -0
  22. package/library/practices/design-system.md +152 -0
  23. package/library/practices/git-workflow.md +119 -0
  24. package/library/practices/harm-taxonomy.md +45 -0
  25. package/library/practices/quality-ratchet.md +48 -0
  26. package/library/practices/revalidation.md +57 -0
  27. package/library/practices/scalable-architecture.md +111 -0
  28. package/library/practices/ship-it-live.md +149 -0
  29. package/library/practices/skill-authoring.md +70 -0
  30. package/library/skills/.gitkeep +0 -0
  31. package/library/skills/boss-learn/SKILL.md +63 -0
  32. package/library/skills/boss-sync/SKILL.md +48 -0
  33. package/package.json +49 -0
  34. package/registry/CHANGELOG.md +2737 -0
  35. package/src/board.js +655 -0
  36. package/src/brain.js +288 -0
  37. package/src/cli.js +542 -0
  38. package/src/conscience.js +426 -0
  39. package/src/insights.js +147 -0
  40. package/src/learn.js +92 -0
  41. package/src/map.js +103 -0
  42. package/src/modes.js +82 -0
  43. package/src/paths.js +36 -0
  44. package/src/registry.js +34 -0
  45. package/src/scaffold.js +138 -0
  46. package/src/sync.js +292 -0
  47. package/src/team.js +103 -0
  48. package/stages/L0-quickstart/manifest.json +12 -0
  49. package/stages/L0-quickstart/template/.claude/agents/coder-generalist.md +31 -0
  50. package/stages/L0-quickstart/template/.claude/agents/mentor-venture.md +57 -0
  51. package/stages/L0-quickstart/template/.claude/agents/pm.md +28 -0
  52. package/stages/L0-quickstart/template/.claude/hooks/conscience.js +89 -0
  53. package/stages/L0-quickstart/template/.claude/hooks/lib/loop-runtime.js +507 -0
  54. package/stages/L0-quickstart/template/.claude/hooks/lib/yaml.js +163 -0
  55. package/stages/L0-quickstart/template/.claude/hooks/memory-cue.js +82 -0
  56. package/stages/L0-quickstart/template/.claude/hooks/secrets-guard.js +87 -0
  57. package/stages/L0-quickstart/template/.claude/rules/your-app-code.md +17 -0
  58. package/stages/L0-quickstart/template/.claude/settings.json +36 -0
  59. package/stages/L0-quickstart/template/.claude/skills/boss/SKILL.md +161 -0
  60. package/stages/L0-quickstart/template/.claude/skills/boss-learn/SKILL.md +63 -0
  61. package/stages/L0-quickstart/template/.claude/skills/boss-sync/SKILL.md +55 -0
  62. package/stages/L0-quickstart/template/.claude/skills/canvas/SKILL.md +112 -0
  63. package/stages/L0-quickstart/template/.claude/skills/comprehend/SKILL.md +72 -0
  64. package/stages/L0-quickstart/template/.claude/skills/decide/SKILL.md +122 -0
  65. package/stages/L0-quickstart/template/.claude/skills/feedback/SKILL.md +68 -0
  66. package/stages/L0-quickstart/template/.claude/skills/import/SKILL.md +73 -0
  67. package/stages/L0-quickstart/template/.claude/skills/persona/SKILL.md +92 -0
  68. package/stages/L0-quickstart/template/.claude/skills/prototype/SKILL.md +114 -0
  69. package/stages/L0-quickstart/template/.claude/skills/triage/SKILL.md +104 -0
  70. package/stages/L0-quickstart/template/.claude/skills/welcome/SKILL.md +262 -0
  71. package/stages/L0-quickstart/template/AGENTS.md +31 -0
  72. package/stages/L0-quickstart/template/CLAUDE.md +57 -0
  73. package/stages/L0-quickstart/template/docs/IDS.md +42 -0
  74. package/stages/L0-quickstart/template/docs/ideas/INDEX.md +24 -0
  75. package/stages/L0-quickstart/template/docs/loops/canvas-loop.md +90 -0
  76. package/stages/L0-quickstart/template/docs/loops/capture-loop.md +64 -0
  77. package/stages/L1-mvp/manifest.json +12 -0
  78. package/stages/L1-mvp/template/.claude/agents/mentor-architect.md +124 -0
  79. package/stages/L1-mvp/template/.claude/agents/mentor-cofounder.md +85 -0
  80. package/stages/L1-mvp/template/.claude/agents/mentor-gtm.md +49 -0
  81. package/stages/L1-mvp/template/.claude/agents/program-manager.md +46 -0
  82. package/stages/L1-mvp/template/.claude/agents/tester.md +42 -0
  83. package/stages/L1-mvp/template/.claude/hooks/auto-log.js +133 -0
  84. package/stages/L1-mvp/template/.claude/rules/feature-context.md +18 -0
  85. package/stages/L1-mvp/template/.claude/skills/ai-cost/SKILL.md +249 -0
  86. package/stages/L1-mvp/template/.claude/skills/ai-failure-states/SKILL.md +226 -0
  87. package/stages/L1-mvp/template/.claude/skills/ai-first-init/SKILL.md +227 -0
  88. package/stages/L1-mvp/template/.claude/skills/close/SKILL.md +170 -0
  89. package/stages/L1-mvp/template/.claude/skills/consult/SKILL.md +72 -0
  90. package/stages/L1-mvp/template/.claude/skills/cost-review/SKILL.md +204 -0
  91. package/stages/L1-mvp/template/.claude/skills/design-tokens-init/SKILL.md +192 -0
  92. package/stages/L1-mvp/template/.claude/skills/drift-deep/SKILL.md +170 -0
  93. package/stages/L1-mvp/template/.claude/skills/evals/SKILL.md +154 -0
  94. package/stages/L1-mvp/template/.claude/skills/extract/SKILL.md +209 -0
  95. package/stages/L1-mvp/template/.claude/skills/judge-traces/SKILL.md +68 -0
  96. package/stages/L1-mvp/template/.claude/skills/log/SKILL.md +64 -0
  97. package/stages/L1-mvp/template/.claude/skills/practice/SKILL.md +92 -0
  98. package/stages/L1-mvp/template/.claude/skills/pretotype/SKILL.md +95 -0
  99. package/stages/L1-mvp/template/.claude/skills/red-team/SKILL.md +137 -0
  100. package/stages/L1-mvp/template/.claude/skills/revalidate/SKILL.md +51 -0
  101. package/stages/L1-mvp/template/.claude/skills/ship/SKILL.md +105 -0
  102. package/stages/L1-mvp/template/.claude/skills/smoke/SKILL.md +43 -0
  103. package/stages/L1-mvp/template/.claude/skills/spec/SKILL.md +145 -0
  104. package/stages/L1-mvp/template/claude-append.md +122 -0
  105. package/stages/L1-mvp/template/docs/loops/ai-failure-state-loop.md +107 -0
  106. package/stages/L1-mvp/template/docs/loops/coordination-loop.md +116 -0
  107. package/stages/L1-mvp/template/docs/loops/cost-budget-loop.md +117 -0
  108. package/stages/L1-mvp/template/docs/loops/cost-review-loop.md +113 -0
  109. package/stages/L1-mvp/template/docs/loops/design-tokens-loop.md +98 -0
  110. package/stages/L1-mvp/template/docs/loops/drift-loop.md +149 -0
  111. package/stages/L1-mvp/template/docs/loops/extraction-loop.md +128 -0
  112. package/stages/L1-mvp/template/docs/loops/focus-loop.md +106 -0
  113. package/stages/L1-mvp/template/docs/loops/pretotype-loop.md +88 -0
  114. package/stages/L1-mvp/template/docs/loops/spec-loop.md +83 -0
  115. package/stages/L2-v1/manifest.json +12 -0
  116. package/stages/L2-v1/template/.claude/agents/db-architect.md +91 -0
  117. package/stages/L2-v1/template/.claude/agents/mentor-business.md +124 -0
  118. package/stages/L2-v1/template/.claude/agents/mentor-fundraising.md +72 -0
  119. package/stages/L2-v1/template/.claude/agents/mentor-pitch.md +84 -0
  120. package/stages/L2-v1/template/.claude/agents/mentor-talent.md +84 -0
  121. package/stages/L2-v1/template/.claude/agents/ui-designer.md +81 -0
  122. package/stages/L2-v1/template/.claude/agents/ux-designer.md +87 -0
  123. package/stages/L2-v1/template/.claude/skills/board/SKILL.md +98 -0
  124. package/stages/L2-v1/template/.claude/skills/design-review/SKILL.md +77 -0
  125. package/stages/L2-v1/template/.claude/skills/ux-check/SKILL.md +93 -0
  126. package/stages/L2-v1/template/claude-append.md +59 -0
  127. package/stages/L2-v1/template/docs/loops/design-drift-loop.md +108 -0
  128. package/stages/L3-scale/README.md +13 -0
package/src/brain.js ADDED
@@ -0,0 +1,288 @@
1
+ // boss brain — the venture brain's CLI surface (FEAT-022, the IDEA-022 spine).
2
+ //
3
+ // The brain is the conscience's persistent, first-person *read on the venture* —
4
+ // what makes continuity (and "feels like its own AI") real. Two homes, one
5
+ // owner each (mentor-architect's hard split, docs/architecture/venture-brain.md):
6
+ //
7
+ // .boss/brain/read.md model-owned. The POV, in plain English, sectioned by
8
+ // date. The conscience writes it at /close; the founder
9
+ // can read and correct it. Markdown is the TRUST
10
+ // mechanism — this is the one surface that records an
11
+ // opinion about the person, so it must be inspectable.
12
+ // .boss/brain/relationship.md model-owned. The relationship log: what the
13
+ // conscience SAID and what the founder DID with it (acted /
14
+ // ignored / pushed back / overrode). This is how the
15
+ // conscience learns whether its nudges land — the outcome
16
+ // half of the frequency ledger (IDEA-013). Written at /close.
17
+ // .boss/brain/index.json CLI-owned. A thin ledger over the prose so the CLI can
18
+ // stamp/diff/gate WITHOUT parsing it. Just {id, ts, kind,
19
+ // headline} — `kind` distinguishes read vs relationship.
20
+ //
21
+ // This module is layer-1 (IDEA-006): zero-dep, host-agnostic, deterministic. It
22
+ // never calls a model. The *writing* of the read is the model's job (the /close
23
+ // skill), and it calls `boss brain record` to keep the index honest rather than
24
+ // hand-writing JSON. v1 ships `boss brain` (read) + `record` (the write-stamp the
25
+ // skill needs); `--diff` / `--forget` are the next increment, per the v1 line in
26
+ // the architecture note.
27
+
28
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
29
+ import { join } from 'node:path';
30
+
31
+ const dim = (s) => (process.stdout.isTTY ? `\x1b[90m${s}\x1b[0m` : s);
32
+
33
+ function brainDir(projectDir) {
34
+ return join(projectDir, '.boss', 'brain');
35
+ }
36
+ function readPath(projectDir) {
37
+ return join(brainDir(projectDir), 'read.md');
38
+ }
39
+ function relationshipPath(projectDir) {
40
+ return join(brainDir(projectDir), 'relationship.md');
41
+ }
42
+ function indexPath(projectDir) {
43
+ return join(brainDir(projectDir), 'index.json');
44
+ }
45
+
46
+ function freshIndex() {
47
+ return { version: 1, created: new Date().toISOString(), last_write_ts: null, next_seq: 1, entries: [] };
48
+ }
49
+
50
+ function readIndex(projectDir) {
51
+ const f = indexPath(projectDir);
52
+ if (!existsSync(f)) return freshIndex();
53
+ try {
54
+ const idx = JSON.parse(readFileSync(f, 'utf8'));
55
+ // Tolerate a hand-edited or older index — fill the shape, don't throw.
56
+ return { ...freshIndex(), ...idx, entries: Array.isArray(idx.entries) ? idx.entries : [] };
57
+ } catch {
58
+ return freshIndex();
59
+ }
60
+ }
61
+
62
+ function writeIndex(projectDir, idx) {
63
+ mkdirSync(brainDir(projectDir), { recursive: true });
64
+ writeFileSync(indexPath(projectDir), JSON.stringify(idx, null, 2) + '\n');
65
+ }
66
+
67
+ // `boss brain record --headline "..."` — append one index entry. Called by the
68
+ // /close skill AFTER it writes the dated prose block into read.md, so the index
69
+ // stays a truthful ledger of the read without the model hand-authoring JSON.
70
+ // Returns the entry. The prose is the model's; the index integrity is the CLI's.
71
+ export function recordBrainEntry(projectDir, { headline, id, kind } = {}) {
72
+ const h = (headline || '').trim();
73
+ if (!h) throw new Error('usage: boss brain record --headline "<one-line summary>" [--kind read|relationship]');
74
+ const k = kind === 'relationship' ? 'relationship' : 'read'; // default read (back-compat)
75
+ const idx = readIndex(projectDir);
76
+ const ts = new Date().toISOString();
77
+ const entry = { id: id || `b${idx.next_seq}`, ts, kind: k, headline: h };
78
+ idx.entries.push(entry);
79
+ idx.next_seq = (idx.next_seq || idx.entries.length) + 1;
80
+ idx.last_write_ts = ts;
81
+ writeIndex(projectDir, idx);
82
+ return entry;
83
+ }
84
+
85
+ // The recency window: how many dated reads we keep verbatim before suggesting
86
+ // the founder evict or /close compress. Living memory ≠ infinite memory — an
87
+ // unbounded read.md is the bloat the conscience itself warns against.
88
+ const BRAIN_WINDOW = 8;
89
+
90
+ // Split read.md into dated blocks (`## YYYY-MM-DD ...`). Anything before the
91
+ // first dated header is preamble (a standing summary) and is never auto-evicted.
92
+ // Format-based only — the CLI owns block boundaries; the model owns the content.
93
+ function parseDatedBlocks(read) {
94
+ const lines = read.split('\n');
95
+ const blocks = [];
96
+ let preamble = [];
97
+ let cur = null;
98
+ const dateRe = /^##\s+(\d{4}-\d{2}-\d{2})\b/;
99
+ for (const l of lines) {
100
+ const m = l.match(dateRe);
101
+ if (m) {
102
+ if (cur) blocks.push(cur);
103
+ cur = { date: m[1], lines: [l] };
104
+ } else if (cur) {
105
+ cur.lines.push(l);
106
+ } else {
107
+ preamble.push(l);
108
+ }
109
+ }
110
+ if (cur) blocks.push(cur);
111
+ return { preamble: preamble.join('\n').replace(/\s+$/, ''), blocks };
112
+ }
113
+
114
+ // `boss brain --diff` — the evolution of the read over time (date + headline per
115
+ // session, from the index). Continuity made visible: how the conscience's read
116
+ // changed session to session, without dumping the whole prose.
117
+ export function renderBrainDiff(projectDir, stamp) {
118
+ const lines = [`\n ${stamp.name} · brain · how the read evolved`, ''];
119
+ const idx = readIndex(projectDir);
120
+ if (!idx.entries.length) {
121
+ // Fall back to dated-block headers if the index is empty but prose exists.
122
+ const rf = readPath(projectDir);
123
+ const blocks = existsSync(rf) ? parseDatedBlocks(readFileSync(rf, 'utf8')).blocks : [];
124
+ if (!blocks.length) {
125
+ lines.push(' Nothing recorded yet — the conscience writes its first read at /close.', '');
126
+ return lines.join('\n');
127
+ }
128
+ for (const b of blocks) lines.push(` ${b.date}`);
129
+ lines.push('');
130
+ return lines.join('\n');
131
+ }
132
+ for (const e of idx.entries) {
133
+ lines.push(` ${dim(e.ts.slice(0, 10))} ${e.headline}`);
134
+ }
135
+ lines.push('');
136
+ lines.push(` ${dim('the full prose is `boss brain`; evict old reads with `boss brain forget --before <date>`')}`);
137
+ lines.push('');
138
+ return lines.join('\n');
139
+ }
140
+
141
+ // `boss brain forget --before <YYYY-MM-DD>` (or `forget <id>`) — the EVICT side of
142
+ // living memory. Founder-invoked, never automatic (it records an opinion about a
143
+ // person — only the human prunes it). Drops dated blocks older than the date from
144
+ // read.md AND the matching index entries, keeping the preamble + recent reads.
145
+ export function forgetBrain(projectDir, { before, id } = {}) {
146
+ const rf = readPath(projectDir);
147
+ const idx = readIndex(projectDir);
148
+ let evictedBlocks = 0;
149
+ let evictedEntries = 0;
150
+
151
+ if (id) {
152
+ const keep = idx.entries.filter((e) => e.id !== id);
153
+ evictedEntries = idx.entries.length - keep.length;
154
+ idx.entries = keep;
155
+ writeIndex(projectDir, idx);
156
+ return { evictedBlocks, evictedEntries, mode: `entry ${id}` };
157
+ }
158
+
159
+ if (!before || !/^\d{4}-\d{2}-\d{2}$/.test(before)) {
160
+ throw new Error('usage: boss brain forget --before <YYYY-MM-DD> (or: forget --id <bN>)');
161
+ }
162
+
163
+ // Symmetric eviction: prune read.md AND relationship.md dated blocks older than
164
+ // the date, keeping each file's preamble (the standing summary survives).
165
+ for (const f of [rf, relationshipPath(projectDir)]) {
166
+ if (!existsSync(f)) continue;
167
+ const { preamble, blocks } = parseDatedBlocks(readFileSync(f, 'utf8'));
168
+ const kept = blocks.filter((b) => b.date >= before); // lexical YYYY-MM-DD compare
169
+ evictedBlocks += blocks.length - kept.length;
170
+ const body = [preamble, ...kept.map((b) => b.lines.join('\n').replace(/\s+$/, ''))]
171
+ .filter(Boolean).join('\n\n') + '\n';
172
+ writeFileSync(f, body);
173
+ }
174
+ const keptEntries = idx.entries.filter((e) => e.ts.slice(0, 10) >= before);
175
+ evictedEntries = idx.entries.length - keptEntries.length;
176
+ idx.entries = keptEntries;
177
+ writeIndex(projectDir, idx);
178
+ return { evictedBlocks, evictedEntries, mode: `before ${before}` };
179
+ }
180
+
181
+ // Render the brain for `boss brain`. A pure read of read.md + a one-line ledger
182
+ // footer from the index. Empty-state is honest: the brain is thin until the
183
+ // conscience has lived a few sessions with you (the anti-fortune-cookie posture).
184
+ export function renderBrain(projectDir, stamp) {
185
+ const lines = [];
186
+ lines.push('');
187
+ lines.push(` ${stamp.name} · brain`);
188
+ lines.push(` ${dim('the conscience\'s read on this venture — its POV, not the facts (canvas/RESUME hold those)')}`);
189
+ lines.push('');
190
+
191
+ const rf = readPath(projectDir);
192
+ if (!existsSync(rf)) {
193
+ lines.push(' The brain is empty — the conscience hasn\'t formed a read yet.');
194
+ lines.push(' It writes one at /close, once there\'s a session of work to look at.');
195
+ lines.push(` ${dim('Nothing to show after 0 sessions. This is the honest empty state, not a bug.')}`);
196
+ if (existsSync(relationshipPath(projectDir)) && readFileSync(relationshipPath(projectDir), 'utf8').trim()) {
197
+ lines.push(` ${dim('(a relationship log exists, though — `boss brain --relationship`)')}`);
198
+ }
199
+ lines.push('');
200
+ return lines.join('\n');
201
+ }
202
+
203
+ const read = readFileSync(rf, 'utf8').replace(/\s+$/, '');
204
+ for (const l of read.split('\n')) lines.push(` ${l}`);
205
+ lines.push('');
206
+
207
+ const idx = readIndex(projectDir);
208
+ const n = idx.entries.length;
209
+ if (n) {
210
+ const last = idx.last_write_ts ? idx.last_write_ts.slice(0, 10) : '—';
211
+ lines.push(` ${dim(`${n} entr${n === 1 ? 'y' : 'ies'} · last written ${last}`)}`);
212
+ }
213
+ // Recency-window gate — living memory, not infinite memory. When the read spans
214
+ // many sessions, nudge toward compression (model, at /close) or eviction (founder).
215
+ const blockCount = parseDatedBlocks(read).blocks.length;
216
+ if (blockCount > BRAIN_WINDOW) {
217
+ lines.push(` ${dim(`the read spans ${blockCount} sessions — \`boss brain forget --before <date>\` to evict old ones, or let /close compress`)}`);
218
+ }
219
+ // Point at the relationship log when one exists — the outcome half of the brain.
220
+ if (existsSync(relationshipPath(projectDir))) {
221
+ const rel = parseDatedBlocks(readFileSync(relationshipPath(projectDir), 'utf8')).blocks.length;
222
+ lines.push(` ${dim(`relationship log: ${rel} session${rel === 1 ? '' : 's'} of what-I-said-and-what-you-did · \`boss brain --relationship\``)}`);
223
+ }
224
+ lines.push(` ${dim('This is yours to correct — edit .boss/brain/read.md if the read is wrong.')}`);
225
+ lines.push('');
226
+ return lines.join('\n');
227
+ }
228
+
229
+ // `boss brain --relationship` — the relationship log: what the conscience said and
230
+ // what the founder did with it. This is the loop the frequency ledger (IDEA-013)
231
+ // only counts: did the nudge land? The conscience reads this to learn (Track 4).
232
+ export function renderRelationship(projectDir, stamp) {
233
+ const lines = [`\n ${stamp.name} · brain · relationship`,
234
+ ` ${dim('what the conscience said — and what you did with it (the outcome of its nudges)')}`, ''];
235
+ const rf = relationshipPath(projectDir);
236
+ if (!existsSync(rf) || !readFileSync(rf, 'utf8').trim()) {
237
+ lines.push(' No relationship log yet — the conscience records it at /close, once it has');
238
+ lines.push(' said something and seen what you did with it.', '');
239
+ return lines.join('\n');
240
+ }
241
+ const rel = readFileSync(rf, 'utf8').replace(/\s+$/, '');
242
+ for (const l of rel.split('\n')) lines.push(` ${l}`);
243
+ lines.push('');
244
+ lines.push(` ${dim('the conscience reads this to learn whether its nudges land; yours to correct too')}`);
245
+ lines.push('');
246
+ return lines.join('\n');
247
+ }
248
+
249
+ // `boss brain [record --headline "..."]`
250
+ export function brain(projectDir, stamp, args = []) {
251
+ const [sub, ...rest] = args;
252
+ if (sub === 'record') {
253
+ const flags = {};
254
+ for (let i = 0; i < rest.length; i++) {
255
+ if (rest[i].startsWith('--')) { flags[rest[i].slice(2)] = rest[i + 1]; i++; }
256
+ }
257
+ const entry = recordBrainEntry(projectDir, { headline: flags.headline, id: flags.id, kind: flags.kind });
258
+ const file = entry.kind === 'relationship' ? 'relationship.md' : 'read.md';
259
+ console.log(`\n ✦ Brain entry recorded — ${entry.id} (${entry.kind})`);
260
+ console.log(` ${entry.headline}`);
261
+ console.log(` ${dim(`(the prose lives in .boss/brain/${file}; this stamps the index)`)}\n`);
262
+ return;
263
+ }
264
+ if (sub === 'forget') {
265
+ const flags = {};
266
+ for (let i = 0; i < rest.length; i++) {
267
+ if (rest[i].startsWith('--')) { flags[rest[i].slice(2)] = rest[i + 1]; i++; }
268
+ }
269
+ const r = forgetBrain(projectDir, { before: flags.before, id: flags.id });
270
+ console.log(`\n ✦ Brain pruned (${r.mode}) — ${r.evictedBlocks} read(s) + ${r.evictedEntries} index entr${r.evictedEntries === 1 ? 'y' : 'ies'} evicted`);
271
+ console.log(` ${dim('the preamble + recent reads are kept; this is yours to do, never automatic')}\n`);
272
+ return;
273
+ }
274
+ if (sub === '--diff' || args.includes('--diff')) {
275
+ console.log(renderBrainDiff(projectDir, stamp));
276
+ return;
277
+ }
278
+ if (sub === '--relationship' || args.includes('--relationship')) {
279
+ console.log(renderRelationship(projectDir, stamp));
280
+ return;
281
+ }
282
+ if (sub) {
283
+ console.error(` boss: unknown subcommand 'brain ${sub}'. options: (none) | --diff | --relationship | record | forget`);
284
+ process.exitCode = 1;
285
+ return;
286
+ }
287
+ console.log(renderBrain(projectDir, stamp));
288
+ }