atris 3.30.12 → 3.32.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 (64) hide show
  1. package/AGENTS.md +16 -3
  2. package/FOR_AGENTS.md +81 -0
  3. package/README.md +6 -4
  4. package/atris/CLAUDE.md +8 -0
  5. package/atris/atris.md +51 -19
  6. package/atris/skills/README.md +1 -0
  7. package/atris/skills/blocks/SKILL.md +134 -0
  8. package/atris/skills/clawhub/philosophy-of-work/SKILL.md +56 -0
  9. package/atris/skills/youtube/SKILL.md +31 -11
  10. package/atris.md +15 -0
  11. package/ax +189 -7
  12. package/bin/atris.js +273 -225
  13. package/commands/autoland.js +379 -0
  14. package/commands/autopilot-front.js +273 -0
  15. package/commands/autopilot.js +94 -4
  16. package/commands/business.js +1 -1
  17. package/commands/clean.js +72 -9
  18. package/commands/codex-goal.js +72 -22
  19. package/commands/computer.js +48 -3
  20. package/commands/gm.js +1 -1
  21. package/commands/harvest.js +179 -0
  22. package/commands/init.js +22 -1
  23. package/commands/land.js +442 -0
  24. package/commands/loop-front.js +122 -4
  25. package/commands/member.js +551 -19
  26. package/commands/mission.js +3674 -278
  27. package/commands/play.js +1 -1
  28. package/commands/pulse.js +65 -7
  29. package/commands/run-front.js +144 -0
  30. package/commands/run.js +10 -7
  31. package/commands/sign.js +90 -0
  32. package/commands/strings.js +301 -0
  33. package/commands/task.js +782 -108
  34. package/commands/truth.js +171 -0
  35. package/commands/xp.js +32 -8
  36. package/commands/youtube.js +72 -5
  37. package/decks/README.md +6 -12
  38. package/lib/auto-accept-certified.js +10 -0
  39. package/lib/autoland.js +391 -0
  40. package/lib/context-gatherer.js +0 -8
  41. package/lib/mission-artifact.js +504 -0
  42. package/lib/mission-room.js +846 -0
  43. package/lib/next-moves.js +212 -6
  44. package/lib/operator-next.js +7 -0
  45. package/lib/pulse.js +78 -4
  46. package/lib/runner-command.js +51 -20
  47. package/lib/runs-prune.js +242 -0
  48. package/lib/task-db.js +19 -2
  49. package/lib/task-proof.js +1 -1
  50. package/package.json +4 -4
  51. package/decks/atris-seed-pitch-v3.json +0 -118
  52. package/decks/atris-seed-pitch-v4-skeleton.json +0 -106
  53. package/decks/atris-seed-pitch-v5.json +0 -109
  54. package/decks/atris-seed-pitch-v6.json +0 -137
  55. package/decks/atris-seed-pitch-v7.json +0 -133
  56. package/decks/mark-pincus-narrative.json +0 -102
  57. package/decks/mark-pincus-sourcery.json +0 -94
  58. package/decks/yash-applied-compute-detailed.json +0 -150
  59. package/decks/yash-applied-compute-generalist.json +0 -82
  60. package/decks/yash-applied-compute-narrative.json +0 -54
  61. package/lib/ax-chat-input.js +0 -164
  62. package/lib/ax-goal.js +0 -307
  63. package/lib/ax-prefs.js +0 -63
  64. package/lib/ax-shimmer.js +0 -63
package/commands/task.js CHANGED
@@ -8,13 +8,14 @@ const http = require('http');
8
8
  const path = require('path');
9
9
  const os = require('os');
10
10
  const { taskProofState, buildVerifiedProof } = require('../lib/task-proof');
11
- const { evaluateAutoAccept, parseVerifyCommand } = require('../lib/auto-accept-certified');
11
+ const { evaluateAutoAccept, parseVerifyCommand, runVerifyCommand, DENIED_TAGS } = require('../lib/auto-accept-certified');
12
12
  const { extractReceiptEvidence } = require('../lib/receipt-evidence');
13
13
  const escapeRegExp = require('../lib/escape-regexp');
14
14
  const {
15
15
  normalizeOwnerSlug,
16
16
  resolveFunctionalOwner: resolveFunctionalTaskOwner,
17
17
  } = require('../lib/functional-owner');
18
+ const { operatorReady, hasAgentJargon } = require('./autoland');
18
19
 
19
20
  const DEFAULT_OWNER = process.env.ATRIS_AGENT_ID
20
21
  || process.env.USER
@@ -90,6 +91,14 @@ function getTaskDb() {
90
91
  }
91
92
  }
92
93
 
