atris 3.15.57 → 3.16.1
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 +2 -2
- package/GETTING_STARTED.md +1 -1
- package/PERSONA.md +4 -4
- package/README.md +12 -11
- package/atris/skills/copy-editor/SKILL.md +30 -4
- package/atris/skills/improve/SKILL.md +18 -20
- package/atris/wiki/concepts/agent-activation-contract.md +5 -3
- package/atris/wiki/concepts/workspace-initialization-contract.md +4 -4
- package/atris/wiki/index.md +1 -0
- package/ax +522 -73
- package/bin/atris.js +78 -44
- package/commands/align.js +0 -14
- package/commands/apps.js +102 -1
- package/commands/autopilot.js +628 -31
- package/commands/brain.js +219 -34
- package/commands/brainstorm.js +0 -829
- package/commands/compile.js +569 -0
- package/commands/computer.js +0 -60
- package/commands/improve.js +501 -0
- package/commands/integrations.js +233 -71
- package/commands/lesson.js +44 -0
- package/commands/member.js +4498 -226
- package/commands/mission.js +302 -27
- package/commands/now.js +89 -1
- package/commands/probe.js +366 -0
- package/commands/radar.js +181 -56
- package/commands/recap.js +203 -0
- package/commands/skill.js +6 -2
- package/commands/soul.js +0 -4
- package/commands/task.js +5587 -499
- package/commands/terminal.js +14 -10
- package/commands/wiki.js +87 -1
- package/commands/workflow.js +288 -73
- package/commands/worktree.js +52 -15
- package/commands/xp.js +6 -65
- package/lib/auto-accept-certified.js +294 -0
- package/lib/file-ops.js +0 -184
- package/lib/member-alive.js +232 -0
- package/lib/policy-lessons.js +280 -0
- package/lib/receipt-evidence.js +64 -0
- package/lib/state-detection.js +75 -1
- package/lib/task-db.js +568 -16
- package/lib/task-proof.js +43 -0
- package/package.json +1 -1
- package/utils/auth.js +13 -4
- package/commands/research.js +0 -52
- package/lib/section-merge.js +0 -196
package/commands/radar.js
CHANGED
|
@@ -114,26 +114,19 @@ function loadTasks(root, deps) {
|
|
|
114
114
|
if (!deps.exists(file)) return [];
|
|
115
115
|
const payload = safeJson(deps.readFile(file, 'utf8'), {});
|
|
116
116
|
const tasks = Array.isArray(payload.tasks) ? payload.tasks : [];
|
|
117
|
-
return tasks.map(task => {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
};
|
|
131
|
-
Object.defineProperty(row, 'routing_text', {
|
|
132
|
-
enumerable: false,
|
|
133
|
-
value: messages.map(message => message && message.content).filter(Boolean).join(' '),
|
|
134
|
-
});
|
|
135
|
-
return row;
|
|
136
|
-
});
|
|
117
|
+
return tasks.map(task => ({
|
|
118
|
+
id: task.id,
|
|
119
|
+
display_id: task.display_id,
|
|
120
|
+
legacy_ref: task.legacy_ref,
|
|
121
|
+
title: task.title,
|
|
122
|
+
status: task.status,
|
|
123
|
+
tag: task.tag,
|
|
124
|
+
workspace_root: task.workspace_root,
|
|
125
|
+
claimed_by: task.claimed_by,
|
|
126
|
+
assigned_to: task.metadata?.assigned_to || task.assigned_to || null,
|
|
127
|
+
metadata: task.metadata || {},
|
|
128
|
+
messages: Array.isArray(task.messages) ? task.messages : [],
|
|
129
|
+
}));
|
|
137
130
|
}
|
|
138
131
|
|
|
139
132
|
function findTaskWorkspaceRoot(cwd, deps) {
|
|
@@ -172,7 +165,7 @@ function untaskedAction(agent, taskWorkspaceRoot, tasks) {
|
|
|
172
165
|
if (reason === 'cwd unknown') return `inspect pid ${pid} cwd with lsof`;
|
|
173
166
|
if (reason === 'no active task') return `cd ${shellQuote(taskWorkspaceRoot)} && atris task next --as ${actor}`;
|
|
174
167
|
if (reason === 'empty task projection') return `cd ${shellQuote(taskWorkspaceRoot)} && atris task new "<small concrete title>" --tag ops`;
|
|
175
|
-
return `inspect ${agent.cwd || 'unknown cwd'} for missing Atris task plane or close pid ${pid} if idle`;
|
|
168
|
+
return `inspect ${agent.cwd || 'unknown cwd'} for missing Atris task plane or close pid ${pid} only with operator approval if idle`;
|
|
176
169
|
}
|
|
177
170
|
|
|
178
171
|
function readJsonFile(file, deps, fallback = null) {
|
|
@@ -340,8 +333,6 @@ function loadBusinessCollaboration(root, deps, team = {}) {
|
|
|
340
333
|
const episodes = countJsonLines(path.join(root, '.atris', 'state', 'episodes.jsonl'), deps);
|
|
341
334
|
const scorecards = countJsonLines(path.join(root, '.atris', 'state', 'scorecards.jsonl'), deps);
|
|
342
335
|
const computerDirs = countDirectoryEntries(path.join(root, 'atris', 'computers'), deps, name => !name.startsWith('.'));
|
|
343
|
-
const runtimeComputer = runtime && (runtime.workspace_id || runtime.business_id || runtime.scope === 'local-business-computer') ? 1 : 0;
|
|
344
|
-
const computers = Math.max(computerDirs, runtimeComputer);
|
|
345
336
|
const hasOnboarding = ingestPacks > 0 || starterBriefs > 0 || onePagers > 0;
|
|
346
337
|
const hasProofLoop = events > 0 || episodes > 0 || scorecards > 0 || localReceipts > 0;
|
|
347
338
|
const hasTeam = Number(team.total || 0) > 0;
|
|
@@ -370,7 +361,7 @@ function loadBusinessCollaboration(root, deps, team = {}) {
|
|
|
370
361
|
} : null,
|
|
371
362
|
onboarding: { packs: ingestPacks, starter_briefs: starterBriefs, first_loops: firstLoops, one_pagers: onePagers, reports },
|
|
372
363
|
proof: { events, episodes, scorecards, receipts: localReceipts },
|
|
373
|
-
computers,
|
|
364
|
+
computers: computerDirs,
|
|
374
365
|
team_members: Number(team.total || 0),
|
|
375
366
|
active_goal_members: Number(team.active_goal_members || 0),
|
|
376
367
|
share_ready: missing.length === 0,
|
|
@@ -418,15 +409,47 @@ function taskRef(task) {
|
|
|
418
409
|
return task ? (task.display_id || task.legacy_ref || task.id || '-') : '-';
|
|
419
410
|
}
|
|
420
411
|
|
|
421
|
-
function
|
|
412
|
+
function taskOwnerMatchesAgent(task, agent) {
|
|
413
|
+
const actor = String(agent?.agent || '').toLowerCase();
|
|
414
|
+
if (!actor) return false;
|
|
415
|
+
return [
|
|
416
|
+
task.assigned_to,
|
|
417
|
+
task.claimed_by,
|
|
418
|
+
task.metadata?.assigned_to,
|
|
419
|
+
task.metadata?.claimed_by,
|
|
420
|
+
task.metadata?.owner,
|
|
421
|
+
]
|
|
422
|
+
.filter(Boolean)
|
|
423
|
+
.some(owner => String(owner).toLowerCase() === actor);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function taskForCwd(tasks, cwd, workspaceRoot = cwd, agent = null) {
|
|
422
427
|
if (!cwd && !workspaceRoot) return null;
|
|
423
428
|
const matchesWorkspace = task => !task.workspace_root || task.workspace_root === cwd || task.workspace_root === workspaceRoot;
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
429
|
+
const candidates = tasks.filter(matchesWorkspace);
|
|
430
|
+
const firstByStatus = status => {
|
|
431
|
+
const statusMatches = candidates.filter(task => task.status === status);
|
|
432
|
+
return statusMatches.find(task => taskOwnerMatchesAgent(task, agent)) || statusMatches[0] || null;
|
|
433
|
+
};
|
|
434
|
+
return firstByStatus('claimed')
|
|
435
|
+
|| firstByStatus('open')
|
|
436
|
+
|| firstByStatus('review')
|
|
427
437
|
|| null;
|
|
428
438
|
}
|
|
429
439
|
|
|
440
|
+
function taskBindingForProjection(task) {
|
|
441
|
+
if (!task) return { task_source: null, task_scope: null };
|
|
442
|
+
return {
|
|
443
|
+
task_source: 'repo_task_projection',
|
|
444
|
+
task_scope: 'repo',
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function taskSourceLabel(sources = []) {
|
|
449
|
+
if (sources.includes('repo_task_projection')) return 'repo projection; verify ownership';
|
|
450
|
+
return sources.filter(Boolean).join(', ');
|
|
451
|
+
}
|
|
452
|
+
|
|
430
453
|
function ownerForTask(task) {
|
|
431
454
|
if (!task) return '-';
|
|
432
455
|
return task.assigned_to || task.claimed_by || task.metadata?.assigned_to || '-';
|
|
@@ -449,33 +472,21 @@ function taskSessionAction(agent, task, taskWorkspaceRoot) {
|
|
|
449
472
|
: 'atris task reviews --limit 5';
|
|
450
473
|
if (task.status === 'review') {
|
|
451
474
|
if (task.metadata?.agent_certified) {
|
|
452
|
-
return `handoff complete for ${ref};
|
|
475
|
+
return `handoff complete for ${ref}; claim fresh work as ${actor} or close pid ${pid} only with operator approval`;
|
|
453
476
|
}
|
|
454
477
|
return `review or hand off ${ref}: ${reviewCommand}`;
|
|
455
478
|
}
|
|
456
|
-
if (ownerActionRequired(task)) return `owner-gated ${ref}; close pid ${pid}
|
|
479
|
+
if (ownerActionRequired(task)) return `owner-gated ${ref}; wait for owner action, do not start duplicate work; close pid ${pid} only with operator approval`;
|
|
457
480
|
return null;
|
|
458
481
|
}
|
|
459
482
|
|
|
460
|
-
function summarize(tasks, missions, worktrees, agents) {
|
|
461
|
-
const count = (rows, pred) => rows.filter(pred).length;
|
|
462
|
-
return {
|
|
463
|
-
agents: { total: agents.length, active: count(agents, a => a.status === 'active'), stopped: count(agents, a => a.status !== 'active') },
|
|
464
|
-
tasks: {
|
|
465
|
-
open: count(tasks, t => t.status === 'open'),
|
|
466
|
-
claimed: count(tasks, t => t.status === 'claimed'),
|
|
467
|
-
review: count(tasks, t => t.status === 'review'),
|
|
468
|
-
certifiedReview: count(tasks, t => t.status === 'review' && t.metadata && t.metadata.agent_certified),
|
|
469
|
-
},
|
|
470
|
-
missions: { running: count(missions, m => m.status === 'running'), stale: count(missions, m => m.stale) },
|
|
471
|
-
worktrees: { total: worktrees.length, dirty: count(worktrees, w => Number(w.dirty) > 0) },
|
|
472
|
-
};
|
|
473
|
-
}
|
|
474
|
-
|
|
475
483
|
function ownerActionRequired(task) {
|
|
476
484
|
const metadata = task?.metadata || {};
|
|
477
485
|
if (metadata.owner_action_required === true || metadata.agent_executable === false) return true;
|
|
478
486
|
if (String(metadata.agent_executable || '').toLowerCase() === 'false') return true;
|
|
487
|
+
const recentMessages = Array.isArray(task?.messages)
|
|
488
|
+
? task.messages.slice(-8).map(message => message?.content || message?.payload?.content || '')
|
|
489
|
+
: [];
|
|
479
490
|
const text = [
|
|
480
491
|
task?.title,
|
|
481
492
|
task?.tag,
|
|
@@ -485,12 +496,32 @@ function ownerActionRequired(task) {
|
|
|
485
496
|
metadata.latest_agent_proof,
|
|
486
497
|
metadata.latest_agent_lesson,
|
|
487
498
|
task?.routing_text,
|
|
499
|
+
...recentMessages,
|
|
488
500
|
].filter(Boolean).join(' ').toLowerCase();
|
|
489
501
|
if (text.includes('owner_action_required') || text.includes('agent_executable=false')) return true;
|
|
490
502
|
if (text.includes('owner-only') || text.includes('owner only') || text.includes('not agent-executable')) return true;
|
|
503
|
+
const productionGateEligible = !['radar', 'task-plane', 'review'].includes(String(task?.tag || '').toLowerCase());
|
|
504
|
+
if (productionGateEligible && (text.includes('human/production gated') || text.includes('production execution proof is still missing'))) return true;
|
|
505
|
+
if (productionGateEligible && text.includes('remaining blocker') && text.includes('deploy receipt') && text.includes('live canary')) return true;
|
|
506
|
+
if (productionGateEligible && text.includes('human approval required') && (text.includes('production deploy') || text.includes('feedback mutation'))) return true;
|
|
491
507
|
return text.includes('owner') && text.includes('billing') && (text.includes('spending limit') || text.includes('failed payments'));
|
|
492
508
|
}
|
|
493
509
|
|
|
510
|
+
function summarize(tasks, missions, worktrees, agents) {
|
|
511
|
+
const count = (rows, pred) => rows.filter(pred).length;
|
|
512
|
+
return {
|
|
513
|
+
agents: { total: agents.length, active: count(agents, a => a.status === 'active'), stopped: count(agents, a => a.status !== 'active') },
|
|
514
|
+
tasks: {
|
|
515
|
+
open: count(tasks, t => t.status === 'open'),
|
|
516
|
+
claimed: count(tasks, t => t.status === 'claimed'),
|
|
517
|
+
review: count(tasks, t => t.status === 'review'),
|
|
518
|
+
certifiedReview: count(tasks, t => t.status === 'review' && t.metadata && t.metadata.agent_certified),
|
|
519
|
+
},
|
|
520
|
+
missions: { running: count(missions, m => m.status === 'running'), stale: count(missions, m => m.stale) },
|
|
521
|
+
worktrees: { total: worktrees.length, dirty: count(worktrees, w => Number(w.dirty) > 0) },
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
|
|
494
525
|
function nextAction(tasks, missions, worktrees, agents, os = {}) {
|
|
495
526
|
const activeTasks = tasks.filter(t => t.status === 'claimed' || t.status === 'open');
|
|
496
527
|
const activeTask = activeTasks.find(t => !ownerActionRequired(t));
|
|
@@ -529,14 +560,16 @@ function collectRadar(options = {}) {
|
|
|
529
560
|
const agents = collectAgents(deps).map(agent => {
|
|
530
561
|
const taskWorkspaceRoot = findTaskWorkspaceRoot(agent.cwd, deps);
|
|
531
562
|
const agentTasks = taskWorkspaceRoot ? loadTasksCached(taskWorkspaceRoot, deps, taskCache) : [];
|
|
532
|
-
const task = taskForCwd(agentTasks, agent.cwd, taskWorkspaceRoot);
|
|
563
|
+
const task = taskForCwd(agentTasks, agent.cwd, taskWorkspaceRoot, agent);
|
|
533
564
|
const taskReason = task ? taskSessionReason(task) : untaskedReason(agent, taskWorkspaceRoot, agentTasks);
|
|
565
|
+
const taskBinding = taskBindingForProjection(task);
|
|
534
566
|
return {
|
|
535
567
|
...agent,
|
|
536
568
|
task: taskRef(task),
|
|
537
569
|
task_status: task?.status || null,
|
|
538
570
|
owner: ownerForTask(task),
|
|
539
571
|
task_workspace: taskWorkspaceRoot ? repoLabel(taskWorkspaceRoot) : null,
|
|
572
|
+
...taskBinding,
|
|
540
573
|
task_reason: taskReason,
|
|
541
574
|
task_action: task ? taskSessionAction(agent, task, taskWorkspaceRoot) : untaskedAction(agent, taskWorkspaceRoot, agentTasks),
|
|
542
575
|
};
|
|
@@ -646,19 +679,73 @@ function sortedAgents(agents = []) {
|
|
|
646
679
|
});
|
|
647
680
|
}
|
|
648
681
|
|
|
682
|
+
function executableAgentLanes(agents = []) {
|
|
683
|
+
return sortedAgents(agents).filter(agent => {
|
|
684
|
+
if (agent.status !== 'active') return false;
|
|
685
|
+
if (!agent.task || agent.task === '-') return false;
|
|
686
|
+
if (!['claimed', 'open'].includes(agent.task_status)) return false;
|
|
687
|
+
return !agent.task_reason;
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
function formatExecutableLane(agent) {
|
|
692
|
+
const repo = agent.repo || agent.cwd || 'unknown repo';
|
|
693
|
+
const actor = agent.agent || 'agent';
|
|
694
|
+
return `continue ${agent.task} in ${repo} as ${actor}`;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function certifiedReviewLanes(agents = []) {
|
|
698
|
+
return sortedAgents(agents).filter(agent => {
|
|
699
|
+
if (agent.status !== 'active') return false;
|
|
700
|
+
if (!agent.task || agent.task === '-') return false;
|
|
701
|
+
if (agent.task_status !== 'review') return false;
|
|
702
|
+
return agent.task_reason === 'certified review';
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function formatReviewCheckpoint(agent) {
|
|
707
|
+
const repo = agent.repo || agent.cwd || 'unknown repo';
|
|
708
|
+
return `accept/revise ${agent.task} in ${repo}`;
|
|
709
|
+
}
|
|
710
|
+
|
|
649
711
|
function agentProcessNextAction(agents = [], fallback = 'no obvious process action') {
|
|
650
712
|
const stopped = agents.filter(agent => agent.status !== 'active').length;
|
|
651
713
|
if (stopped > 0) return `inspect ${stopped} stopped agent session${stopped === 1 ? '' : 's'}`;
|
|
652
714
|
const ownerGated = agents.filter(agent => agent.task_reason === 'owner action required');
|
|
653
715
|
if (ownerGated.length > 0) {
|
|
654
716
|
const tasks = [...new Set(ownerGated.map(agent => agent.task).filter(Boolean))].slice(0, 3).join(', ');
|
|
655
|
-
|
|
717
|
+
const projectionBound = ownerGated.some(agent => agent.task_source === 'repo_task_projection');
|
|
718
|
+
const executable = executableAgentLanes(agents)[0];
|
|
719
|
+
const reviewCheckpoint = executable ? null : certifiedReviewLanes(agents)[0];
|
|
720
|
+
const ownerGateAction = executable
|
|
721
|
+
? `wait for owner action; executable lane: ${formatExecutableLane(executable)}; avoid duplicate work, close only with operator approval`
|
|
722
|
+
: reviewCheckpoint
|
|
723
|
+
? `wait for owner action; review checkpoint: ${formatReviewCheckpoint(reviewCheckpoint)}; avoid duplicate work, close only with operator approval`
|
|
724
|
+
: 'wait for owner action, avoid duplicate work, close only with operator approval';
|
|
725
|
+
if (projectionBound) {
|
|
726
|
+
return `owner-gated repo projection covers ${ownerGated.length} session${ownerGated.length === 1 ? '' : 's'}${tasks ? ` on ${tasks}` : ''}; verify ownership, ${ownerGateAction}`;
|
|
727
|
+
}
|
|
728
|
+
return `owner gate blocks ${ownerGated.length} session${ownerGated.length === 1 ? '' : 's'}${tasks ? ` on ${tasks}` : ''}; ${ownerGateAction}`;
|
|
656
729
|
}
|
|
657
730
|
const taskLoad = summarizeTaskLoad(agents);
|
|
731
|
+
const activePileup = taskLoad.find(row => row.sessions > 1 && !row.status.split(/,\s*/).includes('review'));
|
|
732
|
+
if (activePileup) {
|
|
733
|
+
const source = taskSourceLabel(activePileup.task_sources);
|
|
734
|
+
const sourceHint = source ? `; ${source}` : '';
|
|
735
|
+
return `inspect ${activePileup.sessions} sessions on ${activePileup.task} (${activePileup.cpu.toFixed(1)}% CPU${sourceHint})`;
|
|
736
|
+
}
|
|
658
737
|
const reviewBound = taskLoad.find(row => row.status.split(/,\s*/).includes('review'));
|
|
659
|
-
if (reviewBound)
|
|
738
|
+
if (reviewBound) {
|
|
739
|
+
const source = taskSourceLabel(reviewBound.task_sources);
|
|
740
|
+
const sourceHint = source ? `; ${source}` : '';
|
|
741
|
+
return `close or hand off ${reviewBound.sessions} session${reviewBound.sessions === 1 ? '' : 's'} still bound to review task ${reviewBound.task}${sourceHint}`;
|
|
742
|
+
}
|
|
660
743
|
const pileup = taskLoad.find(row => row.sessions > 1);
|
|
661
|
-
if (pileup)
|
|
744
|
+
if (pileup) {
|
|
745
|
+
const source = taskSourceLabel(pileup.task_sources);
|
|
746
|
+
const sourceHint = source ? `; ${source}` : '';
|
|
747
|
+
return `inspect ${pileup.sessions} sessions on ${pileup.task} (${pileup.cpu.toFixed(1)}% CPU${sourceHint})`;
|
|
748
|
+
}
|
|
662
749
|
const untasked = agents.filter(agent => !agent.task || agent.task === '-').length;
|
|
663
750
|
if (untasked > 0) {
|
|
664
751
|
const reasons = summarizeUntaskedReasons(agents);
|
|
@@ -696,6 +783,8 @@ function summarizeTaskLoad(agents = []) {
|
|
|
696
783
|
statuses: new Set(),
|
|
697
784
|
owners: new Set(),
|
|
698
785
|
repos: new Set(),
|
|
786
|
+
task_sources: new Set(),
|
|
787
|
+
task_scopes: new Set(),
|
|
699
788
|
pids: [],
|
|
700
789
|
});
|
|
701
790
|
}
|
|
@@ -707,6 +796,8 @@ function summarizeTaskLoad(agents = []) {
|
|
|
707
796
|
if (agent.task_status) row.statuses.add(agent.task_status);
|
|
708
797
|
if (agent.owner && agent.owner !== '-') row.owners.add(agent.owner);
|
|
709
798
|
if (agent.repo) row.repos.add(agent.repo);
|
|
799
|
+
if (agent.task_source) row.task_sources.add(agent.task_source);
|
|
800
|
+
if (agent.task_scope) row.task_scopes.add(agent.task_scope);
|
|
710
801
|
if (agent.pid) row.pids.push(agent.pid);
|
|
711
802
|
}
|
|
712
803
|
return [...byTask.values()]
|
|
@@ -721,6 +812,8 @@ function summarizeTaskLoad(agents = []) {
|
|
|
721
812
|
status: statuses.join(', ') || '-',
|
|
722
813
|
owners: [...row.owners].sort(),
|
|
723
814
|
repos: [...row.repos].sort(),
|
|
815
|
+
task_sources: [...row.task_sources].sort(),
|
|
816
|
+
task_scopes: [...row.task_scopes].sort(),
|
|
724
817
|
pids: row.pids.sort((a, b) => number(a) - number(b)),
|
|
725
818
|
attention: row.sessions > 1 || statuses.includes('review'),
|
|
726
819
|
};
|
|
@@ -753,6 +846,26 @@ function agentTopPayload(data) {
|
|
|
753
846
|
};
|
|
754
847
|
}
|
|
755
848
|
|
|
849
|
+
function representativeAgentsByTask(agents = [], limit = 8) {
|
|
850
|
+
const selected = [];
|
|
851
|
+
const selectedRows = new Set();
|
|
852
|
+
const seenTasks = new Set();
|
|
853
|
+
for (const agent of agents) {
|
|
854
|
+
const task = agent.task || '-';
|
|
855
|
+
if (seenTasks.has(task)) continue;
|
|
856
|
+
seenTasks.add(task);
|
|
857
|
+
selected.push(agent);
|
|
858
|
+
selectedRows.add(agent);
|
|
859
|
+
if (selected.length >= limit) return selected;
|
|
860
|
+
}
|
|
861
|
+
for (const agent of agents) {
|
|
862
|
+
if (selected.length >= limit) break;
|
|
863
|
+
if (selectedRows.has(agent)) continue;
|
|
864
|
+
selected.push(agent);
|
|
865
|
+
}
|
|
866
|
+
return selected;
|
|
867
|
+
}
|
|
868
|
+
|
|
756
869
|
function renderAgentTop(data) {
|
|
757
870
|
const payload = agentTopPayload(data);
|
|
758
871
|
const lines = [];
|
|
@@ -760,6 +873,9 @@ function renderAgentTop(data) {
|
|
|
760
873
|
lines.push('');
|
|
761
874
|
lines.push(`Agents: ${payload.summary.active}/${payload.summary.total} active; ${payload.summary.untasked} untasked; CPU ${payload.summary.cpu.toFixed(1)}%; MEM ${payload.summary.mem.toFixed(1)}%`);
|
|
762
875
|
lines.push(`Next: ${payload.next_action}`);
|
|
876
|
+
if (payload.agents.some(agent => agent.task_source === 'repo_task_projection')) {
|
|
877
|
+
lines.push('Task binding: repo projection; verify ownership before assuming every session owns the displayed task.');
|
|
878
|
+
}
|
|
763
879
|
lines.push('');
|
|
764
880
|
lines.push(`${truncate('PID', 7)} ${truncate('AGENT', 8)} ${truncate('CPU', 6)} ${truncate('MEM', 6)} ${truncate('REPO', 24)} ${truncate('BRANCH', 16)} ${truncate('TASK', 10)} ${truncate('STATE', 8)}`);
|
|
765
881
|
for (const agent of payload.agents.slice(0, 32)) {
|
|
@@ -774,19 +890,26 @@ function renderAgentTop(data) {
|
|
|
774
890
|
lines.push(`- ${agent.pid} ${agent.repo || agent.cwd || '-'}: ${agent.task_reason || 'unmapped'} -> ${agent.task_action || 'inspect session'}`);
|
|
775
891
|
}
|
|
776
892
|
}
|
|
777
|
-
const
|
|
893
|
+
const ownerGatedAll = payload.agents.filter(row => row.task_reason === 'owner action required');
|
|
894
|
+
const ownerGatedAgents = representativeAgentsByTask(ownerGatedAll, 8);
|
|
778
895
|
if (ownerGatedAgents.length) {
|
|
779
896
|
lines.push('');
|
|
780
|
-
|
|
897
|
+
const shown = ownerGatedAll.length > ownerGatedAgents.length ? `; showing ${ownerGatedAgents.length}` : '';
|
|
898
|
+
const projectionBound = ownerGatedAll.some(agent => agent.task_source === 'repo_task_projection');
|
|
899
|
+
const label = projectionBound ? 'Owner-gated projection' : 'Owner-gated';
|
|
900
|
+
const actionText = projectionBound ? 'verify ownership and wait on human/owner action' : 'waiting on human/owner action';
|
|
901
|
+
lines.push(`${label}: ${ownerGatedAll.length} session${ownerGatedAll.length === 1 ? '' : 's'} ${actionText}${shown}; do not start duplicate work.`);
|
|
781
902
|
for (const agent of ownerGatedAgents) {
|
|
782
903
|
lines.push(`- ${agent.pid} ${agent.repo || agent.cwd || '-'} ${agent.task}: ${agent.task_action || 'wait for owner action'}`);
|
|
783
904
|
}
|
|
784
905
|
}
|
|
785
|
-
const
|
|
786
|
-
|
|
906
|
+
const reviewAll = payload.agents.filter(row => row.task_reason === 'certified review' || row.task_reason === 'review task');
|
|
907
|
+
const reviewAgents = reviewAll.slice(0, 8);
|
|
908
|
+
if (reviewAgents.length) {
|
|
787
909
|
lines.push('');
|
|
788
|
-
|
|
789
|
-
|
|
910
|
+
const shown = reviewAll.length > reviewAgents.length ? `; showing ${reviewAgents.length}` : '';
|
|
911
|
+
lines.push(`Review-bound: ${reviewAll.length} session${reviewAll.length === 1 ? '' : 's'} should hand off or claim fresh work${shown}.`);
|
|
912
|
+
for (const agent of reviewAgents) {
|
|
790
913
|
lines.push(`- ${agent.pid} ${agent.repo || agent.cwd || '-'} ${agent.task}: ${agent.task_reason || 'review'} -> ${agent.task_action || 'close or hand off session'}`);
|
|
791
914
|
}
|
|
792
915
|
}
|
|
@@ -796,7 +919,9 @@ function renderAgentTop(data) {
|
|
|
796
919
|
lines.push(`Task load: ${payload.summary.task_pileups} pileup${payload.summary.task_pileups === 1 ? '' : 's'}, ${payload.summary.review_bound_tasks} review-bound task${payload.summary.review_bound_tasks === 1 ? '' : 's'}.`);
|
|
797
920
|
for (const row of taskLoadRows) {
|
|
798
921
|
const repoText = row.repos.slice(0, 3).join(', ') || '-';
|
|
799
|
-
|
|
922
|
+
const source = taskSourceLabel(row.task_sources);
|
|
923
|
+
const sourceText = source ? `, ${source}` : '';
|
|
924
|
+
lines.push(`- ${row.task}: ${row.sessions} sessions, ${row.cpu.toFixed(1)}% CPU, ${row.status}, ${repoText}${sourceText}`);
|
|
800
925
|
}
|
|
801
926
|
}
|
|
802
927
|
return lines.join('\n');
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
5
|
+
const DEFAULT_DAYS = 7;
|
|
6
|
+
|
|
7
|
+
function loadTaskDb() {
|
|
8
|
+
try {
|
|
9
|
+
return require('../lib/task-db');
|
|
10
|
+
} catch (e) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function readProjection(root) {
|
|
16
|
+
const projectionPath = path.join(root, '.atris', 'state', 'tasks.projection.json');
|
|
17
|
+
if (!fs.existsSync(projectionPath)) return null;
|
|
18
|
+
try {
|
|
19
|
+
const parsed = JSON.parse(fs.readFileSync(projectionPath, 'utf8'));
|
|
20
|
+
return Array.isArray(parsed.tasks) ? parsed.tasks : null;
|
|
21
|
+
} catch (e) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function loadTasks(root) {
|
|
27
|
+
const taskDb = loadTaskDb();
|
|
28
|
+
if (taskDb) {
|
|
29
|
+
try {
|
|
30
|
+
const db = taskDb.open();
|
|
31
|
+
const ws = taskDb.workspaceRoot(root);
|
|
32
|
+
const rows = taskDb.listTasks(db, { workspaceRoot: ws });
|
|
33
|
+
const refs = taskDb.taskDisplayRefMap(rows);
|
|
34
|
+
return rows.map(row => ({ ...row, display_id: refs.get(row.id) || row.id.slice(-6) }));
|
|
35
|
+
} catch (e) {
|
|
36
|
+
// fall through to projection
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return readProjection(root);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function taskProof(task) {
|
|
43
|
+
const meta = task.metadata || {};
|
|
44
|
+
const proof = String(meta.latest_agent_proof || '').trim();
|
|
45
|
+
if (proof) return proof;
|
|
46
|
+
if (meta.agent_certified === true) return 'verified by repeated agent review';
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function shortProof(proof, width = 70) {
|
|
51
|
+
if (!proof) return null;
|
|
52
|
+
const flat = proof.replace(/\s+/g, ' ').trim();
|
|
53
|
+
return flat.length <= width ? flat : `${flat.slice(0, width - 1)}…`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function shortTitle(title, width = 64) {
|
|
57
|
+
const flat = String(title || '').replace(/\s+/g, ' ').trim();
|
|
58
|
+
return flat.length <= width ? flat : `${flat.slice(0, width - 1)}…`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function buildRecapData(root = process.cwd(), { days = DEFAULT_DAYS } = {}) {
|
|
62
|
+
const windowDays = Number.isFinite(Number(days)) && Number(days) > 0 ? Number(days) : DEFAULT_DAYS;
|
|
63
|
+
const tasks = loadTasks(root);
|
|
64
|
+
if (!tasks || tasks.length === 0) return { empty: true, days: windowDays, workspace: path.basename(root) };
|
|
65
|
+
|
|
66
|
+
const cutoff = Date.now() - windowDays * DAY_MS;
|
|
67
|
+
const pick = t => ({
|
|
68
|
+
id: t.display_id || t.id,
|
|
69
|
+
title: String(t.title || '').trim(),
|
|
70
|
+
proof: taskProof(t),
|
|
71
|
+
owner: t.claimed_by || (t.metadata && t.metadata.assigned_to) || null,
|
|
72
|
+
done_at: t.done_at || null,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const shipped = tasks
|
|
76
|
+
.filter(t => t.status === 'done' && Number(t.done_at || 0) >= cutoff)
|
|
77
|
+
.sort((a, b) => Number(b.done_at || 0) - Number(a.done_at || 0))
|
|
78
|
+
.map(pick);
|
|
79
|
+
const waiting = tasks
|
|
80
|
+
.filter(t => t.status === 'review')
|
|
81
|
+
.sort((a, b) => Number(b.updated_at || 0) - Number(a.updated_at || 0))
|
|
82
|
+
.map(pick);
|
|
83
|
+
const inProgress = tasks
|
|
84
|
+
.filter(t => t.status === 'open' || t.status === 'claimed')
|
|
85
|
+
.map(pick);
|
|
86
|
+
|
|
87
|
+
const withProof = [...shipped, ...waiting].filter(t => t.proof).length;
|
|
88
|
+
return {
|
|
89
|
+
empty: false,
|
|
90
|
+
days: windowDays,
|
|
91
|
+
workspace: path.basename(root),
|
|
92
|
+
shipped,
|
|
93
|
+
waiting,
|
|
94
|
+
inProgress,
|
|
95
|
+
proof_attached: withProof,
|
|
96
|
+
proof_total: shipped.length + waiting.length,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function renderRecap(data) {
|
|
101
|
+
if (data.empty) {
|
|
102
|
+
return [
|
|
103
|
+
`RECAP — ${data.workspace}`,
|
|
104
|
+
'',
|
|
105
|
+
'No task history yet.',
|
|
106
|
+
'Run "atris init", then let an agent work — every finished task lands here with proof.',
|
|
107
|
+
].join('\n');
|
|
108
|
+
}
|
|
109
|
+
const lines = [];
|
|
110
|
+
lines.push(`RECAP — ${data.workspace} — last ${data.days} day${data.days === 1 ? '' : 's'}`);
|
|
111
|
+
lines.push('');
|
|
112
|
+
const headline = [];
|
|
113
|
+
if (data.shipped.length) headline.push(`${data.shipped.length} change${data.shipped.length === 1 ? '' : 's'} shipped`);
|
|
114
|
+
if (data.waiting.length) headline.push(`${data.waiting.length} finished and waiting for your sign-off`);
|
|
115
|
+
if (data.inProgress.length) headline.push(`${data.inProgress.length} in progress`);
|
|
116
|
+
lines.push(headline.length ? `Your AI team: ${headline.join(' · ')}.` : 'Quiet window — no movement in this period.');
|
|
117
|
+
lines.push('Every finished line below carries proof: the commands run and their results.');
|
|
118
|
+
|
|
119
|
+
if (data.shipped.length) {
|
|
120
|
+
lines.push('');
|
|
121
|
+
lines.push(`SHIPPED (accepted by a human) — ${data.shipped.length}`);
|
|
122
|
+
for (const t of data.shipped.slice(0, 12)) {
|
|
123
|
+
lines.push(` ${t.id} ${shortTitle(t.title)}`);
|
|
124
|
+
if (t.proof) lines.push(` proof: ${shortProof(t.proof)}`);
|
|
125
|
+
}
|
|
126
|
+
if (data.shipped.length > 12) lines.push(` … and ${data.shipped.length - 12} more, all with proof on file`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (data.waiting.length) {
|
|
130
|
+
lines.push('');
|
|
131
|
+
lines.push(`FINISHED, WAITING FOR YOUR SIGN-OFF — ${data.waiting.length}`);
|
|
132
|
+
for (const t of data.waiting.slice(0, 10)) {
|
|
133
|
+
lines.push(` ${t.id} ${shortTitle(t.title)}`);
|
|
134
|
+
}
|
|
135
|
+
if (data.waiting.length > 10) lines.push(` … and ${data.waiting.length - 10} more`);
|
|
136
|
+
lines.push(' approve or send back: atris task reviews');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (data.inProgress.length) {
|
|
140
|
+
lines.push('');
|
|
141
|
+
lines.push(`IN PROGRESS — ${data.inProgress.length}`);
|
|
142
|
+
for (const t of data.inProgress) {
|
|
143
|
+
lines.push(` ${t.id} ${shortTitle(t.title)}${t.owner ? ` @${t.owner}` : ''}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
lines.push('');
|
|
148
|
+
lines.push(`Proof attached: ${data.proof_attached}/${data.proof_total} finished items.`);
|
|
149
|
+
lines.push('Paste-ready summary for Slack or email: atris recap --share');
|
|
150
|
+
return lines.join('\n');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function renderShare(data) {
|
|
154
|
+
if (data.empty) return `Nothing to share yet on ${data.workspace} — no finished tasks on record.`;
|
|
155
|
+
const lines = [];
|
|
156
|
+
lines.push(`What the AI team did on ${data.workspace} in the last ${data.days} day${data.days === 1 ? '' : 's'}:`);
|
|
157
|
+
lines.push('');
|
|
158
|
+
if (data.shipped.length) lines.push(`- ${data.shipped.length} change${data.shipped.length === 1 ? '' : 's'} shipped, each verified before a human accepted it`);
|
|
159
|
+
if (data.waiting.length) lines.push(`- ${data.waiting.length} more finished with proof attached, waiting for human sign-off`);
|
|
160
|
+
if (data.inProgress.length) lines.push(`- ${data.inProgress.length} task${data.inProgress.length === 1 ? '' : 's'} in progress`);
|
|
161
|
+
const highlights = [...data.shipped, ...data.waiting].filter(t => t.proof).slice(0, 5);
|
|
162
|
+
if (highlights.length) {
|
|
163
|
+
lines.push('');
|
|
164
|
+
lines.push('Highlights:');
|
|
165
|
+
for (const t of highlights) {
|
|
166
|
+
lines.push(`- ${shortTitle(t.title, 80)} (proof: ${shortProof(t.proof, 60)})`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
lines.push('');
|
|
170
|
+
lines.push('Every item above is backed by a receipt — the exact commands run and their results — not a status update someone typed.');
|
|
171
|
+
return lines.join('\n');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function printRecapHelp() {
|
|
175
|
+
console.log(`
|
|
176
|
+
atris recap - what your AI team actually did, in plain English
|
|
177
|
+
|
|
178
|
+
atris recap Last 7 days: shipped, waiting on you, in progress
|
|
179
|
+
atris recap --days 30 Widen the window
|
|
180
|
+
atris recap --share Paste-ready summary for Slack, email, or a customer
|
|
181
|
+
atris recap --json Structured output for agents and dashboards
|
|
182
|
+
|
|
183
|
+
Reads the workspace task records and their proof. No jargon, no guesses:
|
|
184
|
+
if it is listed as finished, the receipt is on file.
|
|
185
|
+
`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function recapAtris(args = []) {
|
|
189
|
+
if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
|
|
190
|
+
printRecapHelp();
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const daysIdx = args.indexOf('--days');
|
|
194
|
+
const days = daysIdx !== -1 ? Number(args[daysIdx + 1]) : DEFAULT_DAYS;
|
|
195
|
+
const data = buildRecapData(process.cwd(), { days });
|
|
196
|
+
if (args.includes('--json')) {
|
|
197
|
+
console.log(JSON.stringify(data, null, 2));
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
console.log(args.includes('--share') ? renderShare(data) : renderRecap(data));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
module.exports = { recapAtris, buildRecapData, renderRecap, renderShare };
|
package/commands/skill.js
CHANGED
|
@@ -183,8 +183,12 @@ function runAuditChecks(skill) {
|
|
|
183
183
|
});
|
|
184
184
|
|
|
185
185
|
// 7. no XML tags in content (skip placeholders like <name>, <keyword>, code blocks)
|
|
186
|
-
const
|
|
187
|
-
|
|
186
|
+
const proseContent = skill.content
|
|
187
|
+
.replace(/```[\s\S]*?```/g, '') // fenced code blocks
|
|
188
|
+
.replace(/`[^`\n]*`/g, ''); // inline code spans
|
|
189
|
+
const xmlMatches = proseContent.match(/<[a-zA-Z][^>]*>/g) || [];
|
|
190
|
+
// Single-letter tags like <X>/<N> are prose placeholders, not real XML.
|
|
191
|
+
const placeholders = /^<(name|keyword|placeholder|value|type|path|file|dir|id|url|tag|description|your-|user-|project-|skill-|[a-zA-Z]>)/i;
|
|
188
192
|
const realXml = xmlMatches.filter(t =>
|
|
189
193
|
!t.startsWith('<!--') && !t.startsWith('<!') && !placeholders.test(t)
|
|
190
194
|
);
|
package/commands/soul.js
CHANGED
|
@@ -26,10 +26,6 @@ function readFile(filePath) {
|
|
|
26
26
|
try { return fs.readFileSync(filePath, 'utf8'); } catch { return null; }
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
function readJson(filePath) {
|
|
30
|
-
try { return JSON.parse(fs.readFileSync(filePath, 'utf8')); } catch { return null; }
|
|
31
|
-
}
|
|
32
|
-
|
|
33
29
|
function countFiles(dir) {
|
|
34
30
|
try { return fs.readdirSync(dir, { recursive: true }).filter(f => !f.startsWith('.')).length; } catch { return 0; }
|
|
35
31
|
}
|