atris 3.42.0 → 3.44.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/atris/skills/design/SKILL.md +7 -1
- package/atris/skills/engines/SKILL.md +44 -13
- package/atris/team/customer-lead/MEMBER.md +45 -0
- package/atris/team/customer-lead/SOUL.md +33 -0
- package/atris/team/customer-lead/START_HERE.md +7 -0
- package/atris/team/customer-lead/skills/customer-commitments/SKILL.md +45 -0
- package/atris/team/improver/MEMBER.md +33 -0
- package/bin/atris.js +37 -4
- package/commands/autoland.js +15 -1
- package/commands/caretaker.js +303 -0
- package/commands/clean.js +76 -0
- package/commands/engine-watch.js +212 -0
- package/commands/engine.js +99 -11
- package/commands/founder.js +304 -0
- package/commands/human-missions.js +844 -0
- package/commands/init.js +16 -7
- package/commands/lesson.js +178 -4
- package/commands/mission.js +124 -69
- package/commands/slop.js +34 -3
- package/commands/task.js +51 -4
- package/commands/team.js +329 -13
- package/commands/verify.js +99 -6
- package/commands/worktree.js +119 -4
- package/lib/auto-accept-certified.js +302 -0
- package/lib/cloud-mission.js +59 -2
- package/lib/conductor-artifacts.js +1 -1
- package/lib/dispatch-scout.js +383 -0
- package/lib/engine-ask.js +645 -0
- package/lib/engine-job-lifecycle.js +65 -0
- package/lib/engine-receipt-sweep.js +98 -0
- package/lib/engine-registry.js +2 -2
- package/lib/engine-validate.js +374 -0
- package/lib/fleet.js +459 -106
- package/lib/known-commands.js +2 -2
- package/lib/lesson-ledger.js +84 -0
- package/lib/member-alive.js +2 -2
- package/lib/policy-lessons.js +70 -0
- package/lib/receipt-evidence.js +56 -1
- package/lib/runner-command.js +1 -1
- package/lib/secret-gateway.js +588 -0
- package/lib/team-presence.js +13 -1
- package/lib/voice-gate.js +6 -0
- package/lib/wish-audit.js +5 -205
- package/lib/wish-delegate.js +5 -2
- package/package.json +6 -1
package/lib/known-commands.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const knownCommands = ['init', 'log', 'logs', 'wish', 'drill', 'dream', 'now', 'goal', 'wtf', 'orb', 'radar', 'stream', 'ctop', 'launchpad', 'status', 'analytics', 'visualize', 'brain', 'brainstorm', 'autopilot', 'run', '_start', 'plan', 'do', 'review', 'release',
|
|
3
|
+
const knownCommands = ['init', 'log', 'logs', 'wish', 'ask', 'approve', 'stop', 'ready', 'check', 'drill', 'dream', 'now', 'goal', 'wtf', 'founder', 'orb', 'radar', 'stream', 'ctop', 'launchpad', 'status', 'analytics', 'visualize', 'brain', 'brainstorm', 'autopilot', 'run', '_start', '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', 'receipt', 'proof', 'openclaw', 'pull', 'push', 'watch', 'cloud', 'live', 'align', 'terminal', 'computer', 'diff', 'business', 'sync', 'youtube',
|
|
6
|
-
'ingest', 'query', 'lint', 'loop', 'pulse', 'task', 'mission', 'decide', 'agents', 'probe', 'worktree', 'land', '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', 'sync-checkout',
|
|
6
|
+
'ingest', 'query', 'lint', 'loop', 'pulse', 'task', 'mission', 'decide', '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', 'sync-checkout',
|
|
7
7
|
'github', 'vercel', 'supabase', 'linear', 'stripe', 'gmail', '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', 'feed', 'brief'];
|
|
9
9
|
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
const { spawnSync } = require('child_process');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Lesson contract validation + append-only mutation ledger.
|
|
8
|
+
*
|
|
9
|
+
* Every lesson mutation (add / resolve / revert) lands one JSONL record in
|
|
10
|
+
* .atris/state/lesson_ledger.jsonl with evidence and an id, so self-improvement
|
|
11
|
+
* edits are auditable and reversible instead of silent sidecar rewrites. The
|
|
12
|
+
* ledger is append-only: revert writes a new record, it never deletes history.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const LEDGER_REL = path.join('.atris', 'state', 'lesson_ledger.jsonl');
|
|
16
|
+
|
|
17
|
+
function ledgerPath(root) {
|
|
18
|
+
return path.join(root, LEDGER_REL);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Validate a detector command at write time. A detector is a shell command
|
|
23
|
+
* whose exit code is the lesson's falsifiable state (0 = bug gone). At add
|
|
24
|
+
* time either exit code is fine (the bug usually still exists) but the
|
|
25
|
+
* command itself must run: empty strings, missing binaries (127), and
|
|
26
|
+
* non-executable targets (126) are rejected so a typo can't masquerade as a
|
|
27
|
+
* forever-failing detector.
|
|
28
|
+
* @returns {{ ok: boolean, exitCode?: number, reason?: string }}
|
|
29
|
+
*/
|
|
30
|
+
function validateDetector(cmd, cwd, timeoutMs = 10000) {
|
|
31
|
+
if (!cmd || typeof cmd !== 'string' || !cmd.trim()) {
|
|
32
|
+
return { ok: false, reason: 'detector is empty' };
|
|
33
|
+
}
|
|
34
|
+
const res = spawnSync(cmd, { shell: true, cwd, timeout: timeoutMs, stdio: 'pipe' });
|
|
35
|
+
if (res.error) return { ok: false, reason: res.error.message };
|
|
36
|
+
if (res.signal) return { ok: false, reason: `detector killed by ${res.signal} (timeout?)` };
|
|
37
|
+
if (res.status === 127) return { ok: false, exitCode: 127, reason: 'command not found (exit 127)' };
|
|
38
|
+
if (res.status === 126) return { ok: false, exitCode: 126, reason: 'command not executable (exit 126)' };
|
|
39
|
+
return { ok: true, exitCode: res.status };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Append one mutation record. Adds id + ts; returns the full record.
|
|
44
|
+
* @param {string} root
|
|
45
|
+
* @param {{ action: 'add'|'resolve'|'revert', slug: string, evidence?: string, outcome?: string }} entry
|
|
46
|
+
*/
|
|
47
|
+
function appendLedgerEntry(root, entry) {
|
|
48
|
+
const file = ledgerPath(root);
|
|
49
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
50
|
+
const record = {
|
|
51
|
+
id: `ll-${Date.now().toString(36)}-${crypto.randomBytes(3).toString('hex')}`,
|
|
52
|
+
ts: new Date().toISOString(),
|
|
53
|
+
...entry,
|
|
54
|
+
};
|
|
55
|
+
fs.appendFileSync(file, JSON.stringify(record) + '\n');
|
|
56
|
+
return record;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Read ledger records, oldest first. Malformed lines are skipped, not fatal:
|
|
61
|
+
* a half-written line from a crashed process must not brick the view.
|
|
62
|
+
* @param {string} root
|
|
63
|
+
* @param {{ limit?: number, slug?: string }} [options]
|
|
64
|
+
*/
|
|
65
|
+
function readLedger(root, options = {}) {
|
|
66
|
+
const file = ledgerPath(root);
|
|
67
|
+
if (!fs.existsSync(file)) return [];
|
|
68
|
+
const records = [];
|
|
69
|
+
for (const line of fs.readFileSync(file, 'utf8').split('\n')) {
|
|
70
|
+
if (!line.trim()) continue;
|
|
71
|
+
try {
|
|
72
|
+
records.push(JSON.parse(line));
|
|
73
|
+
} catch {
|
|
74
|
+
// skip torn line
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const filtered = options.slug ? records.filter((r) => r.slug === options.slug) : records;
|
|
78
|
+
if (options.limit && filtered.length > options.limit) {
|
|
79
|
+
return filtered.slice(filtered.length - options.limit);
|
|
80
|
+
}
|
|
81
|
+
return filtered;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = { ledgerPath, validateDetector, appendLedgerEntry, readLedger };
|
package/lib/member-alive.js
CHANGED
|
@@ -84,7 +84,7 @@ function runMemberOperateScript(scriptPath, member, opts) {
|
|
|
84
84
|
args.push('--execute');
|
|
85
85
|
if (opts.confirmed) args.push('--confirm-autonomy-policy');
|
|
86
86
|
}
|
|
87
|
-
if (opts.agent
|
|
87
|
+
if (opts.agent) args.push('--agent', String(opts.agent));
|
|
88
88
|
if (opts.model) args.push('--model', String(opts.model));
|
|
89
89
|
if (opts.noPrime) args.push('--no-prime');
|
|
90
90
|
// Inherit stderr so member-operate progress lines reach the parent alive/loop process live.
|
|
@@ -194,7 +194,7 @@ function runAliveTick(name, opts = {}) {
|
|
|
194
194
|
execute: true,
|
|
195
195
|
confirmed: true,
|
|
196
196
|
maxWallSeconds: opts.maxWallSeconds,
|
|
197
|
-
agent: opts.agent ||
|
|
197
|
+
agent: opts.agent || null,
|
|
198
198
|
model: opts.model || null,
|
|
199
199
|
noPrime: true,
|
|
200
200
|
});
|
package/lib/policy-lessons.js
CHANGED
|
@@ -16,6 +16,7 @@ const POLICY_LESSONS_FILE = path.join('.atris', 'state', 'policy_lessons.json');
|
|
|
16
16
|
const CAREER_XP_RECEIPTS_FILE = path.join('.atris', 'state', 'career_xp_receipts.jsonl');
|
|
17
17
|
const TASK_EPISODES_FILE = path.join('.atris', 'state', 'task_episodes.jsonl');
|
|
18
18
|
const SCORECARDS_FILE = path.join('.atris', 'state', 'scorecards.jsonl');
|
|
19
|
+
const TYPED_LESSONS_FILE = path.join('atris', 'lessons.json');
|
|
19
20
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
20
21
|
|
|
21
22
|
// Review actors that are agents, not the human gate. Mining must split the
|
|
@@ -266,6 +267,74 @@ function policyHintsForProof(proofText, mined, root = process.cwd()) {
|
|
|
266
267
|
return hints;
|
|
267
268
|
}
|
|
268
269
|
|
|
270
|
+
function normalizeLessonPath(value) {
|
|
271
|
+
return String(value || '')
|
|
272
|
+
.trim()
|
|
273
|
+
.replace(/\\/g, '/')
|
|
274
|
+
.replace(/^\.\//, '')
|
|
275
|
+
.replace(/\/{2,}/g, '/')
|
|
276
|
+
.replace(/\/$/, '');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function lessonPathOverlaps(left, right) {
|
|
280
|
+
const lessonPath = normalizeLessonPath(left).toLowerCase();
|
|
281
|
+
const candidatePath = normalizeLessonPath(right).toLowerCase();
|
|
282
|
+
if (!lessonPath || !candidatePath) return false;
|
|
283
|
+
return lessonPath === candidatePath
|
|
284
|
+
|| lessonPath.startsWith(`${candidatePath}/`)
|
|
285
|
+
|| candidatePath.startsWith(`${lessonPath}/`);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function readTypedPolicyLessons(root) {
|
|
289
|
+
try {
|
|
290
|
+
const parsed = JSON.parse(fs.readFileSync(path.join(root, TYPED_LESSONS_FILE), 'utf8'));
|
|
291
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return [];
|
|
292
|
+
return Object.entries(parsed)
|
|
293
|
+
.filter(([id, lesson]) => id !== '_schema' && lesson && typeof lesson === 'object')
|
|
294
|
+
.map(([id, lesson]) => ({ id, ...lesson, source: 'typed_lessons' }));
|
|
295
|
+
} catch {
|
|
296
|
+
return [];
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// A lesson is relevant to a landing only when its declared file scope overlaps
|
|
301
|
+
// the candidate's exact paths. Repo-wide or prose-only lessons remain advice,
|
|
302
|
+
// because running them as global blockers would make old debt stop new work.
|
|
303
|
+
function policyLessonsForFiles(files, mined, root = process.cwd()) {
|
|
304
|
+
const candidatePaths = (Array.isArray(files) ? files : [files])
|
|
305
|
+
.map(normalizeLessonPath)
|
|
306
|
+
.filter(Boolean);
|
|
307
|
+
if (!candidatePaths.length) return [];
|
|
308
|
+
const minedLessons = mined && Array.isArray(mined.lessons)
|
|
309
|
+
? mined.lessons.map((lesson) => ({ ...lesson, source: 'mined_policy' }))
|
|
310
|
+
: [];
|
|
311
|
+
// The typed sidecar is the promoted source. If a mined advisory later uses
|
|
312
|
+
// the same id, the promoted detector and file scope must win.
|
|
313
|
+
const combined = [...readTypedPolicyLessons(root), ...minedLessons];
|
|
314
|
+
const matches = [];
|
|
315
|
+
const seen = new Set();
|
|
316
|
+
for (const lesson of combined) {
|
|
317
|
+
const id = String(lesson && lesson.id || '').trim();
|
|
318
|
+
if (!id || seen.has(id)) continue;
|
|
319
|
+
const appliesTo = Array.isArray(lesson.applies_to)
|
|
320
|
+
? lesson.applies_to
|
|
321
|
+
: (Array.isArray(lesson.paths) ? lesson.paths : []);
|
|
322
|
+
const matchedPath = appliesTo.find((lessonPath) =>
|
|
323
|
+
candidatePaths.some((candidatePath) => lessonPathOverlaps(lessonPath, candidatePath)));
|
|
324
|
+
if (!matchedPath) continue;
|
|
325
|
+
seen.add(id);
|
|
326
|
+
matches.push({
|
|
327
|
+
id,
|
|
328
|
+
detector: typeof lesson.detector === 'string' ? lesson.detector.trim() : '',
|
|
329
|
+
lesson: String(lesson.lesson || lesson.text || '').trim(),
|
|
330
|
+
status: lesson.status || null,
|
|
331
|
+
source: lesson.source,
|
|
332
|
+
matched_path: normalizeLessonPath(matchedPath),
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
return matches;
|
|
336
|
+
}
|
|
337
|
+
|
|
269
338
|
function policyLessonsPath(root) {
|
|
270
339
|
return path.join(root, POLICY_LESSONS_FILE);
|
|
271
340
|
}
|
|
@@ -324,6 +393,7 @@ module.exports = {
|
|
|
324
393
|
loadHistory,
|
|
325
394
|
mineProofPolicy,
|
|
326
395
|
policyHintsForProof,
|
|
396
|
+
policyLessonsForFiles,
|
|
327
397
|
readPolicyLessons,
|
|
328
398
|
writePolicyLessons,
|
|
329
399
|
syncLessonsMd,
|
package/lib/receipt-evidence.js
CHANGED
|
@@ -122,4 +122,59 @@ function extractReceiptEvidence(proofText, root = process.cwd()) {
|
|
|
122
122
|
};
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
|
|
125
|
+
// Resume short-circuit: when a tick already wrote its receipt but the mission
|
|
126
|
+
// record never advanced (interrupt between writeReceipt and saveMission), the
|
|
127
|
+
// next run would re-derive the same tick_index. Look up that completed receipt
|
|
128
|
+
// and reuse it instead of re-running the step.
|
|
129
|
+
function findCachedMissionStepReceipt(root, { missionId, tickIndex } = {}) {
|
|
130
|
+
const id = String(missionId || '').trim();
|
|
131
|
+
const idx = Number(tickIndex);
|
|
132
|
+
if (!id || !Number.isInteger(idx) || idx < 1) return null;
|
|
133
|
+
|
|
134
|
+
const runsDir = path.resolve(root, 'atris', 'runs');
|
|
135
|
+
let names;
|
|
136
|
+
try {
|
|
137
|
+
names = fs.readdirSync(runsDir).filter((name) => name.startsWith('mission-') && name.endsWith('.json'));
|
|
138
|
+
} catch {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
let best = null;
|
|
143
|
+
for (const name of names) {
|
|
144
|
+
let parsed = null;
|
|
145
|
+
try {
|
|
146
|
+
parsed = JSON.parse(fs.readFileSync(path.join(runsDir, name), 'utf8'));
|
|
147
|
+
} catch {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (!parsed || parsed.mission_id !== id) continue;
|
|
151
|
+
const tick = parsed.result && parsed.result.tick;
|
|
152
|
+
if (!tick || Number(tick.tick_index) !== idx) continue;
|
|
153
|
+
// Only completed successful steps are safe to skip. Failed ticks stay
|
|
154
|
+
// eligible for a fresh attempt on resume.
|
|
155
|
+
if (tick.status !== 'ran') continue;
|
|
156
|
+
const atMs = Date.parse(tick.finished_at || parsed.at || '') || 0;
|
|
157
|
+
if (best && atMs < best.atMs) continue;
|
|
158
|
+
best = {
|
|
159
|
+
atMs,
|
|
160
|
+
receipt_path: path.join('atris', 'runs', name),
|
|
161
|
+
receipt: parsed,
|
|
162
|
+
tick,
|
|
163
|
+
verifier_result: parsed.result.verifier_result || null,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
if (!best) return null;
|
|
167
|
+
return {
|
|
168
|
+
receipt_path: best.receipt_path,
|
|
169
|
+
receipt: best.receipt,
|
|
170
|
+
tick: best.tick,
|
|
171
|
+
verifier_result: best.verifier_result,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
module.exports = {
|
|
176
|
+
extractReceiptEvidence,
|
|
177
|
+
receiptVerifierPassed,
|
|
178
|
+
findCachedMissionStepReceipt,
|
|
179
|
+
RECEIPT_PATH_PATTERN,
|
|
180
|
+
};
|
package/lib/runner-command.js
CHANGED