atris 3.57.4 → 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 (174) 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 +55 -12
  6. package/atris/skills/x-search/SKILL.md +8 -6
  7. package/atris/skills/youtube/SKILL.md +37 -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 +153 -23
  16. package/bin/atris.js +78 -30
  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 +3 -3
  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 +90 -82
  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 +1 -1
  99. package/commands/write.js +9 -9
  100. package/commands/x-search.js +89 -17
  101. package/commands/xp.js +6 -3
  102. package/commands/youtube.js +601 -48
  103. package/lib/accept-verify-gate.js +3 -3
  104. package/lib/activity-stream.js +1 -1
  105. package/lib/auto-accept-certified.js +28 -12
  106. package/lib/autoland.js +6 -6
  107. package/lib/bench/engines.js +59 -8
  108. package/lib/bench/report.js +1 -1
  109. package/lib/bench/runner.js +204 -5
  110. package/lib/bench/tree-render.js +76 -0
  111. package/lib/card.js +1 -1
  112. package/lib/chat-log-scan.js +1 -1
  113. package/lib/claude-boot-block.js +2 -0
  114. package/lib/deck-compose.js +1 -1
  115. package/lib/deck-history.js +2 -2
  116. package/lib/deck-layout.js +1 -1
  117. package/lib/deck-review.js +5 -5
  118. package/lib/deck-schema.js +1 -1
  119. package/lib/default-verifier.js +1 -1
  120. package/lib/developer-api.js +3 -0
  121. package/lib/engine-ask.js +45 -7
  122. package/lib/engine-registry.js +15 -2
  123. package/lib/file-ops.js +1 -1
  124. package/lib/fleet.js +71 -35
  125. package/lib/html-render.js +1 -1
  126. package/lib/intents.js +172 -0
  127. package/lib/journal.js +1 -1
  128. package/lib/known-commands.js +2 -2
  129. package/lib/land-green.js +364 -0
  130. package/lib/lesson-contradiction.js +2 -2
  131. package/lib/lesson-preflight.js +1 -1
  132. package/lib/manifest.js +5 -5
  133. package/lib/member-alive.js +9 -5
  134. package/lib/memory-view.js +3 -3
  135. package/lib/mission-protected-lane.js +1 -1
  136. package/lib/mission-root.js +2 -2
  137. package/lib/orb-context.js +10 -10
  138. package/lib/pack-capabilities.js +861 -3
  139. package/lib/permission-grants.js +2 -2
  140. package/lib/policy-lessons.js +6 -6
  141. package/lib/pulse.js +8 -8
  142. package/lib/receipt-evidence.js +1 -1
  143. package/lib/reel.js +1 -1
  144. package/lib/review-integrity.js +1 -1
  145. package/lib/runner-command.js +35 -5
  146. package/lib/scorecard.js +4 -4
  147. package/lib/security-scan.js +5 -5
  148. package/lib/self-drive.js +1 -1
  149. package/lib/site-publish.js +372 -0
  150. package/lib/site.js +1 -1
  151. package/lib/slides-deck.js +2 -2
  152. package/lib/state-detection.js +2 -2
  153. package/lib/sync-telemetry.js +2 -2
  154. package/lib/task-db.js +17 -10
  155. package/lib/task-explanation.js +1 -1
  156. package/lib/task-proof.js +2 -2
  157. package/lib/task-receipt.js +3 -3
  158. package/lib/theme.js +12 -12
  159. package/lib/todo-fallback.js +3 -3
  160. package/lib/todo-sections.js +1 -1
  161. package/lib/tree-hash.js +79 -0
  162. package/lib/usage.js +1 -1
  163. package/lib/wish-delegate.js +1 -1
  164. package/lib/workspace-safety.js +4 -4
  165. package/lib/workspace-scaffold.js +3 -3
  166. package/package.json +1 -1
  167. package/scripts/det/concierge-bakeoff.sh +26 -0
  168. package/scripts/det/ytquote-repair.js +0 -1
  169. package/scripts/det/ytrail-race.js +0 -5
  170. package/scripts/member-operate.mjs +52 -14
  171. package/scripts/outbound-artifact-gate.js +0 -3
  172. package/utils/auth.js +0 -3
  173. package/utils/config.js +0 -1
  174. package/scripts/det/checklist-score.js +0 -191
