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.
- package/commands/security-review.js +64 -0
- package/commands/task.js +730 -23
- package/commands/workflow.js +6 -6
- package/commands/xp.js +10 -1
- package/lib/file-ops.js +1 -1
- package/lib/journal.js +7 -73
- package/lib/security-scan.js +73 -1
- package/lib/task-db.js +20 -10
- package/package.json +1 -1
package/commands/workflow.js
CHANGED
|
@@ -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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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/security-scan.js
CHANGED
|
@@ -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);
|