atris 3.43.0 → 3.45.1

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 (52) hide show
  1. package/atris/skills/design/SKILL.md +7 -1
  2. package/atris/skills/engines/SKILL.md +44 -13
  3. package/atris/team/customer-lead/MEMBER.md +45 -0
  4. package/atris/team/customer-lead/SOUL.md +33 -0
  5. package/atris/team/customer-lead/START_HERE.md +7 -0
  6. package/atris/team/customer-lead/skills/customer-commitments/SKILL.md +45 -0
  7. package/atris/team/improver/MEMBER.md +33 -0
  8. package/bin/atris.js +36 -3
  9. package/commands/aeo.js +5 -2
  10. package/commands/align.js +5 -2
  11. package/commands/autoland.js +15 -1
  12. package/commands/caretaker.js +303 -0
  13. package/commands/clean.js +76 -0
  14. package/commands/computer.js +5 -2
  15. package/commands/engine-watch.js +212 -0
  16. package/commands/engine.js +99 -11
  17. package/commands/founder.js +304 -0
  18. package/commands/human-missions.js +844 -0
  19. package/commands/improve.js +29 -6
  20. package/commands/init.js +16 -7
  21. package/commands/mission.js +124 -69
  22. package/commands/pull.js +5 -2
  23. package/commands/push.js +5 -2
  24. package/commands/slop.js +34 -3
  25. package/commands/task.js +51 -4
  26. package/commands/team.js +329 -13
  27. package/commands/terminal.js +5 -2
  28. package/commands/verify.js +99 -6
  29. package/commands/workflow.js +10 -3
  30. package/commands/worktree.js +119 -4
  31. package/lib/auto-accept-certified.js +302 -0
  32. package/lib/cloud-mission.js +59 -2
  33. package/lib/conductor-artifacts.js +1 -1
  34. package/lib/dispatch-scout.js +386 -0
  35. package/lib/engine-ask.js +645 -0
  36. package/lib/engine-job-lifecycle.js +65 -0
  37. package/lib/engine-receipt-sweep.js +98 -0
  38. package/lib/engine-registry.js +2 -2
  39. package/lib/engine-validate.js +382 -0
  40. package/lib/fleet.js +459 -106
  41. package/lib/known-commands.js +2 -2
  42. package/lib/member-alive.js +2 -2
  43. package/lib/policy-lessons.js +70 -0
  44. package/lib/receipt-evidence.js +56 -1
  45. package/lib/runner-command.js +1 -1
  46. package/lib/secret-gateway.js +588 -0
  47. package/lib/team-presence.js +13 -1
  48. package/lib/voice-gate.js +6 -0
  49. package/lib/wish-audit.js +5 -205
  50. package/lib/wish-delegate.js +5 -2
  51. package/package.json +6 -1
  52. package/utils/auth.js +56 -9
package/lib/wish-audit.js CHANGED
@@ -189,84 +189,6 @@ const VAGUE_SUPERLATIVE_WORDS = new Set([
189
189
  'perfect',
190
190
  'smartest',
191
191
  ]);
