atris 3.58.7 → 3.58.8

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 (67) hide show
  1. package/GETTING_STARTED.md +2 -2
  2. package/PERSONA.md +11 -11
  3. package/README.md +1 -1
  4. package/atris/GEMINI.md +7 -6
  5. package/atris/PERSONA.md +4 -4
  6. package/atris/features/README.md +77 -6
  7. package/atris/skills/README.md +44 -15
  8. package/atris/skills/aeo/SKILL.md +10 -10
  9. package/atris/skills/atris/SKILL.md +1 -1
  10. package/atris/skills/engines/SKILL.md +42 -50
  11. package/atris/skills/meta/SKILL.md +4 -4
  12. package/bin/atris.js +5 -6
  13. package/commands/aeo.js +9 -12
  14. package/commands/analytics.js +8 -11
  15. package/commands/autopilot.js +4 -3
  16. package/commands/avail.js +1 -1
  17. package/commands/brain.js +3 -1
  18. package/commands/business.js +13 -8
  19. package/commands/close.js +2 -1
  20. package/commands/computer.js +15 -10
  21. package/commands/context-sync.js +3 -3
  22. package/commands/doc-health.js +50 -2
  23. package/commands/experiments.js +13 -4
  24. package/commands/improve.js +1 -1
  25. package/commands/integrations.js +2 -5
  26. package/commands/learn.js +1 -1
  27. package/commands/member.js +174 -47
  28. package/commands/mission.js +57 -11
  29. package/commands/pack.js +2 -2
  30. package/commands/probe.js +11 -5
  31. package/commands/pull.js +1 -1
  32. package/commands/push.js +1 -1
  33. package/commands/rainmaker.js +3 -16
  34. package/commands/spaceship.js +1 -1
  35. package/commands/stream.js +27 -2
  36. package/commands/study.js +15 -9
  37. package/commands/sync.js +1 -1
  38. package/commands/task.js +13 -2
  39. package/commands/workflow.js +6 -1
  40. package/lib/codex-flight.js +28 -5
  41. package/lib/fleet.js +4 -1
  42. package/lib/improve-vitals-html.js +3 -2
  43. package/lib/lesson-contradiction.js +2 -2
  44. package/lib/map-refs.js +183 -0
  45. package/lib/mission-room.js +4 -4
  46. package/lib/noninteractive.js +15 -4
  47. package/lib/policy-lessons.js +1 -1
  48. package/lib/receipt-block.js +137 -28
  49. package/lib/runner-command.js +1 -1
  50. package/lib/scorecard.js +4 -4
  51. package/lib/sleep-sync.js +11 -0
  52. package/lib/state-detection.js +28 -9
  53. package/lib/task-db.js +3 -2
  54. package/lib/todo-fallback.js +1 -1
  55. package/lib/wish-delegate.js +19 -5
  56. package/mcp/atris-mcp/index.mjs +77 -24
  57. package/package.json +2 -5
  58. package/scripts/det/det.js +2 -2
  59. package/scripts/det/pr-description.js +2 -2
  60. package/scripts/det/test.js +2 -2
  61. package/utils/api.js +1 -1
  62. package/utils/auth.js +17 -7
  63. package/utils/backend-root.js +31 -0
  64. package/utils/config.js +5 -4
  65. package/utils/owner-identity.js +61 -0
  66. package/atris/policies/LESSONS.md +0 -13
  67. package/atris/skills/autopilot/atris-autopilot.md +0 -71
@@ -4,10 +4,12 @@ const crypto = require('crypto');
4
4
  const os = require('os');
5
5
  const { execFileSync, spawnSync } = require('child_process');
6
6
  const { loadCredentials } = require('../utils/auth');
7
+ const { resolveBackendRoot } = require('../utils/backend-root');
7
8
  const { apiRequestJson } = require('../utils/api');
8
9
  const { runAliveTick } = require('../lib/member-alive');
9
10
  const { defaultObjectiveRunner } = require('../lib/default-runner');
10
11
  const { readJson, writeJson } = require('../lib/json-file');
12
+ const { sleepSync } = require('../lib/sleep-sync');
11
13
  const { hasFlag, readFlag, readNumberFlag } = require('../lib/arg-parser');
12
14
  const { ensureMemberBundle, memberBundlePresent } = require('../lib/member-scaffold');
13
15
  const { memberProcessPrompt, MEMBER_PROCESS_PATH } = require('../lib/member-context');
