atris 3.58.5 → 3.58.7
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/README.md +8 -0
- package/atris/policies/engineering-principles.md +129 -0
- package/atris/policies/genesis.md +112 -0
- package/atris/policies/product-design-principles.md +100 -0
- package/atris/skills/design/SKILL.md +3 -1
- package/atris/skills/engines/SKILL.md +3 -3
- package/atris/skills/x-search/SKILL.md +2 -2
- package/atris/skills/youtube/SKILL.md +44 -28
- package/bin/atris.js +56 -3
- package/commands/auth.js +58 -24
- package/commands/brain.js +1 -0
- package/commands/design.js +362 -0
- package/commands/doc-health.js +329 -0
- package/commands/drive.js +32 -0
- package/commands/improve.js +67 -1
- package/commands/land.js +144 -4
- package/commands/learn.js +211 -40
- package/commands/member.js +65 -11
- package/commands/mission.js +37 -7
- package/commands/pulse.js +38 -0
- package/commands/rsi.js +156 -0
- package/commands/task.js +41 -1
- package/commands/workflow.js +15 -14
- package/commands/x-search.js +9 -10
- package/commands/youtube.js +518 -107
- package/lib/apply-gate.js +22 -4
- package/lib/daily-log.js +88 -0
- package/lib/design-api.js +130 -0
- package/lib/engine-ask.js +1 -1
- package/lib/first-minute.js +1 -6
- package/lib/known-commands.js +3 -3
- package/lib/member-context.js +42 -0
- package/lib/rsi-record.js +335 -0
- package/lib/state-detection.js +8 -8
- package/lib/task-db.js +71 -51
- package/lib/task-list-keeper.js +192 -0
- package/lib/todo-fallback.js +9 -3
- package/lib/todo.js +22 -10
- package/mcp/atris-mcp/index.mjs +174 -0
- package/package.json +8 -3
- package/scripts/det/ytnotes +122 -10
- package/utils/auth.js +109 -13
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,24 +403,39 @@ 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
|
|
|
423
|
+
function leftoverClaimableInsight(insight) {
|
|
424
|
+
return /^\[claimable\]/i.test(String(insight || '').trim());
|
|
425
|
+
}
|
|
426
|
+
|
|
278
427
|
/**
|
|
279
428
|
* Harvest learnings from journal Notes sections.
|
|
280
429
|
* Scans recent journals for lines that look like insights.
|
|
281
430
|
*/
|
|
282
|
-
function harvestFromJournals() {
|
|
283
|
-
const
|
|
431
|
+
function harvestFromJournals(deps = {}) {
|
|
432
|
+
const print = typeof deps.output === 'function' ? deps.output : (line = '') => console.log(line);
|
|
433
|
+
const cwd = deps.cwd || process.cwd();
|
|
434
|
+
const atrisDir = path.join(cwd, 'atris');
|
|
284
435
|
const logsDir = path.join(atrisDir, 'logs');
|
|
285
436
|
|
|
286
437
|
if (!fs.existsSync(logsDir)) {
|
|
287
|
-
|
|
438
|
+
print(' No journals found.');
|
|
288
439
|
return;
|
|
289
440
|
}
|
|
290
441
|
|
|
@@ -303,13 +454,14 @@ function harvestFromJournals() {
|
|
|
303
454
|
// Scan last 7 journals for Notes section entries
|
|
304
455
|
const candidates = [];
|
|
305
456
|
for (const logPath of allLogs.slice(0, 7)) {
|
|
306
|
-
const content = fs.readFileSync(logPath, 'utf8');
|
|
457
|
+
const content = fs.readFileSync(logPath, 'utf8').replace(/\r\n/g, '\n');
|
|
307
458
|
const notesMatch = content.match(/## Notes\n([\s\S]*?)(?=\n## |$)/);
|
|
308
459
|
if (notesMatch && notesMatch[1].trim()) {
|
|
309
460
|
const lines = notesMatch[1].trim().split('\n').filter(l => l.startsWith('- '));
|
|
310
461
|
for (const line of lines) {
|
|
311
462
|
// Strip bullet and optional timestamp prefix
|
|
312
463
|
const insight = line.replace(/^- (\d{2}:\d{2} \u2014 )?/, '').trim();
|
|
464
|
+
if (leftoverClaimableInsight(insight)) continue;
|
|
313
465
|
if (insight.length > 10) {
|
|
314
466
|
candidates.push({ insight, source: path.basename(logPath) });
|
|
315
467
|
}
|
|
@@ -318,10 +470,10 @@ function harvestFromJournals() {
|
|
|
318
470
|
}
|
|
319
471
|
|
|
320
472
|
if (candidates.length === 0) {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
473
|
+
print('');
|
|
474
|
+
print(' No harvestable notes found in recent journals.');
|
|
475
|
+
print(' Add notes during "atris review" or write to ## Notes in your journal.');
|
|
476
|
+
print('');
|
|
325
477
|
return;
|
|
326
478
|
}
|
|
327
479
|
|
|
@@ -331,31 +483,38 @@ function harvestFromJournals() {
|
|
|
331
483
|
const fresh = candidates.filter(c => !existingInsights.has(c.insight.toLowerCase()));
|
|
332
484
|
|
|
333
485
|
if (fresh.length === 0) {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
486
|
+
print('');
|
|
487
|
+
print(` Scanned ${candidates.length} journal notes, all already captured.`);
|
|
488
|
+
print('');
|
|
337
489
|
return;
|
|
338
490
|
}
|
|
339
491
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
492
|
+
print('');
|
|
493
|
+
print(` Found ${fresh.length} new ${fresh.length === 1 ? 'note' : 'notes'} to harvest:`);
|
|
494
|
+
print('');
|
|
343
495
|
for (let i = 0; i < fresh.length; i++) {
|
|
344
496
|
const c = fresh[i];
|
|
345
497
|
const isPitfall = /^(don't|never|avoid|watch out|careful)/i.test(c.insight);
|
|
346
498
|
const type = isPitfall ? 'pitfall' : 'pattern';
|
|
347
499
|
const key = c.insight.toLowerCase().replace(/[^a-z0-9\s]/g, '').split(/\s+/).slice(0, 4).join('-');
|
|
348
|
-
|
|
349
|
-
|
|
500
|
+
print(` ${i + 1}. [${type}] ${c.insight}`);
|
|
501
|
+
print(` from: ${c.source}`);
|
|
350
502
|
|
|
351
503
|
try {
|
|
352
|
-
addLearning({ type, key, insight: c.insight, confidence: 6, source: 'review', files: [] });
|
|
353
|
-
|
|
504
|
+
const entry = addLearning({ type, key, insight: c.insight, confidence: 6, source: 'review', files: [] });
|
|
505
|
+
print(` ✓ saved [6/10]`);
|
|
506
|
+
mintRichLearn({
|
|
507
|
+
cwd,
|
|
508
|
+
key: entry.key,
|
|
509
|
+
insight: entry.insight,
|
|
510
|
+
now: deps.now,
|
|
511
|
+
output: print,
|
|
512
|
+
});
|
|
354
513
|
} catch (err) {
|
|
355
|
-
|
|
514
|
+
print(` ✗ ${err.message}`);
|
|
356
515
|
}
|
|
357
516
|
}
|
|
358
|
-
|
|
517
|
+
print('');
|
|
359
518
|
}
|
|
360
519
|
|
|
361
520
|
/**
|
|
@@ -375,10 +534,10 @@ function showLearnHelp() {
|
|
|
375
534
|
console.log('');
|
|
376
535
|
console.log(' Commands:');
|
|
377
536
|
console.log(' (none) Show recent learnings');
|
|
378
|
-
console.log(' add Add a learning interactively');
|
|
379
|
-
console.log(' log <json> Add programmatically (for agents)');
|
|
537
|
+
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.');
|
|
538
|
+
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
539
|
console.log(' search <q> Search learnings by keyword');
|
|
381
|
-
console.log(' harvest Extract learnings from journal Notes');
|
|
540
|
+
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
541
|
console.log(' prune Check for stale/contradictory entries');
|
|
383
542
|
console.log(' stats Show learning statistics');
|
|
384
543
|
console.log(' export Export as markdown');
|
|
@@ -437,5 +596,17 @@ function learnAtris(subcommand, ...args) {
|
|
|
437
596
|
}
|
|
438
597
|
|
|
439
598
|
learnAtris.getLearningCount = getLearningCount;
|
|
599
|
+
learnAtris.learnExperimentSlug = learnExperimentSlug;
|
|
600
|
+
learnAtris.learnExperimentRel = learnExperimentRel;
|
|
601
|
+
learnAtris.learnApplyRel = learnApplyRel;
|
|
602
|
+
learnAtris.learnLessonFromText = learnLessonFromText;
|
|
603
|
+
learnAtris.saveRichLearn = saveRichLearn;
|
|
604
|
+
learnAtris.ensureLearnApply = ensureLearnApply;
|
|
605
|
+
learnAtris.mintRichLearn = mintRichLearn;
|
|
606
|
+
learnAtris.commitAddedLearning = commitAddedLearning;
|
|
607
|
+
learnAtris.commitReviewLearning = commitReviewLearning;
|
|
608
|
+
learnAtris.reviewLearningKey = reviewLearningKey;
|
|
609
|
+
learnAtris.harvestFromJournals = harvestFromJournals;
|
|
610
|
+
learnAtris.logDirect = logDirect;
|
|
440
611
|
|
|
441
612
|
module.exports = learnAtris;
|
package/commands/member.js
CHANGED
|
@@ -10,6 +10,7 @@ const { defaultObjectiveRunner } = require('../lib/default-runner');
|
|
|
10
10
|
const { readJson, writeJson } = require('../lib/json-file');
|
|
11
11
|
const { hasFlag, readFlag, readNumberFlag } = require('../lib/arg-parser');
|
|
12
12
|
const { ensureMemberBundle, memberBundlePresent } = require('../lib/member-scaffold');
|
|
13
|
+
const { memberProcessPrompt, MEMBER_PROCESS_PATH } = require('../lib/member-context');
|
|
13
14
|
|
|
14
15
|
function findWorkspaceBusinessId(startDir = process.cwd()) {
|
|
15
16
|
let dir = path.resolve(startDir);
|
|
@@ -462,6 +463,7 @@ function missionPurpose(paths) {
|
|
|
462
463
|
}
|
|
463
464
|
|
|
464
465
|
const MEMBER_RUN_RUNNABLE_STATUSES = new Set(['planning', 'running', 'ready']);
|
|
466
|
+
const MISSION_TERMINAL_STATUSES = new Set(['complete', 'stopped', 'failed']);
|
|
465
467
|
|
|
466
468
|
function memberRunMissionMap() {
|
|
467
469
|
try {
|
|
@@ -928,12 +930,11 @@ function memberPing(name, ...args) {
|
|
|
928
930
|
process.exit(2);
|
|
929
931
|
}
|
|
930
932
|
const missionMod = require('./mission');
|
|
931
|
-
const terminal = new Set(['complete', 'stopped', 'failed']);
|
|
932
933
|
const candidates = [
|
|
933
934
|
...missionMod.listMissions(process.cwd()),
|
|
934
935
|
...missionMod.listWorktreeRollupMissions(process.cwd()),
|
|
935
936
|
]
|
|
936
|
-
.filter((m) => m && m.owner === name && !
|
|
937
|
+
.filter((m) => m && m.owner === name && !MISSION_TERMINAL_STATUSES.has(m.status))
|
|
937
938
|
.sort((a, b) => String(b.updated_at || b.created_at || '').localeCompare(String(a.updated_at || a.created_at || '')));
|
|
938
939
|
|
|
939
940
|
const taskNote = pingClaimedTaskDialogue(name, text, from);
|
|
@@ -4216,6 +4217,7 @@ function memberActivate(name) {
|
|
|
4216
4217
|
|
|
4217
4218
|
const content = fs.readFileSync(activePath, 'utf8');
|
|
4218
4219
|
const fm = parseFrontmatter(content) || {};
|
|
4220
|
+
const sharedProcess = memberProcessPrompt(process.cwd());
|
|
4219
4221
|
|
|
4220
4222
|
console.log('');
|
|
4221
4223
|
console.log(`Activating: ${fm.name || name} (${fm.role || 'no role'})`);
|
|
@@ -4298,7 +4300,8 @@ function memberActivate(name) {
|
|
|
4298
4300
|
|
|
4299
4301
|
console.log('');
|
|
4300
4302
|
console.log(`Member "${fm.name || name}" activated.`);
|
|
4301
|
-
|
|
4303
|
+
const identityPath = path.relative(process.cwd(), activePath);
|
|
4304
|
+
console.log(`Tell your agent: "You are the ${fm.role || name}. Read ${sharedProcess ? `${MEMBER_PROCESS_PATH}, then ` : ''}${identityPath}. Stay inside this member's permissions."`);
|
|
4302
4305
|
}
|
|
4303
4306
|
|
|
4304
4307
|
// --- UPGRADE subcommand ---
|
|
@@ -4707,6 +4710,49 @@ function memberGoalFromMission(name, ...args) {
|
|
|
4707
4710
|
);
|
|
4708
4711
|
return;
|
|
4709
4712
|
}
|
|
4713
|
+
if (MISSION_TERMINAL_STATUSES.has(lowerCompact(runtime.status || ''))) {
|
|
4714
|
+
// A finished mission must not mint or re-point an active goal; that recreated the
|
|
4715
|
+
// stale-goal defect class (OBL-1961, OBL-2212, OBL-2232) after every retire. --force
|
|
4716
|
+
// cannot bypass this, it only widens the existing-goal match on a living mission.
|
|
4717
|
+
const staleGoals = state.goals.filter((goal) => (
|
|
4718
|
+
goal.source === 'mission' && goal.status === 'active'
|
|
4719
|
+
&& runtime.id && goal.mission_id === runtime.id
|
|
4720
|
+
));
|
|
4721
|
+
const staleList = staleGoals.map((goal) => goal.title || goal.id).join('; ');
|
|
4722
|
+
const ask = staleGoals.length
|
|
4723
|
+
? `Mission ${runtime.id || compactSentence(runtimeFocus, 88)} is ${runtime.status}, so its active goal is stale. Retire ${compactSentence(staleList, 140)} by setting its status in goals.json, or start a living mission and run: atris member goal-from-mission ${name} --force`
|
|
4724
|
+
: `Mission ${runtime.id || compactSentence(runtimeFocus, 88)} is ${runtime.status}. Start a living mission, then run: atris member goal-from-mission ${name} --force`;
|
|
4725
|
+
const logPath = appendMemberGoalLog(paths.memberDir, name, 'Member goal-from-mission blocked', {
|
|
4726
|
+
ask,
|
|
4727
|
+
mission_id: runtime.id || '',
|
|
4728
|
+
mission_status: runtime.status || '',
|
|
4729
|
+
stale_goals: staleGoals.map((goal) => goal.id),
|
|
4730
|
+
});
|
|
4731
|
+
printJsonOrText(
|
|
4732
|
+
{
|
|
4733
|
+
ok: true,
|
|
4734
|
+
action: 'needs_user',
|
|
4735
|
+
member: name,
|
|
4736
|
+
needs_user: true,
|
|
4737
|
+
ask,
|
|
4738
|
+
stale_goals: staleGoals,
|
|
4739
|
+
mission: {
|
|
4740
|
+
north_star: purpose.northStar,
|
|
4741
|
+
runtime_id: runtime.id || null,
|
|
4742
|
+
runtime_status: runtime.status || null,
|
|
4743
|
+
runtime_next: runtime.next || null,
|
|
4744
|
+
},
|
|
4745
|
+
mission_file: paths.missionFile,
|
|
4746
|
+
log_path: logPath,
|
|
4747
|
+
},
|
|
4748
|
+
[
|
|
4749
|
+
`Blocked for ${name}: mission ${runtime.id || 'in now.md'} is ${runtime.status}, a finished mission cannot drive an active goal.`,
|
|
4750
|
+
`Ask: ${ask}`,
|
|
4751
|
+
],
|
|
4752
|
+
asJson,
|
|
4753
|
+
);
|
|
4754
|
+
return;
|
|
4755
|
+
}
|
|
4710
4756
|
// The title IS the mission focus, no boilerplate prefix; the acceptance list already
|
|
4711
4757
|
// says "one bounded step" and the why carries the full sentence.
|
|
4712
4758
|
const title = compactSentence(runtimeFocus, 96);
|
|
@@ -4977,7 +5023,8 @@ function fallbackProposalForGoal(goal, context = {}) {
|
|
|
4977
5023
|
};
|
|
4978
5024
|
}
|
|
4979
5025
|
|
|
4980
|
-
function proposalPromptForGoal(goal, context = {}) {
|
|
5026
|
+
function proposalPromptForGoal(goal, context = {}, cwd = process.cwd()) {
|
|
5027
|
+
const sharedProcess = memberProcessPrompt(cwd);
|
|
4981
5028
|
const files = (context?.evidence?.goal_files?.files || [])
|
|
4982
5029
|
.filter((file) => file.exists && file.excerpt)
|
|
4983
5030
|
.slice(0, 4)
|
|
@@ -5005,6 +5052,7 @@ function proposalPromptForGoal(goal, context = {}) {
|
|
|
5005
5052
|
},
|
|
5006
5053
|
};
|
|
5007
5054
|
return [
|
|
5055
|
+
...(sharedProcess ? [sharedProcess, ''] : []),
|
|
5008
5056
|
'You generate the next bounded Atris member experiment.',
|
|
5009
5057
|
'Read the JSON context and return only JSON with keys: title, proof_target, next_step, verifier, stop_rule.',
|
|
5010
5058
|
'The next_step must be adaptive to the goal/evidence, concrete, receipt-backed, and safe for one bounded tick.',
|
|
@@ -5058,10 +5106,11 @@ async function callAtris2ProposalLlm(goal, context = {}) {
|
|
|
5058
5106
|
const injected = injectedLlmProposal();
|
|
5059
5107
|
if (injected) return injected;
|
|
5060
5108
|
if (process.env.ATRIS_MEMBER_PROPOSAL_LLM !== '1') return null;
|
|
5109
|
+
const prompt = proposalPromptForGoal(goal, context, process.cwd());
|
|
5061
5110
|
try {
|
|
5062
5111
|
const { postTurn } = require('../ax');
|
|
5063
5112
|
const output = { isTTY: false, write() { return true; } };
|
|
5064
|
-
const result = await postTurn(
|
|
5113
|
+
const result = await postTurn(prompt, {
|
|
5065
5114
|
mode: process.env.ATRIS_MEMBER_PROPOSAL_LLM_MODE || 'fast',
|
|
5066
5115
|
route: 'local',
|
|
5067
5116
|
cwd: process.cwd(),
|
|
@@ -5761,6 +5810,7 @@ function emptyObjectiveGeneratorProposal(extra = {}) {
|
|
|
5761
5810
|
updated_at: stampIso(),
|
|
5762
5811
|
status: extra.status || 'empty',
|
|
5763
5812
|
advisory_only: true,
|
|
5813
|
+
auto_task_eligible: extra.auto_task_eligible !== false,
|
|
5764
5814
|
world_model_used: false,
|
|
5765
5815
|
llm_source: extra.llm_source || null,
|
|
5766
5816
|
llm_error: extra.llm_error || null,
|
|
@@ -5893,8 +5943,8 @@ function fallbackObjectiveGeneratorProposal(graph, recommendations, transferPatt
|
|
|
5893
5943
|
),
|
|
5894
5944
|
suggested_member: member,
|
|
5895
5945
|
suggested_patterns: objectivePatternMatches(transferPatterns, proposedObjective),
|
|
5896
|
-
}, { status: 'ok', llm_error: 'llm_not_configured', world_model_used: true });
|
|
5897
|
-
return proposal || emptyObjectiveGeneratorProposal({ status: 'llm_not_configured', llm_error: 'llm_not_configured' });
|
|
5946
|
+
}, { status: 'ok', llm_error: 'llm_not_configured', world_model_used: true, auto_task_eligible: false });
|
|
5947
|
+
return proposal || emptyObjectiveGeneratorProposal({ status: 'llm_not_configured', llm_error: 'llm_not_configured', auto_task_eligible: false });
|
|
5898
5948
|
}
|
|
5899
5949
|
|
|
5900
5950
|
function objectiveGeneratorPrompt(graph, recommendations, transferPatterns = []) {
|
|
@@ -6056,7 +6106,7 @@ async function runObjectiveGeneratorWake(name, paths, { execute = false } = {})
|
|
|
6056
6106
|
proposal = emptyObjectiveGeneratorProposal({ status: llm.error === 'invalid_json' ? 'parse_error' : 'llm_error', llm_source: llm.source, llm_error: llm.error });
|
|
6057
6107
|
proposal.world_model_used = true;
|
|
6058
6108
|
} else {
|
|
6059
|
-
reason = 'heuristic_objective_proposal_written';
|
|
6109
|
+
reason = execute ? 'heuristic_objective_proposal_written' : 'heuristic_objective_proposal_dry_run';
|
|
6060
6110
|
proposal = fallbackObjectiveGeneratorProposal(graph, recommendations, transferPatterns);
|
|
6061
6111
|
}
|
|
6062
6112
|
}
|
|
@@ -6065,7 +6115,7 @@ async function runObjectiveGeneratorWake(name, paths, { execute = false } = {})
|
|
|
6065
6115
|
proposal.suggested_patterns = objectivePatternMatches(transferPatterns, proposal.proposed_objective);
|
|
6066
6116
|
}
|
|
6067
6117
|
|
|
6068
|
-
if (execute && proposal?.status === 'ok' && Number(proposal.overall_score) > 7) {
|
|
6118
|
+
if (execute && proposal?.status === 'ok' && proposal.auto_task_eligible !== false && Number(proposal.overall_score) > 7) {
|
|
6069
6119
|
createdTask = createAutoObjectiveTask(proposal);
|
|
6070
6120
|
proposal.created_task = createdTask.ok ? {
|
|
6071
6121
|
id: createdTask.task_id || null,
|
|
@@ -6081,7 +6131,8 @@ async function runObjectiveGeneratorWake(name, paths, { execute = false } = {})
|
|
|
6081
6131
|
}
|
|
6082
6132
|
|
|
6083
6133
|
const proposalsPath = objectiveGeneratorProposalsPath(root);
|
|
6084
|
-
|
|
6134
|
+
const proposalsWritten = Boolean(execute);
|
|
6135
|
+
if (proposalsWritten) {
|
|
6085
6136
|
fs.mkdirSync(path.dirname(proposalsPath), { recursive: true });
|
|
6086
6137
|
fs.writeFileSync(proposalsPath, JSON.stringify(proposal, null, 2) + '\n', 'utf8');
|
|
6087
6138
|
}
|
|
@@ -6111,6 +6162,7 @@ async function runObjectiveGeneratorWake(name, paths, { execute = false } = {})
|
|
|
6111
6162
|
llm_successful: Boolean(llm?.source && llm?.proposal && proposal.status === 'ok'),
|
|
6112
6163
|
llm_error: llm?.error || proposal.llm_error || null,
|
|
6113
6164
|
proposals_path: path.relative(root, proposalsPath),
|
|
6165
|
+
proposals_written: proposalsWritten,
|
|
6114
6166
|
task_creation_threshold: 7,
|
|
6115
6167
|
task_created: Boolean(createdTask?.ok),
|
|
6116
6168
|
created_task: proposal.created_task,
|
|
@@ -6128,7 +6180,7 @@ async function runObjectiveGeneratorWake(name, paths, { execute = false } = {})
|
|
|
6128
6180
|
score: proposal.overall_score || '',
|
|
6129
6181
|
task: proposal.created_task?.ref || '',
|
|
6130
6182
|
receipt: path.relative(root, receiptPath),
|
|
6131
|
-
output: path.relative(root, proposalsPath),
|
|
6183
|
+
output: proposalsWritten ? path.relative(root, proposalsPath) : '',
|
|
6132
6184
|
});
|
|
6133
6185
|
|
|
6134
6186
|
return {
|
|
@@ -7817,6 +7869,7 @@ const WAKE_REASON_TEXT = {
|
|
|
7817
7869
|
auto_improver_task_create_failed: 'it found an improvement but could not put the task on the board',
|
|
7818
7870
|
heuristic_cross_domain_proof_written: 'it wrote a cross-domain proof using its built-in heuristics',
|
|
7819
7871
|
heuristic_objective_proposal_written: 'it drafted an objective proposal using its built-in heuristics',
|
|
7872
|
+
heuristic_objective_proposal_dry_run: 'it previewed an objective proposal using its built-in heuristics, without writing it',
|
|
7820
7873
|
install_requires_clean_git: 'installing needs a clean git tree first',
|
|
7821
7874
|
insufficient_world_model_data: 'its world model is too thin to act on yet',
|
|
7822
7875
|
llm_json_parse_failed: 'the model reply did not parse, so it stopped rather than act on garbage',
|
|
@@ -9232,6 +9285,7 @@ async function memberCommand(subcommand, ...args) {
|
|
|
9232
9285
|
}
|
|
9233
9286
|
|
|
9234
9287
|
module.exports = {
|
|
9288
|
+
proposalPromptForGoal,
|
|
9235
9289
|
memberCommand,
|
|
9236
9290
|
findAllMembers,
|
|
9237
9291
|
findWorkspaceBusinessId,
|