94
+ function warnIfTaskTitleNeedsOperatorWhy(title) {
95
+ const text = String(title || '').trim();
96
+ if (!text || operatorReady(text)) return null;
97
+ const warning = 'Warning: add the why in plain words to this task title: what it buys or costs, who benefits, and no flags or identifiers.';
98
+ console.error(warning);
99
+ return warning;
100
+ }
101
+
93
102
  function taskUsageText() {
94
103
  return `
95
104
  atris task - durable local task state (SQLite, gitignored)
@@ -113,6 +122,8 @@ atris task - durable local task state (SQLite, gitignored)
113
122
  atris task review-chat <id> [--as <owner>] Start a task-owned /codex verification chat
114
123
  atris task accept <id> [--proof "..."] [--public]
115
124
  Human accepts proof, marks done; --public also publishes AgentXP
125
+ atris task certify-verified [--dry-run] [--limit <n>] [--as <actor>]
126
+ 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)
116
127
  atris task auto-accept-certified --dry-run [--strict-verify] [--limit <n>]
117
128
  Preview certified Review rows; live accept needs --confirm-human-accept --as <human>
118
129
  atris task revise <id> --note "..." Send reviewed work back to Do
@@ -150,8 +161,8 @@ atris task - durable local task state (SQLite, gitignored)
150
161
  atris task finish <id> --proof "..." Legacy alias for done with proof
151
162
  atris task review <id> --reward <n> [--verify "<cmd>"]
152
163
  Write review event + RSI episode
153
- atris task reviews [--limit <n>] Show certified Review items for human accept/revise
154
- atris task reviews --group-by <tag|owner|source> Cluster ready-to-land work for fast triage
164
+ atris task reviews [--all|--limit <n>] [--verbose] Show certified Review items for human accept/revise
165
+ atris task reviews --group-by <tag|owner|source> Cluster approval-ready work for fast triage
155
166
  atris task accept-group <key>=<value> --spot-check K --confirm-human-accept --as <you> --verified <ids>
156
167
  Accept a whole cluster; career XP only on the K you verified
157
168
  atris task status [--json] [--history] Compact live status for web/Swarlo
@@ -284,6 +295,63 @@ function refreshCareerXpAfterReview(reviewed) {
284
295
  return refreshCareerXpProjection(reviewed?.episode?.workspace_root);
285
296
  }
286
297
 
298
+ function compactCareerXpProjection(projection) {
299
+ if (!projection || typeof projection !== 'object') return projection;
300
+ const progress = projection.next_level_progress && typeof projection.next_level_progress === 'object'
301
+ ? {
302
+ level: projection.next_level_progress.level ?? null,
303
+ next_level: projection.next_level_progress.next_level ?? null,
304
+ current_xp: projection.next_level_progress.current_xp ?? null,
305
+ required_xp: projection.next_level_progress.required_xp ?? null,
306
+ remaining_xp: projection.next_level_progress.remaining_xp ?? null,
307
+ percent: projection.next_level_progress.percent ?? null,
308
+ }
309
+ : null;
310
+ const latest = projection.latest_accepted_proof && typeof projection.latest_accepted_proof === 'object'
311
+ ? {
312
+ label: projection.latest_accepted_proof.label ?? null,
313
+ receipt_id: projection.latest_accepted_proof.receipt_id ?? null,
314
+ source: projection.latest_accepted_proof.source ?? null,
315
+ source_task_id: projection.latest_accepted_proof.source_task_id ?? null,
316
+ title: projection.latest_accepted_proof.title ?? null,
317
+ xp: projection.latest_accepted_proof.xp ?? null,
318
+ reward: projection.latest_accepted_proof.reward ?? null,
319
+ accepted_at: projection.latest_accepted_proof.accepted_at ?? null,
320
+ goal: projection.latest_accepted_proof.goal ?? null,
321
+ }
322
+ : null;
323
+ const integrity = projection.integrity && typeof projection.integrity === 'object'
324
+ ? {
325
+ status: projection.integrity.status ?? null,
326
+ receipts_count: projection.integrity.receipts_count ?? projection.receipts_count ?? null,
327
+ head_hash: projection.integrity.head_hash ?? null,
328
+ }
329
+ : null;
330
+ return {
331
+ schema: projection.schema,
332
+ compact: true,
333
+ total_xp: projection.total_xp ?? projection.total_agent_xp ?? projection.agent_xp ?? null,
334
+ agent_xp: projection.agent_xp ?? projection.total_agent_xp ?? null,
335
+ today_xp: projection.today_xp ?? projection.today_agent_xp ?? null,
336
+ collected_receipts: projection.collected_receipts ?? 0,
337
+ receipts_count: projection.receipts_count ?? null,
338
+ level: projection.level ?? null,
339
+ leaderboard_eligible: projection.leaderboard_eligible ?? null,
340
+ integrity_status: projection.integrity_status ?? integrity?.status ?? null,
341
+ next_level_progress: progress,
342
+ latest_accepted_proof: latest,
343
+ integrity,
344
+ omitted: [
345
+ 'contribution_graph',
346
+ 'career',
347
+ 'earning_model',
348
+ 'local_activity',
349
+ 'ledger',
350
+ 'sources',
351
+ ],
352
+ };
353
+ }
354
+
287
355
  function jsonModeActive() {
288
356
  return process.argv.includes('--json');
289
357
  }
@@ -317,13 +385,36 @@ function textFlag(args, names) {
317
385
 
318
386
  function landingFlags(args) {
319
387
  return {
320
- happened: textFlag(args, ['--happened', '--what-happened']),
388
+ happened: textFlag(args, ['--landing', '--happened', '--what-happened']),
321
389
  checked: textFlag(args, ['--checked', '--how-checked']),
322
390
  tested: textFlag(args, ['--tested', '--what-tested']),
323
391
  decision: textFlag(args, ['--decision', '--acceptance']),
324
392
  };
325
393
  }
326
394
 
395
+ function normalizedLandingSentence(value) {
396
+ return String(value || '').replace(/\s+/g, ' ').trim();
397
+ }
398
+
399
+ function defaultLandingSentenceForTitle(title) {
400
+ return `Completed: ${String(title || '').trim()}.`;
401
+ }
402
+
403
+ function landingNeedsDayOnePm(sentence, title) {
404
+ const text = normalizedLandingSentence(sentence);
405
+ if (!text) return true;
406
+ if (text.toLowerCase() === normalizedLandingSentence(defaultLandingSentenceForTitle(title)).toLowerCase()) return true;
407
+ return hasAgentJargon(text) || !operatorReady(text);
408
+ }
409
+
410
+ function warnIfLandingNeedsDayOnePm(landing, title) {
411
+ const sentence = landing && typeof landing === 'object' ? landing.happened : '';
412
+ if (!landingNeedsDayOnePm(sentence, title)) return null;
413
+ const warning = 'Advisory: add --landing with one capability sentence a day-one PM could read, with the result in plain words and no flags or identifiers.';
414
+ console.error(warning);
415
+ return warning;
416
+ }
417
+
327
418
  function numericFlag(args, name) {
328
419
  const value = flag(args, name);
329
420
  if (value === null || value === true || value === undefined) return null;
@@ -540,18 +631,19 @@ function buildPendingReviewChatPredicate(taskDb, db, workspaceRoot) {
540
631
  function createReviewNextTask(taskDb, db, currentTask, title) {
541
632
  const nextTitle = String(title || '').trim();
542
633
  if (!nextTitle) return null;
634
+ const operatorTitleWarning = warnIfTaskTitleNeedsOperatorWhy(nextTitle);
543
635
  const currentMetadata = currentTask && currentTask.metadata && typeof currentTask.metadata === 'object'
544
636
  ? currentTask.metadata
545
637
  : {};
546
638
  const existing = findExistingReviewNextTask(taskDb, db, currentTask, nextTitle);
547
- if (existing) return { id: existing.id, inserted: false };
639
+ if (existing) return { id: existing.id, inserted: false, operator_title_warning: operatorTitleWarning };
548
640
  const goalId = currentMetadata.goal_id || currentMetadata.goalId || null;
549
641
  const parentId = currentTask && currentTask.id || null;
550
642
  const sourceKey = parentId && typeof taskDb.sourceKey === 'function'
551
643
  ? taskDb.sourceKey(`task_review_next:${parentId}`, nextTitle)
552
644
  : null;
553
645
  try {
554
- return taskDb.addTask(db, {
646
+ const created = taskDb.addTask(db, {
555
647
  title: nextTitle,
556
648
  tag: currentTask && currentTask.tag || null,
557
649
  workspaceRoot: taskDb.workspaceRoot(),
@@ -562,10 +654,11 @@ function createReviewNextTask(taskDb, db, currentTask, title) {
562
654
  source: 'task_review_next',
563
655
  },
564
656
  });
657
+ return { ...created, operator_title_warning: operatorTitleWarning };
565
658
  } catch (error) {
566
659
  if (sourceKey && /constraint|unique/i.test(String(error && (error.code || error.message) || error))) {
567
660
  const racedExisting = findExistingReviewNextTask(taskDb, db, currentTask, nextTitle);
568
- if (racedExisting) return { id: racedExisting.id, inserted: false };
661
+ if (racedExisting) return { id: racedExisting.id, inserted: false, operator_title_warning: operatorTitleWarning };
569
662
  }
570
663
  throw error;
571
664
  }
@@ -678,47 +771,103 @@ function reviewSummary(task, payload = {}) {
678
771
  || careerText.includes('agent xp')
679
772
  ) {
680
773
  if (task.status === 'done') {
681
- return `Landed AgentXP result for ${plainTitle}.`;
774
+ return `Completed AgentXP result for ${plainTitle}.`;
682
775
  }
683
776
  if (task.status === 'review') {
684
- return `${plainTitle} is ready to land; land only if the proof is real.`;
777
+ return `${plainTitle}: proof is ready for human approval; approve only if the evidence is real.`;
685
778
  }
686
- return `This explains what landing ${plainTitle} would make real for AgentXP.`;
779
+ return `This explains the AgentXP result ${plainTitle} would make real.`;
687
780
  }
688
781
  if (task.status === 'done') {
689
- return `Landed result for ${plainTitle}.`;
782
+ return `Completed result for ${plainTitle}.`;
690
783
  }
691
784
  if (task.status === 'review') {
692
- return `${plainTitle} is ready to land; land it or send it back.`;
785
+ return `${plainTitle}: review the completed result, then approve or ask for rework.`;
693
786
  }
694
- return `This explains what landing ${plainTitle} would make real.`;
787
+ return `This explains what ${plainTitle} would make real.`;
695
788
  }
696
789
 
697
790
  function titleToResultText(title) {
698
791
  const text = String(title || 'this task').replace(/\s+/g, ' ').trim();
699
- if (!text) return 'This task is ready to review.';
792
+ if (!text) return 'Completed this task.';
793
+ const resultSentence = (prefix, body) => {
794
+ const clean = String(body || '').replace(/\s+/g, ' ').trim().replace(/[.!?]+$/, '');
795
+ return clean ? `${prefix} ${clean}.` : `${prefix}.`;
796
+ };
797
+ const missionXp = text.match(/^Mission XP:\s*(.+)$/i);
798
+ if (missionXp) {
799
+ const missionResult = titleToResultText(missionXp[1]).replace(/[.!?]+$/, '');
800
+ return resultSentence('Completed mission work:', missionResult.replace(/^Completed:\s*/i, ''));
801
+ }
700
802
  const [first, ...restParts] = text.split(' ');
701
803
  const rest = restParts.join(' ');
804
+ const firstLower = String(first || '').toLowerCase();
805
+ const compound = rest.match(/^and\s+([a-z]+)\s+(.+)$/i);
806
+ const compoundPast = compound ? ({
807
+ audit: { close: 'Audited and closed' },
808
+ decide: { start: 'Decided and started' },
809
+ prune: { compress: 'Pruned and compressed' },
810
+ }[firstLower] || {})[String(compound[1] || '').toLowerCase()] : null;
811
+ if (compoundPast && compound[2]) return resultSentence(compoundPast, compound[2]);
702
812
  const past = {
703
813
  add: 'Added',
704
814
  approve: 'Prepared approval for',
815
+ audit: 'Audited',
816
+ batch: 'Batched',
705
817
  build: 'Built',
706
818
  clean: 'Cleaned',
707
819
  create: 'Created',
820
+ decide: 'Decided',
708
821
  design: 'Designed',
709
822
  document: 'Documented',
823
+ find: 'Found',
710
824
  fix: 'Fixed',
825
+ heal: 'Healed',
826
+ ignore: 'Ignored',
827
+ infer: 'Inferred',
828
+ keep: 'Kept',
711
829
  make: 'Made',
830
+ number: 'Numbered',
831
+ prune: 'Pruned',
832
+ reconcile: 'Reconciled',
833
+ refresh: 'Refreshed',
834
+ render: 'Rendered',
835
+ repair: 'Repaired',
712
836
  replace: 'Replaced',
837
+ respect: 'Respected',
713
838
  route: 'Routed',
714
839
  run: 'Ran',
715
840
  ship: 'Shipped',
841
+ show: 'Showed',
842
+ stop: 'Stopped',
843
+ summarize: 'Summarized',
844
+ suppress: 'Suppressed',
845
+ trim: 'Trimmed',
716
846
  update: 'Updated',
847
+ use: 'Used',
717
848
  validate: 'Validated',
718
849
  wire: 'Wired',
719
- }[String(first || '').toLowerCase()];
720
- if (past && rest) return `${past} ${rest}.`;
721
- return `${text} is ready to review.`;
850
+ }[firstLower];
851
+ if (past && rest) return resultSentence(past, rest);
852
+ return resultSentence('Completed:', text);
853
+ }
854
+
855
+ function proofToReasonText(proof) {
856
+ const text = String(proof || '').replace(/\s+/g, ' ').trim();
857
+ if (!text) return '';
858
+ const label = /\b(?:Product proof|Why it matters)\s*:\s*/i.exec(text);
859
+ if (!label) return '';
860
+ const rest = text.slice(label.index + label[0].length);
861
+ const stop = rest.search(/(?:^|[.;]\s+)\b(?:Checks?|Mission receipt|Receipt|Landing|Changed|How I checked|What I tested|Saved|Decision)\s*:/i);
862
+ let section = (stop >= 0 ? rest.slice(0, stop) : rest)
863
+ .replace(/^(?:[-:]\s*)+/, '')
864
+ .replace(/\s+/g, ' ')
865
+ .replace(/\s*[;,:-]\s*$/, '')
866
+ .trim();
867
+ const sentenceStop = section.search(/[.!?]\s+/);
868
+ if (sentenceStop >= 0) section = section.slice(0, sentenceStop + 1).trim();
869
+ if (!section) return '';
870
+ return /[.!?]$/.test(section) ? section : `${section}.`;
722
871
  }
723
872
 
724
873
  function titleToReasonText(task, proof = '') {
@@ -737,35 +886,102 @@ function titleToReasonText(task, proof = '') {
737
886
  return 'It keeps real-world side effects behind a clear human decision.';
738
887
  }
739
888
  if (/\btest|self-test|harness|verifier|proof\b/.test(text)) {
740
- return 'It makes the check repeatable before the work lands.';
889
+ return 'It gives the human a repeatable check before approval.';
741
890
  }
742
- return 'It closes the user-visible gap named by the task.';
891
+ return 'It turns the task title into a concrete result the human can approve.';
743
892
  }
744
893
 
745
894
  function proofToHumanCheck(proof) {
746
895
  const text = String(proof || '').replace(/\s+/g, ' ').trim();
747
896
  if (!text) return 'Proof is attached below.';
748
897
  const lower = text.toLowerCase();
898
+ const passSignal = /\b(?:pass(?:es|ed|ing)?|ok|green|succeeded|verified)\b/.test(lower);
899
+ const verifierCommandSignal = /\b(?:npm|node|git|atris|npx|pnpm|yarn|python3?|pytest|bash|sh|tsc|vitest|curl|gh|rg)\s+\S+/.test(lower);
749
900
  if (/\b(?:atris\s+)?task\s+(?:show|page)\b/.test(lower) && /\b(?:print|prints|printed|render|renders|rendered|show|shows|showed)\b/.test(lower)) {
750
901
  return 'I opened the real task screen and checked the receipt renders before raw proof.';
751
902
  }
752
- if (/git\s+diff\s+--check/.test(lower) && /\bpass(?:ed)?\b/.test(lower)) {
903
+ if (/git\s+diff\s+--check/.test(lower) && passSignal) {
753
904
  if (/node\s+--test/.test(lower)) return 'I ran the behavior check and the diff cleanliness check.';
754
905
  return 'I ran the diff cleanliness check.';
755
906
  }
756
- if (/\btypecheck\b/.test(lower) && /\bpass(?:ed)?\b/.test(lower)) return 'I ran the typecheck.';
757
- if (/\bbuild\b/.test(lower) && /\bpass(?:ed)?\b/.test(lower)) return 'I ran the build check.';
758
- if (/\btest\b/.test(lower) && /\bpass(?:ed)?\b/.test(lower)) return 'I ran the verifier named in the proof.';
759
- if (/\bvalidat(?:e|ion|ed)\b/.test(lower) && /\bpass(?:ed)?\b/.test(lower)) return 'I ran the validation check.';
907
+ if (/\btypecheck\b/.test(lower) && passSignal) return 'I ran the typecheck.';
908
+ if (/\bbuild\b/.test(lower) && passSignal) return 'I ran the build check.';
909
+ if (/\btest\b/.test(lower) && passSignal) return 'I ran the verifier named in the proof.';
910
+ if (/\bvalidat(?:e|ion|ed)\b/.test(lower) && passSignal) return 'I ran the validation check.';
911
+ if (verifierCommandSignal && /\b(?:print|prints|printed|show|shows|showed|report|reports|reported|return|returns|returned)\b/.test(lower)) {
912
+ return 'I checked the verifier output named in the proof.';
913
+ }
914
+ if (verifierCommandSignal && passSignal) return 'I ran the verifier named in the proof.';
915
+ if (/atris\/runs\/[^\s),.;:]+\.json/.test(text)) {
916
+ return passSignal
917
+ ? 'I inspected the passing receipt named in the proof.'
918
+ : 'I inspected the receipt named in the proof.';
919
+ }
920
+ if (/(?:^|\s)(?:\.{0,2}\/)?scripts\/[^\s),.;:]+\.(?:js|mjs|cjs|py|sh)\b/.test(text)) {
921
+ return 'I inspected the verifier artifact named in the proof.';
922
+ }
760
923
  if (/\breview(?:ed)?\b/.test(lower)) return 'Review proof is attached below.';
761
924
  return 'Proof is attached below.';
762
925
  }
763
926
 
927
+ function taskReviewFormatCommandList(commands) {
928
+ const list = Array.isArray(commands)
929
+ ? commands.map(command => String(command || '').trim()).filter(Boolean)
930
+ : [];
931
+ if (!list.length) return '';
932
+ if (list.length > 1) {
933
+ return list.map((command, index) => `command ${index + 1}: ${command}`).join('; ');
934
+ }
935
+ return list[0];
936
+ }
937
+
938
+ function taskReviewProseCheckLabels(proof) {
939
+ const text = String(proof || '').replace(/\s+/g, ' ').trim();
940
+ if (!text) return [];
941
+ const lower = text.toLowerCase();
942
+ const hasPass = /\b(?:pass(?:es|ed|ing)?|green|verified|current|clean|complete|completed)\b/.test(lower);
943
+ if (!hasPass) return [];
944
+ const labels = [];
945
+ const add = label => {
946
+ if (!labels.includes(label)) labels.push(label);
947
+ };
948
+ if (/\bhelp output\b/.test(lower)) add('help output');
949
+ if (/\b(?:wiki verify|public wiki verify|loop wiki)\b/.test(lower)) add('wiki verification');
950
+ if (/\bclean dry-run\b/.test(lower)) add('clean dry-run');
951
+ if (/\b(?:test slice|regression run|tests?|test)\b.*\bpass(?:es|ed|ing)?\b/.test(lower)
952
+ || /\bpass(?:es|ed|ing)?\b.*\b(?:test slice|regression run|tests?|test)\b/.test(lower)) {
953
+ add('passing tests');
954
+ }
955
+ if (/\bverifier\b.*\bpass(?:es|ed|ing)?\b/.test(lower)
956
+ || /\bpass(?:es|ed|ing)?\b.*\bverifier\b/.test(lower)) {
957
+ add('passing verifier');
958
+ }
959
+ if (/\bmission\b.*\bcomplete(?:d)?\b/.test(lower)) add('completed mission');
960
+ if (/\bgit diff --check\b/.test(lower) || /\bdiff cleanliness\b/.test(lower)) add('diff cleanliness');
961
+ return labels;
962
+ }
963
+
764
964
  function taskReviewLandingTested(proof) {
765
- const commands = taskReviewEvidenceCommands(proof, 3);
766
- if (commands.length) return `I ran: ${commands.join(' | ')}.`;
767
- const paths = taskReviewEvidencePaths(proof, 3);
768
- if (paths.length) return `I inspected: ${paths.join(', ')}.`;
965
+ const maxVisible = 3;
966
+ const commands = taskReviewEvidenceCommands(proof, 20);
967
+ if (commands.length) {
968
+ return commands.length === 1
969
+ ? 'I ran the listed check for this result.'
970
+ : 'I ran the listed checks for this result.';
971
+ }
972
+ const paths = taskReviewEvidencePaths(proof, 20);
973
+ if (paths.length) {
974
+ return paths.length === 1
975
+ ? 'I inspected the artifact named in the proof.'
976
+ : `I inspected ${paths.length} artifacts named in the proof.`;
977
+ }
978
+ const proseChecks = taskReviewProseCheckLabels(proof);
979
+ if (proseChecks.length) {
980
+ const visible = proseChecks.slice(0, maxVisible);
981
+ const omitted = proseChecks.length - visible.length;
982
+ const suffix = omitted > 0 ? `, and ${omitted} more ${omitted === 1 ? 'check' : 'checks'}` : '';
983
+ return `I checked: ${visible.join(', ')}${suffix}.`;
984
+ }
769
985
  return String(proof || '').trim() ? 'I attached the proof below.' : 'No verifier command recorded yet.';
770
986
  }
771
987
 
@@ -793,16 +1009,16 @@ function taskReviewLanding(task, review = {}, payload = {}) {
793
1009
  || payload.reason || payload.why || metadata.result_reason || metadata.review_reason || metadata.why_it_matters;
794
1010
  return {
795
1011
  happened: clipStatusText(explicitHappened || titleToResultText(task.title), 220),
796
- reason: clipStatusText(explicitReason || titleToReasonText(task, proof), 220),
1012
+ reason: clipStatusText(explicitReason || proofToReasonText(proof) || titleToReasonText(task, proof), 220),
797
1013
  checked: clipStatusText(explicitChecked || proofToHumanCheck(proof), 220),
798
1014
  tested: clipStatusText(explicitTested || taskReviewLandingTested(proof), 260),
799
1015
  decision: clipStatusText(explicitDecision || (task.status === 'done'
800
- ? 'Landed. No action needed unless this regresses.'
1016
+ ? 'Done. No action needed unless this regresses.'
801
1017
  : approvalStatus === 'revise'
802
1018
  ? 'Rework requested. Fix the note, then send a new receipt.'
803
1019
  : approvalStatus === 'pending' && !agentCertified
804
- ? 'Needs one more check before landing; send it back if the receipt misses the point.'
805
- : 'Land it if this matches the request; send it back if not.'), 220),
1020
+ ? 'Needs one more check; ask for rework if the receipt misses the point.'
1021
+ : 'Approve if this matches the request; ask for rework if not.'), 220),
806
1022
  };
807
1023
  }
808
1024
 
@@ -815,13 +1031,20 @@ function taskReviewResult(task, review = {}, payload = {}) {
815
1031
  changed: landing.happened,
816
1032
  reason: landing.reason,
817
1033
  checked: landing.checked,
818
- saved: clipStatusText(explicitSaved || (task.status === 'done'
819
- ? `Landed as ${ref}.`
820
- : `Ready to land as ${ref}.`), 180),
1034
+ saved: clipStatusText(explicitSaved || taskReviewSavedText(task, review, ref), 180),
821
1035
  accept: landing.decision,
822
1036
  };
823
1037
  }
824
1038
 
1039
+ function taskReviewSavedText(task, review = {}, ref = taskRef(task)) {
1040
+ if (task.status === 'done') return `Result accepted as ${ref}.`;
1041
+ const metadata = task.metadata || {};
1042
+ const agentCertified = review.agent_certified === true || metadata.agent_certified === true;
1043
+ if (task.status === 'review' && agentCertified) return `Result is ready for human approval as ${ref}.`;
1044
+ if (task.status === 'review') return `Completed result saved for review as ${ref}.`;
1045
+ return `Work record saved as ${ref}.`;
1046
+ }
1047
+
825
1048
  function taskReviewSummary(task) {
826
1049
  const reviewed = (task.events || []).slice().reverse().find(e => e.event_type === 'reviewed' || e.event_type === 'proof_ready' || e.event_type === 'revision_requested');
827
1050
  const payload = reviewed && reviewed.payload || {};
@@ -978,6 +1201,8 @@ function buildTaskStreams(tasks, goals) {
978
1201
  if (column === 'doing') stream.doing_count += 1;
979
1202
  if (column === 'blocked') stream.blocked_count += 1;
980
1203
  if (column === 'review') stream.review_count += 1;
1204
+ const review = task.review || {};
1205
+ const metadata = task.metadata || {};
981
1206
  stream.tasks.push({
982
1207
  id: task.id,
983
1208
  title: task.title,
@@ -987,7 +1212,7 @@ function buildTaskStreams(tasks, goals) {
987
1212
  assigned_to: taskAssignee(task),
988
1213
  parent_task_id: task.lineage && task.lineage.parent_task_id || null,
989
1214
  child_task_ids: task.lineage && task.lineage.child_task_ids || [],
990
- proof: task.review && task.review.proof || null,
1215
+ proof: review.proof || metadata.latest_agent_proof || null,
991
1216
  });
992
1217
  }
993
1218
  for (const goal of goals) {
@@ -1292,17 +1517,142 @@ function taskReviewCommandLooksSpecific(command) {
1292
1517
  if (/^atris\s+task\s+\w+\s*$/i.test(text)) return false;
1293
1518
  if (/^atris\s+task\s+(?:accept|auto-accept-certified)\b/i.test(text)) return false;
1294
1519
  if (/^atris[/.]/i.test(text)) return false;
1520
+ if (/^atris-\S+/i.test(text)) return false;
1295
1521
  if (/^atris\s+task\s+\w+\s+json\s*$/i.test(text) && !/--json\b/i.test(text)) return false;
1296
1522
  if (/^atris\s+(?:command|review-chat|smoke|temp)\b/i.test(text)) return false;
1297
1523
  if (/^(?:npm|npx|pnpm|yarn|python3?|pytest|bash|sh|tsc|vitest|curl|gh|rg|git)\s+commands?\b/i.test(text)) return false;
1298
1524
  if (/^(?:npm|pnpm|yarn)\s+(?:tests|checks?)$/i.test(text)) return false;
1299
1525
  if (/^(?:git|gh|rg|curl|bash|sh|tsc)\s+(?:tests?|checks?)$/i.test(text)) return false;
1300
- if (/\s+(?:and|then)\s+\S+/i.test(text)) return false;
1526
+ if (/\s+(?:and|then)\s+\S+/i.test(taskReviewWithoutQuotedSegments(text))) return false;
1301
1527
  if (/^node\s+(?!-|\S*(?:[/.]))/i.test(text)) return false;
1302
1528
  if (/^node\s+--test\s+[\w-]+(?:\s+[\w-]+)+$/i.test(text)) return false;
1303
1529
  return true;
1304
1530
  }
1305
1531
 
1532
+ function taskReviewUnescapedQuoteCount(text, quote) {
1533
+ let count = 0;
1534
+ const source = String(text || '');
1535
+ for (let index = 0; index < source.length; index += 1) {
1536
+ if (source[index] === quote && source[index - 1] !== '\\') count += 1;
1537
+ }
1538
+ return count;
1539
+ }
1540
+
1541
+ function taskReviewCommandStartsInsideQuote(clause, commandIndex) {
1542
+ const before = String(clause || '').slice(0, Math.max(0, commandIndex));
1543
+ return taskReviewUnescapedQuoteCount(before, "'") % 2 === 1
1544
+ || taskReviewUnescapedQuoteCount(before, '"') % 2 === 1;
1545
+ }
1546
+
1547
+ function taskReviewWithoutQuotedSegments(text) {
1548
+ const source = String(text || '');
1549
+ let quote = null;
1550
+ let out = '';
1551
+ for (let index = 0; index < source.length; index += 1) {
1552
+ const char = source[index];
1553
+ if ((char === "'" || char === '"') && source[index - 1] !== '\\') {
1554
+ quote = quote === char ? null : (!quote ? char : quote);
1555
+ out += ' ';
1556
+ continue;
1557
+ }
1558
+ out += quote ? ' ' : char;
1559
+ }
1560
+ return out;
1561
+ }
1562
+
1563
+ function taskReviewTrimOutsideQuotedTail(text, pattern) {
1564
+ const source = String(text || '');
1565
+ const flags = pattern.flags.includes('g') ? pattern.flags : `${pattern.flags}g`;
1566
+ const scanner = new RegExp(pattern.source, flags);
1567
+ let match;
1568
+ while ((match = scanner.exec(source)) !== null) {
1569
+ if (!taskReviewCommandStartsInsideQuote(source, match.index)) {
1570
+ return source.slice(0, match.index);
1571
+ }
1572
+ if (match[0] === '') scanner.lastIndex += 1;
1573
+ }
1574
+ return source;
1575
+ }
1576
+
1577
+ function taskReviewTrimTrailingOutsideQuote(text) {
1578
+ let clean = String(text || '');
1579
+ while (clean.length > 0) {
1580
+ const index = clean.length - 1;
1581
+ if (!/[\]),.;:]/.test(clean[index])) break;
1582
+ if (taskReviewCommandStartsInsideQuote(clean, index)) break;
1583
+ clean = clean.slice(0, index);
1584
+ }
1585
+ return clean;
1586
+ }
1587
+
1588
+ function taskReviewCleanEvidenceCommand(command) {
1589
+ let clean = String(command || '');
1590
+ const tailPatterns = [
1591
+ /\s+from\s+[^,;]*(?:showed|shows|showing|returned|returns|printed|prints|wrote|writes|reported|reports)\b.*$/i,
1592
+ /\s+(?:showed|shows|showing|returned|returns|printed|prints|wrote|writes|reported|reports)\b.*$/i,
1593
+ /\.\s+(?:Reward remains|No human|Human accept|AgentXP|XP)\b.*$/i,
1594
+ /\s+with\s+\d+\s+(?:passing\s+)?(?:tests?|checks?|passes|passed|pass|ok|clean)\b.*$/i,
1595
+ /\s+\((?:passed|ok|clean|failed|errored|timed out|succeeded|succeeds|successful|confirmed)\)$/i,
1596
+ /\s+\(?(?:exit|status|code)\s+\d+\)?$/i,
1597
+ /\s+\(?(?:passed|ok|clean|failed|errored|timed out|succeeded|succeeds|successful|confirmed)\s+\d+\/\d+(?:[,.]\s+.*)?$/i,
1598
+ /\s+\(?(?:passed|ok|clean|failed|errored|timed out|succeeded|succeeds|successful|confirmed)\s+\d+\/\d+(?:\s+(?:tests?|checks?|passed|pass|ok|clean|failed|failures?))?$/i,
1599
+ /\s+\(?(?:passed|ok|clean|failed|errored|timed out|succeeded|succeeds|successful|confirmed)(?:[,.]\s+.*|\s+and\b.*)$/i,
1600
+ /\s+\(?(?:passed|ok|clean|failed|errored|timed out|succeeded|succeeds|successful|confirmed)\s+with\b.*$/i,
1601
+ /,\s+(?:command suite|mission-status tests?|live\b|focused suite|clean dry-run|brain compile)\b.*$/i,
1602
+ /\.\s+(?:The|This|It|Focused|Dogfood|Landing|Product|Review|Receipt|Human|No|Note)\b.*$/i,
1603
+ /\s+\(?\d+\s+(?:passes|passed|pass|ok|clean|tests?|checks?)\)?$/i,
1604
+ /\s+only$/i,
1605
+ /\s+\(?(?:passed|ok|clean|failed|errored|timed out|succeeded|succeeds|successful|confirmed)(?:[.:]\s+.*|\s+after\b.*)$/i,
1606
+ /\s+\(?(?:passed|ok|clean|failed|errored|timed out|succeeded|succeeds|successful|confirmed)\)?$/i,
1607
+ /\s+\(?\d+\/\d+(?:\s+(?:tests?|checks?|passed|pass|ok|clean|failed|failures?))?\)?$/i,
1608
+ /\s+\d+\/\d+(?:\s+(?:tests?|checks?|passed|pass|ok|clean|failed|failures?))?$/i,
1609
+ ];
1610
+ for (const pattern of tailPatterns) {
1611
+ clean = taskReviewTrimOutsideQuotedTail(clean, pattern);
1612
+ clean = taskReviewTrimTrailingOutsideQuote(clean);
1613
+ }
1614
+ return clean.replace(/\s+/g, ' ').trim();
1615
+ }
1616
+
1617
+ function taskReviewSplitEvidenceClauses(source, commandStart) {
1618
+ const text = String(source || '');
1619
+ const hardBoundaryPattern = /^(?:;\s*|\n\s*|\s+&&\s+)/;
1620
+ const softBoundaryPattern = new RegExp(`^(?:,\\s+|\\.\\s+|\\s+and\\s+|\\s+then\\s+|\\.\\s+(?:focused|full|behavior|verifier)[^\\n.;:]{0,80}:\\s+)(?=${commandStart})`, 'i');
1621
+ const clauses = [];
1622
+ let clause = '';
1623
+ let quote = null;
1624
+ for (let index = 0; index < text.length;) {
1625
+ const char = text[index];
1626
+ if ((char === "'" || char === '"') && text[index - 1] !== '\\') {
1627
+ if (quote === char) quote = null;
1628
+ else if (!quote) quote = char;
1629
+ clause += char;
1630
+ index += 1;
1631
+ continue;
1632
+ }
1633
+ if (!quote) {
1634
+ const rest = text.slice(index);
1635
+ const boundary = rest.match(hardBoundaryPattern) || rest.match(softBoundaryPattern);
1636
+ if (boundary) {
1637
+ if (clause.trim()) clauses.push(clause);
1638
+ clause = '';
1639
+ index += boundary[0].length;
1640
+ continue;
1641
+ }
1642
+ }
1643
+ clause += char;
1644
+ index += 1;
1645
+ }
1646
+ if (clause.trim()) clauses.push(clause);
1647
+ return clauses;
1648
+ }
1649
+
1650
+ function taskReviewCommandIsDescribedSubject(clause, commandIndex) {
1651
+ const before = String(clause || '').slice(Math.max(0, commandIndex - 80), commandIndex);
1652
+ return /\b(?:contains?|displays?|extracts?|lists?|mentions?|names?|prints?|renders?|shows?)\s+(?:only\s+)?$/i.test(before)
1653
+ || /\b(?:including|includes?|like|such as|for example|e\.g\.)\s+$/i.test(before);
1654
+ }
1655
+
1306
1656
  function taskReviewEvidenceCommands(text, limit = 8) {
1307
1657
  const source = String(text || '').trim();
1308
1658
  if (!source) return [];
@@ -1310,17 +1660,14 @@ function taskReviewEvidenceCommands(text, limit = 8) {
1310
1660
  const envPrefix = '(?:(?:[A-Z_][A-Z0-9_]*=[^\\s,;|]+)\\s+)*';
1311
1661
  const prosePrefix = '(?:(?:rechecked|reran|re-run|run|verified|validated|passed|validation(?:\\s+passed)?|verification(?:\\s+passed)?|focused|live|scoped|installed|direct|full|current|fresh|then|and|commands?|checks?)[:\\s]+)*';
1312
1662
  const commandStart = `${prosePrefix}${envPrefix}${commandWord}\\b`;
1313
- const commandStartPattern = new RegExp(`(^|[^\\w./-])(${commandStart})`, 'i');
1663
+ const commandStartPattern = new RegExp(`(^|[^\\w./=-])(${commandStart})`, 'i');
1314
1664
  const commandStartInnerPattern = new RegExp(`${envPrefix}${commandWord}\\b`, 'i');
1315
- const commandBoundaryPattern = new RegExp(`(?:;\\s*|\\n\\s*|\\s+&&\\s+|,\\s+|\\.\\s+|\\s+and\\s+|\\s+then\\s+)(?=${commandStart})`, 'gi');
1316
- const clauses = source
1665
+ const normalized = source
1317
1666
  .replace(/\r/g, '\n')
1318
1667
  .replace(/```[ \t]*(?:bash|sh|shell|zsh|console|text|txt)?[ \t]*\n/gi, '\n')
