atris 3.30.1 → 3.30.3

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 (67) hide show
  1. package/AGENTS.md +6 -0
  2. package/atris/AGENTS.md +11 -0
  3. package/atris/CLAUDE.md +5 -0
  4. package/atris/atris.md +19 -4
  5. package/atris/policies/atris-design.md +71 -0
  6. package/atris/policies/design-seed.md +187 -0
  7. package/atris/skills/atris/SKILL.md +26 -3
  8. package/atris/skills/design/SKILL.md +3 -2
  9. package/atris/skills/loop/SKILL.md +5 -3
  10. package/atris/team/_template/MEMBER.md +19 -0
  11. package/atris.md +63 -23
  12. package/ax +1434 -1698
  13. package/bin/atris.js +5 -1
  14. package/commands/agent-spawn.js +2 -2
  15. package/commands/brain.js +92 -7
  16. package/commands/brainstorm.js +62 -22
  17. package/commands/business-sync.js +92 -8
  18. package/commands/business.js +13 -7
  19. package/commands/chat-scan.js +102 -0
  20. package/commands/computer.js +758 -15
  21. package/commands/deck.js +505 -105
  22. package/commands/init.js +6 -1
  23. package/commands/launchpad.js +638 -0
  24. package/commands/log-sync.js +44 -13
  25. package/commands/loop-front.js +290 -0
  26. package/commands/member.js +124 -3
  27. package/commands/mission.js +717 -32
  28. package/commands/pull.js +37 -28
  29. package/commands/pulse.js +11 -8
  30. package/commands/run.js +79 -39
  31. package/commands/sync.js +36 -17
  32. package/commands/task.js +342 -66
  33. package/commands/xp.js +3 -0
  34. package/commands/youtube.js +221 -7
  35. package/decks/README.md +89 -0
  36. package/decks/archetype-catalog.json +180 -0
  37. package/decks/atris-antislop-pitch.json +48 -0
  38. package/decks/atris-archetypes-v2.json +83 -0
  39. package/decks/atris-one-loop-pitch.json +80 -0
  40. package/decks/atris-seed-pitch-v3.json +118 -0
  41. package/decks/atris-seed-pitch-v4-skeleton.json +106 -0
  42. package/decks/atris-seed-pitch-v5.json +109 -0
  43. package/decks/atris-seed-pitch-v6.json +137 -0
  44. package/decks/atris-seed-pitch-v7.json +133 -0
  45. package/decks/atris-single-shot-proof.json +74 -0
  46. package/decks/mark-pincus-narrative.json +102 -0
  47. package/decks/mark-pincus-sourcery.json +94 -0
  48. package/decks/yash-applied-compute-detailed.json +150 -0
  49. package/decks/yash-applied-compute-generalist.json +82 -0
  50. package/decks/yash-applied-compute-narrative.json +54 -0
  51. package/lib/ax-prefs.js +5 -12
  52. package/lib/chat-log-scan.js +377 -0
  53. package/lib/context-gatherer.js +35 -1
  54. package/lib/deck-compose.js +145 -0
  55. package/lib/deck-history.js +64 -0
  56. package/lib/deck-layout.js +169 -0
  57. package/lib/deck-review.js +431 -0
  58. package/lib/deck-schema.js +154 -0
  59. package/lib/file-ops.js +2 -2
  60. package/lib/functional-owner.js +189 -0
  61. package/lib/slides-deck.js +512 -58
  62. package/lib/task-db.js +109 -2
  63. package/package.json +2 -1
  64. package/templates/business-starter/team/START_HERE.md +12 -8
  65. package/utils/auth.js +4 -0
  66. package/utils/config.js +4 -0
  67. package/atris/atrisDev.md +0 -717
@@ -8,6 +8,34 @@ const { ensureValidCredentials } = require('../utils/auth');
8
8
  const { apiRequestJson } = require('../utils/api');
9
9
  const { promptUser } = require('../utils/auth');
10
10
 
