atris 3.82.26 → 3.83.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/AGENTS.md +10 -12
  2. package/README.md +77 -0
  3. package/bin/atris.js +95 -44
  4. package/commands/activate.js +39 -33
  5. package/commands/agent-home.js +403 -0
  6. package/commands/agent-signin.js +214 -0
  7. package/commands/agent-spawn.js +1 -0
  8. package/commands/autoland.js +173 -71
  9. package/commands/brain.js +211 -410
  10. package/commands/doctor.js +38 -26
  11. package/commands/init.js +4 -4
  12. package/commands/join.js +159 -0
  13. package/commands/memory.js +2 -2
  14. package/commands/morning.js +132 -0
  15. package/commands/night.js +178 -0
  16. package/commands/recap.js +4 -4
  17. package/commands/task.js +31 -2
  18. package/lib/agent-home.js +289 -0
  19. package/lib/agent-page.js +1257 -0
  20. package/lib/cleanup-summary.js +4 -2
  21. package/lib/first-minute.js +41 -20
  22. package/lib/gm-phone-page.js +8 -2
  23. package/lib/known-commands.js +1 -1
  24. package/lib/member-export.js +31 -0
  25. package/lib/memory-folder.js +122 -24
  26. package/lib/memory-live.js +2 -2
  27. package/lib/night-learned.js +171 -0
  28. package/lib/night-shift.js +123 -0
  29. package/lib/task-core.js +2 -1
  30. package/lib/task-db.js +32 -0
  31. package/package.json +1 -1
  32. package/templates/members/designer-1/MEMBER.md +34 -0
  33. package/templates/members/designer-1/MISSION.md +11 -0
  34. package/templates/members/designer-1/SOUL.md +11 -0
  35. package/templates/members/designer-1/context/why.md +5 -0
  36. package/templates/members/designer-1/goals.json +5 -0
  37. package/templates/members/designer-1/goals.md +3 -0
  38. package/templates/members/designer-1/skills/designer-1/SKILL.md +179 -0
  39. package/templates/members/designer-1/skills/designer-1/parts.md +55 -0
package/commands/brain.js CHANGED
@@ -3,6 +3,7 @@ const path = require('path');
3
3
  const crypto = require('crypto');
4
4
  const { spawnSync } = require('child_process');
5
5
  const { refreshNowFile } = require('./now');
6
+ const { buildActivationSummary } = require('./activate');
6
7
  const { ownerIdentity } = require('../utils/owner-identity');
7
8
  const escapeRegExp = require('../lib/escape-regexp');
8
9
  const { hasRenderedSections, isOpenSection, isDoneSection } = require('../lib/todo-sections');
@@ -27,6 +28,9 @@ const OPTIONAL_LOAD_ORDER_FILES = [
27
28
  'atris/wiki/concepts/agent-activation-contract.md',
28
29
  'atris/skills/atris/SKILL.md',
29
30
  'atris/PERSONA.md',
31
+ // A few lines an agent wrote about its own nights (`atris morning` saves
32
+ // them). Tiny on purpose: at most 12 short lines.
33
+ 'atris/learned.md',
30
34
  'atris/MAP.md',
31
35
  'atris/TODO.md',
32
36
  'atris/wiki/index.md',
@@ -118,7 +122,7 @@ function parseArgs(args) {
118
122
  }
119
123
  }
120
124
 
121
- const subcommand = positional[0] || 'compile';
125
+ const subcommand = positional[0] || 'activate';
122
126
  if (!options.note && ['yes', 'no', 'edit', 'go', 'hold'].includes(subcommand) && positional.length > 1) {
123
127
  options.note = positional.slice(1).join(' ');
124
128
  }
