@nerviq/cli 1.3.1 → 1.4.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.
Files changed (3) hide show
  1. package/README.md +47 -30
  2. package/package.json +2 -2
  3. package/src/techniques.js +1272 -0
package/src/techniques.js CHANGED
@@ -4114,6 +4114,1278 @@ const TECHNIQUES = {
4114
4114
  confidence: 0.7,
4115
4115
  },
4116
4116
 
4117
+ // ── MC11: WebSocket / Real-time ──────────────────────────────────────
4118
+
4119
+ websocketLib: {
4120
+ id: 130201,
4121
+ name: 'WebSocket library configured',
4122
+ check: (ctx) => {
4123
+ const deps = ctx.fileContent('package.json') || '';
4124
+ const pyDeps = ctx.fileContent('requirements.txt') || '';
4125
+ const goDeps = ctx.fileContent('go.mod') || '';
4126
+ return /socket\.io|"ws"|sockjs|@nestjs\/websockets|phoenix|channels/i.test(deps) ||
4127
+ /websockets|channels|tornado/i.test(pyDeps) ||
4128
+ /gorilla\/websocket|nhooyr\.io\/websocket/i.test(goDeps) || null;
4129
+ },
4130
+ impact: 'low',
4131
+ category: 'realtime',
4132
+ fix: 'Add a WebSocket library for real-time communication if your app needs live updates.',
4133
+ confidence: 0.7,
4134
+ },
4135
+
4136
+ sseEndpoint: {
4137
+ id: 130202,
4138
+ name: 'Server-Sent Events patterns detected',
4139
+ check: (ctx) => {
4140
+ const codeFiles = findProjectFiles(ctx, /\.(js|ts|jsx|tsx|py|go|rb)$/i);
4141
+ if (codeFiles.length === 0) return null;
4142
+ return codeFiles.some(f => {
4143
+ const content = ctx.fileContent(f) || '';
4144
+ return /EventSource|text\/event-stream|SSE/i.test(content);
4145
+ }) || null;
4146
+ },
4147
+ impact: 'low',
4148
+ category: 'realtime',
4149
+ fix: 'Consider Server-Sent Events (SSE) for server-to-client streaming when full duplex is not needed.',
4150
+ confidence: 0.7,
4151
+ },
4152
+
4153
+ realtimeDatabase: {
4154
+ id: 130203,
4155
+ name: 'Real-time database configured',
4156
+ check: (ctx) => {
4157
+ const deps = ctx.fileContent('package.json') || '';
4158
+ const pyDeps = ctx.fileContent('requirements.txt') || '';
4159
+ return /firebase-admin|supabase|convex|pusher/i.test(deps) ||
4160
+ /firebase-admin|supabase|pusher/i.test(pyDeps) || null;
4161
+ },
4162
+ impact: 'low',
4163
+ category: 'realtime',
4164
+ fix: 'Add a real-time database (Firebase, Supabase, Convex) for live data synchronization.',
4165
+ confidence: 0.7,
4166
+ },
4167
+
4168
+ pubsubPattern: {
4169
+ id: 130204,
4170
+ name: 'Pub/sub messaging configured',
4171
+ check: (ctx) => {
4172
+ const deps = ctx.fileContent('package.json') || '';
4173
+ const pyDeps = ctx.fileContent('requirements.txt') || '';
4174
+ const goDeps = ctx.fileContent('go.mod') || '';
4175
+ return /ioredis|redis|nats|kafkajs|amqplib|bullmq/i.test(deps) ||
4176
+ /redis|nats-py|kafka-python|pika|celery/i.test(pyDeps) ||
4177
+ /go-redis|nats\.go|sarama|amqp091-go/i.test(goDeps) || null;
4178
+ },
4179
+ impact: 'low',
4180
+ category: 'realtime',
4181
+ fix: 'Add a pub/sub messaging system (Redis, NATS, Kafka, RabbitMQ) for decoupled real-time communication.',
4182
+ confidence: 0.7,
4183
+ },
4184
+
4185
+ realtimeAuth: {
4186
+ id: 130205,
4187
+ name: 'WebSocket authentication patterns present',
4188
+ check: (ctx) => {
4189
+ const codeFiles = findProjectFiles(ctx, /\.(js|ts|jsx|tsx|py|go)$/i);
4190
+ if (codeFiles.length === 0) return null;
4191
+ return codeFiles.some(f => {
4192
+ const content = ctx.fileContent(f) || '';
4193
+ return /ws.*auth|socket.*token|connection.*auth|handleConnection.*jwt|on.*connect.*verify/i.test(content);
4194
+ }) || null;
4195
+ },
4196
+ impact: 'low',
4197
+ category: 'realtime',
4198
+ fix: 'Add authentication to WebSocket connections — validate tokens on connect to prevent unauthorized access.',
4199
+ confidence: 0.7,
4200
+ },
4201
+
4202
+ // ── MC12: GraphQL ────────────────────────────────────────────────────
4203
+
4204
+ graphqlSchema: {
4205
+ id: 130206,
4206
+ name: 'GraphQL schema defined',
4207
+ check: (ctx) => {
4208
+ const deps = ctx.fileContent('package.json') || '';
4209
+ const pyDeps = ctx.fileContent('requirements.txt') || '';
4210
+ if (!/graphql|apollo|@nestjs\/graphql|type-graphql/i.test(deps) &&
4211
+ !/graphene|ariadne|strawberry/i.test(pyDeps) &&
4212
+ !hasProjectFile(ctx, /\.graphql$/i)) return null;
4213
+ const schemaFiles = findProjectFiles(ctx, /\.(graphql|gql)$/i);
4214
+ if (schemaFiles.length > 0) return true;
4215
+ const codeFiles = findProjectFiles(ctx, /\.(js|ts|py)$/i);
4216
+ return codeFiles.some(f => {
4217
+ const content = ctx.fileContent(f) || '';
4218
+ return /buildSchema|makeExecutableSchema|typeDefs|@ObjectType|type Query/i.test(content);
4219
+ }) || false;
4220
+ },
4221
+ impact: 'low',
4222
+ category: 'graphql',
4223
+ fix: 'Define a GraphQL schema using .graphql files or schema-first/code-first approach.',
4224
+ confidence: 0.7,
4225
+ },
4226
+
4227
+ graphqlResolvers: {
4228
+ id: 130207,
4229
+ name: 'GraphQL resolvers implemented',
4230
+ check: (ctx) => {
4231
+ const deps = ctx.fileContent('package.json') || '';
4232
+ const pyDeps = ctx.fileContent('requirements.txt') || '';
4233
+ if (!/graphql|apollo|@nestjs\/graphql|type-graphql/i.test(deps) &&
4234
+ !/graphene|ariadne|strawberry/i.test(pyDeps) &&
4235
+ !hasProjectFile(ctx, /\.graphql$/i)) return null;
4236
+ const codeFiles = findProjectFiles(ctx, /\.(js|ts|py)$/i);
4237
+ return codeFiles.some(f => {
4238
+ const content = ctx.fileContent(f) || '';
4239
+ return /@Resolver|@Query|@Mutation|resolvers|resolve_/i.test(content) ||
4240
+ /resolver/i.test(f);
4241
+ }) || false;
4242
+ },
4243
+ impact: 'low',
4244
+ category: 'graphql',
4245
+ fix: 'Implement GraphQL resolvers to handle queries, mutations, and field resolution.',
4246
+ confidence: 0.7,
4247
+ },
4248
+
4249
+ graphqlCodegen: {
4250
+ id: 130208,
4251
+ name: 'GraphQL code generation configured',
4252
+ check: (ctx) => {
4253
+ const deps = ctx.fileContent('package.json') || '';
4254
+ const pyDeps = ctx.fileContent('requirements.txt') || '';
4255
+ if (!/graphql|apollo|@nestjs\/graphql|type-graphql/i.test(deps) &&
4256
+ !/graphene|ariadne|strawberry/i.test(pyDeps) &&
4257
+ !hasProjectFile(ctx, /\.graphql$/i)) return null;
4258
+ return /@graphql-codegen|graphql-let|graphql-code-generator/i.test(deps) || false;
4259
+ },
4260
+ impact: 'low',
4261
+ category: 'graphql',
4262
+ fix: 'Add @graphql-codegen for type-safe GraphQL operations and automatic TypeScript type generation.',
4263
+ confidence: 0.7,
4264
+ },
4265
+
4266
+ graphqlNPlusOne: {
4267
+ id: 130209,
4268
+ name: 'GraphQL N+1 prevention (DataLoader)',
4269
+ check: (ctx) => {
4270
+ const deps = ctx.fileContent('package.json') || '';
4271
+ const pyDeps = ctx.fileContent('requirements.txt') || '';
4272
+ if (!/graphql|apollo|@nestjs\/graphql|type-graphql/i.test(deps) &&
4273
+ !/graphene|ariadne|strawberry/i.test(pyDeps) &&
4274
+ !hasProjectFile(ctx, /\.graphql$/i)) return null;
4275
+ const codeFiles = findProjectFiles(ctx, /\.(js|ts|py)$/i);
4276
+ return /dataloader/i.test(deps) ||
4277
+ /aiodataloader|promise/i.test(pyDeps) ||
4278
+ codeFiles.some(f => /dataloader|batch.*load|DataLoader/i.test(ctx.fileContent(f) || '')) || false;
4279
+ },
4280
+ impact: 'low',
4281
+ category: 'graphql',
4282
+ fix: 'Use DataLoader or batch loading patterns to prevent N+1 query problems in GraphQL resolvers.',
4283
+ confidence: 0.7,
4284
+ },
4285
+
4286
+ graphqlSubscriptions: {
4287
+ id: 130210,
4288
+ name: 'GraphQL subscriptions configured',
4289
+ check: (ctx) => {
4290
+ const deps = ctx.fileContent('package.json') || '';
4291
+ const pyDeps = ctx.fileContent('requirements.txt') || '';
4292
+ if (!/graphql|apollo|@nestjs\/graphql|type-graphql/i.test(deps) &&
4293
+ !/graphene|ariadne|strawberry/i.test(pyDeps) &&
4294
+ !hasProjectFile(ctx, /\.graphql$/i)) return null;
4295
+ return /subscriptions-transport-ws|graphql-ws/i.test(deps) ||
4296
+ findProjectFiles(ctx, /\.(js|ts|py)$/i).some(f => {
4297
+ const content = ctx.fileContent(f) || '';
4298
+ return /@Subscription|PubSub|subscription\s+\w+/i.test(content);
4299
+ }) || false;
4300
+ },
4301
+ impact: 'low',
4302
+ category: 'graphql',
4303
+ fix: 'Configure GraphQL subscriptions with graphql-ws for real-time data pushed to clients.',
4304
+ confidence: 0.7,
4305
+ },
4306
+
4307
+ // ============================================================
4308
+ // === MC1: OBSERVABILITY (category: 'observability') =========
4309
+ // ============================================================
4310
+
4311
+ otelConfigured: {
4312
+ id: 130001,
4313
+ name: 'OpenTelemetry SDK configured',
4314
+ check: (ctx) => {
4315
+ const pkg = ctx.fileContent('package.json') || '';
4316
+ const req = readProjectFiles(ctx, /(^|\/)requirements[^/]*\.txt$/i);
4317
+ const goMod = ctx.fileContent('go.mod') || '';
4318
+ const cargo = ctx.fileContent('Cargo.toml') || '';
4319
+ const deps = [pkg, req, goMod, cargo].join('\n');
4320
+ return /opentelemetry|@opentelemetry\/sdk|otel/i.test(deps) ||
4321
+ ctx.files.some(f => /otel.*config|opentelemetry.*config/i.test(f));
4322
+ },
4323
+ impact: 'high',
4324
+ category: 'observability',
4325
+ fix: 'Add OpenTelemetry SDK to your project for unified traces, metrics, and logs collection.',
4326
+ },
4327
+
4328
+ prometheusMetrics: {
4329
+ id: 130002,
4330
+ name: 'Prometheus metrics configured',
4331
+ check: (ctx) => {
4332
+ const pkg = ctx.fileContent('package.json') || '';
4333
+ const req = readProjectFiles(ctx, /(^|\/)requirements[^/]*\.txt$/i);
4334
+ const goMod = ctx.fileContent('go.mod') || '';
4335
+ const cargo = ctx.fileContent('Cargo.toml') || '';
4336
+ const deps = [pkg, req, goMod, cargo].join('\n');
4337
+ if (/prom-client|prometheus_client|prometheus\/client_golang|prometheus/i.test(deps)) return true;
4338
+ const code = readProjectFiles(ctx, /\.(js|ts|py|go|rs|java)$/i);
4339
+ return /\/metrics\b/.test(code);
4340
+ },
4341
+ impact: 'high',
4342
+ category: 'observability',
4343
+ fix: 'Add a Prometheus client library and expose a /metrics endpoint for monitoring.',
4344
+ },
4345
+
4346
+ structuredLogging: {
4347
+ id: 130003,
4348
+ name: 'Structured logging library',
4349
+ check: (ctx) => {
4350
+ const pkg = ctx.fileContent('package.json') || '';
4351
+ const req = readProjectFiles(ctx, /(^|\/)requirements[^/]*\.txt$/i);
4352
+ const goMod = ctx.fileContent('go.mod') || '';
4353
+ const cargo = ctx.fileContent('Cargo.toml') || '';
4354
+ const deps = [pkg, req, goMod, cargo].join('\n');
4355
+ return /winston|pino|bunyan|structlog|python-json-logger|slog|log\/slog|tracing|tracing-subscriber|logback|log4j/i.test(deps);
4356
+ },
4357
+ impact: 'high',
4358
+ category: 'observability',
4359
+ fix: 'Use a structured logging library (winston, pino, structlog, slog, tracing) for machine-readable logs.',
4360
+ },
4361
+
4362
+ distributedTracing: {
4363
+ id: 130004,
4364
+ name: 'Distributed tracing configured',
4365
+ check: (ctx) => {
4366
+ const pkg = ctx.fileContent('package.json') || '';
4367
+ const req = readProjectFiles(ctx, /(^|\/)requirements[^/]*\.txt$/i);
4368
+ const goMod = ctx.fileContent('go.mod') || '';
4369
+ const cargo = ctx.fileContent('Cargo.toml') || '';
4370
+ const deps = [pkg, req, goMod, cargo].join('\n');
4371
+ if (/jaeger|zipkin|opentelemetry-api|@opentelemetry\/api|dd-trace|datadog-apm/i.test(deps)) return true;
4372
+ return ctx.files.some(f => /jaeger|zipkin|tracing.*config/i.test(f));
4373
+ },
4374
+ impact: 'high',
4375
+ category: 'observability',
4376
+ fix: 'Add a distributed tracing library (Jaeger, Zipkin, OpenTelemetry) for cross-service request tracking.',
4377
+ },
4378
+
4379
+ healthEndpoint: {
4380
+ id: 130005,
4381
+ name: 'Health check endpoint',
4382
+ check: (ctx) => {
4383
+ const code = readProjectFiles(ctx, /\.(js|ts|py|go|rs|java|rb)$/i);
4384
+ const configs = readProjectFiles(ctx, /\.(ya?ml|json|toml)$/i);
4385
+ return /['"\/]health[z]?['"]\s*[,):]|\/health[z]?\b|healthCheck|health_check|livenessProbe|readinessProbe/i.test(code + configs);
4386
+ },
4387
+ impact: 'high',
4388
+ category: 'observability',
4389
+ fix: 'Add a /health or /healthz endpoint for load balancer and orchestrator health checks.',
4390
+ },
4391
+
4392
+ alertingConfigured: {
4393
+ id: 130006,
4394
+ name: 'Alerting system configured',
4395
+ check: (ctx) => {
4396
+ const pkg = ctx.fileContent('package.json') || '';
4397
+ const deps = [pkg, readProjectFiles(ctx, /(^|\/)requirements[^/]*\.txt$/i)].join('\n');
4398
+ const configs = readProjectFiles(ctx, /\.(ya?ml|json|toml)$/i);
4399
+ return /alertmanager|pagerduty|opsgenie|victorops|alert.*rule/i.test(deps + configs) ||
4400
+ ctx.files.some(f => /alert.*rule|alertmanager/i.test(f));
4401
+ },
4402
+ impact: 'medium',
4403
+ category: 'observability',
4404
+ fix: 'Configure alerting (Alertmanager, PagerDuty, OpsGenie) to get notified of production issues.',
4405
+ },
4406
+
4407
+ dashboardDefined: {
4408
+ id: 130007,
4409
+ name: 'Monitoring dashboard defined',
4410
+ check: (ctx) => {
4411
+ const pkg = ctx.fileContent('package.json') || '';
4412
+ return ctx.files.some(f => /grafana\/.*\.json|\.dashboard\.json/i.test(f)) ||
4413
+ /grafana|@grafana/i.test(pkg) ||
4414
+ hasProjectFile(ctx, /grafana/i);
4415
+ },
4416
+ impact: 'medium',
4417
+ category: 'observability',
4418
+ fix: 'Add Grafana dashboard JSON files or configure dashboard-as-code for production monitoring.',
4419
+ },
4420
+
4421
+ logAggregation: {
4422
+ id: 130008,
4423
+ name: 'Log aggregation configured',
4424
+ check: (ctx) => {
4425
+ const pkg = ctx.fileContent('package.json') || '';
4426
+ const req = readProjectFiles(ctx, /(^|\/)requirements[^/]*\.txt$/i);
4427
+ const configs = readProjectFiles(ctx, /\.(ya?ml|json|toml)$/i);
4428
+ const all = [pkg, req, configs].join('\n');
4429
+ return /elasticsearch|elastic\.co|logstash|kibana|loki|grafana-loki|cloudwatch.*log|datadog|fluentd|fluent-bit|filebeat/i.test(all);
4430
+ },
4431
+ impact: 'medium',
4432
+ category: 'observability',
4433
+ fix: 'Configure log aggregation (ELK, Loki, CloudWatch, Datadog) for centralized log analysis.',
4434
+ },
4435
+
4436
+ // ============================================================
4437
+ // === MC2: ACCESSIBILITY (category: 'accessibility') =========
4438
+ // ============================================================
4439
+
4440
+ a11yTestingTool: {
4441
+ id: 130011,
4442
+ name: 'Accessibility testing tool',
4443
+ check: (ctx) => {
4444
+ if (!hasFrontendSignals(ctx)) return null;
4445
+ const pkg = ctx.fileContent('package.json') || '';
4446
+ return /axe-core|pa11y|@testing-library\/jest-dom|jest-axe|cypress-axe|@axe-core/i.test(pkg);
4447
+ },
4448
+ impact: 'high',
4449
+ category: 'accessibility',
4450
+ fix: 'Add an accessibility testing tool (axe-core, pa11y, jest-axe) to catch a11y regressions.',
4451
+ },
4452
+
4453
+ ariaLabels: {
4454
+ id: 130012,
4455
+ name: 'ARIA labels in components',
4456
+ check: (ctx) => {
4457
+ if (!hasFrontendSignals(ctx)) return null;
4458
+ const components = readProjectFiles(ctx, /\.(jsx|tsx|vue|svelte|html)$/i);
4459
+ if (!components) return null;
4460
+ return /aria-label|aria-labelledby|aria-describedby/i.test(components);
4461
+ },
4462
+ impact: 'high',
4463
+ category: 'accessibility',
4464
+ fix: 'Add aria-label or aria-labelledby attributes to interactive components for screen readers.',
4465
+ },
4466
+
4467
+ wcagMentioned: {
4468
+ id: 130013,
4469
+ name: 'WCAG or accessibility in docs',
4470
+ check: (ctx) => {
4471
+ if (!hasFrontendSignals(ctx)) return null;
4472
+ const docs = readProjectFiles(ctx, /\.(md|txt|rst)$/i);
4473
+ const configs = readProjectFiles(ctx, /\.(ya?ml|json|toml)$/i);
4474
+ return /wcag|accessibility|a11y/i.test(docs + configs);
4475
+ },
4476
+ impact: 'medium',
4477
+ category: 'accessibility',
4478
+ fix: 'Document WCAG compliance level and accessibility standards in your project docs.',
4479
+ },
4480
+
4481
+ semanticHtml: {
4482
+ id: 130014,
4483
+ name: 'Semantic HTML elements used',
4484
+ check: (ctx) => {
4485
+ if (!hasFrontendSignals(ctx)) return null;
4486
+ const templates = readProjectFiles(ctx, /\.(jsx|tsx|vue|svelte|html)$/i);
4487
+ if (!templates) return null;
4488
+ return /<(nav|main|article|section|aside|header|footer)\b/i.test(templates);
4489
+ },
4490
+ impact: 'medium',
4491
+ category: 'accessibility',
4492
+ fix: 'Use semantic HTML elements (nav, main, article, section, aside) instead of generic div elements.',
4493
+ },
4494
+
4495
+ colorContrastTool: {
4496
+ id: 130015,
4497
+ name: 'Color contrast checking configured',
4498
+ check: (ctx) => {
4499
+ if (!hasFrontendSignals(ctx)) return null;
4500
+ const pkg = ctx.fileContent('package.json') || '';
4501
+ const configs = readProjectFiles(ctx, /\.(ya?ml|json|toml|js|ts)$/i);
4502
+ return /axe-core|lighthouse|contrast-checker|color-contrast|a11y.*color/i.test(pkg + configs);
4503
+ },
4504
+ impact: 'medium',
4505
+ category: 'accessibility',
4506
+ fix: 'Configure a color contrast checking tool (axe, Lighthouse CI, contrast-checker) for WCAG AA compliance.',
4507
+ },
4508
+
4509
+ keyboardNavigation: {
4510
+ id: 130016,
4511
+ name: 'Keyboard navigation patterns',
4512
+ check: (ctx) => {
4513
+ if (!hasFrontendSignals(ctx)) return null;
4514
+ const components = readProjectFiles(ctx, /\.(jsx|tsx|vue|svelte|html)$/i);
4515
+ if (!components) return null;
4516
+ return /tabindex|onKeyDown|onKeyUp|onKeyPress|@keydown|@keyup|v-on:keydown|focus-trap|useFocusTrap|FocusTrap/i.test(components);
4517
+ },
4518
+ impact: 'medium',
4519
+ category: 'accessibility',
4520
+ fix: 'Implement keyboard navigation with tabindex, key handlers, and focus management for accessible UIs.',
4521
+ },
4522
+
4523
+ // ============================================================
4524
+ // === MC4: DATA PRIVACY / GDPR (category: 'privacy') ========
4525
+ // ============================================================
4526
+
4527
+ privacyPolicy: {
4528
+ id: 130021,
4529
+ name: 'Privacy policy document exists',
4530
+ check: (ctx) => {
4531
+ return ctx.files.some(f => /privacy/i.test(f) && /\.(md|txt|html|rst)$/i.test(f)) ||
4532
+ hasProjectFile(ctx, /privacy[_-]?policy/i);
4533
+ },
4534
+ impact: 'high',
4535
+ category: 'privacy',
4536
+ fix: 'Create a PRIVACY.md or privacy-policy document describing data handling practices.',
4537
+ },
4538
+
4539
+ consentManagement: {
4540
+ id: 130022,
4541
+ name: 'Consent management configured',
4542
+ check: (ctx) => {
4543
+ const pkg = ctx.fileContent('package.json') || '';
4544
+ const code = readProjectFiles(ctx, /\.(js|ts|jsx|tsx|html)$/i);
4545
+ return /cookie-consent|cookieconsent|onetrust|cookiebot|consent-manager|cookie.*banner/i.test(pkg + code);
4546
+ },
4547
+ impact: 'high',
4548
+ category: 'privacy',
4549
+ fix: 'Add a consent management solution (CookieConsent, OneTrust, Cookiebot) for GDPR cookie compliance.',
4550
+ },
4551
+
4552
+ dataRetentionPolicy: {
4553
+ id: 130023,
4554
+ name: 'Data retention policy documented',
4555
+ check: (ctx) => {
4556
+ const docs = readProjectFiles(ctx, /\.(md|txt|rst|ya?ml|json)$/i);
4557
+ return /data.retention|retention.polic|ttl.*expir|expir.*polic/i.test(docs);
4558
+ },
4559
+ impact: 'medium',
4560
+ category: 'privacy',
4561
+ fix: 'Document your data retention policy specifying how long user data is stored and when it is deleted.',
4562
+ },
4563
+
4564
+ piiHandling: {
4565
+ id: 130024,
4566
+ name: 'PII handling patterns in code',
4567
+ check: (ctx) => {
4568
+ const code = readProjectFiles(ctx, /\.(js|ts|py|go|rs|java|rb)$/i);
4569
+ return /\bredact|anonymize|pseudonymize|mask.*(email|phone|ssn|pii)|pii.*mask|sanitize.*(user|personal)/i.test(code);
4570
+ },
4571
+ impact: 'high',
4572
+ category: 'privacy',
4573
+ fix: 'Implement PII handling patterns (redact, anonymize, mask) to protect personal data in logs and storage.',
4574
+ },
4575
+
4576
+ gdprCompliance: {
4577
+ id: 130025,
4578
+ name: 'GDPR/CCPA compliance mentioned',
4579
+ check: (ctx) => {
4580
+ const docs = readProjectFiles(ctx, /\.(md|txt|rst)$/i);
4581
+ const configs = readProjectFiles(ctx, /\.(ya?ml|json|toml)$/i);
4582
+ return /\bgdpr\b|\bccpa\b|data.protection|right.to.erasure|data.subject|dpa\b/i.test(docs + configs);
4583
+ },
4584
+ impact: 'high',
4585
+ category: 'privacy',
4586
+ fix: 'Document GDPR/CCPA compliance measures and data protection practices in your project.',
4587
+ },
4588
+
4589
+ dataEncryption: {
4590
+ id: 130026,
4591
+ name: 'Data encryption in deps or config',
4592
+ check: (ctx) => {
4593
+ const pkg = ctx.fileContent('package.json') || '';
4594
+ const req = readProjectFiles(ctx, /(^|\/)requirements[^/]*\.txt$/i);
4595
+ const goMod = ctx.fileContent('go.mod') || '';
4596
+ const cargo = ctx.fileContent('Cargo.toml') || '';
4597
+ const deps = [pkg, req, goMod, cargo].join('\n');
4598
+ return /bcrypt|argon2|scrypt|crypto|node:crypto|cryptography|ring\b|rustls|tls.*config|ssl.*config|encryption.at.rest/i.test(deps) ||
4599
+ /encrypt|bcrypt|argon2/i.test(readProjectFiles(ctx, /\.(js|ts|py|go|rs|java)$/i));
4600
+ },
4601
+ impact: 'high',
4602
+ category: 'privacy',
4603
+ fix: 'Use encryption libraries (bcrypt, argon2, crypto) for data at rest and configure TLS for data in transit.',
4604
+ },
4605
+
4606
+ // ============================================================
4607
+ // === MC9: ERROR TRACKING (category: 'error-tracking') =======
4608
+ // ============================================================
4609
+
4610
+ errorTrackingService: {
4611
+ id: 130031,
4612
+ name: 'Error tracking service configured',
4613
+ check: (ctx) => {
4614
+ const pkg = ctx.fileContent('package.json') || '';
4615
+ const req = readProjectFiles(ctx, /(^|\/)requirements[^/]*\.txt$/i);
4616
+ const goMod = ctx.fileContent('go.mod') || '';
4617
+ const cargo = ctx.fileContent('Cargo.toml') || '';
4618
+ const deps = [pkg, req, goMod, cargo].join('\n');
4619
+ return /@sentry\/|sentry-sdk|sentry_sdk|bugsnag|rollbar|datadog.*apm|dd-trace|getsentry/i.test(deps);
4620
+ },
4621
+ impact: 'high',
4622
+ category: 'error-tracking',
4623
+ fix: 'Add an error tracking service (Sentry, Bugsnag, Rollbar, Datadog APM) to catch production errors.',
4624
+ },
4625
+
4626
+ errorBoundaries: {
4627
+ id: 130032,
4628
+ name: 'Error boundaries in frontend',
4629
+ check: (ctx) => {
4630
+ if (!hasFrontendSignals(ctx)) return null;
4631
+ const components = readProjectFiles(ctx, /\.(jsx|tsx|vue|svelte|js|ts)$/i);
4632
+ if (!components) return null;
4633
+ return /ErrorBoundary|errorHandler|onErrorCaptured|componentDidCatch|getDerivedStateFromError|error\.vue|_error\.(jsx|tsx|js|ts)/i.test(components);
4634
+ },
4635
+ impact: 'high',
4636
+ category: 'error-tracking',
4637
+ fix: 'Add error boundaries (React ErrorBoundary, Vue errorHandler) to gracefully handle frontend errors.',
4638
+ },
4639
+
4640
+ unhandledRejection: {
4641
+ id: 130033,
4642
+ name: 'Unhandled rejection/exception handler',
4643
+ check: (ctx) => {
4644
+ const code = readProjectFiles(ctx, /\.(js|ts|py|go|rs)$/i);
4645
+ return /unhandledRejection|uncaughtException|sys\.excepthook|recover\(\)|panic.*handler|set_hook.*panic/i.test(code);
4646
+ },
4647
+ impact: 'high',
4648
+ category: 'error-tracking',
4649
+ fix: 'Add handlers for unhandledRejection and uncaughtException to prevent silent failures.',
4650
+ },
4651
+
4652
+ errorReporting: {
4653
+ id: 130034,
4654
+ name: 'Error notification/reporting pattern',
4655
+ check: (ctx) => {
4656
+ const code = readProjectFiles(ctx, /\.(js|ts|py|go|rs|java|rb)$/i);
4657
+ return /error.*webhook|error.*slack|error.*notify|alert.*error|captureException|captureMessage|notify.*error/i.test(code);
4658
+ },
4659
+ impact: 'medium',
4660
+ category: 'error-tracking',
4661
+ fix: 'Add error reporting patterns (webhook, Slack alerts, Sentry capture) to get notified of failures.',
4662
+ },
4663
+
4664
+ errorBudgetSlo: {
4665
+ id: 130035,
4666
+ name: 'SLO/SLA or error budget defined',
4667
+ check: (ctx) => {
4668
+ const docs = readProjectFiles(ctx, /\.(md|txt|rst|ya?ml|json|toml)$/i);
4669
+ return /\bslo\b|\bsla\b|error.budget|service.level|uptime.*target|availability.*target/i.test(docs);
4670
+ },
4671
+ impact: 'medium',
4672
+ category: 'error-tracking',
4673
+ fix: 'Define SLOs, SLAs, or error budgets in your docs to set clear reliability targets.',
4674
+ },
4675
+
4676
+ crashReporting: {
4677
+ id: 130036,
4678
+ name: 'Crash reporting for mobile',
4679
+ check: (ctx) => {
4680
+ const hasMobile = isFlutterProject(ctx) || isSwiftProject(ctx) || isKotlinProject(ctx) ||
4681
+ /react-native|expo/i.test(ctx.fileContent('package.json') || '');
4682
+ if (!hasMobile) return null;
4683
+ const deps = [
4684
+ ctx.fileContent('package.json') || '',
4685
+ ctx.fileContent('pubspec.yaml') || '',
4686
+ ctx.fileContent('Podfile') || '',
4687
+ readProjectFiles(ctx, /(^|\/)build\.gradle(\.kts)?$/i),
4688
+ ].join('\n');
4689
+ return /crashlytics|sentry.*native|@sentry\/react-native|bugsnag.*react-native|firebase.*crash/i.test(deps);
4690
+ },
4691
+ impact: 'high',
4692
+ category: 'error-tracking',
4693
+ fix: 'Add crash reporting (Crashlytics, Sentry Native) to track mobile app crashes in production.',
4694
+ },
4695
+
4696
+ // ============================================================
4697
+ // === MC15: SUPPLY CHAIN SECURITY (category: 'supply-chain') =
4698
+ // ============================================================
4699
+
4700
+ sbomExists: {
4701
+ id: 130041,
4702
+ name: 'SBOM file exists',
4703
+ check: (ctx) => {
4704
+ return ctx.files.some(f => /sbom\.(json|xml|cdx\.json)|bom\.xml|cyclonedx/i.test(f));
4705
+ },
4706
+ impact: 'medium',
4707
+ category: 'supply-chain',
4708
+ fix: 'Generate an SBOM (Software Bill of Materials) in CycloneDX or SPDX format for supply chain transparency.',
4709
+ },
4710
+
4711
+ dependencyPinning: {
4712
+ id: 130042,
4713
+ name: 'Lock files committed',
4714
+ check: (ctx) => {
4715
+ return ctx.files.some(f => /^(package-lock\.json|yarn\.lock|pnpm-lock\.yaml|Cargo\.lock|poetry\.lock|Pipfile\.lock|bun\.lockb|composer\.lock|Gemfile\.lock|go\.sum)$/i.test(f));
4716
+ },
4717
+ impact: 'high',
4718
+ category: 'supply-chain',
4719
+ fix: 'Commit lock files (package-lock.json, yarn.lock, Cargo.lock, poetry.lock) for reproducible builds.',
4720
+ },
4721
+
4722
+ provenanceAttestation: {
4723
+ id: 130043,
4724
+ name: 'Provenance or sigstore in CI',
4725
+ check: (ctx) => {
4726
+ const ci = getWorkflowContent(ctx);
4727
+ return /provenance|sigstore|cosign|slsa|attestation/i.test(ci);
4728
+ },
4729
+ impact: 'medium',
4730
+ category: 'supply-chain',
4731
+ fix: 'Add npm provenance or sigstore attestation in CI to verify package integrity.',
4732
+ },
4733
+
4734
+ lockfileIntegrity: {
4735
+ id: 130044,
4736
+ name: 'CI uses frozen lockfile install',
4737
+ check: (ctx) => {
4738
+ const ci = getWorkflowContent(ctx);
4739
+ return /npm ci\b|--frozen-lockfile|--immutable|cargo.*--locked|pip install.*--require-hashes/i.test(ci);
4740
+ },
4741
+ impact: 'high',
4742
+ category: 'supply-chain',
4743
+ fix: 'Use `npm ci` or `--frozen-lockfile` in CI instead of `npm install` for deterministic builds.',
4744
+ },
4745
+
4746
+ dependencyScanning: {
4747
+ id: 130045,
4748
+ name: 'Dependency scanning configured',
4749
+ check: (ctx) => {
4750
+ const hasConfig = ctx.files.some(f => /dependabot\.yml|renovate\.json|\.snyk/i.test(f));
4751
+ if (hasConfig) return true;
4752
+ const ci = getWorkflowContent(ctx);
4753
+ return /dependabot|renovate|snyk|npm audit|cargo audit|pip-audit|safety check/i.test(ci);
4754
+ },
4755
+ impact: 'high',
4756
+ category: 'supply-chain',
4757
+ fix: 'Configure Dependabot, Renovate, or Snyk to automatically scan and update vulnerable dependencies.',
4758
+ },
4759
+
4760
+ // ── MC3: Internationalization i18n ──────────────────────────────────
4761
+
4762
+ i18nLibrary: {
4763
+ id: 130101,
4764
+ name: 'i18n library in dependencies',
4765
+ check: (ctx) => {
4766
+ const pkg = ctx.fileContent('package.json') || '';
4767
+ if (/i18next|react-intl|vue-i18n|@angular\/localize/i.test(pkg)) return true;
4768
+ const py = readProjectFiles(ctx, /(^|\/)pyproject\.toml$/i) + readProjectFiles(ctx, /(^|\/)requirements[^/]*\.txt$/i);
4769
+ if (/gettext|babel|fluent/i.test(py)) return true;
4770
+ return false;
4771
+ },
4772
+ impact: 'medium',
4773
+ category: 'i18n',
4774
+ fix: 'Add an i18n library (i18next, react-intl, vue-i18n, gettext, fluent) for internationalization support.',
4775
+ confidence: 0.7,
4776
+ },
4777
+
4778
+ localeFiles: {
4779
+ id: 130102,
4780
+ name: 'Locale files exist',
4781
+ check: (ctx) => {
4782
+ return hasProjectFile(ctx, /(^|\/)locales\//i) ||
4783
+ hasProjectFile(ctx, /(^|\/)messages\//i) ||
4784
+ hasProjectFile(ctx, /(^|\/)translations\//i) ||
4785
+ hasProjectFile(ctx, /\.(po|xlf)$/i);
4786
+ },
4787
+ impact: 'medium',
4788
+ category: 'i18n',
4789
+ fix: 'Add locale files in a locales/, messages/, or translations/ directory for multi-language support.',
4790
+ confidence: 0.7,
4791
+ },
4792
+
4793
+ rtlSupport: {
4794
+ id: 130103,
4795
+ name: 'RTL support configured',
4796
+ check: (ctx) => {
4797
+ const src = readProjectFiles(ctx, /\.(jsx?|tsx?|vue|html|css|scss)$/i, 30);
4798
+ return /dir=["']rtl["']|\brtl\b|\bbidi\b/i.test(src);
4799
+ },
4800
+ impact: 'low',
4801
+ category: 'i18n',
4802
+ fix: 'Add RTL (right-to-left) support with dir="rtl" or bidi utilities for languages like Arabic and Hebrew.',
4803
+ confidence: 0.7,
4804
+ },
4805
+
4806
+ pluralizationRules: {
4807
+ id: 130104,
4808
+ name: 'ICU message format or pluralization',
4809
+ check: (ctx) => {
4810
+ const src = readProjectFiles(ctx, /\.(jsx?|tsx?|json|properties)$/i, 30);
4811
+ return /\{[^}]*,\s*plural\s*,/i.test(src) || /\bplural\b.*\bone\b|\bICU\b/i.test(src);
4812
+ },
4813
+ impact: 'low',
4814
+ category: 'i18n',
4815
+ fix: 'Use ICU message format or pluralization rules for correct multi-language number/gender handling.',
4816
+ confidence: 0.7,
4817
+ },
4818
+
4819
+ i18nExtraction: {
4820
+ id: 130105,
4821
+ name: 'i18n extraction tool configured',
4822
+ check: (ctx) => {
4823
+ const pkg = ctx.fileContent('package.json') || '';
4824
+ return /babel-plugin-react-intl|i18next-parser|@formatjs\/cli|react-intl-translations-manager/i.test(pkg);
4825
+ },
4826
+ impact: 'low',
4827
+ category: 'i18n',
4828
+ fix: 'Add an i18n extraction tool (i18next-parser, @formatjs/cli) to auto-extract translatable strings.',
4829
+ confidence: 0.7,
4830
+ },
4831
+
4832
+ dateTimeFormatting: {
4833
+ id: 130106,
4834
+ name: 'Locale-aware date/time formatting',
4835
+ check: (ctx) => {
4836
+ const src = readProjectFiles(ctx, /\.(jsx?|tsx?|vue)$/i, 30);
4837
+ return /Intl\.DateTimeFormat|date-fns\/locale|dayjs\/locale|moment\/locale/i.test(src);
4838
+ },
4839
+ impact: 'low',
4840
+ category: 'i18n',
4841
+ fix: 'Use locale-aware date/time formatting (Intl.DateTimeFormat, date-fns/locale, dayjs/locale) instead of hardcoded formats.',
4842
+ confidence: 0.7,
4843
+ },
4844
+
4845
+ // ── MC5: API Versioning ─────────────────────────────────────────────
4846
+
4847
+ apiVersionHeader: {
4848
+ id: 130111,
4849
+ name: 'API versioning pattern present',
4850
+ check: (ctx) => {
4851
+ const src = readProjectFiles(ctx, /\.(jsx?|tsx?|py|go|java|rb)$/i, 30);
4852
+ const config = readProjectFiles(ctx, /\.(ya?ml|json)$/i, 20);
4853
+ return /\/v[12]\/|api-version|Accept-Version|x-api-version/i.test(src + config);
4854
+ },
4855
+ impact: 'medium',
4856
+ category: 'api-versioning',
4857
+ fix: 'Add API versioning (URL prefix /v1/, header Accept-Version) to manage breaking changes safely.',
4858
+ confidence: 0.7,
4859
+ },
4860
+
4861
+ deprecationNotices: {
4862
+ id: 130112,
4863
+ name: 'Deprecation notices in API code',
4864
+ check: (ctx) => {
4865
+ const src = readProjectFiles(ctx, /\.(jsx?|tsx?|py|go|java|rb)$/i, 30);
4866
+ return /@deprecated|Deprecation|Sunset|x-deprecated/i.test(src);
4867
+ },
4868
+ impact: 'low',
4869
+ category: 'api-versioning',
4870
+ fix: 'Add @deprecated annotations or Deprecation/Sunset headers to signal API endpoint retirement.',
4871
+ confidence: 0.7,
4872
+ },
4873
+
4874
+ apiChangelog: {
4875
+ id: 130113,
4876
+ name: 'API changelog exists',
4877
+ check: (ctx) => {
4878
+ if (hasProjectFile(ctx, /(^|\/)api-changelog/i)) return true;
4879
+ const changelog = ctx.fileContent('CHANGELOG.md') || '';
4880
+ return /\bAPI\b/i.test(changelog);
4881
+ },
4882
+ impact: 'low',
4883
+ category: 'api-versioning',
4884
+ fix: 'Add an API changelog (CHANGELOG.md with API section or api-changelog file) to document breaking changes.',
4885
+ confidence: 0.7,
4886
+ },
4887
+
4888
+ backwardCompat: {
4889
+ id: 130114,
4890
+ name: 'Backward compatibility tests or migrations',
4891
+ check: (ctx) => {
4892
+ return hasProjectFile(ctx, /(^|\/)(migration|migrate)/i) ||
4893
+ hasProjectFile(ctx, /(backward|compat).*test/i);
4894
+ },
4895
+ impact: 'medium',
4896
+ category: 'api-versioning',
4897
+ fix: 'Add backward compatibility tests or migration scripts to validate API changes don\'t break clients.',
4898
+ confidence: 0.7,
4899
+ },
4900
+
4901
+ apiDocVersioned: {
4902
+ id: 130115,
4903
+ name: 'Versioned API documentation',
4904
+ check: (ctx) => {
4905
+ const docs = readProjectFiles(ctx, /(openapi|swagger)\.(ya?ml|json)$/i);
4906
+ return /version/i.test(docs);
4907
+ },
4908
+ impact: 'low',
4909
+ category: 'api-versioning',
4910
+ fix: 'Add versioned API documentation (OpenAPI/Swagger spec with version field) for API consumers.',
4911
+ confidence: 0.7,
4912
+ },
4913
+
4914
+ // ── MC6: Caching Strategy ───────────────────────────────────────────
4915
+
4916
+ cacheLayer: {
4917
+ id: 130121,
4918
+ name: 'Cache library in dependencies',
4919
+ check: (ctx) => {
4920
+ const pkg = ctx.fileContent('package.json') || '';
4921
+ if (/redis|memcached|ioredis|node-cache|lru-cache/i.test(pkg)) return true;
4922
+ const py = readProjectFiles(ctx, /(^|\/)pyproject\.toml$/i) + readProjectFiles(ctx, /(^|\/)requirements[^/]*\.txt$/i);
4923
+ if (/redis|memcached|django-cache|cachetools/i.test(py)) return true;
4924
+ return false;
4925
+ },
4926
+ impact: 'medium',
4927
+ category: 'caching',
4928
+ fix: 'Add a caching layer (redis, memcached, ioredis, lru-cache) to reduce latency and database load.',
4929
+ confidence: 0.7,
4930
+ },
4931
+
4932
+ cdnConfigured: {
4933
+ id: 130122,
4934
+ name: 'CDN configured',
4935
+ check: (ctx) => {
4936
+ const config = readProjectFiles(ctx, /\.(json|ya?ml|toml|conf)$/i, 20);
4937
+ const src = readProjectFiles(ctx, /\.(jsx?|tsx?)$/i, 20);
4938
+ return /cloudfront|cloudflare|fastly|cdn/i.test(config + src) ||
4939
+ (ctx.files.includes('vercel.json') && /headers/i.test(ctx.fileContent('vercel.json') || ''));
4940
+ },
4941
+ impact: 'medium',
4942
+ category: 'caching',
4943
+ fix: 'Configure a CDN (CloudFront, Cloudflare, Fastly) for static asset delivery and edge caching.',
4944
+ confidence: 0.7,
4945
+ },
4946
+
4947
+ cacheHeaders: {
4948
+ id: 130123,
4949
+ name: 'Cache-Control headers configured',
4950
+ check: (ctx) => {
4951
+ const src = readProjectFiles(ctx, /\.(jsx?|tsx?|py|go|java|rb|conf)$/i, 30);
4952
+ return /Cache-Control|max-age|s-maxage|stale-while-revalidate/i.test(src);
4953
+ },
4954
+ impact: 'medium',
4955
+ category: 'caching',
4956
+ fix: 'Set Cache-Control headers (max-age, s-maxage, stale-while-revalidate) for HTTP response caching.',
4957
+ confidence: 0.7,
4958
+ },
4959
+
4960
+ cacheInvalidation: {
4961
+ id: 130124,
4962
+ name: 'Cache invalidation patterns present',
4963
+ check: (ctx) => {
4964
+ const src = readProjectFiles(ctx, /\.(jsx?|tsx?|py|go|java|rb)$/i, 30);
4965
+ return /cache.*purge|cache.*bust|cache.*invalidat|\.del\(|\.flush\(/i.test(src);
4966
+ },
4967
+ impact: 'low',
4968
+ category: 'caching',
4969
+ fix: 'Implement cache invalidation patterns (purge, bust, invalidate) to prevent serving stale data.',
4970
+ confidence: 0.7,
4971
+ },
4972
+
4973
+ httpCaching: {
4974
+ id: 130125,
4975
+ name: 'ETag or Last-Modified caching',
4976
+ check: (ctx) => {
4977
+ const src = readProjectFiles(ctx, /\.(jsx?|tsx?|py|go|java|rb|conf)$/i, 30);
4978
+ return /ETag|Last-Modified|If-None-Match|If-Modified-Since/i.test(src);
4979
+ },
4980
+ impact: 'low',
4981
+ category: 'caching',
4982
+ fix: 'Implement ETag or Last-Modified headers for conditional HTTP caching and bandwidth savings.',
4983
+ confidence: 0.7,
4984
+ },
4985
+
4986
+ // ── MC7: Rate Limiting ──────────────────────────────────────────────
4987
+
4988
+ rateLimitMiddleware: {
4989
+ id: 130131,
4990
+ name: 'Rate limiting middleware configured',
4991
+ check: (ctx) => {
4992
+ const pkg = ctx.fileContent('package.json') || '';
4993
+ if (/express-rate-limit|@nestjs\/throttler|rate-limiter-flexible|koa-ratelimit/i.test(pkg)) return true;
4994
+ const py = readProjectFiles(ctx, /(^|\/)pyproject\.toml$/i) + readProjectFiles(ctx, /(^|\/)requirements[^/]*\.txt$/i);
4995
+ if (/django-ratelimit|slowapi|flask-limiter/i.test(py)) return true;
4996
+ return false;
4997
+ },
4998
+ impact: 'medium',
4999
+ category: 'rate-limiting',
5000
+ fix: 'Add rate limiting middleware (express-rate-limit, @nestjs/throttler, rate-limiter-flexible) to protect APIs.',
5001
+ confidence: 0.7,
5002
+ },
5003
+
5004
+ ddosProtection: {
5005
+ id: 130132,
5006
+ name: 'DDoS protection configured',
5007
+ check: (ctx) => {
5008
+ const pkg = ctx.fileContent('package.json') || '';
5009
+ const config = readProjectFiles(ctx, /\.(json|ya?ml|toml|conf)$/i, 20);
5010
+ return /helmet|cors|cloudflare|waf|ddos/i.test(pkg + config);
5011
+ },
5012
+ impact: 'medium',
5013
+ category: 'rate-limiting',
5014
+ fix: 'Add DDoS protection (helmet, CORS, WAF, Cloudflare) to defend against abuse and volumetric attacks.',
5015
+ confidence: 0.7,
5016
+ },
5017
+
5018
+ backoffStrategy: {
5019
+ id: 130133,
5020
+ name: 'Retry/backoff strategy in dependencies',
5021
+ check: (ctx) => {
5022
+ const pkg = ctx.fileContent('package.json') || '';
5023
+ if (/exponential-backoff|p-retry|async-retry|retry|got.*retry|axios-retry/i.test(pkg)) return true;
5024
+ const py = readProjectFiles(ctx, /(^|\/)pyproject\.toml$/i) + readProjectFiles(ctx, /(^|\/)requirements[^/]*\.txt$/i);
5025
+ if (/tenacity|backoff|urllib3.*retry/i.test(py)) return true;
5026
+ return false;
5027
+ },
5028
+ impact: 'low',
5029
+ category: 'rate-limiting',
5030
+ fix: 'Add a retry/backoff library (p-retry, tenacity, exponential-backoff) for resilient external calls.',
5031
+ confidence: 0.7,
5032
+ },
5033
+
5034
+ requestThrottling: {
5035
+ id: 130134,
5036
+ name: 'Request throttling in dependencies',
5037
+ check: (ctx) => {
5038
+ const pkg = ctx.fileContent('package.json') || '';
5039
+ return /bottleneck|p-throttle|p-limit|throttle/i.test(pkg);
5040
+ },
5041
+ impact: 'low',
5042
+ category: 'rate-limiting',
5043
+ fix: 'Add request throttling (bottleneck, p-throttle) to control outbound API call rates.',
5044
+ confidence: 0.7,
5045
+ },
5046
+
5047
+ rateLimitHeaders: {
5048
+ id: 130135,
5049
+ name: 'Rate limit headers or 429 responses',
5050
+ check: (ctx) => {
5051
+ const src = readProjectFiles(ctx, /\.(jsx?|tsx?|py|go|java|rb)$/i, 30);
5052
+ return /X-RateLimit|RateLimit-|429|Too Many Requests/i.test(src);
5053
+ },
5054
+ impact: 'low',
5055
+ category: 'rate-limiting',
5056
+ fix: 'Return X-RateLimit headers and 429 status codes so clients can handle rate limiting gracefully.',
5057
+ confidence: 0.7,
5058
+ },
5059
+
5060
+ // ── MC8: Feature Flags ──────────────────────────────────────────────
5061
+
5062
+ featureFlagService: {
5063
+ id: 130141,
5064
+ name: 'Feature flag service in dependencies',
5065
+ check: (ctx) => {
5066
+ const pkg = ctx.fileContent('package.json') || '';
5067
+ if (/launchdarkly|unleash|flagsmith|growthbook|@split/i.test(pkg)) return true;
5068
+ const py = readProjectFiles(ctx, /(^|\/)pyproject\.toml$/i) + readProjectFiles(ctx, /(^|\/)requirements[^/]*\.txt$/i);
5069
+ if (/launchdarkly|unleash|flagsmith|growthbook/i.test(py)) return true;
5070
+ return false;
5071
+ },
5072
+ impact: 'medium',
5073
+ category: 'feature-flags',
5074
+ fix: 'Add a feature flag service (LaunchDarkly, Unleash, Flagsmith, GrowthBook) for safe feature rollouts.',
5075
+ confidence: 0.7,
5076
+ },
5077
+
5078
+ featureFlagConfig: {
5079
+ id: 130142,
5080
+ name: 'Feature flag config files exist',
5081
+ check: (ctx) => {
5082
+ return hasProjectFile(ctx, /(^|\/)flags\.json$/i) ||
5083
+ hasProjectFile(ctx, /(^|\/)features\.json$/i) ||
5084
+ hasProjectFile(ctx, /(^|\/)feature-flags\//i);
5085
+ },
5086
+ impact: 'low',
5087
+ category: 'feature-flags',
5088
+ fix: 'Add feature flag configuration files (flags.json, features.json, or feature-flags/ directory).',
5089
+ confidence: 0.7,
5090
+ },
5091
+
5092
+ featureFlagTests: {
5093
+ id: 130143,
5094
+ name: 'Feature flag testing present',
5095
+ check: (ctx) => {
5096
+ const testFiles = readProjectFiles(ctx, /(test|spec)\.(jsx?|tsx?|py|go|java|rb)$/i, 30);
5097
+ return /flag|feature.*toggle|variation/i.test(testFiles);
5098
+ },
5099
+ impact: 'low',
5100
+ category: 'feature-flags',
5101
+ fix: 'Add tests for feature flag variations to verify behavior under different flag states.',
5102
+ confidence: 0.7,
5103
+ },
5104
+
5105
+ flagLifecycle: {
5106
+ id: 130144,
5107
+ name: 'Flag lifecycle management',
5108
+ check: (ctx) => {
5109
+ return hasProjectFile(ctx, /flag-audit|remove-flag|flag.*cleanup/i) ||
5110
+ /flag.*lifecycle|flag.*cleanup|stale.*flag/i.test(readProjectFiles(ctx, /\.(md|txt|json)$/i, 10));
5111
+ },
5112
+ impact: 'low',
5113
+ category: 'feature-flags',
5114
+ fix: 'Add flag lifecycle scripts or docs (flag-audit, remove-flag) to prevent stale flag accumulation.',
5115
+ confidence: 0.7,
5116
+ },
5117
+
5118
+ envBasedFlags: {
5119
+ id: 130145,
5120
+ name: 'Environment-based feature toggles',
5121
+ check: (ctx) => {
5122
+ const envFiles = readProjectFiles(ctx, /(^|\/)(\.env|\.env\.\w+)$/i);
5123
+ const config = readProjectFiles(ctx, /\.(json|ya?ml|toml)$/i, 15);
5124
+ return /FEATURE_|ENABLE_|FF_/i.test(envFiles + config);
5125
+ },
5126
+ impact: 'low',
5127
+ category: 'feature-flags',
5128
+ fix: 'Use environment-based feature toggles (FEATURE_, ENABLE_, FF_ prefixes) for deployment-time configuration.',
5129
+ confidence: 0.7,
5130
+ },
5131
+
5132
+ // ── MC10: Documentation Quality ─────────────────────────────────────
5133
+
5134
+ readmeQuality: {
5135
+ id: 130151,
5136
+ name: 'README has installation, usage, and contributing sections',
5137
+ check: (ctx) => {
5138
+ const readme = ctx.fileContent('README.md') || '';
5139
+ if (!readme) return false;
5140
+ return /install/i.test(readme) && /usage/i.test(readme) && /contribut/i.test(readme);
5141
+ },
5142
+ impact: 'medium',
5143
+ category: 'docs-quality',
5144
+ fix: 'Ensure README.md includes installation, usage, and contributing sections for developer onboarding.',
5145
+ confidence: 0.7,
5146
+ },
5147
+
5148
+ contributingGuide: {
5149
+ id: 130152,
5150
+ name: 'CONTRIBUTING.md exists',
5151
+ check: (ctx) => ctx.files.some(f => /^contributing\.md$/i.test(f)),
5152
+ impact: 'low',
5153
+ category: 'docs-quality',
5154
+ fix: 'Add CONTRIBUTING.md with contribution guidelines, code standards, and PR process.',
5155
+ confidence: 0.7,
5156
+ },
5157
+
5158
+ apiDocsGenerated: {
5159
+ id: 130153,
5160
+ name: 'API documentation generator configured',
5161
+ check: (ctx) => {
5162
+ const pkg = ctx.fileContent('package.json') || '';
5163
+ if (/typedoc|jsdoc|apidoc|compodoc/i.test(pkg)) return true;
5164
+ const py = readProjectFiles(ctx, /(^|\/)pyproject\.toml$/i) + readProjectFiles(ctx, /(^|\/)requirements[^/]*\.txt$/i);
5165
+ if (/sphinx|pdoc|mkdocstrings/i.test(py)) return true;
5166
+ if (isGoProject(ctx) && hasProjectFile(ctx, /(^|\/)doc\.go$/i)) return true;
5167
+ return false;
5168
+ },
5169
+ impact: 'low',
5170
+ category: 'docs-quality',
5171
+ fix: 'Add an API documentation generator (typedoc, jsdoc, sphinx, godoc) for auto-generated docs.',
5172
+ confidence: 0.7,
5173
+ },
5174
+
5175
+ storybookConfigured: {
5176
+ id: 130154,
5177
+ name: 'Storybook configured for component docs',
5178
+ check: (ctx) => {
5179
+ if (!hasFrontendSignals(ctx)) return null;
5180
+ return ctx.hasDir('.storybook') || hasProjectFile(ctx, /(^|\/)\.storybook\//i);
5181
+ },
5182
+ impact: 'low',
5183
+ category: 'docs-quality',
5184
+ fix: 'Add Storybook (.storybook/) for interactive component documentation and visual testing.',
5185
+ confidence: 0.7,
5186
+ },
5187
+
5188
+ codeOfConduct: {
5189
+ id: 130155,
5190
+ name: 'CODE_OF_CONDUCT.md exists',
5191
+ check: (ctx) => ctx.files.some(f => /^code.of.conduct\.md$/i.test(f)),
5192
+ impact: 'low',
5193
+ category: 'docs-quality',
5194
+ fix: 'Add CODE_OF_CONDUCT.md to set community standards and expectations.',
5195
+ confidence: 0.7,
5196
+ },
5197
+
5198
+ licenseDeclared: {
5199
+ id: 130156,
5200
+ name: 'LICENSE file exists',
5201
+ check: (ctx) => ctx.files.some(f => /^license/i.test(f)),
5202
+ impact: 'low',
5203
+ category: 'docs-quality',
5204
+ fix: 'Add a LICENSE file to clarify usage rights and legal terms.',
5205
+ confidence: 0.7,
5206
+ },
5207
+
5208
+ // ── MC13: Monorepo Tooling ──────────────────────────────────────────
5209
+
5210
+ monorepoTool: {
5211
+ id: 130161,
5212
+ name: 'Monorepo tool configured',
5213
+ check: (ctx) => {
5214
+ const pkg = ctx.fileContent('package.json') || '';
5215
+ const hasMonorepo = /workspaces/i.test(pkg) || ctx.files.includes('lerna.json') ||
5216
+ ctx.files.includes('nx.json') || ctx.files.includes('turbo.json') ||
5217
+ hasProjectFile(ctx, /(^|\/)pnpm-workspace\.yaml$/i);
5218
+ if (!hasMonorepo) return null;
5219
+ return /turborepo|turbo|"nx"|lerna|rush|bazel/i.test(pkg) ||
5220
+ ctx.files.includes('nx.json') || ctx.files.includes('turbo.json') ||
5221
+ ctx.files.includes('lerna.json') || ctx.files.includes('rush.json');
5222
+ },
5223
+ impact: 'medium',
5224
+ category: 'monorepo',
5225
+ fix: 'Configure a monorepo orchestration tool (Turborepo, Nx, Lerna, Rush) for efficient multi-package builds.',
5226
+ confidence: 0.7,
5227
+ },
5228
+
5229
+ workspaceDeps: {
5230
+ id: 130162,
5231
+ name: 'Workspace dependency management configured',
5232
+ check: (ctx) => {
5233
+ const pkg = ctx.fileContent('package.json') || '';
5234
+ const hasMonorepo = /workspaces/i.test(pkg) || ctx.files.includes('lerna.json') ||
5235
+ ctx.files.includes('nx.json') || ctx.files.includes('turbo.json') ||
5236
+ hasProjectFile(ctx, /(^|\/)pnpm-workspace\.yaml$/i);
5237
+ if (!hasMonorepo) return null;
5238
+ return hasProjectFile(ctx, /(^|\/)pnpm-workspace\.yaml$/i) || /workspaces/i.test(pkg);
5239
+ },
5240
+ impact: 'medium',
5241
+ category: 'monorepo',
5242
+ fix: 'Configure workspace dependencies (pnpm-workspace.yaml or workspaces in package.json) for cross-package linking.',
5243
+ confidence: 0.7,
5244
+ },
5245
+
5246
+ changesetsConfigured: {
5247
+ id: 130163,
5248
+ name: 'Changesets or conventional commits for versioning',
5249
+ check: (ctx) => {
5250
+ const pkg = ctx.fileContent('package.json') || '';
5251
+ const hasMonorepo = /workspaces/i.test(pkg) || ctx.files.includes('lerna.json') ||
5252
+ ctx.files.includes('nx.json') || ctx.files.includes('turbo.json') ||
5253
+ hasProjectFile(ctx, /(^|\/)pnpm-workspace\.yaml$/i);
5254
+ if (!hasMonorepo) return null;
5255
+ return /@changesets\/cli|changeset/i.test(pkg) || ctx.hasDir('.changeset') ||
5256
+ hasProjectFile(ctx, /(^|\/)\.changeset\//i);
5257
+ },
5258
+ impact: 'low',
5259
+ category: 'monorepo',
5260
+ fix: 'Add @changesets/cli or conventional commits for coordinated versioning across packages.',
5261
+ confidence: 0.7,
5262
+ },
5263
+
5264
+ monorepoCI: {
5265
+ id: 130164,
5266
+ name: 'CI uses affected/changed detection',
5267
+ check: (ctx) => {
5268
+ const pkg = ctx.fileContent('package.json') || '';
5269
+ const hasMonorepo = /workspaces/i.test(pkg) || ctx.files.includes('lerna.json') ||
5270
+ ctx.files.includes('nx.json') || ctx.files.includes('turbo.json') ||
5271
+ hasProjectFile(ctx, /(^|\/)pnpm-workspace\.yaml$/i);
5272
+ if (!hasMonorepo) return null;
5273
+ const ci = getWorkflowContent(ctx);
5274
+ return /nx affected|turbo.*--filter|lerna changed|lerna run.*--since/i.test(ci);
5275
+ },
5276
+ impact: 'medium',
5277
+ category: 'monorepo',
5278
+ fix: 'Use affected/changed detection in CI (nx affected, turbo --filter) to only build what changed.',
5279
+ confidence: 0.7,
5280
+ },
5281
+
5282
+ sharedConfigs: {
5283
+ id: 130165,
5284
+ name: 'Shared configs across packages',
5285
+ check: (ctx) => {
5286
+ const pkg = ctx.fileContent('package.json') || '';
5287
+ const hasMonorepo = /workspaces/i.test(pkg) || ctx.files.includes('lerna.json') ||
5288
+ ctx.files.includes('nx.json') || ctx.files.includes('turbo.json') ||
5289
+ hasProjectFile(ctx, /(^|\/)pnpm-workspace\.yaml$/i);
5290
+ if (!hasMonorepo) return null;
5291
+ return hasProjectFile(ctx, /(^|\/)packages\/.*eslint/i) ||
5292
+ hasProjectFile(ctx, /(^|\/)packages\/.*tsconfig/i) ||
5293
+ hasProjectFile(ctx, /shared.*config/i);
5294
+ },
5295
+ impact: 'low',
5296
+ category: 'monorepo',
5297
+ fix: 'Create shared config packages (eslint, tsconfig) referenced across monorepo packages for consistency.',
5298
+ confidence: 0.7,
5299
+ },
5300
+
5301
+ // ── MC14: Performance Budget ────────────────────────────────────────
5302
+
5303
+ lighthouseCI: {
5304
+ id: 130171,
5305
+ name: 'Lighthouse CI configured',
5306
+ check: (ctx) => {
5307
+ if (!hasFrontendSignals(ctx)) return null;
5308
+ return hasProjectFile(ctx, /(^|\/)\.?lighthouserc\.(js|json|ya?ml)$/i) ||
5309
+ /lighthouse/i.test(getWorkflowContent(ctx));
5310
+ },
5311
+ impact: 'medium',
5312
+ category: 'performance-budget',
5313
+ fix: 'Add Lighthouse CI (lighthouserc.js) to enforce performance budgets in your CI pipeline.',
5314
+ confidence: 0.7,
5315
+ },
5316
+
5317
+ bundleSizeLimit: {
5318
+ id: 130172,
5319
+ name: 'Bundle size check configured',
5320
+ check: (ctx) => {
5321
+ if (!hasFrontendSignals(ctx)) return null;
5322
+ const pkg = ctx.fileContent('package.json') || '';
5323
+ return /size-limit|bundlewatch|@next\/bundle-analyzer|webpack-bundle-analyzer/i.test(pkg);
5324
+ },
5325
+ impact: 'medium',
5326
+ category: 'performance-budget',
5327
+ fix: 'Add bundle size checks (size-limit, bundlewatch, @next/bundle-analyzer) to prevent bundle bloat.',
5328
+ confidence: 0.7,
5329
+ },
5330
+
5331
+ webVitals: {
5332
+ id: 130173,
5333
+ name: 'Core Web Vitals tracking configured',
5334
+ check: (ctx) => {
5335
+ if (!hasFrontendSignals(ctx)) return null;
5336
+ const pkg = ctx.fileContent('package.json') || '';
5337
+ const src = readProjectFiles(ctx, /\.(jsx?|tsx?)$/i, 20);
5338
+ return /web-vitals|next\/web-vitals|@vercel\/speed-insights/i.test(pkg + src);
5339
+ },
5340
+ impact: 'medium',
5341
+ category: 'performance-budget',
5342
+ fix: 'Add Core Web Vitals tracking (web-vitals, next/web-vitals) for real user performance monitoring.',
5343
+ confidence: 0.7,
5344
+ },
5345
+
5346
+ performanceRegression: {
5347
+ id: 130174,
5348
+ name: 'Performance regression testing in CI',
5349
+ check: (ctx) => {
5350
+ if (!hasFrontendSignals(ctx)) return null;
5351
+ const ci = getWorkflowContent(ctx);
5352
+ const pkg = ctx.fileContent('package.json') || '';
5353
+ return /benchmark|bench|perf.*test|lighthouse.*assert/i.test(ci + pkg);
5354
+ },
5355
+ impact: 'low',
5356
+ category: 'performance-budget',
5357
+ fix: 'Add performance regression testing in CI (benchmark, lighthouse assert) to catch regressions early.',
5358
+ confidence: 0.7,
5359
+ },
5360
+
5361
+ imageOptimization: {
5362
+ id: 130175,
5363
+ name: 'Image optimization configured',
5364
+ check: (ctx) => {
5365
+ if (!hasFrontendSignals(ctx)) return null;
5366
+ const pkg = ctx.fileContent('package.json') || '';
5367
+ return /sharp|imagemin|next\/image|responsive-loader|@squoosh/i.test(pkg);
5368
+ },
5369
+ impact: 'low',
5370
+ category: 'performance-budget',
5371
+ fix: 'Add image optimization (sharp, imagemin, next/image) to reduce page weight and improve loading.',
5372
+ confidence: 0.7,
5373
+ },
5374
+
5375
+ lazyLoading: {
5376
+ id: 130176,
5377
+ name: 'Code splitting and lazy loading',
5378
+ check: (ctx) => {
5379
+ if (!hasFrontendSignals(ctx)) return null;
5380
+ const src = readProjectFiles(ctx, /\.(jsx?|tsx?)$/i, 30);
5381
+ return /React\.lazy|import\s*\(|loadable|dynamic\s*\(\s*\(\)\s*=>/i.test(src);
5382
+ },
5383
+ impact: 'low',
5384
+ category: 'performance-budget',
5385
+ fix: 'Use code splitting and lazy loading (React.lazy, dynamic import, loadable) to reduce initial bundle size.',
5386
+ confidence: 0.7,
5387
+ },
5388
+
4117
5389
 
4118
5390
  };
4119
5391