Skip to content

Commit 83529b6

Browse files
committed
feat: extract processor switch to polymorphic classes (Part 1)
- Create src/processors/processor-interfaces.ts with: - IProcessor interface for all processors - BaseProcessor abstract class with error handling - PreProcessor and PostProcessor base classes - ProcessorRegistry for managing processor instances - Create src/processors/implementations/ directory with: - PreValidateProcessor: Basic pre-validation - CodexComplianceProcessor: Validates against codex rules - index.ts: Exports all implementations - Demonstrates the new architecture pattern: - Each processor is a class implementing IProcessor - Registry pattern instead of switch statement - Lazy loading to avoid circular dependencies Part 2 will migrate ProcessorManager to use the registry. See docs/reflections/processor-rules-engine-architecture-analysis-2026-03-18.md
1 parent aace35e commit 83529b6

File tree

6 files changed

+323
-21
lines changed

6 files changed

+323
-21
lines changed

.opencode/state

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
{
22
"memory:baseline": {
3-
"heapUsed": 11.31,
4-
"heapTotal": 20.81,
5-
"external": 1.87,
6-
"rss": 58.72,
7-
"timestamp": 1773836401737
3+
"heapUsed": 11.91,
4+
"heapTotal": 20.06,
5+
"external": 1.88,
6+
"rss": 57.22,
7+
"timestamp": 1773840340380
88
}
99
}

