Skip to content

Commit b13f111

Browse files
committed
feat: Add async result streaming with progress callbacks
ARCH-003: Async Result Streaming Implemented real-time progress reporting system for long-running validations, providing better UX with live updates as validators complete. Features: - Progress callbacks via LintConfig.execution.onProgress - ProgressEvent type system (validator-start, validator-complete, validator-error) - ProgressReporter class for structured progress tracking - Live statistics tracking (running, completed, passed, failed, errors) - Verbose and silent modes for flexible output control - Duration tracking and formatting (ms/s) - Final summary with validation results Implementation Details: - types/index.ts: Added ProgressEvent, ProgressCallback, onProgress to LintConfig - linter.ts: Enhanced runValidatorsSequential and runValidatorsParallel to emit progress events - progress-reporter.ts: Full ProgressReporter implementation with stats tracking - tests/progress-reporter.test.ts: 20 comprehensive test cases covering all scenarios Benefits: - Better visibility into validation progress for long-running sessions - Statistics tracking even in silent mode (testable, scriptable) - Non-blocking - events are emitted asynchronously - Backward compatible - onProgress is optional Tests: 324/324 passing (100%) Build: ✅ Passing (0 TypeScript errors)
1 parent ea356eb commit b13f111

4 files changed

Lines changed: 840 additions & 6 deletions

File tree

plugins/ui5/skill-lint/src/core/linter.ts

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,24 +84,44 @@ export class SkillLinter {
8484
}
8585

8686
/**
87-
* Run validators sequentially with error boundaries
87+
* Run validators sequentially with error boundaries and progress reporting
8888
*/
8989
private async runValidatorsSequential(
9090
skill: Skill,
9191
config: LintConfig,
9292
): Promise<ValidationResult[]> {
9393
const results: ValidationResult[] = [];
94+
const onProgress = config.execution.onProgress;
9495

9596
for (const validator of this.validators) {
97+
// Emit start event
98+
if (onProgress) {
99+
onProgress({
100+
type: 'validator-start',
101+
validator: validator.name,
102+
timestamp: Date.now(),
103+
});
104+
}
105+
96106
try {
97107
const result = await validator.validate(skill, config);
98108
results.push(result);
109+
110+
// Emit complete event with result
111+
if (onProgress) {
112+
onProgress({
113+
type: 'validator-complete',
114+
validator: validator.name,
115+
timestamp: Date.now(),
116+
result,
117+
});
118+
}
99119
} catch (error) {
100120
// Don't let one validator crash bring down the entire tool
101121
const errorMessage = error instanceof Error ? error.message : String(error);
102122
console.error(`[SkillLinter] Validator "${validator.name}" crashed:`, errorMessage);
103123

104-
results.push({
124+
const errorResult: ValidationResult = {
105125
validator: validator.name,
106126
passed: false,
107127
duration: 0,
@@ -111,29 +131,65 @@ export class SkillLinter {
111131
message: `Validator "${validator.name}" crashed: ${errorMessage}`,
112132
suggestion: 'This is likely a bug in the validator. Please report this issue.',
113133
}],
114-
});
134+
};
135+
136+
results.push(errorResult);
137+
138+
// Emit error event
139+
if (onProgress) {
140+
onProgress({
141+
type: 'validator-error',
142+
validator: validator.name,
143+
timestamp: Date.now(),
144+
error: errorMessage,
145+
result: errorResult,
146+
});
147+
}
115148
}
116149
}
117150

118151
return results;
119152
}
120153

121154
/**
122-
* Run validators in parallel with error boundaries and concurrency control
155+
* Run validators in parallel with error boundaries, concurrency control, and progress reporting
123156
*/
124157
private async runValidatorsParallel(
125158
skill: Skill,
126159
config: LintConfig,
127160
): Promise<ValidationResult[]> {
161+
const onProgress = config.execution.onProgress;
162+
128163
const tasks = this.validators.map(validator => async (): Promise<ValidationResult> => {
164+
// Emit start event
165+
if (onProgress) {
166+
onProgress({
167+
type: 'validator-start',
168+
validator: validator.name,
169+
timestamp: Date.now(),
170+
});
171+
}
172+
129173
try {
130-
return await validator.validate(skill, config);
174+
const result = await validator.validate(skill, config);
175+
176+
// Emit complete event with result
177+
if (onProgress) {
178+
onProgress({
179+
type: 'validator-complete',
180+
validator: validator.name,
181+
timestamp: Date.now(),
182+
result,
183+
});
184+
}
185+
186+
return result;
131187
} catch (error) {
132188
// Don't let one validator crash bring down the entire tool
133189
const errorMessage = error instanceof Error ? error.message : String(error);
134190
console.error(`[SkillLinter] Validator "${validator.name}" crashed:`, errorMessage);
135191

136-
return {
192+
const errorResult: ValidationResult = {
137193
validator: validator.name,
138194
passed: false,
139195
duration: 0,
@@ -144,6 +200,19 @@ export class SkillLinter {
144200
suggestion: 'This is likely a bug in the validator. Please report this issue.',
145201
}],
146202
};
203+
204+
// Emit error event
205+
if (onProgress) {
206+
onProgress({
207+
type: 'validator-error',
208+
validator: validator.name,
209+
timestamp: Date.now(),
210+
error: errorMessage,
211+
result: errorResult,
212+
});
213+
}
214+
215+
return errorResult;
147216
}
148217
});
149218

plugins/ui5/skill-lint/src/types/index.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,16 @@ export interface HealthCheckResult {
132132
readonly timestamp: number;
133133
}
134134

135+
export interface ProgressEvent {
136+
readonly type: 'validator-start' | 'validator-complete' | 'validator-error';
137+
readonly validator: string;
138+
readonly timestamp: number;
139+
readonly result?: ValidationResult;
140+
readonly error?: string;
141+
}
142+
143+
export type ProgressCallback = (event: ProgressEvent) => void;
144+
135145
// ── Config ──
136146

137147
export interface LintConfig {
@@ -160,6 +170,7 @@ export interface LintConfig {
160170
readonly maxRetries: number;
161171
readonly parallel: boolean;
162172
readonly maxConcurrency?: number;
173+
readonly onProgress?: ProgressCallback;
163174
};
164175
readonly formatters: {
165176
readonly default: string;

0 commit comments

Comments
 (0)