atris 3.35.0 → 3.36.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.
- package/AGENTS.md +37 -0
- package/README.md +5 -3
- package/atris/GETTING_STARTED.md +1 -1
- package/atris/atris.md +3 -0
- package/atris/policies/day-loop-voice.md +102 -0
- package/atris/policies/outbound-artifact-gate.md +2 -0
- package/atris/skills/design/SKILL.md +56 -32
- package/atris/skills/endgame/SKILL.md +12 -6
- package/atris/skills/engines/SKILL.md +22 -4
- package/atris/skills/fable-method/SKILL.md +66 -0
- package/atris/skills/improve/SKILL.md +65 -45
- package/atris/skills/youtube/SKILL.md +10 -1
- package/atris.md +2 -0
- package/ax +147 -19
- package/bin/atris.js +565 -265
- package/commands/activate.js +194 -88
- package/commands/agents.js +166 -0
- package/commands/autoland.js +459 -107
- package/commands/autopilot-front.js +20 -2
- package/commands/autopilot.js +118 -2
- package/commands/avail.js +407 -0
- package/commands/bench.js +188 -0
- package/commands/brain.js +3 -0
- package/commands/brief.js +651 -0
- package/commands/business-sync.js +192 -6
- package/commands/clean.js +50 -24
- package/commands/close.js +1083 -0
- package/commands/cloud.js +245 -0
- package/commands/compile.js +292 -1
- package/commands/computer.js +150 -3
- package/commands/dream.js +365 -0
- package/commands/drill.js +371 -0
- package/commands/engine.js +993 -32
- package/commands/experiments.js +28 -0
- package/commands/feedback.js +34 -12
- package/commands/fleet-report.js +206 -0
- package/commands/gm.js +23 -0
- package/commands/goal.js +247 -0
- package/commands/improve.js +642 -26
- package/commands/init.js +72 -44
- package/commands/interview.js +67 -1
- package/commands/land.js +152 -52
- package/commands/lifecycle.js +39 -3
- package/commands/log.js +84 -1
- package/commands/loops.js +220 -16
- package/commands/meet.js +220 -0
- package/commands/member.js +511 -34
- package/commands/mission.js +3029 -339
- package/commands/next.js +137 -0
- package/commands/now.js +220 -25
- package/commands/one-lap.js +776 -0
- package/commands/orb.js +314 -0
- package/commands/pack-craft.js +179 -0
- package/commands/pack.js +823 -0
- package/commands/play.js +3 -2
- package/commands/probe.js +30 -3
- package/commands/pulse.js +241 -46
- package/commands/push.js +260 -82
- package/commands/rainmaker.js +49 -0
- package/commands/report.js +415 -0
- package/commands/scout.js +147 -0
- package/commands/search.js +363 -0
- package/commands/skill.js +47 -3
- package/commands/slop.js +50 -2
- package/commands/soul.js +1 -1
- package/commands/stream.js +861 -0
- package/commands/study.js +693 -0
- package/commands/sync.js +67 -54
- package/commands/task.js +1346 -117
- package/commands/team.js +73 -0
- package/commands/verify.js +96 -0
- package/commands/watch.js +303 -0
- package/commands/wish.js +500 -0
- package/commands/workflow.js +11 -5
- package/commands/worktree.js +234 -13
- package/commands/xp.js +29 -11
- package/lib/auto-accept-certified.js +331 -34
- package/lib/autoland.js +319 -54
- package/lib/ax-auto-lane.js +79 -0
- package/lib/bench/context.js +147 -0
- package/lib/bench/engines.js +141 -0
- package/lib/bench/report.js +140 -0
- package/lib/bench/runner.js +512 -0
- package/lib/brief-ledger.js +350 -0
- package/lib/cloud-mission.js +259 -0
- package/lib/codex-flight.js +154 -0
- package/lib/default-runner.js +45 -0
- package/lib/default-verifier.js +70 -0
- package/lib/engine-registry.js +232 -0
- package/lib/experiments/daily.js +640 -0
- package/lib/fleet.js +2219 -67
- package/lib/improve-vitals-html.js +171 -0
- package/lib/known-commands.js +58 -0
- package/lib/loop-doctor.js +416 -0
- package/lib/member-switches.js +144 -0
- package/lib/mission-room.js +1 -0
- package/lib/mission-root.js +52 -0
- package/lib/next-moves.js +327 -10
- package/lib/one-lap-validator.js +60 -0
- package/lib/orb-context.js +477 -0
- package/lib/orb-scorecard.js +224 -0
- package/lib/policy-lessons.js +52 -1
- package/lib/pulse.js +277 -3
- package/lib/receipt-block.js +168 -0
- package/lib/receipt-evidence.js +65 -4
- package/lib/router-brain.js +352 -0
- package/lib/runner-command.js +10 -0
- package/lib/self-drive.js +258 -0
- package/lib/short-name.js +103 -0
- package/lib/spawn-env.js +18 -0
- package/lib/state-detection.js +56 -1
- package/lib/sync-status.js +59 -0
- package/lib/task-db.js +108 -29
- package/lib/task-proof.js +23 -1
- package/lib/team-presence.js +260 -0
- package/lib/tool-result-encode.js +7 -0
- package/lib/trust-tiers.js +90 -0
- package/lib/usage.js +107 -0
- package/lib/voice-gate.js +163 -0
- package/lib/wish-audit.js +1368 -0
- package/lib/wish-delegate.js +1840 -0
- package/lib/wish-design.js +110 -0
- package/lib/wish-stats.js +183 -0
- package/lib/wish-store.js +354 -0
- package/lib/zip.js +221 -0
- package/package.json +3 -1
- package/templates/loops/atris/loops/LOOPS.md +55 -0
- package/templates/loops/atris/loops/TICK.md +24 -0
- package/templates/loops/atris/loops/feedback.md +22 -0
- package/templates/loops/atris/loops/quality.md +22 -0
- package/templates/loops/atris/wiki/systems/loops.md +41 -0
- package/utils/api.js +5 -1
- package/utils/auth.js +57 -21
package/commands/task.js
CHANGED
|
@@ -14,16 +14,18 @@ const {
|
|
|
14
14
|
isAutoCertifyVerifyCommandAllowed,
|
|
15
15
|
parseVerifyCommand,
|
|
16
16
|
runVerifyCommand,
|
|
17
|
+
runVerifyCommandCached,
|
|
17
18
|
DENIED_TAGS,
|
|
18
19
|
} = require('../lib/auto-accept-certified');
|
|
19
|
-
const { extractReceiptEvidence } = require('../lib/receipt-evidence');
|
|
20
|
+
const { extractReceiptEvidence, RECEIPT_PATH_PATTERN } = require('../lib/receipt-evidence');
|
|
20
21
|
const escapeRegExp = require('../lib/escape-regexp');
|
|
21
22
|
const reviewIntegrity = require('../lib/review-integrity');
|
|
23
|
+
const { gateForHuman, isRetiredFillerReason, landingWhyClause, numberWord } = require('../lib/voice-gate');
|
|
22
24
|
const {
|
|
23
25
|
normalizeOwnerSlug,
|
|
24
26
|
resolveFunctionalOwner: resolveFunctionalTaskOwner,
|
|
25
27
|
} = require('../lib/functional-owner');
|
|
26
|
-
const { operatorReady, hasAgentJargon } = require('./autoland');
|
|
28
|
+
const { operatorReady, hasAgentJargon, explainResult } = require('./autoland');
|
|
27
29
|
const {
|
|
28
30
|
TASK_INSPECT_FIELDS,
|
|
29
31
|
readFieldsFlag,
|
|
@@ -46,6 +48,13 @@ const REVIEW_LANE_RUN_MAX_RUNS = 20;
|
|
|
46
48
|
const PENDING_REVIEW_CHAT_STOP_REASON = 'pending_review_chat_waiting_for_agent_review';
|
|
47
49
|
const PROOF_BOUNDARY_BLOCKED_ACTION = 'proof_boundary_blocked';
|
|
48
50
|
const PROOF_BOUNDARY_BLOCKED_REASON = 'proof_boundary_blocked_requires_revision';
|
|
51
|
+
const MISSION_XP_END_TO_END_REASON = 'mission_xp_requires_end_to_end_receipt';
|
|
52
|
+
const MISSION_XP_END_TO_END_DETAIL = 'mission XP proof must name a zero-papercut end-to-end fresh-laptop pass through install, init, first mission, and first self-landed task; generic mission/tick receipts are not enough';
|
|
53
|
+
const READY_RESULT_TEACHING = 'ready needs --result: one plain sentence someone new to the project can understand. say what someone can do now and why it matters. no ids, no paths, no commands. example: operators can now read the whole team day on one page instead of scrolling raw logs.';
|
|
54
|
+
const REVIEW_AUTO_ACCEPT_ACTOR = 'auto (certified, small)';
|
|
55
|
+
const REVIEW_AUTO_ACCEPT_POLICY = 'review_autoaccept_certified_small';
|
|
56
|
+
const REVIEW_AUTO_ACCEPT_FILE_LIMIT = 10;
|
|
57
|
+
const REVIEW_AUTO_ACCEPT_LINE_LIMIT = 300;
|
|
49
58
|
|
|
50
59
|
const STATUS_PLAN_TAGS = new Set([
|
|
51
60
|
'agent',
|
|
@@ -107,11 +116,11 @@ function getTaskDb() {
|
|
|
107
116
|
}
|
|
108
117
|
}
|
|
109
118
|
|
|
110
|
-
function warnIfTaskTitleNeedsOperatorWhy(title) {
|
|
119
|
+
function warnIfTaskTitleNeedsOperatorWhy(title, options = {}) {
|
|
111
120
|
const text = String(title || '').trim();
|
|
112
121
|
if (!text || operatorReady(text)) return null;
|
|
113
|
-
const warning = 'Warning:
|
|
114
|
-
console.error(warning);
|
|
122
|
+
const warning = 'Warning: put the why in this task title in plain words: what it buys or costs, and who benefits. Drop flags and ids.';
|
|
123
|
+
if (options.print !== false) console.error(warning);
|
|
115
124
|
return warning;
|
|
116
125
|
}
|
|
117
126
|
|
|
@@ -133,16 +142,19 @@ atris task - durable local task state (SQLite, gitignored)
|
|
|
133
142
|
atris task continue-work <id> Create/reuse a certified Review follow-up task
|
|
134
143
|
atris task say <id> "<message>" Add context to a task
|
|
135
144
|
atris task chat <id> "<message>" [--goal "..."] Refine a task chat + working goal
|
|
136
|
-
atris task ready <id> --proof "..."
|
|
137
|
-
|
|
145
|
+
atris task ready <id> --proof "..." --result "<sentence>"
|
|
146
|
+
Agent proof ready; native goal can complete
|
|
147
|
+
atris task ready <id> --verify "<cmd>" --result "<sentence>"
|
|
148
|
+
Run <cmd>; only ready if it exits 0 (executed proof)
|
|
138
149
|
Writes atris/runs/ receipt (pass or fail), folds path into proof
|
|
139
150
|
atris task receipt <id> --verify "<cmd>" Run <cmd> and write an atris/runs/ receipt without going to ready
|
|
140
151
|
atris task plan-preview "<purpose>" [--tag <tag>] [--owner <member>] [--task <id>]
|
|
141
152
|
Show the plain Plan before work starts
|
|
142
|
-
atris task ready <id> --proof "..." [--changed "..." --checked "..." --saved "..." --try "..."]
|
|
153
|
+
atris task ready <id> --proof "..." --result "<sentence>" [--changed "..." --checked "..." --saved "..." --try "..."]
|
|
143
154
|
Agent proof ready; records Result if needed
|
|
144
|
-
atris task ready <id> --proof "..." [--happened "..." --checked "..." --tested "..." --decision "..."]
|
|
155
|
+
atris task ready <id> --proof "..." --result "<sentence>" [--happened "..." --checked "..." --tested "..." --decision "..."]
|
|
145
156
|
Agent proof ready; writes the human result receipt
|
|
157
|
+
atris task result <id> "<sentence>" Set or replace the day-one PM result sentence
|
|
146
158
|
atris task result <id> --changed "..." --checked "..." [--saved "..."] [--try "..."]
|
|
147
159
|
Show the plain Result and store trace on the task
|
|
148
160
|
atris task review-chat <id> [--as <owner>] Start a task-owned /codex verification chat
|
|
@@ -153,6 +165,7 @@ atris task - durable local task state (SQLite, gitignored)
|
|
|
153
165
|
atris task auto-accept-certified --dry-run [--strict-verify] [--all] [--limit <n>]
|
|
154
166
|
Preview certified Review rows; live accept needs --confirm-human-accept --as <human>
|
|
155
167
|
atris task sweep --auto-accept [--json] Auto-accept verified Review rows; protected lanes wait for human
|
|
168
|
+
atris task audit [--limit <n>] [--revise] re-run stored verifies for newest accepted tasks; report-only unless --revise
|
|
156
169
|
atris task revise <id> --note "..." Send reviewed work back to Do
|
|
157
170
|
|
|
158
171
|
atris task add "<title>" [--tag <tag>] [--goal-id <id>] Create a task
|
|
@@ -163,7 +176,8 @@ atris task - durable local task state (SQLite, gitignored)
|
|
|
163
176
|
Start task-owned Do work from the plan
|
|
164
177
|
atris task backlog <id> [--reason "..."] Move a planned open task back to Backlog
|
|
165
178
|
atris task clear-plan --yes Move all planned open tasks back to Backlog
|
|
166
|
-
atris task day [--all] [--everywhere] [--json] show today's owner-grouped task list
|
|
179
|
+
atris task day [--full] [--all] [--everywhere] [--json] show today's owner-grouped task list
|
|
180
|
+
text shows eight current rows; --full shows every active row
|
|
167
181
|
--all stays in this workspace; --everywhere spans workspaces
|
|
168
182
|
atris task list [--all] [--everywhere] [--status <s>]
|
|
169
183
|
list tasks in this workspace; --everywhere spans workspaces
|
|
@@ -183,6 +197,7 @@ atris task - durable local task state (SQLite, gitignored)
|
|
|
183
197
|
Advance the scoped current task one safe step
|
|
184
198
|
review-state lanes: needs-agent, continue-work, human-accept-waiting, certified
|
|
185
199
|
atris task note <id> "<message>" Append dialogue/context to a task
|
|
200
|
+
atris task retitle <id> "<new title>" Rename a task and preserve the old title in dialogue
|
|
186
201
|
atris task tag <id> --add <tag> [--remove <tag>]
|
|
187
202
|
Update tags on an existing task (e.g. --add needs-human to hold it
|
|
188
203
|
from sweep + fleet staffing); logs a task_tags_updated event
|
|
@@ -196,6 +211,7 @@ atris task - durable local task state (SQLite, gitignored)
|
|
|
196
211
|
atris task archive <id> --reason "..." [--from-failed]
|
|
197
212
|
Sweep off-roadmap/duplicate work as archived (not failed);
|
|
198
213
|
--from-failed opts in to relabel a fail-closed row (never done)
|
|
214
|
+
atris task clear-done [--before <days>] [--dry-run] [--json] Archive completed rows, oldest first
|
|
199
215
|
atris task relabel-archived [--dry-run|--apply]
|
|
200
216
|
One-time OBL-1622 migration: relabel June-10 backlog-reset rows failed -> archived
|
|
201
217
|
atris task finish <id> --proof "..." Legacy alias for done with proof
|
|
@@ -427,6 +443,22 @@ function proofFlagValue(args) {
|
|
|
427
443
|
return typeof proof === 'string' ? proof.trim() : '';
|
|
428
444
|
}
|
|
429
445
|
|
|
446
|
+
function resultSentenceIssue(value) {
|
|
447
|
+
const check = explainResult(value);
|
|
448
|
+
return check.ok ? null : check.reason;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function readyResultDetail(reason) {
|
|
452
|
+
return reason ? `${READY_RESULT_TEACHING}\n${reason}` : READY_RESULT_TEACHING;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function requireResultSentence(label, value, { ready = false } = {}) {
|
|
456
|
+
const issue = resultSentenceIssue(value);
|
|
457
|
+
if (!issue) return String(value || '').replace(/\s+/g, ' ').trim();
|
|
458
|
+
const detail = ready ? readyResultDetail(issue) : issue;
|
|
459
|
+
failTask(label, 'weak_result', detail);
|
|
460
|
+
}
|
|
461
|
+
|
|
430
462
|
function textFlag(args, names) {
|
|
431
463
|
for (const name of names) {
|
|
432
464
|
const value = flag(args, name);
|
|
@@ -456,17 +488,27 @@ function landingNeedsDayOnePm(sentence, title) {
|
|
|
456
488
|
const text = normalizedLandingSentence(sentence);
|
|
457
489
|
if (!text) return true;
|
|
458
490
|
if (text.toLowerCase() === normalizedLandingSentence(defaultLandingSentenceForTitle(title)).toLowerCase()) return true;
|
|
459
|
-
return hasAgentJargon(text) || !operatorReady(text);
|
|
491
|
+
return hasAgentJargon(text) || /\bas\s+exists?\b/i.test(text) || !operatorReady(text);
|
|
460
492
|
}
|
|
461
493
|
|
|
462
494
|
function warnIfLandingNeedsDayOnePm(landing, title) {
|
|
463
495
|
const sentence = landing && typeof landing === 'object' ? landing.happened : '';
|
|
464
496
|
if (!landingNeedsDayOnePm(sentence, title)) return null;
|
|
465
|
-
const warning = 'Advisory: add --landing with one
|
|
497
|
+
const warning = 'Advisory: add --landing with one plain sentence saying what someone can do now, in words a new teammate would get. No flags, no ids.';
|
|
466
498
|
console.error(warning);
|
|
467
499
|
return warning;
|
|
468
500
|
}
|
|
469
501
|
|
|
502
|
+
function requireExplicitLandingDayOnePm(label, landing, title) {
|
|
503
|
+
const sentence = landing && typeof landing === 'object' ? normalizedLandingSentence(landing.happened) : '';
|
|
504
|
+
if (!sentence || !landingNeedsDayOnePm(sentence, title)) return;
|
|
505
|
+
failTask(
|
|
506
|
+
label,
|
|
507
|
+
'weak_landing',
|
|
508
|
+
'landing needs one plain sentence saying what someone can do now and why it matters, in words a new teammate would understand. no flags, ids, or unnamed filters.',
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
|
|
470
512
|
function numericFlag(args, name) {
|
|
471
513
|
const value = flag(args, name);
|
|
472
514
|
if (value === null || value === true || value === undefined) return null;
|
|
@@ -481,6 +523,102 @@ function meaningfulTaskProofIssue(proof, { required = true } = {}) {
|
|
|
481
523
|
return state.ok ? null : state.reason;
|
|
482
524
|
}
|
|
483
525
|
|
|
526
|
+
function goldenPathMissionXpTask(task) {
|
|
527
|
+
if (!task) return false;
|
|
528
|
+
const metadata = task.metadata || {};
|
|
529
|
+
const text = [
|
|
530
|
+
task.title,
|
|
531
|
+
task.tag,
|
|
532
|
+
metadata.goal_id,
|
|
533
|
+
metadata.goalId,
|
|
534
|
+
metadata.mission_id,
|
|
535
|
+
metadata.stop_condition,
|
|
536
|
+
metadata.goal_objective,
|
|
537
|
+
metadata.objective,
|
|
538
|
+
].filter(Boolean).join(' ');
|
|
539
|
+
const missionXp = String(task.title || '').trim().toLowerCase().startsWith('mission xp:')
|
|
540
|
+
|| String(task.tag || '').toLowerCase() === 'agent-xp';
|
|
541
|
+
return missionXp
|
|
542
|
+
&& /\b(?:golden[- ]path|zero[- ]knowledge|zero[- ]papercuts?|fresh[- ](?:laptop|environment|install|home)|self[- ]landed)\b/i.test(text);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function receiptTextForProof(proof, root = process.cwd()) {
|
|
546
|
+
const chunks = [];
|
|
547
|
+
const pattern = new RegExp(RECEIPT_PATH_PATTERN.source, 'g');
|
|
548
|
+
let match;
|
|
549
|
+
while ((match = pattern.exec(String(proof || ''))) && chunks.length < 3) {
|
|
550
|
+
const rel = match[1];
|
|
551
|
+
if (!rel || rel.includes('*')) continue;
|
|
552
|
+
try {
|
|
553
|
+
const raw = fs.readFileSync(path.resolve(root, rel), 'utf8');
|
|
554
|
+
const parsed = JSON.parse(raw);
|
|
555
|
+
chunks.push(JSON.stringify({
|
|
556
|
+
schema: parsed.schema || null,
|
|
557
|
+
mission_id: parsed.mission_id || null,
|
|
558
|
+
result: parsed.result || null,
|
|
559
|
+
landing: parsed.landing || null,
|
|
560
|
+
last_landing: parsed.last_landing || null,
|
|
561
|
+
summary: parsed.summary || null,
|
|
562
|
+
}).slice(0, 12000));
|
|
563
|
+
} catch {}
|
|
564
|
+
}
|
|
565
|
+
return chunks.join(' ');
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// Mission ticks already compose a real landing sentence into the run receipt.
|
|
569
|
+
// When a mission-bridged task lands with only a receipt proof, lift that
|
|
570
|
+
// sentence so the review queue shows the work instead of echoing the title.
|
|
571
|
+
function missionReceiptResultForProof(task, proof, root = process.cwd()) {
|
|
572
|
+
const metadata = task?.metadata || {};
|
|
573
|
+
if (!metadata.mission_id && !metadata.goal_id) return null;
|
|
574
|
+
const pattern = new RegExp(RECEIPT_PATH_PATTERN.source, 'g');
|
|
575
|
+
let match;
|
|
576
|
+
while ((match = pattern.exec(String(proof || '')))) {
|
|
577
|
+
const rel = match[1];
|
|
578
|
+
if (!rel || rel.includes('*')) continue;
|
|
579
|
+
let landing = null;
|
|
580
|
+
try {
|
|
581
|
+
const parsed = JSON.parse(fs.readFileSync(path.resolve(root, rel), 'utf8'));
|
|
582
|
+
landing = parsed?.result?.landing || parsed?.landing || parsed?.last_landing || null;
|
|
583
|
+
} catch { continue; }
|
|
584
|
+
const changed = String(landing?.changed || landing?.happened || '').replace(/\s+/g, ' ').trim();
|
|
585
|
+
if (!changed) continue;
|
|
586
|
+
if (/\brecorded tick \d+\.?$/i.test(changed) || /^recorded a proof heartbeat\b/i.test(changed)) continue;
|
|
587
|
+
const reason = String(landing?.reason || landing?.why || '').replace(/\s+/g, ' ').trim();
|
|
588
|
+
return { changed, reason: reason && !isRetiredFillerReason(reason) ? reason : null };
|
|
589
|
+
}
|
|
590
|
+
return null;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function missionXpEndToEndProofIssue(task, proof, root = process.cwd()) {
|
|
594
|
+
if (!goldenPathMissionXpTask(task)) return null;
|
|
595
|
+
const corpus = `${String(proof || '')} ${receiptTextForProof(proof, root)}`
|
|
596
|
+
.replace(/\s+/g, ' ')
|
|
597
|
+
.trim();
|
|
598
|
+
const hasZeroPapercut = /\b(?:zero|0|no)\s+(?:new\s+)?papercuts?\b|\bzero[- ]papercut\b/i.test(corpus);
|
|
599
|
+
const hasEndToEnd = /\bend[- ]to[- ]end\b|\bfull\s+(?:fresh[- ](?:laptop|environment)\s+)?pass\b|\bfresh[- ](?:laptop|environment|install)\b|\bclean\s+temp\s+home\b|\bnpm\s+pack\b/i.test(corpus);
|
|
600
|
+
const hasSelfLanded = /\bself[- ]landed\b|\bfirst\s+self[- ]landed\s+task\b|\btask\s+reaches\s+done\b|\binstall\b.{0,120}\binit\b.{0,120}\bmission\b.{0,120}\b(?:self[- ]landed|task)\b/i.test(corpus);
|
|
601
|
+
return hasZeroPapercut && hasEndToEnd && hasSelfLanded ? null : MISSION_XP_END_TO_END_DETAIL;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function missionXpProofBoundaryEvaluation(task, proofOverride = null) {
|
|
605
|
+
if (!goldenPathMissionXpTask(task)) return null;
|
|
606
|
+
const metadata = task.metadata || {};
|
|
607
|
+
const review = task.review || {};
|
|
608
|
+
const proof = proofOverride === null
|
|
609
|
+
? String(review.proof || metadata.latest_agent_proof || '').trim()
|
|
610
|
+
: String(proofOverride || '').trim();
|
|
611
|
+
const issue = missionXpEndToEndProofIssue(task, proof, task.workspace_root || process.cwd());
|
|
612
|
+
if (!issue) return null;
|
|
613
|
+
return {
|
|
614
|
+
eligible: false,
|
|
615
|
+
ref: taskRef(task),
|
|
616
|
+
reason: MISSION_XP_END_TO_END_REASON,
|
|
617
|
+
next_action: 'attach the zero-papercut end-to-end fresh-laptop receipt, then resubmit Mission XP proof',
|
|
618
|
+
proof,
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
|
|
484
622
|
function requireMeaningfulTaskProof(label, proof, { required = true } = {}) {
|
|
485
623
|
const issue = meaningfulTaskProofIssue(proof, { required });
|
|
486
624
|
if (issue) failTask(label, 'weak_proof', `meaningful proof required: ${issue}`);
|
|
@@ -509,8 +647,15 @@ function writeDefaultProjection(taskDb, db, options = {}) {
|
|
|
509
647
|
limit: options.all ? null : 500,
|
|
510
648
|
}));
|
|
511
649
|
const outPath = path.resolve(path.join('.atris', 'state', 'tasks.projection.json'));
|
|
650
|
+
const output = JSON.stringify(projection, null, 2) + '\n';
|
|
512
651
|
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
513
|
-
|
|
652
|
+
let shouldWrite = true;
|
|
653
|
+
try {
|
|
654
|
+
shouldWrite = fs.readFileSync(outPath, 'utf8') !== output;
|
|
655
|
+
} catch {
|
|
656
|
+
shouldWrite = true;
|
|
657
|
+
}
|
|
658
|
+
if (shouldWrite) fs.writeFileSync(outPath, output, 'utf8');
|
|
514
659
|
return { projection, outPath };
|
|
515
660
|
}
|
|
516
661
|
|
|
@@ -733,6 +878,8 @@ function certifiedReviewNextAction(nextTaskTitle) {
|
|
|
733
878
|
}
|
|
734
879
|
|
|
735
880
|
function proofBoundaryBlockedEvaluation(task) {
|
|
881
|
+
const missionXpBoundary = missionXpProofBoundaryEvaluation(task);
|
|
882
|
+
if (missionXpBoundary) return missionXpBoundary;
|
|
736
883
|
// strictVerify stays off here: this is a render-path probe for the boundary
|
|
737
884
|
// reason only, and the default-true strict mode would spawn the verify
|
|
738
885
|
// subprocess for every certified row just to draw the desk.
|
|
@@ -926,26 +1073,11 @@ function proofToReasonText(proof) {
|
|
|
926
1073
|
return /[.!?]$/.test(section) ? section : `${section}.`;
|
|
927
1074
|
}
|
|
928
1075
|
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
}
|
|
935
|
-
if (/\bapprove\b/.test(text) && /\b(command|exact|preview|ux)\b/.test(text)) {
|
|
936
|
-
return 'It lets the operator see the next command without hunting.';
|
|
937
|
-
}
|
|
938
|
-
if (/\b(stale|expire|expired)\b/.test(text) && /\bapproval/.test(text)) {
|
|
939
|
-
return 'It stops old approvals from running after their context has gone stale.';
|
|
940
|
-
}
|
|
941
|
-
if (/\bapproval|approve|permission\b/.test(text)) {
|
|
942
|
-
return 'It keeps real-world side effects behind a clear human decision.';
|
|
943
|
-
}
|
|
944
|
-
if (/\btest|self-test|harness|verifier|proof\b/.test(text)) {
|
|
945
|
-
return 'It gives the human a repeatable check before approval.';
|
|
946
|
-
}
|
|
947
|
-
return 'It turns the task title into a concrete result the human can approve.';
|
|
948
|
-
}
|
|
1076
|
+
// A landing sentence written for --result already carries its own why
|
|
1077
|
+
// ("..., so operators keep deciding instead of waiting"). Reuse that clause
|
|
1078
|
+
// as the reason instead of inventing one; with no clause, stay silent.
|
|
1079
|
+
// landingWhyClause lives in lib/voice-gate.js so every human-bound landing
|
|
1080
|
+
// composer (task reviews, mission receipts) shares the same why extraction.
|
|
949
1081
|
|
|
950
1082
|
function proofToHumanCheck(proof) {
|
|
951
1083
|
const text = String(proof || '').replace(/\s+/g, ' ').trim();
|
|
@@ -1055,17 +1187,30 @@ function taskReviewLanding(task, review = {}, payload = {}) {
|
|
|
1055
1187
|
const agentCertified = review.agent_certified === true || metadata.agent_certified === true;
|
|
1056
1188
|
const approvalStatus = review.approval_status || metadata.approval_status || null;
|
|
1057
1189
|
const explicitHappened = landingPayloadValue(payload, metadata, 'happened')
|
|
1058
|
-
|| payload.changed || metadata.result_changed || metadata.human_changed || metadata.changed;
|
|
1190
|
+
|| payload.result || metadata.result || payload.changed || metadata.result_changed || metadata.human_changed || metadata.changed;
|
|
1059
1191
|
const explicitChecked = landingPayloadValue(payload, metadata, 'checked')
|
|
1060
1192
|
|| payload.checked || metadata.result_checked || metadata.human_checked || metadata.checked;
|
|
1061
1193
|
const explicitTested = landingPayloadValue(payload, metadata, 'tested');
|
|
1062
1194
|
const explicitDecision = landingPayloadValue(payload, metadata, 'decision');
|
|
1063
|
-
const
|
|
1195
|
+
const explicitReasonRaw = landingPayloadValue(payload, metadata, 'reason')
|
|
1064
1196
|
|| landingPayloadValue(payload, metadata, 'why')
|
|
1065
1197
|
|| payload.reason || payload.why || metadata.result_reason || metadata.review_reason || metadata.why_it_matters;
|
|
1198
|
+
const explicitReason = explicitReasonRaw && !isRetiredFillerReason(explicitReasonRaw) ? explicitReasonRaw : null;
|
|
1199
|
+
const missionLift = explicitHappened
|
|
1200
|
+
? null
|
|
1201
|
+
: missionReceiptResultForProof(task, proof, task.workspace_root || process.cwd());
|
|
1202
|
+
let happened = clipStatusText(explicitHappened || (missionLift && missionLift.changed) || titleToResultText(task.title), 220);
|
|
1203
|
+
let reason = clipStatusText(explicitReason || proofToReasonText(proof) || (missionLift && missionLift.reason) || '', 220);
|
|
1204
|
+
if (!reason) {
|
|
1205
|
+
const clause = landingWhyClause(happened);
|
|
1206
|
+
if (clause) {
|
|
1207
|
+
happened = clipStatusText(clause.change, 220);
|
|
1208
|
+
reason = clipStatusText(clause.why, 220);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1066
1211
|
return {
|
|
1067
|
-
happened
|
|
1068
|
-
reason
|
|
1212
|
+
happened,
|
|
1213
|
+
reason,
|
|
1069
1214
|
checked: clipStatusText(explicitChecked || proofToHumanCheck(proof), 220),
|
|
1070
1215
|
tested: clipStatusText(explicitTested || taskReviewLandingTested(proof), 260),
|
|
1071
1216
|
decision: clipStatusText(explicitDecision || (task.status === 'done'
|
|
@@ -1658,7 +1803,10 @@ function reviewHandoffForTask(task, { suppressExistingFollowUp = false, hasExist
|
|
|
1658
1803
|
if (proofBoundary) {
|
|
1659
1804
|
handoff.reason = proofBoundary.reason;
|
|
1660
1805
|
handoff.next_action_detail = proofBoundary.next_action || null;
|
|
1661
|
-
|
|
1806
|
+
const note = proofBoundary.reason === MISSION_XP_END_TO_END_REASON
|
|
1807
|
+
? '<attach zero-papercut end-to-end fresh-laptop receipt or move back to Do>'
|
|
1808
|
+
: '<replace stale PR proof with merged proof or move back to Do>';
|
|
1809
|
+
handoff.revise_command = `atris task revise ${taskRef(task)} --note "${note}"`;
|
|
1662
1810
|
} else if (!agentCertified) {
|
|
1663
1811
|
const blocker = reviewBlockerForTask(task);
|
|
1664
1812
|
handoff.reason = blocker.reason;
|
|
@@ -2102,7 +2250,8 @@ function compactTaskForStatus(task) {
|
|
|
2102
2250
|
id: task.id,
|
|
2103
2251
|
display_id: task.display_id || null,
|
|
2104
2252
|
legacy_ref: task.legacy_ref || taskRef(task.id),
|
|
2105
|
-
title:
|
|
2253
|
+
title: clipStatusTitle(task.title, 140),
|
|
2254
|
+
result: clipStatusText(task.result || metadata.result, 180) || null,
|
|
2106
2255
|
status: task.status,
|
|
2107
2256
|
updated_at: task.updated_at,
|
|
2108
2257
|
};
|
|
@@ -2156,7 +2305,7 @@ function compactTaskFromProjection(projection, id) {
|
|
|
2156
2305
|
function compactEventPayload(payload) {
|
|
2157
2306
|
if (!payload || typeof payload !== 'object') return null;
|
|
2158
2307
|
const out = {};
|
|
2159
|
-
for (const key of ['title', 'status', 'tag', 'content', 'goal', 'summary', 'proof', 'lesson', 'reward', 'next_task']) {
|
|
2308
|
+
for (const key of ['title', 'status', 'tag', 'content', 'goal', 'summary', 'proof', 'lesson', 'reward', 'next_task', 'result']) {
|
|
2160
2309
|
if (payload[key] !== undefined && payload[key] !== null && payload[key] !== '') out[key] = payload[key];
|
|
2161
2310
|
}
|
|
2162
2311
|
return Object.keys(out).length ? out : null;
|
|
@@ -2181,6 +2330,19 @@ function clipStatusText(value, max = 180) {
|
|
|
2181
2330
|
return `${text.slice(0, max - 1)}…`;
|
|
2182
2331
|
}
|
|
2183
2332
|
|
|
2333
|
+
function clipStatusTitle(value, max = 140) {
|
|
2334
|
+
const text = String(value || '').replace(/\s+/g, ' ').trim();
|
|
2335
|
+
if (text.length <= max) return text;
|
|
2336
|
+
const cut = text.slice(0, max + 1);
|
|
2337
|
+
const boundaries = [...cut.matchAll(/[.!?;](?=\s|$)/g)];
|
|
2338
|
+
const boundary = boundaries.map((match) => match.index).filter((index) => index >= max * 0.45).pop();
|
|
2339
|
+
if (Number.isInteger(boundary)) {
|
|
2340
|
+
return cut.slice(0, boundary + 1).replace(/[;:]+$/, '').trim();
|
|
2341
|
+
}
|
|
2342
|
+
const wholeWords = text.slice(0, max).replace(/\s+\S*$/, '').trim();
|
|
2343
|
+
return `${wholeWords || text.slice(0, max).trim()}...`;
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2184
2346
|
function compactReviewActionRef(task, { hasExistingReviewFollowUp = null } = {}) {
|
|
2185
2347
|
if (!task) return null;
|
|
2186
2348
|
const handoff = reviewHandoffForTask(task, { suppressExistingFollowUp: true, hasExistingReviewFollowUp }) || {};
|
|
@@ -4577,6 +4739,483 @@ function cmdReviewLaneRun(args) {
|
|
|
4577
4739
|
if (!result.ok) process.exit(1);
|
|
4578
4740
|
}
|
|
4579
4741
|
|
|
4742
|
+
function configValueDisabled(value) {
|
|
4743
|
+
if (value === false) return true;
|
|
4744
|
+
const text = String(value === undefined || value === null ? '' : value).trim().toLowerCase();
|
|
4745
|
+
return ['0', 'false', 'off', 'no'].includes(text);
|
|
4746
|
+
}
|
|
4747
|
+
|
|
4748
|
+
function reviewAutoAcceptEnabled() {
|
|
4749
|
+
try {
|
|
4750
|
+
const { loadConfig } = require('../utils/config');
|
|
4751
|
+
const config = loadConfig();
|
|
4752
|
+
const value = Object.prototype.hasOwnProperty.call(config, 'autoaccept')
|
|
4753
|
+
? config.autoaccept
|
|
4754
|
+
: Object.prototype.hasOwnProperty.call(config, 'review_autoaccept')
|
|
4755
|
+
? config.review_autoaccept
|
|
4756
|
+
: config.reviewAutoaccept;
|
|
4757
|
+
return !configValueDisabled(value);
|
|
4758
|
+
} catch {
|
|
4759
|
+
return true;
|
|
4760
|
+
}
|
|
4761
|
+
}
|
|
4762
|
+
|
|
4763
|
+
function reviewAutoAcceptStatePath(root = process.cwd()) {
|
|
4764
|
+
return path.join(root, '.atris', 'state', 'review-autoaccept.json');
|
|
4765
|
+
}
|
|
4766
|
+
|
|
4767
|
+
function readReviewAutoAcceptState(root = process.cwd()) {
|
|
4768
|
+
const file = reviewAutoAcceptStatePath(root);
|
|
4769
|
+
try {
|
|
4770
|
+
if (!fs.existsSync(file)) return {};
|
|
4771
|
+
const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
4772
|
+
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
4773
|
+
} catch {
|
|
4774
|
+
return {};
|
|
4775
|
+
}
|
|
4776
|
+
}
|
|
4777
|
+
|
|
4778
|
+
function writeReviewAutoAcceptState(root = process.cwd(), state = {}) {
|
|
4779
|
+
const file = reviewAutoAcceptStatePath(root);
|
|
4780
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
4781
|
+
fs.writeFileSync(file, JSON.stringify(state, null, 2) + '\n', 'utf8');
|
|
4782
|
+
}
|
|
4783
|
+
|
|
4784
|
+
function finiteNumber(value) {
|
|
4785
|
+
const number = Number(value);
|
|
4786
|
+
return Number.isFinite(number) ? number : null;
|
|
4787
|
+
}
|
|
4788
|
+
|
|
4789
|
+
function firstNumberFromObject(source, keys) {
|
|
4790
|
+
if (!source || typeof source !== 'object') return null;
|
|
4791
|
+
for (const key of keys) {
|
|
4792
|
+
if (!Object.prototype.hasOwnProperty.call(source, key)) continue;
|
|
4793
|
+
const value = source[key];
|
|
4794
|
+
if (Array.isArray(value)) return value.length;
|
|
4795
|
+
if (typeof value === 'string' && value.includes(',')) {
|
|
4796
|
+
const parts = value.split(',').map(part => part.trim()).filter(Boolean);
|
|
4797
|
+
if (parts.length > 1) return parts.length;
|
|
4798
|
+
}
|
|
4799
|
+
const number = finiteNumber(value);
|
|
4800
|
+
if (number !== null) return number;
|
|
4801
|
+
}
|
|
4802
|
+
return null;
|
|
4803
|
+
}
|
|
4804
|
+
|
|
4805
|
+
function arrayFromPathValue(value) {
|
|
4806
|
+
if (!value) return [];
|
|
4807
|
+
if (Array.isArray(value)) return value.flatMap(arrayFromPathValue);
|
|
4808
|
+
if (typeof value === 'object') {
|
|
4809
|
+
if (typeof value.path === 'string') return [value.path];
|
|
4810
|
+
if (typeof value.file === 'string') return [value.file];
|
|
4811
|
+
if (typeof value.filename === 'string') return [value.filename];
|
|
4812
|
+
return [];
|
|
4813
|
+
}
|
|
4814
|
+
return String(value)
|
|
4815
|
+
.split(/[\n,]+/)
|
|
4816
|
+
.map(part => part.trim())
|
|
4817
|
+
.filter(Boolean);
|
|
4818
|
+
}
|
|
4819
|
+
|
|
4820
|
+
function collectRecordedDiffPaths(source) {
|
|
4821
|
+
if (!source || typeof source !== 'object') return [];
|
|
4822
|
+
const keys = [
|
|
4823
|
+
'files',
|
|
4824
|
+
'paths',
|
|
4825
|
+
'touched_files',
|
|
4826
|
+
'touchedFiles',
|
|
4827
|
+
'changed_files',
|
|
4828
|
+
'changedFiles',
|
|
4829
|
+
'modified_files',
|
|
4830
|
+
'modifiedFiles',
|
|
4831
|
+
];
|
|
4832
|
+
const out = [];
|
|
4833
|
+
for (const key of keys) out.push(...arrayFromPathValue(source[key]));
|
|
4834
|
+
return out;
|
|
4835
|
+
}
|
|
4836
|
+
|
|
4837
|
+
function normalizeRecordedDiffStats(source, label = 'recorded') {
|
|
4838
|
+
if (!source || typeof source !== 'object') return null;
|
|
4839
|
+
const fileKeys = [
|
|
4840
|
+
'files_touched',
|
|
4841
|
+
'filesTouched',
|
|
4842
|
+
'changed_files_count',
|
|
4843
|
+
'changedFilesCount',
|
|
4844
|
+
'files_changed',
|
|
4845
|
+
'filesChanged',
|
|
4846
|
+
'file_count',
|
|
4847
|
+
'fileCount',
|
|
4848
|
+
'files',
|
|
4849
|
+
'changed_files',
|
|
4850
|
+
'changedFiles',
|
|
4851
|
+
];
|
|
4852
|
+
const changedLineKeys = [
|
|
4853
|
+
'changed_lines',
|
|
4854
|
+
'changedLines',
|
|
4855
|
+
'lines_changed',
|
|
4856
|
+
'linesChanged',
|
|
4857
|
+
'changed_line_count',
|
|
4858
|
+
'changedLineCount',
|
|
4859
|
+
'total_changed_lines',
|
|
4860
|
+
'totalChangedLines',
|
|
4861
|
+
'line_count',
|
|
4862
|
+
'lineCount',
|
|
4863
|
+
];
|
|
4864
|
+
const insertions = firstNumberFromObject(source, ['insertions', 'added', 'additions', 'lines_added', 'linesAdded']);
|
|
4865
|
+
const deletions = firstNumberFromObject(source, ['deletions', 'deleted', 'removals', 'lines_deleted', 'linesDeleted']);
|
|
4866
|
+
const paths = collectRecordedDiffPaths(source);
|
|
4867
|
+
const filesTouched = firstNumberFromObject(source, fileKeys) ?? (paths.length ? paths.length : null);
|
|
4868
|
+
const changedLines = firstNumberFromObject(source, changedLineKeys)
|
|
4869
|
+
?? (insertions !== null || deletions !== null ? Number(insertions || 0) + Number(deletions || 0) : null);
|
|
4870
|
+
if (filesTouched === null && changedLines === null && !paths.length) return null;
|
|
4871
|
+
return {
|
|
4872
|
+
source: label,
|
|
4873
|
+
files_touched: filesTouched,
|
|
4874
|
+
changed_lines: changedLines,
|
|
4875
|
+
paths,
|
|
4876
|
+
};
|
|
4877
|
+
}
|
|
4878
|
+
|
|
4879
|
+
function recordedDiffStatsForTask(task) {
|
|
4880
|
+
const metadata = task && task.metadata && typeof task.metadata === 'object' ? task.metadata : {};
|
|
4881
|
+
const candidates = [
|
|
4882
|
+
metadata.diff_stats,
|
|
4883
|
+
metadata.diffStats,
|
|
4884
|
+
metadata.git_diff_stats,
|
|
4885
|
+
metadata.gitDiffStats,
|
|
4886
|
+
metadata.change_stats,
|
|
4887
|
+
metadata.changeStats,
|
|
4888
|
+
metadata.stats,
|
|
4889
|
+
metadata,
|
|
4890
|
+
];
|
|
4891
|
+
for (const candidate of candidates) {
|
|
4892
|
+
const stats = normalizeRecordedDiffStats(candidate, 'recorded');
|
|
4893
|
+
if (stats) return stats;
|
|
4894
|
+
}
|
|
4895
|
+
return null;
|
|
4896
|
+
}
|
|
4897
|
+
|
|
4898
|
+
function proofDiffStats(proof) {
|
|
4899
|
+
const text = String(proof || '').replace(/\s+/g, ' ').trim();
|
|
4900
|
+
if (!text) return null;
|
|
4901
|
+
let filesTouched = null;
|
|
4902
|
+
let changedLines = null;
|
|
4903
|
+
const gitStat = text.match(/(\d+)\s+files?\s+changed(?:,\s*(\d+)\s+insertions?\(\+\))?(?:,\s*(\d+)\s+deletions?\(-\))?/i);
|
|
4904
|
+
if (gitStat) {
|
|
4905
|
+
filesTouched = Number(gitStat[1]);
|
|
4906
|
+
const insertions = gitStat[2] ? Number(gitStat[2]) : 0;
|
|
4907
|
+
const deletions = gitStat[3] ? Number(gitStat[3]) : 0;
|
|
4908
|
+
if (gitStat[2] || gitStat[3]) changedLines = insertions + deletions;
|
|
4909
|
+
}
|
|
4910
|
+
const filePatterns = [
|
|
4911
|
+
/files?\s+(?:touched|changed|modified)\s*[:=]\s*(\d+)/i,
|
|
4912
|
+
/(\d+)\s+files?\s+(?:touched|changed|modified)/i,
|
|
4913
|
+
];
|
|
4914
|
+
for (const pattern of filePatterns) {
|
|
4915
|
+
const match = text.match(pattern);
|
|
4916
|
+
if (match) filesTouched = Number(match[1]);
|
|
4917
|
+
}
|
|
4918
|
+
const linePatterns = [
|
|
4919
|
+
/changed\s+lines?\s*[:=]\s*(\d+)/i,
|
|
4920
|
+
/(\d+)\s+changed\s+lines?/i,
|
|
4921
|
+
/lines?\s+changed\s*[:=]\s*(\d+)/i,
|
|
4922
|
+
];
|
|
4923
|
+
for (const pattern of linePatterns) {
|
|
4924
|
+
const match = text.match(pattern);
|
|
4925
|
+
if (match) changedLines = Number(match[1]);
|
|
4926
|
+
}
|
|
4927
|
+
if (filesTouched === null && changedLines === null) return null;
|
|
4928
|
+
return {
|
|
4929
|
+
source: 'proof',
|
|
4930
|
+
files_touched: filesTouched,
|
|
4931
|
+
changed_lines: changedLines,
|
|
4932
|
+
paths: [],
|
|
4933
|
+
};
|
|
4934
|
+
}
|
|
4935
|
+
|
|
4936
|
+
function safeGitRef(value) {
|
|
4937
|
+
const text = String(value || '').trim();
|
|
4938
|
+
if (!text || text.startsWith('-') || text.includes('..')) return null;
|
|
4939
|
+
if (!/^[a-zA-Z0-9_./@=+~^-]+$/.test(text)) return null;
|
|
4940
|
+
return text;
|
|
4941
|
+
}
|
|
4942
|
+
|
|
4943
|
+
function runGitForAutoAccept(root, args) {
|
|
4944
|
+
try {
|
|
4945
|
+
const { spawnSync } = require('child_process');
|
|
4946
|
+
return spawnSync('git', args, {
|
|
4947
|
+
cwd: root || process.cwd(),
|
|
4948
|
+
encoding: 'utf8',
|
|
4949
|
+
timeout: 10000,
|
|
4950
|
+
});
|
|
4951
|
+
} catch {
|
|
4952
|
+
return { status: 1, stdout: '', stderr: '' };
|
|
4953
|
+
}
|
|
4954
|
+
}
|
|
4955
|
+
|
|
4956
|
+
function gitRefExists(root, ref) {
|
|
4957
|
+
const result = runGitForAutoAccept(root, ['rev-parse', '--verify', `${ref}^{commit}`]);
|
|
4958
|
+
return result.status === 0;
|
|
4959
|
+
}
|
|
4960
|
+
|
|
4961
|
+
function defaultDiffBase(root) {
|
|
4962
|
+
for (const ref of ['origin/master', 'origin/main', 'master', 'main']) {
|
|
4963
|
+
if (gitRefExists(root, ref)) return ref;
|
|
4964
|
+
}
|
|
4965
|
+
return null;
|
|
4966
|
+
}
|
|
4967
|
+
|
|
4968
|
+
function branchDiffStatsForTask(task, root) {
|
|
4969
|
+
const metadata = task && task.metadata && typeof task.metadata === 'object' ? task.metadata : {};
|
|
4970
|
+
const branch = safeGitRef(
|
|
4971
|
+
metadata.branch
|
|
4972
|
+
|| metadata.worktree_branch
|
|
4973
|
+
|| metadata.git_branch
|
|
4974
|
+
|| metadata.pr_branch
|
|
4975
|
+
|| metadata.head_branch
|
|
4976
|
+
|| metadata.worktree?.branch
|
|
4977
|
+
);
|
|
4978
|
+
if (!branch) return null;
|
|
4979
|
+
const base = safeGitRef(
|
|
4980
|
+
metadata.base
|
|
4981
|
+
|| metadata.base_ref
|
|
4982
|
+
|| metadata.target_ref
|
|
4983
|
+
|| metadata.target_branch
|
|
4984
|
+
|| metadata.worktree?.base
|
|
4985
|
+
) || defaultDiffBase(root);
|
|
4986
|
+
if (!base) return null;
|
|
4987
|
+
const result = runGitForAutoAccept(root, ['diff', '--numstat', `${base}...${branch}`]);
|
|
4988
|
+
if (result.status !== 0) return null;
|
|
4989
|
+
const lines = String(result.stdout || '').split(/\r?\n/).filter(Boolean);
|
|
4990
|
+
if (!lines.length) return { source: 'branch', files_touched: 0, changed_lines: 0, paths: [] };
|
|
4991
|
+
let changedLines = 0;
|
|
4992
|
+
let unknownLines = false;
|
|
4993
|
+
const paths = [];
|
|
4994
|
+
for (const line of lines) {
|
|
4995
|
+
const parts = line.split(/\t+/);
|
|
4996
|
+
const added = Number(parts[0]);
|
|
4997
|
+
const deleted = Number(parts[1]);
|
|
4998
|
+
if (!Number.isFinite(added) || !Number.isFinite(deleted)) unknownLines = true;
|
|
4999
|
+
else changedLines += added + deleted;
|
|
5000
|
+
if (parts[2]) paths.push(parts.slice(2).join('\t'));
|
|
5001
|
+
}
|
|
5002
|
+
return {
|
|
5003
|
+
source: 'branch',
|
|
5004
|
+
files_touched: lines.length,
|
|
5005
|
+
changed_lines: unknownLines ? null : changedLines,
|
|
5006
|
+
paths,
|
|
5007
|
+
};
|
|
5008
|
+
}
|
|
5009
|
+
|
|
5010
|
+
function reviewAutoAcceptDiffStats(task, root) {
|
|
5011
|
+
const recorded = recordedDiffStatsForTask(task);
|
|
5012
|
+
if (recorded) return recorded;
|
|
5013
|
+
const branch = branchDiffStatsForTask(task, root);
|
|
5014
|
+
if (branch) return branch;
|
|
5015
|
+
const proof = String(task?.review?.proof || task?.metadata?.latest_agent_proof || '');
|
|
5016
|
+
return proofDiffStats(proof);
|
|
5017
|
+
}
|
|
5018
|
+
|
|
5019
|
+
function reviewAutoAcceptBigTitle(task) {
|
|
5020
|
+
const metadata = task && task.metadata && typeof task.metadata === 'object' ? task.metadata : {};
|
|
5021
|
+
const text = [
|
|
5022
|
+
task && task.title,
|
|
5023
|
+
metadata.kind,
|
|
5024
|
+
metadata.type,
|
|
5025
|
+
metadata.category,
|
|
5026
|
+
metadata.report_type,
|
|
5027
|
+
].map(value => String(value || '')).join(' ').toLowerCase();
|
|
5028
|
+
const match = text.match(/\b(daily\s+update|summary|report|digest|retro|retrospective)\b/i);
|
|
5029
|
+
if (!match) return null;
|
|
5030
|
+
return {
|
|
5031
|
+
ok: false,
|
|
5032
|
+
reason: `big_title_${match[1].toLowerCase().replace(/\s+/g, '_')}`,
|
|
5033
|
+
};
|
|
5034
|
+
}
|
|
5035
|
+
|
|
5036
|
+
function reviewAutoAcceptSizeGate(task, root) {
|
|
5037
|
+
const titleGate = reviewAutoAcceptBigTitle(task);
|
|
5038
|
+
if (titleGate) return titleGate;
|
|
5039
|
+
const stats = reviewAutoAcceptDiffStats(task, root);
|
|
5040
|
+
if (!stats) return { ok: false, reason: 'size_unknown', stats: null };
|
|
5041
|
+
const filesTouched = finiteNumber(stats.files_touched);
|
|
5042
|
+
const changedLines = finiteNumber(stats.changed_lines);
|
|
5043
|
+
if (filesTouched !== null && filesTouched > REVIEW_AUTO_ACCEPT_FILE_LIMIT) {
|
|
5044
|
+
return { ok: false, reason: 'big_files', stats };
|
|
5045
|
+
}
|
|
5046
|
+
if (changedLines !== null && changedLines > REVIEW_AUTO_ACCEPT_LINE_LIMIT) {
|
|
5047
|
+
return { ok: false, reason: 'big_changed_lines', stats };
|
|
5048
|
+
}
|
|
5049
|
+
if (filesTouched === null || changedLines === null) {
|
|
5050
|
+
return { ok: false, reason: 'size_unknown', stats };
|
|
5051
|
+
}
|
|
5052
|
+
return { ok: true, reason: 'small_change', stats };
|
|
5053
|
+
}
|
|
5054
|
+
|
|
5055
|
+
function reviewAutoAcceptMetadataText(task) {
|
|
5056
|
+
const metadata = task && task.metadata && typeof task.metadata === 'object' ? task.metadata : {};
|
|
5057
|
+
return [
|
|
5058
|
+
task && task.title,
|
|
5059
|
+
task && task.tag,
|
|
5060
|
+
task && task.source_key,
|
|
5061
|
+
metadata.kind,
|
|
5062
|
+
metadata.type,
|
|
5063
|
+
metadata.category,
|
|
5064
|
+
metadata.lane,
|
|
5065
|
+
metadata.stage,
|
|
5066
|
+
metadata.area,
|
|
5067
|
+
JSON.stringify(metadata),
|
|
5068
|
+
].map(value => String(value || '')).join(' ').toLowerCase();
|
|
5069
|
+
}
|
|
5070
|
+
|
|
5071
|
+
function reviewAutoAcceptTouchedPaths(task) {
|
|
5072
|
+
const metadata = task && task.metadata && typeof task.metadata === 'object' ? task.metadata : {};
|
|
5073
|
+
const stats = recordedDiffStatsForTask(task);
|
|
5074
|
+
const proof = String(task?.review?.proof || metadata.latest_agent_proof || '');
|
|
5075
|
+
return [
|
|
5076
|
+
...(stats && stats.paths ? stats.paths : []),
|
|
5077
|
+
...collectRecordedDiffPaths(metadata),
|
|
5078
|
+
...taskReviewEvidencePaths(proof, 50),
|
|
5079
|
+
].map(value => String(value || '').trim()).filter(Boolean);
|
|
5080
|
+
}
|
|
5081
|
+
|
|
5082
|
+
function reviewProtectedMatch(task) {
|
|
5083
|
+
const text = reviewAutoAcceptMetadataText(task);
|
|
5084
|
+
const paths = reviewAutoAcceptTouchedPaths(task).join(' ').toLowerCase();
|
|
5085
|
+
const combined = `${text} ${paths}`;
|
|
5086
|
+
const checks = [
|
|
5087
|
+
['auth', /\b(auth|authentication|authorization|oauth|login|session)\b/i],
|
|
5088
|
+
['credentials', /\b(credentials?|secrets?|passwords?|api[-_ ]?keys?|tokens?)\b/i],
|
|
5089
|
+
['csp', /\b(csp|content[-_ ]security[-_ ]policy)\b/i],
|
|
5090
|
+
['sandbox', /\b(sandbox|allow-scripts|allow-same-origin)\b/i],
|
|
5091
|
+
['billing', /\b(billing|invoice|invoices|subscription|subscriptions)\b/i],
|
|
5092
|
+
['payments', /\b(payments?|stripe|checkout|refunds?)\b/i],
|
|
5093
|
+
['outbound_sends', /\b(outbound|send|sending|email|sms|webhook|notification|notifications)\b/i],
|
|
5094
|
+
];
|
|
5095
|
+
for (const [key, pattern] of checks) {
|
|
5096
|
+
if (pattern.test(combined)) return { ok: false, reason: `protected_${key}` };
|
|
5097
|
+
}
|
|
5098
|
+
return { ok: true };
|
|
5099
|
+
}
|
|
5100
|
+
|
|
5101
|
+
function evaluateReviewAutoAccept(task, root) {
|
|
5102
|
+
const ref = taskRef(task);
|
|
5103
|
+
if (!task) return { eligible: false, ref, reason: 'task_not_found' };
|
|
5104
|
+
const protectedGate = reviewProtectedMatch(task);
|
|
5105
|
+
if (!protectedGate.ok) return { eligible: false, ref, reason: protectedGate.reason };
|
|
5106
|
+
const sizeGate = reviewAutoAcceptSizeGate(task, root);
|
|
5107
|
+
if (!sizeGate.ok) return { eligible: false, ref, reason: sizeGate.reason, size: sizeGate.stats };
|
|
5108
|
+
const evaluation = evaluateAutoAccept(task, { strictVerify: true });
|
|
5109
|
+
if (!evaluation.eligible) {
|
|
5110
|
+
return {
|
|
5111
|
+
...evaluation,
|
|
5112
|
+
size: sizeGate.stats,
|
|
5113
|
+
};
|
|
5114
|
+
}
|
|
5115
|
+
return {
|
|
5116
|
+
...evaluation,
|
|
5117
|
+
eligible: true,
|
|
5118
|
+
reason: 'certified_small',
|
|
5119
|
+
policy: REVIEW_AUTO_ACCEPT_POLICY,
|
|
5120
|
+
size: sizeGate.stats,
|
|
5121
|
+
};
|
|
5122
|
+
}
|
|
5123
|
+
|
|
5124
|
+
function autoAcceptCertifiedSmallReviews(taskDb, db, projection) {
|
|
5125
|
+
const enabled = reviewAutoAcceptEnabled();
|
|
5126
|
+
const root = projection.workspace_root || process.cwd();
|
|
5127
|
+
const results = [];
|
|
5128
|
+
if (!enabled) {
|
|
5129
|
+
return {
|
|
5130
|
+
enabled,
|
|
5131
|
+
scanned: 0,
|
|
5132
|
+
accepted: 0,
|
|
5133
|
+
changed: false,
|
|
5134
|
+
results,
|
|
5135
|
+
};
|
|
5136
|
+
}
|
|
5137
|
+
const certified = certifiedPendingReviewTasks(projection);
|
|
5138
|
+
for (const item of certified) {
|
|
5139
|
+
const fullProjection = enrichTaskProjection(taskDb.taskProjection(db, { taskId: item.id }));
|
|
5140
|
+
const task = fullProjection.tasks[0] || null;
|
|
5141
|
+
const evaluation = evaluateReviewAutoAccept(task, root);
|
|
5142
|
+
if (!evaluation.eligible) {
|
|
5143
|
+
results.push({ ...evaluation, action: 'queued', task_id: task?.id || item.id || null });
|
|
5144
|
+
continue;
|
|
5145
|
+
}
|
|
5146
|
+
const accepted = acceptReviewTask(taskDb, db, task.id, {
|
|
5147
|
+
actor: REVIEW_AUTO_ACCEPT_ACTOR,
|
|
5148
|
+
proof: evaluation.proof,
|
|
5149
|
+
reward: 1,
|
|
5150
|
+
lesson: String(task.review?.lesson || task.metadata?.latest_agent_lesson || ''),
|
|
5151
|
+
nextTask: String(task.review?.next_task || task.metadata?.latest_agent_next_task || ''),
|
|
5152
|
+
autoAccepted: true,
|
|
5153
|
+
});
|
|
5154
|
+
if (!accepted.ok) {
|
|
5155
|
+
results.push({ ...evaluation, action: 'accept_failed', task_id: task.id, reason: accepted.reason });
|
|
5156
|
+
continue;
|
|
5157
|
+
}
|
|
5158
|
+
stampAutoAcceptMetadata(taskDb, db, task.id, REVIEW_AUTO_ACCEPT_ACTOR, REVIEW_AUTO_ACCEPT_POLICY);
|
|
5159
|
+
refreshCareerXpAfterReview(accepted.reviewed);
|
|
5160
|
+
results.push({
|
|
5161
|
+
...evaluation,
|
|
5162
|
+
action: 'accepted',
|
|
5163
|
+
task_id: task.id,
|
|
5164
|
+
reward: accepted.reviewed.episode.reward.value,
|
|
5165
|
+
});
|
|
5166
|
+
}
|
|
5167
|
+
const acceptedCount = results.filter(row => row.action === 'accepted').length;
|
|
5168
|
+
return {
|
|
5169
|
+
enabled,
|
|
5170
|
+
scanned: certified.length,
|
|
5171
|
+
accepted: acceptedCount,
|
|
5172
|
+
changed: acceptedCount > 0,
|
|
5173
|
+
results,
|
|
5174
|
+
};
|
|
5175
|
+
}
|
|
5176
|
+
|
|
5177
|
+
function autoAcceptedReviewRowsSince(taskDb, db, workspaceRoot, sinceIso, acceptedNowIds = []) {
|
|
5178
|
+
const sinceMs = Date.parse(sinceIso || '');
|
|
5179
|
+
const rows = taskDb.withTaskDisplayRefs(taskDb.listTasks(db, { workspaceRoot }));
|
|
5180
|
+
const acceptedNow = new Set(acceptedNowIds.filter(Boolean));
|
|
5181
|
+
const seen = new Set();
|
|
5182
|
+
const out = [];
|
|
5183
|
+
for (const row of rows) {
|
|
5184
|
+
const metadata = row.metadata && typeof row.metadata === 'object' ? row.metadata : {};
|
|
5185
|
+
const acceptedAt = Date.parse(metadata.auto_accepted_at || metadata.accepted_at || '');
|
|
5186
|
+
const policyMatch = metadata.auto_accept_policy === REVIEW_AUTO_ACCEPT_POLICY
|
|
5187
|
+
|| metadata.auto_accepted_by === REVIEW_AUTO_ACCEPT_ACTOR
|
|
5188
|
+
|| metadata.accepted_by === REVIEW_AUTO_ACCEPT_ACTOR;
|
|
5189
|
+
const isCurrent = acceptedNow.has(row.id);
|
|
5190
|
+
const isSince = Number.isFinite(sinceMs) && Number.isFinite(acceptedAt) && acceptedAt > sinceMs;
|
|
5191
|
+
const firstLook = !Number.isFinite(sinceMs) && Number.isFinite(acceptedAt);
|
|
5192
|
+
if (row.status !== 'done' || !policyMatch || (!isCurrent && !isSince && !firstLook)) continue;
|
|
5193
|
+
if (seen.has(row.id)) continue;
|
|
5194
|
+
seen.add(row.id);
|
|
5195
|
+
out.push({
|
|
5196
|
+
id: row.id,
|
|
5197
|
+
ref: row.display_id || taskRef(row),
|
|
5198
|
+
title: row.title,
|
|
5199
|
+
accepted_at: metadata.auto_accepted_at || metadata.accepted_at || null,
|
|
5200
|
+
});
|
|
5201
|
+
}
|
|
5202
|
+
out.sort((a, b) => String(b.accepted_at || '').localeCompare(String(a.accepted_at || '')));
|
|
5203
|
+
return out;
|
|
5204
|
+
}
|
|
5205
|
+
|
|
5206
|
+
function reviewAutoAcceptRollup(taskDb, db, workspaceRoot, previousState, autoAcceptRun) {
|
|
5207
|
+
const acceptedNowIds = (autoAcceptRun.results || [])
|
|
5208
|
+
.filter(row => row.action === 'accepted')
|
|
5209
|
+
.map(row => row.task_id)
|
|
5210
|
+
.filter(Boolean);
|
|
5211
|
+
const rows = autoAcceptedReviewRowsSince(taskDb, db, workspaceRoot, previousState.last_look_at, acceptedNowIds);
|
|
5212
|
+
return {
|
|
5213
|
+
count: rows.length,
|
|
5214
|
+
items: rows,
|
|
5215
|
+
since: previousState.last_look_at || null,
|
|
5216
|
+
};
|
|
5217
|
+
}
|
|
5218
|
+
|
|
4580
5219
|
function reviewQueueLimit(args, total) {
|
|
4581
5220
|
if (hasFlag(args, '--all')) return total;
|
|
4582
5221
|
const raw = flag(args, '--limit');
|
|
@@ -4595,6 +5234,17 @@ function reviewGroupTextLimit(args, total) {
|
|
|
4595
5234
|
return Number.isFinite(limit) && limit > 0 ? Math.floor(limit) : 10;
|
|
4596
5235
|
}
|
|
4597
5236
|
|
|
5237
|
+
function plainReviewBlockerMessage(item) {
|
|
5238
|
+
const ref = item.display_id || taskRef(item.id);
|
|
5239
|
+
if (item.reason === 'verify_command_not_allowed') {
|
|
5240
|
+
return `${ref} used a check command the system does not allow; rerun with an approved check`;
|
|
5241
|
+
}
|
|
5242
|
+
if (item.reason === 'needs_second_actor_review') {
|
|
5243
|
+
return `${ref} needs a second reviewer before it can land`;
|
|
5244
|
+
}
|
|
5245
|
+
return `${ref} needs another check before it can land`;
|
|
5246
|
+
}
|
|
5247
|
+
|
|
4598
5248
|
// Risk order for human attention: named receipts that are missing/failing (0)
|
|
4599
5249
|
// beat prose-only proofs (1) beat fully validated green evidence (2).
|
|
4600
5250
|
function evidenceRiskRank(evidence) {
|
|
@@ -4772,30 +5422,72 @@ function taskReviewGroups(projection, key) {
|
|
|
4772
5422
|
};
|
|
4773
5423
|
}
|
|
4774
5424
|
|
|
5425
|
+
function taskReviewLandingLines(item) {
|
|
5426
|
+
if (!item?.landing) return [];
|
|
5427
|
+
const clean = value => value
|
|
5428
|
+
? gateForHuman(value, { title: item.title }).text
|
|
5429
|
+
: '';
|
|
5430
|
+
const happened = clean(item.landing.happened);
|
|
5431
|
+
const reason = clean(item.landing.reason);
|
|
5432
|
+
const unperiod = value => value.replace(/\.$/, '');
|
|
5433
|
+
const checked = unperiod(clean(item.landing.checked));
|
|
5434
|
+
const tested = unperiod(clean(item.landing.tested));
|
|
5435
|
+
return [
|
|
5436
|
+
happened ? ` what's new: ${happened}` : '',
|
|
5437
|
+
reason ? ` why it matters: ${reason}` : '',
|
|
5438
|
+
checked ? ` checked: ${checked}${tested && tested !== checked ? `; tested: ${tested}` : ''}.` : '',
|
|
5439
|
+
].filter(Boolean);
|
|
5440
|
+
}
|
|
5441
|
+
|
|
4775
5442
|
function cmdReviews(args) {
|
|
4776
5443
|
const taskDb = getTaskDb();
|
|
4777
5444
|
const db = taskDb.open();
|
|
4778
|
-
|
|
5445
|
+
let { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
5446
|
+
const workspaceRoot = projection.workspace_root || process.cwd();
|
|
5447
|
+
const previousAutoAcceptState = readReviewAutoAcceptState(workspaceRoot);
|
|
5448
|
+
const autoAcceptRun = autoAcceptCertifiedSmallReviews(taskDb, db, projection);
|
|
5449
|
+
if (autoAcceptRun.changed) {
|
|
5450
|
+
({ projection, outPath } = writeDefaultProjection(taskDb, db));
|
|
5451
|
+
}
|
|
5452
|
+
const autoAcceptRollup = reviewAutoAcceptRollup(taskDb, db, workspaceRoot, previousAutoAcceptState, autoAcceptRun);
|
|
5453
|
+
writeReviewAutoAcceptState(workspaceRoot, {
|
|
5454
|
+
...previousAutoAcceptState,
|
|
5455
|
+
last_look_at: new Date().toISOString(),
|
|
5456
|
+
last_count: autoAcceptRollup.count,
|
|
5457
|
+
});
|
|
4779
5458
|
const groupByRaw = flag(args, '--group-by');
|
|
4780
5459
|
if (groupByRaw) {
|
|
4781
5460
|
const key = reviewGroupKey(groupByRaw);
|
|
4782
5461
|
const groups = taskReviewGroups(projection, key);
|
|
4783
5462
|
if (wantsJson(args)) {
|
|
4784
|
-
printJson({
|
|
5463
|
+
printJson({
|
|
5464
|
+
ok: true,
|
|
5465
|
+
action: 'review_groups',
|
|
5466
|
+
projection_path: outPath,
|
|
5467
|
+
autoaccept: {
|
|
5468
|
+
enabled: autoAcceptRun.enabled,
|
|
5469
|
+
accepted_now: autoAcceptRun.accepted,
|
|
5470
|
+
accepted_since_last_look: autoAcceptRollup.count,
|
|
5471
|
+
results: autoAcceptRun.results,
|
|
5472
|
+
},
|
|
5473
|
+
groups,
|
|
5474
|
+
});
|
|
4785
5475
|
return;
|
|
4786
5476
|
}
|
|
4787
|
-
console.log(
|
|
4788
|
-
|
|
5477
|
+
console.log(gateForHuman(`${numberWord(groups.total_certified)} finished things are waiting, grouped by ${key}.`).text);
|
|
5478
|
+
if (autoAcceptRollup.count > 0) {
|
|
5479
|
+
console.log(gateForHuman(`${numberWord(autoAcceptRollup.count)} landed on their own since you last looked.`).text);
|
|
5480
|
+
}
|
|
4789
5481
|
const visibleGroups = groups.groups.slice(0, reviewGroupTextLimit(args, groups.groups.length));
|
|
4790
5482
|
visibleGroups.forEach((g, index) => {
|
|
4791
5483
|
console.log('');
|
|
4792
|
-
console.log(`${index + 1}. ${g.value}
|
|
4793
|
-
g.sample_titles.forEach(title => console.log(` • ${title}`));
|
|
5484
|
+
console.log(`${index + 1}. ${g.value} - ${numberWord(g.count)} task${g.count === 1 ? '' : 's'}`);
|
|
5485
|
+
g.sample_titles.forEach(title => console.log(` • ${gateForHuman(title, { title }).text}`));
|
|
4794
5486
|
console.log(` approve this group: ${g.accept_group_command} --confirm-human-accept --as <you>`);
|
|
4795
5487
|
});
|
|
4796
5488
|
if (visibleGroups.length < groups.groups.length) {
|
|
4797
5489
|
console.log('');
|
|
4798
|
-
console.log(`
|
|
5490
|
+
console.log(`showing ${numberWord(visibleGroups.length)} of ${numberWord(groups.groups.length)} groups; rerun with --all for every group or --limit N to adjust.`);
|
|
4799
5491
|
}
|
|
4800
5492
|
return;
|
|
4801
5493
|
}
|
|
@@ -4806,32 +5498,45 @@ function cmdReviews(args) {
|
|
|
4806
5498
|
ok: true,
|
|
4807
5499
|
action: 'review_queue',
|
|
4808
5500
|
projection_path: outPath,
|
|
5501
|
+
autoaccept: {
|
|
5502
|
+
enabled: autoAcceptRun.enabled,
|
|
5503
|
+
accepted_now: autoAcceptRun.accepted,
|
|
5504
|
+
accepted_since_last_look: autoAcceptRollup.count,
|
|
5505
|
+
results: autoAcceptRun.results,
|
|
5506
|
+
},
|
|
4809
5507
|
queue,
|
|
4810
5508
|
});
|
|
4811
5509
|
return;
|
|
4812
5510
|
}
|
|
4813
|
-
console.log('READY FOR APPROVAL');
|
|
4814
|
-
console.log(`${queue.counts.certified} ready for approval / ${queue.counts.blocking} need one more check / ${queue.counts.review} total waiting`);
|
|
4815
5511
|
const approvalItems = queue.items.filter(item => item.queue_role !== 'blocked');
|
|
4816
5512
|
const blockedItems = queue.items.filter(item => item.queue_role === 'blocked');
|
|
4817
5513
|
if (!approvalItems.length && !blockedItems.length) {
|
|
4818
|
-
console.log('
|
|
5514
|
+
console.log('nothing is waiting on you. everything that finished has already landed.');
|
|
5515
|
+
if (autoAcceptRollup.count > 0) {
|
|
5516
|
+
console.log(gateForHuman(`${numberWord(autoAcceptRollup.count)} landed on their own since you last looked.`).text);
|
|
5517
|
+
}
|
|
4819
5518
|
return;
|
|
4820
5519
|
}
|
|
5520
|
+
if (queue.counts.certified > 0) {
|
|
5521
|
+
const header = queue.counts.certified === 1
|
|
5522
|
+
? 'one finished thing is waiting for your ok. it passed both checks.'
|
|
5523
|
+
: `${numberWord(queue.counts.certified)} finished things are waiting for your ok. all of them passed both checks.`;
|
|
5524
|
+
console.log(gateForHuman(header).text);
|
|
5525
|
+
}
|
|
5526
|
+
if (queue.counts.blocking > 0) {
|
|
5527
|
+
console.log(gateForHuman(queue.counts.blocking === 1
|
|
5528
|
+
? 'one more is almost ready; a second check is still running.'
|
|
5529
|
+
: `${numberWord(queue.counts.blocking)} more are almost ready; second checks are still running.`).text);
|
|
5530
|
+
}
|
|
5531
|
+
if (autoAcceptRollup.count > 0) {
|
|
5532
|
+
console.log(gateForHuman(`${numberWord(autoAcceptRollup.count)} landed on their own since you last looked.`).text);
|
|
5533
|
+
}
|
|
4821
5534
|
approvalItems.forEach((item, index) => {
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
const badge = item.evidence?.all_passing ? ' [evidence:passing]' : '';
|
|
4825
|
-
console.log('');
|
|
4826
|
-
console.log(`${index + 1}. ${item.display_id || taskRef(item.id)}${tag}${passes}: ${item.title}${badge}`);
|
|
5535
|
+
if (index > 0) console.log('');
|
|
5536
|
+
console.log(`${index + 1}. ${gateForHuman(item.title, { title: item.title }).text}`);
|
|
4827
5537
|
if (item.landing) {
|
|
4828
|
-
console.log(
|
|
4829
|
-
if (item.
|
|
4830
|
-
if (item.landing.reason) console.log(` Why it matters: ${item.landing.reason}`);
|
|
4831
|
-
if (item.landing.checked) console.log(` How I checked: ${item.landing.checked}`);
|
|
4832
|
-
if (item.landing.tested) console.log(` What I tested: ${item.landing.tested}`);
|
|
4833
|
-
if (item.result?.saved) console.log(` Saved: ${item.result.saved}`);
|
|
4834
|
-
if (item.landing.decision) console.log(` Decision: ${item.landing.decision}`);
|
|
5538
|
+
taskReviewLandingLines(item).forEach(line => console.log(line));
|
|
5539
|
+
if (verbose && item.result?.saved) console.log(` saved: ${item.result.saved}`);
|
|
4835
5540
|
}
|
|
4836
5541
|
if (verbose && item.proof) console.log(` details: ${item.proof}`);
|
|
4837
5542
|
if (verbose && item.evidence) {
|
|
@@ -4843,18 +5548,23 @@ function cmdReviews(args) {
|
|
|
4843
5548
|
item.evidence.missing.forEach((missingPath) => console.log(` receipt: ${missingPath} MISSING`));
|
|
4844
5549
|
}
|
|
4845
5550
|
if (verbose && item.review_chat_command) console.log(` /codex: ${item.review_chat_command}`);
|
|
4846
|
-
if (item.
|
|
4847
|
-
|
|
4848
|
-
else if (item.blocked_accept_reason)
|
|
4849
|
-
|
|
5551
|
+
if (item.accept_command) {
|
|
5552
|
+
console.log(` say yes: atris task accept ${item.display_id || taskRef(item.id)}`);
|
|
5553
|
+
} else if (item.blocked_accept_reason) {
|
|
5554
|
+
console.log(` approve: blocked (${item.blocked_accept_reason})`);
|
|
5555
|
+
console.log(` rework: ${item.revise_command}`);
|
|
5556
|
+
}
|
|
4850
5557
|
});
|
|
4851
|
-
|
|
4852
|
-
console.log('');
|
|
4853
|
-
console.log(
|
|
5558
|
+
if (blockedItems.length > 0) {
|
|
5559
|
+
if (approvalItems.length > 0) console.log('');
|
|
5560
|
+
console.log('still being checked:');
|
|
5561
|
+
for (const item of blockedItems) {
|
|
5562
|
+
console.log(`${plainReviewBlockerMessage(item)}; next: ${item.next_command}`);
|
|
5563
|
+
}
|
|
4854
5564
|
}
|
|
4855
5565
|
if (queue.counts.shown < queue.counts.certified) {
|
|
4856
5566
|
console.log('');
|
|
4857
|
-
console.log(`
|
|
5567
|
+
console.log(`showing ${numberWord(queue.counts.shown)} of ${numberWord(queue.counts.certified)}; rerun with --all for every row or --verbose for proof details.`);
|
|
4858
5568
|
}
|
|
4859
5569
|
}
|
|
4860
5570
|
|
|
@@ -4969,6 +5679,11 @@ function cmdAcceptGroup(args) {
|
|
|
4969
5679
|
const isVerified = verifiedIds.has(task.id);
|
|
4970
5680
|
const proof = String(task.review?.proof || task.metadata?.latest_agent_proof || '').trim()
|
|
4971
5681
|
|| `Accepted via group spot-check (${groupLabel}); human ${actor} verified ${verifiedIds.size}/${group.length}.`;
|
|
5682
|
+
const missionXpIssue = missionXpEndToEndProofIssue(task, proof, task.workspace_root || taskDb.workspaceRoot());
|
|
5683
|
+
if (missionXpIssue) {
|
|
5684
|
+
accepted.push({ id: task.id, ok: false, reason: MISSION_XP_END_TO_END_REASON, detail: missionXpIssue });
|
|
5685
|
+
continue;
|
|
5686
|
+
}
|
|
4972
5687
|
const done = taskDb.doneTask(db, {
|
|
4973
5688
|
id: task.id,
|
|
4974
5689
|
status: 'done',
|
|
@@ -5218,7 +5933,7 @@ function delegateHandoff(task, owner, via, tag) {
|
|
|
5218
5933
|
return handoff;
|
|
5219
5934
|
}
|
|
5220
5935
|
|
|
5221
|
-
function
|
|
5936
|
+
function delegateTask(args, options = {}) {
|
|
5222
5937
|
const pos = positional(args);
|
|
5223
5938
|
const title = pos.join(' ').trim();
|
|
5224
5939
|
if (!title) {
|
|
@@ -5236,7 +5951,7 @@ function cmdDelegate(args) {
|
|
|
5236
5951
|
const taskDb = getTaskDb();
|
|
5237
5952
|
const db = taskDb.open();
|
|
5238
5953
|
const ws = taskDb.workspaceRoot();
|
|
5239
|
-
const operatorTitleWarning = warnIfTaskTitleNeedsOperatorWhy(title);
|
|
5954
|
+
const operatorTitleWarning = warnIfTaskTitleNeedsOperatorWhy(title, { print: options.warnOperatorTitle !== false });
|
|
5240
5955
|
const ownerResolution = resolveFunctionalTaskOwner({
|
|
5241
5956
|
requestedOwner: requestedOwner && requestedOwner !== true ? requestedOwner : null,
|
|
5242
5957
|
title,
|
|
@@ -5281,34 +5996,69 @@ function cmdDelegate(args) {
|
|
|
5281
5996
|
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
5282
5997
|
const task = compactTaskFromProjection(projection, result.id);
|
|
5283
5998
|
const handoff = delegateHandoff(task, owner, via, typeof tag === 'string' ? tag : null);
|
|
5999
|
+
return {
|
|
6000
|
+
ok: true,
|
|
6001
|
+
action: 'delegated',
|
|
6002
|
+
task_id: result.id,
|
|
6003
|
+
inserted: result.inserted !== false,
|
|
6004
|
+
owner,
|
|
6005
|
+
owner_resolution: ownerResolution,
|
|
6006
|
+
executed_by: executedBy || null,
|
|
6007
|
+
via,
|
|
6008
|
+
tag: typeof tag === 'string' ? tag : null,
|
|
6009
|
+
handoff,
|
|
6010
|
+
proposed_member_command: metadata.proposed_member_command || null,
|
|
6011
|
+
operator_title_warning: operatorTitleWarning,
|
|
6012
|
+
projection_path: outPath,
|
|
6013
|
+
task,
|
|
6014
|
+
};
|
|
6015
|
+
}
|
|
6016
|
+
|
|
6017
|
+
function cmdDelegate(args) {
|
|
6018
|
+
const payload = delegateTask(args);
|
|
5284
6019
|
if (wantsJson(args)) {
|
|
5285
|
-
printJson(
|
|
5286
|
-
ok: true,
|
|
5287
|
-
action: 'delegated',
|
|
5288
|
-
task_id: result.id,
|
|
5289
|
-
inserted: result.inserted !== false,
|
|
5290
|
-
owner,
|
|
5291
|
-
owner_resolution: ownerResolution,
|
|
5292
|
-
executed_by: executedBy || null,
|
|
5293
|
-
via,
|
|
5294
|
-
handoff,
|
|
5295
|
-
operator_title_warning: operatorTitleWarning,
|
|
5296
|
-
projection_path: outPath,
|
|
5297
|
-
task,
|
|
5298
|
-
});
|
|
6020
|
+
printJson(payload);
|
|
5299
6021
|
return;
|
|
5300
6022
|
}
|
|
5301
|
-
const tagText = tag
|
|
5302
|
-
console.log(`delegated ${taskRef(task)} -> ${owner}${tagText} via=${via}`);
|
|
5303
|
-
if (
|
|
5304
|
-
if (
|
|
5305
|
-
console.log(`claim: ${handoff.command}`);
|
|
5306
|
-
if (handoff.swarlo) console.log(`swarlo: ${handoff.swarlo.channel}/${handoff.swarlo.action}`);
|
|
6023
|
+
const tagText = payload.tag ? ` #${payload.tag}` : '';
|
|
6024
|
+
console.log(`delegated ${taskRef(payload.task)} -> ${payload.owner}${tagText} via=${payload.via}`);
|
|
6025
|
+
if (payload.executed_by) console.log(`executed_by: ${payload.executed_by}`);
|
|
6026
|
+
if (payload.proposed_member_command) console.log(`member: ${payload.proposed_member_command}`);
|
|
6027
|
+
console.log(`claim: ${payload.handoff.command}`);
|
|
6028
|
+
if (payload.handoff.swarlo) console.log(`swarlo: ${payload.handoff.swarlo.channel}/${payload.handoff.swarlo.action}`);
|
|
5307
6029
|
}
|
|
5308
6030
|
|
|
5309
6031
|
// Failed tasks older than this stop earning a daily owner-group row;
|
|
5310
6032
|
// they collapse into one stale summary line instead (target state = clean day view).
|
|
5311
6033
|
const DAY_STALE_FAILED_MS = 7 * 24 * 60 * 60 * 1000;
|
|
6034
|
+
const DAY_TEXT_TASK_LIMIT = 8;
|
|
6035
|
+
|
|
6036
|
+
function taskDayTextGroups(groups, { full = false } = {}) {
|
|
6037
|
+
if (full) return { groups, hiddenTasks: 0, hiddenOwners: 0 };
|
|
6038
|
+
const statusOrder = { claimed: 0, open: 1, review: 2, failed: 3, done: 4 };
|
|
6039
|
+
const ranked = groups
|
|
6040
|
+
.flatMap((group) => group.tasks)
|
|
6041
|
+
.sort((a, b) => ((statusOrder[a.status] ?? 5) - (statusOrder[b.status] ?? 5))
|
|
6042
|
+
|| ((b.updated_at || 0) - (a.updated_at || 0)));
|
|
6043
|
+
const selected = new Set(ranked.slice(0, DAY_TEXT_TASK_LIMIT));
|
|
6044
|
+
const visibleGroups = groups
|
|
6045
|
+
.map((group) => ({ ...group, tasks: group.tasks.filter((task) => selected.has(task)) }))
|
|
6046
|
+
.filter((group) => group.tasks.length > 0);
|
|
6047
|
+
return {
|
|
6048
|
+
groups: visibleGroups,
|
|
6049
|
+
hiddenTasks: Math.max(0, ranked.length - selected.size),
|
|
6050
|
+
hiddenOwners: Math.max(0, groups.length - visibleGroups.length),
|
|
6051
|
+
};
|
|
6052
|
+
}
|
|
6053
|
+
|
|
6054
|
+
function taskDayTitle(title, maxLength = 120) {
|
|
6055
|
+
const text = String(title || '').replace(/\s*[—–]\s*/g, ' - ').replace(/\s+/g, ' ').trim();
|
|
6056
|
+
if (text.length <= maxLength) return text;
|
|
6057
|
+
const sentence = text.slice(0, maxLength + 1).match(/^(.{48,}?[.!?])(?:\s|$)/);
|
|
6058
|
+
if (sentence) return sentence[1];
|
|
6059
|
+
const clipped = text.slice(0, maxLength).replace(/\s+\S*$/, '').trim();
|
|
6060
|
+
return `${clipped || text.slice(0, maxLength).trim()}...`;
|
|
6061
|
+
}
|
|
5312
6062
|
|
|
5313
6063
|
function taskDayGroups(tasks, { now = Date.now() } = {}) {
|
|
5314
6064
|
const active = tasks.filter(task => task.status !== 'done');
|
|
@@ -5343,6 +6093,7 @@ function taskDayGroups(tasks, { now = Date.now() } = {}) {
|
|
|
5343
6093
|
|
|
5344
6094
|
function cmdDay(args) {
|
|
5345
6095
|
const all = hasFlag(args, '--all');
|
|
6096
|
+
const full = hasFlag(args, '--full');
|
|
5346
6097
|
const everywhere = taskScopeEverywhere(args);
|
|
5347
6098
|
const taskDb = getTaskDb();
|
|
5348
6099
|
const db = taskDb.open();
|
|
@@ -5373,24 +6124,30 @@ function cmdDay(args) {
|
|
|
5373
6124
|
});
|
|
5374
6125
|
return;
|
|
5375
6126
|
}
|
|
5376
|
-
|
|
6127
|
+
const textView = taskDayTextGroups(groups, { full });
|
|
6128
|
+
console.log('task day');
|
|
5377
6129
|
const failedText = counts.failed > 0 ? ` / failed ${counts.failed}` : '';
|
|
5378
6130
|
console.log(`${date} active ${counts.active} / owners ${counts.owners} / review ${counts.review}${failedText}`);
|
|
5379
6131
|
console.log('');
|
|
5380
6132
|
if (!groups.length) {
|
|
5381
6133
|
console.log('clear no active tasks');
|
|
5382
6134
|
}
|
|
5383
|
-
for (const group of groups) {
|
|
6135
|
+
for (const group of textView.groups) {
|
|
5384
6136
|
console.log(`${group.owner}`);
|
|
5385
|
-
for (const task of group.tasks
|
|
6137
|
+
for (const task of group.tasks) {
|
|
5386
6138
|
const tag = task.tag ? ` #${task.tag}` : '';
|
|
5387
6139
|
const claim = task.claimed_by ? ` @${task.claimed_by}` : '';
|
|
5388
|
-
console.log(` ${task.status.padEnd(7)} ${taskRef(task)}${claim}${tag} ${task.title}`);
|
|
6140
|
+
console.log(` ${task.status.padEnd(7)} ${taskRef(task)}${claim}${tag} ${taskDayTitle(task.title)}`);
|
|
5389
6141
|
}
|
|
5390
6142
|
}
|
|
6143
|
+
if (textView.hiddenTasks > 0) {
|
|
6144
|
+
console.log('');
|
|
6145
|
+
const ownerText = textView.hiddenOwners > 0 ? `, ${textView.hiddenOwners} owners not shown` : '';
|
|
6146
|
+
console.log(`more ${textView.hiddenTasks} active rows hidden${ownerText} - atris task day --full`);
|
|
6147
|
+
}
|
|
5391
6148
|
if (staleFailed.length) {
|
|
5392
6149
|
console.log('');
|
|
5393
|
-
console.log(`stale ${staleFailed.length} failed >7d hidden
|
|
6150
|
+
console.log(`stale ${staleFailed.length} failed >7d hidden - atris task list --status failed`);
|
|
5394
6151
|
}
|
|
5395
6152
|
console.log('');
|
|
5396
6153
|
console.log('add: atris task delegate "..." --to task-planner --tag tasks');
|
|
@@ -5514,7 +6271,11 @@ function cmdClaim(args) {
|
|
|
5514
6271
|
});
|
|
5515
6272
|
return;
|
|
5516
6273
|
}
|
|
5517
|
-
|
|
6274
|
+
const ref = taskRef(compactTaskFromProjection(projection, taskId));
|
|
6275
|
+
console.log(`claimed ${ref} as ${owner}`);
|
|
6276
|
+
console.log(`Next: make the change, then run: atris task ready ${ref} --verify "git diff --check" --result "<who can do what now and why>" --landing "<what someone can do now>"`);
|
|
6277
|
+
console.log('Use a different verifier only if autoland can rerun it, such as `node --test <file>`.');
|
|
6278
|
+
console.log('Then: atris autoland tick');
|
|
5518
6279
|
} else {
|
|
5519
6280
|
const recoveryCommand = result.reason === 'already_claimed' && result.claimed_by
|
|
5520
6281
|
? `atris task release ${id} --as ${result.claimed_by}`
|
|
@@ -5740,7 +6501,7 @@ function cmdNext(args) {
|
|
|
5740
6501
|
const continueWorkCommand = handoff.next_action === 'continue_work'
|
|
5741
6502
|
? continueWorkCommandForTask(reviewTask, { owner })
|
|
5742
6503
|
: null;
|
|
5743
|
-
const nextAgentAction = handoff.next_action === 'human_accept_waiting'
|
|
6504
|
+
const nextAgentAction = handoff.next_action === 'human_accept_waiting' && !scoped
|
|
5744
6505
|
? readEndgameAgentAction(taskDb.workspaceRoot(), owner, { tasks: projection.tasks || [] })
|
|
5745
6506
|
: null;
|
|
5746
6507
|
if (hasFlag(args, '--create-next')) {
|
|
@@ -5961,6 +6722,58 @@ function cmdNote(args) {
|
|
|
5961
6722
|
console.log(`noted ${taskRef(compactTaskFromProjection(projection, taskId))} v${result.event.version}`);
|
|
5962
6723
|
}
|
|
5963
6724
|
|
|
6725
|
+
function cmdRetitle(args) {
|
|
6726
|
+
const pos = positional(args);
|
|
6727
|
+
const id = pos[0];
|
|
6728
|
+
const title = pos.slice(1).join(' ').trim();
|
|
6729
|
+
if (!id) failTask('atris task retitle', 'missing_id', 'task id required');
|
|
6730
|
+
if (!title) failTask('atris task retitle', 'missing_title', 'new title required');
|
|
6731
|
+
warnIfTaskTitleNeedsOperatorWhy(title);
|
|
6732
|
+
|
|
6733
|
+
const actor = flag(args, '--as') || DEFAULT_OWNER;
|
|
6734
|
+
const taskDb = getTaskDb();
|
|
6735
|
+
const db = taskDb.open();
|
|
6736
|
+
const taskId = requireTaskId(taskDb, db, id, 'atris task retitle');
|
|
6737
|
+
const current = taskDb.getTask(db, taskId);
|
|
6738
|
+
const oldTitle = String(current.title || '');
|
|
6739
|
+
const now = Math.max(Date.now(), Number(current.updated_at || 0) + 1);
|
|
6740
|
+
const updated = db.prepare(`
|
|
6741
|
+
UPDATE tasks
|
|
6742
|
+
SET title = ?,
|
|
6743
|
+
updated_at = ?
|
|
6744
|
+
WHERE id = ?
|
|
6745
|
+
AND updated_at = ?
|
|
6746
|
+
`).run(title, now, taskId, current.updated_at);
|
|
6747
|
+
if (updated.changes !== 1) {
|
|
6748
|
+
failTask('atris task retitle', 'stale_task_state', 'retitle failed: stale task state', 1);
|
|
6749
|
+
}
|
|
6750
|
+
const history = taskDb.noteTask(db, {
|
|
6751
|
+
id: taskId,
|
|
6752
|
+
actor: String(actor),
|
|
6753
|
+
content: `previous title: ${oldTitle}`,
|
|
6754
|
+
});
|
|
6755
|
+
if (!history.noted) {
|
|
6756
|
+
failTask('atris task retitle', history.reason || 'history_failed', `retitle history failed: ${history.reason || 'unknown'}`, 1);
|
|
6757
|
+
}
|
|
6758
|
+
|
|
6759
|
+
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
6760
|
+
const task = compactTaskFromProjection(projection, taskId);
|
|
6761
|
+
if (wantsJson(args)) {
|
|
6762
|
+
printJson({
|
|
6763
|
+
ok: true,
|
|
6764
|
+
action: 'retitled',
|
|
6765
|
+
task_id: taskId,
|
|
6766
|
+
old_title: oldTitle,
|
|
6767
|
+
title,
|
|
6768
|
+
version: history.event.version,
|
|
6769
|
+
projection_path: outPath,
|
|
6770
|
+
task,
|
|
6771
|
+
});
|
|
6772
|
+
return;
|
|
6773
|
+
}
|
|
6774
|
+
console.log(`retitled ${taskRef(task)}: ${title}`);
|
|
6775
|
+
}
|
|
6776
|
+
|
|
5964
6777
|
// Collect EVERY value for a repeatable flag (flag() only returns the first),
|
|
5965
6778
|
// so `--add a --add b` and `--add a,b` both work.
|
|
5966
6779
|
function collectFlagValues(args, name) {
|
|
@@ -6324,7 +7137,7 @@ function cmdShow(args) {
|
|
|
6324
7137
|
const owner = task.claimed_by ? ` / ${task.claimed_by}` : '';
|
|
6325
7138
|
const tag = task.tag ? ` #${task.tag}` : '';
|
|
6326
7139
|
const statusLabel = task.status === 'review'
|
|
6327
|
-
? '
|
|
7140
|
+
? 'ready for approval'
|
|
6328
7141
|
: task.status === 'done'
|
|
6329
7142
|
? 'DONE'
|
|
6330
7143
|
: task.status.toUpperCase();
|
|
@@ -7084,9 +7897,38 @@ function cmdResult(args) {
|
|
|
7084
7897
|
const pos = positional(args);
|
|
7085
7898
|
const id = pos[0];
|
|
7086
7899
|
if (!id) failTask('atris task result', 'missing_id', 'id required');
|
|
7900
|
+
const sentence = pos.slice(1).join(' ').trim();
|
|
7901
|
+
if (sentence) {
|
|
7902
|
+
const resultSentence = requireResultSentence('atris task result', sentence);
|
|
7903
|
+
const actor = String(flag(args, '--as') || DEFAULT_OWNER);
|
|
7904
|
+
const taskDb = getTaskDb();
|
|
7905
|
+
const db = taskDb.open();
|
|
7906
|
+
const taskId = requireTaskId(taskDb, db, id, 'atris task result');
|
|
7907
|
+
const saved = taskDb.setTaskResult(db, {
|
|
7908
|
+
id: taskId,
|
|
7909
|
+
actor,
|
|
7910
|
+
result: resultSentence,
|
|
7911
|
+
});
|
|
7912
|
+
if (!saved.saved) failTask('atris task result', saved.reason || 'result_failed', `result failed: ${saved.reason || 'result_failed'}`, 1);
|
|
7913
|
+
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
7914
|
+
if (wantsJson(args)) {
|
|
7915
|
+
printJson({
|
|
7916
|
+
ok: true,
|
|
7917
|
+
action: 'result',
|
|
7918
|
+
task_id: taskId,
|
|
7919
|
+
version: saved.event.version,
|
|
7920
|
+
result: resultSentence,
|
|
7921
|
+
projection_path: outPath,
|
|
7922
|
+
task: compactTaskFromProjection(projection, taskId),
|
|
7923
|
+
});
|
|
7924
|
+
return;
|
|
7925
|
+
}
|
|
7926
|
+
console.log(`result saved ${taskRef(compactTaskFromProjection(projection, taskId))}: ${resultSentence}`);
|
|
7927
|
+
return;
|
|
7928
|
+
}
|
|
7087
7929
|
const fields = {
|
|
7088
7930
|
purpose: textFlag(args, ['--purpose', '--goal', '--objective']),
|
|
7089
|
-
changed: textFlag(args, ['--changed', '--
|
|
7931
|
+
changed: textFlag(args, ['--changed', '--done']),
|
|
7090
7932
|
checked: textFlag(args, ['--checked', '--check', '--verified']),
|
|
7091
7933
|
passed: textFlag(args, ['--passed', '--pass']),
|
|
7092
7934
|
failed: textFlag(args, ['--failed', '--fail']),
|
|
@@ -7196,7 +8038,7 @@ function taskPageActions(task, { reviewer = 'codex-review', hasExistingReviewFol
|
|
|
7196
8038
|
note_command: `atris task note ${ref} "<context>" --as ${owner}`,
|
|
7197
8039
|
plan_command: `atris task plan ${ref} --goal ${taskCommandQuote(goal)} --exit "<exit condition>" --proof-needed "<verification command>" --first-move "<first move>"`,
|
|
7198
8040
|
do_command: `atris task do ${ref} --as ${owner} --first-move "<first move>"`,
|
|
7199
|
-
ready_command: `atris task ready ${ref} --as ${owner} --proof "<specific proof command/result>" --happened "<what happened>" --checked "<how you know>" --tested "<what you ran or inspected>" --decision "<accept/rework guidance>"`,
|
|
8041
|
+
ready_command: `atris task ready ${ref} --as ${owner} --proof "<specific proof command/result>" --result "<one day-one PM sentence>" --happened "<what happened>" --checked "<how you know>" --tested "<what you ran or inspected>" --decision "<accept/rework guidance>"`,
|
|
7200
8042
|
review_command: `atris task review ${ref} --reward 0 --as ${actor} --proof "<specific proof command/result>" --verify "<safe verifier command>"`,
|
|
7201
8043
|
};
|
|
7202
8044
|
if (task && task.status === 'review') {
|
|
@@ -7632,9 +8474,14 @@ function runTaskStep(taskDb, db, taskId, options = {}) {
|
|
|
7632
8474
|
if (proofIssue) {
|
|
7633
8475
|
throw taskStepError(proof ? 'weak_proof' : 'proof_required', `meaningful proof required: ${proofIssue}`, { status: 400, exitCode: 2, page: actionPage });
|
|
7634
8476
|
}
|
|
8477
|
+
const missionXpIssue = missionXpEndToEndProofIssue(task, proof, task.workspace_root || process.cwd());
|
|
8478
|
+
if (missionXpIssue) {
|
|
8479
|
+
throw taskStepError(MISSION_XP_END_TO_END_REASON, missionXpIssue, { status: 409, exitCode: 1, page: actionPage });
|
|
8480
|
+
}
|
|
7635
8481
|
const lesson = String(options.lesson || '');
|
|
7636
8482
|
const nextTask = String(options.nextTask || '');
|
|
7637
8483
|
const resultTrace = buildAutomaticResultTrace(taskDb, db, taskId, { actor, proof });
|
|
8484
|
+
const missionResult = missionReceiptResultForProof(task, proof, task.workspace_root || process.cwd());
|
|
7638
8485
|
const ready = taskDb.readyTask(db, {
|
|
7639
8486
|
id: taskId,
|
|
7640
8487
|
actor,
|
|
@@ -7642,6 +8489,8 @@ function runTaskStep(taskDb, db, taskId, options = {}) {
|
|
|
7642
8489
|
lesson,
|
|
7643
8490
|
nextTask,
|
|
7644
8491
|
resultTrace: resultTrace && resultTrace.trace,
|
|
8492
|
+
result: missionResult ? missionResult.changed : undefined,
|
|
8493
|
+
reason: missionResult ? missionResult.reason : undefined,
|
|
7645
8494
|
});
|
|
7646
8495
|
if (!ready.ready) taskStepFailure('atris task step', ready, actionPage);
|
|
7647
8496
|
task = taskDetail(taskDb, db, taskId) || task;
|
|
@@ -7651,7 +8500,10 @@ function runTaskStep(taskDb, db, taskId, options = {}) {
|
|
|
7651
8500
|
} else if (current === 'review' && task.status === 'review') {
|
|
7652
8501
|
const handoffState = reviewHandoffForTask(task, { suppressExistingFollowUp: true });
|
|
7653
8502
|
if (handoffState && handoffState.next_action === PROOF_BOUNDARY_BLOCKED_ACTION) {
|
|
7654
|
-
|
|
8503
|
+
const detail = handoffState.reason === MISSION_XP_END_TO_END_REASON
|
|
8504
|
+
? `atris task step: ${MISSION_XP_END_TO_END_DETAIL}`
|
|
8505
|
+
: 'atris task step: Review proof cites an open/draft/unmerged PR boundary; revise the row before further stepping';
|
|
8506
|
+
throw taskStepError(PROOF_BOUNDARY_BLOCKED_REASON, detail, { status: 409, exitCode: 1, page: actionPage });
|
|
7655
8507
|
}
|
|
7656
8508
|
if (handoffState && (handoffState.next_action === 'continue_work' || handoffState.next_action === 'human_accept_waiting')) {
|
|
7657
8509
|
const reason = handoffState.next_action === 'continue_work'
|
|
@@ -7776,9 +8628,13 @@ function runCurrentTaskStep(taskDb, db, { owner = DEFAULT_OWNER, reviewer = 'cod
|
|
|
7776
8628
|
throw error;
|
|
7777
8629
|
}
|
|
7778
8630
|
if (nextActionKey === PROOF_BOUNDARY_BLOCKED_ACTION) {
|
|
8631
|
+
const boundaryReason = current.page?.review?.handoff?.reason || current.selected?.review?.handoff?.reason || '';
|
|
8632
|
+
const detail = boundaryReason === MISSION_XP_END_TO_END_REASON
|
|
8633
|
+
? `atris task current-step: ${MISSION_XP_END_TO_END_DETAIL}`
|
|
8634
|
+
: 'atris task current-step: selected Review row has stale/open/draft/unmerged PR proof; revise it instead of accepting or auto-stepping';
|
|
7779
8635
|
const error = taskStepError(
|
|
7780
8636
|
PROOF_BOUNDARY_BLOCKED_REASON,
|
|
7781
|
-
|
|
8637
|
+
detail,
|
|
7782
8638
|
{
|
|
7783
8639
|
status: 409,
|
|
7784
8640
|
exitCode: 1,
|
|
@@ -7985,7 +8841,7 @@ function cmdDone(args) {
|
|
|
7985
8841
|
if (agentProofOnlyMode() && !failed) {
|
|
7986
8842
|
failAgentProofOnly(
|
|
7987
8843
|
'atris task done',
|
|
7988
|
-
'Agent proof-only mode cannot mark tasks done. Use `atris task ready <id> --proof "..."` or `atris task review <id> --reward 0 --proof "..."`.',
|
|
8844
|
+
'Agent proof-only mode cannot mark tasks done. Use `atris task ready <id> --proof "..." --result "<day-one PM sentence>"` or `atris task review <id> --reward 0 --proof "..."`.',
|
|
7989
8845
|
);
|
|
7990
8846
|
}
|
|
7991
8847
|
const canComplete = beforeTask && (beforeTask.status === 'open' || beforeTask.status === 'claimed');
|
|
@@ -8067,13 +8923,14 @@ function cmdFinish(args) {
|
|
|
8067
8923
|
if (agentProofOnlyMode() && !failed) {
|
|
8068
8924
|
failAgentProofOnly(
|
|
8069
8925
|
'atris task finish',
|
|
8070
|
-
'Agent proof-only mode cannot finish tasks. Use `atris task ready <id> --proof "..."` or `atris task review <id> --reward 0 --proof "..."`.',
|
|
8926
|
+
'Agent proof-only mode cannot finish tasks. Use `atris task ready <id> --proof "..." --result "<day-one PM sentence>"` or `atris task review <id> --reward 0 --proof "..."`.',
|
|
8071
8927
|
);
|
|
8072
8928
|
}
|
|
8073
8929
|
const canComplete = currentTask && (currentTask.status === 'open' || currentTask.status === 'claimed');
|
|
8074
8930
|
if (canComplete) {
|
|
8075
8931
|
if (!failed || hasReview) requireMeaningfulTaskProof('atris task finish', proof);
|
|
8076
8932
|
else if (proof) requireMeaningfulTaskProof('atris task finish', proof);
|
|
8933
|
+
if (!failed && hasReview) requireExplicitLandingDayOnePm('atris task finish', landing, currentTask.title);
|
|
8077
8934
|
}
|
|
8078
8935
|
const done = taskDb.doneTask(db, {
|
|
8079
8936
|
id: taskId,
|
|
@@ -8204,6 +9061,86 @@ function cmdArchive(args) {
|
|
|
8204
9061
|
}
|
|
8205
9062
|
}
|
|
8206
9063
|
|
|
9064
|
+
function taskCompletionTime(row) {
|
|
9065
|
+
const doneAt = Number(row && row.done_at);
|
|
9066
|
+
if (Number.isFinite(doneAt) && doneAt > 0) return doneAt;
|
|
9067
|
+
const acceptedAt = Date.parse(String(row && row.metadata && row.metadata.accepted_at || ''));
|
|
9068
|
+
if (Number.isFinite(acceptedAt)) return acceptedAt;
|
|
9069
|
+
return Number(row && (row.updated_at || row.created_at) || 0);
|
|
9070
|
+
}
|
|
9071
|
+
|
|
9072
|
+
function cmdClearDone(args) {
|
|
9073
|
+
const beforeRaw = flag(args, '--before');
|
|
9074
|
+
let beforeDays = null;
|
|
9075
|
+
if (beforeRaw !== null) {
|
|
9076
|
+
beforeDays = Number(beforeRaw);
|
|
9077
|
+
if (beforeRaw === true || !Number.isFinite(beforeDays) || beforeDays < 0) {
|
|
9078
|
+
failTask('atris task clear-done', 'invalid_before', '--before requires a non-negative number of days');
|
|
9079
|
+
}
|
|
9080
|
+
}
|
|
9081
|
+
|
|
9082
|
+
const dryRun = hasFlag(args, '--dry-run');
|
|
9083
|
+
const taskDb = getTaskDb();
|
|
9084
|
+
const db = taskDb.open();
|
|
9085
|
+
const workspaceRoot = taskDb.workspaceRoot();
|
|
9086
|
+
const cutoff = beforeDays === null ? null : Date.now() - (beforeDays * 24 * 60 * 60 * 1000);
|
|
9087
|
+
const candidates = taskDb.listTasks(db, { workspaceRoot, status: 'done', limit: null })
|
|
9088
|
+
.filter(row => cutoff === null || taskCompletionTime(row) < cutoff)
|
|
9089
|
+
.sort((a, b) => taskCompletionTime(a) - taskCompletionTime(b) || String(a.id).localeCompare(String(b.id)));
|
|
9090
|
+
const sample = candidates.slice(0, 5).map(row => ({
|
|
9091
|
+
task_id: row.id,
|
|
9092
|
+
title: row.title,
|
|
9093
|
+
completed_at: new Date(taskCompletionTime(row)).toISOString(),
|
|
9094
|
+
}));
|
|
9095
|
+
|
|
9096
|
+
if (dryRun) {
|
|
9097
|
+
if (wantsJson(args)) {
|
|
9098
|
+
printJson({
|
|
9099
|
+
ok: true,
|
|
9100
|
+
action: 'clear-done',
|
|
9101
|
+
dry_run: true,
|
|
9102
|
+
before_days: beforeDays,
|
|
9103
|
+
count: candidates.length,
|
|
9104
|
+
sample,
|
|
9105
|
+
});
|
|
9106
|
+
return;
|
|
9107
|
+
}
|
|
9108
|
+
console.log(`clear-done dry-run: ${candidates.length} completed task(s) would be archived.`);
|
|
9109
|
+
for (const row of sample) console.log(` - ${row.title}`);
|
|
9110
|
+
if (candidates.length > sample.length) console.log(` ...and ${candidates.length - sample.length} more`);
|
|
9111
|
+
return;
|
|
9112
|
+
}
|
|
9113
|
+
|
|
9114
|
+
const reason = 'cleared by clear-done sweep';
|
|
9115
|
+
for (const row of candidates) {
|
|
9116
|
+
const result = taskDb.archiveTask(db, {
|
|
9117
|
+
id: row.id,
|
|
9118
|
+
actor: String(flag(args, '--as') || DEFAULT_OWNER),
|
|
9119
|
+
reason,
|
|
9120
|
+
fromDone: true,
|
|
9121
|
+
});
|
|
9122
|
+
if (!result.archived) {
|
|
9123
|
+
failTask('atris task clear-done', result.reason, `clear-done failed: ${row.id} ${result.reason}`, 1);
|
|
9124
|
+
}
|
|
9125
|
+
}
|
|
9126
|
+
const { outPath } = writeDefaultProjection(taskDb, db);
|
|
9127
|
+
|
|
9128
|
+
if (wantsJson(args)) {
|
|
9129
|
+
printJson({
|
|
9130
|
+
ok: true,
|
|
9131
|
+
action: 'clear-done',
|
|
9132
|
+
dry_run: false,
|
|
9133
|
+
before_days: beforeDays,
|
|
9134
|
+
count: candidates.length,
|
|
9135
|
+
reason,
|
|
9136
|
+
sample,
|
|
9137
|
+
projection_path: outPath,
|
|
9138
|
+
});
|
|
9139
|
+
return;
|
|
9140
|
+
}
|
|
9141
|
+
console.log(`cleared ${candidates.length} completed task(s).`);
|
|
9142
|
+
}
|
|
9143
|
+
|
|
8207
9144
|
// One-time migration for OBL-1622: the 2026-06-10 "first-principles backlog
|
|
8208
9145
|
// reset" archived ~125 certified, proof-backed tasks by writing status
|
|
8209
9146
|
// 'failed' (no distinct archived status existed yet). This relabels exactly
|
|
@@ -8272,6 +9209,7 @@ function cmdReady(args) {
|
|
|
8272
9209
|
// turning a claim into executed evidence. --verify can carry an optional --proof note.
|
|
8273
9210
|
const proofFlag = flag(args, '--proof');
|
|
8274
9211
|
const verifyFlag = flag(args, '--verify');
|
|
9212
|
+
const resultSentence = requireResultSentence('atris task ready', textFlag(args, ['--result']), { ready: true });
|
|
8275
9213
|
const usedVerify = typeof verifyFlag === 'string' ? verifyFlag.trim() : '';
|
|
8276
9214
|
let proof = typeof proofFlag === 'string' ? proofFlag : '';
|
|
8277
9215
|
const verifyAutoCertifyAllowed = !usedVerify || isAutoCertifyVerifyCommandAllowed(verifyFlag);
|
|
@@ -8304,7 +9242,7 @@ function cmdReady(args) {
|
|
|
8304
9242
|
guardExplicitActor('atris task ready', flag(args, '--as'));
|
|
8305
9243
|
const actor = String(flag(args, '--as') || DEFAULT_OWNER);
|
|
8306
9244
|
const resultFields = {
|
|
8307
|
-
changed: textFlag(args, ['--changed', '--
|
|
9245
|
+
changed: textFlag(args, ['--changed', '--done']),
|
|
8308
9246
|
checked: textFlag(args, ['--checked', '--check', '--verified']),
|
|
8309
9247
|
passed: textFlag(args, ['--passed', '--pass']),
|
|
8310
9248
|
failed: textFlag(args, ['--failed', '--fail']),
|
|
@@ -8318,10 +9256,17 @@ function cmdReady(args) {
|
|
|
8318
9256
|
const taskDb = getTaskDb();
|
|
8319
9257
|
const db = taskDb.open();
|
|
8320
9258
|
const taskId = requireTaskId(taskDb, db, id, 'atris task ready');
|
|
9259
|
+
const beforeTask = taskDetail(taskDb, db, taskId);
|
|
9260
|
+
requireExplicitLandingDayOnePm('atris task ready', landing, beforeTask && beforeTask.title);
|
|
9261
|
+
const missionXpIssue = missionXpEndToEndProofIssue(beforeTask, proof, taskDb.workspaceRoot());
|
|
9262
|
+
if (missionXpIssue) {
|
|
9263
|
+
failTask('atris task ready', MISSION_XP_END_TO_END_REASON, missionXpIssue);
|
|
9264
|
+
}
|
|
8321
9265
|
const resultTrace = buildAutomaticResultTrace(taskDb, db, taskId, {
|
|
8322
9266
|
actor,
|
|
8323
9267
|
proof: String(proof),
|
|
8324
9268
|
...resultFields,
|
|
9269
|
+
changed: resultFields.changed || resultSentence,
|
|
8325
9270
|
});
|
|
8326
9271
|
const result = taskDb.readyTask(db, {
|
|
8327
9272
|
id: taskId,
|
|
@@ -8331,6 +9276,7 @@ function cmdReady(args) {
|
|
|
8331
9276
|
nextTask: nextTaskInput.nextTask,
|
|
8332
9277
|
resultTrace: resultTrace && resultTrace.trace,
|
|
8333
9278
|
landing,
|
|
9279
|
+
result: resultSentence,
|
|
8334
9280
|
});
|
|
8335
9281
|
if (!result.ready) {
|
|
8336
9282
|
console.error(`ready failed: ${result.reason}`);
|
|
@@ -8354,6 +9300,7 @@ function cmdReady(args) {
|
|
|
8354
9300
|
});
|
|
8355
9301
|
const reviewChat = taskReviewChatHandoff(verifierTask, { reviewer: 'codex-review' });
|
|
8356
9302
|
const autolandOn = require('../lib/autoland').liveAcceptAuthorization(taskDb.workspaceRoot()).ok;
|
|
9303
|
+
const needsExternalVerifier = Boolean(usedVerify && !verifyAutoCertifyAllowed);
|
|
8357
9304
|
const handoff = {
|
|
8358
9305
|
native_goal_status: agentCertified ? 'agent_certified' : 'needs_second_agent_review',
|
|
8359
9306
|
career_xp_status: 'pending_human_accept',
|
|
@@ -8361,6 +9308,8 @@ function cmdReady(args) {
|
|
|
8361
9308
|
rule: autolandOn
|
|
8362
9309
|
? (agentCertified
|
|
8363
9310
|
? 'double-check complete; autoland will accept this on the next tick.'
|
|
9311
|
+
: needsExternalVerifier
|
|
9312
|
+
? 'proof is ready; this verifier needs a second agent review because autoland cannot rerun it.'
|
|
8364
9313
|
: 'proof is ready; autoland runs the second check and lands it on the next tick.')
|
|
8365
9314
|
: (agentCertified
|
|
8366
9315
|
? 'double-check complete; ready to keep moving. XP is awarded only after the human approves the task.'
|
|
@@ -8443,7 +9392,7 @@ function cmdTaskReceipt(args) {
|
|
|
8443
9392
|
}
|
|
8444
9393
|
if (receipt.passed) {
|
|
8445
9394
|
console.log(`receipt written: ${receipt.receiptPath} (exit 0)`);
|
|
8446
|
-
console.log(`use: atris task ready ${taskId} --proof "Receipt: ${receipt.receiptPath}"`);
|
|
9395
|
+
console.log(`use: atris task ready ${taskId} --proof "Receipt: ${receipt.receiptPath}" --result "<what someone can do now and why it matters>"`);
|
|
8447
9396
|
} else {
|
|
8448
9397
|
console.error(`verifier failed (exit ${receipt.exit}); receipt written: ${receipt.receiptPath}`);
|
|
8449
9398
|
if (receipt.output) console.error(receipt.output);
|
|
@@ -8483,6 +9432,10 @@ async function cmdAccept(args) {
|
|
|
8483
9432
|
process.exit(2);
|
|
8484
9433
|
}
|
|
8485
9434
|
requireMeaningfulTaskProof('atris task accept', proof);
|
|
9435
|
+
const missionXpIssue = missionXpEndToEndProofIssue(beforeTask, proof, taskDb.workspaceRoot());
|
|
9436
|
+
if (missionXpIssue) {
|
|
9437
|
+
failTask('atris task accept', MISSION_XP_END_TO_END_REASON, missionXpIssue);
|
|
9438
|
+
}
|
|
8486
9439
|
const readyReview = beforeTask?.review || {};
|
|
8487
9440
|
const clearLesson = hasEmptyFlagValue(args, '--lesson');
|
|
8488
9441
|
const clearNextTask = hasEmptyFlagValue(args, '--next');
|
|
@@ -8583,6 +9536,11 @@ function stampAutoAcceptMetadata(taskDb, db, taskId, actor, policy) {
|
|
|
8583
9536
|
}
|
|
8584
9537
|
|
|
8585
9538
|
function acceptReviewTask(taskDb, db, taskId, { actor, proof, reward, lesson = '', nextTask = '', autoAccepted = false }) {
|
|
9539
|
+
const task = taskDetail(taskDb, db, taskId);
|
|
9540
|
+
const missionXpIssue = missionXpEndToEndProofIssue(task, proof, taskDb.workspaceRoot());
|
|
9541
|
+
if (missionXpIssue) {
|
|
9542
|
+
return { ok: false, reason: MISSION_XP_END_TO_END_REASON, detail: missionXpIssue };
|
|
9543
|
+
}
|
|
8586
9544
|
const done = taskDb.doneTask(db, {
|
|
8587
9545
|
id: taskId,
|
|
8588
9546
|
status: 'done',
|
|
@@ -8665,9 +9623,51 @@ function stampReadyVerifyMetadata(taskDb, db, taskId, verify) {
|
|
|
8665
9623
|
`).run(JSON.stringify(metadata), Date.now(), taskId);
|
|
8666
9624
|
}
|
|
8667
9625
|
|
|
8668
|
-
function
|
|
9626
|
+
function landingVerifyFailureNote(verify, result) {
|
|
9627
|
+
const exit = result && result.status != null ? result.status : 'unknown';
|
|
9628
|
+
return `Autoland re-ran allowlisted verify at landing and it failed: ${verify} (exit ${exit}).`;
|
|
9629
|
+
}
|
|
9630
|
+
|
|
9631
|
+
function reverifyBeforeLanding(taskDb, db, task, { actor = 'autoland-verifier', verifyCache = null } = {}) {
|
|
9632
|
+
const verify = certifyVerifyCandidate(task);
|
|
9633
|
+
if (!verify) {
|
|
9634
|
+
// No runnable check on record. Landing anyway converts "never verified"
|
|
9635
|
+
// into "accepted with XP" — the exact signal poisoning this gate exists
|
|
9636
|
+
// to prevent — so bounce the task back for a recorded verify instead.
|
|
9637
|
+
const note = 'Autoland refused to land without a runnable verify command: record one with `atris task ready --verify "<cmd>" --result "<sentence>"`.';
|
|
9638
|
+
const revised = taskDb.reviseTask(db, { id: task.id, actor, note });
|
|
9639
|
+
return {
|
|
9640
|
+
ok: false,
|
|
9641
|
+
verify: null,
|
|
9642
|
+
result: { reason: 'strict_verify_missing', status: null },
|
|
9643
|
+
note,
|
|
9644
|
+
revised: revised.revised === true,
|
|
9645
|
+
revise_reason: revised.reason || null,
|
|
9646
|
+
};
|
|
9647
|
+
}
|
|
9648
|
+
const result = runVerifyCommandCached(verify, task.workspace_root || process.cwd(), verifyCache);
|
|
9649
|
+
if (result.ok) return { ok: true, verify, result };
|
|
9650
|
+
const note = landingVerifyFailureNote(verify, result);
|
|
9651
|
+
const revised = taskDb.reviseTask(db, {
|
|
9652
|
+
id: task.id,
|
|
9653
|
+
actor,
|
|
9654
|
+
note,
|
|
9655
|
+
});
|
|
9656
|
+
return {
|
|
9657
|
+
ok: false,
|
|
9658
|
+
verify,
|
|
9659
|
+
result,
|
|
9660
|
+
note,
|
|
9661
|
+
revised: revised.revised === true,
|
|
9662
|
+
revise_reason: revised.reason || null,
|
|
9663
|
+
};
|
|
9664
|
+
}
|
|
9665
|
+
|
|
9666
|
+
function cmdCertifyVerified(args, options = {}) {
|
|
8669
9667
|
const dryRun = hasFlag(args, '--dry-run');
|
|
8670
9668
|
const asJson = wantsJson(args);
|
|
9669
|
+
const silent = options.silent === true;
|
|
9670
|
+
const verifyCache = options.verifyCache || null;
|
|
8671
9671
|
const actor = String(flag(args, '--as') || 'autoland-verifier');
|
|
8672
9672
|
const limitRaw = flag(args, '--limit');
|
|
8673
9673
|
const max = limitRaw && limitRaw !== true ? Math.max(1, Number(limitRaw) || 6) : 6;
|
|
@@ -8704,6 +9704,11 @@ function cmdCertifyVerified(args) {
|
|
|
8704
9704
|
results.push({ ref, action: 'skipped', reason: `denied_tag_${tag}` });
|
|
8705
9705
|
continue;
|
|
8706
9706
|
}
|
|
9707
|
+
const proofBoundary = proofBoundaryBlockedEvaluation(task);
|
|
9708
|
+
if (proofBoundary) {
|
|
9709
|
+
results.push({ ref, action: 'skipped', reason: proofBoundary.reason });
|
|
9710
|
+
continue;
|
|
9711
|
+
}
|
|
8707
9712
|
// Skip only rows the accept lane can already land, or rows blocked by
|
|
8708
9713
|
// something an executed second-actor check cannot cure. A row with two
|
|
8709
9714
|
// passes from ONE actor is exactly what this command exists to cure —
|
|
@@ -8739,7 +9744,7 @@ function cmdCertifyVerified(args) {
|
|
|
8739
9744
|
results.push({ ref, action: 'would_certify', verify });
|
|
8740
9745
|
continue;
|
|
8741
9746
|
}
|
|
8742
|
-
const run =
|
|
9747
|
+
const run = runVerifyCommandCached(verify, task.workspace_root || process.cwd(), verifyCache);
|
|
8743
9748
|
if (!run.ok) {
|
|
8744
9749
|
results.push({ ref, action: 'verify_failed', reason: run.reason, verify });
|
|
8745
9750
|
continue;
|
|
@@ -8773,6 +9778,9 @@ function cmdCertifyVerified(args) {
|
|
|
8773
9778
|
results,
|
|
8774
9779
|
projection_path: outPath,
|
|
8775
9780
|
};
|
|
9781
|
+
if (silent) {
|
|
9782
|
+
return payload;
|
|
9783
|
+
}
|
|
8776
9784
|
if (asJson) {
|
|
8777
9785
|
console.log(JSON.stringify(payload, null, 2));
|
|
8778
9786
|
} else if (results.length === 0) {
|
|
@@ -8805,6 +9813,7 @@ function cmdLanding(args) {
|
|
|
8805
9813
|
function cmdAutoAcceptCertified(args) {
|
|
8806
9814
|
const dryRun = hasFlag(args, '--dry-run');
|
|
8807
9815
|
const acceptAll = hasFlag(args, '--all');
|
|
9816
|
+
const certifyFirst = hasFlag(args, '--certify-first');
|
|
8808
9817
|
const strictVerify = !hasFlag(args, '--no-strict-verify') && !acceptAll;
|
|
8809
9818
|
const actorFlag = flag(args, '--as');
|
|
8810
9819
|
const hasHumanActor = validHumanActorFlag(actorFlag);
|
|
@@ -8863,6 +9872,14 @@ function cmdAutoAcceptCertified(args) {
|
|
|
8863
9872
|
);
|
|
8864
9873
|
}
|
|
8865
9874
|
|
|
9875
|
+
// A heartbeat certifies and lands in one process so its live verifier result
|
|
9876
|
+
// remains available to the landing gate. The cache is process-local and is
|
|
9877
|
+
// never persisted: a later heartbeat must prove the checkout again.
|
|
9878
|
+
const verifyCache = new Map();
|
|
9879
|
+
const certification = certifyFirst && !dryRun
|
|
9880
|
+
? cmdCertifyVerified([], { verifyCache, silent: true })
|
|
9881
|
+
: null;
|
|
9882
|
+
|
|
8866
9883
|
const taskDb = getTaskDb();
|
|
8867
9884
|
const db = taskDb.open();
|
|
8868
9885
|
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
@@ -8890,7 +9907,12 @@ function cmdAutoAcceptCertified(args) {
|
|
|
8890
9907
|
results.push({ ref: item.display_id || item.id, eligible: false, reason: 'task_not_found', action: 'skipped' });
|
|
8891
9908
|
continue;
|
|
8892
9909
|
}
|
|
8893
|
-
const
|
|
9910
|
+
const proofBoundary = proofBoundaryBlockedEvaluation(task);
|
|
9911
|
+
if (proofBoundary) {
|
|
9912
|
+
results.push({ ...proofBoundary, action: 'skipped' });
|
|
9913
|
+
continue;
|
|
9914
|
+
}
|
|
9915
|
+
const evaluation = evaluateAutoAccept(task, { strictVerify, acceptAll, verifyCache });
|
|
8894
9916
|
if (!evaluation.eligible) {
|
|
8895
9917
|
results.push({ ...evaluation, action: 'skipped' });
|
|
8896
9918
|
continue;
|
|
@@ -8899,6 +9921,20 @@ function cmdAutoAcceptCertified(args) {
|
|
|
8899
9921
|
results.push({ ...evaluation, action: 'would_accept', reward: parsedReward.value });
|
|
8900
9922
|
continue;
|
|
8901
9923
|
}
|
|
9924
|
+
const landingVerify = reverifyBeforeLanding(taskDb, db, task, { verifyCache });
|
|
9925
|
+
if (!landingVerify.ok) {
|
|
9926
|
+
results.push({
|
|
9927
|
+
...evaluation,
|
|
9928
|
+
eligible: false,
|
|
9929
|
+
action: landingVerify.revised ? 'revised' : 'revise_failed',
|
|
9930
|
+
reason: landingVerify.result.reason || landingVerify.revise_reason || 'verify_failed',
|
|
9931
|
+
verify: landingVerify.verify,
|
|
9932
|
+
exit_code: landingVerify.result.status,
|
|
9933
|
+
revision_note: landingVerify.note,
|
|
9934
|
+
task_id: task.id,
|
|
9935
|
+
});
|
|
9936
|
+
continue;
|
|
9937
|
+
}
|
|
8902
9938
|
const accepted = acceptReviewTask(taskDb, db, task.id, {
|
|
8903
9939
|
actor,
|
|
8904
9940
|
proof: evaluation.proof,
|
|
@@ -8927,8 +9963,9 @@ function cmdAutoAcceptCertified(args) {
|
|
|
8927
9963
|
scanned: pool.length,
|
|
8928
9964
|
accepted: results.filter(row => row.action === 'accepted').length,
|
|
8929
9965
|
would_accept: results.filter(row => row.action === 'would_accept').length,
|
|
9966
|
+
revised: results.filter(row => row.action === 'revised').length,
|
|
8930
9967
|
skipped: results.filter(row => row.action === 'skipped').length,
|
|
8931
|
-
failed: results.filter(row => row.action === 'accept_failed').length,
|
|
9968
|
+
failed: results.filter(row => row.action === 'accept_failed' || row.action === 'revise_failed').length,
|
|
8932
9969
|
// Visible undercount flag: true only if the pool was cut short by `max`
|
|
8933
9970
|
// while certified rows still existed beyond it. --all uses a high safety
|
|
8934
9971
|
// cap (AUTO_ACCEPT_ALL_SWEEP_CAP), so this should stay false in practice;
|
|
@@ -8944,18 +9981,20 @@ function cmdAutoAcceptCertified(args) {
|
|
|
8944
9981
|
summary,
|
|
8945
9982
|
...summary,
|
|
8946
9983
|
results,
|
|
9984
|
+
certification,
|
|
8947
9985
|
projection_path: finalPath,
|
|
8948
9986
|
queue,
|
|
8949
9987
|
});
|
|
8950
|
-
return;
|
|
9988
|
+
return { ...summary, results, certification, projection_path: finalPath, queue };
|
|
8951
9989
|
}
|
|
8952
9990
|
console.log(`AUTO-ACCEPT CERTIFIED (${dryRun ? 'dry-run' : 'execute'})`);
|
|
8953
|
-
console.log(`${summary.certified} certified, ${summary.scanned} scanned, ${summary.accepted || summary.would_accept} accepted, ${summary.skipped} skipped${summary.failed ? `, ${summary.failed} failed` : ''}${summary.undercounted ? ' (UNDERCOUNTED — raise --limit or the sweep cap)' : ''}`);
|
|
9991
|
+
console.log(`${summary.certified} certified, ${summary.scanned} scanned, ${summary.accepted || summary.would_accept} accepted, ${summary.skipped} skipped${summary.revised ? `, ${summary.revised} revised` : ''}${summary.failed ? `, ${summary.failed} failed` : ''}${summary.undercounted ? ' (UNDERCOUNTED — raise --limit or the sweep cap)' : ''}`);
|
|
8954
9992
|
for (const row of results) {
|
|
8955
9993
|
const nextAction = row.next_action ? ` next_action=${row.next_action}` : '';
|
|
8956
9994
|
const reviewChat = row.review_chat_command ? ` review_chat=${row.review_chat_command}` : '';
|
|
8957
9995
|
console.log(`${row.action.toUpperCase()} ${row.ref}: ${row.reason}${row.reward ? ` reward=${row.reward}` : ''}${nextAction}${reviewChat}`);
|
|
8958
9996
|
}
|
|
9997
|
+
return { ...summary, results, certification, projection_path: finalPath, queue };
|
|
8959
9998
|
}
|
|
8960
9999
|
|
|
8961
10000
|
const SWEEP_AUTO_ACCEPT_PROTECTED = new Set([
|
|
@@ -9237,6 +10276,138 @@ function cmdRevise(args) {
|
|
|
9237
10276
|
console.log(`revise ${taskRef(compactTaskFromProjection(projection, taskId))} v${result.event.version}`);
|
|
9238
10277
|
}
|
|
9239
10278
|
|
|
10279
|
+
function taskAuditTimestamp(task) {
|
|
10280
|
+
const acceptedAt = Date.parse(String(task?.metadata?.accepted_at || ''));
|
|
10281
|
+
if (Number.isFinite(acceptedAt)) return acceptedAt;
|
|
10282
|
+
return Number(task?.done_at || task?.updated_at || task?.created_at || 0);
|
|
10283
|
+
}
|
|
10284
|
+
|
|
10285
|
+
function taskAuditOutput(value, max = 800) {
|
|
10286
|
+
const text = String(value || '').trim();
|
|
10287
|
+
if (text.length <= max) return text;
|
|
10288
|
+
return `...${text.slice(-max)}`;
|
|
10289
|
+
}
|
|
10290
|
+
|
|
10291
|
+
function taskAuditReceiptPath(at) {
|
|
10292
|
+
const safeTime = at.replace(/[:.]/g, '-');
|
|
10293
|
+
return path.join('atris', 'runs', `task-audit-${safeTime}.json`);
|
|
10294
|
+
}
|
|
10295
|
+
|
|
10296
|
+
function runTaskAuditVerify(task, verify) {
|
|
10297
|
+
const { spawnSync } = require('child_process');
|
|
10298
|
+
const result = spawnSync('bash', ['-c', verify], {
|
|
10299
|
+
cwd: task.workspace_root,
|
|
10300
|
+
encoding: 'utf8',
|
|
10301
|
+
timeout: 120000,
|
|
10302
|
+
});
|
|
10303
|
+
const passed = !result.error && result.status === 0;
|
|
10304
|
+
return {
|
|
10305
|
+
passed,
|
|
10306
|
+
exit: result.status,
|
|
10307
|
+
signal: result.signal || null,
|
|
10308
|
+
error: result.error ? result.error.message : null,
|
|
10309
|
+
output: taskAuditOutput(`${result.stdout || ''}${result.stderr || ''}`),
|
|
10310
|
+
};
|
|
10311
|
+
}
|
|
10312
|
+
|
|
10313
|
+
function cmdAudit(args) {
|
|
10314
|
+
const limitRaw = flag(args, '--limit');
|
|
10315
|
+
const limit = limitRaw === null ? 20 : Number(limitRaw);
|
|
10316
|
+
if (!Number.isInteger(limit) || limit < 1) {
|
|
10317
|
+
console.error('atris task audit: --limit must be a positive integer');
|
|
10318
|
+
process.exit(2);
|
|
10319
|
+
}
|
|
10320
|
+
|
|
10321
|
+
const revise = hasFlag(args, '--revise');
|
|
10322
|
+
const actor = String(flag(args, '--as') || 'task-audit');
|
|
10323
|
+
const taskDb = getTaskDb();
|
|
10324
|
+
const db = taskDb.open();
|
|
10325
|
+
const workspaceRoot = taskDb.workspaceRoot();
|
|
10326
|
+
const allRows = taskDb.listTasks(db, { workspaceRoot });
|
|
10327
|
+
const accepted = taskDb.withTaskDisplayRefs(
|
|
10328
|
+
allRows
|
|
10329
|
+
.filter(task => task.status === 'done' && task.metadata?.approval_status === 'accepted')
|
|
10330
|
+
.sort((a, b) => taskAuditTimestamp(b) - taskAuditTimestamp(a))
|
|
10331
|
+
.slice(0, limit),
|
|
10332
|
+
allRows,
|
|
10333
|
+
);
|
|
10334
|
+
const at = new Date().toISOString();
|
|
10335
|
+
const receiptPath = taskAuditReceiptPath(at);
|
|
10336
|
+
const results = accepted.map(task => {
|
|
10337
|
+
const verify = typeof task.metadata?.verify === 'string' ? task.metadata.verify : '';
|
|
10338
|
+
if (!verify.trim()) {
|
|
10339
|
+
return {
|
|
10340
|
+
task_id: task.id,
|
|
10341
|
+
ref: task.display_id || task.legacy_ref || taskRef(task),
|
|
10342
|
+
status: 'skipped-no-verify',
|
|
10343
|
+
verify: null,
|
|
10344
|
+
};
|
|
10345
|
+
}
|
|
10346
|
+
const run = runTaskAuditVerify(task, verify);
|
|
10347
|
+
return {
|
|
10348
|
+
task_id: task.id,
|
|
10349
|
+
ref: task.display_id || task.legacy_ref || taskRef(task),
|
|
10350
|
+
status: run.passed ? 'passed' : 'failed',
|
|
10351
|
+
verify,
|
|
10352
|
+
exit: run.exit,
|
|
10353
|
+
signal: run.signal,
|
|
10354
|
+
error: run.error,
|
|
10355
|
+
output: run.output,
|
|
10356
|
+
};
|
|
10357
|
+
});
|
|
10358
|
+
|
|
10359
|
+
const failing = results.filter(row => row.status === 'failed');
|
|
10360
|
+
if (revise) {
|
|
10361
|
+
const note = `task audit re-ran the stored verify and it failed; see ${receiptPath}`;
|
|
10362
|
+
for (const row of failing) {
|
|
10363
|
+
const revised = taskDb.reviseTask(db, {
|
|
10364
|
+
id: row.task_id,
|
|
10365
|
+
actor,
|
|
10366
|
+
note,
|
|
10367
|
+
allowDone: true,
|
|
10368
|
+
});
|
|
10369
|
+
row.revised = revised.revised === true;
|
|
10370
|
+
row.revise_reason = revised.revised ? null : revised.reason;
|
|
10371
|
+
}
|
|
10372
|
+
if (failing.some(row => row.revised)) writeDefaultProjection(taskDb, db);
|
|
10373
|
+
}
|
|
10374
|
+
|
|
10375
|
+
const summary = {
|
|
10376
|
+
sampled: results.length,
|
|
10377
|
+
passed: results.filter(row => row.status === 'passed').length,
|
|
10378
|
+
failed: failing.length,
|
|
10379
|
+
'skipped-no-verify': results.filter(row => row.status === 'skipped-no-verify').length,
|
|
10380
|
+
revised: results.filter(row => row.revised === true).length,
|
|
10381
|
+
};
|
|
10382
|
+
const receipt = {
|
|
10383
|
+
schema: 'atris.task_audit_receipt.v1',
|
|
10384
|
+
at,
|
|
10385
|
+
workspace_root: workspaceRoot,
|
|
10386
|
+
limit,
|
|
10387
|
+
revise,
|
|
10388
|
+
summary,
|
|
10389
|
+
failing_task_ids: failing.map(row => row.task_id),
|
|
10390
|
+
results,
|
|
10391
|
+
};
|
|
10392
|
+
const receiptFile = path.join(workspaceRoot, receiptPath);
|
|
10393
|
+
fs.mkdirSync(path.dirname(receiptFile), { recursive: true });
|
|
10394
|
+
fs.writeFileSync(receiptFile, `${JSON.stringify(receipt, null, 2)}\n`, 'utf8');
|
|
10395
|
+
|
|
10396
|
+
if (wantsJson(args)) {
|
|
10397
|
+
printJson({
|
|
10398
|
+
ok: true,
|
|
10399
|
+
action: 'task_audit',
|
|
10400
|
+
receipt_path: receiptPath,
|
|
10401
|
+
...receipt,
|
|
10402
|
+
});
|
|
10403
|
+
return;
|
|
10404
|
+
}
|
|
10405
|
+
console.log(`task audit: sampled ${summary.sampled}, passed ${summary.passed}, failed ${summary.failed}, skipped-no-verify ${summary['skipped-no-verify']}`);
|
|
10406
|
+
console.log(`failing task ids: ${failing.length ? failing.map(row => row.ref).join(', ') : 'none'}`);
|
|
10407
|
+
if (revise) console.log(`revised: ${summary.revised}`);
|
|
10408
|
+
console.log(`receipt: ${receiptPath}`);
|
|
10409
|
+
}
|
|
10410
|
+
|
|
9240
10411
|
function cmdReview(args) {
|
|
9241
10412
|
const pos = positional(args);
|
|
9242
10413
|
const id = pos[0];
|
|
@@ -9750,6 +10921,25 @@ function refreshExistingTodoMarkdown(taskDb, db, workspaceRoot) {
|
|
|
9750
10921
|
return outPath;
|
|
9751
10922
|
}
|
|
9752
10923
|
|
|
10924
|
+
// TODO.md is a projection of task-db state. Keep this best-effort and silent
|
|
10925
|
+
// because it runs after every successful mutating task command.
|
|
10926
|
+
function autoRenderTodoFromDb(cwd = process.cwd()) {
|
|
10927
|
+
try {
|
|
10928
|
+
const atrisDir = path.join(cwd, 'atris');
|
|
10929
|
+
if (!fs.existsSync(atrisDir)) return null;
|
|
10930
|
+
const taskDb = getTaskDb();
|
|
10931
|
+
const db = taskDb.open();
|
|
10932
|
+
const outPath = path.join(atrisDir, 'TODO.md');
|
|
10933
|
+
if (fs.existsSync(outPath)) return refreshExistingTodoMarkdown(taskDb, db, cwd);
|
|
10934
|
+
const rows = taskDb.listTasks(db, { workspaceRoot: cwd, limit: 500 });
|
|
10935
|
+
const refRows = taskDb.listTasks(db, { workspaceRoot: cwd });
|
|
10936
|
+
fs.writeFileSync(outPath, taskDb.renderTodoMarkdown(rows, { refRows }), 'utf8');
|
|
10937
|
+
return outPath;
|
|
10938
|
+
} catch {
|
|
10939
|
+
return null;
|
|
10940
|
+
}
|
|
10941
|
+
}
|
|
10942
|
+
|
|
9753
10943
|
function cmdRender(args) {
|
|
9754
10944
|
const out = flag(args, '--out') || path.join('atris', 'TODO.md');
|
|
9755
10945
|
const all = hasFlag(args, '--all');
|
|
@@ -10733,10 +11923,15 @@ async function handleTaskApi(req, res, taskDb, db) {
|
|
|
10733
11923
|
if (proofIssue) return sendProofIssue(res, proof, proofIssue);
|
|
10734
11924
|
const nextTaskInput = normalizeReviewNextTaskInput(body.next);
|
|
10735
11925
|
const actor = String(body.actor || DEFAULT_OWNER);
|
|
11926
|
+
const resultText = String(body.result || '').replace(/\s+/g, ' ').trim();
|
|
11927
|
+
if (resultText) {
|
|
11928
|
+
const resultIssue = resultSentenceIssue(resultText);
|
|
11929
|
+
if (resultIssue) return sendJson(res, 400, { ok: false, reason: 'weak_result', detail: resultIssue });
|
|
11930
|
+
}
|
|
10736
11931
|
const resultTrace = buildAutomaticResultTrace(taskDb, db, taskId, {
|
|
10737
11932
|
actor,
|
|
10738
11933
|
proof,
|
|
10739
|
-
changed: body.changed ||
|
|
11934
|
+
changed: body.changed || resultText || body.done,
|
|
10740
11935
|
checked: body.checked || body.check || body.verified,
|
|
10741
11936
|
passed: body.passed || body.pass,
|
|
10742
11937
|
failed: body.failed || body.fail,
|
|
@@ -10754,6 +11949,7 @@ async function handleTaskApi(req, res, taskDb, db) {
|
|
|
10754
11949
|
lesson: String(body.lesson || ''),
|
|
10755
11950
|
nextTask: nextTaskInput.nextTask,
|
|
10756
11951
|
resultTrace: resultTrace && resultTrace.trace,
|
|
11952
|
+
result: resultText,
|
|
10757
11953
|
landing: body.landing || {
|
|
10758
11954
|
happened: body.happened,
|
|
10759
11955
|
checked: body.checked,
|
|
@@ -10894,7 +12090,7 @@ function cmdServe(args) {
|
|
|
10894
12090
|
});
|
|
10895
12091
|
}
|
|
10896
12092
|
|
|
10897
|
-
async function
|
|
12093
|
+
async function runTaskCommand(args) {
|
|
10898
12094
|
const raw = args || [];
|
|
10899
12095
|
if (raw.includes('--help') || raw.includes('-h')) return help();
|
|
10900
12096
|
const first = raw[0];
|
|
@@ -10967,6 +12163,7 @@ async function run(args) {
|
|
|
10967
12163
|
return cmdPlanPreview(rest);
|
|
10968
12164
|
case 'note': return cmdNote(rest);
|
|
10969
12165
|
case 'say': return cmdNote(rest);
|
|
12166
|
+
case 'retitle': return cmdRetitle(rest);
|
|
10970
12167
|
case 'tag':
|
|
10971
12168
|
case 'tags':
|
|
10972
12169
|
return cmdTag(rest);
|
|
@@ -10989,6 +12186,7 @@ async function run(args) {
|
|
|
10989
12186
|
return cmdAutoAcceptCertified(rest);
|
|
10990
12187
|
case 'sweep':
|
|
10991
12188
|
return cmdSweep(rest);
|
|
12189
|
+
case 'audit': return cmdAudit(rest);
|
|
10992
12190
|
case 'certify-verified':
|
|
10993
12191
|
return cmdCertifyVerified(rest);
|
|
10994
12192
|
case 'accept-group':
|
|
@@ -10998,6 +12196,7 @@ async function run(args) {
|
|
|
10998
12196
|
case 'finish': return cmdFinish(rest);
|
|
10999
12197
|
case 'fail': return cmdDone([...rest, '--failed']);
|
|
11000
12198
|
case 'archive': return cmdArchive(rest);
|
|
12199
|
+
case 'clear-done': return cmdClearDone(rest);
|
|
11001
12200
|
case 'relabel-archived': return cmdRelabelArchived(rest);
|
|
11002
12201
|
case 'review': return cmdReview(rest);
|
|
11003
12202
|
case 'reviews':
|
|
@@ -11032,4 +12231,34 @@ async function run(args) {
|
|
|
11032
12231
|
}
|
|
11033
12232
|
}
|
|
11034
12233
|
|
|
11035
|
-
|
|
12234
|
+
const MUTATING_TASK_COMMANDS = new Set([
|
|
12235
|
+
'add', 'new', 'delegate', 'assign', 'plan', 'do', 'backlog', 'unplan',
|
|
12236
|
+
'clear-plan', 'clearplan', 'claim', 'start', 'release', 'unclaim', 'next',
|
|
12237
|
+
'continue-work', 'continue', 'chat', 'note', 'say', 'retitle', 'tag', 'tags', 'step',
|
|
12238
|
+
'ready', 'result', 'accept', 'landing', 'land-review', 'auto-accept-certified',
|
|
12239
|
+
'auto-accept', 'sweep', 'audit', 'certify-verified', 'accept-group', 'revise',
|
|
12240
|
+
'done', 'finish', 'fail', 'archive', 'clear-done', 'relabel-archived', 'review', 'import',
|
|
12241
|
+
'setup', 'review-lane-act', 'review-act', 'act-review', 'review-lane-loop',
|
|
12242
|
+
'review-loop', 'loop-review', 'review-lane-run', 'review-run', 'run-review',
|
|
12243
|
+
]);
|
|
12244
|
+
|
|
12245
|
+
async function run(args) {
|
|
12246
|
+
const raw = args || [];
|
|
12247
|
+
const sub = !raw[0] || raw[0].startsWith('--') ? 'desk' : raw[0];
|
|
12248
|
+
const result = await runTaskCommand(raw);
|
|
12249
|
+
const skipsRender = sub === 'clear-done' && hasFlag(raw, '--dry-run');
|
|
12250
|
+
if (MUTATING_TASK_COMMANDS.has(sub) && !skipsRender) autoRenderTodoFromDb();
|
|
12251
|
+
return result;
|
|
12252
|
+
}
|
|
12253
|
+
|
|
12254
|
+
module.exports = {
|
|
12255
|
+
run,
|
|
12256
|
+
taskDayGroups,
|
|
12257
|
+
taskDayTextGroups,
|
|
12258
|
+
taskDayTitle,
|
|
12259
|
+
taskReviewLanding,
|
|
12260
|
+
taskReviewLandingLines,
|
|
12261
|
+
delegateTask,
|
|
12262
|
+
AGENT_ENV_MARKERS,
|
|
12263
|
+
autoRenderTodoFromDb,
|
|
12264
|
+
};
|