@@ -526,7 +530,7 @@ function collectState(root) {
526
530
  return {
527
531
  generatedAt: new Date().toISOString(),
528
532
  root,
529
- name: business.name || business.slug || firstHeading(status || mapText, path.basename(root)),
533
+ name: business.name || business.slug || path.basename(root),
530
534
  slug: business.slug || path.basename(root),
531
535
  business,
532
536
  todo: countWorkItems(root, todoText),
@@ -593,22 +597,19 @@ function buildLoopHealth(stateFiles) {
593
597
  }
594
598
 
595
599
  function strongestSignal(state) {
600
+ const scores = countStateRows(state, 'scorecards.jsonl');
601
+ const finished = countStateRows(state, ['episodes.jsonl', 'task_episodes.jsonl']);
602
+ const active = (state.loopHealth || buildLoopHealth(state.stateFiles || [])).filter(channel => channel.active);
603
+ const activity = active.length ? ` recent activity comes from ${active.length} sources: ${active.map(channel => channel.label).join(', ')}.` : '';
604
+ if (scores && finished) return `${scores} scores and ${finished} finished work records are available to guide the next step.${activity}`;
605
+ if (scores) return `${scores} scores from past work are available to guide the next step.${activity}`;
606
+ if (finished) return `${finished} finished work records are available to score.${activity}`;
607
+ if (active.length) return `recent activity comes from ${active.length} sources: ${active.map(channel => channel.label).join(', ')}.`;
596
608
  const mail = countStateRows(state, 'agent_mail.jsonl');
609
+ if (mail) return `${mail} team messages are available to guide decisions and follow-ups.`;
597
610
  const tasks = countStateRows(state, 'agent_tasks.jsonl');
598
- const scorecards = countStateRows(state, 'scorecards.jsonl');
599
- const episodes = countStateRows(state, ['episodes.jsonl', 'task_episodes.jsonl']);
600
- const activeLoops = (state.loopHealth || buildLoopHealth(state.stateFiles || []))
601
- .filter(channel => channel.active);
602
- const loopSuffix = activeLoops.length > 0
603
- ? ` Loop health sees ${activeLoops.length} active channel(s): ${activeLoops.map(channel => channel.label).join(', ')}.`
604
- : '';
605
- if (scorecards > 0 && episodes > 0) return `${scorecards} scorecard row(s) and ${episodes} episode row(s) are available for feedback-driven learning.${loopSuffix}`;
606
- if (scorecards > 0) return `${scorecards} scorecard row(s) are available for outcome scoring.${loopSuffix}`;
607
- if (episodes > 0) return `${episodes} episode row(s) are available; compile them into scorecards and next-action memory.${loopSuffix}`;
608
- if (activeLoops.length > 0) return `Loop health sees ${activeLoops.length} active channel(s): ${activeLoops.map(channel => channel.label).join(', ')}.`;
609
- if (mail > 0) return `${mail} agent-mail row(s) are available; compile them into decisions, follow-ups, and CRM memory.`;
610
- if (tasks > 0) return `${tasks} agent-task row(s) are available; use them to choose the next action.`;
611
- return 'Workspace has structure, but little scored state yet; first improvement is to create scorecards and episodes.';
611
+ if (tasks) return `${tasks} team tasks are available to guide the next step.`;
612
+ return 'there is little scored work yet. record feedback after the next task.';
612
613
  }
613
614
 
614
615
  function isActionableScorecardNextMove(value) {
@@ -636,23 +637,19 @@ function isActionableScorecardNextMove(value) {
636
637
  }
637
638
 
638
639
  function operatorActivationNextMove(state) {
639
- return `Run \`atris brain activate --member <name> --root ${state.root} --verify\` to bind the operator and get a concrete work block.`;
640
+ return `pick a team member to get a specific next step: atris brain activate --member <name> --root ${state.root}`;
640
641
  }
641
642
 
642
643
  function nextMove(state) {
643
- const scorecards = countStateRows(state, 'scorecards.jsonl');
644
- const episodes = countStateRows(state, ['episodes.jsonl', 'task_episodes.jsonl']);
645
- if (state.totalRows > 0 && scorecards === 0) {
646
- if (episodes > 0) return 'Turn existing episode rows into the first scorecard so the next run has reward, not just traces.';
647
- return 'Turn existing state rows into the first scorecard so the next run has a reward signal, not just memory.';
648
- }
649
- if (episodes === 0) {
650
- return 'Capture one operator approval, edit, or rejection as an episode so the brain has a learning trace.';
651
- }
652
- if (state.todo.open > 0) return 'Pick the highest-leverage open TODO item and leave a scorecard when done.';
653
- if (state.latestScorecard && isActionableScorecardNextMove(state.latestScorecard.next_task_suggestion)) {
654
- return state.latestScorecard.next_task_suggestion;
655
- }
644
+ const scores = countStateRows(state, 'scorecards.jsonl');
645
+ const finished = countStateRows(state, ['episodes.jsonl', 'task_episodes.jsonl']);
646
+ if (state.totalRows > 0 && scores === 0) {
647
+ if (finished > 0) return 'score finished work to help choose the next step: atris brain scorecard';
648
+ return 'record feedback on the work so the next suggestion can improve.';
649
+ }
650
+ if (finished === 0) return 'record whether a suggestion was useful: atris brain yes, atris brain edit or atris brain no';
651
+ if (state.todo.open > 0) return 'pick the most useful open task and record how it went when done.';
652
+ if (state.latestScorecard && isActionableScorecardNextMove(state.latestScorecard.next_task_suggestion)) return state.latestScorecard.next_task_suggestion;
656
653
  return operatorActivationNextMove(state);
657
654
  }
658
655
 
@@ -754,7 +751,7 @@ function operatorStatePath(root) {
754
751
 
755
752
  function readRememberedOperator(root) {
756
753
  const state = readJson(operatorStatePath(root));
757
- return state?.member || null;
754
+ return normalizeMemberSlug(state?.member) || null;
758
755
  }
759
756
 
760
757
  function rememberOperator(root, member) {
@@ -768,129 +765,63 @@ function rememberOperator(root, member) {
768
765
  }, null, 2) + '\n', 'utf8');
769
766
  }
770
767
 
768
+ function shellQuote(value) {
769
+ return `'${String(value).replace(/'/g, "'\\''")}'`;
770
+ }
771
+
772
+ function rootOption(root) {
773
+ const canonical = value => {
774
+ try { return fs.realpathSync(value); } catch { return path.resolve(value); }
775
+ };
776
+ return canonical(root) === canonical(process.cwd()) ? '' : ` --root ${shellQuote(root)}`;
777
+ }
778
+
779
+ // The command that saves fresh notes, aimed at the folder being talked about
780
+ // even when the person is standing somewhere else.
781
+ function compileCommand(root) {
782
+ return `atris brain compile ${rootOption(root).trim() || '--root .'} --yes`;
783
+ }
784
+
785
+ function taskCommand(root, command) {
786
+ return rootOption(root) ? `(cd ${shellQuote(root)} && ${command})` : command;
787
+ }
788
+
771
789
  function memberNextMove(member, state = null) {
772
790
  if (!member) return null;
773
- const name = member.name || member.slug;
774
- const context = `${member.startHere}\n${member.goals}`;
775
791
  const identity = `${member.slug}\n${member.name}`;
776
- const agentNeededReview = state?.taskProjection?.agentNeededReviewTasks?.[0] || null;
777
- const agentNeededReviewMove = agentNeededReview
778
- ? `${name}: run the agent-safe review lane for ${agentNeededReview.ref}: ` +
779
- `\`atris task review-chat ${agentNeededReview.ref} --as codex-review\`, then run the verifier named in the review packet and certify or revise without accepting XP.`
780
- : null;
781
- const certifiedReview = state?.taskProjection?.certifiedReviewTasks?.[0] || null;
782
- const certifiedReviewRefs = (state?.taskProjection?.certifiedReviewTasks || [])
783
- .map(task => task.ref)
784
- .filter(Boolean);
785
- const certifiedReviewMove = certifiedReview
786
- ? `${name}: hand off certified review ${certifiedReview.ref} to the operator: run ` +
787
- `\`atris task accept ${certifiedReview.ref}\` if approved or ` +
788
- `\`atris task revise ${certifiedReview.ref} --note "<what must change>"\` if not; do not create new work until this checkpoint is clear.`
789
- : null;
790
- const codexEndgameMove = certifiedReviewRefs.length > 0 && member.slug === 'codex-executor'
791
- ? `${name}: certified reviews ${certifiedReviewRefs.slice(0, 3).join(', ')} are human-only; do not accept XP. ` +
792
- `Create the next bounded Codex task from Endgame ${state?.endgame?.slug || 'current-horizon'}: ` +
793
- `${state?.endgame?.horizon || 'the highest-leverage system gap'}; include files, verifier, and stop rule before editing.`
794
- : null;
795
- if (member.slug === 'justin' || /justin/i.test(member.name || '')) {
796
- return `${name}: run one customer-moving GTM rep, update the relevant workspace state within 10 minutes, and leave a scorecard.`;
797
- }
798
- const ownerNames = ownerIdentity().names;
799
- if (ownerNames.includes(String(member.slug || '').toLowerCase()) || ownerNames.includes(String(member.name || '').toLowerCase())) {
800
- return `${name}: act as Customer 0: choose one allocation, narrative, system, or talent leverage move; leave proof and route the next owner.`;
801
- }
802
- if (/opus|overnight/i.test(identity)) {
803
- return `${name}: run the next zero-spend ` +
804
- '`rl-exp2` tick, land one artifact, name the 1% delta, and record the mission receipt.';
805
- }
806
- if (/mission[- ]lead|mission lead/i.test(identity)) {
807
- return `${name}: choose or create one bounded mission step, run its verifier, and close it with proof, a scorecard, and the next move.`;
808
- }
809
- if (/validator|reviewer/i.test(identity)) {
810
- if (agentNeededReviewMove) return agentNeededReviewMove;
811
- if (certifiedReviewMove) return certifiedReviewMove;
812
- if ((state?.todo?.open || 0) === 0 && (state?.todo?.done || 0) === 0) {
813
- return `${name}: wait for one concrete artifact or ask Navigator to create a reviewable task with verifier, proof target, and residual-risk checklist.`;
814
- }
815
- return `${name}: review the highest-risk open or recently completed task, run its verifier, name residual risk, and approve or block with proof.`;
816
- }
817
- if (/executor|builder/i.test(identity)) {
818
- if ((state?.todo?.open || 0) === 0) {
819
- if (agentNeededReviewMove) return agentNeededReviewMove;
820
- if (codexEndgameMove) return codexEndgameMove;
821
- if (certifiedReviewMove) return certifiedReviewMove;
822
- return `${name}: ask Navigator to create one bounded task with files, verifier, and stop rule before making a patch.`;
823
- }
824
- 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.`;
825
- }
826
- if (/navigator|planner/i.test(identity)) {
827
- 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.`;
828
- }
829
- if (/launcher|closer/i.test(identity)) {
830
- if (agentNeededReviewMove) return agentNeededReviewMove;
831
- if (certifiedReviewMove) return certifiedReviewMove;
832
- if ((state?.todo?.done || 0) === 0) {
833
- return `${name}: wait for one validated task receipt before closeout, or ask Validator to produce a review decision with proof.`;
834
- }
835
- 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.`;
836
- }
837
- if (/brainstormer|idea shaper|reality shaper/i.test(identity)) {
838
- return `${name}: shape one raw idea into a concise vision: current reality, 1-2 options, constraints, success criteria, and a navigator-ready next step.`;
839
- }
840
- if (/researcher|research/i.test(identity)) {
841
- return `${name}: answer one explicit research question with primary sources, source-backed findings, unverified gaps, and a short So What handoff.`;
842
- }
843
- if (/gtm|forward deployed/i.test(identity) || /gtm|forward deployed|customer-moving|customer move/i.test(context)) {
844
- return `${name}: run one customer-moving GTM rep, update the relevant workspace state within 10 minutes, and leave a scorecard.`;
845
- }
846
- if (/ceo|lab/i.test(identity) || /ceo|lab|synthesis loop|decision queue|building|closing|investor/i.test(context)) {
847
- return `${name}: make one high-leverage CEO move: ship product, close a strategic loop, or make a queued decision; leave proof and a scorecard.`;
848
- }
849
- if (/rl-exp2|zero-spend|zero spend|tick-prefixed|1%/i.test(context)) {
850
- return `${name}: run the next zero-spend ` +
851
- '`rl-exp2` tick, land one artifact, name the 1% delta, and record the mission receipt.';
852
- }
853
- if (/validation checklist|signoff|reject|falsifiable|residual risk|anti-slop|review/i.test(context)) {
854
- return `${name}: review the highest-risk open or recently completed task, run its verifier, name residual risk, and approve or block with proof.`;
855
- }
856
- if (/bounded mission|mission step|proof target|verifier/i.test(context)) {
857
- return `${name}: choose or create one bounded mission step, run its verifier, and close it with proof, a scorecard, and the next move.`;
858
- }
859
- return `${name}: use your START_HERE, complete the first concrete work block, and leave a scorecard.`;
792
+ const context = `${member.startHere}\n${member.goals}`;
793
+ const review = state?.taskProjection?.agentNeededReviewTasks?.[0];
794
+ const certified = state?.taskProjection?.certifiedReviewTasks?.[0];
795
+ const reviewMove = review ? `review ${review.ref} using ${taskCommand(state.root, `atris task review-chat ${review.ref} --as codex-review`)}. run the check named in the review notes, then pass it or send it back. do not accept it yourself; a person does that.` : null;
796
+ const handoff = certified ? `ask a person to review ${certified.ref}. if approved, they can run ${taskCommand(state.root, `atris task accept ${certified.ref}`)}. if changes are needed, they can run ${taskCommand(state.root, `atris task revise ${certified.ref} --note "<what must change>"`)}. do not accept it yourself; a person does that. wait until this is settled before starting new work.` : null;
797
+ const refs = (state?.taskProjection?.certifiedReviewTasks || []).map(task => task.ref).filter(Boolean);
798
+ const nextGoal = refs.length && member.slug === 'codex-executor'
799
+ ? `reviews ${refs.slice(0, 3).join(', ')} need a person to accept them. do not accept them yourself; a person does that. create the next task from the long-range goal ${state?.endgame?.slug || 'current goal'}: ${state?.endgame?.horizon || 'the most important gap'}. include the files, a way to check it and when to stop before editing.` : null;
800
+ if (member.slug === 'justin' || /justin/i.test(member.name || '')) return 'take one step toward a customer, update the relevant notes within 10 minutes and record what happened.';
801
+ if (ownerIdentity().names.includes(String(member.slug || '').toLowerCase()) || ownerIdentity().names.includes(String(member.name || '').toLowerCase())) return 'choose one important decision or improvement and name who will take the next step.';
802
+ if (/opus|overnight/i.test(identity)) return 'run the next rl-exp2 experiment without spending money. finish one result, describe the improvement and record what happened.';
803
+ if (/mission[- ]lead|mission lead/i.test(identity)) return 'choose or create one step, run its check, record the result and what comes next.';
804
+ if (/validator|reviewer/i.test(identity)) return reviewMove || handoff || ((state?.todo?.open || 0) === 0 && (state?.todo?.done || 0) === 0 ? 'wait for work to review or ask the planner for a task with a way to check it, an expected result and a list of remaining risks.' : 'review the riskiest open or recently finished task, run its check and record any remaining risks. pass it or send it back. do not accept it yourself; a person does that.');
805
+ if (/executor|builder/i.test(identity)) return (state?.todo?.open || 0) === 0 ? (reviewMove || nextGoal || handoff || 'ask the planner for a task with exact files, a way to check it and when to stop before editing.') : 'work on the most useful assigned task, run its check and hand the result over for review. do not accept it yourself; a person does that.';
806
+ if (/navigator|planner/i.test(identity)) return 'turn one messy request into a plan using the map, with a simple diagram, exact files, a way to check it, a way to undo it and a task ready for review.';
807
+ if (/launcher|closer/i.test(identity)) return reviewMove || handoff || ((state?.todo?.done || 0) === 0 ? 'wait for a checked task or ask the reviewer for a decision.' : 'prepare one checked task for release, record what changed and the lesson, update the map or journal if needed and name the next step.');
808
+ if (/brainstormer|idea shaper|reality shaper/i.test(identity)) return 'turn one idea into a clear goal, the current situation, one or two options, limits, success criteria and a next step for the planner.';
809
+ if (/researcher|research/i.test(identity)) return 'answer one research question with original sources, supported findings, what remains uncertain and a short handoff.';
810
+ if (/gtm|forward deployed/i.test(identity) || /gtm|forward deployed|customer-moving|customer move/i.test(context)) return 'take one step toward a customer, update the relevant notes within 10 minutes and record what happened.';
811
+ if (/ceo|lab/i.test(identity) || /ceo|lab|synthesis loop|decision queue|building|closing|investor/i.test(context)) return 'make one important decision or product improvement and record the result.';
812
+ if (/rl-exp2|zero-spend|zero spend|tick-prefixed|1%/i.test(context)) return 'run the next rl-exp2 experiment without spending money. finish one result, describe the improvement and record what happened.';
813
+ if (/validation checklist|signoff|reject|falsifiable|residual risk|anti-slop|review/i.test(context)) return 'review the riskiest open or recently finished task, run its check and record any remaining risks. pass it or send it back. do not accept it yourself; a person does that.';
814
+ if (/bounded mission|mission step|proof target|verifier/i.test(context)) return 'choose or create one step, run its check, record the result and what comes next.';
815
+ return 'follow your starting notes, finish the first task and record how it went.';
860
816
  }
861
817
 
862
818
  function modeNextMove(member, mode) {
863
819
  if (!member || !mode) return null;
864
- const normalized = String(mode).toLowerCase().replace(/[_-]+/g, ' ').trim();
865
- const name = member.name || member.slug;
866
- if (/founder|lab|idea|strategy/.test(normalized)) {
867
- return {
868
- label: 'founder lab',
869
- move: `${name}: think through one crazy company idea, turn it into a customer wedge hypothesis, then route execution to Justin or Build.`,
870
- proof: 'one idea note, one customer target, one delegated next action, and one scorecard',
871
- };
872
- }
873
- if (/build|builder|product|backend|code/.test(normalized)) {
874
- return {
875
- label: 'builder',
876
- move: `${name}: ship one product or system improvement that makes Atris easier to sell, operate, or self-improve.`,
877
- proof: 'one shipped diff or artifact, verification output, and one scorecard',
878
- };
879
- }
880
- if (/close|closer|whale|investor|customer/.test(normalized)) {
881
- return {
882
- label: 'closer',
883
- move: `${name}: advance one whale, investor, or strategic customer conversation with a concrete next step.`,
884
- proof: 'one drafted or sent message, one relationship update, one next step, and one scorecard',
885
- };
886
- }
887
- if (/decision|queue|approve|approval/.test(normalized)) {
888
- return {
889
- label: 'decision queue',
890
- move: `${name}: clear one high-leverage yes/no decision that only you should make, then delegate the follow-through.`,
891
- proof: 'one decision recorded, one owner assigned, one follow-up path, and one scorecard',
892
- };
893
- }
820
+ const text = String(mode).toLowerCase().replace(/[_-]+/g, ' ');
821
+ if (/founder|lab|idea|strategy/.test(text)) return { label: 'founder lab', move: 'develop one company idea, identify a customer and assign the next step.', proof: 'an idea note, a customer target, an assigned next step and feedback are recorded.' };
822
+ if (/build|builder|product|backend|code/.test(text)) return { label: 'builder', move: 'make one product or system improvement.', proof: 'the change is made, its check passes and feedback is recorded.' };
823
+ if (/close|closer|whale|investor|customer/.test(text)) return { label: 'closer', move: 'advance one customer or investor conversation with a clear next step.', proof: 'a drafted or sent message, a relationship update, a next step and feedback are recorded.' };
824
+ if (/decision|queue|approve|approval/.test(text)) return { label: 'decision queue', move: 'make one decision that needs you and assign the follow-up.', proof: 'the decision, person responsible, follow-up and feedback are recorded.' };
894
825
  return null;
895
826
  }
896
827
 
@@ -904,111 +835,71 @@ function memberProfileIssues(member) {
904
835
  return issues;
905
836
  }
906
837
 
907
- function renderMissingMemberCard(state, memberSlug) {
908
- const slug = normalizeMemberSlug(memberSlug) || '<name>';
909
- const members = listMemberSlugs(state.root);
910
- const available = members.length > 0 ? members.join(', ') : 'none';
911
- return `CONTEXT: ${state.name} Brain
912
- OPERATOR: ${slug} (missing)
913
- NEXT MOVE: Create atris/team/${slug}/MEMBER.md or rerun with an existing member.
914
- WHY: Activation can only route by operator after the member profile exists locally.
915
- PROOF: Re-run atris brain activate --member ${slug} --root ${state.root} --verify and get an operator-specific work block.
916
- AVAILABLE MEMBERS: ${available}
917
- FEEDBACK: yes / edit / no`;
918
- }
919
-
920
- function renderPlaceholderMemberCard(state, member, issues) {
921
- const slug = member.slug || '<name>';
922
- return `CONTEXT: ${state.name} Brain
923
- OPERATOR: ${member.name || slug} (not ready)
924
- NEXT MOVE: Replace placeholder sections in atris/team/${slug}/MEMBER.md with the member's real workflow, rules, and proof standard.
925
- WHY: Activation should not turn template text into fake operator work.
926
- PROOF: Re-run atris brain activate --member ${slug} --root ${state.root} --verify and get an operator-specific work block.
927
- PROFILE ISSUES: ${issues.join('; ')}
928
- FEEDBACK: yes / edit / no`;
929
- }
930
-
931
- function renderMissingStartHereCard(state, member) {
932
- const slug = member.slug || '<name>';
933
- return `CONTEXT: ${state.name} Brain
934
- OPERATOR: ${member.name || slug} (not ready)
935
- NEXT MOVE: Create atris/team/${slug}/START_HERE.md with the member's first concrete work block, verifier, and proof target.
936
- WHY: Activation should not tell an operator to use START_HERE until that local contract exists.
937
- PROOF: Re-run atris brain activate --member ${slug} --root ${state.root} --verify and get an operator-specific work block.
938
- FEEDBACK: yes / edit / no`;
939
- }
940
-
941
- function renderActivationCard(state, options = {}) {
942
- const requestedMember = options.member || readRememberedOperator(state.root);
943
- const member = readMemberContext(state.root, requestedMember);
944
- if (!member && requestedMember) {
945
- return renderMissingMemberCard(state, requestedMember);
946
- }
947
- if (!member && !options.member) {
948
- return `CONTEXT: ${state.name} Brain
949
- OPERATOR: unknown
950
- NEXT MOVE: Tell Atris who is operating: atris brain activate --member <name> --root ${state.root}
951
- WHY: The brain should route work by operator, customer, and proof path before it assigns the next move.
952
- PROOF: Activation re-runs with a known operator and produces a specific work block.
953
- FEEDBACK: yes / edit / no`;
954
- }
955
- const profileIssues = memberProfileIssues(member);
956
- if (profileIssues.length > 0) {
957
- return renderPlaceholderMemberCard(state, member, profileIssues);
958
- }
959
- if (!String(member.startHere || '').trim()) {
960
- return renderMissingStartHereCard(state, member);
961
- }
962
- if (options.remember !== false) rememberOperator(state.root, member);
963
- const modeMove = modeNextMove(member, options.mode);
964
- const next = modeMove?.move || memberNextMove(member, state) || nextMove(state);
965
- const scoreContext = memberScoreContext(state.root, member);
966
- 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`;
967
- const scoreLines = scoreContext
968
- ? `${scoreContext.score ? `\nSCORE: ${scoreContext.score}` : ''}${scoreContext.nextRep ? `\nNEXT REP: ${scoreContext.nextRep}` : ''}`
969
- : '';
970
- return `CONTEXT: ${state.name} Brain${member ? `\nOPERATOR: ${member.name}` : ''}${modeMove ? `\nMODE: ${modeMove.label}` : ''}
971
- NEXT MOVE: ${next}
972
- WHY: This is the next business workflow to improve from atris/now.md, the compiled brain, MAP, TODO, wiki, state rows, and reward history.
973
- PROOF: ${proof}${scoreLines}
974
- FEEDBACK: yes / edit / no`;
975
- }
976
-
977
- function renderActivationGallery(state) {
978
- const slugs = listMemberSlugs(state.root);
979
- if (slugs.length === 0) {
980
- return `CONTEXT: ${state.name} Brain
981
- TEAM: no members found
982
- NEXT MOVE: Add a member under atris/team/<name>/MEMBER.md, then run activation again.`;
983
- }
984
-
985
- return slugs.map(slug => renderActivationCard(state, { member: slug, remember: false })).join('\n\n---\n\n');
986
- }
987
-
988
- function galleryReadinessIssues(gallery) {
989
- return String(gallery || '')
990
- .split(/\n---\n/)
991
- .flatMap(card => activationCardReadinessIssues(card));
992
- }
993
-
994
- function activationCardReadinessIssues(card) {
995
- const operator = (String(card || '').match(/^OPERATOR:\s*(.+)$/m) || [null, 'unknown member'])[1];
996
- if (/\((missing|not ready)\)/.test(operator)) return [operator];
997
- return [];
838
+ function notesInfo(root) {
839
+ const saved = readJson(path.join(root, 'atris', 'brain', 'state.json'));
840
+ const date = Date.parse(saved?.generatedAt);
841
+ if (!Number.isFinite(date)) return { status: 'missing', age_days: null };
842
+ const age = Math.max(0, Math.floor((Date.now() - date) / 86400000));
843
+ return { status: age > 3 ? 'stale' : 'fresh', age_days: age };
844
+ }
845
+
846
+ // The folder name is what a person sees on disk, so the card always shows it.
847
+ // A business name that differs from the folder rides along in brackets, which
848
+ // also makes a leftover business file easy to spot.
849
+ function shownName(state) {
850
+ const folder = path.basename(state.root);
851
+ const name = String(state.name || '').trim();
852
+ const key = value => String(value || '').toLowerCase().replace(/[^a-z0-9]+/g, '');
853
+ if (!name || key(name) === key(folder)) return name || folder;
854
+ return `${folder} (${name})`;
855
+ }
856
+
857
+ function buildActivationCard(state, options = {}) {
858
+ const requested = options.member;
859
+ const remembered = readRememberedOperator(state.root);
860
+ const rootArg = rootOption(state.root);
861
+ const member = readMemberContext(state.root, requested);
862
+ const readFirst = ['atris/now.md', 'atris/MAP.md', 'atris/PERSONA.md'].filter(file => fs.existsSync(path.join(state.root, file)));
863
+ const notes = notesInfo(state.root);
864
+ const summary = buildActivationSummary(state.root);
865
+ const contextLine = line => line && line.replace('atris task reviews', taskCommand(state.root, 'atris task reviews'));
866
+ if (!requested) {
867
+ const lines = [`${shownName(state)} is ready.`, '', ...summary.lines.map(contextLine), ...(summary.today ? [contextLine(summary.today)] : [])];
868
+ if (readFirst.length) lines.push('', 'read these first:', ...readFirst.map(file => ` ${file}`));
869
+ if (remembered) lines.push('', `last role used here: ${remembered}. for its next step: atris brain activate --member ${remembered}${rootArg}`);
870
+ if (notes.status === 'missing') lines.push('', `start-up notes are not saved yet. save them: atris brain compile --root ${rootArg ? shellQuote(state.root) : '.'} --yes`);
871
+ if (notes.status === 'stale') lines.push('', `the saved start-up notes are ${notes.age_days} days old. refresh them: atris brain compile --root ${rootArg ? shellQuote(state.root) : '.'} --yes`);
872
+ return { ready: true, member: null, text: lines.join('\n'), readFirst, notes, summary };
873
+ }
874
+ const slug = normalizeMemberSlug(requested);
875
+ let reason = null;
876
+ let fix = null;
877
+ if (!member) { reason = 'there is no team member by that name'; const members = listMemberSlugs(state.root); fix = `create atris/team/${slug}/MEMBER.md${members.length ? `, or pick one of: ${members.join(', ')}` : ''}`; }
878
+ else if (memberProfileIssues(member).length) { reason = `the team member details still contain placeholders: ${memberProfileIssues(member).join('; ')}`; fix = `fill in atris/team/${slug}/MEMBER.md`; }
879
+ else if (!member.startHere.trim()) { reason = 'the starting instructions are missing'; fix = `create atris/team/${slug}/START_HERE.md with the first task, a way to check it and the expected result`; }
880
+ if (reason) return { ready: false, reason, member: slug, text: `${shownName(state)} is not ready for ${slug}: ${reason}.\n\nnext: ${fix}`, readFirst, notes, summary };
881
+ if (options.remember) rememberOperator(state.root, member);
882
+ const mode = modeNextMove(member, options.mode);
883
+ const score = memberScoreContext(state.root, member);
884
+ const proof = mode?.proof || score?.proofNeeded || score?.nextRep || 'the task is finished and its result is recorded.';
885
+ const lines = [`${shownName(state)} is ready for ${slug}.`, '', `next: ${mode?.move || memberNextMove(member, state) || nextMove(state)}`, 'why: it is the next piece of work for this role, picked from the current notes, the task list and past feedback.', `done when: ${proof}`];
886
+ if (mode?.label) lines.push(`mode: ${mode.label}`);
887
+ if (score?.score) lines.push(`score: ${score.score}`);
888
+ if (score?.nextRep && score.nextRep !== proof) lines.push(`next rep: ${score.nextRep}`);
889
+ lines.push(`how did it go: atris brain yes${rootArg}, atris brain edit "what to change"${rootArg}, or atris brain no${rootArg}`);
890
+ const context = [summary.right_now, summary.waiting].filter(Boolean).map(contextLine);
891
+ if (context.length) lines.push('', ...context);
892
+ if (readFirst.length) lines.push('', 'read these first:', ...readFirst.map(file => ` ${file}`));
893
+ return { ready: true, member: slug, text: lines.join('\n'), readFirst, notes, summary };
998
894
  }
999
895
 
1000
- function verifyActivationGallery(gallery) {
1001
- const issues = galleryReadinessIssues(gallery);
1002
- if (issues.length > 0) {
1003
- throw new Error(`brain gallery not-ready member activation cards: ${issues.join(', ')}`);
1004
- }
896
+ function verifyActivationCard(card) {
897
+ if (!card.ready) throw new Error(`${card.member}: ${card.reason}`);
1005
898
  }
1006
899
 
1007
- function verifyActivationCard(card) {
1008
- const issues = activationCardReadinessIssues(card);
1009
- if (issues.length > 0) {
1010
- throw new Error(`brain activate non-executable member activation card: ${issues.join(', ')}`);
1011
- }
900
+ function verifyActivationGallery(cards) {
901
+ const issues = cards.filter(card => !card.ready);
902
+ if (issues.length) throw new Error(issues.map(card => `${card.member}: ${card.reason}`).join(', '));
1012
903
  }
1013
904
 
1014
905
  function printVerifyFailure(error) {
@@ -1388,10 +1279,8 @@ ${nextMove(state)}
1388
1279
 
1389
1280
  ${renderNumberedLoadOrder(state)}
1390
1281
 
1391
- First-message rule: lead with the move before writing to the operator.
1392
- Purpose: optimize for decision-speed; lead with the move, then use descriptions only when they help the operator act.
1393
- Shape: \`<operator>, today is about <move>\` -> \`I picked this because <why now>\` -> \`Ready: <draft/proof/context>\` -> \`Go deeper: <paths>\`.
1394
- 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.
1282
+ First reply: open with the one thing most worth doing today. Then say why, what is already prepared and where to look for more.
1283
+ Shape: \`<name>, today is about <the one thing>\` -> \`I picked this because <why now>\` -> \`Ready: <what is prepared>\` -> \`Go deeper: <2 to 4 paths>\`.
1395
1284
  `;
1396
1285
  }
1397
1286
 
@@ -1441,25 +1330,23 @@ ${nextMove(state)}
1441
1330
 
1442
1331
  function generatedBootBlock(state, voiceCard = voiceCardForRoot(state.root)) {
1443
1332
  return `${GENERATED_START}
1444
- ## Atris Brain Compile
1333
+ ## Atris start-up notes
1445
1334
 
1446
- This workspace has a compiled agent brain.
1335
+ This folder keeps notes that give every agent the same starting context.
1447
1336
 
1448
- On session start, activate it first:
1449
- \`atris brain activate --root . --yes --verify\`
1337
+ At the start of a session, run this and read what it prints:
1338
+ \`atris brain activate --root .\`
1450
1339
 
1451
- Load these first:
1340
+ Then read these files in order:
1452
1341
  ${renderBulletedLoadOrder(state)}
1453
1342
 
1454
- First-message rule: lead with the move before writing to the operator.
1455
- Purpose: optimize for decision-speed; lead with the move, then use descriptions only when they help the operator act.
1456
- Shape: \`<operator>, today is about <move>\` -> \`I picked this because <why now>\` -> \`Ready: <draft/proof/context>\` -> \`Go deeper: <paths>\`.
1457
- 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.
1343
+ First reply: open with the one thing most worth doing today. Then say why, what is already prepared and where to look for more.
1344
+ Shape: \`<name>, today is about <the one thing>\` -> \`I picked this because <why now>\` -> \`Ready: <what is prepared>\` -> \`Go deeper: <2 to 4 paths>\`.
1458
1345
 
1459
- Keep this voice beside every reply:
1346
+ Keep this voice in every reply:
1460
1347
  ${renderVoiceCardBlock(voiceCard)}
1461
1348
 
1462
- Re-run after meaningful work:
1349
+ After meaningful work, save fresh notes:
1463
1350
  \`atris brain compile --root . --yes\`
1464
1351
  ${GENERATED_END}
1465
1352
  `;
@@ -1507,9 +1394,9 @@ function refuseBrainWriteWithoutYes(state, options = {}) {
1507
1394
  next_command: 'atris brain compile --yes',
1508
1395
  }, null, 2));
1509
1396
  } else {
1510
- console.log('Plan only. Would write:');
1397
+ console.log('nothing saved yet. this would write:');
1511
1398
  for (const file of files) console.log(` ${file}`);
1512
- console.log('Re-run with --yes to write these files.');
1399
+ console.log(`to save them: ${compileCommand(state.root)}`);
1513
1400
  }
1514
1401
  if (isNonInteractive()) process.exit(2);
1515
1402
  return { planned: true, files };
@@ -1562,25 +1449,28 @@ function verifyBrain(root) {
1562
1449
  ];
1563
1450
  const missing = required.filter(rel => !fs.existsSync(path.join(root, rel)));
1564
1451
  if (missing.length > 0) {
1565
- throw new Error(`brain compile missing: ${missing.join(', ')}`);
1452
+ throw new Error(`missing: ${missing.join(', ')}. save them: ${compileCommand(root)}`);
1566
1453
  }
1567
1454
  const status = readText(path.join(root, 'atris', 'brain', 'STATUS.md'));
1568
1455
  const ledger = readText(path.join(root, 'atris', 'brain', 'self_improvement_ledger.md'));
1569
1456
  if (!status.includes('## Next Move') || !ledger.includes('## Run N -> Run N+1 Mechanism')) {
1570
- throw new Error('brain compile artifacts are missing required sections');
1457
+ throw new Error(`saved notes are missing required sections. refresh them: ${compileCommand(root)}`);
1571
1458
  }
1572
1459
  }
1573
1460
 
1574
1461
  function brainUsageLines() {
1575
1462
  return [
1576
- 'Usage: atris brain compile [--root <workspace>] [--yes] [--verify] [--json]',
1577
- ' atris brain activate [--member <slug>] [--root <workspace>] [--yes] [--verify] [--json]',
1578
- ' atris brain gallery [--root <workspace>] [--yes] [--verify] [--json]',
1579
- ' atris brain go|hold [note] [--recommendation <text>] [--root <workspace>] [--verify]',
1580
- ' atris brain approval go|edit|hold [note] [--recommendation <text>] [--root <workspace>] [--verify]',
1581
- ' atris brain scorecard [--root <workspace>] [--verify] [--json]',
1582
- ' atris brain feedback --rating approve|edit|reject [--recommendation <text>] [--note <text>] [--root <workspace>] [--verify]',
1583
- ' atris brain yes|edit|no [note] [--root <workspace>] [--verify]',
1463
+ 'atris brain: the notes every agent reads before it starts work in this folder.',
1464
+ '',
1465
+ ' atris brain activate show what is in progress, what is waiting and the next step',
1466
+ ' atris brain activate --member <name> the same, for one team member',
1467
+ ' atris brain compile --yes save fresh start-up notes into this folder',
1468
+ ' atris brain gallery show the next step for every team member',
1469
+ ' atris brain yes | edit | no ["note"] say how the last suggestion went',
1470
+ ' atris brain go | hold ["note"] approve or pause a piece of work',
1471
+ ' atris brain scorecard score finished tasks so later suggestions improve',
1472
+ '',
1473
+ 'options: --root <folder> --yes (allow saving files) --verify (check the notes are in place) --json',
1584
1474
  ];
1585
1475
  }
1586
1476
 
@@ -1610,15 +1500,8 @@ function brainCommand(args = process.argv.slice(3)) {
1610
1500
  console.log(JSON.stringify({ ok: true, approval }, null, 2));
1611
1501
  return;
1612
1502
  }
1613
- console.log('');
1614
- console.log('Atris brain approval recorded.');
1615
- console.log(` Approval: ${approval.approval_id}`);
1616
- console.log(` Decision: ${approval.human_decision}`);
1617
- console.log(` Status: ${approval.status}`);
1618
- console.log(' Wrote: .atris/state/approvals.jsonl');
1619
- console.log(' Next: proceed only if decision is go; otherwise edit or hold.');
1620
- if (options.verify) console.log(' Verify: passed');
1621
- console.log('');
1503
+ console.log(`approval recorded: ${approval.human_decision}. ${approval.human_decision === 'go' ? 'work can proceed.' : 'review the work before proceeding.'}`);
1504
+ if (options.verify) console.log('checked: the decision was saved.');
1622
1505
  return;
1623
1506
  }
1624
1507
 
@@ -1629,16 +1512,8 @@ function brainCommand(args = process.argv.slice(3)) {
1629
1512
  console.log(JSON.stringify({ ok: true, ...result }, null, 2));
1630
1513
  return;
1631
1514
  }
1632
- console.log('');
1633
- console.log('Atris brain feedback recorded.');
1634
- console.log(` Decision: ${result.scorecard.decision_id}`);
1635
- console.log(` Rating: ${result.scorecard.human_rating}`);
1636
- console.log(` Reward: ${result.scorecard.reward}`);
1637
- console.log(' Wrote: .atris/state/scorecards.jsonl');
1638
- console.log(' Wrote: .atris/state/episodes.jsonl');
1639
- console.log(' Next: atris brain compile');
1640
- if (options.verify) console.log(' Verify: passed');
1641
- console.log('');
1515
+ console.log(`feedback recorded: ${result.scorecard.human_rating}. refresh the notes: ${compileCommand(options.root)}`);
1516
+ if (options.verify) console.log('checked: the feedback was saved.');
1642
1517
  return;
1643
1518
  }
1644
1519
 
@@ -1649,133 +1524,59 @@ function brainCommand(args = process.argv.slice(3)) {
1649
1524
  console.log(JSON.stringify({ ok: true, ...result }, null, 2));
1650
1525
  return;
1651
1526
  }
1652
- console.log('');
1653
- console.log('Atris brain scorecards recorded.');
1654
- console.log(` Task episodes: ${result.taskEpisodes}`);
1655
- console.log(` New scorecards: ${result.written}`);
1656
- console.log(' Wrote: .atris/state/scorecards.jsonl');
1657
- console.log(' Next: atris brain compile');
1658
- if (options.verify) console.log(' Verify: passed');
1659
- console.log('');
1527
+ console.log(`finished work scored: ${result.written}. refresh the notes: ${compileCommand(options.root)}`);
1528
+ if (options.verify) console.log('checked: the scores were saved.');
1660
1529
  return;
1661
1530
  }
