atris 3.30.0 → 3.30.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/commands/task.js CHANGED
@@ -17,6 +17,7 @@ const DEFAULT_OWNER = process.env.ATRIS_AGENT_ID
17
17
  || os.userInfo().username
18
18
  || 'unknown';
19
19
  const AGENT_CERTIFICATION_REVIEW_PASSES = 2;
20
+ const RESULT_SAVED_TEXT_LIMIT = 200;
20
21
  const REVIEW_LANE_LOOP_DEFAULT_MAX_STEPS = 3;
21
22
  const REVIEW_LANE_LOOP_MAX_STEPS = 10;
22
23
  const REVIEW_LANE_RUN_DEFAULT_MAX_RUNS = 3;
@@ -97,8 +98,15 @@ atris task - durable local task state (SQLite, gitignored)
97
98
  atris task chat <id> "<message>" [--goal "..."] Refine a task chat + working goal
98
99
  atris task ready <id> --proof "..." Agent proof ready; native goal can complete
99
100
  atris task ready <id> --verify "<cmd>" Run <cmd>; only ready if it exits 0 (executed proof)
101
+ atris task plan-preview "<purpose>" [--tag <tag>] [--owner <member>] [--task <id>]
102
+ Show the plain Plan before work starts
103
+ atris task ready <id> --proof "..." [--changed "..." --checked "..." --saved "..." --try "..."]
104
+ Agent proof ready; records Result if needed
105
+ atris task result <id> --changed "..." --checked "..." [--saved "..."] [--try "..."]
106
+ Show the plain Result and store trace on the task
100
107
  atris task review-chat <id> [--as <owner>] Start a task-owned /codex verification chat
101
- atris task accept <id> [--proof "..."] Human accepts proof, marks done
108
+ atris task accept <id> [--proof "..."] [--public]
109
+ Human accepts proof, marks done; --public also publishes AgentXP
102
110
  atris task auto-accept-certified --dry-run [--strict-verify] [--limit <n>]
103
111
  Preview certified Review rows; live accept needs --confirm-human-accept --as <human>
104
112
  atris task revise <id> --note "..." Send reviewed work back to Do
