atris 3.30.8 → 3.31.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 (59) hide show
  1. package/AGENTS.md +16 -3
  2. package/FOR_AGENTS.md +81 -0
  3. package/README.md +4 -2
  4. package/atris/atris.md +51 -19
  5. package/atris/skills/README.md +1 -0
  6. package/atris/skills/blocks/SKILL.md +134 -0
  7. package/atris/skills/clawhub/philosophy-of-work/SKILL.md +56 -0
  8. package/atris/skills/youtube/SKILL.md +31 -11
  9. package/atris.md +7 -0
  10. package/ax +367 -93
  11. package/bin/atris.js +317 -155
  12. package/commands/autoland.js +319 -0
  13. package/commands/autopilot.js +94 -4
  14. package/commands/business.js +1 -1
  15. package/commands/clean.js +72 -9
  16. package/commands/codex-goal.js +72 -22
  17. package/commands/computer.js +48 -3
  18. package/commands/gm.js +1 -1
  19. package/commands/harvest.js +179 -0
  20. package/commands/init.js +1 -1
  21. package/commands/land.js +442 -0
  22. package/commands/loop-front.js +122 -4
  23. package/commands/member.js +519 -19
  24. package/commands/mission.js +3659 -282
  25. package/commands/play.js +1 -1
  26. package/commands/pulse.js +65 -7
  27. package/commands/run.js +3 -3
  28. package/commands/strings.js +301 -0
  29. package/commands/task.js +575 -102
  30. package/commands/truth.js +170 -0
  31. package/commands/xp.js +32 -8
  32. package/commands/youtube.js +72 -5
  33. package/decks/README.md +6 -12
  34. package/lib/auto-accept-certified.js +10 -0
  35. package/lib/autoland.js +283 -0
  36. package/lib/context-gatherer.js +0 -8
  37. package/lib/mission-artifact.js +504 -0
  38. package/lib/mission-room.js +846 -0
  39. package/lib/mission-runtime-loop.js +320 -0
  40. package/lib/next-moves.js +212 -6
  41. package/lib/pulse.js +74 -1
  42. package/lib/runner-command.js +20 -8
  43. package/lib/runs-prune.js +242 -0
  44. package/lib/task-proof.js +1 -1
  45. package/package.json +3 -3
  46. package/decks/atris-seed-pitch-v3.json +0 -118
  47. package/decks/atris-seed-pitch-v4-skeleton.json +0 -106
  48. package/decks/atris-seed-pitch-v5.json +0 -109
  49. package/decks/atris-seed-pitch-v6.json +0 -137
  50. package/decks/atris-seed-pitch-v7.json +0 -133
  51. package/decks/mark-pincus-narrative.json +0 -102
  52. package/decks/mark-pincus-sourcery.json +0 -94
  53. package/decks/yash-applied-compute-detailed.json +0 -150
  54. package/decks/yash-applied-compute-generalist.json +0 -82
  55. package/decks/yash-applied-compute-narrative.json +0 -54
  56. package/lib/ax-chat-input.js +0 -164
  57. package/lib/ax-goal.js +0 -307
  58. package/lib/ax-prefs.js +0 -63
  59. package/lib/ax-shimmer.js +0 -63
package/bin/atris.js CHANGED
@@ -65,6 +65,7 @@ const {
65
65
  apiRequestJson, streamProChat, spawnClaudeCodeSession,
66
66
  DEFAULT_CLIENT_ID, DEFAULT_USER_AGENT,
67
67
  } = require('../utils/api');
68
+ const missionRuntime = require('../lib/mission-runtime-loop');
68
69
 
69
70
  // Bind DI wrappers (utils/auth uses dependency injection for apiRequestJson)
70
71
  const validateAccessToken = (token) => _validateAccessToken(token, apiRequestJson);
@@ -200,10 +201,14 @@ function loadActiveMissions(workspaceDir) {
200
201
  owner: m.owner || '?',
201
202
  objective: m.objective || '',
202
203
  status,
204
+ created_at: m.created_at || null,
205
+ updated_at: m.updated_at || null,
206
+ completed_at: m.completed_at || null,
203
207
  verifier: m.verifier || null,
204
208
  verifier_passed: (m.verifier_result && m.verifier_result.passed) === true,
205
209
  next_action: m.next_action || '',
206
210
  lane: m.lane || null,
211
+ runner: m.runner || null,
207
212
  });
208
213
  }
209
214
  // Most recently started first (rough — relies on insertion order from reversed walk)
@@ -213,6 +218,93 @@ function loadActiveMissions(workspaceDir) {
213
218
  }
214
219
  }
215
220
 
