atris 3.15.52 → 3.15.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/atris/features/README.md +10 -7
- package/commands/computer.js +40 -4
- package/commands/mission.js +9 -3
- package/commands/radar.js +48 -2
- package/commands/worktree.js +20 -1
- package/package.json +1 -1
package/atris/features/README.md
CHANGED
|
@@ -79,17 +79,20 @@ cp atris/features/_templates/validate.md.template atris/features/your-feature-na
|
|
|
79
79
|
|
|
80
80
|
### Active Features
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
Close remaining audit gaps from self-audit
|
|
84
|
-
- **Files:** atris/team/*.md, atris/features/README.md
|
|
85
|
-
- **Status:** complete
|
|
86
|
-
- **Keywords:** audit, persona, cleanup
|
|
87
|
-
- **What:** Add PERSONA.md reference to all 5 agent specs, clean up stale feature statuses
|
|
82
|
+
None.
|
|
88
83
|
|
|
89
84
|
---
|
|
90
85
|
|
|
91
86
|
### Completed Features
|
|
92
87
|
|
|
88
|
+
#### audit-gaps
|
|
89
|
+
Close remaining audit gaps from self-audit
|
|
90
|
+
- **Files:** atris/team/*/MEMBER.md, atris/features/README.md, atris/features/audit-gaps/*
|
|
91
|
+
- **Status:** complete
|
|
92
|
+
- **Keywords:** audit, persona, cleanup
|
|
93
|
+
- **What:** Agent member specs reference PERSONA.md for communication style, and stale feature statuses are cleaned up
|
|
94
|
+
- **Completed:** 2026-05-19
|
|
95
|
+
|
|
93
96
|
#### endstate
|
|
94
97
|
Public benchmark for proving a coordinated stack beats a pinned single-model baseline
|
|
95
98
|
- **Files:** atris/features/endstate/*, commands/autopilot.js, commands/experiments.js, commands/loop.js, lib/wiki.js
|
|
@@ -116,7 +119,7 @@ Local-first project wiki with cloud opt-in
|
|
|
116
119
|
|
|
117
120
|
#### self-improving-loop
|
|
118
121
|
Make Atris recursive — validate.md lessons feed back into the next idea.md
|
|
119
|
-
- **Files:** atris/lessons.md (new), atris.md, atris/team/navigator.md, atris/team/validator.md, atris/MAP.md
|
|
122
|
+
- **Files:** atris/lessons.md (new), atris.md, atris/team/navigator/MEMBER.md, atris/team/validator/MEMBER.md, atris/MAP.md
|
|
120
123
|
- **Status:** complete
|
|
121
124
|
- **Keywords:** recursion, lessons, feedback-loop, self-improving, lessons.md
|
|
122
125
|
- **What:** lessons.md accumulates validated learnings; navigator reads them before planning; validator harvests them after validating
|
package/commands/computer.js
CHANGED
|
@@ -1126,15 +1126,35 @@ async function resolveComputerCommandContext(token, options = {}) {
|
|
|
1126
1126
|
? await resolveBusinessContextBySlug(token, options.businessSlug, { preferCache: true })
|
|
1127
1127
|
: await resolveBusinessContext(token);
|
|
1128
1128
|
if (!ctx?.businessId) return null;
|
|
1129
|
+
const workspaceId = options.workspaceId
|
|
1130
|
+
? await resolveWorkspaceSelector(token, ctx, options.workspaceId)
|
|
1131
|
+
: ctx.workspaceId;
|
|
1129
1132
|
return {
|
|
1130
1133
|
...ctx,
|
|
1131
|
-
workspaceId
|
|
1134
|
+
workspaceId,
|
|
1132
1135
|
};
|
|
1133
1136
|
}
|
|
1134
1137
|
|
|
1135
1138
|
return resolveBusinessContext(token);
|
|
1136
1139
|
}
|
|
1137
1140
|
|
|
1141
|
+
function looksLikeWorkspaceId(input) {
|
|
1142
|
+
const value = String(input || '').trim();
|
|
1143
|
+
if (!value) return false;
|
|
1144
|
+
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value)
|
|
1145
|
+
|| /^ws-[a-z0-9_-]+$/i.test(value);
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
async function resolveWorkspaceSelector(token, ctx, input) {
|
|
1149
|
+
const selector = String(input || '').trim();
|
|
1150
|
+
if (!selector) return ctx.workspaceId;
|
|
1151
|
+
if (selector === ctx.workspaceId || looksLikeWorkspaceId(selector)) return selector;
|
|
1152
|
+
|
|
1153
|
+
const workspaces = await listBusinessWorkspaces(token, ctx);
|
|
1154
|
+
const workspace = resolveWorkspaceFromList(workspaces, selector);
|
|
1155
|
+
return workspace?.id || selector;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1138
1158
|
async function resolveBusinessOwnerForCreate(token, businessSlug = null) {
|
|
1139
1159
|
const wantedSlug = businessSlug ? String(businessSlug).trim() : null;
|
|
1140
1160
|
if (wantedSlug) {
|
|
@@ -1260,6 +1280,18 @@ function formatWorkspaceRef(workspace) {
|
|
|
1260
1280
|
return workspace.name ? `${workspace.name} (${workspace.id})` : workspace.id;
|
|
1261
1281
|
}
|
|
1262
1282
|
|
|
1283
|
+
function workspaceMatchesInput(workspace, input) {
|
|
1284
|
+
if (!workspace || !input) return false;
|
|
1285
|
+
const wanted = String(input).trim().toLowerCase();
|
|
1286
|
+
if (!wanted) return false;
|
|
1287
|
+
return String(workspace.id || '').toLowerCase() === wanted
|
|
1288
|
+
|| String(workspace.name || '').toLowerCase() === wanted;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
function resolveWorkspaceFromList(workspaces, input) {
|
|
1292
|
+
return (workspaces || []).find((workspace) => workspaceMatchesInput(workspace, input)) || null;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1263
1295
|
function formatLeaseAge(seconds) {
|
|
1264
1296
|
const value = Number(seconds);
|
|
1265
1297
|
if (!Number.isFinite(value) || value < 0) return '-';
|
|
@@ -1519,7 +1551,11 @@ async function computerStatus(token, ctx = null) {
|
|
|
1519
1551
|
if (d.endpoint) console.log(` Endpoint: ${d.endpoint}`);
|
|
1520
1552
|
const workspaces = await listBusinessWorkspaces(token, ctx);
|
|
1521
1553
|
const defaultWorkspace = workspaces.find((workspace) => workspace.is_default);
|
|
1522
|
-
const
|
|
1554
|
+
const resolvedTargetWorkspace = resolveWorkspaceFromList(workspaces, ctx.workspaceId);
|
|
1555
|
+
const targetWorkspace = resolvedTargetWorkspace || (ctx.workspaceId ? { id: ctx.workspaceId } : null);
|
|
1556
|
+
const probeCtx = resolvedTargetWorkspace?.id
|
|
1557
|
+
? { ...ctx, workspaceId: resolvedTargetWorkspace.id }
|
|
1558
|
+
: ctx;
|
|
1523
1559
|
console.log(` Default workspace: ${formatWorkspaceRef(defaultWorkspace)}`);
|
|
1524
1560
|
console.log(` Target workspace: ${formatWorkspaceRef(targetWorkspace)}`);
|
|
1525
1561
|
const attachedFromStatus = d.attached_workspace_id
|
|
@@ -1534,8 +1570,8 @@ async function computerStatus(token, ctx = null) {
|
|
|
1534
1570
|
console.log(` Lease age: ${formatLeaseAge(d.lease_age_seconds)}`);
|
|
1535
1571
|
if (d.takeover_hint) console.log(` Takeover hint: ${d.takeover_hint}`);
|
|
1536
1572
|
}
|
|
1537
|
-
if (status === 'running' && d.endpoint &&
|
|
1538
|
-
const attached = await probeAttachedWorkspace(token,
|
|
1573
|
+
if (status === 'running' && d.endpoint && probeCtx.workspaceId) {
|
|
1574
|
+
const attached = await probeAttachedWorkspace(token, probeCtx);
|
|
1539
1575
|
if (!attachedFromStatus) {
|
|
1540
1576
|
const attachedWorkspace = workspaces.find((workspace) => workspace.id === attached.workspaceId) || (attached.workspaceId ? { id: attached.workspaceId } : null);
|
|
1541
1577
|
console.log(` Attached workspace: ${formatWorkspaceRef(attachedWorkspace)}`);
|
package/commands/mission.js
CHANGED
|
@@ -748,12 +748,18 @@ function missionVerifierPassed(mission) {
|
|
|
748
748
|
|
|
749
749
|
function missionDueAt(mission, now = new Date()) {
|
|
750
750
|
const cadenceSeconds = parseCadenceSeconds(mission.cadence);
|
|
751
|
-
if (!mission.last_tick_at
|
|
751
|
+
if (!mission.last_tick_at) return true;
|
|
752
|
+
if (cadenceSeconds === 0) return !(mission.always_on && missionVerifierPassed(mission));
|
|
752
753
|
const lastTickAt = Date.parse(mission.last_tick_at);
|
|
753
754
|
if (!Number.isFinite(lastTickAt)) return true;
|
|
754
755
|
return now.getTime() - lastTickAt >= cadenceSeconds * 1000;
|
|
755
756
|
}
|
|
756
757
|
|
|
758
|
+
function missionSelectableForLoop(mission, now = new Date()) {
|
|
759
|
+
return missionIsRunnable(mission)
|
|
760
|
+
&& !(mission.always_on && missionVerifierPassed(mission) && !missionDueAt(mission, now));
|
|
761
|
+
}
|
|
762
|
+
|
|
757
763
|
function secondsUntilMissionDue(mission, now = new Date()) {
|
|
758
764
|
const cadenceSeconds = parseCadenceSeconds(mission?.cadence);
|
|
759
765
|
if (!mission || !mission.last_tick_at || cadenceSeconds === 0) return 0;
|
|
@@ -780,7 +786,7 @@ function missionSortTime(mission) {
|
|
|
780
786
|
|
|
781
787
|
function selectDueMission(root = process.cwd(), now = new Date()) {
|
|
782
788
|
const candidates = listMissions(root)
|
|
783
|
-
.filter(
|
|
789
|
+
.filter((mission) => missionSelectableForLoop(mission, now))
|
|
784
790
|
.filter((mission) => mission.verifier)
|
|
785
791
|
.filter((mission) => mission.always_on || !missionVerifierPassed(mission))
|
|
786
792
|
.filter((mission) => missionDueAt(mission, now));
|
|
@@ -799,7 +805,7 @@ function selectDueMission(root = process.cwd(), now = new Date()) {
|
|
|
799
805
|
|
|
800
806
|
function selectCodexGoalMission(root = process.cwd(), now = new Date()) {
|
|
801
807
|
const candidates = listMissions(root)
|
|
802
|
-
.filter(
|
|
808
|
+
.filter((mission) => missionSelectableForLoop(mission, now));
|
|
803
809
|
|
|
804
810
|
candidates.sort((a, b) => {
|
|
805
811
|
const aCaller = runnerUsesCallerSession(a.runner) ? 1 : 0;
|
package/commands/radar.js
CHANGED
|
@@ -432,6 +432,31 @@ function ownerForTask(task) {
|
|
|
432
432
|
return task.assigned_to || task.claimed_by || task.metadata?.assigned_to || '-';
|
|
433
433
|
}
|
|
434
434
|
|
|
435
|
+
function taskSessionReason(task) {
|
|
436
|
+
if (!task) return null;
|
|
437
|
+
if (task.status === 'review') return task.metadata?.agent_certified ? 'certified review' : 'review task';
|
|
438
|
+
if (ownerActionRequired(task)) return 'owner action required';
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function taskSessionAction(agent, task, taskWorkspaceRoot) {
|
|
443
|
+
if (!task) return null;
|
|
444
|
+
const ref = taskRef(task);
|
|
445
|
+
const pid = agent?.pid || '?';
|
|
446
|
+
const actor = agent?.agent || 'agent';
|
|
447
|
+
const reviewCommand = taskWorkspaceRoot
|
|
448
|
+
? `cd ${shellQuote(taskWorkspaceRoot)} && atris task reviews --limit 5`
|
|
449
|
+
: 'atris task reviews --limit 5';
|
|
450
|
+
if (task.status === 'review') {
|
|
451
|
+
if (task.metadata?.agent_certified) {
|
|
452
|
+
return `handoff complete for ${ref}; close pid ${pid} or claim fresh work as ${actor}`;
|
|
453
|
+
}
|
|
454
|
+
return `review or hand off ${ref}: ${reviewCommand}`;
|
|
455
|
+
}
|
|
456
|
+
if (ownerActionRequired(task)) return `owner-gated ${ref}; close pid ${pid} or wait for owner action`;
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
|
|
435
460
|
function summarize(tasks, missions, worktrees, agents) {
|
|
436
461
|
const count = (rows, pred) => rows.filter(pred).length;
|
|
437
462
|
return {
|
|
@@ -505,7 +530,7 @@ function collectRadar(options = {}) {
|
|
|
505
530
|
const taskWorkspaceRoot = findTaskWorkspaceRoot(agent.cwd, deps);
|
|
506
531
|
const agentTasks = taskWorkspaceRoot ? loadTasksCached(taskWorkspaceRoot, deps, taskCache) : [];
|
|
507
532
|
const task = taskForCwd(agentTasks, agent.cwd, taskWorkspaceRoot);
|
|
508
|
-
const taskReason = task ?
|
|
533
|
+
const taskReason = task ? taskSessionReason(task) : untaskedReason(agent, taskWorkspaceRoot, agentTasks);
|
|
509
534
|
return {
|
|
510
535
|
...agent,
|
|
511
536
|
task: taskRef(task),
|
|
@@ -513,7 +538,7 @@ function collectRadar(options = {}) {
|
|
|
513
538
|
owner: ownerForTask(task),
|
|
514
539
|
task_workspace: taskWorkspaceRoot ? repoLabel(taskWorkspaceRoot) : null,
|
|
515
540
|
task_reason: taskReason,
|
|
516
|
-
task_action: task ?
|
|
541
|
+
task_action: task ? taskSessionAction(agent, task, taskWorkspaceRoot) : untaskedAction(agent, taskWorkspaceRoot, agentTasks),
|
|
517
542
|
};
|
|
518
543
|
});
|
|
519
544
|
const osState = {
|
|
@@ -624,6 +649,11 @@ function sortedAgents(agents = []) {
|
|
|
624
649
|
function agentProcessNextAction(agents = [], fallback = 'no obvious process action') {
|
|
625
650
|
const stopped = agents.filter(agent => agent.status !== 'active').length;
|
|
626
651
|
if (stopped > 0) return `inspect ${stopped} stopped agent session${stopped === 1 ? '' : 's'}`;
|
|
652
|
+
const ownerGated = agents.filter(agent => agent.task_reason === 'owner action required');
|
|
653
|
+
if (ownerGated.length > 0) {
|
|
654
|
+
const tasks = [...new Set(ownerGated.map(agent => agent.task).filter(Boolean))].slice(0, 3).join(', ');
|
|
655
|
+
return `owner gate blocks ${ownerGated.length} session${ownerGated.length === 1 ? '' : 's'}${tasks ? ` on ${tasks}` : ''}; wait for owner action or close idle sessions`;
|
|
656
|
+
}
|
|
627
657
|
const taskLoad = summarizeTaskLoad(agents);
|
|
628
658
|
const reviewBound = taskLoad.find(row => row.status.split(/,\s*/).includes('review'));
|
|
629
659
|
if (reviewBound) return `close or hand off ${reviewBound.sessions} session${reviewBound.sessions === 1 ? '' : 's'} still bound to review task ${reviewBound.task}`;
|
|
@@ -744,6 +774,22 @@ function renderAgentTop(data) {
|
|
|
744
774
|
lines.push(`- ${agent.pid} ${agent.repo || agent.cwd || '-'}: ${agent.task_reason || 'unmapped'} -> ${agent.task_action || 'inspect session'}`);
|
|
745
775
|
}
|
|
746
776
|
}
|
|
777
|
+
const ownerGatedAgents = payload.agents.filter(row => row.task_reason === 'owner action required').slice(0, 8);
|
|
778
|
+
if (ownerGatedAgents.length) {
|
|
779
|
+
lines.push('');
|
|
780
|
+
lines.push(`Owner-gated: ${ownerGatedAgents.length} session${ownerGatedAgents.length === 1 ? '' : 's'} waiting on owner-only tasks.`);
|
|
781
|
+
for (const agent of ownerGatedAgents) {
|
|
782
|
+
lines.push(`- ${agent.pid} ${agent.repo || agent.cwd || '-'} ${agent.task}: ${agent.task_action || 'wait for owner action'}`);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
const reviewBoundAgents = payload.agents.filter(row => row.task_status === 'review').slice(0, 8);
|
|
786
|
+
if (reviewBoundAgents.length) {
|
|
787
|
+
lines.push('');
|
|
788
|
+
lines.push(`Review-bound: ${reviewBoundAgents.length} session${reviewBoundAgents.length === 1 ? '' : 's'} still tied to Review tasks.`);
|
|
789
|
+
for (const agent of reviewBoundAgents) {
|
|
790
|
+
lines.push(`- ${agent.pid} ${agent.repo || agent.cwd || '-'} ${agent.task}: ${agent.task_reason || 'review'} -> ${agent.task_action || 'close or hand off session'}`);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
747
793
|
const taskLoadRows = payload.task_load.filter(row => row.attention).slice(0, 8);
|
|
748
794
|
if (taskLoadRows.length) {
|
|
749
795
|
lines.push('');
|
package/commands/worktree.js
CHANGED
|
@@ -285,6 +285,12 @@ function createOrFindPr(root, branch, targetRef, title, dryRun) {
|
|
|
285
285
|
return created.stdout.trim();
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
+
function prMergeRef(prOutput) {
|
|
289
|
+
const text = String(prOutput || '').trim();
|
|
290
|
+
if (!text || text.startsWith('dry-run:')) return '';
|
|
291
|
+
return text.split(/\s+/)[0];
|
|
292
|
+
}
|
|
293
|
+
|
|
288
294
|
function shipWorktree(args) {
|
|
289
295
|
const root = repoRoot();
|
|
290
296
|
const dryRun = hasFlag(args, '--dry-run');
|
|
@@ -359,8 +365,20 @@ function shipWorktree(args) {
|
|
|
359
365
|
if (merge) {
|
|
360
366
|
console.log('merge: requested');
|
|
361
367
|
if (!dryRun) {
|
|
362
|
-
const
|
|
368
|
+
const mergeRef = prMergeRef(pr);
|
|
369
|
+
const mergeArgs = ['pr', 'merge'];
|
|
370
|
+
if (mergeRef) mergeArgs.push(mergeRef);
|
|
371
|
+
mergeArgs.push('--merge');
|
|
372
|
+
const merged = spawnSync('gh', mergeArgs, { cwd: root, encoding: 'utf8' });
|
|
363
373
|
if (merged.status !== 0) throw new Error((merged.stderr || merged.stdout || 'gh pr merge failed').trim());
|
|
374
|
+
console.log('merge: merged');
|
|
375
|
+
const deleted = runGit(['push', 'origin', '--delete', branch], { cwd: root, check: false });
|
|
376
|
+
if (deleted.status === 0) {
|
|
377
|
+
console.log(`merge: remote branch deleted ${branch}`);
|
|
378
|
+
} else {
|
|
379
|
+
const deleteOutput = (deleted.stderr || deleted.stdout || 'remote branch delete failed').trim();
|
|
380
|
+
console.log(`merge: remote branch delete skipped: ${deleteOutput}`);
|
|
381
|
+
}
|
|
364
382
|
}
|
|
365
383
|
}
|
|
366
384
|
} else {
|
|
@@ -461,6 +479,7 @@ module.exports = {
|
|
|
461
479
|
defaultWorktreePath,
|
|
462
480
|
parseWorktrees,
|
|
463
481
|
normalizeTargetRef,
|
|
482
|
+
prMergeRef,
|
|
464
483
|
slugify,
|
|
465
484
|
statusCounts,
|
|
466
485
|
swarloClaim,
|