@hacksmith/doraval 0.2.45 → 0.2.47

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/bin/ui/index.html CHANGED
@@ -249,6 +249,26 @@
249
249
  </div>
250
250
  </div>
251
251
 
252
+ <!-- Evals / Skill Learnings -->
253
+ <div class="lg:col-span-5 section">
254
+ <div class="flex items-center justify-between mb-3">
255
+ <div>
256
+ <div class="text-sm font-semibold tracking-tight">Skill adherence learnings</div>
257
+ <div class="text-[10px] text-zinc-500 mt-0.5">From <span class="font-mono">dora eval</span> (respects DORAVAL_HOME) • last 25</div>
258
+ </div>
259
+ <div class="flex items-center gap-2">
260
+ <input id="eval-search" oninput="filterEvals()"
261
+ class="bg-zinc-950 border border-zinc-800 text-xs rounded-2xl px-3 py-1 placeholder:text-zinc-600 w-40 focus:outline-none focus:border-zinc-600"
262
+ placeholder="filter skill...">
263
+ <div class="text-[10px] text-zinc-500 tabular-nums" id="eval-count"></div>
264
+ <button onclick="loadEvals()"
265
+ class="px-3 py-1 text-xs rounded-xl border border-zinc-800 hover:bg-zinc-900 active:scale-[0.97] transition">Refresh</button>
266
+ </div>
267
+ </div>
268
+ <div id="evals" class="space-y-1.5 text-sm max-h-[260px] overflow-auto pr-1 -mr-1"></div>
269
+ <div class="mt-2 text-[10px] text-zinc-500">Click a row for full checklist. Run <span class="font-mono">dora eval</span> from the terminal to generate more.</div>
270
+ </div>
271
+
252
272
  <!-- Footer actions -->
253
273
  <div class="mt-8 flex items-center gap-2 text-sm">
254
274
  <button onclick="doAction('sync')"
@@ -261,11 +281,11 @@
261
281
  </button>
262
282
  <button onclick="doAction('open-dir')"
263
283
  class="px-4 py-2 rounded-2xl border border-zinc-800 hover:bg-zinc-900 active:scale-[0.975] transition text-sm">
264
- Open ~/.doraval
284
+ Open data dir
265
285
  </button>
266
286
 
267
287
  <div class="flex-1"></div>
268
- <div class="text-xs text-zinc-500">CLI is best for heavy lifting. This is for quick capture &amp; review.</div>
288
+ <div class="text-xs text-zinc-500">CLI is best for heavy lifting. This is for quick capture &amp; review. Data location depends on DORAVAL_HOME.</div>
269
289
  </div>
270
290
  </div>
271
291
 
@@ -300,6 +320,7 @@
300
320
  const $ = (id) => document.getElementById(id);
301
321
 
302
322
  let allEntriesCache = [];
323
+ let allEvalsCache = [];
303
324
  let currentProject = null;
304
325
 
