sertivibed 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/scan.js ADDED
@@ -0,0 +1,278 @@
1
+ "use strict";
2
+ /**
3
+ * Sertivibed Scanner
4
+ *
5
+ * Hovedlogikk for å kjøre regler og samle claims.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.RULES = void 0;
9
+ exports.scan = scan;
10
+ const types_1 = require("./types");
11
+ const index_1 = require("./rules/index");
12
+ const rg_1 = require("./utils/rg");
13
+ const fs_1 = require("./utils/fs");
14
+ // ============================================
15
+ // MAIN SCAN FUNCTION
16
+ // ============================================
17
+ async function scan(config) {
18
+ const startTime = Date.now();
19
+ const { targetPath, verbose } = config;
20
+ if (verbose)
21
+ console.log(`🔍 Scanning ${targetPath}...`);
22
+ // Detect project
23
+ const project = (0, fs_1.detectProject)(targetPath);
24
+ if (verbose)
25
+ console.log(`📦 Project: ${project.name} (${project.stack.join(", ")})`);
26
+ // Discover files
27
+ const allFiles = (0, fs_1.discoverFiles)(targetPath);
28
+ if (verbose)
29
+ console.log(`📁 Found ${allFiles.length} files to analyze`);
30
+ // Run all rules
31
+ const claims = [];
32
+ const rulesChecked = [];
33
+ const rulesSkipped = [];
34
+ for (const rule of index_1.RULES) {
35
+ // Skip RLS rules for Prisma projects without Supabase
36
+ // Prisma handles authorization differently (API routes, middleware)
37
+ if (rule.category === "RLS" && project.hasPrisma && !project.hasSupabase) {
38
+ if (verbose)
39
+ console.log(` ⏭️ Skipping ${rule.id}: Prisma project (RLS not applicable)`);
40
+ rulesSkipped.push({
41
+ ruleId: rule.id,
42
+ reason: "Prisma detected - check API authorization instead"
43
+ });
44
+ continue;
45
+ }
46
+ if (verbose)
47
+ console.log(` ⏳ Checking ${rule.id}: ${rule.title}`);
48
+ const ruleClaims = runRule(rule, targetPath, verbose);
49
+ claims.push(...ruleClaims);
50
+ rulesChecked.push(rule.id);
51
+ if (verbose && ruleClaims.length > 0) {
52
+ console.log(` ⚠️ Found ${ruleClaims.length} issue(s)`);
53
+ }
54
+ }
55
+ // Add info claim for Prisma projects
56
+ if (project.hasPrisma && !project.hasSupabase && rulesSkipped.length > 0) {
57
+ claims.push({
58
+ ruleId: "INFO001",
59
+ title: "Prisma-prosjekt oppdaget",
60
+ category: "RLS",
61
+ severity: "INFO",
62
+ impactType: "info_leak",
63
+ statement: `RLS-regler hoppet over. Prisma bruker ikke Row Level Security. Sjekk autorisasjon i API-ruter og middleware.`,
64
+ evidence: [],
65
+ verificationSteps: [
66
+ "Sjekk at alle API-ruter validerer brukerens tilgang",
67
+ "Verifiser at middleware beskytter sensitive ruter",
68
+ "Sjekk Prisma queries for proper filtering på user_id"
69
+ ],
70
+ fixHint: "Implementer autorisasjonssjekker i API-ruter: if (session.user.id !== resource.userId) throw new Error('Unauthorized')",
71
+ });
72
+ }
73
+ // Sort claims by severity
74
+ claims.sort((a, b) => types_1.SEVERITY_ORDER[b.severity] - types_1.SEVERITY_ORDER[a.severity]);
75
+ // Build result
76
+ const meta = {
77
+ projectName: project.name,
78
+ projectPath: targetPath,
79
+ stack: project.stack,
80
+ scannedAt: new Date().toISOString(),
81
+ certivibeVersion: "0.1.0",
82
+ rulesVersion: "0.1.0",
83
+ totalFilesScanned: allFiles.length,
84
+ scanDurationMs: Date.now() - startTime,
85
+ };
86
+ const summary = buildSummary(claims, rulesChecked.length);
87
+ return {
88
+ meta,
89
+ summary,
90
+ claims,
91
+ rulesChecked,
92
+ rulesSkipped,
93
+ };
94
+ }
95
+ // ============================================
96
+ // RULE EXECUTION
97
+ // ============================================
98
+ function runRule(rule, cwd, verbose) {
99
+ switch (rule.kind) {
100
+ case "presence":
101
+ return runPresenceRule(rule, cwd);
102
+ case "absence":
103
+ return runAbsenceRule(rule, cwd);
104
+ case "correlation":
105
+ return runCorrelationRule(rule, cwd, verbose);
106
+ default:
107
+ return [];
108
+ }
109
+ }
110
+ /**
111
+ * Presence rule: Problem hvis pattern FINNES
112
+ */
113
+ function runPresenceRule(rule, cwd) {
114
+ if (!rule.pattern)
115
+ return [];
116
+ const result = (0, rg_1.search)({
117
+ pattern: rule.pattern,
118
+ paths: rule.paths,
119
+ cwd,
120
+ });
121
+ if (!result.found)
122
+ return [];
123
+ // Dedupliser på fil (ikke rapporter samme fil flere ganger)
124
+ const seenFiles = new Set();
125
+ const uniqueMatches = [];
126
+ for (const match of result.matches) {
127
+ if (!seenFiles.has(match.path)) {
128
+ seenFiles.add(match.path);
129
+ // Hent utvidet kontekst
130
+ match.snippet = (0, rg_1.getExpandedContext)(cwd, match.path, match.lineStart + 2);
131
+ uniqueMatches.push(match);
132
+ }
133
+ }
134
+ if (uniqueMatches.length === 0)
135
+ return [];
136
+ return [{
137
+ ruleId: rule.id,
138
+ title: rule.title,
139
+ category: rule.category,
140
+ severity: rule.severity,
141
+ impactType: rule.impactType,
142
+ statement: `Fant ${uniqueMatches.length} forekomst(er) av potensielt problematisk mønster.`,
143
+ evidence: uniqueMatches,
144
+ verificationSteps: [
145
+ `Søk etter pattern: ${rule.pattern}`,
146
+ `Sjekk filene: ${uniqueMatches.map(m => m.path).join(", ")}`,
147
+ "Vurder om dette er intendert bruk eller et sikkerhetsproblem."
148
+ ],
149
+ fixHint: rule.fixHint,
150
+ }];
151
+ }
152
+ /**
153
+ * Absence rule: Problem hvis pattern MANGLER
154
+ */
155
+ function runAbsenceRule(rule, cwd) {
156
+ if (!rule.pattern)
157
+ return [];
158
+ // Sjekk først om relevante filer finnes
159
+ const sqlFiles = (0, fs_1.findSqlFiles)(cwd);
160
+ if (sqlFiles.length === 0 && rule.category === "RLS") {
161
+ // Ingen SQL-filer, kan ikke sjekke RLS
162
+ return [];
163
+ }
164
+ const result = (0, rg_1.search)({
165
+ pattern: rule.pattern,
166
+ paths: rule.paths,
167
+ cwd,
168
+ });
169
+ // Hvis pattern finnes, ingen problem
170
+ if (result.found)
171
+ return [];
172
+ // Pattern mangler - dette er et funn
173
+ return [{
174
+ ruleId: rule.id,
175
+ title: rule.title,
176
+ category: rule.category,
177
+ severity: rule.severity,
178
+ impactType: rule.impactType,
179
+ statement: `Forventet mønster ble ikke funnet: ${rule.pattern}`,
180
+ evidence: [], // Ingen evidence for absence
181
+ verificationSteps: [
182
+ `Søk etter: ${rule.pattern}`,
183
+ `I filer som matcher: ${rule.paths.join(", ")}`,
184
+ "Hvis du ikke finner det, er funnet bekreftet."
185
+ ],
186
+ fixHint: rule.fixHint,
187
+ }];
188
+ }
189
+ /**
190
+ * Correlation rule: Problem hvis kode finnes MEN SQL mangler
191
+ */
192
+ function runCorrelationRule(rule, cwd, verbose) {
193
+ if (!rule.correlate)
194
+ return [];
195
+ const { codePattern, codePaths, sqlPattern, sqlPaths, condition } = rule.correlate;
196
+ // Steg 1: Finn kode som bruker funksjonaliteten
197
+ const codeResult = (0, rg_1.search)({
198
+ pattern: codePattern,
199
+ paths: codePaths,
200
+ cwd,
201
+ });
202
+ if (!codeResult.found) {
203
+ // Koden bruker ikke denne funksjonaliteten, ingen problem
204
+ if (verbose)
205
+ console.log(` ℹ️ No code using pattern: ${codePattern}`);
206
+ return [];
207
+ }
208
+ // Steg 2: Sjekk om SQL-pattern finnes
209
+ const sqlResult = (0, rg_1.search)({
210
+ pattern: sqlPattern,
211
+ paths: sqlPaths,
212
+ cwd,
213
+ });
214
+ if (condition === "code_without_sql" && !sqlResult.found) {
215
+ // Koden bruker funksjonaliteten, men SQL mangler
216
+ const codeEvidence = codeResult.matches.map(m => ({
217
+ ...m,
218
+ snippet: (0, rg_1.getExpandedContext)(cwd, m.path, m.lineStart + 2),
219
+ }));
220
+ return [{
221
+ ruleId: rule.id,
222
+ title: rule.title,
223
+ category: rule.category,
224
+ severity: rule.severity,
225
+ impactType: rule.impactType,
226
+ statement: `Koden bruker ${codePattern} men tilsvarende ${sqlPattern} mangler i SQL.`,
227
+ evidence: codeEvidence,
228
+ verificationSteps: [
229
+ `1. Bekreft at koden bruker: ${codePattern}`,
230
+ `2. Søk etter: ${sqlPattern} i SQL-filer`,
231
+ "3. Hvis SQL-pattern mangler, er funnet bekreftet."
232
+ ],
233
+ fixHint: rule.fixHint,
234
+ correlationDetails: {
235
+ codeEvidence,
236
+ sqlMissing: sqlPattern,
237
+ },
238
+ }];
239
+ }
240
+ return [];
241
+ }
242
+ // ============================================
243
+ // SUMMARY BUILDING
244
+ // ============================================
245
+ function buildSummary(claims, rulesChecked) {
246
+ const bySeverity = {
247
+ INFO: 0,
248
+ LOW: 0,
249
+ MEDIUM: 0,
250
+ HIGH: 0,
251
+ CRITICAL: 0,
252
+ };
253
+ const byCategory = {
254
+ RLS: 0,
255
+ AUTH: 0,
256
+ XSS: 0,
257
+ SECRETS: 0,
258
+ PRIVACY: 0,
259
+ STORAGE: 0,
260
+ CONFIG: 0,
261
+ };
262
+ for (const claim of claims) {
263
+ bySeverity[claim.severity]++;
264
+ byCategory[claim.category]++;
265
+ }
266
+ return {
267
+ total: claims.length,
268
+ bySeverity,
269
+ byCategory,
270
+ passed: rulesChecked - claims.length,
271
+ failed: claims.length,
272
+ };
273
+ }
274
+ // ============================================
275
+ // EXPORTS
276
+ // ============================================
277
+ var index_2 = require("./rules/index");
278
+ Object.defineProperty(exports, "RULES", { enumerable: true, get: function () { return index_2.RULES; } });
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Sertivibed Types
3
+ *
4
+ * Datamodeller for scanning, regler, claims og rapporter.
5
+ */
6
+ export type Severity = "INFO" | "LOW" | "MEDIUM" | "HIGH" | "CRITICAL";
7
+ export declare const SEVERITY_ORDER: Record<Severity, number>;
8
+ export type Category = "RLS" | "AUTH" | "XSS" | "SECRETS" | "PRIVACY" | "STORAGE" | "CONFIG";
9
+ export type RuleKind = "presence" | "absence" | "correlation";
10
+ export interface Rule {
11
+ id: string;
12
+ title: string;
13
+ category: Category;
14
+ severity: Severity;
15
+ kind: RuleKind;
16
+ pattern?: string;
17
+ paths: string[];
18
+ correlate?: {
19
+ codePattern: string;
20
+ codePaths: string[];
21
+ sqlPattern: string;
22
+ sqlPaths: string[];
23
+ condition: "code_without_sql";
24
+ };
25
+ rationale: string;
26
+ fixHint: string;
27
+ impactType: ImpactType;
28
+ }
29
+ export type ImpactType = "data_exposure" | "privilege_escalation" | "functional_break" | "info_leak";
30
+ export interface Evidence {
31
+ path: string;
32
+ lineStart: number;
33
+ lineEnd: number;
34
+ snippet: string;
35
+ matchText?: string;
36
+ }
37
+ export interface Claim {
38
+ ruleId: string;
39
+ title: string;
40
+ category: Category;
41
+ severity: Severity;
42
+ impactType: ImpactType;
43
+ statement: string;
44
+ evidence: Evidence[];
45
+ verificationSteps: string[];
46
+ fixHint: string;
47
+ correlationDetails?: {
48
+ codeEvidence: Evidence[];
49
+ sqlMissing: string;
50
+ };
51
+ }
52
+ export interface ScanMeta {
53
+ projectName: string;
54
+ projectPath: string;
55
+ stack: string[];
56
+ scannedAt: string;
57
+ certivibeVersion: string;
58
+ rulesVersion: string;
59
+ totalFilesScanned: number;
60
+ scanDurationMs: number;
61
+ }
62
+ export interface ScanSummary {
63
+ total: number;
64
+ bySeverity: Record<Severity, number>;
65
+ byCategory: Record<Category, number>;
66
+ passed: number;
67
+ failed: number;
68
+ }
69
+ export interface SkippedRule {
70
+ ruleId: string;
71
+ reason: string;
72
+ }
73
+ export interface ScanResult {
74
+ meta: ScanMeta;
75
+ summary: ScanSummary;
76
+ claims: Claim[];
77
+ rulesChecked: string[];
78
+ rulesSkipped: SkippedRule[];
79
+ }
80
+ export interface RgMatch {
81
+ type: "match";
82
+ data: {
83
+ path: {
84
+ text: string;
85
+ };
86
+ lines: {
87
+ text: string;
88
+ };
89
+ line_number: number;
90
+ submatches: Array<{
91
+ match: {
92
+ text: string;
93
+ };
94
+ start: number;
95
+ end: number;
96
+ }>;
97
+ };
98
+ }
99
+ export interface RgSummary {
100
+ type: "summary";
101
+ data: {
102
+ stats: {
103
+ matches: number;
104
+ searches: number;
105
+ };
106
+ };
107
+ }
108
+ export type RgOutput = RgMatch | RgSummary | {
109
+ type: string;
110
+ };
111
+ export interface ScanConfig {
112
+ targetPath: string;
113
+ includePaths?: string[];
114
+ excludePaths?: string[];
115
+ failOn?: Severity;
116
+ outputJson?: string;
117
+ outputMd?: string;
118
+ verbose?: boolean;
119
+ quiet?: boolean;
120
+ }
package/dist/types.js ADDED
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ /**
3
+ * Sertivibed Types
4
+ *
5
+ * Datamodeller for scanning, regler, claims og rapporter.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.SEVERITY_ORDER = void 0;
9
+ exports.SEVERITY_ORDER = {
10
+ INFO: 0,
11
+ LOW: 1,
12
+ MEDIUM: 2,
13
+ HIGH: 3,
14
+ CRITICAL: 4,
15
+ };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Filesystem Utilities
3
+ *
4
+ * Hjelpefunksjoner for å jobbe med filer og prosjektstruktur.
5
+ */
6
+ export interface ProjectInfo {
7
+ name: string;
8
+ path: string;
9
+ stack: string[];
10
+ hasSupabase: boolean;
11
+ hasNextJs: boolean;
12
+ hasReact: boolean;
13
+ hasVite: boolean;
14
+ hasPrisma: boolean;
15
+ }
16
+ export declare function detectProject(projectPath: string): ProjectInfo;
17
+ export interface FileInfo {
18
+ path: string;
19
+ relativePath: string;
20
+ extension: string;
21
+ size: number;
22
+ }
23
+ export declare function discoverFiles(projectPath: string, extensions?: Set<string>): FileInfo[];
24
+ export declare function readFile(filePath: string): string | null;
25
+ export declare function fileExists(filePath: string): boolean;
26
+ export declare function getLines(filePath: string, startLine: number, endLine: number): string[];
27
+ export declare function findSqlFiles(projectPath: string): string[];
28
+ export declare function findSourceFiles(projectPath: string): string[];
29
+ export interface TableInfo {
30
+ name: string;
31
+ file: string;
32
+ hasRls: boolean;
33
+ policies: string[];
34
+ }
35
+ export declare function extractTables(sqlContent: string, fileName: string): TableInfo[];
@@ -0,0 +1,195 @@
1
+ "use strict";
2
+ /**
3
+ * Filesystem Utilities
4
+ *
5
+ * Hjelpefunksjoner for å jobbe med filer og prosjektstruktur.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.detectProject = detectProject;
9
+ exports.discoverFiles = discoverFiles;
10
+ exports.readFile = readFile;
11
+ exports.fileExists = fileExists;
12
+ exports.getLines = getLines;
13
+ exports.findSqlFiles = findSqlFiles;
14
+ exports.findSourceFiles = findSourceFiles;
15
+ exports.extractTables = extractTables;
16
+ const fs_1 = require("fs");
17
+ const path_1 = require("path");
18
+ function detectProject(projectPath) {
19
+ const info = {
20
+ name: "unknown",
21
+ path: projectPath,
22
+ stack: [],
23
+ hasSupabase: false,
24
+ hasNextJs: false,
25
+ hasReact: false,
26
+ hasVite: false,
27
+ hasPrisma: false,
28
+ };
29
+ // Les package.json
30
+ const pkgPath = (0, path_1.join)(projectPath, "package.json");
31
+ if ((0, fs_1.existsSync)(pkgPath)) {
32
+ try {
33
+ const pkg = JSON.parse((0, fs_1.readFileSync)(pkgPath, "utf-8"));
34
+ info.name = pkg.name || "unknown";
35
+ const allDeps = {
36
+ ...pkg.dependencies,
37
+ ...pkg.devDependencies,
38
+ };
39
+ // Detect stack
40
+ if (allDeps["@supabase/supabase-js"] || allDeps["@supabase/ssr"]) {
41
+ info.hasSupabase = true;
42
+ info.stack.push("Supabase");
43
+ }
44
+ if (allDeps["next"]) {
45
+ info.hasNextJs = true;
46
+ info.stack.push("Next.js");
47
+ }
48
+ if (allDeps["react"]) {
49
+ info.hasReact = true;
50
+ info.stack.push("React");
51
+ }
52
+ if (allDeps["vite"]) {
53
+ info.hasVite = true;
54
+ info.stack.push("Vite");
55
+ }
56
+ if (allDeps["typescript"]) {
57
+ info.stack.push("TypeScript");
58
+ }
59
+ if (allDeps["prisma"] || allDeps["@prisma/client"]) {
60
+ info.hasPrisma = true;
61
+ info.stack.push("Prisma");
62
+ }
63
+ }
64
+ catch {
65
+ // Ignore parse errors
66
+ }
67
+ }
68
+ // Check for Supabase files
69
+ if ((0, fs_1.existsSync)((0, path_1.join)(projectPath, "supabase")) ||
70
+ (0, fs_1.existsSync)((0, path_1.join)(projectPath, "supabase-setup.sql"))) {
71
+ if (!info.hasSupabase) {
72
+ info.hasSupabase = true;
73
+ info.stack.push("Supabase");
74
+ }
75
+ }
76
+ // Check for Prisma folder
77
+ if ((0, fs_1.existsSync)((0, path_1.join)(projectPath, "prisma"))) {
78
+ if (!info.hasPrisma) {
79
+ info.hasPrisma = true;
80
+ info.stack.push("Prisma");
81
+ }
82
+ }
83
+ return info;
84
+ }
85
+ const IGNORED_DIRS = new Set([
86
+ "node_modules",
87
+ ".git",
88
+ "dist",
89
+ "build",
90
+ ".next",
91
+ ".vercel",
92
+ "coverage",
93
+ ".nyc_output",
94
+ ]);
95
+ const SCAN_EXTENSIONS = new Set([
96
+ ".ts",
97
+ ".tsx",
98
+ ".js",
99
+ ".jsx",
100
+ ".sql",
101
+ ".json",
102
+ ".mjs",
103
+ ".cjs",
104
+ ]);
105
+ function discoverFiles(projectPath, extensions = SCAN_EXTENSIONS) {
106
+ const files = [];
107
+ function walk(dir) {
108
+ try {
109
+ const entries = (0, fs_1.readdirSync)(dir, { withFileTypes: true });
110
+ for (const entry of entries) {
111
+ const fullPath = (0, path_1.join)(dir, entry.name);
112
+ if (entry.isDirectory()) {
113
+ if (!IGNORED_DIRS.has(entry.name) && !entry.name.startsWith(".")) {
114
+ walk(fullPath);
115
+ }
116
+ }
117
+ else if (entry.isFile()) {
118
+ const ext = (0, path_1.extname)(entry.name);
119
+ if (extensions.has(ext)) {
120
+ const stat = (0, fs_1.statSync)(fullPath);
121
+ files.push({
122
+ path: fullPath,
123
+ relativePath: (0, path_1.relative)(projectPath, fullPath),
124
+ extension: ext,
125
+ size: stat.size,
126
+ });
127
+ }
128
+ }
129
+ }
130
+ }
131
+ catch {
132
+ // Ignore permission errors etc
133
+ }
134
+ }
135
+ walk(projectPath);
136
+ return files;
137
+ }
138
+ // ============================================
139
+ // FILE READING
140
+ // ============================================
141
+ function readFile(filePath) {
142
+ try {
143
+ return (0, fs_1.readFileSync)(filePath, "utf-8");
144
+ }
145
+ catch {
146
+ return null;
147
+ }
148
+ }
149
+ function fileExists(filePath) {
150
+ return (0, fs_1.existsSync)(filePath);
151
+ }
152
+ function getLines(filePath, startLine, endLine) {
153
+ const content = readFile(filePath);
154
+ if (!content)
155
+ return [];
156
+ const lines = content.split("\n");
157
+ return lines.slice(Math.max(0, startLine - 1), endLine);
158
+ }
159
+ // ============================================
160
+ // SQL FILE HELPERS
161
+ // ============================================
162
+ function findSqlFiles(projectPath) {
163
+ const files = discoverFiles(projectPath, new Set([".sql"]));
164
+ return files.map((f) => f.relativePath);
165
+ }
166
+ function findSourceFiles(projectPath) {
167
+ const files = discoverFiles(projectPath, new Set([".ts", ".tsx", ".js", ".jsx"]));
168
+ return files.map((f) => f.relativePath);
169
+ }
170
+ function extractTables(sqlContent, fileName) {
171
+ const tables = [];
172
+ // Find CREATE TABLE statements
173
+ const tableRegex = /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:public\.)?(\w+)/gi;
174
+ let match;
175
+ while ((match = tableRegex.exec(sqlContent)) !== null) {
176
+ const tableName = match[1];
177
+ // Check if RLS is enabled for this table
178
+ const rlsRegex = new RegExp(`ALTER\\s+TABLE\\s+(?:public\\.)?${tableName}\\s+ENABLE\\s+ROW\\s+LEVEL\\s+SECURITY`, "i");
179
+ const hasRls = rlsRegex.test(sqlContent);
180
+ // Find policies for this table
181
+ const policyRegex = new RegExp(`CREATE\\s+POLICY\\s+[\\w"']+\\s+ON\\s+(?:public\\.)?${tableName}`, "gi");
182
+ const policies = [];
183
+ let policyMatch;
184
+ while ((policyMatch = policyRegex.exec(sqlContent)) !== null) {
185
+ policies.push(policyMatch[0]);
186
+ }
187
+ tables.push({
188
+ name: tableName,
189
+ file: fileName,
190
+ hasRls,
191
+ policies,
192
+ });
193
+ }
194
+ return tables;
195
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Ripgrep Wrapper
3
+ *
4
+ * Kjører ripgrep (rg) med JSON output for stabil parsing.
5
+ * Fallback til grep hvis rg ikke er installert.
6
+ */
7
+ import { Evidence } from "../types";
8
+ export declare function isRipgrepAvailable(): boolean;
9
+ export interface SearchOptions {
10
+ pattern: string;
11
+ paths: string[];
12
+ cwd: string;
13
+ ignoreCase?: boolean;
14
+ contextLines?: number;
15
+ }
16
+ export interface SearchResult {
17
+ found: boolean;
18
+ matches: Evidence[];
19
+ matchCount: number;
20
+ }
21
+ /**
22
+ * Søk etter pattern i filer med ripgrep
23
+ */
24
+ export declare function search(options: SearchOptions): SearchResult;
25
+ /**
26
+ * Sjekk om pattern IKKE finnes (for absence-regler)
27
+ */
28
+ export declare function searchAbsence(options: SearchOptions): SearchResult;
29
+ /**
30
+ * Hent flere linjer rundt en match for bedre kontekst
31
+ */
32
+ export declare function getExpandedContext(cwd: string, filePath: string, lineNumber: number, contextBefore?: number, contextAfter?: number): string;