atris 3.74.23 → 3.74.24
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 +17 -0
- package/bin/atris.js +5 -0
- package/commands/scorecard.js +297 -0
- package/lib/known-commands.js +1 -1
- package/lib/repo-scorecard.js +1135 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -442,6 +442,23 @@ Under the hood, Atris can keep score on real repo work.
|
|
|
442
442
|
- `atris autopilot` can run that check after review and record the result in the journal.
|
|
443
443
|
- Future task picks can use recent results, so Atris learns from repo-local history without claiming model retraining.
|
|
444
444
|
|
|
445
|
+
## Which AI Can Do Your Work
|
|
446
|
+
|
|
447
|
+
`atris scorecard` measures which AI can do your team's work, on your own repo.
|
|
448
|
+
|
|
449
|
+
```bash
|
|
450
|
+
atris scorecard # find past changes whose own tests can grade an AI (no AI runs)
|
|
451
|
+
atris scorecard run # try each one with Haiku on your Claude plan
|
|
452
|
+
atris scorecard run --models haiku,opus # compare models; past Haiku needs ATRIS_CLAUDE_SPEND_OK=<model>
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
- A past change becomes a task when its tests fail before it and pass after it. The ask is what your team wrote for that change.
|
|
456
|
+
- The ask also names what the change's tests call that the change added or changed: a new file, a function with its arguments, an export, found with no AI. A good ticket names these; a commit message rarely does, and no model can guess a name nobody wrote down.
|
|
457
|
+
- Each try starts from the change's parent in a throwaway copy with no remote. The change's own tests stay hidden until they grade the try.
|
|
458
|
+
- Every try runs on your Claude plan login. Pay-per-use keys are removed, a try stops the moment Claude Code reports any other billing source, and nothing starts with extra usage on unless you pass `--allow-extra-usage`.
|
|
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
|
+
- Tests that need their own command or settings go in `.atris/scorecard.json`.
|
|
461
|
+
|
|
445
462
|
## Benchmark Harness
|
|
446
463
|
|
|
447
464
|
Atris ships one public head-to-head benchmark harness for comparing a pinned
|
package/bin/atris.js
CHANGED
|
@@ -651,6 +651,7 @@ function showHelpAll() {
|
|
|
651
651
|
console.log(' meet - onboard a stranger in one sitting and print their /book link');
|
|
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
|
+
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)');
|
|
654
655
|
console.log(' brain - Compile MAP/TODO/wiki/state into a loadable agent brain');
|
|
655
656
|
console.log(' lesson - atris lesson add <slug> <pass|fail> "<text>" (mine/sweep/resolve/revert/ledger; --detector validates the falsifier at write time)');
|
|
656
657
|
console.log(' taste - Record the operator\'s keep, kill, and more creative verdicts');
|
|
@@ -2234,6 +2235,10 @@ if (command === 'guide') {
|
|
|
2234
2235
|
Promise.resolve().then(() => require('../commands/scoreboard').scoreboardCommand(process.argv.slice(3)))
|
|
2235
2236
|
.then((code) => exitAfterStdoutDrain(typeof code === 'number' ? code : 0))
|
|
2236
2237
|
.catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
|
|
2238
|
+
} else if (command === 'scorecard') {
|
|
2239
|
+
Promise.resolve().then(() => require('../commands/scorecard').scorecardCommand(process.argv.slice(3)))
|
|
2240
|
+
.then((code) => exitAfterStdoutDrain(typeof code === 'number' ? code : 0))
|
|
2241
|
+
.catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
|
|
2237
2242
|
} else if (command === 'meet') {
|
|
2238
2243
|
Promise.resolve().then(() => require('../commands/meet').meetCommand(process.argv.slice(3)))
|
|
2239
2244
|
.then((code) => exitAfterStdoutDrain(typeof code === 'number' ? code : 0))
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// atris scorecard: which AI can do your team's work, measured on your own
|
|
4
|
+
// past changes and graded by the tests that shipped with them. Finding tasks
|
|
5
|
+
// runs no AI. Tries run on your Claude plan login, never an API key.
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const { hasFlag, readFlag, readIntFlag, readNumberFlag } = require('../lib/arg-parser');
|
|
10
|
+
const sc = require('../lib/repo-scorecard');
|
|
11
|
+
|
|
12
|
+
const USAGE = [
|
|
13
|
+
'usage: atris scorecard [find | run | show] [options]',
|
|
14
|
+
' which AI can do your team\'s work, measured on your own past changes.',
|
|
15
|
+
'',
|
|
16
|
+
' atris scorecard find tasks the first time, then show the scorecard',
|
|
17
|
+
' atris scorecard find turn past changes into tasks their own tests grade (no AI runs)',
|
|
18
|
+
' atris scorecard run try each task with each model on your Claude plan',
|
|
19
|
+
' atris scorecard show print the scorecard from saved results',
|
|
20
|
+
'',
|
|
21
|
+
'find options:',
|
|
22
|
+
' --max N keep at most N tasks (default 12)',
|
|
23
|
+
' --scan N look through the last N changes (default 300)',
|
|
24
|
+
' --config FILE test commands for this repo (default .atris/scorecard.json)',
|
|
25
|
+
' --with-env copy .env files into each copy, read-only, billing keys left out',
|
|
26
|
+
'',
|
|
27
|
+
'run options:',
|
|
28
|
+
' --models a,b models to try (default haiku). any model past haiku needs',
|
|
29
|
+
' ATRIS_CLAUDE_SPEND_OK=<model> because it uses much more of your plan',
|
|
30
|
+
' --tries N tries per task and model (default 1). a stopped run picks up where it left off',
|
|
31
|
+
' --jobs N tries at a time (default 2)',
|
|
32
|
+
' --minutes N time limit per try (default 10, 20 for opus and fable)',
|
|
33
|
+
' --task ID only this task (its id or the first characters of its change)',
|
|
34
|
+
' --allow-extra-usage run even when extra usage is on for your Claude account',
|
|
35
|
+
'',
|
|
36
|
+
' --json print the result as json',
|
|
37
|
+
].join('\n');
|
|
38
|
+
|
|
39
|
+
function storePaths(root) {
|
|
40
|
+
const dir = sc.storeDir(root);
|
|
41
|
+
return { dir, tasks: path.join(dir, 'tasks.json'), results: path.join(dir, 'results.jsonl') };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function readTasks(file) {
|
|
45
|
+
try {
|
|
46
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function readResults(file) {
|
|
53
|
+
try {
|
|
54
|
+
return fs.readFileSync(file, 'utf8').split('\n').filter(Boolean).map((line) => {
|
|
55
|
+
try { return JSON.parse(line); } catch { return null; }
|
|
56
|
+
}).filter(Boolean);
|
|
57
|
+
} catch {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function firstLine(text, width) {
|
|
63
|
+
const line = String(text || '').split('\n')[0];
|
|
64
|
+
return line.length > width ? `${line.slice(0, width - 3)}...` : line;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const SKIP_WORDS = {
|
|
68
|
+
no_tests: 'had no tests changed beside the code',
|
|
69
|
+
too_big: 'were too big for one ask',
|
|
70
|
+
short_ask: 'had too few words to work from',
|
|
71
|
+
unknown_runner: 'used a test setup this cannot run yet',
|
|
72
|
+
fails_after: 'had tests that fail even with the change',
|
|
73
|
+
passes_before: 'had tests that already passed before the change',
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
function skipSummary(skipped) {
|
|
77
|
+
return Object.entries(skipped || {}).filter(([, n]) => n > 0)
|
|
78
|
+
.map(([key, n]) => `${n} ${SKIP_WORDS[key] || key}`).join(', ');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function find(root, args, { quiet = false } = {}) {
|
|
82
|
+
const paths = storePaths(root);
|
|
83
|
+
const json = hasFlag(args, '--json');
|
|
84
|
+
const say = (line) => { if (!json && !quiet) console.log(line); };
|
|
85
|
+
const max = readIntFlag(args, '--max', sc.MAX_TASKS);
|
|
86
|
+
const scan = readIntFlag(args, '--scan', 300);
|
|
87
|
+
say(`atris scorecard ${sc.repoName(root)}`);
|
|
88
|
+
say(`looking through the last ${scan} changes for ones whose own tests can grade an AI. no AI runs in this step.`);
|
|
89
|
+
say('');
|
|
90
|
+
const scratch = sc.workDir(root);
|
|
91
|
+
let result;
|
|
92
|
+
try {
|
|
93
|
+
result = await sc.findTasks(root, {
|
|
94
|
+
max,
|
|
95
|
+
scan,
|
|
96
|
+
configPath: readFlag(args, '--config', '') || undefined,
|
|
97
|
+
withEnv: hasFlag(args, '--with-env'),
|
|
98
|
+
workDir: scratch,
|
|
99
|
+
onCheck: (task, verdict) => {
|
|
100
|
+
const word = verdict === 'found' ? 'task' : 'skip';
|
|
101
|
+
const why = verdict === 'found' ? sc.runnerLabel(task.runner) : SKIP_WORDS[verdict];
|
|
102
|
+
say(` ${word} ${task.fix.slice(0, 7)} ${firstLine(task.ask, 60).padEnd(60)} ${why}`);
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
} finally {
|
|
106
|
+
sc.stopAll();
|
|
107
|
+
fs.rmSync(scratch, { recursive: true, force: true });
|
|
108
|
+
}
|
|
109
|
+
const saved = {
|
|
110
|
+
repo: sc.repoName(root),
|
|
111
|
+
root,
|
|
112
|
+
found_at: new Date().toISOString(),
|
|
113
|
+
looked: result.looked,
|
|
114
|
+
candidates: result.candidates,
|
|
115
|
+
skipped: result.skipped,
|
|
116
|
+
tasks: result.tasks,
|
|
117
|
+
};
|
|
118
|
+
fs.mkdirSync(paths.dir, { recursive: true });
|
|
119
|
+
fs.writeFileSync(paths.tasks, `${JSON.stringify(saved, null, 2)}\n`);
|
|
120
|
+
if (json) {
|
|
121
|
+
console.log(JSON.stringify(saved, null, 2));
|
|
122
|
+
return 0;
|
|
123
|
+
}
|
|
124
|
+
if (!quiet) {
|
|
125
|
+
say('');
|
|
126
|
+
say(`found ${result.tasks.length} ${result.tasks.length === 1 ? 'task' : 'tasks'} in ${result.looked} past changes.`);
|
|
127
|
+
const skips = skipSummary(result.skipped);
|
|
128
|
+
if (skips) say(`the rest ${skips}.`);
|
|
129
|
+
if (!result.tasks.length) {
|
|
130
|
+
say('');
|
|
131
|
+
say('if your tests need a command or settings to run, put them in .atris/scorecard.json, for example:');
|
|
132
|
+
say(' {"tests": [{"match": "^backend/", "dir": "backend", "cmd": "python -m pytest -q {files}", "env": {"ENV": "test"}}]}');
|
|
133
|
+
return 1;
|
|
134
|
+
}
|
|
135
|
+
say('');
|
|
136
|
+
say('next: atris scorecard run');
|
|
137
|
+
say(` tries each task with ${sc.shortModel(sc.DEFAULT_MODELS[0])} on your Claude plan, up to 10 minutes each, 2 at a time.`);
|
|
138
|
+
}
|
|
139
|
+
return result.tasks.length ? 0 : 1;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function pickTasks(saved, args) {
|
|
143
|
+
const only = readFlag(args, '--task', '');
|
|
144
|
+
if (!only) return saved.tasks;
|
|
145
|
+
return saved.tasks.filter((task) => task.id === only || task.id.startsWith(only) || task.fix.startsWith(only));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function run(root, args) {
|
|
149
|
+
const paths = storePaths(root);
|
|
150
|
+
const json = hasFlag(args, '--json');
|
|
151
|
+
const say = (line) => { if (!json) console.log(line); };
|
|
152
|
+
let saved = readTasks(paths.tasks);
|
|
153
|
+
if (!saved) {
|
|
154
|
+
say('no tasks yet, so finding them first.');
|
|
155
|
+
say('');
|
|
156
|
+
const code = await find(root, args.filter((a) => a !== '--json'), { quiet: json });
|
|
157
|
+
if (code !== 0) return code;
|
|
158
|
+
saved = readTasks(paths.tasks);
|
|
159
|
+
say('');
|
|
160
|
+
}
|
|
161
|
+
const tasks = pickTasks(saved, args);
|
|
162
|
+
if (!tasks.length) {
|
|
163
|
+
console.error(saved.tasks.length ? 'no task matches --task.' : 'there are no tasks to run. try atris scorecard find --scan 1000');
|
|
164
|
+
return 1;
|
|
165
|
+
}
|
|
166
|
+
const models = sc.normalizeModels(readFlag(args, '--models', ''));
|
|
167
|
+
let login;
|
|
168
|
+
try {
|
|
169
|
+
login = sc.preflight(models, { allowExtraUsage: hasFlag(args, '--allow-extra-usage') });
|
|
170
|
+
} catch (err) {
|
|
171
|
+
console.error(err.message || String(err));
|
|
172
|
+
return 1;
|
|
173
|
+
}
|
|
174
|
+
const tries = Math.max(1, readIntFlag(args, '--tries', 1) || 1);
|
|
175
|
+
const jobs = Math.max(1, readIntFlag(args, '--jobs', 2) || 2);
|
|
176
|
+
const minutes = readNumberFlag(args, '--minutes', null);
|
|
177
|
+
const before = readResults(paths.results);
|
|
178
|
+
const names = models.map(sc.shortModel).join(', ');
|
|
179
|
+
say(`atris scorecard run ${sc.repoName(root)}`);
|
|
180
|
+
say(`${tasks.length} ${tasks.length === 1 ? 'task' : 'tasks'} x ${names}, ${tries} ${tries === 1 ? 'try' : 'tries'} each, ${jobs} at a time.`);
|
|
181
|
+
say(`on your ${login.plan}. pay-per-use keys are removed from every run${login.extraUsage ? '' : ', so this bills $0'}.`);
|
|
182
|
+
say('');
|
|
183
|
+
|
|
184
|
+
const scratch = sc.workDir(root);
|
|
185
|
+
const cleanup = () => {
|
|
186
|
+
sc.stopAll();
|
|
187
|
+
fs.rmSync(scratch, { recursive: true, force: true });
|
|
188
|
+
};
|
|
189
|
+
const onSignal = () => {
|
|
190
|
+
cleanup();
|
|
191
|
+
console.error('\nstopped. finished tries are saved; run the same command to pick up where it left off.');
|
|
192
|
+
process.exit(130);
|
|
193
|
+
};
|
|
194
|
+
process.once('SIGINT', onSignal);
|
|
195
|
+
process.once('SIGTERM', onSignal);
|
|
196
|
+
let outcome;
|
|
197
|
+
try {
|
|
198
|
+
outcome = await sc.runAll(root, tasks, {
|
|
199
|
+
models,
|
|
200
|
+
tries,
|
|
201
|
+
jobs,
|
|
202
|
+
minutes,
|
|
203
|
+
bin: login.bin,
|
|
204
|
+
withEnv: hasFlag(args, '--with-env'),
|
|
205
|
+
done: before,
|
|
206
|
+
workDir: scratch,
|
|
207
|
+
outDir: paths.dir,
|
|
208
|
+
onResult: (row) => {
|
|
209
|
+
fs.appendFileSync(paths.results, `${JSON.stringify(row)}\n`);
|
|
210
|
+
const task = tasks.find((t) => t.id === row.id) || { fix: row.id, ask: '' };
|
|
211
|
+
let word = row.passed ? 'pass' : 'fail';
|
|
212
|
+
let note = row.timed_out ? `${row.limit_minutes} min (time limit)` : `${row.minutes} min`;
|
|
213
|
+
if (row.engine_error) { word = 'skip'; note = firstLine(row.engine_tail, 50) || 'claude did not finish'; }
|
|
214
|
+
if (row.harness_error) { word = 'skip'; note = firstLine(row.harness_error, 50); }
|
|
215
|
+
say(` ${word} ${sc.shortModel(row.model).padEnd(10)} ${task.fix.slice(0, 7)} ${firstLine(task.ask, 50).padEnd(50)} ${note}`);
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
} finally {
|
|
219
|
+
process.removeListener('SIGINT', onSignal);
|
|
220
|
+
process.removeListener('SIGTERM', onSignal);
|
|
221
|
+
cleanup();
|
|
222
|
+
}
|
|
223
|
+
const rows = readResults(paths.results);
|
|
224
|
+
const summary = sc.summarize(saved.tasks, rows, models);
|
|
225
|
+
if (json) {
|
|
226
|
+
console.log(JSON.stringify({ ...summary, stopped: outcome.stopped, ran: outcome.rows.length }, null, 2));
|
|
227
|
+
return outcome.stopped ? 1 : 0;
|
|
228
|
+
}
|
|
229
|
+
if (!outcome.planned) say('every try asked for is already done.');
|
|
230
|
+
say('');
|
|
231
|
+
console.log(sc.renderScorecard(summary, { repo: sc.repoName(root), plan: login.plan, extraUsage: login.extraUsage }));
|
|
232
|
+
if (outcome.stopped) {
|
|
233
|
+
const billed = outcome.rows.find((row) => row.not_plan);
|
|
234
|
+
say('');
|
|
235
|
+
say(billed
|
|
236
|
+
? `stopped: ${billed.engine_tail} log in to your Claude plan with \`claude\` then /login, and run it again.`
|
|
237
|
+
: 'stopped early: your Claude plan hit its usage limit. run the same command after it resets to finish.');
|
|
238
|
+
return 1;
|
|
239
|
+
}
|
|
240
|
+
return 0;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function show(root, args) {
|
|
244
|
+
const paths = storePaths(root);
|
|
245
|
+
const saved = readTasks(paths.tasks);
|
|
246
|
+
if (!saved) {
|
|
247
|
+
console.error('no scorecard yet. run: atris scorecard find');
|
|
248
|
+
return 1;
|
|
249
|
+
}
|
|
250
|
+
const rows = readResults(paths.results);
|
|
251
|
+
const summary = sc.summarize(saved.tasks, rows);
|
|
252
|
+
if (hasFlag(args, '--json')) {
|
|
253
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
254
|
+
return 0;
|
|
255
|
+
}
|
|
256
|
+
if (!rows.some((row) => saved.tasks.some((task) => task.id === row.id))) {
|
|
257
|
+
console.log(`atris scorecard ${saved.repo}`);
|
|
258
|
+
console.log(`${saved.tasks.length} tasks found, none tried yet:`);
|
|
259
|
+
for (const task of saved.tasks) console.log(` ${task.fix.slice(0, 7)} ${firstLine(task.ask, 80)}`);
|
|
260
|
+
console.log('');
|
|
261
|
+
console.log('next: atris scorecard run');
|
|
262
|
+
return 0;
|
|
263
|
+
}
|
|
264
|
+
const login = sc.planLogin();
|
|
265
|
+
console.log(sc.renderScorecard(summary, { repo: saved.repo, plan: login.ok ? login.plan : '', extraUsage: login.extraUsage }));
|
|
266
|
+
return 0;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async function scorecardCommand(args = []) {
|
|
270
|
+
if (hasFlag(args, '--help') || hasFlag(args, '-h') || args[0] === 'help') {
|
|
271
|
+
console.log(USAGE);
|
|
272
|
+
return 0;
|
|
273
|
+
}
|
|
274
|
+
let root;
|
|
275
|
+
try {
|
|
276
|
+
root = sc.repoRoot();
|
|
277
|
+
} catch {
|
|
278
|
+
console.error('atris scorecard works inside a git repo. cd into one and try again.');
|
|
279
|
+
return 1;
|
|
280
|
+
}
|
|
281
|
+
const sub = args[0] && !args[0].startsWith('-') ? args[0] : '';
|
|
282
|
+
const rest = sub ? args.slice(1) : args;
|
|
283
|
+
if (sub === 'find') return find(root, rest);
|
|
284
|
+
if (sub === 'run') return run(root, rest);
|
|
285
|
+
if (sub === 'show') return show(root, rest);
|
|
286
|
+
if (sub) {
|
|
287
|
+
console.error(`unknown step "${sub}".\n\n${USAGE}`);
|
|
288
|
+
return 1;
|
|
289
|
+
}
|
|
290
|
+
if (!readTasks(storePaths(root).tasks)) {
|
|
291
|
+
const code = await find(root, rest);
|
|
292
|
+
return code;
|
|
293
|
+
}
|
|
294
|
+
return show(root, rest);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
module.exports = { scorecardCommand, USAGE };
|
package/lib/known-commands.js
CHANGED
|
@@ -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', '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', '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
|
|