@xera-ai/core 0.9.8 → 0.11.0
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 +1415 -534
- package/dist/bin/templates/coverage-panel.html.fragment +20 -0
- package/dist/bin/templates/graph.css +82 -0
- package/dist/bin/templates/graph.html.template +17 -9
- package/dist/bin/templates/graph.js +185 -0
- package/dist/src/index.js +6 -0
- package/package.json +3 -3
- package/src/bin-internal/ac-coverage-backfill-finalize.ts +90 -0
- package/src/bin-internal/ac-coverage-backfill-prepare.ts +72 -0
- package/src/bin-internal/coverage-prepare.ts +123 -0
- package/src/bin-internal/fill-gap-finalize.ts +115 -0
- package/src/bin-internal/fill-gap-prepare.ts +150 -0
- package/src/bin-internal/graph-render.ts +32 -4
- package/src/bin-internal/index.ts +10 -0
- package/src/bin-internal/verify-prompts.ts +2 -0
- package/src/config/schema.ts +9 -0
- package/src/coverage/index.ts +29 -0
- package/src/coverage/report.ts +206 -0
- package/src/coverage/risk.ts +69 -0
- package/src/coverage/status.ts +76 -0
- package/src/coverage/types.ts +11 -0
- package/src/coverage/why.ts +122 -0
- package/src/graph/render.ts +16 -2
- package/src/graph/schema.ts +54 -1
- package/src/graph/store.ts +96 -6
- package/src/graph/templates/coverage-panel.html.fragment +20 -0
- package/src/graph/templates/graph.css +82 -0
- package/src/graph/templates/graph.html.template +17 -9
- package/src/graph/templates/graph.js +185 -0
- package/src/graph/types.ts +56 -1
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
<div class="logo"></div>
|
|
12
12
|
<span class="title">xera graph</span>
|
|
13
13
|
</div>
|
|
14
|
+
<nav class="toplevel-tabs">
|
|
15
|
+
<button data-tab="knowledge" class="active">Knowledge</button>
|
|
16
|
+
{{COVERAGE_TAB_BUTTON}}
|
|
17
|
+
</nav>
|
|
14
18
|
<div id="stats-bar" data-stats="{{STATS}}"></div>
|
|
15
19
|
<div class="topbar-controls">
|
|
16
20
|
<input type="text" id="search" placeholder="search nodes…" autocomplete="off" />
|
|
@@ -23,21 +27,25 @@
|
|
|
23
27
|
</div>
|
|
24
28
|
</header>
|
|
25
29
|
<div id="progress-wrap"><div id="progress-bar"></div></div>
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
<
|
|
29
|
-
<div
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
<section data-tab-panel="knowledge" class="active">
|
|
31
|
+
<main id="canvas"></main>
|
|
32
|
+
<aside id="sidepanel" class="hidden">
|
|
33
|
+
<div class="sp-header">
|
|
34
|
+
<div id="sp-group" class="sp-group-badge"></div>
|
|
35
|
+
<p id="sp-title" class="sp-title"></p>
|
|
36
|
+
</div>
|
|
37
|
+
<div id="sp-desc" class="sp-desc"></div>
|
|
38
|
+
<div id="sp-actions" class="sp-actions"></div>
|
|
39
|
+
</aside>
|
|
40
|
+
</section>
|
|
41
|
+
{{COVERAGE_TAB_PANEL}}
|
|
35
42
|
<footer id="footer">
|
|
36
43
|
generated {{GENERATED_AT}}
|
|
37
44
|
<span>· scroll to zoom · drag to pan · click to inspect</span>
|
|
38
45
|
</footer>
|
|
39
46
|
<script>{{VIS_NETWORK_JS}}</script>
|
|
40
47
|
<script>window.__GRAPH__ = {{GRAPH_DATA}};</script>
|
|
48
|
+
<script>window.__COVERAGE__ = {{COVERAGE_DATA}};</script>
|
|
41
49
|
<script>{{INTERACTION_JS}}</script>
|
|
42
50
|
</body>
|
|
43
51
|
</html>
|
|
@@ -316,3 +316,188 @@
|
|
|
316
316
|
};
|
|
317
317
|
});
|
|
318
318
|
})();
|
|
319
|
+
|
|
320
|
+
// v0.8.1 — top-level tab switching
|
|
321
|
+
(function setupTabs() {
|
|
322
|
+
const tabButtons = document.querySelectorAll('.toplevel-tabs button');
|
|
323
|
+
if (!tabButtons.length) return;
|
|
324
|
+
tabButtons.forEach((btn) => {
|
|
325
|
+
btn.addEventListener('click', () => {
|
|
326
|
+
tabButtons.forEach((b) => {
|
|
327
|
+
b.classList.remove('active');
|
|
328
|
+
});
|
|
329
|
+
btn.classList.add('active');
|
|
330
|
+
const tab = btn.getAttribute('data-tab');
|
|
331
|
+
document.querySelectorAll('[data-tab-panel]').forEach((panel) => {
|
|
332
|
+
if (panel.getAttribute('data-tab-panel') === tab) {
|
|
333
|
+
panel.classList.add('active');
|
|
334
|
+
panel.removeAttribute('hidden');
|
|
335
|
+
} else {
|
|
336
|
+
panel.classList.remove('active');
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
if (tab === 'coverage' && window.__COVERAGE__) {
|
|
340
|
+
renderCoverageOnce();
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
})();
|
|
345
|
+
|
|
346
|
+
// v0.8.1 — coverage subtab switching
|
|
347
|
+
(function setupSubtabs() {
|
|
348
|
+
const subButtons = document.querySelectorAll('.subtabs button');
|
|
349
|
+
subButtons.forEach((btn) => {
|
|
350
|
+
btn.addEventListener('click', () => {
|
|
351
|
+
subButtons.forEach((b) => {
|
|
352
|
+
b.classList.remove('active');
|
|
353
|
+
});
|
|
354
|
+
btn.classList.add('active');
|
|
355
|
+
const sub = btn.getAttribute('data-subtab');
|
|
356
|
+
document.querySelectorAll('[data-subpanel]').forEach((panel) => {
|
|
357
|
+
if (panel.getAttribute('data-subpanel') === sub) {
|
|
358
|
+
panel.removeAttribute('hidden');
|
|
359
|
+
panel.classList.add('active');
|
|
360
|
+
} else {
|
|
361
|
+
panel.setAttribute('hidden', '');
|
|
362
|
+
panel.classList.remove('active');
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
})();
|
|
368
|
+
|
|
369
|
+
let _coverageRendered = false;
|
|
370
|
+
function renderCoverageOnce() {
|
|
371
|
+
if (_coverageRendered) return;
|
|
372
|
+
_coverageRendered = true;
|
|
373
|
+
renderCoverageList();
|
|
374
|
+
renderCoverageTrend();
|
|
375
|
+
renderCoverageMap();
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Task 27 — coverage map: area color overlay
|
|
379
|
+
function renderCoverageMap() {
|
|
380
|
+
const cov = window.__COVERAGE__;
|
|
381
|
+
if (!cov || !window.__GRAPH__) return;
|
|
382
|
+
const canvas = document.getElementById('coverage-map-canvas');
|
|
383
|
+
if (!canvas) return;
|
|
384
|
+
|
|
385
|
+
const STATUS_COLOR = {
|
|
386
|
+
UNCOVERED: { background: '#fca5a5', border: '#dc2626' },
|
|
387
|
+
STALE: { background: '#fcd34d', border: '#d97706' },
|
|
388
|
+
COVERED: { background: '#86efac', border: '#15803d' },
|
|
389
|
+
};
|
|
390
|
+
const NEUTRAL = { background: '#e5e7eb', border: '#9ca3af' };
|
|
391
|
+
|
|
392
|
+
const areaStatusById = {};
|
|
393
|
+
for (const a of cov.report.areas) {
|
|
394
|
+
areaStatusById[a.id] = a.status;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const mappedNodes = window.__GRAPH__.nodes.map((n) => {
|
|
398
|
+
if (n.group === 'SUTArea' && areaStatusById[n.id]) {
|
|
399
|
+
return Object.assign({}, n, { color: STATUS_COLOR[areaStatusById[n.id]] });
|
|
400
|
+
}
|
|
401
|
+
if (n.group !== 'SUTArea') return Object.assign({}, n, { color: NEUTRAL });
|
|
402
|
+
return n;
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
new vis.Network(
|
|
406
|
+
canvas,
|
|
407
|
+
{ nodes: new vis.DataSet(mappedNodes), edges: new vis.DataSet(window.__GRAPH__.edges) },
|
|
408
|
+
{
|
|
409
|
+
physics: { enabled: true, stabilization: { iterations: 100 } },
|
|
410
|
+
nodes: { shape: 'dot', font: { size: 11 } },
|
|
411
|
+
},
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Task 28 — coverage list: sortable area + AC gap tables
|
|
416
|
+
function renderCoverageList() {
|
|
417
|
+
const cov = window.__COVERAGE__;
|
|
418
|
+
if (!cov) return;
|
|
419
|
+
const listBody = document.querySelector('#coverage-list-table tbody');
|
|
420
|
+
if (listBody) {
|
|
421
|
+
listBody.innerHTML = '';
|
|
422
|
+
for (const a of cov.report.areas) {
|
|
423
|
+
const tr = document.createElement('tr');
|
|
424
|
+
tr.classList.add(`status-${a.status.toLowerCase()}`);
|
|
425
|
+
const cells = [
|
|
426
|
+
a.status,
|
|
427
|
+
a.id,
|
|
428
|
+
String(a.risk),
|
|
429
|
+
String(a.breakdown.recentTickets),
|
|
430
|
+
String(a.breakdown.recentBugs),
|
|
431
|
+
];
|
|
432
|
+
for (const c of cells) {
|
|
433
|
+
const td = document.createElement('td');
|
|
434
|
+
td.textContent = c;
|
|
435
|
+
tr.appendChild(td);
|
|
436
|
+
}
|
|
437
|
+
listBody.appendChild(tr);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const acBody = document.querySelector('#coverage-ac-table tbody');
|
|
442
|
+
if (acBody) {
|
|
443
|
+
acBody.innerHTML = '';
|
|
444
|
+
for (const t of cov.report.tickets) {
|
|
445
|
+
const tr = document.createElement('tr');
|
|
446
|
+
const cells = [
|
|
447
|
+
t.id,
|
|
448
|
+
`${t.satisfiedCount}/${t.acCount}`,
|
|
449
|
+
String(t.gapScore),
|
|
450
|
+
t.unsatisfiedAcs.map((ac) => `AC-${ac.index}`).join(', '),
|
|
451
|
+
];
|
|
452
|
+
for (const c of cells) {
|
|
453
|
+
const td = document.createElement('td');
|
|
454
|
+
td.textContent = c;
|
|
455
|
+
tr.appendChild(td);
|
|
456
|
+
}
|
|
457
|
+
acBody.appendChild(tr);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Task 29 — coverage trend: inline SVG line chart
|
|
463
|
+
function renderCoverageTrend() {
|
|
464
|
+
const cov = window.__COVERAGE__;
|
|
465
|
+
if (!cov) return;
|
|
466
|
+
const container = document.getElementById('coverage-trend-svg');
|
|
467
|
+
if (!container) return;
|
|
468
|
+
|
|
469
|
+
// Dedup by day (latest snapshot per day wins), sort asc.
|
|
470
|
+
const byDay = {};
|
|
471
|
+
for (const s of cov.snapshots) {
|
|
472
|
+
const day = s.ts.slice(0, 10);
|
|
473
|
+
byDay[day] = s;
|
|
474
|
+
}
|
|
475
|
+
const days = Object.keys(byDay).sort();
|
|
476
|
+
if (days.length === 0) {
|
|
477
|
+
container.innerHTML =
|
|
478
|
+
'<p class="subpanel-hint">No snapshots yet — run /xera-coverage on multiple days to build a trend.</p>';
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const points = days.map((d) => {
|
|
483
|
+
const snap = byDay[d];
|
|
484
|
+
const n = snap.areas.filter((a) => a.status === 'UNCOVERED' || a.status === 'STALE').length;
|
|
485
|
+
return { day: d, value: n };
|
|
486
|
+
});
|
|
487
|
+
const W = 800;
|
|
488
|
+
const H = 200;
|
|
489
|
+
const PAD = 30;
|
|
490
|
+
const maxValue = Math.max(...points.map((p) => p.value), 1);
|
|
491
|
+
const stepX = points.length > 1 ? (W - 2 * PAD) / (points.length - 1) : 0;
|
|
492
|
+
const path = points
|
|
493
|
+
.map((p, idx) => {
|
|
494
|
+
const x = PAD + idx * stepX;
|
|
495
|
+
const y = H - PAD - (p.value / maxValue) * (H - 2 * PAD);
|
|
496
|
+
return `${idx === 0 ? 'M' : 'L'}${x},${y}`;
|
|
497
|
+
})
|
|
498
|
+
.join(' ');
|
|
499
|
+
|
|
500
|
+
const labelFirst = points[0].day;
|
|
501
|
+
const labelLast = points[points.length - 1].day;
|
|
502
|
+
container.innerHTML = `<svg viewBox="0 0 ${W} ${H}" xmlns="http://www.w3.org/2000/svg"><path d="${path}" fill="none" stroke="#dc2626" stroke-width="2"/><text x="${PAD}" y="${H - 8}" font-size="11" fill="#6b7280">${labelFirst}</text><text x="${W - PAD - 60}" y="${H - 8}" font-size="11" fill="#6b7280">${labelLast}</text><text x="${PAD - 22}" y="${PAD - 4}" font-size="11" fill="#6b7280">${maxValue}</text></svg>`;
|
|
503
|
+
}
|
package/src/graph/types.ts
CHANGED
|
@@ -4,7 +4,15 @@ export const SCHEMA_VERSION = 1 as const;
|
|
|
4
4
|
|
|
5
5
|
export type Priority = 'p0' | 'p1' | 'p2';
|
|
6
6
|
export type ScenarioStatus = 'pass' | 'fail';
|
|
7
|
-
export type EdgeKind =
|
|
7
|
+
export type EdgeKind =
|
|
8
|
+
| 'tests'
|
|
9
|
+
| 'uses'
|
|
10
|
+
| 'covers'
|
|
11
|
+
| 'modifies'
|
|
12
|
+
| 'jira-linked'
|
|
13
|
+
| 'similar'
|
|
14
|
+
| 'ran'
|
|
15
|
+
| 'satisfies';
|
|
8
16
|
|
|
9
17
|
export type Classification =
|
|
10
18
|
| 'REAL_BUG'
|
|
@@ -43,6 +51,7 @@ export interface ScenarioGeneratedPayload {
|
|
|
43
51
|
priority: Priority;
|
|
44
52
|
featureHash: string;
|
|
45
53
|
generatedAt: string;
|
|
54
|
+
satisfiesAcs?: number[]; // NEW v0.8: AC indices (0-based) this scenario asserts
|
|
46
55
|
}
|
|
47
56
|
|
|
48
57
|
export interface PomGeneratedPayload {
|
|
@@ -93,6 +102,37 @@ export interface EdgeDiscoveredPayload {
|
|
|
93
102
|
source: string;
|
|
94
103
|
}
|
|
95
104
|
|
|
105
|
+
export interface CoverageSnapshotPayload {
|
|
106
|
+
ts: string; // ISO8601
|
|
107
|
+
windowDays: number;
|
|
108
|
+
areas: Array<{
|
|
109
|
+
id: string;
|
|
110
|
+
status: 'UNCOVERED' | 'STALE' | 'COVERED';
|
|
111
|
+
risk: number;
|
|
112
|
+
breakdown: {
|
|
113
|
+
recentTickets: number;
|
|
114
|
+
recentBugs: number;
|
|
115
|
+
criticalBoost: 1 | 2;
|
|
116
|
+
};
|
|
117
|
+
}>;
|
|
118
|
+
tickets: Array<{
|
|
119
|
+
id: string;
|
|
120
|
+
acCount: number;
|
|
121
|
+
satisfiedCount: number;
|
|
122
|
+
gapScore: number;
|
|
123
|
+
}>;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface AcCoverageBackfilledPayload {
|
|
127
|
+
ts: string;
|
|
128
|
+
ticketId: string;
|
|
129
|
+
mappings: Array<{
|
|
130
|
+
scenarioId: string;
|
|
131
|
+
satisfiesAcs: number[];
|
|
132
|
+
confidence: number;
|
|
133
|
+
}>;
|
|
134
|
+
}
|
|
135
|
+
|
|
96
136
|
export type EventPayloadMap = {
|
|
97
137
|
'ticket.fetched': TicketFetchedPayload;
|
|
98
138
|
'ticket.enriched': TicketEnrichedPayload;
|
|
@@ -103,6 +143,8 @@ export type EventPayloadMap = {
|
|
|
103
143
|
'run.classified': RunClassifiedPayload;
|
|
104
144
|
'classification.disputed': ClassificationDisputedPayload;
|
|
105
145
|
'edge.discovered': EdgeDiscoveredPayload;
|
|
146
|
+
'coverage.snapshot': CoverageSnapshotPayload; // NEW
|
|
147
|
+
'ac-coverage.backfilled': AcCoverageBackfilledPayload; // NEW
|
|
106
148
|
};
|
|
107
149
|
|
|
108
150
|
export type EventType = keyof EventPayloadMap;
|
|
@@ -151,6 +193,13 @@ export interface AreaNode {
|
|
|
151
193
|
id: string;
|
|
152
194
|
}
|
|
153
195
|
|
|
196
|
+
export interface ACNode {
|
|
197
|
+
id: string; // `${ticketId}#ac-${index}` (0-based)
|
|
198
|
+
ticketId: string;
|
|
199
|
+
index: number;
|
|
200
|
+
text: string;
|
|
201
|
+
}
|
|
202
|
+
|
|
154
203
|
export interface FailureNode {
|
|
155
204
|
id: string;
|
|
156
205
|
scenarioId: string;
|
|
@@ -180,4 +229,10 @@ export interface Snapshot {
|
|
|
180
229
|
areas: Record<string, AreaNode>;
|
|
181
230
|
edges: EdgeRecord[];
|
|
182
231
|
latest_failures: Record<string, FailureNode>;
|
|
232
|
+
acNodes: Record<string, ACNode>; // NEW v0.8
|
|
233
|
+
classifications: Array<{
|
|
234
|
+
scenarioId: string;
|
|
235
|
+
classification: Classification;
|
|
236
|
+
ts: string;
|
|
237
|
+
}>; // NEW v0.8
|
|
183
238
|
}
|