atris 3.53.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/x-search/SKILL.md +24 -22
- package/atris/skills/youtube/SKILL.md +82 -33
- package/atris/team/_template/SOUL.md +40 -0
- package/atris/team/validator/MEMBER.md +1 -2
- package/bin/atris +21 -0
- package/bin/atris.js +563 -373
- 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/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 +287 -50
- package/commands/now.js +15 -3
- 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 +31 -7
- 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 -25
- package/lib/account-bound.js +47 -0
- package/lib/auto-accept-certified.js +8 -1
- 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/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-root.js +4 -2
- package/lib/noninteractive.js +88 -0
- package/lib/policy-lessons.js +4 -1
- package/lib/revision-metric.js +279 -0
- package/lib/scratch-root.js +48 -0
- package/lib/skill-eval-gate.js +249 -0
- package/lib/task-db.js +4 -0
- package/lib/task-proof.js +69 -8
- 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 +3 -2
- package/scripts/det/checklist-score.js +191 -0
- package/scripts/det/ytsearch +31 -0
- package/utils/update-check.js +15 -0
package/commands/radar.js
CHANGED
|
@@ -4,6 +4,7 @@ const fs = require('fs');
|
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const { execFileSync } = require('child_process');
|
|
7
|
+
const { parseScopeFlag, pathUnderRoot } = require('../lib/cli-scope');
|
|
7
8
|
|
|
8
9
|
function safeJson(text, fallback = null) {
|
|
9
10
|
try { return JSON.parse(text); } catch { return fallback; }
|
|
@@ -795,6 +796,7 @@ function nextAction(tasks, missions, worktrees, agents, os = {}) {
|
|
|
795
796
|
|
|
796
797
|
function collectRadar(options = {}) {
|
|
797
798
|
const root = options.root || process.cwd();
|
|
799
|
+
const scopeKind = options.scope === 'global' ? 'global' : 'workspace';
|
|
798
800
|
const deps = {
|
|
799
801
|
execFile: options.execFileSync || execFileSync,
|
|
800
802
|
readFile: options.readFileSync || fs.readFileSync,
|
|
@@ -810,14 +812,18 @@ function collectRadar(options = {}) {
|
|
|
810
812
|
const identityCache = new Map();
|
|
811
813
|
const missions = loadMissions(root, deps, nowMs);
|
|
812
814
|
identityCache.set(root, { missions, missionLocks: loadMissionLocks(path.join(root, '.atris', 'state'), deps), memberLocks: loadMemberLoopLocks(path.join(root, '.atris', 'state'), deps) });
|
|
813
|
-
|
|
815
|
+
let worktrees = loadWorktrees(root, deps);
|
|
816
|
+
if (scopeKind !== 'global') {
|
|
817
|
+
// Refuse other-repo dirty worktrees in the default workspace view.
|
|
818
|
+
worktrees = worktrees.filter((wt) => pathUnderRoot(wt.path, root));
|
|
819
|
+
}
|
|
814
820
|
const branchIdentityCache = new Map();
|
|
815
821
|
const branchIdentityFor = (cwd) => {
|
|
816
822
|
if (!cwd) return null;
|
|
817
823
|
if (!branchIdentityCache.has(cwd)) branchIdentityCache.set(cwd, branchIdentityAtCwd(cwd, deps.execFile));
|
|
818
824
|
return branchIdentityCache.get(cwd);
|
|
819
825
|
};
|
|
820
|
-
|
|
826
|
+
let agents = collectAgents(deps).map(agent => {
|
|
821
827
|
const taskWorkspaceRoot = findTaskWorkspaceRoot(agent.cwd, deps);
|
|
822
828
|
const agentTasks = taskWorkspaceRoot ? loadTasksCached(taskWorkspaceRoot, deps, taskCache) : [];
|
|
823
829
|
const identity = taskWorkspaceRoot ? loadWorkspaceIdentity(taskWorkspaceRoot, deps, identityCache, nowMs) : { missions: [], missionLocks: [], memberLocks: [] };
|
|
@@ -854,6 +860,10 @@ function collectRadar(options = {}) {
|
|
|
854
860
|
task_action: task ? taskSessionAction(agent, task, taskWorkspaceRoot) : untaskedAction(agent, taskWorkspaceRoot, agentTasks, resolved),
|
|
855
861
|
};
|
|
856
862
|
});
|
|
863
|
+
if (scopeKind !== 'global') {
|
|
864
|
+
agents = agents.filter((agent) => pathUnderRoot(agent.cwd, root)
|
|
865
|
+
|| (agent.task_workspace && pathUnderRoot(findTaskWorkspaceRoot(agent.cwd, deps), root)));
|
|
866
|
+
}
|
|
857
867
|
const scorecards = loadScorecards(root, deps);
|
|
858
868
|
const osState = {
|
|
859
869
|
xp: loadXp(root, deps),
|
|
@@ -863,13 +873,25 @@ function collectRadar(options = {}) {
|
|
|
863
873
|
loop: loadLoop(missions, root, deps),
|
|
864
874
|
};
|
|
865
875
|
osState.business = loadBusinessCollaboration(root, deps, osState.team, scorecards.length);
|
|
866
|
-
return {
|
|
876
|
+
return {
|
|
877
|
+
scope: scopeKind,
|
|
878
|
+
root,
|
|
879
|
+
generated_at: new Date(nowMs).toISOString(),
|
|
880
|
+
summary: summarize(tasks, missions, worktrees, agents),
|
|
881
|
+
os: osState,
|
|
882
|
+
next_action: nextAction(tasks, missions, worktrees, agents, osState),
|
|
883
|
+
agents,
|
|
884
|
+
tasks,
|
|
885
|
+
missions,
|
|
886
|
+
worktrees,
|
|
887
|
+
};
|
|
867
888
|
}
|
|
868
889
|
|
|
869
890
|
function renderRadar(data) {
|
|
870
891
|
const lines = [];
|
|
871
892
|
const s = data.summary;
|
|
872
893
|
lines.push('Operator radar');
|
|
894
|
+
lines.push(`Scope: ${data.scope || 'workspace'}${data.scope === 'global' ? ' (machine)' : ' (this workspace)'}`);
|
|
873
895
|
lines.push('');
|
|
874
896
|
lines.push(`Agents: ${s.agents.active}/${s.agents.total} active`);
|
|
875
897
|
lines.push(`Tasks: ${s.tasks.open} open, ${s.tasks.claimed} claimed, ${s.tasks.review} review (${s.tasks.certifiedReview} certified)`);
|
|
@@ -1210,17 +1232,19 @@ function renderAgentTop(data) {
|
|
|
1210
1232
|
|
|
1211
1233
|
function radarCommand(args = [], options = {}) {
|
|
1212
1234
|
if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
|
|
1213
|
-
console.log('Usage: atris radar [--json] [--agents]');
|
|
1235
|
+
console.log('Usage: atris radar [--json] [--agents] [--global]');
|
|
1214
1236
|
console.log('Usage: atris radar agents');
|
|
1215
1237
|
console.log('Usage: atris ctop');
|
|
1216
1238
|
console.log('');
|
|
1217
1239
|
console.log('Shows live agent processes joined with Atris tasks, missions, and worktrees.');
|
|
1240
|
+
console.log('Default scope is this workspace. Pass --global for the whole machine.');
|
|
1218
1241
|
console.log('Use --agents or ctop for a process-first CPU/memory view.');
|
|
1219
1242
|
return 0;
|
|
1220
1243
|
}
|
|
1221
|
-
const
|
|
1222
|
-
const
|
|
1223
|
-
|
|
1244
|
+
const scope = parseScopeFlag(args);
|
|
1245
|
+
const data = collectRadar({ ...options, scope: scope.kind });
|
|
1246
|
+
const agentsOnly = scope.args.includes('--agents') || scope.args[0] === 'agents';
|
|
1247
|
+
if (scope.args.includes('--json') || args.includes('--json')) console.log(JSON.stringify(agentsOnly ? { ...agentTopPayload(data), scope: data.scope } : data, null, 2));
|
|
1224
1248
|
else if (agentsOnly) console.log(renderAgentTop(data));
|
|
1225
1249
|
else console.log(renderRadar(data));
|
|
1226
1250
|
return 0;
|
package/commands/recap.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const { isCertifiedReview, personName } = require('../lib/first-minute');
|
|
4
|
+
const { isRealTestRunnerProof, quoteVerifierCommand } = require('../lib/verifier-quality');
|
|
3
5
|
|
|
4
6
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
5
7
|
const DEFAULT_DAYS = 7;
|
|
@@ -30,8 +32,10 @@ function loadTasks(root) {
|
|
|
30
32
|
const db = taskDb.open();
|
|
31
33
|
const ws = taskDb.workspaceRoot(root);
|
|
32
34
|
const rows = taskDb.listTasks(db, { workspaceRoot: ws });
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
if (Array.isArray(rows) && rows.length) {
|
|
36
|
+
const refs = taskDb.taskDisplayRefMap(rows);
|
|
37
|
+
return rows.map(row => ({ ...row, display_id: refs.get(row.id) || row.id.slice(-6) }));
|
|
38
|
+
}
|
|
35
39
|
} catch (e) {
|
|
36
40
|
// fall through to projection
|
|
37
41
|
}
|
|
@@ -59,14 +63,22 @@ function plainCheck(proof, width = 70) {
|
|
|
59
63
|
const checks = [];
|
|
60
64
|
const add = label => { if (!checks.includes(label)) checks.push(label); };
|
|
61
65
|
|
|
66
|
+
const quoted = quoteVerifierCommand(flat);
|
|
67
|
+
if (quoted) add(`ran \`${quoted}\``);
|
|
68
|
+
|
|
62
69
|
if (/\b(PR|pull request)\b.*\b(merged|MERGED)\b|\bmerged\b.*\b(PR|pull request)\b/i.test(flat)) add('merged');
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (
|
|
70
|
+
// Only say "tests passed" when a real test runner appears in the proof.
|
|
71
|
+
// A bare `test -f` / file-exists check must not claim tests ran.
|
|
72
|
+
if (isRealTestRunnerProof(flat)) add('tests passed');
|
|
73
|
+
if (/\b(node --check|git diff --check|git diff --exit-code|git diff --quiet)\b/i.test(flat)) add('code check passed');
|
|
74
|
+
if (/\b(?:\brg\b|\bgrep\b)\b/i.test(flat) && !/\btest\s+-[efsd]\b/i.test(flat)) add('content check passed');
|
|
66
75
|
if (/\b(bench|benchmark|measured|latency|speed)\b/i.test(flat)) add('measured improvement');
|
|
67
76
|
if (/\brepeated agent review\b|\bagent review\b/i.test(flat)) add('reviewed repeatedly');
|
|
68
77
|
if (/\batris\/runs\/[^\s]+\.json\b|receipt\b/i.test(flat)) add('record saved');
|
|
69
78
|
if (/\b(human approved|accepted by|accepted_at|reward)\b/i.test(flat)) add('human accepted');
|
|
79
|
+
if (/\btest\s+-[efsd]\b|\b\[\s+-[efsd]\b/i.test(flat) && !isRealTestRunnerProof(flat)) {
|
|
80
|
+
add(quoted ? 'file check' : 'file check only');
|
|
81
|
+
}
|
|
70
82
|
|
|
71
83
|
if (checks.length) return checks.join(', ');
|
|
72
84
|
return shortProof(proof, width);
|
|
@@ -99,28 +111,38 @@ function buildRecapData(root = process.cwd(), { days = DEFAULT_DAYS } = {}) {
|
|
|
99
111
|
done_at: t.done_at || null,
|
|
100
112
|
});
|
|
101
113
|
|
|
114
|
+
const newestFirst = (a, b) => (
|
|
115
|
+
Number(b.updated_at || b.created_at || 0) - Number(a.updated_at || a.created_at || 0)
|
|
116
|
+
);
|
|
102
117
|
const shipped = tasks
|
|
103
118
|
.filter(t => t.status === 'done' && Number(t.done_at || 0) >= cutoff)
|
|
104
119
|
.sort((a, b) => Number(b.done_at || 0) - Number(a.done_at || 0))
|
|
105
120
|
.map(pick);
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
.
|
|
121
|
+
const reviewTasks = tasks.filter(t => t.status === 'review');
|
|
122
|
+
const waiting = reviewTasks
|
|
123
|
+
.filter(isCertifiedReview)
|
|
124
|
+
.sort(newestFirst)
|
|
125
|
+
.map(pick);
|
|
126
|
+
const checking = reviewTasks
|
|
127
|
+
.filter(t => !isCertifiedReview(t))
|
|
128
|
+
.sort(newestFirst)
|
|
109
129
|
.map(pick);
|
|
110
130
|
const inProgress = tasks
|
|
111
131
|
.filter(t => t.status === 'open' || t.status === 'claimed')
|
|
112
132
|
.map(pick);
|
|
113
133
|
|
|
114
|
-
const withProof = [...shipped, ...waiting].filter(t => t.proof).length;
|
|
134
|
+
const withProof = [...shipped, ...waiting, ...checking].filter(t => t.proof).length;
|
|
115
135
|
return {
|
|
116
136
|
empty: false,
|
|
117
137
|
days: windowDays,
|
|
118
138
|
workspace: path.basename(root),
|
|
119
139
|
shipped,
|
|
120
140
|
waiting,
|
|
141
|
+
checking,
|
|
121
142
|
inProgress,
|
|
143
|
+
next: waiting.length ? `atris task accept ${waiting[0].id}` : null,
|
|
122
144
|
proof_attached: withProof,
|
|
123
|
-
proof_total: shipped.length + waiting.length,
|
|
145
|
+
proof_total: shipped.length + waiting.length + checking.length,
|
|
124
146
|
};
|
|
125
147
|
}
|
|
126
148
|
|
|
@@ -140,8 +162,9 @@ function renderRecap(data) {
|
|
|
140
162
|
const headline = [];
|
|
141
163
|
if (data.shipped.length) headline.push(`${data.shipped.length} done`);
|
|
142
164
|
if (data.waiting.length) headline.push(`${data.waiting.length} needs you`);
|
|
165
|
+
if (data.checking.length) headline.push(`${data.checking.length} still being checked`);
|
|
143
166
|
if (data.inProgress.length) headline.push(`${data.inProgress.length} still working`);
|
|
144
|
-
lines.push(headline.length ? headline.join(' · ') : 'Quiet window
|
|
167
|
+
lines.push(headline.length ? headline.join(' · ') : 'Quiet window. nothing moved in this period.');
|
|
145
168
|
|
|
146
169
|
if (data.shipped.length) {
|
|
147
170
|
lines.push('');
|
|
@@ -163,7 +186,16 @@ function renderRecap(data) {
|
|
|
163
186
|
if (check) lines.push(` checked: ${check}`);
|
|
164
187
|
}
|
|
165
188
|
if (data.waiting.length > 10) lines.push(` … and ${data.waiting.length - 10} more`);
|
|
166
|
-
lines.push(
|
|
189
|
+
if (data.next) lines.push(` next: ${data.next}`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (data.checking.length) {
|
|
193
|
+
lines.push('');
|
|
194
|
+
lines.push(`STILL BEING CHECKED — ${data.checking.length}`);
|
|
195
|
+
for (const t of data.checking.slice(0, 10)) {
|
|
196
|
+
lines.push(` ${t.id} ${shortTitle(t.title)}`);
|
|
197
|
+
}
|
|
198
|
+
if (data.checking.length > 10) lines.push(` … and ${data.checking.length - 10} more`);
|
|
167
199
|
}
|
|
168
200
|
|
|
169
201
|
if (data.inProgress.length) {
|
|
@@ -187,6 +219,7 @@ function renderShare(data) {
|
|
|
187
219
|
lines.push('');
|
|
188
220
|
if (data.shipped.length) lines.push(`- ${data.shipped.length} done and accepted`);
|
|
189
221
|
if (data.waiting.length) lines.push(`- ${data.waiting.length} ready for you to approve or send back`);
|
|
222
|
+
if (data.checking.length) lines.push(`- ${data.checking.length} still being checked`);
|
|
190
223
|
if (data.inProgress.length) lines.push(`- ${data.inProgress.length} still being worked on`);
|
|
191
224
|
const highlights = [...data.shipped, ...data.waiting].filter(t => t.proof).slice(0, 5);
|
|
192
225
|
if (highlights.length) {
|
|
@@ -201,17 +234,83 @@ function renderShare(data) {
|
|
|
201
234
|
return lines.join('\n');
|
|
202
235
|
}
|
|
203
236
|
|
|
237
|
+
function recapSoftTitle(title, maxWords = 5) {
|
|
238
|
+
const words = String(title || '').replace(/\s+/g, ' ').trim().split(' ').filter(Boolean);
|
|
239
|
+
if (!words.length) return '';
|
|
240
|
+
const text = words.slice(0, maxWords).join(' ').replace(/[.,;:!?]+$/g, '');
|
|
241
|
+
return `"${text.toLowerCase()}"`;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function renderRecapMinute(data, { person } = {}) {
|
|
245
|
+
const who = person != null ? person : personName();
|
|
246
|
+
const greet = who ? `hey ${who}, ` : '';
|
|
247
|
+
if (data.empty) {
|
|
248
|
+
return [
|
|
249
|
+
`${greet}no task history yet.`,
|
|
250
|
+
'',
|
|
251
|
+
'next: atris init --minimal',
|
|
252
|
+
].join('\n');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const waiting = Array.isArray(data.waiting) ? data.waiting : [];
|
|
256
|
+
const checking = Array.isArray(data.checking) ? data.checking : [];
|
|
257
|
+
const shipped = Array.isArray(data.shipped) ? data.shipped : [];
|
|
258
|
+
const inProgress = Array.isArray(data.inProgress) ? data.inProgress : [];
|
|
259
|
+
const certified = waiting[0] || null;
|
|
260
|
+
const title = certified ? recapSoftTitle(certified.title) : '';
|
|
261
|
+
|
|
262
|
+
if (certified) {
|
|
263
|
+
const win = title
|
|
264
|
+
? `${greet}${title} is waiting for your ok.`
|
|
265
|
+
: `${greet}one finished thing is waiting for your ok.`;
|
|
266
|
+
const lines = [win];
|
|
267
|
+
if (checking.length === 1) lines.push('1 still being checked.');
|
|
268
|
+
if (checking.length > 1) lines.push(`${checking.length} still being checked.`);
|
|
269
|
+
if (data.next) {
|
|
270
|
+
lines.push('');
|
|
271
|
+
lines.push(`next: ${data.next}`);
|
|
272
|
+
}
|
|
273
|
+
return lines.join('\n');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (checking.length === 1) {
|
|
277
|
+
const named = recapSoftTitle(checking[0] && checking[0].title);
|
|
278
|
+
if (named) return `${greet}${named} is still being checked.`;
|
|
279
|
+
return `${greet}1 finished thing is still being checked.`;
|
|
280
|
+
}
|
|
281
|
+
if (checking.length > 1) {
|
|
282
|
+
return `${greet}${checking.length} finished things are still being checked.`;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (shipped.length) {
|
|
286
|
+
const named = recapSoftTitle(shipped[0].title);
|
|
287
|
+
return named
|
|
288
|
+
? `${greet}you already shipped ${named}.`
|
|
289
|
+
: `${greet}you already shipped the last finished thing.`;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (inProgress.length) {
|
|
293
|
+
const item = inProgress[0];
|
|
294
|
+
const named = recapSoftTitle(item && item.title);
|
|
295
|
+
if (item && item.owner && named) return `${greet}${named} is already yours.`;
|
|
296
|
+
if (named) return `${greet}${named} is ready to claim.`;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return `${greet}quiet window. nothing moved in this period.`;
|
|
300
|
+
}
|
|
301
|
+
|
|
204
302
|
function printRecapHelp() {
|
|
205
303
|
console.log(`
|
|
206
|
-
atris recap - what got done, in
|
|
304
|
+
atris recap - what got done, in spoken lines
|
|
207
305
|
|
|
208
|
-
atris recap
|
|
209
|
-
atris recap --
|
|
210
|
-
atris recap --
|
|
211
|
-
atris recap --
|
|
306
|
+
atris recap a few spoken lines: waiting for your ok, still being checked
|
|
307
|
+
atris recap --verbose the old report
|
|
308
|
+
atris recap --days 30 widen the window
|
|
309
|
+
atris recap --share paste-ready summary for Slack, email, or a customer
|
|
310
|
+
atris recap --json structured output for agents and dashboards
|
|
212
311
|
|
|
213
|
-
|
|
214
|
-
|
|
312
|
+
certified work names atris task accept. uncertified stays still being checked.
|
|
313
|
+
use --verbose or --share for the old report.
|
|
215
314
|
`);
|
|
216
315
|
}
|
|
217
316
|
|
|
@@ -227,7 +326,15 @@ function recapAtris(args = []) {
|
|
|
227
326
|
console.log(JSON.stringify(data, null, 2));
|
|
228
327
|
return;
|
|
229
328
|
}
|
|
230
|
-
|
|
329
|
+
if (args.includes('--share')) {
|
|
330
|
+
console.log(renderShare(data));
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (args.includes('--verbose') || args.includes('--full')) {
|
|
334
|
+
console.log(renderRecap(data));
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
console.log(renderRecapMinute(data));
|
|
231
338
|
}
|
|
232
339
|
|
|
233
|
-
module.exports = { recapAtris, buildRecapData, renderRecap, renderShare };
|
|
340
|
+
module.exports = { recapAtris, buildRecapData, renderRecap, renderRecapMinute, renderShare };
|
package/commands/release.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const { execFileSync } = require('child_process');
|
|
3
|
+
const { execFileSync, spawnSync } = require('child_process');
|
|
4
|
+
const { runGit } = require('../lib/checkout-sync');
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* atris release [--dry-run] - Tag a release, create GitHub release, draft /launch post
|
|
@@ -181,4 +182,103 @@ function printLaunchPost(version, commitLines) {
|
|
|
181
182
|
console.log('--- end launch post ---');
|
|
182
183
|
}
|
|
183
184
|
|
|
184
|
-
|
|
185
|
+
function readPackageVersion(cwd) {
|
|
186
|
+
try {
|
|
187
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
|
|
188
|
+
return typeof pkg.version === 'string' && pkg.version.trim() ? pkg.version.trim() : null;
|
|
189
|
+
} catch {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function runFullTestSuite(cwd) {
|
|
195
|
+
return spawnSync('npm', ['test'], {
|
|
196
|
+
cwd,
|
|
197
|
+
env: { ...process.env, CI: 'true' },
|
|
198
|
+
stdio: 'inherit',
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function gitStdout(result) {
|
|
203
|
+
return result && result.status === 0 ? String(result.stdout || '').trim() : '';
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* atris release preflight
|
|
208
|
+
*
|
|
209
|
+
* Enforced checks before pushing a v* tag from master. Prints pass/fail for
|
|
210
|
+
* each check and exits nonzero if any fail. Skips the test suite when earlier
|
|
211
|
+
* git checks already failed so a bad checkout does not burn a full run.
|
|
212
|
+
*/
|
|
213
|
+
function releasePreflight({ cwd = process.cwd(), runTests = runFullTestSuite } = {}) {
|
|
214
|
+
let failed = false;
|
|
215
|
+
|
|
216
|
+
const branchResult = runGit(['rev-parse', '--abbrev-ref', 'HEAD'], cwd);
|
|
217
|
+
const branch = gitStdout(branchResult);
|
|
218
|
+
if (branch === 'master') {
|
|
219
|
+
console.log('pass: current branch is master');
|
|
220
|
+
} else {
|
|
221
|
+
failed = true;
|
|
222
|
+
console.log(`fail: current branch is ${branch || 'unknown'}, need master`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const statusResult = runGit(['status', '--porcelain'], cwd);
|
|
226
|
+
if (statusResult.status === 0 && String(statusResult.stdout || '').trim() === '') {
|
|
227
|
+
console.log('pass: working tree is clean');
|
|
228
|
+
} else {
|
|
229
|
+
failed = true;
|
|
230
|
+
console.log('fail: working tree is not clean');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const localMaster = gitStdout(runGit(['rev-parse', 'master'], cwd));
|
|
234
|
+
const originMaster = gitStdout(runGit(['rev-parse', 'origin/master'], cwd));
|
|
235
|
+
if (!localMaster) {
|
|
236
|
+
failed = true;
|
|
237
|
+
console.log('fail: local master is missing');
|
|
238
|
+
} else if (!originMaster) {
|
|
239
|
+
failed = true;
|
|
240
|
+
console.log('fail: origin/master is missing');
|
|
241
|
+
} else if (localMaster !== originMaster) {
|
|
242
|
+
failed = true;
|
|
243
|
+
console.log('fail: local master does not match origin/master');
|
|
244
|
+
} else {
|
|
245
|
+
console.log('pass: local master matches origin/master');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const version = readPackageVersion(cwd);
|
|
249
|
+
if (!version) {
|
|
250
|
+
failed = true;
|
|
251
|
+
console.log('fail: package.json version is missing');
|
|
252
|
+
} else {
|
|
253
|
+
const tagName = `v${version}`;
|
|
254
|
+
const tagList = gitStdout(runGit(['tag', '--list', tagName], cwd));
|
|
255
|
+
if (tagList) {
|
|
256
|
+
failed = true;
|
|
257
|
+
console.log(`fail: tag ${tagName} already exists`);
|
|
258
|
+
} else {
|
|
259
|
+
console.log(`pass: no tag ${tagName}`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (failed) {
|
|
264
|
+
console.log('skip: test suite (earlier checks failed)');
|
|
265
|
+
} else {
|
|
266
|
+
const testResult = runTests(cwd);
|
|
267
|
+
if (testResult && testResult.error) {
|
|
268
|
+
failed = true;
|
|
269
|
+
console.log(`fail: test suite could not run (${testResult.error.message})`);
|
|
270
|
+
} else if (testResult && testResult.status === 0) {
|
|
271
|
+
console.log('pass: test suite');
|
|
272
|
+
} else {
|
|
273
|
+
const code = testResult && typeof testResult.status === 'number' ? testResult.status : 1;
|
|
274
|
+
failed = true;
|
|
275
|
+
console.log(`fail: test suite exited ${code}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
console.log('');
|
|
280
|
+
console.log(failed ? 'release preflight failed' : 'release preflight passed');
|
|
281
|
+
return failed ? 1 : 0;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
module.exports = { releaseAtris, releasePreflight, runFullTestSuite };
|
package/commands/review.js
CHANGED
|
@@ -30,24 +30,19 @@ function findReviewEngine() {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
function printMissingReviewEngine(jsonMode) {
|
|
33
|
+
const install = 'atris skill install code-review';
|
|
33
34
|
if (jsonMode) {
|
|
34
35
|
console.log(JSON.stringify({
|
|
35
36
|
ok: false,
|
|
36
|
-
error: '
|
|
37
|
+
error: 'not installed',
|
|
37
38
|
expected: 'atris/business/claude-code-review/workspace/review_engine.py',
|
|
38
39
|
specialists: ['Security', 'Testing', 'Performance', 'Maintainability', 'Database', 'Async'],
|
|
39
|
-
install
|
|
40
|
+
install,
|
|
40
41
|
}, null, 2));
|
|
41
42
|
} else {
|
|
42
|
-
console.error(
|
|
43
|
-
console.error('Expected at: atris/business/claude-code-review/workspace/review_engine.py');
|
|
44
|
-
console.error('');
|
|
45
|
-
console.error('The review engine runs 6 specialists:');
|
|
46
|
-
console.error(' Security, Testing, Performance, Maintainability, Database, Async');
|
|
47
|
-
console.error('');
|
|
48
|
-
console.error('Install: copy review_engine.py to your project');
|
|
43
|
+
console.error(`not installed; ${install}`);
|
|
49
44
|
}
|
|
50
|
-
process.exit(
|
|
45
|
+
process.exit(2);
|
|
51
46
|
}
|
|
52
47
|
|
|
53
48
|
function exitReviewError(message, jsonMode, extra = {}) {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
appendDailySummary,
|
|
5
|
+
computeRevisionMetric,
|
|
6
|
+
} = require('../lib/revision-metric');
|
|
7
|
+
|
|
8
|
+
function plural(count, word) {
|
|
9
|
+
return `${count} ${word}${count === 1 ? '' : 's'}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function plainSubject(subject) {
|
|
13
|
+
return String(subject || 'untitled landing')
|
|
14
|
+
.replace(/—/g, ',')
|
|
15
|
+
.replace(/\s+/g, ' ')
|
|
16
|
+
.trim()
|
|
17
|
+
.toLowerCase();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function formatRevisionMetric(metric, saved) {
|
|
21
|
+
const summary = metric.rolling_7_days;
|
|
22
|
+
const lines = [
|
|
23
|
+
`revision signals in the last seven days: ${summary.revision_count} across ${plural(summary.landed_changes, 'atris-assisted landing')}.`,
|
|
24
|
+
`clean so far: ${summary.clean_so_far}. still observing: ${summary.still_observing}. the target is zero revisions.`,
|
|
25
|
+
'this is a same-file git signal, not proof of automatic landing or why a human changed the file.',
|
|
26
|
+
];
|
|
27
|
+
for (const change of (metric.changes || []).filter((item) => item.revision_count > 0)) {
|
|
28
|
+
lines.push(`"${plainSubject(change.subject)}": ${plural(change.revision_count, 'possible revision')}.`);
|
|
29
|
+
}
|
|
30
|
+
lines.push(saved.appended
|
|
31
|
+
? `daily summary saved for ${saved.row.date}.`
|
|
32
|
+
: `daily summary for ${saved.row.date} was already saved.`);
|
|
33
|
+
return lines.join('\n');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function revisionsCommand(args = [], deps = {}) {
|
|
37
|
+
const root = deps.root || process.cwd();
|
|
38
|
+
const json = args.includes('--json');
|
|
39
|
+
let metric;
|
|
40
|
+
try {
|
|
41
|
+
metric = (deps.computeRevisionMetric || computeRevisionMetric)(root, {
|
|
42
|
+
now: deps.now,
|
|
43
|
+
ref: deps.ref,
|
|
44
|
+
});
|
|
45
|
+
} catch {
|
|
46
|
+
console.error('this folder has no readable git history, so revisions cannot be measured.');
|
|
47
|
+
return 1;
|
|
48
|
+
}
|
|
49
|
+
const saved = (deps.appendDailySummary || appendDailySummary)(root, metric, deps.stateOptions);
|
|
50
|
+
if (json) console.log(JSON.stringify({ ...metric, daily_summary: saved }));
|
|
51
|
+
else console.log(formatRevisionMetric(metric, saved));
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = { formatRevisionMetric, plainSubject, revisionsCommand };
|
package/commands/run-front.js
CHANGED
|
@@ -65,19 +65,27 @@ function runnerDrivableForFrontDoor(runner, options = {}) {
|
|
|
65
65
|
return false;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
// Most logical mission: a moving one beats a planned one,
|
|
69
|
-
//
|
|
68
|
+
// Most logical mission: a moving one beats a planned one, ready waits for a
|
|
69
|
+
// resume when nothing else is moving, and newer beats older.
|
|
70
70
|
function pickRunnableMission(root = process.cwd(), missionMap = null, options = {}) {
|
|
71
71
|
let map = missionMap;
|
|
72
72
|
if (!map) {
|
|
73
73
|
try { map = require('./mission').loadMissionMap(root); } catch { return null; }
|
|
74
74
|
}
|
|
75
|
+
const rank = (status) => {
|
|
76
|
+
if (status === 'running') return 0;
|
|
77
|
+
if (status === 'planning') return 1;
|
|
78
|
+
if (status === 'ready') return 2;
|
|
79
|
+
return 9;
|
|
80
|
+
};
|
|
75
81
|
const candidates = Array.from(map.values())
|
|
76
|
-
.filter((mission) => mission && (mission.status === 'running' || mission.status === 'planning'))
|
|
82
|
+
.filter((mission) => mission && (mission.status === 'running' || mission.status === 'planning' || mission.status === 'ready'))
|
|
77
83
|
.filter((mission) => runnerDrivableForFrontDoor(mission?.runner, options))
|
|
78
84
|
.filter((mission) => !/^decide and start the next useful mission after:/i.test(String(mission?.objective || '')))
|
|
79
85
|
.sort((a, b) => {
|
|
80
|
-
|
|
86
|
+
const ra = rank(a.status);
|
|
87
|
+
const rb = rank(b.status);
|
|
88
|
+
if (ra !== rb) return ra - rb;
|
|
81
89
|
return String(b.updated_at || b.created_at || '').localeCompare(String(a.updated_at || a.created_at || ''));
|
|
82
90
|
});
|
|
83
91
|
return candidates[0] || null;
|
package/commands/scout.js
CHANGED
|
@@ -90,6 +90,17 @@ function runScout(question, { dir, model, engine, timeoutMs }) {
|
|
|
90
90
|
|
|
91
91
|
async function scoutCommand(argv) {
|
|
92
92
|
const args = [...argv];
|
|
93
|
+
const { refuseHeadlessUnless } = require('../lib/noninteractive');
|
|
94
|
+
if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
|
|
95
|
+
showHelp();
|
|
96
|
+
return 0;
|
|
97
|
+
}
|
|
98
|
+
if (refuseHeadlessUnless(args, {
|
|
99
|
+
allowYes: true,
|
|
100
|
+
usage: 'Usage: atris scout [--yes] "<question>"\nHeadless scout needs --yes.',
|
|
101
|
+
})) {
|
|
102
|
+
return 2;
|
|
103
|
+
}
|
|
93
104
|
const questions = [];
|
|
94
105
|
let dir = process.cwd();
|
|
95
106
|
let model = MODELS.haiku;
|
|
@@ -105,7 +116,7 @@ async function scoutCommand(argv) {
|
|
|
105
116
|
if (!ENGINES[engine]) { console.error(`✗ unknown engine "${engine}" (claude|cursor|devin|fast)`); return 1; }
|
|
106
117
|
}
|
|
107
118
|
else if (a === '--timeout') timeoutMs = (parseInt(args.shift(), 10) || 180) * 1000;
|
|
108
|
-
else if (a === '--help' || a === '-h') {
|
|
119
|
+
else if (a === '--help' || a === '-h' || a === '--yes' || a === '-y') { /* handled above / proceed flag */ }
|
|
109
120
|
else questions.push(a);
|
|
110
121
|
}
|
|
111
122
|
|