thumbgate 1.2.0 → 1.4.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 (160) hide show
  1. package/.claude-plugin/README.md +4 -4
  2. package/.claude-plugin/marketplace.json +32 -13
  3. package/.claude-plugin/plugin.json +15 -2
  4. package/.well-known/llms.txt +60 -0
  5. package/.well-known/mcp/server-card.json +1 -1
  6. package/README.md +133 -23
  7. package/adapters/README.md +1 -1
  8. package/adapters/chatgpt/openapi.yaml +168 -0
  9. package/adapters/claude/.mcp.json +2 -2
  10. package/adapters/codex/config.toml +2 -2
  11. package/adapters/mcp/server-stdio.js +85 -2
  12. package/adapters/opencode/opencode.json +1 -1
  13. package/bin/cli.js +215 -19
  14. package/bin/postinstall.js +8 -2
  15. package/config/budget.json +18 -0
  16. package/config/gates/code-edit.json +61 -0
  17. package/config/gates/db-write.json +61 -0
  18. package/config/gates/default.json +154 -3
  19. package/config/gates/deploy.json +61 -0
  20. package/config/github-about.json +2 -1
  21. package/config/merge-quality-checks.json +23 -0
  22. package/config/model-tiers.json +11 -0
  23. package/openapi/openapi.yaml +168 -0
  24. package/package.json +47 -13
  25. package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +1 -1
  26. package/plugins/claude-codex-bridge/.mcp.json +1 -1
  27. package/plugins/claude-codex-bridge/scripts/codex-bridge.js +1 -3
  28. package/plugins/codex-profile/.codex-plugin/plugin.json +1 -1
  29. package/plugins/codex-profile/.mcp.json +1 -1
  30. package/plugins/codex-profile/INSTALL.md +27 -4
  31. package/plugins/codex-profile/README.md +33 -9
  32. package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +1 -1
  33. package/plugins/cursor-marketplace/README.md +2 -2
  34. package/plugins/cursor-marketplace/commands/capture-feedback.md +2 -2
  35. package/plugins/cursor-marketplace/rules/feedback-capture.mdc +3 -3
  36. package/plugins/cursor-marketplace/skills/capture-feedback/SKILL.md +3 -2
  37. package/plugins/opencode-profile/INSTALL.md +1 -1
  38. package/public/blog.html +73 -0
  39. package/public/compare/mem0.html +189 -0
  40. package/public/compare/speclock.html +180 -0
  41. package/public/compare.html +12 -4
  42. package/public/guide.html +5 -5
  43. package/public/guides/claude-code-prevent-repeated-mistakes.html +161 -0
  44. package/public/guides/codex-cli-guardrails.html +158 -0
  45. package/public/guides/cursor-prevent-repeated-mistakes.html +161 -0
  46. package/public/guides/pre-action-gates.html +162 -0
  47. package/public/guides/stop-repeated-ai-agent-mistakes.html +159 -0
  48. package/public/index.html +169 -70
  49. package/public/learn/ai-agent-persistent-memory.html +1 -0
  50. package/public/lessons.html +334 -17
  51. package/public/llm-context.md +140 -0
  52. package/public/pro.html +24 -22
  53. package/scripts/__pycache__/train_from_feedback.cpython-312.pyc +0 -0
  54. package/scripts/access-anomaly-detector.js +1 -1
  55. package/scripts/adk-consolidator.js +1 -5
  56. package/scripts/agent-security-hardening.js +4 -6
  57. package/scripts/agentic-data-pipeline.js +1 -3
  58. package/scripts/async-job-runner.js +1 -5
  59. package/scripts/audit-trail.js +7 -5
  60. package/scripts/background-agent-governance.js +2 -10
  61. package/scripts/billing.js +2 -16
  62. package/scripts/budget-enforcer.js +173 -0
  63. package/scripts/build-codex-plugin.js +152 -0
  64. package/scripts/capture-railway-diagnostics.sh +97 -0
  65. package/scripts/check-congruence.js +133 -15
  66. package/scripts/claude-feedback-sync.js +320 -0
  67. package/scripts/cli-telemetry.js +4 -1
  68. package/scripts/commercial-offer.js +5 -7
  69. package/scripts/content-engine/linkedin-content-generator.js +154 -0
  70. package/scripts/content-engine/output/linkedin-memento-validation.md +17 -0
  71. package/scripts/content-engine/output/linkedin-posts-2026-04-09.md +175 -0
  72. package/scripts/content-engine/reddit-thread-finder.js +154 -0
  73. package/scripts/context-engine.js +21 -6
  74. package/scripts/contextfs.js +33 -44
  75. package/scripts/dashboard.js +104 -0
  76. package/scripts/decision-journal.js +341 -0
  77. package/scripts/delegation-runtime.js +1 -5
  78. package/scripts/distribution-surfaces.js +26 -0
  79. package/scripts/document-intake.js +927 -0
  80. package/scripts/ephemeral-agent-store.js +1 -8
  81. package/scripts/evolution-state.js +1 -5
  82. package/scripts/experiment-tracker.js +1 -5
  83. package/scripts/export-databricks-bundle.js +1 -5
  84. package/scripts/export-hf-dataset.js +1 -5
  85. package/scripts/export-training.js +1 -5
  86. package/scripts/feedback-attribution.js +1 -16
  87. package/scripts/feedback-history-distiller.js +1 -16
  88. package/scripts/feedback-loop.js +17 -5
  89. package/scripts/feedback-root-consolidator.js +2 -21
  90. package/scripts/feedback-session.js +49 -0
  91. package/scripts/feedback-to-rules.js +188 -28
  92. package/scripts/filesystem-search.js +1 -9
  93. package/scripts/fs-utils.js +104 -0
  94. package/scripts/gates-engine.js +149 -4
  95. package/scripts/github-about.js +32 -8
  96. package/scripts/gtm-revenue-loop.js +1 -5
  97. package/scripts/harness-selector.js +148 -0
  98. package/scripts/hosted-job-launcher.js +1 -5
  99. package/scripts/hybrid-feedback-context.js +7 -33
  100. package/scripts/intervention-policy.js +753 -0
  101. package/scripts/lesson-db.js +3 -18
  102. package/scripts/lesson-inference.js +194 -16
  103. package/scripts/lesson-retrieval.js +60 -24
  104. package/scripts/llm-client.js +59 -0
  105. package/scripts/local-model-profile.js +18 -2
  106. package/scripts/managed-lesson-agent.js +183 -0
  107. package/scripts/marketing-experiment.js +8 -22
  108. package/scripts/meta-agent-loop.js +624 -0
  109. package/scripts/metered-billing.js +1 -1
  110. package/scripts/model-tier-router.js +10 -1
  111. package/scripts/money-watcher.js +1 -4
  112. package/scripts/obsidian-export.js +1 -5
  113. package/scripts/operational-integrity.js +369 -34
  114. package/scripts/org-dashboard.js +6 -1
  115. package/scripts/per-step-scoring.js +2 -4
  116. package/scripts/pr-manager.js +201 -19
  117. package/scripts/pro-features.js +3 -2
  118. package/scripts/prompt-dlp.js +3 -3
  119. package/scripts/prove-adapters.js +2 -5
  120. package/scripts/prove-attribution.js +1 -5
  121. package/scripts/prove-automation.js +3 -5
  122. package/scripts/prove-cloudflare-sandbox.js +1 -3
  123. package/scripts/prove-data-pipeline.js +1 -3
  124. package/scripts/prove-intelligence.js +1 -3
  125. package/scripts/prove-lancedb.js +1 -5
  126. package/scripts/prove-local-intelligence.js +1 -3
  127. package/scripts/prove-packaged-runtime.js +326 -0
  128. package/scripts/prove-predictive-insights.js +1 -3
  129. package/scripts/prove-runtime.js +13 -0
  130. package/scripts/prove-training-export.js +1 -3
  131. package/scripts/prove-workflow-contract.js +1 -5
  132. package/scripts/rate-limiter.js +6 -4
  133. package/scripts/reddit-dm-outreach.js +14 -4
  134. package/scripts/schedule-manager.js +3 -5
  135. package/scripts/security-scanner.js +448 -0
  136. package/scripts/self-distill-agent.js +579 -0
  137. package/scripts/semantic-dedup.js +115 -0
  138. package/scripts/skill-exporter.js +1 -3
  139. package/scripts/skill-generator.js +1 -5
  140. package/scripts/social-analytics/engagement-audit.js +1 -18
  141. package/scripts/social-analytics/pollers/linkedin.js +26 -16
  142. package/scripts/social-analytics/publishers/linkedin.js +1 -1
  143. package/scripts/social-analytics/publishers/zernio.js +51 -0
  144. package/scripts/social-pipeline.js +1 -3
  145. package/scripts/social-post-hourly.js +47 -4
  146. package/scripts/statusline-links.js +6 -5
  147. package/scripts/statusline-local-stats.js +2 -0
  148. package/scripts/statusline.sh +38 -7
  149. package/scripts/sync-branch-protection.js +340 -0
  150. package/scripts/tessl-export.js +1 -3
  151. package/scripts/thumbgate-search.js +32 -1
  152. package/scripts/tool-kpi-tracker.js +1 -1
  153. package/scripts/tool-registry.js +108 -4
  154. package/scripts/vector-store.js +1 -5
  155. package/scripts/weekly-auto-post.js +1 -1
  156. package/scripts/workflow-sentinel.js +205 -4
  157. package/skills/thumbgate/SKILL.md +2 -2
  158. package/src/api/server.js +273 -4
  159. package/scripts/social-analytics/db/social-analytics.db-shm +0 -0
  160. /package/scripts/social-analytics/db/{social-analytics.db-wal → analytics.sqlite} +0 -0
