@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.
Files changed (37) hide show
  1. package/dist/artifact/status.d.ts +4 -0
  2. package/dist/artifact/status.d.ts.map +1 -1
  3. package/dist/bin/internal.js +321 -69
  4. package/dist/bin-internal/graph-enrich.d.ts +2 -0
  5. package/dist/bin-internal/graph-enrich.d.ts.map +1 -0
  6. package/dist/bin-internal/graph-record.d.ts.map +1 -1
  7. package/dist/bin-internal/index.d.ts.map +1 -1
  8. package/dist/bin-internal/report.d.ts.map +1 -1
  9. package/dist/bin-internal/verify-prompts.d.ts.map +1 -1
  10. package/dist/classifier/aggregate.d.ts.map +1 -1
  11. package/dist/graph/classify.d.ts +42 -0
  12. package/dist/graph/classify.d.ts.map +1 -0
  13. package/dist/graph/enrich.d.ts +10 -0
  14. package/dist/graph/enrich.d.ts.map +1 -0
  15. package/dist/graph/index.d.ts +5 -0
  16. package/dist/graph/index.d.ts.map +1 -1
  17. package/dist/graph/schema.d.ts +3 -0
  18. package/dist/graph/schema.d.ts.map +1 -1
  19. package/dist/graph/similarity.d.ts +3 -0
  20. package/dist/graph/similarity.d.ts.map +1 -0
  21. package/dist/graph/types.d.ts +1 -1
  22. package/dist/graph/types.d.ts.map +1 -1
  23. package/dist/src/index.js +8 -1
  24. package/package.json +1 -1
  25. package/src/artifact/status.ts +8 -1
  26. package/src/bin-internal/graph-enrich.ts +28 -0
  27. package/src/bin-internal/graph-record.ts +45 -1
  28. package/src/bin-internal/index.ts +2 -0
  29. package/src/bin-internal/report.ts +63 -5
  30. package/src/bin-internal/verify-prompts.ts +2 -0
  31. package/src/classifier/aggregate.ts +1 -0
  32. package/src/graph/classify.ts +126 -0
  33. package/src/graph/enrich.ts +103 -0
  34. package/src/graph/index.ts +15 -0
  35. package/src/graph/schema.ts +8 -1
  36. package/src/graph/similarity.ts +43 -0
  37. 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":"AAcA,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAgC/D"}
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;AAaD,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
+ {"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;AAYlF,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,sBAAsB,EAAE,GAAG,cAAc,CAoBtF"}
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"}
@@ -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"}
@@ -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;AA+GrC,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"}
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,3 @@
1
+ import type { TicketNode } from './types';
2
+ export declare function buildSimilarityPrompt(target: TicketNode, candidates: TicketNode[]): string;
3
+ //# sourceMappingURL=similarity.d.ts.map
@@ -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"}
@@ -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":"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"}
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(["PASS", "REAL_BUG", "SELECTOR_DRIFT", "FLAKY", "TEST_BUG"]);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xera-ai/core",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -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(['PASS', 'REAL_BUG', 'SELECTOR_DRIFT', 'FLAKY', 'TEST_BUG']);
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(`Usage: xera-internal graph-record <fetch|script|exec|classify|promote> [args]`);
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: aggregated,
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: aggregated.overall,
38
- overallConfidence: aggregated.overallConfidence,
39
- scenarios: aggregated.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
  });
@@ -11,6 +11,8 @@ const IN_SCOPE_PROMPTS = [
11
11
  'script-from-feature.md',
12
12
  'heal-locator.md',
13
13
  'extract-areas.md',
14
+ 'similarity-match.md',
15
+ 'classify-outdated.md',
14
16
  ] as const;
15
17
 
16
18
  const REQUIRED_SECTION_HEADING = '## Handling untrusted input';
@@ -2,6 +2,7 @@ import type { ClassifyOutput, Confidence, ScenarioClassification } from './types
2
2
 
3
3
  const CLASS_PRIORITY: Array<ClassifyOutput['overall']> = [
4
4
  'REAL_BUG',
5
+ 'TEST_OUTDATED',
5
6
  'TEST_BUG',
6
7
  'SELECTOR_DRIFT',
7
8
  'FLAKY',
@@ -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
+ }