atris 3.53.0 → 3.56.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 (119) 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/x-search/SKILL.md +24 -22
  7. package/atris/skills/youtube/SKILL.md +82 -33
  8. package/atris/team/_template/SOUL.md +40 -0
  9. package/atris/team/validator/MEMBER.md +1 -2
  10. package/bin/atris +21 -0
  11. package/bin/atris.js +563 -373
  12. package/commands/api-key.js +170 -0
  13. package/commands/auth.js +453 -43
  14. package/commands/autoland.js +3 -1
  15. package/commands/autopilot-front.js +58 -11
  16. package/commands/avail.js +5 -4
  17. package/commands/balance.js +55 -0
  18. package/commands/bench.js +29 -1
  19. package/commands/brain.js +63 -12
  20. package/commands/brainstorm.js +18 -0
  21. package/commands/business.js +149 -1
  22. package/commands/chat-scan.js +17 -4
  23. package/commands/close.js +5 -1
  24. package/commands/doctor.js +174 -0
  25. package/commands/dream.js +7 -0
  26. package/commands/drill.js +34 -2
  27. package/commands/engine.js +47 -35
  28. package/commands/errors.js +58 -8
  29. package/commands/experiments.js +14 -0
  30. package/commands/feedback.js +61 -2
  31. package/commands/fleet-report.js +27 -7
  32. package/commands/fleet.js +98 -11
  33. package/commands/founder.js +60 -21
  34. package/commands/human-missions.js +79 -4
  35. package/commands/improve.js +54 -4
  36. package/commands/init.js +68 -92
  37. package/commands/install.js +40 -0
  38. package/commands/integrations.js +565 -30
  39. package/commands/interview.js +8 -0
  40. package/commands/land.js +48 -17
  41. package/commands/learn.js +29 -4
  42. package/commands/log.js +51 -0
  43. package/commands/member.js +68 -10
  44. package/commands/mission.js +287 -50
  45. package/commands/now.js +15 -3
  46. package/commands/orb.js +97 -2
  47. package/commands/pack.js +83 -9
  48. package/commands/playbook.js +381 -0
  49. package/commands/plugin.js +16 -0
  50. package/commands/radar.js +31 -7
  51. package/commands/recap.js +128 -21
  52. package/commands/release.js +102 -2
  53. package/commands/review.js +5 -10
  54. package/commands/revisions.js +55 -0
  55. package/commands/run-front.js +12 -4
  56. package/commands/scout.js +12 -1
  57. package/commands/search.js +123 -28
  58. package/commands/sign.js +2 -2
  59. package/commands/signup.js +12 -3
  60. package/commands/site-deploy.js +17 -4
  61. package/commands/site.js +2 -2
  62. package/commands/skill-eval.js +289 -0
  63. package/commands/skill.js +56 -6
  64. package/commands/social.js +603 -0
  65. package/commands/spaceship.js +69 -6
  66. package/commands/stream.js +8 -1
  67. package/commands/study.js +32 -19
  68. package/commands/sync.js +28 -22
  69. package/commands/task.js +435 -60
  70. package/commands/teach.js +72 -63
  71. package/commands/terminal.js +25 -9
  72. package/commands/topup.js +88 -0
  73. package/commands/truth.js +52 -6
  74. package/commands/usage.js +91 -0
  75. package/commands/verify.js +3 -2
  76. package/commands/version.js +3 -1
  77. package/commands/voice.js +5 -1
  78. package/commands/who.js +69 -11
  79. package/commands/wiki.js +7 -4
  80. package/commands/wish.js +76 -5
  81. package/commands/workflow.js +327 -145
  82. package/commands/worktree.js +1 -0
  83. package/commands/write.js +5 -0
  84. package/commands/x-search.js +340 -0
  85. package/commands/xp.js +5 -1
  86. package/commands/youtube.js +551 -25
  87. package/lib/account-bound.js +47 -0
  88. package/lib/auto-accept-certified.js +8 -1
  89. package/lib/auto-lessons.js +186 -0
  90. package/lib/autoland.js +4 -2
  91. package/lib/chat-log-scan.js +7 -4
  92. package/lib/cli-json.js +77 -0
  93. package/lib/cli-scope.js +26 -0
  94. package/lib/codex-flight.js +1 -0
  95. package/lib/conductor-artifacts.js +1 -1
  96. package/lib/context-gatherer.js +11 -0
  97. package/lib/developer-api.js +116 -0
  98. package/lib/feedback-triage.js +274 -0
  99. package/lib/first-minute.js +398 -0
  100. package/lib/fleet.js +115 -34
  101. package/lib/functional-owner.js +22 -0
  102. package/lib/known-commands.js +31 -5
  103. package/lib/member-scaffold.js +197 -0
  104. package/lib/mission-root.js +4 -2
  105. package/lib/noninteractive.js +88 -0
  106. package/lib/policy-lessons.js +4 -1
  107. package/lib/revision-metric.js +279 -0
  108. package/lib/scratch-root.js +48 -0
  109. package/lib/skill-eval-gate.js +249 -0
  110. package/lib/task-db.js +4 -0
  111. package/lib/task-proof.js +69 -8
  112. package/lib/verifier-quality.js +69 -0
  113. package/lib/wish-audit.js +2 -2
  114. package/lib/wish-delegate.js +10 -1
  115. package/lib/workspace-scaffold.js +270 -0
  116. package/package.json +3 -2
  117. package/scripts/det/checklist-score.js +191 -0
  118. package/scripts/det/ytsearch +31 -0
  119. 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, speakFirstMinute, 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,10 +6162,27 @@ 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) {
6169
+ const root = process.cwd();
6170
+ // Empty folder talks like bare atris. A leftover title is not a
6171
+ // task desk. After init, a title still files.
6172
+ if (isUninitializedTaskFolder(root)) {
6173
+ if (wantsJson(args)) {
6174
+ printJson({
6175
+ ok: true,
6176
+ action: 'init',
6177
+ command: 'atris init --minimal',
6178
+ task_id: null,
6179
+ projection_path: null,
6180
+ task: null,
6181
+ });
6182
+ return;
6183
+ }
6184
+ return speakFirstMinute({ root, fresh: true });
6185
+ }
6141
6186
  const pos = positional(args);
6142
6187
  const title = pos.join(' ').trim();
6143
6188
  if (!title) {
@@ -6446,6 +6491,15 @@ function cmdDay(args) {
6446
6491
  console.log('add: atris task delegate "..." --to task-planner --tag tasks');
6447
6492
  }
6448
6493
 
6494
+ function cmdFirstMinute() {
6495
+ const root = process.cwd();
6496
+ const screen = buildFirstMinute({
6497
+ root,
6498
+ fresh: !fs.existsSync(path.join(root, 'atris')),
6499
+ });
6500
+ console.log(screen.text);
6501
+ }
6502
+
6449
6503
  function cmdHome(args) {
6450
6504
  const all = hasFlag(args, '--all');
6451
6505
  const everywhere = taskScopeEverywhere(args);
@@ -6771,7 +6825,108 @@ function createEndgameSeedTask(taskDb, db, seed, owner) {
6771
6825
  };
6772
6826
  }
6773
6827
 
6828
+ function nextActionFromCommand(command) {
6829
+ const match = String(command || '').trim().match(/^atris (?:task|mission) (\S+)/);
6830
+ const verb = match ? match[1] : '';
6831
+ if (!verb || verb === 'new') return 'none';
6832
+ return verb;
6833
+ }
6834
+
6835
+ function cmdNextTruth(args) {
6836
+ const owner = flag(args, '--as') || personName() || DEFAULT_OWNER;
6837
+ const scope = taskQueueScopeFromArgs(args);
6838
+ const taskDb = getTaskDb();
6839
+ const db = taskDb.open();
6840
+ const workspaceRoot = taskDb.workspaceRoot();
6841
+ const rows = taskDb.listTasks(db, { workspaceRoot, limit: 500 });
6842
+ const existingProj = readProjectionFile(workspaceRoot);
6843
+ let projection;
6844
+ let outPath;
6845
+ if (rows.length === 0 && existingProj && Array.isArray(existingProj.tasks) && existingProj.tasks.length > 0) {
6846
+ projection = existingProj;
6847
+ outPath = path.resolve(path.join(workspaceRoot || '.', '.atris', 'state', 'tasks.projection.json'));
6848
+ } else {
6849
+ const written = writeDefaultProjection(taskDb, db);
6850
+ projection = written.projection;
6851
+ outPath = written.outPath;
6852
+ }
6853
+ const scoped = filterTasksByScope(projection.tasks || [], scope);
6854
+ const openDecisions = scoped.filter((task) => task && isDecisionTask(task) && task.status === 'open');
6855
+ const actionable = scoped.filter((task) => task && !isDecisionTask(task));
6856
+ const picked = pickNext({ tasks: actionable, person: owner });
6857
+ const command = picked.task ? (picked.command || taskCommand(picked.task, owner)) : taskNextCommand(actionable, owner);
6858
+ const task = picked.task || null;
6859
+
6860
+ if (!task && openDecisions.length) {
6861
+ if (wantsJson(args)) {
6862
+ printJson({
6863
+ ok: false,
6864
+ action: 'refused',
6865
+ reason: DECISION_REFUSE_REASON,
6866
+ skipped_decision_count: openDecisions.length,
6867
+ owner: String(owner),
6868
+ scope: normalizeTaskQueueScope(scope),
6869
+ projection_path: outPath,
6870
+ });
6871
+ process.exit(1);
6872
+ }
6873
+ console.error(DECISION_REFUSE_REASON);
6874
+ process.exit(1);
6875
+ }
6876
+
6877
+ if (wantsJson(args)) {
6878
+ printJson({
6879
+ ok: true,
6880
+ action: nextActionFromCommand(command),
6881
+ command,
6882
+ task_id: task ? task.id : null,
6883
+ owner: String(owner),
6884
+ scope: normalizeTaskQueueScope(scope),
6885
+ projection_path: outPath,
6886
+ task: task ? compactTaskFromProjection(projection, task.id) : null,
6887
+ });
6888
+ return;
6889
+ }
6890
+
6891
+ if (!task) {
6892
+ console.log(noOpenTasksMessage(scope));
6893
+ console.log(`next: ${command}`);
6894
+ return;
6895
+ }
6896
+ console.log(`next: ${command}`);
6897
+ }
6898
+
6899
+ function isUninitializedTaskFolder(cwd = process.cwd()) {
6900
+ const workspace = getTaskDb().workspaceRoot(cwd);
6901
+ if (fs.existsSync(path.join(workspace, 'atris'))) return false;
6902
+ const existing = readProjectionFile(workspace);
6903
+ return !(existing && Array.isArray(existing.tasks) && existing.tasks.length > 0);
6904
+ }
6905
+
6774
6906
  function cmdNext(args) {
6907
+ const root = process.cwd();
6908
+ // Empty folder talks like bare atris. Walk up like the task db so a
6909
+ // project subdir stays in the room; stop before inheriting /tmp.
6910
+ // A leftover projection with live tasks still names that work.
6911
+ if (isUninitializedTaskFolder(root)) {
6912
+ if (wantsJson(args)) {
6913
+ printJson({
6914
+ ok: true,
6915
+ action: 'init',
6916
+ command: 'atris init --minimal',
6917
+ task_id: null,
6918
+ owner: String(flag(args, '--as') || personName() || DEFAULT_OWNER),
6919
+ scope: normalizeTaskQueueScope(taskQueueScopeFromArgs(args)),
6920
+ projection_path: null,
6921
+ task: null,
6922
+ });
6923
+ return;
6924
+ }
6925
+ const screen = buildFirstMinute({ root, fresh: true });
6926
+ console.log(screen.text);
6927
+ return;
6928
+ }
6929
+ if (!hasFlag(args, '--create-next')) return cmdNextTruth(args);
6775
6930
  const owner = flag(args, '--as') || DEFAULT_OWNER;
6776
6931
  const scope = taskQueueScopeFromArgs(args);
6777
6932
  const scoped = !taskQueueScopeIsEmpty(scope);
@@ -8637,7 +8792,7 @@ function taskStepError(reason, detail, { status = 409, exitCode = 1, page = null
8637
8792
  }
8638
8793
 
8639
8794
  function taskStepStatusForReason(reason) {
8640
- if (['goal_required', 'exit_required', 'proof_needed_required', 'first_move_required', 'proof_required', 'weak_proof', 'invalid_reward'].includes(reason)) {
8795
+ if (['goal_required', 'exit_required', 'proof_needed_required', 'first_move_required', 'proof_required', 'weak_proof', 'proof_command_not_run', 'invalid_reward'].includes(reason)) {
8641
8796
  return 400;
8642
8797
  }
8643
8798
  if (reason === 'not_found') return 404;
@@ -8848,7 +9003,11 @@ function runTaskStep(taskDb, db, taskId, options = {}) {
8848
9003
  const proof = String(options.proof || '').trim();
8849
9004
  const proofIssue = meaningfulTaskProofIssue(proof);
8850
9005
  if (proofIssue) {
8851
- throw taskStepError(proof ? 'weak_proof' : 'proof_required', `meaningful proof required: ${proofIssue}`, { status: 400, exitCode: 2, page: actionPage });
9006
+ throw taskStepError(proof ? 'weak_proof' : 'proof_required', weakProofDetail(proofIssue), { status: 400, exitCode: 2, page: actionPage });
9007
+ }
9008
+ const unrunIssue = unrunNamedProofCommandIssue(proof, '');
9009
+ if (unrunIssue) {
9010
+ throw taskStepError(unrunIssue.reason, unrunIssue.detail, { status: 400, exitCode: 2, page: actionPage });
8852
9011
  }
8853
9012
  const missionXpIssue = missionXpEndToEndProofIssue(task, proof, task.workspace_root || process.cwd());
8854
9013
  if (missionXpIssue) {
@@ -8895,7 +9054,11 @@ function runTaskStep(taskDb, db, taskId, options = {}) {
8895
9054
  const proof = String(options.proof || '').trim();
8896
9055
  const proofIssue = meaningfulTaskProofIssue(proof);
8897
9056
  if (proofIssue) {
8898
- throw taskStepError(proof ? 'weak_proof' : 'proof_required', `meaningful proof required: ${proofIssue}`, { status: 400, exitCode: 2, page: actionPage });
9057
+ throw taskStepError(proof ? 'weak_proof' : 'proof_required', weakProofDetail(proofIssue), { status: 400, exitCode: 2, page: actionPage });
9058
+ }
9059
+ const unrunIssue = unrunNamedProofCommandIssue(proof, '');
9060
+ if (unrunIssue) {
9061
+ throw taskStepError(unrunIssue.reason, unrunIssue.detail, { status: 400, exitCode: 2, page: actionPage });
8899
9062
  }
8900
9063
  const parsedReward = parseStepReviewReward(options.reward);
8901
9064
  if (!parsedReward.ok) {
@@ -9127,24 +9290,51 @@ function cmdCurrentStep(args) {
9127
9290
  } catch (error) {
9128
9291
  const errorCurrent = error.current || null;
9129
9292
  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
- });
9293
+ const selectedRef = errorCurrent ? errorCurrent.selected_ref : null;
9294
+ const nextCommand = error.page?.stage?.next_action?.command
9295
+ || (errorCurrent && errorCurrent.next && errorCurrent.next.command)
9296
+ || null;
9297
+ printCliJson(
9298
+ {
9299
+ ok: false,
9300
+ action: 'current_step',
9301
+ reason: error.reason || 'step_failed',
9302
+ detail: error.message,
9303
+ selected_task_id: errorCurrent ? errorCurrent.selected_task_id : null,
9304
+ selected_ref: selectedRef,
9305
+ selected_next_key: selectedNextKeyFromCurrent(errorCurrent),
9306
+ next_command: nextCommand,
9307
+ current: errorCurrent,
9308
+ page: error.page || null,
9309
+ },
9310
+ compactErrorPayload({
9311
+ reason: error.reason || 'step_failed',
9312
+ detail: error.message,
9313
+ selected_ref: selectedRef,
9314
+ next_command: nextCommand,
9315
+ }),
9316
+ args,
9317
+ );
9141
9318
  } else {
9142
9319
  console.error(error.message || 'atris task current-step failed');
9143
9320
  }
9144
9321
  process.exit(error.exitCode || 1);
9145
9322
  }
9146
9323
  if (wantsJson(args)) {
9147
- printJson(result);
9324
+ const nextCommand = result.page?.stage?.next_action?.command || null;
9325
+ printCliJson(
9326
+ result,
9327
+ compactSuccessPayload({
9328
+ action: 'current_step',
9329
+ ids: {
9330
+ selected_task_id: result.selected_task_id || null,
9331
+ selected_ref: result.selected_ref || null,
9332
+ step_action: result.step && result.step.step_action ? result.step.step_action : null,
9333
+ },
9334
+ next_command: nextCommand,
9335
+ }),
9336
+ args,
9337
+ );
9148
9338
  return;
9149
9339
  }
9150
9340
  console.log(`current-step ${taskRef(result.task)} -> ${result.step.step_action}`);
@@ -9610,14 +9800,38 @@ function cmdReady(args) {
9610
9800
  console.error('atris task ready: id required');
9611
9801
  process.exit(2);
9612
9802
  }
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.
9803
+ // Two ways to prove: --proof "<note>" (claimed, pattern-checked) or
9804
+ // --verify "<command>" which actually RUNS the command and gates ready on exit 0.
9805
+ // If --proof names npm test, node --test, or git diff --check, this process
9806
+ // must have run that command. A sentence that names one of those is a lie.
9616
9807
  const proofFlag = flag(args, '--proof');
9617
9808
  const verifyFlag = flag(args, '--verify');
9618
- const resultSentence = requireResultSentence('atris task ready', textFlag(args, ['--result']), { ready: true });
9809
+ const proofUrl = textFlag(args, ['--proof-url']);
9810
+ const iFetched = hasFlag(args, '--i-fetched');
9811
+ if (proofUrl && !iFetched) {
9812
+ failTask(
9813
+ 'atris task ready',
9814
+ 'unfetched_proof_url',
9815
+ 'CI run URLs only count after atris fetches them, or with --proof-url and --i-fetched',
9816
+ );
9817
+ }
9818
+ if (iFetched && !proofUrl && !(typeof proofFlag === 'string' && /https?:\/\/github[.]com\//i.test(proofFlag))) {
9819
+ failTask(
9820
+ 'atris task ready',
9821
+ 'proof_url_required',
9822
+ '--i-fetched requires --proof-url <actions-run-url> (or a URL inside --proof)',
9823
+ );
9824
+ }
9619
9825
  const usedVerify = typeof verifyFlag === 'string' ? verifyFlag.trim() : '';
9826
+ const resultSentence = requireResultSentence('atris task ready', textFlag(args, ['--result']), {
9827
+ ready: true,
9828
+ allowCommandMention: Boolean(usedVerify),
9829
+ });
9620
9830
  let proof = typeof proofFlag === 'string' ? proofFlag : '';
9831
+ if (proofUrl && iFetched) {
9832
+ const attested = `[i-fetched] ${proofUrl}`;
9833
+ proof = proof.trim() ? `${proof.trim()} ${attested}` : attested;
9834
+ }
9621
9835
  const verifyAutoCertifyAllowed = !usedVerify || isAutoCertifyVerifyCommandAllowed(verifyFlag);
9622
9836
  if (usedVerify) {
9623
9837
  // Run the verifier once and write a receipt (pass or fail) so the review
@@ -9638,6 +9852,15 @@ function cmdReady(args) {
9638
9852
  // this codebase fails there, and one that passes anywhere passes there too.
9639
9853
  if (!hasFlag(args, '--no-falsify-check')) {
9640
9854
  const { probeVerifierCanFail } = require('../lib/falsifier-probe');
9855
+ const { classifyVerifier } = require('../lib/verifier-quality');
9856
+ const quality = classifyVerifier(verifyFlag);
9857
+ if (!quality.ok) {
9858
+ console.error(`atris task ready: weak verifier — ${quality.reason}`);
9859
+ console.error(` command: ${verifyFlag}`);
9860
+ console.error(' allowlisted examples: node --test, npm test, git diff --check, node --check, rg <symbol>');
9861
+ console.error(' or pass --no-falsify-check to accept anyway.');
9862
+ process.exit(1);
9863
+ }
9641
9864
  const probe = probeVerifierCanFail({ command: verifyFlag });
9642
9865
  if (probe.probed && probe.canFail === false) {
9643
9866
  console.error(`atris task ready: this check cannot fail — ${probe.reason}`);
@@ -9656,6 +9879,7 @@ function cmdReady(args) {
9656
9879
  process.exit(2);
9657
9880
  }
9658
9881
  requireMeaningfulTaskProof('atris task ready', proof);
9882
+ requireRanNamedProofCommand('atris task ready', proof, usedVerify);
9659
9883
  const lesson = flag(args, '--lesson') || '';
9660
9884
  const nextTaskInput = normalizeReviewNextTaskInput(typeof flag(args, '--next') === 'string' ? flag(args, '--next') : '');
9661
9885
  const landing = landingFlags(args);
@@ -9760,7 +9984,12 @@ function cmdReady(args) {
9760
9984
  if (policyHints.length) handoff.policy_hints = policyHints;
9761
9985
  if (readyPolicyGate.gate.advisories.length) handoff.policy_gate = readyPolicyGate.gate;
9762
9986
  if (wantsJson(args)) {
9763
- printJson({
9987
+ const taskCard = compactTaskFromProjection(projection, taskId);
9988
+ const nextCommand = handoff.review_chat_command
9989
+ || (agentCertified
9990
+ ? `atris task accept ${taskRef(taskCard || result.row || taskId)}`
9991
+ : `atris task reviews`);
9992
+ const fullReady = {
9764
9993
  ok: true,
9765
9994
  action: 'ready',
9766
9995
  task_id: taskId,
@@ -9773,8 +10002,23 @@ function cmdReady(args) {
9773
10002
  landing_advisory: landingAdvisory,
9774
10003
  ...(nextTaskInput.ignored ? { review_next_task_ignored: nextTaskInput.ignored } : {}),
9775
10004
  projection_path: outPath,
9776
- task: compactTaskFromProjection(projection, taskId),
9777
- });
10005
+ task: taskCard,
10006
+ next_command: nextCommand,
10007
+ };
10008
+ printCliJson(
10009
+ fullReady,
10010
+ compactSuccessPayload({
10011
+ action: 'ready',
10012
+ ids: {
10013
+ task_id: taskId,
10014
+ selected_ref: taskRef(taskCard || result.row || taskId),
10015
+ version: result.event.version,
10016
+ agent_certified: agentCertified,
10017
+ },
10018
+ next_command: nextCommand,
10019
+ }),
10020
+ args,
10021
+ );
9778
10022
  return;
9779
10023
  }
9780
10024
  console.log(`ready for approval ${taskRef(compactTaskFromProjection(projection, taskId))} v${result.event.version}`);
@@ -11043,15 +11287,23 @@ function importTodoFile(taskDb, db, target) {
11043
11287
  const parsed = parseTodoFile(filePath);
11044
11288
  const ws = taskDb.workspaceRoot();
11045
11289
  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' })),
11290
+ ...parsed.backlog.map(t => ({ ...t, importStatus: 'open', sourceFile: filePath })),
11291
+ ...parsed.inProgress.map(t => ({ ...t, importStatus: 'claimed', sourceFile: filePath })),
11292
+ ...(parsed.review || []).map(t => ({ ...t, importStatus: 'review', sourceFile: filePath })),
11049
11293
  ];
11294
+ const knownTitles = new Set(
11295
+ taskDb.listTasks(db, { workspaceRoot: ws, limit: 500 }).map((row) => taskDb.normalizeTitle(row.title))
11296
+ );
11050
11297
  let inserted = 0;
11051
11298
  let skipped = 0;
11052
11299
  for (const t of all) {
11053
11300
  if (!t.title) continue;
11054
- const sk = taskDb.sourceKey(filePath, t.title);
11301
+ const normalized = taskDb.normalizeTitle(t.title);
11302
+ if (knownTitles.has(normalized)) {
11303
+ skipped++;
11304
+ continue;
11305
+ }
11306
+ const sk = taskDb.sourceKey(t.sourceFile || filePath, t.title);
11055
11307
  const result = taskDb.addTask(db, {
11056
11308
  title: t.title,
11057
11309
  tag: t.tag || null,
@@ -11061,11 +11313,72 @@ function importTodoFile(taskDb, db, target) {
11061
11313
  claimedBy: t.claimed || null,
11062
11314
  metadata: { todo_id: t.id, todo_tags: t.tags || [], claimed: t.claimed, stage: t.stage, verify: t.verify },
11063
11315
  });
11064
- if (result.inserted) inserted++; else skipped++;
11316
+ if (result.inserted) {
11317
+ inserted++;
11318
+ knownTitles.add(normalized);
11319
+ } else {
11320
+ skipped++;
11321
+ }
11322
+ }
11323
+ return { ok: true, inserted, skipped, filePath, knownTitles };
11324
+ }
11325
+
11326
+ function importJournalFile(taskDb, db, journalPath, knownTitles = null) {
11327
+ const filePath = path.resolve(journalPath);
11328
+ if (!fs.existsSync(filePath)) {
11329
+ return { ok: false, reason: 'not_found', filePath, inserted: 0, skipped: 0 };
11330
+ }
11331
+ const { parseSection } = require('../lib/todo-fallback');
11332
+ const { parseInboxItems } = require('../lib/file-ops');
11333
+ const content = fs.readFileSync(filePath, 'utf8');
11334
+ const ws = taskDb.workspaceRoot();
11335
+ const titles = knownTitles || new Set(
11336
+ taskDb.listTasks(db, { workspaceRoot: ws, limit: 500 }).map((row) => taskDb.normalizeTitle(row.title))
11337
+ );
11338
+ const all = [
11339
+ ...parseSection(content, 'Backlog').map((t) => ({ ...t, importStatus: 'open' })),
11340
+ ...parseSection(content, 'In Progress').map((t) => ({ ...t, importStatus: 'claimed' })),
11341
+ ...parseInboxItems(content).map((item) => ({
11342
+ id: `I${item.id}`,
11343
+ title: item.text,
11344
+ tag: 'inbox',
11345
+ importStatus: 'open',
11346
+ })),
11347
+ ];
11348
+ let inserted = 0;
11349
+ let skipped = 0;
11350
+ for (const t of all) {
11351
+ if (!t.title) continue;
11352
+ const normalized = taskDb.normalizeTitle(t.title);
11353
+ if (titles.has(normalized)) {
11354
+ skipped++;
11355
+ continue;
11356
+ }
11357
+ const sk = taskDb.sourceKey(filePath, t.title);
11358
+ const result = taskDb.addTask(db, {
11359
+ title: t.title,
11360
+ tag: t.tag || null,
11361
+ workspaceRoot: ws,
11362
+ sourceKey: sk,
11363
+ status: t.importStatus,
11364
+ claimedBy: t.claimed || null,
11365
+ metadata: { todo_id: t.id, source: 'journal_import' },
11366
+ });
11367
+ if (result.inserted) {
11368
+ inserted++;
11369
+ titles.add(normalized);
11370
+ } else {
11371
+ skipped++;
11372
+ }
11065
11373
  }
11066
11374
  return { ok: true, inserted, skipped, filePath };
11067
11375
  }
11068
11376
 
11377
+ function todayJournalPath() {
11378
+ const { getLogPath } = require('../lib/file-ops');
11379
+ return getLogPath().logFile;
11380
+ }
11381
+
11069
11382
  function cmdImport(args) {
11070
11383
  const pos = positional(args);
11071
11384
  const target = pos[0] || 'atris/TODO.md';
@@ -11076,7 +11389,9 @@ function cmdImport(args) {
11076
11389
  console.error(`atris task import: file not found: ${result.filePath}`);
11077
11390
  process.exit(2);
11078
11391
  }
11079
- const { inserted, skipped, filePath } = result;
11392
+ const journalResult = importJournalFile(taskDb, db, todayJournalPath(), result.knownTitles);
11393
+ const inserted = result.inserted + (journalResult.ok ? journalResult.inserted : 0);
11394
+ const skipped = result.skipped + (journalResult.ok ? journalResult.skipped : 0);
11080
11395
  const { outPath } = writeDefaultProjection(taskDb, db);
11081
11396
  if (wantsJson(args)) {
11082
11397
  printJson({
@@ -11084,12 +11399,16 @@ function cmdImport(args) {
11084
11399
  action: 'imported',
11085
11400
  inserted,
11086
11401
  skipped,
11087
- source: filePath,
11402
+ source: result.filePath,
11403
+ journal: journalResult.ok ? journalResult.filePath : null,
11088
11404
  projection_path: outPath,
11089
11405
  });
11090
11406
  return;
11091
11407
  }
11092
- console.log(`imported ${inserted} new, skipped ${skipped} (already imported), source=${filePath}`);
11408
+ console.log(`imported ${inserted} new, skipped ${skipped} (already imported), source=${result.filePath}`);
11409
+ if (journalResult.ok && journalResult.inserted > 0) {
11410
+ console.log(`journal imported ${journalResult.inserted} from ${journalResult.filePath}`);
11411
+ }
11093
11412
  }
11094
11413
 
11095
11414
  function cmdWhere(args) {
@@ -11275,12 +11594,22 @@ function cmdSetup(args) {
11275
11594
  const db = taskDb.open();
11276
11595
  const ws = taskDb.workspaceRoot();
11277
11596
  let importResult = null;
11597
+ let journalResult = null;
11278
11598
  if (hasFlag(args, '--import-todo')) {
11279
11599
  importResult = importTodoFile(taskDb, db, flag(args, '--todo') || 'atris/TODO.md');
11280
11600
  if (!importResult.ok && flag(args, '--todo')) {
11281
11601
  console.error(`atris task setup: TODO file not found: ${importResult.filePath}`);
11282
11602
  process.exit(2);
11283
11603
  }
11604
+ if (importResult.ok) {
11605
+ journalResult = importJournalFile(taskDb, db, todayJournalPath(), importResult.knownTitles);
11606
+ importResult = {
11607
+ ...importResult,
11608
+ inserted: importResult.inserted + (journalResult.ok ? journalResult.inserted : 0),
11609
+ skipped: importResult.skipped + (journalResult.ok ? journalResult.skipped : 0),
11610
+ journal: journalResult.ok ? journalResult.filePath : null,
11611
+ };
11612
+ }
11284
11613
  }
11285
11614
  const { projection, outPath } = writeDefaultProjection(taskDb, db);
11286
11615
  if (wantsJson(args)) {
@@ -11293,6 +11622,7 @@ function cmdSetup(args) {
11293
11622
  inserted: importResult.inserted,
11294
11623
  skipped: importResult.skipped,
11295
11624
  source: importResult.filePath,
11625
+ journal: importResult.journal || null,
11296
11626
  } : null,
11297
11627
  projection,
11298
11628
  });
@@ -12572,6 +12902,14 @@ function postTaskApiReady({ res, taskDb, db, taskId, body }) {
12572
12902
  const proof = String(body.proof || '').trim();
12573
12903
  const proofIssue = meaningfulTaskProofIssue(proof);
12574
12904
  if (proofIssue) return sendProofIssue(res, proof, proofIssue);
12905
+ const unrunIssue = unrunNamedProofCommandIssue(proof, '');
12906
+ if (unrunIssue) {
12907
+ return sendJson(res, 400, {
12908
+ ok: false,
12909
+ reason: unrunIssue.reason,
12910
+ detail: unrunIssue.detail,
12911
+ });
12912
+ }
12575
12913
  const nextTaskInput = normalizeReviewNextTaskInput(body.next);
12576
12914
  const actor = String(body.actor || DEFAULT_OWNER);
12577
12915
  const resultText = String(body.result || '').replace(/\s+/g, ' ').trim();
@@ -12817,8 +13155,9 @@ async function runTaskCommand(args) {
12817
13155
  const raw = args || [];
12818
13156
  if (raw.includes('--help') || raw.includes('-h')) return help();
12819
13157
  const first = raw[0];
12820
- const sub = !first || first.startsWith('--') ? 'desk' : first;
12821
- const rest = !first || first.startsWith('--') ? raw : raw.slice(1);
13158
+ if (!first) return cmdFirstMinute();
13159
+ const sub = first.startsWith('--') ? 'desk' : first;
13160
+ const rest = first.startsWith('--') ? raw : raw.slice(1);
12822
13161
  switch (sub) {
12823
13162
  case 'desk': return cmdHome(rest);
12824
13163
  case 'today': return cmdDay(rest);
@@ -12971,6 +13310,42 @@ const MUTATING_TASK_COMMANDS = new Set([
12971
13310
  async function run(args) {
12972
13311
  const raw = args || [];
12973
13312
  const sub = !raw[0] || raw[0].startsWith('--') ? 'desk' : raw[0];
13313
+ // `task accept --help` is a help request, not a run. Accept mutates
13314
+ // review rows, so never list or accept just to show usage.
13315
+ if (sub === 'accept' && argsWantHelp(raw.slice(1))) {
13316
+ console.log('Usage: atris task accept <id> [--proof "..."] [--public]');
13317
+ return;
13318
+ }
13319
+ // `task claim --help` is a help request, not a run. Claim mutates
13320
+ // ownership, so never list or claim just to show usage.
13321
+ if (sub === 'claim' && argsWantHelp(raw.slice(1))) {
13322
+ console.log('Usage: atris task claim <id> --as <member>');
13323
+ return;
13324
+ }
13325
+ // `task ready --help` is a help request, not a run. Ready mutates
13326
+ // review state, so never list or ready just to show usage.
13327
+ if (sub === 'ready' && argsWantHelp(raw.slice(1))) {
13328
+ console.log('Usage: atris task ready <id> --proof "..." --result "<sentence>"');
13329
+ return;
13330
+ }
13331
+ // `task step --help` is a help request, not a run. Step mutates
13332
+ // Plan/Do/Review, so never list or step just to show usage.
13333
+ if (sub === 'step' && argsWantHelp(raw.slice(1))) {
13334
+ console.log('Usage: atris task step <id> [--json]');
13335
+ return;
13336
+ }
13337
+ // `task next --help` is a help request, not a run. Next can open
13338
+ // the task db or print the desk, so never list or claim just to show usage.
13339
+ if (sub === 'next' && argsWantHelp(raw.slice(1))) {
13340
+ console.log('Usage: atris task next [--tag <tag>] [--create-next]');
13341
+ return;
13342
+ }
13343
+ // `task show --help` is a help request, not a run. Show can dump
13344
+ // a task card or the encyclopedia, so never list or show just to show usage.
13345
+ if (sub === 'show' && argsWantHelp(raw.slice(1))) {
13346
+ console.log('Usage: atris task show <id> [--json]');
13347
+ return;
13348
+ }
12974
13349
  const result = await runTaskCommand(raw);
12975
13350
  const skipsRender = sub === 'clear-done' && hasFlag(raw, '--dry-run');
12976
13351
  if (MUTATING_TASK_COMMANDS.has(sub) && !skipsRender) autoRenderTodoFromDb();