192
- const GENERIC_INTERPRETATION_VERBS = new Set([
193
- 'add',
194
- 'build',
195
- 'change',
196
- 'create',
197
- 'do',
198
- 'fix',
199
- 'get',
200
- 'improve',
201
- 'make',
202
- 'polish',
203
- 'tweak',
204
- 'update',
205
- ]);
206
- const AMBIGUITY_INTERPRETATIONS = {
207
- 'vague-superlative': {
208
- candidates: ['faster to use', 'smarter by default', 'more complete'],
209
- recommendedIndex: 1,
210
- ask: 'which should I optimize for',
211
- },
212
- 'missing-audience': {
213
- candidates: ['solo operator', 'team member', 'end user'],
214
- recommendedIndex: 0,
215
- ask: 'who is this for',
216
- },
217
- 'missing-first-slice': {
218
- candidates: ['capture path', 'resurfacing rule', 'closure loop'],
219
- recommendedIndex: 1,
220
- ask: 'what should I change first',
221
- },
222
- };
223
- const SUBJECT_KIND_INTERPRETATIONS = {
224
- list: {
225
- 'vague-superlative': {
226
- candidates: ['fastest capture', 'smartest resurfacing', 'most closure'],
227
- recommendedIndex: 1,
228
- },
229
- 'missing-first-slice': {
230
- candidates: ['capture path', 'resurfacing rule', 'closure loop'],
231
- recommendedIndex: 1,
232
- },
233
- },
234
- page: {
235
- 'vague-superlative': {
236
- candidates: ['clearer layout', 'faster scanning', 'stronger action'],
237
- recommendedIndex: 0,
238
- },
239
- 'missing-first-slice': {
240
- candidates: ['top section', 'content hierarchy', 'primary action'],
241
- recommendedIndex: 1,
242
- },
243
- },
244
- loop: {
245
- 'vague-superlative': {
246
- candidates: ['fewer steps', 'smarter defaults', 'more reliable completion'],
247
- recommendedIndex: 1,
248
- },
249
- 'missing-first-slice': {
250
- candidates: ['entry path', 'decision rule', 'completion check'],
251
- recommendedIndex: 1,
252
- },
253
- },
254
- generic: {
255
- 'vague-superlative': {
256
- candidates: ['faster to use', 'smarter by default', 'more complete'],
257
- recommendedIndex: 1,
258
- },
259
- 'missing-first-slice': {
260
- candidates: ['first step', 'default behavior', 'completion path'],
261
- recommendedIndex: 1,
262
- },
263
- },
264
- };
265
- const SUBJECT_KIND_WORDS = {
266
- list: new Set(['backlog', 'board', 'inbox', 'kanban', 'list', 'queue', 'reminder', 'task', 'todo']),
267
- page: new Set(['card', 'dashboard', 'form', 'homepage', 'landing', 'modal', 'page', 'panel', 'screen', 'site', 'surface', 'ui', 'view', 'website']),
268
- loop: new Set(['auth', 'automation', 'cadence', 'checkout', 'flow', 'import', 'login', 'loop', 'onboarding', 'pipeline', 'process', 'routine', 'signup', 'sync', 'workflow']),
269
- };
270
192
  // A conjunction followed by a determiner ("an ml researcher and a 2nd grade
271
193
  // teacher") joins noun phrases inside one clause, not two separate wishes.
272
194
  const PART_JOINER_WORDS = new Set(['and', 'plus', 'also', 'adn', 'nad', 'annd']);
@@ -770,12 +692,6 @@ function sharesMeaningfulWords(left, right) {
770
692
  return false;
771
693
  }
772
694
 
