atris 3.30.0 → 3.30.2
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 +6 -0
- package/atris/AGENTS.md +11 -0
- package/atris/CLAUDE.md +5 -0
- package/atris/atris.md +19 -4
- package/atris/policies/atris-design.md +71 -0
- package/atris/policies/design-seed.md +187 -0
- package/atris/skills/atris/SKILL.md +26 -3
- package/atris/skills/design/SKILL.md +3 -2
- package/atris/skills/loop/SKILL.md +5 -3
- package/atris/team/_template/MEMBER.md +19 -0
- package/atris.md +63 -23
- package/ax +1434 -1698
- package/bin/atris.js +5 -1
- package/commands/agent-spawn.js +2 -2
- package/commands/brain.js +92 -7
- package/commands/brainstorm.js +62 -22
- package/commands/business-sync.js +92 -8
- package/commands/business.js +13 -7
- package/commands/chat-scan.js +102 -0
- package/commands/computer.js +758 -15
- package/commands/deck.js +505 -105
- package/commands/init.js +6 -1
- package/commands/launchpad.js +638 -0
- package/commands/log-sync.js +44 -13
- package/commands/loop-front.js +290 -0
- package/commands/member.js +124 -3
- package/commands/mission.js +653 -30
- package/commands/pull.js +37 -28
- package/commands/pulse.js +11 -8
- package/commands/run.js +79 -39
- package/commands/sync.js +36 -17
- package/commands/task.js +1072 -89
- package/commands/workflow.js +6 -6
- package/commands/xp.js +13 -1
- package/commands/youtube.js +221 -7
- package/decks/README.md +89 -0
- package/decks/archetype-catalog.json +180 -0
- package/decks/atris-antislop-pitch.json +48 -0
- package/decks/atris-archetypes-v2.json +83 -0
- package/decks/atris-one-loop-pitch.json +80 -0
- package/decks/atris-seed-pitch-v3.json +118 -0
- package/decks/atris-seed-pitch-v4-skeleton.json +106 -0
- package/decks/atris-seed-pitch-v5.json +109 -0
- package/decks/atris-seed-pitch-v6.json +137 -0
- package/decks/atris-seed-pitch-v7.json +133 -0
- package/decks/atris-single-shot-proof.json +74 -0
- package/decks/mark-pincus-narrative.json +102 -0
- package/decks/mark-pincus-sourcery.json +94 -0
- package/decks/yash-applied-compute-detailed.json +150 -0
- package/decks/yash-applied-compute-generalist.json +82 -0
- package/decks/yash-applied-compute-narrative.json +54 -0
- package/lib/ax-prefs.js +5 -12
- package/lib/chat-log-scan.js +377 -0
- package/lib/context-gatherer.js +35 -1
- package/lib/deck-compose.js +145 -0
- package/lib/deck-history.js +64 -0
- package/lib/deck-layout.js +169 -0
- package/lib/deck-review.js +431 -0
- package/lib/deck-schema.js +154 -0
- package/lib/file-ops.js +3 -3
- package/lib/functional-owner.js +189 -0
- package/lib/journal.js +7 -73
- package/lib/slides-deck.js +512 -58
- package/lib/task-db.js +128 -11
- package/package.json +2 -1
- package/templates/business-starter/team/START_HERE.md +12 -8
- package/utils/auth.js +4 -0
- package/utils/config.js +4 -0
- package/atris/atrisDev.md +0 -717
package/commands/task.js
CHANGED
|
@@ -11,12 +11,17 @@ const { taskProofState, buildVerifiedProof } = require('../lib/task-proof');
|
|
|
11
11
|
const { evaluateAutoAccept, parseVerifyCommand } = require('../lib/auto-accept-certified');
|
|
12
12
|
const { extractReceiptEvidence } = require('../lib/receipt-evidence');
|
|
13
13
|
const escapeRegExp = require('../lib/escape-regexp');
|
|
14
|
+
const {
|
|
15
|
+
normalizeOwnerSlug,
|
|
16
|
+
resolveFunctionalOwner: resolveFunctionalTaskOwner,
|
|
17
|
+
} = require('../lib/functional-owner');
|
|
14
18
|
|
|
15
19
|
const DEFAULT_OWNER = process.env.ATRIS_AGENT_ID
|
|
16
20
|
|| process.env.USER
|
|
17
21
|
|| os.userInfo().username
|
|
18
22
|
|| 'unknown';
|
|
19
23
|
const AGENT_CERTIFICATION_REVIEW_PASSES = 2;
|
|
24
|
+
const RESULT_SAVED_TEXT_LIMIT = 200;
|
|
20
25
|
const REVIEW_LANE_LOOP_DEFAULT_MAX_STEPS = 3;
|
|
21
26
|
const REVIEW_LANE_LOOP_MAX_STEPS = 10;
|
|
22
27
|
const REVIEW_LANE_RUN_DEFAULT_MAX_RUNS = 3;
|
|
@@ -97,14 +102,23 @@ atris task - durable local task state (SQLite, gitignored)
|
|
|
97
102
|
atris task chat <id> "<message>" [--goal "..."] Refine a task chat + working goal
|
|
98
103
|
atris task ready <id> --proof "..." Agent proof ready; native goal can complete
|
|
99
104
|
atris task ready <id> --verify "<cmd>" Run <cmd>; only ready if it exits 0 (executed proof)
|
|
105
|
+
atris task plan-preview "<purpose>" [--tag <tag>] [--owner <member>] [--task <id>]
|
|
106
|
+
Show the plain Plan before work starts
|
|
107
|
+
atris task ready <id> --proof "..." [--changed "..." --checked "..." --saved "..." --try "..."]
|
|
108
|
+
Agent proof ready; records Result if needed
|
|
109
|
+
atris task ready <id> --proof "..." [--happened "..." --checked "..." --tested "..." --decision "..."]
|
|
110
|
+
Agent proof ready; writes the human result receipt
|
|
111
|
+
atris task result <id> --changed "..." --checked "..." [--saved "..."] [--try "..."]
|
|
112
|
+
Show the plain Result and store trace on the task
|
|
100
113
|
atris task review-chat <id> [--as <owner>] Start a task-owned /codex verification chat
|
|
101
|
-
atris task accept <id> [--proof "..."]
|
|
114
|
+
atris task accept <id> [--proof "..."] [--public]
|
|
115
|
+
Human accepts proof, marks done; --public also publishes AgentXP
|
|
102
116
|
atris task auto-accept-certified --dry-run [--strict-verify] [--limit <n>]
|
|
103
117
|
Preview certified Review rows; live accept needs --confirm-human-accept --as <human>
|
|
104
118
|
atris task revise <id> --note "..." Send reviewed work back to Do
|
|
105
119
|
|
|
106
120
|
atris task add "<title>" [--tag <tag>] [--goal-id <id>] Create a task
|
|
107
|
-
atris task delegate "<title>" --to <
|
|
121
|
+
atris task delegate "<title>" [--to <member>] [--executed-by <engine>] [--goal-id <id>] Create assigned work
|
|
108
122
|
atris task plan <id> --goal "..." --exit "..." --proof-needed "..."
|
|
109
123
|
Record a task-owned Plan stage
|
|
110
124
|
atris task do <id> --as <owner> --first-move "..."
|
|
@@ -137,7 +151,7 @@ atris task - durable local task state (SQLite, gitignored)
|
|
|
137
151
|
atris task review <id> --reward <n> [--verify "<cmd>"]
|
|
138
152
|
Write review event + RSI episode
|
|
139
153
|
atris task reviews [--limit <n>] Show certified Review items for human accept/revise
|
|
140
|
-
atris task reviews --group-by <tag|owner|source> Cluster
|
|
154
|
+
atris task reviews --group-by <tag|owner|source> Cluster ready-to-land work for fast triage
|
|
141
155
|
atris task accept-group <key>=<value> --spot-check K --confirm-human-accept --as <you> --verified <ids>
|
|
142
156
|
Accept a whole cluster; career XP only on the K you verified
|
|
143
157
|
atris task status [--json] [--history] Compact live status for web/Swarlo
|
|
@@ -301,6 +315,15 @@ function textFlag(args, names) {
|
|
|
301
315
|
return '';
|
|
302
316
|
}
|
|
303
317
|
|
|
318
|
+
function landingFlags(args) {
|
|
319
|
+
return {
|
|
320
|
+
happened: textFlag(args, ['--happened', '--what-happened']),
|
|
321
|
+
checked: textFlag(args, ['--checked', '--how-checked']),
|
|
322
|
+
tested: textFlag(args, ['--tested', '--what-tested']),
|
|
323
|
+
decision: textFlag(args, ['--decision', '--acceptance']),
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
304
327
|
function numericFlag(args, name) {
|
|
305
328
|
const value = flag(args, name);
|
|
306
329
|
if (value === null || value === true || value === undefined) return null;
|
|
@@ -637,6 +660,10 @@ function reviewSummary(task, payload = {}) {
|
|
|
637
660
|
|
|
638
661
|
const title = String(task.title || 'this task').replace(/\s+/g, ' ').trim();
|
|
639
662
|
const plainTitle = title ? title.charAt(0).toLowerCase() + title.slice(1) : 'this task';
|
|
663
|
+
const approvalStatus = payload.approval_status || metadata.approval_status || null;
|
|
664
|
+
if (approvalStatus === 'revise') {
|
|
665
|
+
return `Rework requested for ${plainTitle}.`;
|
|
666
|
+
}
|
|
640
667
|
const careerText = [
|
|
641
668
|
task.tag,
|
|
642
669
|
metadata.goal_id,
|
|
@@ -651,20 +678,148 @@ function reviewSummary(task, payload = {}) {
|
|
|
651
678
|
|| careerText.includes('agent xp')
|
|
652
679
|
) {
|
|
653
680
|
if (task.status === 'done') {
|
|
654
|
-
return `
|
|
681
|
+
return `Landed AgentXP result for ${plainTitle}.`;
|
|
655
682
|
}
|
|
656
683
|
if (task.status === 'review') {
|
|
657
|
-
return
|
|
684
|
+
return `${plainTitle} is ready to land; land only if the proof is real.`;
|
|
658
685
|
}
|
|
659
|
-
return `This explains what
|
|
686
|
+
return `This explains what landing ${plainTitle} would make real for AgentXP.`;
|
|
660
687
|
}
|
|
661
688
|
if (task.status === 'done') {
|
|
662
|
-
return `
|
|
689
|
+
return `Landed result for ${plainTitle}.`;
|
|
663
690
|
}
|
|
664
691
|
if (task.status === 'review') {
|
|
665
|
-
return
|
|
666
|
-
}
|
|
667
|
-
return `This explains what
|
|
692
|
+
return `${plainTitle} is ready to land; land it or send it back.`;
|
|
693
|
+
}
|
|
694
|
+
return `This explains what landing ${plainTitle} would make real.`;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function titleToResultText(title) {
|
|
698
|
+
const text = String(title || 'this task').replace(/\s+/g, ' ').trim();
|
|
699
|
+
if (!text) return 'This task is ready to review.';
|
|
700
|
+
const [first, ...restParts] = text.split(' ');
|
|
701
|
+
const rest = restParts.join(' ');
|
|
702
|
+
const past = {
|
|
703
|
+
add: 'Added',
|
|
704
|
+
approve: 'Prepared approval for',
|
|
705
|
+
build: 'Built',
|
|
706
|
+
clean: 'Cleaned',
|
|
707
|
+
create: 'Created',
|
|
708
|
+
design: 'Designed',
|
|
709
|
+
document: 'Documented',
|
|
710
|
+
fix: 'Fixed',
|
|
711
|
+
make: 'Made',
|
|
712
|
+
replace: 'Replaced',
|
|
713
|
+
route: 'Routed',
|
|
714
|
+
run: 'Ran',
|
|
715
|
+
ship: 'Shipped',
|
|
716
|
+
update: 'Updated',
|
|
717
|
+
validate: 'Validated',
|
|
718
|
+
wire: 'Wired',
|
|
719
|
+
}[String(first || '').toLowerCase()];
|
|
720
|
+
if (past && rest) return `${past} ${rest}.`;
|
|
721
|
+
return `${text} is ready to review.`;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function titleToReasonText(task, proof = '') {
|
|
725
|
+
const title = String(task?.title || '').replace(/\s+/g, ' ').trim();
|
|
726
|
+
const text = `${title} ${proof || ''}`.toLowerCase();
|
|
727
|
+
if (/\b(priv(?:ate|acy)|secret|payload|leak|redact)\b/.test(text)) {
|
|
728
|
+
return 'It keeps private data out of the fast human decision screen.';
|
|
729
|
+
}
|
|
730
|
+
if (/\bapprove\b/.test(text) && /\b(command|exact|preview|ux)\b/.test(text)) {
|
|
731
|
+
return 'It lets the operator see the next command without hunting.';
|
|
732
|
+
}
|
|
733
|
+
if (/\b(stale|expire|expired)\b/.test(text) && /\bapproval/.test(text)) {
|
|
734
|
+
return 'It stops old approvals from running after their context has gone stale.';
|
|
735
|
+
}
|
|
736
|
+
if (/\bapproval|approve|permission\b/.test(text)) {
|
|
737
|
+
return 'It keeps real-world side effects behind a clear human decision.';
|
|
738
|
+
}
|
|
739
|
+
if (/\btest|self-test|harness|verifier|proof\b/.test(text)) {
|
|
740
|
+
return 'It makes the check repeatable before the work lands.';
|
|
741
|
+
}
|
|
742
|
+
return 'It closes the user-visible gap named by the task.';
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
function proofToHumanCheck(proof) {
|
|
746
|
+
const text = String(proof || '').replace(/\s+/g, ' ').trim();
|
|
747
|
+
if (!text) return 'Proof is attached below.';
|
|
748
|
+
const lower = text.toLowerCase();
|
|
749
|
+
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
|
+
return 'I opened the real task screen and checked the receipt renders before raw proof.';
|
|
751
|
+
}
|
|
752
|
+
if (/git\s+diff\s+--check/.test(lower) && /\bpass(?:ed)?\b/.test(lower)) {
|
|
753
|
+
if (/node\s+--test/.test(lower)) return 'I ran the behavior check and the diff cleanliness check.';
|
|
754
|
+
return 'I ran the diff cleanliness check.';
|
|
755
|
+
}
|
|
756
|
+
if (/\btypecheck\b/.test(lower) && /\bpass(?:ed)?\b/.test(lower)) return 'I ran the typecheck.';
|
|
757
|
+
if (/\bbuild\b/.test(lower) && /\bpass(?:ed)?\b/.test(lower)) return 'I ran the build check.';
|
|
758
|
+
if (/\btest\b/.test(lower) && /\bpass(?:ed)?\b/.test(lower)) return 'I ran the verifier named in the proof.';
|
|
759
|
+
if (/\bvalidat(?:e|ion|ed)\b/.test(lower) && /\bpass(?:ed)?\b/.test(lower)) return 'I ran the validation check.';
|
|
760
|
+
if (/\breview(?:ed)?\b/.test(lower)) return 'Review proof is attached below.';
|
|
761
|
+
return 'Proof is attached below.';
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function taskReviewLandingTested(proof) {
|
|
765
|
+
const commands = taskReviewEvidenceCommands(proof, 3);
|
|
766
|
+
if (commands.length) return `I ran: ${commands.join(' | ')}.`;
|
|
767
|
+
const paths = taskReviewEvidencePaths(proof, 3);
|
|
768
|
+
if (paths.length) return `I inspected: ${paths.join(', ')}.`;
|
|
769
|
+
return String(proof || '').trim() ? 'I attached the proof below.' : 'No verifier command recorded yet.';
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function landingPayloadValue(payload, metadata, key) {
|
|
773
|
+
const landing = payload && payload.landing && typeof payload.landing === 'object' ? payload.landing : {};
|
|
774
|
+
return landing[key]
|
|
775
|
+
|| payload && payload[`landing_${key}`]
|
|
776
|
+
|| metadata[`landing_${key}`]
|
|
777
|
+
|| null;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
function taskReviewLanding(task, review = {}, payload = {}) {
|
|
781
|
+
const metadata = task.metadata || {};
|
|
782
|
+
const proof = review.proof || payload.proof || metadata.latest_agent_proof || '';
|
|
783
|
+
const agentCertified = review.agent_certified === true || metadata.agent_certified === true;
|
|
784
|
+
const approvalStatus = review.approval_status || metadata.approval_status || null;
|
|
785
|
+
const explicitHappened = landingPayloadValue(payload, metadata, 'happened')
|
|
786
|
+
|| payload.changed || metadata.result_changed || metadata.human_changed || metadata.changed;
|
|
787
|
+
const explicitChecked = landingPayloadValue(payload, metadata, 'checked')
|
|
788
|
+
|| payload.checked || metadata.result_checked || metadata.human_checked || metadata.checked;
|
|
789
|
+
const explicitTested = landingPayloadValue(payload, metadata, 'tested');
|
|
790
|
+
const explicitDecision = landingPayloadValue(payload, metadata, 'decision');
|
|
791
|
+
const explicitReason = landingPayloadValue(payload, metadata, 'reason')
|
|
792
|
+
|| landingPayloadValue(payload, metadata, 'why')
|
|
793
|
+
|| payload.reason || payload.why || metadata.result_reason || metadata.review_reason || metadata.why_it_matters;
|
|
794
|
+
return {
|
|
795
|
+
happened: clipStatusText(explicitHappened || titleToResultText(task.title), 220),
|
|
796
|
+
reason: clipStatusText(explicitReason || titleToReasonText(task, proof), 220),
|
|
797
|
+
checked: clipStatusText(explicitChecked || proofToHumanCheck(proof), 220),
|
|
798
|
+
tested: clipStatusText(explicitTested || taskReviewLandingTested(proof), 260),
|
|
799
|
+
decision: clipStatusText(explicitDecision || (task.status === 'done'
|
|
800
|
+
? 'Landed. No action needed unless this regresses.'
|
|
801
|
+
: approvalStatus === 'revise'
|
|
802
|
+
? 'Rework requested. Fix the note, then send a new receipt.'
|
|
803
|
+
: approvalStatus === 'pending' && !agentCertified
|
|
804
|
+
? 'Needs one more check before landing; send it back if the receipt misses the point.'
|
|
805
|
+
: 'Land it if this matches the request; send it back if not.'), 220),
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
function taskReviewResult(task, review = {}, payload = {}) {
|
|
810
|
+
const metadata = task.metadata || {};
|
|
811
|
+
const ref = taskRef(task);
|
|
812
|
+
const landing = taskReviewLanding(task, review, payload);
|
|
813
|
+
const explicitSaved = payload.saved || metadata.result_saved || metadata.human_saved || metadata.saved;
|
|
814
|
+
return {
|
|
815
|
+
changed: landing.happened,
|
|
816
|
+
reason: landing.reason,
|
|
817
|
+
checked: landing.checked,
|
|
818
|
+
saved: clipStatusText(explicitSaved || (task.status === 'done'
|
|
819
|
+
? `Landed as ${ref}.`
|
|
820
|
+
: `Ready to land as ${ref}.`), 180),
|
|
821
|
+
accept: landing.decision,
|
|
822
|
+
};
|
|
668
823
|
}
|
|
669
824
|
|
|
670
825
|
function taskReviewSummary(task) {
|
|
@@ -673,7 +828,7 @@ function taskReviewSummary(task) {
|
|
|
673
828
|
const metadata = task.metadata || {};
|
|
674
829
|
if (!reviewed && !metadata.approval_status && !metadata.agent_review_pass_count && !metadata.human_revision_count && !metadata.agent_certified) return null;
|
|
675
830
|
if (reviewed && reviewed.event_type === 'revision_requested') {
|
|
676
|
-
|
|
831
|
+
const review = {
|
|
677
832
|
summary: reviewSummary(task, payload),
|
|
678
833
|
reward: null,
|
|
679
834
|
proof: null,
|
|
@@ -685,7 +840,10 @@ function taskReviewSummary(task) {
|
|
|
685
840
|
agent_certification_policy: null,
|
|
686
841
|
human_revision_count: metadata.human_revision_count || payload.revision_count || null,
|
|
687
842
|
human_revision_note: metadata.human_revision_note || payload.note || null,
|
|
688
|
-
}
|
|
843
|
+
};
|
|
844
|
+
review.landing = taskReviewLanding(task, review, payload);
|
|
845
|
+
review.result = taskReviewResult(task, review, payload);
|
|
846
|
+
return reviewSummaryWithVerificationChat(task, review);
|
|
689
847
|
}
|
|
690
848
|
const reviewPassCount = Number(metadata.agent_review_pass_count || payload.review_pass_count || 0);
|
|
691
849
|
const agentCertified = metadata.agent_certified === true
|
|
@@ -705,7 +863,7 @@ function taskReviewSummary(task) {
|
|
|
705
863
|
}
|
|
706
864
|
return payload[key] || metadata[metadataKey] || null;
|
|
707
865
|
};
|
|
708
|
-
|
|
866
|
+
const review = {
|
|
709
867
|
summary: reviewSummary(task, payload),
|
|
710
868
|
reward: reviewed && reviewed.event_type === 'reviewed' && payload.reward !== undefined ? payload.reward : null,
|
|
711
869
|
proof: readyField('proof', 'latest_agent_proof'),
|
|
@@ -718,7 +876,10 @@ function taskReviewSummary(task) {
|
|
|
718
876
|
|| payload.agent_certification_policy
|
|
719
877
|
|| (agentCertified ? `${AGENT_CERTIFICATION_REVIEW_PASSES}_agent_review_passes` : null),
|
|
720
878
|
human_revision_count: metadata.human_revision_count || null,
|
|
721
|
-
}
|
|
879
|
+
};
|
|
880
|
+
review.landing = taskReviewLanding(task, review, payload);
|
|
881
|
+
review.result = taskReviewResult(task, review, payload);
|
|
882
|
+
return reviewSummaryWithVerificationChat(task, review);
|
|
722
883
|
}
|
|
723
884
|
|
|
724
885
|
function reviewSummaryWithVerificationChat(task, review) {
|
|
@@ -943,6 +1104,15 @@ function taskDescriptionForCloud(task) {
|
|
|
943
1104
|
lines.push(`- ${message.actor || 'unknown'}: ${message.content}`);
|
|
944
1105
|
}
|
|
945
1106
|
}
|
|
1107
|
+
const landing = reviewLandingForDisplay(task.review);
|
|
1108
|
+
if (landing) {
|
|
1109
|
+
lines.push('', 'Result:');
|
|
1110
|
+
if (landing.happened) lines.push(`What happened: ${landing.happened}`);
|
|
1111
|
+
if (landing.reason) lines.push(`Why it matters: ${landing.reason}`);
|
|
1112
|
+
if (landing.checked) lines.push(`How checked: ${landing.checked}`);
|
|
1113
|
+
if (landing.tested) lines.push(`Tested: ${landing.tested}`);
|
|
1114
|
+
if (landing.decision) lines.push(`Decision: ${landing.decision}`);
|
|
1115
|
+
}
|
|
946
1116
|
const reviewed = (task.events || []).slice().reverse().find(e => e.event_type === 'reviewed');
|
|
947
1117
|
if (reviewed && reviewed.payload) {
|
|
948
1118
|
if (reviewed.payload.proof) lines.push('', `Proof: ${reviewed.payload.proof}`);
|
|
@@ -956,6 +1126,32 @@ function taskDescriptionForCloud(task) {
|
|
|
956
1126
|
return lines.join('\n').slice(0, 5000);
|
|
957
1127
|
}
|
|
958
1128
|
|
|
1129
|
+
function reviewLandingForDisplay(review) {
|
|
1130
|
+
if (!review) return null;
|
|
1131
|
+
if (review.landing) return review.landing;
|
|
1132
|
+
if (!review.result) return null;
|
|
1133
|
+
return {
|
|
1134
|
+
happened: review.result.changed || null,
|
|
1135
|
+
reason: review.result.reason || null,
|
|
1136
|
+
checked: review.result.checked || null,
|
|
1137
|
+
tested: review.proof ? taskReviewLandingTested(review.proof) : null,
|
|
1138
|
+
decision: review.result.accept || null,
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
function printReviewLanding(review) {
|
|
1143
|
+
const landing = reviewLandingForDisplay(review);
|
|
1144
|
+
if (!landing) return false;
|
|
1145
|
+
console.log('Result:');
|
|
1146
|
+
if (landing.happened) console.log(` What happened: ${landing.happened}`);
|
|
1147
|
+
if (landing.reason) console.log(` Why it matters: ${landing.reason}`);
|
|
1148
|
+
if (landing.checked) console.log(` How I checked: ${landing.checked}`);
|
|
1149
|
+
if (landing.tested) console.log(` What I tested: ${landing.tested}`);
|
|
1150
|
+
if (review.result && review.result.saved) console.log(` Saved: ${review.result.saved}`);
|
|
1151
|
+
if (landing.decision) console.log(` Decision: ${landing.decision}`);
|
|
1152
|
+
return true;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
959
1155
|
function cloudPayloadForTask(task, businessId) {
|
|
960
1156
|
const metadata = task.metadata || {};
|
|
961
1157
|
const claimedAtEvent = (task.events || []).find(e => e.event_type === 'claimed');
|
|
@@ -1373,6 +1569,8 @@ function compactTaskForStatus(task) {
|
|
|
1373
1569
|
const review = {};
|
|
1374
1570
|
if (typeof task.review.reward === 'number') review.reward = task.review.reward;
|
|
1375
1571
|
else if (task.review.reward === null) review.reward = null;
|
|
1572
|
+
if (task.review.landing) review.landing = task.review.landing;
|
|
1573
|
+
if (task.review.result) review.result = task.review.result;
|
|
1376
1574
|
if (task.review.summary) review.summary = clipStatusText(task.review.summary, 240);
|
|
1377
1575
|
if (task.review.proof) review.proof = clipStatusText(task.review.proof, 180);
|
|
1378
1576
|
if (task.review.lesson) review.lesson = clipStatusText(task.review.lesson, 180);
|
|
@@ -1396,7 +1594,7 @@ function compactTaskForStatus(task) {
|
|
|
1396
1594
|
if (Object.keys(lineage).length) out.lineage = lineage;
|
|
1397
1595
|
}
|
|
1398
1596
|
const compactMetadata = {};
|
|
1399
|
-
for (const key of ['todo_id', 'stage', 'verify', 'delegate_via', 'goal_id', 'task_goal', 'goal_objective', 'approval_status', 'agent_review_pass_count', 'agent_certified', 'agent_certification_policy', 'human_revision_count', 'human_revision_note']) {
|
|
1597
|
+
for (const key of ['todo_id', 'stage', 'verify', 'delegate_via', 'owner_resolution', 'requested_owner', 'executed_by', 'proposed_member', 'proposed_member_command', 'goal_id', 'task_goal', 'goal_objective', 'approval_status', 'agent_review_pass_count', 'agent_certified', 'agent_certification_policy', 'human_revision_count', 'human_revision_note']) {
|
|
1400
1598
|
if (metadata[key]) compactMetadata[key] = key === 'verify' ? clipStatusText(metadata[key], 180) : metadata[key];
|
|
1401
1599
|
}
|
|
1402
1600
|
if (Object.keys(compactMetadata).length) out.metadata = compactMetadata;
|
|
@@ -1590,9 +1788,20 @@ function sortTasksNewestFirst(tasks) {
|
|
|
1590
1788
|
});
|
|
1591
1789
|
}
|
|
1592
1790
|
|
|
1791
|
+
function sortTasksOldestFirst(tasks) {
|
|
1792
|
+
return [...tasks].sort((a, b) => {
|
|
1793
|
+
const byUpdated = Number(a.updated_at || 0) - Number(b.updated_at || 0);
|
|
1794
|
+
if (byUpdated) return byUpdated;
|
|
1795
|
+
return String(a.title || '').localeCompare(String(b.title || ''));
|
|
1796
|
+
});
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1593
1799
|
function taskQueueItem(task, { reviewer = 'codex-review', hasExistingReviewFollowUp = null } = {}) {
|
|
1594
1800
|
const page = taskPageContract(task, { reviewer, hasExistingReviewFollowUp });
|
|
1595
1801
|
const item = compactTaskForStatus(task) || {};
|
|
1802
|
+
item.ref = taskRef(task);
|
|
1803
|
+
item.display_id = item.display_id || task.display_id || null;
|
|
1804
|
+
item.legacy_ref = item.legacy_ref || task.legacy_ref || taskRef(task.id);
|
|
1596
1805
|
if (item.review && item.review.verification_chat) {
|
|
1597
1806
|
item.review = { ...item.review };
|
|
1598
1807
|
delete item.review.verification_chat;
|
|
@@ -2674,7 +2883,8 @@ function taskQueueContract(projection, { reviewer = 'codex-review', limit = 8, s
|
|
|
2674
2883
|
}
|
|
2675
2884
|
|
|
2676
2885
|
function selectTaskForCurrent(projection, { owner = DEFAULT_OWNER, scope = {}, hasExistingReviewFollowUp = null } = {}) {
|
|
2677
|
-
const
|
|
2886
|
+
const normalizedScope = normalizeTaskQueueScope(scope);
|
|
2887
|
+
const tasks = filterTasksByScope(sortTasksNewestFirst(projection.tasks || []), normalizedScope, { hasExistingReviewFollowUp });
|
|
2678
2888
|
const columns = {
|
|
2679
2889
|
backlog: [],
|
|
2680
2890
|
plan: [],
|
|
@@ -2695,7 +2905,8 @@ function selectTaskForCurrent(projection, { owner = DEFAULT_OWNER, scope = {}, h
|
|
|
2695
2905
|
if (reviewNeedsAgent) return { task: reviewNeedsAgent, reason: 'review_needs_agent_verification' };
|
|
2696
2906
|
const reviewProofBoundaryBlocked = columns.review.find(task => reviewHandoffForTask(task, { suppressExistingFollowUp: true, hasExistingReviewFollowUp })?.next_action === PROOF_BOUNDARY_BLOCKED_ACTION);
|
|
2697
2907
|
if (reviewProofBoundaryBlocked) return { task: reviewProofBoundaryBlocked, reason: 'review_proof_boundary_blocked' };
|
|
2698
|
-
const
|
|
2908
|
+
const planQueue = normalizedScope.goal_id ? sortTasksOldestFirst(columns.plan) : columns.plan;
|
|
2909
|
+
const planReady = planQueue[0];
|
|
2699
2910
|
if (planReady) return { task: planReady, reason: 'plan_ready' };
|
|
2700
2911
|
const backlogIdea = columns.backlog[0];
|
|
2701
2912
|
if (backlogIdea) return { task: backlogIdea, reason: 'backlog_idea' };
|
|
@@ -3813,10 +4024,14 @@ function reviewQueueItem(task, root = process.cwd(), evidence = undefined) {
|
|
|
3813
4024
|
tag: task.tag || null,
|
|
3814
4025
|
updated_at: task.updated_at || null,
|
|
3815
4026
|
review_pass_count: task.review?.agent_review_pass_count || null,
|
|
4027
|
+
landing: reviewLandingForDisplay(task.review),
|
|
4028
|
+
result: task.review?.result || null,
|
|
3816
4029
|
proof: taskReviewClip(task.review?.proof, 500) || null,
|
|
3817
4030
|
next_action: handoff?.next_action || null,
|
|
3818
4031
|
accept_command: acceptCommand,
|
|
4032
|
+
land_command: acceptCommand,
|
|
3819
4033
|
revise_command: `atris task revise ${ref} --note "<what must change>"`,
|
|
4034
|
+
send_back_command: `atris task revise ${ref} --note "<what must change>"`,
|
|
3820
4035
|
};
|
|
3821
4036
|
const resolvedEvidence = evidence === undefined ? extractReceiptEvidence(task.review?.proof, root) : evidence;
|
|
3822
4037
|
if (resolvedEvidence) item.evidence = resolvedEvidence;
|
|
@@ -3960,13 +4175,13 @@ function cmdReviews(args) {
|
|
|
3960
4175
|
printJson({ ok: true, action: 'review_groups', projection_path: outPath, groups });
|
|
3961
4176
|
return;
|
|
3962
4177
|
}
|
|
3963
|
-
console.log(`
|
|
3964
|
-
console.log(`${groups.total_certified}
|
|
4178
|
+
console.log(`READY TO LAND — grouped by ${key}`);
|
|
4179
|
+
console.log(`${groups.total_certified} ready to land across ${groups.group_count} ${key} group(s)`);
|
|
3965
4180
|
groups.groups.forEach((g, index) => {
|
|
3966
4181
|
console.log('');
|
|
3967
4182
|
console.log(`${index + 1}. ${g.value} — ${g.count} task${g.count === 1 ? '' : 's'}`);
|
|
3968
4183
|
g.sample_titles.forEach(title => console.log(` • ${title}`));
|
|
3969
|
-
console.log(`
|
|
4184
|
+
console.log(` land this group: ${g.accept_group_command} --confirm-human-accept --as <you>`);
|
|
3970
4185
|
});
|
|
3971
4186
|
return;
|
|
3972
4187
|
}
|
|
@@ -3980,10 +4195,10 @@ function cmdReviews(args) {
|
|
|
3980
4195
|
});
|
|
3981
4196
|
return;
|
|
3982
4197
|
}
|
|
3983
|
-
console.log('
|
|
3984
|
-
console.log(`${queue.counts.certified}
|
|
4198
|
+
console.log('READY TO LAND');
|
|
4199
|
+
console.log(`${queue.counts.certified} ready to land / ${queue.counts.blocking} need one more check / ${queue.counts.review} total waiting`);
|
|
3985
4200
|
if (!queue.items.length) {
|
|
3986
|
-
console.log('
|
|
4201
|
+
console.log('Nothing is ready to land.');
|
|
3987
4202
|
return;
|
|
3988
4203
|
}
|
|
3989
4204
|
queue.items.forEach((item, index) => {
|
|
@@ -3992,7 +4207,16 @@ function cmdReviews(args) {
|
|
|
3992
4207
|
const badge = item.evidence?.all_passing ? ' [evidence:passing]' : '';
|
|
3993
4208
|
console.log('');
|
|
3994
4209
|
console.log(`${index + 1}. ${item.display_id || taskRef(item.id)}${tag}${passes}: ${item.title}${badge}`);
|
|
3995
|
-
if (item.
|
|
4210
|
+
if (item.landing) {
|
|
4211
|
+
console.log(' Result:');
|
|
4212
|
+
if (item.landing.happened) console.log(` What happened: ${item.landing.happened}`);
|
|
4213
|
+
if (item.landing.reason) console.log(` Why it matters: ${item.landing.reason}`);
|
|
4214
|
+
if (item.landing.checked) console.log(` How I checked: ${item.landing.checked}`);
|
|
4215
|
+
if (item.landing.tested) console.log(` What I tested: ${item.landing.tested}`);
|
|
4216
|
+
if (item.result?.saved) console.log(` Saved: ${item.result.saved}`);
|
|
4217
|
+
if (item.landing.decision) console.log(` Decision: ${item.landing.decision}`);
|
|
4218
|
+
}
|
|
4219
|
+
if (item.proof) console.log(` details: ${item.proof}`);
|
|
3996
4220
|
if (item.evidence) {
|
|
3997
4221
|
item.evidence.receipts.forEach((receipt) => {
|
|
3998
4222
|
const verdict = receipt.verifier_passed === true ? ' verifier:passed'
|
|
@@ -4003,9 +4227,9 @@ function cmdReviews(args) {
|
|
|
4003
4227
|
}
|
|
4004
4228
|
if (item.review_chat_command) console.log(` /codex: ${item.review_chat_command}`);
|
|
4005
4229
|
if (item.continue_work_command) console.log(` continue: ${item.continue_work_command}`);
|
|
4006
|
-
if (item.accept_command) console.log(`
|
|
4007
|
-
else if (item.blocked_accept_reason) console.log(`
|
|
4008
|
-
console.log(`
|
|
4230
|
+
if (item.accept_command) console.log(` land: ${item.accept_command}`);
|
|
4231
|
+
else if (item.blocked_accept_reason) console.log(` land: blocked (${item.blocked_accept_reason})`);
|
|
4232
|
+
console.log(` send back: ${item.revise_command}`);
|
|
4009
4233
|
});
|
|
4010
4234
|
if (queue.counts.shown < queue.counts.certified) {
|
|
4011
4235
|
console.log('');
|
|
@@ -4372,13 +4596,9 @@ function delegateHandoff(task, owner, via, tag) {
|
|
|
4372
4596
|
function cmdDelegate(args) {
|
|
4373
4597
|
const pos = positional(args);
|
|
4374
4598
|
const title = pos.join(' ').trim();
|
|
4375
|
-
const owner = flag(args, '--to') || flag(args, '--as');
|
|
4376
4599
|
if (!title) {
|
|
4377
4600
|
failTask('atris task delegate', 'missing_title', 'title required');
|
|
4378
4601
|
}
|
|
4379
|
-
if (!owner || owner === true) {
|
|
4380
|
-
failTask('atris task delegate', 'missing_owner', '--to <owner> required');
|
|
4381
|
-
}
|
|
4382
4602
|
const viaFlag = flag(args, '--via');
|
|
4383
4603
|
const via = viaFlag === 'swarlo' ? 'swarlo' : 'local';
|
|
4384
4604
|
const tag = flag(args, '--tag');
|
|
@@ -4386,15 +4606,36 @@ function cmdDelegate(args) {
|
|
|
4386
4606
|
const goalId = flag(args, '--goal-id');
|
|
4387
4607
|
const goalObjective = flag(args, '--goal-objective') || flag(args, '--goal');
|
|
4388
4608
|
const claimNow = hasFlag(args, '--claim');
|
|
4609
|
+
const requestedOwner = flag(args, '--to') || flag(args, '--as');
|
|
4610
|
+
const explicitExecutedBy = flag(args, '--executed-by');
|
|
4389
4611
|
const taskDb = getTaskDb();
|
|
4390
4612
|
const db = taskDb.open();
|
|
4391
4613
|
const ws = taskDb.workspaceRoot();
|
|
4614
|
+
const ownerResolution = resolveFunctionalTaskOwner({
|
|
4615
|
+
requestedOwner: requestedOwner && requestedOwner !== true ? requestedOwner : null,
|
|
4616
|
+
title,
|
|
4617
|
+
tag,
|
|
4618
|
+
note,
|
|
4619
|
+
goal: goalObjective && goalObjective !== true ? goalObjective : '',
|
|
4620
|
+
root: ws,
|
|
4621
|
+
});
|
|
4622
|
+
const owner = ownerResolution.owner;
|
|
4623
|
+
const executedBy = explicitExecutedBy && explicitExecutedBy !== true
|
|
4624
|
+
? normalizeOwnerSlug(explicitExecutedBy)
|
|
4625
|
+
: ownerResolution.executed_by;
|
|
4392
4626
|
const metadata = {
|
|
4393
|
-
assigned_to:
|
|
4627
|
+
assigned_to: owner,
|
|
4394
4628
|
delegate_via: via,
|
|
4395
4629
|
swarlo_channel: via === 'swarlo' ? String(tag || 'tasks') : null,
|
|
4396
4630
|
created_for_day: new Date().toISOString().slice(0, 10),
|
|
4631
|
+
owner_resolution: ownerResolution.reason,
|
|
4397
4632
|
};
|
|
4633
|
+
if (ownerResolution.requested_owner) metadata.requested_owner = ownerResolution.requested_owner;
|
|
4634
|
+
if (executedBy) metadata.executed_by = executedBy;
|
|
4635
|
+
if (ownerResolution.proposed_member) {
|
|
4636
|
+
metadata.proposed_member = ownerResolution.proposed_member;
|
|
4637
|
+
metadata.proposed_member_command = `atris member create ${ownerResolution.proposed_member} --role="${ownerResolution.proposed_member.replace(/-/g, ' ')}"`;
|
|
4638
|
+
}
|
|
4398
4639
|
if (goalId && goalId !== true) metadata.goal_id = String(goalId);
|
|
4399
4640
|
if (goalObjective && goalObjective !== true) {
|
|
4400
4641
|
metadata.task_goal = String(goalObjective);
|
|
@@ -4405,7 +4646,7 @@ function cmdDelegate(args) {
|
|
|
4405
4646
|
tag: typeof tag === 'string' ? tag : null,
|
|
4406
4647
|
workspaceRoot: ws,
|
|
4407
4648
|
status: claimNow ? 'claimed' : 'open',
|
|
4408
|
-
claimedBy: claimNow ?
|
|
4649
|
+
claimedBy: claimNow ? owner : null,
|
|
4409
4650
|
metadata,
|
|
4410
4651
|
});
|
|
4411
4652
|
if (typeof note === 'string' && note.trim()) {
|
|
@@ -4413,14 +4654,16 @@ function cmdDelegate(args) {
|
|
|
4413
4654
|
}
|
|
4414
4655
|
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
4415
4656
|
const task = compactTaskFromProjection(projection, result.id);
|
|
4416
|
-
const handoff = delegateHandoff(task,
|
|
4657
|
+
const handoff = delegateHandoff(task, owner, via, typeof tag === 'string' ? tag : null);
|
|
4417
4658
|
if (wantsJson(args)) {
|
|
4418
4659
|
printJson({
|
|
4419
4660
|
ok: true,
|
|
4420
4661
|
action: 'delegated',
|
|
4421
4662
|
task_id: result.id,
|
|
4422
4663
|
inserted: result.inserted !== false,
|
|
4423
|
-
owner
|
|
4664
|
+
owner,
|
|
4665
|
+
owner_resolution: ownerResolution,
|
|
4666
|
+
executed_by: executedBy || null,
|
|
4424
4667
|
via,
|
|
4425
4668
|
handoff,
|
|
4426
4669
|
projection_path: outPath,
|
|
@@ -4430,6 +4673,8 @@ function cmdDelegate(args) {
|
|
|
4430
4673
|
}
|
|
4431
4674
|
const tagText = tag && tag !== true ? ` #${tag}` : '';
|
|
4432
4675
|
console.log(`delegated ${taskRef(task)} -> ${owner}${tagText} via=${via}`);
|
|
4676
|
+
if (executedBy) console.log(`executed_by: ${executedBy}`);
|
|
4677
|
+
if (ownerResolution.proposed_member) console.log(`member: ${metadata.proposed_member_command}`);
|
|
4433
4678
|
console.log(`claim: ${handoff.command}`);
|
|
4434
4679
|
if (handoff.swarlo) console.log(`swarlo: ${handoff.swarlo.channel}/${handoff.swarlo.action}`);
|
|
4435
4680
|
}
|
|
@@ -4520,7 +4765,7 @@ function cmdDay(args) {
|
|
|
4520
4765
|
console.log(`stale ${staleFailed.length} failed >7d hidden — atris task list --status failed`);
|
|
4521
4766
|
}
|
|
4522
4767
|
console.log('');
|
|
4523
|
-
console.log('add: atris task delegate "..." --to
|
|
4768
|
+
console.log('add: atris task delegate "..." --to task-planner --tag tasks');
|
|
4524
4769
|
}
|
|
4525
4770
|
|
|
4526
4771
|
function cmdHome(args) {
|
|
@@ -4776,7 +5021,7 @@ function cmdNext(args) {
|
|
|
4776
5021
|
}
|
|
4777
5022
|
console.log(`created ${taskRef(createdTask)} @${owner}`);
|
|
4778
5023
|
console.log(createdTask.title);
|
|
4779
|
-
console.log(`Noted v${created.note_version}.
|
|
5024
|
+
console.log(`Noted v${created.note_version}. Landing remains pending on ${taskRef(reviewTask)}.`);
|
|
4780
5025
|
console.log(`Verify: ${nextAgentAction.task_seed.verifier}`);
|
|
4781
5026
|
return;
|
|
4782
5027
|
}
|
|
@@ -4797,12 +5042,12 @@ function cmdNext(args) {
|
|
|
4797
5042
|
}
|
|
4798
5043
|
console.log('No open tasks.');
|
|
4799
5044
|
console.log(handoff.next_action === 'agent_review_again'
|
|
4800
|
-
? `${taskRef(reviewTask)} needs one more agent
|
|
4801
|
-
: `${taskRef(reviewTask)} is
|
|
5045
|
+
? `${taskRef(reviewTask)} needs one more agent check before landing.`
|
|
5046
|
+
: `${taskRef(reviewTask)} is ready to land.`);
|
|
4802
5047
|
console.log(handoff.next_action === 'continue_work'
|
|
4803
|
-
? 'Continue work elsewhere;
|
|
5048
|
+
? 'Continue work elsewhere; XP lands after the human lands this task.'
|
|
4804
5049
|
: handoff.next_action === 'human_accept_waiting'
|
|
4805
|
-
? (nextAgentAction ? nextAgentAction.message : 'No
|
|
5050
|
+
? (nextAgentAction ? nextAgentAction.message : 'No next agent task is attached; this task is ready to land when the human decides.')
|
|
4806
5051
|
: 'Review this task again before continuing.');
|
|
4807
5052
|
if (nextAgentAction) console.log(`Command: ${nextAgentAction.command}`);
|
|
4808
5053
|
if (nextAgentAction && nextAgentAction.task_seed) {
|
|
@@ -5021,7 +5266,8 @@ function cmdPlan(args) {
|
|
|
5021
5266
|
const pos = positional(args);
|
|
5022
5267
|
const id = pos[0];
|
|
5023
5268
|
if (!id) failTask('atris task plan', 'missing_id', 'id required');
|
|
5024
|
-
const
|
|
5269
|
+
const actorFlag = flag(args, '--as');
|
|
5270
|
+
const actor = String(actorFlag || DEFAULT_OWNER);
|
|
5025
5271
|
const goal = textFlag(args, ['--goal', '--objective']);
|
|
5026
5272
|
const exit = textFlag(args, ['--exit', '--exit-condition']);
|
|
5027
5273
|
const proofNeeded = textFlag(args, ['--proof-needed', '--proof', '--verify']);
|
|
@@ -5033,18 +5279,29 @@ function cmdPlan(args) {
|
|
|
5033
5279
|
const taskDb = getTaskDb();
|
|
5034
5280
|
const db = taskDb.open();
|
|
5035
5281
|
const taskId = requireTaskId(taskDb, db, id, 'atris task plan');
|
|
5282
|
+
const task = taskDetail(taskDb, db, taskId);
|
|
5283
|
+
const automaticPlan = buildAutomaticPlanTrace(taskDb, task, {
|
|
5284
|
+
actor,
|
|
5285
|
+
actorExplicit: typeof actorFlag === 'string' && Boolean(actorFlag.trim()),
|
|
5286
|
+
owner,
|
|
5287
|
+
goal,
|
|
5288
|
+
summary,
|
|
5289
|
+
firstMove,
|
|
5290
|
+
exit,
|
|
5291
|
+
});
|
|
5036
5292
|
const result = taskDb.stageTask(db, {
|
|
5037
5293
|
id: taskId,
|
|
5038
5294
|
actor,
|
|
5039
5295
|
stage: 'plan',
|
|
5040
5296
|
goal,
|
|
5041
5297
|
summary,
|
|
5042
|
-
owner,
|
|
5298
|
+
owner: automaticPlan.ownerForStage || owner,
|
|
5043
5299
|
exit,
|
|
5044
5300
|
proofNeeded,
|
|
5045
5301
|
firstMove,
|
|
5046
5302
|
nextButton,
|
|
5047
5303
|
confidence,
|
|
5304
|
+
planTrace: automaticPlan.trace,
|
|
5048
5305
|
});
|
|
5049
5306
|
if (!result.staged) {
|
|
5050
5307
|
failTask('atris task plan', result.reason || 'stage_failed', stageErrorDetail('atris task plan', result.reason, result), 1);
|
|
@@ -5056,6 +5313,11 @@ function cmdPlan(args) {
|
|
|
5056
5313
|
action: 'planned',
|
|
5057
5314
|
task_id: taskId,
|
|
5058
5315
|
version: result.event.version,
|
|
5316
|
+
plan_trace: automaticPlan.trace ? {
|
|
5317
|
+
plan: automaticPlan.plan,
|
|
5318
|
+
owner_choice: automaticPlan.ownerChoice,
|
|
5319
|
+
trace: automaticPlan.trace,
|
|
5320
|
+
} : null,
|
|
5059
5321
|
stage_packet: result.stage_packet,
|
|
5060
5322
|
projection_path: outPath,
|
|
5061
5323
|
task: compactTaskFromProjection(projection, taskId),
|
|
@@ -5196,17 +5458,30 @@ function cmdShow(args) {
|
|
|
5196
5458
|
}
|
|
5197
5459
|
const owner = task.claimed_by ? ` / ${task.claimed_by}` : '';
|
|
5198
5460
|
const tag = task.tag ? ` #${task.tag}` : '';
|
|
5199
|
-
|
|
5461
|
+
const statusLabel = task.status === 'review'
|
|
5462
|
+
? 'READY TO LAND'
|
|
5463
|
+
: task.status === 'done'
|
|
5464
|
+
? 'LANDED'
|
|
5465
|
+
: task.status.toUpperCase();
|
|
5466
|
+
console.log(`${statusLabel} ${taskRef(task)} v${task.current_version}${owner}${tag}`);
|
|
5200
5467
|
console.log(task.title);
|
|
5201
5468
|
if (task.review) {
|
|
5202
5469
|
console.log('');
|
|
5203
|
-
|
|
5204
|
-
if (task.review.
|
|
5470
|
+
printReviewLanding(task.review);
|
|
5471
|
+
if (task.review.summary) console.log(`Short version: ${task.review.summary}`);
|
|
5472
|
+
if (task.review.proof) console.log(`Details: ${task.review.proof}`);
|
|
5205
5473
|
if (task.review.lesson) console.log(`Lesson: ${task.review.lesson}`);
|
|
5206
5474
|
if (task.review.next_task) console.log(`Next: ${task.review.next_task}`);
|
|
5207
|
-
if (task.review.approval_status)
|
|
5208
|
-
|
|
5209
|
-
|
|
5475
|
+
if (task.review.approval_status) {
|
|
5476
|
+
const landingStatus = task.review.approval_status === 'pending'
|
|
5477
|
+
? 'waiting on human'
|
|
5478
|
+
: task.review.approval_status === 'revise'
|
|
5479
|
+
? 'sent back'
|
|
5480
|
+
: task.review.approval_status;
|
|
5481
|
+
console.log(`Landing: ${landingStatus}`);
|
|
5482
|
+
}
|
|
5483
|
+
if (task.review.verification_chat) console.log(`Check command: ${task.review.verification_chat.command}`);
|
|
5484
|
+
if (task.review.agent_certified) console.log(`Checked: yes (${task.review.agent_review_pass_count || AGENT_CERTIFICATION_REVIEW_PASSES} agent checks)`);
|
|
5210
5485
|
}
|
|
5211
5486
|
if (task.messages.length) {
|
|
5212
5487
|
console.log('');
|
|
@@ -5248,10 +5523,11 @@ function cmdPage(args) {
|
|
|
5248
5523
|
console.log(`TASK PAGE ${taskRef(task)}`);
|
|
5249
5524
|
console.log(`Goal: ${page.goal.text || '(none)'}`);
|
|
5250
5525
|
console.log(`Stage: ${page.stage.current}`);
|
|
5526
|
+
printReviewLanding(page.review);
|
|
5251
5527
|
console.log(`Next: ${page.stage.next_action.command || page.stage.next_action.label}`);
|
|
5252
5528
|
console.log(`Chat: ${page.chat.command}`);
|
|
5253
|
-
if (page.review.verification_chat) console.log(`
|
|
5254
|
-
if (page.review.human_accept.enabled) console.log(`
|
|
5529
|
+
if (page.review.verification_chat) console.log(`Check command: ${page.review.verification_chat.command}`);
|
|
5530
|
+
if (page.review.human_accept.enabled) console.log(`Land: ${page.review.human_accept.command}`);
|
|
5255
5531
|
}
|
|
5256
5532
|
|
|
5257
5533
|
function cmdReviewChat(args) {
|
|
@@ -5320,6 +5596,611 @@ function taskCommandQuote(value) {
|
|
|
5320
5596
|
return `"${text || '...'}"`;
|
|
5321
5597
|
}
|
|
5322
5598
|
|
|
5599
|
+
function cleanPublicText(value, max = 500) {
|
|
5600
|
+
const text = String(value || '').replace(/\s+/g, ' ').trim();
|
|
5601
|
+
if (!text) return '';
|
|
5602
|
+
return text.length > max ? `${text.slice(0, Math.max(0, max - 3)).trim()}...` : text;
|
|
5603
|
+
}
|
|
5604
|
+
|
|
5605
|
+
function publicWords(value) {
|
|
5606
|
+
return (String(value || '').toLowerCase().match(/[a-z0-9]{3,}/g) || [])
|
|
5607
|
+
.map(word => word.endsWith('s') && word.length > 4 ? word.slice(0, -1) : word)
|
|
5608
|
+
.filter(word => !new Set([
|
|
5609
|
+
'and',
|
|
5610
|
+
'for',
|
|
5611
|
+
'from',
|
|
5612
|
+
'into',
|
|
5613
|
+
'the',
|
|
5614
|
+
'this',
|
|
5615
|
+
'that',
|
|
5616
|
+
'task',
|
|
5617
|
+
'work',
|
|
5618
|
+
'with',
|
|
5619
|
+
]).has(word));
|
|
5620
|
+
}
|
|
5621
|
+
|
|
5622
|
+
function parseMemberFrontmatter(text) {
|
|
5623
|
+
const source = String(text || '');
|
|
5624
|
+
if (!source.startsWith('---')) return {};
|
|
5625
|
+
const end = source.indexOf('\n---', 3);
|
|
5626
|
+
if (end === -1) return {};
|
|
5627
|
+
const block = source.slice(3, end).split(/\r?\n/);
|
|
5628
|
+
const data = {};
|
|
5629
|
+
for (const raw of block) {
|
|
5630
|
+
const match = raw.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
|
|
5631
|
+
if (!match) continue;
|
|
5632
|
+
data[match[1]] = match[2].replace(/^["']|["']$/g, '').trim();
|
|
5633
|
+
}
|
|
5634
|
+
return data;
|
|
5635
|
+
}
|
|
5636
|
+
|
|
5637
|
+
function readTeamMembers(root = process.cwd()) {
|
|
5638
|
+
const teamDir = path.join(root, 'atris', 'team');
|
|
5639
|
+
if (!fs.existsSync(teamDir)) return [];
|
|
5640
|
+
return fs.readdirSync(teamDir, { withFileTypes: true })
|
|
5641
|
+
.filter(entry => entry.isDirectory())
|
|
5642
|
+
.map(entry => {
|
|
5643
|
+
const slug = entry.name;
|
|
5644
|
+
const memberPath = path.join(teamDir, slug, 'MEMBER.md');
|
|
5645
|
+
if (!fs.existsSync(memberPath)) return null;
|
|
5646
|
+
const text = fs.readFileSync(memberPath, 'utf8');
|
|
5647
|
+
const frontmatter = parseMemberFrontmatter(text);
|
|
5648
|
+
return {
|
|
5649
|
+
slug,
|
|
5650
|
+
role: cleanPublicText(frontmatter.role || slug.replace(/[-_]/g, ' '), 120),
|
|
5651
|
+
description: cleanPublicText(frontmatter.description || '', 240),
|
|
5652
|
+
path: memberPath,
|
|
5653
|
+
};
|
|
5654
|
+
})
|
|
5655
|
+
.filter(Boolean);
|
|
5656
|
+
}
|
|
5657
|
+
|
|
5658
|
+
const GENERIC_MEMBER_SLUGS = new Set([
|
|
5659
|
+
'_template',
|
|
5660
|
+
'coordinator',
|
|
5661
|
+
'executor',
|
|
5662
|
+
'generalist',
|
|
5663
|
+
'navigator',
|
|
5664
|
+
'supervisor',
|
|
5665
|
+
]);
|
|
5666
|
+
|
|
5667
|
+
function scoreTeamMember(member, words, tag) {
|
|
5668
|
+
const slug = String(member.slug || '').toLowerCase();
|
|
5669
|
+
const role = String(member.role || '').toLowerCase();
|
|
5670
|
+
const description = String(member.description || '').toLowerCase();
|
|
5671
|
+
const haystack = `${slug} ${role} ${description}`.replace(/[-_]/g, ' ');
|
|
5672
|
+
let score = 0;
|
|
5673
|
+
const cleanTag = String(tag || '').toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim();
|
|
5674
|
+
if (cleanTag) {
|
|
5675
|
+
if (slug === cleanTag || slug.replace(/[-_]/g, ' ') === cleanTag) score += 12;
|
|
5676
|
+
else if (haystack.includes(cleanTag)) score += 5;
|
|
5677
|
+
}
|
|
5678
|
+
for (const word of words) {
|
|
5679
|
+
if (slug.includes(word)) score += 5;
|
|
5680
|
+
else if (role.includes(word)) score += 3;
|
|
5681
|
+
else if (description.includes(word)) score += 2;
|
|
5682
|
+
}
|
|
5683
|
+
if (GENERIC_MEMBER_SLUGS.has(slug)) score -= 3;
|
|
5684
|
+
return score;
|
|
5685
|
+
}
|
|
5686
|
+
|
|
5687
|
+
function plainMemberDescription(member) {
|
|
5688
|
+
const description = cleanPublicText(member && member.description, 220);
|
|
5689
|
+
if (!description) return '';
|
|
5690
|
+
const lowered = `${description.charAt(0).toLowerCase()}${description.slice(1)}`;
|
|
5691
|
+
return /[.!?]$/.test(lowered) ? lowered : `${lowered}.`;
|
|
5692
|
+
}
|
|
5693
|
+
|
|
5694
|
+
function chooseTaskOwner({ purpose, tag, requestedOwner, root = process.cwd() } = {}) {
|
|
5695
|
+
const members = readTeamMembers(root);
|
|
5696
|
+
const requested = cleanPublicText(requestedOwner, 80);
|
|
5697
|
+
if (requested) {
|
|
5698
|
+
const match = members.find(member => member.slug === requested);
|
|
5699
|
+
if (match) {
|
|
5700
|
+
const description = plainMemberDescription(match);
|
|
5701
|
+
return {
|
|
5702
|
+
owner: match.slug,
|
|
5703
|
+
member: match,
|
|
5704
|
+
source: 'requested',
|
|
5705
|
+
reason: `${match.slug} matches this work${description ? ` because ${description}` : '.'}`,
|
|
5706
|
+
};
|
|
5707
|
+
}
|
|
5708
|
+
return {
|
|
5709
|
+
owner: requested,
|
|
5710
|
+
member: null,
|
|
5711
|
+
source: 'requested',
|
|
5712
|
+
reason: `${requested} was requested, but no matching atris/team member was found.`,
|
|
5713
|
+
};
|
|
5714
|
+
}
|
|
5715
|
+
const words = publicWords(`${purpose || ''} ${tag || ''}`);
|
|
5716
|
+
let best = null;
|
|
5717
|
+
for (const member of members) {
|
|
5718
|
+
const score = scoreTeamMember(member, words, tag);
|
|
5719
|
+
if (!best || score > best.score) best = { member, score };
|
|
5720
|
+
}
|
|
5721
|
+
if (best && best.score > 0) {
|
|
5722
|
+
const member = best.member;
|
|
5723
|
+
const description = plainMemberDescription(member);
|
|
5724
|
+
return {
|
|
5725
|
+
owner: member.slug,
|
|
5726
|
+
member,
|
|
5727
|
+
source: 'team',
|
|
5728
|
+
score: best.score,
|
|
5729
|
+
reason: `${member.slug} fits this work${description ? ` because ${description}` : '.'}`,
|
|
5730
|
+
};
|
|
5731
|
+
}
|
|
5732
|
+
return {
|
|
5733
|
+
owner: DEFAULT_OWNER,
|
|
5734
|
+
member: null,
|
|
5735
|
+
source: 'fallback',
|
|
5736
|
+
reason: `${DEFAULT_OWNER} is handling it because no specific atris/team owner matched this work.`,
|
|
5737
|
+
};
|
|
5738
|
+
}
|
|
5739
|
+
|
|
5740
|
+
function isGenericPlanActor(value) {
|
|
5741
|
+
const actor = cleanPublicText(value, 80).toLowerCase();
|
|
5742
|
+
if (!actor) return true;
|
|
5743
|
+
if (actor === String(DEFAULT_OWNER || '').toLowerCase()) return true;
|
|
5744
|
+
return GENERIC_MEMBER_SLUGS.has(actor) || new Set([
|
|
5745
|
+
'codex',
|
|
5746
|
+
'codex-executor',
|
|
5747
|
+
'claude',
|
|
5748
|
+
'claude-code',
|
|
5749
|
+
'cursor',
|
|
5750
|
+
'devin',
|
|
5751
|
+
]).has(actor);
|
|
5752
|
+
}
|
|
5753
|
+
|
|
5754
|
+
function taskTextMentionsActor(actor, text) {
|
|
5755
|
+
const actorWords = publicWords(actor);
|
|
5756
|
+
if (!actorWords.length) return false;
|
|
5757
|
+
const words = new Set(publicWords(text));
|
|
5758
|
+
return actorWords.some(word => words.has(word));
|
|
5759
|
+
}
|
|
5760
|
+
|
|
5761
|
+
function buildPublicPlan({ purpose, owner, ownerReason, plan, expected }) {
|
|
5762
|
+
const cleanPurpose = cleanPublicText(purpose, 240);
|
|
5763
|
+
const cleanOwner = cleanPublicText(owner, 80);
|
|
5764
|
+
const cleanReason = cleanPublicText(ownerReason, 240);
|
|
5765
|
+
const cleanPlan = cleanPublicText(plan, 320) || `${cleanOwner || 'The owner'} will make the smallest needed change, then check the result.`;
|
|
5766
|
+
const cleanExpected = cleanPublicText(expected, 240) || 'the check passes and the result is ready to review.';
|
|
5767
|
+
return {
|
|
5768
|
+
purpose: cleanPurpose,
|
|
5769
|
+
owner: cleanOwner,
|
|
5770
|
+
owner_reason: cleanReason,
|
|
5771
|
+
plan: cleanPlan,
|
|
5772
|
+
expected_result: cleanExpected,
|
|
5773
|
+
};
|
|
5774
|
+
}
|
|
5775
|
+
|
|
5776
|
+
function renderPublicPlan(plan) {
|
|
5777
|
+
const lines = [];
|
|
5778
|
+
if (plan.purpose) lines.push(`Purpose: ${plan.purpose}`);
|
|
5779
|
+
if (plan.owner) lines.push(`Owner: ${plan.owner} is handling it.`);
|
|
5780
|
+
if (plan.owner_reason) lines.push(`Why: ${plan.owner_reason}`);
|
|
5781
|
+
if (plan.plan) lines.push(`Plan: ${plan.plan}`);
|
|
5782
|
+
if (plan.expected_result) lines.push(`Expected result: ${plan.expected_result}`);
|
|
5783
|
+
return lines.join('\n');
|
|
5784
|
+
}
|
|
5785
|
+
|
|
5786
|
+
function planTraceData(plan, ownerChoice) {
|
|
5787
|
+
return {
|
|
5788
|
+
schema: 'atris.task_plan_trace.v1',
|
|
5789
|
+
purpose: plan.purpose,
|
|
5790
|
+
owner: plan.owner,
|
|
5791
|
+
owner_reason: plan.owner_reason,
|
|
5792
|
+
plan: plan.plan,
|
|
5793
|
+
expected_result: plan.expected_result,
|
|
5794
|
+
owner_source: ownerChoice && ownerChoice.source || null,
|
|
5795
|
+
owner_score: ownerChoice && ownerChoice.score || null,
|
|
5796
|
+
recorded_at: new Date().toISOString(),
|
|
5797
|
+
};
|
|
5798
|
+
}
|
|
5799
|
+
|
|
5800
|
+
function planTraceNote(plan, ownerChoice) {
|
|
5801
|
+
return `TASK_PLAN_TRACE ${JSON.stringify(planTraceData(plan, ownerChoice))}`;
|
|
5802
|
+
}
|
|
5803
|
+
|
|
5804
|
+
function traceLineFromContent(content, prefix) {
|
|
5805
|
+
const lines = String(content || '').split(/\r?\n/);
|
|
5806
|
+
return lines.find(line => line.startsWith(prefix)) || '';
|
|
5807
|
+
}
|
|
5808
|
+
|
|
5809
|
+
function latestTraceValue(task, prefix, key) {
|
|
5810
|
+
const messages = Array.isArray(task && task.messages) ? task.messages : [];
|
|
5811
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
5812
|
+
const content = String(messages[i] && messages[i].content || '');
|
|
5813
|
+
const line = traceLineFromContent(content, prefix);
|
|
5814
|
+
if (!line) continue;
|
|
5815
|
+
try {
|
|
5816
|
+
const parsed = JSON.parse(line.slice(prefix.length).trim());
|
|
5817
|
+
const value = parsed && parsed[key];
|
|
5818
|
+
if (value !== undefined && value !== null && String(value).trim()) return String(value);
|
|
5819
|
+
} catch (_) {
|
|
5820
|
+
continue;
|
|
5821
|
+
}
|
|
5822
|
+
}
|
|
5823
|
+
return '';
|
|
5824
|
+
}
|
|
5825
|
+
|
|
5826
|
+
function taskHasTrace(task, prefix) {
|
|
5827
|
+
const messages = Array.isArray(task && task.messages) ? task.messages : [];
|
|
5828
|
+
if (messages.some(message => traceLineFromContent(message && message.content, prefix))) return true;
|
|
5829
|
+
const events = Array.isArray(task && task.events) ? task.events : [];
|
|
5830
|
+
return events.some(event => {
|
|
5831
|
+
const payload = event && event.payload && typeof event.payload === 'object' ? event.payload : {};
|
|
5832
|
+
if (traceLineFromContent(payload.stage_packet, prefix)) return true;
|
|
5833
|
+
if (traceLineFromContent(payload.result_packet, prefix)) return true;
|
|
5834
|
+
if (prefix === 'TASK_RESULT_TRACE ' && payload.result_trace && typeof payload.result_trace === 'object') return true;
|
|
5835
|
+
if (prefix === 'TASK_PLAN_TRACE ' && payload.plan_trace && typeof payload.plan_trace === 'object') return true;
|
|
5836
|
+
return false;
|
|
5837
|
+
});
|
|
5838
|
+
}
|
|
5839
|
+
|
|
5840
|
+
function taskPurpose(task) {
|
|
5841
|
+
const metadata = task && task.metadata || {};
|
|
5842
|
+
return cleanPublicText(
|
|
5843
|
+
metadata.task_goal
|
|
5844
|
+
|| metadata.goal_objective
|
|
5845
|
+
|| metadata.objective
|
|
5846
|
+
|| metadata.stage_goal
|
|
5847
|
+
|| latestTraceValue(task, 'TASK_PLAN_TRACE ', 'purpose')
|
|
5848
|
+
|| task && task.title
|
|
5849
|
+
|| '',
|
|
5850
|
+
240,
|
|
5851
|
+
);
|
|
5852
|
+
}
|
|
5853
|
+
|
|
5854
|
+
function buildPublicResult(task, fields) {
|
|
5855
|
+
const owner = cleanPublicText(
|
|
5856
|
+
fields.owner
|
|
5857
|
+
|| latestTraceValue(task, 'TASK_PLAN_TRACE ', 'owner')
|
|
5858
|
+
|| taskAssignee(task)
|
|
5859
|
+
|| task && task.claimed_by
|
|
5860
|
+
|| fields.actor,
|
|
5861
|
+
80,
|
|
5862
|
+
);
|
|
5863
|
+
const result = {
|
|
5864
|
+
purpose: cleanPublicText(fields.purpose || taskPurpose(task), 240),
|
|
5865
|
+
owner,
|
|
5866
|
+
changed: cleanPublicText(fields.changed, 320),
|
|
5867
|
+
checked: cleanPublicText(fields.checked, 320),
|
|
5868
|
+
passed: cleanPublicText(fields.passed, 240),
|
|
5869
|
+
failed: cleanPublicText(fields.failed, 240),
|
|
5870
|
+
cost: cleanPublicText(fields.cost, 80),
|
|
5871
|
+
saved: cleanPublicText(fields.saved, RESULT_SAVED_TEXT_LIMIT),
|
|
5872
|
+
try_next: cleanPublicText(fields.tryNext, 240),
|
|
5873
|
+
status: cleanPublicText(fields.status, 160) || 'ready for review',
|
|
5874
|
+
};
|
|
5875
|
+
return result;
|
|
5876
|
+
}
|
|
5877
|
+
|
|
5878
|
+
function renderPublicResult(result) {
|
|
5879
|
+
return [
|
|
5880
|
+
`Changed: ${cleanPublicText(result && result.changed, 320) || 'changed the requested work'}`,
|
|
5881
|
+
`Checked: ${cleanPublicText(result && result.checked, 320) || 'checked the result'}`,
|
|
5882
|
+
`Try: ${cleanPublicText(result && result.try_next, 240) || 'try the changed work'}`,
|
|
5883
|
+
].join('\n');
|
|
5884
|
+
}
|
|
5885
|
+
|
|
5886
|
+
function latestResultTrace(task) {
|
|
5887
|
+
const messages = Array.isArray(task && task.messages) ? task.messages : [];
|
|
5888
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
5889
|
+
const content = String(messages[i] && messages[i].content || '');
|
|
5890
|
+
const line = traceLineFromContent(content, 'TASK_RESULT_TRACE ');
|
|
5891
|
+
if (!line) continue;
|
|
5892
|
+
try {
|
|
5893
|
+
const parsed = JSON.parse(line.slice('TASK_RESULT_TRACE '.length).trim());
|
|
5894
|
+
if (parsed && typeof parsed === 'object') return parsed;
|
|
5895
|
+
} catch (_) {}
|
|
5896
|
+
}
|
|
5897
|
+
|
|
5898
|
+
const events = Array.isArray(task && task.events) ? task.events : [];
|
|
5899
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
5900
|
+
const payload = events[i] && events[i].payload && typeof events[i].payload === 'object'
|
|
5901
|
+
? events[i].payload
|
|
5902
|
+
: {};
|
|
5903
|
+
if (payload.result_trace && typeof payload.result_trace === 'object') return payload.result_trace;
|
|
5904
|
+
const packetLine = traceLineFromContent(payload.result_packet, 'TASK_RESULT_TRACE ');
|
|
5905
|
+
if (!packetLine) continue;
|
|
5906
|
+
try {
|
|
5907
|
+
const parsed = JSON.parse(packetLine.slice('TASK_RESULT_TRACE '.length).trim());
|
|
5908
|
+
if (parsed && typeof parsed === 'object') return parsed;
|
|
5909
|
+
} catch (_) {}
|
|
5910
|
+
}
|
|
5911
|
+
return null;
|
|
5912
|
+
}
|
|
5913
|
+
|
|
5914
|
+
function buildAcceptHumanResult({ task, proof, nextTask, publicSync }) {
|
|
5915
|
+
const trace = latestResultTrace(task) || {};
|
|
5916
|
+
const traceChanged = cleanPublicText(trace.changed, 320);
|
|
5917
|
+
const changed = traceChanged === 'prepared the work for review'
|
|
5918
|
+
? 'accepted the completed work'
|
|
5919
|
+
: traceChanged
|
|
5920
|
+
|| cleanPublicText(task && task.title, 260)
|
|
5921
|
+
|| 'accepted the completed work';
|
|
5922
|
+
let checked = cleanPublicText(trace.checked, 320)
|
|
5923
|
+
|| cleanPublicText(proof, 320)
|
|
5924
|
+
|| 'checked the proof';
|
|
5925
|
+
if (publicSync && publicSync.enabled === true && !publicSync.ok) {
|
|
5926
|
+
const error = cleanPublicText(publicSync.error || 'publish failed', 140);
|
|
5927
|
+
checked = `${checked}; AgentXP publish failed${error ? ` (${error})` : ''}`;
|
|
5928
|
+
}
|
|
5929
|
+
const tryNext = cleanPublicText(nextTask, 240)
|
|
5930
|
+
|| cleanPublicText(trace.try_next, 240)
|
|
5931
|
+
|| 'try the changed work';
|
|
5932
|
+
return { changed, checked, try_next: tryNext };
|
|
5933
|
+
}
|
|
5934
|
+
|
|
5935
|
+
function renderAcceptLanding({ task, proof, nextTask, publicSync }) {
|
|
5936
|
+
return renderPublicResult(buildAcceptHumanResult({
|
|
5937
|
+
task,
|
|
5938
|
+
proof,
|
|
5939
|
+
nextTask,
|
|
5940
|
+
publicSync,
|
|
5941
|
+
}));
|
|
5942
|
+
}
|
|
5943
|
+
|
|
5944
|
+
async function publishAcceptAgentXp(args, actor) {
|
|
5945
|
+
const token = flag(args, '--token');
|
|
5946
|
+
const syncArgs = ['--all', '--root', process.cwd(), '--public', '--as', actor];
|
|
5947
|
+
if (typeof token === 'string' && token.trim()) syncArgs.push('--token', token.trim());
|
|
5948
|
+
try {
|
|
5949
|
+
const { syncAgentXp } = require('../commands/xp');
|
|
5950
|
+
const result = await syncAgentXp(syncArgs);
|
|
5951
|
+
const server = result && result.server ? result.server : {};
|
|
5952
|
+
const publicCount = Number(server.public_accepted_count);
|
|
5953
|
+
const acceptedCount = Number(server.accepted_count);
|
|
5954
|
+
const published = (
|
|
5955
|
+
(Number.isFinite(publicCount) && publicCount > 0)
|
|
5956
|
+
|| (Number.isFinite(acceptedCount) && acceptedCount > 0 && server.private_agentxp !== true)
|
|
5957
|
+
);
|
|
5958
|
+
return {
|
|
5959
|
+
enabled: true,
|
|
5960
|
+
ok: published,
|
|
5961
|
+
result,
|
|
5962
|
+
error: published ? null : 'server accepted no public AgentXP rows',
|
|
5963
|
+
};
|
|
5964
|
+
} catch (error) {
|
|
5965
|
+
return {
|
|
5966
|
+
enabled: true,
|
|
5967
|
+
ok: false,
|
|
5968
|
+
error: error && error.message ? error.message : String(error),
|
|
5969
|
+
};
|
|
5970
|
+
}
|
|
5971
|
+
}
|
|
5972
|
+
|
|
5973
|
+
function todayResultLogName() {
|
|
5974
|
+
const now = new Date();
|
|
5975
|
+
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}.md`;
|
|
5976
|
+
}
|
|
5977
|
+
|
|
5978
|
+
function appendResultOwnerLog(root, task, result) {
|
|
5979
|
+
const owner = cleanPublicText(result && result.owner, 80);
|
|
5980
|
+
if (!owner || !/^[A-Za-z0-9._-]+$/.test(owner)) return null;
|
|
5981
|
+
const memberFile = path.join(root, 'atris', 'team', owner, 'MEMBER.md');
|
|
5982
|
+
if (!fs.existsSync(memberFile)) return null;
|
|
5983
|
+
const logName = todayResultLogName();
|
|
5984
|
+
const logDir = path.join(root, 'atris', 'team', owner, 'logs');
|
|
5985
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
5986
|
+
const logPath = path.join(logDir, logName);
|
|
5987
|
+
const stamp = new Date().toTimeString().slice(0, 5);
|
|
5988
|
+
const lines = [
|
|
5989
|
+
`## ${stamp} - Result`,
|
|
5990
|
+
`- task: ${taskRef(task)}`,
|
|
5991
|
+
`- purpose: ${result.purpose || ''}`,
|
|
5992
|
+
`- result: ${result.changed || ''}`,
|
|
5993
|
+
`- checked: ${result.checked || ''}`,
|
|
5994
|
+
`- saved: ${result.saved || ''}`,
|
|
5995
|
+
`- try: ${result.try_next || ''}`,
|
|
5996
|
+
`- status: ${result.status || ''}`,
|
|
5997
|
+
'',
|
|
5998
|
+
];
|
|
5999
|
+
fs.appendFileSync(logPath, lines.join('\n'), 'utf8');
|
|
6000
|
+
return {
|
|
6001
|
+
member_log_path: path.relative(root, logPath),
|
|
6002
|
+
};
|
|
6003
|
+
}
|
|
6004
|
+
|
|
6005
|
+
function resultTraceData(result, fields) {
|
|
6006
|
+
return {
|
|
6007
|
+
schema: 'atris.task_result_trace.v1',
|
|
6008
|
+
purpose: result.purpose,
|
|
6009
|
+
owner: result.owner || null,
|
|
6010
|
+
changed: result.changed,
|
|
6011
|
+
checked: result.checked,
|
|
6012
|
+
passed: result.passed || null,
|
|
6013
|
+
failed: result.failed || null,
|
|
6014
|
+
cost: result.cost || null,
|
|
6015
|
+
saved: result.saved || null,
|
|
6016
|
+
try_next: result.try_next || null,
|
|
6017
|
+
status: result.status,
|
|
6018
|
+
files: cleanPublicText(fields.files, 500) || null,
|
|
6019
|
+
commands: cleanPublicText(fields.commands, 500) || null,
|
|
6020
|
+
member_log_path: fields.savedPaths && fields.savedPaths.member_log_path || null,
|
|
6021
|
+
recorded_at: new Date().toISOString(),
|
|
6022
|
+
};
|
|
6023
|
+
}
|
|
6024
|
+
|
|
6025
|
+
function resultTraceNote(result, fields) {
|
|
6026
|
+
return `TASK_RESULT_TRACE ${JSON.stringify(resultTraceData(result, fields))}`;
|
|
6027
|
+
}
|
|
6028
|
+
|
|
6029
|
+
function cmdPlanPreview(args) {
|
|
6030
|
+
const pos = positional(args);
|
|
6031
|
+
const purpose = cleanPublicText(textFlag(args, ['--purpose', '--goal', '--objective']) || pos.join(' '), 240);
|
|
6032
|
+
if (!purpose) failTask('atris task plan-preview', 'missing_purpose', 'purpose required');
|
|
6033
|
+
const tag = textFlag(args, ['--tag']);
|
|
6034
|
+
const requestedOwner = textFlag(args, ['--owner', '--as', '--member']);
|
|
6035
|
+
const planText = textFlag(args, ['--plan', '--action', '--first-move']);
|
|
6036
|
+
const expected = textFlag(args, ['--expected', '--expected-result', '--exit']);
|
|
6037
|
+
const recordRef = textFlag(args, ['--task', '--record']);
|
|
6038
|
+
const taskDb = getTaskDb();
|
|
6039
|
+
const db = taskDb.open();
|
|
6040
|
+
const ownerChoice = chooseTaskOwner({
|
|
6041
|
+
purpose,
|
|
6042
|
+
tag,
|
|
6043
|
+
requestedOwner,
|
|
6044
|
+
root: taskDb.workspaceRoot(),
|
|
6045
|
+
});
|
|
6046
|
+
const publicPlan = buildPublicPlan({
|
|
6047
|
+
purpose,
|
|
6048
|
+
owner: ownerChoice.owner,
|
|
6049
|
+
ownerReason: ownerChoice.reason,
|
|
6050
|
+
plan: planText,
|
|
6051
|
+
expected,
|
|
6052
|
+
});
|
|
6053
|
+
let recorded = null;
|
|
6054
|
+
if (recordRef) {
|
|
6055
|
+
const taskId = requireTaskId(taskDb, db, recordRef, 'atris task plan-preview');
|
|
6056
|
+
const note = taskDb.noteTask(db, {
|
|
6057
|
+
id: taskId,
|
|
6058
|
+
actor: publicPlan.owner || DEFAULT_OWNER,
|
|
6059
|
+
content: planTraceNote(publicPlan, ownerChoice),
|
|
6060
|
+
});
|
|
6061
|
+
if (!note.noted) failTask('atris task plan-preview', note.reason || 'note_failed', `plan-preview failed: ${note.reason || 'note_failed'}`, 1);
|
|
6062
|
+
const { outPath } = writeDefaultProjection(taskDb, db);
|
|
6063
|
+
recorded = {
|
|
6064
|
+
task_id: taskId,
|
|
6065
|
+
version: note.event.version,
|
|
6066
|
+
projection_path: outPath,
|
|
6067
|
+
};
|
|
6068
|
+
}
|
|
6069
|
+
if (wantsJson(args)) {
|
|
6070
|
+
printJson({
|
|
6071
|
+
ok: true,
|
|
6072
|
+
action: 'plan_preview',
|
|
6073
|
+
plan: publicPlan,
|
|
6074
|
+
owner_choice: {
|
|
6075
|
+
owner: ownerChoice.owner,
|
|
6076
|
+
source: ownerChoice.source,
|
|
6077
|
+
score: ownerChoice.score || null,
|
|
6078
|
+
member: ownerChoice.member ? {
|
|
6079
|
+
slug: ownerChoice.member.slug,
|
|
6080
|
+
role: ownerChoice.member.role,
|
|
6081
|
+
description: ownerChoice.member.description,
|
|
6082
|
+
} : null,
|
|
6083
|
+
},
|
|
6084
|
+
recorded,
|
|
6085
|
+
text: renderPublicPlan(publicPlan),
|
|
6086
|
+
});
|
|
6087
|
+
return;
|
|
6088
|
+
}
|
|
6089
|
+
console.log(renderPublicPlan(publicPlan));
|
|
6090
|
+
}
|
|
6091
|
+
|
|
6092
|
+
function buildAutomaticPlanTrace(taskDb, task, { actor, actorExplicit = false, owner, goal, summary, firstMove, exit } = {}) {
|
|
6093
|
+
if (!task) return { trace: null, plan: null, ownerChoice: null, ownerForStage: owner || null };
|
|
6094
|
+
const metadata = task.metadata || {};
|
|
6095
|
+
const purpose = cleanPublicText(goal, 240) || taskPurpose(task);
|
|
6096
|
+
const claimedOwner = cleanPublicText(task.claimed_by, 80);
|
|
6097
|
+
const actorNamed = taskTextMentionsActor(actor, `${purpose} ${task.title || ''} ${task.tag || ''}`);
|
|
6098
|
+
const requestedActor = actorExplicit && (!isGenericPlanActor(actor) || actorNamed) ? actor : null;
|
|
6099
|
+
const requestedOwner = owner || claimedOwner || requestedActor || null;
|
|
6100
|
+
const ownerChoice = chooseTaskOwner({
|
|
6101
|
+
purpose,
|
|
6102
|
+
tag: task.tag,
|
|
6103
|
+
requestedOwner,
|
|
6104
|
+
root: taskDb.workspaceRoot(),
|
|
6105
|
+
});
|
|
6106
|
+
const publicPlan = buildPublicPlan({
|
|
6107
|
+
purpose,
|
|
6108
|
+
owner: ownerChoice.owner,
|
|
6109
|
+
ownerReason: ownerChoice.reason,
|
|
6110
|
+
plan: firstMove || summary || metadata.first_move || metadata.stage_summary || '',
|
|
6111
|
+
expected: exit || metadata.exit_condition || '',
|
|
6112
|
+
});
|
|
6113
|
+
return {
|
|
6114
|
+
trace: planTraceData(publicPlan, ownerChoice),
|
|
6115
|
+
plan: publicPlan,
|
|
6116
|
+
ownerChoice: {
|
|
6117
|
+
owner: ownerChoice.owner,
|
|
6118
|
+
source: ownerChoice.source,
|
|
6119
|
+
score: ownerChoice.score || null,
|
|
6120
|
+
},
|
|
6121
|
+
ownerForStage: publicPlan.owner || actor || DEFAULT_OWNER,
|
|
6122
|
+
};
|
|
6123
|
+
}
|
|
6124
|
+
|
|
6125
|
+
function buildAutomaticResultTrace(taskDb, db, taskId, { actor, proof, changed, checked, passed, failed, cost, saved, tryNext, status, files, commands } = {}) {
|
|
6126
|
+
const task = taskDetail(taskDb, db, taskId);
|
|
6127
|
+
if (!task || taskHasTrace(task, 'TASK_RESULT_TRACE ')) return null;
|
|
6128
|
+
const fields = {
|
|
6129
|
+
actor,
|
|
6130
|
+
changed: cleanPublicText(changed, 320) || 'prepared the work for review',
|
|
6131
|
+
checked: cleanPublicText(checked, 320) || cleanPublicText(proof, 320),
|
|
6132
|
+
passed: cleanPublicText(passed, 240),
|
|
6133
|
+
failed: cleanPublicText(failed, 240),
|
|
6134
|
+
cost: cleanPublicText(cost, 80),
|
|
6135
|
+
saved: cleanPublicText(saved, RESULT_SAVED_TEXT_LIMIT) || 'task trace was updated',
|
|
6136
|
+
tryNext: cleanPublicText(tryNext, 240) || 'review the proof and try the changed work',
|
|
6137
|
+
status: cleanPublicText(status, 160) || 'ready for review',
|
|
6138
|
+
files,
|
|
6139
|
+
commands,
|
|
6140
|
+
};
|
|
6141
|
+
const result = buildPublicResult(task, fields);
|
|
6142
|
+
const savedPaths = appendResultOwnerLog(taskDb.workspaceRoot(), task, result);
|
|
6143
|
+
const trace = resultTraceData(result, { ...fields, savedPaths });
|
|
6144
|
+
return {
|
|
6145
|
+
result,
|
|
6146
|
+
trace,
|
|
6147
|
+
saved_paths: savedPaths,
|
|
6148
|
+
};
|
|
6149
|
+
}
|
|
6150
|
+
|
|
6151
|
+
function cmdResult(args) {
|
|
6152
|
+
const pos = positional(args);
|
|
6153
|
+
const id = pos[0];
|
|
6154
|
+
if (!id) failTask('atris task result', 'missing_id', 'id required');
|
|
6155
|
+
const fields = {
|
|
6156
|
+
purpose: textFlag(args, ['--purpose', '--goal', '--objective']),
|
|
6157
|
+
changed: textFlag(args, ['--changed', '--result', '--done']),
|
|
6158
|
+
checked: textFlag(args, ['--checked', '--check', '--verified']),
|
|
6159
|
+
passed: textFlag(args, ['--passed', '--pass']),
|
|
6160
|
+
failed: textFlag(args, ['--failed', '--fail']),
|
|
6161
|
+
cost: textFlag(args, ['--cost']),
|
|
6162
|
+
saved: textFlag(args, ['--saved', '--savings']),
|
|
6163
|
+
tryNext: textFlag(args, ['--try', '--try-next', '--handoff']),
|
|
6164
|
+
status: textFlag(args, ['--status']),
|
|
6165
|
+
files: textFlag(args, ['--files']),
|
|
6166
|
+
commands: textFlag(args, ['--commands', '--command']),
|
|
6167
|
+
};
|
|
6168
|
+
if (!fields.changed) failTask('atris task result', 'changed_required', '--changed required');
|
|
6169
|
+
if (!fields.checked) failTask('atris task result', 'checked_required', '--checked required');
|
|
6170
|
+
if (!fields.tryNext) failTask('atris task result', 'try_required', '--try required');
|
|
6171
|
+
const actor = String(flag(args, '--as') || DEFAULT_OWNER);
|
|
6172
|
+
const taskDb = getTaskDb();
|
|
6173
|
+
const db = taskDb.open();
|
|
6174
|
+
const taskId = requireTaskId(taskDb, db, id, 'atris task result');
|
|
6175
|
+
const task = taskDetail(taskDb, db, taskId);
|
|
6176
|
+
if (!task) failTask('atris task result', 'not_found', `task not found: ${id}`, 1);
|
|
6177
|
+
const result = buildPublicResult(task, { ...fields, actor });
|
|
6178
|
+
const savedPaths = appendResultOwnerLog(taskDb.workspaceRoot(), task, result);
|
|
6179
|
+
const note = taskDb.noteTask(db, {
|
|
6180
|
+
id: taskId,
|
|
6181
|
+
actor,
|
|
6182
|
+
content: resultTraceNote(result, { ...fields, savedPaths }),
|
|
6183
|
+
});
|
|
6184
|
+
if (!note.noted) failTask('atris task result', note.reason || 'note_failed', `result failed: ${note.reason || 'note_failed'}`, 1);
|
|
6185
|
+
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
6186
|
+
const text = renderPublicResult(result);
|
|
6187
|
+
if (wantsJson(args)) {
|
|
6188
|
+
printJson({
|
|
6189
|
+
ok: true,
|
|
6190
|
+
action: 'result',
|
|
6191
|
+
task_id: taskId,
|
|
6192
|
+
version: note.event.version,
|
|
6193
|
+
projection_path: outPath,
|
|
6194
|
+
result,
|
|
6195
|
+
saved_paths: savedPaths,
|
|
6196
|
+
text,
|
|
6197
|
+
task: compactTaskFromProjection(projection, taskId),
|
|
6198
|
+
});
|
|
6199
|
+
return;
|
|
6200
|
+
}
|
|
6201
|
+
console.log(text);
|
|
6202
|
+
}
|
|
6203
|
+
|
|
5323
6204
|
function taskPageGoal(task) {
|
|
5324
6205
|
const metadata = task && task.metadata || {};
|
|
5325
6206
|
const candidates = [
|
|
@@ -5383,7 +6264,7 @@ function taskPageActions(task, { reviewer = 'codex-review', hasExistingReviewFol
|
|
|
5383
6264
|
note_command: `atris task note ${ref} "<context>" --as ${owner}`,
|
|
5384
6265
|
plan_command: `atris task plan ${ref} --goal ${taskCommandQuote(goal)} --exit "<exit condition>" --proof-needed "<verification command>" --first-move "<first move>"`,
|
|
5385
6266
|
do_command: `atris task do ${ref} --as ${owner} --first-move "<first move>"`,
|
|
5386
|
-
ready_command: `atris task ready ${ref} --as ${owner} --proof "<specific proof command/result>"`,
|
|
6267
|
+
ready_command: `atris task ready ${ref} --as ${owner} --proof "<specific proof command/result>" --happened "<what happened>" --checked "<how you know>" --tested "<what you ran or inspected>" --decision "<accept/rework guidance>"`,
|
|
5387
6268
|
review_command: `atris task review ${ref} --reward 0 --as ${actor} --proof "<specific proof command/result>" --verify "<safe verifier command>"`,
|
|
5388
6269
|
};
|
|
5389
6270
|
if (task && task.status === 'review') {
|
|
@@ -5441,7 +6322,7 @@ function taskPageNextAction(task, current, actions, { hasExistingReviewFollowUp
|
|
|
5441
6322
|
if (handoff && handoff.next_action === 'human_accept_waiting') {
|
|
5442
6323
|
return {
|
|
5443
6324
|
key: 'human_accept_waiting',
|
|
5444
|
-
label: '
|
|
6325
|
+
label: 'Ready to land',
|
|
5445
6326
|
command: null,
|
|
5446
6327
|
api: null,
|
|
5447
6328
|
human_accept_command: actions.human_accept_command || null,
|
|
@@ -5462,6 +6343,7 @@ function taskPageContract(task, { reviewer = 'codex-review', hasExistingReviewFo
|
|
|
5462
6343
|
const metadata = task && task.metadata || {};
|
|
5463
6344
|
const current = taskPageCurrentStage(task);
|
|
5464
6345
|
const actions = taskPageActions(task, { reviewer, hasExistingReviewFollowUp });
|
|
6346
|
+
const review = task && (task.review || taskReviewSummary(task));
|
|
5465
6347
|
const recentMessages = (task && Array.isArray(task.messages) ? task.messages : []).slice(-10).map(message => ({
|
|
5466
6348
|
version: message.version || null,
|
|
5467
6349
|
actor: message.actor || null,
|
|
@@ -5505,9 +6387,13 @@ function taskPageContract(task, { reviewer = 'codex-review', hasExistingReviewFo
|
|
|
5505
6387
|
},
|
|
5506
6388
|
actions,
|
|
5507
6389
|
review: {
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
6390
|
+
landing: review && review.landing || null,
|
|
6391
|
+
result: review && review.result || null,
|
|
6392
|
+
summary: review && review.summary || null,
|
|
6393
|
+
proof: review && review.proof || null,
|
|
6394
|
+
approval_status: review && review.approval_status || metadata.approval_status || null,
|
|
6395
|
+
agent_review_pass_count: review && review.agent_review_pass_count || metadata.agent_review_pass_count || null,
|
|
6396
|
+
agent_certified: Boolean(review && review.agent_certified || metadata.agent_certified),
|
|
5511
6397
|
verification_chat: reviewChat,
|
|
5512
6398
|
handoff: reviewHandoff,
|
|
5513
6399
|
human_accept: {
|
|
@@ -5558,7 +6444,7 @@ function appendTaskReviewChat(taskDb, db, taskId, { reviewer = 'codex-review', d
|
|
|
5558
6444
|
throw taskReviewChatError(`not_reviewable_${task.status}`, `review chat requires a task in Review; current status is ${task.status}`, { status: 409, exitCode: 1 });
|
|
5559
6445
|
}
|
|
5560
6446
|
if (!taskAllowsReviewChat(task, { allowCertified: true })) {
|
|
5561
|
-
throw taskReviewChatError('agent_certified_continue_work', 'review chat is closed after agent certification; continue other work or
|
|
6447
|
+
throw taskReviewChatError('agent_certified_continue_work', 'review chat is closed after agent certification; continue other work or land/send back this task', { status: 409, exitCode: 1 });
|
|
5562
6448
|
}
|
|
5563
6449
|
const contract = taskReviewChatContract(task, { reviewer: actor, allowCertified: true });
|
|
5564
6450
|
let event = null;
|
|
@@ -5715,8 +6601,8 @@ function readyHandoffForStep(task, proof, lesson, nextTask, agentCertified) {
|
|
|
5715
6601
|
career_xp_status: 'pending_human_accept',
|
|
5716
6602
|
next_action: agentCertified ? certifiedReviewNextAction(nextTask) : 'agent_review_again',
|
|
5717
6603
|
rule: agentCertified
|
|
5718
|
-
? '
|
|
5719
|
-
: 'Proof is
|
|
6604
|
+
? 'Double-check complete; ready to keep moving. XP lands only after the human lands the task.'
|
|
6605
|
+
: 'Proof is ready; one more agent check before landing. XP waits for the human.',
|
|
5720
6606
|
};
|
|
5721
6607
|
if (reviewChat) {
|
|
5722
6608
|
handoff.review_chat_command = reviewChat.command;
|
|
@@ -5737,7 +6623,7 @@ function runTaskStep(taskDb, db, taskId, options = {}) {
|
|
|
5737
6623
|
const reason = initialHandoffState.next_action === 'continue_work'
|
|
5738
6624
|
? 'agent_certified_continue_work'
|
|
5739
6625
|
: 'agent_certified_waiting_human';
|
|
5740
|
-
throw taskStepError(reason, 'atris task step:
|
|
6626
|
+
throw taskStepError(reason, 'atris task step: ready-to-land rows have no safe agent step; continue other work or land/send back this task', { status: 409, exitCode: 1, page: initialPage });
|
|
5741
6627
|
}
|
|
5742
6628
|
let chat = null;
|
|
5743
6629
|
const message = String(options.message || '').trim();
|
|
@@ -5764,16 +6650,25 @@ function runTaskStep(taskDb, db, taskId, options = {}) {
|
|
|
5764
6650
|
let episode = null;
|
|
5765
6651
|
let xpProjection = null;
|
|
5766
6652
|
if (current === 'backlog') {
|
|
6653
|
+
const automaticPlan = buildAutomaticPlanTrace(taskDb, task, {
|
|
6654
|
+
actor,
|
|
6655
|
+
owner: actor,
|
|
6656
|
+
goal,
|
|
6657
|
+
summary,
|
|
6658
|
+
firstMove: String(options.firstMove || ''),
|
|
6659
|
+
exit: String(options.exit || ''),
|
|
6660
|
+
});
|
|
5767
6661
|
const planned = taskDb.stageTask(db, {
|
|
5768
6662
|
id: taskId,
|
|
5769
6663
|
actor,
|
|
5770
6664
|
stage: 'plan',
|
|
5771
6665
|
goal,
|
|
5772
6666
|
summary,
|
|
5773
|
-
owner: actor,
|
|
6667
|
+
owner: automaticPlan.ownerForStage || actor,
|
|
5774
6668
|
exit: String(options.exit || ''),
|
|
5775
6669
|
proofNeeded: String(options.proofNeeded || ''),
|
|
5776
6670
|
firstMove: String(options.firstMove || ''),
|
|
6671
|
+
planTrace: automaticPlan.trace,
|
|
5777
6672
|
});
|
|
5778
6673
|
if (!planned.staged) taskStepFailure('atris task step', planned, actionPage);
|
|
5779
6674
|
stepAction = 'planned';
|
|
@@ -5807,7 +6702,15 @@ function runTaskStep(taskDb, db, taskId, options = {}) {
|
|
|
5807
6702
|
}
|
|
5808
6703
|
const lesson = String(options.lesson || '');
|
|
5809
6704
|
const nextTask = String(options.nextTask || '');
|
|
5810
|
-
const
|
|
6705
|
+
const resultTrace = buildAutomaticResultTrace(taskDb, db, taskId, { actor, proof });
|
|
6706
|
+
const ready = taskDb.readyTask(db, {
|
|
6707
|
+
id: taskId,
|
|
6708
|
+
actor,
|
|
6709
|
+
proof,
|
|
6710
|
+
lesson,
|
|
6711
|
+
nextTask,
|
|
6712
|
+
resultTrace: resultTrace && resultTrace.trace,
|
|
6713
|
+
});
|
|
5811
6714
|
if (!ready.ready) taskStepFailure('atris task step', ready, actionPage);
|
|
5812
6715
|
task = taskDetail(taskDb, db, taskId) || task;
|
|
5813
6716
|
stepAction = 'ready';
|
|
@@ -5822,7 +6725,7 @@ function runTaskStep(taskDb, db, taskId, options = {}) {
|
|
|
5822
6725
|
const reason = handoffState.next_action === 'continue_work'
|
|
5823
6726
|
? 'agent_certified_continue_work'
|
|
5824
6727
|
: 'agent_certified_waiting_human';
|
|
5825
|
-
throw taskStepError(reason, 'atris task step:
|
|
6728
|
+
throw taskStepError(reason, 'atris task step: ready-to-land rows have no safe agent step; continue other work or land/send back this task', { status: 409, exitCode: 1, page: actionPage });
|
|
5826
6729
|
}
|
|
5827
6730
|
const reviewed = appendTaskReviewChat(taskDb, db, taskId, { reviewer, dryRun: Boolean(options.dryRun) });
|
|
5828
6731
|
stepAction = 'review_chat';
|
|
@@ -5930,7 +6833,7 @@ function runCurrentTaskStep(taskDb, db, { owner = DEFAULT_OWNER, reviewer = 'cod
|
|
|
5930
6833
|
if (nextActionKey === 'human_accept_waiting') {
|
|
5931
6834
|
const error = taskStepError(
|
|
5932
6835
|
'agent_certified_waiting_human',
|
|
5933
|
-
'atris task current-step: selected
|
|
6836
|
+
'atris task current-step: selected row is ready to land; no agent mutation is safe',
|
|
5934
6837
|
{
|
|
5935
6838
|
status: 409,
|
|
5936
6839
|
exitCode: 1,
|
|
@@ -6342,16 +7245,36 @@ function cmdReady(args) {
|
|
|
6342
7245
|
requireMeaningfulTaskProof('atris task ready', proof);
|
|
6343
7246
|
const lesson = flag(args, '--lesson') || '';
|
|
6344
7247
|
const nextTaskInput = normalizeReviewNextTaskInput(typeof flag(args, '--next') === 'string' ? flag(args, '--next') : '');
|
|
7248
|
+
const landing = landingFlags(args);
|
|
6345
7249
|
const actor = String(flag(args, '--as') || DEFAULT_OWNER);
|
|
7250
|
+
const resultFields = {
|
|
7251
|
+
changed: textFlag(args, ['--changed', '--result', '--done']),
|
|
7252
|
+
checked: textFlag(args, ['--checked', '--check', '--verified']),
|
|
7253
|
+
passed: textFlag(args, ['--passed', '--pass']),
|
|
7254
|
+
failed: textFlag(args, ['--failed', '--fail']),
|
|
7255
|
+
cost: textFlag(args, ['--cost']),
|
|
7256
|
+
saved: textFlag(args, ['--saved', '--savings']),
|
|
7257
|
+
tryNext: textFlag(args, ['--try', '--try-next', '--handoff']),
|
|
7258
|
+
status: textFlag(args, ['--status']),
|
|
7259
|
+
files: textFlag(args, ['--files']),
|
|
7260
|
+
commands: textFlag(args, ['--commands', '--command']),
|
|
7261
|
+
};
|
|
6346
7262
|
const taskDb = getTaskDb();
|
|
6347
7263
|
const db = taskDb.open();
|
|
6348
7264
|
const taskId = requireTaskId(taskDb, db, id, 'atris task ready');
|
|
7265
|
+
const resultTrace = buildAutomaticResultTrace(taskDb, db, taskId, {
|
|
7266
|
+
actor,
|
|
7267
|
+
proof: String(proof),
|
|
7268
|
+
...resultFields,
|
|
7269
|
+
});
|
|
6349
7270
|
const result = taskDb.readyTask(db, {
|
|
6350
7271
|
id: taskId,
|
|
6351
7272
|
actor,
|
|
6352
7273
|
proof: String(proof),
|
|
6353
7274
|
lesson: typeof lesson === 'string' ? lesson : '',
|
|
6354
7275
|
nextTask: nextTaskInput.nextTask,
|
|
7276
|
+
resultTrace: resultTrace && resultTrace.trace,
|
|
7277
|
+
landing,
|
|
6355
7278
|
});
|
|
6356
7279
|
if (!result.ready) {
|
|
6357
7280
|
console.error(`ready failed: ${result.reason}`);
|
|
@@ -6373,8 +7296,8 @@ function cmdReady(args) {
|
|
|
6373
7296
|
career_xp_status: 'pending_human_accept',
|
|
6374
7297
|
next_action: agentCertified ? certifiedReviewNextAction(nextTaskInput.nextTask) : 'agent_review_again',
|
|
6375
7298
|
rule: agentCertified
|
|
6376
|
-
? '
|
|
6377
|
-
: 'Proof is
|
|
7299
|
+
? 'Double-check complete; ready to keep moving. XP lands only after the human lands the task.'
|
|
7300
|
+
: 'Proof is ready; one more agent check before landing. XP waits for the human.',
|
|
6378
7301
|
};
|
|
6379
7302
|
if (reviewChat) {
|
|
6380
7303
|
handoff.review_chat_command = reviewChat.command;
|
|
@@ -6397,20 +7320,22 @@ function cmdReady(args) {
|
|
|
6397
7320
|
review_pass_count: result.event.payload.review_pass_count,
|
|
6398
7321
|
agent_certified: agentCertified,
|
|
6399
7322
|
handoff,
|
|
7323
|
+
result_trace: resultTrace,
|
|
6400
7324
|
...(nextTaskInput.ignored ? { review_next_task_ignored: nextTaskInput.ignored } : {}),
|
|
6401
7325
|
projection_path: outPath,
|
|
6402
7326
|
task: compactTaskFromProjection(projection, taskId),
|
|
6403
7327
|
});
|
|
6404
7328
|
return;
|
|
6405
7329
|
}
|
|
6406
|
-
console.log(`ready ${taskRef(compactTaskFromProjection(projection, taskId))} v${result.event.version}
|
|
7330
|
+
console.log(`ready to land ${taskRef(compactTaskFromProjection(projection, taskId))} v${result.event.version}`);
|
|
7331
|
+
if (resultTrace) console.log('Result trace recorded.');
|
|
6407
7332
|
console.log(handoff.rule);
|
|
6408
7333
|
for (const hint of policyHints) {
|
|
6409
7334
|
console.log(`policy (${hint.id}): ${hint.hint}`);
|
|
6410
7335
|
}
|
|
6411
7336
|
}
|
|
6412
7337
|
|
|
6413
|
-
function cmdAccept(args) {
|
|
7338
|
+
async function cmdAccept(args) {
|
|
6414
7339
|
const pos = positional(args);
|
|
6415
7340
|
const id = pos[0];
|
|
6416
7341
|
if (!id) {
|
|
@@ -6490,6 +7415,7 @@ function cmdAccept(args) {
|
|
|
6490
7415
|
// Inform the gate, never block it: show what the receipts named in the proof
|
|
6491
7416
|
// actually say so the accepting human isn't trusting prose.
|
|
6492
7417
|
const evidence = extractReceiptEvidence(proof, projection.workspace_root || process.cwd());
|
|
7418
|
+
const publicSync = hasFlag(args, '--public') ? await publishAcceptAgentXp(args, actor) : null;
|
|
6493
7419
|
if (wantsJson(args)) {
|
|
6494
7420
|
printJson({
|
|
6495
7421
|
ok: true,
|
|
@@ -6499,21 +7425,21 @@ function cmdAccept(args) {
|
|
|
6499
7425
|
reward: reviewed.episode.reward.value,
|
|
6500
7426
|
episode: reviewed.episode,
|
|
6501
7427
|
evidence,
|
|
7428
|
+
public_sync: publicSync,
|
|
6502
7429
|
xp_projection: xpProjection,
|
|
6503
7430
|
projection_path: outPath,
|
|
6504
7431
|
task: compactTaskFromProjection(projection, taskId),
|
|
6505
7432
|
});
|
|
7433
|
+
if (publicSync && !publicSync.ok) process.exitCode = 1;
|
|
6506
7434
|
return;
|
|
6507
7435
|
}
|
|
6508
|
-
console.log(
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6514
|
-
|
|
6515
|
-
evidence.missing.forEach((missingPath) => console.log(` receipt: ${missingPath} MISSING`));
|
|
6516
|
-
}
|
|
7436
|
+
console.log(renderAcceptLanding({
|
|
7437
|
+
task: taskDetail(taskDb, db, taskId) || compactTaskFromProjection(projection, taskId),
|
|
7438
|
+
proof,
|
|
7439
|
+
nextTask,
|
|
7440
|
+
publicSync,
|
|
7441
|
+
}));
|
|
7442
|
+
if (publicSync && !publicSync.ok) process.exitCode = 1;
|
|
6517
7443
|
}
|
|
6518
7444
|
|
|
6519
7445
|
function stampAutoAcceptMetadata(taskDb, db, taskId, actor, policy) {
|
|
@@ -6693,6 +7619,7 @@ function cmdRevise(args) {
|
|
|
6693
7619
|
version: result.event.version,
|
|
6694
7620
|
approval_status: 'revise',
|
|
6695
7621
|
revision_count: result.event.payload.revision_count,
|
|
7622
|
+
episode: result.episode || null,
|
|
6696
7623
|
projection_path: outPath,
|
|
6697
7624
|
task: compactTaskFromProjection(projection, taskId),
|
|
6698
7625
|
});
|
|
@@ -7620,7 +8547,10 @@ function taskBoardHtml() {
|
|
|
7620
8547
|
'<div class="meta"><span class="pill">' + task.status + '</span><span class="pill">' + (task.claimed_by || 'unowned') + '</span><span class="pill">v' + task.current_version + '</span></div>',
|
|
7621
8548
|
'<div class="fact"><b>Goal</b><div id="taskGoal"></div></div>',
|
|
7622
8549
|
'<div class="fact"><b>Lineage</b><div id="taskLineage"></div></div>',
|
|
7623
|
-
'<div class="fact"><b>
|
|
8550
|
+
'<div class="fact"><b>Result</b><div id="taskHappened"></div></div>',
|
|
8551
|
+
'<div class="fact"><b>How I checked</b><div id="taskChecked"></div></div>',
|
|
8552
|
+
'<div class="fact"><b>What I tested</b><div id="taskTested"></div></div>',
|
|
8553
|
+
'<div class="fact"><b>Decision</b><div id="taskDecision"></div></div>',
|
|
7624
8554
|
'<div class="fact"><b>Proof / lesson</b><div id="taskProof"></div></div>',
|
|
7625
8555
|
'<div class="thread">' + (messages || '<div class="empty">No thread yet.</div>') + '</div>',
|
|
7626
8556
|
'<label>Add context</label><textarea id="note" placeholder="Decision, blocker, context, update..."></textarea>',
|
|
@@ -7632,9 +8562,17 @@ function taskBoardHtml() {
|
|
|
7632
8562
|
room.querySelector('h3').textContent = task.title;
|
|
7633
8563
|
$('taskGoal').textContent = task.objective || 'No matching goal yet.';
|
|
7634
8564
|
$('taskLineage').textContent = 'parent: ' + parent + ' / next: ' + children;
|
|
7635
|
-
|
|
7636
|
-
|
|
7637
|
-
:
|
|
8565
|
+
const result = task.review && task.review.result || {};
|
|
8566
|
+
const landing = task.review && task.review.landing || {
|
|
8567
|
+
happened: result.changed,
|
|
8568
|
+
checked: result.checked,
|
|
8569
|
+
tested: task.review && task.review.proof ? 'Proof is attached below.' : '',
|
|
8570
|
+
decision: result.accept,
|
|
8571
|
+
};
|
|
8572
|
+
$('taskHappened').textContent = landing.happened || (task.review && task.review.summary) || 'No review result yet.';
|
|
8573
|
+
$('taskChecked').textContent = landing.checked || 'No check yet.';
|
|
8574
|
+
$('taskTested').textContent = landing.tested || 'No test recorded yet.';
|
|
8575
|
+
$('taskDecision').textContent = landing.decision || 'No accept action yet.';
|
|
7638
8576
|
$('taskProof').textContent = task.review && (task.review.proof || task.review.lesson)
|
|
7639
8577
|
? ((task.review.proof || 'no proof') + ' / ' + (task.review.lesson || 'no lesson'))
|
|
7640
8578
|
: 'No proof yet.';
|
|
@@ -7975,22 +8913,39 @@ async function handleTaskApi(req, res, taskDb, db) {
|
|
|
7975
8913
|
});
|
|
7976
8914
|
}
|
|
7977
8915
|
if (op === 'plan') {
|
|
8916
|
+
const actor = String(body.actor || DEFAULT_OWNER);
|
|
8917
|
+
const goal = String(body.goal || body.objective || '');
|
|
8918
|
+
const summary = String(body.summary || body.plan || '');
|
|
8919
|
+
const owner = String(body.owner || body.assignee || '');
|
|
8920
|
+
const exit = String(body.exit || body.exit_condition || '');
|
|
8921
|
+
const firstMove = String(body.first_move || body.firstMove || body.first || '');
|
|
8922
|
+
const task = taskDetail(taskDb, db, taskId);
|
|
8923
|
+
const automaticPlan = buildAutomaticPlanTrace(taskDb, task, {
|
|
8924
|
+
actor,
|
|
8925
|
+
actorExplicit: Boolean(body.actor),
|
|
8926
|
+
owner,
|
|
8927
|
+
goal,
|
|
8928
|
+
summary,
|
|
8929
|
+
firstMove,
|
|
8930
|
+
exit,
|
|
8931
|
+
});
|
|
7978
8932
|
const result = taskDb.stageTask(db, {
|
|
7979
8933
|
id: taskId,
|
|
7980
|
-
actor
|
|
8934
|
+
actor,
|
|
7981
8935
|
stage: 'plan',
|
|
7982
|
-
goal
|
|
7983
|
-
summary
|
|
7984
|
-
owner:
|
|
7985
|
-
exit
|
|
8936
|
+
goal,
|
|
8937
|
+
summary,
|
|
8938
|
+
owner: automaticPlan.ownerForStage || owner,
|
|
8939
|
+
exit,
|
|
7986
8940
|
proofNeeded: String(body.proof_needed || body.proofNeeded || body.proof || body.verify || ''),
|
|
7987
|
-
firstMove
|
|
8941
|
+
firstMove,
|
|
7988
8942
|
nextButton: String(body.next_button || body.nextButton || ''),
|
|
7989
8943
|
confidence: body.confidence,
|
|
8944
|
+
planTrace: automaticPlan.trace,
|
|
7990
8945
|
});
|
|
7991
8946
|
if (!result.staged) return sendJson(res, 409, { ok: false, reason: result.reason, detail: stageErrorDetail('task plan', result.reason, result) });
|
|
7992
8947
|
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
7993
|
-
return sendJson(res, 200, { ok: true, action: 'planned', task_id: taskId, version: result.event.version, stage_packet: result.stage_packet, projection_path: outPath, task: taskFromProjection(projection, taskId) });
|
|
8948
|
+
return sendJson(res, 200, { ok: true, action: 'planned', task_id: taskId, version: result.event.version, plan_trace: automaticPlan.trace, stage_packet: result.stage_packet, projection_path: outPath, task: taskFromProjection(projection, taskId) });
|
|
7994
8949
|
}
|
|
7995
8950
|
if (op === 'do') {
|
|
7996
8951
|
const firstMove = String(body.first_move || body.firstMove || body.first || '').trim();
|
|
@@ -8097,12 +9052,34 @@ async function handleTaskApi(req, res, taskDb, db) {
|
|
|
8097
9052
|
const proofIssue = meaningfulTaskProofIssue(proof);
|
|
8098
9053
|
if (proofIssue) return sendProofIssue(res, proof, proofIssue);
|
|
8099
9054
|
const nextTaskInput = normalizeReviewNextTaskInput(body.next);
|
|
9055
|
+
const actor = String(body.actor || DEFAULT_OWNER);
|
|
9056
|
+
const resultTrace = buildAutomaticResultTrace(taskDb, db, taskId, {
|
|
9057
|
+
actor,
|
|
9058
|
+
proof,
|
|
9059
|
+
changed: body.changed || body.result || body.done,
|
|
9060
|
+
checked: body.checked || body.check || body.verified,
|
|
9061
|
+
passed: body.passed || body.pass,
|
|
9062
|
+
failed: body.failed || body.fail,
|
|
9063
|
+
cost: body.cost,
|
|
9064
|
+
saved: body.saved || body.savings,
|
|
9065
|
+
tryNext: body.try_next || body.tryNext || body.try || body.handoff,
|
|
9066
|
+
status: body.status,
|
|
9067
|
+
files: body.files,
|
|
9068
|
+
commands: body.commands || body.command,
|
|
9069
|
+
});
|
|
8100
9070
|
const result = taskDb.readyTask(db, {
|
|
8101
9071
|
id: taskId,
|
|
8102
|
-
actor
|
|
9072
|
+
actor,
|
|
8103
9073
|
proof,
|
|
8104
9074
|
lesson: String(body.lesson || ''),
|
|
8105
9075
|
nextTask: nextTaskInput.nextTask,
|
|
9076
|
+
resultTrace: resultTrace && resultTrace.trace,
|
|
9077
|
+
landing: body.landing || {
|
|
9078
|
+
happened: body.happened,
|
|
9079
|
+
checked: body.checked,
|
|
9080
|
+
tested: body.tested,
|
|
9081
|
+
decision: body.decision,
|
|
9082
|
+
},
|
|
8106
9083
|
});
|
|
8107
9084
|
if (!result.ready) return sendJson(res, 409, { ok: false, reason: result.reason });
|
|
8108
9085
|
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
@@ -8110,6 +9087,7 @@ async function handleTaskApi(req, res, taskDb, db) {
|
|
|
8110
9087
|
ok: true,
|
|
8111
9088
|
action: 'ready',
|
|
8112
9089
|
task_id: taskId,
|
|
9090
|
+
result_trace: resultTrace,
|
|
8113
9091
|
...(nextTaskInput.ignored ? { review_next_task_ignored: nextTaskInput.ignored } : {}),
|
|
8114
9092
|
projection_path: outPath,
|
|
8115
9093
|
task: taskFromProjection(projection, taskId),
|
|
@@ -8300,6 +9278,10 @@ async function run(args) {
|
|
|
8300
9278
|
case 'continue':
|
|
8301
9279
|
return cmdContinueWork(rest);
|
|
8302
9280
|
case 'chat': return cmdChat(rest);
|
|
9281
|
+
case 'plan-preview':
|
|
9282
|
+
case 'preview-plan':
|
|
9283
|
+
case 'plan-card':
|
|
9284
|
+
return cmdPlanPreview(rest);
|
|
8303
9285
|
case 'note': return cmdNote(rest);
|
|
8304
9286
|
case 'say': return cmdNote(rest);
|
|
8305
9287
|
case 'show': return cmdShow(rest);
|
|
@@ -8309,6 +9291,7 @@ async function run(args) {
|
|
|
8309
9291
|
case 'chat-review':
|
|
8310
9292
|
return cmdReviewChat(rest);
|
|
8311
9293
|
case 'ready': return cmdReady(rest);
|
|
9294
|
+
case 'result': return cmdResult(rest);
|
|
8312
9295
|
case 'accept': return cmdAccept(rest);
|
|
8313
9296
|
case 'auto-accept-certified':
|
|
8314
9297
|
case 'auto-accept':
|