atris 3.15.57 → 3.16.0
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 +2 -2
- package/GETTING_STARTED.md +1 -1
- package/PERSONA.md +4 -4
- package/README.md +11 -11
- package/atris/skills/copy-editor/SKILL.md +30 -4
- package/atris/skills/improve/SKILL.md +18 -20
- package/atris/wiki/concepts/agent-activation-contract.md +5 -3
- package/atris/wiki/concepts/workspace-initialization-contract.md +4 -4
- package/atris/wiki/index.md +1 -0
- package/ax +522 -73
- package/bin/atris.js +31 -30
- package/commands/align.js +0 -14
- package/commands/apps.js +102 -1
- package/commands/autopilot.js +197 -22
- package/commands/brain.js +219 -34
- package/commands/brainstorm.js +0 -829
- package/commands/computer.js +0 -60
- package/commands/improve.js +501 -0
- package/commands/integrations.js +233 -71
- package/commands/lesson.js +44 -0
- package/commands/member.js +4498 -226
- package/commands/mission.js +302 -27
- package/commands/now.js +89 -1
- package/commands/radar.js +181 -56
- package/commands/soul.js +0 -4
- package/commands/task.js +5582 -517
- package/commands/terminal.js +14 -10
- package/commands/wiki.js +87 -1
- package/commands/workflow.js +288 -73
- package/commands/worktree.js +52 -15
- package/commands/xp.js +6 -65
- package/lib/auto-accept-certified.js +294 -0
- package/lib/file-ops.js +0 -184
- package/lib/member-alive.js +232 -0
- package/lib/policy-lessons.js +280 -0
- package/lib/receipt-evidence.js +64 -0
- package/lib/state-detection.js +34 -0
- package/lib/task-db.js +568 -16
- package/lib/task-proof.js +43 -0
- package/package.json +1 -1
- package/utils/auth.js +13 -4
- package/commands/research.js +0 -52
- package/lib/section-merge.js +0 -196
package/commands/integrations.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* atris calendar yesterday - Show yesterday's events
|
|
9
9
|
* atris calendar week - Show this week's events
|
|
10
10
|
* atris calendar date YYYY-MM-DD - Show events on a date
|
|
11
|
+
* atris calendar search <query> [--days 30] [--limit 20] [--timeout-ms 10000] [--json] - Search upcoming events
|
|
11
12
|
* atris twitter post - Post a tweet (interactive)
|
|
12
13
|
* atris slack channels - List Slack channels
|
|
13
14
|
* atris slack messages <channel> [--limit 20] - Read recent messages
|
|
@@ -21,6 +22,8 @@ const os = require('os');
|
|
|
21
22
|
const path = require('path');
|
|
22
23
|
const { spawnSync } = require('child_process');
|
|
23
24
|
|
|
25
|
+
const CALENDAR_CACHE_PATH = path.join(os.homedir(), '.atris', 'calendar-events-cache.json');
|
|
26
|
+
|
|
24
27
|
async function getAuth() {
|
|
25
28
|
const ensured = await ensureValidCredentials(apiRequestJson);
|
|
26
29
|
const creds = ensured.error ? null : ensured.credentials;
|
|
@@ -209,6 +212,166 @@ function formatCalendarEvents(label, events) {
|
|
|
209
212
|
console.log('─'.repeat(50));
|
|
210
213
|
}
|
|
211
214
|
|
|
215
|
+
function parseCalendarSearchArgs(args = []) {
|
|
216
|
+
const values = [];
|
|
217
|
+
const options = { days: 30, limit: 20, timeoutMs: 10000, json: false };
|
|
218
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
219
|
+
const arg = args[i];
|
|
220
|
+
if (arg === '--json') {
|
|
221
|
+
options.json = true;
|
|
222
|
+
} else if (arg === '--days' || arg === '-d') {
|
|
223
|
+
const parsed = Number.parseInt(args[i + 1], 10);
|
|
224
|
+
if (Number.isFinite(parsed) && parsed > 0) options.days = Math.min(parsed, 365);
|
|
225
|
+
i += 1;
|
|
226
|
+
} else if (arg === '--limit' || arg === '-n') {
|
|
227
|
+
const parsed = Number.parseInt(args[i + 1], 10);
|
|
228
|
+
if (Number.isFinite(parsed) && parsed > 0) options.limit = Math.min(parsed, 100);
|
|
229
|
+
i += 1;
|
|
230
|
+
} else if (arg === '--timeout-ms' || arg === '--timeout') {
|
|
231
|
+
const parsed = Number.parseInt(args[i + 1], 10);
|
|
232
|
+
if (Number.isFinite(parsed) && parsed > 0) options.timeoutMs = Math.min(parsed, 60000);
|
|
233
|
+
i += 1;
|
|
234
|
+
} else {
|
|
235
|
+
values.push(arg);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return { query: values.join(' ').trim(), ...options };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function calendarAttendeesText(attendees) {
|
|
242
|
+
if (!Array.isArray(attendees)) return '';
|
|
243
|
+
return attendees
|
|
244
|
+
.map((attendee) => {
|
|
245
|
+
if (typeof attendee === 'string') return attendee;
|
|
246
|
+
return [
|
|
247
|
+
attendee.email,
|
|
248
|
+
attendee.displayName,
|
|
249
|
+
attendee.name,
|
|
250
|
+
attendee.responseStatus,
|
|
251
|
+
].filter(Boolean).join(' ');
|
|
252
|
+
})
|
|
253
|
+
.filter(Boolean)
|
|
254
|
+
.join(' ');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function calendarSearchText(event = {}) {
|
|
258
|
+
return [
|
|
259
|
+
event.summary,
|
|
260
|
+
event.title,
|
|
261
|
+
event.location,
|
|
262
|
+
event.description,
|
|
263
|
+
calendarAttendeesText(event.attendees),
|
|
264
|
+
].filter(Boolean).join(' ').toLowerCase();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function filterCalendarEventsForSearch(events = [], query = '', limit = 20) {
|
|
268
|
+
const terms = String(query || '').toLowerCase().split(/\s+/).filter(Boolean);
|
|
269
|
+
if (!terms.length) return [];
|
|
270
|
+
return events
|
|
271
|
+
.filter((event) => {
|
|
272
|
+
const haystack = calendarSearchText(event);
|
|
273
|
+
return terms.every((term) => haystack.includes(term));
|
|
274
|
+
})
|
|
275
|
+
.slice(0, limit);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function compactCalendarEvent(event = {}) {
|
|
279
|
+
return {
|
|
280
|
+
id: event.id || event.event_id || '',
|
|
281
|
+
start: calendarEventTimeValue(event.start || event.start_time || event.startTime),
|
|
282
|
+
end: calendarEventTimeValue(event.end || event.end_time || event.endTime),
|
|
283
|
+
summary: event.summary || event.title || '(no title)',
|
|
284
|
+
location: event.location || '',
|
|
285
|
+
htmlLink: event.htmlLink || event.link || '',
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function calendarCachePacket(events = [], meta = {}) {
|
|
290
|
+
return {
|
|
291
|
+
ok: true,
|
|
292
|
+
updatedAt: new Date().toISOString(),
|
|
293
|
+
source: meta.source || 'live',
|
|
294
|
+
days: meta.days || null,
|
|
295
|
+
count: events.length,
|
|
296
|
+
events: events.map(compactCalendarEvent),
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function writeCalendarEventsCache(events = [], meta = {}) {
|
|
301
|
+
const rows = Array.isArray(events) ? events : [];
|
|
302
|
+
try {
|
|
303
|
+
fs.mkdirSync(path.dirname(CALENDAR_CACHE_PATH), { recursive: true });
|
|
304
|
+
fs.writeFileSync(CALENDAR_CACHE_PATH, `${JSON.stringify(calendarCachePacket(rows, meta), null, 2)}\n`, 'utf8');
|
|
305
|
+
return CALENDAR_CACHE_PATH;
|
|
306
|
+
} catch {
|
|
307
|
+
return '';
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function readCalendarEventsCache() {
|
|
312
|
+
try {
|
|
313
|
+
const packet = JSON.parse(fs.readFileSync(CALENDAR_CACHE_PATH, 'utf8'));
|
|
314
|
+
const events = Array.isArray(packet.events) ? packet.events : [];
|
|
315
|
+
return {
|
|
316
|
+
ok: true,
|
|
317
|
+
path: CALENDAR_CACHE_PATH,
|
|
318
|
+
updatedAt: packet.updatedAt || '',
|
|
319
|
+
events,
|
|
320
|
+
};
|
|
321
|
+
} catch {
|
|
322
|
+
return { ok: false, path: CALENDAR_CACHE_PATH, updatedAt: '', events: [] };
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function formatCalendarSearchResults(query, matches, {
|
|
327
|
+
json = false,
|
|
328
|
+
total = matches.length,
|
|
329
|
+
source = 'live',
|
|
330
|
+
liveOk = true,
|
|
331
|
+
cacheUpdatedAt = '',
|
|
332
|
+
error = '',
|
|
333
|
+
} = {}) {
|
|
334
|
+
if (json) {
|
|
335
|
+
console.log(JSON.stringify({
|
|
336
|
+
ok: true,
|
|
337
|
+
query,
|
|
338
|
+
source,
|
|
339
|
+
liveOk,
|
|
340
|
+
cacheUpdatedAt,
|
|
341
|
+
error,
|
|
342
|
+
totalEvents: total,
|
|
343
|
+
matchCount: matches.length,
|
|
344
|
+
matches: matches.map(compactCalendarEvent),
|
|
345
|
+
}, null, 2));
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (!matches.length) {
|
|
350
|
+
console.log(`No upcoming events matched "${query}".`);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
console.log(`Found ${matches.length} upcoming event(s) matching "${query}":\n`);
|
|
355
|
+
formatCalendarEvents('matching your search', matches);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function formatCalendarSearchError(query, message, { json = false, status = 0 } = {}) {
|
|
359
|
+
if (json) {
|
|
360
|
+
console.log(JSON.stringify({
|
|
361
|
+
ok: false,
|
|
362
|
+
query,
|
|
363
|
+
status,
|
|
364
|
+
error: message || 'Failed to fetch events',
|
|
365
|
+
totalEvents: 0,
|
|
366
|
+
matchCount: 0,
|
|
367
|
+
matches: [],
|
|
368
|
+
}, null, 2));
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
console.error(`Error: ${message || 'Failed to fetch events'}`);
|
|
372
|
+
process.exit(1);
|
|
373
|
+
}
|
|
374
|
+
|
|
212
375
|
async function calendarRange(label, { timeMin, timeMax, days } = {}) {
|
|
213
376
|
const { token, email } = await getAuth();
|
|
214
377
|
|
|
@@ -236,9 +399,63 @@ async function calendarRange(label, { timeMin, timeMax, days } = {}) {
|
|
|
236
399
|
}
|
|
237
400
|
|
|
238
401
|
const events = result.data?.events || result.data || [];
|
|
402
|
+
writeCalendarEventsCache(events, { source: 'calendarRange', days: days || null });
|
|
239
403
|
formatCalendarEvents(label.toLowerCase(), events);
|
|
240
404
|
}
|
|
241
405
|
|
|
406
|
+
async function calendarSearch(args = []) {
|
|
407
|
+
const { query, days, limit, timeoutMs, json } = parseCalendarSearchArgs(args);
|
|
408
|
+
if (!query) {
|
|
409
|
+
console.error('Usage: atris calendar search <query> [--days 30] [--limit 20] [--timeout-ms 10000] [--json]');
|
|
410
|
+
process.exit(1);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const credentials = loadCredentials();
|
|
414
|
+
const token = credentials && credentials.token;
|
|
415
|
+
const email = credentials && credentials.email || 'unknown';
|
|
416
|
+
if (!token) {
|
|
417
|
+
formatCalendarSearchError(query, 'Not logged in. Run: atris login', { json, status: 401 });
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
const result = await apiRequestJson(`/integrations/google-calendar/events?days=${encodeURIComponent(String(days))}`, {
|
|
421
|
+
method: 'GET',
|
|
422
|
+
token,
|
|
423
|
+
timeoutMs,
|
|
424
|
+
retries: 0,
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
if (!result.ok) {
|
|
428
|
+
const cache = readCalendarEventsCache();
|
|
429
|
+
if (json && cache.ok) {
|
|
430
|
+
const matches = filterCalendarEventsForSearch(cache.events, query, limit);
|
|
431
|
+
formatCalendarSearchResults(query, matches, {
|
|
432
|
+
json,
|
|
433
|
+
total: cache.events.length,
|
|
434
|
+
source: 'cache',
|
|
435
|
+
liveOk: false,
|
|
436
|
+
cacheUpdatedAt: cache.updatedAt,
|
|
437
|
+
error: result.error || 'Failed to fetch events',
|
|
438
|
+
});
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
if (result.status === 400 || result.status === 401) {
|
|
442
|
+
formatCalendarSearchError(
|
|
443
|
+
query,
|
|
444
|
+
`Calendar not connected for ${email}. Connect at: https://atris.ai/dashboard/settings`,
|
|
445
|
+
{ json, status: result.status },
|
|
446
|
+
);
|
|
447
|
+
} else {
|
|
448
|
+
formatCalendarSearchError(query, result.error || 'Failed to fetch events', { json, status: result.status || 0 });
|
|
449
|
+
}
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const events = result.data?.events || result.data || [];
|
|
454
|
+
writeCalendarEventsCache(events, { source: 'calendarSearch', days });
|
|
455
|
+
const matches = filterCalendarEventsForSearch(events, query, limit);
|
|
456
|
+
formatCalendarSearchResults(query, matches, { json, total: events.length, source: 'live', liveOk: true });
|
|
457
|
+
}
|
|
458
|
+
|
|
242
459
|
async function calendarToday() {
|
|
243
460
|
await calendarRange("Today's events", { days: 1 });
|
|
244
461
|
}
|
|
@@ -272,12 +489,16 @@ async function calendarCommand(subcommand, ...args) {
|
|
|
272
489
|
case 'date':
|
|
273
490
|
await calendarDate(args[0]);
|
|
274
491
|
break;
|
|
492
|
+
case 'search':
|
|
493
|
+
await calendarSearch(args);
|
|
494
|
+
break;
|
|
275
495
|
default:
|
|
276
496
|
console.log('Calendar commands:');
|
|
277
497
|
console.log(' atris calendar today - Show today\'s events');
|
|
278
498
|
console.log(' atris calendar yesterday - Show yesterday\'s events');
|
|
279
499
|
console.log(' atris calendar week - Show this week\'s events');
|
|
280
500
|
console.log(' atris calendar date YYYY-MM-DD - Show events on a date');
|
|
501
|
+
console.log(' atris calendar search <query> [--days 30] [--limit 20] [--timeout-ms 10000] [--json] - Search upcoming events');
|
|
281
502
|
}
|
|
282
503
|
}
|
|
283
504
|
|
|
@@ -614,31 +835,6 @@ function printImessageDoctor(result, json = false) {
|
|
|
614
835
|
}
|
|
615
836
|
}
|
|
616
837
|
|
|
617
|
-
function boundedImessageLimit(value) {
|
|
618
|
-
const parsed = Number(value || 20);
|
|
619
|
-
if (!Number.isFinite(parsed)) return 20;
|
|
620
|
-
return Math.max(1, Math.min(100, Math.trunc(parsed)));
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
function printImessageRows(result, options = {}) {
|
|
624
|
-
if (options.json) {
|
|
625
|
-
const rows = String(result.stdout || '').trim();
|
|
626
|
-
let messages = [];
|
|
627
|
-
try {
|
|
628
|
-
messages = rows ? JSON.parse(rows) : [];
|
|
629
|
-
} catch {
|
|
630
|
-
messages = [];
|
|
631
|
-
}
|
|
632
|
-
console.log(JSON.stringify({
|
|
633
|
-
ok: result.status === 0,
|
|
634
|
-
count: Array.isArray(messages) ? messages.length : 0,
|
|
635
|
-
messages: Array.isArray(messages) ? messages : [],
|
|
636
|
-
}, null, 2));
|
|
637
|
-
return;
|
|
638
|
-
}
|
|
639
|
-
console.log(String(result.stdout || '').trim() || 'No recent messages found.');
|
|
640
|
-
}
|
|
641
|
-
|
|
642
838
|
function imessageRecent(handle, options = {}) {
|
|
643
839
|
if (!handle) {
|
|
644
840
|
console.error('Usage: atris imessage recent <phone-or-email> [--limit 20]');
|
|
@@ -650,7 +846,7 @@ function imessageRecent(handle, options = {}) {
|
|
|
650
846
|
process.exit(1);
|
|
651
847
|
}
|
|
652
848
|
|
|
653
|
-
const limit =
|
|
849
|
+
const limit = Number(options.limit || 20);
|
|
654
850
|
const chatDb = path.join(os.homedir(), 'Library', 'Messages', 'chat.db');
|
|
655
851
|
const sql = `
|
|
656
852
|
SELECT datetime(m.date/1000000000 + 978307200, 'unixepoch', 'localtime') AS ts,
|
|
@@ -660,44 +856,14 @@ function imessageRecent(handle, options = {}) {
|
|
|
660
856
|
JOIN handle h ON h.rowid = m.handle_id
|
|
661
857
|
WHERE h.id = '${String(handle).replace(/'/g, "''")}'
|
|
662
858
|
ORDER BY m.date DESC
|
|
663
|
-
LIMIT ${limit};
|
|
664
|
-
`;
|
|
665
|
-
const sqliteArgs = options.json ? ['-json', '-readonly', chatDb, sql] : ['-readonly', chatDb, sql];
|
|
666
|
-
const result = spawnSync('sqlite3', sqliteArgs, { encoding: 'utf8' });
|
|
667
|
-
if (result.status !== 0) {
|
|
668
|
-
console.error(result.stderr || 'Failed to read Messages database.');
|
|
669
|
-
process.exit(1);
|
|
670
|
-
}
|
|
671
|
-
printImessageRows(result, options);
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
function imessageLatest(options = {}) {
|
|
675
|
-
const doctor = imessageDoctor();
|
|
676
|
-
if (!doctor.connected) {
|
|
677
|
-
printImessageDoctor(doctor, Boolean(options.json));
|
|
678
|
-
process.exit(1);
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
const limit = boundedImessageLimit(options.limit);
|
|
682
|
-
const chatDb = path.join(os.homedir(), 'Library', 'Messages', 'chat.db');
|
|
683
|
-
const sql = `
|
|
684
|
-
SELECT datetime(m.date/1000000000 + 978307200, 'unixepoch', 'localtime') AS ts,
|
|
685
|
-
CASE m.is_from_me WHEN 1 THEN 'me' ELSE COALESCE(h.id, 'unknown') END AS sender,
|
|
686
|
-
COALESCE(h.id, 'unknown') AS handle,
|
|
687
|
-
replace(replace(COALESCE(m.text,''), char(10), ' '), char(13), ' ') AS text
|
|
688
|
-
FROM message m
|
|
689
|
-
LEFT JOIN handle h ON h.rowid = m.handle_id
|
|
690
|
-
WHERE length(COALESCE(m.text,'')) > 0
|
|
691
|
-
ORDER BY m.date DESC
|
|
692
|
-
LIMIT ${limit};
|
|
859
|
+
LIMIT ${Math.max(1, Math.min(100, limit))};
|
|
693
860
|
`;
|
|
694
|
-
const
|
|
695
|
-
const result = spawnSync('sqlite3', sqliteArgs, { encoding: 'utf8' });
|
|
861
|
+
const result = spawnSync('sqlite3', ['-readonly', chatDb, sql], { encoding: 'utf8' });
|
|
696
862
|
if (result.status !== 0) {
|
|
697
863
|
console.error(result.stderr || 'Failed to read Messages database.');
|
|
698
864
|
process.exit(1);
|
|
699
865
|
}
|
|
700
|
-
|
|
866
|
+
console.log(result.stdout.trim() || 'No recent messages found.');
|
|
701
867
|
}
|
|
702
868
|
|
|
703
869
|
function escapeSqlString(value) {
|
|
@@ -1224,22 +1390,12 @@ async function imessageCommand(subcommand, ...args) {
|
|
|
1224
1390
|
break;
|
|
1225
1391
|
}
|
|
1226
1392
|
case 'recent': {
|
|
1393
|
+
const handle = args[0];
|
|
1227
1394
|
const limitFlag = args.findIndex((x) => x === '--limit');
|
|
1228
1395
|
const limit = limitFlag >= 0 ? args[limitFlag + 1] : 20;
|
|
1229
|
-
const handle = args.find((arg) => arg && !arg.startsWith('--') && arg !== String(limit));
|
|
1230
|
-
if (!handle) {
|
|
1231
|
-
imessageLatest({ limit, json: args.includes('--json') });
|
|
1232
|
-
break;
|
|
1233
|
-
}
|
|
1234
1396
|
imessageRecent(handle, { limit, json: args.includes('--json') });
|
|
1235
1397
|
break;
|
|
1236
1398
|
}
|
|
1237
|
-
case 'latest': {
|
|
1238
|
-
const limitFlag = args.findIndex((x) => x === '--limit');
|
|
1239
|
-
const limit = limitFlag >= 0 ? args[limitFlag + 1] : 20;
|
|
1240
|
-
imessageLatest({ limit, json: args.includes('--json') });
|
|
1241
|
-
break;
|
|
1242
|
-
}
|
|
1243
1399
|
case 'lookup': {
|
|
1244
1400
|
imessageLookup(args);
|
|
1245
1401
|
break;
|
|
@@ -1253,7 +1409,6 @@ async function imessageCommand(subcommand, ...args) {
|
|
|
1253
1409
|
console.log(' atris imessage doctor [--json] - Check local Messages access');
|
|
1254
1410
|
console.log(' atris imessage lookup --name <name> [--json] [--refresh]');
|
|
1255
1411
|
console.log(' atris imessage recent <handle> - Read recent local messages');
|
|
1256
|
-
console.log(' atris imessage latest [--limit 20] [--json] - Read latest local messages');
|
|
1257
1412
|
console.log(' atris imessage send --to <handle> --text <text> --approved [--json] [--receipt]');
|
|
1258
1413
|
}
|
|
1259
1414
|
}
|
|
@@ -1299,4 +1454,11 @@ module.exports = {
|
|
|
1299
1454
|
imessageCommand,
|
|
1300
1455
|
imessageDoctor,
|
|
1301
1456
|
integrationsStatus,
|
|
1457
|
+
parseCalendarSearchArgs,
|
|
1458
|
+
filterCalendarEventsForSearch,
|
|
1459
|
+
formatCalendarSearchResults,
|
|
1460
|
+
formatCalendarSearchError,
|
|
1461
|
+
writeCalendarEventsCache,
|
|
1462
|
+
readCalendarEventsCache,
|
|
1463
|
+
CALENDAR_CACHE_PATH,
|
|
1302
1464
|
};
|
package/commands/lesson.js
CHANGED
|
@@ -2,6 +2,44 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { writeLesson } = require('./autopilot');
|
|
4
4
|
|
|
5
|
+
function mineLessons(args) {
|
|
6
|
+
const { loadHistory, mineProofPolicy, writePolicyLessons, syncLessonsMd } = require('../lib/policy-lessons');
|
|
7
|
+
const root = process.cwd();
|
|
8
|
+
const json = args.includes('--json');
|
|
9
|
+
const dryRun = args.includes('--dry-run');
|
|
10
|
+
const history = loadHistory(root);
|
|
11
|
+
const mined = mineProofPolicy(history);
|
|
12
|
+
let statePath = null;
|
|
13
|
+
let lessonsMd = { path: null, written: [] };
|
|
14
|
+
if (!dryRun) {
|
|
15
|
+
statePath = writePolicyLessons(root, mined);
|
|
16
|
+
lessonsMd = syncLessonsMd(root, mined);
|
|
17
|
+
}
|
|
18
|
+
if (json) {
|
|
19
|
+
console.log(JSON.stringify({
|
|
20
|
+
ok: true,
|
|
21
|
+
action: 'lesson_mine',
|
|
22
|
+
dry_run: dryRun,
|
|
23
|
+
state_path: statePath,
|
|
24
|
+
lessons_md_path: lessonsMd.path,
|
|
25
|
+
lessons_md_written: lessonsMd.written,
|
|
26
|
+
...mined,
|
|
27
|
+
}, null, 2));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const { sources } = mined;
|
|
31
|
+
console.log(`mined ${mined.lessons.length} policy lesson(s) from ${sources.career_xp_receipts} receipts / ${sources.task_episodes} episodes / ${sources.scorecards} scorecards (${sources.human_reviewed_episodes} human-reviewed)`);
|
|
32
|
+
for (const lesson of mined.lessons) {
|
|
33
|
+
console.log(`- policy-${lesson.id}: ${lesson.lesson}`);
|
|
34
|
+
}
|
|
35
|
+
if (dryRun) {
|
|
36
|
+
console.log('(dry-run: nothing written)');
|
|
37
|
+
} else {
|
|
38
|
+
console.log(`state: ${statePath}`);
|
|
39
|
+
console.log(`lessons.md: ${lessonsMd.written.length ? lessonsMd.written.map((id) => `policy-${id}`).join(', ') : 'no entries written'}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
5
43
|
function lessonAtris(subcommand, ...args) {
|
|
6
44
|
const atrisDir = path.join(process.cwd(), 'atris');
|
|
7
45
|
if (!fs.existsSync(atrisDir)) {
|
|
@@ -9,9 +47,15 @@ function lessonAtris(subcommand, ...args) {
|
|
|
9
47
|
process.exit(1);
|
|
10
48
|
}
|
|
11
49
|
|
|
50
|
+
if (subcommand === 'mine') {
|
|
51
|
+
mineLessons(args);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
12
55
|
if (subcommand !== 'add') {
|
|
13
56
|
console.log('');
|
|
14
57
|
console.log(' Usage: atris lesson add <slug> <pass|fail> "<text>"');
|
|
58
|
+
console.log(' atris lesson mine [--json] [--dry-run]');
|
|
15
59
|
console.log('');
|
|
16
60
|
process.exit(subcommand ? 1 : 0);
|
|
17
61
|
}
|