11
+ class LogSyncWriteError extends Error {
12
+ constructor(message) {
13
+ super(message);
14
+ this.name = 'LogSyncWriteError';
15
+ }
16
+ }
17
+
18
+ function formatWriteError(error) {
19
+ return error && error.message ? error.message : String(error);
20
+ }
21
+
22
+ function writeLogFile(logFile, content) {
23
+ try {
24
+ fs.writeFileSync(logFile, content, 'utf8');
25
+ } catch (error) {
26
+ const relativePath = path.relative(process.cwd(), logFile);
27
+ throw new LogSyncWriteError(`Could not write local journal "${relativePath}": ${formatWriteError(error)}. Check disk space and file permissions.`);
28
+ }
29
+ }
30
+
31
+ function saveLogSyncStateSafely(state) {
32
+ try {
33
+ saveLogSyncState(state);
34
+ } catch (error) {
35
+ throw new LogSyncWriteError(`Could not save log sync state: ${formatWriteError(error)}. Check disk space and file permissions.`);
36
+ }
37
+ }
38
+
11
39
  async function logSyncAtris() {
12
40
  const targetDir = path.join(process.cwd(), 'atris');
13
41
 
@@ -110,7 +138,7 @@ async function logSyncAtris() {
110
138
  updated_at: remoteUpdatedAt,
111
139
  hash: remoteHash || knownRemoteHash || computeContentHash(remoteContent || ''),
112
140
  };
113
- saveLogSyncState(state);
141
+ saveLogSyncStateSafely(state);
114
142
  }
115
143
  console.log('✓ Already synced (timestamps aligned with web)');
116
144
  return;
