atris 3.15.13 → 3.15.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/AGENTS.md +84 -8
  2. package/README.md +5 -1
  3. package/atris/AGENTS.md +46 -1
  4. package/atris/CLAUDE.md +36 -1
  5. package/atris/GEMINI.md +14 -1
  6. package/atris/atris.md +12 -1
  7. package/atris/atrisDev.md +3 -2
  8. package/atris/context/README.md +11 -0
  9. package/atris/features/company-brain-sync/validate.md +5 -5
  10. package/atris/learnings.jsonl +1 -0
  11. package/atris/policies/atris-design.md +2 -0
  12. package/atris/skills/aeo/SKILL.md +2 -2
  13. package/atris/skills/atris/SKILL.md +15 -62
  14. package/atris/skills/design/SKILL.md +2 -0
  15. package/atris/skills/imessage/SKILL.md +19 -2
  16. package/atris/skills/loop/SKILL.md +6 -5
  17. package/atris/skills/magic-inbox/SKILL.md +1 -1
  18. package/atris/team/_template/MEMBER.md +23 -1
  19. package/atris/team/brainstormer/START_HERE.md +6 -0
  20. package/atris/team/executor/MEMBER.md +13 -0
  21. package/atris/team/executor/START_HERE.md +6 -0
  22. package/atris/team/launcher/START_HERE.md +6 -0
  23. package/atris/team/mission-lead/MEMBER.md +39 -0
  24. package/atris/team/mission-lead/MISSION.md +33 -0
  25. package/atris/team/mission-lead/START_HERE.md +6 -0
  26. package/atris/team/navigator/MEMBER.md +11 -0
  27. package/atris/team/navigator/START_HERE.md +6 -0
  28. package/atris/team/opus-overnight/MEMBER.md +39 -0
  29. package/atris/team/opus-overnight/MISSION.md +61 -0
  30. package/atris/team/opus-overnight/START_HERE.md +6 -0
  31. package/atris/team/opus-overnight/STEERING.md +35 -0
  32. package/atris/team/researcher/START_HERE.md +6 -0
  33. package/atris/team/validator/MEMBER.md +26 -6
  34. package/atris/team/validator/START_HERE.md +6 -0
  35. package/atris/wiki/concepts/agent-activation-contract.md +79 -0
  36. package/atris/wiki/concepts/workspace-initialization-contract.md +73 -0
  37. package/atris/wiki/index.md +27 -0
  38. package/atris/wiki/sources/atris-labs-2026-05-10.txt +17 -0
  39. package/atris/wiki/sources/atris-labs-goals-2026-05-10.txt +15 -0
  40. package/atris/wiki/sources/atrisos-generative-ui-product-surface-2026-05-10.txt +10 -0
  41. package/atris/wiki/sources/jack-dorsey-2026-05-10.txt +12 -0
  42. package/atris.md +49 -13
  43. package/bin/atris.js +660 -22
  44. package/commands/activate.js +12 -3
  45. package/commands/aeo.js +1 -1
  46. package/commands/align.js +10 -10
  47. package/commands/analytics.js +9 -4
  48. package/commands/app.js +2 -0
  49. package/commands/apps.js +276 -0
  50. package/commands/auth.js +1 -1
  51. package/commands/autopilot.js +74 -5
  52. package/commands/brain.js +536 -61
  53. package/commands/brainstorm.js +12 -12
  54. package/commands/business-sync.js +142 -24
  55. package/commands/clean.js +9 -6
  56. package/commands/codex-goal.js +311 -0
  57. package/commands/errors.js +11 -1
  58. package/commands/feedback.js +55 -17
  59. package/commands/fork.js +2 -2
  60. package/commands/gm.js +376 -0
  61. package/commands/init.js +80 -3
  62. package/commands/integrations.js +524 -0
  63. package/commands/learn.js +25 -16
  64. package/commands/lesson.js +41 -0
  65. package/commands/lifecycle.js +2 -2
  66. package/commands/member.js +2416 -9
  67. package/commands/mission.js +1776 -0
  68. package/commands/now.js +48 -7
  69. package/commands/play.js +425 -0
  70. package/commands/publish.js +2 -1
  71. package/commands/pull.js +72 -29
  72. package/commands/push.js +199 -17
  73. package/commands/review.js +51 -13
  74. package/commands/skill.js +2 -2
  75. package/commands/soul.js +19 -13
  76. package/commands/status.js +6 -1
  77. package/commands/sync.js +5 -4
  78. package/commands/task.js +1041 -147
  79. package/commands/terminal.js +5 -5
  80. package/commands/verify.js +7 -5
  81. package/commands/visualize.js +7 -0
  82. package/commands/wiki.js +53 -16
  83. package/commands/workflow.js +298 -54
  84. package/commands/workspace-clean.js +1 -1
  85. package/commands/worktree.js +468 -0
  86. package/commands/xp.js +1608 -0
  87. package/lib/manifest.js +34 -4
  88. package/lib/scorecard.js +3 -2
  89. package/lib/task-db.js +408 -27
  90. package/lib/todo-fallback.js +28 -2
  91. package/lib/todo.js +5 -3
  92. package/package.json +23 -2
  93. package/utils/update-check.js +51 -1
