atris 3.34.0 → 3.35.0

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.
Files changed (53) hide show
  1. package/AGENTS.md +0 -2
  2. package/FOR_AGENTS.md +5 -3
  3. package/atris/skills/engines/SKILL.md +16 -7
  4. package/atris/skills/render-cli/SKILL.md +88 -0
  5. package/atris.md +4 -2
  6. package/ax +475 -17
  7. package/bin/atris.js +197 -44
  8. package/commands/aeo.js +52 -0
  9. package/commands/autoland.js +313 -19
  10. package/commands/business.js +91 -8
  11. package/commands/codex-goal.js +26 -2
  12. package/commands/drive.js +187 -0
  13. package/commands/engine.js +73 -2
  14. package/commands/feed.js +202 -0
  15. package/commands/github.js +38 -0
  16. package/commands/gm.js +262 -3
  17. package/commands/init.js +13 -1
  18. package/commands/integrations.js +39 -11
  19. package/commands/interview.js +143 -0
  20. package/commands/land.js +114 -13
  21. package/commands/lesson.js +112 -1
  22. package/commands/linear.js +38 -0
  23. package/commands/member.js +398 -42
  24. package/commands/mission.js +554 -64
  25. package/commands/now.js +25 -1
  26. package/commands/radar.js +259 -14
  27. package/commands/serve.js +54 -0
  28. package/commands/status.js +50 -5
  29. package/commands/stripe.js +38 -0
  30. package/commands/supabase.js +39 -0
  31. package/commands/task.js +935 -71
  32. package/commands/truth.js +29 -3
  33. package/commands/unknowns.js +627 -0
  34. package/commands/update.js +44 -0
  35. package/commands/vercel.js +38 -0
  36. package/commands/worktree.js +68 -10
  37. package/commands/write.js +399 -0
  38. package/lib/auto-accept-certified.js +70 -19
  39. package/lib/autoland.js +39 -3
  40. package/lib/fleet.js +250 -9
  41. package/lib/memory-view.js +14 -5
  42. package/lib/mission-runtime-loop.js +7 -0
  43. package/lib/official-cli-integration.js +174 -0
  44. package/lib/outbound-send-gate.js +165 -0
  45. package/lib/permission-grants.js +293 -0
  46. package/lib/review-integrity.js +147 -0
  47. package/lib/runner-command.js +23 -0
  48. package/lib/task-db.js +220 -7
  49. package/lib/task-proof.js +20 -0
  50. package/lib/task-receipt.js +93 -0
  51. package/package.json +1 -1
  52. package/utils/update-check.js +27 -6
  53. package/atris/learnings.jsonl +0 -1
package/commands/task.js CHANGED
@@ -7,10 +7,18 @@ const fs = require('fs');
7
7
  const http = require('http');
8
8
  const path = require('path');
9
9
  const os = require('os');
10
- const { taskProofState, buildVerifiedProof } = require('../lib/task-proof');
11
- const { evaluateAutoAccept, parseVerifyCommand, runVerifyCommand, DENIED_TAGS } = require('../lib/auto-accept-certified');
10
+ const { taskProofState } = require('../lib/task-proof');
11
+ const {
12
+ evaluateAutoAccept,
13
+ isAgentCertified,
14
+ isAutoCertifyVerifyCommandAllowed,
15
+ parseVerifyCommand,
16
+ runVerifyCommand,
17
+ DENIED_TAGS,
18
+ } = require('../lib/auto-accept-certified');
12
19
  const { extractReceiptEvidence } = require('../lib/receipt-evidence');
13
20
  const escapeRegExp = require('../lib/escape-regexp');
