@mr.dj2u/cli 0.1.11 → 0.1.13

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 (53) hide show
  1. package/bundles/claude-code/.claude-plugin/plugin.json +1 -1
  2. package/bundles/claude-code/.mcp.json +1 -1
  3. package/bundles/claude-code/commands/create-expo-super-stack.md +10 -7
  4. package/bundles/claude-code/skills/create-expo-super-stack/SKILL.md +10 -7
  5. package/bundles/claude-code/skills/super-stack-startup/SKILL.md +1 -1
  6. package/bundles/codex/.codex-plugin/plugin.json +1 -1
  7. package/bundles/codex/.mcp.json +1 -1
  8. package/bundles/codex/commands/create-expo-super-stack.md +10 -7
  9. package/bundles/codex/skills/super-stack-startup/SKILL.md +1 -1
  10. package/bundles/codex/skills/workflow-create-expo-super-stack/SKILL.md +10 -7
  11. package/bundles/vscode-copilot/.github/prompts/create-expo-super-stack.prompt.md +10 -7
  12. package/bundles/vscode-copilot/.github/skills/super-stack-startup/SKILL.md +1 -1
  13. package/bundles/vscode-copilot/.vscode/mcp.json +1 -1
  14. package/bundles/vscode-copilot/user/.copilot/skills/super-stack-startup/SKILL.md +1 -1
  15. package/bundles/vscode-copilot/user/.copilot/skills/workflow-create-expo-super-stack/SKILL.md +10 -7
  16. package/dist/cess-intake.d.ts +16 -1
  17. package/dist/cess-intake.d.ts.map +1 -1
  18. package/dist/cess-intake.js +677 -34
  19. package/dist/cess-intake.js.map +1 -1
  20. package/dist/cli.d.ts.map +1 -1
  21. package/dist/cli.js +14 -4
  22. package/dist/cli.js.map +1 -1
  23. package/dist/commands/agent.d.ts.map +1 -1
  24. package/dist/commands/agent.js +3 -1
  25. package/dist/commands/agent.js.map +1 -1
  26. package/dist/commands/mcp-install.d.ts +3 -2
  27. package/dist/commands/mcp-install.d.ts.map +1 -1
  28. package/dist/commands/mcp-install.js +17 -44
  29. package/dist/commands/mcp-install.js.map +1 -1
  30. package/dist/commands/onboard.d.ts +9 -3
  31. package/dist/commands/onboard.d.ts.map +1 -1
  32. package/dist/commands/onboard.js +28 -10
  33. package/dist/commands/onboard.js.map +1 -1
  34. package/dist/commands/roadmap.d.ts +6 -0
  35. package/dist/commands/roadmap.d.ts.map +1 -0
  36. package/dist/commands/roadmap.js +54 -0
  37. package/dist/commands/roadmap.js.map +1 -0
  38. package/dist/project-memory.d.ts +8 -1
  39. package/dist/project-memory.d.ts.map +1 -1
  40. package/dist/project-memory.js +189 -93
  41. package/dist/project-memory.js.map +1 -1
  42. package/dist/roadmap.d.ts +71 -0
  43. package/dist/roadmap.d.ts.map +1 -0
  44. package/dist/roadmap.js +865 -0
  45. package/dist/roadmap.js.map +1 -0
  46. package/dist/stylist-theme.d.ts.map +1 -1
  47. package/dist/stylist-theme.js +1 -20
  48. package/dist/stylist-theme.js.map +1 -1
  49. package/package.json +7 -3
  50. package/templates/embedded-fonts.template.ts +72 -72
  51. package/templates/expo-sdk-56-screen-universal.template.tsx +709 -709
  52. package/templates/project/guidelines.md +4 -5
  53. package/templates/stylist-screen.template.tsx +3456 -3446
@@ -1,6 +1,7 @@
1
1
  import path from 'node:path';
2
2
  import { AGENT_DERIVED_CORE_FLOWS, CUSTOM_BACKEND_EXPLANATION, DATA_NEED_OPTIONS, DATA_START_EXPLANATION, EAS_EXPLANATION, EAS_USE_OPTIONS, EXPO_SERVER_ADAPTER_EXPLANATION, OTHER_DATA_NEEDS, PLATFORM_OPTIONS, TEST_TO_MAIN_EXPLANATION, defaultOnboardPlan, deriveDeployedServer, formatDataNeedsSelection, } from './commands/onboard.js';
3
3
  const DEFAULT_PROJECT_NAME = 'my-expo-app';
