atris 3.52.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 (131) 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/engines/SKILL.md +8 -7
  7. package/atris/skills/x-search/SKILL.md +24 -22
  8. package/atris/skills/youtube/SKILL.md +89 -40
  9. package/atris/team/_template/SOUL.md +40 -0
  10. package/atris/team/validator/MEMBER.md +1 -2
  11. package/ax +592 -91
  12. package/bin/atris +21 -0
  13. package/bin/atris.js +580 -378
  14. package/commands/api-key.js +170 -0
  15. package/commands/auth.js +453 -43
  16. package/commands/autoland.js +3 -1
  17. package/commands/autopilot-front.js +58 -11
  18. package/commands/avail.js +5 -4
  19. package/commands/balance.js +55 -0
  20. package/commands/bench.js +29 -1
  21. package/commands/brain.js +63 -12
  22. package/commands/brainstorm.js +18 -0
  23. package/commands/business.js +149 -1
  24. package/commands/chat-scan.js +17 -4
  25. package/commands/close.js +5 -1
  26. package/commands/computer.js +2 -2
  27. package/commands/doctor.js +174 -0
  28. package/commands/dream.js +7 -0
  29. package/commands/drill.js +34 -2
  30. package/commands/engine.js +47 -35
  31. package/commands/errors.js +58 -8
  32. package/commands/experiments.js +14 -0
  33. package/commands/feedback.js +61 -2
  34. package/commands/fleet-report.js +27 -7
  35. package/commands/fleet.js +98 -11
  36. package/commands/founder.js +60 -21
  37. package/commands/human-missions.js +79 -4
  38. package/commands/improve.js +54 -4
  39. package/commands/init.js +68 -92
  40. package/commands/install.js +40 -0
  41. package/commands/integrations.js +399 -30
  42. package/commands/interview.js +8 -0
  43. package/commands/land.js +48 -17
  44. package/commands/learn.js +29 -4
  45. package/commands/log.js +51 -0
  46. package/commands/member.js +68 -10
  47. package/commands/mission.js +339 -74
  48. package/commands/now.js +25 -6
  49. package/commands/orb.js +97 -2
  50. package/commands/pack.js +83 -9
  51. package/commands/playbook.js +381 -0
  52. package/commands/plugin.js +16 -0
  53. package/commands/radar.js +96 -40
  54. package/commands/recap.js +128 -21
  55. package/commands/release.js +102 -2
  56. package/commands/review.js +5 -10
  57. package/commands/revisions.js +55 -0
  58. package/commands/run-front.js +12 -4
  59. package/commands/scout.js +12 -1
  60. package/commands/search.js +123 -28
  61. package/commands/sign.js +2 -2
  62. package/commands/signup.js +12 -3
  63. package/commands/site-deploy.js +17 -4
  64. package/commands/site.js +2 -2
  65. package/commands/skill-eval.js +289 -0
  66. package/commands/skill.js +56 -6
  67. package/commands/social.js +603 -0
  68. package/commands/spaceship.js +69 -6
  69. package/commands/stream.js +8 -1
  70. package/commands/study.js +32 -19
  71. package/commands/sync.js +28 -22
  72. package/commands/task.js +418 -60
  73. package/commands/teach.js +72 -63
  74. package/commands/terminal.js +25 -9
  75. package/commands/topup.js +88 -0
  76. package/commands/truth.js +52 -6
  77. package/commands/usage.js +91 -0
  78. package/commands/verify.js +3 -2
  79. package/commands/version.js +3 -1
  80. package/commands/voice.js +5 -1
  81. package/commands/who.js +69 -11
  82. package/commands/wiki.js +7 -4
  83. package/commands/wish.js +76 -5
  84. package/commands/workflow.js +327 -145
  85. package/commands/worktree.js +1 -0
  86. package/commands/write.js +5 -0
  87. package/commands/x-search.js +340 -0
  88. package/commands/xp.js +5 -1
  89. package/commands/youtube.js +551 -33
  90. package/lib/account-bound.js +47 -0
  91. package/lib/auto-accept-certified.js +10 -2
  92. package/lib/auto-lessons.js +186 -0
  93. package/lib/autoland.js +4 -2
  94. package/lib/chat-log-scan.js +7 -4
  95. package/lib/cli-json.js +77 -0
  96. package/lib/cli-scope.js +26 -0
  97. package/lib/codex-flight.js +1 -0
  98. package/lib/conductor-artifacts.js +1 -1
  99. package/lib/context-gatherer.js +11 -0
  100. package/lib/developer-api.js +116 -0
  101. package/lib/engine-ask.js +11 -3
  102. package/lib/engine-registry.js +32 -7
  103. package/lib/feedback-triage.js +274 -0
  104. package/lib/first-minute.js +398 -0
  105. package/lib/fleet.js +115 -34
  106. package/lib/functional-owner.js +22 -0
  107. package/lib/known-commands.js +31 -5
  108. package/lib/member-scaffold.js +197 -0
  109. package/lib/mission-ledger-compact.js +127 -0
  110. package/lib/mission-root.js +4 -2
  111. package/lib/mission-runtime-loop.js +30 -2
  112. package/lib/next-moves.js +34 -34
  113. package/lib/noninteractive.js +88 -0
  114. package/lib/permission-grants.js +10 -1
  115. package/lib/policy-lessons.js +4 -1
  116. package/lib/revision-metric.js +279 -0
  117. package/lib/runner-command.js +5 -4
  118. package/lib/scratch-root.js +48 -0
  119. package/lib/skill-eval-gate.js +249 -0
  120. package/lib/task-db.js +22 -2
  121. package/lib/task-proof.js +75 -9
  122. package/lib/task-receipt.js +5 -1
  123. package/lib/verifier-quality.js +69 -0
  124. package/lib/wish-audit.js +2 -2
  125. package/lib/wish-delegate.js +10 -1
  126. package/lib/workspace-scaffold.js +270 -0
  127. package/package.json +4 -2
  128. package/scripts/det/checklist-score.js +191 -0
  129. package/scripts/det/ytsearch +31 -0
  130. package/scripts/outbound-artifact-gate.js +227 -0
  131. package/utils/update-check.js +15 -0
