midas-mcp 5.4.0 → 5.15.0

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/dist/reality.js CHANGED
@@ -5,56 +5,204 @@
5
5
  * categorizes them by what AI can/cannot do,
6
6
  * and generates context-aware prompts for Cursor.
7
7
  */
8
- import { existsSync, readFileSync } from 'fs';
8
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
9
9
  import { join } from 'path';
10
10
  import { sanitizePath } from './security.js';
11
11
  // ============================================================================
12
- // REALITY CHECK DEFINITIONS
12
+ // PERSISTENCE
13
13
  // ============================================================================
14
+ const MIDAS_DIR = '.midas';
15
+ const REALITY_STATE_FILE = 'reality-checks.json';
16
+ function getRealityStatePath(projectPath) {
17
+ return join(projectPath, MIDAS_DIR, REALITY_STATE_FILE);
18
+ }
19
+ function loadRealityState(projectPath) {
20
+ const path = getRealityStatePath(projectPath);
21
+ if (existsSync(path)) {
22
+ try {
23
+ return JSON.parse(readFileSync(path, 'utf-8'));
24
+ }
25
+ catch {
26
+ return { checkStates: {}, lastProfileHash: '' };
27
+ }
28
+ }
29
+ return { checkStates: {}, lastProfileHash: '' };
30
+ }
31
+ function saveRealityState(projectPath, state) {
32
+ const dir = join(projectPath, MIDAS_DIR);
33
+ if (!existsSync(dir)) {
34
+ mkdirSync(dir, { recursive: true });
35
+ }
36
+ writeFileSync(getRealityStatePath(projectPath), JSON.stringify(state, null, 2));
37
+ }
38
+ function hashProfile(profile) {
39
+ // Simple hash to detect profile changes
40
+ return JSON.stringify(profile).split('').reduce((a, b) => {
41
+ a = ((a << 5) - a) + b.charCodeAt(0);
42
+ return a & a;
43
+ }, 0).toString(36);
44
+ }
45
+ /**
46
+ * Update the status of a reality check
47
+ */
48
+ export function updateCheckStatus(projectPath, checkKey, status, skippedReason) {
49
+ const safePath = sanitizePath(projectPath);
50
+ const state = loadRealityState(safePath);
51
+ state.checkStates[checkKey] = {
52
+ status,
53
+ updatedAt: new Date().toISOString(),
54
+ ...(skippedReason ? { skippedReason } : {}),
55
+ };
56
+ saveRealityState(safePath, state);
57
+ }
58
+ /**
59
+ * Get the persisted status for a check
60
+ */
61
+ export function getCheckStatus(projectPath, checkKey) {
62
+ const safePath = sanitizePath(projectPath);
63
+ const state = loadRealityState(safePath);
64
+ return state.checkStates[checkKey];
65
+ }
66
+ /**
67
+ * Get all check statuses
68
+ */
69
+ export function getAllCheckStatuses(projectPath) {
70
+ const safePath = sanitizePath(projectPath);
71
+ const state = loadRealityState(safePath);
72
+ return state.checkStates;
73
+ }
74
+ /**
75
+ * Reset all check statuses (e.g., when profile changes significantly)
76
+ */
77
+ export function resetCheckStatuses(projectPath) {
78
+ const safePath = sanitizePath(projectPath);
79
+ saveRealityState(safePath, { checkStates: {}, lastProfileHash: '' });
80
+ }
81
+ /**
82
+ * Map of check keys to the expected generated file paths
83
+ */
84
+ const EXPECTED_OUTPUTS = {
85
+ PRIVACY_POLICY: ['docs/privacy-policy.md', 'privacy-policy.md', 'PRIVACY.md'],
86
+ TERMS_OF_SERVICE: ['docs/terms-of-service.md', 'docs/terms.md', 'TERMS.md'],
87
+ COOKIE_POLICY: ['docs/cookie-policy.md'],
88
+ GDPR_COMPLIANCE: ['docs/gdpr-checklist.md', 'docs/gdpr.md'],
89
+ CCPA_COMPLIANCE: ['docs/ccpa-checklist.md'],
90
+ AI_DISCLOSURE: ['docs/ai-disclosure.md', 'AI_DISCLOSURE.md'],
91
+ ACCESSIBILITY: ['docs/accessibility.md', 'docs/a11y.md', 'ACCESSIBILITY.md'],
92
+ DATA_RETENTION: ['docs/data-retention.md'],
93
+ INCIDENT_RESPONSE: ['docs/incident-response.md'],
94
+ OSS_LICENSE: ['LICENSE', 'LICENSE.md', 'docs/license.md'],
95
+ HIPAA_COMPLIANCE: ['docs/hipaa-checklist.md'],
96
+ FERPA_COMPLIANCE: ['docs/ferpa-checklist.md'],
97
+ EU_AI_ACT: ['docs/eu-ai-act-assessment.md'],
98
+ SBOM: ['sbom.json', 'docs/sbom-readme.md'],
99
+ DATA_RESIDENCY: ['docs/data-residency.md'],
100
+ };
101
+ /**
102
+ * Detect if any expected output files exist and auto-complete checks
103
+ * Returns array of check keys that were auto-completed
104
+ */
105
+ export function detectGeneratedDocs(projectPath) {
106
+ const safePath = sanitizePath(projectPath);
107
+ const state = loadRealityState(safePath);
108
+ const autoCompleted = [];
109
+ for (const [checkKey, possibleFiles] of Object.entries(EXPECTED_OUTPUTS)) {
110
+ // Skip if already completed
111
+ if (state.checkStates[checkKey]?.status === 'completed')
112
+ continue;
113
+ // Check if any of the expected files exist
114
+ const fileExists = possibleFiles.some(file => existsSync(join(safePath, file)));
115
+ if (fileExists) {
116
+ // Auto-complete this check
117
+ state.checkStates[checkKey] = {
118
+ status: 'completed',
119
+ updatedAt: new Date().toISOString(),
120
+ };
121
+ autoCompleted.push(checkKey);
122
+ }
123
+ }
124
+ if (autoCompleted.length > 0) {
125
+ saveRealityState(safePath, state);
126
+ }
127
+ return autoCompleted;
128
+ }
129
+ // Legacy tier mapping for backward compatibility
130
+ const TIER_MAPPING = {
131
+ 'generatable': 'ai_assisted',
132
+ 'assistable': 'ai_assisted',
133
+ 'human_only': 'manual',
134
+ 'ai_assisted': 'ai_assisted',
135
+ 'manual': 'manual',
136
+ };
137
+ // Default triggered-by generators based on common profile fields
138
+ const DEFAULT_TRIGGERS = {
139
+ PRIVACY_POLICY: (p) => p.collectsSensitiveData
140
+ ? 'Project collects sensitive data (health/financial/biometric)'
141
+ : 'Project collects user data',
142
+ TERMS_OF_SERVICE: () => 'Public-facing product',
143
+ COOKIE_POLICY: (p) => p.targetsEU ? 'Targets EU users' : 'Collects user data',
144
+ GDPR_COMPLIANCE: (p) => p.targetsEU ? 'Explicitly targets EU users' : 'May have EU users',
145
+ CCPA_COMPLIANCE: () => 'Targets California or US users',
146
+ COPPA_COMPLIANCE: () => 'May have users under 13',
147
+ AI_DISCLOSURE: () => 'Uses AI for decisions or recommendations',
148
+ PAYMENT_SETUP: () => 'Has payment/subscription features',
149
+ STRIPE_INTEGRATION: () => 'Has payment processing',
150
+ APP_STORE: () => 'Distributes via iOS App Store',
151
+ PLAY_STORE: () => 'Distributes via Google Play Store',
152
+ ACCESSIBILITY: () => 'Public-facing product should be accessible',
153
+ DATA_RETENTION: (p) => p.collectsSensitiveData ? 'Handles sensitive data' : 'Collects user data',
154
+ INCIDENT_RESPONSE: () => 'Production system needs incident handling',
155
+ OSS_LICENSE: () => 'Open source project needs license',
156
+ HIPAA_COMPLIANCE: () => 'Healthcare industry + collects user data',
157
+ FERPA_COMPLIANCE: () => 'Education industry + collects student data',
158
+ EU_AI_ACT: (p) => p.targetsEU
159
+ ? 'AI system targeting EU users'
160
+ : 'AI system in regulated industry (healthcare/education/finance)',
161
+ SBOM: () => 'Enterprise/finance audience expects supply chain transparency',
162
+ DATA_RESIDENCY: (p) => p.targetsEU
163
+ ? 'EU users require data residency documentation (GDPR)'
164
+ : 'Enterprise customers require data location clarity',
165
+ };
14
166
  const REALITY_CHECKS = {
15
167
  // ✅ GENERATABLE - AI can draft these
16
168
  PRIVACY_POLICY: {
17
169
  key: 'PRIVACY_POLICY',
18
170
  category: 'Legal',
19
- tier: 'generatable',
171
+ tier: 'ai_assisted',
20
172
  headline: 'You need a Privacy Policy',
21
173
  explanation: 'You collect user data. Users need to know what you collect, why, and how to delete it.',
22
174
  priority: 'critical',
23
- promptTemplate: `Create a privacy policy for this project based on the brainlift and PRD.
175
+ promptTemplate: `Read docs/brainlift.md and docs/prd.md to understand this project. Then create a privacy policy.
24
176
 
25
- We collect: {{dataCollected}}
26
- Target users: {{targetUsers}}
27
- Business model: {{businessModel}}
177
+ First, identify from the docs:
178
+ - What user data is collected
179
+ - Why it's collected
180
+ - Who the target users are
181
+ - The business model
28
182
 
29
- Include sections:
183
+ Then create docs/privacy-policy.md with sections:
30
184
  - What we collect and why
31
185
  - How we use the data
32
- - Third parties we share with (if any)
33
- - How long we keep data
186
+ - Third parties we share with
187
+ - Data retention
34
188
  - User rights (access, correct, delete)
35
- - How to contact us
36
-
37
- Save to docs/privacy-policy.md
189
+ - Contact information
38
190
 
39
- Add at the top: "DRAFT - Review with a lawyer before publishing"`,
191
+ Add at top: "DRAFT - Review with a lawyer before publishing"`,
40
192
  condition: (p) => p.collectsUserData,
41
193
  },
42
194
  TERMS_OF_SERVICE: {
43
195
  key: 'TERMS_OF_SERVICE',
44
196
  category: 'Legal',
45
- tier: 'generatable',
197
+ tier: 'ai_assisted',
46
198
  headline: 'You need Terms of Service',
47
199
  explanation: 'Any public product needs terms defining the rules of use and liability limits.',
48
200
  priority: 'high',
49
- promptTemplate: `Create terms of service for this project based on the brainlift and PRD.
50
-
51
- Product type: {{productType}}
52
- Business model: {{businessModel}}
53
- Key features: {{keyFeatures}}
201
+ promptTemplate: `Read docs/brainlift.md and docs/prd.md to understand this project. Then create terms of service.
54
202
 
55
- Include sections:
203
+ Create docs/terms-of-service.md with sections:
56
204
  - Acceptance of terms
57
- - Description of service
205
+ - Description of service (from what you read)
58
206
  - User responsibilities
59
207
  - Prohibited uses
60
208
  - Intellectual property
@@ -70,7 +218,7 @@ Add at the top: "DRAFT - Review with a lawyer before publishing"`,
70
218
  AI_DISCLOSURE: {
71
219
  key: 'AI_DISCLOSURE',
72
220
  category: 'Transparency',
73
- tier: 'generatable',
221
+ tier: 'ai_assisted',
74
222
  headline: 'You need an AI disclosure',
75
223
  explanation: 'Users should know when AI is involved and that it can make mistakes.',
76
224
  priority: 'high',
@@ -92,7 +240,7 @@ Also add a brief inline disclosure component/text that can be shown in the UI wh
92
240
  REFUND_POLICY: {
93
241
  key: 'REFUND_POLICY',
94
242
  category: 'Business',
95
- tier: 'generatable',
243
+ tier: 'ai_assisted',
96
244
  headline: 'You need a refund policy',
97
245
  explanation: 'Paid products need clear refund terms to avoid disputes and chargebacks.',
98
246
  priority: 'high',
@@ -116,7 +264,7 @@ Save to docs/refund-policy.md`,
116
264
  CONTENT_POLICY: {
117
265
  key: 'CONTENT_POLICY',
118
266
  category: 'Trust',
119
- tier: 'generatable',
267
+ tier: 'ai_assisted',
120
268
  headline: 'You need a content policy',
121
269
  explanation: 'User-generated content needs rules about what\'s allowed and how violations are handled.',
122
270
  priority: 'high',
@@ -138,7 +286,7 @@ Save to docs/content-policy.md`,
138
286
  GDPR_COMPLIANCE: {
139
287
  key: 'GDPR_COMPLIANCE',
140
288
  category: 'Compliance',
141
- tier: 'assistable',
289
+ tier: 'ai_assisted',
142
290
  headline: 'GDPR applies to your product',
143
291
  explanation: 'You\'re targeting EU users. You need lawful basis for data processing, user consent, and data rights.',
144
292
  priority: 'critical',
@@ -169,7 +317,7 @@ Note at top: "This is a technical implementation guide. Legal review required be
169
317
  CCPA_COMPLIANCE: {
170
318
  key: 'CCPA_COMPLIANCE',
171
319
  category: 'Compliance',
172
- tier: 'assistable',
320
+ tier: 'ai_assisted',
173
321
  headline: 'CCPA applies to your product',
174
322
  explanation: 'California users have rights to know, delete, and opt-out of data sales.',
175
323
  priority: 'high',
@@ -192,7 +340,7 @@ Save to docs/ccpa-checklist.md`,
192
340
  ACCESSIBILITY: {
193
341
  key: 'ACCESSIBILITY',
194
342
  category: 'Inclusion',
195
- tier: 'assistable',
343
+ tier: 'ai_assisted',
196
344
  headline: 'Consider accessibility (WCAG)',
197
345
  explanation: 'Making your product accessible helps more users and may be legally required for some customers.',
198
346
  priority: 'medium',
@@ -214,7 +362,7 @@ Save checklist to docs/accessibility-checklist.md`,
214
362
  BIAS_ASSESSMENT: {
215
363
  key: 'BIAS_ASSESSMENT',
216
364
  category: 'Ethics',
217
- tier: 'assistable',
365
+ tier: 'ai_assisted',
218
366
  headline: 'AI bias assessment needed',
219
367
  explanation: 'AI that makes decisions about people can have unintended bias. Document what you\'ve considered.',
220
368
  priority: 'high',
@@ -241,7 +389,7 @@ This is for internal documentation and transparency.`,
241
389
  PAYMENT_SETUP: {
242
390
  key: 'PAYMENT_SETUP',
243
391
  category: 'Business',
244
- tier: 'human_only',
392
+ tier: 'manual',
245
393
  headline: 'You need payment processing',
246
394
  explanation: 'You want to charge users. You need a payment provider account first.',
247
395
  priority: 'critical',
@@ -271,7 +419,7 @@ I have my Stripe API keys ready. Use STRIPE_SECRET_KEY and STRIPE_PUBLISHABLE_KE
271
419
  BUSINESS_REGISTRATION: {
272
420
  key: 'BUSINESS_REGISTRATION',
273
421
  category: 'Legal',
274
- tier: 'human_only',
422
+ tier: 'manual',
275
423
  headline: 'Consider business registration',
276
424
  explanation: 'If you\'re making money, you may need a business entity for taxes and liability protection.',
277
425
  priority: 'medium',
@@ -293,7 +441,7 @@ Once you have your business set up, update your docs:
293
441
  TAX_SETUP: {
294
442
  key: 'TAX_SETUP',
295
443
  category: 'Business',
296
- tier: 'human_only',
444
+ tier: 'manual',
297
445
  headline: 'You need tax handling',
298
446
  explanation: 'Selling internationally means dealing with VAT, GST, and sales tax. Stripe Tax can help.',
299
447
  priority: 'high',
@@ -317,7 +465,7 @@ See Stripe Tax docs for jurisdiction-specific setup.`,
317
465
  DOMAIN_SSL: {
318
466
  key: 'DOMAIN_SSL',
319
467
  category: 'Infrastructure',
320
- tier: 'human_only',
468
+ tier: 'manual',
321
469
  headline: 'You need a domain and SSL',
322
470
  explanation: 'To launch publicly, you need a domain name and HTTPS.',
323
471
  priority: 'high',
@@ -339,7 +487,7 @@ Once you have your domain, update:
339
487
  APP_STORE: {
340
488
  key: 'APP_STORE',
341
489
  category: 'Distribution',
342
- tier: 'human_only',
490
+ tier: 'manual',
343
491
  headline: 'App Store submission needed',
344
492
  explanation: 'Mobile apps need App Store / Play Store developer accounts and review.',
345
493
  priority: 'critical',
@@ -364,7 +512,7 @@ Check that the app follows platform guidelines before submission.`,
364
512
  COPPA_COMPLIANCE: {
365
513
  key: 'COPPA_COMPLIANCE',
366
514
  category: 'Compliance',
367
- tier: 'human_only',
515
+ tier: 'manual',
368
516
  headline: 'COPPA compliance required',
369
517
  explanation: 'Users under 13 require parental consent and special data handling.',
370
518
  priority: 'critical',
@@ -391,7 +539,7 @@ This requires careful legal review - the FTC enforces COPPA strictly.`,
391
539
  SOC2: {
392
540
  key: 'SOC2',
393
541
  category: 'Certification',
394
- tier: 'human_only',
542
+ tier: 'manual',
395
543
  headline: 'Enterprise customers may require SOC 2',
396
544
  explanation: 'B2B/enterprise sales often require SOC 2 certification to prove security practices.',
397
545
  priority: 'medium',
@@ -414,6 +562,119 @@ However, you can prepare by implementing:
414
562
  Create a security checklist: docs/security-checklist.md`,
415
563
  condition: (p) => p.targetAudience.includes('enterprise'),
416
564
  },
565
+ // ⚠️ ASSISTABLE - Industry-specific regulations
566
+ HIPAA_COMPLIANCE: {
567
+ key: 'HIPAA_COMPLIANCE',
568
+ category: 'Healthcare',
569
+ tier: 'ai_assisted',
570
+ headline: 'Healthcare data requires HIPAA compliance',
571
+ explanation: 'Handling patient health information in the US requires HIPAA compliance. Violations can cost $100-50K per record.',
572
+ priority: 'critical',
573
+ alsoNeeded: ['Legal review of BAA', 'Security audit', 'Employee HIPAA training'],
574
+ promptTemplate: `Read docs/brainlift.md and docs/prd.md. This is a healthcare application that may need HIPAA compliance.
575
+
576
+ Create docs/hipaa-checklist.md covering:
577
+ 1. PHI (Protected Health Information) inventory - what health data do we handle?
578
+ 2. Access controls - minimum necessary access
579
+ 3. Audit logging - who accessed what PHI, when
580
+ 4. Encryption - at rest and in transit
581
+ 5. BAA requirements - list of vendors needing Business Associate Agreements
582
+ 6. Incident response - breach notification within 60 days
583
+
584
+ Add warning: "This checklist requires review by a HIPAA compliance officer or healthcare attorney"`,
585
+ condition: (p) => p.industry.includes('healthcare') && p.collectsUserData,
586
+ },
587
+ FERPA_COMPLIANCE: {
588
+ key: 'FERPA_COMPLIANCE',
589
+ category: 'Education',
590
+ tier: 'ai_assisted',
591
+ headline: 'Education records require FERPA compliance',
592
+ explanation: 'Student education records in US schools are protected by FERPA. Violations can result in loss of federal funding.',
593
+ priority: 'critical',
594
+ alsoNeeded: ['School admin approval', 'Parent consent process', 'Annual notification'],
595
+ promptTemplate: `Read docs/brainlift.md and docs/prd.md. This is an education application that may need FERPA compliance.
596
+
597
+ Create docs/ferpa-checklist.md covering:
598
+ 1. Education records inventory - what student records do we access/store?
599
+ 2. Consent requirements - when do we need parent/student consent?
600
+ 3. Directory information policy - what can be disclosed without consent?
601
+ 4. Access controls - only authorized school officials
602
+ 5. Record keeping - maintain log of disclosures
603
+ 6. Annual notification - how schools notify parents/students
604
+
605
+ Add warning: "This checklist requires review by school legal counsel"`,
606
+ condition: (p) => p.industry.includes('education') && p.collectsUserData,
607
+ },
608
+ EU_AI_ACT: {
609
+ key: 'EU_AI_ACT',
610
+ category: 'AI Regulation',
611
+ tier: 'ai_assisted',
612
+ headline: 'EU AI Act may apply to your AI system',
613
+ explanation: 'The EU AI Act regulates AI systems by risk level. High-risk AI (health, education, employment) has strict requirements.',
614
+ priority: 'high',
615
+ alsoNeeded: ['Risk classification assessment', 'Technical documentation', 'EU representative if non-EU company'],
616
+ promptTemplate: `Read docs/brainlift.md and docs/prd.md. This AI system may be subject to the EU AI Act.
617
+
618
+ Create docs/eu-ai-act-assessment.md covering:
619
+ 1. AI use case classification - what does the AI decide or recommend?
620
+ 2. Risk level assessment:
621
+ - Unacceptable (banned): social scoring, subliminal manipulation
622
+ - High-risk: education, employment, credit, healthcare, law enforcement
623
+ - Limited risk: chatbots (requires transparency)
624
+ - Minimal risk: spam filters, games
625
+ 3. If high-risk, document:
626
+ - Data governance requirements
627
+ - Technical documentation
628
+ - Record keeping
629
+ - Human oversight mechanisms
630
+ - Accuracy, robustness, cybersecurity
631
+
632
+ Add warning: "This requires legal review for final classification"`,
633
+ condition: (p) => p.usesAI && (p.targetsEU || p.industry.some(i => ['healthcare', 'education', 'finance'].includes(i))),
634
+ },
635
+ SBOM: {
636
+ key: 'SBOM',
637
+ category: 'Supply Chain',
638
+ tier: 'ai_assisted',
639
+ headline: 'Generate a Software Bill of Materials (SBOM)',
640
+ explanation: 'An SBOM lists all dependencies in your software. Required by US Executive Order 14028 for government contractors, increasingly expected by enterprise customers.',
641
+ priority: 'medium',
642
+ promptTemplate: `Generate a Software Bill of Materials (SBOM) for this project.
643
+
644
+ Run these commands to generate the SBOM:
645
+ 1. For npm: npx @cyclonedx/cyclonedx-npm --output-file sbom.json
646
+ 2. Or use: npm sbom --sbom-format cyclonedx
647
+
648
+ Then create docs/sbom-readme.md explaining:
649
+ - What the SBOM contains
650
+ - How to regenerate it
651
+ - When to update it (after dependency changes)
652
+ - License summary of all dependencies
653
+
654
+ Add the sbom.json generation to CI/CD pipeline.`,
655
+ condition: (p) => p.targetAudience.includes('enterprise') || p.industry.includes('finance'),
656
+ },
657
+ DATA_RESIDENCY: {
658
+ key: 'DATA_RESIDENCY',
659
+ category: 'Compliance',
660
+ tier: 'ai_assisted',
661
+ headline: 'Document data residency requirements',
662
+ 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.',
663
+ priority: 'high',
664
+ alsoNeeded: ['Legal review of data transfer agreements', 'Cloud provider region verification'],
665
+ promptTemplate: `Read docs/brainlift.md and docs/prd.md. Create a data residency documentation.
666
+
667
+ Create docs/data-residency.md covering:
668
+ 1. Where is user data stored? (AWS region, GCP zone, etc.)
669
+ 2. Does data cross borders? (US ↔ EU, etc.)
670
+ 3. What legal basis for cross-border transfers? (SCCs, adequacy decisions)
671
+ 4. Can customers choose data region? (for enterprise)
672
+ 5. Where are backups stored?
673
+ 6. Third-party services and their data locations (Stripe, analytics, etc.)
674
+
675
+ Add warning: "Verify regions with your cloud provider dashboard"`,
676
+ condition: (p) => p.targetsEU || p.targetAudience.includes('enterprise'),
677
+ },
417
678
  };
418
679
  // ============================================================================
419
680
  // INFERENCE FUNCTIONS
@@ -480,19 +741,24 @@ export function inferProjectProfile(projectPath) {
480
741
  }
481
742
  catch { /* ignore parse errors */ }
482
743
  }
483
- // Keyword analysis
744
+ // CONSERVATIVE keyword analysis
745
+ // Only trigger on explicit mentions, NOT on broad terms like "global" or "international"
746
+ // AI catch-all will handle nuanced cases
484
747
  const keywords = {
485
- // User data
486
- collectsUserData: ['user', 'account', 'login', 'signup', 'register', 'email', 'profile', 'auth'],
487
- collectsSensitiveData: ['health', 'medical', 'financial', 'bank', 'credit', 'ssn', 'biometric', 'face', 'fingerprint'],
488
- hasUnder13Users: ['kids', 'children', 'child', 'k-12', 'elementary', 'middle school', 'under 13'],
489
- targetsEU: ['eu', 'europe', 'european', 'gdpr', 'uk', 'germany', 'france', 'spain', 'global', 'international', 'worldwide'],
490
- targetsCalifornia: ['california', 'ccpa', 'us', 'usa', 'united states', 'global', 'international'],
491
- hasPayments: ['payment', 'subscribe', 'subscription', 'premium', 'paid', 'pricing', 'monetize', 'charge', 'stripe', 'billing', 'freemium', 'pro plan'],
492
- hasSubscriptions: ['subscription', 'monthly', 'yearly', 'annual', 'recurring', 'plan'],
493
- hasUserContent: ['upload', 'post', 'share', 'comment', 'create content', 'user generated', 'ugc', 'community'],
494
- usesAI: ['ai', 'artificial intelligence', 'machine learning', 'ml', 'gpt', 'llm', 'claude', 'openai', 'generate', 'recommend'],
495
- aiMakesDecisions: ['recommend', 'suggest', 'decide', 'score', 'rank', 'filter', 'personalize', 'match'],
748
+ // User data - conservative: must be clear user accounts
749
+ collectsUserData: ['user account', 'login', 'signup', 'sign up', 'register', 'authentication', 'auth'],
750
+ collectsSensitiveData: ['health', 'medical', 'hipaa', 'financial', 'bank account', 'credit card', 'ssn', 'social security', 'biometric', 'fingerprint', 'face id'],
751
+ hasUnder13Users: ['kids', 'children', 'child', 'k-12', 'elementary', 'middle school', 'under 13', 'coppa', 'parental consent'],
752
+ // Geography - VERY conservative: only explicit mentions, not "global"
753
+ targetsEU: ['eu', 'europe', 'european union', 'gdpr', 'germany', 'france', 'spain', 'italy', 'netherlands', 'uk users'],
754
+ targetsCalifornia: ['california', 'ccpa', 'california users'],
755
+ // Business - clear signals only
756
+ hasPayments: ['payment', 'stripe', 'paypal', 'billing', 'checkout', 'purchase', 'monetize', 'pricing page'],
757
+ hasSubscriptions: ['subscription', 'monthly plan', 'yearly plan', 'recurring billing', 'saas'],
758
+ hasUserContent: ['upload', 'user generated', 'ugc', 'user posts', 'comments section', 'community content'],
759
+ // AI - detect AI usage with common patterns
760
+ 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'],
761
+ aiMakesDecisions: ['ai decides', 'ai recommends', 'automated decision', 'algorithm determines', 'ai-driven', 'ai generates', 'ai creates'],
496
762
  };
497
763
  for (const [key, terms] of Object.entries(keywords)) {
498
764
  if (terms.some(term => content.includes(term))) {
@@ -551,10 +817,10 @@ function fillPromptTemplate(template, profile, projectPath) {
551
817
  const brainliftPath = join(safePath, 'docs', 'brainlift.md');
552
818
  const prdPath = join(safePath, 'docs', 'prd.md');
553
819
  if (existsSync(brainliftPath)) {
554
- brainliftContent = readFileSync(brainliftPath, 'utf-8').slice(0, 1000);
820
+ brainliftContent = readFileSync(brainliftPath, 'utf-8'); // Read full doc for accurate inference
555
821
  }
556
822
  if (existsSync(prdPath)) {
557
- prdContent = readFileSync(prdPath, 'utf-8').slice(0, 1000);
823
+ prdContent = readFileSync(prdPath, 'utf-8'); // Read full doc for accurate inference
558
824
  }
559
825
  // Build replacements
560
826
  const replacements = {
@@ -588,11 +854,22 @@ function fillPromptTemplate(template, profile, projectPath) {
588
854
  * Get all applicable reality checks for a project
589
855
  */
590
856
  export function getRealityChecks(projectPath) {
591
- const profile = inferProjectProfile(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);
592
861
  const checks = [];
862
+ // Load persisted state (after detection so it includes auto-completions)
863
+ const persistedState = loadRealityState(safePath);
864
+ const checkStates = persistedState.checkStates;
593
865
  for (const check of Object.values(REALITY_CHECKS)) {
594
866
  if (check.condition(profile)) {
595
- const cursorPrompt = fillPromptTemplate(check.promptTemplate, profile, projectPath);
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';
596
873
  checks.push({
597
874
  key: check.key,
598
875
  category: check.category,
@@ -604,48 +881,227 @@ export function getRealityChecks(projectPath) {
604
881
  externalLinks: check.externalLinks,
605
882
  alsoNeeded: check.alsoNeeded,
606
883
  priority: check.priority,
884
+ triggeredBy,
885
+ // Add persisted status
886
+ status: persisted?.status || 'pending',
887
+ statusUpdatedAt: persisted?.updatedAt,
888
+ skippedReason: persisted?.skippedReason,
607
889
  });
608
890
  }
609
891
  }
610
892
  // Sort by priority and tier
611
893
  const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
612
- const tierOrder = { human_only: 0, assistable: 1, generatable: 2 };
894
+ const tierOrder = { manual: 0, ai_assisted: 1 };
613
895
  checks.sort((a, b) => {
614
896
  const priorityDiff = priorityOrder[a.priority] - priorityOrder[b.priority];
615
897
  if (priorityDiff !== 0)
616
898
  return priorityDiff;
617
899
  return tierOrder[a.tier] - tierOrder[b.tier];
618
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
+ saveRealityState(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
+ }
619
915
  return {
620
916
  profile,
621
- checks,
917
+ checks: displayChecks,
622
918
  summary: {
623
- total: checks.length,
624
- critical: checks.filter(c => c.priority === 'critical').length,
625
- generatable: checks.filter(c => c.tier === 'generatable').length,
626
- assistable: checks.filter(c => c.tier === 'assistable').length,
627
- humanOnly: checks.filter(c => c.tier === 'human_only').length,
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,
628
926
  },
927
+ totalAvailable,
928
+ isFirstSession,
629
929
  };
630
930
  }
931
+ // ============================================================================
932
+ // AI CATCH-ALL FILTER
933
+ // ============================================================================
934
+ /**
935
+ * AI-powered reality 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
+ // Read full docs for context
955
+ let docsContent = '';
956
+ const brainliftPath = join(safePath, 'docs', 'brainlift.md');
957
+ const prdPath = join(safePath, 'docs', 'prd.md');
958
+ const readmePath = join(safePath, 'README.md');
959
+ if (existsSync(brainliftPath)) {
960
+ docsContent += `## Brainlift:\n${readFileSync(brainliftPath, 'utf-8').slice(0, 4000)}\n\n`;
961
+ }
962
+ if (existsSync(prdPath)) {
963
+ docsContent += `## PRD:\n${readFileSync(prdPath, 'utf-8').slice(0, 4000)}\n\n`;
964
+ }
965
+ if (existsSync(readmePath)) {
966
+ docsContent += `## README:\n${readFileSync(readmePath, 'utf-8').slice(0, 2000)}\n`;
967
+ }
968
+ if (!docsContent) {
969
+ // No docs to analyze - return checks as-is
970
+ return { filtered: checks, additions: [], removals: [] };
971
+ }
972
+ // Combined profile validation + check filtering in a single focused prompt
973
+ const systemPrompt = `Compliance check filter. Given project docs and proposed checks, return JSON only:
974
+ {"keep":["KEY",...], "remove":["KEY",...], "add":["KEY",...]}
975
+ Rules: Remove checks that clearly don't apply. Add missing checks from available list. Be conservative.`;
976
+ // Compact prompt - less tokens, faster response
977
+ const proposed = checks.map(c => c.key).join(',');
978
+ const available = Object.keys(REALITY_CHECKS).filter(k => !checks.some(c => c.key === k)).join(',');
979
+ const prompt = `Docs:\n${docsContent}\n\nProposed: ${proposed}\nAvailable: ${available}\n\nProfile: ${profile.businessModel}, EU:${profile.targetsEU}, AI:${profile.usesAI}, payments:${profile.hasPayments}`;
980
+ try {
981
+ const response = await chat(prompt, {
982
+ systemPrompt,
983
+ maxTokens: 1000,
984
+ useThinking: false, // Fast response
985
+ timeout: 15000, // Quick timeout
986
+ });
987
+ // Parse response
988
+ let jsonStr = response.content;
989
+ if (response.content.includes('```')) {
990
+ const match = response.content.match(/```(?:json)?\s*([\s\S]*?)```/);
991
+ if (match)
992
+ jsonStr = match[1];
993
+ }
994
+ const result = JSON.parse(jsonStr.trim());
995
+ // Filter checks based on AI response
996
+ const keepSet = new Set(result.keep || []);
997
+ const removeSet = new Set(result.remove || []);
998
+ const addKeys = result.add || [];
999
+ // Apply filtering
1000
+ let filtered = checks.filter(c => {
1001
+ // If explicitly removed, remove it
1002
+ if (removeSet.has(c.key))
1003
+ return false;
1004
+ // If explicitly kept or not mentioned, keep it (conservative)
1005
+ return true;
1006
+ });
1007
+ // Add any checks AI says we're missing
1008
+ const safePath = sanitizePath(projectPath);
1009
+ const persistedState = loadRealityState(safePath);
1010
+ for (const key of addKeys) {
1011
+ if (REALITY_CHECKS[key] && !filtered.some(c => c.key === key)) {
1012
+ const check = REALITY_CHECKS[key];
1013
+ const cursorPrompt = fillPromptTemplate(check.promptTemplate, profile, projectPath);
1014
+ const persisted = persistedState.checkStates[key];
1015
+ // Get triggered-by reason (AI-added checks)
1016
+ const triggeredBy = check.getTriggeredBy
1017
+ ? check.getTriggeredBy(profile)
1018
+ : 'Added by AI analysis of project context';
1019
+ filtered.push({
1020
+ key: check.key,
1021
+ category: check.category,
1022
+ tier: check.tier,
1023
+ headline: check.headline,
1024
+ explanation: check.explanation,
1025
+ cursorPrompt,
1026
+ humanSteps: check.humanSteps,
1027
+ externalLinks: check.externalLinks,
1028
+ alsoNeeded: check.alsoNeeded,
1029
+ priority: check.priority,
1030
+ triggeredBy,
1031
+ status: persisted?.status || 'pending',
1032
+ statusUpdatedAt: persisted?.updatedAt,
1033
+ skippedReason: persisted?.skippedReason,
1034
+ });
1035
+ }
1036
+ }
1037
+ // Re-sort
1038
+ const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
1039
+ const tierOrder = { manual: 0, ai_assisted: 1 };
1040
+ filtered.sort((a, b) => {
1041
+ const priorityDiff = priorityOrder[a.priority] - priorityOrder[b.priority];
1042
+ if (priorityDiff !== 0)
1043
+ return priorityDiff;
1044
+ return tierOrder[a.tier] - tierOrder[b.tier];
1045
+ });
1046
+ return {
1047
+ filtered,
1048
+ additions: addKeys,
1049
+ removals: Array.from(removeSet),
1050
+ };
1051
+ }
1052
+ catch (error) {
1053
+ // AI failed - return original checks (conservative fallback)
1054
+ return { filtered: checks, additions: [], removals: [] };
1055
+ }
1056
+ }
1057
+ /**
1058
+ * Get reality checks with AI filtering (async version)
1059
+ * Use this when you want the most accurate checks
1060
+ */
1061
+ export async function getRealityChecksWithAI(projectPath) {
1062
+ const basic = getRealityChecks(projectPath);
1063
+ try {
1064
+ const { filtered, additions, removals } = await filterChecksWithAI(basic.profile, basic.checks, projectPath);
1065
+ return {
1066
+ profile: basic.profile,
1067
+ checks: filtered,
1068
+ summary: {
1069
+ total: filtered.length,
1070
+ critical: filtered.filter(c => c.priority === 'critical').length,
1071
+ aiAssisted: filtered.filter(c => c.tier === 'ai_assisted').length,
1072
+ manual: filtered.filter(c => c.tier === 'manual').length,
1073
+ pending: filtered.filter(c => c.status === 'pending').length,
1074
+ completed: filtered.filter(c => c.status === 'completed').length,
1075
+ skipped: filtered.filter(c => c.status === 'skipped').length,
1076
+ },
1077
+ aiFiltered: additions.length > 0 || removals.length > 0,
1078
+ };
1079
+ }
1080
+ catch {
1081
+ // Fallback to basic checks
1082
+ return { ...basic, aiFiltered: false };
1083
+ }
1084
+ }
631
1085
  /**
632
1086
  * Get tier symbol for display
633
1087
  */
634
1088
  export function getTierSymbol(tier) {
635
- switch (tier) {
636
- case 'generatable': return '✅';
637
- case 'assistable': return '⚠️';
638
- case 'human_only': return '🔴';
1089
+ const mapped = TIER_MAPPING[tier] || tier;
1090
+ switch (mapped) {
1091
+ case 'ai_assisted': return '🤖';
1092
+ case 'manual': return '👤';
1093
+ default: return '❓';
639
1094
  }
640
1095
  }
641
1096
  /**
642
1097
  * Get tier description
643
1098
  */
644
1099
  export function getTierDescription(tier) {
645
- switch (tier) {
646
- case 'generatable': return 'AI can draft this';
647
- case 'assistable': return 'AI can help, needs review';
648
- case 'human_only': return 'You need to do this';
1100
+ const mapped = TIER_MAPPING[tier] || tier;
1101
+ switch (mapped) {
1102
+ case 'ai_assisted': return 'AI can help with this';
1103
+ case 'manual': return 'You need to do this yourself';
1104
+ default: return 'Unknown tier';
649
1105
  }
650
1106
  }
651
1107
  //# sourceMappingURL=reality.js.map