4
+ const DEFAULT_DISPLAY_APP_NAME = 'My Expo App';
4
5
  const STACK_DEFAULTS = {
5
6
  scriptLanguage: 'typescript',
6
7
  packageManager: 'npm',
@@ -20,9 +21,9 @@ const CESS_QUESTIONS = [
20
21
  },
21
22
  {
22
23
  id: 'appName',
23
- prompt: 'What should the new Expo app folder be named?',
24
+ prompt: 'What is the name of your app?',
24
25
  kind: 'text',
25
- defaultValue: (context) => context.appName,
26
+ defaultValue: (context) => context.appDisplayName,
26
27
  },
27
28
  {
28
29
  id: 'scriptLanguage',
@@ -112,12 +113,6 @@ const CESS_QUESTIONS = [
112
113
  ],
113
114
  defaultValue: () => STACK_DEFAULTS.easSetup,
114
115
  },
115
- {
116
- id: 'displayAppName',
117
- prompt: 'What display app name should MDS use in project memory?',
118
- kind: 'text',
119
- defaultValue: (context) => context.onboardAnswers.appName,
120
- },
121
116
  {
122
117
  id: 'audience',
123
118
  prompt: 'Who is this app for?',
@@ -267,16 +262,6 @@ const CESS_QUESTIONS = [
267
262
  ],
268
263
  defaultValue: (context) => context.onboardAnswers.includeCreateExpoComponents,
269
264
  },
270
- {
271
- id: 'useLatestExpoSdk',
272
- prompt: 'Use the latest Expo SDK even if Expo Go availability may lag?',
273
- kind: 'single-select',
274
- options: () => [
275
- { value: true, label: 'Yes', hint: 'Default' },
276
- { value: false, label: 'No' },
277
- ],
278
- defaultValue: (context) => context.onboardAnswers.useLatestExpoSdk,
279
- },
280
265
  {
281
266
  id: 'usesExpoUi',
282
267
  prompt: 'Use Expo UI for native-feeling screens?',
@@ -363,22 +348,676 @@ const CESS_QUESTIONS = [
363
348
  defaultValue: () => false,
364
349
  },
365
350
  ];
351
+ const CESS_SNAPSHOT_START = '<!-- MDS_CESS_SNAPSHOT_START -->';
352
+ const CESS_SNAPSHOT_END = '<!-- MDS_CESS_SNAPSHOT_END -->';
353
+ export function extractCessInfoFromMarkdown(input) {
354
+ const infoMarkdown = normalizeLineEndings(input.infoMarkdown);
355
+ const sections = parseMarkdownSections(infoMarkdown);
356
+ const evidence = {};
357
+ const prefilledAnswers = {};
358
+ const ambiguousQuestionIds = new Set();
359
+ const usedSections = new Set();
360
+ const explicitAppName = normalizeText(input.appName);
361
+ let derivedDisplayName = explicitAppName;
362
+ let derivedFolderSlug = explicitAppName ? slugifyAppName(explicitAppName) : undefined;
363
+ const recordEvidence = (key, note) => {
364
+ evidence[key] = [...(evidence[key] ?? []), note];
365
+ };
366
+ const assignValue = (id, value, note) => {
367
+ const normalized = normalizeExtractedAnswerValue(id, value);
368
+ if (normalized === undefined) {
369
+ return;
370
+ }
371
+ if (ambiguousQuestionIds.has(id)) {
372
+ return;
373
+ }
374
+ const current = prefilledAnswers[id];
375
+ if (current !== undefined && !areAnswerValuesEquivalent(id, current, normalized)) {
376
+ delete prefilledAnswers[id];
377
+ ambiguousQuestionIds.add(id);
378
+ recordEvidence(id, `${note} (conflicts with earlier extracted value)`);
379
+ return;
380
+ }
381
+ prefilledAnswers[id] = normalized;
382
+ recordEvidence(id, note);
383
+ };
384
+ const snapshot = parseCessSnapshot(infoMarkdown);
385
+ if (snapshot) {
386
+ recordEvidence('snapshot', 'Parsed machine-readable MDS snapshot block.');
387
+ if (!derivedDisplayName) {
388
+ derivedDisplayName = normalizeText(snapshot.displayAppName);
389
+ }
390
+ if (!derivedFolderSlug) {
391
+ derivedFolderSlug = normalizeText(snapshot.folderSlug) ?? slugifyAppName(snapshot.displayAppName);
392
+ }
393
+ const snapshotAnswers = normalizeCessIntakeAnswers(snapshot.answers);
394
+ for (const [key, value] of Object.entries(snapshotAnswers)) {
395
+ assignValue(key, value, 'MDS snapshot');
396
+ }
397
+ }
398
+ const title = extractProjectTitle(infoMarkdown);
399
+ if (!derivedDisplayName && title) {
400
+ derivedDisplayName = title;
401
+ recordEvidence('appName', `Derived app name from title: ${title}`);
402
+ }
403
+ if (!derivedFolderSlug && derivedDisplayName) {
404
+ derivedFolderSlug = slugifyAppName(derivedDisplayName);
405
+ }
406
+ const targetUsers = sections.get('Target Users');
407
+ if (targetUsers) {
408
+ usedSections.add('Target Users');
409
+ assignValue('audience', normalizeSectionText(targetUsers), 'Target Users section');
410
+ }
411
+ else {
412
+ const overview = sections.get('Overview');
413
+ const overviewAudience = extractAudienceFromOverview(overview);
414
+ if (overviewAudience) {
415
+ usedSections.add('Overview');
416
+ assignValue('audience', overviewAudience, 'Overview section');
417
+ }
418
+ }
419
+ const coreUserFlows = sections.get('Core User Flows');
420
+ if (coreUserFlows) {
421
+ usedSections.add('Core User Flows');
422
+ assignValue('coreFlows', normalizeListSection(coreUserFlows), 'Core User Flows section');
423
+ }
424
+ const mustIncludeScreens = sections.get('Must-Include Screens Or Flows');
425
+ if (mustIncludeScreens) {
426
+ usedSections.add('Must-Include Screens Or Flows');
427
+ assignValue('screens', normalizeListSection(mustIncludeScreens), 'Must-Include Screens Or Flows section');
428
+ }
429
+ const dataAndBackend = sections.get('Data And Backend');
430
+ if (dataAndBackend) {
431
+ usedSections.add('Data And Backend');
432
+ const inferredDataNeeds = inferDataNeedSelections(dataAndBackend);
433
+ if (inferredDataNeeds.length > 0) {
434
+ assignValue('dataNeedSelections', inferredDataNeeds, 'Data And Backend section');
435
+ }
436
+ const dataStart = inferDataStart(dataAndBackend);
437
+ if (dataStart) {
438
+ assignValue('dataStart', dataStart, 'Data And Backend section');
439
+ }
440
+ const authBackend = inferAuthBackend(dataAndBackend);
441
+ if (authBackend) {
442
+ assignValue('authBackend', authBackend, 'Data And Backend section');
443
+ }
444
+ }
445
+ const platforms = sections.get('Platforms');
446
+ if (platforms) {
447
+ usedSections.add('Platforms');
448
+ extractPlatformDecisions(platforms, assignValue);
449
+ }
450
+ const packageChoices = sections.get('Package Choices');
451
+ if (packageChoices) {
452
+ usedSections.add('Package Choices');
453
+ extractPackageChoices(packageChoices, assignValue);
454
+ }
455
+ const releaseStrategy = sections.get('Release Strategy');
456
+ if (releaseStrategy) {
457
+ usedSections.add('Release Strategy');
458
+ const deploymentTarget = extractBulletValue(releaseStrategy, 'Deployment plan');
459
+ if (deploymentTarget) {
460
+ assignValue('deploymentTarget', deploymentTarget, 'Release Strategy section');
461
+ }
462
+ const easUses = inferEasUses(extractBulletValue(releaseStrategy, 'EAS usage') ?? releaseStrategy);
463
+ if (easUses.length > 0) {
464
+ assignValue('easUses', easUses, 'Release Strategy section');
465
+ assignValue('easSetup', true, 'Release Strategy section');
466
+ }
467
+ const testToMain = parseBooleanValue(extractBulletValue(releaseStrategy, 'Test-to-main safeguards'));
468
+ if (typeof testToMain === 'boolean') {
469
+ assignValue('testToMainSafeguards', testToMain, 'Release Strategy section');
470
+ }
471
+ }
472
+ const techStackSection = sections.get('Tech Stack & MDS Onboarding');
473
+ if (techStackSection) {
474
+ usedSections.add('Tech Stack & MDS Onboarding');
475
+ extractTechStackDecisions(techStackSection, assignValue);
476
+ }
477
+ const onboardingDecisionsSection = sections.get('Onboarding Decisions');
478
+ if (onboardingDecisionsSection) {
479
+ usedSections.add('Onboarding Decisions');
480
+ extractOnboardingDecisionLines(onboardingDecisionsSection, assignValue);
481
+ }
482
+ if (derivedDisplayName) {
483
+ assignValue('displayAppName', derivedDisplayName, 'Derived app name');
484
+ }
485
+ const preservedNotes = [...sections.entries()]
486
+ .filter(([heading, body]) => !usedSections.has(heading) && normalizeSectionText(body))
487
+ .map(([heading, body]) => `## ${heading}\n\n${normalizeSectionText(body)}`);
488
+ const missingQuestionIds = validateCessGenerationReadiness({
489
+ parentDir: input.parentDir,
490
+ appName: derivedFolderSlug,
491
+ answers: prefilledAnswers,
492
+ cwd: input.cwd,
493
+ });
494
+ return {
495
+ prefilledAnswers,
496
+ derivedDisplayName,
497
+ derivedFolderSlug,
498
+ missingQuestionIds,
499
+ ambiguousQuestionIds: Array.from(ambiguousQuestionIds),
500
+ evidence,
501
+ preservedNotes,
502
+ };
503
+ }
504
+ function parseCessSnapshot(infoMarkdown) {
505
+ const match = new RegExp(`${escapeRegExp(CESS_SNAPSHOT_START)}([\\s\\S]*?)${escapeRegExp(CESS_SNAPSHOT_END)}`, 'u').exec(infoMarkdown);
506
+ if (!match?.[1]) {
507
+ return null;
508
+ }
509
+ const rawBlock = match[1].trim();
510
+ const jsonMatch = /```json\s*([\s\S]*?)```/u.exec(rawBlock);
511
+ const jsonText = (jsonMatch?.[1] ?? rawBlock).trim();
512
+ if (!jsonText) {
513
+ return null;
514
+ }
515
+ try {
516
+ const parsed = JSON.parse(jsonText);
517
+ const answers = typeof parsed.answers === 'object' && parsed.answers && !Array.isArray(parsed.answers)
518
+ ? parsed.answers
519
+ : undefined;
520
+ return {
521
+ displayAppName: normalizeText(parsed.displayAppName),
522
+ folderSlug: normalizeText(parsed.folderSlug),
523
+ answers,
524
+ };
525
+ }
526
+ catch {
527
+ return null;
528
+ }
529
+ }
530
+ function extractProjectTitle(infoMarkdown) {
531
+ const match = /^#\s+(.+?)\s*$/mu.exec(infoMarkdown);
532
+ if (!match?.[1]) {
533
+ return undefined;
534
+ }
535
+ return normalizeProjectTitle(match[1]);
536
+ }
537
+ function parseMarkdownSections(markdown) {
538
+ const sections = new Map();
539
+ const matches = [...markdown.matchAll(/^##\s+(.+?)\s*$/gmu)];
540
+ for (let index = 0; index < matches.length; index += 1) {
541
+ const match = matches[index];
542
+ const next = matches[index + 1];
543
+ if (!match) {
544
+ continue;
545
+ }
546
+ const heading = match?.[1]?.trim();
547
+ if (!heading || match.index === undefined) {
548
+ continue;
549
+ }
550
+ const start = match.index + match[0].length;
551
+ const end = next?.index ?? markdown.length;
552
+ sections.set(heading, markdown.slice(start, end).trim());
553
+ }
554
+ return sections;
555
+ }
556
+ function extractAudienceFromOverview(overview) {
557
+ const text = normalizeSectionText(overview);
558
+ if (!text) {
559
+ return undefined;
560
+ }
561
+ const match = /^Build an Expo app for\s+(.+?)[.]\s*$/iu.exec(text);
562
+ return normalizeText(match?.[1] ?? text);
563
+ }
564
+ function normalizeSectionText(value) {
565
+ const normalized = normalizeLineEndings(value ?? '')
566
+ .replace(/<!--[\s\S]*?-->/gu, '')
567
+ .replace(/^\s*>/gmu, '')
568
+ .trim();
569
+ return normalized.length > 0 ? normalized : undefined;
570
+ }
571
+ function normalizeListSection(value) {
572
+ const text = normalizeSectionText(value);
573
+ if (!text) {
574
+ return undefined;
575
+ }
576
+ const bulletLines = text
577
+ .split('\n')
578
+ .map((line) => line.trim())
579
+ .filter(Boolean)
580
+ .map((line) => line.replace(/^[-*]\s+/u, ''))
581
+ .filter((line) => !line.startsWith('# TodoForContext'));
582
+ return bulletLines.length > 0 ? bulletLines.join('\n') : text;
583
+ }
584
+ function inferDataNeedSelections(value) {
585
+ const normalized = value.toLowerCase();
586
+ const selected = new Set();
587
+ for (const option of DATA_NEED_OPTIONS) {
588
+ if (normalized.includes(option.toLowerCase())) {
589
+ selected.add(option);
590
+ }
591
+ }
592
+ if (/\blocal\b|\bsqlite\b/u.test(normalized)) {
593
+ selected.add('Local UI/app state');
594
+ }
595
+ if (/\bauth\b|\buser account\b|\bsign in\b|\bsign-up\b|\bsign up\b/u.test(normalized)) {
596
+ selected.add('User accounts/authentication');
597
+ }
598
+ if (/\bdatabase\b|\bsupabase\b|\brecords\b/u.test(normalized)) {
599
+ selected.add('Backend database records');
600
+ }
601
+ if (/\bfile\b|\bimage\b|\bphoto\b|\bupload\b|\bstorage\b/u.test(normalized)) {
602
+ selected.add('File/image uploads or storage');
603
+ }
604
+ if (/\bapi\b|\bintegration\b/u.test(normalized)) {
605
+ selected.add('External APIs/integrations');
606
+ }
607
+ if (/\banalytics\b|\bevents\b/u.test(normalized)) {
608
+ selected.add('Analytics/events');
609
+ }
610
+ if (/\bpayments?\b|\bsubscription\b/u.test(normalized)) {
611
+ selected.add('Payments/subscriptions');
612
+ }
613
+ if (/\brealtime\b|\bcollaboration\b/u.test(normalized)) {
614
+ selected.add('Realtime/collaboration');
615
+ }
616
+ if (/\bpush\b|\bemail\b|\bnotification\b/u.test(normalized)) {
617
+ selected.add('Push/email notifications');
618
+ }
619
+ if (/\boffline\b|\bcache\b|\bsync\b/u.test(normalized)) {
620
+ selected.add('Offline sync/cache');
621
+ }
622
+ if (/\badmin\b|\bmoderation\b/u.test(normalized)) {
623
+ selected.add('Admin/moderation tools');
624
+ }
625
+ return Array.from(selected);
626
+ }
627
+ function inferDataStart(value) {
628
+ const normalized = value.toLowerCase();
629
+ if (normalized.includes('supabase')) {
630
+ return 'supabase';
631
+ }
632
+ if (normalized.includes('local dummy data') || normalized.includes('sqlite') || normalized.includes('local ui/app state')) {
633
+ return 'local';
634
+ }
635
+ return undefined;
636
+ }
637
+ function inferAuthBackend(value) {
638
+ const normalized = value.toLowerCase();
639
+ if (normalized.includes('supabase')) {
640
+ return 'supabase';
641
+ }
642
+ if (normalized.includes('firebase')) {
643
+ return 'firebase';
644
+ }
645
+ return undefined;
646
+ }
647
+ function extractPlatformDecisions(value, assignValue) {
648
+ const targetPlatforms = parsePlatformList(extractBulletValue(value, 'Target platforms'));
649
+ if (targetPlatforms.length > 0) {
650
+ assignValue('targetPlatforms', targetPlatforms, 'Platforms section');
651
+ }
652
+ const firstTargetPlatform = normalizeChoice(extractBulletValue(value, 'First MVP platform'), PLATFORM_OPTIONS);
653
+ if (firstTargetPlatform) {
654
+ assignValue('firstTargetPlatform', firstTargetPlatform, 'Platforms section');
655
+ }
656
+ const appDirectoryValue = extractBulletValue(value, 'Expo Router app directory');
657
+ if (appDirectoryValue?.includes('src/app')) {
658
+ assignValue('appDirectory', 'src', 'Platforms section');
659
+ }
660
+ else if (appDirectoryValue?.includes('app')) {
661
+ assignValue('appDirectory', 'root', 'Platforms section');
662
+ }
663
+ const organization = extractBulletValue(value, 'Platform-specific organization') ?? '';
664
+ if (organization.toLowerCase().includes('folder')) {
665
+ assignValue('platformStrategy', 'folders', 'Platforms section');
666
+ }
667
+ else if (organization.toLowerCase().includes('file')) {
668
+ assignValue('platformStrategy', 'files-only', 'Platforms section');
669
+ }
670
+ const layoutMode = extractBulletValue(value, 'Platform layout mode');
671
+ if (layoutMode?.toLowerCase().includes('platform-specific')) {
672
+ assignValue('platformLayouts', 'platform-specific', 'Platforms section');
673
+ }
674
+ else if (layoutMode?.toLowerCase().includes('shared')) {
675
+ assignValue('platformLayouts', 'shared', 'Platforms section');
676
+ }
677
+ const webOutput = normalizeChoice(extractBulletValue(value, 'Web output'), ['static', 'server', 'spa', 'none']);
678
+ if (webOutput) {
679
+ assignValue('webOutput', webOutput, 'Platforms section');
680
+ }
681
+ const deployedServer = (extractBulletValue(value, 'Deployed server') ?? '').toLowerCase();
682
+ if (deployedServer.includes('no deployed server') || deployedServer === 'none') {
683
+ assignValue('expoServerAdapter', 'none', 'Platforms section');
684
+ assignValue('customBackend', false, 'Platforms section');
685
+ }
686
+ else if (deployedServer.includes('eas')) {
687
+ assignValue('expoServerAdapter', 'eas', 'Platforms section');
688
+ }
689
+ else if (deployedServer.includes('express')) {
690
+ assignValue('expoServerAdapter', 'express', 'Platforms section');
691
+ }
692
+ else if (deployedServer.includes('bun')) {
693
+ assignValue('expoServerAdapter', 'bun', 'Platforms section');
694
+ }
695
+ else if (deployedServer.includes('custom')) {
696
+ assignValue('expoServerAdapter', 'other', 'Platforms section');
697
+ assignValue('customBackend', true, 'Platforms section');
698
+ }
699
+ const expoUi = parseBooleanValue(extractBulletValue(value, 'Expo UI'));
700
+ if (typeof expoUi === 'boolean') {
701
+ assignValue('usesExpoUi', expoUi, 'Platforms section');
702
+ }
703
+ const expoUiUniversal = parseBooleanValue(extractBulletValue(value, 'Expo UI Universal components'));
704
+ if (typeof expoUiUniversal === 'boolean') {
705
+ assignValue('usesExpoUiUniversalComponents', expoUiUniversal, 'Platforms section');
706
+ }
707
+ const expoNativeTabs = parseBooleanValue(extractBulletValue(value, 'Expo Native Tabs'));
708
+ if (typeof expoNativeTabs === 'boolean') {
709
+ assignValue('usesExpoNativeTabs', expoNativeTabs, 'Platforms section');
710
+ }
711
+ }
712
+ function extractPackageChoices(value, assignValue) {
713
+ const entries = normalizeListSection(value)
714
+ ?.split('\n')
715
+ .map((item) => item.trim().toLowerCase())
716
+ .filter(Boolean) ?? [];
717
+ for (const entry of entries) {
718
+ if (entry === 'uniwind') {
719
+ assignValue('stylingSystem', 'uniwind', 'Package Choices section');
720
+ }
721
+ else if (entry === 'nativewind') {
722
+ assignValue('stylingSystem', 'nativewind', 'Package Choices section');
723
+ }
724
+ else if (entry === 'tamagui') {
725
+ assignValue('stylingSystem', 'tamagui', 'Package Choices section');
726
+ }
727
+ else if (entry === 'restyle') {
728
+ assignValue('stylingSystem', 'restyle', 'Package Choices section');
729
+ }
730
+ else if (entry === 'supabase') {
731
+ assignValue('authBackend', 'supabase', 'Package Choices section');
732
+ }
733
+ else if (entry === 'firebase') {
734
+ assignValue('authBackend', 'firebase', 'Package Choices section');
735
+ }
736
+ }
737
+ }
738
+ function extractTechStackDecisions(value, assignValue) {
739
+ const language = normalizeChoice(extractKeyValue(value, 'Language'), ['typescript', 'javascript']);
740
+ if (language) {
741
+ assignValue('scriptLanguage', language, 'Tech Stack & MDS Onboarding section');
742
+ }
743
+ const packageManager = normalizeChoice(extractKeyValue(value, 'Package manager'), ['npm', 'pnpm', 'yarn', 'bun']);
744
+ if (packageManager) {
745
+ assignValue('packageManager', packageManager, 'Tech Stack & MDS Onboarding section');
746
+ }
747
+ const routing = (extractKeyValue(value, 'Routing') ?? '').toLowerCase();
748
+ if (routing.includes('react navigation')) {
749
+ assignValue('navigationLibrary', 'react-navigation', 'Tech Stack & MDS Onboarding section');
750
+ if (routing.includes('tabs')) {
751
+ assignValue('reactNavigationLayout', 'tabs', 'Tech Stack & MDS Onboarding section');
752
+ }
753
+ else if (routing.includes('drawer')) {
754
+ assignValue('reactNavigationLayout', 'drawer', 'Tech Stack & MDS Onboarding section');
755
+ }
756
+ else {
757
+ assignValue('reactNavigationLayout', 'stack', 'Tech Stack & MDS Onboarding section');
758
+ }
759
+ }
760
+ else if (routing.includes('expo router')) {
761
+ assignValue('navigationLibrary', 'expo-router', 'Tech Stack & MDS Onboarding section');
762
+ }
763
+ const styling = (extractKeyValue(value, 'Styling') ?? '').toLowerCase();
764
+ if (styling.includes('uniwind')) {
765
+ assignValue('stylingSystem', 'uniwind', 'Tech Stack & MDS Onboarding section');
766
+ }
767
+ else if (styling.includes('nativewind')) {
768
+ assignValue('stylingSystem', 'nativewind', 'Tech Stack & MDS Onboarding section');
769
+ }
770
+ else if (styling.includes('tamagui')) {
771
+ assignValue('stylingSystem', 'tamagui', 'Tech Stack & MDS Onboarding section');
772
+ }
773
+ else if (styling.includes('restyle')) {
774
+ assignValue('stylingSystem', 'restyle', 'Tech Stack & MDS Onboarding section');
775
+ }
776
+ else if (styling.includes('stylesheet')) {
777
+ assignValue('stylingSystem', 'stylesheet', 'Tech Stack & MDS Onboarding section');
778
+ }
779
+ const stateManagement = (extractKeyValue(value, 'State management') ?? '').toLowerCase();
780
+ if (stateManagement.includes('zustand')) {
781
+ assignValue('stateManagement', 'zustand', 'Tech Stack & MDS Onboarding section');
782
+ }
783
+ else if (stateManagement.includes('none')) {
784
+ assignValue('stateManagement', 'none', 'Tech Stack & MDS Onboarding section');
785
+ }
786
+ const auth = inferAuthBackend(extractKeyValue(value, 'Auth') ?? '');
787
+ if (auth) {
788
+ assignValue('authBackend', auth, 'Tech Stack & MDS Onboarding section');
789
+ }
790
+ const distribution = extractKeyValue(value, 'Distribution');
791
+ if (distribution) {
792
+ assignValue('deploymentTarget', distribution, 'Tech Stack & MDS Onboarding section');
793
+ }
794
+ const easUses = inferEasUses(extractKeyValue(value, 'EAS') ?? '');
795
+ if (easUses.length > 0) {
796
+ assignValue('easUses', easUses, 'Tech Stack & MDS Onboarding section');
797
+ assignValue('easSetup', true, 'Tech Stack & MDS Onboarding section');
798
+ }
799
+ extractOnboardingDecisionLines(value, assignValue);
800
+ }
801
+ function extractOnboardingDecisionLines(value, assignValue) {
802
+ const lines = normalizeLineEndings(value)
803
+ .split('\n')
804
+ .map((line) => line.trim())
805
+ .filter(Boolean);
806
+ for (const line of lines) {
807
+ const rawKeyValue = /^-\s+(.+?):\s+(.+)$/u.exec(line);
808
+ const key = normalizeText(rawKeyValue?.[1]);
809
+ const rawValue = normalizeText(rawKeyValue?.[2]);
810
+ if (!key || !rawValue) {
811
+ continue;
812
+ }
813
+ const loweredKey = key.toLowerCase();
814
+ if (loweredKey === 'advanced package setup') {
815
+ continue;
816
+ }
817
+ if (loweredKey === 'create expo starter components') {
818
+ assignValue('includeCreateExpoComponents', parseBooleanValue(rawValue), 'Onboarding decisions');
819
+ }
820
+ else if (loweredKey === 'latest expo sdk preference') {
821
+ // Captured for human context only; SDK targeting is enforced by the generator.
822
+ continue;
823
+ }
824
+ else if (loweredKey === 'expo ui') {
825
+ assignValue('usesExpoUi', parseBooleanValue(rawValue), 'Onboarding decisions');
826
+ }
827
+ else if (loweredKey === 'expo ui universal components') {
828
+ assignValue('usesExpoUiUniversalComponents', parseBooleanValue(rawValue), 'Onboarding decisions');
829
+ }
830
+ else if (loweredKey === 'expo native tabs') {
831
+ assignValue('usesExpoNativeTabs', parseBooleanValue(rawValue), 'Onboarding decisions');
832
+ }
833
+ else if (loweredKey === 'target platforms') {
834
+ const targetPlatforms = parsePlatformList(rawValue);
835
+ if (targetPlatforms.length > 0) {
836
+ assignValue('targetPlatforms', targetPlatforms, 'Onboarding decisions');
837
+ }
838
+ }
839
+ else if (loweredKey === 'first mvp platform') {
840
+ assignValue('firstTargetPlatform', normalizeChoice(rawValue, PLATFORM_OPTIONS), 'Onboarding decisions');
841
+ }
842
+ else if (loweredKey === 'expo router app directory') {
843
+ assignValue('appDirectory', rawValue.includes('src/app') ? 'src' : 'root', 'Onboarding decisions');
844
+ }
845
+ else if (loweredKey === 'platform-specific organization') {
846
+ assignValue('platformStrategy', rawValue.toLowerCase().includes('folder') ? 'folders' : 'files-only', 'Onboarding decisions');
847
+ }
848
+ else if (loweredKey === 'platform layout mode') {
849
+ assignValue('platformLayouts', rawValue.toLowerCase().includes('platform-specific') ? 'platform-specific' : 'shared', 'Onboarding decisions');
850
+ }
851
+ else if (loweredKey === 'web output') {
852
+ assignValue('webOutput', normalizeChoice(rawValue, ['static', 'server', 'spa', 'none']), 'Onboarding decisions');
853
+ }
854
+ else if (loweredKey === 'deployed server') {
855
+ extractPlatformDecisions(`- Deployed server: ${rawValue}`, assignValue);
856
+ }
857
+ else if (loweredKey === 'eas usage') {
858
+ const easUses = inferEasUses(rawValue);
859
+ if (easUses.length > 0) {
860
+ assignValue('easUses', easUses, 'Onboarding decisions');
861
+ assignValue('easSetup', true, 'Onboarding decisions');
862
+ }
863
+ }
864
+ else if (loweredKey === 'data start') {
865
+ assignValue('dataStart', inferDataStart(rawValue), 'Onboarding decisions');
866
+ }
867
+ else if (loweredKey === 'test-to-main safeguards') {
868
+ assignValue('testToMainSafeguards', parseBooleanValue(rawValue), 'Onboarding decisions');
869
+ }
870
+ }
871
+ }
872
+ function extractBulletValue(value, label) {
873
+ const match = new RegExp(`^-\\s+${escapeRegExp(label)}:\\s+(.+)$`, 'imu').exec(value);
874
+ return normalizeText(match?.[1]);
875
+ }
876
+ function extractKeyValue(value, label) {
877
+ const match = new RegExp(`^-\\s+(?:\\*\\*)?${escapeRegExp(label)}(?:\\*\\*)?:\\s+(.+)$`, 'imu').exec(value);
878
+ return normalizeText(match?.[1]);
879
+ }
880
+ function inferEasUses(value) {
881
+ const normalized = value.toLowerCase();
882
+ return EAS_USE_OPTIONS.filter((item) => normalized.includes(item.toLowerCase()));
883
+ }
884
+ function parsePlatformList(value) {
885
+ const normalized = value
886
+ ?.split(',')
887
+ .map((item) => item.replace(/[`]/gu, '').trim().toLowerCase())
888
+ .filter(Boolean) ?? [];
889
+ return normalized
890
+ .map((item) => {
891
+ if (item === 'ios' || item === 'android' || item === 'web' || item === 'apple-tv' || item === 'android-tv') {
892
+ return item;
893
+ }
894
+ return item.replace(/\s+/gu, '-');
895
+ })
896
+ .filter((item) => PLATFORM_OPTIONS.includes(item));
897
+ }
898
+ function parseBooleanValue(value) {
899
+ const normalized = value?.trim().toLowerCase();
900
+ if (!normalized) {
901
+ return undefined;
902
+ }
903
+ if (['yes', 'true', 'on'].includes(normalized)) {
904
+ return true;
905
+ }
906
+ if (['no', 'false', 'off'].includes(normalized)) {
907
+ return false;
908
+ }
909
+ return undefined;
910
+ }
911
+ function normalizeExtractedAnswerValue(id, value) {
912
+ switch (id) {
913
+ case 'confirmed':
914
+ case 'easSetup':
915
+ case 'customBackend':
916
+ case 'includeCreateExpoComponents':
917
+ case 'usesExpoUi':
918
+ case 'usesExpoUiUniversalComponents':
919
+ case 'usesExpoNativeTabs':
920
+ case 'guidelinesTemplate':
921
+ case 'testToMainSafeguards':
922
+ case 'saveDefaults':
923
+ return typeof value === 'boolean' ? value : undefined;
924
+ case 'dataNeedSelections':
925
+ case 'targetPlatforms':
926
+ case 'easUses':
927
+ return normalizeStringArray(value) ?? undefined;
928
+ case 'scriptLanguage':
929
+ return normalizeEnum(value, ['typescript', 'javascript']);
930
+ case 'packageManager':
931
+ return normalizeEnum(value, ['npm', 'pnpm', 'yarn', 'bun']);
932
+ case 'navigationLibrary':
933
+ return normalizeEnum(value, ['expo-router', 'react-navigation']);
934
+ case 'reactNavigationLayout':
935
+ return normalizeEnum(value, ['stack', 'tabs', 'drawer']);
936
+ case 'stylingSystem':
937
+ return normalizeEnum(value, ['uniwind', 'nativewind', 'tamagui', 'restyle', 'stylesheet']);
938
+ case 'stateManagement':
939
+ return normalizeEnum(value, ['zustand', 'none']);
940
+ case 'authBackend':
941
+ return normalizeEnum(value, ['none', 'supabase', 'firebase']);
942
+ case 'platformStrategy':
943
+ return normalizeEnum(value, ['folders', 'files-only']);
944
+ case 'appDirectory':
945
+ return normalizeEnum(value, ['src', 'root']);
946
+ case 'platformLayouts':
947
+ return normalizeEnum(value, ['shared', 'platform-specific']);
948
+ case 'webOutput':
949
+ return normalizeEnum(value, ['static', 'server', 'spa', 'none']);
950
+ case 'expoServerAdapter':
951
+ return normalizeEnum(value, ['eas', 'express', 'bun', 'other', 'none']);
952
+ case 'dataStart':
953
+ return normalizeEnum(value, ['local', 'supabase']);
954
+ case 'screens':
955
+ return normalizeOptionalDeferText(value);
956
+ default:
957
+ return normalizeText(value);
958
+ }
959
+ }
960
+ function areAnswerValuesEquivalent(id, left, right) {
961
+ if (Array.isArray(left) || Array.isArray(right)) {
962
+ return JSON.stringify(normalizeStringArray(left) ?? []) === JSON.stringify(normalizeStringArray(right) ?? []);
963
+ }
964
+ if (typeof left === 'boolean' || typeof right === 'boolean') {
965
+ return left === right;
966
+ }
967
+ return normalizeExtractedAnswerValue(id, left) === normalizeExtractedAnswerValue(id, right);
968
+ }
969
+ function normalizeProjectTitle(value) {
970
+ const trimmed = value.replace(/\s+project info$/iu, '').trim();
971
+ const normalized = normalizeText(trimmed);
972
+ return normalized ? displayNameFromProjectName(normalized) : undefined;
973
+ }
974
+ function normalizeLineEndings(value) {
975
+ return value.replace(/\r\n/gu, '\n');
976
+ }
977
+ function slugifyAppName(value) {
978
+ const normalized = normalizeText(value);
979
+ if (!normalized) {
980
+ return undefined;
981
+ }
982
+ const slug = normalized
983
+ .toLowerCase()
984
+ .replace(/[^a-z0-9]+/gu, '-')
985
+ .replace(/^-+|-+$/gu, '');
986
+ return slug || DEFAULT_PROJECT_NAME;
987
+ }
988
+ function displayNameFromProjectName(value) {
989
+ const normalized = normalizeText(value);
990
+ if (!normalized) {
991
+ return DEFAULT_DISPLAY_APP_NAME;
992
+ }
993
+ if (normalized.includes(' ')) {
994
+ return normalized;
995
+ }
996
+ return normalized
997
+ .replace(/[-_]+/gu, ' ')
998
+ .replace(/\b\w/gu, (match) => match.toUpperCase());
999
+ }
1000
+ function escapeRegExp(value) {
1001
+ return value.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
1002
+ }
366
1003
  export function buildCessIntakeStep(input) {
367
1004
  const cwd = input.cwd ? path.resolve(input.cwd) : process.cwd();
368
1005
  const parentDirProvided = normalizeText(input.parentDir);
369
1006
  const appNameProvided = normalizeText(input.appName);
370
1007
  const parentDir = normalizeParentDir(input.parentDir, cwd);
371
1008
  const appName = normalizeProjectName(input.appName);
1009
+ const appDisplayName = displayNameFromProjectName(input.appName);
372
1010
  const currentAnswers = normalizeCessIntakeAnswers(input.answers);
373
1011
  const resolvedPlan = resolveCessPlan({
374
1012
  parentDir,
375
- appName,
1013
+ appName: input.appName,
376
1014
  answers: currentAnswers,
377
1015
  cwd,
378
1016
  });
379
1017
  const context = {
380
1018
  parentDir,
381
1019
  appName,
1020
+ appDisplayName,
382
1021
  currentAnswers,
383
1022
  resolvedAnswers: resolvedPlan.answers,
384
1023
  onboardAnswers: resolvedPlan.onboardAnswers,
@@ -438,11 +1077,12 @@ export function resolveCessPlan(input) {
438
1077
  const cwd = input.cwd ? path.resolve(input.cwd) : process.cwd();
439
1078
  const parentDir = normalizeParentDir(input.parentDir, cwd);
440
1079
  const appName = normalizeProjectName(input.appName);
1080
+ const appDisplayName = displayNameFromProjectName(input.appName);
441
1081
  const projectPath = path.resolve(parentDir, appName);
442
1082
  const currentAnswers = normalizeCessIntakeAnswers(input.answers);
443
1083
  const onboardArgv = buildOnboardArgvFromCess(parentDir, appName, currentAnswers);
444
1084
  const onboardPlan = defaultOnboardPlan(onboardArgv, projectPath);
445
- const answers = buildResolvedCessAnswers(currentAnswers, parentDir, appName, onboardPlan.answers);
1085
+ const answers = buildResolvedCessAnswers(currentAnswers, parentDir, appName, appDisplayName, onboardPlan.answers);
446
1086
  const finalOnboardArgv = buildOnboardArgvFromCess(parentDir, appName, answers);
447
1087
  const finalOnboardPlan = defaultOnboardPlan(finalOnboardArgv, projectPath);
448
1088
  const createExpoStackFlags = buildCreateExpoStackFlags(answers);
@@ -476,6 +1116,7 @@ export function validateCessGenerationReadiness(input) {
476
1116
  const nextQuestion = findNextQuestion({
477
1117
  parentDir,
478
1118
  appName,
1119
+ appDisplayName: displayNameFromProjectName(input.appName),
479
1120
  currentAnswers,
480
1121
  resolvedAnswers: resolvedPlan.answers,
481
1122
  onboardAnswers: resolvedPlan.onboardAnswers,
@@ -535,7 +1176,6 @@ export function normalizeCessIntakeAnswers(answers) {
535
1176
  normalized.customBackendEntry = normalizeText(answers.customBackendEntry);
536
1177
  normalized.deploymentTarget = normalizeText(answers.deploymentTarget);
537
1178
  normalized.includeCreateExpoComponents = normalizeBoolean(answers.includeCreateExpoComponents);
538
- normalized.useLatestExpoSdk = normalizeBoolean(answers.useLatestExpoSdk);
539
1179
  normalized.usesExpoUi = normalizeBoolean(answers.usesExpoUi);
540
1180
  normalized.usesExpoUiUniversalComponents = normalizeBoolean(answers.usesExpoUiUniversalComponents);
541
1181
  normalized.usesExpoNativeTabs = normalizeBoolean(answers.usesExpoNativeTabs);
@@ -614,12 +1254,6 @@ export function buildMdsFlags(appName, onboardAnswers, intakeAnswers) {
614
1254
  else {
615
1255
  flags.push('--mds-no-create-expo-components');
616
1256
  }
617
- if (onboardAnswers.useLatestExpoSdk) {
618
- flags.push('--mds-latest-expo-sdk');
619
- }
620
- else {
621
- flags.push('--mds-no-latest-expo-sdk');
622
- }
623
1257
  if (onboardAnswers.usesExpoUi) {
624
1258
  flags.push('--mds-expo-ui');
625
1259
  }
@@ -692,7 +1326,7 @@ export function buildCessSummaryLines(parentDir, appName, answers, onboardAnswer
692
1326
  ? 'web output: none'
693
1327
  : `web output: ${onboardAnswers.webOutput}, deployed server: ${onboardAnswers.deployedServer}`;
694
1328
  return [
695
- `app: ${appName} at ${parentDir}`,
1329
+ `app: ${onboardAnswers.appName} (folder: ${appName}) at ${parentDir}`,
696
1330
  `stack: ${stackLine}`,
697
1331
  `audience: ${onboardAnswers.audience}`,
698
1332
  `core flows: ${onboardAnswers.coreFlows}`,
@@ -701,7 +1335,7 @@ export function buildCessSummaryLines(parentDir, appName, answers, onboardAnswer
701
1335
  `data start: ${onboardAnswers.dataStart}, test-to-main: ${onboardAnswers.testToMainSafeguards ? 'on' : 'off'}, guidelines template: ${answers.guidelinesTemplate === false ? 'off' : 'on'}, save defaults: ${answers.saveDefaults ? 'on' : 'off'}`,
702
1336
  ];
703
1337
  }
704
- function buildResolvedCessAnswers(currentAnswers, parentDir, appName, onboardAnswers) {
1338
+ function buildResolvedCessAnswers(currentAnswers, parentDir, appName, appDisplayName, onboardAnswers) {
705
1339
  const targetPlatforms = currentAnswers.targetPlatforms ?? onboardAnswers.targetPlatforms;
706
1340
  const usesExpoUi = currentAnswers.usesExpoUi ?? onboardAnswers.usesExpoUi;
707
1341
  const screens = currentAnswers.screens === 'defer' ? '' : currentAnswers.screens;
@@ -715,7 +1349,7 @@ function buildResolvedCessAnswers(currentAnswers, parentDir, appName, onboardAns
715
1349
  stateManagement: currentAnswers.stateManagement ?? STACK_DEFAULTS.stateManagement,
716
1350
  authBackend: currentAnswers.authBackend ?? STACK_DEFAULTS.authBackend,
717
1351
  easSetup: currentAnswers.easSetup ?? STACK_DEFAULTS.easSetup,
718
- displayAppName: currentAnswers.displayAppName ?? onboardAnswers.appName ?? appName,
1352
+ displayAppName: currentAnswers.displayAppName ?? appDisplayName ?? onboardAnswers.appName ?? appName,
719
1353
  audience: currentAnswers.audience ?? onboardAnswers.audience,
720
1354
  coreFlows: currentAnswers.coreFlows ?? onboardAnswers.coreFlows ?? AGENT_DERIVED_CORE_FLOWS,
721
1355
  screens,
@@ -732,7 +1366,6 @@ function buildResolvedCessAnswers(currentAnswers, parentDir, appName, onboardAns
732
1366
  customBackendEntry: currentAnswers.customBackendEntry ?? onboardAnswers.customBackendEntry,
733
1367
  deploymentTarget: currentAnswers.deploymentTarget ?? onboardAnswers.deploymentTarget,
734
1368
  includeCreateExpoComponents: currentAnswers.includeCreateExpoComponents ?? onboardAnswers.includeCreateExpoComponents,
735
- useLatestExpoSdk: currentAnswers.useLatestExpoSdk ?? onboardAnswers.useLatestExpoSdk,
736
1369
  usesExpoUi,
737
1370
  usesExpoUiUniversalComponents: currentAnswers.usesExpoUiUniversalComponents ??
738
1371
  (usesExpoUi ? onboardAnswers.usesExpoUiUniversalComponents : false),
@@ -754,14 +1387,21 @@ function buildOnboardArgvFromCess(parentDir, appName, answers) {
754
1387
  return {
755
1388
  project: path.resolve(parentDir, appName),
756
1389
  yes: true,
757
- appName: normalizeText(answers.displayAppName) ?? appName,
1390
+ appName: normalizeText(answers.displayAppName) ?? displayNameFromProjectName(appName),
1391
+ generatorScriptLanguage: answers.scriptLanguage,
1392
+ generatorPackageManager: answers.packageManager,
1393
+ generatorNavigationLibrary: answers.navigationLibrary,
1394
+ generatorReactNavigationLayout: answers.reactNavigationLayout,
1395
+ generatorStylingSystem: answers.stylingSystem,
1396
+ generatorStateManagement: answers.stateManagement,
1397
+ generatorAuthBackend: answers.authBackend,
1398
+ generatorEasSetup: answers.easSetup,
758
1399
  audience: normalizeText(answers.audience),
759
1400
  coreFlows: normalizeText(answers.coreFlows),
760
1401
  screens,
761
1402
  dataNeeds,
762
1403
  deploymentTarget: normalizeText(answers.deploymentTarget),
763
1404
  createExpoComponents: answers.includeCreateExpoComponents,
764
- latestExpoSdk: answers.useLatestExpoSdk,
765
1405
  platforms: targetPlatforms,
766
1406
  firstPlatform: normalizeText(answers.firstTargetPlatform),
767
1407
  platformStrategy: answers.platformStrategy,
@@ -825,7 +1465,7 @@ function normalizeParentDir(parentDir, cwd) {
825
1465
  return path.resolve(cwd, normalizeText(parentDir) ?? '.');
826
1466
  }
827
1467
  function normalizeProjectName(appName) {
828
- return normalizeText(appName) ?? DEFAULT_PROJECT_NAME;
1468
+ return slugifyAppName(appName) ?? DEFAULT_PROJECT_NAME;
829
1469
  }
830
1470
  function normalizeBoolean(value) {
831
1471
  return typeof value === 'boolean' ? value : undefined;
@@ -833,6 +1473,9 @@ function normalizeBoolean(value) {
833
1473
  function normalizeEnum(value, choices) {
834
1474
  return typeof value === 'string' && choices.includes(value) ? value : undefined;
835
1475
  }
1476
+ function normalizeChoice(value, choices) {
1477
+ return normalizeEnum(value, choices);
1478
+ }
836
1479
  function normalizeText(value) {
837
1480
  if (typeof value !== 'string') {
838
1481
  return undefined;