atris 3.30.1 → 3.30.3
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 +717 -32
- 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 +342 -66
- package/commands/xp.js +3 -0
- 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 +2 -2
- package/lib/functional-owner.js +189 -0
- package/lib/slides-deck.js +512 -58
- package/lib/task-db.js +109 -2
- 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/bin/atris.js
CHANGED
|
@@ -369,6 +369,7 @@ function showHelp() {
|
|
|
369
369
|
console.log(' activate - Load Atris context');
|
|
370
370
|
console.log(' radar - Show live agents joined with tasks, missions, and worktrees');
|
|
371
371
|
console.log(' ctop - Show a process-first live agent CPU/memory view');
|
|
372
|
+
console.log(' launchpad - Show the next action from local brain, task, mission, and proof state');
|
|
372
373
|
console.log(' status - See local work and completions (`atris status <business>` for remote)');
|
|
373
374
|
console.log(' recap - What your AI team did, in plain English (--share for paste-ready)');
|
|
374
375
|
console.log(' xp - Show Career XP and contribution graph');
|
|
@@ -820,7 +821,7 @@ if (command === '2' && ['fast', 'pro'].includes(String(firstCommandArg || '').to
|
|
|
820
821
|
}
|
|
821
822
|
|
|
822
823
|
// Check if this is a known command or natural language input
|
|
823
|
-
const knownCommands = ['init', 'log', 'now', 'radar', 'ctop', 'status', 'analytics', 'visualize', 'brain', 'brainstorm', 'autopilot', 'run', 'plan', 'do', 'review', 'release',
|
|
824
|
+
const knownCommands = ['init', 'log', 'now', 'radar', 'ctop', 'launchpad', 'status', 'analytics', 'visualize', 'brain', 'brainstorm', 'autopilot', 'run', 'plan', 'do', 'review', 'release',
|
|
824
825
|
'activate', '_activate', 'agent', 'chat', 'fast', 'ax', 'console', 'serve', 'login', 'logout', 'whoami', 'switch', 'use', 'accounts', '_resolve', '_profile-email', '_switch-session', 'shell-init', 'update', 'upgrade', 'version', 'help', 'next', 'atris',
|
|
825
826
|
'clean', 'verify', 'search', 'skill', 'member', 'codex-goal', 'app', 'apps', 'learn', 'lesson', 'plugin', 'experiments', 'receipt', 'proof', 'openclaw', 'pull', 'push', 'live', 'align', 'terminal', 'computer', 'diff', 'business', 'sync', 'youtube',
|
|
826
827
|
'ingest', 'query', 'lint', 'loop', 'pulse', 'task', 'mission', 'probe', 'worktree', 'aeo', 'slop', 'security-review', 'secure', 'deck', 'site', 'theme', 'card', 'reel', 'improve', 'xp', 'play', 'gm', 'x', 'recap', 'signup', 'clarity', 'moves',
|
|
@@ -1702,6 +1703,9 @@ if (command === 'init') {
|
|
|
1702
1703
|
console.error(`\u2717 Run failed: ${error.message || error}`);
|
|
1703
1704
|
process.exit(1);
|
|
1704
1705
|
});
|
|
1706
|
+
} else if (command === 'launchpad') {
|
|
1707
|
+
const code = require('../commands/launchpad').launchpadCommand(process.argv.slice(3));
|
|
1708
|
+
process.exit(code);
|
|
1705
1709
|
} else if (command === 'autopilot') {
|
|
1706
1710
|
const args = process.argv.slice(3);
|
|
1707
1711
|
if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
|
package/commands/agent-spawn.js
CHANGED
|
@@ -350,9 +350,9 @@ function writeDogfoodReceipt(root, receipt) {
|
|
|
350
350
|
}
|
|
351
351
|
|
|
352
352
|
function showDogfoodHelp(output = console.log) {
|
|
353
|
-
output('
|
|
353
|
+
output('Internal usage: atris agent dogfood [devin|droid|--engine all] [--model glm-5.2] [--live] [--json]');
|
|
354
354
|
output('');
|
|
355
|
-
output('
|
|
355
|
+
output('Internal support smoke for external coding CLIs; hidden from public agent help.');
|
|
356
356
|
output('Add --live to run one exact sentinel prompt through Devin/Droid.');
|
|
357
357
|
}
|
|
358
358
|
|
package/commands/brain.js
CHANGED
|
@@ -13,6 +13,9 @@ const GENERATED_LOAD_ORDER_FILES = [
|
|
|
13
13
|
'atris/brain/STATUS.md',
|
|
14
14
|
'atris/brain/self_improvement_ledger.md',
|
|
15
15
|
];
|
|
16
|
+
const OPTIONAL_STATE_LOAD_ORDER_FILES = [
|
|
17
|
+
'.atris/state/chat_scan.latest.json',
|
|
18
|
+
];
|
|
16
19
|
const OPTIONAL_LOAD_ORDER_FILES = [
|
|
17
20
|
'atris/wiki/concepts/agent-activation-contract.md',
|
|
18
21
|
'atris/skills/atris/SKILL.md',
|
|
@@ -1128,10 +1131,68 @@ function taskEpisodeScorecard(root, episode, workspace, ts = new Date().toISOStr
|
|
|
1128
1131
|
lesson: episode.lesson || '',
|
|
1129
1132
|
proof: episode.proof || '',
|
|
1130
1133
|
next_task_suggestion: episode.next_task_suggestion || null,
|
|
1134
|
+
review_landing: episode.review_landing || null,
|
|
1135
|
+
landing_quality: episode.landing_quality || null,
|
|
1136
|
+
human_feedback: episode.human_feedback || null,
|
|
1137
|
+
approval_status: episode.human_feedback && episode.human_feedback.approval_status || episode.rl && episode.rl.approval_status || null,
|
|
1138
|
+
rl_label: episode.rl && episode.rl.label || null,
|
|
1131
1139
|
episode_created_at: episode.created_at || episode.ts || null,
|
|
1132
1140
|
};
|
|
1133
1141
|
}
|
|
1134
1142
|
|
|
1143
|
+
function landingQualityFromReviewLanding(landing) {
|
|
1144
|
+
const normalized = {
|
|
1145
|
+
happened: landing && landing.happened || null,
|
|
1146
|
+
checked: landing && landing.checked || null,
|
|
1147
|
+
tested: landing && landing.tested || null,
|
|
1148
|
+
decision: landing && landing.decision || null,
|
|
1149
|
+
};
|
|
1150
|
+
const present = Object.entries(normalized)
|
|
1151
|
+
.filter(([, value]) => Boolean(value))
|
|
1152
|
+
.map(([key]) => key);
|
|
1153
|
+
const missing = Object.keys(normalized).filter(key => !normalized[key]);
|
|
1154
|
+
return {
|
|
1155
|
+
present,
|
|
1156
|
+
missing,
|
|
1157
|
+
completeness: present.length / Object.keys(normalized).length,
|
|
1158
|
+
has_decision: Boolean(normalized.decision),
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
function taskProjectionByEpisodeId(root) {
|
|
1163
|
+
const tasks = readTaskProjectionTasks(root) || [];
|
|
1164
|
+
const byId = new Map();
|
|
1165
|
+
for (const task of tasks) {
|
|
1166
|
+
for (const key of [task.id, task.display_id, task.legacy_ref, task.ref].filter(Boolean)) {
|
|
1167
|
+
byId.set(String(key), task);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
return byId;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
function enrichTaskEpisodeFromProjection(episode, taskById) {
|
|
1174
|
+
if (episode.review_landing) return episode;
|
|
1175
|
+
const task = taskById.get(String(episode.task_id || ''));
|
|
1176
|
+
const landing = task?.review?.landing || null;
|
|
1177
|
+
if (!landing) return episode;
|
|
1178
|
+
const approvalStatus = task?.review?.approval_status || episode.human_feedback?.approval_status || episode.rl?.approval_status || null;
|
|
1179
|
+
return {
|
|
1180
|
+
...episode,
|
|
1181
|
+
review_landing: landing,
|
|
1182
|
+
landing_quality: episode.landing_quality || landingQualityFromReviewLanding(landing),
|
|
1183
|
+
human_feedback: episode.human_feedback || {
|
|
1184
|
+
approval_status: approvalStatus,
|
|
1185
|
+
human_revision_count: Number(task?.review?.human_revision_count || 0),
|
|
1186
|
+
human_revision_note: task?.review?.human_revision_note || null,
|
|
1187
|
+
},
|
|
1188
|
+
rl: episode.rl ? {
|
|
1189
|
+
...episode.rl,
|
|
1190
|
+
landing_completeness: episode.rl.landing_completeness ?? landingQualityFromReviewLanding(landing).completeness,
|
|
1191
|
+
approval_status: episode.rl.approval_status || approvalStatus,
|
|
1192
|
+
} : episode.rl,
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1135
1196
|
function latestTaskEpisodes(taskEpisodes) {
|
|
1136
1197
|
const byTask = new Map();
|
|
1137
1198
|
for (const episode of taskEpisodes) {
|
|
@@ -1154,19 +1215,31 @@ function recordTaskEpisodeScorecards(options) {
|
|
|
1154
1215
|
{};
|
|
1155
1216
|
const taskEpisodes = stateDirs.flatMap(item => readJsonlRows(path.join(item, 'task_episodes.jsonl')))
|
|
1156
1217
|
.filter(row => row && row.schema === 'atris.task_episode.v1');
|
|
1157
|
-
const
|
|
1218
|
+
const taskById = taskProjectionByEpisodeId(stateRoot);
|
|
1219
|
+
if (stateRoot !== root) {
|
|
1220
|
+
for (const [key, task] of taskProjectionByEpisodeId(root)) taskById.set(key, task);
|
|
1221
|
+
}
|
|
1222
|
+
const scoreableEpisodes = latestTaskEpisodes(taskEpisodes)
|
|
1223
|
+
.map(episode => enrichTaskEpisodeFromProjection(episode, taskById));
|
|
1158
1224
|
const existing = stateDirs.flatMap(item => readJsonlRows(path.join(item, 'scorecards.jsonl')));
|
|
1159
|
-
const
|
|
1160
|
-
|
|
1161
|
-
.
|
|
1225
|
+
const existingByEpisodeId = new Map();
|
|
1226
|
+
for (const row of existing) {
|
|
1227
|
+
if (!row.source_episode_id) continue;
|
|
1228
|
+
const rows = existingByEpisodeId.get(row.source_episode_id) || [];
|
|
1229
|
+
rows.push(row);
|
|
1230
|
+
existingByEpisodeId.set(row.source_episode_id, rows);
|
|
1231
|
+
}
|
|
1162
1232
|
|
|
1163
1233
|
const written = [];
|
|
1164
1234
|
for (const episode of scoreableEpisodes) {
|
|
1165
1235
|
const episodeId = episode.episode_id;
|
|
1166
|
-
if (seenEpisodeIds.has(episodeId)) continue;
|
|
1167
1236
|
const scorecard = taskEpisodeScorecard(root, episode, workspace);
|
|
1237
|
+
const existingRows = existingByEpisodeId.get(episodeId) || [];
|
|
1238
|
+
const existingHasLanding = existingRows.some(row => row.review_landing);
|
|
1239
|
+
if (existingRows.length && (!scorecard.review_landing || existingHasLanding)) continue;
|
|
1168
1240
|
appendJsonl(scorecardsPath, scorecard);
|
|
1169
|
-
|
|
1241
|
+
existingRows.push(scorecard);
|
|
1242
|
+
existingByEpisodeId.set(episodeId, existingRows);
|
|
1170
1243
|
written.push(scorecard);
|
|
1171
1244
|
}
|
|
1172
1245
|
|
|
@@ -1212,9 +1285,21 @@ function verifyApproval(root, approvalId) {
|
|
|
1212
1285
|
|
|
1213
1286
|
function brainLoadOrderFiles(state) {
|
|
1214
1287
|
const root = state.root;
|
|
1288
|
+
const lookupRoots = [root, ...(state.stateRoots || []), state.stateRoot]
|
|
1289
|
+
.filter(Boolean)
|
|
1290
|
+
.map(item => path.resolve(item));
|
|
1291
|
+
const seen = new Set();
|
|
1292
|
+
const uniqueRoots = lookupRoots.filter(item => {
|
|
1293
|
+
if (seen.has(item)) return false;
|
|
1294
|
+
seen.add(item);
|
|
1295
|
+
return true;
|
|
1296
|
+
});
|
|
1297
|
+
const loadOrderFileExists = rel => uniqueRoots.some(base => fs.existsSync(path.join(base, rel)));
|
|
1298
|
+
const existingState = OPTIONAL_STATE_LOAD_ORDER_FILES
|
|
1299
|
+
.filter(loadOrderFileExists);
|
|
1215
1300
|
const existing = OPTIONAL_LOAD_ORDER_FILES
|
|
1216
1301
|
.filter(rel => fs.existsSync(path.join(root, rel)));
|
|
1217
|
-
return [...GENERATED_LOAD_ORDER_FILES, ...existing];
|
|
1302
|
+
return [...GENERATED_LOAD_ORDER_FILES, ...existingState, ...existing];
|
|
1218
1303
|
}
|
|
1219
1304
|
|
|
1220
1305
|
function renderNumberedLoadOrder(state) {
|
package/commands/brainstorm.js
CHANGED
|
@@ -321,29 +321,43 @@ async function brainstormAtris() {
|
|
|
321
321
|
const nextSteps = nextStepsRaw
|
|
322
322
|
? nextStepsRaw.split(';').map((item) => item.trim()).filter(Boolean)
|
|
323
323
|
: [];
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
324
|
+
try {
|
|
325
|
+
recordBrainstormSession(
|
|
326
|
+
logFile,
|
|
327
|
+
sourceLabel,
|
|
328
|
+
topicSummary,
|
|
329
|
+
userStory,
|
|
330
|
+
[],
|
|
331
|
+
[],
|
|
332
|
+
constraints,
|
|
333
|
+
'',
|
|
334
|
+
feelingsVibe || '',
|
|
335
|
+
nextSteps,
|
|
336
|
+
sessionSummary
|
|
337
|
+
);
|
|
338
|
+
if (selectedInboxItem) {
|
|
339
|
+
const archive = await askYesNo('Archive this Inbox idea now? (y/n): ');
|
|
340
|
+
if (archive) {
|
|
341
|
+
try {
|
|
342
|
+
let latestContent = fs.readFileSync(logFile, 'utf8');
|
|
343
|
+
latestContent = removeInboxItemFromContent(latestContent, selectedInboxItem.id);
|
|
344
|
+
if (typeof latestContent !== 'string') {
|
|
345
|
+
throw new Error('Archive operation produced invalid journal content.');
|
|
346
|
+
}
|
|
347
|
+
writeJournalFile(logFile, latestContent);
|
|
348
|
+
console.log(`✓ Archived I${selectedInboxItem.id} from Inbox.`);
|
|
349
|
+
} catch (error) {
|
|
350
|
+
console.log(`Could not archive I${selectedInboxItem.id}: ${error.message}`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
console.log('✓ Brainstorm session logged.');
|
|
355
|
+
} catch (error) {
|
|
356
|
+
if (error && error.__brainstormAbort) {
|
|
357
|
+
throw error;
|
|
344
358
|
}
|
|
359
|
+
console.log(`Could not log brainstorm session: ${error.message}`);
|
|
345
360
|
}
|
|
346
|
-
console.log('✓ Brainstorm session logged.');
|
|
347
361
|
} else {
|
|
348
362
|
console.log('Skipped journaling. Prompt is ready for your agent.');
|
|
349
363
|
}
|
|
@@ -365,6 +379,32 @@ function removeInboxItemFromContent(content, id) {
|
|
|
365
379
|
return replaceInboxSection(content, items);
|
|
366
380
|
}
|
|
367
381
|
|
|
382
|
+
function writeJournalFile(logFile, content) {
|
|
383
|
+
if (typeof logFile !== 'string' || logFile.trim() === '') {
|
|
384
|
+
throw new Error('Journal log file path is required.');
|
|
385
|
+
}
|
|
386
|
+
if (typeof content !== 'string') {
|
|
387
|
+
throw new Error('Journal content must be a string.');
|
|
388
|
+
}
|
|
389
|
+
const logDir = path.dirname(logFile);
|
|
390
|
+
const tempFile = path.join(logDir, `.${path.basename(logFile)}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`);
|
|
391
|
+
let tempFileWritten = false;
|
|
392
|
+
try {
|
|
393
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
394
|
+
fs.writeFileSync(tempFile, content, { encoding: 'utf8', flag: 'wx' });
|
|
395
|
+
tempFileWritten = true;
|
|
396
|
+
fs.renameSync(tempFile, logFile);
|
|
397
|
+
} catch (error) {
|
|
398
|
+
if (tempFileWritten || !error || error.code !== 'EEXIST') {
|
|
399
|
+
try {
|
|
400
|
+
fs.unlinkSync(tempFile);
|
|
401
|
+
} catch {}
|
|
402
|
+
}
|
|
403
|
+
const message = error && error.message ? error.message : String(error);
|
|
404
|
+
throw new Error(`Could not write journal file: ${message}`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
368
408
|
function insertIntoNotesSection(content, block) {
|
|
369
409
|
const regex = /(## Notes\n)([\s\S]*?)(\n---|\n##|$)/;
|
|
370
410
|
const match = content.match(regex);
|
|
@@ -437,7 +477,7 @@ function recordBrainstormSession(
|
|
|
437
477
|
|
|
438
478
|
const block = lines.join('\n');
|
|
439
479
|
content = insertIntoNotesSection(content, block);
|
|
440
|
-
|
|
480
|
+
writeJournalFile(logFile, content);
|
|
441
481
|
}
|
|
442
482
|
|
|
443
483
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
const { spawn } = require('child_process');
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
|
-
const { loadManifest } = require('../lib/manifest');
|
|
4
|
+
const { loadManifest, saveManifest } = require('../lib/manifest');
|
|
5
|
+
const { removeBaseContents, writeBaseContents } = require('../lib/company-brain-sync');
|
|
5
6
|
|
|
6
7
|
const WATCH_IGNORED_DIRS = new Set([
|
|
7
8
|
'.git', '.atris', '.claude', '.cursor', '.next', '.cache',
|
|
@@ -29,6 +30,7 @@ function parseBusinessSyncArgs(args = []) {
|
|
|
29
30
|
const resolveIdx = positional.indexOf('resolve');
|
|
30
31
|
const resolveFlag = parseFlagValue(args, '--resolve', null);
|
|
31
32
|
const resolve = resolveFlag || (resolveIdx !== -1 ? positional[resolveIdx + 1] : null);
|
|
33
|
+
const resolvePath = parseFlagValue(args, '--path', parseFlagValue(args, '--file', null));
|
|
32
34
|
const commandWords = new Set(['status', 'doctor', 'review', 'resolve', 'local', 'cloud', 'both', 'merge']);
|
|
33
35
|
const slug = positional.find((arg) => !commandWords.has(arg)) || null;
|
|
34
36
|
const dryRun = args.includes('--dry-run');
|
|
@@ -39,7 +41,7 @@ function parseBusinessSyncArgs(args = []) {
|
|
|
39
41
|
const debounceSec = Number.parseInt(parseFlagValue(args, '--debounce', '5'), 10);
|
|
40
42
|
const help = args.includes('--help') || args.includes('-h') || positional[0] === 'help';
|
|
41
43
|
|
|
42
|
-
return { slug, dryRun, timeout, allowDelete, watch, intervalSec, debounceSec, status, review, resolve, help };
|
|
44
|
+
return { slug, dryRun, timeout, allowDelete, watch, intervalSec, debounceSec, status, review, resolve, resolvePath, help };
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
function readBusinessSlug(cwd = process.cwd()) {
|
|
@@ -332,7 +334,7 @@ function renderLocalSyncStatus(status) {
|
|
|
332
334
|
|
|
333
335
|
function renderBusinessSyncHelp() {
|
|
334
336
|
return [
|
|
335
|
-
'Usage: atris sync [business] [--dry-run] [--watch] [--status] [--review] [--resolve local|cloud|both|merge] [--timeout 120]',
|
|
337
|
+
'Usage: atris sync [business] [--dry-run] [--watch] [--status] [--review] [--resolve local|cloud|both|merge] [--path <file>] [--timeout 120]',
|
|
336
338
|
'',
|
|
337
339
|
'Safe loop:',
|
|
338
340
|
' Pull -> Review -> Publish',
|
|
@@ -341,7 +343,7 @@ function renderBusinessSyncHelp() {
|
|
|
341
343
|
' atris sync --status Show local sync health',
|
|
342
344
|
' atris sync --dry-run Preview pull and publish plans without writing cloud',
|
|
343
345
|
' atris sync --review Read the latest conflict packet',
|
|
344
|
-
' atris sync --resolve cloud|local|merge',
|
|
346
|
+
' atris sync --resolve cloud|local|merge [--path <file>]',
|
|
345
347
|
' atris sync --watch Keep the workspace live with the same safety gates',
|
|
346
348
|
'',
|
|
347
349
|
'Publish safety:',
|
|
@@ -398,6 +400,7 @@ function collectConflictResolutionEntries(cwd = process.cwd()) {
|
|
|
398
400
|
const targetRel = path.relative(dir, full).replace(/\\/g, '/').replace(/\.local$/, '');
|
|
399
401
|
entries.push({
|
|
400
402
|
targetRel,
|
|
403
|
+
packetDir: dir,
|
|
401
404
|
basePath: full.replace(/\.local$/, '.base'),
|
|
402
405
|
localPath,
|
|
403
406
|
remotePath,
|
|
@@ -426,6 +429,73 @@ function cleanupResolvedConflictSidecars(cwd, targetRel, { keepCloud = false } =
|
|
|
426
429
|
}
|
|
427
430
|
}
|
|
428
431
|
|
|
432
|
+
function cleanupConflictPacketArtifacts(entry) {
|
|
433
|
+
for (const artifactPath of [entry.basePath, entry.localPath, entry.remotePath]) {
|
|
434
|
+
fs.rmSync(artifactPath, { force: true });
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function conflictPacketHasEntries(packetDir) {
|
|
439
|
+
let hasEntries = false;
|
|
440
|
+
|
|
441
|
+
function walk(current) {
|
|
442
|
+
if (hasEntries) return;
|
|
443
|
+
let items;
|
|
444
|
+
try {
|
|
445
|
+
items = fs.readdirSync(current, { withFileTypes: true });
|
|
446
|
+
} catch {
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
for (const item of items) {
|
|
450
|
+
const full = path.join(current, item.name);
|
|
451
|
+
if (item.isDirectory()) walk(full);
|
|
452
|
+
else if (item.isFile() && item.name.endsWith('.local')) {
|
|
453
|
+
hasEntries = true;
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
if (hasEntries) return;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
walk(packetDir);
|
|
461
|
+
return hasEntries;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function finalizeResolvedConflictPacket(cwd, packetDir) {
|
|
465
|
+
const pendingPath = path.join(packetDir, 'manifest.json');
|
|
466
|
+
const pending = readJsonFile(pendingPath);
|
|
467
|
+
if (!pending || !pending.manifest || !pending.manifest.files) return null;
|
|
468
|
+
|
|
469
|
+
const slug = pending.slug || readBusinessSlug(cwd);
|
|
470
|
+
if (!slug) return null;
|
|
471
|
+
|
|
472
|
+
const manifest = {
|
|
473
|
+
...pending.manifest,
|
|
474
|
+
workspace_root: pending.manifest.workspace_root || cwd,
|
|
475
|
+
};
|
|
476
|
+
saveManifest(slug, manifest);
|
|
477
|
+
if (pending.baseContents && typeof pending.baseContents === 'object') {
|
|
478
|
+
writeBaseContents(cwd, pending.baseContents);
|
|
479
|
+
}
|
|
480
|
+
if (Array.isArray(pending.deletedRemote)) {
|
|
481
|
+
removeBaseContents(cwd, pending.deletedRemote);
|
|
482
|
+
}
|
|
483
|
+
return slug;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function cleanupEmptyConflictPackets(cwd, entries) {
|
|
487
|
+
const finalized = [];
|
|
488
|
+
const packetDirs = Array.from(new Set(entries.map((entry) => entry.packetDir).filter(Boolean)));
|
|
489
|
+
for (const packetDir of packetDirs) {
|
|
490
|
+
if (!conflictPacketHasEntries(packetDir)) {
|
|
491
|
+
const slug = finalizeResolvedConflictPacket(cwd, packetDir);
|
|
492
|
+
if (slug) finalized.push(slug);
|
|
493
|
+
fs.rmSync(packetDir, { recursive: true, force: true });
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
return finalized;
|
|
497
|
+
}
|
|
498
|
+
|
|
429
499
|
function changedRange(baseLines, changedLines) {
|
|
430
500
|
let start = 0;
|
|
431
501
|
while (start < baseLines.length && start < changedLines.length && baseLines[start] === changedLines[start]) {
|
|
@@ -562,16 +632,24 @@ function safeMarkdownMerge(baseContent, localContent, remoteContent) {
|
|
|
562
632
|
return { ok: true, content: merged, mode: 'markdown' };
|
|
563
633
|
}
|
|
564
634
|
|
|
565
|
-
function
|
|
635
|
+
function normalizeResolvePath(value) {
|
|
636
|
+
return String(value || '').replace(/\\/g, '/').replace(/^\.\//, '').replace(/^\/+/, '');
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function resolveLatestConflict(cwd = process.cwd(), strategy = 'local', target = null) {
|
|
566
640
|
if (!['local', 'cloud', 'both', 'merge'].includes(strategy)) {
|
|
567
641
|
throw new Error('Use `atris sync --resolve local`, `atris sync --resolve cloud`, `atris sync --resolve both`, or `atris sync --resolve merge`.');
|
|
568
642
|
}
|
|
569
643
|
|
|
570
|
-
const
|
|
644
|
+
const wantedTarget = normalizeResolvePath(target);
|
|
645
|
+
const entries = collectConflictResolutionEntries(cwd)
|
|
646
|
+
.filter((entry) => !wantedTarget || normalizeResolvePath(entry.targetRel) === wantedTarget);
|
|
571
647
|
if (entries.length === 0) {
|
|
572
648
|
return {
|
|
573
649
|
resolved: [],
|
|
574
|
-
message:
|
|
650
|
+
message: wantedTarget
|
|
651
|
+
? `No sync conflicts need resolution for ${wantedTarget}.\n`
|
|
652
|
+
: 'No sync conflicts need resolution.\n',
|
|
575
653
|
};
|
|
576
654
|
}
|
|
577
655
|
|
|
@@ -591,6 +669,7 @@ function resolveLatestConflict(cwd = process.cwd(), strategy = 'local') {
|
|
|
591
669
|
fs.copyFileSync(entry.remotePath, remoteCopyPath);
|
|
592
670
|
}
|
|
593
671
|
cleanupResolvedConflictSidecars(cwd, entry.targetRel, { keepCloud: true });
|
|
672
|
+
cleanupConflictPacketArtifacts(entry);
|
|
594
673
|
resolved.push(entry.targetRel);
|
|
595
674
|
continue;
|
|
596
675
|
}
|
|
@@ -612,6 +691,7 @@ function resolveLatestConflict(cwd = process.cwd(), strategy = 'local') {
|
|
|
612
691
|
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
613
692
|
fs.writeFileSync(targetPath, merged.content, 'utf8');
|
|
614
693
|
cleanupResolvedConflictSidecars(cwd, entry.targetRel);
|
|
694
|
+
cleanupConflictPacketArtifacts(entry);
|
|
615
695
|
resolved.push(entry.targetRel);
|
|
616
696
|
continue;
|
|
617
697
|
}
|
|
@@ -621,15 +701,19 @@ function resolveLatestConflict(cwd = process.cwd(), strategy = 'local') {
|
|
|
621
701
|
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
622
702
|
fs.copyFileSync(sourcePath, targetPath);
|
|
623
703
|
cleanupResolvedConflictSidecars(cwd, entry.targetRel);
|
|
704
|
+
cleanupConflictPacketArtifacts(entry);
|
|
624
705
|
resolved.push(entry.targetRel);
|
|
625
706
|
}
|
|
626
707
|
|
|
708
|
+
const finalizedManifests = cleanupEmptyConflictPackets(cwd, entries);
|
|
709
|
+
|
|
627
710
|
return {
|
|
628
711
|
resolved,
|
|
629
712
|
unresolved,
|
|
630
713
|
message: [
|
|
631
714
|
`Resolved ${resolved.length} conflict${resolved.length === 1 ? '' : 's'} using ${strategy === 'both' ? 'both versions' : `${strategy === 'merge' ? 'safe merge' : `${strategy === 'local' ? 'local' : 'cloud'} version`}`}.`,
|
|
632
715
|
...resolved.map((rel) => ` - ${rel}`),
|
|
716
|
+
...(finalizedManifests.length ? ['', `Updated sync manifest for ${finalizedManifests.join(', ')}.`] : []),
|
|
633
717
|
...(unresolved.length ? ['', 'Still needs review:', ...unresolved.map((rel) => ` - ${rel}`)] : []),
|
|
634
718
|
'',
|
|
635
719
|
'Next: run `atris sync --dry-run` before publishing.',
|
|
@@ -717,7 +801,7 @@ async function businessSync(args = process.argv.slice(3), cwd = process.cwd()) {
|
|
|
717
801
|
}
|
|
718
802
|
|
|
719
803
|
if (options.resolve) {
|
|
720
|
-
process.stdout.write(resolveLatestConflict(cwd, options.resolve).message);
|
|
804
|
+
process.stdout.write(resolveLatestConflict(cwd, options.resolve, options.resolvePath).message);
|
|
721
805
|
return;
|
|
722
806
|
}
|
|
723
807
|
|
package/commands/business.js
CHANGED
|
@@ -867,7 +867,6 @@ function renderBusinessMissingAction(missing) {
|
|
|
867
867
|
}
|
|
868
868
|
|
|
869
869
|
function renderBusinessCreatedNextSteps(bizMeta = {}, workspaceRoot = '.') {
|
|
870
|
-
const slug = bizMeta.slug || slugifyName(bizMeta.name) || 'business';
|
|
871
870
|
const lines = [
|
|
872
871
|
' Atris: seeded local computer + operator + validator',
|
|
873
872
|
'',
|
|
@@ -887,7 +886,8 @@ function renderBusinessCreatedNextSteps(bizMeta = {}, workspaceRoot = '.') {
|
|
|
887
886
|
' atris business share --write',
|
|
888
887
|
'',
|
|
889
888
|
' Sync when ready:',
|
|
890
|
-
|
|
889
|
+
' atris sync --dry-run',
|
|
890
|
+
' atris sync',
|
|
891
891
|
];
|
|
892
892
|
return lines.join('\n');
|
|
893
893
|
}
|
|
@@ -926,6 +926,7 @@ function renderBusinessShareHandoff(state, options = {}) {
|
|
|
926
926
|
'```bash',
|
|
927
927
|
`atris pull ${bizMeta.slug}`,
|
|
928
928
|
`cd ${bizMeta.slug}`,
|
|
929
|
+
'atris sync --dry-run',
|
|
929
930
|
'atris business start',
|
|
930
931
|
'```',
|
|
931
932
|
] : [
|
|
@@ -959,13 +960,13 @@ function renderBusinessShareHandoff(state, options = {}) {
|
|
|
959
960
|
'',
|
|
960
961
|
'```bash',
|
|
961
962
|
'atris business onboard --website <url> --contact "Name" --note "what changed"',
|
|
962
|
-
'atris
|
|
963
|
+
state.remoteReady ? 'atris sync --dry-run' : '# local-only: no cloud sync is available yet',
|
|
963
964
|
'atris task next',
|
|
964
965
|
...renderBusinessMissionBootstrapLines(bizMeta),
|
|
965
966
|
'atris do',
|
|
966
967
|
'atris business record atris/reports/<recap>.md --outcome mixed --metric "operator speed"',
|
|
967
968
|
'atris business share --write',
|
|
968
|
-
'atris
|
|
969
|
+
state.remoteReady ? 'atris sync' : '# local-only: share the folder directly',
|
|
969
970
|
'```',
|
|
970
971
|
'',
|
|
971
972
|
'## Proof State',
|
|
@@ -984,6 +985,8 @@ function renderBusinessShareHandoff(state, options = {}) {
|
|
|
984
985
|
'Useful commands:',
|
|
985
986
|
'',
|
|
986
987
|
'```bash',
|
|
988
|
+
state.remoteReady ? 'atris sync --status' : '# local-only: no cloud sync status yet',
|
|
989
|
+
state.remoteReady ? 'atris sync --watch' : '# local-only: no cloud watcher yet',
|
|
987
990
|
'atris radar',
|
|
988
991
|
'atris task next',
|
|
989
992
|
...renderBusinessMissionBootstrapLines(bizMeta),
|
|
@@ -1027,7 +1030,7 @@ function renderBusinessStartCard(state, options = {}) {
|
|
|
1027
1030
|
`- ${state.onePager || 'missing operator one-pager'}`,
|
|
1028
1031
|
'',
|
|
1029
1032
|
'Run:',
|
|
1030
|
-
state.remoteReady ? ' atris
|
|
1033
|
+
state.remoteReady ? ' atris sync --dry-run' : ' # local-only: no cloud sync is available yet',
|
|
1031
1034
|
' atris',
|
|
1032
1035
|
' atris business start',
|
|
1033
1036
|
' atris radar',
|
|
@@ -1037,7 +1040,8 @@ function renderBusinessStartCard(state, options = {}) {
|
|
|
1037
1040
|
' atris do',
|
|
1038
1041
|
' atris business record atris/reports/<recap>.md --outcome mixed --metric "operator speed"',
|
|
1039
1042
|
' atris business share --write',
|
|
1040
|
-
' atris
|
|
1043
|
+
state.remoteReady ? ' atris sync' : ' # local-only: share the folder directly',
|
|
1044
|
+
state.remoteReady ? ' atris sync --watch' : ' # local-only: no cloud watcher yet',
|
|
1041
1045
|
'',
|
|
1042
1046
|
'Proof:',
|
|
1043
1047
|
`- team lanes: ${state.teamMembers}`,
|
|
@@ -2833,12 +2837,14 @@ async function quickstart() {
|
|
|
2833
2837
|
atris business share --write
|
|
2834
2838
|
|
|
2835
2839
|
9. Push local state to cloud:
|
|
2836
|
-
atris
|
|
2840
|
+
atris sync --dry-run
|
|
2841
|
+
atris sync
|
|
2837
2842
|
|
|
2838
2843
|
Repeat:
|
|
2839
2844
|
atris radar -> atris task next -> atris do -> record -> share
|
|
2840
2845
|
|
|
2841
2846
|
Optional:
|
|
2847
|
+
atris sync --watch
|
|
2842
2848
|
atris business connect slack --business my-company
|
|
2843
2849
|
atris business connect github --business my-company
|
|
2844
2850
|
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { scanChatLogs, writeLatestScan } = require('../lib/chat-log-scan');
|
|
4
|
+
|
|
5
|
+
function hasFlag(args, name) {
|
|
6
|
+
return args.includes(name);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function readFlag(args, name, fallback) {
|
|
10
|
+
const i = args.indexOf(name);
|
|
11
|
+
if (i === -1 || i === args.length - 1) return fallback;
|
|
12
|
+
return args[i + 1];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function printReport(report) {
|
|
16
|
+
console.log('');
|
|
17
|
+
console.log('┌ Chat scan ─────────────────────────────────────────────────┐');
|
|
18
|
+
console.log(`│ sessions ${String(report.summary.sessions).padEnd(3)} │ ax ${String(report.summary.ax_sessions).padEnd(3)} │ cursor ${String(report.summary.cursor_sessions).padEnd(3)} │ findings ${String(report.summary.findings).padEnd(3)} │`);
|
|
19
|
+
console.log('└────────────────────────────────────────────────────────────┘');
|
|
20
|
+
console.log('');
|
|
21
|
+
console.log(`Window: last ${report.window_hours}h`);
|
|
22
|
+
console.log(`Ax logs: ${report.sources.ax_dirs.join(', ') || '(none)'}`);
|
|
23
|
+
console.log(`Cursor: ${report.sources.cursor_roots.join(', ') || '(none)'}`);
|
|
24
|
+
console.log('');
|
|
25
|
+
|
|
26
|
+
if (!report.sessions.length) {
|
|
27
|
+
console.log('No recent chat logs found.');
|
|
28
|
+
console.log(' ax --pro --chat writes atris/runs/ax-play-*.log');
|
|
29
|
+
console.log(' Cursor writes ~/.cursor/projects/<slug>/agent-transcripts/*.jsonl');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log('Recent sessions:');
|
|
34
|
+
for (const session of report.sessions.slice(0, 8)) {
|
|
35
|
+
const label = session.source === 'ax' ? `ax/${session.mode || '?'}` : 'cursor';
|
|
36
|
+
const when = session.started_at || path.basename(session.path);
|
|
37
|
+
const last = session.last_user || session.last_assistant || '(no text captured)';
|
|
38
|
+
console.log(`- [${label}] ${when}`);
|
|
39
|
+
console.log(` last: ${last}`);
|
|
40
|
+
if (session.interrupted) console.log(' signal: interrupted');
|
|
41
|
+
if (session.log_findings?.length) console.log(` errors: ${session.log_findings[0]}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (report.findings.length) {
|
|
45
|
+
console.log('');
|
|
46
|
+
console.log('Problems detected:');
|
|
47
|
+
for (const finding of report.findings.slice(0, 8)) {
|
|
48
|
+
console.log(`- [${finding.severity}] ${finding.title}`);
|
|
49
|
+
if (finding.evidence) console.log(` ${finding.evidence}`);
|
|
50
|
+
}
|
|
51
|
+
console.log('');
|
|
52
|
+
console.log(`Next: ${report.next_command}`);
|
|
53
|
+
} else {
|
|
54
|
+
console.log('');
|
|
55
|
+
console.log('No problem patterns detected in recent chat logs.');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function chatScanCommand(argv = []) {
|
|
60
|
+
const args = argv.filter((a) => a !== '--');
|
|
61
|
+
if (hasFlag(args, '--help') || hasFlag(args, '-h') || args[0] === 'help') {
|
|
62
|
+
console.log(`Usage: atris chat scan [--json] [--hours <n>] [--limit <n>] [--no-write]
|
|
63
|
+
|
|
64
|
+
Read recent ax chat logs and Cursor agent transcripts; surface friction/errors.
|
|
65
|
+
|
|
66
|
+
Options:
|
|
67
|
+
--hours <n> Lookback window (default 720 = 30 days)
|
|
68
|
+
--limit <n> Max sessions per source (default 12)
|
|
69
|
+
--no-write Skip writing .atris/state/chat_scan.latest.json
|
|
70
|
+
--json Machine-readable output`);
|
|
71
|
+
return { ok: true };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const root = process.cwd();
|
|
75
|
+
if (!fs.existsSync(path.join(root, 'atris'))) {
|
|
76
|
+
console.error('✗ atris/ folder not found. Run from an Atris workspace.');
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const report = scanChatLogs({
|
|
81
|
+
cwd: root,
|
|
82
|
+
hours: readFlag(args, '--hours', 720),
|
|
83
|
+
limit: readFlag(args, '--limit', 12),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
let latestPath = null;
|
|
87
|
+
if (!hasFlag(args, '--no-write')) {
|
|
88
|
+
latestPath = writeLatestScan(root, report);
|
|
89
|
+
report.latest_path = latestPath;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (hasFlag(args, '--json')) {
|
|
93
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
94
|
+
return report;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
printReport(report);
|
|
98
|
+
if (latestPath) console.log(`\nWrote ${path.relative(root, latestPath)}`);
|
|
99
|
+
return report;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
module.exports = { chatScanCommand };
|