atris 2.6.2 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +124 -34
- package/atris/CLAUDE.md +5 -1
- package/atris/atris.md +4 -0
- package/atris/features/README.md +24 -0
- package/atris/skills/autopilot/SKILL.md +74 -75
- package/atris/skills/endgame/SKILL.md +179 -0
- package/atris/skills/flow/SKILL.md +121 -0
- package/atris/skills/improve/SKILL.md +84 -0
- package/atris/skills/loop/SKILL.md +72 -0
- package/atris/skills/wiki/SKILL.md +61 -0
- package/atris/team/executor/MEMBER.md +10 -4
- package/atris/team/navigator/MEMBER.md +2 -0
- package/atris/team/validator/MEMBER.md +8 -5
- package/atris.md +33 -0
- package/bin/atris.js +210 -41
- package/commands/activate.js +28 -2
- package/commands/align.js +720 -0
- package/commands/auth.js +75 -2
- package/commands/autopilot.js +1213 -270
- package/commands/browse.js +100 -0
- package/commands/business.js +785 -12
- package/commands/clean.js +107 -2
- package/commands/computer.js +429 -0
- package/commands/context-sync.js +78 -8
- package/commands/experiments.js +351 -0
- package/commands/feedback.js +150 -0
- package/commands/fleet.js +395 -0
- package/commands/fork.js +127 -0
- package/commands/init.js +50 -1
- package/commands/learn.js +407 -0
- package/commands/lifecycle.js +94 -0
- package/commands/loop.js +114 -0
- package/commands/publish.js +129 -0
- package/commands/pull.js +434 -48
- package/commands/push.js +312 -164
- package/commands/review.js +149 -0
- package/commands/run.js +76 -43
- package/commands/serve.js +360 -0
- package/commands/setup.js +1 -1
- package/commands/soul.js +381 -0
- package/commands/status.js +119 -1
- package/commands/sync.js +147 -1
- package/commands/terminal.js +201 -0
- package/commands/wiki.js +376 -0
- package/commands/workflow.js +191 -74
- package/commands/workspace-clean.js +3 -3
- package/lib/endstate.js +259 -0
- package/lib/learnings.js +235 -0
- package/lib/manifest.js +1 -0
- package/lib/todo.js +9 -5
- package/lib/wiki.js +578 -0
- package/package.json +2 -2
- package/utils/api.js +48 -36
- package/utils/auth.js +1 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Atris Review — Run code review with specialist agents
|
|
3
|
+
*
|
|
4
|
+
* atris review — Review staged changes
|
|
5
|
+
* atris review <file> — Review a specific file
|
|
6
|
+
* atris review --diff HEAD~1 — Review last commit
|
|
7
|
+
* atris review --all — Audit all Python services
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const { execSync, spawnSync } = require('child_process');
|
|
13
|
+
|
|
14
|
+
function findReviewEngine() {
|
|
15
|
+
// Look for review_engine.py in common locations
|
|
16
|
+
const candidates = [
|
|
17
|
+
path.join(process.cwd(), 'atris', 'business', 'claude-code-review', 'workspace', 'review_engine.py'),
|
|
18
|
+
path.join(process.cwd(), 'review_engine.py'),
|
|
19
|
+
];
|
|
20
|
+
// Also check parent dirs
|
|
21
|
+
let dir = process.cwd();
|
|
22
|
+
for (let i = 0; i < 3; i++) {
|
|
23
|
+
candidates.push(path.join(dir, 'atris', 'business', 'claude-code-review', 'workspace', 'review_engine.py'));
|
|
24
|
+
dir = path.dirname(dir);
|
|
25
|
+
}
|
|
26
|
+
for (const p of candidates) {
|
|
27
|
+
if (fs.existsSync(p)) return p;
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function runReview(args) {
|
|
33
|
+
const enginePath = findReviewEngine();
|
|
34
|
+
if (!enginePath) {
|
|
35
|
+
console.error('Review engine not found.');
|
|
36
|
+
console.error('Expected at: atris/business/claude-code-review/workspace/review_engine.py');
|
|
37
|
+
console.error('');
|
|
38
|
+
console.error('The review engine runs 6 specialists:');
|
|
39
|
+
console.error(' Security, Testing, Performance, Maintainability, Database, Async');
|
|
40
|
+
console.error('');
|
|
41
|
+
console.error('Install: copy review_engine.py to your project');
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Parse args
|
|
46
|
+
let file = null;
|
|
47
|
+
let diffRef = null;
|
|
48
|
+
let allMode = false;
|
|
49
|
+
let jsonMode = false;
|
|
50
|
+
|
|
51
|
+
for (let i = 0; i < args.length; i++) {
|
|
52
|
+
if (args[i] === '--diff' && args[i + 1]) {
|
|
53
|
+
diffRef = args[i + 1];
|
|
54
|
+
i++;
|
|
55
|
+
} else if (args[i] === '--all') {
|
|
56
|
+
allMode = true;
|
|
57
|
+
} else if (args[i] === '--json') {
|
|
58
|
+
jsonMode = true;
|
|
59
|
+
} else if (!args[i].startsWith('-')) {
|
|
60
|
+
file = args[i];
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Build command
|
|
65
|
+
const cmdArgs = ['python3', enginePath];
|
|
66
|
+
if (file) {
|
|
67
|
+
cmdArgs.push('--file', file);
|
|
68
|
+
} else if (diffRef) {
|
|
69
|
+
cmdArgs.push('--diff', diffRef);
|
|
70
|
+
}
|
|
71
|
+
if (jsonMode) {
|
|
72
|
+
cmdArgs.push('--json');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (allMode) {
|
|
76
|
+
// Audit all Python services
|
|
77
|
+
console.log('Auditing all Python services...\n');
|
|
78
|
+
const servicesDir = path.join(process.cwd(), 'backend', 'services');
|
|
79
|
+
if (!fs.existsSync(servicesDir)) {
|
|
80
|
+
console.error('No backend/services/ directory found.');
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const files = fs.readdirSync(servicesDir).filter(f => f.endsWith('.py'));
|
|
85
|
+
let totalFindings = 0;
|
|
86
|
+
let cleanCount = 0;
|
|
87
|
+
const issues = [];
|
|
88
|
+
|
|
89
|
+
for (const f of files) {
|
|
90
|
+
const filePath = path.join(servicesDir, f);
|
|
91
|
+
try {
|
|
92
|
+
const result = spawnSync('python3', [enginePath, '--file', filePath, '--json'], {
|
|
93
|
+
encoding: 'utf8', timeout: 10000,
|
|
94
|
+
});
|
|
95
|
+
if (result.stdout) {
|
|
96
|
+
const data = JSON.parse(result.stdout);
|
|
97
|
+
const highMed = data.findings.filter(x => x.severity === 'high' || x.severity === 'medium');
|
|
98
|
+
if (highMed.length > 0) {
|
|
99
|
+
issues.push({ file: f, score: data.quality_score, count: highMed.length, top: highMed[0].rule });
|
|
100
|
+
totalFindings += highMed.length;
|
|
101
|
+
} else {
|
|
102
|
+
cleanCount++;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} catch {}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
issues.sort((a, b) => a.score - b.score);
|
|
109
|
+
|
|
110
|
+
console.log(`AUDIT: ${files.length} services | ${cleanCount} clean | ${issues.length} with findings\n`);
|
|
111
|
+
if (issues.length > 0) {
|
|
112
|
+
console.log(`${'Service'.padEnd(40)} ${'Score'.padStart(6)} ${'Findings'.padStart(8)} Top Issue`);
|
|
113
|
+
console.log(`${'─'.repeat(40)} ${'─'.repeat(6)} ${'─'.repeat(8)} ${'─'.repeat(15)}`);
|
|
114
|
+
for (const i of issues) {
|
|
115
|
+
console.log(`${i.file.padEnd(40)} ${(i.score + '/10').padStart(6)} ${String(i.count).padStart(8)} ${i.top}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
console.log('');
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Run single file or diff review
|
|
123
|
+
const result = spawnSync(cmdArgs[0], cmdArgs.slice(1), {
|
|
124
|
+
encoding: 'utf8',
|
|
125
|
+
stdio: 'inherit',
|
|
126
|
+
timeout: 30000,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
process.exit(result.status || 0);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function reviewCommand(...args) {
|
|
133
|
+
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
134
|
+
console.log('Usage: atris review [file] [options]');
|
|
135
|
+
console.log('');
|
|
136
|
+
console.log(' atris review Review staged changes');
|
|
137
|
+
console.log(' atris review <file.py> Review a specific file');
|
|
138
|
+
console.log(' atris review --diff HEAD~1 Review last commit');
|
|
139
|
+
console.log(' atris review --all Audit all backend services');
|
|
140
|
+
console.log(' atris review --json Machine-readable output');
|
|
141
|
+
console.log('');
|
|
142
|
+
console.log('6 specialists: Security, Testing, Performance, Maintainability, Database, Async');
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
runReview(args);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
module.exports = { reviewCommand };
|
package/commands/run.js
CHANGED
|
@@ -154,7 +154,10 @@ function hasWork(atrisDir) {
|
|
|
154
154
|
const content = fs.readFileSync(logFile, 'utf8');
|
|
155
155
|
const inboxMatch = content.match(/## Inbox\n([\s\S]*?)(?=\n##|$)/);
|
|
156
156
|
if (inboxMatch && inboxMatch[1].trim()) {
|
|
157
|
-
const items = inboxMatch[1].trim().split('\n').filter(l =>
|
|
157
|
+
const items = inboxMatch[1].trim().split('\n').filter(l => {
|
|
158
|
+
const t = l.trim();
|
|
159
|
+
return t.startsWith('- ') && t.length > 2;
|
|
160
|
+
});
|
|
158
161
|
if (items.length > 0) return true;
|
|
159
162
|
}
|
|
160
163
|
}
|
|
@@ -225,14 +228,20 @@ async function runAtris(options = {}) {
|
|
|
225
228
|
}
|
|
226
229
|
|
|
227
230
|
console.log('');
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
231
|
+
if (verbose) {
|
|
232
|
+
console.log('┌─────────────────────────────────────────────────────────────┐');
|
|
233
|
+
console.log(`│ Atris Run v${pkg.version} — autonomous plan → do → review │`);
|
|
234
|
+
console.log('└─────────────────────────────────────────────────────────────┘');
|
|
235
|
+
console.log('');
|
|
236
|
+
console.log(`Max cycles: ${cycles}`);
|
|
237
|
+
console.log(`Phase timeout: ${timeout / 1000}s`);
|
|
238
|
+
console.log(`Verbose: ${verbose}`);
|
|
239
|
+
console.log('');
|
|
240
|
+
} else {
|
|
241
|
+
console.log(`atris run v${pkg.version} — plan, do, review, repeat.`);
|
|
242
|
+
console.log(`i'll run up to ${cycles} cycle${cycles === 1 ? '' : 's'}, ${timeout / 1000}s per phase. next i'll check the backlog.`);
|
|
243
|
+
console.log('');
|
|
244
|
+
}
|
|
236
245
|
|
|
237
246
|
// Build context paths
|
|
238
247
|
const context = {
|
|
@@ -255,13 +264,19 @@ async function runAtris(options = {}) {
|
|
|
255
264
|
let completedCycles = 0;
|
|
256
265
|
|
|
257
266
|
for (let cycle = 1; cycle <= cycles; cycle++) {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
267
|
+
if (verbose) {
|
|
268
|
+
console.log(`\n${'━'.repeat(60)}`);
|
|
269
|
+
console.log(`CYCLE ${cycle}/${cycles}`);
|
|
270
|
+
console.log(`${'━'.repeat(60)}`);
|
|
271
|
+
} else {
|
|
272
|
+
console.log(`\ncycle ${cycle} of ${cycles}.`);
|
|
273
|
+
}
|
|
261
274
|
|
|
262
275
|
// Check if there's work
|
|
263
276
|
if (!hasWork(atrisDir)) {
|
|
264
|
-
console.log(
|
|
277
|
+
console.log(verbose
|
|
278
|
+
? '\nInbox empty. Backlog empty. Nothing to do.'
|
|
279
|
+
: 'i checked the inbox and backlog. both empty. nothing to do.');
|
|
265
280
|
break;
|
|
266
281
|
}
|
|
267
282
|
|
|
@@ -269,67 +284,81 @@ async function runAtris(options = {}) {
|
|
|
269
284
|
|
|
270
285
|
try {
|
|
271
286
|
// PLAN
|
|
272
|
-
console.log(
|
|
287
|
+
console.log(verbose
|
|
288
|
+
? '\n[1/3] PLAN — reading inbox, creating tasks...'
|
|
289
|
+
: 'planning… reading inbox, turning ideas into tasks.');
|
|
273
290
|
let phaseStart = Date.now();
|
|
274
291
|
const planOutput = executePhase('plan', context, { verbose, timeout });
|
|
275
292
|
timing.plan = Date.now() - phaseStart;
|
|
276
293
|
|
|
277
294
|
if (planOutput.includes('[NOTHING_TO_DO]')) {
|
|
278
|
-
console.log('Nothing to do. Stopping.');
|
|
295
|
+
console.log(verbose ? 'Nothing to do. Stopping.' : 'navigator says nothing to do. stopping.');
|
|
279
296
|
break;
|
|
280
297
|
}
|
|
281
|
-
console.log(
|
|
298
|
+
console.log(verbose
|
|
299
|
+
? `✓ Plan complete (${Math.round(timing.plan / 1000)}s)`
|
|
300
|
+
: `planned in ${Math.round(timing.plan / 1000)}s. next i'll pick the top backlog task and build it.`);
|
|
282
301
|
|
|
283
302
|
// Check if plan created tasks
|
|
284
303
|
if (!hasWork(atrisDir)) {
|
|
285
|
-
console.log('No tasks created. Stopping.');
|
|
304
|
+
console.log(verbose ? 'No tasks created. Stopping.' : 'no tasks got created. stopping.');
|
|
286
305
|
break;
|
|
287
306
|
}
|
|
288
307
|
|
|
289
308
|
// DO
|
|
290
|
-
console.log('\n[2/3] DO — building task...');
|
|
309
|
+
console.log(verbose ? '\n[2/3] DO — building task...' : 'building the top task now.');
|
|
291
310
|
phaseStart = Date.now();
|
|
292
311
|
executePhase('do', context, { verbose, timeout });
|
|
293
312
|
timing.do = Date.now() - phaseStart;
|
|
294
|
-
console.log(
|
|
313
|
+
console.log(verbose
|
|
314
|
+
? `✓ Build complete (${Math.round(timing.do / 1000)}s)`
|
|
315
|
+
: `built in ${Math.round(timing.do / 1000)}s. next i'll review it.`);
|
|
295
316
|
|
|
296
317
|
// REVIEW
|
|
297
|
-
console.log('\n[3/3] REVIEW — validating...');
|
|
318
|
+
console.log(verbose ? '\n[3/3] REVIEW — validating...' : 'reviewing the change against tests and validate.md.');
|
|
298
319
|
phaseStart = Date.now();
|
|
299
320
|
const reviewOutput = executePhase('review', context, { verbose, timeout });
|
|
300
321
|
timing.review = Date.now() - phaseStart;
|
|
301
322
|
|
|
302
323
|
if (reviewOutput.includes('[REVIEW_FAILED]')) {
|
|
303
|
-
console.log(
|
|
324
|
+
console.log(verbose
|
|
325
|
+
? '⚠ Review found issues. Stopping for manual check.'
|
|
326
|
+
: 'review found issues. stopping so a human can look.');
|
|
304
327
|
cycleTimings.push(timing);
|
|
305
328
|
completedCycles++;
|
|
306
329
|
break;
|
|
307
330
|
}
|
|
308
|
-
console.log(
|
|
331
|
+
console.log(verbose
|
|
332
|
+
? `✓ Review complete (${Math.round(timing.review / 1000)}s)`
|
|
333
|
+
: `review passed in ${Math.round(timing.review / 1000)}s.`);
|
|
309
334
|
|
|
310
335
|
cycleTimings.push(timing);
|
|
311
336
|
completedCycles++;
|
|
312
337
|
|
|
313
338
|
// Self-heal MAP.md refs after each cycle
|
|
314
|
-
console.log(
|
|
339
|
+
console.log(verbose
|
|
340
|
+
? '\n[+] CLEAN — healing MAP.md refs...'
|
|
341
|
+
: 'cleaning up drifted MAP.md refs.');
|
|
315
342
|
try {
|
|
316
343
|
cleanAtris({ dryRun: false });
|
|
317
344
|
} catch (cleanErr) {
|
|
318
|
-
console.log(
|
|
345
|
+
console.log(`${verbose ? '⚠ Clean failed: ' : 'clean failed: '}${cleanErr.message}`);
|
|
319
346
|
}
|
|
320
347
|
|
|
321
348
|
// Auto-push if not disabled
|
|
322
349
|
if (push) {
|
|
323
|
-
console.log('\n[+] PUSH — pushing to remote...');
|
|
350
|
+
console.log(verbose ? '\n[+] PUSH — pushing to remote...' : 'pushing to remote.');
|
|
324
351
|
try {
|
|
325
352
|
execSync('git push', { cwd: process.cwd(), encoding: 'utf8', stdio: 'pipe' });
|
|
326
|
-
console.log('✓ Pushed to remote');
|
|
353
|
+
console.log(verbose ? '✓ Pushed to remote' : 'pushed.');
|
|
327
354
|
} catch (pushErr) {
|
|
328
|
-
console.log(
|
|
355
|
+
console.log(`${verbose ? '⚠ Push failed: ' : 'push failed: '}${pushErr.message.split('\n')[0]}`);
|
|
329
356
|
}
|
|
330
357
|
}
|
|
331
358
|
|
|
332
|
-
console.log(
|
|
359
|
+
console.log(verbose
|
|
360
|
+
? `\n✓ Cycle ${cycle} done`
|
|
361
|
+
: `cycle ${cycle} done. next cycle.`);
|
|
333
362
|
|
|
334
363
|
} catch (err) {
|
|
335
364
|
console.error(`\n✗ Cycle ${cycle} failed: ${err.message}`);
|
|
@@ -343,24 +372,28 @@ async function runAtris(options = {}) {
|
|
|
343
372
|
logRunCompletion(completedCycles, startTime, cycleTimings);
|
|
344
373
|
|
|
345
374
|
console.log('');
|
|
346
|
-
|
|
347
|
-
|
|
375
|
+
if (verbose) {
|
|
376
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
377
|
+
console.log(`Run complete. ${elapsed}s elapsed.`);
|
|
378
|
+
|
|
379
|
+
if (cycleTimings.length > 0) {
|
|
380
|
+
console.log('');
|
|
381
|
+
console.log(' Cycle │ Plan │ Do │ Review');
|
|
382
|
+
console.log(' ───────┼─────────┼─────────┼────────');
|
|
383
|
+
cycleTimings.forEach((t, i) => {
|
|
384
|
+
const p = `${Math.round(t.plan / 1000)}s`.padStart(5);
|
|
385
|
+
const d = `${Math.round(t.do / 1000)}s`.padStart(5);
|
|
386
|
+
const r = `${Math.round(t.review / 1000)}s`.padStart(5);
|
|
387
|
+
console.log(` ${String(i + 1).padStart(2)} │ ${p} │ ${d} │ ${r}`);
|
|
388
|
+
});
|
|
389
|
+
}
|
|
348
390
|
|
|
349
|
-
|
|
350
|
-
|
|
391
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
392
|
+
console.log('');
|
|
393
|
+
} else {
|
|
394
|
+
console.log(`run complete. ${completedCycles} cycle${completedCycles === 1 ? '' : 's'} in ${elapsed}s. logged to today's journal.`);
|
|
351
395
|
console.log('');
|
|
352
|
-
console.log(' Cycle │ Plan │ Do │ Review');
|
|
353
|
-
console.log(' ───────┼─────────┼─────────┼────────');
|
|
354
|
-
cycleTimings.forEach((t, i) => {
|
|
355
|
-
const p = `${Math.round(t.plan / 1000)}s`.padStart(5);
|
|
356
|
-
const d = `${Math.round(t.do / 1000)}s`.padStart(5);
|
|
357
|
-
const r = `${Math.round(t.review / 1000)}s`.padStart(5);
|
|
358
|
-
console.log(` ${String(i + 1).padStart(2)} │ ${p} │ ${d} │ ${r}`);
|
|
359
|
-
});
|
|
360
396
|
}
|
|
361
|
-
|
|
362
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
363
|
-
console.log('');
|
|
364
397
|
}
|
|
365
398
|
|
|
366
399
|
module.exports = { runAtris };
|