atris 3.56.0 → 3.56.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/atris/skills/engines/SKILL.md +11 -3
- package/atris/skills/x-search/SKILL.md +20 -1
- package/atris/skills/youtube/SKILL.md +51 -43
- package/bin/atris.js +134 -90
- package/commands/auth.js +6 -1
- package/commands/autopilot-front.js +29 -13
- package/commands/brainstorm.js +77 -476
- package/commands/business.js +13 -1
- package/commands/engine.js +7 -0
- package/commands/experiments.js +178 -0
- package/commands/fleet-report.js +2 -2
- package/commands/founder.js +12 -0
- package/commands/human-missions.js +64 -2
- package/commands/init.js +2 -35
- package/commands/integrations.js +100 -0
- package/commands/land.js +53 -23
- package/commands/later.js +52 -0
- package/commands/launchpad.js +45 -10
- package/commands/log.js +79 -30
- package/commands/mission.js +117 -22
- package/commands/next.js +153 -63
- package/commands/now.js +115 -38
- package/commands/recap.js +97 -4
- package/commands/run-front.js +20 -5
- package/commands/spaceship.js +52 -12
- package/commands/status.js +38 -7
- package/commands/task.js +56 -19
- package/commands/terminal.js +5 -5
- package/commands/workflow.js +19 -5
- package/commands/x-search.js +123 -13
- package/commands/youtube.js +941 -36
- package/lib/account-bound.js +7 -0
- package/lib/apply-gate.js +102 -0
- package/lib/config-guard.js +106 -0
- package/lib/context-gatherer.js +55 -8
- package/lib/engine-ask.js +16 -6
- package/lib/engine-registry.js +14 -4
- package/lib/first-minute.js +571 -26
- package/lib/known-commands.js +1 -1
- package/lib/pack-capabilities.js +13 -3
- package/lib/runner-command.js +5 -3
- package/lib/scratch-root.js +44 -0
- package/lib/workspace-scaffold.js +3 -1
- package/package.json +1 -1
- package/utils/auth.js +84 -6
package/commands/next.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { argsWantHelp, wantsJson } = require('../lib/noninteractive');
|
|
4
|
+
const {
|
|
5
|
+
buildFirstMinute,
|
|
6
|
+
folderName,
|
|
7
|
+
freshMinuteJson,
|
|
8
|
+
isFreshWorkspace,
|
|
9
|
+
listUserVisibleWork,
|
|
10
|
+
} = require('../lib/first-minute');
|
|
11
|
+
const { loadContext } = require('../lib/state-detection');
|
|
3
12
|
const {
|
|
4
13
|
claimRoadmapItem,
|
|
5
14
|
nextCards,
|
|
@@ -9,70 +18,146 @@ const {
|
|
|
9
18
|
seedInboxFromMove,
|
|
10
19
|
} = require('../lib/next-moves');
|
|
11
20
|
|
|
12
|
-
function showHelp() {
|
|
13
|
-
|
|
14
|
-
console.log('Usage: atris next [yes|no|skip]');
|
|
15
|
-
console.log('');
|
|
16
|
-
console.log('Shows one next move card.');
|
|
17
|
-
console.log('');
|
|
21
|
+
function showHelp(log = console.log) {
|
|
22
|
+
log('Usage: atris next [--json]');
|
|
18
23
|
return 0;
|
|
19
24
|
}
|
|
20
25
|
|
|
21
|
-
function
|
|
22
|
-
return String(
|
|
26
|
+
function spokenWin(text) {
|
|
27
|
+
return String(text || '')
|
|
28
|
+
.split('\n')
|
|
29
|
+
.map((line) => line.trim())
|
|
30
|
+
.find(Boolean) || '';
|
|
23
31
|
}
|
|
24
32
|
|
|
25
|
-
function
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
function minuteText(screen) {
|
|
34
|
+
const win = spokenWin(screen && screen.text);
|
|
35
|
+
const next = String(screen && screen.nextCommand || '').trim();
|
|
36
|
+
if (!next) return win || 'nothing is waiting.';
|
|
37
|
+
return `${win}\n\nnext: ${next}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function minuteJson(screen, extra = {}) {
|
|
41
|
+
const reason = spokenWin(screen && screen.text).replace(/\.$/, '');
|
|
42
|
+
const next = String((screen && screen.nextCommand) || extra.next_action || '').trim();
|
|
43
|
+
const fresh = extra.fresh === true;
|
|
44
|
+
return {
|
|
45
|
+
schema: 'atris.one_lap.v1',
|
|
46
|
+
ok: Boolean(next) && !fresh,
|
|
47
|
+
status: fresh ? 'stuck' : (next ? 'ok' : 'stuck'),
|
|
48
|
+
reason,
|
|
49
|
+
next_action: next,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function printMinute(screen, { asJson = false, log = console.log, fresh = false } = {}) {
|
|
54
|
+
if (asJson) {
|
|
55
|
+
log(JSON.stringify(minuteJson(screen, { fresh }), null, 2));
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
58
|
+
log('');
|
|
59
|
+
log(minuteText(screen));
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function nextScreen(root) {
|
|
64
|
+
const fresh = isFreshWorkspace(root);
|
|
65
|
+
if (fresh) {
|
|
66
|
+
return {
|
|
67
|
+
fresh: true,
|
|
68
|
+
screen: buildFirstMinute({ root, fresh: true }),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
fresh: false,
|
|
73
|
+
screen: buildFirstMinute({
|
|
74
|
+
root,
|
|
75
|
+
fresh: false,
|
|
76
|
+
context: loadContext(root),
|
|
77
|
+
}),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function speakNext(root, { asJson = false, log = console.log } = {}) {
|
|
82
|
+
const { fresh, screen } = nextScreen(root);
|
|
83
|
+
if (asJson && fresh) {
|
|
84
|
+
log(JSON.stringify(freshMinuteJson(folderName(root), listUserVisibleWork(root), { root }), null, 2));
|
|
85
|
+
return 0;
|
|
86
|
+
}
|
|
87
|
+
return printMinute(screen, { asJson, log, fresh });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function cardLabel(card) {
|
|
91
|
+
return String(card?.label || card?.title || '').trim();
|
|
32
92
|
}
|
|
33
93
|
|
|
34
|
-
function
|
|
35
|
-
if (!card) return '
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
94
|
+
function cardNextCommand(card) {
|
|
95
|
+
if (!card) return '';
|
|
96
|
+
const action = card.next_action || {};
|
|
97
|
+
const prompt = String(action.prompt || '').trim();
|
|
98
|
+
if (/^(atris|ax)\b/i.test(prompt)) return prompt;
|
|
99
|
+
if (action.type === 'mission_complete' && action.mission_id) {
|
|
100
|
+
if (action.proof_path) {
|
|
101
|
+
return `atris mission complete ${action.mission_id} --proof ${action.proof_path}`;
|
|
102
|
+
}
|
|
103
|
+
return `atris mission status ${action.mission_id}`;
|
|
104
|
+
}
|
|
105
|
+
if (action.type === 'mission_review_prompt' && action.mission_id) {
|
|
106
|
+
return `atris mission status ${action.mission_id}`;
|
|
107
|
+
}
|
|
108
|
+
if (action.type === 'wish_answer_prompt') return 'atris wish answer "your words"';
|
|
109
|
+
if (card.source === 'inbox') return 'atris plan';
|
|
110
|
+
if (card.source === 'task' || card.source === 'roadmap' || card.source === 'endgame') {
|
|
111
|
+
return 'atris do';
|
|
112
|
+
}
|
|
113
|
+
if (card.source === 'mission') {
|
|
114
|
+
const id = action.mission_id || card.ref;
|
|
115
|
+
return id ? `atris mission status ${id}` : 'atris mission status --status active';
|
|
116
|
+
}
|
|
117
|
+
return '';
|
|
41
118
|
}
|
|
42
119
|
|
|
43
|
-
function
|
|
44
|
-
|
|
120
|
+
function speakCard(card, { asJson = false, log = console.log } = {}) {
|
|
121
|
+
if (!card) {
|
|
122
|
+
return printMinute({ text: 'nothing is waiting.', nextCommand: '' }, { asJson, log });
|
|
123
|
+
}
|
|
124
|
+
const next = cardNextCommand(card);
|
|
125
|
+
const label = cardLabel(card) || 'this';
|
|
126
|
+
return printMinute({
|
|
127
|
+
text: `${label} is waiting.`,
|
|
128
|
+
nextCommand: next,
|
|
129
|
+
}, { asJson, log });
|
|
45
130
|
}
|
|
46
131
|
|
|
47
132
|
function wishPromptTitle(label) {
|
|
48
133
|
return String(label || 'this').replace(/^(#\d+)\s+/, '$1: ');
|
|
49
134
|
}
|
|
50
135
|
|
|
51
|
-
function printWishAnswerPrompt(card) {
|
|
136
|
+
function printWishAnswerPrompt(card, log = console.log) {
|
|
52
137
|
const action = card.next_action || {};
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
138
|
+
log(`Got it, wish ${wishPromptTitle(action.label || cardLabel(card))}.`);
|
|
139
|
+
log(String(action.question || 'What should be different when this wish comes true?'));
|
|
140
|
+
log('Answer with: atris wish answer "your words"');
|
|
56
141
|
return 0;
|
|
57
142
|
}
|
|
58
143
|
|
|
59
|
-
function approveMove(card, root) {
|
|
144
|
+
function approveMove(card, root, log = console.log) {
|
|
60
145
|
recordDecision(root, card, 'approve', new Date().toISOString());
|
|
61
146
|
if (['roadmap', 'inbox', 'endgame'].includes(card.source)) {
|
|
62
147
|
seedInboxFromMove(root, card);
|
|
63
148
|
if (card.source === 'roadmap') claimRoadmapItem(root, card.title);
|
|
64
|
-
|
|
149
|
+
log(`${cardLabel(card)} is working.`);
|
|
65
150
|
return 0;
|
|
66
151
|
}
|
|
67
152
|
const prompt = card.next_action && card.next_action.prompt;
|
|
68
|
-
|
|
153
|
+
log(prompt || `${cardLabel(card)} is working.`);
|
|
69
154
|
return 0;
|
|
70
155
|
}
|
|
71
156
|
|
|
72
|
-
function completeMissionFromCard(card) {
|
|
157
|
+
function completeMissionFromCard(card, log = console.log) {
|
|
73
158
|
const action = card.next_action || {};
|
|
74
159
|
if (!action.mission_id || !action.proof_path) {
|
|
75
|
-
|
|
160
|
+
log('Review the proof, then complete this mission.');
|
|
76
161
|
return 0;
|
|
77
162
|
}
|
|
78
163
|
const { completeMission } = require('./mission');
|
|
@@ -80,57 +165,62 @@ function completeMissionFromCard(card) {
|
|
|
80
165
|
return 0;
|
|
81
166
|
}
|
|
82
167
|
|
|
83
|
-
function executeCard(card, root) {
|
|
168
|
+
function executeCard(card, root, log = console.log) {
|
|
84
169
|
const action = card && card.next_action;
|
|
85
|
-
if (!card) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
if (action?.type === 'wish_answer_prompt') return printWishAnswerPrompt(card);
|
|
90
|
-
if (action?.type === 'mission_complete') return completeMissionFromCard(card);
|
|
170
|
+
if (!card) return speakCard(null, { log });
|
|
171
|
+
if (action?.type === 'wish_answer_prompt') return printWishAnswerPrompt(card, log);
|
|
172
|
+
if (action?.type === 'mission_complete') return completeMissionFromCard(card, log);
|
|
91
173
|
if (action?.type === 'wish_review_prompt' || action?.type === 'mission_review_prompt') {
|
|
92
|
-
|
|
174
|
+
log(action.prompt || 'Review the proof, then choose done or stuck.');
|
|
93
175
|
return 0;
|
|
94
176
|
}
|
|
95
|
-
return approveMove(card, root);
|
|
177
|
+
return approveMove(card, root, log);
|
|
96
178
|
}
|
|
97
179
|
|
|
98
|
-
function
|
|
99
|
-
const
|
|
100
|
-
|
|
180
|
+
function actionToken(args = []) {
|
|
181
|
+
const list = Array.isArray(args) ? args : [];
|
|
182
|
+
const token = list.find((arg) => {
|
|
183
|
+
const text = String(arg || '').trim();
|
|
184
|
+
if (!text || text.startsWith('-')) return false;
|
|
185
|
+
return true;
|
|
186
|
+
});
|
|
187
|
+
return String(token || '').trim().toLowerCase();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function nextCommand(args = [], root = process.cwd(), { log = console.log } = {}) {
|
|
191
|
+
const list = Array.isArray(args) ? args : [];
|
|
192
|
+
if (argsWantHelp(list)) return showHelp(log);
|
|
193
|
+
|
|
194
|
+
const asJson = wantsJson(list);
|
|
195
|
+
const action = actionToken(list);
|
|
196
|
+
|
|
197
|
+
if (!action || action === 'json') return speakNext(root, { asJson, log });
|
|
101
198
|
|
|
102
199
|
const current = nextCards(root, 1)[0] || null;
|
|
103
|
-
if (!action) {
|
|
104
|
-
printCard(current);
|
|
105
|
-
if (current?.source === 'dream') markDreamCardConsumed(root, current, new Date().toISOString(), 'dealt');
|
|
106
|
-
return 0;
|
|
107
|
-
}
|
|
108
200
|
|
|
109
201
|
if (action === 'skip') {
|
|
110
202
|
const following = current ? nextCards(root, 1, { skipIds: [current.id] })[0] : null;
|
|
111
|
-
if (current?.source === 'dream')
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
203
|
+
if (current?.source === 'dream') {
|
|
204
|
+
markDreamCardConsumed(root, current, new Date().toISOString(), 'skipped');
|
|
205
|
+
}
|
|
206
|
+
if (following?.source === 'dream') {
|
|
207
|
+
markDreamCardConsumed(root, following, new Date().toISOString(), 'dealt');
|
|
208
|
+
}
|
|
209
|
+
return speakCard(following, { asJson, log });
|
|
115
210
|
}
|
|
116
211
|
|
|
117
212
|
if (action === 'no') {
|
|
118
|
-
if (!current) {
|
|
119
|
-
printCard(null);
|
|
120
|
-
return 0;
|
|
121
|
-
}
|
|
213
|
+
if (!current) return speakCard(null, { asJson, log });
|
|
122
214
|
parkNextCard(root, current);
|
|
123
|
-
|
|
215
|
+
log(`Parked ${cardLabel(current)}.`);
|
|
124
216
|
return 0;
|
|
125
217
|
}
|
|
126
218
|
|
|
127
|
-
if (action === 'yes') return executeCard(current, root);
|
|
219
|
+
if (action === 'yes') return executeCard(current, root, log);
|
|
128
220
|
|
|
129
|
-
|
|
130
|
-
return 2;
|
|
221
|
+
return speakNext(root, { asJson, log });
|
|
131
222
|
}
|
|
132
223
|
|
|
133
224
|
module.exports = {
|
|
134
225
|
nextCommand,
|
|
135
|
-
renderCard,
|
|
136
226
|
};
|
package/commands/now.js
CHANGED
|
@@ -3,6 +3,13 @@ const path = require('path');
|
|
|
3
3
|
const { hasRenderedSections, isOpenSection } = require('../lib/todo-sections');
|
|
4
4
|
const { renderMorningCardRow } = require('../lib/receipt-block');
|
|
5
5
|
const { historicalLandingText } = require('../lib/autoland');
|
|
6
|
+
const {
|
|
7
|
+
buildFirstMinute,
|
|
8
|
+
isFreshWorkspace,
|
|
9
|
+
isKeepWorkingMinute,
|
|
10
|
+
speakFirstMinute,
|
|
11
|
+
speakKeepWorkingMinute,
|
|
12
|
+
} = require('../lib/first-minute');
|
|
6
13
|
|
|
7
14
|
const NOW_PATH = path.join('atris', 'now.md');
|
|
8
15
|
const TASK_EPISODES_PATH = path.join('.atris', 'state', 'task_episodes.jsonl');
|
|
@@ -576,6 +583,39 @@ function refreshNowFile(root = process.cwd(), options = {}) {
|
|
|
576
583
|
return { path: nowPath, preserved: false };
|
|
577
584
|
}
|
|
578
585
|
|
|
586
|
+
function stripAnsi(text) {
|
|
587
|
+
return String(text || '').replace(/\u001b\[[0-9;]*m/g, '');
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function spokenCurrent(text) {
|
|
591
|
+
const line = stripAnsi(text).split(/\n/).map((part) => part.trim()).find(Boolean) || '';
|
|
592
|
+
return line.replace(/^hey\s+[^,]+,\s*/i, '').replace(/\.$/, '');
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function printNowJson(payload) {
|
|
596
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function hasLiveKeepWorkingRun(root = process.cwd()) {
|
|
600
|
+
try {
|
|
601
|
+
const { pickLiveLocalMission } = require('./mission');
|
|
602
|
+
return Boolean(pickLiveLocalMission(root));
|
|
603
|
+
} catch {
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function nowJsonPayload(root = process.cwd()) {
|
|
609
|
+
const fresh = isFreshWorkspace(root);
|
|
610
|
+
const screen = buildFirstMinute({ root, fresh });
|
|
611
|
+
const payload = { ok: !fresh };
|
|
612
|
+
const current = spokenCurrent(screen && screen.text);
|
|
613
|
+
const next = stripAnsi(screen && screen.nextCommand);
|
|
614
|
+
if (current) payload.current = current;
|
|
615
|
+
if (next) payload.next = next;
|
|
616
|
+
return payload;
|
|
617
|
+
}
|
|
618
|
+
|
|
579
619
|
function nowAtris(args = process.argv.slice(3), root = process.cwd()) {
|
|
580
620
|
const help = args.includes('--help') || args.includes('-h') || args[0] === 'help';
|
|
581
621
|
if (help) {
|
|
@@ -588,8 +628,8 @@ function nowAtris(args = process.argv.slice(3), root = process.cwd()) {
|
|
|
588
628
|
console.log(' atris now --refresh Regenerate a small local now.md');
|
|
589
629
|
console.log(' atris now --all Refresh this parent and every child Atris workspace');
|
|
590
630
|
console.log(' atris now --path Print the file path only');
|
|
591
|
-
console.log(' atris now --json
|
|
592
|
-
return;
|
|
631
|
+
console.log(' atris now --json Print ok, next or current as JSON');
|
|
632
|
+
return 0;
|
|
593
633
|
}
|
|
594
634
|
|
|
595
635
|
const init = args.includes('--init');
|
|
@@ -598,48 +638,85 @@ function nowAtris(args = process.argv.slice(3), root = process.cwd()) {
|
|
|
598
638
|
const pathOnly = args.includes('--path');
|
|
599
639
|
const asJson = args.includes('--json');
|
|
600
640
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
641
|
+
try {
|
|
642
|
+
if (asJson && !init && !refresh && !all && !pathOnly) {
|
|
643
|
+
const payload = nowJsonPayload(root);
|
|
644
|
+
printNowJson(payload);
|
|
645
|
+
return payload.ok ? 0 : 2;
|
|
606
646
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
647
|
+
|
|
648
|
+
// Fresh folder: empty talks first-talk. A file already here
|
|
649
|
+
// names that file, same as bare atris / now --json. Do not mint.
|
|
650
|
+
// Just-minted file folder, nothing running: same two lines as
|
|
651
|
+
// first-minute / status / recap. Not factory MAP.md.
|
|
652
|
+
// After init, next is claim: same two lines as bare atris /
|
|
653
|
+
// now --json. Not factory now.md.
|
|
654
|
+
// --init / --refresh / --all / --path still write now.md.
|
|
655
|
+
if (!init && !refresh && !all && !pathOnly) {
|
|
656
|
+
if (isFreshWorkspace(root)) {
|
|
657
|
+
return speakFirstMinute({ root, fresh: true });
|
|
658
|
+
}
|
|
659
|
+
if (!hasLiveKeepWorkingRun(root)) {
|
|
660
|
+
const minute = buildFirstMinute({ root });
|
|
661
|
+
if (isKeepWorkingMinute(minute)) {
|
|
662
|
+
return speakKeepWorkingMinute({ root });
|
|
663
|
+
}
|
|
664
|
+
return speakFirstMinute({ root });
|
|
665
|
+
}
|
|
611
666
|
}
|
|
612
|
-
} else if (refresh) {
|
|
613
|
-
result = refreshNowFile(root);
|
|
614
|
-
} else if (init) {
|
|
615
|
-
result = ensureNowFile(root);
|
|
616
|
-
} else {
|
|
617
|
-
result = ensureNowFile(root);
|
|
618
|
-
}
|
|
619
667
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
668
|
+
let result;
|
|
669
|
+
if (all) {
|
|
670
|
+
const workspaces = findChildWorkspaces(root);
|
|
671
|
+
for (const workspace of workspaces) {
|
|
672
|
+
refreshNowFile(workspace.root);
|
|
673
|
+
}
|
|
674
|
+
result = refreshNowFile(root);
|
|
675
|
+
if (!pathOnly && !asJson) {
|
|
676
|
+
console.log(`Refreshed ${workspaces.length} child workspace${workspaces.length === 1 ? '' : 's'}.`);
|
|
677
|
+
console.log('');
|
|
678
|
+
}
|
|
679
|
+
} else if (refresh) {
|
|
680
|
+
result = refreshNowFile(root);
|
|
681
|
+
} else if (init) {
|
|
682
|
+
result = ensureNowFile(root);
|
|
683
|
+
} else {
|
|
684
|
+
result = ensureNowFile(root);
|
|
685
|
+
}
|
|
625
686
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
created: Boolean(result.created),
|
|
632
|
-
content,
|
|
633
|
-
}, null, 2));
|
|
634
|
-
return;
|
|
635
|
-
}
|
|
687
|
+
const rel = path.relative(root, result.path);
|
|
688
|
+
if (pathOnly) {
|
|
689
|
+
console.log(rel);
|
|
690
|
+
return 0;
|
|
691
|
+
}
|
|
636
692
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
693
|
+
if (asJson) {
|
|
694
|
+
const payload = nowJsonPayload(root);
|
|
695
|
+
printNowJson(payload);
|
|
696
|
+
return payload.ok ? 0 : 2;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const content = fs.readFileSync(result.path, 'utf8').trimEnd();
|
|
700
|
+
if (result.created) {
|
|
701
|
+
console.log(`Created ${rel}`);
|
|
702
|
+
console.log('');
|
|
703
|
+
}
|
|
641
704
|
|
|
642
|
-
|
|
705
|
+
console.log(content);
|
|
706
|
+
return 0;
|
|
707
|
+
} catch (err) {
|
|
708
|
+
const message = stripAnsi(err && err.message ? err.message : String(err));
|
|
709
|
+
if (asJson) {
|
|
710
|
+
printNowJson({
|
|
711
|
+
ok: false,
|
|
712
|
+
current: message.replace(/\.$/, ''),
|
|
713
|
+
next: 'atris init --yes',
|
|
714
|
+
});
|
|
715
|
+
return 2;
|
|
716
|
+
}
|
|
717
|
+
console.error(message);
|
|
718
|
+
return 1;
|
|
719
|
+
}
|
|
643
720
|
}
|
|
644
721
|
|
|
645
722
|
module.exports = {
|
package/commands/recap.js
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const {
|
|
3
|
+
const {
|
|
4
|
+
isCertifiedReview,
|
|
5
|
+
isFreshWorkspace,
|
|
6
|
+
isKeepWorkingMinute,
|
|
7
|
+
isClaimMinute,
|
|
8
|
+
buildFirstMinute,
|
|
9
|
+
listUserVisibleWork,
|
|
10
|
+
personName,
|
|
11
|
+
speakFirstMinute,
|
|
12
|
+
speakKeepWorkingMinute,
|
|
13
|
+
taskCommand,
|
|
14
|
+
} = require('../lib/first-minute');
|
|
4
15
|
const { isRealTestRunnerProof, quoteVerifierCommand } = require('../lib/verifier-quality');
|
|
5
16
|
|
|
6
17
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
@@ -241,6 +252,20 @@ function recapSoftTitle(title, maxWords = 5) {
|
|
|
241
252
|
return `"${text.toLowerCase()}"`;
|
|
242
253
|
}
|
|
243
254
|
|
|
255
|
+
function hasLiveKeepWorkingRun(root = process.cwd()) {
|
|
256
|
+
try {
|
|
257
|
+
const { pickLiveLocalMission } = require('./mission');
|
|
258
|
+
return Boolean(pickLiveLocalMission(root));
|
|
259
|
+
} catch {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function pickSpokenInProgress(items) {
|
|
265
|
+
const list = Array.isArray(items) ? items.filter(Boolean) : [];
|
|
266
|
+
return list.find((item) => item.owner) || list[0] || null;
|
|
267
|
+
}
|
|
268
|
+
|
|
244
269
|
function renderRecapMinute(data, { person } = {}) {
|
|
245
270
|
const who = person != null ? person : personName();
|
|
246
271
|
const greet = who ? `hey ${who}, ` : '';
|
|
@@ -290,9 +315,15 @@ function renderRecapMinute(data, { person } = {}) {
|
|
|
290
315
|
}
|
|
291
316
|
|
|
292
317
|
if (inProgress.length) {
|
|
293
|
-
const item = inProgress
|
|
318
|
+
const item = pickSpokenInProgress(inProgress);
|
|
294
319
|
const named = recapSoftTitle(item && item.title);
|
|
295
|
-
if (item && item.owner && named)
|
|
320
|
+
if (item && item.owner && named) {
|
|
321
|
+
return [
|
|
322
|
+
`${greet}${named} is already yours.`,
|
|
323
|
+
'',
|
|
324
|
+
`next: ${taskCommand({ display_id: item.id, status: 'claimed' })}`,
|
|
325
|
+
].join('\n');
|
|
326
|
+
}
|
|
296
327
|
if (named) return `${greet}${named} is ready to claim.`;
|
|
297
328
|
}
|
|
298
329
|
|
|
@@ -319,9 +350,71 @@ function recapAtris(args = []) {
|
|
|
319
350
|
printRecapHelp();
|
|
320
351
|
return;
|
|
321
352
|
}
|
|
353
|
+
const cwd = process.cwd();
|
|
354
|
+
const taskDb = loadTaskDb();
|
|
355
|
+
let root = cwd;
|
|
356
|
+
try {
|
|
357
|
+
root = taskDb ? taskDb.workspaceRoot(cwd) : cwd;
|
|
358
|
+
} catch {
|
|
359
|
+
root = cwd;
|
|
360
|
+
}
|
|
361
|
+
// cd src in a real git project still recaps that project. An empty
|
|
362
|
+
// child under /tmp stays its own room and does not inherit /tmp.
|
|
363
|
+
let inProjectSubdir = false;
|
|
364
|
+
try {
|
|
365
|
+
inProjectSubdir = fs.realpathSync(cwd) !== fs.realpathSync(root);
|
|
366
|
+
} catch {
|
|
367
|
+
inProjectSubdir = path.resolve(cwd) !== path.resolve(root);
|
|
368
|
+
}
|
|
369
|
+
const visibleBefore = listUserVisibleWork(root);
|
|
322
370
|
const daysIdx = args.indexOf('--days');
|
|
323
371
|
const days = daysIdx !== -1 ? Number(args[daysIdx + 1]) : DEFAULT_DAYS;
|
|
324
|
-
const data = buildRecapData(
|
|
372
|
+
const data = buildRecapData(root, { days });
|
|
373
|
+
if (
|
|
374
|
+
data.empty
|
|
375
|
+
&& isFreshWorkspace(root)
|
|
376
|
+
&& !inProjectSubdir
|
|
377
|
+
&& !args.includes('--verbose')
|
|
378
|
+
&& !args.includes('--full')
|
|
379
|
+
&& !args.includes('--share')
|
|
380
|
+
) {
|
|
381
|
+
return speakFirstMinute({
|
|
382
|
+
root,
|
|
383
|
+
asJson: args.includes('--json'),
|
|
384
|
+
files: visibleBefore,
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
// Just-minted file folder, nothing running: same two lines as
|
|
388
|
+
// first-minute / status / recap. After the work is yours, next
|
|
389
|
+
// is task ready so keep-working is not a do loop. Not factory MAP.md.
|
|
390
|
+
// After init, next is claim: same two lines as bare atris /
|
|
391
|
+
// status / now / stop. --json keeps the recap receipt and
|
|
392
|
+
// fills next from first-minute so it is not silent.
|
|
393
|
+
// A claimed non-seed task still recaps that work. A live mission
|
|
394
|
+
// and --verbose / --share keep the recap report.
|
|
395
|
+
if (
|
|
396
|
+
!args.includes('--verbose')
|
|
397
|
+
&& !args.includes('--full')
|
|
398
|
+
&& !args.includes('--share')
|
|
399
|
+
&& !inProjectSubdir
|
|
400
|
+
&& !hasLiveKeepWorkingRun(root)
|
|
401
|
+
) {
|
|
402
|
+
const minute = buildFirstMinute({ root, files: visibleBefore });
|
|
403
|
+
if (isKeepWorkingMinute(minute)) {
|
|
404
|
+
return speakKeepWorkingMinute({
|
|
405
|
+
root,
|
|
406
|
+
asJson: args.includes('--json'),
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
if (isClaimMinute(minute)) {
|
|
410
|
+
if (args.includes('--json')) {
|
|
411
|
+
data.next = minute.nextCommand || null;
|
|
412
|
+
console.log(JSON.stringify(data, null, 2));
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
return speakFirstMinute({ root });
|
|
416
|
+
}
|
|
417
|
+
}
|
|
325
418
|
if (args.includes('--json')) {
|
|
326
419
|
console.log(JSON.stringify(data, null, 2));
|
|
327
420
|
return;
|
package/commands/run-front.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
// atris run: thin front door over the mission runtime loop.
|
|
2
2
|
// One bounded pursuit: start a mission from the objective (or resume the most
|
|
3
|
-
// logical runnable mission
|
|
3
|
+
// logical runnable mission after --yes), then exit.
|
|
4
|
+
// A hours / keep-working mission is never started from a bare invoke.
|
|
4
5
|
// The old plan→do→review loop lives on behind `atris run --legacy`.
|
|
5
6
|
const path = require('path');
|
|
6
7
|
const { spawnSync } = require('child_process');
|
|
8
|
+
const { hasYesFlag, wantsJson } = require('../lib/noninteractive');
|
|
9
|
+
const { speakFirstMinute } = require('../lib/first-minute');
|
|
7
10
|
|
|
8
11
|
const CLI_PATH = path.join(__dirname, '..', 'bin', 'atris.js');
|
|
9
12
|
|
|
@@ -91,6 +94,14 @@ function pickRunnableMission(root = process.cwd(), missionMap = null, options =
|
|
|
91
94
|
return candidates[0] || null;
|
|
92
95
|
}
|
|
93
96
|
|
|
97
|
+
function isHoursMission(mission) {
|
|
98
|
+
if (!mission) return false;
|
|
99
|
+
if (mission.overnight_loop) return true;
|
|
100
|
+
const text = `${mission.objective || ''} ${mission.stop_condition || ''}`;
|
|
101
|
+
if (/\b(overnight|nonstop|forever|goal\s+after\s+goal|self[-\s]?improve)\b/i.test(text)) return true;
|
|
102
|
+
return /\bhours?\b/i.test(text);
|
|
103
|
+
}
|
|
104
|
+
|
|
94
105
|
function runTickBudget(args, budgetSeconds) {
|
|
95
106
|
const explicit = positiveNumber(readValueFlag(args, '--max-ticks'));
|
|
96
107
|
if (explicit) return Math.floor(explicit);
|
|
@@ -122,14 +133,17 @@ async function runMissionFront(args = []) {
|
|
|
122
133
|
return result.ok ? 0 : 1;
|
|
123
134
|
}
|
|
124
135
|
|
|
136
|
+
// Bare `atris run` talks like the desk. A long keep-working mission
|
|
137
|
+
// needs an objective or an explicit --yes. Headless never prompts.
|
|
138
|
+
if (!hasYesFlag(args)) {
|
|
139
|
+
return speakFirstMinute({ asJson: wantsJson(args) });
|
|
140
|
+
}
|
|
141
|
+
|
|
125
142
|
const mission = pickRunnableMission(process.cwd(), null, {
|
|
126
143
|
allowCallerSessionRunners: liveCodexSession(),
|
|
127
144
|
});
|
|
128
145
|
if (!mission) {
|
|
129
|
-
|
|
130
|
-
console.log('Start one: atris run "<objective>" [--minutes N] [--owner <member>]');
|
|
131
|
-
console.log('Or keep going indefinitely: atris autopilot');
|
|
132
|
-
return 1;
|
|
146
|
+
return speakFirstMinute({ asJson: wantsJson(args) });
|
|
133
147
|
}
|
|
134
148
|
console.log(`Resuming mission ${mission.id} (${mission.status}): ${mission.objective}`);
|
|
135
149
|
const result = spawnSync(process.execPath, [
|
|
@@ -144,6 +158,7 @@ async function runMissionFront(args = []) {
|
|
|
144
158
|
module.exports = {
|
|
145
159
|
runMissionFront,
|
|
146
160
|
pickRunnableMission,
|
|
161
|
+
isHoursMission,
|
|
147
162
|
runObjective,
|
|
148
163
|
runBudgetSeconds,
|
|
149
164
|
runTickBudget,
|