@hackerrank/astra-cli 0.1.10 → 0.1.12

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hackerrank/astra-cli",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "Minimal zero-dependency AI coding agent for the HackerRank AI Gateway.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/report.html CHANGED
@@ -85,8 +85,8 @@
85
85
  </section>
86
86
 
87
87
  <section>
88
- <h2>Test-case results <span class="sub">passed, failed, blocked, and errored criteria by model</span></h2>
89
- <div id="verifier-model-pills" class="model-pills"></div>
88
+ <h2>Test-case results <span class="sub">points earned by one selected model</span></h2>
89
+ <div class="toolbar"><label for="verifier-model-select">Model</label><select id="verifier-model-select"></select></div>
90
90
  <div class="panel">
91
91
  <table id="tbl-verifier-matrix"></table>
92
92
  </div>
@@ -312,31 +312,21 @@
312
312
  // ---------------------------------------------------------------- verifier test-case matrix
313
313
  (function () {
314
314
  var container = document.getElementById("tbl-verifier-matrix");
315
- var pills = document.getElementById("verifier-model-pills");
315
+ var selector = document.getElementById("verifier-model-select");
316
316
  if (!container) return;
317
317
  var matrix = DATA.verifier_matrix || { models: [], criteria: [] };
318
- var active = matrix.models.slice();
319
- function cellHtml(cell) {
320
- if (!cell) return '<div class="criterion-cell na">NA</div>';
321
- var rate = cell.total ? Math.round(cell.passed / cell.total * 100) : 0;
322
- var cls = rate === 100 ? "pass" : (cell.failed || cell.blocked || cell.error ? "fail" : "na");
323
- return '<div class="criterion-cell ' + cls + '"><span class="rate">' + rate + '%</span><span class="counts">' + cell.passed + ' pass · ' + (cell.failed + cell.blocked + cell.error) + ' fail</span></div>';
324
- }
318
+ var selected = matrix.models[0] || "";
325
319
  function draw() {
326
- var visible = matrix.models.filter(function (model) { return active.indexOf(model) !== -1; });
327
- container.innerHTML = '<thead><tr><th>Test case</th>' + visible.map(function (model) { return '<th><span class="mono">' + esc(model) + '</span></th>'; }).join('') + '</tr></thead>' +
328
- '<tbody>' + matrix.criteria.map(function (criterion) { return '<tr><td><div class="mono">' + esc(criterion.id) + '</div><div class="sub">' + esc(criterion.title || criterion.id) + '</div></td>' + visible.map(function (model) { return '<td>' + cellHtml(criterion.cells[model]) + '</td>'; }).join('') + '</tr>'; }).join('') + '</tbody>';
320
+ container.innerHTML = '<thead><tr><th>Test case</th><th>Available</th><th>Earned</th><th>Result</th></tr></thead><tbody>' + matrix.criteria.map(function (criterion) {
321
+ var cell = criterion.cells[selected];
322
+ if (!cell) return '<tr><td>' + esc(criterion.title || criterion.id) + '</td><td class="n-a">NA</td><td class="n-a">NA</td><td class="n-a">Unscored</td></tr>';
323
+ var max = cell.maxPoints || 0, earned = cell.earnedPoints || 0;
324
+ var status = cell.passed === cell.total ? 'Passed' : (cell.error ? 'Errored' : cell.blocked ? 'Blocked' : 'Failed');
325
+ return '<tr><td>' + esc(criterion.title || criterion.id) + '</td><td>' + fmt1(max) + '</td><td>' + fmt1(earned) + '</td><td>' + esc(status) + '</td></tr>';
326
+ }).join('') + '</tbody>';
329
327
  }
330
- matrix.models.forEach(function (model, index) {
331
- var pill = el("button", { class: "model-pill active", type: "button" }, esc(model));
332
- pill.addEventListener("click", function () {
333
- var pos = active.indexOf(model);
334
- if (pos === -1) { active.push(model); pill.classList.add("active"); }
335
- else if (active.length > 1) { active.splice(pos, 1); pill.classList.remove("active"); }
336
- draw();
337
- });
338
- pills.appendChild(pill);
339
- });
328
+ matrix.models.forEach(function (model) { selector.appendChild(el("option", { value: model }, esc(model))); });
329
+ selector.addEventListener("change", function () { selected = selector.value; draw(); });
340
330
  if (!matrix.criteria.length) {
341
331
  container.innerHTML = '<tbody><tr><td class="muted">No verifier criteria have produced results yet.</td></tr></tbody>';
342
332
  return;
package/src/report.js CHANGED
@@ -20,7 +20,11 @@ export function refreshReport(root) {
20
20
  if (!fs.existsSync(rootDir)) {
21
21
  throw new Error(`bench root not found: ${rootDir}`);
22
22
  }
23
- const runs = scanRuns(rootDir);
23
+ // Task versions are immutable benchmark definitions. A task output directory
24
+ // may retain old attempts for auditability, but combining versions changes
25
+ // the measured task and makes the dashboard misleading. Show the newest
26
+ // version for each task while preserving unversioned legacy runs.
27
+ const runs = latestTaskRuns(scanRuns(rootDir));
24
28
  const summary = buildSummary(runs);
25
29
 
26
30
  for (const run of runs) {
@@ -82,6 +86,21 @@ export function scanRuns(rootDir) {
82
86
  return runs;
83
87
  }
84
88
 
89
+ function latestTaskRuns(runs) {
90
+ const latestVersionByTask = new Map();
91
+ for (const run of runs) {
92
+ const taskId = run.project?.id;
93
+ const version = Number(run.project?.version);
94
+ if (!taskId || !Number.isFinite(version)) continue;
95
+ latestVersionByTask.set(taskId, Math.max(latestVersionByTask.get(taskId) ?? version, version));
96
+ }
97
+ return runs.filter((run) => {
98
+ const taskId = run.project?.id;
99
+ const version = Number(run.project?.version);
100
+ return !taskId || !Number.isFinite(version) || latestVersionByTask.get(taskId) === version;
101
+ });
102
+ }
103
+
85
104
  export function buildSummary(runs) {
86
105
  runs = runs.map((run) => ({ ...run, criteria: flattenCriteria(run.criteria) }));
87
106
  const verifierCriteria = summarizeCriteria(runs);
@@ -245,7 +264,10 @@ export function renderReportHtml(summary) {
245
264
  if (!tpl.includes("/*__ASTRA_DATA__*/")) {
246
265
  throw new Error("src/report.html is missing the /*__ASTRA_DATA__*/ injection marker");
247
266
  }
248
- return tpl.replace("/*__ASTRA_DATA__*/", `window.__ASTRA_EMBEDDED_DATA__ = ${data};`);
267
+ // Use a callback replacement: a report payload can contain shell snippets
268
+ // such as `$'...'` or `$&`, which String.replace treats as replacement
269
+ // tokens when given a plain string and can therefore corrupt the HTML.
270
+ return tpl.replace("/*__ASTRA_DATA__*/", () => `window.__ASTRA_EMBEDDED_DATA__ = ${data};`);
249
271
  }
250
272
 
251
273
  function toRunDoc(run) {
@@ -521,9 +543,12 @@ function buildVerifierMatrix(runs) {
521
543
  if (!byCriterion.has(criterion.id)) byCriterion.set(criterion.id, { title: criterion.title || criterion.id, byModel: new Map() });
522
544
  const entry = byCriterion.get(criterion.id);
523
545
  const byModel = entry.byModel;
524
- if (!byModel.has(run.slug)) byModel.set(run.slug, { passed: 0, failed: 0, blocked: 0, error: 0, total: 0 });
546
+ if (!byModel.has(run.slug)) byModel.set(run.slug, { passed: 0, failed: 0, blocked: 0, error: 0, total: 0, maxPoints: 0, earnedPoints: 0 });
525
547
  const cell = byModel.get(run.slug);
526
548
  cell.total += 1;
549
+ const weight = Math.max(0, Number(criterion.weight) || 0);
550
+ cell.maxPoints += weight;
551
+ if (criterion.status === "passed") cell.earnedPoints += weight;
527
552
  if (criterion.status === "passed") cell.passed += 1;
528
553
  else if (criterion.status === "blocked") cell.blocked += 1;
529
554
  else if (criterion.status === "error") cell.error += 1;