atris 3.37.1 → 3.40.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 (164) hide show
  1. package/AGENTS.md +25 -6
  2. package/README.md +2 -0
  3. package/atris/PERSONA.md +11 -11
  4. package/atris.md +17 -2
  5. package/ax +160 -34
  6. package/bin/atris.js +78 -6
  7. package/commands/activate.js +16 -3
  8. package/commands/agent-spawn.js +13 -18
  9. package/commands/autoland.js +76 -47
  10. package/commands/autopilot-front.js +0 -4
  11. package/commands/autopilot.js +0 -12
  12. package/commands/bench.js +10 -12
  13. package/commands/brain.js +0 -7
  14. package/commands/brief.js +0 -2
  15. package/commands/business-sync.js +0 -1
  16. package/commands/business.js +452 -12
  17. package/commands/chat-scan.js +5 -7
  18. package/commands/clean.js +31 -2
  19. package/commands/close.js +46 -11
  20. package/commands/cloud.js +0 -1
  21. package/commands/codex-goal.js +8 -12
  22. package/commands/computer.js +6 -5
  23. package/commands/console.js +64 -11
  24. package/commands/decide.js +166 -0
  25. package/commands/deck.js +30 -12
  26. package/commands/dream.js +0 -7
  27. package/commands/drill.js +14 -25
  28. package/commands/engine.js +206 -26
  29. package/commands/experiments.js +0 -4
  30. package/commands/fleet-report.js +12 -4
  31. package/commands/game.js +261 -0
  32. package/commands/gm.js +17 -7
  33. package/commands/goal.js +0 -6
  34. package/commands/harvest.js +1 -5
  35. package/commands/improve.js +29 -8
  36. package/commands/init.js +41 -37
  37. package/commands/integrations.js +0 -7
  38. package/commands/land.js +77 -20
  39. package/commands/launchpad.js +1 -16
  40. package/commands/lesson.js +17 -7
  41. package/commands/lifecycle.js +5 -5
  42. package/commands/log.js +55 -5
  43. package/commands/loop-front.js +0 -2
  44. package/commands/loops.js +6 -5
  45. package/commands/meet.js +0 -2
  46. package/commands/member.js +622 -573
  47. package/commands/mission.js +790 -314
  48. package/commands/next.js +0 -1
  49. package/commands/now.js +0 -12
  50. package/commands/one-lap.js +8 -8
  51. package/commands/orb.js +0 -4
  52. package/commands/pack-craft.js +0 -1
  53. package/commands/pack.js +3599 -424
  54. package/commands/play.js +6 -6
  55. package/commands/probe.js +12 -6
  56. package/commands/pulse.js +88 -64
  57. package/commands/push.js +0 -4
  58. package/commands/radar.js +14 -11
  59. package/commands/recover.js +340 -0
  60. package/commands/release.js +10 -9
  61. package/commands/report.js +0 -1
  62. package/commands/router.js +320 -0
  63. package/commands/run-front.js +0 -2
  64. package/commands/search.js +0 -3
  65. package/commands/site-deploy.js +870 -0
  66. package/commands/site.js +11 -2
  67. package/commands/skill.js +7 -2
  68. package/commands/slop.js +237 -7
  69. package/commands/status.js +6 -0
  70. package/commands/stream.js +4 -20
  71. package/commands/study.js +0 -3
  72. package/commands/sync-checkout.js +51 -0
  73. package/commands/sync.js +11 -40
  74. package/commands/task.js +999 -595
  75. package/commands/taste.js +101 -0
  76. package/commands/teach.js +86 -0
  77. package/commands/team.js +83 -3
  78. package/commands/unknowns.js +0 -10
  79. package/commands/update.js +0 -1
  80. package/commands/vercel.js +4 -2
  81. package/commands/visualize.js +0 -5
  82. package/commands/voice.js +195 -0
  83. package/commands/watch.js +1 -28
  84. package/commands/wiki.js +42 -15
  85. package/commands/wish.js +0 -1
  86. package/commands/workflow.js +2 -2
  87. package/commands/worktree.js +88 -28
  88. package/commands/xp.js +28 -30
  89. package/commands/youtube.js +0 -2
  90. package/lib/accept-verify-gate.js +126 -0
  91. package/lib/activity-stream.js +0 -2
  92. package/lib/arg-parser.js +41 -0
  93. package/lib/auto-accept-certified.js +261 -3
  94. package/lib/autoland.js +66 -3
  95. package/lib/ax-auto-lane.js +68 -7
  96. package/lib/bench/context.js +0 -2
  97. package/lib/bench/report.js +0 -1
  98. package/lib/bench/runner.js +18 -11
  99. package/lib/brief-ledger.js +0 -6
  100. package/lib/business-simulate.js +382 -0
  101. package/lib/chat-log-scan.js +0 -5
  102. package/lib/checkout-sync.js +52 -0
  103. package/lib/claude-boot-block.js +48 -0
  104. package/lib/cloud-mission.js +0 -6
  105. package/lib/codex-flight.js +0 -1
  106. package/lib/conductor-artifacts.js +25 -0
  107. package/lib/context-gatherer.js +7 -2
  108. package/lib/deck-compose.js +0 -1
  109. package/lib/deck-history.js +0 -1
  110. package/lib/deck-layout.js +0 -5
  111. package/lib/deck-review.js +0 -7
  112. package/lib/deck-schema.js +0 -1
  113. package/lib/default-verifier.js +0 -2
  114. package/lib/engine-registry.js +157 -28
  115. package/lib/experiments/daily.js +0 -6
  116. package/lib/falsifier-probe.js +84 -0
  117. package/lib/fleet.js +275 -42
  118. package/lib/functional-owner.js +0 -3
  119. package/lib/git-spawn.js +15 -0
  120. package/lib/inspect-fields.js +0 -1
  121. package/lib/json-file.js +37 -0
  122. package/lib/known-commands.js +2 -2
  123. package/lib/learnings.js +0 -2
  124. package/lib/lesson-preflight.js +146 -0
  125. package/lib/loop-doctor.js +11 -19
  126. package/lib/manifest.js +0 -2
  127. package/lib/member-alive.js +0 -1
  128. package/lib/member-switches.js +0 -5
  129. package/lib/mission-artifact.js +0 -1
  130. package/lib/mission-human-asks.js +28 -0
  131. package/lib/mission-protected-lane.js +296 -0
  132. package/lib/mission-room.js +0 -2
  133. package/lib/mission-runtime-loop.js +0 -1
  134. package/lib/next-moves.js +0 -18
  135. package/lib/official-cli-integration.js +47 -6
  136. package/lib/orb-context.js +14 -4
  137. package/lib/orb-scorecard.js +0 -1
  138. package/lib/outbound-send-gate.js +0 -6
  139. package/lib/pack-capabilities.js +685 -0
  140. package/lib/permission-grants.js +0 -6
  141. package/lib/policy-lessons.js +0 -2
  142. package/lib/pulse.js +68 -15
  143. package/lib/review-integrity.js +0 -2
  144. package/lib/router-brain.js +51 -3
  145. package/lib/runner-command.js +0 -10
  146. package/lib/runtime-bootstrap.js +0 -1
  147. package/lib/scorecard.js +0 -1
  148. package/lib/self-drive.js +45 -14
  149. package/lib/state-detection.js +0 -6
  150. package/lib/task-db.js +137 -7
  151. package/lib/task-decision.js +50 -0
  152. package/lib/taste-lessons.js +153 -0
  153. package/lib/team-presence.js +0 -1
  154. package/lib/tool-result-encode.js +17 -1
  155. package/lib/voice-gate.js +129 -2
  156. package/lib/wiki.js +152 -9
  157. package/lib/wish-audit.js +21 -7
  158. package/lib/wish-delegate.js +27 -21
  159. package/lib/wish-stats.js +0 -5
  160. package/lib/wish-store.js +0 -1
  161. package/lib/workspace-safety.js +0 -2
  162. package/lib/zip.js +95 -8
  163. package/package.json +2 -1
  164. package/templates/business-starter/persona.md +9 -0
