@runhalo/engine 0.7.0 → 0.9.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.
@@ -0,0 +1,44 @@
1
+ /**
2
+ * COPPA 2.0 Countdown Utility
3
+ *
4
+ * Sprint 19: Shared across all output surfaces (CLI, GitHub Action, PDF, dashboard).
5
+ * COPPA 2.0 enforcement begins April 22, 2026.
6
+ * Maximum penalty: $53,088 per violation per day.
7
+ *
8
+ * Don't Go Backwards Rule: Every customer touchpoint includes this countdown.
9
+ */
10
+ declare const COPPA_2_ENFORCEMENT_DATE: Date;
11
+ declare const PENALTY_PER_VIOLATION_PER_DAY = 53088;
12
+ export interface CoppaCountdown {
13
+ /** Days until enforcement (0 if already active) */
14
+ days: number;
15
+ /** Human-readable message */
16
+ message: string;
17
+ /** Whether COPPA 2.0 is currently in effect */
18
+ isActive: boolean;
19
+ /** The enforcement date */
20
+ enforcementDate: Date;
21
+ /** Maximum penalty per violation per day */
22
+ penaltyPerDay: number;
23
+ }
24
+ /**
25
+ * Get the current COPPA 2.0 countdown status.
26
+ * Used by every output surface in Halo.
27
+ */
28
+ export declare function getCoppaCountdown(now?: Date): CoppaCountdown;
29
+ /**
30
+ * Format the COPPA countdown for CLI output (with ANSI colors).
31
+ */
32
+ export declare function formatCoppaCountdownCLI(countdown?: CoppaCountdown): string;
33
+ /**
34
+ * Format the COPPA countdown for GitHub Action PR comments (Markdown).
35
+ */
36
+ export declare function formatCoppaCountdownMarkdown(countdown?: CoppaCountdown): string;
37
+ /**
38
+ * Format for PDF report headers.
39
+ */
40
+ export declare function formatCoppaCountdownPDF(countdown?: CoppaCountdown): {
41
+ text: string;
42
+ severity: 'urgent' | 'warning' | 'active';
43
+ };
44
+ export { COPPA_2_ENFORCEMENT_DATE, PENALTY_PER_VIOLATION_PER_DAY };
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ /**
3
+ * COPPA 2.0 Countdown Utility
4
+ *
5
+ * Sprint 19: Shared across all output surfaces (CLI, GitHub Action, PDF, dashboard).
6
+ * COPPA 2.0 enforcement begins April 22, 2026.
7
+ * Maximum penalty: $53,088 per violation per day.
8
+ *
9
+ * Don't Go Backwards Rule: Every customer touchpoint includes this countdown.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.PENALTY_PER_VIOLATION_PER_DAY = exports.COPPA_2_ENFORCEMENT_DATE = void 0;
13
+ exports.getCoppaCountdown = getCoppaCountdown;
14
+ exports.formatCoppaCountdownCLI = formatCoppaCountdownCLI;
15
+ exports.formatCoppaCountdownMarkdown = formatCoppaCountdownMarkdown;
16
+ exports.formatCoppaCountdownPDF = formatCoppaCountdownPDF;
17
+ const COPPA_2_ENFORCEMENT_DATE = new Date('2026-04-22T00:00:00Z');
18
+ exports.COPPA_2_ENFORCEMENT_DATE = COPPA_2_ENFORCEMENT_DATE;
19
+ const PENALTY_PER_VIOLATION_PER_DAY = 53088;
20
+ exports.PENALTY_PER_VIOLATION_PER_DAY = PENALTY_PER_VIOLATION_PER_DAY;
21
+ /**
22
+ * Get the current COPPA 2.0 countdown status.
23
+ * Used by every output surface in Halo.
24
+ */
25
+ function getCoppaCountdown(now) {
26
+ const currentDate = now || new Date();
27
+ const diffMs = COPPA_2_ENFORCEMENT_DATE.getTime() - currentDate.getTime();
28
+ const days = Math.max(0, Math.ceil(diffMs / 86400000));
29
+ if (days > 0) {
30
+ return {
31
+ days,
32
+ message: `COPPA 2.0 enforcement begins April 22, 2026 \u2014 ${days} day${days === 1 ? '' : 's'}`,
33
+ isActive: false,
34
+ enforcementDate: COPPA_2_ENFORCEMENT_DATE,
35
+ penaltyPerDay: PENALTY_PER_VIOLATION_PER_DAY,
36
+ };
37
+ }
38
+ return {
39
+ days: 0,
40
+ message: 'COPPA 2.0 is NOW IN EFFECT',
41
+ isActive: true,
42
+ enforcementDate: COPPA_2_ENFORCEMENT_DATE,
43
+ penaltyPerDay: PENALTY_PER_VIOLATION_PER_DAY,
44
+ };
45
+ }
46
+ /**
47
+ * Format the COPPA countdown for CLI output (with ANSI colors).
48
+ */
49
+ function formatCoppaCountdownCLI(countdown) {
50
+ const cd = countdown || getCoppaCountdown();
51
+ const lines = [
52
+ '\u2500'.repeat(55),
53
+ ];
54
+ if (cd.isActive) {
55
+ lines.push(`\x1b[31m\x1b[1m\u26a0\ufe0f COPPA 2.0 is NOW IN EFFECT\x1b[0m`);
56
+ lines.push(` Maximum penalty: $${cd.penaltyPerDay.toLocaleString()}/violation/day`);
57
+ }
58
+ else {
59
+ lines.push(`\x1b[33m\u26a0\ufe0f ${cd.message}\x1b[0m`);
60
+ lines.push(` Maximum penalty: $${cd.penaltyPerDay.toLocaleString()}/violation/day`);
61
+ }
62
+ lines.push('\u2500'.repeat(55));
63
+ return lines.join('\n');
64
+ }
65
+ /**
66
+ * Format the COPPA countdown for GitHub Action PR comments (Markdown).
67
+ */
68
+ function formatCoppaCountdownMarkdown(countdown) {
69
+ const cd = countdown || getCoppaCountdown();
70
+ if (cd.isActive) {
71
+ return [
72
+ '---',
73
+ `> \u26a0\ufe0f **COPPA 2.0 is NOW IN EFFECT**`,
74
+ `> Maximum penalty: $${cd.penaltyPerDay.toLocaleString()} per violation per day`,
75
+ `> Run \`npx @runhalo/cli scan . --review\` for AI-verified compliance check`,
76
+ ].join('\n');
77
+ }
78
+ return [
79
+ '---',
80
+ `> \u26a0\ufe0f **COPPA 2.0 enforcement begins April 22, 2026 \u2014 ${cd.days} day${cd.days === 1 ? '' : 's'}**`,
81
+ `> Maximum penalty: $${cd.penaltyPerDay.toLocaleString()} per violation per day`,
82
+ `> Run \`npx @runhalo/cli scan . --review\` for AI-verified compliance check`,
83
+ ].join('\n');
84
+ }
85
+ /**
86
+ * Format for PDF report headers.
87
+ */
88
+ function formatCoppaCountdownPDF(countdown) {
89
+ const cd = countdown || getCoppaCountdown();
90
+ if (cd.isActive) {
91
+ return {
92
+ text: 'COPPA 2.0 IS NOW IN EFFECT — Ensure ongoing compliance',
93
+ severity: 'active',
94
+ };
95
+ }
96
+ if (cd.days <= 30) {
97
+ return {
98
+ text: `COPPA 2.0 ENFORCEMENT IN ${cd.days} DAYS — April 22, 2026`,
99
+ severity: 'urgent',
100
+ };
101
+ }
102
+ return {
103
+ text: `COPPA 2.0 enforcement begins April 22, 2026 (${cd.days} days)`,
104
+ severity: 'warning',
105
+ };
106
+ }
107
+ //# sourceMappingURL=coppa-countdown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coppa-countdown.js","sourceRoot":"","sources":["../src/coppa-countdown.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -167,6 +167,7 @@ export interface JSONRule {
167
167
  guidance_url: string | null;
