atris 3.30.1 → 3.30.2
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.
- package/AGENTS.md +6 -0
- package/atris/AGENTS.md +11 -0
- package/atris/CLAUDE.md +5 -0
- package/atris/atris.md +19 -4
- package/atris/policies/atris-design.md +71 -0
- package/atris/policies/design-seed.md +187 -0
- package/atris/skills/atris/SKILL.md +26 -3
- package/atris/skills/design/SKILL.md +3 -2
- package/atris/skills/loop/SKILL.md +5 -3
- package/atris/team/_template/MEMBER.md +19 -0
- package/atris.md +63 -23
- package/ax +1434 -1698
- package/bin/atris.js +5 -1
- package/commands/agent-spawn.js +2 -2
- package/commands/brain.js +92 -7
- package/commands/brainstorm.js +62 -22
- package/commands/business-sync.js +92 -8
- package/commands/business.js +13 -7
- package/commands/chat-scan.js +102 -0
- package/commands/computer.js +758 -15
- package/commands/deck.js +505 -105
- package/commands/init.js +6 -1
- package/commands/launchpad.js +638 -0
- package/commands/log-sync.js +44 -13
- package/commands/loop-front.js +290 -0
- package/commands/member.js +124 -3
- package/commands/mission.js +653 -30
- package/commands/pull.js +37 -28
- package/commands/pulse.js +11 -8
- package/commands/run.js +79 -39
- package/commands/sync.js +36 -17
- package/commands/task.js +342 -66
- package/commands/xp.js +3 -0
- package/commands/youtube.js +221 -7
- package/decks/README.md +89 -0
- package/decks/archetype-catalog.json +180 -0
- package/decks/atris-antislop-pitch.json +48 -0
- package/decks/atris-archetypes-v2.json +83 -0
- package/decks/atris-one-loop-pitch.json +80 -0
- package/decks/atris-seed-pitch-v3.json +118 -0
- package/decks/atris-seed-pitch-v4-skeleton.json +106 -0
- package/decks/atris-seed-pitch-v5.json +109 -0
- package/decks/atris-seed-pitch-v6.json +137 -0
- package/decks/atris-seed-pitch-v7.json +133 -0
- package/decks/atris-single-shot-proof.json +74 -0
- package/decks/mark-pincus-narrative.json +102 -0
- package/decks/mark-pincus-sourcery.json +94 -0
- package/decks/yash-applied-compute-detailed.json +150 -0
- package/decks/yash-applied-compute-generalist.json +82 -0
- package/decks/yash-applied-compute-narrative.json +54 -0
- package/lib/ax-prefs.js +5 -12
- package/lib/chat-log-scan.js +377 -0
- package/lib/context-gatherer.js +35 -1
- package/lib/deck-compose.js +145 -0
- package/lib/deck-history.js +64 -0
- package/lib/deck-layout.js +169 -0
- package/lib/deck-review.js +431 -0
- package/lib/deck-schema.js +154 -0
- package/lib/file-ops.js +2 -2
- package/lib/functional-owner.js +189 -0
- package/lib/slides-deck.js +512 -58
- package/lib/task-db.js +109 -2
- package/package.json +2 -1
- package/templates/business-starter/team/START_HERE.md +12 -8
- package/utils/auth.js +4 -0
- package/utils/config.js +4 -0
- package/atris/atrisDev.md +0 -717
package/commands/log-sync.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
};
|
package/commands/member.js
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
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
|
-
`
|
|
3863
|
+
`Native goal: ${nativeGoal.slash_goal}`,
|
|
3864
|
+
`Next: ${nativeGoal.next_command}`,
|
|
3744
3865
|
],
|
|
3745
3866
|
asJson,
|
|
3746
3867
|
);
|