@@ -42,6 +42,49 @@
42
42
  .stat-value.purple { color: var(--purple); }
43
43
  .stat-sub { font-size: 11px; color: var(--text-muted); margin-top: 4px; }
44
44
 
45
+ /* IMPROVEMENT METRICS */
46
+ .metrics-panel { background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px; padding: 22px; margin: 0 0 24px; }
47
+ .metrics-header { display: flex; justify-content: space-between; gap: 16px; align-items: flex-start; margin-bottom: 18px; }
48
+ .metrics-header h2 { margin-bottom: 6px; }
49
+ .metrics-header p { font-size: 13px; color: var(--text-muted); max-width: 720px; }
50
+ .metrics-summary-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 18px; }
51
+ .metric-tile { background: var(--bg-raised); border: 1px solid var(--border); border-radius: 10px; padding: 16px; min-height: 94px; }
52
+ .metric-kicker { font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted); margin-bottom: 6px; }
53
+ .metric-number { font-size: 24px; font-weight: 700; letter-spacing: -0.02em; }
54
+ .metric-number.green { color: var(--green); }
55
+ .metric-number.red { color: var(--red); }
56
+ .metric-number.cyan { color: var(--cyan); }
57
+ .metric-number.purple { color: var(--purple); }
58
+ .metric-note { font-size: 12px; color: var(--text-muted); margin-top: 6px; }
59
+ .metrics-chart-wrap { background: var(--bg-raised); border: 1px solid var(--border); border-radius: 10px; padding: 16px; }
60
+ .metrics-chart-title { display: flex; justify-content: space-between; gap: 12px; align-items: center; margin-bottom: 12px; }
61
+ .metrics-chart-title h3 { font-size: 14px; margin: 0; }
62
+ .metrics-chart-title p { font-size: 12px; color: var(--text-muted); }
63
+ .metrics-legend { display: flex; gap: 12px; flex-wrap: wrap; font-size: 12px; color: var(--text-muted); margin-bottom: 12px; }
64
+ .metrics-legend span { display: inline-flex; align-items: center; gap: 6px; }
65
+ .metrics-legend-dot { width: 8px; height: 8px; border-radius: 999px; display: inline-block; }
66
+ .metrics-legend-dot.up { background: var(--green); }
67
+ .metrics-legend-dot.down { background: var(--red); }
68
+ .metrics-legend-dot.deny { background: var(--cyan); }
69
+ .metrics-legend-dot.warn { background: var(--yellow); }
70
+ .metrics-chart { display: grid; grid-template-columns: repeat(14, minmax(0, 1fr)); gap: 8px; align-items: end; min-height: 180px; }
71
+ .metrics-bar { background: transparent; border: 1px solid transparent; border-radius: 10px; padding: 8px 4px; color: inherit; cursor: pointer; display: flex; flex-direction: column; align-items: center; gap: 8px; min-width: 0; }
72
+ .metrics-bar:hover { border-color: rgba(34,211,238,0.35); background: rgba(34,211,238,0.04); }
73
+ .metrics-bar.selected { border-color: rgba(34,211,238,0.65); background: rgba(34,211,238,0.08); }
74
+ .metrics-bar-body { width: 100%; display: flex; justify-content: center; align-items: flex-end; gap: 6px; }
75
+ .metrics-bar-stack { width: 100%; max-width: 28px; height: 120px; display: flex; flex-direction: column; justify-content: flex-end; gap: 3px; }
76
+ .metrics-bar-stack.gate { max-width: 16px; height: 84px; }
77
+ .metrics-bar-empty { width: 100%; border-radius: 8px; background: rgba(255,255,255,0.04); border: 1px dashed rgba(255,255,255,0.05); flex: 1; }
78
+ .metrics-segment { width: 100%; border-radius: 8px; min-height: 0; }
79
+ .metrics-segment.up { background: linear-gradient(180deg, rgba(74,222,128,0.95), rgba(74,222,128,0.55)); }
80
+ .metrics-segment.down { background: linear-gradient(180deg, rgba(248,113,113,0.95), rgba(248,113,113,0.55)); }
81
+ .metrics-segment.deny { background: linear-gradient(180deg, rgba(34,211,238,0.95), rgba(34,211,238,0.55)); }
82
+ .metrics-segment.warn { background: linear-gradient(180deg, rgba(251,191,36,0.95), rgba(251,191,36,0.55)); }
83
+ .metrics-bar-total { font-size: 12px; font-weight: 600; color: var(--text); }
84
+ .metrics-bar-subtotal { font-size: 10px; color: var(--text-muted); line-height: 1; }
85
+ .metrics-bar-label { font-size: 11px; color: var(--text-muted); white-space: nowrap; }
86
+ .metrics-chart-note { font-size: 12px; color: var(--text-muted); margin-top: 12px; }
87
+
45
88
  /* TABS */
