@healflow/classification 0.1.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.
@@ -0,0 +1,23 @@
1
+ import { FailureCategory, type ClassificationResult, type FailureEvent } from '@healflow/shared';
2
+ export interface ClassificationRule {
3
+ category: FailureCategory;
4
+ patterns: RegExp[];
5
+ stackPatterns?: RegExp[];
6
+ weight: number;
7
+ subcategory?: string;
8
+ isProductBug?: boolean;
9
+ }
10
+ /** Rule-based classification engine — no AI required for MVP */
11
+ export declare const CLASSIFICATION_RULES: ClassificationRule[];
12
+ export interface ClassifierOptions {
13
+ rules?: ClassificationRule[];
14
+ productBugPatterns?: RegExp[];
15
+ }
16
+ export declare class FailureClassifier {
17
+ private readonly rules;
18
+ private readonly productBugPatterns;
19
+ constructor(options?: ClassifierOptions);
20
+ classify(failure: FailureEvent): ClassificationResult;
21
+ }
22
+ export declare function classifyFailure(failure: FailureEvent): ClassificationResult;
23
+ //# sourceMappingURL=classifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classifier.d.ts","sourceRoot":"","sources":["../src/classifier.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,KAAK,oBAAoB,EAEzB,KAAK,YAAY,EAClB,MAAM,kBAAkB,CAAC;AAG1B,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,eAAe,CAAC;IAC1B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,gEAAgE;AAChE,eAAO,MAAM,oBAAoB,EAAE,kBAAkB,EA4GpD,CAAC;AAEF,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC/B;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAuB;IAC7C,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAW;gBAElC,OAAO,GAAE,iBAAsB;IAO3C,QAAQ,CAAC,OAAO,EAAE,YAAY,GAAG,oBAAoB;CAsEtD;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG,oBAAoB,CAE3E"}
@@ -0,0 +1,194 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FailureClassifier = exports.CLASSIFICATION_RULES = void 0;
4
+ exports.classifyFailure = classifyFailure;
5
+ const shared_1 = require("@healflow/shared");
6
+ const shared_2 = require("@healflow/shared");
7
+ /** Rule-based classification engine — no AI required for MVP */
8
+ exports.CLASSIFICATION_RULES = [
9
+ {
10
+ category: shared_1.FailureCategory.SELECTOR,
11
+ patterns: [
12
+ /locator\.(?:click|fill|check|selectOption|press)/i,
13
+ /waiting for locator/i,
14
+ /strict mode violation/i,
15
+ /resolved to \d+ elements/i,
16
+ /element is not attached to the dom/i,
17
+ /element is not visible/i,
18
+ /element is outside of the viewport/i,
19
+ /no node found for selector/i,
20
+ /getByRole|getByTestId|getByText|getByLabel/i,
21
+ ],
22
+ weight: 0.85,
23
+ subcategory: 'locator_not_found',
24
+ },
25
+ {
26
+ category: shared_1.FailureCategory.TIMING,
27
+ patterns: [
28
+ /timeout \d+ms exceeded/i,
29
+ /waiting for.*to be visible/i,
30
+ /waiting for.*to be enabled/i,
31
+ /waiting for navigation/i,
32
+ /page\.goto: timeout/i,
33
+ /expect\(.*\)\.toBeVisible/i,
34
+ /networkidle/i,
35
+ ],
36
+ weight: 0.75,
37
+ subcategory: 'wait_timeout',
38
+ },
39
+ {
40
+ category: shared_1.FailureCategory.OVERLAY,
41
+ patterns: [
42
+ /intercepts pointer events/i,
43
+ /element is obscured/i,
44
+ /modal|dialog|overlay|cookie|banner|popup/i,
45
+ /backdrop/i,
46
+ ],
47
+ weight: 0.8,
48
+ subcategory: 'blocking_overlay',
49
+ },
50
+ {
51
+ category: shared_1.FailureCategory.IFRAME,
52
+ patterns: [
53
+ /(?:^|\s|\()(?:frame|iframe|frameLocator|content frame)/i,
54
+ /iframe/i,
55
+ /content frame/i,
56
+ /frameLocator/i,
57
+ ],
58
+ weight: 0.85,
59
+ subcategory: 'frame_context',
60
+ },
61
+ {
62
+ category: shared_1.FailureCategory.SHADOW_DOM,
63
+ patterns: [/shadow root/i, /pierce/i, /closed shadow/i],
64
+ weight: 0.8,
65
+ subcategory: 'shadow_access',
66
+ },
67
+ {
68
+ category: shared_1.FailureCategory.AUTH,
69
+ patterns: [/unauthorized|401|403|login required|authentication/i, /redirect.*login/i],
70
+ weight: 0.85,
71
+ subcategory: 'auth_failure',
72
+ },
73
+ {
74
+ category: shared_1.FailureCategory.SESSION,
75
+ patterns: [/storage state/i, /session expired/i, /cookie/i, /localStorage/i],
76
+ weight: 0.8,
77
+ subcategory: 'session_invalid',
78
+ },
79
+ {
80
+ category: shared_1.FailureCategory.NETWORK,
81
+ patterns: [/net::ERR/i, /ECONNREFUSED/i, /network error/i, /failed to fetch/i],
82
+ weight: 0.75,
83
+ subcategory: 'network_error',
84
+ },
85
+ {
86
+ category: shared_1.FailureCategory.API,
87
+ patterns: [/api.*(?:4\d\d|5\d\d)/i, /graphql error/i, /response status/i],
88
+ weight: 0.7,
89
+ subcategory: 'api_error',
90
+ },
91
+ {
92
+ category: shared_1.FailureCategory.TEST_DATA,
93
+ patterns: [/fixture/i, /test data/i, /seed/i, /mock data/i],
94
+ weight: 0.65,
95
+ subcategory: 'data_issue',
96
+ },
97
+ {
98
+ category: shared_1.FailureCategory.CONFIGURATION,
99
+ patterns: [/playwright\.config/i, /project.*not found/i, /browserType\.launch/i],
100
+ weight: 0.7,
101
+ subcategory: 'config_error',
102
+ },
103
+ {
104
+ category: shared_1.FailureCategory.ENVIRONMENT,
105
+ patterns: [/CI.*env/i, /baseURL/i, /environment variable/i, /ENOTFOUND/i],
106
+ weight: 0.65,
107
+ subcategory: 'env_mismatch',
108
+ },
109
+ {
110
+ category: shared_1.FailureCategory.PRODUCT_BUG,
111
+ patterns: [/assertion.*failed/i, /expected.*received/i, /toEqual|toBe\(/i],
112
+ weight: 0.6,
113
+ isProductBug: true,
114
+ subcategory: 'assertion_failure',
115
+ },
116
+ ];
117
+ class FailureClassifier {
118
+ rules;
119
+ productBugPatterns;
120
+ constructor(options = {}) {
121
+ this.rules = options.rules ?? exports.CLASSIFICATION_RULES;
122
+ this.productBugPatterns = options.productBugPatterns ?? [
123
+ /expect\(.*\)\.(?:toBe|toEqual|toContain|toMatch)/i,
124
+ ];
125
+ }
126
+ classify(failure) {
127
+ const haystack = [failure.errorMessage, failure.errorStack ?? ''].join('\n');
128
+ const signals = [];
129
+ let bestCategory = shared_1.FailureCategory.UNKNOWN;
130
+ let bestScore = 0;
131
+ let bestSubcategory;
132
+ let isProductBug = false;
133
+ for (const rule of this.rules) {
134
+ for (const pattern of rule.patterns) {
135
+ if (pattern.test(haystack)) {
136
+ signals.push({
137
+ name: `${rule.category}:${rule.subcategory ?? 'default'}`,
138
+ weight: rule.weight,
139
+ evidence: pattern.source,
140
+ });
141
+ if (rule.weight > bestScore) {
142
+ bestScore = rule.weight;
143
+ bestCategory = rule.category;
144
+ bestSubcategory = rule.subcategory;
145
+ isProductBug = rule.isProductBug ?? false;
146
+ }
147
+ }
148
+ }
149
+ }
150
+ // Boost product bug detection for assertion failures
151
+ if (!isProductBug) {
152
+ for (const pattern of this.productBugPatterns) {
153
+ if (pattern.test(haystack)) {
154
+ isProductBug = true;
155
+ if (bestCategory === shared_1.FailureCategory.UNKNOWN) {
156
+ bestCategory = shared_1.FailureCategory.PRODUCT_BUG;
157
+ bestScore = 0.7;
158
+ }
159
+ signals.push({
160
+ name: 'PRODUCT_BUG:assertion',
161
+ weight: 0.7,
162
+ evidence: pattern.source,
163
+ });
164
+ }
165
+ }
166
+ }
167
+ const confidence = signals.length > 0 ? Math.min(bestScore + 0.02, 0.99) : 0.3;
168
+ const isAutomationFixable = !isProductBug &&
169
+ bestCategory !== shared_1.FailureCategory.UNKNOWN &&
170
+ bestCategory !== shared_1.FailureCategory.PRODUCT_BUG &&
171
+ (0, shared_2.isCategoryAutomationFixable)(bestCategory);
172
+ const base = {
173
+ failureId: failure.id,
174
+ category: bestCategory,
175
+ subcategory: bestSubcategory,
176
+ confidence,
177
+ signals,
178
+ isAutomationFixable,
179
+ isProductBug,
180
+ detectedAt: new Date().toISOString(),
181
+ };
182
+ const { outcome, recommendedAction } = (0, shared_2.resolveFailureOutcome)(base);
183
+ return {
184
+ ...base,
185
+ healOutcome: outcome,
186
+ recommendedAction,
187
+ };
188
+ }
189
+ }
190
+ exports.FailureClassifier = FailureClassifier;
191
+ function classifyFailure(failure) {
192
+ return new FailureClassifier().classify(failure);
193
+ }
194
+ //# sourceMappingURL=classifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classifier.js","sourceRoot":"","sources":["../src/classifier.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { FailureEvent } from '@healflow/shared';
2
+ /** Generate stable fingerprint for deduplication */
3
+ export declare function fingerprintFailure(failure: Pick<FailureEvent, 'testFile' | 'errorMessage' | 'testTitle'>): string;
4
+ /** Normalize error messages by stripping dynamic values */
5
+ export declare function normalizeError(message: string): string;
6
+ export interface FailureCluster {
7
+ fingerprint: string;
8
+ failures: FailureEvent[];
9
+ representativeError: string;
10
+ affectedTestFiles: string[];
11
+ }
12
+ /** Group failures by fingerprint for root cause deduplication */
13
+ export declare function clusterFailures(failures: FailureEvent[]): FailureCluster[];
14
+ //# sourceMappingURL=deduplication.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deduplication.d.ts","sourceRoot":"","sources":["../src/deduplication.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,oDAAoD;AACpD,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,IAAI,CAAC,YAAY,EAAE,UAAU,GAAG,cAAc,GAAG,WAAW,CAAC,GACrE,MAAM,CAIR;AAED,2DAA2D;AAC3D,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAQtD;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,iBAAiB,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,iEAAiE;AACjE,wBAAgB,eAAe,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,cAAc,EAAE,CAgB1E"}
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fingerprintFailure = fingerprintFailure;
4
+ exports.normalizeError = normalizeError;
5
+ exports.clusterFailures = clusterFailures;
6
+ const node_crypto_1 = require("node:crypto");
7
+ /** Generate stable fingerprint for deduplication */
8
+ function fingerprintFailure(failure) {
9
+ const normalized = normalizeError(failure.errorMessage);
10
+ const input = `${failure.testFile}::${failure.testTitle}::${normalized}`;
11
+ return (0, node_crypto_1.createHash)('sha256').update(input).digest('hex').slice(0, 32);
12
+ }
13
+ /** Normalize error messages by stripping dynamic values */
14
+ function normalizeError(message) {
15
+ return message
16
+ .replace(/\d+ms/g, '{ms}')
17
+ .replace(/line \d+/gi, 'line {n}')
18
+ .replace(/:\d+:\d+/g, ':{line}:{col}')
19
+ .replace(/["'][a-f0-9-]{8,}["']/gi, '{uuid}')
20
+ .replace(/\d+/g, '{n}')
21
+ .trim();
22
+ }
23
+ /** Group failures by fingerprint for root cause deduplication */
24
+ function clusterFailures(failures) {
25
+ const map = new Map();
26
+ for (const failure of failures) {
27
+ const fp = failure.fingerprint || fingerprintFailure(failure);
28
+ const existing = map.get(fp) ?? [];
29
+ existing.push(failure);
30
+ map.set(fp, existing);
31
+ }
32
+ return Array.from(map.entries()).map(([fingerprint, clusterFailures]) => ({
33
+ fingerprint,
34
+ failures: clusterFailures,
35
+ representativeError: clusterFailures[0]?.errorMessage ?? '',
36
+ affectedTestFiles: [...new Set(clusterFailures.map((f) => f.testFile))],
37
+ }));
38
+ }
39
+ //# sourceMappingURL=deduplication.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deduplication.js","sourceRoot":"","sources":["../src/deduplication.ts"],"names":[],"mappings":";;AAIA,gDAMC;AAGD,wCAQC;AAUD,0CAgBC;AA/CD,6CAAyC;AAGzC,oDAAoD;AACpD,SAAgB,kBAAkB,CAChC,OAAsE;IAEtE,MAAM,UAAU,GAAG,cAAc,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IACxD,MAAM,KAAK,GAAG,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,SAAS,KAAK,UAAU,EAAE,CAAC;IACzE,OAAO,IAAA,wBAAU,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACvE,CAAC;AAED,2DAA2D;AAC3D,SAAgB,cAAc,CAAC,OAAe;IAC5C,OAAO,OAAO;SACX,OAAO,CAAC,QAAQ,EAAE,MAAM,CAAC;SACzB,OAAO,CAAC,YAAY,EAAE,UAAU,CAAC;SACjC,OAAO,CAAC,WAAW,EAAE,eAAe,CAAC;SACrC,OAAO,CAAC,yBAAyB,EAAE,QAAQ,CAAC;SAC5C,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC;SACtB,IAAI,EAAE,CAAC;AACZ,CAAC;AASD,iEAAiE;AACjE,SAAgB,eAAe,CAAC,QAAwB;IACtD,MAAM,GAAG,GAAG,IAAI,GAAG,EAA0B,CAAC;IAE9C,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,EAAE,GAAG,OAAO,CAAC,WAAW,IAAI,kBAAkB,CAAC,OAAO,CAAC,CAAC;QAC9D,MAAM,QAAQ,GAAG,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;QACnC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACvB,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;IACxB,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,eAAe,CAAC,EAAE,EAAE,CAAC,CAAC;QACxE,WAAW;QACX,QAAQ,EAAE,eAAe;QACzB,mBAAmB,EAAE,eAAe,CAAC,CAAC,CAAC,EAAE,YAAY,IAAI,EAAE;QAC3D,iBAAiB,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;KACxE,CAAC,CAAC,CAAC;AACN,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { FailureEvent } from '@healflow/shared';
2
+ export interface FlakinessRecord {
3
+ fingerprint: string;
4
+ testFile: string;
5
+ testTitle: string;
6
+ occurrenceCount: number;
7
+ passCount: number;
8
+ failCount: number;
9
+ flakinessScore: number;
10
+ isFlaky: boolean;
11
+ }
12
+ /** Score 0–1 where higher = more flaky (intermittent pass/fail). */
13
+ export declare function computeFlakinessScore(passCount: number, failCount: number): number;
14
+ export declare function scoreFailureHistory(failures: FailureEvent[], passCounts?: Map<string, number>): FlakinessRecord[];
15
+ //# sourceMappingURL=flakiness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flakiness.d.ts","sourceRoot":"","sources":["../src/flakiness.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;CAClB;AAID,oEAAoE;AACpE,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAMlF;AAED,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,YAAY,EAAE,EACxB,UAAU,GAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAa,GAC1C,eAAe,EAAE,CA+BnB"}
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.computeFlakinessScore = computeFlakinessScore;
4
+ exports.scoreFailureHistory = scoreFailureHistory;
5
+ const FLAKY_THRESHOLD = 0.3;
6
+ /** Score 0–1 where higher = more flaky (intermittent pass/fail). */
7
+ function computeFlakinessScore(passCount, failCount) {
8
+ const total = passCount + failCount;
9
+ if (total < 2)
10
+ return 0;
11
+ const failRate = failCount / total;
12
+ const passRate = passCount / total;
13
+ return Math.min(failRate * passRate * 4, 1);
14
+ }
15
+ function scoreFailureHistory(failures, passCounts = new Map()) {
16
+ const grouped = new Map();
17
+ for (const failure of failures) {
18
+ const key = failure.fingerprint || `${failure.testFile}::${failure.testTitle}`;
19
+ const list = grouped.get(key) ?? [];
20
+ list.push(failure);
21
+ grouped.set(key, list);
22
+ }
23
+ const records = [];
24
+ for (const [fingerprint, group] of grouped) {
25
+ const primary = group[0];
26
+ const failCount = group.length;
27
+ const passCount = passCounts.get(fingerprint) ?? 0;
28
+ const flakinessScore = computeFlakinessScore(passCount, failCount);
29
+ records.push({
30
+ fingerprint,
31
+ testFile: primary.testFile,
32
+ testTitle: primary.testTitle,
33
+ occurrenceCount: failCount,
34
+ passCount,
35
+ failCount,
36
+ flakinessScore,
37
+ isFlaky: flakinessScore >= FLAKY_THRESHOLD,
38
+ });
39
+ }
40
+ return records.sort((a, b) => b.flakinessScore - a.flakinessScore);
41
+ }
42
+ //# sourceMappingURL=flakiness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flakiness.js","sourceRoot":"","sources":["../src/flakiness.ts"],"names":[],"mappings":";;AAgBA,sDAMC;AAED,kDAkCC;AA7CD,MAAM,eAAe,GAAG,GAAG,CAAC;AAE5B,oEAAoE;AACpE,SAAgB,qBAAqB,CAAC,SAAiB,EAAE,SAAiB;IACxE,MAAM,KAAK,GAAG,SAAS,GAAG,SAAS,CAAC;IACpC,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC;IACxB,MAAM,QAAQ,GAAG,SAAS,GAAG,KAAK,CAAC;IACnC,MAAM,QAAQ,GAAG,SAAS,GAAG,KAAK,CAAC;IACnC,OAAO,IAAI,CAAC,GAAG,CAAC,QAAQ,GAAG,QAAQ,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED,SAAgB,mBAAmB,CACjC,QAAwB,EACxB,aAAkC,IAAI,GAAG,EAAE;IAE3C,MAAM,OAAO,GAAG,IAAI,GAAG,EAA0B,CAAC;IAElD,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,GAAG,GAAG,OAAO,CAAC,WAAW,IAAI,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,SAAS,EAAE,CAAC;QAC/E,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;QACpC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACnB,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACzB,CAAC;IAED,MAAM,OAAO,GAAsB,EAAE,CAAC;IAEtC,KAAK,MAAM,CAAC,WAAW,EAAE,KAAK,CAAC,IAAI,OAAO,EAAE,CAAC;QAC3C,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;QAC1B,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC;QAC/B,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QACnD,MAAM,cAAc,GAAG,qBAAqB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QAEnE,OAAO,CAAC,IAAI,CAAC;YACX,WAAW;YACX,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,eAAe,EAAE,SAAS;YAC1B,SAAS;YACT,SAAS;YACT,cAAc;YACd,OAAO,EAAE,cAAc,IAAI,eAAe;SAC3C,CAAC,CAAC;IACL,CAAC;IAED,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,GAAG,CAAC,CAAC,cAAc,CAAC,CAAC;AACrE,CAAC"}
@@ -0,0 +1,7 @@
1
+ export { FailureClassifier, classifyFailure, CLASSIFICATION_RULES } from './classifier.js';
2
+ export { fingerprintFailure, normalizeError, clusterFailures } from './deduplication.js';
3
+ export { computeFlakinessScore, scoreFailureHistory } from './flakiness.js';
4
+ export type { ClassificationRule, ClassifierOptions } from './classifier.js';
5
+ export type { FailureCluster } from './deduplication.js';
6
+ export type { FlakinessRecord } from './flakiness.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC3F,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACzF,OAAO,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC5E,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAC7E,YAAY,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.scoreFailureHistory = exports.computeFlakinessScore = exports.clusterFailures = exports.normalizeError = exports.fingerprintFailure = exports.CLASSIFICATION_RULES = exports.classifyFailure = exports.FailureClassifier = void 0;
4
+ var classifier_js_1 = require("./classifier.js");
5
+ Object.defineProperty(exports, "FailureClassifier", { enumerable: true, get: function () { return classifier_js_1.FailureClassifier; } });
6
+ Object.defineProperty(exports, "classifyFailure", { enumerable: true, get: function () { return classifier_js_1.classifyFailure; } });
7
+ Object.defineProperty(exports, "CLASSIFICATION_RULES", { enumerable: true, get: function () { return classifier_js_1.CLASSIFICATION_RULES; } });
8
+ var deduplication_js_1 = require("./deduplication.js");
9
+ Object.defineProperty(exports, "fingerprintFailure", { enumerable: true, get: function () { return deduplication_js_1.fingerprintFailure; } });
10
+ Object.defineProperty(exports, "normalizeError", { enumerable: true, get: function () { return deduplication_js_1.normalizeError; } });
11
+ Object.defineProperty(exports, "clusterFailures", { enumerable: true, get: function () { return deduplication_js_1.clusterFailures; } });
12
+ var flakiness_js_1 = require("./flakiness.js");
13
+ Object.defineProperty(exports, "computeFlakinessScore", { enumerable: true, get: function () { return flakiness_js_1.computeFlakinessScore; } });
14
+ Object.defineProperty(exports, "scoreFailureHistory", { enumerable: true, get: function () { return flakiness_js_1.scoreFailureHistory; } });
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,iDAA2F;AAAlF,kHAAA,iBAAiB,OAAA;AAAE,gHAAA,eAAe,OAAA;AAAE,qHAAA,oBAAoB,OAAA;AACjE,uDAAyF;AAAhF,sHAAA,kBAAkB,OAAA;AAAE,kHAAA,cAAc,OAAA;AAAE,mHAAA,eAAe,OAAA;AAC5D,+CAA4E;AAAnE,qHAAA,qBAAqB,OAAA;AAAE,mHAAA,mBAAmB,OAAA"}
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@healflow/classification",
3
+ "version": "0.1.0",
4
+ "description": "HealFlow failure classification engine",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "dependencies": {
8
+ "@healflow/shared": "^0.1.0"
9
+ },
10
+ "devDependencies": {
11
+ "tsx": "^4.19.2",
12
+ "typescript": "^5.7.2"
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "scripts": {
21
+ "build": "tsc -p tsconfig.json",
22
+ "typecheck": "tsc -p tsconfig.json --noEmit",
23
+ "test": "node --import tsx --test src/**/*.test.ts"
24
+ }
25
+ }