atris 3.15.13 → 3.15.22

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 (93) hide show
  1. package/AGENTS.md +84 -8
  2. package/README.md +5 -1
  3. package/atris/AGENTS.md +46 -1
  4. package/atris/CLAUDE.md +36 -1
  5. package/atris/GEMINI.md +14 -1
  6. package/atris/atris.md +12 -1
  7. package/atris/atrisDev.md +3 -2
  8. package/atris/context/README.md +11 -0
  9. package/atris/features/company-brain-sync/validate.md +5 -5
  10. package/atris/learnings.jsonl +1 -0
  11. package/atris/policies/atris-design.md +2 -0
  12. package/atris/skills/aeo/SKILL.md +2 -2
  13. package/atris/skills/atris/SKILL.md +15 -62
  14. package/atris/skills/design/SKILL.md +2 -0
  15. package/atris/skills/imessage/SKILL.md +19 -2
  16. package/atris/skills/loop/SKILL.md +6 -5
  17. package/atris/skills/magic-inbox/SKILL.md +1 -1
  18. package/atris/team/_template/MEMBER.md +23 -1
  19. package/atris/team/brainstormer/START_HERE.md +6 -0
  20. package/atris/team/executor/MEMBER.md +13 -0
  21. package/atris/team/executor/START_HERE.md +6 -0
  22. package/atris/team/launcher/START_HERE.md +6 -0
  23. package/atris/team/mission-lead/MEMBER.md +39 -0
  24. package/atris/team/mission-lead/MISSION.md +33 -0
  25. package/atris/team/mission-lead/START_HERE.md +6 -0
  26. package/atris/team/navigator/MEMBER.md +11 -0
  27. package/atris/team/navigator/START_HERE.md +6 -0
  28. package/atris/team/opus-overnight/MEMBER.md +39 -0
  29. package/atris/team/opus-overnight/MISSION.md +61 -0
  30. package/atris/team/opus-overnight/START_HERE.md +6 -0
  31. package/atris/team/opus-overnight/STEERING.md +35 -0
  32. package/atris/team/researcher/START_HERE.md +6 -0
  33. package/atris/team/validator/MEMBER.md +26 -6
  34. package/atris/team/validator/START_HERE.md +6 -0
  35. package/atris/wiki/concepts/agent-activation-contract.md +79 -0
  36. package/atris/wiki/concepts/workspace-initialization-contract.md +73 -0
  37. package/atris/wiki/index.md +27 -0
  38. package/atris/wiki/sources/atris-labs-2026-05-10.txt +17 -0
  39. package/atris/wiki/sources/atris-labs-goals-2026-05-10.txt +15 -0
  40. package/atris/wiki/sources/atrisos-generative-ui-product-surface-2026-05-10.txt +10 -0
  41. package/atris/wiki/sources/jack-dorsey-2026-05-10.txt +12 -0
  42. package/atris.md +49 -13
  43. package/bin/atris.js +660 -22
  44. package/commands/activate.js +12 -3
  45. package/commands/aeo.js +1 -1
  46. package/commands/align.js +10 -10
  47. package/commands/analytics.js +9 -4
  48. package/commands/app.js +2 -0
  49. package/commands/apps.js +276 -0
  50. package/commands/auth.js +1 -1
  51. package/commands/autopilot.js +74 -5
  52. package/commands/brain.js +536 -61
  53. package/commands/brainstorm.js +12 -12
  54. package/commands/business-sync.js +142 -24
  55. package/commands/clean.js +9 -6
  56. package/commands/codex-goal.js +311 -0
  57. package/commands/errors.js +11 -1
  58. package/commands/feedback.js +55 -17
  59. package/commands/fork.js +2 -2
  60. package/commands/gm.js +376 -0
  61. package/commands/init.js +80 -3
  62. package/commands/integrations.js +524 -0
  63. package/commands/learn.js +25 -16
  64. package/commands/lesson.js +41 -0
  65. package/commands/lifecycle.js +2 -2
  66. package/commands/member.js +2416 -9
  67. package/commands/mission.js +1776 -0
  68. package/commands/now.js +48 -7
  69. package/commands/play.js +425 -0
  70. package/commands/publish.js +2 -1
  71. package/commands/pull.js +72 -29
  72. package/commands/push.js +199 -17
  73. package/commands/review.js +51 -13
  74. package/commands/skill.js +2 -2
  75. package/commands/soul.js +19 -13
  76. package/commands/status.js +6 -1
  77. package/commands/sync.js +5 -4
  78. package/commands/task.js +1041 -147
  79. package/commands/terminal.js +5 -5
  80. package/commands/verify.js +7 -5
  81. package/commands/visualize.js +7 -0
  82. package/commands/wiki.js +53 -16
  83. package/commands/workflow.js +298 -54
  84. package/commands/workspace-clean.js +1 -1
  85. package/commands/worktree.js +468 -0
  86. package/commands/xp.js +1608 -0
  87. package/lib/manifest.js +34 -4
  88. package/lib/scorecard.js +3 -2
  89. package/lib/task-db.js +408 -27
  90. package/lib/todo-fallback.js +28 -2
  91. package/lib/todo.js +5 -3
  92. package/package.json +23 -2
  93. package/utils/update-check.js +51 -1
package/commands/brain.js CHANGED
@@ -1,9 +1,23 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const crypto = require('crypto');
4
+ const { refreshNowFile } = require('./now');
4
5
 
5
6
  const GENERATED_START = '<!-- ATRIS_BRAIN_COMPILE:START -->';
6
7
  const GENERATED_END = '<!-- ATRIS_BRAIN_COMPILE:END -->';