46
89
  .tabs { display: flex; gap: 0; border-bottom: 1px solid var(--border); margin-bottom: 24px; }
47
90
  .tab { padding: 12px 20px; font-size: 14px; font-weight: 500; color: var(--text-muted); cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -1px; }
@@ -114,6 +157,9 @@
114
157
 
115
158
  @media (max-width: 700px) {
116
159
  .stats-grid { grid-template-columns: repeat(2, 1fr); }
160
+ .metrics-header { flex-direction: column; }
161
+ .metrics-summary-grid { grid-template-columns: repeat(2, 1fr); }
162
+ .metrics-chart { grid-template-columns: repeat(7, minmax(0, 1fr)); row-gap: 12px; }
117
163
  .filter-row { flex-wrap: wrap; }
118
164
  .rule-meta { flex-direction: column; gap: 8px; }
119
165
  }
@@ -152,7 +198,7 @@
152
198
  <div class="container">
153
199
  <div style="margin:32px 0 24px;padding:24px;background:linear-gradient(135deg,rgba(167,139,250,0.08),rgba(34,211,238,0.05));border:1px solid rgba(167,139,250,0.2);border-radius:12px;">
154
200
  <h1 style="font-size:22px;font-weight:700;margin-bottom:8px;letter-spacing:-0.02em;">📚 Lessons Learned</h1>
155
- <p style="font-size:14px;color:var(--text-muted);line-height:1.6;max-width:700px;">Is your AI getting smarter? See every rule auto-generated from your feedback, how many mistakes each one prevented, and which patterns keep recurring. <span style="color:var(--purple);font-weight:600;">This is the proof your feedback works.</span></p>
201
+ <p style="font-size:14px;color:var(--text-muted);line-height:1.6;max-width:700px;">See what ThumbGate learned from your feedback, which failure patterns keep repeating, and how many actions the gate layer actually blocked. <span style="color:var(--purple);font-weight:600;">This view separates repeated failures from recorded gate blocks so the proof stays honest.</span></p>
156
202
  <div style="display:flex;gap:16px;margin-top:12px;font-size:12px;color:var(--text-muted);">
157
203
  <span>📋 <strong style="color:var(--text);">Active Rules</strong> — what was learned</span>
158
204
  <span>📊 <strong style="color:var(--text);">Timeline</strong> — when it was learned</span>
