atris 3.80.1 → 3.82.2

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.
Files changed (70) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +11 -1
  3. package/atris/atris.md +14 -0
  4. package/atris/features/README.md +15 -0
  5. package/atris/policies/README.md +24 -0
  6. package/atris/skills/README.md +61 -45
  7. package/atris/skills/design/SKILL.md +6 -5
  8. package/atris/skills/taste/SKILL.md +39 -0
  9. package/atris/skills/taste/notes.md +29 -0
  10. package/atris/skills/taste/principles.md +23 -0
  11. package/atris/skills/wiki/SKILL.md +14 -2
  12. package/atris/skills/wiki/index-lines.md +30 -0
  13. package/atris/skills/wiki/measure.md +20 -0
  14. package/atris/skills/wiki/note-shape.md +16 -0
  15. package/atris/skills/wiki/upkeep.md +16 -0
  16. package/atris/team/README.md +38 -0
  17. package/atris.md +14 -0
  18. package/bin/atris.js +1 -1
  19. package/commands/activate.js +1 -1
  20. package/commands/autoland.js +25 -26
  21. package/commands/autopilot-front.js +46 -1
  22. package/commands/autopilot.js +55 -25
  23. package/commands/clean.js +22 -11
  24. package/commands/computer.js +1 -1
  25. package/commands/doc-health.js +48 -1
  26. package/commands/improve.js +16 -3
  27. package/commands/init.js +4 -0
  28. package/commands/integrations.js +69 -1616
  29. package/commands/mission.js +112 -14
  30. package/commands/release.js +41 -10
  31. package/commands/status.js +1 -1
  32. package/commands/sync.js +12 -8
  33. package/commands/task.js +150 -137
  34. package/commands/unknowns.js +9 -5
  35. package/commands/visualize.js +1 -12
  36. package/commands/workflow.js +4 -3
  37. package/commands/worktree.js +107 -1610
  38. package/commands/xp.js +23 -2113
  39. package/commands/youtube.js +192 -3110
  40. package/lib/business-core.js +3 -4
  41. package/lib/campaign-run-lock.js +12 -1
  42. package/lib/engine-ask.js +1 -1
  43. package/lib/engine-receipt-sweep.js +64 -0
  44. package/lib/file-lock.js +175 -0
  45. package/lib/first-minute.js +37 -4
  46. package/lib/fleet.js +1 -1
  47. package/lib/integrations-core.js +1695 -0
  48. package/lib/intents.js +7 -0
  49. package/lib/json-file.js +22 -1
  50. package/lib/loop-doctor.js +36 -2
  51. package/lib/map-refs.js +68 -8
  52. package/lib/member-context.js +20 -1
  53. package/lib/member-engine.js +1 -1
  54. package/lib/member-map-fold.js +49 -11
  55. package/lib/member-map-inbox.js +2 -1
  56. package/lib/mission-core.js +9 -2
  57. package/lib/next-work-context.js +190 -0
  58. package/lib/recall-exam.js +171 -0
  59. package/lib/roster-markdown.js +2 -2
  60. package/lib/runner-command.js +5 -5
  61. package/lib/task-db.js +107 -38
  62. package/lib/taste-notes.js +35 -0
  63. package/lib/team-lineup.js +3 -3
  64. package/lib/todo.js +2 -0
  65. package/lib/wiki.js +11 -3
  66. package/lib/worktree-core.js +2032 -0
  67. package/lib/xp-core.js +2172 -0
  68. package/lib/youtube-core.js +3178 -0
  69. package/lib/zen-apply.js +3 -1
  70. package/package.json +2 -2
package/commands/xp.js CHANGED
@@ -1,113 +1,26 @@
1
- const { ensureValidCredentials, getSessionProfile, loadCredentials, profileNameFromEmail } = require('../utils/auth');
1
+ const { ensureValidCredentials } = require('../utils/auth');
2
2
  const { apiRequestJson } = require('../utils/api');
3
- const fs = require('fs');
4
- const os = require('os');
5
- const path = require('path');
6
- const crypto = require('crypto');
7
- const { spawnSync } = require('child_process');
8
- const { hasFlag: hasExactFlag } = require('../lib/arg-parser');
9
- const { treeHashFor } = require('../lib/tree-hash');
10
- const { careerXpRoot, careerXpLinkedRoots } = require('../lib/career-xp-root');
11
- const { withCareerXpLock } = require('../lib/career-xp-lock');
12
- const { normalizeActor, taskBuilder } = require('../lib/review-integrity');
13
-
14
- const DEFAULT_GRAPH_DAYS = 365;
15
- const MAX_SYNC_GRAPH_DAYS = 370;
16
- const INTENSITY_CHARS = [' ', '.', ':', '*', '#'];
17
- const ROW_LABELS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
18
- const TASK_EPISODES_FILE = path.join('.atris', 'state', 'task_episodes.jsonl');
19
- const BUSINESS_BINDING_FILE = path.join('.atris', 'business.json');
20
- const CAREER_XP_RECEIPTS_FILE = path.join('.atris', 'state', 'career_xp_receipts.jsonl');
21
- const CAREER_XP_PROJECTION_FILE = path.join('.atris', 'state', 'career_xp.projection.json');
22
- const CAREER_XP_CURSOR_FILE = path.join('.atris', 'state', 'career_xp.cursor.json');
23
- const CAREER_XP_SESSIONS_DIR = path.join('.atris', 'state', 'career_xp_sessions');
24
- const TASK_PROJECTION_FILE = path.join('.atris', 'state', 'tasks.projection.json');
25
- const CODEX_STATE_FILE = path.join(os.homedir(), '.codex', 'state_5.sqlite');
26
-
27
- // Default single-workspace XP root. Local XP reads task_episodes.jsonl (written
28
- // by task-db at the main checkout's workspace root) and writes its projection
29
- // beside it, so reads must resolve the same shared root, otherwise
30
- // `xp --local` from a subdir (e.g. backend/) read the wrong/empty episodes and
31
- // split the projection into a nested .atris. Explicit --workspace selects the
32
- // repository and nested workspace, then also resolves its shared ledger. Falls
33
- // back to raw cwd if the resolver can't load.
34
- function defaultXpWorkspace() {
35
- try {
36
- return careerXpRoot(require('../lib/mission-root').resolveWorkspaceRoot(process.cwd()));
37
- } catch {
38
- return process.cwd();
39
- }
40
- }
41
- // Codex moved native goals to goals_1.sqlite; thread metadata (cwd/title) stayed in state_5.sqlite.
42
- const CODEX_GOALS_FILE = path.join(os.homedir(), '.codex', 'goals_1.sqlite');
43
- const AGENT_XP_LABEL = 'AgentXP';
44
- const AGENTXP_LEADERBOARD_URL = 'https://api.atris.ai/api/agentxp/leaderboard';
45
- const LEVEL_XP = 1000;
46
- const RECEIPT_CHAIN_VERSION = 'atris.career_xp_receipt_chain.v1';
47
- const VALIDATION_TERMS = ['verify', 'verified', 'verifier', 'passed', 'pytest', 'receipt', 'score', 'smoke', 'git diff --check'];
48
- const QUALITY_TERMS = ['craft', 'scorecard', 'quality', 'critique', 'review', 'taste'];
49
- const ATRIS_ACCEPTED_STATUSES = new Set(['accepted', 'approved', 'done', 'review', 'reviewed', 'unknown']);
50
- const ATRIS_ACCEPTED_EVENTS = new Set(['accepted', 'approved', 'completed', 'done', 'reviewed', 'unknown']);
51
- const EARNING_MODEL = {
52
- schema: 'atris.agentxp_earning_model.v1',
53
- score_name: AGENT_XP_LABEL,
54
- primary_public_source: 'accepted_task_receipt',
55
- public_rule: 'No human-accepted proof-backed task episodes means no AgentXP leaderboard movement.',
56
- policies: {
57
- task_done_with_proof: 'Records proof for review and RL context; emits no Career XP receipt until human accept.',
58
- task_accept: 'Human accept with proof and positive reward is the Career XP receipt boundary.',
59
- },
60
- weights: [
61
- {
62
- id: 'accepted_task_receipt',
63
- label: 'Accepted proof-backed task',
64
- relative_weight: 1,
65
- public_leaderboard: true,
66
- rl_value: 'high',
67
- },
68
- {
69
- id: 'validated_agent_loop',
70
- label: 'Verifier or agent loop inside an accepted task',
71
- relative_weight: 0.35,
72
- public_leaderboard: true,
73
- rl_value: 'high',
74
- },
75
- {
76
- id: 'atris_action_signal',
77
- label: 'Atris action signal',
78
- relative_weight: 0.1,
79
- public_leaderboard: false,
80
- rl_value: 'medium',
81
- },
82
- {
83
- id: 'local_assistant_activity',
84
- label: 'Local assistant activity',
85
- relative_weight: 0.05,
86
- public_leaderboard: false,
87
- rl_value: 'medium',
88
- },
89
- ],
90
- caps: {
91
- local_assistant_activity: 'Capped below 10% of accepted-task XP and never unlocks public ranking alone.',
92
- atris_action_signal: 'Routes RL and next moves, but does not outrank an accepted proof-backed task.',
93
- },
94
- };
95
- const XP_STATE_FILES = new Set([
96
- path.basename(TASK_EPISODES_FILE),
97
- path.basename(CAREER_XP_RECEIPTS_FILE),
98
- path.basename(CAREER_XP_PROJECTION_FILE),
99
- ]);
100
- const SEARCH_EXCLUDED_DIRS = new Set([
101
- '.git',
102
- 'node_modules',
103
- 'dist',
104
- 'build',
105
- '.next',
106
- 'coverage',
107
- 'tmp',
108
- 'temp',
109
- ]);
110
- const DEFAULT_SEARCH_DEPTH = 6;
3
+ const xpCore = require('../lib/xp-core');
4
+ const {
5
+ DEFAULT_GRAPH_DAYS,
6
+ CAREER_XP_PROJECTION_FILE,
7
+ AGENT_XP_LABEL,
8
+ AGENTXP_LEADERBOARD_URL,
9
+ defaultXpWorkspace,
10
+ asNumber,
11
+ formatNumber,
12
+ buildContributionRows,
13
+ currentForm,
14
+ readInlineFirstFlag,
15
+ hasFlagOrValue,
16
+ buildCareerXpProjection,
17
+ collectLocalXpProjection,
18
+ publicWorkspaceBinding,
19
+ collectAllLocalXpProjection,
20
+ textExcerpt,
21
+ buildCareerXpSessionCapsule,
22
+ buildAgentXpSyncPacket,
23
+ } = xpCore;
111
24
 
