atris 3.57.4 → 3.58.6

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 +55 -12
  6. package/atris/skills/x-search/SKILL.md +8 -6
  7. package/atris/skills/youtube/SKILL.md +66 -21
  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 +59 -31
  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 +216 -50
  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 +101 -48
  98. package/commands/worktree.js +1 -1
  99. package/commands/write.js +9 -9
  100. package/commands/x-search.js +93 -22
  101. package/commands/xp.js +6 -3
  102. package/commands/youtube.js +1019 -123
  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/ytnotes +82 -5
  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 +47 -9
  174. package/utils/config.js +0 -1
  175. package/scripts/det/checklist-score.js +0 -191
@@ -7,6 +7,7 @@ const {
7
7
  RUNNER_PROFILE_DEFS,
8
8
  RUNNER_PROFILE_ALIASES,
9
9
  RUNNER_PROFILE_NAMES,
10
+ HIDDEN_PROFILE_NAMES,
10
11
  } = require('./runner-command');
11
12
  const { rankEnginesDetailed, routerPickExplanation } = require('./router-brain');
12
13
 
@@ -53,6 +54,7 @@ const ENGINE_SEED_META = Object.freeze({
53
54
  tier: 'pro',
54
55
  roles: Object.freeze(['executor']),
55
56
  models: Object.freeze([
57
+ 'gemini-3.8-flash-high',
56
58
  'gemini-3.7-flash-high',
57
59
  'gemini-3.1-pro-high',
58
60
  'claude-sonnet-4-6',
@@ -62,6 +64,15 @@ const ENGINE_SEED_META = Object.freeze({
62
64
  duty: 'errands',
63
65
  fallback_order: 90,
64
66
  }),
67
+ opencode: Object.freeze({ tier: 'pro', roles: Object.freeze(['executor']), models: Object.freeze(['opencode/big-pickle', 'opencode/claude-opus-4-8', 'opencode/gpt-5.2']), fallback_order: 95 }),
68
+ commandcode: Object.freeze({
69
+ tier: 'max',
70
+ roles: Object.freeze(['validator', 'executor']),
71
+ // No pinned model: the engine rides this machine's Command Code default.
72
+ models: Object.freeze(['machine default']),
73
+ duty: 'errands',
74
+ fallback_order: 98,
75
+ }),
65
76
  });
66
77
 
67
78
  function engineRegistryFile(root = process.cwd()) {
@@ -176,7 +187,9 @@ function seededRegistry(root = process.cwd(), preloadedRaw = null) {
176
187
  return {
177
188
  schema: ENGINE_REGISTRY_SCHEMA,
178
189
  updated_at: new Date().toISOString(),
179
- engines: RUNNER_PROFILE_NAMES.map((id) => normalizeEngineEntry(id, savedById.get(id) || {})),
190
+ // Hidden profiles are registered engines too: they seed, route, and take
191
+ // health policy exactly like visible ones.
192
+ engines: Object.keys(RUNNER_PROFILE_DEFS).map((id) => normalizeEngineEntry(id, savedById.get(id) || {})),
180
193
  };
181
194
  }
182
195
 
@@ -192,7 +205,7 @@ function writeEngineRegistry(root, registry) {
192
205
 
193
206
  function setEngineOverrides(name, overrides = {}, root = process.cwd()) {
194
207
  const id = canonicalEngineName(name);
195
- const knownIds = RUNNER_PROFILE_NAMES.filter((engineId) => ENGINE_SEED_META[engineId]);
208
+ const knownIds = Object.keys(RUNNER_PROFILE_DEFS).filter((engineId) => ENGINE_SEED_META[engineId]);
196
209
  if (!id || !knownIds.includes(id)) {
197
210
  throw new Error(`Unknown engine "${name}". Known engines: ${knownIds.join(', ')}`);
198
211
  }
package/lib/file-ops.js CHANGED
@@ -83,7 +83,7 @@ function createLogFile(logFile, dateFormatted) {
83
83
  const backlogBody = carryBacklog ? `${carryBacklog}\n\n` : '';
84
84
  const inboxBody = carryInbox ? `${carryInbox}\n\n` : '';
85
85
 
86
- const initialContent = `# Log ${dateFormatted}\n\n## Handoff\n\n---\n\n## Completed ✅\n\n---\n\n## In Progress 🔄\n\n${inProgressBody}---\n\n## Backlog\n\n${backlogBody}---\n\n## Notes\n\n---\n\n## Inbox\n\n${inboxBody}\n`;
86
+ const initialContent = `# Log, ${dateFormatted}\n\n## Handoff\n\n---\n\n## Completed ✅\n\n---\n\n## In Progress 🔄\n\n${inProgressBody}---\n\n## Backlog\n\n${backlogBody}---\n\n## Notes\n\n---\n\n## Inbox\n\n${inboxBody}\n`;
87
87
  fs.writeFileSync(logFile, initialContent);
88
88
  }
89
89
 
package/lib/fleet.js CHANGED
@@ -4,11 +4,11 @@
4
4
  // --fleet`), the full loop inside: staff every idle engine on claimable
5
5
  // safe-lane tasks, one mission per task, one worktree per engine, land
6
6
  // arrivals serially. Proven by hand on 2026-07-02 (PRs #191-193, three
7
- // engines) before any of this code existed each function here is one thing
7
+ // engines) before any of this code existed, each function here is one thing
8
8
  // the orchestrator did manually that day.
9
9
  //
10
10
  // INVARIANT: the fleet has NO state file. Flight state IS missions +
11
- // worktrees + receipts. Kill a flight mid-run and nothing is orphaned it
11
+ // worktrees + receipts. Kill a flight mid-run and nothing is orphaned, it
12
12
  // is just missions in flight, resumable the normal way.
13
13
 
14
14
  const fs = require('fs');
@@ -48,7 +48,7 @@ const { appendEngineLiveLogChunk, createEngineLiveLog, engineTerminalReason } =
48
48
  const DENIED_TAGS = ['billing', 'money', 'payments', 'deploy', 'security', 'customer', 'external', 'feedback'];
49
49
 
50
50
  // ---------------------------------------------------------------------------
51
- // T1 dispatch primitive
51
+ // T1: dispatch primitive
52
52
 
53
53
  // A task's own text is its spec: the board convention writes "Done: ..." and
54
54
  // "Check: ..." into the title/description. Extract both so the dispatch
@@ -68,10 +68,10 @@ function parseDoneCheck(text) {
68
68
  // hypothesis-driven unsticking, smallest diff.
69
69
  const METHOD_KERNEL = [
70
70
  'Read this whole brief before acting; locate every file you will touch before editing any of them.',
71
- 'Never pipe a verify or test command through tail/head/grep run it bare and read the real exit code.',
71
+ 'Never pipe a verify or test command through tail/head/grep, run it bare and read the real exit code.',
72
72
  'Done requires a receipt: paste the exact verify command and its final output lines in your report.',
73
73
  'Before deleting or renaming any function or call site, grep the whole repo for its callers; a caller outside your change means the contract stays intact.',
74
- 'Stuck? Never run the same failing command a third time write 3 one-line hypotheses, then run the cheapest test that discriminates between them.',
74
+ 'Stuck? Never run the same failing command a third time, write 3 one-line hypotheses, then run the cheapest test that discriminates between them.',
75
75
  'Smallest diff that satisfies Done wins; prefer deleting code over adding it.',
76
76
  ];
77
77
 
@@ -99,7 +99,7 @@ function buildFleetPrompt(task, { worktreePath, yolo = false } = {}) {
99
99
  '',
100
100
  ATRIS_BUILD_PROCESS_PREAMBLE,
101
101
  '',
102
- `You are working task ${ref} in this repo checkout (an isolated git worktree${worktreePath ? ` at ${worktreePath}` : ''} commit here, NEVER push).`,
102
+ `You are working task ${ref} in this repo checkout (an isolated git worktree${worktreePath ? ` at ${worktreePath}` : ''}, commit here, NEVER push).`,
103
103
  '',
104
104
  `Task: ${title}`,
105
105
  '',
@@ -145,10 +145,10 @@ function buildFleetPrompt(task, { worktreePath, yolo = false } = {}) {
145
145
  }
146
146
 
147
147
  // Shell command that runs one engine on one prompt file, from the worktree.
148
- // Rides the same profile definitions the whole CLI uses no fleet-only
148
+ // Rides the same profile definitions the whole CLI uses, no fleet-only
149
149
  // spawn shapes.
150
150
  // Build work needs file tools: allowedTools reaches the default claude-shaped
151
- // spawn; template engines (cursor/codex/devin) ignore it their CLIs manage
151
+ // spawn; template engines (cursor/codex/devin) ignore it, their CLIs manage
152
152
  // their own permissions.
153
153
  const FLEET_ALLOWED_TOOLS = 'Bash,Read,Edit,Write,Grep,Glob';
154
154
  const VALIDATOR_ALLOWED_TOOLS = 'Bash,Read,Grep,Glob';
@@ -864,7 +864,7 @@ function dispatchToEngine({ task, engine, worktreePath, root = process.cwd(), ti
864
864
  }
865
865
 
866
866
  // ---------------------------------------------------------------------------
867
- // T2 staffing
867
+ // T2: staffing
868
868
 
869
869
  const {
870
870
  taskTagTokens,
@@ -894,7 +894,7 @@ function isSafeLane(task) {
894
894
  }
895
895
 
896
896
  // Rough file-surface guess from a task's text: paths and bare `commands/x.js`
897
- // style mentions. Used only to keep concurrent picks disjoint the merge
897
+ // style mentions. Used only to keep concurrent picks disjoint, the merge
898
898
  // conflict on 2026-07-02 (two engines in commands/mission.js) is the tax this
899
899
  // avoids.
900
900
  function fileSurface(task) {
@@ -938,7 +938,7 @@ function assignEngines(staffed, engines) {
938
938
  }
939
939
 
940
940
  // ---------------------------------------------------------------------------
941
- // T3 serial landing lane
941
+ // T3: serial landing lane
942
942
 
943
943
  // Land one arrival: rebase onto latest base first; a conflict pauses the
944
944
  // landing (never auto-resolve) and reports which files collided so the
@@ -971,7 +971,7 @@ function dispatchResultToTerminal(entry = {}, result = {}) {
971
971
  }
972
972
 
973
973
  // Fleet landings always target master. Without the explicit --target, ship
974
- // falls back to the launcher branch's atris-base a flight launched from a
974
+ // falls back to the launcher branch's atris-base, a flight launched from a
975
975
  // feature-branch checkout would merge PRs into that branch while the receipt
976
976
  // and land board define "landed" as in-master (PRs #207/#208, 2026-07-04).
977
977
  // A ship failure must name its real cause. MODULE_NOT_FOUND from the spawned
@@ -1010,8 +1010,8 @@ function fleetShipArgs(entry, check) {
1010
1010
  // A fleet build worktree is ephemeral. If any process inside one runs
1011
1011
  // `npm link` (which repoints the global `atris` package symlink at that
1012
1012
  // worktree), then removing the worktree at teardown leaves the global `atris`
1013
- // binary a dangling symlink, and everything resolving `atris` through PATH
1014
- // interactive shells, any tooling that shells out to the bare command dies
1013
+ // binary a dangling symlink, and everything resolving `atris` through PATH:
1014
+ // interactive shells, any tooling that shells out to the bare command, dies
1015
1015
  // with 'command not found'. Measured scope, not assumed: crons that invoke
1016
1016
  // `node <checkout>/bin/atris.js` by absolute path are UNAFFECTED; an autoland
1017
1017
  // tick ran fine four minutes before the repair. The failure is silent and
@@ -1083,7 +1083,7 @@ function guardGlobalCliLink({
1083
1083
  };
1084
1084
  }
1085
1085
 
1086
- // One-line " (path, path)" suffix naming the files behind a paused landing
1086
+ // One-line " (path, path)" suffix naming the files behind a paused landing,
1087
1087
  // unmerged files for a rebase_conflict, uncommitted paths for a dirty_worktree.
1088
1088
  function pausedPathsSuffix(landed) {
1089
1089
  const paths = (landed && landed.conflicts) || (landed && landed.dirty) || [];
@@ -1166,10 +1166,10 @@ module.exports = {
1166
1166
  };
1167
1167
 
1168
1168
  // ---------------------------------------------------------------------------
1169
- // T4 the conductor: one flight, watchable, receipted
1169
+ // T4, the conductor: one flight, watchable, receipted
1170
1170
 
1171
1171
  // Engines that can edit a repo headlessly. atris-fast (ax) is a chat lane,
1172
- // not a repo worker it keeps owning normal mission ticks, not fleet builds.
1172
+ // not a repo worker, it keeps owning normal mission ticks, not fleet builds.
1173
1173
  const FLEET_CAPABLE = ['claude', 'codex', 'cursor', 'devin', 'grok'];
1174
1174
  const DISPATCH_CAPABLE = [...FLEET_CAPABLE, 'fable', 'commandcode'];
1175
1175
 
@@ -2326,6 +2326,33 @@ function defaultOwnCli(root) {
2326
2326
  };
2327
2327
  }
2328
2328
 
2329
+ // A merge and its task handoff are separate writes. Keep the merge on disk
2330
+ // before attempting the handoff, and never report success if that write fails.
2331
+ function recordLandedTaskReview(cli, args, { flight, landedRow, worktree, save }) {
2332
+ landedRow.review_recorded = false;
2333
+ save();
2334
+ let result;
2335
+ try { result = cli(args); } catch (error) {
2336
+ result = { status: null, stderr: error.message };
2337
+ }
2338
+ if (!result || result.status !== 0) {
2339
+ flight.paused.push({
2340
+ task: landedRow.task,
2341
+ engine: landedRow.engine,
2342
+ stage: 'task_ready',
2343
+ landed: true,
2344
+ worktree,
2345
+ detail: headTail(String(result && (result.stderr || result.stdout) || 'task ready failed')),
2346
+ next_action: 'record fresh task proof with task ready; do not rebuild or merge again',
2347
+ });
2348
+ save();
2349
+ return false;
2350
+ }
2351
+ landedRow.review_recorded = true;
2352
+ save();
2353
+ return true;
2354
+ }
2355
+
2329
2356
  // Extract a runnable focused check from the task's Check: line; the wide
2330
2357
  // gates (npm test, CI) stay at the ship gate, not per-arrival.
2331
2358
  function focusedCheck(task) {
@@ -2539,7 +2566,7 @@ async function runFleetFlight({
2539
2566
 
2540
2567
  const staffed = assignEngines(staffedTasks, roster);
2541
2568
  // The receipt path is decided BEFORE landings so each task's ready-proof
2542
- // can cite it the proof policy certifies receipt-backed proofs agent-side.
2569
+ // can cite it, the proof policy certifies receipt-backed proofs agent-side.
2543
2570
  const receiptPath = path.join(root, 'atris', 'runs', `fleet-${nowStamp()}.json`);
2544
2571
  const flight = {
2545
2572
  at: new Date().toISOString(),
@@ -2557,7 +2584,7 @@ async function runFleetFlight({
2557
2584
  }
2558
2585
 
2559
2586
  log('');
2560
- log(` fleet ${roster.length} engine${roster.length === 1 ? '' : 's'} ready, ${staffed.length} task${staffed.length === 1 ? '' : 's'} staffed`);
2587
+ log(` fleet, ${roster.length} engine${roster.length === 1 ? '' : 's'} ready, ${staffed.length} task${staffed.length === 1 ? '' : 's'} staffed`);
2561
2588
  for (const s of staffed) log(` ${s.engine.padEnd(8)} → ${s.task.display_id} ${String(s.task.title || '').slice(0, 80)}`);
2562
2589
  log('');
2563
2590
 
@@ -2596,7 +2623,7 @@ async function runFleetFlight({
2596
2623
  if (!claimed || claimed.status !== 0) {
2597
2624
  const detail = String(claimed && (claimed.stderr || claimed.stdout) || 'claim failed').trim().slice(0, 300);
2598
2625
  flight.paused.push({ task: entry.task.display_id, engine: entry.engine, stage: 'claim', detail });
2599
- log(` ${entry.engine.padEnd(8)} ⏸ paused ${entry.task.display_id} at claim${detail ? ` ${detail}` : ''}`);
2626
+ log(` ${entry.engine.padEnd(8)} ⏸ paused ${entry.task.display_id} at claim${detail ? `, ${detail}` : ''}`);
2600
2627
  continue;
2601
2628
  }
2602
2629
  const started = cli(['worktree', 'start', '--agent', entry.engine, '--task', `fleet-${String(entry.task.display_id).toLowerCase()}`, ...startBaseArgs]);
@@ -2658,7 +2685,7 @@ async function runFleetFlight({
2658
2685
  if (result.deadEngine) paused.deadEngine = result.deadEngine;
2659
2686
  flight.paused.push(paused);
2660
2687
  stampDispatchBrief(root, result.brief_id, 'fail', `build failed for ${entry.task.display_id}`);
2661
- log(` ${activeEngine.padEnd(8)} ✗ build failed ${entry.task.display_id} worktree kept for takeover`);
2688
+ log(` ${activeEngine.padEnd(8)} ✗ build failed ${entry.task.display_id}, worktree kept for takeover`);
2662
2689
  continue;
2663
2690
  }
2664
2691
  log(` ${activeEngine.padEnd(8)} landing ${entry.task.display_id}...`);
@@ -2668,19 +2695,24 @@ async function runFleetFlight({
2668
2695
  if (result.brief_id) landedRow.brief_id = result.brief_id;
2669
2696
  if (result.restaffed) landedRow.restaffed = result.restaffed;
2670
2697
  flight.landed.push(landedRow);
2671
- stampDispatchBrief(root, result.brief_id, 'pass', `landed ${entry.task.display_id} via fleet ship`);
2672
2698
  const restaffProof = result.restaffed ? ` Restaffed from ${result.restaffed.from} to ${result.restaffed.to} (${result.restaffed.reason}).` : '';
2673
- cli([
2699
+ const reviewRecorded = recordLandedTaskReview(cli, [
2674
2700
  'task', 'ready', String(entry.task.display_id),
2675
2701
  '--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, ' '))}`,
2676
2702
  '--result', 'Operators can now review fleet-shipped work faster because the worktree was verified before landing.',
2677
2703
  '--as', `fleet-${activeEngine}`,
2678
- ]);
2704
+ ], { flight, landedRow, worktree: entry.worktreePath, save: () => atomicWriteFlightReceipt(flight, receiptPath) });
2705
+ if (!reviewRecorded) {
2706
+ stampDispatchBrief(root, result.brief_id, 'partial', `landed ${entry.task.display_id}, but task ready failed`);
2707
+ log(' the change landed, but saving its proof failed. repair the task record before starting more work.');
2708
+ continue;
2709
+ }
2710
+ stampDispatchBrief(root, result.brief_id, 'pass', `landed ${entry.task.display_id} via fleet ship`);
2679
2711
  log(` ${activeEngine.padEnd(8)} ✓ landed ${entry.task.display_id}`);
2680
2712
  } else {
2681
2713
  flight.paused.push({ task: entry.task.display_id, engine: activeEngine, ...(result.restaffed ? { restaffed: result.restaffed } : {}), ...landed });
2682
2714
  stampDispatchBrief(root, result.brief_id, 'partial', `paused ${entry.task.display_id} at ${landed.stage || 'landing'}`);
2683
- log(` ${activeEngine.padEnd(8)} ⏸ paused ${entry.task.display_id} at ${landed.stage}${pausedPathsSuffix(landed)} worktree kept`);
2715
+ log(` ${activeEngine.padEnd(8)} ⏸ paused ${entry.task.display_id} at ${landed.stage}${pausedPathsSuffix(landed)}, worktree kept`);
2684
2716
  }
2685
2717
  }
2686
2718
 
@@ -2714,7 +2746,7 @@ async function runFleetFlight({
2714
2746
  }
2715
2747
 
2716
2748
  // ---------------------------------------------------------------------------
2717
- // T5 one-command dispatch: `atris engine dispatch <task-id> --engine <name>`
2749
+ // T5, one-command dispatch: `atris engine dispatch <task-id> --engine <name>`
2718
2750
 
2719
2751
  // A claim taken before the engine starts must not stay held if the flight
2720
2752
  // refuses or errors first. Release through the same task plane the claim used.
@@ -2736,7 +2768,7 @@ function releaseUnstartedDispatchClaim(cli, { taskId, actor, detail }) {
2736
2768
  // claim so takeover stays attributed.
2737
2769
  function releasePausedClaimsWithoutWork(cli, { paused, worktreeByTask, actorByTask, baseRef = 'origin/master', log = () => {} }) {
2738
2770
  for (const row of paused || []) {
2739
- if (row.stage === 'claim' || row.stage === 'task_lookup') continue; // claim never held
2771
+ if (row.stage === 'claim' || row.stage === 'task_lookup' || row.landed === true) continue; // no claim, or work already landed
2740
2772
  if (row.claim_released !== undefined) continue;
2741
2773
  const taskId = String(row.task || '');
2742
2774
  const actor = actorByTask.get(taskId);
@@ -2815,7 +2847,7 @@ async function runDispatchFlight({
2815
2847
  }
2816
2848
 
2817
2849
  const cli = ownCli || defaultOwnCli(root);
2818
- // Cut worktrees from and check self-landing against the workspace's real
2850
+ // Cut worktrees from, and check self-landing against, the workspace's real
2819
2851
  // protected branch, not a hard-coded origin/master (CLI-1298).
2820
2852
  const landTarget = resolveDispatchLandTarget(root, checkoutBase);
2821
2853
  const verify = verifier || (trustedVerifier ? defaultTrustedVerifyRunner : defaultVerifyRunner);
@@ -2844,7 +2876,7 @@ async function runDispatchFlight({
2844
2876
  const liveLogPath = startLiveFlight(root, receiptPath, flight);
2845
2877
 
2846
2878
  log('');
2847
- log(` dispatch ${ids.length} task${ids.length === 1 ? '' : 's'} -> ${engine}`);
2879
+ log(` dispatch, ${ids.length} task${ids.length === 1 ? '' : 's'} -> ${engine}`);
2848
2880
  log('');
2849
2881
 
2850
2882
  // Same rule as the fleet: dispatch worktrees cut from the protected branch by
@@ -3100,7 +3132,7 @@ async function runDispatchFlight({
3100
3132
  if (result.deadEngine) paused.deadEngine = result.deadEngine;
3101
3133
  flight.paused.push(paused);
3102
3134
  stampDispatchBrief(root, result.brief_id, 'fail', `build failed for ${entry.taskId}`);
3103
- log(` ✗ build failed ${entry.taskId} worktree kept for takeover`);
3135
+ log(` ✗ build failed ${entry.taskId}, worktree kept for takeover`);
3104
3136
  continue;
3105
3137
  }
3106
3138
  if (enforceRemoteBoundary && (!candidate || candidate.ok !== true)) {
@@ -3445,16 +3477,20 @@ async function runDispatchFlight({
3445
3477
  if (result.brief_id) landedRow.brief_id = result.brief_id;
3446
3478
  if (result.restaffed) landedRow.restaffed = result.restaffed;
3447
3479
  flight.landed.push(landedRow);
3448
- stampDispatchBrief(root, result.brief_id, 'pass', `landed ${entry.taskId} via engine dispatch`);
3449
- cli([
3480
+ const reviewRecorded = recordLandedTaskReview(cli, [
3450
3481
  'task', 'ready', entry.taskId,
3451
3482
  '--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)}.`,
3452
3483
  '--result', 'Operators can now review engine-built work faster because dispatch reran the verifier before landing.',
3453
- '--landing', 'The verified change is on master and ready for review.',
3454
3484
  '--checked', `${landed.check} passed before landing`,
3455
3485
  '--tested', 'The requested behavior passed its declared verifier.',
3456
3486
  '--as', readyActor,
3457
- ]);
3487
+ ], { flight, landedRow, worktree: entry.worktreePath, save: () => writeDispatchReceipt(flight, receiptPath, { ids, reviewOnly, enforceRemoteBoundary }) });
3488
+ if (!reviewRecorded) {
3489
+ stampDispatchBrief(root, result.brief_id, 'partial', `landed ${entry.taskId}, but task ready failed`);
3490
+ log(' the change landed, but saving its proof failed. repair the task record before starting more work.');
3491
+ continue;
3492
+ }
3493
+ stampDispatchBrief(root, result.brief_id, 'pass', `landed ${entry.taskId} via engine dispatch`);
3458
3494
  log(` ✓ landed ${entry.taskId}`);
3459
3495
  } else {
3460
3496
  flight.paused.push({
@@ -3465,7 +3501,7 @@ async function runDispatchFlight({
3465
3501
  ...landed,
3466
3502
  });
3467
3503
  stampDispatchBrief(root, result.brief_id, 'partial', `paused ${entry.taskId} at ${landed.stage || 'landing'}`);
3468
- log(` ⏸ paused ${entry.taskId} at ${landed.stage}${pausedPathsSuffix(landed)} worktree kept`);
3504
+ log(` ⏸ paused ${entry.taskId} at ${landed.stage}${pausedPathsSuffix(landed)}, worktree kept`);
3469
3505
  }
3470
3506
  }
3471
3507
 
@@ -1,4 +1,4 @@
1
- // Atris HTML renderer same content model as the deck engine, rendered to
1
+ // Atris HTML renderer, same content model as the deck engine, rendered to
2
2
  // beautiful, anti-slop HTML made of semantic blocks. Pure: spec -> HTML string.
3
3
  //
4
4
  // Connects with the web apps: the `atris` theme matches atrisos-web tokens
package/lib/intents.js ADDED
@@ -0,0 +1,172 @@
1
+ 'use strict';
2
+
3
+ const INTENTS = Object.freeze([
4
+ Object.freeze({
5
+ id: 'next_work',
6
+ say: Object.freeze(['what should i do next', 'what is next', 'pick the next thing', 'what do we do now']),
7
+ do: 'atris next',
8
+ plain: 'your agent picks the most useful next step and starts it.',
9
+ offer: 'boot_empty',
10
+ }),
11
+ Object.freeze({
12
+ id: 'keep_going',
13
+ say: Object.freeze(['keep going while i\'m away', 'run overnight', 'finish this', 'keep working without me']),
14
+ do: 'atris spaceship',
15
+ plain: 'your agent keeps working for a few hours and sends updates.',
16
+ offer: 'boot_empty',
17
+ confirm: true,
18
+ }),
19
+ Object.freeze({
20
+ id: 'improve_one_thing',
21
+ say: Object.freeze(['make it better', 'make it faster', 'ship one thing', 'improve this', 'find one useful improvement']),
22
+ do: 'atris improve tick',
23
+ plain: 'your agent finds one useful improvement, makes it, and checks the result.',
24
+ offer: 'after_landing',
25
+ }),
26
+ Object.freeze({
27
+ id: 'clean_up',
28
+ say: Object.freeze(['clean this up', 'it is messy', 'organize this project', 'tidy the loose ends']),
29
+ do: 'atris clean',
30
+ plain: 'your agent finds stale or messy project files and cleans up the safe ones.',
31
+ offer: 'after_landing',
32
+ }),
33
+ Object.freeze({
34
+ id: 'find_or_explain',
35
+ say: Object.freeze(['where is x', 'how does x work', 'where does this live', 'show me where this lives', 'explain this code']),
36
+ do: 'read atris/MAP.md',
37
+ plain: 'your agent reads the project map and points you to the exact files.',
38
+ offer: 'always',
39
+ file: true,
40
+ }),
41
+ Object.freeze({
42
+ id: 'remember_this',
43
+ say: Object.freeze(['remember this', 'save this', 'do not lose this', 'keep this for later']),
44
+ do: 'atris later "<their words>"',
45
+ plain: 'your agent saves this as a note for later.',
46
+ offer: 'always',
47
+ }),
48
+ Object.freeze({
49
+ id: 'schedule_work',
50
+ say: Object.freeze(['every day', 'every morning', 'on a schedule', 'run this regularly']),
51
+ do: 'atris loop',
52
+ plain: 'your agent sets up repeated work on the schedule you describe.',
53
+ offer: 'always',
54
+ confirm: true,
55
+ }),
56
+ Object.freeze({
57
+ id: 'build_an_idea',
58
+ say: Object.freeze(['i want to build x', 'here is an idea', 'can we make x', 'turn this idea into something']),
59
+ do: 'atris wish "<their words>"',
60
+ plain: 'your agent turns the idea into a clear job and starts once any gaps are answered.',
61
+ offer: 'always',
62
+ }),
63
+ Object.freeze({
64
+ id: 'catch_up',
65
+ say: Object.freeze(['catch me up', 'what happened', 'what landed', 'show me recent progress']),
66
+ do: 'atris recap',
67
+ plain: 'your agent summarizes what changed and what still needs attention.',
68
+ offer: 'always',
69
+ }),
70
+ Object.freeze({
71
+ id: 'check_the_work',
72
+ say: Object.freeze(['is this done', 'is it safe', 'check it', 'review this work', 'can i trust this']),
73
+ do: 'atris review',
74
+ plain: 'your agent checks the work, names any risks, and reports whether it is ready.',
75
+ offer: 'after_landing',
76
+ }),
77
+ Object.freeze({
78
+ id: 'use_fewer_credits',
79
+ say: Object.freeze(['use a free model', 'use a cheaper model', 'do not burn credits', 'save credits', 'switch to the fast model']),
80
+ do: 'atris engine atris-fast',
81
+ plain: 'your agent switches this project to the included fast model to spend fewer credits.',
82
+ offer: 'always',
83
+ }),
84
+ Object.freeze({
85
+ id: 'hand_off_work',
86
+ say: Object.freeze(['hand this off', 'have someone else do it', 'delegate this', 'give this to a teammate']),
87
+ do: 'atris task delegate "<their words>" --to <member>',
88
+ plain: 'your agent gives the work to the best available teammate and keeps the handoff visible.',
89
+ offer: 'always',
90
+ confirm: true,
91
+ }),
92
+ Object.freeze({
93
+ id: 'show_or_set_up_team',
94
+ say: Object.freeze(['who owns this', 'set up a team', 'who is responsible', 'show me the team']),
95
+ do: 'atris team',
96
+ plain: 'your agent shows who is responsible and helps fill any missing role.',
97
+ offer: 'always',
98
+ }),
99
+ Object.freeze({
100
+ id: 'speak_plainly',
101
+ say: Object.freeze(['talk plainer', 'too much jargon', 'use plain english', 'keep it simple']),
102
+ do: 'atris clarity --set voice=plain',
103
+ plain: 'your agent saves your preference for short, plain replies.',
104
+ offer: 'always',
105
+ }),
106
+ Object.freeze({
107
+ id: 'set_goal',
108
+ say: Object.freeze(['our goal is x', 'we are trying to x', 'set the goal', 'keep everyone focused on x']),
109
+ do: 'atris goal set "<their words>"',
110
+ plain: 'your agent saves the goal so future work stays pointed at it.',
111
+ offer: 'always',
112
+ }),
113
+ Object.freeze({
114
+ id: 'show_capabilities',
115
+ say: Object.freeze(['what can you do', 'how can you help', 'show me my options', 'what should i ask for']),
116
+ do: 'atris guide',
117
+ plain: 'your agent shows the things it can handle in words you already use.',
118
+ offer: 'always',
119
+ }),
120
+ ]);
121
+
122
+ const STOP_WORDS = new Set([
123
+ 'a', 'an', 'and', 'can', 'could', 'for', 'i', 'im', 'is', 'it', 'me', 'my',
124
+ 'of', 'on', 'please', 'the', 'this', 'to', 'we', 'you', 'your',
125
+ ]);
126
+
127
+ function normalizedTokens(text) {
128
+ const words = String(text || '')
129
+ .toLowerCase()
130
+ .replace(/[\u2018\u2019']/g, '')
131
+ .match(/[a-z0-9]+/g) || [];
132
+ return new Set(words.filter((word) => !STOP_WORDS.has(word)));
133
+ }
134
+
135
+ function tokenOverlapScore(left, right) {
136
+ if (!left.size || !right.size) return 0;
137
+ let overlap = 0;
138
+ for (const token of left) {
139
+ if (right.has(token)) overlap += 1;
140
+ }
141
+ if (!overlap) return 0;
142
+ return (2 * overlap) / (left.size + right.size);
143
+ }
144
+
145
+ function intentScore(tokens, intent) {
146
+ return intent.say.reduce((best, phrase) => (
147
+ Math.max(best, tokenOverlapScore(tokens, normalizedTokens(phrase)))
148
+ ), 0);
149
+ }
150
+
151
+ function matchIntent(text) {
152
+ const tokens = normalizedTokens(text);
153
+ const ranked = INTENTS
154
+ .map((intent, index) => ({ intent, score: intentScore(tokens, intent), index }))
155
+ .sort((left, right) => right.score - left.score || left.index - right.index);
156
+
157
+ // A 0.46 F1 threshold needs about half of the meaningful words to overlap.
158
+ // It catches natural additions such as "please make this faster" while
159
+ // refusing requests that share only one generic word with a phrase.
160
+ const threshold = 0.46;
161
+ if (!ranked.length || ranked[0].score < threshold) {
162
+ return { intent: null, alternatives: ranked.slice(0, 3).map((item) => item.intent) };
163
+ }
164
+
165
+ return {
166
+ intent: ranked[0].intent,
167
+ score: Number(ranked[0].score.toFixed(3)),
168
+ alternatives: ranked.slice(1, 3).map((item) => item.intent),
169
+ };
170
+ }
171
+
172
+ module.exports = { INTENTS, matchIntent };
package/lib/journal.js CHANGED
@@ -84,7 +84,7 @@ function mergeSections(localSections, remoteSections, knownRemoteHash) {
84
84
 
85
85
  // Check if the full remote document matches the known state.
86
86
  // knownRemoteHash is a hash of the entire document at last sync.
87
- // If it matches, remote hasn't changed prefer all local edits.
87
+ // If it matches, remote hasn't changed, prefer all local edits.
88
88
  const remoteReconstructed = reconstructJournal(remoteSections);
89
89
  const currentRemoteHash = computeContentHash(remoteReconstructed);
90
90
  const remoteUnchanged = knownRemoteHash && currentRemoteHash === knownRemoteHash;
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
- const knownCommands = ['init', 'doctor', 'install', 'workspace', 'log', 'logs', 'later', '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',
3
+ const knownCommands = ['init', 'guide', 'doctor', 'install', 'workspace', 'log', 'logs', 'later', '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', 'x-search',
5
+ 'clean', 'close', 'harvest', 'verify', 'recover', 'search', 'scout', 'skill', 'member', 'codex-goal', 'app', 'apps', 'learn', 'lesson', 'taste', 'teach', 'plugin', 'experiments', 'bench', 'router', 'tree', '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
7
  'ci', 'github', 'vercel', 'supabase', 'linear', 'stripe', 'balance', 'usage', 'api-key', 'topup', 'gmail', 'calendar', 'twitter', 'slack', 'imessage', 'integrations', 'setup', 'clean-workspace', 'cw',
8
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'];