atris 3.35.0 → 3.36.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 (133) hide show
  1. package/AGENTS.md +37 -0
  2. package/README.md +5 -3
  3. package/atris/GETTING_STARTED.md +1 -1
  4. package/atris/atris.md +3 -0
  5. package/atris/policies/day-loop-voice.md +102 -0
  6. package/atris/policies/outbound-artifact-gate.md +2 -0
  7. package/atris/skills/design/SKILL.md +56 -32
  8. package/atris/skills/endgame/SKILL.md +12 -6
  9. package/atris/skills/engines/SKILL.md +22 -4
  10. package/atris/skills/fable-method/SKILL.md +66 -0
  11. package/atris/skills/improve/SKILL.md +65 -45
  12. package/atris/skills/youtube/SKILL.md +10 -1
  13. package/atris.md +2 -0
  14. package/ax +147 -19
  15. package/bin/atris.js +565 -265
  16. package/commands/activate.js +194 -88
  17. package/commands/agents.js +166 -0
  18. package/commands/autoland.js +459 -107
  19. package/commands/autopilot-front.js +20 -2
  20. package/commands/autopilot.js +118 -2
  21. package/commands/avail.js +407 -0
  22. package/commands/bench.js +188 -0
  23. package/commands/brain.js +3 -0
  24. package/commands/brief.js +651 -0
  25. package/commands/business-sync.js +192 -6
  26. package/commands/clean.js +50 -24
  27. package/commands/close.js +1083 -0
  28. package/commands/cloud.js +245 -0
  29. package/commands/compile.js +292 -1
  30. package/commands/computer.js +150 -3
  31. package/commands/dream.js +365 -0
  32. package/commands/drill.js +371 -0
  33. package/commands/engine.js +993 -32
  34. package/commands/experiments.js +28 -0
  35. package/commands/feedback.js +34 -12
  36. package/commands/fleet-report.js +206 -0
  37. package/commands/gm.js +23 -0
  38. package/commands/goal.js +247 -0
  39. package/commands/improve.js +642 -26
  40. package/commands/init.js +72 -44
  41. package/commands/interview.js +67 -1
  42. package/commands/land.js +152 -52
  43. package/commands/lifecycle.js +39 -3
  44. package/commands/log.js +84 -1
  45. package/commands/loops.js +220 -16
  46. package/commands/meet.js +220 -0
  47. package/commands/member.js +511 -34
  48. package/commands/mission.js +3029 -339
  49. package/commands/next.js +137 -0
  50. package/commands/now.js +220 -25
  51. package/commands/one-lap.js +776 -0
  52. package/commands/orb.js +314 -0
  53. package/commands/pack-craft.js +179 -0
  54. package/commands/pack.js +823 -0
  55. package/commands/play.js +3 -2
  56. package/commands/probe.js +30 -3
  57. package/commands/pulse.js +241 -46
  58. package/commands/push.js +260 -82
  59. package/commands/rainmaker.js +49 -0
  60. package/commands/report.js +415 -0
  61. package/commands/scout.js +147 -0
  62. package/commands/search.js +363 -0
  63. package/commands/skill.js +47 -3
  64. package/commands/slop.js +50 -2
  65. package/commands/soul.js +1 -1
  66. package/commands/stream.js +861 -0
  67. package/commands/study.js +693 -0
  68. package/commands/sync.js +67 -54
  69. package/commands/task.js +1346 -117
  70. package/commands/team.js +73 -0
  71. package/commands/verify.js +96 -0
  72. package/commands/watch.js +303 -0
  73. package/commands/wish.js +500 -0
  74. package/commands/workflow.js +11 -5
  75. package/commands/worktree.js +234 -13
  76. package/commands/xp.js +29 -11
  77. package/lib/auto-accept-certified.js +331 -34
  78. package/lib/autoland.js +319 -54
  79. package/lib/ax-auto-lane.js +79 -0
  80. package/lib/bench/context.js +147 -0
  81. package/lib/bench/engines.js +141 -0
  82. package/lib/bench/report.js +140 -0
  83. package/lib/bench/runner.js +512 -0
  84. package/lib/brief-ledger.js +350 -0
  85. package/lib/cloud-mission.js +259 -0
  86. package/lib/codex-flight.js +154 -0
  87. package/lib/default-runner.js +45 -0
  88. package/lib/default-verifier.js +70 -0
  89. package/lib/engine-registry.js +232 -0
  90. package/lib/experiments/daily.js +640 -0
  91. package/lib/fleet.js +2219 -67
  92. package/lib/improve-vitals-html.js +171 -0
  93. package/lib/known-commands.js +58 -0
  94. package/lib/loop-doctor.js +416 -0
  95. package/lib/member-switches.js +144 -0
  96. package/lib/mission-room.js +1 -0
  97. package/lib/mission-root.js +52 -0
  98. package/lib/next-moves.js +327 -10
  99. package/lib/one-lap-validator.js +60 -0
  100. package/lib/orb-context.js +477 -0
  101. package/lib/orb-scorecard.js +224 -0
  102. package/lib/policy-lessons.js +52 -1
  103. package/lib/pulse.js +277 -3
  104. package/lib/receipt-block.js +168 -0
  105. package/lib/receipt-evidence.js +65 -4
  106. package/lib/router-brain.js +352 -0
  107. package/lib/runner-command.js +10 -0
  108. package/lib/self-drive.js +258 -0
  109. package/lib/short-name.js +103 -0
  110. package/lib/spawn-env.js +18 -0
  111. package/lib/state-detection.js +56 -1
  112. package/lib/sync-status.js +59 -0
  113. package/lib/task-db.js +108 -29
  114. package/lib/task-proof.js +23 -1
  115. package/lib/team-presence.js +260 -0
  116. package/lib/tool-result-encode.js +7 -0
  117. package/lib/trust-tiers.js +90 -0
  118. package/lib/usage.js +107 -0
  119. package/lib/voice-gate.js +163 -0
  120. package/lib/wish-audit.js +1368 -0
  121. package/lib/wish-delegate.js +1840 -0
  122. package/lib/wish-design.js +110 -0
  123. package/lib/wish-stats.js +183 -0
  124. package/lib/wish-store.js +354 -0
  125. package/lib/zip.js +221 -0
  126. package/package.json +3 -1
  127. package/templates/loops/atris/loops/LOOPS.md +55 -0
  128. package/templates/loops/atris/loops/TICK.md +24 -0
  129. package/templates/loops/atris/loops/feedback.md +22 -0
  130. package/templates/loops/atris/loops/quality.md +22 -0
  131. package/templates/loops/atris/wiki/systems/loops.md +41 -0
  132. package/utils/api.js +5 -1
  133. package/utils/auth.js +57 -21