@@ -0,0 +1,468 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { spawnSync } = require('child_process');
6
+
7
+ function runGit(args, { cwd = process.cwd(), check = true } = {}) {
8
+ const result = spawnSync('git', args, { cwd, encoding: 'utf8' });
9
+ if (check && result.status !== 0) {
10
+ const msg = (result.stderr || result.stdout || `git ${args.join(' ')} failed`).trim();
11
+ throw new Error(msg);
12
+ }
13
+ return result;
14
+ }
15
+
16
+ function runCommand(command, { cwd = process.cwd(), check = true } = {}) {
17
+ const result = spawnSync(command, { cwd, encoding: 'utf8', shell: true, stdio: 'pipe' });
18
+ if (check && result.status !== 0) {
19
+ const msg = (result.stderr || result.stdout || `${command} failed`).trim();
20
+ throw new Error(msg);
21
+ }
22
+ return result;
23
+ }
24
+
25
+ function repoRoot(cwd = process.cwd()) {
26
+ return runGit(['rev-parse', '--show-toplevel'], { cwd }).stdout.trim();
27
+ }
28
+
29
+ function slugify(value, fallback = 'task', limit = 48) {
30
+ const slug = String(value || '')
31
+ .trim()
32
+ .toLowerCase()
33
+ .replace(/[^a-z0-9]+/g, '-')
34
+ .replace(/^-+|-+$/g, '')
35
+ .slice(0, limit)
36
+ .replace(/-+$/g, '');
37
+ return slug || fallback;
38
+ }
39
+
40
+ function stamp(now = new Date()) {
41
+ return now.toISOString().replace(/[-:]/g, '').replace(/\.\d+Z$/, '').replace('T', '-');
42
+ }
43
+
44
+ function branchName(member, task, now = new Date()) {
45
+ return `codex/${slugify(member, 'member', 24)}-${slugify(task, 'task', 36)}-${stamp(now)}`;
46
+ }
47
+
48
+ function defaultWorktreePath(root, member, task, now = new Date()) {
49
+ const name = `${slugify(member, 'member', 24)}-${slugify(task, 'task', 36)}-${stamp(now)}`;
50
+ return path.join(path.dirname(root), '.agent-worktrees', path.basename(root), name);
51
+ }
52
+
53
+ function parseWorktrees(text) {
54
+ const out = [];
55
+ let current = {};
56
+ for (const raw of `${text}\n`.split(/\r?\n/)) {
57
+ const line = raw.trim();
58
+ if (!line) {
59
+ if (current.worktree) {
60
+ let branch = current.branch || 'detached';
61
+ branch = branch.replace(/^refs\/heads\//, '');
62
+ out.push({ path: current.worktree, branch, head: current.HEAD || '' });
63
+ }
64
+ current = {};
65
+ continue;
66
+ }
67
+ const idx = line.indexOf(' ');
68
+ if (idx === -1) {
69
+ current[line] = true;
70
+ } else {
71
+ current[line.slice(0, idx)] = line.slice(idx + 1);
72
+ }
73
+ }
74
+ return out;
75
+ }
76
+
77
+ function listWorktrees(root = repoRoot()) {
78
+ return parseWorktrees(runGit(['worktree', 'list', '--porcelain'], { cwd: root }).stdout);
79
+ }
80
+
81
+ function refExists(root, ref) {
82
+ return runGit(['rev-parse', '--verify', `${ref}^{commit}`], { cwd: root, check: false }).status === 0;
83
+ }
84
+
85
+ function currentBranch(root = repoRoot()) {
86
+ return runGit(['branch', '--show-current'], { cwd: root, check: false }).stdout.trim();
87
+ }
88
+
89
+ function currentUpstream(root) {
90
+ const result = runGit(['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}'], { cwd: root, check: false });
91
+ return result.status === 0 ? result.stdout.trim() : '';
92
+ }
93
+
94
+ function remoteHead(root) {
95
+ const result = runGit(['symbolic-ref', '--quiet', '--short', 'refs/remotes/origin/HEAD'], { cwd: root, check: false });
96
+ return result.status === 0 ? result.stdout.trim() : '';
97
+ }
98
+
99
+ function normalizeTargetRef(root, target) {
100
+ const value = String(target || '').trim();
101
+ if (!value) return '';
102
+ const remotes = runGit(['remote'], { cwd: root, check: false }).stdout.split(/\r?\n/);
103
+ const preferRemote =
104
+ !value.startsWith('origin/') &&
105
+ !value.startsWith('refs/') &&
106
+ value !== 'HEAD' &&
107
+ !/^[0-9a-f]{7,40}$/i.test(value) &&
108
+ remotes.includes('origin');
109
+ const remoteRef = value.startsWith('origin/') ? value : `origin/${value}`;
110
+ if (preferRemote && refExists(root, remoteRef)) return remoteRef;
111
+ if (preferRemote) return remoteRef;
112
+ if (refExists(root, value)) return value;
113
+ if (refExists(root, remoteRef)) return remoteRef;
114
+ if (value.startsWith('origin/') || remotes.includes('origin')) return remoteRef;
115
+ return value;
116
+ }
117
+
118
+ function defaultStartBase(root) {
119
+ const upstream = currentUpstream(root);
120
+ if (upstream && refExists(root, upstream)) return upstream;
121
+ const head = remoteHead(root);
122
+ if (head && refExists(root, head)) return head;
123
+ for (const candidate of ['origin/master', 'origin/main']) {
124
+ if (refExists(root, candidate)) return candidate;
125
+ }
126
+ return 'HEAD';
127
+ }
128
+
129
+ function defaultShipTarget(root) {
130
+ const branch = currentBranch(root);
131
+ if (branch) {
132
+ const configured = runGit(['config', '--get', `branch.${branch}.atris-base`], { cwd: root, check: false }).stdout.trim();
133
+ if (configured && refExists(root, configured)) return configured;
134
+ }
135
+ const head = remoteHead(root);
136
+ if (head && refExists(root, head)) return head;
137
+ for (const candidate of ['origin/master', 'origin/main']) {
138
+ if (refExists(root, candidate)) return candidate;
139
+ }
140
+ return 'HEAD';
141
+ }
142
+
143
+ function refreshRemoteRef(root, ref) {
144
+ if (!ref.startsWith('origin/')) return;
145
+ const remoteBranch = ref.slice('origin/'.length);
146
+ runGit(['fetch', 'origin', remoteBranch], { cwd: root, check: false });
147
+ }
148
+
149
+ function baseBranchName(ref) {
150
+ return String(ref || '').replace(/^refs\/heads\//, '').replace(/^origin\//, '');
151
+ }
152
+
153
+ function statusCounts(root) {
154
+ if (!fs.existsSync(root)) return null;
155
+ const result = runGit(['status', '--porcelain'], { cwd: root, check: false });
156
+ if (result.status !== 0) return null;
157
+ let staged = 0;
158
+ let unstaged = 0;
159
+ let untracked = 0;
160
+ for (const line of result.stdout.split(/\r?\n/).filter(Boolean)) {
161
+ if (line.startsWith('??')) {
162
+ untracked += 1;
163
+ continue;
164
+ }
165
+ if (line[0] !== ' ') staged += 1;
166
+ if (line[1] !== ' ') unstaged += 1;
167
+ }
168
+ return { staged, unstaged, untracked };
169
+ }
170
+
171
+ function readFlag(args, name, fallback = '') {
172
+ const prefix = `${name}=`;
173
+ for (let i = 0; i < args.length; i += 1) {
174
+ const arg = String(args[i]);
175
+ if (arg === name && args[i + 1] && !String(args[i + 1]).startsWith('--')) return String(args[i + 1]);
176
+ if (arg.startsWith(prefix)) return arg.slice(prefix.length);
177
+ }
178
+ return fallback;
179
+ }
180
+
181
+ function hasFlag(args, name) {
182
+ return args.includes(name);
183
+ }
184
+
185
+ function swarloClaim(root, { channel, taskKey, content }) {
186
+ const script = path.join(root, 'scripts', 'swarlo.py');
187
+ if (!fs.existsSync(script)) return 'skip: scripts/swarlo.py not found';
188
+ const result = spawnSync('python3', [script, 'claim', channel, taskKey, content], { cwd: root, encoding: 'utf8' });
189
+ const output = `${result.stdout || ''}${result.stderr || ''}`.trim();
190
+ if (result.status === 0) return output || `claimed ${taskKey}`;
191
+ return `skip: swarlo claim failed: ${output || result.status}`;
192
+ }
193
+
194
+ function printStatus() {
195
+ const root = repoRoot();
196
+ const worktrees = listWorktrees(root);
197
+ const primary = worktrees[0]?.path;
198
+ for (const wt of worktrees) {
199
+ const counts = statusCounts(wt.path);
200
+ const tags = [];
201
+ if (wt.path === primary) tags.push('primary');
202
+ if (path.resolve(wt.path) === path.resolve(root)) tags.push('current');
203
+ const tagText = tags.length ? ` [${tags.join(' ')}]` : '';
204
+ if (!counts) {
205
+ console.log(`${wt.path}${tagText} branch=${wt.branch} missing=true`);
206
+ continue;
207
+ }
208
+ console.log(`${wt.path}${tagText} branch=${wt.branch} staged=${counts.staged} unstaged=${counts.unstaged} untracked=${counts.untracked}`);
209
+ }
210
+ }
211
+
212
+ function startWorktree(args) {
213
+ const root = repoRoot();
214
+ const member = readFlag(args, '--member');
215
+ const agent = readFlag(args, '--agent');
216
+ const owner = member || agent;
217
+ const task = readFlag(args, '--task');
218
+ if (!owner || !task) {
219
+ console.error('Usage: atris worktree start --member <member>|--agent <name> --task "<short task>" [--claim]');
220
+ return 2;
221
+ }
222
+ const now = new Date();
223
+ const branch = readFlag(args, '--branch') || branchName(owner, task, now);
224
+ const target = path.resolve(readFlag(args, '--path') || defaultWorktreePath(root, owner, task, now));
225
+ const base = normalizeTargetRef(root, readFlag(args, '--base') || readFlag(args, '--target') || defaultStartBase(root));
226
+ const memberFile = member ? path.join(root, 'atris', 'team', member, 'MEMBER.md') : '';
227
+
228
+ if (fs.existsSync(target)) {
229
+ console.error(`refusing: worktree path already exists: ${target}`);
230
+ return 2;
231
+ }
232
+ if (memberFile && !fs.existsSync(memberFile)) {
233
+ console.error(`warning: no member persona at ${path.relative(root, memberFile)}`);
234
+ }
235
+ fs.mkdirSync(path.dirname(target), { recursive: true });
236
+ refreshRemoteRef(root, base);
237
+ runGit(['worktree', 'add', '-b', branch, target, base], { cwd: root });
238
+ runGit(['config', `branch.${branch}.atris-base`, base], { cwd: target, check: false });
239
+ runGit(['config', `branch.${branch}.atris-owner`, owner], { cwd: target, check: false });
240
+ runGit(['config', `branch.${branch}.atris-task`, task], { cwd: target, check: false });
241
+
242
+ const counts = statusCounts(root);
243
+ if (counts && (counts.staged || counts.unstaged || counts.untracked)) {
244
+ console.log(`note: primary checkout is dirty staged=${counts.staged} unstaged=${counts.unstaged} untracked=${counts.untracked}`);
245
+ }
246
+ console.log(`path: ${target}`);
247
+ console.log(`branch: ${branch}`);
248
+ console.log(`${member ? 'member' : 'agent'}: ${owner}`);
249
+ if (hasFlag(args, '--claim')) {
250
+ const channel = readFlag(args, '--swarlo-channel', 'general');
251
+ const taskKey = readFlag(args, '--swarlo-task-key') || `${slugify(owner, 'agent', 24)}-${slugify(task, 'task', 36)}`;
252
+ const content = readFlag(args, '--swarlo-content') || `${owner} owns ${task} in ${target}`;
253
+ console.log(`swarlo_channel: ${channel}`);
254
+ console.log(`swarlo_claim: ${swarloClaim(target, { channel, taskKey, content })}`);
255
+ }
256
+ console.log(`base: ${base}`);
257
+ console.log(`next: cd ${target}`);
258
+ return 0;
259
+ }
260
+
261
+ function createOrFindPr(root, branch, targetRef, title, dryRun) {
262
+ const targetBranch = baseBranchName(targetRef);
263
+ const existing = spawnSync('gh', ['pr', 'view', '--json', 'number,url', '--jq', '"\\(.number) \\(.url)"'], {
264
+ cwd: root,
265
+ encoding: 'utf8',
266
+ });
267
+ if (existing.status === 0 && existing.stdout.trim()) {
268
+ return existing.stdout.trim();
269
+ }
270
+ const body = [
271
+ 'Automated Atris worktree ship.',
272
+ '',
273
+ `Branch: ${branch}`,
274
+ `Target: ${targetBranch}`,
275
+ ].join('\n');
276
+ if (dryRun) return `dry-run: gh pr create --base ${targetBranch} --head ${branch}`;
277
+ const created = spawnSync(
278
+ 'gh',
279
+ ['pr', 'create', '--base', targetBranch, '--head', branch, '--title', title, '--body', body],
280
+ { cwd: root, encoding: 'utf8' }
281
+ );
282
+ if (created.status !== 0) {
283
+ throw new Error((created.stderr || created.stdout || 'gh pr create failed').trim());
284
+ }
285
+ return created.stdout.trim();
286
+ }
287
+
288
+ function shipWorktree(args) {
289
+ const root = repoRoot();
290
+ const dryRun = hasFlag(args, '--dry-run');
291
+ const noPr = hasFlag(args, '--no-pr');
292
+ const merge = hasFlag(args, '--merge');
293
+ const message = readFlag(args, '--message') || readFlag(args, '-m');
294
+ const verify = readFlag(args, '--verify');
295
+ const branch = currentBranch(root);
296
+ const worktrees = listWorktrees(root);
297
+ const primary = worktrees[0]?.path;
298
+ const targetRef = normalizeTargetRef(root, readFlag(args, '--target') || defaultShipTarget(root));
299
+
300
+ if (!branch || branch === 'master' || branch === 'main') {
301
+ console.error('blocked: ship from a feature worktree branch, not master/main');
302
+ return 2;
303
+ }
304
+ if (path.resolve(root) === path.resolve(primary) && !hasFlag(args, '--allow-primary')) {
305
+ console.error('blocked: ship from an isolated worktree, not the primary checkout');
306
+ return 2;
307
+ }
308
+
309
+ refreshRemoteRef(root, targetRef);
310
+ const counts = statusCounts(root) || { staged: 0, unstaged: 0, untracked: 0 };
311
+ const dirty = counts.staged || counts.unstaged || counts.untracked;
312
+ if (dirty && !message) {
313
+ console.error('blocked: --message is required when there are local changes to commit');
314
+ return 2;
315
+ }
316
+
317
+ if (dirty) {
318
+ console.log(`commit: ${message}`);
319
+ if (!dryRun) {
320
+ runGit(['add', '-A'], { cwd: root });
321
+ runGit(['commit', '-m', message], { cwd: root });
322
+ }
323
+ } else {
324
+ console.log('commit: skipped (no local changes)');
325
+ }
326
+
327
+ if (verify) {
328
+ console.log(`verify: ${verify}`);
329
+ if (!dryRun) runCommand(verify, { cwd: root });
330
+ const afterVerify = dryRun ? { staged: 0, unstaged: 0, untracked: 0 } : statusCounts(root) || { staged: 0, unstaged: 0, untracked: 0 };
331
+ if (!dryRun && (afterVerify.staged || afterVerify.unstaged || afterVerify.untracked)) {
332
+ console.error(
333
+ `blocked: verifier left checkout dirty staged=${afterVerify.staged} ` +
334
+ `unstaged=${afterVerify.unstaged} untracked=${afterVerify.untracked}`
335
+ );
336
+ return 3;
337
+ }
338
+ } else {
339
+ console.log('verify: skipped (no --verify)');
340
+ }
341
+
342
+ const mergeCheck = dryRun
343
+ ? { status: 0, stdout: 'dry-run' }
344
+ : runGit(['merge-tree', '--write-tree', targetRef, 'HEAD'], { cwd: root, check: false });
345
+ if (mergeCheck.status !== 0) {
346
+ console.error(`blocked: branch does not merge cleanly into ${targetRef}`);
347
+ console.error((mergeCheck.stderr || mergeCheck.stdout || '').trim());
348
+ return 3;
349
+ }
350
+ console.log(`merge_check: ${targetRef} clean`);
351
+
352
+ console.log(`push: origin ${branch}`);
353
+ if (!dryRun) runGit(['push', '-u', 'origin', branch], { cwd: root });
354
+
355
+ if (!noPr || merge) {
356
+ const title = message || branch;
357
+ const pr = createOrFindPr(root, branch, targetRef, title, dryRun);
358
+ console.log(`pr: ${pr}`);
359
+ if (merge) {
360
+ console.log('merge: requested');
361
+ if (!dryRun) {
362
+ const merged = spawnSync('gh', ['pr', 'merge', '--merge', '--delete-branch'], { cwd: root, encoding: 'utf8' });
363
+ if (merged.status !== 0) throw new Error((merged.stderr || merged.stdout || 'gh pr merge failed').trim());
364
+ }
365
+ }
366
+ } else {
367
+ console.log('pr: skipped (--no-pr)');
368
+ }
369
+
370
+ console.log('done: worktree shipped');
371
+ return 0;
372
+ }
373
+
374
+ function guard(args) {
375
+ const root = repoRoot();
376
+ const worktrees = listWorktrees(root);
377
+ const primary = worktrees[0]?.path;
378
+ if (path.resolve(root) === path.resolve(primary) && !hasFlag(args, '--allow-primary')) {
379
+ console.error('blocked: this is the primary checkout; create an agent worktree first');
380
+ console.error('run: atris worktree start --member <member>|--agent <name> --task "<short task>" --claim');
381
+ return 2;
382
+ }
383
+ const counts = statusCounts(root);
384
+ if (counts && (counts.staged || counts.unstaged || counts.untracked) && !hasFlag(args, '--allow-dirty')) {
385
+ console.error(`blocked: checkout is dirty staged=${counts.staged} unstaged=${counts.unstaged} untracked=${counts.untracked}`);
386
+ return 3;
387
+ }
388
+ console.log('atris worktree guard: ok');
389
+ return 0;
390
+ }
391
+
392
+ function prune(args) {
393
+ const cmd = ['worktree', 'prune', '--verbose'];
394
+ if (!hasFlag(args, '--apply')) cmd.push('--dry-run');
395
+ const result = runGit(cmd, { cwd: repoRoot(), check: false });
396
+ const output = `${result.stdout || ''}${result.stderr || ''}`.trim();
397
+ console.log(output || (hasFlag(args, '--apply') ? 'no stale worktree registrations removed' : 'no stale worktree registrations found'));
398
+ return result.status || 0;
399
+ }
400
+
401
+ function guide() {
402
+ console.log('Atris worktree agent recipe');
403
+ console.log('');
404
+ console.log('1. Start isolated work:');
405
+ console.log(' atris worktree start --member <member> --task "<task>" --claim');
406
+ console.log(' atris worktree start --agent <agent> --task "<task>"');
407
+ console.log('');
408
+ console.log('2. Move into the printed path:');
409
+ console.log(' cd <printed path>');
410
+ console.log(' atris worktree guard');
411
+ console.log('');
412
+ console.log('3. Tie work to mission/member state when relevant:');
413
+ console.log(' atris mission start "<objective>" --owner <member> --verify "<cmd>"');
414
+ console.log(' atris member goal-from-mission <member>');
415
+ console.log(' atris member tick <member>');
416
+ console.log(' atris mission tick <id> --verify --complete-on-pass');
417
+ console.log('');
418
+ console.log('4. Ship only from the isolated worktree:');
419
+ console.log(' atris worktree ship --message "<commit>" --verify "<cmd>" --merge');
420
+ console.log('');
421
+ console.log('Notes: start uses the current upstream/default remote base, not dirty local HEAD.');
422
+ console.log('Use `atris worktree status` to see all worktrees and dirty counts.');
423
+ return 0;
424
+ }
425
+
426
+ function help() {
427
+ console.log('Usage: atris worktree <guide|start|ship|status|guard|prune>');
428
+ console.log('');
429
+ console.log(' atris worktree guide');
430
+ console.log(' atris worktree start --member <member>|--agent <name> --task "<task>" [--claim]');
431
+ console.log(' atris worktree ship --message "<commit>" --verify "<cmd>" [--merge]');
432
+ console.log(' atris worktree status');
433
+ console.log(' atris worktree guard [--allow-primary] [--allow-dirty]');
434
+ console.log(' atris worktree prune [--apply]');
435
+ }
436
+
437
+ function worktreeCommand(args = []) {
438
+ const sub = args[0] || 'status';
439
+ const rest = args.slice(1);
440
+ if (sub === '--help' || sub === '-h' || sub === 'help') {
441
+ help();
442
+ return 0;
443
+ }
444
+ if (sub === 'guide' || sub === 'recipe') return guide();
445
+ if (sub === 'start') return startWorktree(rest);
446
+ if (sub === 'ship') return shipWorktree(rest);
447
+ if (sub === 'status') {
448
+ printStatus();
449
+ return 0;
450
+ }
451
+ if (sub === 'guard') return guard(rest);
452
+ if (sub === 'prune') return prune(rest);
453
+ help();
454
+ return 2;
455
+ }
456
+
457
+ module.exports = {
458
+ branchName,
459
+ defaultShipTarget,
460
+ defaultStartBase,
461
+ defaultWorktreePath,
462
+ parseWorktrees,
463
+ normalizeTargetRef,
464
+ slugify,
465
+ statusCounts,
466
+ swarloClaim,
467
+ worktreeCommand,
468
+ };