8
+ const GENERATED_LOAD_ORDER_FILES = [
9
+ 'atris/now.md',
10
+ 'atris/brain/STATUS.md',
11
+ 'atris/brain/self_improvement_ledger.md',
12
+ ];
13
+ const OPTIONAL_LOAD_ORDER_FILES = [
14
+ 'atris/wiki/concepts/agent-activation-contract.md',
15
+ 'atris/skills/atris/SKILL.md',
16
+ 'atris/PERSONA.md',
17
+ 'atris/MAP.md',
18
+ 'atris/TODO.md',
19
+ 'atris/wiki/index.md',
20
+ ];
7
21
 
8
22
  function parseArgs(args) {
9
23
  const options = {
@@ -128,16 +142,56 @@ function readJsonlStats(filePath) {
128
142
  };
129
143
  }
130
144
 
145
+ function readJsonlRows(filePath) {
146
+ const text = readText(filePath);
147
+ const rows = [];
148
+ for (const line of text.split('\n')) {
149
+ if (!line.trim()) continue;
150
+ try {
151
+ rows.push(JSON.parse(line));
152
+ } catch {
153
+ // Bad rows stay visible in stats; callers only use valid rows.
154
+ }
155
+ }
156
+ return rows;
157
+ }
158
+
131
159
  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;
160
+ const text = String(todoText || '');
161
+ const hasRenderedSections = /^##\s+(Backlog|In Progress|Blocked|Completed)\s*$/m.test(text);
162
+ let section = null;
163
+ let unchecked = 0;
164
+ let checked = 0;
165
+ let titled = 0;
166
+ let legacyOpen = 0;
167
+ let renderedOpen = 0;
168
+ let renderedDone = 0;
169
+
170
+ for (const line of text.split(/\r?\n/)) {
171
+ const heading = line.match(/^##\s+(.+?)\s*$/);
172
+ if (heading) {
173
+ section = heading[1];
174
+ continue;
175
+ }
176
+
177
+ const isUnchecked = /^\s*-\s+\[[ ]\]/.test(line);
178
+ const isChecked = /^\s*-\s+\[[xX]\]/.test(line);
179
+ const isTitled = /^\s*-\s+(?:\[[ xX]\]\s+)?\*\*[^*]+:?\*\*/.test(line);
180
+ if (isUnchecked) unchecked += 1;
181
+ if (isChecked) checked += 1;
182
+ if (!hasRenderedSections && (isUnchecked || (isTitled && !isChecked))) legacyOpen += 1;
183
+ if (!isTitled) continue;
184
+
185
+ titled += 1;
186
+ if (hasRenderedSections && ['Backlog', 'In Progress', 'Blocked'].includes(section)) renderedOpen += 1;
187
+ if (hasRenderedSections && section === 'Completed') renderedDone += 1;
188
+ }
189
+
136
190
  return {
137
- open: unchecked + Math.max(0, titled - done),
191
+ open: hasRenderedSections ? renderedOpen : legacyOpen,
138
192
  checked,
139
193
  titled,
140
- done,
194
+ done: hasRenderedSections ? renderedDone : checked + (text.match(/~~|DONE|✅/g) || []).length,
141
195
  };
142
196
  }
143
197
 
@@ -167,6 +221,16 @@ function firstHeading(text, fallback) {
167
221
  return match ? match[1].trim() : fallback;
168
222
  }
169
223
 
224
+ function scorecardTs(row) {
225
+ return String(row?.ts || row?.episode_created_at || '');
226
+ }
227
+
228
+ function isNextMoveScorecard(row) {
229
+ if (!row) return false;
230
+ if (row.type === 'scorecard') return true;
231
+ return row.schema === 'atris.brain.scorecard.v1' && row.source === 'operator_feedback';
232
+ }
233
+
170
234
  function collectState(root) {
171
235
  const atrisDir = path.join(root, 'atris');
172
236
  const stateDir = path.join(root, '.atris', 'state');
@@ -180,6 +244,7 @@ function collectState(root) {
180
244
  const stateFiles = [
181
245
  'events.jsonl',
182
246
  'episodes.jsonl',
247
+ 'task_episodes.jsonl',
183
248
  'scorecards.jsonl',
184
249
  'agent_tasks.jsonl',
185
250
  'agent_mail.jsonl',
@@ -190,6 +255,10 @@ function collectState(root) {
190
255
 
191
256
  const totalRows = stateFiles.reduce((sum, item) => sum + item.rows, 0);
192
257
  const validRows = stateFiles.reduce((sum, item) => sum + item.validRows, 0);
258
+ const latestScorecard = readJsonlRows(path.join(stateDir, 'scorecards.jsonl'))
259
+ .filter(isNextMoveScorecard)
260
+ .sort((a, b) => scorecardTs(a).localeCompare(scorecardTs(b)))
261
+ .pop() || null;
193
262
  const latestStateTs = stateFiles
194
263
  .map(item => item.latestTs)
195
264
  .filter(Boolean)
@@ -212,31 +281,84 @@ function collectState(root) {
212
281
  stateFiles,
213
282
  totalRows,
214
283
  validRows,
284
+ latestScorecard: latestScorecard ? {
285
+ task_title: latestScorecard.task_title || latestScorecard.recommendation || null,
286
+ reward: latestScorecard.reward,
287
+ next_task_suggestion: latestScorecard.next_task_suggestion || null,
288
+ ts: scorecardTs(latestScorecard) || null,
289
+ } : null,
215
290
  latestStateTs,
216
291
  };
217
292
  }
218
293
 
294
+ function prepareBrainState(root) {
295
+ refreshNowFile(root);
296
+ return collectState(root);
297
+ }
298
+
299
+ function countStateRows(state, names) {
300
+ const wanted = new Set(Array.isArray(names) ? names : [names]);
301
+ return state.stateFiles
302
+ .filter(item => wanted.has(path.basename(item.path)))
303
+ .reduce((sum, item) => sum + item.rows, 0);
304
+ }
305
+
219
306
  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;
307
+ const mail = countStateRows(state, 'agent_mail.jsonl');
308
+ const tasks = countStateRows(state, 'agent_tasks.jsonl');
309
+ const scorecards = countStateRows(state, 'scorecards.jsonl');
310
+ const episodes = countStateRows(state, ['episodes.jsonl', 'task_episodes.jsonl']);
224
311
  if (scorecards > 0 && episodes > 0) return `${scorecards} scorecard row(s) and ${episodes} episode row(s) are available for feedback-driven learning.`;
225
312
  if (scorecards > 0) return `${scorecards} scorecard row(s) are available for outcome scoring.`;
313
+ if (episodes > 0) return `${episodes} episode row(s) are available; compile them into scorecards and next-action memory.`;
226
314
  if (mail > 0) return `${mail} agent-mail row(s) are available; compile them into decisions, follow-ups, and CRM memory.`;
227
315
  if (tasks > 0) return `${tasks} agent-task row(s) are available; use them to choose the next action.`;
228
316
  return 'Workspace has structure, but little scored state yet; first improvement is to create scorecards and episodes.';
229
317
  }
230
318
 
319
+ function isActionableScorecardNextMove(value) {
320
+ const text = String(value || '').trim();
321
+ if (text.length < 12) return false;
322
+
323
+ const metaPatterns = [
324
+ /\bcompiled business reward\b/i,
325
+ /\bcompleted business loop\b/i,
326
+ /\bfallback\b/i,
327
+ /\binstead of\b/i,
328
+ /\bbrain\s+(scorecard|compile|feedback|approval|yes|edit|no)\b/i,
329
+ /\bcompile the brain\b/i,
330
+ /\bcompletion audit\b/i,
331
+ /\bnext (move|task|action|operator loop)\b/i,
332
+ /\bonly when there is\b/i,
333
+ /\bprocess work\b/i,
334
+ /\bbefore taking new work\b/i,
335
+ /\brepeating? the completed\b/i,
336
+ /\bscorecard suggestion\b/i,
337
+ ];
338
+ if (metaPatterns.some(pattern => pattern.test(text))) return false;
339
+
340
+ return /\b(add|answer|archive|assign|build|call|choose|claim|clean|clear|close|compile|create|debug|delete|draft|edit|finish|fix|implement|ingest|open|patch|pick|pull|push|record|replace|resolve|retire|review|run|ship|sync|test|triage|update|validate|verify|write)\b/i.test(text);
341
+ }
342
+
343
+ function operatorActivationNextMove(state) {
344
+ return `Run \`atris brain activate --member <name> --root ${state.root} --verify\` to bind the operator and get a concrete work block.`;
345
+ }
346
+
231
347
  function nextMove(state) {
232
- if (state.totalRows > 0 && (state.stateFiles.find(item => item.path.endsWith('scorecards.jsonl'))?.rows || 0) === 0) {
348
+ const scorecards = countStateRows(state, 'scorecards.jsonl');
349
+ const episodes = countStateRows(state, ['episodes.jsonl', 'task_episodes.jsonl']);
350
+ if (state.totalRows > 0 && scorecards === 0) {
351
+ if (episodes > 0) return 'Turn existing episode rows into the first scorecard so the next run has reward, not just traces.';
233
352
  return 'Turn existing state rows into the first scorecard so the next run has a reward signal, not just memory.';
234
353
  }
235
- if ((state.stateFiles.find(item => item.path.endsWith('episodes.jsonl'))?.rows || 0) === 0) {
354
+ if (episodes === 0) {
236
355
  return 'Capture one operator approval, edit, or rejection as an episode so the brain has a learning trace.';
237
356
  }
238
357
  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`.';
358
+ if (state.latestScorecard && isActionableScorecardNextMove(state.latestScorecard.next_task_suggestion)) {
359
+ return state.latestScorecard.next_task_suggestion;
360
+ }
361
+ return operatorActivationNextMove(state);
240
362
  }
241
363
 
242
364
  function rewardForRating(rating) {
@@ -257,9 +379,12 @@ function loadBrainState(root) {
257
379
  return readJson(path.join(root, 'atris', 'brain', 'state.json')) || collectState(root);
258
380
  }
259
381
 
382
+ function normalizeMemberSlug(memberSlug) {
383
+ return String(memberSlug || '').toLowerCase().replace(/[^a-z0-9_-]/g, '');
384
+ }
385
+
260
386
  function readMemberContext(root, memberSlug) {
261
- if (!memberSlug) return null;
262
- const slug = String(memberSlug).toLowerCase().replace(/[^a-z0-9_-]/g, '');
387
+ const slug = normalizeMemberSlug(memberSlug);
263
388
  if (!slug) return null;
264
389
  const memberDir = path.join(root, 'atris', 'team', slug);
265
390
  const memberText = readText(path.join(memberDir, 'MEMBER.md'));
@@ -267,11 +392,60 @@ function readMemberContext(root, memberSlug) {
267
392
  return {
268
393
  slug,
269
394
  name: firstHeading(memberText, slug),
395
+ profile: memberText,
270
396
  startHere: readText(path.join(memberDir, 'START_HERE.md')),
271
397
  goals: readText(path.join(memberDir, 'goals.md')),
272
398
  };
273
399
  }
274
400
 
401
+ function parseContributionCard(text, member) {
402
+ if (!text || !member) return null;
403
+ const firstName = String(member.name || member.slug || '').split(/\s+/)[0].toLowerCase();
404
+ const sections = String(text).split(/\n(?=##\s+)/);
405
+ const memberSections = sections.filter(section => new RegExp(`^##\\s+${firstName}\\b`, 'i').test(section.trim()));
406
+ const section = (
407
+ memberSections.find(candidate => /current_score_signal\s*:/i.test(candidate))
408
+ || memberSections[0]
409
+ || ''
410
+ );
411
+ const fields = {};
412
+ for (const line of section.split('\n')) {
413
+ const match = line.match(/^\s*([a-z_]+):\s*(.+?)\s*$/i);
414
+ if (match) fields[match[1].toLowerCase()] = match[2].trim();
415
+ }
416
+
417
+ const tableRow = text.split('\n').find(line => {
418
+ if (!line.trim().startsWith('|')) return false;
419
+ return line.toLowerCase().includes(`| ${firstName} |`);
420
+ });
421
+ if (tableRow) {
422
+ const cells = tableRow.split('|').map(cell => cell.trim()).filter(Boolean);
423
+ if (cells.length >= 5) {
424
+ fields.operator ||= cells[0];
425
+ fields.current_score_signal ||= cells[2];
426
+ fields.current_signal ||= cells[3];
427
+ fields.next_rep ||= cells[4];
428
+ }
429
+ }
430
+
431
+ const score = fields.current_score_signal || fields.score_signal || fields.score;
432
+ const nextRep = fields.next_rep || fields.proof_needed || fields.next_move;
433
+ if (!score && !nextRep) return null;
434
+
435
+ return {
436
+ score,
437
+ nextRep,
438
+ proofNeeded: fields.proof_needed || '',
439
+ why: fields.why || fields.current_signal || '',
440
+ };
441
+ }
442
+
443
+ function memberScoreContext(root, member) {
444
+ if (!member) return null;
445
+ const contributionScore = readText(path.join(root, 'atris', 'state', 'contribution-score.md'));
446
+ return parseContributionCard(contributionScore, member);
447
+ }
448
+
275
449
  function listMemberSlugs(root) {
276
450
  const teamDir = path.join(root, 'atris', 'team');
277
451
  if (!fs.existsSync(teamDir)) return [];
@@ -303,7 +477,7 @@ function rememberOperator(root, member) {
303
477
  }, null, 2) + '\n', 'utf8');
304
478
  }
305
479
 
306
- function memberNextMove(member) {
480
+ function memberNextMove(member, state = null) {
307
481
  if (!member) return null;
308
482
  const name = member.name || member.slug;
309
483
  const context = `${member.startHere}\n${member.goals}`;
@@ -312,7 +486,41 @@ function memberNextMove(member) {
312
486
  return `${name}: run one customer-moving GTM rep, update the relevant workspace state within 10 minutes, and leave a scorecard.`;
313
487
  }
314
488
  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.`;
489
+ return `${name}: act as Customer 0: choose one allocation, narrative, system, or talent leverage move; leave proof and route the next owner.`;
490
+ }
491
+ if (/opus|overnight/i.test(identity)) {
492
+ return `${name}: run the next zero-spend ` +
493
+ '`rl-exp2` tick, land one artifact, name the 1% delta, and record the mission receipt.';
494
+ }
495
+ if (/mission[- ]lead|mission lead/i.test(identity)) {
496
+ return `${name}: choose or create one bounded mission step, run its verifier, and close it with proof, a scorecard, and the next move.`;
497
+ }
498
+ if (/validator|reviewer/i.test(identity)) {
499
+ if ((state?.todo?.open || 0) === 0 && (state?.todo?.done || 0) === 0) {
500
+ return `${name}: wait for one concrete artifact or ask Navigator to create a reviewable task with verifier, proof target, and residual-risk checklist.`;
501
+ }
502
+ return `${name}: review the highest-risk open or recently completed task, run its verifier, name residual risk, and approve or block with proof.`;
503
+ }
504
+ if (/executor|builder/i.test(identity)) {
505
+ if ((state?.todo?.open || 0) === 0) {
506
+ return `${name}: ask Navigator to create one bounded task with files, verifier, and stop rule before making a patch.`;
507
+ }
508
+ return `${name}: execute the highest-leverage claimed task one scoped step at a time, run the verifier after the patch, and hand off proof for review.`;
509
+ }
510
+ if (/navigator|planner/i.test(identity)) {
511
+ return `${name}: turn one messy or unclaimed intent into a MAP-backed plan with ASCII visualization, exact files, verifier, rollback, and a review-ready task.`;
512
+ }
513
+ if (/launcher|closer/i.test(identity)) {
514
+ if ((state?.todo?.done || 0) === 0) {
515
+ return `${name}: wait for one validated task receipt before closeout, or ask Validator to produce a review decision with proof.`;
516
+ }
517
+ return `${name}: close one validated task into release-ready proof: summarize the shipped change, capture the lesson, update MAP or journal if needed, and name the publish step.`;
518
+ }
519
+ if (/brainstormer|idea shaper|reality shaper/i.test(identity)) {
520
+ return `${name}: shape one raw idea into a concise vision: current reality, 1-2 options, constraints, success criteria, and a navigator-ready next step.`;
521
+ }
522
+ if (/researcher|research/i.test(identity)) {
523
+ return `${name}: answer one explicit research question with primary sources, source-backed findings, unverified gaps, and a short So What handoff.`;
316
524
  }
317
525
  if (/gtm|forward deployed/i.test(identity) || /gtm|forward deployed|customer-moving|customer move/i.test(context)) {
318
526
  return `${name}: run one customer-moving GTM rep, update the relevant workspace state within 10 minutes, and leave a scorecard.`;
@@ -320,6 +528,16 @@ function memberNextMove(member) {
320
528
  if (/ceo|lab/i.test(identity) || /ceo|lab|synthesis loop|decision queue|building|closing|investor/i.test(context)) {
321
529
  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
530
  }
531
+ if (/rl-exp2|zero-spend|zero spend|tick-prefixed|1%/i.test(context)) {
532
+ return `${name}: run the next zero-spend ` +
533
+ '`rl-exp2` tick, land one artifact, name the 1% delta, and record the mission receipt.';
534
+ }
535
+ if (/validation checklist|signoff|reject|falsifiable|residual risk|anti-slop|review/i.test(context)) {
536
+ return `${name}: review the highest-risk open or recently completed task, run its verifier, name residual risk, and approve or block with proof.`;
537
+ }
538
+ if (/bounded mission|mission step|proof target|verifier/i.test(context)) {
539
+ return `${name}: choose or create one bounded mission step, run its verifier, and close it with proof, a scorecard, and the next move.`;
540
+ }
323
541
  return `${name}: use your START_HERE, complete the first concrete work block, and leave a scorecard.`;
324
542
  }
325
543
 
@@ -358,10 +576,56 @@ function modeNextMove(member, mode) {
358
576
  return null;
359
577
  }
360
578
 
579
+ function memberProfileIssues(member) {
580
+ if (!member) return [];
581
+ const profile = String(member.profile || '');
582
+ const issues = [];
583
+ if (/\(Define how this member communicates/i.test(profile)) issues.push('persona is still template text');
584
+ if (/^\s*1\.\s+Step one\s*$/im.test(profile)) issues.push('workflow is still template text');
585
+ if (/^\s*1\.\s+Rule one\s*$/im.test(profile)) issues.push('rules are still template text');
586
+ return issues;
587
+ }
588
+
589
+ function renderMissingMemberCard(state, memberSlug) {
590
+ const slug = normalizeMemberSlug(memberSlug) || '<name>';
591
+ const members = listMemberSlugs(state.root);
592
+ const available = members.length > 0 ? members.join(', ') : 'none';
593
+ return `CONTEXT: ${state.name} Brain
594
+ OPERATOR: ${slug} (missing)
595
+ NEXT MOVE: Create atris/team/${slug}/MEMBER.md or rerun with an existing member.
596
+ WHY: Activation can only route by operator after the member profile exists locally.
597
+ PROOF: Re-run atris brain activate --member ${slug} --root ${state.root} --verify and get an operator-specific work block.
598
+ AVAILABLE MEMBERS: ${available}
599
+ FEEDBACK: yes / edit / no`;
600
+ }
601
+
602
+ function renderPlaceholderMemberCard(state, member, issues) {
603
+ const slug = member.slug || '<name>';
604
+ return `CONTEXT: ${state.name} Brain
605
+ OPERATOR: ${member.name || slug} (not ready)
606
+ NEXT MOVE: Replace placeholder sections in atris/team/${slug}/MEMBER.md with the member's real workflow, rules, and proof standard.
607
+ WHY: Activation should not turn template text into fake operator work.
608
+ PROOF: Re-run atris brain activate --member ${slug} --root ${state.root} --verify and get an operator-specific work block.
609
+ PROFILE ISSUES: ${issues.join('; ')}
610
+ FEEDBACK: yes / edit / no`;
611
+ }
612
+
613
+ function renderMissingStartHereCard(state, member) {
614
+ const slug = member.slug || '<name>';
615
+ return `CONTEXT: ${state.name} Brain
616
+ OPERATOR: ${member.name || slug} (not ready)
617
+ NEXT MOVE: Create atris/team/${slug}/START_HERE.md with the member's first concrete work block, verifier, and proof target.
618
+ WHY: Activation should not tell an operator to use START_HERE until that local contract exists.
619
+ PROOF: Re-run atris brain activate --member ${slug} --root ${state.root} --verify and get an operator-specific work block.
620
+ FEEDBACK: yes / edit / no`;
621
+ }
622
+
361
623
  function renderActivationCard(state, options = {}) {
362
624
  const requestedMember = options.member || readRememberedOperator(state.root);
363
625
  const member = readMemberContext(state.root, requestedMember);
364
- const move = nextMove(state);
626
+ if (!member && requestedMember) {
627
+ return renderMissingMemberCard(state, requestedMember);
628
+ }
365
629
  if (!member && !options.member) {
366
630
  return `CONTEXT: ${state.name} Brain
367
631
  OPERATOR: unknown
@@ -370,14 +634,25 @@ WHY: The brain should route work by operator, customer, and proof path before it
370
634
  PROOF: Activation re-runs with a known operator and produces a specific work block.
371
635
  FEEDBACK: yes / edit / no`;
372
636
  }
373
- rememberOperator(state.root, member);
637
+ const profileIssues = memberProfileIssues(member);
638
+ if (profileIssues.length > 0) {
639
+ return renderPlaceholderMemberCard(state, member, profileIssues);
640
+ }
641
+ if (!String(member.startHere || '').trim()) {
642
+ return renderMissingStartHereCard(state, member);
643
+ }
644
+ if (options.remember !== false) rememberOperator(state.root, member);
374
645
  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`;
646
+ const next = modeMove?.move || memberNextMove(member, state) || nextMove(state);
647
+ const scoreContext = memberScoreContext(state.root, member);
648
+ const proof = modeMove?.proof || scoreContext?.proofNeeded || scoreContext?.nextRep || `After the move, record feedback and recompile: atris brain yes|edit|no "note" --root ${state.root} --verify && atris brain compile --root ${state.root} --verify`;
649
+ const scoreLines = scoreContext
650
+ ? `${scoreContext.score ? `\nSCORE: ${scoreContext.score}` : ''}${scoreContext.nextRep ? `\nNEXT REP: ${scoreContext.nextRep}` : ''}`
651
+ : '';
377
652
  return `CONTEXT: ${state.name} Brain${member ? `\nOPERATOR: ${member.name}` : ''}${modeMove ? `\nMODE: ${modeMove.label}` : ''}
378
653
  NEXT MOVE: ${next}
379
654
  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}
655
+ PROOF: ${proof}${scoreLines}
381
656
  FEEDBACK: yes / edit / no`;
382
657
  }
383
658
 
@@ -389,7 +664,38 @@ TEAM: no members found
389
664
  NEXT MOVE: Add a member under atris/team/<name>/MEMBER.md, then run activation again.`;
390
665
  }
391
666
 
392
- return slugs.map(slug => renderActivationCard(state, { member: slug })).join('\n\n---\n\n');
667
+ return slugs.map(slug => renderActivationCard(state, { member: slug, remember: false })).join('\n\n---\n\n');
668
+ }
669
+
670
+ function galleryReadinessIssues(gallery) {
671
+ return String(gallery || '')
672
+ .split(/\n---\n/)
673
+ .flatMap(card => activationCardReadinessIssues(card));
674
+ }
675
+
676
+ function activationCardReadinessIssues(card) {
677
+ const operator = (String(card || '').match(/^OPERATOR:\s*(.+)$/m) || [null, 'unknown member'])[1];
678
+ if (/\((missing|not ready)\)/.test(operator)) return [operator];
679
+ return [];
680
+ }
681
+
682
+ function verifyActivationGallery(gallery) {
683
+ const issues = galleryReadinessIssues(gallery);
684
+ if (issues.length > 0) {
685
+ throw new Error(`brain gallery not-ready member activation cards: ${issues.join(', ')}`);
686
+ }
687
+ }
688
+
689
+ function verifyActivationCard(card) {
690
+ const issues = activationCardReadinessIssues(card);
691
+ if (issues.length > 0) {
692
+ throw new Error(`brain activate non-executable member activation card: ${issues.join(', ')}`);
693
+ }
694
+ }
695
+
696
+ function printVerifyFailure(error) {
697
+ console.error(error && error.message ? error.message : String(error));
698
+ process.exit(1);
393
699
  }
394
700
 
395
701
  function recordFeedback(options) {
@@ -499,6 +805,90 @@ function recordApproval(options) {
499
805
  return approval;
500
806
  }
501
807
 
808
+ function taskEpisodeScorecard(root, episode, workspace, ts = new Date().toISOString()) {
809
+ const episodeId = episode.episode_id || shortHash(JSON.stringify(episode));
810
+ const reward = Number(episode.reward && episode.reward.value);
811
+ return {
812
+ ts,
813
+ schema: 'atris.brain.task_scorecard.v1',
814
+ type: 'scorecard',
815
+ scorecard_id: `task-scorecard-${episodeId}`,
816
+ source: 'task_review_episode',
817
+ source_episode_id: episodeId,
818
+ workspace: workspace.slug || path.basename(root),
819
+ business_id: workspace.business_id || null,
820
+ workspace_id: workspace.workspace_id || null,
821
+ task_id: episode.task_id || null,
822
+ task_title: episode.state && episode.state.title || null,
823
+ task_tag: episode.state && episode.state.tag || null,
824
+ actor: episode.action && episode.action.actor || null,
825
+ reward: Number.isFinite(reward) ? reward : 0,
826
+ reward_source: episode.reward && episode.reward.source || 'task_review',
827
+ lesson: episode.lesson || '',
828
+ proof: episode.proof || '',
829
+ next_task_suggestion: episode.next_task_suggestion || null,
830
+ episode_created_at: episode.created_at || episode.ts || null,
831
+ };
832
+ }
833
+
834
+ function latestTaskEpisodes(taskEpisodes) {
835
+ const byTask = new Map();
836
+ for (const episode of taskEpisodes) {
837
+ const episodeId = episode.episode_id || shortHash(JSON.stringify(episode));
838
+ const key = episode.task_id || episodeId;
839
+ byTask.set(key, { ...episode, episode_id: episodeId });
840
+ }
841
+ return Array.from(byTask.values());
842
+ }
843
+
844
+ function recordTaskEpisodeScorecards(options) {
845
+ const root = options.root;
846
+ const stateDir = path.join(root, '.atris', 'state');
847
+ const taskEpisodesPath = path.join(stateDir, 'task_episodes.jsonl');
848
+ const scorecardsPath = path.join(stateDir, 'scorecards.jsonl');
849
+ const workspace = readJson(path.join(root, '.atris', 'business.json')) || {};
850
+ const taskEpisodes = readJsonlRows(taskEpisodesPath)
851
+ .filter(row => row && row.schema === 'atris.task_episode.v1');
852
+ const scoreableEpisodes = latestTaskEpisodes(taskEpisodes);
853
+ const existing = readJsonlRows(scorecardsPath);
854
+ const seenEpisodeIds = new Set(existing
855
+ .map(row => row.source_episode_id)
856
+ .filter(Boolean));
857
+
858
+ const written = [];
859
+ for (const episode of scoreableEpisodes) {
860
+ const episodeId = episode.episode_id;
861
+ if (seenEpisodeIds.has(episodeId)) continue;
862
+ const scorecard = taskEpisodeScorecard(root, episode, workspace);
863
+ appendJsonl(scorecardsPath, scorecard);
864
+ seenEpisodeIds.add(episodeId);
865
+ written.push(scorecard);
866
+ }
867
+
868
+ return {
869
+ taskEpisodes: taskEpisodes.length,
870
+ written: written.length,
871
+ scorecards: written,
872
+ };
873
+ }
874
+
875
+ function verifyTaskEpisodeScorecards(root) {
876
+ const stateDir = path.join(root, '.atris', 'state');
877
+ const taskEpisodes = readJsonlRows(path.join(stateDir, 'task_episodes.jsonl'))
878
+ .filter(row => row && row.schema === 'atris.task_episode.v1');
879
+ const scoreableEpisodes = latestTaskEpisodes(taskEpisodes);
880
+ const scorecards = readJsonlRows(path.join(stateDir, 'scorecards.jsonl'));
881
+ const scorecardEpisodeIds = new Set(scorecards
882
+ .map(row => row.source_episode_id)
883
+ .filter(Boolean));
884
+ const missing = scoreableEpisodes
885
+ .map(row => row.episode_id)
886
+ .filter(id => !scorecardEpisodeIds.has(id));
887
+ if (missing.length > 0) {
888
+ throw new Error(`task episode scorecards missing: ${missing.join(', ')}`);
889
+ }
890
+ }
891
+
502
892
  function verifyFeedback(root, decisionId) {
503
893
  const scorecards = readText(path.join(root, '.atris', 'state', 'scorecards.jsonl'));
504
894
  const episodes = readText(path.join(root, '.atris', 'state', 'episodes.jsonl'));
@@ -514,6 +904,25 @@ function verifyApproval(root, approvalId) {
514
904
  }
515
905
  }
516
906
 
907
+ function brainLoadOrderFiles(state) {
908
+ const root = state.root;
909
+ const existing = OPTIONAL_LOAD_ORDER_FILES
910
+ .filter(rel => fs.existsSync(path.join(root, rel)));
911
+ return [...GENERATED_LOAD_ORDER_FILES, ...existing];
912
+ }
913
+
914
+ function renderNumberedLoadOrder(state) {
915
+ return brainLoadOrderFiles(state)
916
+ .map((rel, index) => `${index + 1}. \`${rel}\``)
917
+ .join('\n');
918
+ }
919
+
920
+ function renderBulletedLoadOrder(state) {
921
+ return brainLoadOrderFiles(state)
922
+ .map(rel => `- \`${rel}\``)
923
+ .join('\n');
924
+ }
925
+
517
926
  function renderStatus(state) {
518
927
  return `# Atris Brain Status
519
928
 
@@ -549,16 +958,9 @@ ${nextMove(state)}
549
958
 
550
959
  ## Load Order For Future Agents
551
960
 
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\`
961
+ ${renderNumberedLoadOrder(state)}
560
962
 
561
- First-message rule: follow the sync-language contract before writing to the operator.
963
+ First-message rule: lead with the move before writing to the operator.
562
964
  Purpose: optimize for decision-speed; lead with the move, then use descriptions only when they help the operator act.
563
965
  Shape: \`<operator>, today is about <move>\` -> \`I picked this because <why now>\` -> \`Ready: <draft/proof/context>\` -> \`Go deeper: <paths>\`.
564
966
  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.
@@ -619,15 +1021,9 @@ On session start, activate it first:
619
1021
  \`atris brain activate --root ${state.root} --verify\`
620
1022
 
621
1023
  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.
1024
+ ${renderBulletedLoadOrder(state)}
1025
+
1026
+ First-message rule: lead with the move before writing to the operator.
631
1027
  Purpose: optimize for decision-speed; lead with the move, then use descriptions only when they help the operator act.
632
1028
  Shape: \`<operator>, today is about <move>\` -> \`I picked this because <why now>\` -> \`Ready: <draft/proof/context>\` -> \`Go deeper: <paths>\`.
633
1029
  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.
@@ -707,16 +1103,27 @@ function verifyBrain(root) {
707
1103
  }
708
1104
  }
709
1105
 
1106
+ function brainUsageLines() {
1107
+ return [
1108
+ 'Usage: atris brain compile [--root <workspace>] [--verify] [--json]',
1109
+ ' atris brain activate [--member <slug>] [--root <workspace>] [--verify] [--json]',
1110
+ ' atris brain gallery [--root <workspace>] [--verify] [--json]',
1111
+ ' atris brain go|hold [note] [--recommendation <text>] [--root <workspace>] [--verify]',
1112
+ ' atris brain approval go|edit|hold [note] [--recommendation <text>] [--root <workspace>] [--verify]',
1113
+ ' atris brain scorecard [--root <workspace>] [--verify] [--json]',
1114
+ ' atris brain feedback --rating approve|edit|reject [--recommendation <text>] [--note <text>] [--root <workspace>] [--verify]',
1115
+ ' atris brain yes|edit|no [note] [--root <workspace>] [--verify]',
1116
+ ];
1117
+ }
1118
+
1119
+ function printBrainUsage(stream = console.log) {
1120
+ for (const line of brainUsageLines()) stream(line);
1121
+ }
1122
+
710
1123
  function brainCommand(args = process.argv.slice(3)) {
711
1124
  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]');
1125
+ if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h' || args.includes('--help') || args.includes('-h')) {
1126
+ printBrainUsage();
720
1127
  return;
721
1128
  }
722
1129
  if (subcommand === 'yes') {
@@ -766,48 +1173,112 @@ function brainCommand(args = process.argv.slice(3)) {
766
1173
  console.log('');
767
1174
  return;
768
1175
  }
1176
+
1177
+ if (subcommand === 'scorecard' || subcommand === 'scorecards') {
1178
+ const result = recordTaskEpisodeScorecards(options);
1179
+ if (options.verify) verifyTaskEpisodeScorecards(options.root);
1180
+ if (options.json) {
1181
+ console.log(JSON.stringify({ ok: true, ...result }, null, 2));
1182
+ return;
1183
+ }
1184
+ console.log('');
1185
+ console.log('Atris brain scorecards recorded.');
1186
+ console.log(` Task episodes: ${result.taskEpisodes}`);
1187
+ console.log(` New scorecards: ${result.written}`);
1188
+ console.log(' Wrote: .atris/state/scorecards.jsonl');
1189
+ console.log(' Next: atris brain compile');
1190
+ if (options.verify) console.log(' Verify: passed');
1191
+ console.log('');
1192
+ return;
1193
+ }
1194
+
769
1195
  if (subcommand === 'activate') {
770
- const state = collectState(options.root);
1196
+ const state = prepareBrainState(options.root);
771
1197
  writeBrain(state);
772
1198
  if (options.verify) verifyBrain(options.root);
773
1199
  const card = renderActivationCard(state, options);
774
1200
  if (options.json) {
1201
+ if (options.verify) {
1202
+ try {
1203
+ verifyActivationCard(card);
1204
+ } catch (error) {
1205
+ console.log(JSON.stringify({
1206
+ ok: false,
1207
+ error: error && error.message ? error.message : String(error),
1208
+ state,
1209
+ card,
1210
+ }, null, 2));
1211
+ process.exit(1);
1212
+ }
1213
+ }
775
1214
  console.log(JSON.stringify({ ok: true, state, card }, null, 2));
776
1215
  return;
777
1216
  }
778
1217
  console.log('');
779
1218
  console.log(card);
780
- if (options.verify) console.log('VERIFY: brain artifacts present');
1219
+ if (options.verify) {
1220
+ try {
1221
+ verifyActivationCard(card);
1222
+ } catch (error) {
1223
+ printVerifyFailure(error);
1224
+ return;
1225
+ }
1226
+ const operator = (card.match(/^OPERATOR:\s*(.+)$/m) || [null, 'unknown'])[1];
1227
+ if (operator === 'unknown') {
1228
+ console.log('VERIFY: brain artifacts present');
1229
+ } else {
1230
+ console.log('VERIFY: brain artifacts and member activation executable');
1231
+ }
1232
+ }
781
1233
  console.log('');
782
1234
  return;
783
1235
  }
784
1236
  if (subcommand === 'gallery') {
785
- const state = collectState(options.root);
1237
+ const state = prepareBrainState(options.root);
786
1238
  writeBrain(state);
787
1239
  if (options.verify) verifyBrain(options.root);
788
1240
  const gallery = renderActivationGallery(state);
1241
+ if (options.verify) {
1242
+ try {
1243
+ verifyActivationGallery(gallery);
1244
+ } catch (error) {
1245
+ if (options.json) {
1246
+ console.log(JSON.stringify({
1247
+ ok: false,
1248
+ error: error && error.message ? error.message : String(error),
1249
+ members: listMemberSlugs(options.root),
1250
+ gallery,
1251
+ }, null, 2));
1252
+ process.exit(1);
1253
+ }
1254
+ printVerifyFailure(error);
1255
+ return;
1256
+ }
1257
+ }
789
1258
  if (options.json) {
790
1259
  console.log(JSON.stringify({ ok: true, members: listMemberSlugs(options.root), gallery }, null, 2));
791
1260
  return;
792
1261
  }
793
1262
  console.log('');
794
1263
  console.log(gallery);
795
- if (options.verify) console.log('\nVERIFY: brain artifacts present');
1264
+ if (options.verify) console.log('\nVERIFY: brain artifacts and member readiness present');
796
1265
  console.log('');
797
1266
  return;
798
1267
  }
799
1268
  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]');
1269
+ if (options.json) {
1270
+ console.log(JSON.stringify({
1271
+ ok: false,
1272
+ error: `unknown brain subcommand: ${subcommand}`,
1273
+ usage: brainUsageLines(),
1274
+ }, null, 2));
1275
+ process.exit(1);
1276
+ }
1277
+ printBrainUsage(console.error);
807
1278
  process.exit(1);
808
1279
  }
809
1280
 
810
- const state = collectState(options.root);
1281
+ const state = prepareBrainState(options.root);
811
1282
  const written = writeBrain(state);
812
1283
  if (options.verify) verifyBrain(options.root);
813
1284
 
@@ -830,11 +1301,15 @@ function brainCommand(args = process.argv.slice(3)) {
830
1301
  module.exports = {
831
1302
  brainCommand,
832
1303
  collectState,
1304
+ prepareBrainState,
833
1305
  renderStatus,
834
1306
  renderLedger,
835
1307
  renderActivationCard,
836
1308
  renderActivationGallery,
1309
+ recordTaskEpisodeScorecards,
837
1310
  recordFeedback,
838
1311
  recordApproval,
1312
+ verifyActivationCard,
1313
+ verifyActivationGallery,
839
1314
  verifyBrain,
840
1315
  };