atris 3.15.46 → 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.
- package/AGENTS.md +2 -2
- package/atris/skills/atris/SKILL.md +1 -1
- package/bin/atris.js +10 -4
- package/commands/business.js +574 -21
- package/commands/mission.js +16 -1
- package/commands/radar.js +546 -0
- package/commands/sync.js +101 -1
- package/lib/task-db.js +29 -3
- package/package.json +2 -1
- package/templates/business-starter/MAP.md +1 -0
- package/templates/business-starter/team/README.md +3 -0
- package/templates/business-starter/team/START_HERE.md +45 -0
package/commands/business.js
CHANGED
|
@@ -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(
|
|
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
|
|
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.
|
|
2292
|
-
atris
|
|
2809
|
+
3. See what Atris knows:
|
|
2810
|
+
atris
|
|
2811
|
+
atris business start
|
|
2812
|
+
atris radar
|
|
2293
2813
|
|
|
2294
|
-
4.
|
|
2295
|
-
atris
|
|
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
|
-
|
|
2298
|
-
|
|
2823
|
+
6. Seed onboarding context:
|
|
2824
|
+
atris business onboard --website https://example.com --contact "Founder Name" --note "what they do"
|
|
2299
2825
|
|
|
2300
|
-
|
|
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
|
};
|