atris 3.30.12 → 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 (58) hide show
  1. package/AGENTS.md +16 -3
  2. package/FOR_AGENTS.md +81 -0
  3. package/README.md +3 -1
  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 +95 -3
  11. package/bin/atris.js +126 -153
  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 +3330 -290
  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/next-moves.js +212 -6
  40. package/lib/pulse.js +74 -1
  41. package/lib/runner-command.js +20 -8
  42. package/lib/runs-prune.js +242 -0
  43. package/lib/task-proof.js +1 -1
  44. package/package.json +3 -3
  45. package/decks/atris-seed-pitch-v3.json +0 -118
  46. package/decks/atris-seed-pitch-v4-skeleton.json +0 -106
  47. package/decks/atris-seed-pitch-v5.json +0 -109
  48. package/decks/atris-seed-pitch-v6.json +0 -137
  49. package/decks/atris-seed-pitch-v7.json +0 -133
  50. package/decks/mark-pincus-narrative.json +0 -102
  51. package/decks/mark-pincus-sourcery.json +0 -94
  52. package/decks/yash-applied-compute-detailed.json +0 -150
  53. package/decks/yash-applied-compute-generalist.json +0 -82
  54. package/decks/yash-applied-compute-narrative.json +0 -54
  55. package/lib/ax-chat-input.js +0 -164
  56. package/lib/ax-goal.js +0 -307
  57. package/lib/ax-prefs.js +0 -63
  58. package/lib/ax-shimmer.js +0 -63
