atris 3.57.3 → 3.58.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/README.md +12 -1
  2. package/atris/skills/clawhub/member-runtime/SKILL.md +9 -9
  3. package/atris/skills/create-member/SKILL.md +16 -13
  4. package/atris/skills/design/SKILL.md +27 -4
  5. package/atris/skills/engines/SKILL.md +57 -13
  6. package/atris/skills/x-search/SKILL.md +13 -5
  7. package/atris/skills/youtube/SKILL.md +39 -8
  8. package/atris/team/brainstormer/MEMBER.md +2 -2
  9. package/atris/team/executor/MEMBER.md +3 -3
  10. package/atris/team/launcher/MEMBER.md +4 -4
  11. package/atris/team/navigator/MEMBER.md +3 -3
  12. package/atris/team/researcher/MEMBER.md +2 -2
  13. package/atris/team/validator/MEMBER.md +1 -1
  14. package/atris.md +14 -0
  15. package/ax +157 -24
  16. package/bin/atris.js +79 -31
  17. package/commands/aeo.js +4 -4
  18. package/commands/agents.js +1 -1
  19. package/commands/align.js +12 -12
  20. package/commands/analytics.js +3 -3
  21. package/commands/app.js +5 -5
  22. package/commands/ask.js +175 -0
  23. package/commands/auth.js +7 -7
  24. package/commands/autoland.js +29 -5
  25. package/commands/autopilot-front.js +6 -6
  26. package/commands/autopilot.js +90 -90
  27. package/commands/avail.js +5 -5
  28. package/commands/bench.js +63 -1
  29. package/commands/browse.js +2 -2
  30. package/commands/business.js +12 -12
  31. package/commands/card.js +2 -2
  32. package/commands/clean.js +13 -13
  33. package/commands/close.js +297 -15
  34. package/commands/compile.js +13 -13
  35. package/commands/computer.js +94 -27
  36. package/commands/console.js +5 -5
  37. package/commands/decide.js +1 -1
  38. package/commands/deck.js +9 -9
  39. package/commands/drive.js +4 -4
  40. package/commands/engine.js +173 -19
  41. package/commands/errors.js +3 -3
  42. package/commands/experiments.js +18 -1
  43. package/commands/feed.js +2 -2
  44. package/commands/feedback.js +4 -4
  45. package/commands/fleet.js +12 -12
  46. package/commands/game.js +1 -1
  47. package/commands/guide.js +49 -0
  48. package/commands/improve.js +19 -17
  49. package/commands/init.js +67 -19
  50. package/commands/interview.js +7 -7
  51. package/commands/land.js +40 -15
  52. package/commands/learn.js +12 -12
  53. package/commands/lesson.js +2 -2
  54. package/commands/log.js +1 -1
  55. package/commands/loops.js +4 -4
  56. package/commands/member.js +75 -50
  57. package/commands/mission.js +97 -98
  58. package/commands/one-lap.js +1 -1
  59. package/commands/pack.js +74 -8
  60. package/commands/plugin.js +6 -6
  61. package/commands/probe.js +9 -9
  62. package/commands/pull.js +35 -35
  63. package/commands/pulse.js +1 -1
  64. package/commands/push.js +21 -21
  65. package/commands/rainmaker.js +1 -1
  66. package/commands/recap.js +7 -7
  67. package/commands/reel.js +2 -2
  68. package/commands/release.js +3 -3
  69. package/commands/report.js +1 -1
  70. package/commands/review.js +5 -5
  71. package/commands/revisions.js +1 -1
  72. package/commands/run.js +1 -1
  73. package/commands/scout.js +3 -3
  74. package/commands/security-review.js +7 -7
  75. package/commands/serve.js +9 -9
  76. package/commands/setup.js +1 -1
  77. package/commands/sign.js +4 -4
  78. package/commands/signup.js +4 -4
  79. package/commands/site.js +8 -3
  80. package/commands/skill.js +6 -6
  81. package/commands/slop.js +18 -18
  82. package/commands/social.js +3 -3
  83. package/commands/soul.js +14 -14
  84. package/commands/spaceship.js +1 -1
  85. package/commands/status.js +2 -2
  86. package/commands/strings.js +13 -13
  87. package/commands/study.js +4 -4
  88. package/commands/sync.js +8 -8
  89. package/commands/task.js +50 -25
  90. package/commands/teach.js +2 -2
  91. package/commands/team.js +1 -1
  92. package/commands/theme.js +2 -2
  93. package/commands/tree.js +34 -0
  94. package/commands/truth.js +7 -7
  95. package/commands/verify.js +12 -12
  96. package/commands/visualize.js +2 -2
  97. package/commands/workflow.js +97 -40
  98. package/commands/worktree.js +57 -1
  99. package/commands/write.js +9 -9
  100. package/commands/x-search.js +304 -15
  101. package/commands/xp.js +6 -3
  102. package/commands/youtube.js +916 -65
  103. package/lib/accept-verify-gate.js +3 -3
  104. package/lib/activity-stream.js +1 -1
  105. package/lib/apply-gate.js +13 -0
  106. package/lib/auto-accept-certified.js +28 -12
  107. package/lib/autoland.js +6 -6
  108. package/lib/bench/engines.js +59 -8
  109. package/lib/bench/report.js +1 -1
  110. package/lib/bench/runner.js +204 -5
  111. package/lib/bench/tree-render.js +76 -0
  112. package/lib/card.js +1 -1
  113. package/lib/chat-log-scan.js +1 -1
  114. package/lib/claude-boot-block.js +2 -0
  115. package/lib/deck-compose.js +1 -1
  116. package/lib/deck-history.js +2 -2
  117. package/lib/deck-layout.js +1 -1
  118. package/lib/deck-review.js +5 -5
  119. package/lib/deck-schema.js +1 -1
  120. package/lib/default-verifier.js +1 -1
  121. package/lib/developer-api.js +3 -0
  122. package/lib/engine-ask.js +66 -9
  123. package/lib/engine-registry.js +15 -2
  124. package/lib/file-ops.js +1 -1
  125. package/lib/fleet.js +71 -35
  126. package/lib/html-render.js +1 -1
  127. package/lib/intents.js +172 -0
  128. package/lib/journal.js +1 -1
  129. package/lib/known-commands.js +2 -2
  130. package/lib/land-green.js +364 -0
  131. package/lib/lesson-contradiction.js +2 -2
  132. package/lib/lesson-preflight.js +1 -1
  133. package/lib/manifest.js +5 -5
  134. package/lib/member-alive.js +9 -5
  135. package/lib/memory-view.js +3 -3
  136. package/lib/mission-protected-lane.js +1 -1
  137. package/lib/mission-root.js +2 -2
  138. package/lib/orb-context.js +10 -10
  139. package/lib/pack-capabilities.js +861 -3
  140. package/lib/permission-grants.js +2 -2
  141. package/lib/policy-lessons.js +6 -6
  142. package/lib/pulse.js +8 -8
  143. package/lib/receipt-evidence.js +1 -1
  144. package/lib/reel.js +1 -1
  145. package/lib/review-integrity.js +1 -1
  146. package/lib/runner-command.js +35 -5
  147. package/lib/scorecard.js +4 -4
  148. package/lib/security-scan.js +5 -5
  149. package/lib/self-drive.js +1 -1
  150. package/lib/site-publish.js +372 -0
  151. package/lib/site.js +1 -1
  152. package/lib/slides-deck.js +2 -2
  153. package/lib/state-detection.js +2 -2
  154. package/lib/sync-telemetry.js +2 -2
  155. package/lib/task-db.js +17 -10
  156. package/lib/task-explanation.js +1 -1
  157. package/lib/task-proof.js +2 -2
  158. package/lib/task-receipt.js +3 -3
  159. package/lib/theme.js +12 -12
  160. package/lib/todo-fallback.js +3 -3
  161. package/lib/todo-sections.js +1 -1
  162. package/lib/tree-hash.js +79 -0
  163. package/lib/usage.js +1 -1
  164. package/lib/wish-delegate.js +1 -1
  165. package/lib/workspace-safety.js +4 -4
  166. package/lib/workspace-scaffold.js +3 -3
  167. package/package.json +1 -1
  168. package/scripts/det/concierge-bakeoff.sh +26 -0
  169. package/scripts/det/ytquote-repair.js +0 -1
  170. package/scripts/det/ytrail-race.js +0 -5
  171. package/scripts/member-operate.mjs +52 -14
  172. package/scripts/outbound-artifact-gate.js +0 -3
  173. package/utils/auth.js +0 -3
  174. package/utils/config.js +0 -1
  175. package/scripts/det/checklist-score.js +0 -191
