atris 3.57.3 → 3.58.5
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 +12 -1
- package/atris/skills/clawhub/member-runtime/SKILL.md +9 -9
- package/atris/skills/create-member/SKILL.md +16 -13
- package/atris/skills/design/SKILL.md +27 -4
- package/atris/skills/engines/SKILL.md +57 -13
- package/atris/skills/x-search/SKILL.md +13 -5
- package/atris/skills/youtube/SKILL.md +39 -8
- package/atris/team/brainstormer/MEMBER.md +2 -2
- package/atris/team/executor/MEMBER.md +3 -3
- package/atris/team/launcher/MEMBER.md +4 -4
- package/atris/team/navigator/MEMBER.md +3 -3
- package/atris/team/researcher/MEMBER.md +2 -2
- package/atris/team/validator/MEMBER.md +1 -1
- package/atris.md +14 -0
- package/ax +157 -24
- package/bin/atris.js +79 -31
- package/commands/aeo.js +4 -4
- package/commands/agents.js +1 -1
- package/commands/align.js +12 -12
- package/commands/analytics.js +3 -3
- package/commands/app.js +5 -5
- package/commands/ask.js +175 -0
- package/commands/auth.js +7 -7
- package/commands/autoland.js +29 -5
- package/commands/autopilot-front.js +6 -6
- package/commands/autopilot.js +90 -90
- package/commands/avail.js +5 -5
- package/commands/bench.js +63 -1
- package/commands/browse.js +2 -2
- package/commands/business.js +12 -12
- package/commands/card.js +2 -2
- package/commands/clean.js +13 -13
- package/commands/close.js +297 -15
- package/commands/compile.js +13 -13
- package/commands/computer.js +94 -27
- package/commands/console.js +5 -5
- package/commands/decide.js +1 -1
- package/commands/deck.js +9 -9
- package/commands/drive.js +4 -4
- package/commands/engine.js +173 -19
- package/commands/errors.js +3 -3
- package/commands/experiments.js +18 -1
- package/commands/feed.js +2 -2
- package/commands/feedback.js +4 -4
- package/commands/fleet.js +12 -12
- package/commands/game.js +1 -1
- package/commands/guide.js +49 -0
- package/commands/improve.js +19 -17
- package/commands/init.js +67 -19
- package/commands/interview.js +7 -7
- package/commands/land.js +40 -15
- package/commands/learn.js +12 -12
- package/commands/lesson.js +2 -2
- package/commands/log.js +1 -1
- package/commands/loops.js +4 -4
- package/commands/member.js +75 -50
- package/commands/mission.js +97 -98
- package/commands/one-lap.js +1 -1
- package/commands/pack.js +74 -8
- package/commands/plugin.js +6 -6
- package/commands/probe.js +9 -9
- package/commands/pull.js +35 -35
- package/commands/pulse.js +1 -1
- package/commands/push.js +21 -21
- package/commands/rainmaker.js +1 -1
- package/commands/recap.js +7 -7
- package/commands/reel.js +2 -2
- package/commands/release.js +3 -3
- package/commands/report.js +1 -1
- package/commands/review.js +5 -5
- package/commands/revisions.js +1 -1
- package/commands/run.js +1 -1
- package/commands/scout.js +3 -3
- package/commands/security-review.js +7 -7
- package/commands/serve.js +9 -9
- package/commands/setup.js +1 -1
- package/commands/sign.js +4 -4
- package/commands/signup.js +4 -4
- package/commands/site.js +8 -3
- package/commands/skill.js +6 -6
- package/commands/slop.js +18 -18
- package/commands/social.js +3 -3
- package/commands/soul.js +14 -14
- package/commands/spaceship.js +1 -1
- package/commands/status.js +2 -2
- package/commands/strings.js +13 -13
- package/commands/study.js +4 -4
- package/commands/sync.js +8 -8
- package/commands/task.js +50 -25
- package/commands/teach.js +2 -2
- package/commands/team.js +1 -1
- package/commands/theme.js +2 -2
- package/commands/tree.js +34 -0
- package/commands/truth.js +7 -7
- package/commands/verify.js +12 -12
- package/commands/visualize.js +2 -2
- package/commands/workflow.js +97 -40
- package/commands/worktree.js +57 -1
- package/commands/write.js +9 -9
- package/commands/x-search.js +304 -15
- package/commands/xp.js +6 -3
- package/commands/youtube.js +916 -65
- package/lib/accept-verify-gate.js +3 -3
- package/lib/activity-stream.js +1 -1
- package/lib/apply-gate.js +13 -0
- package/lib/auto-accept-certified.js +28 -12
- package/lib/autoland.js +6 -6
- package/lib/bench/engines.js +59 -8
- package/lib/bench/report.js +1 -1
- package/lib/bench/runner.js +204 -5
- package/lib/bench/tree-render.js +76 -0
- package/lib/card.js +1 -1
- package/lib/chat-log-scan.js +1 -1
- package/lib/claude-boot-block.js +2 -0
- package/lib/deck-compose.js +1 -1
- package/lib/deck-history.js +2 -2
- package/lib/deck-layout.js +1 -1
- package/lib/deck-review.js +5 -5
- package/lib/deck-schema.js +1 -1
- package/lib/default-verifier.js +1 -1
- package/lib/developer-api.js +3 -0
- package/lib/engine-ask.js +66 -9
- package/lib/engine-registry.js +15 -2
- package/lib/file-ops.js +1 -1
- package/lib/fleet.js +71 -35
- package/lib/html-render.js +1 -1
- package/lib/intents.js +172 -0
- package/lib/journal.js +1 -1
- package/lib/known-commands.js +2 -2
- package/lib/land-green.js +364 -0
- package/lib/lesson-contradiction.js +2 -2
- package/lib/lesson-preflight.js +1 -1
- package/lib/manifest.js +5 -5
- package/lib/member-alive.js +9 -5
- package/lib/memory-view.js +3 -3
- package/lib/mission-protected-lane.js +1 -1
- package/lib/mission-root.js +2 -2
- package/lib/orb-context.js +10 -10
- package/lib/pack-capabilities.js +861 -3
- package/lib/permission-grants.js +2 -2
- package/lib/policy-lessons.js +6 -6
- package/lib/pulse.js +8 -8
- package/lib/receipt-evidence.js +1 -1
- package/lib/reel.js +1 -1
- package/lib/review-integrity.js +1 -1
- package/lib/runner-command.js +35 -5
- package/lib/scorecard.js +4 -4
- package/lib/security-scan.js +5 -5
- package/lib/self-drive.js +1 -1
- package/lib/site-publish.js +372 -0
- package/lib/site.js +1 -1
- package/lib/slides-deck.js +2 -2
- package/lib/state-detection.js +2 -2
- package/lib/sync-telemetry.js +2 -2
- package/lib/task-db.js +17 -10
- package/lib/task-explanation.js +1 -1
- package/lib/task-proof.js +2 -2
- package/lib/task-receipt.js +3 -3
- package/lib/theme.js +12 -12
- package/lib/todo-fallback.js +3 -3
- package/lib/todo-sections.js +1 -1
- package/lib/tree-hash.js +79 -0
- package/lib/usage.js +1 -1
- package/lib/wish-delegate.js +1 -1
- package/lib/workspace-safety.js +4 -4
- package/lib/workspace-scaffold.js +3 -3
- package/package.json +1 -1
- package/scripts/det/concierge-bakeoff.sh +26 -0
- package/scripts/det/ytquote-repair.js +0 -1
- package/scripts/det/ytrail-race.js +0 -5
- package/scripts/member-operate.mjs +52 -14
- package/scripts/outbound-artifact-gate.js +0 -3
- package/utils/auth.js +0 -3
- package/utils/config.js +0 -1
- package/scripts/det/checklist-score.js +0 -191
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { spawnSync } = require('child_process');
|
|
6
|
+
const { loadCredentials } = require('../utils/auth');
|
|
7
|
+
const { getApiBaseUrl } = require('../utils/api');
|
|
8
|
+
|
|
9
|
+
const MAX_PAGE_BYTES = 2 * 1024 * 1024;
|
|
10
|
+
const MAX_PAGES = 200;
|
|
11
|
+
const BUILD_DIRS = ['dist', 'build', 'out'];
|
|
12
|
+
const SKIP_DIRS = new Set(['node_modules', '.git']);
|
|
13
|
+
|
|
14
|
+
const TEXT_CONTENT_TYPES = Object.freeze({
|
|
15
|
+
'.html': 'text/html; charset=utf-8',
|
|
16
|
+
'.css': 'text/css; charset=utf-8',
|
|
17
|
+
'.js': 'text/javascript; charset=utf-8',
|
|
18
|
+
'.mjs': 'text/javascript; charset=utf-8',
|
|
19
|
+
'.json': 'application/json',
|
|
20
|
+
'.txt': 'text/plain; charset=utf-8',
|
|
21
|
+
'.xml': 'application/xml',
|
|
22
|
+
'.svg': 'image/svg+xml',
|
|
23
|
+
'.webmanifest': 'application/manifest+json',
|
|
24
|
+
'.map': 'application/json',
|
|
25
|
+
'.md': 'text/markdown; charset=utf-8',
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const BINARY_CONTENT_TYPES = Object.freeze({
|
|
29
|
+
'.png': 'image/png',
|
|
30
|
+
'.jpg': 'image/jpeg',
|
|
31
|
+
'.jpeg': 'image/jpeg',
|
|
32
|
+
'.gif': 'image/gif',
|
|
33
|
+
'.webp': 'image/webp',
|
|
34
|
+
'.ico': 'image/x-icon',
|
|
35
|
+
'.woff': 'font/woff',
|
|
36
|
+
'.woff2': 'font/woff2',
|
|
37
|
+
'.ttf': 'font/ttf',
|
|
38
|
+
'.otf': 'font/otf',
|
|
39
|
+
'.mp3': 'audio/mpeg',
|
|
40
|
+
'.mp4': 'video/mp4',
|
|
41
|
+
'.wasm': 'application/wasm',
|
|
42
|
+
'.pdf': 'application/pdf',
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
function normalizePagePath(value) {
|
|
46
|
+
return String(value || '').replace(/\\/g, '/').replace(/^\/+/, '');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function fileEncoding(filePath) {
|
|
50
|
+
const extension = path.extname(filePath).toLowerCase();
|
|
51
|
+
if (Object.prototype.hasOwnProperty.call(TEXT_CONTENT_TYPES, extension)) {
|
|
52
|
+
return { contentType: TEXT_CONTENT_TYPES[extension], isBase64: false };
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
contentType: BINARY_CONTENT_TYPES[extension] || 'application/octet-stream',
|
|
56
|
+
isBase64: true,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function walkSiteFiles(root, deps = {}) {
|
|
61
|
+
const fileSystem = deps.fs || fs;
|
|
62
|
+
const files = [];
|
|
63
|
+
|
|
64
|
+
function walk(current) {
|
|
65
|
+
const entries = fileSystem.readdirSync(current, { withFileTypes: true })
|
|
66
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
67
|
+
|
|
68
|
+
for (const entry of entries) {
|
|
69
|
+
if (entry.name === '.DS_Store' || entry.name.startsWith('.')) continue;
|
|
70
|
+
if (entry.isDirectory() && SKIP_DIRS.has(entry.name)) continue;
|
|
71
|
+
|
|
72
|
+
const absolutePath = path.join(current, entry.name);
|
|
73
|
+
if (entry.isSymbolicLink()) continue;
|
|
74
|
+
if (entry.isDirectory()) {
|
|
75
|
+
walk(absolutePath);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (!entry.isFile()) continue;
|
|
79
|
+
|
|
80
|
+
const pagePath = normalizePagePath(path.relative(root, absolutePath));
|
|
81
|
+
const { contentType, isBase64 } = fileEncoding(absolutePath);
|
|
82
|
+
files.push({
|
|
83
|
+
absolutePath,
|
|
84
|
+
path: pagePath,
|
|
85
|
+
size: fileSystem.statSync(absolutePath).size,
|
|
86
|
+
content_type: contentType,
|
|
87
|
+
is_base64: isBase64,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
walk(root);
|
|
93
|
+
return files;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function validateFileLimits(files) {
|
|
97
|
+
const oversized = files.filter((file) => file.size > MAX_PAGE_BYTES);
|
|
98
|
+
const excess = files.slice(MAX_PAGES);
|
|
99
|
+
if (oversized.length === 0 && excess.length === 0) return;
|
|
100
|
+
|
|
101
|
+
const lines = ['site files exceed upload limits:'];
|
|
102
|
+
if (oversized.length > 0) {
|
|
103
|
+
lines.push(`pages over 2 mb: ${oversized.map((file) => file.path).join(', ')}`);
|
|
104
|
+
}
|
|
105
|
+
if (excess.length > 0) {
|
|
106
|
+
lines.push(`pages over the 200 page limit: ${excess.map((file) => file.path).join(', ')}`);
|
|
107
|
+
}
|
|
108
|
+
throw new Error(lines.join('\n'));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function collectPages(root, deps = {}) {
|
|
112
|
+
const fileSystem = deps.fs || fs;
|
|
113
|
+
const files = walkSiteFiles(root, deps);
|
|
114
|
+
validateFileLimits(files);
|
|
115
|
+
|
|
116
|
+
return files.map((file) => {
|
|
117
|
+
const content = fileSystem.readFileSync(file.absolutePath);
|
|
118
|
+
return {
|
|
119
|
+
path: file.path,
|
|
120
|
+
content: file.is_base64 ? content.toString('base64') : content.toString('utf8'),
|
|
121
|
+
content_type: file.content_type,
|
|
122
|
+
is_base64: file.is_base64,
|
|
123
|
+
};
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function parseArgs(argv) {
|
|
128
|
+
const options = {
|
|
129
|
+
dir: null,
|
|
130
|
+
slug: null,
|
|
131
|
+
profile: null,
|
|
132
|
+
spa: false,
|
|
133
|
+
claimSubdomain: true,
|
|
134
|
+
build: false,
|
|
135
|
+
json: false,
|
|
136
|
+
help: false,
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
140
|
+
const arg = argv[i];
|
|
141
|
+
if (arg === '--help' || arg === '-h' || arg === 'help') {
|
|
142
|
+
options.help = true;
|
|
143
|
+
} else if (arg === '--spa') {
|
|
144
|
+
options.spa = true;
|
|
145
|
+
} else if (arg === '--no-claim') {
|
|
146
|
+
options.claimSubdomain = false;
|
|
147
|
+
} else if (arg === '--build') {
|
|
148
|
+
options.build = true;
|
|
149
|
+
} else if (arg === '--json') {
|
|
150
|
+
options.json = true;
|
|
151
|
+
} else if (arg === '--slug' || arg === '--profile') {
|
|
152
|
+
const value = argv[i + 1];
|
|
153
|
+
if (!value || value.startsWith('-')) throw new Error(`${arg} needs a value`);
|
|
154
|
+
if (arg === '--slug') options.slug = value;
|
|
155
|
+
else options.profile = value;
|
|
156
|
+
i += 1;
|
|
157
|
+
} else if (arg.startsWith('-')) {
|
|
158
|
+
throw new Error(`unknown option: ${arg}`);
|
|
159
|
+
} else if (!options.dir) {
|
|
160
|
+
options.dir = arg;
|
|
161
|
+
} else {
|
|
162
|
+
throw new Error(`unexpected argument: ${arg}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return options;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function printHelp(log = console.log) {
|
|
170
|
+
log(`
|
|
171
|
+
atris site publish: upload a built web folder to atris.ai
|
|
172
|
+
|
|
173
|
+
atris site publish <dir> --slug <slug> [--profile strict|app] [--spa] [--no-claim] [--build] [--json]
|
|
174
|
+
|
|
175
|
+
--build runs npm run build in <dir>, then publishes dist, build, or out.
|
|
176
|
+
each page is capped at 2 mb, and each site is capped at 200 pages.
|
|
177
|
+
`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function validateOptions(options) {
|
|
181
|
+
if (!options.dir) return 'site publish needs a directory';
|
|
182
|
+
if (!options.slug) return '--slug is required';
|
|
183
|
+
if (!/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(options.slug)) {
|
|
184
|
+
return 'slug must use lowercase letters, digits, and hyphens, with no hyphen at either end';
|
|
185
|
+
}
|
|
186
|
+
if (options.profile && options.profile !== 'strict' && options.profile !== 'app') {
|
|
187
|
+
return '--profile must be strict or app';
|
|
188
|
+
}
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function requireDirectory(dir, fileSystem = fs) {
|
|
193
|
+
const root = path.resolve(dir);
|
|
194
|
+
let stat;
|
|
195
|
+
try {
|
|
196
|
+
stat = fileSystem.statSync(root);
|
|
197
|
+
} catch {
|
|
198
|
+
throw new Error(`site directory does not exist: ${root}`);
|
|
199
|
+
}
|
|
200
|
+
if (!stat.isDirectory()) throw new Error(`site path is not a directory: ${root}`);
|
|
201
|
+
return root;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function buildSite(root, deps = {}) {
|
|
205
|
+
const fileSystem = deps.fs || fs;
|
|
206
|
+
const packagePath = path.join(root, 'package.json');
|
|
207
|
+
let packageJson;
|
|
208
|
+
try {
|
|
209
|
+
packageJson = JSON.parse(fileSystem.readFileSync(packagePath, 'utf8'));
|
|
210
|
+
} catch {
|
|
211
|
+
throw new Error('--build needs package.json with a build script');
|
|
212
|
+
}
|
|
213
|
+
if (!packageJson.scripts || typeof packageJson.scripts.build !== 'string' || !packageJson.scripts.build.trim()) {
|
|
214
|
+
throw new Error('--build needs package.json with a build script');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const spawn = deps.spawnSync || spawnSync;
|
|
218
|
+
const result = spawn('npm', ['run', 'build'], { cwd: root, stdio: 'inherit' });
|
|
219
|
+
if (result.error) throw new Error(`build failed: ${result.error.message}`);
|
|
220
|
+
if (result.status !== 0) throw new Error(`build failed with exit code ${result.status}`);
|
|
221
|
+
|
|
222
|
+
for (const name of BUILD_DIRS) {
|
|
223
|
+
const candidate = path.join(root, name);
|
|
224
|
+
try {
|
|
225
|
+
if (fileSystem.statSync(candidate).isDirectory()) return candidate;
|
|
226
|
+
} catch {
|
|
227
|
+
// Try the next conventional build directory.
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
throw new Error('build finished but no dist, build, or out directory exists');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async function readResponse(response) {
|
|
234
|
+
if (response && typeof response.text === 'function') {
|
|
235
|
+
const text = await response.text();
|
|
236
|
+
if (!text) return { data: null, text: '' };
|
|
237
|
+
try { return { data: JSON.parse(text), text }; } catch { return { data: null, text }; }
|
|
238
|
+
}
|
|
239
|
+
if (response && typeof response.json === 'function') {
|
|
240
|
+
try {
|
|
241
|
+
const data = await response.json();
|
|
242
|
+
return { data, text: data == null ? '' : JSON.stringify(data) };
|
|
243
|
+
} catch {
|
|
244
|
+
return { data: null, text: '' };
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return { data: null, text: '' };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function responsePageCount(value) {
|
|
251
|
+
if (Array.isArray(value)) return value.length;
|
|
252
|
+
return value == null ? 0 : value;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function printPublishResult(data, log) {
|
|
256
|
+
const urls = data && data.urls && typeof data.urls === 'object' ? data.urls : {};
|
|
257
|
+
log(`pages uploaded: ${responsePageCount(data && data.pages)}`);
|
|
258
|
+
log(`verified: ${data && data.verified === true ? 'yes' : 'no'}`);
|
|
259
|
+
log(`site url: ${urls.site || ''}`);
|
|
260
|
+
log(`preview url: ${urls.preview || ''}`);
|
|
261
|
+
log(`publish id: ${(data && data.publish_id) || ''}`);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async function run(argv, deps = {}) {
|
|
265
|
+
const log = deps.log || console.log;
|
|
266
|
+
const errorLog = deps.error || console.error;
|
|
267
|
+
let options;
|
|
268
|
+
try {
|
|
269
|
+
options = parseArgs(argv);
|
|
270
|
+
} catch (error) {
|
|
271
|
+
errorLog(error.message);
|
|
272
|
+
return 2;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (options.help) {
|
|
276
|
+
printHelp(log);
|
|
277
|
+
return 0;
|
|
278
|
+
}
|
|
279
|
+
const optionError = validateOptions(options);
|
|
280
|
+
if (optionError) {
|
|
281
|
+
errorLog(optionError);
|
|
282
|
+
return 2;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const fileSystem = deps.fs || fs;
|
|
286
|
+
let root;
|
|
287
|
+
let pages;
|
|
288
|
+
try {
|
|
289
|
+
root = requireDirectory(options.dir, fileSystem);
|
|
290
|
+
if (options.build) root = buildSite(root, deps);
|
|
291
|
+
pages = collectPages(root, deps);
|
|
292
|
+
} catch (error) {
|
|
293
|
+
errorLog(error.message);
|
|
294
|
+
return 2;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (!pages.some((page) => page.path === 'index.html')) {
|
|
298
|
+
errorLog('site home page is missing: index.html');
|
|
299
|
+
return 2;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const readCredentials = deps.loadCredentials || loadCredentials;
|
|
303
|
+
const credentials = readCredentials();
|
|
304
|
+
const token = credentials && typeof credentials.token === 'string' ? credentials.token.trim() : '';
|
|
305
|
+
if (!token) {
|
|
306
|
+
errorLog('not logged in. run atris login first.');
|
|
307
|
+
return 1;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const body = { pages, claim_subdomain: options.claimSubdomain };
|
|
311
|
+
if (options.spa) body.spa = true;
|
|
312
|
+
if (options.profile) body.csp_profile = options.profile;
|
|
313
|
+
|
|
314
|
+
const fetchFn = deps.fetch || globalThis.fetch;
|
|
315
|
+
if (typeof fetchFn !== 'function') {
|
|
316
|
+
errorLog('site publish needs Node.js 18 or newer');
|
|
317
|
+
return 1;
|
|
318
|
+
}
|
|
319
|
+
const apiBase = String(deps.apiBase || getApiBaseUrl()).replace(/\/+$/, '');
|
|
320
|
+
const url = `${apiBase}/sites/${encodeURIComponent(options.slug)}/publish`;
|
|
321
|
+
|
|
322
|
+
let response;
|
|
323
|
+
let parsed;
|
|
324
|
+
try {
|
|
325
|
+
response = await fetchFn(url, {
|
|
326
|
+
method: 'POST',
|
|
327
|
+
headers: {
|
|
328
|
+
Authorization: `Bearer ${token}`,
|
|
329
|
+
Accept: 'application/json',
|
|
330
|
+
'Content-Type': 'application/json',
|
|
331
|
+
},
|
|
332
|
+
body: JSON.stringify(body),
|
|
333
|
+
});
|
|
334
|
+
parsed = await readResponse(response);
|
|
335
|
+
} catch (error) {
|
|
336
|
+
errorLog(`publish failed: ${error.message}`);
|
|
337
|
+
return 1;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const status = Number(response && response.status) || 0;
|
|
341
|
+
const ok = response && typeof response.ok === 'boolean'
|
|
342
|
+
? response.ok
|
|
343
|
+
: status >= 200 && status < 300;
|
|
344
|
+
if (!ok) {
|
|
345
|
+
const detail = parsed.data && typeof parsed.data === 'object' && parsed.data.detail
|
|
346
|
+
? String(parsed.data.detail)
|
|
347
|
+
: parsed.text.trim() || `request failed with status ${status}`;
|
|
348
|
+
errorLog(detail);
|
|
349
|
+
if (status === 503 && detail === 'subdomain_grant_unavailable') {
|
|
350
|
+
errorLog(`could not claim ${options.slug}.atris.ai (server has no Render credentials); retry with --no-claim if the subdomain already exists`);
|
|
351
|
+
}
|
|
352
|
+
return 1;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const data = parsed.data && typeof parsed.data === 'object' ? parsed.data : {};
|
|
356
|
+
if (options.json) log(JSON.stringify(data));
|
|
357
|
+
else printPublishResult(data, log);
|
|
358
|
+
|
|
359
|
+
if (data.verified !== true) {
|
|
360
|
+
errorLog('site is not live: verified no');
|
|
361
|
+
return 1;
|
|
362
|
+
}
|
|
363
|
+
return 0;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
module.exports = {
|
|
367
|
+
MAX_PAGE_BYTES,
|
|
368
|
+
MAX_PAGES,
|
|
369
|
+
collectPages,
|
|
370
|
+
normalizePagePath,
|
|
371
|
+
run,
|
|
372
|
+
};
|
package/lib/site.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Static site generator
|
|
1
|
+
// Static site generator: point it at a folder of markdown (docs, your wiki,
|
|
2
2
|
// memory) and get a beautiful, navigable HTML site in the design system.
|
|
3
3
|
// Builds on lib/deck-from-md (parse) + lib/html-render (render).
|
|
4
4
|
//
|
package/lib/slides-deck.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Atris deck engine
|
|
1
|
+
// Atris deck engine: turn a plain content spec into a premium, anti-slop
|
|
2
2
|
// Google Slides deck. Pure: spec -> batch-update requests (no network here).
|
|
3
3
|
//
|
|
4
4
|
// The product idea: PMs open Slides and get Arial-on-white slop by default.
|
|
@@ -55,7 +55,7 @@ function rgb(hex) { const n = parseInt(String(hex).slice(1), 16);
|
|
|
55
55
|
// strip the AI tells the engine refuses to ship. Returns sanitized text.
|
|
56
56
|
function sanitize(t) {
|
|
57
57
|
return String(t == null ? '' : t)
|
|
58
|
-
.replace(/\s
|
|
58
|
+
.replace(/\s*\u2014\s*/g, ', ') // em dash -> comma (top AI-writing tell)
|
|
59
59
|
.replace(/\s-\s/g, ', ') // spaced hyphen used as a dash
|
|
60
60
|
.replace(/\bAI-powered\b/gi, 'built for')
|
|
61
61
|
.replace(/\s{2,}/g, ' ');
|
package/lib/state-detection.js
CHANGED
|
@@ -149,7 +149,7 @@ function getTasksFromDbBucket(atrisDir, bucketName) {
|
|
|
149
149
|
|
|
150
150
|
// Single-read task lane counts for boot/status surfaces.
|
|
151
151
|
// Prefers the task DB (source of truth); falls back to TODO.md parsing.
|
|
152
|
-
// Review counts only exist on the DB path
|
|
152
|
+
// Review counts only exist on the DB path, markdown views don't carry the lane.
|
|
153
153
|
function getTaskCounts(atrisDir) {
|
|
154
154
|
try {
|
|
155
155
|
const taskDb = require('./task-db');
|
|
@@ -167,7 +167,7 @@ function getTaskCounts(atrisDir) {
|
|
|
167
167
|
try {
|
|
168
168
|
const meta = typeof row.metadata === 'string' ? JSON.parse(row.metadata || '{}') : (row.metadata || {});
|
|
169
169
|
if (meta.agent_certified && meta.approval_status !== 'accepted') counts.reviewCertified++;
|
|
170
|
-
} catch { /* unreadable metadata
|
|
170
|
+
} catch { /* unreadable metadata, count as uncertified */ }
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
173
|
return counts;
|
package/lib/sync-telemetry.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
// Atris sync telemetry
|
|
1
|
+
// Atris sync telemetry, emit one event per `atris push` / `atris pull`.
|
|
2
2
|
//
|
|
3
3
|
// Why awaited (not fire-and-forget): push.js / pull.js call process.exit(1)
|
|
4
4
|
// on most failure paths. A fire-and-forget POST gets killed mid-flight and
|
|
5
5
|
// the RL loop loses signals on the exact failures it most needs to learn.
|
|
6
6
|
//
|
|
7
|
-
// Best-effort: any error swallowed silently
|
|
7
|
+
// Best-effort: any error swallowed silently, telemetry never blocks UX.
|
|
8
8
|
// Hard 2s timeout so a flaky control plane can't slow real operations.
|
|
9
9
|
|
|
10
10
|
const { apiRequestJson } = require('../utils/api');
|
package/lib/task-db.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
'use strict';
|
|
10
10
|
|
|
11
11
|
// node:sqlite emits an ExperimentalWarning. Suppress only that exact class by
|
|
12
|
-
// monkey-patching process.emit at this narrow filter
|
|
12
|
+
// monkey-patching process.emit at this narrow filter, other warnings (and
|
|
13
13
|
// any pre-existing listeners installed by host code) are untouched.
|
|
14
14
|
{
|
|
15
15
|
const originalEmit = process.emit;
|
|
@@ -32,6 +32,7 @@ const { isGenericScratchRoot } = require('./scratch-root');
|
|
|
32
32
|
const { isDecisionTask } = require('./task-decision');
|
|
33
33
|
const { parseVerifyCommand } = require('./auto-accept-certified');
|
|
34
34
|
const { taskExplanation } = require('./task-explanation');
|
|
35
|
+
const { treeHashFor } = require('./tree-hash');
|
|
35
36
|
|
|
36
37
|
const DEFAULT_DB_PATH = path.join(os.homedir(), '.atris', 'tasks.db');
|
|
37
38
|
const TASK_EPISODES_FILE = path.join('.atris', 'state', 'task_episodes.jsonl');
|
|
@@ -208,7 +209,7 @@ function open(dbPath) {
|
|
|
208
209
|
// Concurrency: WAL gives readers + one writer concurrency; busy_timeout
|
|
209
210
|
// makes contended writers wait instead of returning SQLITE_BUSY at the
|
|
210
211
|
// C library level. We additionally wrap the setup PRAGMAs + DDL in our
|
|
211
|
-
// own retry
|
|
212
|
+
// own retry, under heavy spawn-storm contention, node:sqlite leaks
|
|
212
213
|
// SQLITE_BUSY past the busy_timeout for `db.exec()` calls.
|
|
213
214
|
withBusyRetry(() => db.exec('PRAGMA journal_mode = WAL'));
|
|
214
215
|
withBusyRetry(() => db.exec('PRAGMA busy_timeout = 30000'));
|
|
@@ -249,7 +250,7 @@ function newId() {
|
|
|
249
250
|
// Resolve the canonical workspace root so `atris task` from any subdirectory
|
|
250
251
|
// keys the SAME store as the repo root. Precedence (nearest ancestor wins per
|
|
251
252
|
// pass):
|
|
252
|
-
// 1. A deliberate workspace spine
|
|
253
|
+
// 1. A deliberate workspace spine, .atris/business.json (a bound customer
|
|
253
254
|
// sub-workspace) or atris/atris.md (the framework protocol file). These are
|
|
254
255
|
// intentional nested workspaces INSIDE a repo and must stay isolated.
|
|
255
256
|
// 2. The git toplevel (.git). One git repo == one workspace; this rescues a
|
|
@@ -630,7 +631,7 @@ function doneTask(db, { id, status, actor, allowReview = false, action, proof, a
|
|
|
630
631
|
const final = status || 'done';
|
|
631
632
|
if (!['done', 'failed'].includes(final)) throw new Error('status must be done|failed');
|
|
632
633
|
// 'archived' is a distinct terminal status written by archiveTask(), never
|
|
633
|
-
// by this function
|
|
634
|
+
// by this function, a bulk sweep of duplicate/off-roadmap tasks must not
|
|
634
635
|
// read as a real failure. See archiveTask() below.
|
|
635
636
|
const now = Date.now();
|
|
636
637
|
const allowedStatuses = allowReview ? "'open', 'claimed', 'review'" : "'open', 'claimed'";
|
|
@@ -699,12 +700,12 @@ function reapMissionBlockerTasks(db, { workspaceRoot: ws, missions = [], actor }
|
|
|
699
700
|
|
|
700
701
|
// Distinct terminal status for housekeeping sweeps (duplicate loop-ticks,
|
|
701
702
|
// off-roadmap backlog resets, synthetic-test cleanup). Never conflate this
|
|
702
|
-
// with `failed
|
|
703
|
+
// with `failed`, 'failed' means the work itself did not succeed, 'archived'
|
|
703
704
|
// means the work was swept off the board for reasons unrelated to whether it
|
|
704
705
|
// succeeded. Reward/RSI signal readers must treat the two differently.
|
|
705
706
|
// `fromFailed: true` is an explicit opt-in for sanctioned historical cleanup
|
|
706
707
|
// (e.g. the 109 duplicate "Loop tick:" orphans fail-closed before 'archived'
|
|
707
|
-
// existed
|
|
708
|
+
// existed, cluster 1 of failed-tasks-analysis-2026-07-03). It permits the
|
|
708
709
|
// failed→archived transition and records the prior status in
|
|
709
710
|
// metadata.archived_from. Individual archive commands never archive 'done'
|
|
710
711
|
// rows. The clear-done sweep opts in through fromDone after selecting only the
|
|
@@ -761,7 +762,7 @@ function archiveTask(db, { id, actor, reason, fromFailed = false, fromDone = fal
|
|
|
761
762
|
}
|
|
762
763
|
|
|
763
764
|
// Marks metadata written by the 2026-06-10 "first-principles backlog reset"
|
|
764
|
-
// (see atris/logs/2026/2026-06-10.md)
|
|
765
|
+
// (see atris/logs/2026/2026-06-10.md), a bulk pass that closed off-roadmap
|
|
765
766
|
// tasks, including certified/proof-backed done work, by writing status
|
|
766
767
|
// 'failed' because no distinct 'archived' status existed yet. This
|
|
767
768
|
// identifies exactly those rows so relabelArchivedTasks() can fix the label
|
|
@@ -1202,6 +1203,7 @@ function stageTask(db, {
|
|
|
1202
1203
|
goal,
|
|
1203
1204
|
summary,
|
|
1204
1205
|
owner,
|
|
1206
|
+
ownerExplicit = false,
|
|
1205
1207
|
exit,
|
|
1206
1208
|
proofNeeded,
|
|
1207
1209
|
firstMove,
|
|
@@ -1361,7 +1363,10 @@ function stageTask(db, {
|
|
|
1361
1363
|
metadata.stage_updated_by = cleanStageText(actor) || null;
|
|
1362
1364
|
if (stageOwner) {
|
|
1363
1365
|
metadata.stage_owner = stageOwner;
|
|
1364
|
-
|
|
1366
|
+
// Plan keeps an existing delegated assignee unless the caller named an
|
|
1367
|
+
// owner on purpose; do always records the worker who is doing it.
|
|
1368
|
+
const reassign = targetStage === 'do' || Boolean(ownerExplicit && requestedOwner);
|
|
1369
|
+
metadata.assigned_to = reassign ? stageOwner : (metadata.assigned_to || stageOwner);
|
|
1365
1370
|
}
|
|
1366
1371
|
if (hasConfidence && Number.isFinite(confidenceValue)) metadata.stage_confidence = Math.max(0, Math.min(1, confidenceValue));
|
|
1367
1372
|
|
|
@@ -1524,7 +1529,7 @@ function normalizeTagToken(value) {
|
|
|
1524
1529
|
// Update a task's flag list (`metadata.tags`) on an already-created task.
|
|
1525
1530
|
// Tags are only settable at creation elsewhere; this is the after-the-fact
|
|
1526
1531
|
// escape hatch so an open task that becomes an owner decision can be marked
|
|
1527
|
-
// `needs-human
|
|
1532
|
+
// `needs-human`, which sweep and fleet staffing both honor, instead of
|
|
1528
1533
|
// being restaffed forever (CLI-879). Every change appends a
|
|
1529
1534
|
// `task_tags_updated` event so the trail shows who flagged it and when.
|
|
1530
1535
|
function tagTask(db, { id, actor, add = [], remove = [] } = {}) {
|
|
@@ -1833,6 +1838,7 @@ function taskEpisodeFromReview(row, event, payload) {
|
|
|
1833
1838
|
const humanFeedback = humanFeedbackFromMetadata(metadata, label, payload);
|
|
1834
1839
|
return {
|
|
1835
1840
|
schema: 'atris.task_episode.v1',
|
|
1841
|
+
tree_hash: treeHashFor(row.workspace_root),
|
|
1836
1842
|
episode_id: event.event_id,
|
|
1837
1843
|
task_id: row.id,
|
|
1838
1844
|
workspace_root: row.workspace_root,
|
|
@@ -1885,6 +1891,7 @@ function taskEpisodeFromRevision(row, event, payload) {
|
|
|
1885
1891
|
const humanFeedback = humanFeedbackFromMetadata(metadata, 'revise', payload);
|
|
1886
1892
|
return {
|
|
1887
1893
|
schema: 'atris.task_episode.v1',
|
|
1894
|
+
tree_hash: treeHashFor(row.workspace_root),
|
|
1888
1895
|
episode_id: event.event_id,
|
|
1889
1896
|
task_id: row.id,
|
|
1890
1897
|
workspace_root: row.workspace_root,
|
|
@@ -2196,7 +2203,7 @@ function withBusyRetry(fn, attempts = 8) {
|
|
|
2196
2203
|
const code = e && (e.code || e.errcode);
|
|
2197
2204
|
const busy = /SQLITE_BUSY|database is locked/i.test(msg) || code === 'SQLITE_BUSY' || code === 5;
|
|
2198
2205
|
if (!busy) throw e;
|
|
2199
|
-
// Sleep synchronously
|
|
2206
|
+
// Sleep synchronously, node:sqlite is sync; matches the rest of the API
|
|
2200
2207
|
const end = Date.now() + delay + Math.floor(Math.random() * delay);
|
|
2201
2208
|
while (Date.now() < end) {} // tight loop is fine, delay is small
|
|
2202
2209
|
delay = Math.min(delay * 2, 500);
|
package/lib/task-explanation.js
CHANGED
|
@@ -52,7 +52,7 @@ function endSentence(value) {
|
|
|
52
52
|
// here without weakening or rewriting the durable task record.
|
|
53
53
|
function plainText(value) {
|
|
54
54
|
let text = cleanText(value)
|
|
55
|
-
.replace(/\s*[
|
|
55
|
+
.replace(/\s*[\u2014\u2013]\s*/g, ', ')
|
|
56
56
|
.replace(/--([a-z][a-z-]*)/g, (_match, flag) => flag.replace(/-/g, ' '))
|
|
57
57
|
.replace(/\b[a-z0-9]+(?:_[a-z0-9]+)+\b/gi, match => match.replace(/_/g, ' '))
|
|
58
58
|
.replace(/\b[a-z]+(?:[A-Z][A-Za-z0-9]*)+\b/g, match => match.replace(/([a-z0-9])([A-Z])/g, '$1 $2'))
|
package/lib/task-proof.js
CHANGED
|
@@ -18,7 +18,7 @@ const LOCAL_SUCCESS_PROOF_EXAMPLE = 'local success example: atris task ready <id
|
|
|
18
18
|
const FETCHED_PROOF_URL_RE = /\[(?:fetched|i-fetched)\]\s*https?:\/\/github[.]com\/[^/\s]+\/[^/\s]+\/actions\/runs\/\d+\b/i;
|
|
19
19
|
const HUMAN_PROOF_RE = /\b(?:team human approved|human approved|human approval|approved by|accepted by|reviewed by|customer replied|customer approved|customer accepted|replied)\b/i;
|
|
20
20
|
const FILE_ACTION_RE = /\b(?:changed|updated|edited|created|deleted|saved|wrote|patched|reviewed|verified|validated|opened|read)\b/i;
|
|
21
|
-
const VERIFIED_PROOF_RE = /^\[verified\]\s+`[^`]+`\s+passed\s+\(exit 0\)(
|
|
21
|
+
const VERIFIED_PROOF_RE = /^\[verified\]\s+`[^`]+`\s+passed\s+\(exit 0\)(?:[\s,]|$)/im;
|
|
22
22
|
const LOCAL_RECEIPT_PATH_RE = /(?:^|[\s])(?:Receipt:\s*)?(?:atris\/runs\/|\.atris\/state\/)[^\s'"`,;)]+\.json\b/i;
|
|
23
23
|
const SECOND_ACTOR_EXECUTED_PROOF_RE = /^Second-actor check:\s+`[^`]+`\s+re-run by [^,]+,\s+exited 0(?:[.\s]|$)/i;
|
|
24
24
|
// Ready/receipt prose may name these. If this process did not run the named
|
|
@@ -168,7 +168,7 @@ function buildVerifiedProof(verifyCmd, baseProof = '', runner, options = {}) {
|
|
|
168
168
|
}
|
|
169
169
|
const base = compactWhitespace(baseProof);
|
|
170
170
|
// Phrase so the result also satisfies taskProofState ("verified ... passed").
|
|
171
|
-
const proof = `[verified] \`${cmd}\` passed (exit 0)${base ?
|
|
171
|
+
const proof = `[verified] \`${cmd}\` passed (exit 0)${base ? `, ${base}` : ''}${output ? `\n${output}` : ''}`;
|
|
172
172
|
return { ok: true, exit: 0, output, cmd, proof };
|
|
173
173
|
}
|
|
174
174
|
|
package/lib/task-receipt.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Receipt writer for task proofs. Mirrors the mission receipt shape
|
|
4
4
|
// (commands/mission.js writeReceipt) so lib/receipt-evidence.js can validate
|
|
5
5
|
// paths named in a proof at the review gate: schema tag, `at` timestamp,
|
|
6
|
-
// result.passed. Every call writes a receipt file, pass or fail
|
|
6
|
+
// result.passed. Every call writes a receipt file, pass or fail, a failing
|
|
7
7
|
// verifier still leaves a record, it just can't be cited to move a task to
|
|
8
8
|
// ready.
|
|
9
9
|
|
|
@@ -26,7 +26,7 @@ function gitCommitHash(root) {
|
|
|
26
26
|
const result = spawnSync('git', ['rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8' });
|
|
27
27
|
if (result.status === 0) return String(result.stdout || '').trim() || null;
|
|
28
28
|
} catch {
|
|
29
|
-
// ignore
|
|
29
|
+
// ignore, not every workspace is a git repo
|
|
30
30
|
}
|
|
31
31
|
return null;
|
|
32
32
|
}
|
|
@@ -45,7 +45,7 @@ function slugifyTaskId(taskId) {
|
|
|
45
45
|
|
|
46
46
|
// Runs `command` for `taskId`, writes atris/runs/<date>-task-<id>-<time>.json,
|
|
47
47
|
// and returns { ok, passed, receiptPath, exit, signal, output }. `ok` mirrors
|
|
48
|
-
// `passed
|
|
48
|
+
// `passed`, kept as a separate field so callers reading either name work.
|
|
49
49
|
function writeTaskReceipt({ taskId, command, root = process.cwd(), runner } = {}) {
|
|
50
50
|
const cmd = String(command || '').trim();
|
|
51
51
|
if (!taskId) return { ok: false, passed: false, reason: 'task_id_required' };
|