atris 3.15.56 → 3.16.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 +2 -2
- package/GETTING_STARTED.md +1 -1
- package/PERSONA.md +4 -4
- package/README.md +11 -11
- package/atris/skills/copy-editor/SKILL.md +30 -4
- package/atris/skills/improve/SKILL.md +18 -20
- package/atris/wiki/concepts/agent-activation-contract.md +5 -3
- package/atris/wiki/concepts/workspace-initialization-contract.md +4 -4
- package/atris/wiki/index.md +1 -0
- package/ax +522 -73
- package/bin/atris.js +32 -31
- package/commands/align.js +0 -14
- package/commands/apps.js +102 -1
- package/commands/autopilot.js +197 -22
- package/commands/brain.js +219 -34
- package/commands/brainstorm.js +0 -829
- package/commands/computer.js +45 -83
- package/commands/improve.js +501 -0
- package/commands/integrations.js +228 -0
- package/commands/lesson.js +44 -0
- package/commands/member.js +4498 -226
- package/commands/mission.js +302 -27
- package/commands/now.js +89 -1
- package/commands/radar.js +181 -56
- package/commands/skill.js +37 -6
- package/commands/soul.js +0 -4
- package/commands/task.js +5582 -517
- package/commands/terminal.js +14 -10
- package/commands/wiki.js +87 -1
- package/commands/workflow.js +288 -73
- package/commands/worktree.js +52 -15
- package/commands/xp.js +41 -65
- package/lib/auto-accept-certified.js +294 -0
- package/lib/file-ops.js +0 -184
- package/lib/member-alive.js +232 -0
- package/lib/policy-lessons.js +280 -0
- package/lib/receipt-evidence.js +64 -0
- package/lib/state-detection.js +34 -0
- package/lib/task-db.js +568 -16
- package/lib/task-proof.js +43 -0
- package/package.json +1 -1
- package/utils/auth.js +13 -4
- package/commands/research.js +0 -52
- package/lib/section-merge.js +0 -196
package/bin/atris.js
CHANGED
|
@@ -67,8 +67,8 @@ const ensureValidCredentials = (opts) => _ensureValidCredentials(apiRequestJson,
|
|
|
67
67
|
const fetchMyAgents = (token) => _fetchMyAgents(token, apiRequestJson);
|
|
68
68
|
const displayAccountSummary = () => _displayAccountSummary(apiRequestJson);
|
|
69
69
|
|
|
70
|
-
// Run update check in background (non-blocking)
|
|
71
|
-
// Skip for
|
|
70
|
+
// Run update check in background (non-blocking).
|
|
71
|
+
// Skip for machine-readable JSON and help commands to avoid corrupting stdout.
|
|
72
72
|
let updateCheckPromise = null;
|
|
73
73
|
const updateCommand = process.argv[2];
|
|
74
74
|
const updateArgs = process.argv.slice(3);
|
|
@@ -78,7 +78,8 @@ const helpRequested = updateCommand === 'help'
|
|
|
78
78
|
|| updateArgs.includes('--help')
|
|
79
79
|
|| updateArgs.includes('-h')
|
|
80
80
|
|| updateArgs[0] === 'help';
|
|
81
|
-
const
|
|
81
|
+
const jsonRequested = updateArgs.includes('--json');
|
|
82
|
+
const skipUpdateCheck = Boolean(process.env.ATRIS_SKIP_UPDATE_CHECK || process.env.NO_UPDATE_NOTIFIER || helpRequested || jsonRequested);
|
|
82
83
|
if (!skipUpdateCheck && (!updateCommand || (updateCommand && !['version', 'update'].includes(updateCommand)))) {
|
|
83
84
|
updateCheckPromise = checkForUpdates()
|
|
84
85
|
.then((updateInfo) => {
|
|
@@ -117,15 +118,6 @@ const isBusinessSyncSafetyCommand = command === 'sync'
|
|
|
117
118
|
|| firstCommandArg === 'resolve'
|
|
118
119
|
);
|
|
119
120
|
|
|
120
|
-
// Keep APP.md app-pack operations independent from the heavier workspace boot
|
|
121
|
-
// path so `atris apps --json` stays machine-readable for agents.
|
|
122
|
-
if (command === 'apps') {
|
|
123
|
-
const subcommand = process.argv[3];
|
|
124
|
-
const args = process.argv.slice(4);
|
|
125
|
-
require('../commands/apps').appsCommand(subcommand, ...args);
|
|
126
|
-
process.exit(0);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
121
|
// Auto-sync skills only for commands that modify workspace state
|
|
130
122
|
if (['init', 'update', 'upgrade'].includes(command) || (command === 'sync' && !isBusinessSyncSafetyCommand)) {
|
|
131
123
|
try {
|
|
@@ -346,7 +338,7 @@ function showHelp() {
|
|
|
346
338
|
console.log(' release - Tag release, bump version, create GitHub release, draft /launch');
|
|
347
339
|
console.log(' learn - Project learnings (patterns, pitfalls, preferences)');
|
|
348
340
|
console.log(' brain - Compile MAP/TODO/wiki/state into a loadable agent brain');
|
|
349
|
-
console.log(' lesson - Append a one-line lesson to atris/lessons.md');
|
|
341
|
+
console.log(' lesson - Append a one-line lesson to atris/lessons.md (mine: distill receipts/episodes/scorecards into policy lessons)');
|
|
350
342
|
console.log(' ingest - Local-first wiki ingest into atris/wiki/');
|
|
351
343
|
console.log(' query - Local-first wiki query against atris/wiki/');
|
|
352
344
|
console.log(' lint - Local-first wiki lint for atris/wiki/');
|
|
@@ -355,6 +347,7 @@ function showHelp() {
|
|
|
355
347
|
console.log('Optional helpers:');
|
|
356
348
|
console.log(' brainstorm - Explore ideas conversationally before planning');
|
|
357
349
|
console.log(' autopilot - Guided loop that can clarify TODOs and run plan → do → review');
|
|
350
|
+
console.log(' improve - Run one paid RL tick (POST /api/improve, deducts credits)');
|
|
358
351
|
console.log(' worktree - Isolated Git worktrees plus guarded ship/merge for parallel agents');
|
|
359
352
|
console.log(' visualize - Generate a Slack/deck-ready visual from a prompt');
|
|
360
353
|
console.log('');
|
|
@@ -491,14 +484,18 @@ function showDoHelp() {
|
|
|
491
484
|
|
|
492
485
|
function showReviewHelp() {
|
|
493
486
|
console.log('');
|
|
494
|
-
console.log('Usage: atris review [--
|
|
487
|
+
console.log('Usage: atris review [--limit N|--all|--json] [--full|--execute]');
|
|
495
488
|
console.log('');
|
|
496
489
|
console.log('Description:');
|
|
497
|
-
console.log('
|
|
498
|
-
console.log('
|
|
499
|
-
console.log('
|
|
490
|
+
console.log(' Show the certified Review queue: proof-ready work waiting for');
|
|
491
|
+
console.log(' human accept or revise. Human accept is the AgentXP gate.');
|
|
492
|
+
console.log(' Use --full/--verbose for the legacy Validator prompt.');
|
|
500
493
|
console.log('');
|
|
501
494
|
console.log('Options:');
|
|
495
|
+
console.log(' --limit N Show at most N certified review rows.');
|
|
496
|
+
console.log(' --all Show all certified review rows.');
|
|
497
|
+
console.log(' --json Emit the task-backed review queue as JSON.');
|
|
498
|
+
console.log(' --group-by Group certified rows by tag, owner, or source.');
|
|
502
499
|
console.log(' --execute Run in agent mode via Atris cloud (requires login + agent).');
|
|
503
500
|
console.log(' --full Print full spec/context dumps (verbose copy/paste).');
|
|
504
501
|
console.log(' --verbose Alias for --full.');
|
|
@@ -769,7 +766,7 @@ if (command === '2' && ['fast', 'pro'].includes(String(firstCommandArg || '').to
|
|
|
769
766
|
const knownCommands = ['init', 'log', 'now', 'radar', 'ctop', 'status', 'analytics', 'visualize', 'brain', 'brainstorm', 'autopilot', 'run', 'plan', 'do', 'review', 'release',
|
|
770
767
|
'activate', '_activate', 'agent', 'chat', 'console', 'serve', 'login', 'logout', 'whoami', 'switch', 'use', 'accounts', '_resolve', '_profile-email', '_switch-session', 'shell-init', 'update', 'upgrade', 'version', 'help', 'next', 'atris',
|
|
771
768
|
'clean', 'verify', 'search', 'skill', 'member', 'codex-goal', 'app', 'apps', 'learn', 'lesson', 'plugin', 'experiments', 'receipt', 'proof', 'openclaw', 'pull', 'push', 'live', 'align', 'terminal', 'computer', 'diff', 'business', 'sync',
|
|
772
|
-
'ingest', 'query', 'lint', 'loop', 'task', 'mission', 'worktree', 'aeo', 'xp', 'play', 'gm', 'x',
|
|
769
|
+
'ingest', 'query', 'lint', 'loop', 'task', 'mission', 'worktree', 'aeo', 'improve', 'xp', 'play', 'gm', 'x',
|
|
773
770
|
'gmail', 'calendar', 'twitter', 'slack', 'imessage', 'integrations', 'setup', 'clean-workspace', 'cw',
|
|
774
771
|
'fork', 'browse', 'publish', 'sleep', 'wake', 'feedback', 'errors', 'wiki', 'code-review', 'cr', 'soul', 'fleet'];
|
|
775
772
|
|
|
@@ -1045,7 +1042,6 @@ async function interactiveEntry(userInput) {
|
|
|
1045
1042
|
// ASCII Welcome Visualization
|
|
1046
1043
|
function showWelcomeVisualization() {
|
|
1047
1044
|
const { getBacklogTasks, getInProgressTasks } = require('../lib/state-detection');
|
|
1048
|
-
const { getLogPath, ensureLogDirectory, createLogFile } = require('../lib/journal');
|
|
1049
1045
|
const cwd = process.cwd();
|
|
1050
1046
|
const atrisDir = path.join(cwd, 'atris');
|
|
1051
1047
|
const projectName = path.basename(cwd);
|
|
@@ -1059,16 +1055,6 @@ function showWelcomeVisualization() {
|
|
|
1059
1055
|
let isInitialized = fs.existsSync(atrisDir);
|
|
1060
1056
|
|
|
1061
1057
|
if (isInitialized) {
|
|
1062
|
-
// Auto-create today's journal if missing
|
|
1063
|
-
try {
|
|
1064
|
-
ensureLogDirectory();
|
|
1065
|
-
const { logFile, dateFormatted } = getLogPath();
|
|
1066
|
-
if (!fs.existsSync(logFile)) {
|
|
1067
|
-
createLogFile(logFile, dateFormatted);
|
|
1068
|
-
}
|
|
1069
|
-
} catch {
|
|
1070
|
-
// Silently fail - don't block welcome display
|
|
1071
|
-
}
|
|
1072
1058
|
// Check MAP.md
|
|
1073
1059
|
const mapPath = path.join(atrisDir, 'MAP.md');
|
|
1074
1060
|
if (fs.existsSync(mapPath)) {
|
|
@@ -1197,6 +1183,11 @@ if (command === 'init') {
|
|
|
1197
1183
|
Promise.resolve(require('../commands/aeo').run(process.argv.slice(3)))
|
|
1198
1184
|
.then(() => process.exit(0))
|
|
1199
1185
|
.catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
|
|
1186
|
+
} else if (command === 'improve') {
|
|
1187
|
+
// Improve: one paid RL tick via POST /api/improve (deducts credits), local autopilot fallback.
|
|
1188
|
+
Promise.resolve(require('../commands/improve').run(process.argv.slice(3)))
|
|
1189
|
+
.then((code) => process.exit(typeof code === 'number' ? code : 0))
|
|
1190
|
+
.catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
|
|
1200
1191
|
} else if (command === 'brain') {
|
|
1201
1192
|
Promise.resolve()
|
|
1202
1193
|
.then(() => require('../commands/brain').brainCommand(process.argv.slice(3)))
|
|
@@ -1567,7 +1558,7 @@ if (command === 'init') {
|
|
|
1567
1558
|
searchJournal(keyword);
|
|
1568
1559
|
} else if (command === 'xp') {
|
|
1569
1560
|
require('../commands/xp').xpCommand(...process.argv.slice(3))
|
|
1570
|
-
.then(() => process.
|
|
1561
|
+
.then(() => { process.exitCode = 0; })
|
|
1571
1562
|
.catch((err) => { console.error(`✗ Error: ${err.message || err}`); process.exit(1); });
|
|
1572
1563
|
} else if (command === 'play') {
|
|
1573
1564
|
require('../commands/play').playCommand(...process.argv.slice(3))
|
|
@@ -1622,6 +1613,14 @@ if (command === 'init') {
|
|
|
1622
1613
|
integrationsStatus()
|
|
1623
1614
|
.then(() => process.exit(0))
|
|
1624
1615
|
.catch((err) => { console.error(`✗ Error: ${err.message || err}`); process.exit(1); });
|
|
1616
|
+
} else if (command === 'apps') {
|
|
1617
|
+
// Keep APP.md app-pack operations independent from the heavier workspace boot
|
|
1618
|
+
// path so `atris apps --json` stays machine-readable for agents.
|
|
1619
|
+
const subcommand = process.argv[3];
|
|
1620
|
+
const args = process.argv.slice(4);
|
|
1621
|
+
Promise.resolve(require('../commands/apps').appsCommand(subcommand, ...args))
|
|
1622
|
+
.then(() => process.exit(0))
|
|
1623
|
+
.catch((err) => { console.error(`✗ Error: ${err.message || err}`); process.exit(1); });
|
|
1625
1624
|
} else if (command === 'learn') {
|
|
1626
1625
|
const subcommand = process.argv[3];
|
|
1627
1626
|
const args = process.argv.slice(4);
|
|
@@ -1637,7 +1636,9 @@ if (command === 'init') {
|
|
|
1637
1636
|
} else if (command === 'member') {
|
|
1638
1637
|
const subcommand = process.argv[3];
|
|
1639
1638
|
const args = process.argv.slice(4);
|
|
1640
|
-
require('../commands/member').memberCommand(subcommand, ...args)
|
|
1639
|
+
Promise.resolve(require('../commands/member').memberCommand(subcommand, ...args))
|
|
1640
|
+
.then(() => process.exit(0))
|
|
1641
|
+
.catch((err) => { console.error(`✗ Error: ${err.message || err}`); process.exit(1); });
|
|
1641
1642
|
} else if (command === 'app') {
|
|
1642
1643
|
const subcommand = process.argv[3];
|
|
1643
1644
|
const args = process.argv.slice(4);
|
package/commands/align.js
CHANGED
|
@@ -173,20 +173,6 @@ async function walkCloud(token, businessId, workspaceId) {
|
|
|
173
173
|
return { files: out, errors };
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
-
/**
|
|
177
|
-
* Get cloud file content (for hash computation when /files doesn't return hashes).
|
|
178
|
-
*/
|
|
179
|
-
async function fetchCloudFileHash(token, businessId, workspaceId, filePath) {
|
|
180
|
-
const result = await apiRequestJson(
|
|
181
|
-
`/business/${businessId}/workspaces/${workspaceId}/file?path=${encodeURIComponent(filePath)}`,
|
|
182
|
-
{ method: 'GET', token }
|
|
183
|
-
);
|
|
184
|
-
if (!result.ok) return null;
|
|
185
|
-
const content = result.data && result.data.content;
|
|
186
|
-
if (typeof content !== 'string') return null;
|
|
187
|
-
return hashContent(Buffer.from(content, 'utf-8'));
|
|
188
|
-
}
|
|
189
|
-
|
|
190
176
|
/**
|
|
191
177
|
* Wake the EC2 computer and wait until it's running.
|
|
192
178
|
* Returns the endpoint URL or null on timeout.
|
package/commands/apps.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { spawnSync } = require('child_process');
|
|
4
|
+
const { ensureValidCredentials } = require('../utils/auth');
|
|
5
|
+
const { apiRequestJson } = require('../utils/api');
|
|
6
|
+
|
|
7
|
+
const CLOUD_LOAD_COMMANDS = new Set(['load', 'cloud', 'mine']);
|
|
4
8
|
|
|
5
9
|
function findAppsPackRoot(startDir = process.cwd()) {
|
|
6
10
|
const explicit = process.env.ATRIS_APPS_PACK;
|
|
@@ -25,6 +29,7 @@ function appsUsageLines() {
|
|
|
25
29
|
'',
|
|
26
30
|
'Commands:',
|
|
27
31
|
' list [--json] List available local apps',
|
|
32
|
+
' load [--filter kind] [--json] Load owned cloud apps from Atris',
|
|
28
33
|
' run <slug> [--lines N] Run an app and print data/latest.md or --json status',
|
|
29
34
|
' owner <slug> [--json] Show owner view: launch, usage, learning, next actions',
|
|
30
35
|
' status [--json] Show local app health',
|
|
@@ -156,7 +161,99 @@ function runPackScript(packRoot, script, args) {
|
|
|
156
161
|
process.exit(result.status ?? 0);
|
|
157
162
|
}
|
|
158
163
|
|
|
159
|
-
function
|
|
164
|
+
function normalizeCloudAppsPayload(data) {
|
|
165
|
+
if (Array.isArray(data)) return data;
|
|
166
|
+
if (Array.isArray(data?.apps)) return data.apps;
|
|
167
|
+
if (Array.isArray(data?.items)) return data.items;
|
|
168
|
+
if (Array.isArray(data?.data)) return data.data;
|
|
169
|
+
return [];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function compactCloudApp(app) {
|
|
173
|
+
const slug = app?.slug || app?.id || app?.name || 'unknown';
|
|
174
|
+
return {
|
|
175
|
+
id: app?.id || null,
|
|
176
|
+
name: app?.name || slug,
|
|
177
|
+
slug,
|
|
178
|
+
description: app?.description || null,
|
|
179
|
+
template: app?.template || app?.template_slug || null,
|
|
180
|
+
status: app?.status || app?.health || null,
|
|
181
|
+
last_run: app?.last_run || app?.lastRun || app?.last_run_at || null,
|
|
182
|
+
next_run: app?.next_run || app?.nextRun || app?.next_run_at || null,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function printCloudApps(apps, filter) {
|
|
187
|
+
const suffix = filter ? ` (${filter})` : '';
|
|
188
|
+
if (apps.length === 0) {
|
|
189
|
+
console.log(`No cloud apps found${suffix}.`);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
console.log(`Cloud apps${suffix}:`);
|
|
193
|
+
for (const app of apps) {
|
|
194
|
+
const name = String(app.name || app.slug || 'Untitled app');
|
|
195
|
+
const slug = String(app.slug || app.id || 'unknown');
|
|
196
|
+
const status = app.status ? ` status=${app.status}` : '';
|
|
197
|
+
const template = app.template ? ` template=${app.template}` : '';
|
|
198
|
+
console.log(` ${slug.padEnd(24)} ${name}${status}${template}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function printAppsLoadHelp() {
|
|
203
|
+
console.log('');
|
|
204
|
+
console.log('Usage: atris apps load [--filter <template|paid|free>] [--json]');
|
|
205
|
+
console.log('');
|
|
206
|
+
console.log('Load owned cloud apps from Atris. Requires `atris login`.');
|
|
207
|
+
console.log('');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function loadCloudApps(rawArgs) {
|
|
211
|
+
const args = [...rawArgs];
|
|
212
|
+
if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
|
|
213
|
+
printAppsLoadHelp();
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const json = args.includes('--json');
|
|
217
|
+
if (json) args.splice(args.indexOf('--json'), 1);
|
|
218
|
+
let filter = popOption(args, '--filter', null);
|
|
219
|
+
if (!filter && args[0] && !args[0].startsWith('-')) filter = args.shift();
|
|
220
|
+
if (args.length > 0) {
|
|
221
|
+
exitAppsError(`Unknown apps load option: ${args[0]}`, json, { usage: true, code: 2 });
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const ensured = await ensureValidCredentials(apiRequestJson);
|
|
225
|
+
if (ensured.error || !ensured.credentials?.token) {
|
|
226
|
+
exitAppsError('Not logged in. Run: atris login', json, {
|
|
227
|
+
extra: { login: 'atris login' },
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const query = filter ? `?filter=${encodeURIComponent(filter)}` : '';
|
|
232
|
+
const result = await apiRequestJson(`/apps${query}`, {
|
|
233
|
+
method: 'GET',
|
|
234
|
+
token: ensured.credentials.token,
|
|
235
|
+
});
|
|
236
|
+
if (!result.ok) {
|
|
237
|
+
exitAppsError(`Failed to load cloud apps: ${result.error || result.status || 'request failed'}`, json, {
|
|
238
|
+
extra: { status: result.status || null },
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const apps = normalizeCloudAppsPayload(result.data).map(compactCloudApp);
|
|
243
|
+
if (json) {
|
|
244
|
+
console.log(JSON.stringify({
|
|
245
|
+
ok: true,
|
|
246
|
+
source: 'cloud',
|
|
247
|
+
filter: filter || null,
|
|
248
|
+
count: apps.length,
|
|
249
|
+
apps,
|
|
250
|
+
}, null, 2));
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
printCloudApps(apps, filter || null);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function appsCommand(subcommand, ...rawArgs) {
|
|
160
257
|
const jsonRequested = wantsJson(subcommand, rawArgs);
|
|
161
258
|
const normalized = normalizeInvocation(subcommand, rawArgs);
|
|
162
259
|
subcommand = normalized.subcommand;
|
|
@@ -166,6 +263,10 @@ function appsCommand(subcommand, ...rawArgs) {
|
|
|
166
263
|
printAppsHelp();
|
|
167
264
|
return;
|
|
168
265
|
}
|
|
266
|
+
if (CLOUD_LOAD_COMMANDS.has(subcommand)) {
|
|
267
|
+
await loadCloudApps(rawArgs);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
169
270
|
const packRoot = findAppsPackRoot();
|
|
170
271
|
if (!packRoot) {
|
|
171
272
|
if (jsonRequested) {
|
package/commands/autopilot.js
CHANGED
|
@@ -25,6 +25,46 @@ const pkg = require('../package.json');
|
|
|
25
25
|
|
|
26
26
|
const PHASE_TIMEOUT = 600000; // 10 min per phase
|
|
27
27
|
|
|
28
|
+
function looksOwnerClaimed(claimed) {
|
|
29
|
+
const text = String(claimed || '').toLowerCase();
|
|
30
|
+
return /\bkeshav(?:rao)?\b/.test(text) || /\b(owner|human|operator)\b/.test(text);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function looksOwnerGatedTitle(title) {
|
|
34
|
+
const text = String(title || '').toLowerCase();
|
|
35
|
+
return (
|
|
36
|
+
/\bowner[- ](?:approval|input|gate|gated)\b/.test(text) ||
|
|
37
|
+
/\bhuman[- ](?:approval|input|gate|gated)\b/.test(text) ||
|
|
38
|
+
/\bmanual send\b/.test(text) ||
|
|
39
|
+
/\broute confirmation\b/.test(text) ||
|
|
40
|
+
/\bconfirm pallet destination\b/.test(text) ||
|
|
41
|
+
/\bconfirm .+ destination before .+ approval\b/.test(text) ||
|
|
42
|
+
/\bapprove and manually send\b/.test(text)
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function shouldSkipAutoHumanGate(task) {
|
|
47
|
+
if (!task) return false;
|
|
48
|
+
return looksOwnerClaimed(task.claimed) || looksOwnerGatedTitle(task.title || task.task);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function repoMapAuditReportsClean(cwd) {
|
|
52
|
+
const auditPath = path.join(cwd, 'scripts', 'audit_map_refs.py');
|
|
53
|
+
if (!fs.existsSync(auditPath)) return false;
|
|
54
|
+
|
|
55
|
+
const result = spawnSync('python3', [auditPath], {
|
|
56
|
+
cwd,
|
|
57
|
+
encoding: 'utf8',
|
|
58
|
+
timeout: 120000,
|
|
59
|
+
maxBuffer: 1024 * 1024
|
|
60
|
+
});
|
|
61
|
+
if (result.status !== 0) return false;
|
|
62
|
+
|
|
63
|
+
const output = `${result.stdout || ''}\n${result.stderr || ''}`;
|
|
64
|
+
const match = output.match(/Total broken references:\s*(\d+)/i);
|
|
65
|
+
return Boolean(match && Number(match[1]) === 0);
|
|
66
|
+
}
|
|
67
|
+
|
|
28
68
|
/**
|
|
29
69
|
* Scan workspace for the next thing worth doing.
|
|
30
70
|
* Returns { task, why, kind } or null.
|
|
@@ -54,7 +94,7 @@ async function suggestNextTask(cwd, skipped = new Set(), { auto = false } = {})
|
|
|
54
94
|
// --- Resume interrupted work ---
|
|
55
95
|
if (todo.inProgress.length > 0) {
|
|
56
96
|
const t = todo.inProgress[0];
|
|
57
|
-
if (!(t.tags && t.tags.includes('unverified')) && !skipped.has(t.title)) {
|
|
97
|
+
if (!(t.tags && t.tags.includes('unverified')) && !skipped.has(t.title) && !(auto && shouldSkipAutoHumanGate(t))) {
|
|
58
98
|
suggestions.push({
|
|
59
99
|
task: t.title,
|
|
60
100
|
why: `This was already started${t.claimed ? ` by ${t.claimed}` : ''} but never finished.`,
|
|
@@ -75,6 +115,7 @@ async function suggestNextTask(cwd, skipped = new Set(), { auto = false } = {})
|
|
|
75
115
|
why: `"${sp.staleSource}" changed on ${sp.sourceDate} but the page was last compiled ${sp.compiledDate}. The content may be wrong.`,
|
|
76
116
|
kind: 'staleness',
|
|
77
117
|
priority: 2,
|
|
118
|
+
files: [pageName, sp.staleSource],
|
|
78
119
|
skipKey: key
|
|
79
120
|
});
|
|
80
121
|
break;
|
|
@@ -95,7 +136,9 @@ async function suggestNextTask(cwd, skipped = new Set(), { auto = false } = {})
|
|
|
95
136
|
}
|
|
96
137
|
|
|
97
138
|
// --- Broken MAP.md references ---
|
|
98
|
-
const { unhealable } =
|
|
139
|
+
const { unhealable } = repoMapAuditReportsClean(cwd)
|
|
140
|
+
? { unhealable: [] }
|
|
141
|
+
: healBrokenMapRefs(cwd, atrisDir, true); // dry-run
|
|
99
142
|
if (unhealable.length > 0 && !skipped.has('fix-map-refs')) {
|
|
100
143
|
const sample = unhealable.slice(0, 3).map(r => `${r.file}:${r.line}`).join(', ');
|
|
101
144
|
suggestions.push({
|
|
@@ -127,6 +170,7 @@ async function suggestNextTask(cwd, skipped = new Set(), { auto = false } = {})
|
|
|
127
170
|
for (const t of todo.backlog) {
|
|
128
171
|
if (t.tags && t.tags.includes('unverified')) continue;
|
|
129
172
|
if (shouldSkipEndgameAtPicker(cwd, t)) continue;
|
|
173
|
+
if (auto && shouldSkipAutoHumanGate(t)) continue;
|
|
130
174
|
if (skipped.has(t.title)) continue;
|
|
131
175
|
const remaining = todo.backlog.filter(b => !(b.tags && b.tags.includes('unverified'))).length;
|
|
132
176
|
suggestions.push({
|
|
@@ -383,10 +427,6 @@ function executePhaseDetailed(phase, context, options = {}) {
|
|
|
383
427
|
}
|
|
384
428
|
}
|
|
385
429
|
|
|
386
|
-
function executePhase(phase, context, options = {}) {
|
|
387
|
-
return executePhaseDetailed(phase, context, options).output;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
430
|
/**
|
|
391
431
|
* Build context-aware file list for prompts.
|
|
392
432
|
*/
|
|
@@ -422,7 +462,13 @@ function buildPrompt(phase, context, options = {}) {
|
|
|
422
462
|
contextNote = '',
|
|
423
463
|
runnerName = '',
|
|
424
464
|
} = options;
|
|
425
|
-
const readFiles = getContextFiles(phase,
|
|
465
|
+
const readFiles = getContextFiles(phase, {
|
|
466
|
+
...options,
|
|
467
|
+
extraReadFiles: [
|
|
468
|
+
...(options.extraReadFiles || []),
|
|
469
|
+
...(Array.isArray(context.files) ? context.files : []),
|
|
470
|
+
],
|
|
471
|
+
});
|
|
426
472
|
const benchmarkProtocol = benchmarkStrategy === 'stack'
|
|
427
473
|
? 'coordinated stack run'
|
|
428
474
|
: (benchmarkStrategy === 'single' ? 'pinned single-model baseline run' : '');
|
|
@@ -478,12 +524,24 @@ When done, reply: done.`;
|
|
|
478
524
|
}
|
|
479
525
|
|
|
480
526
|
if (kind === 'staleness' || kind === 'docs' || kind === 'review') {
|
|
527
|
+
const fileList = Array.isArray(context.files) && context.files.length
|
|
528
|
+
? context.files.map((file) => `- ${file}`).join('\n')
|
|
529
|
+
: '- target page or MAP entry from the task title\n- source file(s) that changed';
|
|
481
530
|
return `${baseRules}
|
|
482
531
|
|
|
483
532
|
Maintenance task: ${task}
|
|
484
533
|
|
|
485
|
-
|
|
534
|
+
Relevant files:
|
|
535
|
+
${fileList}
|
|
536
|
+
|
|
537
|
+
Figure out what needs to change and why. Create exactly one focused task in atris/TODO.md unless the drift truly requires separate commits.
|
|
486
538
|
For stale pages, read both the page and its sources to understand the drift.
|
|
539
|
+
The task row must include these fields so plan-review can prove it is executable:
|
|
540
|
+
- **Files:** concrete target page plus source file paths
|
|
541
|
+
- **Exit:** the observable post-update state
|
|
542
|
+
- **Verify:** one raw shell command that checks concrete facts and rejects stale phrases; use shell operators like \`&&\`, \`grep -q\`, or \`test\`, not Markdown backticks or English like "returns 1" / "shows today's date"
|
|
543
|
+
- **Rollback:** git checkout -- <changed-files> before commit, or git revert HEAD --no-edit after commit
|
|
544
|
+
Do not write tasks without Verify and Rollback. Do not use \`true\`, \`echo ok\`, or vague "review manually" verification.
|
|
487
545
|
|
|
488
546
|
When done, reply: done.`;
|
|
489
547
|
}
|
|
@@ -802,6 +860,56 @@ function getVerifyCommand(cwd, taskTitle) {
|
|
|
802
860
|
return { cmd: detectDefaultVerify(cwd), explicit: false };
|
|
803
861
|
}
|
|
804
862
|
|
|
863
|
+
function collectExplicitVerifyTasks(cwd) {
|
|
864
|
+
const todoPath = path.join(cwd, 'atris', 'TODO.md');
|
|
865
|
+
if (!fs.existsSync(todoPath)) return [];
|
|
866
|
+
const todo = parseTodo(todoPath);
|
|
867
|
+
return [...todo.inProgress, ...(todo.review || []), ...todo.backlog, ...todo.completed]
|
|
868
|
+
.filter((task) => task && task.verify)
|
|
869
|
+
.map((task) => ({
|
|
870
|
+
title: task.title,
|
|
871
|
+
verify: task.verify,
|
|
872
|
+
key: `${task.title}\0${task.verify}`,
|
|
873
|
+
}));
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
function findNewExplicitVerifyCommand(cwd, beforeKeys) {
|
|
877
|
+
const prior = beforeKeys instanceof Set ? beforeKeys : new Set(beforeKeys || []);
|
|
878
|
+
const added = collectExplicitVerifyTasks(cwd).filter((task) => !prior.has(task.key));
|
|
879
|
+
if (added.length !== 1) return null;
|
|
880
|
+
return { cmd: added[0].verify, explicit: true, task: added[0].title };
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
function shouldAdoptPlannedVerify(kind) {
|
|
884
|
+
return ['staleness', 'docs', 'review', 'inbox', 'cleanup', 'feature', 'lessons', 'imagined'].includes(kind);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
function validateVerifyCommandShape(cmd) {
|
|
888
|
+
const text = String(cmd || '').trim();
|
|
889
|
+
if (!text) return { ok: true };
|
|
890
|
+
if (text.includes('`')) {
|
|
891
|
+
return { ok: false, reason: 'Verify contains markdown backticks instead of a raw shell command' };
|
|
892
|
+
}
|
|
893
|
+
if (/\b(returns?|shows?|equals?|should|must)\b/i.test(text)) {
|
|
894
|
+
return { ok: false, reason: 'Verify contains prose expectations instead of shell operators/assertions' };
|
|
895
|
+
}
|
|
896
|
+
return { ok: true };
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
function haltInvalidVerify(cwd, context, verifyCmd, reason, startedAt, phaseResults = {}) {
|
|
900
|
+
writeLesson(cwd, 'verify-not-runnable', 'fail',
|
|
901
|
+
`Verify \`${verifyCmd}\` for "${context.task}" is not a runnable shell command: ${reason}. Tick halted.`);
|
|
902
|
+
return {
|
|
903
|
+
outcome: 'halted',
|
|
904
|
+
reason: 'verify-not-runnable',
|
|
905
|
+
phaseResults,
|
|
906
|
+
elapsedSeconds: Math.round((Date.now() - startedAt) / 1000),
|
|
907
|
+
verifyRan: false,
|
|
908
|
+
verifyPass: false,
|
|
909
|
+
verifyCmd,
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
|
|
805
913
|
/**
|
|
806
914
|
* Infer a default verify command from the repo shape. Order matters:
|
|
807
915
|
* package.json with a non-stub test script → `npm test`; then pytest/python;
|
|
@@ -878,7 +986,7 @@ Read from disk:
|
|
|
878
986
|
- atris/lessons.md (recent failures — last 20 lines)
|
|
879
987
|
|
|
880
988
|
Decide if the plan is safe to execute. Check:
|
|
881
|
-
1. Verify points at a falsifiable
|
|
989
|
+
1. Verify points at a falsifiable raw shell command or rubric (not \`true\`, \`echo ok\`, Markdown backticks, or English like "returns 1" / "shows today's date").
|
|
882
990
|
Prefer \`atris verify <slug> --section <name>\`.
|
|
883
991
|
2. Files are explicitly declared (not empty, not vague).
|
|
884
992
|
3. Rollback is named (commit, checkpoint, or \`git revert\`).
|
|
@@ -1170,8 +1278,15 @@ function runTaskOnce(context, options = {}) {
|
|
|
1170
1278
|
|
|
1171
1279
|
const phaseResults = {};
|
|
1172
1280
|
const startedAt = Date.now();
|
|
1173
|
-
|
|
1174
|
-
|
|
1281
|
+
let verifyResult = getVerifyCommand(cwd, context.task);
|
|
1282
|
+
let verifyCmd = verifyResult.cmd;
|
|
1283
|
+
const explicitVerifyBefore = new Set(
|
|
1284
|
+
collectExplicitVerifyTasks(cwd).map((task) => task.key)
|
|
1285
|
+
);
|
|
1286
|
+
const initialVerifyShape = validateVerifyCommandShape(verifyCmd);
|
|
1287
|
+
if (!initialVerifyShape.ok) {
|
|
1288
|
+
return haltInvalidVerify(cwd, context, verifyCmd, initialVerifyShape.reason, startedAt, phaseResults);
|
|
1289
|
+
}
|
|
1175
1290
|
|
|
1176
1291
|
// Guard: endgame tasks must have an explicit Verify field.
|
|
1177
1292
|
// Reactive signals (inbox, staleness, imagined) use npm test as default.
|
|
@@ -1264,6 +1379,18 @@ function runTaskOnce(context, options = {}) {
|
|
|
1264
1379
|
}
|
|
1265
1380
|
}
|
|
1266
1381
|
|
|
1382
|
+
if (!verifyResult.explicit && shouldAdoptPlannedVerify(context.kind)) {
|
|
1383
|
+
const plannedVerify = findNewExplicitVerifyCommand(cwd, explicitVerifyBefore);
|
|
1384
|
+
if (plannedVerify) {
|
|
1385
|
+
verifyResult = plannedVerify;
|
|
1386
|
+
verifyCmd = plannedVerify.cmd;
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
const plannedVerifyShape = validateVerifyCommandShape(verifyCmd);
|
|
1390
|
+
if (!plannedVerifyShape.ok) {
|
|
1391
|
+
return haltInvalidVerify(cwd, context, verifyCmd, plannedVerifyShape.reason, startedAt, phaseResults);
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1267
1394
|
// Phase: do
|
|
1268
1395
|
{
|
|
1269
1396
|
const t0 = Date.now();
|
|
@@ -1975,12 +2102,13 @@ function findCodeTodos(cwd) {
|
|
|
1975
2102
|
try {
|
|
1976
2103
|
const out = execFileSync('git', [
|
|
1977
2104
|
'grep', '-n', '-I', '-E', '(TODO|FIXME)',
|
|
1978
|
-
'--', ':!test/', ':!node_modules/', ':!atris/', ':!**/*.md'
|
|
2105
|
+
'--', ':!test/', ':!node_modules/', ':!atris/', ':!**/_archive/**', ':!**/*.md'
|
|
1979
2106
|
], { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
1980
2107
|
const results = [];
|
|
1981
2108
|
for (const raw of out.split('\n').filter(Boolean)) {
|
|
1982
2109
|
const m = raw.match(/^([^:]+):(\d+):(.*)$/);
|
|
1983
2110
|
if (!m) continue;
|
|
2111
|
+
if (m[1].split(/[\\/]/).includes('_archive')) continue;
|
|
1984
2112
|
const line = m[3];
|
|
1985
2113
|
// A real TODO is a comment marker at the start of the line (allowing
|
|
1986
2114
|
// leading indent) followed by TODO/FIXME and at least one word. This
|
|
@@ -2160,16 +2288,63 @@ function isLessonResolved(lessonLine, cwd, options = {}) {
|
|
|
2160
2288
|
if (!slugMatch) return false;
|
|
2161
2289
|
const slug = slugMatch[1];
|
|
2162
2290
|
|
|
2291
|
+
if (isCleanMapBrokenRefFailLesson(lessonLine, cwd)) return true;
|
|
2292
|
+
|
|
2163
2293
|
// Detector-backed check (typed lesson sidecar)
|
|
2164
2294
|
const meta = options.meta || loadLessonMetadata(cwd)[slug];
|
|
2165
2295
|
if (meta && meta.detector) {
|
|
2166
2296
|
return runLessonDetector(meta.detector, cwd, options.detectorTimeout);
|
|
2167
2297
|
}
|
|
2168
2298
|
|
|
2299
|
+
if (inlinePythonVerifyFailureNowPasses(lessonLine, cwd, options.detectorTimeout)) return true;
|
|
2300
|
+
|
|
2169
2301
|
// Legacy fallback: keyword grep against referenced files.
|
|
2170
2302
|
return isLessonResolvedLegacy(lessonLine, cwd);
|
|
2171
2303
|
}
|
|
2172
2304
|
|
|
2305
|
+
function isCleanMapBrokenRefFailLesson(lessonLine, cwd) {
|
|
2306
|
+
const text = String(lessonLine || '').toLowerCase();
|
|
2307
|
+
if (!/fix \d+ broken references? in map\.md/.test(text)) return false;
|
|
2308
|
+
return repoMapAuditReportsClean(cwd);
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2311
|
+
function extractInlinePythonVerifyFailure(lessonLine) {
|
|
2312
|
+
const commandMatch = String(lessonLine || '').match(/Verify command\s+``([\s\S]*?)``\s+failed/i);
|
|
2313
|
+
if (!commandMatch) return null;
|
|
2314
|
+
const matches = [...commandMatch[1].matchAll(/\b(python3?)\s+-c\s+(["'])([\s\S]*?)\2/g)];
|
|
2315
|
+
const match = matches[matches.length - 1];
|
|
2316
|
+
if (!match) return null;
|
|
2317
|
+
return {
|
|
2318
|
+
executable: match[1],
|
|
2319
|
+
code: match[3].replace(/\\"/g, '"').replace(/\\'/g, "'")
|
|
2320
|
+
};
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
function inlinePythonVerifyFailureNowPasses(lessonLine, cwd, timeout = 10000) {
|
|
2324
|
+
const parsed = extractInlinePythonVerifyFailure(lessonLine);
|
|
2325
|
+
if (!parsed) return false;
|
|
2326
|
+
const result = spawnSync(parsed.executable, ['-c', parsed.code], {
|
|
2327
|
+
cwd,
|
|
2328
|
+
encoding: 'utf8',
|
|
2329
|
+
timeout,
|
|
2330
|
+
stdio: ['ignore', 'ignore', 'ignore']
|
|
2331
|
+
});
|
|
2332
|
+
return result.status === 0;
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
function legacyLessonFileRefs(lessonLine) {
|
|
2336
|
+
const fileRefs = [];
|
|
2337
|
+
const filePattern = /`([a-zA-Z0-9_/./-]+\.[a-zA-Z]+(?::\d+(?:-\d+)?)?)`/g;
|
|
2338
|
+
let m;
|
|
2339
|
+
while ((m = filePattern.exec(lessonLine)) !== null) {
|
|
2340
|
+
const ref = m[1].replace(/:\d+(-\d+)?$/, '');
|
|
2341
|
+
if (ref.includes('/') || ref.endsWith('.js') || ref.endsWith('.md') || ref.endsWith('.ts')) {
|
|
2342
|
+
fileRefs.push(ref);
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
return fileRefs;
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2173
2348
|
/**
|
|
2174
2349
|
* The pre-v3.8 resolver — kept as an internal fallback for prose-only lessons
|
|
2175
2350
|
* that don't have detector metadata yet. Never auto-promotes a prose lesson to
|
|
@@ -2182,16 +2357,7 @@ function isLessonResolvedLegacy(lessonLine, cwd) {
|
|
|
2182
2357
|
if (!slugMatch) return false;
|
|
2183
2358
|
const slug = slugMatch[1];
|
|
2184
2359
|
|
|
2185
|
-
|
|
2186
|
-
const fileRefs = [];
|
|
2187
|
-
const filePattern = /`([a-zA-Z0-9_/./-]+\.[a-zA-Z]+(?::\d+(?:-\d+)?)?)`/g;
|
|
2188
|
-
let m;
|
|
2189
|
-
while ((m = filePattern.exec(lessonLine)) !== null) {
|
|
2190
|
-
const ref = m[1].replace(/:\d+(-\d+)?$/, ''); // strip line numbers
|
|
2191
|
-
if (ref.includes('/') || ref.endsWith('.js') || ref.endsWith('.md') || ref.endsWith('.ts')) {
|
|
2192
|
-
fileRefs.push(ref);
|
|
2193
|
-
}
|
|
2194
|
-
}
|
|
2360
|
+
const fileRefs = legacyLessonFileRefs(lessonLine);
|
|
2195
2361
|
|
|
2196
2362
|
if (fileRefs.length === 0) return false;
|
|
2197
2363
|
|
|
@@ -2274,6 +2440,9 @@ function pickUnresolvedFailLesson(cwd) {
|
|
|
2274
2440
|
const candidates = [];
|
|
2275
2441
|
for (const lesson of lessons) {
|
|
2276
2442
|
if (lesson.verdict !== 'fail') continue;
|
|
2443
|
+
if (lesson.id === 'verify-not-falsifiable') continue;
|
|
2444
|
+
if (lesson.id === 'no-verify-field') continue;
|
|
2445
|
+
if (lesson.id === 'verify-failed' && lesson.legacy) continue;
|
|
2277
2446
|
if (lesson.resolvedTag) continue;
|
|
2278
2447
|
// Typed lesson with explicit status wins — respect the sidecar.
|
|
2279
2448
|
// `resolved` = done. `observed` = process rule, not a fixable code state.
|
|
@@ -2284,6 +2453,7 @@ function pickUnresolvedFailLesson(cwd) {
|
|
|
2284
2453
|
if (s === 'resolved' || s === 'observed') continue;
|
|
2285
2454
|
if (s === 'attempted' && (lesson.meta.attempts || 0) >= MAX_ATTEMPTS) continue;
|
|
2286
2455
|
}
|
|
2456
|
+
if (lesson.legacy && legacyLessonFileRefs(lesson.line).length === 0) continue;
|
|
2287
2457
|
// Detector-backed or legacy grep check.
|
|
2288
2458
|
if (isLessonResolved(lesson.line, cwd, { meta: lesson.meta })) continue;
|
|
2289
2459
|
|
|
@@ -2658,6 +2828,7 @@ async function autopilotAtris(description, options = {}) {
|
|
|
2658
2828
|
const context = {
|
|
2659
2829
|
task: suggestion.task,
|
|
2660
2830
|
kind: suggestion.kind,
|
|
2831
|
+
...(suggestion.files ? { files: suggestion.files } : {}),
|
|
2661
2832
|
...(suggestion.lessonLine ? { lessonLine: suggestion.lessonLine } : {}),
|
|
2662
2833
|
...(suggestion.lessonSlug ? { lessonSlug: suggestion.lessonSlug } : {}),
|
|
2663
2834
|
...(suggestion.lessonDate ? { lessonDate: suggestion.lessonDate } : {})
|
|
@@ -3052,11 +3223,15 @@ module.exports = {
|
|
|
3052
3223
|
proposeCandidateHorizons,
|
|
3053
3224
|
recordTickCommit,
|
|
3054
3225
|
regressionCheck,
|
|
3226
|
+
repoMapAuditReportsClean,
|
|
3227
|
+
isCleanMapBrokenRefFailLesson,
|
|
3228
|
+
inlinePythonVerifyFailureNowPasses,
|
|
3055
3229
|
runPlanReview,
|
|
3056
3230
|
runTaskOnce,
|
|
3057
3231
|
buildPlanReviewPrompt,
|
|
3058
3232
|
parseVerdict,
|
|
3059
3233
|
scoreEndgameCandidates,
|
|
3060
3234
|
suggestNextTask,
|
|
3235
|
+
shouldSkipAutoHumanGate,
|
|
3061
3236
|
writeLesson
|
|
3062
3237
|
};
|