112
25
  function showHelp() {
113
26
  console.log('Usage: atris xp [card|status|collect|session|sync] [--json] [--workspace <path>] [--all] [--root <path>]');
@@ -125,1701 +38,6 @@ function showHelp() {
125
38
  console.log('Use --local to render from proof receipts in the current workspace.');
126
39
  }
127
40
 
128
- function asNumber(value, fallback = 0) {
129
- const parsed = Number(value);
130
- return Number.isFinite(parsed) ? parsed : fallback;
131
- }
132
-
133
- function formatNumber(value) {
134
- return asNumber(value).toLocaleString('en-US');
135
- }
136
-
137
- function buildContributionRows(days) {
138
- const normalized = Array.isArray(days) ? days : [];
139
- if (normalized.length === 0) {
140
- return ROW_LABELS.map(label => `${label} `);
141
- }
142
-
143
- const first = new Date(`${normalized[0].date}T00:00:00Z`);
144
- const pad = Number.isFinite(first.getTime()) ? first.getUTCDay() : 0;
145
- const padded = [
146
- ...Array.from({ length: pad }, () => null),
147
- ...normalized,
148
- ];
149
- while (padded.length % 7 !== 0) padded.push(null);
150
-
151
- return ROW_LABELS.map((label, rowIndex) => {
152
- let line = `${label} `;
153
- for (let index = rowIndex; index < padded.length; index += 7) {
154
- const day = padded[index];
155
- const intensity = Math.max(0, Math.min(4, asNumber(day?.intensity)));
156
- line += INTENSITY_CHARS[intensity];
157
- }
158
- return line;
159
- });
160
- }
161
-
162
- function dateWindow(windowDays = DEFAULT_GRAPH_DAYS) {
163
- const count = Math.max(1, asNumber(windowDays, DEFAULT_GRAPH_DAYS));
164
- const end = new Date();
165
- const dates = [];
166
- for (let offset = count - 1; offset >= 0; offset -= 1) {
167
- const date = new Date(end);
168
- date.setDate(end.getDate() - offset);
169
- const key = localDateKey(date);
170
- if (key && dates[dates.length - 1] !== key) dates.push(key);
171
- }
172
- return dates;
173
- }
174
-
175
- function graphIntensity(xp, maxDailyXp) {
176
- const total = asNumber(xp);
177
- if (total <= 0) return 0;
178
- const max = Math.max(1, asNumber(maxDailyXp, 1));
179
- if (max <= 1) return 1;
180
- const ratio = total / max;
181
- if (ratio >= 0.75) return 4;
182
- if (ratio >= 0.5) return 3;
183
- if (ratio >= 0.25) return 2;
184
- return 1;
185
- }
186
-
187
- function graphFromDailyTotals(dailyTotals, windowDays = DEFAULT_GRAPH_DAYS) {
188
- const totals = dailyTotals instanceof Map
189
- ? dailyTotals
190
- : new Map(Object.entries(dailyTotals || {}));
191
- const dates = dateWindow(windowDays);
192
- const maxDailyXp = dates.reduce((max, date) => Math.max(max, asNumber(totals.get(date))), 0);
193
- const days = dates.map((date) => {
194
- const xp = asNumber(totals.get(date));
195
- return {
196
- date,
197
- xp,
198
- total_xp: xp,
199
- intensity: graphIntensity(xp, maxDailyXp),
200
- };
201
- });
202
- return {
203
- schema: 'atris.agent_xp_contribution_graph.v1',
204
- metric_label: AGENT_XP_LABEL,
205
- window_days: days.length,
206
- total_xp: days.reduce((sum, day) => sum + day.xp, 0),
207
- active_days: days.filter(day => day.xp > 0).length,
208
- first_date: days[0]?.date || null,
209
- last_date: days[days.length - 1]?.date || null,
210
- days,
211
- };
212
- }
213
-
214
- function buildAgentXpContributionGraph(receipts, windowDays = DEFAULT_GRAPH_DAYS) {
215
- const totals = new Map();
216
- for (const receipt of receipts || []) {
217
- if (!receipt || receipt.outcome !== 'accepted') continue;
218
- const xp = asNumber(receipt.xp);
219
- if (xp <= 0) continue;
220
- const date = localDateKey(receipt.accepted_at);
221
- if (!date) continue;
222
- totals.set(date, asNumber(totals.get(date)) + xp);
223
- }
224
- return graphFromDailyTotals(totals, windowDays);
225
- }
226
-
227
- function combineAgentXpContributionGraphs(graphs, windowDays = DEFAULT_GRAPH_DAYS) {
228
- const totals = new Map();
229
- for (const graph of graphs || []) {
230
- for (const day of graph?.days || []) {
231
- const date = day?.date;
232
- if (!date) continue;
233
- const xp = asNumber(day.xp ?? day.total_xp);
234
- if (xp <= 0) continue;
235
- totals.set(date, asNumber(totals.get(date)) + xp);
236
- }
237
- }
238
- return graphFromDailyTotals(totals, windowDays);
239
- }
240
-
241
- function sanitizeContributionGraphForSync(graph) {
242
- if (!graph || typeof graph !== 'object') return null;
243
- const rawDays = Array.isArray(graph.days) ? graph.days : [];
244
- const days = rawDays
245
- .slice(-MAX_SYNC_GRAPH_DAYS)
246
- .map((day) => {
247
- const date = typeof day?.date === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(day.date)
248
- ? day.date
249
- : null;
250
- if (!date) return null;
251
- const xp = Math.max(0, asNumber(day.xp ?? day.total_xp));
252
- return {
253
- date,
254
- xp,
255
- total_xp: Math.max(0, asNumber(day.total_xp, xp)),
256
- intensity: Math.max(0, Math.min(4, asNumber(day.intensity))),
257
- };
258
- })
259
- .filter(Boolean);
260
- if (days.length === 0) return null;
261
- return {
262
- schema: 'atris.agent_xp_contribution_graph.v1',
263
- metric_label: AGENT_XP_LABEL,
264
- window_days: days.length,
265
- total_xp: days.reduce((sum, day) => sum + day.xp, 0),
266
- active_days: days.filter(day => day.xp > 0).length,
267
- first_date: days[0]?.date || null,
268
- last_date: days[days.length - 1]?.date || null,
269
- days,
270
- };
271
- }
272
-
273
- function currentForm(payload) {
274
- const arenas = payload.current_form_by_arena || {};
275
- const local = arenas.local_workspace || {};
276
- const ovr = asNumber(local.ovr || local.current_form);
277
- if (!ovr) return null;
278
- return {
279
- ovr,
280
- visibleStats: Array.isArray(local.visible_stats) ? local.visible_stats : [],
281
- leaderboardEligible: Boolean(local.leaderboard_eligible),
282
- integrityStatus: local.integrity_status || 'unknown',
283
- };
284
- }
285
-
286
- // Preserve the existing rule that any inline value wins over a split value.
287
- function readInlineFirstFlag(args, name, fallback = null) {
288
- const inline = args.find(arg => arg.startsWith(`${name}=`));
289
- if (inline) {
290
- return inline.slice(name.length + 1);
291
- }
292
- const index = args.indexOf(name);
293
- if (index >= 0 && args[index + 1] && !args[index + 1].startsWith('--')) {
294
- return args[index + 1];
295
- }
296
- return fallback;
297
- }
298
-
299
- function readFirstFlag(args, names, fallback = null) {
300
- for (const name of names) {
301
- const value = readInlineFirstFlag(args, name, null);
302
- if (value !== null && value !== undefined && value !== '') return value;
303
- }
304
- return fallback;
305
- }
306
-
307
- function readFlagValues(args, names) {
308
- const wanted = Array.isArray(names) ? names : [names];
309
- const values = [];
310
- for (let index = 0; index < args.length; index += 1) {
311
- const arg = args[index];
312
- for (const name of wanted) {
313
- if (arg === name && args[index + 1] && !args[index + 1].startsWith('--')) {
314
- values.push(args[index + 1]);
315
- } else if (arg.startsWith(`${name}=`)) {
316
- values.push(arg.slice(name.length + 1));
317
- }
318
- }
319
- }
320
- return values.filter(Boolean);
321
- }
322
-
323
- // This command treats --name=value as flag presence as well as a standalone flag.
324
- function hasFlagOrValue(args, name) {
325
- return hasExactFlag(args, name) || args.some(arg => arg.startsWith(`${name}=`));
326
- }
327
-
328
- function levelFromXp(careerXp) {
329
- return Math.max(1, Math.floor(asNumber(careerXp) / LEVEL_XP) + 1);
330
- }
331
-
332
- function localDateKey(value, timeZone = process.env.TZ || Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC') {
333
- const date = value instanceof Date ? value : new Date(value);
334
- if (!Number.isFinite(date.getTime())) return null;
335
- try {
336
- return new Intl.DateTimeFormat('en-CA', {
337
- timeZone,
338
- year: 'numeric',
339
- month: '2-digit',
340
- day: '2-digit',
341
- }).format(date);
342
- } catch (_) {
343
- return date.toISOString().slice(0, 10);
344
- }
345
- }
346
-
347
- function parseJsonlContent(content, options = {}) {
348
- const allowPartialTail = Boolean(options.allowPartialTail);
349
- const rows = [];
350
- const hasTerminalNewline = /\r?\n$/.test(content);
351
- const lines = content.split('\n');
352
- let parsedBytes = 0;
353
- let skippedPartialTail = false;
354
- let skippedPartialTailBytes = 0;
355
-
356
- for (let index = 0; index < lines.length; index += 1) {
357
- const rawLine = lines[index];
358
- const isLast = index === lines.length - 1;
359
- const lineHadNewline = !isLast || hasTerminalNewline;
360
- const lineBytes = isLast && hasTerminalNewline && rawLine === ''
361
- ? 0
362
- : Buffer.byteLength(rawLine, 'utf8') + (lineHadNewline ? 1 : 0);
363
- const line = rawLine.trim();
364
-
365
- if (!line) {
366
- parsedBytes += lineBytes;
367
- continue;
368
- }
369
-
370
- try {
371
- rows.push(JSON.parse(line));
372
- parsedBytes += lineBytes;
373
- } catch (error) {
374
- if (allowPartialTail && isLast && !hasTerminalNewline) {
375
- skippedPartialTail = true;
376
- skippedPartialTailBytes = lineBytes;
377
- break;
378
- }
379
- throw error;
380
- }
381
- }
382
-
383
- return {
384
- rows,
385
- parsedBytes,
386
- skippedPartialTail,
387
- skippedPartialTailBytes,
388
- };
389
- }
390
-
391
- function readJsonl(filePath, options = {}) {
392
- if (!fs.existsSync(filePath)) return [];
393
- const content = fs.readFileSync(filePath, 'utf8');
394
- return parseJsonlContent(content, options).rows;
395
- }
396
-
397
- function writeJson(filePath, payload) {
398
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
399
- fs.writeFileSync(filePath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
400
- }
401
-
402
- function appendJsonl(filePath, rows) {
403
- if (!rows.length) return;
404
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
405
- fs.appendFileSync(filePath, rows.map(row => JSON.stringify(row)).join('\n') + '\n', 'utf8');
406
- }
407
-
408
- function writeJsonl(filePath, rows) {
409
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
410
- fs.writeFileSync(filePath, rows.length ? rows.map(row => JSON.stringify(row)).join('\n') + '\n' : '', 'utf8');
411
- }
412
-
413
- function canonicalJson(value) {
414
- if (Array.isArray(value)) return `[${value.map(canonicalJson).join(',')}]`;
415
- if (value && typeof value === 'object') {
416
- return `{${Object.keys(value).sort().map((key) => {
417
- const item = value[key];
418
- if (item === undefined) return null;
419
- return `${JSON.stringify(key)}:${canonicalJson(item)}`;
420
- }).filter(Boolean).join(',')}}`;
421
- }
422
- return JSON.stringify(value);
423
- }
424
-
425
- function sha256(value) {
426
- return crypto.createHash('sha256').update(String(value)).digest('hex');
427
- }
428
-
429
- function hashPayload(value) {
430
- return sha256(canonicalJson(value));
431
- }
432
-
433
- function slugify(value) {
434
- return String(value || '')
435
- .trim()
436
- .toLowerCase()
437
- .replace(/@.*$/, '')
438
- .replace(/[^a-z0-9]+/g, '-')
439
- .replace(/^-+|-+$/g, '');
440
- }
441
-
442
- function readJsonFile(filePath, fallback = null) {
443
- if (!fs.existsSync(filePath)) return fallback;
444
- return JSON.parse(fs.readFileSync(filePath, 'utf8'));
445
- }
446
-
447
- function fileSize(filePath) {
448
- try {
449
- return fs.statSync(filePath).size;
450
- } catch (_) {
451
- return 0;
452
- }
453
- }
454
-
455
- function timestampMs(value) {
456
- if (value === null || value === undefined || value === '') return null;
457
- if (typeof value === 'number' && Number.isFinite(value)) return value;
458
- const numeric = Number(value);
459
- if (Number.isFinite(numeric) && String(value).trim() !== '') return numeric;
460
- const parsed = new Date(value).getTime();
461
- return Number.isFinite(parsed) ? parsed : null;
462
- }
463
-
464
- function isoFromMs(value) {
465
- const ms = timestampMs(value);
466
- return ms === null ? null : new Date(ms).toISOString();
467
- }
468
-
469
- function startOfToday() {
470
- const date = new Date();
471
- date.setHours(0, 0, 0, 0);
472
- return date;
473
- }
474
-
475
- function startOfTonight() {
476
- const date = new Date();
477
- if (date.getHours() < 6) {
478
- date.setDate(date.getDate() - 1);
479
- }
480
- date.setHours(18, 0, 0, 0);
481
- return date;
482
- }
483
-
484
- function parseSessionBoundary(value, fallback) {
485
- if (!value) return fallback;
486
- const normalized = String(value).trim().toLowerCase();
487
- if (!normalized) return fallback;
488
- if (normalized === 'now') return new Date();
489
- if (normalized === 'today') return startOfToday();
490
- if (normalized === 'tonight') return startOfTonight();
491
- if (normalized === 'yesterday') {
492
- const date = startOfToday();
493
- date.setDate(date.getDate() - 1);
494
- return date;
495
- }
496
-
497
- const dateOnly = /^(\d{4})-(\d{2})-(\d{2})$/.exec(normalized);
498
- if (dateOnly) {
499
- return new Date(
500
- Number(dateOnly[1]),
501
- Number(dateOnly[2]) - 1,
502
- Number(dateOnly[3]),
503
- 0,
504
- 0,
505
- 0,
506
- 0
507
- );
508
- }
509
-
510
- const parsed = new Date(value);
511
- if (Number.isFinite(parsed.getTime())) return parsed;
512
- throw new Error(`Invalid session time: ${value}`);
513
- }
514
-
515
- function inWindow(value, sinceMs, untilMs) {
516
- const ms = timestampMs(value);
517
- return ms !== null && ms >= sinceMs && ms <= untilMs;
518
- }
519
-
520
- function expandHome(filePath) {
521
- if (!filePath) return filePath;
522
- if (filePath === '~') return os.homedir();
523
- if (filePath.startsWith('~/')) return path.join(os.homedir(), filePath.slice(2));
524
- return filePath;
525
- }
526
-
527
- function sqlString(value) {
528
- return `'${String(value).replace(/'/g, "''")}'`;
529
- }
530
-
531
- function existingSignal(label, filePath) {
532
- if (!filePath) return null;
533
- try {
534
- const stat = fs.statSync(expandHome(filePath));
535
- return {
536
- label,
537
- kind: stat.isDirectory() ? 'dir' : 'file',
538
- size_bytes: stat.isFile() ? stat.size : null,
539
- mtime: stat.mtime.toISOString(),
540
- };
541
- } catch (_) {
542
- return null;
543
- }
544
- }
545
-
546
- function localAssistantProviderSignals(workspace) {
547
- const codexStatePath = process.env.CODEX_STATE_DB || CODEX_STATE_FILE;
548
- const candidates = [
549
- {
550
- id: 'codex',
551
- label: 'Codex',
552
- signals: [
553
- existingSignal('codex_state', codexStatePath),
554
- existingSignal('codex_history', path.join(os.homedir(), '.codex', 'history.jsonl')),
555
- existingSignal('codex_sessions', path.join(os.homedir(), '.codex', 'sessions')),
556
- ],
557
- },
558
- {
559
- id: 'claude',
560
- label: 'Claude',
561
- signals: [
562
- existingSignal('claude_home', path.join(os.homedir(), '.claude')),
563
- existingSignal('claude_config', path.join(os.homedir(), '.claude.json')),
564
- existingSignal('workspace_claude', path.join(workspace, '.claude')),
565
- ],
566
- },
567
- {
568
- id: 'cursor',
569
- label: 'Cursor',
570
- signals: [
571
- existingSignal('workspace_cursor', path.join(workspace, '.cursor')),
572
- existingSignal('cursor_home', path.join(os.homedir(), '.cursor')),
573
- existingSignal('cursor_app_support', path.join(os.homedir(), 'Library', 'Application Support', 'Cursor')),
574
- ],
575
- },
576
- {
577
- id: 'devin',
578
- label: 'Devin',
579
- signals: [
580
- existingSignal('workspace_devin', path.join(workspace, '.devin')),
581
- existingSignal('devin_home', path.join(os.homedir(), '.devin')),
582
- ],
583
- },
584
- ];
585
-
586
- return candidates.map((provider) => {
587
- const signals = provider.signals.filter(Boolean);
588
- return {
589
- id: provider.id,
590
- label: provider.label,
591
- detected: signals.length > 0,
592
- signal_count: signals.length,
593
- signals,
594
- };
595
- });
596
- }
597
-
598
- function buildLocalAssistantActivity(workspace, acceptedTaskXp = 0) {
599
- const providers = localAssistantProviderSignals(workspace);
600
- const detected = providers.filter(provider => provider.detected);
601
- const rawSignalCount = detected.reduce((sum, provider) => sum + provider.signal_count, 0);
602
- const weight = EARNING_MODEL.weights.find(item => item.id === 'local_assistant_activity')?.relative_weight || 0.05;
603
- const weightedContext = Math.round(rawSignalCount * weight * 100) / 100;
604
- const acceptedXp = asNumber(acceptedTaskXp);
605
- const contextCap = Math.round(acceptedXp * 0.1 * 100) / 100;
606
- const contextAgentXp = acceptedXp > 0 ? Math.min(weightedContext, contextCap) : 0;
607
-
608
- return {
609
- schema: 'atris.agentxp_local_assistant_activity.v1',
610
- public_leaderboard: false,
611
- source_type: 'local_assistant_activity',
612
- weight,
613
- context_weight: weight,
614
- cap: {
615
- max_public_xp_ratio: 0.1,
616
- requires_accepted_task_xp: true,
617
- },
618
- cap_ratio_to_accepted_task_xp: 0.1,
619
- cap_agent_xp: contextCap,
620
- accepted_task_agent_xp: acceptedXp,
621
- detected_providers: detected.map(provider => provider.id).sort(),
622
- provider_count: detected.length,
623
- providers,
624
- raw_signal_count: rawSignalCount,
625
- weighted_context_score: weightedContext,
626
- context_agent_xp: contextAgentXp,
627
- included_in_total_agent_xp: false,
628
- role: 'context_only',
629
- note: 'Local assistant activity helps context and RL; accepted proof-backed tasks are the public AgentXP source.',
630
- };
631
- }
632
-
633
- function safeSignalKey(value) {
634
- const text = String(value || '').trim().toLowerCase().replace(/[\s_]+/g, '-');
635
- const clean = [...text].filter(char => /[a-z0-9_-]/.test(char)).join('');
636
- return clean.slice(0, 40) || 'unknown';
637
- }
638
-
639
- function hasAnyTerm(text, terms) {
640
- const lower = String(text || '').toLowerCase();
641
- return terms.some(term => lower.includes(term));
642
- }
643
-
644
- function incrementCount(counts, key) {
645
- counts[key] = (counts[key] || 0) + 1;
646
- }
647
-
648
- function buildAtrisActionSignalFromRows(rows = []) {
649
- const eventTypeCounts = {};
650
- const statusCounts = {};
651
- const actors = new Set();
652
- const claimants = new Set();
653
- let proofBackedEpisodeCount = 0;
654
- let acceptedEpisodeCount = 0;
655
- let validationReceiptCount = 0;
656
- let qualityReceiptCount = 0;
657
- let claimedEpisodeCount = 0;
658
-
659
- for (const row of rows || []) {
660
- const action = row?.action && typeof row.action === 'object' ? row.action : {};
661
- const state = row?.state && typeof row.state === 'object' ? row.state : {};
662
- const eventType = safeSignalKey(action.event_type || action.type || action.name);
663
- const status = safeSignalKey(state.status);
664
- incrementCount(eventTypeCounts, eventType);
665
- incrementCount(statusCounts, status);
666
-
667
- const actor = slugify(action.actor || '');
668
- if (actor) actors.add(actor);
669
- const claimant = slugify(state.claimed_by || state.assigned_to || '');
670
- if (claimant) {
671
- claimants.add(claimant);
672
- claimedEpisodeCount += 1;
673
- }
674
-
675
- const proof = String(row?.proof || '').trim();
676
- const proofBacked = proof.length >= 20;
677
- if (proofBacked) proofBackedEpisodeCount += 1;
678
- if (proofBacked && hasAnyTerm(proof, VALIDATION_TERMS)) validationReceiptCount += 1;
679
- if (proofBacked && hasAnyTerm(proof, QUALITY_TERMS)) qualityReceiptCount += 1;
680
- if (
681
- proofBacked
682
- && status !== 'failed'
683
- && (ATRIS_ACCEPTED_STATUSES.has(status) || ATRIS_ACCEPTED_EVENTS.has(eventType))
684
- ) {
685
- acceptedEpisodeCount += 1;
686
- }
687
- }
688
-
689
- return {
690
- schema: 'atris.agentxp_atris_action_signal.v1',
691
- source: '.atris/state/task_episodes.jsonl',
692
- episode_count: rows.length,
693
- accepted_episode_count: acceptedEpisodeCount,
694
- proof_backed_episode_count: proofBackedEpisodeCount,
695
- validation_receipt_count: validationReceiptCount,
696
- quality_receipt_count: qualityReceiptCount,
697
- claimed_episode_count: claimedEpisodeCount,
698
- reviewed_episode_count: eventTypeCounts.reviewed || 0,
699
- distinct_actor_count: actors.size,
700
- distinct_claimant_count: claimants.size,
701
- event_type_counts: Object.fromEntries(Object.entries(eventTypeCounts).sort()),
702
- status_counts: Object.fromEntries(Object.entries(statusCounts).sort()),
703
- included_in_total_agent_xp: false,
704
- public_leaderboard: false,
705
- role: 'rl_routing_only',
706
- note: 'Task done/finish proof can count here as non-public action context; Career XP still requires human accept.',
707
- };
708
- }
709
-
710
- function readTaskEpisodesForWorkspace(workspace) {
711
- if (!workspace) return [];
712
- return readJsonl(path.join(workspace, TASK_EPISODES_FILE));
713
- }
714
-
715
- function buildAtrisActionSignal(projection) {
716
- const workspaceRoots = Array.isArray(projection?.workspaces)
717
- ? projection.workspaces
718
- .filter(workspace => workspace?.included && workspace.workspace_root)
719
- .map(workspace => workspace.workspace_root)
720
- : [projection?.workspace_root].filter(Boolean);
721
- const rows = workspaceRoots.flatMap(workspace => readTaskEpisodesForWorkspace(workspace));
722
- return buildAtrisActionSignalFromRows(rows);
723
- }
724
-
725
- function runSqliteJsonOptional(dbPath, sql) {
726
- if (!dbPath || !fs.existsSync(dbPath)) return [];
727
- const result = spawnSync('sqlite3', ['-readonly', '-json', dbPath, sql], { encoding: 'utf8' });
728
- if (result.error || result.status !== 0) return [];
729
- const out = String(result.stdout || '').trim();
730
- if (!out) return [];
731
- try {
732
- return JSON.parse(out);
733
- } catch (_) {
734
- return [];
735
- }
736
- }
737
-
738
- function receiptHashBase(receipt, previousHash) {
739
- const base = {
740
- ...receipt,
741
- chain_version: RECEIPT_CHAIN_VERSION,
742
- previous_receipt_hash: previousHash || null,
743
- };
744
- delete base.receipt_hash;
745
- return base;
746
- }
747
-
748
- function receiptDuplicateComparable(receipt) {
749
- const comparable = { ...(receipt || {}) };
750
- delete comparable.chain_version;
751
- delete comparable.previous_receipt_hash;
752
- delete comparable.receipt_hash;
753
- return comparable;
754
- }
755
-
756
- function receiptLabel(receipt) {
757
- const title = String(receipt?.title || '').trim();
758
- if (title) return title;
759
- const task = String(receipt?.source_task_id || '').trim();
760
- if (task) return `task ${task.slice(0, 8)}`;
761
- const source = String(receipt?.source_type || receipt?.source || '').trim();
762
- return source || 'accepted proof';
763
- }
764
-
765
- function withReceiptIntegrity(receipt, previousHash) {
766
- const base = receiptHashBase(receipt, previousHash);
767
- return {
768
- ...base,
769
- receipt_hash: hashPayload(base),
770
- };
771
- }
772
-
773
- function normalizeReceiptChain(receipts, { allowLegacyUpgrade = true } = {}) {
774
- const seen = new Map();
775
- const errors = [];
776
- const dedupedReceipts = [];
777
- const normalized = [];
778
- let previousHash = null;
779
- let upgraded = false;
780
-
781
- for (const receipt of receipts) {
782
- if (!receipt || typeof receipt !== 'object') {
783
- errors.push('invalid_receipt');
784
- continue;
785
- }
786
- if (!receipt.receipt_id) errors.push('missing_receipt_id');
787
- if (receipt.receipt_id && seen.has(receipt.receipt_id)) {
788
- const first = seen.get(receipt.receipt_id);
789
- if (canonicalJson(receiptDuplicateComparable(first)) === canonicalJson(receiptDuplicateComparable(receipt))) {
790
- dedupedReceipts.push(receiptLabel(receipt));
791
- continue;
792
- }
793
- errors.push(`conflicting_duplicate_receipt:${receiptLabel(receipt)}`);
794
- }
795
-
796
- const expected = withReceiptIntegrity(receipt, previousHash);
797
- const hadIntegrity = Boolean(receipt.receipt_hash && receipt.chain_version && Object.prototype.hasOwnProperty.call(receipt, 'previous_receipt_hash'));
798
- if (hadIntegrity) {
799
- if (receipt.chain_version !== RECEIPT_CHAIN_VERSION) {
800
- errors.push(`receipt_chain_version_mismatch:${receiptLabel(receipt)}`);
801
- }
802
- if ((receipt.previous_receipt_hash || null) !== (previousHash || null)) {
803
- errors.push(`receipt_previous_hash_mismatch:${receiptLabel(receipt)}`);
804
- }
805
- if (receipt.receipt_hash !== expected.receipt_hash) {
806
- errors.push(`receipt_hash_mismatch:${receiptLabel(receipt)}`);
807
- }
808
- }
809
- if (!hadIntegrity && !allowLegacyUpgrade) {
810
- errors.push(`missing_receipt_integrity:${receiptLabel(receipt)}`);
811
- }
812
- if (!hadIntegrity && allowLegacyUpgrade) upgraded = true;
813
-
814
- normalized.push(expected);
815
- previousHash = expected.receipt_hash;
816
- if (receipt.receipt_id && !seen.has(receipt.receipt_id)) seen.set(receipt.receipt_id, receipt);
817
- }
818
-
819
- return {
820
- receipts: normalized,
821
- upgraded,
822
- deduped: dedupedReceipts.length > 0,
823
- integrity: {
824
- status: errors.length ? 'tampered' : 'verified',
825
- chain_version: RECEIPT_CHAIN_VERSION,
826
- receipts_count: normalized.length,
827
- head_hash: previousHash,
828
- errors,
829
- deduped_receipts: dedupedReceipts,
830
- local_trust: 'tamper_evident_not_attested',
831
- },
832
- };
833
- }
834
-
835
- function alignJsonlReadStart(filePath, start) {
836
- if (!start || start <= 0) return 0;
837
- const fd = fs.openSync(filePath, 'r');
838
- try {
839
- const previous = Buffer.alloc(1);
840
- fs.readSync(fd, previous, 0, 1, start - 1);
841
- if (previous[0] === 0x0a) return start;
842
-
843
- const current = Buffer.alloc(1);
844
- const currentBytes = fs.readSync(fd, current, 0, 1, start);
845
- if (previous[0] === 0x0d && currentBytes === 1 && current[0] === 0x0a) {
846
- return start + 1;
847
- }
848
-
849
- const chunkSize = 64 * 1024;
850
- let scanEnd = start;
851
- while (scanEnd > 0) {
852
- const scanStart = Math.max(0, scanEnd - chunkSize);
853
- const buffer = Buffer.alloc(scanEnd - scanStart);
854
- fs.readSync(fd, buffer, 0, buffer.length, scanStart);
855
- for (let index = buffer.length - 1; index >= 0; index -= 1) {
856
- if (buffer[index] === 0x0a) return scanStart + index + 1;
857
- }
858
- scanEnd = scanStart;
859
- }
860
- return 0;
861
- } finally {
862
- fs.closeSync(fd);
863
- }
864
- }
865
-
866
- function readTaskEpisodeTail(episodePath, cursorPath, options = {}) {
867
- const stat = fs.existsSync(episodePath) ? fs.statSync(episodePath) : null;
868
- const forceReset = Boolean(options.forceReset);
869
- const cursor = forceReset ? null : readJsonFile(cursorPath, null);
870
- const previousBytes = Number(cursor?.bytes_read || 0);
871
- const size = stat ? stat.size : 0;
872
- const reset = forceReset || !cursor || cursor.source_path !== episodePath || size < previousBytes;
873
- const start = reset || !stat ? 0 : alignJsonlReadStart(episodePath, previousBytes);
874
- if (!stat || size === start) {
875
- return {
876
- episodes: [],
877
- cursor: {
878
- schema: 'atris.career_xp_cursor.v1',
879
- source_path: episodePath,
880
- bytes_read: size,
881
- source_size: size,
882
- last_episode_id: cursor?.last_episode_id || null,
883
- updated_at: new Date().toISOString(),
884
- reset,
885
- },
886
- };
887
- }
888
-
889
- const fd = fs.openSync(episodePath, 'r');
890
- try {
891
- const buffer = Buffer.alloc(size - start);
892
- fs.readSync(fd, buffer, 0, buffer.length, start);
893
- const parsed = parseJsonlContent(buffer.toString('utf8'), { allowPartialTail: true });
894
- const episodes = parsed.rows;
895
- const bytesRead = start + parsed.parsedBytes;
896
- return {
897
- episodes,
898
- cursor: {
899
- schema: 'atris.career_xp_cursor.v1',
900
- source_path: episodePath,
901
- bytes_read: bytesRead,
902
- source_size: size,
903
- last_episode_id: episodes.length ? episodes[episodes.length - 1].episode_id || null : cursor?.last_episode_id || null,
904
- updated_at: new Date().toISOString(),
905
- reset,
906
- skipped_partial_tail: parsed.skippedPartialTail,
907
- skipped_partial_tail_bytes: parsed.skippedPartialTailBytes,
908
- },
909
- };
910
- } finally {
911
- fs.closeSync(fd);
912
- }
913
- }
914
-
915
- function receiptFromTaskEpisode(episode, treeHash) {
916
- const episodeId = episode?.episode_id;
917
- const label = episode?.rl?.label;
918
- const rejected = label === 'rework_requested' || label === 'rejected';
919
- const doneForXp = String(episode?.state?.status || '').toLowerCase() === 'done';
920
- const hasExplicitCareerXp = episode?.career_xp && typeof episode.career_xp === 'object';
921
- const eligible = doneForXp && (
922
- episode?.career_xp?.eligible === true
923
- || (!hasExplicitCareerXp && label === 'accepted')
924
- );
925
- const proof = String(episode?.proof || '').trim();
926
- const reward = asNumber(episode?.career_xp?.reward ?? episode?.reward?.value, 0);
927
- if (!episodeId || (!rejected && (!eligible || !proof || reward <= 0 || !hasIndependentXpReview(episode)))) return null;
928
-
929
- return {
930
- schema: 'atris.career_xp_receipt.v1',
931
- tree_hash: treeHash,
932
- receipt_id: `task_review:${episodeId}${rejected ? ':rejected' : ''}`,
933
- source: 'atris-cli',
934
- source_type: 'task_review',
935
- source_task_id: episode.task_id || null,
936
- source_episode_id: episodeId,
937
- workspace_root: episode.workspace_root || null,
938
- actor: episode.action?.actor || null,
939
- // Who built the work. `actor` is the reviewer; trust is earned by the builder.
940
- builder: episode.state?.metadata?.built_by || episode.state?.claimed_by || null,
941
- outcome: rejected ? 'rejected' : 'accepted',
942
- xp: rejected ? 0 : reward,
943
- reward: rejected ? 0 : reward,
944
- proof: proof || null,
945
- proof_ref: proof || null,
946
- source_episode_hash: hashPayload(episode),
947
- title: episode.state?.title || null,
948
- goal: episode.goal || null,
949
- accepted_at: episode.created_at || new Date().toISOString(),
950
- tokens_used: Number.isFinite(Number(episode.tokens_used)) ? Number(episode.tokens_used) : null,
951
- duration_seconds: Number.isFinite(Number(episode.duration_seconds)) ? Number(episode.duration_seconds) : null,
952
- };
953
- }
954
-
955
- function latestReceipt(receipts) {
956
- return receipts
957
- .slice()
958
- .sort((a, b) => new Date(b.accepted_at).getTime() - new Date(a.accepted_at).getTime())[0] || null;
959
- }
960
-
961
- function countBySource(receipts) {
962
- return receipts.reduce((acc, receipt) => {
963
- const source = receipt.source || 'unknown';
964
- acc[source] = (acc[source] || 0) + 1;
965
- return acc;
966
- }, {});
967
- }
968
-
969
- function buildCareerXpProjection(receipts, workspace, integrity = {}) {
970
- const trusted = integrity.status === 'tampered' ? [] : receipts;
971
- const accepted = trusted
972
- .filter(receipt => receipt && receipt.outcome === 'accepted' && asNumber(receipt.xp) > 0)
973
- .sort((a, b) => new Date(a.accepted_at).getTime() - new Date(b.accepted_at).getTime());
974
- const totalXp = accepted.reduce((sum, receipt) => sum + asNumber(receipt.xp), 0);
975
- const today = localDateKey(new Date());
976
- const todayXp = accepted
977
- .filter(receipt => localDateKey(receipt.accepted_at) === today)
978
- .reduce((sum, receipt) => sum + asNumber(receipt.xp), 0);
979
- const level = levelFromXp(totalXp);
980
- const levelBase = (level - 1) * LEVEL_XP;
981
- const currentLevelXp = totalXp - levelBase;
982
- const remainingXp = Math.max(0, (level * LEVEL_XP) - totalXp);
983
- const nextLevelProgress = {
984
- level,
985
- next_level: level + 1,
986
- current_xp: currentLevelXp,
987
- required_xp: LEVEL_XP,
988
- remaining_xp: remainingXp,
989
- percent: Math.round((currentLevelXp / LEVEL_XP) * 1000) / 10,
990
- };
991
- const latest = latestReceipt(accepted);
992
- const localActivity = buildLocalAssistantActivity(workspace, totalXp);
993
-
994
- return {
995
- schema: 'atris.career_xp_projection.v1',
996
- generated_at: new Date().toISOString(),
997
- workspace_root: workspace,
998
- metric_label: AGENT_XP_LABEL,
999
- agent_xp: totalXp,
1000
- total_agent_xp: totalXp,
1001
- today_agent_xp: todayXp,
1002
- career_xp: totalXp,
1003
- total_xp: totalXp,
1004
- today_xp: todayXp,
1005
- level,
1006
- leaderboard_eligible: false,
1007
- integrity_status: integrity.status || 'unknown',
1008
- next_level_progress: nextLevelProgress,
1009
- career: {
1010
- level,
1011
- card_label: 'Career Card',
1012
- progress_label: 'Career Progress',
1013
- next_level_progress: nextLevelProgress,
1014
- },
1015
- contribution_graph: buildAgentXpContributionGraph(accepted),
1016
- receipts_count: accepted.length,
1017
- sources: countBySource(accepted),
1018
- earning_model: EARNING_MODEL,
1019
- local_activity: localActivity,
1020
- latest_accepted_proof: latest ? {
1021
- label: receiptLabel(latest),
1022
- receipt_id: latest.receipt_id,
1023
- source: latest.source,
1024
- source_task_id: latest.source_task_id,
1025
- source_episode_id: latest.source_episode_id,
1026
- title: latest.title,
1027
- proof: latest.proof,
1028
- xp: latest.xp,
1029
- reward: latest.reward,
1030
- actor: latest.actor,
1031
- accepted_at: latest.accepted_at,
1032
- goal: latest.goal || null,
1033
- } : null,
1034
- ledger: {
1035
- receipts_path: path.join(workspace, CAREER_XP_RECEIPTS_FILE),
1036
- projection_path: path.join(workspace, CAREER_XP_PROJECTION_FILE),
1037
- cursor_path: path.join(workspace, CAREER_XP_CURSOR_FILE),
1038
- },
1039
- integrity: {
1040
- status: integrity.status || 'unknown',
1041
- chain_version: integrity.chain_version || RECEIPT_CHAIN_VERSION,
1042
- head_hash: integrity.head_hash || null,
1043
- receipts_count: integrity.receipts_count ?? accepted.length,
1044
- errors: integrity.errors || [],
1045
- deduped_receipts: integrity.deduped_receipts || [],
1046
- local_trust: integrity.local_trust || 'tamper_evident_not_attested',
1047
- cursor: integrity.cursor || null,
1048
- note: 'Local XP is tamper-evident. Public trust still requires cloud/notary attestation.',
1049
- },
1050
- };
1051
- }
1052
-
1053
- // The eligible flag records acceptance intent, not reviewer independence.
1054
- // Apply the same actor evidence to new episodes and historical recovery.
1055
- function hasIndependentXpReview(episode) {
1056
- const builder = taskBuilder(episode?.state);
1057
- if (!builder) return false;
1058
- const reviewer = normalizeActor(episode.action?.actor);
1059
- if (reviewer && reviewer !== builder) return true;
1060
- const metadata = episode.state.metadata || {};
1061
- const verifier = normalizeActor(metadata.certified_verified_by);
1062
- const reviewActors = Array.isArray(metadata.agent_review_actors) ? metadata.agent_review_actors.map(normalizeActor) : [];
1063
- return metadata.agent_certified === true && metadata.machine_verified === true
1064
- && Boolean(verifier) && verifier !== builder
1065
- && verifier === normalizeActor(metadata.independent_review_by)
1066
- && reviewActors.includes(verifier);
1067
- }
1068
-
1069
- function readLinkedXpFile(filePath, options = {}) {
1070
- try {
1071
- return readJsonl(filePath, options);
1072
- } catch {
1073
- console.error(`warning: skipped unreadable linked xp file at ${filePath}`);
1074
- return [];
1075
- }
1076
- }
1077
-
1078
- // Historical linked ledgers predate the shared writer. Require explicit
1079
- // eligibility here; receipt conversion also enforces independent review.
1080
- function recoverLinkedTaskEpisodes(workspace) {
1081
- return careerXpLinkedRoots(workspace).flatMap(root => readLinkedXpFile(path.join(root, TASK_EPISODES_FILE), { allowPartialTail: true }))
1082
- .filter(episode => episode?.state?.status === 'done' && episode?.career_xp?.eligible === true
1083
- && String(episode.proof || '').trim()
1084
- && asNumber(episode.career_xp.reward ?? episode.reward?.value) > 0
1085
- && hasIndependentXpReview(episode));
1086
- }
1087
-
1088
- function recoverLinkedReceipts(workspace) {
1089
- return careerXpLinkedRoots(workspace).flatMap(root => {
1090
- const chain = normalizeReceiptChain(readLinkedXpFile(path.join(root, CAREER_XP_RECEIPTS_FILE)), { allowLegacyUpgrade: false });
1091
- return chain.integrity.status === 'verified' ? chain.receipts : [];
1092
- });
1093
- }
1094
-
1095
- function collectLocalXpProjectionState(args = [], { write = true, resolveSharedRoot = true } = {}) {
1096
- const selected = path.resolve(readInlineFirstFlag(args, '--workspace', defaultXpWorkspace()));
1097
- const workspace = resolveSharedRoot ? careerXpRoot(selected) : selected;
1098
- return withCareerXpLock(workspace, () => collectLockedXpProjectionState(workspace, { write, resolveSharedRoot }));
1099
- }
1100
-
1101
- function collectLockedXpProjectionState(workspace, { write, resolveSharedRoot }) {
1102
- const episodePath = path.join(workspace, TASK_EPISODES_FILE);
1103
- const receiptsPath = path.join(workspace, CAREER_XP_RECEIPTS_FILE);
1104
- const projectionPath = path.join(workspace, CAREER_XP_PROJECTION_FILE);
1105
- const cursorPath = path.join(workspace, CAREER_XP_CURSOR_FILE);
1106
- let existingChain = normalizeReceiptChain(readJsonl(receiptsPath));
1107
- if ((existingChain.upgraded || existingChain.deduped) && existingChain.integrity.status === 'verified') {
1108
- if (write) writeJsonl(receiptsPath, existingChain.receipts);
1109
- existingChain = normalizeReceiptChain(existingChain.receipts);
1110
- }
1111
- if (existingChain.integrity.status === 'tampered') {
1112
- const projection = buildCareerXpProjection([], workspace, existingChain.integrity);
1113
- if (write) writeJson(projectionPath, projection);
1114
- return {
1115
- projection: {
1116
- ...projection,
1117
- collected_receipts: 0,
1118
- },
1119
- receipts: [],
1120
- newReceipts: [],
1121
- };
1122
- }
1123
-
1124
- const replayFromStart = existingChain.receipts.length === 0
1125
- && fileSize(episodePath) > 0
1126
- && fileSize(receiptsPath) === 0;
1127
- const tail = readTaskEpisodeTail(episodePath, cursorPath, { forceReset: replayFromStart });
1128
- const recovered = resolveSharedRoot ? recoverLinkedTaskEpisodes(workspace) : [];
1129
- const episodes = [...tail.episodes, ...recovered];
1130
- const treeHash = write && episodes.length ? treeHashFor(workspace) : null;
1131
- const seen = new Set(existingChain.receipts.map(receipt => receipt.receipt_id).filter(Boolean));
1132
- let previousHash = existingChain.integrity.head_hash || null;
1133
- const recoveredReceipts = resolveSharedRoot ? recoverLinkedReceipts(workspace) : [];
1134
- const newReceipts = [...recoveredReceipts, ...episodes.map(episode => receiptFromTaskEpisode(episode, treeHash)).filter(Boolean)]
1135
- .filter((receipt) => {
1136
- if (seen.has(receipt.receipt_id)) return false;
1137
- seen.add(receipt.receipt_id);
1138
- return true;
1139
- })
1140
- .map((receipt) => {
1141
- const signed = withReceiptIntegrity(receipt, previousHash);
1142
- previousHash = signed.receipt_hash;
1143
- return signed;
1144
- });
1145
- const receipts = [...existingChain.receipts, ...newReceipts];
1146
- const finalChain = normalizeReceiptChain(receipts);
1147
- const finalIntegrity = {
1148
- ...finalChain.integrity,
1149
- cursor: tail.cursor,
1150
- };
1151
- const projection = buildCareerXpProjection(
1152
- finalIntegrity.status === 'tampered' ? [] : finalChain.receipts,
1153
- workspace,
1154
- finalIntegrity,
1155
- );
1156
-
1157
- if (write) {
1158
- appendJsonl(receiptsPath, newReceipts);
1159
- writeJson(cursorPath, tail.cursor);
1160
- writeJson(projectionPath, projection);
1161
- }
1162
-
1163
- return {
1164
- projection: {
1165
- ...projection,
1166
- collected_receipts: newReceipts.length,
1167
- },
1168
- receipts: finalChain.receipts,
1169
- newReceipts,
1170
- cursor: tail.cursor,
1171
- };
1172
- }
1173
-
1174
- function collectLocalXpProjection(args = []) {
1175
- return collectLocalXpProjectionState(args, { write: true }).projection;
1176
- }
1177
-
1178
- function uniquePaths(paths) {
1179
- const seen = new Set();
1180
- return paths
1181
- .map(item => path.resolve(item))
1182
- .filter((item) => {
1183
- if (seen.has(item)) return false;
1184
- seen.add(item);
1185
- return true;
1186
- });
1187
- }
1188
-
1189
- function stateWorkspaceForFile(filePath) {
1190
- const stateDir = path.dirname(filePath);
1191
- if (path.basename(stateDir) !== 'state') return null;
1192
- const atrisDir = path.dirname(stateDir);
1193
- if (path.basename(atrisDir) !== '.atris') return null;
1194
- return path.dirname(atrisDir);
1195
- }
1196
-
1197
- function discoverCareerXpWorkspaces(root, maxDepth = DEFAULT_SEARCH_DEPTH) {
1198
- const start = path.resolve(root);
1199
- if (!fs.existsSync(start)) return [];
1200
- const workspaces = new Set();
1201
- const stack = [{ dir: start, depth: 0 }];
1202
-
1203
- while (stack.length) {
1204
- const { dir, depth } = stack.pop();
1205
- let entries;
1206
- try {
1207
- entries = fs.readdirSync(dir, { withFileTypes: true });
1208
- } catch (_) {
1209
- continue;
1210
- }
1211
-
1212
- for (const entry of entries) {
1213
- const entryPath = path.join(dir, entry.name);
1214
- if (entry.isDirectory()) {
1215
- if (depth >= maxDepth || SEARCH_EXCLUDED_DIRS.has(entry.name)) continue;
1216
- stack.push({ dir: entryPath, depth: depth + 1 });
1217
- continue;
1218
- }
1219
- if (!entry.isFile() || !XP_STATE_FILES.has(entry.name)) continue;
1220
- const workspace = stateWorkspaceForFile(entryPath);
1221
- if (workspace) workspaces.add(workspace);
1222
- }
1223
- }
1224
-
1225
- return Array.from(workspaces).sort();
1226
- }
1227
-
1228
- function defaultAllSearchRoots(args = []) {
1229
- const explicitRoots = readFlagValues(args, ['--root', '--search-root']);
1230
- if (explicitRoots.length) return uniquePaths(explicitRoots);
1231
-
1232
- const roots = [];
1233
- const workspace = readInlineFirstFlag(args, '--workspace', null);
1234
- if (workspace) roots.push(workspace);
1235
- roots.push(process.cwd());
1236
- roots.push(path.join(os.homedir(), 'arena'));
1237
- return uniquePaths(roots);
1238
- }
1239
-
1240
- function workspaceName(workspace) {
1241
- return path.basename(workspace) || workspace;
1242
- }
1243
-
1244
- function booleanSetting(value) {
1245
- if (value === true || value === false) return value;
1246
- const text = String(value || '').trim().toLowerCase();
1247
- if (['1', 'true', 'yes', 'public', 'global'].includes(text)) return true;
1248
- if (['0', 'false', 'no', 'private', 'internal', 'org'].includes(text)) return false;
1249
- return null;
1250
- }
1251
-
1252
- function agentXpPublicBinding(binding = {}) {
1253
- const nested = binding.agentxp && typeof binding.agentxp === 'object' ? binding.agentxp : {};
1254
- const explicit = booleanSetting(
1255
- binding.agentxp_public
1256
- ?? binding.public_agentxp
1257
- ?? binding.agentxp_public_leaderboard
1258
- ?? nested.public
1259
- ?? nested.public_agentxp
1260
- );
1261
- if (explicit !== null) return explicit;
1262
-
1263
- const visibility = slugify(binding.agentxp_visibility || nested.visibility);
1264
- if (visibility === 'public') return true;
1265
- if (['internal', 'private'].includes(visibility)) return false;
1266
-
1267
- const scope = slugify(binding.agentxp_scope || nested.scope);
1268
- if (scope === 'global') return true;
1269
- if (['org', 'internal', 'private'].includes(scope)) return false;
1270
-
1271
- return null;
1272
- }
1273
-
1274
- function publicWorkspaceBinding(workspace) {
1275
- const binding = readJsonFile(path.join(workspace, BUSINESS_BINDING_FILE), null);
1276
- if (!binding || typeof binding !== 'object') return null;
1277
- const businessId = String(binding.business_id || binding.id || '').trim();
1278
- const workspaceId = String(binding.workspace_id || '').trim();
1279
- const businessSlug = slugify(binding.slug || binding.business_slug || binding.name);
1280
- const workspaceTemplate = slugify(binding.workspace_template || binding.organization_type || binding.computer_type);
1281
- const agentxpPublic = agentXpPublicBinding(binding);
1282
- if (!businessId && !workspaceId && !businessSlug) return null;
1283
- return {
1284
- business_id: businessId || null,
1285
- workspace_id: workspaceId || null,
1286
- business_slug: businessSlug || null,
1287
- workspace_template: workspaceTemplate || null,
1288
- computer: businessSlug || workspaceName(workspace),
1289
- computer_slug: businessSlug || slugify(workspaceName(workspace)),
1290
- ...(agentxpPublic === null ? {} : { agentxp_public: agentxpPublic }),
1291
- };
1292
- }
1293
-
1294
- function isVerifiedProjection(projection) {
1295
- return projection?.schema === 'atris.career_xp_projection.v1'
1296
- && projection.integrity_status === 'verified'
1297
- && projection.integrity?.status === 'verified';
1298
- }
1299
-
1300
- function buildAllCareerXpProjection(projections, searchRoots = []) {
1301
- const warnings = [];
1302
- const verified = [];
1303
- const workspaces = projections.map((item) => {
1304
- if (item.error) {
1305
- warnings.push({
1306
- workspace_root: item.workspace_root,
1307
- reason: item.error,
1308
- });
1309
- return {
1310
- workspace_root: item.workspace_root,
1311
- name: workspaceName(item.workspace_root),
1312
- included: false,
1313
- integrity_status: 'error',
1314
- error: item.error,
1315
- };
1316
- }
1317
-
1318
- const projection = item.projection;
1319
- const included = isVerifiedProjection(projection);
1320
- if (included) {
1321
- verified.push(projection);
1322
- } else {
1323
- warnings.push({
1324
- workspace_root: item.workspace_root,
1325
- reason: `integrity:${projection?.integrity_status || projection?.integrity?.status || 'unknown'}`,
1326
- errors: projection?.integrity?.errors || [],
1327
- });
1328
- }
1329
-
1330
- return {
1331
- workspace_root: item.workspace_root,
1332
- name: workspaceName(item.workspace_root),
1333
- included,
1334
- total_xp: asNumber(projection?.total_xp),
1335
- today_xp: asNumber(projection?.today_xp),
1336
- level: asNumber(projection?.level, 1),
1337
- receipts_count: asNumber(projection?.receipts_count),
1338
- integrity_status: projection?.integrity_status || projection?.integrity?.status || 'unknown',
1339
- leaderboard_eligible: Boolean(projection?.leaderboard_eligible),
1340
- latest_accepted_proof: projection?.latest_accepted_proof || null,
1341
- local_activity: projection?.local_activity || null,
1342
- ledger: projection?.ledger || null,
1343
- };
1344
- }).sort((a, b) => {
1345
- if (b.included !== a.included) return Number(b.included) - Number(a.included);
1346
- return b.total_xp - a.total_xp || a.name.localeCompare(b.name);
1347
- });
1348
-
1349
- const totalXp = verified.reduce((sum, projection) => sum + asNumber(projection.total_xp), 0);
1350
- const todayXp = verified.reduce((sum, projection) => sum + asNumber(projection.today_xp), 0);
1351
- const level = levelFromXp(totalXp);
1352
- const levelBase = (level - 1) * LEVEL_XP;
1353
- const currentLevelXp = totalXp - levelBase;
1354
- const nextLevelProgress = {
1355
- level,
1356
- next_level: level + 1,
1357
- current_xp: currentLevelXp,
1358
- required_xp: LEVEL_XP,
1359
- remaining_xp: Math.max(0, (level * LEVEL_XP) - totalXp),
1360
- percent: Math.round((currentLevelXp / LEVEL_XP) * 1000) / 10,
1361
- };
1362
- const latest = verified
1363
- .map(projection => ({
1364
- ...(projection.latest_accepted_proof || {}),
1365
- workspace_root: projection.workspace_root,
1366
- workspace_name: workspaceName(projection.workspace_root),
1367
- }))
1368
- .filter(proof => proof && proof.accepted_at)
1369
- .sort((a, b) => new Date(b.accepted_at).getTime() - new Date(a.accepted_at).getTime())[0] || null;
1370
- const contributionGraph = combineAgentXpContributionGraphs(
1371
- verified.map(projection => projection.contribution_graph),
1372
- );
1373
- const activityProviders = new Set();
1374
- let rawActivitySignals = 0;
1375
- let contextAgentXp = 0;
1376
- for (const projection of verified) {
1377
- const activity = projection.local_activity || {};
1378
- rawActivitySignals += asNumber(activity.raw_signal_count);
1379
- contextAgentXp += asNumber(activity.context_agent_xp);
1380
- for (const provider of activity.detected_providers || []) {
1381
- activityProviders.add(provider);
1382
- }
1383
- }
1384
-
1385
- return {
1386
- schema: 'atris.career_xp_profile.v1',
1387
- generated_at: new Date().toISOString(),
1388
- search_roots: searchRoots,
1389
- workspace_count: workspaces.length,
1390
- verified_workspace_count: verified.length,
1391
- metric_label: AGENT_XP_LABEL,
1392
- agent_xp: totalXp,
1393
- total_agent_xp: totalXp,
1394
- today_agent_xp: todayXp,
1395
- career_xp: totalXp,
1396
- total_xp: totalXp,
1397
- today_xp: todayXp,
1398
- level,
1399
- leaderboard_eligible: false,
1400
- next_level_progress: nextLevelProgress,
1401
- career: {
1402
- level,
1403
- card_label: 'Career Card',
1404
- progress_label: 'Career Progress',
1405
- next_level_progress: nextLevelProgress,
1406
- },
1407
- contribution_graph: contributionGraph,
1408
- receipts_count: verified.reduce((sum, projection) => sum + asNumber(projection.receipts_count), 0),
1409
- earning_model: EARNING_MODEL,
1410
- local_activity: {
1411
- schema: 'atris.agentxp_local_activity_summary.v1',
1412
- public_leaderboard: false,
1413
- detected_providers: Array.from(activityProviders).sort(),
1414
- raw_signal_count: rawActivitySignals,
1415
- context_agent_xp: Math.round(contextAgentXp * 100) / 100,
1416
- included_in_total_agent_xp: false,
1417
- note: 'Aggregated local assistant activity is context only; verified receipts define total AgentXP.',
1418
- },
1419
- latest_accepted_proof: latest,
1420
- workspaces,
1421
- integrity_status: warnings.length ? 'warnings' : 'verified',
1422
- integrity: {
1423
- status: warnings.length ? 'warnings' : 'verified',
1424
- warnings,
1425
- local_trust: 'workspace_local_tamper_evident_not_attested',
1426
- note: 'Only verified local ledgers are counted. Public trust still requires cloud/notary attestation.',
1427
- },
1428
- };
1429
- }
1430
-
1431
- function collectAllLocalXpProjection(args = []) {
1432
- const searchRoots = defaultAllSearchRoots(args).filter(root => fs.existsSync(root));
1433
- const explicitWorkspaces = readFlagValues(args, '--workspace');
1434
- const discovered = searchRoots.flatMap(root => discoverCareerXpWorkspaces(root));
1435
- // Local recovery folds old checkout episodes into the durable main ledger.
1436
- // Canonicalize before aggregation so copies can never earn twice.
1437
- const workspaces = uniquePaths([...explicitWorkspaces, ...discovered].map(careerXpRoot));
1438
- const projections = workspaces.map((workspace) => {
1439
- try {
1440
- return {
1441
- workspace_root: workspace,
1442
- projection: collectLocalXpProjectionState(['--workspace', workspace]).projection,
1443
- };
1444
- } catch (error) {
1445
- return {
1446
- workspace_root: workspace,
1447
- error: error.message,
1448
- };
1449
- }
1450
- });
1451
-
1452
- return buildAllCareerXpProjection(projections, searchRoots);
1453
- }
1454
-
1455
- function readTaskProjectionState(workspace) {
1456
- const projectionPath = path.join(workspace, TASK_PROJECTION_FILE);
1457
- try {
1458
- const projection = readJsonFile(projectionPath, { tasks: [] });
1459
- return {
1460
- tasks: Array.isArray(projection?.tasks) ? projection.tasks : [],
1461
- warning: null,
1462
- };
1463
- } catch (error) {
1464
- return {
1465
- tasks: [],
1466
- warning: `task_projection_unreadable:${error.message}`,
1467
- };
1468
- }
1469
- }
1470
-
1471
- function taskRef(task) {
1472
- return task?.display_id || task?.legacy_ref || task?.id || 'task';
1473
- }
1474
-
1475
- function taskTimes(task) {
1476
- const times = [
1477
- task?.created_at,
1478
- task?.updated_at,
1479
- task?.done_at,
1480
- task?.review?.reviewed_at,
1481
- task?.review?.accepted_at,
1482
- task?.review?.revised_at,
1483
- task?.metadata?.agent_reviewed_at,
1484
- task?.metadata?.accepted_at,
1485
- task?.metadata?.human_revision_at,
1486
- ];
1487
- for (const event of task?.events || []) times.push(event.created_at);
1488
- for (const message of task?.messages || []) times.push(message.created_at);
1489
- return times
1490
- .map(timestampMs)
1491
- .filter(ms => ms !== null);
1492
- }
1493
-
1494
- function taskTouchedInWindow(task, sinceMs, untilMs) {
1495
- return taskTimes(task).some(ms => ms >= sinceMs && ms <= untilMs);
1496
- }
1497
-
1498
- function latestTaskMs(task) {
1499
- const times = taskTimes(task);
1500
- return times.length ? Math.max(...times) : null;
1501
- }
1502
-
1503
- function taskApprovalStatus(task) {
1504
- return task?.review?.approval_status || task?.metadata?.approval_status || null;
1505
- }
1506
-
1507
- function textExcerpt(value, limit = 220) {
1508
- const text = String(value || '').replace(/\s+/g, ' ').trim();
1509
- if (text.length <= limit) return text || null;
1510
- return `${text.slice(0, limit - 3)}...`;
1511
- }
1512
-
1513
- function compactTask(task) {
1514
- const proof = task?.review?.proof || task?.metadata?.latest_agent_proof || null;
1515
- return {
1516
- task_id: taskRef(task),
1517
- id: task?.id || null,
1518
- title: task?.title || null,
1519
- status: task?.status || null,
1520
- tag: task?.tag || null,
1521
- claimed_by: task?.claimed_by || null,
1522
- approval_status: taskApprovalStatus(task),
1523
- summary: task?.review?.summary || null,
1524
- proof_excerpt: textExcerpt(proof),
1525
- has_proof: Boolean(proof),
1526
- updated_at: isoFromMs(latestTaskMs(task)),
1527
- };
1528
- }
1529
-
1530
- function receiptMatchesTask(receipt, task) {
1531
- const sourceTaskId = String(receipt?.source_task_id || '');
1532
- if (!sourceTaskId) return false;
1533
- return [task?.id, task?.display_id, task?.legacy_ref]
1534
- .filter(Boolean)
1535
- .map(String)
1536
- .includes(sourceTaskId);
1537
- }
1538
-
1539
- function compactReceipt(receipt, task = null, options = {}) {
1540
- const proof = receipt?.proof || null;
1541
- const compact = {
1542
- receipt_id: receipt?.receipt_id || null,
1543
- task_id: task ? taskRef(task) : receipt?.source_task_id || null,
1544
- source_task_id: receipt?.source_task_id || null,
1545
- title: receipt?.title || task?.title || null,
1546
- proof_excerpt: textExcerpt(proof),
1547
- has_proof: Boolean(proof),
1548
- xp: asNumber(receipt?.xp),
1549
- actor: receipt?.actor || null,
1550
- accepted_at: receipt?.accepted_at || null,
1551
- goal: receipt?.goal || null,
1552
- };
1553
- if (options.fullProof) {
1554
- compact.proof = proof;
1555
- }
1556
- return compact;
1557
- }
1558
-
1559
- function compactGoal(goal) {
1560
- if (!goal) return null;
1561
- if (typeof goal === 'string') {
1562
- const label = goal.trim();
1563
- return label ? { label } : null;
1564
- }
1565
- const label = goal.objective || goal.title || goal.condition || goal.id || goal.goal_id || null;
1566
- if (!label) return null;
1567
- return {
1568
- provider: goal.provider || null,
1569
- thread_id: goal.thread_id || null,
1570
- goal_id: goal.id || goal.goal_id || null,
1571
- label,
1572
- status: goal.status || null,
1573
- met: typeof goal.met === 'boolean' ? goal.met : null,
1574
- tokens_used: Number.isFinite(Number(goal.tokens_used)) ? Number(goal.tokens_used) : null,
1575
- time_used_seconds: Number.isFinite(Number(goal.time_used_seconds)) ? Number(goal.time_used_seconds) : null,
1576
- created_at: goal.created_at || isoFromMs(goal.created_at_ms),
1577
- updated_at: goal.updated_at || isoFromMs(goal.updated_at_ms),
1578
- workspace_path: goal.thread_cwd || goal.workspace_path || null,
1579
- };
1580
- }
1581
-
1582
- function uniqueGoals(goals) {
1583
- const seen = new Set();
1584
- return goals
1585
- .map(compactGoal)
1586
- .filter(Boolean)
1587
- .filter((goal) => {
1588
- const key = `${goal.goal_id || ''}:${goal.label}`;
1589
- if (seen.has(key)) return false;
1590
- seen.add(key);
1591
- return true;
1592
- });
1593
- }
1594
-
1595
- function buildNextQuest(reviewTasks, touchedTasks, acceptedReceipts) {
1596
- const pendingReview = reviewTasks
1597
- .slice()
1598
- .sort((a, b) => (latestTaskMs(b) || 0) - (latestTaskMs(a) || 0))[0];
1599
- if (pendingReview) {
1600
- return `Review ${taskRef(pendingReview)}: ${pendingReview.title}`;
1601
- }
1602
-
1603
- const activeTask = touchedTasks
1604
- .filter(task => ['claimed', 'open', 'todo', 'do'].includes(String(task.status || '').toLowerCase()))
1605
- .sort((a, b) => (latestTaskMs(b) || 0) - (latestTaskMs(a) || 0))[0];
1606
- if (activeTask) {
1607
- return `Move ${taskRef(activeTask)} to Review with proof.`;
1608
- }
1609
-
1610
- if (!acceptedReceipts.length) {
1611
- return 'Put one task into Review with proof, then accept it if the proof is real.';
1612
- }
1613
- return 'Pick the next highest-leverage task and move it to Review with proof.';
1614
- }
1615
-
1616
- function workspacePathCandidates(workspace) {
1617
- const candidates = [workspace];
1618
- try {
1619
- candidates.push(fs.realpathSync(workspace));
1620
- } catch (_) {
1621
- // best-effort only
1622
- }
1623
- const pwd = process.env.PWD || '';
1624
- if (pwd) {
1625
- try {
1626
- if (fs.existsSync(pwd) && fs.realpathSync(pwd) === fs.realpathSync(workspace)) candidates.push(pwd);
1627
- } catch (_) {
1628
- // best-effort only
1629
- }
1630
- }
1631
- return [...new Set(candidates.filter(Boolean))];
1632
- }
1633
-
1634
- function codexHasThreadsTable(dbPath) {
1635
- if (!dbPath || !fs.existsSync(dbPath)) return false;
1636
- return runSqliteJsonOptional(dbPath, `SELECT name FROM sqlite_master WHERE type='table' AND name='threads' LIMIT 1;`).length > 0;
1637
- }
1638
-
1639
- function readCodexGoalsForSession(args, workspace, sinceMs, untilMs) {
1640
- const explicitDb = readInlineFirstFlag(args, '--codex-state', readInlineFirstFlag(args, '--state', process.env.CODEX_STATE_DB || ''));
1641
- const dbPath = explicitDb
1642
- ? path.resolve(expandHome(explicitDb))
1643
- : path.resolve(fs.existsSync(CODEX_GOALS_FILE) ? CODEX_GOALS_FILE : CODEX_STATE_FILE);
1644
-
1645
- // Goals live in goals_1.sqlite (no `threads` table); cwd/title metadata lives in state_5.sqlite.
1646
- // Legacy single-file layouts (and test fixtures) keep both in one DB.
1647
- let threadsTable = codexHasThreadsTable(dbPath) ? 'threads' : null;
1648
- let attachPrefix = '';
1649
- if (!threadsTable) {
1650
- const threadsDb = CODEX_STATE_FILE !== dbPath ? CODEX_STATE_FILE : '';
1651
- if (codexHasThreadsTable(threadsDb)) {
1652
- threadsTable = 'tdb.threads';
1653
- attachPrefix = `ATTACH ${sqlString(threadsDb)} AS tdb;\n`;
1654
- }
1655
- }
1656
-
1657
- const threadId = readInlineFirstFlag(args, '--thread', process.env.CODEX_THREAD_ID || '');
1658
- const clauses = [];
1659
- if (threadId) clauses.push(`tg.thread_id = ${sqlString(threadId)}`);
1660
- const workspaceCandidates = workspacePathCandidates(workspace);
1661
- if (workspaceCandidates.length && threadsTable) {
1662
- clauses.push(`(t.cwd IN (${workspaceCandidates.map(sqlString).join(', ')}) AND tg.created_at_ms <= ${Number(untilMs)} AND (tg.updated_at_ms >= ${Number(sinceMs)} OR tg.status = 'active'))`);
1663
- }
1664
- if (!clauses.length) return [];
1665
-
1666
- const join = threadsTable ? `LEFT JOIN ${threadsTable} t ON t.id = tg.thread_id` : '';
1667
- const cwdCol = threadsTable ? 't.cwd' : 'NULL';
1668
- const titleCol = threadsTable ? 't.title' : 'NULL';
1669
- const rows = runSqliteJsonOptional(dbPath, `${attachPrefix}
1670
- SELECT
1671
- 'codex' AS provider,
1672
- tg.thread_id,
1673
- tg.goal_id,
1674
- tg.objective,
1675
- tg.status,
1676
- tg.token_budget,
1677
- tg.tokens_used,
1678
- tg.time_used_seconds,
1679
- tg.created_at_ms,
1680
- tg.updated_at_ms,
1681
- ${cwdCol} AS thread_cwd,
1682
- ${titleCol} AS thread_title
1683
- FROM thread_goals tg
1684
- ${join}
1685
- WHERE ${clauses.join(' OR ')}
1686
- ORDER BY tg.updated_at_ms DESC
1687
- LIMIT 25
1688
- `);
1689
-
1690
- return rows.map(row => ({
1691
- ...row,
1692
- provider: 'codex',
1693
- }));
1694
- }
1695
-
1696
- function buildCareerXpSessionCapsule(args = []) {
1697
- const workspace = careerXpRoot(path.resolve(readInlineFirstFlag(args, '--workspace', defaultXpWorkspace())));
1698
- const sinceInput = readInlineFirstFlag(args, '--since', 'today');
1699
- const untilInput = readInlineFirstFlag(args, '--until', null);
1700
- const since = parseSessionBoundary(sinceInput, startOfToday());
1701
- const until = parseSessionBoundary(untilInput, new Date());
1702
- const sinceMs = since.getTime();
1703
- const untilMs = until.getTime();
1704
- if (sinceMs > untilMs) {
1705
- throw new Error('--since must be before --until');
1706
- }
1707
-
1708
- const writeEnabled = !hasFlagOrValue(args, '--no-write') && !hasFlagOrValue(args, '--dry-run');
1709
- const projectionState = collectLocalXpProjectionState(['--workspace', workspace], { write: writeEnabled });
1710
- const projection = projectionState.projection;
1711
- const receiptsPath = path.join(workspace, CAREER_XP_RECEIPTS_FILE);
1712
- const taskProjectionPath = path.join(workspace, TASK_PROJECTION_FILE);
1713
- const sessionDate = localDateKey(since) || since.toISOString().slice(0, 10);
1714
- const sessionPath = path.join(workspace, CAREER_XP_SESSIONS_DIR, `${sessionDate}.json`);
1715
- const warnings = [];
1716
- if (projection.integrity?.cursor?.skipped_partial_tail) {
1717
- warnings.push('task_episodes_partial_tail_waiting');
1718
- }
1719
- const taskProjection = readTaskProjectionState(workspace);
1720
- if (taskProjection.warning) warnings.push(taskProjection.warning);
1721
- const tasks = taskProjection.tasks;
1722
- const touchedTasks = tasks
1723
- .filter(task => taskTouchedInWindow(task, sinceMs, untilMs))
1724
- .sort((a, b) => (latestTaskMs(b) || 0) - (latestTaskMs(a) || 0));
1725
- const reviewTasks = touchedTasks.filter((task) => {
1726
- const status = String(task.status || '').toLowerCase();
1727
- return status === 'review' || taskApprovalStatus(task) === 'pending';
1728
- });
1729
- const doneTasks = touchedTasks.filter(task => String(task.status || '').toLowerCase() === 'done');
1730
- const receipts = projection.integrity_status === 'verified' ? projectionState.receipts : [];
1731
- const acceptedReceipts = receipts
1732
- .filter(receipt => receipt?.outcome === 'accepted' && asNumber(receipt.xp) > 0 && inWindow(receipt.accepted_at, sinceMs, untilMs))
1733
- .sort((a, b) => (timestampMs(b.accepted_at) || 0) - (timestampMs(a.accepted_at) || 0));
1734
- const acceptedTasks = acceptedReceipts.map((receipt) => {
1735
- const task = touchedTasks.find(item => receiptMatchesTask(receipt, item));
1736
- return compactReceipt(receipt, task);
1737
- });
1738
- const windowXp = acceptedReceipts.reduce((sum, receipt) => sum + asNumber(receipt.xp), 0);
1739
- const afterTotalXp = asNumber(projection.total_xp);
1740
- const missionFlag = readInlineFirstFlag(args, '--mission', null);
1741
- const episodeGoals = readJsonl(path.join(workspace, TASK_EPISODES_FILE), { allowPartialTail: true })
1742
- .filter(episode => inWindow(episode?.created_at, sinceMs, untilMs))
1743
- .map(episode => episode.goal);
1744
- const codexGoals = readCodexGoalsForSession(args, workspace, sinceMs, untilMs);
1745
- const goals = uniqueGoals([
1746
- ...codexGoals,
1747
- ...acceptedReceipts.map(receipt => receipt.goal),
1748
- ...episodeGoals,
1749
- ]);
1750
- const latestAccepted = acceptedReceipts[0] || null;
1751
-
1752
- const capsule = {
1753
- schema: 'atris.career_xp_session_capsule.v1',
1754
- generated_at: new Date().toISOString(),
1755
- workspace_root: workspace,
1756
- window: {
1757
- label: String(sinceInput || 'today'),
1758
- since: since.toISOString(),
1759
- until: until.toISOString(),
1760
- since_ms: sinceMs,
1761
- until_ms: untilMs,
1762
- },
1763
- mission: {
1764
- label: missionFlag || goals[0]?.label || touchedTasks.find(task => task.tag === 'career-xp')?.title || 'Capture proof-backed work',
1765
- source: missionFlag ? 'flag' : goals[0] ? 'goal' : 'tasks',
1766
- },
1767
- xp: {
1768
- before_agent_xp: Math.max(0, afterTotalXp - windowXp),
1769
- after_agent_xp: afterTotalXp,
1770
- delta_agent_xp: windowXp,
1771
- today_agent_xp: asNumber(projection.today_xp),
1772
- before_total_xp: Math.max(0, afterTotalXp - windowXp),
1773
- after_total_xp: afterTotalXp,
1774
- delta_xp: windowXp,
1775
- today_xp: asNumber(projection.today_xp),
1776
- level: asNumber(projection.level, 1),
1777
- next_level_progress: projection.next_level_progress || null,
1778
- integrity_status: projection.integrity_status || 'unknown',
1779
- },
1780
- tasks: {
1781
- touched_count: touchedTasks.length,
1782
- review_count: reviewTasks.length,
1783
- accepted_count: acceptedTasks.length,
1784
- done_count: doneTasks.length,
1785
- touched: touchedTasks.map(compactTask),
1786
- review: reviewTasks.map(compactTask),
1787
- accepted: acceptedTasks,
1788
- },
1789
- goals: {
1790
- count: goals.length,
1791
- touched: goals,
1792
- },
1793
- proof: {
1794
- receipts_count: acceptedReceipts.length,
1795
- latest_accepted: latestAccepted ? compactReceipt(
1796
- latestAccepted,
1797
- touchedTasks.find(task => receiptMatchesTask(latestAccepted, task)),
1798
- { fullProof: true }
1799
- ) : null,
1800
- accepted: acceptedTasks,
1801
- },
1802
- next_quest: buildNextQuest(reviewTasks, touchedTasks, acceptedReceipts),
1803
- files: {
1804
- projection_path: path.join(workspace, CAREER_XP_PROJECTION_FILE),
1805
- receipts_path: receiptsPath,
1806
- task_projection_path: fs.existsSync(taskProjectionPath) ? taskProjectionPath : null,
1807
- session_path: writeEnabled ? sessionPath : null,
1808
- },
1809
- written: writeEnabled,
1810
- };
1811
-
1812
- if (warnings.length) {
1813
- capsule.warnings = warnings;
1814
- }
1815
-
1816
- if (writeEnabled) {
1817
- writeJson(sessionPath, capsule);
1818
- }
1819
-
1820
- return capsule;
1821
- }
1822
-
1823
41
  function renderContributionGraph(graph) {
1824
42
  if (!graph || !Array.isArray(graph.days)) return;
1825
43
  console.log('');
@@ -1837,315 +55,6 @@ function renderLocalActivity(activity) {
1837
55
  );
1838
56
  }
1839
57
 
1840
- function earnedAgentXp(value) {
1841
- return Math.max(0, asNumber(value));
1842
- }
1843
-
1844
- function currentFormScore(value) {
1845
- return Math.max(0, Math.min(99, asNumber(value)));
1846
- }
1847
-
1848
- function agentXpLevelProgress(totalXp) {
1849
- const levelXp = Math.max(0, asNumber(totalXp));
1850
- const level = levelFromXp(levelXp);
1851
- const currentLevelFloor = (level - 1) * LEVEL_XP;
1852
- const nextLevelXp = level * LEVEL_XP;
1853
- const xpIntoLevel = levelXp - currentLevelFloor;
1854
- return {
1855
- schema: 'atris.agentxp_level_progress.v1',
1856
- level,
1857
- stage: `Level ${level}`,
1858
- level_xp: levelXp,
1859
- current_level_floor: currentLevelFloor,
1860
- next_level_xp: nextLevelXp,
1861
- xp_into_level: xpIntoLevel,
1862
- xp_to_next: Math.max(0, nextLevelXp - levelXp),
1863
- progress_pct: Math.max(0, Math.min(99, Math.round((xpIntoLevel / LEVEL_XP) * 100))),
1864
- uncapped: true,
1865
- };
1866
- }
1867
-
1868
- function verifiedProjection(projection) {
1869
- return projection?.integrity_status === 'verified'
1870
- && projection?.integrity?.status === 'verified';
1871
- }
1872
-
1873
- function projectionWorkspaceSummaries(projection) {
1874
- if (Array.isArray(projection?.workspaces)) {
1875
- return projection.workspaces.map((workspace) => {
1876
- const workspaceRoot = workspace.workspace_root || '';
1877
- return {
1878
- name: workspace.name || workspaceName(workspaceRoot || 'workspace'),
1879
- workspace_root_hash: workspaceRoot ? sha256(path.resolve(workspaceRoot)) : null,
1880
- included: Boolean(workspace.included),
1881
- agent_xp: asNumber(workspace.total_xp),
1882
- receipts_count: asNumber(workspace.receipts_count),
1883
- integrity_status: workspace.integrity_status || 'unknown',
1884
- ...(workspaceRoot ? publicWorkspaceBinding(workspaceRoot) : null),
1885
- };
1886
- });
1887
- }
1888
-
1889
- const workspaceRoot = projection?.workspace_root || path.resolve(process.cwd());
1890
- return [{
1891
- name: workspaceName(workspaceRoot),
1892
- workspace_root_hash: sha256(path.resolve(workspaceRoot)),
1893
- included: verifiedProjection(projection),
1894
- agent_xp: asNumber(projection?.total_agent_xp ?? projection?.total_xp),
1895
- receipts_count: asNumber(projection?.receipts_count),
1896
- integrity_status: projection?.integrity_status || projection?.integrity?.status || 'unknown',
1897
- ...publicWorkspaceBinding(workspaceRoot),
1898
- }];
1899
- }
1900
-
1901
- function uniqueTruthy(values) {
1902
- return Array.from(new Set(values.map(value => String(value || '').trim()).filter(Boolean))).sort();
1903
- }
1904
-
1905
- function syncAttribution(workspaces, options = {}) {
1906
- const included = workspaces.filter(workspace => workspace.included !== false);
1907
- const scoped = included.length ? included : workspaces;
1908
- const businessIds = uniqueTruthy(scoped.map(workspace => workspace.business_id));
1909
- const workspaceIds = uniqueTruthy(scoped.map(workspace => workspace.workspace_id));
1910
- const businessSlugs = uniqueTruthy(scoped.map(workspace => workspace.business_slug || workspace.computer_slug));
1911
- const templates = uniqueTruthy(scoped.map(workspace => workspace.workspace_template));
1912
- const explicitPublic = typeof options.publicAgentXp === 'boolean' ? options.publicAgentXp : null;
1913
- const workspacePublic = scoped.length === 1 && scoped[0]?.agentxp_public === true;
1914
- const agentxpPublic = explicitPublic === null ? (workspacePublic ? true : null) : explicitPublic;
1915
-
1916
- if (businessIds.length > 1) {
1917
- return { attribution_scope: 'multi_business', computer: 'multiple-workspaces', agentxp_public: explicitPublic === true };
1918
- }
1919
- if (businessIds.length === 0 && scoped.length > 1 && businessSlugs.length > 1) {
1920
- return { attribution_scope: 'multi_workspace', computer: 'multiple-workspaces', agentxp_public: explicitPublic === true };
1921
- }
1922
-
1923
- const businessId = businessIds[0] || null;
1924
- const workspaceId = workspaceIds.length === 1 ? workspaceIds[0] : null;
1925
- const businessSlug = businessSlugs.length === 1 ? businessSlugs[0] : null;
1926
- const workspaceTemplate = templates.length === 1 ? templates[0] : null;
1927
- return {
1928
- attribution_scope: businessId || businessSlug ? 'business_bound' : 'workspace_only',
1929
- business_id: businessId,
1930
- workspace_id: workspaceId,
1931
- business_slug: businessSlug,
1932
- workspace_template: workspaceTemplate,
1933
- computer: businessSlug || scoped[0]?.computer || scoped[0]?.name || 'local',
1934
- agentxp_public: agentxpPublic,
1935
- };
1936
- }
1937
-
1938
- function syncScopeFields(attribution = {}) {
1939
- const attributionScope = slugify(attribution.attribution_scope);
1940
- const orgId = String(attribution.business_id || attribution.business_slug || '').trim() || null;
1941
- const businessBound = Boolean(orgId) || attributionScope === 'business-bound' || attributionScope === 'multi-business';
1942
- const publicAgentXp = typeof attribution.agentxp_public === 'boolean'
1943
- ? attribution.agentxp_public
1944
- : !businessBound;
1945
- return {
1946
- scope: publicAgentXp ? 'global' : (businessBound ? 'org' : 'global'),
1947
- org_id: orgId,
1948
- computer_id: String(attribution.workspace_id || attribution.computer || 'local').trim() || 'local',
1949
- visibility: publicAgentXp ? 'public' : 'internal',
1950
- public_agentxp: publicAgentXp,
1951
- };
1952
- }
1953
-
1954
- function publicAgentXpOverride(args = []) {
1955
- if (hasFlagOrValue(args, '--public')) return true;
1956
- if (hasFlagOrValue(args, '--private') || hasFlagOrValue(args, '--internal')) return false;
1957
- return null;
1958
- }
1959
-
1960
- function credentialHandle(credentials) {
1961
- return slugify(
1962
- credentials?.username
1963
- || credentials?.handle
1964
- || credentials?.display_name
1965
- || credentials?.name
1966
- || profileNameFromEmail(credentials?.email)
1967
- || credentials?.user_id
1968
- );
1969
- }
1970
-
1971
- function activeAccountHandle() {
1972
- const envPlayer = slugify(process.env.ATRIS_PLAYER || process.env.ATRIS_USERNAME);
1973
- if (envPlayer) return envPlayer;
1974
-
1975
- const envProfile = slugify(process.env.ATRIS_PROFILE);
1976
- if (envProfile) return envProfile;
1977
-
1978
- try {
1979
- const sessionProfile = slugify(getSessionProfile());
1980
- if (sessionProfile) return sessionProfile;
1981
- } catch {}
1982
-
1983
- try {
1984
- const credentials = loadCredentials();
1985
- const handle = credentialHandle(credentials);
1986
- if (handle) return handle;
1987
- } catch {}
1988
-
1989
- return null;
1990
- }
1991
-
1992
- function projectionHandle(projection) {
1993
- return slugify(
1994
- projection?.operator
1995
- || projection?.latest_accepted_proof?.actor
1996
- || projection?.latest_accepted_proof?.assignee
1997
- || projection?.latest_accepted_proof?.assigned_to
1998
- );
1999
- }
2000
-
2001
- function syncPlayer(args, projection) {
2002
- const explicit = readFirstFlag(args, ['--as', '--player', '--user', '--operator'], null);
2003
- return slugify(explicit)
2004
- || activeAccountHandle()
2005
- || projectionHandle(projection)
2006
- || slugify(process.env.USER || os.userInfo().username)
2007
- || 'player';
2008
- }
2009
-
2010
- function buildAgentXpSyncPacket(args = []) {
2011
- const localMode = hasFlagOrValue(args, '--local') || hasFlagOrValue(args, '--workspace') || hasFlagOrValue(args, '--operator');
2012
- const projectionArgs = args.filter(arg => !['--dry-run', '--no-post', '--packet', '--public', '--private', '--internal'].includes(arg));
2013
- const projection = hasFlagOrValue(args, '--all') || !localMode
2014
- ? collectAllLocalXpProjection(projectionArgs)
2015
- : collectLocalXpProjection(projectionArgs);
2016
- const player = syncPlayer(args, projection);
2017
- const workspaces = projectionWorkspaceSummaries(projection);
2018
- const attribution = syncAttribution(workspaces, { publicAgentXp: publicAgentXpOverride(args) });
2019
- const scopeFields = syncScopeFields(attribution);
2020
- const totalXp = asNumber(projection.total_agent_xp ?? projection.agent_xp ?? projection.total_xp ?? projection.career_xp);
2021
- const receiptsCount = asNumber(projection.receipts_count);
2022
- const eligible = verifiedProjection(projection) && receiptsCount > 0 && totalXp > 0;
2023
- const publicXp = earnedAgentXp(totalXp);
2024
- const currentForm = currentFormScore(totalXp);
2025
- const levelProgress = agentXpLevelProgress(publicXp);
2026
- const contributionGraph = sanitizeContributionGraphForSync(projection.contribution_graph);
2027
- const workspaceRootHash = sha256(workspaces.map(item => item.workspace_root_hash || item.name).sort().join(':'));
2028
- const entry = {
2029
- user_id: player,
2030
- username: player,
2031
- agent_xp: publicXp,
2032
- career_xp: publicXp,
2033
- current_form: currentForm,
2034
- ovr: currentForm,
2035
- level: levelProgress.level,
2036
- level_progress: levelProgress,
2037
- verified_receipts: receiptsCount,
2038
- reviewed_tasks: receiptsCount,
2039
- recent_verified_receipts: receiptsCount,
2040
- recent_reviewed_tasks: receiptsCount,
2041
- leaderboard_eligible: eligible,
2042
- integrity_status: eligible ? 'trusted' : (projection.integrity_status || projection.integrity?.status || 'unknown'),
2043
- lock_reason: eligible ? null : 'not_enough_trusted_proof',
2044
- public_adjustment: null,
2045
- next_move: eligible ? 'Play the next proof-backed AgentXP mission.' : 'Complete one proof-backed AgentXP rep.',
2046
- contribution_graph: contributionGraph,
2047
- };
2048
- const packet = {
2049
- schema: 'atris.agentxp_sync_packet.v1',
2050
- generated_at: new Date().toISOString(),
2051
- workspace_root_hash: workspaceRootHash,
2052
- scope: scopeFields.scope,
2053
- org_id: scopeFields.org_id,
2054
- attribution_scope: attribution.attribution_scope,
2055
- business_id: attribution.business_id || null,
2056
- workspace_id: attribution.workspace_id || null,
2057
- business_slug: attribution.business_slug || null,
2058
- workspace_template: attribution.workspace_template || null,
2059
- computer_id: scopeFields.computer_id,
2060
- computer: attribution.computer || projection.workspace_name || workspaces[0]?.name || 'local',
2061
- visibility: scopeFields.visibility,
2062
- public_agentxp: scopeFields.public_agentxp,
2063
- operator: player,
2064
- privacy: {
2065
- raw_proofs_included: false,
2066
- raw_receipts_included: false,
2067
- contains_absolute_workspace_root: false,
2068
- public_user_board_omits_lifetime_xp: true,
2069
- },
2070
- sync_contract: {
2071
- anti_bs_rule: 'No accepted proof-backed task episodes means no AgentXP leaderboard movement.',
2072
- trust_rule: 'Only verified local ledgers are uploaded; raw proof and paths stay local.',
2073
- },
2074
- local_evidence: {
2075
- schema: 'atris.agentxp_local_evidence.v1',
2076
- workspace_root_hash: workspaceRootHash,
2077
- scope: scopeFields.scope,
2078
- org_id: scopeFields.org_id,
2079
- attribution_scope: attribution.attribution_scope,
2080
- business_id: attribution.business_id || null,
2081
- workspace_id: attribution.workspace_id || null,
2082
- business_slug: attribution.business_slug || null,
2083
- workspace_template: attribution.workspace_template || null,
2084
- computer_id: scopeFields.computer_id,
2085
- computer: attribution.computer || null,
2086
- visibility: scopeFields.visibility,
2087
- public_agentxp: scopeFields.public_agentxp,
2088
- workspaces,
2089
- verified_workspace_count: asNumber(projection.verified_workspace_count, verifiedProjection(projection) ? 1 : 0),
2090
- receipts_count: receiptsCount,
2091
- integrity_status: projection.integrity?.status || projection.integrity_status || 'unknown',
2092
- ledger_head_hash: projection.integrity?.head_hash || null,
2093
- local_activity: {
2094
- schema: projection.local_activity?.schema || 'atris.agentxp_local_assistant_activity.v1',
2095
- detected_providers: projection.local_activity?.detected_providers || [],
2096
- provider_count: asNumber(projection.local_activity?.provider_count, (projection.local_activity?.detected_providers || []).length),
2097
- raw_signal_count: asNumber(projection.local_activity?.raw_signal_count),
2098
- context_agent_xp: asNumber(projection.local_activity?.context_agent_xp),
2099
- context_weight: asNumber(projection.local_activity?.context_weight, 0.05),
2100
- cap_ratio_to_accepted_task_xp: asNumber(projection.local_activity?.cap_ratio_to_accepted_task_xp, 0.1),
2101
- cap_agent_xp: asNumber(projection.local_activity?.cap_agent_xp),
2102
- accepted_task_agent_xp: asNumber(projection.local_activity?.accepted_task_agent_xp, totalXp),
2103
- included_in_total_agent_xp: false,
2104
- public_leaderboard: false,
2105
- role: 'context_only',
2106
- },
2107
- atris_actions: buildAtrisActionSignal(projection),
2108
- },
2109
- gm_projection: {
2110
- schema: 'atris.gm_xp_projection.v1',
2111
- workspace_root_hash: workspaceRootHash,
2112
- scope: scopeFields.scope,
2113
- org_id: scopeFields.org_id,
2114
- attribution_scope: attribution.attribution_scope,
2115
- business_id: attribution.business_id || null,
2116
- workspace_id: attribution.workspace_id || null,
2117
- business_slug: attribution.business_slug || null,
2118
- workspace_template: attribution.workspace_template || null,
2119
- computer_id: scopeFields.computer_id,
2120
- computer: attribution.computer || null,
2121
- visibility: scopeFields.visibility,
2122
- public_agentxp: scopeFields.public_agentxp,
2123
- operator: player,
2124
- player_score: {
2125
- agent_xp: totalXp,
2126
- career_xp: totalXp,
2127
- leaderboard_eligible: eligible,
2128
- verified_receipts: receiptsCount,
2129
- },
2130
- },
2131
- user_leaderboard: {
2132
- schema: 'atris.agentxp_user_leaderboard.v1',
2133
- workspace_root_hash: workspaceRootHash,
2134
- score_name: AGENT_XP_LABEL,
2135
- entries: [entry],
2136
- },
2137
- };
2138
- packet.packet_hash = hashPayload(packet);
2139
- return {
2140
- schema: 'atris.agentxp_sync_preview.v1',
2141
- generated_at: new Date().toISOString(),
2142
- dry_run: true,
2143
- player,
2144
- entry,
2145
- packet,
2146
- };
2147
- }
2148
-
2149
58
  async function syncAgentXp(args = []) {
2150
59
  const preview = buildAgentXpSyncPacket(args);
2151
60
  const dryRun = hasFlagOrValue(args, '--dry-run') || hasFlagOrValue(args, '--no-post') || hasFlagOrValue(args, '--packet');
@@ -2431,6 +340,7 @@ async function xpCommand(...args) {
2431
340
  }
2432
341
 
2433
342
  module.exports = {
343
+ ...xpCore,
2434
344
  xpCommand,
2435
345
  buildCareerXpProjection,
2436
346
  collectLocalXpProjection,