atris 3.79.0 → 3.80.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 +6 -0
- package/atris/atris.md +66 -167
- package/atris/skills/security-audit/SKILL.md +77 -0
- package/atris/team/brainstormer/MEMBER.md +2 -2
- package/atris.md +66 -167
- package/bin/atris.js +6 -0
- package/commands/business.js +44 -1984
- package/commands/member.js +189 -966
- package/commands/mission.js +229 -1409
- package/commands/pack.js +112 -1476
- package/commands/security-review.js +125 -0
- package/commands/task.js +137 -1359
- package/lib/business-core.js +2022 -0
- package/lib/engine-ask.js +1 -0
- package/lib/handoff-wake.js +8 -6
- package/lib/known-commands.js +1 -1
- package/lib/member-autoland.js +21 -2
- package/lib/member-board.js +12 -10
- package/lib/member-core.js +1014 -0
- package/lib/member-up.js +46 -3
- package/lib/mission-core.js +1559 -0
- package/lib/pack-core.js +1588 -0
- package/lib/security-audit-classes.js +300 -0
- package/lib/security-audit.js +1519 -0
- package/lib/task-core.js +1412 -0
- package/lib/task-decision.js +66 -0
- package/lib/task-explanation.js +20 -0
- package/lib/yes-lock.js +1 -1
- package/package.json +1 -1
package/lib/engine-ask.js
CHANGED
package/lib/handoff-wake.js
CHANGED
|
@@ -25,7 +25,7 @@ const { DEFAULT_MAX } = require('./member-up');
|
|
|
25
25
|
// wakes, so the next handoff counts it even before its mission says running.
|
|
26
26
|
const LOCK_FILE = 'handoff-wake.lock';
|
|
27
27
|
const RECENT_FILE = 'handoff-wakes.json';
|
|
28
|
-
//
|
|
28
|
+
// Malformed locks get this grace period; a live owner always retains its lock.
|
|
29
29
|
const STALE_LOCK_MS = 5 * 60 * 1000;
|
|
30
30
|
const WAIT_LOCK_MS = 3 * 60 * 1000;
|
|
31
31
|
const RECENT_MS = 30 * 60 * 1000;
|
|
@@ -51,16 +51,18 @@ function takeLock(file, { staleMs, waitMs, now }) {
|
|
|
51
51
|
}
|
|
52
52
|
try {
|
|
53
53
|
const stat = fs.statSync(file);
|
|
54
|
+
const raw = fs.readFileSync(file, 'utf8');
|
|
54
55
|
let owner = null;
|
|
55
|
-
try { owner = JSON.parse(
|
|
56
|
+
try { owner = JSON.parse(raw); } catch (error) { if (!(error instanceof SyntaxError)) throw error; }
|
|
56
57
|
const dead = owner && Number.isInteger(owner.pid) && !pidAlive(owner.pid);
|
|
57
58
|
const old = now() - stat.mtimeMs > staleMs;
|
|
58
|
-
if ((dead || old) && fs.statSync(file).ino === stat.ino) {
|
|
59
|
+
if ((dead || (!Number.isInteger(owner?.pid) && old)) && fs.statSync(file).ino === stat.ino) {
|
|
59
60
|
fs.unlinkSync(file);
|
|
60
61
|
continue;
|
|
61
62
|
}
|
|
62
|
-
} catch {
|
|
63
|
-
continue;
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (error.code === 'ENOENT') continue;
|
|
65
|
+
throw error;
|
|
64
66
|
}
|
|
65
67
|
if (now() >= until) return null;
|
|
66
68
|
sleepSync(100);
|
|
@@ -68,7 +70,7 @@ function takeLock(file, { staleMs, waitMs, now }) {
|
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
// Runs fn() while holding an exclusive lock file. A holder that died, or
|
|
71
|
-
//
|
|
73
|
+
// malformed lock older than staleMs, is cleared. Throws when the lock stays held past
|
|
72
74
|
// waitMs, so a caller never writes without it.
|
|
73
75
|
function withFileLock(lockFile, fn, { staleMs = STALE_LOCK_MS, waitMs = WAIT_LOCK_MS, now = Date.now } = {}) {
|
|
74
76
|
fs.mkdirSync(path.dirname(lockFile), { recursive: true });
|
package/lib/known-commands.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const knownCommands = ['init', 'guide', 'doctor', 'doc-health', 'install', 'workspace', 'log', 'logs', 'later', 'idea', 'done', 'wish', 'ask', 'approve', 'stop', 'ready', 'check', 'drill', 'dream', 'now', '99', 'goal', 'wtf', 'founder', 'orb', 'radar', 'who', 'stream', 'ctop', 'launchpad', 'status', 'analytics', 'revisions', 'visualize', 'brain', 'brainstorm', 'autopilot', 'run', 'plan', 'do', 'review', 'release',
|
|
4
4
|
'activate', '_activate', 'agent', 'team', 'chat', 'fast', 'ax', 'console', 'serve', 'login', 'logout', 'whoami', 'switch', 'use', 'accounts', '_resolve', '_profile-email', '_switch-session', 'shell-init', 'update', 'upgrade', 'version', 'help', 'next', 'atris',
|
|
5
5
|
'clean', 'close', 'harvest', 'verify', 'recover', 'search', 'scout', 'skill', 'member', 'codex-goal', 'app', 'apps', 'learn', 'lesson', 'taste', 'teach', 'plugin', 'experiments', 'bench', 'router', 'tree', 'receipt', 'proof', 'openclaw', 'pull', 'push', 'watch', 'cloud', 'live', 'align', 'terminal', 'computer', 'diff', 'business', 'sync', 'youtube', 'x-search',
|
|
6
|
-
'ingest', 'query', 'lint', 'loop', 'pulse', 'task', 'mission', 'decide', 'campaign', 'agents', 'probe', 'worktree', 'land', 'caretaker', 'autoland', 'drive', 'aeo', 'slop', 'voice', 'strings', 'write', 'security-review', 'secure', 'deck', 'site', 'theme', 'card', 'reel', 'improve', 'study', 'rainmaker', 'xp', 'play', 'gm', 'game', 'x', 'recap', 'report', 'signup', 'clarity', 'interview', 'meet', 'moves', 'unknowns', 'avail', 'scoreboard', 'scorecard', 'rating', 'sync-checkout', 'zen',
|
|
6
|
+
'ingest', 'query', 'lint', 'loop', 'pulse', 'task', 'mission', 'decide', 'campaign', 'agents', 'probe', 'worktree', 'land', 'caretaker', 'autoland', 'drive', 'aeo', 'slop', 'voice', 'strings', 'write', 'security-review', 'secure', 'sentinel', 'deck', 'site', 'theme', 'card', 'reel', 'improve', 'study', 'rainmaker', 'xp', 'play', 'gm', 'game', 'x', 'recap', 'report', 'signup', 'clarity', 'interview', 'meet', 'moves', 'unknowns', 'avail', 'scoreboard', 'scorecard', 'rating', 'sync-checkout', 'zen',
|
|
7
7
|
'ci', 'github', 'vercel', 'supabase', 'linear', 'stripe', 'balance', 'usage', 'spend', 'api-key', 'agent-client', 'topup', 'buy', 'transactions', 'hire', 'hires', 'mail', 'design', 'mcp', 'gmail', 'google', 'calendar', 'twitter', 'slack', 'imessage', 'integrations', 'setup', 'clean-workspace', 'cw',
|
|
8
8
|
'fork', 'browse', 'publish', 'pack', 'sleep', 'wake', 'feedback', 'errors', 'wiki', 'code-review', 'cr', 'soul', 'fleet', 'fleet-report', 'loops', 'self-improve', 'compile', 'spaceship', 'truth', 'sign', 'engine', 'engines', 'link', 'playbook', 'feed', 'brief', 'social', 'host', 'people', 'follow', 'unfollow', 'friends', 'msg', 'inbox', 'invite', 'join', 'rsi', 'pay', 'trust'];
|
|
9
9
|
|
package/lib/member-autoland.js
CHANGED
|
@@ -176,7 +176,7 @@ function latestRunReceipt(missionId, cwd) {
|
|
|
176
176
|
// mission's own state and no commit made after it. Every tick in that run
|
|
177
177
|
// must have passed its check and a verdict from a reviewer engine other than
|
|
178
178
|
// the member and every engine that did the work.
|
|
179
|
-
function reviewHold(mission, workerEngine, cwd, pin = {}) {
|
|
179
|
+
function reviewHold(mission, workerEngine, cwd, pin = {}, doneLooksLikes = []) {
|
|
180
180
|
const receipt = latestRunReceipt(mission.id, cwd);
|
|
181
181
|
if (!receipt) return 'no run receipt for this mission could be read';
|
|
182
182
|
const result = receipt.result && typeof receipt.result === 'object' ? receipt.result : {};
|
|
@@ -220,6 +220,18 @@ function reviewHold(mission, workerEngine, cwd, pin = {}) {
|
|
|
220
220
|
if (!reviewer || self.has(reviewer)) return 'the latest run was reviewed by the same engine that did the work';
|
|
221
221
|
}
|
|
222
222
|
if (lastTick.reviewed_git_tree !== head.stdout) return 'the review did not see the code that is checked out now';
|
|
223
|
+
const doneLines = [
|
|
224
|
+
...(Array.isArray(doneLooksLikes) ? doneLooksLikes : []),
|
|
225
|
+
mission.done_looks_like,
|
|
226
|
+
].map((line) => String(line || '').trim()).filter(Boolean);
|
|
227
|
+
if (doneLines.length) {
|
|
228
|
+
const { missingDoneLooksLikeClaim } = require('../commands/mission');
|
|
229
|
+
const reviewOutput = String(lastTick.referee_result?.output || '');
|
|
230
|
+
for (const doneLine of doneLines) {
|
|
231
|
+
const missing = missingDoneLooksLikeClaim(doneLine, { cwd, reviewOutput });
|
|
232
|
+
if (missing) return `missing: ${missing}`;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
223
235
|
pin.tree = head.stdout;
|
|
224
236
|
return null;
|
|
225
237
|
}
|
|
@@ -459,6 +471,7 @@ function autolandMemberRun({ mission, cwd, summary = '', workerEngine = '', load
|
|
|
459
471
|
if (missionLane) return held('protected', `this mission sits in a protected area (${missionLane})`);
|
|
460
472
|
const taskIds = linkedTaskIds(mission);
|
|
461
473
|
if (!taskIds) return held('protected', 'the mission\'s linked tasks are not a clean list of ids');
|
|
474
|
+
const doneLooksLikes = [];
|
|
462
475
|
for (const id of taskIds) {
|
|
463
476
|
let task = null;
|
|
464
477
|
try {
|
|
@@ -475,12 +488,18 @@ function autolandMemberRun({ mission, cwd, summary = '', workerEngine = '', load
|
|
|
475
488
|
if (approvals.some((value) => ['revise', 'revised', 'rejected'].includes(String(value || '').trim().toLowerCase()))) {
|
|
476
489
|
return held('approval', 'a linked task was turned down; it needs fresh proof before it can go to github');
|
|
477
490
|
}
|
|
491
|
+
try {
|
|
492
|
+
const explanation = require('./task-explanation').taskExplanation(task);
|
|
493
|
+
if (explanation.sources?.done_looks_like === 'explicit' && explanation.done_looks_like) {
|
|
494
|
+
doneLooksLikes.push(explanation.done_looks_like);
|
|
495
|
+
}
|
|
496
|
+
} catch {}
|
|
478
497
|
}
|
|
479
498
|
|
|
480
499
|
const unsaved = unsavedHold(cwd);
|
|
481
500
|
if (unsaved) return held('unsaved', unsaved);
|
|
482
501
|
const pin = {};
|
|
483
|
-
const reviewGap = reviewHold(mission, workerEngine, cwd, pin);
|
|
502
|
+
const reviewGap = reviewHold(mission, workerEngine, cwd, pin, doneLooksLikes);
|
|
484
503
|
if (reviewGap) return held('review', reviewGap);
|
|
485
504
|
// Pin the commit the review passed on. Only this commit (or a map-only
|
|
486
505
|
// merge built on it) is pushed and merged, so a commit made after this
|
package/lib/member-board.js
CHANGED
|
@@ -261,9 +261,9 @@ function progressEvent(member, raw) {
|
|
|
261
261
|
// so a line half written before the stream started still arrives once done;
|
|
262
262
|
// files that appear later are read from the top. scan() is sync, so watch
|
|
263
263
|
// and poll can both call it without racing.
|
|
264
|
-
function afterLastNewline(file) {
|
|
264
|
+
function afterLastNewline(file, onError = () => {}) {
|
|
265
265
|
let fd;
|
|
266
|
-
try { fd = fs.openSync(file, 'r'); } catch { return 0; }
|
|
266
|
+
try { fd = fs.openSync(file, 'r'); } catch (error) { onError(`open ${file}`, error); return 0; }
|
|
267
267
|
try {
|
|
268
268
|
const buf = Buffer.alloc(64 * 1024);
|
|
269
269
|
for (let end = fs.fstatSync(fd).size; end > 0;) {
|
|
@@ -274,14 +274,16 @@ function afterLastNewline(file) {
|
|
|
274
274
|
end = start;
|
|
275
275
|
}
|
|
276
276
|
return 0;
|
|
277
|
-
} catch { return 0; } finally { fs.closeSync(fd); }
|
|
277
|
+
} catch (error) { onError(`read ${file}`, error); return 0; } finally { fs.closeSync(fd); }
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
-
function followBoard({ dir = progressDir(), emit, pollMs = 1000, orderByTime = false } = {}) {
|
|
280
|
+
function followBoard({ dir = progressDir(), emit, pollMs = 1000, orderByTime = false, onError = () => {} } = {}) {
|
|
281
281
|
const offsets = new Map();
|
|
282
|
-
|
|
283
|
-
const
|
|
284
|
-
|
|
282
|
+
let directorySeen = false;
|
|
283
|
+
const report = (context, error) => { if (error.code !== 'ENOENT' || directorySeen) onError(context, error); };
|
|
284
|
+
const sizeOf = (file) => { try { return fs.statSync(file).size; } catch (error) { report(`stat ${file}`, error); return null; } };
|
|
285
|
+
const list = () => { try { const files = fs.readdirSync(dir); directorySeen = true; return files.filter((f) => f.endsWith('.log')); } catch (error) { report(`list ${dir}`, error); return []; } };
|
|
286
|
+
for (const f of list()) offsets.set(f, afterLastNewline(path.join(dir, f), report));
|
|
285
287
|
|
|
286
288
|
function readNew(f, events) {
|
|
287
289
|
const file = path.join(dir, f);
|
|
@@ -297,7 +299,7 @@ function followBoard({ dir = progressDir(), emit, pollMs = 1000, orderByTime = f
|
|
|
297
299
|
buf = Buffer.alloc(size - from);
|
|
298
300
|
buf = buf.subarray(0, fs.readSync(fd, buf, 0, buf.length, from));
|
|
299
301
|
} finally { fs.closeSync(fd); }
|
|
300
|
-
} catch { return; }
|
|
302
|
+
} catch (error) { report(`read ${file}`, error); return; }
|
|
301
303
|
// Stop at the last newline; a half-written tail waits for the next scan.
|
|
302
304
|
const end = buf.lastIndexOf(0x0a);
|
|
303
305
|
if (end < 0) { offsets.set(f, from); return; }
|
|
@@ -316,8 +318,8 @@ function followBoard({ dir = progressDir(), emit, pollMs = 1000, orderByTime = f
|
|
|
316
318
|
if (!watcher) {
|
|
317
319
|
try {
|
|
318
320
|
watcher = fs.watch(dir, () => scan());
|
|
319
|
-
watcher.on('error', () => { try { watcher.close(); } catch {} watcher = null; });
|
|
320
|
-
} catch { watcher = null; }
|
|
321
|
+
watcher.on('error', (error) => { report(`watch ${dir}`, error); try { watcher.close(); } catch (closeError) { report(`close watcher ${dir}`, closeError); } watcher = null; });
|
|
322
|
+
} catch (error) { report(`watch ${dir}`, error); watcher = null; }
|
|
321
323
|
}
|
|
322
324
|
const events = [];
|
|
323
325
|
for (const f of list()) readNew(f, events);
|