@xera-ai/core 0.4.0 → 0.4.1
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.
- package/dist/artifact/status.d.ts +4 -0
- package/dist/artifact/status.d.ts.map +1 -1
- package/dist/bin/internal.js +321 -69
- package/dist/bin-internal/graph-enrich.d.ts +2 -0
- package/dist/bin-internal/graph-enrich.d.ts.map +1 -0
- package/dist/bin-internal/graph-record.d.ts.map +1 -1
- package/dist/bin-internal/index.d.ts.map +1 -1
- package/dist/bin-internal/report.d.ts.map +1 -1
- package/dist/bin-internal/verify-prompts.d.ts.map +1 -1
- package/dist/classifier/aggregate.d.ts.map +1 -1
- package/dist/graph/classify.d.ts +42 -0
- package/dist/graph/classify.d.ts.map +1 -0
- package/dist/graph/enrich.d.ts +10 -0
- package/dist/graph/enrich.d.ts.map +1 -0
- package/dist/graph/index.d.ts +5 -0
- package/dist/graph/index.d.ts.map +1 -1
- package/dist/graph/schema.d.ts +3 -0
- package/dist/graph/schema.d.ts.map +1 -1
- package/dist/graph/similarity.d.ts +3 -0
- package/dist/graph/similarity.d.ts.map +1 -0
- package/dist/graph/types.d.ts +1 -1
- package/dist/graph/types.d.ts.map +1 -1
- package/dist/src/index.js +8 -1
- package/package.json +1 -1
- package/src/artifact/status.ts +8 -1
- package/src/bin-internal/graph-enrich.ts +28 -0
- package/src/bin-internal/graph-record.ts +45 -1
- package/src/bin-internal/index.ts +2 -0
- package/src/bin-internal/report.ts +63 -5
- package/src/bin-internal/verify-prompts.ts +2 -0
- package/src/classifier/aggregate.ts +1 -0
- package/src/graph/classify.ts +126 -0
- package/src/graph/enrich.ts +103 -0
- package/src/graph/index.ts +15 -0
- package/src/graph/schema.ts +8 -1
- package/src/graph/similarity.ts +43 -0
- package/src/graph/types.ts +7 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/bin-internal/report.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/bin-internal/report.ts"],"names":[],"mappings":"AAiBA,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAuF/D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify-prompts.d.ts","sourceRoot":"","sources":["../../src/bin-internal/verify-prompts.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CACjB;
|
|
1
|
+
{"version":3,"file":"verify-prompts.d.ts","sourceRoot":"","sources":["../../src/bin-internal/verify-prompts.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CACjB;AAeD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,EAAE,CA8B7D;AAED,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAQvE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aggregate.d.ts","sourceRoot":"","sources":["../../src/classifier/aggregate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAc,sBAAsB,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"aggregate.d.ts","sourceRoot":"","sources":["../../src/classifier/aggregate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAc,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAalF,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,sBAAsB,EAAE,GAAG,cAAc,CAoBtF"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Classification, ScenarioNode, Snapshot, TicketNode } from './types';
|
|
2
|
+
export interface ClassifyInput {
|
|
3
|
+
scenarioId: string;
|
|
4
|
+
traceClassification: Classification;
|
|
5
|
+
}
|
|
6
|
+
export interface CandidateEvidence {
|
|
7
|
+
ticketId: string;
|
|
8
|
+
summary: string;
|
|
9
|
+
modifiedArea: string;
|
|
10
|
+
relevantAcRef?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface ClassifyEvidence {
|
|
13
|
+
candidateTickets?: CandidateEvidence[];
|
|
14
|
+
reasoning?: string;
|
|
15
|
+
expectedByTest?: string;
|
|
16
|
+
actualInApp?: string;
|
|
17
|
+
proposedAction?: 'regenerate-scenario' | 'review-and-decide';
|
|
18
|
+
}
|
|
19
|
+
export interface ClassifyOutput {
|
|
20
|
+
classification: Classification;
|
|
21
|
+
confidence: number;
|
|
22
|
+
evidence?: ClassifyEvidence;
|
|
23
|
+
}
|
|
24
|
+
export interface OutdatedDecision {
|
|
25
|
+
classification: 'TEST_OUTDATED' | 'BUG' | 'AMBIGUOUS';
|
|
26
|
+
confidence: number;
|
|
27
|
+
evidence: {
|
|
28
|
+
reasoning: string;
|
|
29
|
+
expectedByTest?: string;
|
|
30
|
+
actualInApp?: string;
|
|
31
|
+
relevantAcRef?: string;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export type DecideOutdated = (args: {
|
|
35
|
+
scenario: ScenarioNode;
|
|
36
|
+
candidates: TicketNode[];
|
|
37
|
+
}) => Promise<OutdatedDecision>;
|
|
38
|
+
export declare function findCandidateTickets(graph: Snapshot, scenario: ScenarioNode): TicketNode[];
|
|
39
|
+
export declare function enhanceClassification(input: ClassifyInput, graph: Snapshot, decideOutdated: DecideOutdated, options?: {
|
|
40
|
+
threshold?: number;
|
|
41
|
+
}): Promise<ClassifyOutput>;
|
|
42
|
+
//# sourceMappingURL=classify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classify.d.ts","sourceRoot":"","sources":["../../src/graph/classify.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAElF,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,mBAAmB,EAAE,cAAc,CAAC;CACrC;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,gBAAgB,CAAC,EAAE,iBAAiB,EAAE,CAAC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,qBAAqB,GAAG,mBAAmB,CAAC;CAC9D;AAED,MAAM,WAAW,cAAc;IAC7B,cAAc,EAAE,cAAc,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,gBAAgB,CAAC;CAC7B;AAED,MAAM,WAAW,gBAAgB;IAC/B,cAAc,EAAE,eAAe,GAAG,KAAK,GAAG,WAAW,CAAC;IACtD,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE;QACR,SAAS,EAAE,MAAM,CAAC;QAClB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,aAAa,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC;CACH;AAED,MAAM,MAAM,cAAc,GAAG,CAAC,IAAI,EAAE;IAClC,QAAQ,EAAE,YAAY,CAAC;IACvB,UAAU,EAAE,UAAU,EAAE,CAAC;CAC1B,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC;AAKhC,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,GAAG,UAAU,EAAE,CA2B1F;AAED,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,aAAa,EACpB,KAAK,EAAE,QAAQ,EACf,cAAc,EAAE,cAAc,EAC9B,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACnC,OAAO,CAAC,cAAc,CAAC,CA4CzB"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface EnrichOptions {
|
|
2
|
+
force?: boolean;
|
|
3
|
+
}
|
|
4
|
+
export interface EnrichResult {
|
|
5
|
+
ticketId: string;
|
|
6
|
+
similarCount: number;
|
|
7
|
+
enrichedAt: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function enrichTicket(repoRoot: string, ticketId: string, opts: EnrichOptions): Promise<EnrichResult>;
|
|
10
|
+
//# sourceMappingURL=enrich.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enrich.d.ts","sourceRoot":"","sources":["../../src/graph/enrich.ts"],"names":[],"mappings":"AAqBA,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB;AAkBD,wBAAsB,YAAY,CAChC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,YAAY,CAAC,CAmDvB"}
|
package/dist/graph/index.d.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
export type { CandidateEvidence, ClassifyEvidence, ClassifyInput, ClassifyOutput, DecideOutdated, OutdatedDecision, } from './classify';
|
|
2
|
+
export { enhanceClassification, findCandidateTickets, } from './classify';
|
|
1
3
|
export type { CostSummary, LlmCallLog } from './cost';
|
|
2
4
|
export { logLlmCall, summarizeCost } from './cost';
|
|
5
|
+
export type { EnrichOptions, EnrichResult } from './enrich';
|
|
6
|
+
export { enrichTicket } from './enrich';
|
|
3
7
|
export { currentYyyyMm, graphPaths } from './paths';
|
|
4
8
|
export { EventSchema, safeParseEvent } from './schema';
|
|
9
|
+
export { buildSimilarityPrompt } from './similarity';
|
|
5
10
|
export { appendEvents, computeEventsHash, deriveSnapshot, isSnapshotStale, loadAllEvents, loadSnapshot, writeSnapshot, } from './store';
|
|
6
11
|
export * from './types';
|
|
7
12
|
export { ulid } from './ulid';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/graph/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AACvD,OAAO,EACL,YAAY,EACZ,iBAAiB,EACjB,cAAc,EACd,eAAe,EACf,aAAa,EACb,YAAY,EACZ,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/graph/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,cAAc,EACd,gBAAgB,GACjB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACnD,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EACL,YAAY,EACZ,iBAAiB,EACjB,cAAc,EACd,eAAe,EACf,aAAa,EACb,YAAY,EACZ,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC"}
|
package/dist/graph/schema.d.ts
CHANGED
|
@@ -108,6 +108,7 @@ export declare const EventSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
108
108
|
SELECTOR_DRIFT: "SELECTOR_DRIFT";
|
|
109
109
|
FLAKY: "FLAKY";
|
|
110
110
|
TEST_BUG: "TEST_BUG";
|
|
111
|
+
TEST_OUTDATED: "TEST_OUTDATED";
|
|
111
112
|
}>;
|
|
112
113
|
confidence: z.ZodEnum<{
|
|
113
114
|
low: "low";
|
|
@@ -130,6 +131,7 @@ export declare const EventSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
130
131
|
SELECTOR_DRIFT: "SELECTOR_DRIFT";
|
|
131
132
|
FLAKY: "FLAKY";
|
|
132
133
|
TEST_BUG: "TEST_BUG";
|
|
134
|
+
TEST_OUTDATED: "TEST_OUTDATED";
|
|
133
135
|
}>;
|
|
134
136
|
disputedTo: z.ZodEnum<{
|
|
135
137
|
PASS: "PASS";
|
|
@@ -137,6 +139,7 @@ export declare const EventSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
137
139
|
SELECTOR_DRIFT: "SELECTOR_DRIFT";
|
|
138
140
|
FLAKY: "FLAKY";
|
|
139
141
|
TEST_BUG: "TEST_BUG";
|
|
142
|
+
TEST_OUTDATED: "TEST_OUTDATED";
|
|
140
143
|
}>;
|
|
141
144
|
qaActor: z.ZodString;
|
|
142
145
|
qaReason: z.ZodOptional<z.ZodString>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/graph/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/graph/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAsHrC,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BActB,CAAC;AAEH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,OAAO,GACb;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,KAAK,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,CAAC,CAAC,QAAQ,CAAA;CAAE,CAIxE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"similarity.d.ts","sourceRoot":"","sources":["../../src/graph/similarity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAI1C,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,GAAG,MAAM,CAsC1F"}
|
package/dist/graph/types.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export declare const SCHEMA_VERSION: 1;
|
|
|
2
2
|
export type Priority = 'p0' | 'p1' | 'p2';
|
|
3
3
|
export type ScenarioStatus = 'pass' | 'fail';
|
|
4
4
|
export type EdgeKind = 'tests' | 'uses' | 'covers' | 'modifies' | 'jira-linked' | 'similar' | 'ran';
|
|
5
|
-
export type Classification = 'REAL_BUG' | 'TEST_BUG' | 'SELECTOR_DRIFT' | 'FLAKY' | 'PASS';
|
|
5
|
+
export type Classification = 'REAL_BUG' | 'TEST_BUG' | 'SELECTOR_DRIFT' | 'FLAKY' | 'PASS' | 'TEST_OUTDATED';
|
|
6
6
|
export interface TicketFetchedPayload {
|
|
7
7
|
ticketId: string;
|
|
8
8
|
summary: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/graph/types.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,cAAc,EAAG,CAAU,CAAC;AAEzC,MAAM,MAAM,QAAQ,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAC1C,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,CAAC;AAC7C,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,UAAU,GAAG,aAAa,GAAG,SAAS,GAAG,KAAK,CAAC;AAEpG,MAAM,MAAM,cAAc,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/graph/types.ts"],"names":[],"mappings":"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"}
|
package/dist/src/index.js
CHANGED
|
@@ -107,7 +107,14 @@ function generateRunId(now = new Date) {
|
|
|
107
107
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
108
108
|
import { dirname as dirname2 } from "path";
|
|
109
109
|
import { z as z2 } from "zod";
|
|
110
|
-
var ClassificationEnum = z2.enum([
|
|
110
|
+
var ClassificationEnum = z2.enum([
|
|
111
|
+
"PASS",
|
|
112
|
+
"REAL_BUG",
|
|
113
|
+
"SELECTOR_DRIFT",
|
|
114
|
+
"FLAKY",
|
|
115
|
+
"TEST_BUG",
|
|
116
|
+
"TEST_OUTDATED"
|
|
117
|
+
]);
|
|
111
118
|
var ResultEnum = z2.enum(["PASS", "FAIL"]);
|
|
112
119
|
var ConfidenceEnum = z2.enum(["low", "medium", "high"]);
|
|
113
120
|
var HistoryEntrySchema = z2.object({
|
package/package.json
CHANGED
package/src/artifact/status.ts
CHANGED
|
@@ -2,7 +2,14 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
|
2
2
|
import { dirname } from 'node:path';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
|
|
5
|
-
const ClassificationEnum = z.enum([
|
|
5
|
+
const ClassificationEnum = z.enum([
|
|
6
|
+
'PASS',
|
|
7
|
+
'REAL_BUG',
|
|
8
|
+
'SELECTOR_DRIFT',
|
|
9
|
+
'FLAKY',
|
|
10
|
+
'TEST_BUG',
|
|
11
|
+
'TEST_OUTDATED',
|
|
12
|
+
]);
|
|
6
13
|
const ResultEnum = z.enum(['PASS', 'FAIL']);
|
|
7
14
|
const ConfidenceEnum = z.enum(['low', 'medium', 'high']);
|
|
8
15
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { enrichTicket } from '../graph/enrich';
|
|
2
|
+
|
|
3
|
+
export async function graphEnrichCmd(argv: string[]): Promise<number> {
|
|
4
|
+
let ticket: string | undefined;
|
|
5
|
+
let force = false;
|
|
6
|
+
for (let i = 0; i < argv.length; i++) {
|
|
7
|
+
if (argv[i] === '--ticket') ticket = argv[++i];
|
|
8
|
+
else if (argv[i] === '--force') force = true;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const repoRoot = process.cwd();
|
|
12
|
+
|
|
13
|
+
if (!ticket) {
|
|
14
|
+
console.error('[graph-enrich] usage: graph-enrich --ticket <id> [--force]');
|
|
15
|
+
return 1;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const result = await enrichTicket(repoRoot, ticket, { force });
|
|
20
|
+
console.log(
|
|
21
|
+
`[graph-enrich] ${ticket} enriched (${result.similarCount} similar edges, at ${result.enrichedAt})`,
|
|
22
|
+
);
|
|
23
|
+
return 0;
|
|
24
|
+
} catch (e) {
|
|
25
|
+
console.error(`[graph-enrich] ${ticket} failed: ${(e as Error).message}`);
|
|
26
|
+
return 1;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -4,6 +4,8 @@ import { basename, join } from 'node:path';
|
|
|
4
4
|
import { parse as parseYaml } from 'yaml';
|
|
5
5
|
import { appendEvents } from '../graph/store';
|
|
6
6
|
import type {
|
|
7
|
+
Classification,
|
|
8
|
+
ClassificationDisputedPayload,
|
|
7
9
|
EdgeDiscoveredPayload,
|
|
8
10
|
Event,
|
|
9
11
|
PomPromotedPayload,
|
|
@@ -192,7 +194,9 @@ function parseFlags(args: string[]): Map<string, string> {
|
|
|
192
194
|
export async function graphRecordCmd(argv: string[]): Promise<number> {
|
|
193
195
|
const [action, ...rest] = argv;
|
|
194
196
|
if (!action) {
|
|
195
|
-
console.error(
|
|
197
|
+
console.error(
|
|
198
|
+
`Usage: xera-internal graph-record <fetch|script|exec|classify|promote|dispute> [args]`,
|
|
199
|
+
);
|
|
196
200
|
return 1;
|
|
197
201
|
}
|
|
198
202
|
const repoRoot = process.cwd();
|
|
@@ -236,6 +240,46 @@ export async function graphRecordCmd(argv: string[]): Promise<number> {
|
|
|
236
240
|
case 'promote': {
|
|
237
241
|
return recordPromote(repoRoot, parseFlags(rest));
|
|
238
242
|
}
|
|
243
|
+
case 'dispute': {
|
|
244
|
+
const flags = parseFlags(rest);
|
|
245
|
+
const runId = flags.get('--run-id');
|
|
246
|
+
const scenarioIdArg = flags.get('--scenario-id');
|
|
247
|
+
const from = flags.get('--from');
|
|
248
|
+
const to = flags.get('--to');
|
|
249
|
+
const actor = flags.get('--actor');
|
|
250
|
+
const reason = flags.get('--reason');
|
|
251
|
+
if (!runId || !scenarioIdArg || !from || !to || !actor) {
|
|
252
|
+
console.error(
|
|
253
|
+
'[graph-record dispute] required: --run-id --scenario-id --from --to --actor [--reason]',
|
|
254
|
+
);
|
|
255
|
+
return 1;
|
|
256
|
+
}
|
|
257
|
+
const validClass = [
|
|
258
|
+
'REAL_BUG',
|
|
259
|
+
'TEST_BUG',
|
|
260
|
+
'SELECTOR_DRIFT',
|
|
261
|
+
'FLAKY',
|
|
262
|
+
'PASS',
|
|
263
|
+
'TEST_OUTDATED',
|
|
264
|
+
];
|
|
265
|
+
if (!validClass.includes(from) || !validClass.includes(to)) {
|
|
266
|
+
console.error(
|
|
267
|
+
`[graph-record dispute] --from and --to must be one of: ${validClass.join(', ')}`,
|
|
268
|
+
);
|
|
269
|
+
return 1;
|
|
270
|
+
}
|
|
271
|
+
const payload: ClassificationDisputedPayload = {
|
|
272
|
+
runId,
|
|
273
|
+
scenarioId: scenarioIdArg,
|
|
274
|
+
originalClassification: from as Classification,
|
|
275
|
+
disputedTo: to as Classification,
|
|
276
|
+
qaActor: actor,
|
|
277
|
+
};
|
|
278
|
+
if (reason) payload.qaReason = reason;
|
|
279
|
+
const e = makeEvent('xera-report', 'classification.disputed', payload);
|
|
280
|
+
appendEvents(repoRoot, [e], { skill: 'xera-report', ticketId: scenarioIdArg.slice(0, 12) });
|
|
281
|
+
return 0;
|
|
282
|
+
}
|
|
239
283
|
default:
|
|
240
284
|
console.error(`Unknown action: ${action}`);
|
|
241
285
|
return 1;
|
|
@@ -5,6 +5,7 @@ import { evalReportCmd } from './eval-report';
|
|
|
5
5
|
import { execCmd } from './exec';
|
|
6
6
|
import { fetchCmd } from './fetch';
|
|
7
7
|
import { graphBackfillCmd } from './graph-backfill';
|
|
8
|
+
import { graphEnrichCmd } from './graph-enrich';
|
|
8
9
|
import { graphQueryCmd } from './graph-query';
|
|
9
10
|
import { graphRecordCmd } from './graph-record';
|
|
10
11
|
import { graphSnapshotCmd } from './graph-snapshot';
|
|
@@ -28,6 +29,7 @@ const COMMANDS: Record<string, (argv: string[]) => Promise<number>> = {
|
|
|
28
29
|
exec: execCmd,
|
|
29
30
|
fetch: fetchCmd,
|
|
30
31
|
'graph-backfill': graphBackfillCmd,
|
|
32
|
+
'graph-enrich': graphEnrichCmd,
|
|
31
33
|
'graph-query': graphQueryCmd,
|
|
32
34
|
'graph-record': graphRecordCmd,
|
|
33
35
|
'graph-snapshot': graphSnapshotCmd,
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync } from 'node:fs';
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { resolveArtifactPaths } from '../artifact/paths';
|
|
4
4
|
import { aggregateScenarios } from '../classifier/aggregate';
|
|
5
5
|
import type { ScenarioClassification } from '../classifier/types';
|
|
6
|
+
import type { OutdatedDecision } from '../graph/classify';
|
|
7
|
+
import { enhanceClassification } from '../graph/classify';
|
|
8
|
+
import { deriveSnapshot, loadAllEvents } from '../graph/store';
|
|
6
9
|
import { buildJiraComment } from '../reporter/jira-comment';
|
|
7
10
|
import { writeStatusFromClassification } from '../reporter/status-writer';
|
|
8
11
|
|
|
@@ -23,20 +26,75 @@ export async function reportCmd(argv: string[]): Promise<number> {
|
|
|
23
26
|
const input = JSON.parse(readFileSync(inputArg.slice('--input='.length), 'utf8')) as ReportInput;
|
|
24
27
|
|
|
25
28
|
const aggregated = aggregateScenarios(input.scenarios);
|
|
29
|
+
|
|
30
|
+
// v0.6.1: TEST_OUTDATED enhancement.
|
|
31
|
+
// The /xera-report skill writes outdated-decisions.json BEFORE invoking this subcommand,
|
|
32
|
+
// containing { [scenarioId]: { classification, confidence, evidence } } for every
|
|
33
|
+
// failing scenario the skill ran the LLM on. We use those decisions directly via
|
|
34
|
+
// an injected resolver — no Claude call here.
|
|
35
|
+
const decisionsPath = join(paths.ticketDir, 'runs', input.runId, 'outdated-decisions.json');
|
|
36
|
+
const decisions: Record<string, OutdatedDecision> = existsSync(decisionsPath)
|
|
37
|
+
? (JSON.parse(readFileSync(decisionsPath, 'utf8')) as Record<string, OutdatedDecision>)
|
|
38
|
+
: {};
|
|
39
|
+
|
|
40
|
+
const graph = deriveSnapshot(loadAllEvents(process.cwd()));
|
|
41
|
+
|
|
42
|
+
// Build a lookup: normalized name → scenarioId (graph node id) for this ticket.
|
|
43
|
+
// This mirrors how graph-record-script.ts stores scenarios using sha1(ticket:name),
|
|
44
|
+
// but here we look up by the stored node id so both sha1-keyed and stub-keyed graphs work.
|
|
45
|
+
const normalizeScenarioName = (name: string) => name.trim().toLowerCase().replace(/\s+/g, ' ');
|
|
46
|
+
|
|
47
|
+
const scenarioIdByName: Record<string, string> = {};
|
|
48
|
+
for (const [id, node] of Object.entries(graph.scenarios)) {
|
|
49
|
+
if (node.ticketId === ticket) {
|
|
50
|
+
scenarioIdByName[normalizeScenarioName(node.name)] = id;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const enhancedScenarios: ScenarioClassification[] = await Promise.all(
|
|
55
|
+
aggregated.scenarios.map(async (s) => {
|
|
56
|
+
if (s.outcome !== 'FAIL') return s;
|
|
57
|
+
const scenarioId = scenarioIdByName[normalizeScenarioName(s.name)];
|
|
58
|
+
if (!scenarioId) return s;
|
|
59
|
+
const decision = decisions[scenarioId];
|
|
60
|
+
const decideOutdated = async (): Promise<OutdatedDecision> =>
|
|
61
|
+
decision ?? {
|
|
62
|
+
classification: 'BUG' as const,
|
|
63
|
+
confidence: 0,
|
|
64
|
+
evidence: { reasoning: 'no LLM decision' },
|
|
65
|
+
};
|
|
66
|
+
const enhanced = await enhanceClassification(
|
|
67
|
+
{ scenarioId, traceClassification: s.class },
|
|
68
|
+
graph,
|
|
69
|
+
decideOutdated,
|
|
70
|
+
);
|
|
71
|
+
if (enhanced.classification !== s.class) {
|
|
72
|
+
return {
|
|
73
|
+
...s,
|
|
74
|
+
class: enhanced.classification,
|
|
75
|
+
rationale: `${s.rationale} | TEST_OUTDATED override (conf ${enhanced.confidence})`,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return s;
|
|
79
|
+
}),
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const reAggregated = aggregateScenarios(enhancedScenarios);
|
|
83
|
+
|
|
26
84
|
const ts = new Date().toISOString();
|
|
27
85
|
writeStatusFromClassification(paths.statusPath, {
|
|
28
86
|
ticket,
|
|
29
87
|
runTs: ts,
|
|
30
|
-
classification:
|
|
88
|
+
classification: reAggregated,
|
|
31
89
|
scenarioCounts: input.scenarioCounts,
|
|
32
90
|
});
|
|
33
91
|
|
|
34
92
|
const md = buildJiraComment({
|
|
35
93
|
ticket,
|
|
36
94
|
runId: input.runId,
|
|
37
|
-
overall:
|
|
38
|
-
overallConfidence:
|
|
39
|
-
scenarios:
|
|
95
|
+
overall: reAggregated.overall,
|
|
96
|
+
overallConfidence: reAggregated.overallConfidence,
|
|
97
|
+
scenarios: reAggregated.scenarios,
|
|
40
98
|
xeraVersion: '0.1.0',
|
|
41
99
|
promptsVersion: '1.0.0',
|
|
42
100
|
});
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { Classification, ScenarioNode, Snapshot, TicketNode } from './types';
|
|
2
|
+
|
|
3
|
+
export interface ClassifyInput {
|
|
4
|
+
scenarioId: string;
|
|
5
|
+
traceClassification: Classification;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface CandidateEvidence {
|
|
9
|
+
ticketId: string;
|
|
10
|
+
summary: string;
|
|
11
|
+
modifiedArea: string;
|
|
12
|
+
relevantAcRef?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ClassifyEvidence {
|
|
16
|
+
candidateTickets?: CandidateEvidence[];
|
|
17
|
+
reasoning?: string;
|
|
18
|
+
expectedByTest?: string;
|
|
19
|
+
actualInApp?: string;
|
|
20
|
+
proposedAction?: 'regenerate-scenario' | 'review-and-decide';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ClassifyOutput {
|
|
24
|
+
classification: Classification;
|
|
25
|
+
confidence: number;
|
|
26
|
+
evidence?: ClassifyEvidence;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface OutdatedDecision {
|
|
30
|
+
classification: 'TEST_OUTDATED' | 'BUG' | 'AMBIGUOUS';
|
|
31
|
+
confidence: number;
|
|
32
|
+
evidence: {
|
|
33
|
+
reasoning: string;
|
|
34
|
+
expectedByTest?: string;
|
|
35
|
+
actualInApp?: string;
|
|
36
|
+
relevantAcRef?: string;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type DecideOutdated = (args: {
|
|
41
|
+
scenario: ScenarioNode;
|
|
42
|
+
candidates: TicketNode[];
|
|
43
|
+
}) => Promise<OutdatedDecision>;
|
|
44
|
+
|
|
45
|
+
const DEFAULT_THRESHOLD = 0.7;
|
|
46
|
+
const SHORT_CIRCUIT: Classification[] = ['FLAKY', 'PASS'];
|
|
47
|
+
|
|
48
|
+
export function findCandidateTickets(graph: Snapshot, scenario: ScenarioNode): TicketNode[] {
|
|
49
|
+
const poms = graph.edges
|
|
50
|
+
.filter((e) => e.kind === 'uses' && e.from === scenario.id)
|
|
51
|
+
.map((e) => e.to);
|
|
52
|
+
if (poms.length === 0) return [];
|
|
53
|
+
|
|
54
|
+
const areas = graph.edges
|
|
55
|
+
.filter((e) => e.kind === 'covers' && poms.includes(e.from))
|
|
56
|
+
.map((e) => e.to);
|
|
57
|
+
if (areas.length === 0) return [];
|
|
58
|
+
|
|
59
|
+
const ticketIds = graph.edges
|
|
60
|
+
.filter((e) => e.kind === 'modifies' && areas.includes(e.to))
|
|
61
|
+
.map((e) => e.from);
|
|
62
|
+
|
|
63
|
+
const seen = new Set<string>();
|
|
64
|
+
const out: TicketNode[] = [];
|
|
65
|
+
for (const id of ticketIds) {
|
|
66
|
+
if (seen.has(id)) continue;
|
|
67
|
+
seen.add(id);
|
|
68
|
+
if (id === scenario.ticketId) continue;
|
|
69
|
+
const t = graph.tickets[id];
|
|
70
|
+
if (!t) continue;
|
|
71
|
+
if (t.fetchedAt <= scenario.generatedAt) continue;
|
|
72
|
+
out.push(t);
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function enhanceClassification(
|
|
78
|
+
input: ClassifyInput,
|
|
79
|
+
graph: Snapshot,
|
|
80
|
+
decideOutdated: DecideOutdated,
|
|
81
|
+
options: { threshold?: number } = {},
|
|
82
|
+
): Promise<ClassifyOutput> {
|
|
83
|
+
const threshold = options.threshold ?? DEFAULT_THRESHOLD;
|
|
84
|
+
if (SHORT_CIRCUIT.includes(input.traceClassification)) {
|
|
85
|
+
return { classification: input.traceClassification, confidence: 1 };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const scenario = graph.scenarios[input.scenarioId];
|
|
89
|
+
if (!scenario) return { classification: input.traceClassification, confidence: 1 };
|
|
90
|
+
|
|
91
|
+
const candidates = findCandidateTickets(graph, scenario);
|
|
92
|
+
if (candidates.length === 0) {
|
|
93
|
+
return { classification: input.traceClassification, confidence: 1 };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const candidateEvidence: CandidateEvidence[] = candidates.map((t) => {
|
|
97
|
+
const area = graph.edges.find((e) => e.kind === 'modifies' && e.from === t.id)?.to ?? '';
|
|
98
|
+
const ev: CandidateEvidence = { ticketId: t.id, summary: t.summary, modifiedArea: area };
|
|
99
|
+
if (t.ac[0]) ev.relevantAcRef = t.ac[0];
|
|
100
|
+
return ev;
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const decision = await decideOutdated({ scenario, candidates });
|
|
104
|
+
|
|
105
|
+
if (decision.classification === 'TEST_OUTDATED' && decision.confidence >= threshold) {
|
|
106
|
+
const evidence: ClassifyEvidence = {
|
|
107
|
+
candidateTickets: candidateEvidence,
|
|
108
|
+
reasoning: decision.evidence.reasoning,
|
|
109
|
+
proposedAction: 'regenerate-scenario',
|
|
110
|
+
};
|
|
111
|
+
if (decision.evidence.expectedByTest)
|
|
112
|
+
evidence.expectedByTest = decision.evidence.expectedByTest;
|
|
113
|
+
if (decision.evidence.actualInApp) evidence.actualInApp = decision.evidence.actualInApp;
|
|
114
|
+
return {
|
|
115
|
+
classification: 'TEST_OUTDATED',
|
|
116
|
+
confidence: decision.confidence,
|
|
117
|
+
evidence,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
classification: input.traceClassification,
|
|
123
|
+
confidence: 1,
|
|
124
|
+
evidence: { candidateTickets: candidateEvidence },
|
|
125
|
+
};
|
|
126
|
+
}
|