atris 3.74.26 → 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.
@@ -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
- const rows = rosterRows({ outcomes: played.outcomes || [], limit: Number.MAX_SAFE_INTEGER }).filter((row) => row.finished > 0);
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,6 +601,23 @@ function landingFlags(args) {
575
601
  };
576
602
  }
577
603
 
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.
607
+ function readyMergeFlags(args) {
608
+ const commit = textFlag(args, ['--merge-commit']);
609
+ const number = textFlag(args, ['--pr-number']);
610
+ const url = textFlag(args, ['--pr-url']);
611
+ if (!commit && !number && !url) return null;
612
+ return {
613
+ merge_commit: commit,
614
+ number,
615
+ url,
616
+ merged_at: textFlag(args, ['--merged-at']),
617
+ self_landed: hasFlag(args, '--self-landed'),
618
+ };
619
+ }
620
+
578
621
  function normalizedLandingSentence(value) {
579
622
  return String(value || '').replace(/\s+/g, ' ').trim();
580
623
  }
@@ -737,13 +780,18 @@ function positional(args) {
737
780
  });
738
781
  }
739
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.
740
785
  function writeDefaultProjection(taskDb, db, options = {}) {
741
- const workspaceRoot = scopedWorkspaceRoot(taskDb, [], options);
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);
742
790
  const projection = enrichTaskProjection(taskDb.taskProjection(db, {
743
791
  workspaceRoot,
744
792
  limit: options.all ? null : 500,
745
793
  }));
746
- const outPath = path.resolve(path.join('.atris', 'state', 'tasks.projection.json'));
794
+ const outPath = path.resolve(root || process.cwd(), '.atris', 'state', 'tasks.projection.json');
747
795
  const output = JSON.stringify(projection, null, 2) + '\n';
748
796
  fs.mkdirSync(path.dirname(outPath), { recursive: true });
749
797
  let shouldWrite = true;
@@ -6313,26 +6361,39 @@ function cmdRelease(args) {
6313
6361
  failTask('atris task release', 'missing_id', 'id required');
6314
6362
  }
6315
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.
6316
6371
  const taskDb = getTaskDb();
6317
6372
  const db = taskDb.open();
6318
6373
  const taskId = requireTaskId(taskDb, db, id, 'atris task release');
6319
- const result = taskDb.releaseTask(db, { id: taskId, actor: String(owner) });
6374
+ const result = taskDb.releaseTask(db, {
6375
+ id: taskId,
6376
+ actor: String(owner),
6377
+ reason: typeof reasonFlag === 'string' ? reasonFlag : undefined,
6378
+ });
6320
6379
  if (result.released) {
6321
6380
  const { projection, outPath } = writeDefaultProjection(taskDb, db);
6322
6381
  const task = compactTaskFromProjection(projection, taskId);
6382
+ const keptReason = result.event && result.event.payload && result.event.payload.reason || null;
6323
6383
  if (wantsJson(args)) {
6324
6384
  printJson({
6325
6385
  ok: true,
6326
6386
  action: 'released',
6327
6387
  task_id: taskId,
6328
6388
  owner: String(owner),
6389
+ reason: keptReason,
6329
6390
  projection_path: outPath,
6330
6391
  task,
6331
6392
  event_version: result.event && result.event.version || null,
6332
6393
  });
6333
6394
  return;
6334
6395
  }
6335
- console.log(`released ${taskRef(task)} from ${owner}`);
6396
+ console.log(`released ${taskRef(task)} from ${owner}${keptReason ? ` (${keptReason})` : ''}`);
6336
6397
  console.log(`Next: atris task next --as ${owner}`);
6337
6398
  return;
6338
6399
  }
@@ -9237,9 +9298,27 @@ function cmdArchive(args) {
9237
9298
  if (!id) {
9238
9299
  failTask('atris task archive', 'missing_id', 'id required');
9239
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
+ }
9240
9313
  const reason = textFlag(args, ['--reason']);
9241
9314
  if (!reason) {
9242
- failTask('atris task archive', 'missing_reason', 'atris task archive requires --reason "<why this is being swept, not failed>"');
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
+ );
9243
9322
  }
9244
9323
  const taskDb = getTaskDb();
9245
9324
  const db = taskDb.open();
