atris 3.53.0 → 3.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/FOR_AGENTS.md +7 -0
  2. package/README.md +1 -1
  3. package/atris/policies/ANTISLOP.md +2 -1
  4. package/atris/skills/copy-editor/SKILL.md +2 -1
  5. package/atris/skills/design/SKILL.md +1 -1
  6. package/atris/skills/x-search/SKILL.md +24 -22
  7. package/atris/skills/youtube/SKILL.md +82 -33
  8. package/atris/team/_template/SOUL.md +40 -0
  9. package/atris/team/validator/MEMBER.md +1 -2
  10. package/bin/atris +21 -0
  11. package/bin/atris.js +563 -373
  12. package/commands/api-key.js +170 -0
  13. package/commands/auth.js +453 -43
  14. package/commands/autoland.js +3 -1
  15. package/commands/autopilot-front.js +58 -11
  16. package/commands/avail.js +5 -4
  17. package/commands/balance.js +55 -0
  18. package/commands/bench.js +29 -1
  19. package/commands/brain.js +63 -12
  20. package/commands/brainstorm.js +18 -0
  21. package/commands/business.js +149 -1
  22. package/commands/chat-scan.js +17 -4
  23. package/commands/close.js +5 -1
  24. package/commands/doctor.js +174 -0
  25. package/commands/dream.js +7 -0
  26. package/commands/drill.js +34 -2
  27. package/commands/engine.js +47 -35
  28. package/commands/errors.js +58 -8
  29. package/commands/experiments.js +14 -0
  30. package/commands/feedback.js +61 -2
  31. package/commands/fleet-report.js +27 -7
  32. package/commands/fleet.js +98 -11
  33. package/commands/founder.js +60 -21
  34. package/commands/human-missions.js +79 -4
  35. package/commands/improve.js +54 -4
  36. package/commands/init.js +68 -92
  37. package/commands/install.js +40 -0
  38. package/commands/integrations.js +399 -30
  39. package/commands/interview.js +8 -0
  40. package/commands/land.js +48 -17
  41. package/commands/learn.js +29 -4
  42. package/commands/log.js +51 -0
  43. package/commands/member.js +68 -10
  44. package/commands/mission.js +287 -50
  45. package/commands/now.js +15 -3
  46. package/commands/orb.js +97 -2
  47. package/commands/pack.js +83 -9
  48. package/commands/playbook.js +381 -0
  49. package/commands/plugin.js +16 -0
  50. package/commands/radar.js +31 -7
  51. package/commands/recap.js +128 -21
  52. package/commands/release.js +102 -2
  53. package/commands/review.js +5 -10
  54. package/commands/revisions.js +55 -0
  55. package/commands/run-front.js +12 -4
  56. package/commands/scout.js +12 -1
  57. package/commands/search.js +123 -28
  58. package/commands/sign.js +2 -2
  59. package/commands/signup.js +12 -3
  60. package/commands/site-deploy.js +17 -4
  61. package/commands/site.js +2 -2
  62. package/commands/skill-eval.js +289 -0
  63. package/commands/skill.js +56 -6
  64. package/commands/social.js +603 -0
  65. package/commands/spaceship.js +69 -6
  66. package/commands/stream.js +8 -1
  67. package/commands/study.js +32 -19
  68. package/commands/sync.js +28 -22
  69. package/commands/task.js +418 -60
  70. package/commands/teach.js +72 -63
  71. package/commands/terminal.js +25 -9
  72. package/commands/topup.js +88 -0
  73. package/commands/truth.js +52 -6
  74. package/commands/usage.js +91 -0
  75. package/commands/verify.js +3 -2
  76. package/commands/version.js +3 -1
  77. package/commands/voice.js +5 -1
  78. package/commands/who.js +69 -11
  79. package/commands/wiki.js +7 -4
  80. package/commands/wish.js +76 -5
  81. package/commands/workflow.js +327 -145
  82. package/commands/worktree.js +1 -0
  83. package/commands/write.js +5 -0
  84. package/commands/x-search.js +340 -0
  85. package/commands/xp.js +5 -1
  86. package/commands/youtube.js +551 -25
  87. package/lib/account-bound.js +47 -0
  88. package/lib/auto-accept-certified.js +8 -1
  89. package/lib/auto-lessons.js +186 -0
  90. package/lib/autoland.js +4 -2
  91. package/lib/chat-log-scan.js +7 -4
  92. package/lib/cli-json.js +77 -0
  93. package/lib/cli-scope.js +26 -0
  94. package/lib/codex-flight.js +1 -0
  95. package/lib/conductor-artifacts.js +1 -1
  96. package/lib/context-gatherer.js +11 -0
  97. package/lib/developer-api.js +116 -0
  98. package/lib/feedback-triage.js +274 -0
  99. package/lib/first-minute.js +398 -0
  100. package/lib/fleet.js +115 -34
  101. package/lib/functional-owner.js +22 -0
  102. package/lib/known-commands.js +31 -5
  103. package/lib/member-scaffold.js +197 -0
  104. package/lib/mission-root.js +4 -2
  105. package/lib/noninteractive.js +88 -0
  106. package/lib/policy-lessons.js +4 -1
  107. package/lib/revision-metric.js +279 -0
  108. package/lib/scratch-root.js +48 -0
  109. package/lib/skill-eval-gate.js +249 -0
  110. package/lib/task-db.js +4 -0
  111. package/lib/task-proof.js +69 -8
  112. package/lib/verifier-quality.js +69 -0
  113. package/lib/wish-audit.js +2 -2
  114. package/lib/wish-delegate.js +10 -1
  115. package/lib/workspace-scaffold.js +270 -0
  116. package/package.json +3 -2
  117. package/scripts/det/checklist-score.js +191 -0
  118. package/scripts/det/ytsearch +31 -0
  119. package/utils/update-check.js +15 -0