@@ -171,9 +217,9 @@
171
217
  <div class="stat-sub">Highest severity rules</div>
172
218
  </div>
173
219
  <div class="stat-card" onclick="switchTab('timeline')">
174
- <div class="stat-label">Mistakes Prevented</div>
175
- <div class="stat-value green" id="statPrevented">0</div>
176
- <div class="stat-sub">Gate blocks from learned rules</div>
220
+ <div class="stat-label">Actions Blocked</div>
221
+ <div class="stat-value green" id="statBlocked">0</div>
222
+ <div class="stat-sub">Recorded gate denies, not inferred repeats</div>
177
223
  </div>
178
224
  <div class="stat-card" onclick="switchTab('insights')">
179
225
  <div class="stat-label">Approval Trend</div>
@@ -186,6 +232,54 @@
186
232
  Loading lessons view...
187
233
  </div>
188
234
 
235
+ <div class="metrics-panel" id="metricsPanel">
236
+ <div class="metrics-header">
237
+ <div>
238
+ <h2>Improvement Over Time</h2>
239
+ <p id="metricsSummary">Loading defensible live metrics...</p>
240
+ </div>
241
+ <button class="filter-btn" id="metricsClearBtn" type="button" style="display:none;" onclick="clearTimelineDayFilter()">Clear day filter</button>
242
+ </div>
243
+ <div class="metrics-summary-grid" id="metricsSummaryGrid">
244
+ <div class="metric-tile">
245
+ <div class="metric-kicker">Fast path rate</div>
246
+ <div class="metric-number purple">--</div>
247
+ <div class="metric-note">Waiting for live decisions</div>
248
+ </div>
249
+ <div class="metric-tile">
250
+ <div class="metric-kicker">Override rate</div>
251
+ <div class="metric-number red">--</div>
252
+ <div class="metric-note">Waiting for live decisions</div>
253
+ </div>
254
+ <div class="metric-tile">
255
+ <div class="metric-kicker">Rollback rate</div>
256
+ <div class="metric-number cyan">--</div>
257
+ <div class="metric-note">Waiting for live decisions</div>
258
+ </div>
259
+ <div class="metric-tile">
260
+ <div class="metric-kicker">Median latency</div>
261
+ <div class="metric-number green">--</div>
262
+ <div class="metric-note">Time from recommendation to recorded outcome</div>
263
+ </div>
264
+ </div>
265
+ <div class="metrics-chart-wrap">
266
+ <div class="metrics-chart-title">
267
+ <h3>Recent Feedback + Gate Activity</h3>
268
+ <p>Click any day to inspect the recorded feedback behind the trend and compare it with gate interceptions.</p>
269
+ </div>
270
+ <div class="metrics-legend">
271
+ <span><i class="metrics-legend-dot up"></i> Positive feedback</span>
272
+ <span><i class="metrics-legend-dot down"></i> Negative feedback</span>
273
+ <span><i class="metrics-legend-dot deny"></i> Gate deny</span>
274
+ <span><i class="metrics-legend-dot warn"></i> Gate warn</span>
275
+ </div>
276
+ <div class="metrics-chart" id="metricsChart">
277
+ <div class="loading" style="grid-column:1 / -1;">Loading chart...</div>
278
+ </div>
279
+ <div class="metrics-chart-note" id="metricsChartNote">The chart uses recorded feedback events, not inferred rule strength.</div>
280
+ </div>
281
+ </div>
282
+
189
283
  <div class="tabs">
190
284
  <div class="tab active" onclick="switchTab('rules')">📋 Active Rules</div>
191
285
  <div class="tab" onclick="switchTab('timeline')">📊 Feedback Timeline</div>
@@ -224,13 +318,13 @@
224
318
  <!-- TAB 3: INSIGHTS (PRO) -->
225
319
  <div class="tab-content" id="tab-insights">
226
320
  <div class="insight-card">
227
- <h3>🔥 Top Recurring Mistakes This Week</h3>
321
+ <h3>🔥 Top Recurring Failure Patterns</h3>
228
322
  <ul class="insight-list" id="insightMistakes">
229
323
  <li><span>Loading...</span></li>
230
324
  </ul>
231
325
  </div>
232
326
  <div class="insight-card">
233
- <h3>🛡️ Most Effective Rules</h3>
327
+ <h3>🛡️ Most Reinforced Rules</h3>
234
328
  <ul class="insight-list" id="insightEffective">
235
329
  <li><span>Loading...</span></li>
236
330
  </ul>
@@ -243,7 +337,7 @@
243
337
  </div>
244
338
  <div class="pro-badge" id="proBadge" style="display:none;">
245
339
  <h3>Unlock Full Insights</h3>
246
- <p>DPO training data export, effectiveness charts, pattern clustering, and weekly auto-digests.</p>
340
+ <p>DPO training data export, effectiveness charts, pattern clustering, weekly auto-digests, and a learned intervention policy trained from feedback, audits, and diagnostics.</p>
247
341
  <a href="/#pricing">Get Pro — $19/mo</a>
248
342
  </div>
249
343
  </div>
@@ -255,10 +349,14 @@ var allTimeline = [];
255
349
  var isDemo = true;
256
350
  var API_KEY = '';
257
351
  var dashboardSnapshot = null;
352
+ var activeTimelineSignal = 'all';
353
+ var activeTimelineDay = null;
354
+ var currentImprovementSeries = [];
258
355
  const BOOTSTRAP_API_KEY = __LESSONS_BOOTSTRAP_KEY__;
259
356
  const LOCAL_PRO_BOOTSTRAP = __LESSONS_BOOTSTRAP_ENABLED__;
260
357
 
