atris 3.30.5 → 3.30.7
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 +13 -2
- package/README.md +1 -1
- package/atris/AGENTS.md +7 -0
- package/atris/CLAUDE.md +7 -0
- package/atris/GEMINI.md +7 -0
- package/atris/skills/atris/SKILL.md +24 -4
- package/ax +58 -46
- package/commands/init.js +17 -0
- package/commands/mission.js +85 -15
- package/commands/worktree.js +101 -3
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -61,6 +61,14 @@ Always-on agents should move proof-backed work to Review, complete their native
|
|
|
61
61
|
goal, then continue the mission loop with the next goal. They must not run
|
|
62
62
|
`atris task accept` or claim AgentXP unless a human approved the proof.
|
|
63
63
|
|
|
64
|
+
Mission-shaped user intent wins before normal task selection. If the user
|
|
65
|
+
message starts with `atris mission run`, execute that command first, then run
|
|
66
|
+
`atris mission goal --json` and mirror `goal.visible_goal` into the native chat
|
|
67
|
+
goal. If the user asks for a mission, loop, overnight run, "keep going", "fix
|
|
68
|
+
this", or sends a screenshot with task intent and no mission exists yet, start
|
|
69
|
+
one with `atris mission run "<inferred objective>" --owner <member>` instead of
|
|
70
|
+
falling back to manual task/worktree flow.
|
|
71
|
+
|
|
64
72
|
Codex agents with native goal tools should run `atris mission goal --json`
|
|
65
73
|
before choosing work. If the payload includes `goal.visible_goal`, mirror
|
|
66
74
|
`goal.objective` into the visible chat goal when the current goal is empty,
|
|
@@ -77,7 +85,10 @@ CHECK → atris review (verify + cleanup)
|
|
|
77
85
|
|
|
78
86
|
## Parallel Member Worktrees
|
|
79
87
|
|
|
80
|
-
|
|
88
|
+
Default to the current checkout for small, clean, single-agent fixes. Use an
|
|
89
|
+
isolated checkout only when the launcher is dirty, multiple agents may edit in
|
|
90
|
+
parallel, proof will run for a long time, the change is risky, or release/publish
|
|
91
|
+
work needs a clean tree.
|
|
81
92
|
|
|
82
93
|
```bash
|
|
83
94
|
atris worktree guide
|
|
@@ -87,7 +98,7 @@ cd <printed path>
|
|
|
87
98
|
atris worktree ship --message "<commit summary>" --verify "<test command>" --merge
|
|
88
99
|
```
|
|
89
100
|
|
|
90
|
-
This ties member/agent identity, mission/member state, branch name, isolated checkout, optional Swarlo claim, verification, push, PR, and merge together. Use `atris worktree status` before broad staging
|
|
101
|
+
This ties member/agent identity, mission/member state, branch name, isolated checkout, optional Swarlo claim, verification, push, PR, and merge together. Use `atris worktree status` before broad staging and `atris worktree cleanup` / `atris worktree cleanup --apply` to remove clean merged worktrees.
|
|
91
102
|
|
|
92
103
|
## Mission Autonomy
|
|
93
104
|
|
package/README.md
CHANGED
package/atris/AGENTS.md
CHANGED
|
@@ -22,6 +22,9 @@ section.
|
|
|
22
22
|
## Mission Autonomy
|
|
23
23
|
|
|
24
24
|
Use `atris mission` when work should survive this chat or run as an autonomous loop.
|
|
25
|
+
Mission-shaped user intent wins before normal task selection: if the user says
|
|
26
|
+
`atris mission run ...`, execute it first, then run `atris mission goal --json`
|
|
27
|
+
and mirror `goal.visible_goal`.
|
|
25
28
|
|
|
26
29
|
```
|
|
27
30
|
member -> mission start --verify -> status --status active -> one bounded step -> mission tick --verify -> receipt -> complete|run|stop
|
|
@@ -62,6 +65,10 @@ before choosing work. If the payload includes `goal.visible_goal`, mirror
|
|
|
62
65
|
complete, or already matching; never complete unrelated active goals just to
|
|
63
66
|
make room.
|
|
64
67
|
|
|
68
|
+
Default to the current checkout for small, clean, single-agent fixes. Use
|
|
69
|
+
worktrees only for dirty launchers, parallel agents, long proof, risky edits, or
|
|
70
|
+
release/publish work; clean old merged worktrees with `atris worktree cleanup`.
|
|
71
|
+
|
|
65
72
|
Do not write new operating doctrine here first. Add it to Atris policy, skills,
|
|
66
73
|
wiki, or `atris/atris.md`, then regenerate this adapter if needed.
|
|
67
74
|
|
package/atris/CLAUDE.md
CHANGED
|
@@ -70,6 +70,9 @@ Do not write new operating doctrine here first. Add it to Atris policy, skills,
|
|
|
70
70
|
## Mission Autonomy
|
|
71
71
|
|
|
72
72
|
Use `atris mission` when work should survive this chat or run as an autonomous loop.
|
|
73
|
+
Mission-shaped user intent wins before normal task selection: if the user says
|
|
74
|
+
`atris mission run ...`, execute it first, then run `atris mission goal --json`
|
|
75
|
+
and mirror `goal.visible_goal`.
|
|
73
76
|
|
|
74
77
|
```
|
|
75
78
|
member -> mission start --verify -> status --status active -> one bounded step -> mission tick --verify -> receipt -> complete|run|stop
|
|
@@ -81,6 +84,10 @@ member -> mission start --verify -> status --status active -> one bounded step -
|
|
|
81
84
|
- Prove: after one bounded step, run `atris mission tick <id> --verify --summary "<what changed>"`.
|
|
82
85
|
- Close: if the verifier passes, run `atris mission complete <id> --proof "<receipt_path>"`; if current-agent work should keep going, repeat status -> step -> tick.
|
|
83
86
|
|
|
87
|
+
Default to the current checkout for small, clean, single-agent fixes. Use
|
|
88
|
+
worktrees only for dirty launchers, parallel agents, long proof, risky edits, or
|
|
89
|
+
release/publish work; clean old merged worktrees with `atris worktree cleanup`.
|
|
90
|
+
|
|
84
91
|
If the task produces durable project knowledge, update `atris/wiki/` or run the local wiki flow (`atris ingest`, `atris query`, `atris lint`).
|
|
85
92
|
|
|
86
93
|
## Rules (Non‑Negotiable)
|
package/atris/GEMINI.md
CHANGED
|
@@ -39,6 +39,9 @@ This is the Atris boot sequence. Show the output to the user, then respond natur
|
|
|
39
39
|
## Mission Autonomy
|
|
40
40
|
|
|
41
41
|
Use `atris mission` when work should survive this chat or run as an autonomous loop.
|
|
42
|
+
Mission-shaped user intent wins before normal task selection: if the user says
|
|
43
|
+
`atris mission run ...`, execute it first, then run `atris mission goal --json`
|
|
44
|
+
and mirror `goal.visible_goal`.
|
|
42
45
|
|
|
43
46
|
```
|
|
44
47
|
member -> mission start --verify -> status --status active -> one bounded step -> mission tick --verify -> receipt -> complete|run|stop
|
|
@@ -50,6 +53,10 @@ member -> mission start --verify -> status --status active -> one bounded step -
|
|
|
50
53
|
- Prove: after one bounded step, run `atris mission tick <id> --verify --summary "<what changed>"`.
|
|
51
54
|
- Close: if the verifier passes, run `atris mission complete <id> --proof "<receipt_path>"`; if current-agent work should keep going, repeat status -> step -> tick.
|
|
52
55
|
|
|
56
|
+
Default to the current checkout for small, clean, single-agent fixes. Use
|
|
57
|
+
worktrees only for dirty launchers, parallel agents, long proof, risky edits, or
|
|
58
|
+
release/publish work; clean old merged worktrees with `atris worktree cleanup`.
|
|
59
|
+
|
|
53
60
|
## Rules
|
|
54
61
|
|
|
55
62
|
- Plan = ASCII visualization + approval gate. Do not execute during planning.
|
|
@@ -28,6 +28,7 @@ Detect: Project has `atris/` folder with MAP.md, TODO.md, PERSONA.md
|
|
|
28
28
|
Commands: plan → do → review
|
|
29
29
|
|
|
30
30
|
Key behaviors:
|
|
31
|
+
- Mission-shaped intent wins: run `atris mission run ...` before normal task selection when the user invokes it or asks for a loop.
|
|
31
32
|
- Read PERSONA.md (3-4 sentences, ASCII visuals)
|
|
32
33
|
- Check MAP.md for file:line refs
|
|
33
34
|
- Use `atris task` for claims, proof, ready, and accept
|
|
@@ -35,6 +36,24 @@ Key behaviors:
|
|
|
35
36
|
- Treat TODO.md as a rendered view; regenerate it instead of hand-editing tasks
|
|
36
37
|
- In Codex runtimes with native goal tools, mirror `atris mission goal --json` into the visible chat goal via `goal.visible_goal`
|
|
37
38
|
|
|
39
|
+
## Mission Intent Intercept
|
|
40
|
+
|
|
41
|
+
Before checking existing tasks, inspect the user's message. If it starts with
|
|
42
|
+
`atris mission run`, execute that command first. Then run
|
|
43
|
+
`atris mission goal --json` and mirror `goal.visible_goal` into the runtime's
|
|
44
|
+
native goal when available.
|
|
45
|
+
|
|
46
|
+
If the user asks for a mission, loop, overnight run, "keep going", "fix this",
|
|
47
|
+
or sends a screenshot with task intent and no mission exists yet, start one with
|
|
48
|
+
`atris mission run "<inferred objective>" --owner <member>` instead of falling
|
|
49
|
+
back to manual task/worktree flow.
|
|
50
|
+
|
|
51
|
+
Worktrees are not the default. Stay in the current checkout for small, clean,
|
|
52
|
+
single-agent fixes. Use `atris worktree start` only for dirty launchers,
|
|
53
|
+
parallel agents, long proof runs, risky edits, or release/publish work. Use
|
|
54
|
+
`atris worktree cleanup` to preview cleanup and `atris worktree cleanup --apply`
|
|
55
|
+
to remove clean merged worktrees.
|
|
56
|
+
|
|
38
57
|
## Visible Chat Goal Mirror
|
|
39
58
|
|
|
40
59
|
Before choosing work in an Atris repo, run:
|
|
@@ -59,7 +78,8 @@ goal after proof is ready; only a human should run `atris task accept`.
|
|
|
59
78
|
|
|
60
79
|
1. Run `atris atris.md` on first interaction to show workspace status
|
|
61
80
|
2. Read `atris/MAP.md` before any file search to find file:line refs
|
|
62
|
-
3.
|
|
63
|
-
4. Run `atris
|
|
64
|
-
5.
|
|
65
|
-
6.
|
|
81
|
+
3. If user intent is mission-shaped, run `atris mission run ...` before task flow
|
|
82
|
+
4. Run `atris mission goal --json` and mirror `goal.visible_goal` into the native chat goal when the runtime supports it
|
|
83
|
+
5. Run `atris task list` or `atris task next` only after mission intent is handled
|
|
84
|
+
6. Claim tasks with `atris task claim <id> --as <functional-member>`
|
|
85
|
+
7. Move completed work to review with `atris task ready <id> --proof "..."`
|
package/ax
CHANGED
|
@@ -17,6 +17,7 @@ const BACKEND = {
|
|
|
17
17
|
path: '/api/atris2/turn'
|
|
18
18
|
};
|
|
19
19
|
const DEFAULT_BACKEND_BASE = `http://${BACKEND.host}:${BACKEND.port}`;
|
|
20
|
+
const CLOUD_BACKEND_BASE = 'https://api.atris.ai';
|
|
20
21
|
const CODE_FAST = {
|
|
21
22
|
path: '/api/cursor/turn',
|
|
22
23
|
model: 'composer-2-5-fast',
|
|
@@ -124,11 +125,11 @@ function formatUsage() {
|
|
|
124
125
|
' ax [--max|--fast] --benchmark',
|
|
125
126
|
'',
|
|
126
127
|
'Modes:',
|
|
127
|
-
' --max
|
|
128
|
-
' --pro
|
|
129
|
-
' --fast
|
|
128
|
+
' --max highest reasoning, slowest turns',
|
|
129
|
+
' --pro deeper tool loop',
|
|
130
|
+
' --fast faster low-latency turns',
|
|
130
131
|
' --code-fast Atris Code Fast public lane',
|
|
131
|
-
' --local force local workspace tools',
|
|
132
|
+
' --local force local workspace tools with your own AtrisOS backend',
|
|
132
133
|
' --cloud force authenticated cloud connectors/chat',
|
|
133
134
|
' --business <slug> run tools on that business cloud workspace (EC2)',
|
|
134
135
|
' --verify <cmd> gate the turn on this command passing (default: no verifier)',
|
|
@@ -209,19 +210,20 @@ function createRunLogger({ cwd = process.cwd(), mode = 'pro', kind = 'play', out
|
|
|
209
210
|
};
|
|
210
211
|
}
|
|
211
212
|
|
|
212
|
-
function backendBaseUrl() {
|
|
213
|
-
|
|
213
|
+
function backendBaseUrl(options = {}) {
|
|
214
|
+
const explicitBase = process.env.AX_BACKEND_URL
|
|
214
215
|
|| process.env.OBELISK_LOCAL_ATRIS2_BACKEND_URL
|
|
215
|
-
|| process.env.OBELISK_ATRIS2_BACKEND_URL
|
|
216
|
-
|
|
216
|
+
|| process.env.OBELISK_ATRIS2_BACKEND_URL;
|
|
217
|
+
if (explicitBase) return explicitBase.replace(/\/$/, '');
|
|
218
|
+
return options.route === 'cloud' ? CLOUD_BACKEND_BASE : DEFAULT_BACKEND_BASE;
|
|
217
219
|
}
|
|
218
220
|
|
|
219
|
-
function backendUrl() {
|
|
220
|
-
return new URL(BACKEND.path, backendBaseUrl()).toString();
|
|
221
|
+
function backendUrl(options = {}) {
|
|
222
|
+
return new URL(BACKEND.path, backendBaseUrl(options)).toString();
|
|
221
223
|
}
|
|
222
224
|
|
|
223
|
-
function backendPathUrl(pathname) {
|
|
224
|
-
return new URL(pathname, backendBaseUrl()).toString();
|
|
225
|
+
function backendPathUrl(pathname, options = {}) {
|
|
226
|
+
return new URL(pathname, backendBaseUrl(options)).toString();
|
|
225
227
|
}
|
|
226
228
|
|
|
227
229
|
function codeFastBaseUrl() {
|
|
@@ -266,10 +268,11 @@ function buildRunProfile(options = {}) {
|
|
|
266
268
|
reasoning: 'Composer 2.5 fast lane; charges 10 credits per public turn'
|
|
267
269
|
};
|
|
268
270
|
}
|
|
269
|
-
const
|
|
270
|
-
const
|
|
271
|
+
const profileMessage = options.message || 'what files are here?';
|
|
272
|
+
const route = resolveRoute(profileMessage, options);
|
|
273
|
+
const payload = buildPayload(profileMessage, { mode, cwd, route });
|
|
271
274
|
return {
|
|
272
|
-
endpoint: backendUrl(),
|
|
275
|
+
endpoint: backendUrl({ route }),
|
|
273
276
|
mode,
|
|
274
277
|
route,
|
|
275
278
|
model: payload.model,
|
|
@@ -302,7 +305,7 @@ function formatRunProfile(profile, options = {}) {
|
|
|
302
305
|
async function buildRuntimeHealth(options = {}) {
|
|
303
306
|
const healthRes = options.healthRes
|
|
304
307
|
? await Promise.resolve(options.healthRes)
|
|
305
|
-
: await requestJson(ATRIS2_HEALTH_PATH, { token: '', timeoutMs: options.timeoutMs || 1500 });
|
|
308
|
+
: await requestJson(ATRIS2_HEALTH_PATH, { token: '', timeoutMs: options.timeoutMs || 1500, route: options.route || 'local' });
|
|
306
309
|
const data = healthRes.ok && healthRes.data && typeof healthRes.data === 'object' ? healthRes.data : {};
|
|
307
310
|
const models = Array.isArray(data.models) ? data.models : [];
|
|
308
311
|
const fast = models.find(row => row && row.id === 'atris:fast') || null;
|
|
@@ -335,7 +338,7 @@ function formatRuntimeHealth(health, options = {}) {
|
|
|
335
338
|
['fast', fastReady ? 'ready' : 'not ready'],
|
|
336
339
|
['approvals', permissionReady ? 'ready' : 'offline'],
|
|
337
340
|
];
|
|
338
|
-
if (!backendReachable) rows.push(['fix', '
|
|
341
|
+
if (!backendReachable) rows.push(['fix', 'local backend offline; use hosted mode or start your AtrisOS backend']);
|
|
339
342
|
return rows.map(([label, value]) => formatAuxRow(label, value, options)).join('\n');
|
|
340
343
|
}
|
|
341
344
|
|
|
@@ -378,17 +381,17 @@ function authUserId() {
|
|
|
378
381
|
}
|
|
379
382
|
}
|
|
380
383
|
|
|
381
|
-
function isLoopbackBackend() {
|
|
384
|
+
function isLoopbackBackend(options = {}) {
|
|
382
385
|
try {
|
|
383
|
-
const parsed = new URL(backendBaseUrl());
|
|
386
|
+
const parsed = new URL(backendBaseUrl(options));
|
|
384
387
|
return ['127.0.0.1', 'localhost', '::1'].includes(parsed.hostname);
|
|
385
388
|
} catch (_) {
|
|
386
389
|
return false;
|
|
387
390
|
}
|
|
388
391
|
}
|
|
389
392
|
|
|
390
|
-
function requestJson(pathname, { token = authToken(), timeoutMs = 6000 } = {}) {
|
|
391
|
-
const url = backendPathUrl(pathname);
|
|
393
|
+
function requestJson(pathname, { token = authToken(), timeoutMs = 6000, route } = {}) {
|
|
394
|
+
const url = backendPathUrl(pathname, { route });
|
|
392
395
|
const parsed = new URL(url);
|
|
393
396
|
const transport = parsed.protocol === 'https:' ? https : http;
|
|
394
397
|
return new Promise((resolve) => {
|
|
@@ -422,8 +425,8 @@ function requestJson(pathname, { token = authToken(), timeoutMs = 6000 } = {}) {
|
|
|
422
425
|
});
|
|
423
426
|
}
|
|
424
427
|
|
|
425
|
-
function postJson(pathname, body, { token = authToken(), timeoutMs = 30000 } = {}) {
|
|
426
|
-
const url = backendPathUrl(pathname);
|
|
428
|
+
function postJson(pathname, body, { token = authToken(), timeoutMs = 30000, route } = {}) {
|
|
429
|
+
const url = backendPathUrl(pathname, { route });
|
|
427
430
|
const parsed = new URL(url);
|
|
428
431
|
const transport = parsed.protocol === 'https:' ? https : http;
|
|
429
432
|
const postData = JSON.stringify(body || {});
|
|
@@ -524,7 +527,7 @@ function cachedIntegrationStatus(options = {}) {
|
|
|
524
527
|
|
|
525
528
|
async function buildConnectionContext(options = {}) {
|
|
526
529
|
const token = options.token || authToken();
|
|
527
|
-
const localStatusUserId = isLoopbackBackend() ? authUserId() : '';
|
|
530
|
+
const localStatusUserId = isLoopbackBackend({ route: options.route }) ? authUserId() : '';
|
|
528
531
|
const statusPath = localStatusUserId
|
|
529
532
|
? `${ATRIS2_CONNECTION_STATUS_PATH}?connection_user_id=${encodeURIComponent(localStatusUserId)}`
|
|
530
533
|
: CONNECTION_STATUS_PATH;
|
|
@@ -532,9 +535,9 @@ async function buildConnectionContext(options = {}) {
|
|
|
532
535
|
options.statusRes
|
|
533
536
|
? Promise.resolve(options.statusRes)
|
|
534
537
|
: localStatusUserId || token
|
|
535
|
-
? requestJson(statusPath, { token: localStatusUserId ? '' : token })
|
|
538
|
+
? requestJson(statusPath, { token: localStatusUserId ? '' : token, route: options.route })
|
|
536
539
|
: Promise.resolve({ ok: false, data: null }),
|
|
537
|
-
options.contractRes ? Promise.resolve(options.contractRes) : requestJson(CONNECTION_CAPABILITIES_PATH, { token: '' })
|
|
540
|
+
options.contractRes ? Promise.resolve(options.contractRes) : requestJson(CONNECTION_CAPABILITIES_PATH, { token: '', route: options.route })
|
|
538
541
|
]);
|
|
539
542
|
const statusData = statusRes.ok && statusRes.data && typeof statusRes.data === 'object' ? statusRes.data : {};
|
|
540
543
|
const backendStatuses = statusData.statuses && typeof statusData.statuses === 'object' ? statusData.statuses : statusData;
|
|
@@ -609,7 +612,13 @@ function resolveRoute(message, options = {}) {
|
|
|
609
612
|
if (options.route === 'cloud' || options.forceCloud) return 'cloud';
|
|
610
613
|
if (githubWorkspaceIntent(message)) return 'local';
|
|
611
614
|
if (mentionsConnector(message) && !workspaceIntent(message)) return 'cloud';
|
|
612
|
-
return 'local';
|
|
615
|
+
if (workspaceIntent(message)) return 'local';
|
|
616
|
+
return 'cloud';
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function shouldPreflightRuntime(route, options = {}, mode = 'fast') {
|
|
620
|
+
if (options.skipRuntimePreflight || options.turnFunction || options.business || normalizeMode(mode) === 'code-fast') return false;
|
|
621
|
+
return route !== 'cloud';
|
|
613
622
|
}
|
|
614
623
|
|
|
615
624
|
function normalizeMode(mode) {
|
|
@@ -1323,11 +1332,11 @@ function creditsFromApprovalExecution(response) {
|
|
|
1323
1332
|
return Number.isFinite(credits) && credits > 0 ? credits : null;
|
|
1324
1333
|
}
|
|
1325
1334
|
|
|
1326
|
-
async function postApprovalExecution(approvalRequest, { model = 'atris:fast', token = authToken() } = {}) {
|
|
1335
|
+
async function postApprovalExecution(approvalRequest, { model = 'atris:fast', token = authToken(), route = 'cloud' } = {}) {
|
|
1327
1336
|
return postJson(APPROVAL_EXECUTE_PATH, {
|
|
1328
1337
|
model,
|
|
1329
1338
|
approval_request: approvalRequest,
|
|
1330
|
-
}, { token });
|
|
1339
|
+
}, { token, route });
|
|
1331
1340
|
}
|
|
1332
1341
|
|
|
1333
1342
|
async function executeApprovalRequest(approvalRequest, {
|
|
@@ -1716,9 +1725,9 @@ async function postTurn(message, options = {}) {
|
|
|
1716
1725
|
|| mentionsConnector(message)
|
|
1717
1726
|
|| /\b(can you use|can you access|connected|connections?|integrations?|tools?|capabilities)\b/i.test(message || '');
|
|
1718
1727
|
const connectionContext = options.connectionContext || (shouldSendConnectionContext
|
|
1719
|
-
? await buildConnectionContext({ token, localWorkspace: local })
|
|
1728
|
+
? await buildConnectionContext({ token, localWorkspace: local, route })
|
|
1720
1729
|
: null);
|
|
1721
|
-
const connectionUserId = !local && isLoopbackBackend() ? authUserId() : '';
|
|
1730
|
+
const connectionUserId = !local && isLoopbackBackend({ route }) ? authUserId() : '';
|
|
1722
1731
|
const payload = buildPayload(message, { ...options, route, connectionContext, connectionUserId });
|
|
1723
1732
|
const postData = JSON.stringify(payload);
|
|
1724
1733
|
const output = options.output || process.stdout;
|
|
@@ -1726,7 +1735,7 @@ async function postTurn(message, options = {}) {
|
|
|
1726
1735
|
// SSE traffic in between, so the socket-idle timeout needs more headroom.
|
|
1727
1736
|
const baseTimeoutMs = payload.model === 'atris:max' ? 300000 : payload.model === 'atris:pro' ? 180000 : 60000;
|
|
1728
1737
|
const timeoutMs = options.business ? Math.max(baseTimeoutMs, 180000) : baseTimeoutMs;
|
|
1729
|
-
const turnUrl = new URL(backendUrl());
|
|
1738
|
+
const turnUrl = new URL(backendUrl({ route }));
|
|
1730
1739
|
const transport = turnUrl.protocol === 'https:' ? https : http;
|
|
1731
1740
|
const state = {
|
|
1732
1741
|
events: [],
|
|
@@ -1968,19 +1977,18 @@ async function chat(options = {}) {
|
|
|
1968
1977
|
output.write(`${formatHeader({ mode, cwd, chat: true }, output)}\n\n`);
|
|
1969
1978
|
if (logger) output.write(`${formatAuxRow('log', formatPathSubject(logger.path, output), output)}\n\n`);
|
|
1970
1979
|
|
|
1971
|
-
const runtimePreflight = {
|
|
1972
|
-
const ensureRuntimeReady = async () => {
|
|
1973
|
-
if (
|
|
1974
|
-
if (options.skipRuntimePreflight || options.turnFunction || options.business || normalizeMode(mode) === 'code-fast') {
|
|
1975
|
-
runtimePreflight.ready = true;
|
|
1980
|
+
const runtimePreflight = { localReady: false };
|
|
1981
|
+
const ensureRuntimeReady = async (routeForTurn = 'local') => {
|
|
1982
|
+
if (!shouldPreflightRuntime(routeForTurn, options, mode)) {
|
|
1976
1983
|
return true;
|
|
1977
1984
|
}
|
|
1985
|
+
if (runtimePreflight.localReady) return true;
|
|
1978
1986
|
const startedAt = Date.now();
|
|
1979
1987
|
const health = options.runtimeHealth
|
|
1980
1988
|
? await Promise.resolve(options.runtimeHealth({ mode }))
|
|
1981
|
-
: await buildRuntimeHealth({ timeoutMs: options.preflightTimeoutMs || 1200 });
|
|
1989
|
+
: await buildRuntimeHealth({ timeoutMs: options.preflightTimeoutMs || 1200, route: 'local' });
|
|
1982
1990
|
if (runtimeReadyForChat(health, mode)) {
|
|
1983
|
-
runtimePreflight.
|
|
1991
|
+
runtimePreflight.localReady = true;
|
|
1984
1992
|
return true;
|
|
1985
1993
|
}
|
|
1986
1994
|
output.write(`${formatRuntimeHealth(health, output)}\n\n`);
|
|
@@ -2025,7 +2033,7 @@ async function chat(options = {}) {
|
|
|
2025
2033
|
return false;
|
|
2026
2034
|
}
|
|
2027
2035
|
if (pendingApprovalRequest && normalizeMode(mode) !== 'code-fast' && messageApprovesPendingApproval(trimmed)) {
|
|
2028
|
-
if (!(await ensureRuntimeReady())) return false;
|
|
2036
|
+
if (!(await ensureRuntimeReady('cloud'))) return false;
|
|
2029
2037
|
const startedAt = Date.now();
|
|
2030
2038
|
const approvalState = {
|
|
2031
2039
|
events: [],
|
|
@@ -2070,8 +2078,8 @@ async function chat(options = {}) {
|
|
|
2070
2078
|
}
|
|
2071
2079
|
|
|
2072
2080
|
const pendingTaskPreview = latestPendingTaskPreview(history);
|
|
2073
|
-
const route = pendingTaskPreview ? 'cloud' : options.route;
|
|
2074
|
-
if (!(await ensureRuntimeReady())) return false;
|
|
2081
|
+
const route = pendingTaskPreview ? 'cloud' : resolveRoute(trimmed, { route: options.route });
|
|
2082
|
+
if (!(await ensureRuntimeReady(route))) return false;
|
|
2075
2083
|
const turnFunction = options.turnFunction || turnFunctionForMode(mode);
|
|
2076
2084
|
const result = await turnFunction(trimmed, { mode, cwd, history, output, route, business: options.business, verify: options.verify, conversationId });
|
|
2077
2085
|
if (result.output && !result.output.endsWith('\n')) output.write('\n');
|
|
@@ -2135,11 +2143,11 @@ function printBackendHint() {
|
|
|
2135
2143
|
}
|
|
2136
2144
|
|
|
2137
2145
|
function backendStartCommand() {
|
|
2138
|
-
return
|
|
2146
|
+
return 'Run without --local for hosted chat, or set AX_BACKEND_URL to your AtrisOS backend.';
|
|
2139
2147
|
}
|
|
2140
2148
|
|
|
2141
2149
|
function formatBackendHint() {
|
|
2142
|
-
return `
|
|
2150
|
+
return `Local workspace mode needs a running AtrisOS backend:\n${backendStartCommand()}`;
|
|
2143
2151
|
}
|
|
2144
2152
|
|
|
2145
2153
|
function bufferedOutput() {
|
|
@@ -2257,6 +2265,7 @@ async function runSelfTest(options = {}) {
|
|
|
2257
2265
|
try {
|
|
2258
2266
|
await chat({
|
|
2259
2267
|
mode: 'fast',
|
|
2268
|
+
route: 'local',
|
|
2260
2269
|
cwd: os.tmpdir(),
|
|
2261
2270
|
input: Readable.from(['hi\n', 'exit\n']),
|
|
2262
2271
|
output: chatOutput,
|
|
@@ -2277,7 +2286,8 @@ async function runSelfTest(options = {}) {
|
|
|
2277
2286
|
const text = chunks.join('');
|
|
2278
2287
|
assertSelfTest(healthCalls === 1, 'offline preflight repeated unexpectedly');
|
|
2279
2288
|
assertSelfTest(/backend\s+offline/.test(text), 'offline backend status missing');
|
|
2280
|
-
assertSelfTest(/
|
|
2289
|
+
assertSelfTest(/Local workspace mode needs a running AtrisOS backend:/.test(text), 'local backend hint missing');
|
|
2290
|
+
assertSelfTest(!/\/Users\/keshavrao/.test(text), 'local backend hint leaked a developer path');
|
|
2281
2291
|
assertSelfTest(!/secret-token/.test(text), 'offline preflight leaked error detail');
|
|
2282
2292
|
}, results, output);
|
|
2283
2293
|
|
|
@@ -3001,7 +3011,8 @@ async function main() {
|
|
|
3001
3011
|
console.log(formatDoneLine(result.durationMs, creditsFromState(result)));
|
|
3002
3012
|
} catch (error) {
|
|
3003
3013
|
console.error(`x ${error.message}`);
|
|
3004
|
-
|
|
3014
|
+
const shouldShowBackendHint = forceLocal || (prompt && route !== 'cloud' && resolveRoute(prompt, { route: route === 'auto' ? undefined : route }) === 'local');
|
|
3015
|
+
if (shouldShowBackendHint) printBackendHint();
|
|
3005
3016
|
process.exit(1);
|
|
3006
3017
|
}
|
|
3007
3018
|
}
|
|
@@ -3071,6 +3082,7 @@ module.exports = {
|
|
|
3071
3082
|
renderStreamingMarkdown,
|
|
3072
3083
|
renderTerminalMarkdown,
|
|
3073
3084
|
resolveRoute,
|
|
3085
|
+
shouldPreflightRuntime,
|
|
3074
3086
|
runBenchmark,
|
|
3075
3087
|
runSelfTest,
|
|
3076
3088
|
runtimeReadyForChat,
|
package/commands/init.js
CHANGED
|
@@ -671,6 +671,14 @@ Always-on agents should move proof-backed work to Review, complete their native
|
|
|
671
671
|
goal, then continue the mission loop with the next goal. They must not run
|
|
672
672
|
\`atris task accept\` or claim AgentXP unless a human approved the proof.
|
|
673
673
|
|
|
674
|
+
Mission-shaped user intent wins before normal task selection. If the user
|
|
675
|
+
message starts with \`atris mission run\`, execute that command first, then run
|
|
676
|
+
\`atris mission goal --json\` and mirror \`goal.visible_goal\` into the native chat
|
|
677
|
+
goal. If the user asks for a mission, loop, overnight run, "keep going", "fix
|
|
678
|
+
this", or sends a screenshot with task intent and no mission exists yet, start
|
|
679
|
+
one with \`atris mission run "<inferred objective>" --owner <member>\` instead of
|
|
680
|
+
falling back to manual task/worktree flow.
|
|
681
|
+
|
|
674
682
|
Task owners are functional or feature members, not engines. Use \`task-planner\`,
|
|
675
683
|
\`architect\`, \`mission-lead\`, \`validator\`, \`launcher\`, or a feature owner for
|
|
676
684
|
assignment; put coding agent models like Codex and Claude in the \`executed_by\`
|
|
@@ -684,6 +692,15 @@ BUILD → atris do (execute tasks)
|
|
|
684
692
|
CHECK → atris review (verify + cleanup)
|
|
685
693
|
\`\`\`
|
|
686
694
|
|
|
695
|
+
## Worktrees
|
|
696
|
+
|
|
697
|
+
Default to the current checkout for small, clean, single-agent fixes. Use
|
|
698
|
+
\`atris worktree start\` only when the launcher is dirty, multiple agents may
|
|
699
|
+
edit in parallel, proof will run for a long time, the change is risky, or
|
|
700
|
+
release/publish work needs a clean tree. Use \`atris worktree cleanup\` to preview
|
|
701
|
+
merged worktree removal and \`atris worktree cleanup --apply\` to remove clean
|
|
702
|
+
merged worktrees.
|
|
703
|
+
|
|
687
704
|
## Mission Autonomy
|
|
688
705
|
|
|
689
706
|
Use \`atris mission\` when work should survive this chat or run as an autonomous loop.
|
package/commands/mission.js
CHANGED
|
@@ -10,6 +10,8 @@ const {
|
|
|
10
10
|
resolveClaudeRunnerBin,
|
|
11
11
|
} = require('../lib/runner-command');
|
|
12
12
|
const {
|
|
13
|
+
FUNCTIONAL_MEMBER_TOPICS,
|
|
14
|
+
listWorkspaceMemberSlugs,
|
|
13
15
|
normalizeOwnerSlug,
|
|
14
16
|
resolveFunctionalOwner,
|
|
15
17
|
} = require('../lib/functional-owner');
|
|
@@ -142,19 +144,48 @@ function printJsonOrText(payload, lines, asJson) {
|
|
|
142
144
|
for (const line of lines) console.log(line);
|
|
143
145
|
}
|
|
144
146
|
|
|
145
|
-
|
|
147
|
+
const MISSION_RUN_VALUE_FLAGS = [
|
|
148
|
+
'--max-ticks',
|
|
149
|
+
'--max-wall',
|
|
150
|
+
'--cadence',
|
|
151
|
+
'--owner',
|
|
152
|
+
'--runner',
|
|
153
|
+
'--lane',
|
|
154
|
+
'--verify',
|
|
155
|
+
'--stop',
|
|
156
|
+
'--model',
|
|
157
|
+
];
|
|
158
|
+
const MISSION_RUN_BOOLEAN_FLAGS = [
|
|
159
|
+
'--json',
|
|
160
|
+
'--due',
|
|
161
|
+
'--no-claude',
|
|
162
|
+
'--no-verify',
|
|
163
|
+
'--complete-on-pass',
|
|
164
|
+
'--no-drain',
|
|
165
|
+
'--always-on',
|
|
166
|
+
'--xp-task',
|
|
167
|
+
'--agent-xp',
|
|
168
|
+
];
|
|
169
|
+
const DEFAULT_MISSION_RUN_OWNER_SLUGS = new Set(
|
|
170
|
+
FUNCTIONAL_MEMBER_TOPICS.map(topic => normalizeOwnerSlug(topic.owner)),
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
function missionRunInputRequired(asJson = false, owner = '') {
|
|
174
|
+
const defaultOwner = normalizeOwnerSlug(owner || process.env.ATRIS_AGENT_ID || 'mission-lead') || 'mission-lead';
|
|
146
175
|
const payload = {
|
|
147
176
|
ok: false,
|
|
148
177
|
action: 'mission_input_required',
|
|
149
178
|
prompt: 'What mission should Atris run?',
|
|
179
|
+
owner: defaultOwner,
|
|
150
180
|
owner_prompt: 'Which team member should own it?',
|
|
151
|
-
example:
|
|
181
|
+
example: `atris mission run "make onboarding magical" --owner ${defaultOwner}`,
|
|
152
182
|
};
|
|
153
183
|
if (asJson) {
|
|
154
184
|
console.log(JSON.stringify(payload, null, 2));
|
|
155
185
|
} else {
|
|
156
186
|
console.error('What mission should Atris run?');
|
|
157
|
-
console.error(
|
|
187
|
+
if (defaultOwner) console.error(`Team member: ${defaultOwner}`);
|
|
188
|
+
console.error(`Try: atris mission run "make onboarding magical" --owner ${defaultOwner}`);
|
|
158
189
|
}
|
|
159
190
|
process.exit(1);
|
|
160
191
|
}
|
|
@@ -178,6 +209,38 @@ function removeValueFlag(args, name) {
|
|
|
178
209
|
return out;
|
|
179
210
|
}
|
|
180
211
|
|
|
212
|
+
function missionRunOwnerRef(ref, root = process.cwd()) {
|
|
213
|
+
const owner = normalizeOwnerSlug(ref);
|
|
214
|
+
if (!owner || /\s/.test(String(ref || ''))) return null;
|
|
215
|
+
if (listWorkspaceMemberSlugs(root).has(owner)) return owner;
|
|
216
|
+
if (DEFAULT_MISSION_RUN_OWNER_SLUGS.has(owner)) return owner;
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function missionRunArgsWithOwner(args, owner) {
|
|
221
|
+
return [...removeValueFlag(args, '--owner'), '--owner', owner];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function missionRunInputFromArgs(args, root = process.cwd()) {
|
|
225
|
+
const positionals = stripKnownFlags(args, MISSION_RUN_VALUE_FLAGS, MISSION_RUN_BOOLEAN_FLAGS);
|
|
226
|
+
const explicitOwner = Boolean(readFlag(args, '--owner', ''));
|
|
227
|
+
if (!explicitOwner && positionals.length > 0) {
|
|
228
|
+
const owner = missionRunOwnerRef(positionals[0], root);
|
|
229
|
+
if (owner) {
|
|
230
|
+
return {
|
|
231
|
+
ref: positionals.slice(1).join(' ').trim(),
|
|
232
|
+
args: missionRunArgsWithOwner(args, owner),
|
|
233
|
+
owner,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
ref: positionals.join(' ').trim(),
|
|
239
|
+
args,
|
|
240
|
+
owner: readFlag(args, '--owner', ''),
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
181
244
|
async function promptMissionRunInput(args) {
|
|
182
245
|
const defaultOwner = readFlag(args, '--owner', process.env.ATRIS_AGENT_ID || 'mission-lead');
|
|
183
246
|
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
@@ -1825,11 +1888,13 @@ function codexVisibleGoalBridge(mission, goalObjective) {
|
|
|
1825
1888
|
keep_if_matching: 'if current goal objective equals goal.objective, continue the mission',
|
|
1826
1889
|
create_when_empty_or_completed: 'create_goal({ objective: goal.objective })',
|
|
1827
1890
|
complete_after_proof: 'update_goal({ status: "complete" })',
|
|
1891
|
+
refresh_on_phase_change: 'atris mission goal --json before continuing changed work',
|
|
1828
1892
|
refresh_next_candidate: 'atris mission goal --json',
|
|
1829
1893
|
},
|
|
1830
1894
|
guardrails: [
|
|
1831
1895
|
'Do not complete a human-set active goal unless it matches this mission goal or the mission receipt proves handoff.',
|
|
1832
1896
|
'If create_goal fails because another goal is active, keep this bridge waiting for the visible goal slot.',
|
|
1897
|
+
'When the work changes phase or objective, refresh the mission goal before continuing.',
|
|
1833
1898
|
],
|
|
1834
1899
|
};
|
|
1835
1900
|
}
|
|
@@ -1843,7 +1908,8 @@ function codexGoalToolContract(mission) {
|
|
|
1843
1908
|
set_next_goal: 'use goal.visible_goal: create_goal({ objective: goal.objective }) when no active goal blocks the slot',
|
|
1844
1909
|
visible_goal_bridge: 'goal.visible_goal',
|
|
1845
1910
|
platform_requirement: 'Codex runtime must expose replace_goal/set_goal, or allow update_goal({ status: "complete" }) followed by create_goal({ objective }).',
|
|
1846
|
-
|
|
1911
|
+
phase_change_refresh: 'before changed follow-up work, run atris mission goal --json and mirror the returned visible goal',
|
|
1912
|
+
runtime_tool_sequence: 'get_goal -> update_goal({ status: "complete" }) after proof or phase change -> atris mission goal --json -> create_goal({ objective: goal.objective })',
|
|
1847
1913
|
blocked_without_platform_goal_write: true,
|
|
1848
1914
|
mission_id: mission.id,
|
|
1849
1915
|
};
|
|
@@ -2359,6 +2425,10 @@ function cappedClaudeReceiptText(text, limit = 4000) {
|
|
|
2359
2425
|
|
|
2360
2426
|
async function runMission(args) {
|
|
2361
2427
|
const asJson = wantsJson(args);
|
|
2428
|
+
if (hasFlag(args, '--help') || hasFlag(args, '-h')) {
|
|
2429
|
+
help();
|
|
2430
|
+
return;
|
|
2431
|
+
}
|
|
2362
2432
|
const dueMode = hasFlag(args, '--due');
|
|
2363
2433
|
const skipClaude = hasFlag(args, '--no-claude');
|
|
2364
2434
|
const verifyEach = !hasFlag(args, '--no-verify');
|
|
@@ -2368,17 +2438,15 @@ async function runMission(args) {
|
|
|
2368
2438
|
const maxTicks = Math.max(1, Number(maxTicksFlag) || MISSION_RUN_DEFAULTS.maxTicks);
|
|
2369
2439
|
const maxWallSeconds = Math.max(60, Number(readFlag(args, '--max-wall', '')) || MISSION_RUN_DEFAULTS.maxWallSeconds);
|
|
2370
2440
|
const cadenceOverride = readFlag(args, '--cadence', '');
|
|
2371
|
-
const
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
['--json', '--due', '--no-claude', '--no-verify', '--complete-on-pass', '--no-drain', '--always-on', '--xp-task', '--agent-xp'],
|
|
2375
|
-
).join(' ').trim();
|
|
2441
|
+
const input = missionRunInputFromArgs(args);
|
|
2442
|
+
const ref = input.ref;
|
|
2443
|
+
const runArgs = input.args;
|
|
2376
2444
|
|
|
2377
2445
|
if (!dueMode && !ref) {
|
|
2378
2446
|
if (asJson || !process.stdin.isTTY || !process.stderr.isTTY) {
|
|
2379
|
-
missionRunInputRequired(asJson);
|
|
2447
|
+
missionRunInputRequired(asJson, input.owner);
|
|
2380
2448
|
}
|
|
2381
|
-
const prompted = await promptMissionRunInput(
|
|
2449
|
+
const prompted = await promptMissionRunInput(runArgs);
|
|
2382
2450
|
startMissionFromRunObjective(prompted.objective, prompted.args);
|
|
2383
2451
|
return;
|
|
2384
2452
|
}
|
|
@@ -2392,8 +2460,8 @@ async function runMission(args) {
|
|
|
2392
2460
|
);
|
|
2393
2461
|
return;
|
|
2394
2462
|
}
|
|
2395
|
-
if (!mission && ref &&
|
|
2396
|
-
startMissionFromRunObjective(ref,
|
|
2463
|
+
if (!mission && ref && !String(ref).startsWith('mission-')) {
|
|
2464
|
+
startMissionFromRunObjective(ref, runArgs);
|
|
2397
2465
|
return;
|
|
2398
2466
|
}
|
|
2399
2467
|
if (!mission) {
|
|
@@ -3216,9 +3284,10 @@ atris mission - durable goal + loop + owner + proof state
|
|
|
3216
3284
|
atris mission goal [--heartbeat] [--json]
|
|
3217
3285
|
atris mission goal-loop [--max-wall 28800] [--max-iterations 32] [--no-claude] [--json]
|
|
3218
3286
|
atris mission tick <id> [--verify] [--complete-on-pass] [--summary "..."] [--json]
|
|
3219
|
-
atris mission
|
|
3287
|
+
atris mission "<objective>" [--owner <member>] Shortcut for: atris mission run "<objective>"
|
|
3288
|
+
atris mission run ["objective"|<member> ["objective"]|id|--due] [--owner <member>] [--max-ticks 4] [--max-wall 3600] [--cadence "15m"]
|
|
3220
3289
|
[--no-claude] [--no-verify] [--complete-on-pass] [--no-drain] [--json]
|
|
3221
|
-
(bare run prompts
|
|
3290
|
+
(bare/member-only run prompts; one-word fuzzy intent starts a new visible-goal mission; --due runs the saved queue)
|
|
3222
3291
|
(mission-run completions seed the next visible goal: decide and start the next useful mission)
|
|
3223
3292
|
atris mission complete <id> --proof "..."
|
|
3224
3293
|
atris mission stop <id> [--pause] [--reason "..."]
|
|
@@ -3452,6 +3521,7 @@ function missionCommand(args) {
|
|
|
3452
3521
|
case '-h':
|
|
3453
3522
|
return help();
|
|
3454
3523
|
default:
|
|
3524
|
+
if (subcommand && !String(subcommand).startsWith('-')) return runMission(args);
|
|
3455
3525
|
return help();
|
|
3456
3526
|
}
|
|
3457
3527
|
}
|
package/commands/worktree.js
CHANGED
|
@@ -59,7 +59,10 @@ function parseWorktrees(text) {
|
|
|
59
59
|
if (current.worktree) {
|
|
60
60
|
let branch = current.branch || 'detached';
|
|
61
61
|
branch = branch.replace(/^refs\/heads\//, '');
|
|
62
|
-
|
|
62
|
+
const item = { path: current.worktree, branch, head: current.HEAD || '' };
|
|
63
|
+
if (current.locked) item.locked = true;
|
|
64
|
+
if (current.prunable) item.prunable = true;
|
|
65
|
+
out.push(item);
|
|
63
66
|
}
|
|
64
67
|
current = {};
|
|
65
68
|
continue;
|
|
@@ -451,10 +454,98 @@ function prune(args) {
|
|
|
451
454
|
return result.status || 0;
|
|
452
455
|
}
|
|
453
456
|
|
|
457
|
+
const PROTECTED_BRANCHES = new Set(['main', 'master']);
|
|
458
|
+
|
|
459
|
+
function cleanupWorktrees({ root = repoRoot(), base: baseOverride = '', apply = false } = {}) {
|
|
460
|
+
const worktrees = listWorktrees(root);
|
|
461
|
+
const primary = worktrees[0]?.path ? path.resolve(worktrees[0].path) : '';
|
|
462
|
+
const current = path.resolve(root);
|
|
463
|
+
const base = normalizeTargetRef(root, baseOverride || defaultShipTarget(root));
|
|
464
|
+
refreshRemoteRef(root, base);
|
|
465
|
+
const candidates = [];
|
|
466
|
+
const kept = [];
|
|
467
|
+
const removed = [];
|
|
468
|
+
|
|
469
|
+
for (const wt of worktrees) {
|
|
470
|
+
const wtPath = path.resolve(wt.path);
|
|
471
|
+
const item = { path: wt.path, branch: wt.branch, head: wt.head };
|
|
472
|
+
if (wtPath === primary) {
|
|
473
|
+
kept.push({ ...item, reason: 'primary_checkout' });
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
if (wtPath === current) {
|
|
477
|
+
kept.push({ ...item, reason: 'current_checkout' });
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
if (wt.locked) {
|
|
481
|
+
kept.push({ ...item, reason: 'locked' });
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
if (PROTECTED_BRANCHES.has(wt.branch)) {
|
|
485
|
+
kept.push({ ...item, reason: 'protected_branch' });
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
const counts = statusCounts(wt.path);
|
|
489
|
+
if (!counts) {
|
|
490
|
+
kept.push({ ...item, reason: 'missing_or_unreadable' });
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
if (counts.staged || counts.unstaged || counts.untracked) {
|
|
494
|
+
kept.push({ ...item, reason: 'dirty', staged: counts.staged, unstaged: counts.unstaged, untracked: counts.untracked });
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
if (!wt.head) {
|
|
498
|
+
kept.push({ ...item, reason: 'missing_head' });
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
const merged = runGit(['merge-base', '--is-ancestor', wt.head, base], { cwd: root, check: false }).status === 0;
|
|
502
|
+
if (!merged) {
|
|
503
|
+
kept.push({ ...item, reason: 'unmerged' });
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
const candidate = { ...item, reason: 'merged_into_base' };
|
|
507
|
+
candidates.push(candidate);
|
|
508
|
+
if (!apply) continue;
|
|
509
|
+
const removedResult = runGit(['worktree', 'remove', wt.path], { cwd: root, check: false });
|
|
510
|
+
if (removedResult.status === 0) {
|
|
511
|
+
removed.push(candidate);
|
|
512
|
+
} else {
|
|
513
|
+
kept.push({ ...item, reason: 'remove_failed', error: (removedResult.stderr || removedResult.stdout || '').trim() });
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
return { apply, base, candidates, removed, kept };
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function cleanup(args) {
|
|
521
|
+
const asJson = hasFlag(args, '--json');
|
|
522
|
+
const result = cleanupWorktrees({
|
|
523
|
+
root: repoRoot(),
|
|
524
|
+
base: readFlag(args, '--base'),
|
|
525
|
+
apply: hasFlag(args, '--apply'),
|
|
526
|
+
});
|
|
527
|
+
if (asJson) {
|
|
528
|
+
console.log(JSON.stringify({ ok: true, ...result }, null, 2));
|
|
529
|
+
return 0;
|
|
530
|
+
}
|
|
531
|
+
const action = result.apply ? 'removed' : 'candidate';
|
|
532
|
+
console.log(`cleanup: ${result.apply ? 'applied' : 'dry-run'} base=${result.base}`);
|
|
533
|
+
console.log(`${action}s: ${result.apply ? result.removed.length : result.candidates.length}`);
|
|
534
|
+
for (const item of result.apply ? result.removed : result.candidates) {
|
|
535
|
+
console.log(`${action}: ${item.path} branch=${item.branch} reason=${item.reason}`);
|
|
536
|
+
}
|
|
537
|
+
console.log(`kept: ${result.kept.length}`);
|
|
538
|
+
if (!result.apply && result.candidates.length) console.log('next: atris worktree cleanup --apply');
|
|
539
|
+
return 0;
|
|
540
|
+
}
|
|
541
|
+
|
|
454
542
|
function guide() {
|
|
455
543
|
console.log('Atris worktree agent recipe');
|
|
456
544
|
console.log('');
|
|
457
|
-
console.log('
|
|
545
|
+
console.log('Default: stay in the current checkout for small, clean, single-agent fixes.');
|
|
546
|
+
console.log('Use a worktree for dirty launchers, parallel agents, risky edits, releases, or long proof runs.');
|
|
547
|
+
console.log('');
|
|
548
|
+
console.log('1. Start isolated work when needed:');
|
|
458
549
|
console.log(' atris worktree start --member <member> --task "<task>" --claim');
|
|
459
550
|
console.log(' atris worktree start --agent <agent> --task "<task>"');
|
|
460
551
|
console.log('');
|
|
@@ -471,13 +562,17 @@ function guide() {
|
|
|
471
562
|
console.log('4. Ship only from the isolated worktree:');
|
|
472
563
|
console.log(' atris worktree ship --message "<commit>" --verify "<cmd>" --merge');
|
|
473
564
|
console.log('');
|
|
565
|
+
console.log('5. Clean merged worktrees:');
|
|
566
|
+
console.log(' atris worktree cleanup');
|
|
567
|
+
console.log(' atris worktree cleanup --apply');
|
|
568
|
+
console.log('');
|
|
474
569
|
console.log('Notes: start uses the current upstream/default remote base, not dirty local HEAD.');
|
|
475
570
|
console.log('Use `atris worktree status` to see all worktrees and dirty counts.');
|
|
476
571
|
return 0;
|
|
477
572
|
}
|
|
478
573
|
|
|
479
574
|
function help() {
|
|
480
|
-
console.log('Usage: atris worktree <guide|start|ship|status|guard|prune>');
|
|
575
|
+
console.log('Usage: atris worktree <guide|start|ship|status|guard|prune|cleanup>');
|
|
481
576
|
console.log('');
|
|
482
577
|
console.log(' atris worktree guide');
|
|
483
578
|
console.log(' atris worktree start --member <member>|--agent <name> --task "<task>" [--claim]');
|
|
@@ -485,6 +580,7 @@ function help() {
|
|
|
485
580
|
console.log(' atris worktree status');
|
|
486
581
|
console.log(' atris worktree guard [--allow-primary] [--allow-dirty]');
|
|
487
582
|
console.log(' atris worktree prune [--apply]');
|
|
583
|
+
console.log(' atris worktree cleanup [--apply] [--json] [--base origin/master]');
|
|
488
584
|
}
|
|
489
585
|
|
|
490
586
|
function worktreeCommand(args = []) {
|
|
@@ -503,6 +599,7 @@ function worktreeCommand(args = []) {
|
|
|
503
599
|
}
|
|
504
600
|
if (sub === 'guard') return guard(rest);
|
|
505
601
|
if (sub === 'prune') return prune(rest);
|
|
602
|
+
if (sub === 'cleanup' || sub === 'clean') return cleanup(rest);
|
|
506
603
|
help();
|
|
507
604
|
return 2;
|
|
508
605
|
}
|
|
@@ -510,6 +607,7 @@ function worktreeCommand(args = []) {
|
|
|
510
607
|
module.exports = {
|
|
511
608
|
branchName,
|
|
512
609
|
createAgentWorktree,
|
|
610
|
+
cleanupWorktrees,
|
|
513
611
|
defaultShipTarget,
|
|
514
612
|
defaultStartBase,
|
|
515
613
|
defaultWorktreePath,
|