atris 3.58.5 → 3.58.6
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/atris/skills/x-search/SKILL.md +2 -2
- package/atris/skills/youtube/SKILL.md +44 -28
- package/commands/auth.js +52 -24
- package/commands/learn.js +205 -39
- package/commands/workflow.js +4 -8
- package/commands/x-search.js +9 -10
- package/commands/youtube.js +444 -101
- package/package.json +1 -1
- package/scripts/det/ytnotes +82 -5
- package/utils/auth.js +47 -6
package/commands/learn.js
CHANGED
|
@@ -12,6 +12,17 @@ const {
|
|
|
12
12
|
getStats,
|
|
13
13
|
exportMarkdown,
|
|
14
14
|
} = require('../lib/learnings');
|
|
15
|
+
const applyGate = require('../lib/apply-gate');
|
|
16
|
+
const {
|
|
17
|
+
fileTeachExperiment,
|
|
18
|
+
extractTeachNumbers,
|
|
19
|
+
extractTeachMechanisms,
|
|
20
|
+
isThinTeachLesson,
|
|
21
|
+
printLearnerCheckGate,
|
|
22
|
+
proveSavedLearnerBaseline,
|
|
23
|
+
} = require('./youtube');
|
|
24
|
+
|
|
25
|
+
const KEEP_RULE = 'keep only if measure.py moves 0→1. scores 1 only when the fixture contains the check tokens.';
|
|
15
26
|
|
|
16
27
|
function showRecent(limit = 20) {
|
|
17
28
|
const learnings = loadLearnings()
|
|
@@ -149,7 +160,52 @@ function showPrune() {
|
|
|
149
160
|
console.log('');
|
|
150
161
|
}
|
|
151
162
|
|
|
152
|
-
function
|
|
163
|
+
function commitAddedLearning({ type, key, insight, confidence, source, files } = {}, deps = {}) {
|
|
164
|
+
const print = typeof deps.output === 'function' ? deps.output : (line = '') => console.log(line);
|
|
165
|
+
const cwd = deps.cwd || process.cwd();
|
|
166
|
+
const entry = addLearning({ type, key, insight, confidence, source, files });
|
|
167
|
+
print('');
|
|
168
|
+
print(` ✓ Saved: [${entry.confidence}/10] ${entry.type}/${entry.key}`);
|
|
169
|
+
print(` "${entry.insight}"`);
|
|
170
|
+
print('');
|
|
171
|
+
const baseline = mintRichLearn({
|
|
172
|
+
cwd,
|
|
173
|
+
key: entry.key,
|
|
174
|
+
insight: entry.insight,
|
|
175
|
+
now: deps.now,
|
|
176
|
+
output: print,
|
|
177
|
+
});
|
|
178
|
+
return { entry, baseline };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function reviewLearningType(insight) {
|
|
182
|
+
return /^(don't|never|avoid|watch out|careful)/i.test(String(insight || '')) ? 'pitfall' : 'pattern';
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function reviewLearningKey(insight) {
|
|
186
|
+
return String(insight || '').toLowerCase().replace(/[^a-z0-9\s]/g, '').split(/\s+/).slice(0, 4).join('-');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function commitReviewLearning(insight, deps = {}) {
|
|
190
|
+
const print = typeof deps.output === 'function' ? deps.output : (line = '') => console.log(line);
|
|
191
|
+
const cwd = deps.cwd || process.cwd();
|
|
192
|
+
const text = String(insight || '').trim();
|
|
193
|
+
if (!text) return { entry: null, baseline: 0 };
|
|
194
|
+
const type = reviewLearningType(text);
|
|
195
|
+
const key = reviewLearningKey(text);
|
|
196
|
+
const entry = addLearning({ type, key, insight: text, confidence: 7, source: 'review', files: [] });
|
|
197
|
+
print(`✓ Saved to learnings: [7/10] ${entry.type}/${entry.key}`);
|
|
198
|
+
const baseline = mintRichLearn({
|
|
199
|
+
cwd,
|
|
200
|
+
key: entry.key,
|
|
201
|
+
insight: entry.insight,
|
|
202
|
+
now: deps.now,
|
|
203
|
+
output: print,
|
|
204
|
+
});
|
|
205
|
+
return { entry, baseline };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function interactiveAdd(deps = {}) {
|
|
153
209
|
const rl = readline.createInterface({
|
|
154
210
|
input: process.stdin,
|
|
155
211
|
output: process.stdout,
|
|
@@ -214,11 +270,11 @@ function interactiveAdd() {
|
|
|
214
270
|
return;
|
|
215
271
|
}
|
|
216
272
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
273
|
+
commitAddedLearning({ type, key, insight, confidence, source, files }, {
|
|
274
|
+
cwd: deps.cwd || process.cwd(),
|
|
275
|
+
now: deps.now,
|
|
276
|
+
output: deps.output,
|
|
277
|
+
});
|
|
222
278
|
rl.close();
|
|
223
279
|
} catch (err) {
|
|
224
280
|
console.log(` ✗ Error: ${err.message}`);
|
|
@@ -239,24 +295,104 @@ function printLearnLogSchema(stream = console.error) {
|
|
|
239
295
|
for (const line of learnLogSchemaLines()) stream(` ${line}`);
|
|
240
296
|
}
|
|
241
297
|
|
|
298
|
+
function learnExperimentSlug(key) {
|
|
299
|
+
return `learn-${applyGate.applySlug(key)}`;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function learnExperimentRel(key) {
|
|
303
|
+
return `atris/experiments/${learnExperimentSlug(key)}`;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function learnApplyRel(key) {
|
|
307
|
+
return applyGate.applySidecarRel('learn', applyGate.applySlug(key));
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function learnLessonFromText(text) {
|
|
311
|
+
const body = String(text || '');
|
|
312
|
+
return {
|
|
313
|
+
numbers: extractTeachNumbers(body),
|
|
314
|
+
mechanisms: extractTeachMechanisms(body),
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function saveRichLearn({ cwd, key, insight } = {}) {
|
|
319
|
+
const lesson = learnLessonFromText(insight);
|
|
320
|
+
if (isThinTeachLesson(lesson)) {
|
|
321
|
+
return { thin: true, packRel: null, lesson };
|
|
322
|
+
}
|
|
323
|
+
if (cwd) fs.mkdirSync(path.join(cwd, 'atris', 'wiki'), { recursive: true });
|
|
324
|
+
const packRel = fileTeachExperiment({
|
|
325
|
+
cwd,
|
|
326
|
+
lesson,
|
|
327
|
+
slug: key ? learnExperimentSlug(key) : null,
|
|
328
|
+
applyRel: key ? learnApplyRel(key) : null,
|
|
329
|
+
});
|
|
330
|
+
return { thin: false, packRel, lesson };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function ensureLearnApply({ cwd, key, packRel, now, output } = {}) {
|
|
334
|
+
const pack = packRel || (key ? learnExperimentRel(key) : null);
|
|
335
|
+
const slug = pack ? path.basename(pack) : null;
|
|
336
|
+
return applyGate.ensureApply({
|
|
337
|
+
cwd,
|
|
338
|
+
source: key ? `learn:${key}` : 'learn',
|
|
339
|
+
rel: key ? learnApplyRel(key) : null,
|
|
340
|
+
now,
|
|
341
|
+
output,
|
|
342
|
+
incompleteMessage: slug
|
|
343
|
+
? `next: atris experiments keep ${slug}`
|
|
344
|
+
: applyGate.ephemeralApplyMessage('learning'),
|
|
345
|
+
required: false,
|
|
346
|
+
change: pack ? `apply ${pack}` : undefined,
|
|
347
|
+
receipt: pack ? KEEP_RULE : undefined,
|
|
348
|
+
journalLine: pack ? `- [claimable] apply: ${pack}. ${KEEP_RULE}` : undefined,
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function mintRichLearn({ cwd, key, insight, now, output } = {}) {
|
|
353
|
+
const print = typeof output === 'function' ? output : (line = '') => console.log(line);
|
|
354
|
+
const saved = saveRichLearn({ cwd, key, insight });
|
|
355
|
+
if (saved.thin) {
|
|
356
|
+
printLearnerCheckGate(print, saved.lesson, { includeCheck: true });
|
|
357
|
+
return 0;
|
|
358
|
+
}
|
|
359
|
+
ensureLearnApply({
|
|
360
|
+
cwd,
|
|
361
|
+
key,
|
|
362
|
+
packRel: saved.packRel,
|
|
363
|
+
now,
|
|
364
|
+
output: print,
|
|
365
|
+
});
|
|
366
|
+
return proveSavedLearnerBaseline({
|
|
367
|
+
cwd,
|
|
368
|
+
applyRel: key ? learnApplyRel(key) : null,
|
|
369
|
+
lesson: saved.lesson,
|
|
370
|
+
output: print,
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
242
374
|
/**
|
|
243
375
|
* Non-interactive log: `atris learn log '{"type":"pattern","key":"...","insight":"...","confidence":8,"source":"observed"}'`
|
|
244
376
|
* For agents and scripts, no prompts, no quality gate.
|
|
245
377
|
* Aliases: title→key, detail→insight.
|
|
246
378
|
*/
|
|
247
|
-
function logDirect(jsonStr) {
|
|
379
|
+
function logDirect(jsonStr, deps = {}) {
|
|
380
|
+
const error = typeof deps.error === 'function' ? deps.error : (line = '') => console.error(line);
|
|
381
|
+
const print = typeof deps.output === 'function' ? deps.output : (line = '') => console.log(line);
|
|
382
|
+
const exit = typeof deps.exit === 'function' ? deps.exit : (code) => process.exit(code);
|
|
383
|
+
const cwd = deps.cwd || process.cwd();
|
|
248
384
|
if (!jsonStr) {
|
|
249
|
-
|
|
250
|
-
printLearnLogSchema();
|
|
251
|
-
|
|
385
|
+
error(' ✗ Usage: atris learn log \'<json>\'');
|
|
386
|
+
printLearnLogSchema(error);
|
|
387
|
+
return exit(1);
|
|
252
388
|
}
|
|
253
389
|
let data;
|
|
254
390
|
try {
|
|
255
391
|
data = JSON.parse(jsonStr);
|
|
256
392
|
} catch (err) {
|
|
257
|
-
|
|
258
|
-
printLearnLogSchema();
|
|
259
|
-
|
|
393
|
+
error(` ✗ Invalid JSON: ${err.message}`);
|
|
394
|
+
printLearnLogSchema(error);
|
|
395
|
+
return exit(1);
|
|
260
396
|
}
|
|
261
397
|
try {
|
|
262
398
|
const entry = addLearning({
|
|
@@ -267,11 +403,20 @@ function logDirect(jsonStr) {
|
|
|
267
403
|
source: data.source || 'observed',
|
|
268
404
|
files: data.files || [],
|
|
269
405
|
});
|
|
270
|
-
|
|
406
|
+
print(` ✓ [${entry.confidence}/10] ${entry.type}/${entry.key}`);
|
|
407
|
+
const baseline = mintRichLearn({
|
|
408
|
+
cwd,
|
|
409
|
+
key: entry.key,
|
|
410
|
+
insight: entry.insight,
|
|
411
|
+
now: deps.now,
|
|
412
|
+
output: print,
|
|
413
|
+
});
|
|
414
|
+
if (baseline !== 0) return exit(baseline);
|
|
415
|
+
return 0;
|
|
271
416
|
} catch (err) {
|
|
272
|
-
|
|
273
|
-
printLearnLogSchema();
|
|
274
|
-
|
|
417
|
+
error(` ✗ ${err.message}`);
|
|
418
|
+
printLearnLogSchema(error);
|
|
419
|
+
return exit(1);
|
|
275
420
|
}
|
|
276
421
|
}
|
|
277
422
|
|
|
@@ -279,12 +424,14 @@ function logDirect(jsonStr) {
|
|
|
279
424
|
* Harvest learnings from journal Notes sections.
|
|
280
425
|
* Scans recent journals for lines that look like insights.
|
|
281
426
|
*/
|
|
282
|
-
function harvestFromJournals() {
|
|
283
|
-
const
|
|
427
|
+
function harvestFromJournals(deps = {}) {
|
|
428
|
+
const print = typeof deps.output === 'function' ? deps.output : (line = '') => console.log(line);
|
|
429
|
+
const cwd = deps.cwd || process.cwd();
|
|
430
|
+
const atrisDir = path.join(cwd, 'atris');
|
|
284
431
|
const logsDir = path.join(atrisDir, 'logs');
|
|
285
432
|
|
|
286
433
|
if (!fs.existsSync(logsDir)) {
|
|
287
|
-
|
|
434
|
+
print(' No journals found.');
|
|
288
435
|
return;
|
|
289
436
|
}
|
|
290
437
|
|
|
@@ -318,10 +465,10 @@ function harvestFromJournals() {
|
|
|
318
465
|
}
|
|
319
466
|
|
|
320
467
|
if (candidates.length === 0) {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
468
|
+
print('');
|
|
469
|
+
print(' No harvestable notes found in recent journals.');
|
|
470
|
+
print(' Add notes during "atris review" or write to ## Notes in your journal.');
|
|
471
|
+
print('');
|
|
325
472
|
return;
|
|
326
473
|
}
|
|
327
474
|
|
|
@@ -331,31 +478,38 @@ function harvestFromJournals() {
|
|
|
331
478
|
const fresh = candidates.filter(c => !existingInsights.has(c.insight.toLowerCase()));
|
|
332
479
|
|
|
333
480
|
if (fresh.length === 0) {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
481
|
+
print('');
|
|
482
|
+
print(` Scanned ${candidates.length} journal notes, all already captured.`);
|
|
483
|
+
print('');
|
|
337
484
|
return;
|
|
338
485
|
}
|
|
339
486
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
487
|
+
print('');
|
|
488
|
+
print(` Found ${fresh.length} new ${fresh.length === 1 ? 'note' : 'notes'} to harvest:`);
|
|
489
|
+
print('');
|
|
343
490
|
for (let i = 0; i < fresh.length; i++) {
|
|
344
491
|
const c = fresh[i];
|
|
345
492
|
const isPitfall = /^(don't|never|avoid|watch out|careful)/i.test(c.insight);
|
|
346
493
|
const type = isPitfall ? 'pitfall' : 'pattern';
|
|
347
494
|
const key = c.insight.toLowerCase().replace(/[^a-z0-9\s]/g, '').split(/\s+/).slice(0, 4).join('-');
|
|
348
|
-
|
|
349
|
-
|
|
495
|
+
print(` ${i + 1}. [${type}] ${c.insight}`);
|
|
496
|
+
print(` from: ${c.source}`);
|
|
350
497
|
|
|
351
498
|
try {
|
|
352
|
-
addLearning({ type, key, insight: c.insight, confidence: 6, source: 'review', files: [] });
|
|
353
|
-
|
|
499
|
+
const entry = addLearning({ type, key, insight: c.insight, confidence: 6, source: 'review', files: [] });
|
|
500
|
+
print(` ✓ saved [6/10]`);
|
|
501
|
+
mintRichLearn({
|
|
502
|
+
cwd,
|
|
503
|
+
key: entry.key,
|
|
504
|
+
insight: entry.insight,
|
|
505
|
+
now: deps.now,
|
|
506
|
+
output: print,
|
|
507
|
+
});
|
|
354
508
|
} catch (err) {
|
|
355
|
-
|
|
509
|
+
print(` ✗ ${err.message}`);
|
|
356
510
|
}
|
|
357
511
|
}
|
|
358
|
-
|
|
512
|
+
print('');
|
|
359
513
|
}
|
|
360
514
|
|
|
361
515
|
/**
|
|
@@ -375,10 +529,10 @@ function showLearnHelp() {
|
|
|
375
529
|
console.log('');
|
|
376
530
|
console.log(' Commands:');
|
|
377
531
|
console.log(' (none) Show recent learnings');
|
|
378
|
-
console.log(' add Add a learning interactively');
|
|
379
|
-
console.log(' log <json> Add programmatically (for agents)');
|
|
532
|
+
console.log(' add Add a learning interactively. A rich insight mints one apply plus a failing measure.py. A thin insight prints check: fill this.');
|
|
533
|
+
console.log(' log <json> Add programmatically (for agents). A rich insight (number or named mechanism) mints one apply plus a failing measure.py. A thin insight prints check: fill this.');
|
|
380
534
|
console.log(' search <q> Search learnings by keyword');
|
|
381
|
-
console.log(' harvest Extract learnings from journal Notes');
|
|
535
|
+
console.log(' harvest Extract learnings from journal Notes. A rich insight mints one apply plus a failing measure.py. A thin insight prints check: fill this.');
|
|
382
536
|
console.log(' prune Check for stale/contradictory entries');
|
|
383
537
|
console.log(' stats Show learning statistics');
|
|
384
538
|
console.log(' export Export as markdown');
|
|
@@ -437,5 +591,17 @@ function learnAtris(subcommand, ...args) {
|
|
|
437
591
|
}
|
|
438
592
|
|
|
439
593
|
learnAtris.getLearningCount = getLearningCount;
|
|
594
|
+
learnAtris.learnExperimentSlug = learnExperimentSlug;
|
|
595
|
+
learnAtris.learnExperimentRel = learnExperimentRel;
|
|
596
|
+
learnAtris.learnApplyRel = learnApplyRel;
|
|
597
|
+
learnAtris.learnLessonFromText = learnLessonFromText;
|
|
598
|
+
learnAtris.saveRichLearn = saveRichLearn;
|
|
599
|
+
learnAtris.ensureLearnApply = ensureLearnApply;
|
|
600
|
+
learnAtris.mintRichLearn = mintRichLearn;
|
|
601
|
+
learnAtris.commitAddedLearning = commitAddedLearning;
|
|
602
|
+
learnAtris.commitReviewLearning = commitReviewLearning;
|
|
603
|
+
learnAtris.reviewLearningKey = reviewLearningKey;
|
|
604
|
+
learnAtris.harvestFromJournals = harvestFromJournals;
|
|
605
|
+
learnAtris.logDirect = logDirect;
|
|
440
606
|
|
|
441
607
|
module.exports = learnAtris;
|
package/commands/workflow.js
CHANGED
|
@@ -19,6 +19,7 @@ const { startFirstTalk } = require('../lib/context-gatherer');
|
|
|
19
19
|
const { isNonInteractive } = require('../lib/noninteractive');
|
|
20
20
|
const { loadContext } = require('../lib/state-detection');
|
|
21
21
|
const { buildToolResultBody } = require('../lib/tool-result-encode');
|
|
22
|
+
const { commitReviewLearning } = require('./learn');
|
|
22
23
|
|
|
23
24
|
function wrapWorkflowText(text, width = 76) {
|
|
24
25
|
const normalized = String(text || '').replace(/\s+/g, ' ').trim();
|
|
@@ -1786,15 +1787,10 @@ async function reviewAtris() {
|
|
|
1786
1787
|
console.log(`✓ Logged to journal: ${learning}`);
|
|
1787
1788
|
}
|
|
1788
1789
|
|
|
1789
|
-
// Also log to structured learnings (if learnings module exists)
|
|
1790
1790
|
try {
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
const type = /^(don't|never|avoid|watch out|careful)/i.test(insight) ? 'pitfall' : 'pattern';
|
|
1795
|
-
const key = insight.toLowerCase().replace(/[^a-z0-9\s]/g, '').split(/\s+/).slice(0, 4).join('-');
|
|
1796
|
-
addLearning({ type, key, insight, confidence: 7, source: 'review', files: [] });
|
|
1797
|
-
console.log(`✓ Saved to learnings: [7/10] ${type}/${key}`);
|
|
1791
|
+
commitReviewLearning(answer.trim(), {
|
|
1792
|
+
cwd: process.cwd(),
|
|
1793
|
+
});
|
|
1798
1794
|
} catch {
|
|
1799
1795
|
// learnings module not available, so skip silently
|
|
1800
1796
|
}
|
package/commands/x-search.js
CHANGED
|
@@ -32,7 +32,7 @@ function showXSearchHelp(output = console.log, commandName = 'atris x-search') {
|
|
|
32
32
|
output('Prints to stdout. Rich ephemeral prints one apply next-step, then hands off to atris youtube search (no files).');
|
|
33
33
|
output('--save files a brief only when the result is rich.');
|
|
34
34
|
output('unsave deletes the filed brief, apply stub, and matching experiment pack (no paid calls).');
|
|
35
|
-
output('Empty or failed search
|
|
35
|
+
output('Empty or failed search prints credits refunded only when the server marks a refund.');
|
|
36
36
|
output('');
|
|
37
37
|
output('Options:');
|
|
38
38
|
output(' --limit <n> Max results hint (search only)');
|
|
@@ -327,13 +327,6 @@ function xSearchCredits(data) {
|
|
|
327
327
|
return { used, remaining, refunded };
|
|
328
328
|
}
|
|
329
329
|
|
|
330
|
-
function creditsWereRefunded(credits) {
|
|
331
|
-
if (!credits) return false;
|
|
332
|
-
if (credits.used === 0) return true;
|
|
333
|
-
if (credits.refunded === true) return true;
|
|
334
|
-
return typeof credits.refunded === 'number' && credits.refunded > 0;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
330
|
function creditsRefundedExplicitly(credits) {
|
|
338
331
|
if (!credits) return false;
|
|
339
332
|
if (credits.refunded === true) return true;
|
|
@@ -345,7 +338,7 @@ function formatCreditsLines(credits) {
|
|
|
345
338
|
if (credits.used !== undefined || credits.remaining !== undefined) {
|
|
346
339
|
lines.push(`Credits: ${credits.used !== undefined ? credits.used : '?'} used, ${credits.remaining !== undefined ? credits.remaining : '?'} remaining`);
|
|
347
340
|
}
|
|
348
|
-
if (
|
|
341
|
+
if (creditsRefundedExplicitly(credits)) {
|
|
349
342
|
lines.push('credits refunded');
|
|
350
343
|
}
|
|
351
344
|
return lines;
|
|
@@ -360,7 +353,7 @@ function xSearchFailureError(result) {
|
|
|
360
353
|
? ' xAI is unavailable; retry in a few seconds.'
|
|
361
354
|
: '';
|
|
362
355
|
const credits = xSearchCredits(result.data);
|
|
363
|
-
const refundHint = result.status === 502 &&
|
|
356
|
+
const refundHint = result.status === 502 && creditsRefundedExplicitly(credits)
|
|
364
357
|
? ' credits refunded.'
|
|
365
358
|
: '';
|
|
366
359
|
const lines = [`X search failed (${result.status}): ${resultErrorText(result)}.${hint}${refundHint}`];
|
|
@@ -398,6 +391,12 @@ async function runXSearch(options, deps = {}) {
|
|
|
398
391
|
forceMint: true,
|
|
399
392
|
});
|
|
400
393
|
if (remint?.ok && remint.token) {
|
|
394
|
+
if (!options.json) {
|
|
395
|
+
const print = typeof deps.output === 'function' ? deps.output : () => {};
|
|
396
|
+
for (const line of formatCreditsLines(xSearchCredits(result.data))) {
|
|
397
|
+
print(line);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
401
400
|
auth = remint;
|
|
402
401
|
result = await call(auth.token);
|
|
403
402
|
}
|