oh-my-knowledge 0.44.0 → 0.46.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.
Files changed (43) hide show
  1. package/README.md +1 -1
  2. package/README.zh.md +1 -1
  3. package/dist/artifact-graph/doctor.js +1 -1
  4. package/dist/artifact-graph/eval.js +120 -0
  5. package/dist/assets/agent-skills/omk/SKILL.md +3 -3
  6. package/dist/assets/agent-skills/omk/references/commands.md +13 -6
  7. package/dist/cli/commands/doctor.d.ts +3 -2
  8. package/dist/cli/commands/doctor.js +66 -63
  9. package/dist/cli/lib/cmd-flags.d.ts +3 -2
  10. package/dist/cli/lib/i18n-dict/common.d.ts +1 -1
  11. package/dist/cli/lib/i18n-dict/common.js +0 -4
  12. package/dist/doctor/endpoint-rule.js +1 -1
  13. package/dist/doctor/health/composer.js +162 -50
  14. package/dist/doctor/health/consensus.d.ts +43 -0
  15. package/dist/doctor/health/consensus.js +224 -0
  16. package/dist/doctor/health/dimension-spec.d.ts +6 -0
  17. package/dist/doctor/health/register.d.ts +0 -1
  18. package/dist/doctor/health/register.js +0 -1
  19. package/dist/doctor/index.js +26 -3
  20. package/dist/doctor/messages.d.ts +1 -1
  21. package/dist/doctor/messages.js +8 -0
  22. package/dist/doctor/rules.d.ts +4 -2
  23. package/dist/doctor/rules.js +4 -2
  24. package/dist/eval-core/evaluation-reporting.js +1 -0
  25. package/dist/executors/claude-cli.js +1 -1
  26. package/dist/inputs/load-samples.js +27 -0
  27. package/dist/observability/skill-chain-advisories.js +2 -2
  28. package/dist/renderer/doctor-detail-renderer.js +30 -2
  29. package/dist/renderer/report-shell.d.ts +1 -1
  30. package/dist/renderer/report-shell.js +5 -0
  31. package/dist/renderer/skill-detail-renderer.js +908 -94
  32. package/dist/renderer/skill-list-renderer.js +1 -1
  33. package/dist/server/skill-index.js +58 -1
  34. package/dist/shared/llm-prompts/registry.js +2 -1
  35. package/dist/shared/llm-prompts/skill-health-merge.d.ts +22 -0
  36. package/dist/shared/llm-prompts/skill-health-merge.js +79 -0
  37. package/dist/shared/llm-prompts/skill-health.js +1 -1
  38. package/dist/types/artifact-graph.d.ts +1 -1
  39. package/dist/types/doctor.d.ts +32 -8
  40. package/dist/types/eval.d.ts +10 -0
  41. package/dist/types/report.d.ts +3 -1
  42. package/dist/types/skill-index.d.ts +14 -0
  43. package/package.json +6 -5
@@ -400,11 +400,21 @@ details.si-pass-card[open] > summary > .si-pass-row1::before { content:'▾ ' }
400
400
  /* Skill Map:以 SKILL.md 为根的轻量知识图,不暴露完整 graph JSON 拓扑 */
