atris 3.52.0 → 3.55.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/FOR_AGENTS.md +7 -0
- package/README.md +1 -1
- package/atris/policies/ANTISLOP.md +2 -1
- package/atris/skills/copy-editor/SKILL.md +2 -1
- package/atris/skills/design/SKILL.md +1 -1
- package/atris/skills/engines/SKILL.md +8 -7
- package/atris/skills/x-search/SKILL.md +24 -22
- package/atris/skills/youtube/SKILL.md +89 -40
- package/atris/team/_template/SOUL.md +40 -0
- package/atris/team/validator/MEMBER.md +1 -2
- package/ax +592 -91
- package/bin/atris +21 -0
- package/bin/atris.js +580 -378
- package/commands/api-key.js +170 -0
- package/commands/auth.js +453 -43
- package/commands/autoland.js +3 -1
- package/commands/autopilot-front.js +58 -11
- package/commands/avail.js +5 -4
- package/commands/balance.js +55 -0
- package/commands/bench.js +29 -1
- package/commands/brain.js +63 -12
- package/commands/brainstorm.js +18 -0
- package/commands/business.js +149 -1
- package/commands/chat-scan.js +17 -4
- package/commands/close.js +5 -1
- package/commands/computer.js +2 -2
- package/commands/doctor.js +174 -0
- package/commands/dream.js +7 -0
- package/commands/drill.js +34 -2
- package/commands/engine.js +47 -35
- package/commands/errors.js +58 -8
- package/commands/experiments.js +14 -0
- package/commands/feedback.js +61 -2
- package/commands/fleet-report.js +27 -7
- package/commands/fleet.js +98 -11
- package/commands/founder.js +60 -21
- package/commands/human-missions.js +79 -4
- package/commands/improve.js +54 -4
- package/commands/init.js +68 -92
- package/commands/install.js +40 -0
- package/commands/integrations.js +399 -30
- package/commands/interview.js +8 -0
- package/commands/land.js +48 -17
- package/commands/learn.js +29 -4
- package/commands/log.js +51 -0
- package/commands/member.js +68 -10
- package/commands/mission.js +339 -74
- package/commands/now.js +25 -6
- package/commands/orb.js +97 -2
- package/commands/pack.js +83 -9
- package/commands/playbook.js +381 -0
- package/commands/plugin.js +16 -0
- package/commands/radar.js +96 -40
- package/commands/recap.js +128 -21
- package/commands/release.js +102 -2
- package/commands/review.js +5 -10
- package/commands/revisions.js +55 -0
- package/commands/run-front.js +12 -4
- package/commands/scout.js +12 -1
- package/commands/search.js +123 -28
- package/commands/sign.js +2 -2
- package/commands/signup.js +12 -3
- package/commands/site-deploy.js +17 -4
- package/commands/site.js +2 -2
- package/commands/skill-eval.js +289 -0
- package/commands/skill.js +56 -6
- package/commands/social.js +603 -0
- package/commands/spaceship.js +69 -6
- package/commands/stream.js +8 -1
- package/commands/study.js +32 -19
- package/commands/sync.js +28 -22
- package/commands/task.js +418 -60
- package/commands/teach.js +72 -63
- package/commands/terminal.js +25 -9
- package/commands/topup.js +88 -0
- package/commands/truth.js +52 -6
- package/commands/usage.js +91 -0
- package/commands/verify.js +3 -2
- package/commands/version.js +3 -1
- package/commands/voice.js +5 -1
- package/commands/who.js +69 -11
- package/commands/wiki.js +7 -4
- package/commands/wish.js +76 -5
- package/commands/workflow.js +327 -145
- package/commands/worktree.js +1 -0
- package/commands/write.js +5 -0
- package/commands/x-search.js +340 -0
- package/commands/xp.js +5 -1
- package/commands/youtube.js +551 -33
- package/lib/account-bound.js +47 -0
- package/lib/auto-accept-certified.js +10 -2
- package/lib/auto-lessons.js +186 -0
- package/lib/autoland.js +4 -2
- package/lib/chat-log-scan.js +7 -4
- package/lib/cli-json.js +77 -0
- package/lib/cli-scope.js +26 -0
- package/lib/codex-flight.js +1 -0
- package/lib/conductor-artifacts.js +1 -1
- package/lib/context-gatherer.js +11 -0
- package/lib/developer-api.js +116 -0
- package/lib/engine-ask.js +11 -3
- package/lib/engine-registry.js +32 -7
- package/lib/feedback-triage.js +274 -0
- package/lib/first-minute.js +398 -0
- package/lib/fleet.js +115 -34
- package/lib/functional-owner.js +22 -0
- package/lib/known-commands.js +31 -5
- package/lib/member-scaffold.js +197 -0
- package/lib/mission-ledger-compact.js +127 -0
- package/lib/mission-root.js +4 -2
- package/lib/mission-runtime-loop.js +30 -2
- package/lib/next-moves.js +34 -34
- package/lib/noninteractive.js +88 -0
- package/lib/permission-grants.js +10 -1
- package/lib/policy-lessons.js +4 -1
- package/lib/revision-metric.js +279 -0
- package/lib/runner-command.js +5 -4
- package/lib/scratch-root.js +48 -0
- package/lib/skill-eval-gate.js +249 -0
- package/lib/task-db.js +22 -2
- package/lib/task-proof.js +75 -9
- package/lib/task-receipt.js +5 -1
- package/lib/verifier-quality.js +69 -0
- package/lib/wish-audit.js +2 -2
- package/lib/wish-delegate.js +10 -1
- package/lib/workspace-scaffold.js +270 -0
- package/package.json +4 -2
- package/scripts/det/checklist-score.js +191 -0
- package/scripts/det/ytsearch +31 -0
- package/scripts/outbound-artifact-gate.js +227 -0
- package/utils/update-check.js +15 -0
package/commands/feedback.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* atris feedback submit "message here" Submit feedback
|
|
7
7
|
* atris feedback List feedback
|
|
8
8
|
* atris feedback list List feedback
|
|
9
|
+
* atris feedback triage <id> [--dispatch] Build or dispatch a reproduction brief
|
|
9
10
|
* atris feedback resolve <id> "<resolution>" Mark resolved (admin)
|
|
10
11
|
* atris feedback close <id> Close as wontfix (admin)
|
|
11
12
|
* atris feedback delete <id> Delete feedback (admin)
|
|
@@ -18,10 +19,17 @@ const fs = require('fs');
|
|
|
18
19
|
const path = require('path');
|
|
19
20
|
const { loadCredentials } = require('../utils/auth');
|
|
20
21
|
const { apiRequestJson } = require('../utils/api');
|
|
22
|
+
const {
|
|
23
|
+
planFeedbackTriage,
|
|
24
|
+
renderFeedbackTriageBrief,
|
|
25
|
+
dispatchFeedbackTriage,
|
|
26
|
+
recordFeedbackTriageVerdict,
|
|
27
|
+
} = require('../lib/feedback-triage');
|
|
21
28
|
|
|
22
29
|
const KNOWN_FEEDBACK_COMMANDS = new Set([
|
|
23
30
|
'list',
|
|
24
31
|
'submit',
|
|
32
|
+
'triage',
|
|
25
33
|
'resolve',
|
|
26
34
|
'close',
|
|
27
35
|
'delete',
|
|
@@ -151,6 +159,43 @@ async function resolveIdPrefix(prefix, { token, businessId }) {
|
|
|
151
159
|
return { id: matches[0].id, item: matches[0] };
|
|
152
160
|
}
|
|
153
161
|
|
|
162
|
+
function findFeedbackItem(prefix, items) {
|
|
163
|
+
const requested = String(prefix || '').trim();
|
|
164
|
+
if (!requested) return { error: 'feedback id is required' };
|
|
165
|
+
const matches = (items || []).filter((item) => String(item && item.id || '').startsWith(requested));
|
|
166
|
+
if (matches.length === 0) return { error: `No feedback matches id prefix "${requested}"` };
|
|
167
|
+
if (matches.length > 1) return { error: `Ambiguous id "${requested}"; matches ${matches.length} items. Use a longer prefix.` };
|
|
168
|
+
return { item: matches[0] };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function triageFeedback(idPrefix, options = {}) {
|
|
172
|
+
const workspace = path.resolve(options.workspace || process.cwd());
|
|
173
|
+
const token = options.token || getAuth().token;
|
|
174
|
+
const fetchItems = options.fetchFeedbackItems || fetchFeedbackItems;
|
|
175
|
+
const items = await fetchItems({ token, businessId: options.businessId, limit: 200 });
|
|
176
|
+
const lookup = findFeedbackItem(idPrefix, items);
|
|
177
|
+
if (lookup.error) throw new Error(lookup.error);
|
|
178
|
+
|
|
179
|
+
const brief = planFeedbackTriage(lookup.item, workspace);
|
|
180
|
+
const write = options.write || console.log;
|
|
181
|
+
write(renderFeedbackTriageBrief(brief));
|
|
182
|
+
if (!options.dispatch) return { brief, dispatch: null, verdict: null };
|
|
183
|
+
|
|
184
|
+
const dispatchBrief = options.dispatchFeedbackTriage || dispatchFeedbackTriage;
|
|
185
|
+
const dispatched = await dispatchBrief(brief, workspace, options.dispatchDeps || {});
|
|
186
|
+
const recordVerdict = options.recordFeedbackTriageVerdict || recordFeedbackTriageVerdict;
|
|
187
|
+
const recorded = recordVerdict(lookup.item, workspace, {
|
|
188
|
+
verdict: dispatched.verdict,
|
|
189
|
+
evidence: dispatched.evidence,
|
|
190
|
+
verify_command: brief.verify_command,
|
|
191
|
+
engine: dispatched.engine,
|
|
192
|
+
});
|
|
193
|
+
write('');
|
|
194
|
+
write(`verdict: ${recorded.record.verdict}`);
|
|
195
|
+
write(`${recorded.appended ? 'recorded' : 'already recorded'}: ${path.relative(workspace, recorded.path)}`);
|
|
196
|
+
return { brief, dispatch: dispatched, verdict: recorded };
|
|
197
|
+
}
|
|
198
|
+
|
|
154
199
|
async function resolveFeedback(idPrefix, resolution) {
|
|
155
200
|
if (!idPrefix || !resolution) {
|
|
156
201
|
console.error('Usage: atris feedback resolve <id> "<resolution>"');
|
|
@@ -236,6 +281,8 @@ function printHelp(write = console.log) {
|
|
|
236
281
|
write(' atris feedback submit "msg" --business <slug>');
|
|
237
282
|
write(' atris feedback List feedback');
|
|
238
283
|
write(' atris feedback list List feedback');
|
|
284
|
+
write(' atris feedback triage <id> Print a reproduction brief');
|
|
285
|
+
write(' atris feedback triage <id> --dispatch Run triage and record its verdict');
|
|
239
286
|
write(' atris feedback resolve <id> "<note>" Mark resolved (admin)');
|
|
240
287
|
write(' atris feedback close <id> Close as wontfix (admin)');
|
|
241
288
|
write(' atris feedback delete <id> Delete feedback (admin)');
|
|
@@ -296,7 +343,8 @@ function rejectUnknownFeedbackCommand(subcommand) {
|
|
|
296
343
|
|
|
297
344
|
async function feedbackCommand() {
|
|
298
345
|
const rawArgs = process.argv.slice(3);
|
|
299
|
-
const
|
|
346
|
+
const dispatchRequested = rawArgs.includes('--dispatch');
|
|
347
|
+
const [businessArg, args] = extractFlag(rawArgs.filter((arg) => arg !== '--dispatch'), '--business', '-b');
|
|
300
348
|
const businessId = resolveBusinessArg(businessArg);
|
|
301
349
|
if (businessArg && !businessId) {
|
|
302
350
|
console.error(`Error: unknown business "${businessArg}". Check ~/.atris/businesses.json`);
|
|
@@ -329,6 +377,14 @@ async function feedbackCommand() {
|
|
|
329
377
|
return;
|
|
330
378
|
}
|
|
331
379
|
|
|
380
|
+
if (subcommand === 'triage') {
|
|
381
|
+
if (!args[1] || args.length !== 2) {
|
|
382
|
+
throw new Error('usage: atris feedback triage <id> [--dispatch]');
|
|
383
|
+
}
|
|
384
|
+
await triageFeedback(args[1], { businessId, dispatch: dispatchRequested });
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
332
388
|
if (subcommand === 'resolve') {
|
|
333
389
|
const id = args[1];
|
|
334
390
|
const resolution = args.slice(2).join(' ');
|
|
@@ -359,4 +415,7 @@ async function feedbackCommand() {
|
|
|
359
415
|
rejectUnknownFeedbackCommand(subcommand);
|
|
360
416
|
}
|
|
361
417
|
|
|
362
|
-
module.exports = {
|
|
418
|
+
module.exports = {
|
|
419
|
+
feedbackCommand,
|
|
420
|
+
triageFeedback,
|
|
421
|
+
};
|
package/commands/fleet-report.js
CHANGED
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
|
|
16
16
|
const { loadCredentials } = require('../utils/auth');
|
|
17
17
|
const { apiRequestJson } = require('../utils/api');
|
|
18
|
+
const { requireAccountBound, refuseAccountGlobal } = require('../lib/account-bound');
|
|
19
|
+
const { argsWantHelp, wantsJson } = require('../lib/noninteractive');
|
|
18
20
|
|
|
19
21
|
function sleep(ms) {
|
|
20
22
|
return new Promise((r) => setTimeout(r, ms));
|
|
@@ -157,15 +159,33 @@ async function leaderboard(token, businesses) {
|
|
|
157
159
|
|
|
158
160
|
async function fleetReport() {
|
|
159
161
|
const args = process.argv.slice(3);
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
162
|
+
const usage = 'Usage: atris fleet-report <business> [--wake] | --all-alive [--dry-run] | --leaderboard';
|
|
163
|
+
if (argsWantHelp(args) || wantsJson(args)) {
|
|
164
|
+
if (wantsJson(args)) {
|
|
165
|
+
console.log(JSON.stringify({
|
|
166
|
+
ok: false,
|
|
167
|
+
command: 'fleet-report',
|
|
168
|
+
error: 'usage',
|
|
169
|
+
usage,
|
|
170
|
+
}, null, 2));
|
|
171
|
+
} else {
|
|
172
|
+
console.log(usage);
|
|
173
|
+
}
|
|
174
|
+
process.exit(2);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const gate = requireAccountBound(args);
|
|
178
|
+
if (!gate.ok) process.exit(refuseAccountGlobal());
|
|
179
|
+
|
|
180
|
+
const allAlive = gate.args.includes('--all-alive');
|
|
181
|
+
const wake = gate.args.includes('--wake');
|
|
182
|
+
const dryRun = gate.args.includes('--dry-run');
|
|
183
|
+
const board = gate.args.includes('--leaderboard');
|
|
184
|
+
const slug = gate.args.find((a) => !a.startsWith('--'));
|
|
165
185
|
|
|
166
186
|
if (!allAlive && !slug && !board) {
|
|
167
|
-
console.log(
|
|
168
|
-
process.exit(
|
|
187
|
+
console.log(usage);
|
|
188
|
+
process.exit(2);
|
|
169
189
|
}
|
|
170
190
|
|
|
171
191
|
const creds = loadCredentials();
|
package/commands/fleet.js
CHANGED
|
@@ -19,6 +19,7 @@ const fs = require('fs');
|
|
|
19
19
|
const path = require('path');
|
|
20
20
|
const os = require('os');
|
|
21
21
|
const crypto = require('crypto');
|
|
22
|
+
const { parseScopeFlag } = require('../lib/cli-scope');
|
|
22
23
|
|
|
23
24
|
// ── Config ──────────────────────────────────────────────
|
|
24
25
|
|
|
@@ -327,21 +328,74 @@ async function fleetWatch(state, intervalSec = 10) {
|
|
|
327
328
|
});
|
|
328
329
|
}
|
|
329
330
|
|
|
331
|
+
async function fleetWorkspaceStatus(root = process.cwd(), { json = false } = {}) {
|
|
332
|
+
const teamDir = path.join(root, 'atris', 'team');
|
|
333
|
+
const members = [];
|
|
334
|
+
if (fs.existsSync(teamDir)) {
|
|
335
|
+
for (const name of fs.readdirSync(teamDir).filter((n) => !n.startsWith('_') && !n.startsWith('.'))) {
|
|
336
|
+
const memberFile = path.join(teamDir, name, 'MEMBER.md');
|
|
337
|
+
if (!fs.existsSync(memberFile)) continue;
|
|
338
|
+
members.push({ member_id: name, member_name: name, member_type: 'agent', source: 'workspace' });
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
const missionsPath = path.join(root, '.atris', 'state', 'missions.jsonl');
|
|
342
|
+
let activeMissions = 0;
|
|
343
|
+
if (fs.existsSync(missionsPath)) {
|
|
344
|
+
const seen = new Map();
|
|
345
|
+
for (const line of fs.readFileSync(missionsPath, 'utf8').split('\n')) {
|
|
346
|
+
if (!line.trim()) continue;
|
|
347
|
+
try {
|
|
348
|
+
const rec = JSON.parse(line);
|
|
349
|
+
const id = rec.id || rec.mission_id;
|
|
350
|
+
if (id) seen.set(id, rec);
|
|
351
|
+
} catch {}
|
|
352
|
+
}
|
|
353
|
+
activeMissions = [...seen.values()].filter((m) => ['ready', 'running', 'planning', 'paused'].includes(m.status)).length;
|
|
354
|
+
}
|
|
355
|
+
const payload = {
|
|
356
|
+
scope: 'workspace',
|
|
357
|
+
hub: null,
|
|
358
|
+
members: { count: members.length, members },
|
|
359
|
+
claims: { count: 0, claims: [] },
|
|
360
|
+
missions_active: activeMissions,
|
|
361
|
+
note: 'workspace scope only. pass --global to query the machine Swarlo hub.',
|
|
362
|
+
};
|
|
363
|
+
if (json) {
|
|
364
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
console.log(`\n Fleet (workspace)`);
|
|
368
|
+
console.log(` ─────────────────────────────────`);
|
|
369
|
+
console.log(` Scope: workspace`);
|
|
370
|
+
console.log(` Members: ${members.length} (from atris/team)`);
|
|
371
|
+
for (const mem of members.slice(0, 24)) {
|
|
372
|
+
console.log(` ${mem.member_name}`);
|
|
373
|
+
}
|
|
374
|
+
console.log(` Missions active: ${activeMissions}`);
|
|
375
|
+
console.log(` Hub: not queried (use --global for localhost Swarlo)`);
|
|
376
|
+
console.log();
|
|
377
|
+
}
|
|
378
|
+
|
|
330
379
|
// ── Main ────────────────────────────────────────────────
|
|
331
380
|
|
|
332
381
|
async function fleet(args = []) {
|
|
333
|
-
const
|
|
334
|
-
const
|
|
382
|
+
const scope = parseScopeFlag(args);
|
|
383
|
+
const argv = scope.args.filter((a) => !String(a).startsWith('-'));
|
|
384
|
+
const flags = scope.args.filter((a) => String(a).startsWith('-'));
|
|
385
|
+
const subcommand = (argv[0] || 'status').toLowerCase();
|
|
386
|
+
const rest = argv.slice(1).join(' ');
|
|
387
|
+
const json = flags.includes('--json') || args.includes('--json');
|
|
335
388
|
|
|
336
389
|
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h') {
|
|
337
390
|
console.log('');
|
|
338
391
|
console.log(' atris fleet — coordinate agent swarm via Swarlo');
|
|
339
392
|
console.log('');
|
|
340
|
-
console.log(' fleet show
|
|
341
|
-
console.log(' fleet
|
|
342
|
-
console.log(' fleet
|
|
343
|
-
console.log(' fleet
|
|
344
|
-
console.log(' fleet
|
|
393
|
+
console.log(' fleet show workspace fleet (default)');
|
|
394
|
+
console.log(' fleet --global query the machine Swarlo hub');
|
|
395
|
+
console.log(' fleet post <msg> post message to board (global)');
|
|
396
|
+
console.log(' fleet task <prompt> create a task for agents (global)');
|
|
397
|
+
console.log(' fleet claim <key> claim a task (global)');
|
|
398
|
+
console.log(' fleet done <key> report task complete (global)');
|
|
345
399
|
console.log(' fleet members who is online');
|
|
346
400
|
console.log(' fleet prune [min] remove stale members (default 60m)');
|
|
347
401
|
console.log(' fleet watch [sec] live-tail the board (default 10s)');
|
|
@@ -349,12 +403,42 @@ async function fleet(args = []) {
|
|
|
349
403
|
return;
|
|
350
404
|
}
|
|
351
405
|
|
|
406
|
+
if (!scope.global && (subcommand === 'status' || subcommand === 'st' || subcommand === 'members' || subcommand === 'who')) {
|
|
407
|
+
await fleetWorkspaceStatus(process.cwd(), { json });
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (!scope.global && ['post', 'say', 'task', 'claim', 'done', 'report', 'prune', 'watch', 'tail'].includes(subcommand)) {
|
|
412
|
+
console.error('fleet mutations need the machine hub. re-run with --global.');
|
|
413
|
+
process.exitCode = 1;
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
352
417
|
const state = await ensureRegistered();
|
|
353
418
|
|
|
354
419
|
switch (subcommand) {
|
|
355
420
|
case 'status':
|
|
356
421
|
case 'st':
|
|
357
|
-
|
|
422
|
+
if (json) {
|
|
423
|
+
const hubUrl = getHubUrl();
|
|
424
|
+
const hubId = getHubId();
|
|
425
|
+
const h = authHeaders(state);
|
|
426
|
+
const [members, posts, claims] = await Promise.all([
|
|
427
|
+
request('GET', `${hubUrl}/api/${hubId}/members`, null, h),
|
|
428
|
+
request('GET', `${hubUrl}/api/${hubId}/channels/general/posts?limit=5`, null, h),
|
|
429
|
+
request('GET', `${hubUrl}/api/${hubId}/claims`, null, h),
|
|
430
|
+
]);
|
|
431
|
+
console.log(JSON.stringify({
|
|
432
|
+
scope: 'global',
|
|
433
|
+
hub: { id: hubId, url: hubUrl },
|
|
434
|
+
you: state.member_id,
|
|
435
|
+
members: members.data,
|
|
436
|
+
claims: claims.data,
|
|
437
|
+
recent: posts.data,
|
|
438
|
+
}, null, 2));
|
|
439
|
+
} else {
|
|
440
|
+
await fleetStatus(state);
|
|
441
|
+
}
|
|
358
442
|
break;
|
|
359
443
|
case 'post':
|
|
360
444
|
case 'say':
|
|
@@ -387,9 +471,12 @@ async function fleet(args = []) {
|
|
|
387
471
|
await fleetWatch(state, parseInt(rest) || 10);
|
|
388
472
|
break;
|
|
389
473
|
default:
|
|
390
|
-
|
|
391
|
-
|
|
474
|
+
if (!scope.global) {
|
|
475
|
+
await fleetWorkspaceStatus(process.cwd(), { json });
|
|
476
|
+
} else {
|
|
477
|
+
await fleetStatus(state);
|
|
478
|
+
}
|
|
392
479
|
}
|
|
393
480
|
}
|
|
394
481
|
|
|
395
|
-
module.exports = { fleet };
|
|
482
|
+
module.exports = { fleet, fleetWorkspaceStatus };
|
package/commands/founder.js
CHANGED
|
@@ -41,11 +41,23 @@ function parseArgs(args = []) {
|
|
|
41
41
|
let index = args[0] === 'score' ? 1 : 0;
|
|
42
42
|
let root = null;
|
|
43
43
|
let days = DEFAULT_DAYS;
|
|
44
|
+
let json = false;
|
|
45
|
+
let global = false;
|
|
44
46
|
|
|
45
47
|
while (index < args.length) {
|
|
46
48
|
const arg = String(args[index]);
|
|
47
49
|
if (arg === '--help' || arg === '-h' || arg === 'help') {
|
|
48
|
-
return { help: true, root, days };
|
|
50
|
+
return { help: true, root, days, json, global };
|
|
51
|
+
}
|
|
52
|
+
if (arg === '--json') {
|
|
53
|
+
json = true;
|
|
54
|
+
index += 1;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (arg === '--global' || arg === '-g') {
|
|
58
|
+
global = true;
|
|
59
|
+
index += 1;
|
|
60
|
+
continue;
|
|
49
61
|
}
|
|
50
62
|
if (arg === '--root' || arg.startsWith('--root=')) {
|
|
51
63
|
const value = arg.startsWith('--root=') ? arg.slice('--root='.length) : args[++index];
|
|
@@ -65,7 +77,7 @@ function parseArgs(args = []) {
|
|
|
65
77
|
throw new Error(`unknown founder option: ${arg}`);
|
|
66
78
|
}
|
|
67
79
|
|
|
68
|
-
return { help: false, root, days };
|
|
80
|
+
return { help: false, root, days, json, global };
|
|
69
81
|
}
|
|
70
82
|
|
|
71
83
|
function founderNow(env = process.env) {
|
|
@@ -156,17 +168,26 @@ function discoverRepos(root) {
|
|
|
156
168
|
.sort((left, right) => path.basename(left).localeCompare(path.basename(right)));
|
|
157
169
|
}
|
|
158
170
|
|
|
159
|
-
function
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
171
|
+
function scoreOneRepo(repoRoot, bounds) {
|
|
172
|
+
const branch = defaultBranch(repoRoot);
|
|
173
|
+
const history = commitHistory(repoRoot, branch, bounds);
|
|
174
|
+
return {
|
|
175
|
+
repo: path.basename(repoRoot),
|
|
176
|
+
commitsThisWeek: totalInRange(history.timestamps, bounds.currentWindowStart, bounds.until, true),
|
|
177
|
+
commitsLastWeek: totalInRange(history.timestamps, bounds.priorWindowStart, bounds.currentWindowStart),
|
|
178
|
+
perDay: history.perDay,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function collectRepoScorecards(root, bounds, { singleRepo = false, workspace = null } = {}) {
|
|
183
|
+
if (singleRepo) {
|
|
184
|
+
const target = workspace || root;
|
|
185
|
+
if (fs.existsSync(path.join(target, '.git')) || runGit(target, ['rev-parse', '--is-inside-work-tree']).ok) {
|
|
186
|
+
return [scoreOneRepo(target, bounds)];
|
|
187
|
+
}
|
|
188
|
+
return [];
|
|
189
|
+
}
|
|
190
|
+
return discoverRepos(root).map((repoRoot) => scoreOneRepo(repoRoot, bounds)).sort((left, right) => (
|
|
170
191
|
right.commitsThisWeek - left.commitsThisWeek
|
|
171
192
|
|| right.commitsLastWeek - left.commitsLastWeek
|
|
172
193
|
|| left.repo.localeCompare(right.repo)
|
|
@@ -220,12 +241,18 @@ function percentChange(current, previous) {
|
|
|
220
241
|
return Math.round(((current - previous) / previous) * 100);
|
|
221
242
|
}
|
|
222
243
|
|
|
223
|
-
function buildFounderScorecard(root, {
|
|
244
|
+
function buildFounderScorecard(root, {
|
|
245
|
+
days = DEFAULT_DAYS,
|
|
246
|
+
now = new Date(),
|
|
247
|
+
singleRepo = false,
|
|
248
|
+
workspace = null,
|
|
249
|
+
} = {}) {
|
|
224
250
|
const bounds = historyBounds(now, days);
|
|
225
|
-
const perRepo = collectRepoScorecards(root, bounds);
|
|
251
|
+
const perRepo = collectRepoScorecards(root, bounds, { singleRepo, workspace });
|
|
226
252
|
const commitsThisWeek = perRepo.reduce((total, repo) => total + repo.commitsThisWeek, 0);
|
|
227
253
|
const commitsLastWeek = perRepo.reduce((total, repo) => total + repo.commitsLastWeek, 0);
|
|
228
|
-
const
|
|
254
|
+
const taskRoot = singleRepo ? (workspace || root) : root;
|
|
255
|
+
const tasks = readTaskScorecard(taskRoot, bounds);
|
|
229
256
|
|
|
230
257
|
return {
|
|
231
258
|
ts: now.toISOString(),
|
|
@@ -272,8 +299,10 @@ function renderFounderScorecard(scorecard) {
|
|
|
272
299
|
}
|
|
273
300
|
|
|
274
301
|
function showFounderHelp() {
|
|
275
|
-
console.log('usage: atris founder [score] [--days n] [--root dir]');
|
|
302
|
+
console.log('usage: atris founder [score] [--days n] [--root dir] [--json] [--global]');
|
|
276
303
|
console.log('shows the last 7 days against the prior 7 days from git and task receipts.');
|
|
304
|
+
console.log('default scope is this workspace. pass --global to scan sibling repos.');
|
|
305
|
+
console.log('supported flags: --days, --root, --json, --global, --help, -h');
|
|
277
306
|
}
|
|
278
307
|
|
|
279
308
|
function founderCommand(args = [], options = {}) {
|
|
@@ -282,7 +311,8 @@ function founderCommand(args = [], options = {}) {
|
|
|
282
311
|
parsed = parseArgs(args);
|
|
283
312
|
} catch (error) {
|
|
284
313
|
console.error(`error: ${error.message}`);
|
|
285
|
-
|
|
314
|
+
console.error('supported flags: --days, --root, --json, --global, --help, -h');
|
|
315
|
+
return 2;
|
|
286
316
|
}
|
|
287
317
|
if (parsed.help) {
|
|
288
318
|
showFounderHelp();
|
|
@@ -293,11 +323,20 @@ function founderCommand(args = [], options = {}) {
|
|
|
293
323
|
const currentWorkspace = workspaceRoot(cwd);
|
|
294
324
|
const root = parsed.root
|
|
295
325
|
? path.resolve(cwd, parsed.root)
|
|
296
|
-
: defaultScanRoot(cwd, currentWorkspace);
|
|
326
|
+
: (parsed.global ? defaultScanRoot(cwd, currentWorkspace) : currentWorkspace);
|
|
297
327
|
const now = founderNow(options.env || process.env);
|
|
298
|
-
const scorecard = buildFounderScorecard(root, {
|
|
328
|
+
const scorecard = buildFounderScorecard(root, {
|
|
329
|
+
days: parsed.days,
|
|
330
|
+
now,
|
|
331
|
+
singleRepo: !parsed.global && !parsed.root,
|
|
332
|
+
workspace: currentWorkspace,
|
|
333
|
+
});
|
|
299
334
|
appendFounderScorecard(currentWorkspace, scorecard);
|
|
300
|
-
|
|
335
|
+
if (parsed.json) {
|
|
336
|
+
console.log(JSON.stringify(scorecard, null, 2));
|
|
337
|
+
} else {
|
|
338
|
+
console.log(renderFounderScorecard(scorecard));
|
|
339
|
+
}
|
|
301
340
|
return 0;
|
|
302
341
|
}
|
|
303
342
|
|
|
@@ -13,6 +13,8 @@ const {
|
|
|
13
13
|
} = require('../lib/cloud-mission');
|
|
14
14
|
const { apiRequestJson } = require('../utils/api');
|
|
15
15
|
const { decodeJwtClaims, loadCredentials } = require('../utils/auth');
|
|
16
|
+
const { isHelpToken } = require('../lib/noninteractive');
|
|
17
|
+
const { isFreshWorkspace, speakFirstMinute } = require('../lib/first-minute');
|
|
16
18
|
|
|
17
19
|
const PACKAGE_PATH = path.join(__dirname, '..', 'package.json');
|
|
18
20
|
const HUMAN_STATES = Object.freeze({
|
|
@@ -138,6 +140,50 @@ function readBusinessBinding(root) {
|
|
|
138
140
|
}
|
|
139
141
|
}
|
|
140
142
|
|
|
143
|
+
function isBoundCloudComputer(root = process.cwd()) {
|
|
144
|
+
return Boolean(readBusinessBinding(root));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function parseMissionId(args = []) {
|
|
148
|
+
const list = Array.isArray(args) ? args : [];
|
|
149
|
+
for (let i = 0; i < list.length; i += 1) {
|
|
150
|
+
const value = String(list[i] || '');
|
|
151
|
+
if (value === '--mission' || value === '--mission-id' || value === '--id') {
|
|
152
|
+
const next = list[i + 1];
|
|
153
|
+
if (next && !String(next).startsWith('-') && !isHelpToken(next)) return String(next).trim();
|
|
154
|
+
return '';
|
|
155
|
+
}
|
|
156
|
+
if (value.startsWith('--mission=') || value.startsWith('--mission-id=') || value.startsWith('--id=')) {
|
|
157
|
+
return value.slice(value.indexOf('=') + 1).trim();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Bare positional id for stop/ready/approve (never treat free-sentence ask text as an id).
|
|
161
|
+
const flags = new Set(['--json', '--help', '-h', 'help']);
|
|
162
|
+
const positional = list.find((value) => value && !String(value).startsWith('-') && !flags.has(value));
|
|
163
|
+
if (!positional) return '';
|
|
164
|
+
// Mission ids are compact tokens, not multi-word wants.
|
|
165
|
+
if (list.filter((value) => value && !String(value).startsWith('-') && !flags.has(value)).length > 1) return '';
|
|
166
|
+
return String(positional).trim();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Scratch / unbound folders must not drive account-current cloud work.
|
|
171
|
+
* Require an explicit mission/task id: atris stop --mission <id>
|
|
172
|
+
*/
|
|
173
|
+
function refuseUnboundCloudComputer(args = [], options = {}, commandName = 'stop') {
|
|
174
|
+
const root = options.root || process.cwd();
|
|
175
|
+
if (options.businessId || isBoundCloudComputer(root)) return null;
|
|
176
|
+
if (commandName !== 'ask') {
|
|
177
|
+
const missionId = parseMissionId(args);
|
|
178
|
+
if (missionId) return null;
|
|
179
|
+
}
|
|
180
|
+
return new HumanCommandError(
|
|
181
|
+
'This folder is not a bound cloud-computer (missing .atris/business.json).',
|
|
182
|
+
`Pass --mission <id>: atris ${commandName} --mission <id>`,
|
|
183
|
+
2,
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
141
187
|
function credentialUserId(credentials) {
|
|
142
188
|
const claims = decodeJwtClaims(credentials && credentials.token);
|
|
143
189
|
return String(
|
|
@@ -488,12 +534,30 @@ function credentialsOrThrow(options = {}) {
|
|
|
488
534
|
return credentials;
|
|
489
535
|
}
|
|
490
536
|
|
|
537
|
+
function isBareAsk(args = []) {
|
|
538
|
+
return (Array.isArray(args) ? args : []).every((value) => value === '--json');
|
|
539
|
+
}
|
|
540
|
+
|
|
491
541
|
async function askCommand(args, options = {}) {
|
|
492
542
|
const asJson = args.includes('--json');
|
|
493
543
|
if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
|
|
494
544
|
(options.log || console.log)('Usage: atris ask "what you want" [--budget <usd>] [--json]');
|
|
495
545
|
return 0;
|
|
496
546
|
}
|
|
547
|
+
const root = options.root || process.cwd();
|
|
548
|
+
const fresh = isFreshWorkspace(root);
|
|
549
|
+
// Empty folder talks like bare atris. Do not start a mission or hit the
|
|
550
|
+
// network. After init, bare ask stays in the room.
|
|
551
|
+
if (!options.businessId && (fresh || isBareAsk(args))) {
|
|
552
|
+
return speakFirstMinute({
|
|
553
|
+
root,
|
|
554
|
+
fresh,
|
|
555
|
+
asJson,
|
|
556
|
+
log: options.log || console.log,
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
const unbound = refuseUnboundCloudComputer(args, options, 'ask');
|
|
560
|
+
if (unbound) return reportError(unbound, 'start that mission', asJson, options);
|
|
497
561
|
try {
|
|
498
562
|
const parsed = parseAskArgs(args);
|
|
499
563
|
const credentials = credentialsOrThrow(options);
|
|
@@ -620,17 +684,26 @@ async function proofCommand(args = [], options = {}) {
|
|
|
620
684
|
|
|
621
685
|
async function changeCurrentMission(action, body, args, options = {}) {
|
|
622
686
|
const asJson = args.includes('--json');
|
|
687
|
+
const commandName = action === 'approve' ? 'approve' : action === 'answer' ? 'mission answer' : 'stop';
|
|
688
|
+
const unbound = refuseUnboundCloudComputer(args, options, commandName === 'mission answer' ? 'stop' : commandName);
|
|
689
|
+
if (unbound) return reportError(unbound, action === 'approve' ? 'approve that step' : action === 'answer' ? 'send that answer' : 'stop that mission', asJson, options);
|
|
623
690
|
try {
|
|
624
691
|
credentialsOrThrow(options);
|
|
625
|
-
const
|
|
626
|
-
|
|
692
|
+
const explicitId = parseMissionId(args);
|
|
693
|
+
let currentCard;
|
|
694
|
+
if (explicitId) {
|
|
695
|
+
currentCard = missionCard({ mission_id: explicitId }, {}, options.now ? options.now() : Date.now());
|
|
696
|
+
} else {
|
|
697
|
+
const current = await loadCurrentMission(options);
|
|
698
|
+
currentCard = missionCard(current, {}, options.now ? options.now() : Date.now());
|
|
699
|
+
}
|
|
627
700
|
if (!currentCard.mission_id) {
|
|
628
701
|
throw new HumanCommandError(
|
|
629
702
|
'Atris could not identify the current mission.',
|
|
630
703
|
'Run: atris mission',
|
|
631
704
|
);
|
|
632
705
|
}
|
|
633
|
-
if (['approve', 'answer'].includes(action) && currentCard.state !== 'your_turn') {
|
|
706
|
+
if (['approve', 'answer'].includes(action) && currentCard.state !== 'your_turn' && !explicitId) {
|
|
634
707
|
throw new HumanCommandError(
|
|
635
708
|
action === 'approve'
|
|
636
709
|
? 'Nothing is waiting for your approval.'
|
|
@@ -658,7 +731,7 @@ async function approveCommand(args, options = {}) {
|
|
|
658
731
|
|
|
659
732
|
async function stopCommand(args, options = {}) {
|
|
660
733
|
if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
|
|
661
|
-
(options.log || console.log)('Usage: atris stop [--json]');
|
|
734
|
+
(options.log || console.log)('Usage: atris stop [--mission <id>] [--json]');
|
|
662
735
|
return 0;
|
|
663
736
|
}
|
|
664
737
|
return changeCurrentMission('stop', {}, args, options);
|
|
@@ -776,6 +849,8 @@ async function readyCommand(args, options = {}) {
|
|
|
776
849
|
(options.log || console.log)('Usage: atris ready --json');
|
|
777
850
|
return 0;
|
|
778
851
|
}
|
|
852
|
+
const unbound = refuseUnboundCloudComputer(args, options, 'ready');
|
|
853
|
+
if (unbound) return reportError(unbound, 'check readiness', asJson, options);
|
|
779
854
|
const request = options.apiRequestJson || apiRequestJson;
|
|
780
855
|
const load = options.loadCredentials || loadCredentials;
|
|
781
856
|
const credentials = load() || {};
|
package/commands/improve.js
CHANGED
|
@@ -433,10 +433,19 @@ function collectImproveVitals(options = {}, deps = {}) {
|
|
|
433
433
|
const cronFn = deps.cronInstalled || cronInstalled;
|
|
434
434
|
// Per-repo slots (pr 310): the crontab marker is derived from the root, so
|
|
435
435
|
// the check must ask about THIS repo's markers, not the legacy default.
|
|
436
|
-
const
|
|
437
|
-
try { return pulse.resolvePulseSlot(root)
|
|
436
|
+
const slot = (() => {
|
|
437
|
+
try { return pulse.resolvePulseSlot(root); } catch { return null; }
|
|
438
438
|
})();
|
|
439
|
-
const
|
|
439
|
+
const slotMarkers = slot ? slot.markers : undefined;
|
|
440
|
+
const legacyCronInstalled = Boolean(slotMarkers ? cronFn(slotMarkers) : cronFn());
|
|
441
|
+
const homeDir = deps.homeDir || os.homedir();
|
|
442
|
+
const launchdInstalled = (() => {
|
|
443
|
+
if (!slot) return false;
|
|
444
|
+
const label = `com.atris.pulse.${slot.marker.toLowerCase().replace(/^atris_pulse_/, '').replace(/[^a-z0-9]+/g, '-')}`;
|
|
445
|
+
const plistPath = path.join(homeDir, 'Library', 'LaunchAgents', `${label}.plist`);
|
|
446
|
+
return fs.existsSync(plistPath);
|
|
447
|
+
})();
|
|
448
|
+
const installed = launchdInstalled || legacyCronInstalled;
|
|
440
449
|
const installNudge = installed ? null : 'the scheduled improve loop is off. turn it on: atris pulse install --model claude-sonnet-5';
|
|
441
450
|
|
|
442
451
|
const experimentsPath = path.join(root, '.atris', 'state', 'experiments-daily.json');
|
|
@@ -803,6 +812,29 @@ async function runImprove(opts = {}, deps = {}) {
|
|
|
803
812
|
};
|
|
804
813
|
};
|
|
805
814
|
|
|
815
|
+
// Dry-run is a local plan only. Never call the paid /api/improve endpoint.
|
|
816
|
+
if (opts.dryRun) {
|
|
817
|
+
const finishedAt = now();
|
|
818
|
+
return {
|
|
819
|
+
ok: true,
|
|
820
|
+
source: 'local',
|
|
821
|
+
reason: 'dry_run',
|
|
822
|
+
summary: {
|
|
823
|
+
shipped: '(dry-run) no paid improve call',
|
|
824
|
+
verify: null,
|
|
825
|
+
reward: null,
|
|
826
|
+
credits: null,
|
|
827
|
+
files: [],
|
|
828
|
+
dry_run: true,
|
|
829
|
+
},
|
|
830
|
+
scorecardPath: null,
|
|
831
|
+
journalPath: null,
|
|
832
|
+
receipt: 'skipped',
|
|
833
|
+
startedAt,
|
|
834
|
+
finishedAt,
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
|
|
806
838
|
// No auth → local fallback (or report if fallback disabled).
|
|
807
839
|
if (!creds || !creds.token) {
|
|
808
840
|
if (!localFallbackEligible) {
|
|
@@ -934,6 +966,24 @@ function formatImproveReport(result = {}) {
|
|
|
934
966
|
|
|
935
967
|
const REVISIONS_SCHEMA = 'atris.improve_revisions.v1';
|
|
936
968
|
const REVISION_WINDOW_HOURS = 72;
|
|
969
|
+
// Shared journal and state files are touched by agents and humans alike as a
|
|
970
|
+
// matter of routine (daily logs, now.md, run receipts), so overlap there says
|
|
971
|
+
// nothing about the quality of a landing. On 2026-08-21 half the "revisions"
|
|
972
|
+
// in a live reading were journal commits following journal commits. Only
|
|
973
|
+
// product-file overlap counts.
|
|
974
|
+
const REVISION_IGNORED_FILES = [
|
|
975
|
+
/^atris\/logs\//,
|
|
976
|
+
/^atris\/runs\//,
|
|
977
|
+
/^atris\/team\/[^/]+\/(?:logs|now\.md)/,
|
|
978
|
+
/^atris\/(?:now|thinking|TODO)\.md$/,
|
|
979
|
+
/^atris\/brain\//,
|
|
980
|
+
/^atris\/status\//,
|
|
981
|
+
/^\.atris\//,
|
|
982
|
+
];
|
|
983
|
+
|
|
984
|
+
function isRevisionSignalFile(file) {
|
|
985
|
+
return !REVISION_IGNORED_FILES.some((re) => re.test(String(file || '')));
|
|
986
|
+
}
|
|
937
987
|
const REVISION_WINDOW_MS = REVISION_WINDOW_HOURS * 60 * 60 * 1000;
|
|
938
988
|
const AGENT_TRAILER_MARKERS = [
|
|
939
989
|
'atris-builder[bot]',
|
|
@@ -1041,7 +1091,7 @@ function collectRevisionSignals(root, options = {}) {
|
|
|
1041
1091
|
|
|
1042
1092
|
const revisions = [];
|
|
1043
1093
|
for (const landing of landings) {
|
|
1044
|
-
const landedFiles = new Set(filesOf(landing));
|
|
1094
|
+
const landedFiles = new Set(filesOf(landing).filter(isRevisionSignalFile));
|
|
1045
1095
|
if (!landedFiles.size) continue;
|
|
1046
1096
|
const revisedBy = [];
|
|
1047
1097
|
const touched = new Set();
|