atris 3.57.3 → 3.58.5

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 (175) hide show
  1. package/README.md +12 -1
  2. package/atris/skills/clawhub/member-runtime/SKILL.md +9 -9
  3. package/atris/skills/create-member/SKILL.md +16 -13
  4. package/atris/skills/design/SKILL.md +27 -4
  5. package/atris/skills/engines/SKILL.md +57 -13
  6. package/atris/skills/x-search/SKILL.md +13 -5
  7. package/atris/skills/youtube/SKILL.md +39 -8
  8. package/atris/team/brainstormer/MEMBER.md +2 -2
  9. package/atris/team/executor/MEMBER.md +3 -3
  10. package/atris/team/launcher/MEMBER.md +4 -4
  11. package/atris/team/navigator/MEMBER.md +3 -3
  12. package/atris/team/researcher/MEMBER.md +2 -2
  13. package/atris/team/validator/MEMBER.md +1 -1
  14. package/atris.md +14 -0
  15. package/ax +157 -24
  16. package/bin/atris.js +79 -31
  17. package/commands/aeo.js +4 -4
  18. package/commands/agents.js +1 -1
  19. package/commands/align.js +12 -12
  20. package/commands/analytics.js +3 -3
  21. package/commands/app.js +5 -5
  22. package/commands/ask.js +175 -0
  23. package/commands/auth.js +7 -7
  24. package/commands/autoland.js +29 -5
  25. package/commands/autopilot-front.js +6 -6
  26. package/commands/autopilot.js +90 -90
  27. package/commands/avail.js +5 -5
  28. package/commands/bench.js +63 -1
  29. package/commands/browse.js +2 -2
  30. package/commands/business.js +12 -12
  31. package/commands/card.js +2 -2
  32. package/commands/clean.js +13 -13
  33. package/commands/close.js +297 -15
  34. package/commands/compile.js +13 -13
  35. package/commands/computer.js +94 -27
  36. package/commands/console.js +5 -5
  37. package/commands/decide.js +1 -1
  38. package/commands/deck.js +9 -9
  39. package/commands/drive.js +4 -4
  40. package/commands/engine.js +173 -19
  41. package/commands/errors.js +3 -3
  42. package/commands/experiments.js +18 -1
  43. package/commands/feed.js +2 -2
  44. package/commands/feedback.js +4 -4
  45. package/commands/fleet.js +12 -12
  46. package/commands/game.js +1 -1
  47. package/commands/guide.js +49 -0
  48. package/commands/improve.js +19 -17
  49. package/commands/init.js +67 -19
  50. package/commands/interview.js +7 -7
  51. package/commands/land.js +40 -15
  52. package/commands/learn.js +12 -12
  53. package/commands/lesson.js +2 -2
  54. package/commands/log.js +1 -1
  55. package/commands/loops.js +4 -4
  56. package/commands/member.js +75 -50
  57. package/commands/mission.js +97 -98
  58. package/commands/one-lap.js +1 -1
  59. package/commands/pack.js +74 -8
  60. package/commands/plugin.js +6 -6
  61. package/commands/probe.js +9 -9
  62. package/commands/pull.js +35 -35
  63. package/commands/pulse.js +1 -1
  64. package/commands/push.js +21 -21
  65. package/commands/rainmaker.js +1 -1
  66. package/commands/recap.js +7 -7
  67. package/commands/reel.js +2 -2
  68. package/commands/release.js +3 -3
  69. package/commands/report.js +1 -1
  70. package/commands/review.js +5 -5
  71. package/commands/revisions.js +1 -1
  72. package/commands/run.js +1 -1
  73. package/commands/scout.js +3 -3
  74. package/commands/security-review.js +7 -7
  75. package/commands/serve.js +9 -9
  76. package/commands/setup.js +1 -1
  77. package/commands/sign.js +4 -4
  78. package/commands/signup.js +4 -4
  79. package/commands/site.js +8 -3
  80. package/commands/skill.js +6 -6
  81. package/commands/slop.js +18 -18
  82. package/commands/social.js +3 -3
  83. package/commands/soul.js +14 -14
  84. package/commands/spaceship.js +1 -1
  85. package/commands/status.js +2 -2
  86. package/commands/strings.js +13 -13
  87. package/commands/study.js +4 -4
  88. package/commands/sync.js +8 -8
  89. package/commands/task.js +50 -25
  90. package/commands/teach.js +2 -2
  91. package/commands/team.js +1 -1
  92. package/commands/theme.js +2 -2
  93. package/commands/tree.js +34 -0
  94. package/commands/truth.js +7 -7
  95. package/commands/verify.js +12 -12
  96. package/commands/visualize.js +2 -2
  97. package/commands/workflow.js +97 -40
  98. package/commands/worktree.js +57 -1
  99. package/commands/write.js +9 -9
  100. package/commands/x-search.js +304 -15
  101. package/commands/xp.js +6 -3
  102. package/commands/youtube.js +916 -65
  103. package/lib/accept-verify-gate.js +3 -3
  104. package/lib/activity-stream.js +1 -1
  105. package/lib/apply-gate.js +13 -0
  106. package/lib/auto-accept-certified.js +28 -12
  107. package/lib/autoland.js +6 -6
  108. package/lib/bench/engines.js +59 -8
  109. package/lib/bench/report.js +1 -1
  110. package/lib/bench/runner.js +204 -5
  111. package/lib/bench/tree-render.js +76 -0
  112. package/lib/card.js +1 -1
  113. package/lib/chat-log-scan.js +1 -1
  114. package/lib/claude-boot-block.js +2 -0
  115. package/lib/deck-compose.js +1 -1
  116. package/lib/deck-history.js +2 -2
  117. package/lib/deck-layout.js +1 -1
  118. package/lib/deck-review.js +5 -5
  119. package/lib/deck-schema.js +1 -1
  120. package/lib/default-verifier.js +1 -1
  121. package/lib/developer-api.js +3 -0
  122. package/lib/engine-ask.js +66 -9
  123. package/lib/engine-registry.js +15 -2
  124. package/lib/file-ops.js +1 -1
  125. package/lib/fleet.js +71 -35
  126. package/lib/html-render.js +1 -1
  127. package/lib/intents.js +172 -0
  128. package/lib/journal.js +1 -1
  129. package/lib/known-commands.js +2 -2
  130. package/lib/land-green.js +364 -0
  131. package/lib/lesson-contradiction.js +2 -2
  132. package/lib/lesson-preflight.js +1 -1
  133. package/lib/manifest.js +5 -5
  134. package/lib/member-alive.js +9 -5
  135. package/lib/memory-view.js +3 -3
  136. package/lib/mission-protected-lane.js +1 -1
  137. package/lib/mission-root.js +2 -2
  138. package/lib/orb-context.js +10 -10
  139. package/lib/pack-capabilities.js +861 -3
  140. package/lib/permission-grants.js +2 -2
  141. package/lib/policy-lessons.js +6 -6
  142. package/lib/pulse.js +8 -8
  143. package/lib/receipt-evidence.js +1 -1
  144. package/lib/reel.js +1 -1
  145. package/lib/review-integrity.js +1 -1
  146. package/lib/runner-command.js +35 -5
  147. package/lib/scorecard.js +4 -4
  148. package/lib/security-scan.js +5 -5
  149. package/lib/self-drive.js +1 -1
  150. package/lib/site-publish.js +372 -0
  151. package/lib/site.js +1 -1
  152. package/lib/slides-deck.js +2 -2
  153. package/lib/state-detection.js +2 -2
  154. package/lib/sync-telemetry.js +2 -2
  155. package/lib/task-db.js +17 -10
  156. package/lib/task-explanation.js +1 -1
  157. package/lib/task-proof.js +2 -2
  158. package/lib/task-receipt.js +3 -3
  159. package/lib/theme.js +12 -12
  160. package/lib/todo-fallback.js +3 -3
  161. package/lib/todo-sections.js +1 -1
  162. package/lib/tree-hash.js +79 -0
  163. package/lib/usage.js +1 -1
  164. package/lib/wish-delegate.js +1 -1
  165. package/lib/workspace-safety.js +4 -4
  166. package/lib/workspace-scaffold.js +3 -3
  167. package/package.json +1 -1
  168. package/scripts/det/concierge-bakeoff.sh +26 -0
  169. package/scripts/det/ytquote-repair.js +0 -1
  170. package/scripts/det/ytrail-race.js +0 -5
  171. package/scripts/member-operate.mjs +52 -14
  172. package/scripts/outbound-artifact-gate.js +0 -3
  173. package/utils/auth.js +0 -3
  174. package/utils/config.js +0 -1
  175. package/scripts/det/checklist-score.js +0 -191
