@wutangbanger/horus-insight-store 1.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.
@@ -0,0 +1,25 @@
1
+ /**
2
+ * AgentInsightStore
3
+ *
4
+ * JSONL-backed persistence for AI agent insights. Each agent gets its own
5
+ * file: reports/agent-insights/<agentId>.jsonl — one JSON record per line.
6
+ *
7
+ * JSONL (newline-delimited JSON) is used because:
8
+ * - Appends are O(1) — no need to parse and rewrite the whole file
9
+ * - Each line is a valid JSON object, easy to stream or tail
10
+ * - Human-readable and grep-friendly
11
+ *
12
+ * Implements IAgentInsightStore from @horus/contracts.
13
+ */
14
+ import { AgentInsight, IAgentInsightStore, HorusConfig } from '@horus/contracts';
15
+ export declare class AgentInsightStore implements IAgentInsightStore {
16
+ private readonly dir;
17
+ constructor(config: HorusConfig | string);
18
+ append(insight: AgentInsight): Promise<void>;
19
+ readAll(): Promise<AgentInsight[]>;
20
+ readSince(isoTimestamp: string): Promise<AgentInsight[]>;
21
+ readByAgent(agentId: string): Promise<AgentInsight[]>;
22
+ private filePathFor;
23
+ private readFile;
24
+ }
25
+ //# sourceMappingURL=AgentInsightStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AgentInsightStore.d.ts","sourceRoot":"","sources":["../src/AgentInsightStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEjF,qBAAa,iBAAkB,YAAW,kBAAkB;IAC1D,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;gBAEjB,MAAM,EAAE,WAAW,GAAG,MAAM;IAMlC,MAAM,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAM5C,OAAO,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IAalC,SAAS,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAKxD,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAQ3D,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,QAAQ;CAOjB"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * AgentInsightStore
3
+ *
4
+ * JSONL-backed persistence for AI agent insights. Each agent gets its own
5
+ * file: reports/agent-insights/<agentId>.jsonl — one JSON record per line.
6
+ *
7
+ * JSONL (newline-delimited JSON) is used because:
8
+ * - Appends are O(1) — no need to parse and rewrite the whole file
9
+ * - Each line is a valid JSON object, easy to stream or tail
10
+ * - Human-readable and grep-friendly
11
+ *
12
+ * Implements IAgentInsightStore from @horus/contracts.
13
+ */
14
+ import fs from 'node:fs';
15
+ import path from 'node:path';
16
+ export class AgentInsightStore {
17
+ dir;
18
+ constructor(config) {
19
+ const reportsDir = typeof config === 'string' ? config : config.reportsDir;
20
+ this.dir = path.resolve(reportsDir, 'agent-insights');
21
+ fs.mkdirSync(this.dir, { recursive: true });
22
+ }
23
+ async append(insight) {
24
+ const filePath = this.filePathFor(insight.agentId);
25
+ const line = JSON.stringify(insight) + '\n';
26
+ fs.appendFileSync(filePath, line, 'utf8');
27
+ }
28
+ async readAll() {
29
+ const files = fs.existsSync(this.dir)
30
+ ? fs.readdirSync(this.dir).filter((f) => f.endsWith('.jsonl'))
31
+ : [];
32
+ const all = [];
33
+ for (const file of files) {
34
+ all.push(...this.readFile(path.join(this.dir, file)));
35
+ }
36
+ return all.sort((a, b) => a.runAt.localeCompare(b.runAt));
37
+ }
38
+ async readSince(isoTimestamp) {
39
+ const all = await this.readAll();
40
+ return all.filter((insight) => insight.runAt >= isoTimestamp);
41
+ }
42
+ async readByAgent(agentId) {
43
+ const filePath = this.filePathFor(agentId);
44
+ if (!fs.existsSync(filePath))
45
+ return [];
46
+ return this.readFile(filePath);
47
+ }
48
+ // ── Private helpers ──────────────────────────────────────────────────────
49
+ filePathFor(agentId) {
50
+ return path.join(this.dir, `${agentId}.jsonl`);
51
+ }
52
+ readFile(filePath) {
53
+ const raw = fs.readFileSync(filePath, 'utf8');
54
+ return raw
55
+ .split('\n')
56
+ .filter((line) => line.trim().length > 0)
57
+ .map((line) => JSON.parse(line));
58
+ }
59
+ }
60
+ //# sourceMappingURL=AgentInsightStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AgentInsightStore.js","sourceRoot":"","sources":["../src/AgentInsightStore.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * CoverageStore
3
+ *
4
+ * Appends CoverageSnapshots to reports/coverage-history.jsonl after each
5
+ * test:coverage run. Provides helpers to compute deltas between runs.
6
+ */
7
+ import { CoverageSnapshot, CoverageDelta, HorusConfig } from '@horus/contracts';
8
+ export declare class CoverageStore {
9
+ private readonly filePath;
10
+ private readonly thresholds;
11
+ constructor(config: HorusConfig | string);
12
+ append(snapshot: CoverageSnapshot): Promise<void>;
13
+ readAll(): Promise<CoverageSnapshot[]>;
14
+ /** Returns the delta between the two most recent snapshots, or null if fewer than 2 exist. */
15
+ latestDelta(): Promise<CoverageDelta | null>;
16
+ }
17
+ export declare function computeDelta(prev: CoverageSnapshot, curr: CoverageSnapshot, thresholds?: {
18
+ lines: number;
19
+ functions: number;
20
+ branches: number;
21
+ statements: number;
22
+ }): CoverageDelta;
23
+ //# sourceMappingURL=CoverageStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CoverageStore.d.ts","sourceRoot":"","sources":["../src/CoverageStore.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAIhF,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA4B;gBAE3C,MAAM,EAAE,WAAW,GAAG,MAAM;IAQlC,MAAM,CAAC,QAAQ,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAIjD,OAAO,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAU5C,8FAA8F;IACxF,WAAW,IAAI,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;CAOnD;AAED,wBAAgB,YAAY,CAC1B,IAAI,EAAE,gBAAgB,EACtB,IAAI,EAAE,gBAAgB,EACtB,UAAU;;;;;CAAqB,GAC9B,aAAa,CAaf"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * CoverageStore
3
+ *
4
+ * Appends CoverageSnapshots to reports/coverage-history.jsonl after each
5
+ * test:coverage run. Provides helpers to compute deltas between runs.
6
+ */
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+ const DEFAULT_THRESHOLDS = { lines: 80, functions: 80, branches: 75, statements: 80 };
10
+ export class CoverageStore {
11
+ filePath;
12
+ thresholds;
13
+ constructor(config) {
14
+ const reportsDir = typeof config === 'string' ? config : config.reportsDir;
15
+ this.thresholds = (typeof config !== 'string' ? config.coverage : undefined) ?? DEFAULT_THRESHOLDS;
16
+ const dir = path.resolve(reportsDir);
17
+ fs.mkdirSync(dir, { recursive: true });
18
+ this.filePath = path.join(dir, 'coverage-history.jsonl');
19
+ }
20
+ async append(snapshot) {
21
+ fs.appendFileSync(this.filePath, JSON.stringify(snapshot) + '\n', 'utf8');
22
+ }
23
+ async readAll() {
24
+ if (!fs.existsSync(this.filePath))
25
+ return [];
26
+ const raw = fs.readFileSync(this.filePath, 'utf8');
27
+ return raw
28
+ .split('\n')
29
+ .filter((line) => line.trim().length > 0)
30
+ .map((line) => JSON.parse(line))
31
+ .sort((a, b) => a.capturedAt.localeCompare(b.capturedAt));
32
+ }
33
+ /** Returns the delta between the two most recent snapshots, or null if fewer than 2 exist. */
34
+ async latestDelta() {
35
+ const all = await this.readAll();
36
+ if (all.length < 2)
37
+ return null;
38
+ const prev = all[all.length - 2];
39
+ const curr = all[all.length - 1];
40
+ return computeDelta(prev, curr, this.thresholds);
41
+ }
42
+ }
43
+ export function computeDelta(prev, curr, thresholds = DEFAULT_THRESHOLDS) {
44
+ const lines = round(curr.lines - prev.lines);
45
+ const functions = round(curr.functions - prev.functions);
46
+ const branches = round(curr.branches - prev.branches);
47
+ const statements = round(curr.statements - prev.statements);
48
+ const belowThreshold = curr.lines < thresholds.lines ||
49
+ curr.functions < thresholds.functions ||
50
+ curr.branches < thresholds.branches ||
51
+ curr.statements < thresholds.statements;
52
+ return { lines, functions, branches, statements, belowThreshold };
53
+ }
54
+ function round(n) {
55
+ return Math.round(n * 10) / 10;
56
+ }
57
+ //# sourceMappingURL=CoverageStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CoverageStore.js","sourceRoot":"","sources":["../src/CoverageStore.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * EventContractAnalyzer
3
+ *
4
+ * Static analyzer for event contract coverage gaps.
5
+ *
6
+ * For each topic declared in ORDER_EVENTS (or any `const X = { ... }` pattern
7
+ * matching event topic strings), it checks:
8
+ *
9
+ * 1. PUBLISH side — is there a test that asserts this topic was published?
10
+ * (looks for assertPublished / assertPublishedCount / mockEventBus calls)
11
+ * 2. SUBSCRIBE side — is there a test that exercises the handler for this topic?
12
+ * (looks for handler registrations and tests that trigger them)
13
+ *
14
+ * Operates entirely on source text — no imports, no execution.
15
+ * Output is a list of EventContractGap records suitable for AgentInsightStore.
16
+ */
17
+ export interface EventContractGap {
18
+ topic: string;
19
+ publishCovered: boolean;
20
+ subscribeCovered: boolean;
21
+ /** Files where the topic is declared */
22
+ declaredIn: string[];
23
+ /** Test files that cover the publish side */
24
+ publishCoveredBy: string[];
25
+ /** Test files that cover the subscribe side */
26
+ subscribeCoveredBy: string[];
27
+ }
28
+ export interface EventContractReport {
29
+ analyzedAt: string;
30
+ totalTopics: number;
31
+ fullyUncovered: number;
32
+ publishOnly: number;
33
+ subscribeOnly: number;
34
+ fullyCovered: number;
35
+ gaps: EventContractGap[];
36
+ }
37
+ export declare function analyzeEventContracts(rootDir: string): Promise<EventContractReport>;
38
+ //# sourceMappingURL=EventContractAnalyzer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EventContractAnalyzer.d.ts","sourceRoot":"","sources":["../src/EventContractAnalyzer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAMH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,OAAO,CAAC;IACxB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,wCAAwC;IACxC,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,6CAA6C;IAC7C,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,+CAA+C;IAC/C,kBAAkB,EAAE,MAAM,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,gBAAgB,EAAE,CAAC;CAC1B;AAyCD,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAiEzF"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * EventContractAnalyzer
3
+ *
4
+ * Static analyzer for event contract coverage gaps.
5
+ *
6
+ * For each topic declared in ORDER_EVENTS (or any `const X = { ... }` pattern
7
+ * matching event topic strings), it checks:
8
+ *
9
+ * 1. PUBLISH side — is there a test that asserts this topic was published?
10
+ * (looks for assertPublished / assertPublishedCount / mockEventBus calls)
11
+ * 2. SUBSCRIBE side — is there a test that exercises the handler for this topic?
12
+ * (looks for handler registrations and tests that trigger them)
13
+ *
14
+ * Operates entirely on source text — no imports, no execution.
15
+ * Output is a list of EventContractGap records suitable for AgentInsightStore.
16
+ */
17
+ import fs from 'node:fs';
18
+ import path from 'node:path';
19
+ import { glob } from './glob.js';
20
+ // ── Helpers ────────────────────────────────────────────────────────────────
21
+ /** Extract all string values from `const X = { KEY: 'value', ... }` patterns */
22
+ function extractEventTopics(src) {
23
+ const topics = [];
24
+ // Match single or double quoted string values in object literals
25
+ const valuePattern = /:\s*['"]([a-z][a-z0-9]*(?:[._][a-z0-9]+)+)['"]/g;
26
+ let match;
27
+ while ((match = valuePattern.exec(src)) !== null) {
28
+ topics.push(match[1]);
29
+ }
30
+ return [...new Set(topics)];
31
+ }
32
+ /** Check if a file's content references a topic string in a test-assertion context */
33
+ function coversPublish(src, topic) {
34
+ const escaped = topic.replace(/\./g, '\\.');
35
+ // assertPublished / assertPublishedCount / eventBus.publish calls in test files
36
+ const patterns = [
37
+ new RegExp(`assertPublished(?:Count)?\\([^)]*['"]${escaped}['"]`),
38
+ new RegExp(`publish\\([^)]*['"]${escaped}['"]`),
39
+ new RegExp(`ORDER_EVENTS\\.\\w+.*${escaped}`),
40
+ ];
41
+ return patterns.some((p) => p.test(src));
42
+ }
43
+ /** Check if a file's content exercises the subscribe handler for a topic */
44
+ function coversSubscribe(src, topic) {
45
+ const escaped = topic.replace(/\./g, '\\.');
46
+ const patterns = [
47
+ new RegExp(`subscribe\\([^)]*['"]${escaped}['"]`),
48
+ new RegExp(`registerEventHandlers`), // integration tests that call this verify all handlers
49
+ new RegExp(`handle.*${escaped.replace(/\\\./g, '.*')}`),
50
+ ];
51
+ return patterns.some((p) => p.test(src));
52
+ }
53
+ // ── Main analyzer ──────────────────────────────────────────────────────────
54
+ export async function analyzeEventContracts(rootDir) {
55
+ // 1. Find all source files that declare event topics
56
+ const sourceFiles = await glob(rootDir, ['services/**/*.ts', 'shared/contracts/**/*.ts'], [
57
+ 'node_modules', 'dist', '**/*.test.ts', '**/*.spec.ts',
58
+ ]);
59
+ // 2. Find all test files
60
+ const testFiles = await glob(rootDir, ['tests/**/*.ts'], ['node_modules', 'dist']);
61
+ // 3. Collect topics from source files
62
+ const topicToFiles = new Map();
63
+ for (const file of sourceFiles) {
64
+ const src = fs.readFileSync(file, 'utf8');
65
+ const topics = extractEventTopics(src);
66
+ for (const topic of topics) {
67
+ const existing = topicToFiles.get(topic) ?? [];
68
+ existing.push(path.relative(rootDir, file));
69
+ topicToFiles.set(topic, existing);
70
+ }
71
+ }
72
+ // 4. For each topic, check test coverage
73
+ const gaps = [];
74
+ // Pre-read all test files once
75
+ const testContents = testFiles.map((f) => ({
76
+ relPath: path.relative(rootDir, f),
77
+ src: fs.readFileSync(f, 'utf8'),
78
+ }));
79
+ for (const [topic, declaredIn] of topicToFiles) {
80
+ const publishCoveredBy = testContents
81
+ .filter(({ src }) => coversPublish(src, topic))
82
+ .map(({ relPath }) => relPath);
83
+ const subscribeCoveredBy = testContents
84
+ .filter(({ src }) => coversSubscribe(src, topic))
85
+ .map(({ relPath }) => relPath);
86
+ gaps.push({
87
+ topic,
88
+ publishCovered: publishCoveredBy.length > 0,
89
+ subscribeCovered: subscribeCoveredBy.length > 0,
90
+ declaredIn,
91
+ publishCoveredBy,
92
+ subscribeCoveredBy,
93
+ });
94
+ }
95
+ // 5. Sort: fully uncovered first, then partial, then fully covered
96
+ gaps.sort((a, b) => {
97
+ const scoreA = (a.publishCovered ? 1 : 0) + (a.subscribeCovered ? 1 : 0);
98
+ const scoreB = (b.publishCovered ? 1 : 0) + (b.subscribeCovered ? 1 : 0);
99
+ return scoreA - scoreB;
100
+ });
101
+ return {
102
+ analyzedAt: new Date().toISOString(),
103
+ totalTopics: gaps.length,
104
+ fullyUncovered: gaps.filter((g) => !g.publishCovered && !g.subscribeCovered).length,
105
+ publishOnly: gaps.filter((g) => g.publishCovered && !g.subscribeCovered).length,
106
+ subscribeOnly: gaps.filter((g) => !g.publishCovered && g.subscribeCovered).length,
107
+ fullyCovered: gaps.filter((g) => g.publishCovered && g.subscribeCovered).length,
108
+ gaps,
109
+ };
110
+ }
111
+ //# sourceMappingURL=EventContractAnalyzer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EventContractAnalyzer.js","sourceRoot":"","sources":["../src/EventContractAnalyzer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * FlakinessAnalyzer
3
+ *
4
+ * Computes FlakeScore per test from a window of TestRunRecords.
5
+ * Pure function — no I/O. Feed it records, get scores back.
6
+ *
7
+ * Flakiness definition:
8
+ * - flakeRate = failCount / totalRuns
9
+ * - isFlaky = 0 < flakeRate < 1 (passes sometimes, fails sometimes)
10
+ * - isAlwaysFailing = flakeRate === 1
11
+ *
12
+ * A test with flakeRate === 0 is healthy and is excluded from the result
13
+ * unless includeHealthy is true.
14
+ */
15
+ import { TestRunRecord, FlakeScore } from '@horus/contracts';
16
+ export interface FlakinessAnalyzerOptions {
17
+ /** Include tests with flakeRate === 0 in results (default: false) */
18
+ includeHealthy?: boolean;
19
+ }
20
+ export declare function computeFlakeScores(records: TestRunRecord[], options?: FlakinessAnalyzerOptions): FlakeScore[];
21
+ //# sourceMappingURL=FlakinesAnalyzer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FlakinesAnalyzer.d.ts","sourceRoot":"","sources":["../src/FlakinesAnalyzer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE7D,MAAM,WAAW,wBAAwB;IACvC,qEAAqE;IACrE,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,aAAa,EAAE,EACxB,OAAO,GAAE,wBAA6B,GACrC,UAAU,EAAE,CA2Cd"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * FlakinessAnalyzer
3
+ *
4
+ * Computes FlakeScore per test from a window of TestRunRecords.
5
+ * Pure function — no I/O. Feed it records, get scores back.
6
+ *
7
+ * Flakiness definition:
8
+ * - flakeRate = failCount / totalRuns
9
+ * - isFlaky = 0 < flakeRate < 1 (passes sometimes, fails sometimes)
10
+ * - isAlwaysFailing = flakeRate === 1
11
+ *
12
+ * A test with flakeRate === 0 is healthy and is excluded from the result
13
+ * unless includeHealthy is true.
14
+ */
15
+ export function computeFlakeScores(records, options = {}) {
16
+ // Group records by test name
17
+ const byTest = new Map();
18
+ for (const record of records) {
19
+ const group = byTest.get(record.testName) ?? [];
20
+ group.push(record);
21
+ byTest.set(record.testName, group);
22
+ }
23
+ const scores = [];
24
+ for (const [testName, testRecords] of byTest) {
25
+ const totalRuns = testRecords.length;
26
+ const passCount = testRecords.filter((r) => r.passed).length;
27
+ const failCount = totalRuns - passCount;
28
+ const flakeRate = totalRuns > 0 ? failCount / totalRuns : 0;
29
+ const avgDurationMs = totalRuns > 0
30
+ ? Math.round(testRecords.reduce((sum, r) => sum + r.durationMs, 0) / totalRuns)
31
+ : 0;
32
+ const score = {
33
+ testName,
34
+ layer: testRecords[0].layer,
35
+ totalRuns,
36
+ passCount,
37
+ failCount,
38
+ flakeRate: Math.round(flakeRate * 1000) / 1000,
39
+ isFlaky: flakeRate > 0 && flakeRate < 1,
40
+ isAlwaysFailing: flakeRate === 1,
41
+ avgDurationMs,
42
+ };
43
+ if (options.includeHealthy || score.isFlaky || score.isAlwaysFailing) {
44
+ scores.push(score);
45
+ }
46
+ }
47
+ // Sort: always-failing first, then by flake rate descending
48
+ return scores.sort((a, b) => {
49
+ if (a.isAlwaysFailing !== b.isAlwaysFailing)
50
+ return a.isAlwaysFailing ? -1 : 1;
51
+ return b.flakeRate - a.flakeRate;
52
+ });
53
+ }
54
+ //# sourceMappingURL=FlakinesAnalyzer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FlakinesAnalyzer.js","sourceRoot":"","sources":["../src/FlakinesAnalyzer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AASH,MAAM,UAAU,kBAAkB,CAChC,OAAwB,EACxB,UAAoC,EAAE;IAEtC,6BAA6B;IAC7B,MAAM,MAAM,GAAG,IAAI,GAAG,EAA2B,CAAC;IAClD,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QAChD,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnB,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IACrC,CAAC;IAED,MAAM,MAAM,GAAiB,EAAE,CAAC;IAEhC,KAAK,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,IAAI,MAAM,EAAE,CAAC;QAC7C,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM,CAAC;QACrC,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC;QAC7D,MAAM,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;QACxC,MAAM,SAAS,GAAG,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5D,MAAM,aAAa,GACjB,SAAS,GAAG,CAAC;YACX,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,GAAG,SAAS,CAAC;YAC/E,CAAC,CAAC,CAAC,CAAC;QAER,MAAM,KAAK,GAAe;YACxB,QAAQ;YACR,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,KAAK;YAC3B,SAAS;YACT,SAAS;YACT,SAAS;YACT,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,IAAI;YAC9C,OAAO,EAAE,SAAS,GAAG,CAAC,IAAI,SAAS,GAAG,CAAC;YACvC,eAAe,EAAE,SAAS,KAAK,CAAC;YAChC,aAAa;SACd,CAAC;QAEF,IAAI,OAAO,CAAC,cAAc,IAAI,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,eAAe,EAAE,CAAC;YACrE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;IAED,4DAA4D;IAC5D,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QAC1B,IAAI,CAAC,CAAC,eAAe,KAAK,CAAC,CAAC,eAAe;YAAE,OAAO,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/E,OAAO,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC;IACnC,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * HorusVitestReporter
3
+ *
4
+ * A custom Vitest reporter that writes a TestRunRecord to the TestRunStore
5
+ * for every test result. Plugs into vitest.config.ts via the `reporters` array.
6
+ *
7
+ * Usage in vitest.config.ts:
8
+ * import { HorusVitestReporter } from '@horus/insight-store';
9
+ * reporters: ['default', new HorusVitestReporter('./reports')]
10
+ *
11
+ * Records land in: reports/test-runs/<layer>.jsonl
12
+ */
13
+ import type { Reporter, TestCase } from 'vitest/node';
14
+ import { HorusConfig } from '@horus/contracts';
15
+ export declare class HorusVitestReporter implements Reporter {
16
+ private readonly store;
17
+ private readonly commitSha;
18
+ constructor(config: HorusConfig | string);
19
+ onTestCaseResult(testCase: TestCase): Promise<void>;
20
+ }
21
+ //# sourceMappingURL=HorusVitestReporter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HorusVitestReporter.d.ts","sourceRoot":"","sources":["../src/HorusVitestReporter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEtD,OAAO,EAAiB,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAY9D,qBAAa,mBAAoB,YAAW,QAAQ;IAClD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IACrC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,MAAM,EAAE,WAAW,GAAG,MAAM;IASlC,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;CAmB1D"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * HorusVitestReporter
3
+ *
4
+ * A custom Vitest reporter that writes a TestRunRecord to the TestRunStore
5
+ * for every test result. Plugs into vitest.config.ts via the `reporters` array.
6
+ *
7
+ * Usage in vitest.config.ts:
8
+ * import { HorusVitestReporter } from '@horus/insight-store';
9
+ * reporters: ['default', new HorusVitestReporter('./reports')]
10
+ *
11
+ * Records land in: reports/test-runs/<layer>.jsonl
12
+ */
13
+ import { TestRunStore } from './TestRunStore.js';
14
+ import crypto from 'node:crypto';
15
+ import path from 'node:path';
16
+ /** Infer layer from the test file path */
17
+ function inferLayer(filepath) {
18
+ const normalized = filepath.replace(/\\/g, '/');
19
+ if (normalized.includes('/e2e/'))
20
+ return 'e2e';
21
+ if (normalized.includes('/integration/'))
22
+ return 'integration';
23
+ return 'unit';
24
+ }
25
+ export class HorusVitestReporter {
26
+ store;
27
+ commitSha;
28
+ constructor(config) {
29
+ const reportsDir = typeof config === 'string' ? config : config.reportsDir;
30
+ this.commitSha =
31
+ (typeof config !== 'string' ? config.commitSha : undefined) ??
32
+ process.env.GITHUB_SHA ??
33
+ 'local';
34
+ this.store = new TestRunStore(path.resolve(reportsDir));
35
+ }
36
+ async onTestCaseResult(testCase) {
37
+ const result = testCase.result();
38
+ if (!result)
39
+ return;
40
+ const diagnostic = testCase.diagnostic();
41
+ const record = {
42
+ id: crypto.randomUUID(),
43
+ testName: testCase.fullName,
44
+ layer: inferLayer(testCase.module.moduleId),
45
+ runAt: new Date().toISOString(),
46
+ passed: result.state === 'passed',
47
+ durationMs: Math.round(diagnostic?.duration ?? 0),
48
+ retries: diagnostic?.retryCount ?? 0,
49
+ commitSha: this.commitSha,
50
+ };
51
+ await this.store.append(record);
52
+ }
53
+ }
54
+ //# sourceMappingURL=HorusVitestReporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HorusVitestReporter.js","sourceRoot":"","sources":["../src/HorusVitestReporter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,0CAA0C;AAC1C,SAAS,UAAU,CAAC,QAAgB;IAClC,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAChD,IAAI,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,KAAK,CAAC;IAC/C,IAAI,UAAU,CAAC,QAAQ,CAAC,eAAe,CAAC;QAAE,OAAO,aAAa,CAAC;IAC/D,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,OAAO,mBAAmB;IACb,KAAK,CAAe;IACpB,SAAS,CAAS;IAEnC,YAAY,MAA4B;QACtC,MAAM,UAAU,GAAG,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC;QAC3E,IAAI,CAAC,SAAS;YACZ,CAAC,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;gBAC3D,OAAO,CAAC,GAAG,CAAC,UAAU;gBACtB,OAAO,CAAC;QACV,IAAI,CAAC,KAAK,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;IAC1D,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,QAAkB;QACvC,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC;QACjC,IAAI,CAAC,MAAM;YAAE,OAAO;QAEpB,MAAM,UAAU,GAAG,QAAQ,CAAC,UAAU,EAAE,CAAC;QAEzC,MAAM,MAAM,GAAkB;YAC5B,EAAE,EAAE,MAAM,CAAC,UAAU,EAAE;YACvB,QAAQ,EAAE,QAAQ,CAAC,QAAQ;YAC3B,KAAK,EAAE,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC;YAC3C,KAAK,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YAC/B,MAAM,EAAE,MAAM,CAAC,KAAK,KAAK,QAAQ;YACjC,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,QAAQ,IAAI,CAAC,CAAC;YACjD,OAAO,EAAE,UAAU,EAAE,UAAU,IAAI,CAAC;YACpC,SAAS,EAAE,IAAI,CAAC,SAAS;SAC1B,CAAC;QAEF,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAClC,CAAC;CACF"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * TestRunStore
3
+ *
4
+ * JSONL-backed persistence for individual test run records.
5
+ * Files: reports/test-runs/<layer>.jsonl — one record per line per test per CI run.
6
+ *
7
+ * Keeping records by layer (unit/integration/e2e) keeps files small and allows
8
+ * layer-specific queries without scanning everything.
9
+ */
10
+ import { TestRunRecord, ITestRunStore, HorusConfig } from '@horus/contracts';
11
+ export declare class TestRunStore implements ITestRunStore {
12
+ private readonly dir;
13
+ constructor(config: HorusConfig | string);
14
+ append(record: TestRunRecord): Promise<void>;
15
+ readAll(): Promise<TestRunRecord[]>;
16
+ readSince(isoTimestamp: string): Promise<TestRunRecord[]>;
17
+ readByTest(testName: string): Promise<TestRunRecord[]>;
18
+ private readFile;
19
+ }
20
+ //# sourceMappingURL=TestRunStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TestRunStore.d.ts","sourceRoot":"","sources":["../src/TestRunStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE7E,qBAAa,YAAa,YAAW,aAAa;IAChD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;gBAEjB,MAAM,EAAE,WAAW,GAAG,MAAM;IAMlC,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAK5C,OAAO,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;IAYnC,SAAS,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAKzD,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAO5D,OAAO,CAAC,QAAQ;CAQjB"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * TestRunStore
3
+ *
4
+ * JSONL-backed persistence for individual test run records.
5
+ * Files: reports/test-runs/<layer>.jsonl — one record per line per test per CI run.
6
+ *
7
+ * Keeping records by layer (unit/integration/e2e) keeps files small and allows
8
+ * layer-specific queries without scanning everything.
9
+ */
10
+ import fs from 'node:fs';
11
+ import path from 'node:path';
12
+ export class TestRunStore {
13
+ dir;
14
+ constructor(config) {
15
+ const reportsDir = typeof config === 'string' ? config : config.reportsDir;
16
+ this.dir = path.resolve(reportsDir, 'test-runs');
17
+ fs.mkdirSync(this.dir, { recursive: true });
18
+ }
19
+ async append(record) {
20
+ const filePath = path.join(this.dir, `${record.layer}.jsonl`);
21
+ fs.appendFileSync(filePath, JSON.stringify(record) + '\n', 'utf8');
22
+ }
23
+ async readAll() {
24
+ const files = fs.existsSync(this.dir)
25
+ ? fs.readdirSync(this.dir).filter((f) => f.endsWith('.jsonl'))
26
+ : [];
27
+ const all = [];
28
+ for (const file of files) {
29
+ all.push(...this.readFile(path.join(this.dir, file)));
30
+ }
31
+ return all.sort((a, b) => a.runAt.localeCompare(b.runAt));
32
+ }
33
+ async readSince(isoTimestamp) {
34
+ const all = await this.readAll();
35
+ return all.filter((r) => r.runAt >= isoTimestamp);
36
+ }
37
+ async readByTest(testName) {
38
+ const all = await this.readAll();
39
+ return all.filter((r) => r.testName === testName);
40
+ }
41
+ // ── Private helpers ──────────────────────────────────────────────────────
42
+ readFile(filePath) {
43
+ if (!fs.existsSync(filePath))
44
+ return [];
45
+ const raw = fs.readFileSync(filePath, 'utf8');
46
+ return raw
47
+ .split('\n')
48
+ .filter((line) => line.trim().length > 0)
49
+ .map((line) => JSON.parse(line));
50
+ }
51
+ }
52
+ //# sourceMappingURL=TestRunStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TestRunStore.js","sourceRoot":"","sources":["../src/TestRunStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAG7B,MAAM,OAAO,YAAY;IACN,GAAG,CAAS;IAE7B,YAAY,MAA4B;QACtC,MAAM,UAAU,GAAG,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC;QAC3E,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;QACjD,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,MAAqB;QAChC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,KAAK,QAAQ,CAAC,CAAC;QAC9D,EAAE,CAAC,cAAc,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;IACrE,CAAC;IAED,KAAK,CAAC,OAAO;QACX,MAAM,KAAK,GAAG,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC;YACnC,CAAC,CAAC,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;YAC9D,CAAC,CAAC,EAAE,CAAC;QAEP,MAAM,GAAG,GAAoB,EAAE,CAAC;QAChC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;QACxD,CAAC;QACD,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;IAC5D,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,YAAoB;QAClC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACjC,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,YAAY,CAAC,CAAC;IACpD,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,QAAgB;QAC/B,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACjC,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC;IACpD,CAAC;IAED,4EAA4E;IAEpE,QAAQ,CAAC,QAAgB;QAC/B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;YAAE,OAAO,EAAE,CAAC;QACxC,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC9C,OAAO,GAAG;aACP,KAAK,CAAC,IAAI,CAAC;aACX,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;aACxC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAkB,CAAC,CAAC;IACtD,CAAC;CACF"}
package/dist/glob.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Minimal glob utility — no external dependencies.
3
+ * Supports patterns like 'services/**\/*.ts' and an exclude list of directory/file prefixes.
4
+ */
5
+ export declare function glob(rootDir: string, patterns: string[], excludes?: string[]): Promise<string[]>;
6
+ //# sourceMappingURL=glob.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glob.d.ts","sourceRoot":"","sources":["../src/glob.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,wBAAsB,IAAI,CACxB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAAE,EAClB,QAAQ,GAAE,MAAM,EAAO,GACtB,OAAO,CAAC,MAAM,EAAE,CAAC,CAOnB"}
package/dist/glob.js ADDED
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Minimal glob utility — no external dependencies.
3
+ * Supports patterns like 'services/**\/*.ts' and an exclude list of directory/file prefixes.
4
+ */
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ export async function glob(rootDir, patterns, excludes = []) {
8
+ const results = [];
9
+ const compiledPatterns = patterns.map(compileGlob);
10
+ const compiledExcludes = excludes.map((e) => new RegExp(e.replace(/\*/g, '[^/]*')));
11
+ walk(rootDir, rootDir, compiledPatterns, compiledExcludes, results);
12
+ return results;
13
+ }
14
+ function walk(rootDir, dir, patterns, excludes, results) {
15
+ let entries;
16
+ try {
17
+ entries = fs.readdirSync(dir, { withFileTypes: true });
18
+ }
19
+ catch {
20
+ return;
21
+ }
22
+ for (const entry of entries) {
23
+ const abs = path.join(dir, entry.name);
24
+ const rel = path.relative(rootDir, abs).replace(/\\/g, '/');
25
+ if (excludes.some((ex) => ex.test(rel) || ex.test(entry.name)))
26
+ continue;
27
+ if (entry.isDirectory()) {
28
+ walk(rootDir, abs, patterns, excludes, results);
29
+ }
30
+ else if (entry.isFile()) {
31
+ if (patterns.some((p) => p.test(rel))) {
32
+ results.push(abs);
33
+ }
34
+ }
35
+ }
36
+ }
37
+ function compileGlob(pattern) {
38
+ const escaped = pattern
39
+ .replace(/\./g, '\\.')
40
+ .replace(/\*\*\//g, '(.+/)?')
41
+ .replace(/\*/g, '[^/]*');
42
+ return new RegExp(`^${escaped}$`);
43
+ }
44
+ //# sourceMappingURL=glob.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glob.js","sourceRoot":"","sources":["../src/glob.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,MAAM,CAAC,KAAK,UAAU,IAAI,CACxB,OAAe,EACf,QAAkB,EAClB,WAAqB,EAAE;IAEvB,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,gBAAgB,GAAG,QAAQ,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IACnD,MAAM,gBAAgB,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAEpF,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,OAAO,CAAC,CAAC;IACpE,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,IAAI,CACX,OAAe,EACf,GAAW,EACX,QAAkB,EAClB,QAAkB,EAClB,OAAiB;IAEjB,IAAI,OAAoB,CAAC;IACzB,IAAI,CAAC;QACH,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IACzD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;IACT,CAAC;IAED,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QACvC,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAE5D,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAAE,SAAS;QAEzE,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;QAClD,CAAC;aAAM,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;YAC1B,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;gBACtC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACpB,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,WAAW,CAAC,OAAe;IAClC,MAAM,OAAO,GAAG,OAAO;SACpB,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC;SACrB,OAAO,CAAC,SAAS,EAAE,QAAQ,CAAC;SAC5B,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAC3B,OAAO,IAAI,MAAM,CAAC,IAAI,OAAO,GAAG,CAAC,CAAC;AACpC,CAAC"}
@@ -0,0 +1,9 @@
1
+ export { AgentInsightStore } from './AgentInsightStore.js';
2
+ export { TestRunStore } from './TestRunStore.js';
3
+ export { HorusVitestReporter } from './HorusVitestReporter.js';
4
+ export { computeFlakeScores } from './FlakinesAnalyzer.js';
5
+ export type { FlakinessAnalyzerOptions } from './FlakinesAnalyzer.js';
6
+ export { CoverageStore, computeDelta } from './CoverageStore.js';
7
+ export { analyzeEventContracts } from './EventContractAnalyzer.js';
8
+ export type { EventContractGap, EventContractReport } from './EventContractAnalyzer.js';
9
+ //# 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,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,YAAY,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACtE,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,YAAY,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export { AgentInsightStore } from './AgentInsightStore.js';
2
+ export { TestRunStore } from './TestRunStore.js';
3
+ export { HorusVitestReporter } from './HorusVitestReporter.js';
4
+ export { computeFlakeScores } from './FlakinesAnalyzer.js';
5
+ export { CoverageStore, computeDelta } from './CoverageStore.js';
6
+ export { analyzeEventContracts } from './EventContractAnalyzer.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC"}
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * horus-ingest
4
+ *
5
+ * Reads a Vitest or Jest JSON reporter output file and appends a TestRunRecord
6
+ * to the TestRunStore for each test result.
7
+ *
8
+ * Usage:
9
+ * horus-ingest --file reports/unit-results.json --layer unit
10
+ * horus-ingest --file reports/integration-results.json --layer integration
11
+ *
12
+ * Compatible with: Vitest (--reporter=json), Jest (--json)
13
+ */
14
+ export {};
15
+ //# sourceMappingURL=ingest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingest.d.ts","sourceRoot":"","sources":["../src/ingest.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;GAWG"}
package/dist/ingest.js ADDED
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * horus-ingest
4
+ *
5
+ * Reads a Vitest or Jest JSON reporter output file and appends a TestRunRecord
6
+ * to the TestRunStore for each test result.
7
+ *
8
+ * Usage:
9
+ * horus-ingest --file reports/unit-results.json --layer unit
10
+ * horus-ingest --file reports/integration-results.json --layer integration
11
+ *
12
+ * Compatible with: Vitest (--reporter=json), Jest (--json)
13
+ */
14
+ import { TestRunStore } from './TestRunStore.js';
15
+ import { readFileSync } from 'node:fs';
16
+ import { parseArgs } from 'node:util';
17
+ import crypto from 'node:crypto';
18
+ const { values } = parseArgs({
19
+ options: {
20
+ file: { type: 'string' },
21
+ layer: { type: 'string' },
22
+ reportsDir: { type: 'string', default: './reports' },
23
+ },
24
+ });
25
+ if (!values.file) {
26
+ console.error('Error: --file is required');
27
+ process.exit(1);
28
+ }
29
+ const raw = JSON.parse(readFileSync(values.file, 'utf8'));
30
+ const store = new TestRunStore({ reportsDir: values.reportsDir });
31
+ const commitSha = process.env.GITHUB_SHA ?? 'local';
32
+ const layer = (values.layer ?? 'unit');
33
+ // Normalize Vitest and Jest JSON shapes to TestRunRecord
34
+ const tests = raw.testResults // Jest shape
35
+ ?? raw.files?.flatMap((f) => f.tests ?? []) // Vitest shape
36
+ ?? [];
37
+ for (const t of tests) {
38
+ const record = {
39
+ id: crypto.randomUUID(),
40
+ testName: t.fullName ?? t.name ?? 'unknown',
41
+ layer,
42
+ runAt: new Date().toISOString(),
43
+ passed: (t.status ?? t.state) === 'passed',
44
+ durationMs: Math.round(t.duration ?? 0),
45
+ retries: t.retryCount ?? 0,
46
+ commitSha,
47
+ };
48
+ await store.append(record);
49
+ }
50
+ console.log(`Ingested ${tests.length} test records → ${values.reportsDir}/test-runs/${layer}.jsonl`);
51
+ //# sourceMappingURL=ingest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingest.js","sourceRoot":"","sources":["../src/ingest.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,MAAM,MAAM,aAAa,CAAC;AAEjC,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC3B,OAAO,EAAE;QACP,IAAI,EAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE;QAC9B,KAAK,EAAO,EAAE,IAAI,EAAE,QAAQ,EAAE;QAC9B,UAAU,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,EAAE;KACrD;CACF,CAAC,CAAC;AAEH,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IACjB,OAAO,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;IAC3C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;AAC1D,MAAM,KAAK,GAAG,IAAI,YAAY,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,UAAW,EAAE,CAAC,CAAC;AACnE,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,OAAO,CAAC;AACpD,MAAM,KAAK,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI,MAAM,CAA2B,CAAC;AAEjE,yDAAyD;AACzD,MAAM,KAAK,GACT,GAAG,CAAC,WAAW,CAAe,aAAa;OACxC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAwB,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,eAAe;OAC/E,EAAE,CAAC;AAER,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;IACtB,MAAM,MAAM,GAAkB;QAC5B,EAAE,EAAU,MAAM,CAAC,UAAU,EAAE;QAC/B,QAAQ,EAAI,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,IAAI,IAAI,SAAS;QAC7C,KAAK;QACL,KAAK,EAAO,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACpC,MAAM,EAAM,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,KAAK,CAAC,KAAK,QAAQ;QAC9C,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC;QACvC,OAAO,EAAK,CAAC,CAAC,UAAU,IAAI,CAAC;QAC7B,SAAS;KACV,CAAC;IACF,MAAM,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AAC7B,CAAC;AAED,OAAO,CAAC,GAAG,CAAC,YAAY,KAAK,CAAC,MAAM,mBAAmB,MAAM,CAAC,UAAU,cAAc,KAAK,QAAQ,CAAC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@wutangbanger/horus-insight-store",
3
+ "version": "1.0.0",
4
+ "description": "JSONL-backed persistence for AI agent insights",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "dependencies": {
21
+ "@wutangbanger/horus-contracts": "1.0.0"
22
+ },
23
+ "peerDependencies": {
24
+ "vitest": ">=1.0.0 <5.0.0"
25
+ },
26
+ "peerDependenciesMeta": {
27
+ "vitest": {
28
+ "optional": true
29
+ }
30
+ },
31
+ "bin": {
32
+ "horus-ingest": "./dist/ingest.js"
33
+ },
34
+ "scripts": {
35
+ "build": "tsc -p tsconfig.build.json",
36
+ "clean": "rm -rf dist"
37
+ }
38
+ }