atris 3.15.45 → 3.15.48

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 (74) hide show
  1. package/AGENTS.md +2 -2
  2. package/atris/skills/atris/SKILL.md +1 -1
  3. package/bin/atris.js +10 -4
  4. package/commands/business.js +574 -21
  5. package/commands/computer.js +1 -1
  6. package/commands/mission.js +16 -1
  7. package/commands/radar.js +546 -0
  8. package/commands/sync.js +101 -1
  9. package/lib/runtime-bootstrap.js +17 -5
  10. package/lib/task-db.js +29 -3
  11. package/package.json +3 -1
  12. package/templates/business-starter/CLAUDE.md +62 -0
  13. package/templates/business-starter/MAP.md +81 -0
  14. package/templates/business-starter/MEMBER.md +46 -0
  15. package/templates/business-starter/TODO.md +28 -0
  16. package/templates/business-starter/atris.md +61 -0
  17. package/templates/business-starter/context/README.md +19 -0
  18. package/templates/business-starter/context/live-workspace.md +36 -0
  19. package/templates/business-starter/goals.md +33 -0
  20. package/templates/business-starter/instructions.md +40 -0
  21. package/templates/business-starter/memory.md +31 -0
  22. package/templates/business-starter/persona.md +26 -0
  23. package/templates/business-starter/policies/LESSONS.md +5 -0
  24. package/templates/business-starter/policies/REWARD.md +24 -0
  25. package/templates/business-starter/reports/README.md +17 -0
  26. package/templates/business-starter/reports/operating-recap-template.md +44 -0
  27. package/templates/business-starter/skills/README.md +21 -0
  28. package/templates/business-starter/team/README.md +20 -0
  29. package/templates/business-starter/team/START_HERE.md +45 -0
  30. package/templates/business-starter/team/_template/MEMBER.md +32 -0
  31. package/templates/business-starter/team/_template/SOUL.md +40 -0
  32. package/templates/business-starter/team/comms/MEMBER.md +34 -0
  33. package/templates/business-starter/team/comms/SOUL.md +32 -0
  34. package/templates/business-starter/team/operator/MEMBER.md +34 -0
  35. package/templates/business-starter/team/ops/MEMBER.md +34 -0
  36. package/templates/business-starter/team/ops/SOUL.md +32 -0
  37. package/templates/business-starter/team/research/MEMBER.md +34 -0
  38. package/templates/business-starter/team/research/SOUL.md +32 -0
  39. package/templates/business-starter/team/validator/MEMBER.md +34 -0
  40. package/templates/business-starter/wiki/STATUS.md +7 -0
  41. package/templates/business-starter/wiki/concepts/first-loop-template.md +34 -0
  42. package/templates/business-starter/wiki/index.md +30 -0
  43. package/templates/business-starter/wiki/log.md +11 -0
  44. package/templates/business-starter/wiki/wiki.md +26 -0
  45. package/templates/research-canonical/CLAUDE.md +70 -0
  46. package/templates/research-canonical/MAP.md +68 -0
  47. package/templates/research-canonical/MEMBER.md +46 -0
  48. package/templates/research-canonical/TODO.md +28 -0
  49. package/templates/research-canonical/atris.md +62 -0
  50. package/templates/research-canonical/context/README.md +21 -0
  51. package/templates/research-canonical/context/live-workspace.md +24 -0
  52. package/templates/research-canonical/goals.md +23 -0
  53. package/templates/research-canonical/instructions.md +40 -0
  54. package/templates/research-canonical/memory.md +31 -0
  55. package/templates/research-canonical/persona.md +26 -0
  56. package/templates/research-canonical/policies/LESSONS.md +5 -0
  57. package/templates/research-canonical/policies/REWARD.md +21 -0
  58. package/templates/research-canonical/reports/README.md +17 -0
  59. package/templates/research-canonical/skills/README.md +21 -0
  60. package/templates/research-canonical/team/README.md +11 -0
  61. package/templates/research-canonical/team/eval/MEMBER.md +16 -0
  62. package/templates/research-canonical/team/eval/SOUL.md +32 -0
  63. package/templates/research-canonical/team/experiment/MEMBER.md +16 -0
  64. package/templates/research-canonical/team/experiment/SOUL.md +32 -0
  65. package/templates/research-canonical/team/hypothesis/MEMBER.md +16 -0
  66. package/templates/research-canonical/team/hypothesis/SOUL.md +32 -0
  67. package/templates/research-canonical/team/literature/MEMBER.md +16 -0
  68. package/templates/research-canonical/team/literature/SOUL.md +32 -0
  69. package/templates/research-canonical/wiki/STATUS.md +7 -0
  70. package/templates/research-canonical/wiki/briefs/research-program.md +19 -0
  71. package/templates/research-canonical/wiki/concepts/research-loop.md +14 -0
  72. package/templates/research-canonical/wiki/index.md +25 -0
  73. package/templates/research-canonical/wiki/log.md +10 -0
  74. package/templates/research-canonical/wiki/wiki.md +26 -0
@@ -3,7 +3,7 @@ const path = require('path');
3
3
  const os = require('os');
4
4
  const { loadCredentials } = require('../utils/auth');
