@mr.dj2u/cli 0.1.12 → 0.1.14

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 (55) hide show
  1. package/bundles/claude-code/.claude-plugin/plugin.json +20 -20
  2. package/bundles/claude-code/.mcp.json +11 -11
  3. package/bundles/claude-code/agents/mds.md +36 -36
  4. package/bundles/claude-code/commands/create-expo-super-stack.md +34 -32
  5. package/bundles/claude-code/skills/create-expo-super-stack/SKILL.md +9 -7
  6. package/bundles/claude-code/skills/super-stack-startup/SKILL.md +4 -4
  7. package/bundles/codex/.codex-plugin/plugin.json +42 -42
  8. package/bundles/codex/.mcp.json +11 -11
  9. package/bundles/codex/README.md +51 -51
  10. package/bundles/codex/commands/create-expo-super-stack.md +34 -32
  11. package/bundles/codex/skills/super-stack-startup/SKILL.md +34 -34
  12. package/bundles/codex/skills/workflow-continue-development/SKILL.md +48 -48
  13. package/bundles/codex/skills/workflow-create-expo-super-stack/SKILL.md +47 -45
  14. package/bundles/codex/skills/workflow-fix-seo/SKILL.md +42 -42
  15. package/bundles/codex/skills/workflow-prepare-deploy/SKILL.md +42 -42
  16. package/bundles/codex/skills/workflow-project-research-plan/SKILL.md +42 -42
  17. package/bundles/codex/skills/workflow-push-merge-loop/SKILL.md +37 -37
  18. package/bundles/codex/skills/workflow-review-expo-project/SKILL.md +42 -42
  19. package/bundles/codex/skills/workflow-run-doctor/SKILL.md +51 -51
  20. package/bundles/codex/skills/workflow-wrap-up/SKILL.md +80 -80
  21. package/bundles/vscode-copilot/.github/agents/mds.agent.md +22 -22
  22. package/bundles/vscode-copilot/.github/copilot-instructions.md +8 -8
  23. package/bundles/vscode-copilot/.github/prompts/continue-development.prompt.md +40 -40
  24. package/bundles/vscode-copilot/.github/prompts/create-expo-super-stack.prompt.md +39 -37
  25. package/bundles/vscode-copilot/.github/prompts/fix-seo.prompt.md +34 -34
  26. package/bundles/vscode-copilot/.github/prompts/prepare-deploy.prompt.md +34 -34
  27. package/bundles/vscode-copilot/.github/prompts/project-research-plan.prompt.md +34 -34
  28. package/bundles/vscode-copilot/.github/prompts/push-merge-loop.prompt.md +30 -30
  29. package/bundles/vscode-copilot/.github/prompts/review-expo-project.prompt.md +34 -34
  30. package/bundles/vscode-copilot/.github/prompts/run-doctor.prompt.md +43 -43
  31. package/bundles/vscode-copilot/.github/prompts/wrap-up.prompt.md +72 -72
  32. package/bundles/vscode-copilot/.github/skills/super-stack-startup/SKILL.md +39 -39
  33. package/bundles/vscode-copilot/.vscode/mcp.json +11 -11
  34. package/bundles/vscode-copilot/user/.copilot/agents/mds.agent.md +22 -22
  35. package/bundles/vscode-copilot/user/.copilot/instructions.md +8 -8
  36. package/bundles/vscode-copilot/user/.copilot/skills/super-stack-startup/SKILL.md +39 -39
  37. package/bundles/vscode-copilot/user/.copilot/skills/workflow-create-expo-super-stack/SKILL.md +39 -37
  38. package/dist/cess-intake.d.ts +16 -0
  39. package/dist/cess-intake.d.ts.map +1 -1
  40. package/dist/cess-intake.js +677 -15
  41. package/dist/cess-intake.js.map +1 -1
  42. package/dist/commands/mcp-install.d.ts +2 -2
  43. package/dist/commands/mcp-install.js +1 -1
  44. package/dist/commands/onboard.d.ts +8 -0
  45. package/dist/commands/onboard.d.ts.map +1 -1
  46. package/dist/commands/onboard.js +8 -0
  47. package/dist/commands/onboard.js.map +1 -1
  48. package/dist/commands/stylist.d.ts.map +1 -1
  49. package/dist/commands/stylist.js +5 -1
  50. package/dist/commands/stylist.js.map +1 -1
  51. package/dist/project-memory.d.ts +8 -0
  52. package/dist/project-memory.d.ts.map +1 -1
  53. package/dist/project-memory.js +89 -5
  54. package/dist/project-memory.js.map +1 -1
  55. package/package.json +3 -3
@@ -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?',
@@ -353,22 +348,676 @@ const CESS_QUESTIONS = [
353
348
  defaultValue: () => false,
354
349
  },
