atris 3.74.24 → 3.74.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -459,6 +459,22 @@ atris scorecard run --models haiku,opus # compare models; past Haiku needs AT
459
459
  - The scorecard shows how many changes each model did, how long it took, what the same runs would cost on pay-per-use, and which changes no model could do from what was written.
460
460
  - Tests that need their own command or settings go in `.atris/scorecard.json`.
461
461
 
462
+ ## The Atris Rating
463
+
464
+ `atris rating` gives every AI teammate one number, 25 to 99, for how much real work it finishes.
465
+
466
+ ```bash
467
+ atris rating # every engine the combine rated, and your members here
468
+ atris rating opus # one engine's card
469
+ atris rating <member> # one member's rating here, next to its engine's
470
+ ```
471
+
472
+ - 99 means 99 of every 100 finished. Each halving of misses is worth about 6 points, so the top is the hardest climb: 3 in a row rates 76, and it takes about 60 in a row to rate 99.
473
+ - Engines are rated in the combine: Loop Bench tasks from the atris repos, each graded by the tests that shipped with it. No engine is a 99 yet.
474
+ - Each engine's card also rates its Python tasks, its JavaScript tasks, the tasks the Atris prompt was never tuned on, and how rarely a try broke another test, with its median time and pay-per-use price per finished task.
475
+ - Members are rated on the same scale from the jobs they landed in your workspace over the last 30 days. Landed means accepted or merged; drops and abandons count against them. `atris gm` shows the same numbers.
476
+ - The session boot shows the build engine's rating, for example `build opus 5.5 (86 ovr)`. The combine grades building only, so review and search engines show none.
477
+
462
478
  ## Benchmark Harness
463
479
 
464
480
  Atris ships one public head-to-head benchmark harness for comparing a pinned