168
168
  }
169
169
  export declare function loadRulesFromJSON(jsonPath: string): Rule[];
170
+ export declare const ALL_PACK_IDS: string[];
170
171
  export declare function loadRulesFromJSONByPack(jsonPath: string, packIds: string[]): Rule[];
171
172
  export declare function compileRawRules(rawRules: JSONRule[]): Rule[];
172
173
  export declare class TreeSitterParser {
@@ -282,9 +283,9 @@ export declare class HaloEngine {
282
283
  */
283
284
  private loadBundledRulesByPack;
284
285
  /**
285
- * Resolve legacy boolean flags to pack IDs.
286
- * Maps ethical/aiAudit/sectorAuSbd booleans to their pack ID equivalents.
287
- * Always includes 'coppa' as the base pack.
286
+ * Resolve pack IDs from config.
287
+ * Halo 2.0: defaults to ALL packs (loose pre-filter for AI Review Board).
288
+ * Legacy boolean flags still supported for backward compatibility.
288
289
  */
289
290
  static resolvePacks(config: EngineConfig): string[];
290
291
  /**
@@ -332,4 +333,10 @@ export { detectFramework } from './framework-detect';
332
333
  export type { Framework, FrameworkDetectionResult } from './framework-detect';
333
334
  export { SCAFFOLD_REGISTRY } from './scaffolds/index';
334
335
  export type { ScaffoldTemplate, ScaffoldFile } from './scaffolds/index';
336
+ export { detectSDKsFromPackageJson, generateSDKContext, detectSDKs, SDK_RISK_DATABASE } from './sdk-intelligence';
337
+ export type { SDKRiskProfile, DetectedSDK } from './sdk-intelligence';
338
+ export { createTierContext, resolveTierFromKey, getUpgradeCTA, getTierComparison, TIER_LIMITS, TIER_FEATURES, TIER_DISPLAY_NAMES } from './tier-context';
339
+ export type { HaloTier, TierLimits, TierFeatures, TierContext } from './tier-context';
340
+ export { getCoppaCountdown, formatCoppaCountdownCLI, formatCoppaCountdownMarkdown, formatCoppaCountdownPDF, COPPA_2_ENFORCEMENT_DATE, PENALTY_PER_VIOLATION_PER_DAY } from './coppa-countdown';
341
+ export type { CoppaCountdown } from './coppa-countdown';
335
342
  export default HaloEngine;
package/dist/index.js CHANGED
@@ -44,7 +44,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
44
44
  return (mod && mod.__esModule) ? mod : { "default": mod };
45
45
  };
46
46
  Object.defineProperty(exports, "__esModule", { value: true });
47
- exports.SCAFFOLD_REGISTRY = exports.detectFramework = exports.ScaffoldEngine = exports.ComplianceScoreEngine = exports.transformSetDefault = exports.transformSanitizeInput = exports.transformRemoveDefault = exports.transformUrlUpgrade = exports.FixEngine = exports.REMEDIATION_MAP = exports.HaloEngine = exports.AU_SBD_RULES = exports.AI_AUDIT_RULES = exports.ETHICAL_RULES = exports.COPPA_RULES = exports.treeSitterParser = exports.TreeSitterParser = void 0;
47
+ exports.PENALTY_PER_VIOLATION_PER_DAY = exports.COPPA_2_ENFORCEMENT_DATE = exports.formatCoppaCountdownPDF = exports.formatCoppaCountdownMarkdown = exports.formatCoppaCountdownCLI = exports.getCoppaCountdown = exports.TIER_DISPLAY_NAMES = exports.TIER_FEATURES = exports.TIER_LIMITS = exports.getTierComparison = exports.getUpgradeCTA = exports.resolveTierFromKey = exports.createTierContext = exports.SDK_RISK_DATABASE = exports.detectSDKs = exports.generateSDKContext = exports.detectSDKsFromPackageJson = exports.SCAFFOLD_REGISTRY = exports.detectFramework = exports.ScaffoldEngine = exports.ComplianceScoreEngine = exports.transformSetDefault = exports.transformSanitizeInput = exports.transformRemoveDefault = exports.transformUrlUpgrade = exports.FixEngine = exports.REMEDIATION_MAP = exports.HaloEngine = exports.AU_SBD_RULES = exports.AI_AUDIT_RULES = exports.ETHICAL_RULES = exports.COPPA_RULES = exports.treeSitterParser = exports.TreeSitterParser = exports.ALL_PACK_IDS = void 0;
48
48
  exports.classifyFile = classifyFile;
49
49
  exports.loadRulesFromYAML = loadRulesFromYAML;
50
50
  exports.loadRulesFromJSON = loadRulesFromJSON;
@@ -313,6 +313,13 @@ function loadRulesFromJSON(jsonPath) {
313
313
  return [];
314
314
  }
315
315
  }
316
+ // ── Halo 2.0: All 16 packs active by default ──
317
+ // Every pack fires as a loose pre-filter. AI Review Board handles precision.
318
+ exports.ALL_PACK_IDS = [
319
+ 'coppa', 'ethical', 'ai-audit', 'au-sbd', 'ut-sb142', 'uk-aadc',
320
+ 'eu-dsa', 'au-osa', 'caadca', 'eu-ai-act', 'gdpr-art8', 'india-dpdp',
321
+ 'brazil-lgpd', 'canada-pipeda', 'south-korea-pipa', 'behavioral-design',
322
+ ];
316
323
  // JSON Rule Loader — Load rules filtered by pack IDs
317
324
  function loadRulesFromJSONByPack(jsonPath, packIds) {
318
325
  try {
@@ -650,18 +657,18 @@ exports.COPPA_RULES = [
650
657
  severity: 'critical',
651
658
  description: 'HTTP transmission of PII exposes data in transit. All API endpoints handling personal information must use HTTPS.',
652
659
  patterns: [
653
- /http:\/\/[^\s]*(\/api\/|\/login|\/user|\/register|\/profile)/gi,
654
- /http:\/\/localhost:[^\s]*(\/api\/)/gi,
655
- /axios\.get\s*\(\s*['"]http:\/\//gi,
656
- /fetch\s*\(\s*['"]http:\/\//gi,
657
- /http:\/\/[^\s]*email[^\s]*/gi,
660
+ /(?!.*(?:schemas\.|w3\.org|xmlns\.|\.config|README|example\.com|localhost|127\.0\.0|\.test|\.example|\.invalid|\.local|specification|documentation|comment|\/\/\s))http:\/\/[^\s]*(\/api\/|\/login|\/user|\/register|\/profile)/gi,
661
+ /(?!.*(?:schemas\.|w3\.org|xmlns\.|\.config|README|example\.com|localhost|127\.0\.0|\.test|\.example|\.invalid|\.local|specification|documentation|comment|\/\/\s))http:\/\/localhost:[^\s]*(\/api\/)/gi,
662
+ /(?!.*(?:schemas\.|w3\.org|xmlns\.|\.config|README|example\.com|localhost|127\.0\.0|\.test|\.example|\.invalid|\.local|specification|documentation|comment|\/\/\s))axios\.get\s*\(\s*['"]http:\/\//gi,
663
+ /(?!.*(?:schemas\.|w3\.org|xmlns\.|\.config|README|example\.com|localhost|127\.0\.0|\.test|\.example|\.invalid|\.local|specification|documentation|comment|\/\/\s))fetch\s*\(\s*['"]http:\/\//gi,
664
+ /(?!.*(?:schemas\.|w3\.org|xmlns\.|\.config|README|example\.com|localhost|127\.0\.0|\.test|\.example|\.invalid|\.local|specification|documentation|comment|\/\/\s))http:\/\/[^\s]*email[^\s]*/gi,
658
665
  // Python — requests/urllib with HTTP
659
- /requests\.(?:get|post)\s*\(\s*['"]http:\/\/(?!localhost)/gi,
660
- /urllib\.request\.urlopen\s*\(\s*['"]http:\/\/(?!localhost)/gi,
666
+ /(?!.*(?:schemas\.|w3\.org|xmlns\.|\.config|README|example\.com|localhost|127\.0\.0|\.test|\.example|\.invalid|\.local|specification|documentation|comment|\/\/\s))requests\.(?:get|post)\s*\(\s*['"]http:\/\/(?!localhost)/gi,
667
+ /(?!.*(?:schemas\.|w3\.org|xmlns\.|\.config|README|example\.com|localhost|127\.0\.0|\.test|\.example|\.invalid|\.local|specification|documentation|comment|\/\/\s))urllib\.request\.urlopen\s*\(\s*['"]http:\/\/(?!localhost)/gi,
661
668
  // PHP — HTTP API calls
662
- /(?:curl_setopt|file_get_contents|wp_remote_get)\s*\([^)]*['"]http:\/\/(?!localhost)/gi,
669
+ /(?!.*(?:schemas\.|w3\.org|xmlns\.|\.config|README|example\.com|localhost|127\.0\.0|\.test|\.example|\.invalid|\.local|specification|documentation|comment|\/\/\s))(?:curl_setopt|file_get_contents|wp_remote_get)\s*\([^)]*['"]http:\/\/(?!localhost)/gi,
663
670
  // Ruby — HTTP requests
664
- /(?:Net::HTTP|HTTParty|Faraday)\.(?:get|post)\s*\([^)]*['"]http:\/\/(?!localhost)/gi
671
+ /(?!.*(?:schemas\.|w3\.org|xmlns\.|\.config|README|example\.com|localhost|127\.0\.0|\.test|\.example|\.invalid|\.local|specification|documentation|comment|\/\/\s))(?:Net::HTTP|HTTParty|Faraday)\.(?:get|post)\s*\([^)]*['"]http:\/\/(?!localhost)/gi
665
672
  ],
666
673
  fixSuggestion: 'Replace http:// with https:// for all API endpoints and resources',
667
674
  penalty: 'Security breach liability + COPPA penalties',
@@ -705,17 +712,17 @@ exports.COPPA_RULES = [
705
712
  patterns: [
706
713
  // PascalCase component names: SignUpForm, RegisterForm, RegistrationForm, CreateAccountForm
707
714
  // \b after Form prevents matching registerFormat, registerFormats, etc.
708
- /\b(?:SignUp|Register|Registration|CreateAccount)Form\b/gi,
715
+ /(?!.*(?:admin|Admin|ADMIN|educator|Educator|instructor|teacher|oauth|OAuth|lti|LTI|saml|SAML|sso|SSO|staff|moderator|enrollment|grading|assignment|syllabus|cartridge|yui|superclass|brickfield|sitepolicy|confirmation|FormattedMessage))\b(?:SignUp|Register|Registration|CreateAccount)Form\b/gi,
709
716
  // kebab-case / snake_case: sign-up-form, register_form, create-account-form
710
- /\b(?:sign[-_]?up|register|registration|create[-_]?account)[-_]form\b/gi,
717
+ /(?!.*(?:admin|Admin|ADMIN|educator|Educator|instructor|teacher|oauth|OAuth|lti|LTI|saml|SAML|sso|SSO|staff|moderator|enrollment|grading|assignment|syllabus|cartridge|yui|superclass|brickfield|sitepolicy|confirmation|FormattedMessage))\b(?:sign[-_]?up|register|registration|create[-_]?account)[-_]form\b/gi,
711
718
  // HTML form elements with registration-related ids/classes
712
- /<form[^>]*(?:id|class|name)\s*=\s*["'][^"']*(?:register|signup|sign[-_]up|create[-_]account)[^"']*["']/gi,
719
+ /(?!.*(?:admin|Admin|ADMIN|educator|Educator|instructor|teacher|oauth|OAuth|lti|LTI|saml|SAML|sso|SSO|staff|moderator|enrollment|grading|assignment|syllabus|cartridge|yui|superclass|brickfield|sitepolicy|confirmation|FormattedMessage))<form[^>]*(?:id|class|name)\s*=\s*["'][^"']*(?:register|signup|sign[-_]up|create[-_]account)[^"']*["']/gi,
713
720
  // Python — Django/Flask registration form classes
714
- /class\s+(?:SignUp|Register|Registration|CreateAccount)Form\s*\(\s*(?:forms\.Form|ModelForm|FlaskForm)/gi,
721
+ /(?!.*(?:admin|Admin|ADMIN|educator|Educator|instructor|teacher|oauth|OAuth|lti|LTI|saml|SAML|sso|SSO|staff|moderator|enrollment|grading|assignment|syllabus|cartridge|yui|superclass|brickfield|sitepolicy|confirmation|FormattedMessage))class\s+(?:SignUp|Register|Registration|CreateAccount)Form\s*\(\s*(?:forms\.Form|ModelForm|FlaskForm)/gi,
715
722
  // Ruby — Rails registration routes/controllers
716
- /def\s+(?:sign_up|register|create_account)\b/gi,
723
+ /(?!.*(?:admin|Admin|ADMIN|educator|Educator|instructor|teacher|oauth|OAuth|lti|LTI|saml|SAML|sso|SSO|staff|moderator|enrollment|grading|assignment|syllabus|cartridge|yui|superclass|brickfield|sitepolicy|confirmation|FormattedMessage))def\s+(?:sign_up|register|create_account)\b/gi,
717
724
  // PHP — WordPress registration hooks
718
- /(?:register_new_user|wp_create_user|user_register)\s*\(/gi
725
+ /(?!.*(?:admin|Admin|ADMIN|educator|Educator|instructor|teacher|oauth|OAuth|lti|LTI|saml|SAML|sso|SSO|staff|moderator|enrollment|grading|assignment|syllabus|cartridge|yui|superclass|brickfield|sitepolicy|confirmation|FormattedMessage))(?:register_new_user|wp_create_user|user_register)\s*\(/gi
719
726
  ],
720
727
  fixSuggestion: 'Add <a href="/privacy">Privacy Policy</a> link to registration form footer',
721
728
  penalty: 'Compliance failure',
@@ -770,9 +777,9 @@ exports.COPPA_RULES = [
770
777
  /intercom\.init/gi,
771
778
  /zendesk\.init/gi,
772
779
  /drift\.init/gi,
773
- /<script[^>]+src=['"][^'"]*intercom/gi,
774
- /<script[^>]+src=['"][^'"]*(zendesk|zdassets)/gi,
775
- /Freshdesk|FreshChat/gi
780
+ /(?!.*(?:help\.|support\.|docs\.|faq|knowledge.?base|status\.))<script[^>]+src=['"][^'"]*intercom/gi,
781
+ /(?!.*(?:help\.|support\.|docs\.|faq|knowledge.?base|status\.))<script[^>]+src=['"][^'"]*(zendesk|zdassets)/gi,
782
+ /(?!.*(?:help\.|zendesk\.com\/hc|freshdesk\.com\/support|support\.|docs\.|faq|knowledge.?base|status\.))(?:Freshdesk|FreshChat)/gi
776
783
  ],
777
784
  fixSuggestion: 'Disable chat widget for unauthenticated or under-13 users via conditional rendering',
778
785
  penalty: '$53,088 per violation',
@@ -820,25 +827,25 @@ exports.COPPA_RULES = [
820
827
  severity: 'low',
821
828
  description: 'FTC declined to codify push notification restrictions in the 2025 final rule but stated it remains concerned about push notifications and engagement techniques. Best practice: gate push subscriptions behind parental consent. Maps to NGL Labs and Sendit enforcement patterns.',
822
829
  patterns: [
823
- /FirebaseMessaging\.subscribeToTopic/g,
824
- /OneSignal\.(?:promptForPushNotifications|init)\s*\(/g,
825
- /sendPushNotification\s*\(/g,
826
- /fcm\.send\s*\(/g,
827
- /PushManager\.subscribe\s*\(/g,
828
- /pushManager\.subscribe\s*\(/g,
829
- /messaging\(\)\.getToken\s*\(/g,
830
- /registerForPushNotifications\s*\(/g,
831
- /addEventListener\s*\(\s*['"]push['"]/g,
832
- /expo-notifications/g,
833
- /react-native-push-notification/g,
830
+ /FirebaseMessaging\.subscribeToTopic(?!.*(?:vendor|node_modules|\.config|\.json|manifest|package\.json|bower|composer|Gemfile|requirements\.txt|Cargo\.toml|\.lock))/g,
831
+ /OneSignal\.(?:promptForPushNotifications|init)\s*\((?!.*(?:vendor|node_modules|\.config|\.json|manifest|package\.json|bower|composer|Gemfile|requirements\.txt|Cargo\.toml|\.lock))/g,
832
+ /sendPushNotification\s*\((?!.*(?:vendor|node_modules|\.config|\.json|manifest|package\.json|bower|composer|Gemfile|requirements\.txt|Cargo\.toml|\.lock))/g,
833
+ /fcm\.send\s*\((?!.*(?:vendor|node_modules|\.config|\.json|manifest|package\.json|bower|composer|Gemfile|requirements\.txt|Cargo\.toml|\.lock))/g,
834
+ /PushManager\.subscribe\s*\((?!.*(?:vendor|node_modules|\.config|\.json|manifest|package\.json|bower|composer|Gemfile|requirements\.txt|Cargo\.toml|\.lock))/g,
835
+ /pushManager\.subscribe\s*\((?!.*(?:vendor|node_modules|\.config|\.json|manifest|package\.json|bower|composer|Gemfile|requirements\.txt|Cargo\.toml|\.lock))/g,
836
+ /messaging\(\)\.getToken\s*\((?!.*(?:vendor|node_modules|\.config|\.json|manifest|package\.json|bower|composer|Gemfile|requirements\.txt|Cargo\.toml|\.lock))/g,
837
+ /registerForPushNotifications\s*\((?!.*(?:vendor|node_modules|\.config|\.json|manifest|package\.json|bower|composer|Gemfile|requirements\.txt|Cargo\.toml|\.lock))/g,
838
+ /addEventListener\s*\(\s*['"]push['"](?!.*(?:vendor|node_modules|\.config|\.json|manifest|package\.json|bower|composer|Gemfile|requirements\.txt|Cargo\.toml|\.lock))/g,
839
+ /expo-notifications(?!.*(?:vendor|node_modules|\.config|\.json|manifest|package\.json|bower|composer|Gemfile|requirements\.txt|Cargo\.toml|\.lock))/g,
840
+ /react-native-push-notification(?!.*(?:vendor|node_modules|\.config|\.json|manifest|package\.json|bower|composer|Gemfile|requirements\.txt|Cargo\.toml|\.lock))/g,
834
841
  // Python — Django push notification libraries
835
- /(?:import|from)\s+(?:webpush|pywebpush|push_notifications|django_push_notifications)/gi,
836
- /webpush\.send\s*\(/gi,
842
+ /(?:import|from)\s+(?:webpush|pywebpush|push_notifications|django_push_notifications)(?!.*(?:vendor|node_modules|\.config|\.json|manifest|package\.json|bower|composer|Gemfile|requirements\.txt|Cargo\.toml|\.lock))/gi,
843
+ /webpush\.send\s*\((?!.*(?:vendor|node_modules|\.config|\.json|manifest|package\.json|bower|composer|Gemfile|requirements\.txt|Cargo\.toml|\.lock))/gi,
837
844
  // PHP — web-push-php library
838
- /(?:new\s+)?WebPush\s*\(\s*\[/gi,
839
- /\$webPush->sendOneNotification/gi,
845
+ /(?:new\s+)?WebPush\s*\(\s*\[(?!.*(?:vendor|node_modules|\.config|\.json|manifest|package\.json|bower|composer|Gemfile|requirements\.txt|Cargo\.toml|\.lock))/gi,
846
+ /\$webPush->sendOneNotification(?!.*(?:vendor|node_modules|\.config|\.json|manifest|package\.json|bower|composer|Gemfile|requirements\.txt|Cargo\.toml|\.lock))/gi,
840
847
  // Ruby — web-push gem
841
- /WebPush\.payload_send\s*\(/gi
848
+ /WebPush\.payload_send\s*\((?!.*(?:vendor|node_modules|\.config|\.json|manifest|package\.json|bower|composer|Gemfile|requirements\.txt|Cargo\.toml|\.lock))/gi
842
849
  ],
843
850
  fixSuggestion: 'Gate push notification subscription behind parental dashboard setting',
844
851
  penalty: '$53,088 per violation',
@@ -871,11 +878,11 @@ exports.COPPA_RULES = [
871
878
  severity: 'medium',
872
879
  description: 'DangerouslySetInnerHTML or innerHTML with user-controlled content creates XSS vulnerabilities',
873
880
  patterns: [
874
- /dangerouslySetInnerHTML\s*=\s*\{\s*\{\s*__html\s*:\s*(?!['"]<)[^}]*\}\s*\}/gi,
881
+ /dangerouslySetInnerHTML\s*=\s*\{\s*\{\s*__html\s*:\s*(?!['"]<)(?!.*(?:sanitize|Sanitize|purify|Purify|DOMPurify|escape|Escape|clean|xss|JSON\.stringify|sanitized\w*|purified\w*|escaped\w*|cleaned\w*|safeHtml))[^}]*\}\s*\}/gi,
875
882
  /\.innerHTML\s*=\s*\$\{/gi,
876
- /\.innerHTML\s*=\s*(?!['"]?\s*['"]?\s*;)(?!.*[Ll]ocal(?:ize|ization))(?!.*styleContent)[^;]*\b(?:user|input|query|param|req\.|request\.|body\.|data\.)\w*/gi,
877
- /\.html\s*\(\s*(?:user|req\.|request\.|params?\.)/gi,
878
- /v-html\s*=\s*["']?(?!.*sanitize)/gi,
883
+ /\.innerHTML\s*=\s*(?!['"]?\s*['"]?\s*;)(?!['"]\s*$)(?!\s*['"]\s*$)(?!.*(?:[Ll]ocal(?:ize|ization)|styleContent|sanitize|Sanitize|purify|Purify|Escape\.html|clean|xss|sanitized\w*|purified\w*|escaped\w*|cleaned\w*|safeHtml))[^;]*\b(?:user[A-Z]\w*|userInput|user_input|formData|query|param|req\.|request\.|body\.|data\.(?:value|content|body|html|text|detail|innerHTML))\w*/gi,
884
+ /\.html\s*\(\s*(?:userInput|user_input|req\.|request\.|params?\.)/gi,
885
+ /v-html\s*=\s*["']?(?!.*(?:sanitize|purify|clean|escape|xss))(?:.*(?:userInput|user_input|user\.input|formData|req\.|request\.|query\.|rawHtml))/gi,
879
886
  // PHP — echo/print user input without escaping
880
887
  /echo\s+\$_(?:GET|POST|REQUEST)\s*\[/gi,
881
888
  // PHP — WordPress unescaped output
@@ -932,8 +939,8 @@ exports.COPPA_RULES = [
932
939
  severity: 'medium',
933
940
  description: 'External links in child-facing views should trigger a "You are leaving..." modal',
934
941
  patterns: [
935
- /<a[^>]+href=["']https?:\/\/(?!.*(?:privacy|terms|legal|tos|policy|consent|support|help|docs|documentation))[^"']+["'][^>]*target=["']_blank["'][^>]*>/gi,
936
- /window\.open\s*\(\s*['"]https?:\/\/(?!.*(?:privacy|terms|legal|tos|policy))/gi
942
+ /(?!.*(?:admin|Admin|ADMIN|educator|Educator|instructor|teacher|i18n|locale|translation|locales?\/|lang\/|messages\.|translations\.|bundle))<a[^>]+href=["']https?:\/\/(?!.*(?:privacy|terms|legal|tos|policy|consent|support|help|docs|documentation))[^"']+["'][^>]*target=["']_blank["'][^>]*>/gi,
943
+ /(?!.*(?:admin|Admin|ADMIN|educator|Educator|instructor|teacher|i18n|locale|translation|locales?\/|lang\/|messages\.|translations\.|bundle))window\.open\s*\(\s*['"]https?:\/\/(?!.*(?:privacy|terms|legal|tos|policy))/gi
937
944
  ],
938
945
  fixSuggestion: 'Wrap external links in SafeLink component with warning modal',
939
946
  penalty: 'Warning',
@@ -1169,7 +1176,7 @@ exports.AI_AUDIT_RULES = [
1169
1176
  /(?:secret|SECRET|token|TOKEN)\s*[:=]\s*['"](?!process\.env)[a-zA-Z0-9_-]{20,}['"]/gi,
1170
1177
  /SUPABASE_(?:ANON_KEY|SERVICE_ROLE_KEY)\s*[:=]\s*['"]ey[a-zA-Z0-9_.+-]{30,}['"]/gi,
1171
1178
  /FIREBASE_(?:API_KEY|CONFIG)\s*[:=]\s*['"]AI[a-zA-Z0-9_-]{30,}['"]/gi,
1172
- /(?:password|passwd|pwd)\s*[:=]\s*['"](?!process\.env)[^'"]{8,}['"]\s*(?:,|;|\})/gi
1179
+ /(?!.*(?:label|Label|LABEL|placeholder|title|message|error|i18n|translation|locale|t\(|__\())(?:password|passwd|pwd)\s*[:=]\s*['"](?!process\.env)[^'"]{8,}['"]\s*(?:,|;|\})/gi
1173
1180
  ],
1174
1181
  fixSuggestion: 'Move all secrets to environment variables. Use process.env.API_KEY or a secrets manager. Never hardcode credentials.',
1175
1182
  penalty: 'Security exposure — credentials in source code',
@@ -1277,11 +1284,11 @@ exports.AU_SBD_RULES = [
1277
1284
  severity: 'medium',
1278
1285
  description: 'Social interaction features (comments, posts, messaging) detected without corresponding report or block mechanisms. AU SbD Principle 2 requires users to have tools to protect themselves from harmful interactions.',
1279
1286
  patterns: [
1280
- /(?:addComment|postComment|submitComment|createComment|commentCreate)\s*(?:=|:|\()/gi,
1281
- /(?:sendMessage|createMessage|postMessage|submitMessage|messageCreate)\s*(?:=|:|\()/gi,
1282
- /(?:createPost|submitPost|publishPost|addPost|postCreate)\s*(?:=|:|\()/gi,
1283
- /(?:addReview|submitReview|createReview|postReview|reviewCreate)\s*(?:=|:|\()/gi,
1284
- /(?:shareContent|createShare|submitShare)\s*(?:=|:|\()/gi,
1287
+ /(?!.*(?:interface |type |implements |extends |PropTypes|\.d\.ts|@types\/|declare |abstract |readonly |generic |<T>|<T,))(?!.*(?:\?: |: string|: number|: boolean|: any|: void|: never|: unknown|Promise<|Array<|Record<|Partial<|Required<))(?:addComment|postComment|submitComment|createComment|commentCreate)\s*(?:=|:|\()/gi,
1288
+ /(?!.*(?:interface |type |implements |extends |PropTypes|\.d\.ts|@types\/|declare |abstract |readonly |generic |<T>|<T,))(?!.*(?:\?: |: string|: number|: boolean|: any|: void|: never|: unknown|Promise<|Array<|Record<|Partial<|Required<))(?:sendMessage|createMessage|postMessage|submitMessage|messageCreate)\s*(?:=|:|\()/gi,
1289
+ /(?!.*(?:interface |type |implements |extends |PropTypes|\.d\.ts|@types\/|declare |abstract |readonly |generic |<T>|<T,))(?!.*(?:\?: |: string|: number|: boolean|: any|: void|: never|: unknown|Promise<|Array<|Record<|Partial<|Required<))(?:createPost|submitPost|publishPost|addPost|postCreate)\s*(?:=|:|\()/gi,
1290
+ /(?!.*(?:interface |type |implements |extends |PropTypes|\.d\.ts|@types\/|declare |abstract |readonly |generic |<T>|<T,))(?!.*(?:\?: |: string|: number|: boolean|: any|: void|: never|: unknown|Promise<|Array<|Record<|Partial<|Required<))(?:addReview|submitReview|createReview|postReview|reviewCreate)\s*(?:=|:|\()/gi,
1291
+ /(?!.*(?:interface |type |implements |extends |PropTypes|\.d\.ts|@types\/|declare |abstract |readonly |generic |<T>|<T,))(?!.*(?:\?: |: string|: number|: boolean|: any|: void|: never|: unknown|Promise<|Array<|Record<|Partial<|Required<))(?:shareContent|createShare|submitShare)\s*(?:=|:|\()/gi,
1285
1292
  ],
1286
1293
  fixSuggestion: 'Implement report and block mechanisms alongside every social feature. Users must be able to report harmful content and block abusive accounts. AU SbD Principle 2: user empowerment and autonomy.',
1287
1294
  penalty: 'Social features without safety controls',
@@ -1500,7 +1507,7 @@ class HaloEngine {
1500
1507
  // 1. config.loadedRules — pre-compiled rules from CLI API fetch
1501
1508
  // 2. config.rulesPath — YAML file (legacy)
1502
1509
  // 3. config.packs — load from bundled rules.json filtered by pack
1503
- // 4. Default — hardcoded arrays with legacy boolean flags
1510
+ // 4. Default — Halo 2.0: load ALL packs from bundled rules.json
1504
1511
  if (config.loadedRules && config.loadedRules.length > 0) {
1505
1512
  // Priority 1: Pre-loaded rules (from CLI API fetch or external source)
1506
1513
  this.rules = config.loadedRules;
@@ -1516,20 +1523,11 @@ class HaloEngine {
1516
1523
  this.rules = jsonRules.length > 0 ? jsonRules : exports.COPPA_RULES;
1517
1524
  }
1518
1525
  else {
1519
- // Priority 4: Hardcoded arrays with legacy boolean flags
1520
- this.rules = config.rules
1521
- ? exports.COPPA_RULES.filter(r => config.rules.includes(r.id))
1522
- : exports.COPPA_RULES;
1523
- // Append optional packs via legacy boolean flags
1524
- if (config.ethical) {
1525
- this.rules = [...this.rules, ...exports.ETHICAL_RULES];
1526
- }
1527
- if (config.aiAudit) {
1528
- this.rules = [...this.rules, ...exports.AI_AUDIT_RULES];
1529
- }
1530
- if (config.sectorAuSbd) {
1531
- this.rules = [...this.rules, ...exports.AU_SBD_RULES];
1532
- }
1526
+ // Priority 4 (Halo 2.0): Load ALL packs from bundled rules.json
1527
+ // All 160 rules active as loose pre-filters. AI Review Board handles precision.
1528
+ const resolvedPacks = HaloEngine.resolvePacks(config);
1529
+ const jsonRules = this.loadBundledRulesByPack(resolvedPacks);
1530
+ this.rules = jsonRules.length > 0 ? jsonRules : exports.COPPA_RULES;
1533
1531
  }
1534
1532
  // Sprint 15: Filter out disabled rules.
1535
1533
  // Static list ensures rules are disabled regardless of source (API, cache, bundled JSON, hardcoded).
@@ -1537,29 +1535,22 @@ class HaloEngine {
1537
1535
  // Sprint 15: Zero-GT rule actions
1538
1536
  // DISABLE: zero GT entries, cannot validate precision
1539
1537
  // CUT (to proposed tier): zero GT entries, pattern too broad for production
1540
- const DISABLED_RULE_IDS = new Set([
1541
- 'coppa-bio-012', // 0% precision, all FP rebuild needed
1542
- // coppa-notif-013 removed rebuilt Sprint 18 with push-only patterns
1543
- 'coppa-sec-010', // Sprint 16 W1: 100% FP (0/3 TP) all hits wrong
1544
- 'coppa-ugc-014', // Sprint 16 W1: 100% FP (0/3 TP) all hits wrong
1545
- 'coppa-edu-019', // Zero GT — teacher registration patterns too narrow to validate
1546
- 'coppa-default-020', // Zero GT overlaps with AU-SBD-001 default public profiles
1547
- 'ETHICAL-004', // Zero GT manipulative notification language too broad
1548
- 'ETHICAL-005', // Zero GT — artificial scarcity patterns too broad
1549
- 'AU-SBD-003', // Zero GT DM detection patterns too broad
1550
- 'AU-SBD-005', // Sprint 16 W1: 18.2% precision — autoplay pattern fires on media player APIs
1551
- 'AU-SBD-006', // Zero GT location sharing patterns too broad
1552
- // CUT to proposed tier (zero GT, pattern too broad for production)
1553
- 'AI-AUDIT-001', // Placeholder analytics low real-world signal
1554
- 'AI-AUDIT-003', // Hallucinated URLs — low real-world signal
1555
- 'AI-AUDIT-004', // Copy-paste tracking boilerplate — too broad
1556
- 'AI-AUDIT-006', // TODO/FIXME compliance — noise in real codebases
1557
- 'AU-SBD-004', // Algorithmic feeds — pattern too broad for production
1558
- // Sprint 17 Day 0: Zero-GT rules validated against 5 repos — patterns don't match real-world code
1559
- 'ut-sb142-003', // Default DM access — patterns use naming conventions no real app uses (0 hits across Moodle, Discourse, Rocket.Chat, Element, Mastodon)
1560
- 'ut-sb142-004', // Missing parental tools — 2 hits across 5 repos, both FP. API/bundled pattern mismatch. Needs rebuild
1561
- ]);
1562
- this.rules = this.rules.filter(r => r.is_active !== false && !DISABLED_RULE_IDS.has(r.id));
1538
+ // ── Halo 2.0 (Sprint 18 W2): ALL rules active as loose pre-filters ──
1539
+ // Precision is handled by Tier 3 (AI Review Board), not by disabling rules.
1540
+ // Previously-disabled rules are now active. The AI layer handles false positive
1541
+ // elimination via two-agent consensus, graduated patterns, and enforcement context.
1542
+ // See: Halo 2.0 Architecture doc for rationale.
1543
+ //
1544
+ // Rules with known regex limitations (Sprint 18 W1 analysis):
1545
+ // High-FP (AI handles): coppa-sec-015, coppa-bio-012, AU-SBD-002, coppa-sec-006,
1546
+ // ut-sb142-001, coppa-ext-017, coppa-sec-010, coppa-ugc-014
1547
+ // Low-GT (need more scanning): behavioral-*, aadc-*, lgpd-*, pipeda-*
1548
+ // Zero-GT (untested): ETHICAL-004/005, AU-SBD-003/006, AI-AUDIT-001/003/004/006
1549
+ this.rules = this.rules.filter(r => r.is_active !== false);
1550
+ // Filter by specific rule IDs if provided (e.g., config.rules = ['coppa-auth-001'])
1551
+ if (config.rules && config.rules.length > 0) {
1552
+ this.rules = this.rules.filter(r => config.rules.includes(r.id));
1553
+ }
1563
1554
  if (config.severityFilter) {
1564
1555
  this.rules = this.rules.filter(r => config.severityFilter.includes(r.severity));
1565
1556
  }
@@ -1578,23 +1569,28 @@ class HaloEngine {
1578
1569
  }
1579
1570
  }
1580
1571
  /**
1581
- * Resolve legacy boolean flags to pack IDs.
1582
- * Maps ethical/aiAudit/sectorAuSbd booleans to their pack ID equivalents.
1583
- * Always includes 'coppa' as the base pack.
1572
+ * Resolve pack IDs from config.
1573
+ * Halo 2.0: defaults to ALL packs (loose pre-filter for AI Review Board).
1574
+ * Legacy boolean flags still supported for backward compatibility.
1584
1575
  */
1585
1576
  static resolvePacks(config) {
1586
1577
  if (config.packs)
1587
1578
  return config.packs;
1588
- const packs = ['coppa'];
1589
- if (config.ethical)
1590
- packs.push('ethical');
1591
- if (config.aiAudit)
1592
- packs.push('ai-audit');
1593
- if (config.sectorAuSbd)
1594
- packs.push('au-sbd');
1595
- if (config.sectorAuOsa)
1596
- packs.push('au-osa');
1597
- return packs;
1579
+ // Legacy boolean flags — if ANY are set, use selective mode
1580
+ if (config.ethical || config.aiAudit || config.sectorAuSbd || config.sectorAuOsa) {
1581
+ const packs = ['coppa'];
1582
+ if (config.ethical)
1583
+ packs.push('ethical');
1584
+ if (config.aiAudit)
1585
+ packs.push('ai-audit');
1586
+ if (config.sectorAuSbd)
1587
+ packs.push('au-sbd');
1588
+ if (config.sectorAuOsa)
1589
+ packs.push('au-osa');
1590
+ return packs;
1591
+ }
1592
+ // Halo 2.0 default: all packs active
1593
+ return [...exports.ALL_PACK_IDS];
1598
1594
  }
1599
1595
  /**
1600
1596
  * Get the tree-sitter parser for advanced AST analysis
@@ -1742,6 +1738,11 @@ class HaloEngine {
1742
1738
  'coppa-auth-001', // Auth patterns in test helpers
1743
1739
  'coppa-sec-015', // XSS patterns in security test cases
1744
1740
  'coppa-sec-006', // Sprint 11a: HTTP URLs in test config (e.g., http://example-storage.com in envs/test.py)
1741
+ // Sprint 18: Batch C — bundled JSON rules that FP in test files
1742
+ 'dsa-ephemeral-009', // Ephemeral content — test files trigger retention patterns
1743
+ 'AU-OSA-009', // AU Online Safety — test mocks trigger safety patterns
1744
+ 'AU-OSA-010', // AU Online Safety — test fixtures trigger safety patterns
1745
+ 'dpdp-tracking-ban-001', // India DPDP — test configs trigger tracking patterns
1745
1746
  ]);
1746
1747
  // Rules that should be suppressed in consent/compliance implementation files
1747
1748
  // These rules flag patterns that are REQUIRED in consent implementations
@@ -1919,15 +1920,23 @@ class HaloEngine {
1919
1920
  // Sprint 10: For coppa-sec-015 (XSS): skip innerHTML assignments that are already sanitized
1920
1921
  // Y.Escape.html(), DOMPurify.sanitize(), etc. show the developer IS handling XSS
1921
1922
  if (rule.id === 'coppa-sec-015') {
1922
- if (/(?:escape\.html|dompurify|sanitize|purify)\s*\(/i.test(lineContent)) {
1923
+ if (/(?:escape\.html|dompurify|sanitize|purify|xss|\.clean\()\s*\(?/i.test(lineContent)) {
1924
+ continue;
1925
+ }
1926
+ // Also skip if the value is a sanitized variable
1927
+ if (/(?:sanitized|purified|escaped|cleaned|safeHtml)\w*/i.test(lineContent)) {
1923
1928
  continue;
1924
1929
  }
1925
1930
  }
1926
- // Sprint 10: For coppa-ui-008: skip admin tool registration (LTI cartridge, Brickfield, etc.)
1927
- // These are admin/developer-facing forms, not child-facing registration
1931
+ // Sprint 10/18: For coppa-ui-008: skip admin/vendor/widget registration contexts
1932
+ // These are admin/developer-facing forms or vendor widget methods, not child-facing registration
1928
1933
  if (rule.id === 'coppa-ui-008') {
1929
- if (/cartridge[_-]?registration|brickfield|registersetting|tool_configure/i.test(lineContent) ||
1930
- /cartridge[_-]?registration|brickfield|registersetting/i.test(normalizedPath)) {
1934
+ if (/cartridge[_-]?registration|brickfield|registersetting|tool_configure|sitepolicy|registration_confirmation/i.test(lineContent) ||
1935
+ /cartridge[_-]?registration|brickfield|registersetting|yui2?[-_]container|sitepolicy|registration_confirmation/i.test(normalizedPath)) {
1936
+ continue;
1937
+ }
1938
+ // YUI widget method calls (registerForm is a container widget method, not child registration)
1939
+ if (/\.(registerForm|subscribe)\s*\(/i.test(lineContent) && /superclass|subscribe|\.call\b/i.test(lineContent)) {
1931
1940
  continue;
1932
1941
  }
1933
1942
  }
@@ -2114,5 +2123,28 @@ var framework_detect_1 = require("./framework-detect");
2114
2123
  Object.defineProperty(exports, "detectFramework", { enumerable: true, get: function () { return framework_detect_1.detectFramework; } });
2115
2124
  var index_1 = require("./scaffolds/index");
2116
2125
  Object.defineProperty(exports, "SCAFFOLD_REGISTRY", { enumerable: true, get: function () { return index_1.SCAFFOLD_REGISTRY; } });
2126
+ // SDK Intelligence (Sprint 17)
2127
+ var sdk_intelligence_1 = require("./sdk-intelligence");
2128
+ Object.defineProperty(exports, "detectSDKsFromPackageJson", { enumerable: true, get: function () { return sdk_intelligence_1.detectSDKsFromPackageJson; } });
2129
+ Object.defineProperty(exports, "generateSDKContext", { enumerable: true, get: function () { return sdk_intelligence_1.generateSDKContext; } });
2130
+ Object.defineProperty(exports, "detectSDKs", { enumerable: true, get: function () { return sdk_intelligence_1.detectSDKs; } });
2131
+ Object.defineProperty(exports, "SDK_RISK_DATABASE", { enumerable: true, get: function () { return sdk_intelligence_1.SDK_RISK_DATABASE; } });
2132
+ // Tier Context (Sprint 19) — 4-tier feature gating
2133
+ var tier_context_1 = require("./tier-context");
2134
+ Object.defineProperty(exports, "createTierContext", { enumerable: true, get: function () { return tier_context_1.createTierContext; } });
2135
+ Object.defineProperty(exports, "resolveTierFromKey", { enumerable: true, get: function () { return tier_context_1.resolveTierFromKey; } });
2136
+ Object.defineProperty(exports, "getUpgradeCTA", { enumerable: true, get: function () { return tier_context_1.getUpgradeCTA; } });
2137
+ Object.defineProperty(exports, "getTierComparison", { enumerable: true, get: function () { return tier_context_1.getTierComparison; } });
2138
+ Object.defineProperty(exports, "TIER_LIMITS", { enumerable: true, get: function () { return tier_context_1.TIER_LIMITS; } });
2139
+ Object.defineProperty(exports, "TIER_FEATURES", { enumerable: true, get: function () { return tier_context_1.TIER_FEATURES; } });
2140
+ Object.defineProperty(exports, "TIER_DISPLAY_NAMES", { enumerable: true, get: function () { return tier_context_1.TIER_DISPLAY_NAMES; } });
2141
+ // COPPA 2.0 Countdown (Sprint 19) — shared across all output surfaces
2142
+ var coppa_countdown_1 = require("./coppa-countdown");
2143
+ Object.defineProperty(exports, "getCoppaCountdown", { enumerable: true, get: function () { return coppa_countdown_1.getCoppaCountdown; } });
2144
+ Object.defineProperty(exports, "formatCoppaCountdownCLI", { enumerable: true, get: function () { return coppa_countdown_1.formatCoppaCountdownCLI; } });
2145
+ Object.defineProperty(exports, "formatCoppaCountdownMarkdown", { enumerable: true, get: function () { return coppa_countdown_1.formatCoppaCountdownMarkdown; } });
2146
+ Object.defineProperty(exports, "formatCoppaCountdownPDF", { enumerable: true, get: function () { return coppa_countdown_1.formatCoppaCountdownPDF; } });
2147
+ Object.defineProperty(exports, "COPPA_2_ENFORCEMENT_DATE", { enumerable: true, get: function () { return coppa_countdown_1.COPPA_2_ENFORCEMENT_DATE; } });
2148
+ Object.defineProperty(exports, "PENALTY_PER_VIOLATION_PER_DAY", { enumerable: true, get: function () { return coppa_countdown_1.PENALTY_PER_VIOLATION_PER_DAY; } });
2117
2149
  exports.default = HaloEngine;
2118
2150
  //# sourceMappingURL=index.js.map