@rigour-labs/core 4.2.3 → 4.3.1

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.
@@ -1,8 +1,36 @@
1
+ /**
2
+ * Model version — bump when new fine-tuned GGUF is published.
3
+ * The RLAIF pipeline uploads new models to HuggingFace, and
4
+ * model-manager checks this version to auto-update.
5
+ */
6
+ export const MODEL_VERSION = '1';
1
7
  /** All supported model definitions */
2
8
  export const MODELS = {
3
9
  deep: {
4
10
  tier: 'deep',
5
- name: 'Qwen2.5-Coder-0.5B-Instruct',
11
+ name: 'Rigour-Deep-v1 (Qwen2.5-Coder-0.5B fine-tuned)',
12
+ filename: `rigour-deep-v${MODEL_VERSION}-q4_k_m.gguf`,
13
+ url: `https://huggingface.co/rigour-labs/rigour-deep-v1-gguf/resolve/main/rigour-deep-v${MODEL_VERSION}-q4_k_m.gguf`,
14
+ sizeBytes: 350_000_000,
15
+ sizeHuman: '350MB',
16
+ },
17
+ pro: {
18
+ tier: 'pro',
19
+ name: 'Rigour-Pro-v1 (Qwen2.5-Coder-1.5B fine-tuned)',
20
+ filename: `rigour-pro-v${MODEL_VERSION}-q4_k_m.gguf`,
21
+ url: `https://huggingface.co/rigour-labs/rigour-pro-v1-gguf/resolve/main/rigour-pro-v${MODEL_VERSION}-q4_k_m.gguf`,
22
+ sizeBytes: 900_000_000,
23
+ sizeHuman: '900MB',
24
+ },
25
+ };
26
+ /**
27
+ * Fallback stock models — used when fine-tuned model is not yet
28
+ * available on HuggingFace (initial setup / first-time users).
29
+ */
30
+ export const FALLBACK_MODELS = {
31
+ deep: {
32
+ tier: 'deep',
33
+ name: 'Qwen2.5-Coder-0.5B-Instruct (stock)',
6
34
  filename: 'qwen2.5-coder-0.5b-instruct-q4_k_m.gguf',
7
35
  url: 'https://huggingface.co/Qwen/Qwen2.5-Coder-0.5B-Instruct-GGUF/resolve/main/qwen2.5-coder-0.5b-instruct-q4_k_m.gguf',
8
36
  sizeBytes: 350_000_000,
@@ -10,7 +38,7 @@ export const MODELS = {
10
38
  },
11
39
  pro: {
12
40
  tier: 'pro',
13
- name: 'Qwen2.5-Coder-1.5B-Instruct',
41
+ name: 'Qwen2.5-Coder-1.5B-Instruct (stock)',
14
42
  filename: 'qwen2.5-coder-1.5b-instruct-q4_k_m.gguf',
15
43
  url: 'https://huggingface.co/Qwen/Qwen2.5-Coder-1.5B-Instruct-GGUF/resolve/main/qwen2.5-coder-1.5b-instruct-q4_k_m.gguf',
16
44
  sizeBytes: 900_000_000,
@@ -9,6 +9,25 @@ export interface RigourDB {
9
9
  * Returns null if better-sqlite3 is not available.
10
10
  */
11
11
  export declare function openDatabase(dbPath?: string): RigourDB | null;
12
+ /**
13
+ * Compact the database — prune old data, reclaim disk space.
14
+ * Retention policy: keep last `retainDays` of findings, merge old patterns.
15
+ */
16
+ export declare function compactDatabase(retainDays?: number): CompactResult;
17
+ export interface CompactResult {
18
+ pruned: number;
19
+ patternsDecayed: number;
20
+ sizeBefore: number;
21
+ sizeAfter: number;
22
+ }
23
+ /**
24
+ * Get database file size in bytes. Returns 0 if DB doesn't exist.
25
+ */
26
+ export declare function getDatabaseSize(): number;
27
+ /**
28
+ * Reset the database — delete and recreate from scratch.
29
+ */
30
+ export declare function resetDatabase(): void;
12
31
  /**
13
32
  * Check if SQLite is available (better-sqlite3 installed)
14
33
  */
@@ -26,7 +26,15 @@ function loadDatabase() {
26
26
  }
27
27
  const RIGOUR_DIR = path.join(os.homedir(), '.rigour');
28
28
  const DB_PATH = path.join(RIGOUR_DIR, 'rigour.db');
29
+ /** Current schema version — bump when adding migrations. */
30
+ const SCHEMA_VERSION = 2;
29
31
  const SCHEMA_SQL = `
32
+ -- Schema version tracking
33
+ CREATE TABLE IF NOT EXISTS meta (
34
+ key TEXT PRIMARY KEY,
35
+ value TEXT NOT NULL
36
+ );
37
+
30
38
  -- Every scan result, forever
31
39
  CREATE TABLE IF NOT EXISTS scans (
32
40
  id TEXT PRIMARY KEY,
@@ -114,8 +122,9 @@ export function openDatabase(dbPath) {
114
122
  // WAL mode for better concurrent read performance
115
123
  db.pragma('journal_mode = WAL');
116
124
  db.pragma('foreign_keys = ON');
117
- // Run schema migration
125
+ // Run schema creation + migrations
118
126
  db.exec(SCHEMA_SQL);
127
+ runMigrations(db);
119
128
  return {
120
129
  db,
121
130
  close() {
@@ -123,6 +132,84 @@ export function openDatabase(dbPath) {
123
132
  },
124
133
  };
125
134
  }
135
+ /**
136
+ * Run incremental schema migrations based on stored version.
137
+ */
138
+ function runMigrations(db) {
139
+ const row = db.prepare("SELECT value FROM meta WHERE key = 'schema_version'").get();
140
+ const current = row ? parseInt(row.value, 10) : 0;
141
+ if (current < 1) {
142
+ // v1: base schema (already created by SCHEMA_SQL)
143
+ db.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES ('schema_version', '1')").run();
144
+ }
145
+ if (current < 2) {
146
+ // v2: retention indexes for compaction queries
147
+ db.exec(`
148
+ CREATE INDEX IF NOT EXISTS idx_findings_file ON findings(file);
149
+ CREATE INDEX IF NOT EXISTS idx_scans_repo_ts ON scans(repo, timestamp);
150
+ `);
151
+ db.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES ('schema_version', '2')").run();
152
+ }
153
+ // Future: if (current < 3) { ... ALTER TABLE ... }
154
+ }
155
+ /**
156
+ * Compact the database — prune old data, reclaim disk space.
157
+ * Retention policy: keep last `retainDays` of findings, merge old patterns.
158
+ */
159
+ export function compactDatabase(retainDays = 90) {
160
+ const Db = loadDatabase();
161
+ if (!Db)
162
+ return { pruned: 0, patternsDecayed: 0, sizeBefore: 0, sizeAfter: 0 };
163
+ const resolvedPath = DB_PATH;
164
+ const sizeBefore = fs.existsSync(resolvedPath) ? fs.statSync(resolvedPath).size : 0;
165
+ const db = new Db(resolvedPath);
166
+ db.pragma('journal_mode = WAL');
167
+ const cutoff = Date.now() - (retainDays * 24 * 60 * 60 * 1000);
168
+ let pruned = 0;
169
+ let patternsDecayed = 0;
170
+ try {
171
+ db.transaction(() => {
172
+ // 1. Delete old findings (keep scan records for trend lines)
173
+ const r1 = db.prepare(`
174
+ DELETE FROM findings WHERE scan_id IN (
175
+ SELECT id FROM scans WHERE timestamp < ?
176
+ )
177
+ `).run(cutoff);
178
+ pruned += r1.changes;
179
+ // 2. Prune weak patterns (never grew, seen < 3 times)
180
+ const r2 = db.prepare("DELETE FROM patterns WHERE strength < 0.3 AND times_seen < 3").run();
181
+ patternsDecayed += r2.changes;
182
+ // 3. Prune orphaned feedback
183
+ db.prepare("DELETE FROM feedback WHERE finding_id NOT IN (SELECT id FROM findings)").run();
184
+ // 4. Prune old codebase index entries
185
+ db.prepare("DELETE FROM codebase WHERE last_indexed < ?").run(cutoff);
186
+ })();
187
+ // 5. Reclaim disk space
188
+ db.exec('VACUUM');
189
+ }
190
+ finally {
191
+ db.close();
192
+ }
193
+ const sizeAfter = fs.existsSync(resolvedPath) ? fs.statSync(resolvedPath).size : 0;
194
+ return { pruned, patternsDecayed, sizeBefore, sizeAfter };
195
+ }
196
+ /**
197
+ * Get database file size in bytes. Returns 0 if DB doesn't exist.
198
+ */
199
+ export function getDatabaseSize() {
200
+ return fs.existsSync(DB_PATH) ? fs.statSync(DB_PATH).size : 0;
201
+ }
202
+ /**
203
+ * Reset the database — delete and recreate from scratch.
204
+ */
205
+ export function resetDatabase() {
206
+ if (fs.existsSync(DB_PATH))
207
+ fs.removeSync(DB_PATH);
208
+ if (fs.existsSync(DB_PATH + '-wal'))
209
+ fs.removeSync(DB_PATH + '-wal');
210
+ if (fs.existsSync(DB_PATH + '-shm'))
211
+ fs.removeSync(DB_PATH + '-shm');
212
+ }
126
213
  /**
127
214
  * Check if SQLite is available (better-sqlite3 installed)
128
215
  */
@@ -9,6 +9,7 @@ export declare function insertFindings(store: RigourDB, scanId: string, failures
9
9
  */
10
10
  export declare function getFindingsForScan(store: RigourDB, scanId: string): any[];
11
11
  /**
12
- * Get all deep analysis findings for a repo.
12
+ * Get deep analysis and high-confidence AST findings for a repo.
13
+ * Used by local memory to match known patterns against new scans.
13
14
  */
14
15
  export declare function getDeepFindings(store: RigourDB, repo: string, limit?: number): any[];
@@ -25,13 +25,14 @@ export function getFindingsForScan(store, scanId) {
25
25
  return stmt.all(scanId);
26
26
  }
27
27
  /**
28
- * Get all deep analysis findings for a repo.
28
+ * Get deep analysis and high-confidence AST findings for a repo.
29
+ * Used by local memory to match known patterns against new scans.
29
30
  */
30
31
  export function getDeepFindings(store, repo, limit = 50) {
31
32
  const stmt = store.db.prepare(`
32
33
  SELECT f.* FROM findings f
33
34
  JOIN scans s ON f.scan_id = s.id
34
- WHERE s.repo = ? AND f.source = 'llm'
35
+ WHERE s.repo = ? AND (f.source = 'llm' OR f.source = 'hybrid' OR f.confidence >= 0.7)
35
36
  ORDER BY f.confidence DESC LIMIT ?
36
37
  `);
37
38
  return stmt.all(repo, limit);
@@ -2,8 +2,10 @@
2
2
  * Rigour Brain — SQLite storage layer.
3
3
  * Everything in one file: ~/.rigour/rigour.db
4
4
  */
5
- export { openDatabase, isSQLiteAvailable, RIGOUR_DIR, DB_PATH } from './db.js';
6
- export type { RigourDB } from './db.js';
5
+ export { openDatabase, isSQLiteAvailable, compactDatabase, getDatabaseSize, resetDatabase, RIGOUR_DIR, DB_PATH } from './db.js';
6
+ export type { RigourDB, CompactResult } from './db.js';
7
7
  export { insertScan, getRecentScans, getScoreTrendFromDB, getTopIssues } from './scans.js';
8
8
  export { insertFindings, getFindingsForScan, getDeepFindings } from './findings.js';
9
9
  export { reinforcePattern, decayPatterns, getStrongPatterns, getPatterns, getHardRules } from './patterns.js';
10
+ export { checkLocalPatterns, persistAndReinforce, getProjectStats } from './local-memory.js';
11
+ export type { ProjectStats } from './local-memory.js';
@@ -2,7 +2,8 @@
2
2
  * Rigour Brain — SQLite storage layer.
3
3
  * Everything in one file: ~/.rigour/rigour.db
4
4
  */
5
- export { openDatabase, isSQLiteAvailable, RIGOUR_DIR, DB_PATH } from './db.js';
5
+ export { openDatabase, isSQLiteAvailable, compactDatabase, getDatabaseSize, resetDatabase, RIGOUR_DIR, DB_PATH } from './db.js';
6
6
  export { insertScan, getRecentScans, getScoreTrendFromDB, getTopIssues } from './scans.js';
7
7
  export { insertFindings, getFindingsForScan, getDeepFindings } from './findings.js';
8
8
  export { reinforcePattern, decayPatterns, getStrongPatterns, getPatterns, getHardRules } from './patterns.js';
9
+ export { checkLocalPatterns, persistAndReinforce, getProjectStats } from './local-memory.js';
@@ -0,0 +1,37 @@
1
+ import type { Failure } from '../types/index.js';
2
+ import type { DeepFinding } from '../inference/types.js';
3
+ /**
4
+ * Pre-scan: check local SQLite for known patterns → produce instant findings.
5
+ * Returns findings that match known project patterns, WITHOUT model inference.
6
+ *
7
+ * @param cwd - Absolute project root path
8
+ * @param fileList - Relative file paths (from FileFacts.path or globby)
9
+ */
10
+ export declare function checkLocalPatterns(cwd: string, fileList: string[]): DeepFinding[];
11
+ /**
12
+ * Post-scan: persist findings and reinforce patterns.
13
+ * Wrapped in a single transaction for atomicity.
14
+ * Called after every scan (check, scan, scan --deep).
15
+ */
16
+ export declare function persistAndReinforce(cwd: string, report: {
17
+ status: string;
18
+ failures: Failure[];
19
+ stats: any;
20
+ }, meta?: {
21
+ deepTier?: string;
22
+ deepModel?: string;
23
+ }): void;
24
+ /**
25
+ * Get project learning stats (for display in scan output).
26
+ */
27
+ export declare function getProjectStats(cwd: string): ProjectStats | null;
28
+ export interface ProjectStats {
29
+ totalScans: number;
30
+ learnedPatterns: number;
31
+ hardRules: number;
32
+ topPatterns: Array<{
33
+ name: string;
34
+ strength: number;
35
+ timesSeen: number;
36
+ }>;
37
+ }
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Local Project Memory — the hybrid intelligence layer.
3
+ *
4
+ * Before deep scan: checks local SQLite for known patterns in this project.
5
+ * After any scan: stores verified findings and reinforces patterns.
6
+ * Result: Rigour gets smarter every time you use it, code never leaves the machine.
7
+ */
8
+ import path from 'path';
9
+ import { openDatabase } from './db.js';
10
+ import { insertScan, getRecentScans } from './scans.js';
11
+ import { insertFindings, getDeepFindings } from './findings.js';
12
+ import { reinforcePattern, getStrongPatterns, getHardRules, decayPatterns } from './patterns.js';
13
+ import { Logger } from '../utils/logger.js';
14
+ /** Minimum pattern strength to produce instant findings (skip LLM). */
15
+ const INSTANT_MATCH_THRESHOLD = 0.7;
16
+ /** Max local findings to inject per scan (avoid flooding). */
17
+ const MAX_LOCAL_FINDINGS = 15;
18
+ /** Min confidence to reuse a past finding from local memory. */
19
+ const MIN_REUSE_CONFIDENCE = 0.7;
20
+ /**
21
+ * Build a map of relative file path → known issues from past findings.
22
+ */
23
+ function buildFileIssueMap(recentFindings) {
24
+ const map = new Map();
25
+ for (const f of recentFindings) {
26
+ const existing = map.get(f.file) || [];
27
+ existing.push(f);
28
+ map.set(f.file, existing);
29
+ }
30
+ return map;
31
+ }
32
+ /**
33
+ * Pre-scan: check local SQLite for known patterns → produce instant findings.
34
+ * Returns findings that match known project patterns, WITHOUT model inference.
35
+ *
36
+ * @param cwd - Absolute project root path
37
+ * @param fileList - Relative file paths (from FileFacts.path or globby)
38
+ */
39
+ export function checkLocalPatterns(cwd, fileList) {
40
+ const db = openDatabase();
41
+ if (!db)
42
+ return [];
43
+ const repoName = path.basename(cwd);
44
+ const findings = [];
45
+ try {
46
+ const patterns = getStrongPatterns(db, repoName, INSTANT_MATCH_THRESHOLD);
47
+ if (patterns.length === 0)
48
+ return [];
49
+ // Include both AST and LLM findings from history
50
+ const recentFindings = getDeepFindings(db, repoName, 200);
51
+ if (recentFindings.length === 0)
52
+ return [];
53
+ const fileIssueMap = buildFileIssueMap(recentFindings);
54
+ // fileList contains relative paths — match directly against DB (also relative)
55
+ for (const relPath of fileList) {
56
+ const known = fileIssueMap.get(relPath);
57
+ if (!known)
58
+ continue;
59
+ for (const issue of known) {
60
+ if ((issue.confidence ?? 0) < MIN_REUSE_CONFIDENCE)
61
+ continue;
62
+ const matchingPattern = patterns.find((p) => p.pattern === issue.category && p.strength >= INSTANT_MATCH_THRESHOLD);
63
+ if (!matchingPattern)
64
+ continue;
65
+ findings.push({
66
+ category: issue.category,
67
+ severity: issue.severity || 'medium',
68
+ file: relPath,
69
+ line: issue.line ?? undefined,
70
+ description: `[Local Memory] ${issue.description}`,
71
+ suggestion: issue.suggestion || 'Review this known issue.',
72
+ confidence: Math.min(matchingPattern.strength, issue.confidence ?? 0.5),
73
+ });
74
+ if (findings.length >= MAX_LOCAL_FINDINGS)
75
+ break;
76
+ }
77
+ if (findings.length >= MAX_LOCAL_FINDINGS)
78
+ break;
79
+ }
80
+ }
81
+ catch (error) {
82
+ Logger.warn(`Local memory check failed: ${error}`);
83
+ }
84
+ finally {
85
+ db?.db.close();
86
+ }
87
+ return findings;
88
+ }
89
+ /**
90
+ * Post-scan: persist findings and reinforce patterns.
91
+ * Wrapped in a single transaction for atomicity.
92
+ * Called after every scan (check, scan, scan --deep).
93
+ */
94
+ export function persistAndReinforce(cwd, report, meta) {
95
+ const db = openDatabase();
96
+ if (!db)
97
+ return;
98
+ const repoName = path.basename(cwd);
99
+ try {
100
+ // Wrap all writes in a single transaction for atomicity
101
+ const persist = db.db.transaction(() => {
102
+ const scanId = insertScan(db, repoName, report, meta);
103
+ if (report.failures.length > 0) {
104
+ insertFindings(db, scanId, report.failures);
105
+ }
106
+ for (const f of report.failures) {
107
+ const category = f.category || f.id;
108
+ const source = f.source === 'llm' ? 'llm' : 'ast';
109
+ reinforcePattern(db, repoName, category, `${f.title}: ${f.details?.substring(0, 120)}`, source);
110
+ }
111
+ decayPatterns(db, 30);
112
+ });
113
+ persist();
114
+ Logger.info(`Local memory: stored ${report.failures.length} findings, ` +
115
+ `reinforced ${report.failures.length} patterns for ${repoName}`);
116
+ }
117
+ catch (error) {
118
+ Logger.warn(`Local memory persist failed: ${error}`);
119
+ }
120
+ finally {
121
+ db?.db.close();
122
+ }
123
+ }
124
+ /**
125
+ * Get project learning stats (for display in scan output).
126
+ */
127
+ export function getProjectStats(cwd) {
128
+ const db = openDatabase();
129
+ if (!db)
130
+ return null;
131
+ const repoName = path.basename(cwd);
132
+ try {
133
+ const scans = getRecentScans(db, repoName, 100);
134
+ const patterns = getStrongPatterns(db, repoName, 0.3);
135
+ const hardRules = getHardRules(db, repoName);
136
+ return {
137
+ totalScans: scans.length,
138
+ learnedPatterns: patterns.length,
139
+ hardRules: hardRules.length,
140
+ topPatterns: patterns.slice(0, 5).map(p => ({
141
+ name: p.pattern,
142
+ strength: p.strength,
143
+ timesSeen: p.times_seen,
144
+ })),
145
+ };
146
+ }
147
+ catch {
148
+ return null;
149
+ }
150
+ finally {
151
+ db?.db.close();
152
+ }
153
+ }
@@ -187,6 +187,18 @@ export const UNIVERSAL_CONFIG = {
187
187
  ignore_patterns: [],
188
188
  audit_log: true,
189
189
  },
190
+ side_effect_analysis: {
191
+ enabled: true,
192
+ check_unbounded_timers: true,
193
+ check_unbounded_loops: true,
194
+ check_process_lifecycle: true,
195
+ check_recursive_depth: true,
196
+ check_resource_lifecycle: true,
197
+ check_retry_without_limit: true,
198
+ check_circular_triggers: true,
199
+ check_auto_restart: true,
200
+ ignore_patterns: [],
201
+ },
190
202
  deep: {
191
203
  enabled: false,
192
204
  pro: false,
@@ -452,6 +452,40 @@ export declare const GatesSchema: z.ZodObject<{
452
452
  custom_patterns?: string[] | undefined;
453
453
  audit_log?: boolean | undefined;
454
454
  }>>>;
455
+ side_effect_analysis: z.ZodDefault<z.ZodOptional<z.ZodObject<{
456
+ enabled: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
457
+ check_unbounded_timers: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
458
+ check_unbounded_loops: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
459
+ check_process_lifecycle: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
460
+ check_recursive_depth: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
461
+ check_resource_lifecycle: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
462
+ check_retry_without_limit: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
463
+ check_circular_triggers: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
464
+ check_auto_restart: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
465
+ ignore_patterns: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>;
466
+ }, "strip", z.ZodTypeAny, {
467
+ enabled: boolean;
468
+ ignore_patterns: string[];
469
+ check_unbounded_timers: boolean;
470
+ check_unbounded_loops: boolean;
471
+ check_process_lifecycle: boolean;
472
+ check_recursive_depth: boolean;
473
+ check_resource_lifecycle: boolean;
474
+ check_retry_without_limit: boolean;
475
+ check_circular_triggers: boolean;
476
+ check_auto_restart: boolean;
477
+ }, {
478
+ enabled?: boolean | undefined;
479
+ ignore_patterns?: string[] | undefined;
480
+ check_unbounded_timers?: boolean | undefined;
481
+ check_unbounded_loops?: boolean | undefined;
482
+ check_process_lifecycle?: boolean | undefined;
483
+ check_recursive_depth?: boolean | undefined;
484
+ check_resource_lifecycle?: boolean | undefined;
485
+ check_retry_without_limit?: boolean | undefined;
486
+ check_circular_triggers?: boolean | undefined;
487
+ check_auto_restart?: boolean | undefined;
488
+ }>>>;
455
489
  deep: z.ZodDefault<z.ZodOptional<z.ZodObject<{
456
490
  enabled: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
457
491
  pro: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
@@ -725,6 +759,18 @@ export declare const GatesSchema: z.ZodObject<{
725
759
  custom_patterns: string[];
726
760
  audit_log: boolean;
727
761
  };
762
+ side_effect_analysis: {
763
+ enabled: boolean;
764
+ ignore_patterns: string[];
765
+ check_unbounded_timers: boolean;
766
+ check_unbounded_loops: boolean;
767
+ check_process_lifecycle: boolean;
768
+ check_recursive_depth: boolean;
769
+ check_resource_lifecycle: boolean;
770
+ check_retry_without_limit: boolean;
771
+ check_circular_triggers: boolean;
772
+ check_auto_restart: boolean;
773
+ };
728
774
  }, {
729
775
  deep?: {
730
776
  enabled?: boolean | undefined;
@@ -913,6 +959,18 @@ export declare const GatesSchema: z.ZodObject<{
913
959
  custom_patterns?: string[] | undefined;
914
960
  audit_log?: boolean | undefined;
915
961
  } | undefined;
962
+ side_effect_analysis?: {
963
+ enabled?: boolean | undefined;
964
+ ignore_patterns?: string[] | undefined;
965
+ check_unbounded_timers?: boolean | undefined;
966
+ check_unbounded_loops?: boolean | undefined;
967
+ check_process_lifecycle?: boolean | undefined;
968
+ check_recursive_depth?: boolean | undefined;
969
+ check_resource_lifecycle?: boolean | undefined;
970
+ check_retry_without_limit?: boolean | undefined;
971
+ check_circular_triggers?: boolean | undefined;
972
+ check_auto_restart?: boolean | undefined;
973
+ } | undefined;
916
974
  }>;
917
975
  export declare const CommandsSchema: z.ZodObject<{
918
976
  format: z.ZodOptional<z.ZodString>;
@@ -1426,6 +1484,40 @@ export declare const ConfigSchema: z.ZodObject<{
1426
1484
  custom_patterns?: string[] | undefined;
1427
1485
  audit_log?: boolean | undefined;
1428
1486
  }>>>;
1487
+ side_effect_analysis: z.ZodDefault<z.ZodOptional<z.ZodObject<{
1488
+ enabled: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
1489
+ check_unbounded_timers: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
1490
+ check_unbounded_loops: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
1491
+ check_process_lifecycle: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
1492
+ check_recursive_depth: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
1493
+ check_resource_lifecycle: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
1494
+ check_retry_without_limit: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
1495
+ check_circular_triggers: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
1496
+ check_auto_restart: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
1497
+ ignore_patterns: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>;
1498
+ }, "strip", z.ZodTypeAny, {
1499
+ enabled: boolean;
1500
+ ignore_patterns: string[];
1501
+ check_unbounded_timers: boolean;
1502
+ check_unbounded_loops: boolean;
1503
+ check_process_lifecycle: boolean;
1504
+ check_recursive_depth: boolean;
1505
+ check_resource_lifecycle: boolean;
1506
+ check_retry_without_limit: boolean;
1507
+ check_circular_triggers: boolean;
1508
+ check_auto_restart: boolean;
1509
+ }, {
1510
+ enabled?: boolean | undefined;
1511
+ ignore_patterns?: string[] | undefined;
1512
+ check_unbounded_timers?: boolean | undefined;
1513
+ check_unbounded_loops?: boolean | undefined;
1514
+ check_process_lifecycle?: boolean | undefined;
1515
+ check_recursive_depth?: boolean | undefined;
1516
+ check_resource_lifecycle?: boolean | undefined;
1517
+ check_retry_without_limit?: boolean | undefined;
1518
+ check_circular_triggers?: boolean | undefined;
1519
+ check_auto_restart?: boolean | undefined;
1520
+ }>>>;
1429
1521
  deep: z.ZodDefault<z.ZodOptional<z.ZodObject<{
1430
1522
  enabled: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
1431
1523
  pro: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
@@ -1699,6 +1791,18 @@ export declare const ConfigSchema: z.ZodObject<{
1699
1791
  custom_patterns: string[];
1700
1792
  audit_log: boolean;
1701
1793
  };
1794
+ side_effect_analysis: {
1795
+ enabled: boolean;
1796
+ ignore_patterns: string[];
1797
+ check_unbounded_timers: boolean;
1798
+ check_unbounded_loops: boolean;
1799
+ check_process_lifecycle: boolean;
1800
+ check_recursive_depth: boolean;
1801
+ check_resource_lifecycle: boolean;
1802
+ check_retry_without_limit: boolean;
1803
+ check_circular_triggers: boolean;
1804
+ check_auto_restart: boolean;
1805
+ };
1702
1806
  }, {
1703
1807
  deep?: {
1704
1808
  enabled?: boolean | undefined;
@@ -1887,6 +1991,18 @@ export declare const ConfigSchema: z.ZodObject<{
1887
1991
  custom_patterns?: string[] | undefined;
1888
1992
  audit_log?: boolean | undefined;
1889
1993
  } | undefined;
1994
+ side_effect_analysis?: {
1995
+ enabled?: boolean | undefined;
1996
+ ignore_patterns?: string[] | undefined;
1997
+ check_unbounded_timers?: boolean | undefined;
1998
+ check_unbounded_loops?: boolean | undefined;
1999
+ check_process_lifecycle?: boolean | undefined;
2000
+ check_recursive_depth?: boolean | undefined;
2001
+ check_resource_lifecycle?: boolean | undefined;
2002
+ check_retry_without_limit?: boolean | undefined;
2003
+ check_circular_triggers?: boolean | undefined;
2004
+ check_auto_restart?: boolean | undefined;
2005
+ } | undefined;
1890
2006
  }>>>;
1891
2007
  hooks: z.ZodDefault<z.ZodOptional<z.ZodObject<{
1892
2008
  enabled: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
@@ -2117,6 +2233,18 @@ export declare const ConfigSchema: z.ZodObject<{
2117
2233
  custom_patterns: string[];
2118
2234
  audit_log: boolean;
2119
2235
  };
2236
+ side_effect_analysis: {
2237
+ enabled: boolean;
2238
+ ignore_patterns: string[];
2239
+ check_unbounded_timers: boolean;
2240
+ check_unbounded_loops: boolean;
2241
+ check_process_lifecycle: boolean;
2242
+ check_recursive_depth: boolean;
2243
+ check_resource_lifecycle: boolean;
2244
+ check_retry_without_limit: boolean;
2245
+ check_circular_triggers: boolean;
2246
+ check_auto_restart: boolean;
2247
+ };
2120
2248
  };
2121
2249
  hooks: {
2122
2250
  enabled: boolean;
@@ -2331,6 +2459,18 @@ export declare const ConfigSchema: z.ZodObject<{
2331
2459
  custom_patterns?: string[] | undefined;
2332
2460
  audit_log?: boolean | undefined;
2333
2461
  } | undefined;
2462
+ side_effect_analysis?: {
2463
+ enabled?: boolean | undefined;
2464
+ ignore_patterns?: string[] | undefined;
2465
+ check_unbounded_timers?: boolean | undefined;
2466
+ check_unbounded_loops?: boolean | undefined;
2467
+ check_process_lifecycle?: boolean | undefined;
2468
+ check_recursive_depth?: boolean | undefined;
2469
+ check_resource_lifecycle?: boolean | undefined;
2470
+ check_retry_without_limit?: boolean | undefined;
2471
+ check_circular_triggers?: boolean | undefined;
2472
+ check_auto_restart?: boolean | undefined;
2473
+ } | undefined;
2334
2474
  } | undefined;
2335
2475
  hooks?: {
2336
2476
  enabled?: boolean | undefined;
@@ -225,6 +225,19 @@ export const GatesSchema = z.object({
225
225
  ignore_patterns: z.array(z.string()).optional().default([]),
226
226
  audit_log: z.boolean().optional().default(true),
227
227
  }).optional().default({}),
228
+ // v4.3+ Side-Effect Safety Analysis
229
+ side_effect_analysis: z.object({
230
+ enabled: z.boolean().optional().default(true),
231
+ check_unbounded_timers: z.boolean().optional().default(true),
232
+ check_unbounded_loops: z.boolean().optional().default(true),
233
+ check_process_lifecycle: z.boolean().optional().default(true),
234
+ check_recursive_depth: z.boolean().optional().default(true),
235
+ check_resource_lifecycle: z.boolean().optional().default(true),
236
+ check_retry_without_limit: z.boolean().optional().default(true),
237
+ check_circular_triggers: z.boolean().optional().default(true),
238
+ check_auto_restart: z.boolean().optional().default(true),
239
+ ignore_patterns: z.array(z.string()).optional().default([]),
240
+ }).optional().default({}),
228
241
  // v4.0+ Deep Analysis (LLM-powered)
229
242
  deep: z.object({
230
243
  enabled: z.boolean().optional().default(false),
@@ -265,6 +278,7 @@ export const HooksSchema = z.object({
265
278
  'deprecated-apis',
266
279
  'promise-safety',
267
280
  'security-patterns',
281
+ 'side-effect-analysis',
268
282
  'file-size',
269
283
  ]),
270
284
  timeout_ms: z.number().optional().default(5000),