atris 3.62.0 → 3.64.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/atris/features/README.md +8 -0
- package/atris/skills/README.md +1 -0
- package/atris/skills/commerce/SKILL.md +120 -0
- package/atris/skills/x-search/SKILL.md +3 -1
- package/ax +4 -3
- package/bin/atris.js +22 -2
- package/cli/atris_code.py +25 -1
- package/commands/auth.js +79 -10
- package/commands/autoland.js +11 -1
- package/commands/browse.js +2 -2
- package/commands/business.js +3 -3
- package/commands/buy.js +242 -0
- package/commands/deck.js +2 -2
- package/commands/engine.js +122 -13
- package/commands/feedback.js +2 -2
- package/commands/fork.js +2 -2
- package/commands/gm.js +2 -1
- package/commands/improve.js +142 -8
- package/commands/integrations.js +4 -3
- package/commands/loops.js +2 -1
- package/commands/member.js +82 -15
- package/commands/mission.js +4 -1
- package/commands/pack-craft.js +2 -2
- package/commands/publish.js +2 -2
- package/commands/radar.js +3 -2
- package/commands/router.js +2 -2
- package/commands/task.js +91 -2
- package/commands/transactions.js +115 -0
- package/commands/truth.js +2 -2
- package/commands/x-search.js +81 -10
- package/commands/youtube.js +24 -1
- package/lib/autoland.js +3 -2
- package/lib/ax-auto-lane.js +2 -2
- package/lib/brief-rules.js +11 -0
- package/lib/chat-log-scan.js +2 -1
- package/lib/ci-runner.js +3 -2
- package/lib/codex-flight.js +2 -1
- package/lib/commerce.js +90 -0
- package/lib/deck-history.js +2 -2
- package/lib/deck-review.js +3 -3
- package/lib/design-api.js +2 -2
- package/lib/engine-registry.js +102 -18
- package/lib/first-minute.js +2 -2
- package/lib/fleet.js +8 -36
- package/lib/git-spawn.js +5 -2
- package/lib/ignored-cache.js +51 -0
- package/lib/known-commands.js +1 -1
- package/lib/manifest.js +2 -2
- package/lib/map-refs.js +70 -4
- package/lib/member-engine.js +15 -1
- package/lib/member-scaffold.js +2 -0
- package/lib/member-type.js +27 -0
- package/lib/pack-capabilities.js +2 -1
- package/lib/permission-grants.js +2 -2
- package/lib/pulse.js +5 -5
- package/lib/roster-runs.js +13 -2
- package/lib/runner-command.js +54 -3
- package/lib/sandbox-pids.js +113 -0
- package/lib/secret-gateway.js +3 -13
- package/lib/state-home.js +106 -0
- package/lib/task-db.js +4 -3
- package/lib/task-prune.js +462 -0
- package/lib/x-post-check.js +497 -0
- package/package.json +1 -1
- package/scripts/det/data/xsearch-answer.json +24 -0
- package/scripts/det/data/xsearch-live-2026-09-29.json +93 -0
- package/scripts/det/xsearch-bench.js +307 -0
- package/scripts/det/ytnotes +122 -16
- package/scripts/det/ytrail-bench.js +215 -0
- package/scripts/det/ytrail-eval.js +128 -56
- package/utils/auth.js +3 -3
- package/utils/update-check.js +2 -2
package/commands/task.js
CHANGED
|
@@ -253,6 +253,8 @@ atris task - durable local task state (SQLite, gitignored)
|
|
|
253
253
|
Sweep off-roadmap/duplicate work as archived (not failed);
|
|
254
254
|
--from-failed opts in to relabel a fail-closed row (never done)
|
|
255
255
|
atris task clear-done [--before <days>] [--dry-run] [--json] Archive completed rows, oldest first
|
|
256
|
+
atris task prune-test-junk [--yes] [--vacuum] [--json]
|
|
257
|
+
Count rows test runs left from temp folders; --yes backs up the db, then removes them
|
|
256
258
|
atris task keep [--json] Put away finished and untouched work, then refresh the list
|
|
257
259
|
atris task reap-mission-blockers [--json] Close blocker rows whose missions are complete or stopped
|
|
258
260
|
atris task relabel-archived [--dry-run|--apply]
|
|
@@ -1421,7 +1423,7 @@ function taskReviewSummary(task) {
|
|
|
1421
1423
|
const reviewPassCount = Number(metadata.agent_review_pass_count || payload.review_pass_count || 0);
|
|
1422
1424
|
const agentCertified = metadata.agent_certified === true
|
|
1423
1425
|
|| payload.agent_certified === true
|
|
1424
|
-
|| reviewPassCount >= AGENT_CERTIFICATION_REVIEW_PASSES;
|
|
1426
|
+
|| (reviewPassCount >= AGENT_CERTIFICATION_REVIEW_PASSES && reviewIntegrity.hasIndependentReview(task));
|
|
1425
1427
|
const reviewedEventHas = (key) => reviewed && reviewed.event_type === 'reviewed'
|
|
1426
1428
|
&& Object.prototype.hasOwnProperty.call(payload, key);
|
|
1427
1429
|
const clearedReviewFields = new Set(Array.isArray(payload.cleared_review_fields) ? payload.cleared_review_fields : []);
|
|
@@ -7922,6 +7924,8 @@ function publicWords(value) {
|
|
|
7922
7924
|
]).has(word));
|
|
7923
7925
|
}
|
|
7924
7926
|
|
|
7927
|
+
const { memberTypeOf } = require('../lib/member-type');
|
|
7928
|
+
|
|
7925
7929
|
function parseMemberFrontmatter(text) {
|
|
7926
7930
|
const source = String(text || '');
|
|
7927
7931
|
if (!source.startsWith('---')) return {};
|
|
@@ -7952,6 +7956,7 @@ function readTeamMembers(root = process.cwd()) {
|
|
|
7952
7956
|
slug,
|
|
7953
7957
|
role: cleanPublicText(frontmatter.role || slug.replace(/[-_]/g, ' '), 120),
|
|
7954
7958
|
description: cleanPublicText(frontmatter.description || '', 240),
|
|
7959
|
+
type: memberTypeOf(frontmatter).type,
|
|
7955
7960
|
path: memberPath,
|
|
7956
7961
|
};
|
|
7957
7962
|
})
|
|
@@ -8018,6 +8023,8 @@ function chooseTaskOwner({ purpose, tag, requestedOwner, root = process.cwd() }
|
|
|
8018
8023
|
const words = publicWords(`${purpose || ''} ${tag || ''}`);
|
|
8019
8024
|
let best = null;
|
|
8020
8025
|
for (const member of members) {
|
|
8026
|
+
// A person can be named as an owner, but routing never hands them work as if they were an AI.
|
|
8027
|
+
if (member.type !== 'ai') continue;
|
|
8021
8028
|
const score = scoreTeamMember(member, words, tag);
|
|
8022
8029
|
if (!best || score > best.score) best = { member, score };
|
|
8023
8030
|
}
|
|
@@ -8836,7 +8843,7 @@ function taskAllowsReviewChat(task, { allowCertified = false } = {}) {
|
|
|
8836
8843
|
if (allowCertified) return true;
|
|
8837
8844
|
if (review.agent_certified === true || metadata.agent_certified === true) return false;
|
|
8838
8845
|
const reviewPassCount = Number(review.agent_review_pass_count || metadata.agent_review_pass_count || 0);
|
|
8839
|
-
if (reviewPassCount >= AGENT_CERTIFICATION_REVIEW_PASSES) return false;
|
|
8846
|
+
if (reviewPassCount >= AGENT_CERTIFICATION_REVIEW_PASSES && reviewIntegrity.hasIndependentReview(task)) return false;
|
|
8840
8847
|
const handoff = reviewHandoffForTask(task, { suppressExistingFollowUp: true });
|
|
8841
8848
|
return !(handoff && (handoff.next_action === 'continue_work' || handoff.next_action === 'human_accept_waiting'));
|
|
8842
8849
|
}
|
|
@@ -9809,6 +9816,78 @@ function cmdClearDone(args) {
|
|
|
9809
9816
|
console.log(`cleared ${candidates.length} completed task(s).`);
|
|
9810
9817
|
}
|
|
9811
9818
|
|
|
9819
|
+
function formatBytes(bytes) {
|
|
9820
|
+
const mb = Number(bytes || 0) / (1024 * 1024);
|
|
9821
|
+
return mb >= 1 ? `${mb.toFixed(1)} MB` : `${Math.round(Number(bytes || 0) / 1024)} KB`;
|
|
9822
|
+
}
|
|
9823
|
+
|
|
9824
|
+
// Test runs that forgot ATRIS_TASKS_DB left rows in the shared database
|
|
9825
|
+
// under temp folders. Default is a read-only count; --yes backs up, then
|
|
9826
|
+
// removes those rows and their history. Real project rows are never touched.
|
|
9827
|
+
function cmdPruneTestJunk(args) {
|
|
9828
|
+
const taskDb = getTaskDb();
|
|
9829
|
+
const prune = require('../lib/task-prune');
|
|
9830
|
+
const dbPath = taskDb.getDbPath();
|
|
9831
|
+
const apply = hasFlag(args, '--yes') && !hasFlag(args, '--dry-run');
|
|
9832
|
+
const vacuum = apply && hasFlag(args, '--vacuum');
|
|
9833
|
+
if (!apply) {
|
|
9834
|
+
const plan = prune.dryRun(dbPath);
|
|
9835
|
+
const { rows, ...summary } = plan;
|
|
9836
|
+
if (wantsJson(args)) {
|
|
9837
|
+
printJson({ ok: true, action: 'prune-test-junk', dry_run: true, ...summary });
|
|
9838
|
+
return;
|
|
9839
|
+
}
|
|
9840
|
+
if (plan.missing) {
|
|
9841
|
+
console.log(`no task database at ${dbPath}. nothing to prune.`);
|
|
9842
|
+
return;
|
|
9843
|
+
}
|
|
9844
|
+
const r = plan.remove;
|
|
9845
|
+
console.log('prune-test-junk dry run: nothing was changed.');
|
|
9846
|
+
console.log(`database: ${dbPath} (${formatBytes(plan.db_bytes)}, ${plan.total_tasks} tasks)`);
|
|
9847
|
+
console.log(`would remove ${r.tasks} test task(s) from temp folders, ${r.history_rows} history row(s), `
|
|
9848
|
+
+ `${r.stray_history_rows} stray history row(s), ${r.part_uses} part use row(s).`);
|
|
9849
|
+
for (const g of plan.groups) {
|
|
9850
|
+
console.log(` ${g.prefix.padEnd(22)} ${String(g.tasks).padStart(7)} tasks ${String(g.history_rows + g.stray_history_rows).padStart(8)} history rows`);
|
|
9851
|
+
}
|
|
9852
|
+
const k = plan.kept;
|
|
9853
|
+
console.log(`kept: ${k.folder_exists} temp task(s) whose folder still exists, ${k.recent} active in the last ${plan.grace_hours} hour(s), `
|
|
9854
|
+
+ `${k.held} with history from a real project, ${k.stray_held + k.stray_recent} leftover history set(s) that are recent or name a real project.`);
|
|
9855
|
+
if (r.tasks || r.stray_history_rows) {
|
|
9856
|
+
console.log('run with --yes to remove them. a backup copy is written next to the database first.');
|
|
9857
|
+
}
|
|
9858
|
+
return;
|
|
9859
|
+
}
|
|
9860
|
+
|
|
9861
|
+
if (vacuum && !wantsJson(args)) {
|
|
9862
|
+
console.log('note: --vacuum rewrites the whole file and needs every other atris process idle; it waits a few seconds, then gives up.');
|
|
9863
|
+
}
|
|
9864
|
+
let result;
|
|
9865
|
+
try {
|
|
9866
|
+
result = prune.applyPrune(dbPath, { vacuum });
|
|
9867
|
+
} catch (err) {
|
|
9868
|
+
failTask('atris task prune-test-junk', 'prune_failed', `prune-test-junk stopped: ${err && err.message || err}`, 1);
|
|
9869
|
+
}
|
|
9870
|
+
if (wantsJson(args)) {
|
|
9871
|
+
printJson({ ok: true, action: 'prune-test-junk', dry_run: false, ...result });
|
|
9872
|
+
return;
|
|
9873
|
+
}
|
|
9874
|
+
if (result.missing) {
|
|
9875
|
+
console.log(`no task database at ${dbPath}. nothing to prune.`);
|
|
9876
|
+
return;
|
|
9877
|
+
}
|
|
9878
|
+
const r = result.removed;
|
|
9879
|
+
console.log(`backup: ${result.backup_path} (${result.backup_tasks} tasks)`);
|
|
9880
|
+
console.log(`removed ${r.tasks} test task(s), ${r.history_rows} history row(s), `
|
|
9881
|
+
+ `${r.stray_history_rows} stray history row(s), ${r.part_uses} part use row(s).`);
|
|
9882
|
+
const sk = result.skipped;
|
|
9883
|
+
if (sk.held || sk.changed_since_backup || sk.recent || sk.folder_exists) {
|
|
9884
|
+
console.log(`left ${sk.changed_since_backup} that changed after the backup, ${sk.recent} that became active, `
|
|
9885
|
+
+ `${sk.folder_exists} whose folder came back, and ${sk.held} with real-project history.`);
|
|
9886
|
+
}
|
|
9887
|
+
console.log(`database now has ${result.total_tasks} tasks, ${formatBytes(result.bytes_after)} on disk`
|
|
9888
|
+
+ (result.vacuumed ? ' after vacuum.' : '. run again with --yes --vacuum to shrink the file.'));
|
|
9889
|
+
}
|
|
9890
|
+
|
|
9812
9891
|
function cmdReapMissionBlockers(args) {
|
|
9813
9892
|
const taskDb = getTaskDb();
|
|
9814
9893
|
const db = taskDb.open();
|
|
@@ -10463,6 +10542,13 @@ function stampReadyVerifyMetadata(taskDb, db, taskId, verify) {
|
|
|
10463
10542
|
if (!row) return;
|
|
10464
10543
|
const metadata = row.metadata && typeof row.metadata === 'object' ? { ...row.metadata } : {};
|
|
10465
10544
|
metadata.verify = verify;
|
|
10545
|
+
// A real verify stamped at ready means the creation-time degraded flag is
|
|
10546
|
+
// stale: clear it so `task show` stops warning about a missing check. A
|
|
10547
|
+
// diff-only verify keeps the flag; it still cannot fail on real breakage.
|
|
10548
|
+
if (verify.toLowerCase() !== 'git diff --check') {
|
|
10549
|
+
delete metadata.verification_status;
|
|
10550
|
+
delete metadata.verification_degraded_reason;
|
|
10551
|
+
}
|
|
10466
10552
|
db.prepare(`
|
|
10467
10553
|
UPDATE tasks
|
|
10468
10554
|
SET metadata = ?,
|
|
@@ -13369,6 +13455,7 @@ async function runTaskCommand(args) {
|
|
|
13369
13455
|
case 'fail': return cmdDone([...rest, '--failed']);
|
|
13370
13456
|
case 'archive': return cmdArchive(rest);
|
|
13371
13457
|
case 'clear-done': return cmdClearDone(rest);
|
|
13458
|
+
case 'prune-test-junk': return cmdPruneTestJunk(rest);
|
|
13372
13459
|
case 'keep': return cmdKeep(rest);
|
|
13373
13460
|
case 'reap-mission-blockers':
|
|
13374
13461
|
case 'reap-blockers':
|
|
@@ -13465,6 +13552,8 @@ async function run(args) {
|
|
|
13465
13552
|
|
|
13466
13553
|
module.exports = {
|
|
13467
13554
|
run,
|
|
13555
|
+
chooseTaskOwner,
|
|
13556
|
+
readTeamMembers,
|
|
13468
13557
|
createTaskApiServer,
|
|
13469
13558
|
taskDayGroups,
|
|
13470
13559
|
taskDayTextGroups,
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// atris transactions: every order, fee, trade, and wallet send, newest first.
|
|
4
|
+
// Read only. Backend: GET /api/transactions.
|
|
5
|
+
|
|
6
|
+
const { apiRequestJson } = require('../utils/api');
|
|
7
|
+
const { loadCredentials } = require('../utils/auth');
|
|
8
|
+
const { NOT_LOGGED_IN, oneLine, printResult } = require('../lib/developer-api');
|
|
9
|
+
const { parseFlags, formatCents, errorFrom, commerceToken } = require('../lib/commerce');
|
|
10
|
+
|
|
11
|
+
const TX_KINDS = ['ticket', 'shop', 'flight', 'test_purchase', 'fee', 'trade', 'wallet_send'];
|
|
12
|
+
|
|
13
|
+
function showTransactionsHelp(log = console.log) {
|
|
14
|
+
log(`usage: atris transactions [--since YYYY-MM-DD] [--kind k[,k]] [--limit n] [--include-quotes] [--json]
|
|
15
|
+
|
|
16
|
+
list what you bought, paid in fees, traded, or sent, newest first. read only.
|
|
17
|
+
|
|
18
|
+
--since only rows on or after this date, like 2026-09-01
|
|
19
|
+
--kind ${TX_KINDS.join(', ')}
|
|
20
|
+
--limit 1 to 200 (default 50)
|
|
21
|
+
--include-quotes also list quotes nobody approved`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function clip(text, width) {
|
|
25
|
+
const s = String(text == null ? '' : text).replace(/\s+/g, ' ').trim();
|
|
26
|
+
return s.length > width ? `${s.slice(0, width - 1)}…` : s;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function whenText(value) {
|
|
30
|
+
const s = String(value || '');
|
|
31
|
+
const m = s.match(/^(\d{4}-\d{2}-\d{2})[T ](\d{2}:\d{2})/);
|
|
32
|
+
return m ? `${m[1]} ${m[2]}` : clip(s, 16);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function renderTransactions(data = {}) {
|
|
36
|
+
const rows = Array.isArray(data.transactions) ? data.transactions : [];
|
|
37
|
+
const lines = [];
|
|
38
|
+
if (!rows.length) lines.push('No transactions yet.');
|
|
39
|
+
else {
|
|
40
|
+
const table = rows.map((r) => [
|
|
41
|
+
whenText(r.when),
|
|
42
|
+
clip(r.kind, 12),
|
|
43
|
+
clip(r.what, 40),
|
|
44
|
+
formatCents(r.amount_cents, r.currency),
|
|
45
|
+
clip(r.state, 16),
|
|
46
|
+
]);
|
|
47
|
+
const head = ['WHEN', 'KIND', 'WHAT', 'AMOUNT', 'STATE'];
|
|
48
|
+
const widths = head.map((h, i) => Math.max(h.length, ...table.map((row) => row[i].length)));
|
|
49
|
+
const fmt = (row) => row.map((cell, i) => (i === 3 ? cell.padStart(widths[i]) : cell.padEnd(widths[i])))
|
|
50
|
+
.join(' ').trimEnd();
|
|
51
|
+
lines.push(fmt(head));
|
|
52
|
+
for (const row of table) lines.push(fmt(row));
|
|
53
|
+
}
|
|
54
|
+
const down = Object.entries(data.sources || {}).filter(([, v]) => v !== 'ok').map(([k]) => k);
|
|
55
|
+
if (down.length) lines.push('', `Partial list: could not load ${down.join(', ')}.`);
|
|
56
|
+
return lines.join('\n');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function buildQuery(flags) {
|
|
60
|
+
const params = new URLSearchParams();
|
|
61
|
+
if (flags.since != null) {
|
|
62
|
+
const since = String(flags.since).trim();
|
|
63
|
+
if (!/^\d{4}-\d{2}-\d{2}/.test(since)) return { ok: false, error: '--since must be a date like 2026-09-01' };
|
|
64
|
+
params.set('since', since);
|
|
65
|
+
}
|
|
66
|
+
const rawKind = flags.kind != null ? flags.kind : flags.kinds;
|
|
67
|
+
if (rawKind != null) {
|
|
68
|
+
const kinds = String(rawKind).split(',').map((k) => k.trim().toLowerCase()).filter(Boolean);
|
|
69
|
+
const bad = kinds.filter((k) => !TX_KINDS.includes(k));
|
|
70
|
+
if (bad.length || !kinds.length) return { ok: false, error: `--kind must be one of ${TX_KINDS.join(', ')}` };
|
|
71
|
+
params.set('kinds', kinds.join(','));
|
|
72
|
+
}
|
|
73
|
+
if (flags.limit != null) {
|
|
74
|
+
const limit = Number(flags.limit);
|
|
75
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 200) return { ok: false, error: '--limit must be a number from 1 to 200' };
|
|
76
|
+
params.set('limit', String(limit));
|
|
77
|
+
}
|
|
78
|
+
if (flags.include_quotes === true) params.set('include_quotes', 'true');
|
|
79
|
+
const qs = params.toString();
|
|
80
|
+
return { ok: true, path: qs ? `/transactions?${qs}` : '/transactions' };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function transactionsCommand(args = [], deps = {}) {
|
|
84
|
+
const io = { log: deps.log || console.log, err: deps.err || console.error };
|
|
85
|
+
const { flags, pos } = parseFlags(args);
|
|
86
|
+
if (flags.help || pos[0] === 'help') {
|
|
87
|
+
showTransactionsHelp(io.log);
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
|
90
|
+
const json = flags.json === true;
|
|
91
|
+
const fail = (error, status) => {
|
|
92
|
+
printResult({ json, ok: false, error, payload: { ok: false, error, ...(status ? { status } : {}) } }, io);
|
|
93
|
+
return 1;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const query = buildQuery(flags);
|
|
97
|
+
if (!query.ok) return fail(query.error);
|
|
98
|
+
|
|
99
|
+
const load = deps.loadCredentials || loadCredentials;
|
|
100
|
+
const request = deps.apiRequestJson || apiRequestJson;
|
|
101
|
+
const token = commerceToken(load(), 'transactions:read', deps.now ? deps.now() : Date.now());
|
|
102
|
+
if (!token) return fail(NOT_LOGGED_IN);
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const result = await request(query.path, { method: 'GET', token });
|
|
106
|
+
if (!result || !result.ok) return fail(errorFrom(result), result && result.status);
|
|
107
|
+
const data = result.data || {};
|
|
108
|
+
printResult({ json, ok: true, text: renderTransactions(data), payload: { ok: true, ...data } }, io);
|
|
109
|
+
return 0;
|
|
110
|
+
} catch (error) {
|
|
111
|
+
return fail(oneLine(error && error.message));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
module.exports = { transactionsCommand };
|
package/commands/truth.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
const fs = require('fs');
|
|
9
9
|
const path = require('path');
|
|
10
|
-
const
|
|
10
|
+
const { stateHome } = require('../lib/state-home');
|
|
11
11
|
const { execFileSync } = require('child_process');
|
|
12
12
|
const taskDb = require('../lib/task-db');
|
|
13
13
|
const { parseScopeFlag } = require('../lib/cli-scope');
|
|
@@ -172,7 +172,7 @@ function loadFeatures(cwd) {
|
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
function loadHeartbeats() {
|
|
175
|
-
const hbDir = path.join(
|
|
175
|
+
const hbDir = path.join(stateHome(), '.atris', 'heartbeat');
|
|
176
176
|
const registry = readJson(path.join(hbDir, 'registry.json'));
|
|
177
177
|
const state = readJson(path.join(hbDir, 'state.json')) || {};
|
|
178
178
|
if (!registry || !Array.isArray(registry.jobs)) return [];
|
package/commands/x-search.js
CHANGED
|
@@ -4,7 +4,9 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { apiRequestJson } = require('../utils/api');
|
|
6
6
|
const { ensureBilledCommandAuth } = require('./auth');
|
|
7
|
+
const { spawnSync } = require('child_process');
|
|
7
8
|
const applyGate = require('../lib/apply-gate');
|
|
9
|
+
const { checkXPosts, annotateContent, formatTally } = require('../lib/x-post-check');
|
|
8
10
|
const {
|
|
9
11
|
fileTeachExperiment,
|
|
10
12
|
extractTeachNumbers,
|
|
@@ -23,9 +25,10 @@ const KEEP_RULE = 'keep only if measure.py moves 0→1. scores 1 only when the f
|
|
|
23
25
|
|
|
24
26
|
function showXSearchHelp(output = console.log, commandName = 'atris x-search') {
|
|
25
27
|
output('');
|
|
26
|
-
output(`Usage: ${commandName} "<query>" [--limit N] [--days N] [--save] [--json]`);
|
|
27
|
-
output(` ${commandName} person --name <name> [--handle <h>] [--company <c>] [--context <text>] [--save] [--json]`);
|
|
28
|
+
output(`Usage: ${commandName} "<query>" [--limit N] [--days N] [--save] [--json] [--no-check]`);
|
|
29
|
+
output(` ${commandName} person --name <name> [--handle <h>] [--company <c>] [--context <text>] [--save] [--json] [--no-check]`);
|
|
28
30
|
output(` ${commandName} unsave <query-or-source>`);
|
|
31
|
+
output(` ${commandName} bench [--quick|--free]`);
|
|
29
32
|
output('');
|
|
30
33
|
output(`Search X/Twitter via Atris (${COST_HINT}).`);
|
|
31
34
|
output('Requires login. Same auth path as atris youtube process.');
|
|
@@ -33,13 +36,15 @@ function showXSearchHelp(output = console.log, commandName = 'atris x-search') {
|
|
|
33
36
|
output('--save files a brief only when the result is rich.');
|
|
34
37
|
output('unsave deletes the filed brief, apply stub, and matching experiment pack (no paid calls).');
|
|
35
38
|
output('Empty or failed search prints credits refunded only when the server marks a refund.');
|
|
39
|
+
output('Each quoted post is looked up on X for free and marked checked, unverified, or unknown.');
|
|
36
40
|
output('');
|
|
37
41
|
output('Options:');
|
|
38
42
|
output(' --limit <n> Max results hint (search only)');
|
|
39
43
|
output(' --days <n> Only tweets from the last N days (search only)');
|
|
40
44
|
output(' --save File brief, journal, apply; rich results mint a keep/revert experiment');
|
|
41
45
|
output(' --unsave Delete filed brief, apply stub, and matching experiment pack (no paid calls)');
|
|
42
|
-
output(' --json Print the raw JSON response');
|
|
46
|
+
output(' --json Print the raw JSON response (plus a checks list)');
|
|
47
|
+
output(' --no-check Skip looking up the quoted posts on X');
|
|
43
48
|
output(' -h, --help This help');
|
|
44
49
|
output('');
|
|
45
50
|
output('Person options:');
|
|
@@ -55,6 +60,7 @@ function showXSearchHelp(output = console.log, commandName = 'atris x-search') {
|
|
|
55
60
|
output(` ${commandName} person --name "Leah Bonvissuto" --handle leahbon`);
|
|
56
61
|
output(` ${commandName} unsave "MCP agents"`);
|
|
57
62
|
output(` ${commandName} --unsave "MCP agents"`);
|
|
63
|
+
output(` ${commandName} bench --free`);
|
|
58
64
|
output('');
|
|
59
65
|
}
|
|
60
66
|
|
|
@@ -81,6 +87,7 @@ function parseSearchArgs(argv = []) {
|
|
|
81
87
|
json: false,
|
|
82
88
|
save: false,
|
|
83
89
|
unsave: false,
|
|
90
|
+
check: true,
|
|
84
91
|
query: null,
|
|
85
92
|
limit: null,
|
|
86
93
|
daysBack: null,
|
|
@@ -100,6 +107,8 @@ function parseSearchArgs(argv = []) {
|
|
|
100
107
|
options.json = true;
|
|
101
108
|
} else if (arg === '--save') {
|
|
102
109
|
options.save = true;
|
|
110
|
+
} else if (arg === '--no-check') {
|
|
111
|
+
options.check = false;
|
|
103
112
|
} else if (arg === '--unsave') {
|
|
104
113
|
options.unsave = true;
|
|
105
114
|
} else if (arg === '--limit') {
|
|
@@ -153,6 +162,7 @@ function parsePersonArgs(argv = []) {
|
|
|
153
162
|
help: false,
|
|
154
163
|
json: false,
|
|
155
164
|
save: false,
|
|
165
|
+
check: true,
|
|
156
166
|
name: null,
|
|
157
167
|
handle: null,
|
|
158
168
|
company: null,
|
|
@@ -173,6 +183,8 @@ function parsePersonArgs(argv = []) {
|
|
|
173
183
|
options.json = true;
|
|
174
184
|
} else if (arg === '--save') {
|
|
175
185
|
options.save = true;
|
|
186
|
+
} else if (arg === '--no-check') {
|
|
187
|
+
options.check = false;
|
|
176
188
|
} else if (arg === '--name') {
|
|
177
189
|
options.name = readValue(args, i, arg);
|
|
178
190
|
i++;
|
|
@@ -592,23 +604,26 @@ function ensureXSearchApply({ cwd, source, packRel, now, output } = {}) {
|
|
|
592
604
|
});
|
|
593
605
|
}
|
|
594
606
|
|
|
595
|
-
|
|
607
|
+
// check is the result of checkXPosts. With it, each post carries its own
|
|
608
|
+
// link or warning, and citations no post uses are listed as other sources.
|
|
609
|
+
function formatXSearchResult(data, check = null) {
|
|
596
610
|
const lines = [];
|
|
597
611
|
const content = xSearchContent(data);
|
|
598
612
|
const citations = xSearchCitations(data);
|
|
599
613
|
|
|
600
614
|
if (content) {
|
|
601
|
-
lines.push(content);
|
|
615
|
+
lines.push(check ? annotateContent(content, check) : content);
|
|
602
616
|
} else if (data?.message) {
|
|
603
617
|
lines.push(String(data.message).trim());
|
|
604
618
|
} else {
|
|
605
619
|
lines.push('X search completed');
|
|
606
620
|
}
|
|
607
621
|
|
|
608
|
-
|
|
622
|
+
const listed = check ? check.otherSources : citations;
|
|
623
|
+
if (listed.length) {
|
|
609
624
|
lines.push('');
|
|
610
|
-
lines.push('Citations:');
|
|
611
|
-
for (const cite of
|
|
625
|
+
lines.push(check ? 'Other sources:' : 'Citations:');
|
|
626
|
+
for (const cite of listed) {
|
|
612
627
|
lines.push(` ${cite}`);
|
|
613
628
|
}
|
|
614
629
|
}
|
|
@@ -619,9 +634,54 @@ function formatXSearchResult(data) {
|
|
|
619
634
|
lines.push(...creditLines);
|
|
620
635
|
}
|
|
621
636
|
|
|
637
|
+
if (check) {
|
|
638
|
+
lines.push('');
|
|
639
|
+
lines.push(formatTally(check.tally));
|
|
640
|
+
}
|
|
641
|
+
|
|
622
642
|
return lines.join('\n');
|
|
623
643
|
}
|
|
624
644
|
|
|
645
|
+
// Look the quoted posts up on X. Free, and never fatal: any failure means no
|
|
646
|
+
// check, and the original output prints unchanged.
|
|
647
|
+
async function checkXSearchPosts(data, options, deps = {}) {
|
|
648
|
+
if (options.check === false) return null;
|
|
649
|
+
try {
|
|
650
|
+
return await (deps.checkXPosts || checkXPosts)({
|
|
651
|
+
content: xSearchContent(data),
|
|
652
|
+
citations: xSearchCitations(data),
|
|
653
|
+
fetchPost: deps.fetchPost,
|
|
654
|
+
fetch: deps.fetch,
|
|
655
|
+
});
|
|
656
|
+
} catch {
|
|
657
|
+
return null;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
function renderXSearchResult(data, check) {
|
|
662
|
+
if (check) {
|
|
663
|
+
try {
|
|
664
|
+
return formatXSearchResult(data, check);
|
|
665
|
+
} catch {
|
|
666
|
+
// fall through to the original output
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
return formatXSearchResult(data);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
function runXSearchBench(argv, deps = {}) {
|
|
673
|
+
const run = (deps.spawnSync || spawnSync)(
|
|
674
|
+
process.execPath,
|
|
675
|
+
[path.join(__dirname, '..', 'scripts', 'det', 'xsearch-bench.js'), ...argv],
|
|
676
|
+
{ stdio: 'inherit' },
|
|
677
|
+
);
|
|
678
|
+
if (run.error) {
|
|
679
|
+
(deps.output || console.error)(`bench did not start: ${run.error.message}`);
|
|
680
|
+
return 1;
|
|
681
|
+
}
|
|
682
|
+
return typeof run.status === 'number' ? run.status : 1;
|
|
683
|
+
}
|
|
684
|
+
|
|
625
685
|
function formatEmptyXSearchResult(data) {
|
|
626
686
|
const lines = ['no results'];
|
|
627
687
|
const creditLines = formatCreditsLines(xSearchCredits(data));
|
|
@@ -634,6 +694,11 @@ function formatEmptyXSearchResult(data) {
|
|
|
634
694
|
|
|
635
695
|
async function xSearchCommand(argv = process.argv.slice(3), deps = {}) {
|
|
636
696
|
const output = deps.output || ((line = '') => console.log(line));
|
|
697
|
+
if (argv[0] === 'bench') {
|
|
698
|
+
const code = runXSearchBench(argv.slice(1), deps);
|
|
699
|
+
if (!deps.output && !deps.spawnSync) process.exit(code);
|
|
700
|
+
return code;
|
|
701
|
+
}
|
|
637
702
|
let options;
|
|
638
703
|
try {
|
|
639
704
|
options = parseXSearchArgs(argv);
|
|
@@ -655,10 +720,14 @@ async function xSearchCommand(argv = process.argv.slice(3), deps = {}) {
|
|
|
655
720
|
try {
|
|
656
721
|
const data = await runXSearch(options, deps);
|
|
657
722
|
const hasResults = xSearchHasResults(data);
|
|
723
|
+
const check = hasResults ? await checkXSearchPosts(data, options, deps) : null;
|
|
658
724
|
if (options.json) {
|
|
659
|
-
|
|
725
|
+
const withChecks = check && data && typeof data === 'object' && !Array.isArray(data)
|
|
726
|
+
? { ...data, checks: check.checks }
|
|
727
|
+
: data;
|
|
728
|
+
output(JSON.stringify(withChecks, null, 2));
|
|
660
729
|
} else {
|
|
661
|
-
output(hasResults ?
|
|
730
|
+
output(hasResults ? renderXSearchResult(data, check) : formatEmptyXSearchResult(data));
|
|
662
731
|
}
|
|
663
732
|
if (!hasResults) {
|
|
664
733
|
if (
|
|
@@ -734,5 +803,7 @@ module.exports = {
|
|
|
734
803
|
xSearchExperimentSlug,
|
|
735
804
|
xSearchExperimentRel,
|
|
736
805
|
unsaveXSearch,
|
|
806
|
+
formatXSearchResult,
|
|
807
|
+
runXSearchBench,
|
|
737
808
|
xSearchCommand,
|
|
738
809
|
};
|
package/commands/youtube.js
CHANGED
|
@@ -5,6 +5,7 @@ const { spawnSync } = require('child_process');
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const os = require('os');
|
|
7
7
|
const path = require('path');
|
|
8
|
+
const { stateHome } = require('../lib/state-home');
|
|
8
9
|
const https = require('https');
|
|
9
10
|
|
|
10
11
|
const YTNOTES_USAGE = 'usage: ytnotes <youtube-url> [youtube-url-or-playlist...] [auto|gemini|haiku|atris-fast|grok|codex|cursor]';
|
|
@@ -40,6 +41,7 @@ function showYoutubeHelp(output = console.log, commandName = 'atris youtube') {
|
|
|
40
41
|
output(` ${commandName} watch list`);
|
|
41
42
|
output(` ${commandName} watch remove <number>`);
|
|
42
43
|
output(` ${commandName} watch tick`);
|
|
44
|
+
output(` ${commandName} bench [--quick] [--case <name>]`);
|
|
43
45
|
output(` ${commandName} <youtube-url> [options]`);
|
|
44
46
|
output('');
|
|
45
47
|
output('search = free local discovery (ytsearch / yt-dlp), returns youtu.be links; rich free search writes one apply and a failing keep/revert pack; thin hands off to teach');
|
|
@@ -50,6 +52,7 @@ function showYoutubeHelp(output = console.log, commandName = 'atris youtube') {
|
|
|
50
52
|
output('process = 5 credits cloud knowledge (needs a filled Apply); rich process writes one apply and a failing keep/revert pack');
|
|
51
53
|
output('digest = one decision page from this week\'s video briefs; rich digest writes one apply and a failing keep/revert pack');
|
|
52
54
|
output('watch = subscribed channels turn into briefs without a human; add hands off to tick; tick hands off to teach when it briefed');
|
|
55
|
+
output('bench = run notes on real videos, uncached, and grade what a user gets; --quick runs the short video and the stranger checks');
|
|
53
56
|
output('Process a YouTube video through Atris using timestamped transcript-first analysis.');
|
|
54
57
|
output('Falls back to cloud video processing when local captions are unavailable.');
|
|
55
58
|
output('');
|
|
@@ -2254,7 +2257,7 @@ function searchCacheTtlMs(deps = {}) {
|
|
|
2254
2257
|
|
|
2255
2258
|
function resolveSearchCachePath(deps = {}) {
|
|
2256
2259
|
if (deps.searchCachePath) return deps.searchCachePath;
|
|
2257
|
-
const homeDir = deps.homeDir ||
|
|
2260
|
+
const homeDir = deps.homeDir || stateHome();
|
|
2258
2261
|
return path.join(homeDir, '.atris', LOCAL_SEARCH_CACHE_FILE);
|
|
2259
2262
|
}
|
|
2260
2263
|
|
|
@@ -3851,6 +3854,20 @@ async function runYoutubeTeach(args = [], deps = {}) {
|
|
|
3851
3854
|
return applyCode;
|
|
3852
3855
|
}
|
|
3853
3856
|
|
|
3857
|
+
// Bench the notes feature end to end. The script prints its own lines.
|
|
3858
|
+
function runYoutubeBench(argv, deps = {}) {
|
|
3859
|
+
const run = (deps.spawnSync || spawnSync)(
|
|
3860
|
+
process.execPath,
|
|
3861
|
+
[path.join(__dirname, '..', 'scripts', 'det', 'ytrail-bench.js'), ...argv],
|
|
3862
|
+
{ stdio: 'inherit' },
|
|
3863
|
+
);
|
|
3864
|
+
if (run.error) {
|
|
3865
|
+
(deps.output || console.error)(`bench did not start: ${run.error.message}`);
|
|
3866
|
+
return 1;
|
|
3867
|
+
}
|
|
3868
|
+
return typeof run.status === 'number' ? run.status : 1;
|
|
3869
|
+
}
|
|
3870
|
+
|
|
3854
3871
|
async function youtubeCommand(argv = process.argv.slice(3), deps = {}) {
|
|
3855
3872
|
const output = deps.output || ((line = '') => console.log(line));
|
|
3856
3873
|
if (argv[0] === 'search') {
|
|
@@ -3880,6 +3897,11 @@ async function youtubeCommand(argv = process.argv.slice(3), deps = {}) {
|
|
|
3880
3897
|
if (!deps.output && !deps.runner) process.exit(code);
|
|
3881
3898
|
return code;
|
|
3882
3899
|
}
|
|
3900
|
+
if (argv[0] === 'bench') {
|
|
3901
|
+
const code = runYoutubeBench(argv.slice(1), deps);
|
|
3902
|
+
if (!deps.output && !deps.spawnSync) process.exit(code);
|
|
3903
|
+
return code;
|
|
3904
|
+
}
|
|
3883
3905
|
if (argv[0] === 'watch') {
|
|
3884
3906
|
const code = await watchCommand(argv.slice(1), { ...deps, output });
|
|
3885
3907
|
if (!deps.output && !deps.fetcher && !deps.runner && !deps.briefFiler) process.exit(code);
|
|
@@ -3978,5 +4000,6 @@ module.exports = {
|
|
|
3978
4000
|
searchApplyRel,
|
|
3979
4001
|
firstRichWatchLesson,
|
|
3980
4002
|
fileTeachExperiment,
|
|
4003
|
+
runYoutubeBench,
|
|
3981
4004
|
youtubeCommand,
|
|
3982
4005
|
};
|
package/lib/autoland.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
const fs = require('fs');
|
|
9
9
|
const path = require('path');
|
|
10
|
+
const { stateHome } = require('./state-home');
|
|
10
11
|
const os = require('os');
|
|
11
12
|
const { spawnSync } = require('child_process');
|
|
12
13
|
const { computeTrustTier } = require('./trust-tiers');
|
|
@@ -189,7 +190,7 @@ function buildCronLine(root) {
|
|
|
189
190
|
const node = process.execPath;
|
|
190
191
|
const bin = path.join(root, 'bin', 'atris.js');
|
|
191
192
|
const cliBin = fs.existsSync(bin) ? bin : 'atris';
|
|
192
|
-
const logDir = path.join(
|
|
193
|
+
const logDir = path.join(stateHome(), '.atris', 'overnight');
|
|
193
194
|
const logFile = path.join(logDir, `autoland-${path.basename(root)}.log`);
|
|
194
195
|
const invoke = cliBin.endsWith('.js') ? `${node} ${cliBin}` : cliBin;
|
|
195
196
|
return `14 * * * * cd ${root} && ${invoke} autoland tick >> ${logFile} 2>&1 # ${cronMarker(root)}`;
|
|
@@ -210,7 +211,7 @@ function cronInstalled(root) {
|
|
|
210
211
|
}
|
|
211
212
|
|
|
212
213
|
function installCron(root) {
|
|
213
|
-
fs.mkdirSync(path.join(
|
|
214
|
+
fs.mkdirSync(path.join(stateHome(), '.atris', 'overnight'), { recursive: true });
|
|
214
215
|
const marker = cronMarker(root);
|
|
215
216
|
const kept = readCrontab().split(/\r?\n/).filter((line) => line.trim() && !line.includes(marker));
|
|
216
217
|
const next = `${kept.join('\n')}${kept.length ? '\n' : ''}${buildCronLine(root)}\n`;
|
package/lib/ax-auto-lane.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const fs = require('node:fs');
|
|
4
|
-
const os = require('node:os');
|
|
5
4
|
const path = require('node:path');
|
|
5
|
+
const { stateHome } = require('./state-home');
|
|
6
6
|
|
|
7
7
|
const SHORT_LOOKUP_MAX_CHARS = 140;
|
|
8
8
|
const LONG_INPUT_MIN_CHARS = 1200;
|
|
@@ -75,7 +75,7 @@ function overrideMatches(text, override) {
|
|
|
75
75
|
function loadOverrides(filePath) {
|
|
76
76
|
const resolved = filePath
|
|
77
77
|
|| process.env.ATRIS_ROUTER_OVERRIDES_PATH
|
|
78
|
-
|| path.join(
|
|
78
|
+
|| path.join(stateHome(), '.atris', 'router-overrides.json');
|
|
79
79
|
try {
|
|
80
80
|
const parsed = JSON.parse(fs.readFileSync(resolved, 'utf8'));
|
|
81
81
|
return Array.isArray(parsed) ? parsed : [];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Rule lines every builder brief carries (lib/codex-flight.js buildPrompt and
|
|
4
|
+
// lib/fleet.js buildFleetPrompt). The text lives here once so the two briefs
|
|
5
|
+
// cannot drift apart.
|
|
6
|
+
const SHARED_BRIEF_RULES = [
|
|
7
|
+
'- Keep one concern per PR; split anything larger into separate PRs because git history guides future agents and small PRs are cheap to revert and bisect.',
|
|
8
|
+
'- A guard, limit, or cache must hold on every path to the thing it protects; grep for the other callers, templates, and writers, and test at least one path you were not pointed at.',
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
module.exports = { SHARED_BRIEF_RULES };
|
package/lib/chat-log-scan.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const os = require('os');
|
|
3
3
|
const path = require('path');
|
|
4
|
+
const { stateHome } = require('./state-home');
|
|
4
5
|
|
|
5
6
|
const SCHEMA = 'atris.chat_scan.v1';
|
|
6
7
|
const PROMPT_LINE = /^(?:max|pro|fast|code-fast)\s*›\s*(.+)$/i;
|
|
@@ -73,7 +74,7 @@ function axLogDirs(cwd = process.cwd(), options = {}) {
|
|
|
73
74
|
const local = path.join(cwd, 'atris', 'runs');
|
|
74
75
|
if (fs.existsSync(local)) dirs.push(local);
|
|
75
76
|
if (options.global) {
|
|
76
|
-
const home = path.join(
|
|
77
|
+
const home = path.join(stateHome(), '.atris', 'runs');
|
|
77
78
|
if (fs.existsSync(home) && home !== local) dirs.push(home);
|
|
78
79
|
}
|
|
79
80
|
return dirs;
|
package/lib/ci-runner.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('node:fs');
|
|
4
4
|
const path = require('node:path');
|
|
5
|
+
const { stateHome } = require('./state-home');
|
|
5
6
|
const os = require('node:os');
|
|
6
7
|
const https = require('node:https');
|
|
7
8
|
const { spawn, spawnSync } = require('node:child_process');
|
|
@@ -9,7 +10,7 @@ const { spawn, spawnSync } = require('node:child_process');
|
|
|
9
10
|
const RUNNER_VERSION = '2.336.0';
|
|
10
11
|
const GITHUB_API_VERSION = '2026-03-10';
|
|
11
12
|
|
|
12
|
-
function usageFilePath(home =
|
|
13
|
+
function usageFilePath(home = stateHome()) {
|
|
13
14
|
return path.join(home, '.atris', 'ci', 'usage.jsonl');
|
|
14
15
|
}
|
|
15
16
|
|
|
@@ -309,7 +310,7 @@ async function ensureRunner(options = {}) {
|
|
|
309
310
|
const version = options.version || RUNNER_VERSION;
|
|
310
311
|
const platform = options.platform || process.platform;
|
|
311
312
|
const arch = options.arch || process.arch;
|
|
312
|
-
const home = options.home ||
|
|
313
|
+
const home = options.home || stateHome();
|
|
313
314
|
const log = options.log || console.log;
|
|
314
315
|
const asset = runnerAssetName(platform, arch, version);
|
|
315
316
|
const runnerPlatform = platform === 'darwin' ? 'osx' : platform;
|