@runhalo/engine 0.1.0 → 0.3.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/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # @runhalo/engine
2
+
3
+ **Halo rule engine** — COPPA 2.0 risk detection via tree-sitter AST analysis.
4
+
5
+ [![npm](https://img.shields.io/npm/v/@runhalo/engine.svg)](https://www.npmjs.com/package/@runhalo/engine)
6
+ [![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)
7
+
8
+ ## What it does
9
+
10
+ The engine scans source files for code patterns that may indicate COPPA 2.0 privacy risks in children's apps. It ships with **20 COPPA rules** and **5 ethical design rules**, powered by tree-sitter AST analysis and regex pattern matching.
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm install @runhalo/engine
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ```typescript
21
+ import { HaloEngine } from '@runhalo/engine';
22
+
23
+ const engine = new HaloEngine();
24
+
25
+ // Scan a single file
26
+ const results = engine.scanFile('src/auth/login.ts', sourceCode);
27
+
28
+ // Each result includes:
29
+ // - ruleId: 'coppa-auth-001'
30
+ // - severity: 'critical' | 'high' | 'medium' | 'low'
31
+ // - message: human-readable description
32
+ // - line / column / codeSnippet
33
+ // - fixSuggestion: recommended remediation
34
+ ```
35
+
36
+ ## Rules
37
+
38
+ 20 COPPA rules covering authentication, data collection, tracking, encryption, and consent — plus 5 ethical design rules for dark patterns like infinite scroll, streak pressure, and loot boxes.
39
+
40
+ Full rule reference: [github.com/runhalo/halo#rules](https://github.com/runhalo/halo#rules)
41
+
42
+ ## Supported Languages
43
+
44
+ TypeScript, JavaScript, TSX, JSX, Python, Swift, Java, Kotlin, HTML, Vue, Svelte, PHP, C++, C#, SQL
45
+
46
+ ## CLI
47
+
48
+ Most users should install the CLI instead:
49
+
50
+ ```bash
51
+ npx @runhalo/cli scan .
52
+ ```
53
+
54
+ See [@runhalo/cli](https://www.npmjs.com/package/@runhalo/cli) for the command-line scanner.
55
+
56
+ ## License
57
+
58
+ Apache 2.0 — [Mindful Media](https://mindfulmedia.org)
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Halo Fix Engine
3
+ * Tier 1 auto-fix transforms for COPPA violations
4
+ *
5
+ * Sprint 5: 4 auto-fixable rules (line-targeted string transforms, no ts-morph)
6
+ * coppa-sec-006: HTTP → HTTPS (url-upgrade)
7
+ * coppa-sec-010: Remove weak passwords (remove-default)
8
+ * coppa-sec-015: innerHTML → textContent (sanitize-input) ⚠️ behavior change
9
+ * coppa-default-020: Public → private (set-default)
10
+ */
11
+ import type { Violation } from './index';
12
+ /** Result of attempting to fix a single violation */
13
+ export interface FixResult {
14
+ ruleId: string;
15
+ filePath: string;
16
+ line: number;
17
+ status: 'applied' | 'skipped' | 'failed' | 'reverted';
18
+ original: string;
19
+ fixed: string;
20
+ reason?: string;
21
+ /** True if this fix changes rendering behavior (e.g. innerHTML → textContent) */
22
+ warning?: string;
23
+ }
24
+ /** Result of fixing an entire file */
25
+ export interface FileFixResult {
26
+ filePath: string;
27
+ originalContent: string;
28
+ fixedContent: string;
29
+ fixes: FixResult[];
30
+ /** False until caller re-scans to verify violations are gone */
31
+ verified: boolean;
32
+ }
33
+ /** Options for the fix operation */
34
+ export interface FixOptions {
35
+ dryRun?: boolean;
36
+ rules?: string[];
37
+ verbose?: boolean;
38
+ }
39
+ /**
40
+ * Transform: url-upgrade (coppa-sec-006)
41
+ * Replaces http:// with https:// on the violation line.
42
+ */
43
+ export declare function transformUrlUpgrade(line: string, _violation: Violation): string;
44
+ /**
45
+ * Transform: remove-default (coppa-sec-010)
46
+ * Replaces weak hardcoded password string literals with a secure alternative.
47
+ *
48
+ * Guards against false positives:
49
+ * - Enum definitions: PASSWORD = 'password' (discriminant, not a real password)
50
+ * - Input type declarations: type: 'password' (HTML type attribute)
51
+ * - Switch case labels: case 'password': (discriminant)
52
+ */
53
+ export declare function transformRemoveDefault(line: string, _violation: Violation): string;
54
+ /**
55
+ * Transform: sanitize-input (coppa-sec-015)
56
+ * Replaces .innerHTML assignments with .textContent.
57
+ * ⚠️ WARNING: This changes rendering behavior. If the developer needs HTML rendering,
58
+ * this fix will break their UI. Flagged with a warning in dry-run output.
59
+ *
60
+ * Guards against false positives:
61
+ * - Already-sanitized dangerouslySetInnerHTML (content already wrapped in DOMPurify.sanitize)
62
+ */
63
+ export declare function transformSanitizeInput(line: string, _violation: Violation): string;
64
+ /**
65
+ * Transform: set-default (coppa-default-020)
66
+ * Changes public profile defaults to private.
67
+ *
68
+ * Guards against false positives:
69
+ * - TypeScript type unions: visibility: 'public' | 'private' (type definition, not runtime value)
70
+ */
71
+ export declare function transformSetDefault(line: string, _violation: Violation): string;
72
+ export declare class FixEngine {
73
+ /**
74
+ * Check if a violation is auto-fixable (Tier 1).
75
+ * Uses the fixability field already populated on the violation.
76
+ */
77
+ isAutoFixable(violation: Violation): boolean;
78
+ /**
79
+ * Check if a rule ID is auto-fixable by looking at a violation's remediation metadata.
80
+ * For use when you have a ruleId but no violation object — checks against known auto-fix transforms.
81
+ */
82
+ isRuleAutoFixable(ruleId: string): boolean;
83
+ /**
84
+ * Get all auto-fixable rule IDs.
85
+ */
86
+ getAutoFixableRules(): string[];
87
+ /**
88
+ * Apply fixes to file content for a set of violations.
89
+ * Violations must all belong to the same file.
90
+ * Processes bottom-to-top to preserve line numbers.
91
+ * Returns the fixed content (does NOT write to disk).
92
+ */
93
+ applyFixes(content: string, violations: Violation[], options?: FixOptions): FileFixResult;
94
+ /**
95
+ * Generate a simple unified diff between original and fixed content.
96
+ * Used for --dry-run output.
97
+ */
98
+ generateDiff(filePath: string, original: string, fixed: string): string;
99
+ }
package/dist/fixer.js ADDED
@@ -0,0 +1,274 @@
1
+ "use strict";
2
+ /**
3
+ * Halo Fix Engine
4
+ * Tier 1 auto-fix transforms for COPPA violations
5
+ *
6
+ * Sprint 5: 4 auto-fixable rules (line-targeted string transforms, no ts-morph)
7
+ * coppa-sec-006: HTTP → HTTPS (url-upgrade)
8
+ * coppa-sec-010: Remove weak passwords (remove-default)
9
+ * coppa-sec-015: innerHTML → textContent (sanitize-input) ⚠️ behavior change
10
+ * coppa-default-020: Public → private (set-default)
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.FixEngine = void 0;
14
+ exports.transformUrlUpgrade = transformUrlUpgrade;
15
+ exports.transformRemoveDefault = transformRemoveDefault;
16
+ exports.transformSanitizeInput = transformSanitizeInput;
17
+ exports.transformSetDefault = transformSetDefault;
18
+ // ==================== Transform Functions ====================
19
+ /**
20
+ * Transform: url-upgrade (coppa-sec-006)
21
+ * Replaces http:// with https:// on the violation line.
22
+ */
23
+ function transformUrlUpgrade(line, _violation) {
24
+ return line.replace(/http:\/\//g, 'https://');
25
+ }
26
+ /**
27
+ * Transform: remove-default (coppa-sec-010)
28
+ * Replaces weak hardcoded password string literals with a secure alternative.
29
+ *
30
+ * Guards against false positives:
31
+ * - Enum definitions: PASSWORD = 'password' (discriminant, not a real password)
32
+ * - Input type declarations: type: 'password' (HTML type attribute)
33
+ * - Switch case labels: case 'password': (discriminant)
34
+ */
35
+ function transformRemoveDefault(line, _violation) {
36
+ // Skip enum/const member definitions (SCREAMING_CASE = 'value') — case-sensitive for identifier
37
+ if (/^\s*[A-Z][A-Z0-9_]+\s*=\s*(['"])(123456|password|changeme|student|welcome|student123|child123|default|admin)\1/.test(line)) {
38
+ return line;
39
+ }
40
+ // Skip HTML input type declarations (type: 'password', type="password", type = "password")
41
+ if (/\btype\s*[:=]\s*(['"])password\1/i.test(line)) {
42
+ return line;
43
+ }
44
+ // Skip switch/case discriminants (case 'password':)
45
+ if (/\bcase\s+(['"])(password|admin|default)\1\s*:/i.test(line)) {
46
+ return line;
47
+ }
48
+ return line.replace(/(['"])(123456|password|changeme|student|welcome|student123|child123|default|admin)\1/gi, 'require("crypto").randomBytes(16).toString("hex")');
49
+ }
50
+ /**
51
+ * Transform: sanitize-input (coppa-sec-015)
52
+ * Replaces .innerHTML assignments with .textContent.
53
+ * ⚠️ WARNING: This changes rendering behavior. If the developer needs HTML rendering,
54
+ * this fix will break their UI. Flagged with a warning in dry-run output.
55
+ *
56
+ * Guards against false positives:
57
+ * - Already-sanitized dangerouslySetInnerHTML (content already wrapped in DOMPurify.sanitize)
58
+ */
59
+ function transformSanitizeInput(line, _violation) {
60
+ // Handle .innerHTML = ... → .textContent = ...
61
+ if (/\.innerHTML\s*=/.test(line)) {
62
+ return line.replace(/\.innerHTML\s*=/, '.textContent =');
63
+ }
64
+ // Handle dangerouslySetInnerHTML → wrap in DOMPurify.sanitize()
65
+ if (/dangerouslySetInnerHTML\s*=\s*\{\s*\{\s*__html\s*:\s*([^}]+)\}\s*\}/.test(line)) {
66
+ // Skip if the __html value is already wrapped in DOMPurify.sanitize()
67
+ const match = line.match(/dangerouslySetInnerHTML\s*=\s*\{\s*\{\s*__html\s*:\s*([^}]+)\}\s*\}/);
68
+ if (match && /DOMPurify\.sanitize\s*\(/.test(match[1])) {
69
+ return line; // Already sanitized — don't double-wrap
70
+ }
71
+ return line.replace(/dangerouslySetInnerHTML\s*=\s*\{\s*\{\s*__html\s*:\s*([^}]+)\}\s*\}/, 'dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize($1) }}');
72
+ }
73
+ return line;
74
+ }
75
+ /**
76
+ * Transform: set-default (coppa-default-020)
77
+ * Changes public profile defaults to private.
78
+ *
79
+ * Guards against false positives:
80
+ * - TypeScript type unions: visibility: 'public' | 'private' (type definition, not runtime value)
81
+ */
82
+ function transformSetDefault(line, _violation) {
83
+ let result = line;
84
+ // Detect TypeScript type union patterns involving 'public' (e.g., 'public' | 'private')
85
+ const isTypeUnion = /['"]public['"]\s*\|/.test(line) || /\|\s*['"]public['"]/.test(line);
86
+ result = result.replace(/isProfileVisible:\s*true/gi, 'isProfileVisible: false');
87
+ // Only replace string literal 'public' values if NOT in a type union context
88
+ if (!isTypeUnion) {
89
+ result = result.replace(/visibility:\s*['"]public['"]/gi, "visibility: 'private'");
90
+ result = result.replace(/defaultPrivacy:\s*['"]public['"]/gi, "defaultPrivacy: 'private'");
91
+ }
92
+ result = result.replace(/isPublic:\s*true/gi, 'isPublic: false');
93
+ result = result.replace(/profileVisibility\s*=\s*['"]?public['"]?/gi, "profileVisibility = 'private'");
94
+ return result;
95
+ }
96
+ /** Maps transformType → transform function */
97
+ const TRANSFORM_REGISTRY = {
98
+ 'url-upgrade': transformUrlUpgrade,
99
+ 'remove-default': transformRemoveDefault,
100
+ 'sanitize-input': transformSanitizeInput,
101
+ 'set-default': transformSetDefault,
102
+ };
103
+ /** Rules where the fix changes rendering behavior and needs explicit warning */
104
+ const BEHAVIOR_CHANGING_RULES = new Set(['coppa-sec-015']);
105
+ // ==================== FixEngine Class ====================
106
+ class FixEngine {
107
+ /**
108
+ * Check if a violation is auto-fixable (Tier 1).
109
+ * Uses the fixability field already populated on the violation.
110
+ */
111
+ isAutoFixable(violation) {
112
+ return violation.fixability === 'auto' && violation.remediation?.transformType !== undefined;
113
+ }
114
+ /**
115
+ * Check if a rule ID is auto-fixable by looking at a violation's remediation metadata.
116
+ * For use when you have a ruleId but no violation object — checks against known auto-fix transforms.
117
+ */
118
+ isRuleAutoFixable(ruleId) {
119
+ // The 4 Tier 1 auto-fix rules with known transforms
120
+ return ['coppa-sec-006', 'coppa-sec-010', 'coppa-sec-015', 'coppa-default-020'].includes(ruleId);
121
+ }
122
+ /**
123
+ * Get all auto-fixable rule IDs.
124
+ */
125
+ getAutoFixableRules() {
126
+ return ['coppa-sec-006', 'coppa-sec-010', 'coppa-sec-015', 'coppa-default-020'];
127
+ }
128
+ /**
129
+ * Apply fixes to file content for a set of violations.
130
+ * Violations must all belong to the same file.
131
+ * Processes bottom-to-top to preserve line numbers.
132
+ * Returns the fixed content (does NOT write to disk).
133
+ */
134
+ applyFixes(content, violations, options) {
135
+ const fixes = [];
136
+ const lines = content.split('\n');
137
+ const filePath = violations[0]?.filePath || '';
138
+ // Filter to auto-fixable violations with known transforms
139
+ let fixable = violations.filter(v => this.isAutoFixable(v));
140
+ // Further filter by --rules if specified
141
+ if (options?.rules?.length) {
142
+ fixable = fixable.filter(v => options.rules.includes(v.ruleId));
143
+ }
144
+ // Sort by line number descending so edits don't shift subsequent lines
145
+ const sorted = [...fixable].sort((a, b) => b.line - a.line);
146
+ for (const violation of sorted) {
147
+ const transformType = violation.remediation?.transformType;
148
+ if (!transformType) {
149
+ fixes.push({
150
+ ruleId: violation.ruleId,
151
+ filePath,
152
+ line: violation.line,
153
+ status: 'skipped',
154
+ original: '',
155
+ fixed: '',
156
+ reason: 'No transform type defined',
157
+ });
158
+ continue;
159
+ }
160
+ const transform = TRANSFORM_REGISTRY[transformType];
161
+ if (!transform) {
162
+ fixes.push({
163
+ ruleId: violation.ruleId,
164
+ filePath,
165
+ line: violation.line,
166
+ status: 'skipped',
167
+ original: '',
168
+ fixed: '',
169
+ reason: `Unknown transform type: ${transformType}`,
170
+ });
171
+ continue;
172
+ }
173
+ const lineIdx = violation.line - 1;
174
+ if (lineIdx < 0 || lineIdx >= lines.length) {
175
+ fixes.push({
176
+ ruleId: violation.ruleId,
177
+ filePath,
178
+ line: violation.line,
179
+ status: 'failed',
180
+ original: '',
181
+ fixed: '',
182
+ reason: `Line ${violation.line} out of range`,
183
+ });
184
+ continue;
185
+ }
186
+ const originalLine = lines[lineIdx];
187
+ const fixedLine = transform(originalLine, violation);
188
+ if (fixedLine === originalLine) {
189
+ fixes.push({
190
+ ruleId: violation.ruleId,
191
+ filePath,
192
+ line: violation.line,
193
+ status: 'skipped',
194
+ original: originalLine,
195
+ fixed: fixedLine,
196
+ reason: 'Transform produced no change',
197
+ });
198
+ continue;
199
+ }
200
+ // Apply the transform
201
+ const fixedLines = fixedLine.split('\n');
202
+ lines.splice(lineIdx, 1, ...fixedLines);
203
+ const fix = {
204
+ ruleId: violation.ruleId,
205
+ filePath,
206
+ line: violation.line,
207
+ status: 'applied',
208
+ original: originalLine,
209
+ fixed: fixedLine,
210
+ };
211
+ // Add warning for behavior-changing transforms
212
+ if (BEHAVIOR_CHANGING_RULES.has(violation.ruleId)) {
213
+ fix.warning = 'This fix changes rendering behavior. Review before accepting.';
214
+ }
215
+ fixes.push(fix);
216
+ }
217
+ return {
218
+ filePath,
219
+ originalContent: content,
220
+ fixedContent: lines.join('\n'),
221
+ fixes,
222
+ verified: false,
223
+ };
224
+ }
225
+ /**
226
+ * Generate a simple unified diff between original and fixed content.
227
+ * Used for --dry-run output.
228
+ */
229
+ generateDiff(filePath, original, fixed) {
230
+ const origLines = original.split('\n');
231
+ const fixedLines = fixed.split('\n');
232
+ const output = [];
233
+ output.push(`--- a/${filePath}`);
234
+ output.push(`+++ b/${filePath}`);
235
+ let i = 0;
236
+ let j = 0;
237
+ while (i < origLines.length || j < fixedLines.length) {
238
+ if (i < origLines.length && j < fixedLines.length && origLines[i] === fixedLines[j]) {
239
+ i++;
240
+ j++;
241
+ continue;
242
+ }
243
+ // Found a difference — emit a hunk
244
+ const contextStart = Math.max(0, i - 1);
245
+ // Collect removed lines
246
+ const removed = [];
247
+ const added = [];
248
+ while (i < origLines.length && (j >= fixedLines.length || origLines[i] !== fixedLines[j])) {
249
+ removed.push(origLines[i]);
250
+ i++;
251
+ // Check if we've found alignment again
252
+ if (j < fixedLines.length && i < origLines.length && origLines[i] === fixedLines[j + added.length + 1]) {
253
+ break;
254
+ }
255
+ }
256
+ while (j < fixedLines.length && (i >= origLines.length || origLines[i] !== fixedLines[j])) {
257
+ added.push(fixedLines[j]);
258
+ j++;
259
+ }
260
+ if (removed.length > 0 || added.length > 0) {
261
+ output.push(`@@ -${contextStart + 1} +${contextStart + 1} @@`);
262
+ for (const line of removed) {
263
+ output.push(`-${line}`);
264
+ }
265
+ for (const line of added) {
266
+ output.push(`+${line}`);
267
+ }
268
+ }
269
+ }
270
+ return output.join('\n');
271
+ }
272
+ }
273
+ exports.FixEngine = FixEngine;
274
+ //# sourceMappingURL=fixer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixer.js","sourceRoot":"","sources":["../src/fixer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Framework Detection — detects project framework from package.json
3
+ * Used by ScaffoldEngine to generate framework-specific code scaffolds
4
+ */
5
+ export type Framework = 'react' | 'nextjs' | 'vue' | 'svelte' | 'plain-js';
6
+ export interface FrameworkDetectionResult {
7
+ framework: Framework;
8
+ typescript: boolean;
9
+ confidence: number;
10
+ }
11
+ /**
12
+ * Detect the project framework by reading package.json
13
+ * Priority: next > react > vue > svelte > plain-js
14
+ */
15
+ export declare function detectFramework(projectPath: string): FrameworkDetectionResult;
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ /**
3
+ * Framework Detection — detects project framework from package.json
4
+ * Used by ScaffoldEngine to generate framework-specific code scaffolds
5
+ */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.detectFramework = detectFramework;
41
+ const fs = __importStar(require("fs"));
42
+ const path = __importStar(require("path"));
43
+ /**
44
+ * Detect the project framework by reading package.json
45
+ * Priority: next > react > vue > svelte > plain-js
46
+ */
47
+ function detectFramework(projectPath) {
48
+ const result = {
49
+ framework: 'plain-js',
50
+ typescript: false,
51
+ confidence: 0.3,
52
+ };
53
+ // Try to read package.json
54
+ const pkgPath = path.join(projectPath, 'package.json');
55
+ let pkg = null;
56
+ try {
57
+ if (fs.existsSync(pkgPath)) {
58
+ pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
59
+ }
60
+ }
61
+ catch {
62
+ // Malformed package.json — fall through to defaults
63
+ }
64
+ if (!pkg) {
65
+ // Check for tsconfig.json even without package.json
66
+ const tsconfigPath = path.join(projectPath, 'tsconfig.json');
67
+ if (fs.existsSync(tsconfigPath)) {
68
+ result.typescript = true;
69
+ }
70
+ return result;
71
+ }
72
+ const deps = pkg.dependencies || {};
73
+ const devDeps = pkg.devDependencies || {};
74
+ const allDeps = { ...deps, ...devDeps };
75
+ // Detect TypeScript
76
+ if (allDeps['typescript']) {
77
+ result.typescript = true;
78
+ }
79
+ else {
80
+ const tsconfigPath = path.join(projectPath, 'tsconfig.json');
81
+ if (fs.existsSync(tsconfigPath)) {
82
+ result.typescript = true;
83
+ }
84
+ }
85
+ // Detect framework (priority order — Next.js includes React, so check first)
86
+ if (deps['next'] || devDeps['next']) {
87
+ result.framework = 'nextjs';
88
+ result.confidence = 0.95;
89
+ }
90
+ else if (deps['react'] || devDeps['react']) {
91
+ result.framework = 'react';
92
+ result.confidence = 0.9;
93
+ }
94
+ else if (deps['vue'] || devDeps['vue']) {
95
+ result.framework = 'vue';
96
+ result.confidence = 0.9;
97
+ }
98
+ else if (deps['svelte'] || devDeps['svelte']) {
99
+ result.framework = 'svelte';
100
+ result.confidence = 0.9;
101
+ }
102
+ else {
103
+ result.framework = 'plain-js';
104
+ result.confidence = 0.5;
105
+ }
106
+ return result;
107
+ }
108
+ //# sourceMappingURL=framework-detect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"framework-detect.js","sourceRoot":"","sources":["../src/framework-detect.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiBH,0CA6DC;AA5ED,uCAAyB;AACzB,2CAA6B;AAU7B;;;GAGG;AACH,SAAgB,eAAe,CAAC,WAAmB;IACjD,MAAM,MAAM,GAA6B;QACvC,SAAS,EAAE,UAAU;QACrB,UAAU,EAAE,KAAK;QACjB,UAAU,EAAE,GAAG;KAChB,CAAC;IAEF,2BAA2B;IAC3B,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;IACvD,IAAI,GAAG,GAAQ,IAAI,CAAC;IAEpB,IAAI,CAAC;QACH,IAAI,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3B,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;QACtD,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,oDAAoD;IACtD,CAAC;IAED,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,oDAAoD;QACpD,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,eAAe,CAAC,CAAC;QAC7D,IAAI,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;YAChC,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC;QAC3B,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,IAAI,GAAG,GAAG,CAAC,YAAY,IAAI,EAAE,CAAC;IACpC,MAAM,OAAO,GAAG,GAAG,CAAC,eAAe,IAAI,EAAE,CAAC;IAC1C,MAAM,OAAO,GAAG,EAAE,GAAG,IAAI,EAAE,GAAG,OAAO,EAAE,CAAC;IAExC,oBAAoB;IACpB,IAAI,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;QAC1B,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC;IAC3B,CAAC;SAAM,CAAC;QACN,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,eAAe,CAAC,CAAC;QAC7D,IAAI,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;YAChC,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC;QAC3B,CAAC;IACH,CAAC;IAED,6EAA6E;IAC7E,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACpC,MAAM,CAAC,SAAS,GAAG,QAAQ,CAAC;QAC5B,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC;IAC3B,CAAC;SAAM,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7C,MAAM,CAAC,SAAS,GAAG,OAAO,CAAC;QAC3B,MAAM,CAAC,UAAU,GAAG,GAAG,CAAC;IAC1B,CAAC;SAAM,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzC,MAAM,CAAC,SAAS,GAAG,KAAK,CAAC;QACzB,MAAM,CAAC,UAAU,GAAG,GAAG,CAAC;IAC1B,CAAC;SAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC/C,MAAM,CAAC,SAAS,GAAG,QAAQ,CAAC;QAC5B,MAAM,CAAC,UAAU,GAAG,GAAG,CAAC;IAC1B,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,SAAS,GAAG,UAAU,CAAC;QAC9B,MAAM,CAAC,UAAU,GAAG,GAAG,CAAC;IAC1B,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
package/dist/index.d.ts CHANGED
@@ -8,6 +8,14 @@
8
8
  */
9
9
  import Parser from 'tree-sitter';
10
10
  export type Severity = 'critical' | 'high' | 'medium' | 'low';
11
+ export type Fixability = 'auto' | 'guided' | 'flag-only';
12
+ export interface RemediationSpec {
13
+ fixability: Fixability;
14
+ transformType?: string;
15
+ scaffoldId?: string;
16
+ guidanceUrl?: string;
17
+ estimatedCost?: '$0' | '$0.01' | '$0.05';
18
+ }
11
19
  export interface Violation {
12
20
  ruleId: string;
13
21
  ruleName: string;
@@ -21,6 +29,11 @@ export interface Violation {
21
29
  penalty?: string;
22
30
  suppressed?: boolean;
23
31
  suppressionComment?: string;
32
+ category?: string;
33
+ language?: string;
34
+ matchType?: 'regex' | 'ast' | 'hybrid';
35
+ fixability?: Fixability;
36
+ remediation?: RemediationSpec;
24
37
  }
25
38
  export interface Rule {
26
39
  id: string;
@@ -31,6 +44,7 @@ export interface Rule {
31
44
  fixSuggestion: string;
32
45
  penalty: string;
33
46
  languages: string[];
47
+ remediation?: RemediationSpec;
34
48
  }
35
49
  export interface SuppressionConfig {
36
50
  enabled: boolean;
@@ -61,6 +75,8 @@ export declare class TreeSitterParser {
61
75
  extractIdentifiers(code: string): string[];
62
76
  }
63
77
  export declare const treeSitterParser: TreeSitterParser;
78
+ declare const REMEDIATION_MAP: Record<string, RemediationSpec>;
79
+ declare function getRemediation(ruleId: string): RemediationSpec;
64
80
  export declare const COPPA_RULES: Rule[];
65
81
  export declare const ETHICAL_RULES: Rule[];
66
82
  export interface IgnoreConfig {
@@ -151,4 +167,16 @@ export declare class HaloEngine {
151
167
  */
152
168
  getFixSuggestion(ruleId: string): string;
153
169
  }
170
+ export { REMEDIATION_MAP, getRemediation };
171
+ export { FixEngine } from './fixer';
172
+ export type { FixResult, FileFixResult, FixOptions } from './fixer';
173
+ export { transformUrlUpgrade, transformRemoveDefault, transformSanitizeInput, transformSetDefault, } from './fixer';
174
+ export { ComplianceScoreEngine } from './scoring';
175
+ export type { ComplianceScoreResult, LetterGrade } from './scoring';
176
+ export { ScaffoldEngine } from './scaffold-engine';
177
+ export type { GuidedFixResult, GuidedFixSummary } from './scaffold-engine';
178
+ export { detectFramework } from './framework-detect';
179
+ export type { Framework, FrameworkDetectionResult } from './framework-detect';
180
+ export { SCAFFOLD_REGISTRY } from './scaffolds/index';
181
+ export type { ScaffoldTemplate, ScaffoldFile } from './scaffolds/index';
154
182
  export default HaloEngine;