@cleocode/skills 2.0.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 (171) hide show
  1. package/dispatch-config.json +404 -0
  2. package/index.d.ts +178 -0
  3. package/index.js +405 -0
  4. package/package.json +14 -0
  5. package/profiles/core.json +7 -0
  6. package/profiles/full.json +10 -0
  7. package/profiles/minimal.json +7 -0
  8. package/profiles/recommended.json +7 -0
  9. package/provider-skills-map.json +97 -0
  10. package/skills/_shared/cleo-style-guide.md +84 -0
  11. package/skills/_shared/manifest-operations.md +810 -0
  12. package/skills/_shared/placeholders.json +433 -0
  13. package/skills/_shared/skill-chaining-patterns.md +237 -0
  14. package/skills/_shared/subagent-protocol-base.md +223 -0
  15. package/skills/_shared/task-system-integration.md +232 -0
  16. package/skills/_shared/testing-framework-config.md +110 -0
  17. package/skills/ct-cleo/SKILL.md +490 -0
  18. package/skills/ct-cleo/references/anti-patterns.md +19 -0
  19. package/skills/ct-cleo/references/loom-lifecycle.md +136 -0
  20. package/skills/ct-cleo/references/orchestrator-constraints.md +55 -0
  21. package/skills/ct-cleo/references/session-protocol.md +162 -0
  22. package/skills/ct-codebase-mapper/SKILL.md +82 -0
  23. package/skills/ct-contribution/SKILL.md +521 -0
  24. package/skills/ct-contribution/templates/contribution-init.json +21 -0
  25. package/skills/ct-dev-workflow/SKILL.md +423 -0
  26. package/skills/ct-docs-lookup/SKILL.md +66 -0
  27. package/skills/ct-docs-review/SKILL.md +175 -0
  28. package/skills/ct-docs-write/SKILL.md +108 -0
  29. package/skills/ct-documentor/SKILL.md +231 -0
  30. package/skills/ct-epic-architect/SKILL.md +305 -0
  31. package/skills/ct-epic-architect/references/bug-epic-example.md +172 -0
  32. package/skills/ct-epic-architect/references/commands.md +201 -0
  33. package/skills/ct-epic-architect/references/feature-epic-example.md +210 -0
  34. package/skills/ct-epic-architect/references/migration-epic-example.md +244 -0
  35. package/skills/ct-epic-architect/references/output-format.md +92 -0
  36. package/skills/ct-epic-architect/references/patterns.md +284 -0
  37. package/skills/ct-epic-architect/references/refactor-epic-example.md +412 -0
  38. package/skills/ct-epic-architect/references/research-epic-example.md +226 -0
  39. package/skills/ct-epic-architect/references/shell-escaping.md +86 -0
  40. package/skills/ct-epic-architect/references/skill-aware-execution.md +195 -0
  41. package/skills/ct-grade/SKILL.md +230 -0
  42. package/skills/ct-grade/agents/analysis-reporter.md +203 -0
  43. package/skills/ct-grade/agents/blind-comparator.md +157 -0
  44. package/skills/ct-grade/agents/scenario-runner.md +134 -0
  45. package/skills/ct-grade/eval-viewer/__pycache__/generate_grade_review.cpython-314.pyc +0 -0
  46. package/skills/ct-grade/eval-viewer/generate_grade_review.py +1138 -0
  47. package/skills/ct-grade/eval-viewer/generate_grade_viewer.py +544 -0
  48. package/skills/ct-grade/eval-viewer/generate_review.py +283 -0
  49. package/skills/ct-grade/eval-viewer/grade-review.html +1574 -0
  50. package/skills/ct-grade/eval-viewer/viewer.html +219 -0
  51. package/skills/ct-grade/evals/evals.json +94 -0
  52. package/skills/ct-grade/references/ab-test-methodology.md +150 -0
  53. package/skills/ct-grade/references/domains.md +137 -0
  54. package/skills/ct-grade/references/grade-spec.md +236 -0
  55. package/skills/ct-grade/references/scenario-playbook.md +234 -0
  56. package/skills/ct-grade/references/token-tracking.md +120 -0
  57. package/skills/ct-grade/scripts/__pycache__/audit_analyzer.cpython-314.pyc +0 -0
  58. package/skills/ct-grade/scripts/__pycache__/run_ab_test.cpython-314.pyc +0 -0
  59. package/skills/ct-grade/scripts/__pycache__/run_all.cpython-314.pyc +0 -0
  60. package/skills/ct-grade/scripts/__pycache__/token_tracker.cpython-314.pyc +0 -0
  61. package/skills/ct-grade/scripts/audit_analyzer.py +279 -0
  62. package/skills/ct-grade/scripts/generate_report.py +283 -0
  63. package/skills/ct-grade/scripts/run_ab_test.py +504 -0
  64. package/skills/ct-grade/scripts/run_all.py +287 -0
  65. package/skills/ct-grade/scripts/setup_run.py +183 -0
  66. package/skills/ct-grade/scripts/token_tracker.py +630 -0
  67. package/skills/ct-grade-v2-1/SKILL.md +237 -0
  68. package/skills/ct-grade-v2-1/agents/analysis-reporter.md +203 -0
  69. package/skills/ct-grade-v2-1/agents/blind-comparator.md +157 -0
  70. package/skills/ct-grade-v2-1/agents/scenario-runner.md +179 -0
  71. package/skills/ct-grade-v2-1/evals/evals.json +74 -0
  72. package/skills/ct-grade-v2-1/grade-viewer/__pycache__/build_op_stats.cpython-314.pyc +0 -0
  73. package/skills/ct-grade-v2-1/grade-viewer/__pycache__/generate_grade_review.cpython-314.pyc +0 -0
  74. package/skills/ct-grade-v2-1/grade-viewer/build_op_stats.py +174 -0
  75. package/skills/ct-grade-v2-1/grade-viewer/eval-analysis.json +41 -0
  76. package/skills/ct-grade-v2-1/grade-viewer/eval-report.md +34 -0
  77. package/skills/ct-grade-v2-1/grade-viewer/generate_grade_review.py +1023 -0
  78. package/skills/ct-grade-v2-1/grade-viewer/generate_grade_viewer.py +548 -0
  79. package/skills/ct-grade-v2-1/grade-viewer/grade-review-eval.html +613 -0
  80. package/skills/ct-grade-v2-1/grade-viewer/grade-review.html +1532 -0
  81. package/skills/ct-grade-v2-1/grade-viewer/viewer.html +620 -0
  82. package/skills/ct-grade-v2-1/manifest-entry.json +31 -0
  83. package/skills/ct-grade-v2-1/references/ab-testing.md +233 -0
  84. package/skills/ct-grade-v2-1/references/domains-ssot.md +156 -0
  85. package/skills/ct-grade-v2-1/references/grade-spec-v2.md +167 -0
  86. package/skills/ct-grade-v2-1/references/playbook-v2.md +393 -0
  87. package/skills/ct-grade-v2-1/references/token-tracking.md +202 -0
  88. package/skills/ct-grade-v2-1/scripts/generate_report.py +419 -0
  89. package/skills/ct-grade-v2-1/scripts/run_ab_test.py +493 -0
  90. package/skills/ct-grade-v2-1/scripts/run_scenario.py +396 -0
  91. package/skills/ct-grade-v2-1/scripts/setup_run.py +207 -0
  92. package/skills/ct-grade-v2-1/scripts/token_tracker.py +175 -0
  93. package/skills/ct-memory/SKILL.md +84 -0
  94. package/skills/ct-orchestrator/INSTALL.md +61 -0
  95. package/skills/ct-orchestrator/README.md +69 -0
  96. package/skills/ct-orchestrator/SKILL.md +380 -0
  97. package/skills/ct-orchestrator/manifest-entry.json +19 -0
  98. package/skills/ct-orchestrator/orchestrator-prompt.txt +17 -0
  99. package/skills/ct-orchestrator/references/SUBAGENT-PROTOCOL-BLOCK.md +66 -0
  100. package/skills/ct-orchestrator/references/autonomous-operation.md +167 -0
  101. package/skills/ct-orchestrator/references/lifecycle-gates.md +98 -0
  102. package/skills/ct-orchestrator/references/orchestrator-compliance.md +271 -0
  103. package/skills/ct-orchestrator/references/orchestrator-handoffs.md +85 -0
  104. package/skills/ct-orchestrator/references/orchestrator-patterns.md +164 -0
  105. package/skills/ct-orchestrator/references/orchestrator-recovery.md +113 -0
  106. package/skills/ct-orchestrator/references/orchestrator-spawning.md +271 -0
  107. package/skills/ct-orchestrator/references/orchestrator-tokens.md +180 -0
  108. package/skills/ct-research-agent/SKILL.md +226 -0
  109. package/skills/ct-skill-creator/.cleo/.context-state.json +13 -0
  110. package/skills/ct-skill-creator/.cleo/logs/cleo.2026-03-07.1.log +24 -0
  111. package/skills/ct-skill-creator/.cleo/tasks.db +0 -0
  112. package/skills/ct-skill-creator/SKILL.md +356 -0
  113. package/skills/ct-skill-creator/agents/analyzer.md +276 -0
  114. package/skills/ct-skill-creator/agents/comparator.md +204 -0
  115. package/skills/ct-skill-creator/agents/grader.md +225 -0
  116. package/skills/ct-skill-creator/assets/eval_review.html +146 -0
  117. package/skills/ct-skill-creator/eval-viewer/__pycache__/generate_review.cpython-314.pyc +0 -0
  118. package/skills/ct-skill-creator/eval-viewer/generate_review.py +471 -0
  119. package/skills/ct-skill-creator/eval-viewer/viewer.html +1325 -0
  120. package/skills/ct-skill-creator/manifest-entry.json +17 -0
  121. package/skills/ct-skill-creator/references/dynamic-context.md +228 -0
  122. package/skills/ct-skill-creator/references/frontmatter.md +83 -0
  123. package/skills/ct-skill-creator/references/invocation-control.md +165 -0
  124. package/skills/ct-skill-creator/references/output-patterns.md +86 -0
  125. package/skills/ct-skill-creator/references/provider-deployment.md +175 -0
  126. package/skills/ct-skill-creator/references/schemas.md +430 -0
  127. package/skills/ct-skill-creator/references/workflows.md +28 -0
  128. package/skills/ct-skill-creator/scripts/__init__.py +1 -0
  129. package/skills/ct-skill-creator/scripts/__pycache__/__init__.cpython-314.pyc +0 -0
  130. package/skills/ct-skill-creator/scripts/__pycache__/aggregate_benchmark.cpython-314.pyc +0 -0
  131. package/skills/ct-skill-creator/scripts/__pycache__/generate_report.cpython-314.pyc +0 -0
  132. package/skills/ct-skill-creator/scripts/__pycache__/improve_description.cpython-314.pyc +0 -0
  133. package/skills/ct-skill-creator/scripts/__pycache__/init_skill.cpython-314.pyc +0 -0
  134. package/skills/ct-skill-creator/scripts/__pycache__/quick_validate.cpython-314.pyc +0 -0
  135. package/skills/ct-skill-creator/scripts/__pycache__/run_eval.cpython-314.pyc +0 -0
  136. package/skills/ct-skill-creator/scripts/__pycache__/run_loop.cpython-314.pyc +0 -0
  137. package/skills/ct-skill-creator/scripts/__pycache__/utils.cpython-314.pyc +0 -0
  138. package/skills/ct-skill-creator/scripts/aggregate_benchmark.py +401 -0
  139. package/skills/ct-skill-creator/scripts/generate_report.py +326 -0
  140. package/skills/ct-skill-creator/scripts/improve_description.py +247 -0
  141. package/skills/ct-skill-creator/scripts/init_skill.py +306 -0
  142. package/skills/ct-skill-creator/scripts/package_skill.py +110 -0
  143. package/skills/ct-skill-creator/scripts/quick_validate.py +97 -0
  144. package/skills/ct-skill-creator/scripts/run_eval.py +310 -0
  145. package/skills/ct-skill-creator/scripts/run_loop.py +328 -0
  146. package/skills/ct-skill-creator/scripts/utils.py +47 -0
  147. package/skills/ct-skill-validator/SKILL.md +178 -0
  148. package/skills/ct-skill-validator/agents/ecosystem-checker.md +151 -0
  149. package/skills/ct-skill-validator/assets/valid-skill-example.md +13 -0
  150. package/skills/ct-skill-validator/evals/eval_set.json +14 -0
  151. package/skills/ct-skill-validator/evals/evals.json +52 -0
  152. package/skills/ct-skill-validator/manifest-entry.json +20 -0
  153. package/skills/ct-skill-validator/references/cleo-ecosystem-rules.md +163 -0
  154. package/skills/ct-skill-validator/references/validation-rules.md +168 -0
  155. package/skills/ct-skill-validator/scripts/__init__.py +0 -0
  156. package/skills/ct-skill-validator/scripts/__pycache__/audit_body.cpython-314.pyc +0 -0
  157. package/skills/ct-skill-validator/scripts/__pycache__/check_ecosystem.cpython-314.pyc +0 -0
  158. package/skills/ct-skill-validator/scripts/__pycache__/generate_validation_report.cpython-314.pyc +0 -0
  159. package/skills/ct-skill-validator/scripts/__pycache__/validate.cpython-314.pyc +0 -0
  160. package/skills/ct-skill-validator/scripts/audit_body.py +242 -0
  161. package/skills/ct-skill-validator/scripts/check_ecosystem.py +169 -0
  162. package/skills/ct-skill-validator/scripts/check_manifest.py +172 -0
  163. package/skills/ct-skill-validator/scripts/generate_validation_report.py +442 -0
  164. package/skills/ct-skill-validator/scripts/validate.py +422 -0
  165. package/skills/ct-spec-writer/SKILL.md +189 -0
  166. package/skills/ct-stickynote/README.md +14 -0
  167. package/skills/ct-stickynote/SKILL.md +46 -0
  168. package/skills/ct-task-executor/SKILL.md +296 -0
  169. package/skills/ct-validator/SKILL.md +216 -0
  170. package/skills/manifest.json +469 -0
  171. package/skills.json +281 -0
