atris 3.16.0 → 3.17.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.
Files changed (59) hide show
  1. package/README.md +33 -7
  2. package/atris/skills/atris/SKILL.md +15 -2
  3. package/atris/skills/atris-feedback/SKILL.md +7 -0
  4. package/atris/skills/design/SKILL.md +29 -2
  5. package/atris/skills/engines/SKILL.md +44 -0
  6. package/atris/skills/flow/SKILL.md +1 -1
  7. package/atris/skills/wake/SKILL.md +37 -0
  8. package/atris/skills/youtube/SKILL.md +13 -39
  9. package/atris/team/validator/MEMBER.md +1 -0
  10. package/atris/wiki/concepts/agent-activation-contract.md +3 -3
  11. package/atris/wiki/concepts/workspace-initialization-contract.md +3 -3
  12. package/atris/wiki/index.md +1 -0
  13. package/atris.md +43 -19
  14. package/bin/atris.js +446 -43
  15. package/commands/agent-spawn.js +480 -0
  16. package/commands/analytics.js +6 -3
  17. package/commands/apps.js +11 -0
  18. package/commands/autopilot.js +466 -20
  19. package/commands/brain.js +74 -7
  20. package/commands/brainstorm.js +9 -58
  21. package/commands/clean.js +1 -4
  22. package/commands/compile.js +574 -0
  23. package/commands/console.js +8 -3
  24. package/commands/deck.js +135 -0
  25. package/commands/init.js +22 -11
  26. package/commands/lesson.js +76 -0
  27. package/commands/member.js +252 -48
  28. package/commands/mission.js +405 -13
  29. package/commands/now.js +4 -2
  30. package/commands/probe.js +444 -0
  31. package/commands/pulse.js +504 -0
  32. package/commands/radar.js +1 -0
  33. package/commands/recap.js +233 -0
  34. package/commands/run.js +615 -22
  35. package/commands/skill.js +6 -2
  36. package/commands/slop.js +173 -0
  37. package/commands/spaceship.js +39 -0
  38. package/commands/sync.js +0 -2
  39. package/commands/task.js +458 -43
  40. package/commands/verify.js +7 -3
  41. package/lib/activity-stream.js +166 -0
  42. package/lib/auto-accept-certified.js +23 -1
  43. package/lib/context-gatherer.js +170 -0
  44. package/lib/escape-regexp.js +13 -0
  45. package/lib/file-ops.js +6 -3
  46. package/lib/journal.js +1 -1
  47. package/lib/lesson-contradiction.js +113 -0
  48. package/lib/policy-lessons.js +3 -2
  49. package/lib/pulse.js +401 -0
  50. package/lib/runner-command.js +156 -0
  51. package/lib/slides-deck.js +236 -0
  52. package/lib/state-detection.js +40 -3
  53. package/lib/task-db.js +101 -4
  54. package/lib/task-proof.js +1 -1
  55. package/lib/todo-fallback.js +2 -1
  56. package/lib/todo-sections.js +33 -0
  57. package/package.json +1 -2
  58. package/utils/api.js +14 -2
  59. package/atris/atrisDev.md +0 -717
package/commands/brain.js CHANGED
@@ -3,6 +3,8 @@ const path = require('path');
3
3
  const crypto = require('crypto');
4
4
  const { spawnSync } = require('child_process');
5
5
  const { refreshNowFile } = require('./now');
6
+ const escapeRegExp = require('../lib/escape-regexp');
7
+ const { hasRenderedSections, isOpenSection, isDoneSection } = require('../lib/todo-sections');
6
8
 
7
9
  const GENERATED_START = '<!-- ATRIS_BRAIN_COMPILE:START -->';
8
10
  const GENERATED_END = '<!-- ATRIS_BRAIN_COMPILE:END -->';
