atris 3.14.0 → 3.15.11

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.
@@ -0,0 +1,840 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const crypto = require('crypto');
4
+
5
+ const GENERATED_START = '<!-- ATRIS_BRAIN_COMPILE:START -->';
6
+ const GENERATED_END = '<!-- ATRIS_BRAIN_COMPILE:END -->';
7
+
8
+ function parseArgs(args) {
9
+ const options = {
10
+ root: process.cwd(),
11
+ verify: false,
12
+ json: false,
13
+ rating: null,
14
+ recommendation: null,
15
+ note: '',
16
+ member: null,
17
+ mode: null,
18
+ };
19
+
20
+ const positional = [];
21
+ for (let i = 0; i < args.length; i++) {
22
+ const arg = args[i];
23
+ if (arg === '--root' && args[i + 1]) {
24
+ options.root = args[++i];
25
+ } else if (arg.startsWith('--root=')) {
26
+ options.root = arg.slice('--root='.length);
27
+ } else if (arg === '--verify') {
28
+ options.verify = true;
29
+ } else if (arg === '--json') {
30
+ options.json = true;
31
+ } else if (arg === '--rating' && args[i + 1]) {
32
+ options.rating = args[++i];
33
+ } else if (arg.startsWith('--rating=')) {
34
+ options.rating = arg.slice('--rating='.length);
35
+ } else if (arg === '--recommendation' && args[i + 1]) {
36
+ options.recommendation = args[++i];
37
+ } else if (arg.startsWith('--recommendation=')) {
38
+ options.recommendation = arg.slice('--recommendation='.length);
39
+ } else if (arg === '--note' && args[i + 1]) {
40
+ options.note = args[++i];
41
+ } else if (arg.startsWith('--note=')) {
42
+ options.note = arg.slice('--note='.length);
43
+ } else if (arg === '--member' && args[i + 1]) {
44
+ options.member = args[++i];
45
+ } else if (arg.startsWith('--member=')) {
46
+ options.member = arg.slice('--member='.length);
47
+ } else if (arg === '--mode' && args[i + 1]) {
48
+ options.mode = args[++i];
49
+ } else if (arg.startsWith('--mode=')) {
50
+ options.mode = arg.slice('--mode='.length);
51
+ } else if (!arg.startsWith('-')) {
52
+ positional.push(arg);
53
+ }
54
+ }
55
+
56
+ const subcommand = positional[0] || 'compile';
57
+ if (!options.note && ['yes', 'no', 'edit', 'go', 'hold'].includes(subcommand) && positional.length > 1) {
58
+ options.note = positional.slice(1).join(' ');
59
+ }
60
+ if (subcommand === 'approval' || subcommand === 'approve') {
61
+ options.decision = positional[1] || null;
62
+ if (!options.note && positional.length > 2) {
63
+ options.note = positional.slice(2).join(' ');
64
+ }
65
+ }
66
+
67
+ return {
68
+ subcommand,
69
+ options: {
70
+ ...options,
71
+ root: path.resolve(options.root),
72
+ },
73
+ };
74
+ }
75
+
76
+ function readText(filePath) {
77
+ try {
78
+ return fs.readFileSync(filePath, 'utf8');
79
+ } catch {
80
+ return '';
81
+ }
82
+ }
83
+
84
+ function readJson(filePath) {
85
+ try {
86
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
87
+ } catch {
88
+ return null;
89
+ }
90
+ }
91
+
92
+ function appendJsonl(filePath, row) {
93
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
94
+ fs.appendFileSync(filePath, JSON.stringify(row) + '\n', 'utf8');
95
+ }
96
+
97
+ function sha256Text(value) {
98
+ return crypto.createHash('sha256').update(String(value || '')).digest('hex');
99
+ }
100
+
101
+ function shortHash(value) {
102
+ return sha256Text(value).slice(0, 12);
103
+ }
104
+
105
+ function readJsonlStats(filePath) {
106
+ const text = readText(filePath);
107
+ const lines = text.split('\n').filter(line => line.trim());
108
+ let valid = 0;
109
+ let latestTs = null;
110
+
111
+ for (const line of lines) {
112
+ try {
113
+ const row = JSON.parse(line);
114
+ valid += 1;
115
+ const ts = row.ts || row.timestamp || row.created_at || row.updated_at || row.sent_at || row.date;
116
+ if (ts && (!latestTs || String(ts) > String(latestTs))) latestTs = String(ts);
117
+ } catch {
118
+ // Keep the raw count honest but do not fail compilation on one bad line.
119
+ }
120
+ }
121
+
122
+ return {
123
+ path: filePath,
124
+ exists: fs.existsSync(filePath),
125
+ rows: lines.length,
126
+ validRows: valid,
127
+ latestTs,
128
+ };
129
+ }
130
+
131
+ function countTodoItems(todoText) {
132
+ const unchecked = (todoText.match(/^\s*-\s+\[[ ]\]/gm) || []).length;
133
+ const checked = (todoText.match(/^\s*-\s+\[[xX]\]/gm) || []).length;
134
+ const titled = (todoText.match(/^\s*-\s+\*\*[^*]+:\*\*/gm) || []).length;
135
+ const done = (todoText.match(/~~|DONE|✅/g) || []).length;
136
+ return {
137
+ open: unchecked + Math.max(0, titled - done),
138
+ checked,
139
+ titled,
140
+ done,
141
+ };
142
+ }
143
+
144
+ function listMarkdown(root, relDir, limit = 12) {
145
+ const dir = path.join(root, relDir);
146
+ if (!fs.existsSync(dir)) return [];
147
+ const out = [];
148
+
149
+ function walk(current) {
150
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
151
+ if (out.length >= limit) return;
152
+ const full = path.join(current, entry.name);
153
+ if (entry.isDirectory()) {
154
+ walk(full);
155
+ } else if (entry.isFile() && entry.name.endsWith('.md')) {
156
+ out.push(path.relative(root, full).replace(/\\/g, '/'));
157
+ }
158
+ }
159
+ }
160
+
161
+ walk(dir);
162
+ return out;
163
+ }
164
+
165
+ function firstHeading(text, fallback) {
166
+ const match = String(text || '').match(/^#\s+(.+)$/m);
167
+ return match ? match[1].trim() : fallback;
168
+ }
169
+
170
+ function collectState(root) {
171
+ const atrisDir = path.join(root, 'atris');
172
+ const stateDir = path.join(root, '.atris', 'state');
173
+ const business = readJson(path.join(root, '.atris', 'business.json')) || {};
174
+ const todoText = readText(path.join(atrisDir, 'TODO.md'));
175
+ const mapText = readText(path.join(atrisDir, 'MAP.md'));
176
+ const nowText = readText(path.join(atrisDir, 'now.md'));
177
+ const wikiStatus = readText(path.join(atrisDir, 'wiki', 'STATUS.md'));
178
+ const status = readText(path.join(atrisDir, 'STATUS.md'));
179
+
180
+ const stateFiles = [
181
+ 'events.jsonl',
182
+ 'episodes.jsonl',
183
+ 'scorecards.jsonl',
184
+ 'agent_tasks.jsonl',
185
+ 'agent_mail.jsonl',
186
+ 'agent_inboxes.jsonl',
187
+ 'agents.jsonl',
188
+ 'approvals.jsonl',
189
+ ].map(name => readJsonlStats(path.join(stateDir, name)));
190
+
191
+ const totalRows = stateFiles.reduce((sum, item) => sum + item.rows, 0);
192
+ const validRows = stateFiles.reduce((sum, item) => sum + item.validRows, 0);
193
+ const latestStateTs = stateFiles
194
+ .map(item => item.latestTs)
195
+ .filter(Boolean)
196
+ .sort()
197
+ .pop() || null;
198
+
199
+ return {
200
+ generatedAt: new Date().toISOString(),
201
+ root,
202
+ name: business.name || business.slug || firstHeading(status || mapText, path.basename(root)),
203
+ slug: business.slug || path.basename(root),
204
+ business,
205
+ todo: countTodoItems(todoText),
206
+ hasNow: nowText.length > 0,
207
+ nowHeading: firstHeading(nowText, null),
208
+ hasMap: mapText.length > 0,
209
+ hasWikiStatus: wikiStatus.length > 0,
210
+ mapLineCount: mapText ? mapText.split('\n').length : 0,
211
+ wikiPages: listMarkdown(root, 'atris/wiki', 20),
212
+ stateFiles,
213
+ totalRows,
214
+ validRows,
215
+ latestStateTs,
216
+ };
217
+ }
218
+
219
+ function strongestSignal(state) {
220
+ const mail = state.stateFiles.find(item => item.path.endsWith('agent_mail.jsonl'))?.rows || 0;
221
+ const tasks = state.stateFiles.find(item => item.path.endsWith('agent_tasks.jsonl'))?.rows || 0;
222
+ const scorecards = state.stateFiles.find(item => item.path.endsWith('scorecards.jsonl'))?.rows || 0;
223
+ const episodes = state.stateFiles.find(item => item.path.endsWith('episodes.jsonl'))?.rows || 0;
224
+ if (scorecards > 0 && episodes > 0) return `${scorecards} scorecard row(s) and ${episodes} episode row(s) are available for feedback-driven learning.`;
225
+ if (scorecards > 0) return `${scorecards} scorecard row(s) are available for outcome scoring.`;
226
+ if (mail > 0) return `${mail} agent-mail row(s) are available; compile them into decisions, follow-ups, and CRM memory.`;
227
+ if (tasks > 0) return `${tasks} agent-task row(s) are available; use them to choose the next action.`;
228
+ return 'Workspace has structure, but little scored state yet; first improvement is to create scorecards and episodes.';
229
+ }
230
+
231
+ function nextMove(state) {
232
+ if (state.totalRows > 0 && (state.stateFiles.find(item => item.path.endsWith('scorecards.jsonl'))?.rows || 0) === 0) {
233
+ return 'Turn existing state rows into the first scorecard so the next run has a reward signal, not just memory.';
234
+ }
235
+ if ((state.stateFiles.find(item => item.path.endsWith('episodes.jsonl'))?.rows || 0) === 0) {
236
+ return 'Capture one operator approval, edit, or rejection as an episode so the brain has a learning trace.';
237
+ }
238
+ if (state.todo.open > 0) return 'Pick the highest-leverage open TODO item and leave a scorecard when done.';
239
+ return 'Run a business loop, verify the result, then re-run `atris brain compile`.';
240
+ }
241
+
242
+ function rewardForRating(rating) {
243
+ const normalized = String(rating || '').toLowerCase();
244
+ if (normalized === 'approve' || normalized === 'approved' || normalized === 'send' || normalized === 'sent') return 1;
245
+ if (normalized === 'edit' || normalized === 'edited') return 0.5;
246
+ if (normalized === 'reject' || normalized === 'rejected' || normalized === 'no') return -1;
247
+ throw new Error('rating must be approve, edit, or reject');
248
+ }
249
+
250
+ function latestRecommendation(root) {
251
+ const brainState = readJson(path.join(root, 'atris', 'brain', 'state.json'));
252
+ if (brainState) return nextMove(brainState);
253
+ return nextMove(collectState(root));
254
+ }
255
+
256
+ function loadBrainState(root) {
257
+ return readJson(path.join(root, 'atris', 'brain', 'state.json')) || collectState(root);
258
+ }
259
+
260
+ function readMemberContext(root, memberSlug) {
261
+ if (!memberSlug) return null;
262
+ const slug = String(memberSlug).toLowerCase().replace(/[^a-z0-9_-]/g, '');
263
+ if (!slug) return null;
264
+ const memberDir = path.join(root, 'atris', 'team', slug);
265
+ const memberText = readText(path.join(memberDir, 'MEMBER.md'));
266
+ if (!memberText) return null;
267
+ return {
268
+ slug,
269
+ name: firstHeading(memberText, slug),
270
+ startHere: readText(path.join(memberDir, 'START_HERE.md')),
271
+ goals: readText(path.join(memberDir, 'goals.md')),
272
+ };
273
+ }
274
+
275
+ function listMemberSlugs(root) {
276
+ const teamDir = path.join(root, 'atris', 'team');
277
+ if (!fs.existsSync(teamDir)) return [];
278
+ return fs.readdirSync(teamDir, { withFileTypes: true })
279
+ .filter(entry => entry.isDirectory())
280
+ .map(entry => entry.name)
281
+ .filter(name => !name.startsWith('_'))
282
+ .filter(name => fs.existsSync(path.join(teamDir, name, 'MEMBER.md')))
283
+ .sort();
284
+ }
285
+
286
+ function operatorStatePath(root) {
287
+ return path.join(root, '.atris', 'state', 'operator.json');
288
+ }
289
+
290
+ function readRememberedOperator(root) {
291
+ const state = readJson(operatorStatePath(root));
292
+ return state?.member || null;
293
+ }
294
+
295
+ function rememberOperator(root, member) {
296
+ if (!member?.slug) return;
297
+ const filePath = operatorStatePath(root);
298
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
299
+ fs.writeFileSync(filePath, JSON.stringify({
300
+ member: member.slug,
301
+ name: member.name,
302
+ remembered_at: new Date().toISOString(),
303
+ }, null, 2) + '\n', 'utf8');
304
+ }
305
+
306
+ function memberNextMove(member) {
307
+ if (!member) return null;
308
+ const name = member.name || member.slug;
309
+ const context = `${member.startHere}\n${member.goals}`;
310
+ const identity = `${member.slug}\n${member.name}`;
311
+ if (member.slug === 'justin' || /justin/i.test(member.name || '')) {
312
+ return `${name}: run one customer-moving GTM rep, update the relevant workspace state within 10 minutes, and leave a scorecard.`;
313
+ }
314
+ if (member.slug === 'keshav' || /keshav/i.test(member.name || '')) {
315
+ return `${name}: make one high-leverage CEO move: ship product, close a strategic loop, or make a queued decision; leave proof and a scorecard.`;
316
+ }
317
+ if (/gtm|forward deployed/i.test(identity) || /gtm|forward deployed|customer-moving|customer move/i.test(context)) {
318
+ return `${name}: run one customer-moving GTM rep, update the relevant workspace state within 10 minutes, and leave a scorecard.`;
319
+ }
320
+ if (/ceo|lab/i.test(identity) || /ceo|lab|synthesis loop|decision queue|building|closing|investor/i.test(context)) {
321
+ return `${name}: make one high-leverage CEO move: ship product, close a strategic loop, or make a queued decision; leave proof and a scorecard.`;
322
+ }
323
+ return `${name}: use your START_HERE, complete the first concrete work block, and leave a scorecard.`;
324
+ }
325
+
326
+ function modeNextMove(member, mode) {
327
+ if (!member || !mode) return null;
328
+ const normalized = String(mode).toLowerCase().replace(/[_-]+/g, ' ').trim();
329
+ const name = member.name || member.slug;
330
+ if (/founder|lab|idea|strategy/.test(normalized)) {
331
+ return {
332
+ label: 'founder lab',
333
+ move: `${name}: think through one crazy company idea, turn it into a customer wedge hypothesis, then route execution to Justin or Build.`,
334
+ proof: 'one idea note, one customer target, one delegated next action, and one scorecard',
335
+ };
336
+ }
337
+ if (/build|builder|product|backend|code/.test(normalized)) {
338
+ return {
339
+ label: 'builder',
340
+ move: `${name}: ship one product or system improvement that makes Atris easier to sell, operate, or self-improve.`,
341
+ proof: 'one shipped diff or artifact, verification output, and one scorecard',
342
+ };
343
+ }
344
+ if (/close|closer|whale|investor|customer/.test(normalized)) {
345
+ return {
346
+ label: 'closer',
347
+ move: `${name}: advance one whale, investor, or strategic customer conversation with a concrete next step.`,
348
+ proof: 'one drafted or sent message, one relationship update, one next step, and one scorecard',
349
+ };
350
+ }
351
+ if (/decision|queue|approve|approval/.test(normalized)) {
352
+ return {
353
+ label: 'decision queue',
354
+ move: `${name}: clear one high-leverage yes/no decision that only you should make, then delegate the follow-through.`,
355
+ proof: 'one decision recorded, one owner assigned, one follow-up path, and one scorecard',
356
+ };
357
+ }
358
+ return null;
359
+ }
360
+
361
+ function renderActivationCard(state, options = {}) {
362
+ const requestedMember = options.member || readRememberedOperator(state.root);
363
+ const member = readMemberContext(state.root, requestedMember);
364
+ const move = nextMove(state);
365
+ if (!member && !options.member) {
366
+ return `CONTEXT: ${state.name} Brain
367
+ OPERATOR: unknown
368
+ NEXT MOVE: Tell Atris who is operating: atris brain activate --member <name> --root ${state.root}
369
+ WHY: The brain should route work by operator, customer, and proof path before it assigns the next move.
370
+ PROOF: Activation re-runs with a known operator and produces a specific work block.
371
+ FEEDBACK: yes / edit / no`;
372
+ }
373
+ rememberOperator(state.root, member);
374
+ const modeMove = modeNextMove(member, options.mode);
375
+ const next = modeMove?.move || memberNextMove(member) || move;
376
+ const proof = modeMove?.proof || `After the move, record feedback and recompile: atris brain yes|edit|no "note" --root ${state.root} --verify && atris brain compile --root ${state.root} --verify`;
377
+ return `CONTEXT: ${state.name} Brain${member ? `\nOPERATOR: ${member.name}` : ''}${modeMove ? `\nMODE: ${modeMove.label}` : ''}
378
+ NEXT MOVE: ${next}
379
+ WHY: This is the next business workflow to improve from atris/now.md, the compiled brain, MAP, TODO, wiki, state rows, and reward history.
380
+ PROOF: ${proof}
381
+ FEEDBACK: yes / edit / no`;
382
+ }
383
+
384
+ function renderActivationGallery(state) {
385
+ const slugs = listMemberSlugs(state.root);
386
+ if (slugs.length === 0) {
387
+ return `CONTEXT: ${state.name} Brain
388
+ TEAM: no members found
389
+ NEXT MOVE: Add a member under atris/team/<name>/MEMBER.md, then run activation again.`;
390
+ }
391
+
392
+ return slugs.map(slug => renderActivationCard(state, { member: slug })).join('\n\n---\n\n');
393
+ }
394
+
395
+ function recordFeedback(options) {
396
+ const root = options.root;
397
+ const rating = String(options.rating || '').toLowerCase();
398
+ const reward = rewardForRating(rating);
399
+ const recommendation = options.recommendation || latestRecommendation(root);
400
+ if (!recommendation) throw new Error('recommendation is required');
401
+
402
+ const beforeBrain = readText(path.join(root, 'atris', 'brain', 'state.json'));
403
+ const beforeBrainHash = shortHash(beforeBrain);
404
+ const ts = new Date().toISOString();
405
+ const decisionId = `brain-${ts.replace(/[^0-9TZ]/g, '').toLowerCase()}-${shortHash(`${root}|${recommendation}|${rating}|${options.note}`)}`;
406
+ const workspace = readJson(path.join(root, '.atris', 'business.json')) || {};
407
+
408
+ const scorecard = {
409
+ ts,
410
+ schema: 'atris.brain.scorecard.v1',
411
+ decision_id: decisionId,
412
+ workspace: workspace.slug || path.basename(root),
413
+ business_id: workspace.business_id || null,
414
+ workspace_id: workspace.workspace_id || null,
415
+ recommendation,
416
+ human_rating: rating,
417
+ human_note: options.note || '',
418
+ reward,
419
+ before_brain_hash: beforeBrainHash,
420
+ source: 'operator_feedback',
421
+ };
422
+
423
+ const episode = {
424
+ ts,
425
+ schema: 'atris.brain.feedback_episode.v1',
426
+ episode_id: decisionId,
427
+ task_type: 'business_brain_feedback',
428
+ state: {
429
+ workspace: scorecard.workspace,
430
+ before_brain_hash: beforeBrainHash,
431
+ recommendation,
432
+ },
433
+ action: {
434
+ recommendation,
435
+ },
436
+ feedback: {
437
+ rating,
438
+ note: options.note || '',
439
+ },
440
+ reward,
441
+ training_example: {
442
+ messages: [
443
+ { role: 'system', content: 'Recommend the next business action from the compiled Atris brain.' },
444
+ { role: 'assistant', content: recommendation },
445
+ { role: 'user', content: `${rating}${options.note ? `: ${options.note}` : ''}` },
446
+ ],
447
+ },
448
+ };
449
+
450
+ const stateDir = path.join(root, '.atris', 'state');
451
+ appendJsonl(path.join(stateDir, 'scorecards.jsonl'), scorecard);
452
+ appendJsonl(path.join(stateDir, 'episodes.jsonl'), episode);
453
+
454
+ return { scorecard, episode };
455
+ }
456
+
457
+ function normalizeApprovalDecision(decision) {
458
+ const normalized = String(decision || '').toLowerCase();
459
+ if (normalized === 'go') return 'go';
460
+ if (normalized === 'edit') return 'edit';
461
+ if (normalized === 'hold') return 'hold';
462
+ throw new Error('decision must be go, edit, or hold');
463
+ }
464
+
465
+ function approvalStatus(decision) {
466
+ if (decision === 'go') return 'approved_to_proceed';
467
+ if (decision === 'edit') return 'needs_adjustment_before_action';
468
+ return 'held_do_not_proceed';
469
+ }
470
+
471
+ function recordApproval(options) {
472
+ const root = options.root;
473
+ const decision = normalizeApprovalDecision(options.decision);
474
+ const recommendation = options.recommendation || latestRecommendation(root);
475
+ if (!recommendation) throw new Error('recommendation is required');
476
+
477
+ const beforeBrain = readText(path.join(root, 'atris', 'brain', 'state.json'));
478
+ const beforeBrainHash = shortHash(beforeBrain);
479
+ const ts = new Date().toISOString();
480
+ const approvalId = `approval-${ts.replace(/[^0-9TZ]/g, '').toLowerCase()}-${shortHash(`${root}|${recommendation}|${decision}|${options.note}`)}`;
481
+ const workspace = readJson(path.join(root, '.atris', 'business.json')) || {};
482
+
483
+ const approval = {
484
+ ts,
485
+ schema: 'atris.brain.approval.v1',
486
+ approval_id: approvalId,
487
+ workspace: workspace.slug || path.basename(root),
488
+ business_id: workspace.business_id || null,
489
+ workspace_id: workspace.workspace_id || null,
490
+ recommendation,
491
+ human_decision: decision,
492
+ status: approvalStatus(decision),
493
+ human_note: options.note || '',
494
+ before_brain_hash: beforeBrainHash,
495
+ source: 'operator_approval',
496
+ };
497
+
498
+ appendJsonl(path.join(root, '.atris', 'state', 'approvals.jsonl'), approval);
499
+ return approval;
500
+ }
501
+
502
+ function verifyFeedback(root, decisionId) {
503
+ const scorecards = readText(path.join(root, '.atris', 'state', 'scorecards.jsonl'));
504
+ const episodes = readText(path.join(root, '.atris', 'state', 'episodes.jsonl'));
505
+ if (!scorecards.includes(decisionId) || !episodes.includes(decisionId)) {
506
+ throw new Error(`feedback rows missing decision_id ${decisionId}`);
507
+ }
508
+ }
509
+
510
+ function verifyApproval(root, approvalId) {
511
+ const approvals = readText(path.join(root, '.atris', 'state', 'approvals.jsonl'));
512
+ if (!approvals.includes(approvalId)) {
513
+ throw new Error(`approval row missing approval_id ${approvalId}`);
514
+ }
515
+ }
516
+
517
+ function renderStatus(state) {
518
+ return `# Atris Brain Status
519
+
520
+ - Generated: ${state.generatedAt}
521
+ - Workspace: ${state.name}
522
+ - Slug: ${state.slug}
523
+ - Root: ${state.root}
524
+ - Now loaded: ${state.hasNow ? `yes (${state.nowHeading || 'no heading'})` : 'no'}
525
+ - MAP loaded: ${state.hasMap ? `yes (${state.mapLineCount} lines)` : 'no'}
526
+ - Wiki status loaded: ${state.hasWikiStatus ? 'yes' : 'no'}
527
+ - TODO open estimate: ${state.todo.open}
528
+ - State rows: ${state.totalRows} raw / ${state.validRows} valid JSONL
529
+ - Latest state timestamp: ${state.latestStateTs || 'none found'}
530
+
531
+ ## What Improved
532
+
533
+ This run compiled scattered workspace state into one loadable brain:
534
+
535
+ - source map: \`atris/MAP.md\`
536
+ - current state front door: \`atris/now.md\`
537
+ - task queue: \`atris/TODO.md\`
538
+ - wiki status: \`atris/wiki/STATUS.md\`
539
+ - run state: \`.atris/state/*.jsonl\`
540
+ - self-improvement ledger: \`atris/brain/self_improvement_ledger.md\`
541
+
542
+ ## Strongest Signal
543
+
544
+ ${strongestSignal(state)}
545
+
546
+ ## Next Move
547
+
548
+ ${nextMove(state)}
549
+
550
+ ## Load Order For Future Agents
551
+
552
+ 1. \`atris/now.md\`
553
+ 2. \`atris/brain/STATUS.md\`
554
+ 3. \`atris/brain/self_improvement_ledger.md\`
555
+ 4. \`atris/wiki/concepts/sync-language.md\`
556
+ 5. \`atris/skills/activation/SKILL.md\`
557
+ 6. \`atris/MAP.md\`
558
+ 7. \`atris/TODO.md\`
559
+ 8. \`atris/wiki/index.md\`
560
+
561
+ First-message rule: follow the sync-language contract before writing to the operator.
562
+ Purpose: optimize for decision-speed; lead with the move, then use descriptions only when they help the operator act.
563
+ Shape: \`<operator>, today is about <move>\` -> \`I picked this because <why now>\` -> \`Ready: <draft/proof/context>\` -> \`Go deeper: <paths>\`.
564
+ Definitions: operator = current person or agent; move = one concrete high-leverage workflow; why now = business reason; ready = prepared action or proof; paths = 2-4 optional deeper views.
565
+ `;
566
+ }
567
+
568
+ function renderLedger(state) {
569
+ const rows = state.stateFiles.map(item => {
570
+ const rel = path.relative(state.root, item.path).replace(/\\/g, '/');
571
+ return `| \`${rel}\` | ${item.exists ? 'yes' : 'no'} | ${item.rows} | ${item.validRows} | ${item.latestTs || ''} |`;
572
+ }).join('\n');
573
+
574
+ return `# Self-Improvement Ledger
575
+
576
+ Generated: ${state.generatedAt}
577
+
578
+ ## Claim
579
+
580
+ Atris improves itself by improving the operating context future agents load: navigation, memory, task choice, proof, and reward signals.
581
+
582
+ This is not model-weight improvement yet. It is workspace-policy and context improvement.
583
+
584
+ ## Current State Inputs
585
+
586
+ | Source | Exists | Rows | Valid JSONL | Latest timestamp |
587
+ |---|---:|---:|---:|---|
588
+ ${rows}
589
+
590
+ ## Run N -> Run N+1 Mechanism
591
+
592
+ 1. Start from \`atris/now.md\`, then observe workspace state from \`.atris/state\`, TODO, MAP, wiki, and logs.
593
+ 2. Compile it into \`atris/brain/STATUS.md\` and this ledger.
594
+ 3. Point future agents at the compiled brain before they act.
595
+ 4. After action, write scorecards, episodes, lessons, or state rows.
596
+ 5. Re-run \`atris brain compile\`; the next agent starts with a better brain.
597
+
598
+ ## Proof To Watch
599
+
600
+ - More valid state rows over time.
601
+ - More scorecards and episodes, not just prose.
602
+ - Fewer repeated stale TODOs.
603
+ - Faster correct next-action selection.
604
+ - Higher verified business-loop completion rate.
605
+
606
+ ## Next Action
607
+
608
+ ${nextMove(state)}
609
+ `;
610
+ }
611
+
612
+ function generatedBootBlock(state) {
613
+ return `${GENERATED_START}
614
+ ## Atris Brain Compile
615
+
616
+ This workspace has a compiled agent brain.
617
+
618
+ On session start, activate it first:
619
+ \`atris brain activate --root ${state.root} --verify\`
620
+
621
+ Load these first:
622
+ - \`atris/now.md\`
623
+ - \`atris/brain/STATUS.md\`
624
+ - \`atris/brain/self_improvement_ledger.md\`
625
+ - \`atris/wiki/concepts/sync-language.md\`
626
+ - \`atris/skills/activation/SKILL.md\`
627
+ - \`atris/MAP.md\`
628
+ - \`atris/TODO.md\`
629
+
630
+ First-message rule: follow the sync-language contract before writing to the operator.
631
+ Purpose: optimize for decision-speed; lead with the move, then use descriptions only when they help the operator act.
632
+ Shape: \`<operator>, today is about <move>\` -> \`I picked this because <why now>\` -> \`Ready: <draft/proof/context>\` -> \`Go deeper: <paths>\`.
633
+ Definitions: operator = current person or agent; move = one concrete high-leverage workflow; why now = business reason; ready = prepared action or proof; paths = 2-4 optional deeper views.
634
+
635
+ Re-run after meaningful work:
636
+ \`atris brain compile --root ${state.root}\`
637
+ ${GENERATED_END}
638
+ `;
639
+ }
640
+
641
+ function upsertGeneratedBlock(filePath, title, block) {
642
+ let current = readText(filePath);
643
+ if (!current) current = `# ${title}\n\n`;
644
+
645
+ const start = current.indexOf(GENERATED_START);
646
+ const end = current.indexOf(GENERATED_END);
647
+ if (start !== -1 && end !== -1 && end > start) {
648
+ const before = current.slice(0, start).trimEnd();
649
+ const after = current.slice(end + GENERATED_END.length).trimStart();
650
+ fs.writeFileSync(filePath, `${before}\n\n${block}${after ? `\n${after}` : ''}`, 'utf8');
651
+ return;
652
+ }
653
+
654
+ fs.writeFileSync(filePath, `${current.trimEnd()}\n\n${block}`, 'utf8');
655
+ }
656
+
657
+ function writeBrain(state) {
658
+ const brainDir = path.join(state.root, 'atris', 'brain');
659
+ fs.mkdirSync(brainDir, { recursive: true });
660
+
661
+ const statusPath = path.join(brainDir, 'STATUS.md');
662
+ const ledgerPath = path.join(brainDir, 'self_improvement_ledger.md');
663
+ const jsonPath = path.join(brainDir, 'state.json');
664
+
665
+ fs.writeFileSync(statusPath, renderStatus(state), 'utf8');
666
+ fs.writeFileSync(ledgerPath, renderLedger(state), 'utf8');
667
+ fs.writeFileSync(jsonPath, JSON.stringify(state, null, 2) + '\n', 'utf8');
668
+
669
+ const bootBlock = generatedBootBlock(state);
670
+ for (const fileName of ['AGENTS.md', 'CLAUDE.md', 'GEMINI.md']) {
671
+ upsertGeneratedBlock(path.join(state.root, fileName), fileName.replace(/\.md$/, ''), bootBlock);
672
+ }
673
+
674
+ const wikiStatusPath = path.join(state.root, 'atris', 'wiki', 'STATUS.md');
675
+ if (fs.existsSync(wikiStatusPath)) {
676
+ upsertGeneratedBlock(wikiStatusPath, 'Atris Wiki Status', `<!-- ATRIS_BRAIN_COMPILE:START -->
677
+ ## Brain Compile
678
+
679
+ - Last compile: ${state.generatedAt}
680
+ - State rows: ${state.totalRows} raw / ${state.validRows} valid JSONL
681
+ - Strongest signal: ${strongestSignal(state)}
682
+ - Next move: ${nextMove(state)}
683
+ - Brain status: \`atris/brain/STATUS.md\`
684
+ - Ledger: \`atris/brain/self_improvement_ledger.md\`
685
+ <!-- ATRIS_BRAIN_COMPILE:END -->
686
+ `);
687
+ }
688
+
689
+ return { statusPath, ledgerPath, jsonPath };
690
+ }
691
+
692
+ function verifyBrain(root) {
693
+ const required = [
694
+ 'atris/brain/STATUS.md',
695
+ 'atris/brain/self_improvement_ledger.md',
696
+ 'atris/brain/state.json',
697
+ 'AGENTS.md',
698
+ ];
699
+ const missing = required.filter(rel => !fs.existsSync(path.join(root, rel)));
700
+ if (missing.length > 0) {
701
+ throw new Error(`brain compile missing: ${missing.join(', ')}`);
702
+ }
703
+ const status = readText(path.join(root, 'atris', 'brain', 'STATUS.md'));
704
+ const ledger = readText(path.join(root, 'atris', 'brain', 'self_improvement_ledger.md'));
705
+ if (!status.includes('## Next Move') || !ledger.includes('## Run N -> Run N+1 Mechanism')) {
706
+ throw new Error('brain compile artifacts are missing required sections');
707
+ }
708
+ }
709
+
710
+ function brainCommand(args = process.argv.slice(3)) {
711
+ const { subcommand, options } = parseArgs(args);
712
+ if (subcommand === 'help' || subcommand === '--help') {
713
+ console.log('Usage: atris brain compile [--root <workspace>] [--verify] [--json]');
714
+ console.log(' atris brain activate [--member <slug>] [--root <workspace>] [--verify] [--json]');
715
+ console.log(' atris brain gallery [--root <workspace>] [--verify] [--json]');
716
+ console.log(' atris brain go|hold [note] [--recommendation <text>] [--root <workspace>] [--verify]');
717
+ console.log(' atris brain approval go|edit|hold [note] [--recommendation <text>] [--root <workspace>] [--verify]');
718
+ console.log(' atris brain feedback --rating approve|edit|reject [--recommendation <text>] [--note <text>] [--root <workspace>] [--verify]');
719
+ console.log(' atris brain yes|edit|no [note] [--root <workspace>] [--verify]');
720
+ return;
721
+ }
722
+ if (subcommand === 'yes') {
723
+ options.rating = 'approve';
724
+ } else if (subcommand === 'no') {
725
+ options.rating = 'reject';
726
+ } else if (subcommand === 'edit') {
727
+ options.rating = 'edit';
728
+ }
729
+
730
+ if (subcommand === 'go' || subcommand === 'hold' || subcommand === 'approval' || subcommand === 'approve') {
731
+ if (subcommand === 'go' || subcommand === 'hold') options.decision = subcommand;
732
+ const approval = recordApproval(options);
733
+ if (options.verify) verifyApproval(options.root, approval.approval_id);
734
+ if (options.json) {
735
+ console.log(JSON.stringify({ ok: true, approval }, null, 2));
736
+ return;
737
+ }
738
+ console.log('');
739
+ console.log('Atris brain approval recorded.');
740
+ console.log(` Approval: ${approval.approval_id}`);
741
+ console.log(` Decision: ${approval.human_decision}`);
742
+ console.log(` Status: ${approval.status}`);
743
+ console.log(' Wrote: .atris/state/approvals.jsonl');
744
+ console.log(' Next: proceed only if decision is go; otherwise edit or hold.');
745
+ if (options.verify) console.log(' Verify: passed');
746
+ console.log('');
747
+ return;
748
+ }
749
+
750
+ if (subcommand === 'feedback' || ['yes', 'no', 'edit'].includes(subcommand)) {
751
+ const result = recordFeedback(options);
752
+ if (options.verify) verifyFeedback(options.root, result.scorecard.decision_id);
753
+ if (options.json) {
754
+ console.log(JSON.stringify({ ok: true, ...result }, null, 2));
755
+ return;
756
+ }
757
+ console.log('');
758
+ console.log('Atris brain feedback recorded.');
759
+ console.log(` Decision: ${result.scorecard.decision_id}`);
760
+ console.log(` Rating: ${result.scorecard.human_rating}`);
761
+ console.log(` Reward: ${result.scorecard.reward}`);
762
+ console.log(' Wrote: .atris/state/scorecards.jsonl');
763
+ console.log(' Wrote: .atris/state/episodes.jsonl');
764
+ console.log(' Next: atris brain compile');
765
+ if (options.verify) console.log(' Verify: passed');
766
+ console.log('');
767
+ return;
768
+ }
769
+ if (subcommand === 'activate') {
770
+ const state = collectState(options.root);
771
+ writeBrain(state);
772
+ if (options.verify) verifyBrain(options.root);
773
+ const card = renderActivationCard(state, options);
774
+ if (options.json) {
775
+ console.log(JSON.stringify({ ok: true, state, card }, null, 2));
776
+ return;
777
+ }
778
+ console.log('');
779
+ console.log(card);
780
+ if (options.verify) console.log('VERIFY: brain artifacts present');
781
+ console.log('');
782
+ return;
783
+ }
784
+ if (subcommand === 'gallery') {
785
+ const state = collectState(options.root);
786
+ writeBrain(state);
787
+ if (options.verify) verifyBrain(options.root);
788
+ const gallery = renderActivationGallery(state);
789
+ if (options.json) {
790
+ console.log(JSON.stringify({ ok: true, members: listMemberSlugs(options.root), gallery }, null, 2));
791
+ return;
792
+ }
793
+ console.log('');
794
+ console.log(gallery);
795
+ if (options.verify) console.log('\nVERIFY: brain artifacts present');
796
+ console.log('');
797
+ return;
798
+ }
799
+ if (subcommand !== 'compile' && subcommand !== 'status') {
800
+ console.error('Usage: atris brain compile [--root <workspace>] [--verify] [--json]');
801
+ console.error(' atris brain activate [--member <slug>] [--root <workspace>] [--verify] [--json]');
802
+ console.error(' atris brain gallery [--root <workspace>] [--verify] [--json]');
803
+ console.error(' atris brain go|hold [note] [--recommendation <text>] [--root <workspace>] [--verify]');
804
+ console.error(' atris brain approval go|edit|hold [note] [--recommendation <text>] [--root <workspace>] [--verify]');
805
+ console.error(' atris brain feedback --rating approve|edit|reject [--recommendation <text>] [--note <text>] [--root <workspace>] [--verify]');
806
+ console.error(' atris brain yes|edit|no [note] [--root <workspace>] [--verify]');
807
+ process.exit(1);
808
+ }
809
+
810
+ const state = collectState(options.root);
811
+ const written = writeBrain(state);
812
+ if (options.verify) verifyBrain(options.root);
813
+
814
+ if (options.json) {
815
+ console.log(JSON.stringify({ ok: true, state, written }, null, 2));
816
+ return;
817
+ }
818
+
819
+ console.log('');
820
+ console.log('Atris brain compiled.');
821
+ console.log(` Workspace: ${state.name}`);
822
+ console.log(` State rows: ${state.totalRows} raw / ${state.validRows} valid`);
823
+ console.log(` Status: ${path.relative(options.root, written.statusPath).replace(/\\/g, '/')}`);
824
+ console.log(` Ledger: ${path.relative(options.root, written.ledgerPath).replace(/\\/g, '/')}`);
825
+ console.log(` Next: ${nextMove(state)}`);
826
+ if (options.verify) console.log(' Verify: passed');
827
+ console.log('');
828
+ }
829
+
830
+ module.exports = {
831
+ brainCommand,
832
+ collectState,
833
+ renderStatus,
834
+ renderLedger,
835
+ renderActivationCard,
836
+ renderActivationGallery,
837
+ recordFeedback,
838
+ recordApproval,
839
+ verifyBrain,
840
+ };