midas-mcp 5.3.9 → 5.5.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.d.ts +92 -0
- package/dist/reality.d.ts.map +1 -0
- package/dist/reality.js +819 -0
- package/dist/reality.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +4 -1
- package/dist/server.js.map +1 -1
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +2 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/reality.d.ts +55 -0
- package/dist/tools/reality.d.ts.map +1 -0
- package/dist/tools/reality.js +83 -0
- package/dist/tools/reality.js.map +1 -0
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +146 -0
- package/dist/tui.js.map +1 -1
- package/package.json +1 -1
package/dist/reality.js
ADDED
|
@@ -0,0 +1,819 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reality 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 } from 'fs';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import { sanitizePath } from './security.js';
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// REALITY CHECK DEFINITIONS
|
|
13
|
+
// ============================================================================
|
|
14
|
+
const REALITY_CHECKS = {
|
|
15
|
+
// ✅ GENERATABLE - AI can draft these
|
|
16
|
+
PRIVACY_POLICY: {
|
|
17
|
+
key: 'PRIVACY_POLICY',
|
|
18
|
+
category: 'Legal',
|
|
19
|
+
tier: 'generatable',
|
|
20
|
+
headline: 'You need a Privacy Policy',
|
|
21
|
+
explanation: 'You collect user data. Users need to know what you collect, why, and how to delete it.',
|
|
22
|
+
priority: 'critical',
|
|
23
|
+
promptTemplate: `Create a privacy policy for this project based on the brainlift and PRD.
|
|
24
|
+
|
|
25
|
+
We collect: {{dataCollected}}
|
|
26
|
+
Target users: {{targetUsers}}
|
|
27
|
+
Business model: {{businessModel}}
|
|
28
|
+
|
|
29
|
+
Include sections:
|
|
30
|
+
- What we collect and why
|
|
31
|
+
- How we use the data
|
|
32
|
+
- Third parties we share with (if any)
|
|
33
|
+
- How long we keep data
|
|
34
|
+
- User rights (access, correct, delete)
|
|
35
|
+
- How to contact us
|
|
36
|
+
|
|
37
|
+
Save to docs/privacy-policy.md
|
|
38
|
+
|
|
39
|
+
Add at the top: "DRAFT - Review with a lawyer before publishing"`,
|
|
40
|
+
condition: (p) => p.collectsUserData,
|
|
41
|
+
},
|
|
42
|
+
TERMS_OF_SERVICE: {
|
|
43
|
+
key: 'TERMS_OF_SERVICE',
|
|
44
|
+
category: 'Legal',
|
|
45
|
+
tier: 'generatable',
|
|
46
|
+
headline: 'You need Terms of Service',
|
|
47
|
+
explanation: 'Any public product needs terms defining the rules of use and liability limits.',
|
|
48
|
+
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}}
|
|
54
|
+
|
|
55
|
+
Include sections:
|
|
56
|
+
- Acceptance of terms
|
|
57
|
+
- Description of service
|
|
58
|
+
- User responsibilities
|
|
59
|
+
- Prohibited uses
|
|
60
|
+
- Intellectual property
|
|
61
|
+
- Limitation of liability
|
|
62
|
+
- Termination
|
|
63
|
+
- Governing law (placeholder for jurisdiction)
|
|
64
|
+
|
|
65
|
+
Save to docs/terms-of-service.md
|
|
66
|
+
|
|
67
|
+
Add at the top: "DRAFT - Review with a lawyer before publishing"`,
|
|
68
|
+
condition: (p) => p.collectsUserData || p.hasPayments,
|
|
69
|
+
},
|
|
70
|
+
AI_DISCLOSURE: {
|
|
71
|
+
key: 'AI_DISCLOSURE',
|
|
72
|
+
category: 'Transparency',
|
|
73
|
+
tier: 'generatable',
|
|
74
|
+
headline: 'You need an AI disclosure',
|
|
75
|
+
explanation: 'Users should know when AI is involved and that it can make mistakes.',
|
|
76
|
+
priority: 'high',
|
|
77
|
+
promptTemplate: `Create an AI transparency disclosure for this project.
|
|
78
|
+
|
|
79
|
+
Based on the brainlift, this project uses AI to: {{aiUsage}}
|
|
80
|
+
|
|
81
|
+
Create a user-friendly disclosure explaining:
|
|
82
|
+
- What AI does in this product
|
|
83
|
+
- That AI can make mistakes or produce inaccurate results
|
|
84
|
+
- How users can report issues or get human help
|
|
85
|
+
- Any limitations users should know about
|
|
86
|
+
|
|
87
|
+
Save to docs/ai-disclosure.md
|
|
88
|
+
|
|
89
|
+
Also add a brief inline disclosure component/text that can be shown in the UI where AI is used.`,
|
|
90
|
+
condition: (p) => p.usesAI,
|
|
91
|
+
},
|
|
92
|
+
REFUND_POLICY: {
|
|
93
|
+
key: 'REFUND_POLICY',
|
|
94
|
+
category: 'Business',
|
|
95
|
+
tier: 'generatable',
|
|
96
|
+
headline: 'You need a refund policy',
|
|
97
|
+
explanation: 'Paid products need clear refund terms to avoid disputes and chargebacks.',
|
|
98
|
+
priority: 'high',
|
|
99
|
+
promptTemplate: `Create a refund policy for this project.
|
|
100
|
+
|
|
101
|
+
Business model: {{businessModel}}
|
|
102
|
+
Subscription type: {{subscriptionType}}
|
|
103
|
+
|
|
104
|
+
Include:
|
|
105
|
+
- Refund eligibility (time period, conditions)
|
|
106
|
+
- How to request a refund
|
|
107
|
+
- Processing time
|
|
108
|
+
- Exceptions (if any)
|
|
109
|
+
- Contact information
|
|
110
|
+
|
|
111
|
+
Keep it simple and fair - generous refund policies reduce chargebacks.
|
|
112
|
+
|
|
113
|
+
Save to docs/refund-policy.md`,
|
|
114
|
+
condition: (p) => p.hasPayments,
|
|
115
|
+
},
|
|
116
|
+
CONTENT_POLICY: {
|
|
117
|
+
key: 'CONTENT_POLICY',
|
|
118
|
+
category: 'Trust',
|
|
119
|
+
tier: 'generatable',
|
|
120
|
+
headline: 'You need a content policy',
|
|
121
|
+
explanation: 'User-generated content needs rules about what\'s allowed and how violations are handled.',
|
|
122
|
+
priority: 'high',
|
|
123
|
+
promptTemplate: `Create a content policy for this project.
|
|
124
|
+
|
|
125
|
+
This product allows users to: {{userContentType}}
|
|
126
|
+
|
|
127
|
+
Include:
|
|
128
|
+
- What content is allowed
|
|
129
|
+
- What content is prohibited (hate speech, harassment, illegal content, etc.)
|
|
130
|
+
- How violations are reported
|
|
131
|
+
- How we handle violations (warning, removal, ban)
|
|
132
|
+
- Appeal process
|
|
133
|
+
|
|
134
|
+
Save to docs/content-policy.md`,
|
|
135
|
+
condition: (p) => p.hasUserContent,
|
|
136
|
+
},
|
|
137
|
+
// ⚠️ ASSISTABLE - AI can create guide, needs professional verification
|
|
138
|
+
GDPR_COMPLIANCE: {
|
|
139
|
+
key: 'GDPR_COMPLIANCE',
|
|
140
|
+
category: 'Compliance',
|
|
141
|
+
tier: 'assistable',
|
|
142
|
+
headline: 'GDPR applies to your product',
|
|
143
|
+
explanation: 'You\'re targeting EU users. You need lawful basis for data processing, user consent, and data rights.',
|
|
144
|
+
priority: 'critical',
|
|
145
|
+
alsoNeeded: ['Legal review of implementation', 'DPA registration if required', 'Data Processing Agreements with vendors'],
|
|
146
|
+
promptTemplate: `Create a GDPR compliance implementation guide for this project.
|
|
147
|
+
|
|
148
|
+
Based on analysis:
|
|
149
|
+
- Data collected: {{dataCollected}}
|
|
150
|
+
- Processing purposes: {{processingPurposes}}
|
|
151
|
+
- Third parties: {{thirdParties}}
|
|
152
|
+
|
|
153
|
+
Generate:
|
|
154
|
+
1. Data inventory table (what data, why collected, legal basis, retention period)
|
|
155
|
+
2. Consent flow requirements (what needs explicit consent vs legitimate interest)
|
|
156
|
+
3. User rights implementation checklist:
|
|
157
|
+
- Right to access (export user data)
|
|
158
|
+
- Right to rectification (edit profile)
|
|
159
|
+
- Right to erasure (delete account)
|
|
160
|
+
- Right to portability (download data)
|
|
161
|
+
4. Cookie consent requirements
|
|
162
|
+
5. Privacy by design checklist
|
|
163
|
+
|
|
164
|
+
Save to docs/gdpr-implementation.md
|
|
165
|
+
|
|
166
|
+
Note at top: "This is a technical implementation guide. Legal review required before launch."`,
|
|
167
|
+
condition: (p) => p.targetsEU && p.collectsUserData,
|
|
168
|
+
},
|
|
169
|
+
CCPA_COMPLIANCE: {
|
|
170
|
+
key: 'CCPA_COMPLIANCE',
|
|
171
|
+
category: 'Compliance',
|
|
172
|
+
tier: 'assistable',
|
|
173
|
+
headline: 'CCPA applies to your product',
|
|
174
|
+
explanation: 'California users have rights to know, delete, and opt-out of data sales.',
|
|
175
|
+
priority: 'high',
|
|
176
|
+
alsoNeeded: ['Legal review', '"Do Not Sell" link if applicable'],
|
|
177
|
+
promptTemplate: `Create a CCPA compliance checklist for this project.
|
|
178
|
+
|
|
179
|
+
Data collected: {{dataCollected}}
|
|
180
|
+
California users expected: Yes
|
|
181
|
+
|
|
182
|
+
Include:
|
|
183
|
+
1. Right to know (disclosure of data collected)
|
|
184
|
+
2. Right to delete implementation
|
|
185
|
+
3. Right to opt-out (if selling data)
|
|
186
|
+
4. Non-discrimination requirements
|
|
187
|
+
5. Privacy policy updates needed for CCPA
|
|
188
|
+
|
|
189
|
+
Save to docs/ccpa-checklist.md`,
|
|
190
|
+
condition: (p) => p.targetsCalifornia && p.collectsUserData,
|
|
191
|
+
},
|
|
192
|
+
ACCESSIBILITY: {
|
|
193
|
+
key: 'ACCESSIBILITY',
|
|
194
|
+
category: 'Inclusion',
|
|
195
|
+
tier: 'assistable',
|
|
196
|
+
headline: 'Consider accessibility (WCAG)',
|
|
197
|
+
explanation: 'Making your product accessible helps more users and may be legally required for some customers.',
|
|
198
|
+
priority: 'medium',
|
|
199
|
+
alsoNeeded: ['Actual testing with screen readers', 'User testing with diverse users'],
|
|
200
|
+
promptTemplate: `Create an accessibility checklist and implementation guide for this project.
|
|
201
|
+
|
|
202
|
+
Review the codebase and create:
|
|
203
|
+
1. WCAG 2.1 AA compliance checklist with current status
|
|
204
|
+
2. Priority fixes needed (semantic HTML, ARIA labels, color contrast)
|
|
205
|
+
3. Keyboard navigation requirements
|
|
206
|
+
4. Screen reader compatibility notes
|
|
207
|
+
5. Form accessibility (labels, error messages)
|
|
208
|
+
|
|
209
|
+
For each issue found, provide the fix.
|
|
210
|
+
|
|
211
|
+
Save checklist to docs/accessibility-checklist.md`,
|
|
212
|
+
condition: (p) => p.targetAudience.some(a => ['enterprise', 'education', 'government'].includes(a)) || p.collectsUserData,
|
|
213
|
+
},
|
|
214
|
+
BIAS_ASSESSMENT: {
|
|
215
|
+
key: 'BIAS_ASSESSMENT',
|
|
216
|
+
category: 'Ethics',
|
|
217
|
+
tier: 'assistable',
|
|
218
|
+
headline: 'AI bias assessment needed',
|
|
219
|
+
explanation: 'AI that makes decisions about people can have unintended bias. Document what you\'ve considered.',
|
|
220
|
+
priority: 'high',
|
|
221
|
+
alsoNeeded: ['Testing with diverse user groups', 'Regular monitoring for bias', 'Human override mechanism'],
|
|
222
|
+
promptTemplate: `Create an AI bias assessment document for this project.
|
|
223
|
+
|
|
224
|
+
AI is used for: {{aiUsage}}
|
|
225
|
+
Decisions affected: {{decisionsAffected}}
|
|
226
|
+
|
|
227
|
+
Document:
|
|
228
|
+
1. What decisions the AI influences
|
|
229
|
+
2. Potential sources of bias (training data, model architecture)
|
|
230
|
+
3. Protected characteristics that could be affected
|
|
231
|
+
4. Mitigation strategies implemented
|
|
232
|
+
5. Monitoring plan for detecting bias
|
|
233
|
+
6. Human override / appeal mechanism
|
|
234
|
+
|
|
235
|
+
Save to docs/ai-bias-assessment.md
|
|
236
|
+
|
|
237
|
+
This is for internal documentation and transparency.`,
|
|
238
|
+
condition: (p) => p.aiMakesDecisions,
|
|
239
|
+
},
|
|
240
|
+
// ❌ HUMAN ONLY - Requires real-world action
|
|
241
|
+
PAYMENT_SETUP: {
|
|
242
|
+
key: 'PAYMENT_SETUP',
|
|
243
|
+
category: 'Business',
|
|
244
|
+
tier: 'human_only',
|
|
245
|
+
headline: 'You need payment processing',
|
|
246
|
+
explanation: 'You want to charge users. You need a payment provider account first.',
|
|
247
|
+
priority: 'critical',
|
|
248
|
+
humanSteps: [
|
|
249
|
+
'Go to stripe.com and create an account',
|
|
250
|
+
'Complete business verification (1-3 days)',
|
|
251
|
+
'Set up your bank account for payouts',
|
|
252
|
+
'Get your API keys from the dashboard',
|
|
253
|
+
],
|
|
254
|
+
externalLinks: ['https://stripe.com', 'https://stripe.com/docs/keys'],
|
|
255
|
+
promptTemplate: `Implement Stripe payment integration for this project.
|
|
256
|
+
|
|
257
|
+
Requirements from PRD:
|
|
258
|
+
- Business model: {{businessModel}}
|
|
259
|
+
- Pricing: {{pricing}}
|
|
260
|
+
|
|
261
|
+
Implement:
|
|
262
|
+
1. Stripe SDK setup with environment variables for keys
|
|
263
|
+
2. Checkout session creation for {{checkoutType}}
|
|
264
|
+
3. Webhook handler for payment events (payment_intent.succeeded, subscription events)
|
|
265
|
+
4. Customer portal link for subscription management
|
|
266
|
+
5. Subscription status middleware to gate premium features
|
|
267
|
+
|
|
268
|
+
I have my Stripe API keys ready. Use STRIPE_SECRET_KEY and STRIPE_PUBLISHABLE_KEY env vars.`,
|
|
269
|
+
condition: (p) => p.hasPayments,
|
|
270
|
+
},
|
|
271
|
+
BUSINESS_REGISTRATION: {
|
|
272
|
+
key: 'BUSINESS_REGISTRATION',
|
|
273
|
+
category: 'Legal',
|
|
274
|
+
tier: 'human_only',
|
|
275
|
+
headline: 'Consider business registration',
|
|
276
|
+
explanation: 'If you\'re making money, you may need a business entity for taxes and liability protection.',
|
|
277
|
+
priority: 'medium',
|
|
278
|
+
humanSteps: [
|
|
279
|
+
'Research business structures (LLC, Corp, Sole Prop)',
|
|
280
|
+
'Register in your state/country',
|
|
281
|
+
'Get an EIN (US) or equivalent tax ID',
|
|
282
|
+
'Open a business bank account',
|
|
283
|
+
],
|
|
284
|
+
externalLinks: ['https://www.sba.gov/business-guide/launch-your-business/choose-business-structure'],
|
|
285
|
+
promptTemplate: `No code needed. This is a business/legal step.
|
|
286
|
+
|
|
287
|
+
Once you have your business set up, update your docs:
|
|
288
|
+
- Add business name to privacy policy and terms
|
|
289
|
+
- Add business address to contact information
|
|
290
|
+
- Update payment provider with business details`,
|
|
291
|
+
condition: (p) => p.hasPayments && p.businessModel !== 'free',
|
|
292
|
+
},
|
|
293
|
+
TAX_SETUP: {
|
|
294
|
+
key: 'TAX_SETUP',
|
|
295
|
+
category: 'Business',
|
|
296
|
+
tier: 'human_only',
|
|
297
|
+
headline: 'You need tax handling',
|
|
298
|
+
explanation: 'Selling internationally means dealing with VAT, GST, and sales tax. Stripe Tax can help.',
|
|
299
|
+
priority: 'high',
|
|
300
|
+
humanSteps: [
|
|
301
|
+
'Enable Stripe Tax in your Stripe dashboard',
|
|
302
|
+
'Register for VAT/GST in required jurisdictions',
|
|
303
|
+
'Or use a service like Paddle that handles tax for you',
|
|
304
|
+
],
|
|
305
|
+
externalLinks: ['https://stripe.com/tax', 'https://paddle.com'],
|
|
306
|
+
promptTemplate: `Add Stripe Tax integration to handle VAT/GST automatically.
|
|
307
|
+
|
|
308
|
+
Enable tax calculation in checkout:
|
|
309
|
+
1. Add tax_behavior: 'exclusive' or 'inclusive' to prices
|
|
310
|
+
2. Enable automatic_tax in checkout sessions
|
|
311
|
+
3. Collect customer address for tax calculation
|
|
312
|
+
4. Display tax amounts in checkout UI
|
|
313
|
+
|
|
314
|
+
See Stripe Tax docs for jurisdiction-specific setup.`,
|
|
315
|
+
condition: (p) => p.hasPayments && p.targetsEU,
|
|
316
|
+
},
|
|
317
|
+
DOMAIN_SSL: {
|
|
318
|
+
key: 'DOMAIN_SSL',
|
|
319
|
+
category: 'Infrastructure',
|
|
320
|
+
tier: 'human_only',
|
|
321
|
+
headline: 'You need a domain and SSL',
|
|
322
|
+
explanation: 'To launch publicly, you need a domain name and HTTPS.',
|
|
323
|
+
priority: 'high',
|
|
324
|
+
humanSteps: [
|
|
325
|
+
'Choose and purchase a domain (Namecheap, Cloudflare, etc.)',
|
|
326
|
+
'Point DNS to your hosting provider',
|
|
327
|
+
'SSL is usually automatic with modern hosts (Vercel, Netlify, etc.)',
|
|
328
|
+
],
|
|
329
|
+
externalLinks: ['https://www.namecheap.com', 'https://www.cloudflare.com'],
|
|
330
|
+
promptTemplate: `No code needed for domain purchase.
|
|
331
|
+
|
|
332
|
+
Once you have your domain, update:
|
|
333
|
+
1. Environment variables with production URL
|
|
334
|
+
2. OAuth redirect URLs if using social login
|
|
335
|
+
3. Stripe webhook URLs
|
|
336
|
+
4. Any hardcoded localhost references`,
|
|
337
|
+
condition: (p) => p.collectsUserData || p.hasPayments,
|
|
338
|
+
},
|
|
339
|
+
APP_STORE: {
|
|
340
|
+
key: 'APP_STORE',
|
|
341
|
+
category: 'Distribution',
|
|
342
|
+
tier: 'human_only',
|
|
343
|
+
headline: 'App Store submission needed',
|
|
344
|
+
explanation: 'Mobile apps need App Store / Play Store developer accounts and review.',
|
|
345
|
+
priority: 'critical',
|
|
346
|
+
humanSteps: [
|
|
347
|
+
'Apple: Enroll in Apple Developer Program ($99/year)',
|
|
348
|
+
'Google: Create Google Play Developer account ($25 one-time)',
|
|
349
|
+
'Prepare screenshots, descriptions, privacy policy URL',
|
|
350
|
+
'Submit for review (Apple: 1-7 days, Google: hours to days)',
|
|
351
|
+
],
|
|
352
|
+
externalLinks: ['https://developer.apple.com/programs/', 'https://play.google.com/console'],
|
|
353
|
+
promptTemplate: `Prepare app store submission materials:
|
|
354
|
+
|
|
355
|
+
1. Generate app screenshots for required sizes
|
|
356
|
+
2. Write app store description (short and long)
|
|
357
|
+
3. Create app preview video (optional but recommended)
|
|
358
|
+
4. Prepare answers for review questions (data usage, permissions)
|
|
359
|
+
5. Ensure privacy policy URL is live and accessible
|
|
360
|
+
|
|
361
|
+
Check that the app follows platform guidelines before submission.`,
|
|
362
|
+
condition: (p) => p.targetAudience.includes('mobile'),
|
|
363
|
+
},
|
|
364
|
+
COPPA_COMPLIANCE: {
|
|
365
|
+
key: 'COPPA_COMPLIANCE',
|
|
366
|
+
category: 'Compliance',
|
|
367
|
+
tier: 'human_only',
|
|
368
|
+
headline: 'COPPA compliance required',
|
|
369
|
+
explanation: 'Users under 13 require parental consent and special data handling.',
|
|
370
|
+
priority: 'critical',
|
|
371
|
+
humanSteps: [
|
|
372
|
+
'Implement age gate / verification',
|
|
373
|
+
'Get verifiable parental consent mechanism',
|
|
374
|
+
'Limit data collection for children',
|
|
375
|
+
'Review with lawyer specializing in children\'s privacy',
|
|
376
|
+
],
|
|
377
|
+
externalLinks: ['https://www.ftc.gov/business-guidance/resources/complying-coppa-frequently-asked-questions'],
|
|
378
|
+
alsoNeeded: ['Legal review', 'Parental consent mechanism'],
|
|
379
|
+
promptTemplate: `Implement COPPA-compliant age verification:
|
|
380
|
+
|
|
381
|
+
1. Add age gate before registration
|
|
382
|
+
2. If under 13, collect parent email
|
|
383
|
+
3. Send parental consent request
|
|
384
|
+
4. Only allow account creation after consent verified
|
|
385
|
+
5. Limit data collection for child accounts
|
|
386
|
+
6. Add easy way for parents to review/delete child data
|
|
387
|
+
|
|
388
|
+
This requires careful legal review - the FTC enforces COPPA strictly.`,
|
|
389
|
+
condition: (p) => p.hasUnder13Users,
|
|
390
|
+
},
|
|
391
|
+
SOC2: {
|
|
392
|
+
key: 'SOC2',
|
|
393
|
+
category: 'Certification',
|
|
394
|
+
tier: 'human_only',
|
|
395
|
+
headline: 'Enterprise customers may require SOC 2',
|
|
396
|
+
explanation: 'B2B/enterprise sales often require SOC 2 certification to prove security practices.',
|
|
397
|
+
priority: 'medium',
|
|
398
|
+
humanSteps: [
|
|
399
|
+
'This is a 6-12 month process costing $20K-100K+',
|
|
400
|
+
'Choose a SOC 2 auditor (Vanta, Drata can help automate)',
|
|
401
|
+
'Implement required controls',
|
|
402
|
+
'Undergo Type I then Type II audit',
|
|
403
|
+
],
|
|
404
|
+
externalLinks: ['https://vanta.com', 'https://drata.com'],
|
|
405
|
+
promptTemplate: `SOC 2 is a certification process, not code.
|
|
406
|
+
|
|
407
|
+
However, you can prepare by implementing:
|
|
408
|
+
1. Access control (role-based permissions, MFA)
|
|
409
|
+
2. Audit logging (who did what, when)
|
|
410
|
+
3. Encryption at rest and in transit
|
|
411
|
+
4. Incident response procedures
|
|
412
|
+
5. Vendor management documentation
|
|
413
|
+
|
|
414
|
+
Create a security checklist: docs/security-checklist.md`,
|
|
415
|
+
condition: (p) => p.targetAudience.includes('enterprise'),
|
|
416
|
+
},
|
|
417
|
+
};
|
|
418
|
+
// ============================================================================
|
|
419
|
+
// INFERENCE FUNCTIONS
|
|
420
|
+
// ============================================================================
|
|
421
|
+
/**
|
|
422
|
+
* Infer project profile from brainlift, PRD, and package.json
|
|
423
|
+
*/
|
|
424
|
+
export function inferProjectProfile(projectPath) {
|
|
425
|
+
const safePath = sanitizePath(projectPath);
|
|
426
|
+
const profile = {
|
|
427
|
+
collectsUserData: false,
|
|
428
|
+
collectsSensitiveData: false,
|
|
429
|
+
hasUnder13Users: false,
|
|
430
|
+
targetsEU: false,
|
|
431
|
+
targetsCalifornia: false,
|
|
432
|
+
hasPayments: false,
|
|
433
|
+
hasSubscriptions: false,
|
|
434
|
+
hasUserContent: false,
|
|
435
|
+
usesAI: false,
|
|
436
|
+
aiMakesDecisions: false,
|
|
437
|
+
isOpenSource: false,
|
|
438
|
+
targetAudience: [],
|
|
439
|
+
businessModel: 'free',
|
|
440
|
+
industry: [],
|
|
441
|
+
};
|
|
442
|
+
// Read docs
|
|
443
|
+
const brainliftPath = join(safePath, 'docs', 'brainlift.md');
|
|
444
|
+
const prdPath = join(safePath, 'docs', 'prd.md');
|
|
445
|
+
const readmePath = join(safePath, 'README.md');
|
|
446
|
+
const packagePath = join(safePath, 'package.json');
|
|
447
|
+
let content = '';
|
|
448
|
+
if (existsSync(brainliftPath)) {
|
|
449
|
+
content += readFileSync(brainliftPath, 'utf-8').toLowerCase() + '\n';
|
|
450
|
+
}
|
|
451
|
+
if (existsSync(prdPath)) {
|
|
452
|
+
content += readFileSync(prdPath, 'utf-8').toLowerCase() + '\n';
|
|
453
|
+
}
|
|
454
|
+
if (existsSync(readmePath)) {
|
|
455
|
+
content += readFileSync(readmePath, 'utf-8').toLowerCase() + '\n';
|
|
456
|
+
}
|
|
457
|
+
// Package.json analysis
|
|
458
|
+
if (existsSync(packagePath)) {
|
|
459
|
+
try {
|
|
460
|
+
const pkg = JSON.parse(readFileSync(packagePath, 'utf-8'));
|
|
461
|
+
const deps = Object.keys(pkg.dependencies || {}).join(' ').toLowerCase();
|
|
462
|
+
const allContent = JSON.stringify(pkg).toLowerCase();
|
|
463
|
+
// Check for payment libraries
|
|
464
|
+
if (deps.includes('stripe') || deps.includes('paypal') || deps.includes('paddle')) {
|
|
465
|
+
profile.hasPayments = true;
|
|
466
|
+
}
|
|
467
|
+
// Check for AI libraries
|
|
468
|
+
if (deps.includes('openai') || deps.includes('anthropic') || deps.includes('langchain') || deps.includes('ai')) {
|
|
469
|
+
profile.usesAI = true;
|
|
470
|
+
}
|
|
471
|
+
// Check for auth (implies user data)
|
|
472
|
+
if (deps.includes('next-auth') || deps.includes('passport') || deps.includes('clerk') || deps.includes('auth0') || deps.includes('firebase')) {
|
|
473
|
+
profile.collectsUserData = true;
|
|
474
|
+
}
|
|
475
|
+
// Open source check
|
|
476
|
+
if (pkg.license && pkg.license !== 'UNLICENSED' && pkg.license !== 'proprietary') {
|
|
477
|
+
profile.isOpenSource = true;
|
|
478
|
+
}
|
|
479
|
+
content += allContent + '\n';
|
|
480
|
+
}
|
|
481
|
+
catch { /* ignore parse errors */ }
|
|
482
|
+
}
|
|
483
|
+
// CONSERVATIVE keyword analysis
|
|
484
|
+
// Only trigger on explicit mentions, NOT on broad terms like "global" or "international"
|
|
485
|
+
// AI catch-all will handle nuanced cases
|
|
486
|
+
const keywords = {
|
|
487
|
+
// User data - conservative: must be clear user accounts
|
|
488
|
+
collectsUserData: ['user account', 'login', 'signup', 'sign up', 'register', 'authentication', 'auth'],
|
|
489
|
+
collectsSensitiveData: ['health', 'medical', 'hipaa', 'financial', 'bank account', 'credit card', 'ssn', 'social security', 'biometric', 'fingerprint', 'face id'],
|
|
490
|
+
hasUnder13Users: ['kids', 'children', 'child', 'k-12', 'elementary', 'middle school', 'under 13', 'coppa', 'parental consent'],
|
|
491
|
+
// Geography - VERY conservative: only explicit mentions, not "global"
|
|
492
|
+
targetsEU: ['eu', 'europe', 'european union', 'gdpr', 'germany', 'france', 'spain', 'italy', 'netherlands', 'uk users'],
|
|
493
|
+
targetsCalifornia: ['california', 'ccpa', 'california users'],
|
|
494
|
+
// Business - clear signals only
|
|
495
|
+
hasPayments: ['payment', 'stripe', 'paypal', 'billing', 'checkout', 'purchase', 'monetize', 'pricing page'],
|
|
496
|
+
hasSubscriptions: ['subscription', 'monthly plan', 'yearly plan', 'recurring billing', 'saas'],
|
|
497
|
+
hasUserContent: ['upload', 'user generated', 'ugc', 'user posts', 'comments section', 'community content'],
|
|
498
|
+
// AI - only when clearly AI-powered
|
|
499
|
+
usesAI: ['ai-powered', 'artificial intelligence', 'machine learning', 'gpt', 'llm', 'claude api', 'openai api', 'langchain'],
|
|
500
|
+
aiMakesDecisions: ['ai decides', 'ai recommends', 'automated decision', 'algorithm determines', 'ai-driven'],
|
|
501
|
+
};
|
|
502
|
+
for (const [key, terms] of Object.entries(keywords)) {
|
|
503
|
+
if (terms.some(term => content.includes(term))) {
|
|
504
|
+
profile[key] = true;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
// Target audience inference
|
|
508
|
+
if (content.includes('student') || content.includes('education') || content.includes('learn')) {
|
|
509
|
+
profile.targetAudience.push('students');
|
|
510
|
+
}
|
|
511
|
+
if (content.includes('enterprise') || content.includes('b2b') || content.includes('business') || content.includes('team')) {
|
|
512
|
+
profile.targetAudience.push('enterprise');
|
|
513
|
+
}
|
|
514
|
+
if (content.includes('developer') || content.includes('api') || content.includes('sdk')) {
|
|
515
|
+
profile.targetAudience.push('developers');
|
|
516
|
+
}
|
|
517
|
+
if (content.includes('mobile') || content.includes('ios') || content.includes('android') || content.includes('app store')) {
|
|
518
|
+
profile.targetAudience.push('mobile');
|
|
519
|
+
}
|
|
520
|
+
// Business model inference
|
|
521
|
+
if (content.includes('free') && !content.includes('freemium') && !content.includes('premium')) {
|
|
522
|
+
profile.businessModel = 'free';
|
|
523
|
+
}
|
|
524
|
+
else if (content.includes('freemium') || (content.includes('free') && content.includes('premium'))) {
|
|
525
|
+
profile.businessModel = 'freemium';
|
|
526
|
+
}
|
|
527
|
+
else if (content.includes('subscription') || content.includes('saas')) {
|
|
528
|
+
profile.businessModel = 'subscription';
|
|
529
|
+
}
|
|
530
|
+
else if (content.includes('enterprise') || content.includes('b2b')) {
|
|
531
|
+
profile.businessModel = 'b2b';
|
|
532
|
+
}
|
|
533
|
+
else if (profile.hasPayments) {
|
|
534
|
+
profile.businessModel = 'paid';
|
|
535
|
+
}
|
|
536
|
+
// Industry inference
|
|
537
|
+
if (content.includes('health') || content.includes('medical') || content.includes('patient')) {
|
|
538
|
+
profile.industry.push('healthcare');
|
|
539
|
+
}
|
|
540
|
+
if (content.includes('finance') || content.includes('banking') || content.includes('invest')) {
|
|
541
|
+
profile.industry.push('finance');
|
|
542
|
+
}
|
|
543
|
+
if (content.includes('education') || content.includes('school') || content.includes('course')) {
|
|
544
|
+
profile.industry.push('education');
|
|
545
|
+
}
|
|
546
|
+
return profile;
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Generate context-aware prompt by filling in template variables
|
|
550
|
+
*/
|
|
551
|
+
function fillPromptTemplate(template, profile, projectPath) {
|
|
552
|
+
const safePath = sanitizePath(projectPath);
|
|
553
|
+
// Read docs for context
|
|
554
|
+
let brainliftContent = '';
|
|
555
|
+
let prdContent = '';
|
|
556
|
+
const brainliftPath = join(safePath, 'docs', 'brainlift.md');
|
|
557
|
+
const prdPath = join(safePath, 'docs', 'prd.md');
|
|
558
|
+
if (existsSync(brainliftPath)) {
|
|
559
|
+
brainliftContent = readFileSync(brainliftPath, 'utf-8').slice(0, 1000);
|
|
560
|
+
}
|
|
561
|
+
if (existsSync(prdPath)) {
|
|
562
|
+
prdContent = readFileSync(prdPath, 'utf-8').slice(0, 1000);
|
|
563
|
+
}
|
|
564
|
+
// Build replacements
|
|
565
|
+
const replacements = {
|
|
566
|
+
'{{dataCollected}}': profile.collectsSensitiveData
|
|
567
|
+
? 'user accounts, emails, and sensitive data (health/financial)'
|
|
568
|
+
: profile.collectsUserData
|
|
569
|
+
? 'user accounts, emails, preferences'
|
|
570
|
+
: 'minimal data',
|
|
571
|
+
'{{targetUsers}}': profile.targetAudience.length > 0
|
|
572
|
+
? profile.targetAudience.join(', ')
|
|
573
|
+
: 'general users',
|
|
574
|
+
'{{businessModel}}': profile.businessModel,
|
|
575
|
+
'{{productType}}': profile.usesAI ? 'AI-powered application' : 'web application',
|
|
576
|
+
'{{keyFeatures}}': brainliftContent.slice(0, 200) || 'See brainlift for details',
|
|
577
|
+
'{{aiUsage}}': profile.usesAI ? 'AI features (see brainlift for specifics)' : 'No AI',
|
|
578
|
+
'{{processingPurposes}}': 'account management, service delivery, analytics',
|
|
579
|
+
'{{thirdParties}}': profile.hasPayments ? 'Stripe for payments, analytics provider' : 'analytics provider',
|
|
580
|
+
'{{subscriptionType}}': profile.hasSubscriptions ? 'recurring subscription' : 'one-time purchase',
|
|
581
|
+
'{{userContentType}}': 'create and share content',
|
|
582
|
+
'{{decisionsAffected}}': 'recommendations, personalization',
|
|
583
|
+
'{{pricing}}': 'See PRD for pricing details',
|
|
584
|
+
'{{checkoutType}}': profile.hasSubscriptions ? 'subscription' : 'one-time payment',
|
|
585
|
+
};
|
|
586
|
+
let result = template;
|
|
587
|
+
for (const [key, value] of Object.entries(replacements)) {
|
|
588
|
+
result = result.replace(new RegExp(key.replace(/[{}]/g, '\\$&'), 'g'), value);
|
|
589
|
+
}
|
|
590
|
+
return result;
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Get all applicable reality checks for a project
|
|
594
|
+
*/
|
|
595
|
+
export function getRealityChecks(projectPath) {
|
|
596
|
+
const profile = inferProjectProfile(projectPath);
|
|
597
|
+
const checks = [];
|
|
598
|
+
for (const check of Object.values(REALITY_CHECKS)) {
|
|
599
|
+
if (check.condition(profile)) {
|
|
600
|
+
const cursorPrompt = fillPromptTemplate(check.promptTemplate, profile, projectPath);
|
|
601
|
+
checks.push({
|
|
602
|
+
key: check.key,
|
|
603
|
+
category: check.category,
|
|
604
|
+
tier: check.tier,
|
|
605
|
+
headline: check.headline,
|
|
606
|
+
explanation: check.explanation,
|
|
607
|
+
cursorPrompt,
|
|
608
|
+
humanSteps: check.humanSteps,
|
|
609
|
+
externalLinks: check.externalLinks,
|
|
610
|
+
alsoNeeded: check.alsoNeeded,
|
|
611
|
+
priority: check.priority,
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
// Sort by priority and tier
|
|
616
|
+
const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
617
|
+
const tierOrder = { human_only: 0, assistable: 1, generatable: 2 };
|
|
618
|
+
checks.sort((a, b) => {
|
|
619
|
+
const priorityDiff = priorityOrder[a.priority] - priorityOrder[b.priority];
|
|
620
|
+
if (priorityDiff !== 0)
|
|
621
|
+
return priorityDiff;
|
|
622
|
+
return tierOrder[a.tier] - tierOrder[b.tier];
|
|
623
|
+
});
|
|
624
|
+
return {
|
|
625
|
+
profile,
|
|
626
|
+
checks,
|
|
627
|
+
summary: {
|
|
628
|
+
total: checks.length,
|
|
629
|
+
critical: checks.filter(c => c.priority === 'critical').length,
|
|
630
|
+
generatable: checks.filter(c => c.tier === 'generatable').length,
|
|
631
|
+
assistable: checks.filter(c => c.tier === 'assistable').length,
|
|
632
|
+
humanOnly: checks.filter(c => c.tier === 'human_only').length,
|
|
633
|
+
},
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
// ============================================================================
|
|
637
|
+
// AI CATCH-ALL FILTER
|
|
638
|
+
// ============================================================================
|
|
639
|
+
/**
|
|
640
|
+
* AI-powered reality check filter
|
|
641
|
+
*
|
|
642
|
+
* Conservative defaults may miss edge cases. This AI pass:
|
|
643
|
+
* 1. Reviews the full project context
|
|
644
|
+
* 2. Filters out irrelevant checks (e.g., GDPR for US-only app)
|
|
645
|
+
* 3. Adds missing checks based on nuanced understanding
|
|
646
|
+
*
|
|
647
|
+
* Called only when API key is available, falls back to keyword-based otherwise.
|
|
648
|
+
*/
|
|
649
|
+
export async function filterChecksWithAI(profile, checks, projectPath) {
|
|
650
|
+
// Dynamic import to avoid circular dependency
|
|
651
|
+
const { getApiKey } = await import('./config.js');
|
|
652
|
+
const { chat } = await import('./providers.js');
|
|
653
|
+
const apiKey = getApiKey();
|
|
654
|
+
if (!apiKey) {
|
|
655
|
+
// No API key - return checks as-is
|
|
656
|
+
return { filtered: checks, additions: [], removals: [] };
|
|
657
|
+
}
|
|
658
|
+
const safePath = sanitizePath(projectPath);
|
|
659
|
+
// Read full docs for context
|
|
660
|
+
let docsContent = '';
|
|
661
|
+
const brainliftPath = join(safePath, 'docs', 'brainlift.md');
|
|
662
|
+
const prdPath = join(safePath, 'docs', 'prd.md');
|
|
663
|
+
const readmePath = join(safePath, 'README.md');
|
|
664
|
+
if (existsSync(brainliftPath)) {
|
|
665
|
+
docsContent += `## Brainlift:\n${readFileSync(brainliftPath, 'utf-8').slice(0, 2000)}\n\n`;
|
|
666
|
+
}
|
|
667
|
+
if (existsSync(prdPath)) {
|
|
668
|
+
docsContent += `## PRD:\n${readFileSync(prdPath, 'utf-8').slice(0, 2000)}\n\n`;
|
|
669
|
+
}
|
|
670
|
+
if (existsSync(readmePath)) {
|
|
671
|
+
docsContent += `## README:\n${readFileSync(readmePath, 'utf-8').slice(0, 1000)}\n`;
|
|
672
|
+
}
|
|
673
|
+
if (!docsContent) {
|
|
674
|
+
// No docs to analyze - return checks as-is
|
|
675
|
+
return { filtered: checks, additions: [], removals: [] };
|
|
676
|
+
}
|
|
677
|
+
const systemPrompt = `You are a compliance advisor reviewing reality checks for a software project.
|
|
678
|
+
Your job is to FILTER the proposed checks based on the actual project context.
|
|
679
|
+
|
|
680
|
+
Rules:
|
|
681
|
+
1. REMOVE checks that don't apply (e.g., GDPR for US-only apps, COPPA for adult-only products)
|
|
682
|
+
2. FLAG checks that might apply but need confirmation (add to "maybeAdd" list)
|
|
683
|
+
3. Be CONSERVATIVE: when in doubt, keep the check
|
|
684
|
+
4. Consider industry-specific requirements (healthcare → HIPAA, education → FERPA, etc.)
|
|
685
|
+
5. Consider geographic requirements (EU → GDPR, California → CCPA, etc.)
|
|
686
|
+
|
|
687
|
+
Respond ONLY with JSON.`;
|
|
688
|
+
const prompt = `# Project Documentation
|
|
689
|
+
${docsContent}
|
|
690
|
+
|
|
691
|
+
# Inferred Profile
|
|
692
|
+
${JSON.stringify(profile, null, 2)}
|
|
693
|
+
|
|
694
|
+
# Proposed Checks
|
|
695
|
+
${checks.map(c => `- ${c.key}: ${c.headline}`).join('\n')}
|
|
696
|
+
|
|
697
|
+
# All Available Checks (not currently triggered)
|
|
698
|
+
${Object.keys(REALITY_CHECKS).filter(k => !checks.some(c => c.key === k)).map(k => `- ${k}: ${REALITY_CHECKS[k].headline}`).join('\n')}
|
|
699
|
+
|
|
700
|
+
Review this and respond with:
|
|
701
|
+
{
|
|
702
|
+
"keep": ["CHECK_KEY", ...], // Checks that definitely apply
|
|
703
|
+
"remove": ["CHECK_KEY", ...], // Checks that don't apply (with reason)
|
|
704
|
+
"add": ["CHECK_KEY", ...], // Checks from available list that SHOULD apply
|
|
705
|
+
"reasoning": "Brief explanation of key decisions"
|
|
706
|
+
}`;
|
|
707
|
+
try {
|
|
708
|
+
const response = await chat(prompt, {
|
|
709
|
+
systemPrompt,
|
|
710
|
+
maxTokens: 1000,
|
|
711
|
+
useThinking: false, // Fast response
|
|
712
|
+
timeout: 15000, // Quick timeout
|
|
713
|
+
});
|
|
714
|
+
// Parse response
|
|
715
|
+
let jsonStr = response.content;
|
|
716
|
+
if (response.content.includes('```')) {
|
|
717
|
+
const match = response.content.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
718
|
+
if (match)
|
|
719
|
+
jsonStr = match[1];
|
|
720
|
+
}
|
|
721
|
+
const result = JSON.parse(jsonStr.trim());
|
|
722
|
+
// Filter checks based on AI response
|
|
723
|
+
const keepSet = new Set(result.keep || []);
|
|
724
|
+
const removeSet = new Set(result.remove || []);
|
|
725
|
+
const addKeys = result.add || [];
|
|
726
|
+
// Apply filtering
|
|
727
|
+
let filtered = checks.filter(c => {
|
|
728
|
+
// If explicitly removed, remove it
|
|
729
|
+
if (removeSet.has(c.key))
|
|
730
|
+
return false;
|
|
731
|
+
// If explicitly kept or not mentioned, keep it (conservative)
|
|
732
|
+
return true;
|
|
733
|
+
});
|
|
734
|
+
// Add any checks AI says we're missing
|
|
735
|
+
for (const key of addKeys) {
|
|
736
|
+
if (REALITY_CHECKS[key] && !filtered.some(c => c.key === key)) {
|
|
737
|
+
const check = REALITY_CHECKS[key];
|
|
738
|
+
const cursorPrompt = fillPromptTemplate(check.promptTemplate, profile, projectPath);
|
|
739
|
+
filtered.push({
|
|
740
|
+
key: check.key,
|
|
741
|
+
category: check.category,
|
|
742
|
+
tier: check.tier,
|
|
743
|
+
headline: check.headline,
|
|
744
|
+
explanation: check.explanation,
|
|
745
|
+
cursorPrompt,
|
|
746
|
+
humanSteps: check.humanSteps,
|
|
747
|
+
externalLinks: check.externalLinks,
|
|
748
|
+
alsoNeeded: check.alsoNeeded,
|
|
749
|
+
priority: check.priority,
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
// Re-sort
|
|
754
|
+
const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
755
|
+
const tierOrder = { human_only: 0, assistable: 1, generatable: 2 };
|
|
756
|
+
filtered.sort((a, b) => {
|
|
757
|
+
const priorityDiff = priorityOrder[a.priority] - priorityOrder[b.priority];
|
|
758
|
+
if (priorityDiff !== 0)
|
|
759
|
+
return priorityDiff;
|
|
760
|
+
return tierOrder[a.tier] - tierOrder[b.tier];
|
|
761
|
+
});
|
|
762
|
+
return {
|
|
763
|
+
filtered,
|
|
764
|
+
additions: addKeys,
|
|
765
|
+
removals: Array.from(removeSet),
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
catch (error) {
|
|
769
|
+
// AI failed - return original checks (conservative fallback)
|
|
770
|
+
return { filtered: checks, additions: [], removals: [] };
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Get reality checks with AI filtering (async version)
|
|
775
|
+
* Use this when you want the most accurate checks
|
|
776
|
+
*/
|
|
777
|
+
export async function getRealityChecksWithAI(projectPath) {
|
|
778
|
+
const basic = getRealityChecks(projectPath);
|
|
779
|
+
try {
|
|
780
|
+
const { filtered, additions, removals } = await filterChecksWithAI(basic.profile, basic.checks, projectPath);
|
|
781
|
+
return {
|
|
782
|
+
profile: basic.profile,
|
|
783
|
+
checks: filtered,
|
|
784
|
+
summary: {
|
|
785
|
+
total: filtered.length,
|
|
786
|
+
critical: filtered.filter(c => c.priority === 'critical').length,
|
|
787
|
+
generatable: filtered.filter(c => c.tier === 'generatable').length,
|
|
788
|
+
assistable: filtered.filter(c => c.tier === 'assistable').length,
|
|
789
|
+
humanOnly: filtered.filter(c => c.tier === 'human_only').length,
|
|
790
|
+
},
|
|
791
|
+
aiFiltered: additions.length > 0 || removals.length > 0,
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
catch {
|
|
795
|
+
// Fallback to basic checks
|
|
796
|
+
return { ...basic, aiFiltered: false };
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Get tier symbol for display
|
|
801
|
+
*/
|
|
802
|
+
export function getTierSymbol(tier) {
|
|
803
|
+
switch (tier) {
|
|
804
|
+
case 'generatable': return '✅';
|
|
805
|
+
case 'assistable': return '⚠️';
|
|
806
|
+
case 'human_only': return '🔴';
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Get tier description
|
|
811
|
+
*/
|
|
812
|
+
export function getTierDescription(tier) {
|
|
813
|
+
switch (tier) {
|
|
814
|
+
case 'generatable': return 'AI can draft this';
|
|
815
|
+
case 'assistable': return 'AI can help, needs review';
|
|
816
|
+
case 'human_only': return 'You need to do this';
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
//# sourceMappingURL=reality.js.map
|