@xera-ai/core 0.11.1 → 0.11.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 +12 -9
- package/dist/bin/templates/coverage-panel.html.fragment +10 -1
- package/dist/bin/templates/graph.css +805 -32
- package/dist/bin/templates/graph.js +510 -90
- package/package.json +3 -3
- package/src/graph/enrich.ts +25 -10
- package/src/graph/render.ts +1 -1
- package/src/graph/templates/coverage-panel.html.fragment +10 -1
- package/src/graph/templates/graph.css +805 -32
- package/src/graph/templates/graph.js +510 -90
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xera-ai/core",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"zod": "4.4.3",
|
|
34
|
-
"@xera-ai/web": "^0.11.
|
|
35
|
-
"@xera-ai/http": "^0.11.
|
|
34
|
+
"@xera-ai/web": "^0.11.3",
|
|
35
|
+
"@xera-ai/http": "^0.11.3",
|
|
36
36
|
"@playwright/test": "1.60.0",
|
|
37
37
|
"dotenv": "^16.0.0",
|
|
38
38
|
"fflate": "0.8.3",
|
package/src/graph/enrich.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
1
|
+
import { existsSync, readFileSync, unlinkSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { appendEvents, deriveSnapshot, loadAllEvents } from './store';
|
|
@@ -50,6 +50,22 @@ export async function enrichTicket(
|
|
|
50
50
|
ticketId: string,
|
|
51
51
|
opts: EnrichOptions,
|
|
52
52
|
): Promise<EnrichResult> {
|
|
53
|
+
// Check the graph snapshot first so a missing ticket surfaces as the
|
|
54
|
+
// actionable "fetch it first" error instead of a confusing
|
|
55
|
+
// "enrichment-input.json not found" — the input file may live under
|
|
56
|
+
// .xera/<CANDIDATE>/, a directory that doesn't exist until the
|
|
57
|
+
// candidate has been fetched.
|
|
58
|
+
const snapshot = deriveSnapshot(loadAllEvents(repoRoot));
|
|
59
|
+
if (!snapshot.tickets[ticketId]) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`ticket ${ticketId} not in graph; fetch it first with \`/xera-fetch ${ticketId}\``,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (snapshot.tickets[ticketId]!.enrichedAt && !opts.force) {
|
|
66
|
+
return { ticketId, similarCount: 0, enrichedAt: snapshot.tickets[ticketId]!.enrichedAt! };
|
|
67
|
+
}
|
|
68
|
+
|
|
53
69
|
const inputPath = join(repoRoot, '.xera', ticketId, 'enrichment-input.json');
|
|
54
70
|
if (!existsSync(inputPath)) {
|
|
55
71
|
throw new Error(`enrichment-input.json not found at ${inputPath}`);
|
|
@@ -61,15 +77,6 @@ export async function enrichTicket(
|
|
|
61
77
|
throw new Error(`invalid enrichment-input.json: ${parsed.error.message}`);
|
|
62
78
|
}
|
|
63
79
|
|
|
64
|
-
const snapshot = deriveSnapshot(loadAllEvents(repoRoot));
|
|
65
|
-
if (!snapshot.tickets[ticketId]) {
|
|
66
|
-
throw new Error(`ticket ${ticketId} not in graph; run /xera-fetch first`);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (snapshot.tickets[ticketId]!.enrichedAt && !opts.force) {
|
|
70
|
-
return { ticketId, similarCount: 0, enrichedAt: snapshot.tickets[ticketId]!.enrichedAt! };
|
|
71
|
-
}
|
|
72
|
-
|
|
73
80
|
const validated = parsed.data.similar
|
|
74
81
|
.map((s) => ({ ...s, confidence: Math.max(0, Math.min(1, s.confidence)) }))
|
|
75
82
|
.filter((s) => s.confidence >= MIN_CONFIDENCE)
|
|
@@ -99,5 +106,13 @@ export async function enrichTicket(
|
|
|
99
106
|
|
|
100
107
|
appendEvents(repoRoot, events, { skill: 'graph-enrich', ticketId });
|
|
101
108
|
|
|
109
|
+
// Consumed — remove so a stale file can't accidentally re-drive enrich
|
|
110
|
+
// on a later invocation outside the /xera-report skill flow.
|
|
111
|
+
try {
|
|
112
|
+
unlinkSync(inputPath);
|
|
113
|
+
} catch {
|
|
114
|
+
// Best-effort cleanup; ignore if already gone.
|
|
115
|
+
}
|
|
116
|
+
|
|
102
117
|
return { ticketId, similarCount: validated.length, enrichedAt };
|
|
103
118
|
}
|
package/src/graph/render.ts
CHANGED
|
@@ -328,7 +328,7 @@ export function renderHtml(input: RenderHtmlInput): string {
|
|
|
328
328
|
return template
|
|
329
329
|
.replace('{{CSS}}', () => css)
|
|
330
330
|
.replace('{{STATS}}', () => statsHuman)
|
|
331
|
-
.replace(
|
|
331
|
+
.replace(/\{\{GENERATED_AT\}\}/g, () => input.generatedAt)
|
|
332
332
|
.replace('{{VIS_NETWORK_JS}}', () => visNetwork)
|
|
333
333
|
.replace('{{GRAPH_DATA}}', () => graphJson)
|
|
334
334
|
.replace('{{INTERACTION_JS}}', () => js)
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
<button data-subtab="trend">Trend</button>
|
|
6
6
|
</nav>
|
|
7
7
|
<div data-subpanel="map" class="active">
|
|
8
|
-
<p class="subpanel-hint">Area nodes are colored by status. Red = UNCOVERED, amber = STALE, green = COVERED. Other nodes neutral.</p>
|
|
9
8
|
<main id="coverage-map-canvas"></main>
|
|
10
9
|
</div>
|
|
11
10
|
<div data-subpanel="list" hidden>
|
|
@@ -17,4 +16,14 @@
|
|
|
17
16
|
<p class="subpanel-hint">UNCOVERED + STALE area count over time (one point per day, latest snapshot wins).</p>
|
|
18
17
|
<div id="coverage-trend-svg"></div>
|
|
19
18
|
</div>
|
|
19
|
+
<aside id="cov-drawer" class="cov-drawer hidden" aria-hidden="true">
|
|
20
|
+
<header class="cov-drawer-head">
|
|
21
|
+
<div class="cov-drawer-head-text">
|
|
22
|
+
<span id="cov-drawer-status" class="cov-drawer-status"></span>
|
|
23
|
+
<h3 id="cov-drawer-title"></h3>
|
|
24
|
+
</div>
|
|
25
|
+
<button id="cov-drawer-close" aria-label="Close" type="button">×</button>
|
|
26
|
+
</header>
|
|
27
|
+
<div class="cov-drawer-body" id="cov-drawer-body"></div>
|
|
28
|
+
</aside>
|
|
20
29
|
</section>
|