5
5
  const { apiRequestJson } = require('../utils/api');
6
- const { syncBusinessCanonical, ensureWorkspaceStateFiles } = require('./sync');
6
+ const { syncBusinessCanonical, ensureWorkspaceStateFiles, ensureBusinessRootAgentAdapters } = require('./sync');
7
7
  const { ensureContextScaffold, writeWikiStatus, appendWikiLog } = require('../lib/wiki');
8
8
  const { writeRuntimeReceipt } = require('../lib/runtime-bootstrap');
9
9
 
@@ -398,7 +398,8 @@ function createCanonicalBusinessWorkspace(targetRoot, bizMeta, options = {}) {
398
398
  created_at: new Date().toISOString(),
399
399
  }, null, 2));
400
400
 
401
- syncBusinessCanonical(targetRoot, bizMeta, { force: false, dryRun: false, templateName: workspaceTemplate });
401
+ const syncResult = syncBusinessCanonical(targetRoot, bizMeta, { force: false, dryRun: false, templateName: workspaceTemplate });
402
+ const agentAdapters = syncResult?.agentAdapterList || ensureBusinessRootAgentAdapters(targetRoot, bizMeta);
402
403
  writeRuntimeReceipt(targetRoot, {
403
404
  scope: 'local-business-computer',
404
405
  boundary: 'business-workspace-scaffold',
@@ -409,8 +410,9 @@ function createCanonicalBusinessWorkspace(targetRoot, bizMeta, options = {}) {
409
410
  workspace_template: workspaceTemplate,
410
411
  install_status: 'local_cli_present',
411
412
  sync_status: 'templates_seeded',
413
+ agent_adapters: agentAdapters,
412
414
  });
413
- return { targetRoot, businessJsonPath, workspaceTemplate };
415
+ return { targetRoot, businessJsonPath, workspaceTemplate, agentAdapters };
414
416
  }
415
417
 