773
- function capitalizeFirst(text) {
774
- const clean = String(text || '').trim();
775
- if (!clean) return '';
776
- return clean.charAt(0).toUpperCase() + clean.slice(1);
777
- }
778
-
779
695
  function singularInterpretationWord(word) {
780
696
  const clean = cleanWord(word);
781
697
  if (clean.length > 3 && clean.endsWith('s')) return clean.slice(0, -1);
@@ -799,15 +715,6 @@ function interpretationWords(text, subject) {
799
715
  return values;
800
716
  }
801
717
 
802
- function interpretationVerb(text) {
803
- for (const word of wordList(text)) {
804
- const clean = cleanWord(word);
805
- if (!clean || GENERIC_INTERPRETATION_VERBS.has(clean)) continue;
806
- if (VERBS.has(clean)) return clean;
807
- }
808
- return '';
809
- }
810
-
811
718
  function superlativeWord(text) {
812
719
  for (const word of wordList(text)) {
813
720
  const clean = cleanWord(word);
@@ -818,114 +725,12 @@ function superlativeWord(text) {
818
725
 
819
726
  function interpretationContext(text, subject) {
820
727
  const nouns = interpretationWords(text, subject);
821
- const topic = nouns.slice(0, 2).join(' ');
822
728
  return {
823
- words: wordList(text).map(cleanWord).filter(Boolean),
824
- topicWords: nouns,
825
- topic,
826
- topicHead: nouns[0] || '',
827
- action: interpretationVerb(text),
729
+ topic: nouns.slice(0, 2).join(' '),
828
730
  superlative: superlativeWord(text),
829
731
  };
830
732
  }
831
733
 
832
- function subjectKindForInterpretation(ctx) {
833
- const topicWords = Array.isArray(ctx.topicWords) ? ctx.topicWords : [];
834
- if (!topicWords.length) return 'generic';
835
- const words = new Set(topicWords);
836
- for (const [kind, kindWords] of Object.entries(SUBJECT_KIND_WORDS)) {
837
- if (Array.from(words).some((word) => kindWords.has(word))) return kind;
838
- }
839
- return 'generic';
840
- }
841
-
842
- function subjectKindInterpretation(kind, ctx) {
843
- const subjectKind = subjectKindForInterpretation(ctx);
844
- return (SUBJECT_KIND_INTERPRETATIONS[subjectKind] && SUBJECT_KIND_INTERPRETATIONS[subjectKind][kind])
845
- || (SUBJECT_KIND_INTERPRETATIONS.generic && SUBJECT_KIND_INTERPRETATIONS.generic[kind])
846
- || null;
847
- }
848
-
849
- function uniqueInterpretations(values) {
850
- const seen = new Set();
851
- const unique = [];
852
- for (const value of values) {
853
- const clean = String(value || '').trim().replace(/\s+/g, ' ');
854
- const key = clean.toLowerCase();
855
- if (!clean || seen.has(key)) continue;
856
- seen.add(key);
857
- unique.push(clean);
858
- }
859
- return unique;
860
- }
861
-
862
- function joinInterpretations(values) {
863
- if (values.length <= 1) return values[0] || '';
864
- if (values.length === 2) return `${values[0]} or ${values[1]}`;
865
- return `${values.slice(0, -1).join(', ')}, or ${values[values.length - 1]}`;
866
- }
867
-
868
- function interpretationCandidates(kind, ctx) {
869
- const domain = kind === 'vague-superlative' || kind === 'missing-first-slice'
870
- ? subjectKindInterpretation(kind, ctx)
871
- : null;
872
- const table = domain || AMBIGUITY_INTERPRETATIONS[kind];
873
- const candidates = table ? [...table.candidates] : [];
874
- if (kind === 'missing-audience' && ctx.topic) candidates[0] = `${ctx.topic} owner`;
875
- if (kind === 'missing-first-slice' && domain && ctx.topic && subjectKindForInterpretation(ctx) !== 'generic') {
876
- candidates[0] = `${ctx.topic} ${candidates[0]}`;
877
- }
878
- if (kind === 'missing-first-slice' && !domain && ctx.topic) candidates[0] = `${ctx.topic} capture path`;
879
- if (kind === 'missing-first-slice' && !domain && ctx.action) candidates[0] = `${ctx.action} path`;
880
- return uniqueInterpretations(candidates).slice(0, 3);
881
- }
882
-
883
- function recommendedInterpretationIndex(kind, ctx, candidates) {
884
- const words = new Set(ctx.words);
885
- if (kind === 'missing-audience') {
886
- if (words.has('team') || words.has('member')) return Math.min(1, candidates.length - 1);
887
- if (words.has('customer') || words.has('visitor') || words.has('client')) return Math.min(2, candidates.length - 1);
888
- }
889
- if (kind === 'vague-superlative' || kind === 'missing-first-slice') {
890
- if (['fast', 'faster', 'fastest', 'speed', 'quick', 'quickly'].some((word) => words.has(word))) return 0;
891
- if (['done', 'finish', 'finished', 'close', 'closure', 'complete', 'ship'].some((word) => words.has(word))) {
892
- return Math.min(2, candidates.length - 1);
893
- }
894
- if (['todo', 'task', 'tasks', 'list', 'backlog', 'inbox', 'remind', 'reminder', 'surface', 'resurface'].some((word) => words.has(word))) {
895
- return Math.min(1, candidates.length - 1);
896
- }
897
- }
898
- const domain = kind === 'vague-superlative' || kind === 'missing-first-slice'
899
- ? subjectKindInterpretation(kind, ctx)
900
- : null;
901
- const table = domain || AMBIGUITY_INTERPRETATIONS[kind];
902
- return Math.min(table ? table.recommendedIndex : 0, candidates.length - 1);
903
- }
904
-
905
- function recommendedInterpretationName(candidate) {
906
- return String(candidate || '').replace(/^(?:fastest|smartest|most)\s+/i, '').trim();
907
- }
908
-
909
- function interpretationLabel(kind, ctx) {
910
- if (kind === 'vague-superlative') {
911
- if (ctx.superlative && ctx.topic) return `${capitalizeFirst(ctx.superlative)} ${ctx.topic}`;
912
- return capitalizeFirst(ctx.superlative || ctx.topic || 'this');
913
- }
914
- const topic = ctx.topic ? `${ctx.topic} ` : '';
915
- if (kind === 'missing-audience') return capitalizeFirst(`${topic}audience`);
916
- if (kind === 'missing-first-slice') return capitalizeFirst(`${topic}first slice`);
917
- return capitalizeFirst(ctx.topic || 'this');
918
- }
919
-
920
- function buildInterpretationQuestion(text, subject, kind) {
921
- const table = AMBIGUITY_INTERPRETATIONS[kind];
922
- const ctx = interpretationContext(text, subject);
923
- const candidates = interpretationCandidates(kind, ctx);
924
- const recommended = recommendedInterpretationName(candidates[recommendedInterpretationIndex(kind, ctx, candidates)]);
925
- const label = interpretationLabel(kind, ctx);
926
- return `${label} could mean ${joinInterpretations(candidates)}. I would bet on ${recommended}, so ${table.ask}?`;
927
- }
928
-
929
734
  function hasSubjectBearingSuperlative(text, subject) {
930
735
  const ctx = interpretationContext(text, subject);
931
736
  return Boolean(ctx.superlative && ctx.topic);
@@ -936,15 +741,10 @@ function buildClarityQuestions(text, subject, vague, options = {}) {
936
741
  ? hasSubjectBearingSuperlative(text, subject)
937
742
  : !!options.subjectSuperlative;
938
743
  if (!vague && !subjectSuperlative) return [];
939
- if (subjectSuperlative) return [buildInterpretationQuestion(text, subject, 'vague-superlative')];
940
- const questions = [
941
- buildInterpretationQuestion(text, subject, 'vague-superlative'),
942
- ];
943
- if (!AUDIENCE_WORDS.test(text)) {
944
- questions.push(buildInterpretationQuestion(text, subject, 'missing-audience'));
945
- }
946
- questions.push(buildInterpretationQuestion(text, subject, 'missing-first-slice'));
947
- return questions;
744
+ const topic = interpretationContext(text, subject).topic;
745
+ return [topic
746
+ ? `What should be different about ${topic} when this is done?`
747
+ : 'What should be different when this is done?'];
948
748
  }
949
749
 
950
750
  function priorQuestionRowForAuditTextAnyQuestion(text, root) {
@@ -212,7 +212,7 @@ function wishTitle(wish) {
212
212
 
213
213
  // One question at a time, always ending with how to answer it.
214
214
  function printQuestion(wish, question) {
215
- console.log(`Got it, wish ${wishTitle(wish)}.`);
215
+ console.log(`Got it: ${quoteText(wish.text || '')}.`);
216
216
  console.log(String(question || 'What should be different when this wish comes true?'));
217
217
  console.log('Answer with: atris wish answer "your words"');
218
218
  }
@@ -1097,7 +1097,7 @@ function builderWish(wish, audit, root, asJson) {
1097
1097
 
1098
1098
  // Plain per-part reply: what started, what lives elsewhere, and one question per unclear part.
1099
1099
  function printDecomposed(wish, parts, delegatedParts, waitingParts) {
1100
- console.log(`Got it, wish ${wishTitle(wish)}. It has ${parts.length} parts.`);
1100
+ console.log(`Got it: ${quoteText(wish.text || '')}. It has ${parts.length} parts.`);
1101
1101
  delegatedParts.forEach((part) => {
1102
1102
  if (part.attached_to_existing_flight) {
1103
1103
  printGranted(part.text, null, {
@@ -1267,6 +1267,9 @@ function intakeQuestionSubject(question) {
1267
1267
  const interpretationMarker = ' could mean ';
1268
1268
  const interpretationIndex = normalized.indexOf(interpretationMarker);
1269
1269
  if (interpretationIndex > 0) return `interpretation:${normalized.slice(0, interpretationIndex)}`;
1270
+ const grounded = normalized.match(/^what should be different about (.+?) when this is done\?$/);
1271
+ if (grounded) return `interpretation:${grounded[1]}`;
1272
+ if (normalized === 'what should be different when this is done?') return 'interpretation:this';
1270
1273
  return `question:${normalized}`;
1271
1274
  }
1272
1275
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atris",
3
- "version": "3.43.0",
3
+ "version": "3.45.1",
4
4
  "description": "you say what you want in plain words. atris builds it, checks it, and shows you proof.",
5
5
  "main": "bin/atris.js",
6
6
  "bin": {
@@ -43,6 +43,11 @@
43
43
  "atris/team/mission-lead/MEMBER.md",
44
44
  "atris/team/mission-lead/MISSION.md",
45
45
  "atris/team/mission-lead/START_HERE.md",
46
+ "atris/team/improver/MEMBER.md",
47
+ "atris/team/customer-lead/MEMBER.md",
48
+ "atris/team/customer-lead/SOUL.md",
49
+ "atris/team/customer-lead/START_HERE.md",
50
+ "atris/team/customer-lead/skills/customer-commitments/SKILL.md",
46
51
  "atris/team/opus-overnight/MEMBER.md",
47
52
  "atris/team/opus-overnight/MISSION.md",
48
53
  "atris/team/opus-overnight/START_HERE.md",
package/utils/auth.js CHANGED
@@ -407,13 +407,27 @@ async function validateAccessToken(token, apiRequestJson) {
407
407
  });
408
408
  }
409
409
 
410
+ function refreshProviderHint(provider, refreshToken) {
411
+ const hint = typeof provider === 'string' ? provider.trim().toLowerCase() : '';
412
+ if (!hint) return null;
413
+ // provider=google makes /auth/refresh skip app-JWT refresh and POST the
414
+ // minted refresh JWT to Google OAuth, which returns google_refresh_failed.
415
+ // Only send that hint when the stored token is actually a Google OAuth
416
+ // refresh token (they start with 1//). Pack refresh already strips this.
417
+ if (hint === 'google' && !String(refreshToken || '').startsWith('1//')) {
418
+ return null;
419
+ }
420
+ return hint;
421
+ }
422
+
410
423
  async function refreshAccessToken(refreshToken, provider, apiRequestJson) {
411
424
  if (!refreshToken) {
412
425
  return { ok: false, status: 0, error: 'Missing refresh token' };
413
426
  }
414
427
  const body = { refresh_token: refreshToken };
415
- if (provider) {
416
- body.provider = provider;
428
+ const hint = refreshProviderHint(provider, refreshToken);
429
+ if (hint) {
430
+ body.provider = hint;
417
431
  }
418
432
  return apiRequestJson('/auth/refresh', {
419
433
  method: 'POST',
@@ -421,6 +435,24 @@ async function refreshAccessToken(refreshToken, provider, apiRequestJson) {
421
435
  });
422
436
  }
423
437
 
438
+ function isAuthFailure(result) {
439
+ const status = result && result.status;
440
+ return status === 401 || status === 403;
441
+ }
442
+
443
+ function printAuthRequired() {
444
+ console.error('Auth problem. Run: atris login');
445
+ console.error('Check with: atris whoami');
446
+ }
447
+
448
+ function abortOnAuthFailure(result, printedWake = false, exitFn = (code) => process.exit(code)) {
449
+ if (!isAuthFailure(result)) return false;
450
+ if (printedWake) console.log('auth failed');
451
+ printAuthRequired();
452
+ exitFn(1);
453
+ return true;
454
+ }
455
+
424
456
  async function performTokenRefresh(credentials, apiRequestJson) {
425
457
  if (!credentials || !credentials.refresh_token) {
426
458
  return { ok: false, error: 'missing_refresh_token' };
@@ -535,13 +567,24 @@ async function ensureValidCredentials(apiRequestJson, options = {}) {
535
567
  updatedProvider !== credentials.provider ||
536
568
  updatedUserId !== credentials.user_id)
537
569
  ) {
538
- saveCredentials(
539
- credentials.token,
540
- credentials.refresh_token,
541
- updatedEmail,
542
- updatedUserId,
543
- updatedProvider
544
- );
570
+ if (credentials.source_profile) {
571
+ const { source_profile, source, ...rest } = { ...credentials };
572
+ saveProfile(credentials.source_profile, {
573
+ ...rest,
574
+ email: updatedEmail,
575
+ user_id: updatedUserId,
576
+ provider: updatedProvider,
577
+ saved_at: new Date().toISOString(),
578
+ });
579
+ } else {
580
+ saveCredentials(
581
+ credentials.token,
582
+ credentials.refresh_token,
583
+ updatedEmail,
584
+ updatedUserId,
585
+ updatedProvider
586
+ );
587
+ }
545
588
  }
546
589
 
547
590
  return {
@@ -639,8 +682,12 @@ module.exports = {
639
682
  promptUser,
640
683
  validateAccessToken,
641
684
  refreshAccessToken,
685
+ refreshProviderHint,
642
686
  performTokenRefresh,
643
687
  ensureValidCredentials,
688
+ isAuthFailure,
689
+ printAuthRequired,
690
+ abortOnAuthFailure,
644
691
  fetchMyAgents,
645
692
  displayAccountSummary,
646
693
  // Profile switching