1662
1531
 
1663
- if (subcommand === 'activate') {
1664
- const state = prepareBrainState(options.root);
1665
- if (!options.yes) {
1666
- refuseBrainWriteWithoutYes(state, options);
1667
- return 0;
1668
- }
1669
- writeBrain(state);
1670
- if (options.verify) verifyBrain(options.root);
1671
- const card = renderActivationCard(state, options);
1672
- if (options.json) {
1673
- if (options.verify) {
1674
- try {
1675
- verifyActivationCard(card);
1676
- } catch (error) {
1677
- console.log(JSON.stringify({
1678
- ok: false,
1679
- error: error && error.message ? error.message : String(error),
1680
- state,
1681
- card,
1682
- }, null, 2));
1683
- process.exit(1);
1684
- }
1685
- }
1686
- console.log(JSON.stringify({ ok: true, state, card }, null, 2));
1687
- return 0;
1688
- }
1689
- console.log('');
1690
- console.log(card);
1691
- if (options.verify) {
1692
- try {
1693
- verifyActivationCard(card);
1694
- } catch (error) {
1695
- printVerifyFailure(error);
1696
- return 1;
1697
- }
1698
- const operator = (card.match(/^OPERATOR:\s*(.+)$/m) || [null, 'unknown'])[1];
1699
- if (operator === 'unknown') {
1700
- console.log('VERIFY: brain artifacts present');
1701
- } else {
1702
- console.log('VERIFY: brain artifacts and member activation executable');
1703
- }
1704
- }
1705
- console.log('');
1706
- return 0;
1707
- }
1708
- if (subcommand === 'gallery') {
1709
- const state = prepareBrainState(options.root);
1710
- if (!options.yes) {
1711
- refuseBrainWriteWithoutYes(state, options);
1712
- return 0;
1713
- }
1714
- writeBrain(state);
1715
- if (options.verify) verifyBrain(options.root);
1716
- const gallery = renderActivationGallery(state);
1532
+ if (!['activate', 'status', 'gallery', 'compile'].includes(subcommand)) {
1533
+ if (options.json) console.log(JSON.stringify({ ok: false, error: `unknown brain subcommand: ${subcommand}`, usage: brainUsageLines() }, null, 2));
1534
+ else printBrainUsage(console.error);
1535
+ return 1;
1536
+ }
1537
+ if (!fs.existsSync(path.join(options.root, 'atris'))) {
1538
+ throw new Error('this folder is not set up for atris yet. set it up: atris init');
1539
+ }
1540
+ if (['activate', 'status', 'gallery'].includes(subcommand)) {
1541
+ const state = options.yes && subcommand !== 'status' ? prepareBrainState(options.root) : collectState(options.root);
1542
+ const wrote = Boolean(options.yes && subcommand !== 'status');
1543
+ if (wrote) writeBrain(state);
1544
+ const cards = subcommand === 'gallery'
1545
+ ? listMemberSlugs(options.root).map(member => buildActivationCard(state, { member }))
1546
+ : [buildActivationCard(state, { ...options, remember: subcommand === 'activate' && Boolean(options.member) })];
1547
+ const card = cards[0];
1548
+ const output = subcommand === 'gallery' ? (cards.map(item => item.text).join('\n\n---\n\n') || 'no team members found. create one in atris/team/<name>/MEMBER.md') : card.text;
1549
+ let error = null;
1550
+ let readinessError = false;
1717
1551
  if (options.verify) {
1718
- try {
1719
- verifyActivationGallery(gallery);
1720
- } catch (error) {
1721
- if (options.json) {
1722
- console.log(JSON.stringify({
1723
- ok: false,
1724
- error: error && error.message ? error.message : String(error),
1725
- members: listMemberSlugs(options.root),
1726
- gallery,
1727
- }, null, 2));
1728
- process.exit(1);
1729
- }
1730
- printVerifyFailure(error);
1731
- return 1;
1552
+ try { verifyBrain(options.root); } catch (err) { error = err.message; }
1553
+ if (!error) {
1554
+ try { if (subcommand === 'gallery') verifyActivationGallery(cards); else verifyActivationCard(card); }
1555
+ catch (err) { error = err.message; readinessError = true; }
1732
1556
  }
1733
1557
  }
1734
1558
  if (options.json) {
1735
- console.log(JSON.stringify({ ok: true, members: listMemberSlugs(options.root), gallery }, null, 2));
1736
- return 0;
1737
- }
1738
- console.log('');
1739
- console.log(gallery);
1740
- if (options.verify) console.log('\nVERIFY: brain artifacts and member readiness present');
1741
- console.log('');
1742
- return 0;
1743
- }
1744
- if (subcommand !== 'compile' && subcommand !== 'status') {
1745
- if (options.json) {
1746
- console.log(JSON.stringify({
1747
- ok: false,
1748
- error: `unknown brain subcommand: ${subcommand}`,
1749
- usage: brainUsageLines(),
1750
- }, null, 2));
1751
- process.exit(1);
1559
+ const data = subcommand === 'gallery' ? { members: listMemberSlugs(options.root), gallery: output, remembered_member: readRememberedOperator(options.root) } : { state, card: output, name: state.name, member: card.member, remembered_member: readRememberedOperator(options.root), ready: card.ready, wrote, read_first: card.readFirst, notes: card.notes, summary: card.summary, ...card.summary };
1560
+ console.log(JSON.stringify({ ok: !error, ...data, ...(error ? { error } : {}) }, null, 2));
1561
+ } else {
1562
+ console.log(`\n${output}`);
1563
+ if (error && !readinessError) console.error(error);
1564
+ else if (options.verify && !error) console.log('\nchecked: the saved notes are in place.');
1565
+ console.log('');
1752
1566
  }
1753
- printBrainUsage(console.error);
1754
- process.exit(1);
1755
- }
1756
-
1757
- const state = prepareBrainState(options.root);
1758
- if (!options.yes) {
1759
- refuseBrainWriteWithoutYes(state, options);
1760
- return 0;
1567
+ return error ? 1 : 0;
1761
1568
  }
1569
+ const state = options.yes ? prepareBrainState(options.root) : collectState(options.root);
1570
+ if (!options.yes) { refuseBrainWriteWithoutYes(state, options); return 0; }
1762
1571
  const written = writeBrain(state);
1763
1572
  if (options.verify) verifyBrain(options.root);
1764
-
1765
- if (options.json) {
1766
- console.log(JSON.stringify({ ok: true, state, written }, null, 2));
1767
- return 0;
1768
- }
1769
-
1770
- console.log('');
1771
- console.log('Atris brain compiled.');
1772
- console.log(` Workspace: ${state.name}`);
1773
- console.log(` State rows: ${state.totalRows} raw / ${state.validRows} valid`);
1774
- console.log(` Status: ${path.relative(options.root, written.statusPath).replace(/\\/g, '/')}`);
1775
- console.log(` Ledger: ${path.relative(options.root, written.ledgerPath).replace(/\\/g, '/')}`);
1776
- console.log(` Next: ${nextMove(state)}`);
1777
- if (options.verify) console.log(' Verify: passed');
1778
- console.log('');
1573
+ if (options.json) { console.log(JSON.stringify({ ok: true, state, written }, null, 2)); return 0; }
1574
+ console.log(`start-up notes saved for ${shownName(state)}.`);
1575
+ console.log(` status: ${path.relative(options.root, written.statusPath)}`);
1576
+ console.log(` history: ${state.totalRows} records read, ${state.validRows} usable`);
1577
+ console.log(` history file: ${path.relative(options.root, written.ledgerPath)}`);
1578
+ console.log(` next: ${nextMove(state)}`);
1579
+ if (options.verify) console.log(' checked: the saved notes are in place.');
1779
1580
  return 0;
1780
1581
  }
1781
1582