355
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
+ }
356
1003
  export function buildCessIntakeStep(input) {
357
1004
  const cwd = input.cwd ? path.resolve(input.cwd) : process.cwd();
358
1005
  const parentDirProvided = normalizeText(input.parentDir);
359
1006
  const appNameProvided = normalizeText(input.appName);
360
1007
  const parentDir = normalizeParentDir(input.parentDir, cwd);
361
1008
  const appName = normalizeProjectName(input.appName);
1009
+ const appDisplayName = displayNameFromProjectName(input.appName);
362
1010
  const currentAnswers = normalizeCessIntakeAnswers(input.answers);
363
1011
  const resolvedPlan = resolveCessPlan({
364
1012
  parentDir,
365
- appName,
1013
+ appName: input.appName,
366
1014
  answers: currentAnswers,
367
1015
  cwd,
368
1016
  });
369
1017
  const context = {
370
1018
  parentDir,
371
1019
  appName,
1020
+ appDisplayName,
372
1021
  currentAnswers,
373
1022
  resolvedAnswers: resolvedPlan.answers,
374
1023
  onboardAnswers: resolvedPlan.onboardAnswers,
@@ -428,11 +1077,12 @@ export function resolveCessPlan(input) {
428
1077
  const cwd = input.cwd ? path.resolve(input.cwd) : process.cwd();
429
1078
  const parentDir = normalizeParentDir(input.parentDir, cwd);
430
1079
  const appName = normalizeProjectName(input.appName);
1080
+ const appDisplayName = displayNameFromProjectName(input.appName);
431
1081
  const projectPath = path.resolve(parentDir, appName);
432
1082
  const currentAnswers = normalizeCessIntakeAnswers(input.answers);
433
1083
  const onboardArgv = buildOnboardArgvFromCess(parentDir, appName, currentAnswers);
434
1084
  const onboardPlan = defaultOnboardPlan(onboardArgv, projectPath);
435
- const answers = buildResolvedCessAnswers(currentAnswers, parentDir, appName, onboardPlan.answers);
1085
+ const answers = buildResolvedCessAnswers(currentAnswers, parentDir, appName, appDisplayName, onboardPlan.answers);
436
1086
  const finalOnboardArgv = buildOnboardArgvFromCess(parentDir, appName, answers);
437
1087
  const finalOnboardPlan = defaultOnboardPlan(finalOnboardArgv, projectPath);
438
1088
  const createExpoStackFlags = buildCreateExpoStackFlags(answers);
@@ -466,6 +1116,7 @@ export function validateCessGenerationReadiness(input) {
466
1116
  const nextQuestion = findNextQuestion({
467
1117
  parentDir,
468
1118
  appName,
1119
+ appDisplayName: displayNameFromProjectName(input.appName),
469
1120
  currentAnswers,
470
1121
  resolvedAnswers: resolvedPlan.answers,
471
1122
  onboardAnswers: resolvedPlan.onboardAnswers,
@@ -675,7 +1326,7 @@ export function buildCessSummaryLines(parentDir, appName, answers, onboardAnswer
675
1326
  ? 'web output: none'
676
1327
  : `web output: ${onboardAnswers.webOutput}, deployed server: ${onboardAnswers.deployedServer}`;
677
1328
  return [
678
- `app: ${appName} at ${parentDir}`,
1329
+ `app: ${onboardAnswers.appName} (folder: ${appName}) at ${parentDir}`,
679
1330
  `stack: ${stackLine}`,
680
1331
  `audience: ${onboardAnswers.audience}`,
681
1332
  `core flows: ${onboardAnswers.coreFlows}`,
@@ -684,7 +1335,7 @@ export function buildCessSummaryLines(parentDir, appName, answers, onboardAnswer
684
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'}`,
685
1336
  ];
686
1337
  }
687
- function buildResolvedCessAnswers(currentAnswers, parentDir, appName, onboardAnswers) {
1338
+ function buildResolvedCessAnswers(currentAnswers, parentDir, appName, appDisplayName, onboardAnswers) {
688
1339
  const targetPlatforms = currentAnswers.targetPlatforms ?? onboardAnswers.targetPlatforms;
689
1340
  const usesExpoUi = currentAnswers.usesExpoUi ?? onboardAnswers.usesExpoUi;
690
1341
  const screens = currentAnswers.screens === 'defer' ? '' : currentAnswers.screens;
@@ -698,7 +1349,7 @@ function buildResolvedCessAnswers(currentAnswers, parentDir, appName, onboardAns
698
1349
  stateManagement: currentAnswers.stateManagement ?? STACK_DEFAULTS.stateManagement,
699
1350
  authBackend: currentAnswers.authBackend ?? STACK_DEFAULTS.authBackend,
700
1351
  easSetup: currentAnswers.easSetup ?? STACK_DEFAULTS.easSetup,
701
- displayAppName: currentAnswers.displayAppName ?? onboardAnswers.appName ?? appName,
1352
+ displayAppName: currentAnswers.displayAppName ?? appDisplayName ?? onboardAnswers.appName ?? appName,
702
1353
  audience: currentAnswers.audience ?? onboardAnswers.audience,
703
1354
  coreFlows: currentAnswers.coreFlows ?? onboardAnswers.coreFlows ?? AGENT_DERIVED_CORE_FLOWS,
704
1355
  screens,
@@ -736,7 +1387,15 @@ function buildOnboardArgvFromCess(parentDir, appName, answers) {
736
1387
  return {
737
1388
  project: path.resolve(parentDir, appName),
738
1389
  yes: true,
739
- 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,
740
1399
  audience: normalizeText(answers.audience),
741
1400
  coreFlows: normalizeText(answers.coreFlows),
742
1401
  screens,
@@ -806,7 +1465,7 @@ function normalizeParentDir(parentDir, cwd) {
806
1465
  return path.resolve(cwd, normalizeText(parentDir) ?? '.');
807
1466
  }
808
1467
  function normalizeProjectName(appName) {
809
- return normalizeText(appName) ?? DEFAULT_PROJECT_NAME;
1468
+ return slugifyAppName(appName) ?? DEFAULT_PROJECT_NAME;
810
1469
  }
811
1470
  function normalizeBoolean(value) {
812
1471
  return typeof value === 'boolean' ? value : undefined;
@@ -814,6 +1473,9 @@ function normalizeBoolean(value) {
814
1473
  function normalizeEnum(value, choices) {
815
1474
  return typeof value === 'string' && choices.includes(value) ? value : undefined;
816
1475
  }
1476
+ function normalizeChoice(value, choices) {
1477
+ return normalizeEnum(value, choices);
1478
+ }
817
1479
  function normalizeText(value) {
818
1480
  if (typeof value !== 'string') {
819
1481
  return undefined;