221
+ function readAtrisGoalState(workspaceDir) {
222
+ try {
223
+ const file = path.join(workspaceDir, '.atris', 'state', 'atris_goal.json');
224
+ if (!fs.existsSync(file)) return null;
225
+ const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
226
+ return parsed?.goal || null;
227
+ } catch {
228
+ return null;
229
+ }
230
+ }
231
+
232
+ function currentAtrisGoal(workspaceDir) {
233
+ const activeMissions = loadActiveMissions(workspaceDir);
234
+ const stored = readAtrisGoalState(workspaceDir);
235
+ if (stored && stored.objective) {
236
+ const mission = activeMissions.find((item) => item.id === stored.mission_id) || null;
237
+ return {
238
+ ...stored,
239
+ mission_status: mission?.status || stored.mission_status,
240
+ created_at: stored.created_at || mission?.created_at || null,
241
+ updated_at: stored.updated_at || mission?.updated_at || null,
242
+ completed_at: stored.completed_at || mission?.completed_at || null,
243
+ next_command: stored.next_command || mission?.next_action || null,
244
+ };
245
+ }
246
+ const mission = activeMissions[0] || null;
247
+ if (!mission) return null;
248
+ return {
249
+ objective: mission.objective,
250
+ mission_id: mission.id,
251
+ mission_status: mission.status,
252
+ owner: mission.owner,
253
+ runner: mission.runner || 'mission',
254
+ created_at: mission.created_at,
255
+ updated_at: mission.updated_at,
256
+ completed_at: mission.completed_at,
257
+ next_command: mission.next_action || `atris mission tick ${mission.id} --summary "<what changed>"`,
258
+ };
259
+ }
260
+
261
+ function goalElapsedSeconds(goal) {
262
+ const started = Date.parse(goal?.created_at || '');
263
+ if (!Number.isFinite(started)) return null;
264
+ const ended = Date.parse(goal?.completed_at || '') || Date.now();
265
+ return Math.max(0, Math.floor((ended - started) / 1000));
266
+ }
267
+
268
+ function formatGoalDuration(seconds) {
269
+ if (!Number.isFinite(seconds)) return null;
270
+ const total = Math.max(0, Math.floor(seconds));
271
+ const hours = Math.floor(total / 3600);
272
+ const minutes = Math.floor((total % 3600) / 60);
273
+ const secs = total % 60;
274
+ if (hours > 0) return `${hours}h ${minutes}m`;
275
+ if (minutes > 0) return `${minutes}m ${secs}s`;
276
+ return `${secs}s`;
277
+ }
278
+
279
+ function goalAchieved(goal) {
280
+ return ['complete', 'completed', 'achieved'].includes(String(goal?.mission_status || goal?.status || '').toLowerCase());
281
+ }
282
+
283
+ function printAtrisGoalBanner(workspaceDir = process.cwd(), label = 'Atris goal') {
284
+ if (process.env.ATRIS_SHOW_GOAL_BANNER !== '1' && process.env.AX_SHOW_GOAL_BANNER !== '1') return null;
285
+ const goal = currentAtrisGoal(workspaceDir);
286
+ if (!goal) return null;
287
+ if (String(goal.runner || '').trim().toLowerCase() === 'codex_goal') return null;
288
+ const objective = String(goal.objective || '').length > 92
289
+ ? `${String(goal.objective).slice(0, 89)}...`
290
+ : String(goal.objective || '');
291
+ console.log(`${label}: ${objective}`);
292
+ if (goal.mission_id || goal.mission_status || goal.runner) {
293
+ const elapsed = formatGoalDuration(goalElapsedSeconds(goal));
294
+ const parts = [
295
+ goal.mission_id || '?',
296
+ goal.mission_status || '?',
297
+ goal.runner || 'mission',
298
+ ];
299
+ if (elapsed) parts.push(`elapsed ${elapsed}`);
300
+ parts.push(`achieved ${goalAchieved(goal) ? 'yes' : 'no'}`);
301
+ console.log(`Mission: ${parts.join(' · ')}`);
302
+ }
303
+ if (goal.next_command) console.log(`Next: ${goal.next_command}`);
304
+ console.log('');
305
+ return goal;
306
+ }
307
+
216
308
  function showSearchHelp() {
217
309
  console.log('Usage: atris search <keyword>');
218
310
  console.log('Example: atris search auth');
@@ -376,6 +468,7 @@ function showHelp() {
376
468
  console.log(' analytics - Show recent productivity from journals');
377
469
  console.log(' search - Search journal history (atris search <keyword>)');
378
470
  console.log(' clean - Housekeeping (stale tasks, archive journals, broken refs)');
471
+ console.log(' harvest - Find bugs and next actions from receipts, run logs, and thinking');
379
472
  console.log(' verify - Validate work is done (tests, MAP.md, changes)');
380
473
  console.log(' task - Local agent task plane (atomic claims, TODO import)');
381
474
  console.log(' mission - Goal + loop + member owner + verifier + receipt');
@@ -393,8 +486,10 @@ function showHelp() {
393
486
  console.log(' autopilot - Guided loop that can clarify TODOs and run plan → do → review');
394
487
  console.log(' improve - Run one paid RL tick (POST /api/improve, deducts credits)');
395
488
  console.log(' worktree - Isolated Git worktrees plus guarded ship/merge for parallel agents');
489
+ console.log(' land - The landing: what is actually done vs still in the air; --reap backs up + clears overdue');
490
+ console.log(' autoland - Approve the policy once; certified work lands itself, you keep irreversible calls');
396
491
  console.log(' visualize - Generate a Slack/deck-ready visual from a prompt');
397
- console.log(' youtube - Process YouTube videos with Gemini native video analysis');
492
+ console.log(' youtube - Process YouTube videos with timestamped transcript-first analysis');
398
493
  console.log('');
399
494
  console.log('Experiments:');
400
495
  console.log(' experiments init [slug] - Prepare atris/experiments/ or scaffold a pack');
@@ -739,22 +834,27 @@ function showServeHelp() {
739
834
 
740
835
  function showLoopHelp() {
741
836
  console.log('');
742
- console.log('Usage: atris loop [--dry-run] [--json] [--limit=N]');
837
+ console.log('Usage: atris loop [add|start|status|report|stop|wiki] [options]');
743
838
  console.log('');
744
839
  console.log('Description:');
745
- console.log(' Inspect wiki upkeep state and optionally refresh wiki status/log files.');
746
- console.log('');
747
- console.log('Options:');
748
- console.log(' --dry-run Preview wiki loop state without writing files.');
749
- console.log(' --json Print the loop report as JSON.');
750
- console.log(' --limit=N Limit suggested source count.');
751
- console.log(' --help, -h Show this help.');
840
+ console.log(' One front door for the self-improvement loop. It reads ROADMAP.md,');
841
+ console.log(' runs bounded proof-backed work, and keeps wiki upkeep under loop wiki.');
842
+ console.log('');
843
+ console.log('Commands:');
844
+ console.log(' atris loop add "<task>" Put a bounded task into the loop queue.');
845
+ console.log(' atris loop start [--once] Run the local loop.');
846
+ console.log(' atris loop start --overnight Install the durable heartbeat.');
847
+ console.log(' atris loop status [--json] Show heartbeat, local runs, and next moves.');
848
+ console.log(' atris loop report [--json] Show proof of handled and queued loop work.');
849
+ console.log(' atris loop stop Remove the durable heartbeat.');
850
+ console.log(' atris loop wiki Run the wiki upkeep loop.');
851
+ console.log(' --help, -h Show this help.');
752
852
  console.log('');
753
853
  }
754
854
 
755
855
  function showCleanHelp() {
756
856
  console.log('');
757
- console.log('Usage: atris clean [--dry-run]');
857
+ console.log('Usage: atris clean [--dry-run] [--json]');
758
858
  console.log('');
759
859
  console.log('Description:');
760
860
  console.log(' Check workspace housekeeping: stale tasks, MAP.md refs, old journals,');
@@ -762,6 +862,7 @@ function showCleanHelp() {
762
862
  console.log('');
763
863
  console.log('Options:');
764
864
  console.log(' --dry-run, -n Preview cleanup without changing files.');
865
+ console.log(' --json Print machine-readable cleanup results.');
765
866
  console.log(' --help, -h Show this help.');
766
867
  console.log('');
767
868
  }
@@ -821,12 +922,12 @@ if (command === '2' && ['fast', 'pro'].includes(String(firstCommandArg || '').to
821
922
  }
822
923
 
823
924
  // Check if this is a known command or natural language input
824
- const knownCommands = ['init', 'log', 'now', 'radar', 'ctop', 'launchpad', 'status', 'analytics', 'visualize', 'brain', 'brainstorm', 'autopilot', 'run', 'plan', 'do', 'review', 'release',
925
+ const knownCommands = ['init', 'log', 'now', 'radar', 'ctop', 'launchpad', 'status', 'analytics', 'visualize', 'brain', 'brainstorm', 'autopilot', 'run', '_start', 'plan', 'do', 'review', 'release',
825
926
  'activate', '_activate', 'agent', 'chat', 'fast', 'ax', 'console', 'serve', 'login', 'logout', 'whoami', 'switch', 'use', 'accounts', '_resolve', '_profile-email', '_switch-session', 'shell-init', 'update', 'upgrade', 'version', 'help', 'next', 'atris',
826
- 'clean', 'verify', 'search', 'skill', 'member', 'codex-goal', 'app', 'apps', 'learn', 'lesson', 'plugin', 'experiments', 'receipt', 'proof', 'openclaw', 'pull', 'push', 'live', 'align', 'terminal', 'computer', 'diff', 'business', 'sync', 'youtube',
827
- 'ingest', 'query', 'lint', 'loop', 'pulse', 'task', 'mission', 'probe', 'worktree', 'aeo', 'slop', 'security-review', 'secure', 'deck', 'site', 'theme', 'card', 'reel', 'improve', 'xp', 'play', 'gm', 'x', 'recap', 'signup', 'clarity', 'moves',
927
+ 'clean', 'harvest', 'verify', 'search', 'skill', 'member', 'codex-goal', 'app', 'apps', 'learn', 'lesson', 'plugin', 'experiments', 'receipt', 'proof', 'openclaw', 'pull', 'push', 'live', 'align', 'terminal', 'computer', 'diff', 'business', 'sync', 'youtube',
928
+ 'ingest', 'query', 'lint', 'loop', 'pulse', 'task', 'mission', 'probe', 'worktree', 'land', 'autoland', 'aeo', 'slop', 'strings', 'security-review', 'secure', 'deck', 'site', 'theme', 'card', 'reel', 'improve', 'xp', 'play', 'gm', 'x', 'recap', 'signup', 'clarity', 'moves',
828
929
  'gmail', 'calendar', 'twitter', 'slack', 'imessage', 'integrations', 'setup', 'clean-workspace', 'cw',
829
- 'fork', 'browse', 'publish', 'sleep', 'wake', 'feedback', 'errors', 'wiki', 'code-review', 'cr', 'soul', 'fleet', 'compile', 'spaceship'];
930
+ 'fork', 'browse', 'publish', 'sleep', 'wake', 'feedback', 'errors', 'wiki', 'code-review', 'cr', 'soul', 'fleet', 'compile', 'spaceship', 'truth'];
830
931
 
831
932
  // Check if command is an atris.md spec file - triggers welcome visualization
832
933
  function isSpecFile(cmd) {
@@ -848,7 +949,15 @@ if (command === '--version' || command === '-v' || process.argv.includes('--vers
848
949
  // If no command OR command is not recognized, treat as natural language
849
950
  // Voice-friendly aliases — natural language → command mapping
850
951
  // Solves speech-to-text issues (inspired by gstack v0.14.6 voice-triggers)
952
+ const START_MISSION_OBJECTIVE = 'self improve goal after goal: pick one useful bounded mission from current Atris state, run proof, and continue only with real next work';
953
+
851
954
  const voiceTriggers = {
955
+ 'start': '_start',
956
+ 'start now': '_start',
957
+ 'go': '_start',
958
+ 'keep going': '_start',
959
+ 'keepgoing': '_start',
960
+ 'keep-going': '_start',
852
961
  'review my code': 'code-review',
853
962
  'check my code': 'code-review',
854
963
  'run a review': 'code-review',
@@ -873,7 +982,12 @@ const voiceTriggers = {
873
982
  if (!command || !knownCommands.includes(command)) {
874
983
  // Check voice triggers before falling through to natural language
875
984
  const fullInput = process.argv.slice(2).join(' ').toLowerCase().trim();
876
- const triggered = voiceTriggers[fullInput];
985
+ const fullInputWithoutFlags = process.argv.slice(2)
986
+ .filter((arg, index, args) => !String(arg).startsWith('-') && !isOptionValue(args, index, RUNNER_FLAG_NAMES))
987
+ .join(' ')
988
+ .toLowerCase()
989
+ .trim();
990
+ const triggered = voiceTriggers[fullInput] || voiceTriggers[fullInputWithoutFlags];
877
991
  if (triggered) {
878
992
  command = triggered;
879
993
  // Re-check — if it's now a known command, fall through to dispatch
@@ -1263,6 +1377,12 @@ function showWelcomeVisualization() {
1263
1377
  : `${tasksInReview} waiting`;
1264
1378
  console.log(` │ ⏳ Review: ${reviewText.padEnd(26)}│`);
1265
1379
  }
1380
+ let landInfo = null;
1381
+ try { landInfo = require('../commands/land').landSummary(process.cwd()); } catch (err) { landInfo = null; }
1382
+ if (landInfo && landInfo.branches > 0) {
1383
+ const landText = `${landInfo.branches} in the air, ${landInfo.due} overdue`;
1384
+ console.log(` │ 🛬 Land: ${landText.padEnd(26)}│`);
1385
+ }
1266
1386
  console.log(` │ 📝 Journal: ${(journalEntries + ' entries today').padEnd(26)}│`);
1267
1387
  if (endgameState.slug !== 'unset' && endgameState.horizon) {
1268
1388
  const endgameLine = endgameState.slug + ' — ' + endgameState.horizon;
@@ -1285,7 +1405,13 @@ function showWelcomeVisualization() {
1285
1405
  if (tasksCertified > 0) {
1286
1406
  console.log(` Ready. ${tasksCertified} certified await accept — run 'atris task reviews'.`);
1287
1407
  } else {
1288
- console.log(` Ready. Run 'atris plan' to start.`);
1408
+ let landHint = null;
1409
+ try { landHint = require('../commands/land').landSummary(process.cwd()); } catch (err) { landHint = null; }
1410
+ if (landHint && landHint.due > 0) {
1411
+ console.log(` Ready. ${landHint.due} overdue in the landing — run 'atris land --reap'.`);
1412
+ } else {
1413
+ console.log(` Ready. Run 'atris plan' to start.`);
1414
+ }
1289
1415
  }
1290
1416
  console.log('');
1291
1417
  }
@@ -1306,6 +1432,40 @@ if (command === 'init') {
1306
1432
  console.error(`✗ Error: ${error.message || error}`);
1307
1433
  process.exit(1);
1308
1434
  });
1435
+ } else if (command === '_start') {
1436
+ const args = process.argv.slice(3);
1437
+ if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
1438
+ console.log('');
1439
+ console.log('Usage: atris start [options]');
1440
+ console.log('');
1441
+ console.log('Starts the durable mission loop for one useful self-improvement mission.');
1442
+ console.log('');
1443
+ console.log('Options:');
1444
+ console.log(' --json Print the mission route without starting it.');
1445
+ console.log(' --owner X Override mission owner.');
1446
+ console.log(' --cadence X Override mission cadence.');
1447
+ console.log('');
1448
+ process.exit(0);
1449
+ }
1450
+
1451
+ if (args.includes('--json')) {
1452
+ console.log(JSON.stringify({
1453
+ ok: true,
1454
+ action: 'start_mission_run',
1455
+ route: `atris mission run "${START_MISSION_OBJECTIVE}"`,
1456
+ reason: 'casual_launch',
1457
+ objective: START_MISSION_OBJECTIVE,
1458
+ expected_loop: 'mission_run',
1459
+ }, null, 2));
1460
+ process.exit(0);
1461
+ }
1462
+
1463
+ Promise.resolve(require('../commands/mission').missionCommand(['run', START_MISSION_OBJECTIVE, ...args]))
1464
+ .then(() => process.exit(process.exitCode || 0))
1465
+ .catch((error) => {
1466
+ console.error(`✗ Start failed: ${error.message || error}`);
1467
+ process.exit(1);
1468
+ });
1309
1469
  } else if (command === 'task') {
1310
1470
  // SQLite-backed task plane. ~/.atris/tasks.db, gitignored, per-workspace.
1311
1471
  Promise.resolve(require('../commands/task').run(process.argv.slice(3)))
@@ -1313,7 +1473,7 @@ if (command === 'init') {
1313
1473
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1314
1474
  } else if (command === 'mission') {
1315
1475
  Promise.resolve(require('../commands/mission').missionCommand(process.argv.slice(3)))
1316
- .then(() => process.exit(0))
1476
+ .then(() => process.exit(process.exitCode || 0))
1317
1477
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1318
1478
  } else if (command === 'pulse') {
1319
1479
  // Pulse: durable overnight self-improvement heartbeat (OS cron) for atris-cli.
@@ -1329,11 +1489,24 @@ if (command === 'init') {
1329
1489
  Promise.resolve(require('../commands/worktree').worktreeCommand(process.argv.slice(3)))
1330
1490
  .then((code) => process.exit(code || 0))
1331
1491
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1492
+ } else if (command === 'autoland') {
1493
+ Promise.resolve(require('../commands/autoland').autolandCommand(process.argv.slice(3)))
1494
+ .then((code) => process.exit(code || 0))
1495
+ .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1496
+ } else if (command === 'land') {
1497
+ Promise.resolve(require('../commands/land').landCommand(process.argv.slice(3)))
1498
+ .then((code) => process.exit(code || 0))
1499
+ .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1332
1500
  } else if (command === 'radar' || command === 'ctop') {
1333
1501
  const radarArgs = command === 'ctop' ? ['--agents', ...process.argv.slice(3)] : process.argv.slice(3);
1334
1502
  Promise.resolve(require('../commands/radar').radarCommand(radarArgs))
1335
1503
  .then((code) => process.exit(code || 0))
1336
1504
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1505
+ } else if (command === 'truth') {
1506
+ // Truth: one table rolling up mission state, tasks, feature proof receipts, and loop heartbeats.
1507
+ Promise.resolve(require('../commands/truth').truthCommand(process.argv.slice(3)))
1508
+ .then((code) => process.exit(code || 0))
1509
+ .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1337
1510
  } else if (command === 'codex-goal') {
1338
1511
  Promise.resolve(require('../commands/codex-goal').codexGoalCommand(process.argv.slice(3)))
1339
1512
  .then(() => process.exit(process.exitCode || 0))
@@ -1830,7 +2003,14 @@ if (command === 'init') {
1830
2003
  process.exit(0);
1831
2004
  }
1832
2005
  const dryRun = process.argv.includes('--dry-run') || process.argv.includes('-n');
1833
- require('../commands/clean').cleanAtris({ dryRun });
2006
+ const json = process.argv.includes('--json');
2007
+ require('../commands/clean').cleanAtris({ dryRun, json });
2008
+ } else if (command === 'harvest') {
2009
+ Promise.resolve(require('../commands/harvest').harvestCommand(process.argv.slice(3)))
2010
+ .catch((err) => {
2011
+ console.error(err.message || err);
2012
+ process.exit(1);
2013
+ });
1834
2014
  } else if (command === 'verify') {
1835
2015
  const args = process.argv.slice(3);
1836
2016
  if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
@@ -2038,8 +2218,8 @@ if (command === 'init') {
2038
2218
  showLoopHelp();
2039
2219
  process.exit(0);
2040
2220
  }
2041
- require('../commands/loop').loopAtris(args)
2042
- .then(() => process.exit(0))
2221
+ Promise.resolve(require('../commands/loop-front').loopFront(args))
2222
+ .then((code) => process.exit(typeof code === 'number' ? code : 0))
2043
2223
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
2044
2224
  } else if (command === 'clean-workspace' || command === 'cw') {
2045
2225
  const { cleanWorkspace } = require('../commands/workspace-clean');
@@ -2065,6 +2245,12 @@ if (command === 'init') {
2065
2245
  Promise.resolve(require('../commands/slop').slopCommand(process.argv.slice(3)))
2066
2246
  .then((code) => process.exit(typeof code === 'number' ? code : 0))
2067
2247
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
2248
+ } else if (command === 'strings') {
2249
+ // Strings: content design system from live code (no LLM). Extracts UI strings, flags variants,
2250
+ // enforces preferred terms at the gate. Exit 1 = variants/banned terms found, for CI + the gate.
2251
+ Promise.resolve(require('../commands/strings').stringsCommand(process.argv.slice(3)))
2252
+ .then((code) => process.exit(typeof code === 'number' ? code : 0))
2253
+ .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
2068
2254
  } else if (command === 'security-review' || command === 'secure') {
2069
2255
  // Security review: deterministic secrets/PII/code-risk scan (no LLM). Exit 1 = HIGH finding,
2070
2256
  // for the autopilot/mission/CI gate + a SOC 2 evidence artifact via --json.
@@ -2318,13 +2504,12 @@ async function agentAtris() {
2318
2504
  // Respect -h / --help / help before any auth/state work
2319
2505
  const firstArg = process.argv[3];
2320
2506
  if (firstArg === '-h' || firstArg === '--help' || firstArg === 'help') {
2321
- console.log('Usage: atris agent [doctor|dogfood|spawn|spawns|spawn-status]');
2507
+ console.log('Usage: atris agent [doctor|spawn|spawns|spawn-status]');
2322
2508
  console.log('');
2323
2509
  console.log(' Pick which cloud agent to chat with from this workspace.');
2324
2510
  console.log(' Run `atris agent spawn <role> --task "..."` to create a worker request.');
2325
2511
  console.log(' Run `atris agent spawns` to list worker requests.');
2326
2512
  console.log(' Run `atris agent doctor` to verify local AI CLIs can see Atris context.');
2327
- console.log(' Run `atris agent dogfood --live` to smoke-test Devin/Droid with GLM 5.2.');
2328
2513
  console.log(' Requires `atris login` first.');
2329
2514
  console.log('');
2330
2515
  console.log(' After selecting, use: atris chat ["message"]');
@@ -2335,7 +2520,19 @@ async function agentAtris() {
2335
2520
  agentDoctor();
2336
2521
  }
2337
2522
  if (firstArg === 'dogfood') {
2338
- const result = require('../commands/agent-spawn').agentDogfoodCommand(process.argv.slice(4));
2523
+ // Internal diagnostic, gated off the public CLI. Operators use `atris agent doctor`.
2524
+ if (!process.env.ATRIS_INTERNAL_AGENT_DOGFOOD) {
2525
+ console.error('atris agent dogfood is an internal diagnostic and is not part of the public CLI.');
2526
+ console.error('Run `atris agent doctor` to verify local AI CLIs can see Atris context.');
2527
+ process.exit(1);
2528
+ }
2529
+ const dogfoodArgs = process.argv.slice(4);
2530
+ if (dogfoodArgs.includes('--help') || dogfoodArgs.includes('-h') || dogfoodArgs[0] === 'help') {
2531
+ console.log('Internal usage: atris agent dogfood [--live]');
2532
+ console.log(' Smoke-test Devin/Droid with GLM 5.2. Gated behind ATRIS_INTERNAL_AGENT_DOGFOOD.');
2533
+ process.exit(0);
2534
+ }
2535
+ const result = require('../commands/agent-spawn').agentDogfoodCommand(dogfoodArgs);
2339
2536
  process.exit(result.ok ? 0 : 1);
2340
2537
  }
2341
2538
  if (firstArg === 'spawn') {
@@ -2359,6 +2556,8 @@ async function agentAtris() {
2359
2556
  process.exit(1);
2360
2557
  }
2361
2558
 
2559
+ printAtrisGoalBanner(process.cwd());
2560
+
2362
2561
  // Check if logged in (with token refresh)
2363
2562
  const ensured = await ensureValidCredentials();
2364
2563
  if (ensured.error === 'not_logged_in' || !ensured.credentials?.token) {
@@ -2461,6 +2660,13 @@ async function chatAtris() {
2461
2660
  process.exit(1);
2462
2661
  }
2463
2662
 
2663
+ const missionIntent = missionRunIntentFromFastMessage(message);
2664
+ if (missionIntent) {
2665
+ process.exit(await runLocalFastMission(missionIntent));
2666
+ }
2667
+
2668
+ printAtrisGoalBanner(process.cwd());
2669
+
2464
2670
  // Check agent selected
2465
2671
  const config = loadConfig();
2466
2672
  if (!config.agent_id) {
@@ -2550,6 +2756,15 @@ async function chatInteractive(config, credentials) {
2550
2756
  return;
2551
2757
  }
2552
2758
 
2759
+ const missionIntent = missionRunIntentFromFastMessage(input);
2760
+ if (missionIntent) {
2761
+ console.log('');
2762
+ await runLocalFastMission(missionIntent);
2763
+ console.log('');
2764
+ rl.prompt();
2765
+ return;
2766
+ }
2767
+
2553
2768
  // Send to pro-chat
2554
2769
  console.log('');
2555
2770
  const apiUrl = getApiBaseUrl().replace(/\/api$/, '');
@@ -2598,6 +2813,78 @@ function atrisFastMessageFromArgs() {
2598
2813
  return process.argv.slice(offset).join(' ').trim();
2599
2814
  }
2600
2815
 
2816
+ function stripFastMissionQuotes(value) {
2817
+ const text = String(value || '').trim();
2818
+ if (text.length >= 2) {
2819
+ const first = text[0];
2820
+ const last = text[text.length - 1];
2821
+ if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
2822
+ return text.slice(1, -1).trim();
2823
+ }
2824
+ }
2825
+ return text;
2826
+ }
2827
+
2828
+ function extractFastMissionFlag(text, flagName) {
2829
+ const source = String(text || '');
2830
+ const inline = new RegExp(`(^|\\s)${flagName}=([^\\s]+)(?=\\s|$)`).exec(source);
2831
+ if (inline) {
2832
+ return {
2833
+ value: inline[2],
2834
+ text: `${source.slice(0, inline.index)}${inline[1] || ''}${source.slice(inline.index + inline[0].length)}`.replace(/\s+/g, ' ').trim(),
2835
+ };
2836
+ }
2837
+ const split = new RegExp(`(^|\\s)${flagName}\\s+([^\\s]+)(?=\\s|$)`).exec(source);
2838
+ if (split) {
2839
+ return {
2840
+ value: split[2],
2841
+ text: `${source.slice(0, split.index)}${split[1] || ''}${source.slice(split.index + split[0].length)}`.replace(/\s+/g, ' ').trim(),
2842
+ };
2843
+ }
2844
+ return { value: null, text: source.trim() };
2845
+ }
2846
+
2847
+ function missionRunIntentFromFastMessage(message) {
2848
+ return missionRuntime.missionRunIntentFromMessage(message);
2849
+ }
2850
+
2851
+ function printFastMissionStartReceipt(payload) {
2852
+ const mission = payload?.mission || {};
2853
+ const goal = payload?.atris_goal_state?.goal || {};
2854
+ const receiptGoal = {
2855
+ ...goal,
2856
+ objective: goal.objective || mission.objective,
2857
+ mission_id: goal.mission_id || mission.id,
2858
+ mission_status: goal.mission_status || mission.status,
2859
+ runner: goal.runner || mission.runner,
2860
+ created_at: goal.created_at || mission.created_at,
2861
+ updated_at: goal.updated_at || mission.updated_at,
2862
+ completed_at: goal.completed_at || mission.completed_at,
2863
+ next_command: goal.next_command || payload?.next_command || mission.next_action,
2864
+ };
2865
+ const elapsed = formatGoalDuration(goalElapsedSeconds(receiptGoal)) || '0s';
2866
+
2867
+ console.log('Atris mission started');
2868
+ console.log(`Goal: ${receiptGoal.objective || '?'}`);
2869
+ console.log(`Mission: ${receiptGoal.mission_id || '?'} · ${receiptGoal.mission_status || '?'} · ${receiptGoal.runner || 'atris2'}`);
2870
+ console.log(`Elapsed: ${elapsed}`);
2871
+ console.log(`Achieved: ${goalAchieved(receiptGoal) ? 'yes' : 'no'}`);
2872
+ if (receiptGoal.next_command) console.log(`Next: ${receiptGoal.next_command}`);
2873
+ }
2874
+
2875
+ async function runLocalFastMission(intent) {
2876
+ const result = await missionRuntime.runRuntimeMissionLoop(intent, {
2877
+ cwd: process.cwd(),
2878
+ cliPath: __filename,
2879
+ onProgress: (line) => {
2880
+ if (/^pursuing /.test(line)) console.log('Pursuing goal...');
2881
+ else if (/^\[tick /.test(line)) console.log(line);
2882
+ },
2883
+ });
2884
+ console.log(result.text || 'Mission command failed.');
2885
+ return result.ok ? 0 : (result.status || 1);
2886
+ }
2887
+
2601
2888
  async function atrisFastChat() {
2602
2889
  if (command === 'ax' && process.argv[3] !== 'fast') {
2603
2890
  console.error('Usage: atris ax fast "message"');
@@ -2622,6 +2909,13 @@ async function atrisFastChat() {
2622
2909
  process.exit(1);
2623
2910
  }
2624
2911
 
2912
+ const missionIntent = missionRunIntentFromFastMessage(message);
2913
+ if (missionIntent) {
2914
+ process.exit(await runLocalFastMission(missionIntent));
2915
+ }
2916
+
2917
+ printAtrisGoalBanner(process.cwd());
2918
+
2625
2919
  const ensured = await ensureValidCredentials();
2626
2920
  if (ensured.error === 'not_logged_in' || !ensured.credentials?.token) {
2627
2921
  console.error('✗ Error: Not logged in. Run "atris login" first.');
@@ -2651,135 +2945,3 @@ async function atrisFastOnce(credentials, message) {
2651
2945
  await streamProChat(endpoint, credentials.token, body);
2652
2946
  console.log('\n\n✓ Complete\n');
2653
2947
  }
2654
-
2655
- async function atrisDevEntry(userInput = null) {
2656
- // Load workspace context and present planning-ready state
2657
- // userInput: optional task description for hot start
2658
- const targetDir = path.join(process.cwd(), 'atris');
2659
-
2660
- // Check if Atris is initialized
2661
- if (!fs.existsSync(targetDir)) {
2662
- console.log('');
2663
- console.log('🚀 Welcome to Atris\n');
2664
- console.log('Not initialized yet. Let\'s get started:\n');
2665
- console.log(' → atris init Set up your workspace');
2666
- console.log(' → atris help See all commands\n');
2667
- return;
2668
- }
2669
-
2670
- ensureLogDirectory();
2671
- const { logFile, dateFormatted } = getLogPath();
2672
- if (!fs.existsSync(logFile)) {
2673
- createLogFile(logFile, dateFormatted);
2674
- }
2675
-
2676
- // Load context
2677
- const workspaceDir = process.cwd();
2678
- const state = detectWorkspaceState(workspaceDir);
2679
- const context = loadContext(workspaceDir);
2680
-
2681
- // Detect existing features
2682
- const featuresDir = path.join(targetDir, 'features');
2683
- let existingFeatures = [];
2684
- if (fs.existsSync(featuresDir)) {
2685
- existingFeatures = fs.readdirSync(featuresDir)
2686
- .filter(name => {
2687
- const featurePath = path.join(featuresDir, name);
2688
- return fs.statSync(featurePath).isDirectory() && !name.startsWith('_');
2689
- });
2690
- }
2691
-
2692
- console.log('');
2693
- console.log('┌─────────────────────────────────────────────────────────────┐');
2694
- console.log('│ Atris Mode │');
2695
- console.log('└─────────────────────────────────────────────────────────────┘');
2696
- console.log('');
2697
- console.log(`📅 ${dateFormatted}`);
2698
- console.log('');
2699
-
2700
- // Show existing features
2701
- if (existingFeatures.length > 0) {
2702
- console.log('📦 Features: ' + existingFeatures.join(', '));
2703
- console.log('');
2704
- }
2705
-
2706
- // Show active work
2707
- if (context.inProgressFeatures.length > 0) {
2708
- console.log('⚡ Active: ' + context.inProgressFeatures.join(', '));
2709
- console.log('');
2710
- }
2711
-
2712
- // Show inbox
2713
- if (context.hasInbox && context.inboxItems.length > 0) {
2714
- console.log(`📥 Inbox (${context.inboxItems.length}):`);
2715
- context.inboxItems.slice(0, 3).forEach((item, i) => {
2716
- const preview = item.length > 50 ? item.substring(0, 47) + '...' : item;
2717
- console.log(` ${i + 1}. ${preview}`);
2718
- });
2719
- if (context.inboxItems.length > 3) {
2720
- console.log(` ... and ${context.inboxItems.length - 3} more`);
2721
- }
2722
- console.log('');
2723
- }
2724
-
2725
- // Show recent completions
2726
- const logContent = fs.existsSync(logFile) ? fs.readFileSync(logFile, 'utf8') : '';
2727
- const completedMatch = logContent.match(/## Completed ✅\n([\s\S]*?)(?=\n##|$)/);
2728
- if (completedMatch && completedMatch[1].trim()) {
2729
- const completedItems = completedMatch[1].trim().split('\n')
2730
- .filter(line => line.match(/^- \*\*C\d+:/))
2731
- .slice(-2);
2732
- if (completedItems.length > 0) {
2733
- console.log('✅ Recent:');
2734
- completedItems.forEach(item => {
2735
- const match = item.match(/^- \*\*C\d+:\s*(.+)\*\*/);
2736
- if (match) {
2737
- const text = match[1].length > 50 ? match[1].substring(0, 47) + '...' : match[1];
2738
- console.log(` • ${text}`);
2739
- }
2740
- });
2741
- console.log('');
2742
- }
2743
- }
2744
-
2745
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
2746
- console.log('atris — navigator agent');
2747
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
2748
- console.log('');
2749
-
2750
- if (userInput) {
2751
- // Hot start - user provided task
2752
- console.log('User wants:');
2753
- console.log(`"${userInput}"`);
2754
- console.log('');
2755
- } else {
2756
- // Cold start - no specific task
2757
- console.log('Wait for user to describe what they want.');
2758
- console.log('');
2759
- }
2760
-
2761
- console.log('⚠️ APPROVAL REQUIRED — Follow this workflow:');
2762
- console.log('');
2763
- console.log('STEP 1: Show ASCII visualization');
2764
- console.log(' Create diagrams showing architecture/flow/UI');
2765
- console.log(' SHOW diagrams to user and WAIT for approval.');
2766
- console.log('');
2767
- console.log('STEP 2: After approval, determine scope');
2768
- if (existingFeatures.length > 0) {
2769
- console.log(' Existing: ' + existingFeatures.join(', '));
2770
- }
2771
- console.log(' NEW feature → atris/features/[name]/idea.md + build.md + validate.md');
2772
- console.log(' EXISTING → Update that feature\'s docs');
2773
- console.log(' SIMPLE → TODO.md only');
2774
- console.log('');
2775
- console.log('STEP 3: Create/update docs');
2776
- console.log(' idea.md = intent (any format)');
2777
- console.log(' build.md = technical spec');
2778
- console.log(' validate.md = proof it works (from _templates/validate.md.template)');
2779
- console.log(' lessons.md = read past lessons before planning, write new ones after validating');
2780
- console.log('');
2781
- console.log('⛔ DO NOT execute — that\'s for "atris do"');
2782
- console.log('');
2783
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
2784
- console.log('');
2785
- }