@xera-ai/core 0.4.2 → 0.4.3
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/bin/internal.js +343 -42
- package/dist/bin-internal/graph-render.d.ts +2 -0
- package/dist/bin-internal/graph-render.d.ts.map +1 -0
- package/dist/bin-internal/index.d.ts.map +1 -1
- package/dist/graph/index.d.ts +2 -0
- package/dist/graph/index.d.ts.map +1 -1
- package/dist/graph/render.d.ts +50 -0
- package/dist/graph/render.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/bin-internal/graph-render.ts +70 -0
- package/src/bin-internal/index.ts +2 -0
- package/src/graph/index.ts +2 -0
- package/src/graph/render.ts +324 -0
- package/src/graph/templates/LICENSE-vis-network.txt +176 -0
- package/src/graph/templates/graph.css +88 -0
- package/src/graph/templates/graph.html.template +31 -0
- package/src/graph/templates/graph.js +101 -0
- package/src/graph/templates/vis-network.min.js +25000 -0
package/dist/bin/internal.js
CHANGED
|
@@ -9457,6 +9457,306 @@ async function graphQueryCmd(argv) {
|
|
|
9457
9457
|
// src/bin-internal/index.ts
|
|
9458
9458
|
init_graph_record();
|
|
9459
9459
|
|
|
9460
|
+
// src/bin-internal/graph-render.ts
|
|
9461
|
+
import { mkdirSync as mkdirSync11, renameSync as renameSync2, writeFileSync as writeFileSync10 } from "fs";
|
|
9462
|
+
import { dirname as dirname7, join as join17 } from "path";
|
|
9463
|
+
|
|
9464
|
+
// src/graph/render.ts
|
|
9465
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
9466
|
+
import { dirname as dirname6, join as join16 } from "path";
|
|
9467
|
+
import { fileURLToPath } from "url";
|
|
9468
|
+
var COLORS = {
|
|
9469
|
+
ticket: "#3B82F6",
|
|
9470
|
+
scenarioPass: "#10B981",
|
|
9471
|
+
scenarioFail: "#EF4444",
|
|
9472
|
+
pom: "#F59E0B",
|
|
9473
|
+
area: "#6B7280",
|
|
9474
|
+
failure: "#EF4444",
|
|
9475
|
+
edgeModifies: "#EF4444",
|
|
9476
|
+
edgeDefault: "#9CA3AF",
|
|
9477
|
+
edgeJira: "#3B82F6",
|
|
9478
|
+
edgeSimilar: "#A855F7"
|
|
9479
|
+
};
|
|
9480
|
+
function ticketsAfter(since, fetchedAt) {
|
|
9481
|
+
if (!since)
|
|
9482
|
+
return true;
|
|
9483
|
+
return Date.parse(fetchedAt) >= Date.parse(since);
|
|
9484
|
+
}
|
|
9485
|
+
function scenariosAfter(since, generatedAt) {
|
|
9486
|
+
if (!since)
|
|
9487
|
+
return true;
|
|
9488
|
+
return Date.parse(generatedAt) >= Date.parse(since);
|
|
9489
|
+
}
|
|
9490
|
+
function buildTicketNode(snap, ticketId) {
|
|
9491
|
+
const t = snap.tickets[ticketId];
|
|
9492
|
+
const usageCount = snap.edges.filter((e) => e.kind === "tests" && e.from === ticketId).length;
|
|
9493
|
+
const node = {
|
|
9494
|
+
id: t.id,
|
|
9495
|
+
label: t.id,
|
|
9496
|
+
group: "Ticket",
|
|
9497
|
+
color: COLORS.ticket,
|
|
9498
|
+
shape: "dot",
|
|
9499
|
+
size: 10 + Math.min(usageCount * 2, 20),
|
|
9500
|
+
title: `${t.id} \u2014 ${t.summary}`
|
|
9501
|
+
};
|
|
9502
|
+
return node;
|
|
9503
|
+
}
|
|
9504
|
+
function buildScenarioNode(snap, scenarioId2) {
|
|
9505
|
+
const s = snap.scenarios[scenarioId2];
|
|
9506
|
+
const failed = snap.latest_failures[scenarioId2];
|
|
9507
|
+
const sizeBase = s.priority === "p0" ? 14 : s.priority === "p1" ? 11 : 9;
|
|
9508
|
+
const node = {
|
|
9509
|
+
id: s.id,
|
|
9510
|
+
label: s.name,
|
|
9511
|
+
group: "Scenario",
|
|
9512
|
+
color: failed ? COLORS.scenarioFail : COLORS.scenarioPass,
|
|
9513
|
+
shape: "square",
|
|
9514
|
+
size: sizeBase,
|
|
9515
|
+
title: `${s.ticketId} / ${s.name} [${s.priority.toUpperCase()}]`
|
|
9516
|
+
};
|
|
9517
|
+
return node;
|
|
9518
|
+
}
|
|
9519
|
+
function buildPomNode(snap, pomId2) {
|
|
9520
|
+
const p = snap.poms[pomId2];
|
|
9521
|
+
const usageCount = snap.edges.filter((e) => e.kind === "uses" && e.to === pomId2).length;
|
|
9522
|
+
const node = {
|
|
9523
|
+
id: p.id,
|
|
9524
|
+
label: p.filePath.split("/").pop() ?? p.id,
|
|
9525
|
+
group: "POM",
|
|
9526
|
+
color: COLORS.pom,
|
|
9527
|
+
shape: "diamond",
|
|
9528
|
+
size: 8 + Math.min(usageCount * 2, 16),
|
|
9529
|
+
title: `${p.filePath} (${p.route || "no route"})`
|
|
9530
|
+
};
|
|
9531
|
+
return node;
|
|
9532
|
+
}
|
|
9533
|
+
function buildAreaNode(snap, areaId) {
|
|
9534
|
+
const a = snap.areas[areaId];
|
|
9535
|
+
const node = {
|
|
9536
|
+
id: a.id,
|
|
9537
|
+
label: a.id,
|
|
9538
|
+
group: "SUTArea",
|
|
9539
|
+
color: COLORS.area,
|
|
9540
|
+
shape: "hexagon",
|
|
9541
|
+
size: 12,
|
|
9542
|
+
title: `area: ${a.id}`
|
|
9543
|
+
};
|
|
9544
|
+
return node;
|
|
9545
|
+
}
|
|
9546
|
+
function buildFailureNode(_snap, failure) {
|
|
9547
|
+
const node = {
|
|
9548
|
+
id: failure.id,
|
|
9549
|
+
label: "fail",
|
|
9550
|
+
group: "Failure",
|
|
9551
|
+
color: COLORS.failure,
|
|
9552
|
+
shape: "triangle",
|
|
9553
|
+
size: 10,
|
|
9554
|
+
title: `failure on ${failure.scenarioId} @ ${failure.ts}`
|
|
9555
|
+
};
|
|
9556
|
+
return node;
|
|
9557
|
+
}
|
|
9558
|
+
function buildEdge(edge, idx) {
|
|
9559
|
+
const v = {
|
|
9560
|
+
id: `e-${idx}`,
|
|
9561
|
+
from: edge.from,
|
|
9562
|
+
to: edge.to,
|
|
9563
|
+
label: edge.kind,
|
|
9564
|
+
arrows: "to",
|
|
9565
|
+
width: 1
|
|
9566
|
+
};
|
|
9567
|
+
switch (edge.kind) {
|
|
9568
|
+
case "modifies":
|
|
9569
|
+
v.color = COLORS.edgeModifies;
|
|
9570
|
+
v.dashes = true;
|
|
9571
|
+
v.width = 2;
|
|
9572
|
+
break;
|
|
9573
|
+
case "jira-linked":
|
|
9574
|
+
v.color = COLORS.edgeJira;
|
|
9575
|
+
v.dashes = true;
|
|
9576
|
+
break;
|
|
9577
|
+
case "similar":
|
|
9578
|
+
v.color = COLORS.edgeSimilar;
|
|
9579
|
+
v.dashes = false;
|
|
9580
|
+
v.width = 1 + Math.round((edge.confidence ?? 0) * 3);
|
|
9581
|
+
break;
|
|
9582
|
+
default:
|
|
9583
|
+
v.color = COLORS.edgeDefault;
|
|
9584
|
+
break;
|
|
9585
|
+
}
|
|
9586
|
+
return v;
|
|
9587
|
+
}
|
|
9588
|
+
function bfsFromTicket(snap, ticketId, depth) {
|
|
9589
|
+
const nodeIds = new Set([ticketId]);
|
|
9590
|
+
const edgeIdxs = new Set;
|
|
9591
|
+
let frontier = new Set([ticketId]);
|
|
9592
|
+
for (let d = 0;d < depth; d++) {
|
|
9593
|
+
const next = new Set;
|
|
9594
|
+
snap.edges.forEach((e, i) => {
|
|
9595
|
+
if (frontier.has(e.from) && !nodeIds.has(e.to)) {
|
|
9596
|
+
nodeIds.add(e.to);
|
|
9597
|
+
next.add(e.to);
|
|
9598
|
+
edgeIdxs.add(i);
|
|
9599
|
+
} else if (frontier.has(e.to) && !nodeIds.has(e.from)) {
|
|
9600
|
+
nodeIds.add(e.from);
|
|
9601
|
+
next.add(e.from);
|
|
9602
|
+
edgeIdxs.add(i);
|
|
9603
|
+
} else if (frontier.has(e.from) && nodeIds.has(e.to)) {
|
|
9604
|
+
edgeIdxs.add(i);
|
|
9605
|
+
} else if (frontier.has(e.to) && nodeIds.has(e.from)) {
|
|
9606
|
+
edgeIdxs.add(i);
|
|
9607
|
+
}
|
|
9608
|
+
});
|
|
9609
|
+
frontier = next;
|
|
9610
|
+
if (frontier.size === 0)
|
|
9611
|
+
break;
|
|
9612
|
+
}
|
|
9613
|
+
return { nodeIds, edgeIdxs };
|
|
9614
|
+
}
|
|
9615
|
+
function transformForVisNetwork(snap, opts) {
|
|
9616
|
+
const mode = opts.performanceMode ?? "full";
|
|
9617
|
+
const nodes = [];
|
|
9618
|
+
const edges = [];
|
|
9619
|
+
let includeTickets = new Set;
|
|
9620
|
+
let includeScenarios = new Set;
|
|
9621
|
+
let includePoms = new Set;
|
|
9622
|
+
let includeAreas = new Set;
|
|
9623
|
+
let includeEdgeIdxs = new Set;
|
|
9624
|
+
if (opts.ticketId) {
|
|
9625
|
+
const result = bfsFromTicket(snap, opts.ticketId, opts.depth ?? 2);
|
|
9626
|
+
for (const id of result.nodeIds) {
|
|
9627
|
+
if (snap.tickets[id])
|
|
9628
|
+
includeTickets.add(id);
|
|
9629
|
+
else if (snap.scenarios[id])
|
|
9630
|
+
includeScenarios.add(id);
|
|
9631
|
+
else if (snap.poms[id])
|
|
9632
|
+
includePoms.add(id);
|
|
9633
|
+
else if (snap.areas[id])
|
|
9634
|
+
includeAreas.add(id);
|
|
9635
|
+
}
|
|
9636
|
+
includeEdgeIdxs = result.edgeIdxs;
|
|
9637
|
+
} else {
|
|
9638
|
+
includeTickets = new Set(Object.keys(snap.tickets).filter((id) => ticketsAfter(opts.since, snap.tickets[id].fetchedAt)));
|
|
9639
|
+
includeScenarios = new Set(Object.keys(snap.scenarios).filter((id) => scenariosAfter(opts.since, snap.scenarios[id].generatedAt)));
|
|
9640
|
+
includePoms = new Set(Object.keys(snap.poms));
|
|
9641
|
+
includeAreas = new Set(Object.keys(snap.areas));
|
|
9642
|
+
snap.edges.forEach((_, i) => {
|
|
9643
|
+
includeEdgeIdxs.add(i);
|
|
9644
|
+
});
|
|
9645
|
+
}
|
|
9646
|
+
if (mode === "ticket-only") {
|
|
9647
|
+
includeScenarios.clear();
|
|
9648
|
+
includePoms.clear();
|
|
9649
|
+
includeAreas.clear();
|
|
9650
|
+
}
|
|
9651
|
+
for (const id of includeTickets)
|
|
9652
|
+
nodes.push(buildTicketNode(snap, id));
|
|
9653
|
+
for (const id of includeScenarios)
|
|
9654
|
+
nodes.push(buildScenarioNode(snap, id));
|
|
9655
|
+
for (const id of includePoms)
|
|
9656
|
+
nodes.push(buildPomNode(snap, id));
|
|
9657
|
+
for (const id of includeAreas)
|
|
9658
|
+
nodes.push(buildAreaNode(snap, id));
|
|
9659
|
+
for (const failure of Object.values(snap.latest_failures)) {
|
|
9660
|
+
if (includeScenarios.has(failure.scenarioId)) {
|
|
9661
|
+
nodes.push(buildFailureNode(snap, failure));
|
|
9662
|
+
}
|
|
9663
|
+
}
|
|
9664
|
+
const visibleNodeIds = new Set(nodes.map((n) => n.id));
|
|
9665
|
+
for (const i of includeEdgeIdxs) {
|
|
9666
|
+
const e = snap.edges[i];
|
|
9667
|
+
if (!e)
|
|
9668
|
+
continue;
|
|
9669
|
+
if (!visibleNodeIds.has(e.from) || !visibleNodeIds.has(e.to))
|
|
9670
|
+
continue;
|
|
9671
|
+
edges.push(buildEdge(e, i));
|
|
9672
|
+
}
|
|
9673
|
+
const stats = {
|
|
9674
|
+
tickets: includeTickets.size,
|
|
9675
|
+
scenarios: includeScenarios.size,
|
|
9676
|
+
poms: includePoms.size,
|
|
9677
|
+
areas: includeAreas.size,
|
|
9678
|
+
failures: nodes.filter((n) => n.group === "Failure").length,
|
|
9679
|
+
edges: edges.length
|
|
9680
|
+
};
|
|
9681
|
+
return { nodes, edges, stats };
|
|
9682
|
+
}
|
|
9683
|
+
var __filename2 = fileURLToPath(import.meta.url);
|
|
9684
|
+
var __dirname2 = dirname6(__filename2);
|
|
9685
|
+
var TEMPLATES_DIR = join16(__dirname2, "templates");
|
|
9686
|
+
function loadTemplate(name) {
|
|
9687
|
+
return readFileSync17(join16(TEMPLATES_DIR, name), "utf8");
|
|
9688
|
+
}
|
|
9689
|
+
function statsToHuman(s) {
|
|
9690
|
+
return `${s.tickets} tickets \xB7 ${s.scenarios} scenarios \xB7 ${s.poms} POMs \xB7 ${s.edges} edges`;
|
|
9691
|
+
}
|
|
9692
|
+
function renderHtml(input) {
|
|
9693
|
+
const template = loadTemplate("graph.html.template");
|
|
9694
|
+
const css = loadTemplate("graph.css");
|
|
9695
|
+
const js = loadTemplate("graph.js");
|
|
9696
|
+
const visNetwork = loadTemplate("vis-network.min.js");
|
|
9697
|
+
const graphJson = JSON.stringify(input.data);
|
|
9698
|
+
const statsHuman = statsToHuman(input.stats);
|
|
9699
|
+
return template.replace("{{CSS}}", () => css).replace("{{STATS}}", () => statsHuman).replace("{{GENERATED_AT}}", () => input.generatedAt).replace("{{VIS_NETWORK_JS}}", () => visNetwork).replace("{{GRAPH_DATA}}", () => graphJson).replace("{{INTERACTION_JS}}", () => js);
|
|
9700
|
+
}
|
|
9701
|
+
|
|
9702
|
+
// src/bin-internal/graph-render.ts
|
|
9703
|
+
init_store();
|
|
9704
|
+
function parseDepth(s) {
|
|
9705
|
+
const n = s ? Number.parseInt(s, 10) : 2;
|
|
9706
|
+
if (n === 1 || n === 3)
|
|
9707
|
+
return n;
|
|
9708
|
+
return 2;
|
|
9709
|
+
}
|
|
9710
|
+
function decidePerformanceMode(nodeCount) {
|
|
9711
|
+
if (nodeCount > 2000)
|
|
9712
|
+
return "text-fallback";
|
|
9713
|
+
if (nodeCount > 500)
|
|
9714
|
+
return "ticket-only";
|
|
9715
|
+
return "full";
|
|
9716
|
+
}
|
|
9717
|
+
async function graphRenderCmd(argv) {
|
|
9718
|
+
let outPath;
|
|
9719
|
+
let ticketId;
|
|
9720
|
+
let since;
|
|
9721
|
+
let depth = 2;
|
|
9722
|
+
for (let i = 0;i < argv.length; i++) {
|
|
9723
|
+
if (argv[i] === "--out")
|
|
9724
|
+
outPath = argv[++i];
|
|
9725
|
+
else if (argv[i] === "--ticket")
|
|
9726
|
+
ticketId = argv[++i];
|
|
9727
|
+
else if (argv[i] === "--since")
|
|
9728
|
+
since = argv[++i];
|
|
9729
|
+
else if (argv[i] === "--depth")
|
|
9730
|
+
depth = parseDepth(argv[++i]);
|
|
9731
|
+
}
|
|
9732
|
+
const repoRoot = process.cwd();
|
|
9733
|
+
const finalPath = outPath ?? join17(repoRoot, ".xera/graph.html");
|
|
9734
|
+
const snap = deriveSnapshot(loadAllEvents(repoRoot));
|
|
9735
|
+
const totalNodeCount = Object.keys(snap.tickets).length + Object.keys(snap.scenarios).length + Object.keys(snap.poms).length + Object.keys(snap.areas).length;
|
|
9736
|
+
const performanceMode = decidePerformanceMode(totalNodeCount);
|
|
9737
|
+
if (performanceMode === "text-fallback") {
|
|
9738
|
+
const txtPath = finalPath.replace(/\.html$/, ".txt");
|
|
9739
|
+
mkdirSync11(dirname7(txtPath), { recursive: true });
|
|
9740
|
+
writeFileSync10(txtPath, `Graph too large for HTML viewer (${totalNodeCount} nodes). Use 'xera:graph-query --format text' instead.
|
|
9741
|
+
`);
|
|
9742
|
+
console.log(`[graph-render] graph too large (${totalNodeCount} nodes); wrote ${txtPath}`);
|
|
9743
|
+
return 0;
|
|
9744
|
+
}
|
|
9745
|
+
const opts = { depth, performanceMode };
|
|
9746
|
+
if (ticketId)
|
|
9747
|
+
opts.ticketId = ticketId;
|
|
9748
|
+
if (since)
|
|
9749
|
+
opts.since = since;
|
|
9750
|
+
const data = transformForVisNetwork(snap, opts);
|
|
9751
|
+
const html = renderHtml({ data, stats: data.stats, generatedAt: new Date().toISOString() });
|
|
9752
|
+
mkdirSync11(dirname7(finalPath), { recursive: true });
|
|
9753
|
+
const tmpPath = `${finalPath}.tmp`;
|
|
9754
|
+
writeFileSync10(tmpPath, html);
|
|
9755
|
+
renameSync2(tmpPath, finalPath);
|
|
9756
|
+
console.log(`[graph-render] wrote ${finalPath} (${data.stats.tickets} tickets \xB7 ${data.stats.scenarios} scenarios \xB7 ${html.length} bytes)`);
|
|
9757
|
+
return 0;
|
|
9758
|
+
}
|
|
9759
|
+
|
|
9460
9760
|
// src/bin-internal/graph-snapshot.ts
|
|
9461
9761
|
init_store();
|
|
9462
9762
|
async function graphSnapshotCmd(argv) {
|
|
@@ -9482,8 +9782,8 @@ async function graphSnapshotCmd(argv) {
|
|
|
9482
9782
|
}
|
|
9483
9783
|
|
|
9484
9784
|
// src/bin-internal/heal-prepare.ts
|
|
9485
|
-
import { existsSync as existsSync20, readdirSync as readdirSync6, readFileSync as
|
|
9486
|
-
import { join as
|
|
9785
|
+
import { existsSync as existsSync20, readdirSync as readdirSync6, readFileSync as readFileSync18, writeFileSync as writeFileSync11 } from "fs";
|
|
9786
|
+
import { join as join18 } from "path";
|
|
9487
9787
|
import { scrubFreeText } from "@xera-ai/web";
|
|
9488
9788
|
|
|
9489
9789
|
// ../../node_modules/.bun/fflate@0.8.3/node_modules/fflate/esm/index.mjs
|
|
@@ -9913,7 +10213,7 @@ function classifyKind(raw) {
|
|
|
9913
10213
|
function extractDomSnapshot(tracePath) {
|
|
9914
10214
|
if (!existsSync20(tracePath))
|
|
9915
10215
|
return "";
|
|
9916
|
-
const buf =
|
|
10216
|
+
const buf = readFileSync18(tracePath);
|
|
9917
10217
|
const entries = unzipSync(buf);
|
|
9918
10218
|
const traceKey = Object.keys(entries).find((name) => name.endsWith(".trace"));
|
|
9919
10219
|
let chosenKey = null;
|
|
@@ -9961,16 +10261,16 @@ function extractDomSnapshot(tracePath) {
|
|
|
9961
10261
|
return scrubFreeText(html);
|
|
9962
10262
|
}
|
|
9963
10263
|
function findPomLine(ticketDir, rawLocator) {
|
|
9964
|
-
const pomDir =
|
|
10264
|
+
const pomDir = join18(ticketDir, "page-objects");
|
|
9965
10265
|
const candidates = [];
|
|
9966
10266
|
if (existsSync20(pomDir)) {
|
|
9967
10267
|
for (const name of readdirSync6(pomDir)) {
|
|
9968
10268
|
if (name.endsWith(".ts"))
|
|
9969
|
-
candidates.push(
|
|
10269
|
+
candidates.push(join18(pomDir, name));
|
|
9970
10270
|
}
|
|
9971
10271
|
}
|
|
9972
10272
|
for (const file of candidates) {
|
|
9973
|
-
const text =
|
|
10273
|
+
const text = readFileSync18(file, "utf8");
|
|
9974
10274
|
const lines = text.split(`
|
|
9975
10275
|
`);
|
|
9976
10276
|
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
@@ -10008,13 +10308,13 @@ function findGherkinStep(featureText, rawLocator) {
|
|
|
10008
10308
|
}
|
|
10009
10309
|
function healPrepare(repoRoot, ticket, runId, scenarioName) {
|
|
10010
10310
|
const paths = resolveArtifactPaths(repoRoot, ticket);
|
|
10011
|
-
const classifierPath =
|
|
10012
|
-
const classifier = JSON.parse(
|
|
10311
|
+
const classifierPath = join18(paths.ticketDir, "classifier-input.json");
|
|
10312
|
+
const classifier = JSON.parse(readFileSync18(classifierPath, "utf8"));
|
|
10013
10313
|
const cls = classifier.scenarios.find((s) => s.name === scenarioName);
|
|
10014
10314
|
if (!cls)
|
|
10015
10315
|
throw new Error(`scenario not found in classifier-input: "${scenarioName}"`);
|
|
10016
|
-
const runDir =
|
|
10017
|
-
const normalized = JSON.parse(
|
|
10316
|
+
const runDir = join18(paths.runsDir, runId);
|
|
10317
|
+
const normalized = JSON.parse(readFileSync18(join18(runDir, "normalized.json"), "utf8"));
|
|
10018
10318
|
const normSc = normalized.scenarios.find((s) => s.name === scenarioName);
|
|
10019
10319
|
if (!normSc?.failure)
|
|
10020
10320
|
throw new Error(`no failure recorded for scenario "${scenarioName}"`);
|
|
@@ -10025,9 +10325,9 @@ function healPrepare(repoRoot, ticket, runId, scenarioName) {
|
|
|
10025
10325
|
const raw = m[1].trim();
|
|
10026
10326
|
const kind = classifyKind(raw);
|
|
10027
10327
|
const pomLoc = findPomLine(paths.ticketDir, raw);
|
|
10028
|
-
const featureText =
|
|
10328
|
+
const featureText = readFileSync18(paths.featurePath, "utf8");
|
|
10029
10329
|
const gherkinStep = findGherkinStep(featureText, raw);
|
|
10030
|
-
const domSnapshotAtFailure = extractDomSnapshot(
|
|
10330
|
+
const domSnapshotAtFailure = extractDomSnapshot(join18(runDir, "trace.zip"));
|
|
10031
10331
|
return {
|
|
10032
10332
|
ticket,
|
|
10033
10333
|
runId,
|
|
@@ -10047,8 +10347,8 @@ async function healPrepareCmd(argv) {
|
|
|
10047
10347
|
try {
|
|
10048
10348
|
const result = healPrepare(process.cwd(), ticket, runId, scenarioName);
|
|
10049
10349
|
const paths = resolveArtifactPaths(process.cwd(), ticket);
|
|
10050
|
-
const outPath =
|
|
10051
|
-
|
|
10350
|
+
const outPath = join18(paths.runsDir, runId, "heal-input.json");
|
|
10351
|
+
writeFileSync11(outPath, JSON.stringify(result, null, 2));
|
|
10052
10352
|
console.log(`[xera:heal-prepare] wrote ${outPath}`);
|
|
10053
10353
|
return 0;
|
|
10054
10354
|
} catch (err2) {
|
|
@@ -10058,8 +10358,8 @@ async function healPrepareCmd(argv) {
|
|
|
10058
10358
|
}
|
|
10059
10359
|
|
|
10060
10360
|
// src/bin-internal/impact-prepare.ts
|
|
10061
|
-
import { mkdirSync as
|
|
10062
|
-
import { join as
|
|
10361
|
+
import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync12 } from "fs";
|
|
10362
|
+
import { join as join19 } from "path";
|
|
10063
10363
|
|
|
10064
10364
|
// src/graph/impact.ts
|
|
10065
10365
|
var PRIORITY_WEIGHT = { p0: 3, p1: 2, p2: 1 };
|
|
@@ -10264,7 +10564,7 @@ function renderImpactMarkdown(report) {
|
|
|
10264
10564
|
|
|
10265
10565
|
// src/bin-internal/impact-prepare.ts
|
|
10266
10566
|
init_store();
|
|
10267
|
-
function
|
|
10567
|
+
function parseDepth2(s) {
|
|
10268
10568
|
const n = s ? Number.parseInt(s, 10) : 2;
|
|
10269
10569
|
if (n === 1 || n === 3)
|
|
10270
10570
|
return n;
|
|
@@ -10286,7 +10586,7 @@ async function impactPrepareCmd(argv) {
|
|
|
10286
10586
|
let quiet = false;
|
|
10287
10587
|
for (let i2 = 1;i2 < argv.length; i2++) {
|
|
10288
10588
|
if (argv[i2] === "--depth")
|
|
10289
|
-
depth =
|
|
10589
|
+
depth = parseDepth2(argv[++i2]);
|
|
10290
10590
|
else if (argv[i2] === "--min-priority")
|
|
10291
10591
|
minPriority = parseMinPriority(argv[++i2]);
|
|
10292
10592
|
else if (argv[i2] === "--quiet")
|
|
@@ -10309,11 +10609,11 @@ async function impactPrepareCmd(argv) {
|
|
|
10309
10609
|
scenarios,
|
|
10310
10610
|
generatedAt: new Date().toISOString()
|
|
10311
10611
|
};
|
|
10312
|
-
const impactDir =
|
|
10313
|
-
|
|
10314
|
-
|
|
10612
|
+
const impactDir = join19(repoRoot, ".xera/impact");
|
|
10613
|
+
mkdirSync12(impactDir, { recursive: true });
|
|
10614
|
+
writeFileSync12(join19(impactDir, `${ticket}.json`), JSON.stringify(report, null, 2));
|
|
10315
10615
|
if (!quiet) {
|
|
10316
|
-
|
|
10616
|
+
writeFileSync12(join19(impactDir, `${ticket}.md`), renderImpactMarkdown(report));
|
|
10317
10617
|
}
|
|
10318
10618
|
return 0;
|
|
10319
10619
|
}
|
|
@@ -10339,7 +10639,7 @@ async function lintCmd(argv) {
|
|
|
10339
10639
|
|
|
10340
10640
|
// src/bin-internal/normalize.ts
|
|
10341
10641
|
import { existsSync as existsSync21, readdirSync as readdirSync7 } from "fs";
|
|
10342
|
-
import { join as
|
|
10642
|
+
import { join as join20 } from "path";
|
|
10343
10643
|
import { normalizeRun } from "@xera-ai/web";
|
|
10344
10644
|
async function normalizeCmd(argv) {
|
|
10345
10645
|
const ticket = argv[0];
|
|
@@ -10354,7 +10654,7 @@ async function normalizeCmd(argv) {
|
|
|
10354
10654
|
console.error("[xera:normalize] no run found");
|
|
10355
10655
|
return 1;
|
|
10356
10656
|
}
|
|
10357
|
-
const runDir =
|
|
10657
|
+
const runDir = join20(paths.runsDir, runId);
|
|
10358
10658
|
if (!existsSync21(runDir)) {
|
|
10359
10659
|
console.error(`[xera:normalize] runs/${runId} missing`);
|
|
10360
10660
|
return 1;
|
|
@@ -10365,12 +10665,12 @@ async function normalizeCmd(argv) {
|
|
|
10365
10665
|
}
|
|
10366
10666
|
|
|
10367
10667
|
// src/bin-internal/post.ts
|
|
10368
|
-
import { existsSync as existsSync23, readFileSync as
|
|
10369
|
-
import { join as
|
|
10668
|
+
import { existsSync as existsSync23, readFileSync as readFileSync20 } from "fs";
|
|
10669
|
+
import { join as join21 } from "path";
|
|
10370
10670
|
|
|
10371
10671
|
// src/artifact/status.ts
|
|
10372
|
-
import { existsSync as existsSync22, mkdirSync as
|
|
10373
|
-
import { dirname as
|
|
10672
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync13, readFileSync as readFileSync19, writeFileSync as writeFileSync13 } from "fs";
|
|
10673
|
+
import { dirname as dirname8 } from "path";
|
|
10374
10674
|
import { z as z7 } from "zod";
|
|
10375
10675
|
var ClassificationEnum = z7.enum([
|
|
10376
10676
|
"PASS",
|
|
@@ -10406,11 +10706,11 @@ var HISTORY_CAP = 20;
|
|
|
10406
10706
|
function readStatus(path) {
|
|
10407
10707
|
if (!existsSync22(path))
|
|
10408
10708
|
return null;
|
|
10409
|
-
return StatusJsonSchema.parse(JSON.parse(
|
|
10709
|
+
return StatusJsonSchema.parse(JSON.parse(readFileSync19(path, "utf8")));
|
|
10410
10710
|
}
|
|
10411
10711
|
function writeStatus(path, status) {
|
|
10412
|
-
|
|
10413
|
-
|
|
10712
|
+
mkdirSync13(dirname8(path), { recursive: true });
|
|
10713
|
+
writeFileSync13(path, JSON.stringify(status, null, 2));
|
|
10414
10714
|
}
|
|
10415
10715
|
function appendHistory(path, entry) {
|
|
10416
10716
|
const s = readStatus(path);
|
|
@@ -10436,12 +10736,12 @@ async function postCmd(argv) {
|
|
|
10436
10736
|
return 0;
|
|
10437
10737
|
}
|
|
10438
10738
|
const paths = resolveArtifactPaths(cwd, ticket);
|
|
10439
|
-
const draftPath =
|
|
10739
|
+
const draftPath = join21(paths.ticketDir, "jira-comment.draft.md");
|
|
10440
10740
|
if (!existsSync23(draftPath)) {
|
|
10441
10741
|
console.error(`[xera:post] no draft at ${draftPath}; run \`xera-internal report\` first.`);
|
|
10442
10742
|
return 1;
|
|
10443
10743
|
}
|
|
10444
|
-
const body =
|
|
10744
|
+
const body = readFileSync20(draftPath, "utf8");
|
|
10445
10745
|
const client = await createJiraClient({
|
|
10446
10746
|
baseUrl: config.jira.baseUrl,
|
|
10447
10747
|
preferMcp: true,
|
|
@@ -10469,8 +10769,8 @@ async function promoteCmd(argv) {
|
|
|
10469
10769
|
}
|
|
10470
10770
|
|
|
10471
10771
|
// src/bin-internal/report.ts
|
|
10472
|
-
import { existsSync as existsSync25, readFileSync as
|
|
10473
|
-
import { join as
|
|
10772
|
+
import { existsSync as existsSync25, readFileSync as readFileSync21, writeFileSync as writeFileSync14 } from "fs";
|
|
10773
|
+
import { join as join22 } from "path";
|
|
10474
10774
|
|
|
10475
10775
|
// src/classifier/aggregate.ts
|
|
10476
10776
|
var CLASS_PRIORITY = [
|
|
@@ -10642,10 +10942,10 @@ async function reportCmd(argv) {
|
|
|
10642
10942
|
return 1;
|
|
10643
10943
|
}
|
|
10644
10944
|
const paths = resolveArtifactPaths(process.cwd(), ticket);
|
|
10645
|
-
const input = JSON.parse(
|
|
10945
|
+
const input = JSON.parse(readFileSync21(inputArg.slice("--input=".length), "utf8"));
|
|
10646
10946
|
const aggregated = aggregateScenarios(input.scenarios);
|
|
10647
|
-
const decisionsPath =
|
|
10648
|
-
const decisions = existsSync25(decisionsPath) ? JSON.parse(
|
|
10947
|
+
const decisionsPath = join22(paths.ticketDir, "runs", input.runId, "outdated-decisions.json");
|
|
10948
|
+
const decisions = existsSync25(decisionsPath) ? JSON.parse(readFileSync21(decisionsPath, "utf8")) : {};
|
|
10649
10949
|
const graph = deriveSnapshot(loadAllEvents(process.cwd()));
|
|
10650
10950
|
const normalizeScenarioName = (name) => name.trim().toLowerCase().replace(/\s+/g, " ");
|
|
10651
10951
|
const scenarioIdByName = {};
|
|
@@ -10693,8 +10993,8 @@ async function reportCmd(argv) {
|
|
|
10693
10993
|
xeraVersion: "0.1.0",
|
|
10694
10994
|
promptsVersion: "1.0.0"
|
|
10695
10995
|
});
|
|
10696
|
-
const draftPath =
|
|
10697
|
-
|
|
10996
|
+
const draftPath = join22(paths.ticketDir, "jira-comment.draft.md");
|
|
10997
|
+
writeFileSync14(draftPath, md);
|
|
10698
10998
|
console.log(`[xera:report] wrote status.json and ${draftPath}`);
|
|
10699
10999
|
return 0;
|
|
10700
11000
|
}
|
|
@@ -10761,7 +11061,7 @@ async function unlockCmd(argv) {
|
|
|
10761
11061
|
}
|
|
10762
11062
|
|
|
10763
11063
|
// src/bin-internal/validate-feature.ts
|
|
10764
|
-
import { existsSync as existsSync26, readFileSync as
|
|
11064
|
+
import { existsSync as existsSync26, readFileSync as readFileSync22 } from "fs";
|
|
10765
11065
|
import { validateGherkin as validateGherkin2 } from "@xera-ai/web";
|
|
10766
11066
|
async function validateFeatureCmd(argv) {
|
|
10767
11067
|
const ticket = argv[0];
|
|
@@ -10774,7 +11074,7 @@ async function validateFeatureCmd(argv) {
|
|
|
10774
11074
|
console.error(`[xera:validate-feature] missing ${paths.featurePath}`);
|
|
10775
11075
|
return 1;
|
|
10776
11076
|
}
|
|
10777
|
-
const r = validateGherkin2(
|
|
11077
|
+
const r = validateGherkin2(readFileSync22(paths.featurePath, "utf8"));
|
|
10778
11078
|
if (r.ok) {
|
|
10779
11079
|
console.log("[xera:validate-feature] ok");
|
|
10780
11080
|
return 0;
|
|
@@ -10794,6 +11094,7 @@ var COMMANDS = {
|
|
|
10794
11094
|
fetch: fetchCmd,
|
|
10795
11095
|
"graph-backfill": graphBackfillCmd,
|
|
10796
11096
|
"graph-enrich": graphEnrichCmd,
|
|
11097
|
+
"graph-render": graphRenderCmd,
|
|
10797
11098
|
"graph-query": graphQueryCmd,
|
|
10798
11099
|
"graph-record": graphRecordCmd,
|
|
10799
11100
|
"graph-snapshot": graphSnapshotCmd,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-render.d.ts","sourceRoot":"","sources":["../../src/bin-internal/graph-render.ts"],"names":[],"mappings":"AAkBA,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAmDpE"}
|
|
@@ -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":"AAoDA,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAczD"}
|
package/dist/graph/index.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ export { enrichTicket } from './enrich';
|
|
|
7
7
|
export type { ImpactEdge, ImpactOpts, ImpactReport, ImpactScenario, } from './impact';
|
|
8
8
|
export { renderImpactMarkdown, riskScore, walkImpact, } from './impact';
|
|
9
9
|
export { currentYyyyMm, graphPaths } from './paths';
|
|
10
|
+
export type { GraphStats, RenderHtmlInput, RenderOpts, VisEdge, VisNode } from './render';
|
|
11
|
+
export { renderHtml, transformForVisNetwork } from './render';
|
|
10
12
|
export { EventSchema, safeParseEvent } from './schema';
|
|
11
13
|
export { buildSimilarityPrompt } from './similarity';
|
|
12
14
|
export { appendEvents, computeEventsHash, deriveSnapshot, isSnapshotStale, loadAllEvents, loadSnapshot, writeSnapshot, } from './store';
|
|
@@ -1 +1 @@
|
|
|
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,YAAY,EACV,UAAU,EACV,UAAU,EACV,YAAY,EACZ,cAAc,GACf,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,oBAAoB,EACpB,SAAS,EACT,UAAU,GACX,MAAM,UAAU,CAAC;AAClB,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"}
|
|
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,YAAY,EACV,UAAU,EACV,UAAU,EACV,YAAY,EACZ,cAAc,GACf,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,oBAAoB,EACpB,SAAS,EACT,UAAU,GACX,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACpD,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1F,OAAO,EAAE,UAAU,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAC9D,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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Snapshot } from './types';
|
|
2
|
+
export interface VisNode {
|
|
3
|
+
id: string;
|
|
4
|
+
label: string;
|
|
5
|
+
group: 'Ticket' | 'Scenario' | 'POM' | 'SUTArea' | 'Failure';
|
|
6
|
+
color?: string;
|
|
7
|
+
shape?: string;
|
|
8
|
+
size?: number;
|
|
9
|
+
title?: string;
|
|
10
|
+
borderWidth?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface VisEdge {
|
|
13
|
+
id?: string;
|
|
14
|
+
from: string;
|
|
15
|
+
to: string;
|
|
16
|
+
label?: string;
|
|
17
|
+
color?: string;
|
|
18
|
+
dashes?: boolean;
|
|
19
|
+
width?: number;
|
|
20
|
+
arrows?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface GraphStats {
|
|
23
|
+
tickets: number;
|
|
24
|
+
scenarios: number;
|
|
25
|
+
poms: number;
|
|
26
|
+
areas: number;
|
|
27
|
+
failures: number;
|
|
28
|
+
edges: number;
|
|
29
|
+
}
|
|
30
|
+
export interface RenderOpts {
|
|
31
|
+
since?: string;
|
|
32
|
+
ticketId?: string;
|
|
33
|
+
depth?: 1 | 2 | 3;
|
|
34
|
+
performanceMode?: 'full' | 'ticket-only' | 'text-fallback';
|
|
35
|
+
}
|
|
36
|
+
export declare function transformForVisNetwork(snap: Snapshot, opts: RenderOpts): {
|
|
37
|
+
nodes: VisNode[];
|
|
38
|
+
edges: VisEdge[];
|
|
39
|
+
stats: GraphStats;
|
|
40
|
+
};
|
|
41
|
+
export interface RenderHtmlInput {
|
|
42
|
+
data: {
|
|
43
|
+
nodes: VisNode[];
|
|
44
|
+
edges: VisEdge[];
|
|
45
|
+
};
|
|
46
|
+
stats: GraphStats;
|
|
47
|
+
generatedAt: string;
|
|
48
|
+
}
|
|
49
|
+
export declare function renderHtml(input: RenderHtmlInput): string;
|
|
50
|
+
//# sourceMappingURL=render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/graph/render.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAc,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEpD,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,QAAQ,GAAG,UAAU,GAAG,KAAK,GAAG,SAAS,GAAG,SAAS,CAAC;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,GAAG,aAAa,GAAG,eAAe,CAAC;CAC5D;AAmKD,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,UAAU,GACf;IACD,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,KAAK,EAAE,UAAU,CAAC;CACnB,CA4EA;AAcD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE;QAAE,KAAK,EAAE,OAAO,EAAE,CAAC;QAAC,KAAK,EAAE,OAAO,EAAE,CAAA;KAAE,CAAC;IAC7C,KAAK,EAAE,UAAU,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CAgBzD"}
|