atris 3.74.15 → 3.74.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/atris/atris.md +1 -1
- package/atris.md +1 -1
- package/commands/member.js +52 -2
- package/commands/mission.js +94 -23
- package/package.json +1 -1
package/atris/atris.md
CHANGED
|
@@ -58,7 +58,7 @@ You can move fast. You do not get to move blindly.
|
|
|
58
58
|
We have done a lot of great work, and because of it we will do even more.
|
|
59
59
|
|
|
60
60
|
- **Acceleration.** Progress compounds. The early days are slow, then the curve bends and everything speeds up. Build on what is already done and settled; never redo it or re-ask it. Each landed piece makes the next one faster.
|
|
61
|
-
- **Accuracy.** A win counts when the result is validated. A test that failed before and passes after, a gate, or a real run proves it. Small fast tasks can be proven by a check alone; good and big work also gets a second reviewer. Accuracy is the floor the other three stand on, so better tests make everything faster.
|
|
61
|
+
- **Accuracy.** A win counts when the result is validated. A test that failed before and passes after, a gate, or a real run proves it. Not evidence: a test piped into `tail` or `grep` so its exit code is lost, a mock standing in for the real runtime, "should work", grading your own work, a test count with no repro that failed first, a summary of a file you did not open. Evidence: the real thing run end to end, a repro that failed and now passes, before and after numbers, the transcript or screenshot of the real run. Say done only next to that proof. Small fast tasks can be proven by a check alone; good and big work also gets a second reviewer. Accuracy is the floor the other three stand on, so better tests make everything faster.
|
|
62
62
|
- **Ambition.** Always one more level. When the goal has a clear check, go for the bigger version or the great task. We are capable of it, and we always find ways to win.
|
|
63
63
|
- **Agency.** Take action and own the outcome. Use every tool you have. When something is missing or broken, fix it the same turn instead of handing it back.
|
|
64
64
|
|
package/atris.md
CHANGED
|
@@ -58,7 +58,7 @@ You can move fast. You do not get to move blindly.
|
|
|
58
58
|
We have done a lot of great work, and because of it we will do even more.
|
|
59
59
|
|
|
60
60
|
- **Acceleration.** Progress compounds. The early days are slow, then the curve bends and everything speeds up. Build on what is already done and settled; never redo it or re-ask it. Each landed piece makes the next one faster.
|
|
61
|
-
- **Accuracy.** A win counts when the result is validated. A test that failed before and passes after, a gate, or a real run proves it. Small fast tasks can be proven by a check alone; good and big work also gets a second reviewer. Accuracy is the floor the other three stand on, so better tests make everything faster.
|
|
61
|
+
- **Accuracy.** A win counts when the result is validated. A test that failed before and passes after, a gate, or a real run proves it. Not evidence: a test piped into `tail` or `grep` so its exit code is lost, a mock standing in for the real runtime, "should work", grading your own work, a test count with no repro that failed first, a summary of a file you did not open. Evidence: the real thing run end to end, a repro that failed and now passes, before and after numbers, the transcript or screenshot of the real run. Say done only next to that proof. Small fast tasks can be proven by a check alone; good and big work also gets a second reviewer. Accuracy is the floor the other three stand on, so better tests make everything faster.
|
|
62
62
|
- **Ambition.** Always one more level. When the goal has a clear check, go for the bigger version or the great task. We are capable of it, and we always find ways to win.
|
|
63
63
|
- **Agency.** Take action and own the outcome. Use every tool you have. When something is missing or broken, fix it the same turn instead of handing it back.
|
|
64
64
|
|
package/commands/member.js
CHANGED
|
@@ -981,9 +981,39 @@ function inferMemberRunVerifier(missionText, args = [], cwd = process.cwd()) {
|
|
|
981
981
|
// chose that check; it is not a silent broad-suite default.
|
|
982
982
|
const npmMatch = text.match(/\bnpm\s+(run\s+)?test(?![\w:-]|\.\w)/i);
|
|
983
983
|
if (npmMatch && projectHasTestScript(cwd)) return npmMatch[1] ? 'npm run test' : 'npm test';
|
|
984
|
+
// A named test file in a project that runs node's own test runner: check
|
|
985
|
+
// just that file, since other files may fail on purpose ("legacy.js is out
|
|
986
|
+
// of scope"). Member lab: these jobs fell back to a whitespace check.
|
|
987
|
+
// Only sentences that ask for the work count; "x is out of scope" or "do not
|
|
988
|
+
// run the failing tests" must not pick a check.
|
|
989
|
+
const asks = text.split(/[;,\n]+|\.(?=\s|$)|\s(?:and|but)\s/i).filter((part) => part.trim()
|
|
990
|
+
&& !/\b(out\s+of\s+scope|do\s+not|don't|never|without|skip|ignore|leave|except|known\s+bug|reference)\b/i.test(part)).join('. ');
|
|
991
|
+
const testScript = projectTestScript(cwd);
|
|
992
|
+
// The project runs node's test runner itself, not as part of another command.
|
|
993
|
+
if (/^\s*node\s+--test\b/.test(testScript) && !/&&|\|\||[;|]/.test(testScript)) {
|
|
994
|
+
const root = path.resolve(cwd);
|
|
995
|
+
const named = (asks.match(/[\w./-]+\.[cm]?js\b/g) || [])
|
|
996
|
+
.filter((file) => /(^|\/)(tests?|checks?|specs?|__tests__)\/|\.(test|spec)\.[cm]?js$/.test(file))
|
|
997
|
+
.filter((file) => !path.isAbsolute(file) && path.resolve(root, file).startsWith(`${root}${path.sep}`))
|
|
998
|
+
.find((file) => fs.existsSync(path.join(root, file)));
|
|
999
|
+
if (named) return `node --test ${named}`;
|
|
1000
|
+
}
|
|
1001
|
+
// "fix the failing tests" names the project's tests as the target.
|
|
1002
|
+
if (testScript && /\b(fail(ing|ed)?\s+(\w+\s+){0,3}tests?|tests?\s+(pass|green)|fix\s+the\s+(\w+\s+){0,2}tests?)\b/i.test(asks)) {
|
|
1003
|
+
return 'npm test';
|
|
1004
|
+
}
|
|
984
1005
|
return '';
|
|
985
1006
|
}
|
|
986
1007
|
|
|
1008
|
+
function projectTestScript(cwd) {
|
|
1009
|
+
try {
|
|
1010
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
|
|
1011
|
+
return String(pkg?.scripts?.test || '');
|
|
1012
|
+
} catch {
|
|
1013
|
+
return '';
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
|
|
987
1017
|
function projectHasTestScript(cwd) {
|
|
988
1018
|
try {
|
|
989
1019
|
const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
|
|
@@ -1069,6 +1099,16 @@ function startMemberRunMission(name, missionText, args = []) {
|
|
|
1069
1099
|
// second engine only reran the same commands and cost about a minute a run
|
|
1070
1100
|
// (member lab 2026-10-24), so it stays for runs whose check is not a test
|
|
1071
1101
|
// runner (whitespace, syntax, or file-exists checks). --no-referee skips it; an explicit --referee is kept.
|
|
1102
|
+
// 'checked' when the run passed, 'unchecked' when the only thing missing is a
|
|
1103
|
+
// reviewer verdict (timed out or could not start), '' otherwise.
|
|
1104
|
+
function memberRunSaveDecision({ started, ok, verdict } = {}) {
|
|
1105
|
+
if (!started) return '';
|
|
1106
|
+
const v = verdict || {};
|
|
1107
|
+
if (v.passed === true) return ok ? 'checked' : '';
|
|
1108
|
+
const reviewerGone = v.referee?.mode === 'engine-unavailable' || v.mode === 'engine-unavailable';
|
|
1109
|
+
return reviewerGone && (v.status === undefined || v.status === 0) ? 'unchecked' : '';
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1072
1112
|
function memberRunHasRealCheck(verifier) {
|
|
1073
1113
|
const command = String(verifier || '').trim();
|
|
1074
1114
|
// A plain test runner only: chaining (|| true, ; , &&, pipes, subshells) or a
|
|
@@ -1468,13 +1508,22 @@ function memberRunWith(name, args, { quiet = false, closeTaskIds = [] } = {}) {
|
|
|
1468
1508
|
}
|
|
1469
1509
|
// Passed but never committed: save the loose work on the member's branch so
|
|
1470
1510
|
// the branch holds it. Only in the member's own side checkout, never the main one.
|
|
1511
|
+
// A reviewer that timed out or could not start gave no verdict; the work is
|
|
1512
|
+
// unchecked, not rejected, so save it too (member lab: a correct answer was
|
|
1513
|
+
// lost when the reviewer hit its time limit on a busy machine). Landing still
|
|
1514
|
+
// needs a real pass.
|
|
1471
1515
|
const worktreePath = finalMission.worktree?.path || mission.worktree?.path || '';
|
|
1472
|
-
|
|
1516
|
+
const saveDecision = memberRunSaveDecision({ started, ok, verdict: finalMission.verifier_result });
|
|
1517
|
+
const reviewerUnavailable = saveDecision === 'unchecked';
|
|
1518
|
+
if (worktreePath && saveDecision) {
|
|
1473
1519
|
result.saved_work = require('../lib/member-save-work').saveLeftoverMemberWork({
|
|
1474
1520
|
worktreePath, member: name, objective: finalMission.objective || mission.objective,
|
|
1475
1521
|
});
|
|
1522
|
+
if (reviewerUnavailable) result.saved_work.unchecked = true;
|
|
1476
1523
|
if (result.saved_work.saved && !asJson && !quiet) {
|
|
1477
|
-
console.log(
|
|
1524
|
+
console.log(reviewerUnavailable
|
|
1525
|
+
? `saved ${result.saved_work.files.length} file(s) onto ${name}'s branch, not checked yet: the reviewer timed out or could not start`
|
|
1526
|
+
: `saved ${result.saved_work.files.length} file(s) ${name} left unsaved onto its branch`);
|
|
1478
1527
|
}
|
|
1479
1528
|
}
|
|
1480
1529
|
// A finished ignited run whose check passed hands its branch to GitHub
|
|
@@ -12140,6 +12189,7 @@ module.exports = {
|
|
|
12140
12189
|
wakeBootLines,
|
|
12141
12190
|
buildMemberRunStartArgs,
|
|
12142
12191
|
inferMemberRunVerifier,
|
|
12192
|
+
memberRunSaveDecision,
|
|
12143
12193
|
buildMemberSubagentShell,
|
|
12144
12194
|
pickMemberForIdea,
|
|
12145
12195
|
appendMemberGoalLog,
|
package/commands/mission.js
CHANGED
|
@@ -6971,10 +6971,21 @@ function tickTouchedCheckDefinition(verifier, before, root = process.cwd()) {
|
|
|
6971
6971
|
// Runners that find tests by name (node --test, jest, pytest, go test) never
|
|
6972
6972
|
// list them, so any changed file shaped like a test counts as well.
|
|
6973
6973
|
const testShaped = /(^|\/)(tests?|__tests__|specs?|checks?)\/|\.(test|spec)\.[cm]?[jt]sx?$|_test\.(go|py)$|(^|\/)test_[^/]*\.py$|(^|\/)conftest\.py$/i;
|
|
6974
|
+
// Whole paths only: order.js is not named by `node --test checks/order.js`.
|
|
6975
|
+
// Shell punctuation ends a word, and $PWD or this folder's own path is cut off.
|
|
6976
|
+
const here = [path.resolve(root), (() => { try { return fs.realpathSync(root); } catch { return ''; } })()].filter(Boolean);
|
|
6977
|
+
const named = checkText.split(/[\s'"`=;&|<>()]+/)
|
|
6978
|
+
.map((word) => {
|
|
6979
|
+
let clean = word.replace(/^\$\{?PWD\}?\//, '');
|
|
6980
|
+
const inside = here.find((dir) => clean.startsWith(`${dir}/`));
|
|
6981
|
+
if (inside) clean = clean.slice(inside.length + 1);
|
|
6982
|
+
return clean.replace(/^(\.\/)+/, '');
|
|
6983
|
+
})
|
|
6984
|
+
.filter(Boolean);
|
|
6974
6985
|
return changed.some((file) => path.basename(file) === 'package.json'
|
|
6975
6986
|
|| testShaped.test(file)
|
|
6976
|
-
||
|
|
6977
|
-
|| (path.dirname(file) !== '.' &&
|
|
6987
|
+
|| named.includes(file)
|
|
6988
|
+
|| (path.dirname(file) !== '.' && named.some((word) => word.startsWith(`${path.dirname(file)}/`))));
|
|
6978
6989
|
}
|
|
6979
6990
|
|
|
6980
6991
|
// The worker's `learned:` line from its receipt, so the lesson reaches the
|
|
@@ -9997,6 +10008,9 @@ function buildEngineVerifyPrompt(mission, tickIndex, doneLooksLike = '', { timeo
|
|
|
9997
10008
|
`Split that line into each separate thing it promises and check every one. List each part as met or missing.`,
|
|
9998
10009
|
`Work that does only some of the parts is not done. If any part is missing, end with 'VERDICT: FAIL; missing: <that part>'.`,
|
|
9999
10010
|
] : []),
|
|
10011
|
+
// Member lab: reviewers spent about half their time on the workspace boot
|
|
10012
|
+
// (atris atris.md, MAP, PERSONA, skills) before reading the change.
|
|
10013
|
+
`Skip any workspace boot or start-of-session routine the folder's instructions ask for: do not run \`atris atris.md\` and do not read MAP.md, PERSONA.md, FOR_AGENTS.md, or skill files. Start with \`git diff\` and the changed files.`,
|
|
10000
10014
|
`Read the diff this tick produced. Do not edit files and do not start another work slice.`,
|
|
10001
10015
|
`ACTUALLY RUN the changed surface on this computer using real commands and real exit codes.`,
|
|
10002
10016
|
`Paste the commands and their real output. Do not accept a receipt claim or static inspection as execution proof.`,
|
|
@@ -10053,31 +10067,87 @@ function pickCrossEngineVerifier(mission, cwd = process.cwd(), preferredEngine =
|
|
|
10053
10067
|
return workerEngine === 'codex' ? 'claude' : 'codex';
|
|
10054
10068
|
}
|
|
10055
10069
|
|
|
10070
|
+
// Engine health lives in the main checkout, so a reviewer benched from one
|
|
10071
|
+
// member's side folder stays benched for the next member's review.
|
|
10072
|
+
function engineHealthRoot(cwd) {
|
|
10073
|
+
const common = spawnSync('git', ['rev-parse', '--path-format=absolute', '--git-common-dir'], { cwd, encoding: 'utf8' });
|
|
10074
|
+
const dir = common.status === 0 ? String(common.stdout || '').trim() : '';
|
|
10075
|
+
return dir && path.basename(dir) === '.git' ? path.dirname(dir) : cwd;
|
|
10076
|
+
}
|
|
10077
|
+
|
|
10078
|
+
// Every spelling and version of one model family counts as the same model,
|
|
10079
|
+
// with or without the long-context tag. Unknown counts as same.
|
|
10080
|
+
function reviewerMayBeSameModel(a, b) {
|
|
10081
|
+
const { CLAUDE_MODEL_ALIASES, CLAUDE_LONG_CONTEXT } = require('../lib/model-names');
|
|
10082
|
+
const key = (model) => {
|
|
10083
|
+
const name = String(model || '').toLowerCase().replace(CLAUDE_LONG_CONTEXT, '').replace(/^claude-/, '').trim();
|
|
10084
|
+
return CLAUDE_MODEL_ALIASES.find((family) => name.split(/[^a-z]+/).includes(family)) || name;
|
|
10085
|
+
};
|
|
10086
|
+
const left = key(a);
|
|
10087
|
+
const right = key(b);
|
|
10088
|
+
return !left || !right || left.includes(right) || right.includes(left);
|
|
10089
|
+
}
|
|
10090
|
+
|
|
10091
|
+
// Who reviews, in order. The usual reviewer first; then the roster's next
|
|
10092
|
+
// review worker, when it is not the builder grading itself: on the builder's
|
|
10093
|
+
// engine it must be a different model. A benched reviewer is skipped while
|
|
10094
|
+
// its backup can run. A named reviewer is never swapped.
|
|
10095
|
+
function engineVerifierLineup(mission, cwd, preferredEngine = '', options = {}) {
|
|
10096
|
+
const primary = pickCrossEngineVerifier(mission, cwd, preferredEngine);
|
|
10097
|
+
const lineup = [{ engine: primary, model: '' }];
|
|
10098
|
+
if (preferredEngine || mission?.referee_engine) return lineup;
|
|
10099
|
+
const root = options.healthRoot || engineHealthRoot(cwd);
|
|
10100
|
+
try {
|
|
10101
|
+
const registry = require('../lib/engine-registry');
|
|
10102
|
+
const workerEngine = canonicalEngineName(mission?.runner) || 'claude';
|
|
10103
|
+
const workerModel = resolveMissionTickRunnerModel(mission);
|
|
10104
|
+
const team = registry.resolveEngineForRoleRanked('validator', root, { job: 'review', quiet: true }).team || [];
|
|
10105
|
+
const backup = team.find((engine) => engine && engine.id !== primary
|
|
10106
|
+
&& !(engine.id === workerEngine && reviewerMayBeSameModel(engine.roster_model, workerModel)));
|
|
10107
|
+
if (!backup) return lineup;
|
|
10108
|
+
lineup.push({ engine: backup.id, model: backup.roster_model || '' });
|
|
10109
|
+
const saved = registry.readEngineRegistry(root, { persist: false }).engines.find((engine) => engine.id === primary);
|
|
10110
|
+
if (saved && !registry.engineReadyAt(saved)) lineup.shift();
|
|
10111
|
+
} catch {}
|
|
10112
|
+
return lineup;
|
|
10113
|
+
}
|
|
10114
|
+
|
|
10056
10115
|
async function runEngineVerifier(mission, options = {}) {
|
|
10057
10116
|
const cwd = options.cwd || process.cwd();
|
|
10058
|
-
const
|
|
10059
|
-
const
|
|
10060
|
-
|
|
10061
|
-
|
|
10062
|
-
|
|
10063
|
-
|
|
10064
|
-
|
|
10065
|
-
|
|
10066
|
-
|
|
10067
|
-
|
|
10068
|
-
|
|
10069
|
-
|
|
10117
|
+
const healthRoot = engineHealthRoot(cwd);
|
|
10118
|
+
const lineup = engineVerifierLineup(mission, cwd, options.refereeEngine, { healthRoot });
|
|
10119
|
+
let verdict = null;
|
|
10120
|
+
for (const { engine, model } of lineup) {
|
|
10121
|
+
const verifyMission = { ...mission, runner: engine, runner_kind: 'engine', model };
|
|
10122
|
+
const restoreRunnerProfile = applyMissionRunnerProfile(engine);
|
|
10123
|
+
let engineResult;
|
|
10124
|
+
try {
|
|
10125
|
+
engineResult = await spawnClaudeTick(verifyMission, {
|
|
10126
|
+
sessionMode: 'set',
|
|
10127
|
+
sessionId: crypto.randomUUID(),
|
|
10128
|
+
cwd,
|
|
10129
|
+
signal: options.signal,
|
|
10070
10130
|
timeoutMs: ENGINE_VERIFY_TIMEOUT_MS,
|
|
10071
|
-
|
|
10072
|
-
|
|
10073
|
-
|
|
10074
|
-
|
|
10075
|
-
|
|
10076
|
-
|
|
10077
|
-
|
|
10078
|
-
|
|
10131
|
+
prompt: buildEngineVerifyPrompt(mission, options.tickIndex || 1, missionDoneLooksLike(mission, cwd), {
|
|
10132
|
+
timeoutMs: ENGINE_VERIFY_TIMEOUT_MS,
|
|
10133
|
+
checkPassed: options.checkPassed || '',
|
|
10134
|
+
}),
|
|
10135
|
+
model: resolveMissionTickRunnerModel(verifyMission),
|
|
10136
|
+
});
|
|
10137
|
+
} catch (error) {
|
|
10138
|
+
engineResult = { ok: false, error: error.message };
|
|
10139
|
+
} finally {
|
|
10140
|
+
restoreRunnerProfile();
|
|
10141
|
+
}
|
|
10142
|
+
verdict = engineVerifierResultFromRun(engineResult, engine);
|
|
10143
|
+
if (verdict.mode !== 'engine-unavailable' || engineResult?.aborted) return verdict;
|
|
10144
|
+
// Member lab 10/26: codex hung on every review, and each job waited out
|
|
10145
|
+
// the full five minutes. Bench it so the next review starts on the backup.
|
|
10146
|
+
if (engineResult?.timedOut) {
|
|
10147
|
+
try { setEngineHealth(engine, 'cooling', healthRoot); } catch {}
|
|
10148
|
+
}
|
|
10079
10149
|
}
|
|
10080
|
-
return
|
|
10150
|
+
return verdict;
|
|
10081
10151
|
}
|
|
10082
10152
|
|
|
10083
10153
|
function stripClaudeReceiptLine(line) {
|
|
@@ -12928,6 +12998,7 @@ function missionCommand(args) {
|
|
|
12928
12998
|
module.exports = {
|
|
12929
12999
|
receiptLearnedLine,
|
|
12930
13000
|
tickTouchedCheckDefinition,
|
|
13001
|
+
engineVerifierLineup,
|
|
12931
13002
|
claudeRunnerMcpArgs,
|
|
12932
13003
|
crystallizeMemberSkillLesson,
|
|
12933
13004
|
listSiblingWorkspaceMissionHints,
|
package/package.json
CHANGED