midas-mcp 5.41.0 → 5.43.1

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 (34) hide show
  1. package/dist/analyzer.d.ts.map +1 -1
  2. package/dist/analyzer.js +9 -2
  3. package/dist/analyzer.js.map +1 -1
  4. package/dist/context.d.ts +264 -5
  5. package/dist/context.d.ts.map +1 -1
  6. package/dist/context.js +634 -7
  7. package/dist/context.js.map +1 -1
  8. package/dist/experiments/recursive-planning/recursive-session.d.ts +201 -0
  9. package/dist/experiments/recursive-planning/recursive-session.d.ts.map +1 -0
  10. package/dist/experiments/recursive-planning/recursive-session.js +348 -0
  11. package/dist/experiments/recursive-planning/recursive-session.js.map +1 -0
  12. package/dist/experiments/recursive-planning/recursive-session.test.d.ts +19 -0
  13. package/dist/experiments/recursive-planning/recursive-session.test.d.ts.map +1 -0
  14. package/dist/experiments/recursive-planning/recursive-session.test.js +799 -0
  15. package/dist/experiments/recursive-planning/recursive-session.test.js.map +1 -0
  16. package/dist/preflight.d.ts +140 -0
  17. package/dist/preflight.d.ts.map +1 -0
  18. package/dist/preflight.js +1100 -0
  19. package/dist/preflight.js.map +1 -0
  20. package/dist/server.js +4 -4
  21. package/dist/server.js.map +1 -1
  22. package/dist/tools/index.d.ts +1 -1
  23. package/dist/tools/index.d.ts.map +1 -1
  24. package/dist/tools/index.js +4 -2
  25. package/dist/tools/index.js.map +1 -1
  26. package/dist/tools/preflight.d.ts +121 -0
  27. package/dist/tools/preflight.d.ts.map +1 -0
  28. package/dist/tools/preflight.js +144 -0
  29. package/dist/tools/preflight.js.map +1 -0
  30. package/dist/tools/verify.js +2 -2
  31. package/dist/tools/verify.js.map +1 -1
  32. package/dist/tui.js +53 -53
  33. package/dist/tui.js.map +1 -1
  34. package/package.json +1 -1
