atris 3.30.12 → 3.32.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 (64) hide show
  1. package/AGENTS.md +16 -3
  2. package/FOR_AGENTS.md +81 -0
  3. package/README.md +6 -4
  4. package/atris/CLAUDE.md +8 -0
  5. package/atris/atris.md +51 -19
  6. package/atris/skills/README.md +1 -0
  7. package/atris/skills/blocks/SKILL.md +134 -0
  8. package/atris/skills/clawhub/philosophy-of-work/SKILL.md +56 -0
  9. package/atris/skills/youtube/SKILL.md +31 -11
  10. package/atris.md +15 -0
  11. package/ax +189 -7
  12. package/bin/atris.js +273 -225
  13. package/commands/autoland.js +379 -0
  14. package/commands/autopilot-front.js +273 -0
  15. package/commands/autopilot.js +94 -4
  16. package/commands/business.js +1 -1
  17. package/commands/clean.js +72 -9
  18. package/commands/codex-goal.js +72 -22
  19. package/commands/computer.js +48 -3
  20. package/commands/gm.js +1 -1
  21. package/commands/harvest.js +179 -0
  22. package/commands/init.js +22 -1
  23. package/commands/land.js +442 -0
  24. package/commands/loop-front.js +122 -4
  25. package/commands/member.js +551 -19
  26. package/commands/mission.js +3674 -278
  27. package/commands/play.js +1 -1
  28. package/commands/pulse.js +65 -7
  29. package/commands/run-front.js +144 -0
  30. package/commands/run.js +10 -7
  31. package/commands/sign.js +90 -0
  32. package/commands/strings.js +301 -0
  33. package/commands/task.js +782 -108
  34. package/commands/truth.js +171 -0
  35. package/commands/xp.js +32 -8
  36. package/commands/youtube.js +72 -5
  37. package/decks/README.md +6 -12
  38. package/lib/auto-accept-certified.js +10 -0
  39. package/lib/autoland.js +391 -0
  40. package/lib/context-gatherer.js +0 -8
  41. package/lib/mission-artifact.js +504 -0
  42. package/lib/mission-room.js +846 -0
  43. package/lib/next-moves.js +212 -6
  44. package/lib/operator-next.js +7 -0
  45. package/lib/pulse.js +78 -4
  46. package/lib/runner-command.js +51 -20
  47. package/lib/runs-prune.js +242 -0
  48. package/lib/task-db.js +19 -2
  49. package/lib/task-proof.js +1 -1
  50. package/package.json +4 -4
  51. package/decks/atris-seed-pitch-v3.json +0 -118
  52. package/decks/atris-seed-pitch-v4-skeleton.json +0 -106
  53. package/decks/atris-seed-pitch-v5.json +0 -109
  54. package/decks/atris-seed-pitch-v6.json +0 -137
  55. package/decks/atris-seed-pitch-v7.json +0 -133
  56. package/decks/mark-pincus-narrative.json +0 -102
  57. package/decks/mark-pincus-sourcery.json +0 -94
  58. package/decks/yash-applied-compute-detailed.json +0 -150
  59. package/decks/yash-applied-compute-generalist.json +0 -82
  60. package/decks/yash-applied-compute-narrative.json +0 -54
  61. package/lib/ax-chat-input.js +0 -164
  62. package/lib/ax-goal.js +0 -307
  63. package/lib/ax-prefs.js +0 -63
  64. package/lib/ax-shimmer.js +0 -63
@@ -15,7 +15,7 @@ const { parseTodo } = require('../lib/todo');
15
15
  const {
16
16
  buildRunnerCommand,
17
17
  buildRunnerAvailabilityCommand,
18
- resolveClaudeRunnerBin,
18
+ runnerAvailabilityFailureMessage,
19
19
  } = require('../lib/runner-command');
20
20
  const { findStalePages, findStaleTasks, healBrokenMapRefs } = require('./clean');
21
21
  const {
@@ -484,6 +484,91 @@ function executePhaseDetailed(phase, context, options = {}) {
484
484
  }
485
485
  }
486
486
 