@@ -5021,7 +5029,8 @@ function cmdPlan(args) {
5021
5029
  const pos = positional(args);
5022
5030
  const id = pos[0];
5023
5031
  if (!id) failTask('atris task plan', 'missing_id', 'id required');
5024
- const actor = String(flag(args, '--as') || DEFAULT_OWNER);
5032
+ const actorFlag = flag(args, '--as');
5033
+ const actor = String(actorFlag || DEFAULT_OWNER);
5025
5034
  const goal = textFlag(args, ['--goal', '--objective']);
5026
5035
  const exit = textFlag(args, ['--exit', '--exit-condition']);
5027
5036
  const proofNeeded = textFlag(args, ['--proof-needed', '--proof', '--verify']);
@@ -5033,18 +5042,29 @@ function cmdPlan(args) {
5033
5042
  const taskDb = getTaskDb();
5034
5043
  const db = taskDb.open();
5035
5044
  const taskId = requireTaskId(taskDb, db, id, 'atris task plan');
5045
+ const task = taskDetail(taskDb, db, taskId);
5046
+ const automaticPlan = buildAutomaticPlanTrace(taskDb, task, {
5047
+ actor,
5048
+ actorExplicit: typeof actorFlag === 'string' && Boolean(actorFlag.trim()),
5049
+ owner,
5050
+ goal,
5051
+ summary,
5052
+ firstMove,
5053
+ exit,
5054
+ });
5036
5055
  const result = taskDb.stageTask(db, {
5037
5056
  id: taskId,
5038
5057
  actor,
5039
5058
  stage: 'plan',
5040
5059
  goal,
5041
5060
  summary,
5042
- owner,
5061
+ owner: automaticPlan.ownerForStage || owner,
5043
5062
  exit,
5044
5063
  proofNeeded,
5045
5064
  firstMove,
5046
5065
  nextButton,
5047
5066
  confidence,
5067
+ planTrace: automaticPlan.trace,
5048
5068
  });
5049
5069
  if (!result.staged) {
5050
5070
  failTask('atris task plan', result.reason || 'stage_failed', stageErrorDetail('atris task plan', result.reason, result), 1);
@@ -5056,6 +5076,11 @@ function cmdPlan(args) {
5056
5076
  action: 'planned',
5057
5077
  task_id: taskId,
5058
5078
  version: result.event.version,
5079
+ plan_trace: automaticPlan.trace ? {
5080
+ plan: automaticPlan.plan,
5081
+ owner_choice: automaticPlan.ownerChoice,
5082
+ trace: automaticPlan.trace,
5083
+ } : null,
5059
5084
  stage_packet: result.stage_packet,
5060
5085
  projection_path: outPath,
5061
5086
  task: compactTaskFromProjection(projection, taskId),
@@ -5320,6 +5345,611 @@ function taskCommandQuote(value) {
5320
5345
  return `"${text || '...'}"`;
5321
5346
  }
5322
5347
 
5348
+ function cleanPublicText(value, max = 500) {
5349
+ const text = String(value || '').replace(/\s+/g, ' ').trim();
5350
+ if (!text) return '';
5351
+ return text.length > max ? `${text.slice(0, Math.max(0, max - 3)).trim()}...` : text;
5352
+ }
5353
+
5354
+ function publicWords(value) {
5355
+ return (String(value || '').toLowerCase().match(/[a-z0-9]{3,}/g) || [])
5356
+ .map(word => word.endsWith('s') && word.length > 4 ? word.slice(0, -1) : word)
5357
+ .filter(word => !new Set([
5358
+ 'and',
5359
+ 'for',
5360
+ 'from',
5361
+ 'into',
5362
+ 'the',
5363
+ 'this',
5364
+ 'that',
5365
+ 'task',
5366
+ 'work',
5367
+ 'with',
5368
+ ]).has(word));
5369
+ }
5370
+
5371
+ function parseMemberFrontmatter(text) {
5372
+ const source = String(text || '');
5373
+ if (!source.startsWith('---')) return {};
5374
+ const end = source.indexOf('\n---', 3);
5375
+ if (end === -1) return {};
5376
+ const block = source.slice(3, end).split(/\r?\n/);
5377
+ const data = {};
5378
+ for (const raw of block) {
5379
+ const match = raw.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
5380
+ if (!match) continue;
5381
+ data[match[1]] = match[2].replace(/^["']|["']$/g, '').trim();
5382
+ }
5383
+ return data;
5384
+ }
5385
+
5386
+ function readTeamMembers(root = process.cwd()) {
5387
+ const teamDir = path.join(root, 'atris', 'team');
5388
+ if (!fs.existsSync(teamDir)) return [];
5389
+ return fs.readdirSync(teamDir, { withFileTypes: true })
5390
+ .filter(entry => entry.isDirectory())
5391
+ .map(entry => {
5392
+ const slug = entry.name;
5393
+ const memberPath = path.join(teamDir, slug, 'MEMBER.md');
5394
+ if (!fs.existsSync(memberPath)) return null;
5395
+ const text = fs.readFileSync(memberPath, 'utf8');
5396
+ const frontmatter = parseMemberFrontmatter(text);
5397
+ return {
5398
+ slug,
5399
+ role: cleanPublicText(frontmatter.role || slug.replace(/[-_]/g, ' '), 120),
5400
+ description: cleanPublicText(frontmatter.description || '', 240),
5401
+ path: memberPath,
5402
+ };
5403
+ })
5404
+ .filter(Boolean);
5405
+ }
5406
+
5407
+ const GENERIC_MEMBER_SLUGS = new Set([
5408
+ '_template',
5409
+ 'coordinator',
5410
+ 'executor',
5411
+ 'generalist',
5412
+ 'navigator',
5413
+ 'supervisor',
5414
+ ]);
5415
+
5416
+ function scoreTeamMember(member, words, tag) {
5417
+ const slug = String(member.slug || '').toLowerCase();
5418
+ const role = String(member.role || '').toLowerCase();
5419
+ const description = String(member.description || '').toLowerCase();
5420
+ const haystack = `${slug} ${role} ${description}`.replace(/[-_]/g, ' ');
5421
+ let score = 0;
5422
+ const cleanTag = String(tag || '').toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim();
5423
+ if (cleanTag) {
5424
+ if (slug === cleanTag || slug.replace(/[-_]/g, ' ') === cleanTag) score += 12;
5425
+ else if (haystack.includes(cleanTag)) score += 5;
5426
+ }
5427
+ for (const word of words) {
5428
+ if (slug.includes(word)) score += 5;
5429
+ else if (role.includes(word)) score += 3;
5430
+ else if (description.includes(word)) score += 2;
5431
+ }
5432
+ if (GENERIC_MEMBER_SLUGS.has(slug)) score -= 3;
5433
+ return score;
5434
+ }
5435
+
5436
+ function plainMemberDescription(member) {
5437
+ const description = cleanPublicText(member && member.description, 220);
5438
+ if (!description) return '';
5439
+ const lowered = `${description.charAt(0).toLowerCase()}${description.slice(1)}`;
5440
+ return /[.!?]$/.test(lowered) ? lowered : `${lowered}.`;
5441
+ }
5442
+
5443
+ function chooseTaskOwner({ purpose, tag, requestedOwner, root = process.cwd() } = {}) {
5444
+ const members = readTeamMembers(root);
5445
+ const requested = cleanPublicText(requestedOwner, 80);
5446
+ if (requested) {
5447
+ const match = members.find(member => member.slug === requested);
5448
+ if (match) {
5449
+ const description = plainMemberDescription(match);
5450
+ return {
5451
+ owner: match.slug,
5452
+ member: match,
5453
+ source: 'requested',
5454
+ reason: `${match.slug} matches this work${description ? ` because ${description}` : '.'}`,
5455
+ };
5456
+ }
5457
+ return {
5458
+ owner: requested,
5459
+ member: null,
5460
+ source: 'requested',
5461
+ reason: `${requested} was requested, but no matching atris/team member was found.`,
5462
+ };
5463
+ }
5464
+ const words = publicWords(`${purpose || ''} ${tag || ''}`);
5465
+ let best = null;
5466
+ for (const member of members) {
5467
+ const score = scoreTeamMember(member, words, tag);
5468
+ if (!best || score > best.score) best = { member, score };
5469
+ }
5470
+ if (best && best.score > 0) {
5471
+ const member = best.member;
5472
+ const description = plainMemberDescription(member);
5473
+ return {
5474
+ owner: member.slug,
5475
+ member,
5476
+ source: 'team',
5477
+ score: best.score,
5478
+ reason: `${member.slug} fits this work${description ? ` because ${description}` : '.'}`,
5479
+ };
5480
+ }
5481
+ return {
5482
+ owner: DEFAULT_OWNER,
5483
+ member: null,
5484
+ source: 'fallback',
5485
+ reason: `${DEFAULT_OWNER} is handling it because no specific atris/team owner matched this work.`,
5486
+ };
5487
+ }
5488
+
5489
+ function isGenericPlanActor(value) {
5490
+ const actor = cleanPublicText(value, 80).toLowerCase();
5491
+ if (!actor) return true;
5492
+ if (actor === String(DEFAULT_OWNER || '').toLowerCase()) return true;
5493
+ return GENERIC_MEMBER_SLUGS.has(actor) || new Set([
5494
+ 'codex',
5495
+ 'codex-executor',
5496
+ 'claude',
5497
+ 'claude-code',
5498
+ 'cursor',
5499
+ 'devin',
5500
+ ]).has(actor);
5501
+ }
5502
+
5503
+ function taskTextMentionsActor(actor, text) {
5504
+ const actorWords = publicWords(actor);
5505
+ if (!actorWords.length) return false;
5506
+ const words = new Set(publicWords(text));
5507
+ return actorWords.some(word => words.has(word));
5508
+ }
5509
+
5510
+ function buildPublicPlan({ purpose, owner, ownerReason, plan, expected }) {
5511
+ const cleanPurpose = cleanPublicText(purpose, 240);
5512
+ const cleanOwner = cleanPublicText(owner, 80);
5513
+ const cleanReason = cleanPublicText(ownerReason, 240);
5514
+ const cleanPlan = cleanPublicText(plan, 320) || `${cleanOwner || 'The owner'} will make the smallest needed change, then check the result.`;
5515
+ const cleanExpected = cleanPublicText(expected, 240) || 'the check passes and the result is ready to review.';
5516
+ return {
5517
+ purpose: cleanPurpose,
5518
+ owner: cleanOwner,
5519
+ owner_reason: cleanReason,
5520
+ plan: cleanPlan,
5521
+ expected_result: cleanExpected,
5522
+ };
5523
+ }
5524
+
5525
+ function renderPublicPlan(plan) {
5526
+ const lines = [];
5527
+ if (plan.purpose) lines.push(`Purpose: ${plan.purpose}`);
5528
+ if (plan.owner) lines.push(`Owner: ${plan.owner} is handling it.`);
5529
+ if (plan.owner_reason) lines.push(`Why: ${plan.owner_reason}`);
5530
+ if (plan.plan) lines.push(`Plan: ${plan.plan}`);
5531
+ if (plan.expected_result) lines.push(`Expected result: ${plan.expected_result}`);
5532
+ return lines.join('\n');
5533
+ }
5534
+
5535
+ function planTraceData(plan, ownerChoice) {
5536
+ return {
5537
+ schema: 'atris.task_plan_trace.v1',
5538
+ purpose: plan.purpose,
5539
+ owner: plan.owner,
5540
+ owner_reason: plan.owner_reason,
5541
+ plan: plan.plan,
5542
+ expected_result: plan.expected_result,
5543
+ owner_source: ownerChoice && ownerChoice.source || null,
5544
+ owner_score: ownerChoice && ownerChoice.score || null,
5545
+ recorded_at: new Date().toISOString(),
5546
+ };
5547
+ }
5548
+
5549
+ function planTraceNote(plan, ownerChoice) {
5550
+ return `TASK_PLAN_TRACE ${JSON.stringify(planTraceData(plan, ownerChoice))}`;
5551
+ }
5552
+
5553
+ function traceLineFromContent(content, prefix) {
5554
+ const lines = String(content || '').split(/\r?\n/);
5555
+ return lines.find(line => line.startsWith(prefix)) || '';
5556
+ }
5557
+
5558
+ function latestTraceValue(task, prefix, key) {
5559
+ const messages = Array.isArray(task && task.messages) ? task.messages : [];
5560
+ for (let i = messages.length - 1; i >= 0; i--) {
5561
+ const content = String(messages[i] && messages[i].content || '');
5562
+ const line = traceLineFromContent(content, prefix);
5563
+ if (!line) continue;
5564
+ try {
5565
+ const parsed = JSON.parse(line.slice(prefix.length).trim());
5566
+ const value = parsed && parsed[key];
5567
+ if (value !== undefined && value !== null && String(value).trim()) return String(value);
5568
+ } catch (_) {
5569
+ continue;
5570
+ }
5571
+ }
5572
+ return '';
5573
+ }
5574
+
5575
+ function taskHasTrace(task, prefix) {
5576
+ const messages = Array.isArray(task && task.messages) ? task.messages : [];
5577
+ if (messages.some(message => traceLineFromContent(message && message.content, prefix))) return true;
5578
+ const events = Array.isArray(task && task.events) ? task.events : [];
5579
+ return events.some(event => {
5580
+ const payload = event && event.payload && typeof event.payload === 'object' ? event.payload : {};
5581
+ if (traceLineFromContent(payload.stage_packet, prefix)) return true;
5582
+ if (traceLineFromContent(payload.result_packet, prefix)) return true;
5583
+ if (prefix === 'TASK_RESULT_TRACE ' && payload.result_trace && typeof payload.result_trace === 'object') return true;
5584
+ if (prefix === 'TASK_PLAN_TRACE ' && payload.plan_trace && typeof payload.plan_trace === 'object') return true;
5585
+ return false;
5586
+ });
5587
+ }
5588
+
5589
+ function taskPurpose(task) {
5590
+ const metadata = task && task.metadata || {};
5591
+ return cleanPublicText(
5592
+ metadata.task_goal
5593
+ || metadata.goal_objective
5594
+ || metadata.objective
5595
+ || metadata.stage_goal
5596
+ || latestTraceValue(task, 'TASK_PLAN_TRACE ', 'purpose')
5597
+ || task && task.title
5598
+ || '',
5599
+ 240,
5600
+ );
5601
+ }
5602
+
5603
+ function buildPublicResult(task, fields) {
5604
+ const owner = cleanPublicText(
5605
+ fields.owner
5606
+ || latestTraceValue(task, 'TASK_PLAN_TRACE ', 'owner')
5607
+ || taskAssignee(task)
5608
+ || task && task.claimed_by
5609
+ || fields.actor,
5610
+ 80,
5611
+ );
5612
+ const result = {
5613
+ purpose: cleanPublicText(fields.purpose || taskPurpose(task), 240),
5614
+ owner,
5615
+ changed: cleanPublicText(fields.changed, 320),
5616
+ checked: cleanPublicText(fields.checked, 320),
5617
+ passed: cleanPublicText(fields.passed, 240),
5618
+ failed: cleanPublicText(fields.failed, 240),
5619
+ cost: cleanPublicText(fields.cost, 80),
5620
+ saved: cleanPublicText(fields.saved, RESULT_SAVED_TEXT_LIMIT),
5621
+ try_next: cleanPublicText(fields.tryNext, 240),
5622
+ status: cleanPublicText(fields.status, 160) || 'ready for review',
5623
+ };
5624
+ return result;
5625
+ }
5626
+
5627
+ function renderPublicResult(result) {
5628
+ return [
5629
+ `Changed: ${cleanPublicText(result && result.changed, 320) || 'changed the requested work'}`,
5630
+ `Checked: ${cleanPublicText(result && result.checked, 320) || 'checked the result'}`,
5631
+ `Try: ${cleanPublicText(result && result.try_next, 240) || 'try the changed work'}`,
5632
+ ].join('\n');
5633
+ }
5634
+
5635
+ function latestResultTrace(task) {
5636
+ const messages = Array.isArray(task && task.messages) ? task.messages : [];
5637
+ for (let i = messages.length - 1; i >= 0; i--) {
5638
+ const content = String(messages[i] && messages[i].content || '');
5639
+ const line = traceLineFromContent(content, 'TASK_RESULT_TRACE ');
5640
+ if (!line) continue;
5641
+ try {
5642
+ const parsed = JSON.parse(line.slice('TASK_RESULT_TRACE '.length).trim());
5643
+ if (parsed && typeof parsed === 'object') return parsed;
5644
+ } catch (_) {}
5645
+ }
5646
+
5647
+ const events = Array.isArray(task && task.events) ? task.events : [];
5648
+ for (let i = events.length - 1; i >= 0; i--) {
5649
+ const payload = events[i] && events[i].payload && typeof events[i].payload === 'object'
5650
+ ? events[i].payload
5651
+ : {};
5652
+ if (payload.result_trace && typeof payload.result_trace === 'object') return payload.result_trace;
5653
+ const packetLine = traceLineFromContent(payload.result_packet, 'TASK_RESULT_TRACE ');
5654
+ if (!packetLine) continue;
5655
+ try {
5656
+ const parsed = JSON.parse(packetLine.slice('TASK_RESULT_TRACE '.length).trim());
5657
+ if (parsed && typeof parsed === 'object') return parsed;
5658
+ } catch (_) {}
5659
+ }
5660
+ return null;
5661
+ }
5662
+
5663
+ function buildAcceptHumanResult({ task, proof, nextTask, publicSync }) {
5664
+ const trace = latestResultTrace(task) || {};
5665
+ const traceChanged = cleanPublicText(trace.changed, 320);
5666
+ const changed = traceChanged === 'prepared the work for review'
5667
+ ? 'accepted the completed work'
5668
+ : traceChanged
5669
+ || cleanPublicText(task && task.title, 260)
5670
+ || 'accepted the completed work';
5671
+ let checked = cleanPublicText(trace.checked, 320)
5672
+ || cleanPublicText(proof, 320)
5673
+ || 'checked the proof';
5674
+ if (publicSync && publicSync.enabled === true && !publicSync.ok) {
5675
+ const error = cleanPublicText(publicSync.error || 'publish failed', 140);
5676
+ checked = `${checked}; AgentXP publish failed${error ? ` (${error})` : ''}`;
5677
+ }
5678
+ const tryNext = cleanPublicText(nextTask, 240)
5679
+ || cleanPublicText(trace.try_next, 240)
5680
+ || 'try the changed work';
5681
+ return { changed, checked, try_next: tryNext };
5682
+ }
5683
+
5684
+ function renderAcceptLanding({ task, proof, nextTask, publicSync }) {
5685
+ return renderPublicResult(buildAcceptHumanResult({
5686
+ task,
5687
+ proof,
5688
+ nextTask,
5689
+ publicSync,
5690
+ }));
5691
+ }
5692
+
5693
+ async function publishAcceptAgentXp(args, actor) {
5694
+ const token = flag(args, '--token');
5695
+ const syncArgs = ['--all', '--root', process.cwd(), '--public', '--as', actor];
5696
+ if (typeof token === 'string' && token.trim()) syncArgs.push('--token', token.trim());
5697
+ try {
5698
+ const { syncAgentXp } = require('../commands/xp');
5699
+ const result = await syncAgentXp(syncArgs);
5700
+ const server = result && result.server ? result.server : {};
5701
+ const publicCount = Number(server.public_accepted_count);
5702
+ const acceptedCount = Number(server.accepted_count);
5703
+ const published = (
5704
+ (Number.isFinite(publicCount) && publicCount > 0)
5705
+ || (Number.isFinite(acceptedCount) && acceptedCount > 0 && server.private_agentxp !== true)
5706
+ );
5707
+ return {
5708
+ enabled: true,
5709
+ ok: published,
5710
+ result,
5711
+ error: published ? null : 'server accepted no public AgentXP rows',
5712
+ };
5713
+ } catch (error) {
5714
+ return {
5715
+ enabled: true,
5716
+ ok: false,
5717
+ error: error && error.message ? error.message : String(error),
5718
+ };
5719
+ }
5720
+ }
5721
+
5722
+ function todayResultLogName() {
5723
+ const now = new Date();
5724
+ return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}.md`;
5725
+ }
5726
+
5727
+ function appendResultOwnerLog(root, task, result) {
5728
+ const owner = cleanPublicText(result && result.owner, 80);
5729
+ if (!owner || !/^[A-Za-z0-9._-]+$/.test(owner)) return null;
5730
+ const memberFile = path.join(root, 'atris', 'team', owner, 'MEMBER.md');
5731
+ if (!fs.existsSync(memberFile)) return null;
5732
+ const logName = todayResultLogName();
5733
+ const logDir = path.join(root, 'atris', 'team', owner, 'logs');
5734
+ fs.mkdirSync(logDir, { recursive: true });
5735
+ const logPath = path.join(logDir, logName);
5736
+ const stamp = new Date().toTimeString().slice(0, 5);
5737
+ const lines = [
5738
+ `## ${stamp} - Result`,
5739
+ `- task: ${taskRef(task)}`,
5740
+ `- purpose: ${result.purpose || ''}`,
5741
+ `- result: ${result.changed || ''}`,
5742
+ `- checked: ${result.checked || ''}`,
5743
+ `- saved: ${result.saved || ''}`,
5744
+ `- try: ${result.try_next || ''}`,
5745
+ `- status: ${result.status || ''}`,
5746
+ '',
5747
+ ];
5748
+ fs.appendFileSync(logPath, lines.join('\n'), 'utf8');
5749
+ return {
5750
+ member_log_path: path.relative(root, logPath),
5751
+ };
5752
+ }
5753
+
5754
+ function resultTraceData(result, fields) {
5755
+ return {
5756
+ schema: 'atris.task_result_trace.v1',
5757
+ purpose: result.purpose,
5758
+ owner: result.owner || null,
5759
+ changed: result.changed,
5760
+ checked: result.checked,
5761
+ passed: result.passed || null,
5762
+ failed: result.failed || null,
5763
+ cost: result.cost || null,
5764
+ saved: result.saved || null,
5765
+ try_next: result.try_next || null,
5766
+ status: result.status,
5767
+ files: cleanPublicText(fields.files, 500) || null,
5768
+ commands: cleanPublicText(fields.commands, 500) || null,
5769
+ member_log_path: fields.savedPaths && fields.savedPaths.member_log_path || null,
5770
+ recorded_at: new Date().toISOString(),
5771
+ };
5772
+ }
5773
+
5774
+ function resultTraceNote(result, fields) {
5775
+ return `TASK_RESULT_TRACE ${JSON.stringify(resultTraceData(result, fields))}`;
5776
+ }
5777
+
5778
+ function cmdPlanPreview(args) {
5779
+ const pos = positional(args);
5780
+ const purpose = cleanPublicText(textFlag(args, ['--purpose', '--goal', '--objective']) || pos.join(' '), 240);
5781
+ if (!purpose) failTask('atris task plan-preview', 'missing_purpose', 'purpose required');
5782
+ const tag = textFlag(args, ['--tag']);
5783
+ const requestedOwner = textFlag(args, ['--owner', '--as', '--member']);
5784
+ const planText = textFlag(args, ['--plan', '--action', '--first-move']);
5785
+ const expected = textFlag(args, ['--expected', '--expected-result', '--exit']);
5786
+ const recordRef = textFlag(args, ['--task', '--record']);
5787
+ const taskDb = getTaskDb();
5788
+ const db = taskDb.open();
5789
+ const ownerChoice = chooseTaskOwner({
5790
+ purpose,
5791
+ tag,
5792
+ requestedOwner,
5793
+ root: taskDb.workspaceRoot(),
5794
+ });
5795
+ const publicPlan = buildPublicPlan({
5796
+ purpose,
5797
+ owner: ownerChoice.owner,
5798
+ ownerReason: ownerChoice.reason,
5799
+ plan: planText,
5800
+ expected,
5801
+ });
5802
+ let recorded = null;
5803
+ if (recordRef) {
5804
+ const taskId = requireTaskId(taskDb, db, recordRef, 'atris task plan-preview');
5805
+ const note = taskDb.noteTask(db, {
5806
+ id: taskId,
5807
+ actor: publicPlan.owner || DEFAULT_OWNER,
5808
+ content: planTraceNote(publicPlan, ownerChoice),
5809
+ });
5810
+ if (!note.noted) failTask('atris task plan-preview', note.reason || 'note_failed', `plan-preview failed: ${note.reason || 'note_failed'}`, 1);
5811
+ const { outPath } = writeDefaultProjection(taskDb, db);
5812
+ recorded = {
5813
+ task_id: taskId,
5814
+ version: note.event.version,
5815
+ projection_path: outPath,
5816
+ };
5817
+ }
5818
+ if (wantsJson(args)) {
5819
+ printJson({
5820
+ ok: true,
5821
+ action: 'plan_preview',
5822
+ plan: publicPlan,
5823
+ owner_choice: {
5824
+ owner: ownerChoice.owner,
5825
+ source: ownerChoice.source,
5826
+ score: ownerChoice.score || null,
5827
+ member: ownerChoice.member ? {
5828
+ slug: ownerChoice.member.slug,
5829
+ role: ownerChoice.member.role,
5830
+ description: ownerChoice.member.description,
5831
+ } : null,
5832
+ },
5833
+ recorded,
5834
+ text: renderPublicPlan(publicPlan),
5835
+ });
5836
+ return;
5837
+ }
5838
+ console.log(renderPublicPlan(publicPlan));
5839
+ }
5840
+
5841
+ function buildAutomaticPlanTrace(taskDb, task, { actor, actorExplicit = false, owner, goal, summary, firstMove, exit } = {}) {
5842
+ if (!task) return { trace: null, plan: null, ownerChoice: null, ownerForStage: owner || null };
5843
+ const metadata = task.metadata || {};
5844
+ const purpose = cleanPublicText(goal, 240) || taskPurpose(task);
5845
+ const claimedOwner = cleanPublicText(task.claimed_by, 80);
5846
+ const actorNamed = taskTextMentionsActor(actor, `${purpose} ${task.title || ''} ${task.tag || ''}`);
5847
+ const requestedActor = actorExplicit && (!isGenericPlanActor(actor) || actorNamed) ? actor : null;
5848
+ const requestedOwner = owner || claimedOwner || requestedActor || null;
5849
+ const ownerChoice = chooseTaskOwner({
5850
+ purpose,
5851
+ tag: task.tag,
5852
+ requestedOwner,
5853
+ root: taskDb.workspaceRoot(),
5854
+ });
5855
+ const publicPlan = buildPublicPlan({
5856
+ purpose,
5857
+ owner: ownerChoice.owner,
5858
+ ownerReason: ownerChoice.reason,
5859
+ plan: firstMove || summary || metadata.first_move || metadata.stage_summary || '',
5860
+ expected: exit || metadata.exit_condition || '',
5861
+ });
5862
+ return {
5863
+ trace: planTraceData(publicPlan, ownerChoice),
5864
+ plan: publicPlan,
5865
+ ownerChoice: {
5866
+ owner: ownerChoice.owner,
5867
+ source: ownerChoice.source,
5868
+ score: ownerChoice.score || null,
5869
+ },
5870
+ ownerForStage: publicPlan.owner || actor || DEFAULT_OWNER,
5871
+ };
5872
+ }
5873
+
5874
+ function buildAutomaticResultTrace(taskDb, db, taskId, { actor, proof, changed, checked, passed, failed, cost, saved, tryNext, status, files, commands } = {}) {
5875
+ const task = taskDetail(taskDb, db, taskId);
5876
+ if (!task || taskHasTrace(task, 'TASK_RESULT_TRACE ')) return null;
5877
+ const fields = {
5878
+ actor,
5879
+ changed: cleanPublicText(changed, 320) || 'prepared the work for review',
5880
+ checked: cleanPublicText(checked, 320) || cleanPublicText(proof, 320),
5881
+ passed: cleanPublicText(passed, 240),
5882
+ failed: cleanPublicText(failed, 240),
5883
+ cost: cleanPublicText(cost, 80),
5884
+ saved: cleanPublicText(saved, RESULT_SAVED_TEXT_LIMIT) || 'task trace was updated',
5885
+ tryNext: cleanPublicText(tryNext, 240) || 'review the proof and try the changed work',
5886
+ status: cleanPublicText(status, 160) || 'ready for review',
5887
+ files,
5888
+ commands,
5889
+ };
5890
+ const result = buildPublicResult(task, fields);
5891
+ const savedPaths = appendResultOwnerLog(taskDb.workspaceRoot(), task, result);
5892
+ const trace = resultTraceData(result, { ...fields, savedPaths });
5893
+ return {
5894
+ result,
5895
+ trace,
5896
+ saved_paths: savedPaths,
5897
+ };
5898
+ }
5899
+
5900
+ function cmdResult(args) {
5901
+ const pos = positional(args);
5902
+ const id = pos[0];
5903
+ if (!id) failTask('atris task result', 'missing_id', 'id required');
5904
+ const fields = {
5905
+ purpose: textFlag(args, ['--purpose', '--goal', '--objective']),
5906
+ changed: textFlag(args, ['--changed', '--result', '--done']),
5907
+ checked: textFlag(args, ['--checked', '--check', '--verified']),
5908
+ passed: textFlag(args, ['--passed', '--pass']),
5909
+ failed: textFlag(args, ['--failed', '--fail']),
5910
+ cost: textFlag(args, ['--cost']),
5911
+ saved: textFlag(args, ['--saved', '--savings']),
5912
+ tryNext: textFlag(args, ['--try', '--try-next', '--handoff']),
5913
+ status: textFlag(args, ['--status']),
5914
+ files: textFlag(args, ['--files']),
5915
+ commands: textFlag(args, ['--commands', '--command']),
5916
+ };
5917
+ if (!fields.changed) failTask('atris task result', 'changed_required', '--changed required');
5918
+ if (!fields.checked) failTask('atris task result', 'checked_required', '--checked required');
5919
+ if (!fields.tryNext) failTask('atris task result', 'try_required', '--try required');
5920
+ const actor = String(flag(args, '--as') || DEFAULT_OWNER);
5921
+ const taskDb = getTaskDb();
5922
+ const db = taskDb.open();
5923
+ const taskId = requireTaskId(taskDb, db, id, 'atris task result');
5924
+ const task = taskDetail(taskDb, db, taskId);
5925
+ if (!task) failTask('atris task result', 'not_found', `task not found: ${id}`, 1);
5926
+ const result = buildPublicResult(task, { ...fields, actor });
5927
+ const savedPaths = appendResultOwnerLog(taskDb.workspaceRoot(), task, result);
5928
+ const note = taskDb.noteTask(db, {
5929
+ id: taskId,
5930
+ actor,
5931
+ content: resultTraceNote(result, { ...fields, savedPaths }),
5932
+ });
5933
+ if (!note.noted) failTask('atris task result', note.reason || 'note_failed', `result failed: ${note.reason || 'note_failed'}`, 1);
5934
+ const { projection, outPath } = writeDefaultProjection(taskDb, db);
5935
+ const text = renderPublicResult(result);
5936
+ if (wantsJson(args)) {
5937
+ printJson({
5938
+ ok: true,
5939
+ action: 'result',
5940
+ task_id: taskId,
5941
+ version: note.event.version,
5942
+ projection_path: outPath,
5943
+ result,
5944
+ saved_paths: savedPaths,
5945
+ text,
5946
+ task: compactTaskFromProjection(projection, taskId),
5947
+ });
5948
+ return;
5949
+ }
5950
+ console.log(text);
5951
+ }
5952
+
5323
5953
  function taskPageGoal(task) {
5324
5954
  const metadata = task && task.metadata || {};
5325
5955
  const candidates = [
@@ -5764,16 +6394,25 @@ function runTaskStep(taskDb, db, taskId, options = {}) {
5764
6394
  let episode = null;
5765
6395
  let xpProjection = null;
5766
6396
  if (current === 'backlog') {
6397
+ const automaticPlan = buildAutomaticPlanTrace(taskDb, task, {
6398
+ actor,
6399
+ owner: actor,
6400
+ goal,
6401
+ summary,
6402
+ firstMove: String(options.firstMove || ''),
6403
+ exit: String(options.exit || ''),
6404
+ });
5767
6405
  const planned = taskDb.stageTask(db, {
5768
6406
  id: taskId,
5769
6407
  actor,
5770
6408
  stage: 'plan',
5771
6409
  goal,
5772
6410
  summary,
5773
- owner: actor,
6411
+ owner: automaticPlan.ownerForStage || actor,
5774
6412
  exit: String(options.exit || ''),
5775
6413
  proofNeeded: String(options.proofNeeded || ''),
5776
6414
  firstMove: String(options.firstMove || ''),
6415
+ planTrace: automaticPlan.trace,
5777
6416
  });
5778
6417
  if (!planned.staged) taskStepFailure('atris task step', planned, actionPage);
5779
6418
  stepAction = 'planned';
@@ -5807,7 +6446,15 @@ function runTaskStep(taskDb, db, taskId, options = {}) {
5807
6446
  }
5808
6447
  const lesson = String(options.lesson || '');
5809
6448
  const nextTask = String(options.nextTask || '');
5810
- const ready = taskDb.readyTask(db, { id: taskId, actor, proof, lesson, nextTask });
6449
+ const resultTrace = buildAutomaticResultTrace(taskDb, db, taskId, { actor, proof });
6450
+ const ready = taskDb.readyTask(db, {
6451
+ id: taskId,
6452
+ actor,
6453
+ proof,
6454
+ lesson,
6455
+ nextTask,
6456
+ resultTrace: resultTrace && resultTrace.trace,
6457
+ });
5811
6458
  if (!ready.ready) taskStepFailure('atris task step', ready, actionPage);
5812
6459
  task = taskDetail(taskDb, db, taskId) || task;
5813
6460
  stepAction = 'ready';
@@ -6343,15 +6990,33 @@ function cmdReady(args) {
6343
6990
  const lesson = flag(args, '--lesson') || '';
6344
6991
  const nextTaskInput = normalizeReviewNextTaskInput(typeof flag(args, '--next') === 'string' ? flag(args, '--next') : '');
6345
6992
  const actor = String(flag(args, '--as') || DEFAULT_OWNER);
6993
+ const resultFields = {
6994
+ changed: textFlag(args, ['--changed', '--result', '--done']),
6995
+ checked: textFlag(args, ['--checked', '--check', '--verified']),
6996
+ passed: textFlag(args, ['--passed', '--pass']),
6997
+ failed: textFlag(args, ['--failed', '--fail']),
6998
+ cost: textFlag(args, ['--cost']),
6999
+ saved: textFlag(args, ['--saved', '--savings']),
7000
+ tryNext: textFlag(args, ['--try', '--try-next', '--handoff']),
7001
+ status: textFlag(args, ['--status']),
7002
+ files: textFlag(args, ['--files']),
7003
+ commands: textFlag(args, ['--commands', '--command']),
7004
+ };
6346
7005
  const taskDb = getTaskDb();
6347
7006
  const db = taskDb.open();
6348
7007
  const taskId = requireTaskId(taskDb, db, id, 'atris task ready');
7008
+ const resultTrace = buildAutomaticResultTrace(taskDb, db, taskId, {
7009
+ actor,
7010
+ proof: String(proof),
7011
+ ...resultFields,
7012
+ });
6349
7013
  const result = taskDb.readyTask(db, {
6350
7014
  id: taskId,
6351
7015
  actor,
6352
7016
  proof: String(proof),
6353
7017
  lesson: typeof lesson === 'string' ? lesson : '',
6354
7018
  nextTask: nextTaskInput.nextTask,
7019
+ resultTrace: resultTrace && resultTrace.trace,
6355
7020
  });
6356
7021
  if (!result.ready) {
6357
7022
  console.error(`ready failed: ${result.reason}`);
@@ -6397,6 +7062,7 @@ function cmdReady(args) {
6397
7062
  review_pass_count: result.event.payload.review_pass_count,
6398
7063
  agent_certified: agentCertified,
6399
7064
  handoff,
7065
+ result_trace: resultTrace,
6400
7066
  ...(nextTaskInput.ignored ? { review_next_task_ignored: nextTaskInput.ignored } : {}),
6401
7067
  projection_path: outPath,
6402
7068
  task: compactTaskFromProjection(projection, taskId),
@@ -6404,13 +7070,14 @@ function cmdReady(args) {
6404
7070
  return;
6405
7071
  }
6406
7072
  console.log(`ready ${taskRef(compactTaskFromProjection(projection, taskId))} v${result.event.version} pending approval`);
7073
+ if (resultTrace) console.log('Result trace recorded.');
6407
7074
  console.log(handoff.rule);
6408
7075
  for (const hint of policyHints) {
6409
7076
  console.log(`policy (${hint.id}): ${hint.hint}`);
6410
7077
  }
6411
7078
  }
6412
7079
 
6413
- function cmdAccept(args) {
7080
+ async function cmdAccept(args) {
6414
7081
  const pos = positional(args);
6415
7082
  const id = pos[0];
6416
7083
  if (!id) {
@@ -6490,6 +7157,7 @@ function cmdAccept(args) {
6490
7157
  // Inform the gate, never block it: show what the receipts named in the proof
6491
7158
  // actually say so the accepting human isn't trusting prose.
6492
7159
  const evidence = extractReceiptEvidence(proof, projection.workspace_root || process.cwd());
7160
+ const publicSync = hasFlag(args, '--public') ? await publishAcceptAgentXp(args, actor) : null;
6493
7161
  if (wantsJson(args)) {
6494
7162
  printJson({
6495
7163
  ok: true,
@@ -6499,21 +7167,21 @@ function cmdAccept(args) {
6499
7167
  reward: reviewed.episode.reward.value,
6500
7168
  episode: reviewed.episode,
6501
7169
  evidence,
7170
+ public_sync: publicSync,
6502
7171
  xp_projection: xpProjection,
6503
7172
  projection_path: outPath,
6504
7173
  task: compactTaskFromProjection(projection, taskId),
6505
7174
  });
7175
+ if (publicSync && !publicSync.ok) process.exitCode = 1;
6506
7176
  return;
6507
7177
  }
6508
- console.log(`accepted ${taskRef(compactTaskFromProjection(projection, taskId))} reward=${reviewed.episode.reward.value}`);
6509
- if (evidence) {
6510
- evidence.receipts.forEach((receipt) => {
6511
- const verdict = receipt.verifier_passed === true ? ' verifier:passed'
6512
- : receipt.verifier_passed === false ? ' verifier:FAILED' : '';
6513
- console.log(` receipt: ${receipt.path}${verdict}`);
6514
- });
6515
- evidence.missing.forEach((missingPath) => console.log(` receipt: ${missingPath} MISSING`));
6516
- }
7178
+ console.log(renderAcceptLanding({
7179
+ task: taskDetail(taskDb, db, taskId) || compactTaskFromProjection(projection, taskId),
7180
+ proof,
7181
+ nextTask,
7182
+ publicSync,
7183
+ }));
7184
+ if (publicSync && !publicSync.ok) process.exitCode = 1;
6517
7185
  }
6518
7186
 
6519
7187
  function stampAutoAcceptMetadata(taskDb, db, taskId, actor, policy) {
@@ -7975,22 +8643,39 @@ async function handleTaskApi(req, res, taskDb, db) {
7975
8643
  });
7976
8644
  }
7977
8645
  if (op === 'plan') {
8646
+ const actor = String(body.actor || DEFAULT_OWNER);
8647
+ const goal = String(body.goal || body.objective || '');
8648
+ const summary = String(body.summary || body.plan || '');
8649
+ const owner = String(body.owner || body.assignee || '');
8650
+ const exit = String(body.exit || body.exit_condition || '');
8651
+ const firstMove = String(body.first_move || body.firstMove || body.first || '');
8652
+ const task = taskDetail(taskDb, db, taskId);
8653
+ const automaticPlan = buildAutomaticPlanTrace(taskDb, task, {
8654
+ actor,
8655
+ actorExplicit: Boolean(body.actor),
8656
+ owner,
8657
+ goal,
8658
+ summary,
8659
+ firstMove,
8660
+ exit,
8661
+ });
7978
8662
  const result = taskDb.stageTask(db, {
7979
8663
  id: taskId,
7980
- actor: String(body.actor || DEFAULT_OWNER),
8664
+ actor,
7981
8665
  stage: 'plan',
7982
- goal: String(body.goal || body.objective || ''),
7983
- summary: String(body.summary || body.plan || ''),
7984
- owner: String(body.owner || body.assignee || ''),
7985
- exit: String(body.exit || body.exit_condition || ''),
8666
+ goal,
8667
+ summary,
8668
+ owner: automaticPlan.ownerForStage || owner,
8669
+ exit,
7986
8670
  proofNeeded: String(body.proof_needed || body.proofNeeded || body.proof || body.verify || ''),
7987
- firstMove: String(body.first_move || body.firstMove || body.first || ''),
8671
+ firstMove,
7988
8672
  nextButton: String(body.next_button || body.nextButton || ''),
7989
8673
  confidence: body.confidence,
8674
+ planTrace: automaticPlan.trace,
7990
8675
  });
7991
8676
  if (!result.staged) return sendJson(res, 409, { ok: false, reason: result.reason, detail: stageErrorDetail('task plan', result.reason, result) });
7992
8677
  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) });
8678
+ 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
8679
  }
7995
8680
  if (op === 'do') {
7996
8681
  const firstMove = String(body.first_move || body.firstMove || body.first || '').trim();
@@ -8097,12 +8782,28 @@ async function handleTaskApi(req, res, taskDb, db) {
8097
8782
  const proofIssue = meaningfulTaskProofIssue(proof);
8098
8783
  if (proofIssue) return sendProofIssue(res, proof, proofIssue);
8099
8784
  const nextTaskInput = normalizeReviewNextTaskInput(body.next);
8785
+ const actor = String(body.actor || DEFAULT_OWNER);
8786
+ const resultTrace = buildAutomaticResultTrace(taskDb, db, taskId, {
8787
+ actor,
8788
+ proof,
8789
+ changed: body.changed || body.result || body.done,
8790
+ checked: body.checked || body.check || body.verified,
8791
+ passed: body.passed || body.pass,
8792
+ failed: body.failed || body.fail,
8793
+ cost: body.cost,
8794
+ saved: body.saved || body.savings,
8795
+ tryNext: body.try_next || body.tryNext || body.try || body.handoff,
8796
+ status: body.status,
8797
+ files: body.files,
8798
+ commands: body.commands || body.command,
8799
+ });
8100
8800
  const result = taskDb.readyTask(db, {
8101
8801
  id: taskId,
8102
- actor: String(body.actor || DEFAULT_OWNER),
8802
+ actor,
8103
8803
  proof,
8104
8804
  lesson: String(body.lesson || ''),
8105
8805
  nextTask: nextTaskInput.nextTask,
8806
+ resultTrace: resultTrace && resultTrace.trace,
8106
8807
  });
8107
8808
  if (!result.ready) return sendJson(res, 409, { ok: false, reason: result.reason });
8108
8809
  const { projection, outPath } = writeDefaultProjection(taskDb, db);
@@ -8110,6 +8811,7 @@ async function handleTaskApi(req, res, taskDb, db) {
8110
8811
  ok: true,
8111
8812
  action: 'ready',
8112
8813
  task_id: taskId,
8814
+ result_trace: resultTrace,
8113
8815
  ...(nextTaskInput.ignored ? { review_next_task_ignored: nextTaskInput.ignored } : {}),
8114
8816
  projection_path: outPath,
8115
8817
  task: taskFromProjection(projection, taskId),
@@ -8300,6 +9002,10 @@ async function run(args) {
8300
9002
  case 'continue':
8301
9003
  return cmdContinueWork(rest);
8302
9004
  case 'chat': return cmdChat(rest);
9005
+ case 'plan-preview':
9006
+ case 'preview-plan':
9007
+ case 'plan-card':
9008
+ return cmdPlanPreview(rest);
8303
9009
  case 'note': return cmdNote(rest);
8304
9010
  case 'say': return cmdNote(rest);
8305
9011
  case 'show': return cmdShow(rest);
@@ -8309,6 +9015,7 @@ async function run(args) {
8309
9015
  case 'chat-review':
8310
9016
  return cmdReviewChat(rest);
8311
9017
  case 'ready': return cmdReady(rest);
9018
+ case 'result': return cmdResult(rest);
8312
9019
  case 'accept': return cmdAccept(rest);
8313
9020
  case 'auto-accept-certified':
8314
9021
  case 'auto-accept':
@@ -555,7 +555,7 @@ async function planAtris(userInput = null) {
555
555
  printConfidenceGate(' ');
556
556
  console.log('3) Write tasks to atris/TODO.md under ## Backlog');
557
557
  console.log(' Format: - **T#:** Description [explore|execute]');
558
- console.log('4) Log to atris/team/navigator/journal/YYYY-MM-DD.md');
558
+ console.log('4) Log to atris/team/navigator/logs/YYYY-MM-DD.md');
559
559
  console.log(' (Task, Delivered, User reaction, Pattern)');
560
560
  if (atris2Mode) {
561
561
  console.log('5) EXECUTE MODE ENABLED: Will execute tasks directly.');
@@ -636,7 +636,7 @@ async function planAtris(userInput = null) {
636
636
  userPrompt += ` - Each task: one job, clear exit condition\n`;
637
637
  userPrompt += ` - Include file:line references from MAP.md\n\n`;
638
638
  userPrompt += `STEP 5: Log to your journal\n`;
639
- userPrompt += ` - Write to atris/team/navigator/journal/YYYY-MM-DD.md\n`;
639
+ userPrompt += ` - Write to atris/team/navigator/logs/YYYY-MM-DD.md\n`;
640
640
  userPrompt += ` - Include: Task, Delivered, User reaction, Pattern\n`;
641
641
  userPrompt += ` - Your journal is how you learn — record what worked\n\n`;
642
642
  userPrompt += `Start planning now. Read MAP.md for file references.`;
@@ -900,7 +900,7 @@ async function doAtris() {
900
900
  console.log('3) Execute step-by-step. Run tests as you go.');
901
901
  console.log('4) Before completion, rerun the gate against proof and residual risk');
902
902
  console.log('5) When done, move task to ## Completed');
903
- console.log('6) Log to atris/team/executor/journal/YYYY-MM-DD.md');
903
+ console.log('6) Log to atris/team/executor/logs/YYYY-MM-DD.md');
904
904
  console.log(' (Task, Delivered, Errors hit, Learned)');
905
905
  console.log('');
906
906
  console.log('⛔ Do NOT plan — just execute what\'s written.');
@@ -989,7 +989,7 @@ async function doAtris() {
989
989
  userPrompt += `4. Execute task: Use file edit tools, terminal commands, etc.\n`;
990
990
  userPrompt += `5. Before completion, rerun the gate against proof and residual risk\n`;
991
991
  userPrompt += `6. Move task to ## Completed in TODO.md\n`;
992
- userPrompt += `7. Log to atris/team/executor/journal/YYYY-MM-DD.md\n`;
992
+ userPrompt += `7. Log to atris/team/executor/logs/YYYY-MM-DD.md\n`;
993
993
  userPrompt += ` (Task, Delivered, Errors hit, Learned)\n`;
994
994
  userPrompt += `8. Use MAP.md to navigate codebase\n\n`;
995
995
  userPrompt += `DO NOT just describe what you would do - actually edit files and execute commands!\n`;
@@ -1288,7 +1288,7 @@ async function reviewAtris() {
1288
1288
  console.log('4) Confirm active task state is clean: no unresolved Backlog/In Progress/Blocked rows for the reviewed work.');
1289
1289
  console.log(' If durable task state changed, regenerate the readable view with `atris task render --out atris/TODO.md`.');
1290
1290
  console.log(' Do not hand-delete rendered completed history; use `atris task list --status done` for the ledger.');
1291
- console.log('5) Log to atris/team/validator/journal/YYYY-MM-DD.md');
1291
+ console.log('5) Log to atris/team/validator/logs/YYYY-MM-DD.md');
1292
1292
  console.log(' (Task, Result, Issues found, Learned)');
1293
1293
  console.log('6) If anything surprised you, append to atris/lessons.md.');
1294
1294
  console.log('');
@@ -1389,7 +1389,7 @@ async function reviewAtris() {
1389
1389
  userPrompt += ` • Confirm active task state is clean — no unresolved Backlog/In Progress/Blocked rows for reviewed work.\n`;
1390
1390
  userPrompt += ` If durable task state changed, regenerate the readable view with \`atris task render --out atris/TODO.md\`.\n`;
1391
1391
  userPrompt += ` Do not hand-delete rendered completed history; if a task fails, move or mark it blocked with a note.\n`;
1392
- userPrompt += ` • Log to atris/team/validator/journal/YYYY-MM-DD.md\n`;
1392
+ userPrompt += ` • Log to atris/team/validator/logs/YYYY-MM-DD.md\n`;
1393
1393
  userPrompt += ` (Task, Result, Issues found, Learned)\n`;
1394
1394
  userPrompt += ` • If anything surprised you, append to atris/lessons.md\n`;
1395
1395
  userPrompt += ` • EVOLUTION: If you see drift in the logs, propose a tool upgrade.\n\n`;
package/commands/xp.js CHANGED
@@ -2106,7 +2106,16 @@ function renderSync(payload) {
2106
2106
  console.log('Login auth mapped this sync to your Atris account.');
2107
2107
  }
2108
2108
  console.log(`Packet ${payload.packet_hash}`);
2109
- console.log(`Leaderboard: ${AGENTXP_LEADERBOARD_URL}`);
2109
+ const publicAccepted = asNumber(server.public_accepted_count);
2110
+ const internalAccepted = asNumber(server.internal_accepted_count);
2111
+ if (publicAccepted > 0) {
2112
+ console.log(`Published to AgentXP: ${AGENTXP_LEADERBOARD_URL}`);
2113
+ } else if (internalAccepted > 0 || server.private_agentxp) {
2114
+ console.log('Saved internally. Public AgentXP was not updated.');
2115
+ console.log('To publish publicly, run: atris xp sync --all --public');
2116
+ } else {
2117
+ console.log(`Leaderboard: ${AGENTXP_LEADERBOARD_URL}`);
2118
+ }
2110
2119
  }
2111
2120
 
2112
2121
  function render(payload) {
package/lib/file-ops.js CHANGED
@@ -57,7 +57,7 @@ function createLogFile(logFile, dateFormatted) {
57
57
  const prevMonth = String(prev.getMonth() + 1).padStart(2, '0');
58
58
  const prevDay = String(prev.getDate()).padStart(2, '0');
59
59
  const prevDateFormatted = `${prevYear}-${prevMonth}-${prevDay}`;
60
- const prevLogFile = path.join(process.cwd(), 'atris', 'logs', prevYear.toString(), `${prevDateFormatted}.md`);
60
+ const { logFile: prevLogFile } = getLogPath(prevDateFormatted);
61
61
 
62
62
  if (fs.existsSync(prevLogFile)) {
63
63
  const prevContent = fs.readFileSync(prevLogFile, 'utf8');
package/lib/journal.js CHANGED
@@ -3,7 +3,13 @@ const fs = require('fs');
3
3
  const path = require('path');
4
4
  const os = require('os');
5
5
  const { spawnSync } = require('child_process');
6
- const escapeRegExp = require('./escape-regexp');
6
+
7
+ // Path/directory helpers for atris/logs/<YYYY>/<YYYY-MM-DD>.md live in lib/file-ops.
8
+ // Re-export them here so existing `require('../lib/journal')` consumers
9
+ // (activate, autopilot, brainstorm, run, status, visualize, workflow) keep working
10
+ // without code changes while we collapse the duplicate-truth bug. PR2/3 will
11
+ // re-point consumers directly at lib/file-ops.
12
+ const { getLogPath, ensureLogDirectory, createLogFile } = require('./file-ops');
7
13
 
8
14
  /**
9
15
  * Check if two timestamps are effectively the same (within 5ms).
@@ -189,78 +195,6 @@ function showLogDiff(localPath, remoteContent) {
189
195
  }
190
196
  }
191
197
 
192
- function getLogPath(dateStr) {
193
- const targetDir = path.join(process.cwd(), 'atris');
194
- const date = dateStr ? new Date(dateStr) : new Date();
195
- const year = date.getFullYear();
196
- const month = String(date.getMonth() + 1).padStart(2, '0');
197
- const day = String(date.getDate()).padStart(2, '0');
198
- const dateFormatted = `${year}-${month}-${day}`; // YYYY-MM-DD in local time
199
-
200
- const logsDir = path.join(targetDir, 'logs');
201
- const yearDir = path.join(logsDir, year.toString());
202
- const logFile = path.join(yearDir, `${dateFormatted}.md`);
203
-
204
- return { logsDir, yearDir, logFile, dateFormatted };
205
- }
206
-
207
- function ensureLogDirectory() {
208
- const { logsDir, yearDir } = getLogPath();
209
-
210
- if (!fs.existsSync(logsDir)) {
211
- fs.mkdirSync(logsDir, { recursive: true });
212
- }
213
-
214
- if (!fs.existsSync(yearDir)) {
215
- fs.mkdirSync(yearDir, { recursive: true });
216
- }
217
- }
218
-
219
- function createLogFile(logFile, dateFormatted) {
220
- let carryInProgress = '';
221
- let carryBacklog = '';
222
- let carryInbox = '';
223
-
224
- try {
225
- const [y, m, d] = String(dateFormatted).split('-').map(Number);
226
- if (Number.isFinite(y) && Number.isFinite(m) && Number.isFinite(d)) {
227
- const prev = new Date(y, m - 1, d);
228
- prev.setDate(prev.getDate() - 1);
229
-
230
- const prevYear = prev.getFullYear();
231
- const prevMonth = String(prev.getMonth() + 1).padStart(2, '0');
232
- const prevDay = String(prev.getDate()).padStart(2, '0');
233
- const prevDateFormatted = `${prevYear}-${prevMonth}-${prevDay}`;
234
- const prevLogFile = path.join(process.cwd(), 'atris', 'logs', prevYear.toString(), `${prevDateFormatted}.md`);
235
-
236
- if (fs.existsSync(prevLogFile)) {
237
- const prevContent = fs.readFileSync(prevLogFile, 'utf8');
238
-
239
- const sectionBody = (headingLine) => {
240
- const regex = new RegExp(
241
- `## ${escapeRegExp(headingLine)}\\n([\\s\\S]*?)(?=\\n---|\\n## |$)`
242
- );
243
- const match = prevContent.match(regex);
244
- return match ? match[1].trim() : '';
245
- };
246
-
247
- carryInProgress = sectionBody('In Progress 🔄');
248
- carryBacklog = sectionBody('Backlog');
249
- carryInbox = sectionBody('Inbox');
250
- }
251
- }
252
- } catch {
253
- // Best-effort carry-forward; never block journal creation.
254
- }
255
-
256
- const inProgressBody = carryInProgress ? `${carryInProgress}\n\n` : '';
257
- const backlogBody = carryBacklog ? `${carryBacklog}\n\n` : '';
258
- const inboxBody = carryInbox ? `${carryInbox}\n\n` : '';
259
-
260
- const initialContent = `# Log — ${dateFormatted}\n\n## Handoff\n\n---\n\n## Completed ✅\n\n---\n\n## In Progress 🔄\n\n${inProgressBody}---\n\n## Backlog\n\n${backlogBody}---\n\n## Notes\n\n---\n\n## Inbox\n\n${inboxBody}\n`;
261
- fs.writeFileSync(logFile, initialContent);
262
- }
263
-
264
198
  module.exports = {
265
199
  isSameTimestamp,
266
200
  computeContentHash,
package/lib/task-db.js CHANGED
@@ -501,7 +501,7 @@ function doneTask(db, { id, status, actor, allowReview = false, action, proof }
501
501
  return { updated: false };
502
502
  }
503
503
 
504
- function readyTask(db, { id, actor, proof, lesson, nextTask }) {
504
+ function readyTask(db, { id, actor, proof, lesson, nextTask, resultTrace }) {
505
505
  if (!id) throw new Error('id required');
506
506
  const text = String(proof || '').trim();
507
507
  if (!text) throw new Error('proof required');
@@ -537,20 +537,25 @@ function readyTask(db, { id, actor, proof, lesson, nextTask }) {
537
537
  `).run(now, JSON.stringify(metadata), id));
538
538
  if (result.changes !== 1) return { ready: false, reason: 'not_open_claimed_or_review' };
539
539
  const updated = getTask(db, id);
540
+ const payload = {
541
+ proof: text,
542
+ lesson: metadata.latest_agent_lesson,
543
+ next_task: metadata.latest_agent_next_task,
544
+ approval_status: 'pending',
545
+ review_pass_count: reviewPassCount,
546
+ agent_certified: metadata.agent_certified === true,
547
+ agent_certification_policy: metadata.agent_certification_policy || null,
548
+ };
549
+ if (resultTrace && typeof resultTrace === 'object') {
550
+ payload.result_trace = resultTrace;
551
+ payload.result_packet = `TASK_RESULT_TRACE ${JSON.stringify(resultTrace)}`;
552
+ }
540
553
  const event = appendTaskEvent(db, {
541
554
  taskId: id,
542
555
  workspaceRoot: updated.workspace_root,
543
556
  actor: actor || null,
544
557
  eventType: 'proof_ready',
545
- payload: {
546
- proof: text,
547
- lesson: metadata.latest_agent_lesson,
548
- next_task: metadata.latest_agent_next_task,
549
- approval_status: 'pending',
550
- review_pass_count: reviewPassCount,
551
- agent_certified: metadata.agent_certified === true,
552
- agent_certification_policy: metadata.agent_certification_policy || null,
553
- },
558
+ payload,
554
559
  });
555
560
  return { ready: true, event, row: updated };
556
561
  }
@@ -770,6 +775,9 @@ function stagePacketFromPayload(payload) {
770
775
  if (data.proof_needed) lines.push(`proof_needed: ${data.proof_needed}`);
771
776
  if (data.first_move) lines.push(`first_move: ${data.first_move}`);
772
777
  if (data.next_button) lines.push(`next_button: ${data.next_button}`);
778
+ if (data.plan_trace && typeof data.plan_trace === 'object') {
779
+ lines.push(`TASK_PLAN_TRACE ${JSON.stringify(data.plan_trace)}`);
780
+ }
773
781
  return lines.filter(Boolean).join('\n');
774
782
  }
775
783
 
@@ -797,6 +805,7 @@ function stageTask(db, {
797
805
  firstMove,
798
806
  nextButton,
799
807
  confidence,
808
+ planTrace,
800
809
  }) {
801
810
  if (!id) throw new Error('id required');
802
811
  const targetStage = cleanStageText(stage).toLowerCase();
@@ -1015,6 +1024,7 @@ function stageTask(db, {
1015
1024
  first_move: cleanStageText(firstMove) || null,
1016
1025
  next_button: metadata.next_button || null,
1017
1026
  confidence: Number.isFinite(confidenceValue) ? metadata.stage_confidence : null,
1027
+ plan_trace: targetStage === 'plan' && planTrace && typeof planTrace === 'object' ? planTrace : null,
1018
1028
  };
1019
1029
  payload.stage_packet = stagePacketFromPayload(payload);
1020
1030
  const updated = getTask(db, id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atris",
3
- "version": "3.30.0",
3
+ "version": "3.30.1",
4
4
  "main": "bin/atris.js",
5
5
  "bin": {
6
6
  "atris": "bin/atris.js",