@@ -0,0 +1,1100 @@
1
+ /**
2
+ * Preflight Check Module
3
+ *
4
+ * Analyzes project docs to infer what requirements apply,
5
+ * categorizes them by what AI can/cannot do,
6
+ * and generates context-aware prompts for Cursor.
7
+ */
8
+ import { existsSync, readFileSync, mkdirSync } from 'fs';
9
+ import writeFileAtomic from 'write-file-atomic';
10
+ import { join } from 'path';
11
+ import { sanitizePath } from './security.js';
12
+ import { discoverDocsSync, getPlanningContext } from './docs-discovery.js';
13
+ // ============================================================================
14
+ // PERSISTENCE
15
+ // ============================================================================
16
+ const MIDAS_DIR = '.midas';
17
+ const PREFLIGHT_STATE_FILE = 'preflight-checks.json';
18
+ function getPreflightStatePath(projectPath) {
19
+ return join(projectPath, MIDAS_DIR, PREFLIGHT_STATE_FILE);
20
+ }
21
+ export function loadPreflightState(projectPath) {
22
+ const path = getPreflightStatePath(projectPath);
23
+ if (existsSync(path)) {
24
+ try {
25
+ return JSON.parse(readFileSync(path, 'utf-8'));
26
+ }
27
+ catch {
28
+ return { checkStates: {}, lastProfileHash: '' };
29
+ }
30
+ }
31
+ // Try legacy reality-checks.json for backward compatibility
32
+ const legacyPath = join(projectPath, MIDAS_DIR, 'reality-checks.json');
33
+ if (existsSync(legacyPath)) {
34
+ try {
35
+ return JSON.parse(readFileSync(legacyPath, 'utf-8'));
36
+ }
37
+ catch {
38
+ return { checkStates: {}, lastProfileHash: '' };
39
+ }
40
+ }
41
+ return { checkStates: {}, lastProfileHash: '' };
42
+ }
43
+ function savePreflightState(projectPath, state) {
44
+ const dir = join(projectPath, MIDAS_DIR);
45
+ if (!existsSync(dir)) {
46
+ mkdirSync(dir, { recursive: true });
47
+ }
48
+ // Use atomic write to prevent corruption from concurrent access
49
+ writeFileAtomic.sync(getPreflightStatePath(projectPath), JSON.stringify(state, null, 2));
50
+ }
51
+ function hashProfile(profile) {
52
+ // Simple hash to detect profile changes
53
+ return JSON.stringify(profile).split('').reduce((a, b) => {
54
+ a = ((a << 5) - a) + b.charCodeAt(0);
55
+ return a & a;
56
+ }, 0).toString(36);
57
+ }
58
+ /**
59
+ * Update the status of a preflight check
60
+ */
61
+ export function updateCheckStatus(projectPath, checkKey, status, skippedReason) {
62
+ const safePath = sanitizePath(projectPath);
63
+ const state = loadPreflightState(safePath);
64
+ state.checkStates[checkKey] = {
65
+ status,
66
+ updatedAt: new Date().toISOString(),
67
+ ...(skippedReason ? { skippedReason } : {}),
68
+ };
69
+ savePreflightState(safePath, state);
70
+ }
71
+ /**
72
+ * Get the persisted status for a check
73
+ */
74
+ export function getCheckStatus(projectPath, checkKey) {
75
+ const safePath = sanitizePath(projectPath);
76
+ const state = loadPreflightState(safePath);
77
+ return state.checkStates[checkKey];
78
+ }
79
+ /**
80
+ * Get all check statuses
81
+ */
82
+ export function getAllCheckStatuses(projectPath) {
83
+ const safePath = sanitizePath(projectPath);
84
+ const state = loadPreflightState(safePath);
85
+ return state.checkStates;
86
+ }
87
+ /**
88
+ * Reset all check statuses (e.g., when profile changes significantly)
89
+ */
90
+ export function resetCheckStatuses(projectPath) {
91
+ const safePath = sanitizePath(projectPath);
92
+ savePreflightState(safePath, { checkStates: {}, lastProfileHash: '' });
93
+ }
94
+ /**
95
+ * Map of check keys to the expected generated file paths
96
+ */
97
+ const EXPECTED_OUTPUTS = {
98
+ PRIVACY_POLICY: ['docs/privacy-policy.md', 'privacy-policy.md', 'PRIVACY.md'],
99
+ TERMS_OF_SERVICE: ['docs/terms-of-service.md', 'docs/terms.md', 'TERMS.md'],
100
+ COOKIE_POLICY: ['docs/cookie-policy.md'],
101
+ GDPR_COMPLIANCE: ['docs/gdpr-checklist.md', 'docs/gdpr.md'],
102
+ CCPA_COMPLIANCE: ['docs/ccpa-checklist.md'],
103
+ AI_DISCLOSURE: ['docs/ai-disclosure.md', 'AI_DISCLOSURE.md'],
104
+ ACCESSIBILITY: ['docs/accessibility.md', 'docs/a11y.md', 'ACCESSIBILITY.md'],
105
+ DATA_RETENTION: ['docs/data-retention.md'],
106
+ INCIDENT_RESPONSE: ['docs/incident-response.md'],
107
+ OSS_LICENSE: ['LICENSE', 'LICENSE.md', 'docs/license.md'],
108
+ HIPAA_COMPLIANCE: ['docs/hipaa-checklist.md'],
109
+ FERPA_COMPLIANCE: ['docs/ferpa-checklist.md'],
110
+ EU_AI_ACT: ['docs/eu-ai-act-assessment.md'],
111
+ SBOM: ['sbom.json', 'docs/sbom-readme.md'],
112
+ DATA_RESIDENCY: ['docs/data-residency.md'],
113
+ };
114
+ /**
115
+ * Detect if any expected output files exist and auto-complete checks
116
+ * Returns array of check keys that were auto-completed
117
+ */
118
+ export function detectGeneratedDocs(projectPath) {
119
+ const safePath = sanitizePath(projectPath);
120
+ const state = loadPreflightState(safePath);
121
+ const autoCompleted = [];
122
+ for (const [checkKey, possibleFiles] of Object.entries(EXPECTED_OUTPUTS)) {
123
+ // Skip if already completed
124
+ if (state.checkStates[checkKey]?.status === 'completed')
125
+ continue;
126
+ // Check if any of the expected files exist
127
+ const fileExists = possibleFiles.some(file => existsSync(join(safePath, file)));
128
+ if (fileExists) {
129
+ // Auto-complete this check
130
+ state.checkStates[checkKey] = {
131
+ status: 'completed',
132
+ updatedAt: new Date().toISOString(),
133
+ };
134
+ autoCompleted.push(checkKey);
135
+ }
136
+ }
137
+ if (autoCompleted.length > 0) {
138
+ savePreflightState(safePath, state);
139
+ }
140
+ return autoCompleted;
141
+ }
142
+ // Legacy tier mapping for backward compatibility
143
+ const TIER_MAPPING = {
144
+ 'generatable': 'ai_assisted',
145
+ 'assistable': 'ai_assisted',
146
+ 'human_only': 'manual',
147
+ 'ai_assisted': 'ai_assisted',
148
+ 'manual': 'manual',
149
+ };
150
+ // Default triggered-by generators based on common profile fields
151
+ const DEFAULT_TRIGGERS = {
152
+ PRIVACY_POLICY: (p) => p.collectsSensitiveData
153
+ ? 'Project collects sensitive data (health/financial/biometric)'
154
+ : 'Project collects user data',
155
+ TERMS_OF_SERVICE: () => 'Public-facing product',
156
+ COOKIE_POLICY: (p) => p.targetsEU ? 'Targets EU users' : 'Collects user data',
157
+ GDPR_COMPLIANCE: (p) => p.targetsEU ? 'Explicitly targets EU users' : 'May have EU users',
158
+ CCPA_COMPLIANCE: () => 'Targets California or US users',
159
+ COPPA_COMPLIANCE: () => 'May have users under 13',
160
+ AI_DISCLOSURE: () => 'Uses AI for decisions or recommendations',
161
+ PAYMENT_SETUP: () => 'Has payment/subscription features',
162
+ STRIPE_INTEGRATION: () => 'Has payment processing',
163
+ APP_STORE: () => 'Distributes via iOS App Store',
164
+ PLAY_STORE: () => 'Distributes via Google Play Store',
165
+ ACCESSIBILITY: () => 'Public-facing product should be accessible',
166
+ DATA_RETENTION: (p) => p.collectsSensitiveData ? 'Handles sensitive data' : 'Collects user data',
167
+ INCIDENT_RESPONSE: () => 'Production system needs incident handling',
168
+ OSS_LICENSE: () => 'Open source project needs license',
169
+ HIPAA_COMPLIANCE: () => 'Healthcare industry + collects user data',
170
+ FERPA_COMPLIANCE: () => 'Education industry + collects student data',
171
+ EU_AI_ACT: (p) => p.targetsEU
172
+ ? 'AI system targeting EU users'
173
+ : 'AI system in regulated industry (healthcare/education/finance)',
174
+ SBOM: () => 'Enterprise/finance audience expects supply chain transparency',
175
+ DATA_RESIDENCY: (p) => p.targetsEU
176
+ ? 'EU users require data residency documentation (GDPR)'
177
+ : 'Enterprise customers require data location clarity',
178
+ };
179
+ const PREFLIGHT_CHECKS = {
180
+ // ✅ GENERATABLE - AI can draft these
181
+ PRIVACY_POLICY: {
182
+ key: 'PRIVACY_POLICY',
183
+ category: 'Legal',
184
+ tier: 'ai_assisted',
185
+ headline: 'You need a Privacy Policy',
186
+ explanation: 'You collect user data. Users need to know what you collect, why, and how to delete it.',
187
+ priority: 'critical',
188
+ promptTemplate: `Read docs/brainlift.md and docs/prd.md to understand this project. Then create a privacy policy.
189
+
190
+ First, identify from the docs:
191
+ - What user data is collected
192
+ - Why it's collected
193
+ - Who the target users are
194
+ - The business model
195
+
196
+ Then create docs/privacy-policy.md with sections:
197
+ - What we collect and why
198
+ - How we use the data
199
+ - Third parties we share with
200
+ - Data retention
201
+ - User rights (access, correct, delete)
202
+ - Contact information
203
+
204
+ Add at top: "DRAFT - Review with a lawyer before publishing"`,
205
+ condition: (p) => p.collectsUserData,
206
+ },
207
+ TERMS_OF_SERVICE: {
208
+ key: 'TERMS_OF_SERVICE',
209
+ category: 'Legal',
210
+ tier: 'ai_assisted',
211
+ headline: 'You need Terms of Service',
212
+ explanation: 'Any public product needs terms defining the rules of use and liability limits.',
213
+ priority: 'high',
214
+ promptTemplate: `Read docs/brainlift.md and docs/prd.md to understand this project. Then create terms of service.
215
+
216
+ Create docs/terms-of-service.md with sections:
217
+ - Acceptance of terms
218
+ - Description of service (from what you read)
219
+ - User responsibilities
220
+ - Prohibited uses
221
+ - Intellectual property
222
+ - Limitation of liability
223
+ - Termination
224
+ - Governing law (placeholder for jurisdiction)
225
+
226
+ Save to docs/terms-of-service.md
227
+
228
+ Add at the top: "DRAFT - Review with a lawyer before publishing"`,
229
+ condition: (p) => p.collectsUserData || p.hasPayments,
230
+ },
231
+ AI_DISCLOSURE: {
232
+ key: 'AI_DISCLOSURE',
233
+ category: 'Transparency',
234
+ tier: 'ai_assisted',
235
+ headline: 'You need an AI disclosure',
236
+ explanation: 'Users should know when AI is involved and that it can make mistakes.',
237
+ priority: 'high',
238
+ promptTemplate: `Create an AI transparency disclosure for this project.
239
+
240
+ Based on the brainlift, this project uses AI to: {{aiUsage}}
241
+
242
+ Create a user-friendly disclosure explaining:
243
+ - What AI does in this product
244
+ - That AI can make mistakes or produce inaccurate results
245
+ - How users can report issues or get human help
246
+ - Any limitations users should know about
247
+
248
+ Save to docs/ai-disclosure.md
249
+
250
+ Also add a brief inline disclosure component/text that can be shown in the UI where AI is used.`,
251
+ condition: (p) => p.usesAI,
252
+ },
253
+ REFUND_POLICY: {
254
+ key: 'REFUND_POLICY',
255
+ category: 'Business',
256
+ tier: 'ai_assisted',
257
+ headline: 'You need a refund policy',
258
+ explanation: 'Paid products need clear refund terms to avoid disputes and chargebacks.',
259
+ priority: 'high',
260
+ promptTemplate: `Create a refund policy for this project.
261
+
262
+ Business model: {{businessModel}}
263
+ Subscription type: {{subscriptionType}}
264
+
265
+ Include:
266
+ - Refund eligibility (time period, conditions)
267
+ - How to request a refund
268
+ - Processing time
269
+ - Exceptions (if any)
270
+ - Contact information
271
+
272
+ Keep it simple and fair - generous refund policies reduce chargebacks.
273
+
274
+ Save to docs/refund-policy.md`,
275
+ condition: (p) => p.hasPayments,
276
+ },
277
+ CONTENT_POLICY: {
278
+ key: 'CONTENT_POLICY',
279
+ category: 'Trust',
280
+ tier: 'ai_assisted',
281
+ headline: 'You need a content policy',
282
+ explanation: 'User-generated content needs rules about what\'s allowed and how violations are handled.',
283
+ priority: 'high',
284
+ promptTemplate: `Create a content policy for this project.
285
+
286
+ This product allows users to: {{userContentType}}
287
+
288
+ Include:
289
+ - What content is allowed
290
+ - What content is prohibited (hate speech, harassment, illegal content, etc.)
291
+ - How violations are reported
292
+ - How we handle violations (warning, removal, ban)
293
+ - Appeal process
294
+
295
+ Save to docs/content-policy.md`,
296
+ condition: (p) => p.hasUserContent,
297
+ },
298
+ // ⚠️ ASSISTABLE - AI can create guide, needs professional verification
299
+ GDPR_COMPLIANCE: {
300
+ key: 'GDPR_COMPLIANCE',
301
+ category: 'Compliance',
302
+ tier: 'ai_assisted',
303
+ headline: 'GDPR applies to your product',
304
+ explanation: 'You\'re targeting EU users. You need lawful basis for data processing, user consent, and data rights.',
305
+ priority: 'critical',
306
+ alsoNeeded: ['Legal review of implementation', 'DPA registration if required', 'Data Processing Agreements with vendors'],
307
+ promptTemplate: `Create a GDPR compliance implementation guide for this project.
308
+
309
+ Based on analysis:
310
+ - Data collected: {{dataCollected}}
311
+ - Processing purposes: {{processingPurposes}}
312
+ - Third parties: {{thirdParties}}
313
+
314
+ Generate:
315
+ 1. Data inventory table (what data, why collected, legal basis, retention period)
316
+ 2. Consent flow requirements (what needs explicit consent vs legitimate interest)
317
+ 3. User rights implementation checklist:
318
+ - Right to access (export user data)
319
+ - Right to rectification (edit profile)
320
+ - Right to erasure (delete account)
321
+ - Right to portability (download data)
322
+ 4. Cookie consent requirements
323
+ 5. Privacy by design checklist
324
+
325
+ Save to docs/gdpr-implementation.md
326
+
327
+ Note at top: "This is a technical implementation guide. Legal review required before launch."`,
328
+ condition: (p) => p.targetsEU && p.collectsUserData,
329
+ },
330
+ CCPA_COMPLIANCE: {
331
+ key: 'CCPA_COMPLIANCE',
332
+ category: 'Compliance',
333
+ tier: 'ai_assisted',
334
+ headline: 'CCPA applies to your product',
335
+ explanation: 'California users have rights to know, delete, and opt-out of data sales.',
336
+ priority: 'high',
337
+ alsoNeeded: ['Legal review', '"Do Not Sell" link if applicable'],
338
+ promptTemplate: `Create a CCPA compliance checklist for this project.
339
+
340
+ Data collected: {{dataCollected}}
341
+ California users expected: Yes
342
+
343
+ Include:
344
+ 1. Right to know (disclosure of data collected)
345
+ 2. Right to delete implementation
346
+ 3. Right to opt-out (if selling data)
347
+ 4. Non-discrimination requirements
348
+ 5. Privacy policy updates needed for CCPA
349
+
350
+ Save to docs/ccpa-checklist.md`,
351
+ condition: (p) => p.targetsCalifornia && p.collectsUserData,
352
+ },
353
+ ACCESSIBILITY: {
354
+ key: 'ACCESSIBILITY',
355
+ category: 'Inclusion',
356
+ tier: 'ai_assisted',
357
+ headline: 'Consider accessibility (WCAG)',
358
+ explanation: 'Making your product accessible helps more users and may be legally required for some customers.',
359
+ priority: 'medium',
360
+ alsoNeeded: ['Actual testing with screen readers', 'User testing with diverse users'],
361
+ promptTemplate: `Create an accessibility checklist and implementation guide for this project.
362
+
363
+ Review the codebase and create:
364
+ 1. WCAG 2.1 AA compliance checklist with current status
365
+ 2. Priority fixes needed (semantic HTML, ARIA labels, color contrast)
366
+ 3. Keyboard navigation requirements
367
+ 4. Screen reader compatibility notes
368
+ 5. Form accessibility (labels, error messages)
369
+
370
+ For each issue found, provide the fix.
371
+
372
+ Save checklist to docs/accessibility-checklist.md`,
373
+ condition: (p) => p.targetAudience.some(a => ['enterprise', 'education', 'government'].includes(a)) || p.collectsUserData,
374
+ },
375
+ BIAS_ASSESSMENT: {
376
+ key: 'BIAS_ASSESSMENT',
377
+ category: 'Ethics',
378
+ tier: 'ai_assisted',
379
+ headline: 'AI bias assessment needed',
380
+ explanation: 'AI that makes decisions about people can have unintended bias. Document what you\'ve considered.',
381
+ priority: 'high',
382
+ alsoNeeded: ['Testing with diverse user groups', 'Regular monitoring for bias', 'Human override mechanism'],
383
+ promptTemplate: `Create an AI bias assessment document for this project.
384
+
385
+ AI is used for: {{aiUsage}}
386
+ Decisions affected: {{decisionsAffected}}
387
+
388
+ Document:
389
+ 1. What decisions the AI influences
390
+ 2. Potential sources of bias (training data, model architecture)
391
+ 3. Protected characteristics that could be affected
392
+ 4. Mitigation strategies implemented
393
+ 5. Monitoring plan for detecting bias
394
+ 6. Human override / appeal mechanism
395
+
396
+ Save to docs/ai-bias-assessment.md
397
+
398
+ This is for internal documentation and transparency.`,
399
+ condition: (p) => p.aiMakesDecisions,
400
+ },
401
+ // ❌ HUMAN ONLY - Requires real-world action
402
+ PAYMENT_SETUP: {
403
+ key: 'PAYMENT_SETUP',
404
+ category: 'Business',
405
+ tier: 'manual',
406
+ headline: 'You need payment processing',
407
+ explanation: 'You want to charge users. You need a payment provider account first.',
408
+ priority: 'critical',
409
+ humanSteps: [
410
+ 'Go to stripe.com and create an account',
411
+ 'Complete business verification (1-3 days)',
412
+ 'Set up your bank account for payouts',
413
+ 'Get your API keys from the dashboard',
414
+ ],
415
+ externalLinks: ['https://stripe.com', 'https://stripe.com/docs/keys'],
416
+ promptTemplate: `Implement Stripe payment integration for this project.
417
+
418
+ Requirements from PRD:
419
+ - Business model: {{businessModel}}
420
+ - Pricing: {{pricing}}
421
+
422
+ Implement:
423
+ 1. Stripe SDK setup with environment variables for keys
424
+ 2. Checkout session creation for {{checkoutType}}
425
+ 3. Webhook handler for payment events (payment_intent.succeeded, subscription events)
426
+ 4. Customer portal link for subscription management
427
+ 5. Subscription status middleware to gate premium features
428
+
429
+ I have my Stripe API keys ready. Use STRIPE_SECRET_KEY and STRIPE_PUBLISHABLE_KEY env vars.`,
430
+ condition: (p) => p.hasPayments,
431
+ },
432
+ BUSINESS_REGISTRATION: {
433
+ key: 'BUSINESS_REGISTRATION',
434
+ category: 'Legal',
435
+ tier: 'manual',
436
+ headline: 'Consider business registration',
437
+ explanation: 'If you\'re making money, you may need a business entity for taxes and liability protection.',
438
+ priority: 'medium',
439
+ humanSteps: [
440
+ 'Research business structures (LLC, Corp, Sole Prop)',
441
+ 'Register in your state/country',
442
+ 'Get an EIN (US) or equivalent tax ID',
443
+ 'Open a business bank account',
444
+ ],
445
+ externalLinks: ['https://www.sba.gov/business-guide/launch-your-business/choose-business-structure'],
446
+ promptTemplate: `No code needed. This is a business/legal step.
447
+
448
+ Once you have your business set up, update your docs:
449
+ - Add business name to privacy policy and terms
450
+ - Add business address to contact information
451
+ - Update payment provider with business details`,
452
+ condition: (p) => p.hasPayments && p.businessModel !== 'free',
453
+ },
454
+ TAX_SETUP: {
455
+ key: 'TAX_SETUP',
456
+ category: 'Business',
457
+ tier: 'manual',
458
+ headline: 'You need tax handling',
459
+ explanation: 'Selling internationally means dealing with VAT, GST, and sales tax. Stripe Tax can help.',
460
+ priority: 'high',
461
+ humanSteps: [
462
+ 'Enable Stripe Tax in your Stripe dashboard',
463
+ 'Register for VAT/GST in required jurisdictions',
464
+ 'Or use a service like Paddle that handles tax for you',
465
+ ],
466
+ externalLinks: ['https://stripe.com/tax', 'https://paddle.com'],
467
+ promptTemplate: `Add Stripe Tax integration to handle VAT/GST automatically.
468
+
469
+ Enable tax calculation in checkout:
470
+ 1. Add tax_behavior: 'exclusive' or 'inclusive' to prices
471
+ 2. Enable automatic_tax in checkout sessions
472
+ 3. Collect customer address for tax calculation
473
+ 4. Display tax amounts in checkout UI
474
+
475
+ See Stripe Tax docs for jurisdiction-specific setup.`,
476
+ condition: (p) => p.hasPayments && p.targetsEU,
477
+ },
478
+ DOMAIN_SSL: {
479
+ key: 'DOMAIN_SSL',
480
+ category: 'Infrastructure',
481
+ tier: 'manual',
482
+ headline: 'You need a domain and SSL',
483
+ explanation: 'To launch publicly, you need a domain name and HTTPS.',
484
+ priority: 'high',
485
+ humanSteps: [
486
+ 'Choose and purchase a domain (Namecheap, Cloudflare, etc.)',
487
+ 'Point DNS to your hosting provider',
488
+ 'SSL is usually automatic with modern hosts (Vercel, Netlify, etc.)',
489
+ ],
490
+ externalLinks: ['https://www.namecheap.com', 'https://www.cloudflare.com'],
491
+ promptTemplate: `No code needed for domain purchase.
492
+
493
+ Once you have your domain, update:
494
+ 1. Environment variables with production URL
495
+ 2. OAuth redirect URLs if using social login
496
+ 3. Stripe webhook URLs
497
+ 4. Any hardcoded localhost references`,
498
+ condition: (p) => p.collectsUserData || p.hasPayments,
499
+ },
500
+ APP_STORE: {
501
+ key: 'APP_STORE',
502
+ category: 'Distribution',
503
+ tier: 'manual',
504
+ headline: 'App Store submission needed',
505
+ explanation: 'Mobile apps need App Store / Play Store developer accounts and review.',
506
+ priority: 'critical',
507
+ humanSteps: [
508
+ 'Apple: Enroll in Apple Developer Program ($99/year)',
509
+ 'Google: Create Google Play Developer account ($25 one-time)',
510
+ 'Prepare screenshots, descriptions, privacy policy URL',
511
+ 'Submit for review (Apple: 1-7 days, Google: hours to days)',
512
+ ],
513
+ externalLinks: ['https://developer.apple.com/programs/', 'https://play.google.com/console'],
514
+ promptTemplate: `Prepare app store submission materials:
515
+
516
+ 1. Generate app screenshots for required sizes
517
+ 2. Write app store description (short and long)
518
+ 3. Create app preview video (optional but recommended)
519
+ 4. Prepare answers for review questions (data usage, permissions)
520
+ 5. Ensure privacy policy URL is live and accessible
521
+
522
+ Check that the app follows platform guidelines before submission.`,
523
+ condition: (p) => p.targetAudience.includes('mobile'),
524
+ },
525
+ COPPA_COMPLIANCE: {
526
+ key: 'COPPA_COMPLIANCE',
527
+ category: 'Compliance',
528
+ tier: 'manual',
529
+ headline: 'COPPA compliance required',
530
+ explanation: 'Users under 13 require parental consent and special data handling.',
531
+ priority: 'critical',
532
+ humanSteps: [
533
+ 'Implement age gate / verification',
534
+ 'Get verifiable parental consent mechanism',
535
+ 'Limit data collection for children',
536
+ 'Review with lawyer specializing in children\'s privacy',
537
+ ],
538
+ externalLinks: ['https://www.ftc.gov/business-guidance/resources/complying-coppa-frequently-asked-questions'],
539
+ alsoNeeded: ['Legal review', 'Parental consent mechanism'],
540
+ promptTemplate: `Implement COPPA-compliant age verification:
541
+
542
+ 1. Add age gate before registration
543
+ 2. If under 13, collect parent email
544
+ 3. Send parental consent request
545
+ 4. Only allow account creation after consent verified
546
+ 5. Limit data collection for child accounts
547
+ 6. Add easy way for parents to review/delete child data
548
+
549
+ This requires careful legal review - the FTC enforces COPPA strictly.`,
550
+ condition: (p) => p.hasUnder13Users,
551
+ },
552
+ SOC2: {
553
+ key: 'SOC2',
554
+ category: 'Certification',
555
+ tier: 'manual',
556
+ headline: 'Enterprise customers may require SOC 2',
557
+ explanation: 'B2B/enterprise sales often require SOC 2 certification to prove security practices.',
558
+ priority: 'medium',
559
+ humanSteps: [
560
+ 'This is a 6-12 month process costing $20K-100K+',
561
+ 'Choose a SOC 2 auditor (Vanta, Drata can help automate)',
562
+ 'Implement required controls',
563
+ 'Undergo Type I then Type II audit',
564
+ ],
565
+ externalLinks: ['https://vanta.com', 'https://drata.com'],
566
+ promptTemplate: `SOC 2 is a certification process, not code.
567
+
568
+ However, you can prepare by implementing:
569
+ 1. Access control (role-based permissions, MFA)
570
+ 2. Audit logging (who did what, when)
571
+ 3. Encryption at rest and in transit
572
+ 4. Incident response procedures
573
+ 5. Vendor management documentation
574
+
575
+ Create a security checklist: docs/security-checklist.md`,
576
+ condition: (p) => p.targetAudience.includes('enterprise'),
577
+ },
578
+ // ⚠️ ASSISTABLE - Industry-specific regulations
579
+ HIPAA_COMPLIANCE: {
580
+ key: 'HIPAA_COMPLIANCE',
581
+ category: 'Healthcare',
582
+ tier: 'ai_assisted',
583
+ headline: 'Healthcare data requires HIPAA compliance',
584
+ explanation: 'Handling patient health information in the US requires HIPAA compliance. Violations can cost $100-50K per record.',
585
+ priority: 'critical',
586
+ alsoNeeded: ['Legal review of BAA', 'Security audit', 'Employee HIPAA training'],
587
+ promptTemplate: `Read docs/brainlift.md and docs/prd.md. This is a healthcare application that may need HIPAA compliance.
588
+
589
+ Create docs/hipaa-checklist.md covering:
590
+ 1. PHI (Protected Health Information) inventory - what health data do we handle?
591
+ 2. Access controls - minimum necessary access
592
+ 3. Audit logging - who accessed what PHI, when
593
+ 4. Encryption - at rest and in transit
594
+ 5. BAA requirements - list of vendors needing Business Associate Agreements
595
+ 6. Incident response - breach notification within 60 days
596
+
597
+ Add warning: "This checklist requires review by a HIPAA compliance officer or healthcare attorney"`,
598
+ condition: (p) => p.industry.includes('healthcare') && p.collectsUserData,
599
+ },
600
+ FERPA_COMPLIANCE: {
601
+ key: 'FERPA_COMPLIANCE',
602
+ category: 'Education',
603
+ tier: 'ai_assisted',
604
+ headline: 'Education records require FERPA compliance',
605
+ explanation: 'Student education records in US schools are protected by FERPA. Violations can result in loss of federal funding.',
606
+ priority: 'critical',
607
+ alsoNeeded: ['School admin approval', 'Parent consent process', 'Annual notification'],
608
+ promptTemplate: `Read docs/brainlift.md and docs/prd.md. This is an education application that may need FERPA compliance.
609
+
610
+ Create docs/ferpa-checklist.md covering:
611
+ 1. Education records inventory - what student records do we access/store?
612
+ 2. Consent requirements - when do we need parent/student consent?
613
+ 3. Directory information policy - what can be disclosed without consent?
614
+ 4. Access controls - only authorized school officials
615
+ 5. Record keeping - maintain log of disclosures
616
+ 6. Annual notification - how schools notify parents/students
617
+
618
+ Add warning: "This checklist requires review by school legal counsel"`,
619
+ condition: (p) => p.industry.includes('education') && p.collectsUserData,
620
+ },
621
+ EU_AI_ACT: {
622
+ key: 'EU_AI_ACT',
623
+ category: 'AI Regulation',
624
+ tier: 'ai_assisted',
625
+ headline: 'EU AI Act may apply to your AI system',
626
+ explanation: 'The EU AI Act regulates AI systems by risk level. High-risk AI (health, education, employment) has strict requirements.',
627
+ priority: 'high',
628
+ alsoNeeded: ['Risk classification assessment', 'Technical documentation', 'EU representative if non-EU company'],
629
+ promptTemplate: `Read docs/brainlift.md and docs/prd.md. This AI system may be subject to the EU AI Act.
630
+
631
+ Create docs/eu-ai-act-assessment.md covering:
632
+ 1. AI use case classification - what does the AI decide or recommend?
633
+ 2. Risk level assessment:
634
+ - Unacceptable (banned): social scoring, subliminal manipulation
635
+ - High-risk: education, employment, credit, healthcare, law enforcement
636
+ - Limited risk: chatbots (requires transparency)
637
+ - Minimal risk: spam filters, games
638
+ 3. If high-risk, document:
639
+ - Data governance requirements
640
+ - Technical documentation
641
+ - Record keeping
642
+ - Human oversight mechanisms
643
+ - Accuracy, robustness, cybersecurity
644
+
645
+ Add warning: "This requires legal review for final classification"`,
646
+ condition: (p) => p.usesAI && (p.targetsEU || p.industry.some(i => ['healthcare', 'education', 'finance'].includes(i))),
647
+ },
648
+ SBOM: {
649
+ key: 'SBOM',
650
+ category: 'Supply Chain',
651
+ tier: 'ai_assisted',
652
+ headline: 'Generate a Software Bill of Materials (SBOM)',
653
+ explanation: 'An SBOM lists all dependencies in your software. Required by US Executive Order 14028 for government contractors, increasingly expected by enterprise customers.',
654
+ priority: 'medium',
655
+ promptTemplate: `Generate a Software Bill of Materials (SBOM) for this project.
656
+
657
+ Run these commands to generate the SBOM:
658
+ 1. For npm: npx @cyclonedx/cyclonedx-npm --output-file sbom.json
659
+ 2. Or use: npm sbom --sbom-format cyclonedx
660
+
661
+ Then create docs/sbom-readme.md explaining:
662
+ - What the SBOM contains
663
+ - How to regenerate it
664
+ - When to update it (after dependency changes)
665
+ - License summary of all dependencies
666
+
667
+ Add the sbom.json generation to CI/CD pipeline.`,
668
+ condition: (p) => p.targetAudience.includes('enterprise') || p.industry.includes('finance'),
669
+ },
670
+ DATA_RESIDENCY: {
671
+ key: 'DATA_RESIDENCY',
672
+ category: 'Compliance',
673
+ tier: 'ai_assisted',
674
+ headline: 'Document data residency requirements',
675
+ explanation: 'If you store data in specific regions, you need to document where data lives. GDPR, data sovereignty laws, and enterprise contracts often require this.',
676
+ priority: 'high',
677
+ alsoNeeded: ['Legal review of data transfer agreements', 'Cloud provider region verification'],
678
+ promptTemplate: `Read docs/brainlift.md and docs/prd.md. Create a data residency documentation.
679
+
680
+ Create docs/data-residency.md covering:
681
+ 1. Where is user data stored? (AWS region, GCP zone, etc.)
682
+ 2. Does data cross borders? (US ↔ EU, etc.)
683
+ 3. What legal basis for cross-border transfers? (SCCs, adequacy decisions)
684
+ 4. Can customers choose data region? (for enterprise)
685
+ 5. Where are backups stored?
686
+ 6. Third-party services and their data locations (Stripe, analytics, etc.)
687
+
688
+ Add warning: "Verify regions with your cloud provider dashboard"`,
689
+ condition: (p) => p.targetsEU || p.targetAudience.includes('enterprise'),
690
+ },
691
+ };
692
+ // ============================================================================
693
+ // INFERENCE FUNCTIONS
694
+ // ============================================================================
695
+ /**
696
+ * Infer project profile from brainlift, PRD, and package.json
697
+ */
698
+ export function inferProjectProfile(projectPath) {
699
+ const safePath = sanitizePath(projectPath);
700
+ const profile = {
701
+ collectsUserData: false,
702
+ collectsSensitiveData: false,
703
+ hasUnder13Users: false,
704
+ targetsEU: false,
705
+ targetsCalifornia: false,
706
+ hasPayments: false,
707
+ hasSubscriptions: false,
708
+ hasUserContent: false,
709
+ usesAI: false,
710
+ aiMakesDecisions: false,
711
+ isOpenSource: false,
712
+ targetAudience: [],
713
+ businessModel: 'free',
714
+ industry: [],
715
+ };
716
+ // Discover and read all planning docs (intelligent detection)
717
+ const docsResult = discoverDocsSync(safePath);
718
+ let content = getPlanningContext(docsResult).toLowerCase();
719
+ // Also check README if not already included
720
+ const readmePath = join(safePath, 'README.md');
721
+ const packagePath = join(safePath, 'package.json');
722
+ if (existsSync(readmePath) && !docsResult.readme) {
723
+ content += readFileSync(readmePath, 'utf-8').toLowerCase() + '\n';
724
+ }
725
+ // Package.json analysis
726
+ if (existsSync(packagePath)) {
727
+ try {
728
+ const pkg = JSON.parse(readFileSync(packagePath, 'utf-8'));
729
+ const deps = Object.keys(pkg.dependencies || {}).join(' ').toLowerCase();
730
+ const allContent = JSON.stringify(pkg).toLowerCase();
731
+ // Check for payment libraries
732
+ if (deps.includes('stripe') || deps.includes('paypal') || deps.includes('paddle')) {
733
+ profile.hasPayments = true;
734
+ }
735
+ // Check for AI libraries
736
+ if (deps.includes('openai') || deps.includes('anthropic') || deps.includes('langchain') || deps.includes('ai')) {
737
+ profile.usesAI = true;
738
+ }
739
+ // Check for auth (implies user data)
740
+ if (deps.includes('next-auth') || deps.includes('passport') || deps.includes('clerk') || deps.includes('auth0') || deps.includes('firebase')) {
741
+ profile.collectsUserData = true;
742
+ }
743
+ // Open source check
744
+ if (pkg.license && pkg.license !== 'UNLICENSED' && pkg.license !== 'proprietary') {
745
+ profile.isOpenSource = true;
746
+ }
747
+ content += allContent + '\n';
748
+ }
749
+ catch { /* ignore parse errors */ }
750
+ }
751
+ // CONSERVATIVE keyword analysis
752
+ // Only trigger on explicit mentions, NOT on broad terms like "global" or "international"
753
+ // AI catch-all will handle nuanced cases
754
+ const keywords = {
755
+ // User data - conservative: must be clear user accounts
756
+ collectsUserData: ['user account', 'login', 'signup', 'sign up', 'register', 'authentication', 'auth'],
757
+ collectsSensitiveData: ['health', 'medical', 'hipaa', 'financial', 'bank account', 'credit card', 'ssn', 'social security', 'biometric', 'fingerprint', 'face id'],
758
+ hasUnder13Users: ['kids', 'children', 'child', 'k-12', 'elementary', 'middle school', 'under 13', 'coppa', 'parental consent'],
759
+ // Geography - VERY conservative: only explicit mentions, not "global"
760
+ targetsEU: ['eu', 'europe', 'european union', 'gdpr', 'germany', 'france', 'spain', 'italy', 'netherlands', 'uk users'],
761
+ targetsCalifornia: ['california', 'ccpa', 'california users'],
762
+ // Business - clear signals only
763
+ hasPayments: ['payment', 'stripe', 'paypal', 'billing', 'checkout', 'purchase', 'monetize', 'pricing page'],
764
+ hasSubscriptions: ['subscription', 'monthly plan', 'yearly plan', 'recurring billing', 'saas'],
765
+ hasUserContent: ['upload', 'user generated', 'ugc', 'user posts', 'comments section', 'community content'],
766
+ // AI - detect AI usage with common patterns
767
+ usesAI: ['ai-powered', 'artificial intelligence', 'machine learning', 'gpt', 'llm', 'claude', 'openai', 'langchain', 'uses ai', 'ai features', 'ai model', 'neural network', 'deep learning', 'genai', 'generative ai'],
768
+ aiMakesDecisions: ['ai decides', 'ai recommends', 'automated decision', 'algorithm determines', 'ai-driven', 'ai generates', 'ai creates'],
769
+ };
770
+ for (const [key, terms] of Object.entries(keywords)) {
771
+ if (terms.some(term => content.includes(term))) {
772
+ profile[key] = true;
773
+ }
774
+ }
775
+ // Target audience inference
776
+ if (content.includes('student') || content.includes('education') || content.includes('learn')) {
777
+ profile.targetAudience.push('students');
778
+ }
779
+ if (content.includes('enterprise') || content.includes('b2b') || content.includes('business') || content.includes('team')) {
780
+ profile.targetAudience.push('enterprise');
781
+ }
782
+ if (content.includes('developer') || content.includes('api') || content.includes('sdk')) {
783
+ profile.targetAudience.push('developers');
784
+ }
785
+ if (content.includes('mobile') || content.includes('ios') || content.includes('android') || content.includes('app store')) {
786
+ profile.targetAudience.push('mobile');
787
+ }
788
+ // Business model inference
789
+ if (content.includes('free') && !content.includes('freemium') && !content.includes('premium')) {
790
+ profile.businessModel = 'free';
791
+ }
792
+ else if (content.includes('freemium') || (content.includes('free') && content.includes('premium'))) {
793
+ profile.businessModel = 'freemium';
794
+ }
795
+ else if (content.includes('subscription') || content.includes('saas')) {
796
+ profile.businessModel = 'subscription';
797
+ }
798
+ else if (content.includes('enterprise') || content.includes('b2b')) {
799
+ profile.businessModel = 'b2b';
800
+ }
801
+ else if (profile.hasPayments) {
802
+ profile.businessModel = 'paid';
803
+ }
804
+ // Industry inference
805
+ if (content.includes('health') || content.includes('medical') || content.includes('patient')) {
806
+ profile.industry.push('healthcare');
807
+ }
808
+ if (content.includes('finance') || content.includes('banking') || content.includes('invest')) {
809
+ profile.industry.push('finance');
810
+ }
811
+ if (content.includes('education') || content.includes('school') || content.includes('course')) {
812
+ profile.industry.push('education');
813
+ }
814
+ return profile;
815
+ }
816
+ /**
817
+ * Generate context-aware prompt by filling in template variables
818
+ */
819
+ function fillPromptTemplate(template, profile, projectPath) {
820
+ const safePath = sanitizePath(projectPath);
821
+ // Use intelligent docs discovery
822
+ const docsResult = discoverDocsSync(safePath);
823
+ const brainliftContent = docsResult.brainlift?.content || '';
824
+ const prdContent = docsResult.prd?.content || '';
825
+ // Build replacements
826
+ const replacements = {
827
+ '{{dataCollected}}': profile.collectsSensitiveData
828
+ ? 'user accounts, emails, and sensitive data (health/financial)'
829
+ : profile.collectsUserData
830
+ ? 'user accounts, emails, preferences'
831
+ : 'minimal data',
832
+ '{{targetUsers}}': profile.targetAudience.length > 0
833
+ ? profile.targetAudience.join(', ')
834
+ : 'general users',
835
+ '{{businessModel}}': profile.businessModel,
836
+ '{{productType}}': profile.usesAI ? 'AI-powered application' : 'web application',
837
+ '{{keyFeatures}}': brainliftContent.slice(0, 200) || 'See brainlift for details',
838
+ '{{aiUsage}}': profile.usesAI ? 'AI features (see brainlift for specifics)' : 'No AI',
839
+ '{{processingPurposes}}': 'account management, service delivery, analytics',
840
+ '{{thirdParties}}': profile.hasPayments ? 'Stripe for payments, analytics provider' : 'analytics provider',
841
+ '{{subscriptionType}}': profile.hasSubscriptions ? 'recurring subscription' : 'one-time purchase',
842
+ '{{userContentType}}': 'create and share content',
843
+ '{{decisionsAffected}}': 'recommendations, personalization',
844
+ '{{pricing}}': 'See PRD for pricing details',
845
+ '{{checkoutType}}': profile.hasSubscriptions ? 'subscription' : 'one-time payment',
846
+ };
847
+ let result = template;
848
+ for (const [key, value] of Object.entries(replacements)) {
849
+ result = result.replace(new RegExp(key.replace(/[{}]/g, '\\$&'), 'g'), value);
850
+ }
851
+ return result;
852
+ }
853
+ /**
854
+ * Get all applicable preflight checks for a project
855
+ */
856
+ export function getPreflightChecks(projectPath) {
857
+ const safePath = sanitizePath(projectPath);
858
+ // Auto-detect generated docs and mark checks complete (feedback loop)
859
+ detectGeneratedDocs(safePath);
860
+ const profile = inferProjectProfile(safePath);
861
+ const checks = [];
862
+ // Load persisted state (after detection so it includes auto-completions)
863
+ const persistedState = loadPreflightState(safePath);
864
+ const checkStates = persistedState.checkStates;
865
+ for (const check of Object.values(PREFLIGHT_CHECKS)) {
866
+ if (check.condition(profile)) {
867
+ const cursorPrompt = fillPromptTemplate(check.promptTemplate, profile, safePath);
868
+ const persisted = checkStates[check.key];
869
+ // Get triggered-by reason
870
+ const triggeredBy = check.getTriggeredBy
871
+ ? check.getTriggeredBy(profile)
872
+ : DEFAULT_TRIGGERS[check.key]?.(profile) || 'Inferred from project profile';
873
+ checks.push({
874
+ key: check.key,
875
+ category: check.category,
876
+ tier: check.tier,
877
+ headline: check.headline,
878
+ explanation: check.explanation,
879
+ cursorPrompt,
880
+ humanSteps: check.humanSteps,
881
+ externalLinks: check.externalLinks,
882
+ alsoNeeded: check.alsoNeeded,
883
+ priority: check.priority,
884
+ triggeredBy,
885
+ // Add persisted status
886
+ status: persisted?.status || 'pending',
887
+ statusUpdatedAt: persisted?.updatedAt,
888
+ skippedReason: persisted?.skippedReason,
889
+ });
890
+ }
891
+ }
892
+ // Sort by priority and tier
893
+ const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
894
+ const tierOrder = { manual: 0, ai_assisted: 1 };
895
+ checks.sort((a, b) => {
896
+ const priorityDiff = priorityOrder[a.priority] - priorityOrder[b.priority];
897
+ if (priorityDiff !== 0)
898
+ return priorityDiff;
899
+ return tierOrder[a.tier] - tierOrder[b.tier];
900
+ });
901
+ // Progressive disclosure: first 2 views show only critical + 2 more
902
+ const totalAvailable = checks.length;
903
+ const viewCount = persistedState.viewCount || 0;
904
+ const isFirstSession = viewCount < 2;
905
+ // Increment view count
906
+ const newState = { ...persistedState, viewCount: viewCount + 1 };
907
+ savePreflightState(safePath, newState);
908
+ // On first sessions, limit to critical + 2 non-critical
909
+ let displayChecks = checks;
910
+ if (isFirstSession && checks.length > 4) {
911
+ const critical = checks.filter(c => c.priority === 'critical');
912
+ const nonCritical = checks.filter(c => c.priority !== 'critical').slice(0, 2);
913
+ displayChecks = [...critical, ...nonCritical];
914
+ }
915
+ return {
916
+ profile,
917
+ checks: displayChecks,
918
+ summary: {
919
+ total: displayChecks.length,
920
+ critical: displayChecks.filter(c => c.priority === 'critical').length,
921
+ aiAssisted: displayChecks.filter(c => c.tier === 'ai_assisted').length,
922
+ manual: displayChecks.filter(c => c.tier === 'manual').length,
923
+ pending: displayChecks.filter(c => c.status === 'pending').length,
924
+ completed: displayChecks.filter(c => c.status === 'completed').length,
925
+ skipped: displayChecks.filter(c => c.status === 'skipped').length,
926
+ },
927
+ totalAvailable,
928
+ isFirstSession,
929
+ };
930
+ }
931
+ // ============================================================================
932
+ // AI CATCH-ALL FILTER
933
+ // ============================================================================
934
+ /**
935
+ * AI-powered preflight check filter
936
+ *
937
+ * Conservative defaults may miss edge cases. This AI pass:
938
+ * 1. Reviews the full project context
939
+ * 2. Filters out irrelevant checks (e.g., GDPR for US-only app)
940
+ * 3. Adds missing checks based on nuanced understanding
941
+ *
942
+ * Called only when API key is available, falls back to keyword-based otherwise.
943
+ */
944
+ export async function filterChecksWithAI(profile, checks, projectPath) {
945
+ // Dynamic import to avoid circular dependency
946
+ const { getApiKey } = await import('./config.js');
947
+ const { chat } = await import('./providers.js');
948
+ const apiKey = getApiKey();
949
+ if (!apiKey) {
950
+ // No API key - return checks as-is
951
+ return { filtered: checks, additions: [], removals: [] };
952
+ }
953
+ const safePath = sanitizePath(projectPath);
954
+ // Use intelligent docs discovery
955
+ const docsResult = discoverDocsSync(safePath);
956
+ const docsContent = getPlanningContext(docsResult);
957
+ if (!docsContent || docsResult.totalDocsFound === 0) {
958
+ // No docs to analyze - return checks as-is
959
+ return { filtered: checks, additions: [], removals: [] };
960
+ }
961
+ // Combined profile validation + check filtering in a single focused prompt
962
+ const systemPrompt = `Compliance check filter. Given project docs and proposed checks, return JSON only:
963
+ {"keep":["KEY",...], "remove":["KEY",...], "add":["KEY",...]}
964
+ Rules: Remove checks that clearly don't apply. Add missing checks from available list. Be conservative.`;
965
+ // Compact prompt - less tokens, faster response
966
+ const proposed = checks.map(c => c.key).join(',');
967
+ const available = Object.keys(PREFLIGHT_CHECKS).filter(k => !checks.some(c => c.key === k)).join(',');
968
+ const prompt = `Docs:\n${docsContent}\n\nProposed: ${proposed}\nAvailable: ${available}\n\nProfile: ${profile.businessModel}, EU:${profile.targetsEU}, AI:${profile.usesAI}, payments:${profile.hasPayments}`;
969
+ try {
970
+ const response = await chat(prompt, {
971
+ systemPrompt,
972
+ maxTokens: 1000,
973
+ useThinking: false, // Fast response
974
+ timeout: 15000, // Quick timeout
975
+ });
976
+ // Parse response
977
+ let jsonStr = response.content;
978
+ if (response.content.includes('```')) {
979
+ const match = response.content.match(/```(?:json)?\s*([\s\S]*?)```/);
980
+ if (match)
981
+ jsonStr = match[1];
982
+ }
983
+ const result = JSON.parse(jsonStr.trim());
984
+ // Filter checks based on AI response
985
+ const keepSet = new Set(result.keep || []);
986
+ const removeSet = new Set(result.remove || []);
987
+ const addKeys = result.add || [];
988
+ // Apply filtering
989
+ let filtered = checks.filter(c => {
990
+ // If explicitly removed, remove it
991
+ if (removeSet.has(c.key))
992
+ return false;
993
+ // If explicitly kept or not mentioned, keep it (conservative)
994
+ return true;
995
+ });
996
+ // Add any checks AI says we're missing
997
+ const safePath = sanitizePath(projectPath);
998
+ const persistedState = loadPreflightState(safePath);
999
+ for (const key of addKeys) {
1000
+ if (PREFLIGHT_CHECKS[key] && !filtered.some(c => c.key === key)) {
1001
+ const check = PREFLIGHT_CHECKS[key];
1002
+ const cursorPrompt = fillPromptTemplate(check.promptTemplate, profile, projectPath);
1003
+ const persisted = persistedState.checkStates[key];
1004
+ // Get triggered-by reason (AI-added checks)
1005
+ const triggeredBy = check.getTriggeredBy
1006
+ ? check.getTriggeredBy(profile)
1007
+ : 'Added by AI analysis of project context';
1008
+ filtered.push({
1009
+ key: check.key,
1010
+ category: check.category,
1011
+ tier: check.tier,
1012
+ headline: check.headline,
1013
+ explanation: check.explanation,
1014
+ cursorPrompt,
1015
+ humanSteps: check.humanSteps,
1016
+ externalLinks: check.externalLinks,
1017
+ alsoNeeded: check.alsoNeeded,
1018
+ priority: check.priority,
1019
+ triggeredBy,
1020
+ status: persisted?.status || 'pending',
1021
+ statusUpdatedAt: persisted?.updatedAt,
1022
+ skippedReason: persisted?.skippedReason,
1023
+ });
1024
+ }
1025
+ }
1026
+ // Re-sort
1027
+ const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
1028
+ const tierOrder = { manual: 0, ai_assisted: 1 };
1029
+ filtered.sort((a, b) => {
1030
+ const priorityDiff = priorityOrder[a.priority] - priorityOrder[b.priority];
1031
+ if (priorityDiff !== 0)
1032
+ return priorityDiff;
1033
+ return tierOrder[a.tier] - tierOrder[b.tier];
1034
+ });
1035
+ return {
1036
+ filtered,
1037
+ additions: addKeys,
1038
+ removals: Array.from(removeSet),
1039
+ };
1040
+ }
1041
+ catch (error) {
1042
+ // AI failed - return original checks (conservative fallback)
1043
+ return { filtered: checks, additions: [], removals: [] };
1044
+ }
1045
+ }
1046
+ /**
1047
+ * Get preflight checks with AI filtering (async version)
1048
+ * Use this when you want the most accurate checks
1049
+ */
1050
+ export async function getPreflightChecksWithAI(projectPath) {
1051
+ const basic = getPreflightChecks(projectPath);
1052
+ try {
1053
+ const { filtered, additions, removals } = await filterChecksWithAI(basic.profile, basic.checks, projectPath);
1054
+ return {
1055
+ profile: basic.profile,
1056
+ checks: filtered,
1057
+ summary: {
1058
+ total: filtered.length,
1059
+ critical: filtered.filter(c => c.priority === 'critical').length,
1060
+ aiAssisted: filtered.filter(c => c.tier === 'ai_assisted').length,
1061
+ manual: filtered.filter(c => c.tier === 'manual').length,
1062
+ pending: filtered.filter(c => c.status === 'pending').length,
1063
+ completed: filtered.filter(c => c.status === 'completed').length,
1064
+ skipped: filtered.filter(c => c.status === 'skipped').length,
1065
+ },
1066
+ aiFiltered: additions.length > 0 || removals.length > 0,
1067
+ };
1068
+ }
1069
+ catch {
1070
+ // Fallback to basic checks
1071
+ return { ...basic, aiFiltered: false };
1072
+ }
1073
+ }
1074
+ /**
1075
+ * Get tier symbol for display
1076
+ */
1077
+ export function getTierSymbol(tier) {
1078
+ const mapped = TIER_MAPPING[tier] || tier;
1079
+ switch (mapped) {
1080
+ case 'ai_assisted': return '🤖';
1081
+ case 'manual': return '👤';
1082
+ default: return '❓';
1083
+ }
1084
+ }
1085
+ /**
1086
+ * Get tier description
1087
+ */
1088
+ export function getTierDescription(tier) {
1089
+ const mapped = TIER_MAPPING[tier] || tier;
1090
+ switch (mapped) {
1091
+ case 'ai_assisted': return 'AI can help with this';
1092
+ case 'manual': return 'You need to do this yourself';
1093
+ default: return 'Unknown tier';
1094
+ }
1095
+ }
1096
+ // Function aliases for backward compatibility
1097
+ export const getRealityChecks = getPreflightChecks;
1098
+ export const getRealityChecksWithAI = getPreflightChecksWithAI;
1099
+ export const loadRealityState = loadPreflightState;
1100
+ //# sourceMappingURL=preflight.js.map