487
+ function autopilotGlassLogDir(cwd = process.cwd()) {
488
+ const dir = path.join(cwd, 'atris', 'logs', 'autopilot');
489
+ fs.mkdirSync(dir, { recursive: true });
490
+ return dir;
491
+ }
492
+
493
+ function autopilotGlassSlug(value) {
494
+ const slug = String(value || 'tick')
495
+ .toLowerCase()
496
+ .replace(/[^a-z0-9]+/g, '-')
497
+ .replace(/^-+|-+$/g, '')
498
+ .slice(0, 64);
499
+ return slug || 'tick';
500
+ }
501
+
502
+ function autopilotGlassTimestamp(now = new Date()) {
503
+ return now.toISOString().replace(/[:.]/g, '-');
504
+ }
505
+
506
+ function phaseGlassText(phaseResults, key) {
507
+ const phase = phaseResults && phaseResults[key] ? phaseResults[key] : {};
508
+ const output = String(phase.output || '').trim();
509
+ const elapsed = Number.isFinite(phase.elapsedSeconds) ? `\n\nElapsed: ${phase.elapsedSeconds}s` : '';
510
+ return `${output || '(no output)'}${elapsed}`;
511
+ }
512
+
513
+ function buildAutopilotGlassLog(context, execution, options = {}) {
514
+ const phaseResults = execution.phaseResults || {};
515
+ const task = context.task || 'Autopilot tick';
516
+ const generatedAt = options.generatedAt || new Date().toISOString();
517
+ const proof = execution.verifyRan
518
+ ? `${execution.verifyPass ? 'PASS' : 'FAIL'}: ${phaseGlassText(phaseResults, 'verify')}`
519
+ : 'NOT RUN: no verifier command was available';
520
+
521
+ return [
522
+ '# Autopilot Glass Log',
523
+ '',
524
+ `Generated: ${generatedAt}`,
525
+ `Task: ${task}`,
526
+ `Kind: ${context.kind || 'unknown'}`,
527
+ `Verify: ${execution.verifyCmd || '(none)'}`,
528
+ `Outcome: ${execution.success ? 'success' : 'needs attention'}`,
529
+ '',
530
+ '## Plan',
531
+ '',
532
+ phaseGlassText(phaseResults, 'plan'),
533
+ '',
534
+ '## Plan Review',
535
+ '',
536
+ phaseGlassText(phaseResults, 'plan-review'),
537
+ '',
538
+ '## Action',
539
+ '',
540
+ phaseGlassText(phaseResults, 'do'),
541
+ '',
542
+ '## Result',
543
+ '',
544
+ phaseGlassText(phaseResults, 'review'),
545
+ '',
546
+ '## Proof',
547
+ '',
548
+ proof,
549
+ ''
550
+ ].join('\n');
551
+ }
552
+
553
+ function writeAutopilotGlassLog(cwd, context, execution, options = {}) {
554
+ const dir = autopilotGlassLogDir(cwd);
555
+ const now = options.now || new Date();
556
+ const file = `${autopilotGlassTimestamp(now)}-${autopilotGlassSlug(context.task)}.md`;
557
+ const absPath = path.join(dir, file);
558
+ fs.writeFileSync(absPath, buildAutopilotGlassLog(context, execution, {
559
+ generatedAt: now.toISOString(),
560
+ }));
561
+ return path.relative(cwd, absPath);
562
+ }
563
+
564
+ function maybeWriteAutopilotGlassLog(cwd, context, execution) {
565
+ try {
566
+ return writeAutopilotGlassLog(cwd, context, execution);
567
+ } catch {
568
+ return null;
569
+ }
570
+ }
571
+
487
572
  /**
488
573
  * Build context-aware file list for prompts.
489
574
  */
@@ -1806,7 +1891,7 @@ function runTaskOnce(context, options = {}) {
1806
1891
  }
1807
1892
  }
1808
1893
 
