atris 3.58.6 → 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 (90) hide show
  1. package/GETTING_STARTED.md +2 -2
  2. package/PERSONA.md +11 -11
  3. package/README.md +9 -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/policies/engineering-principles.md +129 -0
  8. package/atris/policies/genesis.md +112 -0
  9. package/atris/policies/product-design-principles.md +100 -0
  10. package/atris/skills/README.md +44 -15
  11. package/atris/skills/aeo/SKILL.md +10 -10
  12. package/atris/skills/atris/SKILL.md +1 -1
  13. package/atris/skills/design/SKILL.md +3 -1
  14. package/atris/skills/engines/SKILL.md +42 -50
  15. package/atris/skills/meta/SKILL.md +4 -4
  16. package/atris/skills/youtube/SKILL.md +11 -11
  17. package/bin/atris.js +60 -8
  18. package/commands/aeo.js +9 -12
  19. package/commands/analytics.js +8 -11
  20. package/commands/auth.js +8 -2
  21. package/commands/autopilot.js +4 -3
  22. package/commands/avail.js +1 -1
  23. package/commands/brain.js +4 -1
  24. package/commands/business.js +13 -8
  25. package/commands/close.js +2 -1
  26. package/commands/computer.js +15 -10
  27. package/commands/context-sync.js +3 -3
  28. package/commands/design.js +362 -0
  29. package/commands/doc-health.js +377 -0
  30. package/commands/drive.js +32 -0
  31. package/commands/experiments.js +13 -4
  32. package/commands/improve.js +68 -2
  33. package/commands/integrations.js +2 -5
  34. package/commands/land.js +144 -4
  35. package/commands/learn.js +7 -2
  36. package/commands/member.js +239 -58
  37. package/commands/mission.js +94 -18
  38. package/commands/pack.js +2 -2
  39. package/commands/probe.js +11 -5
  40. package/commands/pull.js +1 -1
  41. package/commands/pulse.js +38 -0
  42. package/commands/push.js +1 -1
  43. package/commands/rainmaker.js +3 -16
  44. package/commands/rsi.js +156 -0
  45. package/commands/spaceship.js +1 -1
  46. package/commands/stream.js +27 -2
  47. package/commands/study.js +15 -9
  48. package/commands/sync.js +1 -1
  49. package/commands/task.js +54 -3
  50. package/commands/workflow.js +17 -7
  51. package/commands/youtube.js +76 -8
  52. package/lib/apply-gate.js +22 -4
  53. package/lib/codex-flight.js +28 -5
  54. package/lib/daily-log.js +88 -0
  55. package/lib/design-api.js +130 -0
  56. package/lib/engine-ask.js +1 -1
  57. package/lib/first-minute.js +1 -6
  58. package/lib/fleet.js +4 -1
  59. package/lib/improve-vitals-html.js +3 -2
  60. package/lib/known-commands.js +3 -3
  61. package/lib/lesson-contradiction.js +2 -2
  62. package/lib/map-refs.js +183 -0
  63. package/lib/member-context.js +42 -0
  64. package/lib/mission-room.js +4 -4
  65. package/lib/noninteractive.js +15 -4
  66. package/lib/policy-lessons.js +1 -1
  67. package/lib/receipt-block.js +137 -28
  68. package/lib/rsi-record.js +335 -0
  69. package/lib/runner-command.js +1 -1
  70. package/lib/scorecard.js +4 -4
  71. package/lib/sleep-sync.js +11 -0
  72. package/lib/state-detection.js +36 -17
  73. package/lib/task-db.js +74 -53
  74. package/lib/task-list-keeper.js +192 -0
  75. package/lib/todo-fallback.js +10 -4
  76. package/lib/todo.js +22 -10
  77. package/lib/wish-delegate.js +19 -5
  78. package/mcp/atris-mcp/index.mjs +227 -0
  79. package/package.json +4 -2
  80. package/scripts/det/det.js +2 -2
  81. package/scripts/det/pr-description.js +2 -2
  82. package/scripts/det/test.js +2 -2
  83. package/scripts/det/ytnotes +43 -8
  84. package/utils/api.js +1 -1
  85. package/utils/auth.js +79 -14
  86. package/utils/backend-root.js +31 -0
  87. package/utils/config.js +5 -4
  88. package/utils/owner-identity.js +61 -0
  89. package/atris/policies/LESSONS.md +0 -13
  90. package/atris/skills/autopilot/atris-autopilot.md +0 -71
