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.
- package/LICENSE +21 -0
- package/PRINCIPLES.md +70 -0
- package/README.md +213 -0
- package/VERSION +1 -0
- package/bin/boss +3 -0
- package/library/README.md +19 -0
- package/library/agents/.gitkeep +0 -0
- package/library/agents/mentor-venture.md +57 -0
- package/library/hooks/.gitkeep +0 -0
- package/library/hooks/auto-log.js +133 -0
- package/library/hooks/memory-cue.js +82 -0
- package/library/hooks/secrets-guard.js +87 -0
- package/library/memory-seed/README.md +29 -0
- package/library/memory-seed/durable-facts-example.md +16 -0
- package/library/practices/.gitkeep +0 -0
- package/library/practices/agent-security.md +111 -0
- package/library/practices/ai-adoption-culture.md +104 -0
- package/library/practices/ai-ux-patterns.md +246 -0
- package/library/practices/celebration-of-done.md +100 -0
- package/library/practices/conscience-voicing.md +121 -0
- package/library/practices/context-discipline.md +116 -0
- package/library/practices/design-system.md +152 -0
- package/library/practices/git-workflow.md +119 -0
- package/library/practices/harm-taxonomy.md +45 -0
- package/library/practices/quality-ratchet.md +48 -0
- package/library/practices/revalidation.md +57 -0
- package/library/practices/scalable-architecture.md +111 -0
- package/library/practices/ship-it-live.md +149 -0
- package/library/practices/skill-authoring.md +70 -0
- package/library/skills/.gitkeep +0 -0
- package/library/skills/boss-learn/SKILL.md +63 -0
- package/library/skills/boss-sync/SKILL.md +48 -0
- package/package.json +49 -0
- package/registry/CHANGELOG.md +2737 -0
- package/src/board.js +655 -0
- package/src/brain.js +288 -0
- package/src/cli.js +542 -0
- package/src/conscience.js +426 -0
- package/src/insights.js +147 -0
- package/src/learn.js +92 -0
- package/src/map.js +103 -0
- package/src/modes.js +82 -0
- package/src/paths.js +36 -0
- package/src/registry.js +34 -0
- package/src/scaffold.js +138 -0
- package/src/sync.js +292 -0
- package/src/team.js +103 -0
- package/stages/L0-quickstart/manifest.json +12 -0
- package/stages/L0-quickstart/template/.claude/agents/coder-generalist.md +31 -0
- package/stages/L0-quickstart/template/.claude/agents/mentor-venture.md +57 -0
- package/stages/L0-quickstart/template/.claude/agents/pm.md +28 -0
- package/stages/L0-quickstart/template/.claude/hooks/conscience.js +89 -0
- package/stages/L0-quickstart/template/.claude/hooks/lib/loop-runtime.js +507 -0
- package/stages/L0-quickstart/template/.claude/hooks/lib/yaml.js +163 -0
- package/stages/L0-quickstart/template/.claude/hooks/memory-cue.js +82 -0
- package/stages/L0-quickstart/template/.claude/hooks/secrets-guard.js +87 -0
- package/stages/L0-quickstart/template/.claude/rules/your-app-code.md +17 -0
- package/stages/L0-quickstart/template/.claude/settings.json +36 -0
- package/stages/L0-quickstart/template/.claude/skills/boss/SKILL.md +161 -0
- package/stages/L0-quickstart/template/.claude/skills/boss-learn/SKILL.md +63 -0
- package/stages/L0-quickstart/template/.claude/skills/boss-sync/SKILL.md +55 -0
- package/stages/L0-quickstart/template/.claude/skills/canvas/SKILL.md +112 -0
- package/stages/L0-quickstart/template/.claude/skills/comprehend/SKILL.md +72 -0
- package/stages/L0-quickstart/template/.claude/skills/decide/SKILL.md +122 -0
- package/stages/L0-quickstart/template/.claude/skills/feedback/SKILL.md +68 -0
- package/stages/L0-quickstart/template/.claude/skills/import/SKILL.md +73 -0
- package/stages/L0-quickstart/template/.claude/skills/persona/SKILL.md +92 -0
- package/stages/L0-quickstart/template/.claude/skills/prototype/SKILL.md +114 -0
- package/stages/L0-quickstart/template/.claude/skills/triage/SKILL.md +104 -0
- package/stages/L0-quickstart/template/.claude/skills/welcome/SKILL.md +262 -0
- package/stages/L0-quickstart/template/AGENTS.md +31 -0
- package/stages/L0-quickstart/template/CLAUDE.md +57 -0
- package/stages/L0-quickstart/template/docs/IDS.md +42 -0
- package/stages/L0-quickstart/template/docs/ideas/INDEX.md +24 -0
- package/stages/L0-quickstart/template/docs/loops/canvas-loop.md +90 -0
- package/stages/L0-quickstart/template/docs/loops/capture-loop.md +64 -0
- package/stages/L1-mvp/manifest.json +12 -0
- package/stages/L1-mvp/template/.claude/agents/mentor-architect.md +124 -0
- package/stages/L1-mvp/template/.claude/agents/mentor-cofounder.md +85 -0
- package/stages/L1-mvp/template/.claude/agents/mentor-gtm.md +49 -0
- package/stages/L1-mvp/template/.claude/agents/program-manager.md +46 -0
- package/stages/L1-mvp/template/.claude/agents/tester.md +42 -0
- package/stages/L1-mvp/template/.claude/hooks/auto-log.js +133 -0
- package/stages/L1-mvp/template/.claude/rules/feature-context.md +18 -0
- package/stages/L1-mvp/template/.claude/skills/ai-cost/SKILL.md +249 -0
- package/stages/L1-mvp/template/.claude/skills/ai-failure-states/SKILL.md +226 -0
- package/stages/L1-mvp/template/.claude/skills/ai-first-init/SKILL.md +227 -0
- package/stages/L1-mvp/template/.claude/skills/close/SKILL.md +170 -0
- package/stages/L1-mvp/template/.claude/skills/consult/SKILL.md +72 -0
- package/stages/L1-mvp/template/.claude/skills/cost-review/SKILL.md +204 -0
- package/stages/L1-mvp/template/.claude/skills/design-tokens-init/SKILL.md +192 -0
- package/stages/L1-mvp/template/.claude/skills/drift-deep/SKILL.md +170 -0
- package/stages/L1-mvp/template/.claude/skills/evals/SKILL.md +154 -0
- package/stages/L1-mvp/template/.claude/skills/extract/SKILL.md +209 -0
- package/stages/L1-mvp/template/.claude/skills/judge-traces/SKILL.md +68 -0
- package/stages/L1-mvp/template/.claude/skills/log/SKILL.md +64 -0
- package/stages/L1-mvp/template/.claude/skills/practice/SKILL.md +92 -0
- package/stages/L1-mvp/template/.claude/skills/pretotype/SKILL.md +95 -0
- package/stages/L1-mvp/template/.claude/skills/red-team/SKILL.md +137 -0
- package/stages/L1-mvp/template/.claude/skills/revalidate/SKILL.md +51 -0
- package/stages/L1-mvp/template/.claude/skills/ship/SKILL.md +105 -0
- package/stages/L1-mvp/template/.claude/skills/smoke/SKILL.md +43 -0
- package/stages/L1-mvp/template/.claude/skills/spec/SKILL.md +145 -0
- package/stages/L1-mvp/template/claude-append.md +122 -0
- package/stages/L1-mvp/template/docs/loops/ai-failure-state-loop.md +107 -0
- package/stages/L1-mvp/template/docs/loops/coordination-loop.md +116 -0
- package/stages/L1-mvp/template/docs/loops/cost-budget-loop.md +117 -0
- package/stages/L1-mvp/template/docs/loops/cost-review-loop.md +113 -0
- package/stages/L1-mvp/template/docs/loops/design-tokens-loop.md +98 -0
- package/stages/L1-mvp/template/docs/loops/drift-loop.md +149 -0
- package/stages/L1-mvp/template/docs/loops/extraction-loop.md +128 -0
- package/stages/L1-mvp/template/docs/loops/focus-loop.md +106 -0
- package/stages/L1-mvp/template/docs/loops/pretotype-loop.md +88 -0
- package/stages/L1-mvp/template/docs/loops/spec-loop.md +83 -0
- package/stages/L2-v1/manifest.json +12 -0
- package/stages/L2-v1/template/.claude/agents/db-architect.md +91 -0
- package/stages/L2-v1/template/.claude/agents/mentor-business.md +124 -0
- package/stages/L2-v1/template/.claude/agents/mentor-fundraising.md +72 -0
- package/stages/L2-v1/template/.claude/agents/mentor-pitch.md +84 -0
- package/stages/L2-v1/template/.claude/agents/mentor-talent.md +84 -0
- package/stages/L2-v1/template/.claude/agents/ui-designer.md +81 -0
- package/stages/L2-v1/template/.claude/agents/ux-designer.md +87 -0
- package/stages/L2-v1/template/.claude/skills/board/SKILL.md +98 -0
- package/stages/L2-v1/template/.claude/skills/design-review/SKILL.md +77 -0
- package/stages/L2-v1/template/.claude/skills/ux-check/SKILL.md +93 -0
- package/stages/L2-v1/template/claude-append.md +59 -0
- package/stages/L2-v1/template/docs/loops/design-drift-loop.md +108 -0
- 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
|
+
}
|