@@ -9249,8 +9328,13 @@ function cmdArchive(args) {
9249
9328
  // loop-tick orphans fail-closed before 'archived' existed). Without the
9250
9329
  // flag, failed rows stay failed; done rows are never archivable.
9251
9330
  const fromFailed = hasFlag(args, '--from-failed');
9252
- const result = taskDb.archiveTask(db, { id: taskId, actor, reason, fromFailed });
9253
- if (result.archived) {
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) {
9254
9338
  const { projection, outPath } = writeDefaultProjection(taskDb, db);
9255
9339
  if (wantsJson(args)) {
9256
9340
  printJson({
@@ -9258,6 +9342,7 @@ function cmdArchive(args) {
9258
9342
  action: 'archived',
9259
9343
  task_id: taskId,
9260
9344
  reason,
9345
+ ...(notNeeded ? { not_needed: true, not_needed_by: actor, already_archived: !result.archived } : {}),
9261
9346
  archived_from: result.row && result.row.metadata && result.row.metadata.archived_from || null,
9262
9347
  projection_path: outPath,
9263
9348
  task: compactTaskFromProjection(projection, taskId),
@@ -9267,7 +9352,12 @@ function cmdArchive(args) {
9267
9352
  const fromNote = result.row && result.row.metadata && result.row.metadata.archived_from
9268
9353
  ? ` (was ${result.row.metadata.archived_from})`
9269
9354
  : '';
9270
- console.log(`archived ${taskRef(compactTaskFromProjection(projection, taskId))}${fromNote}: ${reason}`);
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
+ }
9271
9361
  } else {
9272
9362
  const hint = result.reason === 'already_failed'
9273
9363
  ? ' (use --from-failed to archive a fail-closed duplicate/off-roadmap row)'
@@ -9650,6 +9740,7 @@ function cmdReady(args) {
9650
9740
  const lesson = flag(args, '--lesson') || '';
9651
9741
  const nextTaskInput = normalizeReviewNextTaskInput(typeof flag(args, '--next') === 'string' ? flag(args, '--next') : '');
9652
9742
  const landing = landingFlags(args);
9743
+ const merge = readyMergeFlags(args);
9653
9744
  guardExplicitActor('atris task ready', flag(args, '--as'));
9654
9745
  const actor = String(flag(args, '--as') || DEFAULT_OWNER);
9655
9746
  const resultFields = {
@@ -9697,6 +9788,7 @@ function cmdReady(args) {
9697
9788
  nextTask: nextTaskInput.nextTask,
9698
9789
  resultTrace: resultTrace && resultTrace.trace,
9699
9790
  landing,
9791
+ merge,
9700
9792
  result: resultSentence,
9701
9793
  });
9702
9794
  if (!result.ready) {
@@ -10036,6 +10128,13 @@ function stampAutoAcceptMetadata(taskDb, db, task, actor, policy) {
10036
10128
 
10037
10129
  function acceptReviewTask(taskDb, db, taskId, { actor, proof, reward, lesson = '', nextTask = '', autoAccepted = false }) {
10038
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
+ }
10039
10138
  const missionXpIssue = missionXpEndToEndProofIssue(task, proof, taskDb.workspaceRoot());
10040
10139
  if (missionXpIssue) {
10041
10140
  return { ok: false, reason: MISSION_XP_END_TO_END_REASON, detail: missionXpIssue };
@@ -10719,6 +10818,10 @@ function evaluateSweepAutoAccept(task, root, includeProtected = false) {
10719
10818
  if (metadata.auto_accepted_at) return { eligible: false, ref, reason: 'already_auto_accepted' };
10720
10819
  const denied = autoAcceptSweepDeniedReason(task, includeProtected);
10721
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;
10722
10825
  const proof = autoAcceptSweepLatestProof(task);
10723
10826
  if (!proof) return { eligible: false, ref, reason: 'no_proof' };
10724
10827
  const storedVerify = typeof metadata.verify === 'string' ? metadata.verify.trim() : '';
@@ -13327,4 +13430,7 @@ module.exports = {
13327
13430
  certifyVerifyCandidate,
13328
13431
  landingProofVerify,
13329
13432
  stampAutoAcceptMetadata,
13433
+ acceptReviewTask,
13434
+ evaluateSweepAutoAccept,
13435
+ writeDefaultProjection,
13330
13436
  };