@@ -0,0 +1,1368 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const os = require('os');
5
+ const path = require('path');
6
+ const engineRegistry = require('./engine-registry');
7
+ const { slugify } = require('./wish-store');
8
+
9
+ const VERBS = new Set([
10
+ 'add',
11
+ 'archive',
12
+ 'audit',
13
+ 'backfill',
14
+ 'build',
15
+ 'bump',
16
+ 'bundle',
17
+ 'buries',
18
+ 'bury',
19
+ 'change',
20
+ 'check',
21
+ 'clean',
22
+ 'compress',
23
+ 'create',
24
+ 'cut',
25
+ 'debug',
26
+ 'debounce',
27
+ 'delegate',
28
+ 'deserve',
29
+ 'deserves',
30
+ 'deploy',
31
+ 'design',
32
+ 'disable',
33
+ 'do',
34
+ 'draft',
35
+ 'downgrade',
36
+ 'drop',
37
+ 'enable',
38
+ 'explain',
39
+ 'extract',
40
+ 'find',
41
+ 'fix',
42
+ 'get',
43
+ 'hide',
44
+ 'hook',
45
+ 'improve',
46
+ 'inline',
47
+ 'install',
48
+ 'kill',
49
+ 'lighten',
50
+ 'make',
51
+ 'merge',
52
+ 'migrate',
53
+ 'move',
54
+ 'need',
55
+ 'needs',
56
+ 'open',
57
+ 'paginate',
58
+ 'pin',
59
+ 'plan',
60
+ 'polish',
61
+ 'refactor',
62
+ 'rebuild',
63
+ 'remove',
64
+ 'rename',
65
+ 'research',
66
+ 'reduce',
67
+ 'review',
68
+ 'redo',
69
+ 'release',
70
+ 'restore',
71
+ 'retry',
72
+ 'rework',
73
+ 'rewrite',
74
+ 'rotate',
75
+ 'run',
76
+ 'schedule',
77
+ 'set',
78
+ 'setup',
79
+ 'shave',
80
+ 'ship',
81
+ 'shrink',
82
+ 'sort',
83
+ 'speed',
84
+ 'split',
85
+ 'start',
86
+ 'stop',
87
+ 'swap',
88
+ 'sweep',
89
+ 'test',
90
+ 'theres',
91
+ 'throttle',
92
+ 'toggle',
93
+ 'trim',
94
+ 'tweak',
95
+ 'update',
96
+ 'upgrade',
97
+ 'verify',
98
+ 'wire',
99
+ 'write',
100
+ 'understand',
101
+ ]);
102
+
103
+ const PROPER_SKIP = new Set([
104
+ 'A',
105
+ 'An',
106
+ 'And',
107
+ 'At',
108
+ 'But',
109
+ 'Cant',
110
+ 'Didnt',
111
+ 'Doesnt',
112
+ 'Dont',
113
+ 'For',
114
+ 'Hes',
115
+ 'Id',
116
+ 'Ill',
117
+ 'Im',
118
+ 'I',
119
+ 'Isnt',
120
+ 'Its',
121
+ 'Ive',
122
+ 'In',
123
+ 'Lets',
124
+ 'Make',
125
+ 'On',
126
+ 'Or',
127
+ 'Please',
128
+ 'Shes',
129
+ 'Thats',
130
+ 'The',
131
+ 'Theres',
132
+ 'This',
133
+ 'To',
134
+ 'Were',
135
+ 'Weve',
136
+ 'Whats',
137
+ 'With',
138
+ 'Wont',
139
+ 'Youre',
140
+ 'Monday',
141
+ 'Tuesday',
142
+ 'Wednesday',
143
+ 'Thursday',
144
+ 'Friday',
145
+ 'Saturday',
146
+ 'Sunday',
147
+ 'January',
148
+ 'February',
149
+ 'March',
150
+ 'April',
151
+ 'May',
152
+ 'June',
153
+ 'July',
154
+ 'August',
155
+ 'September',
156
+ 'October',
157
+ 'November',
158
+ 'December',
159
+ ]);
160
+
161
+ const BUDGET_LABELS = {
162
+ quick: 'about fifteen minutes',
163
+ long: 'about an hour',
164
+ deep: 'about three hours',
165
+ };
166
+
167
+ const WISH_SWEEP_LIMIT = 3;
168
+ const WISH_MISSION_RUNNER = 'claude';
169
+ const TEST_VERIFY_COMMAND = 'node --test';
170
+ const JOURNAL_RESULT_TEXT = 'a written result will be waiting in your journal';
171
+ const WAITING_INPUT_STATUSES = new Set(['needs_input', 'waiting_input']);
172
+ const PATH_SLOT_QUESTION = 'Where should I find the file or folder you named?';
173
+ const AUDIENCE_WORDS = /\b(?:for|user|users|customer|customers|operator|operators|agent|agents|developer|developers|admin|admins|member|members|team|teams|visitor|visitors|client|clients|student|students|founder|founders|newcomer|newcomers)\b/i;
174
+ const BUDGET_RANK = { quick: 1, long: 2, deep: 3 };
175
+ let answerAuditContext = null;
176
+ const VAGUE_SUPERLATIVE_WORDS = new Set([
177
+ 'amazing',
178
+ 'awesome',
179
+ 'best',
180
+ 'better',
181
+ 'clearest',
182
+ 'easiest',
183
+ 'fastest',
184
+ 'friendliest',
185
+ 'good',
186
+ 'great',
187
+ 'greatest',
188
+ 'nicest',
189
+ 'perfect',
190
+ 'smartest',
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
+ // A conjunction followed by a determiner ("an ml researcher and a 2nd grade
271
+ // teacher") joins noun phrases inside one clause, not two separate wishes.
272
+ const PART_JOINER_WORDS = new Set(['and', 'plus', 'also', 'adn', 'nad', 'annd']);
273
+ const PART_JOINER_DETERMINERS = new Set(['a', 'an', 'the', 'my', 'our', 'your', 'this', 'that', 'these', 'those', 'its', 'his', 'her', 'their']);
274
+ const PART_JOINER_PRONOUN_OBJECTS = new Set(['it', 'them', 'this', 'that']);
275
+ const FIXED_AND_COMPOUNDS = new Set([
276
+ 'back and forth',
277
+ 'black and white',
278
+ 'copy and paste',
279
+ 'drag and drop',
280
+ 'plug and play',
281
+ 'pros and cons',
282
+ ]);
283
+ const LEADING_PART_CONNECTORS = /^(?:(?:and|also|then|plus|finally|next)\s+)+/i;
284
+ const EXTERNAL_REF_WORDS = new Set(['repo', 'repository', 'project', 'workspace']);
285
+ const EXTERNAL_REF_STOP = new Set([
286
+ 'and',
287
+ 'also',
288
+ 'but',
289
+ 'for',
290
+ 'hard',
291
+ 'it',
292
+ 'may',
293
+ 'might',
294
+ 'or',
295
+ 'plus',
296
+ 'seem',
297
+ 'seems',
298
+ 'that',
299
+ 'though',
300
+ 'to',
301
+ 'when',
302
+ 'where',
303
+ 'which',
304
+ 'with',
305
+ ]);
306
+ const SUBJECT_SKIP_WORDS = new Set([
307
+ 'can',
308
+ 'could',
309
+ 'done',
310
+ 'everything',
311
+ 'finish',
312
+ 'line',
313
+ 'maybe',
314
+ 'me',
315
+ 'might',
316
+ 'my',
317
+ 'need',
318
+ 'needs',
319
+ 'our',
320
+ 'should',
321
+ 'take',
322
+ 'this',
323
+ 'us',
324
+ 'we',
325
+ 'will',
326
+ 'wish',
327
+ 'would',
328
+ 'you',
329
+ 'your',
330
+ ]);
331
+ const FILLER_WORDS = new Set([
332
+ 'a',
333
+ 'about',
334
+ 'an',
335
+ 'and',
336
+ 'are',
337
+ 'as',
338
+ 'at',
339
+ 'be',
340
+ 'better',
341
+ 'by',
342
+ 'for',
343
+ 'from',
344
+ 'good',
345
+ 'great',
346
+ 'help',
347
+ 'improved',
348
+ 'into',
349
+ 'it',
350
+ 'its',
351
+ 'like',
352
+ 'more',
353
+ 'nice',
354
+ 'of',
355
+ 'on',
356
+ 'or',
357
+ 'our',
358
+ 'please',
359
+ 'pls',
360
+ 'plz',
361
+ 'rough',
362
+ 'roughly',
363
+ 'the',
364
+ 'their',
365
+ 'this',
366
+ 'to',
367
+ 'want',
368
+ 'with',
369
+ ]);
370
+ const DEICTIC_STAND_INS = new Set(['thing', 'stuff', 'one', 'it', 'that', 'those', 'these', 'other', 'same', 'again']);
371
+ const COMMON_GERUND_NOUNS = new Set([
372
+ 'spacing',
373
+ 'padding',
374
+ 'loading',
375
+ 'landing',
376
+ 'onboarding',
377
+ 'branding',
378
+ 'heading',
379
+ 'setting',
380
+ 'settings',
381
+ 'styling',
382
+ 'everything',
383
+ 'nothing',
384
+ 'morning',
385
+ 'evening',
386
+ ]);
387
+ const KEYWORD_VALUE_SKIP = new Set([
388
+ ...FILLER_WORDS,
389
+ 'again',
390
+ 'already',
391
+ 'anyway',
392
+ 'everywhere',
393
+ 'first',
394
+ 'in',
395
+ 'is',
396
+ 'next',
397
+ 'now',
398
+ 'still',
399
+ 'though',
400
+ 'today',
401
+ 'yet',
402
+ ]);
403
+ const INTERPRETATION_SKIP_WORDS = new Set([
404
+ ...FILLER_WORDS,
405
+ ...SUBJECT_SKIP_WORDS,
406
+ ...DEICTIC_STAND_INS,
407
+ ...VAGUE_SUPERLATIVE_WORDS,
408
+ 'ever',
409
+ ]);
410
+
411
+ function validateEngineOverride(engineOverride, root) {
412
+ const clean = String(engineOverride || '').trim();
413
+ if (clean) engineRegistry.resolveRegisteredEngine(clean, root);
414
+ return clean;
415
+ }
416
+
417
+ function wordList(text) {
418
+ return String(text || '')
419
+ .trim()
420
+ .split(/\s+/)
421
+ .map((word) => word.trim())
422
+ .filter(Boolean);
423
+ }
424
+
425
+ function quoteText(text) {
426
+ return JSON.stringify(String(text || ''));
427
+ }
428
+
429
+ function cleanWord(value) {
430
+ return String(value || '').toLowerCase().replace(/^[^a-z0-9]+|[^a-z0-9]+$/g, '');
431
+ }
432
+
433
+ function hasVerbToken(value) {
434
+ const clean = cleanWord(value);
435
+ if (clean.endsWith('ing') && COMMON_GERUND_NOUNS.has(clean)) return false;
436
+ return VERBS.has(clean) || /(?:ed|ing)$/.test(clean);
437
+ }
438
+
439
+ function hasVerb(text) {
440
+ return wordList(text).some((word) => hasVerbToken(word));
441
+ }
442
+
443
+ function hasStandaloneInstructionVerb(text) {
444
+ for (const word of wordList(text)) {
445
+ const clean = cleanWord(word);
446
+ if (!clean) continue;
447
+ if (VERBS.has(clean)) return true;
448
+ if (/(?:ed|ing)$/.test(clean)) {
449
+ if (!clean.endsWith('ing') || !COMMON_GERUND_NOUNS.has(clean)) return true;
450
+ }
451
+ }
452
+ return false;
453
+ }
454
+
455
+ function largerBudget(left, right) {
456
+ if (!left) return right || null;
457
+ if (!right) return left;
458
+ return BUDGET_RANK[right] > BUDGET_RANK[left] ? right : left;
459
+ }
460
+
461
+ function speedBudgetSignal(text) {
462
+ const compact = String(text || '').toLowerCase();
463
+ const action = '(?:fix|patch|polish|tweak|change|update|edit|cleanup|clean|rename|remove|add|copy)';
464
+ const quick = '(?:quick|quik|qucik|small|smal|tiny)';
465
+ if (new RegExp(`\\b${quick}\\s+${action}\\b`).test(compact)) return 'quick';
466
+ if (new RegExp(`\\b${action}\\b.{0,32}\\b(?:${quick}|quickly)\\b`).test(compact)) return 'quick';
467
+ return null;
468
+ }
469
+
470
+ function deliverableBudgetSignal(text) {
471
+ const compact = String(text || '').toLowerCase();
472
+ if (/\b(big|full|overhaul|all|refactor|restructure|rewrite|architecture|system|suite|migration|migrate|e2e|end-to-end)\b/.test(compact)) {
473
+ return 'deep';
474
+ }
475
+ if (/\b(test|tests|testing|coverage|command|commands|cli|subcommand|workflow|flow|integration|real results)\b/.test(compact)) {
476
+ return 'long';
477
+ }
478
+ return null;
479
+ }
480
+
481
+ function inferBudgetTier(text) {
482
+ let tier = null;
483
+ tier = largerBudget(tier, speedBudgetSignal(text));
484
+ tier = largerBudget(tier, deliverableBudgetSignal(text));
485
+ return tier || 'long';
486
+ }
487
+
488
+ function resolveRole(role, root) {
489
+ const resolver = engineRegistry.resolveRole || engineRegistry.resolve || engineRegistry.resolveEngineForRole;
490
+ return resolver(role, root);
491
+ }
492
+
493
+ function resolveExecutor(root, engineOverride = '') {
494
+ if (engineOverride) {
495
+ return engineRegistry.resolveEngineForRoleWithPreference('executor', root, engineOverride);
496
+ }
497
+ return {
498
+ engine: resolveRole('executor', root),
499
+ requested_engine: null,
500
+ engine_fallback_reason: null,
501
+ };
502
+ }
503
+
504
+ function cleanToken(value) {
505
+ return String(value || '')
506
+ .replace(/^[`'"]+|[`'",;:!?]+$/g, '')
507
+ .trim();
508
+ }
509
+
510
+ function expandHomePath(value) {
511
+ const raw = String(value || '');
512
+ if (raw === '~') return os.homedir();
513
+ if (raw.startsWith('~/')) return path.join(os.homedir(), raw.slice(2));
514
+ return raw;
515
+ }
516
+
517
+ function pathLike(value) {
518
+ const text = cleanToken(value);
519
+ if (/^v?\d+(?:\.\d+)+$/i.test(text)) return false;
520
+ if (/^\d+(?:\.\d+)?(?:s|ms|x|k|m|gb|mb|px|%)$/i.test(text)) return false;
521
+ if (!/[\\/]/.test(text)) {
522
+ const suffix = (text.match(/\.([A-Za-z]+)$/) || [])[1];
523
+ if (suffix && new Set(['then', 'also', 'and', 'so', 'but', 'now', 'next', 'we', 'it']).has(suffix.toLowerCase())) {
524
+ return false;
525
+ }
526
+ }
527
+ return text.startsWith('./')
528
+ || text.startsWith('../')
529
+ || text.startsWith('/')
530
+ || text.startsWith('~/')
531
+ || text.includes('/')
532
+ || /\.[A-Za-z0-9]{1,8}$/.test(text);
533
+ }
534
+
535
+ function isNameShapedToken(value) {
536
+ const text = cleanToken(value);
537
+ if (!text) return false;
538
+ if (/^[A-Z]/.test(text)) return true;
539
+ return /[./_\-0-9]/.test(text);
540
+ }
541
+
542
+ function fileExists(root, value) {
543
+ const raw = cleanToken(value);
544
+ if (!raw) return false;
545
+ const expanded = expandHomePath(raw);
546
+ const candidates = path.isAbsolute(expanded)
547
+ ? [expanded]
548
+ : [path.join(root, expanded), path.join(path.dirname(root), expanded)];
549
+ return candidates.some((candidate) => fs.existsSync(candidate));
550
+ }
551
+
552
+ function wishEventsFile(root) {
553
+ return path.join(root, '.atris', 'state', 'wishes.jsonl');
554
+ }
555
+
556
+ function readWishAuditEvents(root) {
557
+ const file = wishEventsFile(root);
558
+ if (!fs.existsSync(file)) return [];
559
+ return fs.readFileSync(file, 'utf8')
560
+ .split(/\r?\n/)
561
+ .map((line) => line.trim())
562
+ .filter(Boolean)
563
+ .map((line) => {
564
+ try {
565
+ return JSON.parse(line);
566
+ } catch {
567
+ return null;
568
+ }
569
+ })
570
+ .filter(Boolean);
571
+ }
572
+
573
+ function pathLikeTokens(text) {
574
+ return wordList(text).map(cleanToken).filter((word) => pathLike(word));
575
+ }
576
+
577
+ function rowAskedQuestion(row, expected) {
578
+ const cleanExpected = String(expected || '').trim();
579
+ if (!cleanExpected) return false;
580
+ return Array.isArray(row && row.questions)
581
+ && row.questions.some((question) => String(question || '').trim() === cleanExpected);
582
+ }
583
+
584
+ function rowMatchesAuditText(row, auditText) {
585
+ const text = String(row && row.text || '').trim();
586
+ const full = String(auditText || '').trim();
587
+ return !!text && (full === text || full.startsWith(`${text} `));
588
+ }
589
+
590
+ function priorQuestionRowForAuditText(text, root, question) {
591
+ return readWishAuditEvents(root).find((row) => rowAskedQuestion(row, question) && rowMatchesAuditText(row, text)) || null;
592
+ }
593
+
594
+ function activeAnswerContext(root) {
595
+ const context = answerAuditContext;
596
+ if (!context || path.resolve(context.root || root) !== path.resolve(root)) return false;
597
+ if (!String(context.answer || '').trim()) return false;
598
+ return context;
599
+ }
600
+
601
+ function rememberFilledQuestion(context, askedRow, question, slot) {
602
+ if (slot && !context.filled_slot) {
603
+ context.filled_slot = {
604
+ kind: slot.kind,
605
+ question,
606
+ value: slot.value,
607
+ };
608
+ }
609
+ context.wish_id = askedRow.id || context.wish_id || null;
610
+ context.n = askedRow.n || context.n || undefined;
611
+ context.text = askedRow.text || context.text || '';
612
+ }
613
+
614
+ function shouldAcceptRepeatedQuestion(text, root, question, slot = null) {
615
+ const context = activeAnswerContext(root);
616
+ if (!context) return false;
617
+ const askedRow = priorQuestionRowForAuditText(text, root, question);
618
+ if (!askedRow) return false;
619
+ rememberFilledQuestion(context, askedRow, question, slot);
620
+ return true;
621
+ }
622
+
623
+ function shouldAcceptPathAnswer(text, root) {
624
+ const context = activeAnswerContext(root);
625
+ if (!context) return false;
626
+ const tokens = pathLikeTokens(context.answer || '');
627
+ return shouldAcceptRepeatedQuestion(text, root, PATH_SLOT_QUESTION, {
628
+ kind: 'path',
629
+ value: tokens[0] || String(context.answer || '').trim(),
630
+ });
631
+ }
632
+
633
+ function answeredRepeatedQuestionValue(text, root, question, kind = 'question') {
634
+ const context = activeAnswerContext(root);
635
+ if (!context) return '';
636
+ const value = String(context.answer || '').trim();
637
+ return shouldAcceptRepeatedQuestion(text, root, question, { kind, value }) ? value : '';
638
+ }
639
+
640
+ function withWishAnswerAuditContext(context, fn) {
641
+ const previous = answerAuditContext;
642
+ answerAuditContext = context || null;
643
+ try {
644
+ return fn();
645
+ } finally {
646
+ answerAuditContext = previous;
647
+ }
648
+ }
649
+
650
+ function namedThingExists(root, value) {
651
+ const raw = cleanToken(value);
652
+ if (!raw) return false;
653
+ if (fileExists(root, raw)) return true;
654
+ const slug = slugify(raw);
655
+ const base = path.basename(root).toLowerCase();
656
+ const parentBase = path.basename(path.dirname(root)).toLowerCase();
657
+ if (slug && (base.includes(slug) || parentBase.includes(slug))) return true;
658
+ const candidates = [
659
+ path.join(root, raw),
660
+ path.join(root, slug),
661
+ path.join(root, `${raw}.md`),
662
+ path.join(root, `${slug}.md`),
663
+ path.join(path.dirname(root), raw),
664
+ path.join(path.dirname(root), slug),
665
+ path.join(path.dirname(root), `${raw}.md`),
666
+ path.join(path.dirname(root), `${slug}.md`),
667
+ path.join(root, 'atris', 'team', raw),
668
+ path.join(root, 'atris', 'team', slug),
669
+ ];
670
+ if (candidates.some((candidate) => fs.existsSync(candidate))) return true;
671
+ try {
672
+ const names = new Set([raw, slug, `${raw}.md`, `${slug}.md`]
673
+ .filter(Boolean)
674
+ .map((name) => String(name).toLowerCase()));
675
+ return fs.readdirSync(root).some((entry) => {
676
+ const lower = entry.toLowerCase();
677
+ const base = path.basename(entry, path.extname(entry)).toLowerCase();
678
+ return names.has(lower) || names.has(base);
679
+ });
680
+ } catch {
681
+ return false;
682
+ }
683
+ }
684
+
685
+ function subjectForWish(text) {
686
+ const words = wordList(text)
687
+ .map(cleanToken)
688
+ .filter(Boolean);
689
+ if (!words.length) return '';
690
+ let usable = words;
691
+ while (usable.length) {
692
+ const clean = cleanWord(usable[0]);
693
+ if (VERBS.has(clean) || FILLER_WORDS.has(clean) || SUBJECT_SKIP_WORDS.has(clean) || DEICTIC_STAND_INS.has(clean)) {
694
+ usable = usable.slice(1);
695
+ continue;
696
+ }
697
+ break;
698
+ }
699
+ const phrase = [];
700
+ for (const word of usable) {
701
+ const clean = cleanWord(word);
702
+ if (!clean) continue;
703
+ if (['by', 'for', 'from', 'in', 'into', 'of', 'on', 'to', 'with'].includes(clean) && phrase.length) break;
704
+ if (FILLER_WORDS.has(clean) || SUBJECT_SKIP_WORDS.has(clean) || DEICTIC_STAND_INS.has(clean)) continue;
705
+ if (VERBS.has(clean)) continue;
706
+ phrase.push(clean);
707
+ if (phrase.length >= 4) break;
708
+ }
709
+ return phrase.join(' ');
710
+ }
711
+
712
+ function meaningfulWordSet(text) {
713
+ const words = new Set();
714
+ for (const raw of wordList(text)) {
715
+ const clean = cleanWord(raw);
716
+ if (!clean || clean.length < 3) continue;
717
+ if (VERBS.has(clean) || FILLER_WORDS.has(clean)) continue;
718
+ words.add(clean.replace(/s$/, ''));
719
+ }
720
+ return words;
721
+ }
722
+
723
+ function meaningfulInstructionWordCount(text) {
724
+ let count = 0;
725
+ for (const raw of wordList(text)) {
726
+ const clean = cleanWord(raw);
727
+ if (!clean || clean.length < 2) continue;
728
+ if (FILLER_WORDS.has(clean) || SUBJECT_SKIP_WORDS.has(clean) || DEICTIC_STAND_INS.has(clean)) continue;
729
+ count += 1;
730
+ }
731
+ return count;
732
+ }
733
+
734
+ function hasMeaningfulContent(text) {
735
+ for (const raw of wordList(text)) {
736
+ const clean = cleanWord(raw);
737
+ if (!clean || clean.length < 2) continue;
738
+ if (FILLER_WORDS.has(clean) || SUBJECT_SKIP_WORDS.has(clean) || DEICTIC_STAND_INS.has(clean)) continue;
739
+ if (VERBS.has(clean) || hasVerbToken(raw)) continue;
740
+ return true;
741
+ }
742
+ return false;
743
+ }
744
+
745
+ function sharesMeaningfulWords(left, right) {
746
+ const leftWords = meaningfulWordSet(left);
747
+ const rightWords = meaningfulWordSet(right);
748
+ if (!leftWords.size || !rightWords.size) return false;
749
+ for (const word of leftWords) {
750
+ if (rightWords.has(word)) return true;
751
+ }
752
+ return false;
753
+ }
754
+
755
+ function capitalizeFirst(text) {
756
+ const clean = String(text || '').trim();
757
+ if (!clean) return '';
758
+ return clean.charAt(0).toUpperCase() + clean.slice(1);
759
+ }
760
+
761
+ function singularInterpretationWord(word) {
762
+ const clean = cleanWord(word);
763
+ if (clean.length > 3 && clean.endsWith('s')) return clean.slice(0, -1);
764
+ return clean;
765
+ }
766
+
767
+ function interpretationWords(text, subject) {
768
+ const values = [];
769
+ for (const source of [subject, text]) {
770
+ for (const word of wordList(source)) {
771
+ const raw = cleanWord(word);
772
+ if (!raw || INTERPRETATION_SKIP_WORDS.has(raw)) continue;
773
+ const clean = singularInterpretationWord(raw);
774
+ if (!clean || clean.length < 3) continue;
775
+ if (INTERPRETATION_SKIP_WORDS.has(clean)) continue;
776
+ if (VERBS.has(clean)) continue;
777
+ if (hasVerbToken(clean) && (!clean.endsWith('ing') || !COMMON_GERUND_NOUNS.has(clean))) continue;
778
+ if (!values.includes(clean)) values.push(clean);
779
+ }
780
+ }
781
+ return values;
782
+ }
783
+
784
+ function interpretationVerb(text) {
785
+ for (const word of wordList(text)) {
786
+ const clean = cleanWord(word);
787
+ if (!clean || GENERIC_INTERPRETATION_VERBS.has(clean)) continue;
788
+ if (VERBS.has(clean)) return clean;
789
+ }
790
+ return '';
791
+ }
792
+
793
+ function superlativeWord(text) {
794
+ for (const word of wordList(text)) {
795
+ const clean = cleanWord(word);
796
+ if (VAGUE_SUPERLATIVE_WORDS.has(clean)) return clean;
797
+ }
798
+ return '';
799
+ }
800
+
801
+ function interpretationContext(text, subject) {
802
+ const nouns = interpretationWords(text, subject);
803
+ const topic = nouns.slice(0, 2).join(' ');
804
+ return {
805
+ words: wordList(text).map(cleanWord).filter(Boolean),
806
+ topicWords: nouns,
807
+ topic,
808
+ topicHead: nouns[0] || '',
809
+ action: interpretationVerb(text),
810
+ superlative: superlativeWord(text),
811
+ };
812
+ }
813
+
814
+ function subjectKindForInterpretation(ctx) {
815
+ const topicWords = Array.isArray(ctx.topicWords) ? ctx.topicWords : [];
816
+ if (!topicWords.length) return 'generic';
817
+ const words = new Set(topicWords);
818
+ for (const [kind, kindWords] of Object.entries(SUBJECT_KIND_WORDS)) {
819
+ if (Array.from(words).some((word) => kindWords.has(word))) return kind;
820
+ }
821
+ return 'generic';
822
+ }
823
+
824
+ function subjectKindInterpretation(kind, ctx) {
825
+ const subjectKind = subjectKindForInterpretation(ctx);
826
+ return (SUBJECT_KIND_INTERPRETATIONS[subjectKind] && SUBJECT_KIND_INTERPRETATIONS[subjectKind][kind])
827
+ || (SUBJECT_KIND_INTERPRETATIONS.generic && SUBJECT_KIND_INTERPRETATIONS.generic[kind])
828
+ || null;
829
+ }
830
+
831
+ function uniqueInterpretations(values) {
832
+ const seen = new Set();
833
+ const unique = [];
834
+ for (const value of values) {
835
+ const clean = String(value || '').trim().replace(/\s+/g, ' ');
836
+ const key = clean.toLowerCase();
837
+ if (!clean || seen.has(key)) continue;
838
+ seen.add(key);
839
+ unique.push(clean);
840
+ }
841
+ return unique;
842
+ }
843
+
844
+ function joinInterpretations(values) {
845
+ if (values.length <= 1) return values[0] || '';
846
+ if (values.length === 2) return `${values[0]} or ${values[1]}`;
847
+ return `${values.slice(0, -1).join(', ')}, or ${values[values.length - 1]}`;
848
+ }
849
+
850
+ function interpretationCandidates(kind, ctx) {
851
+ const domain = kind === 'vague-superlative' || kind === 'missing-first-slice'
852
+ ? subjectKindInterpretation(kind, ctx)
853
+ : null;
854
+ const table = domain || AMBIGUITY_INTERPRETATIONS[kind];
855
+ const candidates = table ? [...table.candidates] : [];
856
+ if (kind === 'missing-audience' && ctx.topic) candidates[0] = `${ctx.topic} owner`;
857
+ if (kind === 'missing-first-slice' && domain && ctx.topic && subjectKindForInterpretation(ctx) !== 'generic') {
858
+ candidates[0] = `${ctx.topic} ${candidates[0]}`;
859
+ }
860
+ if (kind === 'missing-first-slice' && !domain && ctx.topic) candidates[0] = `${ctx.topic} capture path`;
861
+ if (kind === 'missing-first-slice' && !domain && ctx.action) candidates[0] = `${ctx.action} path`;
862
+ return uniqueInterpretations(candidates).slice(0, 3);
863
+ }
864
+
865
+ function recommendedInterpretationIndex(kind, ctx, candidates) {
866
+ const words = new Set(ctx.words);
867
+ if (kind === 'missing-audience') {
868
+ if (words.has('team') || words.has('member')) return Math.min(1, candidates.length - 1);
869
+ if (words.has('customer') || words.has('visitor') || words.has('client')) return Math.min(2, candidates.length - 1);
870
+ }
871
+ if (kind === 'vague-superlative' || kind === 'missing-first-slice') {
872
+ if (['fast', 'faster', 'fastest', 'speed', 'quick', 'quickly'].some((word) => words.has(word))) return 0;
873
+ if (['done', 'finish', 'finished', 'close', 'closure', 'complete', 'ship'].some((word) => words.has(word))) {
874
+ return Math.min(2, candidates.length - 1);
875
+ }
876
+ if (['todo', 'task', 'tasks', 'list', 'backlog', 'inbox', 'remind', 'reminder', 'surface', 'resurface'].some((word) => words.has(word))) {
877
+ return Math.min(1, candidates.length - 1);
878
+ }
879
+ }
880
+ const domain = kind === 'vague-superlative' || kind === 'missing-first-slice'
881
+ ? subjectKindInterpretation(kind, ctx)
882
+ : null;
883
+ const table = domain || AMBIGUITY_INTERPRETATIONS[kind];
884
+ return Math.min(table ? table.recommendedIndex : 0, candidates.length - 1);
885
+ }
886
+
887
+ function recommendedInterpretationName(candidate) {
888
+ return String(candidate || '').replace(/^(?:fastest|smartest|most)\s+/i, '').trim();
889
+ }
890
+
891
+ function interpretationLabel(kind, ctx) {
892
+ if (kind === 'vague-superlative') {
893
+ if (ctx.superlative && ctx.topic) return `${capitalizeFirst(ctx.superlative)} ${ctx.topic}`;
894
+ return capitalizeFirst(ctx.superlative || ctx.topic || 'this');
895
+ }
896
+ const topic = ctx.topic ? `${ctx.topic} ` : '';
897
+ if (kind === 'missing-audience') return capitalizeFirst(`${topic}audience`);
898
+ if (kind === 'missing-first-slice') return capitalizeFirst(`${topic}first slice`);
899
+ return capitalizeFirst(ctx.topic || 'this');
900
+ }
901
+
902
+ function buildInterpretationQuestion(text, subject, kind) {
903
+ const table = AMBIGUITY_INTERPRETATIONS[kind];
904
+ const ctx = interpretationContext(text, subject);
905
+ const candidates = interpretationCandidates(kind, ctx);
906
+ const recommended = recommendedInterpretationName(candidates[recommendedInterpretationIndex(kind, ctx, candidates)]);
907
+ const label = interpretationLabel(kind, ctx);
908
+ return `${label} could mean ${joinInterpretations(candidates)}. I would bet on ${recommended}, so ${table.ask}?`;
909
+ }
910
+
911
+ function hasSubjectBearingSuperlative(text, subject) {
912
+ const ctx = interpretationContext(text, subject);
913
+ return Boolean(ctx.superlative && ctx.topic);
914
+ }
915
+
916
+ function buildClarityQuestions(text, subject, vague, options = {}) {
917
+ const subjectSuperlative = options.subjectSuperlative === undefined
918
+ ? hasSubjectBearingSuperlative(text, subject)
919
+ : !!options.subjectSuperlative;
920
+ if (!vague && !subjectSuperlative) return [];
921
+ if (subjectSuperlative) return [buildInterpretationQuestion(text, subject, 'vague-superlative')];
922
+ const questions = [
923
+ buildInterpretationQuestion(text, subject, 'vague-superlative'),
924
+ ];
925
+ if (!AUDIENCE_WORDS.test(text)) {
926
+ questions.push(buildInterpretationQuestion(text, subject, 'missing-audience'));
927
+ }
928
+ questions.push(buildInterpretationQuestion(text, subject, 'missing-first-slice'));
929
+ return questions;
930
+ }
931
+
932
+ function priorQuestionRowForAuditTextAnyQuestion(text, root) {
933
+ const rows = readWishAuditEvents(root)
934
+ .filter((row) => Array.isArray(row && row.questions)
935
+ && row.questions.length
936
+ && rowMatchesAuditText(row, text)
937
+ && String(row.text || '').trim());
938
+ return rows.length ? rows[rows.length - 1] : null;
939
+ }
940
+
941
+ function clarityQuestionSourceText(text, root) {
942
+ if (!activeAnswerContext(root)) return text;
943
+ const row = priorQuestionRowForAuditTextAnyQuestion(text, root);
944
+ return row ? row.text : text;
945
+ }
946
+
947
+ function hasHistoricalAnswerForPriorQuestion(text, root) {
948
+ const rows = readWishAuditEvents(root);
949
+ const questionRows = rows.filter((row) => Array.isArray(row && row.questions)
950
+ && row.questions.length
951
+ && rowMatchesAuditText(row, text)
952
+ && String(row.text || '').trim());
953
+ if (!questionRows.length) return false;
954
+ return questionRows.some((questionRow) => rows.some((row) => {
955
+ if (!row || !String(row.answer || '').trim()) return false;
956
+ if (questionRow.id && row.id && questionRow.id === row.id) return true;
957
+ if (questionRow.wish_id && row.wish_id && questionRow.wish_id === row.wish_id) return true;
958
+ return String(questionRow.text || '').trim() === String(row.text || '').trim();
959
+ }));
960
+ }
961
+
962
+ function clarityAudit(text, root = process.cwd(), options = {}) {
963
+ const sourceText = String(options.originalText || clarityQuestionSourceText(text, root));
964
+ const resolutionText = options.originalText ? text : sourceText;
965
+ const words = wordList(resolutionText);
966
+ const subject = subjectForWish(sourceText);
967
+ const missing = [];
968
+ const structurallyVague = words.length < 4 || !hasVerb(resolutionText) || !hasMeaningfulContent(resolutionText);
969
+ const subjectSuperlative = hasSubjectBearingSuperlative(sourceText, subject);
970
+ const historicalAnswer = !activeAnswerContext(root) && hasHistoricalAnswerForPriorQuestion(text, root);
971
+ const unresolvedSubjectSuperlative = subjectSuperlative && !historicalAnswer;
972
+ const vague = structurallyVague || unresolvedSubjectSuperlative;
973
+
974
+ if (vague) {
975
+ missing.push('outcome');
976
+ }
977
+ if (vague && !AUDIENCE_WORDS.test(resolutionText)) {
978
+ missing.push('audience');
979
+ }
980
+ if (vague) {
981
+ missing.push('scope');
982
+ }
983
+
984
+ return {
985
+ vague,
986
+ missing,
987
+ questions: buildClarityQuestions(sourceText, subject, vague, {
988
+ subjectSuperlative: unresolvedSubjectSuperlative,
989
+ }),
990
+ };
991
+ }
992
+
993
+ function missingNamedInputs(text, root) {
994
+ const missing = [];
995
+ const seen = new Set();
996
+ const addMissing = (value, kind) => {
997
+ const clean = cleanToken(value);
998
+ if (!clean || seen.has(clean)) return;
999
+ seen.add(clean);
1000
+ const exists = kind === 'path' ? fileExists(root, clean) : namedThingExists(root, clean);
1001
+ if (!exists) missing.push({ value: clean, kind });
1002
+ };
1003
+
1004
+ const words = wordList(text);
1005
+ const isSentenceStartPlainWord = (word, index) => {
1006
+ const clean = cleanToken(word);
1007
+ if (!/^[A-Z][a-z]+$/.test(clean)) return false;
1008
+ if (!/^[a-z]+$/.test(clean.toLowerCase())) return false;
1009
+ if (index === 0) return true;
1010
+ return /[.!?]$/.test(cleanToken(words[index - 1] || ''));
1011
+ };
1012
+ for (const word of words) {
1013
+ if (pathLike(word)) addMissing(word, 'path');
1014
+ }
1015
+
1016
+ const keywordPattern = /\b(?:member|repo|repository|file|folder|path)\s+[`'"]?([A-Za-z0-9._/-]+)[`'"]?/gi;
1017
+ let match;
1018
+ while ((match = keywordPattern.exec(text))) {
1019
+ const token = String(match[1] || '');
1020
+ if (KEYWORD_VALUE_SKIP.has(token.toLowerCase())) continue;
1021
+ if (!isNameShapedToken(token)) continue;
1022
+ addMissing(match[1], pathLike(match[1]) ? 'path' : 'proper');
1023
+ }
1024
+
1025
+ for (let index = 0; index < words.length; index += 1) {
1026
+ const clean = cleanToken(words[index]);
1027
+ if (!/^[A-Z][A-Za-z0-9_-]{2,}$/.test(clean)) continue;
1028
+ if (PROPER_SKIP.has(clean)) continue;
1029
+ if (index === 0 && VERBS.has(clean.toLowerCase())) continue;
1030
+ if (isSentenceStartPlainWord(words[index], index)) continue;
1031
+ addMissing(clean, 'proper');
1032
+ }
1033
+ return missing;
1034
+ }
1035
+
1036
+ function cleanPartText(text) {
1037
+ return String(text || '')
1038
+ .replace(/\s+/g, ' ')
1039
+ .replace(/^[,;:\s]+|[,;:\s]+$/g, '')
1040
+ .replace(LEADING_PART_CONNECTORS, '')
1041
+ .trim();
1042
+ }
1043
+
1044
+ function checkLineCommand(text) {
1045
+ const lines = String(text || '').split(/\r?\n/);
1046
+ for (const line of lines) {
1047
+ const match = line.match(/^\s*Check:\s*(.*?)\s*$/i);
1048
+ if (match && match[1]) return match[1];
1049
+ }
1050
+ return '';
1051
+ }
1052
+
1053
+ function wishBodyText(text) {
1054
+ return String(text || '')
1055
+ .split(/\r?\n/)
1056
+ .filter((line) => !/^\s*Check:\s*/i.test(line))
1057
+ .join('\n')
1058
+ .trim();
1059
+ }
1060
+
1061
+ function externalReference(text) {
1062
+ const words = wordList(text).map(cleanToken).filter(Boolean);
1063
+ for (let index = 0; index < words.length; index += 1) {
1064
+ const marker = words[index].toLowerCase().replace(/[^a-z]/g, '');
1065
+ if (!EXTERNAL_REF_WORDS.has(marker)) continue;
1066
+ const collected = [];
1067
+ for (let next = index + 1; next < words.length; next += 1) {
1068
+ const clean = words[next].toLowerCase().replace(/[^a-z0-9._-]/g, '');
1069
+ if (!clean || EXTERNAL_REF_STOP.has(clean)) break;
1070
+ if (VERBS.has(clean) || FILLER_WORDS.has(clean)) break;
1071
+ collected.push(clean);
1072
+ if (collected.length >= 3) break;
1073
+ }
1074
+ if (collected.length) return { marker, name: collected.join(' ') };
1075
+ }
1076
+ return null;
1077
+ }
1078
+
1079
+ function outOfScopeReason(text, root) {
1080
+ const ref = externalReference(text);
1081
+ if (ref && !namedThingExists(root, ref.name)) {
1082
+ return `${ref.marker} ${ref.name} is not in this checkout`;
1083
+ }
1084
+ const missing = missingNamedInputs(text, root);
1085
+ const pathMissing = missing.find((item) => item.kind === 'path');
1086
+ if (pathMissing) return `${pathMissing.value} is not in this checkout`;
1087
+ return '';
1088
+ }
1089
+
1090
+ function tokenSpans(text) {
1091
+ const spans = [];
1092
+ const source = String(text || '');
1093
+ for (const match of source.matchAll(/\S+/g)) {
1094
+ spans.push({
1095
+ raw: match[0],
1096
+ clean: cleanWord(match[0]),
1097
+ start: match.index,
1098
+ end: match.index + match[0].length,
1099
+ });
1100
+ }
1101
+ return spans;
1102
+ }
1103
+
1104
+ function fixedCompoundAt(tokens, index) {
1105
+ const previous = tokens[index - 1] && tokens[index - 1].clean;
1106
+ const current = tokens[index] && tokens[index].clean;
1107
+ const next = tokens[index + 1] && tokens[index + 1].clean;
1108
+ if (current !== 'and' || !previous || !next) return false;
1109
+ return FIXED_AND_COMPOUNDS.has(`${previous} and ${next}`);
1110
+ }
1111
+
1112
+ function partStartsWithVerbPronounObject(tokens, index) {
1113
+ const first = tokens[index + 1] && tokens[index + 1].clean;
1114
+ const second = tokens[index + 2] && tokens[index + 2].clean;
1115
+ return Boolean(first && second && VERBS.has(first) && PART_JOINER_PRONOUN_OBJECTS.has(second));
1116
+ }
1117
+
1118
+ function splitSegmentOnJoiners(segment) {
1119
+ const tokens = tokenSpans(segment);
1120
+ const cuts = [];
1121
+ let betweenPending = false;
1122
+
1123
+ for (let index = 0; index < tokens.length; index += 1) {
1124
+ const token = tokens[index];
1125
+ if (!token.clean) continue;
1126
+ if (token.clean === 'between') {
1127
+ betweenPending = true;
1128
+ continue;
1129
+ }
1130
+ if (!PART_JOINER_WORDS.has(token.clean)) continue;
1131
+
1132
+ const next = tokens[index + 1] && tokens[index + 1].clean;
1133
+ if (!next) continue;
1134
+ if (fixedCompoundAt(tokens, index)) continue;
1135
+ if (token.clean === 'plus') {
1136
+ const previous = tokens[index - 1] && tokens[index - 1].clean;
1137
+ if (PART_JOINER_DETERMINERS.has(previous)) continue;
1138
+ }
1139
+ if ((token.clean === 'and' || token.clean === 'adn' || token.clean === 'nad' || token.clean === 'annd') && betweenPending) {
1140
+ betweenPending = false;
1141
+ continue;
1142
+ }
1143
+ if (PART_JOINER_DETERMINERS.has(next)) continue;
1144
+ if (partStartsWithVerbPronounObject(tokens, index)) continue;
1145
+
1146
+ cuts.push({ start: token.start, end: token.end });
1147
+ }
1148
+
1149
+ if (!cuts.length) return [segment];
1150
+
1151
+ const parts = [];
1152
+ let start = 0;
1153
+ for (const cut of cuts) {
1154
+ parts.push(segment.slice(start, cut.start));
1155
+ start = cut.end;
1156
+ }
1157
+ parts.push(segment.slice(start));
1158
+ return parts;
1159
+ }
1160
+
1161
+ function splitWishText(text, root) {
1162
+ const source = String(text || '');
1163
+ const segments = source.split(';');
1164
+ const semicolonSplit = segments.length > 1;
1165
+ const parts = [];
1166
+ for (const segment of segments) {
1167
+ const cleanSegment = cleanPartText(segment);
1168
+ if (!cleanSegment) continue;
1169
+ const joinerParts = splitSegmentOnJoiners(segment).map(cleanPartText).filter(Boolean);
1170
+ if (joinerParts.length > 1 && joinerParts.every((part) => partLooksDeliverable(part, root))) parts.push(...joinerParts);
1171
+ else parts.push(cleanSegment);
1172
+ }
1173
+ return { parts, semicolonSplit };
1174
+ }
1175
+
1176
+ function partLooksDeliverable(text, root) {
1177
+ if (outOfScopeReason(text, root)) return true;
1178
+ return hasStandaloneInstructionVerb(text) && meaningfulInstructionWordCount(text) >= 2;
1179
+ }
1180
+
1181
+ function partLooksSemicolonDeliverable(text, root) {
1182
+ return partLooksDeliverable(text, root) || meaningfulInstructionWordCount(text) >= 2;
1183
+ }
1184
+
1185
+ function analyzeWishParts(text, root) {
1186
+ const clean = cleanPartText(wishBodyText(text));
1187
+ if (!clean) return null;
1188
+ const { parts: rough, semicolonSplit } = splitWishText(clean, root);
1189
+ if (rough.length > 1) {
1190
+ const validator = semicolonSplit ? partLooksSemicolonDeliverable : partLooksDeliverable;
1191
+ if (!rough.every((part) => validator(part, root))) return null;
1192
+ }
1193
+ const parts = (rough.length > 1 ? rough : [clean])
1194
+ .map((part, index) => ({
1195
+ part: index + 1,
1196
+ text: part,
1197
+ waiting_reason: outOfScopeReason(part, root),
1198
+ }))
1199
+ if (parts.length < 2 && !parts.some((part) => part.waiting_reason)) return null;
1200
+ return parts.length ? parts : null;
1201
+ }
1202
+
1203
+ function auditWish(text, root = process.cwd(), options = {}) {
1204
+ const questions = [];
1205
+ const originalText = String(options.originalText || text);
1206
+ const clarity = options.originalText
1207
+ ? clarityAudit(text, root, { originalText })
1208
+ : clarityAudit(text, root);
1209
+ for (const question of clarity.questions) {
1210
+ if (!answeredRepeatedQuestionValue(text, root, question, 'clarity')) questions.push(question);
1211
+ }
1212
+ const engineOverride = String(options.engineOverride || '').trim();
1213
+
1214
+ let executor = null;
1215
+ let validator = null;
1216
+ let requestedEngine = null;
1217
+ let engineFallbackReason = null;
1218
+ try {
1219
+ const resolved = resolveExecutor(root, engineOverride);
1220
+ executor = resolved.engine;
1221
+ requestedEngine = resolved.requested_engine;
1222
+ engineFallbackReason = resolved.engine_fallback_reason;
1223
+ } catch (error) {
1224
+ if (engineOverride) throw error;
1225
+ executor = null;
1226
+ }
1227
+ try {
1228
+ validator = resolveRole('validator', root);
1229
+ } catch {
1230
+ validator = null;
1231
+ }
1232
+ if (!executor) {
1233
+ const question = 'Which working builder should handle this?';
1234
+ const answered = answeredRepeatedQuestionValue(text, root, question, 'builder');
1235
+ if (answered) executor = { id: slugify(answered), name: answered };
1236
+ else questions.push(question);
1237
+ }
1238
+ if (!validator) {
1239
+ const question = 'Which working reviewer should validate it?';
1240
+ const answered = answeredRepeatedQuestionValue(text, root, question, 'reviewer');
1241
+ if (answered) validator = { id: slugify(answered), name: answered };
1242
+ else questions.push(question);
1243
+ }
1244
+
1245
+ let missing = missingNamedInputs(originalText, root);
1246
+ if (missing.some((item) => item.kind === 'path') && shouldAcceptPathAnswer(text, root)) {
1247
+ missing = missing.filter((item) => item.kind !== 'path');
1248
+ }
1249
+ if (missing.some((item) => item.kind === 'path')) {
1250
+ questions.push(PATH_SLOT_QUESTION);
1251
+ } else {
1252
+ while (missing.length) {
1253
+ const [first, ...rest] = missing;
1254
+ const question = `Which workspace, repo, file, or team member did you mean by ${first.value}?`;
1255
+ const answered = answeredRepeatedQuestionValue(text, root, question, first.kind);
1256
+ if (!answered) {
1257
+ questions.push(question);
1258
+ break;
1259
+ }
1260
+ missing = rest;
1261
+ }
1262
+ }
1263
+
1264
+ return {
1265
+ ok: questions.length === 0,
1266
+ questions: questions.slice(0, 3),
1267
+ executor,
1268
+ validator,
1269
+ budget: inferBudgetTier(text),
1270
+ missing,
1271
+ vague: clarity.vague,
1272
+ missing_slots: clarity.missing,
1273
+ requested_engine: requestedEngine,
1274
+ engine_fallback_reason: engineFallbackReason,
1275
+ };
1276
+ }
1277
+
1278
+ function namedTestFiles(text) {
1279
+ const paths = [];
1280
+ const pattern = /(?:^|[^A-Za-z0-9._/-])((?:\.\/)?(?:test|tests)\/[A-Za-z0-9._/-]+\.(?:[cm]?js|jsx|ts|tsx))(?=$|[^A-Za-z0-9._/-])/g;
1281
+ for (const match of String(text || '').matchAll(pattern)) {
1282
+ const file = String(match[1] || '').trim();
1283
+ if (!file || file.split('/').includes('..') || paths.includes(file)) continue;
1284
+ paths.push(file);
1285
+ }
1286
+ return paths;
1287
+ }
1288
+
1289
+ function deriveVerifyPlan(text) {
1290
+ const explicitCommand = checkLineCommand(text);
1291
+ if (explicitCommand) {
1292
+ return {
1293
+ command: explicitCommand,
1294
+ outcome: 'the supplied verifier passes',
1295
+ status: 'explicit',
1296
+ };
1297
+ }
1298
+ const testFiles = namedTestFiles(text);
1299
+ if (testFiles.length) {
1300
+ return {
1301
+ command: `node --test ${testFiles.join(' ')}`,
1302
+ outcome: 'the named tests pass',
1303
+ status: 'derived',
1304
+ scope: 'named-test-files',
1305
+ };
1306
+ }
1307
+ const compact = String(text || '').toLowerCase();
1308
+ const testWord = '(?:test|tests|testing|tets|tesst)';
1309
+ const testAction = '(?:add|write|fix|run|improve)';
1310
+ const createsTests = new RegExp(`\\b${testAction}\\b.{0,48}\\b${testWord}\\b`).test(compact);
1311
+ const testDeliverable = createsTests
1312
+ || /\bkeep\b.{0,32}\btests?\b.{0,32}\bgreen\b/.test(compact)
1313
+ || /\b(?:smoke|stress|load)\s+tests?\b/.test(compact)
1314
+ || /\btest\s+suite\b/.test(compact)
1315
+ || /\b(?:unit|integration)\s+tests?\b/.test(compact)
1316
+ || /\bcoverage\b/.test(compact);
1317
+ if (testDeliverable && !/\btest\s+(?:of|drive)\b/.test(compact)) {
1318
+ return {
1319
+ command: TEST_VERIFY_COMMAND,
1320
+ outcome: 'the tests pass',
1321
+ status: 'derived',
1322
+ };
1323
+ }
1324
+ const commandAction = '(?:add|build|create|write|fix|run|update|install|wire|hook)';
1325
+ if (new RegExp(`\\b${commandAction}\\b.{0,48}\\b(?:command|commands|subcommand|subcommands|cli)\\b`).test(compact)
1326
+ || new RegExp(`\\b(?:command|commands|subcommand|subcommands)\\b.{0,48}\\b${commandAction}\\b`).test(compact)) {
1327
+ return {
1328
+ command: TEST_VERIFY_COMMAND,
1329
+ outcome: 'the new command runs end to end',
1330
+ status: 'derived',
1331
+ };
1332
+ }
1333
+ return {
1334
+ command: '',
1335
+ outcome: 'I will show you the result to judge',
1336
+ status: 'needs-review',
1337
+ };
1338
+ }
1339
+
1340
+ function verifyOutcomeText(planOrCommand) {
1341
+ if (planOrCommand && typeof planOrCommand === 'object') return planOrCommand.outcome || JOURNAL_RESULT_TEXT;
1342
+ const verify = String(planOrCommand || '').trim();
1343
+ if (!verify) return JOURNAL_RESULT_TEXT;
1344
+ if (verify === TEST_VERIFY_COMMAND) return 'the tests pass';
1345
+ return `the verify command ${quoteText(verify)} passes`;
1346
+ }
1347
+
1348
+ module.exports = {
1349
+ BUDGET_LABELS,
1350
+ JOURNAL_RESULT_TEXT,
1351
+ TEST_VERIFY_COMMAND,
1352
+ WAITING_INPUT_STATUSES,
1353
+ WISH_MISSION_RUNNER,
1354
+ WISH_SWEEP_LIMIT,
1355
+ analyzeWishParts,
1356
+ auditWish,
1357
+ deriveVerifyPlan,
1358
+ inferBudgetTier,
1359
+ missingNamedInputs,
1360
+ namedTestFiles,
1361
+ PATH_SLOT_QUESTION,
1362
+ quoteText,
1363
+ sharesMeaningfulWords,
1364
+ validateEngineOverride,
1365
+ verifyOutcomeText,
1366
+ wishBodyText,
1367
+ withWishAnswerAuditContext,
1368
+ };