21
+ const reviewIntegrity = require('../lib/review-integrity');
14
22
  const {
15
23
  normalizeOwnerSlug,
16
24
  resolveFunctionalOwner: resolveFunctionalTaskOwner,
@@ -111,6 +119,13 @@ function taskUsageText() {
111
119
  return `
112
120
  atris task - durable local task state (SQLite, gitignored)
113
121
 
122
+ golden path (zero human turns):
123
+ atris task delegate "fix the login bug" --to <member>
124
+ atris task claim <id> --as <member>
125
+ ... build ...
126
+ atris task ready <id> --verify
127
+ atris autoland tick # second check runs, task lands
128
+
114
129
  atris task Show the task desk
115
130
  atris task new "<title>" Create a task
116
131
  atris task next [--tag <tag>] [--create-next]
@@ -120,6 +135,8 @@ atris task - durable local task state (SQLite, gitignored)
120
135
  atris task chat <id> "<message>" [--goal "..."] Refine a task chat + working goal
121
136
  atris task ready <id> --proof "..." Agent proof ready; native goal can complete
122
137
  atris task ready <id> --verify "<cmd>" Run <cmd>; only ready if it exits 0 (executed proof)
138
+ Writes atris/runs/ receipt (pass or fail), folds path into proof
139
+ atris task receipt <id> --verify "<cmd>" Run <cmd> and write an atris/runs/ receipt without going to ready
123
140
  atris task plan-preview "<purpose>" [--tag <tag>] [--owner <member>] [--task <id>]
124
141
  Show the plain Plan before work starts
125
142
  atris task ready <id> --proof "..." [--changed "..." --checked "..." --saved "..." --try "..."]
@@ -133,20 +150,23 @@ atris task - durable local task state (SQLite, gitignored)
133
150
  Human accepts proof, marks done; --public also publishes AgentXP
134
151
  atris task certify-verified [--dry-run] [--limit <n>] [--as <actor>]
135
152
  Re-run the runnable check named in each Review proof as a second actor; passing rows certify (denied lanes and check-less rows wait for a human)
136
- atris task auto-accept-certified --dry-run [--strict-verify] [--limit <n>]
153
+ atris task auto-accept-certified --dry-run [--strict-verify] [--all] [--limit <n>]
137
154
  Preview certified Review rows; live accept needs --confirm-human-accept --as <human>
155
+ atris task sweep --auto-accept [--json] Auto-accept verified Review rows; protected lanes wait for human
138
156
  atris task revise <id> --note "..." Send reviewed work back to Do
139
157
 
140
158
  atris task add "<title>" [--tag <tag>] [--goal-id <id>] Create a task
141
- atris task delegate "<title>" [--to <member>] [--executed-by <engine>] [--goal-id <id>] Create assigned work
159
+ atris task delegate "<title>" [--to <member>] [--executed-by <engine>] [--goal-id <id>] [--tag <tag>] Create assigned work
142
160
  atris task plan <id> --goal "..." --exit "..." --proof-needed "..."
143
161
  Record a task-owned Plan stage
144
162
  atris task do <id> --as <owner> --first-move "..."
145
163
  Start task-owned Do work from the plan
146
164
  atris task backlog <id> [--reason "..."] Move a planned open task back to Backlog
147
165
  atris task clear-plan --yes Move all planned open tasks back to Backlog
148
- atris task day [--json] Show today's owner-grouped task list
149
- atris task list [--all] [--status <s>] List tasks (default: this workspace)
166
+ atris task day [--all] [--everywhere] [--json] show today's owner-grouped task list
167
+ --all stays in this workspace; --everywhere spans workspaces
168
+ atris task list [--all] [--everywhere] [--status <s>]
169
+ list tasks in this workspace; --everywhere spans workspaces
150
170
  atris task claim <id> [--as <owner>] Atomic claim
151
171
  atris task release <id> [--as <owner>] Release your own mistaken claim back to open
152
172
  atris task capabilities [--json] Read-only task CLI/API capability contract
@@ -163,6 +183,9 @@ atris task - durable local task state (SQLite, gitignored)
163
183
  Advance the scoped current task one safe step
164
184
  review-state lanes: needs-agent, continue-work, human-accept-waiting, certified
165
185
  atris task note <id> "<message>" Append dialogue/context to a task
186
+ atris task tag <id> --add <tag> [--remove <tag>]
187
+ Update tags on an existing task (e.g. --add needs-human to hold it
188
+ from sweep + fleet staffing); logs a task_tags_updated event
166
189
  atris task show <id> [--json] Show a task card + dialogue
167
190
  atris task inspect <id> --fields review,status,title [--json]
168
191
  Field-selectable task state (review metadata, status, title, owner, tag)
@@ -170,6 +193,11 @@ atris task - durable local task state (SQLite, gitignored)
170
193
  atris task step <id> [--json] Refine chat, then advance one safe Plan/Do/Review step
171
194
  atris task done <id> --proof "..." Mark complete with proof
172
195
  atris task done <id> --failed [--proof "..."] Mark failed, optionally reviewed
196
+ atris task archive <id> --reason "..." [--from-failed]
197
+ Sweep off-roadmap/duplicate work as archived (not failed);
198
+ --from-failed opts in to relabel a fail-closed row (never done)
199
+ atris task relabel-archived [--dry-run|--apply]
200
+ One-time OBL-1622 migration: relabel June-10 backlog-reset rows failed -> archived
173
201
  atris task finish <id> --proof "..." Legacy alias for done with proof
174
202
  atris task review <id> --reward <n> [--verify "<cmd>"]
175
203
  Write review event + RSI episode
@@ -183,10 +211,13 @@ atris task - durable local task state (SQLite, gitignored)
183
211
  atris task sync --dry-run Plan cloud/Swarlo task sync writes
184
212
  atris task import <file> One-shot import from TODO.md
185
213
  atris task lineage <id> [--json] Show endgame -> tasks -> commits chain
186
- atris task events [id] [--limit <n>] Print recent task events
187
- atris task events --all Print the full append-only ledger
188
- atris task export [--out <file>] Write web/desktop JSON projection
189
- atris task render [--out <file>] Regenerate compact TODO.md view from state
214
+ atris task events [id] [--limit <n>] print recent task events
215
+ atris task events --all print the full current-workspace ledger
216
+ atris task events --everywhere print the full ledger across workspaces
217
+ atris task export [--all] [--everywhere] [--out <file>]
218
+ write web/desktop JSON projection
219
+ atris task render [--all] [--everywhere] [--out <file>]
220
+ regenerate compact TODO.md view from state
190
221
  atris task where Print db path + workspace scope
191
222
  atris task help This help
192
223
 
@@ -226,6 +257,15 @@ function hasFlag(args, name) {
226
257
  return args.indexOf(name) !== -1;
227
258
  }
228
259
 
260
+ function taskScopeEverywhere(args = [], options = {}) {
261
+ if (options.everywhere !== undefined) return Boolean(options.everywhere);
262
+ return hasFlag(args, '--everywhere');
263
+ }
264
+
265
+ function scopedWorkspaceRoot(taskDb, args = [], options = {}) {
266
+ return taskScopeEverywhere(args, options) ? null : taskDb.workspaceRoot();
267
+ }
268
+
229
269
  function hasEmptyFlagValue(args, name) {
230
270
  const i = args.indexOf(name);
231
271
  return i !== -1 && args[i + 1] === '';
@@ -462,10 +502,11 @@ function positional(args) {
462
502
  });
463
503
  }
464
504
 
465
- function writeDefaultProjection(taskDb, db, { all = false } = {}) {
505
+ function writeDefaultProjection(taskDb, db, options = {}) {
506
+ const workspaceRoot = scopedWorkspaceRoot(taskDb, [], options);
466
507
  const projection = enrichTaskProjection(taskDb.taskProjection(db, {
467
- workspaceRoot: all ? null : taskDb.workspaceRoot(),
468
- limit: 500,
508
+ workspaceRoot,
509
+ limit: options.all ? null : 500,
469
510
  }));
470
511
  const outPath = path.resolve(path.join('.atris', 'state', 'tasks.projection.json'));
471
512
  fs.mkdirSync(path.dirname(outPath), { recursive: true });
@@ -692,6 +733,9 @@ function certifiedReviewNextAction(nextTaskTitle) {
692
733
  }
693
734
 
694
735
  function proofBoundaryBlockedEvaluation(task) {
736
+ // strictVerify stays off here: this is a render-path probe for the boundary
737
+ // reason only, and the default-true strict mode would spawn the verify
738
+ // subprocess for every certified row just to draw the desk.
695
739
  const evaluation = evaluateAutoAccept(task, { strictVerify: false, minPasses: 0 });
696
740
  return evaluation && evaluation.reason === 'proof_unmerged_or_draft_pr_boundary'
697
741
  ? evaluation
@@ -1057,11 +1101,65 @@ function taskReviewSavedText(task, review = {}, ref = taskRef(task)) {
1057
1101
  return `Work record saved as ${ref}.`;
1058
1102
  }
1059
1103
 
1104
+ function cleanReviewProofText(value) {
1105
+ const text = String(value || '').trim();
1106
+ return text || null;
1107
+ }
1108
+
1109
+ function reviewMessageLooksLikeProof(value) {
1110
+ const text = cleanReviewProofText(value);
1111
+ return Boolean(text && /\b(?:proof|verified|verifier|passed|receipt|git diff --check|node --test|npm test|pnpm test|yarn test|pytest|typecheck)\b/i.test(text));
1112
+ }
1113
+
1114
+ function taskReviewEventProof(task) {
1115
+ const events = Array.isArray(task.events) ? task.events : [];
1116
+ for (const event of events.slice().reverse()) {
1117
+ const payload = event && event.payload && typeof event.payload === 'object' ? event.payload : {};
1118
+ const explicit = cleanReviewProofText(payload.proof || payload.verify);
1119
+ if (explicit) return explicit;
1120
+ const message = cleanReviewProofText(payload.content || payload.chat_packet || payload.stage_packet);
1121
+ if (message && reviewMessageLooksLikeProof(message)) return message;
1122
+ }
1123
+ const messages = Array.isArray(task.messages) ? task.messages : [];
1124
+ for (const message of messages.slice().reverse()) {
1125
+ const content = cleanReviewProofText(message && message.content);
1126
+ if (content && reviewMessageLooksLikeProof(content)) return content;
1127
+ }
1128
+ return null;
1129
+ }
1130
+
1131
+ function taskReviewProofFallback(task, payload = {}) {
1132
+ if (!task || task.status !== 'review') return null;
1133
+ const metadata = task.metadata || {};
1134
+ return cleanReviewProofText(metadata.latest_agent_proof)
1135
+ || cleanReviewProofText(payload.proof)
1136
+ || cleanReviewProofText(metadata.proof)
1137
+ || cleanReviewProofText(metadata.verify)
1138
+ || cleanReviewProofText(metadata.latest_agent_verify)
1139
+ || cleanReviewProofText(payload.verify)
1140
+ || taskReviewEventProof(task);
1141
+ }
1142
+
1143
+ function reviewReceiptPath(proofText, root) {
1144
+ const evidence = extractReceiptEvidence(proofText, root);
1145
+ if (!evidence) return null;
1146
+ return evidence.receipts?.[0]?.path || evidence.missing?.[0] || null;
1147
+ }
1148
+
1149
+ function withReviewReceiptPath(review, root) {
1150
+ if (!review) return null;
1151
+ return {
1152
+ ...review,
1153
+ receipt_path: reviewReceiptPath(review.proof, root),
1154
+ };
1155
+ }
1156
+
1060
1157
  function taskReviewSummary(task) {
1061
1158
  const reviewed = (task.events || []).slice().reverse().find(e => e.event_type === 'reviewed' || e.event_type === 'proof_ready' || e.event_type === 'revision_requested');
1062
1159
  const payload = reviewed && reviewed.payload || {};
1063
1160
  const metadata = task.metadata || {};
1064
- if (!reviewed && !metadata.approval_status && !metadata.agent_review_pass_count && !metadata.human_revision_count && !metadata.agent_certified) return null;
1161
+ const fallbackProof = taskReviewProofFallback(task, payload);
1162
+ if (!reviewed && !metadata.approval_status && !metadata.agent_review_pass_count && !metadata.human_revision_count && !metadata.agent_certified && !fallbackProof) return null;
1065
1163
  if (reviewed && reviewed.event_type === 'revision_requested') {
1066
1164
  const review = {
1067
1165
  summary: reviewSummary(task, payload),
@@ -1101,7 +1199,7 @@ function taskReviewSummary(task) {
1101
1199
  const review = {
1102
1200
  summary: reviewSummary(task, payload),
1103
1201
  reward: reviewed && reviewed.event_type === 'reviewed' && payload.reward !== undefined ? payload.reward : null,
1104
- proof: readyField('proof', 'latest_agent_proof'),
1202
+ proof: readyField('proof', 'latest_agent_proof') || fallbackProof,
1105
1203
  lesson: readyField('lesson', 'latest_agent_lesson'),
1106
1204
  next_task: readyField('next_task', 'latest_agent_next_task'),
1107
1205
  approval_status: metadata.approval_status || (task.status === 'review' ? 'pending' : null),
@@ -1132,6 +1230,7 @@ function taskReviewInspectMetadata(task) {
1132
1230
  human_revision_count: review.human_revision_count || null,
1133
1231
  human_revision_note: review.human_revision_note || null,
1134
1232
  reward: review.reward ?? null,
1233
+ receipt_path: review.receipt_path || null,
1135
1234
  };
1136
1235
  }
1137
1236
 
@@ -1317,7 +1416,7 @@ function enrichTaskProjection(projection) {
1317
1416
  const parentLinkType = parentFromParentId ? 'parent_task_id' : parentFromGoalId ? 'goal_id' : null;
1318
1417
  const parentId = parent ? parent.id : metadata.parent_task_id || null;
1319
1418
  const childTasks = children.get(task.id) || [];
1320
- const review = taskReviewSummary(task);
1419
+ const review = withReviewReceiptPath(taskReviewSummary(task), root);
1321
1420
  return {
1322
1421
  ...task,
1323
1422
  objective: taskObjective(task, parent, goalSource.goals, { parentLinkType, baseObjectives }),
@@ -1502,6 +1601,44 @@ function latestTaskEvent(task) {
1502
1601
  return events.length ? events[events.length - 1] : null;
1503
1602
  }
1504
1603
 
1604
+ function verifiedProofCommand(proof) {
1605
+ const match = String(proof || '').match(/\[verified\]\s+`([^`]+)`\s+passed\s+\(exit 0\)/i);
1606
+ return match ? String(match[1] || '').trim() : '';
1607
+ }
1608
+
1609
+ function autoCertifyCommandCandidatesForTask(task) {
1610
+ const metadata = task && task.metadata || {};
1611
+ const review = task && task.review || {};
1612
+ const proof = String(review.proof || metadata.latest_agent_proof || '');
1613
+ const candidates = [
1614
+ metadata.verify,
1615
+ metadata.latest_agent_verify,
1616
+ verifiedProofCommand(review.proof),
1617
+ verifiedProofCommand(metadata.latest_agent_proof),
1618
+ ...taskReviewEvidenceCommands(proof),
1619
+ ].map(value => String(value || '').trim()).filter(Boolean);
1620
+ const seen = new Set();
1621
+ return candidates.filter((candidate) => {
1622
+ const key = candidate.toLowerCase();
1623
+ if (seen.has(key)) return false;
1624
+ seen.add(key);
1625
+ return true;
1626
+ });
1627
+ }
1628
+
1629
+ function reviewBlockerForTask(task) {
1630
+ const ref = taskRef(task);
1631
+ const candidates = autoCertifyCommandCandidatesForTask(task);
1632
+ const safe = candidates.find(command => isAutoCertifyVerifyCommandAllowed(command));
1633
+ const unsafe = candidates.find(command => !isAutoCertifyVerifyCommandAllowed(command));
1634
+ const reason = !safe && unsafe ? 'verify_command_not_allowed' : 'needs_second_actor_review';
1635
+ return {
1636
+ reason,
1637
+ verify_command: (!safe && unsafe) ? unsafe : (safe || candidates[0] || null),
1638
+ next_command: `atris task review-chat ${ref} --as codex-review`,
1639
+ };
1640
+ }
1641
+
1505
1642
  function reviewHandoffForTask(task, { suppressExistingFollowUp = false, hasExistingReviewFollowUp = null } = {}) {
1506
1643
  const review = task && task.review || {};
1507
1644
  if (task && task.status !== 'review') return null;
@@ -1522,6 +1659,11 @@ function reviewHandoffForTask(task, { suppressExistingFollowUp = false, hasExist
1522
1659
  handoff.reason = proofBoundary.reason;
1523
1660
  handoff.next_action_detail = proofBoundary.next_action || null;
1524
1661
  handoff.revise_command = `atris task revise ${taskRef(task)} --note "<replace stale PR proof with merged proof or move back to Do>"`;
1662
+ } else if (!agentCertified) {
1663
+ const blocker = reviewBlockerForTask(task);
1664
+ handoff.reason = blocker.reason;
1665
+ handoff.next_action_detail = blocker.verify_command || null;
1666
+ handoff.review_chat_command = blocker.next_command;
1525
1667
  } else if (agentCertified && nextTask && !hasExistingFollowUp) {
1526
1668
  handoff.next_task = nextTask;
1527
1669
  handoff.continue_work_command = continueWorkCommandForTask(task);
@@ -1980,6 +2122,7 @@ function compactTaskForStatus(task) {
1980
2122
  if (task.review.proof) review.proof = clipStatusText(task.review.proof, 180);
1981
2123
  if (task.review.lesson) review.lesson = clipStatusText(task.review.lesson, 180);
1982
2124
  if (task.review.next_task) review.next_task = clipStatusText(task.review.next_task, 140);
2125
+ if (Object.prototype.hasOwnProperty.call(task.review, 'receipt_path')) review.receipt_path = task.review.receipt_path || null;
1983
2126
  if (task.review.approval_status) review.approval_status = task.review.approval_status;
1984
2127
  if (task.review.agent_review_pass_count) review.agent_review_pass_count = task.review.agent_review_pass_count;
1985
2128
  if (task.review.agent_certified) review.agent_certified = task.review.agent_certified;
@@ -3055,6 +3198,7 @@ function taskCapabilitiesCheckReport(taskDb, db, args = [], options = {}) {
3055
3198
  const owner = options.owner || flag(args, '--as') || flag(args, '--owner') || DEFAULT_OWNER;
3056
3199
  const reviewer = reviewActor(options.reviewer || flag(args, '--reviewer') || flag(args, '--as-reviewer') || 'codex-review');
3057
3200
  const all = options.all !== undefined ? Boolean(options.all) : hasFlag(args, '--all');
3201
+ const everywhere = taskScopeEverywhere(args, options);
3058
3202
  const limit = options.limit !== undefined ? options.limit : taskQueueLimit(args);
3059
3203
  const scope = normalizeTaskQueueScope(options.scope || taskQueueScopeFromArgs(args));
3060
3204
  const standalone = taskCapabilitiesContract();
@@ -3062,6 +3206,7 @@ function taskCapabilitiesCheckReport(taskDb, db, args = [], options = {}) {
3062
3206
  owner,
3063
3207
  reviewer,
3064
3208
  all,
3209
+ everywhere,
3065
3210
  limit,
3066
3211
  scope,
3067
3212
  });
@@ -3326,7 +3471,11 @@ function selectTaskForCurrent(projection, { owner = DEFAULT_OWNER, scope = {}, h
3326
3471
  if (reviewNeedsAgent) return { task: reviewNeedsAgent, reason: 'review_needs_agent_verification' };
3327
3472
  const reviewProofBoundaryBlocked = columns.review.find(task => reviewHandoffForTask(task, { suppressExistingFollowUp: true, hasExistingReviewFollowUp })?.next_action === PROOF_BOUNDARY_BLOCKED_ACTION);
3328
3473
  if (reviewProofBoundaryBlocked) return { task: reviewProofBoundaryBlocked, reason: 'review_proof_boundary_blocked' };
3329
- const planQueue = normalizedScope.goal_id ? sortTasksOldestFirst(columns.plan) : columns.plan;
3474
+ // Scoped selection (goal_id, tag, status, or review_state) implies a sequenced
3475
+ // work stream (e.g. golden-path "pass 1a" before "pass 2"): earlier-created
3476
+ // tasks must win over newer ones, not the newest-first default used for the
3477
+ // unscoped desk view.
3478
+ const planQueue = !taskQueueScopeIsEmpty(normalizedScope) ? sortTasksOldestFirst(columns.plan) : columns.plan;
3330
3479
  const planReady = planQueue[0];
3331
3480
  if (planReady) return { task: planReady, reason: 'plan_ready' };
3332
3481
  const backlogIdea = columns.backlog[0];
@@ -3392,18 +3541,20 @@ function buildTaskCurrent(taskDb, db, args = [], options = {}) {
3392
3541
  const owner = options.owner || flag(args, '--as') || flag(args, '--owner') || DEFAULT_OWNER;
3393
3542
  const reviewer = reviewActor(options.reviewer || flag(args, '--reviewer') || flag(args, '--as-reviewer') || 'codex-review');
3394
3543
  const all = options.all !== undefined ? Boolean(options.all) : hasFlag(args, '--all');
3544
+ const everywhere = taskScopeEverywhere(args, options);
3545
+ const workspaceRoot = scopedWorkspaceRoot(taskDb, args, { everywhere });
3395
3546
  const limit = options.limit !== undefined ? options.limit : taskQueueLimit(args);
3396
3547
  const scope = normalizeTaskQueueScope(options.scope || taskQueueScopeFromArgs(args));
3397
- const { projection, outPath } = writeDefaultProjection(taskDb, db, { all });
3548
+ const { projection, outPath } = writeDefaultProjection(taskDb, db, { all, everywhere });
3398
3549
  const hasExistingReviewFollowUp = buildReviewFollowUpChildPredicate(
3399
3550
  taskDb,
3400
3551
  db,
3401
- all ? null : taskDb.workspaceRoot(),
3552
+ workspaceRoot,
3402
3553
  );
3403
3554
  const hasPendingReviewChat = buildPendingReviewChatPredicate(
3404
3555
  taskDb,
3405
3556
  db,
3406
- all ? null : taskDb.workspaceRoot(),
3557
+ workspaceRoot,
3407
3558
  );
3408
3559
  return {
3409
3560
  projection,
@@ -3676,12 +3827,14 @@ function taskReviewLaneDrainReport(taskDb, db, args = [], options = {}) {
3676
3827
  const owner = options.owner || flag(args, '--as') || flag(args, '--owner') || DEFAULT_OWNER;
3677
3828
  const reviewer = reviewActor(options.reviewer || flag(args, '--reviewer') || flag(args, '--as-reviewer') || 'codex-review');
3678
3829
  const all = options.all !== undefined ? Boolean(options.all) : hasFlag(args, '--all');
3830
+ const everywhere = taskScopeEverywhere(args, options);
3679
3831
  const limit = options.limit !== undefined ? options.limit : taskQueueLimit(args);
3680
3832
  const scope = normalizeTaskQueueScope(options.scope || taskQueueScopeFromArgs(args));
3681
3833
  const capabilitiesCheck = taskCapabilitiesCheckReport(taskDb, db, [], {
3682
3834
  owner,
3683
3835
  reviewer,
3684
3836
  all,
3837
+ everywhere,
3685
3838
  limit,
3686
3839
  scope,
3687
3840
  });
@@ -3689,6 +3842,7 @@ function taskReviewLaneDrainReport(taskDb, db, args = [], options = {}) {
3689
3842
  owner,
3690
3843
  reviewer,
3691
3844
  all,
3845
+ everywhere,
3692
3846
  limit,
3693
3847
  scope,
3694
3848
  excludeTaskIds: options.excludeTaskIds,
@@ -3749,6 +3903,7 @@ function taskReviewLaneActOptionsFromArgs(args = []) {
3749
3903
  owner: flag(args, '--as') || flag(args, '--owner') || DEFAULT_OWNER,
3750
3904
  reviewer: reviewActor(flag(args, '--reviewer') || flag(args, '--as-reviewer') || 'codex-review'),
3751
3905
  all: hasFlag(args, '--all'),
3906
+ everywhere: hasFlag(args, '--everywhere'),
3752
3907
  limit: taskQueueLimit(args),
3753
3908
  scope: taskQueueScopeFromArgs(args),
3754
3909
  dryRun: hasFlag(args, '--dry-run'),
@@ -3766,6 +3921,7 @@ function taskReviewLaneActOptionsFromBody(body = {}, searchParams = new URLSearc
3766
3921
  owner: String(queryOwner || body.owner || body.as || body.actor || DEFAULT_OWNER),
3767
3922
  reviewer: reviewActor(queryReviewer || body.reviewer || body.review_actor || body.reviewActor || 'codex-review'),
3768
3923
  all: searchParams.get('all') === '1' || searchParams.get('all') === 'true' || Boolean(body.all),
3924
+ everywhere: searchParams.get('everywhere') === '1' || searchParams.get('everywhere') === 'true' || Boolean(body.everywhere),
3769
3925
  limit: Number.isFinite(limit) && limit > 0 ? Math.floor(limit) : 8,
3770
3926
  scope: mergeTaskQueueScopes(queryScope, bodyScope),
3771
3927
  dryRun: searchParams.get('dry_run') === '1'
@@ -3831,6 +3987,7 @@ function taskReviewLaneAct(taskDb, db, options = {}) {
3831
3987
  owner,
3832
3988
  reviewer,
3833
3989
  all: Boolean(options.all),
3990
+ everywhere: Boolean(options.everywhere),
3834
3991
  limit: options.limit !== undefined ? options.limit : 8,
3835
3992
  scope,
3836
3993
  excludeTaskIds: options.excludeTaskIds,
@@ -4069,6 +4226,7 @@ function taskReviewLaneLoop(taskDb, db, options = {}) {
4069
4226
  owner,
4070
4227
  reviewer,
4071
4228
  all: Boolean(options.all),
4229
+ everywhere: Boolean(options.everywhere),
4072
4230
  limit: options.limit !== undefined ? options.limit : 8,
4073
4231
  scope,
4074
4232
  excludeTaskIds,
@@ -4121,6 +4279,7 @@ function taskReviewLaneLoop(taskDb, db, options = {}) {
4121
4279
  owner,
4122
4280
  reviewer,
4123
4281
  all: Boolean(options.all),
4282
+ everywhere: Boolean(options.everywhere),
4124
4283
  limit: options.limit !== undefined ? options.limit : 8,
4125
4284
  scope,
4126
4285
  excludeTaskIds,
@@ -4308,6 +4467,7 @@ function taskReviewLaneRun(taskDb, db, options = {}) {
4308
4467
  owner,
4309
4468
  reviewer,
4310
4469
  all: Boolean(options.all),
4470
+ everywhere: Boolean(options.everywhere),
4311
4471
  limit: options.limit !== undefined ? options.limit : 8,
4312
4472
  scope,
4313
4473
  excludeTaskIds,
@@ -4488,6 +4648,19 @@ function reviewQueueItem(task, root = process.cwd(), evidence = undefined) {
4488
4648
  return item;
4489
4649
  }
4490
4650
 
4651
+ function blockedReviewQueueItem(task, root = process.cwd()) {
4652
+ const item = reviewQueueItem(task, root);
4653
+ const blocker = reviewBlockerForTask(task);
4654
+ item.queue_role = 'blocked';
4655
+ item.reason = blocker.reason;
4656
+ item.blocked_reason = blocker.reason;
4657
+ item.next_command = blocker.next_command;
4658
+ item.verify_command = blocker.verify_command;
4659
+ item.accept_command = null;
4660
+ item.land_command = null;
4661
+ return item;
4662
+ }
4663
+
4491
4664
  function reviewQueueHygiene(tasks) {
4492
4665
  const genericContinuations = (tasks || []).map(task => {
4493
4666
  const issues = genericContinuationIssues(task);
@@ -4526,7 +4699,10 @@ function taskReviewQueue(projection, args = []) {
4526
4699
  const ordered = [...certified].sort((a, b) =>
4527
4700
  evidenceRiskRank(evidenceByTaskId.get(b.id)) - evidenceRiskRank(evidenceByTaskId.get(a.id))
4528
4701
  || Number(b.updated_at || 0) - Number(a.updated_at || 0));
4529
- const items = ordered.slice(0, limit).map((task) => reviewQueueItem(task, root, evidenceByTaskId.get(task.id)));
4702
+ const certifiedItems = ordered.slice(0, limit).map((task) => reviewQueueItem(task, root, evidenceByTaskId.get(task.id)));
4703
+ const blockedLimit = reviewQueueLimit(args, blocking.length);
4704
+ const blockedItems = blocking.slice(0, blockedLimit).map((task) => blockedReviewQueueItem(task, root));
4705
+ const items = [...certifiedItems, ...blockedItems];
4530
4706
  return {
4531
4707
  schema: 'atris.task_review_queue.v1',
4532
4708
  generated_at: projection.generated_at,
@@ -4537,7 +4713,8 @@ function taskReviewQueue(projection, args = []) {
4537
4713
  evidence_passing: certified.filter((task) => evidenceByTaskId.get(task.id)?.all_passing).length,
4538
4714
  blocking: blocking.length,
4539
4715
  proof_boundary_blocked: proofBoundaryBlocked.length,
4540
- shown: items.length,
4716
+ shown: certifiedItems.length,
4717
+ blocking_shown: blockedItems.length,
4541
4718
  },
4542
4719
  hygiene: reviewQueueHygiene(reviewTasks),
4543
4720
  items,
@@ -4635,11 +4812,13 @@ function cmdReviews(args) {
4635
4812
  }
4636
4813
  console.log('READY FOR APPROVAL');
4637
4814
  console.log(`${queue.counts.certified} ready for approval / ${queue.counts.blocking} need one more check / ${queue.counts.review} total waiting`);
4638
- if (!queue.items.length) {
4815
+ const approvalItems = queue.items.filter(item => item.queue_role !== 'blocked');
4816
+ const blockedItems = queue.items.filter(item => item.queue_role === 'blocked');
4817
+ if (!approvalItems.length && !blockedItems.length) {
4639
4818
  console.log('Nothing is ready for approval.');
4640
4819
  return;
4641
4820
  }
4642
- queue.items.forEach((item, index) => {
4821
+ approvalItems.forEach((item, index) => {
4643
4822
  const tag = item.tag ? ` [${item.tag}]` : '';
4644
4823
  const passes = item.review_pass_count ? ` (${item.review_pass_count} reviews)` : '';
4645
4824
  const badge = item.evidence?.all_passing ? ' [evidence:passing]' : '';
@@ -4669,6 +4848,10 @@ function cmdReviews(args) {
4669
4848
  else if (item.blocked_accept_reason) console.log(` approve: blocked (${item.blocked_accept_reason})`);
4670
4849
  console.log(` rework: ${item.revise_command}`);
4671
4850
  });
4851
+ for (const item of blockedItems) {
4852
+ console.log('');
4853
+ console.log(`blocked: ${item.display_id || taskRef(item.id)}: ${item.reason}; next: ${item.next_command}`);
4854
+ }
4672
4855
  if (queue.counts.shown < queue.counts.certified) {
4673
4856
  console.log('');
4674
4857
  console.log(`Showing ${queue.counts.shown}/${queue.counts.certified}; rerun with --all for every row or --verbose for proof details.`);
@@ -4867,19 +5050,21 @@ function formatTaskLine(task) {
4867
5050
 
4868
5051
  function cmdStatus(args) {
4869
5052
  const all = hasFlag(args, '--all');
5053
+ const everywhere = taskScopeEverywhere(args);
4870
5054
  const history = hasFlag(args, '--history');
4871
5055
  const taskDb = getTaskDb();
4872
5056
  const db = taskDb.open();
4873
- const compact = writeDefaultProjection(taskDb, db, { all });
5057
+ const workspaceRoot = scopedWorkspaceRoot(taskDb, args, { everywhere });
5058
+ const compact = writeDefaultProjection(taskDb, db, { all, everywhere });
4874
5059
  const projection = history
4875
5060
  ? enrichTaskProjection(taskDb.taskProjection(db, {
4876
- workspaceRoot: all ? null : taskDb.workspaceRoot(),
4877
- limit: 500,
5061
+ workspaceRoot,
5062
+ limit: all ? null : 500,
4878
5063
  includeHistory: true,
4879
5064
  }))
4880
5065
  : compact.projection;
4881
5066
  const outPath = compact.outPath;
4882
- const hasExistingReviewFollowUp = buildReviewFollowUpChildPredicate(taskDb, db, all ? null : taskDb.workspaceRoot());
5067
+ const hasExistingReviewFollowUp = buildReviewFollowUpChildPredicate(taskDb, db, workspaceRoot);
4883
5068
  const status = taskStatusSummary(projection, { history, hasExistingReviewFollowUp });
4884
5069
  if (wantsJson(args)) {
4885
5070
  printJson({
@@ -4936,13 +5121,13 @@ function requireTaskId(taskDb, db, ref, label) {
4936
5121
  }
4937
5122
  }
4938
5123
 
4939
- function workspaceRefRows(taskDb, db, all = false) {
4940
- return taskDb.listTasks(db, { workspaceRoot: all ? null : taskDb.workspaceRoot() });
5124
+ function workspaceRefRows(taskDb, db, options = {}) {
5125
+ return taskDb.listTasks(db, { workspaceRoot: scopedWorkspaceRoot(taskDb, [], options) });
4941
5126
  }
4942
5127
 
4943
5128
  function renderTaskDesk(rows, refRows = rows) {
4944
5129
  const displayRows = getTaskDb().withTaskDisplayRefs(rows, refRows);
4945
- const active = displayRows.filter(r => r.status !== 'done');
5130
+ const active = displayRows.filter(r => r.status !== 'done' && r.status !== 'archived');
4946
5131
  const done = displayRows.filter(r => r.status === 'done');
4947
5132
  if (rows.length === 0) {
4948
5133
  console.log('No tasks yet.');
@@ -5158,9 +5343,10 @@ function taskDayGroups(tasks, { now = Date.now() } = {}) {
5158
5343
 
5159
5344
  function cmdDay(args) {
5160
5345
  const all = hasFlag(args, '--all');
5346
+ const everywhere = taskScopeEverywhere(args);
5161
5347
  const taskDb = getTaskDb();
5162
5348
  const db = taskDb.open();
5163
- const { projection, outPath } = writeDefaultProjection(taskDb, db, { all });
5349
+ const { projection, outPath } = writeDefaultProjection(taskDb, db, { all, everywhere });
5164
5350
  const { groups, staleFailed } = taskDayGroups(projection.tasks || []);
5165
5351
  const counts = {
5166
5352
  active: groups.reduce((sum, group) => sum + group.tasks.length, 0),
@@ -5212,13 +5398,15 @@ function cmdDay(args) {
5212
5398
 
5213
5399
  function cmdHome(args) {
5214
5400
  const all = hasFlag(args, '--all');
5401
+ const everywhere = taskScopeEverywhere(args);
5215
5402
  const taskDb = getTaskDb();
5216
5403
  const db = taskDb.open();
5404
+ const workspaceRoot = scopedWorkspaceRoot(taskDb, args, { everywhere });
5217
5405
  const rows = taskDb.listTasks(db, {
5218
- workspaceRoot: all ? null : taskDb.workspaceRoot(),
5219
- limit: 200,
5406
+ workspaceRoot,
5407
+ limit: all ? null : 200,
5220
5408
  });
5221
- const { projection, outPath } = writeDefaultProjection(taskDb, db, { all });
5409
+ const { projection, outPath } = writeDefaultProjection(taskDb, db, { all, everywhere });
5222
5410
  if (wantsJson(args)) {
5223
5411
  printJson({
5224
5412
  ok: true,
@@ -5235,18 +5423,20 @@ function cmdHome(args) {
5235
5423
 
5236
5424
  function cmdList(args) {
5237
5425
  const all = hasFlag(args, '--all');
5426
+ const everywhere = taskScopeEverywhere(args);
5238
5427
  const status = flag(args, '--status');
5239
5428
  const scope = taskQueueScopeFromArgs(args);
5240
5429
  const scoped = !taskQueueScopeIsEmpty(scope);
5241
5430
  const taskDb = getTaskDb();
5242
5431
  const db = taskDb.open();
5432
+ const workspaceRoot = scopedWorkspaceRoot(taskDb, args, { everywhere });
5243
5433
  const rawRows = taskDb.listTasks(db, {
5244
- workspaceRoot: all ? null : taskDb.workspaceRoot(),
5434
+ workspaceRoot,
5245
5435
  status: typeof status === 'string' ? status : null,
5246
- limit: scoped ? null : 200,
5436
+ limit: scoped || all ? null : 200,
5247
5437
  });
5248
5438
  const rows = filterTasksByScope(rawRows, scope);
5249
- const displayRows = taskDb.withTaskDisplayRefs(rows, workspaceRefRows(taskDb, db, all));
5439
+ const displayRows = taskDb.withTaskDisplayRefs(rows, workspaceRefRows(taskDb, db, { everywhere }));
5250
5440
  if (wantsJson(args)) {
5251
5441
  printJson({ ok: true, action: 'list', scope: normalizeTaskQueueScope(scope), tasks: displayRows });
5252
5442
  return;
@@ -5263,12 +5453,49 @@ function cmdList(args) {
5263
5453
  }
5264
5454
  }
5265
5455
 
5456
+ // judge != worker support: reserved system names (autoland-verifier and co)
5457
+ // can never be assumed via --as, and unknown names warn by default or fail
5458
+ // under ATRIS_ACTOR_VALIDATION=enforce. Only explicit --as values are
5459
+ // checked; the DEFAULT_OWNER fallback stays silent.
5460
+ function guardExplicitActor(command, value) {
5461
+ if (typeof value !== 'string' || !value.trim()) return;
5462
+ const check = reviewIntegrity.validateActor(value, { root: process.cwd() });
5463
+ if (!check.ok) {
5464
+ if (check.reason === 'reserved_actor') {
5465
+ console.error(`${command}: reserved_actor: '${value}' is a system actor and cannot be used with --as`);
5466
+ } else {
5467
+ console.error(`${command}: actor_not_on_roster: '${value}' is not a workspace member or engine (actor validation is enforced)`);
5468
+ }
5469
+ process.exit(1);
5470
+ }
5471
+ if (check.reason === 'actor_not_on_roster' && check.mode === 'warn') {
5472
+ console.error(`Warning: '${value}' is not a workspace member or engine; reviews under unknown names weaken the audit trail.`);
5473
+ }
5474
+ }
5475
+
5476
+ // A claim against an already-done task burns a whole dispatch when a
5477
+ // rendered view (atris/TODO.md) is stale: the agent claims, builds, then
5478
+ // discovers the work was already done. Point straight at a real open task
5479
+ // instead of just reporting the failure, straight from the live projection
5480
+ // (never the rendered file), preferring the same tag when one is open.
5481
+ function suggestNextClaimableTask(projection, { excludeId = null, tag = '' } = {}) {
5482
+ const open = (projection && projection.tasks || []).filter((t) => t && t.status === 'open' && t.id !== excludeId);
5483
+ if (!open.length) return null;
5484
+ const normalizedTag = String(tag || '').trim().toLowerCase();
5485
+ if (normalizedTag) {
5486
+ const sameTag = open.find((t) => String(t.tag || '').trim().toLowerCase() === normalizedTag);
5487
+ if (sameTag) return sameTag;
5488
+ }
5489
+ return open[0];
5490
+ }
5491
+
5266
5492
  function cmdClaim(args) {
5267
5493
  const pos = positional(args);
5268
5494
  const id = pos[0];
5269
5495
  if (!id) {
5270
5496
  failTask('atris task claim', 'missing_id', 'id required');
5271
5497
  }
5498
+ guardExplicitActor('atris task claim', flag(args, '--as'));
5272
5499
  const owner = flag(args, '--as') || DEFAULT_OWNER;
5273
5500
  const taskDb = getTaskDb();
5274
5501
  const db = taskDb.open();
@@ -5292,6 +5519,12 @@ function cmdClaim(args) {
5292
5519
  const recoveryCommand = result.reason === 'already_claimed' && result.claimed_by
5293
5520
  ? `atris task release ${id} --as ${result.claimed_by}`
5294
5521
  : null;
5522
+ let nextClaimable = null;
5523
+ if (result.reason === 'already_done') {
5524
+ const doneRow = taskDb.getTask(db, taskId);
5525
+ const { projection } = writeDefaultProjection(taskDb, db);
5526
+ nextClaimable = suggestNextClaimableTask(projection, { excludeId: taskId, tag: doneRow && doneRow.tag });
5527
+ }
5295
5528
  if (wantsJson(args)) {
5296
5529
  printJson({
5297
5530
  ok: false,
@@ -5299,12 +5532,16 @@ function cmdClaim(args) {
5299
5532
  reason: result.reason,
5300
5533
  claimed_by: result.claimed_by || null,
5301
5534
  recovery_command: recoveryCommand,
5535
+ next_claimable: nextClaimable ? { id: nextClaimable.id, ref: taskRef(nextClaimable), tag: nextClaimable.tag || null, title: nextClaimable.title } : null,
5302
5536
  detail: `claim failed: ${result.reason}${result.claimed_by ? ` (held by ${result.claimed_by})` : ''}`,
5303
5537
  });
5304
5538
  process.exit(1);
5305
5539
  }
5306
5540
  console.error(`claim failed: ${result.reason}${result.claimed_by ? ` (held by ${result.claimed_by})` : ''}`);
5307
5541
  if (recoveryCommand) console.error(`Recovery: ${recoveryCommand}`);
5542
+ if (nextClaimable) {
5543
+ console.error(`next claimable: ${taskRef(nextClaimable)} ${String(nextClaimable.title || '').slice(0, 80)} (atris task claim ${taskRef(nextClaimable)} --as ${owner})`);
5544
+ }
5308
5545
  process.exit(1);
5309
5546
  }
5310
5547
  }
@@ -5724,6 +5961,79 @@ function cmdNote(args) {
5724
5961
  console.log(`noted ${taskRef(compactTaskFromProjection(projection, taskId))} v${result.event.version}`);
5725
5962
  }
5726
5963
 
5964
+ // Collect EVERY value for a repeatable flag (flag() only returns the first),
5965
+ // so `--add a --add b` and `--add a,b` both work.
5966
+ function collectFlagValues(args, name) {
5967
+ const values = [];
5968
+ for (let i = 0; i < args.length; i += 1) {
5969
+ if (args[i] === name && i + 1 < args.length && !String(args[i + 1]).startsWith('--')) {
5970
+ values.push(args[i + 1]);
5971
+ }
5972
+ }
5973
+ return values
5974
+ .flatMap((value) => String(value).split(','))
5975
+ .map((value) => value.trim())
5976
+ .filter(Boolean);
5977
+ }
5978
+
5979
+ function cmdTag(args) {
5980
+ const pos = positional(args);
5981
+ const id = pos[0];
5982
+ if (!id) failTask('atris task tag', 'missing_id', 'task id required');
5983
+ const add = collectFlagValues(args, '--add');
5984
+ const remove = collectFlagValues(args, '--remove');
5985
+ if (!add.length && !remove.length) {
5986
+ failTask('atris task tag', 'missing_tags', 'at least one --add <tag> or --remove <tag> required');
5987
+ }
5988
+ const actor = flag(args, '--as') || DEFAULT_OWNER;
5989
+ const taskDb = getTaskDb();
5990
+ const db = taskDb.open();
5991
+ const taskId = requireTaskId(taskDb, db, id, 'atris task tag');
5992
+ const result = taskDb.tagTask(db, { id: taskId, actor: String(actor), add, remove });
5993
+ if (!result.tagged) {
5994
+ if (result.reason === 'no_changes') {
5995
+ const { projection, outPath } = writeDefaultProjection(taskDb, db);
5996
+ const task = compactTaskFromProjection(projection, taskId);
5997
+ if (wantsJson(args)) {
5998
+ printJson({
5999
+ ok: true,
6000
+ action: 'unchanged',
6001
+ task_id: taskId,
6002
+ added: [],
6003
+ removed: [],
6004
+ tags: result.tags || [],
6005
+ projection_path: outPath,
6006
+ task,
6007
+ });
6008
+ return;
6009
+ }
6010
+ console.log(`no change ${taskRef(task)} tags [${(result.tags || []).join(', ')}]`);
6011
+ return;
6012
+ }
6013
+ failTask('atris task tag', result.reason || 'tag_failed', `tag failed: ${result.reason || 'unknown'}`, 1);
6014
+ }
6015
+ const { projection, outPath } = writeDefaultProjection(taskDb, db);
6016
+ const task = compactTaskFromProjection(projection, taskId);
6017
+ if (wantsJson(args)) {
6018
+ printJson({
6019
+ ok: true,
6020
+ action: 'tagged',
6021
+ task_id: taskId,
6022
+ added: result.added,
6023
+ removed: result.removed,
6024
+ tags: result.tags,
6025
+ version: result.event.version,
6026
+ projection_path: outPath,
6027
+ task,
6028
+ });
6029
+ return;
6030
+ }
6031
+ const parts = [];
6032
+ if (result.added.length) parts.push(`+${result.added.join(' +')}`);
6033
+ if (result.removed.length) parts.push(`-${result.removed.join(' -')}`);
6034
+ console.log(`tagged ${taskRef(task)} ${parts.join(' ')} -> [${result.tags.join(', ')}] v${result.event.version}`);
6035
+ }
6036
+
5727
6037
  function cmdChat(args) {
5728
6038
  const pos = positional(args);
5729
6039
  const id = pos[0];
@@ -7839,6 +8149,117 @@ function cmdFinish(args) {
7839
8149
  console.log(`finished ${taskRef(compactTaskFromProjection(projection, taskId))}`);
7840
8150
  }
7841
8151
 
8152
+ // Distinct from `done --failed`: a bulk sweep of duplicates/off-roadmap work
8153
+ // closes the task without ever claiming it did or didn't succeed. Writing
8154
+ // 'failed' here would corrupt the reward signal readers rely on (see
8155
+ // atris/reports/failed-tasks-analysis-2026-07-03.md, cluster 2, OBL-1622).
8156
+ function cmdArchive(args) {
8157
+ const pos = positional(args);
8158
+ const id = pos[0];
8159
+ if (!id) {
8160
+ failTask('atris task archive', 'missing_id', 'id required');
8161
+ }
8162
+ const reason = textFlag(args, ['--reason']);
8163
+ if (!reason) {
8164
+ failTask('atris task archive', 'missing_reason', 'atris task archive requires --reason "<why this is being swept, not failed>"');
8165
+ }
8166
+ const taskDb = getTaskDb();
8167
+ const db = taskDb.open();
8168
+ const taskId = requireTaskId(taskDb, db, id, 'atris task archive');
8169
+ const actor = String(flag(args, '--as') || DEFAULT_OWNER);
8170
+ // Explicit opt-in for sanctioned failed→archived cleanup (e.g. duplicate
8171
+ // loop-tick orphans fail-closed before 'archived' existed). Without the
8172
+ // flag, failed rows stay failed; done rows are never archivable.
8173
+ const fromFailed = hasFlag(args, '--from-failed');
8174
+ const result = taskDb.archiveTask(db, { id: taskId, actor, reason, fromFailed });
8175
+ if (result.archived) {
8176
+ const { projection, outPath } = writeDefaultProjection(taskDb, db);
8177
+ if (wantsJson(args)) {
8178
+ printJson({
8179
+ ok: true,
8180
+ action: 'archived',
8181
+ task_id: taskId,
8182
+ reason,
8183
+ archived_from: result.row && result.row.metadata && result.row.metadata.archived_from || null,
8184
+ projection_path: outPath,
8185
+ task: compactTaskFromProjection(projection, taskId),
8186
+ });
8187
+ return;
8188
+ }
8189
+ const fromNote = result.row && result.row.metadata && result.row.metadata.archived_from
8190
+ ? ` (was ${result.row.metadata.archived_from})`
8191
+ : '';
8192
+ console.log(`archived ${taskRef(compactTaskFromProjection(projection, taskId))}${fromNote}: ${reason}`);
8193
+ } else {
8194
+ const hint = result.reason === 'already_failed'
8195
+ ? ' (use --from-failed to archive a fail-closed duplicate/off-roadmap row)'
8196
+ : '';
8197
+ const detail = `archive failed: ${taskId} ${result.reason}${hint}`;
8198
+ if (wantsJson(args)) {
8199
+ printJson({ ok: false, command: 'atris task archive', reason: result.reason, task_id: taskId, detail });
8200
+ process.exit(1);
8201
+ }
8202
+ console.error(detail);
8203
+ process.exit(1);
8204
+ }
8205
+ }
8206
+
8207
+ // One-time migration for OBL-1622: the 2026-06-10 "first-principles backlog
8208
+ // reset" archived ~125 certified, proof-backed tasks by writing status
8209
+ // 'failed' (no distinct archived status existed yet). This relabels exactly
8210
+ // the rows that carry that reset's metadata marker, using the same
8211
+ // UPDATE+appendTaskEvent write path as every other status transition in
8212
+ // lib/task-db.js — never a raw projection-JSON edit.
8213
+ function cmdRelabelArchived(args) {
8214
+ const apply = hasFlag(args, '--apply');
8215
+ const taskDb = getTaskDb();
8216
+ const db = taskDb.open();
8217
+ const workspaceRoot = taskDb.workspaceRoot();
8218
+ const actor = String(flag(args, '--as') || DEFAULT_OWNER);
8219
+ const result = taskDb.relabelArchivedTasks(db, { workspaceRoot, apply, actor });
8220
+ if (apply && result.count > 0) {
8221
+ appendRelabelArchivedJournalReceipt(workspaceRoot, { actor, count: result.count, ids: result.ids });
8222
+ }
8223
+ if (wantsJson(args)) {
8224
+ printJson({
8225
+ ok: true,
8226
+ action: apply ? 'relabeled' : 'preview',
8227
+ dry_run: !apply,
8228
+ workspace_root: workspaceRoot,
8229
+ ...result,
8230
+ });
8231
+ return;
8232
+ }
8233
+ if (!apply) {
8234
+ console.log(`relabel-archived (dry-run): ${result.count} failed task(s) match the June 10 backlog-reset marker.`);
8235
+ for (const s of result.sample) console.log(` - ${s.id} ${s.title}`);
8236
+ if (result.count > result.sample.length) console.log(` ...and ${result.count - result.sample.length} more`);
8237
+ console.log('Run with --apply --as <you> to relabel these failed -> archived.');
8238
+ return;
8239
+ }
8240
+ console.log(`relabeled ${result.count} task(s) failed -> archived (June 10 backlog reset, OBL-1622).`);
8241
+ }
8242
+
8243
+ function appendRelabelArchivedJournalReceipt(workspaceRoot, { actor, count, ids }) {
8244
+ if (!workspaceRoot || !fs.existsSync(path.join(workspaceRoot, 'atris'))) return null;
8245
+ const now = new Date();
8246
+ const logName = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}.md`;
8247
+ const stamp = now.toTimeString().slice(0, 5);
8248
+ const projectDir = path.join(workspaceRoot, 'atris', 'logs', logName.slice(0, 4));
8249
+ fs.mkdirSync(projectDir, { recursive: true });
8250
+ const logPath = path.join(projectDir, logName);
8251
+ const idPreview = ids.slice(0, 10).join(', ') + (ids.length > 10 ? `, ...(+${ids.length - 10} more)` : '');
8252
+ fs.appendFileSync(logPath, [
8253
+ `## ${stamp} · Task relabel: failed -> archived (OBL-1622)`,
8254
+ `- count: ${count}`,
8255
+ `- reason: June 10 backlog-reset rows mislabeled failed; relabeled to archived`,
8256
+ `- actor: ${actor}`,
8257
+ `- ids: ${idPreview}`,
8258
+ '',
8259
+ ].join('\n'), 'utf8');
8260
+ return logPath;
8261
+ }
8262
+
7842
8263
  function cmdReady(args) {
7843
8264
  const pos = positional(args);
7844
8265
  const id = pos[0];
@@ -7851,18 +8272,26 @@ function cmdReady(args) {
7851
8272
  // turning a claim into executed evidence. --verify can carry an optional --proof note.
7852
8273
  const proofFlag = flag(args, '--proof');
7853
8274
  const verifyFlag = flag(args, '--verify');
8275
+ const usedVerify = typeof verifyFlag === 'string' ? verifyFlag.trim() : '';
7854
8276
  let proof = typeof proofFlag === 'string' ? proofFlag : '';
7855
- if (typeof verifyFlag === 'string' && verifyFlag.trim()) {
7856
- const verified = buildVerifiedProof(verifyFlag, proof, undefined, { cwd: process.cwd() });
7857
- if (!verified.ok) {
7858
- const detail = verified.exit != null ? ` (exit ${verified.exit})` : (verified.signal ? ` (signal ${verified.signal})` : '');
8277
+ const verifyAutoCertifyAllowed = !usedVerify || isAutoCertifyVerifyCommandAllowed(verifyFlag);
8278
+ if (usedVerify) {
8279
+ // Run the verifier once and write a receipt (pass or fail) so the review
8280
+ // gate in lib/receipt-evidence.js can validate the exact path named in
8281
+ // the proof, not just trust the prose.
8282
+ const { writeTaskReceipt } = require('../lib/task-receipt');
8283
+ const receipt = writeTaskReceipt({ taskId: id, command: verifyFlag, root: process.cwd() });
8284
+ if (!receipt.passed) {
8285
+ const detail = receipt.exit != null ? ` (exit ${receipt.exit})` : (receipt.signal ? ` (signal ${receipt.signal})` : '');
7859
8286
  console.error(`atris task ready: verifier failed${detail}: ${verifyFlag}`);
7860
- if (verified.output) console.error(verified.output);
7861
- else if (verified.error) console.error(verified.error);
8287
+ if (receipt.output) console.error(receipt.output);
8288
+ else if (receipt.error) console.error(receipt.error);
8289
+ if (receipt.receiptPath) console.error(`receipt: ${receipt.receiptPath}`);
7862
8290
  process.exit(1);
7863
8291
  }
7864
- proof = verified.proof;
7865
- if (!wantsJson(args)) console.log(`✓ verified: \`${verifyFlag}\` exited 0`);
8292
+ const base = proof.trim();
8293
+ proof = `[verified] \`${verifyFlag}\` passed (exit 0)${base ? ` — ${base}` : ''}${receipt.output ? `\n${receipt.output}` : ''}\nReceipt: ${receipt.receiptPath}`;
8294
+ if (!wantsJson(args)) console.log(`✓ verified: \`${verifyFlag}\` exited 0 (receipt ${receipt.receiptPath})`);
7866
8295
  }
7867
8296
  if (!proof) {
7868
8297
  console.error('atris task ready: --proof or --verify required');
@@ -7872,6 +8301,7 @@ function cmdReady(args) {
7872
8301
  const lesson = flag(args, '--lesson') || '';
7873
8302
  const nextTaskInput = normalizeReviewNextTaskInput(typeof flag(args, '--next') === 'string' ? flag(args, '--next') : '');
7874
8303
  const landing = landingFlags(args);
8304
+ guardExplicitActor('atris task ready', flag(args, '--as'));
7875
8305
  const actor = String(flag(args, '--as') || DEFAULT_OWNER);
7876
8306
  const resultFields = {
7877
8307
  changed: textFlag(args, ['--changed', '--result', '--done']),
@@ -7906,6 +8336,11 @@ function cmdReady(args) {
7906
8336
  console.error(`ready failed: ${result.reason}`);
7907
8337
  process.exit(1);
7908
8338
  }
8339
+ // Store the exact verifier command on the task itself (not just baked into
8340
+ // proof prose) so sweep --auto-accept and certify-verified can re-run it
8341
+ // live against the current checkout later, instead of re-deriving it from
8342
+ // text or trusting a receipt file that may no longer exist.
8343
+ if (usedVerify) stampReadyVerifyMetadata(taskDb, db, taskId, usedVerify);
7909
8344
  const landingAdvisory = warnIfLandingNeedsDayOnePm(landing, result.row && result.row.title);
7910
8345
  const { projection, outPath } = writeDefaultProjection(taskDb, db);
7911
8346
  const agentCertified = result.event.payload.agent_certified === true;
@@ -7918,13 +8353,18 @@ function cmdReady(args) {
7918
8353
  nextTask: nextTaskInput.nextTask,
7919
8354
  });
7920
8355
  const reviewChat = taskReviewChatHandoff(verifierTask, { reviewer: 'codex-review' });
8356
+ const autolandOn = require('../lib/autoland').liveAcceptAuthorization(taskDb.workspaceRoot()).ok;
7921
8357
  const handoff = {
7922
8358
  native_goal_status: agentCertified ? 'agent_certified' : 'needs_second_agent_review',
7923
8359
  career_xp_status: 'pending_human_accept',
7924
8360
  next_action: agentCertified ? certifiedReviewNextAction(nextTaskInput.nextTask) : 'agent_review_again',
7925
- rule: agentCertified
7926
- ? 'Double-check complete; ready to keep moving. XP is awarded only after the human approves the task.'
7927
- : 'Proof is ready; one more agent check before human approval. XP waits for the human.',
8361
+ rule: autolandOn
8362
+ ? (agentCertified
8363
+ ? 'double-check complete; autoland will accept this on the next tick.'
8364
+ : 'proof is ready; autoland runs the second check and lands it on the next tick.')
8365
+ : (agentCertified
8366
+ ? 'double-check complete; ready to keep moving. XP is awarded only after the human approves the task.'
8367
+ : 'proof is ready; one more agent check before human approval. XP waits for the human.'),
7928
8368
  };
7929
8369
  if (reviewChat) {
7930
8370
  handoff.review_chat_command = reviewChat.command;
@@ -7958,11 +8398,59 @@ function cmdReady(args) {
7958
8398
  console.log(`ready for approval ${taskRef(compactTaskFromProjection(projection, taskId))} v${result.event.version}`);
7959
8399
  if (resultTrace) console.log('Result trace recorded.');
7960
8400
  console.log(handoff.rule);
8401
+ if (!verifyAutoCertifyAllowed) {
8402
+ const ref = taskRef(compactTaskFromProjection(projection, taskId) || result.row || taskId);
8403
+ console.log(`note: this verify command is outside the auto-certify allowlist, so autoland cannot run the second check itself. use a test command like node --test <file> or git diff --check, or have a second agent run: atris task review-chat ${ref} --as <reviewer>`);
8404
+ }
7961
8405
  for (const hint of policyHints) {
7962
8406
  console.log(`policy (${hint.id}): ${hint.hint}`);
7963
8407
  }
7964
8408
  }
7965
8409
 
8410
+ // Standalone receipt writer: runs a verifier for a task and writes atris/runs/
8411
+ // evidence without moving the task to ready. Useful when you want a receipt
8412
+ // on record before or independent of a ready call, or to record a failed
8413
+ // verifier run for the audit trail. `atris task ready --verify` calls the
8414
+ // same writer inline and folds the resulting path into the proof.
8415
+ function cmdTaskReceipt(args) {
8416
+ const pos = positional(args);
8417
+ const id = pos[0];
8418
+ if (!id) {
8419
+ console.error('atris task receipt: id required');
8420
+ process.exit(2);
8421
+ }
8422
+ const verifyFlag = flag(args, '--verify');
8423
+ if (typeof verifyFlag !== 'string' || !verifyFlag.trim()) {
8424
+ console.error('atris task receipt: --verify "<cmd>" required');
8425
+ process.exit(2);
8426
+ }
8427
+ const taskDb = getTaskDb();
8428
+ const db = taskDb.open();
8429
+ const taskId = requireTaskId(taskDb, db, id, 'atris task receipt');
8430
+ const { writeTaskReceipt } = require('../lib/task-receipt');
8431
+ const receipt = writeTaskReceipt({ taskId, command: verifyFlag, root: process.cwd() });
8432
+ if (wantsJson(args)) {
8433
+ printJson({
8434
+ ok: receipt.passed,
8435
+ task_id: taskId,
8436
+ command: verifyFlag,
8437
+ receipt_path: receipt.receiptPath,
8438
+ exit: receipt.exit,
8439
+ passed: receipt.passed,
8440
+ });
8441
+ if (!receipt.passed) process.exit(1);
8442
+ return;
8443
+ }
8444
+ if (receipt.passed) {
8445
+ console.log(`receipt written: ${receipt.receiptPath} (exit 0)`);
8446
+ console.log(`use: atris task ready ${taskId} --proof "Receipt: ${receipt.receiptPath}"`);
8447
+ } else {
8448
+ console.error(`verifier failed (exit ${receipt.exit}); receipt written: ${receipt.receiptPath}`);
8449
+ if (receipt.output) console.error(receipt.output);
8450
+ process.exit(1);
8451
+ }
8452
+ }
8453
+
7966
8454
  async function cmdAccept(args) {
7967
8455
  const pos = positional(args);
7968
8456
  const id = pos[0];
@@ -8041,6 +8529,7 @@ async function cmdAccept(args) {
8041
8529
  const xpProjection = refreshCareerXpAfterReview(reviewed);
8042
8530
  const { projection, outPath } = writeDefaultProjection(taskDb, db);
8043
8531
  const workspaceRoot = projection.workspace_root || process.cwd();
8532
+ refreshExistingTodoMarkdown(taskDb, db, workspaceRoot);
8044
8533
  const brainScorecards = refreshBrainScorecardsAfterAccept(workspaceRoot);
8045
8534
  const nextMissionRoute = nextMissionRouteAfterAccept(workspaceRoot);
8046
8535
  // Inform the gate, never block it: show what the receipts named in the proof
@@ -8093,7 +8582,7 @@ function stampAutoAcceptMetadata(taskDb, db, taskId, actor, policy) {
8093
8582
  `).run(JSON.stringify(metadata), Date.now(), taskId);
8094
8583
  }
8095
8584
 
8096
- function acceptReviewTask(taskDb, db, taskId, { actor, proof, reward, lesson = '', nextTask = '' }) {
8585
+ function acceptReviewTask(taskDb, db, taskId, { actor, proof, reward, lesson = '', nextTask = '', autoAccepted = false }) {
8097
8586
  const done = taskDb.doneTask(db, {
8098
8587
  id: taskId,
8099
8588
  status: 'done',
@@ -8101,6 +8590,7 @@ function acceptReviewTask(taskDb, db, taskId, { actor, proof, reward, lesson = '
8101
8590
  allowReview: true,
8102
8591
  action: 'accepted',
8103
8592
  proof,
8593
+ autoAccepted,
8104
8594
  });
8105
8595
  if (!done.updated) {
8106
8596
  return { ok: false, reason: 'not_open_claimed_or_review' };
@@ -8113,7 +8603,10 @@ function acceptReviewTask(taskDb, db, taskId, { actor, proof, reward, lesson = '
8113
8603
  nextTask,
8114
8604
  proof,
8115
8605
  careerXpEligible: true,
8606
+ autoAccepted,
8116
8607
  });
8608
+ const acceptedRow = taskDb.getTask(db, taskId);
8609
+ refreshExistingTodoMarkdown(taskDb, db, acceptedRow && acceptedRow.workspace_root);
8117
8610
  return { ok: true, reviewed };
8118
8611
  }
8119
8612
 
@@ -8145,6 +8638,25 @@ function stampCertifyVerifyMetadata(taskDb, db, taskId, actor, verify) {
8145
8638
  metadata.verify = metadata.verify || verify;
8146
8639
  metadata.certified_verified_at = new Date().toISOString();
8147
8640
  metadata.certified_verified_by = actor;
8641
+ metadata.machine_verified = true;
8642
+ db.prepare(`
8643
+ UPDATE tasks
8644
+ SET metadata = ?,
8645
+ updated_at = ?
8646
+ WHERE id = ?
8647
+ `).run(JSON.stringify(metadata), Date.now(), taskId);
8648
+ }
8649
+
8650
+ // `atris task ready --verify "<cmd>"` already runs the command live and
8651
+ // gates on exit 0, but that alone leaves no machine re-runnable trace once
8652
+ // the proof text scrolls out of easy reach: storing the exact command on
8653
+ // metadata.verify is what lets sweep --auto-accept (and certify-verified)
8654
+ // re-run it later against the current checkout instead of re-parsing prose.
8655
+ function stampReadyVerifyMetadata(taskDb, db, taskId, verify) {
8656
+ const row = taskDb.getTask(db, taskId);
8657
+ if (!row) return;
8658
+ const metadata = row.metadata && typeof row.metadata === 'object' ? { ...row.metadata } : {};
8659
+ metadata.verify = verify;
8148
8660
  db.prepare(`
8149
8661
  UPDATE tasks
8150
8662
  SET metadata = ?,
@@ -8196,16 +8708,28 @@ function cmdCertifyVerified(args) {
8196
8708
  // something an executed second-actor check cannot cure. A row with two
8197
8709
  // passes from ONE actor is exactly what this command exists to cure —
8198
8710
  // "certified" alone is not landable.
8711
+ // strictVerify stays off in this eligibility probe: certify-verified runs
8712
+ // the check itself right below, and strict mode here would execute it a
8713
+ // second time per row before the real run.
8199
8714
  const evaluation = evaluateAutoAccept(task, { strictVerify: false });
8200
8715
  if (evaluation.eligible) {
8201
8716
  results.push({ ref, action: 'skipped', reason: 'already_landable' });
8202
8717
  continue;
8203
8718
  }
8204
- const curable = ['not_agent_certified', 'needs_second_reviewer_or_third_pass', 'insufficient_review_passes'];
8719
+ // proof_not_executed is curable by definition: this command re-runs the
8720
+ // named check and replaces the free-text claim with executed evidence.
8721
+ const curable = ['not_agent_certified', 'needs_independent_reviewer', 'needs_second_reviewer_or_third_pass', 'insufficient_review_passes', 'proof_not_executed'];
8205
8722
  if (!curable.includes(evaluation.reason)) {
8206
8723
  results.push({ ref, action: 'skipped', reason: evaluation.reason });
8207
8724
  continue;
8208
8725
  }
8726
+ // judge != worker: the re-run only counts as an independent check when
8727
+ // its actor is not the builder of the row it is judging.
8728
+ const builder = reviewIntegrity.taskBuilder(task);
8729
+ if (builder && reviewIntegrity.normalizeActor(actor) === builder) {
8730
+ results.push({ ref, action: 'skipped', reason: 'verifier_is_builder' });
8731
+ continue;
8732
+ }
8209
8733
  const verify = certifyVerifyCandidate(task);
8210
8734
  if (!verify) {
8211
8735
  results.push({ ref, action: 'skipped', reason: 'no_runnable_check_in_proof' });
@@ -8262,9 +8786,26 @@ function cmdCertifyVerified(args) {
8262
8786
  return payload;
8263
8787
  }
8264
8788
 
8789
+ // The landing: everything certified, one summary, one human gesture.
8790
+ // Review-by-N-pastes was the operator pain; this is the batch gate that
8791
+ // keeps human accept as the one gate without making it N gates.
8792
+ function cmdLanding(args) {
8793
+ if (hasFlag(args, '--accept')) {
8794
+ const passthrough = args.filter(arg => arg !== '--accept');
8795
+ if (!passthrough.includes('--confirm-human-accept')) passthrough.push('--confirm-human-accept');
8796
+ return cmdAutoAcceptCertified(passthrough);
8797
+ }
8798
+ cmdReviews(args);
8799
+ console.log('');
8800
+ console.log('land everything certified above in one gesture:');
8801
+ console.log(' atris task landing --accept --as <you>');
8802
+ console.log('(items needing one more check stay in review; only certified work lands)');
8803
+ }
8804
+
8265
8805
  function cmdAutoAcceptCertified(args) {
8266
8806
  const dryRun = hasFlag(args, '--dry-run');
8267
- const strictVerify = hasFlag(args, '--strict-verify');
8807
+ const acceptAll = hasFlag(args, '--all');
8808
+ const strictVerify = !hasFlag(args, '--no-strict-verify') && !acceptAll;
8268
8809
  const actorFlag = flag(args, '--as');
8269
8810
  const hasHumanActor = validHumanActorFlag(actorFlag);
8270
8811
  const confirmedHumanAccept = hasFlag(args, '--confirm-human-accept');
@@ -8277,7 +8818,17 @@ function cmdAutoAcceptCertified(args) {
8277
8818
  : { ok: false };
8278
8819
  const actor = String(actorFlag || (policyAuth.ok ? policyAuth.actor : 'auto-accept-certified'));
8279
8820
  const limitRaw = flag(args, '--limit');
8280
- const max = limitRaw && limitRaw !== true ? Math.max(1, Number(limitRaw) || 12) : 12;
8821
+ const hasExplicitLimit = Boolean(limitRaw) && limitRaw !== true;
8822
+ // --all means sweep the full certified backlog, not just the first page of
8823
+ // it. Before this fix `max` was hard-capped at 12 even under --all, so a
8824
+ // real backlog (78 certified rows observed live) only ever drained 12/run —
8825
+ // an invisible undercount the autoland heartbeat repeated every hour.
8826
+ // AUTO_ACCEPT_ALL_SWEEP_CAP is a safety ceiling, not a target: a well-formed
8827
+ // --all run should always scan fewer rows than this.
8828
+ const AUTO_ACCEPT_ALL_SWEEP_CAP = 500;
8829
+ const max = hasExplicitLimit
8830
+ ? Math.max(1, Number(limitRaw) || 12)
8831
+ : (acceptAll ? AUTO_ACCEPT_ALL_SWEEP_CAP : 12);
8281
8832
  const parsedReward = parseAcceptReward(flag(args, '--reward'));
8282
8833
  if (!parsedReward.ok) {
8283
8834
  console.error('atris task auto-accept-certified: reward must be a positive number');
@@ -8297,10 +8848,18 @@ function cmdAutoAcceptCertified(args) {
8297
8848
  'live auto-accept requires --as <human> so XP has an explicit human acceptance actor',
8298
8849
  );
8299
8850
  }
8300
- if (agentProofOnlyMode() && !dryRun) {
8851
+ // The standing autoland policy clears this gate too, same as the two
8852
+ // per-run human gates above: the owner flipped the policy, accepts run as
8853
+ // that owner, and the cron tick would land the same rows an hour later
8854
+ // anyway. Without this exception `atris autoland tick` was blind whenever
8855
+ // invoked from an agent session (CLAUDECODE etc. in env): the spawned
8856
+ // sweep failTask'd with no summary and the tick receipt showed nulls.
8857
+ // A per-run --confirm-human-accept claim from an agent is still refused —
8858
+ // policyAuth is only consulted when no per-run confirmation is passed.
8859
+ if (agentProofOnlyMode() && !dryRun && !policyAuth.ok) {
8301
8860
  failAgentProofOnly(
8302
8861
  'atris task auto-accept-certified',
8303
- 'Agent proof-only mode can preview certified rows with --dry-run, but cannot live-accept them.',
8862
+ 'Agent proof-only mode can preview certified rows with --dry-run, but cannot live-accept them without the standing autoland policy.',
8304
8863
  );
8305
8864
  }
8306
8865
 
@@ -8308,16 +8867,30 @@ function cmdAutoAcceptCertified(args) {
8308
8867
  const db = taskDb.open();
8309
8868
  const { projection, outPath } = writeDefaultProjection(taskDb, db);
8310
8869
  const queue = taskReviewQueue(projection, ['--limit', String(max)]);
8870
+ const pendingReview = (projection.tasks || [])
8871
+ .filter((t) => t && t.status === 'review' && t.review && t.review.approval_status === 'pending');
8872
+ // Honest denominator for the summary line below: how many rows the
8873
+ // projection actually certified, independent of any scan cap. If `scanned`
8874
+ // ever comes in under `certified`, the undercount is visible instead of
8875
+ // silent (the exact failure mode this fix closes).
8876
+ const certifiedTotal = pendingReview.filter((t) => isAgentCertified(t)).length;
8877
+ // The review queue only surfaces certified rows. Under --all the bar is
8878
+ // the protected lanes, not certification, so scan every pending review.
8879
+ const pool = acceptAll
8880
+ ? pendingReview
8881
+ .sort((a, b) => Number(b.updated_at || 0) - Number(a.updated_at || 0))
8882
+ .slice(0, max)
8883
+ : queue.items.filter(item => item.queue_role !== 'blocked');
8311
8884
  const results = [];
8312
8885
 
8313
- for (const item of queue.items) {
8886
+ for (const item of pool) {
8314
8887
  const fullProjection = enrichTaskProjection(taskDb.taskProjection(db, { taskId: item.id }));
8315
8888
  const task = fullProjection.tasks[0] || null;
8316
8889
  if (!task) {
8317
8890
  results.push({ ref: item.display_id || item.id, eligible: false, reason: 'task_not_found', action: 'skipped' });
8318
8891
  continue;
8319
8892
  }
8320
- const evaluation = evaluateAutoAccept(task, { strictVerify });
8893
+ const evaluation = evaluateAutoAccept(task, { strictVerify, acceptAll });
8321
8894
  if (!evaluation.eligible) {
8322
8895
  results.push({ ...evaluation, action: 'skipped' });
8323
8896
  continue;
@@ -8332,6 +8905,7 @@ function cmdAutoAcceptCertified(args) {
8332
8905
  reward: parsedReward.value,
8333
8906
  lesson: String(task.review?.lesson || task.metadata?.latest_agent_lesson || ''),
8334
8907
  nextTask: String(task.review?.next_task || task.metadata?.latest_agent_next_task || ''),
8908
+ autoAccepted: true,
8335
8909
  });
8336
8910
  if (!accepted.ok) {
8337
8911
  results.push({ ...evaluation, action: 'accept_failed', reason: accepted.reason });
@@ -8349,17 +8923,24 @@ function cmdAutoAcceptCertified(args) {
8349
8923
 
8350
8924
  const { projection: finalProjection, outPath: finalPath } = writeDefaultProjection(taskDb, db);
8351
8925
  const summary = {
8352
- scanned: queue.items.length,
8926
+ certified: certifiedTotal,
8927
+ scanned: pool.length,
8353
8928
  accepted: results.filter(row => row.action === 'accepted').length,
8354
8929
  would_accept: results.filter(row => row.action === 'would_accept').length,
8355
8930
  skipped: results.filter(row => row.action === 'skipped').length,
8356
8931
  failed: results.filter(row => row.action === 'accept_failed').length,
8932
+ // Visible undercount flag: true only if the pool was cut short by `max`
8933
+ // while certified rows still existed beyond it. --all uses a high safety
8934
+ // cap (AUTO_ACCEPT_ALL_SWEEP_CAP), so this should stay false in practice;
8935
+ // if it ever flips true, the cap itself needs raising, not silence.
8936
+ undercounted: certifiedTotal > pool.length,
8357
8937
  };
8358
8938
  if (wantsJson(args)) {
8359
8939
  printJson({
8360
8940
  ok: true,
8361
8941
  action: dryRun ? 'auto_accept_certified_dry_run' : 'auto_accept_certified',
8362
8942
  strict_verify: strictVerify,
8943
+ accept_all: acceptAll,
8363
8944
  summary,
8364
8945
  ...summary,
8365
8946
  results,
@@ -8369,7 +8950,7 @@ function cmdAutoAcceptCertified(args) {
8369
8950
  return;
8370
8951
  }
8371
8952
  console.log(`AUTO-ACCEPT CERTIFIED (${dryRun ? 'dry-run' : 'execute'})`);
8372
- console.log(`${summary.accepted || summary.would_accept} accepted / ${summary.skipped} skipped / ${summary.failed} failed / ${summary.scanned} scanned`);
8953
+ console.log(`${summary.certified} certified, ${summary.scanned} scanned, ${summary.accepted || summary.would_accept} accepted, ${summary.skipped} skipped${summary.failed ? `, ${summary.failed} failed` : ''}${summary.undercounted ? ' (UNDERCOUNTED — raise --limit or the sweep cap)' : ''}`);
8373
8954
  for (const row of results) {
8374
8955
  const nextAction = row.next_action ? ` next_action=${row.next_action}` : '';
8375
8956
  const reviewChat = row.review_chat_command ? ` review_chat=${row.review_chat_command}` : '';
@@ -8377,6 +8958,246 @@ function cmdAutoAcceptCertified(args) {
8377
8958
  }
8378
8959
  }
8379
8960
 
8961
+ const SWEEP_AUTO_ACCEPT_PROTECTED = new Set([
8962
+ 'money', 'deploy', 'release', 'publish', 'security', 'customer', 'outward',
8963
+ ]);
8964
+
8965
+ function autoAcceptSweepLabelValues(task) {
8966
+ const metadata = task && task.metadata && typeof task.metadata === 'object' ? task.metadata : {};
8967
+ const tags = Array.isArray(metadata.tags) ? metadata.tags : [];
8968
+ return [
8969
+ task && task.tag,
8970
+ task && task.lane,
8971
+ metadata.tag,
8972
+ metadata.lane,
8973
+ metadata.mission_lane,
8974
+ metadata.stage,
8975
+ ...tags,
8976
+ ].filter(value => value !== undefined && value !== null && String(value).trim());
8977
+ }
8978
+
8979
+ function autoAcceptSweepNormalizedTokens(value) {
8980
+ const normalized = String(value || '').trim().toLowerCase().replace(/_/g, '-');
8981
+ if (!normalized) return [];
8982
+ return [normalized, ...normalized.split(/[^a-z0-9-]+/).filter(Boolean)];
8983
+ }
8984
+
8985
+ function autoAcceptSweepDeniedReason(task) {
8986
+ for (const value of autoAcceptSweepLabelValues(task)) {
8987
+ const tokens = autoAcceptSweepNormalizedTokens(value);
8988
+ for (const token of tokens) {
8989
+ if (token === 'needs-human' || token === 'needshuman') {
8990
+ return 'needs_human';
8991
+ }
8992
+ const protectedLane = [...SWEEP_AUTO_ACCEPT_PROTECTED].find((denied) =>
8993
+ token === denied || token.replace(/s$/, '') === denied
8994
+ );
8995
+ if (protectedLane) return 'protected_lane';
8996
+ }
8997
+ }
8998
+ return null;
8999
+ }
9000
+
9001
+ function autoAcceptSweepLatestProof(task) {
9002
+ const metadata = task && task.metadata || {};
9003
+ const review = task && task.review || {};
9004
+ return String(review.proof || metadata.latest_agent_proof || '').trim();
9005
+ }
9006
+
9007
+ function autoAcceptSweepVerifierEvidence(proof, root) {
9008
+ const evidence = extractReceiptEvidence(proof, root);
9009
+ if (!evidence || !evidence.receipts || !evidence.receipts.length) {
9010
+ return { ok: false, reason: 'no_passing_verifier', evidence: evidence || null };
9011
+ }
9012
+ if (evidence.missing && evidence.missing.length) {
9013
+ return { ok: false, reason: 'receipt_missing', evidence };
9014
+ }
9015
+ const passing = evidence.receipts.filter((receipt) => receipt.verifier_passed === true);
9016
+ if (!passing.length) {
9017
+ return { ok: false, reason: 'no_passing_verifier', evidence };
9018
+ }
9019
+ const notPassed = evidence.receipts.find((receipt) => receipt.verifier_passed !== true);
9020
+ if (notPassed) {
9021
+ return { ok: false, reason: 'receipt_verifier_not_passed', evidence };
9022
+ }
9023
+ return {
9024
+ ok: true,
9025
+ evidence,
9026
+ passing,
9027
+ proved_by: passing.map((receipt) => `${receipt.path} verifier_passed=true`),
9028
+ };
9029
+ }
9030
+
9031
+ function autoAcceptSweepHappened(task) {
9032
+ const review = task && task.review || {};
9033
+ return clipStatusText(
9034
+ review.landing?.happened
9035
+ || review.result?.changed
9036
+ || task?.title
9037
+ || 'accepted verified task',
9038
+ 180,
9039
+ );
9040
+ }
9041
+
9042
+ function evaluateSweepAutoAccept(task, root) {
9043
+ const ref = taskRef(task);
9044
+ if (!task) return { eligible: false, ref, reason: 'task_not_found' };
9045
+ if (task.status !== 'review') return { eligible: false, ref, reason: 'not_in_review' };
9046
+ const metadata = task.metadata || {};
9047
+ const review = task.review || {};
9048
+ const approval = String(review.approval_status || metadata.approval_status || 'pending').toLowerCase();
9049
+ if (approval !== 'pending') return { eligible: false, ref, reason: `approval_${approval}` };
9050
+ if (metadata.auto_accepted_at) return { eligible: false, ref, reason: 'already_auto_accepted' };
9051
+ const denied = autoAcceptSweepDeniedReason(task);
9052
+ if (denied) return { eligible: false, ref, reason: denied };
9053
+ const proof = autoAcceptSweepLatestProof(task);
9054
+ if (!proof) return { eligible: false, ref, reason: 'no_proof' };
9055
+
9056
+ // 1. An explicit, stored verifier (`atris task ready --verify`, or a prior
9057
+ // certify-verified stamp) is the strongest signal: re-run it live, right
9058
+ // now, against the current checkout, and let it decide outright. This is
9059
+ // what unblocks CLI-762/CLI-861-shaped proofs: a real green test cited (or
9060
+ // executed) in the proof, but the older receipt-path check below could not
9061
+ // find file evidence for it once `ready --verify` stopped writing a
9062
+ // receipt file and started embedding the executed result into proof text.
9063
+ const storedVerify = typeof metadata.verify === 'string' ? metadata.verify.trim() : '';
9064
+ if (storedVerify) {
9065
+ const result = runVerifyCommand(storedVerify, root);
9066
+ if (!result.ok) return { eligible: false, ref, reason: result.reason, verify: storedVerify };
9067
+ return {
9068
+ eligible: true,
9069
+ ref,
9070
+ reason: 'verified_command',
9071
+ policy: 'sweep_auto_accept_verified_command',
9072
+ proof,
9073
+ verify: storedVerify,
9074
+ proved_by: [`${storedVerify} exited 0`],
9075
+ happened: autoAcceptSweepHappened(task),
9076
+ };
9077
+ }
9078
+
9079
+ // 2. Legacy path: a receipt file explicitly named in proof text.
9080
+ const verifier = autoAcceptSweepVerifierEvidence(proof, root);
9081
+ if (verifier.ok) {
9082
+ return {
9083
+ eligible: true,
9084
+ ref,
9085
+ reason: 'verified_receipt',
9086
+ policy: 'sweep_auto_accept_verified',
9087
+ proof,
9088
+ evidence: verifier.evidence,
9089
+ proved_by: verifier.proved_by,
9090
+ happened: autoAcceptSweepHappened(task),
9091
+ };
9092
+ }
9093
+
9094
+ // 3. No stored verifier and no receipt was even cited: try deriving a
9095
+ // safe, runnable command straight from the proof text itself (same
9096
+ // extractor certify-verified uses) and re-run it live. Only reached when
9097
+ // there is nothing else to go on, so a proof that legitimately cites a
9098
+ // real receipt keeps taking the receipt path above rather than racing an
9099
+ // unrelated command mentioned in the same sentence.
9100
+ if (verifier.reason === 'no_passing_verifier') {
9101
+ const derived = certifyVerifyCandidate(task);
9102
+ if (derived) {
9103
+ const result = runVerifyCommand(derived, root);
9104
+ if (result.ok) {
9105
+ return {
9106
+ eligible: true,
9107
+ ref,
9108
+ reason: 'verified_derived_command',
9109
+ policy: 'sweep_auto_accept_verified_derived',
9110
+ proof,
9111
+ verify: derived,
9112
+ proved_by: [`${derived} exited 0`],
9113
+ happened: autoAcceptSweepHappened(task),
9114
+ };
9115
+ }
9116
+ return { eligible: false, ref, reason: result.reason, verify: derived };
9117
+ }
9118
+ }
9119
+
9120
+ return { eligible: false, ref, reason: verifier.reason, evidence: verifier.evidence };
9121
+ }
9122
+
9123
+ function cmdSweep(args) {
9124
+ if (!hasFlag(args, '--auto-accept')) {
9125
+ failTask(
9126
+ 'atris task sweep',
9127
+ 'missing_auto_accept',
9128
+ 'atris task sweep currently requires --auto-accept for the explicit verified accept policy',
9129
+ );
9130
+ }
9131
+ const actor = String(flag(args, '--as') || 'orb-autoaccept');
9132
+ const parsedReward = parseAcceptReward(flag(args, '--reward'));
9133
+ if (!parsedReward.ok) {
9134
+ failTask('atris task sweep', 'invalid_reward', 'atris task sweep --auto-accept reward must be a positive number');
9135
+ }
9136
+ const limitRaw = flag(args, '--limit');
9137
+ const explicitLimit = limitRaw && limitRaw !== true ? Math.max(1, Number(limitRaw) || 0) : null;
9138
+ const taskDb = getTaskDb();
9139
+ const db = taskDb.open();
9140
+ const { projection } = writeDefaultProjection(taskDb, db);
9141
+ const root = projection.workspace_root || process.cwd();
9142
+ let pendingReview = (projection.tasks || [])
9143
+ .filter((task) => task && task.status === 'review' && task.review && task.review.approval_status === 'pending')
9144
+ .sort((a, b) => Number(b.updated_at || 0) - Number(a.updated_at || 0));
9145
+ if (explicitLimit) pendingReview = pendingReview.slice(0, explicitLimit);
9146
+ const results = [];
9147
+ for (const item of pendingReview) {
9148
+ const fullProjection = enrichTaskProjection(taskDb.taskProjection(db, { taskId: item.id }));
9149
+ const task = fullProjection.tasks[0] || null;
9150
+ const evaluation = evaluateSweepAutoAccept(task, root);
9151
+ if (!evaluation.eligible) {
9152
+ results.push({ ...evaluation, action: 'skipped', task_id: task?.id || item.id || null });
9153
+ continue;
9154
+ }
9155
+ const accepted = acceptReviewTask(taskDb, db, task.id, {
9156
+ actor,
9157
+ proof: evaluation.proof,
9158
+ reward: parsedReward.value,
9159
+ lesson: String(task.review?.lesson || task.metadata?.latest_agent_lesson || ''),
9160
+ nextTask: String(task.review?.next_task || task.metadata?.latest_agent_next_task || ''),
9161
+ autoAccepted: true,
9162
+ });
9163
+ if (!accepted.ok) {
9164
+ results.push({ ...evaluation, action: 'accept_failed', task_id: task.id, reason: accepted.reason });
9165
+ continue;
9166
+ }
9167
+ stampAutoAcceptMetadata(taskDb, db, task.id, actor, evaluation.policy);
9168
+ refreshCareerXpAfterReview(accepted.reviewed);
9169
+ results.push({
9170
+ ...evaluation,
9171
+ action: 'accepted',
9172
+ task_id: task.id,
9173
+ reward: accepted.reviewed.episode.reward.value,
9174
+ });
9175
+ }
9176
+ const { outPath } = writeDefaultProjection(taskDb, db);
9177
+ const summary = {
9178
+ scanned: pendingReview.length,
9179
+ accepted: results.filter((row) => row.action === 'accepted').length,
9180
+ skipped: results.filter((row) => row.action === 'skipped').length,
9181
+ failed: results.filter((row) => row.action === 'accept_failed').length,
9182
+ };
9183
+ if (wantsJson(args)) {
9184
+ printJson({
9185
+ ok: true,
9186
+ action: 'sweep_auto_accept',
9187
+ actor,
9188
+ summary,
9189
+ ...summary,
9190
+ results,
9191
+ projection_path: outPath,
9192
+ });
9193
+ return;
9194
+ }
9195
+ console.log(`TASK SWEEP AUTO-ACCEPT: ${summary.accepted} accepted / ${summary.scanned} scanned / ${summary.skipped} skipped${summary.failed ? ` / ${summary.failed} failed` : ''}`);
9196
+ for (const row of results.filter((item) => item.action === 'accepted')) {
9197
+ console.log(`ACCEPTED ${row.ref}: ${row.happened} | proved by ${row.proved_by.join(', ')}`);
9198
+ }
9199
+ }
9200
+
8380
9201
  function cmdRevise(args) {
8381
9202
  const pos = positional(args);
8382
9203
  const id = pos[0];
@@ -8444,6 +9265,7 @@ function cmdReview(args) {
8444
9265
  if (clearNextTask || (typeof nextTaskFlag === 'string' && !String(nextTaskFlag).trim())) clearedFields.push('next_task');
8445
9266
  const proof = proofFlagValue(args);
8446
9267
  const verify = textFlag(args, ['--verify']);
9268
+ guardExplicitActor('atris task review', flag(args, '--as'));
8447
9269
  const actor = flag(args, '--as') || DEFAULT_OWNER;
8448
9270
  const rewardValue = reward === true || reward === null ? 0 : reward;
8449
9271
  if (agentProofOnlyMode() && Number(rewardValue) > 0) {
@@ -8481,7 +9303,11 @@ function cmdReview(args) {
8481
9303
  clearedFields,
8482
9304
  });
8483
9305
  if (!result.reviewed) {
8484
- console.error(`review failed: ${result.reason}`);
9306
+ if (result.reason === 'judge_equals_worker') {
9307
+ console.error(`review failed: judge_equals_worker: ${result.builder} built this task and cannot judge it. Hand off: atris task review ${id} --reward 1 --as <another member>`);
9308
+ } else {
9309
+ console.error(`review failed: ${result.reason}`);
9310
+ }
8485
9311
  process.exit(1);
8486
9312
  }
8487
9313
  const nextCreated = createNextTaskIfRequested(taskDb, db, args, currentTask, result.episode.next_task_suggestion);
@@ -8587,21 +9413,23 @@ function cmdEvents(args) {
8587
9413
  const pos = positional(args);
8588
9414
  let taskId = pos[0] || null;
8589
9415
  const all = hasFlag(args, '--all');
9416
+ const everywhere = taskScopeEverywhere(args);
8590
9417
  const rawLimit = flag(args, '--limit');
8591
9418
  const explicitLimit = rawLimit && rawLimit !== true ? Number(rawLimit) : null;
8592
9419
  const defaultRecentLimit = 24;
8593
- const limit = explicitLimit || (taskId ? 500 : (all ? null : defaultRecentLimit));
9420
+ const limit = explicitLimit || (taskId ? 500 : (all || everywhere ? null : defaultRecentLimit));
8594
9421
  const taskDb = getTaskDb();
8595
9422
  const db = taskDb.open();
8596
9423
  if (taskId) taskId = requireTaskId(taskDb, db, taskId, 'atris task events');
9424
+ const workspaceRoot = scopedWorkspaceRoot(taskDb, args, { everywhere });
8597
9425
  const events = taskDb.listTaskEvents(db, {
8598
9426
  taskId,
8599
- workspaceRoot: all || taskId ? null : taskDb.workspaceRoot(),
9427
+ workspaceRoot: taskId ? null : workspaceRoot,
8600
9428
  limit,
8601
- order: taskId || all ? 'asc' : 'desc',
9429
+ order: taskId || all || everywhere ? 'asc' : 'desc',
8602
9430
  });
8603
9431
  const refRows = taskDb.listTasks(db, {
8604
- workspaceRoot: all ? null : (taskId ? (taskDb.getTask(db, taskId) || {}).workspace_root : taskDb.workspaceRoot()),
9432
+ workspaceRoot: everywhere ? null : (taskId ? (taskDb.getTask(db, taskId) || {}).workspace_root : workspaceRoot),
8605
9433
  });
8606
9434
  const refById = taskDb.taskDisplayRefMap(refRows);
8607
9435
  if (wantsJson(args)) {
@@ -8609,7 +9437,7 @@ function cmdEvents(args) {
8609
9437
  ok: true,
8610
9438
  action: 'events',
8611
9439
  task_id: taskId,
8612
- mode: taskId ? 'task' : (all ? 'ledger' : 'recent'),
9440
+ mode: taskId ? 'task' : (everywhere ? 'ledger_everywhere' : (all ? 'ledger' : 'recent')),
8613
9441
  limit,
8614
9442
  events,
8615
9443
  });
@@ -8619,7 +9447,7 @@ function cmdEvents(args) {
8619
9447
  console.log('(no task events)');
8620
9448
  return;
8621
9449
  }
8622
- if (!taskId && !all) {
9450
+ if (!taskId && !all && !everywhere) {
8623
9451
  console.log('TASK EVENTS');
8624
9452
  console.log(`recent ${events.length} event${events.length === 1 ? '' : 's'} (use --all for the full ledger, --limit N to adjust)`);
8625
9453
  console.log('');
@@ -8720,12 +9548,13 @@ function cmdLineage(args) {
8720
9548
  function cmdExport(args) {
8721
9549
  const out = flag(args, '--out') || path.join('.atris', 'state', 'tasks.projection.json');
8722
9550
  const all = hasFlag(args, '--all');
9551
+ const everywhere = taskScopeEverywhere(args);
8723
9552
  const taskDb = getTaskDb();
8724
9553
  const db = taskDb.open();
8725
9554
  const outPath = path.resolve(String(out));
8726
9555
  const projection = enrichTaskProjection(taskDb.taskProjection(db, {
8727
- workspaceRoot: all ? null : taskDb.workspaceRoot(),
8728
- limit: 500,
9556
+ workspaceRoot: scopedWorkspaceRoot(taskDb, args, { everywhere }),
9557
+ limit: all ? null : 500,
8729
9558
  }));
8730
9559
  fs.mkdirSync(path.dirname(outPath), { recursive: true });
8731
9560
  fs.writeFileSync(outPath, JSON.stringify(projection, null, 2) + '\n', 'utf8');
@@ -8902,21 +9731,42 @@ function taskRenderSummaryLine(counts) {
8902
9731
  ].join('; ');
8903
9732
  }
8904
9733
 
9734
+ function refreshExistingTodoMarkdown(taskDb, db, workspaceRoot) {
9735
+ const ws = workspaceRoot || taskDb.workspaceRoot();
9736
+ const outPath = path.join(ws, 'atris', 'TODO.md');
9737
+ if (!fs.existsSync(outPath)) return null;
9738
+ const rows = taskDb.listTasks(db, { workspaceRoot: ws, limit: 500 });
9739
+ const refRows = taskDb.listTasks(db, { workspaceRoot: ws });
9740
+ const existingTodo = fs.readFileSync(outPath, 'utf8');
9741
+ const preservedSections = [];
9742
+ const endgameSection = extractTodoSectionMarkdown(existingTodo, 'Endgame');
9743
+ if (endgameSection) preservedSections.push(endgameSection);
9744
+ const markdownRows = markdownRowsForRender(taskDb, outPath, rows, refRows);
9745
+ const markdown = taskDb.renderTodoMarkdown([...rows, ...markdownRows], {
9746
+ refRows,
9747
+ preservedSections,
9748
+ });
9749
+ fs.writeFileSync(outPath, markdown, 'utf8');
9750
+ return outPath;
9751
+ }
9752
+
8905
9753
  function cmdRender(args) {
8906
9754
  const out = flag(args, '--out') || path.join('atris', 'TODO.md');
8907
9755
  const all = hasFlag(args, '--all');
9756
+ const everywhere = taskScopeEverywhere(args);
8908
9757
  const doneLimitRaw = flag(args, '--done-limit');
8909
9758
  const doneLimit = doneLimitRaw && doneLimitRaw !== true ? Number(doneLimitRaw) : undefined;
8910
9759
  const failedLimitRaw = flag(args, '--failed-limit');
8911
9760
  const failedLimit = failedLimitRaw && failedLimitRaw !== true ? Number(failedLimitRaw) : undefined;
8912
9761
  const taskDb = getTaskDb();
8913
9762
  const db = taskDb.open();
9763
+ const workspaceRoot = scopedWorkspaceRoot(taskDb, args, { everywhere });
8914
9764
  const rows = taskDb.listTasks(db, {
8915
- workspaceRoot: all ? null : taskDb.workspaceRoot(),
8916
- limit: 500,
9765
+ workspaceRoot,
9766
+ limit: all ? null : 500,
8917
9767
  });
8918
9768
  const refRows = taskDb.listTasks(db, {
8919
- workspaceRoot: all ? null : taskDb.workspaceRoot(),
9769
+ workspaceRoot,
8920
9770
  });
8921
9771
  const outPath = path.resolve(String(out));
8922
9772
  const existingTodo = fs.existsSync(outPath) ? fs.readFileSync(outPath, 'utf8') : '';
@@ -9525,6 +10375,7 @@ async function handleTaskApi(req, res, taskDb, db) {
9525
10375
  owner,
9526
10376
  reviewer,
9527
10377
  all: url.searchParams.get('all') === '1' || url.searchParams.get('all') === 'true',
10378
+ everywhere: url.searchParams.get('everywhere') === '1' || url.searchParams.get('everywhere') === 'true',
9528
10379
  limit: Number.isFinite(limit) && limit > 0 ? Math.floor(limit) : 8,
9529
10380
  scope,
9530
10381
  });
@@ -9540,6 +10391,7 @@ async function handleTaskApi(req, res, taskDb, db) {
9540
10391
  owner,
9541
10392
  reviewer,
9542
10393
  all: url.searchParams.get('all') === '1' || url.searchParams.get('all') === 'true',
10394
+ everywhere: url.searchParams.get('everywhere') === '1' || url.searchParams.get('everywhere') === 'true',
9543
10395
  limit: Number.isFinite(limit) && limit > 0 ? Math.floor(limit) : 8,
9544
10396
  scope,
9545
10397
  });
@@ -9570,6 +10422,7 @@ async function handleTaskApi(req, res, taskDb, db) {
9570
10422
  owner,
9571
10423
  reviewer,
9572
10424
  all: url.searchParams.get('all') === '1' || url.searchParams.get('all') === 'true',
10425
+ everywhere: url.searchParams.get('everywhere') === '1' || url.searchParams.get('everywhere') === 'true',
9573
10426
  limit: Number.isFinite(limit) && limit > 0 ? Math.floor(limit) : 8,
9574
10427
  scope,
9575
10428
  });
@@ -10114,6 +10967,9 @@ async function run(args) {
10114
10967
  return cmdPlanPreview(rest);
10115
10968
  case 'note': return cmdNote(rest);
10116
10969
  case 'say': return cmdNote(rest);
10970
+ case 'tag':
10971
+ case 'tags':
10972
+ return cmdTag(rest);
10117
10973
  case 'show': return cmdShow(rest);
10118
10974
  case 'inspect': return cmdInspect(rest);
10119
10975
  case 'page': return cmdPage(rest);
@@ -10122,11 +10978,17 @@ async function run(args) {
10122
10978
  case 'chat-review':
10123
10979
  return cmdReviewChat(rest);
10124
10980
  case 'ready': return cmdReady(rest);
10981
+ case 'receipt': return cmdTaskReceipt(rest);
10125
10982
  case 'result': return cmdResult(rest);
10126
10983
  case 'accept': return cmdAccept(rest);
10984
+ case 'landing':
10985
+ case 'land-review':
10986
+ return cmdLanding(rest);
10127
10987
  case 'auto-accept-certified':
10128
10988
  case 'auto-accept':
10129
10989
  return cmdAutoAcceptCertified(rest);
10990
+ case 'sweep':
10991
+ return cmdSweep(rest);
10130
10992
  case 'certify-verified':
10131
10993
  return cmdCertifyVerified(rest);
10132
10994
  case 'accept-group':
@@ -10135,6 +10997,8 @@ async function run(args) {
10135
10997
  case 'done': return cmdDone(rest);
10136
10998
  case 'finish': return cmdFinish(rest);
10137
10999
  case 'fail': return cmdDone([...rest, '--failed']);
11000
+ case 'archive': return cmdArchive(rest);
11001
+ case 'relabel-archived': return cmdRelabelArchived(rest);
10138
11002
  case 'review': return cmdReview(rest);
10139
11003
  case 'reviews':
10140
11004
  case 'review-queue':