atris 3.74.19 → 3.74.21

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.
package/commands/task.js CHANGED
@@ -32,7 +32,7 @@ const {
32
32
  runVerifyCommand,
33
33
  DENIED_TAGS,
34
34
  } = require('../lib/auto-accept-certified');
35
- const { evaluateAcceptVerify } = require('../lib/accept-verify-gate');
35
+ const { evaluateAcceptVerify, nonFalsifyingIssue } = require('../lib/accept-verify-gate');
36
36
  const { checkedCodeSha, runVerifyAtJobCode } = require('../lib/job-code-verify');
37
37
  const { proveTaskLanding } = require('../lib/landing-proof');
38
38
  const {
@@ -10089,7 +10089,7 @@ function certifyVerifyCandidate(task) {
10089
10089
  // The evidence extractor can keep trailing prose ("git diff --check. Evidence
10090
10090
  // inspected: ..."); try the full clause, then the first sentence of it.
10091
10091
  for (const candidate of [raw, raw.split(/\.(?:\s|$)/)[0]].map((c) => String(c || '').trim())) {
10092
- if (candidate && isAutoCertifyVerifyCommandAllowed(candidate) && !verifyIsOnlyFileChecks(candidate)) return candidate;
10092
+ if (candidate && !nonFalsifyingIssue(candidate) && isAutoCertifyVerifyCommandAllowed(candidate) && !verifyIsOnlyFileChecks(candidate)) return candidate;
10093
10093
  }
10094
10094
  }
10095
10095
  return null;
@@ -10702,6 +10702,10 @@ function evaluateSweepAutoAccept(task, root) {
10702
10702
  if (denied) return { eligible: false, ref, reason: denied };
10703
10703
  const proof = autoAcceptSweepLatestProof(task);
10704
10704
  if (!proof) return { eligible: false, ref, reason: 'no_proof' };
10705
+ const storedVerify = typeof metadata.verify === 'string' ? metadata.verify.trim() : '';
10706
+ if (storedVerify && nonFalsifyingIssue(storedVerify)) {
10707
+ return { eligible: false, ref, reason: 'non_falsifying_verify', verify: storedVerify };
10708
+ }
10705
10709
  const policyGate = candidatePolicyGate(task, { executeDetectors: true });
10706
10710
  if (!policyGate.ok) return { ...policyGate, ref };
10707
10711
 
@@ -10712,7 +10716,6 @@ function evaluateSweepAutoAccept(task, root) {
10712
10716
  // executed) in the proof, but the older receipt-path check below could not
10713
10717
  // find file evidence for it once `ready --verify` stopped writing a
10714
10718
  // receipt file and started embedding the executed result into proof text.
10715
- const storedVerify = typeof metadata.verify === 'string' ? metadata.verify.trim() : '';
10716
10719
  if (storedVerify) {
10717
10720
  const result = runVerifyCommand(storedVerify, root);
10718
10721
  if (!result.ok) return { eligible: false, ref, reason: result.reason, verify: storedVerify };
@@ -10770,6 +10773,12 @@ function evaluateSweepAutoAccept(task, root) {
10770
10773
  }
10771
10774
  return { eligible: false, ref, reason: result.reason, verify: derived };
10772
10775
  }
10776
+ // Certification filters these out, but the sweep should explain why a
10777
+ // legacy proof with only whitespace evidence cannot approve the work.
10778
+ const proofCommands = taskReviewEvidenceCommands(proof);
10779
+ if (proofCommands.length && proofCommands.every(command => nonFalsifyingIssue(command))) {
10780
+ return { eligible: false, ref, reason: 'non_falsifying_verify' };
10781
+ }
10773
10782
  }
10774
10783
 
10775
10784
  return { eligible: false, ref, reason: verifier.reason, evidence: verifier.evidence };
@@ -10783,6 +10792,14 @@ function cmdSweep(args) {
10783
10792
  'atris task sweep currently requires --auto-accept for the explicit verified accept policy',
10784
10793
  );
10785
10794
  }
10795
+ // Sweep has no dry-run implementation: every successful invocation can
10796
+ // write accepts, even when an unrecognized --dry-run flag is supplied.
10797
+ if (agentProofOnlyMode()) {
10798
+ failAgentProofOnly(
10799
+ 'atris task sweep',
10800
+ 'agents cannot accept tasks or award xp. leave proof in review for a human to accept or revise.',
10801
+ );
10802
+ }
10786
10803
  const actor = String(flag(args, '--as') || 'orb-autoaccept');
10787
10804
  const parsedReward = parseAcceptReward(flag(args, '--reward'));
10788
10805
  if (!parsedReward.ok) {
@@ -10,6 +10,7 @@ const { computeTrustTier, taskTrustSubject } = require('./trust-tiers');
10
10
  const { rewriteWorktreeVerify, missingOnMain } = require('./verify-worktree-rewrite');
11
11
  const { policyLessonsForFiles, readPolicyLessons } = require('./policy-lessons');
12
12
  const { skillEvalGate } = require('./skill-eval-gate');
13
+ const escapeRegExp = require('./escape-regexp');
13
14
 
14
15
  const AGENT_CERTIFICATION_REVIEW_PASSES = 2;
15
16
 
@@ -1074,9 +1075,10 @@ function declaredProtectedLane(task) {
1074
1075
  // work and would hold the whole queue.
1075
1076
  const PROTECTED_LANE_TERMS = [
1076
1077
  ['billing', ['stripe', 'checkout url', 'checkout_url', 'invoice', 'paywall', 'subscription', 'wallet', 'credit card', 'pricing tier']],
1078
+ ['money', ['money', 'refund', 'reimburs', 'expense', 'payment', 'cash', 'bank', 'venmo', 'zelle', 'tax']],
1077
1079
  ['security', ['session minting', 'mints a', 'service token', 'api key', 'credential', 'secret key', 'csrf', 'access token']],
1078
1080
  ['deploy', ['render deploy', 'production deploy', 'deploy to prod', 'migration apply']],
1079
- ['customer', ['send email to', 'customer email', 'outbound email', 'notify users']],
1081
+ ['customer', ['send email to', 'customer email', 'outbound email', 'notify users', 'imessage', 'text message', 'send a text', 'reply to', 'outreach', 'cold email', 'send to', 'dm ']],
1080
1082
  ];
1081
1083
 
1082
1084
  function sniffedProtectedLane(task) {
@@ -1086,7 +1088,7 @@ function sniffedProtectedLane(task) {
1086
1088
  .toLowerCase();
1087
1089
  if (!haystack.trim()) return null;
1088
1090
  for (const [lane, terms] of PROTECTED_LANE_TERMS) {
1089
- const term = terms.find((t) => haystack.includes(t));
1091
+ const term = terms.find((t) => new RegExp(`\\b${escapeRegExp(t)}`).test(haystack));
1090
1092
  if (term) return { lane, term };
1091
1093
  }
1092
1094
  return null;
@@ -1216,6 +1218,12 @@ function evaluateAutoAccept(task, options = {}) {
1216
1218
  };
1217
1219
  }
1218
1220
 
1221
+ // Load lazily: the human accept gate reuses this module's parser and runner.
1222
+ const verifyCommand = resolveTaskVerify(task).verify;
1223
+ if (verifyCommand && require('./accept-verify-gate').nonFalsifyingIssue(verifyCommand)) {
1224
+ return { eligible: false, ref, reason: 'non_falsifying_verify', verify: verifyCommand };
1225
+ }
1226
+
1219
1227
  // accept-all: the protected lanes above are the only human gate. No
1220
1228
  // certification, pass-count, reviewer, or proof-quality bar, but work
1221
1229
  // is never marked done against evidence it isn't: a proof naming an
@@ -155,6 +155,8 @@ function createStarterTask(root, answer) {
155
155
  title,
156
156
  status: task && task.status || 'claimed',
157
157
  claimed_by: (task && task.claimed_by) || who,
158
+ workspace_root: workspaceRoot,
159
+ metadata: task && task.metadata,
158
160
  };
159
161
  } catch (error) {
160
162
  return {
@@ -6,10 +6,12 @@ const path = require('path');
6
6
  const { stateHome } = require('./state-home');
7
7
  const { spawnSync } = require('child_process');
8
8
  const { isGenericScratchRoot } = require('./scratch-root');
9
+ const { resolveDefaultVerifier } = require('./default-verifier');
10
+ const { nonFalsifyingIssue } = require('./accept-verify-gate');
9
11
 
10
12
  const FIRST_USE_COMMAND = 'atris "help me choose the first useful step for this project"';
11
13
  const FIRST_TALK_ASK = 'what do you want here';
12
- const CLAIMED_KEEP_WORKING_VERIFY = 'git diff --check';
14
+ const ADD_TEST_COMMAND = 'atris "add a test for the claimed task"';
13
15
  const LATER_NOTE_FILE = '.atris-later';
14
16
  const BARE_ATRIS_FLAGS = new Set(['--yes', '-y', '--json', '--verbose']);
15
17
  const NOT_A_PERSON = new Set([
@@ -237,13 +239,17 @@ function loadLatestRecap(root = process.cwd()) {
237
239
 
238
240
  function claimedKeepWorkingCommand(task) {
239
241
  const ref = taskRef(task);
242
+ const recorded = task && task.metadata && task.metadata.verify;
243
+ const available = recorded || (task && task.workspace_root && resolveDefaultVerifier(task.workspace_root));
244
+ if (!available || nonFalsifyingIssue(available)) return ADD_TEST_COMMAND;
240
245
  return ref
241
- ? `atris task ready ${ref} --verify "${CLAIMED_KEEP_WORKING_VERIFY}"`
242
- : `atris task ready --verify "${CLAIMED_KEEP_WORKING_VERIFY}"`;
246
+ ? `atris task ready ${ref} --verify "${available}"`
247
+ : `atris task ready --verify "${available}"`;
243
248
  }
244
249
 
245
250
  function isClaimedKeepWorkingCommand(command) {
246
- return /^atris task ready\b/.test(String(command || ''));
251
+ return /^atris task ready\b/.test(String(command || ''))
252
+ || command === ADD_TEST_COMMAND;
247
253
  }
248
254
 
249
255
  function taskCommand(task, person) {
@@ -709,7 +715,7 @@ function firstTalkNext({ starter = null, person = '', folder = 'this folder' } =
709
715
  const room = folder || 'this folder';
710
716
  if (starter && starter.display_id) {
711
717
  return taskCommand({
712
- display_id: starter.display_id,
718
+ ...starter,
713
719
  status: starter.status || 'claimed',
714
720
  }, person);
715
721
  }
@@ -1621,9 +1621,9 @@ body.waiting .bet { position: absolute; left: 24px; right: 24px; width: auto; to
1621
1621
  orb.melt(then, to, swap, fast);
1622
1622
  }
1623
1623
 
1624
- // the card has faded all the way: its opacity fade ended, or ms passed with
1625
- // a frame to spare when none ran. A timer alone cut the last sixth of a fade
1626
- // that started a frame late on a slow phone, and the card vanished at 0.17.
1624
+ // a fade has run all the way: the node's opacity fade ended, or ms passed
1625
+ // with a frame to spare when none ran. A timer alone cut the last sixth of a
1626
+ // fade that started a frame late on a slow phone, and it vanished at 0.17.
1627
1627
  function afterFade(node, ms, cb) {
1628
1628
  var ran = false;
1629
1629
  var finish = function (e) {
@@ -1669,7 +1669,7 @@ body.waiting .bet { position: absolute; left: 24px; right: 24px; width: auto; to
1669
1669
  // reduced motion: no spiral; the card fades in where it goes
1670
1670
  if (reduced) {
1671
1671
  var show = function () { orb.settled(); showCard(card, true); bet.busy = false; if (after) after(); };
1672
- if (fadeSphere()) setTimeout(show, 200); else show();
1672
+ if (fadeSphere()) afterFade(document.getElementById('stage'), 200, show); else show();
1673
1673
  return;
1674
1674
  }
1675
1675
  orb.play(function (phase) {
@@ -1992,7 +1992,7 @@ body.waiting .bet { position: absolute; left: 24px; right: 24px; width: auto; to
1992
1992
  };
1993
1993
  if (reduced) {
1994
1994
  fadeSphere();
1995
- setTimeout(function () { off(); orb.settled(); showCard(card, true); bet.busy = false; if (after) after(); }, 200);
1995
+ afterFade(b, 200, function () { off(); orb.settled(); showCard(card, true); bet.busy = false; if (after) after(); });
1996
1996
  return;
1997
1997
  }
1998
1998
  orb.play(function (phase) {
@@ -2057,7 +2057,7 @@ body.waiting .bet { position: absolute; left: 24px; right: 24px; width: auto; to
2057
2057
  b.style.minHeight = '';
2058
2058
  b.classList.remove('up');
2059
2059
  planned('turn', 400);
2060
- setTimeout(function () { paint(); void b.offsetWidth; b.classList.add('up'); setTimeout(after, 200); }, 200);
2060
+ afterFade(b, 200, function () { paint(); void b.offsetWidth; b.classList.add('up'); afterFade(b, 200, after); });
2061
2061
  return;
2062
2062
  }
2063
2063
  var from = b.offsetHeight;
@@ -2199,7 +2199,7 @@ body.waiting .bet { position: absolute; left: 24px; right: 24px; width: auto; to
2199
2199
  // the card fades up as the lava eases into its spot, or once it is there
2200
2200
  var up = false;
2201
2201
  var play = function () { orb.play(function (phase) { if (!up && (phase === 'landing' || phase === 'settled')) { up = true; showCard(r.card, true, true); } if (phase === 'settled') bet.busy = false; }, true); };
2202
- if (fadeSphere()) setTimeout(play, 200); else play();
2202
+ if (fadeSphere()) afterFade(document.getElementById('stage'), 200, play); else play();
2203
2203
  }, failed);
2204
2204
  }
2205
2205
 
package/lib/voice-gate.js CHANGED
@@ -250,6 +250,7 @@ const LANDING_REASON_SENTENCES = {
250
250
  receipt_verifier_failed: 'a saved receipt failed its re-check, fix what it names and re-certify',
251
251
  slop_gate: 'the touched prose breaks a deterministic taste rule, fix the named lines and re-certify',
252
252
  strict_verify_missing: 'no recorded check command to re-run, add one and re-certify',
253
+ non_falsifying_verify: 'its recorded check cannot prove the work is correct, add a real check and re-certify',
253
254
  untagged_protected_lane_text: 'the description reads like protected-lane work without the tag, tag it or decide it yourself',
254
255
  verification_pending: 'the recorded check has not been re-run yet, the next hourly pass runs it',
255
256
  verifier_is_builder: 'the re-check actor built this row, another actor must re-check',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atris",
3
- "version": "3.74.19",
3
+ "version": "3.74.21",
4
4
  "description": "you say what you want in plain words. atris builds it, checks it, and shows you proof.",
5
5
  "mcpName": "io.github.atrislabs/atris",
6
6
  "main": "bin/atris.js",