atris 3.38.0 → 3.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +25 -6
- package/atris/PERSONA.md +8 -4
- package/atris.md +7 -0
- package/ax +2 -1
- package/bin/atris.js +30 -5
- package/commands/agent-spawn.js +13 -11
- package/commands/autoland.js +28 -77
- package/commands/bench.js +10 -12
- package/commands/business.js +345 -0
- package/commands/chat-scan.js +5 -7
- package/commands/codex-goal.js +8 -10
- package/commands/console.js +19 -3
- package/commands/decide.js +166 -0
- package/commands/deck.js +1 -4
- package/commands/drill.js +14 -24
- package/commands/engine.js +196 -8
- package/commands/gm.js +8 -6
- package/commands/harvest.js +1 -4
- package/commands/init.js +23 -3
- package/commands/land.js +8 -14
- package/commands/launchpad.js +1 -14
- package/commands/lifecycle.js +5 -5
- package/commands/log.js +55 -5
- package/commands/member.js +558 -574
- package/commands/mission.js +456 -237
- package/commands/pack.js +2746 -164
- package/commands/play.js +6 -4
- package/commands/probe.js +2 -2
- package/commands/pulse.js +15 -16
- package/commands/release.js +10 -9
- package/commands/router.js +5 -4
- package/commands/site-deploy.js +870 -0
- package/commands/site.js +11 -2
- package/commands/slop.js +14 -2
- package/commands/stream.js +4 -18
- package/commands/task.js +880 -558
- package/commands/taste.js +101 -0
- package/commands/team.js +83 -3
- package/commands/vercel.js +4 -2
- package/commands/voice.js +195 -0
- package/commands/watch.js +1 -22
- package/commands/wiki.js +1 -4
- package/commands/workflow.js +2 -2
- package/commands/worktree.js +1 -14
- package/commands/xp.js +27 -24
- package/lib/accept-verify-gate.js +5 -1
- package/lib/arg-parser.js +41 -0
- package/lib/auto-accept-certified.js +116 -1
- package/lib/autoland.js +66 -0
- package/lib/bench/runner.js +19 -1
- package/lib/context-gatherer.js +7 -1
- package/lib/engine-registry.js +141 -20
- package/lib/falsifier-probe.js +84 -0
- package/lib/fleet.js +65 -15
- package/lib/git-spawn.js +15 -0
- package/lib/json-file.js +37 -0
- package/lib/known-commands.js +2 -2
- package/lib/lesson-preflight.js +146 -0
- package/lib/loop-doctor.js +0 -2
- package/lib/mission-human-asks.js +28 -0
- package/lib/mission-protected-lane.js +4 -1
- package/lib/official-cli-integration.js +47 -2
- package/lib/orb-context.js +8 -1
- package/lib/pack-capabilities.js +685 -0
- package/lib/router-brain.js +51 -1
- package/lib/runner-command.js +0 -6
- package/lib/self-drive.js +44 -13
- package/lib/task-db.js +137 -3
- package/lib/task-decision.js +50 -0
- package/lib/taste-lessons.js +153 -0
- package/lib/tool-result-encode.js +17 -1
- package/lib/voice-gate.js +66 -0
- package/lib/wish-audit.js +1 -1
- package/lib/wish-delegate.js +1 -1
- package/lib/zip.js +95 -7
- package/package.json +2 -1
- package/templates/business-starter/persona.md +9 -0
package/commands/drill.js
CHANGED
|
@@ -3,22 +3,12 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const path = require('path');
|
|
6
|
-
const {
|
|
6
|
+
const { hasFlag } = require('../lib/arg-parser');
|
|
7
|
+
const { runGit } = require('../lib/git-spawn');
|
|
7
8
|
|
|
8
9
|
const VERIFY_COMMAND = 'node -e "process.exit(0)"';
|
|
9
10
|
const COAUTHOR = 'Co-authored-by: Atris <299057014+atris-builder[bot]@users.noreply.github.com>';
|
|
10
11
|
|
|
11
|
-
function hasFlag(args, name) {
|
|
12
|
-
return args.includes(name);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function runGit(args, cwd, options = {}) {
|
|
16
|
-
const result = spawnSync('git', args, { cwd, encoding: 'utf8' });
|
|
17
|
-
if (options.check === false || result.status === 0) return result;
|
|
18
|
-
const output = (result.stderr || result.stdout || `git ${args.join(' ')} failed`).trim();
|
|
19
|
-
throw new Error(output);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
12
|
function parseJsonOutput(text) {
|
|
23
13
|
const raw = String(text || '').trim();
|
|
24
14
|
if (!raw) throw new Error('expected JSON output, got empty output');
|
|
@@ -97,9 +87,9 @@ async function createSandbox(ctx) {
|
|
|
97
87
|
ctx.tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'atris-drill-'));
|
|
98
88
|
ctx.sandboxPath = path.join(ctx.tmpRoot, 'sandbox');
|
|
99
89
|
fs.mkdirSync(ctx.sandboxPath, { recursive: true });
|
|
100
|
-
runGit(['init', '-b', 'master'], ctx.sandboxPath);
|
|
101
|
-
runGit(['config', 'user.email', 'drill@example.invalid'], ctx.sandboxPath);
|
|
102
|
-
runGit(['config', 'user.name', 'Atris Drill'], ctx.sandboxPath);
|
|
90
|
+
runGit(['init', '-b', 'master'], { cwd: ctx.sandboxPath });
|
|
91
|
+
runGit(['config', 'user.email', 'drill@example.invalid'], { cwd: ctx.sandboxPath });
|
|
92
|
+
runGit(['config', 'user.name', 'Atris Drill'], { cwd: ctx.sandboxPath });
|
|
103
93
|
fs.writeFileSync(path.join(ctx.sandboxPath, 'README.md'), '# Atris drill sandbox\n', 'utf8');
|
|
104
94
|
await withSandboxProcessState(ctx.sandboxPath, {
|
|
105
95
|
ATRIS_SKIP_UPDATE_CHECK: '1',
|
|
@@ -108,8 +98,8 @@ async function createSandbox(ctx) {
|
|
|
108
98
|
}, async () => {
|
|
109
99
|
await callInitAtris(ctx.sandboxPath);
|
|
110
100
|
});
|
|
111
|
-
runGit(['add', '-A'], ctx.sandboxPath);
|
|
112
|
-
runGit(['commit', '-m', 'initial sandbox'], ctx.sandboxPath);
|
|
101
|
+
runGit(['add', '-A'], { cwd: ctx.sandboxPath });
|
|
102
|
+
runGit(['commit', '-m', 'initial sandbox'], { cwd: ctx.sandboxPath });
|
|
113
103
|
}
|
|
114
104
|
|
|
115
105
|
async function wishCaptured(ctx) {
|
|
@@ -199,10 +189,10 @@ async function landingShippedLocal(ctx) {
|
|
|
199
189
|
try {
|
|
200
190
|
require('../lib/task-db').close();
|
|
201
191
|
} catch {}
|
|
202
|
-
const dirty = runGit(['status', '--porcelain'], ctx.sandboxPath).stdout.trim();
|
|
192
|
+
const dirty = runGit(['status', '--porcelain'], { cwd: ctx.sandboxPath }).stdout.trim();
|
|
203
193
|
if (dirty) {
|
|
204
|
-
runGit(['add', '-A'], ctx.sandboxPath);
|
|
205
|
-
runGit(['commit', '-m', 'record drill pipeline state'], ctx.sandboxPath);
|
|
194
|
+
runGit(['add', '-A'], { cwd: ctx.sandboxPath });
|
|
195
|
+
runGit(['commit', '-m', 'record drill pipeline state'], { cwd: ctx.sandboxPath });
|
|
206
196
|
}
|
|
207
197
|
ctx.landingWorktree = path.join(ctx.tmpRoot, 'landing-worktree');
|
|
208
198
|
const created = worktree.createAgentWorktree({
|
|
@@ -229,9 +219,9 @@ async function landingShippedLocal(ctx) {
|
|
|
229
219
|
assertOk(shipped.value === 0, `worktree ship exited ${shipped.value}`);
|
|
230
220
|
assertOk(/local mode/.test(`${shipped.stdout}\n${shipped.stderr}`), 'worktree ship did not report local mode');
|
|
231
221
|
});
|
|
232
|
-
runGit(['worktree', 'remove', ctx.landingWorktree, '--force'], ctx.sandboxPath);
|
|
233
|
-
runGit(['branch', '-d', ctx.landingBranch], ctx.sandboxPath);
|
|
234
|
-
const landed = runGit(['show', 'master:drill-landing.txt'], ctx.sandboxPath).stdout;
|
|
222
|
+
runGit(['worktree', 'remove', ctx.landingWorktree, '--force'], { cwd: ctx.sandboxPath });
|
|
223
|
+
runGit(['branch', '-d', ctx.landingBranch], { cwd: ctx.sandboxPath });
|
|
224
|
+
const landed = runGit(['show', 'master:drill-landing.txt'], { cwd: ctx.sandboxPath }).stdout;
|
|
235
225
|
assertOk(landed.trim() === 'local landing ok', 'landing file was not merged into sandbox master');
|
|
236
226
|
}
|
|
237
227
|
|
|
@@ -244,7 +234,7 @@ async function ledgerChecked(ctx) {
|
|
|
244
234
|
assertOk(currentMission, `mission not found: ${ctx.missionId}`);
|
|
245
235
|
assertOk(currentMission.verifier_result && currentMission.verifier_result.passed === true, 'mission verifier result is not passing');
|
|
246
236
|
assertOk(currentMission.receipt_path && fs.existsSync(path.join(ctx.sandboxPath, currentMission.receipt_path)), `mission receipt missing: ${currentMission.receipt_path}`);
|
|
247
|
-
const unmerged = runGit(['branch', '--no-merged', 'master'], ctx.sandboxPath).stdout
|
|
237
|
+
const unmerged = runGit(['branch', '--no-merged', 'master'], { cwd: ctx.sandboxPath }).stdout
|
|
248
238
|
.split(/\r?\n/)
|
|
249
239
|
.map((line) => line.replace(/^\*/, '').trim())
|
|
250
240
|
.filter(Boolean)
|
package/commands/engine.js
CHANGED
|
@@ -26,6 +26,7 @@ const {
|
|
|
26
26
|
} = require('../lib/runner-command');
|
|
27
27
|
const {
|
|
28
28
|
ENGINE_ROLES,
|
|
29
|
+
ENGINE_DUTIES,
|
|
29
30
|
ENGINE_HEALTH_STATUSES,
|
|
30
31
|
binInstalled,
|
|
31
32
|
canonicalEngineName,
|
|
@@ -34,6 +35,9 @@ const {
|
|
|
34
35
|
readEngineRegistry,
|
|
35
36
|
resolveEngineForRole,
|
|
36
37
|
resolveEngineForRoleRanked,
|
|
38
|
+
requireEngineBin,
|
|
39
|
+
engineDoctorReport,
|
|
40
|
+
setEngineOverrides,
|
|
37
41
|
setEngineHealth,
|
|
38
42
|
} = require('../lib/engine-registry');
|
|
39
43
|
const { FLEET_CAPABLE, runDispatchFlight } = require('../lib/fleet');
|
|
@@ -90,14 +94,22 @@ function readSavedEngine(root = process.cwd()) {
|
|
|
90
94
|
|
|
91
95
|
// The default engine for this workspace, in precedence order:
|
|
92
96
|
// env (per-run flags land here) -> .atris/engine.json -> house default.
|
|
93
|
-
//
|
|
97
|
+
// When nothing is configured the pick comes from the registry's saved health
|
|
98
|
+
// (seeded once on first sight, refreshed by `atris engine doctor`), never a
|
|
99
|
+
// machine probe: policy over probes. A stale "ready" is the execution stage's
|
|
100
|
+
// problem, where requireEngineBin fails in one plain sentence.
|
|
94
101
|
function resolveDefaultEngine(root = process.cwd()) {
|
|
95
102
|
const env = canonicalEngineName(process.env.ATRIS_RUNNER_PROFILE);
|
|
96
103
|
if (env) return { name: env, source: 'env' };
|
|
97
104
|
const saved = readSavedEngine(root);
|
|
98
105
|
if (saved) return { name: saved, source: 'saved' };
|
|
99
|
-
|
|
100
|
-
|
|
106
|
+
const ready = new Set(
|
|
107
|
+
engineRegistryView(root)
|
|
108
|
+
.filter((engine) => engine.health && engine.health.status === 'ready')
|
|
109
|
+
.map((engine) => engine.id)
|
|
110
|
+
);
|
|
111
|
+
if (ready.has(HOUSE_ENGINE)) return { name: HOUSE_ENGINE, source: 'house' };
|
|
112
|
+
const fallback = RUNNER_PROFILE_NAMES.find((name) => ready.has(name));
|
|
101
113
|
return fallback ? { name: fallback, source: 'detected' } : { name: HOUSE_ENGINE, source: 'none' };
|
|
102
114
|
}
|
|
103
115
|
|
|
@@ -887,13 +899,16 @@ function printRoster(root) {
|
|
|
887
899
|
const found = list.filter((e) => e.installed).length;
|
|
888
900
|
const current = resolveDefaultEngine(root);
|
|
889
901
|
console.log('');
|
|
890
|
-
console.log(` engines
|
|
902
|
+
console.log(` engines: ${found} intelligence${found === 1 ? '' : 's'} found`);
|
|
891
903
|
console.log('');
|
|
892
904
|
for (const engine of list) {
|
|
893
905
|
const mark = engine.default ? '→' : ' ';
|
|
894
906
|
const state = engine.health.status === 'not_installed' ? 'not installed' : engine.health.status.replace(/_/g, ' ');
|
|
895
907
|
const roles = engine.roles.join(',');
|
|
896
908
|
console.log(` ${mark} ${engine.id.padEnd(12)} ${state.padEnd(13)} ${engine.tier.padEnd(4)} ${roles}`);
|
|
909
|
+
const details = [`models: ${engine.models.join(', ')}`];
|
|
910
|
+
if (engine.duty) details.push(`duty: ${engine.duty}`);
|
|
911
|
+
console.log(` ${details.join(' ')}`);
|
|
897
912
|
}
|
|
898
913
|
console.log('');
|
|
899
914
|
console.log(` default: ${current.name}${current.source === 'saved' ? ' (set here)' : current.source === 'env' ? ' (this session)' : ''}`);
|
|
@@ -901,6 +916,139 @@ function printRoster(root) {
|
|
|
901
916
|
console.log('');
|
|
902
917
|
}
|
|
903
918
|
|
|
919
|
+
function chartEngineLabel(engine) {
|
|
920
|
+
const id = String(engine && (engine.id || engine.name) || '').trim();
|
|
921
|
+
const models = Array.isArray(engine && engine.models)
|
|
922
|
+
? engine.models.map((model) => String(model || '').trim()).filter(Boolean)
|
|
923
|
+
: [];
|
|
924
|
+
return models.length ? `${id}: ${models.join(', ')}` : id;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
function chartBox(title, engines, note = '', emptyLabel = 'unassigned') {
|
|
928
|
+
const labels = engines.length ? engines.map(chartEngineLabel) : (emptyLabel ? [emptyLabel] : []);
|
|
929
|
+
const body = [title, ...labels, ...(note ? [note] : [])];
|
|
930
|
+
const innerWidth = Math.max(...body.map((line) => line.length)) + 2;
|
|
931
|
+
return {
|
|
932
|
+
width: innerWidth + 2,
|
|
933
|
+
lines: [
|
|
934
|
+
`┌${'─'.repeat(innerWidth)}┐`,
|
|
935
|
+
...body.map((line) => `│ ${line.padEnd(innerWidth - 2)} │`),
|
|
936
|
+
`└${'─'.repeat(innerWidth)}┘`,
|
|
937
|
+
],
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
function centerChartLine(line, width, center = Math.floor((width - 1) / 2)) {
|
|
942
|
+
const left = Math.max(0, center - Math.floor(line.length / 2));
|
|
943
|
+
return `${' '.repeat(left)}${line}`.padEnd(width);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
function renderEngineChart(registry) {
|
|
947
|
+
const engines = Array.isArray(registry) ? registry : ((registry && registry.engines) || []);
|
|
948
|
+
const specialDuties = new Set(ENGINE_DUTIES);
|
|
949
|
+
const leaders = engines.filter((entry) => entry.duty === 'leader');
|
|
950
|
+
const rolePool = engines.filter((entry) => !specialDuties.has(entry.duty));
|
|
951
|
+
const builders = rolePool.filter((entry) => Array.isArray(entry.roles) && entry.roles.includes('executor'));
|
|
952
|
+
const checkers = rolePool.filter((entry) => Array.isArray(entry.roles) && entry.roles.includes('validator'));
|
|
953
|
+
const errands = engines.filter((entry) => entry.duty === 'errands');
|
|
954
|
+
const apprentices = engines.filter((entry) => entry.duty === 'learning');
|
|
955
|
+
|
|
956
|
+
const ownerBox = chartBox('owner', [], '', '');
|
|
957
|
+
const leaderBox = chartBox('leader', leaders);
|
|
958
|
+
const columns = [
|
|
959
|
+
chartBox('builders', builders),
|
|
960
|
+
chartBox('checkers', checkers),
|
|
961
|
+
chartBox('errands', errands),
|
|
962
|
+
];
|
|
963
|
+
const gap = 3;
|
|
964
|
+
const totalWidth = columns.reduce((sum, column) => sum + column.width, 0) + (gap * (columns.length - 1));
|
|
965
|
+
const columnHeight = Math.max(...columns.map((column) => column.lines.length));
|
|
966
|
+
const columnLines = [];
|
|
967
|
+
for (let row = 0; row < columnHeight; row += 1) {
|
|
968
|
+
columnLines.push(columns.map((column) => (column.lines[row] || '').padEnd(column.width)).join(' '.repeat(gap)));
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
const centers = [];
|
|
972
|
+
let offset = 0;
|
|
973
|
+
for (const column of columns) {
|
|
974
|
+
centers.push(offset + Math.floor(column.width / 2));
|
|
975
|
+
offset += column.width + gap;
|
|
976
|
+
}
|
|
977
|
+
const fleetCenter = centers[1];
|
|
978
|
+
const branch = Array(totalWidth).fill(' ');
|
|
979
|
+
for (let i = centers[0]; i <= centers[centers.length - 1]; i += 1) branch[i] = '─';
|
|
980
|
+
branch[centers[0]] = '┌';
|
|
981
|
+
branch[centers[centers.length - 1]] = '┐';
|
|
982
|
+
for (const center of centers.slice(1, -1)) branch[center] = '┬';
|
|
983
|
+
branch[fleetCenter] = centers.includes(fleetCenter) ? '┼' : '┴';
|
|
984
|
+
const stems = Array(totalWidth).fill(' ');
|
|
985
|
+
for (const center of centers) stems[center] = '│';
|
|
986
|
+
|
|
987
|
+
const lines = [
|
|
988
|
+
...ownerBox.lines.map((line) => centerChartLine(line, totalWidth, fleetCenter)),
|
|
989
|
+
centerChartLine('│', totalWidth, fleetCenter),
|
|
990
|
+
...leaderBox.lines.map((line) => centerChartLine(line, totalWidth, fleetCenter)),
|
|
991
|
+
centerChartLine('│', totalWidth, fleetCenter),
|
|
992
|
+
branch.join(''),
|
|
993
|
+
stems.join(''),
|
|
994
|
+
...columnLines,
|
|
995
|
+
];
|
|
996
|
+
if (apprentices.length) {
|
|
997
|
+
const apprenticeBox = chartBox('apprentice', apprentices, 'learning the system');
|
|
998
|
+
lines.push(centerChartLine('│', totalWidth, fleetCenter));
|
|
999
|
+
lines.push(...apprenticeBox.lines.map((line) => centerChartLine(line, totalWidth, fleetCenter)));
|
|
1000
|
+
}
|
|
1001
|
+
return lines.map((line) => line.replace(/\s+$/, '')).join('\n');
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
function printEngineChart(root) {
|
|
1005
|
+
console.log(renderEngineChart(readEngineRegistry(root)));
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
function flagValue(args, flag) {
|
|
1009
|
+
const equals = `${flag}=`;
|
|
1010
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
1011
|
+
const value = String(args[i] || '');
|
|
1012
|
+
if (value === flag) return { present: true, value: args[i + 1] || '' };
|
|
1013
|
+
if (value.startsWith(equals)) return { present: true, value: value.slice(equals.length) };
|
|
1014
|
+
}
|
|
1015
|
+
return { present: false, value: '' };
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
function runSetEngineCommand(args, root) {
|
|
1019
|
+
const name = String(args[0] || '').trim();
|
|
1020
|
+
const dutyFlag = flagValue(args.slice(1), '--duty');
|
|
1021
|
+
const modelsFlag = flagValue(args.slice(1), '--models');
|
|
1022
|
+
const duty = String(dutyFlag.value || '').trim();
|
|
1023
|
+
const models = String(modelsFlag.value || '').split(',').map((model) => model.trim()).filter(Boolean);
|
|
1024
|
+
if (!name || (!dutyFlag.present && !modelsFlag.present)) {
|
|
1025
|
+
console.error('usage: atris engine set <name> [--duty leader|errands|learning] [--models "a, b"]');
|
|
1026
|
+
return 2;
|
|
1027
|
+
}
|
|
1028
|
+
if (dutyFlag.present && !ENGINE_DUTIES.includes(duty)) {
|
|
1029
|
+
console.error(`unknown duty "${duty}". known duties: ${ENGINE_DUTIES.join(', ')}`);
|
|
1030
|
+
return 2;
|
|
1031
|
+
}
|
|
1032
|
+
if (modelsFlag.present && !models.length) {
|
|
1033
|
+
console.error('models must include at least one name');
|
|
1034
|
+
return 2;
|
|
1035
|
+
}
|
|
1036
|
+
try {
|
|
1037
|
+
const updated = setEngineOverrides(name, {
|
|
1038
|
+
...(dutyFlag.present ? { duty } : {}),
|
|
1039
|
+
...(modelsFlag.present ? { models } : {}),
|
|
1040
|
+
}, root);
|
|
1041
|
+
const details = [];
|
|
1042
|
+
if (updated.duty) details.push(`duty ${updated.duty}`);
|
|
1043
|
+
if (updated.models) details.push(`models ${updated.models.join(', ')}`);
|
|
1044
|
+
console.log(`${updated.id} updated: ${details.join('; ')}`);
|
|
1045
|
+
return 0;
|
|
1046
|
+
} catch (err) {
|
|
1047
|
+
console.error(String(err.message || err).replace(/^Unknown/, 'unknown'));
|
|
1048
|
+
return 2;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
904
1052
|
function registryPayload(root) {
|
|
905
1053
|
const current = resolveDefaultEngine(root);
|
|
906
1054
|
const registry = readEngineRegistry(root);
|
|
@@ -956,6 +1104,28 @@ function runResolveCommand(args, root) {
|
|
|
956
1104
|
return 0;
|
|
957
1105
|
}
|
|
958
1106
|
|
|
1107
|
+
// Doctor is the one opt-in place that probes the machine: it checks every
|
|
1108
|
+
// engine binary, reports installed state, and folds ready/not_installed flips
|
|
1109
|
+
// back into the policy file. Routing itself never probes.
|
|
1110
|
+
function runDoctorCommand(args, root) {
|
|
1111
|
+
const json = args.includes('--json');
|
|
1112
|
+
const engines = engineDoctorReport(root);
|
|
1113
|
+
if (json) {
|
|
1114
|
+
console.log(JSON.stringify({ ok: true, engines }, null, 2));
|
|
1115
|
+
return 0;
|
|
1116
|
+
}
|
|
1117
|
+
console.log('');
|
|
1118
|
+
for (const engine of engines) {
|
|
1119
|
+
const state = engine.installed ? 'installed' : 'missing';
|
|
1120
|
+
const health = engine.health.status.replace(/_/g, ' ');
|
|
1121
|
+
console.log(` ${engine.id.padEnd(12)} ${String(engine.bin).padEnd(14)} ${state.padEnd(10)} health: ${health}`);
|
|
1122
|
+
}
|
|
1123
|
+
console.log('');
|
|
1124
|
+
console.log(' probed just now and saved to the registry; routing reads that saved policy, never the machine.');
|
|
1125
|
+
console.log('');
|
|
1126
|
+
return 0;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
959
1129
|
function runHealthCommand(args, root) {
|
|
960
1130
|
const json = args.includes('--json');
|
|
961
1131
|
const positional = args.filter((a) => !String(a).startsWith('--'));
|
|
@@ -1203,9 +1373,12 @@ function runDispatchCommand(args, root) {
|
|
|
1203
1373
|
return 2;
|
|
1204
1374
|
}
|
|
1205
1375
|
}
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1376
|
+
// Execution stage: this is where the binary must exist. Routing above never
|
|
1377
|
+
// probed the machine; a missing CLI fails loudly here instead.
|
|
1378
|
+
try {
|
|
1379
|
+
requireEngineBin(canonical);
|
|
1380
|
+
} catch (err) {
|
|
1381
|
+
console.error(`engine dispatch: ${err.message}`);
|
|
1209
1382
|
return 2;
|
|
1210
1383
|
}
|
|
1211
1384
|
return runDispatchFlight({ root, taskIds, engine: canonical, prompt: promptOverride, yolo, ...(base ? { checkoutBase: base } : {}) }).then((flight) => {
|
|
@@ -1248,6 +1421,19 @@ function engineCommand(args = [], deps = {}) {
|
|
|
1248
1421
|
return runHealthCommand(args.slice(args.indexOf('health') + 1), root);
|
|
1249
1422
|
}
|
|
1250
1423
|
|
|
1424
|
+
if (sub === 'doctor') {
|
|
1425
|
+
return runDoctorCommand(args.slice(args.indexOf('doctor') + 1), root);
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
if (sub === 'set') {
|
|
1429
|
+
return runSetEngineCommand(args.slice(args.indexOf('set') + 1), root);
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
if (sub === 'chart' || args.includes('--chart')) {
|
|
1433
|
+
printEngineChart(root);
|
|
1434
|
+
return 0;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1251
1437
|
if (!sub || sub === 'list' || sub === 'status') {
|
|
1252
1438
|
if (json) {
|
|
1253
1439
|
console.log(JSON.stringify(registryPayload(root), null, 2));
|
|
@@ -1266,7 +1452,7 @@ function engineCommand(args = [], deps = {}) {
|
|
|
1266
1452
|
}
|
|
1267
1453
|
|
|
1268
1454
|
if (sub === 'help') {
|
|
1269
|
-
console.log('\n atris engine roster + current default\n atris engine list --json full registry: default + engines with tier, roles, fallback, health\n atris engine resolve <role> [--json]\n choose the best ready engine for navigator|executor|validator\n atris engine health <name> --set ready|not_installed|credit_out\n flip runtime health, for example when credits run out\n atris engine <name> make that engine the default here\n atris engine test [name] preflight: run the engine CLI headless, report pass/fail\n atris engine dispatch <task-id> [<task-id> ...] --engine cursor|codex [--prompt-file <f>] [--yolo]\n one-command claim, worktree, build, verify, ship, ready\n atris engine login <provider> --yes\n upload a local provider CLI login to the backend vault\n atris engine login <provider> --computer [--seat <name>]\n atris engine login <provider> --business <id> [--seat <name>]\n sign in on an Atris computer by device flow\n atris engine login --list | --remove <provider>\n list or remove vaulted provider logins\n atris engine seats show which named accounts are ready to work\n atris engine seed <provider> --business <id>|--user\n push a vaulted login onto an Atris computer\n atris engine reset back to the house default\n --engine <name> one run on that engine (mission run / autopilot / run)\n');
|
|
1455
|
+
console.log('\n atris engine roster + current default\n atris engines --chart show the fleet as an org chart\n atris engine list --json full registry: default + engines with tier, roles, fallback, health\n atris engine set <name> --duty leader|errands|learning [--models "a, b"]\n arrange the fleet and save its model policy\n atris engine resolve <role> [--json]\n choose the best ready engine for navigator|executor|validator\n atris engine health <name> --set ready|not_installed|credit_out\n flip runtime health, for example when credits run out\n atris engine doctor [--json]\n probe which engine CLIs are installed here and sync that into health policy\n atris engine <name> make that engine the default here\n atris engine test [name] preflight: run the engine CLI headless, report pass/fail\n atris engine dispatch <task-id> [<task-id> ...] --engine cursor|codex [--prompt-file <f>] [--yolo]\n one-command claim, worktree, build, verify, ship, ready\n atris engine login <provider> --yes\n upload a local provider CLI login to the backend vault\n atris engine login <provider> --computer [--seat <name>]\n atris engine login <provider> --business <id> [--seat <name>]\n sign in on an Atris computer by device flow\n atris engine login --list | --remove <provider>\n list or remove vaulted provider logins\n atris engine seats show which named accounts are ready to work\n atris engine seed <provider> --business <id>|--user\n push a vaulted login onto an Atris computer\n atris engine reset back to the house default\n --engine <name> one run on that engine (mission run / autopilot / run)\n');
|
|
1270
1456
|
return 0;
|
|
1271
1457
|
}
|
|
1272
1458
|
|
|
@@ -1304,6 +1490,8 @@ module.exports = {
|
|
|
1304
1490
|
readEngineRegistry,
|
|
1305
1491
|
resolveEngineForRole,
|
|
1306
1492
|
setEngineHealth,
|
|
1493
|
+
setEngineOverrides,
|
|
1494
|
+
renderEngineChart,
|
|
1307
1495
|
parseDispatchArgs,
|
|
1308
1496
|
runDispatchCommand,
|
|
1309
1497
|
ENGINE_LOGIN_MANIFESTS,
|
package/commands/gm.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const path = require('path');
|
|
6
|
+
const { hasFlag: hasExactFlag } = require('../lib/arg-parser');
|
|
6
7
|
|
|
7
8
|
const AGENTXP_LEADERBOARD_URL = 'https://api.atris.ai/api/agentxp/leaderboard';
|
|
8
9
|
const AGENTXP_GLOBAL_SYNC_RULE = 'Run atris login, then sync. Owner-provided sync tokens are guided-demo fallback only.';
|
|
@@ -54,8 +55,9 @@ function flag(args, name) {
|
|
|
54
55
|
return null;
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
// This command treats --name=value as flag presence as well as a standalone flag.
|
|
59
|
+
function hasFlagOrValue(args, name) {
|
|
60
|
+
return hasExactFlag(args, name) || args.some(arg => arg.startsWith(`${name}=`));
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
function positional(args) {
|
|
@@ -185,7 +187,7 @@ function pickSeedPlayer(workspaceRoot, tasks, args = []) {
|
|
|
185
187
|
}
|
|
186
188
|
|
|
187
189
|
function ensureStarterMission(taskDb, db, workspaceRoot, tasks, args = []) {
|
|
188
|
-
if (
|
|
190
|
+
if (hasFlagOrValue(args, '--no-seed')) return { tasks, seeded: null };
|
|
189
191
|
if (!fs.existsSync(path.join(workspaceRoot, 'atris'))) return { tasks, seeded: null };
|
|
190
192
|
if (activeAgentXpTasks(tasks).length) return { tasks, seeded: null };
|
|
191
193
|
|
|
@@ -658,7 +660,7 @@ async function gmCommand(...args) {
|
|
|
658
660
|
return memberCommand('wake', member, ...passthrough);
|
|
659
661
|
}
|
|
660
662
|
|
|
661
|
-
if (!
|
|
663
|
+
if (!hasFlagOrValue(args, '--json') && !hasFlagOrValue(args, '--watch')) {
|
|
662
664
|
try {
|
|
663
665
|
const { gameCommand } = require('./game');
|
|
664
666
|
const dashboardShown = await gameCommand([], { silentMissing: true });
|
|
@@ -669,12 +671,12 @@ async function gmCommand(...args) {
|
|
|
669
671
|
}
|
|
670
672
|
|
|
671
673
|
const state = gmState(args);
|
|
672
|
-
if (
|
|
674
|
+
if (hasFlagOrValue(args, '--json')) {
|
|
673
675
|
console.log(JSON.stringify(state, null, 2));
|
|
674
676
|
return;
|
|
675
677
|
}
|
|
676
678
|
render(state);
|
|
677
|
-
if (
|
|
679
|
+
if (hasFlagOrValue(args, '--watch')) {
|
|
678
680
|
await gmWatch(args);
|
|
679
681
|
}
|
|
680
682
|
}
|
package/commands/harvest.js
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const { hasFlag } = require('../lib/arg-parser');
|
|
3
4
|
const {
|
|
4
5
|
isGenericInboxPlaceholder,
|
|
5
6
|
seedInboxFromMove,
|
|
6
7
|
} = require('../lib/next-moves');
|
|
7
8
|
|
|
8
|
-
function hasFlag(args, name) {
|
|
9
|
-
return args.includes(name);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
9
|
function safeRead(file) {
|
|
13
10
|
try { return fs.readFileSync(file, 'utf8'); } catch { return ''; }
|
|
14
11
|
}
|
package/commands/init.js
CHANGED
|
@@ -282,6 +282,7 @@ function initAtris() {
|
|
|
282
282
|
const printReadyCounts = () => {
|
|
283
283
|
console.log(`workspace files ready (${ready.workspace.size})`);
|
|
284
284
|
console.log(`team members ready (${ready.team.size})`);
|
|
285
|
+
console.log('more members are available with atris member create <name>');
|
|
285
286
|
console.log(`agent adapters ready (${ready.adapters.size})`);
|
|
286
287
|
console.log(`skills installed (${ready.skills.size})`);
|
|
287
288
|
};
|
|
@@ -601,8 +602,8 @@ function initAtris() {
|
|
|
601
602
|
|
|
602
603
|
|
|
603
604
|
// Copy team members (MEMBER.md format — directory per member with skills/tools/context)
|
|
604
|
-
const
|
|
605
|
-
|
|
605
|
+
const starterMembers = ['navigator', 'executor', 'validator', 'mission-lead', 'improver'];
|
|
606
|
+
starterMembers.forEach(name => {
|
|
606
607
|
const sourceFile = path.join(__dirname, '..', 'atris', 'team', name, 'MEMBER.md');
|
|
607
608
|
const targetMemberDir = path.join(teamDir, name);
|
|
608
609
|
const targetFile = path.join(targetMemberDir, 'MEMBER.md');
|
|
@@ -644,7 +645,7 @@ tools: []
|
|
|
644
645
|
# Insert persona, workflow, and rules below
|
|
645
646
|
`;
|
|
646
647
|
fs.writeFileSync(path.join(templateTargetDir, 'MEMBER.md'), templateContent);
|
|
647
|
-
markReady('
|
|
648
|
+
markReady('workspace', 'team/_template/MEMBER.md', '✓ Created team/_template/MEMBER.md');
|
|
648
649
|
}
|
|
649
650
|
|
|
650
651
|
// Detect project context and generate profile
|
|
@@ -676,6 +677,25 @@ atris atris.md
|
|
|
676
677
|
|
|
677
678
|
This is the Atris boot sequence. Show the output to the user, then respond naturally.
|
|
678
679
|
|
|
680
|
+
## The way of talking (every reply, no exceptions)
|
|
681
|
+
|
|
682
|
+
Talk like a person. This governs every message you send the operator: boot, status, summaries, everything.
|
|
683
|
+
|
|
684
|
+
- Plain words. Say what happened and what it means for the reader, cause and effect. Not the machinery.
|
|
685
|
+
- No task codes, branch names, commit hashes, PR numbers, or system nouns (worktree, verifier, projection, tick) in the message body. If the reader needs a command, put ONE copyable line at the end.
|
|
686
|
+
- No insider terms without defining them in the same breath. A sharp non-engineer must follow it on first read.
|
|
687
|
+
- Lead with the outcome. Complete sentences, one or two per paragraph, blank line between. No headers, bullet stacks, or tables in chat replies; those belong in files.
|
|
688
|
+
- Detail lives in files and receipts. Offer "want the detail?" instead of dumping it.
|
|
689
|
+
- No em dashes anywhere; use a comma, colon, or period.
|
|
690
|
+
- The test before sending: read it fried at 2am. If decoding takes work, rewrite it.
|
|
691
|
+
|
|
692
|
+
copy these shapes:
|
|
693
|
+
- **Status:** "The reply check is built. I am running the final checks now, so the result is not ready yet."
|
|
694
|
+
- **Landing:** "Replies now get a plain-language check before they reach you. The checks passed, and the change is ready."
|
|
695
|
+
- **Failure:** "The plain-language check could not run because its model was unavailable. Your reply still went through, so no work was blocked."
|
|
696
|
+
|
|
697
|
+
Canonical source: \`atris/atris.md\` \`## voice\`. This block is a copy for tools that only read \`AGENTS.md\`; if they differ, atris.md wins.
|
|
698
|
+
|
|
679
699
|
## Core Files
|
|
680
700
|
|
|
681
701
|
Atris is the source of truth. This file is only an adapter for tools that read
|
package/commands/land.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const {
|
|
4
|
-
|
|
3
|
+
const { runGit } = require('../lib/git-spawn');
|
|
5
4
|
const { listWorktrees, statusCounts } = require('./worktree');
|
|
6
5
|
|
|
7
6
|
const DEFAULT_TTL_DAYS = 7;
|
|
@@ -14,14 +13,6 @@ function countLabel(n, word) {
|
|
|
14
13
|
return `${n} ${word}${n === 1 ? '' : 's'}`;
|
|
15
14
|
}
|
|
16
15
|
|
|
17
|
-
function runGit(args, { cwd = process.cwd(), check = true, maxBuffer } = {}) {
|
|
18
|
-
const result = spawnSync('git', args, { cwd, encoding: 'utf8', ...(maxBuffer ? { maxBuffer } : {}) });
|
|
19
|
-
if (check && result.status !== 0) {
|
|
20
|
-
throw new Error(`git ${args.join(' ')} failed: ${(result.stderr || result.stdout || '').trim()}`);
|
|
21
|
-
}
|
|
22
|
-
return result;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
16
|
function repoRoot(cwd = process.cwd()) {
|
|
26
17
|
const result = runGit(['rev-parse', '--show-toplevel'], { cwd, check: false });
|
|
27
18
|
if (result.status !== 0) return '';
|
|
@@ -614,7 +605,9 @@ function printReceipt(receipt) {
|
|
|
614
605
|
// fresh-grace keeps are healthy active work, not a call to action —
|
|
615
606
|
// "needs a human" on those trained readers to ignore the real ones.
|
|
616
607
|
for (const k of receipt.keptWorktrees || []) {
|
|
608
|
+
// The receipt keeps the raw codes; the human line speaks in sentences.
|
|
617
609
|
if (String(k).includes('(fresh_worktree_grace)')) console.log(` in use, left alone: ${k.replace(' (fresh_worktree_grace)', '')}`);
|
|
610
|
+
else if (String(k).includes('(current_checkout)')) console.log(` you are standing in it, left alone: ${k.replace(' (current_checkout)', '')}`);
|
|
618
611
|
else console.log(` ✋ kept, needs a human: ${k}`);
|
|
619
612
|
}
|
|
620
613
|
for (const m of receipt.keptMovedBranches || []) console.log(` moved since scan, left alone: ${m}`);
|
|
@@ -624,7 +617,8 @@ function printReceipt(receipt) {
|
|
|
624
617
|
console.log('');
|
|
625
618
|
}
|
|
626
619
|
|
|
627
|
-
|
|
620
|
+
// Preserve split-only parsing and its fallback for missing or falsey values.
|
|
621
|
+
function readFollowingFlag(args, name, fallback = '') {
|
|
628
622
|
const idx = args.indexOf(name);
|
|
629
623
|
if (idx === -1) return fallback;
|
|
630
624
|
return args[idx + 1] || fallback;
|
|
@@ -665,10 +659,10 @@ function landCommand(args = []) {
|
|
|
665
659
|
console.error('not a git repository');
|
|
666
660
|
return 1;
|
|
667
661
|
}
|
|
668
|
-
const ttlRaw =
|
|
662
|
+
const ttlRaw = readFollowingFlag(args, '--ttl', '');
|
|
669
663
|
const ttlParsed = Number(ttlRaw);
|
|
670
664
|
const ttlDays = ttlRaw !== '' && Number.isFinite(ttlParsed) && ttlParsed >= 0 ? ttlParsed : DEFAULT_TTL_DAYS;
|
|
671
|
-
const base =
|
|
665
|
+
const base = readFollowingFlag(args, '--base', '');
|
|
672
666
|
const json = args.includes('--json');
|
|
673
667
|
|
|
674
668
|
if (args.includes('--reap')) {
|
|
@@ -679,7 +673,7 @@ function landCommand(args = []) {
|
|
|
679
673
|
}
|
|
680
674
|
|
|
681
675
|
const lookupArgs = args[0] === 'status' ? args.slice(1) : args;
|
|
682
|
-
const name = lookupArgs.find((a) => !a.startsWith('-') && a !==
|
|
676
|
+
const name = lookupArgs.find((a) => !a.startsWith('-') && a !== readFollowingFlag(lookupArgs, '--ttl') && a !== readFollowingFlag(lookupArgs, '--base'));
|
|
683
677
|
if (name) {
|
|
684
678
|
const story = collectStory(root, name, { base });
|
|
685
679
|
if (!story) {
|
package/commands/launchpad.js
CHANGED
|
@@ -2,23 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
|
+
const { hasFlag, readFlag } = require('../lib/arg-parser');
|
|
5
6
|
|
|
6
7
|
const ACTIVE_MISSION_STATUSES = new Set(['planning', 'running', 'ready']);
|
|
7
8
|
|
|
8
|
-
function hasFlag(args, name) {
|
|
9
|
-
return args.includes(name);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function readFlag(args, name, fallback = '') {
|
|
13
|
-
const prefix = `${name}=`;
|
|
14
|
-
for (let i = 0; i < args.length; i += 1) {
|
|
15
|
-
const arg = String(args[i]);
|
|
16
|
-
if (arg === name && args[i + 1] && !String(args[i + 1]).startsWith('--')) return String(args[i + 1]);
|
|
17
|
-
if (arg.startsWith(prefix)) return arg.slice(prefix.length);
|
|
18
|
-
}
|
|
19
|
-
return fallback;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
9
|
function readJson(file) {
|
|
23
10
|
try {
|
|
24
11
|
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
package/commands/lifecycle.js
CHANGED
|
@@ -168,7 +168,7 @@ async function sleepAtris() {
|
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
/**
|
|
171
|
-
* Wake a sleeping workspace
|
|
171
|
+
* Wake a sleeping workspace: remote wake request, or local wake flag.
|
|
172
172
|
* @returns {Promise<void>}
|
|
173
173
|
*/
|
|
174
174
|
async function wakeAtris() {
|
|
@@ -179,7 +179,7 @@ async function wakeAtris() {
|
|
|
179
179
|
if (!slug || slug === '--help' || slug === '-h' || slug === 'help') {
|
|
180
180
|
console.log('Usage: atris wake [business|member] [--loop <loop-id>]');
|
|
181
181
|
console.log('');
|
|
182
|
-
console.log(' Wake a sleeping workspace.
|
|
182
|
+
console.log(' Wake a sleeping workspace. Wake signal sent; loops pick it up on their next pass.');
|
|
183
183
|
console.log(' Or flip a local member / member-loop switch awake.');
|
|
184
184
|
process.exit(0);
|
|
185
185
|
}
|
|
@@ -207,13 +207,13 @@ async function wakeAtris() {
|
|
|
207
207
|
console.log(`Waking business computer '${business.name || slug}'...`);
|
|
208
208
|
|
|
209
209
|
if (result.data && result.data.status === 'running' && result.data.endpoint) {
|
|
210
|
-
console.log(`Business computer '${business.name || slug}' is alive.
|
|
210
|
+
console.log(`Business computer '${business.name || slug}' is alive. Wake signal sent; loops pick it up on their next pass.`);
|
|
211
211
|
return;
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
const running = await waitForBusinessComputer(creds.token, business);
|
|
215
215
|
if (running) {
|
|
216
|
-
console.log(`Business computer '${business.name || slug}' is alive.
|
|
216
|
+
console.log(`Business computer '${business.name || slug}' is alive. Wake signal sent; loops pick it up on their next pass.`);
|
|
217
217
|
return;
|
|
218
218
|
}
|
|
219
219
|
|
|
@@ -246,7 +246,7 @@ async function wakeAtris() {
|
|
|
246
246
|
});
|
|
247
247
|
|
|
248
248
|
if (status.ok && status.data && status.data.status === 'running') {
|
|
249
|
-
console.log(`Workspace '${slug}' is alive.
|
|
249
|
+
console.log(`Workspace '${slug}' is alive. Wake signal sent; loops pick it up on their next pass.`);
|
|
250
250
|
return;
|
|
251
251
|
}
|
|
252
252
|
}
|