305
326
  function showToast(msg, type = 'info') {
@@ -553,14 +574,143 @@ async function copyContext() {
553
574
  }
554
575
 
555
576
  async function refreshAll() {
556
- await Promise.all([loadEntries(), loadContext(), loadHooks()]);
577
+ await Promise.all([loadEntries(), loadContext(), loadHooks(), loadEvals()]);
557
578
  showToast('Refreshed');
558
579
  }
559
580
 
581
+ async function loadEvals() {
582
+ try {
583
+ const data = await fetchJSON('/api/evals');
584
+ allEvalsCache = data.evals || [];
585
+ const container = $('evals');
586
+ container.innerHTML = '';
587
+
588
+ if (allEvalsCache.length === 0) {
589
+ $('eval-count').textContent = '0 evals';
590
+ container.innerHTML = '<div class="text-zinc-500 text-sm py-2">No evals yet. Make sure <span class="font-mono">DORAVAL_HOME</span> matches where you ran <span class="font-mono">dora eval</span>, then refresh.</div>';
591
+ return;
592
+ }
593
+
594
+ $('eval-count').textContent = `${allEvalsCache.length} evals`;
595
+ allEvalsCache.forEach(e => renderEval(e, container));
596
+ } catch (e) {
597
+ const container = $('evals');
598
+ container.innerHTML = '<div class="text-red-400/70 text-sm py-2">Failed to load evals (check DORAVAL_HOME and restart ui with --force)</div>';
599
+ }
600
+ }
601
+
602
+ function renderEval(e, container) {
603
+ const div = document.createElement('div');
604
+ div.className = `entry border-l-[3px] bg-zinc-900/50 hover:bg-zinc-900/80 rounded-r-2xl px-3 py-2 cursor-pointer flex items-start gap-3 ${e.verdict === 'PASS' ? 'border-emerald-400' : e.verdict === 'FAIL' ? 'border-red-400' : 'border-yellow-400'}`;
605
+ div.onclick = () => showEvalModal(e);
606
+
607
+ const date = (e.timestamp || '').slice(0, 10);
608
+ const skillShort = (e.skill || '').slice(0, 28);
609
+ const fam = typeof e.userFamiliarity === 'number' ? `${e.userFamiliarity}/10` : '';
610
+ const passed = e.checklist ? e.checklist.filter((c) => c.pass).length : 0;
611
+ const total = e.checklist ? e.checklist.length : 0;
612
+ const score = total > 0 ? `${passed}/${total}` : '';
613
+
614
+ const verdictColor = e.verdict === 'PASS' ? 'text-emerald-400' : e.verdict === 'FAIL' ? 'text-red-400' : 'text-yellow-400';
615
+
616
+ div.innerHTML = `
617
+ <div class="w-14 shrink-0 pt-0.5">
618
+ <div class="${verdictColor} font-semibold text-xs tracking-wider">${e.verdict || 'UNKNOWN'}</div>
619
+ <div class="text-[10px] text-zinc-500 mt-0.5 tabular-nums">${date}</div>
620
+ </div>
621
+ <div class="min-w-0 flex-1">
622
+ <div class="font-medium leading-tight flex items-baseline gap-2">
623
+ <span>${skillShort}</span>
624
+ ${score ? `<span class="text-[10px] font-mono text-zinc-400">${score}</span>` : ''}
625
+ </div>
626
+ <div class="text-xs text-zinc-400 mt-0.5 flex gap-x-2 flex-wrap">
627
+ <span>familiarity ${fam || '—'}</span>
628
+ <span class="text-zinc-700">·</span>
629
+ <span>${e.closure || ''}</span>
630
+ ${e.sessionTitle ? `<span class="text-zinc-700">·</span> <span class="truncate max-w-[220px]">${e.sessionTitle}</span>` : ''}
631
+ </div>
632
+ </div>
633
+ `;
634
+ container.appendChild(div);
635
+ }
636
+
637
+ function filterEvals() {
638
+ const q = ($('eval-search')?.value || '').toLowerCase().trim();
639
+ const container = $('evals');
640
+ container.innerHTML = '';
641
+
642
+ let filtered = allEvalsCache;
643
+ if (q) {
644
+ filtered = allEvalsCache.filter((e) =>
645
+ (e.skill || '').toLowerCase().includes(q) ||
646
+ (e.sessionTitle || '').toLowerCase().includes(q) ||
647
+ (e.verdict || '').toLowerCase().includes(q)
648
+ );
649
+ }
650
+
651
+ $('eval-count').textContent = `${filtered.length} / ${allEvalsCache.length} evals`;
652
+
653
+ if (filtered.length === 0) {
654
+ container.innerHTML = `<div class="text-zinc-500 text-sm py-2">No matches.</div>`;
655
+ return;
656
+ }
657
+ filtered.forEach(e => renderEval(e, container));
658
+ }
659
+
660
+ function showEvalModal(e) {
661
+ const modal = $('modal');
662
+ modal.classList.remove('hidden');
663
+ modal.classList.add('flex');
664
+
665
+ // Repurpose modal for eval
666
+ $('modal-pb').className = `inline-flex items-center px-3 py-px rounded-full text-[10px] font-semibold tracking-[0.5px] ${e.verdict === 'PASS' ? 'bg-emerald-500 text-black' : e.verdict === 'FAIL' ? 'bg-red-500 text-white' : 'bg-yellow-400 text-black'}`;
667
+ $('modal-pb').textContent = `${e.verdict || 'UNKNOWN'} • ${e.skill || ''}`;
668
+
669
+ $('modal-title').textContent = e.sessionTitle || e.skill || 'Session eval';
670
+
671
+ const metaParts = [];
672
+ if (e.timestamp) metaParts.push(e.timestamp.slice(0,16).replace('T',' '));
673
+ if (typeof e.userFamiliarity === 'number') metaParts.push(`familiarity ${e.userFamiliarity}/10`);
674
+ if (e.closure) metaParts.push(e.closure);
675
+ if (e.agent) metaParts.push(e.agent);
676
+ $('modal-meta').innerHTML = metaParts.join(' · ');
677
+
678
+ const tagsStr = e.model ? `model: ${e.model}` : '';
679
+ $('modal-tags').innerHTML = tagsStr;
680
+
681
+ // Build rich rationale area with checklist
682
+ const checklistHtml = (e.checklist || []).map((item) => {
683
+ const sym = item.pass ? '<span class="text-emerald-400">✓</span>' : '<span class="text-red-400">✗</span>';
684
+ const detail = item.detail ? ` <span class="text-zinc-500 text-xs">— ${item.detail}</span>` : '';
685
+ return `<div class="py-0.5">${sym} ${item.instruction}${detail}</div>`;
686
+ }).join('');
687
+
688
+ const reason = e.verdictReason ? `<div class="mt-3 text-xs text-zinc-400">Reason: ${e.verdictReason}</div>` : '';
689
+
690
+ $('modal-rationale').innerHTML = `
691
+ <div class="text-sm">
692
+ <div class="font-medium mb-1">Adherence checklist</div>
693
+ <div class="space-y-0.5 text-[13px]">${checklistHtml || 'No checklist items.'}</div>
694
+ ${reason}
695
+ <div class="mt-3 text-[11px] text-zinc-500">Result: ${e.checklist ? e.checklist.filter((c)=>c.pass).length : 0}/${(e.checklist||[]).length} — ${e.verdict}</div>
696
+ </div>
697
+ `;
698
+
699
+ const actions = $('modal-actions');
700
+ actions.innerHTML = '';
701
+
702
+ const closeBtn = document.createElement('button');
703
+ closeBtn.className = 'ml-auto px-5 py-1.5 text-sm rounded-2xl border border-zinc-700 hover:bg-zinc-800 active:scale-[0.975] transition';
704
+ closeBtn.textContent = 'Close';
705
+ closeBtn.onclick = closeModal;
706
+ actions.appendChild(closeBtn);
707
+ }
708
+
560
709
  async function doAction(name) {
561
710
  if (name === 'open-dir') {
562
711
  const s = await fetchJSON('/api/status');
563
- showToast('Path: ' + s.doravalDir);
712
+ const root = s.doravalRoot || s.doravalDir || '~/.doraval';
713
+ showToast('Data directory: ' + root);
564
714
  // optionally try to open, but browser security limits it
565
715
  return;
566
716
  }
@@ -697,7 +847,7 @@ async function boot() {
697
847
  setupCaptureKeys();
698
848
 
699
849
  await loadStatus();
700
- await Promise.all([loadContext(), loadEntries(), loadHooks()]);
850
+ await Promise.all([loadContext(), loadEntries(), loadHooks(), loadEvals()]);
701
851
 
702
852
  const search = $('search');
703
853
  if (search) {
@@ -714,6 +864,7 @@ async function boot() {
714
864
  if (!document.hidden) {
715
865
  loadEntries().catch(() => {});
716
866
  loadContext().catch(() => {});
867
+ loadEvals().catch(() => {});
717
868
  }
718
869
  }, 9000);
719
870
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hacksmith/doraval",
3
- "version": "0.2.45",
3
+ "version": "0.2.47",
4
4
  "author": "Saif",
5
5
  "repository": {
6
6
  "type": "git",
@@ -13,7 +13,7 @@
13
13
  "doraval": "bin/doraval-wrapper.js",
14
14
  "dora": "bin/doraval-wrapper.js"
15
15
  },
16
- "description": "The context engineering toolkit for coding agents",
16
+ "description": "The context engineering toolkit for coding agent orchestrators",
17
17
  "engines": {
18
18
  "bun": ">=1.2.0",
19
19
  "node": ">=14.18.0"