@rigour-labs/core 3.0.5 → 4.0.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 (85) hide show
  1. package/dist/deep/fact-extractor.d.ts +80 -0
  2. package/dist/deep/fact-extractor.js +626 -0
  3. package/dist/deep/index.d.ts +14 -0
  4. package/dist/deep/index.js +12 -0
  5. package/dist/deep/prompts.d.ts +22 -0
  6. package/dist/deep/prompts.js +374 -0
  7. package/dist/deep/verifier.d.ts +16 -0
  8. package/dist/deep/verifier.js +388 -0
  9. package/dist/gates/deep-analysis.d.ts +28 -0
  10. package/dist/gates/deep-analysis.js +302 -0
  11. package/dist/gates/deprecated-apis-rules-lang.d.ts +21 -0
  12. package/dist/gates/deprecated-apis-rules-lang.js +311 -0
  13. package/dist/gates/deprecated-apis-rules-node.d.ts +19 -0
  14. package/dist/gates/deprecated-apis-rules-node.js +199 -0
  15. package/dist/gates/deprecated-apis-rules.d.ts +6 -0
  16. package/dist/gates/deprecated-apis-rules.js +6 -0
  17. package/dist/gates/deprecated-apis.js +1 -502
  18. package/dist/gates/hallucinated-imports-lang.d.ts +16 -0
  19. package/dist/gates/hallucinated-imports-lang.js +374 -0
  20. package/dist/gates/hallucinated-imports-stdlib.d.ts +12 -0
  21. package/dist/gates/hallucinated-imports-stdlib.js +228 -0
  22. package/dist/gates/hallucinated-imports.d.ts +0 -98
  23. package/dist/gates/hallucinated-imports.js +10 -678
  24. package/dist/gates/phantom-apis-data.d.ts +33 -0
  25. package/dist/gates/phantom-apis-data.js +398 -0
  26. package/dist/gates/phantom-apis.js +1 -393
  27. package/dist/gates/phantom-apis.test.js +52 -0
  28. package/dist/gates/promise-safety-helpers.d.ts +19 -0
  29. package/dist/gates/promise-safety-helpers.js +101 -0
  30. package/dist/gates/promise-safety-rules.d.ts +7 -0
  31. package/dist/gates/promise-safety-rules.js +19 -0
  32. package/dist/gates/promise-safety.d.ts +1 -21
  33. package/dist/gates/promise-safety.js +51 -257
  34. package/dist/gates/runner.d.ts +4 -2
  35. package/dist/gates/runner.js +46 -1
  36. package/dist/gates/test-quality-lang.d.ts +30 -0
  37. package/dist/gates/test-quality-lang.js +188 -0
  38. package/dist/gates/test-quality.d.ts +0 -14
  39. package/dist/gates/test-quality.js +13 -186
  40. package/dist/index.d.ts +10 -0
  41. package/dist/index.js +12 -2
  42. package/dist/inference/cloud-provider.d.ts +34 -0
  43. package/dist/inference/cloud-provider.js +126 -0
  44. package/dist/inference/index.d.ts +17 -0
  45. package/dist/inference/index.js +23 -0
  46. package/dist/inference/model-manager.d.ts +26 -0
  47. package/dist/inference/model-manager.js +106 -0
  48. package/dist/inference/sidecar-provider.d.ts +15 -0
  49. package/dist/inference/sidecar-provider.js +153 -0
  50. package/dist/inference/types.d.ts +77 -0
  51. package/dist/inference/types.js +19 -0
  52. package/dist/pattern-index/indexer-helpers.d.ts +38 -0
  53. package/dist/pattern-index/indexer-helpers.js +111 -0
  54. package/dist/pattern-index/indexer-lang.d.ts +13 -0
  55. package/dist/pattern-index/indexer-lang.js +244 -0
  56. package/dist/pattern-index/indexer-ts.d.ts +22 -0
  57. package/dist/pattern-index/indexer-ts.js +258 -0
  58. package/dist/pattern-index/indexer.d.ts +4 -106
  59. package/dist/pattern-index/indexer.js +58 -707
  60. package/dist/pattern-index/staleness-data.d.ts +6 -0
  61. package/dist/pattern-index/staleness-data.js +262 -0
  62. package/dist/pattern-index/staleness.js +1 -258
  63. package/dist/settings.d.ts +104 -0
  64. package/dist/settings.js +186 -0
  65. package/dist/storage/db.d.ts +16 -0
  66. package/dist/storage/db.js +132 -0
  67. package/dist/storage/findings.d.ts +14 -0
  68. package/dist/storage/findings.js +38 -0
  69. package/dist/storage/index.d.ts +9 -0
  70. package/dist/storage/index.js +8 -0
  71. package/dist/storage/patterns.d.ts +35 -0
  72. package/dist/storage/patterns.js +62 -0
  73. package/dist/storage/scans.d.ts +42 -0
  74. package/dist/storage/scans.js +55 -0
  75. package/dist/templates/index.d.ts +12 -16
  76. package/dist/templates/index.js +11 -527
  77. package/dist/templates/paradigms.d.ts +2 -0
  78. package/dist/templates/paradigms.js +46 -0
  79. package/dist/templates/presets.d.ts +14 -0
  80. package/dist/templates/presets.js +227 -0
  81. package/dist/templates/universal-config.d.ts +2 -0
  82. package/dist/templates/universal-config.js +190 -0
  83. package/dist/types/index.d.ts +438 -15
  84. package/dist/types/index.js +41 -1
  85. package/package.json +6 -2