@@ -0,0 +1,175 @@
1
+ /**
2
+ * atris ask: chat with your personal Atris computer.
3
+ *
4
+ * Same brain as texting: your memory, calendar, email, and tools.
5
+ * One-shot: atris ask what should I do today
6
+ * Interactive: atris ask
7
+ */
8
+
9
+ const readline = require('readline');
10
+ const { getApiBaseUrl, httpRequest, apiRequestJson } = require('../utils/api');
11
+ const auth = require('../utils/auth');
12
+
13
+ const TIMEOUT_MS = 300000;
14
+
15
+ function stripEvents(text) {
16
+ // Drop inline [[EVENT]]{...} tool chatter; keep only the spoken reply.
17
+ // Tool names are surfaced dimmed in the terminal (texts never see them).
18
+ if (typeof text !== 'string') return text;
19
+ const parts = [];
20
+ const toolNames = [];
21
+ for (let chunk of text.split('[[EVENT]]')) {
22
+ const trimmed = chunk.trimStart();
23
+ if (trimmed.startsWith('{')) {
24
+ let depth = 0;
25
+ let inString = false;
26
+ let escaped = false;
27
+ let end = -1;
28
+ for (let i = 0; i < trimmed.length; i += 1) {
29
+ const ch = trimmed[i];
30
+ if (escaped) { escaped = false; continue; }
31
+ if (ch === '\\') { escaped = true; continue; }
32
+ if (ch === '"') inString = !inString;
33
+ if (inString) continue;
34
+ if (ch === '{') depth += 1;
35
+ if (ch === '}') { depth -= 1; if (depth === 0) { end = i + 1; break; } }
36
+ }
37
+ if (end > 0) {
38
+ try {
39
+ const evt = JSON.parse(trimmed.slice(0, end));
40
+ if (evt.type === 'tool_call' && evt.tool) toolNames.push(evt.tool);
41
+ } catch (_) { /* not JSON, keep going */ }
42
+ chunk = trimmed.slice(end);
43
+ }
44
+ }
45
+ parts.push(chunk);
46
+ }
47
+ const reply = parts.join('').trim();
48
+ if (toolNames.length && process.stdout.isTTY) {
49
+ process.stdout.write(`\x1b[2m[⚙ ${[...new Set(toolNames)].join(', ')}]\x1b[0m\n`);
50
+ }
51
+ return reply;
52
+ }
53
+
54
+ function startSpinner(label) {
55
+ if (!process.stdout.isTTY) return { stop: () => {} };
56
+ const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
57
+ const started = Date.now();
58
+ let i = 0;
59
+ const timer = setInterval(() => {
60
+ const secs = Math.floor((Date.now() - started) / 1000);
61
+ process.stdout.write(`\r${frames[i % frames.length]} ${label}${secs > 2 ? ` · ${secs}s` : ''} `);
62
+ i += 1;
63
+ }, 90);
64
+ return {
65
+ stop: () => {
66
+ clearInterval(timer);
67
+ process.stdout.write('\r\x1b[2K');
68
+ },
69
+ };
70
+ }
71
+
72
+ async function typeOut(text) {
73
+ if (!process.stdout.isTTY || text.length > 4000) {
74
+ process.stdout.write(`${text}\n`);
75
+ return;
76
+ }
77
+ for (const ch of text) {
78
+ process.stdout.write(ch);
79
+ // eslint-disable-next-line no-await-in-loop
80
+ await new Promise((r) => setTimeout(r, ch === '\n' ? 12 : 3));
81
+ }
82
+ process.stdout.write('\n');
83
+ }
84
+
85
+ async function sendMessage(token, message) {
86
+ const res = await httpRequest(`${getApiBaseUrl()}/message/incoming`, {
87
+ method: 'POST',
88
+ headers: {
89
+ Authorization: `Bearer ${token}`,
90
+ 'Content-Type': 'application/json',
91
+ },
92
+ body: JSON.stringify({ message, source: 'cli' }),
93
+ timeoutMs: TIMEOUT_MS,
94
+ });
95
+ if (res.status !== 200) {
96
+ let detail = res.body.toString('utf8').slice(0, 200);
97
+ try { detail = JSON.parse(detail).detail || detail; } catch (_) { /* raw */ }
98
+ throw new Error(`HTTP ${res.status}: ${detail}`);
99
+ }
100
+ return JSON.parse(res.body.toString('utf8'));
101
+ }
102
+
103
+ async function askAtris(args) {
104
+ const ensured = await auth.ensureValidCredentials(apiRequestJson);
105
+ if (ensured.error === 'not_logged_in' || !ensured.credentials?.token) {
106
+ console.error('✗ Not logged in. Run "atris login" first.');
107
+ process.exit(1);
108
+ }
109
+ if (ensured.error) {
110
+ console.error(`✗ Authentication failed: ${ensured.detail || ensured.error}. Run "atris login".`);
111
+ process.exit(1);
112
+ }
113
+ const { token } = ensured.credentials;
114
+
115
+ const oneShot = (args || []).join(' ').trim();
116
+ if (oneShot === '-h' || oneShot === '--help' || oneShot === 'help') {
117
+ console.log('Usage: atris ask ["message"]');
118
+ console.log('');
119
+ console.log(' Chat with your personal Atris computer: memory, calendar, email, tools.');
120
+ console.log('');
121
+ console.log(' atris ask Interactive chat');
122
+ console.log(' atris ask what is on today One-shot question');
123
+ return;
124
+ }
125
+
126
+ if (oneShot) {
127
+ const spinner = startSpinner('thinking');
128
+ try {
129
+ const body = await sendMessage(token, oneShot);
130
+ spinner.stop();
131
+ await typeOut(stripEvents(body.response) || JSON.stringify(body));
132
+ } catch (err) {
133
+ spinner.stop();
134
+ console.error(`✗ ${err.message}`);
135
+ process.exit(1);
136
+ }
137
+ return;
138
+ }
139
+
140
+ console.log('Chatting with your computer. Ctrl-C or "bye" to leave.\n');
141
+ await new Promise((resolve) => {
142
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
143
+ let busy = false;
144
+ let closed = false;
145
+ const prompt = () => { if (!closed) rl.question('you › ', onLine); };
146
+ async function onLine(line) {
147
+ const message = line.trim();
148
+ if (!message) { prompt(); return; }
149
+ if (['bye', 'exit', 'quit'].includes(message.toLowerCase())) { rl.close(); return; }
150
+ busy = true;
151
+ const spinner = startSpinner('thinking');
152
+ try {
153
+ const body = await sendMessage(token, message);
154
+ spinner.stop();
155
+ process.stdout.write('\natris › ');
156
+ await typeOut(stripEvents(body.response) || JSON.stringify(body));
157
+ process.stdout.write('\n');
158
+ } catch (err) {
159
+ spinner.stop();
160
+ console.error(`✗ ${err.message}\n`);
161
+ }
162
+ busy = false;
163
+ if (closed) { console.log('\nbye.'); resolve(); return; }
164
+ prompt();
165
+ }
166
+ rl.on('close', () => {
167
+ closed = true;
168
+ // Let an in-flight answer finish printing before we leave.
169
+ if (!busy) { console.log('\nbye.'); resolve(); }
170
+ });
171
+ prompt();
172
+ });
173
+ }
174
+
175
+ module.exports = { askAtris };
package/commands/auth.js CHANGED
@@ -565,7 +565,7 @@ async function switchAccount() {
565
565
  const profile = loadProfile(name);
566
566
  const email = profile?.email || 'unknown';
567
567
  const marker = name === currentName ? ' ← active' : '';
568
- console.log(` ${i + 1}. ${name} ${email}${marker}`);
568
+ console.log(` ${i + 1}. ${name}, ${email}${marker}`);
569
569
  });
