@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
@@ -0,0 +1,865 @@
1
+ import { access, readFile, readdir, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ const TODO_FOR_CONTEXT_MARKER = '# TodoForContext(optional):';
4
+ const LEGACY_MARKER_PREFIX = '<!-- MDS_DERIVED_PHASE_';
5
+ const ROADMAP_STATE_FILE = 'roadmap-state.json';
6
+ export const ROADMAP_BLOCKED_MARKER_WARNING = 'Roadmap generation is blocked until every `# TodoForContext(optional):` marker in `project/` is resolved. Fill the section underneath or delete the marker line first.';
7
+ export const ROADMAP_CLARIFICATION_WARNING = 'Roadmap generation needs clarification because `project/info.md` still reads as too generic for high-confidence product planning.';
8
+ const PHASE_SPECS = [
9
+ {
10
+ id: 'phase-0',
11
+ title: 'Phase 0: Orientation And Planning',
12
+ heading: '## Phase 0: Orientation And Planning',
13
+ },
14
+ {
15
+ id: 'phase-1',
16
+ title: 'Phase 1: App Shell And First Flow',
17
+ heading: '## Phase 1: App Shell And First Flow',
18
+ },
19
+ {
20
+ id: 'phase-2',
21
+ title: 'Phase 2: Data Layer',
22
+ heading: '## Phase 2: Data Layer',
23
+ },
24
+ {
25
+ id: 'phase-3',
26
+ title: 'Phase 3: Complete Product Flows',
27
+ heading: '## Phase 3: Complete Product Flows',
28
+ },
29
+ {
30
+ id: 'phase-4',
31
+ title: 'Phase 4: Polish, Safeguards, And Release',
32
+ heading: '## Phase 4: Polish, Safeguards, And Release',
33
+ },
34
+ ];
35
+ const SECTION_ALIASES = {
36
+ overview: ['overview', 'summary', 'app overview', 'product overview'],
37
+ targetUsers: ['target users', 'users', 'audience', 'who this app is for'],
38
+ productGoals: ['product goals', 'goals', 'business goals', 'success criteria'],
39
+ nonGoals: ['non-goals', 'non goals', 'out of scope'],
40
+ coreFeatures: ['core features', 'features', 'feature list', 'main features'],
41
+ coreUserFlows: ['core user flows', 'user flows', 'flows', 'core flows', 'primary flows'],
42
+ mustIncludeScreensOrFlows: [
43
+ 'must-include screens or flows',
44
+ 'must include screens or flows',
45
+ 'must-have screens or flows',
46
+ 'must have screens or flows',
47
+ 'known screens or flows',
48
+ 'screens',
49
+ ],
50
+ dataAndBackend: [
51
+ 'data and backend',
52
+ 'data',
53
+ 'backend',
54
+ 'data model',
55
+ 'backend and integrations',
56
+ 'data & backend',
57
+ ],
58
+ platforms: ['platforms', 'targets', 'platform targets'],
59
+ packageChoices: ['package choices', 'packages', 'stack choices'],
60
+ monetizationStrategy: ['monetization strategy', 'monetization'],
61
+ teamContext: ['team context', 'team', 'stakeholders'],
62
+ releaseStrategy: ['release strategy', 'release plan', 'deployment plan', 'distribution'],
63
+ questionsToRevisit: ['questions to revisit', 'open questions', 'unknowns', 'risks'],
64
+ resources: ['resources', 'references', 'links'],
65
+ techStack: ['tech stack & mds onboarding', 'tech stack', 'mds onboarding'],
66
+ };
67
+ const KEYWORD_TASKS = [
68
+ {
69
+ phaseId: 'phase-2',
70
+ source: 'data',
71
+ patterns: [/\bauth\b/i, /\blogin\b/i, /\bsign[- ]?in\b/i, /\buser accounts?\b/i],
72
+ text: 'Implement authentication, session handling, and signed-in user boundaries.',
73
+ },
74
+ {
75
+ phaseId: 'phase-2',
76
+ source: 'data',
77
+ patterns: [/\bsupabase\b/i, /\bdrizzle\b/i, /\bdatabase\b/i, /\btables?\b/i],
78
+ text: 'Design the initial data model, persistence layer, and migration boundaries.',
79
+ },
80
+ {
81
+ phaseId: 'phase-2',
82
+ source: 'integration',
83
+ patterns: [/\buploads?\b/i, /\bstorage\b/i, /\bimages?\b/i, /\bfiles?\b/i],
84
+ text: 'Plan file and media upload flows, storage ownership, and failure handling.',
85
+ },
86
+ {
87
+ phaseId: 'phase-2',
88
+ source: 'integration',
89
+ patterns: [/\bexternal apis?\b/i, /\bintegrations?\b/i, /\bthird[- ]party\b/i],
90
+ text: 'Define external integration boundaries, request flows, and error handling.',
91
+ },
92
+ {
93
+ phaseId: 'phase-2',
94
+ source: 'integration',
95
+ patterns: [/\brealtime\b/i, /\bcollaboration\b/i, /\bsync\b/i],
96
+ text: 'Design realtime or sync behavior before wiring collaboration-heavy features.',
97
+ },
98
+ {
99
+ phaseId: 'phase-3',
100
+ source: 'integration',
101
+ patterns: [/\banalytics\b/i, /\bevents?\b/i],
102
+ text: 'Add an analytics/event plan once the MVP flow is working end to end.',
103
+ },
104
+ {
105
+ phaseId: 'phase-3',
106
+ source: 'integration',
107
+ patterns: [/\bpush\b/i, /\bemail\b/i, /\bnotifications?\b/i],
108
+ text: 'Implement notification and messaging flows after the core product loop is stable.',
109
+ },
110
+ {
111
+ phaseId: 'phase-3',
112
+ source: 'integration',
113
+ patterns: [/\boffline\b/i, /\bcache\b/i],
114
+ text: 'Add offline and cache behavior for the flows that need resilience away from the network.',
115
+ },
116
+ {
117
+ phaseId: 'phase-3',
118
+ source: 'integration',
119
+ patterns: [/\badmin\b/i, /\bmoderation\b/i],
120
+ text: 'Implement admin or moderation tooling after the primary user experience is established.',
121
+ },
122
+ {
123
+ phaseId: 'phase-3',
124
+ source: 'monetization',
125
+ patterns: [/\bpayments?\b/i, /\bsubscriptions?\b/i, /\bmonetization\b/i, /\bpricing\b/i],
126
+ text: 'Implement the monetization or payments flow after the core value loop is proven.',
127
+ },
128
+ {
129
+ phaseId: 'phase-4',
130
+ source: 'release',
131
+ patterns: [/\btestflight\b/i, /\bapp store\b/i, /\bplay store\b/i, /\bside-loaded apk\b/i],
132
+ text: 'Prepare store/distribution packaging, review notes, and release validation for the chosen delivery path.',
133
+ },
134
+ {
135
+ phaseId: 'phase-4',
136
+ source: 'release',
137
+ patterns: [/\bweb hosting\b/i, /\bvps\b/i, /\bplesk\b/i, /\btemp domain\b/i, /\bnginx\b/i, /\bserver\b/i],
138
+ text: 'Validate the production web/server hosting path, environment ownership, and rollout checklist.',
139
+ },
140
+ {
141
+ phaseId: 'phase-4',
142
+ source: 'release',
143
+ patterns: [/\btest-to-main\b/i, /\bbranch protection\b/i, /\bpr checks?\b/i],
144
+ text: 'Confirm branch protection, CI checks, and release gating match the planned ship workflow.',
145
+ },
146
+ ];
147
+ const GENERIC_PATTERNS = {
148
+ audience: [
149
+ /\bexpo app users\b/i,
150
+ /\btarget users?\b/i,
151
+ /\bgeneral users?\b/i,
152
+ ],
153
+ coreFlows: [
154
+ /\bagent should derive\b/i,
155
+ /\blet the agent derive\b/i,
156
+ /\bderive the first core user flows\b/i,
157
+ /\bdecide later\b/i,
158
+ /\btbd\b/i,
159
+ ],
160
+ release: [
161
+ /\bexpo web\/native deployment\b/i,
162
+ /\bnot planned yet\b/i,
163
+ /\bdecide later\b/i,
164
+ ],
165
+ productGoals: [
166
+ /\badd the business\/product outcomes\b/i,
167
+ /\bdecide later\b/i,
168
+ ],
169
+ };
170
+ export async function generateProjectRoadmap(projectPathInput = '.', options = {}) {
171
+ const projectPath = path.resolve(projectPathInput);
172
+ const infoPath = path.join(projectPath, 'project', 'info.md');
173
+ const todoPath = path.join(projectPath, 'project', 'todo.md');
174
+ const roadmapStatePath = path.join(projectPath, 'project', ROADMAP_STATE_FILE);
175
+ const write = options.write ?? false;
176
+ const preserveStatus = options.preserveStatus ?? true;
177
+ const infoRaw = await readFile(infoPath, 'utf8');
178
+ const existingTodo = (await pathExists(todoPath))
179
+ ? await readFile(todoPath, 'utf8')
180
+ : renderTodoSkeleton(extractProjectName(infoRaw));
181
+ const markerHits = await scanProjectTodoForContextMarkers(projectPath, { scope: 'info' });
182
+ if (markerHits.length > 0) {
183
+ return {
184
+ kind: 'project-roadmap',
185
+ projectPath,
186
+ infoPath,
187
+ todoPath,
188
+ roadmapStatePath,
189
+ blockedByMarkers: true,
190
+ markerHits,
191
+ needsClarification: false,
192
+ clarificationQuestions: [],
193
+ confidenceWarnings: [],
194
+ phases: [],
195
+ warnings: [ROADMAP_BLOCKED_MARKER_WARNING],
196
+ write,
197
+ wrote: false,
198
+ preservedStatuses: 0,
199
+ todoContent: ensureTrailingNewline(existingTodo),
200
+ };
201
+ }
202
+ const sections = parseInfoSections(infoRaw);
203
+ const clarification = detectRoadmapClarificationNeeds(sections);
204
+ if (clarification.clarificationQuestions.length > 0) {
205
+ return {
206
+ kind: 'project-roadmap',
207
+ projectPath,
208
+ infoPath,
209
+ todoPath,
210
+ roadmapStatePath,
211
+ blockedByMarkers: false,
212
+ markerHits: [],
213
+ needsClarification: true,
214
+ clarificationQuestions: clarification.clarificationQuestions,
215
+ confidenceWarnings: clarification.confidenceWarnings,
216
+ phases: [],
217
+ warnings: [ROADMAP_CLARIFICATION_WARNING],
218
+ write,
219
+ wrote: false,
220
+ preservedStatuses: 0,
221
+ todoContent: ensureTrailingNewline(stripLegacyDerivedMarkers(existingTodo)),
222
+ };
223
+ }
224
+ const warnings = [];
225
+ const derivedPhases = deriveRoadmapPhases(sections, warnings);
226
+ const previousState = (await readRoadmapState(roadmapStatePath)) ?? inferLegacyRoadmapState(existingTodo);
227
+ const nextState = buildRoadmapState(derivedPhases);
228
+ const merged = mergeRoadmapIntoTodo(existingTodo, derivedPhases, previousState, preserveStatus);
229
+ const nextTodo = merged.todoContent;
230
+ const todoChanged = normalizeLineEndings(nextTodo) !== normalizeLineEndings(existingTodo);
231
+ const previousStateJson = previousState ? JSON.stringify(previousState) : null;
232
+ const nextStateJson = JSON.stringify(nextState);
233
+ const stateChanged = previousStateJson !== nextStateJson;
234
+ const wrote = write && (todoChanged || stateChanged);
235
+ if (write) {
236
+ if (todoChanged) {
237
+ await writeFile(todoPath, nextTodo, 'utf8');
238
+ }
239
+ if (stateChanged) {
240
+ await writeFile(roadmapStatePath, `${JSON.stringify(nextState, null, 2)}\n`, 'utf8');
241
+ }
242
+ }
243
+ return {
244
+ kind: 'project-roadmap',
245
+ projectPath,
246
+ infoPath,
247
+ todoPath,
248
+ roadmapStatePath,
249
+ blockedByMarkers: false,
250
+ markerHits: [],
251
+ needsClarification: false,
252
+ clarificationQuestions: [],
253
+ confidenceWarnings: [],
254
+ phases: derivedPhases,
255
+ warnings,
256
+ write,
257
+ wrote,
258
+ preservedStatuses: merged.preservedStatuses,
259
+ todoContent: nextTodo,
260
+ };
261
+ }
262
+ export function parseInfoSections(infoRaw) {
263
+ const lines = normalizeLineEndings(infoRaw).split('\n');
264
+ const map = {};
265
+ let currentHeading = null;
266
+ let currentKey = null;
267
+ let buffer = [];
268
+ const flush = () => {
269
+ if (!currentHeading || !currentKey) {
270
+ buffer = [];
271
+ return;
272
+ }
273
+ const content = buffer.join('\n').trim();
274
+ const existing = map[currentKey];
275
+ map[currentKey] = {
276
+ key: currentKey,
277
+ headings: existing ? [...existing.headings, currentHeading] : [currentHeading],
278
+ content: existing && existing.content ? `${existing.content}\n\n${content}`.trim() : content,
279
+ };
280
+ buffer = [];
281
+ };
282
+ for (const line of lines) {
283
+ const headingMatch = /^##\s+(.+?)\s*$/.exec(line);
284
+ if (headingMatch?.[1]) {
285
+ flush();
286
+ currentHeading = headingMatch[1].trim();
287
+ currentKey = normalizeSectionHeading(currentHeading);
288
+ continue;
289
+ }
290
+ if (currentHeading) {
291
+ buffer.push(line);
292
+ }
293
+ }
294
+ flush();
295
+ return map;
296
+ }
297
+ export function deriveRoadmapPhases(sections, warnings = []) {
298
+ const phaseTasks = new Map(PHASE_SPECS.map((phase) => [phase.id, []]));
299
+ const seen = new Map(PHASE_SPECS.map((phase) => [phase.id, new Set()]));
300
+ const addTask = (phaseId, text, source) => {
301
+ const cleaned = cleanTaskText(text);
302
+ if (!cleaned) {
303
+ return;
304
+ }
305
+ const key = normalizeTaskKey(cleaned);
306
+ const phaseSeen = seen.get(phaseId);
307
+ if (phaseSeen?.has(key)) {
308
+ return;
309
+ }
310
+ phaseSeen?.add(key);
311
+ phaseTasks.get(phaseId)?.push({
312
+ phaseId,
313
+ text: cleaned,
314
+ source,
315
+ completed: false,
316
+ });
317
+ };
318
+ const flowItems = extractSectionItems(sections.coreUserFlows?.content);
319
+ const featureItems = extractSectionItems(sections.coreFeatures?.content);
320
+ const screenItems = extractSectionItems(sections.mustIncludeScreensOrFlows?.content);
321
+ const dataItems = extractSectionItems(sections.dataAndBackend?.content);
322
+ const releaseItems = extractSectionItems(sections.releaseStrategy?.content);
323
+ const monetizationItems = extractSectionItems(sections.monetizationStrategy?.content);
324
+ const questionItems = extractSectionItems(sections.questionsToRevisit?.content);
325
+ const platformItems = extractSectionItems(sections.platforms?.content);
326
+ const packageItems = extractSectionItems(sections.packageChoices?.content);
327
+ const firstFlow = flowItems[0];
328
+ if (firstFlow) {
329
+ addTask('phase-1', `Implement the first core user flow: ${firstFlow}.`, 'core-flow');
330
+ }
331
+ else {
332
+ warnings.push('No concrete core flow was found in `project/info.md`; roadmap generation should be rerun after the app intent is clarified.');
333
+ }
334
+ const firstScreens = pickDistinct(screenItems, 3);
335
+ if (firstScreens.length > 0) {
336
+ addTask('phase-1', `Build the first screens or routes needed for the MVP flow: ${formatTaskList(firstScreens)}.`, 'screen');
337
+ }
338
+ const firstFeatures = pickDistinct(featureItems, 3);
339
+ if (firstFeatures.length > 0) {
340
+ addTask('phase-1', `Scope the first feature modules around: ${formatTaskList(firstFeatures)}.`, 'feature');
341
+ }
342
+ if (dataItems.length > 0) {
343
+ addTask('phase-2', `Design the initial data layer and service boundaries for: ${formatTaskList(pickDistinct(dataItems, 3))}.`, 'data');
344
+ }
345
+ else {
346
+ warnings.push('No concrete data/backend notes were found in `project/info.md`; data-layer work will rely on the scaffolded phase anchors until clarified.');
347
+ }
348
+ const keywordSource = [dataItems, releaseItems, monetizationItems, packageItems, platformItems]
349
+ .flat()
350
+ .join(' ');
351
+ for (const task of KEYWORD_TASKS) {
352
+ if (task.patterns.some((pattern) => pattern.test(keywordSource))) {
353
+ addTask(task.phaseId, task.text, task.source);
354
+ }
355
+ }
356
+ const remainingFlows = flowItems.slice(1);
357
+ if (remainingFlows.length > 0) {
358
+ addTask('phase-3', `Implement the remaining core flows from ` +
359
+ '`project/info.md`' +
360
+ `: ${formatTaskList(pickDistinct(remainingFlows, 4))}.`, 'core-flow');
361
+ }
362
+ const remainingScreens = screenItems.slice(firstScreens.length);
363
+ if (remainingScreens.length > 0) {
364
+ addTask('phase-3', `Add the remaining must-include screens or workflows: ${formatTaskList(pickDistinct(remainingScreens, 4))}.`, 'screen');
365
+ }
366
+ if (monetizationItems.length > 0 && !isPlaceholderSection(sections.monetizationStrategy?.content)) {
367
+ addTask('phase-3', `Translate the monetization or business model into product work: ${formatTaskList(pickDistinct(monetizationItems, 2))}.`, 'monetization');
368
+ }
369
+ const remainingPlatforms = extractPlatformTargets(platformItems);
370
+ if (remainingPlatforms.length > 1) {
371
+ addTask('phase-3', `Adapt the completed flows for the remaining target platforms: ${formatTaskList(remainingPlatforms)}.`, 'platform');
372
+ }
373
+ if (releaseItems.length > 0) {
374
+ addTask('phase-4', `Prepare the release flow for: ${formatTaskList(pickDistinct(releaseItems, 3))}.`, 'release');
375
+ }
376
+ if (questionItems.length > 0) {
377
+ addTask('phase-4', 'Close or explicitly defer the remaining open questions before production release.', 'open-question');
378
+ }
379
+ return PHASE_SPECS.map((phase) => ({
380
+ id: phase.id,
381
+ title: phase.title,
382
+ tasks: phaseTasks.get(phase.id) ?? [],
383
+ }));
384
+ }
385
+ export function renderDerivedRoadmapPlaceholder(_phaseId) {
386
+ return '';
387
+ }
388
+ export async function scanProjectTodoForContextMarkers(projectPathInput = '.', options = {}) {
389
+ const projectPath = path.resolve(projectPathInput);
390
+ const projectDir = path.join(projectPath, 'project');
391
+ if (!(await pathExists(projectDir))) {
392
+ return [];
393
+ }
394
+ const markdownFiles = options.scope === 'info'
395
+ ? ['info.md']
396
+ : (await readdir(projectDir, { withFileTypes: true }))
397
+ .filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith('.md'))
398
+ .map((entry) => entry.name)
399
+ .sort();
400
+ const hits = [];
401
+ for (const file of markdownFiles) {
402
+ const filePath = path.join(projectDir, file);
403
+ if (!(await pathExists(filePath))) {
404
+ continue;
405
+ }
406
+ const contents = await readFile(filePath, 'utf8');
407
+ const lines = contents.split(/\r?\n/);
408
+ for (let index = 0; index < lines.length; index += 1) {
409
+ const text = lines[index] ?? '';
410
+ if (isUnresolvedTodoForContextMarkerLine(text)) {
411
+ hits.push({
412
+ file: `project/${file}`,
413
+ line: index + 1,
414
+ text: text.trim(),
415
+ });
416
+ }
417
+ }
418
+ }
419
+ return hits;
420
+ }
421
+ function detectRoadmapClarificationNeeds(sections) {
422
+ const checks = [];
423
+ if (isMissingOrLowConfidenceSection(sections.targetUsers?.content, 'audience')) {
424
+ checks.push({
425
+ warning: '`project/info.md` still has a generic target audience, so roadmap generation cannot tell who the first experience is really for.',
426
+ question: {
427
+ id: 'target-users',
428
+ section: 'targetUsers',
429
+ prompt: 'Who is the actual target user for this app, and what context are they in when they use it?',
430
+ reason: 'Target Users is still generic or placeholder-level.',
431
+ },
432
+ });
433
+ }
434
+ if (isMissingOrLowConfidenceSection(sections.coreUserFlows?.content, 'coreFlows')) {
435
+ checks.push({
436
+ warning: '`project/info.md` still lacks a concrete first user flow, so roadmap generation would be guessing at the MVP.',
437
+ question: {
438
+ id: 'core-user-flows',
439
+ section: 'coreUserFlows',
440
+ prompt: 'What is the first real end-to-end user flow this app should support?',
441
+ reason: 'Core User Flows is still generic or placeholder-level.',
442
+ },
443
+ });
444
+ }
445
+ if (isMissingOrLowConfidenceSection(sections.productGoals?.content, 'productGoals')) {
446
+ checks.push({
447
+ warning: '`project/info.md` still does not say what success looks like, so roadmap priorities would be low-confidence.',
448
+ question: {
449
+ id: 'product-goals',
450
+ section: 'productGoals',
451
+ prompt: 'What business or product outcome would make the first version successful?',
452
+ reason: 'Product Goals is still generic or placeholder-level.',
453
+ },
454
+ });
455
+ }
456
+ if (isMissingOrLowConfidenceSection(sections.releaseStrategy?.content, 'release')) {
457
+ checks.push({
458
+ warning: '`project/info.md` still has a generic release/distribution plan, so release-phase tasks would be mostly filler.',
459
+ question: {
460
+ id: 'release-strategy',
461
+ section: 'releaseStrategy',
462
+ prompt: 'How will the first version actually reach its users: internal demo, TestFlight, web deployment, store launch, or something else?',
463
+ reason: 'Release Strategy is still generic or placeholder-level.',
464
+ },
465
+ });
466
+ }
467
+ return {
468
+ confidenceWarnings: checks.map((check) => check.warning),
469
+ clarificationQuestions: checks.map((check) => check.question),
470
+ };
471
+ }
472
+ function mergeRoadmapIntoTodo(todoRaw, derivedPhases, previousState, preserveStatus) {
473
+ const next = ensureTodoHasPhaseHeadings(todoRaw);
474
+ const lines = normalizeLineEndings(next).split('\n');
475
+ const phaseRanges = getPhaseRanges(lines);
476
+ if (phaseRanges.length === 0) {
477
+ return {
478
+ todoContent: ensureTrailingNewline(next),
479
+ preservedStatuses: 0,
480
+ };
481
+ }
482
+ const output = trimTrailingBlankLines(lines.slice(0, phaseRanges[0]?.start ?? 0));
483
+ let preservedStatuses = 0;
484
+ for (const range of phaseRanges) {
485
+ const phaseTasks = derivedPhases.find((phase) => phase.id === range.phase.id)?.tasks ?? [];
486
+ const sectionLines = lines.slice(range.start + 1, range.end);
487
+ const previousDerivedKeys = new Set(previousState?.phases[range.phase.id]?.derivedTaskKeys ?? []);
488
+ const statusMap = preserveStatus ? readCheckboxStatus(sectionLines) : new Map();
489
+ preservedStatuses += phaseTasks.filter((task) => statusMap.get(normalizeTaskKey(task.text)) === true).length;
490
+ const nextSection = rebuildPhaseSection(sectionLines, phaseTasks, previousDerivedKeys, statusMap);
491
+ if (output.length > 0) {
492
+ output.push('');
493
+ }
494
+ output.push(range.phase.heading, '');
495
+ output.push(...nextSection);
496
+ }
497
+ return {
498
+ todoContent: ensureTrailingNewline(output.join('\n')),
499
+ preservedStatuses,
500
+ };
501
+ }
502
+ function rebuildPhaseSection(existingSectionLines, derivedTasks, previousDerivedKeys, statusMap) {
503
+ const preservedLines = trimBlankEdges(existingSectionLines.filter((line) => {
504
+ if (isLegacyMarkerLine(line)) {
505
+ return false;
506
+ }
507
+ const checkbox = parseCheckbox(line);
508
+ if (!checkbox) {
509
+ return true;
510
+ }
511
+ return !previousDerivedKeys.has(normalizeTaskKey(checkbox.text));
512
+ }));
513
+ const nextDerivedLines = derivedTasks.map((task) => {
514
+ const checked = statusMap.get(normalizeTaskKey(task.text)) === true;
515
+ return `- [${checked ? 'x' : ' '}] ${task.text}`;
516
+ });
517
+ if (preservedLines.length === 0) {
518
+ return nextDerivedLines;
519
+ }
520
+ if (nextDerivedLines.length === 0) {
521
+ return preservedLines;
522
+ }
523
+ return [...preservedLines, '', ...nextDerivedLines];
524
+ }
525
+ function buildRoadmapState(phases) {
526
+ return {
527
+ version: 1,
528
+ phases: {
529
+ 'phase-0': {
530
+ derivedTaskKeys: phases
531
+ .find((phase) => phase.id === 'phase-0')
532
+ ?.tasks.map((task) => normalizeTaskKey(task.text)) ?? [],
533
+ },
534
+ 'phase-1': {
535
+ derivedTaskKeys: phases
536
+ .find((phase) => phase.id === 'phase-1')
537
+ ?.tasks.map((task) => normalizeTaskKey(task.text)) ?? [],
538
+ },
539
+ 'phase-2': {
540
+ derivedTaskKeys: phases
541
+ .find((phase) => phase.id === 'phase-2')
542
+ ?.tasks.map((task) => normalizeTaskKey(task.text)) ?? [],
543
+ },
544
+ 'phase-3': {
545
+ derivedTaskKeys: phases
546
+ .find((phase) => phase.id === 'phase-3')
547
+ ?.tasks.map((task) => normalizeTaskKey(task.text)) ?? [],
548
+ },
549
+ 'phase-4': {
550
+ derivedTaskKeys: phases
551
+ .find((phase) => phase.id === 'phase-4')
552
+ ?.tasks.map((task) => normalizeTaskKey(task.text)) ?? [],
553
+ },
554
+ },
555
+ };
556
+ }
557
+ async function readRoadmapState(filePath) {
558
+ if (!(await pathExists(filePath))) {
559
+ return null;
560
+ }
561
+ try {
562
+ const parsed = JSON.parse(await readFile(filePath, 'utf8'));
563
+ if (parsed?.version !== 1 || typeof parsed.phases !== 'object' || parsed.phases === null) {
564
+ return null;
565
+ }
566
+ return parsed;
567
+ }
568
+ catch {
569
+ return null;
570
+ }
571
+ }
572
+ function inferLegacyRoadmapState(todoRaw) {
573
+ if (!todoRaw.includes(LEGACY_MARKER_PREFIX)) {
574
+ return null;
575
+ }
576
+ const phases = Object.fromEntries(PHASE_SPECS.map((phase) => [
577
+ phase.id,
578
+ {
579
+ derivedTaskKeys: readLegacyDerivedTaskKeys(todoRaw, phase.id),
580
+ },
581
+ ]));
582
+ return {
583
+ version: 1,
584
+ phases,
585
+ };
586
+ }
587
+ function readLegacyDerivedTaskKeys(todoRaw, phaseId) {
588
+ const marker = `MDS_DERIVED_${phaseId.toUpperCase().replace(/-/g, '_')}`;
589
+ const pattern = new RegExp(`<!-- ${marker}_START -->([\\s\\S]*?)<!-- ${marker}_END -->`);
590
+ const match = pattern.exec(todoRaw);
591
+ if (!match?.[1]) {
592
+ return [];
593
+ }
594
+ return match[1]
595
+ .split(/\r?\n/u)
596
+ .map((line) => parseCheckbox(line)?.text)
597
+ .filter((value) => Boolean(value))
598
+ .map(normalizeTaskKey)
599
+ .filter(Boolean);
600
+ }
601
+ function ensureTodoHasPhaseHeadings(todoRaw) {
602
+ let next = ensureTrailingNewline(stripLegacyDerivedMarkers(todoRaw));
603
+ for (const phase of PHASE_SPECS) {
604
+ if (!next.includes(phase.heading)) {
605
+ next = `${next.trimEnd()}\n\n${phase.heading}\n`;
606
+ }
607
+ }
608
+ return ensureTrailingNewline(next);
609
+ }
610
+ function getPhaseRanges(lines) {
611
+ const ranges = [];
612
+ const headingIndexes = PHASE_SPECS.map((phase) => ({
613
+ phase,
614
+ index: lines.findIndex((line) => line.trim() === phase.heading),
615
+ })).filter((item) => item.index >= 0);
616
+ for (let index = 0; index < headingIndexes.length; index += 1) {
617
+ const current = headingIndexes[index];
618
+ const next = headingIndexes[index + 1];
619
+ if (!current) {
620
+ continue;
621
+ }
622
+ ranges.push({
623
+ phase: current.phase,
624
+ start: current.index,
625
+ end: next?.index ?? lines.length,
626
+ });
627
+ }
628
+ return ranges;
629
+ }
630
+ function readCheckboxStatus(lines) {
631
+ const status = new Map();
632
+ for (const line of lines) {
633
+ const checkbox = parseCheckbox(line);
634
+ if (!checkbox) {
635
+ continue;
636
+ }
637
+ status.set(normalizeTaskKey(checkbox.text), checkbox.checked);
638
+ }
639
+ return status;
640
+ }
641
+ function parseCheckbox(line) {
642
+ const match = /^-\s+\[(x| )\]\s+(.+?)\s*$/i.exec(line.trim());
643
+ if (!match?.[1] || !match[2]) {
644
+ return null;
645
+ }
646
+ return {
647
+ checked: match[1].toLowerCase() === 'x',
648
+ text: match[2],
649
+ };
650
+ }
651
+ function stripLegacyDerivedMarkers(todoRaw) {
652
+ return normalizeLineEndings(todoRaw)
653
+ .replace(/<!-- MDS_DERIVED_PHASE_[^>]+_START -->\n?/g, '')
654
+ .replace(/<!-- MDS_DERIVED_PHASE_[^>]+_END -->\n?/g, '');
655
+ }
656
+ function isLegacyMarkerLine(line) {
657
+ return line.trim().startsWith(LEGACY_MARKER_PREFIX);
658
+ }
659
+ function normalizeSectionHeading(value) {
660
+ const normalized = normalizeTaskKey(value);
661
+ for (const [key, aliases] of Object.entries(SECTION_ALIASES)) {
662
+ if (aliases.map(normalizeTaskKey).includes(normalized)) {
663
+ return key;
664
+ }
665
+ }
666
+ return null;
667
+ }
668
+ function extractSectionItems(content) {
669
+ if (!content || isPlaceholderSection(content)) {
670
+ return [];
671
+ }
672
+ const lines = normalizeLineEndings(content).split('\n');
673
+ const bulletItems = lines
674
+ .map((line) => {
675
+ const bulletMatch = /^\s*[-*]\s+(.+?)\s*$/.exec(line);
676
+ const numberedMatch = /^\s*\d+\.\s+(.+?)\s*$/.exec(line);
677
+ return bulletMatch?.[1] ?? numberedMatch?.[1] ?? null;
678
+ })
679
+ .filter((value) => Boolean(value))
680
+ .map(cleanTaskText)
681
+ .filter(Boolean);
682
+ if (bulletItems.length > 0) {
683
+ return uniqueItems(bulletItems);
684
+ }
685
+ const prose = lines
686
+ .map((line) => line.trim())
687
+ .filter((line) => line.length > 0)
688
+ .filter((line) => !line.startsWith('#'))
689
+ .filter((line) => !line.startsWith('>'))
690
+ .filter((line) => !line.startsWith('```'))
691
+ .filter((line) => !line.includes(TODO_FOR_CONTEXT_MARKER))
692
+ .join(' ');
693
+ if (!prose) {
694
+ return [];
695
+ }
696
+ const sentenceSplit = prose
697
+ .split(/(?:[.;]|\s{2,})/u)
698
+ .map((item) => cleanTaskText(item))
699
+ .filter(Boolean);
700
+ return uniqueItems(sentenceSplit);
701
+ }
702
+ function extractPlatformTargets(platformItems) {
703
+ const values = platformItems
704
+ .flatMap((item) => item.split(/[:,]/u))
705
+ .map((item) => item.trim())
706
+ .filter(Boolean)
707
+ .flatMap((item) => item.split(/\s+and\s+|\s*,\s*/u))
708
+ .map((item) => item.trim())
709
+ .filter((item) => ['web', 'ios', 'android', 'apple tv', 'android tv', 'tvos'].includes(normalizeTaskKey(item)))
710
+ .map((item) => normalizeTaskKey(item))
711
+ .map((item) => {
712
+ if (item === 'tvos') {
713
+ return 'apple tv';
714
+ }
715
+ return item;
716
+ });
717
+ return uniqueItems(values);
718
+ }
719
+ function formatTaskList(items) {
720
+ const picked = pickDistinct(items, 4);
721
+ if (picked.length === 0) {
722
+ return '';
723
+ }
724
+ if (picked.length === 1) {
725
+ return picked[0] ?? '';
726
+ }
727
+ if (picked.length === 2) {
728
+ return `${picked[0]} and ${picked[1]}`;
729
+ }
730
+ return `${picked.slice(0, -1).join(', ')}, and ${picked[picked.length - 1]}`;
731
+ }
732
+ function pickDistinct(items, max) {
733
+ return uniqueItems(items).slice(0, max);
734
+ }
735
+ function uniqueItems(items) {
736
+ const seen = new Set();
737
+ const result = [];
738
+ for (const item of items) {
739
+ const key = normalizeTaskKey(item);
740
+ if (!key || seen.has(key)) {
741
+ continue;
742
+ }
743
+ seen.add(key);
744
+ result.push(item);
745
+ }
746
+ return result;
747
+ }
748
+ function cleanTaskText(value) {
749
+ return value
750
+ .replace(/^Derived from the first planned flows:\s*/i, '')
751
+ .replace(/^Starting mode:\s*/i, '')
752
+ .replace(/^Deployment plan:\s*/i, '')
753
+ .replace(/^-\s*/, '')
754
+ .replace(/^`|`$/g, '')
755
+ .replace(/\s+/g, ' ')
756
+ .replace(/\s+\.$/, '.')
757
+ .trim()
758
+ .replace(/[;,:-]+$/u, '')
759
+ .trim();
760
+ }
761
+ function normalizeTaskKey(value) {
762
+ return value
763
+ .toLowerCase()
764
+ .replace(/`/g, '')
765
+ .replace(/[^a-z0-9]+/g, ' ')
766
+ .replace(/\s+/g, ' ')
767
+ .trim();
768
+ }
769
+ function isMissingOrLowConfidenceSection(content, kind) {
770
+ if (!content || isPlaceholderSection(content)) {
771
+ return true;
772
+ }
773
+ const normalized = normalizeTaskKey(content);
774
+ if (!normalized) {
775
+ return true;
776
+ }
777
+ return GENERIC_PATTERNS[kind].some((pattern) => pattern.test(content));
778
+ }
779
+ function isPlaceholderSection(content) {
780
+ if (!content) {
781
+ return true;
782
+ }
783
+ const normalized = normalizeTaskKey(content);
784
+ return (content.includes(TODO_FOR_CONTEXT_MARKER) ||
785
+ normalized.startsWith('add ') ||
786
+ normalized.includes('not planned yet') ||
787
+ normalized.includes('agent should derive') ||
788
+ normalized.includes('replace generic onboarding defaults'));
789
+ }
790
+ function extractProjectName(infoRaw) {
791
+ const match = /^#\s+(.+?)\s*$/m.exec(normalizeLineEndings(infoRaw));
792
+ if (!match?.[1]) {
793
+ return 'Project';
794
+ }
795
+ return match[1].replace(/\s+project info$/i, '').trim();
796
+ }
797
+ function renderTodoSkeleton(appName) {
798
+ return [
799
+ `# ${appName} TODO`,
800
+ '',
801
+ '## Phase 0: Orientation And Planning',
802
+ '',
803
+ '- [ ] Review `project/` files for accuracy and planning adjustments.',
804
+ '- [ ] 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.',
805
+ '- [ ] Refresh the agent-derived roadmap from `project/info.md` and review it for accuracy before implementation.',
806
+ '',
807
+ '## Phase 1: App Shell And First Flow',
808
+ '',
809
+ '- [ ] Establish the app shell and first implementation-ready route for the MVP.',
810
+ '- [ ] Implement the first concrete product flow from `project/info.md` and the roadmap.',
811
+ '',
812
+ '## Phase 2: Data Layer',
813
+ '',
814
+ '- [ ] Implement the initial data layer and service boundaries needed for the MVP.',
815
+ '',
816
+ '## Phase 3: Complete Product Flows',
817
+ '',
818
+ '- [ ] Complete the remaining product flows needed for the MVP.',
819
+ '',
820
+ '## Phase 4: Polish, Safeguards, And Release',
821
+ '',
822
+ '- [ ] Run `mds doctor --ci` and address errors before release.',
823
+ '',
824
+ ].join('\n');
825
+ }
826
+ function ensureTrailingNewline(value) {
827
+ return `${normalizeLineEndings(value).trimEnd()}\n`;
828
+ }
829
+ function normalizeLineEndings(value) {
830
+ return value.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
831
+ }
832
+ function isUnresolvedTodoForContextMarkerLine(line) {
833
+ const trimmed = line.trim();
834
+ return (trimmed.startsWith(TODO_FOR_CONTEXT_MARKER) ||
835
+ trimmed.startsWith(`- ${TODO_FOR_CONTEXT_MARKER}`) ||
836
+ trimmed.startsWith(`* ${TODO_FOR_CONTEXT_MARKER}`));
837
+ }
838
+ function trimBlankEdges(lines) {
839
+ let start = 0;
840
+ let end = lines.length;
841
+ while (start < end && (lines[start]?.trim() ?? '') === '') {
842
+ start += 1;
843
+ }
844
+ while (end > start && (lines[end - 1]?.trim() ?? '') === '') {
845
+ end -= 1;
846
+ }
847
+ return lines.slice(start, end);
848
+ }
849
+ function trimTrailingBlankLines(lines) {
850
+ const next = [...lines];
851
+ while (next.length > 0 && (next[next.length - 1]?.trim() ?? '') === '') {
852
+ next.pop();
853
+ }
854
+ return next;
855
+ }
856
+ async function pathExists(filePath) {
857
+ try {
858
+ await access(filePath);
859
+ return true;
860
+ }
861
+ catch {
862
+ return false;
863
+ }
864
+ }
865
+ //# sourceMappingURL=roadmap.js.map