@@ -124,7 +152,7 @@ async function logSyncAtris() {
124
152
  if (conflicts.length === 0) {
125
153
  // Clean merge - auto-merge and continue
126
154
  const mergedContent = reconstructJournal(merged);
127
- fs.writeFileSync(logFile, mergedContent, 'utf8');
155
+ writeLogFile(logFile, mergedContent);
128
156
  console.log('✓ Auto-merged web and local changes');
129
157
  console.log(` Merged sections: ${Object.keys(merged).filter(k => k !== '__header__').join(', ')}`);
130
158
  finalLocalContent = mergedContent;
@@ -143,7 +171,7 @@ async function logSyncAtris() {
143
171
  updated_at: remoteUpdatedAt,
144
172
  hash: computeContentHash(mergedContent),
145
173
  };
146
- saveLogSyncState(state);
174
+ saveLogSyncStateSafely(state);
147
175
  return;
148
176
  }
149
177
  } else {
@@ -168,7 +196,7 @@ async function logSyncAtris() {
168
196
  if (answer === '1') {
169
197
  // Use web version
170
198
  const pulledContent = existing.data?.content || '';
171
- fs.writeFileSync(logFile, pulledContent, 'utf8');
199
+ writeLogFile(logFile, pulledContent);
172
200
  remoteHash = computeContentHash(pulledContent);
173
201
  finalLocalContent = pulledContent;
174
202
  console.log('✓ Local journal updated from web');
@@ -183,7 +211,7 @@ async function logSyncAtris() {
183
211
  updated_at: remoteUpdatedAt,
184
212
  hash: remoteHash || computeContentHash(pulledContent),
185
213
  };
186
- saveLogSyncState(state);
214
+ saveLogSyncStateSafely(state);
187
215
  }
188
216
  // Don't push - web already has the correct version
189
217
  shouldPush = false;
@@ -194,7 +222,7 @@ async function logSyncAtris() {
194
222
  } else if (answer === '3') {
195
223
  // Merge both
196
224
  const mergedContent = reconstructJournal(merged);
197
- fs.writeFileSync(logFile, mergedContent, 'utf8');
225
+ writeLogFile(logFile, mergedContent);
198
226
  finalLocalContent = mergedContent;
199
227
  console.log('✓ Merged both versions (check for duplicates)');
200
228
  console.log(` Merged sections: ${Object.keys(merged).filter(k => k !== '__header__').join(', ')}`);
@@ -205,6 +233,9 @@ async function logSyncAtris() {
205
233
  }
206
234
  }
207
235
  } catch (parseError) {
236
+ if (parseError instanceof LogSyncWriteError) {
237
+ throw parseError;
238
+ }
208
239
  // Fallback to simple prompt
209
240
  console.log('⚠️ Web version is newer than local version');
210
241
  console.log(` Remote updated: ${remoteUpdatedAt}`);
@@ -223,7 +254,7 @@ async function logSyncAtris() {
223
254
 
224
255
  if (answer === '1') {
225
256
  const pulledContent = existing.data?.content || '';
226
- fs.writeFileSync(logFile, pulledContent, 'utf8');
257
+ writeLogFile(logFile, pulledContent);
227
258
  remoteHash = computeContentHash(pulledContent);
228
259
  finalLocalContent = pulledContent;
229
260
  console.log('✓ Local journal updated from web');
@@ -238,7 +269,7 @@ async function logSyncAtris() {
238
269
  updated_at: remoteUpdatedAt,
239
270
  hash: remoteHash || computeContentHash(pulledContent),
240
271
  };
241
- saveLogSyncState(state);
272
+ saveLogSyncStateSafely(state);
242
273
  }
243
274
  // Don't push - web already has the correct version
244
275
  shouldPush = false;
@@ -265,14 +296,14 @@ async function logSyncAtris() {
265
296
  updated_at: remoteUpdatedAt,
266
297
  hash: remoteHash || knownRemoteHash || computeContentHash(remoteContent || ''),
267
298
  };
268
- saveLogSyncState(state);
299
+ saveLogSyncStateSafely(state);
269
300
  }
270
301
  return;
271
302
  } else if (localMatchesKnown && !remoteMatchesKnown) {
272
303
  // Local unchanged, web has updates - just pull
273
304
  console.log('📥 Web has updates, local unchanged - pulling...');
274
305
  const pulledContent = existing.data?.content || '';
275
- fs.writeFileSync(logFile, pulledContent, 'utf8');
306
+ writeLogFile(logFile, pulledContent);
276
307
  const pulledHash = computeContentHash(pulledContent);
277
308
  if (remoteUpdatedAt) {
278
309
  const remoteDate = new Date(remoteUpdatedAt);
@@ -284,7 +315,7 @@ async function logSyncAtris() {
284
315
  updated_at: remoteUpdatedAt,
285
316
  hash: pulledHash,
286
317
  };
287
- saveLogSyncState(state);
318
+ saveLogSyncStateSafely(state);
288
319
  }
289
320
  console.log('✓ Local journal updated from web');
290
321
  console.log(`🗒️ File: ${path.relative(process.cwd(), logFile)}`);
@@ -304,7 +335,7 @@ async function logSyncAtris() {
304
335
 
305
336
  // Update local file with final content if it changed
306
337
  if (finalLocalContent !== localContent) {
307
- fs.writeFileSync(logFile, finalLocalContent, 'utf8');
338
+ writeLogFile(logFile, finalLocalContent);
308
339
  }
309
340
 
310
341
  const payload = {
@@ -350,7 +381,7 @@ async function logSyncAtris() {
350
381
  updated_at: updatedAt,
351
382
  hash: finalHash,
352
383
  };
353
- saveLogSyncState(finalState);
384
+ saveLogSyncStateSafely(finalState);
354
385
  }
355
386
 
356
387
  module.exports = { logSyncAtris };
@@ -0,0 +1,290 @@
1
+ 'use strict';
2
+
3
+ // `atris loop` is the single front door to the self-improvement loop.
4
+ //
5
+ // Before this, "start a loop" meant guessing between six commands: run,
6
+ // autopilot, mission, improve, pulse, and the old wiki `loop`. This collapses
7
+ // the ENTRY to one plain-English verb and delegates to the engines that already
8
+ // exist. It does not add a seventh engine.
9
+ //
10
+ // atris loop home: status + the next moves
11
+ // atris loop start run it now, here (local) -> run.js
12
+ // atris loop start --once one cycle, then stop -> run.js
13
+ // atris loop start --overnight durable heartbeat (~15m) -> pulse.js
14
+ // atris loop status liveness, last tick, reward -> pulse.js
15
+ // atris loop stop remove the durable heartbeat -> pulse.js
16
+ // atris loop wiki wiki upkeep (the old `loop`) -> loop.js
17
+ //
18
+ // The loop reads ROADMAP.md for what to pursue.
19
+
20
+ function isFlag(arg) {
21
+ return typeof arg === 'string' && arg.startsWith('-');
22
+ }
23
+
24
+ // Pure router: decide what `atris loop ...` means without executing anything.
25
+ // Kept side-effect-free so it is trivially testable.
26
+ function routeLoop(argv = []) {
27
+ const positional = argv.filter((a) => !isFlag(a));
28
+ const sub = (positional[0] || '').toLowerCase();
29
+ const cloud = argv.includes('--cloud') || argv.includes('--overnight');
30
+ const once = argv.includes('--once');
31
+
32
+ switch (sub) {
33
+ case 'start':
34
+ return { action: cloud ? 'start-overnight' : 'start-local', once };
35
+ case 'status':
36
+ return { action: 'status' };
37
+ case 'stop':
38
+ return { action: 'stop' };
39
+ case 'wiki':
40
+ // Forward the remaining args (e.g. --json, --limit=) to wiki upkeep.
41
+ return { action: 'wiki', rest: argv.filter((a) => a.toLowerCase() !== 'wiki') };
42
+ case 'add': {
43
+ // Everything after `add` (minus flags) is the item text.
44
+ const i = argv.findIndex((a) => a.toLowerCase() === 'add');
45
+ const text = argv.slice(i + 1).filter((a) => !isFlag(a)).join(' ').trim();
46
+ return { action: 'add', text };
47
+ }
48
+ case 'report':
49
+ return { action: 'report' };
50
+ case '': {
51
+ // A start flag with no `start` verb does nothing on its own; nudge.
52
+ const stray = ['--overnight', '--cloud', '--once'].find((f) => argv.includes(f));
53
+ return stray ? { action: 'home', strayStartFlag: stray } : { action: 'home' };
54
+ }
55
+ default:
56
+ return { action: 'home', unknown: sub };
57
+ }
58
+ }
59
+
60
+ function renderLoopHome(route = { action: 'home' }) {
61
+ const lines = [
62
+ '',
63
+ 'atris loop: the self-improvement loop',
64
+ '',
65
+ ' one loop: plan, do, review, verify, commit. it ships one small',
66
+ ' verifiable change at a time, then goes again. it reads ROADMAP.md',
67
+ ' for what to pursue.',
68
+ '',
69
+ ' feed it',
70
+ ' atris loop add "<task>" put a bounded task into the queue',
71
+ '',
72
+ ' start it',
73
+ ' atris loop start run it now, here (local)',
74
+ ' atris loop start --once one cycle, then stop',
75
+ ' atris loop start --overnight install the durable heartbeat (~15m)',
76
+ '',
77
+ ' watch it',
78
+ ' atris loop status liveness, last tick, reward',
79
+ ' atris loop report what the loop has handled and what is next',
80
+ ' atris run logs read each phase (local runs)',
81
+ '',
82
+ ' stop it',
83
+ ' atris loop stop remove the durable heartbeat',
84
+ '',
85
+ ' wiki upkeep is at: atris loop wiki',
86
+ '',
87
+ ];
88
+ if (route && route.unknown) {
89
+ lines.splice(1, 0, ` (unknown: "${route.unknown}". here is the loop:)`);
90
+ }
91
+ if (route && route.strayStartFlag) {
92
+ lines.splice(1, 0, ` (did you mean: atris loop start ${route.strayStartFlag}?)`);
93
+ }
94
+ return lines.join('\n');
95
+ }
96
+
97
+ function printLoopHome(route) {
98
+ console.log(renderLoopHome(route));
99
+ }
100
+
101
+ // Parse the handful of local-run flags `atris loop start` forwards to run.js.
102
+ function startLocalOptions(argv = []) {
103
+ const opts = {
104
+ once: argv.includes('--once'),
105
+ verbose: argv.includes('--verbose') || argv.includes('-v'),
106
+ dryRun: argv.includes('--dry-run'),
107
+ push: !argv.includes('--no-push'),
108
+ };
109
+ const cyclesArg = argv.find((a) => a.startsWith('--cycles='));
110
+ if (cyclesArg) {
111
+ const n = parseInt(cyclesArg.split('=')[1], 10);
112
+ if (!Number.isNaN(n)) opts.maxCycles = n;
113
+ }
114
+ const timeoutArg = argv.find((a) => a.startsWith('--timeout='));
115
+ if (timeoutArg) {
116
+ const n = parseInt(timeoutArg.split('=')[1], 10);
117
+ if (!Number.isNaN(n)) opts.timeout = n * 1000;
118
+ }
119
+ return opts;
120
+ }
121
+
122
+ // Summarize the local plan/do/review run logs (run.js writes these). Pure of
123
+ // console, takes the dir, so it is testable.
124
+ function localRunSummary(dir) {
125
+ const fs = require('fs');
126
+ let files;
127
+ try {
128
+ files = fs.readdirSync(dir).filter((f) => f.endsWith('.md')).sort();
129
+ } catch {
130
+ return { count: 0, latest: null };
131
+ }
132
+ if (!files.length) return { count: 0, latest: null };
133
+ return { count: files.length, latest: files[files.length - 1] };
134
+ }
135
+
136
+ function printLocalRunSummary() {
137
+ try {
138
+ const { getRunLogDir } = require('./run');
139
+ const s = localRunSummary(getRunLogDir());
140
+ if (!s.count) {
141
+ console.log('local runs: none yet. start one with `atris loop start`.');
142
+ return;
143
+ }
144
+ console.log(`local runs: ${s.count} logged, latest ${s.latest}. read them: atris run logs`);
145
+ } catch { /* best-effort: never block status */ }
146
+ }
147
+
148
+ // Combined machine-readable status: the overnight pulse heartbeat and the local
149
+ // runs in one object. Best-effort per engine so a missing one does not fail it.
150
+ function loopStatusJson(root = process.cwd()) {
151
+ const out = { ok: true, action: 'loop_status', pulse: null, local_runs: { count: 0, latest: null } };
152
+ try {
153
+ const lp = require('../lib/pulse');
154
+ const { cronInstalled } = require('./pulse');
155
+ const summary = lp.summarizePulse(lp.readPulseReceipts(root));
156
+ out.pulse = { cron_installed: cronInstalled(), ...summary };
157
+ } catch { /* pulse optional */ }
158
+ try {
159
+ out.local_runs = localRunSummary(require('./run').getRunLogDir());
160
+ } catch { /* runs optional */ }
161
+ return out;
162
+ }
163
+
164
+ // The evidence that the loop is improving things: ROADMAP items it has handled,
165
+ // what is in flight and queued, and the heartbeat's reward/verify trend. Pure of
166
+ // console so it is testable.
167
+ function loopReport(root = process.cwd()) {
168
+ const status = loopStatusJson(root);
169
+ let roadmap = { open: [], claimed: [], done: [] };
170
+ try {
171
+ roadmap = require('../lib/next-moves').roadmapItemsByState(root);
172
+ } catch { /* roadmap optional */ }
173
+ return { ok: true, action: 'loop_report', roadmap, pulse: status.pulse, local_runs: status.local_runs };
174
+ }
175
+
176
+ function renderLoopReport(rep) {
177
+ const r = rep.roadmap;
178
+ const lines = ['', 'loop report: what the self-improvement loop has done', ''];
179
+ lines.push(` roadmap: ${r.done.length} done, ${r.claimed.length} in flight, ${r.open.length} queued`);
180
+ if (r.done.length) {
181
+ lines.push('');
182
+ lines.push(' handled:');
183
+ r.done.slice(0, 6).forEach((t) => lines.push(` [x] ${t}`));
184
+ if (r.done.length > 6) lines.push(` ... and ${r.done.length - 6} more`);
185
+ }
186
+ if (r.claimed.length) {
187
+ lines.push('');
188
+ lines.push(' in flight:');
189
+ r.claimed.slice(0, 4).forEach((t) => lines.push(` [~] ${t}`));
190
+ }
191
+ if (r.open.length) {
192
+ lines.push('');
193
+ lines.push(' next up:');
194
+ r.open.slice(0, 4).forEach((t) => lines.push(` [ ] ${t}`));
195
+ }
196
+ if (rep.pulse) {
197
+ lines.push('');
198
+ lines.push(` heartbeat: ${rep.pulse.total_ticks} ticks, reward ${rep.pulse.reward_sum}, verify ${rep.pulse.verify_pass}/${rep.pulse.verify_fail}`);
199
+ }
200
+ lines.push(` local runs: ${rep.local_runs.count}`);
201
+ lines.push('');
202
+ return lines.join('\n');
203
+ }
204
+
205
+ // Executor. Returns a Promise resolving to an exit code (0 = ok).
206
+ function loopFront(argv = []) {
207
+ const route = routeLoop(argv);
208
+ const jsonFlag = argv.includes('--json') ? ['--json'] : [];
209
+
210
+ switch (route.action) {
211
+ case 'home':
212
+ printLoopHome(route);
213
+ return Promise.resolve(0);
214
+
215
+ case 'wiki':
216
+ return Promise.resolve(require('./loop').loopAtris(route.rest)).then(() => 0);
217
+
218
+ case 'report': {
219
+ const rep = loopReport(process.cwd());
220
+ console.log(jsonFlag.length ? JSON.stringify(rep, null, 2) : renderLoopReport(rep));
221
+ return Promise.resolve(0);
222
+ }
223
+
224
+ case 'add': {
225
+ if (!route.text) {
226
+ console.log('usage: atris loop add "<one bounded task the loop should pursue>"');
227
+ return Promise.resolve(0);
228
+ }
229
+ const res = require('../lib/next-moves').addRoadmapItem(process.cwd(), route.text);
230
+ if (res.added) {
231
+ console.log(`added to the loop: ${res.title}`);
232
+ console.log('see it: atris moves | run it: atris loop start');
233
+ } else {
234
+ console.log(`not added (${res.reason}${res.title ? `: ${res.title}` : ''})`);
235
+ }
236
+ return Promise.resolve(0);
237
+ }
238
+
239
+ case 'status': {
240
+ if (jsonFlag.length) {
241
+ // One machine-readable object covering BOTH engines (overnight pulse
242
+ // heartbeat + local runs), for headless agents and web status.
243
+ const out = loopStatusJson(process.cwd());
244
+ console.log(JSON.stringify(out, null, 2));
245
+ return Promise.resolve(out.ok === false ? 1 : 0);
246
+ }
247
+ return Promise.resolve(require('./pulse').pulseCommand(['status']))
248
+ .then((res) => {
249
+ // Pulse covers the overnight heartbeat; also surface local runs so
250
+ // `atris loop status` reflects both engines, not just pulse.
251
+ printLocalRunSummary();
252
+ return res && res.ok === false ? 1 : 0;
253
+ });
254
+ }
255
+
256
+ case 'stop':
257
+ return Promise.resolve(require('./pulse').pulseCommand(['uninstall', ...jsonFlag]))
258
+ .then((res) => (res && res.ok === false ? 1 : 0));
259
+
260
+ case 'start-overnight': {
261
+ // Durable heartbeat via the pulse OS-cron installer. Pass through any
262
+ // pulse install flags the operator added (e.g. --cadence).
263
+ const passthrough = argv.filter((a) => a !== 'start' && a !== '--overnight' && a !== '--cloud');
264
+ return Promise.resolve(require('./pulse').pulseCommand(['install', ...passthrough]))
265
+ .then((res) => {
266
+ const failed = res && res.ok === false;
267
+ // Keep the front door consistent: the stop verb is `atris loop stop`,
268
+ // not the underlying pulse command.
269
+ if (!failed && !argv.includes('--json')) console.log('to stop: atris loop stop');
270
+ return failed ? 1 : 0;
271
+ });
272
+ }
273
+
274
+ case 'start-local':
275
+ default:
276
+ return Promise.resolve(require('./run').runAtris(startLocalOptions(argv))).then(() => 0);
277
+ }
278
+ }
279
+
280
+ module.exports = {
281
+ routeLoop,
282
+ renderLoopHome,
283
+ printLoopHome,
284
+ startLocalOptions,
285
+ localRunSummary,
286
+ loopStatusJson,
287
+ loopReport,
288
+ renderLoopReport,
289
+ loopFront,
290
+ };
@@ -946,6 +946,92 @@ function readTaskProjectionEvidence(name) {
946
946
  };
947
947
  }
948
948
 
949
+ function taskGoalId(task) {
950
+ return task?.metadata?.goal_id
951
+ || task?.metadata?.goalId
952
+ || task?.goal_id
953
+ || task?.goalId
954
+ || task?.atrisContext?.goalId
955
+ || '';
956
+ }
957
+
958
+ function missionTaskStatusRank(task) {
959
+ const status = lowerCompact(task?.status || task?.state || '');
960
+ if (['claimed', 'doing', 'in_progress', 'in-progress', 'working'].includes(status)) return 0;
961
+ if (['open', 'todo', 'plan', 'planned'].includes(status)) return 1;
962
+ if (['review', 'ready'].includes(status)) return 2;
963
+ if (status === 'blocked') return 3;
964
+ return 4;
965
+ }
966
+
967
+ function missionTaskUpdatedAt(task) {
968
+ const numeric = Number(task?.updated_at || task?.updatedAt || task?.created_at || task?.createdAt || 0);
969
+ if (Number.isFinite(numeric) && numeric > 0) return numeric;
970
+ const parsed = Date.parse(task?.updated_at || task?.updatedAt || task?.created_at || task?.createdAt || '');
971
+ return Number.isFinite(parsed) ? parsed : 0;
972
+ }
973
+
974
+ function taskOwnerLabel(task) {
975
+ return task?.claimed_by
976
+ || task?.claimedBy
977
+ || task?.assigned_to
978
+ || task?.assignedTo
979
+ || task?.metadata?.assigned_to
980
+ || task?.metadata?.owner
981
+ || task?.owner
982
+ || null;
983
+ }
984
+
985
+ function missionTaskContextForMember(name, missionId) {
986
+ const wanted = String(missionId || '').trim();
987
+ if (!wanted) return null;
988
+ const tasks = taskProjectionRows()
989
+ .filter((task) => String(taskGoalId(task) || '').trim() === wanted)
990
+ .filter((task) => !taskIsClosed(task));
991
+ if (!tasks.length) return null;
992
+ const selected = tasks.slice().sort((a, b) => {
993
+ const byOwner = (taskBelongsToMember(a, name) ? 0 : 1) - (taskBelongsToMember(b, name) ? 0 : 1);
994
+ if (byOwner) return byOwner;
995
+ const byStatus = missionTaskStatusRank(a) - missionTaskStatusRank(b);
996
+ if (byStatus) return byStatus;
997
+ const byUpdated = missionTaskUpdatedAt(a) - missionTaskUpdatedAt(b);
998
+ if (byUpdated) return byUpdated;
999
+ return String(a.title || '').localeCompare(String(b.title || ''));
1000
+ })[0];
1001
+ const ref = taskRef(selected);
1002
+ return {
1003
+ ref,
1004
+ title: compactSentence(selected.title || selected.summary || ref || 'mission task', 140),
1005
+ status: selected.status || selected.state || null,
1006
+ owner: taskOwnerLabel(selected),
1007
+ goal_id: wanted,
1008
+ command: `atris task current-step --goal-id ${wanted} --as ${name} --proof "<proof>" --json`,
1009
+ };
1010
+ }
1011
+
1012
+ function nativeGoalFromMissionTask(name, goal, runtime, taskContext) {
1013
+ const missionId = runtime?.id || goal?.mission_id || null;
1014
+ const objective = taskContext
1015
+ ? `${name}: complete ${taskContext.ref} - ${taskContext.title}`
1016
+ : `${name}: ${goal.title}`;
1017
+ return {
1018
+ schema: 'atris.native_goal.v1',
1019
+ owner: name,
1020
+ runner: 'coding-agent',
1021
+ objective,
1022
+ slash_goal: objective,
1023
+ member_goal_id: goal.id,
1024
+ mission_id: missionId,
1025
+ task: taskContext || null,
1026
+ acceptance: taskContext ? [
1027
+ `Move ${taskContext.ref} to Review with a human-readable Result receipt.`,
1028
+ 'Record the verifier/proof used to check the work.',
1029
+ 'Stop only when the task is in Review, blocked with a human ask, or accepted by a human.',
1030
+ ] : goal.acceptance,
1031
+ next_command: taskContext?.command || `atris member tick ${name} --goal ${goal.id}`,
1032
+ };
1033
+ }
1034
+
949
1035
  function listThreadJsonFiles(root) {
950
1036
  if (!root || !fs.existsSync(root)) return [];
951
1037
  const out = [];
@@ -3099,6 +3185,9 @@ skills: []
3099
3185
 
3100
3186
  permissions:
3101
3187
  can-read: true
3188
+ can-execute: true
3189
+ can-approve: false
3190
+ can-accept-task: false
3102
3191
  approval-required: []
3103
3192
 
3104
3193
  tools: []
@@ -3116,6 +3205,29 @@ tools: []
3116
3205
  2. Step two
3117
3206
  3. Step three
3118
3207
 
3208
+ ## Cadence
3209
+
3210
+ - Wake cadence: define when this member may run unattended.
3211
+ - Lease: define the maximum time one tick may hold a task or worktree.
3212
+ - Stop condition: define the state that pauses the loop.
3213
+
3214
+ ## Ownership Contract
3215
+
3216
+ - Own tasks by function or feature, never by execution engine.
3217
+ - If no existing member fits, create a member-creation task before assigning broad work.
3218
+ - Put coding agent models like Codex and Claude in the executed_by section.
3219
+
3220
+ ## Proof Standard
3221
+
3222
+ - Move proof-backed work to Review with verifier output, receipt path, or concrete artifact proof.
3223
+ - Never run human accept or claim AgentXP without human approval.
3224
+
3225
+ ## Cleanup Contract
3226
+
3227
+ - Use isolated worktrees for parallel work.
3228
+ - Ship or archive worktrees before the lease ends.
3229
+ - Leave task notes another member can resume without chat context.
3230
+
3119
3231
  ## Rules
3120
3232
 
3121
3233
  1. Rule one
@@ -3693,6 +3805,10 @@ function memberGoalFromMission(name, ...args) {
3693
3805
  goal.now_file = fs.existsSync(path.join(paths.memberDir, 'now.md')) ? path.relative(process.cwd(), path.join(paths.memberDir, 'now.md')) : null;
3694
3806
  goal.mission_id = runtime.id || goal.mission_id || null;
3695
3807
  goal.mission_north_star = purpose.northStar;
3808
+ const taskContext = missionTaskContextForMember(name, goal.mission_id);
3809
+ const nativeGoal = nativeGoalFromMissionTask(name, goal, runtime, taskContext);
3810
+ goal.task_context = taskContext;
3811
+ goal.native_goal = nativeGoal;
3696
3812
  goal.history = Array.isArray(goal.history) ? goal.history : [];
3697
3813
  const historyEntry = {
3698
3814
  at: stampIso(),
@@ -3718,7 +3834,9 @@ function memberGoalFromMission(name, ...args) {
3718
3834
  goal: goal.title,
3719
3835
  north_star: purpose.northStar,
3720
3836
  runtime_mission: runtime.id || '',
3721
- next: `atris member tick ${name} --goal ${goal.id}`,
3837
+ current_task: taskContext?.ref || '',
3838
+ native_goal: nativeGoal.objective,
3839
+ next: nativeGoal.next_command,
3722
3840
  });
3723
3841
  printJsonOrText(
3724
3842
  {
@@ -3732,15 +3850,18 @@ function memberGoalFromMission(name, ...args) {
3732
3850
  runtime_status: runtime.status || null,
3733
3851
  runtime_next: runtime.next || null,
3734
3852
  },
3853
+ task: taskContext,
3854
+ native_goal: nativeGoal,
3735
3855
  goals_path: paths.goalsJson,
3736
3856
  goals_md_path: paths.goalsMd,
3737
3857
  log_path: logPath,
3738
- next_command: `atris member tick ${name} --goal ${goal.id}`,
3858
+ next_command: nativeGoal.next_command,
3739
3859
  },
3740
3860
  [
3741
3861
  `${existing ? 'Reused' : 'Created'} mission-derived goal for ${name}: ${goal.title}`,
3742
3862
  `Mission: ${purpose.northStar}`,
3743
- `Next: atris member tick ${name} --goal ${goal.id}`,
3863
+ `Native goal: ${nativeGoal.slash_goal}`,
3864
+ `Next: ${nativeGoal.next_command}`,
3744
3865
  ],
3745
3866
  asJson,
3746
3867
  );