@@ -0,0 +1,261 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { execFileSync } = require('child_process');
6
+
7
+ const DEFAULT_WIDTH = 80;
8
+ const MIN_WIDTH = 20;
9
+ const MAX_WIDTH = 120;
10
+ const STORYLINE_SLUG_PATTERN = /^[a-z0-9-]+$/;
11
+
12
+ function isValidStorylineSlug(slug) {
13
+ return typeof slug === 'string' && STORYLINE_SLUG_PATTERN.test(slug);
14
+ }
15
+
16
+ function parseGameArgs(args = []) {
17
+ let storyline = null;
18
+
19
+ for (let index = 0; index < args.length; index += 1) {
20
+ const arg = args[index];
21
+ if (arg === '--storyline') {
22
+ const value = args[index + 1];
23
+ if (!value || value.startsWith('--')) {
24
+ return { error: 'missing storyline slug' };
25
+ }
26
+ if (storyline) return { error: 'choose one storyline' };
27
+ storyline = value;
28
+ index += 1;
29
+ continue;
30
+ }
31
+ if (arg.startsWith('--')) return { error: `unknown game option: ${arg}` };
32
+ if (storyline) return { error: 'choose one storyline' };
33
+ storyline = arg;
34
+ }
35
+
36
+ return {
37
+ storyline,
38
+ publisherArgs: storyline ? ['--json', '--storyline', storyline] : ['--json'],
39
+ };
40
+ }
41
+
42
+ function availableStorylines(workspaceRoot) {
43
+ const directory = path.join(workspaceRoot, 'atris', 'storylines');
44
+ try {
45
+ return fs.readdirSync(directory, { withFileTypes: true })
46
+ .filter(entry => entry.isFile() && entry.name.endsWith('.md'))
47
+ .map(entry => path.basename(entry.name, '.md'))
48
+ .filter(slug => slug !== 'resume-log')
49
+ .sort();
50
+ } catch {
51
+ return [];
52
+ }
53
+ }
54
+
55
+ function publisherSupportsStoryline(script) {
56
+ try {
57
+ return fs.readFileSync(script, 'utf8').includes('--storyline');
58
+ } catch {
59
+ return false;
60
+ }
61
+ }
62
+
63
+ function clean(value, fallback = '') {
64
+ const text = String(value == null ? '' : value)
65
+ .replace(/[—–]/g, '-')
66
+ .replace(/\s+/g, ' ')
67
+ .trim();
68
+ return text || fallback;
69
+ }
70
+
71
+ function clip(value, width) {
72
+ const chars = Array.from(clean(value));
73
+ if (chars.length <= width) return chars.join('');
74
+ if (width <= 3) return chars.slice(0, width).join('');
75
+ return `${chars.slice(0, width - 3).join('').trimEnd()}...`;
76
+ }
77
+
78
+ function pad(value, width) {
79
+ const text = clip(value, width);
80
+ return `${text}${' '.repeat(Math.max(0, width - Array.from(text).length))}`;
81
+ }
82
+
83
+ function dashboardWidth(opts) {
84
+ const requested = Number(opts && opts.width);
85
+ if (!Number.isFinite(requested)) return DEFAULT_WIDTH;
86
+ return Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, Math.trunc(requested)));
87
+ }
88
+
89
+ function styles(enabled) {
90
+ const wrap = code => value => (
91
+ enabled ? `\x1b[${code}m${value}\x1b[0m` : String(value)
92
+ );
93
+ return {
94
+ bold: wrap('1'),
95
+ dim: wrap('2'),
96
+ green: wrap('32'),
97
+ yellow: wrap('33'),
98
+ cyan: wrap('36'),
99
+ };
100
+ }
101
+
102
+ function chapterLine(chapter, width, style) {
103
+ const state = clean(chapter && chapter.state, 'locked').toLowerCase();
104
+ const current = state === 'current';
105
+ const done = state === 'complete' || state === 'done';
106
+ const marker = current ? '[NOW ]' : done ? '[DONE]' : '[LOCK]';
107
+ const number = chapter && chapter.n != null ? `${chapter.n}.` : '';
108
+ const location = current ? ' YOU ARE HERE' : '';
109
+ const line = clip(`${marker} ${number} ${clean(chapter && chapter.title, 'Untitled chapter')}${location}`, width);
110
+ if (current) return style.yellow(style.bold(line));
111
+ if (done) return style.green(line);
112
+ return style.dim(line);
113
+ }
114
+
115
+ function missionBox(mission, width, style) {
116
+ const innerWidth = width - 2;
117
+ const contentWidth = width - 4;
118
+ const heading = ' CURRENT MISSION ';
119
+ const top = heading.length <= innerWidth
120
+ ? `╔${heading}${'═'.repeat(innerWidth - heading.length)}╗`
121
+ : `╔${'═'.repeat(innerWidth)}╗`;
122
+ const row = value => `║ ${pad(value, contentWidth)} ║`;
123
+ const bottom = `╚${'═'.repeat(innerWidth)}╝`;
124
+ const title = clean(mission && mission.title, 'No current mission');
125
+ const status = clean(mission && mission.status, 'unavailable').toUpperCase();
126
+ return [
127
+ style.cyan(style.bold(top)),
128
+ style.bold(row(title)),
129
+ row(`STATUS: ${status}`),
130
+ style.cyan(style.bold(bottom)),
131
+ ];
132
+ }
133
+
134
+ /**
135
+ * Render an atris.game_state.v1 payload without reading process or filesystem state.
136
+ * Callers opt into ANSI with opts.color and provide their terminal width via opts.width.
137
+ */
138
+ function renderGameDashboard(payload, opts = {}) {
139
+ const state = payload && typeof payload === 'object' ? payload : {};
140
+ const storyline = state.storyline && typeof state.storyline === 'object'
141
+ ? state.storyline
142
+ : null;
143
+ const missions = state.missions && typeof state.missions === 'object'
144
+ ? state.missions
145
+ : {};
146
+ const active = Array.isArray(missions.active) ? missions.active.slice(0, 3) : [];
147
+ const recentComplete = Array.isArray(missions.recent_complete)
148
+ ? missions.recent_complete.length
149
+ : Number(missions.recent_complete) || 0;
150
+ const width = dashboardWidth(opts);
151
+ const style = styles(opts.color === true);
152
+ const title = clean(storyline && storyline.name, 'No storyline loaded');
153
+ const destination = clean(storyline && storyline.destination, 'No destination loaded');
154
+ const chapters = Array.isArray(storyline && storyline.chapters)
155
+ ? storyline.chapters.slice(0, 7)
156
+ : [];
157
+ const streak = storyline && storyline.streak && typeof storyline.streak === 'object'
158
+ ? storyline.streak
159
+ : {};
160
+ const streakCurrent = Number.isFinite(Number(streak.current)) ? Number(streak.current) : 0;
161
+ const streakTarget = Number.isFinite(Number(streak.target)) ? Number(streak.target) : 0;
162
+ const streakLabel = clean(streak.label, 'mission streak');
163
+ const lines = [];
164
+
165
+ lines.push(style.cyan(style.bold(clip(title, width))));
166
+ lines.push(style.dim(clip(`DESTINATION: ${destination}`, width)));
167
+ lines.push('');
168
+ lines.push(style.bold('CHAPTER RAIL'));
169
+ if (chapters.length) {
170
+ for (const chapter of chapters) lines.push(chapterLine(chapter, width, style));
171
+ } else {
172
+ lines.push(style.dim(clip('[LOCK] No chapters loaded', width)));
173
+ }
174
+
175
+ lines.push('');
176
+ lines.push(...missionBox(storyline && storyline.mission, width, style));
177
+ lines.push('');
178
+ lines.push(style.yellow(style.bold(clip(`STREAK: ${streakCurrent} / ${streakTarget} | ${streakLabel}`, width))));
179
+ lines.push('');
180
+ lines.push(style.bold('LIVE MISSIONS'));
181
+ if (active.length) {
182
+ for (const mission of active) {
183
+ const id = mission && mission.id != null ? `#${mission.id}` : '#?';
184
+ lines.push(style.bold(clip(`${id} ${clean(mission && mission.title, 'Untitled mission')}`, width)));
185
+ lines.push(style.dim(clip(` owner: ${clean(mission && mission.owner, 'unassigned')}`, width)));
186
+ lines.push(clip(` next: ${clean(mission && mission.next, 'No next action recorded')}`, width));
187
+ }
188
+ } else {
189
+ lines.push(style.dim(clip('No active missions.', width)));
190
+ }
191
+ lines.push(clip(`RECENT COMPLETES: ${recentComplete}`, width));
192
+ lines.push('');
193
+ lines.push(style.cyan(style.bold(clip(`NEXT MOVE: ${clean(active[0] && active[0].next, 'No active mission')}`, width))));
194
+
195
+ return lines.join('\n');
196
+ }
197
+
198
+ function loadGameState(workspaceRoot = process.cwd(), publisherArgs = ['--json']) {
199
+ const script = path.join(workspaceRoot, 'scripts', 'game_state_sync.py');
200
+ const python = path.join(workspaceRoot, 'venv', 'bin', 'python');
201
+ if (!fs.existsSync(script) || !fs.existsSync(python)) return null;
202
+
203
+ try {
204
+ const stdout = execFileSync(python, [script, ...publisherArgs], {
205
+ cwd: workspaceRoot,
206
+ encoding: 'utf8',
207
+ stdio: ['ignore', 'pipe', 'ignore'],
208
+ maxBuffer: 4 * 1024 * 1024,
209
+ });
210
+ const payload = JSON.parse(stdout.trim());
211
+ return payload && typeof payload === 'object' ? payload : null;
212
+ } catch {
213
+ return null;
214
+ }
215
+ }
216
+
217
+ async function gameCommand(args = [], opts = {}) {
218
+ const parsed = parseGameArgs(args);
219
+ if (parsed.error) {
220
+ console.error(parsed.error);
221
+ return 1;
222
+ }
223
+ if (parsed.storyline && !isValidStorylineSlug(parsed.storyline)) {
224
+ console.error(`invalid storyline slug: ${parsed.storyline}`);
225
+ return 1;
226
+ }
227
+
228
+ const workspaceRoot = process.cwd();
229
+ if (parsed.storyline) {
230
+ const storylines = availableStorylines(workspaceRoot);
231
+ if (!storylines.includes(parsed.storyline)) {
232
+ console.error(`storyline not found: ${parsed.storyline}. available: ${storylines.join(', ') || 'none'}`);
233
+ return 1;
234
+ }
235
+ }
236
+
237
+ const script = path.join(workspaceRoot, 'scripts', 'game_state_sync.py');
238
+ const publisherArgs = parsed.storyline && publisherSupportsStoryline(script)
239
+ ? parsed.publisherArgs
240
+ : ['--json'];
241
+ const payload = loadGameState(workspaceRoot, publisherArgs);
242
+ if (!payload) {
243
+ if (!opts.silentMissing) {
244
+ console.log('no game state in this workspace (needs atris/storylines/)');
245
+ }
246
+ return false;
247
+ }
248
+
249
+ console.log(renderGameDashboard(payload, {
250
+ color: Boolean(process.stdout.isTTY) && !process.env.NO_COLOR,
251
+ width: process.stdout.columns || DEFAULT_WIDTH,
252
+ }));
253
+ return true;
254
+ }
255
+
256
+ module.exports = {
257
+ renderGameDashboard,
258
+ isValidStorylineSlug,
259
+ parseGameArgs,
260
+ gameCommand,
261
+ };
package/commands/gm.js CHANGED
@@ -3,6 +3,7 @@
3
3
  const fs = require('fs');