package/lib/theme.js CHANGED
@@ -1,4 +1,4 @@
1
- // Brand themes a project defines its own theme in .atris/theme.json and every
1
+ // Brand themes: a project defines its own theme in .atris/theme.json and every
2
2
  // renderer (deck, HTML, site, memory view) uses it. Compounds like slop rules:
3
3
  // describe your brand once, every deck and page is on-brand.
4
4
  //
@@ -72,7 +72,7 @@ function writeStarterTheme(root = process.cwd()) {
72
72
  return { file, already: false };
73
73
  }
74
74
 
75
- // ---------- color math (WCAG contrast the tasteful guardrail) ----------
75
+ // ---------- color math (WCAG contrast, the tasteful guardrail) ----------
76
76
  function hexToRgb(hex) {
77
77
  const h = String(hex == null ? '' : hex).trim().replace(/^#/, '');
78
78
  const full = h.length === 3 ? h.split('').map((c) => c + c).join('') : h;
@@ -128,7 +128,7 @@ const FONT_PERSONALITIES = {
128
128
  // the user picks a feeling; we keep the surface tasteful and let their accent lead.
129
129
  const MOODS = {
130
130
  editorial: {
131
- blurb: 'warm, literary, considered Fraunces headlines on calm paper',
131
+ blurb: 'warm, literary, considered: Fraunces headlines on calm paper',
132
132
  defaultMode: 'dark', fonts: 'editorial-serif',
133
133
  swatches: ['#B5572E', '#D98E5C', '#9E3B2E', '#C08552'],
134
134
  modes: {
@@ -137,7 +137,7 @@ const MOODS = {
137
137
  },
138
138
  },
139
139
  technical: {
140
- blurb: 'cool, precise, engineered Space Grotesk on slate, mono accents',
140
+ blurb: 'cool, precise, engineered: Space Grotesk on slate, mono accents',
141
141
  defaultMode: 'dark', fonts: 'clean-sans',
142
142
  swatches: ['#3B82F6', '#06B6D4', '#6366F1', '#14B8A6'],
143
143
  modes: {
@@ -146,7 +146,7 @@ const MOODS = {
146
146
  },
147
147
  },
148
148
  noir: {
149
- blurb: 'high-contrast, near-black, one electric accent confident and stark',
149
+ blurb: 'high-contrast, near-black, one electric accent: confident and stark',
150
150
  defaultMode: 'dark', forceMode: 'dark', fonts: 'clean-sans',
151
151
  swatches: ['#C8FF00', '#FF2D78', '#FFB000', '#7DD3FC'],
152
152
  modes: {
@@ -154,7 +154,7 @@ const MOODS = {
154
154
  },
155
155
  },
156
156
  paper: {
157
- blurb: 'calm, light, low-key ink on cream, nothing shouting',
157
+ blurb: 'calm, light, low-key: ink on cream, nothing shouting',
158
158
  defaultMode: 'light', forceMode: 'light', fonts: 'editorial-serif',
159
159
  swatches: ['#2F4858', '#5B7553', '#B5572E', '#6B4E71'],
160
160
  modes: {
@@ -162,7 +162,7 @@ const MOODS = {
162
162
  },
163
163
  },
164
164
  vivid: {
165
- blurb: 'saturated, playful, energetic a bold accent that carries the page',
165
+ blurb: 'saturated, playful, energetic: a bold accent that carries the page',
166
166
  defaultMode: 'dark', fonts: 'clean-sans',
167
167
  swatches: ['#FF5C5C', '#8B5CF6', '#10B981', '#F59E0B'],
168
168
  modes: {
@@ -207,16 +207,16 @@ function buildTheme(answers = {}) {
207
207
  });
208
208
  }
209
209
 
210
- // readability guardrail human messages for low-contrast choices (empty array = clean)
210
+ // readability guardrail: human messages for low-contrast choices (empty array = clean)
211
211
  function themeWarnings(theme) {
212
212
  const c = normalizeTheme(theme).color;
213
213
  const warn = [];
214
214
  const body = contrastRatio(c.ink, c.bg);
215
- if (body < 4.5) warn.push(`body text (ink on bg) is ${body.toFixed(1)}:1 below the 4.5:1 readable floor`);
215
+ if (body < 4.5) warn.push(`body text (ink on bg) is ${body.toFixed(1)}:1, below the 4.5:1 readable floor`);
216
216
  const onAcc = contrastRatio(c.onAccent, c.accent);
217
- if (onAcc < 4.5) warn.push(`text on the accent is ${onAcc.toFixed(1)}:1 below 4.5:1`);
217
+ if (onAcc < 4.5) warn.push(`text on the accent is ${onAcc.toFixed(1)}:1, below 4.5:1`);
218
218
  const accentBg = contrastRatio(c.accent, c.bg);
219
- if (accentBg < 2.0) warn.push(`the accent barely separates from the background (${accentBg.toFixed(1)}:1) pick a bolder color`);
219
+ if (accentBg < 2.0) warn.push(`the accent barely separates from the background (${accentBg.toFixed(1)}:1), pick a bolder color`);
220
220
  return warn;
221
221
  }
222
222
 
@@ -230,7 +230,7 @@ function upsertProjectTheme(name, theme, root = process.cwd(), recipe = null) {
230
230
  if (fs.existsSync(file)) {
231
231
  let raw;
232
232
  try { raw = JSON.parse(fs.readFileSync(file, 'utf8')); }
233
- catch { const e = new Error(`${PROJECT_THEME_FILE} is not valid JSON fix or remove it before saving`); e.code = 'EBADTHEME'; throw e; }
233
+ catch { const e = new Error(`${PROJECT_THEME_FILE} is not valid JSON, fix or remove it before saving`); e.code = 'EBADTHEME'; throw e; }
234
234
  if (raw && raw.themes && typeof raw.themes === 'object') themes = { ...raw.themes };
235
235
  else if (isValidThemeShape(raw)) themes[raw.name || 'brand'] = { name: raw.name, color: raw.color, fonts: raw.fonts };
236
236
  }
@@ -2,7 +2,7 @@
2
2
  // the shim in lib/todo.js can fall back to it when the SQLite store is empty
3
3
  // (or when ATRIS_TASK_DB is not enabled).
4
4
  //
5
- // Do not add new callers. Use lib/todo.js (the shim) it merges DB + markdown.
5
+ // Do not add new callers. Use lib/todo.js (the shim), it merges DB + markdown.
6
6
 
7
7
  'use strict';
8
8
 
@@ -32,9 +32,9 @@ function tagsFromText(text) {
32
32
  function cleanTaskTitle(text) {
33
33
  const raw = String(text || '').trim();
34
34
  const withoutTags = raw.replace(/\s*\[[\w-]+\]/g, '').trim();
35
- const bold = withoutTags.match(/^\*\*(.+?)\*\*\s*(?:[—-]\s*)?(.*)$/);
35
+ const bold = withoutTags.match(/^\*\*(.+?)\*\*\s*(?:[\u2014-]\s*)?(.*)$/);
36
36
  if (!bold) return withoutTags;
37
- return [bold[1], bold[2]].filter(Boolean).join(' ').trim();
37
+ return [bold[1], bold[2]].filter(Boolean).join(' \u2014 ').trim();
38
38
  }
39
39
 
40
40
  function parseSection(content, sectionName) {
@@ -5,7 +5,7 @@
5
5
  // commands/brain.js (CLI-288); centralizing it here so a third copy can't drift.
6
6
  //
7
7
  // Headings may carry trailing decoration ("## In Progress 🔄", "## Completed ✅"),
8
- // so detection uses \b (not \s*$) and section names match by prefix "In Progress 🔄"
8
+ // so detection uses \b (not \s*$) and section names match by prefix: "In Progress 🔄"
9
9
  // counts as "In Progress", while "Backlogged Notes" does NOT count as "Backlog".
10
10
 
11
11
  const RENDERED_SECTION_RE = /^##\s+(Backlog|In Progress|Blocked|Completed)\b/m;
@@ -0,0 +1,79 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+
7
+ const TREE_HASH_SCHEMA = 'atris.tree_hash.v1';
8
+ const FIXED_FILES = [
9
+ 'atris.md',
10
+ 'atris/CLAUDE.md',
11
+ 'atris/atris.md',
12
+ ];
13
+
14
+ function optionalChildren(root, parent, filename) {
15
+ const directory = path.join(root, ...parent.split('/'));
16
+ let entries;
17
+ try {
18
+ entries = fs.readdirSync(directory, { withFileTypes: true });
19
+ } catch (error) {
20
+ if (error && (error.code === 'ENOENT' || error.code === 'ENOTDIR')) return [];
21
+ throw error;
22
+ }
23
+ return entries.map((entry) => `${parent}/${entry.name}/${filename}`);
24
+ }
25
+
26
+ function optionalBytes(root, relativePath) {
27
+ try {
28
+ return fs.readFileSync(path.join(root, ...relativePath.split('/')));
29
+ } catch (error) {
30
+ if (error && (error.code === 'ENOENT' || error.code === 'ENOTDIR')) return null;
31
+ throw error;
32
+ }
33
+ }
34
+
35
+ function computeTreeHash(root = process.cwd()) {
36
+ const workspaceRoot = path.resolve(root);
37
+ const stat = fs.statSync(workspaceRoot);
38
+ if (!stat.isDirectory()) throw new Error(`tree hash root is not a directory: ${workspaceRoot}`);
39
+ fs.accessSync(workspaceRoot, fs.constants.R_OK);
40
+
41
+ const paths = [
42
+ ...FIXED_FILES,
43
+ ...optionalChildren(workspaceRoot, 'atris/skills', 'SKILL.md'),
44
+ ...optionalChildren(workspaceRoot, 'atris/team', 'MEMBER.md'),
45
+ ].sort();
46
+ const manifest = [];
47
+ for (const relativePath of paths) {
48
+ const bytes = optionalBytes(workspaceRoot, relativePath);
49
+ if (bytes === null) continue;
50
+ manifest.push({
51
+ path: relativePath,
52
+ sha256: crypto.createHash('sha256').update(bytes).digest('hex'),
53
+ });
54
+ }
55
+ const hash = crypto.createHash('sha256')
56
+ .update(manifest.map((file) => `${file.path}\0${file.sha256}\n`).join(''))
57
+ .digest('hex');
58
+ return {
59
+ schema: TREE_HASH_SCHEMA,
60
+ hash,
61
+ short: hash.slice(0, 12),
62
+ files: manifest.length,
63
+ manifest,
64
+ };
65
+ }
66
+
67
+ function treeHashFor(root) {
68
+ try {
69
+ return computeTreeHash(root).hash;
70
+ } catch {
71
+ return null;
72
+ }
73
+ }
74
+
75
+ module.exports = {
76
+ TREE_HASH_SCHEMA,
77
+ computeTreeHash,
78
+ treeHashFor,
79
+ };
package/lib/usage.js CHANGED
@@ -10,7 +10,7 @@ const USAGE_MAX_BYTES = 5 * 1024 * 1024;
10
10
 
11
11
  // Usage telemetry used to write to `<cwd>/.atris/state/usage.jsonl`, so a
12
12
  // command run from a subdirectory that already had a nested .atris (e.g.
13
- // backend/) split its usage away from the workspace root's store the same
13
+ // backend/) split its usage away from the workspace root's store, the same
14
14
  // footgun the mission and task stores hit. Resolve to the shared workspace
15
15
  // root (spine-first -> git toplevel -> cwd) so usage lands with the rest of the
16
16
  // state, whichever subdir the caller stood in. Falls back to the raw cwd if the
@@ -1749,7 +1749,7 @@ function explicitAnswerRef(value) {
1749
1749
  return /^#\d+$/.test(text) || looksLikeWishId(text);
1750
1750
  }
1751
1751
 
1752
- // atris wish answer "your words" answers the pending question of the wish
1752
+ // atris wish answer "your words", answers the pending question of the wish
1753
1753
  // that is waiting on you, no list numbers involved.
1754
1754
  function answerWish(positionals, root = process.cwd(), options = {}) {
1755
1755
  const parts = positionals.slice(1).map(String).filter((value) => value.trim());
@@ -27,7 +27,7 @@ function dangerousWorkspaceReason(dir) {
27
27
  if (abs === p) return `the system path ${p}`;
28
28
  }
29
29
 
30
- // Reserved top-level folders inside the user's home never workspaces.
30
+ // Reserved top-level folders inside the user's home: never workspaces.
31
31
  const homeReservedNames = new Set([
32
32
  'Library', 'Applications', 'Documents', 'Downloads', 'Desktop',
33
33
  'Pictures', 'Music', 'Movies', 'Public', 'Sites', 'Dropbox',
@@ -51,20 +51,20 @@ function safeFallbackDir(slug, cwd) {
51
51
 
52
52
  // Resolve an outputDir, auto-relocating to a safe fallback if the originally
53
53
  // chosen path is dangerous. Non-technical users don't need to know about
54
- // mkdir/cd atris picks a safe folder and tells them where it landed.
54
+ // mkdir/cd: atris picks a safe folder and tells them where it landed.
55
55
  function resolveSafeOutputDir(requested, { slug, cwd = process.cwd(), op = 'use' } = {}) {
56
56
  const reason = dangerousWorkspaceReason(requested);
57
57
  if (!reason) return { dir: path.resolve(requested), relocated: false };
58
58
 
59
59
  const fallback = safeFallbackDir(slug, cwd);
60
60
  console.log('');
61
- console.log(` ${requested} is ${reason} not safe to ${op}.`);
61
+ console.log(` ${requested} is ${reason}, not safe to ${op}.`);
62
62
  console.log(` Using ${fallback} instead.`);
63
63
  console.log('');
64
64
  return { dir: fallback, relocated: true, originalReason: reason };
65
65
  }
66
66
 
67
- // Hard-refuse variant used by push where we can't auto-relocate (the
67
+ // Hard-refuse variant, used by push where we can't auto-relocate (the
68
68
  // source has to be where the user actually has their files).
69
69
  function assertSafeWorkspaceRoot(dir, { slug, op } = {}) {
70
70
  const reason = dangerousWorkspaceReason(dir);
@@ -18,7 +18,7 @@ function todoPlaceholder() {
18
18
 
19
19
  ## Backlog
20
20
 
21
- - **T1:** Generate MAP.md scan codebase, create navigation index with file:line refs
21
+ - **T1:** Generate MAP.md, scan codebase, create navigation index with file:line refs
22
22
 
23
23
  ---
24
24
 
@@ -37,7 +37,7 @@ function todoPlaceholder() {
37
37
  }
38
38
 
39
39
  function firstJournalMarkdown(today) {
40
- return `# Log ${today}
40
+ return `# Log: ${today}
41
41
 
42
42
  ## Completed ✅
43
43
 
@@ -49,7 +49,7 @@ function firstJournalMarkdown(today) {
49
49
 
50
50
  ## Backlog
51
51
 
52
- - **T1:** Generate MAP.md scan codebase, create navigation index with file:line refs
52
+ - **T1:** Generate MAP.md, scan codebase, create navigation index with file:line refs
53
53
 
54
54
  ---
55
55
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atris",
3
- "version": "3.57.3",
3
+ "version": "3.58.5",
4
4
  "description": "you say what you want in plain words. atris builds it, checks it, and shows you proof.",
5
5
  "main": "bin/atris.js",
6
6
  "bin": {
@@ -0,0 +1,26 @@
1
+ #!/bin/sh
2
+ # concierge bakeoff: does a fresh-install agent translate plain english into
3
+ # atris moves on its own? usage: concierge-bakeoff.sh <engine> <project-dir>
4
+ # engine: opencode | devin. logs land in <project-dir>/bakeoff/<n>.log
5
+ set -u
6
+ engine="$1"; dir="$2"; logdir=/tmp/bakeoff-logs/$engine; mkdir -p "$logdir"
7
+ export PATH=/tmp/atris-shim:$PATH
8
+ i=0
9
+ while IFS= read -r prompt; do
10
+ i=$((i+1)); log="$logdir/$i.log"
11
+ echo "prompt: $prompt" > "$log"
12
+ start=$(date +%s)
13
+ case "$engine" in
14
+ opencode) (cd "$dir" && perl -e 'alarm shift; exec @ARGV' 420 opencode run --model opencode/muse-spark-1.3-contributor-free "$prompt" </dev/null >>"$log" 2>&1); code=$? ;;
15
+ devin) (cd "$dir" && perl -e 'alarm shift; exec @ARGV' 420 devin -p --model swe-1-7 -- "$prompt" </dev/null >>"$log" 2>&1); code=$? ;;
16
+ *) echo "unknown engine" >&2; exit 2 ;;
17
+ esac
18
+ echo "exit=$code seconds=$(( $(date +%s) - start ))" >> "$log"
19
+ hits=$(grep -o 'atris \(guide\|next\|engine\|spaceship\|plan\|moves\|improve\|task\|atris\.md\)[^ "]*' "$log" | sort | uniq -c | tr '\n' ';')
20
+ told=$(grep -c '^`atris [a-z]' "$log")
21
+ echo "$engine #$i exit=$code atris-calls=[$hits] told-user-to-run=$told"
22
+ done <<'PROMPTS'
23
+ what should I do next on this project?
24
+ use a free model so we don't burn credits
25
+ keep going on this while I'm away tonight
26
+ PROMPTS
@@ -177,5 +177,4 @@ if (require.main === module) {
177
177
 
178
178
  module.exports = {
179
179
  normalizeText,
180
- repairNotes,
181
180
  };
@@ -179,14 +179,9 @@ if (require.main === module) {
179
179
 
180
180
  module.exports = {
181
181
  DEFAULT_URL,
182
- DEFAULT_ENGINES,
183
182
  DEFAULT_TIMEOUT_MS,
184
183
  parseArgs,
185
184
  parseEvalLine,
186
- createDefaultRunner,
187
- pickWinner,
188
- formatTable,
189
- formatVerdict,
190
185
  runRace,
191
186
  main,
192
187
  };
@@ -3,7 +3,7 @@
3
3
  //
4
4
  // Contract (lib/member-alive.js runMemberOperateScript):
5
5
  // node scripts/member-operate.mjs <member> --json --max-wall <60..1800>
6
- // --execute --confirm-autonomy-policy [--agent claude] [--model X] [--no-prime]
6
+ // --execute --confirm-autonomy-policy [--agent claude] [--model X] [--shared-checkout]
7
7
  // stdout: final JSON only (last line wins). stderr: live progress, prefixed
8
8
  // with ATRIS_MEMBER_OPERATE_PROGRESS\t so the alive loop can stream phases.
9
9
  //
@@ -39,12 +39,40 @@ function finish(result) {
39
39
  function collectJsonSignals(text) {
40
40
  // member run / mission run print JSON payloads along the way; harvest the
41
41
  // fields the alive loop cares about without depending on exact shapes.
42
- const signals = { receipt_path: null, needs_user: false, summary: null };
42
+ const signals = { receipt_path: null, needs_user: false, summary: null, failed: false, reason: null, detail: null, mission_started: false, execution_result: false };
43
+ let buffer = '';
44
+ let depth = 0;
45
+ let quoted = false;
46
+ let escaped = false;
43
47
  for (const line of String(text || '').split(/\r?\n/)) {
44
- const trimmed = line.trim();
45
- if (!trimmed.startsWith('{')) continue;
48
+ if (!buffer && !line.trimStart().startsWith('{')) continue;
49
+ buffer += line + '\n';
50
+ for (const character of line) {
51
+ if (quoted) {
52
+ if (escaped) escaped = false;
53
+ else if (character === '\\') escaped = true;
54
+ else if (character === '"') quoted = false;
55
+ } else if (character === '"') quoted = true;
56
+ else if (character === '{') depth++;
57
+ else if (character === '}') depth--;
58
+ }
59
+ if (depth > 0) continue;
46
60
  try {
47
- const parsed = JSON.parse(trimmed);
61
+ const parsed = JSON.parse(buffer);
62
+ const failedTick = parsed.action === 'mission_run' && Array.isArray(parsed.ticks)
63
+ ? parsed.ticks.find((tick) => ['errored', 'failed'].includes(tick.status)
64
+ || tick.claude?.ok === false || tick.atris2?.ok === false)
65
+ : null;
66
+ const missionErrored = parsed.action === 'mission_run'
67
+ && ['errored', 'failed'].includes(parsed.mission?.last_tick_status);
68
+ if (parsed.ok === false || failedTick || missionErrored) {
69
+ signals.failed = true;
70
+ signals.reason ||= parsed.reason || parsed.error || failedTick?.reason || 'operate_failed';
71
+ signals.detail ||= parsed.detail || failedTick?.claude?.summary || failedTick?.atris2?.error || null;
72
+ }
73
+ if (parsed.action === 'mission_started') signals.mission_started = true;
74
+ else if (parsed.action === 'mission_run') signals.execution_result ||= Number(parsed.ran_ticks) > 0;
75
+ else if (parsed.executed === true || parsed.result || parsed.receipt_path) signals.execution_result = true;
48
76
  if (parsed.receipt_path) signals.receipt_path = parsed.receipt_path;
49
77
  if (parsed.mission?.receipt_path) signals.receipt_path = parsed.mission.receipt_path;
50
78
  if (parsed.needs_user === true) signals.needs_user = true;
@@ -52,8 +80,12 @@ function collectJsonSignals(text) {
52
80
  if (landing?.changed) signals.summary = landing.changed;
53
81
  else if (parsed.summary) signals.summary = parsed.summary;
54
82
  } catch {
55
- // Partial or non-JSON line; keep streaming.
83
+ // A log line that resembles JSON is not a result.
56
84
  }
85
+ buffer = '';
86
+ depth = 0;
87
+ quoted = false;
88
+ escaped = false;
57
89
  }
58
90
  return signals;
59
91
  }
@@ -103,6 +135,7 @@ if (!atrisBin) {
103
135
  const runArgs = [atrisBin, 'member', 'run', member, '--json', '--max-wall', String(maxWall)];
104
136
  if (agent === 'claude') runArgs.push('--runner', 'claude');
105
137
  if (model) runArgs.push('--model', model);
138
+ if (args.includes('--shared-checkout')) runArgs.push('--shared-checkout');
106
139
 
107
140
  progress({ kind: 'phase', text: `Dispatching one bounded ${member} slice (wall cap ${maxWall}s).` });
108
141
 
@@ -113,14 +146,16 @@ const child = spawn(process.execPath, runArgs, {
113
146
  });
114
147
 
115
148
  let output = '';
116
- const forward = (chunk) => {
149
+ let errors = '';
150
+ const forward = (chunk, isError = false) => {
117
151
  const text = chunk.toString();
118
- output += text;
152
+ if (isError) errors += text;
153
+ else output += text;
119
154
  // Keep stdout clean for the final JSON; the human-visible stream is stderr.
120
155
  process.stderr.write(text);
121
156
  };
122
157
  child.stdout.on('data', forward);
123
- child.stderr.on('data', forward);
158
+ child.stderr.on('data', (chunk) => forward(chunk, true));
124
159
 
125
160
  const wallTimer = setTimeout(() => {
126
161
  progress({ kind: 'phase', text: `Wall cap ${maxWall}s reached; stopping the slice.` });
@@ -130,17 +165,20 @@ const wallTimer = setTimeout(() => {
130
165
 
131
166
  child.on('close', (code) => {
132
167
  clearTimeout(wallTimer);
133
- const signals = collectJsonSignals(output);
134
- const ok = code === 0;
168
+ const signals = collectJsonSignals(output + '\n' + errors);
169
+ const ok = code === 0 && !signals.failed;
170
+ const planned = ok && !signals.execution_result;
135
171
  finish({
136
172
  ok,
137
- reason: ok ? 'operate_complete' : 'operate_failed',
173
+ reason: planned ? signals.mission_started ? 'mission_started' : 'no_execution_evidence' : ok ? 'operate_complete' : signals.reason || 'operate_failed',
174
+ ...(planned ? { status: 'planned' } : {}),
175
+ ...(signals.detail ? { detail: signals.detail } : {}),
138
176
  member,
139
- executed: true,
177
+ executed: !planned,
140
178
  exit_code: code,
141
179
  max_wall_seconds: maxWall,
142
180
  needs_user: signals.needs_user,
143
- receipt_path: signals.receipt_path,
181
+ receipt_path: planned ? null : signals.receipt_path,
144
182
  summary: signals.summary,
145
183
  });
146
184
  });
@@ -219,9 +219,6 @@ if (require.main === module) {
219
219
  module.exports = {
220
220
  HTML_TAG_RE,
221
221
  RENDERED_SOURCE_FENCE_RE,
222
- SLOP_RULES,
223
- COACH_INTERNAL_RE,
224
- COACH_PRESSURE_RE,
225
222
  parseArgs,
226
223
  scanOutboundArtifact,
227
224
  };
package/utils/auth.js CHANGED
@@ -760,11 +760,9 @@ module.exports = {
760
760
  promptUser,
761
761
  validateAccessToken,
762
762
  refreshAccessToken,
763
- refreshProviderHint,
764
763
  performTokenRefresh,
765
764
  ensureValidCredentials,
766
765
  isAuthFailure,
767
- printAuthRequired,
768
766
  abortOnAuthFailure,
769
767
  fetchMyAgents,
770
768
  displayAccountSummary,
@@ -774,7 +772,6 @@ module.exports = {
774
772
  listProfiles,
775
773
  deleteProfile,
776
774
  profileNameFromEmail,
777
- autoSaveProfile,
778
775
  // Per-terminal sessions
779
776
  getTerminalSessionId,
780
777
  getSessionsDir,
package/utils/config.js CHANGED
@@ -88,7 +88,6 @@ module.exports = {
88
88
  getConfigPath,
89
89
  loadConfig,
90
90
  saveConfig,
91
- getLogSyncStatePath,
92
91
  loadLogSyncState,
93
92
  saveLogSyncState,
94
93
  };