atris 3.30.8 → 3.31.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/AGENTS.md +16 -3
- package/FOR_AGENTS.md +81 -0
- package/README.md +4 -2
- package/atris/atris.md +51 -19
- package/atris/skills/README.md +1 -0
- package/atris/skills/blocks/SKILL.md +134 -0
- package/atris/skills/clawhub/philosophy-of-work/SKILL.md +56 -0
- package/atris/skills/youtube/SKILL.md +31 -11
- package/atris.md +7 -0
- package/ax +367 -93
- package/bin/atris.js +317 -155
- package/commands/autoland.js +319 -0
- package/commands/autopilot.js +94 -4
- package/commands/business.js +1 -1
- package/commands/clean.js +72 -9
- package/commands/codex-goal.js +72 -22
- package/commands/computer.js +48 -3
- package/commands/gm.js +1 -1
- package/commands/harvest.js +179 -0
- package/commands/init.js +1 -1
- package/commands/land.js +442 -0
- package/commands/loop-front.js +122 -4
- package/commands/member.js +519 -19
- package/commands/mission.js +3659 -282
- package/commands/play.js +1 -1
- package/commands/pulse.js +65 -7
- package/commands/run.js +3 -3
- package/commands/strings.js +301 -0
- package/commands/task.js +575 -102
- package/commands/truth.js +170 -0
- package/commands/xp.js +32 -8
- package/commands/youtube.js +72 -5
- package/decks/README.md +6 -12
- package/lib/auto-accept-certified.js +10 -0
- package/lib/autoland.js +283 -0
- package/lib/context-gatherer.js +0 -8
- package/lib/mission-artifact.js +504 -0
- package/lib/mission-room.js +846 -0
- package/lib/mission-runtime-loop.js +320 -0
- package/lib/next-moves.js +212 -6
- package/lib/pulse.js +74 -1
- package/lib/runner-command.js +20 -8
- package/lib/runs-prune.js +242 -0
- package/lib/task-proof.js +1 -1
- package/package.json +3 -3
- package/decks/atris-seed-pitch-v3.json +0 -118
- package/decks/atris-seed-pitch-v4-skeleton.json +0 -106
- package/decks/atris-seed-pitch-v5.json +0 -109
- package/decks/atris-seed-pitch-v6.json +0 -137
- package/decks/atris-seed-pitch-v7.json +0 -133
- package/decks/mark-pincus-narrative.json +0 -102
- package/decks/mark-pincus-sourcery.json +0 -94
- package/decks/yash-applied-compute-detailed.json +0 -150
- package/decks/yash-applied-compute-generalist.json +0 -82
- package/decks/yash-applied-compute-narrative.json +0 -54
- package/lib/ax-chat-input.js +0 -164
- package/lib/ax-goal.js +0 -307
- package/lib/ax-prefs.js +0 -63
- package/lib/ax-shimmer.js +0 -63
package/lib/pulse.js
CHANGED
|
@@ -85,6 +85,21 @@ function buildPulseScorecardRow(input = {}) {
|
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
function buildInterruptedPulseReceipt(input = {}) {
|
|
89
|
+
const signal = input.signal || 'signal';
|
|
90
|
+
return buildPulseReceipt({
|
|
91
|
+
tickIndex: input.tickIndex,
|
|
92
|
+
phase: 'finished',
|
|
93
|
+
actor: 'pulse_signal',
|
|
94
|
+
actorOk: false,
|
|
95
|
+
actorReason: String(signal).toLowerCase(),
|
|
96
|
+
what: `tick interrupted by ${signal}`,
|
|
97
|
+
elapsedMs: input.startedAt ? Date.now() - input.startedAt : input.elapsedMs,
|
|
98
|
+
prevTickStale: input.prevTickStale,
|
|
99
|
+
reward: -1,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
88
103
|
// The heartbeat's full composition (mirrors the /loop skill): run the due
|
|
89
104
|
// mission to continue an existing goal; if none is due, fall back to an
|
|
90
105
|
// autopilot tick — that path is where proposeCandidateHorizons AUTHORS a new
|
|
@@ -198,6 +213,61 @@ function shellSingleQuote(value) {
|
|
|
198
213
|
return `'${String(value || '').replace(/'/g, "'\\''")}'`;
|
|
199
214
|
}
|
|
200
215
|
|
|
216
|
+
function normalizeCronCadence(value = DEFAULT_CADENCE_CRON) {
|
|
217
|
+
const raw = String(value || DEFAULT_CADENCE_CRON).trim();
|
|
218
|
+
if (!raw) return DEFAULT_CADENCE_CRON;
|
|
219
|
+
if (raw.toLowerCase() === 'hourly') return DEFAULT_CADENCE_CRON;
|
|
220
|
+
if (raw.toLowerCase() === 'daily') return '23 2 * * *';
|
|
221
|
+
if (raw.split(/\s+/).length === 5) return raw;
|
|
222
|
+
|
|
223
|
+
const minutes = raw.match(/^(\d+)\s*(m|min|mins|minute|minutes)$/i);
|
|
224
|
+
if (minutes) {
|
|
225
|
+
const n = Number(minutes[1]);
|
|
226
|
+
if (Number.isInteger(n) && n >= 1 && n <= 59) return `*/${n} * * * *`;
|
|
227
|
+
throw new Error(`invalid cadence "${raw}": minute cadence must be 1m-59m or a 5-field cron`);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const hours = raw.match(/^(\d+)\s*(h|hr|hrs|hour|hours)$/i);
|
|
231
|
+
if (hours) {
|
|
232
|
+
const n = Number(hours[1]);
|
|
233
|
+
if (Number.isInteger(n) && n >= 1 && n <= 23) return `23 */${n} * * *`;
|
|
234
|
+
if (n === 24) return '23 0 * * *';
|
|
235
|
+
throw new Error(`invalid cadence "${raw}": hour cadence must be 1h-24h or a 5-field cron`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
throw new Error(`invalid cadence "${raw}": use 13m, 2h, hourly, daily, or a 5-field cron`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function normalizeExpiryDuration(input = {}) {
|
|
242
|
+
const hasHours = input.hours !== undefined && input.hours !== null && String(input.hours).trim() !== '';
|
|
243
|
+
if (hasHours) {
|
|
244
|
+
const hours = Number(input.hours);
|
|
245
|
+
if (Number.isFinite(hours) && hours > 0) {
|
|
246
|
+
return {
|
|
247
|
+
source: 'hours',
|
|
248
|
+
hours,
|
|
249
|
+
days: null,
|
|
250
|
+
seconds: Math.ceil(hours * 60 * 60),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
throw new Error(`invalid hours "${input.hours}": use a positive number of hours`);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const rawDays = input.days === undefined || input.days === null || String(input.days).trim() === ''
|
|
257
|
+
? 7
|
|
258
|
+
: input.days;
|
|
259
|
+
const days = Number(rawDays);
|
|
260
|
+
if (Number.isFinite(days) && days > 0) {
|
|
261
|
+
return {
|
|
262
|
+
source: 'days',
|
|
263
|
+
hours: null,
|
|
264
|
+
days,
|
|
265
|
+
seconds: Math.ceil(days * 24 * 60 * 60),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
throw new Error(`invalid days "${rawDays}": use a positive number of days`);
|
|
269
|
+
}
|
|
270
|
+
|
|
201
271
|
function runnerEnvAliasExport({ genericName, legacyName, value }) {
|
|
202
272
|
if (!value) return '';
|
|
203
273
|
return [
|
|
@@ -365,7 +435,7 @@ echo "done: $(date -Iseconds) exit=$?" >> "$log"
|
|
|
365
435
|
function buildCrontabLine(opts = {}) {
|
|
366
436
|
const { cron = DEFAULT_CADENCE_CRON, scriptPath, marker = PULSE_MARKER } = opts;
|
|
367
437
|
if (!scriptPath) throw new Error('buildCrontabLine: scriptPath is required');
|
|
368
|
-
return `${cron} ${scriptPath} # ${marker}`;
|
|
438
|
+
return `${normalizeCronCadence(cron)} ${scriptPath} # ${marker}`;
|
|
369
439
|
}
|
|
370
440
|
|
|
371
441
|
module.exports = {
|
|
@@ -382,7 +452,9 @@ module.exports = {
|
|
|
382
452
|
pulseLockDir,
|
|
383
453
|
buildPulseReceipt,
|
|
384
454
|
buildPulseScorecardRow,
|
|
455
|
+
buildInterruptedPulseReceipt,
|
|
385
456
|
scoreTick,
|
|
457
|
+
normalizeExpiryDuration,
|
|
386
458
|
shouldWriteScorecard,
|
|
387
459
|
shouldFallbackToAutopilot,
|
|
388
460
|
findOrphanStarts,
|
|
@@ -398,4 +470,5 @@ module.exports = {
|
|
|
398
470
|
releaseLock,
|
|
399
471
|
buildTickScript,
|
|
400
472
|
buildCrontabLine,
|
|
473
|
+
normalizeCronCadence,
|
|
401
474
|
};
|
package/lib/runner-command.js
CHANGED
|
@@ -3,14 +3,12 @@
|
|
|
3
3
|
// Shared worker-spawn builder for the autonomous loops (missions, autopilot, run).
|
|
4
4
|
//
|
|
5
5
|
// Autonomous ticks must target a LIVE model. Inheriting the CLI's persisted
|
|
6
|
-
// selection is fragile:
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// ATRIS_RUNNER_PROFILE -> legacy ATRIS_CLAUDE_MODEL env ->
|
|
11
|
-
|
|
12
|
-
// from under the loop.
|
|
13
|
-
const DEFAULT_CLAUDE_RUNNER_MODEL = 'opus';
|
|
6
|
+
// selection is fragile: the local Claude Code `opus` alias can resolve to
|
|
7
|
+
// different Opus releases across machines and account rollouts. Pin the default
|
|
8
|
+
// so autonomous runs are reproducible, while keeping explicit per-run/env knobs
|
|
9
|
+
// first in precedence. Precedence: explicit model -> ATRIS_RUNNER_MODEL env ->
|
|
10
|
+
// ATRIS_RUNNER_PROFILE -> legacy ATRIS_CLAUDE_MODEL env -> pinned default.
|
|
11
|
+
const DEFAULT_CLAUDE_RUNNER_MODEL = 'claude-opus-4-8';
|
|
14
12
|
const DEFAULT_CLAUDE_RUNNER_BIN = 'claude';
|
|
15
13
|
const RUNNER_PROFILES = Object.freeze({
|
|
16
14
|
'atris-fast': Object.freeze({
|
|
@@ -97,6 +95,19 @@ function buildRunnerAvailabilityCommand() {
|
|
|
97
95
|
return `command -v ${shellWord(resolveClaudeRunnerBin())}`;
|
|
98
96
|
}
|
|
99
97
|
|
|
98
|
+
function runnerAvailabilityFailureMessage(error) {
|
|
99
|
+
const message = error && error.message ? String(error.message).trim() : '';
|
|
100
|
+
if (message.startsWith('Unknown ATRIS_RUNNER_PROFILE')) {
|
|
101
|
+
return `${message}. Set ATRIS_RUNNER_PROFILE to one of: ${Object.keys(RUNNER_PROFILES).join(', ')}.`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let runnerBin = 'configured runner';
|
|
105
|
+
try {
|
|
106
|
+
runnerBin = resolveClaudeRunnerBin();
|
|
107
|
+
} catch {}
|
|
108
|
+
return `${runnerBin} CLI not found. Set ATRIS_RUNNER_BIN (or legacy ATRIS_CLAUDE_BIN), or install the configured runner first.`;
|
|
109
|
+
}
|
|
110
|
+
|
|
100
111
|
function renderRunnerCommandTemplate(template, { promptFile, allowedTools, model }) {
|
|
101
112
|
const allowedToolsFlag = allowedTools ? `--allowedTools ${shellWord(allowedTools)}` : '';
|
|
102
113
|
const promptFileWord = shellWord(promptFile);
|
|
@@ -152,5 +163,6 @@ module.exports = {
|
|
|
152
163
|
resolveClaudeRunnerBin,
|
|
153
164
|
resolveClaudeRunnerCommandTemplate,
|
|
154
165
|
buildRunnerAvailabilityCommand,
|
|
166
|
+
runnerAvailabilityFailureMessage,
|
|
155
167
|
buildRunnerCommand,
|
|
156
168
|
};
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
function stampIso() {
|
|
7
|
+
return new Date().toISOString();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function toPosixPath(value) {
|
|
11
|
+
return String(value || '').split(path.sep).join('/');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function fileSize(filePath) {
|
|
15
|
+
try {
|
|
16
|
+
return fs.statSync(filePath).size;
|
|
17
|
+
} catch {
|
|
18
|
+
return 0;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function listFiles(dir) {
|
|
23
|
+
const out = [];
|
|
24
|
+
function visit(current) {
|
|
25
|
+
let entries = [];
|
|
26
|
+
try {
|
|
27
|
+
entries = fs.readdirSync(current, { withFileTypes: true });
|
|
28
|
+
} catch {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
for (const entry of entries) {
|
|
32
|
+
const fullPath = path.join(current, entry.name);
|
|
33
|
+
if (entry.isDirectory()) {
|
|
34
|
+
if (entry.name === '_archive') continue;
|
|
35
|
+
visit(fullPath);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (entry.isFile()) out.push(fullPath);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
visit(dir);
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function runPathReferences(root) {
|
|
46
|
+
const stateDir = path.join(root, '.atris', 'state');
|
|
47
|
+
const refs = new Set();
|
|
48
|
+
const pattern = /atris\/runs\/[A-Za-z0-9._~:/@%+=,-]+(?:\/[A-Za-z0-9._~:@%+=,-]+)*/g;
|
|
49
|
+
const scan = (filePath) => {
|
|
50
|
+
let text = '';
|
|
51
|
+
try {
|
|
52
|
+
text = fs.readFileSync(filePath, 'utf8');
|
|
53
|
+
} catch {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
for (const match of text.matchAll(pattern)) {
|
|
57
|
+
refs.add(match[0].replace(/[)"'`.,;]+$/g, ''));
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
if (fs.existsSync(stateDir)) {
|
|
61
|
+
for (const filePath of listFiles(stateDir)) scan(filePath);
|
|
62
|
+
}
|
|
63
|
+
return refs;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function referencedDirs(refs) {
|
|
67
|
+
const dirs = new Set();
|
|
68
|
+
for (const ref of refs) {
|
|
69
|
+
const dir = path.posix.dirname(ref);
|
|
70
|
+
if (dir && dir !== '.' && dir !== 'atris/runs') dirs.add(dir);
|
|
71
|
+
}
|
|
72
|
+
return dirs;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function runEntry(root, filePath, refs, refDirs, nowMs) {
|
|
76
|
+
const rel = toPosixPath(path.relative(root, filePath));
|
|
77
|
+
let stat = null;
|
|
78
|
+
try {
|
|
79
|
+
stat = fs.statSync(filePath);
|
|
80
|
+
} catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
const dir = path.posix.dirname(rel);
|
|
84
|
+
const referenced = refs.has(rel) || refDirs.has(dir);
|
|
85
|
+
return {
|
|
86
|
+
path: rel,
|
|
87
|
+
absolute_path: filePath,
|
|
88
|
+
bytes: stat.size,
|
|
89
|
+
mtime_ms: stat.mtimeMs,
|
|
90
|
+
age_days: Math.max(0, Math.floor((nowMs - stat.mtimeMs) / 86400000)),
|
|
91
|
+
referenced,
|
|
92
|
+
kind: path.extname(filePath).toLowerCase().replace(/^\./, '') || 'file',
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function compactJsonReceipt(filePath) {
|
|
97
|
+
let parsed = null;
|
|
98
|
+
try {
|
|
99
|
+
parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
100
|
+
} catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
schema: parsed.schema || null,
|
|
105
|
+
mission_id: parsed.mission_id || parsed.mission?.id || null,
|
|
106
|
+
objective: parsed.objective || parsed.mission?.objective || null,
|
|
107
|
+
owner: parsed.owner || parsed.mission?.owner || null,
|
|
108
|
+
at: parsed.at || parsed.generated_at || parsed.created_at || null,
|
|
109
|
+
verifier: parsed.verifier || parsed.result?.verifier_result?.command || null,
|
|
110
|
+
passed: parsed.result?.passed === true || parsed.result?.verifier_result?.passed === true || null,
|
|
111
|
+
kind: parsed.result?.kind || parsed.action || null,
|
|
112
|
+
summary: parsed.result?.tick?.summary || parsed.result?.summary || parsed.landing?.happened || null,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function removeEmptyDirs(dir, stopDir) {
|
|
117
|
+
let current = dir;
|
|
118
|
+
while (current && current.startsWith(stopDir) && current !== stopDir) {
|
|
119
|
+
try {
|
|
120
|
+
const entries = fs.readdirSync(current);
|
|
121
|
+
if (entries.length) break;
|
|
122
|
+
fs.rmdirSync(current);
|
|
123
|
+
} catch {
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
current = path.dirname(current);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function pruneRuns(root = process.cwd(), options = {}) {
|
|
131
|
+
const runsDir = options.runsDir || path.join(root, 'atris', 'runs');
|
|
132
|
+
const apply = options.apply === true;
|
|
133
|
+
const archive = options.archive !== false;
|
|
134
|
+
const keepNewest = Number.isFinite(options.keepNewest) ? Math.max(0, Math.floor(options.keepNewest)) : 200;
|
|
135
|
+
const keepDays = Number.isFinite(options.keepDays) ? Math.max(0, Math.floor(options.keepDays)) : 14;
|
|
136
|
+
const nowMs = options.nowMs || Date.now();
|
|
137
|
+
const cutoffMs = nowMs - keepDays * 86400000;
|
|
138
|
+
const refs = runPathReferences(root);
|
|
139
|
+
const refDirs = referencedDirs(refs);
|
|
140
|
+
const files = fs.existsSync(runsDir)
|
|
141
|
+
? listFiles(runsDir).map((filePath) => runEntry(root, filePath, refs, refDirs, nowMs)).filter(Boolean)
|
|
142
|
+
: [];
|
|
143
|
+
const newest = new Set(
|
|
144
|
+
files
|
|
145
|
+
.slice()
|
|
146
|
+
.sort((a, b) => b.mtime_ms - a.mtime_ms || a.path.localeCompare(b.path))
|
|
147
|
+
.slice(0, keepNewest)
|
|
148
|
+
.map((entry) => entry.path),
|
|
149
|
+
);
|
|
150
|
+
const totalBytes = files.reduce((sum, entry) => sum + entry.bytes, 0);
|
|
151
|
+
const decisions = files.map((entry) => {
|
|
152
|
+
const reasons = [];
|
|
153
|
+
if (entry.referenced) reasons.push('referenced');
|
|
154
|
+
if (newest.has(entry.path)) reasons.push('newest');
|
|
155
|
+
if (entry.mtime_ms >= cutoffMs) reasons.push('recent');
|
|
156
|
+
const prune = reasons.length === 0;
|
|
157
|
+
return { ...entry, prune, keep_reasons: reasons };
|
|
158
|
+
});
|
|
159
|
+
const candidates = decisions.filter((entry) => entry.prune);
|
|
160
|
+
const archiveEntries = archive
|
|
161
|
+
? candidates.map((entry) => ({
|
|
162
|
+
path: entry.path,
|
|
163
|
+
bytes: entry.bytes,
|
|
164
|
+
age_days: entry.age_days,
|
|
165
|
+
compact: entry.kind === 'json' ? compactJsonReceipt(entry.absolute_path) : null,
|
|
166
|
+
}))
|
|
167
|
+
: [];
|
|
168
|
+
let manifestPath = null;
|
|
169
|
+
const deleted = [];
|
|
170
|
+
const errors = [];
|
|
171
|
+
if (apply && candidates.length) {
|
|
172
|
+
const archiveDir = path.join(runsDir, '_archive');
|
|
173
|
+
if (archive) {
|
|
174
|
+
fs.mkdirSync(archiveDir, { recursive: true });
|
|
175
|
+
manifestPath = path.join(archiveDir, `prune-${stampIso().replace(/[:.]/g, '-')}.json`);
|
|
176
|
+
fs.writeFileSync(manifestPath, JSON.stringify({
|
|
177
|
+
schema: 'atris.runs_prune_manifest.v1',
|
|
178
|
+
generated_at: stampIso(),
|
|
179
|
+
policy: { keep_newest: keepNewest, keep_days: keepDays },
|
|
180
|
+
pruned_count: candidates.length,
|
|
181
|
+
pruned_bytes: candidates.reduce((sum, entry) => sum + entry.bytes, 0),
|
|
182
|
+
entries: archiveEntries,
|
|
183
|
+
}, null, 2) + '\n', 'utf8');
|
|
184
|
+
}
|
|
185
|
+
for (const entry of candidates) {
|
|
186
|
+
try {
|
|
187
|
+
fs.unlinkSync(entry.absolute_path);
|
|
188
|
+
deleted.push(entry.path);
|
|
189
|
+
removeEmptyDirs(path.dirname(entry.absolute_path), runsDir);
|
|
190
|
+
} catch (error) {
|
|
191
|
+
errors.push({ path: entry.path, error: error.message || String(error) });
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
schema: 'atris.runs_prune_preview.v1',
|
|
197
|
+
action: apply ? 'runs_pruned' : 'runs_prune_preview',
|
|
198
|
+
runs_dir: toPosixPath(path.relative(root, runsDir)) || 'atris/runs',
|
|
199
|
+
applied: apply,
|
|
200
|
+
policy: { keep_newest: keepNewest, keep_days: keepDays, archive },
|
|
201
|
+
total_files: files.length,
|
|
202
|
+
total_bytes: totalBytes,
|
|
203
|
+
referenced_files: decisions.filter((entry) => entry.keep_reasons.includes('referenced')).length,
|
|
204
|
+
recent_files: decisions.filter((entry) => entry.keep_reasons.includes('recent')).length,
|
|
205
|
+
newest_files: decisions.filter((entry) => entry.keep_reasons.includes('newest')).length,
|
|
206
|
+
prune_count: candidates.length,
|
|
207
|
+
prune_bytes: candidates.reduce((sum, entry) => sum + entry.bytes, 0),
|
|
208
|
+
deleted_count: deleted.length,
|
|
209
|
+
manifest_path: manifestPath ? toPosixPath(path.relative(root, manifestPath)) : null,
|
|
210
|
+
candidates: candidates.map((entry) => ({
|
|
211
|
+
path: entry.path,
|
|
212
|
+
bytes: entry.bytes,
|
|
213
|
+
age_days: entry.age_days,
|
|
214
|
+
kind: entry.kind,
|
|
215
|
+
})),
|
|
216
|
+
deleted,
|
|
217
|
+
errors,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function formatBytes(bytes) {
|
|
222
|
+
const value = Number(bytes || 0);
|
|
223
|
+
if (value < 1024) return `${value} B`;
|
|
224
|
+
if (value < 1024 * 1024) return `${(value / 1024).toFixed(1)} KB`;
|
|
225
|
+
return `${(value / (1024 * 1024)).toFixed(1)} MB`;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function runsPruneLines(result) {
|
|
229
|
+
return [
|
|
230
|
+
'Landing:',
|
|
231
|
+
` Changed: ${result.applied ? `Pruned ${result.deleted_count} old run file(s).` : `Found ${result.prune_count} old run file(s) eligible for pruning.`}`,
|
|
232
|
+
` How I checked: Scanned ${result.total_files} files in ${result.runs_dir}; kept referenced, newest, and recent proof.`,
|
|
233
|
+
` Proof: ${result.applied ? (result.manifest_path ? `Manifest saved at ${result.manifest_path}.` : 'No manifest was needed.') : `Would reclaim ${formatBytes(result.prune_bytes)}.`}`,
|
|
234
|
+
` Next: ${result.applied ? 'Run this command on a heartbeat when the run folder grows again.' : 'Run again with --apply to delete the eligible old files.'}`,
|
|
235
|
+
];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
module.exports = {
|
|
239
|
+
pruneRuns,
|
|
240
|
+
runsPruneLines,
|
|
241
|
+
formatBytes,
|
|
242
|
+
};
|
package/lib/task-proof.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const GENERIC_COMPLETION_PROOF_RE = /^(?:done|done now|complete|completed|finished|fixed|handled|ship|shipped|ok|okay|yes|yep|looks good|looks good to me|all set|should be good|works now|approved|approve|lgtm|failed)$/i;
|
|
4
4
|
|
|
5
|
-
const COMMAND_PROOF_RE = /\b(?:npm\s+run|npm\s+test|node\s+--test|node\s+scripts\/|pnpm\b|yarn\b|npx\b|pytest\b|python\s+-m|tsc\b|vite\s+build|git\s+diff\s+--(?:check|exit-code|quiet)|grep\s+-[A-Za-z]*q[A-Za-z]*|rg\s+(?:-\S+\s+)*(?:"[^"]+"|'[^']+'|\S+)\s+(?:\.{0,2}\/|~\/|\/|[\w.-]+\/|[\w.-]+\.[A-Za-z0-9]|\b(?:atris|bin|commands|lib|scripts|src|test)\b)|diff\s+(?:-u|--brief)|cmp\s+-s|curl\b|atris\s+task|\.\/ax\b|ax\s+--|test\s+-s)\b/i;
|
|
5
|
+
const COMMAND_PROOF_RE = /\b(?:npm\s+run|npm\s+test|node\s+--test|node\s+scripts\/|node\s+bin\/atris[.]js\s+clean\s+--dry-run\s+--json|pnpm\b|yarn\b|npx\b|pytest\b|python\s+-m|tsc\b|vite\s+build|git\s+diff\s+--(?:check|exit-code|quiet)|grep\s+-[A-Za-z]*q[A-Za-z]*|rg\s+(?:-\S+\s+)*(?:"[^"]+"|'[^']+'|\S+)\s+(?:\.{0,2}\/|~\/|\/|[\w.-]+\/|[\w.-]+\.[A-Za-z0-9]|\b(?:atris|bin|commands|lib|scripts|src|test)\b)|diff\s+(?:-u|--brief)|cmp\s+-s|curl\b|atris\s+task|\.\/ax\b|ax\s+--|test\s+-s)\b/i;
|
|
6
6
|
const FILE_PROOF_RE = /(?:^|[\s'"`])(?:\.{0,2}\/|~\/|\/Users\/|src\/|scripts\/|atris\/|backend\/|public\/|resources\/|package[.]json|main[.]js|preload[.]js|AGENTXP_PROOF[.]md)[^\s'"`,;)]*/i;
|
|
7
7
|
const PATH_ONLY_PROOF_RE = /(?:^|[\s'"`])(?:\.{0,2}\/|~\/|\/Users\/|\/private\/|\/var\/|atris\/runs\/|\.atris\/state\/)[^\s'"`,;)]+(?:[.](?:json|jsonl|md|log|txt|png|jpg|jpeg|pdf))?/i;
|
|
8
8
|
const RECEIPT_OR_ARTIFACT_RE = /\b(?:receipt|artifact|screenshot|log|trace|path=|file=|bytes=|model=|opened=|https?:\/\/)\b/i;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "atris",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.31.0",
|
|
4
4
|
"main": "bin/atris.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"atris": "bin/atris.js",
|
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
"templates/",
|
|
18
18
|
"README.md",
|
|
19
19
|
"AGENTS.md",
|
|
20
|
+
"FOR_AGENTS.md",
|
|
20
21
|
"atris.md",
|
|
21
22
|
"GETTING_STARTED.md",
|
|
22
23
|
"PERSONA.md",
|
|
23
|
-
"atris/atrisDev.md",
|
|
24
24
|
"atris/CLAUDE.md",
|
|
25
25
|
"atris/GEMINI.md",
|
|
26
26
|
"atris/GETTING_STARTED.md",
|
|
@@ -48,12 +48,12 @@
|
|
|
48
48
|
"atris/team/_template/MEMBER.md",
|
|
49
49
|
"atris/features/_templates/",
|
|
50
50
|
"atris/features/company-brain-sync/",
|
|
51
|
-
"templates/",
|
|
52
51
|
"atris/policies/",
|
|
53
52
|
"atris/skills/"
|
|
54
53
|
],
|
|
55
54
|
"scripts": {
|
|
56
55
|
"test": "node --test",
|
|
56
|
+
"lint:decks": "node scripts/lint-all-decks.js",
|
|
57
57
|
"publish:release": "node scripts/publish-atris-release.js",
|
|
58
58
|
"prepare": "cp scripts/pre-commit .git/hooks/pre-commit 2>/dev/null && chmod +x .git/hooks/pre-commit || true"
|
|
59
59
|
},
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"theme": "paper",
|
|
3
|
-
"brand": { "name": "Atris", "accent": "." },
|
|
4
|
-
"slides": [
|
|
5
|
-
{
|
|
6
|
-
"type": "title",
|
|
7
|
-
"headline": "The self-improving **business computer.**",
|
|
8
|
-
"sub": "Work closes its own loop. Memory, authority, receipts, outcomes. One standard for humans and agents.",
|
|
9
|
-
"panel": {
|
|
10
|
-
"header": { "title": "Closed loop", "meta": "every action" },
|
|
11
|
-
"rows": [
|
|
12
|
-
{ "title": "context", "sub": "memory in", "value": "1", "valueSub": "step", "sev": 2 },
|
|
13
|
-
{ "title": "action", "sub": "member acts", "value": "2", "valueSub": "step", "sev": 1 },
|
|
14
|
-
{ "title": "receipt", "sub": "proof out", "value": "3", "valueSub": "step", "sev": 0, "active": true },
|
|
15
|
-
{ "title": "outcome", "sub": "result", "value": "4", "valueSub": "step", "sev": 2 }
|
|
16
|
-
],
|
|
17
|
-
"footer": { "left": "environment first", "right": "model later" }
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
"type": "statement",
|
|
22
|
-
"text": "Work used to **end.** Now every action can become memory.",
|
|
23
|
-
"sub": "Stored work becomes acted work, then remembered work. Context, action, receipt, outcome, reward, better next action."
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
"type": "columns",
|
|
27
|
-
"heading": "A business computer, not a chatbot",
|
|
28
|
-
"columns": [
|
|
29
|
-
{
|
|
30
|
-
"h": "Memory",
|
|
31
|
-
"b": "Receipts compress into lessons and return as minimum useful context."
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
"h": "Members",
|
|
35
|
-
"b": "Humans and agents with owner, tools, authority, trail, and score."
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
"h": "Receipts",
|
|
39
|
-
"b": "Who authorized, who acted, what proof confirms it, what the business got."
|
|
40
|
-
}
|
|
41
|
-
]
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
"type": "statement",
|
|
45
|
-
"text": "They learn the **click.** We learn the outcome.",
|
|
46
|
-
"sub": "Others reward the draft interaction. Atris rewards the business result: reply, booking, deal, recovery."
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
"type": "panel",
|
|
50
|
-
"heading": "The market proved the gap.",
|
|
51
|
-
"sub": "Agents act everywhere. Almost none verify the result.",
|
|
52
|
-
"panel": {
|
|
53
|
-
"header": { "title": "Why now", "meta": "2026" },
|
|
54
|
-
"rows": [
|
|
55
|
-
{ "title": "No verification", "sub": "AIRQ audit", "value": "98%", "valueSub": "of agents", "sev": 0, "active": true },
|
|
56
|
-
{ "title": "Self-grader cheats", "sub": "Reward benchmark", "value": "14%", "valueSub": "top models", "sev": 0 },
|
|
57
|
-
{ "title": "Agent rollback", "sub": "Sinch survey", "value": "74%", "valueSub": "enterprises", "sev": 1 }
|
|
58
|
-
],
|
|
59
|
-
"footer": { "left": "guardrails gate behavior", "right": "not results" }
|
|
60
|
-
}
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
"type": "columns",
|
|
64
|
-
"heading": "Product today",
|
|
65
|
-
"columns": [
|
|
66
|
-
{
|
|
67
|
-
"h": "Atris2 Fast",
|
|
68
|
-
"b": "Open tool harness. Periphery prepares work without usurping judgment."
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
"h": "Deferral queue",
|
|
72
|
-
"b": "Sends, money, and public acts pause for your approval with a judgment packet."
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
"h": "Antislop gates",
|
|
76
|
-
"b": "Deterministic block on AI UI tells, hype copy, and raw outbound artifacts."
|
|
77
|
-
}
|
|
78
|
-
]
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
"type": "bignumber",
|
|
82
|
-
"number": "+3.35",
|
|
83
|
-
"label": "RL readiness lift after verified episodes",
|
|
84
|
-
"sub": "Diff, receipt, verifier, outcome. Atris runs on Atris."
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
"type": "panel",
|
|
88
|
-
"heading": "Customer recovery first.",
|
|
89
|
-
"sub": "Close one painful loop. Prove the outcome. Expand.",
|
|
90
|
-
"panel": {
|
|
91
|
-
"header": { "title": "Wedge", "meta": "expand" },
|
|
92
|
-
"rows": [
|
|
93
|
-
{ "title": "Customer silent", "sub": "churn or complaint", "value": "in", "sev": 1 },
|
|
94
|
-
{ "title": "Member acts", "sub": "draft recovery", "value": "act", "sev": 0, "active": true },
|
|
95
|
-
{ "title": "Receipt", "sub": "trust or revenue?", "value": "proof", "sev": 2 },
|
|
96
|
-
{ "title": "Expand", "sub": "sales, ops, finance", "value": "out", "sev": 1 }
|
|
97
|
-
],
|
|
98
|
-
"footer": { "left": "one loop proven", "right": "then expand" }
|
|
99
|
-
}
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
"type": "chips",
|
|
103
|
-
"heading": "Business model",
|
|
104
|
-
"sub": "We do not tax Members. Members close loops.",
|
|
105
|
-
"chips": ["$200/mo computer", "Unlimited Members", "Usage credits", "Managed setup", "Enterprise"],
|
|
106
|
-
"mono": "base + verified outcomes"
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
"type": "close",
|
|
110
|
-
"tagline": "Ambition and freedom should not cancel each other out.",
|
|
111
|
-
"buttons": [
|
|
112
|
-
{ "label": "See the demo", "primary": true },
|
|
113
|
-
{ "label": "Talk to us" }
|
|
114
|
-
],
|
|
115
|
-
"footer": "Raising $10M seed · atris.ai · 2026"
|
|
116
|
-
}
|
|
117
|
-
]
|
|
118
|
-
}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"theme": "paper",
|
|
3
|
-
"brand": { "name": "Atris", "accent": "." },
|
|
4
|
-
"slides": [
|
|
5
|
-
{
|
|
6
|
-
"type": "columns",
|
|
7
|
-
"heading": "1 · The gate",
|
|
8
|
-
"sub": "Models regulated, access unequal, AI narrative toxic.",
|
|
9
|
-
"columns": [
|
|
10
|
-
{ "h": "bullet 1", "b": "(your words)" },
|
|
11
|
-
{ "h": "bullet 2", "b": "(your words)" },
|
|
12
|
-
{ "h": "bullet 3", "b": "(your words)" }
|
|
13
|
-
]
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
"type": "columns",
|
|
17
|
-
"heading": "2 · The trap",
|
|
18
|
-
"sub": "Everyone is building the same mimetic slop on rented intelligence.",
|
|
19
|
-
"columns": [
|
|
20
|
-
{ "h": "bullet 1", "b": "(your words)" },
|
|
21
|
-
{ "h": "bullet 2", "b": "(your words)" },
|
|
22
|
-
{ "h": "bullet 3", "b": "(your words)" }
|
|
23
|
-
]
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
"type": "columns",
|
|
27
|
-
"heading": "3 · The shift",
|
|
28
|
-
"sub": "Early adopters want an owned center, not another chatbot.",
|
|
29
|
-
"columns": [
|
|
30
|
-
{ "h": "bullet 1", "b": "(your words)" },
|
|
31
|
-
{ "h": "bullet 2", "b": "(your words)" },
|
|
32
|
-
{ "h": "bullet 3", "b": "(your words)" }
|
|
33
|
-
]
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
"type": "columns",
|
|
37
|
-
"heading": "4 · The sovereign individual",
|
|
38
|
-
"sub": "At the center. Ecosystem around them — software, school, community, capital.",
|
|
39
|
-
"columns": [
|
|
40
|
-
{ "h": "bullet 1", "b": "(your words)" },
|
|
41
|
-
{ "h": "bullet 2", "b": "(your words)" },
|
|
42
|
-
{ "h": "bullet 3", "b": "(your words)" }
|
|
43
|
-
]
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
"type": "columns",
|
|
47
|
-
"heading": "5 · The product",
|
|
48
|
-
"sub": "Self-improving environment, swappable models, agent-native system of record.",
|
|
49
|
-
"columns": [
|
|
50
|
-
{ "h": "bullet 1", "b": "(your words)" },
|
|
51
|
-
{ "h": "bullet 2", "b": "(your words)" },
|
|
52
|
-
{ "h": "bullet 3", "b": "(your words)" }
|
|
53
|
-
]
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
"type": "columns",
|
|
57
|
-
"heading": "6 · The proof",
|
|
58
|
-
"sub": "Center Study and businesses that improve overnight. Live loops, not demos.",
|
|
59
|
-
"columns": [
|
|
60
|
-
{ "h": "bullet 1", "b": "(your words)" },
|
|
61
|
-
{ "h": "bullet 2", "b": "(your words)" },
|
|
62
|
-
{ "h": "bullet 3", "b": "(your words)" }
|
|
63
|
-
]
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
"type": "columns",
|
|
67
|
-
"heading": "7 · First business",
|
|
68
|
-
"sub": "Education / transformation. ~$250k run rate. 1–3 FDEs per cluster.",
|
|
69
|
-
"columns": [
|
|
70
|
-
{ "h": "bullet 1", "b": "(your words)" },
|
|
71
|
-
{ "h": "bullet 2", "b": "(your words)" },
|
|
72
|
-
{ "h": "bullet 3", "b": "(your words)" }
|
|
73
|
-
]
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
"type": "columns",
|
|
77
|
-
"heading": "8 · Customers",
|
|
78
|
-
"sub": "DoorDash and Pallet — what actually runs today.",
|
|
79
|
-
"columns": [
|
|
80
|
-
{ "h": "bullet 1", "b": "(your words)" },
|
|
81
|
-
{ "h": "bullet 2", "b": "(your words)" },
|
|
82
|
-
{ "h": "bullet 3", "b": "(your words)" }
|
|
83
|
-
]
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
"type": "columns",
|
|
87
|
-
"heading": "9 · Growth",
|
|
88
|
-
"sub": "Founder-first distribution. Mimetic spread. Clone the machine.",
|
|
89
|
-
"columns": [
|
|
90
|
-
{ "h": "bullet 1", "b": "(your words)" },
|
|
91
|
-
{ "h": "bullet 2", "b": "(your words)" },
|
|
92
|
-
{ "h": "bullet 3", "b": "(your words)" }
|
|
93
|
-
]
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
"type": "columns",
|
|
97
|
-
"heading": "10 · The ask",
|
|
98
|
-
"sub": "$10M — FDE team, narrative, run more businesses, own the layer beneath the gate.",
|
|
99
|
-
"columns": [
|
|
100
|
-
{ "h": "bullet 1", "b": "(your words)" },
|
|
101
|
-
{ "h": "bullet 2", "b": "(your words)" },
|
|
102
|
-
{ "h": "bullet 3", "b": "(your words)" }
|
|
103
|
-
]
|
|
104
|
-
}
|
|
105
|
-
]
|
|
106
|
-
}
|