atris 3.68.9 → 3.70.0
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/README.md +53 -0
- package/atris/team/opus-overnight/MEMBER.md +1 -0
- package/commands/init.js +9 -0
- package/commands/member.js +436 -40
- package/commands/mission.js +158 -12
- package/commands/sync.js +13 -0
- package/commands/task.js +63 -1
- package/commands/worktree.js +42 -5
- package/lib/auto-accept-certified.js +1 -0
- package/lib/engine-ask.js +2 -1
- package/lib/engine-registry.js +5 -4
- package/lib/engine-validate.js +189 -9
- package/lib/gitignore.js +56 -0
- package/lib/member-autoland.js +442 -0
- package/lib/member-board.js +257 -29
- package/lib/member-progress.js +68 -4
- package/lib/member-report.js +314 -0
- package/lib/member-scaffold.js +1 -2
- package/lib/member-switches.js +7 -0
- package/lib/member-task-pick.js +116 -0
- package/lib/member-tidy.js +235 -0
- package/lib/member-up.js +286 -0
- package/lib/member-verbs.js +84 -0
- package/lib/runner-command.js +29 -0
- package/lib/task-db.js +21 -1
- package/package.json +1 -1
package/commands/member.js
CHANGED
|
@@ -51,8 +51,7 @@ function ensureMemberLog(memberDir, { name, role, description, source = 'cli' }
|
|
|
51
51
|
description ? `- mission: ${description}` : '',
|
|
52
52
|
`- source: ${source}`,
|
|
53
53
|
'- status: ready_for_room',
|
|
54
|
-
|
|
55
|
-
].filter(Boolean).join('\n');
|
|
54
|
+
].filter(Boolean).join('\n') + '\n';
|
|
56
55
|
fs.writeFileSync(logPath, content, 'utf8');
|
|
57
56
|
return logPath;
|
|
58
57
|
}
|
|
@@ -915,6 +914,17 @@ function memberRunBudgetSeconds(args = []) {
|
|
|
915
914
|
return null;
|
|
916
915
|
}
|
|
917
916
|
|
|
917
|
+
// A job with no time given gets 15 minutes, or the roster's limit for this
|
|
918
|
+
// member's engine plus ten minutes for the review, whichever is longer.
|
|
919
|
+
function memberRunDefaultWallSeconds(name, cwd = process.cwd()) {
|
|
920
|
+
let cap = 0;
|
|
921
|
+
try {
|
|
922
|
+
const picked = require('../lib/member-engine').memberRosterEngine(name, cwd);
|
|
923
|
+
cap = Number(picked && picked.engine && picked.engine.roster_max_seconds) || 0;
|
|
924
|
+
} catch {}
|
|
925
|
+
return Math.max(900, cap ? cap + 600 : 0);
|
|
926
|
+
}
|
|
927
|
+
|
|
918
928
|
function pushFlagValue(out, args, name) {
|
|
919
929
|
const value = readFlag(args, name, '');
|
|
920
930
|
if (value) out.push(name, String(value));
|
|
@@ -1017,6 +1027,12 @@ function startMemberRunMission(name, missionText, args = []) {
|
|
|
1017
1027
|
return memberRunCli(startArgs, process.cwd());
|
|
1018
1028
|
}
|
|
1019
1029
|
|
|
1030
|
+
// An ignited run asks a second engine to review the work by default, so every
|
|
1031
|
+
// run ends with a verdict. --no-referee skips it; an explicit --referee is kept.
|
|
1032
|
+
function memberRunWantsReferee(runArgs, ignite) {
|
|
1033
|
+
return Boolean(ignite) && !hasFlag(runArgs, '--referee') && !hasFlag(runArgs, '--no-referee');
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1020
1036
|
function memberRunCli(args, cwd, { live = false, missionId = '' } = {}) {
|
|
1021
1037
|
const cliPath = path.join(__dirname, '..', 'bin', 'atris.js');
|
|
1022
1038
|
const runsDir = path.join(cwd, 'atris', 'runs');
|
|
@@ -1034,7 +1050,7 @@ function memberRunCli(args, cwd, { live = false, missionId = '' } = {}) {
|
|
|
1034
1050
|
: null;
|
|
1035
1051
|
const receipt = receiptName ? readJson(path.join(runsDir, receiptName), null) : null;
|
|
1036
1052
|
return { status: result.status, payload: receipt?.result?.kind === 'mission_run_summary'
|
|
1037
|
-
? { ran_ticks: receipt.result.ran_ticks } : null, stderr: '', errorReported: result.status !== 0 };
|
|
1053
|
+
? { ran_ticks: receipt.result.ran_ticks, ticks: receipt.result.ticks } : null, stderr: '', errorReported: result.status !== 0 };
|
|
1038
1054
|
}
|
|
1039
1055
|
let payload = null;
|
|
1040
1056
|
try {
|
|
@@ -1229,9 +1245,19 @@ function memberRun(name, ...args) {
|
|
|
1229
1245
|
return memberRunWith(name, args);
|
|
1230
1246
|
}
|
|
1231
1247
|
|
|
1248
|
+
// One plain line for why the reviewing engine refused the work.
|
|
1249
|
+
function reviewRefusalLine(refusal) {
|
|
1250
|
+
if (refusal.timed_out) return 'the reviewer ran out of time';
|
|
1251
|
+
const text = String(refusal.output || refusal.reason || '');
|
|
1252
|
+
const verdict = text.match(/VERDICT:\s*FAIL\s*[;:]\s*(.+)/i);
|
|
1253
|
+
const line = verdict ? verdict[1] : (text.split(/\r?\n/).find((l) => l.trim()) || refusal.reason || 'no reason given');
|
|
1254
|
+
return line.trim().slice(0, 200);
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1232
1257
|
function memberRunWith(name, args, { quiet = false } = {}) {
|
|
1233
1258
|
if (!name || name === '--help' || name === '-h' || hasFlag(args, '--help') || hasFlag(args, '-h')) {
|
|
1234
1259
|
console.log('usage: atris member run <name> ["mission text"] [--verify "<cmd>"] [--minutes N|--hours N] [--json]');
|
|
1260
|
+
console.log('pick for me: atris member run "fix the flaky login test" (the best member takes it and runs it)');
|
|
1235
1261
|
console.log('new mission: atris member run growth "improve onboarding proof" --verify "npm test" --minutes 30 --json');
|
|
1236
1262
|
console.log('choose work: atris member run growth --verify "npm test" --industry logistics --minutes 30 --json');
|
|
1237
1263
|
console.log('subagent shell: atris member run growth "improve onboarding proof" --engine claude --ignite --json');
|
|
@@ -1240,6 +1266,7 @@ function memberRunWith(name, args, { quiet = false } = {}) {
|
|
|
1240
1266
|
console.log('truth: useful work must improve revenue, reliability, speed, security, clarity, or trust, then show plain proof.');
|
|
1241
1267
|
console.log('isolation: new missions use --worktree by default; add --shared-checkout to stay here.');
|
|
1242
1268
|
console.log('check: --verify or a verify field in the member file adds a mission check.');
|
|
1269
|
+
console.log('review: an --ignite run has a different engine review the work; add --no-referee to skip it.');
|
|
1243
1270
|
return;
|
|
1244
1271
|
}
|
|
1245
1272
|
|
|
@@ -1305,6 +1332,7 @@ function memberRunWith(name, args, { quiet = false } = {}) {
|
|
|
1305
1332
|
const progressDir = process.env.ATRIS_PROGRESS_DIR || '~/.atris/progress';
|
|
1306
1333
|
console.log(`watch: tail -f ${progressDir}/${String(name).replace(/[^A-Za-z0-9._-]/g, '_')}.log`);
|
|
1307
1334
|
console.log('board: atris member board --watch');
|
|
1335
|
+
console.log('stop: atris member stop <name>');
|
|
1308
1336
|
}
|
|
1309
1337
|
|
|
1310
1338
|
const cwd = mission.worktree?.path || process.cwd();
|
|
@@ -1324,11 +1352,12 @@ function memberRunWith(name, args, { quiet = false } = {}) {
|
|
|
1324
1352
|
['--mission', '--mission-id', '--minutes', '--hours', '--industry', '--domain', '--value', '--outcome', '--truth', '--proof', '--base'],
|
|
1325
1353
|
['--worktree', '--shared-checkout', '--no-worktree', '--choose-work', '--auto', '--ignite', '--wait', '--sync']);
|
|
1326
1354
|
if (ignite && !hasFlag(runArgs, '--complete-on-pass')) runArgs.push('--complete-on-pass');
|
|
1355
|
+
if (memberRunWantsReferee(runArgs, ignite)) runArgs.push('--referee');
|
|
1327
1356
|
const budgetSeconds = memberRunBudgetSeconds(args);
|
|
1328
1357
|
if (!readFlag(runArgs, '--max-ticks', '')) {
|
|
1329
1358
|
runArgs.push('--max-ticks', budgetSeconds ? String(Math.max(4, Math.ceil(budgetSeconds / 300))) : '1');
|
|
1330
1359
|
}
|
|
1331
|
-
if (!readFlag(runArgs, '--max-wall', '')) runArgs.push('--max-wall', String(budgetSeconds ||
|
|
1360
|
+
if (!readFlag(runArgs, '--max-wall', '')) runArgs.push('--max-wall', String(budgetSeconds || memberRunDefaultWallSeconds(name, cwd)));
|
|
1332
1361
|
const caller = callerRuntime(args);
|
|
1333
1362
|
const runnerSwap = memberRunRunnerSwap(mission, args, caller);
|
|
1334
1363
|
if (runnerSwap) runArgs.push('--engine', runnerSwap.to);
|
|
@@ -1341,7 +1370,12 @@ function memberRunWith(name, args, { quiet = false } = {}) {
|
|
|
1341
1370
|
const waitingOnNativeGoal = caller === 'codex' && payload.requires_native_goal_start === true
|
|
1342
1371
|
&& memberRunUsesCallerSession(finalMission.runner);
|
|
1343
1372
|
const noWorkStepExpected = hasFlag(runArgs, '--detach') || hasFlag(runArgs, '--dry-run');
|
|
1344
|
-
|
|
1373
|
+
// The work ran but its check failed: the run failed, even though a work
|
|
1374
|
+
// step happened. Scripts and the caller read this from the exit code.
|
|
1375
|
+
const lastRanTick = [...(Array.isArray(payload.ticks) ? payload.ticks : [])].reverse().find((tick) => tick && tick.ran === true);
|
|
1376
|
+
const checkFailed = started && Boolean(String(finalMission.verifier || '').trim())
|
|
1377
|
+
&& lastRanTick?.verifier_passed === false && finalMission.status !== 'complete';
|
|
1378
|
+
const ok = ((run.status === 0 && payload.ok !== false && (started || noWorkStepExpected)) || waitingOnNativeGoal) && !checkFailed;
|
|
1345
1379
|
const reviewAsk = memberRunReviewAsk(name, payload, finalMission, cwd);
|
|
1346
1380
|
const result = { ...payload, ok, started, state: finalMission.status, mission_id: mission.id,
|
|
1347
1381
|
mission: finalMission, node_modules_linked: nodeModulesLinked, node_modules_source: nodeModulesSource,
|
|
@@ -1365,7 +1399,23 @@ function memberRunWith(name, args, { quiet = false } = {}) {
|
|
|
1365
1399
|
result.ask = `${name} is waiting on you: ${payload.human_asks[0]}`;
|
|
1366
1400
|
result.next_command = payload.next_command || 'atris decide';
|
|
1367
1401
|
}
|
|
1368
|
-
|
|
1402
|
+
// A finished ignited run whose check passed hands its branch to GitHub
|
|
1403
|
+
// auto-merge, only past the local gates; any held gate leaves the branch.
|
|
1404
|
+
if (ignite && ok && started && !reviewAsk && finalMission.verifier_result?.passed === true) {
|
|
1405
|
+
const ticks = Array.isArray(payload.ticks) ? payload.ticks : [];
|
|
1406
|
+
const summary = ticks.map((tick) => tick?.summary || tick?.claude?.summary).filter(Boolean).pop() || '';
|
|
1407
|
+
const workerEngine = ticks.map((tick) => tick?.engine_id).filter(Boolean).pop()
|
|
1408
|
+
|| runnerSwap?.to || readFlag(args, '--runner', '') || '';
|
|
1409
|
+
result.autoland = require('../lib/member-autoland').autolandMemberRun({ mission: finalMission, cwd, summary, workerEngine });
|
|
1410
|
+
if (!asJson && !quiet) console.log(result.autoland.line);
|
|
1411
|
+
}
|
|
1412
|
+
if (checkFailed) {
|
|
1413
|
+
result.check_failed = true;
|
|
1414
|
+
const refusal = lastRanTick?.referee_result?.passed === false ? lastRanTick.referee_result : null;
|
|
1415
|
+
result.error = refusal
|
|
1416
|
+
? `turned down in review: ${reviewRefusalLine(refusal)}`
|
|
1417
|
+
: `the work ran but its check failed: ${finalMission.verifier || 'completion check'}`;
|
|
1418
|
+
} else if (!ok) {
|
|
1369
1419
|
result.error = result.ask || payload.detail || payload.error || payload.reason || payload.next_action
|
|
1370
1420
|
|| run.stderr || 'mission did not run a work step';
|
|
1371
1421
|
}
|
|
@@ -4236,7 +4286,10 @@ function parseFrontmatter(content) {
|
|
|
4236
4286
|
} else if (val.startsWith('[') && val.endsWith(']')) {
|
|
4237
4287
|
result[currentKey] = val.slice(1, -1).split(',').map(s => s.trim().replace(/^["']|["']$/g, ''));
|
|
4238
4288
|
} else {
|
|
4239
|
-
|
|
4289
|
+
// Unwrap only a matching outer pair: `verify: node -e "process.exit(0)"`
|
|
4290
|
+
// must keep its closing quote or the check is a shell syntax error.
|
|
4291
|
+
const quoted = val.match(/^(["'])([\s\S]*)\1$/);
|
|
4292
|
+
result[currentKey] = quoted ? quoted[2] : val;
|
|
4240
4293
|
}
|
|
4241
4294
|
}
|
|
4242
4295
|
}
|
|
@@ -9796,11 +9849,35 @@ const IDEA_ARCHETYPE_RULES = [
|
|
|
9796
9849
|
},
|
|
9797
9850
|
];
|
|
9798
9851
|
|
|
9799
|
-
//
|
|
9800
|
-
//
|
|
9801
|
-
const
|
|
9802
|
-
|
|
9803
|
-
|
|
9852
|
+
// Keep specialized idea rules, then fall back to the same job inference used
|
|
9853
|
+
// by the engine roster. Renamed members get the same job score as built-ins.
|
|
9854
|
+
const ARCHETYPE_FOR_JOB = { build: 'executor', review: 'validator', search: 'researcher' };
|
|
9855
|
+
const EXPERIMENT_STATUSES = new Set(['experimental', 'one-off']);
|
|
9856
|
+
|
|
9857
|
+
function ideaRuleFor(slug, record, text) {
|
|
9858
|
+
const own = IDEA_ARCHETYPE_RULES.find((rule) => rule.slugs.includes(slug));
|
|
9859
|
+
if (own && own.pattern.test(text)) return own;
|
|
9860
|
+
const { autoJobForMember } = require('../lib/member-engine');
|
|
9861
|
+
const archetype = ARCHETYPE_FOR_JOB[autoJobForMember(slug, record.role)];
|
|
9862
|
+
return IDEA_ARCHETYPE_RULES.find((rule) => rule.slugs.includes(archetype)) || null;
|
|
9863
|
+
}
|
|
9864
|
+
|
|
9865
|
+
// 0 = normal, 1 = experiment or one-off, 2 = parked, human, or odd-typed.
|
|
9866
|
+
function dispatchTier(record) {
|
|
9867
|
+
const frontmatter = record.frontmatter || {};
|
|
9868
|
+
const status = String(frontmatter.status || '').trim().toLowerCase();
|
|
9869
|
+
if (status === 'parked' || memberRefusalReason(memberSlugFromRecord(record), frontmatter)) return 2;
|
|
9870
|
+
if (EXPERIMENT_STATUSES.has(status)) return 1;
|
|
9871
|
+
return 0;
|
|
9872
|
+
}
|
|
9873
|
+
|
|
9874
|
+
// Lower tier wins first, then higher score, then name order for a stable pick.
|
|
9875
|
+
function outranksDispatch(candidate, best) {
|
|
9876
|
+
if (!best) return true;
|
|
9877
|
+
if (candidate.tier !== best.tier) return candidate.tier < best.tier;
|
|
9878
|
+
if (candidate.score !== best.score) return candidate.score > best.score;
|
|
9879
|
+
return candidate.member.localeCompare(best.member) < 0;
|
|
9880
|
+
}
|
|
9804
9881
|
|
|
9805
9882
|
function ideaNamesMember(text, slug) {
|
|
9806
9883
|
const escaped = slug.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/-/g, '[-\\s]+');
|
|
@@ -9835,15 +9912,17 @@ function pickMemberForIdea(ideaText, root = process.cwd(), preferredMember = '')
|
|
|
9835
9912
|
let best = null;
|
|
9836
9913
|
for (const record of members) {
|
|
9837
9914
|
const slug = memberSlugFromRecord(record);
|
|
9915
|
+
const named = ideaNamesMember(text, slug);
|
|
9916
|
+
// Naming an experiment in the idea is the owner's pick, so it ranks normally.
|
|
9917
|
+
const baseTier = dispatchTier(record);
|
|
9918
|
+
const tier = named && baseTier === 1 ? 0 : baseTier;
|
|
9838
9919
|
let score = 0;
|
|
9839
9920
|
let matchedReason = '';
|
|
9840
9921
|
|
|
9841
|
-
|
|
9842
|
-
|
|
9843
|
-
|
|
9844
|
-
|
|
9845
|
-
break;
|
|
9846
|
-
}
|
|
9922
|
+
const rule = ideaRuleFor(slug, record, text);
|
|
9923
|
+
if (rule && rule.pattern.test(text)) {
|
|
9924
|
+
score += 30;
|
|
9925
|
+
matchedReason = rule.reason;
|
|
9847
9926
|
}
|
|
9848
9927
|
|
|
9849
9928
|
const missionFile = path.join(teamDir, slug, 'MISSION.md');
|
|
@@ -9856,14 +9935,10 @@ function pickMemberForIdea(ideaText, root = process.cwd(), preferredMember = '')
|
|
|
9856
9935
|
}
|
|
9857
9936
|
score += overlapCount * 4;
|
|
9858
9937
|
if (Number(record.skillCount || 0) > 0) score += 2;
|
|
9859
|
-
if (slug === 'executor') score += 3;
|
|
9860
|
-
if (slug === 'auto-improver') score += 2;
|
|
9861
9938
|
|
|
9862
|
-
if (
|
|
9939
|
+
if (named) {
|
|
9863
9940
|
score += 40;
|
|
9864
9941
|
matchedReason = 'named in the idea';
|
|
9865
|
-
} else if (!CORE_DISPATCH_MEMBERS.has(slug)) {
|
|
9866
|
-
score = Math.floor(score / 2);
|
|
9867
9942
|
}
|
|
9868
9943
|
|
|
9869
9944
|
if (!matchedReason) {
|
|
@@ -9877,10 +9952,9 @@ function pickMemberForIdea(ideaText, root = process.cwd(), preferredMember = '')
|
|
|
9877
9952
|
role: record.role || 'Team Member',
|
|
9878
9953
|
reason: matchedReason,
|
|
9879
9954
|
score,
|
|
9955
|
+
tier,
|
|
9880
9956
|
};
|
|
9881
|
-
if (
|
|
9882
|
-
best = candidate;
|
|
9883
|
-
}
|
|
9957
|
+
if (outranksDispatch(candidate, best)) best = candidate;
|
|
9884
9958
|
}
|
|
9885
9959
|
|
|
9886
9960
|
return best;
|
|
@@ -9938,7 +10012,7 @@ function memberDispatch(...args) {
|
|
|
9938
10012
|
printJsonOrText(
|
|
9939
10013
|
payload,
|
|
9940
10014
|
[
|
|
9941
|
-
`${picked.member} (${picked.role}) will take this
|
|
10015
|
+
`${picked.member} (${picked.role}) will take this: ${picked.reason.replace(/\.$/, '')}.`,
|
|
9942
10016
|
'',
|
|
9943
10017
|
commandPreview,
|
|
9944
10018
|
],
|
|
@@ -9948,7 +10022,7 @@ function memberDispatch(...args) {
|
|
|
9948
10022
|
}
|
|
9949
10023
|
|
|
9950
10024
|
if (!asJson) {
|
|
9951
|
-
console.log(`${picked.member} (${picked.role}) is taking this
|
|
10025
|
+
console.log(`${picked.member} (${picked.role}) is taking this: ${picked.reason.replace(/\.$/, '')}.`);
|
|
9952
10026
|
}
|
|
9953
10027
|
// --ignite means do the work now, the same as the previewed member run;
|
|
9954
10028
|
// without it dispatch only opens the mission.
|
|
@@ -10371,7 +10445,101 @@ function isKnownMemberSlug(token, root = process.cwd()) {
|
|
|
10371
10445
|
|| fs.existsSync(path.join(root, 'atris', 'team', `${clean}.md`));
|
|
10372
10446
|
}
|
|
10373
10447
|
|
|
10448
|
+
// The job a member does (build, review or search), read from the same team
|
|
10449
|
+
// roster that picks its engine.
|
|
10450
|
+
function memberGoJob(owner, root) {
|
|
10451
|
+
const byRole = { navigator: 'search', executor: 'build', validator: 'review' };
|
|
10452
|
+
try {
|
|
10453
|
+
const seat = require('../lib/member-engine').resolveEngineForMember(owner, root);
|
|
10454
|
+
return byRole[seat.role] || byRole[seat.job_key] || 'build';
|
|
10455
|
+
} catch {
|
|
10456
|
+
return 'build';
|
|
10457
|
+
}
|
|
10458
|
+
}
|
|
10459
|
+
|
|
10460
|
+
// With no idea given, member go takes the next real job from the task list:
|
|
10461
|
+
// the oldest open task that fits the member's job. A dry run only looks.
|
|
10462
|
+
// requireCheck: only tasks with a runnable check count.
|
|
10463
|
+
function memberGoTask(owner, root, { claim, requireCheck = false }) {
|
|
10464
|
+
try {
|
|
10465
|
+
const taskDb = require('../lib/task-db');
|
|
10466
|
+
const picker = require('../lib/member-task-pick');
|
|
10467
|
+
const db = taskDb.open();
|
|
10468
|
+
const job = memberGoJob(owner, root);
|
|
10469
|
+
const workspaceRoot = taskDb.workspaceRoot(root);
|
|
10470
|
+
if (claim) {
|
|
10471
|
+
const got = picker.claimTaskForMember(taskDb, db, { workspaceRoot, member: owner, job, requireCheck });
|
|
10472
|
+
return got ? { ...got, job } : null;
|
|
10473
|
+
}
|
|
10474
|
+
const task = picker.pickTaskForMember(taskDb, db, { workspaceRoot, job, requireCheck });
|
|
10475
|
+
return task ? { task, objective: picker.taskObjective(task), job } : null;
|
|
10476
|
+
} catch (error) {
|
|
10477
|
+
console.error(`could not take a task from the list: ${String(error && error.message || error).split('\n')[0]}`);
|
|
10478
|
+
return null;
|
|
10479
|
+
}
|
|
10480
|
+
}
|
|
10481
|
+
|
|
10482
|
+
// A pulled task leaves the run in review when the run's check passed (the
|
|
10483
|
+
// mission is complete, or ready: checked and waiting on a person), else back
|
|
10484
|
+
// on the list with a one-line reason.
|
|
10485
|
+
function memberGoFinishTask(owner, task, result, thrown = null) {
|
|
10486
|
+
const taskDb = require('../lib/task-db');
|
|
10487
|
+
const db = taskDb.open();
|
|
10488
|
+
const mission = (result && result.mission) || {};
|
|
10489
|
+
const receipt = (result && result.work_step && result.work_step.receipt_path) || mission.receipt_path || '';
|
|
10490
|
+
const required = require('../lib/member-task-pick').taskCheck(task).command;
|
|
10491
|
+
const checked = mission.verifier_result && mission.verifier_result.passed === true
|
|
10492
|
+
&& (!required || String(mission.verifier_result.command || '').trim() === required);
|
|
10493
|
+
if (!thrown && result && result.started && ['complete', 'ready'].includes(mission.status) && checked) {
|
|
10494
|
+
const proof = [`[verified] ${owner} ran mission ${result.mission_id} and its check passed.`,
|
|
10495
|
+
receipt ? `Receipt: ${receipt}` : ''].filter(Boolean).join('\n');
|
|
10496
|
+
const ready = taskDb.readyTask(db, { id: task.id, actor: owner, proof });
|
|
10497
|
+
if (ready.ready !== false) return { id: task.id, title: task.title, outcome: 'review', receipt: receipt || null };
|
|
10498
|
+
}
|
|
10499
|
+
const ticks = Array.isArray(result && result.ticks) ? result.ticks : [];
|
|
10500
|
+
const lastReason = String((ticks[ticks.length - 1] || {}).reason || (ticks[ticks.length - 1] || {}).error || '');
|
|
10501
|
+
const outOfTime = /timeout|wall-exceeded/.test(lastReason);
|
|
10502
|
+
const why = thrown ? thrown.message
|
|
10503
|
+
: outOfTime ? 'ran out of time before the work finished'
|
|
10504
|
+
: (result && result.error) || (result && result.started ? 'the check did not pass' : 'the run did not start');
|
|
10505
|
+
const reason = String(why || 'the run did not finish').split(/\r?\n/)[0].trim().slice(0, 200);
|
|
10506
|
+
try { taskDb.noteTask(db, { id: task.id, actor: owner, content: `Released by ${owner}: ${reason}` }); } catch {}
|
|
10507
|
+
// Third failure in a row: hold the task for a person instead of letting
|
|
10508
|
+
// members pick it up again forever.
|
|
10509
|
+
let held = false;
|
|
10510
|
+
if (memberGoFailStreak(taskDb, db, task.id) >= MEMBER_GO_STRIKES) {
|
|
10511
|
+
try {
|
|
10512
|
+
held = taskDb.tagTask(db, { id: task.id, actor: owner, add: ['needs-human'] }).tagged === true;
|
|
10513
|
+
if (held) taskDb.noteTask(db, { id: task.id, actor: owner,
|
|
10514
|
+
content: `Held for a person: failed ${MEMBER_GO_STRIKES} times in a row. Last reason: ${reason}` });
|
|
10515
|
+
} catch {}
|
|
10516
|
+
}
|
|
10517
|
+
const released = taskDb.releaseTask(db, { id: task.id, actor: owner });
|
|
10518
|
+
const outcome = released.released ? (held ? 'needs_human' : 'released') : 'left_claimed';
|
|
10519
|
+
return { id: task.id, title: task.title, outcome, reason };
|
|
10520
|
+
}
|
|
10521
|
+
|
|
10522
|
+
const MEMBER_GO_STRIKES = 3;
|
|
10523
|
+
|
|
10524
|
+
// Failed runs on a task since its last pass, review, reopen, or a person
|
|
10525
|
+
// clearing its hold.
|
|
10526
|
+
function memberGoFailStreak(taskDb, db, taskId) {
|
|
10527
|
+
let streak = 0;
|
|
10528
|
+
for (const event of taskDb.listTaskEvents(db, { taskId })) {
|
|
10529
|
+
const type = event.event_type;
|
|
10530
|
+
const payload = event.payload || {};
|
|
10531
|
+
if (type === 'message' && /^Released by \S+: /.test(String(payload.content || ''))) streak += 1;
|
|
10532
|
+
else if (['proof_ready', 'reviewed', 'reopened'].includes(type)) streak = 0;
|
|
10533
|
+
else if (type === 'task_tags_updated' && (payload.removed || []).includes('needs-human')) streak = 0;
|
|
10534
|
+
}
|
|
10535
|
+
return streak;
|
|
10536
|
+
}
|
|
10537
|
+
|
|
10374
10538
|
async function memberSteerOrDispatch(...rawArgs) {
|
|
10539
|
+
return memberSteerOrDispatchWith({}, rawArgs);
|
|
10540
|
+
}
|
|
10541
|
+
|
|
10542
|
+
async function memberSteerOrDispatchWith({ pullTask = false } = {}, rawArgs = []) {
|
|
10375
10543
|
if (hasFlag(rawArgs, '--help') || hasFlag(rawArgs, '-h')) {
|
|
10376
10544
|
console.log('Usage: atris member steer [member] "<direction>" [--dry-run] [--json]');
|
|
10377
10545
|
console.log(' atris member "<direction>" [--dry-run] [--json]');
|
|
@@ -10381,6 +10549,9 @@ async function memberSteerOrDispatch(...rawArgs) {
|
|
|
10381
10549
|
const root = process.cwd();
|
|
10382
10550
|
const asJson = hasFlag(rawArgs, '--json');
|
|
10383
10551
|
const dryRun = hasFlag(rawArgs, '--dry-run');
|
|
10552
|
+
// Set by `member up`: run only a claimed task with a runnable check, never
|
|
10553
|
+
// the member's own goal text.
|
|
10554
|
+
const needsCheck = pullTask && hasFlag(rawArgs, '--needs-check');
|
|
10384
10555
|
const valueFlags = new Set(['--engine', '--model', '--verify', '--referee-engine', '--minutes', '--hours', '--max-ticks', '--from']);
|
|
10385
10556
|
const positionals = [];
|
|
10386
10557
|
const flagArgs = [];
|
|
@@ -10388,6 +10559,7 @@ async function memberSteerOrDispatch(...rawArgs) {
|
|
|
10388
10559
|
const token = String(rawArgs[i] || '');
|
|
10389
10560
|
if (!token) continue;
|
|
10390
10561
|
if (token.startsWith('-')) {
|
|
10562
|
+
if (token === '--needs-check') continue;
|
|
10391
10563
|
flagArgs.push(token);
|
|
10392
10564
|
if (valueFlags.has(token) && i + 1 < rawArgs.length) {
|
|
10393
10565
|
flagArgs.push(rawArgs[i + 1]);
|
|
@@ -10419,18 +10591,31 @@ async function memberSteerOrDispatch(...rawArgs) {
|
|
|
10419
10591
|
fm = parseFrontmatter(fs.readFileSync(paths.memberFile, 'utf8')) || {};
|
|
10420
10592
|
} catch {}
|
|
10421
10593
|
const goalPlane = memberGoalPlaneStatus(owner, paths);
|
|
10594
|
+
const missionMap = memberRunMissionMap(root);
|
|
10595
|
+
const missionSummary = memberMissionSummary(owner, missionMap, root);
|
|
10596
|
+
// A human or odd-typed member never takes a task: it would be claimed and
|
|
10597
|
+
// then stranded when the run refuses the member.
|
|
10598
|
+
const pulled = pullTask && !prompt && !missionSummary.live && !memberRefusalFor(targetMember, paths.memberFile)
|
|
10599
|
+
? memberGoTask(owner, root, { claim: !dryRun, requireCheck: needsCheck })
|
|
10600
|
+
: null;
|
|
10601
|
+
if (needsCheck && !pulled) {
|
|
10602
|
+
const payload = { ok: true, action: 'member_go', mode: 'idle', member: owner, reason: 'no task with a check' };
|
|
10603
|
+
printJsonOrText(payload, [`${owner} is idle, no task with a check`], asJson);
|
|
10604
|
+
process.exitCode = require('../lib/member-up').IDLE_EXIT;
|
|
10605
|
+
return payload;
|
|
10606
|
+
}
|
|
10422
10607
|
const effectivePrompt = prompt
|
|
10608
|
+
|| pulled?.objective
|
|
10423
10609
|
|| goalPlane.goal?.next_push
|
|
10424
10610
|
|| goalPlane.goal?.title
|
|
10425
10611
|
|| extractMemberNorthStar(paths.memberDir, '');
|
|
10426
|
-
|
|
10427
|
-
|
|
10428
|
-
const missionSummary = memberMissionSummary(owner, missionMap, root);
|
|
10612
|
+
const pulledTask = pulled ? { id: pulled.task.id, title: pulled.task.title, job: pulled.job } : null;
|
|
10613
|
+
if (pulled) whyPicked = `${whyPicked} Took the oldest open ${pulled.job} task from the list.`.trim();
|
|
10429
10614
|
const engineSeat = resolveMemberEngineSeat(owner, root);
|
|
10430
10615
|
const { autoQueueMemberNextPush, closeMemberHandoffsForReceiver } = require('./mission');
|
|
10431
10616
|
const subagentShell = buildMemberSubagentShell(owner, fm.role || 'Team Member', engineSeat.engine_id, effectivePrompt, root);
|
|
10432
10617
|
|
|
10433
|
-
if (effectivePrompt) {
|
|
10618
|
+
if (effectivePrompt && !pulled) {
|
|
10434
10619
|
try {
|
|
10435
10620
|
autoQueueMemberNextPush(owner, effectivePrompt, missionSummary.live?.objective || '', root);
|
|
10436
10621
|
} catch {}
|
|
@@ -10494,11 +10679,13 @@ async function memberSteerOrDispatch(...rawArgs) {
|
|
|
10494
10679
|
engine_reason: engineSeat.reason,
|
|
10495
10680
|
prompt: effectivePrompt,
|
|
10496
10681
|
why: whyPicked,
|
|
10682
|
+
task: pulledTask,
|
|
10497
10683
|
command,
|
|
10498
10684
|
subagent_shell: subagentShell,
|
|
10499
10685
|
};
|
|
10500
10686
|
const lines = [
|
|
10501
10687
|
`Routed to ${owner} on engine ${engineSeat.engine_id}: ${whyPicked}`,
|
|
10688
|
+
...(pulledTask ? [`Task: ${pulledTask.title}`] : []),
|
|
10502
10689
|
`Objective: ${effectivePrompt}`,
|
|
10503
10690
|
`Next: ${command}`,
|
|
10504
10691
|
];
|
|
@@ -10511,7 +10698,26 @@ async function memberSteerOrDispatch(...rawArgs) {
|
|
|
10511
10698
|
}
|
|
10512
10699
|
const hasExplicitEngine = flagArgs.includes('--engine');
|
|
10513
10700
|
const nextFlags = hasExplicitEngine ? flagArgs : ['--engine', engineSeat.engine_id, ...flagArgs];
|
|
10514
|
-
return memberRun(owner, effectivePrompt, '--ignite', ...nextFlags);
|
|
10701
|
+
if (!pulled) return memberRun(owner, effectivePrompt, '--ignite', ...nextFlags);
|
|
10702
|
+
// A pulled task runs with the check it requires, not the member's own.
|
|
10703
|
+
const required = require('../lib/member-task-pick').taskCheck(pulled.task).command;
|
|
10704
|
+
const runFlags = required ? [...stripKnownFlags(nextFlags, ['--verify'], []), '--verify', required] : nextFlags;
|
|
10705
|
+
let result = null;
|
|
10706
|
+
try {
|
|
10707
|
+
result = await memberRunWith(owner, [effectivePrompt, '--ignite', ...runFlags], { quiet: asJson });
|
|
10708
|
+
} catch (error) {
|
|
10709
|
+
memberGoFinishTask(owner, pulled.task, null, error);
|
|
10710
|
+
throw error;
|
|
10711
|
+
}
|
|
10712
|
+
const task = memberGoFinishTask(owner, pulled.task, result);
|
|
10713
|
+
const merged = { ...(result || {}), task };
|
|
10714
|
+
if (asJson) console.log(JSON.stringify(needsCheck
|
|
10715
|
+
? { task, work_step: { summary: result?.work_step?.summary || null }, error: result?.error || null }
|
|
10716
|
+
: merged));
|
|
10717
|
+
else if (task.outcome === 'review') console.log(`Task "${task.title}" is ready for review.`);
|
|
10718
|
+
else if (task.outcome === 'needs_human') console.log(`Task "${task.title}" failed three times in a row and now waits for a person: ${task.reason}`);
|
|
10719
|
+
else console.log(`Task "${task.title}" went back on the list: ${task.reason}`);
|
|
10720
|
+
return merged;
|
|
10515
10721
|
}
|
|
10516
10722
|
|
|
10517
10723
|
async function memberGo(...rawArgs) {
|
|
@@ -10522,7 +10728,7 @@ async function memberGo(...rawArgs) {
|
|
|
10522
10728
|
}
|
|
10523
10729
|
const nonFlags = rawArgs.filter((a) => a && !String(a).startsWith('-'));
|
|
10524
10730
|
if (nonFlags.length > 0) {
|
|
10525
|
-
return
|
|
10731
|
+
return memberSteerOrDispatchWith({ pullTask: true }, rawArgs);
|
|
10526
10732
|
}
|
|
10527
10733
|
const root = process.cwd();
|
|
10528
10734
|
const teamDir = path.join(root, 'atris', 'team');
|
|
@@ -10542,7 +10748,155 @@ async function memberGo(...rawArgs) {
|
|
|
10542
10748
|
const fallback = findAllMembers(teamDir).filter(isFleetMember)[0];
|
|
10543
10749
|
if (fallback) chosen = memberSlugFromRecord(fallback);
|
|
10544
10750
|
}
|
|
10545
|
-
return
|
|
10751
|
+
return memberSteerOrDispatchWith({ pullTask: true }, [chosen, ...rawArgs]);
|
|
10752
|
+
}
|
|
10753
|
+
|
|
10754
|
+
// Where a running `atris member up` leaves its pid, so a whole-team sleep
|
|
10755
|
+
// can end it.
|
|
10756
|
+
function memberUpPidFile(root) {
|
|
10757
|
+
return path.join(root, '.atris', 'state', 'member-up.json');
|
|
10758
|
+
}
|
|
10759
|
+
|
|
10760
|
+
// A member slept by name stays asleep for member up until someone wakes it;
|
|
10761
|
+
// a whole-team sleep only rests the team for now.
|
|
10762
|
+
function memberSleepingByChoice(latest) {
|
|
10763
|
+
return Boolean(latest && latest.status === 'paused' && latest.stop_reason === 'member-sleep'
|
|
10764
|
+
&& latest.sleep_target === 'member');
|
|
10765
|
+
}
|
|
10766
|
+
|
|
10767
|
+
function memberUpTeam(root) {
|
|
10768
|
+
const { isMemberParked } = require('../lib/member-park');
|
|
10769
|
+
const missionMap = memberRunMissionMap(root);
|
|
10770
|
+
return findAllMembers(path.join(root, 'atris', 'team')).filter(isFleetMember).map((record) => {
|
|
10771
|
+
const name = memberSlugFromRecord(record);
|
|
10772
|
+
const summary = memberMissionSummary(name, missionMap, root);
|
|
10773
|
+
return {
|
|
10774
|
+
name,
|
|
10775
|
+
parked: isMemberParked(root, name),
|
|
10776
|
+
human: Boolean(memberRefusalFor(name, path.join(root, 'atris', 'team', name, 'MEMBER.md'))),
|
|
10777
|
+
sleeping: memberSleepingByChoice(summary.latest),
|
|
10778
|
+
live: Boolean(summary.live_mission),
|
|
10779
|
+
};
|
|
10780
|
+
});
|
|
10781
|
+
}
|
|
10782
|
+
|
|
10783
|
+
// The task `member go <name>` would claim, skipping ones already handed out.
|
|
10784
|
+
// Looks only; the child claims it.
|
|
10785
|
+
function memberUpPick(root, name, taken) {
|
|
10786
|
+
try {
|
|
10787
|
+
const taskDb = require('../lib/task-db');
|
|
10788
|
+
const { fittingTasks } = require('../lib/member-task-pick');
|
|
10789
|
+
const db = taskDb.open();
|
|
10790
|
+
const job = memberGoJob(name, root);
|
|
10791
|
+
return fittingTasks(taskDb, db, { workspaceRoot: taskDb.workspaceRoot(root), job, requireCheck: true })
|
|
10792
|
+
.find((task) => !taken.has(task.id)) || null;
|
|
10793
|
+
} catch {
|
|
10794
|
+
return null;
|
|
10795
|
+
}
|
|
10796
|
+
}
|
|
10797
|
+
|
|
10798
|
+
function memberUpSpawn(root, name, onExit) {
|
|
10799
|
+
const { spawn } = require('child_process');
|
|
10800
|
+
const logDir = path.join(root, '.atris', 'state', 'member-up');
|
|
10801
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
10802
|
+
const logFile = path.join(logDir, `${name}.log`);
|
|
10803
|
+
const out = fs.openSync(logFile, 'a');
|
|
10804
|
+
const start = fs.fstatSync(out).size;
|
|
10805
|
+
let child;
|
|
10806
|
+
try {
|
|
10807
|
+
child = spawn(process.execPath, [path.join(__dirname, '..', 'bin', 'atris.js'), 'member', 'go', name, '--needs-check', '--json'], {
|
|
10808
|
+
cwd: root,
|
|
10809
|
+
detached: true,
|
|
10810
|
+
// Children keep their log handle when the foreground supervisor stops.
|
|
10811
|
+
stdio: ['ignore', out, out],
|
|
10812
|
+
env: process.env,
|
|
10813
|
+
});
|
|
10814
|
+
} finally {
|
|
10815
|
+
fs.closeSync(out);
|
|
10816
|
+
}
|
|
10817
|
+
child.on('error', (error) => onExit(null, { ok: false, reason: error.message }));
|
|
10818
|
+
child.on('close', (code, signal) => {
|
|
10819
|
+
let output = '';
|
|
10820
|
+
let fd;
|
|
10821
|
+
try {
|
|
10822
|
+
fd = fs.openSync(logFile, 'r');
|
|
10823
|
+
const size = fs.fstatSync(fd).size;
|
|
10824
|
+
const from = Math.max(start, size - 64 * 1024);
|
|
10825
|
+
const buf = Buffer.alloc(Math.max(0, size - from));
|
|
10826
|
+
const read = fs.readSync(fd, buf, 0, buf.length, from);
|
|
10827
|
+
output = buf.subarray(0, read).toString('utf8');
|
|
10828
|
+
} catch {} finally {
|
|
10829
|
+
if (fd !== undefined) fs.closeSync(fd);
|
|
10830
|
+
}
|
|
10831
|
+
const { memberUpResult } = require('../lib/member-up');
|
|
10832
|
+
onExit(code, memberUpResult(output, '', signal));
|
|
10833
|
+
});
|
|
10834
|
+
return child;
|
|
10835
|
+
}
|
|
10836
|
+
|
|
10837
|
+
async function memberUp(...args) {
|
|
10838
|
+
if (hasFlag(args, '--help') || hasFlag(args, '-h')) {
|
|
10839
|
+
console.log('Usage: atris member up [--max N] [--only a,b] [--dry-run]');
|
|
10840
|
+
console.log('Keeps the team working: every minute, starts each free member that has a fitting task with a runnable check.');
|
|
10841
|
+
console.log('A member with no such task stays idle; it never runs its own goal instead.');
|
|
10842
|
+
console.log('--only limits it to those members. Same as atris member wake 5 or atris member wake a b.');
|
|
10843
|
+
console.log('Ctrl-C or atris member sleep --all ends it. Runs it started keep going.');
|
|
10844
|
+
return;
|
|
10845
|
+
}
|
|
10846
|
+
const root = process.cwd();
|
|
10847
|
+
const { createMemberUp, runMemberUp, parseMemberUpArgs, DEFAULT_MAX } = require('../lib/member-up');
|
|
10848
|
+
const parsed = parseMemberUpArgs(args);
|
|
10849
|
+
if (parsed.error) {
|
|
10850
|
+
console.error(parsed.error);
|
|
10851
|
+
process.exitCode = 1;
|
|
10852
|
+
return;
|
|
10853
|
+
}
|
|
10854
|
+
const { max, only } = parsed;
|
|
10855
|
+
const deps = {
|
|
10856
|
+
listMembers: () => memberUpTeam(root),
|
|
10857
|
+
pickTask: (name, taken) => memberUpPick(root, name, taken),
|
|
10858
|
+
spawn: (name, onExit) => memberUpSpawn(root, name, onExit),
|
|
10859
|
+
now: () => Date.now(),
|
|
10860
|
+
};
|
|
10861
|
+
if (parsed.dryRun) {
|
|
10862
|
+
const up = createMemberUp(deps, { max, only, dryRun: true });
|
|
10863
|
+
const started = up.check();
|
|
10864
|
+
if (!started.length && up.working() >= up.max) {
|
|
10865
|
+
console.log(`nothing to start: ${up.working()} already working, the limit is ${up.max}`);
|
|
10866
|
+
} else if (!started.length) {
|
|
10867
|
+
console.log('nothing to start: no free member has a task with a runnable check');
|
|
10868
|
+
}
|
|
10869
|
+
return started;
|
|
10870
|
+
}
|
|
10871
|
+
const pidFile = memberUpPidFile(root);
|
|
10872
|
+
fs.mkdirSync(path.dirname(pidFile), { recursive: true });
|
|
10873
|
+
fs.writeFileSync(pidFile, JSON.stringify({ pid: process.pid, started_at: new Date().toISOString() }));
|
|
10874
|
+
const loop = runMemberUp(deps, { max, only });
|
|
10875
|
+
const end = () => loop.stop();
|
|
10876
|
+
process.once('SIGINT', end);
|
|
10877
|
+
process.once('SIGTERM', end);
|
|
10878
|
+
const who = only ? ` for ${only.join(', ')}` : '';
|
|
10879
|
+
console.log(`team is up${who} (at most ${max || DEFAULT_MAX} at once). Ctrl-C to stop.`);
|
|
10880
|
+
await loop.done;
|
|
10881
|
+
process.removeListener('SIGINT', end);
|
|
10882
|
+
process.removeListener('SIGTERM', end);
|
|
10883
|
+
try {
|
|
10884
|
+
if (JSON.parse(fs.readFileSync(pidFile, 'utf8')).pid === process.pid) fs.unlinkSync(pidFile);
|
|
10885
|
+
} catch {}
|
|
10886
|
+
const working = loop.up.running();
|
|
10887
|
+
console.log(working.length ? `team is down; still finishing: ${working.join(', ')}` : 'team is down');
|
|
10888
|
+
}
|
|
10889
|
+
|
|
10890
|
+
// Ends a running `atris member up`. Returns true when one was told to stop.
|
|
10891
|
+
function stopMemberUp(root) {
|
|
10892
|
+
try {
|
|
10893
|
+
const { pid } = JSON.parse(fs.readFileSync(memberUpPidFile(root), 'utf8'));
|
|
10894
|
+
if (!Number.isInteger(pid) || pid === process.pid) return false;
|
|
10895
|
+
process.kill(pid, 'SIGTERM');
|
|
10896
|
+
return true;
|
|
10897
|
+
} catch {
|
|
10898
|
+
return false;
|
|
10899
|
+
}
|
|
10546
10900
|
}
|
|
10547
10901
|
|
|
10548
10902
|
function memberSleep(target, ...args) {
|
|
@@ -10568,6 +10922,7 @@ function memberSleep(target, ...args) {
|
|
|
10568
10922
|
status: 'paused',
|
|
10569
10923
|
paused_at: new Date().toISOString(),
|
|
10570
10924
|
stop_reason: 'member-sleep',
|
|
10925
|
+
sleep_target: filterMember ? 'member' : 'all',
|
|
10571
10926
|
next_action: `sleeping; wake with: atris member go ${owner || ''}`.trim(),
|
|
10572
10927
|
updated_at: new Date().toISOString(),
|
|
10573
10928
|
};
|
|
@@ -10577,9 +10932,11 @@ function memberSleep(target, ...args) {
|
|
|
10577
10932
|
slept.push({ mission_id: m.id, member: owner || 'mission-lead', objective: m.objective });
|
|
10578
10933
|
}
|
|
10579
10934
|
|
|
10935
|
+
const supervisorStopped = !filterMember && stopMemberUp(root);
|
|
10580
10936
|
const payload = {
|
|
10581
10937
|
ok: true,
|
|
10582
10938
|
action: 'member_sleep',
|
|
10939
|
+
supervisor_stopped: supervisorStopped,
|
|
10583
10940
|
target: filterMember || 'all',
|
|
10584
10941
|
slept_count: slept.length,
|
|
10585
10942
|
slept,
|
|
@@ -10595,6 +10952,7 @@ function memberSleep(target, ...args) {
|
|
|
10595
10952
|
`All members are already resting.`,
|
|
10596
10953
|
`Wake anytime: ${payload.wake_command}`,
|
|
10597
10954
|
];
|
|
10955
|
+
if (supervisorStopped) lines.unshift('Stopped member up; it starts nobody new.');
|
|
10598
10956
|
printJsonOrText(payload, lines, asJson);
|
|
10599
10957
|
return payload;
|
|
10600
10958
|
}
|
|
@@ -10927,14 +11285,26 @@ function memberExport(...args) {
|
|
|
10927
11285
|
return result;
|
|
10928
11286
|
}
|
|
10929
11287
|
|
|
11288
|
+
function memberTidy(...args) {
|
|
11289
|
+
const { tidyMemberCopies, formatTidy } = require('../lib/member-tidy');
|
|
11290
|
+
const dirIndex = args.indexOf('--dir');
|
|
11291
|
+
const result = tidyMemberCopies({
|
|
11292
|
+
dryRun: args.includes('--dry-run'),
|
|
11293
|
+
worktreesDir: dirIndex >= 0 ? args[dirIndex + 1] : undefined,
|
|
11294
|
+
});
|
|
11295
|
+
console.log(formatTidy(result));
|
|
11296
|
+
if (result.aborted) process.exitCode = 1;
|
|
11297
|
+
return result;
|
|
11298
|
+
}
|
|
11299
|
+
|
|
10930
11300
|
async function memberCommand(subcommand, ...args) {
|
|
10931
11301
|
// Subcommands that take a member name as args[0] otherwise treat `--help` as
|
|
10932
11302
|
// a name and error with "Member '--help' not found". `create`/`new` handle
|
|
10933
11303
|
// help themselves (with subcommand-specific usage), leave those alone.
|
|
10934
11304
|
const HELP_AWARE_SUBCOMMANDS = new Set([
|
|
10935
11305
|
'create', 'new', 'run', 'install', 'result', 'dispatch', 'route', 'feed', 'stream',
|
|
10936
|
-
'card', 'cards', 'go', 'work', 'steer', 'sleep', 'rest', '1on1', 'interview',
|
|
10937
|
-
'room', 'ui', 'day', 'standup', 'handoff', 'handoffs',
|
|
11306
|
+
'card', 'cards', 'go', 'work', 'up', 'steer', 'sleep', 'rest', '1on1', 'interview',
|
|
11307
|
+
'room', 'ui', 'day', 'standup', 'handoff', 'handoffs', 'report',
|
|
10938
11308
|
]);
|
|
10939
11309
|
const explicitHelp = subcommand === 'help' || subcommand === '--help' || subcommand === '-h'
|
|
10940
11310
|
|| (!HELP_AWARE_SUBCOMMANDS.has(subcommand) && (args[0] === '-h' || args[0] === '--help'));
|
|
@@ -10971,9 +11341,17 @@ async function memberCommand(subcommand, ...args) {
|
|
|
10971
11341
|
case 'tick':
|
|
10972
11342
|
return memberTick(args[0], ...args.slice(1));
|
|
10973
11343
|
case 'wake':
|
|
10974
|
-
return
|
|
11344
|
+
return require('../lib/member-verbs').memberWakeVerb(args, {
|
|
11345
|
+
wake: memberWake,
|
|
11346
|
+
up: memberUp,
|
|
11347
|
+
});
|
|
10975
11348
|
case 'run':
|
|
10976
|
-
return
|
|
11349
|
+
return require('../lib/member-verbs').memberRunVerb(args, {
|
|
11350
|
+
root: process.cwd(),
|
|
11351
|
+
aliasOf: memberRuntimeKind,
|
|
11352
|
+
run: memberRun,
|
|
11353
|
+
dispatch: memberDispatch,
|
|
11354
|
+
});
|
|
10977
11355
|
case 'result': {
|
|
10978
11356
|
const code = require('../lib/member-result').memberResultCommand(args);
|
|
10979
11357
|
if (code) process.exitCode = code;
|
|
@@ -10982,6 +11360,8 @@ async function memberCommand(subcommand, ...args) {
|
|
|
10982
11360
|
case 'go':
|
|
10983
11361
|
case 'work':
|
|
10984
11362
|
return memberGo(...args);
|
|
11363
|
+
case 'up':
|
|
11364
|
+
return memberUp(...args);
|
|
10985
11365
|
case 'steer':
|
|
10986
11366
|
return memberSteerOrDispatch(...args);
|
|
10987
11367
|
case 'sleep':
|
|
@@ -11006,6 +11386,8 @@ async function memberCommand(subcommand, ...args) {
|
|
|
11006
11386
|
return memberFeed(...args);
|
|
11007
11387
|
case 'board':
|
|
11008
11388
|
return require('../lib/member-board').memberBoardCommand(...args);
|
|
11389
|
+
case 'stop':
|
|
11390
|
+
return require('../lib/member-board').memberStopCommand(args[0]);
|
|
11009
11391
|
case 'card':
|
|
11010
11392
|
case 'cards':
|
|
11011
11393
|
return memberCard(args[0], ...args.slice(1));
|
|
@@ -11028,6 +11410,8 @@ async function memberCommand(subcommand, ...args) {
|
|
|
11028
11410
|
return memberHeartbeat(args[0], ...args.slice(1));
|
|
11029
11411
|
case 'improvements':
|
|
11030
11412
|
return memberImprovements(args[0], ...args.slice(1));
|
|
11413
|
+
case 'report':
|
|
11414
|
+
return require('../lib/member-report').memberReportCommand(...args);
|
|
11031
11415
|
case 'history':
|
|
11032
11416
|
return memberHistory(args[0], ...args.slice(1));
|
|
11033
11417
|
case 'supervisor':
|
|
@@ -11042,6 +11426,8 @@ async function memberCommand(subcommand, ...args) {
|
|
|
11042
11426
|
return memberArchive(args[0]);
|
|
11043
11427
|
case 'purge-archived':
|
|
11044
11428
|
return memberPurgeArchived(...args);
|
|
11429
|
+
case 'tidy':
|
|
11430
|
+
return memberTidy(...args);
|
|
11045
11431
|
case undefined:
|
|
11046
11432
|
case '--json':
|
|
11047
11433
|
if (fs.existsSync(path.join(process.cwd(), 'atris', 'team'))) {
|
|
@@ -11056,6 +11442,9 @@ async function memberCommand(subcommand, ...args) {
|
|
|
11056
11442
|
}
|
|
11057
11443
|
return memberSteerOrDispatch(subcommand, ...args);
|
|
11058
11444
|
}
|
|
11445
|
+
console.log('run "<job>" pick the right member and run it: atris member run "fix the flaky login test"');
|
|
11446
|
+
console.log('wake <N|names> keep members working: atris member wake 5 or atris member wake navigator executor');
|
|
11447
|
+
console.log('sleep [name|--all] rest the team: atris member sleep --all');
|
|
11059
11448
|
console.log('');
|
|
11060
11449
|
console.log('Usage: atris member <subcommand|"<prompt>"> [name]');
|
|
11061
11450
|
console.log('');
|
|
@@ -11072,6 +11461,7 @@ async function memberCommand(subcommand, ...args) {
|
|
|
11072
11461
|
console.log(' install <slug> [--update] Install missing files, or refresh template skills');
|
|
11073
11462
|
console.log(' chat <name> "..." Send a message to this member\'s desk in Atris Desktop');
|
|
11074
11463
|
console.log(' list [--json] Show all team members');
|
|
11464
|
+
console.log(' tidy [--dry-run] Put away member copies of the repo whose work already landed');
|
|
11075
11465
|
console.log(' export [--dry-run] Write each member as a subagent for Claude Code, Cursor, and Codex');
|
|
11076
11466
|
console.log(' activate <name> Symlink member skills, show context and permissions');
|
|
11077
11467
|
console.log(' upgrade <name> Convert flat file (name.md) to directory format');
|
|
@@ -11080,10 +11470,13 @@ async function memberCommand(subcommand, ...args) {
|
|
|
11080
11470
|
console.log(' goal <name> "..." Create/update a member long-term goal');
|
|
11081
11471
|
console.log(' goal-from-mission <name> Create/reuse a goal from MISSION.md and now.md');
|
|
11082
11472
|
console.log(' goal-from-score <name> Create/reuse an active goal from Team score evidence');
|
|
11083
|
-
console.log(' wake <name> Decide tick/wait/ask/stop, then run one bounded work step on the roster engine (--plan to only decide)');
|
|
11473
|
+
console.log(' wake <name> Decide tick/wait/ask/stop, then run one bounded work step on the roster engine (--plan to only decide; --keep keeps it working)');
|
|
11474
|
+
console.log(' run "<job>" Pick the best member for the job and run it (same as dispatch "<job>" --ignite)');
|
|
11084
11475
|
console.log(' run <name> ["..."] Start a budgeted member mission, or run its active Mission Runtime');
|
|
11476
|
+
console.log(' report [--since 2h] Short summary per member: merged, finished, turned down, done but not merged yet [--json]');
|
|
11085
11477
|
console.log(' result <name> Newest run result: run record, receipt, artifacts, number [--since <iso>] [--json]');
|
|
11086
11478
|
console.log(' dispatch "<idea>" Route an idea to the best specialist member and start work');
|
|
11479
|
+
console.log(' up [--max N] [--only a,b] Keep the team working: start free members on fitting tasks every minute (wake 5 / wake a b)');
|
|
11087
11480
|
console.log(' feed [--member <n>] Show a plain-language linear feed of real work across the team');
|
|
11088
11481
|
console.log(' card [name|--all] Show a member\'s roadmap, unlocked skills, live work, and recent wins');
|
|
11089
11482
|
console.log(' loop <name|--all> Repeat wake on a bounded cadence with a no-overlap lease');
|
|
@@ -11144,7 +11537,10 @@ async function memberCommand(subcommand, ...args) {
|
|
|
11144
11537
|
}
|
|
11145
11538
|
|
|
11146
11539
|
module.exports = {
|
|
11540
|
+
memberRunDefaultWallSeconds,
|
|
11541
|
+
memberRunWantsReferee,
|
|
11147
11542
|
proposalPromptForGoal,
|
|
11543
|
+
memberGoFinishTask,
|
|
11148
11544
|
appendMemberGoalLog,
|
|
11149
11545
|
appendProjectLog,
|
|
11150
11546
|
memberCommand,
|