1809
- return {
1894
+ const execution = {
1810
1895
  success: verifyRan && verifyPass,
1811
1896
  elapsedSeconds: Math.round((Date.now() - startedAt) / 1000),
1812
1897
  phaseResults,
@@ -1815,6 +1900,8 @@ function runTaskOnce(context, options = {}) {
1815
1900
  verifyPass,
1816
1901
  verifyRan,
1817
1902
  };
1903
+ execution.glassLogPath = maybeWriteAutopilotGlassLog(cwd, context, execution);
1904
+ return execution;
1818
1905
  }
1819
1906
 
1820
1907
  /**
@@ -3022,8 +3109,8 @@ async function autopilotAtris(description, options = {}) {
3022
3109
  process.exit(1);
3023
3110
  }
3024
3111
 
3025
- try { execSync(buildRunnerAvailabilityCommand(), { stdio: 'pipe' }); } catch {
3026
- console.error(`${resolveClaudeRunnerBin()} CLI not found. Set ATRIS_RUNNER_BIN (or legacy ATRIS_CLAUDE_BIN), or install the configured runner first.`);
3112
+ try { execSync(buildRunnerAvailabilityCommand(), { stdio: 'pipe' }); } catch (err) {
3113
+ console.error(runnerAvailabilityFailureMessage(err));
3027
3114
  process.exit(1);
3028
3115
  }
3029
3116
 
@@ -3679,5 +3766,8 @@ module.exports = {
3679
3766
  isPhaseKillError,
3680
3767
  execPhaseCommandSync,
3681
3768
  executePhaseDetailed,
3769
+ autopilotGlassLogDir,
3770
+ buildAutopilotGlassLog,
3771
+ writeAutopilotGlassLog,
3682
3772
  lessonSlug
3683
3773
  };
@@ -840,7 +840,7 @@ function renderBusinessOsLines(os = {}, prefix = '- ') {
840
840
  `${prefix}Team goals: ${team.members || 0} member lanes, ${team.activeGoalMembers || 0} with active goals`,
841
841
  `${prefix}${xp.metric || 'AgentXP'}: ${xp.total || 0} total, ${xp.today || 0} today, ${xp.receipts || 0} receipts, integrity ${xp.integrity || 'unknown'}`,
842
842
  `${prefix}Loop: ${loop.ticks || 0} mission ticks; Codex goal ${loop.codexGoal || 'none'}`,
843
- `${prefix}XP gate: proof can move to Review; XP lands only after human accept`,
843
+ `${prefix}XP gate: proof can move to Review; XP is awarded only after human approval`,
844
844
  ];
845
845
  }
846
846
 
package/commands/clean.js CHANGED
@@ -15,20 +15,23 @@ function cleanAtris(options = {}) {
15
15
  const atrisDir = path.join(cwd, 'atris');
16
16
 
17
17
  if (!fs.existsSync(atrisDir)) {
18
+ if (options.json) {
19
+ console.log(JSON.stringify({
20
+ ok: false,
21
+ action: 'clean',
22
+ error: 'atris/ folder not found. Run "atris init" first.',
23
+ }, null, 2));
24
+ process.exit(1);
25
+ }
18
26
  console.log('✗ atris/ folder not found. Run "atris init" first.');
19
27
  process.exit(1);
20
28
  }
21
29
 
22
- console.log('');
23
- console.log('┌─────────────────────────────────────────────────────────────┐');
24
- console.log('│ Atris Clean │');
25
- console.log('└─────────────────────────────────────────────────────────────┘');
26
- console.log('');
27
-
28
30
  const results = {
29
31
  staleTasks: [],
30
32
  brokenRefs: [],
31
33
  healedRefs: 0,
34
+ healedRefDetails: [],
32
35
  unhealableRefs: [],
33
36
  archivedJournals: 0,
34
37
  cleanedSections: 0
@@ -39,8 +42,9 @@ function cleanAtris(options = {}) {
39
42
  results.staleTasks = staleTasks;
40
43
 
41
44
  // 2. Find and HEAL broken MAP.md references
42
- const { healed, unhealable } = healBrokenMapRefs(cwd, atrisDir, options.dryRun);
45
+ const { healed, unhealable, replacements } = healBrokenMapRefs(cwd, atrisDir, options.dryRun);
43
46
  results.healedRefs = healed;
47
+ results.healedRefDetails = replacements;
44
48
  results.unhealableRefs = unhealable;
45
49
 
46
50
  // 3. Archive old journals (>30 days)
@@ -55,6 +59,17 @@ function cleanAtris(options = {}) {
55
59
  const stalePages = findStalePages(cwd, atrisDir);
56
60
  results.stalePages = stalePages;
57
61
 
62
+ if (options.json) {
63
+ console.log(JSON.stringify(cleanResultPayload(results, options, cwd), null, 2));
64
+ return results;
65
+ }
66
+
67
+ console.log('');
68
+ console.log('┌─────────────────────────────────────────────────────────────┐');
69
+ console.log('│ Atris Clean │');
70
+ console.log('└─────────────────────────────────────────────────────────────┘');
71
+ console.log('');
72
+
58
73
  // Report results
59
74
  console.log('Results:');
60
75
  console.log('');
@@ -74,6 +89,12 @@ function cleanAtris(options = {}) {
74
89
  if (healed > 0) {
75
90
  const verb = options.dryRun ? 'Would heal' : 'Healed';
76
91
  console.log(`✓ ${verb} ${healed} MAP.md ${healed === 1 ? 'reference' : 'references'}`);
92
+ (results.healedRefDetails || []).slice(0, 3).forEach(ref => {
93
+ console.log(` • ${ref.old} -> ${ref.new}${ref.symbol ? ` (${ref.symbol})` : ''}`);
94
+ });
95
+ if ((results.healedRefDetails || []).length > 3) {
96
+ console.log(` ... and ${results.healedRefDetails.length - 3} more`);
97
+ }
77
98
  }
78
99
 
79
100
  // Unhealable refs
@@ -138,6 +159,47 @@ function cleanAtris(options = {}) {
138
159
  return results;
139
160
  }
140
161
 
162
+ function cleanResultPayload(results, options = {}, cwd = process.cwd()) {
163
+ const manualAction = [];
164
+ if (results.staleTasks.length > 0) manualAction.push('Delete stale tasks or finish them');
165
+ if (results.unhealableRefs.length > 0) manualAction.push('Manually fix unhealable MAP.md refs');
166
+ if (results.stalePages.length > 0) manualAction.push('Re-compile stale pages');
167
+ return {
168
+ ok: manualAction.length === 0,
169
+ action: 'clean',
170
+ dry_run: !!options.dryRun,
171
+ results: {
172
+ stale_tasks: {
173
+ count: results.staleTasks.length,
174
+ items: results.staleTasks,
175
+ },
176
+ map_refs: {
177
+ healed_count: results.healedRefs,
178
+ verb: options.dryRun ? 'would_heal' : 'healed',
179
+ items: results.healedRefDetails || [],
180
+ unhealable_count: results.unhealableRefs.length,
181
+ unhealable: results.unhealableRefs,
182
+ },
183
+ journals: {
184
+ archived_count: results.archivedJournals,
185
+ verb: options.dryRun ? 'would_archive' : 'archived',
186
+ },
187
+ sections: {
188
+ cleaned_count: results.cleanedSections,
189
+ verb: options.dryRun ? 'would_clean' : 'cleaned',
190
+ },
191
+ stale_pages: {
192
+ count: results.stalePages.length,
193
+ items: results.stalePages.map(page => ({
194
+ page: path.relative(cwd, page.page),
195
+ stale_source: page.staleSource,
196
+ })),
197
+ },
198
+ },
199
+ manual_action: manualAction,
200
+ };
201
+ }
202
+
141
203
  /**
142
204
  * Find tasks claimed but not completed after 3+ days
143
205
  */
@@ -295,7 +357,7 @@ function healBrokenMapRefs(cwd, atrisDir, dryRun = false) {
295
357
  fs.writeFileSync(mapFile, mapContent);
296
358
  }
297
359
 
298
- return { healed, unhealable, replacements: dryRun ? replacements : [] };
360
+ return { healed, unhealable, replacements };
299
361
  }