@@ -90,6 +90,7 @@ const {
90
90
  const { resolveWorkspaceRoot, redirectToWorkspaceRoot } = require('../lib/mission-root');
91
91
  const { isFreshWorkspace, speakFirstMinute } = require('../lib/first-minute');
92
92
  const { readJson, writeJson } = require('../lib/json-file');
93
+ const { treeHashFor } = require('../lib/tree-hash');
93
94
  const {
94
95
  normalizeHumanAsks,
95
96
  openHumanAsks,
@@ -453,7 +454,7 @@ function exitMissionError(message, code = 1, asJson = false) {
453
454
  }
454
455
 
455
456
  // Write-time warnings judge only what a machine can truly judge: identifiers,
456
- // flags, and task ids. Whether a why is present is a judgment call the tick
457
+ // flags, and task ids. Whether a why is present is a judgment call, the tick
457
458
  // prompt demands it, the review pass judges it. A warning that cries wolf on
458
459
  // plain sentences teaches agents to ignore it (golden-path papercut). The
459
460
  // strict operatorReady bar stays at the digest surface, where under-showing
@@ -978,11 +979,6 @@ function loadMissionMap(root = process.cwd()) {
978
979
  return map;
979
980
  }
980
981
 
981
- function hasLocalMissionState(root = process.cwd()) {
982
- return readJsonLines(statePaths(root).missionsJsonl)
983
- .some((mission) => mission && mission.id && mission.cloud !== true);
984
- }
985
-
986
982
  const LIVE_MISSION_TICK_MS = 24 * 60 * 60 * 1000;
987
983
 
988
984
  function isLiveInFlightMission(mission, now = Date.now(), root = process.cwd()) {
@@ -1024,19 +1020,15 @@ function speakMissionDoor(args = [], root = resolveWorkspaceRoot()) {
1024
1020
  if (live) {
1025
1021
  return statusMission(asJson ? [live.id, '--json'] : [live.id]);
1026
1022
  }
1027
- if (isFreshWorkspace(root) || hasLocalMissionState(root)) {
1028
- const code = speakFirstMinute({
1029
- root,
1030
- fresh: isFreshWorkspace(root),
1031
- asJson,
1032
- });
1033
- process.exitCode = code;
1034
- return code;
1035
- }
1036
- return require('./human-missions').currentMissionCommand(args, {
1023
+ // Nothing running in this folder: same two spoken lines as bare atris.
1024
+ // A finished or account-global mission from another room is not the live card.
1025
+ const code = speakFirstMinute({
1037
1026
  root,
1038
- fallbackOnMissing: true,
1027
+ fresh: isFreshWorkspace(root),
1028
+ asJson,
1039
1029
  });
1030
+ process.exitCode = code;
1031
+ return code;
1040
1032
  }
1041
1033
 
1042
1034
  function terminalNextAction(status) {
@@ -1212,7 +1204,7 @@ function listWorktreeRollupMissions(root = process.cwd()) {
1212
1204
  // run/tick/show. This is the same local+rollup, deduped-by-id, list-sorted
1213
1205
  // set `mission status`/`list` renders, so id/slug/suffix lookups search
1214
1206
  // exactly what list showed. n stays exactly as each mission's home workspace
1215
- // assigned it (loadMissionMap owns that) this index does NOT renumber,
1207
+ // assigned it (loadMissionMap owns that), this index does NOT renumber,
1216
1208
  // because n must stay a stable, durable handle across saves (see
1217
1209
  // mission-number-stability.test.js); numeric-n resolution stays scoped to
1218
1210
  // the local root, where n is guaranteed unique.
@@ -1275,7 +1267,7 @@ function missionNumberDisplayIndex(missions, canonicalMissions = missions) {
1275
1267
  const MISSION_REF_SUFFIX_MIN_LENGTH = 6;
1276
1268
 
1277
1269
  // A ref that "looks like" an id/suffix/number is a single token with no
1278
- // whitespace matching a hex/id/numeric shape. Those must resolve or error
1270
+ // whitespace matching a hex/id/numeric shape. Those must resolve or error,
1279
1271
  // never silently fall through to starting a new mission (a mistyped id/n
1280
1272
  // creating a junk mission is the worst failure mode here).
1281
1273
  function missionRefLooksLikeHandle(ref) {
@@ -1411,10 +1403,10 @@ function exitMissingMission(ref, code = 1, asJson = false, root = process.cwd())
1411
1403
  } else if (candidates.length) {
1412
1404
  console.error('Nearest candidates:');
1413
1405
  for (const candidate of candidates) {
1414
- console.error(` #${candidate.n} ${candidate.id} (${candidate.status}) ${candidate.objective}`);
1406
+ console.error(` #${candidate.n} ${candidate.id} (${candidate.status}), ${candidate.objective}`);
1415
1407
  }
1416
1408
  } else {
1417
- console.error('Mission ids are workspace-local run this from the workspace that created the mission.');
1409
+ console.error('Mission ids are workspace-local, run this from the workspace that created the mission.');
1418
1410
  }
1419
1411
  }
1420
1412
  process.exit(code);
@@ -1444,7 +1436,7 @@ function resolveMission(ref, root = process.cwd()) {
1444
1436
  return chosen;
1445
1437
  }
1446
1438
  if (matches.length) return matches[0];
1447
- // No mission has this n. An all-digit ref can still be a hash suffix
1439
+ // No mission has this n. An all-digit ref can still be a hash suffix,
1448
1440
  // missionId()'s 8-hex-char suffix is purely numeric ~2.4% of the time
1449
1441
  // ((10/16)^8), and returning null here made those missions unresolvable
1450
1442
  // by suffix. Fall through to slug/suffix resolution instead.
@@ -1509,7 +1501,7 @@ function saveMission(mission, root = process.cwd(), eventType = 'mission_updated
1509
1501
  });
1510
1502
  appendJsonLine(paths.missionsJsonl, next);
1511
1503
  // Self-maintenance: once history outweighs live missions 4:1 the ledger is
1512
- // rewritten to one row per mission (latest state, first display number
1504
+ // rewritten to one row per mission (latest state, first display number,
1513
1505
  // exactly what loadMissionMap reconstructs). Every status/tick path parses
1514
1506
  // this file several times, so unbounded snapshot history taxes everything.
1515
1507
  try { compactMissionLedger(paths.missionsJsonl); } catch {}
@@ -1789,7 +1781,7 @@ function missionLandingStepSummary(summary) {
1789
1781
  const withoutProofTail = withoutInternalId.replace(/;\s+(?=(?:PR\s+\d+|(?:node|npm|git)\b)).*$/i, '').trim();
1790
1782
  const plainVerified = missionPlainVerifiedSummary(withoutProofTail);
1791
1783
  if (plainVerified) return plainVerified;
1792
- const beforeInlineProof = withoutProofTail.split(/\s+(?:—|-)\s+verified by\b/i)[0] || withoutProofTail;
1784
+ const beforeInlineProof = withoutProofTail.split(/\s+(?:\u2014|-)\s+verified by\b/i)[0] || withoutProofTail;
1793
1785
  const beforeChecks = (beforeInlineProof.split(/\s+(?:checks?|verified|proof):\s+/i)[0] || beforeInlineProof)
1794
1786
  .replace(/,\s*(?:verifier|proof|checks?)\s+(?:still|pending)[.!]?$/i, '')
1795
1787
  .trim();
@@ -2104,26 +2096,38 @@ function normalizeMissionReceiptResult(mission, result, receiptPath = '') {
2104
2096
 
2105
2097
  function missionRunStartNextLine(mission, nextCommand, warnings = []) {
2106
2098
  const missingVerifier = warnings.some((warning) => warning && warning.code === 'missing_verifier');
2107
- if (missingVerifier) return 'Add a verifier before completion, then run the first proof tick.';
2099
+ if (missingVerifier) return 'Add a completion check, then start the first piece of work.';
2108
2100
  if (isCodexGoalMission(mission) && !codexNativeGoalAck(mission)) {
2109
- return 'Start the visible goal, then continue this mission.';
2101
+ return 'Start the first piece of work in this chat.';
2110
2102
  }
2111
- if (/attach-task/.test(nextCommand || '')) return 'Attach task context, then continue this mission.';
2112
- return 'Run the first proof tick.';
2103
+ if (/attach-task/.test(nextCommand || '')) return 'Start the first piece of work.';
2104
+ return 'Keep working on the next useful result.';
2105
+ }
2106
+
2107
+ function missionRunCheckPlanText(command) {
2108
+ const check = String(command || '').trim();
2109
+ if (!check) return 'No completion check is ready yet.';
2110
+ if (/^git\s+diff\s+--check\b/i.test(check)) {
2111
+ return 'Atris will check the changed files for formatting problems before the result is ready.';
2112
+ }
2113
+ if (/\bnode\s+--test\b/i.test(check)) {
2114
+ return 'Atris will run the relevant behavior checks before the result is ready.';
2115
+ }
2116
+ if (/^test\s+-s\s+\S+/i.test(check)) {
2117
+ return 'Atris will confirm the saved result exists and is not empty.';
2118
+ }
2119
+ return 'A completion check is ready and will run before the result is ready.';
2113
2120
  }
2114
2121
 
2115
2122
  function missionRunTakeoffLines(mission, { warnings = [], nextCommand = '' } = {}) {
2116
- const checked = mission.verifier
2117
- ? `Verifier configured: ${mission.verifier}.`
2118
- : 'No verifier was recorded for this mission.';
2119
2123
  return [
2120
- 'Takeoff:',
2124
+ 'Mission started.',
2121
2125
  ` Goal: ${mission.objective}`,
2122
- ` Done when: ${mission.stop_condition || 'the mission has proof or a human decision'}.`,
2126
+ ` Done when: ${mission.stop_condition || 'the result is checked and ready to review'}.`,
2123
2127
  ...(missionBudgetLine(mission) ? [` Budget: ${missionBudgetLine(mission)}`] : []),
2124
2128
  ...missionGoalChainLines(mission),
2125
- ' Proof: Mission state saved in .atris/state/missions.jsonl.',
2126
- ` Check: ${checked}`,
2129
+ ' Saved: Atris will remember this mission and its progress.',
2130
+ ` How it will be checked: ${missionRunCheckPlanText(mission.verifier)}`,
2127
2131
  ` Next: ${missionRunStartNextLine(mission, nextCommand, warnings)}`,
2128
2132
  ];
2129
2133
  }
@@ -2185,16 +2189,9 @@ function selectMissionRunUsefulTarget(rawObjective, root = process.cwd()) {
2185
2189
  }
2186
2190
  }
2187
2191
 
2188
- function missionRunPreflightSentence(text, max = 140) {
2189
- const clean = String(text || '').replace(/\s+/g, ' ').trim();
2190
- if (clean.length <= max) return clean;
2191
- return `${clean.slice(0, max - 3).replace(/\s+\S*$/, '').trimEnd()}...`;
2192
- }
2193
-
2194
- function missionRunPreflightObjective(rawObjective, room, owner) {
2195
- const name = room?.name || 'Mission Room';
2192
+ function missionRunPreflightObjective(rawObjective, room) {
2196
2193
  const task = room?.task_plan_preview?.task || room?.truth_snapshot || rawObjective;
2197
- return `${name} with ${owner}: turn "${missionRunPreflightSentence(task)}" into one visible goal, task spine, proof receipt, and next action.`;
2194
+ return String(task || rawObjective || '').replace(/\s+/g, ' ').trim();
2198
2195
  }
2199
2196
 
2200
2197
  function missionRunTrustedObjective(rawObjective, room, target) {
@@ -2266,7 +2263,7 @@ function buildMissionRunRoomPreflight(rawObjective, args = [], options = {}) {
2266
2263
  const written = writeMissionRoomReceipt(room, { root });
2267
2264
  const shapedObjective = trustedRun
2268
2265
  ? missionRunTrustedObjective(rawObjective, written.room, selectedTarget)
2269
- : missionRunPreflightObjective(rawObjective, written.room, ownerResolution.owner);
2266
+ : missionRunPreflightObjective(rawObjective, written.room);
2270
2267
  const shaping = guardMissionRunShaping(rawObjective, shapedObjective, selectedTarget);
2271
2268
  const acceptedTarget = shaping.selectedTarget;
2272
2269
  const taskSpineRequired = !acceptedTarget && (explicitPreflight || signalPreflight);
@@ -4031,7 +4028,7 @@ function inheritedWorktreeBase(cwd) {
4031
4028
  // Dedup gate: the same objective + owner already active anywhere in the
4032
4029
  // workspace family (this store or any worktree's) is reused, never cloned.
4033
4030
  // Born 2026-07-02: an hourly alive loop spawned six identical auto-improver
4034
- // missions in six fresh worktrees in one day pure token burn. --duplicate
4031
+ // missions in six fresh worktrees in one day, pure token burn. --duplicate
4035
4032
  // is the explicit escape hatch.
4036
4033
  const TWIN_ACTIVE_STATUSES = new Set(['planning', 'ready', 'running']);
4037
4034
 
@@ -4078,7 +4075,7 @@ function startMission(args, options = {}) {
4078
4075
  printJsonOrText(
4079
4076
  { ok: true, action: 'mission_recovered', recovered: true, mission: existing, note: 'objective looked like a mission id; recovered the existing mission instead of creating a new one' },
4080
4077
  [
4081
- `That's a mission id, not an objective recovered ${existing.id} (${existing.status}).`,
4078
+ `That's a mission id, not an objective, recovered ${existing.id} (${existing.status}).`,
4082
4079
  `Resume: atris mission run ${existing.id}`,
4083
4080
  ],
4084
4081
  asJson,
@@ -4128,7 +4125,7 @@ function startMission(args, options = {}) {
4128
4125
  { ok: true, action: 'mission_reused', reused: true, mission: twin, note: 'an active mission with this objective and owner already exists; resumed instead of cloning (pass --duplicate to force a second one)' },
4129
4126
  [
4130
4127
  `Already active: ${twin.id} (${twin.status})`,
4131
- `Same objective, same owner reusing it instead of starting a clone.`,
4128
+ `Same objective, same owner, reusing it instead of starting a clone.`,
4132
4129
  `Resume: atris mission run ${twin.id}`,
4133
4130
  `Really want a second one: re-run with --duplicate`,
4134
4131
  ],
@@ -4149,7 +4146,7 @@ function startMission(args, options = {}) {
4149
4146
  }
4150
4147
  // --worktree: bind the mission to its own isolated checkout. We chdir before
4151
4148
  // any state writes so the mission record, baseline sidecar, receipts, and
4152
- // member files all land inside the worktree ticks run there, and the main
4149
+ // member files all land inside the worktree, ticks run there, and the main
4153
4150
  // checkout's dirt never reaches the mission baseline.
4154
4151
  if (hasFlag(args, '--worktree')) {
4155
4152
  let created;
@@ -4277,14 +4274,15 @@ async function startMissionFromRunObjective(objective, args) {
4277
4274
  landRun ? DEFAULT_LONG_RUN_VERIFIER : inferRunObjectiveVerifier(missionObjective)
4278
4275
  || inferRunObjectiveVerifier(rawObjective)
4279
4276
  || (missionRunPreflight?.trusted_run ? DEFAULT_LONG_RUN_VERIFIER : '')
4280
- || (inferredLoop.wantsLongRun ? DEFAULT_LONG_RUN_VERIFIER : ''),
4277
+ || (inferredLoop.wantsLongRun ? DEFAULT_LONG_RUN_VERIFIER : '')
4278
+ || (hasFlag(args, '--no-verify') ? '' : resolveDefaultVerifier(process.cwd(), { allowBroadSuite: false })),
4281
4279
  );
4282
4280
  const stopCondition = readFlag(
4283
4281
  args,
4284
4282
  '--stop',
4285
4283
  budgetStopCondition(budgetContract) || (inferredLoop.wantsLongRun
4286
4284
  ? `run for ${inferredLoop.requestedHours || 'the requested overnight window'} hour${inferredLoop.requestedHours === 1 ? '' : 's'}, or stop when proof is ready`
4287
- : (verifier ? 'verifier passes and visible goal lands' : 'visible goal lands and proof is ready')),
4285
+ : (verifier ? 'the first result is checked and ready to review' : 'the first result is saved and ready to review')),
4288
4286
  );
4289
4287
  const startArgs = [
4290
4288
  missionObjective,
@@ -5092,7 +5090,7 @@ function missionRunsReceiptIndex(root = process.cwd()) {
5092
5090
  }
5093
5091
 
5094
5092
  // Roll every ran tick for a mission into a verification-debt tally. `unchecked`
5095
- // counts ran ticks that recorded no verifier result at all the "tick recorded
5093
+ // counts ran ticks that recorded no verifier result at all, the "tick recorded
5096
5094
  // but nothing was checked" red the report and now.md rollup surface and count
5097
5095
  // against the mission.
5098
5096
  function missionVerificationDebt(mission, root = process.cwd()) {
@@ -5120,7 +5118,7 @@ function missionVerificationDebt(mission, root = process.cwd()) {
5120
5118
  // when every ran tick was checked, so clean missions stay quiet.
5121
5119
  function missionVerificationDebtLine(debt) {
5122
5120
  if (!debt || !debt.unchecked) return '';
5123
- return `unverified ticks: ${debt.unchecked} of ${debt.ran} ran no check treat those increments as unproven`;
5121
+ return `unverified ticks: ${debt.unchecked} of ${debt.ran} ran no check ⚠, treat those increments as unproven`;
5124
5122
  }
5125
5123
 
5126
5124
  function missionReportTimeline(mission, root = process.cwd(), limit = 6) {
@@ -6035,7 +6033,7 @@ function doctorMission(args) {
6035
6033
  if (findings.length) process.exitCode = 1;
6036
6034
  }
6037
6035
 
6038
- // `atris mission watch [id]` read-only live heartbeat. Prints a line per tick as it
6036
+ // `atris mission watch [id]`, read-only live heartbeat. Prints a line per tick as it
6039
6037
  // lands so a human (or any terminal) can see the loop is alive without rerunning status.
6040
6038
  function watchMission(args) {
6041
6039
  if (hasFlag(args, '--help') || hasFlag(args, '-h') || String(args[0] || '').trim() === 'help') {
@@ -6061,7 +6059,7 @@ function watchMission(args) {
6061
6059
  const fingerprint = (mission) => [mission.status, mission.last_tick_at, mission.last_tick_index, mission.receipt_path].join('|');
6062
6060
  const tickNote = (mission) => {
6063
6061
  const heartbeat = missionHeartbeatLines(mission).map((line) => line.trim()).join(', ');
6064
- return `${heartbeat || `state: ${mission.status}`}${mission.receipt_path ? ` proof: ${mission.receipt_path}` : ''}`;
6062
+ return `${heartbeat || `state: ${mission.status}`}${mission.receipt_path ? `, proof: ${mission.receipt_path}` : ''}`;
6065
6063
  };
6066
6064
  const seen = new Map();
6067
6065
  let lastIdleAt = Date.now();
@@ -6069,7 +6067,7 @@ function watchMission(args) {
6069
6067
  const poll = () => {
6070
6068
  const targets = loadTargets();
6071
6069
  if (!targets.length && !seen.size) {
6072
- emitOnce('no active missions yet waiting');
6070
+ emitOnce('no active missions yet, waiting');
6073
6071
  }
6074
6072
  let changed = false;
6075
6073
  for (const mission of targets) {
@@ -6110,6 +6108,7 @@ function writeReceipt(mission, result, root = process.cwd()) {
6110
6108
  const finalResult = normalizeMissionReceiptResult(mission, result, relativeReceiptPath);
6111
6109
  const receipt = {
6112
6110
  schema: 'atris.mission_receipt.v1',
6111
+ tree_hash: treeHashFor(root),
6113
6112
  mission_id: mission.id,
6114
6113
  objective: mission.objective,
6115
6114
  owner: mission.owner,
@@ -6358,7 +6357,7 @@ function loadMissionWorktreeBaseline(missionId, root = process.cwd()) {
6358
6357
  // Closed missions no longer tick, so the sidecar is dead weight; prune it and
6359
6358
  // fold a compact audit summary into the mission record (full path lists stay
6360
6359
  // out of missions.jsonl, which re-appends the whole record on every save).
6361
- // Paused missions keep their sidecar resume ticks still subtract it.
6360
+ // Paused missions keep their sidecar, resume ticks still subtract it.
6362
6361
  function pruneMissionWorktreeBaseline(mission, root = process.cwd()) {
6363
6362
  const baseline = loadMissionWorktreeBaseline(mission.id, root);
6364
6363
  try { fs.rmSync(missionBaselinePath(mission.id, root), { force: true }); } catch {}
@@ -6389,7 +6388,7 @@ function worktreeReceipt(before, after, { verifier = '', baseline = null } = {})
6389
6388
  // tick-start snapshot for legacy missions). Only paths dirtied beyond that
6390
6389
  // baseline count toward the unverified signal. Loop exhaust the mission
6391
6390
  // writes about itself (state plane, receipts, rendered status) is not work
6392
- // product, so it never counts otherwise every multi-tick mission in a repo
6391
+ // product, so it never counts, otherwise every multi-tick mission in a repo
6393
6392
  // that doesn't gitignore those dirs would flag its own bookkeeping.
6394
6393
  const baselinePaths = baseline
6395
6394
  ? new Set(baseline.paths)
@@ -6418,7 +6417,7 @@ function worktreeReceipt(before, after, { verifier = '', baseline = null } = {})
6418
6417
  }
6419
6418
 
6420
6419
  // ---------------------------------------------------------------------------
6421
- // `atris mission run <id>` bounded local headless loop. v0.1.
6420
+ // `atris mission run <id>`, bounded local headless loop. v0.1.
6422
6421
  // Spawns `claude -p --resume <session>` per tick. Honors cadence, active-hours,
6423
6422
  // rate-limit info, and a flock per mission. `max-ticks` bounds total attempts;
6424
6423
  // `ran_ticks` separately reports ticks that actually made progress.
@@ -7820,19 +7819,19 @@ function consecutiveVerifierFails(ticks) {
7820
7819
  }
7821
7820
 
7822
7821
  // BCK-1324: a "holding tick" is a tick that reports status=ran/reason=tick-ok
7823
- // (so it never trips the error-streak breakers) but left no structural trace
7822
+ // (so it never trips the error-streak breakers) but left no structural trace,
7824
7823
  // no new or cleared dirty files beyond whatever was already dirty at tick
7825
7824
  // start, and no fresh verifier pass. Claude's own summary text ("holding
7826
7825
  // tick, no drift") is not the signal: agents self-label busywork as progress
7827
7826
  // constantly. The worktree diff and verifier result are ground truth.
7828
7827
  function tickMadeProgress(tick) {
7829
- if (!tick || tick.status !== 'ran') return true; // errors/skips aren't "idle" other breakers own those
7828
+ if (!tick || tick.status !== 'ran') return true; // errors/skips aren't "idle", other breakers own those
7830
7829
  const wt = tick.worktree;
7831
7830
  if (wt && wt.available) {
7832
7831
  if ((wt.new_dirty_count || 0) > 0) return true;
7833
7832
  if ((wt.cleared_dirty_count || 0) > 0) return true;
7834
7833
  } else {
7835
- // No worktree signal available (e.g. git unavailable) don't punish a
7834
+ // No worktree signal available (e.g. git unavailable), don't punish a
7836
7835
  // tick we have no evidence against.
7837
7836
  return true;
7838
7837
  }
@@ -7842,7 +7841,7 @@ function tickMadeProgress(tick) {
7842
7841
 
7843
7842
  // Count the trailing run of ticks (most recent first) that made no progress
7844
7843
  // per tickMadeProgress. A single progressing tick anywhere in the run resets
7845
- // this to 0 only the tail streak matters.
7844
+ // this to 0, only the tail streak matters.
7846
7845
  function consecutiveNoProgressTicks(ticks) {
7847
7846
  let n = 0;
7848
7847
  for (let i = ticks.length - 1; i >= 0; i--) {
@@ -7863,7 +7862,7 @@ function missionJudgmentPlainText(value) {
7863
7862
  .replace(/\b[0-9A-HJKMNP-TV-Z]{26}\b/gi, ' ')
7864
7863
  .replace(/\b[0-9a-f]{12,40}\b/gi, ' ')
7865
7864
  .replace(/\b[A-Z][A-Z0-9]{1,9}-\d+\b/g, ' ')
7866
- .replace(/[—–]/g, ' ')
7865
+ .replace(/[\u2014–]/g, ' ')
7867
7866
  .replace(/[*_~#>]+/g, ' ')
7868
7867
  .replace(/\s+/g, ' ')
7869
7868
  .replace(/^[\s:;,.-]+|[\s:;,.-]+$/g, '')
@@ -8211,7 +8210,7 @@ function acquireMissionLock(missionId, root = process.cwd(), options = {}) {
8211
8210
  const holderKnown = Number.isInteger(holderPid) && holderPid > 0;
8212
8211
  // A lock is created empty (openSync 'wx') and its pid record is written a
8213
8212
  // moment later. A racing waiter that reads the file inside that window sees
8214
- // no pid do NOT treat that as a dead owner, or two processes both "own"
8213
+ // no pid, do NOT treat that as a dead owner, or two processes both "own"
8215
8214
  // the lock and clobber each other's writes. Only steal when the holder pid
8216
8215
  // is known-dead, or when a pidless lock has sat abandoned past the tiny
8217
8216
  // creation window (guards against a crash between openSync and writeSync).
@@ -8443,7 +8442,7 @@ function buildTickPrompt(mission, tickIndex, maxTicks, frozen, pings = []) {
8443
8442
  `**Lane:** ${frozen.lane}`,
8444
8443
  `**Cadence:** ${mission.cadence}`,
8445
8444
  `**Stop condition:** ${mission.stop_condition || 'human marks complete'}`,
8446
- `**Verifier (frozen):** ${frozen.verifier || '(none receipt only)'}`,
8445
+ `**Verifier (frozen):** ${frozen.verifier || '(none, receipt only)'}`,
8447
8446
  `**Last status:** ${mission.status}`,
8448
8447
  `**Last tick:** ${mission.last_tick_at || 'never'}`,
8449
8448
  ...missionBudgetPromptLines(mission),
@@ -8451,7 +8450,7 @@ function buildTickPrompt(mission, tickIndex, maxTicks, frozen, pings = []) {
8451
8450
  ``,
8452
8451
  `## Your task`,
8453
8452
  `Do ONE increment of work toward the stop condition. ONE. No more.`,
8454
- `- You are the member "${mission.owner}". Read atris/team/${mission.owner}/MEMBER.md (and SOUL.md if present) before acting work in that identity, inside its scope and stop rules. After your work, append what you did and what you learned to atris/team/${mission.owner}/logs/<today's date>.md.`,
8453
+ `- You are the member "${mission.owner}". Read atris/team/${mission.owner}/MEMBER.md (and SOUL.md if present) before acting, work in that identity, inside its scope and stop rules. After your work, append what you did and what you learned to atris/team/${mission.owner}/logs/<today's date>.md.`,
8455
8454
  `- FIRST: inspect current mission/task state before acting. Read the relevant files, run \`atris mission status ${mission.id}\`, \`git status\`, or \`atris task list\` as needed so you know what's already done.`,
8456
8455
  `- Pick the smallest concrete action that moves the mission forward.`,
8457
8456
  `- Before acting, state your single next move in one sentence.`,
@@ -8482,14 +8481,14 @@ function buildTickPrompt(mission, tickIndex, maxTicks, frozen, pings = []) {
8482
8481
  `- Do NOT start new missions, modify other missions, or expand scope.`,
8483
8482
  `- Do NOT run destructive commands without strong evidence they're correct.`,
8484
8483
  ``,
8485
- `When done, output a short receipt. The summary's first line must name what changed and how it was verified. Then include: (1) the exact files edited / commands run / artifacts produced name them, (1b) one verify command a reviewer can rerun to check the work, (2) the metric of progress, (3) what the next tick should pick up. End the receipt with one line naming the layer this tick touched: \`layer: identity|beliefs|capabilities|behaviors|environment\` (final line the harness parses it).`,
8484
+ `When done, output a short receipt. The summary's first line must name what changed and how it was verified. Then include: (1) the exact files edited / commands run / artifacts produced, name them, (1b) one verify command a reviewer can rerun to check the work, (2) the metric of progress, (3) what the next tick should pick up. End the receipt with one line naming the layer this tick touched: \`layer: identity|beliefs|capabilities|behaviors|environment\` (final line, the harness parses it).`,
8486
8485
  ];
8487
8486
  if (mission.task_ids?.length) {
8488
8487
  lines.push('', `## Task ids`, mission.task_ids.map((t) => `- ${t}`).join('\n'));
8489
8488
  }
8490
8489
  const humanAsks = openHumanAsks(mission.human_asks);
8491
8490
  if (humanAsks.length) {
8492
- lines.push('', `## Human asks (don't act on these surface them)`, humanAsks.map((ask) => `- ${ask.text}`).join('\n'));
8491
+ lines.push('', `## Human asks (don't act on these, surface them)`, humanAsks.map((ask) => `- ${ask.text}`).join('\n'));
8493
8492
  }
8494
8493
  return lines.join('\n');
8495
8494
  }
@@ -8524,16 +8523,16 @@ function missionPauseNextAction(pauseReason, missionId, deadModel = null, lastEr
8524
8523
  return 'stopped: the worker kept reporting the same thing 3 times in a row';
8525
8524
  }
8526
8525
  if (pauseReason === 'model-unavailable' && deadModel) {
8527
- return `model "${deadModel}" is unavailable set a live model (mission.model, ATRIS_RUNNER_MODEL, or legacy ATRIS_CLAUDE_MODEL), then: atris mission run ${missionId}`;
8526
+ return `model "${deadModel}" is unavailable, set a live model (mission.model, ATRIS_RUNNER_MODEL, or legacy ATRIS_CLAUDE_MODEL), then: atris mission run ${missionId}`;
8528
8527
  }
8529
8528
  if (typeof pauseReason === 'string' && pauseReason.startsWith('repeated-error:')) {
8530
8529
  const reason = pauseReason.slice('repeated-error:'.length);
8531
- return `tick kept failing with "${reason}" inspect the last receipt, fix the cause, then: atris mission run ${missionId}`;
8530
+ return `tick kept failing with "${reason}", inspect the last receipt, fix the cause, then: atris mission run ${missionId}`;
8532
8531
  }
8533
8532
  // Single-tick cron runs pause via max-ticks-reached on the very first errored tick.
8534
8533
  // A bare "resume" there just re-errors; point the operator at the cause instead.
8535
8534
  if (pauseReason === 'max-ticks-reached' && lastErrorReason) {
8536
- return `hit the tick budget while erroring ("${lastErrorReason}") inspect the last receipt before resuming: atris mission run ${missionId}`;
8535
+ return `hit the tick budget while erroring ("${lastErrorReason}"), inspect the last receipt before resuming: atris mission run ${missionId}`;
8537
8536
  }
8538
8537
  return `resume with: atris mission run ${missionId}`;
8539
8538
  }
@@ -9090,7 +9089,7 @@ async function parseAndValidateMissionRunPhase(args) {
9090
9089
  }
9091
9090
  // --fleet: staff every idle capable engine on the board's claimable
9092
9091
  // safe-lane tasks, build in parallel worktrees, land serially. Humble flag,
9093
- // full loop see lib/fleet.js. --dry-run previews the staffing only.
9092
+ // full loop, see lib/fleet.js. --dry-run previews the staffing only.
9094
9093
  if (hasFlag(args, '--fleet')) {
9095
9094
  const { runFleetFlight } = require('../lib/fleet');
9096
9095
  const slots = Math.max(1, Number(readFlag(args, '--slots', '')) || 3);
@@ -9211,7 +9210,7 @@ async function resolveMissionRunPhase(context) {
9211
9210
  // BCK-1319: a bare single token that looks like an id/suffix/number (no
9212
9211
  // whitespace, hex/id/numeric shape) is a mistyped or stale mission handle,
9213
9212
  // not a new objective. Silently starting a fresh mission from it is the
9214
- // worst failure mode it buries the mission the operator meant to run.
9213
+ // worst failure mode, it buries the mission the operator meant to run.
9215
9214
  // Only a genuine multi-word (or non-handle-shaped) ref keeps the
9216
9215
  // start-a-new-mission shortcut.
9217
9216
  if (!mission && ref && missionRefLooksLikeHandle(ref)) {
@@ -9392,7 +9391,7 @@ async function executeMissionRunTicksPhase(context) {
9392
9391
  const effectiveMaxTicks = (cadenceSeconds === 0 && !hasExplicitTickBudget) ? 1 : maxTicks;
9393
9392
 
9394
9393
  // Session setup: only Claude-backed workers need a persisted session id.
9395
- // atris2 turns are stateless per tick continuity lives on disk (logs, receipts, now.md).
9394
+ // atris2 turns are stateless per tick, continuity lives on disk (logs, receipts, now.md).
9396
9395
  if (!skipWorker && !atris2Runner && !drillRunner && !sessionId && !pendingSessionId) {
9397
9396
  pendingSessionId = crypto.randomUUID();
9398
9397
  mission = saveMission({ ...mission, pending_session_id: pendingSessionId }, cwd, 'mission_session_pending', { session_id: pendingSessionId }).mission;
@@ -9473,7 +9472,7 @@ async function executeMissionRunTicksPhase(context) {
9473
9472
  // the auto runner has already resolved this tick to a *different* ready engine
9474
9473
  // (the walled one is now credit_out and excluded from resolution), the run-wide
9475
9474
  // cooldown no longer applies. Clear it so the fresh engine runs now instead of
9476
- // the loop sleeping through or pausing on `rate-limit-exceeded-wall` for a
9475
+ // the loop sleeping through, or pausing on `rate-limit-exceeded-wall` for, a
9477
9476
  // wall this engine never hit.
9478
9477
  if (lastRateLimit && rateLimitedEngineId && tickEngineId && tickEngineId !== rateLimitedEngineId) {
9479
9478
  result.engine_swapped_from = rateLimitedEngineId;
@@ -9672,8 +9671,8 @@ async function executeMissionRunTicksPhase(context) {
9672
9671
  // held, so the next resume fails with "already in use". A session
9673
9672
  // that was cleaned up between ticks fails the resume the other way,
9674
9673
  // with "No conversation found with session ID". Both mean the stored
9675
- // id is dead. Session continuity is disposable mission state lives
9676
- // on disk (receipts, logs, now.md) so rotate to a fresh id instead
9674
+ // id is dead. Session continuity is disposable, mission state lives
9675
+ // on disk (receipts, logs, now.md), so rotate to a fresh id instead
9677
9676
  // of grinding the repeated-error breaker on a stale session.
9678
9677
  const staleSession = /session id .* is already in use/i.test(claudeResult.stderr || '')
9679
9678
  || /no conversation found with session id/i.test(claudeResult.stderr || '');
@@ -9701,7 +9700,7 @@ async function executeMissionRunTicksPhase(context) {
9701
9700
  break;
9702
9701
  }
9703
9702
  } else if (sessionId && claudeResult.sessionIds.length > 0 && !claudeResult.sessionIds.includes(sessionId)) {
9704
- // session_id mismatch on a resumed session abort run
9703
+ // session_id mismatch on a resumed session, abort run
9705
9704
  pauseReason = 'session-id-mismatch';
9706
9705
  break;
9707
9706
  }
@@ -9750,7 +9749,7 @@ async function executeMissionRunTicksPhase(context) {
9750
9749
  || worktreeReceipt(tickWorktreeBefore, gitWorktreeSnapshot(cwd), { verifier: frozen.verifier, baseline: runWorktreeBaseline });
9751
9750
 
9752
9751
  // Layer classification needs the receipt text AND the worktree receipt, so it
9753
- // runs here after both exist covering the claude and atris2 branches alike.
9752
+ // runs here, after both exist, covering the claude and atris2 branches alike.
9754
9753
  if (!(cachedStep && cachedStep.tick && cachedStep.tick.layer)) {
9755
9754
  const tickReceiptText = result.atris2?.receipt_text || result.claude?.receipt_text || result.drill?.receipt_text || '';
9756
9755
  const layerInfo = extractLayerFromReceiptText(tickReceiptText, tickWorktree?.new_since_baseline_sample);
@@ -9779,7 +9778,7 @@ async function executeMissionRunTicksPhase(context) {
9779
9778
  const fullBudgetMode = budgetRemainingSeconds > 0;
9780
9779
  // Worker may call `mission complete` mid-tick. Re-read disk so we do not
9781
9780
  // demote terminal status back to ready (always-on + verifier-pass used to
9782
- // reopen closed rooms mission 32 complete@19:23:33 → ready@19:23:48).
9781
+ // reopen closed rooms, mission 32 complete@19:23:33 → ready@19:23:48).
9783
9782
  const latestOnDisk = loadMissionMap(cwd).get(mission.id) || mission;
9784
9783
  const alreadyTerminal = TERMINAL_STATUSES.has(String(latestOnDisk.status || ''));
9785
9784
  let newStatus = (verifierResult?.passed && mission.always_on) ? 'ready' :
@@ -9872,7 +9871,7 @@ async function executeMissionRunTicksPhase(context) {
9872
9871
  ranTicks++;
9873
9872
  backoffAttempt = 0;
9874
9873
  } else if (result.status === 'errored' && result.reason !== 'claude-session-busy') {
9875
- // A rotated session is already healed the next tick starts on a
9874
+ // A rotated session is already healed, the next tick starts on a
9876
9875
  // fresh id, so backing off just burns wall clock. If rotation itself
9877
9876
  // keeps failing, the repeated-error breaker below still stops the run.
9878
9877
  backoffAttempt++;
@@ -9896,7 +9895,7 @@ async function executeMissionRunTicksPhase(context) {
9896
9895
  if (consecutiveIdenticalSummaryTicks(ticks) >= 3) { pauseReason = 'stuck-repeating'; break; }
9897
9896
  // A retired/inaccessible model is deterministic: the id is fixed for the run, so
9898
9897
  // every remaining tick (and every future cron firing) fails identically. Backoff
9899
- // only slows the bleeding. Stop on first detection and surface the dead id
9898
+ // only slows the bleeding. Stop on first detection and surface the dead id,
9900
9899
  // CLI-245 named this failure; this stops the loop from grinding on it forever.
9901
9900
  if (result.status === 'errored' && result.reason === 'model-unavailable') { pauseReason = 'model-unavailable'; break; }
9902
9901
  // Any OTHER error that recurs identically (claude-timeout, atris2-error, claude-error)
@@ -10063,12 +10062,12 @@ function completeMissionRunPhase(context) {
10063
10062
  }
10064
10063
  if (context.handled) return;
10065
10064
 
10066
- // BCK-1324: no-progress is a clean, honest stop the run did what it
10065
+ // BCK-1324: no-progress is a clean, honest stop, the run did what it
10067
10066
  // could and correctly recognized there was nothing left to do. It is NOT
10068
10067
  // a failure/blocker: pausing it (resumable, retried by cron/self-drive)
10069
10068
  // or dispatching handleMissionBlocker (files a fleet task, dispatches an
10070
10069
  // engine) would recreate the exact busywork loop this guard exists to
10071
- // stop. Give it its own branch, shaped like stopMission()'s stop path
10070
+ // stop. Give it its own branch, shaped like stopMission()'s stop path,
10072
10071
  // status=stopped, a receipt, no escalation, no blocker.
10073
10072
  if (pauseReason === 'no-progress') {
10074
10073
  const stoppedAt = stampIso();
@@ -10086,7 +10085,7 @@ function completeMissionRunPhase(context) {
10086
10085
  stop_reason: noProgressReason,
10087
10086
  receipt_path: noProgressReceipt || mission.receipt_path || null,
10088
10087
  worktree_baseline: baselineSummary || mission.worktree_baseline || null,
10089
- next_action: 'mission stopped: no progress inspect the last few receipts, then start a fresh mission or resume with new context',
10088
+ next_action: 'mission stopped: no progress, inspect the last few receipts, then start a fresh mission or resume with new context',
10090
10089
  }, cwd, 'mission_run_stopped_no_progress', {
10091
10090
  reason: noProgressReason,
10092
10091
  idle_ticks: idleCount,
@@ -10115,7 +10114,7 @@ function completeMissionRunPhase(context) {
10115
10114
  }).mission;
10116
10115
  }
10117
10116
 
10118
- // 'no-progress' is a clean stop the run diagnosed itself never route it
10117
+ // 'no-progress' is a clean stop the run diagnosed itself, never route it
10119
10118
  // through handleMissionBlocker (that would file a blocker task and
10120
10119
  // dispatch an engine to "fix" a mission that correctly stopped itself).
10121
10120
  const blockerReason = ['no-progress', 'budget-exhausted'].includes(pauseReason)
@@ -10235,7 +10234,7 @@ function tickMission(args) {
10235
10234
  }
10236
10235
 
10237
10236
  try {
10238
- // Re-read inside the lock the initial resolveMission ran before we held it.
10237
+ // Re-read inside the lock, the initial resolveMission ran before we held it.
10239
10238
  mission = resolveMission(mission.id) || mission;
10240
10239
 
10241
10240
  if (['complete', 'stopped'].includes(mission.status)) {
@@ -10250,7 +10249,7 @@ function tickMission(args) {
10250
10249
  // budget window from now BEFORE the budget check below reads it. Without this,
10251
10250
  // a spend-full-budget mission whose original window already elapsed, resumed
10252
10251
  // via `mission tick` rather than `mission run`, would compute zero remaining
10253
- // budget on this very tick and land immediately the footgun `mission run`
10252
+ // budget on this very tick and land immediately, the footgun `mission run`
10254
10253
  // already closes. Stamping resumed_at here makes both resume paths agree.
10255
10254
  mission = saveMission({
10256
10255
  ...mission,
@@ -10495,7 +10494,7 @@ function receiptShowsPass(receipt) {
10495
10494
  || result?.tick?.verifier_passed === true;
10496
10495
  }
10497
10496
 
10498
- // Terminal gate: a verifier mission may only complete on real evidence a
10497
+ // Terminal gate: a verifier mission may only complete on real evidence, a
10499
10498
  // passing receipt belonging to this mission, or durable state showing the
10500
10499
  // verifier passed. Mirrors the task plane's proof-only accept guard so the
10501
10500
  // final transition consumes the receipts instead of trusting free text.
@@ -10667,7 +10666,7 @@ function stopMission(args) {
10667
10666
  }
10668
10667
 
10669
10668
  // A mission parked in paused/planning/ready and untouched for a week is
10670
- // abandoned in practice nobody resumes it, and each one is a line the
10669
+ // abandoned in practice, nobody resumes it, and each one is a line the
10671
10670
  // operator re-reads forever. The daily autoland tick expires them to
10672
10671
  // stopped with a revive hint; running missions and anything touched
10673
10672
  // recently are never aged out, and a tick on an expired id revives it.
@@ -10709,7 +10708,7 @@ function expireStaleMissions(root = process.cwd(), { idleDays = MISSION_IDLE_EXP
10709
10708
  for (const mission of listMissions(root)) {
10710
10709
  const status = String(mission.status || '').toLowerCase();
10711
10710
  if (!statuses.has(status)) continue;
10712
- // updated_at and paused_at are machine-polluted status renders and
10711
+ // updated_at and paused_at are machine-polluted, status renders and
10713
10712
  // goal controllers re-save parked missions daily, so a mission nobody
10714
10713
  // has run since May reads as "touched today". Real activity is the
10715
10714
  // last tick (or creation, for missions that never ran).
@@ -10746,7 +10745,7 @@ function expireStaleMissions(root = process.cwd(), { idleDays = MISSION_IDLE_EXP
10746
10745
  }
10747
10746
 
10748
10747
  // Zombie-mission reap: a mission left paused past a short leash (48h default)
10749
- // is dead in practice long before the 7-day general idle expiry ever fires
10748
+ // is dead in practice long before the 7-day general idle expiry ever fires,
10750
10749
  // nobody is coming back to it inside a session, and it just sits on
10751
10750
  // `mission list` as noise. Narrower than expireStaleMissions on purpose: only
10752
10751
  // `paused`, never planning/ready, so the weekly cadence for those is unchanged.
@@ -11199,7 +11198,7 @@ function classifyPathsByLayer(paths) {
11199
11198
  return winnerLayer ? { layer: winnerLayer, source: 'fallback' } : { layer: null, source: 'unknown' };
11200
11199
  }
11201
11200
 
11202
- // `atris mission layers` per-layer growth curve across tick receipts. The member
11201
+ // `atris mission layers`, per-layer growth curve across tick receipts. The member
11203
11202
  // proof standard says: if every tick is one layer and none touch the others, the
11204
11203
  // loop is doing work but not getting smarter. This makes that check one command.
11205
11204
  function layersMission(args) {
@@ -11260,7 +11259,7 @@ function layersMission(args) {
11260
11259
  ...LAYERS.map((l) => ` ${l.padEnd(12)} ${String(byLayer[l]).padStart(3)}${byLayer[l] ? ' ' + '█'.repeat(Math.min(byLayer[l], 40)) : ''}`),
11261
11260
  ...(untagged ? [` untagged ${String(untagged).padStart(3)} (pre-layer receipts or missing tag)`] : []),
11262
11261
  ` provenance: explicit ${bySource.explicit}, explicit-inline ${bySource['explicit-inline']}, fallback ${bySource.fallback}`,
11263
- ...(skewed ? [` rebalance: ${Math.round((byLayer[dominant] / tagged) * 100)}% of tagged ticks are "${dominant}" the proof standard wants the other layers moving too`] : []),
11262
+ ...(skewed ? [` rebalance: ${Math.round((byLayer[dominant] / tagged) * 100)}% of tagged ticks are "${dominant}", the proof standard wants the other layers moving too`] : []),
11264
11263
  ];
11265
11264
  printJsonOrText({ ok: true, since: sinceRaw || null, total, tagged, untagged, by_layer: byLayer, by_source: bySource, dominant: tagged ? dominant : null, skewed }, lines, asJson);
11266
11265
  }
@@ -11349,7 +11348,7 @@ function findMissionAcrossWorktrees(ref, root = process.cwd()) {
11349
11348
  return null;
11350
11349
  }
11351
11350
 
11352
- // atris mission ping <id> "<message>" leave a note the mission's next tick
11351
+ // atris mission ping <id> "<message>", leave a note the mission's next tick
11353
11352
  // reads (and consumes) as operator direction. This is how you talk to an
11354
11353
  // always-on member mid-run without stopping it.
11355
11354
  function inspectMission(args) {
@@ -11428,7 +11427,7 @@ function pingMission(args, opts = {}) {
11428
11427
  if (asJson) {
11429
11428
  console.log(JSON.stringify({ ok: true, action: 'mission_ping', mission_id: saved.id, pending_pings: pending, ping }));
11430
11429
  } else {
11431
- console.log(`pinged ${saved.id} the next tick reads it (${pending} unread).`);
11430
+ console.log(`pinged ${saved.id}, the next tick reads it (${pending} unread).`);
11432
11431
  }
11433
11432
  }
11434
11433
  return saved;
@@ -11578,7 +11577,7 @@ function missionCommand(args) {
11578
11577
  if (first && !first.startsWith('-')) {
11579
11578
  // Shortcut forms: a mission id, or an objective (quoted or spread
11580
11579
  // across argv: `atris mission fix the issue`). Two shapes are
11581
- // mistyped verbs, not objectives a later arg naming a mission id
11580
+ // mistyped verbs, not objectives, a later arg naming a mission id
11582
11581
  // (`mission say <id> ...`), and a single bare word with no objective
11583
11582
  // after it. Creating a mission from those silently is worse than erroring.
11584
11583
  const positionals = args.filter((value) => !String(value).startsWith('-'));