@rigstate/cli 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/.env.example +5 -0
  2. package/IMPLEMENTATION.md +239 -0
  3. package/QUICK_START.md +220 -0
  4. package/README.md +150 -0
  5. package/dist/index.cjs +3987 -0
  6. package/dist/index.cjs.map +1 -0
  7. package/dist/index.d.cts +1 -0
  8. package/dist/index.d.ts +1 -0
  9. package/dist/index.js +3964 -0
  10. package/dist/index.js.map +1 -0
  11. package/install.sh +15 -0
  12. package/package.json +53 -0
  13. package/src/commands/check.ts +329 -0
  14. package/src/commands/config.ts +81 -0
  15. package/src/commands/daemon.ts +197 -0
  16. package/src/commands/env.ts +158 -0
  17. package/src/commands/fix.ts +140 -0
  18. package/src/commands/focus.ts +134 -0
  19. package/src/commands/hooks.ts +163 -0
  20. package/src/commands/init.ts +282 -0
  21. package/src/commands/link.ts +45 -0
  22. package/src/commands/login.ts +35 -0
  23. package/src/commands/mcp.ts +73 -0
  24. package/src/commands/nexus.ts +81 -0
  25. package/src/commands/override.ts +65 -0
  26. package/src/commands/scan.ts +242 -0
  27. package/src/commands/sync-rules.ts +191 -0
  28. package/src/commands/sync.ts +339 -0
  29. package/src/commands/watch.ts +283 -0
  30. package/src/commands/work.ts +172 -0
  31. package/src/daemon/bridge-listener.ts +127 -0
  32. package/src/daemon/core.ts +184 -0
  33. package/src/daemon/factory.ts +45 -0
  34. package/src/daemon/file-watcher.ts +97 -0
  35. package/src/daemon/guardian-monitor.ts +133 -0
  36. package/src/daemon/heuristic-engine.ts +203 -0
  37. package/src/daemon/intervention-protocol.ts +128 -0
  38. package/src/daemon/telemetry.ts +23 -0
  39. package/src/daemon/types.ts +18 -0
  40. package/src/hive/gateway.ts +74 -0
  41. package/src/hive/protocol.ts +29 -0
  42. package/src/hive/scrubber.ts +72 -0
  43. package/src/index.ts +85 -0
  44. package/src/nexus/council.ts +103 -0
  45. package/src/nexus/dispatcher.ts +133 -0
  46. package/src/utils/config.ts +83 -0
  47. package/src/utils/files.ts +95 -0
  48. package/src/utils/governance.ts +128 -0
  49. package/src/utils/logger.ts +66 -0
  50. package/src/utils/manifest.ts +18 -0
  51. package/src/utils/rule-engine.ts +292 -0
  52. package/src/utils/skills-provisioner.ts +153 -0
  53. package/src/utils/version.ts +1 -0
  54. package/src/utils/watchdog.ts +215 -0
  55. package/tsconfig.json +29 -0
  56. package/tsup.config.ts +11 -0
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Guardian Monitor - Checks files against Guardian rules
3
+ */
4
+
5
+ import axios from 'axios';
6
+ import fs from 'fs/promises';
7
+ import path from 'path';
8
+ import { checkFile, type EffectiveRule, type Violation, type CheckResult } from '../utils/rule-engine.js';
9
+
10
+ const CACHE_FILE = '.rigstate/rules-cache.json';
11
+ const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
12
+
13
+ interface CachedRules {
14
+ timestamp: string;
15
+ projectId: string;
16
+ rules: EffectiveRule[];
17
+ settings: { lmax: number; lmax_warning: number };
18
+ }
19
+
20
+ export interface GuardianMonitor {
21
+ loadRules(): Promise<void>;
22
+ checkFile(filePath: string): Promise<CheckResult>;
23
+ getRuleCount(): number;
24
+ getRules(): EffectiveRule[];
25
+ }
26
+
27
+ export function createGuardianMonitor(
28
+ projectId: string,
29
+ apiUrl: string,
30
+ apiKey: string
31
+ ): GuardianMonitor {
32
+ let rules: EffectiveRule[] = [];
33
+ let lastFetch: number = 0;
34
+
35
+ const loadRules = async (): Promise<void> => {
36
+ // Check if cache is fresh
37
+ if (rules.length > 0 && Date.now() - lastFetch < CACHE_TTL_MS) {
38
+ return;
39
+ }
40
+
41
+ try {
42
+ // Try API first
43
+ const response = await axios.get(`${apiUrl}/api/v1/guardian/rules`, {
44
+ params: { project_id: projectId },
45
+ headers: { Authorization: `Bearer ${apiKey}` },
46
+ timeout: 10000
47
+ });
48
+
49
+ if (response.data.success && response.data.data.rules) {
50
+ rules = response.data.data.rules;
51
+ lastFetch = Date.now();
52
+
53
+ // Save to cache
54
+ await saveCachedRules(projectId, rules);
55
+ return;
56
+ }
57
+ } catch (error) {
58
+ // Try cache fallback
59
+ const cached = await loadCachedRules(projectId);
60
+ if (cached) {
61
+ rules = cached.rules;
62
+ lastFetch = Date.now();
63
+ return;
64
+ }
65
+ }
66
+
67
+ // No rules available
68
+ rules = [];
69
+ };
70
+
71
+ const checkFileImpl = async (filePath: string): Promise<CheckResult> => {
72
+ // Ensure rules are loaded
73
+ await loadRules();
74
+
75
+ if (rules.length === 0) {
76
+ return {
77
+ file: filePath,
78
+ violations: [],
79
+ passed: true
80
+ };
81
+ }
82
+
83
+ const absolutePath = path.resolve(process.cwd(), filePath);
84
+ return checkFile(absolutePath, rules, process.cwd());
85
+ };
86
+
87
+ const getRuleCount = (): number => rules.length;
88
+ const getRules = (): EffectiveRule[] => rules;
89
+
90
+ return {
91
+ loadRules,
92
+ checkFile: checkFileImpl,
93
+ getRuleCount,
94
+ getRules
95
+ };
96
+ }
97
+
98
+ async function loadCachedRules(projectId: string): Promise<CachedRules | null> {
99
+ try {
100
+ const cachePath = path.join(process.cwd(), CACHE_FILE);
101
+ const content = await fs.readFile(cachePath, 'utf-8');
102
+ const cached: CachedRules = JSON.parse(content);
103
+
104
+ if (cached.projectId !== projectId) {
105
+ return null;
106
+ }
107
+
108
+ return cached;
109
+ } catch {
110
+ return null;
111
+ }
112
+ }
113
+
114
+ async function saveCachedRules(projectId: string, rules: EffectiveRule[]): Promise<void> {
115
+ try {
116
+ const cacheDir = path.join(process.cwd(), '.rigstate');
117
+ await fs.mkdir(cacheDir, { recursive: true });
118
+
119
+ const cached: CachedRules = {
120
+ timestamp: new Date().toISOString(),
121
+ projectId,
122
+ rules,
123
+ settings: { lmax: 400, lmax_warning: 350 }
124
+ };
125
+
126
+ await fs.writeFile(
127
+ path.join(cacheDir, 'rules-cache.json'),
128
+ JSON.stringify(cached, null, 2)
129
+ );
130
+ } catch {
131
+ // Silently fail cache write
132
+ }
133
+ }
@@ -0,0 +1,203 @@
1
+ import { readFile, writeFile, mkdir } from 'fs/promises';
2
+ import { dirname } from 'path';
3
+ import path from 'path';
4
+ import axios from 'axios';
5
+
6
+ export interface SkillTrigger {
7
+ skillId: string;
8
+ patterns: {
9
+ imports?: string[]; // e.g. "@stripe/stripe-js"
10
+ content?: string[]; // Regex strings e.g. "payment_intent"
11
+ files?: string[]; // Glob patterns e.g. "**/*.sql"
12
+ violation_id?: string;
13
+ metric_threshold?: number;
14
+ };
15
+ confidence: 'high' | 'medium' | 'low';
16
+ }
17
+
18
+ export interface HeuristicMatch {
19
+ skillId: string;
20
+ file: string;
21
+ reason: string;
22
+ confidence: 'high' | 'medium' | 'low';
23
+ }
24
+
25
+ // TODO: In the future, this should fetch from the Rigstate API (The Registry)
26
+ const GLOBAL_HEURISTICS: SkillTrigger[] = [
27
+ {
28
+ skillId: 'payment-expert',
29
+ patterns: {
30
+ imports: ['@stripe/', 'stripe'],
31
+ content: ['PaymentIntent', 'CheckoutSession'],
32
+ },
33
+ confidence: 'high'
34
+ },
35
+ {
36
+ skillId: 'rigstate-integrity-gate',
37
+ patterns: {
38
+ files: ['**/release.config.js', '**/manifest.json', '**/.rigstate/release/*'],
39
+ content: ['[CORE INTEGRITY]', 'prepare_release']
40
+ },
41
+ confidence: 'high'
42
+ },
43
+ {
44
+ skillId: 'database-architect',
45
+ patterns: {
46
+ files: ['**/*.sql', '**/schema.prisma', '**/migrations/*'],
47
+ imports: ['@supabase/supabase-js', 'drizzle-orm', 'prisma']
48
+ },
49
+ confidence: 'medium'
50
+ }
51
+ ];
52
+
53
+ export class HeuristicEngine {
54
+ private rules: SkillTrigger[] = [];
55
+ private cachePath: string;
56
+
57
+ constructor() {
58
+ this.cachePath = path.join(process.cwd(), '.rigstate', 'cache', 'heuristics.json');
59
+ this.loadRules();
60
+ }
61
+
62
+ private async loadRules() {
63
+ try {
64
+ const cached = await readFile(this.cachePath, 'utf-8');
65
+ const data = JSON.parse(cached);
66
+ if (Array.isArray(data) && data.length > 0) {
67
+ this.rules = data;
68
+ return;
69
+ }
70
+ } catch (e) {
71
+ // No cache, use defaults
72
+ }
73
+ this.rules = GLOBAL_HEURISTICS;
74
+ }
75
+
76
+ async refreshRules(projectId: string, apiUrl: string, apiKey: string) {
77
+ try {
78
+ // Ensure cache directory exists
79
+ await mkdir(dirname(this.cachePath), { recursive: true });
80
+
81
+ const endpoint = `${apiUrl}/api/v1/skills/triggers`;
82
+
83
+ const response = await axios.get(endpoint, {
84
+ headers: {
85
+ 'x-api-key': apiKey,
86
+ 'Content-Type': 'application/json'
87
+ }
88
+ });
89
+
90
+ if (response.data && Array.isArray(response.data.triggers)) {
91
+ const cloudRules = response.data.triggers;
92
+
93
+ // Write to cache
94
+ await writeFile(this.cachePath, JSON.stringify(cloudRules, null, 2));
95
+
96
+ this.rules = cloudRules;
97
+ return true;
98
+ }
99
+ } catch (error) {
100
+ return false;
101
+ }
102
+ }
103
+
104
+ async analyzeFile(filePath: string, metrics?: { lineCount: number, rules: any[] }): Promise<HeuristicMatch[]> {
105
+ try {
106
+ const content = await readFile(filePath, 'utf-8');
107
+ const matches: HeuristicMatch[] = [];
108
+
109
+ // Use dynamic rules
110
+ const activeRules = this.rules.length > 0 ? this.rules : GLOBAL_HEURISTICS;
111
+
112
+ for (const heuristic of activeRules) {
113
+ const match = this.checkHeuristic(filePath, content, heuristic, metrics);
114
+ if (match) {
115
+ matches.push(match);
116
+ }
117
+ }
118
+
119
+ return matches;
120
+ } catch (error) {
121
+ // Ignore file read errors (deleted files, etc)
122
+ return [];
123
+ }
124
+ }
125
+
126
+ private checkHeuristic(
127
+ filePath: string,
128
+ content: string,
129
+ heuristic: SkillTrigger,
130
+ metrics?: { lineCount: number, rules: any[] }
131
+ ): HeuristicMatch | null {
132
+ // 0. Check Metric Thresholds (The 80% Rule)
133
+ if (heuristic.patterns.metric_threshold && metrics) {
134
+ const lineLimitRule = metrics.rules.find(r => r.rule_type === 'MAX_FILE_LINES');
135
+ if (lineLimitRule) {
136
+ const limit = (lineLimitRule.value as any).limit;
137
+ const threshold = limit * heuristic.patterns.metric_threshold;
138
+
139
+ if (metrics.lineCount >= threshold && metrics.lineCount < limit) {
140
+ return {
141
+ skillId: heuristic.skillId,
142
+ file: filePath,
143
+ reason: `File reached ${Math.round((metrics.lineCount / limit) * 100)}% of its line limit (${metrics.lineCount}/${limit})`,
144
+ confidence: 'high'
145
+ };
146
+ }
147
+ }
148
+ }
149
+
150
+ // 1. Check File Path Patterns
151
+ if (heuristic.patterns.files) {
152
+ // Simple endsWith check for now, ideally use micromatch
153
+ const matchesFile = heuristic.patterns.files.some(pattern => {
154
+ if (pattern.startsWith('**/*')) return filePath.endsWith(pattern.replace('**/*', ''));
155
+ return filePath.includes(pattern);
156
+ });
157
+ if (matchesFile) {
158
+ return {
159
+ skillId: heuristic.skillId,
160
+ file: filePath,
161
+ reason: `Matches file pattern: ${heuristic.patterns.files.join(', ')}`,
162
+ confidence: heuristic.confidence
163
+ };
164
+ }
165
+ }
166
+
167
+ // 2. Check Imports
168
+ if (heuristic.patterns.imports) {
169
+ for (const imp of heuristic.patterns.imports) {
170
+ // Regex to find import or require
171
+ const importRegex = new RegExp(`(import .* from ['"]${imp}|require\\(['"]${imp})`, 'i');
172
+ if (importRegex.test(content)) {
173
+ return {
174
+ skillId: heuristic.skillId,
175
+ file: filePath,
176
+ reason: `Detected import: ${imp}`,
177
+ confidence: heuristic.confidence
178
+ };
179
+ }
180
+ }
181
+ }
182
+
183
+ // 3. Check Content
184
+ if (heuristic.patterns.content) {
185
+ for (const pattern of heuristic.patterns.content) {
186
+ if (content.includes(pattern)) {
187
+ return {
188
+ skillId: heuristic.skillId,
189
+ file: filePath,
190
+ reason: `Detected content pattern: ${pattern}`,
191
+ confidence: heuristic.confidence
192
+ };
193
+ }
194
+ }
195
+ }
196
+
197
+ return null;
198
+ }
199
+ }
200
+
201
+ export function createHeuristicEngine() {
202
+ return new HeuristicEngine();
203
+ }
@@ -0,0 +1,128 @@
1
+ import chalk from 'chalk';
2
+
3
+ import * as fs from 'fs';
4
+ import * as path from 'path';
5
+
6
+ export type GovernanceMode = 'OPEN' | 'SOFT_LOCK' | 'HARD_LOCK';
7
+
8
+ export interface InterventionDecision {
9
+ mode: GovernanceMode;
10
+ message: string;
11
+ blockCommit: boolean;
12
+ }
13
+
14
+ /**
15
+ * The Silent Sentinel (Frank's Enforcer)
16
+ * Determines the severity of an event and the required system response.
17
+ */
18
+ export class InterventionProtocol {
19
+ private activeViolators = new Set<string>();
20
+
21
+ /**
22
+ * Registers a violation outcome to update the global lock state.
23
+ */
24
+ registerViolation(filePath: string, decision: InterventionDecision) {
25
+ if (decision.mode === 'HARD_LOCK') {
26
+ this.activeViolators.add(filePath);
27
+ this.syncLockFile();
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Clears any active locks for a specific file (e.g. after a fix).
33
+ */
34
+ clear(filePath: string) {
35
+ if (this.activeViolators.has(filePath)) {
36
+ this.activeViolators.delete(filePath);
37
+ this.syncLockFile();
38
+ }
39
+ }
40
+
41
+ private syncLockFile() {
42
+ try {
43
+ const lockDir = path.join(process.cwd(), '.rigstate');
44
+ if (!fs.existsSync(lockDir)) fs.mkdirSync(lockDir, { recursive: true });
45
+
46
+ const lockPath = path.join(lockDir, 'guardian.lock');
47
+
48
+ if (this.activeViolators.size > 0) {
49
+ const content = `HARD_LOCK_ACTIVE\nTimestamp: ${new Date().toISOString()}\n\nBlocking Files:\n${Array.from(this.activeViolators).join('\n')}`;
50
+ fs.writeFileSync(lockPath, content, 'utf-8');
51
+ } else {
52
+ if (fs.existsSync(lockPath)) fs.unlinkSync(lockPath);
53
+ }
54
+ } catch (e) {
55
+ console.error('Failed to sync guardian lock file:', e);
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Evaluate a Heuristic Trigger (Preventative)
61
+ */
62
+ evaluateTrigger(skillId: string, confidence: string): InterventionDecision {
63
+ // Example: If a skill is marked as 'HARD_LOCK' in its metadata, we block.
64
+ // For now, most triggers are informational (JIT provisioning).
65
+
66
+ if (skillId === 'rigstate-integrity-gate') {
67
+ return {
68
+ mode: 'SOFT_LOCK',
69
+ message: 'Integrity Gate detected. Release Manifest required before final push.',
70
+ blockCommit: false // Soft lock just warns
71
+ };
72
+ }
73
+
74
+ return {
75
+ mode: 'OPEN',
76
+ message: `Predictive activation: ${skillId}`,
77
+ blockCommit: false
78
+ };
79
+ }
80
+
81
+ /**
82
+ * Evaluate a Guardian Violation (Corrective)
83
+ */
84
+ evaluateViolation(ruleId: string, severity: 'critical' | 'warning' | 'info'): InterventionDecision {
85
+ if (severity === 'critical' || (severity as any) === 'error') {
86
+ return {
87
+ mode: 'HARD_LOCK',
88
+ message: `CRITICAL VIOLATION: ${ruleId}. System Integrity Compromised.`,
89
+ blockCommit: true
90
+ };
91
+ }
92
+
93
+ if (severity === 'warning') {
94
+ return {
95
+ mode: 'SOFT_LOCK',
96
+ message: `Warning: ${ruleId}. Review recommended.`,
97
+ blockCommit: false
98
+ };
99
+ }
100
+
101
+ return {
102
+ mode: 'OPEN',
103
+ message: 'Info notice.',
104
+ blockCommit: false
105
+ };
106
+ }
107
+
108
+ /**
109
+ * Logs the intervention to the console with appropriate visual weight
110
+ */
111
+ enforce(decision: InterventionDecision) {
112
+ if (decision.mode === 'OPEN') return;
113
+
114
+ const icon = decision.mode === 'HARD_LOCK' ? '🚫' : '⚠️';
115
+ const color = decision.mode === 'HARD_LOCK' ? chalk.bgRed.white.bold : chalk.yellow.bold;
116
+
117
+ console.log('\n' + color(` ${icon} [${decision.mode}] INTERVENTION `));
118
+ console.log(chalk.redBright(` ${decision.message}`));
119
+
120
+ if (decision.blockCommit) {
121
+ console.log(chalk.dim(' 🔒 Commit functionality is logically suspended until fixed.'));
122
+ }
123
+ }
124
+ }
125
+
126
+ export function createInterventionProtocol() {
127
+ return new InterventionProtocol();
128
+ }
@@ -0,0 +1,23 @@
1
+ import axios from 'axios';
2
+
3
+ /**
4
+ * Reports skill usage to the Rigstate Cloud.
5
+ */
6
+ export async function trackSkillUsage(
7
+ apiUrl: string,
8
+ apiKey: string,
9
+ projectId: string,
10
+ skillId: string
11
+ ) {
12
+ try {
13
+ await axios.post(`${apiUrl}/api/v1/skills/usage`, {
14
+ projectId,
15
+ skillName: skillId,
16
+ status: 'ACTIVATED'
17
+ }, {
18
+ headers: { 'x-api-key': apiKey }
19
+ });
20
+ } catch (e) {
21
+ // Silhouette feedback failure - do not interrupt user
22
+ }
23
+ }
@@ -0,0 +1,18 @@
1
+ export interface DaemonConfig {
2
+ projectId: string;
3
+ apiUrl: string;
4
+ apiKey: string;
5
+ watchPath: string;
6
+ checkOnChange: boolean;
7
+ bridgeEnabled: boolean;
8
+ verbose: boolean;
9
+ }
10
+
11
+ export interface DaemonState {
12
+ isRunning: boolean;
13
+ startedAt: string | null;
14
+ filesChecked: number;
15
+ violationsFound: number;
16
+ tasksProcessed: number;
17
+ lastActivity: string | null;
18
+ }
@@ -0,0 +1,74 @@
1
+
2
+ import axios, { AxiosInstance } from 'axios';
3
+ import { ImmuneSignal } from './protocol';
4
+ import { HiveScrubber } from './scrubber';
5
+ import chalk from 'chalk';
6
+
7
+ /**
8
+ * THE HIVE GATEWAY
9
+ * Connects the local Rigstate instance to the Global Hive Mind (rigstate.com).
10
+ */
11
+ export class HiveGateway {
12
+ private client: AxiosInstance;
13
+ private enabled: boolean;
14
+ private lastSignalTime: number = 0;
15
+ private readonly MIN_INTERVAL_MS = 5000; // Throttle: Max 1 signal per 5s
16
+
17
+ constructor(baseUrl: string, token?: string) {
18
+ this.enabled = !!token;
19
+
20
+ if (!this.enabled) {
21
+ console.log(chalk.dim('⚠️ Hive Gateway disabled (No Token provided). Running in localized mode.'));
22
+ }
23
+
24
+ this.client = axios.create({
25
+ baseURL: baseUrl,
26
+ headers: {
27
+ 'Authorization': `Bearer ${token}`,
28
+ 'Content-Type': 'application/json',
29
+ 'X-Rigstate-Client': 'CLI-0.2.0'
30
+ },
31
+ timeout: 5000
32
+ });
33
+ }
34
+
35
+ /**
36
+ * Transmit an Immune Signal to the Hive.
37
+ * Includes Pre-Flight Scrubbing and Throttling.
38
+ */
39
+ public async transmit(signal: ImmuneSignal): Promise<boolean> {
40
+ if (!this.enabled) return false;
41
+
42
+ // 1. THROTTLE CHECK
43
+ const now = Date.now();
44
+ if (now - this.lastSignalTime < this.MIN_INTERVAL_MS) {
45
+ console.warn(chalk.yellow('⏳ Hive Gateway Throttled. Signal dropped to preventing spam.'));
46
+ return false;
47
+ }
48
+
49
+ // 2. SCRUBBER VERIFICATION (Double Check)
50
+ // Even if Frank ran it, we run it again here at the edge.
51
+ const scrubResult = HiveScrubber.scrub(signal.ruleContent);
52
+ if (scrubResult.riskScore > 20) {
53
+ console.error(chalk.red(`🛑 HIVE BLOCKED: Signal contains sensitive data (Risk: ${scrubResult.riskScore})`));
54
+ return false;
55
+ }
56
+
57
+ // 3. TRANSMISSION
58
+ try {
59
+ console.log(chalk.blue(`📡 Uplinking to Hive... [${signal.vector}]`));
60
+
61
+ // Using the scrubbed content just to be 100% safe
62
+ const payload = { ...signal, ruleContent: scrubResult.sanitizedContent };
63
+
64
+ await this.client.post('/signal', payload);
65
+
66
+ this.lastSignalTime = now;
67
+ console.log(chalk.green('✅ Signal Received by Hive Core. Knowledge Shared.'));
68
+ return true;
69
+ } catch (error: any) {
70
+ console.error(chalk.red(`❌ Hive Transmission Failed: ${error.message}`));
71
+ return false;
72
+ }
73
+ }
74
+ }
@@ -0,0 +1,29 @@
1
+
2
+ /**
3
+ * THE HIVE PROTOCOL
4
+ * The standard format for "Immune Signals" shared between Rigstate Instances.
5
+ */
6
+
7
+ export interface ImmuneSignal {
8
+ id: string; // UUID
9
+ type: 'SECURITY_PATCH' | 'PERFORMANCE_FIX' | 'ARCH_VIOLATION';
10
+
11
+ // Abstracted vector (e.g. "Next.js Server Action")
12
+ vector: string;
13
+
14
+ // The rule itself (Sanitized via Scrubber)
15
+ ruleContent: string;
16
+
17
+ // Metrics proving why this is good
18
+ confidenceScore: number; // 0-100 (Based on local tests passed)
19
+
20
+ schemaVersion: '1.0';
21
+ timestamp: string;
22
+ }
23
+
24
+ export interface HiveSyncConfig {
25
+ enabled: boolean;
26
+ endpoint: string;
27
+ mode: 'CONSUME_ONLY' | 'CONTRIBUTE' | 'FULL_DUPLEX';
28
+ minConfidenceThreshold: number; // Only import rules with score > 80
29
+ }
@@ -0,0 +1,72 @@
1
+
2
+ /**
3
+ * THE HIVE SCRUBBER
4
+ * Sanitizes local rules and signals before they are broadcast to the Global Registry.
5
+ * Ensures strict anonymity.
6
+ */
7
+
8
+ export interface ScrubberResult {
9
+ sanitizedContent: string;
10
+ redactionCount: number;
11
+ riskScore: number; // 0-100 (If too high, do not broadcast)
12
+ }
13
+
14
+ export class HiveScrubber {
15
+
16
+ // Patterns that definitely identify a project and MUST be removed
17
+ private static SENSITIVE_PATTERNS = [
18
+ /(api_key|secret|token|password)[\s]*[:=][\s]*['"][a-zA-Z0-9_\-]+['"]/gi, // Secrets
19
+ /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, // Emails
20
+ /(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\. (?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/g, // IPs
21
+ /postgres:\/\/[^:]+:[^@]+@/g, // DB Connection Strings
22
+ ];
23
+
24
+ // Generic replacements for project-specific terms to keep the rule abstract
25
+ private static ABSTRACTION_MAP: Record<string, string> = {
26
+ 'Vibeline': '{PROJECT_NAME}',
27
+ 'Rigstate': '{FRAMEWORK}',
28
+ 'Steinhofve': '{USER}',
29
+ };
30
+
31
+ /**
32
+ * Scrubs a string (rule content or log excerpt) of sensitive data.
33
+ */
34
+ public static scrub(content: string, customTerms: string[] = []): ScrubberResult {
35
+ let scrubbed = content;
36
+ let count = 0;
37
+ let risk = 0;
38
+
39
+ // 1. Remove Hard Credentials (High Risk)
40
+ this.SENSITIVE_PATTERNS.forEach(pattern => {
41
+ if (pattern.test(scrubbed)) {
42
+ scrubbed = scrubbed.replace(pattern, '[REDACTED_CREDENTIAL]');
43
+ count++;
44
+ risk += 50; // High penalty for finding secrets
45
+ }
46
+ });
47
+
48
+ // 2. Abstract Project Specifics (Medium Risk)
49
+ Object.entries(this.ABSTRACTION_MAP).forEach(([term, replacement]) => {
50
+ const regex = new RegExp(term, 'gi');
51
+ if (regex.test(scrubbed)) {
52
+ scrubbed = scrubbed.replace(regex, replacement);
53
+ count++;
54
+ }
55
+ });
56
+
57
+ // 3. Scrub Custom Terms (e.g. Table names passed from context)
58
+ customTerms.forEach(term => {
59
+ const regex = new RegExp(term, 'gi');
60
+ if (regex.test(scrubbed)) {
61
+ scrubbed = scrubbed.replace(regex, '{ENTITY}');
62
+ count++;
63
+ }
64
+ });
65
+
66
+ return {
67
+ sanitizedContent: scrubbed,
68
+ redactionCount: count,
69
+ riskScore: Math.min(risk, 100)
70
+ };
71
+ }
72
+ }