@@ -4,12 +4,15 @@ 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');
15
+ const { memberProcessPrompt, MEMBER_PROCESS_PATH } = require('../lib/member-context');
13
16
 
14
17
  function findWorkspaceBusinessId(startDir = process.cwd()) {
15
18
  let dir = path.resolve(startDir);
@@ -462,6 +465,7 @@ function missionPurpose(paths) {
462
465
  }
463
466
 
464
467
  const MEMBER_RUN_RUNNABLE_STATUSES = new Set(['planning', 'running', 'ready']);
468
+ const MISSION_TERMINAL_STATUSES = new Set(['complete', 'stopped', 'failed']);
465
469
 
466
470
  function memberRunMissionMap() {
467
471
  try {
@@ -684,6 +688,15 @@ const MEMBER_RUN_START_VALUE_FLAGS = [
684
688
  '--mission-id',
685
689
  '--owner',
686
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',
687
700
  '--lane',
688
701
  '--cadence',
689
702
  '--verify',
@@ -715,10 +728,49 @@ const MEMBER_RUN_START_BOOLEAN_FLAGS = [
715
728
  '--stop-when-done',
716
729
  '--choose-work',
717
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',
718
753
  ];
719
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
+
720
772
  function memberRunMissionText(args = []) {
721
- 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();
722
774
  }
723
775
 
724
776
  function cleanMemberRunPhrase(value) {
@@ -832,10 +884,8 @@ function buildMemberRunStartArgs(owner, missionText, args = [], cwd = process.cw
832
884
  pushFlagValue(startArgs, args, '--minutes');
833
885
  pushFlagValue(startArgs, args, '--hours');
834
886
  pushFlagValue(startArgs, args, '--base');
835
- // A member-run mission is driven immediately by its runner, not parked on
836
- // the queue, so the mission-start verifier gate (built for parked planning
837
- // wishes) would only kill autonomous legs like autopilot's "member chooses
838
- // 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.
839
889
  if (hasFlag(args, '--no-verify') || !readFlag(args, '--verify', '')) {
840
890
  startArgs.push('--no-verify');
841
891
  }
@@ -851,7 +901,7 @@ function buildMemberRunStartArgs(owner, missionText, args = [], cwd = process.cw
851
901
  if (!hasFlag(args, '--shared-checkout') && !hasFlag(args, '--no-worktree') && insideGitRepo(cwd)) {
852
902
  startArgs.push('--worktree');
853
903
  }
854
- if (hasFlag(args, '--json')) startArgs.push('--json');
904
+ startArgs.push('--json');
855
905
  return startArgs;
856
906
  }
857
907
 
@@ -869,15 +919,52 @@ function startMemberRunMission(name, missionText, args = []) {
869
919
  const paths = requireMemberDir(name);
870
920
  const owner = paths.storageName || name;
871
921
  const startArgs = buildMemberRunStartArgs(owner, missionText, args);
922
+ return memberRunCli(startArgs, process.cwd());
923
+ }
872
924
 
925
+ function memberRunCli(args, cwd, { live = false, missionId = '' } = {}) {
873
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;
874
945
  try {
875
- execFileSync(process.execPath, [cliPath, ...startArgs], {
876
- cwd: process.cwd(),
877
- stdio: 'inherit',
878
- });
946
+ payload = JSON.parse(result.stdout);
879
947
  } catch (error) {
880
- 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;
881
968
  }
882
969
  }
883
970
 
@@ -928,12 +1015,11 @@ function memberPing(name, ...args) {
928
1015
  process.exit(2);
929
1016
  }
930
1017
  const missionMod = require('./mission');
931
- const terminal = new Set(['complete', 'stopped', 'failed']);
932
1018
  const candidates = [
933
1019
  ...missionMod.listMissions(process.cwd()),
934
1020
  ...missionMod.listWorktreeRollupMissions(process.cwd()),
935
1021
  ]
936
- .filter((m) => m && m.owner === name && !terminal.has(m.status))
1022
+ .filter((m) => m && m.owner === name && !MISSION_TERMINAL_STATUSES.has(m.status))
937
1023
  .sort((a, b) => String(b.updated_at || b.created_at || '').localeCompare(String(a.updated_at || a.created_at || '')));
938
1024
 
939
1025
  const taskNote = pingClaimedTaskDialogue(name, text, from);
@@ -966,48 +1052,96 @@ function memberPing(name, ...args) {
966
1052
 
967
1053
  function memberRun(name, ...args) {
968
1054
  if (!name || name === '--help' || name === '-h' || hasFlag(args, '--help') || hasFlag(args, '-h')) {
969
- console.log('Usage: atris member run <name> ["mission text"] [--minutes N|--hours N] [--json]');
970
- console.log('New mission: atris member run growth "Improve onboarding proof" --minutes 30 --json');
971
- console.log('Choose work: atris member run growth --industry logistics --value "reduce support time" --minutes 30 --json');
972
- console.log('Existing mission: atris member run growth --mission <mission-id> --max-ticks 1 --json');
973
- console.log('Meaning: if mission text is omitted, the member chooses one useful bounded task from Atris state.');
974
- console.log('Truth: useful work must improve revenue, reliability, speed, security, clarity, or trust, then show plain proof.');
975
- 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.');
976
1063
  return;
977
1064
  }
978
1065
 
1066
+ const asJson = hasFlag(args, '--json');
1067
+ const paths = requireMemberDir(name);
1068
+ const requestedVerifier = readFlag(args, '--verify', '') || memberRunConfiguredVerifier(paths);
979
1069
  const missionText = memberRunMissionText(args);
980
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;
981
1075
  if (missionText && !hasMissionOverride) {
982
- startMemberRunMission(name, missionText, args);
983
- 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
+ }
984
1106
  }
985
1107
 
986
- const missionId = resolveMemberRunMissionId(name, args);
987
- if (!missionId) {
988
- startMemberRunMission(name, memberRunAutoMissionText(name, args), args);
989
- 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}`);
990
1111
  }
991
1112
 
992
- const runArgs = stripKnownFlags(args, ['--mission', '--mission-id', '--minutes', '--hours'], ['--worktree', '--shared-checkout', '--no-worktree']);
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;
1123
+ }
1124
+
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']);
993
1130
  const budgetSeconds = memberRunBudgetSeconds(args);
994
- // A timed run is a loop contract, not one tick: keep picking the next useful
995
- // move until the wall clock (--max-wall) spends the budget. Only untimed runs
996
- // default to a single tick.
997
1131
  if (!readFlag(runArgs, '--max-ticks', '')) {
998
1132
  runArgs.push('--max-ticks', budgetSeconds ? String(Math.max(4, Math.ceil(budgetSeconds / 300))) : '1');
999
1133
  }
1000
1134
  if (!readFlag(runArgs, '--max-wall', '')) runArgs.push('--max-wall', String(budgetSeconds || 900));
1001
-
1002
- const cliPath = path.join(__dirname, '..', 'bin', 'atris.js');
1003
- try {
1004
- execFileSync(process.execPath, [cliPath, 'mission', 'run', missionId, ...runArgs], {
1005
- cwd: process.cwd(),
1006
- stdio: 'inherit',
1007
- });
1008
- } catch (error) {
1009
- process.exitCode = Number(error?.status) || 1;
1010
- }
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);
1011
1145
  }
1012
1146
 
1013
1147
  function loadTeamScoreEvidence(scoreJsonPath) {
@@ -1830,8 +1964,8 @@ function problemSignalSources(root = process.cwd()) {
1830
1964
  sources.push(source);
1831
1965
  }
1832
1966
  for (const source of problemSignalFilesForStateDir(path.join(root, '.atris', 'state'))) push(source);
1833
- const siblingBackend = path.resolve(root, '..', 'atrisos-backend');
1834
- if (fs.existsSync(siblingBackend)) {
1967
+ const siblingBackend = resolveBackendRoot(root);
1968
+ if (siblingBackend) {
1835
1969
  for (const source of problemSignalFilesForStateDir(path.join(siblingBackend, '.atris', 'state'), 'backend')) push(source);
1836
1970
  }
1837
1971
  for (const source of configuredProblemSignalSources(root)) push(source);
@@ -4216,6 +4350,7 @@ function memberActivate(name) {
4216
4350
 
4217
4351
  const content = fs.readFileSync(activePath, 'utf8');
4218
4352
  const fm = parseFrontmatter(content) || {};
4353
+ const sharedProcess = memberProcessPrompt(process.cwd());
4219
4354
 
4220
4355
  console.log('');
4221
4356
  console.log(`Activating: ${fm.name || name} (${fm.role || 'no role'})`);
@@ -4298,7 +4433,8 @@ function memberActivate(name) {
4298
4433
 
4299
4434
  console.log('');
4300
4435
  console.log(`Member "${fm.name || name}" activated.`);
4301
- console.log(`Tell your agent: "You are the ${fm.role || name}. Read team/${name}/MEMBER.md."`);
4436
+ const identityPath = path.relative(process.cwd(), activePath);
4437
+ console.log(`Tell your agent: "You are the ${fm.role || name}. Read ${sharedProcess ? `${MEMBER_PROCESS_PATH}, then ` : ''}${identityPath}. Stay inside this member's permissions."`);
4302
4438
  }
4303
4439
 
4304
4440
  // --- UPGRADE subcommand ---
@@ -4591,12 +4727,6 @@ function printJsonOrText(payload, lines, asJson) {
4591
4727
  for (const line of lines) console.log(line);
4592
4728
  }
4593
4729
 
4594
- function sleepSync(ms) {
4595
- const duration = Math.max(0, Number(ms) || 0);
4596
- if (duration <= 0) return;
4597
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, duration);
4598
- }
4599
-
4600
4730
  function memberGoal(name, ...args) {
4601
4731
  const paths = requireMemberDir(name);
4602
4732
  const asJson = hasFlag(args, '--json');
@@ -4707,6 +4837,49 @@ function memberGoalFromMission(name, ...args) {
4707
4837
  );
4708
4838
  return;
4709
4839
  }
4840
+ if (MISSION_TERMINAL_STATUSES.has(lowerCompact(runtime.status || ''))) {
4841
+ // A finished mission must not mint or re-point an active goal; that recreated the
4842
+ // stale-goal defect class (OBL-1961, OBL-2212, OBL-2232) after every retire. --force
4843
+ // cannot bypass this, it only widens the existing-goal match on a living mission.
4844
+ const staleGoals = state.goals.filter((goal) => (
4845
+ goal.source === 'mission' && goal.status === 'active'
4846
+ && runtime.id && goal.mission_id === runtime.id
4847
+ ));
4848
+ const staleList = staleGoals.map((goal) => goal.title || goal.id).join('; ');
4849
+ const ask = staleGoals.length
4850
+ ? `Mission ${runtime.id || compactSentence(runtimeFocus, 88)} is ${runtime.status}, so its active goal is stale. Retire ${compactSentence(staleList, 140)} by setting its status in goals.json, or start a living mission and run: atris member goal-from-mission ${name} --force`
4851
+ : `Mission ${runtime.id || compactSentence(runtimeFocus, 88)} is ${runtime.status}. Start a living mission, then run: atris member goal-from-mission ${name} --force`;
4852
+ const logPath = appendMemberGoalLog(paths.memberDir, name, 'Member goal-from-mission blocked', {
4853
+ ask,
4854
+ mission_id: runtime.id || '',
4855
+ mission_status: runtime.status || '',
4856
+ stale_goals: staleGoals.map((goal) => goal.id),
4857
+ });
4858
+ printJsonOrText(
4859
+ {
4860
+ ok: true,
4861
+ action: 'needs_user',
4862
+ member: name,
4863
+ needs_user: true,
4864
+ ask,
4865
+ stale_goals: staleGoals,
4866
+ mission: {
4867
+ north_star: purpose.northStar,
4868
+ runtime_id: runtime.id || null,
4869
+ runtime_status: runtime.status || null,
4870
+ runtime_next: runtime.next || null,
4871
+ },
4872
+ mission_file: paths.missionFile,
4873
+ log_path: logPath,
4874
+ },
4875
+ [
4876
+ `Blocked for ${name}: mission ${runtime.id || 'in now.md'} is ${runtime.status}, a finished mission cannot drive an active goal.`,
4877
+ `Ask: ${ask}`,
4878
+ ],
4879
+ asJson,
4880
+ );
4881
+ return;
4882
+ }
4710
4883
  // The title IS the mission focus, no boilerplate prefix; the acceptance list already
4711
4884
  // says "one bounded step" and the why carries the full sentence.
4712
4885
  const title = compactSentence(runtimeFocus, 96);
@@ -4977,7 +5150,8 @@ function fallbackProposalForGoal(goal, context = {}) {
4977
5150
  };
4978
5151
  }
4979
5152
 
4980
- function proposalPromptForGoal(goal, context = {}) {
5153
+ function proposalPromptForGoal(goal, context = {}, cwd = process.cwd()) {
5154
+ const sharedProcess = memberProcessPrompt(cwd);
4981
5155
  const files = (context?.evidence?.goal_files?.files || [])
4982
5156
  .filter((file) => file.exists && file.excerpt)
4983
5157
  .slice(0, 4)
@@ -5005,6 +5179,7 @@ function proposalPromptForGoal(goal, context = {}) {
5005
5179
  },
5006
5180
  };
5007
5181
  return [
5182
+ ...(sharedProcess ? [sharedProcess, ''] : []),
5008
5183
  'You generate the next bounded Atris member experiment.',
5009
5184
  'Read the JSON context and return only JSON with keys: title, proof_target, next_step, verifier, stop_rule.',
5010
5185
  'The next_step must be adaptive to the goal/evidence, concrete, receipt-backed, and safe for one bounded tick.',
@@ -5058,10 +5233,11 @@ async function callAtris2ProposalLlm(goal, context = {}) {
5058
5233
  const injected = injectedLlmProposal();
5059
5234
  if (injected) return injected;
5060
5235
  if (process.env.ATRIS_MEMBER_PROPOSAL_LLM !== '1') return null;
5236
+ const prompt = proposalPromptForGoal(goal, context, process.cwd());
5061
5237
  try {
5062
5238
  const { postTurn } = require('../ax');
5063
5239
  const output = { isTTY: false, write() { return true; } };
5064
- const result = await postTurn(proposalPromptForGoal(goal, context), {
5240
+ const result = await postTurn(prompt, {
5065
5241
  mode: process.env.ATRIS_MEMBER_PROPOSAL_LLM_MODE || 'fast',
5066
5242
  route: 'local',
5067
5243
  cwd: process.cwd(),
@@ -5761,6 +5937,7 @@ function emptyObjectiveGeneratorProposal(extra = {}) {
5761
5937
  updated_at: stampIso(),
5762
5938
  status: extra.status || 'empty',
5763
5939
  advisory_only: true,
5940
+ auto_task_eligible: extra.auto_task_eligible !== false,
5764
5941
  world_model_used: false,
5765
5942
  llm_source: extra.llm_source || null,
5766
5943
  llm_error: extra.llm_error || null,
@@ -5893,8 +6070,8 @@ function fallbackObjectiveGeneratorProposal(graph, recommendations, transferPatt
5893
6070
  ),
5894
6071
  suggested_member: member,
5895
6072
  suggested_patterns: objectivePatternMatches(transferPatterns, proposedObjective),
5896
- }, { status: 'ok', llm_error: 'llm_not_configured', world_model_used: true });
5897
- return proposal || emptyObjectiveGeneratorProposal({ status: 'llm_not_configured', llm_error: 'llm_not_configured' });
6073
+ }, { status: 'ok', llm_error: 'llm_not_configured', world_model_used: true, auto_task_eligible: false });
6074
+ return proposal || emptyObjectiveGeneratorProposal({ status: 'llm_not_configured', llm_error: 'llm_not_configured', auto_task_eligible: false });
5898
6075
  }
5899
6076
 
5900
6077
  function objectiveGeneratorPrompt(graph, recommendations, transferPatterns = []) {
@@ -6056,7 +6233,7 @@ async function runObjectiveGeneratorWake(name, paths, { execute = false } = {})
6056
6233
  proposal = emptyObjectiveGeneratorProposal({ status: llm.error === 'invalid_json' ? 'parse_error' : 'llm_error', llm_source: llm.source, llm_error: llm.error });
6057
6234
  proposal.world_model_used = true;
6058
6235
  } else {
6059
- reason = 'heuristic_objective_proposal_written';
6236
+ reason = execute ? 'heuristic_objective_proposal_written' : 'heuristic_objective_proposal_dry_run';
6060
6237
  proposal = fallbackObjectiveGeneratorProposal(graph, recommendations, transferPatterns);
6061
6238
  }
6062
6239
  }
@@ -6065,7 +6242,7 @@ async function runObjectiveGeneratorWake(name, paths, { execute = false } = {})
6065
6242
  proposal.suggested_patterns = objectivePatternMatches(transferPatterns, proposal.proposed_objective);
6066
6243
  }
6067
6244
 
6068
- if (execute && proposal?.status === 'ok' && Number(proposal.overall_score) > 7) {
6245
+ if (execute && proposal?.status === 'ok' && proposal.auto_task_eligible !== false && Number(proposal.overall_score) > 7) {
6069
6246
  createdTask = createAutoObjectiveTask(proposal);
6070
6247
  proposal.created_task = createdTask.ok ? {
6071
6248
  id: createdTask.task_id || null,
@@ -6081,7 +6258,8 @@ async function runObjectiveGeneratorWake(name, paths, { execute = false } = {})
6081
6258
  }
6082
6259
 
6083
6260
  const proposalsPath = objectiveGeneratorProposalsPath(root);
6084
- if (execute) {
6261
+ const proposalsWritten = Boolean(execute);
6262
+ if (proposalsWritten) {
6085
6263
  fs.mkdirSync(path.dirname(proposalsPath), { recursive: true });
6086
6264
  fs.writeFileSync(proposalsPath, JSON.stringify(proposal, null, 2) + '\n', 'utf8');
6087
6265
  }
@@ -6111,6 +6289,7 @@ async function runObjectiveGeneratorWake(name, paths, { execute = false } = {})
6111
6289
  llm_successful: Boolean(llm?.source && llm?.proposal && proposal.status === 'ok'),
6112
6290
  llm_error: llm?.error || proposal.llm_error || null,
6113
6291
  proposals_path: path.relative(root, proposalsPath),
6292
+ proposals_written: proposalsWritten,
6114
6293
  task_creation_threshold: 7,
6115
6294
  task_created: Boolean(createdTask?.ok),
6116
6295
  created_task: proposal.created_task,
@@ -6128,7 +6307,7 @@ async function runObjectiveGeneratorWake(name, paths, { execute = false } = {})
6128
6307
  score: proposal.overall_score || '',
6129
6308
  task: proposal.created_task?.ref || '',
6130
6309
  receipt: path.relative(root, receiptPath),
6131
- output: path.relative(root, proposalsPath),
6310
+ output: proposalsWritten ? path.relative(root, proposalsPath) : '',
6132
6311
  });
6133
6312
 
6134
6313
  return {
@@ -7817,6 +7996,7 @@ const WAKE_REASON_TEXT = {
7817
7996
  auto_improver_task_create_failed: 'it found an improvement but could not put the task on the board',
7818
7997
  heuristic_cross_domain_proof_written: 'it wrote a cross-domain proof using its built-in heuristics',
7819
7998
  heuristic_objective_proposal_written: 'it drafted an objective proposal using its built-in heuristics',
7999
+ heuristic_objective_proposal_dry_run: 'it previewed an objective proposal using its built-in heuristics, without writing it',
7820
8000
  install_requires_clean_git: 'installing needs a clean git tree first',
7821
8001
  insufficient_world_model_data: 'its world model is too thin to act on yet',
7822
8002
  llm_json_parse_failed: 'the model reply did not parse, so it stopped rather than act on garbage',
@@ -9208,7 +9388,7 @@ async function memberCommand(subcommand, ...args) {
9208
9388
  console.log(' atris member goal-from-mission growth --json');
9209
9389
  console.log(' atris member goal-from-score growth --score-json team-score.json --json');
9210
9390
  console.log(' atris member wake growth --json');
9211
- 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');
9212
9392
  console.log(' atris member run growth --mission <mission-id> --max-ticks 1 --json');
9213
9393
  console.log(' atris member wake growth --execute --confirm-autonomy-policy');
9214
9394
  console.log(' atris member supervisor recommendations --json');
@@ -9232,6 +9412,7 @@ async function memberCommand(subcommand, ...args) {
9232
9412
  }
9233
9413
 
9234
9414
  module.exports = {
9415
+ proposalPromptForGoal,
9235
9416
  memberCommand,
9236
9417
  findAllMembers,
9237
9418
  findWorkspaceBusinessId,