@@ -296,7 +298,8 @@ function resolveStateRoot(root) {
296
298
 
297
299
  function countTodoItems(todoText) {
298
300
  const text = String(todoText || '');
299
- const hasRenderedSections = /^##\s+(Backlog|In Progress|Blocked|Completed)\s*$/m.test(text);
301
+ // Section classification (incl. emoji-decorated headings) lives in lib/todo-sections.
302
+ const rendered = hasRenderedSections(text);
300
303
  let section = null;
301
304
  let unchecked = 0;
302
305
  let checked = 0;
@@ -317,22 +320,46 @@ function countTodoItems(todoText) {
317
320
  const isTitled = /^\s*-\s+(?:\[[ xX]\]\s+)?\*\*[^*]+:?\*\*/.test(line);
318
321
  if (isUnchecked) unchecked += 1;
319
322
  if (isChecked) checked += 1;
320
- if (!hasRenderedSections && (isUnchecked || (isTitled && !isChecked))) legacyOpen += 1;
323
+ if (!rendered && (isUnchecked || (isTitled && !isChecked))) legacyOpen += 1;
321
324
  if (!isTitled) continue;
322
325
 
323
326
  titled += 1;
324
- if (hasRenderedSections && ['Backlog', 'In Progress'].includes(section)) renderedOpen += 1;
325
- if (hasRenderedSections && section === 'Completed') renderedDone += 1;
327
+ if (rendered && isOpenSection(section)) renderedOpen += 1;
328
+ if (rendered && isDoneSection(section)) renderedDone += 1;
326
329
  }
327
330
 
328
331
  return {
329
- open: hasRenderedSections ? renderedOpen : legacyOpen,
332
+ open: rendered ? renderedOpen : legacyOpen,
330
333
  checked,
331
334
  titled,
332
- done: hasRenderedSections ? renderedDone : checked + (text.match(/~~|DONE|✅/g) || []).length,
335
+ done: rendered ? renderedDone : checked + (text.match(/~~|DONE|✅/g) || []).length,
333
336
  };
334
337
  }
335
338
 
339
+ function parseTodoEndgame(todoText) {
340
+ const text = String(todoText || '');
341
+ const fields = {};
342
+ let inEndgame = false;
343
+
344
+ for (const line of text.split(/\r?\n/)) {
345
+ const heading = line.match(/^##\s+(.+?)\s*$/);
346
+ if (heading) {
347
+ inEndgame = heading[1].trim().toLowerCase() === 'endgame';
348
+ continue;
349
+ }
350
+ if (!inEndgame) continue;
351
+
352
+ const field = line.match(/^\*\*(Slug|Horizon|Source):\*\*\s*(.*?)\s*$/i);
353
+ if (field) fields[field[1].toLowerCase()] = field[2].trim();
354
+ }
355
+
356
+ const slug = fields.slug || null;
357
+ const horizon = fields.horizon || null;
358
+ const source = fields.source || null;
359
+ if (!slug && !horizon && !source) return null;
360
+ return { slug, horizon, source };
361
+ }
362
+
336
363
  const EXECUTABLE_TASK_STATUSES = new Set(['open', 'claimed']);
337
364
  const COMPLETED_TASK_STATUSES = new Set(['done', 'completed', 'accepted']);
338
365
 
@@ -350,15 +377,33 @@ function isCertifiedReviewTask(task) {
350
377
  return Boolean(metadata.agent_certified || review.agent_certified || passCount >= 2);
351
378
  }
352
379
 
380
+ function isAgentNeededReviewTask(task) {
381
+ if (String(task?.status || '').toLowerCase() !== 'review') return false;
382
+ if (isCertifiedReviewTask(task)) return false;
383
+ const metadata = task.metadata || {};
384
+ const review = task.review || {};
385
+ const approvalStatus = String(metadata.approval_status || review.approval_status || 'pending').toLowerCase();
386
+ if (approvalStatus && approvalStatus !== 'pending') return false;
387
+ const passCount = Number(metadata.agent_review_pass_count || review.agent_review_pass_count || 0);
388
+ return passCount < 2;
389
+ }
390
+
353
391
  function summarizeTaskProjection(root) {
354
392
  const tasks = readTaskProjectionTasks(root);
355
393
  if (!tasks) return null;
356
394
 
357
395
  const counts = {};
358
396
  const certifiedReviewTasks = [];
397
+ const agentNeededReviewTasks = [];
359
398
  for (const task of tasks) {
360
399
  const status = String(task?.status || '').toLowerCase();
361
400
  counts[status] = (counts[status] || 0) + 1;
401
+ if (isAgentNeededReviewTask(task)) {
402
+ agentNeededReviewTasks.push({
403
+ ref: task.display_id || task.legacy_ref || task.id,
404
+ title: task.title || 'Untitled task',
405
+ });
406
+ }
362
407
  if (isCertifiedReviewTask(task)) {
363
408
  certifiedReviewTasks.push({
364
409
  ref: task.display_id || task.legacy_ref || task.id,
@@ -370,6 +415,7 @@ function summarizeTaskProjection(root) {
370
415
  return {
371
416
  tasks,
372
417
  counts,
418
+ agentNeededReviewTasks,
373
419
  certifiedReviewTasks,
374
420
  };
375
421
  }
@@ -466,6 +512,7 @@ function collectState(root) {
466
512
  slug: business.slug || path.basename(root),
467
513
  business,
468
514
  todo: countWorkItems(root, todoText),
515
+ endgame: parseTodoEndgame(todoText),
469
516
  taskProjection: summarizeTaskProjection(root),
470
517
  hasNow: nowText.length > 0,
471
518
  nowHeading: firstHeading(nowText, null),
@@ -628,7 +675,7 @@ function parseContributionCard(text, member) {
628
675
  if (!text || !member) return null;
629
676
  const firstName = String(member.name || member.slug || '').split(/\s+/)[0].toLowerCase();
630
677
  const sections = String(text).split(/\n(?=##\s+)/);
631
- const memberSections = sections.filter(section => new RegExp(`^##\\s+${firstName}\\b`, 'i').test(section.trim()));
678
+ const memberSections = sections.filter(section => new RegExp(`^##\\s+${escapeRegExp(firstName)}\\b`, 'i').test(section.trim()));
632
679
  const section = (
633
680
  memberSections.find(candidate => /current_score_signal\s*:/i.test(candidate))
634
681
  || memberSections[0]
@@ -708,12 +755,25 @@ function memberNextMove(member, state = null) {
708
755
  const name = member.name || member.slug;
709
756
  const context = `${member.startHere}\n${member.goals}`;
710
757
  const identity = `${member.slug}\n${member.name}`;
758
+ const agentNeededReview = state?.taskProjection?.agentNeededReviewTasks?.[0] || null;
759
+ const agentNeededReviewMove = agentNeededReview
760
+ ? `${name}: run the agent-safe review lane for ${agentNeededReview.ref}: ` +
761
+ `\`atris task review-chat ${agentNeededReview.ref} --as codex-review\`, then run the verifier named in the review packet and certify or revise without accepting XP.`
762
+ : null;
711
763
  const certifiedReview = state?.taskProjection?.certifiedReviewTasks?.[0] || null;
764
+ const certifiedReviewRefs = (state?.taskProjection?.certifiedReviewTasks || [])
765
+ .map(task => task.ref)
766
+ .filter(Boolean);
712
767
  const certifiedReviewMove = certifiedReview
713
768
  ? `${name}: hand off certified review ${certifiedReview.ref} to the operator: run ` +
714
769
  `\`atris task accept ${certifiedReview.ref}\` if approved or ` +
715
770
  `\`atris task revise ${certifiedReview.ref} --note "<what must change>"\` if not; do not create new work until this checkpoint is clear.`
716
771
  : null;
772
+ const codexEndgameMove = certifiedReviewRefs.length > 0 && member.slug === 'codex-executor'
773
+ ? `${name}: certified reviews ${certifiedReviewRefs.slice(0, 3).join(', ')} are human-only; do not accept XP. ` +
774
+ `Create the next bounded Codex task from Endgame ${state?.endgame?.slug || 'current-horizon'}: ` +
775
+ `${state?.endgame?.horizon || 'the highest-leverage system gap'}; include files, verifier, and stop rule before editing.`
776
+ : null;
717
777
  if (member.slug === 'justin' || /justin/i.test(member.name || '')) {
718
778
  return `${name}: run one customer-moving GTM rep, update the relevant workspace state within 10 minutes, and leave a scorecard.`;
719
779
  }
@@ -728,6 +788,7 @@ function memberNextMove(member, state = null) {
728
788
  return `${name}: choose or create one bounded mission step, run its verifier, and close it with proof, a scorecard, and the next move.`;
729
789
  }
730
790
  if (/validator|reviewer/i.test(identity)) {
791
+ if (agentNeededReviewMove) return agentNeededReviewMove;
731
792
  if (certifiedReviewMove) return certifiedReviewMove;
732
793
  if ((state?.todo?.open || 0) === 0 && (state?.todo?.done || 0) === 0) {
733
794
  return `${name}: wait for one concrete artifact or ask Navigator to create a reviewable task with verifier, proof target, and residual-risk checklist.`;
@@ -736,6 +797,8 @@ function memberNextMove(member, state = null) {
736
797
  }
737
798
  if (/executor|builder/i.test(identity)) {
738
799
  if ((state?.todo?.open || 0) === 0) {
800
+ if (agentNeededReviewMove) return agentNeededReviewMove;
801
+ if (codexEndgameMove) return codexEndgameMove;
739
802
  if (certifiedReviewMove) return certifiedReviewMove;
740
803
  return `${name}: ask Navigator to create one bounded task with files, verifier, and stop rule before making a patch.`;
741
804
  }
@@ -745,6 +808,7 @@ function memberNextMove(member, state = null) {
745
808
  return `${name}: turn one messy or unclaimed intent into a MAP-backed plan with ASCII visualization, exact files, verifier, rollback, and a review-ready task.`;
746
809
  }
747
810
  if (/launcher|closer/i.test(identity)) {
811
+ if (agentNeededReviewMove) return agentNeededReviewMove;
748
812
  if (certifiedReviewMove) return certifiedReviewMove;
749
813
  if ((state?.todo?.done || 0) === 0) {
750
814
  return `${name}: wait for one validated task receipt before closeout, or ask Validator to produce a review decision with proof.`;
@@ -1571,4 +1635,7 @@ module.exports = {
1571
1635
  verifyActivationCard,
1572
1636
  verifyActivationGallery,
1573
1637
  verifyBrain,
1638
+ parseContributionCard,
1639
+ escapeRegExp,
1640
+ countTodoItems,
1574
1641
  };
@@ -2,6 +2,15 @@ const fs = require('fs');
2
2
  const path = require('path');
3
3
  const readline = require('readline');
4
4
  const { getLogPath, ensureLogDirectory, createLogFile } = require('../lib/journal');
5
+ // Inbox helpers live canonically (and CRLF-tolerant) in lib/file-ops; brainstorm
6
+ // used to carry byte-identical local copies that silently missed CRLF journals.
7
+ const {
8
+ parseInboxItems,
9
+ replaceInboxSection,
10
+ addInboxItemToContent,
11
+ getNextInboxId,
12
+ addInboxIdea,
13
+ } = require('../lib/file-ops');
5
14
  const { loadConfig } = require('../utils/config');
6
15
  const { loadCredentials, ensureValidCredentials } = require('../utils/auth');
7
16
  const { apiRequestJson } = require('../utils/api');
@@ -351,69 +360,11 @@ function brainstormAbortError() {
351
360
  return error;
352
361
  }
353
362
 
354
- function addInboxIdea(logFile, summary) {
355
- const content = fs.readFileSync(logFile, 'utf8');
356
- const nextId = getNextInboxId(content);
357
- const updated = addInboxItemToContent(content, nextId, summary);
358
- fs.writeFileSync(logFile, updated);
359
- return nextId;
360
- }
361
-
362
- function parseInboxItems(content) {
363
- const match = content.match(/## Inbox\n([\s\S]*?)(?=\n##|\n---|$)/);
364
- if (!match) {
365
- return [];
366
- }
367
- const body = match[1];
368
- const lines = body.split('\n');
369
- const items = [];
370
- lines.forEach((line) => {
371
- const trimmed = line.trim();
372
- if (!trimmed) return;
373
- if (trimmed.startsWith('(Empty')) return;
374
- const parsed = trimmed.match(/^- \*\*I(\d+):\*\*\s*(.+)$|^- \*\*I(\d+):\s+(.+)$/);
375
- if (parsed) {
376
- const id = parseInt(parsed[1] || parsed[3], 10);
377
- const text = parsed[2] || parsed[4];
378
- items.push({ id, text, line: trimmed });
379
- }
380
- });
381
- return items;
382
- }
383
-
384
- function replaceInboxSection(content, items) {
385
- const regex = /(## Inbox\n)([\s\S]*?)(\n---|\n##|$)/;
386
- if (!regex.test(content)) {
387
- const lines = items.length ? items.map((item) => item.line).join('\n') : '(Empty - inbox zero achieved)';
388
- return `${content}\n\n## Inbox\n\n${lines}\n`;
389
- }
390
-
391
- return content.replace(regex, (match, header, body, suffix) => {
392
- const inner = items.length
393
- ? `\n${items.map((item) => item.line).join('\n')}\n`
394
- : '\n(Empty - inbox zero achieved)\n';
395
- return `${header}${inner}${suffix}`;
396
- });
397
- }
398
-
399
- function addInboxItemToContent(content, id, summary) {
400
- const items = parseInboxItems(content).filter((item) => item.id !== id);
401
- const newItem = { id, text: summary, line: `- **I${id}:** ${summary}` };
402
- const updatedItems = [newItem, ...items];
403
- return replaceInboxSection(content, updatedItems);
404
- }
405
-
406
363
  function removeInboxItemFromContent(content, id) {
407
364
  const items = parseInboxItems(content).filter((item) => item.id !== id);
408
365
  return replaceInboxSection(content, items);
409
366
  }
410
367
 
411
- function getNextInboxId(content) {
412
- const items = parseInboxItems(content);
413
- if (items.length === 0) return 1;
414
- return items.reduce((max, item) => (item.id > max ? item.id : max), 0) + 1;
415
- }
416
-
417
368
  function insertIntoNotesSection(content, block) {
418
369
  const regex = /(## Notes\n)([\s\S]*?)(\n---|\n##|$)/;
419
370
  const match = content.match(regex);
package/commands/clean.js CHANGED
@@ -1,5 +1,6 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
+ const escapeRegExp = require('../lib/escape-regexp');
3
4
 
4
5
  /**
5
6
  * atris clean - Workspace housekeeping with auto-heal
@@ -396,10 +397,6 @@ function findSymbolLine(fileContent, symbol) {
396
397
  return null;
397
398
  }
398
399
 
399
- function escapeRegExp(string) {
400
- return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
401
- }
402
-
403
400
  /**
404
401
  * Find wiki pages whose sources have been modified after last_compiled.
405
402
  * Scans all .md files under atris/ for frontmatter with last_compiled + sources.