package/lib/fleet.js CHANGED
@@ -30,7 +30,7 @@ const {
30
30
  buildOneLapValidatorPrompt,
31
31
  parseOneLapValidatorVerdict,
32
32
  } = require('./one-lap-validator');
33
- const { listWorktrees } = require('../commands/worktree');
33
+ const { listWorktrees, defaultMainlineBase } = require('../commands/worktree');
34
34
  const { isConductorStatusLine } = require('./conductor-artifacts');
35
35
  const { matchLessons } = require('./lesson-preflight');
36
36
  const { matchTaste } = require('./taste-lessons');
@@ -81,7 +81,7 @@ const ATRIS_BUILD_PROCESS_PREAMBLE = [
81
81
  'claim the task, read the map, and work only the named contract.',
82
82
  'run every gate bare and read its real exit code.',
83
83
  'report honestly, including failures, and stop at the named stop point.',
84
- 'never use --no-verify.',
84
+ 'if a git hook blocks your commit, stop and report it; never use --no-verify.',
85
85
  ].join('\n');
86
86
 
87
87
  // The bounded prompt every engine gets. Same contract the manual flight used:
@@ -109,6 +109,7 @@ function buildFleetPrompt(task, { worktreePath, yolo = false } = {}) {
109
109
  lines.push(
110
110
  'Rules:',
111
111
  '- Read atris/MAP.md first to locate the code; never guess file locations.',
112
+ '- Keep one concern per PR; split anything larger into separate PRs because git history guides future agents and small PRs are cheap to revert and bisect.',
112
113
  '- Run git status first. Stage ONLY files you changed. Never revert or touch files another agent modified.',
113
114
  '- Include or update a focused regression test; run it with npm run test:fast && node --test <focused files> before committing.',
114
115
  `- ${commitRule}`,
@@ -161,6 +162,21 @@ const YOLO_ENGINE_FLAGS = Object.freeze({
161
162
  claude: '--dangerously-skip-permissions',
162
163
  });
163
164
  const DISPATCH_SELF_LAND_TARGET = 'origin/master';
165
+
166
+ // Dispatch used to cut every build worktree from origin/master and check
167
+ // self-landing against it, hard-coded. A workspace whose protected branch is
168
+ // `main` had no origin/master, so the worktree cut and the ancestry check both
169
+ // failed. Resolve the workspace's real protected branch instead (remote HEAD,
170
+ // then origin/main), falling back to the historical origin/master when nothing
171
+ // else can be determined so single-branch repos and test roots behave as before.
172
+ function resolveDispatchLandTarget(root, explicitBase) {
173
+ if (explicitBase) return explicitBase;
174
+ try {
175
+ const mainline = defaultMainlineBase(root);
176
+ if (mainline && mainline !== 'HEAD') return mainline;
177
+ } catch {}
178
+ return DISPATCH_SELF_LAND_TARGET;
179
+ }
164
180
  const DEFAULT_DISPATCH_TIMEOUT_MS = 900000;
165
181
  const CODEX_DISPATCH_TIMEOUT_MS = 3660000;
166
182
  const CODEX_WATCHDOG_SOURCE = path.join(__dirname, '..', 'scripts', 'det', 'codex-watchdog.js');
@@ -213,12 +229,18 @@ function buildEngineCommand(engineName, promptFile, { yolo = false, sealed = fal
213
229
  }
214
230
  if (sealed && engineName === 'cursor') cmd = `${cmd} --sandbox enabled`;
215
231
  if (sealed && engineName === 'devin') return cmd.replace(/^devin -p /, 'devin -p --sandbox --permission-mode accept-edits ');
232
+ // Headless print mode has one write gate: blocked by default, open with
233
+ // --yolo (--auto-accept is interactive-only and does not lift it). Fleet
234
+ // builds always run in an isolated worktree, so --yolo here rides the
235
+ // same trust model as every other engine's sealed mode.
236
+ if (sealed && engineName === 'commandcode') cmd = `${cmd} --yolo --trust`;
216
237
  if (sealed && engineName === 'grok') {
217
238
  cmd = `${cmd.replace(/\s+--always-approve\b/, '')} --sandbox enabled --permission-mode acceptEdits --no-memory --no-subagents`;
218
239
  }
219
240
  if (engineName === 'devin') return cmd.replace(/^devin -p /, 'devin -p --permission-mode dangerous ');
220
241
  if (yolo && engineName === 'codex') cmd = cmd.replace(/\bexec\b/, `exec ${YOLO_ENGINE_FLAGS.codex}`);
221
242
  if (yolo && (engineName === 'claude' || engineName === 'fable')) cmd = `${cmd} ${YOLO_ENGINE_FLAGS.claude}`;
243
+ if (yolo && engineName === 'commandcode') cmd = `${cmd} --yolo`;
222
244
  if (engineName === 'codex') cmd = wrapCodexWithWatchdog(cmd, watchdogPath, watchdogReceiptPath);
223
245
  return cmd;
224
246
  } finally {
@@ -923,10 +945,14 @@ function assignEngines(staffed, engines) {
923
945
  // conductor or a human can take it. `git` is injectable for tests.
924
946
  // Ship failures print the cause first (MODULE_NOT_FOUND, command name) and a
925
947
  // long node stack after. Tail-only clips hide the cause; keep a head+tail.
926
- function clipHeadTail(text, { head = 400, tail = 300 } = {}) {
927
- const s = String(text || '');
948
+ function headTail(str, head = 400, tail = 500) {
949
+ const s = String(str || '');
928
950
  if (s.length <= head + tail) return s;
929
- return `${s.slice(0, head)}\n...\n${s.slice(-tail)}`;
951
+ return `${s.slice(0, head)}${s.slice(-tail)}`;
952
+ }
953
+
954
+ function clipHeadTail(text, { head = 400, tail = 500 } = {}) {
955
+ return headTail(text, head, tail);
930
956
  }
931
957
 
932
958
  function dispatchResultToTerminal(entry = {}, result = {}) {
@@ -964,11 +990,11 @@ function shipWithRetry(cli, args, cwd) {
964
990
  return shipped;
965
991
  }
966
992
 
967
- function shipFailureDetail(shipped, { head = 400, tail = 300 } = {}) {
993
+ function shipFailureDetail(shipped, { head = 400, tail = 500 } = {}) {
968
994
  const combined = `${shipped.stderr || ''}\n${shipped.stdout || ''}`;
969
995
  const moduleLine = combined.match(/Cannot find module '[^']+'[^\n]*/);
970
996
  const cause = moduleLine ? `${moduleLine[0]}${shipped.retried_module_not_found ? ' (persisted after one retry)' : ''}\n` : '';
971
- return `${cause}${clipHeadTail(combined.trim(), { head, tail })}`;
997
+ return `${cause}${headTail(combined.trim(), head, tail)}`;
972
998
  }
973
999
 
974
1000
  function fleetShipArgs(entry, check) {
@@ -1095,6 +1121,7 @@ function landArrival({ worktreePath, git = null }) {
1095
1121
  module.exports = {
1096
1122
  DENIED_TAGS,
1097
1123
  DEAD_ENGINE_OUTPUT_PATTERNS,
1124
+ releasePausedClaimsWithoutWork,
1098
1125
  reviewOnlyEngineEnvironment,
1099
1126
  runInReapedProcessGroup,
1100
1127
  shipWithRetry,
@@ -1108,6 +1135,7 @@ module.exports = {
1108
1135
  get prepareReviewSandbox() { return prepareReviewSandbox; },
1109
1136
  YOLO_ENGINE_FLAGS,
1110
1137
  DISPATCH_SELF_LAND_TARGET,
1138
+ resolveDispatchLandTarget,
1111
1139
  parseDoneCheck,
1112
1140
  METHOD_KERNEL,
1113
1141
  ATRIS_BUILD_PROCESS_PREAMBLE,
@@ -1131,6 +1159,7 @@ module.exports = {
1131
1159
  dispatchResultToTerminal,
1132
1160
  failedDispatchLeg,
1133
1161
  fleetShipArgs,
1162
+ headTail,
1134
1163
  clipHeadTail,
1135
1164
  guardGlobalCliLink,
1136
1165
  defaultSelfLandCheck,
@@ -1142,7 +1171,7 @@ module.exports = {
1142
1171
  // Engines that can edit a repo headlessly. atris-fast (ax) is a chat lane,
1143
1172
  // not a repo worker — it keeps owning normal mission ticks, not fleet builds.
1144
1173
  const FLEET_CAPABLE = ['claude', 'codex', 'cursor', 'devin', 'grok'];
1145
- const DISPATCH_CAPABLE = [...FLEET_CAPABLE, 'fable'];
1174
+ const DISPATCH_CAPABLE = [...FLEET_CAPABLE, 'fable', 'commandcode'];
1146
1175
 
1147
1176
  let receiptSequence = 0;
1148
1177
  function nowStamp() {
@@ -1880,7 +1909,7 @@ function bindOneLapProof(worktreePath, proofRef, sourceCommit) {
1880
1909
  }
1881
1910
 
1882
1911
  function importReviewCommit(boundary, { cli, taskId, engine, expectedCommit = '', expectedTree = '', startBaseArgs = [] } = {}) {
1883
- const fail = (stage, detail, extra = {}) => ({ ok: false, stage, detail: String(detail || '').trim().slice(-500), ...extra });
1912
+ const fail = (stage, detail, extra = {}) => ({ ok: false, stage, detail: headTail(String(detail || '').trim()), ...extra });
1884
1913
  const runSandbox = (args) => spawnSync('git', args, {
1885
1914
  cwd: boundary.worktreePath,
1886
1915
  encoding: 'utf8',
@@ -2193,7 +2222,7 @@ async function runIndependentValidator({
2193
2222
  if (exitCode !== 0) {
2194
2223
  const outage = detectDeadEngineDispatch(dispatched);
2195
2224
  if (outage && outage.reason === 'usage_limit' && index + 1 < candidates.length) continue;
2196
- validatorResult.reason = String(dispatched.stderr || verdict.reason || `validator exited ${exitCode}`).trim().slice(-500);
2225
+ validatorResult.reason = headTail(String(dispatched.stderr || verdict.reason || `validator exited ${exitCode}`).trim());
2197
2226
  return { ok: false, stage: 'validator_failed', validator_result: validatorResult };
2198
2227
  }
2199
2228
  if (verdict.verdict === 'reject') return { ok: false, stage: 'validation_rejected', validator_result: validatorResult };
@@ -2607,7 +2636,7 @@ async function runFleetFlight({
2607
2636
  const check = focusedCheck(entry.task) || 'git log -1 --oneline';
2608
2637
  const shipped = shipWithRetry(cli, fleetShipArgs(entry, check), entry.worktreePath);
2609
2638
  if (shipped.status !== 0 || !/done: worktree shipped/.test(shipped.stdout)) {
2610
- return { ok: false, stage: 'ship', detail: shipFailureDetail(shipped, { head: 400, tail: 300 }) };
2639
+ return { ok: false, stage: 'ship', detail: shipFailureDetail(shipped) };
2611
2640
  }
2612
2641
  return { ok: true, stage: 'shipped' };
2613
2642
  });
@@ -2643,7 +2672,7 @@ async function runFleetFlight({
2643
2672
  const restaffProof = result.restaffed ? ` Restaffed from ${result.restaffed.from} to ${result.restaffed.to} (${result.restaffed.reason}).` : '';
2644
2673
  cli([
2645
2674
  'task', 'ready', String(entry.task.display_id),
2646
- '--proof', `Built by ${activeEngine} engine in fleet flight.${restaffProof} Landed via worktree ship gate (rebase-before-ship, verify re-run). Receipt saved at ${path.relative(root, receiptPath)}. Report tail: ${String(result.report || '').slice(-300).replace(/\n/g, ' ')}`,
2675
+ '--proof', `Built by ${activeEngine} engine in fleet flight.${restaffProof} Landed via worktree ship gate (rebase-before-ship, verify re-run). Receipt saved at ${path.relative(root, receiptPath)}. Report tail: ${headTail(String(result.report || '').replace(/\n/g, ' '))}`,
2647
2676
  '--result', 'Operators can now review fleet-shipped work faster because the worktree was verified before landing.',
2648
2677
  '--as', `fleet-${activeEngine}`,
2649
2678
  ]);
@@ -2667,6 +2696,14 @@ async function runFleetFlight({
2667
2696
  : ` alert: global atris link was ${cliLink.reason}; repoint attempted at ${cliLink.restoredTo} but the cli still fails, so every cron/loop/mission is down`);
2668
2697
  }
2669
2698
 
2699
+ releasePausedClaimsWithoutWork(cli, {
2700
+ paused: flight.paused,
2701
+ worktreeByTask: new Map(prepared.map((entry) => [String(entry.task.display_id), entry.worktreePath])),
2702
+ actorByTask: new Map(staffed.map((entry) => [String(entry.task.display_id), `fleet-${entry.engine}`])),
2703
+ baseRef: checkoutBase || 'origin/master',
2704
+ log,
2705
+ });
2706
+
2670
2707
  flight.status = flight.paused.length ? 'failed' : 'completed';
2671
2708
  flight.finished_at = new Date().toISOString();
2672
2709
  atomicWriteFlightReceipt(flight, receiptPath);
@@ -2691,6 +2728,38 @@ function releaseUnstartedDispatchClaim(cli, { taskId, actor, detail }) {
2691
2728
  return { released: releasedOk, detail: base ? `${base}. ${suffix}` : suffix };
2692
2729
  }
2693
2730
 
2731
+ // A pause with no work must not keep the task. On 2026-08-21, 49 bug tasks sat
2732
+ // falsely "in progress" because guard-blocked engines paused their flights
2733
+ // while the claims taken before dispatch outlived them. After landing, any
2734
+ // paused task whose worktree holds no commits past base and nothing but
2735
+ // conductor litter gets its claim released; a paused task WITH work keeps the
2736
+ // claim so takeover stays attributed.
2737
+ function releasePausedClaimsWithoutWork(cli, { paused, worktreeByTask, actorByTask, baseRef = 'origin/master', log = () => {} }) {
2738
+ for (const row of paused || []) {
2739
+ if (row.stage === 'claim' || row.stage === 'task_lookup') continue; // claim never held
2740
+ if (row.claim_released !== undefined) continue;
2741
+ const taskId = String(row.task || '');
2742
+ const actor = actorByTask.get(taskId);
2743
+ if (!taskId || !actor) continue;
2744
+ const worktreePath = row.worktree || worktreeByTask.get(taskId) || '';
2745
+ if (worktreePath && fs.existsSync(worktreePath)) {
2746
+ const ahead = spawnSync('git', ['-C', worktreePath, 'rev-list', '--count', `${baseRef}..HEAD`], { encoding: 'utf8' });
2747
+ if (ahead.status !== 0 || parseInt(String(ahead.stdout).trim(), 10) > 0) continue;
2748
+ // -uall lists untracked files one by one; the default collapses a fully
2749
+ // untracked .atris/ into "?? .atris/", which the allowlist cannot judge.
2750
+ const status = spawnSync('git', ['-C', worktreePath, 'status', '--porcelain', '-uall'], { encoding: 'utf8' });
2751
+ if (status.status !== 0) continue;
2752
+ const dirt = String(status.stdout || '').split('\n').filter(Boolean).filter((line) => !isConductorStatusLine(line));
2753
+ if (dirt.length > 0) continue;
2754
+ } else if (row.stage !== 'worktree_start' && row.stage !== 'remote_quarantine' && row.stage !== 'prepare') {
2755
+ continue; // worktree gone and stage ambiguous: leave the claim for a human
2756
+ }
2757
+ const released = cli(['task', 'release', taskId, '--as', actor]);
2758
+ row.claim_released = Boolean(released && released.status === 0);
2759
+ if (row.claim_released) log(` released claim on ${taskId} (engine produced no work)`);
2760
+ }
2761
+ }
2762
+
2694
2763
  // The manual version of this loop took 6 Bash calls per task the night this
2695
2764
  // was written: claim, worktree start, prompt file, engine -p, verify, ship.
2696
2765
  // One or more explicit task ids build in parallel isolated worktrees on ONE
@@ -2710,7 +2779,7 @@ async function runDispatchFlight({
2710
2779
  lander = null,
2711
2780
  verifier = null,
2712
2781
  rebase = null,
2713
- checkoutBase = 'origin/master',
2782
+ checkoutBase = null,
2714
2783
  installedEngines = null,
2715
2784
  selfLandCheck = null,
2716
2785
  startCommitReader = null,
@@ -2746,6 +2815,9 @@ async function runDispatchFlight({
2746
2815
  }
2747
2816
 
2748
2817
  const cli = ownCli || defaultOwnCli(root);
2818
+ // Cut worktrees from — and check self-landing against — the workspace's real
2819
+ // protected branch, not a hard-coded origin/master (CLI-1298).
2820
+ const landTarget = resolveDispatchLandTarget(root, checkoutBase);
2749
2821
  const verify = verifier || (trustedVerifier ? defaultTrustedVerifyRunner : defaultVerifyRunner);
2750
2822
  const inspectChange = changeInspector || inspectReviewChange;
2751
2823
  const readStartCommit = startCommitReader || defaultDispatchStartCommit;
@@ -2775,9 +2847,9 @@ async function runDispatchFlight({
2775
2847
  log(` dispatch — ${ids.length} task${ids.length === 1 ? '' : 's'} -> ${engine}`);
2776
2848
  log('');
2777
2849
 
2778
- // Same rule as the fleet: dispatch worktrees cut from origin/master by
2850
+ // Same rule as the fleet: dispatch worktrees cut from the protected branch by
2779
2851
  // default so rebase-before-ship never replays a launcher feature branch.
2780
- const startBaseArgs = checkoutBase ? ['--base', checkoutBase] : [];
2852
+ const startBaseArgs = landTarget ? ['--base', landTarget] : [];
2781
2853
  const prepared = [];
2782
2854
  for (const taskId of ids) {
2783
2855
  const task = readTaskById(cli, taskId);
@@ -2807,7 +2879,7 @@ async function runDispatchFlight({
2807
2879
  actor: taskActor,
2808
2880
  detail: String(started.stderr || '').slice(0, 200),
2809
2881
  });
2810
- flight.paused.push({ task: taskId, stage: 'worktree_start', detail: released.detail });
2882
+ flight.paused.push({ task: taskId, stage: 'worktree_start', detail: released.detail, claim_released: released.released });
2811
2883
  log(` ✗ ${taskId} worktree start failed`);
2812
2884
  continue;
2813
2885
  }
@@ -2817,13 +2889,14 @@ async function runDispatchFlight({
2817
2889
  const released = releaseUnstartedDispatchClaim(cli, {
2818
2890
  taskId,
2819
2891
  actor: taskActor,
2820
- detail: String(remoteBoundary && remoteBoundary.detail || 'could not prepare a sealed review sandbox').slice(-500),
2892
+ detail: headTail(String(remoteBoundary && remoteBoundary.detail || 'could not prepare a sealed review sandbox')),
2821
2893
  });
2822
2894
  flight.paused.push({
2823
2895
  task: taskId,
2824
2896
  stage: 'remote_quarantine',
2825
2897
  detail: released.detail,
2826
2898
  worktree: null,
2899
+ claim_released: released.released,
2827
2900
  });
2828
2901
  log(` paused ${taskId} because its sealed review sandbox could not be prepared`);
2829
2902
  continue;
@@ -2855,7 +2928,7 @@ async function runDispatchFlight({
2855
2928
  actor: taskActor,
2856
2929
  detail: String(err && err.message || err).slice(0, 200),
2857
2930
  });
2858
- flight.paused.push({ task: taskId, stage: 'prepare', detail: released.detail });
2931
+ flight.paused.push({ task: taskId, stage: 'prepare', detail: released.detail, claim_released: released.released });
2859
2932
  log(` ✗ ${taskId} prepare failed`);
2860
2933
  }
2861
2934
  }
@@ -2942,12 +3015,12 @@ async function runDispatchFlight({
2942
3015
  const candidate = reviewCandidateSnapshot(entry.remoteBoundary, entry.candidate);
2943
3016
  if (!candidate.ok) return candidate;
2944
3017
  }
2945
- const change = reviewOnly ? inspectChange(entry.worktreePath, checkoutBase || 'origin/master') : null;
3018
+ const change = reviewOnly ? inspectChange(entry.worktreePath, landTarget) : null;
2946
3019
  if (reviewOnly && (!change || change.has_change !== true)) {
2947
3020
  return {
2948
3021
  ok: false,
2949
3022
  stage: 'no_change',
2950
- detail: String(change && change.detail || 'the engine produced no committed or worktree diff').slice(-500),
3023
+ detail: headTail(String(change && change.detail || 'the engine produced no committed or worktree diff')),
2951
3024
  change: change || null,
2952
3025
  };
2953
3026
  }
@@ -2975,7 +3048,7 @@ async function runDispatchFlight({
2975
3048
  return {
2976
3049
  ok: false,
2977
3050
  stage: 'verify_failed',
2978
- detail: `${verified.stdout}${verified.stderr}`.slice(-500),
3051
+ detail: headTail(`${verified.stdout}${verified.stderr}`),
2979
3052
  verifyOutput: `${verified.stdout}${verified.stderr}`,
2980
3053
  check,
2981
3054
  verifier_result: verifierResult,
@@ -2997,7 +3070,7 @@ async function runDispatchFlight({
2997
3070
  }
2998
3071
  const shipped = shipWithRetry(cli, fleetShipArgs({ task: entry.task, engine: entry.engine || engine }, check), entry.worktreePath);
2999
3072
  if (shipped.status !== 0 || !/done: worktree shipped/.test(shipped.stdout)) {
3000
- return { ok: false, stage: 'ship', detail: shipFailureDetail(shipped, { head: 400, tail: 500 }) };
3073
+ return { ok: false, stage: 'ship', detail: shipFailureDetail(shipped) };
3001
3074
  }
3002
3075
  return {
3003
3076
  ok: true,
@@ -3035,7 +3108,7 @@ async function runDispatchFlight({
3035
3108
  task: entry.taskId,
3036
3109
  engine: activeEngine,
3037
3110
  stage: candidate && candidate.stage || 'candidate_changed',
3038
- detail: String(candidate && candidate.detail || 'the executor candidate could not be frozen').slice(-500),
3111
+ detail: headTail(String(candidate && candidate.detail || 'the executor candidate could not be frozen')),
3039
3112
  worktree: entry.worktreePath,
3040
3113
  };
3041
3114
  flight.paused.push(paused);
@@ -3066,11 +3139,11 @@ async function runDispatchFlight({
3066
3139
  entry,
3067
3140
  result,
3068
3141
  worktreePath: entry.worktreePath,
3069
- targetRef: DISPATCH_SELF_LAND_TARGET,
3142
+ targetRef: landTarget,
3070
3143
  startCommit: entry.startCommit,
3071
3144
  });
3072
3145
  if (selfLanded.ok) {
3073
- const target = selfLanded.target || DISPATCH_SELF_LAND_TARGET;
3146
+ const target = selfLanded.target || landTarget;
3074
3147
  flight.landed.push({
3075
3148
  task: entry.taskId,
3076
3149
  engine: activeEngine,
@@ -3092,7 +3165,7 @@ async function runDispatchFlight({
3092
3165
  task: entry.taskId,
3093
3166
  engine,
3094
3167
  stage,
3095
- target: selfLanded.target || DISPATCH_SELF_LAND_TARGET,
3168
+ target: selfLanded.target || landTarget,
3096
3169
  detail: selfLanded.detail || '',
3097
3170
  });
3098
3171
  stampDispatchBrief(root, result.brief_id, 'partial', stage === 'no_work_landed'
@@ -3136,13 +3209,13 @@ async function runDispatchFlight({
3136
3209
  continue;
3137
3210
  }
3138
3211
  finalMaster = boundaryBeforeReview.protected_master || finalMaster;
3139
- const change = landed.change || inspectChange(entry.worktreePath, checkoutBase || 'origin/master');
3212
+ const change = landed.change || inspectChange(entry.worktreePath, landTarget);
3140
3213
  if (!change || change.has_change !== true) {
3141
3214
  flight.paused.push({
3142
3215
  task: entry.taskId,
3143
3216
  engine: activeEngine,
3144
3217
  stage: 'no_change',
3145
- detail: String(change && change.detail || 'the engine produced no committed or worktree diff').slice(-500),
3218
+ detail: headTail(String(change && change.detail || 'the engine produced no committed or worktree diff')),
3146
3219
  check: landed.check,
3147
3220
  verifier_result: landed.verifier_result,
3148
3221
  worktree: entry.worktreePath,
@@ -3172,7 +3245,7 @@ async function runDispatchFlight({
3172
3245
  task: entry.taskId,
3173
3246
  engine: activeEngine,
3174
3247
  stage: validation.stage,
3175
- detail: String(validatorResult && validatorResult.reason || 'independent validator failed').slice(-500),
3248
+ detail: headTail(String(validatorResult && validatorResult.reason || 'independent validator failed')),
3176
3249
  check: landed.check,
3177
3250
  verifier_result: landed.verifier_result,
3178
3251
  validator_result: validatorResult,
@@ -3331,14 +3404,14 @@ async function runDispatchFlight({
3331
3404
  writeDispatchReceipt(flight, receiptPath, { ids, reviewOnly, enforceRemoteBoundary });
3332
3405
  const verificationCommit = String(reviewChange.source_commit || reviewChange.commit || reviewChange.head || '').trim();
3333
3406
  const verificationCitation = verificationCommit
3334
- ? ` Verification snapshot: commit ${verificationCommit}${reviewChange.dirty ? ' with a worktree diff' : ''}; ${landed.check} passed (exit 0).`
3335
- : ` Verification snapshot: the worktree diff was present; ${landed.check} passed (exit 0).`;
3407
+ ? ` Verification snapshot: commit ${verificationCommit}${reviewChange.dirty ? ' with a worktree diff' : ''}; the declared verifier passed (exit 0).`
3408
+ : ' Verification snapshot: the worktree diff was present; the declared verifier passed (exit 0).';
3336
3409
  const validatorCitation = validatorResult
3337
3410
  ? ` Independent validator ${validatorResult.engine} signed off: ${validatorResult.reason}.`
3338
3411
  : '';
3339
3412
  const readyResult = cli([
3340
3413
  'task', 'ready', entry.taskId,
3341
- '--proof', `Built by ${activeEngine} engine via one-lap dispatch.${restaffProof} Preserved in isolated worktree ${reviewWorktreePath}. Check re-run: ${landed.check}.${verificationCitation}${validatorCitation} Verify output: ${verifyTail || '(command produced no output, exit 0)'}. Receipt saved at ${path.relative(root, receiptPath)}.`,
3414
+ '--proof', `Built by ${activeEngine} engine via one-lap dispatch.${restaffProof} Preserved in isolated worktree ${reviewWorktreePath}.${verificationCitation}${validatorCitation} Verify output: ${verifyTail || '(command produced no output, exit 0)'}. Receipt saved at ${path.relative(root, receiptPath)}.`,
3342
3415
  '--result', 'Operators can review a completed, verified change before it lands, reducing the risk of unreviewed changes reaching users.',
3343
3416
  '--landing', 'Operators can review the verified change before it reaches users, reducing the risk of an unreviewed release.',
3344
3417
  '--checked', `${landed.check} passed in the isolated worktree`,
@@ -3351,7 +3424,7 @@ async function runDispatchFlight({
3351
3424
  task: entry.taskId,
3352
3425
  engine: activeEngine,
3353
3426
  stage: 'task_ready',
3354
- detail: String(readyResult && (readyResult.stderr || readyResult.stdout) || 'task ready failed').slice(-500),
3427
+ detail: headTail(String(readyResult && (readyResult.stderr || readyResult.stdout) || 'task ready failed')),
3355
3428
  check: landed.check,
3356
3429
  verifier_result: landed.verifier_result,
3357
3430
  worktree: reviewWorktreePath,
@@ -3375,7 +3448,7 @@ async function runDispatchFlight({
3375
3448
  stampDispatchBrief(root, result.brief_id, 'pass', `landed ${entry.taskId} via engine dispatch`);
3376
3449
  cli([
3377
3450
  'task', 'ready', entry.taskId,
3378
- '--proof', `Built by ${activeEngine} engine via atris engine dispatch.${restaffProof} Landed via worktree ship gate (rebase-before-ship, verify re-run). Check re-run: ${landed.check}. Verify output: ${verifyTail || '(command produced no output, exit 0)'}. Receipt saved at ${path.relative(root, receiptPath)}.`,
3451
+ '--proof', `Built by ${activeEngine} engine via atris engine dispatch.${restaffProof} Landed via worktree ship gate (rebase-before-ship, verify re-run). The declared verifier passed (exit 0). Verify output: ${verifyTail || '(command produced no output, exit 0)'}. Receipt saved at ${path.relative(root, receiptPath)}.`,
3379
3452
  '--result', 'Operators can now review engine-built work faster because dispatch reran the verifier before landing.',
3380
3453
  '--landing', 'The verified change is on master and ready for review.',
3381
3454
  '--checked', `${landed.check} passed before landing`,
@@ -3396,6 +3469,14 @@ async function runDispatchFlight({
3396
3469
  }
3397
3470
  }
3398
3471
 
3472
+ releasePausedClaimsWithoutWork(cli, {
3473
+ paused: flight.paused,
3474
+ worktreeByTask: new Map(prepared.map((entry) => [String(entry.taskId), entry.worktreePath])),
3475
+ actorByTask: new Map(ids.map((taskId) => [String(taskId), taskActor])),
3476
+ baseRef: landTarget,
3477
+ log,
3478
+ });
3479
+
3399
3480
  flight.status = flight.paused.length ? 'failed' : 'completed';
3400
3481
  flight.finished_at = new Date().toISOString();
3401
3482
  writeDispatchReceipt(flight, receiptPath, { ids, reviewOnly, enforceRemoteBoundary });
@@ -124,6 +124,16 @@ function resolveFunctionalOwner({ requestedOwner, title, tag, note, goal, root,
124
124
  const tagText = tag && tag !== true ? String(tag).toLowerCase() : '';
125
125
  const text = [highSignalText, tagText].filter(Boolean).join(' ');
126
126
 
127
+ // Existing workspace members win, even when the slug is also an engine name
128
+ // (starter team ships `executor`; dogfood expects --owner executor to stick).
129
+ if (requested && existingMembers.has(requested)) {
130
+ return {
131
+ owner: requested,
132
+ reason: 'explicit_member_owner',
133
+ requested_owner: requested,
134
+ };
135
+ }
136
+
127
137
  if (requested && !isEngineOwner(requested)) {
128
138
  return {
129
139
  owner: requested,
@@ -137,11 +147,15 @@ function resolveFunctionalOwner({ requestedOwner, title, tag, note, goal, root,
137
147
  existingMembers
138
148
  );
139
149
  if (exact) {
150
+ const remapped = Boolean(requested && isEngineOwner(requested) && requested !== exact);
140
151
  return {
141
152
  owner: exact,
142
153
  reason: requested ? 'engine_owner_resolved_by_exact_member' : 'inferred_exact_member',
143
154
  requested_owner: requested || null,
144
155
  executed_by: requested && isEngineOwner(requested) ? requested : null,
156
+ ...(remapped ? {
157
+ remap_reason: `"${requested}" is an engine name with no matching member folder; using ${exact}. Override: atris mission start "<objective>" --owner <existing-member> --verify "..."`,
158
+ } : {}),
145
159
  };
146
160
  }
147
161
 
@@ -158,23 +172,31 @@ function resolveFunctionalOwner({ requestedOwner, title, tag, note, goal, root,
158
172
  .sort((a, b) => b.score - a.score);
159
173
  const inferred = firstAvailableFunctionalOwner(scored.map(row => row.owner), existingMembers);
160
174
  if (inferred) {
175
+ const remapped = Boolean(requested && isEngineOwner(requested) && requested !== inferred);
161
176
  return {
162
177
  owner: inferred,
163
178
  reason: requested ? 'engine_owner_resolved_by_task_signal' : 'inferred_by_task_signal',
164
179
  requested_owner: requested || null,
165
180
  executed_by: requested && isEngineOwner(requested) ? requested : null,
181
+ ...(remapped ? {
182
+ remap_reason: `"${requested}" is an engine name with no matching member folder; using ${inferred}. Override: atris mission start "<objective>" --owner <existing-member> --verify "..."`,
183
+ } : {}),
166
184
  };
167
185
  }
168
186
 
169
187
  const fallback = firstAvailableFunctionalOwner(fallbackOwners || ['architect', 'task-planner', 'mission-lead', 'validator'], existingMembers)
170
188
  || Array.from(existingMembers).find(owner => !isEngineOwner(owner))
171
189
  || 'architect';
190
+ const remapped = Boolean(requested && requested !== fallback);
172
191
  return {
173
192
  owner: fallback,
174
193
  reason: requested ? 'engine_owner_resolved_to_factory_owner' : 'missing_functional_owner_fit',
175
194
  requested_owner: requested || null,
176
195
  executed_by: requested && isEngineOwner(requested) ? requested : null,
177
196
  proposed_member: proposedMemberSlugFromTask({ title, tag }),
197
+ ...(remapped ? {
198
+ remap_reason: `"${requested}" could not be kept as owner; using ${fallback}. Override: atris mission start "<objective>" --owner <existing-member> --verify "..."`,
199
+ } : {}),
178
200
  };
179
201
  }
180
202
 
@@ -1,11 +1,11 @@
1
1
  'use strict';
2
2
 
3
- const knownCommands = ['init', 'log', 'logs', 'wish', 'ask', 'approve', 'stop', 'ready', 'check', 'drill', 'dream', 'now', 'goal', 'wtf', 'founder', 'orb', 'radar', 'who', 'stream', 'ctop', 'launchpad', 'status', 'analytics', 'visualize', 'brain', 'brainstorm', 'autopilot', 'run', '_start', 'plan', 'do', 'review', 'release',
3
+ const knownCommands = ['init', 'doctor', 'install', 'workspace', 'log', 'logs', 'wish', 'ask', 'approve', 'stop', 'ready', 'check', 'drill', 'dream', 'now', 'goal', 'wtf', 'founder', 'orb', 'radar', 'who', 'stream', 'ctop', 'launchpad', 'status', 'analytics', 'revisions', 'visualize', 'brain', 'brainstorm', 'autopilot', 'run', 'plan', 'do', 'review', 'release',
4
4
  'activate', '_activate', 'agent', 'team', 'chat', 'fast', 'ax', 'console', 'serve', 'login', 'logout', 'whoami', 'switch', 'use', 'accounts', '_resolve', '_profile-email', '_switch-session', 'shell-init', 'update', 'upgrade', 'version', 'help', 'next', 'atris',
5
- 'clean', 'close', 'harvest', 'verify', 'recover', 'search', 'scout', 'skill', 'member', 'codex-goal', 'app', 'apps', 'learn', 'lesson', 'taste', 'teach', 'plugin', 'experiments', 'bench', 'router', 'receipt', 'proof', 'openclaw', 'pull', 'push', 'watch', 'cloud', 'live', 'align', 'terminal', 'computer', 'diff', 'business', 'sync', 'youtube',
5
+ 'clean', 'close', 'harvest', 'verify', 'recover', 'search', 'scout', 'skill', 'member', 'codex-goal', 'app', 'apps', 'learn', 'lesson', 'taste', 'teach', 'plugin', 'experiments', 'bench', 'router', 'receipt', 'proof', 'openclaw', 'pull', 'push', 'watch', 'cloud', 'live', 'align', 'terminal', 'computer', 'diff', 'business', 'sync', 'youtube', 'x-search',
6
6
  'ingest', 'query', 'lint', 'loop', 'pulse', 'task', 'mission', 'decide', 'agents', 'probe', 'worktree', 'land', 'caretaker', 'autoland', 'drive', 'aeo', 'slop', 'voice', 'strings', 'write', 'security-review', 'secure', 'deck', 'site', 'theme', 'card', 'reel', 'improve', 'study', 'rainmaker', 'xp', 'play', 'gm', 'game', 'x', 'recap', 'report', 'signup', 'clarity', 'interview', 'meet', 'moves', 'unknowns', 'avail', 'sync-checkout',
7
- 'ci', 'github', 'vercel', 'supabase', 'linear', 'stripe', 'gmail', 'calendar', 'twitter', 'slack', 'imessage', 'integrations', 'setup', 'clean-workspace', 'cw',
8
- 'fork', 'browse', 'publish', 'pack', 'sleep', 'wake', 'feedback', 'errors', 'wiki', 'code-review', 'cr', 'soul', 'fleet', 'fleet-report', 'loops', 'self-improve', 'compile', 'spaceship', 'truth', 'sign', 'engine', 'engines', 'feed', 'brief'];
7
+ 'ci', 'github', 'vercel', 'supabase', 'linear', 'stripe', 'balance', 'usage', 'api-key', 'topup', 'gmail', 'calendar', 'twitter', 'slack', 'imessage', 'integrations', 'setup', 'clean-workspace', 'cw',
8
+ 'fork', 'browse', 'publish', 'pack', 'sleep', 'wake', 'feedback', 'errors', 'wiki', 'code-review', 'cr', 'soul', 'fleet', 'fleet-report', 'loops', 'self-improve', 'compile', 'spaceship', 'truth', 'sign', 'engine', 'engines', 'playbook', 'feed', 'brief', 'social', 'people', 'follow', 'unfollow', 'friends', 'msg', 'inbox', 'invite', 'join'];
9
9
 
10
10
  // Damerau-Levenshtein edit distance between two short strings. Counts an
11
11
  // adjacent transposition (e.g. "taks" -> "task") as a single edit, since
@@ -39,6 +39,15 @@ function suggestCommand(input, commands = knownCommands) {
39
39
  if (!input || typeof input !== 'string') return null;
40
40
  const word = input.toLowerCase().trim();
41
41
  if (!word) return null;
42
+ // Semantic near-misses that edit distance will not catch (context ≠ activate).
43
+ const ALIASES = {
44
+ context: 'activate',
45
+ ctx: 'activate',
46
+ info: 'status',
47
+ state: 'status',
48
+ whereami: 'status',
49
+ };
50
+ if (ALIASES[word] && commands.includes(ALIASES[word])) return ALIASES[word];
42
51
  // Ignore internal/private commands (leading underscore) as suggestions.
43
52
  const candidates = commands.filter((c) => !c.startsWith('_'));
44
53
  let best = null;
@@ -55,4 +64,21 @@ function suggestCommand(input, commands = knownCommands) {
55
64
  return best;
56
65
  }
57
66
 
58
- module.exports = { knownCommands, suggestCommand, editDistance };
67
+ function suggestCommands(input, commands = knownCommands) {
68
+ const primary = suggestCommand(input, commands);
69
+ const word = String(input || '').toLowerCase().trim();
70
+ const extras = [];
71
+ if (word === 'context' || word === 'ctx') {
72
+ for (const name of ['activate', 'status']) {
73
+ if (commands.includes(name) && name !== primary) extras.push(name);
74
+ }
75
+ }
76
+ const out = [];
77
+ if (primary) out.push(primary);
78
+ for (const name of extras) {
79
+ if (!out.includes(name)) out.push(name);
80
+ }
81
+ return out;
82
+ }
83
+
84
+ module.exports = { knownCommands, suggestCommand, suggestCommands, editDistance };