atris 3.56.0 → 3.56.1
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 +119 -87
- package/commands/autopilot-front.js +29 -13
- package/commands/brainstorm.js +77 -476
- package/commands/business.js +13 -1
- package/commands/fleet-report.js +2 -2
- package/commands/human-missions.js +26 -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/log.js +79 -30
- package/commands/mission.js +117 -22
- package/commands/next.js +153 -63
- package/commands/now.js +80 -38
- package/commands/recap.js +66 -4
- package/commands/run-front.js +20 -5
- package/commands/spaceship.js +52 -12
- package/commands/status.js +30 -7
- package/commands/task.js +56 -19
- package/commands/terminal.js +5 -5
- package/commands/workflow.js +18 -5
- package/commands/x-search.js +123 -13
- package/commands/youtube.js +639 -36
- package/lib/account-bound.js +7 -0
- package/lib/apply-gate.js +94 -0
- package/lib/context-gatherer.js +55 -8
- package/lib/engine-ask.js +16 -6
- package/lib/first-minute.js +552 -26
- package/lib/known-commands.js +1 -1
- package/lib/runner-command.js +5 -3
- package/lib/scratch-root.js +44 -0
- package/package.json +1 -1
- package/utils/auth.js +9 -0
package/commands/recap.js
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const {
|
|
3
|
+
const {
|
|
4
|
+
isCertifiedReview,
|
|
5
|
+
isFreshWorkspace,
|
|
6
|
+
isKeepWorkingMinute,
|
|
7
|
+
buildFirstMinute,
|
|
8
|
+
listUserVisibleWork,
|
|
9
|
+
personName,
|
|
10
|
+
speakFirstMinute,
|
|
11
|
+
speakKeepWorkingMinute,
|
|
12
|
+
} = require('../lib/first-minute');
|
|
4
13
|
const { isRealTestRunnerProof, quoteVerifierCommand } = require('../lib/verifier-quality');
|
|
5
14
|
|
|
6
15
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
@@ -241,6 +250,20 @@ function recapSoftTitle(title, maxWords = 5) {
|
|
|
241
250
|
return `"${text.toLowerCase()}"`;
|
|
242
251
|
}
|
|
243
252
|
|
|
253
|
+
function hasLiveKeepWorkingRun(root = process.cwd()) {
|
|
254
|
+
try {
|
|
255
|
+
const { pickLiveLocalMission } = require('./mission');
|
|
256
|
+
return Boolean(pickLiveLocalMission(root));
|
|
257
|
+
} catch {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function pickSpokenInProgress(items) {
|
|
263
|
+
const list = Array.isArray(items) ? items.filter(Boolean) : [];
|
|
264
|
+
return list.find((item) => item.owner) || list[0] || null;
|
|
265
|
+
}
|
|
266
|
+
|
|
244
267
|
function renderRecapMinute(data, { person } = {}) {
|
|
245
268
|
const who = person != null ? person : personName();
|
|
246
269
|
const greet = who ? `hey ${who}, ` : '';
|
|
@@ -290,9 +313,15 @@ function renderRecapMinute(data, { person } = {}) {
|
|
|
290
313
|
}
|
|
291
314
|
|
|
292
315
|
if (inProgress.length) {
|
|
293
|
-
const item = inProgress
|
|
316
|
+
const item = pickSpokenInProgress(inProgress);
|
|
294
317
|
const named = recapSoftTitle(item && item.title);
|
|
295
|
-
if (item && item.owner && named)
|
|
318
|
+
if (item && item.owner && named) {
|
|
319
|
+
return [
|
|
320
|
+
`${greet}${named} is already yours.`,
|
|
321
|
+
'',
|
|
322
|
+
'next: atris do',
|
|
323
|
+
].join('\n');
|
|
324
|
+
}
|
|
296
325
|
if (named) return `${greet}${named} is ready to claim.`;
|
|
297
326
|
}
|
|
298
327
|
|
|
@@ -319,9 +348,42 @@ function recapAtris(args = []) {
|
|
|
319
348
|
printRecapHelp();
|
|
320
349
|
return;
|
|
321
350
|
}
|
|
351
|
+
const root = process.cwd();
|
|
352
|
+
const visibleBefore = listUserVisibleWork(root);
|
|
322
353
|
const daysIdx = args.indexOf('--days');
|
|
323
354
|
const days = daysIdx !== -1 ? Number(args[daysIdx + 1]) : DEFAULT_DAYS;
|
|
324
|
-
const data = buildRecapData(
|
|
355
|
+
const data = buildRecapData(root, { days });
|
|
356
|
+
if (
|
|
357
|
+
data.empty
|
|
358
|
+
&& isFreshWorkspace(root)
|
|
359
|
+
&& !args.includes('--verbose')
|
|
360
|
+
&& !args.includes('--full')
|
|
361
|
+
&& !args.includes('--share')
|
|
362
|
+
) {
|
|
363
|
+
return speakFirstMinute({
|
|
364
|
+
root,
|
|
365
|
+
asJson: args.includes('--json'),
|
|
366
|
+
files: visibleBefore,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
// Just-minted file folder, nothing running: same two lines as
|
|
370
|
+
// first-minute / status / the next do. Not factory MAP.md.
|
|
371
|
+
// A claimed non-seed task still recaps that work. A live mission
|
|
372
|
+
// and --verbose / --share keep the recap report.
|
|
373
|
+
if (
|
|
374
|
+
!args.includes('--verbose')
|
|
375
|
+
&& !args.includes('--full')
|
|
376
|
+
&& !args.includes('--share')
|
|
377
|
+
&& !hasLiveKeepWorkingRun(root)
|
|
378
|
+
) {
|
|
379
|
+
const minute = buildFirstMinute({ root, files: visibleBefore });
|
|
380
|
+
if (isKeepWorkingMinute(minute)) {
|
|
381
|
+
return speakKeepWorkingMinute({
|
|
382
|
+
root,
|
|
383
|
+
asJson: args.includes('--json'),
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
}
|
|
325
387
|
if (args.includes('--json')) {
|
|
326
388
|
console.log(JSON.stringify(data, null, 2));
|
|
327
389
|
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,
|
package/commands/spaceship.js
CHANGED
|
@@ -13,17 +13,42 @@
|
|
|
13
13
|
* atris spaceship --hours 4 --repo /path/to/repo --interval 780 --yes
|
|
14
14
|
* atris spaceship --hours 0.01 --tick-cmd /tmp/stub.sh --no-email --yes
|
|
15
15
|
*
|
|
16
|
-
* Without --yes, spaceship only
|
|
16
|
+
* Without --yes, spaceship only talks the keep-working plan (no email, no run).
|
|
17
17
|
* `--json` without --yes prints a JSON refuse and still does not start.
|
|
18
|
+
* `--yes` from an unbound scratch folder refuses: that folder is not a room.
|
|
18
19
|
*/
|
|
19
20
|
|
|
20
21
|
const path = require('path');
|
|
21
22
|
const fs = require('fs');
|
|
22
23
|
const { spawn } = require('child_process');
|
|
23
24
|
const { hasYesFlag, argsWantHelp, wantsJson } = require('../lib/noninteractive');
|
|
25
|
+
const { isUnboundScratchFolder, refuseUnboundScratch } = require('../lib/scratch-root');
|
|
26
|
+
const { resolveWorkspaceRoot } = require('../lib/mission-root');
|
|
27
|
+
const { personName } = require('../lib/first-minute');
|
|
24
28
|
|
|
25
29
|
const SCRIPT = path.join(__dirname, '..', 'scripts', 'spaceship.sh');
|
|
26
30
|
|
|
31
|
+
function greet(person) {
|
|
32
|
+
return person ? `hey ${person}, ` : '';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function readFlagValue(args, name) {
|
|
36
|
+
const list = Array.isArray(args) ? args : [];
|
|
37
|
+
const idx = list.indexOf(name);
|
|
38
|
+
const raw = idx !== -1 && list[idx + 1] && !String(list[idx + 1]).startsWith('-')
|
|
39
|
+
? String(list[idx + 1]).trim()
|
|
40
|
+
: '';
|
|
41
|
+
return raw;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function spokenHours(raw) {
|
|
45
|
+
const text = raw === undefined || raw === null ? '4' : String(raw).trim();
|
|
46
|
+
const n = Number(text);
|
|
47
|
+
if (!Number.isFinite(n) || n <= 0) return '4 hours';
|
|
48
|
+
if (n === 1) return `${text} hour`;
|
|
49
|
+
return `${text} hours`;
|
|
50
|
+
}
|
|
51
|
+
|
|
27
52
|
function printUsage() {
|
|
28
53
|
const lines = [
|
|
29
54
|
'Usage: atris spaceship [--hours N] [--interval SEC] [--repo PATH]',
|
|
@@ -31,25 +56,34 @@ function printUsage() {
|
|
|
31
56
|
' [--idle-alert N] [--halt-alert N] [--label NAME]',
|
|
32
57
|
' [--no-email] [--yes]',
|
|
33
58
|
'',
|
|
34
|
-
|
|
35
|
-
'Without --yes,
|
|
36
|
-
'Defaults: --hours 4, --interval 780, tick = atris autopilot --auto --iterations=1',
|
|
59
|
+
"Keep working here for a few hours. I'll write you if something changes.",
|
|
60
|
+
'Without --yes, I only say the plan. --no-email stays quiet.',
|
|
37
61
|
];
|
|
38
62
|
console.log(lines.join('\n'));
|
|
39
63
|
}
|
|
40
64
|
|
|
41
65
|
function planLines(args = []) {
|
|
42
|
-
const
|
|
43
|
-
const hours =
|
|
66
|
+
const list = Array.isArray(args) ? args : [];
|
|
67
|
+
const hours = spokenHours(readFlagValue(list, '--hours') || '4');
|
|
68
|
+
const next = list.includes('--no-email')
|
|
69
|
+
? 'next: atris spaceship --yes'
|
|
70
|
+
: "I'll write you if something changes. next: atris spaceship --yes";
|
|
44
71
|
return [
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
' tick: atris autopilot --auto --iterations=1',
|
|
49
|
-
'Pass --yes to start the overnight run.',
|
|
72
|
+
`${greet(personName())}I can keep working here for ${hours}.`,
|
|
73
|
+
'',
|
|
74
|
+
next,
|
|
50
75
|
];
|
|
51
76
|
}
|
|
52
77
|
|
|
78
|
+
function spaceshipTargetRoot(args = []) {
|
|
79
|
+
const list = Array.isArray(args) ? args : [];
|
|
80
|
+
const idx = list.indexOf('--repo');
|
|
81
|
+
const raw = idx !== -1 && list[idx + 1] && !String(list[idx + 1]).startsWith('-')
|
|
82
|
+
? path.resolve(list[idx + 1])
|
|
83
|
+
: process.cwd();
|
|
84
|
+
return resolveWorkspaceRoot(raw);
|
|
85
|
+
}
|
|
86
|
+
|
|
53
87
|
function spaceship(args = []) {
|
|
54
88
|
const list = Array.isArray(args) ? args : [];
|
|
55
89
|
if (argsWantHelp(list) || list.includes('--help') || list.includes('-h')) {
|
|
@@ -75,6 +109,12 @@ function spaceship(args = []) {
|
|
|
75
109
|
process.exit(2);
|
|
76
110
|
}
|
|
77
111
|
|
|
112
|
+
// --yes starts the run. It is not a workspace unlock. An unbound
|
|
113
|
+
// scratch folder is not a room (same class as slack/gmail from scratch).
|
|
114
|
+
if (isUnboundScratchFolder(spaceshipTargetRoot(list))) {
|
|
115
|
+
process.exit(refuseUnboundScratch());
|
|
116
|
+
}
|
|
117
|
+
|
|
78
118
|
const runArgs = list.filter((a) => a !== '--yes' && a !== '-y' && a !== '--json');
|
|
79
119
|
return new Promise((resolve, reject) => {
|
|
80
120
|
if (!fs.existsSync(SCRIPT)) {
|
|
@@ -99,4 +139,4 @@ function spaceship(args = []) {
|
|
|
99
139
|
});
|
|
100
140
|
}
|
|
101
141
|
|
|
102
|
-
module.exports = { spaceship, SCRIPT, printUsage, planLines };
|
|
142
|
+
module.exports = { spaceship, SCRIPT, printUsage, planLines, spokenHours, spaceshipTargetRoot };
|
package/commands/status.js
CHANGED
|
@@ -4,6 +4,13 @@ const { getLogPath, ensureLogDirectory, createLogFile } = require('../lib/journa
|
|
|
4
4
|
const { parseTodo, getTeamActivity } = require('../lib/todo');
|
|
5
5
|
const { clarify } = require('../lib/autoland');
|
|
6
6
|
const { checkoutBehindMessage } = require('../lib/checkout-sync');
|
|
7
|
+
const {
|
|
8
|
+
isFreshWorkspace,
|
|
9
|
+
isKeepWorkingMinute,
|
|
10
|
+
buildFirstMinute,
|
|
11
|
+
speakKeepWorkingMinute,
|
|
12
|
+
speakNothingRunning,
|
|
13
|
+
} = require('../lib/first-minute');
|
|
7
14
|
|
|
8
15
|
// Box drawing helpers
|
|
9
16
|
const W = 64; // inner width
|
|
@@ -122,18 +129,34 @@ function parseStatusTodo(todoFile) {
|
|
|
122
129
|
}
|
|
123
130
|
}
|
|
124
131
|
|
|
132
|
+
function hasLiveKeepWorkingRun(root = process.cwd()) {
|
|
133
|
+
try {
|
|
134
|
+
const { pickLiveLocalMission } = require('./mission');
|
|
135
|
+
return Boolean(pickLiveLocalMission(root));
|
|
136
|
+
} catch {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
125
141
|
function statusAtris(isQuick = false, jsonMode = false, verbose = false) {
|
|
126
|
-
|
|
142
|
+
// Fresh folder: empty talks first-talk. A file already here
|
|
143
|
+
// names that file, same as bare atris. Do not mint a room.
|
|
144
|
+
if (isFreshWorkspace()) {
|
|
145
|
+
process.exit(speakNothingRunning({ asJson: jsonMode }));
|
|
146
|
+
}
|
|
127
147
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
148
|
+
// Just-minted file folder, nothing running: same two lines as
|
|
149
|
+
// first-minute / the next do. A live mission still gets the board.
|
|
150
|
+
// --verbose keeps the factory dump because the operator asked for it.
|
|
151
|
+
if (!verbose && !hasLiveKeepWorkingRun()) {
|
|
152
|
+
const minute = buildFirstMinute({ root: process.cwd() });
|
|
153
|
+
if (isKeepWorkingMinute(minute)) {
|
|
154
|
+
process.exit(speakKeepWorkingMinute({ asJson: jsonMode }));
|
|
132
155
|
}
|
|
133
|
-
console.log('✗ atris/ folder not found. Run "atris init" first.');
|
|
134
|
-
process.exit(1);
|
|
135
156
|
}
|
|
136
157
|
|
|
158
|
+
const targetDir = path.join(process.cwd(), 'atris');
|
|
159
|
+
|
|
137
160
|
// Load task board state.
|
|
138
161
|
const todoFile = path.join(targetDir, 'TODO.md');
|
|
139
162
|
const todo = parseStatusTodo(todoFile);
|
package/commands/task.js
CHANGED
|
@@ -60,7 +60,18 @@ const {
|
|
|
60
60
|
decisionMarkerFor,
|
|
61
61
|
DECISION_REFUSE_REASON,
|
|
62
62
|
} = require('../lib/task-decision');
|
|
63
|
-
const {
|
|
63
|
+
const {
|
|
64
|
+
buildFirstMinute,
|
|
65
|
+
deskNextCommand,
|
|
66
|
+
firstTalkCommand,
|
|
67
|
+
folderName,
|
|
68
|
+
personName,
|
|
69
|
+
pickNext,
|
|
70
|
+
speakFirstMinute,
|
|
71
|
+
taskCommand,
|
|
72
|
+
taskNextCommand,
|
|
73
|
+
} = require('../lib/first-minute');
|
|
74
|
+
const { loadContext } = require('../lib/state-detection');
|
|
64
75
|
|
|
65
76
|
const DEFAULT_OWNER = process.env.ATRIS_AGENT_ID
|
|
66
77
|
|| process.env.USER
|
|
@@ -6172,9 +6183,9 @@ function cmdAdd(args) {
|
|
|
6172
6183
|
if (isUninitializedTaskFolder(root)) {
|
|
6173
6184
|
if (wantsJson(args)) {
|
|
6174
6185
|
printJson({
|
|
6175
|
-
ok:
|
|
6176
|
-
action: '
|
|
6177
|
-
command:
|
|
6186
|
+
ok: false,
|
|
6187
|
+
action: 'none',
|
|
6188
|
+
command: firstTalkCommand(folderName(root)),
|
|
6178
6189
|
task_id: null,
|
|
6179
6190
|
projection_path: null,
|
|
6180
6191
|
task: null,
|
|
@@ -6493,9 +6504,19 @@ function cmdDay(args) {
|
|
|
6493
6504
|
|
|
6494
6505
|
function cmdFirstMinute() {
|
|
6495
6506
|
const root = process.cwd();
|
|
6507
|
+
const fresh = !fs.existsSync(path.join(root, 'atris'));
|
|
6508
|
+
let context = {};
|
|
6509
|
+
if (!fresh) {
|
|
6510
|
+
try {
|
|
6511
|
+
context = loadContext(root);
|
|
6512
|
+
} catch {
|
|
6513
|
+
context = {};
|
|
6514
|
+
}
|
|
6515
|
+
}
|
|
6496
6516
|
const screen = buildFirstMinute({
|
|
6497
6517
|
root,
|
|
6498
|
-
fresh
|
|
6518
|
+
fresh,
|
|
6519
|
+
context,
|
|
6499
6520
|
});
|
|
6500
6521
|
console.log(screen.text);
|
|
6501
6522
|
}
|
|
@@ -6826,10 +6847,12 @@ function createEndgameSeedTask(taskDb, db, seed, owner) {
|
|
|
6826
6847
|
}
|
|
6827
6848
|
|
|
6828
6849
|
function nextActionFromCommand(command) {
|
|
6829
|
-
const
|
|
6850
|
+
const text = String(command || '').trim();
|
|
6851
|
+
const match = text.match(/^atris (?:task|mission) (\S+)/);
|
|
6830
6852
|
const verb = match ? match[1] : '';
|
|
6831
|
-
if (
|
|
6832
|
-
return
|
|
6853
|
+
if (verb && verb !== 'new') return verb;
|
|
6854
|
+
if (/^atris do\b/.test(text)) return 'do';
|
|
6855
|
+
return 'none';
|
|
6833
6856
|
}
|
|
6834
6857
|
|
|
6835
6858
|
function cmdNextTruth(args) {
|
|
@@ -6840,9 +6863,24 @@ function cmdNextTruth(args) {
|
|
|
6840
6863
|
const workspaceRoot = taskDb.workspaceRoot();
|
|
6841
6864
|
const rows = taskDb.listTasks(db, { workspaceRoot, limit: 500 });
|
|
6842
6865
|
const existingProj = readProjectionFile(workspaceRoot);
|
|
6866
|
+
const dbHasActionable = rows.some((task) => {
|
|
6867
|
+
const status = task && task.status;
|
|
6868
|
+
return status === 'open' || status === 'claimed' || status === 'review';
|
|
6869
|
+
});
|
|
6870
|
+
const projHasActionable = Boolean(
|
|
6871
|
+
existingProj
|
|
6872
|
+
&& Array.isArray(existingProj.tasks)
|
|
6873
|
+
&& existingProj.tasks.some((task) => {
|
|
6874
|
+
const status = task && task.status;
|
|
6875
|
+
return status === 'open' || status === 'claimed' || status === 'review';
|
|
6876
|
+
}),
|
|
6877
|
+
);
|
|
6843
6878
|
let projection;
|
|
6844
6879
|
let outPath;
|
|
6845
|
-
if (
|
|
6880
|
+
if (!dbHasActionable && projHasActionable) {
|
|
6881
|
+
projection = existingProj;
|
|
6882
|
+
outPath = path.resolve(path.join(workspaceRoot || '.', '.atris', 'state', 'tasks.projection.json'));
|
|
6883
|
+
} else if (rows.length === 0 && existingProj && Array.isArray(existingProj.tasks) && existingProj.tasks.length > 0) {
|
|
6846
6884
|
projection = existingProj;
|
|
6847
6885
|
outPath = path.resolve(path.join(workspaceRoot || '.', '.atris', 'state', 'tasks.projection.json'));
|
|
6848
6886
|
} else {
|
|
@@ -6911,9 +6949,9 @@ function cmdNext(args) {
|
|
|
6911
6949
|
if (isUninitializedTaskFolder(root)) {
|
|
6912
6950
|
if (wantsJson(args)) {
|
|
6913
6951
|
printJson({
|
|
6914
|
-
ok:
|
|
6915
|
-
action: '
|
|
6916
|
-
command:
|
|
6952
|
+
ok: false,
|
|
6953
|
+
action: 'none',
|
|
6954
|
+
command: firstTalkCommand(folderName(root)),
|
|
6917
6955
|
task_id: null,
|
|
6918
6956
|
owner: String(flag(args, '--as') || personName() || DEFAULT_OWNER),
|
|
6919
6957
|
scope: normalizeTaskQueueScope(taskQueueScopeFromArgs(args)),
|
|
@@ -6922,9 +6960,7 @@ function cmdNext(args) {
|
|
|
6922
6960
|
});
|
|
6923
6961
|
return;
|
|
6924
6962
|
}
|
|
6925
|
-
|
|
6926
|
-
console.log(screen.text);
|
|
6927
|
-
return;
|
|
6963
|
+
return speakFirstMinute({ root, fresh: true });
|
|
6928
6964
|
}
|
|
6929
6965
|
if (!hasFlag(args, '--create-next')) return cmdNextTruth(args);
|
|
6930
6966
|
const owner = flag(args, '--as') || DEFAULT_OWNER;
|
|
@@ -9796,16 +9832,17 @@ function appendRelabelArchivedJournalReceipt(workspaceRoot, { actor, count, ids
|
|
|
9796
9832
|
function cmdReady(args) {
|
|
9797
9833
|
const pos = positional(args);
|
|
9798
9834
|
const id = pos[0];
|
|
9799
|
-
|
|
9800
|
-
|
|
9835
|
+
const proofFlag = flag(args, '--proof');
|
|
9836
|
+
const verifyFlag = flag(args, '--verify');
|
|
9837
|
+
const resultFlag = textFlag(args, ['--result']);
|
|
9838
|
+
if (!id || (!proofFlag && !verifyFlag && !resultFlag)) {
|
|
9839
|
+
console.log('Usage: atris task ready <id> --proof "..." --result "<sentence>"');
|
|
9801
9840
|
process.exit(2);
|
|
9802
9841
|
}
|
|
9803
9842
|
// Two ways to prove: --proof "<note>" (claimed, pattern-checked) or
|
|
9804
9843
|
// --verify "<command>" which actually RUNS the command and gates ready on exit 0.
|
|
9805
9844
|
// If --proof names npm test, node --test, or git diff --check, this process
|
|
9806
9845
|
// must have run that command. A sentence that names one of those is a lie.
|
|
9807
|
-
const proofFlag = flag(args, '--proof');
|
|
9808
|
-
const verifyFlag = flag(args, '--verify');
|
|
9809
9846
|
const proofUrl = textFlag(args, ['--proof-url']);
|
|
9810
9847
|
const iFetched = hasFlag(args, '--i-fetched');
|
|
9811
9848
|
if (proofUrl && !iFetched) {
|
package/commands/terminal.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* atris terminal <business> <command...> [--timeout N]
|
|
3
3
|
*
|
|
4
4
|
* Run a shell command directly on a business EC2 workspace via the warm runner.
|
|
5
|
-
* This is the load-bearing primitive for fast bulk ops
|
|
5
|
+
* This is the load-bearing primitive for fast bulk ops: one bash call beats
|
|
6
6
|
* hundreds of rate-limited individual file API calls.
|
|
7
7
|
*
|
|
8
8
|
* SAFETY:
|
|
@@ -27,7 +27,7 @@ const path = require('path');
|
|
|
27
27
|
const { loadCredentials, abortOnAuthFailure } = require('../utils/auth');
|
|
28
28
|
const { apiRequestJson } = require('../utils/api');
|
|
29
29
|
const { loadBusinesses, saveBusinesses } = require('./business');
|
|
30
|
-
const { requireAccountBound, refuseAccountGlobal } = require('../lib/account-bound');
|
|
30
|
+
const { looksLikeBusinessSlug, requireAccountBound, refuseAccountGlobal } = require('../lib/account-bound');
|
|
31
31
|
const { argsWantHelp, wantsJson } = require('../lib/noninteractive');
|
|
32
32
|
|
|
33
33
|
function sleep(ms) {
|
|
@@ -151,8 +151,8 @@ async function terminalAtris() {
|
|
|
151
151
|
try { return JSON.parse(fs.readFileSync(bizFile, 'utf8')).slug; } catch { return null; }
|
|
152
152
|
})();
|
|
153
153
|
|
|
154
|
-
//
|
|
155
|
-
const firstLooksLikeSlug =
|
|
154
|
+
// Flags like --json match a hyphenated word regex. Require a real slug first.
|
|
155
|
+
const firstLooksLikeSlug = looksLikeBusinessSlug(args[0]);
|
|
156
156
|
|
|
157
157
|
if (firstLooksLikeSlug && args.length > 1) {
|
|
158
158
|
slug = args[0];
|
|
@@ -161,7 +161,7 @@ async function terminalAtris() {
|
|
|
161
161
|
slug = cwdSlug;
|
|
162
162
|
command = args.join(' ');
|
|
163
163
|
} else if (firstLooksLikeSlug && args.length === 1) {
|
|
164
|
-
// First
|
|
164
|
+
// First and only arg is a slug-shaped word with no command.
|
|
165
165
|
console.error('Missing command. Usage: atris terminal <business> <command>');
|
|
166
166
|
process.exit(1);
|
|
167
167
|
} else {
|
package/commands/workflow.js
CHANGED
|
@@ -6,13 +6,16 @@ const {
|
|
|
6
6
|
folderName,
|
|
7
7
|
freshMinuteJson,
|
|
8
8
|
isCertifiedReview,
|
|
9
|
+
listUserVisibleWork,
|
|
9
10
|
isFreshWorkspace,
|
|
10
11
|
personName,
|
|
11
12
|
pickNext,
|
|
12
13
|
renderWorkspace,
|
|
13
14
|
speakFirstMinute,
|
|
14
15
|
taskCommand,
|
|
16
|
+
visibleWorkTitle,
|
|
15
17
|
} = require('../lib/first-minute');
|
|
18
|
+
const { startFirstTalk } = require('../lib/context-gatherer');
|
|
16
19
|
const { isNonInteractive } = require('../lib/noninteractive');
|
|
17
20
|
const { loadContext } = require('../lib/state-detection');
|
|
18
21
|
const { buildToolResultBody } = require('../lib/tool-result-encode');
|
|
@@ -64,7 +67,8 @@ function reviewSoftTitle(title, maxWords = 5) {
|
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
function isHumanDeskNext(command) {
|
|
67
|
-
|
|
70
|
+
const text = String(command || '');
|
|
71
|
+
return /^atris do\b/.test(text) || /^atris task (?:claim|show|ready|accept)\b/.test(text);
|
|
68
72
|
}
|
|
69
73
|
|
|
70
74
|
function loadReviewTasks(root = process.cwd()) {
|
|
@@ -506,7 +510,7 @@ async function planAtris(userInput = null) {
|
|
|
506
510
|
// init --minimal is optional context, not a factory bounce.
|
|
507
511
|
if (!fs.existsSync(targetDir)) {
|
|
508
512
|
if (args.includes('--json')) {
|
|
509
|
-
console.log(JSON.stringify(freshMinuteJson(), null, 2));
|
|
513
|
+
console.log(JSON.stringify(freshMinuteJson(folderName(cwd), listUserVisibleWork(cwd), { root: cwd }), null, 2));
|
|
510
514
|
process.exit(2);
|
|
511
515
|
}
|
|
512
516
|
const screen = buildFirstMinute({ root: cwd, fresh: true });
|
|
@@ -877,11 +881,20 @@ async function doAtris() {
|
|
|
877
881
|
const cwd = process.cwd();
|
|
878
882
|
const targetDir = path.join(cwd, 'atris');
|
|
879
883
|
|
|
880
|
-
// Empty folder talks like bare atris.
|
|
881
|
-
//
|
|
884
|
+
// Empty folder talks like bare atris. Files already here start
|
|
885
|
+
// first-talk, then next is atris do. A second do stays two first-minute
|
|
886
|
+
// lines. Missing executor.md after init --minimal is optional
|
|
887
|
+
// context, not a factory bounce.
|
|
882
888
|
if (!fs.existsSync(targetDir)) {
|
|
889
|
+
const visible = listUserVisibleWork(cwd);
|
|
890
|
+
if (visible.length) {
|
|
891
|
+
const title = visibleWorkTitle(visible, folderName(cwd));
|
|
892
|
+
const code = startFirstTalk(cwd, title, { asJson: args.includes('--json') });
|
|
893
|
+
if (code !== 0) process.exit(code);
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
883
896
|
if (args.includes('--json')) {
|
|
884
|
-
console.log(JSON.stringify(freshMinuteJson(), null, 2));
|
|
897
|
+
console.log(JSON.stringify(freshMinuteJson(folderName(cwd), visible, { root: cwd }), null, 2));
|
|
885
898
|
process.exit(2);
|
|
886
899
|
}
|
|
887
900
|
const screen = buildFirstMinute({ root: cwd, fresh: true });
|