261
358
  function escHtml(s) { var d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
359
+ function escAttr(s) { return escHtml(String(s || '')).replace(/"/g, '&quot;'); }
262
360
 
263
361
  function getHeaders() {
264
362
  return { 'Authorization': 'Bearer ' + API_KEY, 'Content-Type': 'application/json' };
@@ -284,6 +382,29 @@ function formatDate(value) {
284
382
  return ts.toLocaleString(undefined, { year: 'numeric', month: 'numeric', day: 'numeric', hour: 'numeric', minute: '2-digit', second: '2-digit', timeZoneName: 'short' });
285
383
  }
286
384
 
385
+ function formatShortDay(dayKey) {
386
+ if (!dayKey) return '';
387
+ var ts = new Date(dayKey + 'T00:00:00');
388
+ if (Number.isNaN(ts.getTime())) return dayKey;
389
+ return ts.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
390
+ }
391
+
392
+ function normalizeSignal(value) {
393
+ var signal = String(value || '').toLowerCase();
394
+ if (signal === 'up' || signal === 'positive' || signal === 'thumbs_up') return 'up';
395
+ return 'down';
396
+ }
397
+
398
+ function toDayKey(value) {
399
+ if (!value) return '';
400
+ var ts = new Date(value);
401
+ if (Number.isNaN(ts.getTime())) return '';
402
+ var year = ts.getFullYear();
403
+ var month = String(ts.getMonth() + 1).padStart(2, '0');
404
+ var day = String(ts.getDate()).padStart(2, '0');
405
+ return year + '-' + month + '-' + day;
406
+ }
407
+
287
408
  function highlightCard(index) {
288
409
  document.querySelectorAll('.stat-card').forEach(function(c, i) {
289
410
  c.classList.toggle('selected', i === index);
@@ -325,12 +446,54 @@ function filterSeverity(level, el) {
325
446
  }
326
447
 
327
448
  function filterTimeline(signal, el) {
449
+ activeTimelineSignal = signal;
328
450
  if (el) {
329
451
  document.querySelectorAll('#tab-timeline .filter-btn').forEach(function(b) { b.classList.remove('active'); });
330
452
  el.classList.add('active');
331
453
  }
332
- var filtered = signal === 'all' ? allTimeline : allTimeline.filter(function(r) { return r.signal === signal; });
333
- renderTimeline(filtered);
454
+ renderTimeline(getFilteredTimelineItems());
455
+ updateMetricsSelection();
456
+ }
457
+
458
+ function getFilteredTimelineItems() {
459
+ return allTimeline.filter(function(item) {
460
+ if (activeTimelineSignal !== 'all' && item.signal !== activeTimelineSignal) return false;
461
+ if (activeTimelineDay && item.dayKey !== activeTimelineDay) return false;
462
+ return true;
463
+ });
464
+ }
465
+
466
+ function focusTimelineDay(dayKey) {
467
+ activeTimelineDay = dayKey;
468
+ switchTab('timeline');
469
+ renderTimeline(getFilteredTimelineItems());
470
+ updateMetricsSelection();
471
+ }
472
+
473
+ function clearTimelineDayFilter() {
474
+ activeTimelineDay = null;
475
+ renderTimeline(getFilteredTimelineItems());
476
+ updateMetricsSelection();
477
+ }
478
+
479
+ function updateMetricsSelection() {
480
+ document.querySelectorAll('.metrics-bar').forEach(function(bar) {
481
+ bar.classList.toggle('selected', activeTimelineDay && bar.getAttribute('data-day-key') === activeTimelineDay);
482
+ });
483
+ var clearBtn = document.getElementById('metricsClearBtn');
484
+ if (clearBtn) clearBtn.style.display = activeTimelineDay ? 'inline-flex' : 'none';
485
+ var note = document.getElementById('metricsChartNote');
486
+ if (!note) return;
487
+ if (activeTimelineDay) {
488
+ var selected = currentImprovementSeries.find(function(item) { return item.dayKey === activeTimelineDay; }) || null;
489
+ if (selected) {
490
+ note.textContent = 'Showing timeline events for ' + formatShortDay(activeTimelineDay) + '. Feedback: ' + selected.total + ' (' + selected.up + ' positive, ' + selected.down + ' negative). Gate audit: ' + selected.intercepted + ' intercepted (' + selected.deny + ' denies, ' + selected.warn + ' warns).';
491
+ } else {
492
+ note.textContent = 'Showing timeline events for ' + formatShortDay(activeTimelineDay) + '. Clear the day filter to return to the full timeline.';
493
+ }
494
+ } else {
495
+ note.textContent = 'The chart combines recorded feedback events with daily gate-audit activity. Decision-loop metrics above come from recorded evaluations and outcomes.';
496
+ }
334
497
  }
335
498
 
336
499
  function searchRules() {
@@ -362,7 +525,7 @@ function renderRules(rules) {
362
525
  '</div>' +
363
526
  (r.context ? '<div class="rule-context">' + escHtml(r.context) + '</div>' : '') +
364
527
  '<div class="rule-meta">' +
365
- '<div class="rule-meta-item">Triggered <span class="value">' + (r.occurrences || 0) + '</span> times</div>' +
528
+ '<div class="rule-meta-item">Seen in feedback <span class="value">' + (r.occurrences || 0) + '</span> times</div>' +
366
529
  (r.lastTriggered ? '<div class="rule-meta-item">Last: <span class="value">' + escHtml(r.lastTriggered) + '</span></div>' : '') +
367
530
  '</div>' +
368
531
  (r.tags && r.tags.length ? '<div class="rule-tags">' + r.tags.map(function(t) { return '<span class="tag">' + escHtml(t) + '</span>'; }).join('') + '</div>' : '') +
@@ -420,17 +583,163 @@ function renderInsights(data) {
420
583
 
421
584
  mistakes.innerHTML = topMistakes.length ? topMistakes.map(function(r) {
422
585
  return insightRow(r, '<span style="color:var(--red);font-weight:600">' + (r.occurrences || 0) + 'x</span>');
423
- }).join('') : '<li><span style="color:var(--green)">No critical mistakes this week</span></li>';
586
+ }).join('') : '<li><span style="color:var(--green)">No critical repeated failures this week</span></li>';
424
587
 
425
588
  effective.innerHTML = topEffective.length ? topEffective.map(function(r) {
426
- return insightRow(r, '<span class="rule-effectiveness">Triggered ' + (r.occurrences || 0) + 'x</span>');
427
- }).join('') : '<li><span>No high-frequency rules yet</span></li>';
589
+ return insightRow(r, '<span class="rule-effectiveness">Seen ' + (r.occurrences || 0) + 'x</span>');
590
+ }).join('') : '<li><span>No reinforced rules yet</span></li>';
428
591
 
429
592
  stale.innerHTML = staleRules.length ? staleRules.map(function(r) {
430
593
  return insightRow(r, '<span style="color:var(--text-muted)">Archive?</span>');
431
594
  }).join('') : '<li><span style="color:var(--green)">All rules are active</span></li>';
432
595
  }
433
596
 
597
+ function buildFeedbackSeries(items, dayCount) {
598
+ var series = [];
599
+ var today = new Date();
600
+ today.setHours(0, 0, 0, 0);
601
+ var counts = {};
602
+ items.forEach(function(item) {
603
+ if (!item.dayKey) return;
604
+ if (!counts[item.dayKey]) counts[item.dayKey] = { up: 0, down: 0 };
605
+ counts[item.dayKey][item.signal] = (counts[item.dayKey][item.signal] || 0) + 1;
606
+ });
607
+ for (var idx = dayCount - 1; idx >= 0; idx--) {
608
+ var day = new Date(today);
609
+ day.setDate(today.getDate() - idx);
610
+ var dayKey = toDayKey(day.toISOString());
611
+ var entry = counts[dayKey] || { up: 0, down: 0 };
612
+ series.push({
613
+ dayKey: dayKey,
614
+ label: formatShortDay(dayKey),
615
+ up: entry.up || 0,
616
+ down: entry.down || 0,
617
+ total: (entry.up || 0) + (entry.down || 0)
618
+ });
619
+ }
620
+ return series;
621
+ }
622
+
623
+ function renderImprovementMetrics(stats, data) {
624
+ var summary = document.getElementById('metricsSummary');
625
+ var grid = document.getElementById('metricsSummaryGrid');
626
+ var chart = document.getElementById('metricsChart');
627
+ if (!summary || !grid || !chart) return;
628
+
629
+ if (isDemo) {
630
+ summary.textContent = 'Live improvement trends appear here once local Pro is connected. The decision tiles are based on recorded evaluations plus override and rollback outcomes, while the chart below stays anchored to feedback and gate audits.';
631
+ grid.innerHTML = [
632
+ { label: 'Fast path rate', value: '62%', tone: 'purple', note: 'Sample auto-execute share' },
633
+ { label: 'Override rate', value: '14%', tone: 'red', note: 'Sample operator overrides' },
634
+ { label: 'Rollback rate', value: '4%', tone: 'cyan', note: 'Sample reversed decisions' },
635
+ { label: 'Median latency', value: '6m', tone: 'green', note: 'Sample decision completion speed' }
636
+ ].map(function(tile) {
637
+ return '<div class="metric-tile"><div class="metric-kicker">' + escHtml(tile.label) + '</div><div class="metric-number ' + escHtml(tile.tone) + '">' + escHtml(tile.value) + '</div><div class="metric-note">' + escHtml(tile.note) + '</div></div>';
638
+ }).join('');
639
+ currentImprovementSeries = [];
640
+ chart.innerHTML = '<div class="empty" style="grid-column:1 / -1;">Upgrade to Pro to see the clickable live feedback and gate-audit timeline.</div>';
641
+ updateMetricsSelection();
642
+ return;
643
+ }
644
+
645
+ var gateStats = (data && data.gateStats) || {};
646
+ var gateAudit = (data && data.gateAudit) || {};
647
+ var liveMetrics = (data && data.liveMetrics) || {};
648
+ var decisionLoop = (data && data.decisions) || {};
649
+ var harness = (data && data.harness) || {};
650
+ var approvalRate = Math.round(((stats && (stats.recentRate || stats.approvalRate)) || 0) * 100);
651
+ var errorTrend = liveMetrics.errorTrend || {};
652
+ var fastPathRate = Math.round(((decisionLoop.fastPathRate || 0) * 100));
653
+ var overrideRate = Math.round(((decisionLoop.overrideRate || 0) * 100));
654
+ var rollbackRate = Math.round(((decisionLoop.rollbackRate || 0) * 100));
655
+ var medianLatencyMs = Number(decisionLoop.medianLatencyMs || 0);
656
+ var medianLatencyText = medianLatencyMs >= 3600000
657
+ ? (medianLatencyMs / 3600000).toFixed(1) + 'h'
658
+ : medianLatencyMs >= 60000
659
+ ? Math.round(medianLatencyMs / 60000) + 'm'
660
+ : Math.round(medianLatencyMs / 1000) + 's';
661
+ var thisWeekNeg = errorTrend.thisWeek || 0;
662
+ var lastWeekNeg = errorTrend.lastWeek || 0;
663
+ var blocked = gateStats.blocked || 0;
664
+ var resolvedCount = decisionLoop.resolvedCount || 0;
665
+
666
+ summary.textContent = 'ThumbGate is auto-routing ' + fastPathRate + '% of tracked decisions while holding override rate at ' + overrideRate + '% and rollback rate at ' + rollbackRate + '% across '
667
+ + resolvedCount + ' resolved decision' + (resolvedCount === 1 ? '' : 's') + '. Feedback approval is ' + approvalRate + '%, and gates still recorded ' + blocked + ' deny decision' + (blocked === 1 ? '' : 's') + '.';
668
+
669
+ grid.innerHTML = [
670
+ { label: 'Fast path rate', value: fastPathRate + '%', tone: 'purple', note: 'Recorded evaluations that stayed auto-executable' },
671
+ { label: 'Override rate', value: overrideRate + '%', tone: 'red', note: 'Resolved decisions later changed by a human' },
672
+ { label: 'Rollback rate', value: rollbackRate + '%', tone: 'cyan', note: 'Resolved decisions later reversed' },
673
+ { label: 'Median latency', value: medianLatencyText, tone: 'green', note: 'Time from recommendation to recorded outcome' }
674
+ ].map(function(tile) {
675
+ return '<div class="metric-tile"><div class="metric-kicker">' + escHtml(tile.label) + '</div><div class="metric-number ' + escHtml(tile.tone) + '">' + escHtml(tile.value) + '</div><div class="metric-note">' + escHtml(tile.note) + '</div></div>';
676
+ }).join('');
677
+
678
+ var gateDays = {};
679
+ ((gateAudit.days) || []).forEach(function(day) {
680
+ gateDays[day.dayKey] = day;
681
+ });
682
+ var series = buildFeedbackSeries(allTimeline, 14).map(function(item) {
683
+ var gateDay = gateDays[item.dayKey] || { deny: 0, warn: 0, intercepted: 0 };
684
+ return {
685
+ dayKey: item.dayKey,
686
+ label: item.label,
687
+ up: item.up,
688
+ down: item.down,
689
+ total: item.total,
690
+ deny: gateDay.deny || 0,
691
+ warn: gateDay.warn || 0,
692
+ intercepted: gateDay.intercepted || 0
693
+ };
694
+ });
695
+ currentImprovementSeries = series;
696
+ var maxFeedbackTotal = series.reduce(function(max, item) { return Math.max(max, item.total); }, 0);
697
+ var maxGateTotal = series.reduce(function(max, item) { return Math.max(max, item.intercepted); }, 0);
698
+ if (maxFeedbackTotal === 0 && maxGateTotal === 0) {
699
+ currentImprovementSeries = [];
700
+ chart.innerHTML = '<div class="empty" style="grid-column:1 / -1;">No recent feedback events to chart yet.</div>';
701
+ updateMetricsSelection();
702
+ return;
703
+ }
704
+
705
+ chart.innerHTML = series.map(function(item) {
706
+ if (!item.total && !item.intercepted) {
707
+ return '<button class="metrics-bar" type="button" data-day-key="' + escAttr(item.dayKey) + '" onclick="focusTimelineDay(\'' + escAttr(item.dayKey) + '\')">' +
708
+ '<div class="metrics-bar-body"><div class="metrics-bar-stack"><div class="metrics-bar-empty"></div></div><div class="metrics-bar-stack gate"><div class="metrics-bar-empty"></div></div></div>' +
709
+ '<div class="metrics-bar-total">F0</div>' +
710
+ '<div class="metrics-bar-subtotal">G0</div>' +
711
+ '<div class="metrics-bar-label">' + escHtml(item.label) + '</div>' +
712
+ '</button>';
713
+ }
714
+ var feedbackHeight = item.total > 0 ? Math.max(14, Math.round((item.total / Math.max(maxFeedbackTotal, 1)) * 120)) : 0;
715
+ var upHeight = item.up > 0 ? Math.max(8, Math.round((item.up / Math.max(item.total, 1)) * feedbackHeight)) : 0;
716
+ var downHeight = item.down > 0 ? Math.max(8, feedbackHeight - upHeight) : 0;
717
+ if (item.up > 0 && item.down > 0 && upHeight + downHeight > 120) downHeight = Math.max(8, 120 - upHeight);
718
+ var gateHeight = item.intercepted > 0 ? Math.max(12, Math.round((item.intercepted / Math.max(maxGateTotal, 1)) * 84)) : 0;
719
+ var denyHeight = item.deny > 0 ? Math.max(8, Math.round((item.deny / Math.max(item.intercepted, 1)) * gateHeight)) : 0;
720
+ var warnHeight = item.warn > 0 ? Math.max(8, gateHeight - denyHeight) : 0;
721
+ if (item.deny > 0 && item.warn > 0 && denyHeight + warnHeight > 84) warnHeight = Math.max(8, 84 - denyHeight);
722
+ return '<button class="metrics-bar" type="button" data-day-key="' + escAttr(item.dayKey) + '" onclick="focusTimelineDay(\'' + escAttr(item.dayKey) + '\')">' +
723
+ '<div class="metrics-bar-body">' +
724
+ '<div class="metrics-bar-stack">' +
725
+ (item.up ? '<div class="metrics-segment up" style="height:' + upHeight + 'px" title="' + item.up + ' positive feedback"></div>' : '') +
726
+ (item.down ? '<div class="metrics-segment down" style="height:' + downHeight + 'px" title="' + item.down + ' negative feedback"></div>' : '') +
727
+ (!item.total ? '<div class="metrics-bar-empty"></div>' : '') +
728
+ '</div>' +
729
+ '<div class="metrics-bar-stack gate">' +
730
+ (item.deny ? '<div class="metrics-segment deny" style="height:' + denyHeight + 'px" title="' + item.deny + ' gate denies"></div>' : '') +
731
+ (item.warn ? '<div class="metrics-segment warn" style="height:' + warnHeight + 'px" title="' + item.warn + ' gate warns"></div>' : '') +
732
+ (!item.intercepted ? '<div class="metrics-bar-empty"></div>' : '') +
733
+ '</div>' +
734
+ '</div>' +
735
+ '<div class="metrics-bar-total">F' + item.total + '</div>' +
736
+ '<div class="metrics-bar-subtotal">G' + item.intercepted + '</div>' +
737
+ '<div class="metrics-bar-label">' + escHtml(item.label) + '</div>' +
738
+ '</button>';
739
+ }).join('');
740
+ updateMetricsSelection();
741
+ }
742
+
434
743
  function searchAndHighlight(ruleId) {
435
744
  var el = document.querySelector('[data-rule-id="' + ruleId + '"]');
436
745
  if (el) { el.scrollIntoView({ behavior: 'smooth', block: 'center' }); el.style.borderColor = 'var(--cyan)'; setTimeout(function() { el.style.borderColor = ''; }, 2000); }
@@ -452,7 +761,6 @@ function mapLiveRules(payload) {
452
761
  title: result.title || ((result.lesson || {}).summary) || 'Lesson',
453
762
  context: ((result.lesson || {}).howToAvoid) || ((result.lesson || {}).summary) || (sourceFeedback && sourceFeedback.context) || '',
454
763
  occurrences: linkedGate && linkedGate.occurrences ? linkedGate.occurrences : 1,
455
- prevented: linkedGate && linkedGate.occurrences ? linkedGate.occurrences : 0,
456
764
  severity: deriveSeverity(result),
457
765
  tags: Array.isArray(result.tags) ? result.tags : [],
458
766
  lastTriggered: sourceFeedback && sourceFeedback.timestamp ? formatDate(sourceFeedback.timestamp) : formatDate(result.timestamp),
@@ -464,10 +772,13 @@ function mapLiveRules(payload) {
464
772
  function mapTimelineItems(payload, lessonMap) {
465
773
  return (payload.results || []).map(function(entry) {
466
774
  var linkedLesson = lessonMap.get(entry.id) || null;
775
+ var timestamp = entry.timestamp || null;
467
776
  return {
468
- signal: entry.signal || 'down',
777
+ signal: normalizeSignal(entry.signal || entry.feedback),
469
778
  context: entry.context || entry.title || '',
470
779
  date: formatDate(entry.timestamp),
780
+ timestamp: timestamp,
781
+ dayKey: toDayKey(timestamp),
471
782
  learned: linkedLesson ? linkedLesson.title : '',
472
783
  feedbackId: entry.id || ''
473
784
  };
@@ -517,16 +828,19 @@ function loadDemo() {
517
828
  allRules = demoRules.slice(0, 3);
518
829
  allTimeline = demoTimeline.slice(0, 3);
519
830
  dashboardSnapshot = null;
831
+ activeTimelineSignal = 'all';
832
+ activeTimelineDay = null;
520
833
 
521
834
  document.getElementById('statRules').textContent = demoRules.length;
522
835
  document.getElementById('statCritical').textContent = demoRules.filter(function(r) { return r.severity === 'critical'; }).length;
523
- document.getElementById('statPrevented').textContent = demoRules.reduce(function(sum, r) { return sum + (r.prevented || 0); }, 0);
836
+ document.getElementById('statBlocked').textContent = demoRules.reduce(function(sum, r) { return sum + (r.prevented || 0); }, 0);
524
837
  document.getElementById('statTrend').textContent = '12.5%';
525
838
  document.getElementById('statTrendSub').textContent = '7-day approval rate';
526
839
 
527
840
  renderRules(allRules);
528
841
  renderTimeline(allTimeline);
529
842
  renderInsights();
843
+ renderImprovementMetrics({ recentRate: 0.125 }, null);
530
844
  renderUpgradeWall('rulesList');
531
845
  renderUpgradeWall('timelineList');
532
846
  setMode('Demo preview — upgrade to Pro to see your live lessons, timeline, and insights.', true);
@@ -560,16 +874,19 @@ async function loadLive() {
560
874
  .filter(function(rule) { return rule.sourceFeedbackId; })
561
875
  .map(function(rule) { return [rule.sourceFeedbackId, rule]; }));
562
876
  allTimeline = mapTimelineItems(feedbackPayload, lessonMap);
877
+ activeTimelineSignal = 'all';
878
+ activeTimelineDay = null;
563
879
 
564
880
  document.getElementById('statRules').textContent = allRules.length;
565
881
  document.getElementById('statCritical').textContent = allRules.filter(function(r) { return r.severity === 'critical'; }).length;
566
- document.getElementById('statPrevented').textContent = allRules.reduce(function(sum, r) { return sum + (r.prevented || 0); }, 0);
882
+ document.getElementById('statBlocked').textContent = ((dashboardSnapshot.gateStats || {}).blocked || 0);
567
883
  document.getElementById('statTrend').textContent = ((stats.recentRate || stats.approvalRate || 0) * 100).toFixed(1) + '%';
568
884
  document.getElementById('statTrendSub').textContent = 'Live approval rate';
569
885
 
570
886
  renderRules(allRules);
571
887
  renderTimeline(allTimeline);
572
888
  renderInsights(dashboardSnapshot);
889
+ renderImprovementMetrics(stats, dashboardSnapshot);
573
890
  document.getElementById('proBadge').style.display = 'none';
574
891
  setMode('Local Pro connected — this lessons view is reading your live rules, feedback timeline, and insights.', false);
575
892
  } catch (_err) {