416
418
  function parseRecordFlags(args, cwd = process.cwd()) {
@@ -455,6 +457,50 @@ function parseRecordFlags(args, cwd = process.cwd()) {
455
457
  return options;
456
458
  }
457
459
 
460
+ function parseShareFlags(args, cwd = process.cwd()) {
461
+ const options = {
462
+ cwd,
463
+ role: 'collaborator',
464
+ name: '',
465
+ email: '',
466
+ write: false,
467
+ out: null,
468
+ };
469
+
470
+ for (let i = 0; i < args.length; i++) {
471
+ const arg = args[i];
472
+ const next = args[i + 1];
473
+ if ((arg === '--role' || arg === '-r') && next) {
474
+ options.role = next;
475
+ i++;
476
+ } else if ((arg === '--name' || arg === '--person') && next) {
477
+ options.name = next;
478
+ i++;
479
+ } else if (arg === '--email' && next) {
480
+ options.email = next;
481
+ i++;
482
+ } else if (arg === '--write') {
483
+ options.write = true;
484
+ } else if (arg === '--out' && next) {
485
+ options.out = next;
486
+ options.write = true;
487
+ i++;
488
+ } else if (arg.startsWith('--out=')) {
489
+ options.out = arg.slice('--out='.length);
490
+ options.write = true;
491
+ } else if ((arg === '--cwd' || arg === '--workspace') && next) {
492
+ options.cwd = path.resolve(cwd, next);
493
+ i++;
494
+ } else if (arg.startsWith('--cwd=')) {
495
+ options.cwd = path.resolve(cwd, arg.slice('--cwd='.length));
496
+ } else if (arg.startsWith('--workspace=')) {
497
+ options.cwd = path.resolve(cwd, arg.slice('--workspace='.length));
498
+ }
499
+ }
500
+
501
+ return options;
502
+ }
503
+
458
504
  function parseOnboardFlags(args, cwd = process.cwd()) {
459
505
  const options = {
460
506
  cwd,
@@ -567,6 +613,478 @@ function appendJsonl(filePath, record) {
567
613
  fs.appendFileSync(filePath, `${JSON.stringify(record)}\n`, 'utf8');
568
614
  }
569
615
 
616
+ function countJsonlRows(filePath) {
617
+ if (!fs.existsSync(filePath)) return 0;
618
+ return fs.readFileSync(filePath, 'utf8').split(/\r?\n/).filter(line => line.trim()).length;
619
+ }
620
+
621
+ function readJsonFile(filePath, fallback = null) {
622
+ if (!fs.existsSync(filePath)) return fallback;
623
+ try {
624
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
625
+ } catch {
626
+ return fallback;
627
+ }
628
+ }
629
+
630
+ function readJsonlRows(filePath) {
631
+ if (!fs.existsSync(filePath)) return [];
632
+ return fs.readFileSync(filePath, 'utf8')
633
+ .split(/\r?\n/)
634
+ .map(line => line.trim())
635
+ .filter(Boolean)
636
+ .map((line) => {
637
+ try { return JSON.parse(line); } catch { return null; }
638
+ })
639
+ .filter(Boolean);
640
+ }
641
+
642
+ function countFiles(dir, predicate = () => true) {
643
+ if (!fs.existsSync(dir)) return 0;
644
+ try {
645
+ return fs.readdirSync(dir).filter(predicate).length;
646
+ } catch {
647
+ return 0;
648
+ }
649
+ }
650
+
651
+ function latestMatchingFile(dir, predicate = () => true) {
652
+ if (!fs.existsSync(dir)) return null;
653
+ const rows = fs.readdirSync(dir)
654
+ .filter(predicate)
655
+ .map((name) => {
656
+ const full = path.join(dir, name);
657
+ let mtime = 0;
658
+ try { mtime = fs.statSync(full).mtimeMs; } catch {}
659
+ return { name, full, mtime };
660
+ })
661
+ .sort((a, b) => b.mtime - a.mtime);
662
+ return rows[0]?.full || null;
663
+ }
664
+
665
+ function rel(cwd, filePath) {
666
+ return filePath ? path.relative(cwd, filePath).replace(/\\/g, '/') : null;
667
+ }
668
+
669
+ function hasCloudWorkspaceId(value) {
670
+ const normalized = String(value || '').trim().toLowerCase();
671
+ return Boolean(normalized && normalized !== 'local-only');
672
+ }
673
+
674
+ function countBusinessTeamGoals(teamDir) {
675
+ if (!fs.existsSync(teamDir)) return { members: 0, activeGoalMembers: 0 };
676
+ let members = 0;
677
+ let activeGoalMembers = 0;
678
+ for (const name of fs.readdirSync(teamDir).filter(entry => !entry.startsWith('.'))) {
679
+ const memberDir = path.join(teamDir, name);
680
+ if (!fs.existsSync(path.join(memberDir, 'MEMBER.md'))) continue;
681
+ members++;
682
+ const goals = readJsonFile(path.join(memberDir, 'goals.json'), { goals: [] });
683
+ const activeGoals = Array.isArray(goals?.goals) ? goals.goals.filter(goal => goal.status === 'active') : [];
684
+ if (activeGoals.length > 0) activeGoalMembers++;
685
+ }
686
+ return { members, activeGoalMembers };
687
+ }
688
+
689
+ function collectBusinessOperatingState(cwd = process.cwd(), nowMs = Date.now()) {
690
+ const stateDir = path.join(cwd, '.atris', 'state');
691
+ const projection = readJsonFile(path.join(stateDir, 'tasks.projection.json'), { tasks: [] }) || { tasks: [] };
692
+ const tasks = Array.isArray(projection.tasks) ? projection.tasks : [];
693
+ const taskCounts = {
694
+ open: tasks.filter(task => task.status === 'open').length,
695
+ claimed: tasks.filter(task => task.status === 'claimed').length,
696
+ review: tasks.filter(task => task.status === 'review').length,
697
+ certifiedReview: tasks.filter(task => task.status === 'review' && task.metadata?.agent_certified).length,
698
+ blocked: tasks.filter(task => task.status === 'blocked').length,
699
+ };
700
+
701
+ const missionsById = new Map();
702
+ for (const mission of readJsonlRows(path.join(stateDir, 'missions.jsonl'))) {
703
+ if (mission?.id) missionsById.set(mission.id, mission);
704
+ }
705
+ const missions = [...missionsById.values()];
706
+ const terminal = new Set(['complete', 'completed', 'stopped', 'cancelled', 'done']);
707
+ const activeMissions = missions.filter(mission => !terminal.has(String(mission.status || '').toLowerCase()));
708
+ const staleMissions = activeMissions.filter((mission) => {
709
+ const status = String(mission.status || '').toLowerCase();
710
+ const lastTick = mission.last_tick_at ? Date.parse(mission.last_tick_at) : 0;
711
+ return status === 'running' && (!mission.verifier || !lastTick || nowMs - lastTick > 3 * 24 * 60 * 60 * 1000);
712
+ });
713
+ const missionEvents = readJsonlRows(path.join(stateDir, 'mission_events.jsonl'));
714
+ const codexGoal = readJsonFile(path.join(stateDir, 'codex_goal.json'), {}) || {};
715
+ const xp = readJsonFile(path.join(stateDir, 'career_xp.projection.json'), {}) || {};
716
+ const team = countBusinessTeamGoals(path.join(cwd, 'atris', 'team'));
717
+
718
+ return {
719
+ tasks: taskCounts,
720
+ missions: {
721
+ active: activeMissions.length,
722
+ running: activeMissions.filter(mission => mission.status === 'running').length,
723
+ alwaysOn: activeMissions.filter(mission => mission.always_on).length,
724
+ stale: staleMissions.length,
725
+ },
726
+ loop: {
727
+ ticks: missionEvents.filter(event => event.type === 'mission_tick').length,
728
+ codexGoal: codexGoal.goal?.objective || '',
729
+ },
730
+ team,
731
+ xp: {
732
+ metric: xp.metric_label || 'AgentXP',
733
+ total: Number(xp.total_agent_xp ?? xp.agent_xp ?? xp.total_xp ?? 0) || 0,
734
+ today: Number(xp.today_agent_xp ?? xp.today_xp ?? 0) || 0,
735
+ receipts: countJsonlRows(path.join(stateDir, 'career_xp_receipts.jsonl')),
736
+ integrity: xp.integrity_status || 'unknown',
737
+ },
738
+ };
739
+ }
740
+
741
+ function seedBusinessStarterTask(cwd, todoPath, starterAction) {
742
+ const title = `${starterAction.title} — ${starterAction.action}`;
743
+ try {
744
+ const taskDb = require('../lib/task-db');
745
+ const db = taskDb.open();
746
+ const workspaceRoot = taskDb.workspaceRoot(cwd);
747
+ const sourceKey = taskDb.sourceKey(todoPath, title);
748
+ const result = taskDb.addTask(db, {
749
+ title,
750
+ tag: 'execute',
751
+ workspaceRoot,
752
+ sourceKey,
753
+ metadata: {
754
+ source: 'business_onboard',
755
+ todo_id: 'Onboard',
756
+ todo_tags: ['execute'],
757
+ business_slug: readWorkspaceBusinessMeta(cwd).slug || null,
758
+ verify: 'atris business record atris/reports/<recap>.md --outcome mixed --metric "operator speed"',
759
+ },
760
+ });
761
+ const projection = taskDb.taskProjection(db, { workspaceRoot, limit: 500 });
762
+ const projectionPath = path.join(cwd, '.atris', 'state', 'tasks.projection.json');
763
+ fs.mkdirSync(path.dirname(projectionPath), { recursive: true });
764
+ fs.writeFileSync(projectionPath, `${JSON.stringify(projection, null, 2)}\n`, 'utf8');
765
+ return { ok: true, inserted: Boolean(result.inserted), taskId: result.id, projectionPath };
766
+ } catch (error) {
767
+ return { ok: false, error: error && error.message ? error.message : String(error) };
768
+ }
769
+ }
770
+
771
+ function collectBusinessShareState(cwd = process.cwd()) {
772
+ const bizMeta = readWorkspaceBusinessMeta(cwd);
773
+ const remoteReady = hasCloudWorkspaceId(bizMeta.business_id) && hasCloudWorkspaceId(bizMeta.workspace_id);
774
+ const reportsDir = path.join(cwd, 'atris', 'reports');
775
+ const briefsDir = path.join(cwd, 'atris', 'wiki', 'briefs');
776
+ const conceptsDir = path.join(cwd, 'atris', 'wiki', 'concepts');
777
+ const ingestDir = path.join(cwd, 'atris', 'context', '_ingest');
778
+ const teamDir = path.join(cwd, 'atris', 'team');
779
+ const teamStartPath = path.join(teamDir, 'START_HERE.md');
780
+ const stateDir = path.join(cwd, '.atris', 'state');
781
+ const rootAgentAdapterNames = ['AGENTS.md', 'CLAUDE.md', 'GEMINI.md'];
782
+ const rootAgentAdapters = rootAgentAdapterNames.filter(name => fs.existsSync(path.join(cwd, name)));
783
+ const scaffold = {
784
+ map: fs.existsSync(path.join(cwd, 'atris', 'MAP.md')),
785
+ todo: fs.existsSync(path.join(cwd, 'atris', 'TODO.md')),
786
+ persona: fs.existsSync(path.join(cwd, 'atris', 'PERSONA.md')),
787
+ runtime: fs.existsSync(path.join(stateDir, 'runtime.json')),
788
+ sync: fs.existsSync(path.join(stateDir, '_sync.json')),
789
+ };
790
+ const starterBrief = latestMatchingFile(briefsDir, name => /starter-brief\.md$/i.test(name));
791
+ const expectedFirstLoop = `${bizMeta.slug || slugifyName(bizMeta.name)}-first-loop.md`;
792
+ const firstLoop = latestMatchingFile(conceptsDir, name => name === expectedFirstLoop);
793
+ const onePager = latestMatchingFile(reportsDir, name => /one-pager|cheat-sheet|onboarding/i.test(name));
794
+ const reports = countFiles(reportsDir, name => /\.(md|json)$/i.test(name));
795
+ const teamMembers = countFiles(teamDir, name => !name.startsWith('.') && fs.existsSync(path.join(teamDir, name, 'MEMBER.md')));
796
+ const events = countJsonlRows(path.join(stateDir, 'events.jsonl'));
797
+ const episodes = countJsonlRows(path.join(stateDir, 'episodes.jsonl'));
798
+ const scorecards = countJsonlRows(path.join(stateDir, 'scorecards.jsonl'));
799
+ const missing = [];
800
+ if (!scaffold.map || !scaffold.todo || !scaffold.persona) missing.push('canonical Atris scaffold');
801
+ if (!scaffold.runtime) missing.push('runtime receipt');
802
+ if (!scaffold.sync) missing.push('sync state');
803
+ if (!starterBrief) missing.push('starter brief');
804
+ if (!firstLoop) missing.push('first loop');
805
+ if (!onePager) missing.push('operator one-pager');
806
+ if (teamMembers < 1) missing.push('team member lanes');
807
+ if (!fs.existsSync(teamStartPath)) missing.push('team start guide');
808
+ if (rootAgentAdapters.length < rootAgentAdapterNames.length) missing.push('root agent adapters');
809
+ if (scorecards < 1 && events < 1 && episodes < 1) missing.push('first proof recap');
810
+
811
+ return {
812
+ bizMeta,
813
+ scaffold,
814
+ starterBrief: rel(cwd, starterBrief),
815
+ firstLoop: rel(cwd, firstLoop),
816
+ onePager: rel(cwd, onePager),
817
+ teamStart: rel(cwd, fs.existsSync(teamStartPath) ? teamStartPath : null),
818
+ rootAgentAdapters,
819
+ missingRootAgentAdapters: rootAgentAdapterNames.filter(name => !rootAgentAdapters.includes(name)),
820
+ remoteReady,
821
+ os: collectBusinessOperatingState(cwd),
822
+ ingestPacks: countFiles(ingestDir, name => !name.startsWith('.')),
823
+ reports,
824
+ teamMembers,
825
+ proof: { events, episodes, scorecards },
826
+ ready: missing.length === 0,
827
+ missing,
828
+ };
829
+ }
830
+
831
+ function renderBusinessOsLines(os = {}, prefix = '- ') {
832
+ const tasks = os.tasks || {};
833
+ const missions = os.missions || {};
834
+ const team = os.team || {};
835
+ const xp = os.xp || {};
836
+ const loop = os.loop || {};
837
+ return [
838
+ `${prefix}Tasks: ${tasks.open || 0} open, ${tasks.claimed || 0} claimed, ${tasks.review || 0} review (${tasks.certifiedReview || 0} certified), ${tasks.blocked || 0} blocked`,
839
+ `${prefix}Missions: ${missions.active || 0} active, ${missions.running || 0} running, ${missions.alwaysOn || 0} always-on, ${missions.stale || 0} stale/no-verifier`,
840
+ `${prefix}Team goals: ${team.members || 0} member lanes, ${team.activeGoalMembers || 0} with active goals`,
841
+ `${prefix}${xp.metric || 'AgentXP'}: ${xp.total || 0} total, ${xp.today || 0} today, ${xp.receipts || 0} receipts, integrity ${xp.integrity || 'unknown'}`,
842
+ `${prefix}Loop: ${loop.ticks || 0} mission ticks; Codex goal ${loop.codexGoal || 'none'}`,
843
+ `${prefix}XP gate: proof can move to Review; XP lands only after human accept`,
844
+ ];
845
+ }
846
+
847
+ function shellDoubleQuote(value) {
848
+ return `"${String(value || '').replace(/(["\\$`])/g, '\\$1')}"`;
849
+ }
850
+
851
+ function renderBusinessMissionBootstrapLines(bizMeta = {}, prefix = '') {
852
+ const missionTitle = shellDoubleQuote(`Run the first useful loop for ${bizMeta.name || bizMeta.slug || 'this business'}`);
853
+ return [
854
+ `${prefix}atris mission status --status active --json`,
855
+ `${prefix}# If no active mission exists:`,
856
+ `${prefix}atris mission start ${missionTitle} --owner operator --runner codex_goal --lane business --verify "atris business check" --stop "first proof recap recorded"`,
857
+ `${prefix}atris member goal-from-mission operator`,
858
+ ];
859
+ }
860
+
861
+ function renderBusinessMissingAction(missing) {
862
+ if (!missing) return 'Start from the first loop, ship one small artifact, then record the recap.';
863
+ if (missing === 'root agent adapters') {
864
+ return 'Run `atris update` to restore root AGENTS.md, CLAUDE.md, and GEMINI.md adapters.';
865
+ }
866
+ return `Add ${missing}.`;
867
+ }
868
+
869
+ function renderBusinessCreatedNextSteps(bizMeta = {}, workspaceRoot = '.') {
870
+ const slug = bizMeta.slug || slugifyName(bizMeta.name) || 'business';
871
+ const lines = [
872
+ ' Atris: seeded local computer + operator + validator',
873
+ '',
874
+ ' Start here:',
875
+ ` cd ${workspaceRoot}`,
876
+ ' atris',
877
+ ' atris business start',
878
+ ' atris radar',
879
+ ' atris task next',
880
+ ' atris member activate operator',
881
+ ...renderBusinessMissionBootstrapLines(bizMeta, ' '),
882
+ '',
883
+ ' First loop:',
884
+ ' atris business onboard --website <url> --contact "Name" --note "what they do"',
885
+ ' atris do',
886
+ ' atris business record atris/reports/<recap>.md --outcome mixed --metric "operator speed"',
887
+ ' atris business share --write',
888
+ '',
889
+ ' Sync when ready:',
890
+ ` atris align ${slug} --fix`,
891
+ ];
892
+ return lines.join('\n');
893
+ }
894
+
895
+ function renderBusinessShareHandoff(state, options = {}) {
896
+ const { bizMeta } = state;
897
+ const role = options.role || 'collaborator';
898
+ const person = options.name || role;
899
+ const workspacePath = options.cwd || process.cwd();
900
+ const firstMissing = state.missing[0] || null;
901
+ const lines = [
902
+ `# ${bizMeta.name} Share Handoff`,
903
+ '',
904
+ `For: ${person}${options.email ? ` <${options.email}>` : ''}`,
905
+ `Role: ${role}`,
906
+ `Business: ${bizMeta.name} (${bizMeta.slug})`,
907
+ `Business ID: ${bizMeta.business_id || 'local-only'}`,
908
+ `Workspace ID: ${bizMeta.workspace_id || 'local-only'}`,
909
+ `Local path: ${workspacePath}`,
910
+ `Ready to share: ${state.ready ? 'yes' : 'no'}`,
911
+ `Remote pull: ${state.remoteReady ? 'available' : 'local-only'}`,
912
+ '',
913
+ '## Get The Workspace',
914
+ '',
915
+ 'If this folder is already on your machine:',
916
+ '',
917
+ '```bash',
918
+ `cd ${workspacePath}`,
919
+ 'atris business start',
920
+ '```',
921
+ '',
922
+ state.remoteReady ? 'If you need to pull it first:' : 'Remote pull is not available yet:',
923
+ '',
924
+ ...(state.remoteReady ? [
925
+ '```bash',
926
+ `atris pull ${bizMeta.slug}`,
927
+ `cd ${bizMeta.slug}`,
928
+ 'atris business start',
929
+ '```',
930
+ ] : [
931
+ '- This workspace is local-only because it is missing a cloud business ID or workspace ID.',
932
+ '- Share the folder directly, or create/pull the cloud business workspace before sending this handoff.',
933
+ ]),
934
+ '',
935
+ '## Start Here',
936
+ '',
937
+ '```bash',
938
+ `cd ${workspacePath}`,
939
+ 'atris',
940
+ 'atris business start',
941
+ 'atris radar',
942
+ 'atris task next',
943
+ 'atris member activate operator',
944
+ ...renderBusinessMissionBootstrapLines(bizMeta),
945
+ '```',
946
+ '',
947
+ '## What To Read',
948
+ '',
949
+ `- Map: atris/MAP.md`,
950
+ `- Queue: atris/TODO.md`,
951
+ `- Agent adapters: ${state.rootAgentAdapters && state.rootAgentAdapters.length ? state.rootAgentAdapters.join(', ') : 'missing'}`,
952
+ `- Team start: ${state.teamStart || 'missing'}`,
953
+ `- Starter brief: ${state.starterBrief || 'missing'}`,
954
+ `- First loop: ${state.firstLoop || 'missing'}`,
955
+ `- Operator one-pager: ${state.onePager || 'missing'}`,
956
+ '',
957
+ '## First Useful Loop',
958
+ '',
959
+ '```bash',
960
+ 'atris business onboard --website <url> --contact "Name" --note "what changed"',
961
+ 'atris pull --dry-run',
962
+ 'atris task next',
963
+ ...renderBusinessMissionBootstrapLines(bizMeta),
964
+ 'atris do',
965
+ 'atris business record atris/reports/<recap>.md --outcome mixed --metric "operator speed"',
966
+ 'atris business share --write',
967
+ 'atris align --fix',
968
+ '```',
969
+ '',
970
+ '## Proof State',
971
+ '',
972
+ `- Team lanes: ${state.teamMembers}`,
973
+ `- Onboarding packs: ${state.ingestPacks}`,
974
+ `- Reports: ${state.reports}`,
975
+ `- Events: ${state.proof.events}`,
976
+ `- Episodes: ${state.proof.episodes}`,
977
+ `- Scorecards: ${state.proof.scorecards}`,
978
+ '',
979
+ '## Atris OS State',
980
+ '',
981
+ ...renderBusinessOsLines(state.os),
982
+ '',
983
+ 'Useful commands:',
984
+ '',
985
+ '```bash',
986
+ 'atris radar',
987
+ 'atris task next',
988
+ ...renderBusinessMissionBootstrapLines(bizMeta),
989
+ 'atris xp status --local --json',
990
+ '```',
991
+ '',
992
+ '## Next Action',
993
+ '',
994
+ `- ${renderBusinessMissingAction(firstMissing)}`,
995
+ '',
996
+ '## Guardrails',
997
+ '',
998
+ '- Do not mix another business into this workspace.',
999
+ '- No external sends without operator approval.',
1000
+ '- No XP until proof is accepted by a human.',
1001
+ '',
1002
+ ];
1003
+ return lines.join('\n');
1004
+ }
1005
+
1006
+ function renderBusinessStartCard(state, options = {}) {
1007
+ const { bizMeta } = state;
1008
+ const workspacePath = options.cwd || process.cwd();
1009
+ const firstMissing = state.missing[0] || null;
1010
+ const lines = [
1011
+ `${bizMeta.name} collaborator start`,
1012
+ '',
1013
+ `Business: ${bizMeta.name} (${bizMeta.slug})`,
1014
+ `Workspace: ${workspacePath}`,
1015
+ `Ready: ${state.ready ? 'yes' : 'no'}`,
1016
+ `Remote pull: ${state.remoteReady ? 'available' : 'local-only'}`,
1017
+ '',
1018
+ 'Read:',
1019
+ `- atris/MAP.md`,
1020
+ `- atris/TODO.md`,
1021
+ `- ${(state.rootAgentAdapters && state.rootAgentAdapters.length === 3) ? state.rootAgentAdapters.join(', ') : 'missing root agent adapters'}`,
1022
+ `- ${state.teamStart || 'missing team start guide'}`,
1023
+ `- ${state.starterBrief || 'missing starter brief'}`,
1024
+ `- ${state.firstLoop || 'missing first loop'}`,
1025
+ `- ${state.onePager || 'missing operator one-pager'}`,
1026
+ '',
1027
+ 'Run:',
1028
+ state.remoteReady ? ' atris pull --dry-run' : ' # local-only: no cloud pull is available yet',
1029
+ ' atris',
1030
+ ' atris business start',
1031
+ ' atris radar',
1032
+ ' atris task next',
1033
+ ' atris member activate operator',
1034
+ ...renderBusinessMissionBootstrapLines(bizMeta, ' '),
1035
+ ' atris do',
1036
+ ' atris business record atris/reports/<recap>.md --outcome mixed --metric "operator speed"',
1037
+ ' atris business share --write',
1038
+ ' atris align --fix',
1039
+ '',
1040
+ 'Proof:',
1041
+ `- team lanes: ${state.teamMembers}`,
1042
+ `- onboarding packs: ${state.ingestPacks}`,
1043
+ `- scorecards: ${state.proof.scorecards}`,
1044
+ '',
1045
+ 'OS:',
1046
+ ...renderBusinessOsLines(state.os, ' '),
1047
+ '',
1048
+ 'Next:',
1049
+ `- ${state.ready ? 'Work the first loop, record the recap, then rewrite the share handoff.' : renderBusinessMissingAction(firstMissing)}`,
1050
+ '',
1051
+ ];
1052
+ return lines.join('\n');
1053
+ }
1054
+
1055
+ async function startBusinessWorkspace(...args) {
1056
+ const options = parseShareFlags(args, process.cwd());
1057
+ const state = collectBusinessShareState(options.cwd);
1058
+ const content = renderBusinessStartCard(state, options);
1059
+ console.log(content);
1060
+ return state;
1061
+ }
1062
+
1063
+ function defaultSharePath(cwd, role) {
1064
+ const stamp = new Date().toISOString().slice(0, 10);
1065
+ const roleSlug = slugifyName(role || 'collaborator');
1066
+ return path.join(cwd, 'atris', 'reports', `${stamp}-share-${roleSlug}.md`);
1067
+ }
1068
+
1069
+ async function shareBusinessWorkspace(...args) {
1070
+ const options = parseShareFlags(args, process.cwd());
1071
+ const state = collectBusinessShareState(options.cwd);
1072
+ const content = renderBusinessShareHandoff(state, options);
1073
+ console.log(content);
1074
+
1075
+ if (options.write || options.out) {
1076
+ const outputPath = options.out
1077
+ ? (path.isAbsolute(options.out) ? options.out : path.join(options.cwd, options.out))
1078
+ : defaultSharePath(options.cwd, options.role);
1079
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
1080
+ fs.writeFileSync(outputPath, content, 'utf8');
1081
+ console.log(`Wrote ${path.relative(options.cwd, outputPath).replace(/\\/g, '/')}`);
1082
+ return outputPath;
1083
+ }
1084
+
1085
+ return state;
1086
+ }
1087
+
570
1088
  function slugifyName(value) {
571
1089
  return String(value || '')
572
1090
  .toLowerCase()
@@ -955,6 +1473,7 @@ ${stagedSources.length > 0 ? `- Local evidence files: ${stagedSources.length}` :
955
1473
  '- Open the starter brief and correct anything false.',
956
1474
  `- ${starterAction.action}`,
957
1475
  '- After the first real run, write a recap and record it with `atris business record ...`.',
1476
+ '- Before sharing the workspace, run `atris business share --write` and send the handoff.',
958
1477
  ].filter(Boolean).join('\n') + '\n';
959
1478
  fs.writeFileSync(cheatSheetPath, operatorSummary, 'utf8');
960
1479
  fs.writeFileSync(onePagerPath, operatorSummary.replace('# ', '# One Pager — '), 'utf8');
@@ -972,6 +1491,7 @@ ${stagedSources.length > 0 ? `- Local evidence files: ${stagedSources.length}` :
972
1491
  }
973
1492
  fs.writeFileSync(todoPath, todoContent, 'utf8');
974
1493
  }
