@vibecheckai/cli 3.5.0 → 3.5.2
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/bin/registry.js +214 -237
- package/bin/runners/cli-utils.js +33 -2
- package/bin/runners/context/analyzer.js +52 -1
- package/bin/runners/context/generators/cursor.js +2 -49
- package/bin/runners/context/git-context.js +3 -1
- package/bin/runners/context/team-conventions.js +33 -7
- package/bin/runners/lib/analysis-core.js +25 -5
- package/bin/runners/lib/analyzers.js +431 -481
- package/bin/runners/lib/default-config.js +127 -0
- package/bin/runners/lib/doctor/modules/security.js +3 -1
- package/bin/runners/lib/engine/ast-cache.js +210 -0
- package/bin/runners/lib/engine/auth-extractor.js +211 -0
- package/bin/runners/lib/engine/billing-extractor.js +112 -0
- package/bin/runners/lib/engine/enforcement-extractor.js +100 -0
- package/bin/runners/lib/engine/env-extractor.js +207 -0
- package/bin/runners/lib/engine/express-extractor.js +208 -0
- package/bin/runners/lib/engine/extractors.js +849 -0
- package/bin/runners/lib/engine/index.js +207 -0
- package/bin/runners/lib/engine/repo-index.js +514 -0
- package/bin/runners/lib/engine/types.js +124 -0
- package/bin/runners/lib/engines/accessibility-engine.js +18 -218
- package/bin/runners/lib/engines/api-consistency-engine.js +30 -335
- package/bin/runners/lib/engines/cross-file-analysis-engine.js +27 -292
- package/bin/runners/lib/engines/empty-catch-engine.js +17 -127
- package/bin/runners/lib/engines/mock-data-engine.js +10 -53
- package/bin/runners/lib/engines/performance-issues-engine.js +36 -176
- package/bin/runners/lib/engines/security-vulnerabilities-engine.js +54 -382
- package/bin/runners/lib/engines/type-aware-engine.js +39 -263
- package/bin/runners/lib/engines/vibecheck-engines/index.js +13 -122
- package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +291 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +83 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +275 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +167 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +73 -373
- package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +140 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +234 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +78 -0
- package/bin/runners/lib/entitlements-v2.js +73 -97
- package/bin/runners/lib/error-handler.js +44 -3
- package/bin/runners/lib/error-messages.js +289 -0
- package/bin/runners/lib/evidence-pack.js +7 -1
- package/bin/runners/lib/finding-id.js +69 -0
- package/bin/runners/lib/finding-sorter.js +89 -0
- package/bin/runners/lib/html-proof-report.js +700 -350
- package/bin/runners/lib/missions/plan.js +6 -46
- package/bin/runners/lib/missions/templates.js +0 -232
- package/bin/runners/lib/next-action.js +560 -0
- package/bin/runners/lib/prerequisites.js +149 -0
- package/bin/runners/lib/route-detection.js +137 -68
- package/bin/runners/lib/scan-output.js +91 -76
- package/bin/runners/lib/scan-runner.js +135 -0
- package/bin/runners/lib/schemas/ajv-validator.js +464 -0
- package/bin/runners/lib/schemas/error-envelope.schema.json +105 -0
- package/bin/runners/lib/schemas/finding-v3.schema.json +151 -0
- package/bin/runners/lib/schemas/report-artifact.schema.json +120 -0
- package/bin/runners/lib/schemas/run-request.schema.json +108 -0
- package/bin/runners/lib/schemas/validator.js +27 -0
- package/bin/runners/lib/schemas/verdict.schema.json +140 -0
- package/bin/runners/lib/ship-output-enterprise.js +23 -23
- package/bin/runners/lib/ship-output.js +75 -31
- package/bin/runners/lib/terminal-ui.js +6 -113
- package/bin/runners/lib/truth.js +351 -10
- package/bin/runners/lib/unified-cli-output.js +430 -603
- package/bin/runners/lib/unified-output.js +13 -9
- package/bin/runners/runAIAgent.js +10 -5
- package/bin/runners/runAgent.js +0 -3
- package/bin/runners/runAllowlist.js +389 -0
- package/bin/runners/runApprove.js +0 -33
- package/bin/runners/runAuth.js +73 -45
- package/bin/runners/runCheckpoint.js +51 -11
- package/bin/runners/runClassify.js +85 -21
- package/bin/runners/runContext.js +0 -3
- package/bin/runners/runDoctor.js +41 -28
- package/bin/runners/runEvidencePack.js +362 -0
- package/bin/runners/runFirewall.js +0 -3
- package/bin/runners/runFirewallHook.js +0 -3
- package/bin/runners/runFix.js +66 -76
- package/bin/runners/runGuard.js +18 -411
- package/bin/runners/runInit.js +113 -30
- package/bin/runners/runLabs.js +424 -0
- package/bin/runners/runMcp.js +19 -25
- package/bin/runners/runPolish.js +64 -240
- package/bin/runners/runPromptFirewall.js +12 -5
- package/bin/runners/runProve.js +57 -22
- package/bin/runners/runQuickstart.js +531 -0
- package/bin/runners/runReality.js +59 -68
- package/bin/runners/runReport.js +38 -33
- package/bin/runners/runRuntime.js +8 -5
- package/bin/runners/runScan.js +1413 -190
- package/bin/runners/runShip.js +113 -719
- package/bin/runners/runTruth.js +0 -3
- package/bin/runners/runValidate.js +13 -9
- package/bin/runners/runWatch.js +23 -14
- package/bin/scan.js +6 -1
- package/bin/vibecheck.js +204 -185
- package/mcp-server/deprecation-middleware.js +282 -0
- package/mcp-server/handlers/index.ts +15 -0
- package/mcp-server/handlers/tool-handler.ts +554 -0
- package/mcp-server/index-v1.js +698 -0
- package/mcp-server/index.js +210 -238
- package/mcp-server/lib/cache-wrapper.cjs +383 -0
- package/mcp-server/lib/error-envelope.js +138 -0
- package/mcp-server/lib/executor.ts +499 -0
- package/mcp-server/lib/index.ts +19 -0
- package/mcp-server/lib/rate-limiter.js +166 -0
- package/mcp-server/lib/sandbox.test.ts +519 -0
- package/mcp-server/lib/sandbox.ts +395 -0
- package/mcp-server/lib/types.ts +267 -0
- package/mcp-server/package.json +12 -3
- package/mcp-server/registry/tool-registry.js +794 -0
- package/mcp-server/registry/tools.json +605 -0
- package/mcp-server/registry.test.ts +334 -0
- package/mcp-server/tests/tier-gating.test.js +297 -0
- package/mcp-server/tier-auth.js +378 -45
- package/mcp-server/tools-v3.js +353 -442
- package/mcp-server/tsconfig.json +37 -0
- package/mcp-server/vibecheck-2.0-tools.js +14 -1
- package/package.json +1 -1
- package/bin/runners/lib/agent-firewall/learning/learning-engine.js +0 -849
- package/bin/runners/lib/audit-logger.js +0 -532
- package/bin/runners/lib/authority/authorities/architecture.js +0 -364
- package/bin/runners/lib/authority/authorities/compliance.js +0 -341
- package/bin/runners/lib/authority/authorities/human.js +0 -343
- package/bin/runners/lib/authority/authorities/quality.js +0 -420
- package/bin/runners/lib/authority/authorities/security.js +0 -228
- package/bin/runners/lib/authority/index.js +0 -293
- package/bin/runners/lib/bundle/bundle-intelligence.js +0 -846
- package/bin/runners/lib/cli-charts.js +0 -368
- package/bin/runners/lib/cli-config-display.js +0 -405
- package/bin/runners/lib/cli-demo.js +0 -275
- package/bin/runners/lib/cli-errors.js +0 -438
- package/bin/runners/lib/cli-help-formatter.js +0 -439
- package/bin/runners/lib/cli-interactive-menu.js +0 -509
- package/bin/runners/lib/cli-prompts.js +0 -441
- package/bin/runners/lib/cli-scan-cards.js +0 -362
- package/bin/runners/lib/compliance-reporter.js +0 -710
- package/bin/runners/lib/conductor/index.js +0 -671
- package/bin/runners/lib/easy/README.md +0 -123
- package/bin/runners/lib/easy/index.js +0 -140
- package/bin/runners/lib/easy/interactive-wizard.js +0 -788
- package/bin/runners/lib/easy/one-click-firewall.js +0 -564
- package/bin/runners/lib/easy/zero-config-reality.js +0 -714
- package/bin/runners/lib/engines/async-patterns-engine.js +0 -444
- package/bin/runners/lib/engines/bundle-size-engine.js +0 -433
- package/bin/runners/lib/engines/confidence-scoring.js +0 -276
- package/bin/runners/lib/engines/context-detection.js +0 -264
- package/bin/runners/lib/engines/database-patterns-engine.js +0 -429
- package/bin/runners/lib/engines/duplicate-code-engine.js +0 -354
- package/bin/runners/lib/engines/env-variables-engine.js +0 -458
- package/bin/runners/lib/engines/error-handling-engine.js +0 -437
- package/bin/runners/lib/engines/false-positive-prevention.js +0 -630
- package/bin/runners/lib/engines/framework-adapters/index.js +0 -607
- package/bin/runners/lib/engines/framework-detection.js +0 -508
- package/bin/runners/lib/engines/import-order-engine.js +0 -429
- package/bin/runners/lib/engines/naming-conventions-engine.js +0 -544
- package/bin/runners/lib/engines/noise-reduction-engine.js +0 -452
- package/bin/runners/lib/engines/orchestrator.js +0 -334
- package/bin/runners/lib/engines/react-patterns-engine.js +0 -457
- package/bin/runners/lib/engines/vibecheck-engines/lib/ai-hallucination-engine.js +0 -806
- package/bin/runners/lib/engines/vibecheck-engines/lib/smart-fix-engine.js +0 -577
- package/bin/runners/lib/engines/vibecheck-engines/lib/vibe-score-engine.js +0 -543
- package/bin/runners/lib/engines/vibecheck-engines.js +0 -514
- package/bin/runners/lib/enhanced-features/index.js +0 -305
- package/bin/runners/lib/enhanced-output.js +0 -631
- package/bin/runners/lib/enterprise.js +0 -300
- package/bin/runners/lib/firewall/command-validator.js +0 -351
- package/bin/runners/lib/firewall/config.js +0 -341
- package/bin/runners/lib/firewall/content-validator.js +0 -519
- package/bin/runners/lib/firewall/index.js +0 -101
- package/bin/runners/lib/firewall/path-validator.js +0 -256
- package/bin/runners/lib/intelligence/cross-repo-intelligence.js +0 -817
- package/bin/runners/lib/mcp-utils.js +0 -425
- package/bin/runners/lib/output/index.js +0 -1022
- package/bin/runners/lib/policy-engine.js +0 -652
- package/bin/runners/lib/polish/autofix/accessibility-fixes.js +0 -333
- package/bin/runners/lib/polish/autofix/async-handlers.js +0 -273
- package/bin/runners/lib/polish/autofix/dead-code.js +0 -280
- package/bin/runners/lib/polish/autofix/imports-optimizer.js +0 -344
- package/bin/runners/lib/polish/autofix/index.js +0 -200
- package/bin/runners/lib/polish/autofix/remove-consoles.js +0 -209
- package/bin/runners/lib/polish/autofix/strengthen-types.js +0 -245
- package/bin/runners/lib/polish/backend-checks.js +0 -148
- package/bin/runners/lib/polish/documentation-checks.js +0 -111
- package/bin/runners/lib/polish/frontend-checks.js +0 -168
- package/bin/runners/lib/polish/index.js +0 -71
- package/bin/runners/lib/polish/infrastructure-checks.js +0 -131
- package/bin/runners/lib/polish/library-detection.js +0 -175
- package/bin/runners/lib/polish/performance-checks.js +0 -100
- package/bin/runners/lib/polish/security-checks.js +0 -148
- package/bin/runners/lib/polish/utils.js +0 -203
- package/bin/runners/lib/prompt-builder.js +0 -540
- package/bin/runners/lib/proof-certificate.js +0 -634
- package/bin/runners/lib/reality/accessibility-audit.js +0 -946
- package/bin/runners/lib/reality/api-contract-validator.js +0 -1012
- package/bin/runners/lib/reality/chaos-engineering.js +0 -1084
- package/bin/runners/lib/reality/performance-tracker.js +0 -1077
- package/bin/runners/lib/reality/scenario-generator.js +0 -1404
- package/bin/runners/lib/reality/visual-regression.js +0 -852
- package/bin/runners/lib/reality-profiler.js +0 -717
- package/bin/runners/lib/replay/flight-recorder-viewer.js +0 -1160
- package/bin/runners/lib/review/ai-code-review.js +0 -832
- package/bin/runners/lib/rules/custom-rule-engine.js +0 -985
- package/bin/runners/lib/sbom-generator.js +0 -641
- package/bin/runners/lib/scan-output-enhanced.js +0 -512
- package/bin/runners/lib/security/owasp-scanner.js +0 -939
- package/bin/runners/lib/validators/contract-validator.js +0 -283
- package/bin/runners/lib/validators/dead-export-detector.js +0 -279
- package/bin/runners/lib/validators/dep-audit.js +0 -245
- package/bin/runners/lib/validators/env-validator.js +0 -319
- package/bin/runners/lib/validators/index.js +0 -120
- package/bin/runners/lib/validators/license-checker.js +0 -252
- package/bin/runners/lib/validators/route-validator.js +0 -290
- package/bin/runners/runAuthority.js +0 -528
- package/bin/runners/runConductor.js +0 -772
- package/bin/runners/runContainer.js +0 -366
- package/bin/runners/runEasy.js +0 -410
- package/bin/runners/runIaC.js +0 -372
- package/bin/runners/runVibe.js +0 -791
- package/mcp-server/tools.js +0 -495
|
@@ -1,985 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Custom Rule DSL Engine
|
|
3
|
-
*
|
|
4
|
-
* ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
-
* COMPETITIVE MOAT FEATURE - User-Defined Detection Rules
|
|
6
|
-
* ═══════════════════════════════════════════════════════════════════════════════
|
|
7
|
-
*
|
|
8
|
-
* This engine allows users to create custom detection rules using a simple DSL.
|
|
9
|
-
* Rules can be shared with the community via a marketplace.
|
|
10
|
-
*
|
|
11
|
-
* Features:
|
|
12
|
-
* - Simple, readable rule syntax
|
|
13
|
-
* - Pattern matching (regex, glob, AST)
|
|
14
|
-
* - Context-aware conditions
|
|
15
|
-
* - Custom fix suggestions
|
|
16
|
-
* - Rule composition (extend/override)
|
|
17
|
-
* - Marketplace publishing/installing
|
|
18
|
-
* - Team rule sharing
|
|
19
|
-
*
|
|
20
|
-
* Example rule:
|
|
21
|
-
* ```yaml
|
|
22
|
-
* name: no-console-in-api
|
|
23
|
-
* description: Console statements should not be in API routes
|
|
24
|
-
* severity: WARN
|
|
25
|
-
* match:
|
|
26
|
-
* file: "api/**\/*.ts"
|
|
27
|
-
* pattern: "console\\.(log|warn|error)"
|
|
28
|
-
* exclude:
|
|
29
|
-
* - "**\/*.test.ts"
|
|
30
|
-
* fix:
|
|
31
|
-
* suggestion: "Use a proper logger instead of console"
|
|
32
|
-
* autofix: "logger.$1"
|
|
33
|
-
* ```
|
|
34
|
-
*/
|
|
35
|
-
|
|
36
|
-
"use strict";
|
|
37
|
-
|
|
38
|
-
const fs = require("fs");
|
|
39
|
-
const path = require("path");
|
|
40
|
-
const crypto = require("crypto");
|
|
41
|
-
|
|
42
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
43
|
-
// RULE SCHEMA
|
|
44
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
45
|
-
|
|
46
|
-
const RULE_SCHEMA = {
|
|
47
|
-
// Required fields
|
|
48
|
-
name: { type: "string", required: true },
|
|
49
|
-
description: { type: "string", required: true },
|
|
50
|
-
severity: { type: "enum", values: ["BLOCK", "WARN", "INFO"], default: "WARN" },
|
|
51
|
-
|
|
52
|
-
// Match conditions (at least one required)
|
|
53
|
-
match: {
|
|
54
|
-
type: "object",
|
|
55
|
-
properties: {
|
|
56
|
-
file: { type: "glob" }, // File pattern to match
|
|
57
|
-
pattern: { type: "regex" }, // Code pattern to match
|
|
58
|
-
ast: { type: "ast-query" }, // AST selector
|
|
59
|
-
content: { type: "string" }, // Literal content match
|
|
60
|
-
import: { type: "string" }, // Import statement match
|
|
61
|
-
call: { type: "string" }, // Function call match
|
|
62
|
-
comment: { type: "regex" } // Comment pattern match
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
|
|
66
|
-
// Exclude conditions
|
|
67
|
-
exclude: {
|
|
68
|
-
type: "array",
|
|
69
|
-
items: { type: "glob" }
|
|
70
|
-
},
|
|
71
|
-
|
|
72
|
-
// Context conditions
|
|
73
|
-
context: {
|
|
74
|
-
type: "object",
|
|
75
|
-
properties: {
|
|
76
|
-
framework: { type: "array" }, // Only apply for these frameworks
|
|
77
|
-
language: { type: "array" }, // Only apply for these languages
|
|
78
|
-
env: { type: "string" }, // Only in production/development
|
|
79
|
-
inside: { type: "string" }, // Must be inside (function, class, etc.)
|
|
80
|
-
notInside: { type: "string" } // Must NOT be inside
|
|
81
|
-
}
|
|
82
|
-
},
|
|
83
|
-
|
|
84
|
-
// Fix suggestion
|
|
85
|
-
fix: {
|
|
86
|
-
type: "object",
|
|
87
|
-
properties: {
|
|
88
|
-
suggestion: { type: "string" }, // Human-readable fix suggestion
|
|
89
|
-
autofix: { type: "string" }, // Autofix replacement pattern
|
|
90
|
-
dangerous: { type: "boolean" } // Is autofix potentially dangerous?
|
|
91
|
-
}
|
|
92
|
-
},
|
|
93
|
-
|
|
94
|
-
// Metadata
|
|
95
|
-
meta: {
|
|
96
|
-
type: "object",
|
|
97
|
-
properties: {
|
|
98
|
-
author: { type: "string" },
|
|
99
|
-
version: { type: "string" },
|
|
100
|
-
tags: { type: "array" },
|
|
101
|
-
docs: { type: "string" }
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
107
|
-
// RULE PARSER
|
|
108
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
109
|
-
|
|
110
|
-
class RuleParser {
|
|
111
|
-
/**
|
|
112
|
-
* Parse a rule definition
|
|
113
|
-
*/
|
|
114
|
-
static parse(ruleDefinition) {
|
|
115
|
-
// Support YAML-like format or JSON
|
|
116
|
-
let rule;
|
|
117
|
-
|
|
118
|
-
if (typeof ruleDefinition === "string") {
|
|
119
|
-
// Try to parse as simplified DSL
|
|
120
|
-
rule = this.parseDSL(ruleDefinition);
|
|
121
|
-
} else {
|
|
122
|
-
rule = ruleDefinition;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Validate required fields
|
|
126
|
-
if (!rule.name) {
|
|
127
|
-
throw new Error("Rule must have a name");
|
|
128
|
-
}
|
|
129
|
-
if (!rule.description) {
|
|
130
|
-
throw new Error("Rule must have a description");
|
|
131
|
-
}
|
|
132
|
-
if (!rule.match) {
|
|
133
|
-
throw new Error("Rule must have at least one match condition");
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Apply defaults
|
|
137
|
-
rule.severity = rule.severity || "WARN";
|
|
138
|
-
rule.exclude = rule.exclude || [];
|
|
139
|
-
rule.context = rule.context || {};
|
|
140
|
-
rule.fix = rule.fix || {};
|
|
141
|
-
rule.meta = rule.meta || {};
|
|
142
|
-
|
|
143
|
-
// Compile patterns
|
|
144
|
-
rule._compiled = this.compileRule(rule);
|
|
145
|
-
|
|
146
|
-
return rule;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Parse simplified DSL format
|
|
151
|
-
*/
|
|
152
|
-
static parseDSL(dsl) {
|
|
153
|
-
const lines = dsl.split("\n");
|
|
154
|
-
const rule = {};
|
|
155
|
-
let currentSection = null;
|
|
156
|
-
let currentIndent = 0;
|
|
157
|
-
|
|
158
|
-
for (const line of lines) {
|
|
159
|
-
const trimmed = line.trim();
|
|
160
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
161
|
-
|
|
162
|
-
const indent = line.search(/\S/);
|
|
163
|
-
const keyMatch = trimmed.match(/^(\w+):\s*(.*)$/);
|
|
164
|
-
|
|
165
|
-
if (keyMatch) {
|
|
166
|
-
const [, key, value] = keyMatch;
|
|
167
|
-
|
|
168
|
-
if (indent === 0) {
|
|
169
|
-
// Top-level key
|
|
170
|
-
if (value) {
|
|
171
|
-
rule[key] = this.parseValue(value);
|
|
172
|
-
} else {
|
|
173
|
-
rule[key] = {};
|
|
174
|
-
currentSection = key;
|
|
175
|
-
}
|
|
176
|
-
currentIndent = indent;
|
|
177
|
-
} else if (currentSection) {
|
|
178
|
-
// Nested key
|
|
179
|
-
if (typeof rule[currentSection] !== "object") {
|
|
180
|
-
rule[currentSection] = {};
|
|
181
|
-
}
|
|
182
|
-
rule[currentSection][key] = this.parseValue(value);
|
|
183
|
-
}
|
|
184
|
-
} else if (trimmed.startsWith("-")) {
|
|
185
|
-
// Array item
|
|
186
|
-
const itemValue = trimmed.slice(1).trim();
|
|
187
|
-
if (currentSection) {
|
|
188
|
-
if (!Array.isArray(rule[currentSection])) {
|
|
189
|
-
rule[currentSection] = [];
|
|
190
|
-
}
|
|
191
|
-
rule[currentSection].push(this.parseValue(itemValue));
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return rule;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Parse a value (string, number, boolean, etc.)
|
|
201
|
-
*/
|
|
202
|
-
static parseValue(value) {
|
|
203
|
-
if (!value) return null;
|
|
204
|
-
|
|
205
|
-
// Remove quotes
|
|
206
|
-
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
207
|
-
(value.startsWith("'") && value.endsWith("'"))) {
|
|
208
|
-
return value.slice(1, -1);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Boolean
|
|
212
|
-
if (value === "true") return true;
|
|
213
|
-
if (value === "false") return false;
|
|
214
|
-
|
|
215
|
-
// Number
|
|
216
|
-
if (/^\d+$/.test(value)) return parseInt(value, 10);
|
|
217
|
-
if (/^\d+\.\d+$/.test(value)) return parseFloat(value);
|
|
218
|
-
|
|
219
|
-
// Array (inline)
|
|
220
|
-
if (value.startsWith("[") && value.endsWith("]")) {
|
|
221
|
-
return value.slice(1, -1).split(",").map(v => this.parseValue(v.trim()));
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
return value;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Compile rule patterns for efficient matching
|
|
229
|
-
*/
|
|
230
|
-
static compileRule(rule) {
|
|
231
|
-
const compiled = {
|
|
232
|
-
filePattern: null,
|
|
233
|
-
codePattern: null,
|
|
234
|
-
excludePatterns: [],
|
|
235
|
-
astQuery: null
|
|
236
|
-
};
|
|
237
|
-
|
|
238
|
-
// Compile file glob
|
|
239
|
-
if (rule.match.file) {
|
|
240
|
-
compiled.filePattern = this.globToRegex(rule.match.file);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// Compile code pattern
|
|
244
|
-
if (rule.match.pattern) {
|
|
245
|
-
try {
|
|
246
|
-
compiled.codePattern = new RegExp(rule.match.pattern, "gm");
|
|
247
|
-
} catch {
|
|
248
|
-
throw new Error(`Invalid regex pattern: ${rule.match.pattern}`);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// Compile exclude patterns
|
|
253
|
-
for (const exclude of rule.exclude) {
|
|
254
|
-
compiled.excludePatterns.push(this.globToRegex(exclude));
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// Parse AST query (simplified)
|
|
258
|
-
if (rule.match.ast) {
|
|
259
|
-
compiled.astQuery = this.parseAstQuery(rule.match.ast);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
return compiled;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Convert glob pattern to regex
|
|
267
|
-
*/
|
|
268
|
-
static globToRegex(glob) {
|
|
269
|
-
const regex = glob
|
|
270
|
-
.replace(/[.+^${}()|[\]\\]/g, "\\$&") // Escape special chars
|
|
271
|
-
.replace(/\*\*/g, "{{GLOBSTAR}}") // Protect **
|
|
272
|
-
.replace(/\*/g, "[^/]*") // * matches anything except /
|
|
273
|
-
.replace(/\{\{GLOBSTAR\}\}/g, ".*") // ** matches everything
|
|
274
|
-
.replace(/\?/g, "."); // ? matches single char
|
|
275
|
-
|
|
276
|
-
return new RegExp(`^${regex}$`);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Parse AST query (simplified CSS-like selector)
|
|
281
|
-
*/
|
|
282
|
-
static parseAstQuery(query) {
|
|
283
|
-
// Simple AST query format: NodeType[attribute=value]
|
|
284
|
-
// Example: CallExpression[callee.name="console"]
|
|
285
|
-
|
|
286
|
-
const match = query.match(/^(\w+)(?:\[([^\]]+)\])?$/);
|
|
287
|
-
if (!match) {
|
|
288
|
-
throw new Error(`Invalid AST query: ${query}`);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
return {
|
|
292
|
-
nodeType: match[1],
|
|
293
|
-
condition: match[2] ? this.parseAstCondition(match[2]) : null
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Parse AST condition
|
|
299
|
-
*/
|
|
300
|
-
static parseAstCondition(condition) {
|
|
301
|
-
const match = condition.match(/^([\w.]+)\s*(=|!=|~=)\s*"([^"]+)"$/);
|
|
302
|
-
if (!match) {
|
|
303
|
-
throw new Error(`Invalid AST condition: ${condition}`);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
return {
|
|
307
|
-
path: match[1],
|
|
308
|
-
operator: match[2],
|
|
309
|
-
value: match[3]
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
315
|
-
// RULE EXECUTOR
|
|
316
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
317
|
-
|
|
318
|
-
class RuleExecutor {
|
|
319
|
-
constructor(rule) {
|
|
320
|
-
this.rule = rule;
|
|
321
|
-
this.compiled = rule._compiled;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Execute rule against a file
|
|
326
|
-
*/
|
|
327
|
-
execute(fileContext) {
|
|
328
|
-
const { filePath, content, ast, framework, language } = fileContext;
|
|
329
|
-
const findings = [];
|
|
330
|
-
|
|
331
|
-
// Check file pattern
|
|
332
|
-
if (this.compiled.filePattern && !this.compiled.filePattern.test(filePath)) {
|
|
333
|
-
return findings; // File doesn't match
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
// Check excludes
|
|
337
|
-
for (const excludePattern of this.compiled.excludePatterns) {
|
|
338
|
-
if (excludePattern.test(filePath)) {
|
|
339
|
-
return findings; // File is excluded
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Check context conditions
|
|
344
|
-
if (!this.checkContext(fileContext)) {
|
|
345
|
-
return findings;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// Execute pattern match
|
|
349
|
-
if (this.compiled.codePattern) {
|
|
350
|
-
const matches = this.findPatternMatches(content);
|
|
351
|
-
for (const match of matches) {
|
|
352
|
-
findings.push(this.createFinding(match, fileContext));
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// Execute AST match
|
|
357
|
-
if (this.compiled.astQuery && ast) {
|
|
358
|
-
const matches = this.findAstMatches(ast);
|
|
359
|
-
for (const match of matches) {
|
|
360
|
-
findings.push(this.createFinding(match, fileContext));
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// Execute content match
|
|
365
|
-
if (this.rule.match.content) {
|
|
366
|
-
if (content.includes(this.rule.match.content)) {
|
|
367
|
-
const index = content.indexOf(this.rule.match.content);
|
|
368
|
-
const line = content.substring(0, index).split("\n").length;
|
|
369
|
-
findings.push(this.createFinding({
|
|
370
|
-
match: this.rule.match.content,
|
|
371
|
-
line,
|
|
372
|
-
column: 0
|
|
373
|
-
}, fileContext));
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
// Execute import match
|
|
378
|
-
if (this.rule.match.import) {
|
|
379
|
-
const importMatches = this.findImportMatches(content);
|
|
380
|
-
for (const match of importMatches) {
|
|
381
|
-
findings.push(this.createFinding(match, fileContext));
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// Execute call match
|
|
386
|
-
if (this.rule.match.call) {
|
|
387
|
-
const callMatches = this.findCallMatches(content);
|
|
388
|
-
for (const match of callMatches) {
|
|
389
|
-
findings.push(this.createFinding(match, fileContext));
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
return findings;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
/**
|
|
397
|
-
* Check context conditions
|
|
398
|
-
*/
|
|
399
|
-
checkContext(fileContext) {
|
|
400
|
-
const ctx = this.rule.context;
|
|
401
|
-
|
|
402
|
-
if (ctx.framework && ctx.framework.length > 0) {
|
|
403
|
-
if (!ctx.framework.includes(fileContext.framework)) {
|
|
404
|
-
return false;
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
if (ctx.language && ctx.language.length > 0) {
|
|
409
|
-
if (!ctx.language.includes(fileContext.language)) {
|
|
410
|
-
return false;
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
if (ctx.env) {
|
|
415
|
-
const isProduction = process.env.NODE_ENV === "production";
|
|
416
|
-
if (ctx.env === "production" && !isProduction) return false;
|
|
417
|
-
if (ctx.env === "development" && isProduction) return false;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
return true;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
/**
|
|
424
|
-
* Find pattern matches in content
|
|
425
|
-
*/
|
|
426
|
-
findPatternMatches(content) {
|
|
427
|
-
const matches = [];
|
|
428
|
-
const lines = content.split("\n");
|
|
429
|
-
|
|
430
|
-
let match;
|
|
431
|
-
const regex = new RegExp(this.compiled.codePattern.source, "gm");
|
|
432
|
-
|
|
433
|
-
while ((match = regex.exec(content)) !== null) {
|
|
434
|
-
const index = match.index;
|
|
435
|
-
const beforeMatch = content.substring(0, index);
|
|
436
|
-
const line = beforeMatch.split("\n").length;
|
|
437
|
-
const lastNewline = beforeMatch.lastIndexOf("\n");
|
|
438
|
-
const column = index - lastNewline - 1;
|
|
439
|
-
|
|
440
|
-
matches.push({
|
|
441
|
-
match: match[0],
|
|
442
|
-
groups: match.slice(1),
|
|
443
|
-
line,
|
|
444
|
-
column,
|
|
445
|
-
lineContent: lines[line - 1]
|
|
446
|
-
});
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
return matches;
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
/**
|
|
453
|
-
* Find AST matches
|
|
454
|
-
*/
|
|
455
|
-
findAstMatches(ast) {
|
|
456
|
-
const matches = [];
|
|
457
|
-
const query = this.compiled.astQuery;
|
|
458
|
-
|
|
459
|
-
const walk = (node, parent = null) => {
|
|
460
|
-
if (!node || typeof node !== "object") return;
|
|
461
|
-
|
|
462
|
-
if (node.type === query.nodeType) {
|
|
463
|
-
if (!query.condition || this.checkAstCondition(node, query.condition)) {
|
|
464
|
-
matches.push({
|
|
465
|
-
node,
|
|
466
|
-
match: node.type,
|
|
467
|
-
line: node.loc?.start?.line || 0,
|
|
468
|
-
column: node.loc?.start?.column || 0
|
|
469
|
-
});
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// Walk children
|
|
474
|
-
for (const key of Object.keys(node)) {
|
|
475
|
-
const child = node[key];
|
|
476
|
-
if (Array.isArray(child)) {
|
|
477
|
-
for (const item of child) {
|
|
478
|
-
walk(item, node);
|
|
479
|
-
}
|
|
480
|
-
} else if (child && typeof child === "object" && child.type) {
|
|
481
|
-
walk(child, node);
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
};
|
|
485
|
-
|
|
486
|
-
walk(ast);
|
|
487
|
-
return matches;
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
/**
|
|
491
|
-
* Check AST condition
|
|
492
|
-
*/
|
|
493
|
-
checkAstCondition(node, condition) {
|
|
494
|
-
const value = this.getNestedValue(node, condition.path);
|
|
495
|
-
|
|
496
|
-
switch (condition.operator) {
|
|
497
|
-
case "=":
|
|
498
|
-
return value === condition.value;
|
|
499
|
-
case "!=":
|
|
500
|
-
return value !== condition.value;
|
|
501
|
-
case "~=":
|
|
502
|
-
return new RegExp(condition.value).test(value);
|
|
503
|
-
default:
|
|
504
|
-
return false;
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
/**
|
|
509
|
-
* Get nested value from object using dot notation
|
|
510
|
-
*/
|
|
511
|
-
getNestedValue(obj, path) {
|
|
512
|
-
const parts = path.split(".");
|
|
513
|
-
let current = obj;
|
|
514
|
-
|
|
515
|
-
for (const part of parts) {
|
|
516
|
-
if (current === null || current === undefined) return undefined;
|
|
517
|
-
current = current[part];
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
return current;
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
/**
|
|
524
|
-
* Find import matches
|
|
525
|
-
*/
|
|
526
|
-
findImportMatches(content) {
|
|
527
|
-
const matches = [];
|
|
528
|
-
const importPattern = this.rule.match.import;
|
|
529
|
-
const regex = new RegExp(
|
|
530
|
-
`import\\s+(?:{[^}]*}|[\\w*]+)\\s+from\\s+['"]${importPattern}['"]`,
|
|
531
|
-
"gm"
|
|
532
|
-
);
|
|
533
|
-
|
|
534
|
-
let match;
|
|
535
|
-
while ((match = regex.exec(content)) !== null) {
|
|
536
|
-
const line = content.substring(0, match.index).split("\n").length;
|
|
537
|
-
matches.push({
|
|
538
|
-
match: match[0],
|
|
539
|
-
line,
|
|
540
|
-
column: 0
|
|
541
|
-
});
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
return matches;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
/**
|
|
548
|
-
* Find function call matches
|
|
549
|
-
*/
|
|
550
|
-
findCallMatches(content) {
|
|
551
|
-
const matches = [];
|
|
552
|
-
const callPattern = this.rule.match.call;
|
|
553
|
-
const regex = new RegExp(`\\b${callPattern}\\s*\\(`, "gm");
|
|
554
|
-
|
|
555
|
-
let match;
|
|
556
|
-
while ((match = regex.exec(content)) !== null) {
|
|
557
|
-
const line = content.substring(0, match.index).split("\n").length;
|
|
558
|
-
matches.push({
|
|
559
|
-
match: match[0],
|
|
560
|
-
line,
|
|
561
|
-
column: 0
|
|
562
|
-
});
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
return matches;
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
/**
|
|
569
|
-
* Create a finding from a match
|
|
570
|
-
*/
|
|
571
|
-
createFinding(match, fileContext) {
|
|
572
|
-
return {
|
|
573
|
-
rule: this.rule.name,
|
|
574
|
-
type: this.rule.name,
|
|
575
|
-
severity: this.rule.severity,
|
|
576
|
-
message: this.rule.description,
|
|
577
|
-
file: fileContext.filePath,
|
|
578
|
-
line: match.line,
|
|
579
|
-
column: match.column,
|
|
580
|
-
match: match.match,
|
|
581
|
-
lineContent: match.lineContent,
|
|
582
|
-
fix: this.rule.fix.suggestion ? {
|
|
583
|
-
suggestion: this.rule.fix.suggestion,
|
|
584
|
-
autofix: this.rule.fix.autofix,
|
|
585
|
-
dangerous: this.rule.fix.dangerous
|
|
586
|
-
} : null,
|
|
587
|
-
meta: {
|
|
588
|
-
ruleAuthor: this.rule.meta.author,
|
|
589
|
-
ruleVersion: this.rule.meta.version,
|
|
590
|
-
ruleDocs: this.rule.meta.docs
|
|
591
|
-
}
|
|
592
|
-
};
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
597
|
-
// CUSTOM RULE ENGINE
|
|
598
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
599
|
-
|
|
600
|
-
class CustomRuleEngine {
|
|
601
|
-
constructor(options = {}) {
|
|
602
|
-
this.projectRoot = options.projectRoot || process.cwd();
|
|
603
|
-
this.rulesPath = options.rulesPath ||
|
|
604
|
-
path.join(this.projectRoot, ".vibecheck", "rules");
|
|
605
|
-
this.marketplacePath = options.marketplacePath ||
|
|
606
|
-
path.join(process.env.HOME || process.env.USERPROFILE, ".vibecheck", "marketplace-rules");
|
|
607
|
-
this.rules = new Map();
|
|
608
|
-
this.executors = new Map();
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
/**
|
|
612
|
-
* Load all rules
|
|
613
|
-
*/
|
|
614
|
-
async loadRules() {
|
|
615
|
-
// Load project rules
|
|
616
|
-
await this.loadRulesFromDirectory(this.rulesPath, "project");
|
|
617
|
-
|
|
618
|
-
// Load marketplace rules
|
|
619
|
-
await this.loadRulesFromDirectory(this.marketplacePath, "marketplace");
|
|
620
|
-
|
|
621
|
-
return {
|
|
622
|
-
loaded: this.rules.size,
|
|
623
|
-
rules: Array.from(this.rules.keys())
|
|
624
|
-
};
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
/**
|
|
628
|
-
* Load rules from a directory
|
|
629
|
-
*/
|
|
630
|
-
async loadRulesFromDirectory(dir, source) {
|
|
631
|
-
if (!fs.existsSync(dir)) return;
|
|
632
|
-
|
|
633
|
-
const files = fs.readdirSync(dir);
|
|
634
|
-
|
|
635
|
-
for (const file of files) {
|
|
636
|
-
if (!file.endsWith(".json") && !file.endsWith(".yaml") && !file.endsWith(".yml")) {
|
|
637
|
-
continue;
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
const filePath = path.join(dir, file);
|
|
641
|
-
|
|
642
|
-
try {
|
|
643
|
-
const content = fs.readFileSync(filePath, "utf8");
|
|
644
|
-
let ruleData;
|
|
645
|
-
|
|
646
|
-
if (file.endsWith(".json")) {
|
|
647
|
-
ruleData = JSON.parse(content);
|
|
648
|
-
} else {
|
|
649
|
-
// For YAML, use DSL parser
|
|
650
|
-
ruleData = RuleParser.parseDSL(content);
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
// Support single rule or array of rules
|
|
654
|
-
const rulesArray = Array.isArray(ruleData) ? ruleData : [ruleData];
|
|
655
|
-
|
|
656
|
-
for (const ruleDef of rulesArray) {
|
|
657
|
-
const rule = RuleParser.parse(ruleDef);
|
|
658
|
-
rule._source = source;
|
|
659
|
-
rule._file = filePath;
|
|
660
|
-
|
|
661
|
-
this.rules.set(rule.name, rule);
|
|
662
|
-
this.executors.set(rule.name, new RuleExecutor(rule));
|
|
663
|
-
}
|
|
664
|
-
} catch (error) {
|
|
665
|
-
console.error(`Failed to load rule from ${filePath}: ${error.message}`);
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
/**
|
|
671
|
-
* Add a rule programmatically
|
|
672
|
-
*/
|
|
673
|
-
addRule(ruleDefinition) {
|
|
674
|
-
const rule = RuleParser.parse(ruleDefinition);
|
|
675
|
-
rule._source = "programmatic";
|
|
676
|
-
|
|
677
|
-
this.rules.set(rule.name, rule);
|
|
678
|
-
this.executors.set(rule.name, new RuleExecutor(rule));
|
|
679
|
-
|
|
680
|
-
return rule;
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
/**
|
|
684
|
-
* Execute all rules against a file
|
|
685
|
-
*/
|
|
686
|
-
executeRules(fileContext) {
|
|
687
|
-
const findings = [];
|
|
688
|
-
|
|
689
|
-
for (const executor of this.executors.values()) {
|
|
690
|
-
const ruleFindings = executor.execute(fileContext);
|
|
691
|
-
findings.push(...ruleFindings);
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
return findings;
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
/**
|
|
698
|
-
* Execute specific rules
|
|
699
|
-
*/
|
|
700
|
-
executeSpecificRules(fileContext, ruleNames) {
|
|
701
|
-
const findings = [];
|
|
702
|
-
|
|
703
|
-
for (const name of ruleNames) {
|
|
704
|
-
const executor = this.executors.get(name);
|
|
705
|
-
if (executor) {
|
|
706
|
-
const ruleFindings = executor.execute(fileContext);
|
|
707
|
-
findings.push(...ruleFindings);
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
return findings;
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
/**
|
|
715
|
-
* Save a rule to project
|
|
716
|
-
*/
|
|
717
|
-
saveRule(rule, fileName = null) {
|
|
718
|
-
if (!fs.existsSync(this.rulesPath)) {
|
|
719
|
-
fs.mkdirSync(this.rulesPath, { recursive: true });
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
const name = fileName || `${rule.name}.json`;
|
|
723
|
-
const filePath = path.join(this.rulesPath, name);
|
|
724
|
-
|
|
725
|
-
// Remove internal properties
|
|
726
|
-
const ruleToSave = { ...rule };
|
|
727
|
-
delete ruleToSave._compiled;
|
|
728
|
-
delete ruleToSave._source;
|
|
729
|
-
delete ruleToSave._file;
|
|
730
|
-
|
|
731
|
-
fs.writeFileSync(filePath, JSON.stringify(ruleToSave, null, 2), "utf8");
|
|
732
|
-
|
|
733
|
-
return filePath;
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
/**
|
|
737
|
-
* Create rule from template
|
|
738
|
-
*/
|
|
739
|
-
createFromTemplate(template, options = {}) {
|
|
740
|
-
const templates = {
|
|
741
|
-
"no-pattern": {
|
|
742
|
-
name: options.name || "custom-pattern-rule",
|
|
743
|
-
description: options.description || "Custom pattern detection",
|
|
744
|
-
severity: options.severity || "WARN",
|
|
745
|
-
match: {
|
|
746
|
-
file: options.file || "**/*.{ts,tsx,js,jsx}",
|
|
747
|
-
pattern: options.pattern || "TODO"
|
|
748
|
-
},
|
|
749
|
-
exclude: options.exclude || ["**/*.test.*", "**/__tests__/**"],
|
|
750
|
-
fix: {
|
|
751
|
-
suggestion: options.suggestion || "Address this pattern"
|
|
752
|
-
}
|
|
753
|
-
},
|
|
754
|
-
"no-import": {
|
|
755
|
-
name: options.name || "custom-import-rule",
|
|
756
|
-
description: options.description || "Disallow specific import",
|
|
757
|
-
severity: options.severity || "WARN",
|
|
758
|
-
match: {
|
|
759
|
-
file: "**/*.{ts,tsx,js,jsx}",
|
|
760
|
-
import: options.import || "lodash"
|
|
761
|
-
},
|
|
762
|
-
fix: {
|
|
763
|
-
suggestion: options.suggestion || "Use native alternatives"
|
|
764
|
-
}
|
|
765
|
-
},
|
|
766
|
-
"no-call": {
|
|
767
|
-
name: options.name || "custom-call-rule",
|
|
768
|
-
description: options.description || "Disallow specific function call",
|
|
769
|
-
severity: options.severity || "WARN",
|
|
770
|
-
match: {
|
|
771
|
-
file: "**/*.{ts,tsx,js,jsx}",
|
|
772
|
-
call: options.call || "eval"
|
|
773
|
-
},
|
|
774
|
-
fix: {
|
|
775
|
-
suggestion: options.suggestion || "Avoid this function"
|
|
776
|
-
}
|
|
777
|
-
},
|
|
778
|
-
"framework-specific": {
|
|
779
|
-
name: options.name || "framework-rule",
|
|
780
|
-
description: options.description || "Framework-specific rule",
|
|
781
|
-
severity: options.severity || "WARN",
|
|
782
|
-
match: {
|
|
783
|
-
file: options.file || "**/*.{ts,tsx}",
|
|
784
|
-
pattern: options.pattern || ""
|
|
785
|
-
},
|
|
786
|
-
context: {
|
|
787
|
-
framework: options.framework ? [options.framework] : ["react"]
|
|
788
|
-
},
|
|
789
|
-
fix: {
|
|
790
|
-
suggestion: options.suggestion || "Follow framework best practices"
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
};
|
|
794
|
-
|
|
795
|
-
const templateRule = templates[template];
|
|
796
|
-
if (!templateRule) {
|
|
797
|
-
throw new Error(`Unknown template: ${template}`);
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
return RuleParser.parse(templateRule);
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
804
|
-
// MARKETPLACE
|
|
805
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
806
|
-
|
|
807
|
-
/**
|
|
808
|
-
* Publish rule to marketplace (placeholder)
|
|
809
|
-
*/
|
|
810
|
-
async publishRule(ruleName) {
|
|
811
|
-
const rule = this.rules.get(ruleName);
|
|
812
|
-
if (!rule) {
|
|
813
|
-
throw new Error(`Rule not found: ${ruleName}`);
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
// Generate unique ID
|
|
817
|
-
const ruleId = crypto.randomBytes(8).toString("hex");
|
|
818
|
-
|
|
819
|
-
// This would upload to marketplace API
|
|
820
|
-
const published = {
|
|
821
|
-
id: ruleId,
|
|
822
|
-
name: rule.name,
|
|
823
|
-
description: rule.description,
|
|
824
|
-
author: rule.meta?.author || "anonymous",
|
|
825
|
-
version: rule.meta?.version || "1.0.0",
|
|
826
|
-
downloads: 0,
|
|
827
|
-
rating: 0,
|
|
828
|
-
publishedAt: new Date().toISOString()
|
|
829
|
-
};
|
|
830
|
-
|
|
831
|
-
return published;
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
/**
|
|
835
|
-
* Install rule from marketplace (placeholder)
|
|
836
|
-
*/
|
|
837
|
-
async installRule(ruleId) {
|
|
838
|
-
if (!fs.existsSync(this.marketplacePath)) {
|
|
839
|
-
fs.mkdirSync(this.marketplacePath, { recursive: true });
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
// This would fetch from marketplace API
|
|
843
|
-
// For now, return placeholder
|
|
844
|
-
return {
|
|
845
|
-
installed: true,
|
|
846
|
-
ruleId,
|
|
847
|
-
path: this.marketplacePath
|
|
848
|
-
};
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
/**
|
|
852
|
-
* Search marketplace rules (placeholder)
|
|
853
|
-
*/
|
|
854
|
-
async searchMarketplace(query) {
|
|
855
|
-
// This would search marketplace API
|
|
856
|
-
return {
|
|
857
|
-
query,
|
|
858
|
-
results: [],
|
|
859
|
-
total: 0
|
|
860
|
-
};
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
/**
|
|
864
|
-
* Get popular marketplace rules (placeholder)
|
|
865
|
-
*/
|
|
866
|
-
async getPopularRules(limit = 10) {
|
|
867
|
-
// This would fetch from marketplace API
|
|
868
|
-
return {
|
|
869
|
-
rules: [],
|
|
870
|
-
total: 0
|
|
871
|
-
};
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
875
|
-
// UTILITIES
|
|
876
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
877
|
-
|
|
878
|
-
/**
|
|
879
|
-
* List all loaded rules
|
|
880
|
-
*/
|
|
881
|
-
listRules() {
|
|
882
|
-
const rules = [];
|
|
883
|
-
|
|
884
|
-
for (const [name, rule] of this.rules) {
|
|
885
|
-
rules.push({
|
|
886
|
-
name,
|
|
887
|
-
description: rule.description,
|
|
888
|
-
severity: rule.severity,
|
|
889
|
-
source: rule._source,
|
|
890
|
-
file: rule._file
|
|
891
|
-
});
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
return rules;
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
/**
|
|
898
|
-
* Get rule by name
|
|
899
|
-
*/
|
|
900
|
-
getRule(name) {
|
|
901
|
-
return this.rules.get(name);
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
/**
|
|
905
|
-
* Remove rule
|
|
906
|
-
*/
|
|
907
|
-
removeRule(name) {
|
|
908
|
-
const rule = this.rules.get(name);
|
|
909
|
-
|
|
910
|
-
if (rule && rule._file && fs.existsSync(rule._file)) {
|
|
911
|
-
fs.unlinkSync(rule._file);
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
this.rules.delete(name);
|
|
915
|
-
this.executors.delete(name);
|
|
916
|
-
|
|
917
|
-
return true;
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
/**
|
|
921
|
-
* Validate rule definition
|
|
922
|
-
*/
|
|
923
|
-
validateRule(ruleDefinition) {
|
|
924
|
-
const errors = [];
|
|
925
|
-
|
|
926
|
-
try {
|
|
927
|
-
RuleParser.parse(ruleDefinition);
|
|
928
|
-
} catch (error) {
|
|
929
|
-
errors.push(error.message);
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
return {
|
|
933
|
-
valid: errors.length === 0,
|
|
934
|
-
errors
|
|
935
|
-
};
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
/**
|
|
939
|
-
* Export rules for sharing
|
|
940
|
-
*/
|
|
941
|
-
exportRules(ruleNames = null) {
|
|
942
|
-
const toExport = ruleNames
|
|
943
|
-
? ruleNames.map(n => this.rules.get(n)).filter(Boolean)
|
|
944
|
-
: Array.from(this.rules.values());
|
|
945
|
-
|
|
946
|
-
return toExport.map(rule => {
|
|
947
|
-
const exportRule = { ...rule };
|
|
948
|
-
delete exportRule._compiled;
|
|
949
|
-
delete exportRule._source;
|
|
950
|
-
delete exportRule._file;
|
|
951
|
-
return exportRule;
|
|
952
|
-
});
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
/**
|
|
956
|
-
* Import rules from export
|
|
957
|
-
*/
|
|
958
|
-
importRules(rulesData) {
|
|
959
|
-
const imported = [];
|
|
960
|
-
|
|
961
|
-
for (const ruleData of rulesData) {
|
|
962
|
-
try {
|
|
963
|
-
const rule = RuleParser.parse(ruleData);
|
|
964
|
-
this.rules.set(rule.name, rule);
|
|
965
|
-
this.executors.set(rule.name, new RuleExecutor(rule));
|
|
966
|
-
imported.push(rule.name);
|
|
967
|
-
} catch (error) {
|
|
968
|
-
console.error(`Failed to import rule: ${error.message}`);
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
return imported;
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
977
|
-
// EXPORTS
|
|
978
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
979
|
-
|
|
980
|
-
module.exports = {
|
|
981
|
-
CustomRuleEngine,
|
|
982
|
-
RuleParser,
|
|
983
|
-
RuleExecutor,
|
|
984
|
-
RULE_SCHEMA
|
|
985
|
-
};
|