@@ -0,0 +1,23 @@
1
+ export { MODELS } from './types.js';
2
+ export { SidecarProvider } from './sidecar-provider.js';
3
+ export { CloudProvider } from './cloud-provider.js';
4
+ export { ensureModel, isModelCached, getModelPath, getModelInfo, downloadModel, getModelsDir } from './model-manager.js';
5
+ import { SidecarProvider } from './sidecar-provider.js';
6
+ import { CloudProvider } from './cloud-provider.js';
7
+ /**
8
+ * Create the appropriate inference provider based on options.
9
+ *
10
+ * - No API key → SidecarProvider (local llama.cpp binary)
11
+ * - API key + any provider → CloudProvider (no restrictions, user's key, user's choice)
12
+ */
13
+ export function createProvider(options) {
14
+ if (options.apiKey && options.provider && options.provider !== 'local') {
15
+ return new CloudProvider(options.provider, options.apiKey, {
16
+ baseUrl: options.apiBaseUrl,
17
+ modelName: options.modelName,
18
+ });
19
+ }
20
+ // Default: local sidecar
21
+ const tier = options.pro ? 'pro' : 'deep';
22
+ return new SidecarProvider(tier);
23
+ }
@@ -0,0 +1,26 @@
1
+ import { type ModelTier, type ModelInfo } from './types.js';
2
+ /**
3
+ * Check if a model is already downloaded and valid.
4
+ */
5
+ export declare function isModelCached(tier: ModelTier): boolean;
6
+ /**
7
+ * Get the path to a cached model.
8
+ */
9
+ export declare function getModelPath(tier: ModelTier): string;
10
+ /**
11
+ * Get model info for a tier.
12
+ */
13
+ export declare function getModelInfo(tier: ModelTier): ModelInfo;
14
+ /**
15
+ * Download a model from HuggingFace CDN.
16
+ * Calls onProgress with status updates.
17
+ */
18
+ export declare function downloadModel(tier: ModelTier, onProgress?: (message: string, percent?: number) => void): Promise<string>;
19
+ /**
20
+ * Ensure a model is available, downloading if needed.
21
+ */
22
+ export declare function ensureModel(tier: ModelTier, onProgress?: (message: string, percent?: number) => void): Promise<string>;
23
+ /**
24
+ * Get the models directory path.
25
+ */
26
+ export declare function getModelsDir(): string;
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Model Manager — handles downloading, caching, and verifying GGUF models.
3
+ * Models cached at ~/.rigour/models/
4
+ */
5
+ import path from 'path';
6
+ import fs from 'fs-extra';
7
+ import { RIGOUR_DIR } from '../storage/db.js';
8
+ import { MODELS } from './types.js';
9
+ const MODELS_DIR = path.join(RIGOUR_DIR, 'models');
10
+ /**
11
+ * Check if a model is already downloaded and valid.
12
+ */
13
+ export function isModelCached(tier) {
14
+ const model = MODELS[tier];
15
+ const modelPath = path.join(MODELS_DIR, model.filename);
16
+ if (!fs.existsSync(modelPath))
17
+ return false;
18
+ // Basic size check (within 10% tolerance)
19
+ const stat = fs.statSync(modelPath);
20
+ const tolerance = model.sizeBytes * 0.1;
21
+ return stat.size > model.sizeBytes - tolerance;
22
+ }
23
+ /**
24
+ * Get the path to a cached model.
25
+ */
26
+ export function getModelPath(tier) {
27
+ return path.join(MODELS_DIR, MODELS[tier].filename);
28
+ }
29
+ /**
30
+ * Get model info for a tier.
31
+ */
32
+ export function getModelInfo(tier) {
33
+ return MODELS[tier];
34
+ }
35
+ /**
36
+ * Download a model from HuggingFace CDN.
37
+ * Calls onProgress with status updates.
38
+ */
39
+ export async function downloadModel(tier, onProgress) {
40
+ const model = MODELS[tier];
41
+ const destPath = path.join(MODELS_DIR, model.filename);
42
+ const tempPath = destPath + '.download';
43
+ fs.ensureDirSync(MODELS_DIR);
44
+ // Already cached
45
+ if (isModelCached(tier)) {
46
+ onProgress?.(`Model ${model.name} already cached`, 100);
47
+ return destPath;
48
+ }
49
+ onProgress?.(`Downloading ${model.name} (${model.sizeHuman})...`, 0);
50
+ try {
51
+ const response = await fetch(model.url);
52
+ if (!response.ok) {
53
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
54
+ }
55
+ const contentLength = parseInt(response.headers.get('content-length') || '0', 10);
56
+ const reader = response.body?.getReader();
57
+ if (!reader)
58
+ throw new Error('No response body');
59
+ const writeStream = fs.createWriteStream(tempPath);
60
+ let downloaded = 0;
61
+ let lastProgressPercent = 0;
62
+ while (true) {
63
+ const { done, value } = await reader.read();
64
+ if (done)
65
+ break;
66
+ writeStream.write(Buffer.from(value));
67
+ downloaded += value.length;
68
+ if (contentLength > 0) {
69
+ const percent = Math.round((downloaded / contentLength) * 100);
70
+ if (percent >= lastProgressPercent + 5) { // Report every 5%
71
+ lastProgressPercent = percent;
72
+ onProgress?.(`Downloading ${model.name}: ${percent}%`, percent);
73
+ }
74
+ }
75
+ }
76
+ writeStream.end();
77
+ await new Promise((resolve, reject) => {
78
+ writeStream.on('finish', resolve);
79
+ writeStream.on('error', reject);
80
+ });
81
+ // Atomic rename
82
+ fs.renameSync(tempPath, destPath);
83
+ onProgress?.(`Model ${model.name} ready`, 100);
84
+ return destPath;
85
+ }
86
+ catch (error) {
87
+ // Clean up temp file on failure
88
+ fs.removeSync(tempPath);
89
+ throw error;
90
+ }
91
+ }
92
+ /**
93
+ * Ensure a model is available, downloading if needed.
94
+ */
95
+ export async function ensureModel(tier, onProgress) {
96
+ if (isModelCached(tier)) {
97
+ return getModelPath(tier);
98
+ }
99
+ return downloadModel(tier, onProgress);
100
+ }
101
+ /**
102
+ * Get the models directory path.
103
+ */
104
+ export function getModelsDir() {
105
+ return MODELS_DIR;
106
+ }
@@ -0,0 +1,15 @@
1
+ import type { InferenceProvider, InferenceOptions, ModelTier } from './types.js';
2
+ export declare class SidecarProvider implements InferenceProvider {
3
+ readonly name = "sidecar";
4
+ private binaryPath;
5
+ private modelPath;
6
+ private tier;
7
+ private threads;
8
+ constructor(tier?: ModelTier, threads?: number);
9
+ isAvailable(): Promise<boolean>;
10
+ setup(onProgress?: (message: string) => void): Promise<void>;
11
+ analyze(prompt: string, options?: InferenceOptions): Promise<string>;
12
+ dispose(): void;
13
+ private getPlatformKey;
14
+ private resolveBinaryPath;
15
+ }
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Sidecar Binary Provider — runs inference via pre-compiled llama.cpp binary.
3
+ * Binary ships as @rigour/brain-{platform} optional npm dependency.
4
+ * Falls back to PATH lookup for development/manual installs.
5
+ */
6
+ import { execFile } from 'child_process';
7
+ import { promisify } from 'util';
8
+ import path from 'path';
9
+ import os from 'os';
10
+ import fs from 'fs-extra';
11
+ import { ensureModel, isModelCached, getModelInfo } from './model-manager.js';
12
+ const execFileAsync = promisify(execFile);
13
+ /** Platform → npm package mapping */
14
+ const PLATFORM_PACKAGES = {
15
+ 'darwin-arm64': '@rigour/brain-darwin-arm64',
16
+ 'darwin-x64': '@rigour/brain-darwin-x64',
17
+ 'linux-x64': '@rigour/brain-linux-x64',
18
+ 'linux-arm64': '@rigour/brain-linux-arm64',
19
+ 'win32-x64': '@rigour/brain-win-x64',
20
+ };
21
+ export class SidecarProvider {
22
+ name = 'sidecar';
23
+ binaryPath = null;
24
+ modelPath = null;
25
+ tier;
26
+ threads;
27
+ constructor(tier = 'deep', threads = 4) {
28
+ this.tier = tier;
29
+ this.threads = threads;
30
+ }
31
+ async isAvailable() {
32
+ const binary = await this.resolveBinaryPath();
33
+ return binary !== null;
34
+ }
35
+ async setup(onProgress) {
36
+ // 1. Check/resolve binary
37
+ this.binaryPath = await this.resolveBinaryPath();
38
+ if (this.binaryPath) {
39
+ onProgress?.('✓ Inference engine ready');
40
+ }
41
+ else {
42
+ onProgress?.('⚠ Inference engine not found. Install @rigour/brain-* or add llama-cli to PATH');
43
+ throw new Error('Sidecar binary not found. Run: npm install @rigour/brain-' + this.getPlatformKey());
44
+ }
45
+ // 2. Ensure model is downloaded
46
+ if (!isModelCached(this.tier)) {
47
+ const modelInfo = getModelInfo(this.tier);
48
+ onProgress?.(`⬇ Downloading analysis model (${modelInfo.sizeHuman})...`);
49
+ }
50
+ this.modelPath = await ensureModel(this.tier, (msg, percent) => {
51
+ if (percent !== undefined && percent < 100) {
52
+ onProgress?.(` ${msg}`);
53
+ }
54
+ });
55
+ onProgress?.('✓ Model ready');
56
+ }
57
+ async analyze(prompt, options) {
58
+ if (!this.binaryPath || !this.modelPath) {
59
+ throw new Error('Provider not set up. Call setup() first.');
60
+ }
61
+ const args = [
62
+ '--model', this.modelPath,
63
+ '--prompt', prompt,
64
+ '--n-predict', String(options?.maxTokens || 512),
65
+ '--threads', String(this.threads),
66
+ '--temp', String(options?.temperature || 0.1),
67
+ '--no-display-prompt', // Don't echo the prompt
68
+ '--log-disable', // Suppress llama.cpp logging
69
+ ];
70
+ // JSON grammar constraint if available
71
+ if (options?.jsonMode) {
72
+ args.push('--json');
73
+ }
74
+ try {
75
+ const { stdout, stderr } = await execFileAsync(this.binaryPath, args, {
76
+ timeout: options?.timeout || 60000,
77
+ maxBuffer: 10 * 1024 * 1024, // 10MB
78
+ env: { ...process.env, LLAMA_LOG_DISABLE: '1' },
79
+ });
80
+ // llama.cpp sometimes outputs to stderr for diagnostics — ignore
81
+ return stdout.trim();
82
+ }
83
+ catch (error) {
84
+ if (error.killed) {
85
+ throw new Error(`Inference timed out after ${(options?.timeout || 60000) / 1000}s`);
86
+ }
87
+ throw new Error(`Inference failed: ${error.message}`);
88
+ }
89
+ }
90
+ dispose() {
91
+ // No persistent process to clean up
92
+ this.binaryPath = null;
93
+ this.modelPath = null;
94
+ }
95
+ getPlatformKey() {
96
+ return `${os.platform()}-${os.arch()}`;
97
+ }
98
+ async resolveBinaryPath() {
99
+ const platformKey = this.getPlatformKey();
100
+ // Strategy 1: Check @rigour/brain-{platform} optional dependency
101
+ const packageName = PLATFORM_PACKAGES[platformKey];
102
+ if (packageName) {
103
+ try {
104
+ // Try to resolve from node_modules
105
+ const possiblePaths = [
106
+ // From rigour-core node_modules
107
+ path.join(__dirname, '..', '..', '..', 'node_modules', ...packageName.split('/'), 'bin', 'rigour-brain'),
108
+ // From global node_modules
109
+ path.join(os.homedir(), '.npm-global', 'lib', 'node_modules', ...packageName.split('/'), 'bin', 'rigour-brain'),
110
+ ];
111
+ for (const p of possiblePaths) {
112
+ const binPath = os.platform() === 'win32' ? p + '.exe' : p;
113
+ if (await fs.pathExists(binPath)) {
114
+ return binPath;
115
+ }
116
+ }
117
+ }
118
+ catch {
119
+ // Package not installed
120
+ }
121
+ }
122
+ // Strategy 2: Check ~/.rigour/bin/
123
+ const localBin = path.join(os.homedir(), '.rigour', 'bin', 'rigour-brain');
124
+ const localBinPath = os.platform() === 'win32' ? localBin + '.exe' : localBin;
125
+ if (await fs.pathExists(localBinPath)) {
126
+ return localBinPath;
127
+ }
128
+ // Strategy 3: Check PATH for llama-cli (llama.cpp CLI)
129
+ try {
130
+ const { stdout } = await execFileAsync('which', ['llama-cli']);
131
+ const llamaPath = stdout.trim();
132
+ if (llamaPath && await fs.pathExists(llamaPath)) {
133
+ return llamaPath;
134
+ }
135
+ }
136
+ catch {
137
+ // Not in PATH
138
+ }
139
+ // Strategy 4: Check for llama.cpp server-style binary names
140
+ const altNames = ['llama-cli', 'llama', 'main'];
141
+ for (const name of altNames) {
142
+ try {
143
+ const { stdout } = await execFileAsync('which', [name]);
144
+ if (stdout.trim())
145
+ return stdout.trim();
146
+ }
147
+ catch {
148
+ // Continue
149
+ }
150
+ }
151
+ return null;
152
+ }
153
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Inference provider interface for Rigour deep analysis.
3
+ * Supports sidecar binary (local llama.cpp), cloud APIs (Claude/OpenAI).
4
+ */
5
+ import type { Severity } from '../types/index.js';
6
+ /**
7
+ * Abstract inference provider — all backends implement this.
8
+ */
9
+ export interface InferenceProvider {
10
+ /** Provider name for logging/reporting */
11
+ readonly name: string;
12
+ /** Check if this provider is available (binary exists, API key valid, etc.) */
13
+ isAvailable(): Promise<boolean>;
14
+ /**
15
+ * One-time setup: download model, verify binary, etc.
16
+ * Should show progress to user via callback.
17
+ */
18
+ setup(onProgress?: (message: string) => void): Promise<void>;
19
+ /**
20
+ * Run inference on a prompt. Returns raw text response.
21
+ * Provider handles tokenization, temperature, etc.
22
+ */
23
+ analyze(prompt: string, options?: InferenceOptions): Promise<string>;
24
+ /** Clean up resources (kill process, close connection) */
25
+ dispose(): void;
26
+ }
27
+ export interface InferenceOptions {
28
+ maxTokens?: number;
29
+ temperature?: number;
30
+ timeout?: number;
31
+ jsonMode?: boolean;
32
+ }
33
+ /**
34
+ * A single finding from deep LLM analysis.
35
+ */
36
+ export interface DeepFinding {
37
+ /** Category like 'srp_violation', 'god_function', 'dry_violation' */
38
+ category: string;
39
+ /** Severity level */
40
+ severity: Severity;
41
+ /** Relative file path */
42
+ file: string;
43
+ /** Line number (if available) */
44
+ line?: number;
45
+ /** Human-readable description of the issue */
46
+ description: string;
47
+ /** Actionable suggestion for how to fix */
48
+ suggestion: string;
49
+ /** LLM confidence score 0.0-1.0 */
50
+ confidence: number;
51
+ }
52
+ /**
53
+ * Result of a deep analysis batch.
54
+ */
55
+ export interface DeepAnalysisResult {
56
+ findings: DeepFinding[];
57
+ model: string;
58
+ tokensUsed?: number;
59
+ durationMs: number;
60
+ }
61
+ /**
62
+ * Available model tiers.
63
+ */
64
+ export type ModelTier = 'deep' | 'pro';
65
+ /**
66
+ * Model info for download/caching.
67
+ */
68
+ export interface ModelInfo {
69
+ tier: ModelTier;
70
+ name: string;
71
+ filename: string;
72
+ url: string;
73
+ sizeBytes: number;
74
+ sizeHuman: string;
75
+ }
76
+ /** All supported model definitions */
77
+ export declare const MODELS: Record<ModelTier, ModelInfo>;
@@ -0,0 +1,19 @@
1
+ /** All supported model definitions */
2
+ export const MODELS = {
3
+ deep: {
4
+ tier: 'deep',
5
+ name: 'Qwen2.5-Coder-0.5B-Instruct',
6
+ filename: 'qwen2.5-coder-0.5b-instruct-q4_k_m.gguf',
7
+ 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
+ sizeBytes: 350_000_000,
9
+ sizeHuman: '350MB',
10
+ },
11
+ pro: {
12
+ tier: 'pro',
13
+ name: 'Qwen2.5-Coder-1.5B-Instruct',
14
+ filename: 'qwen2.5-coder-1.5b-instruct-q4_k_m.gguf',
15
+ 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
+ sizeBytes: 900_000_000,
17
+ sizeHuman: '900MB',
18
+ },
19
+ };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Pattern Indexer — Pure Utility Helpers
3
+ *
4
+ * Standalone pure functions shared across language extractors and the main
5
+ * indexer class. No class state is referenced here.
6
+ */
7
+ import type { PatternEntry, PatternType } from './types.js';
8
+ /** SHA-256 of `content`, truncated to 16 hex chars. */
9
+ export declare function hashContent(content: string): string;
10
+ export interface PatternEntryParams {
11
+ type: PatternType;
12
+ name: string;
13
+ file: string;
14
+ line: number;
15
+ endLine: number;
16
+ signature: string;
17
+ description: string;
18
+ keywords: string[];
19
+ content: string;
20
+ exported: boolean;
21
+ }
22
+ /** Build a complete PatternEntry from constituent parts. */
23
+ export declare function createPatternEntry(params: PatternEntryParams): PatternEntry;
24
+ /** Split camelCase / PascalCase / snake_case names into unique lowercase words. */
25
+ export declare function extractKeywords(name: string): string[];
26
+ /** Walk forward from `startIndex` and return the line index after the closing brace. */
27
+ export declare function findBraceBlockEnd(lines: string[], startIndex: number): number;
28
+ /** Return the source lines for a brace-delimited block starting at `startIndex`. */
29
+ export declare function getBraceBlockContent(lines: string[], startIndex: number): string;
30
+ /**
31
+ * Collect consecutive `//` comments immediately above `startIndex` (Go / Rust style).
32
+ * Walks upward until a non-comment line is encountered.
33
+ */
34
+ export declare function getCOMLineComments(lines: string[], startIndex: number): string;
35
+ /**
36
+ * Extract the first JavaDoc `/** … *\/` comment block found above `startIndex`.
37
+ */
38
+ export declare function getJavaDoc(lines: string[], startIndex: number): string;
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Pattern Indexer — Pure Utility Helpers
3
+ *
4
+ * Standalone pure functions shared across language extractors and the main
5
+ * indexer class. No class state is referenced here.
6
+ */
7
+ import { createHash } from 'crypto';
8
+ // ---------------------------------------------------------------------------
9
+ // Hashing / ID generation
10
+ // ---------------------------------------------------------------------------
11
+ /** SHA-256 of `content`, truncated to 16 hex chars. */
12
+ export function hashContent(content) {
13
+ return createHash('sha256').update(content).digest('hex').slice(0, 16);
14
+ }
15
+ /** Build a complete PatternEntry from constituent parts. */
16
+ export function createPatternEntry(params) {
17
+ const id = hashContent(`${params.file}:${params.name}:${params.line}`);
18
+ const hash = hashContent(params.content);
19
+ return {
20
+ id,
21
+ type: params.type,
22
+ name: params.name,
23
+ file: params.file,
24
+ line: params.line,
25
+ endLine: params.endLine,
26
+ signature: params.signature,
27
+ description: params.description,
28
+ keywords: params.keywords,
29
+ hash,
30
+ exported: params.exported,
31
+ usageCount: 0,
32
+ indexedAt: new Date().toISOString(),
33
+ };
34
+ }
35
+ // ---------------------------------------------------------------------------
36
+ // Keyword extraction
37
+ // ---------------------------------------------------------------------------
38
+ /** Split camelCase / PascalCase / snake_case names into unique lowercase words. */
39
+ export function extractKeywords(name) {
40
+ const words = name
41
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
42
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
43
+ .toLowerCase()
44
+ .split(/[\s_-]+/)
45
+ .filter(w => w.length > 1);
46
+ return [...new Set(words)];
47
+ }
48
+ // ---------------------------------------------------------------------------
49
+ // Brace-based block helpers (Go, Rust, JVM, C-style)
50
+ // ---------------------------------------------------------------------------
51
+ /** Walk forward from `startIndex` and return the line index after the closing brace. */
52
+ export function findBraceBlockEnd(lines, startIndex) {
53
+ let braceCount = 0;
54
+ let started = false;
55
+ for (let i = startIndex; i < lines.length; i++) {
56
+ const line = lines[i];
57
+ if (line.includes('{')) {
58
+ braceCount += (line.match(/\{/g) || []).length;
59
+ started = true;
60
+ }
61
+ if (line.includes('}')) {
62
+ braceCount -= (line.match(/\}/g) || []).length;
63
+ }
64
+ if (started && braceCount === 0)
65
+ return i + 1;
66
+ }
67
+ return lines.length;
68
+ }
69
+ /** Return the source lines for a brace-delimited block starting at `startIndex`. */
70
+ export function getBraceBlockContent(lines, startIndex) {
71
+ const end = findBraceBlockEnd(lines, startIndex);
72
+ return lines.slice(startIndex, end).join('\n');
73
+ }
74
+ // ---------------------------------------------------------------------------
75
+ // Comment extraction helpers
76
+ // ---------------------------------------------------------------------------
77
+ /**
78
+ * Collect consecutive `//` comments immediately above `startIndex` (Go / Rust style).
79
+ * Walks upward until a non-comment line is encountered.
80
+ */
81
+ export function getCOMLineComments(lines, startIndex) {
82
+ const comments = [];
83
+ for (let i = startIndex; i >= 0; i--) {
84
+ const line = lines[i].trim();
85
+ if (line.startsWith('//')) {
86
+ comments.unshift(line.replace('//', '').trim());
87
+ }
88
+ else {
89
+ break;
90
+ }
91
+ }
92
+ return comments.join(' ');
93
+ }
94
+ /**
95
+ * Extract the first JavaDoc `/** … *\/` comment block found above `startIndex`.
96
+ */
97
+ export function getJavaDoc(lines, startIndex) {
98
+ const comments = [];
99
+ let inDoc = false;
100
+ for (let i = startIndex; i >= 0; i--) {
101
+ const line = lines[i].trim();
102
+ if (line.endsWith('*/'))
103
+ inDoc = true;
104
+ if (inDoc) {
105
+ comments.unshift(line.replace('/**', '').replace('*/', '').replace(/^\*/, '').trim());
106
+ }
107
+ if (line.startsWith('/**'))
108
+ break;
109
+ }
110
+ return comments.join(' ');
111
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Pattern Indexer — Language-Specific Extractors
3
+ *
4
+ * Standalone extraction functions for Go, Rust, JVM (Java/Kotlin/C#),
5
+ * Python, and a generic C-style fallback. Each function is pure and
6
+ * receives all required context as parameters.
7
+ */
8
+ import type { PatternEntry } from './types.js';
9
+ export declare function extractGoPatterns(filePath: string, content: string, rootDir: string): PatternEntry[];
10
+ export declare function extractRustPatterns(filePath: string, content: string, rootDir: string): PatternEntry[];
11
+ export declare function extractJVMStylePatterns(filePath: string, content: string, rootDir: string): PatternEntry[];
12
+ export declare function extractGenericCPatterns(_filePath: string, _content: string): PatternEntry[];
13
+ export declare function extractPythonPatterns(filePath: string, content: string, rootDir: string, minNameLength: number): PatternEntry[];