atris 3.16.1 → 3.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -7
- package/atris/skills/atris/SKILL.md +15 -2
- package/atris/skills/atris-feedback/SKILL.md +7 -0
- package/atris/skills/design/SKILL.md +29 -2
- package/atris/skills/engines/SKILL.md +44 -0
- package/atris/skills/flow/SKILL.md +1 -1
- package/atris/skills/wake/SKILL.md +37 -0
- package/atris/skills/youtube/SKILL.md +13 -39
- package/atris/team/validator/MEMBER.md +1 -0
- package/atris/wiki/concepts/agent-activation-contract.md +3 -3
- package/atris/wiki/concepts/workspace-initialization-contract.md +3 -3
- package/atris/wiki/index.md +1 -0
- package/atris.md +43 -19
- package/bin/atris.js +400 -30
- package/commands/agent-spawn.js +480 -0
- package/commands/analytics.js +6 -3
- package/commands/apps.js +11 -0
- package/commands/autopilot.js +42 -18
- package/commands/brain.js +74 -7
- package/commands/brainstorm.js +9 -58
- package/commands/clean.js +1 -4
- package/commands/compile.js +9 -4
- package/commands/console.js +8 -3
- package/commands/deck.js +135 -0
- package/commands/init.js +22 -11
- package/commands/lesson.js +76 -0
- package/commands/member.js +252 -48
- package/commands/mission.js +405 -13
- package/commands/now.js +4 -2
- package/commands/probe.js +105 -27
- package/commands/pulse.js +504 -0
- package/commands/radar.js +1 -0
- package/commands/recap.js +55 -25
- package/commands/run.js +615 -22
- package/commands/slop.js +173 -0
- package/commands/spaceship.js +39 -0
- package/commands/sync.js +0 -2
- package/commands/task.js +429 -37
- package/commands/verify.js +7 -3
- package/lib/activity-stream.js +166 -0
- package/lib/auto-accept-certified.js +23 -1
- package/lib/context-gatherer.js +170 -0
- package/lib/escape-regexp.js +13 -0
- package/lib/file-ops.js +6 -3
- package/lib/journal.js +1 -1
- package/lib/lesson-contradiction.js +113 -0
- package/lib/policy-lessons.js +3 -2
- package/lib/pulse.js +401 -0
- package/lib/runner-command.js +156 -0
- package/lib/slides-deck.js +236 -0
- package/lib/state-detection.js +1 -4
- package/lib/task-db.js +101 -4
- package/lib/task-proof.js +1 -1
- package/lib/todo-fallback.js +2 -1
- package/lib/todo-sections.js +33 -0
- package/package.json +1 -2
- package/utils/api.js +14 -2
- package/atris/atrisDev.md +0 -717
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
const { spawnSync } = require('child_process');
|
|
5
|
+
|
|
6
|
+
const SPAWN_SCHEMA = 'atris.agent_spawn.v1';
|
|
7
|
+
const DOGFOOD_SCHEMA = 'atris.agent_cli_dogfood.v1';
|
|
8
|
+
const SPAWN_STATE_REL = path.join('.atris', 'state', 'agent_spawns.jsonl');
|
|
9
|
+
const ALLOWED_ENGINES = new Set(['manual', 'codex', 'claude', 'cursor', 'devin', 'droid']);
|
|
10
|
+
const DOGFOOD_ENGINES = new Set(['devin', 'droid']);
|
|
11
|
+
|
|
12
|
+
function nowIso() {
|
|
13
|
+
return new Date().toISOString();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function spawnStatePath(root = process.cwd()) {
|
|
17
|
+
return path.join(root, SPAWN_STATE_REL);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function ensureSpawnState(root = process.cwd()) {
|
|
21
|
+
const statePath = spawnStatePath(root);
|
|
22
|
+
fs.mkdirSync(path.dirname(statePath), { recursive: true });
|
|
23
|
+
return statePath;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function readJsonl(filePath) {
|
|
27
|
+
try {
|
|
28
|
+
if (!fs.existsSync(filePath)) return [];
|
|
29
|
+
return fs.readFileSync(filePath, 'utf8')
|
|
30
|
+
.split(/\r?\n/)
|
|
31
|
+
.map(line => line.trim())
|
|
32
|
+
.filter(Boolean)
|
|
33
|
+
.map((line) => JSON.parse(line));
|
|
34
|
+
} catch {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function loadSpawnRequests(root = process.cwd()) {
|
|
40
|
+
return readJsonl(spawnStatePath(root));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function appendSpawnRequest(root, request) {
|
|
44
|
+
const statePath = ensureSpawnState(root);
|
|
45
|
+
fs.appendFileSync(statePath, `${JSON.stringify(request)}\n`);
|
|
46
|
+
return statePath;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function idForSpawn(role) {
|
|
50
|
+
const stamp = new Date().toISOString().replace(/[-:.TZ]/g, '').slice(0, 14);
|
|
51
|
+
const suffix = crypto.randomBytes(3).toString('hex');
|
|
52
|
+
const cleanRole = String(role || 'agent').toLowerCase().replace(/[^a-z0-9-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 24) || 'agent';
|
|
53
|
+
return `spawn-${stamp}-${cleanRole}-${suffix}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function flagValue(args, names) {
|
|
57
|
+
for (const name of names) {
|
|
58
|
+
const inline = args.find(arg => arg.startsWith(`${name}=`));
|
|
59
|
+
if (inline) return inline.slice(name.length + 1);
|
|
60
|
+
const idx = args.indexOf(name);
|
|
61
|
+
if (idx !== -1 && idx < args.length - 1 && !String(args[idx + 1]).startsWith('--')) return args[idx + 1];
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function hasFlag(args, names) {
|
|
67
|
+
return names.some(name => args.includes(name));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function positionalArgs(args) {
|
|
71
|
+
const out = [];
|
|
72
|
+
for (let i = 0; i < args.length; i++) {
|
|
73
|
+
const arg = args[i];
|
|
74
|
+
if (arg.startsWith('--')) {
|
|
75
|
+
if (!arg.includes('=') && i < args.length - 1 && !String(args[i + 1]).startsWith('--')) i++;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
out.push(arg);
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function shellQuote(value) {
|
|
84
|
+
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function timestampForFile(date = new Date()) {
|
|
88
|
+
return date.toISOString().replace(/[:.]/g, '-');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function runsDir(root = process.cwd()) {
|
|
92
|
+
const atrisRuns = path.join(root, 'atris', 'runs');
|
|
93
|
+
if (fs.existsSync(path.join(root, 'atris'))) return atrisRuns;
|
|
94
|
+
return path.join(root, '.atris', 'runs');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function commandOnPath(name, deps = {}) {
|
|
98
|
+
if (typeof deps.commandOnPath === 'function') return deps.commandOnPath(name);
|
|
99
|
+
const runner = deps.spawnSync || spawnSync;
|
|
100
|
+
const result = runner('which', [name], { encoding: 'utf8', timeout: 1000 });
|
|
101
|
+
return result.status === 0 ? String(result.stdout || '').trim() : null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function runCli(argv, deps = {}, options = {}) {
|
|
105
|
+
const runner = deps.spawnSync || spawnSync;
|
|
106
|
+
const [cmd, ...args] = argv;
|
|
107
|
+
const started = Date.now();
|
|
108
|
+
const result = runner(cmd, args, {
|
|
109
|
+
cwd: options.cwd || deps.root || process.cwd(),
|
|
110
|
+
encoding: 'utf8',
|
|
111
|
+
timeout: options.timeoutMs || 30000,
|
|
112
|
+
maxBuffer: 1024 * 1024 * 4,
|
|
113
|
+
env: options.env || process.env,
|
|
114
|
+
});
|
|
115
|
+
return {
|
|
116
|
+
command: argv.join(' '),
|
|
117
|
+
status: result.status,
|
|
118
|
+
signal: result.signal || null,
|
|
119
|
+
ok: !result.error && result.status === 0,
|
|
120
|
+
duration_ms: Date.now() - started,
|
|
121
|
+
error: result.error ? result.error.message : null,
|
|
122
|
+
stdout: String(result.stdout || ''),
|
|
123
|
+
stderr: String(result.stderr || ''),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function buildDelegatePrompt({ role, task, cwd }) {
|
|
128
|
+
return [
|
|
129
|
+
`You are an Atris delegated ${role}.`,
|
|
130
|
+
'',
|
|
131
|
+
`Workspace: ${cwd || process.cwd()}`,
|
|
132
|
+
`Task: ${task}`,
|
|
133
|
+
'',
|
|
134
|
+
'Do one bounded proof-backed pass.',
|
|
135
|
+
'Do not revert unrelated edits.',
|
|
136
|
+
'Return changed files, verifier commands, and remaining risk.',
|
|
137
|
+
].join('\n');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function commandForEngine(request) {
|
|
141
|
+
const prompt = buildDelegatePrompt(request);
|
|
142
|
+
if (request.engine === 'codex') return `codex exec ${shellQuote(prompt)}`;
|
|
143
|
+
if (request.engine === 'claude') return `claude -p ${shellQuote(prompt)}`;
|
|
144
|
+
if (request.engine === 'cursor') return `cursor-agent ${shellQuote(prompt)}`;
|
|
145
|
+
if (request.engine === 'devin') return `devin --model glm-5.2 --permission-mode auto -p ${shellQuote(prompt)}`;
|
|
146
|
+
if (request.engine === 'droid') return `droid exec --model glm-5.2 --reasoning-effort off ${shellQuote(prompt)}`;
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function parseSpawnArgs(args = []) {
|
|
151
|
+
const help = args.length === 0 || hasFlag(args, ['--help', '-h']) || args[0] === 'help';
|
|
152
|
+
if (help) return { help: true };
|
|
153
|
+
|
|
154
|
+
const json = hasFlag(args, ['--json']);
|
|
155
|
+
const dryRun = hasFlag(args, ['--dry-run']);
|
|
156
|
+
const roleFlag = flagValue(args, ['--role']);
|
|
157
|
+
const engine = String(flagValue(args, ['--engine']) || 'manual').toLowerCase();
|
|
158
|
+
if (!ALLOWED_ENGINES.has(engine)) throw new Error(`Unknown engine: ${engine}`);
|
|
159
|
+
|
|
160
|
+
const pos = positionalArgs(args);
|
|
161
|
+
const role = roleFlag || pos[0];
|
|
162
|
+
const task = flagValue(args, ['--task', '--message']) || pos.slice(roleFlag ? 0 : 1).join(' ');
|
|
163
|
+
if (!role) throw new Error('Missing role. Usage: atris agent spawn <role> --task "..."');
|
|
164
|
+
if (!String(task || '').trim()) throw new Error('Missing task. Usage: atris agent spawn <role> --task "..."');
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
help: false,
|
|
168
|
+
json,
|
|
169
|
+
dryRun,
|
|
170
|
+
role: String(role).trim(),
|
|
171
|
+
task: String(task).trim(),
|
|
172
|
+
engine,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function createSpawnRequest(root, options) {
|
|
177
|
+
const request = {
|
|
178
|
+
schema: SPAWN_SCHEMA,
|
|
179
|
+
id: idForSpawn(options.role),
|
|
180
|
+
status: 'requested',
|
|
181
|
+
role: options.role,
|
|
182
|
+
task: options.task,
|
|
183
|
+
engine: options.engine,
|
|
184
|
+
cwd: root,
|
|
185
|
+
created_at: nowIso(),
|
|
186
|
+
updated_at: nowIso(),
|
|
187
|
+
};
|
|
188
|
+
request.command = commandForEngine(request);
|
|
189
|
+
request.next_action = request.command
|
|
190
|
+
? `Run: ${request.command}`
|
|
191
|
+
: 'Assign this request to a worker runtime, or rerun with --engine codex|claude|cursor|devin|droid.';
|
|
192
|
+
return request;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function showSpawnHelp(output = console.log, commandName = 'atris agent spawn') {
|
|
196
|
+
output(`Usage: ${commandName} <role> --task "<bounded task>" [options]`);
|
|
197
|
+
output('');
|
|
198
|
+
output('Create a durable worker request that ax, humans, or another runtime can pick up.');
|
|
199
|
+
output('');
|
|
200
|
+
output('Options:');
|
|
201
|
+
output(' --task <text> Bounded task to delegate');
|
|
202
|
+
output(' --engine <name> manual|codex|claude|cursor|devin|droid (default: manual)');
|
|
203
|
+
output(' --json Machine-readable output');
|
|
204
|
+
output(' --dry-run Preview without writing .atris/state/agent_spawns.jsonl');
|
|
205
|
+
output('');
|
|
206
|
+
output('Examples:');
|
|
207
|
+
output(` ${commandName} worker --task "Fix the failing smoke test"`);
|
|
208
|
+
output(` ${commandName} explorer --engine codex --task "Find where auth is routed"`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function parseDogfoodArgs(args = []) {
|
|
212
|
+
const help = hasFlag(args, ['--help', '-h']) || args[0] === 'help';
|
|
213
|
+
const json = hasFlag(args, ['--json']);
|
|
214
|
+
const live = hasFlag(args, ['--live']);
|
|
215
|
+
const noWrite = hasFlag(args, ['--no-write']);
|
|
216
|
+
const model = flagValue(args, ['--model']) || 'glm-5.2';
|
|
217
|
+
const timeoutRaw = Number(flagValue(args, ['--timeout']) || 45);
|
|
218
|
+
const timeoutMs = Math.max(5, Math.min(300, timeoutRaw)) * 1000;
|
|
219
|
+
const engine = String(flagValue(args, ['--engine']) || positionalArgs(args)[0] || 'all').toLowerCase();
|
|
220
|
+
if (engine !== 'all' && !DOGFOOD_ENGINES.has(engine)) {
|
|
221
|
+
throw new Error(`Unknown dogfood engine: ${engine}. Use devin, droid, or all.`);
|
|
222
|
+
}
|
|
223
|
+
return { help, json, live, noWrite, model, timeoutMs, engine };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function livePromptFor(engine) {
|
|
227
|
+
const upper = engine === 'devin' ? 'DEVIN' : 'DROID';
|
|
228
|
+
return `Return exactly ATRIS_${upper}_GLM52_OK. Do not inspect files. Do not run tools.`;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function liveCommandFor(engine, model) {
|
|
232
|
+
if (engine === 'devin') {
|
|
233
|
+
return [
|
|
234
|
+
'devin',
|
|
235
|
+
'--model',
|
|
236
|
+
model,
|
|
237
|
+
'--permission-mode',
|
|
238
|
+
'auto',
|
|
239
|
+
'-p',
|
|
240
|
+
livePromptFor(engine),
|
|
241
|
+
];
|
|
242
|
+
}
|
|
243
|
+
return [
|
|
244
|
+
'droid',
|
|
245
|
+
'exec',
|
|
246
|
+
'--model',
|
|
247
|
+
model,
|
|
248
|
+
'--reasoning-effort',
|
|
249
|
+
'off',
|
|
250
|
+
'--output-format',
|
|
251
|
+
'text',
|
|
252
|
+
'--append-system-prompt',
|
|
253
|
+
'Do not use tools. Reply with exactly the requested token.',
|
|
254
|
+
livePromptFor(engine),
|
|
255
|
+
];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function dryChecksFor(engine, model, deps = {}, options = {}) {
|
|
259
|
+
const binary = engine === 'devin' ? 'devin' : 'droid';
|
|
260
|
+
const binaryPath = commandOnPath(binary, deps);
|
|
261
|
+
const checks = [
|
|
262
|
+
{
|
|
263
|
+
name: 'binary_on_path',
|
|
264
|
+
ok: Boolean(binaryPath),
|
|
265
|
+
detail: binaryPath || `${binary} not on PATH`,
|
|
266
|
+
},
|
|
267
|
+
];
|
|
268
|
+
if (!binaryPath) return { binary, binaryPath, checks };
|
|
269
|
+
|
|
270
|
+
const versionArgs = engine === 'devin' ? ['devin', 'version'] : ['droid', '--version'];
|
|
271
|
+
const version = runCli(versionArgs, deps, options);
|
|
272
|
+
checks.push({
|
|
273
|
+
name: 'version_command',
|
|
274
|
+
ok: version.ok,
|
|
275
|
+
command: version.command,
|
|
276
|
+
stdout_head: version.stdout.slice(0, 500),
|
|
277
|
+
stderr_head: version.stderr.slice(0, 500),
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
const helpArgs = engine === 'devin' ? ['devin', '--help'] : ['droid', 'exec', '--help'];
|
|
281
|
+
const help = runCli(helpArgs, deps, options);
|
|
282
|
+
const modelSupport = engine === 'droid'
|
|
283
|
+
? help.stdout.includes(model)
|
|
284
|
+
: /--model\s+<MODEL>/.test(help.stdout);
|
|
285
|
+
checks.push({
|
|
286
|
+
name: engine === 'droid' ? 'model_list_advertises_glm' : 'model_flag_available',
|
|
287
|
+
ok: help.ok && modelSupport,
|
|
288
|
+
command: help.command,
|
|
289
|
+
detail: engine === 'droid'
|
|
290
|
+
? `${model} ${modelSupport ? 'listed' : 'missing'}`
|
|
291
|
+
: `--model flag ${modelSupport ? 'available' : 'missing'}`,
|
|
292
|
+
stdout_head: help.stdout.slice(0, 700),
|
|
293
|
+
stderr_head: help.stderr.slice(0, 500),
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
return { binary, binaryPath, checks };
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function runDogfoodEngine(engine, options = {}, deps = {}) {
|
|
300
|
+
const dry = dryChecksFor(engine, options.model || 'glm-5.2', deps, {
|
|
301
|
+
cwd: deps.root || process.cwd(),
|
|
302
|
+
timeoutMs: options.timeoutMs,
|
|
303
|
+
});
|
|
304
|
+
const result = {
|
|
305
|
+
engine,
|
|
306
|
+
model: options.model || 'glm-5.2',
|
|
307
|
+
binary: dry.binary,
|
|
308
|
+
binary_path: dry.binaryPath,
|
|
309
|
+
live: Boolean(options.live),
|
|
310
|
+
checks: dry.checks,
|
|
311
|
+
ok: dry.checks.every(check => check.ok),
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
if (options.live && result.ok) {
|
|
315
|
+
const command = liveCommandFor(engine, result.model);
|
|
316
|
+
const run = runCli(command, deps, {
|
|
317
|
+
cwd: deps.root || process.cwd(),
|
|
318
|
+
timeoutMs: options.timeoutMs,
|
|
319
|
+
env: {
|
|
320
|
+
...process.env,
|
|
321
|
+
ATRIS_SKIP_UPDATE_CHECK: '1',
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
const expected = `ATRIS_${engine === 'devin' ? 'DEVIN' : 'DROID'}_GLM52_OK`;
|
|
325
|
+
const matched = run.ok && run.stdout.includes(expected);
|
|
326
|
+
result.checks.push({
|
|
327
|
+
name: 'live_sentinel_prompt',
|
|
328
|
+
ok: matched,
|
|
329
|
+
command: run.command,
|
|
330
|
+
expected,
|
|
331
|
+
stdout_head: run.stdout.slice(0, 700),
|
|
332
|
+
stderr_head: run.stderr.slice(0, 700),
|
|
333
|
+
status: run.status,
|
|
334
|
+
signal: run.signal,
|
|
335
|
+
duration_ms: run.duration_ms,
|
|
336
|
+
error: run.error,
|
|
337
|
+
});
|
|
338
|
+
result.ok = result.checks.every(check => check.ok);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return result;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function writeDogfoodReceipt(root, receipt) {
|
|
345
|
+
const dir = runsDir(root);
|
|
346
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
347
|
+
const filePath = path.join(dir, `agent-dogfood-${timestampForFile()}.json`);
|
|
348
|
+
fs.writeFileSync(filePath, `${JSON.stringify(receipt, null, 2)}\n`, 'utf8');
|
|
349
|
+
return filePath;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function showDogfoodHelp(output = console.log) {
|
|
353
|
+
output('Usage: atris agent dogfood [devin|droid|--engine all] [--model glm-5.2] [--live] [--json]');
|
|
354
|
+
output('');
|
|
355
|
+
output('Cheaply verifies external coding CLIs without a model call by default.');
|
|
356
|
+
output('Add --live to run one exact sentinel prompt through Devin/Droid.');
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function agentDogfoodCommand(args = [], deps = {}) {
|
|
360
|
+
const root = deps.root || process.cwd();
|
|
361
|
+
const output = deps.output || ((line = '') => console.log(line));
|
|
362
|
+
const options = parseDogfoodArgs(args);
|
|
363
|
+
if (options.help) {
|
|
364
|
+
showDogfoodHelp(output);
|
|
365
|
+
return { ok: true, action: 'help' };
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const engines = options.engine === 'all' ? ['devin', 'droid'] : [options.engine];
|
|
369
|
+
const receipt = {
|
|
370
|
+
schema: DOGFOOD_SCHEMA,
|
|
371
|
+
action: 'agent_dogfood',
|
|
372
|
+
root,
|
|
373
|
+
model: options.model,
|
|
374
|
+
live: options.live,
|
|
375
|
+
started_at: nowIso(),
|
|
376
|
+
finished_at: null,
|
|
377
|
+
engines: engines.map(engine => runDogfoodEngine(engine, options, deps)),
|
|
378
|
+
};
|
|
379
|
+
receipt.finished_at = nowIso();
|
|
380
|
+
receipt.ok = receipt.engines.every(engine => engine.ok);
|
|
381
|
+
if (!options.noWrite) {
|
|
382
|
+
receipt.receipt_path = writeDogfoodReceipt(root, receipt);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (options.json) {
|
|
386
|
+
output(JSON.stringify(receipt, null, 2));
|
|
387
|
+
} else {
|
|
388
|
+
output(`agent dogfood ${receipt.ok ? 'passed' : 'failed'} (${options.live ? 'live' : 'dry'})`);
|
|
389
|
+
for (const engine of receipt.engines) {
|
|
390
|
+
output(`${engine.ok ? '✓' : '✗'} ${engine.engine} ${engine.model}${engine.binary_path ? ` -> ${engine.binary_path}` : ''}`);
|
|
391
|
+
for (const check of engine.checks) {
|
|
392
|
+
output(` ${check.ok ? '✓' : '✗'} ${check.name}${check.detail ? `: ${check.detail}` : ''}`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
if (receipt.receipt_path) output(`receipt: ${path.relative(root, receipt.receipt_path)}`);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return receipt;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function printSpawnRequest(request, output = console.log) {
|
|
402
|
+
output(`spawned ${request.id}`);
|
|
403
|
+
output(`role: ${request.role}`);
|
|
404
|
+
output(`engine: ${request.engine}`);
|
|
405
|
+
output(`task: ${request.task}`);
|
|
406
|
+
output(`next: ${request.next_action}`);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function agentSpawnCommand(args = [], deps = {}) {
|
|
410
|
+
const root = deps.root || process.cwd();
|
|
411
|
+
const output = deps.output || ((line = '') => console.log(line));
|
|
412
|
+
const options = parseSpawnArgs(args);
|
|
413
|
+
if (options.help) {
|
|
414
|
+
showSpawnHelp(output, deps.commandName || 'atris agent spawn');
|
|
415
|
+
return { ok: true, action: 'help' };
|
|
416
|
+
}
|
|
417
|
+
const request = createSpawnRequest(root, options);
|
|
418
|
+
let statePath = spawnStatePath(root);
|
|
419
|
+
if (!options.dryRun) statePath = appendSpawnRequest(root, request);
|
|
420
|
+
const payload = {
|
|
421
|
+
ok: true,
|
|
422
|
+
action: options.dryRun ? 'spawn_preview' : 'spawn_created',
|
|
423
|
+
request,
|
|
424
|
+
state_path: statePath,
|
|
425
|
+
};
|
|
426
|
+
if (options.json) output(JSON.stringify(payload, null, 2));
|
|
427
|
+
else printSpawnRequest(request, output);
|
|
428
|
+
return payload;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function agentSpawnListCommand(args = [], deps = {}) {
|
|
432
|
+
const root = deps.root || process.cwd();
|
|
433
|
+
const output = deps.output || ((line = '') => console.log(line));
|
|
434
|
+
const json = hasFlag(args, ['--json']);
|
|
435
|
+
const requests = loadSpawnRequests(root).reverse();
|
|
436
|
+
const payload = { ok: true, action: 'spawn_list', requests, state_path: spawnStatePath(root) };
|
|
437
|
+
if (json) {
|
|
438
|
+
output(JSON.stringify(payload, null, 2));
|
|
439
|
+
return payload;
|
|
440
|
+
}
|
|
441
|
+
if (!requests.length) {
|
|
442
|
+
output('No agent spawn requests.');
|
|
443
|
+
return payload;
|
|
444
|
+
}
|
|
445
|
+
for (const req of requests.slice(0, 20)) {
|
|
446
|
+
output(`${req.id}\t${req.status}\t${req.engine}\t${req.role}\t${req.task}`);
|
|
447
|
+
}
|
|
448
|
+
return payload;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function agentSpawnStatusCommand(args = [], deps = {}) {
|
|
452
|
+
const root = deps.root || process.cwd();
|
|
453
|
+
const output = deps.output || ((line = '') => console.log(line));
|
|
454
|
+
const json = hasFlag(args, ['--json']);
|
|
455
|
+
const id = positionalArgs(args)[0];
|
|
456
|
+
if (!id) throw new Error('Missing spawn id. Usage: atris agent spawn-status <id>');
|
|
457
|
+
const request = loadSpawnRequests(root).find(req => req.id === id);
|
|
458
|
+
if (!request) throw new Error(`Spawn request not found: ${id}`);
|
|
459
|
+
const payload = { ok: true, action: 'spawn_status', request, state_path: spawnStatePath(root) };
|
|
460
|
+
if (json) output(JSON.stringify(payload, null, 2));
|
|
461
|
+
else printSpawnRequest(request, output);
|
|
462
|
+
return payload;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
module.exports = {
|
|
466
|
+
SPAWN_SCHEMA,
|
|
467
|
+
SPAWN_STATE_REL,
|
|
468
|
+
parseSpawnArgs,
|
|
469
|
+
buildDelegatePrompt,
|
|
470
|
+
commandForEngine,
|
|
471
|
+
createSpawnRequest,
|
|
472
|
+
agentDogfoodCommand,
|
|
473
|
+
parseDogfoodArgs,
|
|
474
|
+
runDogfoodEngine,
|
|
475
|
+
loadSpawnRequests,
|
|
476
|
+
agentSpawnCommand,
|
|
477
|
+
agentSpawnListCommand,
|
|
478
|
+
agentSpawnStatusCommand,
|
|
479
|
+
showSpawnHelp,
|
|
480
|
+
};
|
package/commands/analytics.js
CHANGED
|
@@ -51,7 +51,7 @@ function analyticsAtris() {
|
|
|
51
51
|
todayCompletions = completionCount;
|
|
52
52
|
|
|
53
53
|
// Count today's inbox
|
|
54
|
-
const inboxMatch = content.match(/## Inbox\n([\s\S]*?)(?=\n##|---|$)/);
|
|
54
|
+
const inboxMatch = content.match(/## Inbox\r?\n([\s\S]*?)(?=\r?\n##|---|$)/);
|
|
55
55
|
if (inboxMatch && inboxMatch[1].trim()) {
|
|
56
56
|
const inboxMatches = inboxMatch[1].match(/- \*\*I\d+:/g);
|
|
57
57
|
todayInbox = inboxMatches ? inboxMatches.length : 0;
|
|
@@ -60,7 +60,7 @@ function analyticsAtris() {
|
|
|
60
60
|
|
|
61
61
|
if (index === 6) {
|
|
62
62
|
// Count oldest day's inbox for trend
|
|
63
|
-
const inboxMatch = content.match(/## Inbox\n([\s\S]*?)(?=\n##|---|$)/);
|
|
63
|
+
const inboxMatch = content.match(/## Inbox\r?\n([\s\S]*?)(?=\r?\n##|---|$)/);
|
|
64
64
|
if (inboxMatch && inboxMatch[1].trim()) {
|
|
65
65
|
const inboxMatches = inboxMatch[1].match(/- \*\*I\d+:/g);
|
|
66
66
|
oldestInbox = inboxMatches ? inboxMatches.length : 0;
|
|
@@ -136,9 +136,12 @@ function analyticsAtris() {
|
|
|
136
136
|
// Daily breakdown
|
|
137
137
|
console.log(`📅 Daily Breakdown`);
|
|
138
138
|
const sortedDates = Object.keys(completionsByDay).sort().reverse();
|
|
139
|
+
// Cap the bar so a high-count day can't overflow the 63-char box; the exact
|
|
140
|
+
// count is still printed numerically after it. Mirrors `mission layers`.
|
|
141
|
+
const BAR_MAX = 40;
|
|
139
142
|
sortedDates.forEach((date, index) => {
|
|
140
143
|
const count = completionsByDay[date];
|
|
141
|
-
const bar = '█'.repeat(count);
|
|
144
|
+
const bar = '█'.repeat(Math.min(count, BAR_MAX));
|
|
142
145
|
const label = index === 0 ? ' (today)' : '';
|
|
143
146
|
console.log(` ${date}: ${bar} ${count}${label}`);
|
|
144
147
|
});
|
package/commands/apps.js
CHANGED
|
@@ -34,6 +34,7 @@ function appsUsageLines() {
|
|
|
34
34
|
' owner <slug> [--json] Show owner view: launch, usage, learning, next actions',
|
|
35
35
|
' status [--json] Show local app health',
|
|
36
36
|
' queue Show app improvement queue',
|
|
37
|
+
' apply [--target s] [--llm] Fix lowest-quality app; keep only if quality improves',
|
|
37
38
|
' rate <slug> <up|down> [note] Record output feedback',
|
|
38
39
|
' smoke Run fresh-checkout smoke test',
|
|
39
40
|
' doctor [--strict] Audit pack health, smoke, and source cleanliness',
|
|
@@ -302,6 +303,16 @@ async function appsCommand(subcommand, ...rawArgs) {
|
|
|
302
303
|
}
|
|
303
304
|
if (subcommand === 'status') runPackScript(packRoot, 'scripts/app_status.py', json ? ['--json'] : []);
|
|
304
305
|
if (subcommand === 'queue') runPackScript(packRoot, 'scripts/app_improvement_queue.py', []);
|
|
306
|
+
if (subcommand === 'apply') {
|
|
307
|
+
const target = popOption(args, '--target', null);
|
|
308
|
+
const command = ['--workspace', workspace];
|
|
309
|
+
if (target) command.push('--target', target);
|
|
310
|
+
else command.push('--auto');
|
|
311
|
+
if (args.includes('--llm')) command.push('--llm'); // else app_apply defaults to dry-run
|
|
312
|
+
if (args.includes('--dry-run')) command.push('--dry-run');
|
|
313
|
+
if (json) command.push('--json');
|
|
314
|
+
runPackScript(packRoot, 'scripts/app_apply.py', command);
|
|
315
|
+
}
|
|
305
316
|
if (subcommand === 'smoke') runPackScript(packRoot, 'scripts/install_smoke.py', []);
|
|
306
317
|
if (subcommand === 'doctor') runPackScript(packRoot, 'scripts/install_smoke.py', []);
|
|
307
318
|
if (subcommand === 'handoff') {
|