4
4
  const os = require('os');
5
5
  const path = require('path');
6
+ const { hasFlag: hasExactFlag } = require('../lib/arg-parser');
6
7
 
7
8
  const AGENTXP_LEADERBOARD_URL = 'https://api.atris.ai/api/agentxp/leaderboard';
8
9
  const AGENTXP_GLOBAL_SYNC_RULE = 'Run atris login, then sync. Owner-provided sync tokens are guided-demo fallback only.';
@@ -54,8 +55,9 @@ function flag(args, name) {
54
55
  return null;
55
56
  }
56
57
 
57
- function hasFlag(args, name) {
58
- return args.includes(name) || args.some(arg => arg.startsWith(`${name}=`));
58
+ // This command treats --name=value as flag presence as well as a standalone flag.
59
+ function hasFlagOrValue(args, name) {
60
+ return hasExactFlag(args, name) || args.some(arg => arg.startsWith(`${name}=`));
59
61
  }
60
62
 
61
63
  function positional(args) {
@@ -185,7 +187,7 @@ function pickSeedPlayer(workspaceRoot, tasks, args = []) {
185
187
  }
186
188
 
187
189
  function ensureStarterMission(taskDb, db, workspaceRoot, tasks, args = []) {
188
- if (hasFlag(args, '--no-seed')) return { tasks, seeded: null };
190
+ if (hasFlagOrValue(args, '--no-seed')) return { tasks, seeded: null };
189
191
  if (!fs.existsSync(path.join(workspaceRoot, 'atris'))) return { tasks, seeded: null };
190
192
  if (activeAgentXpTasks(tasks).length) return { tasks, seeded: null };
191
193
 
@@ -658,19 +660,27 @@ async function gmCommand(...args) {
658
660
  return memberCommand('wake', member, ...passthrough);
659
661
  }
660
662
 
663
+ if (!hasFlagOrValue(args, '--json') && !hasFlagOrValue(args, '--watch')) {
664
+ try {
665
+ const { gameCommand } = require('./game');
666
+ const dashboardShown = await gameCommand([], { silentMissing: true });
667
+ if (dashboardShown) console.log('');
668
+ } catch {
669
+ // The dashboard is an optional opening screen and must never block GM mode.
670
+ }
671
+ }
672
+
661
673
  const state = gmState(args);
662
- if (hasFlag(args, '--json')) {
674
+ if (hasFlagOrValue(args, '--json')) {
663
675
  console.log(JSON.stringify(state, null, 2));
664
676
  return;
665
677
  }
666
678
  render(state);
667
- if (hasFlag(args, '--watch')) {
679
+ if (hasFlagOrValue(args, '--watch')) {
668
680
  await gmWatch(args);
669
681
  }
670
682
  }
671
683
 
672
684
  module.exports = {
673
685
  gmCommand,
674
- gmState,
675
- gmWatchSnapshot,
676
686
  };
package/commands/goal.js CHANGED
@@ -237,11 +237,5 @@ function run(args = [], context = {}) {
237
237
  }
238
238
 
239
239
  module.exports = {
240
- SCOREBOARD_FILE,
241
- buildGoalView,
242
- readScoreboard,
243
- renderGoalView,
244
240
  run,
245
- scoreboardPath,
246
- writeScoreboard,
247
241
  };
@@ -1,14 +1,11 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
+ const { hasFlag } = require('../lib/arg-parser');
3
4
  const {
4
5
  isGenericInboxPlaceholder,
5
6
  seedInboxFromMove,
6
7
  } = require('../lib/next-moves');
7
8
 
8
- function hasFlag(args, name) {
9
- return args.includes(name);
10
- }
11
-
12
9
  function safeRead(file) {
13
10
  try { return fs.readFileSync(file, 'utf8'); } catch { return ''; }
14
11
  }
@@ -174,6 +171,5 @@ function harvestCommand(args = []) {
174
171
  }
175
172
 
176
173
  module.exports = {
177
- harvestActions,
178
174
  harvestCommand,
179
175
  };
@@ -149,6 +149,10 @@ function summarizeImproveResponse(data = {}) {
149
149
  files,
150
150
  model: d.model_used || d.model || null,
151
151
  taskId: d.task_id || d.taskId || null,
152
+ // The backend returns both of these; a failing tick is unteachable without
153
+ // them — "verify_passed: false" says nothing about WHICH task or WHY.
154
+ taskDescription: d.task_description || d.task_title || null,
155
+ verifyOutput: typeof d.verify_output === 'string' ? d.verify_output : null,
152
156
  elapsedMs: typeof d.elapsed_ms === 'number' ? d.elapsed_ms : null,
153
157
  scorecardWritten: d.scorecard_written === true,
154
158
  error: d.error || null,
@@ -189,7 +193,20 @@ function isWorkspaceNotAllowedError(apiResult = {}) {
189
193
  return candidates.some((c) => typeof c === 'string' && c.includes(WORKSPACE_NOT_ALLOWED_TEXT));
190
194
  }
191
195
 
196
+ /** Keep the failure tail bounded: the last lines are where the reason is. */
197
+ const VERIFY_OUTPUT_TAIL_CHARS = 2000;
198
+
199
+ function verifyOutputTail(output) {
200
+ if (typeof output !== 'string') return null;
201
+ const text = output.trim();
202
+ if (!text) return null;
203
+ return text.length > VERIFY_OUTPUT_TAIL_CHARS ? text.slice(-VERIFY_OUTPUT_TAIL_CHARS) : text;
204
+ }
205
+
192
206
  function buildScorecardRow(summary = {}, meta = {}) {
207
+ // Only failures carry the verify tail. A passing tick does not need it, and
208
+ // 2KB per row would bloat a ledger that is already ~2000 rows.
209
+ const failed = summary.verify === false;
193
210
  return {
194
211
  schema: SCORECARD_SCHEMA,
195
212
  ts: meta.ts || new Date().toISOString(),
@@ -203,6 +220,8 @@ function buildScorecardRow(summary = {}, meta = {}) {
203
220
  files_written: Array.isArray(summary.files) ? summary.files : [],
204
221
  model_used: summary.model || null,
205
222
  task_id: summary.taskId || null,
223
+ task_description: summary.taskDescription || null,
224
+ verify_output: failed ? verifyOutputTail(summary.verifyOutput) : null,
206
225
  elapsed_ms: summary.elapsedMs != null ? summary.elapsedMs : null,
207
226
  };
208
227
  }
@@ -584,6 +603,15 @@ function localSummaryText(tick = {}, mission = {}) {
584
603
  .trim();
585
604
  }
586
605
 
606
+ function tickTouchedFiles(worktree = {}) {
607
+ if (!Array.isArray(worktree.new_dirty_sample)) return [];
608
+ return worktree.new_dirty_sample.map((entry) => {
609
+ const pathText = String(entry || '').slice(3);
610
+ const arrow = pathText.indexOf(' -> ');
611
+ return arrow >= 0 ? pathText.slice(arrow + 4) : pathText;
612
+ }).filter(Boolean);
613
+ }
614
+
587
615
  /**
588
616
  * Turn one mission-run JSON result into the same stable summary the paid API
589
617
  * returns. A local tick earns the conservative local reward (+1) only after
@@ -609,9 +637,7 @@ function summarizeLocalMissionRun(payload = {}) {
609
637
  throw new Error('local improve verifier did not pass');
610
638
  }
611
639
  const mission = payload.mission || {};
612
- const files = tick.worktree && Array.isArray(tick.worktree.new_since_baseline_sample)
613
- ? tick.worktree.new_since_baseline_sample
614
- : [];
640
+ const files = tickTouchedFiles(tick.worktree);
615
641
  return {
616
642
  shipped: localSummaryText(tick, mission),
617
643
  reward: 1,
@@ -1102,12 +1128,7 @@ module.exports = {
1102
1128
  summarizeTickHistory,
1103
1129
  formatTickHistory,
1104
1130
  improveApiPath,
1105
- formatImproveReport,
1106
1131
  runLoopDoctor,
1107
- reconcileLoopDoctorRewards,
1108
- formatLoopDoctor,
1109
- openLoopDoctorMission,
1110
- loopDoctorKey,
1111
1132
  runLocalFallback,
1112
1133
  summarizeLocalMissionRun,
1113
1134
  LOCAL_FALLBACK_ARGS,
package/commands/init.js CHANGED
@@ -2,6 +2,7 @@ const fs = require('fs');
2
2
  const path = require('path');
3
3
  const { ensureExperimentsFramework } = require('./experiments');
4
4
  const { ensureWikiScaffold } = require('../lib/wiki');
5
+ const { upsertAtrisClaudeBootBlock } = require('../lib/claude-boot-block');
5
6
 
6
7
  /**
7
8
  * Detect project context by scanning project structure
@@ -174,7 +175,9 @@ function injectProjectPatterns(agentTeamDir, profile) {
174
175
  const navigatorFile = path.join(agentTeamDir, 'navigator', 'MEMBER.md');
175
176
  const validatorFile = path.join(agentTeamDir, 'validator', 'MEMBER.md');
176
177
 
178
+ // Injection is best-effort: a read-only or vanished MEMBER.md must not crash init.
177
179
  // Inject into executor.md
180
+ try {
178
181
  if (fs.existsSync(executorFile)) {
179
182
  let executorContent = fs.readFileSync(executorFile, 'utf8');
180
183
 
@@ -199,8 +202,10 @@ ${profile.hasCode ? `**Validation:** Run \`${profile.testCommand}\` before marki
199
202
  fs.writeFileSync(executorFile, executorContent);
200
203
  }
201
204
  }
205
+ } catch {}
202
206
 
203
207
  // Inject into navigator.md
208
+ try {
204
209
  if (fs.existsSync(navigatorFile)) {
205
210
  let navigatorContent = fs.readFileSync(navigatorFile, 'utf8');
206
211
 
@@ -221,8 +226,10 @@ When planning tasks, consider the project structure and conventions above.
221
226
  fs.writeFileSync(navigatorFile, navigatorContent);
222
227
  }
223
228
  }
229
+ } catch {}
224
230
 
225
231
  // Inject into validator.md
232
+ try {
226
233
  if (fs.existsSync(validatorFile)) {
227
234
  let validatorContent = fs.readFileSync(validatorFile, 'utf8');
228
235
 
@@ -241,6 +248,7 @@ ${profile.hasCode ? `**Validation:** Run \`${profile.testCommand}\` to verify ch
241
248
  fs.writeFileSync(validatorFile, validatorContent);
242
249
  }
243
250
  }
251
+ } catch {}
244
252
  }
245
253
 
246
254
  function initAtris() {
@@ -274,6 +282,7 @@ function initAtris() {
274
282
  const printReadyCounts = () => {
275
283
  console.log(`workspace files ready (${ready.workspace.size})`);
276
284
  console.log(`team members ready (${ready.team.size})`);
285
+ console.log('more members are available with atris member create <name>');
277
286
  console.log(`agent adapters ready (${ready.adapters.size})`);
278
287
  console.log(`skills installed (${ready.skills.size})`);
279
288
  };
@@ -593,8 +602,8 @@ function initAtris() {
593
602
 
594
603
 
595
604
  // Copy team members (MEMBER.md format — directory per member with skills/tools/context)
596
- const members = ['navigator', 'executor', 'validator', 'launcher', 'brainstormer', 'researcher'];
597
- members.forEach(name => {
605
+ const starterMembers = ['navigator', 'executor', 'validator', 'mission-lead', 'improver'];
606
+ starterMembers.forEach(name => {
598
607
  const sourceFile = path.join(__dirname, '..', 'atris', 'team', name, 'MEMBER.md');
599
608
  const targetMemberDir = path.join(teamDir, name);
600
609
  const targetFile = path.join(targetMemberDir, 'MEMBER.md');
@@ -636,7 +645,7 @@ tools: []
636
645
  # Insert persona, workflow, and rules below
637
646
  `;
638
647
  fs.writeFileSync(path.join(templateTargetDir, 'MEMBER.md'), templateContent);
639
- markReady('team', '_template', '✓ Created team/_template/MEMBER.md');
648
+ markReady('workspace', 'team/_template/MEMBER.md', '✓ Created team/_template/MEMBER.md');
640
649
  }
641
650
 
642
651
  // Detect project context and generate profile
@@ -668,6 +677,25 @@ atris atris.md
668
677
 
669
678
  This is the Atris boot sequence. Show the output to the user, then respond naturally.
670
679
 
680
+ ## The way of talking (every reply, no exceptions)
681
+
682
+ Talk like a person. This governs every message you send the operator: boot, status, summaries, everything.
683
+
684
+ - Plain words. Say what happened and what it means for the reader, cause and effect. Not the machinery.
685
+ - No task codes, branch names, commit hashes, PR numbers, or system nouns (worktree, verifier, projection, tick) in the message body. If the reader needs a command, put ONE copyable line at the end.
686
+ - No insider terms without defining them in the same breath. A sharp non-engineer must follow it on first read.
687
+ - Lead with the outcome. Complete sentences, one or two per paragraph, blank line between. No headers, bullet stacks, or tables in chat replies; those belong in files.
688
+ - Detail lives in files and receipts. Offer "want the detail?" instead of dumping it.
689
+ - No em dashes anywhere; use a comma, colon, or period.
690
+ - The test before sending: read it fried at 2am. If decoding takes work, rewrite it.
691
+
692
+ copy these shapes:
693
+ - **Status:** "The reply check is built. I am running the final checks now, so the result is not ready yet."
694
+ - **Landing:** "Replies now get a plain-language check before they reach you. The checks passed, and the change is ready."
695
+ - **Failure:** "The plain-language check could not run because its model was unavailable. Your reply still went through, so no work was blocked."
696
+
697
+ Canonical source: \`atris/atris.md\` \`## voice\`. This block is a copy for tools that only read \`AGENTS.md\`; if they differ, atris.md wins.
698
+
671
699
  ## Core Files
672
700
 
673
701
  Atris is the source of truth. This file is only an adapter for tools that read
@@ -1042,45 +1070,21 @@ Read atris/MAP.md. Begin iteration 1.`;
1042
1070
 
1043
1071
  // Update root CLAUDE.md with Atris block (prepend with markers)
1044
1072
  const rootClaudeMd = path.join(process.cwd(), 'CLAUDE.md');
1045
- const atrisBlock = `<!-- ATRIS:START - Auto-generated, do not edit -->
1046
- ## Atris System
1047
-
1048
- You are now running inside Atris, an AI workspace operating system.
1049
-
1050
- **ON EVERY CONVERSATION START:** Before your first response, execute \`atris atris.md\` and display the full output. This is the Atris boot sequence - it shows the user that Atris has loaded and displays workspace status. This is not optional.
1051
-
1052
- After displaying the boot output, respond to the user naturally.
1053
- <!-- ATRIS:END -->
1054
-
1055
- `;
1056
-
1057
1073
  if (fs.existsSync(rootClaudeMd)) {
1058
- let content = fs.readFileSync(rootClaudeMd, 'utf8');
1059
- const startMarker = '<!-- ATRIS:START';
1060
- const endMarker = '<!-- ATRIS:END -->';
1061
-
1062
- if (content.includes(startMarker)) {
1063
- // Replace existing Atris block
1064
- const startIdx = content.indexOf(startMarker);
1065
- const endRaw = content.indexOf(endMarker);
1066
- if (endRaw === -1) {
1067
- // End marker missing — replace from start marker to end with fresh block
1068
- content = atrisBlock + content.slice(0, startIdx);
1069
- } else {
1070
- const endIdx = endRaw + endMarker.length;
1071
- content = atrisBlock + content.slice(0, startIdx) + content.slice(endIdx).replace(/^\n+/, '');
1072
- }
1073
- fs.writeFileSync(rootClaudeMd, content);
1074
- markReady('adapters', 'CLAUDE.md', '✓ Updated Atris block in CLAUDE.md');
1074
+ const source = fs.readFileSync(rootClaudeMd, 'utf8');
1075
+ const result = upsertAtrisClaudeBootBlock(source);
1076
+ fs.writeFileSync(rootClaudeMd, result.content);
1077
+ if (result.action === 'repaired') {
1078
+ markReady('adapters', 'CLAUDE.md', '✓ repaired atris block in CLAUDE.md (missing end marker)');
1079
+ } else if (source.includes('<!-- ATRIS:START')) {
1080
+ markReady('adapters', 'CLAUDE.md', '✓ updated atris block in CLAUDE.md');
1075
1081
  } else {
1076
- // Prepend Atris block
1077
- fs.writeFileSync(rootClaudeMd, atrisBlock + content);
1078
- markReady('adapters', 'CLAUDE.md', '✓ Prepended Atris block to CLAUDE.md');
1082
+ markReady('adapters', 'CLAUDE.md', '✓ prepended atris block to CLAUDE.md');
1079
1083
  }
1080
1084
  } else {
1081
1085
  // Create new CLAUDE.md with just Atris block
1082
- fs.writeFileSync(rootClaudeMd, atrisBlock.trim() + '\n');
1083
- markReady('adapters', 'CLAUDE.md', '✓ Created CLAUDE.md with Atris block');
1086
+ fs.writeFileSync(rootClaudeMd, upsertAtrisClaudeBootBlock().content);
1087
+ markReady('adapters', 'CLAUDE.md', '✓ created CLAUDE.md with atris block');
1084
1088
  }
1085
1089
 
1086
1090
  if (fs.existsSync(sourceFile)) {
@@ -1621,19 +1621,12 @@ module.exports = {
1621
1621
  gmailCommand,
1622
1622
  calendarCommand,
1623
1623
  twitterCommand,
1624
- twitterPost,
1625
1624
  slackCommand,
1626
1625
  slackSend,
1627
- slackDm,
1628
1626
  imessageCommand,
1629
- imessageSend,
1630
- imessageDoctor,
1631
1627
  integrationsStatus,
1632
1628
  parseCalendarSearchArgs,
1633
1629
  filterCalendarEventsForSearch,
1634
1630
  formatCalendarSearchResults,
1635
1631
  formatCalendarSearchError,
1636
- writeCalendarEventsCache,
1637
- readCalendarEventsCache,
1638
- CALENDAR_CACHE_PATH,
1639
1632
  };