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