@@ -686,6 +688,15 @@ const MEMBER_RUN_START_VALUE_FLAGS = [
686
688
  '--mission-id',
687
689
  '--owner',
688
690
  '--runner',
691
+ '--engine',
692
+ '--budget',
693
+ '--repo',
694
+ '--native-goal-status',
695
+ '--native-goal-objective',
696
+ '--visible-goal-status',
697
+ '--visible-goal-objective',
698
+ '--max-idle-ticks',
699
+ '--slots',
689
700
  '--lane',
690
701
  '--cadence',
691
702
  '--verify',
@@ -717,10 +728,49 @@ const MEMBER_RUN_START_BOOLEAN_FLAGS = [
717
728
  '--stop-when-done',
718
729
  '--choose-work',
719
730
  '--auto',
731
+ '--headless',
732
+ '--self-drive',
733
+ '--no-claude',
734
+ '--complete-on-pass',
735
+ '--no-drain',
736
+ '--create-next',
737
+ '--preflight',
738
+ '--no-preflight',
739
+ '--room-preflight',
740
+ '--no-room-preflight',
741
+ '--room-auto-run',
742
+ '--no-room-auto-run',
743
+ '--manual-ack',
744
+ '--allow-native-goal-supersede',
745
+ '--supersede-paused-native-goal',
746
+ '--take-goal-slot',
747
+ '--due',
748
+ '--detach',
749
+ '--fleet',
750
+ '--dry-run',
751
+ '--land',
752
+ '--cloud',
720
753
  ];
721
754
 
755
+ function memberRunMissionWordIndices(args = []) {
756
+ const values = new Set(MEMBER_RUN_START_VALUE_FLAGS);
757
+ const booleans = new Set(['--full', '--verbose', ...MEMBER_RUN_START_BOOLEAN_FLAGS]);
758
+ const indices = [];
759
+ for (let i = 0; i < args.length; i += 1) {
760
+ const arg = String(args[i]);
761
+ const key = arg.includes('=') ? arg.slice(0, arg.indexOf('=')) : arg;
762
+ if (booleans.has(key)) continue;
763
+ if (values.has(key)) {
764
+ if (!arg.includes('=') && args[i + 1] && !String(args[i + 1]).startsWith('--')) i += 1;
765
+ continue;
766
+ }
767
+ indices.push(i);
768
+ }
769
+ return indices;
770
+ }
771
+
722
772
  function memberRunMissionText(args = []) {
723
- return stripKnownFlags(args, MEMBER_RUN_START_VALUE_FLAGS, MEMBER_RUN_START_BOOLEAN_FLAGS).join(' ').trim();
773
+ return memberRunMissionWordIndices(args).map((index) => args[index]).join(' ').trim();
724
774
  }
725
775
 
726
776
  function cleanMemberRunPhrase(value) {
@@ -834,10 +884,8 @@ function buildMemberRunStartArgs(owner, missionText, args = [], cwd = process.cw
834
884
  pushFlagValue(startArgs, args, '--minutes');
835
885
  pushFlagValue(startArgs, args, '--hours');
836
886
  pushFlagValue(startArgs, args, '--base');
837
- // A member-run mission is driven immediately by its runner, not parked on
838
- // the queue, so the mission-start verifier gate (built for parked planning
839
- // wishes) would only kill autonomous legs like autopilot's "member chooses
840
- // useful work". When the caller named no verifier, opt out explicitly.
887
+ // Member runs choose and drive work immediately, so an absent check must not
888
+ // leave autonomous callers with a mission that never ran.
841
889
  if (hasFlag(args, '--no-verify') || !readFlag(args, '--verify', '')) {
842
890
  startArgs.push('--no-verify');
843
891
  }
@@ -853,7 +901,7 @@ function buildMemberRunStartArgs(owner, missionText, args = [], cwd = process.cw
853
901
  if (!hasFlag(args, '--shared-checkout') && !hasFlag(args, '--no-worktree') && insideGitRepo(cwd)) {
854
902
  startArgs.push('--worktree');
855
903
  }
856
- if (hasFlag(args, '--json')) startArgs.push('--json');
904
+ startArgs.push('--json');
857
905
  return startArgs;
858
906
  }
859
907
 
@@ -871,15 +919,52 @@ function startMemberRunMission(name, missionText, args = []) {
871
919
  const paths = requireMemberDir(name);
872
920
  const owner = paths.storageName || name;
873
921
  const startArgs = buildMemberRunStartArgs(owner, missionText, args);
922
+ return memberRunCli(startArgs, process.cwd());
923
+ }
874
924
 
925
+ function memberRunCli(args, cwd, { live = false, missionId = '' } = {}) {
875
926
  const cliPath = path.join(__dirname, '..', 'bin', 'atris.js');
927
+ const runsDir = path.join(cwd, 'atris', 'runs');
928
+ const previousReceipts = live && fs.existsSync(runsDir) ? new Set(fs.readdirSync(runsDir)) : new Set();
929
+ const result = spawnSync(process.execPath, [cliPath, ...args], {
930
+ cwd,
931
+ encoding: 'utf8',
932
+ maxBuffer: 64 * 1024 * 1024,
933
+ stdio: live ? 'inherit' : ['ignore', 'pipe', 'pipe'],
934
+ });
935
+ if (result.error) throw result.error;
936
+ if (live) {
937
+ const receiptName = fs.existsSync(runsDir)
938
+ ? fs.readdirSync(runsDir).filter((name) => !previousReceipts.has(name) && name.startsWith(`mission-${missionId}-`) && name.endsWith('.json')).sort().at(-1)
939
+ : null;
940
+ const receipt = receiptName ? readJson(path.join(runsDir, receiptName), null) : null;
941
+ return { status: result.status, payload: receipt?.result?.kind === 'mission_run_summary'
942
+ ? { ran_ticks: receipt.result.ran_ticks } : null, stderr: '', errorReported: result.status !== 0 };
943
+ }
944
+ let payload = null;
876
945
  try {
877
- execFileSync(process.execPath, [cliPath, ...startArgs], {
878
- cwd: process.cwd(),
879
- stdio: 'inherit',
880
- });
946
+ payload = JSON.parse(result.stdout);
881
947
  } catch (error) {
882
- process.exitCode = Number(error?.status) || 1;
948
+ if (result.status === 0) throw new Error(`member run: mission command returned invalid json: ${error.message}`);
949
+ }
950
+ return { status: result.status, payload, stderr: String(result.stderr || '').trim() };
951
+ }
952
+
953
+ function memberRunConfiguredVerifier(paths) {
954
+ const frontmatter = parseFrontmatter(fs.readFileSync(paths.memberFile, 'utf8')) || {};
955
+ const check = frontmatter.verify || frontmatter.verifier;
956
+ return typeof check === 'string' ? check.trim() : '';
957
+ }
958
+
959
+ function printMemberRunResult(result, asJson) {
960
+ if (asJson) {
961
+ console.log(JSON.stringify(result));
962
+ } else if (result.started) {
963
+ console.log(`started ${result.mission_id} (${result.state})`);
964
+ }
965
+ if (result.ok === false || (!result.started && result.ok !== true)) {
966
+ if (!result.errorReported) console.error(result.error);
967
+ process.exitCode = 1;
883
968
  }
884
969
  }
885
970
 
@@ -967,48 +1052,96 @@ function memberPing(name, ...args) {
967
1052
 
968
1053
  function memberRun(name, ...args) {
969
1054
  if (!name || name === '--help' || name === '-h' || hasFlag(args, '--help') || hasFlag(args, '-h')) {
970
- console.log('Usage: atris member run <name> ["mission text"] [--minutes N|--hours N] [--json]');
971
- console.log('New mission: atris member run growth "Improve onboarding proof" --minutes 30 --json');
972
- console.log('Choose work: atris member run growth --industry logistics --value "reduce support time" --minutes 30 --json');
973
- console.log('Existing mission: atris member run growth --mission <mission-id> --max-ticks 1 --json');
974
- console.log('Meaning: if mission text is omitted, the member chooses one useful bounded task from Atris state.');
975
- console.log('Truth: useful work must improve revenue, reliability, speed, security, clarity, or trust, then show plain proof.');
976
- console.log('Isolation: new missions use --worktree by default; add --shared-checkout to stay here.');
1055
+ console.log('usage: atris member run <name> ["mission text"] [--verify "<cmd>"] [--minutes N|--hours N] [--json]');
1056
+ console.log('new mission: atris member run growth "improve onboarding proof" --verify "npm test" --minutes 30 --json');
1057
+ console.log('choose work: atris member run growth --verify "npm test" --industry logistics --minutes 30 --json');
1058
+ console.log('existing mission: atris member run growth --mission <mission-id> --max-ticks 1 --json');
1059
+ console.log('meaning: if mission text is omitted, the member chooses one useful bounded task from atris state.');
1060
+ console.log('truth: useful work must improve revenue, reliability, speed, security, clarity, or trust, then show plain proof.');
1061
+ console.log('isolation: new missions use --worktree by default; add --shared-checkout to stay here.');
1062
+ console.log('check: --verify or a verify field in the member file adds a mission check.');
977
1063
  return;
978
1064
  }
979
1065
 
1066
+ const asJson = hasFlag(args, '--json');
1067
+ const paths = requireMemberDir(name);
1068
+ const requestedVerifier = readFlag(args, '--verify', '') || memberRunConfiguredVerifier(paths);
980
1069
  const missionText = memberRunMissionText(args);
981
1070
  const hasMissionOverride = Boolean(readFlag(args, '--mission', '') || readFlag(args, '--mission-id', ''));
1071
+ let mission = null;
1072
+ let startedNewMission = false;
1073
+ let nodeModulesLinked = false;
1074
+ let nodeModulesSource = null;
982
1075
  if (missionText && !hasMissionOverride) {
983
- startMemberRunMission(name, missionText, args);
984
- return;
1076
+ const start = startMemberRunMission(name, missionText, requestedVerifier ? [...args, '--verify', requestedVerifier] : args);
1077
+ if (start.status !== 0 || !start.payload?.mission) {
1078
+ printMemberRunResult({ started: false, state: null, mission_id: start.payload?.mission?.id || null,
1079
+ error: start.payload?.detail || start.payload?.error || start.stderr || 'mission creation failed' }, asJson);
1080
+ return;
1081
+ }
1082
+ mission = start.payload.mission;
1083
+ startedNewMission = true;
1084
+ nodeModulesLinked = Boolean(start.payload.node_modules_linked);
1085
+ nodeModulesSource = start.payload.node_modules_source || null;
1086
+ } else {
1087
+ const missionId = resolveMemberRunMissionId(name, args);
1088
+ if (!missionId) {
1089
+ const start = startMemberRunMission(name, memberRunAutoMissionText(name, args), requestedVerifier ? [...args, '--verify', requestedVerifier] : args);
1090
+ if (start.status !== 0 || !start.payload?.mission) {
1091
+ printMemberRunResult({ started: false, state: null, mission_id: start.payload?.mission?.id || null,
1092
+ error: start.payload?.detail || start.payload?.error || start.stderr || 'mission creation failed' }, asJson);
1093
+ return;
1094
+ }
1095
+ mission = start.payload.mission;
1096
+ startedNewMission = true;
1097
+ nodeModulesLinked = Boolean(start.payload.node_modules_linked);
1098
+ nodeModulesSource = start.payload.node_modules_source || null;
1099
+ } else {
1100
+ mission = require('./mission').resolveMission(missionId);
1101
+ if (!mission) {
1102
+ printMemberRunResult({ started: false, state: null, mission_id: missionId, error: `mission ${missionId} was not found` }, asJson);
1103
+ return;
1104
+ }
1105
+ }
985
1106
  }
986
1107
 
987
- const missionId = resolveMemberRunMissionId(name, args);
988
- if (!missionId) {
989
- startMemberRunMission(name, memberRunAutoMissionText(name, args), args);
990
- return;
1108
+ if (startedNewMission && !asJson) {
1109
+ if (mission.worktree?.path) console.log(`worktree: ${mission.worktree.path}`);
1110
+ if (nodeModulesLinked) console.log(`linked node_modules from ${nodeModulesSource}`);
1111
+ }
1112
+
1113
+ const cwd = mission.worktree?.path || process.cwd();
1114
+ const verifier = readFlag(args, '--verify', '') || mission.verifier || requestedVerifier;
1115
+ if (verifier && verifier !== mission.verifier) {
1116
+ const changed = memberRunCli(['mission', 'set-verifier', mission.id, verifier, '--json'], cwd);
1117
+ if (changed.status !== 0) {
1118
+ printMemberRunResult({ started: false, state: mission.status, mission_id: mission.id, mission,
1119
+ error: changed.payload?.detail || changed.payload?.error || changed.stderr || 'could not set mission check' }, asJson);
1120
+ return;
1121
+ }
1122
+ mission.verifier = verifier;
991
1123
  }
992
1124
 
993
- const runArgs = stripKnownFlags(args, ['--mission', '--mission-id', '--minutes', '--hours'], ['--worktree', '--shared-checkout', '--no-worktree']);
1125
+ const missionWordIndices = missionText && !hasMissionOverride ? new Set(memberRunMissionWordIndices(args)) : new Set();
1126
+ const executionArgs = args.filter((_, index) => !missionWordIndices.has(index));
1127
+ const runArgs = stripKnownFlags(executionArgs,
1128
+ ['--mission', '--mission-id', '--minutes', '--hours', '--industry', '--domain', '--value', '--outcome', '--truth', '--proof', '--base'],
1129
+ ['--worktree', '--shared-checkout', '--no-worktree', '--choose-work', '--auto']);
994
1130
  const budgetSeconds = memberRunBudgetSeconds(args);
995
- // A timed run is a loop contract, not one tick: keep picking the next useful
996
- // move until the wall clock (--max-wall) spends the budget. Only untimed runs
997
- // default to a single tick.
998
1131
  if (!readFlag(runArgs, '--max-ticks', '')) {
999
1132
  runArgs.push('--max-ticks', budgetSeconds ? String(Math.max(4, Math.ceil(budgetSeconds / 300))) : '1');
1000
1133
  }
1001
1134
  if (!readFlag(runArgs, '--max-wall', '')) runArgs.push('--max-wall', String(budgetSeconds || 900));
1002
-
1003
- const cliPath = path.join(__dirname, '..', 'bin', 'atris.js');
1004
- try {
1005
- execFileSync(process.execPath, [cliPath, 'mission', 'run', missionId, ...runArgs], {
1006
- cwd: process.cwd(),
1007
- stdio: 'inherit',
1008
- });
1009
- } catch (error) {
1010
- process.exitCode = Number(error?.status) || 1;
1011
- }
1135
+ const run = memberRunCli(['mission', 'run', mission.id, ...runArgs], cwd, { live: !asJson, missionId: mission.id });
1136
+ const payload = run.payload || {};
1137
+ const finalMission = payload.mission || require('./mission').resolveMission(mission.id, cwd) || mission;
1138
+ const started = run.status === 0 && payload.ok !== false && Number(payload.ran_ticks) > 0;
1139
+ const noWorkStepExpected = hasFlag(runArgs, '--detach') || hasFlag(runArgs, '--dry-run');
1140
+ const ok = run.status === 0 && payload.ok !== false && (started || noWorkStepExpected);
1141
+ printMemberRunResult({ ...payload, ok, started, state: finalMission.status, mission_id: mission.id,
1142
+ mission: finalMission, node_modules_linked: nodeModulesLinked, node_modules_source: nodeModulesSource,
1143
+ errorReported: run.errorReported,
1144
+ ...(!ok ? { error: payload.detail || payload.error || payload.reason || run.stderr || 'mission did not run a work step' } : {}) }, asJson);
1012
1145
  }
1013
1146
 
1014
1147
  function loadTeamScoreEvidence(scoreJsonPath) {
@@ -1831,8 +1964,8 @@ function problemSignalSources(root = process.cwd()) {
1831
1964
  sources.push(source);
1832
1965
  }
1833
1966
  for (const source of problemSignalFilesForStateDir(path.join(root, '.atris', 'state'))) push(source);
1834
- const siblingBackend = path.resolve(root, '..', 'atrisos-backend');
1835
- if (fs.existsSync(siblingBackend)) {
1967
+ const siblingBackend = resolveBackendRoot(root);
1968
+ if (siblingBackend) {
1836
1969
  for (const source of problemSignalFilesForStateDir(path.join(siblingBackend, '.atris', 'state'), 'backend')) push(source);
1837
1970
  }
1838
1971
  for (const source of configuredProblemSignalSources(root)) push(source);
@@ -4594,12 +4727,6 @@ function printJsonOrText(payload, lines, asJson) {
4594
4727
  for (const line of lines) console.log(line);
4595
4728
  }
4596
4729
 
4597
- function sleepSync(ms) {
4598
- const duration = Math.max(0, Number(ms) || 0);
4599
- if (duration <= 0) return;
4600
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, duration);
4601
- }
4602
-
4603
4730
  function memberGoal(name, ...args) {
4604
4731
  const paths = requireMemberDir(name);
4605
4732
  const asJson = hasFlag(args, '--json');
@@ -9261,7 +9388,7 @@ async function memberCommand(subcommand, ...args) {
9261
9388
  console.log(' atris member goal-from-mission growth --json');
9262
9389
  console.log(' atris member goal-from-score growth --score-json team-score.json --json');
9263
9390
  console.log(' atris member wake growth --json');
9264
- console.log(' atris member run growth "Improve onboarding proof" --minutes 30 --json');
9391
+ console.log(' atris member run growth "improve onboarding proof" --verify "npm test" --minutes 30 --json');
9265
9392
  console.log(' atris member run growth --mission <mission-id> --max-ticks 1 --json');
9266
9393
  console.log(' atris member wake growth --execute --confirm-autonomy-policy');
9267
9394
  console.log(' atris member supervisor recommendations --json');
@@ -13,6 +13,7 @@ const {
13
13
  printCliJson,
14
14
  } = require('../lib/cli-json');
15
15
  const escapeRegExp = require('../lib/escape-regexp');
16
+ const { sleepSync } = require('../lib/sleep-sync');
16
17
  const {
17
18
  appendBriefRecord,
18
19
  stampBriefOutcome,
@@ -3568,8 +3569,8 @@ function readTasteReviewHistory(root, limit = 4) {
3568
3569
 
3569
3570
  function missionTasteMemorySignals(text) {
3570
3571
  const definitions = [
3571
- ['plain_language', 'Keshav asks for plain English', /\b(plain|jargon|feynman|understand|simple|clarify|clarity)\b/i],
3572
- ['proof_gate', 'Keshav wants proof before accept', /\b(proof|receipt|verify|verified|checked|tested|accept|approval)\b/i],
3572
+ ['plain_language', 'the operator asks for plain English', /\b(plain|jargon|feynman|understand|simple|clarify|clarity)\b/i],
3573
+ ['proof_gate', 'the operator wants proof before accept', /\b(proof|receipt|verify|verified|checked|tested|accept|approval)\b/i],
3573
3574
  ['runway_revenue', 'runway pushes user or revenue work', /\b(runway|revenue|cash|user|users|customer|demo|adoption|retention)\b/i],
3574
3575
  ['technical_ambition', 'technical advancement still matters', /\b(technical|homerun|research|runtime|architecture|model|benchmark|agent)\b/i],
3575
3576
  ['trust_boundary', 'trust and approval boundaries matter', /\b(security|safe|safety|trust|privacy|approval|permission|gmail|email|connector)\b/i],
@@ -3668,9 +3669,9 @@ function missionPlainTaskPreview(title) {
3668
3669
  function missionTasteMemoryReason(tasteMemory, signals) {
3669
3670
  const candidateIds = new Set((signals || []).map((signal) => signal.id));
3670
3671
  const memoryIds = new Set((tasteMemory?.signals || []).map((signal) => signal.id));
3671
- if (memoryIds.has('runway_revenue') && candidateIds.has('users_revenue')) return 'Keshav has been steering toward user/revenue proof because runway is tight.';
3672
- if (memoryIds.has('plain_language') && candidateIds.has('usability')) return 'Keshav keeps asking for plain-English, understandable work.';
3673
- if (memoryIds.has('proof_gate') && (candidateIds.has('trust') || candidateIds.has('technical'))) return 'Keshav wants proof and receipts before serious work is accepted.';
3672
+ if (memoryIds.has('runway_revenue') && candidateIds.has('users_revenue')) return 'the operator has been steering toward user/revenue proof because runway is tight.';
3673
+ if (memoryIds.has('plain_language') && candidateIds.has('usability')) return 'the operator keeps asking for plain-English, understandable work.';
3674
+ if (memoryIds.has('proof_gate') && (candidateIds.has('trust') || candidateIds.has('technical'))) return 'the operator wants proof and receipts before serious work is accepted.';
3674
3675
  if (memoryIds.has('working_memory')) return 'Recent logs are being used as working memory for what matters now.';
3675
3676
  if (memoryIds.has('technical_ambition') && candidateIds.has('technical')) return 'The member memory still values technical advancement when it has proof.';
3676
3677
  return '';
@@ -4160,14 +4161,61 @@ function startMission(args, options = {}) {
4160
4161
  // checkout's dirt never reaches the mission baseline.
4161
4162
  if (hasFlag(args, '--worktree')) {
4162
4163
  let created;
4164
+ const sourceTop = spawnSync('git', ['rev-parse', '--show-toplevel'], { cwd: process.cwd(), encoding: 'utf8' });
4165
+ const sourceCheckout = sourceTop.status === 0 ? String(sourceTop.stdout).trim() : process.cwd();
4166
+ let nodeModulesSource = null;
4163
4167
  try {
4164
4168
  const { createAgentWorktree } = require('./worktree');
4165
4169
  const base = readFlag(args, '--base', '') || inheritedWorktreeBase(process.cwd());
4166
4170
  created = createAgentWorktree({ member: mission.owner, task: mission.objective, ...(base ? { base } : {}) });
4171
+ const packageFile = path.join(sourceCheckout, 'package.json');
4172
+ const sourceModules = path.join(sourceCheckout, 'node_modules');
4173
+ const targetModules = path.join(created.path, 'node_modules');
4174
+ try {
4175
+ let targetExists = false;
4176
+ try {
4177
+ fs.lstatSync(targetModules);
4178
+ targetExists = true;
4179
+ } catch (error) {
4180
+ if (error.code !== 'ENOENT') throw error;
4181
+ }
4182
+ if (fs.existsSync(packageFile) && fs.existsSync(sourceModules) && !targetExists) {
4183
+ const manifest = JSON.parse(fs.readFileSync(packageFile, 'utf8'));
4184
+ const hasDependencies = ['dependencies', 'devDependencies', 'optionalDependencies']
4185
+ .some((field) => manifest[field] && Object.keys(manifest[field]).length > 0);
4186
+ if (hasDependencies) {
4187
+ const checkIgnored = () => {
4188
+ const result = spawnSync('git', ['check-ignore', '-q', 'node_modules'], { cwd: created.path, encoding: 'utf8' });
4189
+ if (result.error || (result.status !== 0 && result.status !== 1)) {
4190
+ throw new Error(result.error?.message || String(result.stderr || 'git check-ignore failed').trim());
4191
+ }
4192
+ return result.status === 0;
4193
+ };
4194
+ if (!checkIgnored()) {
4195
+ const commonDir = spawnSync('git', ['rev-parse', '--git-common-dir'], { cwd: created.path, encoding: 'utf8' });
4196
+ if (commonDir.error || commonDir.status !== 0) {
4197
+ throw new Error(commonDir.error?.message || String(commonDir.stderr || 'could not find shared git directory').trim());
4198
+ }
4199
+ const excludeFile = path.resolve(created.path, String(commonDir.stdout).trim(), 'info', 'exclude');
4200
+ fs.mkdirSync(path.dirname(excludeFile), { recursive: true });
4201
+ const exclude = fs.existsSync(excludeFile) ? fs.readFileSync(excludeFile, 'utf8') : '';
4202
+ if (!exclude.split(/\r?\n/).includes('/node_modules')) {
4203
+ fs.appendFileSync(excludeFile, `${exclude && !exclude.endsWith('\n') ? '\n' : ''}/node_modules\n`);
4204
+ }
4205
+ }
4206
+ if (!checkIgnored()) throw new Error('git still tracks node_modules after updating the shared exclude file');
4207
+ fs.symlinkSync(sourceModules, targetModules, 'dir');
4208
+ nodeModulesSource = sourceModules;
4209
+ }
4210
+ }
4211
+ } catch (error) {
4212
+ console.warn(`warning: could not link node_modules: ${String(error.message).split(/\r?\n/)[0]}`);
4213
+ }
4167
4214
  } catch (e) {
4168
4215
  exitMissionError(`[mission start] worktree creation failed: ${e.message}`, 2, asJson);
4169
4216
  }
4170
4217
  mission.worktree = { path: created.path, branch: created.branch, base: created.base };
4218
+ if (nodeModulesSource) mission.node_modules_source = nodeModulesSource;
4171
4219
  process.chdir(created.path);
4172
4220
  }
4173
4221
  if (mission.xp_task_enabled) {
@@ -4225,6 +4273,8 @@ function startMission(args, options = {}) {
4225
4273
  dirty_count: worktreeBaseline.dirty_count,
4226
4274
  dirty_hash: worktreeBaseline.dirty_hash,
4227
4275
  } : null,
4276
+ node_modules_linked: Boolean(saved.node_modules_source),
4277
+ node_modules_source: saved.node_modules_source || null,
4228
4278
  ...(saved.remap_reason ? { remap_reason: saved.remap_reason } : {}),
4229
4279
  };
4230
4280
  if (!options.silent) {
@@ -4236,6 +4286,7 @@ function startMission(args, options = {}) {
4236
4286
  `State: ${saved.status}`,
4237
4287
  ...(saved.remap_reason ? [`Remap: ${saved.remap_reason}`] : []),
4238
4288
  ...(saved.worktree ? [`Worktree: ${saved.worktree.path}`, `Branch: ${saved.worktree.branch}`] : []),
4289
+ ...(saved.node_modules_source ? [`linked node_modules from ${saved.node_modules_source}`] : []),
4239
4290
  ...warnings.map((warning) => `Warning: ${warning.message}`),
4240
4291
  ...(saved.xp_task ? [`AgentXP task: ${saved.xp_task.ref}`] : []),
4241
4292
  ...(saved.worktree ? [`Next: cd ${saved.worktree.path} && ${nextTickCommand}`] : [`Next: ${nextTickCommand}`]),
@@ -8049,12 +8100,6 @@ function isWithinActiveHours(activeHours, now = new Date()) {
8049
8100
  return cur >= start || cur < end;
8050
8101
  }
8051
8102
 
8052
- function sleepSync(ms) {
8053
- const waitMs = Math.max(0, Number(ms) || 0);
8054
- if (!waitMs) return;
8055
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, waitMs);
8056
- }
8057
-
8058
8103
  function missionLockOwnerIsAlive(pid) {
8059
8104
  const ownerPid = Number(pid);
8060
8105
  if (!Number.isInteger(ownerPid) || ownerPid <= 0) return false;
@@ -11629,6 +11674,7 @@ module.exports = {
11629
11674
  missionProtectedLaneHold,
11630
11675
  missionCommand,
11631
11676
  startMission,
11677
+ resolveMission,
11632
11678
  spawnMissionDriver,
11633
11679
  missionRunnerNeedsLiveSession,
11634
11680
  completeMission,
package/commands/pack.js CHANGED
@@ -1622,7 +1622,7 @@ async function publishPack(rawArgs, cwd = process.cwd(), options = {}) {
1622
1622
  }
1623
1623
 
1624
1624
  // pack.json belongs to the pack, not to whatever repo happens to contain it
1625
- // (2026-07-27: publishing ~/arena/atrisos-backend/atris wrote a pack.json
1625
+ // (2026-07-27: publishing a backend workspace's atris folder wrote a pack.json
1626
1626
  // into atrisos-backend/ and polluted the source repo).
1627
1627
  const manifestPath = path.join(sourceDir, 'pack.json');
1628
1628
  const existing = readJson(manifestPath);
@@ -2453,7 +2453,7 @@ function printCapabilityTrustCard(policy, trust, receiptPath, userDenyRuleCount
2453
2453
  if (options.operatorInput) {
2454
2454
  console.log(` operator input: ${options.operatorInput.bytes} bytes injected; source path withheld from the pack and receipt`);
2455
2455
  }
2456
- console.log(` host shell: ${policy.grantedCapabilities.includes('host.shell') ? 'GRANTED \u2014 Bash can reach host files and network' : 'denied'}`);
2456
+ console.log(` host shell: ${policy.grantedCapabilities.includes('host.shell') ? 'granted, bash can reach host files and network' : 'denied'}`);
2457
2457
  console.log(' receipt coverage: Atris hook tool events; direct slash-skill invocations and later Claude or policy denials may not appear');
2458
2458
  console.log(` receipt: ${receiptPath}`);
2459
2459
  }
package/commands/probe.js CHANGED
@@ -454,18 +454,24 @@ async function probeCommand(argv) {
454
454
  const detail = ok
455
455
  ? `answer: ${resultText.slice(0, 80).replace(/\n/g, ' ')}`
456
456
  : `dead-end: ${deadEnd}`;
457
- const line = `- **atris-probe** \`${stamp}\` \u2014 ${where} · ${a.model}`
457
+ const line = buildProbeLine({ stamp, where, model: a.model, member, engine, ok, secs, toolsRun, calendar: a.calendar, cliCalendarOps, detail });
458
+ console.log(line);
459
+ return ok ? 0 : 1;
460
+ }
461
+
462
+ function buildProbeLine({ stamp, where, model, member, engine, ok, secs, toolsRun, calendar, cliCalendarOps, detail }) {
463
+ const line = `- **atris-probe** \`${stamp}\` - ${where} · ${model}`
458
464
  + (member ? ` · member=${member}` : '')
459
465
  + (engine ? ` · engine=${engine}` : '')
460
- + ` · ${ok ? 'PASS' : 'FAIL'} · ${secs}s · tools=${toolsRun}`
461
- + (a.calendar ? ` cal_cli=${cliCalendarOps}` : '')
466
+ + ` · ${ok ? 'pass' : 'fail'} · ${secs}s · tools=${toolsRun}`
467
+ + (calendar ? ` cal_cli=${cliCalendarOps}` : '')
462
468
  + ` · ${detail}`;
463
- console.log(line);
464
- return ok ? 0 : 1;
469
+ return line.replace(/\s*\u2014\s*/g, ', ');
465
470
  }
466
471
 
467
472
  module.exports = {
468
473
  probeCommand,
474
+ buildProbeLine,
469
475
  runAtris2Turn,
470
476
  // exported for tests
471
477
  normalizePath,
package/commands/pull.js CHANGED
@@ -722,7 +722,7 @@ async function pullBusiness(slug) {
722
722
  fs.mkdirSync(path.dirname(localPath), { recursive: true });
723
723
  fs.writeFileSync(localPath, content);
724
724
  }
725
- console.log(` \u26A0 ${p.replace(/^\//, '')} CONFLICT \u2014 both you and the computer changed this`);
725
+ console.log(` \u26A0 ${p.replace(/^\//, '')}: conflict, both you and the computer changed this`);
726
726
  console.log(` \u2192 Remote version saved as ${p.replace(/^\//, '')}.remote`);
727
727
  conflictCount++;
728
728
  }
package/commands/push.js CHANGED
@@ -985,7 +985,7 @@ async function pushAtris() {
985
985
  console.log(` ... +${failedToLand.length - shown.length} more`);
986
986
  }
987
987
  console.log('');
988
- console.log(' Common causes: path is outside the workspace (e.g. absolute /Users/... path),');
988
+ console.log(' common causes: path is outside the workspace (for example, an absolute home path),');
989
989
  console.log(' your role lacks write permission for that folder, or warm runner returned an error.');
990
990
  console.log(' These files will appear as drift on your next push so you can retry.');
991
991
  }
@@ -9,22 +9,9 @@
9
9
  */
10
10
 
11
11
  const fs = require('fs');
12
- const os = require('os');
13
12
  const path = require('path');
14
13
  const { spawnSync } = require('child_process');
15
-
16
- function resolveBackendRoot() {
17
- const candidates = [
18
- process.env.ATRIS_BACKEND_DIR,
19
- path.resolve(__dirname, '..', '..', 'atrisos-backend'),
20
- path.resolve(process.cwd(), '..', 'atrisos-backend'),
21
- path.join(os.homedir(), 'arena', 'atrisos-backend'),
22
- ].filter(Boolean);
23
- for (const root of candidates) {
24
- if (fs.existsSync(path.join(root, 'scripts', 'rainmaker.py'))) return root;
25
- }
26
- return null;
27
- }
14
+ const { BACKEND_ROOT_HINT, resolveBackendRoot } = require('../utils/backend-root');
28
15
 
29
16
  function rainmakerCommand(args = []) {
30
17
  if (args[0] === '--help' || args[0] === '-h') {
@@ -32,8 +19,8 @@ function rainmakerCommand(args = []) {
32
19
  return 0;
33
20
  }
34
21
  const root = resolveBackendRoot();
35
- if (!root) {
36
- console.error('Cannot find scripts/rainmaker.py. Set ATRIS_BACKEND_DIR to atrisos-backend.');
22
+ if (!root || !fs.existsSync(path.join(root, 'scripts', 'rainmaker.py'))) {
23
+ console.error(BACKEND_ROOT_HINT);
37
24
  return 1;
38
25
  }
39
26
  const script = path.join(root, 'scripts', 'rainmaker.py');
@@ -4,7 +4,7 @@
4
4
  * atris spaceship, bounded, self-reporting overnight runner.
5
5
  *
6
6
  * Thin wrapper over scripts/spaceship.sh. The script is the implementation
7
- * (a supervised loop that survives bad ticks and emails Keshav on every
7
+ * (a supervised loop that survives bad ticks and emails the operator on every
8
8
  * meaningful state change); this module just makes it reachable as
9
9
  * `atris spaceship ...` and integrates with the CLI's command dispatch.
10
10
  *
@@ -293,6 +293,31 @@ function pushEvent(events, input) {
293
293
  if (event) events.push({ ...event, key: eventKey(event) });
294
294
  }
295
295
 
296
+ // When timestamps match, state snapshots precede receipts, then landing summaries.
297
+ const STREAM_SOURCE_RANK = {
298
+ task: 20,
299
+ task_episode: 30,
300
+ scorecard: 40,
301
+ mission_event: 50,
302
+ mission_state: 60,
303
+ mission_receipt: 70,
304
+ mission_landing: 80,
305
+ worktree: 85,
306
+ landing: 90,
307
+ state: 10,
308
+ };
309
+
310
+ function compareStreamEvents(a, b) {
311
+ const byMs = a.ms - b.ms;
312
+ if (byMs) return byMs;
313
+ const rankA = STREAM_SOURCE_RANK[a.source] ?? 55;
314
+ const rankB = STREAM_SOURCE_RANK[b.source] ?? 55;
315
+ if (rankA !== rankB) return rankA - rankB;
316
+ const byKey = String(a.key || '').localeCompare(String(b.key || ''));
317
+ if (byKey) return byKey;
318
+ return String(a.event || '').localeCompare(String(b.event || ''));
319
+ }
320
+
296
321
  function statePaths(root) {
297
322
  const stateDir = path.join(root, '.atris', 'state');
298
323
  return {
@@ -455,7 +480,6 @@ function collectRunReceiptEvents(root, deps, events) {
455
480
 
456
481
  function candidateWorktreeDirs(root, deps) {
457
482
  const dirs = new Set([
458
- path.resolve(root, '..', '.agent-worktrees'),
459
483
  path.join(path.dirname(root), '.agent-worktrees', path.basename(root)),
460
484
  ]);
461
485
  const sidecar = readJson(statePaths(root).sidecar, deps, null);
@@ -606,7 +630,7 @@ function collectStreamEvents({ root = process.cwd(), sinceMs = 0, agent = '', no
606
630
  if (wantedAgent && event.agent.toLowerCase() !== wantedAgent) continue;
607
631
  byKey.set(event.key, event);
608
632
  }
609
- return [...byKey.values()].sort((a, b) => (a.ms - b.ms) || a.event.localeCompare(b.event));
633
+ return [...byKey.values()].sort(compareStreamEvents);
610
634
  }
611
635
 
612
636
  function waitingOnOperator(tasks) {
@@ -841,6 +865,7 @@ function streamCommand(args = [], deps = defaultDeps()) {
841
865
  module.exports = {
842
866
  collectSnapshot,
843
867
  collectStreamEvents,
868
+ compareStreamEvents,
844
869
  createStreamState,
845
870
  parseArgs,
846
871
  parseSince,