1494
+ const taskSeed = seedBusinessStarterTask(cwd, todoPath, starterAction);
975
1495
 
976
1496
  writeWikiStatus(cwd, {
977
1497
  health: `starter onboarding compiled from ${intakeRel}`,
@@ -997,6 +1517,12 @@ ${stagedSources.length > 0 ? `- Local evidence files: ${stagedSources.length}` :
997
1517
  console.log(` Cheat sheet: ${path.relative(cwd, cheatSheetPath).replace(/\\/g, '/')}`);
998
1518
  console.log(` One pager: ${path.relative(cwd, onePagerPath).replace(/\\/g, '/')}`);
999
1519
  console.log(` Next action: ${starterAction.title}`);
1520
+ if (taskSeed.ok) {
1521
+ console.log(` Task plane: ${taskSeed.inserted ? 'seeded starter task' : 'starter task already present'}`);
1522
+ } else {
1523
+ console.log(` Task plane: TODO fallback only (${taskSeed.error})`);
1524
+ }
1525
+ console.log(' Share: atris business share --write');
1000
1526
  console.log('');
1001
1527
  }
1002
1528
 
@@ -1839,15 +2365,7 @@ async function createBusinessInternal(name, flags = [], mode = 'auto') {
1839
2365
  console.log(` Dashboard: https://atris.ai/dashboard/gm/${biz.id}`);
1840
2366
  if (shouldCreateCanonicalWorkspace) {
1841
2367
  const workspaceRoot = resolveWorkspaceRoot(biz.slug, options);
1842
- console.log(' Atris: seeded local computer + operator + validator');
1843
- console.log('');
1844
- console.log(' Start here:');
1845
- console.log(` cd ${workspaceRoot}`);
1846
- console.log(' atris member activate operator');
1847
- console.log(' atris business onboard --website <url> --contact "Name" --note "what they do"');
1848
- console.log('');
1849
- console.log(' Sync when ready:');
1850
- console.log(` atris align ${biz.slug} --fix`);
2368
+ console.log(renderBusinessCreatedNextSteps(biz, workspaceRoot));
1851
2369
  }
1852
2370
  console.log('');
1853
2371
  }
@@ -2279,7 +2797,7 @@ function findAtrisDir() {
2279
2797
  async function quickstart() {
2280
2798
  console.log(`
2281
2799
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2282
- Start a Business in 3 Commands
2800
+ Start a Business With An Operating Loop
2283
2801
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2284
2802
 
2285
2803
  1. Create:
@@ -2288,18 +2806,36 @@ async function quickstart() {
2288
2806
  2. Open the local workspace:
2289
2807
  cd ~/arena/atris-business/my-company
2290
2808
 
2291
- 3. Seed onboarding context:
2292
- atris business onboard --website https://example.com --contact "Founder Name" --note "what they do"
2809
+ 3. See what Atris knows:
2810
+ atris
2811
+ atris business start
2812
+ atris radar
2293
2813
 
2294
- 4. Push local state to cloud:
2295
- atris align --fix
2814
+ 4. Claim the first real action:
2815
+ atris task next
2816
+ atris member activate operator
2817
+ atris mission status --status active --json
2818
+
2819
+ 5. If no active mission exists:
2820
+ atris mission start "Run the first useful loop for My Company" --owner operator --runner codex_goal --lane business --verify "atris business check" --stop "first proof recap recorded"
2821
+ atris member goal-from-mission operator
2296
2822
 
2297
- Then open atris/TODO.md and work the starter queue:
2298
- define the first loop -> add named humans -> write the first recap
2823
+ 6. Seed onboarding context:
2824
+ atris business onboard --website https://example.com --contact "Founder Name" --note "what they do"
2299
2825
 
2300
- After the first recap lands:
2826
+ 7. Do one useful loop:
2827
+ atris do
2301
2828
  atris business record atris/reports/YYYY-MM-DD-your-recap.md --outcome mixed --metric "operator speed"
2302
2829
 
2830
+ 8. Write the handoff before sharing:
2831
+ atris business share --write
2832
+
2833
+ 9. Push local state to cloud:
2834
+ atris align --fix
2835
+
2836
+ Repeat:
2837
+ atris radar -> atris task next -> atris do -> record -> share
2838
+
2303
2839
  Optional:
2304
2840
  atris business connect slack --business my-company
2305
2841
  atris business connect github --business my-company
@@ -2336,6 +2872,9 @@ function printBusinessHelp() {
2336
2872
  console.log(' notify <mode> Set notification mode (digest/silent/push)');
2337
2873
  console.log(' deploy <slug> Push local business to cloud');
2338
2874
  console.log(' onboard Seed brief, person, first loop, safe next action, and one-pager from sparse input');
2875
+ console.log(' start Check a received business workspace and show the first loop');
2876
+ console.log(' check Alias for start');
2877
+ console.log(' share Print/write a collaborator handoff for this business workspace');
2339
2878
  console.log(' record <report> Append recap state into events, episodes, and scorecards');
2340
2879
  console.log(' remove <slug> Unregister locally');
2341
2880
  console.log('');
@@ -2421,8 +2960,16 @@ async function businessCommand(subcommand, ...args) {
2421
2960
  case 'onboard':
2422
2961
  await onboardBusiness(...args);
2423
2962
  break;
2424
- case 'quickstart':
2425
2963
  case 'start':
2964
+ case 'check':
2965
+ case 'ready':
2966
+ await startBusinessWorkspace(...args);
2967
+ break;
2968
+ case 'share':
2969
+ case 'handoff':
2970
+ await shareBusinessWorkspace(...args);
2971
+ break;
2972
+ case 'quickstart':
2426
2973
  case 'guide':
2427
2974
  await quickstart();
2428
2975
  break;
@@ -2449,5 +2996,11 @@ module.exports = {
2449
2996
  createCanonicalBusinessWorkspace,
2450
2997
  initBusinessWorkspace,
2451
2998
  onboardBusiness,
2999
+ startBusinessWorkspace,
3000
+ shareBusinessWorkspace,
3001
+ collectBusinessShareState,
3002
+ renderBusinessCreatedNextSteps,
3003
+ renderBusinessShareHandoff,
3004
+ renderBusinessStartCard,
2452
3005
  recordBusinessRun,
2453
3006
  };