@@ -0,0 +1,1574 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>ct-grade — Grade Review</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
10
+ <style>
11
+ :root {
12
+ --bg: #0d1117;
13
+ --surface: #161b22;
14
+ --surface2: #1c2128;
15
+ --border: #30363d;
16
+ --text: #e6edf3;
17
+ --text-muted: #7d8590;
18
+ --accent: #f78166;
19
+ --radius: 6px;
20
+ --mono: 'JetBrains Mono', monospace;
21
+ --grade-a: #3fb950; --grade-a-bg: #0d2818;
22
+ --grade-b: #58a6ff; --grade-b-bg: #0d1f3c;
23
+ --grade-c: #e3b341; --grade-c-bg: #2d1d00;
24
+ --grade-d: #f0883e; --grade-d-bg: #2c1700;
25
+ --grade-f: #f85149; --grade-f-bg: #2d0f0d;
26
+ --mcp: #58a6ff; --cli: #a371f7; --tie: #7d8590;
27
+ --win: #3fb950; --loss: #f85149;
28
+ }
29
+ * { box-sizing: border-box; margin: 0; padding: 0; }
30
+ body { font-family: 'Poppins', sans-serif; background: var(--bg); color: var(--text); height: 100vh; display: flex; flex-direction: column; font-size: 14px; }
31
+
32
+ /* Header */
33
+ .header { background: var(--surface); border-bottom: 1px solid var(--border); padding: .75rem 1.5rem; display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; gap: 1rem; }
34
+ .header-left { display: flex; align-items: center; gap: .75rem; }
35
+ .header-logo { font-size: .65rem; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; background: var(--accent); color: #fff; padding: .2em .5em; border-radius: 3px; }
36
+ .header-title { font-size: 1rem; font-weight: 600; }
37
+ .header-subtitle { font-size: .75rem; color: var(--text-muted); }
38
+ .header-meta { font-size: .7rem; color: var(--text-muted); text-align: right; line-height: 1.6; }
39
+
40
+ /* Tabs */
41
+ .tabs-bar { background: var(--surface); border-bottom: 1px solid var(--border); display: flex; padding: 0 1.5rem; gap: .25rem; flex-shrink: 0; overflow-x: auto; }
42
+ .tab-btn { font-family: 'Poppins', sans-serif; font-size: .75rem; font-weight: 500; padding: .6rem .9rem; border: none; background: transparent; color: var(--text-muted); cursor: pointer; border-bottom: 2px solid transparent; transition: color .15s, border-color .15s; letter-spacing: .02em; white-space: nowrap; }
43
+ .tab-btn:hover { color: var(--text); }
44
+ .tab-btn.active { color: var(--accent); border-bottom-color: var(--accent); }
45
+ .tab-badge { display: inline-block; background: var(--surface2); border-radius: 9px; font-size: .65rem; padding: .1em .45em; margin-left: .3em; color: var(--text-muted); }
46
+ .tab-btn.active .tab-badge { background: rgba(247,129,102,.15); color: var(--accent); }
47
+
48
+ /* Layout */
49
+ .main { display: flex; flex: 1; overflow: hidden; }
50
+ .tab-panel { display: none; width: 100%; overflow: hidden; }
51
+ .tab-panel.active { display: flex; }
52
+ .sidebar { width: 260px; min-width: 200px; border-right: 1px solid var(--border); background: var(--surface); display: flex; flex-direction: column; flex-shrink: 0; overflow-y: auto; }
53
+ .sidebar-header { font-size: .65rem; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; color: var(--text-muted); padding: .75rem 1rem .4rem; }
54
+ .sidebar-item { padding: .55rem 1rem; cursor: pointer; display: flex; align-items: center; gap: .5rem; border-left: 3px solid transparent; transition: background .1s; font-size: .8rem; }
55
+ .sidebar-item:hover { background: var(--surface2); }
56
+ .sidebar-item.active { background: var(--surface2); border-left-color: var(--accent); }
57
+ .item-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
58
+ .item-score { font-family: var(--mono); font-size: .7rem; font-weight: 500; }
59
+ .item-scenario { font-size: .6rem; color: var(--text-muted); background: var(--surface2); border: 1px solid var(--border); padding: .05em .35em; border-radius: 3px; margin-left: .25rem; }
60
+ .content { flex: 1; overflow-y: auto; padding: 1.5rem; display: flex; flex-direction: column; gap: 1.25rem; }
61
+ .scroll-panel { width: 100%; padding: 1.5rem; overflow-y: auto; display: flex; flex-direction: column; gap: 1.25rem; }
62
+
63
+ /* Cards */
64
+ .card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 1.25rem; }
65
+ .card-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; margin-bottom: 1rem; }
66
+ .score-block { display: flex; align-items: center; gap: 1rem; }
67
+ .letter-badge { font-size: 2.5rem; font-weight: 700; width: 64px; height: 64px; display: flex; align-items: center; justify-content: center; border-radius: 8px; }
68
+ .letter-badge.sm { font-size: 1.1rem; width: 36px; height: 36px; border-radius: 5px; }
69
+ .letter-badge.A { background: var(--grade-a-bg); color: var(--grade-a); }
70
+ .letter-badge.B { background: var(--grade-b-bg); color: var(--grade-b); }
71
+ .letter-badge.C { background: var(--grade-c-bg); color: var(--grade-c); }
72
+ .letter-badge.D { background: var(--grade-d-bg); color: var(--grade-d); }
73
+ .letter-badge.F { background: var(--grade-f-bg); color: var(--grade-f); }
74
+ .score-details h2 { font-size: 1.5rem; font-weight: 700; line-height: 1; }
75
+ .score-details .pct { font-size: .85rem; color: var(--text-muted); }
76
+ .session-chip { font-family: var(--mono); font-size: .65rem; color: var(--text-muted); background: var(--surface2); padding: .2em .5em; border-radius: 3px; }
77
+ .section-title { font-size: .75rem; font-weight: 600; text-transform: uppercase; letter-spacing: .08em; color: var(--text-muted); margin-bottom: .75rem; }
78
+
79
+ /* Dimensions */
80
+ .dimensions { display: flex; flex-direction: column; gap: .65rem; }
81
+ .dim-row { display: flex; align-items: center; gap: .75rem; }
82
+ .dim-name { font-size: .7rem; font-weight: 500; width: 155px; flex-shrink: 0; }
83
+ .dim-bar-wrap { flex: 1; height: 8px; background: var(--surface2); border-radius: 4px; overflow: hidden; }
84
+ .dim-bar { height: 100%; border-radius: 4px; transition: width .4s ease; }
85
+ .dim-score { font-family: var(--mono); font-size: .7rem; width: 42px; text-align: right; flex-shrink: 0; }
86
+
87
+ /* Flags */
88
+ .flags-section h3 { font-size: .75rem; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: .07em; margin-bottom: .6rem; }
89
+ .flag-item { display: flex; align-items: flex-start; gap: .5rem; font-size: .78rem; padding: .4rem .6rem; background: var(--grade-f-bg); border-left: 3px solid var(--grade-f); border-radius: 0 var(--radius) var(--radius) 0; line-height: 1.4; margin-bottom: .35rem; }
90
+ .no-flags { font-size: .78rem; color: var(--win); background: var(--grade-a-bg); padding: .4rem .6rem; border-left: 3px solid var(--win); border-radius: 0 var(--radius) var(--radius) 0; }
91
+
92
+ /* Evidence collapsible */
93
+ .evidence-toggle { font-size: .72rem; color: var(--accent); cursor: pointer; background: none; border: none; padding: .25rem 0; }
94
+ .evidence-toggle:hover { text-decoration: underline; }
95
+ .evidence-list { display: none; padding-left: .75rem; }
96
+ .evidence-list.open { display: block; }
97
+ .ev-bullet { font-size: .74rem; padding: .15rem 0; color: var(--text-muted); }
98
+
99
+ /* Token meta */
100
+ .token-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); gap: .6rem; }
101
+ .token-stat { background: var(--surface2); border-radius: var(--radius); padding: .6rem .75rem; }
102
+ .token-stat-label { font-size: .6rem; font-weight: 600; text-transform: uppercase; letter-spacing: .07em; color: var(--text-muted); margin-bottom: .2rem; }
103
+ .token-stat-value { font-family: var(--mono); font-size: .95rem; font-weight: 500; }
104
+ .confidence-badge { display: inline-block; padding: .15em .5em; border-radius: 9px; font-size: .65rem; font-weight: 600; text-transform: uppercase; }
105
+ .confidence-badge.real { background: var(--grade-a-bg); color: var(--grade-a); }
106
+ .confidence-badge.high { background: var(--grade-b-bg); color: var(--grade-b); }
107
+ .confidence-badge.estimated { background: var(--grade-c-bg); color: var(--grade-c); }
108
+ .confidence-badge.coarse { background: var(--grade-d-bg); color: var(--grade-d); }
109
+
110
+ /* Tables */
111
+ table.data-table { width: 100%; border-collapse: collapse; font-size: .76rem; }
112
+ .data-table th { font-size: .65rem; font-weight: 600; text-transform: uppercase; letter-spacing: .07em; color: var(--text-muted); padding: .5rem .75rem; text-align: left; background: var(--surface); border-bottom: 1px solid var(--border); white-space: nowrap; position: sticky; top: 0; z-index: 1; }
113
+ .data-table td { padding: .45rem .75rem; border-bottom: 1px solid var(--border); vertical-align: middle; }
114
+ .data-table tr:hover td { background: var(--surface2); }
115
+ .table-wrap { overflow-x: auto; }
116
+ .op-name { font-family: var(--mono); font-size: .72rem; }
117
+
118
+ /* Pills */
119
+ .pill { display: inline-block; padding: .15em .5em; border-radius: 9px; font-size: .65rem; font-weight: 600; text-transform: uppercase; white-space: nowrap; }
120
+ .pill.mcp { background: rgba(88,166,255,.15); color: var(--mcp); }
121
+ .pill.cli { background: rgba(163,113,247,.15); color: var(--cli); }
122
+ .pill.tie { background: var(--surface2); color: var(--text-muted); }
123
+ .pill.query { background: rgba(88,166,255,.15); color: var(--mcp); }
124
+ .pill.mutate { background: rgba(247,129,102,.15); color: var(--accent); }
125
+ .pill.active { background: var(--grade-a-bg); color: var(--grade-a); }
126
+ .pill.ended { background: var(--surface2); color: var(--text-muted); }
127
+ .pill.pass { background: var(--grade-a-bg); color: var(--grade-a); }
128
+ .pill.fail { background: var(--grade-f-bg); color: var(--grade-f); }
129
+ .pill.not-run { background: var(--surface2); color: var(--text-muted); }
130
+
131
+ /* Delta colors */
132
+ .delta-pos { color: var(--loss); font-family: var(--mono); font-size: .72rem; }
133
+ .delta-neg { color: var(--win); font-family: var(--mono); font-size: .72rem; }
134
+ .delta-zero { color: var(--text-muted); font-family: var(--mono); font-size: .72rem; }
135
+
136
+ /* Bar charts */
137
+ .bar-chart { display: flex; flex-direction: column; gap: .5rem; }
138
+ .bar-row { display: flex; align-items: center; gap: .75rem; }
139
+ .bar-label { font-family: var(--mono); font-size: .72rem; width: 130px; flex-shrink: 0; text-align: right; }
140
+ .bar-track { flex: 1; height: 16px; background: var(--surface2); border-radius: 3px; overflow: hidden; }
141
+ .bar-fill { height: 100%; border-radius: 3px; transition: width .4s ease; }
142
+ .bar-val { font-family: var(--mono); font-size: .7rem; width: 80px; flex-shrink: 0; }
143
+ .chart-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 1rem 1.25rem; }
144
+ .chart-title { font-size: .8rem; font-weight: 600; margin-bottom: 1rem; }
145
+
146
+ /* Summary grid */
147
+ .stat-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: .75rem; }
148
+ .stat-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: .85rem 1rem; }
149
+ .stat-label { font-size: .65rem; font-weight: 600; text-transform: uppercase; letter-spacing: .07em; color: var(--text-muted); margin-bottom: .3rem; }
150
+ .stat-value { font-family: var(--mono); font-size: 1.3rem; font-weight: 500; }
151
+ .stat-value.win { color: var(--win); }
152
+ .stat-value.loss { color: var(--loss); }
153
+ .stat-value.neutral { color: var(--text-muted); }
154
+
155
+ /* Winner banner */
156
+ .winner-banner { display: flex; align-items: center; gap: .6rem; padding: .6rem 1rem; border-radius: var(--radius); font-size: .8rem; font-weight: 600; }
157
+ .winner-banner.mcp { background: rgba(88,166,255,.1); color: var(--mcp); }
158
+ .winner-banner.cli { background: rgba(163,113,247,.1); color: var(--cli); }
159
+ .winner-banner.tie { background: var(--surface2); color: var(--text-muted); }
160
+
161
+ /* Filter bar */
162
+ .filter-bar { display: flex; gap: .75rem; flex-wrap: wrap; align-items: center; padding: .5rem 0; }
163
+ .filter-bar select, .filter-bar input { font-family: var(--mono); font-size: .72rem; background: var(--surface2); color: var(--text); border: 1px solid var(--border); border-radius: var(--radius); padding: .35rem .5rem; outline: none; }
164
+ .filter-bar select:focus, .filter-bar input:focus { border-color: var(--accent); }
165
+ .filter-bar label { font-size: .68rem; color: var(--text-muted); font-weight: 500; }
166
+
167
+ /* Session row expand */
168
+ .session-row { cursor: pointer; }
169
+ .session-expand { display: none; }
170
+ .session-expand.open { display: table-row; }
171
+ .session-expand td { padding: 1rem; background: var(--surface2); }
172
+ .pulse-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--grade-a); animation: pulse 1.5s ease-in-out infinite; margin-right: .35rem; vertical-align: middle; }
173
+ @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: .3; } }
174
+
175
+ /* Session detail expand */
176
+ .session-detail { padding: .25rem 0; }
177
+ .session-detail-header { display: flex; align-items: center; gap: 1.5rem; padding: .5rem 0 .75rem; flex-wrap: wrap; }
178
+ .session-detail-stat { font-size: .72rem; color: var(--text-muted); }
179
+ .session-detail-stat strong { color: var(--text); font-weight: 600; }
180
+ .token-summary { display: flex; gap: 1.25rem; padding: .5rem .75rem; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); margin-bottom: .75rem; flex-wrap: wrap; }
181
+ .token-stat { font-size: .72rem; color: var(--text-muted); }
182
+ .token-stat strong { font-family: var(--mono); color: var(--accent); }
183
+ .audit-table-wrap { overflow-x: auto; }
184
+ .audit-table { width: 100%; border-collapse: collapse; font-size: .72rem; }
185
+ .audit-table th { text-align: left; padding: .4rem .6rem; font-size: .65rem; font-weight: 600; text-transform: uppercase; letter-spacing: .07em; color: var(--text-muted); border-bottom: 1px solid var(--border); white-space: nowrap; }
186
+ .audit-table td { padding: .35rem .6rem; border-bottom: 1px solid rgba(48,54,61,.5); vertical-align: middle; white-space: nowrap; }
187
+ .audit-table tr:last-child td { border-bottom: none; }
188
+ .audit-table tr:hover td { background: rgba(255,255,255,.02); }
189
+ .audit-time { font-family: var(--mono); color: var(--text-muted); font-size: .68rem; }
190
+ .audit-domain { font-family: var(--mono); font-size: .72rem; }
191
+ .audit-op { font-family: var(--mono); font-size: .75rem; font-weight: 500; }
192
+ .audit-source { font-size: .68rem; color: var(--text-muted); }
193
+ .audit-ms { font-family: var(--mono); font-size: .7rem; text-align: right; color: var(--text-muted); }
194
+ .audit-ms.slow { color: var(--grade-c); }
195
+ .audit-ms.vslow { color: var(--grade-d); }
196
+ .audit-success { text-align: center; font-size: .85rem; }
197
+ .gw-pill { display: inline-block; padding: .12em .5em; border-radius: 3px; font-size: .65rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; }
198
+ .gw-pill.mutate { background: rgba(247,129,102,.15); color: #f78166; border: 1px solid rgba(247,129,102,.3); }
199
+ .gw-pill.query { background: rgba(88,166,255,.15); color: #58a6ff; border: 1px solid rgba(88,166,255,.3); }
200
+ .audit-section-title { font-size: .65rem; font-weight: 600; text-transform: uppercase; letter-spacing: .08em; color: var(--text-muted); padding: .6rem 0 .4rem; }
201
+ .audit-loading { font-size: .75rem; color: var(--text-muted); padding: .5rem 0; font-style: italic; }
202
+ .audit-count-chip { font-family: var(--mono); font-size: .65rem; background: var(--surface); border: 1px solid var(--border); border-radius: 9px; padding: .1em .5em; margin-left: .5rem; color: var(--text-muted); }
203
+ .session-chain { display: flex; align-items: center; gap: .5rem; padding: .4rem 0 .6rem; font-size: .72rem; flex-wrap: wrap; }
204
+ .session-chain-label { color: var(--text-muted); font-size: .65rem; text-transform: uppercase; letter-spacing: .07em; margin-right: .25rem; }
205
+ .chain-sep { color: var(--text-muted); }
206
+ .chain-current { background: var(--accent); color: #fff; }
207
+ .session-scope { font-size: .72rem; color: var(--text-muted); padding: .25rem 0 .5rem; }
208
+ .session-scope strong { color: var(--text); }
209
+ .debrief-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: .6rem .85rem; margin-bottom: .6rem; display: flex; flex-direction: column; gap: .35rem; }
210
+ .debrief-row { font-size: .72rem; display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
211
+ .debrief-label { color: var(--text-muted); font-size: .68rem; text-transform: uppercase; letter-spacing: .06em; min-width: 90px; }
212
+ .debrief-note { font-size: .73rem; color: var(--text); font-style: italic; padding: .25rem 0; }
213
+ .git-branch { font-family: var(--mono); font-size: .7rem; color: var(--grade-b); }
214
+ .git-hash { font-family: var(--mono); font-size: .68rem; color: var(--text-muted); }
215
+ .uncommitted-badge { background: rgba(248,81,73,.15); color: var(--grade-f); border: 1px solid rgba(248,81,73,.3); padding: .1em .4em; border-radius: 3px; font-size: .63rem; font-weight: 600; }
216
+ .clean-badge { background: rgba(63,185,80,.15); color: var(--grade-a); border: 1px solid rgba(63,185,80,.3); padding: .1em .4em; border-radius: 3px; font-size: .63rem; }
217
+ .notes-toggle { font-size: .68rem; color: var(--accent); cursor: pointer; user-select: none; }
218
+ .notes-list { display: none; }
219
+ .notes-list.open { display: block; }
220
+ .note-entry { font-size: .7rem; color: var(--text-muted); padding: .2rem 0; border-bottom: 1px solid var(--border); display: flex; gap: .5rem; }
221
+ .note-ts { font-family: var(--mono); color: var(--text-muted); opacity: .6; flex-shrink: 0; font-size: .67rem; }
222
+ .note-text { color: var(--text); }
223
+
224
+ /* Eval cards */
225
+ .eval-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 1rem 1.25rem; }
226
+ .eval-card-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: .75rem; }
227
+ .eval-card-title { font-weight: 600; font-size: .85rem; }
228
+ .pass-rate-bar { width: 120px; height: 6px; background: var(--surface2); border-radius: 3px; overflow: hidden; }
229
+ .pass-rate-fill { height: 100%; border-radius: 3px; background: var(--grade-a); }
230
+ .expectation-row { display: flex; align-items: flex-start; gap: .5rem; font-size: .76rem; padding: .3rem 0; border-bottom: 1px solid var(--border); }
231
+ .expectation-row:last-child { border-bottom: none; }
232
+ .expect-icon { flex-shrink: 0; font-size: .8rem; }
233
+ .expect-evidence { font-size: .7rem; color: var(--text-muted); margin-top: .15rem; }
234
+
235
+ /* Note block */
236
+ .note-block { font-size: .72rem; color: var(--text-muted); background: var(--grade-c-bg); border-left: 3px solid var(--grade-c); padding: .5rem .75rem; border-radius: 0 4px 4px 0; }
237
+ .api-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 1rem 1.25rem; display: flex; flex-direction: column; gap: .85rem; }
238
+ .api-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: .75rem; }
239
+ .api-box { background: var(--surface2); border: 1px solid var(--border); border-radius: var(--radius); padding: .75rem .85rem; }
240
+ .api-box-title { font-size: .65rem; font-weight: 600; text-transform: uppercase; letter-spacing: .07em; color: var(--text-muted); margin-bottom: .45rem; }
241
+ .api-list { display: flex; flex-direction: column; gap: .3rem; }
242
+ .api-list code { font-family: var(--mono); font-size: .7rem; color: var(--text); }
243
+
244
+ /* Empty state */
245
+ .empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: .75rem; padding: 3rem 2rem; color: var(--text-muted); text-align: center; }
246
+ .empty-state h3 { font-size: .9rem; color: var(--text); }
247
+ .empty-state p { font-size: .78rem; max-width: 380px; line-height: 1.6; }
248
+ .empty-cmd { font-family: var(--mono); background: var(--surface2); padding: .25em .5em; border-radius: 3px; font-size: .74rem; display: inline-block; margin-top: .25rem; }
249
+
250
+ /* Scrollbar */
251
+ ::-webkit-scrollbar { width: 6px; height: 6px; }
252
+ ::-webkit-scrollbar-track { background: transparent; }
253
+ ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
254
+ ::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
255
+ </style>
256
+ </head>
257
+ <body>
258
+
259
+ <div class="header">
260
+ <div class="header-left">
261
+ <span class="header-logo">CLEO</span>
262
+ <div>
263
+ <div class="header-title" id="hdr-title">ct-grade — Grade Review</div>
264
+ <div class="header-subtitle" id="hdr-sub"></div>
265
+ </div>
266
+ </div>
267
+ <div class="header-meta" id="hdr-meta"></div>
268
+ </div>
269
+
270
+ <div class="tabs-bar">
271
+ <button class="tab-btn active" data-tab="grades">Grades <span class="tab-badge" id="tb-grades">0</span></button>
272
+ <button class="tab-btn" data-tab="sessions">Sessions <span class="tab-badge" id="tb-sessions">0</span></button>
273
+ <button class="tab-btn" data-tab="ab">A/B <span class="tab-badge" id="tb-ab">0</span></button>
274
+ <button class="tab-btn" data-tab="tokens">Token Economy</button>
275
+ <button class="tab-btn" data-tab="matrix">Operation Matrix <span class="tab-badge" id="tb-matrix">0</span></button>
276
+ <button class="tab-btn" data-tab="evals">Eval Report <span class="tab-badge" id="tb-evals">0</span></button>
277
+ </div>
278
+
279
+ <div class="main">
280
+ <div class="tab-panel active" id="panel-grades">
281
+ <div class="sidebar" id="grade-sidebar"></div>
282
+ <div class="content" id="grade-content"></div>
283
+ </div>
284
+ <div class="tab-panel" id="panel-sessions">
285
+ <div class="scroll-panel" id="sessions-content"></div>
286
+ </div>
287
+ <div class="tab-panel" id="panel-ab">
288
+ <div class="scroll-panel" id="ab-content"></div>
289
+ </div>
290
+ <div class="tab-panel" id="panel-tokens">
291
+ <div class="scroll-panel" id="tokens-content"></div>
292
+ </div>
293
+ <div class="tab-panel" id="panel-matrix">
294
+ <div class="scroll-panel" id="matrix-content"></div>
295
+ </div>
296
+ <div class="tab-panel" id="panel-evals">
297
+ <div class="scroll-panel" id="evals-content"></div>
298
+ </div>
299
+ </div>
300
+
301
+ <script id="grade-data" type="application/json">{{EMBEDDED_DATA}}</script>
302
+ <script>
303
+ // ── Data ──────────────────────────────────────────────────────────────────────
304
+ var DATA = {};
305
+ try { DATA = JSON.parse(document.getElementById('grade-data').textContent || '{}'); } catch(e) { DATA = {}; }
306
+
307
+ // ── Helpers ───────────────────────────────────────────────────────────────────
308
+ function scoreToLetter(score, max) {
309
+ if (score == null) return '?';
310
+ var pct = (score / (max || 100)) * 100;
311
+ if (pct >= 90) return 'A';
312
+ if (pct >= 80) return 'B';
313
+ if (pct >= 70) return 'C';
314
+ if (pct >= 60) return 'D';
315
+ return 'F';
316
+ }
317
+ function gradeColor(letter) {
318
+ var m = { A: 'var(--grade-a)', B: 'var(--grade-b)', C: 'var(--grade-c)', D: 'var(--grade-d)', F: 'var(--grade-f)' };
319
+ return m[letter] || 'var(--text-muted)';
320
+ }
321
+ var DIM_NAMES = ['S1 Session Discipline', 'S2 Discovery Efficiency', 'S3 Task Hygiene', 'S4 Error Protocol', 'S5 Progressive Disclosure'];
322
+ var DIM_KEYS = ['sessionDiscipline', 'discoveryEfficiency', 'taskHygiene', 'errorProtocol', 'disclosureUse'];
323
+ var DIM_COLORS = ['#a371f7', '#58a6ff', '#3fb950', '#e3b341', '#f78166'];
324
+
325
+ function fmtNum(n, fallback) {
326
+ if (n == null) return fallback !== undefined ? fallback : '\u2014';
327
+ return typeof n === 'number' && !Number.isInteger(n) ? n.toFixed(1) : String(n);
328
+ }
329
+ function fmtDate(s) {
330
+ if (!s) return '';
331
+ try { return new Date(s).toLocaleString(undefined, { dateStyle: 'short', timeStyle: 'short' }); } catch(e) { return s; }
332
+ }
333
+ function fmtDuration(ms) {
334
+ if (!ms && ms !== 0) return '\u2014';
335
+ if (ms < 1000) return ms + 'ms';
336
+ var s = Math.round(ms / 1000);
337
+ if (s < 60) return s + 's';
338
+ var m = Math.floor(s / 60);
339
+ return m + 'm ' + (s % 60) + 's';
340
+ }
341
+
342
+ // Safe element builders
343
+ function mk(tag, cls, text) {
344
+ var e = document.createElement(tag);
345
+ if (cls) e.className = cls;
346
+ if (text != null) e.textContent = String(text);
347
+ return e;
348
+ }
349
+ function mks(tag, css, text) {
350
+ var e = document.createElement(tag);
351
+ if (css) e.style.cssText = css;
352
+ if (text != null) e.textContent = String(text);
353
+ return e;
354
+ }
355
+ function app(parent) {
356
+ for (var i = 1; i < arguments.length; i++) {
357
+ if (arguments[i] != null) parent.appendChild(arguments[i]);
358
+ }
359
+ return parent;
360
+ }
361
+ function emptyState(title, desc, cmd) {
362
+ var d = mk('div', 'empty-state');
363
+ app(d, mk('h3', null, title), mk('p', null, desc));
364
+ if (cmd) app(d, mk('span', 'empty-cmd', cmd));
365
+ return d;
366
+ }
367
+
368
+ // ── Tab switching ─────────────────────────────────────────────────────────────
369
+ document.querySelectorAll('.tab-btn').forEach(function(btn) {
370
+ btn.addEventListener('click', function() {
371
+ document.querySelectorAll('.tab-btn').forEach(function(b) { b.classList.remove('active'); });
372
+ document.querySelectorAll('.tab-panel').forEach(function(p) { p.classList.remove('active'); });
373
+ btn.classList.add('active');
374
+ document.getElementById('panel-' + btn.dataset.tab).classList.add('active');
375
+ });
376
+ });
377
+
378
+ // ── Tab 1: Grades ─────────────────────────────────────────────────────────────
379
+ function renderGradesTab(grades) {
380
+ var sidebar = document.getElementById('grade-sidebar');
381
+ var content = document.getElementById('grade-content');
382
+ sidebar.textContent = '';
383
+ content.textContent = '';
384
+ if (!grades || !grades.length) {
385
+ content.appendChild(emptyState('No grade results yet', 'Run a scenario to generate grades.', 'python scripts/run_scenario.py --scenario S1'));
386
+ return;
387
+ }
388
+ document.getElementById('tb-grades').textContent = grades.length;
389
+ app(sidebar, mk('div', 'sidebar-header', 'Sessions'));
390
+ grades.forEach(function(g, i) {
391
+ var score = g.totalScore != null ? g.totalScore : g.score;
392
+ var letter = scoreToLetter(score, g.maxScore);
393
+ var sid = (g.sessionId || '').slice(0, 22) || ('Run ' + (i + 1));
394
+ var item = mk('div', 'sidebar-item' + (i === 0 ? ' active' : ''));
395
+ var ls = mks('span', 'font-weight:700;font-size:.85rem;color:' + gradeColor(letter) + ';flex-shrink:0;width:18px;', letter);
396
+ var lab = mk('span', 'item-label', sid);
397
+ var sc = mk('span', 'item-score');
398
+ sc.textContent = score != null ? (score + '/100') : '\u2014';
399
+ sc.style.color = gradeColor(letter);
400
+ app(item, ls, lab);
401
+ if (g._scenarioId || g.scenario) {
402
+ app(item, mk('span', 'item-scenario', g._scenarioId || g.scenario));
403
+ }
404
+ app(item, sc);
405
+ item.addEventListener('click', (function(g2, item2) {
406
+ return function() {
407
+ sidebar.querySelectorAll('.sidebar-item').forEach(function(x) { x.classList.remove('active'); });
408
+ item2.classList.add('active');
409
+ renderGradeDetail(g2, content);
410
+ };
411
+ })(g, item));
412
+ sidebar.appendChild(item);
413
+ });
414
+ renderGradeDetail(grades[0], content);
415
+ }
416
+
417
+ function renderGradeDetail(g, container) {
418
+ container.textContent = '';
419
+ var score = g.totalScore != null ? g.totalScore : (g.score || 0);
420
+ var max = g.maxScore || 100;
421
+ var letter = scoreToLetter(score, max);
422
+ var pct = Math.round((score / max) * 100);
423
+ var dims = g.dimensions || {};
424
+ var flags = g.flags || [];
425
+ var tmeta = g._tokenMeta || null;
426
+ var scenario = g._scenarioId || g.scenario || '';
427
+
428
+ // Score card
429
+ var card = mk('div', 'card');
430
+ var hdr = mk('div', 'card-header');
431
+ var sb = mk('div', 'score-block');
432
+ var badge = mk('div', 'letter-badge ' + letter, letter);
433
+ var sd = mk('div', 'score-details');
434
+ var h2 = mk('h2'); h2.textContent = score + '/' + max; h2.style.color = gradeColor(letter);
435
+ var pctEl = mk('div', 'pct', pct + '% \u2014 Grade ' + letter + (scenario ? ' \u2014 ' + scenario : ''));
436
+ app(sd, h2, pctEl);
437
+ if (g.timestamp) app(sd, mks('div', 'font-size:.68rem;color:var(--text-muted);margin-top:.25rem;', fmtDate(g.timestamp)));
438
+ app(sb, badge, sd);
439
+ app(hdr, sb);
440
+ if (g.sessionId) app(hdr, mk('code', 'session-chip', g.sessionId));
441
+ card.appendChild(hdr);
442
+
443
+ // Dimension bars
444
+ var dimsWrap = mk('div', 'dimensions');
445
+ DIM_KEYS.forEach(function(key, i) {
446
+ var d = dims[key]; if (!d) return;
447
+ var ds = d.score || 0, dm = d.max || 20, dp = Math.round((ds / dm) * 100);
448
+ var row = mk('div', 'dim-row');
449
+ var bar = mk('div', 'dim-bar');
450
+ bar.style.cssText = 'width:' + dp + '%;background:' + DIM_COLORS[i] + ';';
451
+ var bw = mk('div', 'dim-bar-wrap'); bw.appendChild(bar);
452
+ var sl = mk('span', 'dim-score', ds + '/' + dm);
453
+ sl.style.color = DIM_COLORS[i];
454
+ app(row, mk('span', 'dim-name', DIM_NAMES[i]), bw, sl);
455
+ dimsWrap.appendChild(row);
456
+ });
457
+ card.appendChild(dimsWrap);
458
+ container.appendChild(card);
459
+
460
+ // Flags
461
+ var fs = mk('div', 'card flags-section');
462
+ var ft = mk('h3', null, 'Flags' + (flags.length ? ' (' + flags.length + ')' : ''));
463
+ fs.appendChild(ft);
464
+ if (!flags.length) {
465
+ fs.appendChild(mk('div', 'no-flags', '\u2713 Clean \u2014 all protocol checks passed'));
466
+ } else {
467
+ flags.forEach(function(f) {
468
+ var item = mk('div', 'flag-item');
469
+ app(item, mks('span', 'flex-shrink:0;', '\u26A0'), mk('span', null, f));
470
+ fs.appendChild(item);
471
+ });
472
+ }
473
+ container.appendChild(fs);
474
+
475
+ // Evidence (collapsed, expandable per dimension)
476
+ var hasEvidence = DIM_KEYS.some(function(k) { return dims[k] && dims[k].evidence && dims[k].evidence.length; });
477
+ if (hasEvidence) {
478
+ var ec = mk('div', 'card');
479
+ ec.appendChild(mk('div', 'section-title', 'Evidence'));
480
+ DIM_KEYS.forEach(function(key, i) {
481
+ var d = dims[key]; if (!d || !d.evidence || !d.evidence.length) return;
482
+ var dimLabel = mk('div', 'dim-name', DIM_NAMES[i]);
483
+ dimLabel.style.cssText = 'color:' + DIM_COLORS[i] + ';width:auto;margin-bottom:.25rem;';
484
+ ec.appendChild(dimLabel);
485
+ var toggle = mk('button', 'evidence-toggle', 'Show ' + d.evidence.length + ' evidence item(s)');
486
+ var list = mk('div', 'evidence-list');
487
+ d.evidence.forEach(function(ev) { list.appendChild(mk('div', 'ev-bullet', '\u2022 ' + ev)); });
488
+ toggle.addEventListener('click', function() {
489
+ var open = list.classList.toggle('open');
490
+ toggle.textContent = (open ? 'Hide' : 'Show ' + d.evidence.length) + ' evidence item(s)';
491
+ });
492
+ app(ec, toggle, list);
493
+ });
494
+ container.appendChild(ec);
495
+ }
496
+
497
+ // Token display
498
+ var tokCard = mk('div', 'card');
499
+ tokCard.appendChild(mk('div', 'section-title', 'Token Metadata'));
500
+ var grid = mk('div', 'token-grid');
501
+ function addStat(label, value) {
502
+ var s = mk('div', 'token-stat');
503
+ app(s, mk('div', 'token-stat-label', label), mk('div', 'token-stat-value', fmtNum(value, '\u2014')));
504
+ grid.appendChild(s);
505
+ }
506
+ if (tmeta && tmeta.confidence !== 'heuristic') {
507
+ var conf = (tmeta.confidence || tmeta.estimationMethod || 'estimated').toLowerCase();
508
+ var confBadge = mk('span', 'confidence-badge ' + conf, conf.toUpperCase());
509
+ var confWrap = mk('div', 'token-stat');
510
+ app(confWrap, mk('div', 'token-stat-label', 'Confidence'), confBadge);
511
+ grid.appendChild(confWrap);
512
+ if (tmeta.totalEstimatedTokens || tmeta.total_tokens) addStat('Total Tokens', tmeta.totalEstimatedTokens || tmeta.total_tokens);
513
+ if (tmeta.inputTokens) addStat('Input', tmeta.inputTokens);
514
+ if (tmeta.outputTokens) addStat('Output', tmeta.outputTokens);
515
+ if (tmeta.cacheReadTokens) addStat('Cache Read', tmeta.cacheReadTokens);
516
+ } else if (g.entryCount) {
517
+ var coarseBadge = mk('span', 'confidence-badge coarse', 'COARSE');
518
+ var cWrap = mk('div', 'token-stat');
519
+ app(cWrap, mk('div', 'token-stat-label', 'Confidence'), coarseBadge);
520
+ grid.appendChild(cWrap);
521
+ addStat('Estimate', '~' + (g.entryCount * 150) + 't');
522
+ addStat('Basis', g.entryCount + ' entries x ~150t');
523
+ } else {
524
+ addStat('Note', 'Enable OTEL for token data');
525
+ }
526
+ app(tokCard, grid);
527
+ container.appendChild(tokCard);
528
+ }
529
+
530
+ // ── Tab 2: Sessions ───────────────────────────────────────────────────────────
531
+ function renderSessionsTab(sessions) {
532
+ var container = document.getElementById('sessions-content');
533
+ container.textContent = '';
534
+ if (!sessions || !sessions.length) {
535
+ container.appendChild(emptyState('No sessions found', 'Session data will appear here after graded sessions run.'));
536
+ return;
537
+ }
538
+ document.getElementById('tb-sessions').textContent = sessions.length;
539
+
540
+ // Grade coverage summary
541
+ var summary = DATA.grade_summary || {};
542
+ if (summary.total > 0) {
543
+ var statsRow = mk('div', 'token-summary');
544
+ function gstat(label, val, color) {
545
+ var el = mk('div', 'token-stat');
546
+ var lbl = document.createElement('span'); lbl.textContent = label + ': ';
547
+ var v = document.createElement('strong');
548
+ v.textContent = val != null ? String(val) : '\u2014';
549
+ if (color) v.style.color = color;
550
+ el.appendChild(lbl); el.appendChild(v);
551
+ return el;
552
+ }
553
+ var letter = summary.avgScore != null ? scoreToLetter(summary.avgScore) : null;
554
+ app(statsRow,
555
+ gstat('Total grades', summary.total),
556
+ gstat('Graded sessions', summary.graded),
557
+ gstat('Avg score', summary.avgScore != null ? summary.avgScore + '/100' : null, letter ? gradeColor(letter) : null),
558
+ gstat('Best', summary.maxScore),
559
+ gstat('Worst', summary.minScore)
560
+ );
561
+ // Distribution badges
562
+ if (summary.distribution) {
563
+ var distEl = mk('div', 'token-stat');
564
+ var distLbl = document.createElement('span'); distLbl.textContent = 'Distribution: ';
565
+ distEl.appendChild(distLbl);
566
+ ['A','B','C','D','F'].forEach(function(l) {
567
+ var n = summary.distribution[l];
568
+ if (n) {
569
+ var badge = mk('span', 'pill', l + ':' + n);
570
+ badge.style.cssText = 'background:' + gradeColorBg(l) + ';color:' + gradeColor(l) + ';margin-left:3px;padding:.1em .4em;border-radius:3px;font-size:.65rem;font-weight:700;';
571
+ distEl.appendChild(badge);
572
+ }
573
+ });
574
+ statsRow.appendChild(distEl);
575
+ }
576
+ container.appendChild(statsRow);
577
+ }
578
+
579
+ // Filter bar
580
+ var filterBar = mk('div', 'filter-bar');
581
+ var statusSel = document.createElement('select');
582
+ [['all', 'All'], ['active', 'Active'], ['ended', 'Ended']].forEach(function(opt) {
583
+ var o = document.createElement('option'); o.value = opt[0]; o.textContent = opt[1]; statusSel.appendChild(o);
584
+ });
585
+ var sortSel = document.createElement('select');
586
+ [['newest', 'Newest first'], ['oldest', 'Oldest first'], ['score', 'By score']].forEach(function(opt) {
587
+ var o = document.createElement('option'); o.value = opt[0]; o.textContent = opt[1]; sortSel.appendChild(o);
588
+ });
589
+ var searchInput = document.createElement('input');
590
+ searchInput.type = 'text'; searchInput.placeholder = 'Search sessions...';
591
+ app(filterBar, mk('label', null, 'Status:'), statusSel, mk('label', null, 'Sort:'), sortSel, searchInput);
592
+ container.appendChild(filterBar);
593
+
594
+ var tableWrap = mk('div', 'table-wrap');
595
+ container.appendChild(tableWrap);
596
+
597
+ function renderTable() {
598
+ tableWrap.textContent = '';
599
+ var filtered = sessions.slice();
600
+ var statusVal = statusSel.value;
601
+ if (statusVal !== 'all') {
602
+ filtered = filtered.filter(function(s) {
603
+ var st = (s.status || '').toLowerCase();
604
+ return statusVal === 'active' ? (st === 'active' || st === 'running') : (st === 'ended' || st === 'completed');
605
+ });
606
+ }
607
+ var q = searchInput.value.toLowerCase();
608
+ if (q) {
609
+ filtered = filtered.filter(function(s) {
610
+ return (s.name || '').toLowerCase().indexOf(q) >= 0 ||
611
+ (s.sessionId || '').toLowerCase().indexOf(q) >= 0 ||
612
+ (s.scope || '').toLowerCase().indexOf(q) >= 0;
613
+ });
614
+ }
615
+ var sortVal = sortSel.value;
616
+ filtered.sort(function(a, b) {
617
+ if (sortVal === 'score') return (b.gradeScore || 0) - (a.gradeScore || 0);
618
+ if (sortVal === 'oldest') return new Date(a.startedAt || 0) - new Date(b.startedAt || 0);
619
+ return new Date(b.startedAt || 0) - new Date(a.startedAt || 0);
620
+ });
621
+
622
+ if (!filtered.length) {
623
+ tableWrap.appendChild(emptyState('No matching sessions', 'Try adjusting your filters.'));
624
+ return;
625
+ }
626
+
627
+ var tbl = document.createElement('table'); tbl.className = 'data-table';
628
+ var thead = document.createElement('thead');
629
+ var hrow = document.createElement('tr');
630
+ ['Status', 'Session Name', 'Session ID', 'Scope', 'Started', 'Duration', 'Grade Score', 'Grade Letter', 'Audit Entries', 'MCP Calls', 'CLI Calls'].forEach(function(h) {
631
+ hrow.appendChild(mk('th', null, h));
632
+ });
633
+ thead.appendChild(hrow); tbl.appendChild(thead);
634
+ var tbody = document.createElement('tbody');
635
+
636
+ filtered.forEach(function(s) {
637
+ var row = document.createElement('tr'); row.className = 'session-row';
638
+ var isActive = (s.status || '').toLowerCase() === 'active' || (s.status || '').toLowerCase() === 'running';
639
+ var letter = s.gradeLetter || (s.gradeScore != null ? scoreToLetter(s.gradeScore) : '\u2014');
640
+
641
+ // Status pill
642
+ var statusTd = document.createElement('td');
643
+ if (isActive) {
644
+ var dot = mk('span', 'pulse-dot');
645
+ statusTd.appendChild(dot);
646
+ }
647
+ statusTd.appendChild(mk('span', 'pill ' + (isActive ? 'active' : 'ended'), isActive ? 'Active' : 'Ended'));
648
+ row.appendChild(statusTd);
649
+
650
+ // Name with grade mode badge
651
+ var nameTd = document.createElement('td');
652
+ if (s.gradeMode) {
653
+ var gmBadge = mk('span', 'gw-pill query', '\u{1F4CA}');
654
+ gmBadge.style.cssText = 'font-size:.6rem;padding:.1em .3em;margin-right:.35rem;';
655
+ gmBadge.title = 'Graded session';
656
+ nameTd.appendChild(gmBadge);
657
+ }
658
+ var nameSpan = document.createElement('span');
659
+ nameSpan.textContent = s.name || '\u2014';
660
+ nameTd.appendChild(nameSpan);
661
+ row.appendChild(nameTd);
662
+ // Session ID chip
663
+ var idTd = document.createElement('td');
664
+ idTd.appendChild(mk('code', 'session-chip', (s.sessionId || '').slice(0, 18)));
665
+ row.appendChild(idTd);
666
+ // Scope
667
+ row.appendChild(mk('td', null, s.scope || '\u2014'));
668
+ // Started
669
+ row.appendChild(mk('td', null, fmtDate(s.startedAt)));
670
+ // Duration
671
+ row.appendChild(mk('td', null, s.durationMs ? fmtDuration(s.durationMs) : '\u2014'));
672
+ // Grade Score
673
+ var scoreTd = mk('td', null, s.gradeScore != null ? String(s.gradeScore) : '\u2014');
674
+ if (s.gradeScore != null) scoreTd.style.color = gradeColor(scoreToLetter(s.gradeScore));
675
+ row.appendChild(scoreTd);
676
+ // Grade Letter
677
+ var letterTd = mk('td');
678
+ if (letter !== '\u2014') {
679
+ letterTd.style.fontWeight = '700';
680
+ letterTd.style.color = gradeColor(letter);
681
+ }
682
+ letterTd.textContent = letter;
683
+ row.appendChild(letterTd);
684
+ // Audit entries
685
+ row.appendChild(mk('td', null, fmtNum(s.auditEntries, '0')));
686
+ // MCP Calls
687
+ row.appendChild(mk('td', null, fmtNum(s.mcpCalls, '0')));
688
+ // CLI Calls
689
+ row.appendChild(mk('td', null, fmtNum(s.cliCalls, '0')));
690
+
691
+ tbody.appendChild(row);
692
+
693
+ // Expandable detail row
694
+ var expandRow = document.createElement('tr'); expandRow.className = 'session-expand';
695
+ var expandTd = document.createElement('td'); expandTd.colSpan = 11;
696
+
697
+ row.addEventListener('click', function() {
698
+ var open = expandRow.classList.toggle('open');
699
+ if (open && !expandTd.hasChildNodes()) {
700
+ renderSessionExpand(s, expandTd);
701
+ }
702
+ });
703
+ expandRow.appendChild(expandTd);
704
+ tbody.appendChild(expandRow);
705
+ });
706
+ tbl.appendChild(tbody);
707
+ tableWrap.appendChild(tbl);
708
+ }
709
+
710
+ statusSel.addEventListener('change', renderTable);
711
+ sortSel.addEventListener('change', renderTable);
712
+ searchInput.addEventListener('input', renderTable);
713
+ renderTable();
714
+ }
715
+
716
+ function renderSessionExpand(s, td) {
717
+ var wrap = mk('div', 'session-detail');
718
+ td.appendChild(wrap);
719
+
720
+ // ── Header: session metadata ──────────────────────────────────────────────
721
+ var hdr = mk('div', 'session-detail-header');
722
+ function stat(label, val) {
723
+ var el = mk('div', 'session-detail-stat');
724
+ var lbl = document.createElement('span'); lbl.textContent = label + ': ';
725
+ var v = document.createElement('strong'); v.textContent = val || '\u2014';
726
+ el.appendChild(lbl); el.appendChild(v); return el;
727
+ }
728
+ app(hdr,
729
+ stat('ID', s.sessionId || s.id),
730
+ stat('Scope', s.scope || ((s.scopeType || '') + (s.scopeRootTaskId ? ':' + s.scopeRootTaskId : ''))),
731
+ stat('Started', fmtDate(s.startedAt)),
732
+ stat('Duration', s.durationMs ? fmtDuration(s.durationMs) : (s.endedAt ? fmtDuration(new Date(s.endedAt) - new Date(s.startedAt)) : 'Active')),
733
+ stat('Resumes', s.resumeCount != null ? String(s.resumeCount) : '0'),
734
+ stat('Tasks Done', s.tasksCompleted != null ? String(s.tasksCompleted) : '\u2014')
735
+ );
736
+
737
+ // Grade badge inline
738
+ if (s.gradeScore != null) {
739
+ var letter = s.gradeLetter || scoreToLetter(s.gradeScore);
740
+ var gbadge = mk('div', 'letter-badge sm ' + letter, letter);
741
+ gbadge.style.cssText = 'width:24px;height:24px;font-size:.75rem;display:inline-flex;align-items:center;justify-content:center;border-radius:4px;font-weight:700;background:' + gradeColorBg(letter) + ';color:' + gradeColor(letter) + ';flex-shrink:0;';
742
+ var ginfo = mks('span', 'font-size:.75rem;color:var(--text-muted);margin-left:.4rem;', s.gradeScore + '/100 \u2014 Grade ' + letter);
743
+ var gline = mk('div', 'session-detail-stat');
744
+ gline.style.display = 'flex'; gline.style.alignItems = 'center';
745
+ app(gline, gbadge, ginfo);
746
+ hdr.appendChild(gline);
747
+ }
748
+ wrap.appendChild(hdr);
749
+
750
+ // ── Token summary (if available) ─────────────────────────────────────────
751
+ if (s.totalTokens || s.tokenRecords) {
752
+ var tsumm = mk('div', 'token-summary');
753
+ function tstat(label, val) {
754
+ var el = mk('div', 'token-stat');
755
+ var lbl = document.createElement('span'); lbl.textContent = label + ': ';
756
+ var v = document.createElement('strong'); v.textContent = val != null ? String(val) : '\u2014';
757
+ el.appendChild(lbl); el.appendChild(v); return el;
758
+ }
759
+ app(tsumm,
760
+ tstat('Total tokens', s.totalTokens ? fmtNum(s.totalTokens) : '\u2014'),
761
+ tstat('Token records', s.tokenRecords || 0)
762
+ );
763
+ wrap.appendChild(tsumm);
764
+ }
765
+
766
+ // ── Grade dimension mini-chart (from session-grade join) ────────────────
767
+ var gradeDetails = s.gradeDetails || {};
768
+ var gradeFlags = s.gradeFlags || [];
769
+ if (gradeDetails && typeof gradeDetails === 'object' && Object.keys(gradeDetails).length) {
770
+ var dims = ['sessionDiscipline','discoveryEfficiency','taskHygiene','errorProtocol','disclosureUse'];
771
+ var dimNames = ['S1 Discipline','S2 Efficiency','S3 Hygiene','S4 Protocol','S5 MCP Gateway'];
772
+ var dimColors = ['#a371f7','#58a6ff','#3fb950','#e3b341','#f78166'];
773
+ var dimCard = mk('div', 'token-summary');
774
+ dimCard.style.flexDirection = 'column';
775
+ dims.forEach(function(k, i) {
776
+ var dim = gradeDetails[k] || {};
777
+ var score = dim.score != null ? dim.score : (typeof dim === 'number' ? dim : null);
778
+ var maxS = dim.max != null ? dim.max : 20;
779
+ if (score === null) return;
780
+ var row = mk('div', '');
781
+ row.style.cssText = 'display:flex;align-items:center;gap:.5rem;margin:.2rem 0;';
782
+ var label = mk('span', '');
783
+ label.style.cssText = 'font-size:.68rem;color:var(--text-muted);width:100px;flex-shrink:0;';
784
+ label.textContent = dimNames[i];
785
+ var track = mk('div', '');
786
+ track.style.cssText = 'flex:1;height:6px;background:var(--surface2);border-radius:3px;overflow:hidden;';
787
+ var fill = mk('div', '');
788
+ var pct = maxS > 0 ? Math.min(100, (score / maxS) * 100) : 0;
789
+ fill.style.cssText = 'height:100%;border-radius:3px;background:' + dimColors[i] + ';width:' + pct + '%;';
790
+ track.appendChild(fill);
791
+ var val = mk('span', '');
792
+ val.style.cssText = 'font-family:var(--mono);font-size:.65rem;color:var(--text-muted);width:40px;flex-shrink:0;text-align:right;';
793
+ val.textContent = score + '/' + maxS;
794
+ app(row, label, track, val);
795
+ dimCard.appendChild(row);
796
+ });
797
+ if (gradeFlags.length) {
798
+ var flagRow = mk('div', '');
799
+ flagRow.style.cssText = 'margin-top:.4rem;display:flex;gap:.3rem;flex-wrap:wrap;';
800
+ gradeFlags.forEach(function(f) {
801
+ flagRow.appendChild(mk('span', 'pill', f.replace(/_/g,' ')));
802
+ });
803
+ dimCard.appendChild(flagRow);
804
+ }
805
+ wrap.appendChild(dimCard);
806
+ }
807
+
808
+ // ── Audit log section (fetched from /sessions-data) ───────────────────────
809
+ var auditTitle = mk('div', 'audit-section-title');
810
+ var countChip = mk('span', 'audit-count-chip', s.auditEntries ? fmtNum(s.auditEntries) + ' entries' : '…');
811
+ auditTitle.textContent = 'Audit Log';
812
+ auditTitle.appendChild(countChip);
813
+ wrap.appendChild(auditTitle);
814
+
815
+ var tableWrap = mk('div', 'audit-table-wrap');
816
+ wrap.appendChild(tableWrap);
817
+ var loadingMsg = mk('div', 'audit-loading', 'Loading audit entries…');
818
+ tableWrap.appendChild(loadingMsg);
819
+
820
+ // Fetch full audit data from the server
821
+ var sid = s.sessionId || s.id || '';
822
+ if (!sid) {
823
+ loadingMsg.textContent = 'No session ID available.';
824
+ return;
825
+ }
826
+
827
+ fetch('/sessions-data?sessionId=' + encodeURIComponent(sid))
828
+ .then(function(r) { return r.json(); })
829
+ .then(function(data) {
830
+ tableWrap.textContent = '';
831
+ var entries = (data.entries || []);
832
+ var tokens = data.tokens || {};
833
+
834
+ // Update token summary if richer data available
835
+ if (tokens.total_tokens && !s.totalTokens) {
836
+ var tsumm2 = mk('div', 'token-summary');
837
+ function tstat2(label, val) {
838
+ var el = mk('div', 'token-stat');
839
+ var lbl = document.createElement('span'); lbl.textContent = label + ': ';
840
+ var v = document.createElement('strong'); v.textContent = val != null ? String(val) : '\u2014';
841
+ el.appendChild(lbl); el.appendChild(v); return el;
842
+ }
843
+ app(tsumm2,
844
+ tstat2('Total tokens', fmtNum(tokens.total_tokens)),
845
+ tstat2('Input', fmtNum(tokens.input_tokens || 0)),
846
+ tstat2('Output', fmtNum(tokens.output_tokens || 0)),
847
+ tstat2('Records', tokens.records || 0)
848
+ );
849
+ wrap.insertBefore(tsumm2, auditTitle);
850
+ }
851
+
852
+ // ── Rich session detail sections ────────────────────────────────────
853
+ var sess = data.session || {};
854
+
855
+ // 1. Stats card from session.stats
856
+ var sstats = sess.stats;
857
+ if (sstats) {
858
+ var statsCard = mk('div', 'token-summary');
859
+ function sstat(label, val) {
860
+ var el = mk('div', 'token-stat');
861
+ var lbl = document.createElement('span'); lbl.textContent = label + ': ';
862
+ var v = document.createElement('strong'); v.textContent = val != null ? String(val) : '\u2014';
863
+ el.appendChild(lbl); el.appendChild(v); return el;
864
+ }
865
+ app(statsCard,
866
+ sstat('Tasks Done', sstats.tasksCompleted),
867
+ sstat('Tasks Created', sstats.tasksCreated),
868
+ sstat('Focus Changes', sstats.focusChanges),
869
+ sstat('Active', sstats.totalActiveMinutes != null ? sstats.totalActiveMinutes + ' min' : '\u2014'),
870
+ sstat('Suspends', sstats.suspendCount),
871
+ sstat('Resumes', sess.resumeCount != null ? sess.resumeCount : 0)
872
+ );
873
+ wrap.insertBefore(statsCard, auditTitle);
874
+ }
875
+
876
+ // 2. Session chain (prev / current / next)
877
+ if (sess.previousSessionId || sess.nextSessionId) {
878
+ var chainDiv = mk('div', 'session-chain');
879
+ var chainLabel = mk('span', 'session-chain-label', 'Session Chain');
880
+ chainDiv.appendChild(chainLabel);
881
+
882
+ if (sess.previousSessionId) {
883
+ var prevChip = mk('code', 'session-chip', '\u2190 ' + sess.previousSessionId);
884
+ chainDiv.appendChild(prevChip);
885
+ } else {
886
+ var prevDim = mk('span', 'chain-sep', '\u2014');
887
+ chainDiv.appendChild(prevDim);
888
+ }
889
+
890
+ var sep1 = mk('span', 'chain-sep', '\u2022');
891
+ chainDiv.appendChild(sep1);
892
+
893
+ var curChip = mk('code', 'session-chip chain-current', sess.id || sid);
894
+ chainDiv.appendChild(curChip);
895
+
896
+ var sep2 = mk('span', 'chain-sep', '\u2022');
897
+ chainDiv.appendChild(sep2);
898
+
899
+ if (sess.nextSessionId) {
900
+ var nextChip = mk('code', 'session-chip', sess.nextSessionId + ' \u2192');
901
+ chainDiv.appendChild(nextChip);
902
+ } else {
903
+ var nextDim = mk('span', 'chain-sep', '\u2014');
904
+ chainDiv.appendChild(nextDim);
905
+ }
906
+
907
+ wrap.insertBefore(chainDiv, auditTitle);
908
+ }
909
+
910
+ // 3. Scope section
911
+ var scope = sess.scope;
912
+ if (scope && scope.computedTaskIds && scope.computedTaskIds.length) {
913
+ var scopeDiv = mk('div', 'session-scope');
914
+ var scopeLabel = document.createElement('span');
915
+ scopeLabel.textContent = 'Scope: ';
916
+ var scopeStrong = document.createElement('strong');
917
+ scopeStrong.textContent = (scope.type || 'epic') + ':' + (scope.rootTaskId || '\u2014');
918
+ var scopeCount = document.createElement('span');
919
+ scopeCount.textContent = ' \u2022 ' + scope.computedTaskIds.length + ' tasks in scope';
920
+ scopeDiv.appendChild(scopeLabel);
921
+ scopeDiv.appendChild(scopeStrong);
922
+ scopeDiv.appendChild(scopeCount);
923
+ wrap.insertBefore(scopeDiv, auditTitle);
924
+ }
925
+
926
+ // 4. Debrief section
927
+ var debrief = sess.debrief;
928
+ if (debrief) {
929
+ var ho = debrief.handoff || debrief;
930
+ var debriefCard = mk('div', 'debrief-card');
931
+
932
+ // Note
933
+ if (ho.note) {
934
+ var noteDiv = mk('div', 'debrief-note', ho.note);
935
+ debriefCard.appendChild(noteDiv);
936
+ }
937
+
938
+ // Git state
939
+ var git = ho.gitState || debrief.gitState;
940
+ if (git) {
941
+ var gitRow = mk('div', 'debrief-row');
942
+ var gitLabel = mk('span', 'debrief-label', 'Git');
943
+ gitRow.appendChild(gitLabel);
944
+ if (git.branch) {
945
+ var brSpan = mk('span', 'git-branch', git.branch);
946
+ gitRow.appendChild(brSpan);
947
+ }
948
+ if (git.commit) {
949
+ var hashSpan = mk('span', 'git-hash', String(git.commit).slice(0, 7));
950
+ gitRow.appendChild(hashSpan);
951
+ }
952
+ if (git.uncommittedChanges || git.dirty) {
953
+ var ucBadge = mk('span', 'uncommitted-badge', 'uncommitted changes');
954
+ gitRow.appendChild(ucBadge);
955
+ } else {
956
+ var cleanBadge = mk('span', 'clean-badge', 'clean');
957
+ gitRow.appendChild(cleanBadge);
958
+ }
959
+ debriefCard.appendChild(gitRow);
960
+ }
961
+
962
+ // Chain position
963
+ var chainPos = ho.chainPosition || debrief.chainPosition;
964
+ var chainLen = ho.chainLength || debrief.chainLength;
965
+ if (chainPos != null && chainLen != null) {
966
+ var posRow = mk('div', 'debrief-row');
967
+ var posLabel = mk('span', 'debrief-label', 'Chain');
968
+ posRow.appendChild(posLabel);
969
+ var posVal = document.createElement('span');
970
+ posVal.textContent = 'Position ' + chainPos + ' of ' + chainLen;
971
+ posRow.appendChild(posVal);
972
+ debriefCard.appendChild(posRow);
973
+ }
974
+
975
+ // Next suggested
976
+ var nextSugg = ho.nextSuggested || debrief.nextSuggested;
977
+ if (nextSugg && nextSugg.length) {
978
+ var nsRow = mk('div', 'debrief-row');
979
+ var nsLabel = mk('span', 'debrief-label', 'Next');
980
+ nsRow.appendChild(nsLabel);
981
+ nextSugg.forEach(function(tid) {
982
+ var chip = mk('code', 'session-chip', tid);
983
+ nsRow.appendChild(chip);
984
+ });
985
+ debriefCard.appendChild(nsRow);
986
+ }
987
+
988
+ // Open blockers
989
+ var blockers = ho.openBlockers || debrief.openBlockers;
990
+ if (blockers && blockers.length) {
991
+ var blRow = mk('div', 'debrief-row');
992
+ var blLabel = mk('span', 'debrief-label', 'Blockers');
993
+ blRow.appendChild(blLabel);
994
+ var blCount = document.createElement('span');
995
+ blCount.textContent = blockers.length;
996
+ blCount.style.color = 'var(--grade-d)';
997
+ blCount.style.fontWeight = '600';
998
+ blRow.appendChild(blCount);
999
+ debriefCard.appendChild(blRow);
1000
+ }
1001
+
1002
+ wrap.insertBefore(debriefCard, auditTitle);
1003
+ }
1004
+
1005
+ // 5. Session notes
1006
+ var notes = sess.notes;
1007
+ if (notes && notes.length) {
1008
+ var notesWrap = mk('div', '');
1009
+ var toggle = mk('span', 'notes-toggle', 'Session Notes (' + notes.length + ') \u25B6');
1010
+ var notesList = mk('div', 'notes-list');
1011
+
1012
+ var displayNotes = notes.slice(-5);
1013
+ displayNotes.forEach(function(n) {
1014
+ var entry = mk('div', 'note-entry');
1015
+ var ts = mk('span', 'note-ts', '');
1016
+ var noteText = typeof n === 'string' ? n : (n.text || n.note || '');
1017
+ var noteTs = typeof n === 'object' && n.timestamp ? fmtTime(n.timestamp) : '';
1018
+ ts.textContent = noteTs ? '[' + noteTs + ']' : '';
1019
+ var txt = mk('span', 'note-text', noteText);
1020
+ entry.appendChild(ts);
1021
+ entry.appendChild(txt);
1022
+ notesList.appendChild(entry);
1023
+ });
1024
+
1025
+ toggle.addEventListener('click', function() {
1026
+ var open = notesList.classList.toggle('open');
1027
+ toggle.textContent = 'Session Notes (' + notes.length + ') ' + (open ? '\u25BC' : '\u25B6');
1028
+ });
1029
+
1030
+ notesWrap.appendChild(toggle);
1031
+ notesWrap.appendChild(notesList);
1032
+ wrap.insertBefore(notesWrap, auditTitle);
1033
+ }
1034
+
1035
+ // Update count chip
1036
+ countChip.textContent = fmtNum(entries.length) + ' entries';
1037
+
1038
+ if (!entries.length) {
1039
+ tableWrap.appendChild(mks('div', 'color:var(--text-muted);font-size:.75rem;padding:.5rem 0;', 'No audit entries found for this session.'));
1040
+ return;
1041
+ }
1042
+
1043
+ // Build full audit table
1044
+ var tbl = document.createElement('table'); tbl.className = 'audit-table';
1045
+ var thead = document.createElement('thead');
1046
+ var hrow = document.createElement('tr');
1047
+ ['Time', 'Domain', 'Operation', 'Source', 'Gateway', 'Duration ms', 'Success'].forEach(function(h) {
1048
+ var th = document.createElement('th'); th.textContent = h; hrow.appendChild(th);
1049
+ });
1050
+ thead.appendChild(hrow); tbl.appendChild(thead);
1051
+
1052
+ var tbody = document.createElement('tbody');
1053
+ entries.forEach(function(e) {
1054
+ var tr = document.createElement('tr');
1055
+
1056
+ // Time
1057
+ var timeTd = document.createElement('td');
1058
+ timeTd.className = 'audit-time';
1059
+ timeTd.textContent = fmtTime(e.timestamp);
1060
+ tr.appendChild(timeTd);
1061
+
1062
+ // Domain
1063
+ var domTd = document.createElement('td');
1064
+ domTd.className = 'audit-domain';
1065
+ domTd.textContent = e.domain || '\u2014';
1066
+ tr.appendChild(domTd);
1067
+
1068
+ // Operation
1069
+ var opTd = document.createElement('td');
1070
+ opTd.className = 'audit-op';
1071
+ opTd.textContent = e.operation || '\u2014';
1072
+ tr.appendChild(opTd);
1073
+
1074
+ // Source
1075
+ var srcTd = document.createElement('td');
1076
+ srcTd.className = 'audit-source';
1077
+ srcTd.textContent = e.source || '\u2014';
1078
+ tr.appendChild(srcTd);
1079
+
1080
+ // Gateway pill
1081
+ var gwTd = document.createElement('td');
1082
+ var gw = (e.gateway || '').toUpperCase();
1083
+ var pill = mk('span', 'gw-pill ' + (e.gateway || '').toLowerCase(), gw || '\u2014');
1084
+ gwTd.appendChild(pill);
1085
+ tr.appendChild(gwTd);
1086
+
1087
+ // Duration ms
1088
+ var ms = e.duration_ms != null ? e.duration_ms : null;
1089
+ var msTd = document.createElement('td');
1090
+ msTd.className = 'audit-ms' + (ms > 2000 ? ' vslow' : ms > 500 ? ' slow' : '');
1091
+ msTd.textContent = ms != null ? ms : '\u2014';
1092
+ tr.appendChild(msTd);
1093
+
1094
+ // Success
1095
+ var sucTd = document.createElement('td');
1096
+ sucTd.className = 'audit-success';
1097
+ var sucSpan = document.createElement('span');
1098
+ sucSpan.textContent = e.success ? '\u2713' : '\u2717';
1099
+ sucSpan.style.color = e.success ? 'var(--grade-a)' : 'var(--grade-f)';
1100
+ sucSpan.style.fontWeight = '700';
1101
+ sucTd.appendChild(sucSpan);
1102
+ tr.appendChild(sucTd);
1103
+
1104
+ tbody.appendChild(tr);
1105
+ });
1106
+ tbl.appendChild(tbody);
1107
+ tableWrap.appendChild(tbl);
1108
+ })
1109
+ .catch(function(err) {
1110
+ tableWrap.textContent = '';
1111
+ tableWrap.appendChild(mks('div', 'color:var(--grade-f);font-size:.75rem;padding:.5rem 0;', 'Failed to load audit entries: ' + err.message));
1112
+ });
1113
+ }
1114
+
1115
+ function fmtTime(ts) {
1116
+ if (!ts) return '\u2014';
1117
+ try {
1118
+ var d = new Date(ts);
1119
+ return d.toLocaleTimeString([], {hour: '2-digit', minute: '2-digit', second: '2-digit'});
1120
+ } catch(e) { return String(ts).slice(11, 19) || ts; }
1121
+ }
1122
+
1123
+ function gradeColorBg(l) {
1124
+ var m = {'A':'var(--grade-a-bg)','B':'var(--grade-b-bg)','C':'var(--grade-c-bg)','D':'var(--grade-d-bg)','F':'var(--grade-f-bg)'};
1125
+ return m[l] || 'var(--surface2)';
1126
+ }
1127
+
1128
+ // ── Tab 3: A/B Comparison ─────────────────────────────────────────────────────
1129
+ function renderABTab(abResults, abHistory) {
1130
+ var container = document.getElementById('ab-content');
1131
+ container.textContent = '';
1132
+ var history = abHistory || [];
1133
+ if (!history.length && (!abResults || !abResults.total_runs)) {
1134
+ container.appendChild(emptyState('No A/B results yet', 'Run a blind comparison to generate data.', 'python scripts/run_ab_test.py --domain tasks --runs 3'));
1135
+ return;
1136
+ }
1137
+
1138
+ // Run selector
1139
+ if (history.length) {
1140
+ var selWrap = mk('div', 'filter-bar');
1141
+ selWrap.appendChild(mk('label', null, 'Run:'));
1142
+ var runSel = document.createElement('select');
1143
+ history.sort(function(a, b) { return new Date(b.createdAt || 0) - new Date(a.createdAt || 0); });
1144
+ history.forEach(function(run, i) {
1145
+ var o = document.createElement('option'); o.value = i;
1146
+ o.textContent = (run.name || 'Run ' + (i + 1)) + ' \u2014 ' + fmtDate(run.createdAt);
1147
+ runSel.appendChild(o);
1148
+ });
1149
+ app(selWrap, runSel);
1150
+ container.appendChild(selWrap);
1151
+ }
1152
+
1153
+ renderABContent(abResults, container);
1154
+ document.getElementById('tb-ab').textContent = (abResults && abResults.total_runs) || history.length || 0;
1155
+ }
1156
+
1157
+ function renderABContent(ab, container) {
1158
+ if (!ab || !ab.total_runs) return;
1159
+ var wins = ab.global_wins || {}, wr = ab.global_win_rate || {};
1160
+ var delta = ab.avg_token_delta_mcp_minus_cli;
1161
+ var winner = ab.overall_winner || 'tie';
1162
+
1163
+ // Winner banner
1164
+ var banner = mk('div', 'winner-banner ' + winner);
1165
+ var wlabel = winner === 'mcp' ? 'MCP wins overall' : winner === 'cli' ? 'CLI wins overall' : 'Overall tie';
1166
+ banner.appendChild(mk('span', null, wlabel));
1167
+ if (delta != null) {
1168
+ var sign = delta > 0 ? '+' : '';
1169
+ banner.appendChild(mks('span', 'font-size:.7rem;font-weight:400;opacity:.8;margin-left:.5rem;',
1170
+ 'MCP uses ' + sign + delta.toFixed(1) + ' tokens/op vs CLI'));
1171
+ }
1172
+ container.appendChild(banner);
1173
+
1174
+ // Summary grid
1175
+ container.appendChild(mk('div', 'section-title', 'Global Stats'));
1176
+ var grid = mk('div', 'stat-grid');
1177
+ [
1178
+ ['Total Runs', ab.total_runs, ''],
1179
+ ['Arm A Wins', wins.mcp || 0, 'win'],
1180
+ ['Arm B Wins', wins.cli || 0, winner === 'cli' ? 'win' : ''],
1181
+ ['Ties', wins.tie || 0, 'neutral'],
1182
+ ].forEach(function(row) {
1183
+ var c = mk('div', 'stat-card');
1184
+ app(c, mk('div', 'stat-label', row[0]), mk('div', 'stat-value ' + row[2], String(row[1])));
1185
+ grid.appendChild(c);
1186
+ });
1187
+ container.appendChild(grid);
1188
+
1189
+ // Per-operation table
1190
+ var rawPerOp = ab.per_operation;
1191
+ var perOp = null;
1192
+ if (Array.isArray(rawPerOp)) {
1193
+ perOp = {};
1194
+ rawPerOp.forEach(function(s) { if (s && s.operation) perOp[s.operation] = s; });
1195
+ } else if (rawPerOp && typeof rawPerOp === 'object') {
1196
+ perOp = rawPerOp;
1197
+ }
1198
+ if (perOp && Object.keys(perOp).length) {
1199
+ container.appendChild(mk('div', 'section-title', 'Per-Operation Results'));
1200
+ var wrap = mk('div', 'table-wrap');
1201
+ var tbl = document.createElement('table'); tbl.className = 'data-table';
1202
+ var thead = document.createElement('thead'), hrow = document.createElement('tr');
1203
+ ['Operation', 'Winner', 'MCP Calls', 'CLI Calls', 'Token \u0394', 'MCP ms', 'CLI ms', 'MCP Chars', 'CLI Chars'].forEach(function(h) {
1204
+ hrow.appendChild(mk('th', null, h));
1205
+ });
1206
+ thead.appendChild(hrow); tbl.appendChild(thead);
1207
+ var tbody = document.createElement('tbody');
1208
+ Object.keys(perOp).sort().forEach(function(opKey) {
1209
+ var d = perOp[opKey], row = document.createElement('tr');
1210
+ var dt = d.avg_token_delta != null ? d.avg_token_delta : 0;
1211
+ var dcls = dt > 10 ? 'delta-pos' : dt < -10 ? 'delta-neg' : 'delta-zero';
1212
+ var opW = d.winner || (d.mcp_wins > d.cli_wins ? 'mcp' : d.cli_wins > d.mcp_wins ? 'cli' : 'tie');
1213
+ var cells = [
1214
+ mk('span', 'op-name', opKey),
1215
+ mk('span', 'pill ' + opW, opW.toUpperCase()),
1216
+ document.createTextNode(String(d.mcp_wins || d.mcp_calls || 0)),
1217
+ document.createTextNode(String(d.cli_wins || d.cli_calls || 0)),
1218
+ mk('span', dcls, (dt > 0 ? '+' : '') + dt.toFixed(0) + 't'),
1219
+ document.createTextNode(fmtNum(d.avg_mcp_ms) + 'ms'),
1220
+ document.createTextNode(fmtNum(d.avg_cli_ms) + 'ms'),
1221
+ document.createTextNode(fmtNum(d.avg_mcp_chars)),
1222
+ document.createTextNode(fmtNum(d.avg_cli_chars)),
1223
+ ];
1224
+ cells.forEach(function(c) { var td = document.createElement('td'); td.appendChild(c); row.appendChild(td); });
1225
+ tbody.appendChild(row);
1226
+ });
1227
+ tbl.appendChild(tbody); wrap.appendChild(tbl); container.appendChild(wrap);
1228
+ }
1229
+ }
1230
+
1231
+ // ── Tab 4: Token Economy ──────────────────────────────────────────────────────
1232
+ function renderTokensTab(ta) {
1233
+ var container = document.getElementById('tokens-content');
1234
+ container.textContent = '';
1235
+ if (!ta || (!ta.by_transport && !ta.by_domain)) {
1236
+ container.appendChild(emptyState('No token data yet', 'Token economy data will appear after graded sessions with OTEL enabled.', 'source .cleo/setup-otel.sh'));
1237
+ return;
1238
+ }
1239
+
1240
+ var apiSurface = DATA.api_surface || {};
1241
+ if (apiSurface.canonical || apiSurface.webApi) {
1242
+ var apiCard = mk('div', 'api-card');
1243
+ apiCard.appendChild(mk('div', 'chart-title', 'API Guidance'));
1244
+ var apiGrid = mk('div', 'api-grid');
1245
+
1246
+ function apiBox(title, items) {
1247
+ var box = mk('div', 'api-box');
1248
+ box.appendChild(mk('div', 'api-box-title', title));
1249
+ var list = mk('div', 'api-list');
1250
+ (items || []).forEach(function(item) {
1251
+ var row = document.createElement('code');
1252
+ row.textContent = item;
1253
+ list.appendChild(row);
1254
+ });
1255
+ box.appendChild(list);
1256
+ return box;
1257
+ }
1258
+
1259
+ apiGrid.appendChild(apiBox('Canonical Query Ops', ((apiSurface.canonical || {}).query) || []));
1260
+ apiGrid.appendChild(apiBox('Compatibility Aliases', ((apiSurface.compatibility || {}).query) || []));
1261
+ apiGrid.appendChild(apiBox('Planned Grade Run Reads', apiSurface.planned || []));
1262
+ apiGrid.appendChild(apiBox('Web API', [
1263
+ ((apiSurface.webApi || {}).queryEndpoint) || '/api/query',
1264
+ ((apiSurface.webApi || {}).mutateEndpoint) || '/api/mutate'
1265
+ ].concat(((apiSurface.webApi || {}).lafsHeaders) || [])));
1266
+ apiCard.appendChild(apiGrid);
1267
+ if ((apiSurface.webApi || {}).transportAliasNote) {
1268
+ apiCard.appendChild(mk('div', 'note-block', apiSurface.webApi.transportAliasNote));
1269
+ }
1270
+ container.appendChild(apiCard);
1271
+ }
1272
+
1273
+ // Confidence badge
1274
+ if (ta.confidence) {
1275
+ var conf = ta.confidence.toLowerCase();
1276
+ var confCard = mk('div', 'card');
1277
+ confCard.style.cssText = 'display:flex;align-items:center;gap:.75rem;';
1278
+ confCard.appendChild(mk('div', 'section-title', 'Data Confidence'));
1279
+ confCard.appendChild(mk('span', 'confidence-badge ' + conf, conf.toUpperCase()));
1280
+ container.appendChild(confCard);
1281
+ }
1282
+
1283
+ // By Transport
1284
+ if (ta.by_transport && Object.keys(ta.by_transport).length) {
1285
+ var entries = Object.entries(ta.by_transport).map(function(kv) {
1286
+ return [kv[0], kv[1].mean || kv[1].avg || kv[1].total || 0, kv[1].count || kv[1].samples || 0];
1287
+ });
1288
+ var maxT = Math.max.apply(null, entries.map(function(e) { return e[1]; }).concat([1]));
1289
+ var tColors = { mcp: 'var(--mcp)', cli: 'var(--cli)', agent: 'var(--grade-c)', api: 'var(--accent)' };
1290
+ var card = mk('div', 'chart-card');
1291
+ card.appendChild(mk('div', 'chart-title', 'By Transport'));
1292
+ var chart = mk('div', 'bar-chart');
1293
+ entries.forEach(function(row) {
1294
+ var pct = maxT > 0 ? Math.min((row[1] / maxT) * 90, 90) : 0;
1295
+ var r = mk('div', 'bar-row');
1296
+ var track = mk('div', 'bar-track');
1297
+ var fill = mk('div', 'bar-fill');
1298
+ fill.style.cssText = 'width:' + pct + '%;background:' + (tColors[row[0]] || 'var(--accent)') + ';';
1299
+ track.appendChild(fill);
1300
+ app(r, mk('span', 'bar-label', row[0]), track, mk('span', 'bar-val', fmtNum(row[1]) + 't (n=' + row[2] + ')'));
1301
+ chart.appendChild(r);
1302
+ });
1303
+ card.appendChild(chart);
1304
+ if (entries.some(function(row) { return row[0] === 'http'; })) {
1305
+ card.appendChild(mk('div', 'note-block', 'Transport view normalizes legacy token rows labeled api into http, following CLEO-WEB-API v2.1.'));
1306
+ }
1307
+ container.appendChild(card);
1308
+ }
1309
+
1310
+ // By Domain
1311
+ if (ta.by_domain && Object.keys(ta.by_domain).length) {
1312
+ var dEntries = Object.entries(ta.by_domain).map(function(kv) {
1313
+ return [kv[0], kv[1].mean || kv[1].avg || kv[1].total || 0];
1314
+ });
1315
+ dEntries.sort(function(a, b) { return b[1] - a[1]; });
1316
+ var maxD = Math.max.apply(null, dEntries.map(function(e) { return e[1]; }).concat([1]));
1317
+ var domColors = ['#a371f7', '#58a6ff', '#3fb950', '#e3b341', '#f78166', '#f0883e', '#7d8590', '#3fb950', '#58a6ff', '#a371f7'];
1318
+ var dCard = mk('div', 'chart-card');
1319
+ dCard.appendChild(mk('div', 'chart-title', 'By Domain'));
1320
+ var dChart = mk('div', 'bar-chart');
1321
+ dEntries.forEach(function(row, i) {
1322
+ var pct = maxD > 0 ? Math.min((row[1] / maxD) * 90, 90) : 0;
1323
+ var r = mk('div', 'bar-row');
1324
+ var track = mk('div', 'bar-track');
1325
+ var fill = mk('div', 'bar-fill');
1326
+ fill.style.cssText = 'width:' + pct + '%;background:' + domColors[i % domColors.length] + ';';
1327
+ track.appendChild(fill);
1328
+ app(r, mk('span', 'bar-label', row[0]), track, mk('span', 'bar-val', fmtNum(row[1]) + 't'));
1329
+ dChart.appendChild(r);
1330
+ });
1331
+ dCard.appendChild(dChart);
1332
+ container.appendChild(dCard);
1333
+ }
1334
+ }
1335
+
1336
+ // ── Tab 5: Operation Matrix ───────────────────────────────────────────────────
1337
+ function renderMatrixTab(matrix) {
1338
+ var container = document.getElementById('matrix-content');
1339
+ container.textContent = '';
1340
+ if (!matrix || !matrix.length) {
1341
+ container.appendChild(emptyState('No operation matrix data', 'Operation matrix data is generated from the 202-op registry.'));
1342
+ return;
1343
+ }
1344
+ document.getElementById('tb-matrix').textContent = matrix.length;
1345
+
1346
+ // Collect unique domains and gateways
1347
+ var domains = ['all'];
1348
+ var domainSet = {};
1349
+ matrix.forEach(function(op) {
1350
+ if (op.domain && !domainSet[op.domain]) { domainSet[op.domain] = true; domains.push(op.domain); }
1351
+ });
1352
+
1353
+ // Filter row
1354
+ var filterBar = mk('div', 'filter-bar');
1355
+ var domainSel = document.createElement('select');
1356
+ domains.forEach(function(d) {
1357
+ var o = document.createElement('option'); o.value = d; o.textContent = d === 'all' ? 'All Domains' : d; domainSel.appendChild(o);
1358
+ });
1359
+ var gatewaySel = document.createElement('select');
1360
+ [['all', 'All Gateways'], ['query', 'Query'], ['mutate', 'Mutate']].forEach(function(opt) {
1361
+ var o = document.createElement('option'); o.value = opt[0]; o.textContent = opt[1]; gatewaySel.appendChild(o);
1362
+ });
1363
+ var testedSel = document.createElement('select');
1364
+ [['all', 'All'], ['tested', 'Tested'], ['untested', 'Untested']].forEach(function(opt) {
1365
+ var o = document.createElement('option'); o.value = opt[0]; o.textContent = opt[1]; testedSel.appendChild(o);
1366
+ });
1367
+ app(filterBar, mk('label', null, 'Domain:'), domainSel, mk('label', null, 'Gateway:'), gatewaySel, mk('label', null, 'Tested:'), testedSel);
1368
+ container.appendChild(filterBar);
1369
+
1370
+ var tableWrap = mk('div', 'table-wrap');
1371
+ container.appendChild(tableWrap);
1372
+
1373
+ function renderMatrix() {
1374
+ tableWrap.textContent = '';
1375
+ var filtered = matrix.slice();
1376
+ if (domainSel.value !== 'all') filtered = filtered.filter(function(op) { return op.domain === domainSel.value; });
1377
+ if (gatewaySel.value !== 'all') filtered = filtered.filter(function(op) { return op.gateway === gatewaySel.value; });
1378
+ if (testedSel.value === 'tested') filtered = filtered.filter(function(op) { return op.tested; });
1379
+ if (testedSel.value === 'untested') filtered = filtered.filter(function(op) { return !op.tested; });
1380
+
1381
+ if (!filtered.length) {
1382
+ tableWrap.appendChild(emptyState('No operations match filters', 'Adjust the filters above.'));
1383
+ return;
1384
+ }
1385
+
1386
+ var tbl = document.createElement('table'); tbl.className = 'data-table';
1387
+ var thead = document.createElement('thead');
1388
+ var hrow = document.createElement('tr');
1389
+ ['Operation', 'Domain', 'Gateway', 'Tested', 'MCP Calls', 'CLI Calls', 'Avg MCP ms', 'Avg CLI ms'].forEach(function(h) {
1390
+ hrow.appendChild(mk('th', null, h));
1391
+ });
1392
+ thead.appendChild(hrow); tbl.appendChild(thead);
1393
+ var tbody = document.createElement('tbody');
1394
+
1395
+ filtered.forEach(function(op) {
1396
+ var row = document.createElement('tr');
1397
+ // Operation name
1398
+ row.appendChild((function() { var td = document.createElement('td'); td.appendChild(mk('span', 'op-name', op.operation || op.name || '')); return td; })());
1399
+ // Domain
1400
+ row.appendChild(mk('td', null, op.domain || '\u2014'));
1401
+ // Gateway pill
1402
+ var gwTd = document.createElement('td');
1403
+ gwTd.appendChild(mk('span', 'pill ' + (op.gateway || ''), (op.gateway || '').toUpperCase()));
1404
+ row.appendChild(gwTd);
1405
+ // Tested
1406
+ var testedTd = mk('td');
1407
+ testedTd.textContent = op.tested ? '\u2713' : '\u2014';
1408
+ testedTd.style.color = op.tested ? 'var(--win)' : 'var(--text-muted)';
1409
+ row.appendChild(testedTd);
1410
+ // MCP Calls
1411
+ row.appendChild(mk('td', null, fmtNum(op.mcpCalls || op.mcp_calls, '0')));
1412
+ // CLI Calls
1413
+ row.appendChild(mk('td', null, fmtNum(op.cliCalls || op.cli_calls, '0')));
1414
+ // Avg MCP ms
1415
+ row.appendChild(mk('td', null, op.avgMcpMs || op.avg_mcp_ms ? fmtNum(op.avgMcpMs || op.avg_mcp_ms) + 'ms' : '\u2014'));
1416
+ // Avg CLI ms
1417
+ row.appendChild(mk('td', null, op.avgCliMs || op.avg_cli_ms ? fmtNum(op.avgCliMs || op.avg_cli_ms) + 'ms' : '\u2014'));
1418
+ tbody.appendChild(row);
1419
+ });
1420
+ tbl.appendChild(tbody);
1421
+ tableWrap.appendChild(tbl);
1422
+ }
1423
+
1424
+ domainSel.addEventListener('change', renderMatrix);
1425
+ gatewaySel.addEventListener('change', renderMatrix);
1426
+ testedSel.addEventListener('change', renderMatrix);
1427
+ renderMatrix();
1428
+ }
1429
+
1430
+ // ── Tab 6: Eval Report ────────────────────────────────────────────────────────
1431
+ function renderEvalsTab(evalReport) {
1432
+ var container = document.getElementById('evals-content');
1433
+ container.textContent = '';
1434
+ if (!evalReport || (!evalReport.evals && !evalReport.results)) {
1435
+ container.appendChild(emptyState('No eval report data', 'Eval results will appear after running the eval suite.'));
1436
+ return;
1437
+ }
1438
+
1439
+ var evals = evalReport.evals || evalReport.results || [];
1440
+ if (!Array.isArray(evals)) {
1441
+ // Could be object keyed by eval ID
1442
+ evals = Object.entries(evals).map(function(kv) { var e = kv[1]; e.id = e.id || kv[0]; return e; });
1443
+ }
1444
+ document.getElementById('tb-evals').textContent = evals.length;
1445
+
1446
+ // Sort controls
1447
+ var sortBar = mk('div', 'filter-bar');
1448
+ var sortSel = document.createElement('select');
1449
+ [['id', 'By ID'], ['passRate', 'By Pass Rate'], ['name', 'By Name']].forEach(function(opt) {
1450
+ var o = document.createElement('option'); o.value = opt[0]; o.textContent = opt[1]; sortSel.appendChild(o);
1451
+ });
1452
+ app(sortBar, mk('label', null, 'Sort:'), sortSel);
1453
+ container.appendChild(sortBar);
1454
+
1455
+ var cardsWrap = mk('div', null);
1456
+ cardsWrap.style.cssText = 'display:flex;flex-direction:column;gap:1rem;';
1457
+ container.appendChild(cardsWrap);
1458
+
1459
+ function renderEvals() {
1460
+ cardsWrap.textContent = '';
1461
+ var sorted = evals.slice();
1462
+ var sortVal = sortSel.value;
1463
+ sorted.sort(function(a, b) {
1464
+ if (sortVal === 'passRate') {
1465
+ var aRate = getPassRate(a), bRate = getPassRate(b);
1466
+ return bRate - aRate;
1467
+ }
1468
+ if (sortVal === 'name') return (a.name || '').localeCompare(b.name || '');
1469
+ return (a.id || '').localeCompare(b.id || '');
1470
+ });
1471
+
1472
+ sorted.forEach(function(ev) {
1473
+ var card = mk('div', 'eval-card');
1474
+ var header = mk('div', 'eval-card-header');
1475
+
1476
+ // Title + badge
1477
+ var titleWrap = mk('div');
1478
+ app(titleWrap, mk('span', 'eval-card-title', (ev.id ? ev.id + ': ' : '') + (ev.name || ev.description || 'Eval')));
1479
+ header.appendChild(titleWrap);
1480
+
1481
+ // Status badge
1482
+ var status = getEvalStatus(ev);
1483
+ header.appendChild(mk('span', 'pill ' + status.cls, status.label));
1484
+ card.appendChild(header);
1485
+
1486
+ // Pass rate bar
1487
+ var expectations = ev.expectations || ev.results || [];
1488
+ if (expectations.length) {
1489
+ var passed = expectations.filter(function(e) { return e.pass || e.passed || e.result === 'pass'; }).length;
1490
+ var total = expectations.length;
1491
+ var pct = Math.round((passed / total) * 100);
1492
+
1493
+ var rateRow = mk('div', null);
1494
+ rateRow.style.cssText = 'display:flex;align-items:center;gap:.75rem;margin-bottom:.75rem;';
1495
+ rateRow.appendChild(mks('span', 'font-family:var(--mono);font-size:.72rem;', passed + '/' + total + ' (' + pct + '%)'));
1496
+ var prBar = mk('div', 'pass-rate-bar');
1497
+ var prFill = mk('div', 'pass-rate-fill');
1498
+ prFill.style.width = pct + '%';
1499
+ if (pct < 60) prFill.style.background = 'var(--grade-f)';
1500
+ else if (pct < 80) prFill.style.background = 'var(--grade-c)';
1501
+ prBar.appendChild(prFill);
1502
+ rateRow.appendChild(prBar);
1503
+ card.appendChild(rateRow);
1504
+
1505
+ // Expectation list
1506
+ expectations.forEach(function(exp) {
1507
+ var expRow = mk('div', 'expectation-row');
1508
+ var passed2 = exp.pass || exp.passed || exp.result === 'pass';
1509
+ var icon = mk('span', 'expect-icon');
1510
+ icon.textContent = passed2 ? '\u2713' : '\u2717';
1511
+ icon.style.color = passed2 ? 'var(--win)' : 'var(--loss)';
1512
+ expRow.appendChild(icon);
1513
+ var expContent = mk('div');
1514
+ expContent.appendChild(mk('div', null, exp.name || exp.description || exp.expectation || 'Expectation'));
1515
+ if (exp.evidence) {
1516
+ expContent.appendChild(mk('div', 'expect-evidence', exp.evidence));
1517
+ }
1518
+ expRow.appendChild(expContent);
1519
+ card.appendChild(expRow);
1520
+ });
1521
+ }
1522
+ cardsWrap.appendChild(card);
1523
+ });
1524
+ }
1525
+
1526
+ sortSel.addEventListener('change', renderEvals);
1527
+ renderEvals();
1528
+ }
1529
+
1530
+ function getPassRate(ev) {
1531
+ var expectations = ev.expectations || ev.results || [];
1532
+ if (!expectations.length) return -1;
1533
+ var passed = expectations.filter(function(e) { return e.pass || e.passed || e.result === 'pass'; }).length;
1534
+ return passed / expectations.length;
1535
+ }
1536
+
1537
+ function getEvalStatus(ev) {
1538
+ var expectations = ev.expectations || ev.results || [];
1539
+ if (!expectations.length) return { cls: 'not-run', label: 'NOT RUN' };
1540
+ var passed = expectations.filter(function(e) { return e.pass || e.passed || e.result === 'pass'; }).length;
1541
+ if (passed === expectations.length) return { cls: 'pass', label: 'PASS' };
1542
+ return { cls: 'fail', label: 'FAIL' };
1543
+ }
1544
+
1545
+ // ── Init ──────────────────────────────────────────────────────────────────────
1546
+ (function() {
1547
+ var meta = DATA.metadata || {};
1548
+ document.getElementById('hdr-title').textContent = 'ct-grade v2.1 \u2014 Grade Review';
1549
+ if (meta.workspace) document.getElementById('hdr-sub').textContent = meta.workspace;
1550
+ var hdrMeta = document.getElementById('hdr-meta');
1551
+ if (meta.generated_at) {
1552
+ hdrMeta.appendChild(document.createTextNode('Generated ' + fmtDate(meta.generated_at)));
1553
+ hdrMeta.appendChild(document.createElement('br'));
1554
+ }
1555
+ var grades = DATA.grades || [];
1556
+ var sessions = DATA.sessions || [];
1557
+ hdrMeta.appendChild(document.createTextNode(
1558
+ grades.length + ' grade(s) \u00b7 ' + sessions.length + ' session(s)'
1559
+ ));
1560
+ if (meta.skill_version) {
1561
+ hdrMeta.appendChild(document.createElement('br'));
1562
+ hdrMeta.appendChild(document.createTextNode('v' + meta.skill_version));
1563
+ }
1564
+
1565
+ renderGradesTab(grades);
1566
+ renderSessionsTab(sessions);
1567
+ renderABTab(DATA.ab_results || {}, DATA.ab_history || []);
1568
+ renderTokensTab(DATA.token_analysis || {});
1569
+ renderMatrixTab(DATA.operation_matrix || []);
1570
+ renderEvalsTab(DATA.eval_report || {});
1571
+ })();
1572
+ </script>
1573
+ </body>
1574
+ </html>