@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.
- package/bundles/claude-code/.claude-plugin/plugin.json +1 -1
- package/bundles/claude-code/.mcp.json +1 -1
- package/bundles/claude-code/commands/create-expo-super-stack.md +10 -7
- package/bundles/claude-code/skills/create-expo-super-stack/SKILL.md +10 -7
- package/bundles/claude-code/skills/super-stack-startup/SKILL.md +1 -1
- package/bundles/codex/.codex-plugin/plugin.json +1 -1
- package/bundles/codex/.mcp.json +1 -1
- package/bundles/codex/commands/create-expo-super-stack.md +10 -7
- package/bundles/codex/skills/super-stack-startup/SKILL.md +1 -1
- package/bundles/codex/skills/workflow-create-expo-super-stack/SKILL.md +10 -7
- package/bundles/vscode-copilot/.github/prompts/create-expo-super-stack.prompt.md +10 -7
- package/bundles/vscode-copilot/.github/skills/super-stack-startup/SKILL.md +1 -1
- package/bundles/vscode-copilot/.vscode/mcp.json +1 -1
- package/bundles/vscode-copilot/user/.copilot/skills/super-stack-startup/SKILL.md +1 -1
- package/bundles/vscode-copilot/user/.copilot/skills/workflow-create-expo-super-stack/SKILL.md +10 -7
- package/dist/cess-intake.d.ts +16 -1
- package/dist/cess-intake.d.ts.map +1 -1
- package/dist/cess-intake.js +677 -34
- package/dist/cess-intake.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +14 -4
- package/dist/cli.js.map +1 -1
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +3 -1
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/mcp-install.d.ts +3 -2
- package/dist/commands/mcp-install.d.ts.map +1 -1
- package/dist/commands/mcp-install.js +17 -44
- package/dist/commands/mcp-install.js.map +1 -1
- package/dist/commands/onboard.d.ts +9 -3
- package/dist/commands/onboard.d.ts.map +1 -1
- package/dist/commands/onboard.js +28 -10
- package/dist/commands/onboard.js.map +1 -1
- package/dist/commands/roadmap.d.ts +6 -0
- package/dist/commands/roadmap.d.ts.map +1 -0
- package/dist/commands/roadmap.js +54 -0
- package/dist/commands/roadmap.js.map +1 -0
- package/dist/project-memory.d.ts +8 -1
- package/dist/project-memory.d.ts.map +1 -1
- package/dist/project-memory.js +189 -93
- package/dist/project-memory.js.map +1 -1
- package/dist/roadmap.d.ts +71 -0
- package/dist/roadmap.d.ts.map +1 -0
- package/dist/roadmap.js +865 -0
- package/dist/roadmap.js.map +1 -0
- package/dist/stylist-theme.d.ts.map +1 -1
- package/dist/stylist-theme.js +1 -20
- package/dist/stylist-theme.js.map +1 -1
- package/package.json +7 -3
- package/templates/embedded-fonts.template.ts +72 -72
- package/templates/expo-sdk-56-screen-universal.template.tsx +709 -709
- package/templates/project/guidelines.md +4 -5
- package/templates/stylist-screen.template.tsx +3456 -3446
package/dist/project-memory.js
CHANGED
|
@@ -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.
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
286
|
-
|
|
287
|
-
`- **Routing:**
|
|
318
|
+
`- **Language:** ${formatGeneratorLanguage(answers.generatorScriptLanguage)}`,
|
|
319
|
+
`- **Package manager:** ${formatGeneratorPackageManager(answers.generatorPackageManager)}`,
|
|
320
|
+
`- **Routing:** ${formatGeneratorRouting(answers)}`,
|
|
288
321
|
`- **Styling:** ${formatStyleStack(answers)}`,
|
|
289
|
-
|
|
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
|
-
`-
|
|
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
|
-
'- [ ]
|
|
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.
|
|
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
|
-
`- [ ]
|
|
340
|
-
|
|
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
|
-
`- [ ]
|
|
349
|
-
...(answers.dataStart === '
|
|
350
|
-
? [
|
|
351
|
-
|
|
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
|
-
|
|
364
|
-
|
|
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
|
|
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
|
-
' ...
|
|
466
|
-
' activeScheme:
|
|
467
|
-
' activeColors:
|
|
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>(
|
|
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
|
|
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
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
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.
|
|
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
|
|
1039
|
-
const updated =
|
|
1040
|
-
? layout.
|
|
1041
|
-
|
|
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 (
|
|
1049
|
-
|
|
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
|
|
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 ??
|
|
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
|
|
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(
|
|
1969
|
-
' const [fontsLoaded, fontsError] = useFonts(
|
|
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:
|
|
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
|
|
4402
|
+
"import defaultThemeTokens from '../../theme/tokens';",
|
|
4307
4403
|
'',
|
|
4308
|
-
'type StylistTheme = typeof
|
|
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:
|
|
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>(
|
|
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('');",
|