401
401
  .sm-sect { border-left-color:#4f46e5 }
402
402
  .sm-body { display:flex;flex-direction:column;gap:10px }
403
+ .sm-evidence-strip { display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px }
404
+ .sm-evidence-item { min-height:44px;padding:8px 10px;border:1px solid #e4e8f1;border-radius:8px;background:#fff;display:flex;flex-direction:column;justify-content:center;gap:2px }
405
+ .sm-evidence-item--risk { border-color:rgba(220,38,38,.34);background:rgba(220,38,38,.055) }
406
+ .sm-evidence-item--action { border-color:rgba(245,158,11,.42);background:rgba(245,158,11,.075) }
407
+ .sm-evidence-k { font-size:10px;color:#8792a4;font-weight:700;line-height:1;text-transform:uppercase;font-family:"SF Mono",Menlo,monospace }
408
+ .sm-evidence-v { font-size:12.5px;color:#182033;font-weight:700;line-height:1.35;word-break:break-word }
403
409
  .sm-toolbar { display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;padding:8px 10px;border:1px solid #e4e8f1;border-radius:8px;background:#fff }
404
410
  .sm-tools,.sm-toggles { display:flex;align-items:center;gap:6px;flex-wrap:wrap }
405
411
  .sm-tool { min-width:30px;height:28px;padding:0 9px;border:1px solid #dbe2ee;border-radius:6px;background:#fff;color:#384255;font-size:13px;font-weight:700;cursor:pointer }
406
412
  .sm-tool:hover { background:#f7f8ff;border-color:#c7d2fe }
407
413
  .sm-zoom-label { min-width:44px;text-align:center;font-size:11.5px;color:#637083;font-variant-numeric:tabular-nums }
414
+ .sm-view-switch { display:inline-flex;align-items:center;gap:3px;padding:3px;border:1px solid #dbe2ee;border-radius:8px;background:#f8fafc }
415
+ .sm-view { height:26px;padding:0 10px;border:0;border-radius:6px;background:transparent;color:#637083;font-size:11.5px;font-weight:700;cursor:pointer }
416
+ .sm-view:hover { color:#384255;background:#fff }
417
+ .sm-view.is-active { color:#182033;background:#fff;box-shadow:0 1px 4px rgba(31,41,55,.08) }
408
418
  .sm-toggle { display:inline-flex;align-items:center;gap:5px;height:28px;padding:0 8px;border:1px solid #dbe2ee;border-radius:14px;background:#fff;color:#384255;font-size:11.5px;cursor:pointer;user-select:none }
409
419
  .sm-toggle input { margin:0;accent-color:#4f46e5 }
410
420
  .sm-canvas { position:relative;height:min(68vh,620px);min-height:520px;padding:0;background:
@@ -412,6 +422,13 @@ details.si-pass-card[open] > summary > .si-pass-row1::before { content:'▾ ' }
412
422
  linear-gradient(rgba(148,163,184,.075) 1px,transparent 1px),
413
423
  linear-gradient(90deg,rgba(148,163,184,.06) 1px,transparent 1px),
414
424
  linear-gradient(#fff,#fbfcff);background-size:auto,28px 28px,28px 28px,auto;background-position:0 0;border:1px solid #e4e8f1;border-radius:8px;overflow:auto;overscroll-behavior:contain }
425
+ .sm-body[data-sm-view="boundary"] .sm-canvas { background:
426
+ linear-gradient(90deg,rgba(31,157,99,.052) 0%,rgba(31,157,99,.025) 42%,transparent 42%,transparent 100%),
427
+ linear-gradient(rgba(148,163,184,.07) 1px,transparent 1px),
428
+ linear-gradient(90deg,rgba(148,163,184,.055) 1px,transparent 1px),
429
+ linear-gradient(#fff,#fbfcff);background-size:auto,28px 28px,28px 28px,auto;background-position:0 0 }
430
+ .sm-body[data-sm-view="boundary"] [data-sm-layer="measurement"],
431
+ .sm-body[data-sm-view="boundary"] [data-sm-evidence-edge="1"] { display:none !important }
415
432
  .sm-graph-stage { position:relative;width:1100px;height:560px }
416
433
  .sm-graph { position:absolute;left:0;top:0;width:1100px;height:560px;transform-origin:0 0;transition:transform .12s ease }
417
434
  .sm-graph [hidden] { display:none !important }
@@ -419,6 +436,11 @@ details.si-pass-card[open] > summary > .si-pass-row1::before { content:'▾ ' }
419
436
  .sm-edge { fill:none;stroke:#d8e0ec;stroke-width:1.45;stroke-linecap:round;opacity:.86;vector-effect:non-scaling-stroke }
420
437
  .sm-edge--definition { stroke:#a9cfc4 }
421
438
  .sm-edge--measurement { stroke:#bbc7ff }
439
+ .sm-edge--coverage { stroke:#f59e0b;stroke-width:2;stroke-dasharray:5 5;opacity:.9 }
440
+ .sm-edge--coverage-failed { stroke:#dc2626;stroke-width:2.2;opacity:.92 }
441
+ .sm-zone-label { position:absolute;top:16px;z-index:1;font-size:10.5px;font-weight:800;letter-spacing:0;text-transform:uppercase;color:#8792a4;font-family:"SF Mono",Menlo,monospace;background:rgba(255,255,255,.82);border:1px solid #e4e8f1;border-radius:12px;padding:4px 9px;pointer-events:none }
442
+ .sm-zone-label--definition { left:36px;color:#2f7d68;border-color:rgba(47,125,104,.22);background:rgba(244,251,248,.86) }
443
+ .sm-zone-label--measurement { right:36px;color:#4f46e5;border-color:rgba(79,70,229,.22);background:rgba(247,248,255,.86) }
422
444
  .sm-node { position:absolute;z-index:1;box-sizing:border-box;transform:translate(-50%,-50%);width:148px;min-height:54px;padding:8px 10px;border:1px solid #d8deea;border-radius:7px;background:rgba(255,255,255,.96);box-shadow:0 8px 20px rgba(31,41,55,6%);display:flex;flex-direction:column;justify-content:center;gap:4px;text-align:center;backdrop-filter:blur(4px) }
423
445
  .sm-node[data-sm-draggable="1"] { cursor:grab;touch-action:none;user-select:none }
424
446
  .sm-node[data-sm-draggable="1"].is-dragging { cursor:grabbing;z-index:3;box-shadow:0 16px 34px rgba(31,41,55,14%) }
@@ -426,18 +448,36 @@ details.si-pass-card[open] > summary > .si-pass-row1::before { content:'▾ ' }
426
448
  .sm-node[data-sm-draggable="1"][data-sm-more-toggle] { cursor:grab }
427
449
  .sm-node[data-sm-draggable="1"][data-sm-more-toggle].is-dragging { cursor:grabbing }
428
450
  .sm-node[data-sm-more-toggle]:hover,.sm-node[data-sm-more-toggle]:focus-visible { border-color:#9fbfb5;background:#f1fbf7;outline:none }
451
+ .sm-node:hover,.sm-node:focus-visible,.sm-node.is-selected { border-color:#4f46e5;outline:3px solid rgba(79,70,229,.12);outline-offset:2px }
452
+ .sm-node.is-selected { z-index:2;box-shadow:0 14px 30px rgba(79,70,229,13%) }
453
+ .sm-node.is-related { z-index:2;outline:3px solid rgba(245,158,11,.14);outline-offset:2px;box-shadow:0 12px 26px rgba(245,158,11,.12) }
429
454
  .sm-node--skill { width:172px;min-height:86px;border-color:#4f46e5;background:linear-gradient(180deg,#fff 0%,#f7f8ff 100%);box-shadow:0 16px 34px rgba(79,70,229,14%);outline:4px solid rgba(79,70,229,.07) }
455
+ .sm-node--skill.is-selected { outline-color:rgba(79,70,229,.15) }
430
456
  .sm-node--definition { border-color:#b7d8cf;background:#f4fbf8 }
431
457
  .sm-node--measurement { border-color:#c7d2fe;background:#f7f8ff }
432
458
  .sm-node--ok { border-color:rgba(31,157,99,.38);background:rgba(31,157,99,.06) }
433
459
  .sm-node--warning { border-color:rgba(217,119,6,.38);background:rgba(217,119,6,.07) }
434
460
  .sm-node--failed { border-color:rgba(220,38,38,.38);background:rgba(220,38,38,.06) }
435
461
  .sm-node--pending,.sm-node--more { border-style:dashed;background:rgba(248,249,251,.94);color:#637083 }
462
+ .sm-node--coverage-declared { border-color:rgba(31,157,99,.42);box-shadow:0 10px 24px rgba(31,157,99,.08) }
463
+ .sm-node--coverage-undeclared { border-style:dashed;background:rgba(248,249,251,.94);color:#637083 }
436
464
  .sm-node-title { font-size:12.2px;font-weight:700;color:#182033;line-height:1.28;word-break:break-word;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden }
437
465
  .sm-node--skill .sm-node-title { font-size:15px;line-height:1.2 }
438
466
  .sm-node-meta { font-size:10.5px;color:#637083;line-height:1.35;word-break:break-word }
439
467
  .sm-node--skill .sm-node-meta { font-size:11.5px }
440
468
  .sm-node-kind { font-size:9.5px;text-transform:uppercase;letter-spacing:0;color:#8792a4;font-family:"SF Mono",Menlo,monospace;line-height:1 }
469
+ .sm-node--group { width:164px;min-height:66px;border-color:rgba(47,125,104,.42);background:linear-gradient(180deg,#fff 0%,#f4fbf8 100%);box-shadow:0 12px 28px rgba(47,125,104,.1) }
470
+ .sm-node--group .sm-node-title { font-size:13px }
471
+ .sm-node-toggle-icon { position:absolute;right:7px;top:7px;width:17px;height:17px;border:1px solid rgba(47,125,104,.24);border-radius:50%;background:rgba(255,255,255,.72);box-shadow:0 4px 10px rgba(47,125,104,.06);color:#2f7d68;display:flex;align-items:center;justify-content:center;transition:background .14s ease,border-color .14s ease,box-shadow .14s ease }
472
+ .sm-node-toggle-icon::before { content:'';width:5px;height:5px;border-right:1.8px solid currentColor;border-bottom:1.8px solid currentColor;transform:translateY(-1px) rotate(45deg);transition:transform .14s ease }
473
+ .sm-node[data-sm-more-toggle]:hover .sm-node-toggle-icon,.sm-node[data-sm-more-toggle]:focus-visible .sm-node-toggle-icon { background:#fff;border-color:rgba(47,125,104,.42);box-shadow:0 5px 12px rgba(47,125,104,.11) }
474
+ .sm-node[data-sm-more-toggle][aria-expanded="true"] .sm-node-toggle-icon { background:rgba(47,125,104,.1);border-color:rgba(47,125,104,.34);box-shadow:0 4px 10px rgba(47,125,104,.08) }
475
+ .sm-node[data-sm-more-toggle][aria-expanded="true"] .sm-node-toggle-icon::before { transform:translateY(1px) rotate(225deg) }
476
+ .sm-node-signal { align-self:center;max-width:100%;padding:1px 6px;border-radius:9px;font-size:9.5px;font-weight:700;line-height:1.25;color:#637083;background:#eef2f7;white-space:nowrap;overflow:hidden;text-overflow:ellipsis }
477
+ .sm-node-signal--failed { color:#dc2626;background:rgba(220,38,38,.12) }
478
+ .sm-node-signal--warning { color:#d97706;background:rgba(217,119,6,.13) }
479
+ .sm-node-signal--declared { color:#1f9d63;background:rgba(31,157,99,.13) }
480
+ .sm-node-signal--undeclared { color:#637083;background:#eef2f7;border:1px dashed #cbd5e1 }
441
481
  .sm-stage-rail { display:grid;grid-template-columns:1fr auto 1fr auto 1fr;align-items:center;gap:8px;padding:10px 12px;border:1px solid #e4e8f1;border-radius:8px;background:#fff }
442
482
  .sm-stage-card { min-height:50px;padding:9px 11px;border:1px solid #dbe2ee;border-radius:8px;background:#fbfcff;text-align:center;display:flex;flex-direction:column;justify-content:center;gap:3px }
443
483
  .sm-stage-card--ok { border-color:rgba(31,157,99,.38);background:rgba(31,157,99,.06) }
@@ -447,6 +487,16 @@ details.si-pass-card[open] > summary > .si-pass-row1::before { content:'▾ ' }
447
487
  .sm-stage-k { font-size:10px;text-transform:uppercase;letter-spacing:0;color:#7b8798;font-family:"SF Mono",Menlo,monospace;line-height:1 }
448
488
  .sm-stage-v { font-size:13px;font-weight:700;color:#182033;line-height:1.35 }
449
489
  .sm-stage-arrow { color:#b8c5d8;font-size:18px;line-height:1;text-align:center }
490
+ .sm-detail { display:grid;grid-template-columns:minmax(180px,1.1fr) minmax(260px,2fr);gap:12px;align-items:start;padding:12px;border:1px solid #e4e8f1;border-radius:8px;background:linear-gradient(180deg,#fff 0%,#fbfcff 100%) }
491
+ .sm-detail-head { min-width:0 }
492
+ .sm-detail-kind { display:inline-flex;align-items:center;min-height:20px;padding:0 7px;border-radius:10px;background:#eef2ff;color:#4f46e5;font-size:10.5px;font-weight:700;line-height:1 }
493
+ .sm-detail-title { margin-top:8px;font-size:15px;font-weight:700;line-height:1.35;color:#182033;word-break:break-word }
494
+ .sm-detail-subtitle { margin-top:3px;font-size:12px;line-height:1.45;color:#637083;word-break:break-word }
495
+ .sm-detail-list { margin:0;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px }
496
+ .sm-detail-row { min-width:0;padding:8px 10px;border:1px solid #eef2f7;border-radius:7px;background:#fff }
497
+ .sm-detail-row[hidden] { display:none !important }
498
+ .sm-detail-row dt { margin:0 0 3px;font-size:10.5px;color:#8792a4;font-weight:600;line-height:1.2 }
499
+ .sm-detail-row dd { margin:0;font-size:12px;color:#384255;line-height:1.45;word-break:break-word }
450
500
  .sm-card { border:1px solid #e4e8f1;border-radius:8px;background:#fff;overflow:hidden }
451
501
  .sm-card > summary { cursor:pointer;list-style:none;padding:10px 12px;font-size:12.5px;font-weight:600;color:#4f46e5;user-select:none }
452
502
  .sm-card > summary::-webkit-details-marker { display:none }
@@ -455,9 +505,12 @@ details.si-pass-card[open] > summary > .si-pass-row1::before { content:'▾ ' }
455
505
  .sm-card textarea { width:100%;min-height:260px;border:0;border-top:1px solid #e4e8f1;background:#fbfcff;padding:12px;font-family:"SF Mono",Menlo,monospace;font-size:11.5px;line-height:1.55;color:#182033;resize:vertical;box-sizing:border-box }
456
506
  @media(max-width:900px){
457
507
  .sm-toolbar { align-items:flex-start }
508
+ .sm-evidence-strip { grid-template-columns:1fr }
458
509
  .sm-stage-rail { grid-template-columns:1fr;gap:6px }
459
510
  .sm-stage-arrow { transform:rotate(90deg);font-size:16px }
460
511
  .sm-canvas { height:620px;min-height:620px }
512
+ .sm-detail { grid-template-columns:1fr }
513
+ .sm-detail-list { grid-template-columns:1fr }
461
514
  .sm-node { width:142px }
462
515
  .sm-node--skill { width:168px }
463
516
  }
@@ -844,6 +897,7 @@ const SKILL_MAP_INIT_SCRIPT = `
844
897
  var stage = map.querySelector('[data-sm-graph-stage]');
845
898
  var graph = map.querySelector('[data-sm-graph]');
846
899
  var label = map.querySelector('[data-sm-zoom-label]');
900
+ var detail = map.querySelector('[data-sm-detail]');
847
901
  if (!viewport || !stage || !graph) return;
848
902
  var baseWidth = Number(map.getAttribute('data-sm-base-width')) || 1100;
849
903
  var baseHeight = Number(map.getAttribute('data-sm-base-height')) || 560;
@@ -852,6 +906,8 @@ const SKILL_MAP_INIT_SCRIPT = `
852
906
  var zoom = 1;
853
907
  var dragState = null;
854
908
  var suppressMoreClickUntil = 0;
909
+ var selectedNodeId = '';
910
+ var currentView = map.getAttribute('data-sm-view') || 'boundary';
855
911
 
856
912
  function edgePoint(fromX, fromY, toX, toY, box){
857
913
  var dx = toX - fromX;
@@ -900,6 +956,92 @@ const SKILL_MAP_INIT_SCRIPT = `
900
956
  node.style.top = y + 'px';
901
957
  }
902
958
 
959
+ function nodeViewPosition(node, view){
960
+ var prefix = view === 'evidence' ? 'data-sm-evidence' : 'data-sm-boundary';
961
+ var x = Number(node.getAttribute(prefix + '-x'));
962
+ var y = Number(node.getAttribute(prefix + '-y'));
963
+ if (Number.isFinite(x) && Number.isFinite(y)) return { x: x, y: y };
964
+ return {
965
+ x: Number(node.getAttribute('data-sm-origin-x')) || Number(node.getAttribute('data-sm-x')) || parseFloat(node.style.left) || 0,
966
+ y: Number(node.getAttribute('data-sm-origin-y')) || Number(node.getAttribute('data-sm-y')) || parseFloat(node.style.top) || 0
967
+ };
968
+ }
969
+
970
+ function hideFilteredNode(node){
971
+ node.hidden = true;
972
+ node.toggleAttribute('hidden', true);
973
+ node.setAttribute('aria-hidden', 'true');
974
+ node.style.display = 'none';
975
+ }
976
+
977
+ function revealNode(node, transient){
978
+ node.hidden = false;
979
+ node.toggleAttribute('hidden', false);
980
+ node.setAttribute('aria-hidden', 'false');
981
+ node.style.display = '';
982
+ node.setAttribute('data-sm-filter-hidden', '0');
983
+ if (transient) node.setAttribute('data-sm-evidence-revealed', '1');
984
+ }
985
+
986
+ function detailText(node, attr, fallback){
987
+ var value = node ? node.getAttribute(attr) : '';
988
+ return value && value.trim() ? value : (fallback || '');
989
+ }
990
+
991
+ function setDetailValue(key, value, required){
992
+ if (!detail) return;
993
+ var row = detail.querySelector('[data-sm-detail-row="' + key + '"]');
994
+ var valueEl = detail.querySelector('[data-sm-detail-value="' + key + '"]');
995
+ var hasValue = !!(value && value.trim());
996
+ if (valueEl) valueEl.textContent = hasValue ? value : '—';
997
+ if (row) {
998
+ var hidden = !hasValue && !required;
999
+ row.hidden = hidden;
1000
+ row.toggleAttribute('hidden', hidden);
1001
+ }
1002
+ }
1003
+
1004
+ function selectedMoreStatus(node){
1005
+ if (!node || !node.hasAttribute('data-sm-more-toggle')) return '';
1006
+ var attr = node.getAttribute('aria-expanded') === 'true'
1007
+ ? 'data-sm-detail-expanded-status'
1008
+ : 'data-sm-detail-collapsed-status';
1009
+ return node.getAttribute(attr) || '';
1010
+ }
1011
+
1012
+ function setSelectedNode(node){
1013
+ if (!node || node.hidden) return;
1014
+ var nodeId = node.getAttribute('data-sm-node-id') || '';
1015
+ selectedNodeId = nodeId;
1016
+ graph.querySelectorAll('.sm-node.is-selected').forEach(function(item){
1017
+ item.classList.remove('is-selected');
1018
+ item.setAttribute('aria-pressed', 'false');
1019
+ });
1020
+ node.classList.add('is-selected');
1021
+ node.setAttribute('aria-pressed', 'true');
1022
+ if (!detail) return;
1023
+ var title = detailText(node, 'data-sm-detail-title', node.textContent || '');
1024
+ var subtitle = detailText(node, 'data-sm-detail-subtitle', '');
1025
+ var kind = detailText(node, 'data-sm-detail-kind', '');
1026
+ var kindEl = detail.querySelector('[data-sm-detail-kind-label]');
1027
+ var titleEl = detail.querySelector('[data-sm-detail-title]');
1028
+ var subtitleEl = detail.querySelector('[data-sm-detail-subtitle]');
1029
+ if (kindEl) kindEl.textContent = kind;
1030
+ if (titleEl) titleEl.textContent = title;
1031
+ if (subtitleEl) {
1032
+ subtitleEl.textContent = subtitle;
1033
+ subtitleEl.hidden = !subtitle;
1034
+ subtitleEl.toggleAttribute('hidden', !subtitle);
1035
+ }
1036
+ setDetailValue('scope', detailText(node, 'data-sm-detail-scope', ''), true);
1037
+ setDetailValue('source', detailText(node, 'data-sm-detail-source', ''), false);
1038
+ setDetailValue('coverage', detailText(node, 'data-sm-detail-coverage', ''), false);
1039
+ setDetailValue('status', selectedMoreStatus(node) || detailText(node, 'data-sm-detail-status', ''), false);
1040
+ setDetailValue('evidence', detailText(node, 'data-sm-detail-evidence', ''), false);
1041
+ setDetailValue('action', detailText(node, 'data-sm-detail-action', ''), false);
1042
+ updateEvidenceFocus();
1043
+ }
1044
+
903
1045
  function updateEdgesForNode(nodeId, x, y){
904
1046
  map.querySelectorAll('[data-sm-edge-to]').forEach(function(edge){
905
1047
  if (edge.getAttribute('data-sm-edge-to') !== nodeId) return;
@@ -949,13 +1091,16 @@ const SKILL_MAP_INIT_SCRIPT = `
949
1091
  }
950
1092
 
951
1093
  function resetNodePositions(){
1094
+ applyViewPositions(currentView);
1095
+ }
1096
+
1097
+ function applyViewPositions(view){
952
1098
  graph.querySelectorAll('[data-sm-draggable="1"]').forEach(function(node){
953
1099
  var nodeId = node.getAttribute('data-sm-node-id');
954
- var originX = Number(node.getAttribute('data-sm-origin-x')) || Number(node.getAttribute('data-sm-x')) || parseFloat(node.style.left) || 0;
955
- var originY = Number(node.getAttribute('data-sm-origin-y')) || Number(node.getAttribute('data-sm-y')) || parseFloat(node.style.top) || 0;
956
- setNodePosition(node, originX, originY);
957
- if (nodeId) updateEdgesForNode(nodeId, originX, originY);
958
- if (nodeId) updateEdgesFromNode(nodeId, originX, originY);
1100
+ var next = nodeViewPosition(node, view);
1101
+ setNodePosition(node, next.x, next.y);
1102
+ if (nodeId) updateEdgesForNode(nodeId, next.x, next.y);
1103
+ if (nodeId) updateEdgesFromNode(nodeId, next.x, next.y);
959
1104
  });
960
1105
  }
961
1106
 
@@ -987,35 +1132,103 @@ const SKILL_MAP_INIT_SCRIPT = `
987
1132
  viewport.scrollTop = Math.max(0, scaledRootY - viewport.clientHeight / 2);
988
1133
  }
989
1134
 
990
- function layerEnabled(layer){
991
- var input = map.querySelector('[data-sm-toggle-layer="' + layer + '"]');
992
- return !input || input.checked;
993
- }
994
-
995
1135
  function overflowGroupExpanded(group){
996
1136
  if (!group) return true;
997
1137
  var toggle = map.querySelector('[data-sm-more-toggle="' + group + '"]');
998
1138
  return !!toggle && toggle.getAttribute('aria-expanded') === 'true';
999
1139
  }
1000
1140
 
1141
+ function updateViewButtons(){
1142
+ map.setAttribute('data-sm-view', currentView);
1143
+ map.querySelectorAll('[data-sm-view-button]').forEach(function(button){
1144
+ var active = button.getAttribute('data-sm-view-button') === currentView;
1145
+ button.classList.toggle('is-active', active);
1146
+ button.setAttribute('aria-pressed', active ? 'true' : 'false');
1147
+ });
1148
+ }
1149
+
1150
+ function updateEvidenceFocus(){
1151
+ if (!graph) return;
1152
+ graph.querySelectorAll('[data-sm-evidence-revealed="1"]').forEach(function(node){
1153
+ node.removeAttribute('data-sm-evidence-revealed');
1154
+ if (node.getAttribute('data-sm-layer') === 'definition' && currentView === 'evidence') hideFilteredNode(node);
1155
+ });
1156
+ graph.querySelectorAll('.sm-node.is-related').forEach(function(node){
1157
+ node.classList.remove('is-related');
1158
+ });
1159
+ map.querySelectorAll('[data-sm-evidence-edge="1"]').forEach(function(edge){
1160
+ var toId = edge.getAttribute('data-sm-edge-to');
1161
+ var fromId = edge.getAttribute('data-sm-edge-from-node');
1162
+ var toNode = toId ? graph.querySelector('[data-sm-node-id="' + toId + '"]') : null;
1163
+ var fromNode = fromId ? graph.querySelector('[data-sm-node-id="' + fromId + '"]') : null;
1164
+ var focused = currentView === 'evidence' && !!selectedNodeId && (toId === selectedNodeId || fromId === selectedNodeId);
1165
+ if (focused) {
1166
+ if (toNode) revealNode(toNode, true);
1167
+ if (fromNode) revealNode(fromNode, true);
1168
+ }
1169
+ var endpointHidden = (toNode && toNode.hidden) || (fromNode && fromNode.hidden);
1170
+ var shouldHide = !focused || endpointHidden;
1171
+ edge.hidden = shouldHide;
1172
+ edge.toggleAttribute('hidden', shouldHide);
1173
+ edge.setAttribute('aria-hidden', shouldHide ? 'true' : 'false');
1174
+ edge.style.display = shouldHide ? 'none' : '';
1175
+ if (!shouldHide) {
1176
+ if (toNode && toId !== selectedNodeId) toNode.classList.add('is-related');
1177
+ if (fromNode && fromId !== selectedNodeId) fromNode.classList.add('is-related');
1178
+ }
1179
+ });
1180
+ }
1181
+
1001
1182
  function updateVisibility(){
1002
1183
  var showLeaves = true;
1003
1184
  var leafToggle = map.querySelector('[data-sm-toggle-leaves]');
1004
1185
  if (leafToggle) showLeaves = leafToggle.checked;
1005
1186
  var layers = {
1006
- definition: layerEnabled('definition'),
1007
- measurement: layerEnabled('measurement')
1187
+ definition: currentView === 'boundary',
1188
+ measurement: currentView === 'evidence'
1008
1189
  };
1009
1190
  map.querySelectorAll('[data-sm-layer]').forEach(function(el){
1010
1191
  var layer = el.getAttribute('data-sm-layer');
1011
1192
  var isLeaf = el.getAttribute('data-sm-leaf') === '1';
1012
1193
  var overflowGroup = el.getAttribute('data-sm-overflow-group');
1013
1194
  var shouldHide = !layers[layer] || (isLeaf && !showLeaves) || !overflowGroupExpanded(overflowGroup);
1195
+ el.setAttribute('data-sm-filter-hidden', shouldHide ? '1' : '0');
1014
1196
  el.hidden = shouldHide;
1015
1197
  el.toggleAttribute('hidden', shouldHide);
1016
1198
  el.setAttribute('aria-hidden', shouldHide ? 'true' : 'false');
1017
1199
  el.style.display = shouldHide ? 'none' : '';
1018
1200
  });
1201
+ map.querySelectorAll('[data-sm-edge-to]').forEach(function(edge){
1202
+ if (edge.getAttribute('data-sm-evidence-edge') === '1') return;
1203
+ var toId = edge.getAttribute('data-sm-edge-to');
1204
+ var fromId = edge.getAttribute('data-sm-edge-from-node');
1205
+ var toNode = toId ? graph.querySelector('[data-sm-node-id="' + toId + '"]') : null;
1206
+ var fromNode = fromId ? graph.querySelector('[data-sm-node-id="' + fromId + '"]') : null;
1207
+ var filterHidden = edge.getAttribute('data-sm-filter-hidden') === '1';
1208
+ var endpointHidden = (toNode && toNode.hidden) || (fromNode && fromNode.hidden);
1209
+ var shouldHideEdge = filterHidden || endpointHidden;
1210
+ edge.hidden = shouldHideEdge;
1211
+ edge.toggleAttribute('hidden', shouldHideEdge);
1212
+ edge.setAttribute('aria-hidden', shouldHideEdge ? 'true' : 'false');
1213
+ edge.style.display = shouldHideEdge ? 'none' : '';
1214
+ });
1215
+ if (selectedNodeId) {
1216
+ var selectedNode = graph.querySelector('[data-sm-node-id="' + selectedNodeId + '"]');
1217
+ if (selectedNode && selectedNode.hidden) {
1218
+ var rootNode = graph.querySelector('[data-sm-root]');
1219
+ if (rootNode) setSelectedNode(rootNode);
1220
+ }
1221
+ }
1222
+ updateEvidenceFocus();
1223
+ }
1224
+
1225
+ function setView(view){
1226
+ currentView = view === 'evidence' ? 'evidence' : 'boundary';
1227
+ applyViewPositions(currentView);
1228
+ updateViewButtons();
1229
+ updateVisibility();
1230
+ refreshEdges();
1231
+ centerRoot();
1019
1232
  }
1020
1233
 
1021
1234
  function setMoreExpanded(toggle, expanded){
@@ -1023,11 +1236,11 @@ const SKILL_MAP_INIT_SCRIPT = `
1023
1236
  var nextLabel = expanded
1024
1237
  ? toggle.getAttribute('data-sm-expanded-label')
1025
1238
  : toggle.getAttribute('data-sm-collapsed-label');
1026
- var title = toggle.querySelector('.sm-node-title');
1027
- if (title && nextLabel) title.textContent = nextLabel;
1028
1239
  if (nextLabel) toggle.setAttribute('title', nextLabel);
1240
+ if (nextLabel) toggle.setAttribute('aria-label', nextLabel);
1029
1241
  updateVisibility();
1030
1242
  refreshEdges();
1243
+ if (selectedNodeId && toggle.getAttribute('data-sm-node-id') === selectedNodeId) setSelectedNode(toggle);
1031
1244
  }
1032
1245
 
1033
1246
  function collapseMoreGroups(){
@@ -1051,7 +1264,13 @@ const SKILL_MAP_INIT_SCRIPT = `
1051
1264
  });
1052
1265
  });
1053
1266
 
1054
- map.querySelectorAll('[data-sm-toggle-layer], [data-sm-toggle-leaves]').forEach(function(input){
1267
+ map.querySelectorAll('[data-sm-view-button]').forEach(function(button){
1268
+ button.addEventListener('click', function(){
1269
+ setView(button.getAttribute('data-sm-view-button') || 'boundary');
1270
+ });
1271
+ });
1272
+
1273
+ map.querySelectorAll('[data-sm-toggle-leaves]').forEach(function(input){
1055
1274
  input.addEventListener('change', updateVisibility);
1056
1275
  });
1057
1276
 
@@ -1062,11 +1281,13 @@ const SKILL_MAP_INIT_SCRIPT = `
1062
1281
  return;
1063
1282
  }
1064
1283
  ev.preventDefault();
1284
+ setSelectedNode(toggle);
1065
1285
  setMoreExpanded(toggle, toggle.getAttribute('aria-expanded') !== 'true');
1066
1286
  });
1067
1287
  toggle.addEventListener('keydown', function(ev){
1068
1288
  if (ev.key !== 'Enter' && ev.key !== ' ') return;
1069
1289
  ev.preventDefault();
1290
+ setSelectedNode(toggle);
1070
1291
  setMoreExpanded(toggle, toggle.getAttribute('aria-expanded') !== 'true');
1071
1292
  });
1072
1293
  });
@@ -1078,6 +1299,7 @@ const SKILL_MAP_INIT_SCRIPT = `
1078
1299
  var nodeId = node.getAttribute('data-sm-node-id');
1079
1300
  if (!nodeId) return;
1080
1301
  ev.preventDefault();
1302
+ setSelectedNode(node);
1081
1303
  dragState = {
1082
1304
  node: node,
1083
1305
  nodeId: nodeId,
@@ -1126,6 +1348,19 @@ const SKILL_MAP_INIT_SCRIPT = `
1126
1348
  node.addEventListener('pointercancel', finishDrag);
1127
1349
  });
1128
1350
 
1351
+ graph.querySelectorAll('[data-sm-node-id]').forEach(function(node){
1352
+ node.addEventListener('click', function(){
1353
+ if (node.hasAttribute('data-sm-more-toggle') && Date.now() < suppressMoreClickUntil) return;
1354
+ setSelectedNode(node);
1355
+ });
1356
+ node.addEventListener('keydown', function(ev){
1357
+ if (ev.key !== 'Enter' && ev.key !== ' ') return;
1358
+ if (node.hasAttribute('data-sm-more-toggle')) return;
1359
+ ev.preventDefault();
1360
+ setSelectedNode(node);
1361
+ });
1362
+ });
1363
+
1129
1364
  viewport.addEventListener('wheel', function(ev){
1130
1365
  if (!ev.ctrlKey && !ev.metaKey) return;
1131
1366
  ev.preventDefault();
@@ -1134,8 +1369,11 @@ const SKILL_MAP_INIT_SCRIPT = `
1134
1369
  setZoom(zoom + delta, ev.clientX - rect.left, ev.clientY - rect.top);
1135
1370
  }, { passive: false });
1136
1371
 
1372
+ updateViewButtons();
1137
1373
  applyZoom();
1138
1374
  updateVisibility();
1375
+ var initialRoot = graph.querySelector('[data-sm-root]');
1376
+ if (initialRoot) setSelectedNode(initialRoot);
1139
1377
  requestAnimationFrame(function(){
1140
1378
  refreshEdges();
1141
1379
  centerRoot();
@@ -1585,6 +1823,7 @@ function nodeKindLabel(kind, lang) {
1585
1823
  eval_result: ['eval', 'eval'],
1586
1824
  trace_session: ['observe', 'observe'],
1587
1825
  more: ['more', 'more'],
1826
+ knowledge_group: ['group', 'group'],
1588
1827
  };
1589
1828
  const pair = labels[kind] ?? [kind, kind];
1590
1829
  return zh ? pair[0] : pair[1];
@@ -1628,47 +1867,392 @@ function previewNodes(nodes, allowedKinds, limit) {
1628
1867
  const SKILL_MAP_WIDTH = 1100;
1629
1868
  const SKILL_MAP_HEIGHT = 560;
1630
1869
  const SKILL_MAP_ROOT_ID = 'skill-root';
1631
- const SKILL_MAP_ROOT = { x: 470, y: 260 };
1870
+ const SKILL_MAP_BOUNDARY_ROOT = { x: 560, y: 285 };
1871
+ const SKILL_MAP_EVIDENCE_ROOT = { x: 470, y: 260 };
1632
1872
  const SKILL_MAP_ROOT_SIZE = { width: 172, height: 86 };
1633
1873
  const SKILL_MAP_NODE_SIZE = { width: 148, height: 54 };
1634
- const SKILL_MAP_DEFINITION_COORDS = [
1635
- { x: 170, y: 112 },
1636
- { x: 140, y: 255 },
1637
- { x: 220, y: 420 },
1638
- { x: 392, y: 112 },
1639
- { x: 382, y: 425 },
1640
- { x: 312, y: 190 },
1641
- { x: 310, y: 328 },
1874
+ const SKILL_MAP_DEFINITION_GROUPS = [
1875
+ {
1876
+ key: 'references',
1877
+ labelZh: '引用材料',
1878
+ labelEn: 'References',
1879
+ descriptionZh: 'Skill 显式依赖的参考文档和知识来源。',
1880
+ descriptionEn: 'Reference docs and knowledge sources the Skill depends on.',
1881
+ nodeKinds: ['reference'],
1882
+ boundaryPosition: { x: 270, y: 225 },
1883
+ evidencePosition: { x: 140, y: 145 },
1884
+ childBoundaryPositions: [
1885
+ { x: 135, y: 125 },
1886
+ { x: 130, y: 325 },
1887
+ { x: 270, y: 435 },
1888
+ { x: 410, y: 125 },
1889
+ { x: 405, y: 325 },
1890
+ ],
1891
+ },
1892
+ {
1893
+ key: 'workflow',
1894
+ labelZh: '执行流程',
1895
+ labelEn: 'Workflow',
1896
+ descriptionZh: 'Skill 期望代理遵循的流程与步骤。',
1897
+ descriptionEn: 'Workflows and steps the agent is expected to follow.',
1898
+ nodeKinds: ['workflow', 'workflow_node'],
1899
+ boundaryPosition: { x: 850, y: 225 },
1900
+ evidencePosition: { x: 960, y: 145 },
1901
+ childBoundaryPositions: [
1902
+ { x: 990, y: 125 },
1903
+ { x: 990, y: 325 },
1904
+ { x: 850, y: 435 },
1905
+ { x: 710, y: 125 },
1906
+ { x: 715, y: 325 },
1907
+ ],
1908
+ },
1909
+ {
1910
+ key: 'rules',
1911
+ labelZh: '约束规则',
1912
+ labelEn: 'Rules',
1913
+ descriptionZh: 'Skill 中不可违反的硬规则和输出约束。',
1914
+ descriptionEn: 'Hard rules and output constraints that must not be violated.',
1915
+ nodeKinds: ['hard_rule'],
1916
+ boundaryPosition: { x: 560, y: 105 },
1917
+ evidencePosition: { x: 140, y: 285 },
1918
+ childBoundaryPositions: [
1919
+ { x: 360, y: 80 },
1920
+ { x: 760, y: 80 },
1921
+ { x: 560, y: 30 },
1922
+ { x: 360, y: 160 },
1923
+ { x: 760, y: 160 },
1924
+ ],
1925
+ },
1642
1926
  ];
1643
- const SKILL_MAP_MEASUREMENT_COORDS = [
1644
- { x: 720, y: 105 },
1645
- { x: 920, y: 165 },
1646
- { x: 750, y: 275 },
1647
- { x: 948, y: 318 },
1648
- { x: 770, y: 430 },
1649
- { x: 960, y: 430 },
1927
+ const SKILL_MAP_BOUNDARY_ASSET_COORDS = [
1928
+ { x: 380, y: 455 },
1929
+ { x: 740, y: 455 },
1930
+ { x: 205, y: 455 },
1931
+ { x: 915, y: 455 },
1932
+ { x: 560, y: 510 },
1933
+ ];
1934
+ const SKILL_MAP_EVIDENCE_DEFINITION_COORDS = [
1935
+ { x: 145, y: 145 },
1936
+ { x: 145, y: 285 },
1937
+ { x: 145, y: 425 },
1938
+ { x: 955, y: 145 },
1939
+ { x: 955, y: 285 },
1940
+ { x: 955, y: 425 },
1941
+ { x: 560, y: 485 },
1942
+ ];
1943
+ const SKILL_MAP_EVIDENCE_MEASUREMENT_COORDS = [
1944
+ { x: 320, y: 140 },
1945
+ { x: 560, y: 115 },
1946
+ { x: 800, y: 150 },
1947
+ { x: 345, y: 360 },
1948
+ { x: 775, y: 365 },
1949
+ { x: 560, y: 455 },
1650
1950
  ];
1651
1951
  const SKILL_MAP_NODE_MARGIN = { x: 92, y: 48 };
1652
1952
  const SKILL_MAP_STATUS_CLASSES = new Set(['ok', 'warning', 'failed', 'skipped', 'unknown', 'not_measured']);
1653
1953
  function graphNodeStatusClass(node) {
1654
1954
  return node.status && SKILL_MAP_STATUS_CLASSES.has(node.status) ? ` sm-node--${node.status}` : '';
1655
1955
  }
1956
+ function graphNodeCoverageClass(node) {
1957
+ if (node.coverage === 'declared')
1958
+ return ' sm-node--coverage-declared';
1959
+ if (node.coverage === 'undeclared')
1960
+ return ' sm-node--coverage-undeclared';
1961
+ return '';
1962
+ }
1963
+ function graphNodeCoverageLabel(node, lang) {
1964
+ const groupSummary = skillMapGroupSummary(node);
1965
+ if (groupSummary) {
1966
+ return lang === 'zh'
1967
+ ? `${groupSummary.declared}/${groupSummary.total} 个节点已被评测用例声明`
1968
+ : `${groupSummary.declared}/${groupSummary.total} nodes declared by eval samples`;
1969
+ }
1970
+ if (node.coverage === 'declared')
1971
+ return lang === 'zh' ? '已由评测用例声明' : 'declared by eval samples';
1972
+ if (node.coverage === 'undeclared')
1973
+ return lang === 'zh' ? '尚未由评测用例声明' : 'not declared by eval samples';
1974
+ return '';
1975
+ }
1976
+ function graphNodeEvidenceLabel(node, lang) {
1977
+ const zh = lang === 'zh';
1978
+ const groupSummary = skillMapGroupSummary(node);
1979
+ if (groupSummary) {
1980
+ return zh
1981
+ ? `包含 ${groupSummary.total} 个结构节点,其中 ${groupSummary.declared} 个已有评测用例声明。`
1982
+ : `Contains ${groupSummary.total} structure nodes; ${groupSummary.declared} are declared by eval samples.`;
1983
+ }
1984
+ if (node.coveredBySamples?.length) {
1985
+ const samples = node.coveredBySamples.slice(0, 3).join(zh ? '、' : ', ');
1986
+ const more = node.coveredBySamples.length > 3 ? (zh ? ` 等 ${node.coveredBySamples.length} 条用例` : ` and ${node.coveredBySamples.length} samples`) : '';
1987
+ return zh ? `由 ${samples}${more} 声明覆盖` : `Declared by ${samples}${more}`;
1988
+ }
1989
+ if (node.coverage === 'undeclared')
1990
+ return zh ? '暂无评测用例显式声明覆盖' : 'No eval sample explicitly declares this node yet';
1991
+ if (node.status === 'failed')
1992
+ return zh ? '当前测量证据失败' : 'Current measurement evidence failed';
1993
+ if (node.status === 'warning')
1994
+ return zh ? '当前测量证据有告警' : 'Current measurement evidence has warnings';
1995
+ if (node.nodeKind === 'sample')
1996
+ return zh ? '这条用例用于测量当前 Skill' : 'This sample measures the current Skill';
1997
+ if (node.nodeKind === 'assertion')
1998
+ return zh ? '断言用于把用例输出转成可比较证据' : 'Assertion turns sample output into comparable evidence';
1999
+ return '';
2000
+ }
2001
+ function graphNodeActionLabel(node, lang) {
2002
+ const zh = lang === 'zh';
2003
+ if (node.nodeKind === 'knowledge_group') {
2004
+ return zh
2005
+ ? '点击分组展开叶子节点;优先检查未声明或失败证据集中的节点。'
2006
+ : 'Open the group to inspect leaves; prioritize undeclared nodes or nodes linked to failures.';
2007
+ }
2008
+ if (node.status === 'failed' && node.nodeKind === 'sample') {
2009
+ return zh ? '优先查看这条失败用例的输出,确认是 Skill 缺知识、流程缺口还是断言过严。' : 'Inspect this failed sample first and decide whether the Skill, workflow, or assertion needs work.';
2010
+ }
2011
+ if (node.status === 'failed' && node.nodeKind === 'assertion') {
2012
+ return zh ? '回到对应用例,检查失败断言期望是否与 Skill 边界一致。' : 'Go back to the sample and verify whether the failed assertion matches the Skill boundary.';
2013
+ }
2014
+ if (node.coverage === 'undeclared') {
2015
+ return zh ? '如果这是关键知识边界,给代表性评测用例补 `sample.covers`;否则可保持未声明。' : 'If this is a key boundary, add sample.covers to a representative eval sample; otherwise leave it undeclared.';
2016
+ }
2017
+ if (node.coveredBySamples?.length) {
2018
+ return zh ? '优先看覆盖它的用例是否失败;失败集中时,回到这条知识边界修 Skill。' : 'Check whether samples covering this node fail; clustered failures point back to this Skill boundary.';
2019
+ }
2020
+ return '';
2021
+ }
2022
+ function graphNodeDetailKindLabel(nodeKind, lang) {
2023
+ const zh = lang === 'zh';
2024
+ const labels = {
2025
+ skill: ['Skill 根节点', 'Skill root'],
2026
+ skill_file: ['Skill 文件', 'Skill file'],
2027
+ frontmatter: ['Frontmatter', 'Frontmatter'],
2028
+ reference: ['引用文档', 'Reference'],
2029
+ script: ['脚本', 'Script'],
2030
+ tool: ['工具', 'Tool'],
2031
+ hard_rule: ['硬规则', 'Hard rule'],
2032
+ workflow: ['流程', 'Workflow'],
2033
+ workflow_node: ['流程节点', 'Workflow node'],
2034
+ sample: ['评测用例', 'Eval sample'],
2035
+ assertion: ['断言', 'Assertion'],
2036
+ eval_result: ['评测结果', 'Eval result'],
2037
+ diagnostic: ['诊断', 'Diagnostic'],
2038
+ more: ['折叠分组', 'Collapsed group'],
2039
+ knowledge_group: ['知识分组', 'Knowledge group'],
2040
+ };
2041
+ const pair = labels[nodeKind] ?? [nodeKind, nodeKind];
2042
+ return zh ? pair[0] : pair[1];
2043
+ }
2044
+ function graphNodeStatusLabel(status, lang) {
2045
+ if (!status)
2046
+ return '';
2047
+ const zh = lang === 'zh';
2048
+ const labels = {
2049
+ ok: ['正常', 'ok'],
2050
+ warning: ['告警', 'warning'],
2051
+ failed: ['失败', 'failed'],
2052
+ skipped: ['已跳过', 'skipped'],
2053
+ unknown: ['未知', 'unknown'],
2054
+ not_measured: ['未测量', 'not measured'],
2055
+ };
2056
+ const pair = labels[status];
2057
+ if (!pair)
2058
+ return '';
2059
+ return zh ? pair[0] : pair[1];
2060
+ }
2061
+ function skillMapLayerLabel(layer, lang) {
2062
+ if (layer === 'definition')
2063
+ return lang === 'zh' ? '结构定义' : 'Definition';
2064
+ return lang === 'zh' ? '评测测量' : 'Measurement';
2065
+ }
2066
+ function skillMapGroupSummary(node) {
2067
+ return node.groupSummary;
2068
+ }
2069
+ function skillMapGroupDescription(node) {
2070
+ return node.groupDescription ?? '';
2071
+ }
2072
+ function graphNodeDetailSource(node, lang) {
2073
+ const zh = lang === 'zh';
2074
+ switch (node.nodeKind) {
2075
+ case 'knowledge_group':
2076
+ return skillMapGroupDescription(node);
2077
+ case 'reference':
2078
+ case 'script':
2079
+ return node.label;
2080
+ case 'workflow':
2081
+ return `${zh ? 'workflow id' : 'workflow id'}: ${node.label}`;
2082
+ case 'workflow_node':
2083
+ return `${zh ? 'workflow node' : 'workflow node'}: ${node.label}`;
2084
+ case 'hard_rule':
2085
+ return `${zh ? 'rule id' : 'rule id'}: ${node.label}`;
2086
+ case 'sample':
2087
+ return `${zh ? 'sample_id' : 'sample_id'}: ${node.label}`;
2088
+ case 'assertion':
2089
+ case 'diagnostic':
2090
+ case 'eval_result':
2091
+ case 'tool':
2092
+ case 'frontmatter':
2093
+ return node.label;
2094
+ default:
2095
+ return '';
2096
+ }
2097
+ }
2098
+ function attr(name, value) {
2099
+ return value ? ` ${name}="${e(value)}"` : '';
2100
+ }
2101
+ function renderSkillMapDetailAttrs(item, lang) {
2102
+ const coverageLabel = graphNodeCoverageLabel(item.node, lang);
2103
+ const isMore = item.node.nodeKind === 'more';
2104
+ const isGroup = item.node.nodeKind === 'knowledge_group';
2105
+ const groupSummary = skillMapGroupSummary(item.node);
2106
+ const scope = skillMapLayerLabel(item.layer, lang);
2107
+ const detailTitle = item.presentation?.detailTitle ?? item.presentation?.title;
2108
+ const status = isMore
2109
+ ? (lang === 'zh' ? '已收起' : 'collapsed')
2110
+ : isGroup && groupSummary
2111
+ ? (lang === 'zh' ? `${groupSummary.total} 个节点` : `${groupSummary.total} nodes`)
2112
+ : graphNodeStatusLabel(item.node.status, lang);
2113
+ return [
2114
+ attr('data-sm-detail-kind', graphNodeDetailKindLabel(item.node.nodeKind, lang)),
2115
+ attr('data-sm-detail-title', isMore ? item.node.label : detailTitle ?? graphNodeDisplayLabel(item.node, lang)),
2116
+ attr('data-sm-detail-subtitle', isMore ? scope : graphNodeDetailSource(item.node, lang)),
2117
+ attr('data-sm-detail-scope', scope),
2118
+ attr('data-sm-detail-source', graphNodeDetailSource(item.node, lang)),
2119
+ attr('data-sm-detail-coverage', coverageLabel),
2120
+ attr('data-sm-detail-status', status),
2121
+ attr('data-sm-detail-evidence', graphNodeEvidenceLabel(item.node, lang)),
2122
+ attr('data-sm-detail-action', graphNodeActionLabel(item.node, lang)),
2123
+ ...(item.moreToggle ? [
2124
+ attr('data-sm-detail-collapsed-status', lang === 'zh' ? '已收起' : 'collapsed'),
2125
+ attr('data-sm-detail-expanded-status', lang === 'zh' ? '已展开' : 'expanded'),
2126
+ ] : []),
2127
+ ].join('');
2128
+ }
2129
+ function graphNodeSignalLabel(node, lang) {
2130
+ const zh = lang === 'zh';
2131
+ const groupSummary = skillMapGroupSummary(node);
2132
+ if (groupSummary)
2133
+ return zh ? `${groupSummary.declared}/${groupSummary.total} 已声明` : `${groupSummary.declared}/${groupSummary.total} declared`;
2134
+ if (node.status === 'failed')
2135
+ return zh ? '失败证据' : 'failed';
2136
+ if (node.status === 'warning')
2137
+ return zh ? '告警' : 'warning';
2138
+ if (node.coverage === 'declared')
2139
+ return zh ? '已声明覆盖' : 'declared';
2140
+ if (node.coverage === 'undeclared')
2141
+ return zh ? '未声明' : 'undeclared';
2142
+ return '';
2143
+ }
2144
+ function graphNodeSignalClass(node) {
2145
+ const groupSummary = skillMapGroupSummary(node);
2146
+ if (groupSummary)
2147
+ return groupSummary.declared === groupSummary.total ? ' sm-node-signal--declared' : ' sm-node-signal--undeclared';
2148
+ if (node.status === 'failed')
2149
+ return ' sm-node-signal--failed';
2150
+ if (node.status === 'warning')
2151
+ return ' sm-node-signal--warning';
2152
+ if (node.coverage === 'declared')
2153
+ return ' sm-node-signal--declared';
2154
+ if (node.coverage === 'undeclared')
2155
+ return ' sm-node-signal--undeclared';
2156
+ return '';
2157
+ }
2158
+ function skillMapRootDetail(entry, coverageSummary, lang) {
2159
+ const zh = lang === 'zh';
2160
+ const failedSamples = entry.eval?.failCount ?? 0;
2161
+ return {
2162
+ kindLabel: graphNodeDetailKindLabel('skill', lang),
2163
+ title: 'SKILL.md',
2164
+ subtitle: entry.skillName,
2165
+ scope: zh ? '中心节点' : 'Root',
2166
+ source: 'SKILL.md',
2167
+ coverage: coverageSummary ?? '',
2168
+ status: [
2169
+ `doctor ${stageText(entry, 'doctor', lang)}`,
2170
+ `eval ${stageText(entry, 'eval', lang)}`,
2171
+ `observe ${stageText(entry, 'observe', lang)}`,
2172
+ ].join(' · '),
2173
+ evidence: coverageSummary ?? (zh ? '尚未形成声明锚点' : 'No declared anchors yet'),
2174
+ action: failedSamples > 0
2175
+ ? (zh ? `先处理 ${failedSamples} 条失败评测用例,再回看其覆盖的知识边界。` : `Start with ${failedSamples} failed eval samples, then inspect the covered Skill boundaries.`)
2176
+ : (zh ? '继续补充关键场景的 sample.covers,让图谱从结构图变成证据图。' : 'Keep adding sample.covers for key scenarios so the map becomes evidence-oriented.'),
2177
+ };
2178
+ }
2179
+ function renderSkillMapRootDetailAttrs(entry, coverageSummary, lang) {
2180
+ const detail = skillMapRootDetail(entry, coverageSummary, lang);
2181
+ return [
2182
+ attr('data-sm-detail-kind', detail.kindLabel),
2183
+ attr('data-sm-detail-title', detail.title),
2184
+ attr('data-sm-detail-subtitle', detail.subtitle),
2185
+ attr('data-sm-detail-scope', detail.scope),
2186
+ attr('data-sm-detail-source', detail.source),
2187
+ attr('data-sm-detail-coverage', detail.coverage),
2188
+ attr('data-sm-detail-status', detail.status),
2189
+ attr('data-sm-detail-evidence', detail.evidence),
2190
+ attr('data-sm-detail-action', detail.action),
2191
+ ].join('');
2192
+ }
2193
+ function renderSkillMapInspector(entry, coverageSummary, lang) {
2194
+ const zh = lang === 'zh';
2195
+ const detail = skillMapRootDetail(entry, coverageSummary, lang);
2196
+ const row = (key, label, value, required = false) => {
2197
+ const hidden = !value && !required ? ' hidden' : '';
2198
+ return `<div class="sm-detail-row" data-sm-detail-row="${e(key)}"${hidden}>
2199
+ <dt>${e(label)}</dt>
2200
+ <dd data-sm-detail-value="${e(key)}">${e(value || '—')}</dd>
2201
+ </div>`;
2202
+ };
2203
+ return `<div class="sm-detail" data-sm-detail>
2204
+ <div class="sm-detail-head">
2205
+ <div class="sm-detail-kind" data-sm-detail-kind-label>${e(detail.kindLabel)}</div>
2206
+ <div class="sm-detail-title" data-sm-detail-title>${e(detail.title)}</div>
2207
+ <div class="sm-detail-subtitle" data-sm-detail-subtitle>${e(detail.subtitle)}</div>
2208
+ </div>
2209
+ <dl class="sm-detail-list">
2210
+ ${row('scope', zh ? '范围' : 'Scope', detail.scope, true)}
2211
+ ${row('source', zh ? '来源' : 'Source', detail.source)}
2212
+ ${row('coverage', zh ? '声明' : 'Declaration', detail.coverage)}
2213
+ ${row('status', zh ? '状态' : 'Status', detail.status)}
2214
+ ${row('evidence', zh ? '证据' : 'Evidence', detail.evidence)}
2215
+ ${row('action', zh ? '下一步' : 'Next', detail.action)}
2216
+ </dl>
2217
+ </div>`;
2218
+ }
1656
2219
  function positionedNodeStyle(node) {
1657
2220
  return `left:${node.x}px;top:${node.y}px`;
1658
2221
  }
2222
+ function renderSkillMapViewPositionAttrs(item) {
2223
+ const attrs = [];
2224
+ if (item.boundaryPosition) {
2225
+ attrs.push(` data-sm-boundary-x="${item.boundaryPosition.x}" data-sm-boundary-y="${item.boundaryPosition.y}"`);
2226
+ }
2227
+ if (item.evidencePosition) {
2228
+ attrs.push(` data-sm-evidence-x="${item.evidencePosition.x}" data-sm-evidence-y="${item.evidencePosition.y}"`);
2229
+ }
2230
+ return attrs.join('');
2231
+ }
1659
2232
  function renderSkillMapNode(item, lang) {
1660
- const title = graphNodeMapLabel(item.node);
1661
- const fullTitle = graphNodeDisplayLabel(item.node, lang);
1662
- const kindClass = item.node.nodeKind === 'more' ? ' sm-node--more' : '';
1663
- const leaf = item.layer === 'definition' || item.layer === 'measurement' ? ' data-sm-leaf="1"' : '';
2233
+ const title = item.presentation?.title ?? graphNodeMapLabel(item.node);
2234
+ const coverageLabel = graphNodeCoverageLabel(item.node, lang);
2235
+ const fullTitle = [
2236
+ item.presentation?.title,
2237
+ graphNodeDisplayLabel(item.node, lang),
2238
+ coverageLabel,
2239
+ ].filter((value, index, values) => Boolean(value) && values.indexOf(value) === index).join(' · ');
2240
+ const signal = graphNodeSignalLabel(item.node, lang);
2241
+ const kindLabel = item.presentation?.hideKind || item.node.nodeKind === 'knowledge_group' ? '' : nodeKindLabel(item.node.nodeKind, lang);
2242
+ const kindClass = item.node.nodeKind === 'more' ? ' sm-node--more' : item.node.nodeKind === 'knowledge_group' ? ' sm-node--group' : '';
2243
+ const isLeaf = item.isLeaf !== false;
2244
+ const leaf = isLeaf ? ' data-sm-leaf="1"' : '';
1664
2245
  const draggable = item.layer === 'definition' || item.layer === 'measurement' ? ' data-sm-draggable="1"' : '';
1665
2246
  const overflow = item.overflowGroup ? ` data-sm-overflow-group="${item.overflowGroup}" hidden aria-hidden="true"` : '';
2247
+ const toggleIcon = item.moreToggle ? '<span class="sm-node-toggle-icon" aria-hidden="true"></span>' : '';
1666
2248
  const moreToggle = item.moreToggle
1667
2249
  ? ` data-sm-more-toggle="${item.moreToggle.group}" data-sm-collapsed-label="${e(item.moreToggle.collapsedLabel)}" data-sm-expanded-label="${e(item.moreToggle.expandedLabel)}" role="button" tabindex="0" aria-expanded="false"`
1668
- : '';
1669
- return `<div class="sm-node sm-node--${item.layer}${kindClass}${graphNodeStatusClass(item.node)}" data-sm-node-id="${e(item.id)}" data-sm-layer="${item.layer}" data-sm-x="${item.x}" data-sm-y="${item.y}" data-sm-origin-x="${item.x}" data-sm-origin-y="${item.y}"${leaf}${draggable}${overflow}${moreToggle} style="${positionedNodeStyle(item)}" title="${e(fullTitle)}">
1670
- <div class="sm-node-kind">${e(nodeKindLabel(item.node.nodeKind, lang))}</div>
2250
+ : ' role="button" tabindex="0"';
2251
+ return `<div class="sm-node sm-node--${item.layer}${kindClass}${graphNodeStatusClass(item.node)}${graphNodeCoverageClass(item.node)}" data-sm-node-id="${e(item.id)}" data-sm-layer="${item.layer}" data-sm-x="${item.x}" data-sm-y="${item.y}" data-sm-origin-x="${item.x}" data-sm-origin-y="${item.y}"${renderSkillMapViewPositionAttrs(item)}${leaf}${draggable}${overflow}${moreToggle}${renderSkillMapDetailAttrs(item, lang)} style="${positionedNodeStyle(item)}" title="${e(fullTitle)}" aria-label="${e(fullTitle)}">
2252
+ ${toggleIcon}
2253
+ ${kindLabel ? `<div class="sm-node-kind">${e(kindLabel)}</div>` : ''}
1671
2254
  <div class="sm-node-title">${e(title)}</div>
2255
+ ${signal ? `<div class="sm-node-signal${graphNodeSignalClass(item.node)}">${e(signal)}</div>` : ''}
1672
2256
  </div>`;
1673
2257
  }
1674
2258
  function edgePoint(from, to, box) {
@@ -1687,33 +2271,212 @@ function skillMapPath(from, to, fromBox, toBox) {
1687
2271
  const c2x = end.x - (end.x >= start.x ? dx * 0.45 : -dx * 0.45);
1688
2272
  return `M ${start.x} ${start.y} C ${c1x} ${start.y}, ${c2x} ${end.y}, ${end.x} ${end.y}`;
1689
2273
  }
1690
- function renderSkillMapEdges(nodes, dimensions) {
2274
+ function renderSkillMapEdges(nodes, dimensions, evidenceLinks) {
1691
2275
  const rootEdges = nodes
1692
2276
  .map((node) => {
1693
- const from = node.edgeFrom ?? SKILL_MAP_ROOT;
2277
+ const from = node.edgeFrom ?? SKILL_MAP_BOUNDARY_ROOT;
1694
2278
  const fromBox = node.edgeFrom ? SKILL_MAP_NODE_SIZE : SKILL_MAP_ROOT_SIZE;
1695
2279
  const overflow = node.overflowGroup ? ` data-sm-overflow-group="${node.overflowGroup}" hidden aria-hidden="true"` : '';
1696
- const fromNodeId = node.overflowGroup ? `${node.overflowGroup}-more` : SKILL_MAP_ROOT_ID;
1697
- return `<path class="sm-edge sm-edge--${node.layer}" data-sm-layer="${node.layer}" data-sm-leaf="1" data-sm-edge-to="${e(node.id)}" data-sm-edge-from-node="${e(fromNodeId)}" data-sm-edge-from-x="${from.x}" data-sm-edge-from-y="${from.y}"${overflow} d="${skillMapPath(from, node, fromBox, SKILL_MAP_NODE_SIZE)}"></path>`;
2280
+ const fromNodeId = node.edgeFromId ?? (node.overflowGroup ? `${node.overflowGroup}-more` : SKILL_MAP_ROOT_ID);
2281
+ const leaf = node.isLeaf === false ? '' : ' data-sm-leaf="1"';
2282
+ return `<path class="sm-edge sm-edge--${node.layer}" data-sm-layer="${node.layer}"${leaf} data-sm-edge-to="${e(node.id)}" data-sm-edge-from-node="${e(fromNodeId)}" data-sm-edge-from-x="${from.x}" data-sm-edge-from-y="${from.y}"${overflow} d="${skillMapPath(from, node, fromBox, SKILL_MAP_NODE_SIZE)}"></path>`;
1698
2283
  })
1699
2284
  .join('');
1700
- return `<svg class="sm-svg" viewBox="0 0 ${dimensions.width} ${dimensions.height}" aria-hidden="true">${rootEdges}</svg>`;
2285
+ const evidenceEdges = evidenceLinks
2286
+ .map((link, index) => `<path class="sm-edge sm-edge--coverage${link.failed ? ' sm-edge--coverage-failed' : ''}" data-sm-layer="measurement" data-sm-leaf="1" data-sm-evidence-edge="1" data-sm-edge-to="${e(link.toId)}" data-sm-edge-from-node="${e(link.fromId)}" data-sm-edge-from-x="${link.from.x}" data-sm-edge-from-y="${link.from.y}" d="${skillMapPath(link.from, link.to, SKILL_MAP_NODE_SIZE, SKILL_MAP_NODE_SIZE)}" data-sm-edge-index="${index}" hidden aria-hidden="true"></path>`)
2287
+ .join('');
2288
+ return `<svg class="sm-svg" viewBox="0 0 ${dimensions.width} ${dimensions.height}" aria-hidden="true">${rootEdges}${evidenceEdges}</svg>`;
2289
+ }
2290
+ function coverageSamplesByStableKey(evalGraph, lang) {
2291
+ const zh = lang === 'zh';
2292
+ const out = new Map();
2293
+ for (const edge of evalGraph?.declaredCoverageEdges ?? []) {
2294
+ const samples = out.get(edge.targetStableKey) ?? [];
2295
+ const sampleLabel = edge.sampleStatus === 'failed'
2296
+ ? (zh ? `${edge.sampleLabel}(失败)` : `${edge.sampleLabel} (failed)`)
2297
+ : edge.sampleLabel;
2298
+ if (!samples.includes(sampleLabel))
2299
+ samples.push(sampleLabel);
2300
+ out.set(edge.targetStableKey, samples);
2301
+ }
2302
+ return out;
2303
+ }
2304
+ function skillMapEvidenceLinks(nodes, evalGraph) {
2305
+ if (!evalGraph?.declaredCoverageEdges.length)
2306
+ return [];
2307
+ const targetByStableKey = new Map();
2308
+ const sampleByLabel = new Map();
2309
+ for (const node of nodes) {
2310
+ if (node.node.stableKey)
2311
+ targetByStableKey.set(node.node.stableKey, node);
2312
+ if (node.node.nodeKind === 'sample')
2313
+ sampleByLabel.set(node.node.label, node);
2314
+ }
2315
+ return evalGraph.declaredCoverageEdges
2316
+ .map((edge) => {
2317
+ const from = sampleByLabel.get(edge.sampleLabel);
2318
+ const to = targetByStableKey.get(edge.targetStableKey);
2319
+ if (!from || !to)
2320
+ return null;
2321
+ return {
2322
+ fromId: from.id,
2323
+ toId: to.id,
2324
+ from: { x: from.x, y: from.y },
2325
+ to: { x: to.x, y: to.y },
2326
+ ...(edge.sampleStatus === 'failed' ? { failed: true } : {}),
2327
+ };
2328
+ })
2329
+ .filter((link) => Boolean(link));
1701
2330
  }
1702
2331
  function moreNode(label) {
1703
2332
  return { nodeKind: 'more', label };
1704
2333
  }
1705
- function overflowNodePosition(layer, index) {
1706
- const columns = layer === 'definition' ? [112, 270, 428] : [720, 880, 1040];
1707
- const startY = layer === 'definition' ? 500 : 560;
2334
+ function overflowNodePosition(layer, index, view) {
2335
+ const columns = layer === 'definition'
2336
+ ? view === 'boundary' ? [205, 382, 560, 738, 915] : [120, 290, 810, 980]
2337
+ : [250, 430, 610, 790, 970];
2338
+ const startY = layer === 'definition'
2339
+ ? view === 'boundary' ? 570 : 535
2340
+ : 555;
1708
2341
  return {
1709
2342
  x: columns[index % columns.length],
1710
2343
  y: startY + Math.floor(index / columns.length) * 98,
1711
2344
  };
1712
2345
  }
2346
+ function definitionGroupSpecForNode(node) {
2347
+ return SKILL_MAP_DEFINITION_GROUPS.find((group) => group.nodeKinds.includes(node.nodeKind));
2348
+ }
2349
+ function definitionGroupLabel(group, lang) {
2350
+ return lang === 'zh' ? group.labelZh : group.labelEn;
2351
+ }
2352
+ function definitionGroupDescription(group, lang) {
2353
+ return lang === 'zh' ? group.descriptionZh : group.descriptionEn;
2354
+ }
2355
+ function knowledgeGroupNode(group, children, lang) {
2356
+ const declared = children.filter((node) => node.coverage === 'declared').length;
2357
+ return {
2358
+ nodeKind: 'knowledge_group',
2359
+ label: definitionGroupLabel(group, lang),
2360
+ groupDescription: definitionGroupDescription(group, lang),
2361
+ groupSummary: {
2362
+ total: children.length,
2363
+ declared,
2364
+ },
2365
+ };
2366
+ }
2367
+ function definitionGroupChildPosition(group, index) {
2368
+ const position = group.childBoundaryPositions[index];
2369
+ if (position)
2370
+ return position;
2371
+ const columns = group.key === 'references'
2372
+ ? [120, 290, 460]
2373
+ : group.key === 'workflow'
2374
+ ? [660, 830, 1000]
2375
+ : [250, 405, 560, 715, 870];
2376
+ const startY = group.key === 'rules' ? 190 : 500;
2377
+ return {
2378
+ x: columns[index % columns.length],
2379
+ y: startY + Math.floor(index / columns.length) * 92,
2380
+ };
2381
+ }
2382
+ function standaloneAssetPosition(index) {
2383
+ const position = SKILL_MAP_BOUNDARY_ASSET_COORDS[index];
2384
+ if (position)
2385
+ return position;
2386
+ const columns = [205, 380, 560, 740, 915];
2387
+ return {
2388
+ x: columns[index % columns.length],
2389
+ y: 610 + Math.floor(index / columns.length) * 92,
2390
+ };
2391
+ }
2392
+ function standaloneAssetPresentation(node, lang) {
2393
+ const zh = lang === 'zh';
2394
+ if (node.nodeKind === 'script') {
2395
+ return {
2396
+ title: zh ? '执行脚本' : 'Script',
2397
+ detailTitle: zh ? '执行脚本' : 'Script',
2398
+ hideKind: true,
2399
+ };
2400
+ }
2401
+ if (node.nodeKind === 'frontmatter') {
2402
+ return {
2403
+ title: zh ? '元信息' : 'Metadata',
2404
+ detailTitle: zh ? '元信息' : 'Metadata',
2405
+ hideKind: true,
2406
+ };
2407
+ }
2408
+ return undefined;
2409
+ }
2410
+ function buildDefinitionMapNodes(candidates, lang) {
2411
+ let evidenceIndex = 0;
2412
+ const groupNodes = SKILL_MAP_DEFINITION_GROUPS.flatMap((group) => {
2413
+ const children = candidates.filter((node) => definitionGroupSpecForNode(node)?.key === group.key);
2414
+ if (children.length === 0)
2415
+ return [];
2416
+ const groupId = `definition-group-${group.key}`;
2417
+ const label = definitionGroupLabel(group, lang);
2418
+ const groupNode = {
2419
+ id: groupId,
2420
+ node: knowledgeGroupNode(group, children, lang),
2421
+ layer: 'definition',
2422
+ x: group.boundaryPosition.x,
2423
+ y: group.boundaryPosition.y,
2424
+ boundaryPosition: group.boundaryPosition,
2425
+ evidencePosition: group.evidencePosition,
2426
+ isLeaf: false,
2427
+ moreToggle: {
2428
+ group: groupId,
2429
+ collapsedLabel: lang === 'zh' ? `展开${label}` : `Expand ${label}`,
2430
+ expandedLabel: lang === 'zh' ? `收起${label}` : `Collapse ${label}`,
2431
+ },
2432
+ };
2433
+ const childNodes = children.map((node, index) => {
2434
+ const boundaryPosition = definitionGroupChildPosition(group, index);
2435
+ const evidencePosition = SKILL_MAP_EVIDENCE_DEFINITION_COORDS[evidenceIndex] ?? overflowNodePosition('definition', evidenceIndex - SKILL_MAP_EVIDENCE_DEFINITION_COORDS.length, 'evidence');
2436
+ evidenceIndex += 1;
2437
+ return {
2438
+ id: `${groupId}-node-${index}`,
2439
+ node,
2440
+ layer: 'definition',
2441
+ edgeFrom: group.boundaryPosition,
2442
+ edgeFromId: groupId,
2443
+ overflowGroup: groupId,
2444
+ x: boundaryPosition.x,
2445
+ y: boundaryPosition.y,
2446
+ boundaryPosition,
2447
+ evidencePosition,
2448
+ };
2449
+ });
2450
+ return [groupNode, ...childNodes];
2451
+ });
2452
+ const standaloneAssets = candidates
2453
+ .filter((node) => node.nodeKind === 'script' || node.nodeKind === 'frontmatter')
2454
+ .map((node, index) => {
2455
+ const boundaryPosition = standaloneAssetPosition(index);
2456
+ const evidencePosition = SKILL_MAP_EVIDENCE_DEFINITION_COORDS[evidenceIndex] ?? overflowNodePosition('definition', evidenceIndex - SKILL_MAP_EVIDENCE_DEFINITION_COORDS.length, 'evidence');
2457
+ evidenceIndex += 1;
2458
+ return {
2459
+ id: `definition-asset-${index}`,
2460
+ node,
2461
+ layer: 'definition',
2462
+ x: boundaryPosition.x,
2463
+ y: boundaryPosition.y,
2464
+ boundaryPosition,
2465
+ evidencePosition,
2466
+ presentation: standaloneAssetPresentation(node, lang),
2467
+ };
2468
+ });
2469
+ return [...groupNodes, ...standaloneAssets];
2470
+ }
1713
2471
  function skillMapDimensions(nodes) {
2472
+ const allPositions = nodes.flatMap((node) => [
2473
+ { x: node.x, y: node.y },
2474
+ ...(node.boundaryPosition ? [node.boundaryPosition] : []),
2475
+ ...(node.evidencePosition ? [node.evidencePosition] : []),
2476
+ ]);
1714
2477
  return {
1715
- width: Math.max(SKILL_MAP_WIDTH, ...nodes.map((node) => node.x + SKILL_MAP_NODE_MARGIN.x)),
1716
- height: Math.max(SKILL_MAP_HEIGHT, ...nodes.map((node) => node.y + SKILL_MAP_NODE_MARGIN.y)),
2478
+ width: Math.max(SKILL_MAP_WIDTH, ...allPositions.map((node) => node.x + SKILL_MAP_NODE_MARGIN.x)),
2479
+ height: Math.max(SKILL_MAP_HEIGHT, ...allPositions.map((node) => node.y + SKILL_MAP_NODE_MARGIN.y)),
1717
2480
  };
1718
2481
  }
1719
2482
  function renderSkillStageRail(entry, lang) {
@@ -1732,6 +2495,24 @@ function renderSkillStageRail(entry, lang) {
1732
2495
  ${card('observe', stageText(entry, 'observe', lang), observeStatus)}
1733
2496
  </div>`;
1734
2497
  }
2498
+ function renderSkillMapEvidenceStrip(entry, doctor, evalGraph, lang) {
2499
+ const zh = lang === 'zh';
2500
+ const failedSamples = entry.eval?.failCount ?? 0;
2501
+ const totalSamples = entry.eval?.totalSamples ?? evalGraph?.samples ?? 0;
2502
+ const declaredKeys = new Set(evalGraph?.declaredCoverageStableKeys ?? []);
2503
+ const coverable = doctor?.definitionNodes.filter((node) => node.stableKey && DEFINITION_PREVIEW_KINDS.has(node.nodeKind)) ?? [];
2504
+ const declaredCount = coverable.filter((node) => node.stableKey && declaredKeys.has(node.stableKey)).length;
2505
+ const undeclaredCount = coverable.filter((node) => node.stableKey && !declaredKeys.has(node.stableKey)).length;
2506
+ const item = (key, value, tone = '') => `<div class="sm-evidence-item${tone ? ` sm-evidence-item--${tone}` : ''}">
2507
+ <div class="sm-evidence-k">${e(key)}</div>
2508
+ <div class="sm-evidence-v">${e(value)}</div>
2509
+ </div>`;
2510
+ return `<div class="sm-evidence-strip" aria-label="${zh ? '图谱诊断摘要' : 'Map diagnosis summary'}">
2511
+ ${item(zh ? '边界覆盖' : 'Boundary coverage', coverable.length > 0 ? `${declaredCount}/${coverable.length} ${zh ? '个结构节点' : 'structure nodes'}` : (zh ? '暂无结构节点' : 'no structure nodes'))}
2512
+ ${item(zh ? '未声明' : 'Undeclared', coverable.length > 0 ? `${undeclaredCount} ${zh ? '个结构节点' : 'structure nodes'}` : (zh ? '暂无' : 'none'), undeclaredCount > 0 ? 'action' : '')}
2513
+ ${item(zh ? '失败证据' : 'Failed evidence', totalSamples > 0 ? `${failedSamples}/${totalSamples} ${zh ? '条评测用例' : 'eval samples'}` : (zh ? '暂无评测用例' : 'no eval samples'), failedSamples > 0 ? 'risk' : '')}
2514
+ </div>`;
2515
+ }
1735
2516
  function orderedPreviewNodes(nodes, allowedKinds, priority) {
1736
2517
  return (nodes ?? [])
1737
2518
  .map((node, index) => ({ node, index }))
@@ -1739,6 +2520,50 @@ function orderedPreviewNodes(nodes, allowedKinds, priority) {
1739
2520
  .sort((a, b) => (priority[a.node.nodeKind] ?? 99) - (priority[b.node.nodeKind] ?? 99) || a.index - b.index)
1740
2521
  .map(({ node }) => node);
1741
2522
  }
2523
+ function measurementStatusPriority(node) {
2524
+ if (node.status === 'failed')
2525
+ return 0;
2526
+ if (node.status === 'warning')
2527
+ return 1;
2528
+ if (node.nodeKind === 'sample')
2529
+ return 2;
2530
+ return 3;
2531
+ }
2532
+ function applyDefinitionCoverageDeclarations(nodes, declaredStableKeys, coveredByStableKey) {
2533
+ if (!nodes)
2534
+ return undefined;
2535
+ if ((!declaredStableKeys || declaredStableKeys.length === 0) && (!coveredByStableKey || coveredByStableKey.size === 0))
2536
+ return nodes;
2537
+ const declared = new Set(declaredStableKeys);
2538
+ return nodes.map((node) => {
2539
+ if (!node.stableKey || !DEFINITION_PREVIEW_KINDS.has(node.nodeKind))
2540
+ return node;
2541
+ const coveredBySamples = coveredByStableKey?.get(node.stableKey) ?? [];
2542
+ return {
2543
+ ...node,
2544
+ coverage: declared.has(node.stableKey) ? 'declared' : 'undeclared',
2545
+ ...(coveredBySamples.length > 0 ? { coveredBySamples } : {}),
2546
+ };
2547
+ });
2548
+ }
2549
+ function skillMapCoverageDeclarationSummary(doctor, evalGraph, lang) {
2550
+ const declaredKeys = new Set(evalGraph?.declaredCoverageStableKeys ?? []);
2551
+ if (!doctor || declaredKeys.size === 0)
2552
+ return null;
2553
+ const coverable = doctor.definitionNodes.filter((node) => node.stableKey && DEFINITION_PREVIEW_KINDS.has(node.nodeKind));
2554
+ if (coverable.length === 0)
2555
+ return null;
2556
+ const declaredCount = coverable.filter((node) => node.stableKey && declaredKeys.has(node.stableKey)).length;
2557
+ const samples = evalGraph?.samples ?? 0;
2558
+ return lang === 'zh'
2559
+ ? `${samples} 条评测用例声明了 ${declaredCount}/${coverable.length} 个结构节点`
2560
+ : `${samples} eval samples declared ${declaredCount}/${coverable.length} structure nodes`;
2561
+ }
2562
+ function skillMapCoverageDeclarationHint(lang) {
2563
+ return lang === 'zh'
2564
+ ? '这些关系来自 sample.covers,表示评测用例显式标注的主要触达节点;尚未声明只表示还没有被评测用例显式标注,不应直接视为测试缺口。'
2565
+ : 'These relationships come from sample.covers and show the main nodes explicitly annotated by eval samples; not declared only means not explicitly annotated yet, not necessarily a test gap.';
2566
+ }
1742
2567
  function renderSkillEvidenceMarkdown(entry, lang) {
1743
2568
  const zh = lang === 'zh';
1744
2569
  const graph = entry.graph;
@@ -1758,7 +2583,8 @@ function renderSkillEvidenceMarkdown(entry, lang) {
1758
2583
  !entry.eval ? `- ${zh ? '测量效果' : 'Measure impact'}${stepSeparator} \`omk eval --control baseline --treatment ${sourceArg}\`` : '',
1759
2584
  !entry.observe ? `- ${zh ? '接入观察' : 'Add observation'}${stepSeparator} \`omk observe ingest <trace-dir>\`` : '',
1760
2585
  ].filter(Boolean);
1761
- const definitionPreview = previewNodes(doctor?.definitionNodes, DEFINITION_PREVIEW_KINDS, 8);
2586
+ const coverageSummary = skillMapCoverageDeclarationSummary(doctor, evalGraph, lang);
2587
+ const definitionPreview = previewNodes(applyDefinitionCoverageDeclarations(doctor?.definitionNodes, evalGraph?.declaredCoverageStableKeys, coverageSamplesByStableKey(evalGraph, lang)), DEFINITION_PREVIEW_KINDS, 8);
1762
2588
  const measurementPreview = previewNodes(evalGraph?.measurementNodes, MEASUREMENT_PREVIEW_KINDS, 8);
1763
2589
  const previewMermaidLines = [
1764
2590
  ...definitionPreview.visible.flatMap((node, index) => [
@@ -1771,7 +2597,7 @@ function renderSkillEvidenceMarkdown(entry, lang) {
1771
2597
  ]),
1772
2598
  ];
1773
2599
  return [
1774
- `## Skill Evidence Card:${entry.skillName}`,
2600
+ `## ${zh ? '知识图谱摘要' : 'Skill Map Summary'}${zh ? ':' : ': '}${entry.skillName}`,
1775
2601
  '',
1776
2602
  summary,
1777
2603
  '',
@@ -1793,6 +2619,7 @@ function renderSkillEvidenceMarkdown(entry, lang) {
1793
2619
  `| doctor | ${doctorStatus} | ${doctor ? `${doctor.nodeCount} nodes / ${doctor.edgeCount} edges` : (zh ? '无 graph' : 'no graph')} |`,
1794
2620
  `| eval | ${evalStatus} | ${evalGraph ? `${zh ? 'variant 子图' : 'variant subgraph'}: ${evalGraph.nodeCount} nodes / ${evalGraph.edgeCount} edges` : (zh ? '无 graph' : 'no graph')} |`,
1795
2621
  `| observe | ${observeStatus} | ${zh ? '未接 production graph' : 'production graph not connected'} |`,
2622
+ ...(coverageSummary ? ['', zh ? '### 声明锚点' : '### Declared Anchors', '', coverageSummary, '', skillMapCoverageDeclarationHint(lang)] : []),
1796
2623
  '',
1797
2624
  zh ? '### 关键计数' : '### Key Counts',
1798
2625
  '',
@@ -1820,7 +2647,10 @@ function renderSkillMapSection(entry, lang) {
1820
2647
  const evalGraph = graph.eval;
1821
2648
  const band = skillMapBand(entry);
1822
2649
  const markdown = renderSkillEvidenceMarkdown(entry, lang);
1823
- const definitionCandidates = orderedPreviewNodes(doctor?.definitionNodes, DEFINITION_PREVIEW_KINDS, {
2650
+ const coverageSamples = coverageSamplesByStableKey(evalGraph, lang);
2651
+ const definitionNodesWithCoverageDeclarations = applyDefinitionCoverageDeclarations(doctor?.definitionNodes, evalGraph?.declaredCoverageStableKeys, coverageSamples);
2652
+ const coverageSummary = skillMapCoverageDeclarationSummary(doctor, evalGraph, lang);
2653
+ const definitionCandidates = orderedPreviewNodes(definitionNodesWithCoverageDeclarations, DEFINITION_PREVIEW_KINDS, {
1824
2654
  reference: 1,
1825
2655
  script: 2,
1826
2656
  workflow: 3,
@@ -1832,53 +2662,27 @@ function renderSkillMapSection(entry, lang) {
1832
2662
  sample: 1,
1833
2663
  diagnostic: 2,
1834
2664
  assertion: 3,
1835
- });
1836
- const definitionLimit = SKILL_MAP_DEFINITION_COORDS.length - 1;
1837
- const measurementLimit = SKILL_MAP_MEASUREMENT_COORDS.length - 1;
1838
- const definitionVisible = definitionCandidates.slice(0, definitionLimit);
1839
- const definitionOverflow = definitionCandidates.slice(definitionLimit);
2665
+ }).sort((a, b) => measurementStatusPriority(a) - measurementStatusPriority(b));
2666
+ const measurementLimit = SKILL_MAP_EVIDENCE_MEASUREMENT_COORDS.length - 1;
1840
2667
  const measurementVisible = measurementCandidates.slice(0, measurementLimit);
1841
2668
  const measurementOverflow = measurementCandidates.slice(measurementLimit);
1842
- const definitionMorePosition = SKILL_MAP_DEFINITION_COORDS[SKILL_MAP_DEFINITION_COORDS.length - 1];
1843
- const measurementMorePosition = SKILL_MAP_MEASUREMENT_COORDS[SKILL_MAP_MEASUREMENT_COORDS.length - 1];
1844
- const definitionNodes = [
1845
- ...definitionVisible.map((node, index) => ({
1846
- id: `definition-${index}`,
1847
- node,
1848
- layer: 'definition',
1849
- ...SKILL_MAP_DEFINITION_COORDS[index],
1850
- })),
1851
- ...(definitionOverflow.length > 0 ? [{
1852
- id: 'definition-more',
1853
- node: moreNode(`+ ${definitionOverflow.length} ${zh ? '更多结构节点' : 'more structure nodes'}`),
1854
- layer: 'definition',
1855
- moreToggle: {
1856
- group: 'definition',
1857
- collapsedLabel: `+ ${definitionOverflow.length} ${zh ? '更多结构节点' : 'more structure nodes'}`,
1858
- expandedLabel: zh ? '收起结构节点' : 'collapse structure nodes',
1859
- },
1860
- ...definitionMorePosition,
1861
- }] : []),
1862
- ...definitionOverflow.map((node, index) => ({
1863
- id: `definition-overflow-${index}`,
1864
- node,
1865
- layer: 'definition',
1866
- edgeFrom: definitionMorePosition,
1867
- overflowGroup: 'definition',
1868
- ...overflowNodePosition('definition', index),
1869
- })),
1870
- ];
2669
+ const measurementMorePosition = SKILL_MAP_EVIDENCE_MEASUREMENT_COORDS[SKILL_MAP_EVIDENCE_MEASUREMENT_COORDS.length - 1];
2670
+ const definitionNodes = buildDefinitionMapNodes(definitionCandidates, lang);
1871
2671
  const measurementNodes = [
1872
2672
  ...measurementVisible.map((node, index) => ({
1873
2673
  id: `measurement-${index}`,
1874
2674
  node,
1875
2675
  layer: 'measurement',
1876
- ...SKILL_MAP_MEASUREMENT_COORDS[index],
2676
+ ...SKILL_MAP_EVIDENCE_MEASUREMENT_COORDS[index],
2677
+ boundaryPosition: SKILL_MAP_EVIDENCE_MEASUREMENT_COORDS[index],
2678
+ evidencePosition: SKILL_MAP_EVIDENCE_MEASUREMENT_COORDS[index],
1877
2679
  })),
1878
2680
  ...(measurementOverflow.length > 0 ? [{
1879
2681
  id: 'measurement-more',
1880
2682
  node: moreNode(`+ ${measurementOverflow.length} ${zh ? '更多测量节点' : 'more measurement nodes'}`),
1881
2683
  layer: 'measurement',
2684
+ boundaryPosition: measurementMorePosition,
2685
+ evidencePosition: measurementMorePosition,
1882
2686
  moreToggle: {
1883
2687
  group: 'measurement',
1884
2688
  collapsedLabel: `+ ${measurementOverflow.length} ${zh ? '更多测量节点' : 'more measurement nodes'}`,
@@ -1892,18 +2696,23 @@ function renderSkillMapSection(entry, lang) {
1892
2696
  layer: 'measurement',
1893
2697
  edgeFrom: measurementMorePosition,
1894
2698
  overflowGroup: 'measurement',
1895
- ...overflowNodePosition('measurement', index),
2699
+ ...overflowNodePosition('measurement', index, 'evidence'),
2700
+ boundaryPosition: overflowNodePosition('measurement', index, 'evidence'),
2701
+ evidencePosition: overflowNodePosition('measurement', index, 'evidence'),
1896
2702
  })),
1897
2703
  ];
1898
2704
  const mapNodes = [...definitionNodes, ...measurementNodes];
2705
+ const evidenceLinks = skillMapEvidenceLinks(mapNodes, evalGraph);
1899
2706
  const mapDimensions = skillMapDimensions(mapNodes);
2707
+ const topMeta = '';
1900
2708
  return `<section id="section-skill-map" class="si-sect si-sect--${band} sm-sect">
1901
2709
  <div class="si-sect-h">
1902
2710
  <span class="si-sect-title">🗺 ${zh ? 'Skill Map' : 'Skill Map'}</span>
1903
- <span class="si-sect-meta">${doctor ? `${doctor.nodeCount} definition nodes` : (zh ? 'definition 未生成' : 'definition missing')} · ${evalGraph ? `${evalGraph.nodeCount} variant subgraph nodes` : (zh ? 'measurement 未生成' : 'measurement missing')}</span>
2711
+ ${topMeta ? `<span class="si-sect-meta">${e(topMeta)}</span>` : ''}
1904
2712
  </div>
1905
- <div class="sm-body" data-sm-map data-sm-base-width="${mapDimensions.width}" data-sm-base-height="${mapDimensions.height}" data-sm-root-x="${SKILL_MAP_ROOT.x}" data-sm-root-y="${SKILL_MAP_ROOT.y}">
2713
+ <div class="sm-body" data-sm-map data-sm-view="boundary" data-sm-base-width="${mapDimensions.width}" data-sm-base-height="${mapDimensions.height}" data-sm-root-x="${SKILL_MAP_BOUNDARY_ROOT.x}" data-sm-root-y="${SKILL_MAP_BOUNDARY_ROOT.y}">
1906
2714
  ${renderSkillStageRail(entry, lang)}
2715
+ ${renderSkillMapEvidenceStrip(entry, doctor, evalGraph, lang)}
1907
2716
  <div class="sm-toolbar" aria-label="${zh ? 'Skill Map 控制' : 'Skill Map controls'}">
1908
2717
  <div class="sm-tools">
1909
2718
  <button class="sm-tool" type="button" data-sm-action="zoom-out" title="${zh ? '缩小' : 'Zoom out'}">−</button>
@@ -1912,16 +2721,20 @@ function renderSkillMapSection(entry, lang) {
1912
2721
  <button class="sm-tool" type="button" data-sm-action="reset" title="${zh ? '重置视图' : 'Reset view'}">${zh ? '重置' : 'Reset'}</button>
1913
2722
  </div>
1914
2723
  <div class="sm-toggles">
1915
- <label class="sm-toggle"><input type="checkbox" data-sm-toggle-layer="definition" checked> ${zh ? '定义' : 'Definition'}</label>
1916
- <label class="sm-toggle"><input type="checkbox" data-sm-toggle-layer="measurement" checked> ${zh ? '测量' : 'Measurement'}</label>
2724
+ <div class="sm-view-switch" role="group" aria-label="${zh ? '图谱视图' : 'Map view'}">
2725
+ <button class="sm-view is-active" type="button" data-sm-view-button="boundary" aria-pressed="true">${zh ? '边界图' : 'Boundary'}</button>
2726
+ <button class="sm-view" type="button" data-sm-view-button="evidence" aria-pressed="false">${zh ? '证据视图' : 'Evidence'}</button>
2727
+ </div>
1917
2728
  <label class="sm-toggle"><input type="checkbox" data-sm-toggle-leaves checked> ${zh ? '叶子节点' : 'Leaf nodes'}</label>
1918
2729
  </div>
1919
2730
  </div>
1920
2731
  <div class="sm-canvas" data-sm-viewport aria-label="Skill Map">
1921
2732
  <div class="sm-graph-stage" data-sm-graph-stage style="width:${mapDimensions.width}px;height:${mapDimensions.height}px">
1922
2733
  <div class="sm-graph" data-sm-graph style="width:${mapDimensions.width}px;height:${mapDimensions.height}px">
1923
- ${renderSkillMapEdges(mapNodes, mapDimensions)}
1924
- <div class="sm-node sm-node--skill" data-sm-root data-sm-node-id="${SKILL_MAP_ROOT_ID}" data-sm-x="${SKILL_MAP_ROOT.x}" data-sm-y="${SKILL_MAP_ROOT.y}" data-sm-origin-x="${SKILL_MAP_ROOT.x}" data-sm-origin-y="${SKILL_MAP_ROOT.y}" data-sm-draggable="1" style="${positionedNodeStyle(SKILL_MAP_ROOT)}">
2734
+ <div class="sm-zone-label sm-zone-label--definition" data-sm-layer="definition">${zh ? '知识边界' : 'Knowledge'}</div>
2735
+ <div class="sm-zone-label sm-zone-label--measurement" data-sm-layer="measurement">${zh ? '评测证据' : 'Evidence'}</div>
2736
+ ${renderSkillMapEdges(mapNodes, mapDimensions, evidenceLinks)}
2737
+ <div class="sm-node sm-node--skill" data-sm-root data-sm-node-id="${SKILL_MAP_ROOT_ID}" data-sm-x="${SKILL_MAP_BOUNDARY_ROOT.x}" data-sm-y="${SKILL_MAP_BOUNDARY_ROOT.y}" data-sm-origin-x="${SKILL_MAP_BOUNDARY_ROOT.x}" data-sm-origin-y="${SKILL_MAP_BOUNDARY_ROOT.y}" data-sm-boundary-x="${SKILL_MAP_BOUNDARY_ROOT.x}" data-sm-boundary-y="${SKILL_MAP_BOUNDARY_ROOT.y}" data-sm-evidence-x="${SKILL_MAP_EVIDENCE_ROOT.x}" data-sm-evidence-y="${SKILL_MAP_EVIDENCE_ROOT.y}" data-sm-draggable="1"${renderSkillMapRootDetailAttrs(entry, coverageSummary, lang)} role="button" tabindex="0" aria-label="SKILL.md ${e(entry.skillName)}" style="${positionedNodeStyle(SKILL_MAP_BOUNDARY_ROOT)}">
1925
2738
  <div class="sm-node-title">SKILL.md</div>
1926
2739
  <div class="sm-node-meta">${e(entry.skillName)}</div>
1927
2740
  </div>
@@ -1929,8 +2742,9 @@ function renderSkillMapSection(entry, lang) {
1929
2742
  </div>
1930
2743
  </div>
1931
2744
  </div>
2745
+ ${renderSkillMapInspector(entry, coverageSummary, lang)}
1932
2746
  <details class="sm-card">
1933
- <summary>${zh ? '复制 Markdown Evidence Card' : 'Copy Markdown Evidence Card'}</summary>
2747
+ <summary>${zh ? '复制图谱摘要' : 'Copy Skill Map Summary'}</summary>
1934
2748
  <textarea readonly>${e(markdown)}</textarea>
1935
2749
  </details>
1936
2750
  </div>