atris 3.52.0 → 3.55.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 (131) hide show
  1. package/FOR_AGENTS.md +7 -0
  2. package/README.md +1 -1
  3. package/atris/policies/ANTISLOP.md +2 -1
  4. package/atris/skills/copy-editor/SKILL.md +2 -1
  5. package/atris/skills/design/SKILL.md +1 -1
  6. package/atris/skills/engines/SKILL.md +8 -7
  7. package/atris/skills/x-search/SKILL.md +24 -22
  8. package/atris/skills/youtube/SKILL.md +89 -40
  9. package/atris/team/_template/SOUL.md +40 -0
  10. package/atris/team/validator/MEMBER.md +1 -2
  11. package/ax +592 -91
  12. package/bin/atris +21 -0
  13. package/bin/atris.js +580 -378
  14. package/commands/api-key.js +170 -0
  15. package/commands/auth.js +453 -43
  16. package/commands/autoland.js +3 -1
  17. package/commands/autopilot-front.js +58 -11
  18. package/commands/avail.js +5 -4
  19. package/commands/balance.js +55 -0
  20. package/commands/bench.js +29 -1
  21. package/commands/brain.js +63 -12
  22. package/commands/brainstorm.js +18 -0
  23. package/commands/business.js +149 -1
  24. package/commands/chat-scan.js +17 -4
  25. package/commands/close.js +5 -1
  26. package/commands/computer.js +2 -2
  27. package/commands/doctor.js +174 -0
  28. package/commands/dream.js +7 -0
  29. package/commands/drill.js +34 -2
  30. package/commands/engine.js +47 -35
  31. package/commands/errors.js +58 -8
  32. package/commands/experiments.js +14 -0
  33. package/commands/feedback.js +61 -2
  34. package/commands/fleet-report.js +27 -7
  35. package/commands/fleet.js +98 -11
  36. package/commands/founder.js +60 -21
  37. package/commands/human-missions.js +79 -4
  38. package/commands/improve.js +54 -4
  39. package/commands/init.js +68 -92
  40. package/commands/install.js +40 -0
  41. package/commands/integrations.js +399 -30
  42. package/commands/interview.js +8 -0
  43. package/commands/land.js +48 -17
  44. package/commands/learn.js +29 -4
  45. package/commands/log.js +51 -0
  46. package/commands/member.js +68 -10
  47. package/commands/mission.js +339 -74
  48. package/commands/now.js +25 -6
  49. package/commands/orb.js +97 -2
  50. package/commands/pack.js +83 -9
  51. package/commands/playbook.js +381 -0
  52. package/commands/plugin.js +16 -0
  53. package/commands/radar.js +96 -40
  54. package/commands/recap.js +128 -21
  55. package/commands/release.js +102 -2
  56. package/commands/review.js +5 -10
  57. package/commands/revisions.js +55 -0
  58. package/commands/run-front.js +12 -4
  59. package/commands/scout.js +12 -1
  60. package/commands/search.js +123 -28
  61. package/commands/sign.js +2 -2
  62. package/commands/signup.js +12 -3
  63. package/commands/site-deploy.js +17 -4
  64. package/commands/site.js +2 -2
  65. package/commands/skill-eval.js +289 -0
  66. package/commands/skill.js +56 -6
  67. package/commands/social.js +603 -0
  68. package/commands/spaceship.js +69 -6
  69. package/commands/stream.js +8 -1
  70. package/commands/study.js +32 -19
  71. package/commands/sync.js +28 -22
  72. package/commands/task.js +418 -60
  73. package/commands/teach.js +72 -63
  74. package/commands/terminal.js +25 -9
  75. package/commands/topup.js +88 -0
  76. package/commands/truth.js +52 -6
  77. package/commands/usage.js +91 -0
  78. package/commands/verify.js +3 -2
  79. package/commands/version.js +3 -1
  80. package/commands/voice.js +5 -1
  81. package/commands/who.js +69 -11
  82. package/commands/wiki.js +7 -4
  83. package/commands/wish.js +76 -5
  84. package/commands/workflow.js +327 -145
  85. package/commands/worktree.js +1 -0
  86. package/commands/write.js +5 -0
  87. package/commands/x-search.js +340 -0
  88. package/commands/xp.js +5 -1
  89. package/commands/youtube.js +551 -33
  90. package/lib/account-bound.js +47 -0
  91. package/lib/auto-accept-certified.js +10 -2
  92. package/lib/auto-lessons.js +186 -0
  93. package/lib/autoland.js +4 -2
  94. package/lib/chat-log-scan.js +7 -4
  95. package/lib/cli-json.js +77 -0
  96. package/lib/cli-scope.js +26 -0
  97. package/lib/codex-flight.js +1 -0
  98. package/lib/conductor-artifacts.js +1 -1
  99. package/lib/context-gatherer.js +11 -0
  100. package/lib/developer-api.js +116 -0
  101. package/lib/engine-ask.js +11 -3
  102. package/lib/engine-registry.js +32 -7
  103. package/lib/feedback-triage.js +274 -0
  104. package/lib/first-minute.js +398 -0
  105. package/lib/fleet.js +115 -34
  106. package/lib/functional-owner.js +22 -0
  107. package/lib/known-commands.js +31 -5
  108. package/lib/member-scaffold.js +197 -0
  109. package/lib/mission-ledger-compact.js +127 -0
  110. package/lib/mission-root.js +4 -2
  111. package/lib/mission-runtime-loop.js +30 -2
  112. package/lib/next-moves.js +34 -34
  113. package/lib/noninteractive.js +88 -0
  114. package/lib/permission-grants.js +10 -1
  115. package/lib/policy-lessons.js +4 -1
  116. package/lib/revision-metric.js +279 -0
  117. package/lib/runner-command.js +5 -4
  118. package/lib/scratch-root.js +48 -0
  119. package/lib/skill-eval-gate.js +249 -0
  120. package/lib/task-db.js +22 -2
  121. package/lib/task-proof.js +75 -9
  122. package/lib/task-receipt.js +5 -1
  123. package/lib/verifier-quality.js +69 -0
  124. package/lib/wish-audit.js +2 -2
  125. package/lib/wish-delegate.js +10 -1
  126. package/lib/workspace-scaffold.js +270 -0
  127. package/package.json +4 -2
  128. package/scripts/det/checklist-score.js +191 -0
  129. package/scripts/det/ytsearch +31 -0
  130. package/scripts/outbound-artifact-gate.js +227 -0
  131. package/utils/update-check.js +15 -0
