atris 3.29.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.
@@ -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,
@@ -431,10 +431,82 @@ function scoreFindings(findings) {
431
431
  return findings.reduce((sum, f) => sum + (score[f.sev] || 0), 0);
432
432
  }
433
433
 
434
+ // --- the loop's flight recorder + landing ---
435
+ // Takeoff = the overnight loop grinding the repo. Landing = the one clean
436
+ // artifact a human reads after: what changed, what still needs you, the trend.
437
+ // One compact row per run keeps it honest without noise.
438
+ const DEFAULT_LEDGER = path.join('.atris', 'state', 'security_review.jsonl');
439
+
440
+ function gitCommit(root) {
441
+ try { return execFileSync('git', ['rev-parse', '--short', 'HEAD'], { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim(); }
442
+ catch { return null; }
443
+ }
444
+
445
+ function openFingerprints(findings, failOn = 'high') {
446
+ const threshold = SEVERITY_RANK[failOn] == null ? SEVERITY_RANK.high : SEVERITY_RANK[failOn];
447
+ return findings.filter((f) => SEVERITY_RANK[f.sev] >= threshold)
448
+ .map((f) => f.fingerprint || findingFingerprint(f));
449
+ }
450
+
451
+ function recordRun(root, scan, { failOn = 'high' } = {}) {
452
+ try {
453
+ const findings = scan.findings || [];
454
+ const row = {
455
+ ts: new Date().toISOString(),
456
+ commit: gitCommit(root),
457
+ scanned: scan.scanned || 0,
458
+ counts: scan.counts || summarizeFindings(findings),
459
+ accepted: scan.suppressed || 0,
460
+ open: [...new Set(openFingerprints(findings, failOn))].sort(),
461
+ };
462
+ const file = path.join(root, DEFAULT_LEDGER);
463
+ fs.mkdirSync(path.dirname(file), { recursive: true });
464
+ fs.appendFileSync(file, JSON.stringify(row) + '\n');
465
+ return row;
466
+ } catch { return null; }
467
+ }
468
+
469
+ function loadLedger(root) {
470
+ try {
471
+ return fs.readFileSync(path.join(root, DEFAULT_LEDGER), 'utf8')
472
+ .split('\n').filter(Boolean)
473
+ .map((l) => { try { return JSON.parse(l); } catch { return null; } })
474
+ .filter(Boolean);
475
+ } catch { return []; }
476
+ }
477
+
478
+ // Compare the current scan to the last recorded run. Returns the decision-ready
479
+ // landing: cleared-to-ship or hold, what was fixed/appeared, what still needs a
480
+ // human, and the trend over recent runs.
481
+ function buildLanding(root, scan, { failOn = 'high' } = {}) {
482
+ const ledger = loadLedger(root);
483
+ const prev = ledger.length ? ledger[ledger.length - 1] : null;
484
+ const findings = scan.findings || [];
485
+ const open = findings.filter((f) => SEVERITY_RANK[f.sev] >= (SEVERITY_RANK[failOn] ?? SEVERITY_RANK.high));
486
+ const openNow = new Set(open.map((f) => f.fingerprint || findingFingerprint(f)));
487
+ const prevOpen = new Set(prev ? prev.open || [] : []);
488
+ return {
489
+ cleared: openNow.size === 0,
490
+ open,
491
+ fixed: [...prevOpen].filter((fp) => !openNow.has(fp)).length,
492
+ appeared: prev ? [...openNow].filter((fp) => !prevOpen.has(fp)).length : openNow.size,
493
+ accepted: scan.suppressed || 0,
494
+ scanned: scan.scanned || 0,
495
+ runs: ledger.length + 1,
496
+ hadPrevRun: Boolean(prev),
497
+ // include THIS run as the latest trend point so the posture line is current
498
+ trend: [
499
+ ...ledger.slice(-4).map((r) => ({ ts: r.ts, critical: (r.counts && r.counts.critical) || 0, high: (r.counts && r.counts.high) || 0 })),
500
+ { ts: new Date().toISOString(), critical: (scan.counts && scan.counts.critical) || 0, high: (scan.counts && scan.counts.high) || 0 },
501
+ ],
502
+ };
503
+ }
504
+
434
505
  module.exports = {
435
506
  scanLine, scanText, scanFile, sensitiveFileFindings, gitTrackedFiles,
436
507
  resolveTargets, runScan, SECRET_RULES, PII_RULES, RULES, CODE_RULES,
437
- DEFAULT_BASELINE, SEVERITIES, SEVERITY_RANK, normalizeSnippet,
508
+ DEFAULT_BASELINE, DEFAULT_LEDGER, SEVERITIES, SEVERITY_RANK, normalizeSnippet,
438
509
  shannonEntropy, secretQuality, findingFingerprint, summarizeFindings,
439
510
  loadBaseline, writeBaseline, applyBaseline, shouldFail, scoreFindings,
511
+ recordRun, loadLedger, buildLanding,
440
512
  };
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.29.0",
3
+ "version": "3.30.1",
4
4
  "main": "bin/atris.js",
5
5
  "bin": {
6
6
  "atris": "bin/atris.js",