@@ -0,0 +1,846 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('crypto');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ const STOP_WORDS = new Set([
8
+ 'a',
9
+ 'able',
10
+ 'about',
11
+ 'after',
12
+ 'again',
13
+ 'all',
14
+ 'also',
15
+ 'am',
16
+ 'an',
17
+ 'and',
18
+ 'are',
19
+ 'as',
20
+ 'at',
21
+ 'atris',
22
+ 'be',
23
+ 'become',
24
+ 'because',
25
+ 'but',
26
+ 'by',
27
+ 'can',
28
+ 'cant',
29
+ 'could',
30
+ 'day',
31
+ 'days',
32
+ 'do',
33
+ 'done',
34
+ 'dont',
35
+ 'everything',
36
+ 'for',
37
+ 'from',
38
+ 'get',
39
+ 'got',
40
+ 'had',
41
+ 'has',
42
+ 'have',
43
+ 'how',
44
+ 'i',
45
+ 'if',
46
+ 'in',
47
+ 'into',
48
+ 'is',
49
+ 'it',
50
+ 'its',
51
+ 'just',
52
+ 'led',
53
+ 'like',
54
+ 'make',
55
+ 'me',
56
+ 'mission',
57
+ 'my',
58
+ 'need',
59
+ 'not',
60
+ 'now',
61
+ 'of',
62
+ 'on',
63
+ 'one',
64
+ 'or',
65
+ 'our',
66
+ 'out',
67
+ 'place',
68
+ 'reference',
69
+ 'room',
70
+ 'select',
71
+ 'selected',
72
+ 'should',
73
+ 'so',
74
+ 'suggest',
75
+ 'suggested',
76
+ 'suggests',
77
+ 'that',
78
+ 'the',
79
+ 'this',
80
+ 'to',
81
+ 'too',
82
+ 'turn',
83
+ 'up',
84
+ 'us',
85
+ 'we',
86
+ 'what',
87
+ 'when',
88
+ 'with',
89
+ 'without',
90
+ 'want',
91
+ 'wants',
92
+ 'why',
93
+ 'where',
94
+ ]);
95
+
96
+ const PRIORITY_TERMS = new Map([
97
+ ['member', 122],
98
+ ['members', 122],
99
+ ['log', 120],
100
+ ['logs', 120],
101
+ ['context', 118],
102
+ ['proactive', 116],
103
+ ['thinking', 115],
104
+ ['approval', 110],
105
+ ['approve', 108],
106
+ ['operator', 106],
107
+ ['next', 105],
108
+ ['clarify', 104],
109
+ ['clarifies', 104],
110
+ ['proof', 100],
111
+ ['runway', 100],
112
+ ['cash', 95],
113
+ ['revenue', 90],
114
+ ['goals', 88],
115
+ ['goal', 88],
116
+ ['bounded', 86],
117
+ ['product', 85],
118
+ ['growth', 80],
119
+ ['wedge', 78],
120
+ ['proof', 75],
121
+ ['receipt', 72],
122
+ ['customer', 70],
123
+ ['buyer', 68],
124
+ ['launch', 66],
125
+ ['activation', 64],
126
+ ['share', 62],
127
+ ['invite', 60],
128
+ ['agent', 58],
129
+ ['agents', 58],
130
+ ]);
131
+
132
+ const PROCESS_NAMING_TERMS = new Set([
133
+ 'approval',
134
+ 'approve',
135
+ 'clarify',
136
+ 'context',
137
+ 'goal',
138
+ 'log',
139
+ 'member',
140
+ 'next',
141
+ 'operator',
142
+ 'receipt',
143
+ 'thinking',
144
+ ]);
145
+
146
+ function normalizeInput(input) {
147
+ return String(input || '').replace(/\s+/g, ' ').trim();
148
+ }
149
+
150
+ function slugify(value) {
151
+ return String(value || 'mission-room')
152
+ .toLowerCase()
153
+ .replace(/[^a-z0-9]+/g, '-')
154
+ .replace(/^-+|-+$/g, '')
155
+ .slice(0, 64) || 'mission-room';
156
+ }
157
+
158
+ function titleCase(value) {
159
+ return String(value || '')
160
+ .split(/\s+/)
161
+ .filter(Boolean)
162
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
163
+ .join(' ');
164
+ }
165
+
166
+ function keywordTerms(input) {
167
+ const counts = new Map();
168
+ const firstIndex = new Map();
169
+ for (const match of normalizeInput(input).toLowerCase().matchAll(/[a-z0-9][a-z0-9'-]{2,}/g)) {
170
+ const parts = match[0].split(/[-']/).filter(Boolean);
171
+ for (const part of parts) {
172
+ let word = part.replace(/[^a-z0-9]/g, '');
173
+ if (word === 'approve' || word === 'approved' || word === 'approves') word = 'approval';
174
+ if (word === 'goals') word = 'goal';
175
+ if (word === 'members') word = 'member';
176
+ if (word === 'logs') word = 'log';
177
+ if (word === 'clarifies' || word === 'clarified') word = 'clarify';
178
+ if (!word || STOP_WORDS.has(word)) continue;
179
+ if (!firstIndex.has(word)) firstIndex.set(word, match.index);
180
+ counts.set(word, (counts.get(word) || 0) + 1);
181
+ }
182
+ }
183
+ return Array.from(counts.entries())
184
+ .sort((a, b) => {
185
+ const scoreA = (PRIORITY_TERMS.get(a[0]) || 0) + (a[1] * 10);
186
+ const scoreB = (PRIORITY_TERMS.get(b[0]) || 0) + (b[1] * 10);
187
+ return scoreB - scoreA || firstIndex.get(a[0]) - firstIndex.get(b[0]) || a[0].localeCompare(b[0]);
188
+ })
189
+ .map(([word]) => word)
190
+ .slice(0, 4);
191
+ }
192
+
193
+ function sentenceExcerpt(input, max = 220) {
194
+ const normalized = normalizeInput(input);
195
+ if (normalized.length <= max) return normalized;
196
+ return `${normalized.slice(0, max - 3).replace(/\s+\S*$/, '')}...`;
197
+ }
198
+
199
+ function hasWord(input, words) {
200
+ return words.some((word) => new RegExp(`\\b${word}\\b`, 'i').test(input));
201
+ }
202
+
203
+ function missionName(input) {
204
+ const normalized = normalizeInput(input);
205
+ const lower = normalized.toLowerCase();
206
+ const asksForNextMission = /\b(decide|pick|choose|select|start|find)\b.{0,120}\bnext\b.{0,80}\bmission\b/i.test(lower)
207
+ || /\bnext\s+useful\s+mission\b/i.test(lower);
208
+ const hasRunway = hasWord(lower, ['runway']);
209
+ const hasProductLed = /\bproduct[-\s]+led\b/i.test(lower)
210
+ || (hasWord(lower, ['product']) && hasWord(lower, ['growth', 'wedge', 'adoption']));
211
+ const hasCashSignal = hasWord(lower, ['cash', 'revenue', 'payment', 'paid', 'buyer', 'customer', 'adoption']);
212
+ const hasProofSignal = hasWord(lower, ['proof', 'demo', 'artifact', 'validate', 'validated']);
213
+ const hasMessyInputSignal = /\bmessy\s+(input|intent|ask|thought|need)\b/i.test(lower);
214
+ const hasGoalChainSignal = /\b(right\s+mission\s+input|visible\s+goal|next\s+goal|finish[-\s]+line|goal\s+chain)\b/i.test(lower);
215
+
216
+ if (asksForNextMission) return 'Decide Next Useful Mission Room';
217
+ if (hasMessyInputSignal && hasGoalChainSignal) return 'Messy Input Goal Chain Mission Room';
218
+ if (hasRunway && hasProductLed && (hasCashSignal || hasProofSignal)) return 'Ship Product-Led Cash Proof Mission Room';
219
+ if (hasRunway && hasProductLed) return 'Ship Product-Led Runway Proof Mission Room';
220
+ if ((hasRunway || hasCashSignal) && hasProofSignal) return 'Ship Cash Proof Mission Room';
221
+ if (/\bwarm\s+buyer\b/i.test(lower) && hasWord(lower, ['loop', 'loops', 'waste'])) return 'Replace Warm Buyer Loops Mission Room';
222
+
223
+ const rawTerms = keywordTerms(input);
224
+ if (rawTerms.includes('thinking') && rawTerms.includes('approval')) return 'Thinking Approval Mission Room';
225
+ const terms = rawTerms.filter((term) => !PROCESS_NAMING_TERMS.has(term));
226
+ if (!terms.length) return 'Clarity Mission Room';
227
+ return `${titleCase(terms.slice(0, 3).join(' '))} Mission Room`;
228
+ }
229
+
230
+ function toPosixPath(value) {
231
+ return String(value || '').split(path.sep).join('/');
232
+ }
233
+
234
+ function relativePath(root, absolutePath) {
235
+ return toPosixPath(path.relative(root, absolutePath));
236
+ }
237
+
238
+ function readFileExcerpt(filePath, max = 180) {
239
+ if (!filePath || !fs.existsSync(filePath)) return '';
240
+ let body = '';
241
+ try {
242
+ body = fs.readFileSync(filePath, 'utf8');
243
+ } catch (_) {
244
+ return '';
245
+ }
246
+ const line = body
247
+ .split(/\r?\n/)
248
+ .map((row) => row.trim())
249
+ .filter(Boolean)
250
+ .filter((row) => !row.startsWith('<!--'))
251
+ .find((row) => row.startsWith('#') || row.startsWith('- ') || row.length > 20)
252
+ || '';
253
+ return sentenceExcerpt(line.replace(/^#+\s*/, '').replace(/^-\s*/, ''), max);
254
+ }
255
+
256
+ function fileSignal(root, filePath, label) {
257
+ if (!filePath || !fs.existsSync(filePath)) return null;
258
+ return {
259
+ label,
260
+ path: relativePath(root, filePath),
261
+ excerpt: readFileExcerpt(filePath),
262
+ };
263
+ }
264
+
265
+ function listTeamMembers(root) {
266
+ const teamDir = path.join(root, 'atris', 'team');
267
+ try {
268
+ return fs.readdirSync(teamDir, { withFileTypes: true })
269
+ .filter((entry) => entry.isDirectory())
270
+ .map((entry) => entry.name)
271
+ .filter((name) => name !== '_template')
272
+ .filter((name) => fs.existsSync(path.join(teamDir, name, 'MEMBER.md')))
273
+ .sort();
274
+ } catch (_) {
275
+ return [];
276
+ }
277
+ }
278
+
279
+ function listMarkdownFiles(dir, limit = 200) {
280
+ const found = [];
281
+ function visit(current) {
282
+ let entries = [];
283
+ try {
284
+ entries = fs.readdirSync(current, { withFileTypes: true });
285
+ } catch (_) {
286
+ return;
287
+ }
288
+ for (const entry of entries) {
289
+ const fullPath = path.join(current, entry.name);
290
+ if (entry.isDirectory()) {
291
+ visit(fullPath);
292
+ continue;
293
+ }
294
+ if (!entry.isFile() || !entry.name.endsWith('.md')) continue;
295
+ let stat = null;
296
+ try {
297
+ stat = fs.statSync(fullPath);
298
+ } catch (_) {
299
+ continue;
300
+ }
301
+ found.push({ path: fullPath, mtimeMs: stat.mtimeMs });
302
+ }
303
+ }
304
+ visit(dir);
305
+ return found
306
+ .sort((a, b) => b.mtimeMs - a.mtimeMs || a.path.localeCompare(b.path))
307
+ .slice(0, limit)
308
+ .map((row) => row.path);
309
+ }
310
+
311
+ function latestMarkdownFile(dir) {
312
+ return listMarkdownFiles(dir, 1)[0] || null;
313
+ }
314
+
315
+ function buildClarifyingQuestions() {
316
+ return [
317
+ {
318
+ id: 'done_standard',
319
+ question: 'What would make this mission undeniably done for you?',
320
+ why: 'Defines the approval threshold before Atris starts running goals.',
321
+ },
322
+ {
323
+ id: 'approval_boundary',
324
+ question: 'Which decisions can Atris make alone, and which ones must wait for your approval?',
325
+ why: 'Keeps your role at judgment, priority, and final accept instead of task execution.',
326
+ },
327
+ {
328
+ id: 'first_trusted_proof',
329
+ question: 'What first proof would you trust: changed file, test, screenshot, customer signal, payment, or receipt?',
330
+ why: 'Prevents goal loops from becoming motion without evidence.',
331
+ },
332
+ ];
333
+ }
334
+
335
+ function buildApprovalPacket(name, summary, owner, options = {}) {
336
+ const verifier = options.verifier || '<cmd>';
337
+ const approvedCommand = `atris mission start "${name}" --owner ${owner} --runner codex_goal --lane code --verify "${verifier}" --xp-task`;
338
+ const trustedRun = options.trustedRun === true;
339
+ return {
340
+ status: trustedRun ? 'trusted_auto_run' : 'awaiting_operator_approval',
341
+ approve_question: trustedRun
342
+ ? `Run ${name} after this preview?`
343
+ : `Approve ${name} as the mission?`,
344
+ operator_role: 'Approve taste, judgment, priority, and final accept; Atris owns bounded execution and proof receipts.',
345
+ proposed_mission: name,
346
+ proposed_outcome: `A proof-backed mission that resolves: ${summary}`,
347
+ approval_standard: [
348
+ 'The mission is easy to explain in one sentence.',
349
+ 'The first proof step is inspectable by the operator.',
350
+ 'Every next goal must produce a receipt, ask for approval, or stop.',
351
+ ],
352
+ decision_options: ['approve', 'revise', 'stop'],
353
+ approved_next_command: approvedCommand,
354
+ };
355
+ }
356
+
357
+ function buildGoalChain(name, options = {}) {
358
+ if (options.trustedRun === true) {
359
+ return {
360
+ mode: 'trusted_run',
361
+ loop: 'clarify -> plan preview -> set one goal -> prove -> receipt -> next goal or stop',
362
+ first_goal: `Run ${name} until one inspectable proof receipt exists.`,
363
+ next_goal_policy: 'Set exactly one next goal only after proof; stop when no concrete useful move is available.',
364
+ stop_conditions: [
365
+ 'the useful-work selector finds no concrete bounded move',
366
+ 'verifier fails and cannot be repaired inside the current goal',
367
+ 'the next step requires taste, priority, budget, or customer judgment',
368
+ 'the mission reaches an inspectable delivery receipt',
369
+ ],
370
+ };
371
+ }
372
+ return {
373
+ mode: 'approval_gated',
374
+ loop: 'clarify -> approve packet -> set one goal -> prove -> receipt -> approve/revise/next goal',
375
+ first_goal: `Prove ${name} with the smallest inspectable receipt.`,
376
+ next_goal_policy: 'Set exactly one next goal after each receipt; stop when proof is strong enough for operator approval or a real blocker appears.',
377
+ stop_conditions: [
378
+ 'operator rejects the approval packet',
379
+ 'verifier fails and cannot be repaired inside the current goal',
380
+ 'the next step requires taste, priority, budget, or customer judgment',
381
+ 'the mission reaches an inspectable delivery receipt',
382
+ ],
383
+ };
384
+ }
385
+
386
+ function thinkingPath(root = process.cwd()) {
387
+ return {
388
+ relativePath: path.join('atris', 'thinking.md'),
389
+ absolutePath: path.join(root, 'atris', 'thinking.md'),
390
+ };
391
+ }
392
+
393
+ function defaultThinkingBody() {
394
+ return [
395
+ '# thinking.md',
396
+ '',
397
+ '<!-- Human-editable. Mission Room updates this with how the operator thinks, decides, approves, rejects, prioritizes, and explains. -->',
398
+ '',
399
+ '## What This File Is',
400
+ '',
401
+ 'Team logs say what happened.',
402
+ '',
403
+ 'This file says how Keshav thinks.',
404
+ '',
405
+ '## How Keshav Thinks',
406
+ '',
407
+ '- Starts from messy truth, then wants it clarified into a mission he can approve or reject.',
408
+ '- Values plain English, no jargon, and proof over motion.',
409
+ '- Wants Atris to raise his role from doing tasks to judging taste, priority, and final accept.',
410
+ '',
411
+ '## Approval Rules',
412
+ '',
413
+ '- Atris can clarify, propose, run bounded proof steps, and write receipts.',
414
+ '- Keshav keeps taste, judgment, priority, budget, customer calls, and final accept.',
415
+ '- Real-world side effects wait for approval unless the mission explicitly grants permission.',
416
+ '',
417
+ '## Proof Standards',
418
+ '',
419
+ '- Every goal needs a receipt, verifier, visible artifact, or stop reason.',
420
+ '- A good receipt is easy to inspect and easy to explain.',
421
+ '- A goal chain stops when proof is strong enough for approval or a real blocker appears.',
422
+ '',
423
+ '## Mission Room Signals',
424
+ '',
425
+ '<!-- ATRIS_MISSION_ROOM_SIGNALS:START -->',
426
+ '<!-- ATRIS_MISSION_ROOM_SIGNALS:END -->',
427
+ '',
428
+ ].join('\n');
429
+ }
430
+
431
+ function upsertSignal(body, line) {
432
+ const start = '<!-- ATRIS_MISSION_ROOM_SIGNALS:START -->';
433
+ const end = '<!-- ATRIS_MISSION_ROOM_SIGNALS:END -->';
434
+ if (!body.includes(start) || !body.includes(end)) {
435
+ const base = body.trimEnd();
436
+ return `${base}\n\n## Mission Room Signals\n\n${start}\n${line}\n${end}\n`;
437
+ }
438
+ if (body.includes(line)) return body;
439
+ return body.replace(end, `${line}\n${end}`);
440
+ }
441
+
442
+ function writeThinkingMemory(room, options = {}) {
443
+ const root = options.root || process.cwd();
444
+ const at = options.at || new Date();
445
+ const { relativePath, absolutePath } = thinkingPath(root);
446
+ fs.mkdirSync(path.dirname(absolutePath), { recursive: true });
447
+
448
+ const source = sentenceExcerpt(room.truth_snapshot || room.source?.excerpt || room.name, 180);
449
+ const signal = `- ${at.toISOString()} - ${room.name}: ${source}`;
450
+ const before = fs.existsSync(absolutePath)
451
+ ? fs.readFileSync(absolutePath, 'utf8')
452
+ : defaultThinkingBody();
453
+ const after = upsertSignal(before, signal);
454
+ fs.writeFileSync(absolutePath, after.endsWith('\n') ? after : `${after}\n`, 'utf8');
455
+
456
+ return {
457
+ path: relativePath,
458
+ updated: true,
459
+ latest_signal: signal,
460
+ purpose: 'how Keshav thinks, decides, approves, rejects, prioritizes, and explains.',
461
+ };
462
+ }
463
+
464
+ function collectMissionRoomContext(input, options = {}) {
465
+ const root = options.root || process.cwd();
466
+ const owner = options.owner || 'mission-lead';
467
+ const ownerResolution = options.ownerResolution || null;
468
+ const members = listTeamMembers(root);
469
+ const memberRoot = path.join(root, 'atris', 'team', owner);
470
+ const memberFile = path.join(memberRoot, 'MEMBER.md');
471
+ const memberExists = fs.existsSync(memberFile);
472
+ const latestMemberLog = memberExists ? latestMarkdownFile(path.join(memberRoot, 'logs')) : null;
473
+ const latestWorkspaceLog = latestMarkdownFile(path.join(root, 'atris', 'logs'));
474
+ const { relativePath: thinkingRelative, absolutePath: thinkingAbsolute } = thinkingPath(root);
475
+
476
+ const memberFiles = [
477
+ fileSignal(root, memberFile, 'member'),
478
+ fileSignal(root, path.join(memberRoot, 'MISSION.md'), 'mission'),
479
+ fileSignal(root, path.join(memberRoot, 'now.md'), 'now'),
480
+ fileSignal(root, latestMemberLog, 'member_log'),
481
+ ].filter(Boolean);
482
+
483
+ const memberStatus = memberExists
484
+ ? 'member_selected'
485
+ : members.length
486
+ ? 'selected_member_missing'
487
+ : 'no_members_task_first';
488
+
489
+ return {
490
+ schema: 'atris.mission_room_context.v1',
491
+ selected_member: owner,
492
+ selection_reason: ownerResolution?.reason || (memberExists ? 'selected_member' : memberStatus),
493
+ proposed_member: ownerResolution?.proposed_member || null,
494
+ available_members: members,
495
+ member_context: {
496
+ status: memberStatus,
497
+ member_exists: memberExists,
498
+ guidance: memberExists
499
+ ? 'Read this member, now.md, mission file, and latest log before starting the mission.'
500
+ : 'No matching MEMBER.md is available; reason from the task first, then create or select the member before autonomous execution.',
501
+ files: memberFiles,
502
+ },
503
+ memory_context: {
504
+ thinking: {
505
+ path: thinkingRelative,
506
+ exists: fs.existsSync(thinkingAbsolute),
507
+ purpose: 'operator judgment, approval rules, proof standards, and recurring mission signals.',
508
+ excerpt: readFileExcerpt(thinkingAbsolute),
509
+ },
510
+ workspace_log: fileSignal(root, latestWorkspaceLog, 'workspace_log'),
511
+ },
512
+ task_first_summary: sentenceExcerpt(input, 180),
513
+ };
514
+ }
515
+
516
+ function contextPaths(context) {
517
+ const paths = [];
518
+ for (const file of context?.member_context?.files || []) {
519
+ if (file?.path) paths.push(file.path);
520
+ }
521
+ const thinking = context?.memory_context?.thinking;
522
+ if (thinking?.path) paths.push(thinking.path);
523
+ const workspaceLog = context?.memory_context?.workspace_log;
524
+ if (workspaceLog?.path) paths.push(workspaceLog.path);
525
+ return Array.from(new Set(paths));
526
+ }
527
+
528
+ function buildProactiveNextMission(name, owner, context, approvalPacket, options = {}) {
529
+ const hasMember = context?.member_context?.member_exists;
530
+ const proposedMember = context?.proposed_member || owner;
531
+ const trustedRun = options.trustedRun === true;
532
+ return {
533
+ status: trustedRun ? 'ready_to_run_after_preview' : 'suggested_after_operator_approval',
534
+ objective: hasMember
535
+ ? `Run ${name} with ${owner} until the first proof receipt is ready.`
536
+ : `Select or create ${proposedMember}, then run ${name} until the first proof receipt is ready.`,
537
+ why_now: 'This is the most direct next mission from the messy input, selected member, logs, and thinking.md.',
538
+ selected_member: owner,
539
+ context_paths: contextPaths(context),
540
+ approval_question: trustedRun
541
+ ? `Start this as the next mission after the preview?`
542
+ : `Start this as the next mission after you approve ${name}?`,
543
+ start_command: approvalPacket.approved_next_command,
544
+ member_setup_command: hasMember ? null : `atris member create ${proposedMember}`,
545
+ stop_rule: 'Stop at one proof receipt, one approval question, or one real blocker.',
546
+ };
547
+ }
548
+
549
+ function memberRouteReason(context) {
550
+ const reason = context?.selection_reason || '';
551
+ if (reason.includes('explicit')) return 'you named this owner';
552
+ if (reason.includes('task_signal')) return 'the task language points at this owner';
553
+ if (reason.includes('exact')) return 'the task names this owner directly';
554
+ if (context?.member_context?.member_exists) return 'this is the best available owner for the task';
555
+ if (context?.available_members?.length) return 'no perfect owner matched, so this is a routing suggestion';
556
+ return 'no members exist yet, so Atris should reason from the task first';
557
+ }
558
+
559
+ function buildMemberRoute(name, owner, context, approvalPacket) {
560
+ const hasMember = context?.member_context?.member_exists;
561
+ const proposedMember = context?.proposed_member || owner;
562
+ const suggestedMember = hasMember ? owner : proposedMember;
563
+ const alternatives = (context?.available_members || [])
564
+ .filter((member) => member !== owner)
565
+ .slice(0, 5);
566
+ return {
567
+ status: hasMember ? 'suggested_member' : 'needs_member_selection',
568
+ suggested_member: suggestedMember,
569
+ editable: true,
570
+ why: memberRouteReason(context),
571
+ approval_prompt: `I think ${suggestedMember} should own ${name}. Approve or change the member?`,
572
+ change_hint: 'Change the route before approval with --owner <member>.',
573
+ alternatives,
574
+ approve_command: approvalPacket.approved_next_command,
575
+ setup_command: hasMember ? null : `atris member create ${suggestedMember}`,
576
+ };
577
+ }
578
+
579
+ function buildTaskPlanPreview(name, summary, owner, context, goalChain, firstProofStep, memberRoute) {
580
+ return {
581
+ schema: 'atris.mission_room_task_plan_preview.v1',
582
+ order: 'task_first',
583
+ task: summary,
584
+ mission: name,
585
+ first_goal: goalChain.first_goal,
586
+ first_proof_step: firstProofStep,
587
+ stop_rule: 'Stop at one proof receipt, one approval question, or one real blocker.',
588
+ member_route: memberRoute,
589
+ context_paths: contextPaths(context),
590
+ preview_then_route: `First understand the task. Then suggest ${owner} as editable routing.`,
591
+ };
592
+ }
593
+
594
+ function buildTimelinePreview(name, summary, owner, goalChain) {
595
+ return {
596
+ schema: 'atris.mission_room_timeline_preview.v1',
597
+ mode: 'human_goal_chain',
598
+ items: [
599
+ {
600
+ order: 1,
601
+ title: 'Messy ask captured',
602
+ did: `Turned "${summary}" into ${name}.`,
603
+ meant: 'The room starts from the operator truth, not a generic task template.',
604
+ },
605
+ {
606
+ order: 2,
607
+ title: 'Goal set',
608
+ did: goalChain.first_goal,
609
+ meant: 'Atris runs one bounded goal before proposing another.',
610
+ },
611
+ {
612
+ order: 3,
613
+ title: 'Goal done',
614
+ did: 'Build the smallest artifact or change, then attach a receipt.',
615
+ meant: 'The operator reviews visible proof instead of internal run noise.',
616
+ },
617
+ {
618
+ order: 4,
619
+ title: 'Next goal set',
620
+ did: `Route the next proof step to ${owner} only after approval or proof.`,
621
+ meant: 'The loop can continue without hiding priority or taste decisions.',
622
+ },
623
+ {
624
+ order: 5,
625
+ title: 'Mission accomplished',
626
+ did: 'Stop when the proof is strong enough to accept, revise, or ship.',
627
+ meant: 'The final handoff is a short story plus links to inspectable proof.',
628
+ },
629
+ ],
630
+ };
631
+ }
632
+
633
+ function buildPendingResultLanding(name, owner, timelinePreview, options = {}) {
634
+ if (options.trustedRun === true) {
635
+ return {
636
+ schema: 'atris.mission_room_result.v1',
637
+ status: 'ready_to_run',
638
+ landing: {
639
+ status: 'ready_to_run',
640
+ changed: `Room ready: ${name} has a preview and can start one bounded goal with ${owner}.`,
641
+ checked: 'Plan preview, member route, timeline, and trusted run policy are prepared; mission state is created only after useful work is selected.',
642
+ proof: null,
643
+ decision: 'Start one bounded goal, produce proof, then continue only if the next move is concrete.',
644
+ timeline_preview: timelinePreview?.items || [],
645
+ },
646
+ };
647
+ }
648
+ return {
649
+ schema: 'atris.mission_room_result.v1',
650
+ status: 'pending_goal_run',
651
+ landing: {
652
+ status: 'pending_goal_run',
653
+ changed: `Room open: ${name} is being clarified before ${owner} runs a goal.`,
654
+ checked: 'Plan preview, member route, timeline, and approval gate are prepared; no mission goal has run yet.',
655
+ proof: null,
656
+ decision: 'Approve to start one bounded goal, revise the room, or stop before execution.',
657
+ timeline_preview: timelinePreview?.items || [],
658
+ },
659
+ };
660
+ }
661
+
662
+ function buildChatZone(name, owner, taskPlanPreview, timelinePreview, approvalPacket, options = {}) {
663
+ const trustedRun = options.trustedRun === true;
664
+ return {
665
+ schema: 'atris.mission_room_chat_zone.v1',
666
+ status: trustedRun ? 'ready_to_run' : 'clarifying',
667
+ current_step: trustedRun ? 'preview_then_execute_one_goal' : 'shape_the_mission_before_execution',
668
+ member: owner,
669
+ plan_preview: {
670
+ mission: name,
671
+ first_goal: taskPlanPreview?.first_goal || `Prove ${name} with the smallest inspectable receipt.`,
672
+ first_proof_step: taskPlanPreview?.first_proof_step,
673
+ stop_rule: taskPlanPreview?.stop_rule,
674
+ },
675
+ timeline_preview: timelinePreview?.items || [],
676
+ approval_gate: {
677
+ status: approvalPacket?.status || 'awaiting_operator_approval',
678
+ decision_options: approvalPacket?.decision_options || ['approve', 'revise', 'stop'],
679
+ approve_question: approvalPacket?.approve_question || `Approve ${name} as the mission?`,
680
+ },
681
+ execution_policy: trustedRun
682
+ ? 'Trusted run: after the preview, start exactly one bounded goal and stop at proof or a real blocker.'
683
+ : 'Do not start a mission goal until the operator approves this room.',
684
+ result_landing_policy: 'Use result.landing only after a bounded goal runs and proof exists.',
685
+ };
686
+ }
687
+
688
+ function buildMissionRoom(input, options = {}) {
689
+ const normalized = normalizeInput(input);
690
+ if (!normalized) {
691
+ const err = new Error('Mission Room needs messy input.');
692
+ err.code = 'MISSION_ROOM_INPUT_REQUIRED';
693
+ throw err;
694
+ }
695
+
696
+ const name = missionName(normalized);
697
+ const owner = options.owner || 'mission-lead';
698
+ const summary = sentenceExcerpt(normalized);
699
+ const hash = crypto.createHash('sha1').update(normalized).digest('hex').slice(0, 12);
700
+ const slug = slugify(name);
701
+ const trustedRun = options.trustedRun === true;
702
+ const clarifyingQuestions = buildClarifyingQuestions();
703
+ const approvalPacket = buildApprovalPacket(name, summary, owner, {
704
+ trustedRun,
705
+ verifier: options.verifier,
706
+ });
707
+ const goalChain = buildGoalChain(name, { trustedRun });
708
+ const timelinePreview = buildTimelinePreview(name, summary, owner, goalChain);
709
+ const firstProofStep = 'Create the smallest artifact or change that proves this mission advanced, then attach a verifier, receipt path, screenshot, or human accept gate.';
710
+ const context = collectMissionRoomContext(normalized, {
711
+ root: options.root || process.cwd(),
712
+ owner,
713
+ ownerResolution: options.ownerResolution,
714
+ });
715
+ const memberRoute = buildMemberRoute(name, owner, context, approvalPacket);
716
+ const taskPlanPreview = buildTaskPlanPreview(name, summary, owner, context, goalChain, firstProofStep, memberRoute);
717
+ const proactiveNextMission = buildProactiveNextMission(name, owner, context, approvalPacket, { trustedRun });
718
+ const result = buildPendingResultLanding(name, owner, timelinePreview, { trustedRun });
719
+ const chatZone = buildChatZone(name, owner, taskPlanPreview, timelinePreview, approvalPacket, { trustedRun });
720
+
721
+ return {
722
+ schema: 'atris.mission_room.v1',
723
+ name,
724
+ owner,
725
+ source: {
726
+ input_hash: hash,
727
+ excerpt: summary,
728
+ },
729
+ truth_snapshot: summary,
730
+ target_outcome: `Move from messy intent to a proof-backed mission: ${summary}`,
731
+ stop_doing: [
732
+ 'Do not widen scope before the first proof step.',
733
+ 'Do not turn this into generic AI agent positioning.',
734
+ 'Do not leave the room without a receipt someone can inspect.',
735
+ ],
736
+ member_context: context.member_context,
737
+ memory_context: context.memory_context,
738
+ context,
739
+ chat_zone: chatZone,
740
+ task_plan_preview: taskPlanPreview,
741
+ member_route: memberRoute,
742
+ clarifying_questions: clarifyingQuestions,
743
+ approval_packet: approvalPacket,
744
+ goal_chain: goalChain,
745
+ timeline_preview: timelinePreview,
746
+ proactive_next_mission: proactiveNextMission,
747
+ result,
748
+ execution_mode: trustedRun ? 'trusted_run' : 'approval_gated',
749
+ trusted_execution: trustedRun ? {
750
+ status: 'ready_to_run',
751
+ selected_target: options.selectedTarget || null,
752
+ policy: 'preview first, start one bounded goal, write proof, then continue only if the next move is concrete',
753
+ } : null,
754
+ first_proof_step: firstProofStep,
755
+ verifier: 'Receipt must include mission name, truth snapshot, target outcome, stop-doing list, first proof step, and share line.',
756
+ share_line: `This mission moved from chaos to proof: ${name}. Inspect the receipt or claim the next step.`,
757
+ next_command: `${trustedRun ? 'After preview' : 'After approval'}: ${approvalPacket.approved_next_command}`,
758
+ receipt_slug: slug,
759
+ };
760
+ }
761
+
762
+ function missionRoomReceiptPath(room, root = process.cwd(), now = new Date()) {
763
+ const stamp = now.toISOString().replace(/[:.]/g, '-');
764
+ return path.join('atris', 'runs', `mission-room-${stamp}-${room.receipt_slug}.json`);
765
+ }
766
+
767
+ function writeMissionRoomReceipt(room, options = {}) {
768
+ const root = options.root || process.cwd();
769
+ const at = options.at || new Date();
770
+ const thinkingMemory = writeThinkingMemory(room, { root, at });
771
+ const thinkingAbsolutePath = path.join(root, thinkingMemory.path);
772
+ const thinkingContext = {
773
+ ...(room.memory_context?.thinking || {}),
774
+ path: thinkingMemory.path,
775
+ exists: true,
776
+ purpose: 'operator judgment, approval rules, proof standards, and recurring mission signals.',
777
+ excerpt: readFileExcerpt(thinkingAbsolutePath),
778
+ };
779
+ const memoryContext = {
780
+ ...(room.memory_context || {}),
781
+ thinking: thinkingContext,
782
+ };
783
+ const context = room.context ? {
784
+ ...room.context,
785
+ memory_context: {
786
+ ...(room.context.memory_context || {}),
787
+ thinking: thinkingContext,
788
+ },
789
+ } : null;
790
+ const roomWithMemory = {
791
+ ...room,
792
+ memory_context: memoryContext,
793
+ ...(context ? { context } : {}),
794
+ thinking_memory: thinkingMemory,
795
+ };
796
+ const relativePath = missionRoomReceiptPath(roomWithMemory, root, at);
797
+ const absolutePath = path.join(root, relativePath);
798
+ const receipt = {
799
+ schema: 'atris.mission_room_receipt.v1',
800
+ at: at.toISOString(),
801
+ product_wedge: 'Chaos -> Mission Room',
802
+ room: roomWithMemory,
803
+ thinking_memory: thinkingMemory,
804
+ receipt_path: relativePath,
805
+ };
806
+
807
+ fs.mkdirSync(path.dirname(absolutePath), { recursive: true });
808
+ fs.writeFileSync(absolutePath, JSON.stringify(receipt, null, 2) + '\n', 'utf8');
809
+ return { receipt, relativePath, absolutePath, room: roomWithMemory };
810
+ }
811
+
812
+ function missionRoomLines(room, receiptPath) {
813
+ const timelineItems = (room.timeline_preview?.items || [])
814
+ .map((item) => ` ${item.order}. ${item.title}: ${item.did} Meaning: ${item.meant}`);
815
+ const trustedRun = room.execution_mode === 'trusted_run';
816
+ return [
817
+ 'Mission Room',
818
+ ` Chat zone: ${room.chat_zone?.status || 'clarifying'} (${trustedRun ? 'preview then run one bounded goal' : 'no goal runs until approve'})`,
819
+ ` Task plan preview: ${room.task_plan_preview?.task || room.truth_snapshot}`,
820
+ ` Mission: ${room.name}`,
821
+ ` Member route: ${room.member_route?.suggested_member || room.owner} (editable; ${room.member_route?.why || room.member_context?.status || 'suggested'})`,
822
+ ` First proof: ${room.task_plan_preview?.first_proof_step || room.first_proof_step}`,
823
+ ` Stop rule: ${room.task_plan_preview?.stop_rule || 'Stop at one proof receipt, one approval question, or one real blocker.'}`,
824
+ ` Context: ${(room.task_plan_preview?.context_paths || room.proactive_next_mission?.context_paths || []).join(' | ') || 'task first'}`,
825
+ ` Clarify: ${room.clarifying_questions.map((question) => question.question).join(' | ')}`,
826
+ ` Approval: ${room.approval_packet.approve_question} (${room.approval_packet.decision_options.join('/')}; member can change)`,
827
+ ` Goal chain: ${room.goal_chain.loop}`,
828
+ ' Timeline preview:',
829
+ ...(timelineItems.length ? timelineItems : [' 1. Goal set: pending. Meaning: prove one visible step before the next goal.']),
830
+ ` Thinking: ${room.thinking_memory?.path || 'atris/thinking.md'}`,
831
+ ` Proactive suggestion: ${room.proactive_next_mission?.objective || room.goal_chain.first_goal}`,
832
+ ` Result landing: ${room.result?.landing?.status || 'pending_goal_run'} -> ${trustedRun ? 'ready to start one bounded goal' : 'stays pending until a goal runs and proof exists'}`,
833
+ ` Verifier: ${room.verifier}`,
834
+ ` Share: ${room.share_line}`,
835
+ ` Receipt: ${receiptPath}`,
836
+ ` ${trustedRun ? 'After preview' : 'After approve'}: ${room.approval_packet.approved_next_command}`,
837
+ ];
838
+ }
839
+
840
+ module.exports = {
841
+ buildMissionRoom,
842
+ writeMissionRoomReceipt,
843
+ missionRoomLines,
844
+ writeThinkingMemory,
845
+ collectMissionRoomContext,
846
+ };