performance-baselines.json

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,34 @@
11
{
22
"bundle-size-analysis": {
33
"testName": "bundle-size-analysis",
4-
"averageDuration": 9.873930402777775,
5-
"standardDeviation": 0.5583387367407121,
6-
"sampleCount": 288,
7-
"lastUpdated": 1773836398049,
4+
"averageDuration": 9.869833605351168,
5+
"standardDeviation": 0.5558387883847822,
6+
"sampleCount": 299,
7+
"lastUpdated": 1773840313382,
88
"tolerance": 10
99
},
1010
"module-import-performance": {
1111
"testName": "module-import-performance",
12-
"averageDuration": 27.368656665226773,
13-
"standardDeviation": 3.1741469569897465,
14-
"sampleCount": 463,
15-
"lastUpdated": 1773836398049,
12+
"averageDuration": 27.37792217107942,
13+
"standardDeviation": 3.17773022615393,
14+
"sampleCount": 491,
15+
"lastUpdated": 1773840313382,
1616
"tolerance": 10
1717
},
1818
"memory-usage-check": {
1919
"testName": "memory-usage-check",
20-
"averageDuration": 0.09729197154471665,
21-
"standardDeviation": 0.0053422199512606705,
22-
"sampleCount": 246,
23-
"lastUpdated": 1773836376650,
20+
"averageDuration": 0.09729749609375082,
21+
"standardDeviation": 0.005329687172289129,
22+
"sampleCount": 256,
23+
"lastUpdated": 1773840258938,
2424
"tolerance": 10
2525
},
2626
"test-execution-performance": {
2727
"testName": "test-execution-performance",
28-
"averageDuration": 0.10799480595482683,
29-
"standardDeviation": 0.02442426405449053,
30-
"sampleCount": 974,
31-
"lastUpdated": 1773836398049,
28+
"averageDuration": 0.10766623720472562,
29+
"standardDeviation": 0.023987105568075727,
30+
"sampleCount": 1016,
31+
"lastUpdated": 1773840336765,
3232
"tolerance": 10
3333
}
3434
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/**
2+
* Codex Compliance Processor
3+
*
4+
* Validates code against the Universal Development Codex.
5+
*
6+
* @module processors/implementations
7+
* @version 1.0.0
8+
*/
9+
10+
import { PreProcessor } from "../processor-interfaces.js";
11+
import { frameworkLogger } from "../../core/framework-logger.js";
12+
13+
export class CodexComplianceProcessor extends PreProcessor {
14+
readonly name = "codexCompliance";
15+
readonly priority = 20;
16+
17+
protected async run(context: unknown): Promise<unknown> {
18+
const ctx = context as Record<string, unknown>;
19+
20+
// Lazy load RuleEnforcer to avoid circular dependencies
21+
const { RuleEnforcer } = await import("../../enforcement/rule-enforcer.js");
22+
const ruleEnforcer = new RuleEnforcer();
23+
24+
const operation = (ctx.operation as string) || "modify";
25+
const filePath = ctx.filePath as string | undefined;
26+
const content = ctx.content as string | undefined;
27+
28+
await frameworkLogger.log(
29+
"codex-compliance-processor",
30+
"validating",
31+
"info",
32+
{ operation, filePath: filePath?.slice(0, 100) },
33+
);
34+
35+
// Build validation context
36+
const validationContext: import("../../enforcement/types.js").RuleValidationContext = {
37+
operation,
38+
};
39+
40+
if (filePath) {
41+
validationContext.files = [filePath];
42+
}
43+
44+
if (content) {
45+
validationContext.newCode = content;
46+
}
47+
48+
// Validate against codex rules
49+
const validationResult = await ruleEnforcer.validateOperation(
50+
operation,
51+
validationContext,
52+
);
53+
54+
if (!validationResult.passed) {
55+
const errors = validationResult.errors.join("; ");
56+
throw new Error(`Codex compliance failed: ${errors}`);
57+
}
58+
59+
return {
60+
passed: true,
61+
rulesChecked: validationResult.results.length,
62+
errors: validationResult.errors.length,
63+
warnings: validationResult.warnings.length,
64+
};
65+
}
66+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* Processor Implementations Index
3+
*
4+
* Exports all processor implementations for registration.
5+
*
6+
* @module processors/implementations
7+
* @version 1.0.0
8+
*/
9+
10+
export { PreValidateProcessor } from "./pre-validate-processor.js";
11+
export { CodexComplianceProcessor } from "./codex-compliance-processor.js";
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* Pre-Validation Processor
3+
*
4+
* Validates input before processing begins.
5+
*
6+
* @module processors/implementations
7+
* @version 1.0.0
8+
*/
9+
10+
import { PreProcessor } from "../processor-interfaces.js";
11+
12+
export class PreValidateProcessor extends PreProcessor {
13+
readonly name = "preValidate";
14+
readonly priority = 10;
15+
16+
protected async run(context: unknown): Promise<unknown> {
17+
// Basic pre-validation logic
18+
// This is intentionally lightweight - just validation setup
19+
return { validated: true, timestamp: Date.now() };
20+
}
21+
}
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
/**
2+
* Processor Interface and Base Classes
3+
*
4+
* Defines the contract for all processors in the StringRay framework.
5+
* Replaces the switch statement anti-pattern in ProcessorManager with
6+
* polymorphic processor classes.
7+
*
8+
* @module processors/interfaces
9+
* @version 1.0.0
10+
*/
11+
12+
import { ProcessorResult } from "./processor-manager.js";
13+
14+
/**
15+
* Processor execution context
16+
*/
17+
export interface ProcessorContext {
18+
/** Tool input (for pre-processors) */
19+
toolInput?: {
20+
tool?: string;
21+
args?: {
22+
filePath?: string;
23+
content?: string;
24+
[key: string]: unknown;
25+
};
26+
};
27+
/** File path being processed */
28+
filePath?: string;
29+
/** Operation being performed */
30+
operation?: string;
31+
/** Content being processed */
32+
content?: string;
33+
/** Additional context */
34+
[key: string]: unknown;
35+
}
36+
37+
/**
38+
* Processor interface - all processors must implement this
39+
*/
40+
export interface IProcessor {
41+
/** Unique processor identifier */
42+
readonly name: string;
43+
44+
/** Processor type: pre or post */
45+
readonly type: "pre" | "post";
46+
47+
/** Execution priority (lower = earlier) */
48+
readonly priority: number;
49+
50+
/** Whether processor is enabled */
51+
enabled: boolean;
52+
53+
/**
54+
* Execute the processor
55+
* @param context Processor execution context
56+
* @returns Processor result
57+
*/
58+
execute(context: ProcessorContext): Promise<ProcessorResult>;
59+
}
60+
61+
/**
62+
* Base processor class with common functionality
63+
*/
64+
export abstract class BaseProcessor implements IProcessor {
65+
abstract readonly name: string;
66+
abstract readonly type: "pre" | "post";
67+
abstract readonly priority: number;
68+
enabled = true;
69+
70+
/**
71+
* Execute the processor with error handling and metrics
72+
* @param context Processor execution context
73+
* @returns Processor result
74+
*/
75+
async execute(context: ProcessorContext): Promise<ProcessorResult> {
76+
const startTime = Date.now();
77+
78+
try {
79+
const data = await this.run(context);
80+
const duration = Date.now() - startTime;
81+
82+
return {
83+
success: true,
84+
data,
85+
duration,
86+
processorName: this.name,
87+
};
88+
} catch (error) {
89+
const duration = Date.now() - startTime;
90+
91+
return {
92+
success: false,
93+
error: error instanceof Error ? error.message : String(error),
94+
duration,
95+
processorName: this.name,
96+
};
97+
}
98+
}
99+
100+
/**
101+
* Override this method in subclasses to implement processor logic
102+
* @param context Processor execution context
103+
* @returns Processor data
104+
*/
105+
protected abstract run(context: ProcessorContext): Promise<unknown>;
106+
107+
/**
108+
* Safely extract file path from context
109+
*/
110+
protected getFilePath(context: ProcessorContext): string | undefined {
111+
return context.toolInput?.args?.filePath || context.filePath;
112+
}
113+
114+
/**
115+
* Safely extract content from context
116+
*/
117+
protected getContent(context: ProcessorContext): string | undefined {
118+
return context.toolInput?.args?.content;
119+
}
120+
}
121+
122+
/**
123+
* Pre-processor base class
124+
*/
125+
export abstract class PreProcessor extends BaseProcessor {
126+
readonly type = "pre" as const;
127+
}
128+
129+
/**
130+
* Post-processor base class
131+
*/
132+
export abstract class PostProcessor extends BaseProcessor {
133+
readonly type = "post" as const;
134+
}
135+
136+
/**
137+
* Processor registry for managing processor instances
138+
*/
139+
export class ProcessorRegistry {
140+
private processors = new Map<string, IProcessor>();
141+
142+
/**
143+
* Register a processor
144+
* @param processor Processor instance
145+
*/
146+
register(processor: IProcessor): void {
147+
this.processors.set(processor.name, processor);
148+
}
149+
150+
/**
151+
* Unregister a processor
152+
* @param name Processor name
153+
*/
154+
unregister(name: string): void {
155+
this.processors.delete(name);
156+
}
157+
158+
/**
159+
* Get a processor by name
160+
* @param name Processor name
161+
* @returns Processor instance or undefined
162+
*/
163+
get(name: string): IProcessor | undefined {
164+
return this.processors.get(name);
165+
}
166+
167+
/**
168+
* Get all registered processors
169+
* @returns Array of processors
170+
*/
171+
getAll(): IProcessor[] {
172+
return Array.from(this.processors.values());
173+
}
174+
175+
/**
176+
* Get processors by type
177+
* @param type Processor type
178+
* @returns Array of processors
179+
*/
180+
getByType(type: "pre" | "post"): IProcessor[] {
181+
return this.getAll()
182+
.filter((p) => p.type === type)
183+
.sort((a, b) => a.priority - b.priority);
184+
}
185+
186+
/**
187+
* Check if processor exists
188+
* @param name Processor name
189+
* @returns True if processor exists
190+
*/
191+
has(name: string): boolean {
192+
return this.processors.has(name);
193+
}
194+
195+
/**
196+
* Clear all processors
197+
*/
198+
clear(): void {
199+
this.processors.clear();
200+
}
201+
}
202+
203+
// Singleton registry instance
204+
export const processorRegistry = new ProcessorRegistry();

0 commit comments

Comments
 (0)