package/bin/atris.js CHANGED
@@ -652,6 +652,7 @@ function showHelpAll() {
652
652
  console.log(' avail - Booking availability (/book/{username} weekly windows)');
653
653
  console.log(' scoreboard - Today\'s github contributions vs your best day, streak, repo commits (--watch, --json)');
654
654
  console.log(' scorecard - Which AI can do your team\'s work: past changes graded by their own tests, run on your Claude plan (find, run, show)');
655
+ console.log(' rating - The Atris Rating, 25 to 99: how much real work each engine and member finishes (atris rating opus)');
655
656
  console.log(' brain - Compile MAP/TODO/wiki/state into a loadable agent brain');
656
657
  console.log(' lesson - atris lesson add <slug> <pass|fail> "<text>" (mine/sweep/resolve/revert/ledger; --detector validates the falsifier at write time)');
657
658
  console.log(' taste - Record the operator\'s keep, kill, and more creative verdicts');
@@ -2239,6 +2240,10 @@ if (command === 'guide') {
2239
2240
  Promise.resolve().then(() => require('../commands/scorecard').scorecardCommand(process.argv.slice(3)))
2240
2241
  .then((code) => exitAfterStdoutDrain(typeof code === 'number' ? code : 0))
2241
2242
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
2243
+ } else if (command === 'rating') {
2244
+ Promise.resolve().then(() => require('../commands/rating').ratingCommand(process.argv.slice(3)))
2245
+ .then((code) => exitAfterStdoutDrain(typeof code === 'number' ? code : 0))
2246
+ .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
2242
2247
  } else if (command === 'meet') {
2243
2248
  Promise.resolve().then(() => require('../commands/meet').meetCommand(process.argv.slice(3)))
2244
2249
  .then((code) => exitAfterStdoutDrain(typeof code === 'number' ? code : 0))
package/commands/gm.js CHANGED
@@ -41,7 +41,9 @@ function showHelp() {
41
41
  console.log(' what is up next. Every job a member takes in the last 30 days ends');
42
42
  console.log(' landed (accepted or merged), dropped (claim let go), sent back, or');
43
43
  console.log(' abandoned (archived without landing). Jobs in review are pending.');
44
- console.log(' ovr = round(60 + 39 * landed / finished), rookie under 3 finished.');
44
+ console.log(' ovr = the Atris Rating of landed over finished, 25 to 99: 99 means 99');
45
+ console.log(' of every 100 and each halving of misses is worth about 6 (atris rating).');
46
+ console.log(' Rookie under 3 finished.');
45
47
  console.log(' Drops and abandons count, so this is stricter than the website rating.');
46
48
  console.log(' "on a job" means the claimed task moved in the last 6 hours; a claim');
47
49
  console.log(' that sat longer shows as stuck, with its age and task id.');
@@ -936,8 +938,8 @@ function renderBoardLines(board, { interactive = false, width = termWidth(), bor
936
938
  }
937
939
  lines.push('');
938
940
 
939
- lines.push(`key: ovr 60 to 99 = share of finished jobs landed, ${boxed ? 'new' : 'rookie'} under 3 finished,`);
940
- lines.push(' stricter than the website rating on purpose (drops and abandons count)');
941
+ lines.push(`key: ovr = the Atris Rating of finished jobs landed, 25 to 99, ${boxed ? 'new' : 'rookie'} under 3 finished.`);
942
+ lines.push(' 99 means 99 of every 100. drops and abandons count (atris rating)');
941
943
  if (boxed) lines.push(' ·· = nothing to show yet: no finished job, no role, or no pinned model');
942
944
  else lines.push(' form: last 5, newest right: k landed, d dropped, x sent back, a abandoned');
943
945
  lines.push(' hours: claim to result. on a job: moved in the last 6h. review: pending');
@@ -0,0 +1,152 @@
1
+ 'use strict';
2
+
3
+ // atris rating: the Atris Rating, one number from 25 to 99 for how much real
4
+ // work a teammate finishes. Engines are rated in the combine (Loop Bench);
5
+ // members are rated on the jobs they land here, the same numbers atris gm
6
+ // shows. Read only: nothing runs and nothing is written.
7
+
8
+ const { hasFlag } = require('../lib/arg-parser');
9
+ const { modelLabel } = require('../lib/roster-models');
10
+ const rating = require('../lib/atris-rating');
11
+
12
+ const USAGE = [
13
+ 'usage: atris rating [engine | member] [--json]',
14
+ ' how much real work each AI teammate finishes, one number from 25 to 99.',
15
+ '',
16
+ ' atris rating every engine the combine rated, and your members here',
17
+ ' atris rating opus one engine\'s card (opus, sonnet, haiku, or a model id)',
18
+ ' atris rating <member> one member\'s rating here, next to its engine\'s',
19
+ ' --json print it as json',
20
+ ].join('\n');
21
+
22
+ // "an 86", "a 76": eighty-something, eleven, and eighteen start with a vowel.
23
+ function rated(n) {
24
+ return `${/^(8|11$|18$)/.test(String(n)) ? 'an' : 'a'} ${n}`;
25
+ }
26
+
27
+ // Members rated on the last 30 days of jobs here, best first, each with the
28
+ // job and model the roster gives it, plus the build line: what the build
29
+ // engine rates in the combine and what members doing build landed here.
30
+ // null when there is no task list or no finished job to rate.
31
+ function readHere(deps = {}) {
32
+ let root = deps.root;
33
+ let played = deps.season;
34
+ try {
35
+ if (!root) root = require('../lib/task-db').workspaceRoot(process.cwd());
36
+ if (!played) played = require('../lib/gm-season').loadSeason(root, { lastSeen: null });
37
+ } catch {
38
+ return null;
39
+ }
40
+ if (!played || played.error || played.no_history) return null;
41
+ const { rosterRows } = require('../lib/gm-season');
42
+ const rows = rosterRows({ outcomes: played.outcomes || [], limit: Number.MAX_SAFE_INTEGER }).filter((row) => row.finished > 0);
43
+ if (!rows.length) return null;
44
+ let lineup = deps.lineup;
45
+ if (lineup === undefined) lineup = require('../lib/team-lineup').readLineupSafe(root);
46
+ const team = new Map(((lineup && lineup.ok && lineup.team) || []).map((row) => [String(row.member || '').toLowerCase(), row]));
47
+ const members = rows.map((row) => {
48
+ const seat = team.get(row.name.toLowerCase()) || {};
49
+ return { member: row.name, ovr: row.ovr, landed: row.landed, finished: row.finished, job: seat.job || null, model: seat.model || null };
50
+ });
51
+ const buildJob = ((lineup && lineup.ok && lineup.jobs) || []).find((row) => row.job === 'build' && !row.key);
52
+ const buildModel = buildJob && buildJob.now_runs ? buildJob.now_runs.model : null;
53
+ const card = buildModel ? rating.combineCards().find((c) => c.model === buildModel) : null;
54
+ const builders = members.filter((m) => m.job === 'build');
55
+ const landed = builders.reduce((sum, m) => sum + m.landed, 0);
56
+ const finished = builders.reduce((sum, m) => sum + m.finished, 0);
57
+ return {
58
+ workspace: played.workspace_name || null,
59
+ members,
60
+ build: card && finished ? { model: buildModel, combine_ovr: card.ovr, landed, finished, ovr: rating.atrisRating(landed, finished) } : null,
61
+ };
62
+ }
63
+
64
+ function renderHere(here) {
65
+ const ranked = here.members.filter((m) => m.ovr != null);
66
+ const fresh = here.members.filter((m) => m.ovr == null);
67
+ const nameW = Math.min(24, Math.max(6, ...ranked.map((m) => m.member.length))) + 2;
68
+ const lines = [`here${here.workspace ? `: ${here.workspace}` : ''}, last 30 days, the same ratings as atris gm`, ''];
69
+ if (ranked.length) {
70
+ lines.push(` ${'ovr'.padEnd(5)}${'member'.padEnd(nameW)}${'landed'.padEnd(11)}${'job'.padEnd(8)}model`);
71
+ for (const m of ranked) {
72
+ const name = m.member.length > nameW - 2 ? `${m.member.slice(0, nameW - 5)}...` : m.member;
73
+ lines.push(` ${String(m.ovr).padEnd(5)}${name.padEnd(nameW)}${`${m.landed} of ${m.finished}`.padEnd(11)}${(m.job || '').padEnd(8)}${m.model ? modelLabel(m.model) : ''}`.trimEnd());
74
+ }
75
+ } else {
76
+ lines.push(' no member has 3 finished jobs here yet');
77
+ }
78
+ if (fresh.length) {
79
+ const names = fresh.map((m) => m.member);
80
+ const head = `new, under 3 finished jobs (${names.length}): `;
81
+ const shown = [];
82
+ for (const name of names) {
83
+ if (head.length + [...shown, name].join(', ').length + ', and 99 more'.length > 80) break;
84
+ shown.push(name);
85
+ }
86
+ const rest = names.length - shown.length;
87
+ lines.push('', `${head}${shown.join(', ')}${rest ? `, and ${rest} more` : ''}`);
88
+ }
89
+ lines.push('landed: accepted or merged. drops and abandons count against a member.');
90
+ if (here.build) {
91
+ const b = here.build;
92
+ lines.push('', `build runs ${modelLabel(b.model)}, ${rated(b.combine_ovr)} in the combine.`,
93
+ `members doing build here landed ${b.landed} of ${b.finished} jobs, ${rated(b.ovr)}.`);
94
+ }
95
+ return lines.join('\n');
96
+ }
97
+
98
+ function renderMember(m, card) {
99
+ const job = m.job ? `, ${m.job}${m.model ? ` on ${modelLabel(m.model)}` : ''}` : '';
100
+ const head = `${m.member}${job}`;
101
+ const lines = [
102
+ `${head}${' '.repeat(Math.max(2, 44 - head.length))}${m.ovr == null ? 'new' : `${m.ovr} ovr`} here`,
103
+ '',
104
+ ` landed ${m.landed} of ${m.finished} finished jobs in the last 30 days`,
105
+ ];
106
+ if (m.ovr == null) lines.push(' rated once it has 3 finished jobs');
107
+ if (card && m.job === 'build') lines.push(` its engine, ${card.label}, is ${rated(card.ovr)} in the combine: atris rating ${card.label.split(' ')[0]}`);
108
+ lines.push(` every play: atris gm card ${m.member}`);
109
+ return lines.join('\n');
110
+ }
111
+
112
+ function ratingCommand(args = [], deps = {}) {
113
+ const write = deps.write || ((text) => process.stdout.write(text));
114
+ if (hasFlag(args, '--help') || hasFlag(args, '-h') || args[0] === 'help') {
115
+ write(`${USAGE}\n`);
116
+ return 0;
117
+ }
118
+ const json = hasFlag(args, '--json');
119
+ const name = args.filter((arg) => !arg.startsWith('-')).join(' ').trim();
120
+ const cards = rating.combineCards();
121
+ if (name) {
122
+ const card = rating.combineCardFor(name);
123
+ if (card) {
124
+ write(json ? `${JSON.stringify({ schema: 'atris.rating.v1', engine: card }, null, 2)}\n` : `${rating.renderCard(card)}\n`);
125
+ return 0;
126
+ }
127
+ const here = readHere(deps);
128
+ const member = here && here.members.find((m) => m.member.toLowerCase() === name.toLowerCase());
129
+ if (member) {
130
+ const own = member.model ? cards.find((c) => c.model === member.model) || null : null;
131
+ write(json ? `${JSON.stringify({ schema: 'atris.rating.v1', member, engine: member.job === 'build' ? own : null }, null, 2)}\n` : `${renderMember(member, own)}\n`);
132
+ return 0;
133
+ }
134
+ const err = deps.error || ((text) => process.stderr.write(text));
135
+ err(`no engine or member called "${name}" is rated. engines: ${cards.map((c) => c.label).join(', ')}. members show once they finish a job here.\n`);
136
+ return 1;
137
+ }
138
+ const here = readHere(deps);
139
+ if (json) {
140
+ write(`${JSON.stringify({
141
+ schema: 'atris.rating.v1',
142
+ scale: { floor: 25, top: 99, means: rating.SCALE_LINES.join(' ') },
143
+ combine: { bench: rating.COMBINE.bench, month: rating.COMBINE.month, tasks: rating.COMBINE.tasks, unseen: rating.COMBINE.unseen, engines: cards },
144
+ here,
145
+ }, null, 2)}\n`);
146
+ return 0;
147
+ }
148
+ write(`${rating.renderCombine(cards)}${here ? `\n\n${renderHere(here)}` : ''}\n`);
149
+ return 0;
150
+ }
151
+
152
+ module.exports = { ratingCommand, USAGE };
@@ -0,0 +1,77 @@
1
+ {
2
+ "bench": "Loop Bench",
3
+ "rated": "2026-10-29",
4
+ "tasks": 80,
5
+ "unseen": 32,
6
+ "engines": [
7
+ {
8
+ "model": "claude-opus-5-5",
9
+ "tasks": 80,
10
+ "finished": 77.0,
11
+ "python": {
12
+ "finished": 35.0,
13
+ "tasks": 36
14
+ },
15
+ "javascript": {
16
+ "finished": 42.0,
17
+ "tasks": 44
18
+ },
19
+ "unseen": {
20
+ "finished": 31.0,
21
+ "tasks": 32
22
+ },
23
+ "tries": 83,
24
+ "broke": 1,
25
+ "timed_out": 1,
26
+ "minutes_cap": 20,
27
+ "median_minutes": 2.23,
28
+ "cost_per_finish": 0.914
29
+ },
30
+ {
31
+ "model": "claude-sonnet-5-5",
32
+ "tasks": 79,
33
+ "finished": 73.0,
34
+ "python": {
35
+ "finished": 33.6,
36
+ "tasks": 35
37
+ },
38
+ "javascript": {
39
+ "finished": 39.4,
40
+ "tasks": 44
41
+ },
42
+ "unseen": {
43
+ "finished": 27.0,
44
+ "tasks": 32
45
+ },
46
+ "tries": 140,
47
+ "broke": 3,
48
+ "timed_out": 5,
49
+ "minutes_cap": 10,
50
+ "median_minutes": 1.46,
51
+ "cost_per_finish": 0.428
52
+ },
53
+ {
54
+ "model": "claude-haiku-5-5",
55
+ "tasks": 80,
56
+ "finished": 70.5,
57
+ "python": {
58
+ "finished": 30.8,
59
+ "tasks": 36
60
+ },
61
+ "javascript": {
62
+ "finished": 39.8,
63
+ "tasks": 44
64
+ },
65
+ "unseen": {
66
+ "finished": 27.2,
67
+ "tasks": 32
68
+ },
69
+ "tries": 545,
70
+ "broke": 8,
71
+ "timed_out": 87,
72
+ "minutes_cap": 10,
73
+ "median_minutes": 2.48,
74
+ "cost_per_finish": 0.082
75
+ }
76
+ ]
77
+ }
@@ -0,0 +1,179 @@
1
+ 'use strict';
2
+
3
+ // The Atris Rating: one number, 25 to 99, for how much real work a teammate
4
+ // finishes. 99 means 99 of every 100. Each time a teammate halves its misses
5
+ // it gains about 6 points, so the top is the hardest climb: going from 96 to
6
+ // 99 percent finished is worth about 12.
7
+ //
8
+ // One scale rates two things. An engine gets its number from the combine
9
+ // below: real tasks graded by the tests that shipped with them. A member
10
+ // gets its number from the jobs it lands in its own workspace (gm-season).
11
+
12
+ const { modelLabel } = require('./roster-models');
13
+
14
+ const TOP = 99;
15
+ const FLOOR = 25;
16
+ const STEP = 6;
17
+
18
+ // Every rating starts from half a finish and half a miss, so a short perfect
19
+ // run cannot reach the top: 3 of 3 rates 76, and it takes about 60 in a row
20
+ // to rate 99. null when nothing has been tried.
21
+ function atrisRating(finished, tries) {
22
+ const all = Number(tries);
23
+ const done = Number(finished);
24
+ if (!Number.isFinite(all) || all <= 0 || !Number.isFinite(done)) return null;
25
+ const share = (Math.min(Math.max(done, 0), all) + 0.5) / (all + 1);
26
+ const raw = TOP - STEP * (Math.log2(TOP) - Math.log2(share / (1 - share)));
27
+ return Math.max(FLOOR, Math.min(TOP, Math.round(raw)));
28
+ }
29
+
30
+ // The combine: Loop Bench, tasks from the atris repos (javascript from the
31
+ // CLI, python from the backend), each graded by the hidden tests that shipped
32
+ // with its fix, run in Claude Code on the builder prompt Atris kept. unseen
33
+ // tasks were held out of every prompt change, so the prompt was never tuned
34
+ // on them. finished adds each task's pass rate over its tries; time and price
35
+ // count each task once too. broke counts tries that passed their own tests
36
+ // but broke another one. The file is what evals/loopbench/combine.py in
37
+ // atrisos-backend prints, copied as is, so re-rating is one copy.
38
+ function freeze(value) {
39
+ if (value && typeof value === 'object') {
40
+ for (const inner of Object.values(value)) freeze(inner);
41
+ Object.freeze(value);
42
+ }
43
+ return value;
44
+ }
45
+
46
+ const COMBINE = freeze(require('./atris-rating-combine.json'));
47
+
48
+ const MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July',
49
+ 'August', 'September', 'October', 'November', 'December'];
50
+
51
+ // "2026-10-29" -> "October 2026"
52
+ function monthOf(date) {
53
+ const [year, month] = String(date || '').split('-').map(Number);
54
+ return year && month >= 1 && month <= 12 ? `${MONTHS[month - 1]} ${year}` : '';
55
+ }
56
+
57
+ const RATED = monthOf(COMBINE.rated);
58
+
59
+ // One engine's card: the overall and four parts, each on the same scale.
60
+ function engineCard(row) {
61
+ return {
62
+ model: row.model,
63
+ label: modelLabel(row.model),
64
+ ovr: atrisRating(row.finished, row.tasks),
65
+ finished: row.finished,
66
+ tasks: row.tasks,
67
+ python: atrisRating(row.python.finished, row.python.tasks),
68
+ javascript: atrisRating(row.javascript.finished, row.javascript.tasks),
69
+ unseen: atrisRating(row.unseen.finished, row.unseen.tasks),
70
+ clean: atrisRating(row.tries - row.broke, row.tries),
71
+ parts: { python: row.python, javascript: row.javascript, unseen: row.unseen },
72
+ tries: row.tries,
73
+ broke: row.broke,
74
+ minutes_cap: row.minutes_cap,
75
+ median_minutes: row.median_minutes,
76
+ cost_per_finish: row.cost_per_finish,
77
+ };
78
+ }
79
+
80
+ // Every rated engine, best first.
81
+ function combineCards() {
82
+ return COMBINE.engines.map(engineCard).sort((a, b) => b.ovr - a.ovr || a.label.localeCompare(b.label));
83
+ }
84
+
85
+ // The card for a model id, a label, or a family word that names one engine;
86
+ // null for a model the combine has not rated.
87
+ function combineCardFor(name) {
88
+ const text = String(name || '').trim().toLowerCase().replace(/\s+/g, ' ');
89
+ if (!text) return null;
90
+ const cards = combineCards();
91
+ const exact = cards.find((card) => card.model === text || card.label === text);
92
+ if (exact) return exact;
93
+ const family = cards.filter((card) => card.label.split(' ')[0] === text);
94
+ return family.length === 1 ? family[0] : null;
95
+ }
96
+
97
+ function finishedText(value) {
98
+ return Number.isInteger(value) ? String(value) : String(Math.round(value * 10) / 10);
99
+ }
100
+
101
+ function priceText(dollars) {
102
+ if (typeof dollars !== 'number') return '-';
103
+ return dollars < 1 ? `${Math.round(dollars * 100)}¢` : `$${dollars.toFixed(2)}`;
104
+ }
105
+
106
+ function minutesText(minutes) {
107
+ return typeof minutes === 'number' ? `${minutes.toFixed(1)} min` : '-';
108
+ }
109
+
110
+ const SCALE_LINES = Object.freeze([
111
+ 'how much real work a teammate finishes, 25 to 99. 99 means 99 of every 100.',
112
+ 'each halving of misses is worth about 6 points, so the top is the hardest climb.',
113
+ ]);
114
+
115
+ // The league table: every rated engine, best first.
116
+ function renderCombine(cards = combineCards()) {
117
+ const caps = [...new Set(cards.map((card) => card.minutes_cap))];
118
+ const capLine = caps.length > 1
119
+ ? cards.filter((card) => card.minutes_cap !== Math.min(...caps)).map((card) => `${card.label} gets ${card.minutes_cap} minutes a task`).join(', ') + `, the others ${Math.min(...caps)}.`
120
+ : `every engine gets ${caps[0]} minutes a task.`;
121
+ const lines = [
122
+ 'atris rating',
123
+ ...SCALE_LINES,
124
+ '',
125
+ `the combine: ${COMBINE.tasks} atris tasks, each graded by the tests that shipped with it`,
126
+ '',
127
+ ` ${'ovr'.padEnd(5)}${'engine'.padEnd(12)}${'finished'.padEnd(12)}${'py'.padEnd(4)}${'js'.padEnd(4)}${'unseen'.padEnd(8)}${'clean'.padEnd(7)}${'time'.padEnd(9)}price`,
128
+ ];
129
+ for (const card of cards) {
130
+ lines.push(` ${String(card.ovr).padEnd(5)}${card.label.padEnd(12)}${`${finishedText(card.finished)} of ${card.tasks}`.padEnd(12)}${String(card.python).padEnd(4)}${String(card.javascript).padEnd(4)}${String(card.unseen).padEnd(8)}${String(card.clean).padEnd(7)}${minutesText(card.median_minutes).padEnd(9)}${priceText(card.cost_per_finish)}`);
131
+ }
132
+ const top = cards[0];
133
+ lines.push(
134
+ '',
135
+ top && top.ovr >= TOP ? `${top.label} is a 99.` : 'no engine is a 99 yet.',
136
+ 'py, js: python and javascript tasks. unseen: ones the prompt was never tuned on.',
137
+ 'clean: tries that broke no other test. time: median to finish.',
138
+ 'price: per finished task, pay-per-use.',
139
+ capLine,
140
+ `${COMBINE.bench}, ${RATED}. one engine: atris rating <engine>`,
141
+ );
142
+ return lines.join('\n');
143
+ }
144
+
145
+ // One engine's card.
146
+ function renderCard(card) {
147
+ const head = `${card.label}, builder`;
148
+ const part = (name, value, text) => ` ${name.padEnd(13)}${String(value).padEnd(5)}${text}`;
149
+ return [
150
+ `${head}${' '.repeat(Math.max(2, 44 - head.length))}${card.ovr} ovr`,
151
+ '',
152
+ part('python', card.python, `${finishedText(card.parts.python.finished)} of ${card.parts.python.tasks} tasks`),
153
+ part('javascript', card.javascript, `${finishedText(card.parts.javascript.finished)} of ${card.parts.javascript.tasks} tasks`),
154
+ part('unseen', card.unseen, `${finishedText(card.parts.unseen.finished)} of ${card.parts.unseen.tasks} tasks the Atris prompt was never tuned on`),
155
+ part('clean', card.clean, `broke another test on ${card.broke} of ${card.tries} tries`),
156
+ '',
157
+ ` finished ${finishedText(card.finished)} of ${card.tasks} tasks, ${minutesText(card.median_minutes)} median, ${card.minutes_cap} minute cap`,
158
+ ` ${priceText(card.cost_per_finish)} per finished task on pay-per-use`,
159
+ ` ${COMBINE.bench}, ${RATED}, ${card.tries} tries graded by each task's shipped tests`,
160
+ ].join('\n');
161
+ }
162
+
163
+ // What the boot team line adds after a build engine, like " (86 ovr)", or ''
164
+ // for a model the combine has not rated.
165
+ function bootRatingText(model) {
166
+ const card = model ? combineCards().find((c) => c.model === String(model).trim()) : null;
167
+ return card ? ` (${card.ovr} ovr)` : '';
168
+ }
169
+
170
+ module.exports = {
171
+ COMBINE,
172
+ SCALE_LINES,
173
+ atrisRating,
174
+ bootRatingText,
175
+ combineCardFor,
176
+ combineCards,
177
+ renderCard,
178
+ renderCombine,
179
+ };
package/lib/gm-bet.js CHANGED
@@ -4573,8 +4573,8 @@ function createBetDesk(deps) {
4573
4573
 
4574
4574
  // The team page: every member on the team, one row each. role is a few
4575
4575
  // words from its MEMBER.md. ovr is the board's own rating (atris gm
4576
- // --board): 60 to 99 from the share of finished jobs that landed in the
4577
- // last 30 days, "new" with 1 or 2 finished, and two dots with none
4576
+ // --board): the Atris Rating, 25 to 99, of the finished jobs that landed
4577
+ // in the last 30 days, "new" with 1 or 2 finished, and two dots with none
4578
4578
  // finished in that window (a first claim alone is not a rating) or no
4579
4579
  // task list to read. engine is the tool and the exact model the roster
4580
4580
  // gives the member (atris engine roster), two dots when no model is
package/lib/gm-season.js CHANGED
@@ -10,6 +10,7 @@
10
10
 
11
11
  const fs = require('fs');
12
12
  const path = require('path');
13
+ const { atrisRating } = require('./atris-rating');
13
14
 
14
15
  const HOUR_MS = 60 * 60 * 1000;
15
16
  const DAY_MS = 24 * HOUR_MS;
@@ -49,17 +50,18 @@ function tally(outcomes) {
49
50
  return out;
50
51
  }
51
52
 
52
- // round(60 + 39 * landed / finished), rookie under 3 finished jobs. This is
53
+ // The Atris Rating of landed over finished (lib/atris-rating.js): 25 to 99,
54
+ // where 99 means 99 of every 100, rookie under 3 finished jobs. This is
53
55
  // stricter than the website rule (kept / graded) on purpose: drops and
54
56
  // abandoned jobs count.
55
57
  function ovr(outcomes) {
56
58
  const { landed, finished } = tally(outcomes);
57
59
  if (finished < ROOKIE_FINISHED) return null;
58
- return Math.round(60 + 39 * landed / finished);
60
+ return atrisRating(landed, finished);
59
61
  }
60
62
 
61
63
  // Each member's rating over one window of plays, the board's own numbers:
62
- // ovr is 60 to 99, or null for a rookie under 3 finished. A member with no
64
+ // ovr is 25 to 99, or null for a rookie under 3 finished. A member with no
63
65
  // plays in the window is not in the map.
64
66
  function memberRatings(outcomes = []) {
65
67
  const groups = new Map();
@@ -3,7 +3,7 @@
3
3
  const knownCommands = ['init', 'guide', 'doctor', 'doc-health', 'install', 'workspace', 'log', 'logs', 'later', 'idea', 'done', 'wish', 'ask', 'approve', 'stop', 'ready', 'check', 'drill', 'dream', 'now', '99', 'goal', 'wtf', 'founder', 'orb', 'radar', 'who', 'stream', 'ctop', 'launchpad', 'status', 'analytics', 'revisions', 'visualize', 'brain', 'brainstorm', 'autopilot', 'run', 'plan', 'do', 'review', 'release',
4
4
  'activate', '_activate', 'agent', 'team', 'chat', 'fast', 'ax', 'console', 'serve', 'login', 'logout', 'whoami', 'switch', 'use', 'accounts', '_resolve', '_profile-email', '_switch-session', 'shell-init', 'update', 'upgrade', 'version', 'help', 'next', 'atris',
5
5
  'clean', 'close', 'harvest', 'verify', 'recover', 'search', 'scout', 'skill', 'member', 'codex-goal', 'app', 'apps', 'learn', 'lesson', 'taste', 'teach', 'plugin', 'experiments', 'bench', 'router', 'tree', 'receipt', 'proof', 'openclaw', 'pull', 'push', 'watch', 'cloud', 'live', 'align', 'terminal', 'computer', 'diff', 'business', 'sync', 'youtube', 'x-search',
6
- 'ingest', 'query', 'lint', 'loop', 'pulse', 'task', 'mission', 'decide', 'campaign', 'agents', 'probe', 'worktree', 'land', 'caretaker', 'autoland', 'drive', 'aeo', 'slop', 'voice', 'strings', 'write', 'security-review', 'secure', 'deck', 'site', 'theme', 'card', 'reel', 'improve', 'study', 'rainmaker', 'xp', 'play', 'gm', 'game', 'x', 'recap', 'report', 'signup', 'clarity', 'interview', 'meet', 'moves', 'unknowns', 'avail', 'scoreboard', 'scorecard', 'sync-checkout', 'zen',
6
+ 'ingest', 'query', 'lint', 'loop', 'pulse', 'task', 'mission', 'decide', 'campaign', 'agents', 'probe', 'worktree', 'land', 'caretaker', 'autoland', 'drive', 'aeo', 'slop', 'voice', 'strings', 'write', 'security-review', 'secure', 'deck', 'site', 'theme', 'card', 'reel', 'improve', 'study', 'rainmaker', 'xp', 'play', 'gm', 'game', 'x', 'recap', 'report', 'signup', 'clarity', 'interview', 'meet', 'moves', 'unknowns', 'avail', 'scoreboard', 'scorecard', 'rating', 'sync-checkout', 'zen',
7
7
  'ci', 'github', 'vercel', 'supabase', 'linear', 'stripe', 'balance', 'usage', 'spend', 'api-key', 'agent-client', 'topup', 'buy', 'transactions', 'hire', 'hires', 'mail', 'design', 'mcp', 'gmail', 'calendar', 'twitter', 'slack', 'imessage', 'integrations', 'setup', 'clean-workspace', 'cw',
8
8
  'fork', 'browse', 'publish', 'pack', 'sleep', 'wake', 'feedback', 'errors', 'wiki', 'code-review', 'cr', 'soul', 'fleet', 'fleet-report', 'loops', 'self-improve', 'compile', 'spaceship', 'truth', 'sign', 'engine', 'engines', 'link', 'playbook', 'feed', 'brief', 'social', 'host', 'people', 'follow', 'unfollow', 'friends', 'msg', 'inbox', 'invite', 'join', 'rsi', 'pay', 'trust'];
9
9
 
@@ -6,6 +6,7 @@
6
6
 
7
7
  const { modelLabel } = require('./roster-models');
8
8
  const { rosterToolLabel } = require('./engine-registry');
9
+ const { bootRatingText } = require('./atris-rating');
9
10
  const { suggestionCount } = require('./roster-suggest');
10
11
 
11
12
  const BUILT_IN_JOBS = Object.freeze(['build', 'review', 'search']);
@@ -52,6 +53,8 @@ function readLineupSafe(root = process.cwd(), now = new Date()) {
52
53
 
53
54
  // One boot line: the tool and model that runs each built-in job right now,
54
55
  // custom jobs only when all of them still fit. '' when nothing resolves.
56
+ // A build job's model shows its combine rating (atris rating), since the
57
+ // combine grades building only.
55
58
  // When the run record suggests a new order for any job, a short marker
56
59
  // points at the roster view; only the end of the record is read.
57
60
  function bootTeamLine(root = process.cwd(), { width = 69, now = new Date(), jobs = null } = {}) {
@@ -63,7 +66,9 @@ function bootTeamLine(root = process.cwd(), { width = 69, now = new Date(), jobs
63
66
  const ordered = orderJobs(rows);
64
67
  const part = (row) => {
65
68
  const text = shortRuns(row.now_runs);
66
- return text ? `${row.job} ${text}` : '';
69
+ if (!text) return '';
70
+ const rated = row.role === 'executor' ? bootRatingText(row.now_runs.model) : '';
71
+ return `${row.job} ${text}${rated}`;
67
72
  };
68
73
  const builtIn = ordered.filter((row) => BUILT_IN_JOBS.includes(row.job) && !row.key).map(part).filter(Boolean);
69
74
  const custom = ordered.filter((row) => !BUILT_IN_JOBS.includes(row.job) || row.key).map(part).filter(Boolean);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atris",
3
- "version": "3.74.24",
3
+ "version": "3.74.25",
4
4
  "description": "you say what you want in plain words. atris builds it, checks it, and shows you proof.",
5
5
  "mcpName": "io.github.atrislabs/atris",
6
6
  "main": "bin/atris.js",