570
570
  console.log(` ${profiles.length + 1}. Add new account`);
571
571
  console.log(` ${profiles.length + 2}. Cancel`);
@@ -620,13 +620,13 @@ function activateProfile(name, currentName, { global = false } = {}) {
620
620
  }
621
621
 
622
622
  if (global || !getTerminalSessionId()) {
623
- // Global switch write to credentials.json (affects all terminals)
623
+ // Global switch, write to credentials.json (affects all terminals)
624
624
  const credentialsPath = getCredentialsPath();
625
625
  fs.writeFileSync(credentialsPath, JSON.stringify(profile, null, 2));
626
626
  try { fs.chmodSync(credentialsPath, 0o600); } catch {}
627
- console.log(`Switched to ${profile.email || name} (global all terminals)`);
627
+ console.log(`Switched to ${profile.email || name} (global, all terminals)`);
628
628
  } else {
629
- // Per-terminal switch write session file (only this terminal)
629
+ // Per-terminal switch, write session file (only this terminal)
630
630
  setSessionProfile(name);
631
631
  console.log(`Switched to ${profile.email || name}`);
632
632
  }
@@ -682,7 +682,7 @@ function useAccount() {
682
682
  if (!process.stdout.isTTY) {
683
683
  process.stdout.write(`export ATRIS_PROFILE=${match}\n`);
684
684
  } else {
685
- // Interactive print instructions
685
+ // Interactive, print instructions
686
686
  console.log(`export ATRIS_PROFILE=${match}`);
687
687
  console.log(`\n# Run this to activate ${email} in this terminal:`);
688
688
  console.log(`# eval "$(atris use ${targetName})"`);
@@ -893,7 +893,7 @@ function switchSession() {
893
893
  if (termId) {
894
894
  setSessionProfile(name);
895
895
  } else {
896
- // No terminal ID global fallback
896
+ // No terminal ID, global fallback
897
897
  const credentialsPath = getCredentialsPath();
898
898
  fs.writeFileSync(credentialsPath, JSON.stringify(profile, null, 2));
899
899
  try { fs.chmodSync(credentialsPath, 0o600); } catch {}
@@ -910,7 +910,7 @@ function shellInit() {
910
910
  '# Atris per-terminal account switching',
911
911
  '# Added by: eval "$(atris shell-init)"',
912
912
  '_atris_session_id() {',
913
- ' # Detect terminal session ID same priority as the Node.js code',
913
+ ' # Detect terminal session ID, same priority as the Node.js code',
914
914
  ' local _sid="${TERM_SESSION_ID:-${ITERM_SESSION_ID:-${TMUX_PANE:-${WT_SESSION:-${WEZTERM_PANE:-}}}}}"',
915
915
  ' if [[ -n "$_sid" ]]; then echo "$_sid"; return; fi',
916
916
  ' # Fallback: TTY device (unique per macOS/Linux terminal tab)',
@@ -752,7 +752,7 @@ async function runTickBody(root, { json, policy, receipt, engineValidationDeps =
752
752
  // the landing gate reuse the live certification verifier result without
753
753
  // persisting trust across heartbeats. Denied lanes and check-less proofs wait.
754
754
  // No hardcoded --limit here: a fixed low cap (this used to be 12) silently
755
- // undercounts a real backlog every single tick 12/hour forever even with
755
+ // undercounts a real backlog every single tick: 12/hour forever even with
756
756
  // 78 certified rows waiting. Let `atris task auto-accept-certified` apply
757
757
  // its own default (12 without --all, a high safety cap under --all) so a
758
758
  // policy with accept_all:true actually drains the full certified set.
@@ -763,7 +763,7 @@ async function runTickBody(root, { json, policy, receipt, engineValidationDeps =
763
763
  try {
764
764
  const parsed = JSON.parse(accept.stdout);
765
765
  const results = Array.isArray(parsed.results) ? parsed.results : [];
766
- // A refused sweep (ok:false a guard tripped, policy race) carries no
766
+ // A refused sweep (ok:false, a guard tripped, policy race) carries no
767
767
  // summary fields. Name the reason instead of leaving nulls that read as
768
768
  // "no work": a blind heartbeat must say WHY it is blind.
769
769
  if (parsed.ok === false) {
@@ -841,7 +841,7 @@ async function runTickBody(root, { json, policy, receipt, engineValidationDeps =
841
841
  }
842
842
 
843
843
  // 3b. janitor, every tick: a mission left paused past 48h and a worktree
844
- // already merged into base are the same rot pattern as an unlanded branch
844
+ // already merged into base are the same rot pattern as an unlanded branch,
845
845
  // left alone they accumulate until a human runs `mission stop` and
846
846
  // `worktree cleanup --apply` by hand, which is exactly the chore autoland
847
847
  // exists to remove. Off switch: policy.janitor === false (default on, the
@@ -880,6 +880,30 @@ async function runTickBody(root, { json, policy, receipt, engineValidationDeps =
880
880
  recordJanitorState(state, { stopped: stoppedMissions, held: heldMissions, worktrees: receipt.worktrees_reaped });
881
881
  }
882
882
 
883
+ // 3b2. green landing, every tick: one stale branch that merges clean and
884
+ // passes the full verify in a scratch checkout lands on base and is pushed.
885
+ // A red branch opens a close flag the same hour instead of waiting for the
886
+ // TTL reap to salvage it. Off by policy: green_landing:false.
887
+ if (policy.green_landing !== false) {
888
+ try {
889
+ const { landStaleGreenBranches, summaryLine } = require('../lib/land-green');
890
+ const green = landStaleGreenBranches(root, {
891
+ verifyCommand: policy.verify_command || undefined,
892
+ limit: Number(policy.green_landing_limit) > 0 ? Number(policy.green_landing_limit) : 1,
893
+ });
894
+ receipt.green_landing = {
895
+ candidates: green.candidates.length,
896
+ landed: green.landed,
897
+ red: green.red,
898
+ verdicts: green.verdicts.map((v) => ({ branch: v.branch, action: v.action, reason: v.reason || null, detail: v.detail || null, failing: v.verify ? v.verify.failing : undefined })),
899
+ summary: summaryLine(green),
900
+ };
901
+ state.green_landing = { at: green.at, ...receipt.green_landing };
902
+ } catch (err) {
903
+ receipt.green_landing_error = String((err && err.message) || err).slice(0, 200);
904
+ }
905
+ }
906
+
883
907
  // 3c. landing sweep, every tick: merged branch residue is cleared,
884
908
  // TTL-expired work is salvaged before deletion, and 48h-stale active work
885
909
  // is carried into the digest instead of silently aging forever.
@@ -952,7 +976,7 @@ async function runTickBody(root, { json, policy, receipt, engineValidationDeps =
952
976
  }
953
977
  }
954
978
 
955
- // 5b. daily keep/revert experiment self-gated inside experiments daily;
979
+ // 5b. daily keep/revert experiment, self-gated inside experiments daily;
956
980
  // hourly ticks are harmless no-ops after the first run each day.
957
981
  if (policy.daily_experiment !== false) {
958
982
  const daily = runOwnCli(root, ['experiments', 'daily', '--json']);
@@ -1021,7 +1045,7 @@ async function runTickBody(root, { json, policy, receipt, engineValidationDeps =
1021
1045
  // Deltas alone ("0 certified, 0 landed") are true and useless: they read
1022
1046
  // as "nothing to do" whether the queue is empty or 19-deep and wedged.
1023
1047
  // Always print the standing backlog, and name work that cleared every
1024
- // gate and still did not land that is the shape of a stuck loop.
1048
+ // gate and still did not land. That is the shape of a stuck loop.
1025
1049
  const stuckNote = stuckBacklogNote(receipt);
1026
1050
  const summary = `autoland tick: ${receipt.reviews_certified ?? 0} reviews certified, ${receipt.landed.length} landed${receipt.landed.length ? ` (${receipt.landed.join(', ')})` : ''}, ${receipt.alarms} alarms${stuckNote}, digest ${digestTickStatus(receipt)}${reapNote}${janitorNote}${heldNote}${wishNote}${engineSweepNote}${receiptNote}`;
1027
1051
  const validationLine = engineAnswerValidationLine(receipt);
@@ -1,6 +1,6 @@
1
1
  // atris autopilot: point it at the workspace and it keeps going.
2
- // Each leg picks the most logical work a moving mission first, then a
3
- // member choosing useful work, then a self-chosen mission and drives it
2
+ // Each leg picks the most logical work, a moving mission first, then a
3
+ // member choosing useful work, then a self-chosen mission, and drives it
4
4
  // through the mission runtime. It loops until stopped:
5
5
  // atris autopilot stop (from any terminal) or Ctrl-C here.
6
6
  // The old suggest→justify→execute loop lives on behind `atris autopilot --legacy`.
@@ -57,7 +57,7 @@ function positiveNumber(value) {
57
57
  return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
58
58
  }
59
59
 
60
- // `--iterations=N` or `--iterations N` the leg cap legacy callers pass.
60
+ // `--iterations=N` or `--iterations N`, the leg cap legacy callers pass.
61
61
  function maxLegsFlag(args) {
62
62
  const eq = args.find((a) => typeof a === 'string' && a.startsWith('--iterations='));
63
63
  if (eq) return positiveNumber(eq.split('=')[1]);
@@ -187,7 +187,7 @@ function autopilotStatus(root = process.cwd()) {
187
187
  const state = readState(root);
188
188
  if (!state) { console.log('Autopilot is not running.'); return 0; }
189
189
  const alive = pidAlive(state.pid);
190
- console.log(`Autopilot ${alive ? 'running' : 'not running (stale state)'} pid ${state.pid}, started ${state.started_at}, legs ${state.legs || 0}`);
190
+ console.log(`Autopilot ${alive ? 'running' : 'not running (stale state)'}, pid ${state.pid}, started ${state.started_at}, legs ${state.legs || 0}`);
191
191
  if (state.current_leg) console.log(`Current leg: ${state.current_leg}`);
192
192
  if (!alive) clearState(root);
193
193
  return 0;
@@ -276,7 +276,7 @@ async function autopilotFront(args = []) {
276
276
 
277
277
  const current = { child: null };
278
278
  // `autopilot stop` SIGTERMs this pid directly, so the farewell must print
279
- // here the signal always beats the loop's own stop-file check.
279
+ // here, the signal always beats the loop's own stop-file check.
280
280
  const onSignal = () => {
281
281
  if (current.child) { try { current.child.kill('SIGTERM'); } catch {} }
282
282
  const reason = stopRequested(root) ? 'stop requested' : 'interrupted';
@@ -314,7 +314,7 @@ async function autopilotFront(args = []) {
314
314
  if (maxLegs && state.legs >= maxLegs) { stopReason = `leg cap reached (--iterations=${maxLegs})`; break; }
315
315
  if (stopRequested(root)) { stopReason = 'stop requested'; break; }
316
316
 
317
- // A leg that finishes in seconds means no real work happened a crash,
317
+ // A leg that finishes in seconds means no real work happened, a crash,
318
318
  // or a degenerate mission whose ticks record instantly (exit 0). Either
319
319
  // way, back off and stop instead of hot-looping on it.
320
320
  const legSeconds = (Date.now() - legStarted) / 1000;