package/commands/task.js CHANGED
@@ -9,7 +9,17 @@ const { execSync } = require('node:child_process');
9
9
  const path = require('path');
10
10
  const os = require('os');
11
11
  const { hasFlag } = require('../lib/arg-parser');
12
- const { taskProofState } = require('../lib/task-proof');
12
+ const { argsWantHelp } = require('../lib/noninteractive');
13
+ const {
14
+ compactErrorPayload,
15
+ compactSuccessPayload,
16
+ printCliJson,
17
+ } = require('../lib/cli-json');
18
+ const {
19
+ taskProofState,
20
+ LOCAL_SUCCESS_PROOF_EXAMPLE,
21
+ unrunNamedProofCommandIssue,
22
+ } = require('../lib/task-proof');
13
23
  const {
14
24
  candidatePolicyGate,
15
25
  evaluateAutoAccept,
@@ -50,6 +60,7 @@ const {
50
60
  decisionMarkerFor,
51
61
  DECISION_REFUSE_REASON,
52
62
  } = require('../lib/task-decision');
63
+ const { buildFirstMinute, deskNextCommand, personName, pickNext, taskCommand, taskNextCommand } = require('../lib/first-minute');
53
64
 
54
65
  const DEFAULT_OWNER = process.env.ATRIS_AGENT_ID
55
66
  || process.env.USER
@@ -151,11 +162,12 @@ atris task - durable local task state (SQLite, gitignored)
151
162
  atris task ready <id> --verify
152
163
  atris autoland tick # second check runs, task lands
153
164
 
154
- atris task Show the task desk
165
+ atris task Same two-line next as bare atris
166
+ atris task desk [--all] Show the full task desk
155
167
  atris task new "<title>" [--what-changes "..."] [--why-it-matters "..."] [--done-looks-like "..."] [--verify <cmd>]
156
168
  Create a task with a plain explanation; omitted fields get honest defaults
157
169
  atris task next [--tag <tag>] [--create-next]
158
- Claim/show next open task; optionally create the generated Endgame fallback
170
+ Same next command as bare atris and the task desk; --create-next still seeds Endgame fallback
159
171
  atris task continue-work <id> Create/reuse a certified Review follow-up task
160
172
  atris task say <id> "<message>" Add context to a task
161
173
  atris task chat <id> "<message>" [--goal "..."] Refine a task chat + working goal
@@ -163,7 +175,8 @@ atris task - durable local task state (SQLite, gitignored)
163
175
  Agent proof ready; native goal can complete
164
176
  atris task ready <id> --verify "<cmd>" --result "<sentence>"
165
177
  Run <cmd>; only ready if it exits 0 (executed proof)
166
- Writes atris/runs/ receipt (pass or fail), folds path into proof
178
+ Writes atris/runs/ receipt (pass or fail); that local pass is enough proof
179
+ Optional CI URL only with --proof-url and --i-fetched
167
180
  atris task receipt <id> --verify "<cmd>" Run <cmd> and write an atris/runs/ receipt without going to ready
168
181
  atris task plan-preview "<purpose>" [--tag <tag>] [--owner <member>] [--task <id>]
169
182
  Show the plain Plan before work starts
@@ -440,12 +453,18 @@ function jsonModeActive() {
440
453
 
441
454
  function failTask(label, reason, detail, exitCode = 2) {
442
455
  if (jsonModeActive()) {
443
- console.log(JSON.stringify({
444
- ok: false,
445
- command: label,
446
- reason,
447
- detail: detail || null,
448
- }));
456
+ printCliJson(
457
+ {
458
+ ok: false,
459
+ command: label,
460
+ reason,
461
+ detail: detail || null,
462
+ selected_ref: null,
463
+ next_command: null,
464
+ },
465
+ compactErrorPayload({ reason, detail: detail || null }),
466
+ process.argv.slice(2),
467
+ );
449
468
  } else {
450
469
  console.error(detail || `${label}: ${reason}`);
451
470
  }
@@ -472,8 +491,8 @@ function proofFlagValue(args) {
472
491
  return typeof proof === 'string' ? proof.trim() : '';
473
492
  }
474
493
 
475
- function resultSentenceIssue(value) {
476
- const check = explainResult(value);
494
+ function resultSentenceIssue(value, { allowCommandMention = false } = {}) {
495
+ const check = explainResult(value, { allowCommandMention });
477
496
  return check.ok ? null : check.reason;
478
497
  }
479
498
 
@@ -481,13 +500,35 @@ function readyResultDetail(reason) {
481
500
  return reason ? `${READY_RESULT_TEACHING}\n${reason}` : READY_RESULT_TEACHING;
482
501
  }
483
502
 
484
- function requireResultSentence(label, value, { ready = false } = {}) {
485
- const issue = resultSentenceIssue(value);
503
+ function requireResultSentence(label, value, { ready = false, allowCommandMention = false } = {}) {
504
+ const issue = resultSentenceIssue(value, { allowCommandMention });
486
505
  if (!issue) return String(value || '').replace(/\s+/g, ' ').trim();
487
506
  const detail = ready ? readyResultDetail(issue) : issue;
488
507
  failTask(label, 'weak_result', detail);
489
508
  }
490
509
 
510
+ function weakProofDetail(issue) {
511
+ return `meaningful proof required: ${issue}\n${LOCAL_SUCCESS_PROOF_EXAMPLE}`;
512
+ }
513
+
514
+ function requireMeaningfulTaskProof(label, proof, { required = true } = {}) {
515
+ const issue = meaningfulTaskProofIssue(proof, { required });
516
+ if (issue) failTask(label, 'weak_proof', weakProofDetail(issue));
517
+ }
518
+
519
+ function requireRanNamedProofCommand(label, proof, ranCommand = '') {
520
+ const issue = unrunNamedProofCommandIssue(proof, ranCommand);
521
+ if (issue) failTask(label, issue.reason, issue.detail);
522
+ }
523
+
524
+ function sendProofIssue(res, proof, issue) {
525
+ return sendJson(res, 400, {
526
+ ok: false,
527
+ reason: String(proof || '').trim() ? 'weak_proof' : 'proof_required',
528
+ detail: weakProofDetail(issue),
529
+ });
530
+ }
531
+
491
532
  function textFlag(args, names) {
492
533
  for (const name of names) {
493
534
  const value = flag(args, name);
@@ -658,19 +699,6 @@ function missionXpProofBoundaryEvaluation(task, proofOverride = null) {
658
699
  };
659
700
  }
660
701
 
661
- function requireMeaningfulTaskProof(label, proof, { required = true } = {}) {
662
- const issue = meaningfulTaskProofIssue(proof, { required });
663
- if (issue) failTask(label, 'weak_proof', `meaningful proof required: ${issue}`);
664
- }
665
-
666
- function sendProofIssue(res, proof, issue) {
667
- return sendJson(res, 400, {
668
- ok: false,
669
- reason: String(proof || '').trim() ? 'weak_proof' : 'proof_required',
670
- detail: `meaningful proof required: ${issue}`,
671
- });
672
- }
673
-
674
702
  function positional(args) {
675
703
  return args.filter((a, i) => {
676
704
  if (a.startsWith('--')) return false;
@@ -6134,7 +6162,7 @@ function renderTaskDesk(rows, refRows = rows) {
6134
6162
  if (active.length === 0) console.log('clear no active tasks');
6135
6163
  console.log('');
6136
6164
  console.log(`active ${active.length} / done ${done.length}`);
6137
- console.log('next: atris task next');
6165
+ console.log(`next: ${deskNextCommand(displayRows, personName())}`);
6138
6166
  }
6139
6167
 
6140
6168
  function cmdAdd(args) {
@@ -6446,6 +6474,15 @@ function cmdDay(args) {
6446
6474
  console.log('add: atris task delegate "..." --to task-planner --tag tasks');
6447
6475
  }
6448
6476
 
6477
+ function cmdFirstMinute() {
6478
+ const root = process.cwd();
6479
+ const screen = buildFirstMinute({
6480
+ root,
6481
+ fresh: !fs.existsSync(path.join(root, 'atris')),
6482
+ });
6483
+ console.log(screen.text);
6484
+ }
6485
+
6449
6486
  function cmdHome(args) {
6450
6487
  const all = hasFlag(args, '--all');
6451
6488
  const everywhere = taskScopeEverywhere(args);
@@ -6771,7 +6808,108 @@ function createEndgameSeedTask(taskDb, db, seed, owner) {
6771
6808
  };
6772
6809
  }
6773
6810
 
6811
+ function nextActionFromCommand(command) {
6812
+ const match = String(command || '').trim().match(/^atris (?:task|mission) (\S+)/);
6813
+ const verb = match ? match[1] : '';
6814
+ if (!verb || verb === 'new') return 'none';
6815
+ return verb;
6816
+ }
6817
+
6818
+ function cmdNextTruth(args) {
6819
+ const owner = flag(args, '--as') || personName() || DEFAULT_OWNER;
6820
+ const scope = taskQueueScopeFromArgs(args);
6821
+ const taskDb = getTaskDb();
6822
+ const db = taskDb.open();
6823
+ const workspaceRoot = taskDb.workspaceRoot();
6824
+ const rows = taskDb.listTasks(db, { workspaceRoot, limit: 500 });
6825
+ const existingProj = readProjectionFile(workspaceRoot);
6826
+ let projection;
6827
+ let outPath;
6828
+ if (rows.length === 0 && existingProj && Array.isArray(existingProj.tasks) && existingProj.tasks.length > 0) {
6829
+ projection = existingProj;
6830
+ outPath = path.resolve(path.join(workspaceRoot || '.', '.atris', 'state', 'tasks.projection.json'));
6831
+ } else {
6832
+ const written = writeDefaultProjection(taskDb, db);
6833
+ projection = written.projection;
6834
+ outPath = written.outPath;
6835
+ }
6836
+ const scoped = filterTasksByScope(projection.tasks || [], scope);
6837
+ const openDecisions = scoped.filter((task) => task && isDecisionTask(task) && task.status === 'open');
6838
+ const actionable = scoped.filter((task) => task && !isDecisionTask(task));
6839
+ const picked = pickNext({ tasks: actionable, person: owner });
6840
+ const command = picked.task ? (picked.command || taskCommand(picked.task, owner)) : taskNextCommand(actionable, owner);
6841
+ const task = picked.task || null;
6842
+
6843
+ if (!task && openDecisions.length) {
6844
+ if (wantsJson(args)) {
6845
+ printJson({
6846
+ ok: false,
6847
+ action: 'refused',
6848
+ reason: DECISION_REFUSE_REASON,
6849
+ skipped_decision_count: openDecisions.length,
6850
+ owner: String(owner),
6851
+ scope: normalizeTaskQueueScope(scope),
6852
+ projection_path: outPath,
6853
+ });
6854
+ process.exit(1);
6855
+ }
6856
+ console.error(DECISION_REFUSE_REASON);
6857
+ process.exit(1);
6858
+ }
6859
+
6860
+ if (wantsJson(args)) {
6861
+ printJson({
6862
+ ok: true,
6863
+ action: nextActionFromCommand(command),
6864
+ command,
6865
+ task_id: task ? task.id : null,
6866
+ owner: String(owner),
6867
+ scope: normalizeTaskQueueScope(scope),
6868
+ projection_path: outPath,
6869
+ task: task ? compactTaskFromProjection(projection, task.id) : null,
6870
+ });
6871
+ return;
6872
+ }
6873
+
6874
+ if (!task) {
6875
+ console.log(noOpenTasksMessage(scope));
6876
+ console.log(`next: ${command}`);
6877
+ return;
6878
+ }
6879
+ console.log(`next: ${command}`);
6880
+ }
6881
+
6882
+ function isUninitializedTaskFolder(cwd = process.cwd()) {
6883
+ const workspace = getTaskDb().workspaceRoot(cwd);
6884
+ if (fs.existsSync(path.join(workspace, 'atris'))) return false;
6885
+ const existing = readProjectionFile(workspace);
6886
+ return !(existing && Array.isArray(existing.tasks) && existing.tasks.length > 0);
6887
+ }
6888
+
6774
6889
  function cmdNext(args) {
6890
+ const root = process.cwd();
6891
+ // Empty folder talks like bare atris. Walk up like the task db so a
6892
+ // project subdir stays in the room; stop before inheriting /tmp.
6893
+ // A leftover projection with live tasks still names that work.
6894
+ if (isUninitializedTaskFolder(root)) {
6895
+ if (wantsJson(args)) {
6896
+ printJson({
6897
+ ok: true,
6898
+ action: 'init',
6899
+ command: 'atris init --minimal',
6900
+ task_id: null,
6901
+ owner: String(flag(args, '--as') || personName() || DEFAULT_OWNER),
6902
+ scope: normalizeTaskQueueScope(taskQueueScopeFromArgs(args)),
6903
+ projection_path: null,
6904
+ task: null,
6905
+ });
6906
+ return;
6907
+ }
6908
+ const screen = buildFirstMinute({ root, fresh: true });
6909
+ console.log(screen.text);
6910
+ return;
6911
+ }
6912
+ if (!hasFlag(args, '--create-next')) return cmdNextTruth(args);
6775
6913
  const owner = flag(args, '--as') || DEFAULT_OWNER;
6776
6914
  const scope = taskQueueScopeFromArgs(args);
6777
6915
  const scoped = !taskQueueScopeIsEmpty(scope);
@@ -8637,7 +8775,7 @@ function taskStepError(reason, detail, { status = 409, exitCode = 1, page = null
8637
8775
  }
8638
8776
 
8639
8777
  function taskStepStatusForReason(reason) {
8640
- if (['goal_required', 'exit_required', 'proof_needed_required', 'first_move_required', 'proof_required', 'weak_proof', 'invalid_reward'].includes(reason)) {
8778
+ if (['goal_required', 'exit_required', 'proof_needed_required', 'first_move_required', 'proof_required', 'weak_proof', 'proof_command_not_run', 'invalid_reward'].includes(reason)) {
8641
8779
  return 400;
8642
8780
  }
8643
8781
  if (reason === 'not_found') return 404;
@@ -8848,7 +8986,11 @@ function runTaskStep(taskDb, db, taskId, options = {}) {
8848
8986
  const proof = String(options.proof || '').trim();
8849
8987
  const proofIssue = meaningfulTaskProofIssue(proof);
8850
8988
  if (proofIssue) {
8851
- throw taskStepError(proof ? 'weak_proof' : 'proof_required', `meaningful proof required: ${proofIssue}`, { status: 400, exitCode: 2, page: actionPage });
8989
+ throw taskStepError(proof ? 'weak_proof' : 'proof_required', weakProofDetail(proofIssue), { status: 400, exitCode: 2, page: actionPage });
8990
+ }
8991
+ const unrunIssue = unrunNamedProofCommandIssue(proof, '');
8992
+ if (unrunIssue) {
8993
+ throw taskStepError(unrunIssue.reason, unrunIssue.detail, { status: 400, exitCode: 2, page: actionPage });
8852
8994
  }
8853
8995
  const missionXpIssue = missionXpEndToEndProofIssue(task, proof, task.workspace_root || process.cwd());
8854
8996
  if (missionXpIssue) {
@@ -8895,7 +9037,11 @@ function runTaskStep(taskDb, db, taskId, options = {}) {
8895
9037
  const proof = String(options.proof || '').trim();
8896
9038
  const proofIssue = meaningfulTaskProofIssue(proof);
8897
9039
  if (proofIssue) {
8898
- throw taskStepError(proof ? 'weak_proof' : 'proof_required', `meaningful proof required: ${proofIssue}`, { status: 400, exitCode: 2, page: actionPage });
9040
+ throw taskStepError(proof ? 'weak_proof' : 'proof_required', weakProofDetail(proofIssue), { status: 400, exitCode: 2, page: actionPage });
9041
+ }
9042
+ const unrunIssue = unrunNamedProofCommandIssue(proof, '');
9043
+ if (unrunIssue) {
9044
+ throw taskStepError(unrunIssue.reason, unrunIssue.detail, { status: 400, exitCode: 2, page: actionPage });
8899
9045
  }
8900
9046
  const parsedReward = parseStepReviewReward(options.reward);
8901
9047
  if (!parsedReward.ok) {
@@ -9127,24 +9273,51 @@ function cmdCurrentStep(args) {
9127
9273
  } catch (error) {
9128
9274
  const errorCurrent = error.current || null;
9129
9275
  if (wantsJson(args)) {
9130
- printJson({
9131
- ok: false,
9132
- action: 'current_step',
9133
- reason: error.reason || 'step_failed',
9134
- detail: error.message,
9135
- selected_task_id: errorCurrent ? errorCurrent.selected_task_id : null,
9136
- selected_ref: errorCurrent ? errorCurrent.selected_ref : null,
9137
- selected_next_key: selectedNextKeyFromCurrent(errorCurrent),
9138
- current: errorCurrent,
9139
- page: error.page || null,
9140
- });
9276
+ const selectedRef = errorCurrent ? errorCurrent.selected_ref : null;
9277
+ const nextCommand = error.page?.stage?.next_action?.command
9278
+ || (errorCurrent && errorCurrent.next && errorCurrent.next.command)
9279
+ || null;
9280
+ printCliJson(
9281
+ {
9282
+ ok: false,
9283
+ action: 'current_step',
9284
+ reason: error.reason || 'step_failed',
9285
+ detail: error.message,
9286
+ selected_task_id: errorCurrent ? errorCurrent.selected_task_id : null,
9287
+ selected_ref: selectedRef,
9288
+ selected_next_key: selectedNextKeyFromCurrent(errorCurrent),
9289
+ next_command: nextCommand,
9290
+ current: errorCurrent,
9291
+ page: error.page || null,
9292
+ },
9293
+ compactErrorPayload({
9294
+ reason: error.reason || 'step_failed',
9295
+ detail: error.message,
9296
+ selected_ref: selectedRef,
9297
+ next_command: nextCommand,
9298
+ }),
9299
+ args,
9300
+ );
9141
9301
  } else {
9142
9302
  console.error(error.message || 'atris task current-step failed');
9143
9303
  }
9144
9304
  process.exit(error.exitCode || 1);
9145
9305
  }
9146
9306
  if (wantsJson(args)) {
9147
- printJson(result);
9307
+ const nextCommand = result.page?.stage?.next_action?.command || null;
9308
+ printCliJson(
9309
+ result,
9310
+ compactSuccessPayload({
9311
+ action: 'current_step',
9312
+ ids: {
9313
+ selected_task_id: result.selected_task_id || null,
9314
+ selected_ref: result.selected_ref || null,
9315
+ step_action: result.step && result.step.step_action ? result.step.step_action : null,
9316
+ },
9317
+ next_command: nextCommand,
9318
+ }),
9319
+ args,
9320
+ );
9148
9321
  return;
9149
9322
  }
9150
9323
  console.log(`current-step ${taskRef(result.task)} -> ${result.step.step_action}`);
@@ -9610,14 +9783,38 @@ function cmdReady(args) {
9610
9783
  console.error('atris task ready: id required');
9611
9784
  process.exit(2);
9612
9785
  }
9613
- // Two ways to prove: --proof "<note>" (claimed, pattern-checked, unchanged) or
9614
- // --verify "<command>" which actually RUNS the command and gates ready on exit 0,
9615
- // turning a claim into executed evidence. --verify can carry an optional --proof note.
9786
+ // Two ways to prove: --proof "<note>" (claimed, pattern-checked) or
9787
+ // --verify "<command>" which actually RUNS the command and gates ready on exit 0.
9788
+ // If --proof names npm test, node --test, or git diff --check, this process
9789
+ // must have run that command. A sentence that names one of those is a lie.
9616
9790
  const proofFlag = flag(args, '--proof');
9617
9791
  const verifyFlag = flag(args, '--verify');
9618
- const resultSentence = requireResultSentence('atris task ready', textFlag(args, ['--result']), { ready: true });
9792
+ const proofUrl = textFlag(args, ['--proof-url']);
9793
+ const iFetched = hasFlag(args, '--i-fetched');
9794
+ if (proofUrl && !iFetched) {
9795
+ failTask(
9796
+ 'atris task ready',
9797
+ 'unfetched_proof_url',
9798
+ 'CI run URLs only count after atris fetches them, or with --proof-url and --i-fetched',
9799
+ );
9800
+ }
9801
+ if (iFetched && !proofUrl && !(typeof proofFlag === 'string' && /https?:\/\/github[.]com\//i.test(proofFlag))) {
9802
+ failTask(
9803
+ 'atris task ready',
9804
+ 'proof_url_required',
9805
+ '--i-fetched requires --proof-url <actions-run-url> (or a URL inside --proof)',
9806
+ );
9807
+ }
9619
9808
  const usedVerify = typeof verifyFlag === 'string' ? verifyFlag.trim() : '';
9809
+ const resultSentence = requireResultSentence('atris task ready', textFlag(args, ['--result']), {
9810
+ ready: true,
9811
+ allowCommandMention: Boolean(usedVerify),
9812
+ });
9620
9813
  let proof = typeof proofFlag === 'string' ? proofFlag : '';
9814
+ if (proofUrl && iFetched) {
9815
+ const attested = `[i-fetched] ${proofUrl}`;
9816
+ proof = proof.trim() ? `${proof.trim()} ${attested}` : attested;
9817
+ }
9621
9818
  const verifyAutoCertifyAllowed = !usedVerify || isAutoCertifyVerifyCommandAllowed(verifyFlag);
9622
9819
  if (usedVerify) {
9623
9820
  // Run the verifier once and write a receipt (pass or fail) so the review
@@ -9638,6 +9835,15 @@ function cmdReady(args) {
9638
9835
  // this codebase fails there, and one that passes anywhere passes there too.
9639
9836
  if (!hasFlag(args, '--no-falsify-check')) {
9640
9837
  const { probeVerifierCanFail } = require('../lib/falsifier-probe');
9838
+ const { classifyVerifier } = require('../lib/verifier-quality');
9839
+ const quality = classifyVerifier(verifyFlag);
9840
+ if (!quality.ok) {
9841
+ console.error(`atris task ready: weak verifier — ${quality.reason}`);
9842
+ console.error(` command: ${verifyFlag}`);
9843
+ console.error(' allowlisted examples: node --test, npm test, git diff --check, node --check, rg <symbol>');
9844
+ console.error(' or pass --no-falsify-check to accept anyway.');
9845
+ process.exit(1);
9846
+ }
9641
9847
  const probe = probeVerifierCanFail({ command: verifyFlag });
9642
9848
  if (probe.probed && probe.canFail === false) {
9643
9849
  console.error(`atris task ready: this check cannot fail — ${probe.reason}`);
@@ -9656,6 +9862,7 @@ function cmdReady(args) {
9656
9862
  process.exit(2);
9657
9863
  }
9658
9864
  requireMeaningfulTaskProof('atris task ready', proof);
9865
+ requireRanNamedProofCommand('atris task ready', proof, usedVerify);
9659
9866
  const lesson = flag(args, '--lesson') || '';
9660
9867
  const nextTaskInput = normalizeReviewNextTaskInput(typeof flag(args, '--next') === 'string' ? flag(args, '--next') : '');
9661
9868
  const landing = landingFlags(args);
@@ -9760,7 +9967,12 @@ function cmdReady(args) {
9760
9967
  if (policyHints.length) handoff.policy_hints = policyHints;
9761
9968
  if (readyPolicyGate.gate.advisories.length) handoff.policy_gate = readyPolicyGate.gate;
9762
9969
  if (wantsJson(args)) {
9763
- printJson({
9970
+ const taskCard = compactTaskFromProjection(projection, taskId);
9971
+ const nextCommand = handoff.review_chat_command
9972
+ || (agentCertified
9973
+ ? `atris task accept ${taskRef(taskCard || result.row || taskId)}`
9974
+ : `atris task reviews`);
9975
+ const fullReady = {
9764
9976
  ok: true,
9765
9977
  action: 'ready',
9766
9978
  task_id: taskId,
@@ -9773,8 +9985,23 @@ function cmdReady(args) {
9773
9985
  landing_advisory: landingAdvisory,
9774
9986
  ...(nextTaskInput.ignored ? { review_next_task_ignored: nextTaskInput.ignored } : {}),
9775
9987
  projection_path: outPath,
9776
- task: compactTaskFromProjection(projection, taskId),
9777
- });
9988
+ task: taskCard,
9989
+ next_command: nextCommand,
9990
+ };
9991
+ printCliJson(
9992
+ fullReady,
9993
+ compactSuccessPayload({
9994
+ action: 'ready',
9995
+ ids: {
9996
+ task_id: taskId,
9997
+ selected_ref: taskRef(taskCard || result.row || taskId),
9998
+ version: result.event.version,
9999
+ agent_certified: agentCertified,
10000
+ },
10001
+ next_command: nextCommand,
10002
+ }),
10003
+ args,
10004
+ );
9778
10005
  return;
9779
10006
  }
9780
10007
  console.log(`ready for approval ${taskRef(compactTaskFromProjection(projection, taskId))} v${result.event.version}`);
@@ -11043,15 +11270,23 @@ function importTodoFile(taskDb, db, target) {
11043
11270
  const parsed = parseTodoFile(filePath);
11044
11271
  const ws = taskDb.workspaceRoot();
11045
11272
  const all = [
11046
- ...parsed.backlog.map(t => ({ ...t, importStatus: 'open' })),
11047
- ...parsed.inProgress.map(t => ({ ...t, importStatus: 'claimed' })),
11048
- ...(parsed.review || []).map(t => ({ ...t, importStatus: 'review' })),
11273
+ ...parsed.backlog.map(t => ({ ...t, importStatus: 'open', sourceFile: filePath })),
11274
+ ...parsed.inProgress.map(t => ({ ...t, importStatus: 'claimed', sourceFile: filePath })),
11275
+ ...(parsed.review || []).map(t => ({ ...t, importStatus: 'review', sourceFile: filePath })),
11049
11276
  ];
11277
+ const knownTitles = new Set(
11278
+ taskDb.listTasks(db, { workspaceRoot: ws, limit: 500 }).map((row) => taskDb.normalizeTitle(row.title))
11279
+ );
11050
11280
  let inserted = 0;
11051
11281
  let skipped = 0;
11052
11282
  for (const t of all) {
11053
11283
  if (!t.title) continue;
11054
- const sk = taskDb.sourceKey(filePath, t.title);
11284
+ const normalized = taskDb.normalizeTitle(t.title);
11285
+ if (knownTitles.has(normalized)) {
11286
+ skipped++;
11287
+ continue;
11288
+ }
11289
+ const sk = taskDb.sourceKey(t.sourceFile || filePath, t.title);
11055
11290
  const result = taskDb.addTask(db, {
11056
11291
  title: t.title,
11057
11292
  tag: t.tag || null,
@@ -11061,11 +11296,72 @@ function importTodoFile(taskDb, db, target) {
11061
11296
  claimedBy: t.claimed || null,
11062
11297
  metadata: { todo_id: t.id, todo_tags: t.tags || [], claimed: t.claimed, stage: t.stage, verify: t.verify },
11063
11298
  });
11064
- if (result.inserted) inserted++; else skipped++;
11299
+ if (result.inserted) {
11300
+ inserted++;
11301
+ knownTitles.add(normalized);
11302
+ } else {
11303
+ skipped++;
11304
+ }
11305
+ }
11306
+ return { ok: true, inserted, skipped, filePath, knownTitles };
11307
+ }
11308
+
11309
+ function importJournalFile(taskDb, db, journalPath, knownTitles = null) {
11310
+ const filePath = path.resolve(journalPath);
11311
+ if (!fs.existsSync(filePath)) {
11312
+ return { ok: false, reason: 'not_found', filePath, inserted: 0, skipped: 0 };
11313
+ }
11314
+ const { parseSection } = require('../lib/todo-fallback');
11315
+ const { parseInboxItems } = require('../lib/file-ops');
11316
+ const content = fs.readFileSync(filePath, 'utf8');
11317
+ const ws = taskDb.workspaceRoot();
11318
+ const titles = knownTitles || new Set(
11319
+ taskDb.listTasks(db, { workspaceRoot: ws, limit: 500 }).map((row) => taskDb.normalizeTitle(row.title))
11320
+ );
11321
+ const all = [
11322
+ ...parseSection(content, 'Backlog').map((t) => ({ ...t, importStatus: 'open' })),
11323
+ ...parseSection(content, 'In Progress').map((t) => ({ ...t, importStatus: 'claimed' })),
11324
+ ...parseInboxItems(content).map((item) => ({
11325
+ id: `I${item.id}`,
11326
+ title: item.text,
11327
+ tag: 'inbox',
11328
+ importStatus: 'open',
11329
+ })),
11330
+ ];
11331
+ let inserted = 0;
11332
+ let skipped = 0;
11333
+ for (const t of all) {
11334
+ if (!t.title) continue;
11335
+ const normalized = taskDb.normalizeTitle(t.title);
11336
+ if (titles.has(normalized)) {
11337
+ skipped++;
11338
+ continue;
11339
+ }
11340
+ const sk = taskDb.sourceKey(filePath, t.title);
11341
+ const result = taskDb.addTask(db, {
11342
+ title: t.title,
11343
+ tag: t.tag || null,
11344
+ workspaceRoot: ws,
11345
+ sourceKey: sk,
11346
+ status: t.importStatus,
11347
+ claimedBy: t.claimed || null,
11348
+ metadata: { todo_id: t.id, source: 'journal_import' },
11349
+ });
11350
+ if (result.inserted) {
11351
+ inserted++;
11352
+ titles.add(normalized);
11353
+ } else {
11354
+ skipped++;
11355
+ }
11065
11356
  }
11066
11357
  return { ok: true, inserted, skipped, filePath };
11067
11358
  }
11068
11359
 
11360
+ function todayJournalPath() {
11361
+ const { getLogPath } = require('../lib/file-ops');
11362
+ return getLogPath().logFile;
11363
+ }
11364
+
11069
11365
  function cmdImport(args) {
11070
11366
  const pos = positional(args);
11071
11367
  const target = pos[0] || 'atris/TODO.md';
@@ -11076,7 +11372,9 @@ function cmdImport(args) {
11076
11372
  console.error(`atris task import: file not found: ${result.filePath}`);
11077
11373
  process.exit(2);
11078
11374
  }
11079
- const { inserted, skipped, filePath } = result;
11375
+ const journalResult = importJournalFile(taskDb, db, todayJournalPath(), result.knownTitles);
11376
+ const inserted = result.inserted + (journalResult.ok ? journalResult.inserted : 0);
11377
+ const skipped = result.skipped + (journalResult.ok ? journalResult.skipped : 0);
11080
11378
  const { outPath } = writeDefaultProjection(taskDb, db);
11081
11379
  if (wantsJson(args)) {
11082
11380
  printJson({
@@ -11084,12 +11382,16 @@ function cmdImport(args) {
11084
11382
  action: 'imported',
11085
11383
  inserted,
11086
11384
  skipped,
11087
- source: filePath,
11385
+ source: result.filePath,
11386
+ journal: journalResult.ok ? journalResult.filePath : null,
11088
11387
  projection_path: outPath,
11089
11388
  });
11090
11389
  return;
11091
11390
  }
11092
- console.log(`imported ${inserted} new, skipped ${skipped} (already imported), source=${filePath}`);
11391
+ console.log(`imported ${inserted} new, skipped ${skipped} (already imported), source=${result.filePath}`);
11392
+ if (journalResult.ok && journalResult.inserted > 0) {
11393
+ console.log(`journal imported ${journalResult.inserted} from ${journalResult.filePath}`);
11394
+ }
11093
11395
  }
11094
11396
 
11095
11397
  function cmdWhere(args) {
@@ -11275,12 +11577,22 @@ function cmdSetup(args) {
11275
11577
  const db = taskDb.open();
11276
11578
  const ws = taskDb.workspaceRoot();
11277
11579
  let importResult = null;
11580
+ let journalResult = null;
11278
11581
  if (hasFlag(args, '--import-todo')) {
11279
11582
  importResult = importTodoFile(taskDb, db, flag(args, '--todo') || 'atris/TODO.md');
11280
11583
  if (!importResult.ok && flag(args, '--todo')) {
11281
11584
  console.error(`atris task setup: TODO file not found: ${importResult.filePath}`);
11282
11585
  process.exit(2);
11283
11586
  }
11587
+ if (importResult.ok) {
11588
+ journalResult = importJournalFile(taskDb, db, todayJournalPath(), importResult.knownTitles);
11589
+ importResult = {
11590
+ ...importResult,
11591
+ inserted: importResult.inserted + (journalResult.ok ? journalResult.inserted : 0),
11592
+ skipped: importResult.skipped + (journalResult.ok ? journalResult.skipped : 0),
11593
+ journal: journalResult.ok ? journalResult.filePath : null,
11594
+ };
11595
+ }
11284
11596
  }
11285
11597
  const { projection, outPath } = writeDefaultProjection(taskDb, db);
11286
11598
  if (wantsJson(args)) {
@@ -11293,6 +11605,7 @@ function cmdSetup(args) {
11293
11605
  inserted: importResult.inserted,
11294
11606
  skipped: importResult.skipped,
11295
11607
  source: importResult.filePath,
11608
+ journal: importResult.journal || null,
11296
11609
  } : null,
11297
11610
  projection,
11298
11611
  });
@@ -12572,6 +12885,14 @@ function postTaskApiReady({ res, taskDb, db, taskId, body }) {
12572
12885
  const proof = String(body.proof || '').trim();
12573
12886
  const proofIssue = meaningfulTaskProofIssue(proof);
12574
12887
  if (proofIssue) return sendProofIssue(res, proof, proofIssue);
12888
+ const unrunIssue = unrunNamedProofCommandIssue(proof, '');
12889
+ if (unrunIssue) {
12890
+ return sendJson(res, 400, {
12891
+ ok: false,
12892
+ reason: unrunIssue.reason,
12893
+ detail: unrunIssue.detail,
12894
+ });
12895
+ }
12575
12896
  const nextTaskInput = normalizeReviewNextTaskInput(body.next);
12576
12897
  const actor = String(body.actor || DEFAULT_OWNER);
12577
12898
  const resultText = String(body.result || '').replace(/\s+/g, ' ').trim();
@@ -12817,8 +13138,9 @@ async function runTaskCommand(args) {
12817
13138
  const raw = args || [];
12818
13139
  if (raw.includes('--help') || raw.includes('-h')) return help();
12819
13140
  const first = raw[0];
12820
- const sub = !first || first.startsWith('--') ? 'desk' : first;
12821
- const rest = !first || first.startsWith('--') ? raw : raw.slice(1);
13141
+ if (!first) return cmdFirstMinute();
13142
+ const sub = first.startsWith('--') ? 'desk' : first;
13143
+ const rest = first.startsWith('--') ? raw : raw.slice(1);
12822
13144
  switch (sub) {
12823
13145
  case 'desk': return cmdHome(rest);
12824
13146
  case 'today': return cmdDay(rest);
@@ -12971,6 +13293,42 @@ const MUTATING_TASK_COMMANDS = new Set([
12971
13293
  async function run(args) {
12972
13294
  const raw = args || [];
12973
13295
  const sub = !raw[0] || raw[0].startsWith('--') ? 'desk' : raw[0];
13296
+ // `task accept --help` is a help request, not a run. Accept mutates
13297
+ // review rows, so never list or accept just to show usage.
13298
+ if (sub === 'accept' && argsWantHelp(raw.slice(1))) {
13299
+ console.log('Usage: atris task accept <id> [--proof "..."] [--public]');
13300
+ return;
13301
+ }
13302
+ // `task claim --help` is a help request, not a run. Claim mutates
13303
+ // ownership, so never list or claim just to show usage.
13304
+ if (sub === 'claim' && argsWantHelp(raw.slice(1))) {
13305
+ console.log('Usage: atris task claim <id> --as <member>');
13306
+ return;
13307
+ }
13308
+ // `task ready --help` is a help request, not a run. Ready mutates
13309
+ // review state, so never list or ready just to show usage.
13310
+ if (sub === 'ready' && argsWantHelp(raw.slice(1))) {
13311
+ console.log('Usage: atris task ready <id> --proof "..." --result "<sentence>"');
13312
+ return;
13313
+ }
13314
+ // `task step --help` is a help request, not a run. Step mutates
13315
+ // Plan/Do/Review, so never list or step just to show usage.
13316
+ if (sub === 'step' && argsWantHelp(raw.slice(1))) {
13317
+ console.log('Usage: atris task step <id> [--json]');
13318
+ return;
13319
+ }
13320
+ // `task next --help` is a help request, not a run. Next can open
13321
+ // the task db or print the desk, so never list or claim just to show usage.
13322
+ if (sub === 'next' && argsWantHelp(raw.slice(1))) {
13323
+ console.log('Usage: atris task next [--tag <tag>] [--create-next]');
13324
+ return;
13325
+ }
13326
+ // `task show --help` is a help request, not a run. Show can dump
13327
+ // a task card or the encyclopedia, so never list or show just to show usage.
13328
+ if (sub === 'show' && argsWantHelp(raw.slice(1))) {
13329
+ console.log('Usage: atris task show <id> [--json]');
13330
+ return;
13331
+ }
12974
13332
  const result = await runTaskCommand(raw);
12975
13333
  const skipsRender = sub === 'clear-done' && hasFlag(raw, '--dry-run');
12976
13334
  if (MUTATING_TASK_COMMANDS.has(sub) && !skipsRender) autoRenderTodoFromDb();