atris 3.68.1 → 3.68.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/atris/skills/engines/SKILL.md +1 -1
- package/atris/team/customer-lead/MEMBER.md +1 -1
- package/atris/team/executor/MEMBER.md +19 -18
- package/atris/team/executor/skills/isolated-slice-builder/SKILL.md +21 -0
- package/atris/team/navigator/MEMBER.md +3 -1
- package/atris/team/navigator/skills/loophole-proof-planner/SKILL.md +20 -0
- package/atris/team/researcher/MEMBER.md +2 -1
- package/atris/team/researcher/skills/grounded-evidence-brief/SKILL.md +19 -0
- package/atris/team/validator/MEMBER.md +2 -7
- package/atris/team/validator/skills/cross-model-referee/SKILL.md +20 -0
- package/bin/atris.js +40 -1
- package/commands/autoland.js +2 -1
- package/commands/done.js +374 -0
- package/commands/engine.js +2 -2
- package/commands/goal.js +61 -2
- package/commands/land.js +34 -7
- package/commands/member.js +1542 -41
- package/commands/mission.js +506 -18
- package/commands/task.js +26 -3
- package/commands/workflow.js +1 -1
- package/commands/worktree.js +144 -34
- package/lib/auto-accept-certified.js +7 -6
- package/lib/codex-flight.js +6 -12
- package/lib/conductor-artifacts.js +3 -1
- package/lib/default-runner.js +42 -7
- package/lib/engine-ask.js +8 -1
- package/lib/engine-registry.js +17 -0
- package/lib/falsifier-probe.js +6 -0
- package/lib/first-minute.js +257 -6
- package/lib/fleet.js +163 -88
- package/lib/known-commands.js +1 -1
- package/lib/member-alive.js +1 -1
- package/lib/member-approval.js +153 -0
- package/lib/mission-task-gate.js +170 -0
- package/lib/one-lap-validator.js +2 -1
- package/lib/roster-runs.js +28 -0
- package/lib/runner-command.js +32 -8
- package/lib/task-db.js +25 -1
- package/package.json +9 -2
- package/scripts/det/ytnotes +49 -10
- package/utils/backend-root.js +4 -0
package/commands/member.js
CHANGED
|
@@ -8,7 +8,7 @@ const { resolveBackendRoot } = require('../utils/backend-root');
|
|
|
8
8
|
const { apiRequestJson } = require('../utils/api');
|
|
9
9
|
const { runAliveTick } = require('../lib/member-alive');
|
|
10
10
|
const { memberRefusalReason } = require('../lib/member-type');
|
|
11
|
-
const { defaultObjectiveRunner } = require('../lib/default-runner');
|
|
11
|
+
const { callerRuntime, defaultObjectiveRunner } = require('../lib/default-runner');
|
|
12
12
|
const { readJson, writeJson } = require('../lib/json-file');
|
|
13
13
|
const { sleepSync } = require('../lib/sleep-sync');
|
|
14
14
|
const { hasFlag, readFlag, readNumberFlag } = require('../lib/arg-parser');
|
|
@@ -383,7 +383,10 @@ function loadMemberGoals(name, paths = memberPaths(name)) {
|
|
|
383
383
|
|
|
384
384
|
function activeGoal(goalsState, goalId = '') {
|
|
385
385
|
if (goalId) return goalsState.goals.find((goal) => goal.id === goalId) || null;
|
|
386
|
-
|
|
386
|
+
for (let i = (goalsState.goals || []).length - 1; i >= 0; i -= 1) {
|
|
387
|
+
if (goalsState.goals[i]?.status === 'active') return goalsState.goals[i];
|
|
388
|
+
}
|
|
389
|
+
return goalsState.goals[0] || null;
|
|
387
390
|
}
|
|
388
391
|
|
|
389
392
|
function allExperiments(state) {
|
|
@@ -490,9 +493,18 @@ function missionPurpose(paths) {
|
|
|
490
493
|
const MEMBER_RUN_RUNNABLE_STATUSES = new Set(['planning', 'running', 'ready']);
|
|
491
494
|
const MISSION_TERMINAL_STATUSES = new Set(['complete', 'stopped', 'failed']);
|
|
492
495
|
|
|
493
|
-
function memberRunMissionMap() {
|
|
496
|
+
function memberRunMissionMap(root = process.cwd()) {
|
|
494
497
|
try {
|
|
495
|
-
|
|
498
|
+
const missionMod = require('./mission');
|
|
499
|
+
const map = new Map(missionMod.loadMissionMap(root));
|
|
500
|
+
for (const rolled of missionMod.listWorktreeRollupMissions(root)) {
|
|
501
|
+
if (!rolled?.id) continue;
|
|
502
|
+
const existing = map.get(rolled.id);
|
|
503
|
+
if (!existing || missionTimeMs(rolled) >= missionTimeMs(existing)) {
|
|
504
|
+
map.set(rolled.id, rolled);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return map;
|
|
496
508
|
} catch {
|
|
497
509
|
return new Map();
|
|
498
510
|
}
|
|
@@ -627,18 +639,38 @@ function memberLastActivity(name, paths = memberPaths(name), root = process.cwd(
|
|
|
627
639
|
};
|
|
628
640
|
}
|
|
629
641
|
|
|
630
|
-
function memberMissionSummary(name, missionMap = memberRunMissionMap()) {
|
|
642
|
+
function memberMissionSummary(name, missionMap = memberRunMissionMap(), root = process.cwd()) {
|
|
631
643
|
const latest = memberLatestMission(name, missionMap);
|
|
632
644
|
const live = memberLiveMission(name, missionMap);
|
|
633
645
|
const lastTickMs = timestampMs(latest?.last_tick_at);
|
|
646
|
+
let driver = null;
|
|
647
|
+
if (latest) {
|
|
648
|
+
try {
|
|
649
|
+
const { missionDriverHealth } = require('./mission');
|
|
650
|
+
const missionRoot = latest.worktree_root || latest.worktree?.path || root;
|
|
651
|
+
driver = missionDriverHealth(latest, missionRoot);
|
|
652
|
+
} catch {
|
|
653
|
+
driver = null;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
const worktreeRoot = latest?.worktree_root || latest?.worktree?.path || null;
|
|
657
|
+
const worktreeBranch = latest?.worktree_branch || latest?.worktree?.branch || null;
|
|
634
658
|
return {
|
|
635
659
|
latest,
|
|
636
660
|
live,
|
|
637
661
|
name: missionShortName(latest),
|
|
662
|
+
objective: latest?.objective || null,
|
|
638
663
|
state: latest?.status || '-',
|
|
664
|
+
runner: latest?.runner || null,
|
|
665
|
+
model: latest?.model || null,
|
|
666
|
+
worktree_root: worktreeRoot,
|
|
667
|
+
worktree_branch: worktreeBranch,
|
|
668
|
+
task_ref: latest?.task_ref || latest?.xp_task?.ref || null,
|
|
669
|
+
current_task_id: latest?.current_task_id || latest?.task_ids?.[0] || null,
|
|
639
670
|
last_tick_at: latest?.last_tick_at || null,
|
|
640
671
|
last_tick_age: ageLabelFromMs(lastTickMs),
|
|
641
|
-
live_mission: Boolean(live),
|
|
672
|
+
live_mission: Boolean(live) || Boolean(driver?.alive),
|
|
673
|
+
driver,
|
|
642
674
|
};
|
|
643
675
|
}
|
|
644
676
|
|
|
@@ -720,6 +752,7 @@ const MEMBER_RUN_START_VALUE_FLAGS = [
|
|
|
720
752
|
'--visible-goal-objective',
|
|
721
753
|
'--max-idle-ticks',
|
|
722
754
|
'--slots',
|
|
755
|
+
'--referee-engine',
|
|
723
756
|
'--lane',
|
|
724
757
|
'--cadence',
|
|
725
758
|
'--verify',
|
|
@@ -735,11 +768,14 @@ const MEMBER_RUN_START_VALUE_FLAGS = [
|
|
|
735
768
|
'--outcome',
|
|
736
769
|
'--truth',
|
|
737
770
|
'--proof',
|
|
771
|
+
'--base',
|
|
738
772
|
];
|
|
739
773
|
|
|
740
774
|
const MEMBER_RUN_START_BOOLEAN_FLAGS = [
|
|
741
775
|
'--json',
|
|
742
776
|
'--no-verify',
|
|
777
|
+
'--referee',
|
|
778
|
+
'--no-referee',
|
|
743
779
|
'--worktree',
|
|
744
780
|
'--shared-checkout',
|
|
745
781
|
'--no-worktree',
|
|
@@ -773,6 +809,10 @@ const MEMBER_RUN_START_BOOLEAN_FLAGS = [
|
|
|
773
809
|
'--dry-run',
|
|
774
810
|
'--land',
|
|
775
811
|
'--cloud',
|
|
812
|
+
'--ignite',
|
|
813
|
+
'--wait',
|
|
814
|
+
'--sync',
|
|
815
|
+
'--no-driver',
|
|
776
816
|
];
|
|
777
817
|
|
|
778
818
|
function memberRunMissionWordIndices(args = []) {
|
|
@@ -900,7 +940,23 @@ function memberRunRunner(owner, args = [], cwd = process.cwd()) {
|
|
|
900
940
|
return memberRosterEngine(owner, cwd) ? 'auto' : fallback;
|
|
901
941
|
}
|
|
902
942
|
|
|
943
|
+
function inferMemberRunVerifier(missionText, args = [], cwd = process.cwd()) {
|
|
944
|
+
if (hasFlag(args, '--no-verify')) return '';
|
|
945
|
+
const explicit = readFlag(args, '--verify', '');
|
|
946
|
+
if (explicit) return explicit;
|
|
947
|
+
const text = String(missionText || '').trim();
|
|
948
|
+
if (!text) return '';
|
|
949
|
+
const cmdMatch = text.match(/\b(npm\s+run\s+type-check|node\s+--test\s+tests?\/[A-Za-z0-9_./-]+)\b/);
|
|
950
|
+
if (cmdMatch) return cmdMatch[1].replace(/\s+/g, ' ');
|
|
951
|
+
const testFileMatch = text.match(/\b(tests?\/[A-Za-z0-9_./-]+\.test\.m?js)\b/);
|
|
952
|
+
if (testFileMatch && fs.existsSync(path.join(cwd, testFileMatch[1]))) {
|
|
953
|
+
return `node --test ${testFileMatch[1]}`;
|
|
954
|
+
}
|
|
955
|
+
return '';
|
|
956
|
+
}
|
|
957
|
+
|
|
903
958
|
function buildMemberRunStartArgs(owner, missionText, args = [], cwd = process.cwd()) {
|
|
959
|
+
const resolvedVerifier = inferMemberRunVerifier(missionText, args, cwd);
|
|
904
960
|
const startArgs = [
|
|
905
961
|
'mission',
|
|
906
962
|
'start',
|
|
@@ -913,7 +969,9 @@ function buildMemberRunStartArgs(owner, missionText, args = [], cwd = process.cw
|
|
|
913
969
|
readFlag(args, '--lane', 'workspace'),
|
|
914
970
|
];
|
|
915
971
|
pushFlagValue(startArgs, args, '--cadence');
|
|
916
|
-
|
|
972
|
+
if (resolvedVerifier) {
|
|
973
|
+
startArgs.push('--verify', String(resolvedVerifier));
|
|
974
|
+
}
|
|
917
975
|
pushFlagValue(startArgs, args, '--stop');
|
|
918
976
|
pushFlagValue(startArgs, args, '--model');
|
|
919
977
|
pushFlagValue(startArgs, args, '--max-wall');
|
|
@@ -921,8 +979,9 @@ function buildMemberRunStartArgs(owner, missionText, args = [], cwd = process.cw
|
|
|
921
979
|
pushFlagValue(startArgs, args, '--hours');
|
|
922
980
|
pushFlagValue(startArgs, args, '--base');
|
|
923
981
|
// Member runs choose and drive work immediately, so an absent check must not
|
|
924
|
-
// leave autonomous callers with a mission that never ran.
|
|
925
|
-
|
|
982
|
+
// leave autonomous callers with a mission that never ran. A check inferred
|
|
983
|
+
// from the mission text counts as named.
|
|
984
|
+
if (hasFlag(args, '--no-verify') || !resolvedVerifier) {
|
|
926
985
|
startArgs.push('--no-verify');
|
|
927
986
|
}
|
|
928
987
|
pushFlagWhenPresent(startArgs, args, '--always-on');
|
|
@@ -992,16 +1051,96 @@ function memberRunConfiguredVerifier(paths) {
|
|
|
992
1051
|
return typeof check === 'string' ? check.trim() : '';
|
|
993
1052
|
}
|
|
994
1053
|
|
|
995
|
-
function printMemberRunResult(result, asJson) {
|
|
996
|
-
if (
|
|
1054
|
+
function printMemberRunResult(result, asJson, { quiet = false } = {}) {
|
|
1055
|
+
if (quiet) {
|
|
1056
|
+
// A caller (member wake) prints this result inside its own report.
|
|
1057
|
+
} else if (asJson) {
|
|
997
1058
|
console.log(JSON.stringify(result));
|
|
998
1059
|
} else if (result.started) {
|
|
999
1060
|
console.log(`started ${result.mission_id} (${result.state})`);
|
|
1000
1061
|
}
|
|
1062
|
+
if (!quiet && !asJson && result.ask) console.error(result.ask);
|
|
1001
1063
|
if (result.ok === false || (!result.started && result.ok !== true)) {
|
|
1002
|
-
if (!result.errorReported) console.error(result.error);
|
|
1064
|
+
if (!quiet && !result.errorReported && !result.ask) console.error(result.error);
|
|
1003
1065
|
process.exitCode = 1;
|
|
1004
1066
|
}
|
|
1067
|
+
return result;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
// Runners that hand the mission to the agent session that called Atris. From
|
|
1071
|
+
// a plain terminal nothing is on the other end of them.
|
|
1072
|
+
const MEMBER_RUN_CALLER_SESSION_RUNNERS = new Set(['codex_goal', 'caller_session', 'current_agent']);
|
|
1073
|
+
|
|
1074
|
+
function memberRunUsesCallerSession(runner) {
|
|
1075
|
+
return MEMBER_RUN_CALLER_SESSION_RUNNERS.has(String(runner || '').trim().toLowerCase());
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// A mission whose runner waits on a caller session cannot do anything when
|
|
1079
|
+
// no such session started this run. Swap in the roster engine for this run
|
|
1080
|
+
// ("auto" resolves the owner's roster pick per tick) instead of waiting on a
|
|
1081
|
+
// native goal nothing will start. An explicit --runner or --engine on this
|
|
1082
|
+
// command still wins, and a real codex caller keeps its native goal.
|
|
1083
|
+
function memberRunRunnerSwap(mission, args, caller) {
|
|
1084
|
+
if (!memberRunUsesCallerSession(mission?.runner)) return null;
|
|
1085
|
+
if (caller === 'codex') return null;
|
|
1086
|
+
if (readFlag(args, '--runner', '') || readFlag(args, '--engine', '')) return null;
|
|
1087
|
+
return {
|
|
1088
|
+
from: String(mission.runner),
|
|
1089
|
+
to: 'auto',
|
|
1090
|
+
reason: `no ${String(mission.runner) === 'codex_goal' ? 'codex' : 'agent'} session started this run, so the member's roster engine does the work`,
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
function memberRunTickDurationMs(tick) {
|
|
1095
|
+
const start = Date.parse(tick?.started_at || '');
|
|
1096
|
+
const end = Date.parse(tick?.finished_at || '');
|
|
1097
|
+
return Number.isFinite(start) && Number.isFinite(end) && end >= start ? end - start : null;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
// One plain record of the work step this run executed (or why none ran), so
|
|
1101
|
+
// a reader never has to dig through mission internals to learn what happened.
|
|
1102
|
+
function memberRunWorkStep(payload, finalMission) {
|
|
1103
|
+
const ticks = Array.isArray(payload?.ticks) ? payload.ticks : [];
|
|
1104
|
+
const ran = [...ticks].reverse().find((tick) => tick && tick.ran === true) || null;
|
|
1105
|
+
const last = ran || ticks[ticks.length - 1] || null;
|
|
1106
|
+
const worker = last?.claude || last?.atris2 || null;
|
|
1107
|
+
return {
|
|
1108
|
+
ran: Boolean(ran),
|
|
1109
|
+
ticks_ran: Number(payload?.ran_ticks) || 0,
|
|
1110
|
+
engine: last?.engine_id || finalMission?.runner || null,
|
|
1111
|
+
model: last?.model || worker?.model || finalMission?.model || null,
|
|
1112
|
+
duration_ms: last ? memberRunTickDurationMs(last) : null,
|
|
1113
|
+
status: last?.status || null,
|
|
1114
|
+
reason: last?.reason || null,
|
|
1115
|
+
summary: worker?.summary ? String(worker.summary).slice(0, 400) : null,
|
|
1116
|
+
receipt_path: payload?.summary_receipt || finalMission?.receipt_path || null,
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// The protected-surface guard keeps the staged work and pauses for a person.
|
|
1121
|
+
// Say so in one line, with the command that shows the work.
|
|
1122
|
+
function memberRunReviewAsk(name, payload, mission, cwd) {
|
|
1123
|
+
const ticks = Array.isArray(payload?.ticks) ? payload.ticks : [];
|
|
1124
|
+
const guarded = ticks.find((tick) => tick?.protected_lane_guard && tick.protected_lane_guard.allowed === false);
|
|
1125
|
+
const guard = guarded?.protected_lane_guard || null;
|
|
1126
|
+
if (!guard) return null;
|
|
1127
|
+
const surfaces = Array.isArray(guard.surfaces) && guard.surfaces.length ? guard.surfaces.join(', ') : 'a protected surface';
|
|
1128
|
+
if (guard.unreadable) {
|
|
1129
|
+
return {
|
|
1130
|
+
needs_review: true,
|
|
1131
|
+
ask: `${name} paused: Atris could not read the mission diff; fix that, then rerun.`,
|
|
1132
|
+
next_command: `atris mission tick ${mission.id} --verify`,
|
|
1133
|
+
review_path: cwd,
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
const where = JSON.stringify(cwd);
|
|
1137
|
+
return {
|
|
1138
|
+
needs_review: true,
|
|
1139
|
+
ask: `${name} staged a change that touches ${surfaces}; it is kept in ${cwd} for you to review, land, or discard.`,
|
|
1140
|
+
next_command: `git -C ${where} status --short && git -C ${where} diff HEAD`,
|
|
1141
|
+
resume_command: `atris mission run ${mission.id}`,
|
|
1142
|
+
review_path: cwd,
|
|
1143
|
+
};
|
|
1005
1144
|
}
|
|
1006
1145
|
|
|
1007
1146
|
// atris member ping <name> "<message>": talk to a busy member mid-run.
|
|
@@ -1087,10 +1226,15 @@ function memberPing(name, ...args) {
|
|
|
1087
1226
|
}
|
|
1088
1227
|
|
|
1089
1228
|
function memberRun(name, ...args) {
|
|
1229
|
+
return memberRunWith(name, args);
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
function memberRunWith(name, args, { quiet = false } = {}) {
|
|
1090
1233
|
if (!name || name === '--help' || name === '-h' || hasFlag(args, '--help') || hasFlag(args, '-h')) {
|
|
1091
1234
|
console.log('usage: atris member run <name> ["mission text"] [--verify "<cmd>"] [--minutes N|--hours N] [--json]');
|
|
1092
1235
|
console.log('new mission: atris member run growth "improve onboarding proof" --verify "npm test" --minutes 30 --json');
|
|
1093
1236
|
console.log('choose work: atris member run growth --verify "npm test" --industry logistics --minutes 30 --json');
|
|
1237
|
+
console.log('subagent shell: atris member run growth "improve onboarding proof" --engine claude --ignite --json');
|
|
1094
1238
|
console.log('existing mission: atris member run growth --mission <mission-id> --max-ticks 1 --json');
|
|
1095
1239
|
console.log('meaning: if mission text is omitted, the member chooses one useful bounded task from atris state.');
|
|
1096
1240
|
console.log('truth: useful work must improve revenue, reliability, speed, security, clarity, or trust, then show plain proof.');
|
|
@@ -1099,6 +1243,12 @@ function memberRun(name, ...args) {
|
|
|
1099
1243
|
return;
|
|
1100
1244
|
}
|
|
1101
1245
|
|
|
1246
|
+
// --ignite (or --wait, --sync) runs the member as a subagent shell: it works
|
|
1247
|
+
// on the named engine until the check passes, then the mission completes.
|
|
1248
|
+
const ignite = hasFlag(args, '--ignite') || hasFlag(args, '--wait') || hasFlag(args, '--sync');
|
|
1249
|
+
if (ignite && readFlag(args, '--engine', '') && !readFlag(args, '--runner', '')) {
|
|
1250
|
+
args = [...args, '--runner', readFlag(args, '--engine', '')];
|
|
1251
|
+
}
|
|
1102
1252
|
const asJson = hasFlag(args, '--json');
|
|
1103
1253
|
const paths = requireRunnableMember(name);
|
|
1104
1254
|
// Parking only hides a member from the team views; a run by name still goes.
|
|
@@ -1118,9 +1268,8 @@ function memberRun(name, ...args) {
|
|
|
1118
1268
|
if (missionText && !hasMissionOverride) {
|
|
1119
1269
|
const start = startMemberRunMission(name, missionText, requestedVerifier ? [...args, '--verify', requestedVerifier] : args);
|
|
1120
1270
|
if (start.status !== 0 || !start.payload?.mission) {
|
|
1121
|
-
printMemberRunResult({ started: false, state: null, mission_id: start.payload?.mission?.id || null,
|
|
1122
|
-
error: start.payload?.detail || start.payload?.error || start.stderr || 'mission creation failed' }, asJson);
|
|
1123
|
-
return;
|
|
1271
|
+
return printMemberRunResult({ started: false, state: null, mission_id: start.payload?.mission?.id || null,
|
|
1272
|
+
error: start.payload?.detail || start.payload?.error || start.stderr || 'mission creation failed' }, asJson, { quiet });
|
|
1124
1273
|
}
|
|
1125
1274
|
mission = start.payload.mission;
|
|
1126
1275
|
startedNewMission = true;
|
|
@@ -1131,9 +1280,8 @@ function memberRun(name, ...args) {
|
|
|
1131
1280
|
if (!missionId) {
|
|
1132
1281
|
const start = startMemberRunMission(name, memberRunAutoMissionText(name, args), requestedVerifier ? [...args, '--verify', requestedVerifier] : args);
|
|
1133
1282
|
if (start.status !== 0 || !start.payload?.mission) {
|
|
1134
|
-
printMemberRunResult({ started: false, state: null, mission_id: start.payload?.mission?.id || null,
|
|
1135
|
-
error: start.payload?.detail || start.payload?.error || start.stderr || 'mission creation failed' }, asJson);
|
|
1136
|
-
return;
|
|
1283
|
+
return printMemberRunResult({ started: false, state: null, mission_id: start.payload?.mission?.id || null,
|
|
1284
|
+
error: start.payload?.detail || start.payload?.error || start.stderr || 'mission creation failed' }, asJson, { quiet });
|
|
1137
1285
|
}
|
|
1138
1286
|
mission = start.payload.mission;
|
|
1139
1287
|
startedNewMission = true;
|
|
@@ -1142,8 +1290,7 @@ function memberRun(name, ...args) {
|
|
|
1142
1290
|
} else {
|
|
1143
1291
|
mission = require('./mission').resolveMission(missionId);
|
|
1144
1292
|
if (!mission) {
|
|
1145
|
-
printMemberRunResult({ started: false, state: null, mission_id: missionId, error: `mission ${missionId} was not found` }, asJson);
|
|
1146
|
-
return;
|
|
1293
|
+
return printMemberRunResult({ started: false, state: null, mission_id: missionId, error: `mission ${missionId} was not found` }, asJson, { quiet });
|
|
1147
1294
|
}
|
|
1148
1295
|
}
|
|
1149
1296
|
}
|
|
@@ -1158,9 +1305,8 @@ function memberRun(name, ...args) {
|
|
|
1158
1305
|
if (verifier && verifier !== mission.verifier) {
|
|
1159
1306
|
const changed = memberRunCli(['mission', 'set-verifier', mission.id, verifier, '--json'], cwd);
|
|
1160
1307
|
if (changed.status !== 0) {
|
|
1161
|
-
printMemberRunResult({ started: false, state: mission.status, mission_id: mission.id, mission,
|
|
1162
|
-
error: changed.payload?.detail || changed.payload?.error || changed.stderr || 'could not set mission check' }, asJson);
|
|
1163
|
-
return;
|
|
1308
|
+
return printMemberRunResult({ started: false, state: mission.status, mission_id: mission.id, mission,
|
|
1309
|
+
error: changed.payload?.detail || changed.payload?.error || changed.stderr || 'could not set mission check' }, asJson, { quiet });
|
|
1164
1310
|
}
|
|
1165
1311
|
mission.verifier = verifier;
|
|
1166
1312
|
}
|
|
@@ -1169,22 +1315,54 @@ function memberRun(name, ...args) {
|
|
|
1169
1315
|
const executionArgs = args.filter((_, index) => !missionWordIndices.has(index));
|
|
1170
1316
|
const runArgs = stripKnownFlags(executionArgs,
|
|
1171
1317
|
['--mission', '--mission-id', '--minutes', '--hours', '--industry', '--domain', '--value', '--outcome', '--truth', '--proof', '--base'],
|
|
1172
|
-
['--worktree', '--shared-checkout', '--no-worktree', '--choose-work', '--auto']);
|
|
1318
|
+
['--worktree', '--shared-checkout', '--no-worktree', '--choose-work', '--auto', '--ignite', '--wait', '--sync']);
|
|
1319
|
+
if (ignite && !hasFlag(runArgs, '--complete-on-pass')) runArgs.push('--complete-on-pass');
|
|
1173
1320
|
const budgetSeconds = memberRunBudgetSeconds(args);
|
|
1174
1321
|
if (!readFlag(runArgs, '--max-ticks', '')) {
|
|
1175
1322
|
runArgs.push('--max-ticks', budgetSeconds ? String(Math.max(4, Math.ceil(budgetSeconds / 300))) : '1');
|
|
1176
1323
|
}
|
|
1177
1324
|
if (!readFlag(runArgs, '--max-wall', '')) runArgs.push('--max-wall', String(budgetSeconds || 900));
|
|
1178
|
-
const
|
|
1325
|
+
const caller = callerRuntime(args);
|
|
1326
|
+
const runnerSwap = memberRunRunnerSwap(mission, args, caller);
|
|
1327
|
+
if (runnerSwap) runArgs.push('--engine', runnerSwap.to);
|
|
1328
|
+
const run = memberRunCli(['mission', 'run', mission.id, ...runArgs], cwd, { live: !asJson && !quiet, missionId: mission.id });
|
|
1179
1329
|
const payload = run.payload || {};
|
|
1180
1330
|
const finalMission = payload.mission || require('./mission').resolveMission(mission.id, cwd) || mission;
|
|
1181
1331
|
const started = run.status === 0 && payload.ok !== false && Number(payload.ran_ticks) > 0;
|
|
1332
|
+
// Inside a real codex session the native goal is the work step: the caller
|
|
1333
|
+
// starts it with the action below, so waiting on it is not a failure.
|
|
1334
|
+
const waitingOnNativeGoal = caller === 'codex' && payload.requires_native_goal_start === true
|
|
1335
|
+
&& memberRunUsesCallerSession(finalMission.runner);
|
|
1182
1336
|
const noWorkStepExpected = hasFlag(runArgs, '--detach') || hasFlag(runArgs, '--dry-run');
|
|
1183
|
-
const ok = run.status === 0 && payload.ok !== false && (started || noWorkStepExpected);
|
|
1184
|
-
|
|
1337
|
+
const ok = (run.status === 0 && payload.ok !== false && (started || noWorkStepExpected)) || waitingOnNativeGoal;
|
|
1338
|
+
const reviewAsk = memberRunReviewAsk(name, payload, finalMission, cwd);
|
|
1339
|
+
const result = { ...payload, ok, started, state: finalMission.status, mission_id: mission.id,
|
|
1185
1340
|
mission: finalMission, node_modules_linked: nodeModulesLinked, node_modules_source: nodeModulesSource,
|
|
1186
|
-
|
|
1187
|
-
|
|
1341
|
+
started_mission: startedNewMission ? mission : null, worktree: mission.worktree || null,
|
|
1342
|
+
caller_runtime: caller,
|
|
1343
|
+
runner_swap: runnerSwap,
|
|
1344
|
+
work_step: memberRunWorkStep(payload, finalMission),
|
|
1345
|
+
errorReported: run.errorReported };
|
|
1346
|
+
// The goal-controller view describes whichever mission holds the codex goal
|
|
1347
|
+
// slot, often an older one. On a run that is not a native goal it reads as
|
|
1348
|
+
// "waiting on create_goal" after this run already worked, so leave it out.
|
|
1349
|
+
if (runnerSwap || !memberRunUsesCallerSession(finalMission.runner)) {
|
|
1350
|
+
delete result.codex_goal_state;
|
|
1351
|
+
}
|
|
1352
|
+
if (waitingOnNativeGoal) {
|
|
1353
|
+
result.waiting_on = 'native_goal_start';
|
|
1354
|
+
result.next_command = payload.next_action || payload.next_command || null;
|
|
1355
|
+
}
|
|
1356
|
+
if (reviewAsk) Object.assign(result, reviewAsk);
|
|
1357
|
+
if (!reviewAsk && payload.reason === 'human_asks_open' && Array.isArray(payload.human_asks) && payload.human_asks.length) {
|
|
1358
|
+
result.ask = `${name} is waiting on you: ${payload.human_asks[0]}`;
|
|
1359
|
+
result.next_command = payload.next_command || 'atris decide';
|
|
1360
|
+
}
|
|
1361
|
+
if (!ok) {
|
|
1362
|
+
result.error = result.ask || payload.detail || payload.error || payload.reason || payload.next_action
|
|
1363
|
+
|| run.stderr || 'mission did not run a work step';
|
|
1364
|
+
}
|
|
1365
|
+
return printMemberRunResult(result, asJson, { quiet });
|
|
1188
1366
|
}
|
|
1189
1367
|
|
|
1190
1368
|
function loadTeamScoreEvidence(scoreJsonPath) {
|
|
@@ -1385,7 +1563,23 @@ function taskHasReviewProof(task) {
|
|
|
1385
1563
|
return (task?.events || []).some((event) => event?.payload?.proof || event?.payload?.review?.proof);
|
|
1386
1564
|
}
|
|
1387
1565
|
|
|
1566
|
+
function taskIsTaggedNeedsHuman(task) {
|
|
1567
|
+
if (!task || typeof task !== 'object') return false;
|
|
1568
|
+
if (task?.metadata?.needs_human === true || task?.metadata?.human_only === true) return true;
|
|
1569
|
+
const tags = [
|
|
1570
|
+
task.tag,
|
|
1571
|
+
...(Array.isArray(task.tags) ? task.tags : []),
|
|
1572
|
+
task.metadata?.tag,
|
|
1573
|
+
...(Array.isArray(task.metadata?.tags) ? task.metadata.tags : []),
|
|
1574
|
+
]
|
|
1575
|
+
.filter(Boolean)
|
|
1576
|
+
.flatMap((t) => String(t).split(/[,;\s]+/))
|
|
1577
|
+
.map(lowerCompact);
|
|
1578
|
+
return tags.some((t) => t === 'needs-human' || t === 'needs_human' || t === 'human-only' || t === 'human_only');
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1388
1581
|
function taskCandidateFromSource(task, source, sourcePath = '') {
|
|
1582
|
+
if (taskIsTaggedNeedsHuman(task)) return null;
|
|
1389
1583
|
const ref = taskRef(task);
|
|
1390
1584
|
if (!ref) return null;
|
|
1391
1585
|
const status = lowerCompact(task.status || task.state || 'open');
|
|
@@ -3295,6 +3489,7 @@ function collectAutoImproverLogSignals(root) {
|
|
|
3295
3489
|
}
|
|
3296
3490
|
|
|
3297
3491
|
function autoImproverTaskWaitingForHuman(task, status) {
|
|
3492
|
+
if (taskIsTaggedNeedsHuman(task)) return true;
|
|
3298
3493
|
if (status !== 'review') return false;
|
|
3299
3494
|
const metadata = task?.metadata || {};
|
|
3300
3495
|
const review = task?.review || {};
|
|
@@ -3826,6 +4021,9 @@ async function runAutoImproverWake(name, paths, { execute = false, confirmed = f
|
|
|
3826
4021
|
});
|
|
3827
4022
|
}
|
|
3828
4023
|
|
|
4024
|
+
const purpose = missionPurpose(paths);
|
|
4025
|
+
const currentGoal = activeGoal(loadMemberGoals(name, paths));
|
|
4026
|
+
|
|
3829
4027
|
return {
|
|
3830
4028
|
ok: true,
|
|
3831
4029
|
action: 'wake',
|
|
@@ -3838,6 +4036,13 @@ async function runAutoImproverWake(name, paths, { execute = false, confirmed = f
|
|
|
3838
4036
|
needs_user: false,
|
|
3839
4037
|
ask: null,
|
|
3840
4038
|
next_command: nextCommand,
|
|
4039
|
+
mission: wakeMissionSummary(purpose),
|
|
4040
|
+
active_goal: currentGoal ? {
|
|
4041
|
+
id: currentGoal.id,
|
|
4042
|
+
title: currentGoal.title,
|
|
4043
|
+
source: currentGoal.source || null,
|
|
4044
|
+
mission_id: currentGoal.mission_id || null,
|
|
4045
|
+
} : null,
|
|
3841
4046
|
auto_improver: payload,
|
|
3842
4047
|
receipt_path: finalWrite.receiptPath,
|
|
3843
4048
|
latest_path: finalWrite.latestPath,
|
|
@@ -3957,8 +4162,10 @@ function appendMemberGoalLog(memberDir, name, title, fields = {}) {
|
|
|
3957
4162
|
`## ${stamp} · ${title}`,
|
|
3958
4163
|
`- team: ${name}`,
|
|
3959
4164
|
...Object.entries(fields)
|
|
3960
|
-
.filter(([, value]) => value !== undefined && value !== null
|
|
3961
|
-
.map(([key, value]) =>
|
|
4165
|
+
.filter(([, value]) => value !== undefined && value !== null)
|
|
4166
|
+
.map(([key, value]) => [key, String(value).replace(/\n/g, ' ').trim()])
|
|
4167
|
+
.filter(([, text]) => text !== '')
|
|
4168
|
+
.map(([key, text]) => `- ${key}: ${text}`),
|
|
3962
4169
|
'',
|
|
3963
4170
|
];
|
|
3964
4171
|
fs.appendFileSync(logPath, rows.join('\n'), 'utf8');
|
|
@@ -3975,8 +4182,10 @@ function appendProjectLog(title, fields = {}) {
|
|
|
3975
4182
|
const rows = [
|
|
3976
4183
|
`## ${stamp} · ${title}`,
|
|
3977
4184
|
...Object.entries(fields)
|
|
3978
|
-
.filter(([, value]) => value !== undefined && value !== null
|
|
3979
|
-
.map(([key, value]) =>
|
|
4185
|
+
.filter(([, value]) => value !== undefined && value !== null)
|
|
4186
|
+
.map(([key, value]) => [key, String(value).replace(/\n/g, ' ').trim()])
|
|
4187
|
+
.filter(([, text]) => text !== '')
|
|
4188
|
+
.map(([key, text]) => `- ${key}: ${text}`),
|
|
3980
4189
|
'',
|
|
3981
4190
|
];
|
|
3982
4191
|
fs.appendFileSync(logPath, rows.join('\n'), 'utf8');
|
|
@@ -4834,6 +5043,11 @@ function memberGoal(name, ...args) {
|
|
|
4834
5043
|
experiments: [],
|
|
4835
5044
|
history: [],
|
|
4836
5045
|
};
|
|
5046
|
+
for (const prev of state.goals) {
|
|
5047
|
+
if (prev && prev !== existing && prev.status === 'active') {
|
|
5048
|
+
prev.status = 'superseded';
|
|
5049
|
+
}
|
|
5050
|
+
}
|
|
4837
5051
|
goal.status = 'active';
|
|
4838
5052
|
goal.cadence = cadence || goal.cadence || 'manual';
|
|
4839
5053
|
if (why) goal.why = why;
|
|
@@ -7577,9 +7791,15 @@ function readMemberScope(name, paths) {
|
|
|
7577
7791
|
// and must not gate the next wake, otherwise the loop deadlocks on its own log writes.
|
|
7578
7792
|
function isAutoGeneratedArtifact(filePath, name) {
|
|
7579
7793
|
const memberLogs = `atris/team/${name}/logs/`;
|
|
7794
|
+
const memberSkills = `atris/team/${name}/skills/`;
|
|
7580
7795
|
if (filePath.startsWith(memberLogs)) return true;
|
|
7796
|
+
if (filePath.startsWith(memberSkills)) return true;
|
|
7581
7797
|
if (filePath === `atris/team/${name}/goals.json`) return true;
|
|
7798
|
+
if (filePath === `atris/team/${name}/goals.md`) return true;
|
|
7582
7799
|
if (filePath === `atris/team/${name}/now.md`) return true;
|
|
7800
|
+
if (filePath === `atris/team/${name}/MEMBER.md`) return true;
|
|
7801
|
+
if (filePath === `atris/team/${name}/MISSION.md`) return true;
|
|
7802
|
+
if (filePath === `atris/team/${name}/SOUL.md`) return true;
|
|
7583
7803
|
return false;
|
|
7584
7804
|
}
|
|
7585
7805
|
|
|
@@ -8292,7 +8512,91 @@ async function memberWake(name, ...args) {
|
|
|
8292
8512
|
// switch store unreadable: fail open and continue wake
|
|
8293
8513
|
}
|
|
8294
8514
|
const result = await runMemberWake(name, { execute, confirmed, force, domainInput });
|
|
8295
|
-
|
|
8515
|
+
const work = wakeWorkStepPlan(name, args, result, { execute });
|
|
8516
|
+
if (!work.run) {
|
|
8517
|
+
if (work.reason) result.work_step = { ran: false, skipped: true, reason: work.reason };
|
|
8518
|
+
printJsonOrText(result, wakeBootLines(name, result), asJson);
|
|
8519
|
+
return result;
|
|
8520
|
+
}
|
|
8521
|
+
// Wake then work: one bounded member run on the roster engine, so waking a
|
|
8522
|
+
// member from a terminal (or cron) does something instead of only deciding.
|
|
8523
|
+
if (!asJson) printJsonOrText(result, wakeBootLines(name, result), false);
|
|
8524
|
+
const runArgs = [...(work.objective ? [work.objective] : []), ...work.flags, ...(asJson ? ['--json'] : [])];
|
|
8525
|
+
const run = memberRunWith(name, runArgs, { quiet: asJson });
|
|
8526
|
+
result.work_step = wakeWorkStepSummary(run, work);
|
|
8527
|
+
if (asJson) printJsonOrText(result, [], true);
|
|
8528
|
+
else for (const line of wakeWorkStepLines(result.work_step)) console.log(line);
|
|
8529
|
+
return result;
|
|
8530
|
+
}
|
|
8531
|
+
|
|
8532
|
+
// Wake decisions that leave a member free to work now. Everything else
|
|
8533
|
+
// (ask, wait on a review or dirty files, stop for another reason) keeps the
|
|
8534
|
+
// member's own judgment and only reports it.
|
|
8535
|
+
const WAKE_WORK_DECISIONS = new Set(['tick', 'set_objective', 'create_task']);
|
|
8536
|
+
|
|
8537
|
+
// Whether this wake also runs one bounded work step, and on what. A wake from
|
|
8538
|
+
// a plain terminal (or cron) runs one when the roster names the member's
|
|
8539
|
+
// engine. --plan or --dry-run only plans; --run forces the step; the legacy
|
|
8540
|
+
// --execute path keeps proposing experiments; a codex or agent session around
|
|
8541
|
+
// the CLI does the work itself, so it only plans.
|
|
8542
|
+
function wakeWorkStepPlan(name, args, result, { execute = false } = {}) {
|
|
8543
|
+
if (!name || !result || result.ok === false || result.skipped) return { run: false };
|
|
8544
|
+
if (hasFlag(args, '--plan') || hasFlag(args, '--dry-run')) return { run: false };
|
|
8545
|
+
const forced = hasFlag(args, '--run') || hasFlag(args, '--work');
|
|
8546
|
+
if (execute && !forced) return { run: false };
|
|
8547
|
+
const caller = callerRuntime(args);
|
|
8548
|
+
if (!forced && caller !== 'terminal') return { run: false, reason: `${caller} session does the work itself` };
|
|
8549
|
+
const decision = String(result.decision || '');
|
|
8550
|
+
const freeToWork = WAKE_WORK_DECISIONS.has(decision)
|
|
8551
|
+
|| (decision === 'stop' && result.reason === 'no_active_goal');
|
|
8552
|
+
if (result.needs_user || !freeToWork) {
|
|
8553
|
+
return { run: false, reason: `wake decided ${decision || 'nothing'} (${result.reason || 'no reason'})` };
|
|
8554
|
+
}
|
|
8555
|
+
if (!forced) {
|
|
8556
|
+
const { memberRosterEngine } = require('../lib/member-engine');
|
|
8557
|
+
if (!memberRosterEngine(name)) return { run: false, reason: 'no roster engine for this member; add a build pick to ROSTER.md or pass --run' };
|
|
8558
|
+
}
|
|
8559
|
+
const objective = decision === 'tick'
|
|
8560
|
+
? String(result.active_goal?.title || '').trim()
|
|
8561
|
+
: String(result.autonomous_problem?.problem || result.autonomous_problem?.title || '').trim();
|
|
8562
|
+
const flags = [];
|
|
8563
|
+
for (const flag of ['--minutes', '--hours', '--verify']) {
|
|
8564
|
+
const value = readFlag(args, flag, '');
|
|
8565
|
+
if (value) flags.push(flag, value);
|
|
8566
|
+
}
|
|
8567
|
+
if (!readFlag(args, '--minutes', '') && !readFlag(args, '--hours', '')) flags.push('--minutes', '15');
|
|
8568
|
+
if (!readFlag(args, '--verify', '')) flags.push('--no-verify');
|
|
8569
|
+
return { run: true, objective, flags, caller };
|
|
8570
|
+
}
|
|
8571
|
+
|
|
8572
|
+
function wakeWorkStepSummary(run, work) {
|
|
8573
|
+
const step = run?.work_step || {};
|
|
8574
|
+
return {
|
|
8575
|
+
ran: step.ran === true,
|
|
8576
|
+
ok: run?.ok === true,
|
|
8577
|
+
mission_id: run?.mission_id || null,
|
|
8578
|
+
objective: work.objective || run?.mission?.objective || null,
|
|
8579
|
+
engine: step.engine || null,
|
|
8580
|
+
model: step.model || null,
|
|
8581
|
+
duration_ms: step.duration_ms == null ? null : step.duration_ms,
|
|
8582
|
+
summary: step.summary || null,
|
|
8583
|
+
receipt_path: step.receipt_path || null,
|
|
8584
|
+
review_path: run?.mission?.worktree?.path || null,
|
|
8585
|
+
...(run?.needs_review ? { needs_review: true, ask: run.ask, next_command: run.next_command } : {}),
|
|
8586
|
+
...(run?.ok === false && !run?.needs_review ? { error: run.error || 'the work step did not run' } : {}),
|
|
8587
|
+
};
|
|
8588
|
+
}
|
|
8589
|
+
|
|
8590
|
+
function wakeWorkStepLines(step) {
|
|
8591
|
+
if (!step) return [];
|
|
8592
|
+
if (step.needs_review) return ['', ` ${step.ask}`, ` ${step.next_command}`];
|
|
8593
|
+
if (!step.ran) return ['', ` work step did not run: ${step.error || step.reason || 'unknown'}`];
|
|
8594
|
+
const seconds = step.duration_ms == null ? '' : ` in ${Math.round(step.duration_ms / 1000)}s`;
|
|
8595
|
+
return [
|
|
8596
|
+
'',
|
|
8597
|
+
` worked on ${step.engine}${step.model ? ` ${step.model}` : ''}${seconds}`,
|
|
8598
|
+
...(step.receipt_path ? [` receipt \u00b7 ${step.receipt_path}`] : []),
|
|
8599
|
+
];
|
|
8296
8600
|
}
|
|
8297
8601
|
|
|
8298
8602
|
function memberAlive(name, ...args) {
|
|
@@ -9015,14 +9319,14 @@ function memberBlock(name, experimentId, ...args) {
|
|
|
9015
9319
|
|
|
9016
9320
|
function collectFleetMemberStatus(root = process.cwd()) {
|
|
9017
9321
|
const teamDir = path.join(root, 'atris', 'team');
|
|
9018
|
-
const missionMap = memberRunMissionMap();
|
|
9322
|
+
const missionMap = memberRunMissionMap(root);
|
|
9019
9323
|
return findAllMembers(teamDir)
|
|
9020
9324
|
.filter(isFleetMember)
|
|
9021
9325
|
.map((member) => {
|
|
9022
9326
|
const slug = memberSlugFromRecord(member);
|
|
9023
9327
|
const paths = memberPaths(slug);
|
|
9024
9328
|
const goalPlane = memberGoalPlaneStatus(slug, paths);
|
|
9025
|
-
const mission = memberMissionSummary(slug, missionMap);
|
|
9329
|
+
const mission = memberMissionSummary(slug, missionMap, root);
|
|
9026
9330
|
const activity = memberLastActivity(slug, paths, root);
|
|
9027
9331
|
const verdict = memberVerdict({ mission, activity });
|
|
9028
9332
|
return {
|
|
@@ -9033,10 +9337,18 @@ function collectFleetMemberStatus(root = process.cwd()) {
|
|
|
9033
9337
|
mission: mission.latest ? {
|
|
9034
9338
|
id: mission.latest.id || null,
|
|
9035
9339
|
name: mission.name,
|
|
9340
|
+
objective: mission.objective,
|
|
9036
9341
|
state: mission.state,
|
|
9342
|
+
runner: mission.runner,
|
|
9343
|
+
model: mission.model,
|
|
9344
|
+
worktree_root: mission.worktree_root,
|
|
9345
|
+
worktree_branch: mission.worktree_branch,
|
|
9346
|
+
task_ref: mission.task_ref,
|
|
9347
|
+
current_task_id: mission.current_task_id,
|
|
9037
9348
|
last_tick_at: mission.last_tick_at,
|
|
9038
9349
|
last_tick_age: mission.last_tick_age,
|
|
9039
9350
|
live: mission.live_mission,
|
|
9351
|
+
driver: mission.driver,
|
|
9040
9352
|
} : null,
|
|
9041
9353
|
last_activity: activity,
|
|
9042
9354
|
verdict,
|
|
@@ -9148,10 +9460,18 @@ function memberStatus(name, ...args) {
|
|
|
9148
9460
|
mission: mission.latest ? {
|
|
9149
9461
|
id: mission.latest.id || null,
|
|
9150
9462
|
name: mission.name,
|
|
9463
|
+
objective: mission.objective,
|
|
9151
9464
|
state: mission.state,
|
|
9465
|
+
runner: mission.runner,
|
|
9466
|
+
model: mission.model,
|
|
9467
|
+
worktree_root: mission.worktree_root,
|
|
9468
|
+
worktree_branch: mission.worktree_branch,
|
|
9469
|
+
task_ref: mission.task_ref,
|
|
9470
|
+
current_task_id: mission.current_task_id,
|
|
9152
9471
|
last_tick_at: mission.last_tick_at,
|
|
9153
9472
|
last_tick_age: mission.last_tick_age,
|
|
9154
9473
|
live: mission.live_mission,
|
|
9474
|
+
driver: mission.driver,
|
|
9155
9475
|
} : null,
|
|
9156
9476
|
last_activity: activity,
|
|
9157
9477
|
verdict,
|
|
@@ -9431,13 +9751,1137 @@ async function memberChat(name, ...rest) {
|
|
|
9431
9751
|
return { ok: true };
|
|
9432
9752
|
}
|
|
9433
9753
|
|
|
9754
|
+
const IDEA_ARCHETYPE_RULES = [
|
|
9755
|
+
{
|
|
9756
|
+
slugs: ['auto-improver', 'improver'],
|
|
9757
|
+
pattern: /\b(repeat|recurring|broken\s+test|flaky|error|regression|pain|fix\s+bug|self[- ]improv|crash)\b/i,
|
|
9758
|
+
reason: 'handles recurring errors, regressions, and test fixes',
|
|
9759
|
+
},
|
|
9760
|
+
{
|
|
9761
|
+
slugs: ['validator', 'orb'],
|
|
9762
|
+
pattern: /\b(review|audit|validate|verify|check\s+diff|quality|slop|security)\b/i,
|
|
9763
|
+
reason: 'reviews changes and blocks weak work before merge',
|
|
9764
|
+
},
|
|
9765
|
+
{
|
|
9766
|
+
slugs: ['architect'],
|
|
9767
|
+
pattern: /\b(architect|migration|schema|api\s+design|refactor|boundary)\b/i,
|
|
9768
|
+
reason: 'judges architecture and multi-file design before code changes',
|
|
9769
|
+
},
|
|
9770
|
+
{
|
|
9771
|
+
slugs: ['navigator', 'task-planner'],
|
|
9772
|
+
pattern: /\b(roadmap|plan|spec|break\s+down|map|where\s+is|navigate)\b/i,
|
|
9773
|
+
reason: 'maps the codebase and breaks ideas into clear steps',
|
|
9774
|
+
},
|
|
9775
|
+
{
|
|
9776
|
+
slugs: ['customer-lead'],
|
|
9777
|
+
pattern: /\b(customer|churn|retention|onboarding|support|user\s+feedback|commitment)\b/i,
|
|
9778
|
+
reason: 'turns customer signals into clear commitments and next moves',
|
|
9779
|
+
},
|
|
9780
|
+
{
|
|
9781
|
+
slugs: ['researcher', 'scout'],
|
|
9782
|
+
pattern: /\b(research|benchmark|investigate|compare|study|survey)\b/i,
|
|
9783
|
+
reason: 'researches options and gathers grounded evidence',
|
|
9784
|
+
},
|
|
9785
|
+
{
|
|
9786
|
+
slugs: ['executor', 'builder', 'codex-executor'],
|
|
9787
|
+
pattern: /\b(build|implement|feature|add|wire|create|ship|code|update)\b/i,
|
|
9788
|
+
reason: 'builds and tests concrete product changes',
|
|
9789
|
+
},
|
|
9790
|
+
];
|
|
9791
|
+
|
|
9792
|
+
function pickMemberForIdea(ideaText, root = process.cwd(), preferredMember = '') {
|
|
9793
|
+
const teamDir = path.join(root, 'atris', 'team');
|
|
9794
|
+
const members = findAllMembers(teamDir).filter(isFleetMember);
|
|
9795
|
+
if (!members.length) return null;
|
|
9796
|
+
|
|
9797
|
+
const cleanPreferred = String(preferredMember || '').trim().toLowerCase();
|
|
9798
|
+
if (cleanPreferred) {
|
|
9799
|
+
const match = members.find((m) => memberSlugFromRecord(m).toLowerCase() === cleanPreferred);
|
|
9800
|
+
if (match) {
|
|
9801
|
+
const slug = memberSlugFromRecord(match);
|
|
9802
|
+
return {
|
|
9803
|
+
member: slug,
|
|
9804
|
+
role: match.role || 'Team Member',
|
|
9805
|
+
reason: 'selected directly by you',
|
|
9806
|
+
score: 100,
|
|
9807
|
+
};
|
|
9808
|
+
}
|
|
9809
|
+
}
|
|
9810
|
+
|
|
9811
|
+
const text = String(ideaText || '').trim();
|
|
9812
|
+
const words = text
|
|
9813
|
+
.toLowerCase()
|
|
9814
|
+
.split(/[^a-z0-9]+/)
|
|
9815
|
+
.filter((w) => w.length >= 4);
|
|
9816
|
+
|
|
9817
|
+
let best = null;
|
|
9818
|
+
for (const record of members) {
|
|
9819
|
+
const slug = memberSlugFromRecord(record);
|
|
9820
|
+
let score = 0;
|
|
9821
|
+
let matchedReason = '';
|
|
9822
|
+
|
|
9823
|
+
for (const rule of IDEA_ARCHETYPE_RULES) {
|
|
9824
|
+
if (rule.slugs.includes(slug) && rule.pattern.test(text)) {
|
|
9825
|
+
score += 25 + (rule.slugs[0] === slug ? 5 : 0);
|
|
9826
|
+
matchedReason = rule.reason;
|
|
9827
|
+
break;
|
|
9828
|
+
}
|
|
9829
|
+
}
|
|
9830
|
+
|
|
9831
|
+
const missionFile = path.join(teamDir, slug, 'MISSION.md');
|
|
9832
|
+
const missionText = fs.existsSync(missionFile) ? fs.readFileSync(missionFile, 'utf8') : '';
|
|
9833
|
+
const corpus = `${slug} ${record.role || ''} ${record.description || ''} ${(record.skills || []).join(' ')} ${missionText}`.toLowerCase();
|
|
9834
|
+
|
|
9835
|
+
let overlapCount = 0;
|
|
9836
|
+
for (const word of words) {
|
|
9837
|
+
if (corpus.includes(word)) overlapCount += 1;
|
|
9838
|
+
}
|
|
9839
|
+
score += overlapCount * 4;
|
|
9840
|
+
if (Number(record.skillCount || 0) > 0) score += 2;
|
|
9841
|
+
if (slug === 'executor') score += 3;
|
|
9842
|
+
if (slug === 'auto-improver') score += 2;
|
|
9843
|
+
|
|
9844
|
+
if (!matchedReason) {
|
|
9845
|
+
matchedReason = record.description
|
|
9846
|
+
? compactSentence(record.description, 90)
|
|
9847
|
+
: `fits ${record.role || slug} responsibilities`;
|
|
9848
|
+
}
|
|
9849
|
+
|
|
9850
|
+
const candidate = {
|
|
9851
|
+
member: slug,
|
|
9852
|
+
role: record.role || 'Team Member',
|
|
9853
|
+
reason: matchedReason,
|
|
9854
|
+
score,
|
|
9855
|
+
};
|
|
9856
|
+
if (!best || candidate.score > best.score || (candidate.score === best.score && candidate.member.localeCompare(best.member) < 0)) {
|
|
9857
|
+
best = candidate;
|
|
9858
|
+
}
|
|
9859
|
+
}
|
|
9860
|
+
|
|
9861
|
+
return best;
|
|
9862
|
+
}
|
|
9863
|
+
|
|
9864
|
+
function memberDispatch(...args) {
|
|
9865
|
+
if (!args.length || hasFlag(args, '--help') || hasFlag(args, '-h')) {
|
|
9866
|
+
console.log('Usage: atris member dispatch "<idea>" [--to <member>] [--engine <name>] [--ignite] [--dry-run] [--json]');
|
|
9867
|
+
console.log('Routes your idea to the best-suited team member and starts the work in an isolated checkout.');
|
|
9868
|
+
return;
|
|
9869
|
+
}
|
|
9870
|
+
const asJson = hasFlag(args, '--json');
|
|
9871
|
+
const dryRun = hasFlag(args, '--dry-run');
|
|
9872
|
+
const preferredMember = readFlag(args, '--to', '') || readFlag(args, '--member', '');
|
|
9873
|
+
const valueFlags = [...MEMBER_RUN_START_VALUE_FLAGS, '--to', '--member'];
|
|
9874
|
+
const boolFlags = [...MEMBER_RUN_START_BOOLEAN_FLAGS, '--dry-run'];
|
|
9875
|
+
const ideaText = stripKnownFlags(args, valueFlags, boolFlags).join(' ').trim();
|
|
9876
|
+
if (!ideaText) {
|
|
9877
|
+
console.error('atris member dispatch: idea text is required.');
|
|
9878
|
+
process.exitCode = 2;
|
|
9879
|
+
return;
|
|
9880
|
+
}
|
|
9881
|
+
|
|
9882
|
+
const picked = pickMemberForIdea(ideaText, process.cwd(), preferredMember);
|
|
9883
|
+
if (!picked) {
|
|
9884
|
+
console.error('No active team members found in atris/team/.');
|
|
9885
|
+
process.exitCode = 1;
|
|
9886
|
+
return;
|
|
9887
|
+
}
|
|
9888
|
+
|
|
9889
|
+
const forwardedArgs = [];
|
|
9890
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
9891
|
+
const token = String(args[i] || '');
|
|
9892
|
+
if (token === '--dry-run') continue;
|
|
9893
|
+
if (token === '--to' || token === '--member') {
|
|
9894
|
+
i += 1;
|
|
9895
|
+
continue;
|
|
9896
|
+
}
|
|
9897
|
+
if (token.startsWith('--to=') || token.startsWith('--member=')) continue;
|
|
9898
|
+
forwardedArgs.push(args[i]);
|
|
9899
|
+
}
|
|
9900
|
+
|
|
9901
|
+
const commandPreview = `atris member run ${picked.member} ${JSON.stringify(ideaText)} --ignite`;
|
|
9902
|
+
if (dryRun) {
|
|
9903
|
+
const payload = {
|
|
9904
|
+
ok: true,
|
|
9905
|
+
action: 'member_dispatch',
|
|
9906
|
+
dry_run: true,
|
|
9907
|
+
idea: ideaText,
|
|
9908
|
+
member: picked.member,
|
|
9909
|
+
role: picked.role,
|
|
9910
|
+
reason: picked.reason,
|
|
9911
|
+
command: commandPreview,
|
|
9912
|
+
};
|
|
9913
|
+
printJsonOrText(
|
|
9914
|
+
payload,
|
|
9915
|
+
[
|
|
9916
|
+
`${picked.member} (${picked.role}) will take this because it ${picked.reason.replace(/^[A-Z]/, (c) => c.toLowerCase())}.`,
|
|
9917
|
+
'',
|
|
9918
|
+
commandPreview,
|
|
9919
|
+
],
|
|
9920
|
+
asJson,
|
|
9921
|
+
);
|
|
9922
|
+
return payload;
|
|
9923
|
+
}
|
|
9924
|
+
|
|
9925
|
+
if (!asJson) {
|
|
9926
|
+
console.log(`${picked.member} (${picked.role}) is taking this because it ${picked.reason.replace(/^[A-Z]/, (c) => c.toLowerCase())}.`);
|
|
9927
|
+
}
|
|
9928
|
+
return startMemberRunMission(picked.member, ideaText, forwardedArgs);
|
|
9929
|
+
}
|
|
9930
|
+
|
|
9931
|
+
function scrubFeedJargon(text) {
|
|
9932
|
+
return String(text || '')
|
|
9933
|
+
.replace(/\bmission-\d{4}-\d{2}-\d{2}-[a-z0-9-]+\b/gi, '')
|
|
9934
|
+
.replace(/\b[0-9a-f]{7,40}\b/g, '')
|
|
9935
|
+
.replace(/\s+/g, ' ')
|
|
9936
|
+
.trim();
|
|
9937
|
+
}
|
|
9938
|
+
|
|
9939
|
+
function plainFeedStatusLabel(mission) {
|
|
9940
|
+
const status = String(mission?.status || '').toLowerCase();
|
|
9941
|
+
if (mission?.verifier_result?.passed && (status === 'complete' || status === 'ready')) return 'verified and ready';
|
|
9942
|
+
if (status === 'complete' || status === 'ready') return 'ready for review';
|
|
9943
|
+
if (status === 'running') return 'working now';
|
|
9944
|
+
if (status === 'blocked' || status === 'paused') return 'needs your input';
|
|
9945
|
+
return 'queued';
|
|
9946
|
+
}
|
|
9947
|
+
|
|
9948
|
+
function buildMemberImpactChain({ summary, verified, goalWhy, nextPush, skills = [] }) {
|
|
9949
|
+
const action = compactSentence(scrubFeedJargon(summary || 'Shipped one bounded slice'), 100);
|
|
9950
|
+
const rawConsequence = verified
|
|
9951
|
+
? (goalWhy ? scrubFeedJargon(goalWhy) : 'Verified check passed so this slice stays solid')
|
|
9952
|
+
: (goalWhy ? scrubFeedJargon(goalWhy) : 'Work is in flight toward a verified check');
|
|
9953
|
+
const consequence = compactSentence(rawConsequence, 100);
|
|
9954
|
+
const skillHint = Array.isArray(skills) && skills.length ? `Sharpens ${skills[0]} and queues the next optimization` : 'Queues the next optimization automatically';
|
|
9955
|
+
const compounding = compactSentence(scrubFeedJargon(nextPush || skillHint), 110);
|
|
9956
|
+
return {
|
|
9957
|
+
action,
|
|
9958
|
+
consequence,
|
|
9959
|
+
compounding,
|
|
9960
|
+
arrow: `${action} -> ${consequence} -> ${compounding}`,
|
|
9961
|
+
};
|
|
9962
|
+
}
|
|
9963
|
+
|
|
9964
|
+
function collectMemberWorkFeed(root = process.cwd(), options = {}) {
|
|
9965
|
+
const teamDir = path.join(root, 'atris', 'team');
|
|
9966
|
+
const members = findAllMembers(teamDir).filter(isFleetMember);
|
|
9967
|
+
const roleByMember = new Map(members.map((m) => [memberSlugFromRecord(m), m.role || 'Team Member']));
|
|
9968
|
+
const metaByMember = new Map();
|
|
9969
|
+
for (const m of members) {
|
|
9970
|
+
const slug = memberSlugFromRecord(m);
|
|
9971
|
+
const mDir = path.join(teamDir, slug);
|
|
9972
|
+
const skills = listMemberUnlockedSkills(mDir, m.skills);
|
|
9973
|
+
let goalWhy = null;
|
|
9974
|
+
let goalTitle = null;
|
|
9975
|
+
try {
|
|
9976
|
+
const gp = memberGoalPlaneStatus(slug, { memberDir: mDir, goalsFile: path.join(mDir, 'goals.json') });
|
|
9977
|
+
goalWhy = gp?.goal?.why || null;
|
|
9978
|
+
goalTitle = gp?.goal?.title || null;
|
|
9979
|
+
} catch {}
|
|
9980
|
+
metaByMember.set(slug, { skills, goalWhy, goalTitle });
|
|
9981
|
+
}
|
|
9982
|
+
const filterMember = String(options.member || '').trim().toLowerCase();
|
|
9983
|
+
const limit = Number.isFinite(Number(options.limit)) && Number(options.limit) > 0 ? Number(options.limit) : 15;
|
|
9984
|
+
|
|
9985
|
+
const items = [];
|
|
9986
|
+
const missionMap = memberRunMissionMap(root);
|
|
9987
|
+
for (const mission of missionMap.values()) {
|
|
9988
|
+
const owner = String(mission?.owner || '').trim();
|
|
9989
|
+
if (!owner) continue;
|
|
9990
|
+
if (filterMember && owner.toLowerCase() !== filterMember) continue;
|
|
9991
|
+
const atIso = mission.last_tick_at || mission.updated_at || mission.created_at || null;
|
|
9992
|
+
const atMs = timestampMs(atIso);
|
|
9993
|
+
const summaryText = scrubFeedJargon(mission.last_summary || mission.objective || '');
|
|
9994
|
+
if (!summaryText) continue;
|
|
9995
|
+
const cleanSummary = compactSentence(summaryText, 140);
|
|
9996
|
+
const verified = Boolean(mission?.verifier_result?.passed);
|
|
9997
|
+
const meta = metaByMember.get(owner) || { skills: [], goalWhy: null, goalTitle: null };
|
|
9998
|
+
const nextPush = scrubFeedJargon(mission.last_next_push || meta.goalTitle || '');
|
|
9999
|
+
const impactChain = buildMemberImpactChain({
|
|
10000
|
+
summary: cleanSummary,
|
|
10001
|
+
verified,
|
|
10002
|
+
goalWhy: meta.goalWhy,
|
|
10003
|
+
nextPush,
|
|
10004
|
+
skills: meta.skills,
|
|
10005
|
+
});
|
|
10006
|
+
items.push({
|
|
10007
|
+
member: owner,
|
|
10008
|
+
role: roleByMember.get(owner) || 'Team Member',
|
|
10009
|
+
at: atIso,
|
|
10010
|
+
at_ms: atMs,
|
|
10011
|
+
age: ageLabelFromMs(atMs),
|
|
10012
|
+
summary: cleanSummary,
|
|
10013
|
+
status: plainFeedStatusLabel(mission),
|
|
10014
|
+
verified,
|
|
10015
|
+
next_push: impactChain.compounding,
|
|
10016
|
+
impact_chain: impactChain,
|
|
10017
|
+
});
|
|
10018
|
+
}
|
|
10019
|
+
|
|
10020
|
+
for (const record of members) {
|
|
10021
|
+
const slug = memberSlugFromRecord(record);
|
|
10022
|
+
if (filterMember && slug.toLowerCase() !== filterMember) continue;
|
|
10023
|
+
const logsDir = path.join(teamDir, slug, 'logs');
|
|
10024
|
+
if (!fs.existsSync(logsDir)) continue;
|
|
10025
|
+
const logFiles = fs.readdirSync(logsDir).filter((f) => f.endsWith('.md')).sort().slice(-2);
|
|
10026
|
+
for (const file of logFiles) {
|
|
10027
|
+
const fullPath = path.join(logsDir, file);
|
|
10028
|
+
let raw = '';
|
|
10029
|
+
let mtimeMs = 0;
|
|
10030
|
+
try {
|
|
10031
|
+
raw = fs.readFileSync(fullPath, 'utf8');
|
|
10032
|
+
mtimeMs = fs.statSync(fullPath).mtimeMs || 0;
|
|
10033
|
+
} catch {
|
|
10034
|
+
continue;
|
|
10035
|
+
}
|
|
10036
|
+
const sections = raw.split(/^##\s+/m).slice(1);
|
|
10037
|
+
for (const section of sections.slice(-3)) {
|
|
10038
|
+
const lines = section.trim().split(/\r?\n/);
|
|
10039
|
+
const header = lines[0] || '';
|
|
10040
|
+
const summaryLine = lines.find((l) => /^-\s*(summary|mission|title):/i.test(l));
|
|
10041
|
+
const verifierLine = lines.find((l) => /^-\s*verifier:/i.test(l));
|
|
10042
|
+
const nextPushLine = lines.find((l) => /^-\s*(next_push|next push|next):/i.test(l));
|
|
10043
|
+
const rawSummary = summaryLine
|
|
10044
|
+
? summaryLine.replace(/^-\s*(summary|mission|title):\s*/i, '')
|
|
10045
|
+
: header.replace(/^\d{2}:\d{2}\s*·\s*/, '');
|
|
10046
|
+
const cleanSummary = compactSentence(scrubFeedJargon(rawSummary), 140);
|
|
10047
|
+
if (!cleanSummary) continue;
|
|
10048
|
+
const alreadyInItems = items.some((item) => item.member === slug && item.summary === cleanSummary);
|
|
10049
|
+
if (alreadyInItems) continue;
|
|
10050
|
+
const passed = verifierLine ? /passed/i.test(verifierLine) : false;
|
|
10051
|
+
const meta = metaByMember.get(slug) || { skills: [], goalWhy: null, goalTitle: null };
|
|
10052
|
+
const rawNextPush = nextPushLine
|
|
10053
|
+
? nextPushLine.replace(/^-\s*(next_push|next push|next):\s*/i, '')
|
|
10054
|
+
: (meta.goalTitle || '');
|
|
10055
|
+
const impactChain = buildMemberImpactChain({
|
|
10056
|
+
summary: cleanSummary,
|
|
10057
|
+
verified: passed,
|
|
10058
|
+
goalWhy: meta.goalWhy,
|
|
10059
|
+
nextPush: rawNextPush,
|
|
10060
|
+
skills: meta.skills,
|
|
10061
|
+
});
|
|
10062
|
+
items.push({
|
|
10063
|
+
member: slug,
|
|
10064
|
+
role: record.role || 'Team Member',
|
|
10065
|
+
at: new Date(mtimeMs).toISOString(),
|
|
10066
|
+
at_ms: mtimeMs,
|
|
10067
|
+
age: ageLabelFromMs(mtimeMs),
|
|
10068
|
+
summary: cleanSummary,
|
|
10069
|
+
status: passed ? 'verified and ready' : 'logged update',
|
|
10070
|
+
verified: passed,
|
|
10071
|
+
next_push: impactChain.compounding,
|
|
10072
|
+
impact_chain: impactChain,
|
|
10073
|
+
});
|
|
10074
|
+
}
|
|
10075
|
+
}
|
|
10076
|
+
}
|
|
10077
|
+
|
|
10078
|
+
return items
|
|
10079
|
+
.sort((a, b) => (b.at_ms || 0) - (a.at_ms || 0))
|
|
10080
|
+
.slice(0, limit);
|
|
10081
|
+
}
|
|
10082
|
+
|
|
10083
|
+
function memberFeed(...args) {
|
|
10084
|
+
if (hasFlag(args, '--help') || hasFlag(args, '-h')) {
|
|
10085
|
+
console.log('Usage: atris member feed [--member <name>] [--limit N] [--json]');
|
|
10086
|
+
console.log('Shows a plain-language linear feed of real work across your team.');
|
|
10087
|
+
return;
|
|
10088
|
+
}
|
|
10089
|
+
const asJson = hasFlag(args, '--json');
|
|
10090
|
+
const memberFilter = readFlag(args, '--member', '') || readFlag(args, '--author', '');
|
|
10091
|
+
const limit = readNumberFlag(args, '--limit', 15);
|
|
10092
|
+
const feed = collectMemberWorkFeed(process.cwd(), { member: memberFilter, limit });
|
|
10093
|
+
const lines = !feed.length
|
|
10094
|
+
? ['No team work recorded yet. Run: atris member dispatch "<idea>"']
|
|
10095
|
+
: [
|
|
10096
|
+
'Team work feed',
|
|
10097
|
+
...feed.flatMap((item) => [
|
|
10098
|
+
`${item.age.padEnd(4)} · ${item.member} (${item.role}): ${item.summary} [${item.status}]`,
|
|
10099
|
+
` -> ${item.impact_chain.consequence} -> Next push: ${item.impact_chain.compounding}`,
|
|
10100
|
+
]),
|
|
10101
|
+
];
|
|
10102
|
+
printJsonOrText(
|
|
10103
|
+
{
|
|
10104
|
+
ok: true,
|
|
10105
|
+
action: 'member_feed',
|
|
10106
|
+
items: feed,
|
|
10107
|
+
},
|
|
10108
|
+
lines,
|
|
10109
|
+
asJson,
|
|
10110
|
+
);
|
|
10111
|
+
return feed;
|
|
10112
|
+
}
|
|
10113
|
+
|
|
10114
|
+
function extractMemberNorthStar(memberDir, fallbackDescription = '') {
|
|
10115
|
+
const missionPath = path.join(memberDir, 'MISSION.md');
|
|
10116
|
+
if (fs.existsSync(missionPath)) {
|
|
10117
|
+
try {
|
|
10118
|
+
const raw = fs.readFileSync(missionPath, 'utf8');
|
|
10119
|
+
const northStarMatch = raw.match(/##\s+North Star\s+([^\n#]+)/i);
|
|
10120
|
+
if (northStarMatch && northStarMatch[1].trim()) {
|
|
10121
|
+
return compactSentence(northStarMatch[1].trim(), 160);
|
|
10122
|
+
}
|
|
10123
|
+
const firstPara = raw
|
|
10124
|
+
.split(/\r?\n/)
|
|
10125
|
+
.map((l) => l.trim())
|
|
10126
|
+
.find((l) => l && !l.startsWith('#') && !l.startsWith('-'));
|
|
10127
|
+
if (firstPara) return compactSentence(firstPara, 160);
|
|
10128
|
+
} catch {}
|
|
10129
|
+
}
|
|
10130
|
+
return compactSentence(fallbackDescription || 'Execute bounded, verified work in this member lane.', 160);
|
|
10131
|
+
}
|
|
10132
|
+
|
|
10133
|
+
function listMemberUnlockedSkills(memberDir, frontmatterSkills = []) {
|
|
10134
|
+
const skills = new Set();
|
|
10135
|
+
const skillsDir = path.join(memberDir, 'skills');
|
|
10136
|
+
if (fs.existsSync(skillsDir)) {
|
|
10137
|
+
try {
|
|
10138
|
+
for (const entry of fs.readdirSync(skillsDir)) {
|
|
10139
|
+
if (fs.existsSync(path.join(skillsDir, entry, 'SKILL.md'))) skills.add(entry);
|
|
10140
|
+
}
|
|
10141
|
+
} catch {}
|
|
10142
|
+
}
|
|
10143
|
+
for (const s of Array.isArray(frontmatterSkills) ? frontmatterSkills : []) {
|
|
10144
|
+
if (s && typeof s === 'string') skills.add(s.trim());
|
|
10145
|
+
}
|
|
10146
|
+
return [...skills];
|
|
10147
|
+
}
|
|
10148
|
+
|
|
10149
|
+
function resolveMemberEngineSeat(memberSlug, root = process.cwd()) {
|
|
10150
|
+
const slug = String(memberSlug || '').trim().toLowerCase();
|
|
10151
|
+
const engineRole = slug === 'navigator'
|
|
10152
|
+
? 'navigator'
|
|
10153
|
+
: (slug === 'validator' || slug === 'architect')
|
|
10154
|
+
? 'validator'
|
|
10155
|
+
: 'executor';
|
|
10156
|
+
try {
|
|
10157
|
+
const { resolveEngineForRoleRanked } = require('../lib/engine-registry');
|
|
10158
|
+
const picked = resolveEngineForRoleRanked(engineRole, root, { taskType: engineRole });
|
|
10159
|
+
if (picked && picked.engine) {
|
|
10160
|
+
return {
|
|
10161
|
+
engine_id: picked.engine.id,
|
|
10162
|
+
tier: picked.engine.tier || 'pro',
|
|
10163
|
+
models: Array.isArray(picked.engine.models) ? picked.engine.models : [],
|
|
10164
|
+
role: engineRole,
|
|
10165
|
+
reason: picked.reason || `${picked.engine.id} selected for ${engineRole}.`,
|
|
10166
|
+
};
|
|
10167
|
+
}
|
|
10168
|
+
} catch {}
|
|
10169
|
+
return {
|
|
10170
|
+
engine_id: 'claude',
|
|
10171
|
+
tier: 'pro',
|
|
10172
|
+
models: ['claude'],
|
|
10173
|
+
role: engineRole,
|
|
10174
|
+
reason: 'Default engine seat.',
|
|
10175
|
+
};
|
|
10176
|
+
}
|
|
10177
|
+
|
|
10178
|
+
function buildMemberSubagentShell(owner, role, engineId, objective, root = process.cwd()) {
|
|
10179
|
+
const cleanObjective = compactSentence(objective || `Advance ${owner} roadmap`, 160);
|
|
10180
|
+
const verify = inferMemberRunVerifier(owner, cleanObjective, root);
|
|
10181
|
+
return {
|
|
10182
|
+
role: `${owner} (${role || 'Team Member'})`,
|
|
10183
|
+
member: owner,
|
|
10184
|
+
engine: engineId || 'claude',
|
|
10185
|
+
objective: cleanObjective,
|
|
10186
|
+
verify,
|
|
10187
|
+
prompt: `You are ${owner} (${role || 'Team Member'}). Read atris/team/${owner}/MEMBER.md, MISSION.md, goals.json, and skills/. Execute one bounded slice for: "${cleanObjective}". Run verify: ${verify}. Report 3 lines: Found, Fixed, Next push.`,
|
|
10188
|
+
shell_command: `atris member run ${owner} ${JSON.stringify(cleanObjective)} --engine ${engineId || 'claude'} --ignite`,
|
|
10189
|
+
};
|
|
10190
|
+
}
|
|
10191
|
+
|
|
10192
|
+
function buildMemberCard(name, root = process.cwd()) {
|
|
10193
|
+
const paths = requireMemberDir(name);
|
|
10194
|
+
const owner = paths.storageName || name;
|
|
10195
|
+
let fm = {};
|
|
10196
|
+
try {
|
|
10197
|
+
fm = parseFrontmatter(fs.readFileSync(paths.memberFile, 'utf8')) || {};
|
|
10198
|
+
} catch {}
|
|
10199
|
+
const goalPlane = memberGoalPlaneStatus(owner, paths);
|
|
10200
|
+
const mission = memberMissionSummary(owner, memberRunMissionMap(root), root);
|
|
10201
|
+
const activity = memberLastActivity(owner, paths, root);
|
|
10202
|
+
const verdict = memberVerdict({ mission, activity });
|
|
10203
|
+
const skills = listMemberUnlockedSkills(paths.memberDir, fm.skills);
|
|
10204
|
+
const northStar = extractMemberNorthStar(paths.memberDir, fm.description);
|
|
10205
|
+
const recentWork = collectMemberWorkFeed(root, { member: owner, limit: 3 });
|
|
10206
|
+
const engineSeat = resolveMemberEngineSeat(owner, root);
|
|
10207
|
+
|
|
10208
|
+
const doingNow = mission.latest
|
|
10209
|
+
? {
|
|
10210
|
+
summary: compactSentence(scrubFeedJargon(mission.latest.last_summary || mission.objective || mission.name || ''), 120),
|
|
10211
|
+
state: plainFeedStatusLabel(mission.latest),
|
|
10212
|
+
engine: mission.runner || engineSeat.engine_id || null,
|
|
10213
|
+
model: mission.model || null,
|
|
10214
|
+
worktree_branch: mission.worktree_branch || null,
|
|
10215
|
+
last_tick_age: mission.last_tick_age,
|
|
10216
|
+
}
|
|
10217
|
+
: null;
|
|
10218
|
+
|
|
10219
|
+
const activeGoalTitle = goalPlane.goal?.title || null;
|
|
10220
|
+
const activeGoalWhy = goalPlane.goal?.why || null;
|
|
10221
|
+
const activeGoalNextPush = goalPlane.goal?.next_push || null;
|
|
10222
|
+
const latestItem = recentWork[0] || null;
|
|
10223
|
+
const isVerified = doingNow?.state === 'verified and ready' || Boolean(latestItem?.verified);
|
|
10224
|
+
const impactChain = buildMemberImpactChain({
|
|
10225
|
+
summary: doingNow?.summary || latestItem?.summary || activeGoalTitle || northStar,
|
|
10226
|
+
verified: isVerified,
|
|
10227
|
+
goalWhy: activeGoalWhy || northStar,
|
|
10228
|
+
nextPush: mission.latest?.last_next_push || activeGoalNextPush || activeGoalTitle,
|
|
10229
|
+
skills,
|
|
10230
|
+
});
|
|
10231
|
+
|
|
10232
|
+
const nextMove = mission.live
|
|
10233
|
+
? `atris member run ${owner} --mission ${mission.live.id} --ignite`
|
|
10234
|
+
: activeGoalTitle
|
|
10235
|
+
? `atris member run ${owner} ${JSON.stringify(activeGoalTitle)} --ignite`
|
|
10236
|
+
: `atris interview ${owner}`;
|
|
10237
|
+
const roleLabel = fm.role || 'Team Member';
|
|
10238
|
+
const subagentShell = buildMemberSubagentShell(
|
|
10239
|
+
owner,
|
|
10240
|
+
roleLabel,
|
|
10241
|
+
engineSeat?.engine_id || 'claude',
|
|
10242
|
+
impactChain.compounding || activeGoalTitle || northStar,
|
|
10243
|
+
root,
|
|
10244
|
+
);
|
|
10245
|
+
|
|
10246
|
+
return {
|
|
10247
|
+
member: owner,
|
|
10248
|
+
role: roleLabel,
|
|
10249
|
+
version: fm.version || '1.0.0',
|
|
10250
|
+
verdict,
|
|
10251
|
+
last_activity_age: activity.age,
|
|
10252
|
+
north_star: northStar,
|
|
10253
|
+
engine_seat: engineSeat,
|
|
10254
|
+
skills,
|
|
10255
|
+
impact_chain: impactChain,
|
|
10256
|
+
next_push: impactChain.compounding,
|
|
10257
|
+
subagent_shell: subagentShell,
|
|
10258
|
+
roadmap: {
|
|
10259
|
+
active_goal: activeGoalTitle,
|
|
10260
|
+
why: activeGoalWhy,
|
|
10261
|
+
acceptance: goalPlane.goal?.acceptance || [],
|
|
10262
|
+
doing_now: doingNow,
|
|
10263
|
+
},
|
|
10264
|
+
actions: {
|
|
10265
|
+
go: `atris member go ${owner}`,
|
|
10266
|
+
steer: `atris member steer ${owner} "<direction>"`,
|
|
10267
|
+
one_on_one: `atris member 1on1 ${owner}`,
|
|
10268
|
+
sleep: `atris member sleep ${owner}`,
|
|
10269
|
+
},
|
|
10270
|
+
recent_work: recentWork,
|
|
10271
|
+
next_move: nextMove,
|
|
10272
|
+
};
|
|
10273
|
+
}
|
|
10274
|
+
|
|
10275
|
+
function renderMemberCardLines(card) {
|
|
10276
|
+
const skillsLabel = card.skills.length ? card.skills.join(', ') : 'none yet';
|
|
10277
|
+
const engineLabel = card.engine_seat ? `${card.engine_seat.engine_id} (${card.engine_seat.tier})` : 'auto';
|
|
10278
|
+
const goalLabel = card.roadmap.active_goal || 'none set yet (run atris member 1on1 ' + card.member + ')';
|
|
10279
|
+
const doingLabel = card.roadmap.doing_now
|
|
10280
|
+
? `${card.roadmap.doing_now.summary} [${card.roadmap.doing_now.state}]`
|
|
10281
|
+
: 'idle, ready for next dispatch';
|
|
10282
|
+
const recentLines = card.recent_work.length
|
|
10283
|
+
? card.recent_work.map((item) => ` - ${item.age}: ${item.summary} [${item.status}]`)
|
|
10284
|
+
: [' - no verified runs logged yet'];
|
|
10285
|
+
|
|
10286
|
+
return [
|
|
10287
|
+
`┌─ ${card.member} · ${card.role} (v${card.version}) · ${card.verdict.toLowerCase()} · engine: ${engineLabel}`,
|
|
10288
|
+
`│ North Star: ${card.north_star}`,
|
|
10289
|
+
`│ Skills: ${skillsLabel}`,
|
|
10290
|
+
`│ Roadmap: ${goalLabel}`,
|
|
10291
|
+
`│ Doing now: ${doingLabel}`,
|
|
10292
|
+
`│ Loop: ${card.impact_chain.arrow}`,
|
|
10293
|
+
`│ Next push: ${card.next_push}`,
|
|
10294
|
+
`│ Recent work:`,
|
|
10295
|
+
...recentLines,
|
|
10296
|
+
`└─ Controls: go: ${card.actions.go} | 1on1: ${card.actions.one_on_one} | sleep: ${card.actions.sleep}`,
|
|
10297
|
+
];
|
|
10298
|
+
}
|
|
10299
|
+
|
|
10300
|
+
function memberCard(name, ...args) {
|
|
10301
|
+
if (hasFlag(args, '--help') || hasFlag(args, '-h') || name === '--help' || name === '-h') {
|
|
10302
|
+
console.log('Usage: atris member card <name|--all> [--json]');
|
|
10303
|
+
console.log('Shows a member\'s long-horizon roadmap, unlocked skills, current work, and recent wins.');
|
|
10304
|
+
return;
|
|
10305
|
+
}
|
|
10306
|
+
const asJson = hasFlag(args, '--json') || name === '--json';
|
|
10307
|
+
if (!name || name === '--all' || name === 'all' || name === '--json') {
|
|
10308
|
+
const teamDir = path.join(process.cwd(), 'atris', 'team');
|
|
10309
|
+
const coreOrder = ['auto-improver', 'navigator', 'executor', 'validator', 'architect', 'customer-lead', 'researcher'];
|
|
10310
|
+
const allSlugs = findAllMembers(teamDir)
|
|
10311
|
+
.filter(isFleetMember)
|
|
10312
|
+
.map(memberSlugFromRecord);
|
|
10313
|
+
const ordered = [
|
|
10314
|
+
...coreOrder.filter((s) => allSlugs.includes(s)),
|
|
10315
|
+
...allSlugs.filter((s) => !coreOrder.includes(s) && hasFlag(args, '--all')),
|
|
10316
|
+
];
|
|
10317
|
+
const cards = ordered.map((slug) => buildMemberCard(slug, process.cwd()));
|
|
10318
|
+
printJsonOrText(
|
|
10319
|
+
{ ok: true, action: 'member_card_all', cards },
|
|
10320
|
+
cards.flatMap((c) => [...renderMemberCardLines(c), '']),
|
|
10321
|
+
asJson,
|
|
10322
|
+
);
|
|
10323
|
+
return cards;
|
|
10324
|
+
}
|
|
10325
|
+
const card = buildMemberCard(name, process.cwd());
|
|
10326
|
+
printJsonOrText(
|
|
10327
|
+
{ ok: true, action: 'member_card', card },
|
|
10328
|
+
renderMemberCardLines(card),
|
|
10329
|
+
asJson,
|
|
10330
|
+
);
|
|
10331
|
+
return card;
|
|
10332
|
+
}
|
|
10333
|
+
|
|
10334
|
+
function isKnownMemberSlug(token, root = process.cwd()) {
|
|
10335
|
+
const clean = String(token || '').trim();
|
|
10336
|
+
if (!clean || clean.startsWith('-')) return false;
|
|
10337
|
+
return fs.existsSync(path.join(root, 'atris', 'team', clean, 'MEMBER.md'))
|
|
10338
|
+
|| fs.existsSync(path.join(root, 'atris', 'team', `${clean}.md`));
|
|
10339
|
+
}
|
|
10340
|
+
|
|
10341
|
+
async function memberSteerOrDispatch(...rawArgs) {
|
|
10342
|
+
if (hasFlag(rawArgs, '--help') || hasFlag(rawArgs, '-h')) {
|
|
10343
|
+
console.log('Usage: atris member steer [member] "<direction>" [--dry-run] [--json]');
|
|
10344
|
+
console.log(' atris member "<direction>" [--dry-run] [--json]');
|
|
10345
|
+
console.log('Steers a running member mid-flight or dispatches the best specialist if idle.');
|
|
10346
|
+
return;
|
|
10347
|
+
}
|
|
10348
|
+
const root = process.cwd();
|
|
10349
|
+
const asJson = hasFlag(rawArgs, '--json');
|
|
10350
|
+
const dryRun = hasFlag(rawArgs, '--dry-run');
|
|
10351
|
+
const valueFlags = new Set(['--engine', '--model', '--verify', '--referee-engine', '--minutes', '--hours', '--max-ticks', '--from']);
|
|
10352
|
+
const positionals = [];
|
|
10353
|
+
const flagArgs = [];
|
|
10354
|
+
for (let i = 0; i < rawArgs.length; i++) {
|
|
10355
|
+
const token = String(rawArgs[i] || '');
|
|
10356
|
+
if (!token) continue;
|
|
10357
|
+
if (token.startsWith('-')) {
|
|
10358
|
+
flagArgs.push(token);
|
|
10359
|
+
if (valueFlags.has(token) && i + 1 < rawArgs.length) {
|
|
10360
|
+
flagArgs.push(rawArgs[i + 1]);
|
|
10361
|
+
i++;
|
|
10362
|
+
}
|
|
10363
|
+
continue;
|
|
10364
|
+
}
|
|
10365
|
+
positionals.push(token);
|
|
10366
|
+
}
|
|
10367
|
+
|
|
10368
|
+
let targetMember = null;
|
|
10369
|
+
let prompt = '';
|
|
10370
|
+
let whyPicked = '';
|
|
10371
|
+
if (positionals.length > 0 && isKnownMemberSlug(positionals[0], root)) {
|
|
10372
|
+
targetMember = positionals[0];
|
|
10373
|
+
prompt = positionals.slice(1).join(' ').trim();
|
|
10374
|
+
whyPicked = `Explicitly targeted ${targetMember}.`;
|
|
10375
|
+
} else {
|
|
10376
|
+
prompt = positionals.join(' ').trim();
|
|
10377
|
+
const picked = pickMemberForIdea(prompt, root);
|
|
10378
|
+
targetMember = picked.member;
|
|
10379
|
+
whyPicked = picked.reason || picked.why || `fits ${picked.member} responsibilities`;
|
|
10380
|
+
}
|
|
10381
|
+
|
|
10382
|
+
const paths = requireMemberDir(targetMember);
|
|
10383
|
+
const owner = paths.storageName || targetMember;
|
|
10384
|
+
let fm = {};
|
|
10385
|
+
try {
|
|
10386
|
+
fm = parseFrontmatter(fs.readFileSync(paths.memberFile, 'utf8')) || {};
|
|
10387
|
+
} catch {}
|
|
10388
|
+
const goalPlane = memberGoalPlaneStatus(owner, paths);
|
|
10389
|
+
const effectivePrompt = prompt
|
|
10390
|
+
|| goalPlane.goal?.next_push
|
|
10391
|
+
|| goalPlane.goal?.title
|
|
10392
|
+
|| extractMemberNorthStar(paths.memberDir, '');
|
|
10393
|
+
|
|
10394
|
+
const missionMap = memberRunMissionMap(root);
|
|
10395
|
+
const missionSummary = memberMissionSummary(owner, missionMap, root);
|
|
10396
|
+
const engineSeat = resolveMemberEngineSeat(owner, root);
|
|
10397
|
+
const { autoQueueMemberNextPush, closeMemberHandoffsForReceiver } = require('./mission');
|
|
10398
|
+
const subagentShell = buildMemberSubagentShell(owner, fm.role || 'Team Member', engineSeat.engine_id, effectivePrompt, root);
|
|
10399
|
+
|
|
10400
|
+
if (effectivePrompt) {
|
|
10401
|
+
try {
|
|
10402
|
+
autoQueueMemberNextPush(owner, effectivePrompt, missionSummary.live?.objective || '', root);
|
|
10403
|
+
} catch {}
|
|
10404
|
+
}
|
|
10405
|
+
if (!dryRun) {
|
|
10406
|
+
try {
|
|
10407
|
+
closeMemberHandoffsForReceiver(owner, root);
|
|
10408
|
+
} catch {}
|
|
10409
|
+
}
|
|
10410
|
+
|
|
10411
|
+
if (missionSummary.live) {
|
|
10412
|
+
if (!dryRun) {
|
|
10413
|
+
try {
|
|
10414
|
+
const liveM = missionSummary.live;
|
|
10415
|
+
const targetRoot = liveM.worktree_root || root;
|
|
10416
|
+
const missionsFile = path.join(targetRoot, '.atris', 'state', 'missions.jsonl');
|
|
10417
|
+
const nextPings = [
|
|
10418
|
+
...(Array.isArray(liveM.pings) ? liveM.pings : []),
|
|
10419
|
+
{ at: new Date().toISOString(), from: 'operator', text: effectivePrompt },
|
|
10420
|
+
];
|
|
10421
|
+
const updatedMission = {
|
|
10422
|
+
...liveM,
|
|
10423
|
+
pings: nextPings,
|
|
10424
|
+
last_next_push: effectivePrompt,
|
|
10425
|
+
updated_at: new Date().toISOString(),
|
|
10426
|
+
};
|
|
10427
|
+
fs.mkdirSync(path.dirname(missionsFile), { recursive: true });
|
|
10428
|
+
fs.appendFileSync(missionsFile, `${JSON.stringify(updatedMission)}\n`, 'utf8');
|
|
10429
|
+
} catch {}
|
|
10430
|
+
}
|
|
10431
|
+
const payload = {
|
|
10432
|
+
ok: true,
|
|
10433
|
+
action: 'member_steered',
|
|
10434
|
+
mode: 'steered_live',
|
|
10435
|
+
dry_run: dryRun,
|
|
10436
|
+
member: owner,
|
|
10437
|
+
mission_id: missionSummary.live.id,
|
|
10438
|
+
engine: missionSummary.runner || engineSeat.engine_id,
|
|
10439
|
+
prompt: effectivePrompt,
|
|
10440
|
+
why: whyPicked,
|
|
10441
|
+
subagent_shell: subagentShell,
|
|
10442
|
+
};
|
|
10443
|
+
const lines = [
|
|
10444
|
+
`Steered ${owner} mid-run (${missionSummary.live.id}).`,
|
|
10445
|
+
`Direction: ${effectivePrompt}`,
|
|
10446
|
+
`Consequence: ${owner} will read this steer on the next loop and adjust course.`,
|
|
10447
|
+
];
|
|
10448
|
+
printJsonOrText(payload, lines, asJson);
|
|
10449
|
+
return payload;
|
|
10450
|
+
}
|
|
10451
|
+
|
|
10452
|
+
const command = `atris member run ${owner} ${JSON.stringify(effectivePrompt)} --engine ${engineSeat.engine_id} --ignite`;
|
|
10453
|
+
if (dryRun) {
|
|
10454
|
+
const payload = {
|
|
10455
|
+
ok: true,
|
|
10456
|
+
action: 'member_go',
|
|
10457
|
+
mode: 'dispatched_idle',
|
|
10458
|
+
dry_run: true,
|
|
10459
|
+
member: owner,
|
|
10460
|
+
engine: engineSeat.engine_id,
|
|
10461
|
+
engine_reason: engineSeat.reason,
|
|
10462
|
+
prompt: effectivePrompt,
|
|
10463
|
+
why: whyPicked,
|
|
10464
|
+
command,
|
|
10465
|
+
subagent_shell: subagentShell,
|
|
10466
|
+
};
|
|
10467
|
+
const lines = [
|
|
10468
|
+
`Routed to ${owner} on engine ${engineSeat.engine_id}: ${whyPicked}`,
|
|
10469
|
+
`Objective: ${effectivePrompt}`,
|
|
10470
|
+
`Next: ${command}`,
|
|
10471
|
+
];
|
|
10472
|
+
printJsonOrText(payload, lines, asJson);
|
|
10473
|
+
return payload;
|
|
10474
|
+
}
|
|
10475
|
+
|
|
10476
|
+
if (!asJson) {
|
|
10477
|
+
console.log(`Routed to ${owner} on engine ${engineSeat.engine_id}: ${whyPicked}`);
|
|
10478
|
+
}
|
|
10479
|
+
const hasExplicitEngine = flagArgs.includes('--engine');
|
|
10480
|
+
const nextFlags = hasExplicitEngine ? flagArgs : ['--engine', engineSeat.engine_id, ...flagArgs];
|
|
10481
|
+
return memberRun(owner, effectivePrompt, '--ignite', ...nextFlags);
|
|
10482
|
+
}
|
|
10483
|
+
|
|
10484
|
+
async function memberGo(...rawArgs) {
|
|
10485
|
+
if (hasFlag(rawArgs, '--help') || hasFlag(rawArgs, '-h')) {
|
|
10486
|
+
console.log('Usage: atris member go [member|"<direction>"] [--dry-run] [--json]');
|
|
10487
|
+
console.log('Wakes the highest-leverage member on their next_push, or routes a prompt to the right specialist.');
|
|
10488
|
+
return;
|
|
10489
|
+
}
|
|
10490
|
+
const nonFlags = rawArgs.filter((a) => a && !String(a).startsWith('-'));
|
|
10491
|
+
if (nonFlags.length > 0) {
|
|
10492
|
+
return memberSteerOrDispatch(...rawArgs);
|
|
10493
|
+
}
|
|
10494
|
+
const root = process.cwd();
|
|
10495
|
+
const teamDir = path.join(root, 'atris', 'team');
|
|
10496
|
+
const coreOrder = ['auto-improver', 'executor', 'navigator', 'validator', 'architect', 'customer-lead', 'researcher'];
|
|
10497
|
+
let chosen = 'auto-improver';
|
|
10498
|
+
for (const slug of coreOrder) {
|
|
10499
|
+
const mDir = path.join(teamDir, slug);
|
|
10500
|
+
if (!fs.existsSync(path.join(mDir, 'MEMBER.md'))) continue;
|
|
10501
|
+
const gp = memberGoalPlaneStatus(slug, { memberDir: mDir, goalsFile: path.join(mDir, 'goals.json') });
|
|
10502
|
+
if (gp?.goal?.next_push || gp?.goal?.title) {
|
|
10503
|
+
chosen = slug;
|
|
10504
|
+
break;
|
|
10505
|
+
}
|
|
10506
|
+
}
|
|
10507
|
+
return memberSteerOrDispatch(chosen, ...rawArgs);
|
|
10508
|
+
}
|
|
10509
|
+
|
|
10510
|
+
function memberSleep(target, ...args) {
|
|
10511
|
+
if (hasFlag(args, '--help') || hasFlag(args, '-h') || target === '--help' || target === '-h') {
|
|
10512
|
+
console.log('Usage: atris member sleep [member|--all] [--json]');
|
|
10513
|
+
console.log('Cleanly parks active member loops so your team rests until you wake them.');
|
|
10514
|
+
return;
|
|
10515
|
+
}
|
|
10516
|
+
const root = process.cwd();
|
|
10517
|
+
const asJson = hasFlag(args, '--json') || target === '--json';
|
|
10518
|
+
const filterMember = target && !target.startsWith('-') && target !== 'all' ? target.trim().toLowerCase() : '';
|
|
10519
|
+
const { loadMissionMap } = require('./mission');
|
|
10520
|
+
const missions = loadMissionMap(root);
|
|
10521
|
+
const activeStatuses = new Set(['running', 'planning', 'ready']);
|
|
10522
|
+
const slept = [];
|
|
10523
|
+
|
|
10524
|
+
for (const m of missions.values()) {
|
|
10525
|
+
if (!m || !activeStatuses.has(String(m.status || '').toLowerCase())) continue;
|
|
10526
|
+
const owner = String(m.owner || '').trim();
|
|
10527
|
+
if (filterMember && owner.toLowerCase() !== filterMember) continue;
|
|
10528
|
+
const updated = {
|
|
10529
|
+
...m,
|
|
10530
|
+
status: 'paused',
|
|
10531
|
+
paused_at: new Date().toISOString(),
|
|
10532
|
+
stop_reason: 'member-sleep',
|
|
10533
|
+
next_action: `sleeping; wake with: atris member go ${owner || ''}`.trim(),
|
|
10534
|
+
updated_at: new Date().toISOString(),
|
|
10535
|
+
};
|
|
10536
|
+
const missionsFile = path.join(root, '.atris', 'state', 'missions.jsonl');
|
|
10537
|
+
fs.mkdirSync(path.dirname(missionsFile), { recursive: true });
|
|
10538
|
+
fs.appendFileSync(missionsFile, `${JSON.stringify(updated)}\n`, 'utf8');
|
|
10539
|
+
slept.push({ mission_id: m.id, member: owner || 'mission-lead', objective: m.objective });
|
|
10540
|
+
}
|
|
10541
|
+
|
|
10542
|
+
const payload = {
|
|
10543
|
+
ok: true,
|
|
10544
|
+
action: 'member_sleep',
|
|
10545
|
+
target: filterMember || 'all',
|
|
10546
|
+
slept_count: slept.length,
|
|
10547
|
+
slept,
|
|
10548
|
+
wake_command: filterMember ? `atris member go ${filterMember}` : 'atris member go',
|
|
10549
|
+
};
|
|
10550
|
+
const lines = slept.length
|
|
10551
|
+
? [
|
|
10552
|
+
`Put ${slept.length} active member loop${slept.length === 1 ? '' : 's'} to sleep.`,
|
|
10553
|
+
...slept.map((s) => ` - ${s.member}: ${s.objective}`),
|
|
10554
|
+
`Wake anytime: ${payload.wake_command}`,
|
|
10555
|
+
]
|
|
10556
|
+
: [
|
|
10557
|
+
`All members are already resting.`,
|
|
10558
|
+
`Wake anytime: ${payload.wake_command}`,
|
|
10559
|
+
];
|
|
10560
|
+
printJsonOrText(payload, lines, asJson);
|
|
10561
|
+
return payload;
|
|
10562
|
+
}
|
|
10563
|
+
|
|
10564
|
+
async function memberOneOnOne(name, ...args) {
|
|
10565
|
+
if (!name || name === '--help' || name === '-h' || hasFlag(args, '--help') || hasFlag(args, '-h')) {
|
|
10566
|
+
console.log('Usage: atris member 1on1 <name> [--note "..."]');
|
|
10567
|
+
console.log('Sit down for a 1-on-1 with a team member to review recent work and set their next roadmap goal.');
|
|
10568
|
+
return;
|
|
10569
|
+
}
|
|
10570
|
+
const { interviewCommand } = require('./interview');
|
|
10571
|
+
const code = await interviewCommand([name, ...args]);
|
|
10572
|
+
if (code) process.exitCode = code;
|
|
10573
|
+
return code;
|
|
10574
|
+
}
|
|
10575
|
+
|
|
10576
|
+
function collectMemberHandoffs(root = process.cwd(), cards = []) {
|
|
10577
|
+
const handoffsFile = path.join(root, '.atris', 'state', 'member-handoffs.jsonl');
|
|
10578
|
+
const recorded = readJsonlRowsIfExists(handoffsFile, 20)
|
|
10579
|
+
.filter((h) => h && h.from && h.to && (h.status || 'open') === 'open')
|
|
10580
|
+
.reverse();
|
|
10581
|
+
if (recorded.length > 0) return recorded.slice(0, 6);
|
|
10582
|
+
const relay = {
|
|
10583
|
+
researcher: 'navigator',
|
|
10584
|
+
'customer-lead': 'navigator',
|
|
10585
|
+
navigator: 'executor',
|
|
10586
|
+
architect: 'executor',
|
|
10587
|
+
executor: 'validator',
|
|
10588
|
+
'auto-improver': 'validator',
|
|
10589
|
+
};
|
|
10590
|
+
const inferred = [];
|
|
10591
|
+
for (const c of cards) {
|
|
10592
|
+
const to = relay[c.member];
|
|
10593
|
+
if (!to) continue;
|
|
10594
|
+
const state = c.roadmap?.doing_now?.state || '';
|
|
10595
|
+
if (state === 'verified and ready' || c.verdict === 'ACTIVE') {
|
|
10596
|
+
inferred.push({
|
|
10597
|
+
id: `relay_${c.member}_${to}`,
|
|
10598
|
+
from: c.member,
|
|
10599
|
+
to,
|
|
10600
|
+
summary: compactSentence(c.roadmap?.doing_now?.summary || c.roadmap?.active_goal || c.next_push, 90),
|
|
10601
|
+
status: 'ready',
|
|
10602
|
+
next_command: `atris member go ${to}`,
|
|
10603
|
+
});
|
|
10604
|
+
}
|
|
10605
|
+
}
|
|
10606
|
+
return inferred.slice(0, 4);
|
|
10607
|
+
}
|
|
10608
|
+
|
|
10609
|
+
function collectMemberOneOnOnes(root = process.cwd(), slugs = []) {
|
|
10610
|
+
const queue = [];
|
|
10611
|
+
for (const slug of slugs) {
|
|
10612
|
+
try {
|
|
10613
|
+
const paths = requireMemberDir(slug);
|
|
10614
|
+
const gp = memberGoalPlaneStatus(slug, paths);
|
|
10615
|
+
if (!gp.goal) {
|
|
10616
|
+
queue.push({
|
|
10617
|
+
member: slug,
|
|
10618
|
+
reason: 'No active roadmap goal set yet',
|
|
10619
|
+
command: `atris member 1on1 ${slug}`,
|
|
10620
|
+
});
|
|
10621
|
+
} else if (gp.needsUser) {
|
|
10622
|
+
queue.push({
|
|
10623
|
+
member: slug,
|
|
10624
|
+
reason: gp.ask || 'Blocked and waiting on your answer',
|
|
10625
|
+
command: `atris member 1on1 ${slug}`,
|
|
10626
|
+
});
|
|
10627
|
+
}
|
|
10628
|
+
} catch {}
|
|
10629
|
+
}
|
|
10630
|
+
return queue;
|
|
10631
|
+
}
|
|
10632
|
+
|
|
10633
|
+
function memberHandoff(from, to, ...args) {
|
|
10634
|
+
if (from === '--help' || from === '-h' || hasFlag(args, '--help') || hasFlag(args, '-h')) {
|
|
10635
|
+
console.log('Usage: atris member handoff [<from> <to> "<slice>"] [--json]');
|
|
10636
|
+
console.log('Passes a completed slice from one member to the next specialist and queues their next_push.');
|
|
10637
|
+
return;
|
|
10638
|
+
}
|
|
10639
|
+
const root = process.cwd();
|
|
10640
|
+
const asJson = from === '--json' || to === '--json' || hasFlag(args, '--json');
|
|
10641
|
+
const sliceWords = [to, ...args].filter((a) => a && !String(a).startsWith('-'));
|
|
10642
|
+
if (!from || from.startsWith('-') || sliceWords.length < 2) {
|
|
10643
|
+
const room = buildMemberRoom(root);
|
|
10644
|
+
const lines = room.handoffs.length
|
|
10645
|
+
? [
|
|
10646
|
+
'Active Member Handoffs:',
|
|
10647
|
+
...room.handoffs.map((h) => ` - ${h.from} -> ${h.to}: ${h.summary} (run: ${h.next_command})`),
|
|
10648
|
+
]
|
|
10649
|
+
: ['No open handoffs right now.'];
|
|
10650
|
+
printJsonOrText({ ok: true, action: 'member_handoffs', handoffs: room.handoffs }, lines, asJson);
|
|
10651
|
+
return room.handoffs;
|
|
10652
|
+
}
|
|
10653
|
+
const targetTo = sliceWords[0];
|
|
10654
|
+
const summary = sliceWords.slice(1).join(' ').trim();
|
|
10655
|
+
const { recordMemberHandoff } = require('./mission');
|
|
10656
|
+
const handoff = recordMemberHandoff(from, targetTo, summary, root);
|
|
10657
|
+
if (!handoff) {
|
|
10658
|
+
console.error(`Could not record handoff from ${from} to ${targetTo}. Check member names.`);
|
|
10659
|
+
process.exitCode = 1;
|
|
10660
|
+
return null;
|
|
10661
|
+
}
|
|
10662
|
+
const lines = [
|
|
10663
|
+
`Handoff recorded: ${handoff.from} -> ${handoff.to}`,
|
|
10664
|
+
`Slice: ${handoff.summary}`,
|
|
10665
|
+
`Next: ${handoff.next_command}`,
|
|
10666
|
+
];
|
|
10667
|
+
printJsonOrText({ ok: true, action: 'member_handoff', handoff }, lines, asJson);
|
|
10668
|
+
return handoff;
|
|
10669
|
+
}
|
|
10670
|
+
|
|
10671
|
+
function buildMemberRoom(root = process.cwd()) {
|
|
10672
|
+
const teamDir = path.join(root, 'atris', 'team');
|
|
10673
|
+
const coreOrder = ['auto-improver', 'navigator', 'executor', 'validator', 'architect', 'customer-lead', 'researcher'];
|
|
10674
|
+
const allSlugs = findAllMembers(teamDir)
|
|
10675
|
+
.filter(isFleetMember)
|
|
10676
|
+
.map(memberSlugFromRecord);
|
|
10677
|
+
const ordered = coreOrder.filter((s) => allSlugs.includes(s));
|
|
10678
|
+
const cards = ordered.map((slug) => buildMemberCard(slug, root));
|
|
10679
|
+
const feed = collectMemberWorkFeed(root, { limit: 6 });
|
|
10680
|
+
|
|
10681
|
+
const standup = cards.map((c) => {
|
|
10682
|
+
const status = c.roadmap.doing_now ? c.roadmap.doing_now.state : c.verdict.toLowerCase();
|
|
10683
|
+
const latestUpdate = c.roadmap.doing_now?.summary
|
|
10684
|
+
|| c.recent_work[0]?.summary
|
|
10685
|
+
|| c.roadmap.active_goal
|
|
10686
|
+
|| c.north_star;
|
|
10687
|
+
return {
|
|
10688
|
+
member: c.member,
|
|
10689
|
+
role: c.role,
|
|
10690
|
+
status,
|
|
10691
|
+
engine: c.engine_seat?.engine_id || 'auto',
|
|
10692
|
+
skills_count: c.skills.length,
|
|
10693
|
+
latest_update: compactSentence(latestUpdate, 92),
|
|
10694
|
+
consequence: c.impact_chain.consequence,
|
|
10695
|
+
next_push: compactSentence(c.next_push, 84),
|
|
10696
|
+
subagent_shell: c.subagent_shell,
|
|
10697
|
+
one_on_one: `atris member 1on1 ${c.member}`,
|
|
10698
|
+
};
|
|
10699
|
+
});
|
|
10700
|
+
|
|
10701
|
+
const handoffs = collectMemberHandoffs(root, cards);
|
|
10702
|
+
const oneOnOnes = collectMemberOneOnOnes(root, ordered);
|
|
10703
|
+
|
|
10704
|
+
let readyEngines = [];
|
|
10705
|
+
try {
|
|
10706
|
+
const { engineRegistryView } = require('../lib/engine-registry');
|
|
10707
|
+
readyEngines = engineRegistryView(root)
|
|
10708
|
+
.filter((e) => e.health && e.health.status === 'ready')
|
|
10709
|
+
.map((e) => ({
|
|
10710
|
+
id: e.id,
|
|
10711
|
+
tier: e.tier,
|
|
10712
|
+
roles: e.roles,
|
|
10713
|
+
models: e.models,
|
|
10714
|
+
}));
|
|
10715
|
+
} catch {}
|
|
10716
|
+
|
|
10717
|
+
return {
|
|
10718
|
+
schema: 'atris.member.room.v1',
|
|
10719
|
+
utopia: {
|
|
10720
|
+
percent: 100,
|
|
10721
|
+
loop: 'ACTION: Plain prompt or click -> CONSEQUENCE: Verified two-model execution + SKILL.md lesson -> COMPOUNDING: Self-queued next_push + Moneyball engine routing -> UTOPIA: Autonomous team that improves forever',
|
|
10722
|
+
},
|
|
10723
|
+
engines: readyEngines,
|
|
10724
|
+
members: cards,
|
|
10725
|
+
standup,
|
|
10726
|
+
handoffs,
|
|
10727
|
+
one_on_ones: oneOnOnes,
|
|
10728
|
+
feed,
|
|
10729
|
+
controls: {
|
|
10730
|
+
go: 'atris member go',
|
|
10731
|
+
day: 'atris member day',
|
|
10732
|
+
handoff: 'atris member handoff <from> <to> "<slice>"',
|
|
10733
|
+
steer: 'atris member "<prompt>"',
|
|
10734
|
+
one_on_one: 'atris member 1on1 <member>',
|
|
10735
|
+
sleep: 'atris member sleep',
|
|
10736
|
+
feed: 'atris member feed',
|
|
10737
|
+
},
|
|
10738
|
+
};
|
|
10739
|
+
}
|
|
10740
|
+
|
|
10741
|
+
async function memberStandupWalk(room, args = []) {
|
|
10742
|
+
const asJson = hasFlag(args, '--json');
|
|
10743
|
+
const limit = Number(readNumberFlag(args, '--limit', 0)) || room.standup.length;
|
|
10744
|
+
const rawDecisions = readFlag(args, '--decisions', '');
|
|
10745
|
+
const scripted = rawDecisions
|
|
10746
|
+
? rawDecisions.split('|').map((s) => s.trim())
|
|
10747
|
+
: [];
|
|
10748
|
+
const slice = room.standup.slice(0, Math.max(1, limit));
|
|
10749
|
+
const { autoQueueMemberNextPush } = require('./mission');
|
|
10750
|
+
const results = [];
|
|
10751
|
+
|
|
10752
|
+
let rl = null;
|
|
10753
|
+
if (!scripted.length && process.stdin.isTTY && !asJson) {
|
|
10754
|
+
const readline = require('readline');
|
|
10755
|
+
rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
10756
|
+
}
|
|
10757
|
+
const askLine = (promptText) => new Promise((resolve) => {
|
|
10758
|
+
if (!rl) return resolve('y');
|
|
10759
|
+
rl.question(promptText, (ans) => resolve(String(ans || '').trim()));
|
|
10760
|
+
});
|
|
10761
|
+
|
|
10762
|
+
try {
|
|
10763
|
+
for (let i = 0; i < slice.length; i++) {
|
|
10764
|
+
const s = slice[i];
|
|
10765
|
+
if (!asJson) {
|
|
10766
|
+
console.log(`\n+-----------------------------------------------------------------------+`);
|
|
10767
|
+
console.log(`| ${i + 1}. ${s.member.toUpperCase()} (${s.role})`);
|
|
10768
|
+
console.log(`| Found: ${s.latest_update}`);
|
|
10769
|
+
console.log(`| Push: ${s.next_push}`);
|
|
10770
|
+
console.log(`+-----------------------------------------------------------------------+`);
|
|
10771
|
+
}
|
|
10772
|
+
const rawAns = scripted[i] !== undefined
|
|
10773
|
+
? scripted[i]
|
|
10774
|
+
: await askLine(' Decision [y = approve / n = skip / or type new idea]: ');
|
|
10775
|
+
const cleanAns = String(rawAns || 'y').trim();
|
|
10776
|
+
const lower = cleanAns.toLowerCase();
|
|
10777
|
+
|
|
10778
|
+
if (lower === 'n' || lower === 'no' || lower === 'skip' || lower === 'sleep') {
|
|
10779
|
+
results.push({ member: s.member, decision: 'skip', engine: s.engine, next_push: s.next_push });
|
|
10780
|
+
if (!asJson) console.log(` -> NO: skipped ${s.member} for this cycle.`);
|
|
10781
|
+
} else if (lower === 'y' || lower === 'yes' || lower === '') {
|
|
10782
|
+
autoQueueMemberNextPush(s.member, s.next_push, s.latest_update, process.cwd(), { promote: true });
|
|
10783
|
+
results.push({ member: s.member, decision: 'approved', engine: s.engine, next_push: s.next_push, command: `atris member go ${s.member}`, subagent_shell: s.subagent_shell });
|
|
10784
|
+
if (!asJson) console.log(` -> YES: approved "${s.next_push}" on ${s.engine} (run: atris member go ${s.member})`);
|
|
10785
|
+
} else {
|
|
10786
|
+
autoQueueMemberNextPush(s.member, cleanAns, 'Operator standup steer', process.cwd(), { promote: true });
|
|
10787
|
+
const steeredShell = buildMemberSubagentShell(s.member, s.role, s.engine, cleanAns, process.cwd());
|
|
10788
|
+
results.push({ member: s.member, decision: 'steered', engine: s.engine, next_push: cleanAns, command: `atris member go ${s.member}`, subagent_shell: steeredShell });
|
|
10789
|
+
if (!asJson) console.log(` -> IDEA: steered ${s.member} to "${cleanAns}" on ${s.engine} (run: atris member go ${s.member})`);
|
|
10790
|
+
}
|
|
10791
|
+
}
|
|
10792
|
+
} finally {
|
|
10793
|
+
if (rl) rl.close();
|
|
10794
|
+
}
|
|
10795
|
+
|
|
10796
|
+
const payload = {
|
|
10797
|
+
ok: true,
|
|
10798
|
+
action: 'member_standup_walk',
|
|
10799
|
+
reviewed: results.length,
|
|
10800
|
+
results,
|
|
10801
|
+
};
|
|
10802
|
+
if (asJson) {
|
|
10803
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
10804
|
+
} else {
|
|
10805
|
+
console.log(`\nStandup complete (${results.length} members reviewed). Launch any approved member with: atris member go <name>`);
|
|
10806
|
+
}
|
|
10807
|
+
return payload;
|
|
10808
|
+
}
|
|
10809
|
+
|
|
10810
|
+
async function memberRoom(...args) {
|
|
10811
|
+
if (hasFlag(args, '--help') || hasFlag(args, '-h')) {
|
|
10812
|
+
console.log('Usage: atris member [day|standup|room] [--walk] [--limit N] [--decisions "y|idea|n"] [--json]');
|
|
10813
|
+
console.log('Shows your Workday Briefing & Team Room: member-by-member standup, handoffs, 1-on-1 queue, and controls.');
|
|
10814
|
+
return;
|
|
10815
|
+
}
|
|
10816
|
+
const asJson = hasFlag(args, '--json');
|
|
10817
|
+
const room = buildMemberRoom(process.cwd());
|
|
10818
|
+
if (hasFlag(args, '--walk') || Boolean(readFlag(args, '--decisions', ''))) {
|
|
10819
|
+
return memberStandupWalk(room, args);
|
|
10820
|
+
}
|
|
10821
|
+
const engineSummary = room.engines.length
|
|
10822
|
+
? room.engines.map((e) => `${e.id}(${e.tier})`).join(', ')
|
|
10823
|
+
: 'claude, codex';
|
|
10824
|
+
|
|
10825
|
+
const standupRows = room.standup.flatMap((s, idx) => [
|
|
10826
|
+
`+-----------------------------------------------------------------------+`,
|
|
10827
|
+
`| ${idx + 1}. ${s.member.toUpperCase()} (${s.role}) · ${s.status} · engine:${s.engine}`,
|
|
10828
|
+
`| Found: ${s.latest_update}`,
|
|
10829
|
+
`| Push: ${s.next_push}`,
|
|
10830
|
+
`+-----------------------------------------------------------------------+`,
|
|
10831
|
+
]);
|
|
10832
|
+
|
|
10833
|
+
const handoffRows = room.handoffs.length
|
|
10834
|
+
? room.handoffs.map((h) => ` - ${h.from} -> ${h.to}: ${h.summary} (run: ${h.next_command})`)
|
|
10835
|
+
: [' - No pending baton passes right now.'];
|
|
10836
|
+
|
|
10837
|
+
const oneOnOneRows = room.one_on_ones.length
|
|
10838
|
+
? room.one_on_ones.map((o) => ` - ${o.member}: ${o.reason} -> ${o.command}`)
|
|
10839
|
+
: [' - Zero blocking 1-on-1s today. Every member has an active roadmap and engine seat.'];
|
|
10840
|
+
|
|
10841
|
+
const feedRows = room.feed.length
|
|
10842
|
+
? room.feed.slice(0, 3).flatMap((item) => [
|
|
10843
|
+
` ${item.age.padEnd(4)} · ${item.member}: ${item.summary} [${item.status}]`,
|
|
10844
|
+
` -> ${item.impact_chain.consequence} -> Next push: ${item.impact_chain.compounding}`,
|
|
10845
|
+
])
|
|
10846
|
+
: [' No work recorded yet. Run: atris member go'];
|
|
10847
|
+
|
|
10848
|
+
const lines = [
|
|
10849
|
+
'WORKDAY BRIEFING · TEAM ROOM (100% Autonomous Member Loop)',
|
|
10850
|
+
`Engines ready: ${engineSummary}`,
|
|
10851
|
+
`Loop: ${room.utopia.loop}`,
|
|
10852
|
+
'',
|
|
10853
|
+
'1. Member-by-Member Standup (detail: `atris member card <name>` | rapid walk: `atris member standup --walk`):',
|
|
10854
|
+
...standupRows,
|
|
10855
|
+
'',
|
|
10856
|
+
'2. Handoffs & Baton Passes (`atris member handoff <from> <to> "<slice>"`):',
|
|
10857
|
+
...handoffRows,
|
|
10858
|
+
'',
|
|
10859
|
+
'3. Your 1-on-1s Today (`atris member 1on1 <name>`):',
|
|
10860
|
+
...oneOnOneRows,
|
|
10861
|
+
'',
|
|
10862
|
+
'Live Consequence Feed:',
|
|
10863
|
+
...feedRows,
|
|
10864
|
+
'',
|
|
10865
|
+
'Controls: atris member standup --walk | atris member go | atris member "<prompt>" | atris member 1on1 <name> | atris member sleep',
|
|
10866
|
+
];
|
|
10867
|
+
|
|
10868
|
+
printJsonOrText({ ok: true, action: 'member_room', ...room }, lines, asJson);
|
|
10869
|
+
return room;
|
|
10870
|
+
}
|
|
10871
|
+
|
|
9434
10872
|
async function memberCommand(subcommand, ...args) {
|
|
9435
10873
|
// Subcommands that take a member name as args[0] otherwise treat `--help` as
|
|
9436
10874
|
// a name and error with "Member '--help' not found". `create`/`new` handle
|
|
9437
10875
|
// help themselves (with subcommand-specific usage), leave those alone.
|
|
9438
|
-
const HELP_AWARE_SUBCOMMANDS = new Set([
|
|
9439
|
-
|
|
9440
|
-
|
|
10876
|
+
const HELP_AWARE_SUBCOMMANDS = new Set([
|
|
10877
|
+
'create', 'new', 'run', 'install', 'result', 'dispatch', 'route', 'feed', 'stream',
|
|
10878
|
+
'card', 'cards', 'go', 'work', 'steer', 'sleep', 'rest', '1on1', 'interview',
|
|
10879
|
+
'room', 'ui', 'day', 'standup', 'handoff', 'handoffs',
|
|
10880
|
+
]);
|
|
10881
|
+
const explicitHelp = subcommand === 'help' || subcommand === '--help' || subcommand === '-h'
|
|
10882
|
+
|| (!HELP_AWARE_SUBCOMMANDS.has(subcommand) && (args[0] === '-h' || args[0] === '--help'));
|
|
10883
|
+
if (explicitHelp) {
|
|
10884
|
+
subcommand = 'help';
|
|
9441
10885
|
}
|
|
9442
10886
|
switch (subcommand) {
|
|
9443
10887
|
case 'list':
|
|
@@ -9477,6 +10921,34 @@ async function memberCommand(subcommand, ...args) {
|
|
|
9477
10921
|
if (code) process.exitCode = code;
|
|
9478
10922
|
return undefined;
|
|
9479
10923
|
}
|
|
10924
|
+
case 'go':
|
|
10925
|
+
case 'work':
|
|
10926
|
+
return memberGo(...args);
|
|
10927
|
+
case 'steer':
|
|
10928
|
+
return memberSteerOrDispatch(...args);
|
|
10929
|
+
case 'sleep':
|
|
10930
|
+
case 'rest':
|
|
10931
|
+
return memberSleep(args[0], ...args.slice(1));
|
|
10932
|
+
case '1on1':
|
|
10933
|
+
case 'interview':
|
|
10934
|
+
return memberOneOnOne(args[0], ...args.slice(1));
|
|
10935
|
+
case 'room':
|
|
10936
|
+
case 'ui':
|
|
10937
|
+
case 'day':
|
|
10938
|
+
case 'standup':
|
|
10939
|
+
return memberRoom(...args);
|
|
10940
|
+
case 'handoff':
|
|
10941
|
+
case 'handoffs':
|
|
10942
|
+
return memberHandoff(args[0], args[1], ...args.slice(2));
|
|
10943
|
+
case 'dispatch':
|
|
10944
|
+
case 'route':
|
|
10945
|
+
return memberDispatch(...args);
|
|
10946
|
+
case 'feed':
|
|
10947
|
+
case 'stream':
|
|
10948
|
+
return memberFeed(...args);
|
|
10949
|
+
case 'card':
|
|
10950
|
+
case 'cards':
|
|
10951
|
+
return memberCard(args[0], ...args.slice(1));
|
|
9480
10952
|
case 'ping':
|
|
9481
10953
|
return memberPing(args[0], ...args.slice(1));
|
|
9482
10954
|
case 'loop':
|
|
@@ -9508,11 +10980,31 @@ async function memberCommand(subcommand, ...args) {
|
|
|
9508
10980
|
return memberArchive(args[0]);
|
|
9509
10981
|
case 'purge-archived':
|
|
9510
10982
|
return memberPurgeArchived(...args);
|
|
10983
|
+
case undefined:
|
|
10984
|
+
case '--json':
|
|
10985
|
+
if (fs.existsSync(path.join(process.cwd(), 'atris', 'team'))) {
|
|
10986
|
+
return memberRoom(...(subcommand === '--json' ? ['--json', ...args] : args));
|
|
10987
|
+
}
|
|
10988
|
+
// fall through to help if not in a workspace with atris/team
|
|
9511
10989
|
default:
|
|
10990
|
+
if (subcommand && subcommand !== 'help' && !subcommand.startsWith('-')) {
|
|
10991
|
+
// Natural prompt or member name: `atris member "make checkout faster"` or `atris member navigator`
|
|
10992
|
+
if (isKnownMemberSlug(subcommand, process.cwd()) && args.filter((a) => !String(a).startsWith('-')).length === 0) {
|
|
10993
|
+
return memberCard(subcommand, ...args);
|
|
10994
|
+
}
|
|
10995
|
+
return memberSteerOrDispatch(subcommand, ...args);
|
|
10996
|
+
}
|
|
9512
10997
|
console.log('');
|
|
9513
|
-
console.log('Usage: atris member <subcommand> [name]');
|
|
10998
|
+
console.log('Usage: atris member <subcommand|"<prompt>"> [name]');
|
|
9514
10999
|
console.log('');
|
|
9515
11000
|
console.log('Subcommands:');
|
|
11001
|
+
console.log(' (bare) Open the Team Room (members, engines, live consequence feed)');
|
|
11002
|
+
console.log(' "<prompt>" Steer a running member mid-flight or start the best specialist');
|
|
11003
|
+
console.log(' go [name|"..."] Wake the highest-leverage member on its next_push or run a prompt');
|
|
11004
|
+
console.log(' steer [name] "..." Steer a member mid-run or update its immediate direction');
|
|
11005
|
+
console.log(' 1on1 <name> Sit down for a 1-on-1 to review recent wins and set the roadmap');
|
|
11006
|
+
console.log(' sleep [name|--all] Park active member loops so your team rests until woken');
|
|
11007
|
+
console.log(' room [--json] Render the visual Team Room and desktop UI payload');
|
|
9516
11008
|
console.log(' create <name> Scaffold a new team member (MEMBER.md + dirs) [--push]');
|
|
9517
11009
|
console.log(' install <slug> [--update] Install missing files, or refresh template skills');
|
|
9518
11010
|
console.log(' chat <name> "..." Send a message to this member\'s desk in Atris Desktop');
|
|
@@ -9524,9 +11016,12 @@ async function memberCommand(subcommand, ...args) {
|
|
|
9524
11016
|
console.log(' goal <name> "..." Create/update a member long-term goal');
|
|
9525
11017
|
console.log(' goal-from-mission <name> Create/reuse a goal from MISSION.md and now.md');
|
|
9526
11018
|
console.log(' goal-from-score <name> Create/reuse an active goal from Team score evidence');
|
|
9527
|
-
console.log(' wake <name>
|
|
11019
|
+
console.log(' wake <name> Decide tick/wait/ask/stop, then run one bounded work step on the roster engine (--plan to only decide)');
|
|
9528
11020
|
console.log(' run <name> ["..."] Start a budgeted member mission, or run its active Mission Runtime');
|
|
9529
11021
|
console.log(' result <name> Newest run result: run record, receipt, artifacts, number [--since <iso>] [--json]');
|
|
11022
|
+
console.log(' dispatch "<idea>" Route an idea to the best specialist member and start work');
|
|
11023
|
+
console.log(' feed [--member <n>] Show a plain-language linear feed of real work across the team');
|
|
11024
|
+
console.log(' card [name|--all] Show a member\'s roadmap, unlocked skills, live work, and recent wins');
|
|
9530
11025
|
console.log(' loop <name|--all> Repeat wake on a bounded cadence with a no-overlap lease');
|
|
9531
11026
|
console.log(' tick <name> Propose the next bounded experiment');
|
|
9532
11027
|
console.log(' review <name> <id> Accept/discard an experiment with proof');
|
|
@@ -9586,10 +11081,16 @@ async function memberCommand(subcommand, ...args) {
|
|
|
9586
11081
|
|
|
9587
11082
|
module.exports = {
|
|
9588
11083
|
proposalPromptForGoal,
|
|
11084
|
+
appendMemberGoalLog,
|
|
11085
|
+
appendProjectLog,
|
|
9589
11086
|
memberCommand,
|
|
9590
11087
|
findAllMembers,
|
|
9591
11088
|
findWorkspaceBusinessId,
|
|
9592
11089
|
parseFrontmatter,
|
|
9593
11090
|
wakeBootLines,
|
|
9594
11091
|
buildMemberRunStartArgs,
|
|
11092
|
+
inferMemberRunVerifier,
|
|
11093
|
+
pickMemberForIdea,
|
|
11094
|
+
appendMemberGoalLog,
|
|
11095
|
+
appendProjectLog,
|
|
9595
11096
|
};
|