atris 3.74.27 → 3.74.28
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/commands/rating.js +7 -2
- package/commands/task.js +105 -15
- package/commands/worktree.js +510 -18
- package/lib/auto-accept-certified.js +56 -0
- package/lib/campaign-keep.js +5 -1
- package/lib/fleet.js +329 -48
- package/lib/gm-season.js +44 -8
- package/lib/release-kind.js +27 -0
- package/lib/review-integrity.js +7 -0
- package/lib/roster-runs.js +11 -5
- package/lib/roster-suggest.js +3 -2
- package/lib/roster-swap.js +3 -1
- package/lib/router-brain.js +7 -6
- package/lib/task-db.js +88 -2
- package/lib/voice-gate.js +1 -0
- package/lib/worktree-space.js +357 -0
- package/package.json +1 -1
package/commands/rating.js
CHANGED
|
@@ -39,7 +39,10 @@ function readHere(deps = {}) {
|
|
|
39
39
|
}
|
|
40
40
|
if (!played || played.error || played.no_history) return null;
|
|
41
41
|
const { rosterRows } = require('../lib/gm-season');
|
|
42
|
-
|
|
42
|
+
// A member whose only misses were later confirmed not needed, or whose only jobs
|
|
43
|
+
// wait on a pull request, has no finished job, but still shows, so those jobs
|
|
44
|
+
// have somewhere to be counted.
|
|
45
|
+
const rows = rosterRows({ outcomes: played.outcomes || [], limit: Number.MAX_SAFE_INTEGER }).filter((row) => row.finished > 0 || row.not_needed > 0 || row.held > 0);
|
|
43
46
|
if (!rows.length) return null;
|
|
44
47
|
let lineup = deps.lineup;
|
|
45
48
|
if (lineup === undefined) lineup = require('../lib/team-lineup').readLineupSafe(root);
|
|
@@ -54,7 +57,7 @@ function readHere(deps = {}) {
|
|
|
54
57
|
}
|
|
55
58
|
const miss_reasons = [...reasons].map(([reason, count]) => ({ reason, count }))
|
|
56
59
|
.sort((a, b) => b.count - a.count || a.reason.localeCompare(b.reason)).slice(0, 3);
|
|
57
|
-
return { member: row.name, ovr: row.ovr, landed: row.landed, finished: row.finished, job: seat.job || null, model: seat.model || null, ...(miss_reasons.length ? { miss_reasons } : {}) };
|
|
60
|
+
return { member: row.name, ovr: row.ovr, landed: row.landed, finished: row.finished, ...(row.not_needed ? { not_needed: row.not_needed } : {}), ...(row.held ? { held: row.held } : {}), job: seat.job || null, model: seat.model || null, ...(miss_reasons.length ? { miss_reasons } : {}) };
|
|
58
61
|
});
|
|
59
62
|
const buildJob = ((lineup && lineup.ok && lineup.jobs) || []).find((row) => row.job === 'build' && !row.key);
|
|
60
63
|
const buildModel = buildJob && buildJob.now_runs ? buildJob.now_runs.model : null;
|
|
@@ -111,6 +114,8 @@ function renderMember(m, card) {
|
|
|
111
114
|
'',
|
|
112
115
|
` landed ${m.landed} of ${m.finished} finished jobs in the last 30 days`,
|
|
113
116
|
];
|
|
117
|
+
if (m.not_needed) lines.push(` not needed: ${m.not_needed} (confirmed by someone else, so ${m.not_needed === 1 ? 'it does' : 'they do'} not count either way)`);
|
|
118
|
+
if (m.held) lines.push(` waiting on a pull request: ${m.held} (the work is done and a person has to land it, so ${m.held === 1 ? 'it does' : 'they do'} not count either way)`);
|
|
114
119
|
if (m.ovr == null) lines.push(' rated once it has 3 finished jobs');
|
|
115
120
|
if (m.miss_reasons && m.miss_reasons.length) {
|
|
116
121
|
lines.push(' top miss reasons:');
|
package/commands/task.js
CHANGED
|
@@ -27,6 +27,7 @@ const {
|
|
|
27
27
|
evaluateAutoAccept,
|
|
28
28
|
isAgentCertified,
|
|
29
29
|
isAutoCertifyVerifyCommandAllowed,
|
|
30
|
+
openPullRequestRefusal,
|
|
30
31
|
parseVerifyChain,
|
|
31
32
|
parseVerifyCommand,
|
|
32
33
|
runVerifyCommand,
|
|
@@ -226,7 +227,7 @@ atris task - durable local task state (SQLite, gitignored)
|
|
|
226
227
|
atris task list [--all] [--everywhere] [--status <s>]
|
|
227
228
|
list tasks in this workspace; --everywhere spans workspaces
|
|
228
229
|
atris task claim <id> [--as <owner>] Atomic claim
|
|
229
|
-
atris task release <id> [--as <owner>] Release your own mistaken claim back to open
|
|
230
|
+
atris task release <id> [--as <owner>] [--reason "<why>"] Release your own mistaken claim back to open
|
|
230
231
|
atris task capabilities [--json] Read-only task CLI/API capability contract
|
|
231
232
|
atris task capabilities-check [--json] Verify task capability contract conformance
|
|
232
233
|
atris task review-lane-drain [--json] Pick next safe Review-lane agent action
|
|
@@ -252,9 +253,12 @@ atris task - durable local task state (SQLite, gitignored)
|
|
|
252
253
|
atris task step <id> [--json] Refine chat, then advance one safe Plan/Do/Review step
|
|
253
254
|
atris task done <id> --proof "..." Mark complete with proof
|
|
254
255
|
atris task done <id> --failed [--proof "..."] Mark failed, optionally reviewed
|
|
255
|
-
atris task archive <id> --reason "..." [--from-failed]
|
|
256
|
+
atris task archive <id> --reason "..." [--from-failed] [--not-needed]
|
|
256
257
|
Sweep off-roadmap/duplicate work as archived (not failed);
|
|
257
258
|
--from-failed opts in to relabel a fail-closed row (never done)
|
|
259
|
+
--not-needed: a person confirms the job did not need doing, so a
|
|
260
|
+
drop on it stops counting against the member (needs --reason;
|
|
261
|
+
refused inside an agent session or a dispatched worker)
|
|
258
262
|
atris task reopen <id> [--reason "..."] Send a done, waiting, failed, or archived task back to open
|
|
259
263
|
atris task clear-done [--before <days>] [--dry-run] [--json] Archive completed rows, oldest first
|
|
260
264
|
atris task prune-test-junk [--yes] [--vacuum] [--json]
|
|
@@ -366,6 +370,28 @@ function agentProofOnlyMode() {
|
|
|
366
370
|
return AGENT_ENV_MARKERS.some((marker) => String(process.env[marker] || '').trim() !== '');
|
|
367
371
|
}
|
|
368
372
|
|
|
373
|
+
// Everything an engine process carries: what the engine tools export in the
|
|
374
|
+
// shells they run, and what a mission flight sets on the drivers and engines it
|
|
375
|
+
// starts. ATRIS_AGENT_ID is left out on purpose: it is the owner's own id, which
|
|
376
|
+
// a person may export. The fleet's own dispatch adds no marker of its own, so
|
|
377
|
+
// it relies on the engine tool's.
|
|
378
|
+
const WORKER_ENV_MARKERS = [
|
|
379
|
+
...AGENT_ENV_MARKERS,
|
|
380
|
+
'CODEX_THREAD_ID',
|
|
381
|
+
'CODEX_SESSION_ID',
|
|
382
|
+
'ATRIS_MISSION_DRIVER_DETACHED',
|
|
383
|
+
'ATRIS_MISSION_DRIVER_PARENT_PID',
|
|
384
|
+
'ATRIS_DISPATCH_DEPTH',
|
|
385
|
+
];
|
|
386
|
+
|
|
387
|
+
// The first worker marker set in this shell, or ''. Unlike agentProofOnlyMode
|
|
388
|
+
// it cannot be switched off with ATRIS_AGENT_PROOF_ONLY=0.
|
|
389
|
+
function workerEnvMarker(env = process.env) {
|
|
390
|
+
const found = WORKER_ENV_MARKERS.find((marker) => String(env[marker] || '').trim() !== '');
|
|
391
|
+
if (found) return found;
|
|
392
|
+
return String(env.ATRIS_AGENT_PROOF_ONLY || '').trim() === '1' ? 'ATRIS_AGENT_PROOF_ONLY' : '';
|
|
393
|
+
}
|
|
394
|
+
|
|
369
395
|
function failAgentProofOnly(label, detail) {
|
|
370
396
|
failTask(
|
|
371
397
|
label,
|
|
@@ -575,15 +601,18 @@ function landingFlags(args) {
|
|
|
575
601
|
};
|
|
576
602
|
}
|
|
577
603
|
|
|
578
|
-
// The pull request
|
|
579
|
-
//
|
|
604
|
+
// The pull request a flight opened or merged, so the job record names it.
|
|
605
|
+
// Only a merge commit makes it count as landed. A number or link alone is an
|
|
606
|
+
// open pull request still waiting for a person to land it.
|
|
580
607
|
function readyMergeFlags(args) {
|
|
581
608
|
const commit = textFlag(args, ['--merge-commit']);
|
|
582
|
-
|
|
609
|
+
const number = textFlag(args, ['--pr-number']);
|
|
610
|
+
const url = textFlag(args, ['--pr-url']);
|
|
611
|
+
if (!commit && !number && !url) return null;
|
|
583
612
|
return {
|
|
584
613
|
merge_commit: commit,
|
|
585
|
-
number
|
|
586
|
-
url
|
|
614
|
+
number,
|
|
615
|
+
url,
|
|
587
616
|
merged_at: textFlag(args, ['--merged-at']),
|
|
588
617
|
self_landed: hasFlag(args, '--self-landed'),
|
|
589
618
|
};
|
|
@@ -751,13 +780,18 @@ function positional(args) {
|
|
|
751
780
|
});
|
|
752
781
|
}
|
|
753
782
|
|
|
783
|
+
// options.root is for a caller that is not the command line, the dispatch flight: it
|
|
784
|
+
// keeps the list for its own workspace instead of for the directory this process is in.
|
|
754
785
|
function writeDefaultProjection(taskDb, db, options = {}) {
|
|
755
|
-
const
|
|
786
|
+
const root = options.root ? path.resolve(options.root) : null;
|
|
787
|
+
const workspaceRoot = root
|
|
788
|
+
? (taskScopeEverywhere([], options) ? null : taskDb.workspaceRoot(root))
|
|
789
|
+
: scopedWorkspaceRoot(taskDb, [], options);
|
|
756
790
|
const projection = enrichTaskProjection(taskDb.taskProjection(db, {
|
|
757
791
|
workspaceRoot,
|
|
758
792
|
limit: options.all ? null : 500,
|
|
759
793
|
}));
|
|
760
|
-
const outPath = path.resolve(
|
|
794
|
+
const outPath = path.resolve(root || process.cwd(), '.atris', 'state', 'tasks.projection.json');
|
|
761
795
|
const output = JSON.stringify(projection, null, 2) + '\n';
|
|
762
796
|
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
763
797
|
let shouldWrite = true;
|
|
@@ -6327,26 +6361,39 @@ function cmdRelease(args) {
|
|
|
6327
6361
|
failTask('atris task release', 'missing_id', 'id required');
|
|
6328
6362
|
}
|
|
6329
6363
|
const owner = flag(args, '--as') || DEFAULT_OWNER;
|
|
6364
|
+
const reasonFlag = flag(args, '--reason');
|
|
6365
|
+
// This command takes no kind and no pull request. A release that says its work is
|
|
6366
|
+
// waiting on a person would keep a miss out of the rating, and any worker holding a
|
|
6367
|
+
// failed job could type that to excuse itself. Only the dispatch flight, which
|
|
6368
|
+
// opened the pull request itself, records that release, in its own process through
|
|
6369
|
+
// releaseTask. Flags this command does not know are ignored, so a release that still
|
|
6370
|
+
// passes them is a plain release and is rated as a drop.
|
|
6330
6371
|
const taskDb = getTaskDb();
|
|
6331
6372
|
const db = taskDb.open();
|
|
6332
6373
|
const taskId = requireTaskId(taskDb, db, id, 'atris task release');
|
|
6333
|
-
const result = taskDb.releaseTask(db, {
|
|
6374
|
+
const result = taskDb.releaseTask(db, {
|
|
6375
|
+
id: taskId,
|
|
6376
|
+
actor: String(owner),
|
|
6377
|
+
reason: typeof reasonFlag === 'string' ? reasonFlag : undefined,
|
|
6378
|
+
});
|
|
6334
6379
|
if (result.released) {
|
|
6335
6380
|
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
6336
6381
|
const task = compactTaskFromProjection(projection, taskId);
|
|
6382
|
+
const keptReason = result.event && result.event.payload && result.event.payload.reason || null;
|
|
6337
6383
|
if (wantsJson(args)) {
|
|
6338
6384
|
printJson({
|
|
6339
6385
|
ok: true,
|
|
6340
6386
|
action: 'released',
|
|
6341
6387
|
task_id: taskId,
|
|
6342
6388
|
owner: String(owner),
|
|
6389
|
+
reason: keptReason,
|
|
6343
6390
|
projection_path: outPath,
|
|
6344
6391
|
task,
|
|
6345
6392
|
event_version: result.event && result.event.version || null,
|
|
6346
6393
|
});
|
|
6347
6394
|
return;
|
|
6348
6395
|
}
|
|
6349
|
-
console.log(`released ${taskRef(task)} from ${owner}`);
|
|
6396
|
+
console.log(`released ${taskRef(task)} from ${owner}${keptReason ? ` (${keptReason})` : ''}`);
|
|
6350
6397
|
console.log(`Next: atris task next --as ${owner}`);
|
|
6351
6398
|
return;
|
|
6352
6399
|
}
|
|
@@ -9251,9 +9298,27 @@ function cmdArchive(args) {
|
|
|
9251
9298
|
if (!id) {
|
|
9252
9299
|
failTask('atris task archive', 'missing_id', 'id required');
|
|
9253
9300
|
}
|
|
9301
|
+
const notNeeded = hasFlag(args, '--not-needed');
|
|
9302
|
+
// The note clears a drop off the member's record, and --as is only a name. A
|
|
9303
|
+
// worker that dropped the job could name someone else and clear its own miss,
|
|
9304
|
+
// so inside an agent session or a dispatched worker it is refused outright.
|
|
9305
|
+
const workerMarker = notNeeded ? workerEnvMarker() : '';
|
|
9306
|
+
if (workerMarker) {
|
|
9307
|
+
failTask(
|
|
9308
|
+
'atris task archive',
|
|
9309
|
+
'not_needed_needs_a_person',
|
|
9310
|
+
`atris task archive --not-needed is for a person to confirm, and this shell looks like an agent session or a dispatched worker (${workerMarker} is set). Run it from a plain terminal.`,
|
|
9311
|
+
);
|
|
9312
|
+
}
|
|
9254
9313
|
const reason = textFlag(args, ['--reason']);
|
|
9255
9314
|
if (!reason) {
|
|
9256
|
-
failTask(
|
|
9315
|
+
failTask(
|
|
9316
|
+
'atris task archive',
|
|
9317
|
+
'missing_reason',
|
|
9318
|
+
notNeeded
|
|
9319
|
+
? 'atris task archive --not-needed requires --reason "<why this job did not need doing>"'
|
|
9320
|
+
: 'atris task archive requires --reason "<why this is being swept, not failed>"',
|
|
9321
|
+
);
|
|
9257
9322
|
}
|
|
9258
9323
|
const taskDb = getTaskDb();
|
|
9259
9324
|
const db = taskDb.open();
|
|
@@ -9263,8 +9328,13 @@ function cmdArchive(args) {
|
|
|
9263
9328
|
// loop-tick orphans fail-closed before 'archived' existed). Without the
|
|
9264
9329
|
// flag, failed rows stay failed; done rows are never archivable.
|
|
9265
9330
|
const fromFailed = hasFlag(args, '--from-failed');
|
|
9266
|
-
|
|
9267
|
-
|
|
9331
|
+
// --not-needed also writes who said so, which clears a dropped job off the
|
|
9332
|
+
// member's record when it is someone other than the member. A job that is
|
|
9333
|
+
// already archived only gets that note.
|
|
9334
|
+
const result = notNeeded
|
|
9335
|
+
? taskDb.archiveNotNeeded(db, { id: taskId, actor, reason, fromFailed })
|
|
9336
|
+
: taskDb.archiveTask(db, { id: taskId, actor, reason, fromFailed });
|
|
9337
|
+
if (result.archived || result.recorded) {
|
|
9268
9338
|
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
9269
9339
|
if (wantsJson(args)) {
|
|
9270
9340
|
printJson({
|
|
@@ -9272,6 +9342,7 @@ function cmdArchive(args) {
|
|
|
9272
9342
|
action: 'archived',
|
|
9273
9343
|
task_id: taskId,
|
|
9274
9344
|
reason,
|
|
9345
|
+
...(notNeeded ? { not_needed: true, not_needed_by: actor, already_archived: !result.archived } : {}),
|
|
9275
9346
|
archived_from: result.row && result.row.metadata && result.row.metadata.archived_from || null,
|
|
9276
9347
|
projection_path: outPath,
|
|
9277
9348
|
task: compactTaskFromProjection(projection, taskId),
|
|
@@ -9281,7 +9352,12 @@ function cmdArchive(args) {
|
|
|
9281
9352
|
const fromNote = result.row && result.row.metadata && result.row.metadata.archived_from
|
|
9282
9353
|
? ` (was ${result.row.metadata.archived_from})`
|
|
9283
9354
|
: '';
|
|
9284
|
-
|
|
9355
|
+
const ref = taskRef(compactTaskFromProjection(projection, taskId));
|
|
9356
|
+
if (notNeeded && !result.archived) {
|
|
9357
|
+
console.log(`${ref} was already archived; recorded as not needed by ${actor}: ${reason}`);
|
|
9358
|
+
} else {
|
|
9359
|
+
console.log(`archived ${ref}${fromNote}${notNeeded ? ' as not needed' : ''}: ${reason}`);
|
|
9360
|
+
}
|
|
9285
9361
|
} else {
|
|
9286
9362
|
const hint = result.reason === 'already_failed'
|
|
9287
9363
|
? ' (use --from-failed to archive a fail-closed duplicate/off-roadmap row)'
|
|
@@ -10052,6 +10128,13 @@ function stampAutoAcceptMetadata(taskDb, db, task, actor, policy) {
|
|
|
10052
10128
|
|
|
10053
10129
|
function acceptReviewTask(taskDb, db, taskId, { actor, proof, reward, lesson = '', nextTask = '', autoAccepted = false }) {
|
|
10054
10130
|
const task = taskDetail(taskDb, db, taskId);
|
|
10131
|
+
// The last door for the machine: whatever decided this job was eligible, a job
|
|
10132
|
+
// whose pull request is still open is never completed without a person. A
|
|
10133
|
+
// person's own accept (autoAccepted false) is untouched.
|
|
10134
|
+
if (autoAccepted) {
|
|
10135
|
+
const pullRequestOpen = openPullRequestRefusal(task, taskId);
|
|
10136
|
+
if (pullRequestOpen) return { ok: false, reason: pullRequestOpen.reason, pull_request: pullRequestOpen.pull_request };
|
|
10137
|
+
}
|
|
10055
10138
|
const missionXpIssue = missionXpEndToEndProofIssue(task, proof, taskDb.workspaceRoot());
|
|
10056
10139
|
if (missionXpIssue) {
|
|
10057
10140
|
return { ok: false, reason: MISSION_XP_END_TO_END_REASON, detail: missionXpIssue };
|
|
@@ -10735,6 +10818,10 @@ function evaluateSweepAutoAccept(task, root, includeProtected = false) {
|
|
|
10735
10818
|
if (metadata.auto_accepted_at) return { eligible: false, ref, reason: 'already_auto_accepted' };
|
|
10736
10819
|
const denied = autoAcceptSweepDeniedReason(task, includeProtected);
|
|
10737
10820
|
if (denied) return { eligible: false, ref, reason: denied };
|
|
10821
|
+
// A job whose pull request is still open has not landed, whatever its stored check
|
|
10822
|
+
// says: the same refusal as auto-accept-certified, before any check is run.
|
|
10823
|
+
const pullRequestOpen = openPullRequestRefusal(task, ref);
|
|
10824
|
+
if (pullRequestOpen) return pullRequestOpen;
|
|
10738
10825
|
const proof = autoAcceptSweepLatestProof(task);
|
|
10739
10826
|
if (!proof) return { eligible: false, ref, reason: 'no_proof' };
|
|
10740
10827
|
const storedVerify = typeof metadata.verify === 'string' ? metadata.verify.trim() : '';
|
|
@@ -13343,4 +13430,7 @@ module.exports = {
|
|
|
13343
13430
|
certifyVerifyCandidate,
|
|
13344
13431
|
landingProofVerify,
|
|
13345
13432
|
stampAutoAcceptMetadata,
|
|
13433
|
+
acceptReviewTask,
|
|
13434
|
+
evaluateSweepAutoAccept,
|
|
13435
|
+
writeDefaultProjection,
|
|
13346
13436
|
};
|