@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
|
@@ -5,6 +5,7 @@ declare const ClassificationEnum: z.ZodEnum<{
|
|
|
5
5
|
SELECTOR_DRIFT: "SELECTOR_DRIFT";
|
|
6
6
|
FLAKY: "FLAKY";
|
|
7
7
|
TEST_BUG: "TEST_BUG";
|
|
8
|
+
TEST_OUTDATED: "TEST_OUTDATED";
|
|
8
9
|
}>;
|
|
9
10
|
export declare const HistoryEntrySchema: z.ZodObject<{
|
|
10
11
|
ts: z.ZodString;
|
|
@@ -18,6 +19,7 @@ export declare const HistoryEntrySchema: z.ZodObject<{
|
|
|
18
19
|
SELECTOR_DRIFT: "SELECTOR_DRIFT";
|
|
19
20
|
FLAKY: "FLAKY";
|
|
20
21
|
TEST_BUG: "TEST_BUG";
|
|
22
|
+
TEST_OUTDATED: "TEST_OUTDATED";
|
|
21
23
|
}>;
|
|
22
24
|
}, z.core.$strip>;
|
|
23
25
|
export declare const StatusJsonSchema: z.ZodObject<{
|
|
@@ -33,6 +35,7 @@ export declare const StatusJsonSchema: z.ZodObject<{
|
|
|
33
35
|
SELECTOR_DRIFT: "SELECTOR_DRIFT";
|
|
34
36
|
FLAKY: "FLAKY";
|
|
35
37
|
TEST_BUG: "TEST_BUG";
|
|
38
|
+
TEST_OUTDATED: "TEST_OUTDATED";
|
|
36
39
|
}>;
|
|
37
40
|
confidence: z.ZodEnum<{
|
|
38
41
|
low: "low";
|
|
@@ -57,6 +60,7 @@ export declare const StatusJsonSchema: z.ZodObject<{
|
|
|
57
60
|
SELECTOR_DRIFT: "SELECTOR_DRIFT";
|
|
58
61
|
FLAKY: "FLAKY";
|
|
59
62
|
TEST_BUG: "TEST_BUG";
|
|
63
|
+
TEST_OUTDATED: "TEST_OUTDATED";
|
|
60
64
|
}>;
|
|
61
65
|
}, z.core.$strip>>>;
|
|
62
66
|
last_jira_comment_id: z.ZodOptional<z.ZodString>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/artifact/status.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,QAAA,MAAM,kBAAkB
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/artifact/status.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,QAAA,MAAM,kBAAkB;;;;;;;EAOtB,CAAC;AAIH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;iBAI7B,CAAC;AAEH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAc3B,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC1D,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAC9D,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAIhE,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAG1D;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI,CAGlE;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,UAAU,CAQ3E"}
|
package/dist/bin/internal.js
CHANGED
|
@@ -98,7 +98,14 @@ var init_schema = __esm(() => {
|
|
|
98
98
|
traceId: z.string().optional(),
|
|
99
99
|
runtime: z.number().nonnegative()
|
|
100
100
|
}).passthrough();
|
|
101
|
-
classification = z.enum([
|
|
101
|
+
classification = z.enum([
|
|
102
|
+
"REAL_BUG",
|
|
103
|
+
"TEST_BUG",
|
|
104
|
+
"SELECTOR_DRIFT",
|
|
105
|
+
"FLAKY",
|
|
106
|
+
"PASS",
|
|
107
|
+
"TEST_OUTDATED"
|
|
108
|
+
]);
|
|
102
109
|
runClassified = z.object({
|
|
103
110
|
scenarioId: z.string(),
|
|
104
111
|
runId: z.string(),
|
|
@@ -7730,7 +7737,7 @@ function parseFlags2(args) {
|
|
|
7730
7737
|
async function graphRecordCmd(argv) {
|
|
7731
7738
|
const [action, ...rest] = argv;
|
|
7732
7739
|
if (!action) {
|
|
7733
|
-
console.error(`Usage: xera-internal graph-record <fetch|script|exec|classify|promote> [args]`);
|
|
7740
|
+
console.error(`Usage: xera-internal graph-record <fetch|script|exec|classify|promote|dispute> [args]`);
|
|
7734
7741
|
return 1;
|
|
7735
7742
|
}
|
|
7736
7743
|
const repoRoot = process.cwd();
|
|
@@ -7774,6 +7781,43 @@ async function graphRecordCmd(argv) {
|
|
|
7774
7781
|
case "promote": {
|
|
7775
7782
|
return recordPromote(repoRoot, parseFlags2(rest));
|
|
7776
7783
|
}
|
|
7784
|
+
case "dispute": {
|
|
7785
|
+
const flags = parseFlags2(rest);
|
|
7786
|
+
const runId = flags.get("--run-id");
|
|
7787
|
+
const scenarioIdArg = flags.get("--scenario-id");
|
|
7788
|
+
const from = flags.get("--from");
|
|
7789
|
+
const to = flags.get("--to");
|
|
7790
|
+
const actor = flags.get("--actor");
|
|
7791
|
+
const reason = flags.get("--reason");
|
|
7792
|
+
if (!runId || !scenarioIdArg || !from || !to || !actor) {
|
|
7793
|
+
console.error("[graph-record dispute] required: --run-id --scenario-id --from --to --actor [--reason]");
|
|
7794
|
+
return 1;
|
|
7795
|
+
}
|
|
7796
|
+
const validClass = [
|
|
7797
|
+
"REAL_BUG",
|
|
7798
|
+
"TEST_BUG",
|
|
7799
|
+
"SELECTOR_DRIFT",
|
|
7800
|
+
"FLAKY",
|
|
7801
|
+
"PASS",
|
|
7802
|
+
"TEST_OUTDATED"
|
|
7803
|
+
];
|
|
7804
|
+
if (!validClass.includes(from) || !validClass.includes(to)) {
|
|
7805
|
+
console.error(`[graph-record dispute] --from and --to must be one of: ${validClass.join(", ")}`);
|
|
7806
|
+
return 1;
|
|
7807
|
+
}
|
|
7808
|
+
const payload = {
|
|
7809
|
+
runId,
|
|
7810
|
+
scenarioId: scenarioIdArg,
|
|
7811
|
+
originalClassification: from,
|
|
7812
|
+
disputedTo: to,
|
|
7813
|
+
qaActor: actor
|
|
7814
|
+
};
|
|
7815
|
+
if (reason)
|
|
7816
|
+
payload.qaReason = reason;
|
|
7817
|
+
const e = makeEvent("xera-report", "classification.disputed", payload);
|
|
7818
|
+
appendEvents(repoRoot, [e], { skill: "xera-report", ticketId: scenarioIdArg.slice(0, 12) });
|
|
7819
|
+
return 0;
|
|
7820
|
+
}
|
|
7777
7821
|
default:
|
|
7778
7822
|
console.error(`Unknown action: ${action}`);
|
|
7779
7823
|
return 1;
|
|
@@ -7831,7 +7875,9 @@ var IN_SCOPE_PROMPTS = [
|
|
|
7831
7875
|
"feature-from-story.md",
|
|
7832
7876
|
"script-from-feature.md",
|
|
7833
7877
|
"heal-locator.md",
|
|
7834
|
-
"extract-areas.md"
|
|
7878
|
+
"extract-areas.md",
|
|
7879
|
+
"similarity-match.md",
|
|
7880
|
+
"classify-outdated.md"
|
|
7835
7881
|
];
|
|
7836
7882
|
var REQUIRED_SECTION_HEADING = "## Handling untrusted input";
|
|
7837
7883
|
var REQUIRED_KEYWORDS = ["UNTRUSTED", "injection-follow", "<XR_"];
|
|
@@ -9266,6 +9312,96 @@ async function graphBackfillCmd(argv) {
|
|
|
9266
9312
|
return 0;
|
|
9267
9313
|
}
|
|
9268
9314
|
|
|
9315
|
+
// src/graph/enrich.ts
|
|
9316
|
+
init_store();
|
|
9317
|
+
init_ulid();
|
|
9318
|
+
import { existsSync as existsSync19, readFileSync as readFileSync16 } from "fs";
|
|
9319
|
+
import { join as join15 } from "path";
|
|
9320
|
+
import { z as z6 } from "zod";
|
|
9321
|
+
var MAX_SIMILAR_EDGES = 10;
|
|
9322
|
+
var MIN_CONFIDENCE = 0.7;
|
|
9323
|
+
var SimilarEntrySchema = z6.object({
|
|
9324
|
+
ticketId: z6.string().regex(/^[A-Z][A-Z0-9]*-\d+$/),
|
|
9325
|
+
confidence: z6.number(),
|
|
9326
|
+
reason: z6.string()
|
|
9327
|
+
});
|
|
9328
|
+
var EnrichmentInputSchema = z6.object({
|
|
9329
|
+
similar: z6.array(SimilarEntrySchema)
|
|
9330
|
+
});
|
|
9331
|
+
var nowIso3 = () => new Date().toISOString();
|
|
9332
|
+
var mk2 = (actor, type, payload) => ({
|
|
9333
|
+
event_id: ulid(),
|
|
9334
|
+
schema_version: SCHEMA_VERSION,
|
|
9335
|
+
ts: nowIso3(),
|
|
9336
|
+
actor,
|
|
9337
|
+
type,
|
|
9338
|
+
payload
|
|
9339
|
+
});
|
|
9340
|
+
async function enrichTicket(repoRoot, ticketId, opts) {
|
|
9341
|
+
const inputPath = join15(repoRoot, ".xera", ticketId, "enrichment-input.json");
|
|
9342
|
+
if (!existsSync19(inputPath)) {
|
|
9343
|
+
throw new Error(`enrichment-input.json not found at ${inputPath}`);
|
|
9344
|
+
}
|
|
9345
|
+
const raw = JSON.parse(readFileSync16(inputPath, "utf8"));
|
|
9346
|
+
const parsed = EnrichmentInputSchema.safeParse(raw);
|
|
9347
|
+
if (!parsed.success) {
|
|
9348
|
+
throw new Error(`invalid enrichment-input.json: ${parsed.error.message}`);
|
|
9349
|
+
}
|
|
9350
|
+
const snapshot = deriveSnapshot(loadAllEvents(repoRoot));
|
|
9351
|
+
if (!snapshot.tickets[ticketId]) {
|
|
9352
|
+
throw new Error(`ticket ${ticketId} not in graph; run /xera-fetch first`);
|
|
9353
|
+
}
|
|
9354
|
+
if (snapshot.tickets[ticketId].enrichedAt && !opts.force) {
|
|
9355
|
+
return { ticketId, similarCount: 0, enrichedAt: snapshot.tickets[ticketId].enrichedAt };
|
|
9356
|
+
}
|
|
9357
|
+
const validated = parsed.data.similar.map((s) => ({ ...s, confidence: Math.max(0, Math.min(1, s.confidence)) })).filter((s) => s.confidence >= MIN_CONFIDENCE).filter((s) => snapshot.tickets[s.ticketId] !== undefined).filter((s) => s.ticketId !== ticketId).slice(0, MAX_SIMILAR_EDGES);
|
|
9358
|
+
const events = [];
|
|
9359
|
+
for (const s of validated) {
|
|
9360
|
+
const payload = {
|
|
9361
|
+
kind: "similar",
|
|
9362
|
+
from: ticketId,
|
|
9363
|
+
to: s.ticketId,
|
|
9364
|
+
confidence: s.confidence,
|
|
9365
|
+
source: `llm-similarity:${s.reason.slice(0, 80)}`
|
|
9366
|
+
};
|
|
9367
|
+
events.push(mk2("graph-enrich", "edge.discovered", payload));
|
|
9368
|
+
}
|
|
9369
|
+
const enrichedAt = nowIso3();
|
|
9370
|
+
const enrichedPayload = {
|
|
9371
|
+
ticketId,
|
|
9372
|
+
enrichedAt,
|
|
9373
|
+
similarCount: validated.length
|
|
9374
|
+
};
|
|
9375
|
+
events.push(mk2("graph-enrich", "ticket.enriched", enrichedPayload));
|
|
9376
|
+
appendEvents(repoRoot, events, { skill: "graph-enrich", ticketId });
|
|
9377
|
+
return { ticketId, similarCount: validated.length, enrichedAt };
|
|
9378
|
+
}
|
|
9379
|
+
|
|
9380
|
+
// src/bin-internal/graph-enrich.ts
|
|
9381
|
+
async function graphEnrichCmd(argv) {
|
|
9382
|
+
let ticket;
|
|
9383
|
+
let force = false;
|
|
9384
|
+
for (let i = 0;i < argv.length; i++) {
|
|
9385
|
+
if (argv[i] === "--ticket")
|
|
9386
|
+
ticket = argv[++i];
|
|
9387
|
+
else if (argv[i] === "--force")
|
|
9388
|
+
force = true;
|
|
9389
|
+
}
|
|
9390
|
+
const repoRoot = process.cwd();
|
|
9391
|
+
if (!ticket) {
|
|
9392
|
+
console.error("[graph-enrich] usage: graph-enrich --ticket <id> [--force]");
|
|
9393
|
+
return 1;
|
|
9394
|
+
}
|
|
9395
|
+
try {
|
|
9396
|
+
const result = await enrichTicket(repoRoot, ticket, { force });
|
|
9397
|
+
console.log(`[graph-enrich] ${ticket} enriched (${result.similarCount} similar edges, at ${result.enrichedAt})`);
|
|
9398
|
+
return 0;
|
|
9399
|
+
} catch (e) {
|
|
9400
|
+
console.error(`[graph-enrich] ${ticket} failed: ${e.message}`);
|
|
9401
|
+
return 1;
|
|
9402
|
+
}
|
|
9403
|
+
}
|
|
9404
|
+
|
|
9269
9405
|
// src/bin-internal/graph-query.ts
|
|
9270
9406
|
init_store();
|
|
9271
9407
|
function filterByTicket(snap, ticket) {
|
|
@@ -9339,8 +9475,8 @@ async function graphSnapshotCmd(argv) {
|
|
|
9339
9475
|
}
|
|
9340
9476
|
|
|
9341
9477
|
// src/bin-internal/heal-prepare.ts
|
|
9342
|
-
import { existsSync as
|
|
9343
|
-
import { join as
|
|
9478
|
+
import { existsSync as existsSync20, readdirSync as readdirSync6, readFileSync as readFileSync17, writeFileSync as writeFileSync10 } from "fs";
|
|
9479
|
+
import { join as join16 } from "path";
|
|
9344
9480
|
import { scrubFreeText } from "@xera-ai/web";
|
|
9345
9481
|
|
|
9346
9482
|
// ../../node_modules/.bun/fflate@0.8.3/node_modules/fflate/esm/index.mjs
|
|
@@ -9687,15 +9823,15 @@ function strFromU8(dat, latin1) {
|
|
|
9687
9823
|
var slzh = function(d, b) {
|
|
9688
9824
|
return b + 30 + b2(d, b + 26) + b2(d, b + 28);
|
|
9689
9825
|
};
|
|
9690
|
-
var zh = function(d, b,
|
|
9826
|
+
var zh = function(d, b, z7) {
|
|
9691
9827
|
var fnl = b2(d, b + 28), efl = b2(d, b + 30), fn = strFromU8(d.subarray(b + 46, b + 46 + fnl), !(b2(d, b + 8) & 2048)), es = b + 46 + fnl;
|
|
9692
|
-
var _a2 = z64hs(d, es, efl,
|
|
9828
|
+
var _a2 = z64hs(d, es, efl, z7, b4(d, b + 20), b4(d, b + 24), b4(d, b + 42)), sc = _a2[0], su = _a2[1], off = _a2[2];
|
|
9693
9829
|
return [b2(d, b + 10), sc, su, fn, es + efl + b2(d, b + 32), off];
|
|
9694
9830
|
};
|
|
9695
|
-
var z64hs = function(d, b, l,
|
|
9831
|
+
var z64hs = function(d, b, l, z7, sc, su, off) {
|
|
9696
9832
|
var nsc = sc == 4294967295, nsu = su == 4294967295, noff = off == 4294967295, e = b + l;
|
|
9697
9833
|
var nf = nsc + nsu + noff;
|
|
9698
|
-
if (
|
|
9834
|
+
if (z7 && nf) {
|
|
9699
9835
|
for (;b + 4 < e; b += 4 + b2(d, b + 2)) {
|
|
9700
9836
|
if (b2(d, b) == 1) {
|
|
9701
9837
|
return [
|
|
@@ -9706,7 +9842,7 @@ var z64hs = function(d, b, l, z6, sc, su, off) {
|
|
|
9706
9842
|
];
|
|
9707
9843
|
}
|
|
9708
9844
|
}
|
|
9709
|
-
if (
|
|
9845
|
+
if (z7 < 2)
|
|
9710
9846
|
err(13);
|
|
9711
9847
|
}
|
|
9712
9848
|
return [sc, su, off, 0];
|
|
@@ -9722,18 +9858,18 @@ function unzipSync(data, opts) {
|
|
|
9722
9858
|
if (!c)
|
|
9723
9859
|
return {};
|
|
9724
9860
|
var o = b4(data, e + 16);
|
|
9725
|
-
var
|
|
9726
|
-
if (
|
|
9861
|
+
var z7 = b4(data, e - 20) == 117853008;
|
|
9862
|
+
if (z7) {
|
|
9727
9863
|
var ze = b4(data, e - 12);
|
|
9728
|
-
|
|
9729
|
-
if (
|
|
9864
|
+
z7 = b4(data, ze) == 101075792;
|
|
9865
|
+
if (z7) {
|
|
9730
9866
|
c = b4(data, ze + 32);
|
|
9731
9867
|
o = b4(data, ze + 48);
|
|
9732
9868
|
}
|
|
9733
9869
|
}
|
|
9734
9870
|
var fltr = opts && opts.filter;
|
|
9735
9871
|
for (var i2 = 0;i2 < c; ++i2) {
|
|
9736
|
-
var _a2 = zh(data, o,
|
|
9872
|
+
var _a2 = zh(data, o, z7), c_2 = _a2[0], sc = _a2[1], su = _a2[2], fn = _a2[3], no = _a2[4], off = _a2[5], b = slzh(data, off);
|
|
9737
9873
|
o = no;
|
|
9738
9874
|
if (!fltr || fltr({
|
|
9739
9875
|
name: fn,
|
|
@@ -9768,9 +9904,9 @@ function classifyKind(raw) {
|
|
|
9768
9904
|
return "other";
|
|
9769
9905
|
}
|
|
9770
9906
|
function extractDomSnapshot(tracePath) {
|
|
9771
|
-
if (!
|
|
9907
|
+
if (!existsSync20(tracePath))
|
|
9772
9908
|
return "";
|
|
9773
|
-
const buf =
|
|
9909
|
+
const buf = readFileSync17(tracePath);
|
|
9774
9910
|
const entries = unzipSync(buf);
|
|
9775
9911
|
const traceKey = Object.keys(entries).find((name) => name.endsWith(".trace"));
|
|
9776
9912
|
let chosenKey = null;
|
|
@@ -9818,16 +9954,16 @@ function extractDomSnapshot(tracePath) {
|
|
|
9818
9954
|
return scrubFreeText(html);
|
|
9819
9955
|
}
|
|
9820
9956
|
function findPomLine(ticketDir, rawLocator) {
|
|
9821
|
-
const pomDir =
|
|
9957
|
+
const pomDir = join16(ticketDir, "page-objects");
|
|
9822
9958
|
const candidates = [];
|
|
9823
|
-
if (
|
|
9959
|
+
if (existsSync20(pomDir)) {
|
|
9824
9960
|
for (const name of readdirSync6(pomDir)) {
|
|
9825
9961
|
if (name.endsWith(".ts"))
|
|
9826
|
-
candidates.push(
|
|
9962
|
+
candidates.push(join16(pomDir, name));
|
|
9827
9963
|
}
|
|
9828
9964
|
}
|
|
9829
9965
|
for (const file of candidates) {
|
|
9830
|
-
const text =
|
|
9966
|
+
const text = readFileSync17(file, "utf8");
|
|
9831
9967
|
const lines = text.split(`
|
|
9832
9968
|
`);
|
|
9833
9969
|
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
@@ -9865,13 +10001,13 @@ function findGherkinStep(featureText, rawLocator) {
|
|
|
9865
10001
|
}
|
|
9866
10002
|
function healPrepare(repoRoot, ticket, runId, scenarioName) {
|
|
9867
10003
|
const paths = resolveArtifactPaths(repoRoot, ticket);
|
|
9868
|
-
const classifierPath =
|
|
9869
|
-
const classifier = JSON.parse(
|
|
10004
|
+
const classifierPath = join16(paths.ticketDir, "classifier-input.json");
|
|
10005
|
+
const classifier = JSON.parse(readFileSync17(classifierPath, "utf8"));
|
|
9870
10006
|
const cls = classifier.scenarios.find((s) => s.name === scenarioName);
|
|
9871
10007
|
if (!cls)
|
|
9872
10008
|
throw new Error(`scenario not found in classifier-input: "${scenarioName}"`);
|
|
9873
|
-
const runDir =
|
|
9874
|
-
const normalized = JSON.parse(
|
|
10009
|
+
const runDir = join16(paths.runsDir, runId);
|
|
10010
|
+
const normalized = JSON.parse(readFileSync17(join16(runDir, "normalized.json"), "utf8"));
|
|
9875
10011
|
const normSc = normalized.scenarios.find((s) => s.name === scenarioName);
|
|
9876
10012
|
if (!normSc?.failure)
|
|
9877
10013
|
throw new Error(`no failure recorded for scenario "${scenarioName}"`);
|
|
@@ -9882,9 +10018,9 @@ function healPrepare(repoRoot, ticket, runId, scenarioName) {
|
|
|
9882
10018
|
const raw = m[1].trim();
|
|
9883
10019
|
const kind = classifyKind(raw);
|
|
9884
10020
|
const pomLoc = findPomLine(paths.ticketDir, raw);
|
|
9885
|
-
const featureText =
|
|
10021
|
+
const featureText = readFileSync17(paths.featurePath, "utf8");
|
|
9886
10022
|
const gherkinStep = findGherkinStep(featureText, raw);
|
|
9887
|
-
const domSnapshotAtFailure = extractDomSnapshot(
|
|
10023
|
+
const domSnapshotAtFailure = extractDomSnapshot(join16(runDir, "trace.zip"));
|
|
9888
10024
|
return {
|
|
9889
10025
|
ticket,
|
|
9890
10026
|
runId,
|
|
@@ -9904,7 +10040,7 @@ async function healPrepareCmd(argv) {
|
|
|
9904
10040
|
try {
|
|
9905
10041
|
const result = healPrepare(process.cwd(), ticket, runId, scenarioName);
|
|
9906
10042
|
const paths = resolveArtifactPaths(process.cwd(), ticket);
|
|
9907
|
-
const outPath =
|
|
10043
|
+
const outPath = join16(paths.runsDir, runId, "heal-input.json");
|
|
9908
10044
|
writeFileSync10(outPath, JSON.stringify(result, null, 2));
|
|
9909
10045
|
console.log(`[xera:heal-prepare] wrote ${outPath}`);
|
|
9910
10046
|
return 0;
|
|
@@ -9934,8 +10070,8 @@ async function lintCmd(argv) {
|
|
|
9934
10070
|
}
|
|
9935
10071
|
|
|
9936
10072
|
// src/bin-internal/normalize.ts
|
|
9937
|
-
import { existsSync as
|
|
9938
|
-
import { join as
|
|
10073
|
+
import { existsSync as existsSync21, readdirSync as readdirSync7 } from "fs";
|
|
10074
|
+
import { join as join17 } from "path";
|
|
9939
10075
|
import { normalizeRun } from "@xera-ai/web";
|
|
9940
10076
|
async function normalizeCmd(argv) {
|
|
9941
10077
|
const ticket = argv[0];
|
|
@@ -9950,8 +10086,8 @@ async function normalizeCmd(argv) {
|
|
|
9950
10086
|
console.error("[xera:normalize] no run found");
|
|
9951
10087
|
return 1;
|
|
9952
10088
|
}
|
|
9953
|
-
const runDir =
|
|
9954
|
-
if (!
|
|
10089
|
+
const runDir = join17(paths.runsDir, runId);
|
|
10090
|
+
if (!existsSync21(runDir)) {
|
|
9955
10091
|
console.error(`[xera:normalize] runs/${runId} missing`);
|
|
9956
10092
|
return 1;
|
|
9957
10093
|
}
|
|
@@ -9961,41 +10097,48 @@ async function normalizeCmd(argv) {
|
|
|
9961
10097
|
}
|
|
9962
10098
|
|
|
9963
10099
|
// src/bin-internal/post.ts
|
|
9964
|
-
import { existsSync as
|
|
9965
|
-
import { join as
|
|
10100
|
+
import { existsSync as existsSync23, readFileSync as readFileSync19 } from "fs";
|
|
10101
|
+
import { join as join18 } from "path";
|
|
9966
10102
|
|
|
9967
10103
|
// src/artifact/status.ts
|
|
9968
|
-
import { existsSync as
|
|
10104
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync11, readFileSync as readFileSync18, writeFileSync as writeFileSync11 } from "fs";
|
|
9969
10105
|
import { dirname as dirname6 } from "path";
|
|
9970
|
-
import { z as
|
|
9971
|
-
var ClassificationEnum =
|
|
9972
|
-
|
|
9973
|
-
|
|
9974
|
-
|
|
9975
|
-
|
|
10106
|
+
import { z as z7 } from "zod";
|
|
10107
|
+
var ClassificationEnum = z7.enum([
|
|
10108
|
+
"PASS",
|
|
10109
|
+
"REAL_BUG",
|
|
10110
|
+
"SELECTOR_DRIFT",
|
|
10111
|
+
"FLAKY",
|
|
10112
|
+
"TEST_BUG",
|
|
10113
|
+
"TEST_OUTDATED"
|
|
10114
|
+
]);
|
|
10115
|
+
var ResultEnum = z7.enum(["PASS", "FAIL"]);
|
|
10116
|
+
var ConfidenceEnum = z7.enum(["low", "medium", "high"]);
|
|
10117
|
+
var HistoryEntrySchema = z7.object({
|
|
10118
|
+
ts: z7.string(),
|
|
9976
10119
|
result: ResultEnum,
|
|
9977
10120
|
class: ClassificationEnum
|
|
9978
10121
|
});
|
|
9979
|
-
var StatusJsonSchema =
|
|
9980
|
-
ticket:
|
|
9981
|
-
lastRun:
|
|
10122
|
+
var StatusJsonSchema = z7.object({
|
|
10123
|
+
ticket: z7.string(),
|
|
10124
|
+
lastRun: z7.string(),
|
|
9982
10125
|
result: ResultEnum,
|
|
9983
10126
|
classification: ClassificationEnum,
|
|
9984
10127
|
confidence: ConfidenceEnum,
|
|
9985
|
-
scenarios:
|
|
9986
|
-
total:
|
|
9987
|
-
passed:
|
|
9988
|
-
failed:
|
|
9989
|
-
skipped:
|
|
10128
|
+
scenarios: z7.object({
|
|
10129
|
+
total: z7.number().int().nonnegative(),
|
|
10130
|
+
passed: z7.number().int().nonnegative(),
|
|
10131
|
+
failed: z7.number().int().nonnegative(),
|
|
10132
|
+
skipped: z7.number().int().nonnegative()
|
|
9990
10133
|
}),
|
|
9991
|
-
history:
|
|
9992
|
-
last_jira_comment_id:
|
|
10134
|
+
history: z7.array(HistoryEntrySchema).default([]),
|
|
10135
|
+
last_jira_comment_id: z7.string().optional()
|
|
9993
10136
|
});
|
|
9994
10137
|
var HISTORY_CAP = 20;
|
|
9995
10138
|
function readStatus(path) {
|
|
9996
|
-
if (!
|
|
10139
|
+
if (!existsSync22(path))
|
|
9997
10140
|
return null;
|
|
9998
|
-
return StatusJsonSchema.parse(JSON.parse(
|
|
10141
|
+
return StatusJsonSchema.parse(JSON.parse(readFileSync18(path, "utf8")));
|
|
9999
10142
|
}
|
|
10000
10143
|
function writeStatus(path, status) {
|
|
10001
10144
|
mkdirSync11(dirname6(path), { recursive: true });
|
|
@@ -10025,12 +10168,12 @@ async function postCmd(argv) {
|
|
|
10025
10168
|
return 0;
|
|
10026
10169
|
}
|
|
10027
10170
|
const paths = resolveArtifactPaths(cwd, ticket);
|
|
10028
|
-
const draftPath =
|
|
10029
|
-
if (!
|
|
10171
|
+
const draftPath = join18(paths.ticketDir, "jira-comment.draft.md");
|
|
10172
|
+
if (!existsSync23(draftPath)) {
|
|
10030
10173
|
console.error(`[xera:post] no draft at ${draftPath}; run \`xera-internal report\` first.`);
|
|
10031
10174
|
return 1;
|
|
10032
10175
|
}
|
|
10033
|
-
const body =
|
|
10176
|
+
const body = readFileSync19(draftPath, "utf8");
|
|
10034
10177
|
const client = await createJiraClient({
|
|
10035
10178
|
baseUrl: config.jira.baseUrl,
|
|
10036
10179
|
preferMcp: true,
|
|
@@ -10058,12 +10201,13 @@ async function promoteCmd(argv) {
|
|
|
10058
10201
|
}
|
|
10059
10202
|
|
|
10060
10203
|
// src/bin-internal/report.ts
|
|
10061
|
-
import { readFileSync as
|
|
10062
|
-
import { join as
|
|
10204
|
+
import { existsSync as existsSync25, readFileSync as readFileSync20, writeFileSync as writeFileSync12 } from "fs";
|
|
10205
|
+
import { join as join19 } from "path";
|
|
10063
10206
|
|
|
10064
10207
|
// src/classifier/aggregate.ts
|
|
10065
10208
|
var CLASS_PRIORITY = [
|
|
10066
10209
|
"REAL_BUG",
|
|
10210
|
+
"TEST_OUTDATED",
|
|
10067
10211
|
"TEST_BUG",
|
|
10068
10212
|
"SELECTOR_DRIFT",
|
|
10069
10213
|
"FLAKY",
|
|
@@ -10089,6 +10233,80 @@ function aggregateScenarios(scenarios) {
|
|
|
10089
10233
|
return { overall: chosen, overallConfidence: minConf, scenarios };
|
|
10090
10234
|
}
|
|
10091
10235
|
|
|
10236
|
+
// src/graph/classify.ts
|
|
10237
|
+
var DEFAULT_THRESHOLD = 0.7;
|
|
10238
|
+
var SHORT_CIRCUIT = ["FLAKY", "PASS"];
|
|
10239
|
+
function findCandidateTickets(graph, scenario) {
|
|
10240
|
+
const poms = graph.edges.filter((e) => e.kind === "uses" && e.from === scenario.id).map((e) => e.to);
|
|
10241
|
+
if (poms.length === 0)
|
|
10242
|
+
return [];
|
|
10243
|
+
const areas = graph.edges.filter((e) => e.kind === "covers" && poms.includes(e.from)).map((e) => e.to);
|
|
10244
|
+
if (areas.length === 0)
|
|
10245
|
+
return [];
|
|
10246
|
+
const ticketIds = graph.edges.filter((e) => e.kind === "modifies" && areas.includes(e.to)).map((e) => e.from);
|
|
10247
|
+
const seen = new Set;
|
|
10248
|
+
const out = [];
|
|
10249
|
+
for (const id of ticketIds) {
|
|
10250
|
+
if (seen.has(id))
|
|
10251
|
+
continue;
|
|
10252
|
+
seen.add(id);
|
|
10253
|
+
if (id === scenario.ticketId)
|
|
10254
|
+
continue;
|
|
10255
|
+
const t = graph.tickets[id];
|
|
10256
|
+
if (!t)
|
|
10257
|
+
continue;
|
|
10258
|
+
if (t.fetchedAt <= scenario.generatedAt)
|
|
10259
|
+
continue;
|
|
10260
|
+
out.push(t);
|
|
10261
|
+
}
|
|
10262
|
+
return out;
|
|
10263
|
+
}
|
|
10264
|
+
async function enhanceClassification(input, graph, decideOutdated, options = {}) {
|
|
10265
|
+
const threshold = options.threshold ?? DEFAULT_THRESHOLD;
|
|
10266
|
+
if (SHORT_CIRCUIT.includes(input.traceClassification)) {
|
|
10267
|
+
return { classification: input.traceClassification, confidence: 1 };
|
|
10268
|
+
}
|
|
10269
|
+
const scenario = graph.scenarios[input.scenarioId];
|
|
10270
|
+
if (!scenario)
|
|
10271
|
+
return { classification: input.traceClassification, confidence: 1 };
|
|
10272
|
+
const candidates = findCandidateTickets(graph, scenario);
|
|
10273
|
+
if (candidates.length === 0) {
|
|
10274
|
+
return { classification: input.traceClassification, confidence: 1 };
|
|
10275
|
+
}
|
|
10276
|
+
const candidateEvidence = candidates.map((t) => {
|
|
10277
|
+
const area = graph.edges.find((e) => e.kind === "modifies" && e.from === t.id)?.to ?? "";
|
|
10278
|
+
const ev = { ticketId: t.id, summary: t.summary, modifiedArea: area };
|
|
10279
|
+
if (t.ac[0])
|
|
10280
|
+
ev.relevantAcRef = t.ac[0];
|
|
10281
|
+
return ev;
|
|
10282
|
+
});
|
|
10283
|
+
const decision = await decideOutdated({ scenario, candidates });
|
|
10284
|
+
if (decision.classification === "TEST_OUTDATED" && decision.confidence >= threshold) {
|
|
10285
|
+
const evidence = {
|
|
10286
|
+
candidateTickets: candidateEvidence,
|
|
10287
|
+
reasoning: decision.evidence.reasoning,
|
|
10288
|
+
proposedAction: "regenerate-scenario"
|
|
10289
|
+
};
|
|
10290
|
+
if (decision.evidence.expectedByTest)
|
|
10291
|
+
evidence.expectedByTest = decision.evidence.expectedByTest;
|
|
10292
|
+
if (decision.evidence.actualInApp)
|
|
10293
|
+
evidence.actualInApp = decision.evidence.actualInApp;
|
|
10294
|
+
return {
|
|
10295
|
+
classification: "TEST_OUTDATED",
|
|
10296
|
+
confidence: decision.confidence,
|
|
10297
|
+
evidence
|
|
10298
|
+
};
|
|
10299
|
+
}
|
|
10300
|
+
return {
|
|
10301
|
+
classification: input.traceClassification,
|
|
10302
|
+
confidence: 1,
|
|
10303
|
+
evidence: { candidateTickets: candidateEvidence }
|
|
10304
|
+
};
|
|
10305
|
+
}
|
|
10306
|
+
|
|
10307
|
+
// src/bin-internal/report.ts
|
|
10308
|
+
init_store();
|
|
10309
|
+
|
|
10092
10310
|
// src/reporter/jira-comment.ts
|
|
10093
10311
|
function buildJiraComment(input) {
|
|
10094
10312
|
const passed = input.scenarios.filter((s) => s.outcome === "PASS").length;
|
|
@@ -10119,11 +10337,11 @@ xera v${input.xeraVersion} \u2022 prompts v${input.promptsVersion}`;
|
|
|
10119
10337
|
}
|
|
10120
10338
|
|
|
10121
10339
|
// src/reporter/status-writer.ts
|
|
10122
|
-
import { existsSync as
|
|
10340
|
+
import { existsSync as existsSync24 } from "fs";
|
|
10123
10341
|
function writeStatusFromClassification(path, input) {
|
|
10124
10342
|
const result = input.classification.overall === "PASS" ? "PASS" : "FAIL";
|
|
10125
10343
|
const entry = { ts: input.runTs, result, class: input.classification.overall };
|
|
10126
|
-
if (!
|
|
10344
|
+
if (!existsSync24(path)) {
|
|
10127
10345
|
writeStatus(path, {
|
|
10128
10346
|
ticket: input.ticket,
|
|
10129
10347
|
lastRun: input.runTs,
|
|
@@ -10156,25 +10374,58 @@ async function reportCmd(argv) {
|
|
|
10156
10374
|
return 1;
|
|
10157
10375
|
}
|
|
10158
10376
|
const paths = resolveArtifactPaths(process.cwd(), ticket);
|
|
10159
|
-
const input = JSON.parse(
|
|
10377
|
+
const input = JSON.parse(readFileSync20(inputArg.slice("--input=".length), "utf8"));
|
|
10160
10378
|
const aggregated = aggregateScenarios(input.scenarios);
|
|
10379
|
+
const decisionsPath = join19(paths.ticketDir, "runs", input.runId, "outdated-decisions.json");
|
|
10380
|
+
const decisions = existsSync25(decisionsPath) ? JSON.parse(readFileSync20(decisionsPath, "utf8")) : {};
|
|
10381
|
+
const graph = deriveSnapshot(loadAllEvents(process.cwd()));
|
|
10382
|
+
const normalizeScenarioName = (name) => name.trim().toLowerCase().replace(/\s+/g, " ");
|
|
10383
|
+
const scenarioIdByName = {};
|
|
10384
|
+
for (const [id, node] of Object.entries(graph.scenarios)) {
|
|
10385
|
+
if (node.ticketId === ticket) {
|
|
10386
|
+
scenarioIdByName[normalizeScenarioName(node.name)] = id;
|
|
10387
|
+
}
|
|
10388
|
+
}
|
|
10389
|
+
const enhancedScenarios = await Promise.all(aggregated.scenarios.map(async (s) => {
|
|
10390
|
+
if (s.outcome !== "FAIL")
|
|
10391
|
+
return s;
|
|
10392
|
+
const scenarioId2 = scenarioIdByName[normalizeScenarioName(s.name)];
|
|
10393
|
+
if (!scenarioId2)
|
|
10394
|
+
return s;
|
|
10395
|
+
const decision = decisions[scenarioId2];
|
|
10396
|
+
const decideOutdated = async () => decision ?? {
|
|
10397
|
+
classification: "BUG",
|
|
10398
|
+
confidence: 0,
|
|
10399
|
+
evidence: { reasoning: "no LLM decision" }
|
|
10400
|
+
};
|
|
10401
|
+
const enhanced = await enhanceClassification({ scenarioId: scenarioId2, traceClassification: s.class }, graph, decideOutdated);
|
|
10402
|
+
if (enhanced.classification !== s.class) {
|
|
10403
|
+
return {
|
|
10404
|
+
...s,
|
|
10405
|
+
class: enhanced.classification,
|
|
10406
|
+
rationale: `${s.rationale} | TEST_OUTDATED override (conf ${enhanced.confidence})`
|
|
10407
|
+
};
|
|
10408
|
+
}
|
|
10409
|
+
return s;
|
|
10410
|
+
}));
|
|
10411
|
+
const reAggregated = aggregateScenarios(enhancedScenarios);
|
|
10161
10412
|
const ts = new Date().toISOString();
|
|
10162
10413
|
writeStatusFromClassification(paths.statusPath, {
|
|
10163
10414
|
ticket,
|
|
10164
10415
|
runTs: ts,
|
|
10165
|
-
classification:
|
|
10416
|
+
classification: reAggregated,
|
|
10166
10417
|
scenarioCounts: input.scenarioCounts
|
|
10167
10418
|
});
|
|
10168
10419
|
const md = buildJiraComment({
|
|
10169
10420
|
ticket,
|
|
10170
10421
|
runId: input.runId,
|
|
10171
|
-
overall:
|
|
10172
|
-
overallConfidence:
|
|
10173
|
-
scenarios:
|
|
10422
|
+
overall: reAggregated.overall,
|
|
10423
|
+
overallConfidence: reAggregated.overallConfidence,
|
|
10424
|
+
scenarios: reAggregated.scenarios,
|
|
10174
10425
|
xeraVersion: "0.1.0",
|
|
10175
10426
|
promptsVersion: "1.0.0"
|
|
10176
10427
|
});
|
|
10177
|
-
const draftPath =
|
|
10428
|
+
const draftPath = join19(paths.ticketDir, "jira-comment.draft.md");
|
|
10178
10429
|
writeFileSync12(draftPath, md);
|
|
10179
10430
|
console.log(`[xera:report] wrote status.json and ${draftPath}`);
|
|
10180
10431
|
return 0;
|
|
@@ -10242,7 +10493,7 @@ async function unlockCmd(argv) {
|
|
|
10242
10493
|
}
|
|
10243
10494
|
|
|
10244
10495
|
// src/bin-internal/validate-feature.ts
|
|
10245
|
-
import { existsSync as
|
|
10496
|
+
import { existsSync as existsSync26, readFileSync as readFileSync21 } from "fs";
|
|
10246
10497
|
import { validateGherkin as validateGherkin2 } from "@xera-ai/web";
|
|
10247
10498
|
async function validateFeatureCmd(argv) {
|
|
10248
10499
|
const ticket = argv[0];
|
|
@@ -10251,11 +10502,11 @@ async function validateFeatureCmd(argv) {
|
|
|
10251
10502
|
return 1;
|
|
10252
10503
|
}
|
|
10253
10504
|
const paths = resolveArtifactPaths(process.cwd(), ticket);
|
|
10254
|
-
if (!
|
|
10505
|
+
if (!existsSync26(paths.featurePath)) {
|
|
10255
10506
|
console.error(`[xera:validate-feature] missing ${paths.featurePath}`);
|
|
10256
10507
|
return 1;
|
|
10257
10508
|
}
|
|
10258
|
-
const r = validateGherkin2(
|
|
10509
|
+
const r = validateGherkin2(readFileSync21(paths.featurePath, "utf8"));
|
|
10259
10510
|
if (r.ok) {
|
|
10260
10511
|
console.log("[xera:validate-feature] ok");
|
|
10261
10512
|
return 0;
|
|
@@ -10274,6 +10525,7 @@ var COMMANDS = {
|
|
|
10274
10525
|
exec: execCmd,
|
|
10275
10526
|
fetch: fetchCmd,
|
|
10276
10527
|
"graph-backfill": graphBackfillCmd,
|
|
10528
|
+
"graph-enrich": graphEnrichCmd,
|
|
10277
10529
|
"graph-query": graphQueryCmd,
|
|
10278
10530
|
"graph-record": graphRecordCmd,
|
|
10279
10531
|
"graph-snapshot": graphSnapshotCmd,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-enrich.d.ts","sourceRoot":"","sources":["../../src/bin-internal/graph-enrich.ts"],"names":[],"mappings":"AAEA,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAyBpE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-record.d.ts","sourceRoot":"","sources":["../../src/bin-internal/graph-record.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"graph-record.d.ts","sourceRoot":"","sources":["../../src/bin-internal/graph-record.ts"],"names":[],"mappings":"AA2EA,wBAAsB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAqCnF;AAiFD,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA6FpE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bin-internal/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bin-internal/index.ts"],"names":[],"mappings":"AAgDA,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAczD"}
|