1319
1668
  .replace(/```/g, '\n')
1320
- .replace(/`/g, '')
1321
- .replace(/(?:;\s*|\n\s*|\s+&&\s+)/g, '\n')
1322
- .replace(commandBoundaryPattern, '\n')
1323
- .split('\n');
1669
+ .replace(/`/g, '');
1670
+ const clauses = taskReviewSplitEvidenceClauses(normalized, commandStart);
1324
1671
  const out = [];
1325
1672
  const seen = new Set();
1326
1673
  for (const clause of clauses) {
@@ -1330,22 +1677,13 @@ function taskReviewEvidenceCommands(text, limit = 8) {
1330
1677
  const raw = clause.slice(start.index + Math.max(0, commandStartOffset));
1331
1678
  const prefix = raw.match(commandStartInnerPattern);
1332
1679
  const commandOffset = prefix && prefix.index != null ? prefix.index : 0;
1680
+ const commandIndex = start.index + Math.max(0, commandStartOffset) + Math.max(0, commandOffset);
1681
+ if (
1682
+ taskReviewCommandStartsInsideQuote(clause, commandIndex)
1683
+ || taskReviewCommandIsDescribedSubject(clause, commandIndex)
1684
+ ) continue;
1333
1685
  const command = raw.slice(Math.max(0, commandOffset));
1334
- const clean = command
1335
- .replace(/\s+from\s+[^,;]*(?:showed|shows|showing|returned|returns|printed|prints|wrote|writes|reported|reports)\b.*$/i, '')
1336
- .replace(/\s+(?:showed|shows|showing|returned|returns|printed|prints|wrote|writes|reported|reports)\b.*$/i, '')
1337
- .replace(/\.\s+(?:Reward remains|No human|Human accept|AgentXP|XP)\b.*$/i, '')
1338
- .replace(/\s+\((?:passed|ok|clean|failed|errored|timed out|succeeded|succeeds|successful|confirmed)\)$/i, '')
1339
- .replace(/\s+\(?(?:exit|status|code)\s+\d+\)?$/i, '')
1340
- .replace(/[\]),.;:]+$/g, '')
1341
- .replace(/\s+\(?(?:passed|ok|clean|failed|errored|timed out|succeeded|succeeds|successful|confirmed)\s+\d+\/\d+(?:\s+(?:tests?|checks?|passed|pass|ok|clean|failed|failures?))?$/i, '')
1342
- .replace(/\s+\(?(?:passed|ok|clean|failed|errored|timed out|succeeded|succeeds|successful|confirmed)(?:[.:]\s+.*|\s+after\b.*)$/i, '')
1343
- .replace(/\s+\(?(?:passed|ok|clean|failed|errored|timed out|succeeded|succeeds|successful|confirmed)\)?$/i, '')
1344
- .replace(/\s+\(?\d+\/\d+(?:\s+(?:tests?|checks?|passed|pass|ok|clean|failed|failures?))?\)?$/i, '')
1345
- .replace(/\s+\d+\/\d+(?:\s+(?:tests?|checks?|passed|pass|ok|clean|failed|failures?))?$/i, '')
1346
- .replace(/[\]),.;:]+$/g, '')
1347
- .replace(/\s+/g, ' ')
1348
- .trim();
1686
+ const clean = taskReviewCleanEvidenceCommand(command);
1349
1687
  if (!taskReviewCommandLooksSpecific(clean) || seen.has(clean.toLowerCase())) continue;
1350
1688
  seen.add(clean.toLowerCase());
1351
1689
  out.push(clean);
@@ -1365,13 +1703,24 @@ function taskReviewRecentThread(task, limit = 4) {
1365
1703
  .filter(message => message.content);
1366
1704
  }
1367
1705
 
1706
+ function taskReviewObjective(task) {
1707
+ const metadata = task && task.metadata || {};
1708
+ return task && (
1709
+ metadata.task_goal
1710
+ || task.title
1711
+ || metadata.goal_objective
1712
+ || metadata.objective
1713
+ || task.objective
1714
+ ) || '';
1715
+ }
1716
+
1368
1717
  function taskReviewVerificationFocus(task) {
1369
1718
  const review = task && task.review || {};
1370
1719
  const metadata = task && task.metadata || {};
1371
1720
  const proof = review.proof || metadata.latest_agent_proof || '';
1372
1721
  const lesson = review.lesson || metadata.latest_agent_lesson || '';
1373
1722
  const nextTask = review.next_task || metadata.latest_agent_next_task || '';
1374
- const objective = task && (task.objective || metadata.task_goal || metadata.goal_objective || metadata.objective) || '';
1723
+ const objective = taskReviewObjective(task);
1375
1724
  const evidenceText = [proof].filter(Boolean).join('\n');
1376
1725
  return {
1377
1726
  objective: taskReviewClip(objective, 260) || null,
@@ -1388,7 +1737,7 @@ function taskReviewSpecificCodexPrompt(task, focus, actor) {
1388
1737
  const title = taskReviewClip(task && task.title, 180);
1389
1738
  const proof = focus && focus.proof_claim ? ` Proof: ${taskReviewClip(focus.proof_claim, 1800)}` : '';
1390
1739
  const commands = focus && focus.commands_to_verify && focus.commands_to_verify.length
1391
- ? ` Commands: ${focus.commands_to_verify.join(' | ')}.`
1740
+ ? ` Commands: ${taskReviewFormatCommandList(focus.commands_to_verify)}.`
1392
1741
  : '';
1393
1742
  const files = focus && focus.files_to_inspect && focus.files_to_inspect.length
1394
1743
  ? ` Files/artifacts: ${focus.files_to_inspect.join(', ')}.`
@@ -1475,7 +1824,7 @@ function taskReviewChatContract(task, { reviewer = 'codex-review', allowCertifie
1475
1824
  const proof = review.proof || metadata.latest_agent_proof || '';
1476
1825
  const lesson = review.lesson || metadata.latest_agent_lesson || '';
1477
1826
  const nextTask = review.next_task || metadata.latest_agent_next_task || '';
1478
- const objective = task && (task.objective || metadata.task_goal || metadata.goal_objective || metadata.objective) || '';
1827
+ const objective = taskReviewObjective(task);
1479
1828
  const verificationFocus = taskReviewVerificationFocus(task);
1480
1829
  const actor = reviewActor(reviewer);
1481
1830
  return {
@@ -1501,7 +1850,7 @@ function taskReviewChatContract(task, { reviewer = 'codex-review', allowCertifie
1501
1850
  required_checks: [
1502
1851
  `Run ${`atris task show ${taskRef(task)} --json`} and read the current proof plus dialogue.`,
1503
1852
  verificationFocus.commands_to_verify.length
1504
- ? `Re-run or inspect these proof commands: ${verificationFocus.commands_to_verify.join(' | ')}.`
1853
+ ? `Re-run or inspect these proof commands: ${taskReviewFormatCommandList(verificationFocus.commands_to_verify)}.`
1505
1854
  : 'Find the concrete verifier command because the proof did not name one.',
1506
1855
  verificationFocus.files_to_inspect.length
1507
1856
  ? `Inspect these named files/artifacts before certifying: ${verificationFocus.files_to_inspect.join(', ')}.`
@@ -1755,6 +2104,17 @@ function taskStatusSummary(projection, { history = false, hasExistingReviewFollo
1755
2104
  doing_count: stream.doing_count,
1756
2105
  review_count: stream.review_count,
1757
2106
  blocked_count: stream.blocked_count,
2107
+ tasks: (stream.tasks || []).map(task => ({
2108
+ id: task.id,
2109
+ title: clipStatusText(task.title, 120),
2110
+ status: task.status,
2111
+ tag: task.tag || null,
2112
+ claimed_by: task.claimed_by || null,
2113
+ assigned_to: task.assigned_to || null,
2114
+ parent_task_id: task.parent_task_id || null,
2115
+ child_task_ids: task.child_task_ids || [],
2116
+ proof: task.proof || null,
2117
+ })),
1758
2118
  })),
1759
2119
  last_updated_at: lastUpdated ? new Date(lastUpdated).toISOString() : null,
1760
2120
  };
@@ -3999,8 +4359,19 @@ function cmdReviewLaneRun(args) {
3999
4359
  function reviewQueueLimit(args, total) {
4000
4360
  if (hasFlag(args, '--all')) return total;
4001
4361
  const raw = flag(args, '--limit');
4002
- const limit = raw && raw !== true ? Number(raw) : 12;
4003
- return Number.isFinite(limit) && limit > 0 ? Math.floor(limit) : 12;
4362
+ const limit = raw && raw !== true ? Number(raw) : 5;
4363
+ return Number.isFinite(limit) && limit > 0 ? Math.floor(limit) : 5;
4364
+ }
4365
+
4366
+ function reviewQueueVerbose(args) {
4367
+ return hasFlag(args, '--verbose') || hasFlag(args, '--details') || hasFlag(args, '--all');
4368
+ }
4369
+
4370
+ function reviewGroupTextLimit(args, total) {
4371
+ if (hasFlag(args, '--all')) return total;
4372
+ const raw = flag(args, '--limit');
4373
+ const limit = raw && raw !== true ? Number(raw) : 10;
4374
+ return Number.isFinite(limit) && limit > 0 ? Math.floor(limit) : 10;
4004
4375
  }
4005
4376
 
4006
4377
  // Risk order for human attention: named receipts that are missing/failing (0)
@@ -4175,17 +4546,23 @@ function cmdReviews(args) {
4175
4546
  printJson({ ok: true, action: 'review_groups', projection_path: outPath, groups });
4176
4547
  return;
4177
4548
  }
4178
- console.log(`READY TO LAND — grouped by ${key}`);
4179
- console.log(`${groups.total_certified} ready to land across ${groups.group_count} ${key} group(s)`);
4180
- groups.groups.forEach((g, index) => {
4549
+ console.log(`READY FOR APPROVAL — grouped by ${key}`);
4550
+ console.log(`${groups.total_certified} ready for approval across ${groups.group_count} ${key} group(s)`);
4551
+ const visibleGroups = groups.groups.slice(0, reviewGroupTextLimit(args, groups.groups.length));
4552
+ visibleGroups.forEach((g, index) => {
4181
4553
  console.log('');
4182
4554
  console.log(`${index + 1}. ${g.value} — ${g.count} task${g.count === 1 ? '' : 's'}`);
4183
4555
  g.sample_titles.forEach(title => console.log(` • ${title}`));
4184
- console.log(` land this group: ${g.accept_group_command} --confirm-human-accept --as <you>`);
4556
+ console.log(` approve this group: ${g.accept_group_command} --confirm-human-accept --as <you>`);
4185
4557
  });
4558
+ if (visibleGroups.length < groups.groups.length) {
4559
+ console.log('');
4560
+ console.log(`Showing ${visibleGroups.length}/${groups.groups.length} groups; rerun with --all for every group or --limit N to adjust.`);
4561
+ }
4186
4562
  return;
4187
4563
  }
4188
4564
  const queue = taskReviewQueue(projection, args);
4565
+ const verbose = reviewQueueVerbose(args);
4189
4566
  if (wantsJson(args)) {
4190
4567
  printJson({
4191
4568
  ok: true,
@@ -4195,10 +4572,10 @@ function cmdReviews(args) {
4195
4572
  });
4196
4573
  return;
4197
4574
  }
4198
- console.log('READY TO LAND');
4199
- console.log(`${queue.counts.certified} ready to land / ${queue.counts.blocking} need one more check / ${queue.counts.review} total waiting`);
4575
+ console.log('READY FOR APPROVAL');
4576
+ console.log(`${queue.counts.certified} ready for approval / ${queue.counts.blocking} need one more check / ${queue.counts.review} total waiting`);
4200
4577
  if (!queue.items.length) {
4201
- console.log('Nothing is ready to land.');
4578
+ console.log('Nothing is ready for approval.');
4202
4579
  return;
4203
4580
  }
4204
4581
  queue.items.forEach((item, index) => {
@@ -4216,8 +4593,8 @@ function cmdReviews(args) {
4216
4593
  if (item.result?.saved) console.log(` Saved: ${item.result.saved}`);
4217
4594
  if (item.landing.decision) console.log(` Decision: ${item.landing.decision}`);
4218
4595
  }
4219
- if (item.proof) console.log(` details: ${item.proof}`);
4220
- if (item.evidence) {
4596
+ if (verbose && item.proof) console.log(` details: ${item.proof}`);
4597
+ if (verbose && item.evidence) {
4221
4598
  item.evidence.receipts.forEach((receipt) => {
4222
4599
  const verdict = receipt.verifier_passed === true ? ' verifier:passed'
4223
4600
  : receipt.verifier_passed === false ? ' verifier:FAILED' : '';
@@ -4225,15 +4602,15 @@ function cmdReviews(args) {
4225
4602
  });
4226
4603
  item.evidence.missing.forEach((missingPath) => console.log(` receipt: ${missingPath} MISSING`));
4227
4604
  }
4228
- if (item.review_chat_command) console.log(` /codex: ${item.review_chat_command}`);
4605
+ if (verbose && item.review_chat_command) console.log(` /codex: ${item.review_chat_command}`);
4229
4606
  if (item.continue_work_command) console.log(` continue: ${item.continue_work_command}`);
4230
- if (item.accept_command) console.log(` land: ${item.accept_command}`);
4231
- else if (item.blocked_accept_reason) console.log(` land: blocked (${item.blocked_accept_reason})`);
4232
- console.log(` send back: ${item.revise_command}`);
4607
+ if (item.accept_command) console.log(` approve: ${item.accept_command}`);
4608
+ else if (item.blocked_accept_reason) console.log(` approve: blocked (${item.blocked_accept_reason})`);
4609
+ console.log(` rework: ${item.revise_command}`);
4233
4610
  });
4234
4611
  if (queue.counts.shown < queue.counts.certified) {
4235
4612
  console.log('');
4236
- console.log(`Showing ${queue.counts.shown}/${queue.counts.certified}; rerun with --all or --limit ${queue.counts.certified}.`);
4613
+ console.log(`Showing ${queue.counts.shown}/${queue.counts.certified}; rerun with --all for every row or --verbose for proof details.`);
4237
4614
  }
4238
4615
  }
4239
4616
 
@@ -4544,6 +4921,7 @@ function cmdAdd(args) {
4544
4921
  const taskDb = getTaskDb();
4545
4922
  const db = taskDb.open();
4546
4923
  const ws = taskDb.workspaceRoot();
4924
+ const operatorTitleWarning = warnIfTaskTitleNeedsOperatorWhy(title);
4547
4925
  // Generation throttle — the named root cause is generation > human-review rate. An AGENT cannot keep
4548
4926
  // minting tasks while a wall of certified-but-unaccepted work waits; that treadmill is what accept-group
4549
4927
  // only drains. Humans and --force bypass. Closes the tap instead of just enlarging the bucket.
@@ -4569,6 +4947,7 @@ function cmdAdd(args) {
4569
4947
  action: 'created',
4570
4948
  task_id: result.id,
4571
4949
  inserted: result.inserted !== false,
4950
+ operator_title_warning: operatorTitleWarning,
4572
4951
  projection_path: outPath,
4573
4952
  task,
4574
4953
  });
@@ -4611,6 +4990,7 @@ function cmdDelegate(args) {
4611
4990
  const taskDb = getTaskDb();
4612
4991
  const db = taskDb.open();
4613
4992
  const ws = taskDb.workspaceRoot();
4993
+ const operatorTitleWarning = warnIfTaskTitleNeedsOperatorWhy(title);
4614
4994
  const ownerResolution = resolveFunctionalTaskOwner({
4615
4995
  requestedOwner: requestedOwner && requestedOwner !== true ? requestedOwner : null,
4616
4996
  title,
@@ -4666,6 +5046,7 @@ function cmdDelegate(args) {
4666
5046
  executed_by: executedBy || null,
4667
5047
  via,
4668
5048
  handoff,
5049
+ operator_title_warning: operatorTitleWarning,
4669
5050
  projection_path: outPath,
4670
5051
  task,
4671
5052
  });
@@ -4858,7 +5239,17 @@ function cmdClaim(args) {
4858
5239
  }
4859
5240
  }
4860
5241
 
4861
- function readEndgameAgentAction(root, owner) {
5242
+ function liveTaskWithTitle(tasks, title) {
5243
+ const wanted = String(title || '').trim().toLowerCase();
5244
+ if (!wanted) return null;
5245
+ return (tasks || []).find(task => {
5246
+ const taskTitle = String(task.title || task.task || '').trim().toLowerCase();
5247
+ const status = String(task.status || '').toLowerCase();
5248
+ return taskTitle === wanted && !new Set(['done', 'accepted', 'failed']).has(status);
5249
+ }) || null;
5250
+ }
5251
+
5252
+ function readEndgameAgentAction(root, owner, { tasks = [] } = {}) {
4862
5253
  const todoPath = path.join(root || process.cwd(), 'atris', 'TODO.md');
4863
5254
  if (!fs.existsSync(todoPath)) return null;
4864
5255
  const content = fs.readFileSync(todoPath, 'utf8');
@@ -4869,6 +5260,8 @@ function readEndgameAgentAction(root, owner) {
4869
5260
  if (!slug && !horizon) return null;
4870
5261
  const member = String(owner || DEFAULT_OWNER);
4871
5262
  const taskSeed = buildEndgameTaskSeed({ slug, horizon, owner: member });
5263
+ const existing = liveTaskWithTitle(tasks, taskSeed.title);
5264
+ if (existing) return null;
4872
5265
  return {
4873
5266
  kind: 'create_bounded_endgame_task',
4874
5267
  endgame_slug: slug || null,
@@ -4918,6 +5311,7 @@ function buildEndgameTaskSeed({ slug, horizon, owner }) {
4918
5311
 
4919
5312
  function createEndgameSeedTask(taskDb, db, seed, owner) {
4920
5313
  const taskOwner = String(owner || DEFAULT_OWNER);
5314
+ const operatorTitleWarning = warnIfTaskTitleNeedsOperatorWhy(seed.title);
4921
5315
  const result = taskDb.addTask(db, {
4922
5316
  title: seed.title,
4923
5317
  tag: seed.tag,
@@ -4941,6 +5335,7 @@ function createEndgameSeedTask(taskDb, db, seed, owner) {
4941
5335
  ok: true,
4942
5336
  task_id: result.id,
4943
5337
  inserted: result.inserted !== false,
5338
+ operator_title_warning: operatorTitleWarning,
4944
5339
  note_version: note.event.version,
4945
5340
  };
4946
5341
  }
@@ -4992,7 +5387,7 @@ function cmdNext(args) {
4992
5387
  ? continueWorkCommandForTask(reviewTask, { owner })
4993
5388
  : null;
4994
5389
  const nextAgentAction = handoff.next_action === 'human_accept_waiting'
4995
- ? readEndgameAgentAction(taskDb.workspaceRoot(), owner)
5390
+ ? readEndgameAgentAction(taskDb.workspaceRoot(), owner, { tasks: projection.tasks || [] })
4996
5391
  : null;
4997
5392
  if (hasFlag(args, '--create-next')) {
4998
5393
  if (!nextAgentAction || !nextAgentAction.task_seed) {
@@ -5014,6 +5409,7 @@ function cmdNext(args) {
5014
5409
  handoff,
5015
5410
  next_agent_action: nextAgentAction,
5016
5411
  note_version: created.note_version,
5412
+ operator_title_warning: created.operator_title_warning || null,
5017
5413
  task: createdTask,
5018
5414
  review_task: reviewTask,
5019
5415
  });
@@ -5042,12 +5438,12 @@ function cmdNext(args) {
5042
5438
  }
5043
5439
  console.log('No open tasks.');
5044
5440
  console.log(handoff.next_action === 'agent_review_again'
5045
- ? `${taskRef(reviewTask)} needs one more agent check before landing.`
5046
- : `${taskRef(reviewTask)} is ready to land.`);
5441
+ ? `${taskRef(reviewTask)} needs one more agent check before approval.`
5442
+ : `${taskRef(reviewTask)} is ready for approval.`);
5047
5443
  console.log(handoff.next_action === 'continue_work'
5048
- ? 'Continue work elsewhere; XP lands after the human lands this task.'
5444
+ ? 'Continue work elsewhere; XP is awarded only after the human approves this task.'
5049
5445
  : handoff.next_action === 'human_accept_waiting'
5050
- ? (nextAgentAction ? nextAgentAction.message : 'No next agent task is attached; this task is ready to land when the human decides.')
5446
+ ? (nextAgentAction ? nextAgentAction.message : 'No next agent task is attached; this task is ready for human approval.')
5051
5447
  : 'Review this task again before continuing.');
5052
5448
  if (nextAgentAction) console.log(`Command: ${nextAgentAction.command}`);
5053
5449
  if (nextAgentAction && nextAgentAction.task_seed) {
@@ -5092,6 +5488,9 @@ function cmdNext(args) {
5092
5488
  }
5093
5489
  console.log(`next ${taskRef(compactTaskFromProjection(projection, open[0].id))} @${owner}`);
5094
5490
  console.log(open[0].title);
5491
+ const ref = taskRef(compactTaskFromProjection(projection, open[0].id));
5492
+ const { printOperatorNext } = require('../lib/operator-next');
5493
+ printOperatorNext(`atris task step ${ref}`);
5095
5494
  }
5096
5495
 
5097
5496
  function continueWorkForReviewTask(taskDb, db, taskId, { owner = DEFAULT_OWNER } = {}) {
@@ -5135,6 +5534,7 @@ function continueWorkForReviewTask(taskDb, db, taskId, { owner = DEFAULT_OWNER }
5135
5534
  parent_task_id: taskId,
5136
5535
  next_task_id: nextCreated ? nextCreated.id : null,
5137
5536
  created: Boolean(nextCreated && nextCreated.inserted !== false),
5537
+ operator_title_warning: nextCreated ? nextCreated.operator_title_warning || null : null,
5138
5538
  owner: String(owner || DEFAULT_OWNER),
5139
5539
  projection_path: outPath,
5140
5540
  parent,
@@ -5459,9 +5859,9 @@ function cmdShow(args) {
5459
5859
  const owner = task.claimed_by ? ` / ${task.claimed_by}` : '';
5460
5860
  const tag = task.tag ? ` #${task.tag}` : '';
5461
5861
  const statusLabel = task.status === 'review'
5462
- ? 'READY TO LAND'
5862
+ ? 'READY FOR APPROVAL'
5463
5863
  : task.status === 'done'
5464
- ? 'LANDED'
5864
+ ? 'DONE'
5465
5865
  : task.status.toUpperCase();
5466
5866
  console.log(`${statusLabel} ${taskRef(task)} v${task.current_version}${owner}${tag}`);
5467
5867
  console.log(task.title);
@@ -5932,13 +6332,80 @@ function buildAcceptHumanResult({ task, proof, nextTask, publicSync }) {
5932
6332
  return { changed, checked, try_next: tryNext };
5933
6333
  }
5934
6334
 
5935
- function renderAcceptLanding({ task, proof, nextTask, publicSync }) {
5936
- return renderPublicResult(buildAcceptHumanResult({
6335
+ function refreshBrainScorecardsAfterAccept(root = process.cwd()) {
6336
+ try {
6337
+ const { recordTaskEpisodeScorecards } = require('../commands/brain');
6338
+ return { ok: true, ...recordTaskEpisodeScorecards({ root }) };
6339
+ } catch (error) {
6340
+ return {
6341
+ ok: false,
6342
+ error: error && error.message ? error.message : String(error),
6343
+ };
6344
+ }
6345
+ }
6346
+
6347
+ function nextMissionRouteAfterAccept(root = process.cwd()) {
6348
+ try {
6349
+ const { selectCodexGoalMission } = require('../commands/mission');
6350
+ const selected = selectCodexGoalMission(root);
6351
+ const mission = selected && selected.mission;
6352
+ if (!mission) return { ok: true, objective: null, route: 'next mission: none' };
6353
+ return {
6354
+ ok: true,
6355
+ mission_id: mission.id,
6356
+ objective: mission.objective,
6357
+ reason: selected.reason,
6358
+ route: `next mission: ${cleanPublicText(mission.objective, 160)}`,
6359
+ command: mission.next_action || `atris mission run ${mission.id}`,
6360
+ };
6361
+ } catch (error) {
6362
+ return {
6363
+ ok: false,
6364
+ route: 'next mission: needs refresh',
6365
+ error: error && error.message ? error.message : String(error),
6366
+ };
6367
+ }
6368
+ }
6369
+
6370
+ function xpLandingText(xpProjection) {
6371
+ if (!xpProjection) return 'XP updated';
6372
+ if (xpProjection.ok === false) return 'XP refresh failed';
6373
+ const candidates = [
6374
+ xpProjection.total_agent_xp,
6375
+ xpProjection.total_xp,
6376
+ xpProjection.summary && xpProjection.summary.total_agent_xp,
6377
+ xpProjection.totals && xpProjection.totals.total_agent_xp,
6378
+ ];
6379
+ const total = candidates.map(Number).find((value) => Number.isFinite(value));
6380
+ return Number.isFinite(total) ? `XP updated (${total} total)` : 'XP updated';
6381
+ }
6382
+
6383
+ function brainScorecardLandingText(brainScorecards) {
6384
+ if (!brainScorecards) return 'brain scorecards checked';
6385
+ if (brainScorecards.ok === false) return 'brain scorecard refresh failed';
6386
+ const written = Number(brainScorecards.written);
6387
+ return Number.isFinite(written) ? `brain scorecards +${written}` : 'brain scorecards checked';
6388
+ }
6389
+
6390
+ function buildVisibleAcceptReceipt(result, { xpProjection, brainScorecards, nextMissionRoute } = {}) {
6391
+ const updates = [
6392
+ xpLandingText(xpProjection),
6393
+ brainScorecardLandingText(brainScorecards),
6394
+ ].filter(Boolean);
6395
+ const checked = updates.length ? `${result.checked}; ${updates.join('; ')}` : result.checked;
6396
+ const route = cleanPublicText(nextMissionRoute && nextMissionRoute.route, 180);
6397
+ const tryNext = route ? `${result.try_next}; ${route}` : result.try_next;
6398
+ return { ...result, checked, try_next: tryNext };
6399
+ }
6400
+
6401
+ function renderAcceptLanding({ task, proof, nextTask, publicSync, xpProjection, brainScorecards, nextMissionRoute }) {
6402
+ const result = buildAcceptHumanResult({
5937
6403
  task,
5938
6404
  proof,
5939
6405
  nextTask,
5940
6406
  publicSync,
5941
- }));
6407
+ });
6408
+ return renderPublicResult(buildVisibleAcceptReceipt(result, { xpProjection, brainScorecards, nextMissionRoute }));
5942
6409
  }
5943
6410
 
5944
6411
  async function publishAcceptAgentXp(args, actor) {
@@ -6322,7 +6789,7 @@ function taskPageNextAction(task, current, actions, { hasExistingReviewFollowUp
6322
6789
  if (handoff && handoff.next_action === 'human_accept_waiting') {
6323
6790
  return {
6324
6791
  key: 'human_accept_waiting',
6325
- label: 'Ready to land',
6792
+ label: 'Ready for approval',
6326
6793
  command: null,
6327
6794
  api: null,
6328
6795
  human_accept_command: actions.human_accept_command || null,
@@ -6444,7 +6911,7 @@ function appendTaskReviewChat(taskDb, db, taskId, { reviewer = 'codex-review', d
6444
6911
  throw taskReviewChatError(`not_reviewable_${task.status}`, `review chat requires a task in Review; current status is ${task.status}`, { status: 409, exitCode: 1 });
6445
6912
  }
6446
6913
  if (!taskAllowsReviewChat(task, { allowCertified: true })) {
6447
- throw taskReviewChatError('agent_certified_continue_work', 'review chat is closed after agent certification; continue other work or land/send back this task', { status: 409, exitCode: 1 });
6914
+ throw taskReviewChatError('agent_certified_continue_work', 'review chat is closed after agent certification; continue other work or wait for human approval/rework on this task', { status: 409, exitCode: 1 });
6448
6915
  }
6449
6916
  const contract = taskReviewChatContract(task, { reviewer: actor, allowCertified: true });
6450
6917
  let event = null;
@@ -6601,8 +7068,8 @@ function readyHandoffForStep(task, proof, lesson, nextTask, agentCertified) {
6601
7068
  career_xp_status: 'pending_human_accept',
6602
7069
  next_action: agentCertified ? certifiedReviewNextAction(nextTask) : 'agent_review_again',
6603
7070
  rule: agentCertified
6604
- ? 'Double-check complete; ready to keep moving. XP lands only after the human lands the task.'
6605
- : 'Proof is ready; one more agent check before landing. XP waits for the human.',
7071
+ ? 'Double-check complete; ready to keep moving. XP is awarded only after the human approves the task.'
7072
+ : 'Proof is ready; one more agent check before human approval. XP waits for the human.',
6606
7073
  };
6607
7074
  if (reviewChat) {
6608
7075
  handoff.review_chat_command = reviewChat.command;
@@ -6623,7 +7090,7 @@ function runTaskStep(taskDb, db, taskId, options = {}) {
6623
7090
  const reason = initialHandoffState.next_action === 'continue_work'
6624
7091
  ? 'agent_certified_continue_work'
6625
7092
  : 'agent_certified_waiting_human';
6626
- throw taskStepError(reason, 'atris task step: ready-to-land rows have no safe agent step; continue other work or land/send back this task', { status: 409, exitCode: 1, page: initialPage });
7093
+ throw taskStepError(reason, 'atris task step: approval-ready rows have no safe agent step; continue other work or wait for human approval/rework on this task', { status: 409, exitCode: 1, page: initialPage });
6627
7094
  }
6628
7095
  let chat = null;
6629
7096
  const message = String(options.message || '').trim();
@@ -6725,7 +7192,7 @@ function runTaskStep(taskDb, db, taskId, options = {}) {
6725
7192
  const reason = handoffState.next_action === 'continue_work'
6726
7193
  ? 'agent_certified_continue_work'
6727
7194
  : 'agent_certified_waiting_human';
6728
- throw taskStepError(reason, 'atris task step: ready-to-land rows have no safe agent step; continue other work or land/send back this task', { status: 409, exitCode: 1, page: actionPage });
7195
+ throw taskStepError(reason, 'atris task step: approval-ready rows have no safe agent step; continue other work or wait for human approval/rework on this task', { status: 409, exitCode: 1, page: actionPage });
6729
7196
  }
6730
7197
  const reviewed = appendTaskReviewChat(taskDb, db, taskId, { reviewer, dryRun: Boolean(options.dryRun) });
6731
7198
  stepAction = 'review_chat';
@@ -6833,7 +7300,7 @@ function runCurrentTaskStep(taskDb, db, { owner = DEFAULT_OWNER, reviewer = 'cod
6833
7300
  if (nextActionKey === 'human_accept_waiting') {
6834
7301
  const error = taskStepError(
6835
7302
  'agent_certified_waiting_human',
6836
- 'atris task current-step: selected row is ready to land; no agent mutation is safe',
7303
+ 'atris task current-step: selected row is ready for human approval; no agent mutation is safe',
6837
7304
  {
6838
7305
  status: 409,
6839
7306
  exitCode: 1,
@@ -7129,6 +7596,7 @@ function cmdFinish(args) {
7129
7596
  const currentTask = taskDb.getTask(db, taskId);
7130
7597
  const actor = String(flag(args, '--as') || DEFAULT_OWNER);
7131
7598
  const proof = proofFlagValue(args);
7599
+ const landing = landingFlags(args);
7132
7600
  const failed = hasFlag(args, '--failed');
7133
7601
  const hasReview = hasFlag(args, '--review') || flag(args, '--lesson') || flag(args, '--next') || flag(args, '--proof') || flag(args, '--reward');
7134
7602
  if (agentProofOnlyMode() && !failed) {
@@ -7165,6 +7633,7 @@ function cmdFinish(args) {
7165
7633
  process.exit(1);
7166
7634
  }
7167
7635
  if (hasReview) {
7636
+ const landingAdvisory = !failed ? warnIfLandingNeedsDayOnePm(landing, currentTask && currentTask.title) : null;
7168
7637
  const result = taskDb.reviewTask(db, {
7169
7638
  id: taskId,
7170
7639
  actor,
@@ -7173,6 +7642,7 @@ function cmdFinish(args) {
7173
7642
  nextTask: typeof flag(args, '--next') === 'string' ? flag(args, '--next') : '',
7174
7643
  proof,
7175
7644
  careerXpEligible: false,
7645
+ landing,
7176
7646
  });
7177
7647
  const nextCreated = createNextTaskIfRequested(taskDb, db, args, currentTask, result.episode.next_task_suggestion);
7178
7648
  const xpProjection = refreshCareerXpAfterReview(result);
@@ -7186,6 +7656,7 @@ function cmdFinish(args) {
7186
7656
  reward: result.episode.reward.value,
7187
7657
  episode: result.episode,
7188
7658
  xp_projection: xpProjection,
7659
+ landing_advisory: landingAdvisory,
7189
7660
  next_task_id: nextCreated ? nextCreated.id : null,
7190
7661
  projection_path: outPath,
7191
7662
  task: compactTaskFromProjection(projection, taskId),
@@ -7280,6 +7751,7 @@ function cmdReady(args) {
7280
7751
  console.error(`ready failed: ${result.reason}`);
7281
7752
  process.exit(1);
7282
7753
  }
7754
+ const landingAdvisory = warnIfLandingNeedsDayOnePm(landing, result.row && result.row.title);
7283
7755
  const { projection, outPath } = writeDefaultProjection(taskDb, db);
7284
7756
  const agentCertified = result.event.payload.agent_certified === true;
7285
7757
  const projectionTask = taskFromProjection(projection, taskId)
@@ -7296,8 +7768,8 @@ function cmdReady(args) {
7296
7768
  career_xp_status: 'pending_human_accept',
7297
7769
  next_action: agentCertified ? certifiedReviewNextAction(nextTaskInput.nextTask) : 'agent_review_again',
7298
7770
  rule: agentCertified
7299
- ? 'Double-check complete; ready to keep moving. XP lands only after the human lands the task.'
7300
- : 'Proof is ready; one more agent check before landing. XP waits for the human.',
7771
+ ? 'Double-check complete; ready to keep moving. XP is awarded only after the human approves the task.'
7772
+ : 'Proof is ready; one more agent check before human approval. XP waits for the human.',
7301
7773
  };
7302
7774
  if (reviewChat) {
7303
7775
  handoff.review_chat_command = reviewChat.command;
@@ -7321,13 +7793,14 @@ function cmdReady(args) {
7321
7793
  agent_certified: agentCertified,
7322
7794
  handoff,
7323
7795
  result_trace: resultTrace,
7796
+ landing_advisory: landingAdvisory,
7324
7797
  ...(nextTaskInput.ignored ? { review_next_task_ignored: nextTaskInput.ignored } : {}),
7325
7798
  projection_path: outPath,
7326
7799
  task: compactTaskFromProjection(projection, taskId),
7327
7800
  });
7328
7801
  return;
7329
7802
  }
7330
- console.log(`ready to land ${taskRef(compactTaskFromProjection(projection, taskId))} v${result.event.version}`);
7803
+ console.log(`ready for approval ${taskRef(compactTaskFromProjection(projection, taskId))} v${result.event.version}`);
7331
7804
  if (resultTrace) console.log('Result trace recorded.');
7332
7805
  console.log(handoff.rule);
7333
7806
  for (const hint of policyHints) {
@@ -7412,9 +7885,12 @@ async function cmdAccept(args) {
7412
7885
  });
7413
7886
  const xpProjection = refreshCareerXpAfterReview(reviewed);
7414
7887
  const { projection, outPath } = writeDefaultProjection(taskDb, db);
7888
+ const workspaceRoot = projection.workspace_root || process.cwd();
7889
+ const brainScorecards = refreshBrainScorecardsAfterAccept(workspaceRoot);
7890
+ const nextMissionRoute = nextMissionRouteAfterAccept(workspaceRoot);
7415
7891
  // Inform the gate, never block it: show what the receipts named in the proof
7416
7892
  // actually say so the accepting human isn't trusting prose.
7417
- const evidence = extractReceiptEvidence(proof, projection.workspace_root || process.cwd());
7893
+ const evidence = extractReceiptEvidence(proof, workspaceRoot);
7418
7894
  const publicSync = hasFlag(args, '--public') ? await publishAcceptAgentXp(args, actor) : null;
7419
7895
  if (wantsJson(args)) {
7420
7896
  printJson({
@@ -7427,6 +7903,8 @@ async function cmdAccept(args) {
7427
7903
  evidence,
7428
7904
  public_sync: publicSync,
7429
7905
  xp_projection: xpProjection,
7906
+ brain_scorecards: brainScorecards,
7907
+ next_mission_route: nextMissionRoute,
7430
7908
  projection_path: outPath,
7431
7909
  task: compactTaskFromProjection(projection, taskId),
7432
7910
  });
@@ -7438,6 +7916,9 @@ async function cmdAccept(args) {
7438
7916
  proof,
7439
7917
  nextTask,
7440
7918
  publicSync,
7919
+ xpProjection,
7920
+ brainScorecards,
7921
+ nextMissionRoute,
7441
7922
  }));
7442
7923
  if (publicSync && !publicSync.ok) process.exitCode = 1;
7443
7924
  }
@@ -7481,13 +7962,165 @@ function acceptReviewTask(taskDb, db, taskId, { actor, proof, reward, lesson = '
7481
7962
  return { ok: true, reviewed };
7482
7963
  }
7483
7964
 
7965
+ // Second-actor certification for proof-backed Review rows whose proof names a
7966
+ // runnable, allowlisted check. Re-running that check as a distinct actor IS
7967
+ // the independent verification the certification gate asks for — the row
7968
+ // becomes certified and autoland can land it on the same heartbeat. Rows in
7969
+ // denied lanes, without a runnable check, or already certified keep their
7970
+ // existing paths (review chats, human accept).
7971
+ function certifyVerifyCandidate(task) {
7972
+ const metadata = task.metadata || {};
7973
+ const review = task.review || {};
7974
+ const recorded = typeof metadata.verify === 'string' && metadata.verify.trim() ? [metadata.verify.trim()] : [];
7975
+ const proof = String(review.proof || metadata.latest_agent_proof || '');
7976
+ for (const raw of [...recorded, ...taskReviewEvidenceCommands(proof)]) {
7977
+ // The evidence extractor can keep trailing prose ("git diff --check. Evidence
7978
+ // inspected: ..."); try the full clause, then the first sentence of it.
7979
+ for (const candidate of [raw, raw.split(/\.(?:\s|$)/)[0]].map((c) => String(c || '').trim())) {
7980
+ if (candidate && parseVerifyCommand(candidate).ok) return candidate;
7981
+ }
7982
+ }
7983
+ return null;
7984
+ }
7985
+
7986
+ function stampCertifyVerifyMetadata(taskDb, db, taskId, actor, verify) {
7987
+ const row = taskDb.getTask(db, taskId);
7988
+ if (!row) return;
7989
+ const metadata = row.metadata && typeof row.metadata === 'object' ? { ...row.metadata } : {};
7990
+ metadata.verify = metadata.verify || verify;
7991
+ metadata.certified_verified_at = new Date().toISOString();
7992
+ metadata.certified_verified_by = actor;
7993
+ db.prepare(`
7994
+ UPDATE tasks
7995
+ SET metadata = ?,
7996
+ updated_at = ?
7997
+ WHERE id = ?
7998
+ `).run(JSON.stringify(metadata), Date.now(), taskId);
7999
+ }
8000
+
8001
+ function cmdCertifyVerified(args) {
8002
+ const dryRun = hasFlag(args, '--dry-run');
8003
+ const asJson = wantsJson(args);
8004
+ const actor = String(flag(args, '--as') || 'autoland-verifier');
8005
+ const limitRaw = flag(args, '--limit');
8006
+ const max = limitRaw && limitRaw !== true ? Math.max(1, Number(limitRaw) || 6) : 6;
8007
+
8008
+ const taskDb = getTaskDb();
8009
+ const db = taskDb.open();
8010
+ const { projection } = writeDefaultProjection(taskDb, db);
8011
+ const candidates = (projection.tasks || [])
8012
+ .filter((t) => t && t.status === 'review')
8013
+ .sort((a, b) => Number(b.updated_at || 0) - Number(a.updated_at || 0))
8014
+ .slice(0, max);
8015
+ const results = [];
8016
+ for (const item of candidates) {
8017
+ const fullProjection = enrichTaskProjection(taskDb.taskProjection(db, { taskId: item.id }));
8018
+ const task = fullProjection.tasks[0] || null;
8019
+ if (!task) {
8020
+ results.push({ ref: item.display_id || item.id, action: 'skipped', reason: 'task_not_found' });
8021
+ continue;
8022
+ }
8023
+ const ref = task.display_id || task.legacy_ref || task.id;
8024
+ const metadata = task.metadata || {};
8025
+ const review = task.review || {};
8026
+ if (task.status !== 'review') {
8027
+ results.push({ ref, action: 'skipped', reason: 'not_in_review' });
8028
+ continue;
8029
+ }
8030
+ const approval = String(review.approval_status || metadata.approval_status || 'pending').toLowerCase();
8031
+ if (approval !== 'pending') {
8032
+ results.push({ ref, action: 'skipped', reason: `approval_${approval}` });
8033
+ continue;
8034
+ }
8035
+ const tag = String(task.tag || '').toLowerCase();
8036
+ if (DENIED_TAGS.has(tag)) {
8037
+ results.push({ ref, action: 'skipped', reason: `denied_tag_${tag}` });
8038
+ continue;
8039
+ }
8040
+ // Skip only rows the accept lane can already land, or rows blocked by
8041
+ // something an executed second-actor check cannot cure. A row with two
8042
+ // passes from ONE actor is exactly what this command exists to cure —
8043
+ // "certified" alone is not landable.
8044
+ const evaluation = evaluateAutoAccept(task, { strictVerify: false });
8045
+ if (evaluation.eligible) {
8046
+ results.push({ ref, action: 'skipped', reason: 'already_landable' });
8047
+ continue;
8048
+ }
8049
+ const curable = ['not_agent_certified', 'needs_second_reviewer_or_third_pass', 'insufficient_review_passes'];
8050
+ if (!curable.includes(evaluation.reason)) {
8051
+ results.push({ ref, action: 'skipped', reason: evaluation.reason });
8052
+ continue;
8053
+ }
8054
+ const verify = certifyVerifyCandidate(task);
8055
+ if (!verify) {
8056
+ results.push({ ref, action: 'skipped', reason: 'no_runnable_check_in_proof' });
8057
+ continue;
8058
+ }
8059
+ if (dryRun) {
8060
+ results.push({ ref, action: 'would_certify', verify });
8061
+ continue;
8062
+ }
8063
+ const run = runVerifyCommand(verify, task.workspace_root || process.cwd());
8064
+ if (!run.ok) {
8065
+ results.push({ ref, action: 'verify_failed', reason: run.reason, verify });
8066
+ continue;
8067
+ }
8068
+ const builderProof = String(review.proof || metadata.latest_agent_proof || '').slice(0, 200);
8069
+ const readied = taskDb.readyTask(db, {
8070
+ id: task.id,
8071
+ actor,
8072
+ proof: `Second-actor check: \`${verify}\` re-run by ${actor}, exited 0. Builder proof inspected: ${builderProof}`,
8073
+ lesson: '',
8074
+ nextTask: '',
8075
+ });
8076
+ if (!readied.ready) {
8077
+ results.push({ ref, action: 'certify_failed', reason: readied.reason, verify });
8078
+ continue;
8079
+ }
8080
+ stampCertifyVerifyMetadata(taskDb, db, task.id, actor, verify);
8081
+ const certifiedNow = readied.event?.payload?.agent_certified === true;
8082
+ results.push({ ref, action: certifiedNow ? 'certified' : 'pass_recorded', verify });
8083
+ }
8084
+ const { outPath } = writeDefaultProjection(taskDb, db);
8085
+ const certified = results.filter((r) => r.action === 'certified').length;
8086
+ const payload = {
8087
+ ok: true,
8088
+ action: dryRun ? 'certify_verified_dry_run' : 'certify_verified',
8089
+ actor,
8090
+ certified,
8091
+ would_certify: results.filter((r) => r.action === 'would_certify').length,
8092
+ skipped: results.filter((r) => r.action === 'skipped').length,
8093
+ failed: results.filter((r) => r.action === 'verify_failed' || r.action === 'certify_failed').length,
8094
+ results,
8095
+ projection_path: outPath,
8096
+ };
8097
+ if (asJson) {
8098
+ console.log(JSON.stringify(payload, null, 2));
8099
+ } else if (results.length === 0) {
8100
+ console.log('certify-verified: no review rows to consider.');
8101
+ } else {
8102
+ for (const r of results) {
8103
+ console.log(`${r.action.padEnd(14)} ${r.ref}${r.verify ? ` \`${r.verify}\`` : ''}${r.reason ? ` (${r.reason})` : ''}`);
8104
+ }
8105
+ console.log(`certified ${certified}; humans keep denied lanes and rows without a runnable check.`);
8106
+ }
8107
+ return payload;
8108
+ }
8109
+
7484
8110
  function cmdAutoAcceptCertified(args) {
7485
8111
  const dryRun = hasFlag(args, '--dry-run');
7486
8112
  const strictVerify = hasFlag(args, '--strict-verify');
7487
8113
  const actorFlag = flag(args, '--as');
7488
- const actor = String(actorFlag || 'auto-accept-certified');
7489
8114
  const hasHumanActor = validHumanActorFlag(actorFlag);
7490
8115
  const confirmedHumanAccept = hasFlag(args, '--confirm-human-accept');
8116
+ // Standing owner authorization: when the autoland policy is on for this
8117
+ // workspace, live accepts run as the owner who flipped it, no per-run
8118
+ // confirmation needed. See lib/autoland.js and 'atris autoland'.
8119
+ const dryRunEarly = hasFlag(args, '--dry-run');
8120
+ const policyAuth = (!dryRunEarly && !confirmedHumanAccept)
8121
+ ? require('../lib/autoland').liveAcceptAuthorization()
8122
+ : { ok: false };
8123
+ const actor = String(actorFlag || (policyAuth.ok ? policyAuth.actor : 'auto-accept-certified'));
7491
8124
  const limitRaw = flag(args, '--limit');
7492
8125
  const max = limitRaw && limitRaw !== true ? Math.max(1, Number(limitRaw) || 12) : 12;
7493
8126
  const parsedReward = parseAcceptReward(flag(args, '--reward'));
@@ -7495,14 +8128,14 @@ function cmdAutoAcceptCertified(args) {
7495
8128
  console.error('atris task auto-accept-certified: reward must be a positive number');
7496
8129
  process.exit(2);
7497
8130
  }
7498
- if (!dryRun && !confirmedHumanAccept) {
8131
+ if (!dryRun && !confirmedHumanAccept && !policyAuth.ok) {
7499
8132
  failTask(
7500
8133
  'atris task auto-accept-certified',
7501
8134
  'human_accept_confirmation_required',
7502
- 'live auto-accept requires --confirm-human-accept --as <human>; use --dry-run to preview',
8135
+ 'live auto-accept requires --confirm-human-accept --as <human>, or the owner flips the standing policy with atris autoland on; use --dry-run to preview',
7503
8136
  );
7504
8137
  }
7505
- if (!dryRun && !hasHumanActor) {
8138
+ if (!dryRun && !hasHumanActor && !policyAuth.ok) {
7506
8139
  failTask(
7507
8140
  'atris task auto-accept-certified',
7508
8141
  'human_actor_required',
@@ -7707,7 +8340,7 @@ function cmdReview(args) {
7707
8340
  version: result.event.version,
7708
8341
  reward: result.episode.reward.value,
7709
8342
  episode: result.episode,
7710
- xp_projection: xpProjection,
8343
+ xp_projection: compactCareerXpProjection(xpProjection),
7711
8344
  next_task_id: nextCreated ? nextCreated.id : null,
7712
8345
  ...(nextTaskInput.ignored ? { review_next_task_ignored: nextTaskInput.ignored } : {}),
7713
8346
  projection_path: outPath,
@@ -8080,6 +8713,40 @@ function markdownRowsForRender(taskDb, existingTodoPath, rows, refRows) {
8080
8713
  return out;
8081
8714
  }
8082
8715
 
8716
+ function taskRenderStatusCounts(rows) {
8717
+ const counts = {
8718
+ backlog: 0,
8719
+ in_progress: 0,
8720
+ review: 0,
8721
+ blocked: 0,
8722
+ done: 0,
8723
+ total: 0,
8724
+ };
8725
+ for (const row of Array.isArray(rows) ? rows : []) {
8726
+ counts.total += 1;
8727
+ if (row.status === 'open') counts.backlog += 1;
8728
+ else if (row.status === 'claimed') counts.in_progress += 1;
8729
+ else if (row.status === 'review') counts.review += 1;
8730
+ else if (row.status === 'failed') counts.blocked += 1;
8731
+ else if (row.status === 'done') counts.done += 1;
8732
+ }
8733
+ return counts;
8734
+ }
8735
+
8736
+ function taskRenderCountLabel(count) {
8737
+ return count === 0 ? 'empty' : String(count);
8738
+ }
8739
+
8740
+ function taskRenderSummaryLine(counts) {
8741
+ return [
8742
+ `Backlog: ${taskRenderCountLabel(counts.backlog)}`,
8743
+ `In Progress: ${taskRenderCountLabel(counts.in_progress)}`,
8744
+ `Review: ${taskRenderCountLabel(counts.review)}`,
8745
+ `Blocked: ${taskRenderCountLabel(counts.blocked)}`,
8746
+ `Done saved: ${taskRenderCountLabel(counts.done)}`,
8747
+ ].join('; ');
8748
+ }
8749
+
8083
8750
  function cmdRender(args) {
8084
8751
  const out = flag(args, '--out') || path.join('atris', 'TODO.md');
8085
8752
  const all = hasFlag(args, '--all');
@@ -8103,6 +8770,7 @@ function cmdRender(args) {
8103
8770
  if (endgameSection) preservedSections.push(endgameSection);
8104
8771
  const markdownRows = markdownRowsForRender(taskDb, outPath, rows, refRows);
8105
8772
  const rowsToRender = [...rows, ...markdownRows];
8773
+ const statusCounts = taskRenderStatusCounts(rowsToRender);
8106
8774
  const markdown = taskDb.renderTodoMarkdown(rowsToRender, { doneLimit, failedLimit, refRows, preservedSections });
8107
8775
  fs.mkdirSync(path.dirname(outPath), { recursive: true });
8108
8776
  fs.writeFileSync(outPath, markdown, 'utf8');
@@ -8111,11 +8779,14 @@ function cmdRender(args) {
8111
8779
  ok: true,
8112
8780
  action: 'rendered',
8113
8781
  count: rowsToRender.length,
8782
+ status_counts: statusCounts,
8783
+ backlog_empty: statusCounts.backlog === 0,
8114
8784
  path: outPath,
8115
8785
  });
8116
8786
  return;
8117
8787
  }
8118
- console.log(`rendered ${rowsToRender.length} task${rowsToRender.length === 1 ? '' : 's'} -> ${outPath}`);
8788
+ console.log(`rendered TODO.md -> ${outPath}`);
8789
+ console.log(taskRenderSummaryLine(statusCounts));
8119
8790
  }
8120
8791
 
8121
8792
  function cmdSync(args) {
@@ -8783,13 +9454,14 @@ async function handleTaskApi(req, res, taskDb, db) {
8783
9454
  const body = await readJsonBody(req);
8784
9455
  const title = String(body.title || '').trim();
8785
9456
  if (!title) return sendJson(res, 400, { ok: false, reason: 'missing_title', detail: 'title required' });
9457
+ const operatorTitleWarning = warnIfTaskTitleNeedsOperatorWhy(title);
8786
9458
  const result = taskDb.addTask(db, {
8787
9459
  title,
8788
9460
  tag: body.tag ? String(body.tag) : 'tasks',
8789
9461
  workspaceRoot: taskDb.workspaceRoot(),
8790
9462
  });
8791
9463
  const { projection, outPath } = writeDefaultProjection(taskDb, db);
8792
- return sendJson(res, 200, { ok: true, action: 'created', task_id: result.id, projection_path: outPath, task: taskFromProjection(projection, result.id) });
9464
+ return sendJson(res, 200, { ok: true, action: 'created', task_id: result.id, operator_title_warning: operatorTitleWarning, projection_path: outPath, task: taskFromProjection(projection, result.id) });
8793
9465
  }
8794
9466
  if (req.method === 'POST' && url.pathname === '/api/tasks/clear-plan') {
8795
9467
  const body = await readJsonBody(req);
@@ -9296,6 +9968,8 @@ async function run(args) {
9296
9968
  case 'auto-accept-certified':
9297
9969
  case 'auto-accept':
9298
9970
  return cmdAutoAcceptCertified(rest);
9971
+ case 'certify-verified':
9972
+ return cmdCertifyVerified(rest);
9299
9973
  case 'accept-group':
9300
9974
  return cmdAcceptGroup(rest);
9301
9975
  case 'revise': return cmdRevise(rest);