300
362
 
301
363
  /**
@@ -337,7 +399,7 @@ function findFunctionEnd(lines, startLine) {
337
399
  }
338
400
 
339
401
  /**
340
- * Extract a symbol name from context like "(atrisDevEntry function)" or "— Main entry"
402
+ * Extract a symbol name from context like "(interactiveEntry function)" or "— Main entry"
341
403
  */
342
404
  function extractSymbol(context) {
343
405
  if (!context) return null;
@@ -568,6 +630,7 @@ function cleanEmptySections(atrisDir, dryRun = false) {
568
630
 
569
631
  module.exports = {
570
632
  cleanAtris,
633
+ cleanResultPayload,
571
634
  findStaleTasks,
572
635
  healBrokenMapRefs,
573
636
  archiveOldJournals,
@@ -32,7 +32,50 @@ function sqlString(value) {
32
32
  }
33
33
 
34
34
  function resolveStatePath(args = []) {
35
- return path.resolve(expandHome(readFlag(args, '--state', process.env.CODEX_STATE_DB || path.join(os.homedir(), '.codex', 'state_5.sqlite'))));
35
+ const explicit = readFlag(args, '--state', process.env.CODEX_STATE_DB || '');
36
+ if (explicit) return path.resolve(expandHome(explicit));
37
+ // Codex moved native goals into ~/.codex/goals_1.sqlite; older builds kept them in state_5.sqlite.
38
+ // Prefer the live goals DB so the bridge sees real goal activity, fall back to the legacy state DB.
39
+ const goalsDb = path.join(os.homedir(), '.codex', 'goals_1.sqlite');
40
+ const legacyDb = path.join(os.homedir(), '.codex', 'state_5.sqlite');
41
+ return path.resolve(fs.existsSync(goalsDb) ? goalsDb : legacyDb);
42
+ }
43
+
44
+ // Thread metadata (cwd/title) stayed in state_5.sqlite even after goals moved to goals_1.sqlite.
45
+ function resolveThreadsPath(args = []) {
46
+ const explicit = readFlag(args, '--threads-db', process.env.CODEX_THREADS_DB || '');
47
+ if (explicit) return path.resolve(expandHome(explicit));
48
+ const legacyDb = path.join(os.homedir(), '.codex', 'state_5.sqlite');
49
+ return fs.existsSync(legacyDb) ? path.resolve(legacyDb) : '';
50
+ }
51
+
52
+ function tableExists(dbPath, table) {
53
+ if (!dbPath || !fs.existsSync(dbPath)) return false;
54
+ try {
55
+ const rows = runSqliteJson(dbPath, `SELECT name FROM sqlite_master WHERE type='table' AND name=${sqlString(table)} LIMIT 1;`);
56
+ return rows.length > 0;
57
+ } catch {
58
+ return false;
59
+ }
60
+ }
61
+
62
+ // Goals live in goals_1.sqlite (no `threads` table); thread metadata lives in state_5.sqlite.
63
+ // Legacy single-file layouts (and test fixtures) keep both in one DB. Resolve which join to use.
64
+ function goalQueryContext(args = []) {
65
+ const goalsDb = resolveStatePath(args);
66
+ if (tableExists(goalsDb, 'threads')) {
67
+ return { goalsDb, threadsTable: 'threads', prefix: '' };
68
+ }
69
+ const threadsDb = resolveThreadsPath(args);
70
+ if (threadsDb && threadsDb !== goalsDb && tableExists(threadsDb, 'threads')) {
71
+ return { goalsDb, threadsTable: 'tdb.threads', prefix: `ATTACH ${sqlString(threadsDb)} AS tdb;\n` };
72
+ }
73
+ return { goalsDb, threadsTable: null, prefix: '' };
74
+ }
75
+
76
+ function runGoalQuery(args, buildSql) {
77
+ const ctx = goalQueryContext(args);
78
+ return runSqliteJson(ctx.goalsDb, ctx.prefix + buildSql(ctx.threadsTable));
36
79
  }
37
80
 
38
81
  function defaultRunsDir(args = []) {
@@ -81,7 +124,11 @@ function ensureStateDb(dbPath) {
81
124
  }
82
125
  }
83
126
 
84
- function selectGoalSql(whereClause, limit = 10) {
127
+ function selectGoalSql(whereClause, limit = 10, threadsTable = 'threads') {
128
+ const join = threadsTable ? `LEFT JOIN ${threadsTable} t ON t.id = tg.thread_id` : '';
129
+ const cwd = threadsTable ? 't.cwd' : 'NULL';
130
+ const title = threadsTable ? 't.title' : 'NULL';
131
+ const threadUpdated = threadsTable ? 't.updated_at_ms' : 'NULL';
85
132
  return `
86
133
  SELECT
87
134
  tg.thread_id,
@@ -93,41 +140,43 @@ SELECT
93
140
  tg.time_used_seconds,
94
141
  tg.created_at_ms,
95
142
  tg.updated_at_ms,
96
- t.cwd AS thread_cwd,
97
- t.title AS thread_title,
98
- t.updated_at_ms AS thread_updated_at_ms
143
+ ${cwd} AS thread_cwd,
144
+ ${title} AS thread_title,
145
+ ${threadUpdated} AS thread_updated_at_ms
99
146
  FROM thread_goals tg
100
- LEFT JOIN threads t ON t.id = tg.thread_id
147
+ ${join}
101
148
  ${whereClause}
102
- ORDER BY COALESCE(t.updated_at_ms, tg.updated_at_ms) DESC
149
+ ORDER BY COALESCE(${threadUpdated}, tg.updated_at_ms) DESC
103
150
  LIMIT ${Number(limit) || 10}
104
151
  `;
105
152
  }
106
153
 
107
- function readGoalByThread(dbPath, threadId) {
108
- const rows = runSqliteJson(dbPath, selectGoalSql(`WHERE tg.thread_id = ${sqlString(threadId)}`, 1));
154
+ function readGoalByThread(args, threadId) {
155
+ const rows = runGoalQuery(args, (tt) => selectGoalSql(`WHERE tg.thread_id = ${sqlString(threadId)}`, 1, tt));
109
156
  return rows[0] || null;
110
157
  }
111
158
 
112
- function readLatestGoalForCwd(dbPath, cwd) {
159
+ function readLatestGoalForCwd(args, cwd) {
160
+ const ctx = goalQueryContext(args);
161
+ if (!ctx.threadsTable) return null; // cannot cwd-match without thread metadata
113
162
  const realCwd = fs.realpathSync.native ? fs.realpathSync.native(cwd) : fs.realpathSync(cwd);
114
163
  const pwd = process.env.PWD || '';
115
164
  const pwdReal = pwd && fs.existsSync(pwd) ? (fs.realpathSync.native ? fs.realpathSync.native(pwd) : fs.realpathSync(pwd)) : '';
116
165
  const candidates = [...new Set([cwd, realCwd, pwdReal === realCwd ? pwd : ''].filter(Boolean))];
117
- const rows = runSqliteJson(dbPath, selectGoalSql(`WHERE t.cwd IN (${candidates.map(sqlString).join(', ')})`, 1));
166
+ const rows = runSqliteJson(ctx.goalsDb, ctx.prefix + selectGoalSql(`WHERE t.cwd IN (${candidates.map(sqlString).join(', ')})`, 1, ctx.threadsTable));
118
167
  return rows[0] || null;
119
168
  }
120
169
 
121
- function readRecentGoals(dbPath, limit = 10) {
122
- return runSqliteJson(dbPath, selectGoalSql('', limit));
170
+ function readRecentGoals(args, limit = 10) {
171
+ return runGoalQuery(args, (tt) => selectGoalSql('', limit, tt));
123
172
  }
124
173
 
125
- function resolveThreadGoal(dbPath, args) {
174
+ function resolveThreadGoal(args) {
126
175
  const explicitThread = readFlag(args, '--thread', '');
127
- if (explicitThread) return readGoalByThread(dbPath, explicitThread);
128
- if (hasFlag(args, '--latest')) return readLatestGoalForCwd(dbPath, process.cwd());
176
+ if (explicitThread) return readGoalByThread(args, explicitThread);
177
+ if (hasFlag(args, '--latest')) return readLatestGoalForCwd(args, process.cwd());
129
178
  const envThread = process.env.CODEX_THREAD_ID || '';
130
- if (envThread) return readGoalByThread(dbPath, envThread);
179
+ if (envThread) return readGoalByThread(args, envThread);
131
180
  return null;
132
181
  }
133
182
 
@@ -163,7 +212,7 @@ function statusCommand(args) {
163
212
  const dbPath = resolveStatePath(args);
164
213
  ensureStateDb(dbPath);
165
214
 
166
- const goal = resolveThreadGoal(dbPath, args);
215
+ const goal = resolveThreadGoal(args);
167
216
  if (goal) {
168
217
  const payload = { ok: true, schema: SCHEMA, action: 'status', state_path: dbPath, goal };
169
218
  printJsonOrText(payload, [
@@ -176,7 +225,7 @@ function statusCommand(args) {
176
225
  }
177
226
 
178
227
  const limit = Math.max(1, Math.min(50, Number(readFlag(args, '--limit', '10')) || 10));
179
- const goals = readRecentGoals(dbPath, limit);
228
+ const goals = readRecentGoals(args, limit);
180
229
  const payload = { ok: true, schema: SCHEMA, action: 'status', state_path: dbPath, goals };
181
230
  printJsonOrText(payload, [
182
231
  `Codex goals: ${goals.length} recent`,
@@ -192,7 +241,7 @@ function resetCommand(args) {
192
241
  ensureStateDb(dbPath);
193
242
 
194
243
  const startedAt = new Date().toISOString();
195
- const goal = resolveThreadGoal(dbPath, args);
244
+ const goal = resolveThreadGoal(args);
196
245
  if (!goal) {
197
246
  throw new Error('No Codex goal found. Pass --thread <thread-id> or --latest.');
198
247
  }
@@ -238,7 +287,7 @@ SELECT changes() AS deleted;
238
287
  COMMIT;
239
288
  `, { readonly: false });
240
289
  const deleted = Number(rows[0]?.deleted || 0);
241
- const remaining = readGoalByThread(dbPath, goal.thread_id);
290
+ const remaining = readGoalByThread(args, goal.thread_id);
242
291
  const ok = deleted === 1 && !remaining;
243
292
  const payload = writeReceipt(outDir, {
244
293
  ok,
@@ -277,7 +326,8 @@ function usage() {
277
326
  ` atris codex-goal reset --thread <id> ${CONFIRM_RESET_FLAG}`,
278
327
  '',
279
328
  'Flags:',
280
- ' --state <path> Codex state DB (default ~/.codex/state_5.sqlite)',
329
+ ' --state <path> Codex goals DB (default ~/.codex/goals_1.sqlite, falls back to state_5.sqlite)',
330
+ ' --threads-db <path> Codex thread metadata DB for cwd/title (default ~/.codex/state_5.sqlite)',
281
331
  ' --latest Use the latest Codex goal whose thread cwd matches the current directory',
282
332
  ' --out-dir <path> Receipt/backup directory (default .atris/runs)',
283
333
  '',
@@ -1635,6 +1635,41 @@ async function resolveBusinessContextBySlug(token, slug, options = {}) {
1635
1635
  return null;
1636
1636
  }
1637
1637
 
1638
+ function shortcutBusinessLabel(slug, fallbackName) {
1639
+ if (normalizeBusinessSlug(slug) === RECRUITING_BUSINESS_SLUG) return 'Atris Labs';
1640
+ return fallbackName || slug;
1641
+ }
1642
+
1643
+ async function resolveSingleBusinessShortcutFallback(token, slug) {
1644
+ const wantedSlug = normalizeBusinessSlug(slug);
1645
+ if (wantedSlug !== RECRUITING_BUSINESS_SLUG) return null;
1646
+
1647
+ const list = await apiRequestJson('/business/', { method: 'GET', token });
1648
+ const rows = list.ok && Array.isArray(list.data)
1649
+ ? list.data.filter((business) => business && business.id)
1650
+ : [];
1651
+ if (rows.length !== 1) return null;
1652
+
1653
+ const match = rows[0];
1654
+ const businesses = loadBusinesses();
1655
+ const businessName = shortcutBusinessLabel(wantedSlug, match.name || match.slug);
1656
+ businesses[wantedSlug] = {
1657
+ business_id: match.id,
1658
+ workspace_id: match.workspace_id,
1659
+ name: businessName,
1660
+ slug: wantedSlug,
1661
+ cloud_slug: match.slug || null,
1662
+ added_at: new Date().toISOString(),
1663
+ };
1664
+ saveBusinesses(businesses);
1665
+ return {
1666
+ slug: wantedSlug,
1667
+ businessId: match.id,
1668
+ workspaceId: match.workspace_id,
1669
+ businessName,
1670
+ };
1671
+ }
1672
+
1638
1673
  async function resolveComputerCommandContext(token, options = {}) {
1639
1674
  if (options.businessSlug || options.workspaceId) {
1640
1675
  const ctx = options.businessSlug
@@ -1660,7 +1695,10 @@ async function resolveTypedBusinessComputerContext(token, options = {}, defaults
1660
1695
  return resolveComputerCommandContext(token, { ...options, businessSlug });
1661
1696
  }
1662
1697
 
1663
- const ctx = await resolveBusinessContextBySlug(token, businessSlug, { preferCache: true });
1698
+ let ctx = await resolveBusinessContextBySlug(token, businessSlug, { preferCache: true });
1699
+ if (!ctx?.businessId && (options.allowCanonicalShortcutFallback || defaults.allowCanonicalShortcutFallback)) {
1700
+ ctx = await resolveSingleBusinessShortcutFallback(token, businessSlug);
1701
+ }
1664
1702
  if (!ctx?.businessId) return null;
1665
1703
  const workspaces = await listBusinessWorkspaces(token, ctx);
1666
1704
  const workspace = resolveWorkspaceByComputerType(workspaces, computerType);
@@ -1705,7 +1743,7 @@ async function resolveWorkspaceSelector(token, ctx, input) {
1705
1743
  return workspace?.id || selector;
1706
1744
  }
1707
1745
 
1708
- async function resolveBusinessOwnerForCreate(token, businessSlug = null) {
1746
+ async function resolveBusinessOwnerForCreate(token, businessSlug = null, options = {}) {
1709
1747
  const wantedSlug = businessSlug ? String(businessSlug).trim() : null;
1710
1748
  if (wantedSlug) {
1711
1749
  const fromApi = await resolveBusinessContextBySlug(token, wantedSlug);
@@ -1720,6 +1758,9 @@ async function resolveBusinessOwnerForCreate(token, businessSlug = null) {
1720
1758
  businessName: cached.name || cached.slug || wantedSlug,
1721
1759
  };
1722
1760
  }
1761
+ if (options.allowCanonicalShortcutFallback) {
1762
+ return resolveSingleBusinessShortcutFallback(token, wantedSlug);
1763
+ }
1723
1764
  return null;
1724
1765
  }
1725
1766
 
@@ -2248,7 +2289,9 @@ async function computerCreate(token, args = [], defaults = {}) {
2248
2289
  return;
2249
2290
  }
2250
2291
 
2251
- const ctx = await resolveBusinessOwnerForCreate(token, options.businessSlug);
2292
+ const ctx = await resolveBusinessOwnerForCreate(token, options.businessSlug, {
2293
+ allowCanonicalShortcutFallback: Boolean(defaults.allowCanonicalShortcutFallback),
2294
+ });
2252
2295
  if (!ctx?.businessId) {
2253
2296
  console.error('No business found.');
2254
2297
  console.error('Run inside a bound business workspace or pass: --business <slug>');
@@ -3874,6 +3917,7 @@ async function runRecruitingComputerShortcut(token, args, cloudOptions = {}) {
3874
3917
  return computerCreate(token, finalArgs, {
3875
3918
  businessSlug: cloudOptions.businessSlug || RECRUITING_BUSINESS_SLUG,
3876
3919
  computerType: 'recruiting',
3920
+ allowCanonicalShortcutFallback: !cloudOptions.businessSlug,
3877
3921
  });
3878
3922
  }
3879
3923
 
@@ -3888,6 +3932,7 @@ async function runRecruitingComputerShortcut(token, args, cloudOptions = {}) {
3888
3932
  const ctx = await resolveTypedBusinessComputerContext(token, recruitingOptions, {
3889
3933
  businessSlug: RECRUITING_BUSINESS_SLUG,
3890
3934
  computerType: 'recruiting',
3935
+ allowCanonicalShortcutFallback: !recruitingOptions.businessSlug,
3891
3936
  });
3892
3937
  if (!ctx?.businessId) {
3893
3938
  console.error('Atris Recruiting is not available for this account.');
package/commands/gm.js CHANGED
@@ -331,7 +331,7 @@ function gmState(args = []) {
331
331
  missions,
332
332
  review_queue: reviewQueue,
333
333
  next_commands: commands,
334
- xp_rule: 'GM can route missions and review proof, but AgentXP still lands only after human accept.',
334
+ xp_rule: 'GM can route missions and review proof, but AgentXP is awarded only after human approval.',
335
335
  global_sync_rule: AGENTXP_GLOBAL_SYNC_RULE,
336
336
  leaderboard_url: AGENTXP_LEADERBOARD_URL,
337
337
  };