@@ -6,6 +6,11 @@ const crypto = require('crypto');
6
6
  const readline = require('readline');
7
7
  const { spawn, spawnSync } = require('child_process');
8
8
  const { hasFlag, readFlag, readIntFlag } = require('../lib/arg-parser');
9
+ const {
10
+ compactErrorPayload,
11
+ compactSuccessPayload,
12
+ printCliJson,
13
+ } = require('../lib/cli-json');
9
14
  const escapeRegExp = require('../lib/escape-regexp');
10
15
  const {
11
16
  appendBriefRecord,
@@ -83,6 +88,7 @@ const {
83
88
  resolveDefaultVerifier,
84
89
  } = require('../lib/default-verifier');
85
90
  const { resolveWorkspaceRoot, redirectToWorkspaceRoot } = require('../lib/mission-root');
91
+ const { isFreshWorkspace, speakFirstMinute } = require('../lib/first-minute');
86
92
  const { readJson, writeJson } = require('../lib/json-file');
87
93
  const {
88
94
  normalizeHumanAsks,
@@ -427,8 +433,22 @@ function applyMissionRunnerProfile(runner) {
427
433
  }
428
434
 
429
435
  function exitMissionError(message, code = 1, asJson = false) {
430
- if (asJson) console.log(JSON.stringify({ ok: false, error: message }, null, 2));
431
- else console.error(message);
436
+ if (asJson) {
437
+ printCliJson(
438
+ {
439
+ ok: false,
440
+ error: message,
441
+ reason: 'mission_error',
442
+ detail: message,
443
+ selected_ref: null,
444
+ next_command: null,
445
+ },
446
+ compactErrorPayload({ reason: 'mission_error', detail: message }),
447
+ process.argv.slice(2),
448
+ );
449
+ } else {
450
+ console.error(message);
451
+ }
432
452
  process.exit(code);
433
453
  }
434
454
 
@@ -518,7 +538,7 @@ function assertMissionVerifier(command, asJson = false) {
518
538
 
519
539
  function stripKnownFlags(args, valueNames, booleanNames = []) {
520
540
  const valueSet = new Set(valueNames);
521
- const booleanSet = new Set(booleanNames);
541
+ const booleanSet = new Set(['--full', '--verbose', ...booleanNames]);
522
542
  const out = [];
523
543
  for (let i = 0; i < args.length; i += 1) {
524
544
  const arg = String(args[i]);
@@ -569,6 +589,8 @@ const MISSION_RUN_VALUE_FLAGS = [
569
589
  ];
570
590
  const MISSION_RUN_BOOLEAN_FLAGS = [
571
591
  '--json',
592
+ '--full',
593
+ '--verbose',
572
594
  '--due',
573
595
  '--detach',
574
596
  '--headless',
@@ -727,7 +749,72 @@ function missionXpTaskTitle(objective) {
727
749
  return `${prefix}${shortClause || 'mission work'}`;
728
750
  }
729
751
 
752
+ function normalizeMissionTaskMatchText(text) {
753
+ return String(text || '')
754
+ .replace(/^Mission XP:\s*/i, '')
755
+ .toLowerCase()
756
+ .replace(/\s+/g, ' ')
757
+ .trim();
758
+ }
759
+
760
+ function missionObjectiveTaskMatch(objective, title) {
761
+ const want = normalizeMissionTaskMatchText(objective);
762
+ const have = normalizeMissionTaskMatchText(title);
763
+ if (!want || !have) return false;
764
+ if (want === have) return true;
765
+ // First clause of a long objective often matches the claimed task title.
766
+ const wantClause = want.split(/:|[.!?](?=\s|$)/)[0].trim();
767
+ const haveClause = have.split(/:|[.!?](?=\s|$)/)[0].trim();
768
+ if (wantClause && haveClause && (wantClause === haveClause || want.startsWith(haveClause) || have.startsWith(wantClause))) {
769
+ return true;
770
+ }
771
+ return false;
772
+ }
773
+
774
+ function taskRowToMissionXpBinding(task, ownerResolution, outPath, { inserted = false, reused = true } = {}) {
775
+ return {
776
+ task_id: task.id,
777
+ ref: missionTaskRef(task) || task.id,
778
+ title: task.title,
779
+ operator_title_warning: warnIfTaskTitleNeedsOperatorWhy(task.title),
780
+ status: task.status || 'claimed',
781
+ assigned_to: task.claimed_by || ownerResolution.owner,
782
+ owner_resolution: ownerResolution.reason,
783
+ executed_by: ownerResolution.executed_by ? normalizeOwnerSlug(ownerResolution.executed_by) : null,
784
+ inserted,
785
+ reused,
786
+ projection_path: outPath,
787
+ };
788
+ }
789
+
790
+ function findOpenTaskMatchingMissionObjective(mission, root = process.cwd(), asJson = false) {
791
+ const taskDb = loadTaskDb(asJson);
792
+ const db = taskDb.open();
793
+ const workspaceRoot = taskDb.workspaceRoot(root);
794
+ const rows = taskDb.withTaskDisplayRefs(taskDb.listTasks(db, { workspaceRoot }));
795
+ const open = rows.filter((row) => row && ['open', 'claimed', 'review'].includes(row.status));
796
+ const explicitIds = Array.isArray(mission.task_ids) ? mission.task_ids.filter(Boolean) : [];
797
+ for (const id of explicitIds) {
798
+ const hit = open.find((row) => row.id === id || row.display_id === id || missionTaskRef(row) === id);
799
+ if (hit) return hit;
800
+ }
801
+ return open.find((row) => missionObjectiveTaskMatch(mission.objective, row.title)) || null;
802
+ }
803
+
804
+ function bindExistingMissionTask(mission, task, root = process.cwd(), asJson = false) {
805
+ const taskDb = loadTaskDb(asJson);
806
+ const db = taskDb.open();
807
+ const workspaceRoot = taskDb.workspaceRoot(root);
808
+ const ownerResolution = resolveMissionOwner(mission, workspaceRoot);
809
+ const { outPath } = writeMissionTaskProjection(taskDb, db, workspaceRoot);
810
+ return taskRowToMissionXpBinding(task, ownerResolution, outPath, { inserted: false, reused: true });
811
+ }
812
+
730
813
  function createMissionXpTask(mission, root = process.cwd(), asJson = false) {
814
+ const existing = findOpenTaskMatchingMissionObjective(mission, root, asJson);
815
+ if (existing) {
816
+ return bindExistingMissionTask(mission, existing, root, asJson);
817
+ }
731
818
  const taskDb = loadTaskDb(asJson);
732
819
  const db = taskDb.open();
733
820
  const workspaceRoot = taskDb.workspaceRoot(root);
@@ -782,6 +869,7 @@ function createMissionXpTask(mission, root = process.cwd(), asJson = false) {
782
869
  owner_resolution: ownerResolution.reason,
783
870
  executed_by: ownerResolution.executed_by ? normalizeOwnerSlug(ownerResolution.executed_by) : null,
784
871
  inserted: result.inserted !== false,
872
+ reused: false,
785
873
  projection_path: outPath,
786
874
  };
787
875
  }
@@ -1238,11 +1326,27 @@ function exitMissingMission(ref, code = 1, asJson = false, root = process.cwd())
1238
1326
  } catch { /* best-effort hint only */ }
1239
1327
  }
1240
1328
  if (asJson) {
1241
- const payload = { ok: false, error };
1242
- if (hints.length) payload.workspace_hint = hints[0];
1243
- if (hints.length > 1) payload.workspace_hints = hints;
1244
- if (candidates.length) payload.nearest_candidates = candidates;
1245
- console.log(JSON.stringify(payload, null, 2));
1329
+ const nextCommand = hints[0] && hints[0].command ? hints[0].command : null;
1330
+ printCliJson(
1331
+ {
1332
+ ok: false,
1333
+ error,
1334
+ reason: 'mission_not_found',
1335
+ detail: error,
1336
+ selected_ref: ref || null,
1337
+ next_command: nextCommand,
1338
+ ...(hints.length ? { workspace_hint: hints[0] } : {}),
1339
+ ...(hints.length > 1 ? { workspace_hints: hints } : {}),
1340
+ ...(candidates.length ? { nearest_candidates: candidates } : {}),
1341
+ },
1342
+ compactErrorPayload({
1343
+ reason: 'mission_not_found',
1344
+ detail: error,
1345
+ selected_ref: ref || null,
1346
+ next_command: nextCommand,
1347
+ }),
1348
+ process.argv.slice(2),
1349
+ );
1246
1350
  } else {
1247
1351
  console.error(error);
1248
1352
  if (hints.length) {
@@ -1688,8 +1792,26 @@ function verifierTimeoutSeconds(verifierResult) {
1688
1792
  return Number.isFinite(ms) ? Math.round(ms / 1000) : 0;
1689
1793
  }
1690
1794
 
1691
- function missionVerifierCheckedText(verifierResult, mission) {
1692
- if (!verifierResult) return 'UNVERIFIED: tick recorded but nothing was checked; treat this increment as unproven.';
1795
+ function missionGuardBlockedText(guard) {
1796
+ if (!guard || guard.allowed !== false) return '';
1797
+ const detail = String(guard.detail || '').trim();
1798
+ const reason = String(guard.reason || 'the check was held').trim();
1799
+ return `VERIFY DID NOT RUN: ${reason}${detail ? ` (${detail})` : ''}. Treat this increment as unproven.`;
1800
+ }
1801
+
1802
+ function missionGuardNextText(guard, missionId) {
1803
+ if (!guard || guard.allowed !== false || !missionId) return '';
1804
+ if (guard.unreadable) {
1805
+ return `repair the mission diff tooling failure, then rerun: atris mission tick ${missionId} --verify`;
1806
+ }
1807
+ return `review the protected diff and receipt before resuming: atris mission run ${missionId}`;
1808
+ }
1809
+
1810
+ function missionVerifierCheckedText(verifierResult, mission, guard = null) {
1811
+ if (!verifierResult) {
1812
+ return missionGuardBlockedText(guard)
1813
+ || 'UNVERIFIED: tick recorded but nothing was checked; treat this increment as unproven.';
1814
+ }
1693
1815
  if (verifierResult.mode === 'engine-unavailable') {
1694
1816
  return 'VERIFY FAILED: the engine verify pass was unavailable; treat this increment as unproven.';
1695
1817
  }
@@ -1715,8 +1837,15 @@ function missionVerifierCheckedText(verifierResult, mission) {
1715
1837
  return `VERIFY FAILED: ${command}.`;
1716
1838
  }
1717
1839
 
1718
- function missionVerifierHighLevelTestText(verifierResult, mission) {
1719
- if (!verifierResult) return 'No automated verifier ran for this receipt; judge it from the receipt, changed files, and next action.';
1840
+ function missionVerifierHighLevelTestText(verifierResult, mission, guard = null) {
1841
+ if (!verifierResult) {
1842
+ if (guard && guard.allowed === false) {
1843
+ const detail = String(guard.detail || '').trim();
1844
+ const reason = String(guard.reason || 'the check was held').trim();
1845
+ return `No automated verifier ran: ${reason}${detail ? ` (${detail})` : ''}. Clear the hold, then rerun to get a real verdict.`;
1846
+ }
1847
+ return 'No automated verifier ran for this receipt; judge it from the receipt, changed files, and next action.';
1848
+ }
1720
1849
  if (verifierResult.mode === 'engine-unavailable') {
1721
1850
  return 'No changed-surface commands were confirmed because the engine verify pass was unavailable.';
1722
1851
  }
@@ -1767,11 +1896,12 @@ function missionFallbackChangedText(mission, status, tickIndex, { ranTicks = nul
1767
1896
  return `${mission.objective} recorded tick ${tickIndex}.`;
1768
1897
  }
1769
1898
 
1770
- function missionTickResultLines(mission, tickIndex, receiptPath, verifierResult = null, stepSummary = '') {
1899
+ function missionTickResultLines(mission, tickIndex, receiptPath, verifierResult = null, stepSummary = '', guard = null) {
1771
1900
  return missionLandingLines(missionReceiptLanding(mission, {
1772
- tick: { tick_index: tickIndex },
1901
+ tick: { tick_index: tickIndex, protected_lane_guard: guard },
1773
1902
  summary: stepSummary,
1774
1903
  verifier_result: verifierResult,
1904
+ protected_lane_guard: guard,
1775
1905
  }, receiptPath));
1776
1906
  }
1777
1907
 
@@ -1801,6 +1931,9 @@ function missionReceiptStatus(mission, result) {
1801
1931
  function missionReceiptNextText(mission, result, receiptPath = '') {
1802
1932
  if (result?.next) return String(result.next);
1803
1933
  if (result?.landing?.next) return String(result.landing.next);
1934
+ const guard = result?.protected_lane_guard || result?.tick?.protected_lane_guard || null;
1935
+ const guardNext = missionGuardNextText(guard, mission?.id);
1936
+ if (guardNext) return guardNext;
1804
1937
  if (result?.verifier_result?.passed === true) {
1805
1938
  if (!mission?.always_on && receiptPath) {
1806
1939
  return `Review proof, then run: atris mission complete ${mission.id} --proof "${receiptPath}".`;
@@ -1817,8 +1950,9 @@ function missionReceiptLanding(mission, result, receiptPath = '') {
1817
1950
  const summary = missionReceiptSummaryText(result);
1818
1951
  const changed = missionLandingStepSummary(summary)
1819
1952
  || missionFallbackChangedText(mission, status, missionReceiptTickIndex(mission, result));
1820
- const checked = missionVerifierCheckedText(verifierResult, mission);
1821
- const tested = missionVerifierHighLevelTestText(verifierResult, mission);
1953
+ const guard = result?.protected_lane_guard || result?.tick?.protected_lane_guard || null;
1954
+ const checked = missionVerifierCheckedText(verifierResult, mission, guard);
1955
+ const tested = missionVerifierHighLevelTestText(verifierResult, mission, guard);
1822
1956
  const reason = missionHumanReasonText(mission, changed);
1823
1957
  return {
1824
1958
  schema: 'atris.result_landing.v1',
@@ -2373,20 +2507,25 @@ function missionRunSummaryLines(mission, ranTicks, effectiveMaxTicks, finalRecei
2373
2507
  const changed = missionRunChangedText(mission, ranTicks, effectiveMaxTicks, ticks, createdNext);
2374
2508
  const reason = missionHumanReasonText(mission, changed);
2375
2509
  const lastTick = ticks[ticks.length - 1] || null;
2510
+ const guard = lastTick?.protected_lane_guard || null;
2376
2511
  const verifier = lastTick && lastTick.verifier_passed == null ? null : mission.verifier_result;
2377
2512
  const checked = verifier
2378
- ? missionVerifierCheckedText(verifier, mission)
2379
- : pauseReason === 'no-progress'
2380
- ? `Run stopped: ${mission.stop_reason || 'no progress across consecutive ticks'}.`
2381
- : pauseReason
2382
- ? `Run paused: ${pauseReason}.`
2383
- : 'UNVERIFIED: run recorded but nothing was checked; treat this increment as unproven.';
2513
+ ? missionVerifierCheckedText(verifier, mission, guard)
2514
+ : missionGuardBlockedText(guard)
2515
+ || (pauseReason === 'no-progress'
2516
+ ? `Run stopped: ${mission.stop_reason || 'no progress across consecutive ticks'}.`
2517
+ : pauseReason
2518
+ ? `Run paused: ${pauseReason}.`
2519
+ : 'UNVERIFIED: run recorded but nothing was checked; treat this increment as unproven.');
2384
2520
  const tested = verifier
2385
- ? missionVerifierHighLevelTestText(verifier, mission)
2386
- : mission.verifier
2387
- ? `Verifier was configured but not completed: ${mission.verifier}.`
2388
- : 'No verifier command was recorded for this mission.';
2389
- const nextLine = missionRunCreatedNextLine(createdNext, continuationGoal, mission);
2521
+ ? missionVerifierHighLevelTestText(verifier, mission, guard)
2522
+ : (guard && guard.allowed === false)
2523
+ ? missionVerifierHighLevelTestText(null, mission, guard)
2524
+ : mission.verifier
2525
+ ? `Verifier was configured but not completed: ${mission.verifier}.`
2526
+ : 'No verifier command was recorded for this mission.';
2527
+ const nextLine = missionGuardNextText(guard, mission?.id)
2528
+ || missionRunCreatedNextLine(createdNext, continuationGoal, mission);
2390
2529
  const lines = [
2391
2530
  'Landing:',
2392
2531
  ` Changed: ${changed}`,
@@ -2660,6 +2799,7 @@ function missionFromArgs(args) {
2660
2799
  ...(ownerResolution.requested_owner && ownerResolution.requested_owner !== owner ? { requested_owner: ownerResolution.requested_owner } : {}),
2661
2800
  ...(ownerResolution.executed_by ? { executed_by: normalizeOwnerSlug(ownerResolution.executed_by) } : {}),
2662
2801
  ...(ownerResolution.proposed_member ? { proposed_member: ownerResolution.proposed_member } : {}),
2802
+ ...(ownerResolution.remap_reason ? { remap_reason: ownerResolution.remap_reason } : {}),
2663
2803
  status: 'planning',
2664
2804
  cadence,
2665
2805
  runner,
@@ -3971,9 +4111,23 @@ function startMission(args, options = {}) {
3971
4111
  const xpTask = createMissionXpTask(mission, process.cwd(), asJson);
3972
4112
  mission.xp_task = xpTask;
3973
4113
  mission.task_ids = Array.from(new Set([...(mission.task_ids || []), xpTask.task_id]));
4114
+ mission.current_task_id = xpTask.task_id;
4115
+ mission.task_ref = xpTask.ref;
3974
4116
  if (!mission.verifier && !mission.always_on) {
3975
4117
  mission.next_action = `work task then run: atris task current-step --goal-id ${mission.id} --as ${mission.owner} --proof "<proof>" --json`;
3976
4118
  }
4119
+ } else if (!(mission.task_ids && mission.task_ids.length)) {
4120
+ // Bind a live open/claimed task with the same objective instead of leaving
4121
+ // task_ids empty and forcing attach-task to spawn a Mission XP twin later.
4122
+ const existing = findOpenTaskMatchingMissionObjective(mission, process.cwd(), asJson);
4123
+ if (existing) {
4124
+ const bound = bindExistingMissionTask(mission, existing, process.cwd(), asJson);
4125
+ mission.task_ids = [bound.task_id];
4126
+ mission.current_task_id = bound.task_id;
4127
+ mission.task_ref = bound.ref;
4128
+ mission.xp_task = bound;
4129
+ mission.xp_task_enabled = true;
4130
+ }
3977
4131
  }
3978
4132
  if (typeof options.beforeMissionSave === 'function') {
3979
4133
  mission = options.beforeMissionSave(mission) || mission;
@@ -4008,6 +4162,7 @@ function startMission(args, options = {}) {
4008
4162
  dirty_count: worktreeBaseline.dirty_count,
4009
4163
  dirty_hash: worktreeBaseline.dirty_hash,
4010
4164
  } : null,
4165
+ ...(saved.remap_reason ? { remap_reason: saved.remap_reason } : {}),
4011
4166
  };
4012
4167
  if (!options.silent) {
4013
4168
  printJsonOrText(
@@ -4016,6 +4171,7 @@ function startMission(args, options = {}) {
4016
4171
  `Started mission: ${saved.objective}`,
4017
4172
  `Owner: ${saved.owner}`,
4018
4173
  `State: ${saved.status}`,
4174
+ ...(saved.remap_reason ? [`Remap: ${saved.remap_reason}`] : []),
4019
4175
  ...(saved.worktree ? [`Worktree: ${saved.worktree.path}`, `Branch: ${saved.worktree.branch}`] : []),
4020
4176
  ...warnings.map((warning) => `Warning: ${warning.message}`),
4021
4177
  ...(saved.xp_task ? [`AgentXP task: ${saved.xp_task.ref}`] : []),
@@ -4136,6 +4292,8 @@ async function startMissionFromRunObjective(objective, args) {
4136
4292
  const xpTask = createMissionXpTask(mission, process.cwd(), asJson);
4137
4293
  mission.xp_task = xpTask;
4138
4294
  mission.task_ids = Array.from(new Set([...(mission.task_ids || []), xpTask.task_id]));
4295
+ mission.current_task_id = xpTask.task_id;
4296
+ mission.task_ref = xpTask.ref;
4139
4297
  }
4140
4298
  if (landRun) {
4141
4299
  const repoPath = path.resolve(readFlag(args, '--repo', process.cwd()));
@@ -4262,14 +4420,16 @@ async function startMissionFromRunObjective(objective, args) {
4262
4420
  function attachMissionTask(args) {
4263
4421
  const asJson = wantsJson(args);
4264
4422
  if (hasFlag(args, '--help') || hasFlag(args, '-h') || String(args[0] || '').trim() === 'help') {
4265
- console.log('Usage: atris mission attach-task <id> [--json]');
4423
+ console.log('Usage: atris mission attach-task <id> [--task <task-id>] [--json]');
4424
+ console.log('Binds an open task with the same objective when one exists; pass --task to choose explicitly.');
4266
4425
  console.log('Run `atris mission --help` for the full option list.');
4267
4426
  process.exit(0);
4268
4427
  }
4269
- const ref = stripKnownFlags(args, [], ['--json'])[0] || '';
4428
+ const ref = stripKnownFlags(args, ['--task'], ['--json'])[0] || '';
4270
4429
  if (!ref) {
4271
- exitMissionError('Usage: atris mission attach-task <id> [--json]', 1, asJson);
4430
+ exitMissionError('Usage: atris mission attach-task <id> [--task <task-id>] [--json]', 1, asJson);
4272
4431
  }
4432
+ const explicitTaskIds = readRepeatedFlag(args, '--task');
4273
4433
  let mission = resolveMission(ref);
4274
4434
  if (!mission) {
4275
4435
  exitMissingMission(ref, 1, asJson);
@@ -4287,6 +4447,13 @@ function attachMissionTask(args) {
4287
4447
  exitMissionError(`Mission "${ref}" is ${mission.status}; task spines attach only to active missions.`, 2, asJson);
4288
4448
  }
4289
4449
 
4450
+ if (explicitTaskIds.length) {
4451
+ mission = {
4452
+ ...mission,
4453
+ task_ids: Array.from(new Set([...(mission.task_ids || []), ...explicitTaskIds])),
4454
+ };
4455
+ }
4456
+
4290
4457
  const existingSpine = missionTaskSpine(mission);
4291
4458
  if (existingSpine?.has_task) {
4292
4459
  let currentMission = mission;
@@ -4362,6 +4529,8 @@ function attachMissionTask(args) {
4362
4529
  xp_task_enabled: true,
4363
4530
  xp_task: xpTask,
4364
4531
  task_ids: Array.from(new Set([...(baseMission.task_ids || []), xpTask.task_id])),
4532
+ current_task_id: xpTask.task_id,
4533
+ task_ref: xpTask.ref,
4365
4534
  };
4366
4535
  if (nextMission.status === 'ready' && nextMission.receipt_path) {
4367
4536
  nextMission.next_action = missionXpReadyAction(nextMission, nextMission.receipt_path) || nextMission.next_action;
@@ -4373,17 +4542,17 @@ function attachMissionTask(args) {
4373
4542
  nextMission.next_action = `work task then run: atris task current-step --goal-id ${nextMission.id} --as ${nextMission.owner} --proof "<proof>" --json`;
4374
4543
  }
4375
4544
 
4376
- const { mission: saved } = saveMission(nextMission, process.cwd(), 'mission_task_spine_attached', { task_id: xpTask.task_id, task_ref: xpTask.ref });
4545
+ const { mission: saved } = saveMission(nextMission, process.cwd(), 'mission_task_spine_attached', { task_id: xpTask.task_id, task_ref: xpTask.ref, reused: xpTask.reused === true });
4377
4546
  const memberState = renderMemberMissionState(saved.owner);
4378
- const logPath = appendMemberLog(saved.owner, 'Mission task spine attached', {
4547
+ const logPath = appendMemberLog(saved.owner, xpTask.reused ? 'Mission task spine bound to existing task' : 'Mission task spine attached', {
4379
4548
  mission: saved.objective,
4380
4549
  task: xpTask.ref,
4381
4550
  });
4382
4551
  const view = missionStatusView(saved);
4383
4552
  printJsonOrText(
4384
- { ok: true, action: 'mission_task_spine_attached', mission: view, task: xpTask, task_spine: view.task_spine, member_state: memberState, log_path: logPath },
4553
+ { ok: true, action: xpTask.reused ? 'mission_task_spine_bound' : 'mission_task_spine_attached', mission: view, task: xpTask, task_spine: view.task_spine, member_state: memberState, log_path: logPath },
4385
4554
  [
4386
- `Attached task spine: ${saved.objective}`,
4555
+ xpTask.reused ? `Bound existing task spine: ${saved.objective}` : `Attached task spine: ${saved.objective}`,
4387
4556
  `Task: ${xpTask.ref}`,
4388
4557
  `Next: ${view.task_spine.current_step_command}`,
4389
4558
  ],
@@ -5188,6 +5357,14 @@ function missionReportFor(mission, root = process.cwd()) {
5188
5357
  ? 'The last verifier passed; full-budget work is continuing.'
5189
5358
  : mission.operator_outcome
5190
5359
  || (verifierPassed ? 'Verifier passed.' : mission.status === 'complete' ? 'Mission is complete.' : mission.status === 'blocked' ? 'Mission is blocked.' : 'Mission is still in progress.');
5360
+ // Keep summary aligned with outcome: never claim a Codex handoff when the
5361
+ // same report also says no receipt / no live worker.
5362
+ let workerSummary = missionWorkerSummary(mission, receipt);
5363
+ if (awaitingFirstWorker || noLiveWorker) {
5364
+ workerSummary = awaitingFirstWorker
5365
+ ? 'Waiting for the first worker check-in; no receipt yet.'
5366
+ : 'No worker receipt yet.';
5367
+ }
5191
5368
  return {
5192
5369
  id: mission.id,
5193
5370
  objective: mission.objective,
@@ -5201,7 +5378,7 @@ function missionReportFor(mission, root = process.cwd()) {
5201
5378
  operator_outcome: operatorOutcome,
5202
5379
  verification: verificationDebt,
5203
5380
  worker: mission.worker || missionWorkerLabel(mission),
5204
- worker_summary: missionWorkerSummary(mission, receipt),
5381
+ worker_summary: workerSummary,
5205
5382
  timeline,
5206
5383
  worker_receipt_path: workerReceiptPath,
5207
5384
  verifier_receipt_path: verifierReceiptPath,
@@ -9528,6 +9705,7 @@ async function executeMissionRunTicksPhase(context) {
9528
9705
  tick: tickRecord,
9529
9706
  frozen,
9530
9707
  verifier_result: verifierResult,
9708
+ protected_lane_guard: result.protected_lane_guard || null,
9531
9709
  rate_limit_info: lastRateLimit,
9532
9710
  worktree: tickWorktree,
9533
9711
  });
@@ -9585,7 +9763,7 @@ async function executeMissionRunTicksPhase(context) {
9585
9763
  last_tick_index: tickIdx,
9586
9764
  last_tick_layer: result.layer,
9587
9765
  last_tick_layer_source: result.layer_source,
9588
- verifier_result: verifierResult || latestOnDisk.verifier_result || null,
9766
+ verifier_result: verifierResult || (verifyEach && result.protected_lane_guard && result.protected_lane_guard.allowed === false ? null : latestOnDisk.verifier_result) || null,
9589
9767
  last_check_feedback: verifierResult
9590
9768
  ? extractCheckFeedback(verifierResult)
9591
9769
  : latestOnDisk.last_check_feedback || '',
@@ -10096,9 +10274,7 @@ function tickMission(args) {
10096
10274
  const protectedLaneGuard = tickRecord.protected_lane_guard || { allowed: true };
10097
10275
  if (!protectedLaneGuard.allowed) {
10098
10276
  status = 'paused';
10099
- nextAction = protectedLaneGuard.unreadable
10100
- ? `repair the mission diff tooling failure, then rerun: atris mission run ${mission.id}`
10101
- : `review the protected diff and receipt before resuming: atris mission run ${mission.id}`;
10277
+ nextAction = missionGuardNextText(protectedLaneGuard, mission.id);
10102
10278
  } else if (verifierResult?.passed && nextGoalChain && !nextGoalChain.pause_ready) {
10103
10279
  status = 'running';
10104
10280
  nextAction = missionGoalChainNextAction(nextGoalChain);
@@ -10136,7 +10312,7 @@ function tickMission(args) {
10136
10312
  last_tick_index: tickIdx,
10137
10313
  last_tick_layer: tickRecord.layer,
10138
10314
  last_tick_layer_source: tickRecord.layer_source,
10139
- verifier_result: verifierResult || mission.verifier_result || null,
10315
+ verifier_result: verifierResult || (verify && !protectedLaneGuard.allowed ? null : mission.verifier_result) || null,
10140
10316
  last_check_feedback: verifierResult
10141
10317
  ? extractCheckFeedback(verifierResult)
10142
10318
  : mission.last_check_feedback || '',
@@ -10169,16 +10345,64 @@ function tickMission(args) {
10169
10345
  const outputMission = continuationGoal?.parent || saved;
10170
10346
  const atrisGoalState = refreshAtrisGoalController(process.cwd(), { missionId: outputMission.id });
10171
10347
  const codexGoalState = refreshCodexGoalController(process.cwd());
10172
- printJsonOrText(
10173
- { ok: true, action: 'mission_tick', mission: outputMission, tick: tickRecord, verifier_result: verifierResult, blocker, receipt_path: receiptPath, log_path: logPath, atris_goal_state: atrisGoalState, codex_goal_state: codexGoalState, continuation_goal: continuationGoal, operator_summary_warning: operatorSummaryWarning, cached: Boolean(tickRecord.cached) },
10174
- [
10175
- ...(tickRecord.cached ? [`Reused receipt for tick ${tickIdx}: ${receiptPath}`] : []),
10176
- ...missionTickResultLines(outputMission, tickIdx, receiptPath, verifierResult, summary || tickRecord.summary),
10177
- ...(missionBlockerReceiptLine(blocker) ? [missionBlockerReceiptLine(blocker)] : []),
10178
- ...(continuationGoal?.mission ? [`Next goal: ${continuationGoal.mission.objective}`] : []),
10179
- ],
10180
- asJson,
10181
- );
10348
+ const verifyToolingFailed = Boolean(verify && !verifierResult && protectedLaneGuard.unreadable);
10349
+ const skipDetail = missionGuardBlockedText(protectedLaneGuard)
10350
+ || 'VERIFY DID NOT RUN: the check could not start. Treat this increment as unproven.';
10351
+ const fullTick = {
10352
+ ok: !verifyToolingFailed,
10353
+ action: 'mission_tick',
10354
+ mission: outputMission,
10355
+ tick: tickRecord,
10356
+ verifier_result: verifierResult,
10357
+ blocker,
10358
+ receipt_path: receiptPath,
10359
+ log_path: logPath,
10360
+ atris_goal_state: atrisGoalState,
10361
+ codex_goal_state: codexGoalState,
10362
+ continuation_goal: continuationGoal,
10363
+ operator_summary_warning: operatorSummaryWarning,
10364
+ cached: Boolean(tickRecord.cached),
10365
+ next_command: outputMission.next_action || null,
10366
+ ...(verifyToolingFailed ? {
10367
+ reason: 'verify_did_not_run',
10368
+ detail: skipDetail,
10369
+ } : {}),
10370
+ };
10371
+ if (asJson) {
10372
+ printCliJson(
10373
+ fullTick,
10374
+ verifyToolingFailed
10375
+ ? compactErrorPayload({
10376
+ reason: 'verify_did_not_run',
10377
+ detail: skipDetail,
10378
+ selected_ref: outputMission.id,
10379
+ next_command: outputMission.next_action || null,
10380
+ })
10381
+ : compactSuccessPayload({
10382
+ action: 'mission_tick',
10383
+ ids: {
10384
+ mission_id: outputMission.id,
10385
+ tick_index: tickIdx,
10386
+ status: outputMission.status,
10387
+ receipt_path: receiptPath,
10388
+ },
10389
+ next_command: outputMission.next_action || null,
10390
+ }),
10391
+ args,
10392
+ );
10393
+ } else {
10394
+ printJsonOrText(
10395
+ fullTick,
10396
+ [
10397
+ ...(tickRecord.cached ? [`Reused receipt for tick ${tickIdx}: ${receiptPath}`] : []),
10398
+ ...missionTickResultLines(outputMission, tickIdx, receiptPath, verifierResult, summary || tickRecord.summary, protectedLaneGuard),
10399
+ ...(missionBlockerReceiptLine(blocker) ? [missionBlockerReceiptLine(blocker)] : []),
10400
+ ...(continuationGoal?.mission ? [`Next goal: ${continuationGoal.mission.objective}`] : []),
10401
+ ],
10402
+ false,
10403
+ );
10404
+ }
10405
+ if (verifyToolingFailed) process.exitCode = 2;
10182
10406
  } finally {
10183
10407
  releaseMissionLock(lock);
10184
10408
  }
@@ -11149,6 +11373,18 @@ function missionCommand(args) {
11149
11373
  return require('./human-missions').answerCommand(args.slice(1));
11150
11374
  }
11151
11375
  if (isBareMission && !hasLocalMissionState(resolveWorkspaceRoot())) {
11376
+ const root = resolveWorkspaceRoot();
11377
+ // Empty folder talks like bare atris. After init, stay on the current
11378
+ // card or an honest no-mission, and do not bounce back to init.
11379
+ if (isFreshWorkspace(root)) {
11380
+ const code = speakFirstMinute({
11381
+ root,
11382
+ fresh: true,
11383
+ asJson: args.includes('--json'),
11384
+ });
11385
+ process.exitCode = code;
11386
+ return code;
11387
+ }
11152
11388
  return require('./human-missions').currentMissionCommand(args);
11153
11389
  }
11154
11390
  const subcommand = isBareMission ? 'status' : (args[0] || 'status');
@@ -11300,6 +11536,7 @@ module.exports = {
11300
11536
  missionVerifierTimeoutMs,
11301
11537
  missionLandingStepSummary,
11302
11538
  missionLandingLines,
11539
+ missionGuardNextText,
11303
11540
  missionVerifierCheckedText,
11304
11541
  missionVerifierHighLevelTestText,
11305
11542
  buildEngineVerifyPrompt,
package/commands/now.js CHANGED
@@ -579,7 +579,7 @@ function refreshNowFile(root = process.cwd(), options = {}) {
579
579
  function nowAtris(args = process.argv.slice(3), root = process.cwd()) {
580
580
  const help = args.includes('--help') || args.includes('-h') || args[0] === 'help';
581
581
  if (help) {
582
- console.log('Usage: atris now [--init|--refresh|--all|--path]');
582
+ console.log('Usage: atris now [--init|--refresh|--all|--path] [--json]');
583
583
  console.log('');
584
584
  console.log('Show the current operating truth for this workspace.');
585
585
  console.log('');
@@ -588,6 +588,7 @@ function nowAtris(args = process.argv.slice(3), root = process.cwd()) {
588
588
  console.log(' atris now --refresh Regenerate a small local now.md');
589
589
  console.log(' atris now --all Refresh this parent and every child Atris workspace');
590
590
  console.log(' atris now --path Print the file path only');
591
+ console.log(' atris now --json Emit path and content as JSON');
591
592
  return;
592
593
  }
593
594
 
@@ -595,6 +596,7 @@ function nowAtris(args = process.argv.slice(3), root = process.cwd()) {
595
596
  const refresh = args.includes('--refresh');
596
597
  const all = args.includes('--all');
597
598
  const pathOnly = args.includes('--path');
599
+ const asJson = args.includes('--json');
598
600
 
599
601
  let result;
600
602
  if (all) {
@@ -603,7 +605,7 @@ function nowAtris(args = process.argv.slice(3), root = process.cwd()) {
603
605
  refreshNowFile(workspace.root);
604
606
  }
605
607
  result = refreshNowFile(root);
606
- if (!pathOnly) {
608
+ if (!pathOnly && !asJson) {
607
609
  console.log(`Refreshed ${workspaces.length} child workspace${workspaces.length === 1 ? '' : 's'}.`);
608
610
  console.log('');
609
611
  }
@@ -621,12 +623,22 @@ function nowAtris(args = process.argv.slice(3), root = process.cwd()) {
621
623
  return;
622
624
  }
623
625
 
626
+ const content = fs.readFileSync(result.path, 'utf8').trimEnd();
627
+ if (asJson) {
628
+ console.log(JSON.stringify({
629
+ ok: true,
630
+ path: rel,
631
+ created: Boolean(result.created),
632
+ content,
633
+ }, null, 2));
634
+ return;
635
+ }
636
+
624
637
  if (result.created) {
625
638
  console.log(`Created ${rel}`);
626
639
  console.log('');
627
640
  }
628
641
 
629
- const content = fs.readFileSync(result.path, 'utf8').trimEnd();
630
642
  console.log(content);
631
643
  }
632
644