atris 3.30.8 → 3.31.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 +4 -2
- 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 +7 -0
- package/ax +367 -93
- package/bin/atris.js +317 -155
- package/commands/autoland.js +319 -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 +1 -1
- package/commands/land.js +442 -0
- package/commands/loop-front.js +122 -4
- package/commands/member.js +519 -19
- package/commands/mission.js +3659 -282
- package/commands/play.js +1 -1
- package/commands/pulse.js +65 -7
- package/commands/run.js +3 -3
- package/commands/strings.js +301 -0
- package/commands/task.js +575 -102
- package/commands/truth.js +170 -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 +283 -0
- package/lib/context-gatherer.js +0 -8
- package/lib/mission-artifact.js +504 -0
- package/lib/mission-room.js +846 -0
- package/lib/mission-runtime-loop.js +320 -0
- package/lib/next-moves.js +212 -6
- package/lib/pulse.js +74 -1
- package/lib/runner-command.js +20 -8
- package/lib/runs-prune.js +242 -0
- package/lib/task-proof.js +1 -1
- package/package.json +3 -3
- 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
|
@@ -150,8 +150,8 @@ atris task - durable local task state (SQLite, gitignored)
|
|
|
150
150
|
atris task finish <id> --proof "..." Legacy alias for done with proof
|
|
151
151
|
atris task review <id> --reward <n> [--verify "<cmd>"]
|
|
152
152
|
Write review event + RSI episode
|
|
153
|
-
atris task reviews [--limit <n>]
|
|
154
|
-
atris task reviews --group-by <tag|owner|source> Cluster ready
|
|
153
|
+
atris task reviews [--all|--limit <n>] [--verbose] Show certified Review items for human accept/revise
|
|
154
|
+
atris task reviews --group-by <tag|owner|source> Cluster approval-ready work for fast triage
|
|
155
155
|
atris task accept-group <key>=<value> --spot-check K --confirm-human-accept --as <you> --verified <ids>
|
|
156
156
|
Accept a whole cluster; career XP only on the K you verified
|
|
157
157
|
atris task status [--json] [--history] Compact live status for web/Swarlo
|
|
@@ -284,6 +284,63 @@ function refreshCareerXpAfterReview(reviewed) {
|
|
|
284
284
|
return refreshCareerXpProjection(reviewed?.episode?.workspace_root);
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
+
function compactCareerXpProjection(projection) {
|
|
288
|
+
if (!projection || typeof projection !== 'object') return projection;
|
|
289
|
+
const progress = projection.next_level_progress && typeof projection.next_level_progress === 'object'
|
|
290
|
+
? {
|
|
291
|
+
level: projection.next_level_progress.level ?? null,
|
|
292
|
+
next_level: projection.next_level_progress.next_level ?? null,
|
|
293
|
+
current_xp: projection.next_level_progress.current_xp ?? null,
|
|
294
|
+
required_xp: projection.next_level_progress.required_xp ?? null,
|
|
295
|
+
remaining_xp: projection.next_level_progress.remaining_xp ?? null,
|
|
296
|
+
percent: projection.next_level_progress.percent ?? null,
|
|
297
|
+
}
|
|
298
|
+
: null;
|
|
299
|
+
const latest = projection.latest_accepted_proof && typeof projection.latest_accepted_proof === 'object'
|
|
300
|
+
? {
|
|
301
|
+
label: projection.latest_accepted_proof.label ?? null,
|
|
302
|
+
receipt_id: projection.latest_accepted_proof.receipt_id ?? null,
|
|
303
|
+
source: projection.latest_accepted_proof.source ?? null,
|
|
304
|
+
source_task_id: projection.latest_accepted_proof.source_task_id ?? null,
|
|
305
|
+
title: projection.latest_accepted_proof.title ?? null,
|
|
306
|
+
xp: projection.latest_accepted_proof.xp ?? null,
|
|
307
|
+
reward: projection.latest_accepted_proof.reward ?? null,
|
|
308
|
+
accepted_at: projection.latest_accepted_proof.accepted_at ?? null,
|
|
309
|
+
goal: projection.latest_accepted_proof.goal ?? null,
|
|
310
|
+
}
|
|
311
|
+
: null;
|
|
312
|
+
const integrity = projection.integrity && typeof projection.integrity === 'object'
|
|
313
|
+
? {
|
|
314
|
+
status: projection.integrity.status ?? null,
|
|
315
|
+
receipts_count: projection.integrity.receipts_count ?? projection.receipts_count ?? null,
|
|
316
|
+
head_hash: projection.integrity.head_hash ?? null,
|
|
317
|
+
}
|
|
318
|
+
: null;
|
|
319
|
+
return {
|
|
320
|
+
schema: projection.schema,
|
|
321
|
+
compact: true,
|
|
322
|
+
total_xp: projection.total_xp ?? projection.total_agent_xp ?? projection.agent_xp ?? null,
|
|
323
|
+
agent_xp: projection.agent_xp ?? projection.total_agent_xp ?? null,
|
|
324
|
+
today_xp: projection.today_xp ?? projection.today_agent_xp ?? null,
|
|
325
|
+
collected_receipts: projection.collected_receipts ?? 0,
|
|
326
|
+
receipts_count: projection.receipts_count ?? null,
|
|
327
|
+
level: projection.level ?? null,
|
|
328
|
+
leaderboard_eligible: projection.leaderboard_eligible ?? null,
|
|
329
|
+
integrity_status: projection.integrity_status ?? integrity?.status ?? null,
|
|
330
|
+
next_level_progress: progress,
|
|
331
|
+
latest_accepted_proof: latest,
|
|
332
|
+
integrity,
|
|
333
|
+
omitted: [
|
|
334
|
+
'contribution_graph',
|
|
335
|
+
'career',
|
|
336
|
+
'earning_model',
|
|
337
|
+
'local_activity',
|
|
338
|
+
'ledger',
|
|
339
|
+
'sources',
|
|
340
|
+
],
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
287
344
|
function jsonModeActive() {
|
|
288
345
|
return process.argv.includes('--json');
|
|
289
346
|
}
|
|
@@ -678,47 +735,103 @@ function reviewSummary(task, payload = {}) {
|
|
|
678
735
|
|| careerText.includes('agent xp')
|
|
679
736
|
) {
|
|
680
737
|
if (task.status === 'done') {
|
|
681
|
-
return `
|
|
738
|
+
return `Completed AgentXP result for ${plainTitle}.`;
|
|
682
739
|
}
|
|
683
740
|
if (task.status === 'review') {
|
|
684
|
-
return `${plainTitle} is ready
|
|
741
|
+
return `${plainTitle}: proof is ready for human approval; approve only if the evidence is real.`;
|
|
685
742
|
}
|
|
686
|
-
return `This explains
|
|
743
|
+
return `This explains the AgentXP result ${plainTitle} would make real.`;
|
|
687
744
|
}
|
|
688
745
|
if (task.status === 'done') {
|
|
689
|
-
return `
|
|
746
|
+
return `Completed result for ${plainTitle}.`;
|
|
690
747
|
}
|
|
691
748
|
if (task.status === 'review') {
|
|
692
|
-
return `${plainTitle}
|
|
749
|
+
return `${plainTitle}: review the completed result, then approve or ask for rework.`;
|
|
693
750
|
}
|
|
694
|
-
return `This explains what
|
|
751
|
+
return `This explains what ${plainTitle} would make real.`;
|
|
695
752
|
}
|
|
696
753
|
|
|
697
754
|
function titleToResultText(title) {
|
|
698
755
|
const text = String(title || 'this task').replace(/\s+/g, ' ').trim();
|
|
699
|
-
if (!text) return '
|
|
756
|
+
if (!text) return 'Completed this task.';
|
|
757
|
+
const resultSentence = (prefix, body) => {
|
|
758
|
+
const clean = String(body || '').replace(/\s+/g, ' ').trim().replace(/[.!?]+$/, '');
|
|
759
|
+
return clean ? `${prefix} ${clean}.` : `${prefix}.`;
|
|
760
|
+
};
|
|
761
|
+
const missionXp = text.match(/^Mission XP:\s*(.+)$/i);
|
|
762
|
+
if (missionXp) {
|
|
763
|
+
const missionResult = titleToResultText(missionXp[1]).replace(/[.!?]+$/, '');
|
|
764
|
+
return resultSentence('Completed mission work:', missionResult.replace(/^Completed:\s*/i, ''));
|
|
765
|
+
}
|
|
700
766
|
const [first, ...restParts] = text.split(' ');
|
|
701
767
|
const rest = restParts.join(' ');
|
|
768
|
+
const firstLower = String(first || '').toLowerCase();
|
|
769
|
+
const compound = rest.match(/^and\s+([a-z]+)\s+(.+)$/i);
|
|
770
|
+
const compoundPast = compound ? ({
|
|
771
|
+
audit: { close: 'Audited and closed' },
|
|
772
|
+
decide: { start: 'Decided and started' },
|
|
773
|
+
prune: { compress: 'Pruned and compressed' },
|
|
774
|
+
}[firstLower] || {})[String(compound[1] || '').toLowerCase()] : null;
|
|
775
|
+
if (compoundPast && compound[2]) return resultSentence(compoundPast, compound[2]);
|
|
702
776
|
const past = {
|
|
703
777
|
add: 'Added',
|
|
704
778
|
approve: 'Prepared approval for',
|
|
779
|
+
audit: 'Audited',
|
|
780
|
+
batch: 'Batched',
|
|
705
781
|
build: 'Built',
|
|
706
782
|
clean: 'Cleaned',
|
|
707
783
|
create: 'Created',
|
|
784
|
+
decide: 'Decided',
|
|
708
785
|
design: 'Designed',
|
|
709
786
|
document: 'Documented',
|
|
787
|
+
find: 'Found',
|
|
710
788
|
fix: 'Fixed',
|
|
789
|
+
heal: 'Healed',
|
|
790
|
+
ignore: 'Ignored',
|
|
791
|
+
infer: 'Inferred',
|
|
792
|
+
keep: 'Kept',
|
|
711
793
|
make: 'Made',
|
|
794
|
+
number: 'Numbered',
|
|
795
|
+
prune: 'Pruned',
|
|
796
|
+
reconcile: 'Reconciled',
|
|
797
|
+
refresh: 'Refreshed',
|
|
798
|
+
render: 'Rendered',
|
|
799
|
+
repair: 'Repaired',
|
|
712
800
|
replace: 'Replaced',
|
|
801
|
+
respect: 'Respected',
|
|
713
802
|
route: 'Routed',
|
|
714
803
|
run: 'Ran',
|
|
715
804
|
ship: 'Shipped',
|
|
805
|
+
show: 'Showed',
|
|
806
|
+
stop: 'Stopped',
|
|
807
|
+
summarize: 'Summarized',
|
|
808
|
+
suppress: 'Suppressed',
|
|
809
|
+
trim: 'Trimmed',
|
|
716
810
|
update: 'Updated',
|
|
811
|
+
use: 'Used',
|
|
717
812
|
validate: 'Validated',
|
|
718
813
|
wire: 'Wired',
|
|
719
|
-
}[
|
|
720
|
-
if (past && rest) return
|
|
721
|
-
return
|
|
814
|
+
}[firstLower];
|
|
815
|
+
if (past && rest) return resultSentence(past, rest);
|
|
816
|
+
return resultSentence('Completed:', text);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
function proofToReasonText(proof) {
|
|
820
|
+
const text = String(proof || '').replace(/\s+/g, ' ').trim();
|
|
821
|
+
if (!text) return '';
|
|
822
|
+
const label = /\b(?:Product proof|Why it matters)\s*:\s*/i.exec(text);
|
|
823
|
+
if (!label) return '';
|
|
824
|
+
const rest = text.slice(label.index + label[0].length);
|
|
825
|
+
const stop = rest.search(/(?:^|[.;]\s+)\b(?:Checks?|Mission receipt|Receipt|Landing|Changed|How I checked|What I tested|Saved|Decision)\s*:/i);
|
|
826
|
+
let section = (stop >= 0 ? rest.slice(0, stop) : rest)
|
|
827
|
+
.replace(/^(?:[-:]\s*)+/, '')
|
|
828
|
+
.replace(/\s+/g, ' ')
|
|
829
|
+
.replace(/\s*[;,:-]\s*$/, '')
|
|
830
|
+
.trim();
|
|
831
|
+
const sentenceStop = section.search(/[.!?]\s+/);
|
|
832
|
+
if (sentenceStop >= 0) section = section.slice(0, sentenceStop + 1).trim();
|
|
833
|
+
if (!section) return '';
|
|
834
|
+
return /[.!?]$/.test(section) ? section : `${section}.`;
|
|
722
835
|
}
|
|
723
836
|
|
|
724
837
|
function titleToReasonText(task, proof = '') {
|
|
@@ -737,35 +850,102 @@ function titleToReasonText(task, proof = '') {
|
|
|
737
850
|
return 'It keeps real-world side effects behind a clear human decision.';
|
|
738
851
|
}
|
|
739
852
|
if (/\btest|self-test|harness|verifier|proof\b/.test(text)) {
|
|
740
|
-
return 'It
|
|
853
|
+
return 'It gives the human a repeatable check before approval.';
|
|
741
854
|
}
|
|
742
|
-
return 'It
|
|
855
|
+
return 'It turns the task title into a concrete result the human can approve.';
|
|
743
856
|
}
|
|
744
857
|
|
|
745
858
|
function proofToHumanCheck(proof) {
|
|
746
859
|
const text = String(proof || '').replace(/\s+/g, ' ').trim();
|
|
747
860
|
if (!text) return 'Proof is attached below.';
|
|
748
861
|
const lower = text.toLowerCase();
|
|
862
|
+
const passSignal = /\b(?:pass(?:es|ed|ing)?|ok|green|succeeded|verified)\b/.test(lower);
|
|
863
|
+
const verifierCommandSignal = /\b(?:npm|node|git|atris|npx|pnpm|yarn|python3?|pytest|bash|sh|tsc|vitest|curl|gh|rg)\s+\S+/.test(lower);
|
|
749
864
|
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
865
|
return 'I opened the real task screen and checked the receipt renders before raw proof.';
|
|
751
866
|
}
|
|
752
|
-
if (/git\s+diff\s+--check/.test(lower) &&
|
|
867
|
+
if (/git\s+diff\s+--check/.test(lower) && passSignal) {
|
|
753
868
|
if (/node\s+--test/.test(lower)) return 'I ran the behavior check and the diff cleanliness check.';
|
|
754
869
|
return 'I ran the diff cleanliness check.';
|
|
755
870
|
}
|
|
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) &&
|
|
871
|
+
if (/\btypecheck\b/.test(lower) && passSignal) return 'I ran the typecheck.';
|
|
872
|
+
if (/\bbuild\b/.test(lower) && passSignal) return 'I ran the build check.';
|
|
873
|
+
if (/\btest\b/.test(lower) && passSignal) return 'I ran the verifier named in the proof.';
|
|
874
|
+
if (/\bvalidat(?:e|ion|ed)\b/.test(lower) && passSignal) return 'I ran the validation check.';
|
|
875
|
+
if (verifierCommandSignal && /\b(?:print|prints|printed|show|shows|showed|report|reports|reported|return|returns|returned)\b/.test(lower)) {
|
|
876
|
+
return 'I checked the verifier output named in the proof.';
|
|
877
|
+
}
|
|
878
|
+
if (verifierCommandSignal && passSignal) return 'I ran the verifier named in the proof.';
|
|
879
|
+
if (/atris\/runs\/[^\s),.;:]+\.json/.test(text)) {
|
|
880
|
+
return passSignal
|
|
881
|
+
? 'I inspected the passing receipt named in the proof.'
|
|
882
|
+
: 'I inspected the receipt named in the proof.';
|
|
883
|
+
}
|
|
884
|
+
if (/(?:^|\s)(?:\.{0,2}\/)?scripts\/[^\s),.;:]+\.(?:js|mjs|cjs|py|sh)\b/.test(text)) {
|
|
885
|
+
return 'I inspected the verifier artifact named in the proof.';
|
|
886
|
+
}
|
|
760
887
|
if (/\breview(?:ed)?\b/.test(lower)) return 'Review proof is attached below.';
|
|
761
888
|
return 'Proof is attached below.';
|
|
762
889
|
}
|
|
763
890
|
|
|
891
|
+
function taskReviewFormatCommandList(commands) {
|
|
892
|
+
const list = Array.isArray(commands)
|
|
893
|
+
? commands.map(command => String(command || '').trim()).filter(Boolean)
|
|
894
|
+
: [];
|
|
895
|
+
if (!list.length) return '';
|
|
896
|
+
if (list.length > 1) {
|
|
897
|
+
return list.map((command, index) => `command ${index + 1}: ${command}`).join('; ');
|
|
898
|
+
}
|
|
899
|
+
return list[0];
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
function taskReviewProseCheckLabels(proof) {
|
|
903
|
+
const text = String(proof || '').replace(/\s+/g, ' ').trim();
|
|
904
|
+
if (!text) return [];
|
|
905
|
+
const lower = text.toLowerCase();
|
|
906
|
+
const hasPass = /\b(?:pass(?:es|ed|ing)?|green|verified|current|clean|complete|completed)\b/.test(lower);
|
|
907
|
+
if (!hasPass) return [];
|
|
908
|
+
const labels = [];
|
|
909
|
+
const add = label => {
|
|
910
|
+
if (!labels.includes(label)) labels.push(label);
|
|
911
|
+
};
|
|
912
|
+
if (/\bhelp output\b/.test(lower)) add('help output');
|
|
913
|
+
if (/\b(?:wiki verify|public wiki verify|loop wiki)\b/.test(lower)) add('wiki verification');
|
|
914
|
+
if (/\bclean dry-run\b/.test(lower)) add('clean dry-run');
|
|
915
|
+
if (/\b(?:test slice|regression run|tests?|test)\b.*\bpass(?:es|ed|ing)?\b/.test(lower)
|
|
916
|
+
|| /\bpass(?:es|ed|ing)?\b.*\b(?:test slice|regression run|tests?|test)\b/.test(lower)) {
|
|
917
|
+
add('passing tests');
|
|
918
|
+
}
|
|
919
|
+
if (/\bverifier\b.*\bpass(?:es|ed|ing)?\b/.test(lower)
|
|
920
|
+
|| /\bpass(?:es|ed|ing)?\b.*\bverifier\b/.test(lower)) {
|
|
921
|
+
add('passing verifier');
|
|
922
|
+
}
|
|
923
|
+
if (/\bmission\b.*\bcomplete(?:d)?\b/.test(lower)) add('completed mission');
|
|
924
|
+
if (/\bgit diff --check\b/.test(lower) || /\bdiff cleanliness\b/.test(lower)) add('diff cleanliness');
|
|
925
|
+
return labels;
|
|
926
|
+
}
|
|
927
|
+
|
|
764
928
|
function taskReviewLandingTested(proof) {
|
|
765
|
-
const
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
929
|
+
const maxVisible = 3;
|
|
930
|
+
const commands = taskReviewEvidenceCommands(proof, 20);
|
|
931
|
+
if (commands.length) {
|
|
932
|
+
return commands.length === 1
|
|
933
|
+
? 'I ran the listed check for this result.'
|
|
934
|
+
: 'I ran the listed checks for this result.';
|
|
935
|
+
}
|
|
936
|
+
const paths = taskReviewEvidencePaths(proof, 20);
|
|
937
|
+
if (paths.length) {
|
|
938
|
+
return paths.length === 1
|
|
939
|
+
? 'I inspected the artifact named in the proof.'
|
|
940
|
+
: `I inspected ${paths.length} artifacts named in the proof.`;
|
|
941
|
+
}
|
|
942
|
+
const proseChecks = taskReviewProseCheckLabels(proof);
|
|
943
|
+
if (proseChecks.length) {
|
|
944
|
+
const visible = proseChecks.slice(0, maxVisible);
|
|
945
|
+
const omitted = proseChecks.length - visible.length;
|
|
946
|
+
const suffix = omitted > 0 ? `, and ${omitted} more ${omitted === 1 ? 'check' : 'checks'}` : '';
|
|
947
|
+
return `I checked: ${visible.join(', ')}${suffix}.`;
|
|
948
|
+
}
|
|
769
949
|
return String(proof || '').trim() ? 'I attached the proof below.' : 'No verifier command recorded yet.';
|
|
770
950
|
}
|
|
771
951
|
|
|
@@ -793,16 +973,16 @@ function taskReviewLanding(task, review = {}, payload = {}) {
|
|
|
793
973
|
|| payload.reason || payload.why || metadata.result_reason || metadata.review_reason || metadata.why_it_matters;
|
|
794
974
|
return {
|
|
795
975
|
happened: clipStatusText(explicitHappened || titleToResultText(task.title), 220),
|
|
796
|
-
reason: clipStatusText(explicitReason || titleToReasonText(task, proof), 220),
|
|
976
|
+
reason: clipStatusText(explicitReason || proofToReasonText(proof) || titleToReasonText(task, proof), 220),
|
|
797
977
|
checked: clipStatusText(explicitChecked || proofToHumanCheck(proof), 220),
|
|
798
978
|
tested: clipStatusText(explicitTested || taskReviewLandingTested(proof), 260),
|
|
799
979
|
decision: clipStatusText(explicitDecision || (task.status === 'done'
|
|
800
|
-
? '
|
|
980
|
+
? 'Done. No action needed unless this regresses.'
|
|
801
981
|
: approvalStatus === 'revise'
|
|
802
982
|
? 'Rework requested. Fix the note, then send a new receipt.'
|
|
803
983
|
: approvalStatus === 'pending' && !agentCertified
|
|
804
|
-
? 'Needs one more check
|
|
805
|
-
: '
|
|
984
|
+
? 'Needs one more check; ask for rework if the receipt misses the point.'
|
|
985
|
+
: 'Approve if this matches the request; ask for rework if not.'), 220),
|
|
806
986
|
};
|
|
807
987
|
}
|
|
808
988
|
|
|
@@ -815,13 +995,20 @@ function taskReviewResult(task, review = {}, payload = {}) {
|
|
|
815
995
|
changed: landing.happened,
|
|
816
996
|
reason: landing.reason,
|
|
817
997
|
checked: landing.checked,
|
|
818
|
-
saved: clipStatusText(explicitSaved || (task
|
|
819
|
-
? `Landed as ${ref}.`
|
|
820
|
-
: `Ready to land as ${ref}.`), 180),
|
|
998
|
+
saved: clipStatusText(explicitSaved || taskReviewSavedText(task, review, ref), 180),
|
|
821
999
|
accept: landing.decision,
|
|
822
1000
|
};
|
|
823
1001
|
}
|
|
824
1002
|
|
|
1003
|
+
function taskReviewSavedText(task, review = {}, ref = taskRef(task)) {
|
|
1004
|
+
if (task.status === 'done') return `Result accepted as ${ref}.`;
|
|
1005
|
+
const metadata = task.metadata || {};
|
|
1006
|
+
const agentCertified = review.agent_certified === true || metadata.agent_certified === true;
|
|
1007
|
+
if (task.status === 'review' && agentCertified) return `Result is ready for human approval as ${ref}.`;
|
|
1008
|
+
if (task.status === 'review') return `Completed result saved for review as ${ref}.`;
|
|
1009
|
+
return `Work record saved as ${ref}.`;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
825
1012
|
function taskReviewSummary(task) {
|
|
826
1013
|
const reviewed = (task.events || []).slice().reverse().find(e => e.event_type === 'reviewed' || e.event_type === 'proof_ready' || e.event_type === 'revision_requested');
|
|
827
1014
|
const payload = reviewed && reviewed.payload || {};
|
|
@@ -978,6 +1165,8 @@ function buildTaskStreams(tasks, goals) {
|
|
|
978
1165
|
if (column === 'doing') stream.doing_count += 1;
|
|
979
1166
|
if (column === 'blocked') stream.blocked_count += 1;
|
|
980
1167
|
if (column === 'review') stream.review_count += 1;
|
|
1168
|
+
const review = task.review || {};
|
|
1169
|
+
const metadata = task.metadata || {};
|
|
981
1170
|
stream.tasks.push({
|
|
982
1171
|
id: task.id,
|
|
983
1172
|
title: task.title,
|
|
@@ -987,7 +1176,7 @@ function buildTaskStreams(tasks, goals) {
|
|
|
987
1176
|
assigned_to: taskAssignee(task),
|
|
988
1177
|
parent_task_id: task.lineage && task.lineage.parent_task_id || null,
|
|
989
1178
|
child_task_ids: task.lineage && task.lineage.child_task_ids || [],
|
|
990
|
-
proof:
|
|
1179
|
+
proof: review.proof || metadata.latest_agent_proof || null,
|
|
991
1180
|
});
|
|
992
1181
|
}
|
|
993
1182
|
for (const goal of goals) {
|
|
@@ -1292,17 +1481,142 @@ function taskReviewCommandLooksSpecific(command) {
|
|
|
1292
1481
|
if (/^atris\s+task\s+\w+\s*$/i.test(text)) return false;
|
|
1293
1482
|
if (/^atris\s+task\s+(?:accept|auto-accept-certified)\b/i.test(text)) return false;
|
|
1294
1483
|
if (/^atris[/.]/i.test(text)) return false;
|
|
1484
|
+
if (/^atris-\S+/i.test(text)) return false;
|
|
1295
1485
|
if (/^atris\s+task\s+\w+\s+json\s*$/i.test(text) && !/--json\b/i.test(text)) return false;
|
|
1296
1486
|
if (/^atris\s+(?:command|review-chat|smoke|temp)\b/i.test(text)) return false;
|
|
1297
1487
|
if (/^(?:npm|npx|pnpm|yarn|python3?|pytest|bash|sh|tsc|vitest|curl|gh|rg|git)\s+commands?\b/i.test(text)) return false;
|
|
1298
1488
|
if (/^(?:npm|pnpm|yarn)\s+(?:tests|checks?)$/i.test(text)) return false;
|
|
1299
1489
|
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;
|
|
1490
|
+
if (/\s+(?:and|then)\s+\S+/i.test(taskReviewWithoutQuotedSegments(text))) return false;
|
|
1301
1491
|
if (/^node\s+(?!-|\S*(?:[/.]))/i.test(text)) return false;
|
|
1302
1492
|
if (/^node\s+--test\s+[\w-]+(?:\s+[\w-]+)+$/i.test(text)) return false;
|
|
1303
1493
|
return true;
|
|
1304
1494
|
}
|
|
1305
1495
|
|
|
1496
|
+
function taskReviewUnescapedQuoteCount(text, quote) {
|
|
1497
|
+
let count = 0;
|
|
1498
|
+
const source = String(text || '');
|
|
1499
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
1500
|
+
if (source[index] === quote && source[index - 1] !== '\\') count += 1;
|
|
1501
|
+
}
|
|
1502
|
+
return count;
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
function taskReviewCommandStartsInsideQuote(clause, commandIndex) {
|
|
1506
|
+
const before = String(clause || '').slice(0, Math.max(0, commandIndex));
|
|
1507
|
+
return taskReviewUnescapedQuoteCount(before, "'") % 2 === 1
|
|
1508
|
+
|| taskReviewUnescapedQuoteCount(before, '"') % 2 === 1;
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
function taskReviewWithoutQuotedSegments(text) {
|
|
1512
|
+
const source = String(text || '');
|
|
1513
|
+
let quote = null;
|
|
1514
|
+
let out = '';
|
|
1515
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
1516
|
+
const char = source[index];
|
|
1517
|
+
if ((char === "'" || char === '"') && source[index - 1] !== '\\') {
|
|
1518
|
+
quote = quote === char ? null : (!quote ? char : quote);
|
|
1519
|
+
out += ' ';
|
|
1520
|
+
continue;
|
|
1521
|
+
}
|
|
1522
|
+
out += quote ? ' ' : char;
|
|
1523
|
+
}
|
|
1524
|
+
return out;
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
function taskReviewTrimOutsideQuotedTail(text, pattern) {
|
|
1528
|
+
const source = String(text || '');
|
|
1529
|
+
const flags = pattern.flags.includes('g') ? pattern.flags : `${pattern.flags}g`;
|
|
1530
|
+
const scanner = new RegExp(pattern.source, flags);
|
|
1531
|
+
let match;
|
|
1532
|
+
while ((match = scanner.exec(source)) !== null) {
|
|
1533
|
+
if (!taskReviewCommandStartsInsideQuote(source, match.index)) {
|
|
1534
|
+
return source.slice(0, match.index);
|
|
1535
|
+
}
|
|
1536
|
+
if (match[0] === '') scanner.lastIndex += 1;
|
|
1537
|
+
}
|
|
1538
|
+
return source;
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
function taskReviewTrimTrailingOutsideQuote(text) {
|
|
1542
|
+
let clean = String(text || '');
|
|
1543
|
+
while (clean.length > 0) {
|
|
1544
|
+
const index = clean.length - 1;
|
|
1545
|
+
if (!/[\]),.;:]/.test(clean[index])) break;
|
|
1546
|
+
if (taskReviewCommandStartsInsideQuote(clean, index)) break;
|
|
1547
|
+
clean = clean.slice(0, index);
|
|
1548
|
+
}
|
|
1549
|
+
return clean;
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
function taskReviewCleanEvidenceCommand(command) {
|
|
1553
|
+
let clean = String(command || '');
|
|
1554
|
+
const tailPatterns = [
|
|
1555
|
+
/\s+from\s+[^,;]*(?:showed|shows|showing|returned|returns|printed|prints|wrote|writes|reported|reports)\b.*$/i,
|
|
1556
|
+
/\s+(?:showed|shows|showing|returned|returns|printed|prints|wrote|writes|reported|reports)\b.*$/i,
|
|
1557
|
+
/\.\s+(?:Reward remains|No human|Human accept|AgentXP|XP)\b.*$/i,
|
|
1558
|
+
/\s+with\s+\d+\s+(?:passing\s+)?(?:tests?|checks?|passes|passed|pass|ok|clean)\b.*$/i,
|
|
1559
|
+
/\s+\((?:passed|ok|clean|failed|errored|timed out|succeeded|succeeds|successful|confirmed)\)$/i,
|
|
1560
|
+
/\s+\(?(?:exit|status|code)\s+\d+\)?$/i,
|
|
1561
|
+
/\s+\(?(?:passed|ok|clean|failed|errored|timed out|succeeded|succeeds|successful|confirmed)\s+\d+\/\d+(?:[,.]\s+.*)?$/i,
|
|
1562
|
+
/\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,
|
|
1563
|
+
/\s+\(?(?:passed|ok|clean|failed|errored|timed out|succeeded|succeeds|successful|confirmed)(?:[,.]\s+.*|\s+and\b.*)$/i,
|
|
1564
|
+
/\s+\(?(?:passed|ok|clean|failed|errored|timed out|succeeded|succeeds|successful|confirmed)\s+with\b.*$/i,
|
|
1565
|
+
/,\s+(?:command suite|mission-status tests?|live\b|focused suite|clean dry-run|brain compile)\b.*$/i,
|
|
1566
|
+
/\.\s+(?:The|This|It|Focused|Dogfood|Landing|Product|Review|Receipt|Human|No|Note)\b.*$/i,
|
|
1567
|
+
/\s+\(?\d+\s+(?:passes|passed|pass|ok|clean|tests?|checks?)\)?$/i,
|
|
1568
|
+
/\s+only$/i,
|
|
1569
|
+
/\s+\(?(?:passed|ok|clean|failed|errored|timed out|succeeded|succeeds|successful|confirmed)(?:[.:]\s+.*|\s+after\b.*)$/i,
|
|
1570
|
+
/\s+\(?(?:passed|ok|clean|failed|errored|timed out|succeeded|succeeds|successful|confirmed)\)?$/i,
|
|
1571
|
+
/\s+\(?\d+\/\d+(?:\s+(?:tests?|checks?|passed|pass|ok|clean|failed|failures?))?\)?$/i,
|
|
1572
|
+
/\s+\d+\/\d+(?:\s+(?:tests?|checks?|passed|pass|ok|clean|failed|failures?))?$/i,
|
|
1573
|
+
];
|
|
1574
|
+
for (const pattern of tailPatterns) {
|
|
1575
|
+
clean = taskReviewTrimOutsideQuotedTail(clean, pattern);
|
|
1576
|
+
clean = taskReviewTrimTrailingOutsideQuote(clean);
|
|
1577
|
+
}
|
|
1578
|
+
return clean.replace(/\s+/g, ' ').trim();
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
function taskReviewSplitEvidenceClauses(source, commandStart) {
|
|
1582
|
+
const text = String(source || '');
|
|
1583
|
+
const hardBoundaryPattern = /^(?:;\s*|\n\s*|\s+&&\s+)/;
|
|
1584
|
+
const softBoundaryPattern = new RegExp(`^(?:,\\s+|\\.\\s+|\\s+and\\s+|\\s+then\\s+|\\.\\s+(?:focused|full|behavior|verifier)[^\\n.;:]{0,80}:\\s+)(?=${commandStart})`, 'i');
|
|
1585
|
+
const clauses = [];
|
|
1586
|
+
let clause = '';
|
|
1587
|
+
let quote = null;
|
|
1588
|
+
for (let index = 0; index < text.length;) {
|
|
1589
|
+
const char = text[index];
|
|
1590
|
+
if ((char === "'" || char === '"') && text[index - 1] !== '\\') {
|
|
1591
|
+
if (quote === char) quote = null;
|
|
1592
|
+
else if (!quote) quote = char;
|
|
1593
|
+
clause += char;
|
|
1594
|
+
index += 1;
|
|
1595
|
+
continue;
|
|
1596
|
+
}
|
|
1597
|
+
if (!quote) {
|
|
1598
|
+
const rest = text.slice(index);
|
|
1599
|
+
const boundary = rest.match(hardBoundaryPattern) || rest.match(softBoundaryPattern);
|
|
1600
|
+
if (boundary) {
|
|
1601
|
+
if (clause.trim()) clauses.push(clause);
|
|
1602
|
+
clause = '';
|
|
1603
|
+
index += boundary[0].length;
|
|
1604
|
+
continue;
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
clause += char;
|
|
1608
|
+
index += 1;
|
|
1609
|
+
}
|
|
1610
|
+
if (clause.trim()) clauses.push(clause);
|
|
1611
|
+
return clauses;
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
function taskReviewCommandIsDescribedSubject(clause, commandIndex) {
|
|
1615
|
+
const before = String(clause || '').slice(Math.max(0, commandIndex - 80), commandIndex);
|
|
1616
|
+
return /\b(?:contains?|displays?|extracts?|lists?|mentions?|names?|prints?|renders?|shows?)\s+(?:only\s+)?$/i.test(before)
|
|
1617
|
+
|| /\b(?:including|includes?|like|such as|for example|e\.g\.)\s+$/i.test(before);
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1306
1620
|
function taskReviewEvidenceCommands(text, limit = 8) {
|
|
1307
1621
|
const source = String(text || '').trim();
|
|
1308
1622
|
if (!source) return [];
|
|
@@ -1310,17 +1624,14 @@ function taskReviewEvidenceCommands(text, limit = 8) {
|
|
|
1310
1624
|
const envPrefix = '(?:(?:[A-Z_][A-Z0-9_]*=[^\\s,;|]+)\\s+)*';
|
|
1311
1625
|
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
1626
|
const commandStart = `${prosePrefix}${envPrefix}${commandWord}\\b`;
|
|
1313
|
-
const commandStartPattern = new RegExp(`(^|[^\\w
|
|
1627
|
+
const commandStartPattern = new RegExp(`(^|[^\\w./=-])(${commandStart})`, 'i');
|
|
1314
1628
|
const commandStartInnerPattern = new RegExp(`${envPrefix}${commandWord}\\b`, 'i');
|
|
1315
|
-
const
|
|
1316
|
-
const clauses = source
|
|
1629
|
+
const normalized = source
|
|
1317
1630
|
.replace(/\r/g, '\n')
|
|
1318
1631
|
.replace(/```[ \t]*(?:bash|sh|shell|zsh|console|text|txt)?[ \t]*\n/gi, '\n')
|
|
1319
1632
|
.replace(/```/g, '\n')
|
|
1320
|
-
.replace(/`/g, '')
|
|
1321
|
-
|
|
1322
|
-
.replace(commandBoundaryPattern, '\n')
|
|
1323
|
-
.split('\n');
|
|
1633
|
+
.replace(/`/g, '');
|
|
1634
|
+
const clauses = taskReviewSplitEvidenceClauses(normalized, commandStart);
|
|
1324
1635
|
const out = [];
|
|
1325
1636
|
const seen = new Set();
|
|
1326
1637
|
for (const clause of clauses) {
|
|
@@ -1330,22 +1641,13 @@ function taskReviewEvidenceCommands(text, limit = 8) {
|
|
|
1330
1641
|
const raw = clause.slice(start.index + Math.max(0, commandStartOffset));
|
|
1331
1642
|
const prefix = raw.match(commandStartInnerPattern);
|
|
1332
1643
|
const commandOffset = prefix && prefix.index != null ? prefix.index : 0;
|
|
1644
|
+
const commandIndex = start.index + Math.max(0, commandStartOffset) + Math.max(0, commandOffset);
|
|
1645
|
+
if (
|
|
1646
|
+
taskReviewCommandStartsInsideQuote(clause, commandIndex)
|
|
1647
|
+
|| taskReviewCommandIsDescribedSubject(clause, commandIndex)
|
|
1648
|
+
) continue;
|
|
1333
1649
|
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();
|
|
1650
|
+
const clean = taskReviewCleanEvidenceCommand(command);
|
|
1349
1651
|
if (!taskReviewCommandLooksSpecific(clean) || seen.has(clean.toLowerCase())) continue;
|
|
1350
1652
|
seen.add(clean.toLowerCase());
|
|
1351
1653
|
out.push(clean);
|
|
@@ -1365,13 +1667,24 @@ function taskReviewRecentThread(task, limit = 4) {
|
|
|
1365
1667
|
.filter(message => message.content);
|
|
1366
1668
|
}
|
|
1367
1669
|
|
|
1670
|
+
function taskReviewObjective(task) {
|
|
1671
|
+
const metadata = task && task.metadata || {};
|
|
1672
|
+
return task && (
|
|
1673
|
+
metadata.task_goal
|
|
1674
|
+
|| task.title
|
|
1675
|
+
|| metadata.goal_objective
|
|
1676
|
+
|| metadata.objective
|
|
1677
|
+
|| task.objective
|
|
1678
|
+
) || '';
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1368
1681
|
function taskReviewVerificationFocus(task) {
|
|
1369
1682
|
const review = task && task.review || {};
|
|
1370
1683
|
const metadata = task && task.metadata || {};
|
|
1371
1684
|
const proof = review.proof || metadata.latest_agent_proof || '';
|
|
1372
1685
|
const lesson = review.lesson || metadata.latest_agent_lesson || '';
|
|
1373
1686
|
const nextTask = review.next_task || metadata.latest_agent_next_task || '';
|
|
1374
|
-
const objective =
|
|
1687
|
+
const objective = taskReviewObjective(task);
|
|
1375
1688
|
const evidenceText = [proof].filter(Boolean).join('\n');
|
|
1376
1689
|
return {
|
|
1377
1690
|
objective: taskReviewClip(objective, 260) || null,
|
|
@@ -1388,7 +1701,7 @@ function taskReviewSpecificCodexPrompt(task, focus, actor) {
|
|
|
1388
1701
|
const title = taskReviewClip(task && task.title, 180);
|
|
1389
1702
|
const proof = focus && focus.proof_claim ? ` Proof: ${taskReviewClip(focus.proof_claim, 1800)}` : '';
|
|
1390
1703
|
const commands = focus && focus.commands_to_verify && focus.commands_to_verify.length
|
|
1391
|
-
? ` Commands: ${focus.commands_to_verify
|
|
1704
|
+
? ` Commands: ${taskReviewFormatCommandList(focus.commands_to_verify)}.`
|
|
1392
1705
|
: '';
|
|
1393
1706
|
const files = focus && focus.files_to_inspect && focus.files_to_inspect.length
|
|
1394
1707
|
? ` Files/artifacts: ${focus.files_to_inspect.join(', ')}.`
|
|
@@ -1475,7 +1788,7 @@ function taskReviewChatContract(task, { reviewer = 'codex-review', allowCertifie
|
|
|
1475
1788
|
const proof = review.proof || metadata.latest_agent_proof || '';
|
|
1476
1789
|
const lesson = review.lesson || metadata.latest_agent_lesson || '';
|
|
1477
1790
|
const nextTask = review.next_task || metadata.latest_agent_next_task || '';
|
|
1478
|
-
const objective =
|
|
1791
|
+
const objective = taskReviewObjective(task);
|
|
1479
1792
|
const verificationFocus = taskReviewVerificationFocus(task);
|
|
1480
1793
|
const actor = reviewActor(reviewer);
|
|
1481
1794
|
return {
|
|
@@ -1501,7 +1814,7 @@ function taskReviewChatContract(task, { reviewer = 'codex-review', allowCertifie
|
|
|
1501
1814
|
required_checks: [
|
|
1502
1815
|
`Run ${`atris task show ${taskRef(task)} --json`} and read the current proof plus dialogue.`,
|
|
1503
1816
|
verificationFocus.commands_to_verify.length
|
|
1504
|
-
? `Re-run or inspect these proof commands: ${verificationFocus.commands_to_verify
|
|
1817
|
+
? `Re-run or inspect these proof commands: ${taskReviewFormatCommandList(verificationFocus.commands_to_verify)}.`
|
|
1505
1818
|
: 'Find the concrete verifier command because the proof did not name one.',
|
|
1506
1819
|
verificationFocus.files_to_inspect.length
|
|
1507
1820
|
? `Inspect these named files/artifacts before certifying: ${verificationFocus.files_to_inspect.join(', ')}.`
|
|
@@ -1755,6 +2068,17 @@ function taskStatusSummary(projection, { history = false, hasExistingReviewFollo
|
|
|
1755
2068
|
doing_count: stream.doing_count,
|
|
1756
2069
|
review_count: stream.review_count,
|
|
1757
2070
|
blocked_count: stream.blocked_count,
|
|
2071
|
+
tasks: (stream.tasks || []).map(task => ({
|
|
2072
|
+
id: task.id,
|
|
2073
|
+
title: clipStatusText(task.title, 120),
|
|
2074
|
+
status: task.status,
|
|
2075
|
+
tag: task.tag || null,
|
|
2076
|
+
claimed_by: task.claimed_by || null,
|
|
2077
|
+
assigned_to: task.assigned_to || null,
|
|
2078
|
+
parent_task_id: task.parent_task_id || null,
|
|
2079
|
+
child_task_ids: task.child_task_ids || [],
|
|
2080
|
+
proof: task.proof || null,
|
|
2081
|
+
})),
|
|
1758
2082
|
})),
|
|
1759
2083
|
last_updated_at: lastUpdated ? new Date(lastUpdated).toISOString() : null,
|
|
1760
2084
|
};
|
|
@@ -3999,8 +4323,19 @@ function cmdReviewLaneRun(args) {
|
|
|
3999
4323
|
function reviewQueueLimit(args, total) {
|
|
4000
4324
|
if (hasFlag(args, '--all')) return total;
|
|
4001
4325
|
const raw = flag(args, '--limit');
|
|
4002
|
-
const limit = raw && raw !== true ? Number(raw) :
|
|
4003
|
-
return Number.isFinite(limit) && limit > 0 ? Math.floor(limit) :
|
|
4326
|
+
const limit = raw && raw !== true ? Number(raw) : 5;
|
|
4327
|
+
return Number.isFinite(limit) && limit > 0 ? Math.floor(limit) : 5;
|
|
4328
|
+
}
|
|
4329
|
+
|
|
4330
|
+
function reviewQueueVerbose(args) {
|
|
4331
|
+
return hasFlag(args, '--verbose') || hasFlag(args, '--details') || hasFlag(args, '--all');
|
|
4332
|
+
}
|
|
4333
|
+
|
|
4334
|
+
function reviewGroupTextLimit(args, total) {
|
|
4335
|
+
if (hasFlag(args, '--all')) return total;
|
|
4336
|
+
const raw = flag(args, '--limit');
|
|
4337
|
+
const limit = raw && raw !== true ? Number(raw) : 10;
|
|
4338
|
+
return Number.isFinite(limit) && limit > 0 ? Math.floor(limit) : 10;
|
|
4004
4339
|
}
|
|
4005
4340
|
|
|
4006
4341
|
// Risk order for human attention: named receipts that are missing/failing (0)
|
|
@@ -4175,17 +4510,23 @@ function cmdReviews(args) {
|
|
|
4175
4510
|
printJson({ ok: true, action: 'review_groups', projection_path: outPath, groups });
|
|
4176
4511
|
return;
|
|
4177
4512
|
}
|
|
4178
|
-
console.log(`READY
|
|
4179
|
-
console.log(`${groups.total_certified} ready
|
|
4180
|
-
groups.groups.
|
|
4513
|
+
console.log(`READY FOR APPROVAL — grouped by ${key}`);
|
|
4514
|
+
console.log(`${groups.total_certified} ready for approval across ${groups.group_count} ${key} group(s)`);
|
|
4515
|
+
const visibleGroups = groups.groups.slice(0, reviewGroupTextLimit(args, groups.groups.length));
|
|
4516
|
+
visibleGroups.forEach((g, index) => {
|
|
4181
4517
|
console.log('');
|
|
4182
4518
|
console.log(`${index + 1}. ${g.value} — ${g.count} task${g.count === 1 ? '' : 's'}`);
|
|
4183
4519
|
g.sample_titles.forEach(title => console.log(` • ${title}`));
|
|
4184
|
-
console.log(`
|
|
4520
|
+
console.log(` approve this group: ${g.accept_group_command} --confirm-human-accept --as <you>`);
|
|
4185
4521
|
});
|
|
4522
|
+
if (visibleGroups.length < groups.groups.length) {
|
|
4523
|
+
console.log('');
|
|
4524
|
+
console.log(`Showing ${visibleGroups.length}/${groups.groups.length} groups; rerun with --all for every group or --limit N to adjust.`);
|
|
4525
|
+
}
|
|
4186
4526
|
return;
|
|
4187
4527
|
}
|
|
4188
4528
|
const queue = taskReviewQueue(projection, args);
|
|
4529
|
+
const verbose = reviewQueueVerbose(args);
|
|
4189
4530
|
if (wantsJson(args)) {
|
|
4190
4531
|
printJson({
|
|
4191
4532
|
ok: true,
|
|
@@ -4195,10 +4536,10 @@ function cmdReviews(args) {
|
|
|
4195
4536
|
});
|
|
4196
4537
|
return;
|
|
4197
4538
|
}
|
|
4198
|
-
console.log('READY
|
|
4199
|
-
console.log(`${queue.counts.certified} ready
|
|
4539
|
+
console.log('READY FOR APPROVAL');
|
|
4540
|
+
console.log(`${queue.counts.certified} ready for approval / ${queue.counts.blocking} need one more check / ${queue.counts.review} total waiting`);
|
|
4200
4541
|
if (!queue.items.length) {
|
|
4201
|
-
console.log('Nothing is ready
|
|
4542
|
+
console.log('Nothing is ready for approval.');
|
|
4202
4543
|
return;
|
|
4203
4544
|
}
|
|
4204
4545
|
queue.items.forEach((item, index) => {
|
|
@@ -4216,8 +4557,8 @@ function cmdReviews(args) {
|
|
|
4216
4557
|
if (item.result?.saved) console.log(` Saved: ${item.result.saved}`);
|
|
4217
4558
|
if (item.landing.decision) console.log(` Decision: ${item.landing.decision}`);
|
|
4218
4559
|
}
|
|
4219
|
-
if (item.proof) console.log(` details: ${item.proof}`);
|
|
4220
|
-
if (item.evidence) {
|
|
4560
|
+
if (verbose && item.proof) console.log(` details: ${item.proof}`);
|
|
4561
|
+
if (verbose && item.evidence) {
|
|
4221
4562
|
item.evidence.receipts.forEach((receipt) => {
|
|
4222
4563
|
const verdict = receipt.verifier_passed === true ? ' verifier:passed'
|
|
4223
4564
|
: receipt.verifier_passed === false ? ' verifier:FAILED' : '';
|
|
@@ -4225,15 +4566,15 @@ function cmdReviews(args) {
|
|
|
4225
4566
|
});
|
|
4226
4567
|
item.evidence.missing.forEach((missingPath) => console.log(` receipt: ${missingPath} MISSING`));
|
|
4227
4568
|
}
|
|
4228
|
-
if (item.review_chat_command) console.log(` /codex: ${item.review_chat_command}`);
|
|
4569
|
+
if (verbose && item.review_chat_command) console.log(` /codex: ${item.review_chat_command}`);
|
|
4229
4570
|
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(`
|
|
4571
|
+
if (item.accept_command) console.log(` approve: ${item.accept_command}`);
|
|
4572
|
+
else if (item.blocked_accept_reason) console.log(` approve: blocked (${item.blocked_accept_reason})`);
|
|
4573
|
+
console.log(` rework: ${item.revise_command}`);
|
|
4233
4574
|
});
|
|
4234
4575
|
if (queue.counts.shown < queue.counts.certified) {
|
|
4235
4576
|
console.log('');
|
|
4236
|
-
console.log(`Showing ${queue.counts.shown}/${queue.counts.certified}; rerun with --all or --
|
|
4577
|
+
console.log(`Showing ${queue.counts.shown}/${queue.counts.certified}; rerun with --all for every row or --verbose for proof details.`);
|
|
4237
4578
|
}
|
|
4238
4579
|
}
|
|
4239
4580
|
|
|
@@ -4858,7 +5199,17 @@ function cmdClaim(args) {
|
|
|
4858
5199
|
}
|
|
4859
5200
|
}
|
|
4860
5201
|
|
|
4861
|
-
function
|
|
5202
|
+
function liveTaskWithTitle(tasks, title) {
|
|
5203
|
+
const wanted = String(title || '').trim().toLowerCase();
|
|
5204
|
+
if (!wanted) return null;
|
|
5205
|
+
return (tasks || []).find(task => {
|
|
5206
|
+
const taskTitle = String(task.title || task.task || '').trim().toLowerCase();
|
|
5207
|
+
const status = String(task.status || '').toLowerCase();
|
|
5208
|
+
return taskTitle === wanted && !new Set(['done', 'accepted', 'failed']).has(status);
|
|
5209
|
+
}) || null;
|
|
5210
|
+
}
|
|
5211
|
+
|
|
5212
|
+
function readEndgameAgentAction(root, owner, { tasks = [] } = {}) {
|
|
4862
5213
|
const todoPath = path.join(root || process.cwd(), 'atris', 'TODO.md');
|
|
4863
5214
|
if (!fs.existsSync(todoPath)) return null;
|
|
4864
5215
|
const content = fs.readFileSync(todoPath, 'utf8');
|
|
@@ -4869,6 +5220,8 @@ function readEndgameAgentAction(root, owner) {
|
|
|
4869
5220
|
if (!slug && !horizon) return null;
|
|
4870
5221
|
const member = String(owner || DEFAULT_OWNER);
|
|
4871
5222
|
const taskSeed = buildEndgameTaskSeed({ slug, horizon, owner: member });
|
|
5223
|
+
const existing = liveTaskWithTitle(tasks, taskSeed.title);
|
|
5224
|
+
if (existing) return null;
|
|
4872
5225
|
return {
|
|
4873
5226
|
kind: 'create_bounded_endgame_task',
|
|
4874
5227
|
endgame_slug: slug || null,
|
|
@@ -4992,7 +5345,7 @@ function cmdNext(args) {
|
|
|
4992
5345
|
? continueWorkCommandForTask(reviewTask, { owner })
|
|
4993
5346
|
: null;
|
|
4994
5347
|
const nextAgentAction = handoff.next_action === 'human_accept_waiting'
|
|
4995
|
-
? readEndgameAgentAction(taskDb.workspaceRoot(), owner)
|
|
5348
|
+
? readEndgameAgentAction(taskDb.workspaceRoot(), owner, { tasks: projection.tasks || [] })
|
|
4996
5349
|
: null;
|
|
4997
5350
|
if (hasFlag(args, '--create-next')) {
|
|
4998
5351
|
if (!nextAgentAction || !nextAgentAction.task_seed) {
|
|
@@ -5042,12 +5395,12 @@ function cmdNext(args) {
|
|
|
5042
5395
|
}
|
|
5043
5396
|
console.log('No open tasks.');
|
|
5044
5397
|
console.log(handoff.next_action === 'agent_review_again'
|
|
5045
|
-
? `${taskRef(reviewTask)} needs one more agent check before
|
|
5046
|
-
: `${taskRef(reviewTask)} is ready
|
|
5398
|
+
? `${taskRef(reviewTask)} needs one more agent check before approval.`
|
|
5399
|
+
: `${taskRef(reviewTask)} is ready for approval.`);
|
|
5047
5400
|
console.log(handoff.next_action === 'continue_work'
|
|
5048
|
-
? 'Continue work elsewhere; XP
|
|
5401
|
+
? 'Continue work elsewhere; XP is awarded only after the human approves this task.'
|
|
5049
5402
|
: handoff.next_action === 'human_accept_waiting'
|
|
5050
|
-
? (nextAgentAction ? nextAgentAction.message : 'No next agent task is attached; this task is ready
|
|
5403
|
+
? (nextAgentAction ? nextAgentAction.message : 'No next agent task is attached; this task is ready for human approval.')
|
|
5051
5404
|
: 'Review this task again before continuing.');
|
|
5052
5405
|
if (nextAgentAction) console.log(`Command: ${nextAgentAction.command}`);
|
|
5053
5406
|
if (nextAgentAction && nextAgentAction.task_seed) {
|
|
@@ -5459,9 +5812,9 @@ function cmdShow(args) {
|
|
|
5459
5812
|
const owner = task.claimed_by ? ` / ${task.claimed_by}` : '';
|
|
5460
5813
|
const tag = task.tag ? ` #${task.tag}` : '';
|
|
5461
5814
|
const statusLabel = task.status === 'review'
|
|
5462
|
-
? 'READY
|
|
5815
|
+
? 'READY FOR APPROVAL'
|
|
5463
5816
|
: task.status === 'done'
|
|
5464
|
-
? '
|
|
5817
|
+
? 'DONE'
|
|
5465
5818
|
: task.status.toUpperCase();
|
|
5466
5819
|
console.log(`${statusLabel} ${taskRef(task)} v${task.current_version}${owner}${tag}`);
|
|
5467
5820
|
console.log(task.title);
|
|
@@ -5932,13 +6285,80 @@ function buildAcceptHumanResult({ task, proof, nextTask, publicSync }) {
|
|
|
5932
6285
|
return { changed, checked, try_next: tryNext };
|
|
5933
6286
|
}
|
|
5934
6287
|
|
|
5935
|
-
function
|
|
5936
|
-
|
|
6288
|
+
function refreshBrainScorecardsAfterAccept(root = process.cwd()) {
|
|
6289
|
+
try {
|
|
6290
|
+
const { recordTaskEpisodeScorecards } = require('../commands/brain');
|
|
6291
|
+
return { ok: true, ...recordTaskEpisodeScorecards({ root }) };
|
|
6292
|
+
} catch (error) {
|
|
6293
|
+
return {
|
|
6294
|
+
ok: false,
|
|
6295
|
+
error: error && error.message ? error.message : String(error),
|
|
6296
|
+
};
|
|
6297
|
+
}
|
|
6298
|
+
}
|
|
6299
|
+
|
|
6300
|
+
function nextMissionRouteAfterAccept(root = process.cwd()) {
|
|
6301
|
+
try {
|
|
6302
|
+
const { selectCodexGoalMission } = require('../commands/mission');
|
|
6303
|
+
const selected = selectCodexGoalMission(root);
|
|
6304
|
+
const mission = selected && selected.mission;
|
|
6305
|
+
if (!mission) return { ok: true, objective: null, route: 'next mission: none' };
|
|
6306
|
+
return {
|
|
6307
|
+
ok: true,
|
|
6308
|
+
mission_id: mission.id,
|
|
6309
|
+
objective: mission.objective,
|
|
6310
|
+
reason: selected.reason,
|
|
6311
|
+
route: `next mission: ${cleanPublicText(mission.objective, 160)}`,
|
|
6312
|
+
command: mission.next_action || `atris mission run ${mission.id}`,
|
|
6313
|
+
};
|
|
6314
|
+
} catch (error) {
|
|
6315
|
+
return {
|
|
6316
|
+
ok: false,
|
|
6317
|
+
route: 'next mission: needs refresh',
|
|
6318
|
+
error: error && error.message ? error.message : String(error),
|
|
6319
|
+
};
|
|
6320
|
+
}
|
|
6321
|
+
}
|
|
6322
|
+
|
|
6323
|
+
function xpLandingText(xpProjection) {
|
|
6324
|
+
if (!xpProjection) return 'XP updated';
|
|
6325
|
+
if (xpProjection.ok === false) return 'XP refresh failed';
|
|
6326
|
+
const candidates = [
|
|
6327
|
+
xpProjection.total_agent_xp,
|
|
6328
|
+
xpProjection.total_xp,
|
|
6329
|
+
xpProjection.summary && xpProjection.summary.total_agent_xp,
|
|
6330
|
+
xpProjection.totals && xpProjection.totals.total_agent_xp,
|
|
6331
|
+
];
|
|
6332
|
+
const total = candidates.map(Number).find((value) => Number.isFinite(value));
|
|
6333
|
+
return Number.isFinite(total) ? `XP updated (${total} total)` : 'XP updated';
|
|
6334
|
+
}
|
|
6335
|
+
|
|
6336
|
+
function brainScorecardLandingText(brainScorecards) {
|
|
6337
|
+
if (!brainScorecards) return 'brain scorecards checked';
|
|
6338
|
+
if (brainScorecards.ok === false) return 'brain scorecard refresh failed';
|
|
6339
|
+
const written = Number(brainScorecards.written);
|
|
6340
|
+
return Number.isFinite(written) ? `brain scorecards +${written}` : 'brain scorecards checked';
|
|
6341
|
+
}
|
|
6342
|
+
|
|
6343
|
+
function buildVisibleAcceptReceipt(result, { xpProjection, brainScorecards, nextMissionRoute } = {}) {
|
|
6344
|
+
const updates = [
|
|
6345
|
+
xpLandingText(xpProjection),
|
|
6346
|
+
brainScorecardLandingText(brainScorecards),
|
|
6347
|
+
].filter(Boolean);
|
|
6348
|
+
const checked = updates.length ? `${result.checked}; ${updates.join('; ')}` : result.checked;
|
|
6349
|
+
const route = cleanPublicText(nextMissionRoute && nextMissionRoute.route, 180);
|
|
6350
|
+
const tryNext = route ? `${result.try_next}; ${route}` : result.try_next;
|
|
6351
|
+
return { ...result, checked, try_next: tryNext };
|
|
6352
|
+
}
|
|
6353
|
+
|
|
6354
|
+
function renderAcceptLanding({ task, proof, nextTask, publicSync, xpProjection, brainScorecards, nextMissionRoute }) {
|
|
6355
|
+
const result = buildAcceptHumanResult({
|
|
5937
6356
|
task,
|
|
5938
6357
|
proof,
|
|
5939
6358
|
nextTask,
|
|
5940
6359
|
publicSync,
|
|
5941
|
-
})
|
|
6360
|
+
});
|
|
6361
|
+
return renderPublicResult(buildVisibleAcceptReceipt(result, { xpProjection, brainScorecards, nextMissionRoute }));
|
|
5942
6362
|
}
|
|
5943
6363
|
|
|
5944
6364
|
async function publishAcceptAgentXp(args, actor) {
|
|
@@ -6322,7 +6742,7 @@ function taskPageNextAction(task, current, actions, { hasExistingReviewFollowUp
|
|
|
6322
6742
|
if (handoff && handoff.next_action === 'human_accept_waiting') {
|
|
6323
6743
|
return {
|
|
6324
6744
|
key: 'human_accept_waiting',
|
|
6325
|
-
label: 'Ready
|
|
6745
|
+
label: 'Ready for approval',
|
|
6326
6746
|
command: null,
|
|
6327
6747
|
api: null,
|
|
6328
6748
|
human_accept_command: actions.human_accept_command || null,
|
|
@@ -6444,7 +6864,7 @@ function appendTaskReviewChat(taskDb, db, taskId, { reviewer = 'codex-review', d
|
|
|
6444
6864
|
throw taskReviewChatError(`not_reviewable_${task.status}`, `review chat requires a task in Review; current status is ${task.status}`, { status: 409, exitCode: 1 });
|
|
6445
6865
|
}
|
|
6446
6866
|
if (!taskAllowsReviewChat(task, { allowCertified: true })) {
|
|
6447
|
-
throw taskReviewChatError('agent_certified_continue_work', 'review chat is closed after agent certification; continue other work or
|
|
6867
|
+
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
6868
|
}
|
|
6449
6869
|
const contract = taskReviewChatContract(task, { reviewer: actor, allowCertified: true });
|
|
6450
6870
|
let event = null;
|
|
@@ -6601,8 +7021,8 @@ function readyHandoffForStep(task, proof, lesson, nextTask, agentCertified) {
|
|
|
6601
7021
|
career_xp_status: 'pending_human_accept',
|
|
6602
7022
|
next_action: agentCertified ? certifiedReviewNextAction(nextTask) : 'agent_review_again',
|
|
6603
7023
|
rule: agentCertified
|
|
6604
|
-
? 'Double-check complete; ready to keep moving. XP
|
|
6605
|
-
: 'Proof is ready; one more agent check before
|
|
7024
|
+
? 'Double-check complete; ready to keep moving. XP is awarded only after the human approves the task.'
|
|
7025
|
+
: 'Proof is ready; one more agent check before human approval. XP waits for the human.',
|
|
6606
7026
|
};
|
|
6607
7027
|
if (reviewChat) {
|
|
6608
7028
|
handoff.review_chat_command = reviewChat.command;
|
|
@@ -6623,7 +7043,7 @@ function runTaskStep(taskDb, db, taskId, options = {}) {
|
|
|
6623
7043
|
const reason = initialHandoffState.next_action === 'continue_work'
|
|
6624
7044
|
? 'agent_certified_continue_work'
|
|
6625
7045
|
: 'agent_certified_waiting_human';
|
|
6626
|
-
throw taskStepError(reason, 'atris task step: ready
|
|
7046
|
+
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
7047
|
}
|
|
6628
7048
|
let chat = null;
|
|
6629
7049
|
const message = String(options.message || '').trim();
|
|
@@ -6725,7 +7145,7 @@ function runTaskStep(taskDb, db, taskId, options = {}) {
|
|
|
6725
7145
|
const reason = handoffState.next_action === 'continue_work'
|
|
6726
7146
|
? 'agent_certified_continue_work'
|
|
6727
7147
|
: 'agent_certified_waiting_human';
|
|
6728
|
-
throw taskStepError(reason, 'atris task step: ready
|
|
7148
|
+
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
7149
|
}
|
|
6730
7150
|
const reviewed = appendTaskReviewChat(taskDb, db, taskId, { reviewer, dryRun: Boolean(options.dryRun) });
|
|
6731
7151
|
stepAction = 'review_chat';
|
|
@@ -6833,7 +7253,7 @@ function runCurrentTaskStep(taskDb, db, { owner = DEFAULT_OWNER, reviewer = 'cod
|
|
|
6833
7253
|
if (nextActionKey === 'human_accept_waiting') {
|
|
6834
7254
|
const error = taskStepError(
|
|
6835
7255
|
'agent_certified_waiting_human',
|
|
6836
|
-
'atris task current-step: selected row is ready
|
|
7256
|
+
'atris task current-step: selected row is ready for human approval; no agent mutation is safe',
|
|
6837
7257
|
{
|
|
6838
7258
|
status: 409,
|
|
6839
7259
|
exitCode: 1,
|
|
@@ -7296,8 +7716,8 @@ function cmdReady(args) {
|
|
|
7296
7716
|
career_xp_status: 'pending_human_accept',
|
|
7297
7717
|
next_action: agentCertified ? certifiedReviewNextAction(nextTaskInput.nextTask) : 'agent_review_again',
|
|
7298
7718
|
rule: agentCertified
|
|
7299
|
-
? 'Double-check complete; ready to keep moving. XP
|
|
7300
|
-
: 'Proof is ready; one more agent check before
|
|
7719
|
+
? 'Double-check complete; ready to keep moving. XP is awarded only after the human approves the task.'
|
|
7720
|
+
: 'Proof is ready; one more agent check before human approval. XP waits for the human.',
|
|
7301
7721
|
};
|
|
7302
7722
|
if (reviewChat) {
|
|
7303
7723
|
handoff.review_chat_command = reviewChat.command;
|
|
@@ -7327,7 +7747,7 @@ function cmdReady(args) {
|
|
|
7327
7747
|
});
|
|
7328
7748
|
return;
|
|
7329
7749
|
}
|
|
7330
|
-
console.log(`ready
|
|
7750
|
+
console.log(`ready for approval ${taskRef(compactTaskFromProjection(projection, taskId))} v${result.event.version}`);
|
|
7331
7751
|
if (resultTrace) console.log('Result trace recorded.');
|
|
7332
7752
|
console.log(handoff.rule);
|
|
7333
7753
|
for (const hint of policyHints) {
|
|
@@ -7412,9 +7832,12 @@ async function cmdAccept(args) {
|
|
|
7412
7832
|
});
|
|
7413
7833
|
const xpProjection = refreshCareerXpAfterReview(reviewed);
|
|
7414
7834
|
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
7835
|
+
const workspaceRoot = projection.workspace_root || process.cwd();
|
|
7836
|
+
const brainScorecards = refreshBrainScorecardsAfterAccept(workspaceRoot);
|
|
7837
|
+
const nextMissionRoute = nextMissionRouteAfterAccept(workspaceRoot);
|
|
7415
7838
|
// Inform the gate, never block it: show what the receipts named in the proof
|
|
7416
7839
|
// actually say so the accepting human isn't trusting prose.
|
|
7417
|
-
const evidence = extractReceiptEvidence(proof,
|
|
7840
|
+
const evidence = extractReceiptEvidence(proof, workspaceRoot);
|
|
7418
7841
|
const publicSync = hasFlag(args, '--public') ? await publishAcceptAgentXp(args, actor) : null;
|
|
7419
7842
|
if (wantsJson(args)) {
|
|
7420
7843
|
printJson({
|
|
@@ -7427,6 +7850,8 @@ async function cmdAccept(args) {
|
|
|
7427
7850
|
evidence,
|
|
7428
7851
|
public_sync: publicSync,
|
|
7429
7852
|
xp_projection: xpProjection,
|
|
7853
|
+
brain_scorecards: brainScorecards,
|
|
7854
|
+
next_mission_route: nextMissionRoute,
|
|
7430
7855
|
projection_path: outPath,
|
|
7431
7856
|
task: compactTaskFromProjection(projection, taskId),
|
|
7432
7857
|
});
|
|
@@ -7438,6 +7863,9 @@ async function cmdAccept(args) {
|
|
|
7438
7863
|
proof,
|
|
7439
7864
|
nextTask,
|
|
7440
7865
|
publicSync,
|
|
7866
|
+
xpProjection,
|
|
7867
|
+
brainScorecards,
|
|
7868
|
+
nextMissionRoute,
|
|
7441
7869
|
}));
|
|
7442
7870
|
if (publicSync && !publicSync.ok) process.exitCode = 1;
|
|
7443
7871
|
}
|
|
@@ -7485,9 +7913,16 @@ function cmdAutoAcceptCertified(args) {
|
|
|
7485
7913
|
const dryRun = hasFlag(args, '--dry-run');
|
|
7486
7914
|
const strictVerify = hasFlag(args, '--strict-verify');
|
|
7487
7915
|
const actorFlag = flag(args, '--as');
|
|
7488
|
-
const actor = String(actorFlag || 'auto-accept-certified');
|
|
7489
7916
|
const hasHumanActor = validHumanActorFlag(actorFlag);
|
|
7490
7917
|
const confirmedHumanAccept = hasFlag(args, '--confirm-human-accept');
|
|
7918
|
+
// Standing owner authorization: when the autoland policy is on for this
|
|
7919
|
+
// workspace, live accepts run as the owner who flipped it, no per-run
|
|
7920
|
+
// confirmation needed. See lib/autoland.js and 'atris autoland'.
|
|
7921
|
+
const dryRunEarly = hasFlag(args, '--dry-run');
|
|
7922
|
+
const policyAuth = (!dryRunEarly && !confirmedHumanAccept)
|
|
7923
|
+
? require('../lib/autoland').liveAcceptAuthorization()
|
|
7924
|
+
: { ok: false };
|
|
7925
|
+
const actor = String(actorFlag || (policyAuth.ok ? policyAuth.actor : 'auto-accept-certified'));
|
|
7491
7926
|
const limitRaw = flag(args, '--limit');
|
|
7492
7927
|
const max = limitRaw && limitRaw !== true ? Math.max(1, Number(limitRaw) || 12) : 12;
|
|
7493
7928
|
const parsedReward = parseAcceptReward(flag(args, '--reward'));
|
|
@@ -7495,14 +7930,14 @@ function cmdAutoAcceptCertified(args) {
|
|
|
7495
7930
|
console.error('atris task auto-accept-certified: reward must be a positive number');
|
|
7496
7931
|
process.exit(2);
|
|
7497
7932
|
}
|
|
7498
|
-
if (!dryRun && !confirmedHumanAccept) {
|
|
7933
|
+
if (!dryRun && !confirmedHumanAccept && !policyAuth.ok) {
|
|
7499
7934
|
failTask(
|
|
7500
7935
|
'atris task auto-accept-certified',
|
|
7501
7936
|
'human_accept_confirmation_required',
|
|
7502
|
-
'live auto-accept requires --confirm-human-accept --as <human
|
|
7937
|
+
'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
7938
|
);
|
|
7504
7939
|
}
|
|
7505
|
-
if (!dryRun && !hasHumanActor) {
|
|
7940
|
+
if (!dryRun && !hasHumanActor && !policyAuth.ok) {
|
|
7506
7941
|
failTask(
|
|
7507
7942
|
'atris task auto-accept-certified',
|
|
7508
7943
|
'human_actor_required',
|
|
@@ -7707,7 +8142,7 @@ function cmdReview(args) {
|
|
|
7707
8142
|
version: result.event.version,
|
|
7708
8143
|
reward: result.episode.reward.value,
|
|
7709
8144
|
episode: result.episode,
|
|
7710
|
-
xp_projection: xpProjection,
|
|
8145
|
+
xp_projection: compactCareerXpProjection(xpProjection),
|
|
7711
8146
|
next_task_id: nextCreated ? nextCreated.id : null,
|
|
7712
8147
|
...(nextTaskInput.ignored ? { review_next_task_ignored: nextTaskInput.ignored } : {}),
|
|
7713
8148
|
projection_path: outPath,
|
|
@@ -8080,6 +8515,40 @@ function markdownRowsForRender(taskDb, existingTodoPath, rows, refRows) {
|
|
|
8080
8515
|
return out;
|
|
8081
8516
|
}
|
|
8082
8517
|
|
|
8518
|
+
function taskRenderStatusCounts(rows) {
|
|
8519
|
+
const counts = {
|
|
8520
|
+
backlog: 0,
|
|
8521
|
+
in_progress: 0,
|
|
8522
|
+
review: 0,
|
|
8523
|
+
blocked: 0,
|
|
8524
|
+
done: 0,
|
|
8525
|
+
total: 0,
|
|
8526
|
+
};
|
|
8527
|
+
for (const row of Array.isArray(rows) ? rows : []) {
|
|
8528
|
+
counts.total += 1;
|
|
8529
|
+
if (row.status === 'open') counts.backlog += 1;
|
|
8530
|
+
else if (row.status === 'claimed') counts.in_progress += 1;
|
|
8531
|
+
else if (row.status === 'review') counts.review += 1;
|
|
8532
|
+
else if (row.status === 'failed') counts.blocked += 1;
|
|
8533
|
+
else if (row.status === 'done') counts.done += 1;
|
|
8534
|
+
}
|
|
8535
|
+
return counts;
|
|
8536
|
+
}
|
|
8537
|
+
|
|
8538
|
+
function taskRenderCountLabel(count) {
|
|
8539
|
+
return count === 0 ? 'empty' : String(count);
|
|
8540
|
+
}
|
|
8541
|
+
|
|
8542
|
+
function taskRenderSummaryLine(counts) {
|
|
8543
|
+
return [
|
|
8544
|
+
`Backlog: ${taskRenderCountLabel(counts.backlog)}`,
|
|
8545
|
+
`In Progress: ${taskRenderCountLabel(counts.in_progress)}`,
|
|
8546
|
+
`Review: ${taskRenderCountLabel(counts.review)}`,
|
|
8547
|
+
`Blocked: ${taskRenderCountLabel(counts.blocked)}`,
|
|
8548
|
+
`Done saved: ${taskRenderCountLabel(counts.done)}`,
|
|
8549
|
+
].join('; ');
|
|
8550
|
+
}
|
|
8551
|
+
|
|
8083
8552
|
function cmdRender(args) {
|
|
8084
8553
|
const out = flag(args, '--out') || path.join('atris', 'TODO.md');
|
|
8085
8554
|
const all = hasFlag(args, '--all');
|
|
@@ -8103,6 +8572,7 @@ function cmdRender(args) {
|
|
|
8103
8572
|
if (endgameSection) preservedSections.push(endgameSection);
|
|
8104
8573
|
const markdownRows = markdownRowsForRender(taskDb, outPath, rows, refRows);
|
|
8105
8574
|
const rowsToRender = [...rows, ...markdownRows];
|
|
8575
|
+
const statusCounts = taskRenderStatusCounts(rowsToRender);
|
|
8106
8576
|
const markdown = taskDb.renderTodoMarkdown(rowsToRender, { doneLimit, failedLimit, refRows, preservedSections });
|
|
8107
8577
|
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
8108
8578
|
fs.writeFileSync(outPath, markdown, 'utf8');
|
|
@@ -8111,11 +8581,14 @@ function cmdRender(args) {
|
|
|
8111
8581
|
ok: true,
|
|
8112
8582
|
action: 'rendered',
|
|
8113
8583
|
count: rowsToRender.length,
|
|
8584
|
+
status_counts: statusCounts,
|
|
8585
|
+
backlog_empty: statusCounts.backlog === 0,
|
|
8114
8586
|
path: outPath,
|
|
8115
8587
|
});
|
|
8116
8588
|
return;
|
|
8117
8589
|
}
|
|
8118
|
-
console.log(`rendered
|
|
8590
|
+
console.log(`rendered TODO.md -> ${outPath}`);
|
|
8591
|
+
console.log(taskRenderSummaryLine(statusCounts));
|
|
8119
8592
|
}
|
|
8120
8593
|
|
|
8121
8594
|
function cmdSync(args) {
|