@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
@@ -2,6 +2,7 @@ import { access, mkdir, readFile, unlink, writeFile } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import { DEFAULT_STYLIST_THEME, renderGlobalCssThemeBlock, renderThemeTokensFile, } from './stylist-theme.js';
5
+ import { generateProjectRoadmap } from './roadmap.js';
5
6
  const SOFTWARE_MANSION_CORE_DEPENDENCIES = {
6
7
  'react-native-gesture-handler': '~2.30.0',
7
8
  'react-native-reanimated': '4.3.1',
@@ -24,6 +25,10 @@ const STYLIST_DEPENDENCIES = {
24
25
  '@react-native-async-storage/async-storage': '2.2.0',
25
26
  'reanimated-color-picker': '^4.2.0',
26
27
  };
28
+ const STYLIST_DEV_DEPENDENCIES = {
29
+ '@types/node': '^25.9.1',
30
+ tailwindcss: '^4.2.4',
31
+ };
27
32
  const EXPO_UI_DEPENDENCIES = {
28
33
  '@expo/ui': '~56.0.14',
29
34
  };
@@ -34,12 +39,14 @@ const UNIWIND_DEV_DEPENDENCIES = {
34
39
  tailwindcss: '^4.2.4',
35
40
  };
36
41
  const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
37
- const MDS_CLI_VERSION = '0.1.11';
42
+ const MDS_CLI_VERSION = '0.1.13';
38
43
  const MDS_NPX_COMMAND = 'npx mds';
39
44
  const DEFAULT_GUIDELINES_TEMPLATE_PATH = path.join(PACKAGE_ROOT, 'templates', 'project', 'guidelines.md');
40
45
  const STYLIST_SCREEN_TEMPLATE_PATH = path.join(PACKAGE_ROOT, 'templates', 'stylist-screen.template.tsx');
41
46
  const EMBEDDED_FONTS_TEMPLATE_PATH = path.join(PACKAGE_ROOT, 'templates', 'embedded-fonts.template.ts');
42
47
  const EXPO_SDK_56_SCREEN_UNIVERSAL_TEMPLATE_PATH = path.join(PACKAGE_ROOT, 'templates', 'expo-sdk-56-screen-universal.template.tsx');
48
+ const CESS_SNAPSHOT_START = '<!-- MDS_CESS_SNAPSHOT_START -->';
49
+ const CESS_SNAPSHOT_END = '<!-- MDS_CESS_SNAPSHOT_END -->';
43
50
  const INFO_HEADINGS = [
44
51
  'Overview',
45
52
  'Target Users',
@@ -75,18 +82,39 @@ export async function scaffoldProjectMemory(projectPath, answers, options = {})
75
82
  await mkdir(projectDir, { recursive: true });
76
83
  const force = Boolean(options.force);
77
84
  const infoPath = path.join(projectDir, 'info.md');
85
+ const todoPath = path.join(projectDir, 'todo.md');
78
86
  const stylePath = path.join(projectDir, 'style.md');
79
87
  const existingInfo = await readOptionalText(infoPath);
80
88
  const existingStyle = await readOptionalText(stylePath);
81
89
  const guidelines = await resolveGuidelines(answers, options);
82
90
  const results = await Promise.all([
83
91
  writeProjectMemoryFile(infoPath, renderInfo(projectPath, answers, existingInfo), force, true),
84
- writeIfAllowed(path.join(projectDir, 'todo.md'), renderTodo(answers), force),
92
+ writeIfAllowed(todoPath, renderTodo(answers), force),
85
93
  writeProjectMemoryFile(stylePath, renderStyle(answers, existingStyle), force, true),
86
94
  writeIfAllowed(path.join(projectDir, 'guidelines.md'), guidelines, force),
87
95
  writeIfAllowed(path.join(projectPath, 'AGENTS.md'), renderAgentInstructions(answers), force),
88
96
  writeIfAllowed(path.join(projectPath, 'CLAUDE.md'), renderClaudeMd(answers), force),
89
97
  ]);
98
+ const roadmapResult = await generateProjectRoadmap(projectPath, {
99
+ write: true,
100
+ preserveStatus: true,
101
+ });
102
+ const todoResultIndex = results.findIndex((result) => result.filePath === todoPath);
103
+ if (todoResultIndex >= 0) {
104
+ const todoResult = results[todoResultIndex];
105
+ if (todoResult) {
106
+ results[todoResultIndex] = {
107
+ filePath: todoResult.filePath,
108
+ wrote: todoResult.wrote || roadmapResult.wrote,
109
+ };
110
+ }
111
+ }
112
+ else {
113
+ results.push({
114
+ filePath: todoPath,
115
+ wrote: roadmapResult.wrote,
116
+ });
117
+ }
90
118
  if (shouldGenerateIntakeAgentHandoff(answers, existingInfo, existingStyle)) {
91
119
  results.push(await writeIfAllowed(path.join(projectDir, 'intake-agent.md'), renderIntakeAgentHandoff(answers), force));
92
120
  }
@@ -188,6 +216,7 @@ export async function scaffoldRichBoilerplate(projectPath, answers, force, optio
188
216
  }
189
217
  export function renderInfo(projectPath, answers, existingInfo) {
190
218
  const importedNotes = renderImportedNotes(existingInfo, INFO_HEADINGS);
219
+ const hasConcreteCoreFlows = !isGenericCoreFlowsText(answers.coreFlows);
191
220
  return [
192
221
  `# ${answers.appName} Project Info`,
193
222
  '',
@@ -209,11 +238,15 @@ export function renderInfo(projectPath, answers, existingInfo) {
209
238
  '',
210
239
  '## Core Features',
211
240
  '',
212
- `Derived from the first planned flows: ${answers.coreFlows}`,
241
+ hasConcreteCoreFlows
242
+ ? `Derived from the first planned flows: ${answers.coreFlows}`
243
+ : '# TodoForContext(optional): List the first core features the MVP should deliver.',
213
244
  '',
214
245
  '## Core User Flows',
215
246
  '',
216
- answers.coreFlows,
247
+ hasConcreteCoreFlows
248
+ ? answers.coreFlows
249
+ : '# TodoForContext(optional): Describe the first real end-to-end user flow the MVP should support.',
217
250
  '',
218
251
  '## Must-Include Screens Or Flows',
219
252
  '',
@@ -263,12 +296,6 @@ export function renderInfo(projectPath, answers, existingInfo) {
263
296
  '',
264
297
  '## Questions To Revisit',
265
298
  '',
266
- ...(hasThinOnboardingAnswers(answers)
267
- ? [
268
- '- Replace generic onboarding defaults with app-specific decisions.',
269
- '- Confirm the exact first user flow before production buildout starts.',
270
- ]
271
- : []),
272
299
  '',
273
300
  '## Resources',
274
301
  '',
@@ -281,12 +308,18 @@ export function renderInfo(projectPath, answers, existingInfo) {
281
308
  '',
282
309
  '> Quick-reference stack summary for agents and collaborators. Fill in or correct any items marked below.',
283
310
  '',
311
+ CESS_SNAPSHOT_START,
312
+ '```json',
313
+ JSON.stringify(buildCessSnapshot(answers), null, 2),
314
+ '```',
315
+ CESS_SNAPSHOT_END,
316
+ '',
284
317
  `- **App:** ${answers.appName} — ${answers.audience}`,
285
- '- **Language:** TypeScript',
286
- '- **Package manager:** # TodoForContext(optional): pnpm / npm / yarn / bun',
287
- `- **Routing:** Expo Router (${formatAppDirectory(answers.appDirectory)})`,
318
+ `- **Language:** ${formatGeneratorLanguage(answers.generatorScriptLanguage)}`,
319
+ `- **Package manager:** ${formatGeneratorPackageManager(answers.generatorPackageManager)}`,
320
+ `- **Routing:** ${formatGeneratorRouting(answers)}`,
288
321
  `- **Styling:** ${formatStyleStack(answers)}`,
289
- '- **State management:** # TodoForContext(optional): Zustand / Jotai / React context / none',
322
+ `- **State management:** ${formatGeneratorStateManagement(answers.generatorStateManagement)}`,
290
323
  `- **Auth:** ${formatAuthSummary(answers)}`,
291
324
  `- **Data:** ${formatDataStart(answers.dataStart)}`,
292
325
  `- **Platforms:** ${answers.targetPlatforms.join(', ') || 'none selected'}, first MVP target: ${answers.firstTargetPlatform}`,
@@ -299,7 +332,7 @@ export function renderInfo(projectPath, answers, existingInfo) {
299
332
  '',
300
333
  `- Advanced package setup: ${formatBoolean(answers.advancedPackageSetup)}`,
301
334
  `- Create Expo starter components: ${formatBoolean(answers.includeCreateExpoComponents)}`,
302
- `- Latest Expo SDK preference: ${formatBoolean(answers.useLatestExpoSdk)}`,
335
+ `- EAS starter selected during generation: ${formatBoolean(answers.generatorEasSetup ?? answers.easUses.length > 0)}`,
303
336
  `- MDS guidelines template: yes`,
304
337
  `- Expo UI: ${formatBoolean(answers.usesExpoUi)}`,
305
338
  `- Expo UI Universal components: ${formatBoolean(answers.usesExpoUiUniversalComponents)}`,
@@ -311,7 +344,6 @@ export function renderInfo(projectPath, answers, existingInfo) {
311
344
  ].join('\n');
312
345
  }
313
346
  export function renderTodo(answers) {
314
- const needsReview = hasThinOnboardingAnswers(answers);
315
347
  return [
316
348
  `# ${answers.appName} TODO`,
317
349
  '',
@@ -320,55 +352,31 @@ export function renderTodo(answers) {
320
352
  '- [ ] Browse exposition pages to understand included base packages.',
321
353
  "- [ ] Review styling in the 'Stylist' page.",
322
354
  '- [ ] Review `project/` files for accuracy and planning adjustments.',
323
- '- [ ] Decide whether to keep or defer `eject-stylist`; mark the decision explicitly.',
355
+ '- [ ] Run or defer `eject-stylist`; mark this todo done after ejection or deciding to defer (if you want to keep the stylist around for tinkering).',
324
356
  '- [ ] Run `mds eject exposition` and keep only the generated sections you want to retain.',
325
- '- [ ] Resolve every `# TodoForContext(optional):` marker by filling the section underneath or deleting the marker line to acknowledge no extra context is needed. (There may be none of these if the agent was thorough in onboarding, but if there are any, they should be resolved before development starts.)',
326
- '',
327
- '- [x] Confirm app purpose, audience, and primary flows in `project/info.md`.',
357
+ '- [ ] Resolve every `# TodoForContext(optional):` marker in `project/info.md` by filling the section underneath or deleting the marker line to acknowledge no extra context is needed.',
328
358
  '- [ ] Confirm visual direction in `project/style.md` after using the Stylist page.',
359
+ '- [ ] After the `project/info.md` markers are resolved, refresh the agent-derived roadmap from `project/info.md` and review it for accuracy.',
329
360
  '- [ ] Keep or prune included package examples after reviewing `/exposition`.',
330
361
  '- [ ] Remove exposition pages before production once their lessons are absorbed.',
331
- ...(needsReview
332
- ? [
333
- '- [ ] Replace generic onboarding placeholders with real app decisions before full implementation.',
334
- ]
335
- : []),
336
362
  '',
337
363
  '## Phase 1: App Shell And First Flow',
338
364
  '',
339
- `- [ ] Build the MVP first for ${answers.firstTargetPlatform}.`,
340
- `- [ ] Establish app shell, navigation, layouts, and route groups in ${formatAppDirectory(answers.appDirectory)}.`,
341
- `- [ ] Use ${formatPlatformLayoutMode(answers.platformLayoutMode)} unless project memory is updated.`,
342
- `- [ ] Implement the first core flow from project info: ${answers.coreFlows}.`,
343
- '- [ ] Keep route files thin and move real UI into feature screens.',
344
- '- [ ] Apply Stylist synced theme tokens to production UI components and screens.',
365
+ `- [ ] Establish the app shell and first implementation-ready route in ${formatAppDirectory(answers.appDirectory)}.`,
366
+ '- [ ] Implement the first concrete product flow from `project/info.md` and the roadmap.',
345
367
  '',
346
368
  '## Phase 2: Data Layer',
347
369
  '',
348
- `- [ ] Start with ${formatDataStart(answers.dataStart)}.`,
349
- ...(answers.dataStart === 'local'
350
- ? [
351
- '- [ ] Use the local Expo SQLite demo as the first adapter.',
352
- '- [ ] Replace the local adapter with Supabase when the product needs synced/authenticated data.',
353
- ]
354
- : [
355
- '- [ ] Create separate Supabase projects for test/staging and production.',
356
- '- [ ] Wire publishable client keys through environment files, never service-role keys.',
357
- ]),
358
- '- [ ] Verify data requirements against `project/info.md` before adding tables or auth.',
370
+ `- [ ] Implement the initial data layer using ${formatDataStart(answers.dataStart)}.`,
371
+ ...(answers.dataStart === 'supabase'
372
+ ? ['- [ ] Create separate Supabase projects for test/staging and production.']
373
+ : []),
359
374
  '',
360
375
  '## Phase 3: Complete Product Flows',
361
376
  '',
362
377
  '- [ ] Build the remaining core flows from `project/info.md` phase by phase.',
363
- '- [ ] Add shared state only when state crosses screens or features.',
364
- '- [ ] Verify each selected platform after the MVP flow works.',
365
- ...answers.targetPlatforms.map((platform) => `- [ ] Verify ${platform} behavior.`),
366
- ...(answers.usesExpoUi ? ['- [ ] Add Expo UI examples where they improve native feel.'] : []),
367
- ...(answers.usesExpoUiUniversalComponents
368
- ? ['- [ ] Review the Expo UI Universal examples before replacing generated exposition code.']
369
- : []),
370
- ...(answers.usesExpoNativeTabs
371
- ? ['- [ ] Prototype Expo Native Tabs for mobile navigation.']
378
+ ...(answers.targetPlatforms.length > 1
379
+ ? ['- [ ] Adapt the working MVP flow for the remaining target platforms after the primary flow is stable.']
372
380
  : []),
373
381
  ...(answers.easUses.length > 0
374
382
  ? answers.easUses.map((item) => `- [ ] Configure EAS for ${item}.`)
@@ -376,11 +384,11 @@ export function renderTodo(answers) {
376
384
  '',
377
385
  '## Phase 4: Polish, Safeguards, And Release',
378
386
  '',
379
- '- [ ] Prune unused Software Mansion examples and remove unneeded packages.',
380
387
  '- [ ] Run `mds doctor --ci` and address errors.',
381
388
  ...(answers.testToMainSafeguards
382
389
  ? [
383
390
  '- [ ] Follow `project/release-flow.md` for test-to-main development.',
391
+ '- [ ] Complete the one-time GitHub repo setup from `project/release-flow.md` so `test` and `main` are protected correctly.',
384
392
  '- [ ] Add GitHub branch protection so PR checks pass before merging into `test` or `main`.',
385
393
  ]
386
394
  : ['- [ ] Decide on release safeguards before production work begins.']),
@@ -390,7 +398,6 @@ export function renderTodo(answers) {
390
398
  ...(answers.deployedServer !== 'none'
391
399
  ? [`- [ ] Plan deployed server work: ${formatServerChoice(answers.deployedServer)}.`]
392
400
  : []),
393
- '- [ ] Add monorepo support after the MVP is stable.',
394
401
  '',
395
402
  ].join('\n');
396
403
  }
@@ -454,7 +461,7 @@ function renderThemeProvider() {
454
461
  return [
455
462
  "import { createContext, useContext, useMemo, useState, type Dispatch, type ReactNode, type SetStateAction } from 'react';",
456
463
  '',
457
- "import stylistThemeTokens, { type StylistColorPalette, type StylistColorScheme, type StylistThemeTokens } from './tokens';",
464
+ "import defaultThemeTokens, { type StylistColorPalette, type StylistColorScheme, type StylistThemeTokens } from './tokens';",
458
465
  '',
459
466
  'export type AppThemeValue = StylistThemeTokens & {',
460
467
  ' activeScheme: StylistColorScheme;',
@@ -462,14 +469,14 @@ function renderThemeProvider() {
462
469
  '};',
463
470
  '',
464
471
  'const AppThemeContext = createContext<AppThemeValue>({',
465
- ' ...stylistThemeTokens,',
466
- ' activeScheme: stylistThemeTokens.colorSystem.previewScheme,',
467
- ' activeColors: stylistThemeTokens.colors[stylistThemeTokens.colorSystem.previewScheme],',
472
+ ' ...defaultThemeTokens,',
473
+ ' activeScheme: defaultThemeTokens.colorSystem.previewScheme,',
474
+ ' activeColors: defaultThemeTokens.colors[defaultThemeTokens.colorSystem.previewScheme],',
468
475
  '});',
469
476
  'const AppThemeSetterContext = createContext<Dispatch<SetStateAction<StylistThemeTokens>> | null>(null);',
470
477
  '',
471
478
  'export function AppThemeProvider({ children }: { children: ReactNode }) {',
472
- ' const [theme, setTheme] = useState<StylistThemeTokens>(stylistThemeTokens);',
479
+ ' const [theme, setTheme] = useState<StylistThemeTokens>(defaultThemeTokens);',
473
480
  ' const value = useMemo<AppThemeValue>(() => {',
474
481
  ' const activeScheme = theme.colorSystem.previewScheme;',
475
482
  ' return {',
@@ -570,7 +577,6 @@ export function renderGuidelines(answers) {
570
577
  '- Develop through feature branches into `test`, then promote validated work from `test` to `main`.',
571
578
  ]
572
579
  : []),
573
- `- Latest Expo SDK preference captured during onboarding: ${formatBoolean(answers.useLatestExpoSdk)}.`,
574
580
  `- Expo UI Universal components preference captured during onboarding: ${formatBoolean(answers.usesExpoUiUniversalComponents)}.`,
575
581
  '- Treat monorepo scaffolding as future work until the single-app MVP is stable.',
576
582
  '',
@@ -595,7 +601,7 @@ export function renderAgentInstructions(answers) {
595
601
  '',
596
602
  'If the user says `mds continue` or `MDS Continue`, first run `mds continue` from the app root if available. Use the MDS Continue brief to propose the next plan and wait for approval before editing files. If the command is unavailable, manually inspect markers, Doctor status, git status, and `project/todo.md` in that order.',
597
603
  '',
598
- 'Before any intake, planning, scaffolding, or phase work, scan every `project/` file for the marker `# TodoForContext(optional):`. If any are present, stop and tell the user to fill the section underneath OR delete the marker line to acknowledge they do not want to add that context. Only proceed when zero markers remain.',
604
+ 'Before any intake, planning, scaffolding, or phase work, scan `project/info.md` for the marker `# TodoForContext(optional):`. If any remain, stop and tell the user to fill the section underneath OR delete the marker line to acknowledge they do not want to add that context. Only proceed when zero `project/info.md` markers remain.',
599
605
  '',
600
606
  'Then build from `project/todo.md` in phase order. Do not make changes that conflict with project memory. If the files are unclear or generic, update the project memory first or ask the user.',
601
607
  '',
@@ -733,6 +739,7 @@ async function ensurePackageJson(projectPath, answers, manageUniwind) {
733
739
  'expo-doctor': packageJson.scripts?.['expo-doctor'] ?? 'npx expo-doctor',
734
740
  'post-create-check': packageJson.scripts?.['post-create-check'] ?? 'npx expo install --fix && npx expo-doctor',
735
741
  'ci:verify': packageJson.scripts?.['ci:verify'] ?? `${MDS_NPX_COMMAND} doctor --ci`,
742
+ test: packageJson.scripts?.test ?? 'npm run lint && npm run typecheck',
736
743
  };
737
744
  if (!manageUniwind) {
738
745
  packageJson.scripts['patch:nativewind-metro'] =
@@ -786,12 +793,10 @@ async function ensurePackageJson(projectPath, answers, manageUniwind) {
786
793
  ...packageJson.dependencies,
787
794
  };
788
795
  }
789
- if (answers.targetPlatforms.includes('android')) {
790
- packageJson.dependencies = {
791
- ...ANDROID_NAVIGATION_BAR_DEPENDENCIES,
792
- ...packageJson.dependencies,
793
- };
794
- }
796
+ packageJson.dependencies = {
797
+ ...ANDROID_NAVIGATION_BAR_DEPENDENCIES,
798
+ ...packageJson.dependencies,
799
+ };
795
800
  if (manageUniwind) {
796
801
  packageJson.dependencies = {
797
802
  ...UNIWIND_DEPENDENCIES,
@@ -807,6 +812,7 @@ async function ensurePackageJson(projectPath, answers, manageUniwind) {
807
812
  delete packageJson.devDependencies['prettier-plugin-tailwindcss'];
808
813
  }
809
814
  packageJson.devDependencies = {
815
+ ...STYLIST_DEV_DEPENDENCIES,
810
816
  ...packageJson.devDependencies,
811
817
  '@mr.dj2u/cli': packageJson.devDependencies?.['@mr.dj2u/cli'] ?? `^${MDS_CLI_VERSION}`,
812
818
  };
@@ -828,7 +834,6 @@ function applyGuidelinesTemplate(template, answers) {
828
834
  deploymentTarget: answers.deploymentTarget,
829
835
  advancedPackageSetup: formatBoolean(answers.advancedPackageSetup),
830
836
  includeCreateExpoComponents: formatBoolean(answers.includeCreateExpoComponents),
831
- useLatestExpoSdk: formatBoolean(answers.useLatestExpoSdk),
832
837
  targetPlatforms: answers.targetPlatforms.map((item) => `- ${item}`).join('\n'),
833
838
  firstTargetPlatform: answers.firstTargetPlatform,
834
839
  appDirectory: formatAppDirectory(answers.appDirectory),
@@ -944,18 +949,99 @@ function formatCodeOrg(answers) {
944
949
  }
945
950
  return parts.join(', ');
946
951
  }
952
+ function buildCessSnapshot(answers) {
953
+ return {
954
+ version: 1,
955
+ displayAppName: answers.appName,
956
+ folderSlug: toKebabCase(answers.appName),
957
+ answers: {
958
+ scriptLanguage: answers.generatorScriptLanguage,
959
+ packageManager: answers.generatorPackageManager,
960
+ navigationLibrary: answers.generatorNavigationLibrary,
961
+ reactNavigationLayout: answers.generatorReactNavigationLayout,
962
+ stylingSystem: answers.generatorStylingSystem,
963
+ stateManagement: answers.generatorStateManagement,
964
+ authBackend: answers.generatorAuthBackend,
965
+ easSetup: answers.generatorEasSetup,
966
+ displayAppName: answers.appName,
967
+ audience: answers.audience,
968
+ coreFlows: answers.coreFlows,
969
+ screens: answers.screens,
970
+ targetPlatforms: answers.targetPlatforms,
971
+ firstTargetPlatform: answers.firstTargetPlatform,
972
+ platformStrategy: answers.platformFileStrategy,
973
+ appDirectory: answers.appDirectory,
974
+ platformLayouts: answers.platformLayoutMode,
975
+ webOutput: answers.webOutput,
976
+ expoServerAdapter: answers.expoServerAdapter,
977
+ customBackend: answers.customBackend,
978
+ customBackendEntry: answers.customBackendEntry,
979
+ deploymentTarget: answers.deploymentTarget,
980
+ includeCreateExpoComponents: answers.includeCreateExpoComponents,
981
+ usesExpoUi: answers.usesExpoUi,
982
+ usesExpoUiUniversalComponents: answers.usesExpoUiUniversalComponents,
983
+ usesExpoNativeTabs: answers.usesExpoNativeTabs,
984
+ easUses: answers.easUses,
985
+ guidelinesTemplate: true,
986
+ dataStart: answers.dataStart,
987
+ testToMainSafeguards: answers.testToMainSafeguards,
988
+ },
989
+ };
990
+ }
991
+ function formatGeneratorLanguage(value) {
992
+ return value === 'javascript' ? 'JavaScript' : value === 'typescript' ? 'TypeScript' : '# TodoForContext(optional): TypeScript / JavaScript';
993
+ }
994
+ function formatGeneratorPackageManager(value) {
995
+ return value ?? '# TodoForContext(optional): pnpm / npm / yarn / bun';
996
+ }
997
+ function formatGeneratorRouting(answers) {
998
+ if (answers.generatorNavigationLibrary === 'react-navigation') {
999
+ const layout = answers.generatorReactNavigationLayout === 'tabs'
1000
+ ? 'Tabs'
1001
+ : answers.generatorReactNavigationLayout === 'drawer'
1002
+ ? 'Drawer + Tabs'
1003
+ : 'Stack';
1004
+ return `React Navigation (${layout})`;
1005
+ }
1006
+ if (answers.generatorNavigationLibrary === 'expo-router') {
1007
+ return `Expo Router (${formatAppDirectory(answers.appDirectory)})`;
1008
+ }
1009
+ return `# TodoForContext(optional): Expo Router / React Navigation (${formatAppDirectory(answers.appDirectory)})`;
1010
+ }
1011
+ function formatGeneratorStateManagement(value) {
1012
+ if (value === 'zustand') {
1013
+ return 'Zustand';
1014
+ }
1015
+ if (value === 'none') {
1016
+ return 'None';
1017
+ }
1018
+ return '# TodoForContext(optional): Zustand / Jotai / React context / none';
1019
+ }
1020
+ function toKebabCase(value) {
1021
+ return value
1022
+ .trim()
1023
+ .toLowerCase()
1024
+ .replace(/[^a-z0-9]+/gu, '-')
1025
+ .replace(/^-+|-+$/gu, '') || 'expo-app';
1026
+ }
947
1027
  function hasThinOnboardingAnswers(answers) {
948
1028
  const genericValues = new Set([
949
1029
  'Expo app users',
950
1030
  'Onboarding, primary app workflow, settings',
951
- 'Agent should derive the first core user flows from project/info.md during intake.',
952
1031
  'Local state first; add backend only when needed',
953
1032
  'Expo web/native deployment',
954
1033
  ]);
955
1034
  if (!answers.screens?.trim()) {
956
1035
  return true;
957
1036
  }
958
- return [answers.audience, answers.coreFlows, answers.dataNeeds, answers.deploymentTarget].some((value) => genericValues.has(value.trim()));
1037
+ return ([answers.audience, answers.dataNeeds, answers.deploymentTarget].some((value) => genericValues.has(value.trim())) || isGenericCoreFlowsText(answers.coreFlows));
1038
+ }
1039
+ function isGenericCoreFlowsText(value) {
1040
+ const trimmed = value.trim();
1041
+ return (trimmed.length === 0 ||
1042
+ trimmed ===
1043
+ 'Let the agent derive the first real core user flows later from the fully clarified `project/info.md`.' ||
1044
+ trimmed === 'Agent should derive the first core user flows from project/info.md during intake.');
959
1045
  }
960
1046
  async function ensureUniwindMetroConfig(projectPath) {
961
1047
  const metroPath = path.join(projectPath, 'metro.config.js');
@@ -1033,20 +1119,35 @@ async function removeTailwindPrettierPluginConfig(filePath) {
1033
1119
  async function ensureGlobalCssImport(projectPath, appDirectory) {
1034
1120
  const layoutPath = path.join(getExpoRouterAppDir(projectPath, appDirectory), '_layout.tsx');
1035
1121
  const appPath = path.join(projectPath, 'App.tsx');
1122
+ const globalCssPath = path.join(projectPath, 'global.css');
1123
+ const hasGlobalCss = await pathExists(globalCssPath);
1036
1124
  const layout = await readOptionalText(layoutPath);
1037
1125
  if (layout) {
1038
- const importStatement = renderGlobalCssImport(layoutPath, projectPath);
1039
- const updated = layout.match(/^\s*import\s+['"][^'"]*global\.css['"];?\r?\n/m)
1040
- ? layout.replace(/^\s*import\s+['"][^'"]*global\.css['"];?\r?\n/m, `${importStatement}\n`)
1041
- : `${importStatement}\n${layout}`;
1126
+ const globalCssImportPattern = /^\s*import\s+['"][^'"]*global\.css['"];?\r?\n/m;
1127
+ const updated = hasGlobalCss
1128
+ ? layout.match(globalCssImportPattern)
1129
+ ? layout.replace(globalCssImportPattern, `${renderGlobalCssImport(layoutPath, projectPath)}\n`)
1130
+ : `${renderGlobalCssImport(layoutPath, projectPath)}\n${layout}`
1131
+ : layout.replace(globalCssImportPattern, '');
1042
1132
  if (updated !== layout) {
1043
1133
  await writeFile(layoutPath, updated, 'utf8');
1044
1134
  }
1045
1135
  return;
1046
1136
  }
1047
1137
  const app = await readOptionalText(appPath);
1048
- if (app && !app.includes('global.css')) {
1049
- await writeFile(appPath, `import './global.css';\n${app}`, 'utf8');
1138
+ if (!app) {
1139
+ return;
1140
+ }
1141
+ const globalCssImportPattern = /^\s*import\s+['"][^'"]*global\.css['"];?\r?\n/m;
1142
+ if (hasGlobalCss) {
1143
+ if (!app.match(globalCssImportPattern)) {
1144
+ await writeFile(appPath, `import './global.css';\n${app}`, 'utf8');
1145
+ }
1146
+ return;
1147
+ }
1148
+ const updated = app.replace(globalCssImportPattern, '');
1149
+ if (updated !== app) {
1150
+ await writeFile(appPath, updated, 'utf8');
1050
1151
  }
1051
1152
  }
1052
1153
  function getExpoRouterAppDir(projectPath, appDirectory) {
@@ -1307,7 +1408,7 @@ function renderStylistSyncApiRoute() {
1307
1408
  "import { access, mkdir, readFile, unlink, writeFile } from 'node:fs/promises';",
1308
1409
  "import path from 'node:path';",
1309
1410
  '',
1310
- "import stylistThemeTokens from '../../theme/tokens';",
1411
+ "import defaultThemeTokens from '../../theme/tokens';",
1311
1412
  '',
1312
1413
  'interface SyncResponse {',
1313
1414
  ' projectPath: string;',
@@ -1359,7 +1460,7 @@ function renderStylistSyncApiRoute() {
1359
1460
  '',
1360
1461
  ' const themeFromJson = await readThemeJson(themePath);',
1361
1462
  ' const themeFromStyle = await readThemeFromStyleMarkdown(stylePath);',
1362
- ' const resolvedTheme = themeFromStyle ?? themeFromJson ?? stylistThemeTokens;',
1463
+ ' const resolvedTheme = themeFromStyle ?? themeFromJson ?? defaultThemeTokens;',
1363
1464
  " const themeSource = themeFromStyle ? 'style.md' : themeFromJson ? 'theme.json' : 'default';",
1364
1465
  ' const mismatchDetected =',
1365
1466
  ' Boolean(themeFromJson) &&',
@@ -1883,7 +1984,7 @@ function renderRichRootLayout(projectPath, appDir, navigationShell, answers) {
1883
1984
  "import { GestureHandlerRootView } from 'react-native-gesture-handler';",
1884
1985
  "import { KeyboardProvider } from 'react-native-keyboard-controller';",
1885
1986
  "import { SafeAreaProvider } from 'react-native-safe-area-context';",
1886
- `import THEME_FONT_ASSETS from '${themeFontAssetsImport}';`,
1987
+ `import themeFontAssets from '${themeFontAssetsImport}';`,
1887
1988
  `import { AppThemeProvider, useAppTheme } from '${themeProviderImport}';`,
1888
1989
  '',
1889
1990
  'function RouterThemeBridge({ children }: { children: ReactNode }) {',
@@ -1965,8 +2066,8 @@ function renderRichRootLayout(projectPath, appDir, navigationShell, answers) {
1965
2066
  '}',
1966
2067
  '',
1967
2068
  'export default function Layout() {',
1968
- ' const hasFontAssets = Object.keys(THEME_FONT_ASSETS).length > 0;',
1969
- ' const [fontsLoaded, fontsError] = useFonts(THEME_FONT_ASSETS);',
2069
+ ' const hasFontAssets = Object.keys(themeFontAssets).length > 0;',
2070
+ ' const [fontsLoaded, fontsError] = useFonts(themeFontAssets);',
1970
2071
  '',
1971
2072
  ' if (hasFontAssets && !fontsLoaded && !fontsError) {',
1972
2073
  ' return null;',
@@ -2063,8 +2164,10 @@ function renderReleaseFlow(answers) {
2063
2164
  '## GitHub Setup The User Still Needs To Do',
2064
2165
  '',
2065
2166
  '- Create `test` and `main` branches.',
2167
+ '- Confirm GitHub Actions is enabled for the repo and that the generated workflow is allowed to run.',
2066
2168
  '- In GitHub branch protection, require pull requests and status checks for `test` and `main`.',
2067
2169
  '- Require the generated `MDS PR Checks` workflow before merge.',
2170
+ '- If the agent has GitHub access with enough permissions, let it apply these repo settings for you; otherwise do this one-time setup in the GitHub UI.',
2068
2171
  '',
2069
2172
  ].join('\n');
2070
2173
  }
@@ -2190,6 +2293,7 @@ function renderGestureCard() {
2190
2293
  function renderKeyboardForm() {
2191
2294
  return [
2192
2295
  "import { Keyboard, Platform, ScrollView, StyleSheet, TextInput } from 'react-native';",
2296
+ "import { KeyboardAwareScrollView, KeyboardToolbar } from 'react-native-keyboard-controller';",
2193
2297
  '',
2194
2298
  'export function KeyboardForm() {',
2195
2299
  ' if (Platform.OS === "web") {',
@@ -2200,14 +2304,6 @@ function renderKeyboardForm() {
2200
2304
  ' </ScrollView>',
2201
2305
  ' );',
2202
2306
  ' }',
2203
- '',
2204
- " const keyboardController = require('react-native-keyboard-controller') as {",
2205
- ' KeyboardAwareScrollView: any;',
2206
- ' KeyboardToolbar: any;',
2207
- ' };',
2208
- ' const KeyboardAwareScrollView = keyboardController.KeyboardAwareScrollView;',
2209
- ' const KeyboardToolbar = keyboardController.KeyboardToolbar;',
2210
- '',
2211
2307
  ' return (',
2212
2308
  ' <>',
2213
2309
  ' <KeyboardAwareScrollView bottomOffset={72} contentContainerStyle={styles.form} style={styles.scroller}>',
@@ -3008,7 +3104,7 @@ function renderHomeScreen(answers, navigationShell) {
3008
3104
  "import { appSnapshot } from '../../data/mock-app';",
3009
3105
  "import { useAppTheme } from '../../theme/provider';",
3010
3106
  '',
3011
- 'const expositionLinks: Array<{ href: Href; title: string; body: string }> = [',
3107
+ 'const expositionLinks: { href: Href; title: string; body: string }[] = [',
3012
3108
  ...expositionLinks,
3013
3109
  '];',
3014
3110
  '',
@@ -4303,9 +4399,9 @@ function renderStylistScreen(answers) {
4303
4399
  "import ColorPicker, { HueSlider, Panel1, Preview, Swatches } from 'reanimated-color-picker';",
4304
4400
  '',
4305
4401
  "import { AnimatedPressable, ExpositionNotice } from '../../components/exposition';",
4306
- "import stylistThemeTokens from '../../theme/tokens';",
4402
+ "import defaultThemeTokens from '../../theme/tokens';",
4307
4403
  '',
4308
- 'type StylistTheme = typeof stylistThemeTokens;',
4404
+ 'type StylistTheme = typeof defaultThemeTokens;',
4309
4405
  "type ColorKey = keyof StylistTheme['colors'];",
4310
4406
  '',
4311
4407
  'const colorKeys: ColorKey[] = [',
@@ -4317,11 +4413,11 @@ function renderStylistScreen(answers) {
4317
4413
  " 'warning',",
4318
4414
  '];',
4319
4415
  '',
4320
- "const spacingKeys: Array<keyof StylistTheme['layout']['spacing']> = ['xs', 'sm', 'md', 'lg', 'xl'];",
4416
+ "const spacingKeys: (keyof StylistTheme['layout']['spacing'])[] = ['xs', 'sm', 'md', 'lg', 'xl'];",
4321
4417
  "const NATIVE_SAVE_COMMAND = 'npm run stylist:sync:android';",
4322
4418
  '',
4323
4419
  'export default function StylistScreen() {',
4324
- ' const [theme, setTheme] = useState<StylistTheme>(stylistThemeTokens);',
4420
+ ' const [theme, setTheme] = useState<StylistTheme>(defaultThemeTokens);',
4325
4421
  " const [selectedColor, setSelectedColor] = useState<ColorKey>('primary');",
4326
4422
  " const [saveMessage, setSaveMessage] = useState('');",
4327
4423
  " const [nativeDraft, setNativeDraft] = useState('');",