thumbgate 0.9.10

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 (364) hide show
  1. package/.claude-plugin/README.md +134 -0
  2. package/.claude-plugin/bundle/icon.png +0 -0
  3. package/.claude-plugin/bundle/icon.svg +18 -0
  4. package/.claude-plugin/bundle/server/index.js +24 -0
  5. package/.claude-plugin/marketplace.json +36 -0
  6. package/.claude-plugin/plugin.json +21 -0
  7. package/.well-known/mcp/server-card.json +231 -0
  8. package/LICENSE +21 -0
  9. package/README.md +375 -0
  10. package/adapters/README.md +9 -0
  11. package/adapters/amp/skills/thumbgate-feedback/SKILL.md +22 -0
  12. package/adapters/chatgpt/INSTALL.md +83 -0
  13. package/adapters/chatgpt/openapi.yaml +1281 -0
  14. package/adapters/claude/.mcp.json +14 -0
  15. package/adapters/codex/config.toml +9 -0
  16. package/adapters/gemini/function-declarations.json +224 -0
  17. package/adapters/mcp/server-stdio.js +788 -0
  18. package/adapters/opencode/opencode.json +15 -0
  19. package/bin/cli.js +1484 -0
  20. package/bin/memory.sh +64 -0
  21. package/bin/obsidian-sync.sh +20 -0
  22. package/bin/postinstall.js +37 -0
  23. package/config/build-metadata.json +4 -0
  24. package/config/e2e-critical-flows.json +45 -0
  25. package/config/gate-templates.json +77 -0
  26. package/config/gates/claim-verification.json +29 -0
  27. package/config/gates/computer-use.json +39 -0
  28. package/config/gates/default.json +117 -0
  29. package/config/github-about.json +25 -0
  30. package/config/mcp-allowlists.json +135 -0
  31. package/config/model-tiers.json +33 -0
  32. package/config/partner-routing.json +132 -0
  33. package/config/policy-bundles/constrained-v1.json +64 -0
  34. package/config/policy-bundles/default-v1.json +91 -0
  35. package/config/rubrics/default-v1.json +52 -0
  36. package/config/skill-packs/react-testing.json +23 -0
  37. package/config/skill-packs/stripe-integration/references/api-spec.json +1 -0
  38. package/config/skill-packs/stripe-integration/references/webhook-guide.md +3 -0
  39. package/config/skill-specs/pr-reviewer.json +9 -0
  40. package/config/skill-specs/release-status.json +9 -0
  41. package/config/skill-specs/ticket-triage.json +9 -0
  42. package/config/subagent-profiles.json +32 -0
  43. package/config/tessl-tiles.json +29 -0
  44. package/config/thumbgate-settings.managed.json +12 -0
  45. package/openapi/openapi.yaml +1281 -0
  46. package/package.json +283 -0
  47. package/plugins/amp-skill/INSTALL.md +52 -0
  48. package/plugins/amp-skill/SKILL.md +64 -0
  49. package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +22 -0
  50. package/plugins/claude-codex-bridge/.mcp.json +12 -0
  51. package/plugins/claude-codex-bridge/INSTALL.md +43 -0
  52. package/plugins/claude-codex-bridge/README.md +46 -0
  53. package/plugins/claude-codex-bridge/scripts/codex-bridge.js +288 -0
  54. package/plugins/claude-codex-bridge/skills/adversarial-review/SKILL.md +24 -0
  55. package/plugins/claude-codex-bridge/skills/result/SKILL.md +22 -0
  56. package/plugins/claude-codex-bridge/skills/review/SKILL.md +28 -0
  57. package/plugins/claude-codex-bridge/skills/second-pass/SKILL.md +27 -0
  58. package/plugins/claude-codex-bridge/skills/setup/SKILL.md +21 -0
  59. package/plugins/claude-codex-bridge/skills/status/SKILL.md +19 -0
  60. package/plugins/claude-skill/INSTALL.md +55 -0
  61. package/plugins/claude-skill/SKILL.md +46 -0
  62. package/plugins/codex-profile/.codex-plugin/plugin.json +43 -0
  63. package/plugins/codex-profile/.mcp.json +12 -0
  64. package/plugins/codex-profile/AGENTS.md +20 -0
  65. package/plugins/codex-profile/INSTALL.md +66 -0
  66. package/plugins/codex-profile/README.md +37 -0
  67. package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +23 -0
  68. package/plugins/cursor-marketplace/CHANGELOG.md +30 -0
  69. package/plugins/cursor-marketplace/LICENSE +21 -0
  70. package/plugins/cursor-marketplace/README.md +124 -0
  71. package/plugins/cursor-marketplace/agents/reliability-reviewer.md +31 -0
  72. package/plugins/cursor-marketplace/assets/logo-400x400.png +0 -0
  73. package/plugins/cursor-marketplace/commands/capture-feedback.md +33 -0
  74. package/plugins/cursor-marketplace/commands/check-gates.md +25 -0
  75. package/plugins/cursor-marketplace/commands/show-lessons.md +27 -0
  76. package/plugins/cursor-marketplace/hooks/hooks.json +10 -0
  77. package/plugins/cursor-marketplace/mcp.json +12 -0
  78. package/plugins/cursor-marketplace/rules/feedback-capture.mdc +34 -0
  79. package/plugins/cursor-marketplace/rules/pre-action-gates.mdc +30 -0
  80. package/plugins/cursor-marketplace/rules/session-continuity.mdc +28 -0
  81. package/plugins/cursor-marketplace/scripts/gate-check.sh +11 -0
  82. package/plugins/cursor-marketplace/skills/capture-feedback/SKILL.md +47 -0
  83. package/plugins/cursor-marketplace/skills/prevention-rules/SKILL.md +31 -0
  84. package/plugins/cursor-marketplace/skills/recall-context/SKILL.md +30 -0
  85. package/plugins/cursor-marketplace/skills/search-lessons/SKILL.md +33 -0
  86. package/plugins/gemini-extension/INSTALL.md +92 -0
  87. package/plugins/gemini-extension/gemini_prompt.txt +14 -0
  88. package/plugins/gemini-extension/tool_contract.json +45 -0
  89. package/plugins/opencode-profile/INSTALL.md +57 -0
  90. package/public/assets/instagram-card.png +0 -0
  91. package/public/assets/tiktok-agent-memory.mp4 +0 -0
  92. package/public/blog.html +400 -0
  93. package/public/dashboard.html +1093 -0
  94. package/public/guide.html +317 -0
  95. package/public/index.html +1014 -0
  96. package/public/learn/agent-harness-pattern.html +180 -0
  97. package/public/learn/ai-agent-persistent-memory.html +202 -0
  98. package/public/learn/learn.css +45 -0
  99. package/public/learn/mcp-pre-action-gates-explained.html +172 -0
  100. package/public/learn/stop-ai-agent-force-push.html +134 -0
  101. package/public/learn/vibe-coding-safety-net.html +142 -0
  102. package/public/learn.html +213 -0
  103. package/public/lessons.html +650 -0
  104. package/public/vercel.json +8 -0
  105. package/scripts/__pycache__/train_from_feedback.cpython-312.pyc +0 -0
  106. package/scripts/a2ui-engine.js +73 -0
  107. package/scripts/access-anomaly-detector.js +12 -0
  108. package/scripts/adk-consolidator.js +266 -0
  109. package/scripts/agent-readiness.js +220 -0
  110. package/scripts/agent-security-hardening.js +227 -0
  111. package/scripts/agentic-data-pipeline.js +847 -0
  112. package/scripts/analytics-report.js +328 -0
  113. package/scripts/analytics-window.js +158 -0
  114. package/scripts/async-job-runner.js +1001 -0
  115. package/scripts/audit-trail.js +398 -0
  116. package/scripts/auto-promote-gates.js +299 -0
  117. package/scripts/auto-wire-hooks.js +312 -0
  118. package/scripts/autonomous-sales-agent.js +39 -0
  119. package/scripts/autoresearch-runner.js +216 -0
  120. package/scripts/background-agent-governance.js +237 -0
  121. package/scripts/behavioral-extraction.js +97 -0
  122. package/scripts/belief-update.js +84 -0
  123. package/scripts/billing.js +2438 -0
  124. package/scripts/bot-detector.js +50 -0
  125. package/scripts/budget-guard.js +173 -0
  126. package/scripts/build-claude-mcpb.js +189 -0
  127. package/scripts/build-metadata.js +97 -0
  128. package/scripts/check-congruence.js +322 -0
  129. package/scripts/cli-feedback.js +135 -0
  130. package/scripts/cli-telemetry.js +87 -0
  131. package/scripts/cloudflare-dynamic-sandbox.js +315 -0
  132. package/scripts/code-reasoning.js +350 -0
  133. package/scripts/codegraph-context.js +466 -0
  134. package/scripts/commercial-offer.js +56 -0
  135. package/scripts/computer-use-firewall.js +250 -0
  136. package/scripts/context-engine.js +694 -0
  137. package/scripts/contextfs.js +1287 -0
  138. package/scripts/conversation-context.js +119 -0
  139. package/scripts/creator-campaigns.js +239 -0
  140. package/scripts/daemon-manager.js +108 -0
  141. package/scripts/daily-digest.js +11 -0
  142. package/scripts/dashboard-render-spec.js +395 -0
  143. package/scripts/dashboard.js +1058 -0
  144. package/scripts/data-governance.js +173 -0
  145. package/scripts/delegation-runtime.js +900 -0
  146. package/scripts/deploy-gcp.sh +44 -0
  147. package/scripts/deploy-policy.js +263 -0
  148. package/scripts/disagreement-mining.js +315 -0
  149. package/scripts/dispatch-brief.js +159 -0
  150. package/scripts/distribution-surfaces.js +44 -0
  151. package/scripts/dpo-optimizer.js +209 -0
  152. package/scripts/ephemeral-agent-store.js +219 -0
  153. package/scripts/eval-harness.js +56 -0
  154. package/scripts/evolution-state.js +241 -0
  155. package/scripts/experiment-tracker.js +267 -0
  156. package/scripts/export-databricks-bundle.js +242 -0
  157. package/scripts/export-dpo-pairs.js +345 -0
  158. package/scripts/export-kto-pairs.js +310 -0
  159. package/scripts/export-training.js +448 -0
  160. package/scripts/failure-diagnostics.js +558 -0
  161. package/scripts/feedback-attribution.js +313 -0
  162. package/scripts/feedback-fallback.js +111 -0
  163. package/scripts/feedback-history-distiller.js +391 -0
  164. package/scripts/feedback-inbox-read.js +162 -0
  165. package/scripts/feedback-loop.js +1887 -0
  166. package/scripts/feedback-paths.js +145 -0
  167. package/scripts/feedback-quality.js +139 -0
  168. package/scripts/feedback-root-consolidator.js +238 -0
  169. package/scripts/feedback-schema.js +426 -0
  170. package/scripts/feedback-session.js +286 -0
  171. package/scripts/feedback-to-memory.js +185 -0
  172. package/scripts/feedback-to-rules.js +163 -0
  173. package/scripts/filesystem-search.js +404 -0
  174. package/scripts/funnel-analytics.js +35 -0
  175. package/scripts/gate-satisfy.js +42 -0
  176. package/scripts/gate-stats.js +116 -0
  177. package/scripts/gate-templates.js +70 -0
  178. package/scripts/gates-engine.js +816 -0
  179. package/scripts/generate-paperbanana-diagrams.sh +99 -0
  180. package/scripts/generate-pretool-hook.sh +40 -0
  181. package/scripts/github-about.js +350 -0
  182. package/scripts/github-outreach.js +65 -0
  183. package/scripts/gtm-revenue-loop.js +520 -0
  184. package/scripts/hallucination-detector.js +226 -0
  185. package/scripts/hf-papers.js +317 -0
  186. package/scripts/history-distiller.js +200 -0
  187. package/scripts/hook-auto-capture.sh +95 -0
  188. package/scripts/hook-stop-pr-thread-check.sh +68 -0
  189. package/scripts/hook-stop-self-score.sh +51 -0
  190. package/scripts/hook-stop-verify-deploy.sh +31 -0
  191. package/scripts/hook-thumbgate-cache-updater.js +48 -0
  192. package/scripts/hook-verify-before-done.sh +20 -0
  193. package/scripts/hosted-config.js +170 -0
  194. package/scripts/hybrid-feedback-context.js +676 -0
  195. package/scripts/install-mcp.js +159 -0
  196. package/scripts/intent-router.js +392 -0
  197. package/scripts/internal-agent-bootstrap.js +490 -0
  198. package/scripts/jsonl-watcher.js +155 -0
  199. package/scripts/lesson-db.js +613 -0
  200. package/scripts/lesson-inference.js +315 -0
  201. package/scripts/lesson-retrieval.js +95 -0
  202. package/scripts/lesson-rotation.js +137 -0
  203. package/scripts/lesson-search.js +644 -0
  204. package/scripts/lesson-synthesis.js +196 -0
  205. package/scripts/license.js +50 -0
  206. package/scripts/local-model-profile.js +383 -0
  207. package/scripts/markdown-escape.js +12 -0
  208. package/scripts/marketing-experiment.js +671 -0
  209. package/scripts/mcp-config.js +149 -0
  210. package/scripts/mcp-policy.js +99 -0
  211. package/scripts/memalign-recall.js +111 -0
  212. package/scripts/memory-firewall.js +222 -0
  213. package/scripts/memory-migration.js +296 -0
  214. package/scripts/meta-policy.js +194 -0
  215. package/scripts/metered-billing.js +16 -0
  216. package/scripts/model-tier-router.js +301 -0
  217. package/scripts/money-watcher.js +71 -0
  218. package/scripts/multi-hop-recall.js +240 -0
  219. package/scripts/natural-language-harness.js +330 -0
  220. package/scripts/obsidian-export.js +712 -0
  221. package/scripts/operational-dashboard.js +103 -0
  222. package/scripts/operational-summary.js +93 -0
  223. package/scripts/optimize-context.js +17 -0
  224. package/scripts/org-dashboard.js +201 -0
  225. package/scripts/partner-orchestration.js +146 -0
  226. package/scripts/per-step-scoring.js +165 -0
  227. package/scripts/perplexity-marketing.js +466 -0
  228. package/scripts/pii-scanner.js +153 -0
  229. package/scripts/plan-gate.js +154 -0
  230. package/scripts/post-everywhere.js +308 -0
  231. package/scripts/post-to-x-retry.sh +22 -0
  232. package/scripts/post-to-x.js +369 -0
  233. package/scripts/pr-manager.js +236 -0
  234. package/scripts/predictive-insights.js +356 -0
  235. package/scripts/principle-extractor.js +162 -0
  236. package/scripts/pro-features.js +40 -0
  237. package/scripts/pro-local-dashboard.js +174 -0
  238. package/scripts/problem-detail.js +53 -0
  239. package/scripts/product-feedback.js +134 -0
  240. package/scripts/profile-router.js +245 -0
  241. package/scripts/prompt-dlp.js +221 -0
  242. package/scripts/prompt-guard.js +83 -0
  243. package/scripts/prove-adapters.js +863 -0
  244. package/scripts/prove-attribution.js +365 -0
  245. package/scripts/prove-automation.js +653 -0
  246. package/scripts/prove-autoresearch.js +304 -0
  247. package/scripts/prove-claim-verification.js +277 -0
  248. package/scripts/prove-cloudflare-sandbox.js +163 -0
  249. package/scripts/prove-data-pipeline.js +410 -0
  250. package/scripts/prove-data-quality.js +227 -0
  251. package/scripts/prove-evolution.js +352 -0
  252. package/scripts/prove-harnesses.js +287 -0
  253. package/scripts/prove-intelligence.js +259 -0
  254. package/scripts/prove-lancedb.js +371 -0
  255. package/scripts/prove-local-intelligence.js +342 -0
  256. package/scripts/prove-loop-closure.js +263 -0
  257. package/scripts/prove-predictive-insights.js +357 -0
  258. package/scripts/prove-runtime.js +350 -0
  259. package/scripts/prove-seo-gsd.js +234 -0
  260. package/scripts/prove-settings.js +279 -0
  261. package/scripts/prove-subway-upgrades.js +277 -0
  262. package/scripts/prove-tessl.js +229 -0
  263. package/scripts/prove-training-export.js +327 -0
  264. package/scripts/prove-workflow-contract.js +116 -0
  265. package/scripts/prove-xmemory.js +332 -0
  266. package/scripts/publish-decision.js +133 -0
  267. package/scripts/pulse.js +80 -0
  268. package/scripts/rate-limiter.js +125 -0
  269. package/scripts/reddit-dm-outreach.js +182 -0
  270. package/scripts/reddit-monitor-cron.sh +26 -0
  271. package/scripts/reflector-agent.js +221 -0
  272. package/scripts/reminder-engine.js +132 -0
  273. package/scripts/revenue-status.js +472 -0
  274. package/scripts/risk-scorer.js +458 -0
  275. package/scripts/rlaif-self-audit.js +129 -0
  276. package/scripts/rubric-engine.js +230 -0
  277. package/scripts/schedule-manager.js +251 -0
  278. package/scripts/secret-scanner.js +414 -0
  279. package/scripts/self-heal.js +147 -0
  280. package/scripts/self-healing-check.js +188 -0
  281. package/scripts/semantic-layer.js +98 -0
  282. package/scripts/seo-gsd.js +1153 -0
  283. package/scripts/settings-hierarchy.js +214 -0
  284. package/scripts/shieldcortex-memory-firewall-runner.mjs +53 -0
  285. package/scripts/skill-exporter.js +262 -0
  286. package/scripts/skill-generator.js +446 -0
  287. package/scripts/skill-materializer.js +134 -0
  288. package/scripts/skill-packs.js +136 -0
  289. package/scripts/skill-proposer.js +99 -0
  290. package/scripts/skill-quality-tracker.js +284 -0
  291. package/scripts/slo-alert-engine.js +14 -0
  292. package/scripts/slow-loop.js +72 -0
  293. package/scripts/social-analytics/db/schema.sql +32 -0
  294. package/scripts/social-analytics/digest.js +256 -0
  295. package/scripts/social-analytics/generate-instagram-card.js +97 -0
  296. package/scripts/social-analytics/instagram-thumbgate-post.js +73 -0
  297. package/scripts/social-analytics/mcp-server.js +289 -0
  298. package/scripts/social-analytics/normalizer.js +580 -0
  299. package/scripts/social-analytics/notify.js +162 -0
  300. package/scripts/social-analytics/poll-all.js +107 -0
  301. package/scripts/social-analytics/pollers/github.js +195 -0
  302. package/scripts/social-analytics/pollers/instagram.js +253 -0
  303. package/scripts/social-analytics/pollers/linkedin.js +330 -0
  304. package/scripts/social-analytics/pollers/plausible.js +247 -0
  305. package/scripts/social-analytics/pollers/reddit.js +306 -0
  306. package/scripts/social-analytics/pollers/threads.js +233 -0
  307. package/scripts/social-analytics/pollers/tiktok.js +203 -0
  308. package/scripts/social-analytics/pollers/x.js +227 -0
  309. package/scripts/social-analytics/pollers/youtube.js +304 -0
  310. package/scripts/social-analytics/pollers/zernio.js +180 -0
  311. package/scripts/social-analytics/publish-instagram-thumbgate.js +85 -0
  312. package/scripts/social-analytics/publishers/devto.js +122 -0
  313. package/scripts/social-analytics/publishers/instagram.js +317 -0
  314. package/scripts/social-analytics/publishers/linkedin.js +294 -0
  315. package/scripts/social-analytics/publishers/reddit.js +390 -0
  316. package/scripts/social-analytics/publishers/threads.js +275 -0
  317. package/scripts/social-analytics/publishers/tiktok.js +217 -0
  318. package/scripts/social-analytics/publishers/x.js +259 -0
  319. package/scripts/social-analytics/publishers/youtube.js +223 -0
  320. package/scripts/social-analytics/publishers/zernio.js +209 -0
  321. package/scripts/social-analytics/run-digest.js +34 -0
  322. package/scripts/social-analytics/store.js +257 -0
  323. package/scripts/social-analytics/utm.js +143 -0
  324. package/scripts/social-pipeline.js +2628 -0
  325. package/scripts/social-quality-gate.js +18 -0
  326. package/scripts/social-reply-monitor.js +445 -0
  327. package/scripts/status-dashboard.js +155 -0
  328. package/scripts/statusline-lesson.js +16 -0
  329. package/scripts/statusline-tower.js +8 -0
  330. package/scripts/statusline.sh +116 -0
  331. package/scripts/stripe-live-status.js +115 -0
  332. package/scripts/subagent-profiles.js +79 -0
  333. package/scripts/sync-gh-secrets-from-env.sh +70 -0
  334. package/scripts/sync-github-about.js +52 -0
  335. package/scripts/sync-version.js +451 -0
  336. package/scripts/synthetic-dpo.js +234 -0
  337. package/scripts/telemetry-analytics.js +821 -0
  338. package/scripts/tessl-export.js +371 -0
  339. package/scripts/test-coverage.js +120 -0
  340. package/scripts/thompson-sampling.js +417 -0
  341. package/scripts/thumbgate-search.js +189 -0
  342. package/scripts/tool-kpi-tracker.js +12 -0
  343. package/scripts/tool-registry.js +811 -0
  344. package/scripts/train_from_feedback.py +910 -0
  345. package/scripts/user-profile.js +78 -0
  346. package/scripts/validate-feedback.js +580 -0
  347. package/scripts/validate-workflow-contract.js +287 -0
  348. package/scripts/vector-store.js +198 -0
  349. package/scripts/verification-loop.js +291 -0
  350. package/scripts/verify-obsidian-setup.sh +269 -0
  351. package/scripts/verify-run.js +269 -0
  352. package/scripts/webhook-delivery.js +62 -0
  353. package/scripts/weekly-auto-post.js +124 -0
  354. package/scripts/workflow-runs.js +154 -0
  355. package/scripts/workflow-sprint-intake.js +475 -0
  356. package/scripts/workspace-evolver.js +374 -0
  357. package/scripts/x-autonomous-marketing.js +139 -0
  358. package/scripts/xmemory-lite.js +405 -0
  359. package/skills/agent-memory/SKILL.md +97 -0
  360. package/skills/solve-architecture-autonomy/SKILL.md +17 -0
  361. package/skills/solve-architecture-autonomy/tool.js +33 -0
  362. package/skills/thumbgate/SKILL.md +114 -0
  363. package/skills/thumbgate-feedback/SKILL.md +49 -0
  364. package/src/api/server.js +4208 -0
@@ -0,0 +1,1058 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const { aggregateFailureDiagnostics } = require('./failure-diagnostics');
7
+ const { getBillingSummary, loadFunnelLedger, loadResolvedRevenueEvents } = require('./billing');
8
+ const { getTelemetryAnalytics, loadTelemetryEvents } = require('./telemetry-analytics');
9
+ const { getAutoGatesPath } = require('./auto-promote-gates');
10
+ const { summarizeDelegation } = require('./delegation-runtime');
11
+ const { loadGatesConfig } = require('./gates-engine');
12
+ const { filterEntriesForWindow, resolveAnalyticsWindow } = require('./analytics-window');
13
+ const { resolveHostedBillingConfig } = require('./hosted-config');
14
+ const { generateAgentReadinessReport } = require('./agent-readiness');
15
+ const { summarizeGateTemplates } = require('./gate-templates');
16
+ const { generateOrgDashboard } = require('./org-dashboard');
17
+ const { buildPredictiveInsights } = require('./predictive-insights');
18
+ const { routeProfile } = require('./profile-router');
19
+ const { getSettingsStatus } = require('./settings-hierarchy');
20
+ const { summarizeWorkflowRuns } = require('./workflow-runs');
21
+ const { searchLessons } = require('./lesson-search');
22
+
23
+ const PROJECT_ROOT = path.join(__dirname, '..');
24
+ const DEFAULT_GATES_PATH = path.join(PROJECT_ROOT, 'config', 'gates', 'default.json');
25
+ const LANDING_PAGE_PATH = path.join(PROJECT_ROOT, 'public', 'index.html');
26
+
27
+ // ---------------------------------------------------------------------------
28
+ // Data readers
29
+ // ---------------------------------------------------------------------------
30
+
31
+ function readJSONL(filePath) {
32
+ if (!fs.existsSync(filePath)) return [];
33
+ const raw = fs.readFileSync(filePath, 'utf-8').trim();
34
+ if (!raw) return [];
35
+ return raw.split('\n').map((line) => {
36
+ try { return JSON.parse(line); } catch { return null; }
37
+ }).filter(Boolean);
38
+ }
39
+
40
+ function readJsonFile(filePath) {
41
+ if (!fs.existsSync(filePath)) return null;
42
+ try {
43
+ return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
44
+ } catch {
45
+ return null;
46
+ }
47
+ }
48
+
49
+ function normalizeText(value) {
50
+ if (value === undefined || value === null) return null;
51
+ const text = String(value).trim();
52
+ return text || null;
53
+ }
54
+
55
+ function pickFirstText(...values) {
56
+ for (const value of values) {
57
+ const normalized = normalizeText(value);
58
+ if (normalized) return normalized;
59
+ }
60
+ return null;
61
+ }
62
+
63
+ // ---------------------------------------------------------------------------
64
+ // Approval rate + trend
65
+ // ---------------------------------------------------------------------------
66
+
67
+ function computeApprovalStats(entries) {
68
+ const total = entries.length;
69
+ const positive = entries.filter((e) => e.signal === 'positive').length;
70
+ const negative = entries.filter((e) => e.signal === 'negative').length;
71
+ const approvalRate = total > 0 ? Math.round((positive / total) * 100) : 0;
72
+
73
+ // 7-day trend
74
+ const now = Date.now();
75
+ const sevenDaysAgo = now - 7 * 24 * 60 * 60 * 1000;
76
+ const recentEntries = entries.filter((e) => {
77
+ const ts = e.timestamp ? new Date(e.timestamp).getTime() : 0;
78
+ return ts >= sevenDaysAgo;
79
+ });
80
+ const recentPositive = recentEntries.filter((e) => e.signal === 'positive').length;
81
+ const recentRate = recentEntries.length > 0
82
+ ? Math.round((recentPositive / recentEntries.length) * 100)
83
+ : approvalRate;
84
+
85
+ let trendDirection = 'stable';
86
+ const diff = recentRate - approvalRate;
87
+ if (diff > 5) trendDirection = 'improving';
88
+ else if (diff < -5) trendDirection = 'declining';
89
+
90
+ return {
91
+ total,
92
+ positive,
93
+ negative,
94
+ approvalRate,
95
+ recentRate,
96
+ trendDirection,
97
+ };
98
+ }
99
+
100
+ // ---------------------------------------------------------------------------
101
+ // Gate enforcement stats
102
+ // ---------------------------------------------------------------------------
103
+
104
+ function computeGateStats() {
105
+ const autoGatesPath = getAutoGatesPath();
106
+ const statsPath = path.join(
107
+ process.env.HOME || '/tmp',
108
+ '.thumbgate',
109
+ 'gate-stats.json'
110
+ );
111
+ const stats = readJsonFile(statsPath) || { blocked: 0, warned: 0, passed: 0, byGate: {} };
112
+
113
+ // Count manual vs auto-promoted gates
114
+ const defaultGates = readJsonFile(DEFAULT_GATES_PATH);
115
+ const autoGates = readJsonFile(autoGatesPath);
116
+ const manualCount = defaultGates && Array.isArray(defaultGates.gates) ? defaultGates.gates.length : 0;
117
+ const autoCount = autoGates && Array.isArray(autoGates.gates) ? autoGates.gates.length : 0;
118
+ const totalGates = manualCount + autoCount;
119
+
120
+ // Top blocked gate
121
+ let topBlocked = null;
122
+ let topBlockedCount = 0;
123
+ if (stats.byGate) {
124
+ for (const [gateId, gateStat] of Object.entries(stats.byGate)) {
125
+ const blocked = gateStat.blocked || 0;
126
+ if (blocked > topBlockedCount) {
127
+ topBlockedCount = blocked;
128
+ topBlocked = gateId;
129
+ }
130
+ }
131
+ }
132
+
133
+ return {
134
+ totalGates,
135
+ manualCount,
136
+ autoCount,
137
+ blocked: stats.blocked || 0,
138
+ warned: stats.warned || 0,
139
+ passed: stats.passed || 0,
140
+ topBlocked,
141
+ topBlockedCount,
142
+ byGate: stats.byGate || {},
143
+ };
144
+ }
145
+
146
+ function listActiveGates() {
147
+ try {
148
+ const config = loadGatesConfig();
149
+ return (config.gates || []).map((gate) => ({
150
+ id: gate.id || null,
151
+ name: gate.id || gate.pattern || 'gate',
152
+ pattern: gate.pattern || '',
153
+ action: gate.action || 'warn',
154
+ severity: gate.severity || 'medium',
155
+ layer: gate.layer || null,
156
+ }));
157
+ } catch {
158
+ return [];
159
+ }
160
+ }
161
+
162
+ // ---------------------------------------------------------------------------
163
+ // Prevention impact
164
+ // ---------------------------------------------------------------------------
165
+
166
+ function computePreventionImpact(feedbackDir, gateStats) {
167
+ const autoGatesPath = getAutoGatesPath();
168
+ const preventionRulesPath = path.join(feedbackDir, 'prevention-rules.md');
169
+ let ruleCount = 0;
170
+ if (fs.existsSync(preventionRulesPath)) {
171
+ const content = fs.readFileSync(preventionRulesPath, 'utf-8');
172
+ const headers = content.match(/^## /gm);
173
+ ruleCount = headers ? headers.length : 0;
174
+ }
175
+
176
+ // Estimate time saved: ~16 min per blocked action (conservative)
177
+ const estimatedMinutesSaved = gateStats.blocked * 16;
178
+ const estimatedHoursSaved = (estimatedMinutesSaved / 60).toFixed(1);
179
+
180
+ // Last auto-promotion
181
+ const autoGates = readJsonFile(autoGatesPath);
182
+ let lastPromotion = null;
183
+ if (autoGates && Array.isArray(autoGates.promotionLog) && autoGates.promotionLog.length > 0) {
184
+ const sorted = autoGates.promotionLog
185
+ .filter((p) => p.timestamp)
186
+ .sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
187
+ if (sorted.length > 0) {
188
+ const last = sorted[0];
189
+ const daysAgo = Math.round((Date.now() - new Date(last.timestamp).getTime()) / (1000 * 60 * 60 * 24));
190
+ lastPromotion = { id: last.gateId || last.id || 'unknown', daysAgo };
191
+ }
192
+ }
193
+
194
+ return {
195
+ estimatedHoursSaved,
196
+ ruleCount,
197
+ lastPromotion,
198
+ };
199
+ }
200
+
201
+ // ---------------------------------------------------------------------------
202
+ // Session trend (last N sessions)
203
+ // ---------------------------------------------------------------------------
204
+
205
+ function computeSessionTrend(entries, windowCount) {
206
+ if (entries.length < 10) return { bars: '', percentage: 0 };
207
+ const windowSize = Math.max(1, Math.floor(entries.length / windowCount));
208
+ const windows = [];
209
+ for (let i = 0; i + windowSize <= entries.length; i += windowSize) {
210
+ const slice = entries.slice(i, i + windowSize);
211
+ const pos = slice.filter((e) => e.signal === 'positive').length;
212
+ windows.push(Math.round((pos / slice.length) * 100));
213
+ }
214
+ const recent = windows.slice(-windowCount);
215
+ const avg = recent.length > 0 ? Math.round(recent.reduce((a, b) => a + b, 0) / recent.length) : 0;
216
+ const filledBlocks = Math.round((avg / 100) * windowCount);
217
+ const bars = '\u2588'.repeat(filledBlocks) + '\u2591'.repeat(windowCount - filledBlocks);
218
+ return { bars, percentage: avg };
219
+ }
220
+
221
+ // ---------------------------------------------------------------------------
222
+ // System health
223
+ // ---------------------------------------------------------------------------
224
+
225
+ function computeSystemHealth(feedbackDir, gateStats) {
226
+ const feedbackLogPath = path.join(feedbackDir, 'feedback-log.jsonl');
227
+ const memoryLogPath = path.join(feedbackDir, 'memory-log.jsonl');
228
+
229
+ const feedbackCount = readJSONL(feedbackLogPath).length;
230
+ const memoryCount = readJSONL(memoryLogPath).length;
231
+
232
+ return {
233
+ feedbackCount,
234
+ memoryCount,
235
+ gateConfigLoaded: gateStats.totalGates > 0,
236
+ gateCount: gateStats.totalGates,
237
+ mcpServerRunning: true, // If dashboard is running, server is available
238
+ };
239
+ }
240
+
241
+ function safeRate(numerator, denominator) {
242
+ if (!denominator) return 0;
243
+ return Number((numerator / denominator).toFixed(4));
244
+ }
245
+
246
+ function computeEfficiencyMetrics(feedbackDir) {
247
+ const provenanceDir = path.join(feedbackDir, 'contextfs', 'provenance');
248
+ const packs = readJSONL(path.join(provenanceDir, 'packs.jsonl'));
249
+ const cacheHits = packs.filter((pack) => pack && pack.cache && pack.cache.hit === true);
250
+ const similarities = cacheHits
251
+ .map((pack) => Number(pack.cache && pack.cache.similarity))
252
+ .filter((value) => Number.isFinite(value));
253
+ const estimatedContextCharsReused = cacheHits.reduce((sum, pack) => {
254
+ const usedChars = Number(pack && pack.usedChars);
255
+ return sum + (Number.isFinite(usedChars) ? usedChars : 0);
256
+ }, 0);
257
+
258
+ return {
259
+ semanticCacheEnabled: process.env.THUMBGATE_SEMANTIC_CACHE_ENABLED !== 'false',
260
+ contextPackRequests: packs.length,
261
+ semanticCacheHits: cacheHits.length,
262
+ semanticCacheHitRate: safeRate(cacheHits.length, packs.length),
263
+ averageSemanticSimilarity: similarities.length > 0
264
+ ? Number((similarities.reduce((sum, value) => sum + value, 0) / similarities.length).toFixed(4))
265
+ : 0,
266
+ estimatedContextCharsReused,
267
+ estimatedContextTokensReused: Math.round(estimatedContextCharsReused / 4),
268
+ };
269
+ }
270
+
271
+ function resolveJourneyKey(entry = {}) {
272
+ const metadata = entry.metadata && typeof entry.metadata === 'object' ? entry.metadata : {};
273
+ const attribution = entry.attribution && typeof entry.attribution === 'object' ? entry.attribution : {};
274
+ return pickFirstText(
275
+ entry.acquisitionId,
276
+ metadata.acquisitionId,
277
+ attribution.acquisitionId,
278
+ entry.traceId,
279
+ metadata.traceId,
280
+ entry.installId,
281
+ metadata.installId,
282
+ entry.visitorId,
283
+ metadata.visitorId,
284
+ entry.sessionId,
285
+ metadata.sessionId,
286
+ entry.orderId,
287
+ entry.evidence
288
+ );
289
+ }
290
+
291
+ function countCoverage(entries, resolver) {
292
+ if (!entries.length) return 0;
293
+ const matched = entries.filter((entry) => resolver(entry)).length;
294
+ return safeRate(matched, entries.length);
295
+ }
296
+
297
+ function computeAnalyticsSummary(feedbackDir, options = {}) {
298
+ const analyticsWindow = resolveAnalyticsWindow(options.analyticsWindow || options);
299
+ const telemetryEntries = filterEntriesForWindow(
300
+ loadTelemetryEvents(feedbackDir),
301
+ analyticsWindow,
302
+ (entry) => entry && (entry.receivedAt || entry.timestamp)
303
+ );
304
+ const telemetry = getTelemetryAnalytics(feedbackDir, analyticsWindow);
305
+ const billing = options.billingSummary || getBillingSummary(analyticsWindow);
306
+ const funnelEntries = filterEntriesForWindow(
307
+ loadFunnelLedger(),
308
+ analyticsWindow,
309
+ (entry) => entry && entry.timestamp
310
+ );
311
+ const paidOrderEntries = filterEntriesForWindow(
312
+ loadResolvedRevenueEvents(),
313
+ analyticsWindow,
314
+ (entry) => entry && entry.timestamp
315
+ ).filter((entry) => entry && entry.status === 'paid');
316
+ const efficiency = computeEfficiencyMetrics(feedbackDir);
317
+ const northStar = summarizeWorkflowRuns(feedbackDir);
318
+ const uniqueVisitors = telemetry.visitors.uniqueVisitors;
319
+ const ctaClicks = telemetry.ctas.totalClicks;
320
+ const checkoutStarts = telemetry.ctas.checkoutStarts || 0;
321
+ const acquisitionLeads = billing.signups ? billing.signups.uniqueLeads || 0 : 0;
322
+ const paidOrders = billing.revenue ? billing.revenue.paidOrders || 0 : 0;
323
+ const checkoutStartEntries = telemetryEntries.filter((entry) => {
324
+ const eventType = entry.eventType || entry.event;
325
+ return eventType === 'checkout_start' || eventType === 'checkout_bootstrap';
326
+ });
327
+ const acquisitionEntries = funnelEntries.filter((entry) => entry && entry.stage === 'acquisition');
328
+ const checkoutKeys = new Set(checkoutStartEntries.map(resolveJourneyKey).filter(Boolean));
329
+ const acquisitionKeys = new Set(acquisitionEntries.map(resolveJourneyKey).filter(Boolean));
330
+ const matchedAcquisitionKeys = new Set([...checkoutKeys].filter((key) => acquisitionKeys.has(key)));
331
+ const matchedPaidOrders = paidOrderEntries.filter((entry) => {
332
+ const key = resolveJourneyKey(entry);
333
+ return key && checkoutKeys.has(key);
334
+ }).length;
335
+ const unmatchedCheckoutStarts = checkoutStartEntries.filter((entry) => {
336
+ const key = resolveJourneyKey(entry);
337
+ return !key || !acquisitionKeys.has(key);
338
+ }).length;
339
+ const paidWithoutAcquisition = paidOrderEntries.filter((entry) => {
340
+ const key = resolveJourneyKey(entry);
341
+ return !key || !acquisitionKeys.has(key);
342
+ }).length;
343
+ const stitchedJourneyEntries = [...checkoutStartEntries, ...acquisitionEntries, ...paidOrderEntries];
344
+
345
+ return {
346
+ window: telemetry.window || analyticsWindow,
347
+ telemetry,
348
+ funnel: {
349
+ visitors: uniqueVisitors,
350
+ sessions: telemetry.visitors ? telemetry.visitors.uniqueSessions || 0 : 0,
351
+ pageViews: telemetry.visitors ? telemetry.visitors.pageViews || 0 : 0,
352
+ ctaClicks,
353
+ checkoutStarts,
354
+ acquisitionLeads,
355
+ paidOrders,
356
+ visitorToLeadRate: safeRate(acquisitionLeads, uniqueVisitors),
357
+ visitorToPaidRate: safeRate(paidOrders, uniqueVisitors),
358
+ ctaToLeadRate: safeRate(acquisitionLeads, ctaClicks),
359
+ ctaToPaidRate: safeRate(paidOrders, ctaClicks),
360
+ topTrafficChannel: telemetry.visitors ? telemetry.visitors.topTrafficChannel || null : null,
361
+ checkoutConversionByTrafficChannel: telemetry.ctas ? telemetry.ctas.conversionByTrafficChannel || {} : {},
362
+ },
363
+ buyerLoss: telemetry.buyerLoss || {
364
+ totalSignals: 0,
365
+ reasonsByCode: {},
366
+ cancellationReasons: {},
367
+ abandonmentReasons: {},
368
+ topReason: null,
369
+ },
370
+ pricing: telemetry.pricing || {
371
+ pricingInterestEvents: 0,
372
+ interestByLevel: {},
373
+ },
374
+ seo: telemetry.seo || {
375
+ landingViews: 0,
376
+ bySurface: {},
377
+ byQuery: {},
378
+ topSurface: null,
379
+ topQuery: null,
380
+ },
381
+ efficiency,
382
+ revenue: billing.revenue || {
383
+ paidProviderEvents: 0,
384
+ paidOrders: 0,
385
+ bookedRevenueCents: 0,
386
+ amountKnownOrders: 0,
387
+ amountUnknownOrders: 0,
388
+ amountKnownCoverageRate: 0,
389
+ },
390
+ attribution: billing.attribution || {
391
+ acquisitionBySource: {},
392
+ acquisitionByCampaign: {},
393
+ paidBySource: {},
394
+ paidByCampaign: {},
395
+ bookedRevenueBySourceCents: {},
396
+ bookedRevenueByCampaignCents: {},
397
+ bookedRevenueByCtaId: {},
398
+ bookedRevenueByLandingPath: {},
399
+ bookedRevenueByReferrerHost: {},
400
+ conversionBySource: {},
401
+ conversionByCampaign: {},
402
+ },
403
+ pipeline: billing.pipeline || {
404
+ workflowSprintLeads: { total: 0, bySource: {} },
405
+ qualifiedWorkflowSprintLeads: { total: 0, bySource: {} },
406
+ },
407
+ northStar,
408
+ trafficMetrics: billing.trafficMetrics || {
409
+ visitors: 0,
410
+ sessions: 0,
411
+ pageViews: 0,
412
+ ctaClicks: 0,
413
+ checkoutStarts: 0,
414
+ buyerLossFeedback: 0,
415
+ seoLandingViews: 0,
416
+ },
417
+ operatorGeneratedAcquisition: billing.operatorGeneratedAcquisition || {
418
+ totalEvents: 0,
419
+ uniqueLeads: 0,
420
+ bySource: {},
421
+ },
422
+ dataQuality: billing.dataQuality || {
423
+ telemetryCoverage: 0,
424
+ attributionCoverage: 0,
425
+ amountKnownCoverage: 0,
426
+ unreconciledPaidEvents: 0,
427
+ },
428
+ reconciliation: {
429
+ telemetryCheckoutStarts: checkoutStarts,
430
+ uniqueCheckoutStarters: telemetry.ctas.uniqueCheckoutStarters,
431
+ matchedAcquisitions: matchedAcquisitionKeys.size,
432
+ matchedPaidOrders,
433
+ unmatchedCheckoutStarts,
434
+ paidWithoutAcquisition,
435
+ paidWithoutAmount: paidOrderEntries.filter((entry) => !entry.amountKnown).length,
436
+ },
437
+ identityCoverage: {
438
+ visitorIdCoverage: telemetry.visitors.visitorIdCoverageRate,
439
+ sessionIdCoverage: telemetry.visitors.sessionIdCoverageRate,
440
+ acquisitionIdCoverage: countCoverage(
441
+ stitchedJourneyEntries,
442
+ (entry) => pickFirstText(entry.acquisitionId, entry.metadata && entry.metadata.acquisitionId)
443
+ ),
444
+ amountKnownCoverage: billing.revenue ? billing.revenue.amountKnownCoverageRate || 0 : 0,
445
+ },
446
+ };
447
+ }
448
+
449
+ function computeSecretGuardStats(diagnosticEntries) {
450
+ const secretEntries = diagnosticEntries.filter((entry) => {
451
+ if (entry.source === 'secret_guard') return true;
452
+ const violations = entry.diagnosis && Array.isArray(entry.diagnosis.violations)
453
+ ? entry.diagnosis.violations
454
+ : [];
455
+ return violations.some((violation) => String(violation.constraintId || '').startsWith('security:'));
456
+ });
457
+
458
+ const byConstraint = {};
459
+ for (const entry of secretEntries) {
460
+ const violations = entry.diagnosis && Array.isArray(entry.diagnosis.violations)
461
+ ? entry.diagnosis.violations
462
+ : [];
463
+ for (const violation of violations) {
464
+ const key = String(violation.constraintId || 'security:unknown');
465
+ byConstraint[key] = (byConstraint[key] || 0) + 1;
466
+ }
467
+ }
468
+
469
+ const topConstraint = Object.entries(byConstraint)
470
+ .sort((a, b) => b[1] - a[1])[0] || null;
471
+
472
+ return {
473
+ blocked: secretEntries.length,
474
+ topConstraint: topConstraint ? { key: topConstraint[0], count: topConstraint[1] } : null,
475
+ recent: secretEntries
476
+ .slice(-5)
477
+ .reverse()
478
+ .map((entry) => ({
479
+ step: entry.step || null,
480
+ source: entry.source || null,
481
+ timestamp: entry.timestamp || null,
482
+ })),
483
+ };
484
+ }
485
+
486
+ function computeObservabilityStats(diagnosticEntries, diagnostics, secretGuard, telemetry = null) {
487
+ const bySource = {};
488
+ let latestEventAt = null;
489
+
490
+ for (const entry of diagnosticEntries) {
491
+ const key = String(entry.source || 'unknown');
492
+ bySource[key] = (bySource[key] || 0) + 1;
493
+ if (!latestEventAt || String(entry.timestamp || '') > latestEventAt) {
494
+ latestEventAt = entry.timestamp || null;
495
+ }
496
+ }
497
+
498
+ const topSource = Object.entries(bySource).sort((a, b) => b[1] - a[1])[0] || null;
499
+
500
+ return {
501
+ diagnosticEvents: diagnosticEntries.length,
502
+ bySource,
503
+ topSource: topSource ? { key: topSource[0], count: topSource[1] } : null,
504
+ latestEventAt,
505
+ topRootCause: diagnostics.categories[0] || null,
506
+ secretGuardBlocks: secretGuard.blocked,
507
+ telemetryIngestErrors: diagnosticEntries.filter((entry) => entry.source === 'telemetry_ingest').length,
508
+ checkoutApiFailuresByCode: telemetry && telemetry.ctas ? telemetry.ctas.failuresByCode || {} : {},
509
+ buyerLossSignals: telemetry && telemetry.buyerLoss ? telemetry.buyerLoss.totalSignals || 0 : 0,
510
+ topBuyerLossReason: telemetry && telemetry.buyerLoss ? telemetry.buyerLoss.topReason || null : null,
511
+ seoLandingViews: telemetry && telemetry.seo ? telemetry.seo.landingViews || 0 : 0,
512
+ };
513
+ }
514
+
515
+ function computeInstrumentationReadiness(analytics, billing) {
516
+ const landingPage = fs.existsSync(LANDING_PAGE_PATH)
517
+ ? fs.readFileSync(LANDING_PAGE_PATH, 'utf-8')
518
+ : '';
519
+ const runtimeConfig = resolveHostedBillingConfig();
520
+ const coverage = billing && billing.coverage ? billing.coverage : {};
521
+ const telemetry = analytics.telemetry || {};
522
+ const visitors = telemetry.visitors || {};
523
+ const cli = telemetry.cli || {};
524
+
525
+ return {
526
+ plausibleConfigured: /\/js\/analytics\.js/.test(landingPage),
527
+ ga4Configured: Boolean(runtimeConfig.gaMeasurementId),
528
+ googleSearchConsoleConfigured: Boolean(runtimeConfig.googleSiteVerification),
529
+ softwareApplicationSchemaPresent: /"@type": "SoftwareApplication"/.test(landingPage),
530
+ faqSchemaPresent: /"@type": "FAQPage"/.test(landingPage),
531
+ telemetryEventsPresent: (telemetry.totalEvents || 0) > 0,
532
+ uniqueVisitorsTracked: visitors.uniqueVisitors || 0,
533
+ cliInstallsTracked: cli.uniqueInstalls || 0,
534
+ funnelEventsPresent: (analytics.reconciliation.telemetryCheckoutStarts || 0) > 0,
535
+ seoSignalsPresent: (analytics.seo.landingViews || 0) > 0,
536
+ buyerLossSignalsPresent: (analytics.buyerLoss.totalSignals || 0) > 0,
537
+ trafficAttributionCoverage: visitors.attributionCoverageRate || 0,
538
+ bookedRevenueTrackingEnabled: Boolean(coverage.tracksBookedRevenue),
539
+ paidOrderTrackingEnabled: Boolean(coverage.tracksPaidOrders),
540
+ invoiceTrackingEnabled: Boolean(coverage.tracksInvoices),
541
+ attributionTrackingEnabled: Boolean(coverage.tracksAttribution),
542
+ };
543
+ }
544
+
545
+ function priorityWeight(priority) {
546
+ return ({
547
+ critical: 4,
548
+ high: 3,
549
+ medium: 2,
550
+ low: 1,
551
+ })[String(priority || '').toLowerCase()] || 0;
552
+ }
553
+
554
+ function detectRepeatFailurePressure(entries) {
555
+ const negativeEntries = entries.filter((entry) => entry && entry.signal === 'negative');
556
+ if (!negativeEntries.length) {
557
+ return {
558
+ negativeCount: 0,
559
+ repeatedCount: 0,
560
+ repeatFailureRate: 0,
561
+ topPattern: null,
562
+ };
563
+ }
564
+
565
+ const buckets = new Map();
566
+ for (const entry of negativeEntries) {
567
+ const tags = Array.isArray(entry.tags) ? entry.tags.filter(Boolean).map((tag) => String(tag).toLowerCase()).sort() : [];
568
+ const diagnosis = entry.diagnosis && entry.diagnosis.rootCauseCategory
569
+ ? String(entry.diagnosis.rootCauseCategory).toLowerCase()
570
+ : '';
571
+ const context = pickFirstText(entry.context, entry.whatWentWrong, entry.whatToChange) || '';
572
+ const normalizedContext = context.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim().slice(0, 80);
573
+ const key = diagnosis || (tags.length ? tags.join('|') : normalizedContext || 'uncategorized-negative');
574
+ const bucket = buckets.get(key) || { key, count: 0 };
575
+ bucket.count += 1;
576
+ buckets.set(key, bucket);
577
+ }
578
+
579
+ const repeated = [...buckets.values()].filter((bucket) => bucket.count >= 2);
580
+ const repeatedCount = repeated.reduce((sum, bucket) => sum + bucket.count, 0);
581
+ const topPattern = repeated.sort((a, b) => b.count - a.count)[0] || null;
582
+
583
+ return {
584
+ negativeCount: negativeEntries.length,
585
+ repeatedCount,
586
+ repeatFailureRate: safeRate(repeatedCount, negativeEntries.length),
587
+ topPattern,
588
+ };
589
+ }
590
+
591
+ function aggregateHarnessRecommendations(lessons, limit = 3) {
592
+ const grouped = new Map();
593
+
594
+ for (const lesson of lessons) {
595
+ for (const recommendation of lesson.systemResponse && lesson.systemResponse.harnessRecommendations || []) {
596
+ const key = recommendation.type;
597
+ const current = grouped.get(key) || {
598
+ type: recommendation.type,
599
+ count: 0,
600
+ priority: recommendation.priority,
601
+ priorityScore: 0,
602
+ action: recommendation.action,
603
+ reason: recommendation.reason,
604
+ exampleLessonId: lesson.id,
605
+ exampleLessonTitle: lesson.title,
606
+ };
607
+ current.count += 1;
608
+ const score = priorityWeight(recommendation.priority);
609
+ if (score >= current.priorityScore) {
610
+ current.priority = recommendation.priority;
611
+ current.priorityScore = score;
612
+ current.action = recommendation.action;
613
+ current.reason = recommendation.reason;
614
+ current.exampleLessonId = lesson.id;
615
+ current.exampleLessonTitle = lesson.title;
616
+ }
617
+ grouped.set(key, current);
618
+ }
619
+ }
620
+
621
+ return [...grouped.values()]
622
+ .sort((a, b) => {
623
+ if (b.priorityScore !== a.priorityScore) return b.priorityScore - a.priorityScore;
624
+ if (b.count !== a.count) return b.count - a.count;
625
+ return String(a.type).localeCompare(String(b.type));
626
+ })
627
+ .slice(0, limit)
628
+ .map(({ priorityScore, ...recommendation }) => recommendation);
629
+ }
630
+
631
+ function computeHarnessOverview(feedbackDir, entries) {
632
+ const lessons = searchLessons('', { feedbackDir, limit: 1000 }).results || [];
633
+ const errorLessons = lessons.filter((lesson) => lesson.category === 'error');
634
+ const negativeEntries = entries.filter((entry) => entry && entry.signal === 'negative');
635
+ const diagnosticsCovered = negativeEntries.filter((entry) => entry && entry.diagnosis && entry.diagnosis.rootCauseCategory);
636
+ const repeatPressure = detectRepeatFailurePressure(entries);
637
+ const lifecycleCounts = lessons.reduce((acc, lesson) => {
638
+ const stage = lesson.systemResponse && lesson.systemResponse.lifecycle
639
+ ? lesson.systemResponse.lifecycle.stage
640
+ : 'detected';
641
+ acc[stage] = (acc[stage] || 0) + 1;
642
+ return acc;
643
+ }, {
644
+ detected: 0,
645
+ promoted: 0,
646
+ enforced: 0,
647
+ measured: 0,
648
+ });
649
+
650
+ const correctionCoverage = safeRate(
651
+ errorLessons.filter((lesson) => lesson.systemResponse.lifecycle.correctiveActionCaptured).length,
652
+ errorLessons.length
653
+ );
654
+ const enforcementCoverage = safeRate(
655
+ errorLessons.filter((lesson) => lesson.systemResponse.lifecycle.preventionRuleLinked || lesson.systemResponse.lifecycle.gateLinked).length,
656
+ errorLessons.length
657
+ );
658
+ const diagnosticCoverage = safeRate(diagnosticsCovered.length, negativeEntries.length);
659
+ const repeatResistance = 1 - repeatPressure.repeatFailureRate;
660
+ const score = Math.round(100 * (
661
+ (correctionCoverage * 0.3)
662
+ + (enforcementCoverage * 0.3)
663
+ + (diagnosticCoverage * 0.2)
664
+ + (repeatResistance * 0.2)
665
+ ));
666
+
667
+ let status = 'bootstrapping';
668
+ if (score >= 80) status = 'strong';
669
+ else if (score >= 60) status = 'improving';
670
+ else if (score >= 40) status = 'weak';
671
+
672
+ return {
673
+ score,
674
+ status,
675
+ lessonCount: lessons.length,
676
+ errorLessonCount: errorLessons.length,
677
+ correctionCoverage,
678
+ enforcementCoverage,
679
+ diagnosticCoverage,
680
+ repeatFailureRate: repeatPressure.repeatFailureRate,
681
+ repeatedFailureCount: repeatPressure.repeatedCount,
682
+ topRepeatedPattern: repeatPressure.topPattern,
683
+ lifecycleCounts,
684
+ topRecommendations: aggregateHarnessRecommendations(errorLessons),
685
+ };
686
+ }
687
+
688
+ function resolveTeamWindowHours(analyticsWindow) {
689
+ const window = analyticsWindow && analyticsWindow.window;
690
+ if (window === 'today') return 24;
691
+ if (window === '7d') return 24 * 7;
692
+ if (window === '30d') return 24 * 30;
693
+ return 24;
694
+ }
695
+
696
+ // ---------------------------------------------------------------------------
697
+ // Full dashboard data
698
+ // ---------------------------------------------------------------------------
699
+
700
+ function generateDashboard(feedbackDir, options = {}) {
701
+ const analyticsWindow = resolveAnalyticsWindow(options.analyticsWindow || options);
702
+ const feedbackLogPath = path.join(feedbackDir, 'feedback-log.jsonl');
703
+ const diagnosticLogPath = path.join(feedbackDir, 'diagnostic-log.jsonl');
704
+ const entries = readJSONL(feedbackLogPath);
705
+ const diagnosticEntries = readJSONL(diagnosticLogPath);
706
+ const billingSummary = options.billingSummary || getBillingSummary(analyticsWindow);
707
+
708
+ const approval = computeApprovalStats(entries);
709
+ const gateStats = computeGateStats();
710
+ const prevention = computePreventionImpact(feedbackDir, gateStats);
711
+ const trend = computeSessionTrend(entries, 10);
712
+ const health = computeSystemHealth(feedbackDir, gateStats);
713
+ const diagnostics = aggregateFailureDiagnostics([...entries, ...diagnosticEntries]);
714
+ const secretGuard = computeSecretGuardStats(diagnosticEntries);
715
+ const gates = listActiveGates();
716
+ const analytics = computeAnalyticsSummary(feedbackDir, {
717
+ analyticsWindow,
718
+ billingSummary,
719
+ });
720
+ const observability = computeObservabilityStats(diagnosticEntries, diagnostics, secretGuard, analytics.telemetry);
721
+ const instrumentation = computeInstrumentationReadiness(analytics, billingSummary);
722
+ const delegation = summarizeDelegation(feedbackDir);
723
+ const readiness = generateAgentReadinessReport({ projectRoot: PROJECT_ROOT });
724
+ const harness = computeHarnessOverview(feedbackDir, entries);
725
+ const settingsStatus = getSettingsStatus({ projectRoot: PROJECT_ROOT });
726
+ settingsStatus.routingPreview = {
727
+ dashboardTool: routeProfile({
728
+ toolName: 'dashboard',
729
+ settingsOptions: { projectRoot: PROJECT_ROOT },
730
+ }),
731
+ defaultSession: routeProfile({
732
+ settingsOptions: { projectRoot: PROJECT_ROOT },
733
+ }),
734
+ reviewSession: routeProfile({
735
+ sessionType: 'review',
736
+ settingsOptions: { projectRoot: PROJECT_ROOT },
737
+ }),
738
+ };
739
+
740
+ // Live metrics — gate hit rate, lesson effectiveness, error trend
741
+ const now = Date.now();
742
+ const day = 24 * 60 * 60 * 1000;
743
+ const weekAgo = now - 7 * day;
744
+ const recentEntries = entries.filter((e) => e.timestamp && new Date(e.timestamp).getTime() > weekAgo);
745
+ const negRecent = recentEntries.filter((e) => ['down', 'negative', 'thumbs_down'].includes(String(e.signal || e.feedback || '').toLowerCase()));
746
+ const posRecent = recentEntries.filter((e) => ['up', 'positive', 'thumbs_up'].includes(String(e.signal || e.feedback || '').toLowerCase()));
747
+ const timestamps = entries.filter((e) => e.timestamp).map((e) => new Date(e.timestamp).getTime());
748
+ const daysActive = timestamps.length > 0 ? Math.max(1, Math.ceil((now - Math.min(...timestamps)) / day)) : 1;
749
+ const totalNeg = entries.filter((e) => ['down', 'negative', 'thumbs_down'].includes(String(e.signal || e.feedback || '').toLowerCase())).length;
750
+ const autoGates = gateStats.autoCount || 0;
751
+ const twoWeeksAgo = now - 14 * day;
752
+ const lastWeekNeg = entries.filter((e) => e.timestamp && new Date(e.timestamp).getTime() > twoWeeksAgo && new Date(e.timestamp).getTime() <= weekAgo && ['down', 'negative', 'thumbs_down'].includes(String(e.signal || e.feedback || '').toLowerCase())).length;
753
+ const liveMetrics = {
754
+ gateHitRate: { blockedPerDay: Math.round(((gateStats.blocked || 0) / daysActive) * 100) / 100, warnedPerDay: Math.round(((gateStats.warned || 0) / daysActive) * 100) / 100, daysActive },
755
+ lessonEffectiveness: { rate: totalNeg > 0 ? Math.round((autoGates / totalNeg) * 10000) / 100 : 0, totalNegative: totalNeg, autoGatesCreated: autoGates },
756
+ errorTrend: { direction: lastWeekNeg > 0 ? (negRecent.length < lastWeekNeg ? 'improving' : negRecent.length > lastWeekNeg ? 'worsening' : 'stable') : (negRecent.length > 0 ? 'new-errors' : 'clean'), thisWeek: negRecent.length, lastWeek: lastWeekNeg },
757
+ weeklyActivity: { positive: posRecent.length, negative: negRecent.length, total: recentEntries.length },
758
+ };
759
+
760
+ const team = generateOrgDashboard({
761
+ windowHours: resolveTeamWindowHours(analyticsWindow),
762
+ authContext: options.authContext,
763
+ proOverride: options.teamProOverride,
764
+ });
765
+ const templateLibrary = summarizeGateTemplates();
766
+ const predictive = buildPredictiveInsights({
767
+ telemetryAnalytics: analytics.telemetry,
768
+ billingSummary,
769
+ gateStats,
770
+ team,
771
+ });
772
+
773
+ return {
774
+ operational: {
775
+ source: options.billingSource || 'local',
776
+ fallbackReason: options.billingFallbackReason || null,
777
+ window: analytics.window || analyticsWindow,
778
+ },
779
+ approval,
780
+ gateStats,
781
+ gates,
782
+ prevention,
783
+ trend,
784
+ health,
785
+ diagnostics,
786
+ delegation,
787
+ secretGuard,
788
+ analytics,
789
+ harness,
790
+ observability,
791
+ instrumentation,
792
+ readiness,
793
+ settingsStatus,
794
+ team,
795
+ templateLibrary,
796
+ liveMetrics,
797
+ predictive,
798
+ };
799
+ }
800
+
801
+ // ---------------------------------------------------------------------------
802
+ // Rich CLI output
803
+ // ---------------------------------------------------------------------------
804
+
805
+ function printDashboard(data) {
806
+ const {
807
+ approval,
808
+ gateStats,
809
+ prevention,
810
+ trend,
811
+ health,
812
+ diagnostics,
813
+ delegation,
814
+ secretGuard,
815
+ analytics,
816
+ harness,
817
+ observability,
818
+ instrumentation,
819
+ readiness,
820
+ settingsStatus,
821
+ team,
822
+ templateLibrary,
823
+ predictive,
824
+ } = data;
825
+
826
+ const trendArrow = approval.trendDirection === 'improving' ? '\u2191'
827
+ : approval.trendDirection === 'declining' ? '\u2193'
828
+ : '\u2192';
829
+
830
+ console.log('');
831
+ console.log('\uD83D\uDCCA ThumbGate Dashboard');
832
+ console.log('\u2550'.repeat(46));
833
+ console.log(` Approval Rate : ${approval.approvalRate}% \u2192 ${approval.recentRate}% (7-day trend ${trendArrow})`);
834
+ console.log(` Total Signals : ${approval.total} (${approval.positive} positive, ${approval.negative} negative)`);
835
+
836
+ console.log('');
837
+ console.log('\uD83D\uDEE1\uFE0F Gate Enforcement');
838
+ console.log(` Active Gates : ${gateStats.totalGates} (${gateStats.manualCount} manual, ${gateStats.autoCount} auto-promoted)`);
839
+ console.log(` Actions Blocked : ${gateStats.blocked}`);
840
+ console.log(` Actions Warned : ${gateStats.warned}`);
841
+ if (gateStats.topBlocked) {
842
+ console.log(` Top Blocked : ${gateStats.topBlocked} (${gateStats.topBlockedCount}\u00D7)`);
843
+ }
844
+
845
+ console.log('');
846
+ console.log('\u26A1 Prevention Impact');
847
+ console.log(` Estimated Saves : ${prevention.estimatedHoursSaved} hours`);
848
+ console.log(` Rules Active : ${prevention.ruleCount} prevention rules`);
849
+ if (prevention.lastPromotion) {
850
+ console.log(` Last Promotion : ${prevention.lastPromotion.id} (${prevention.lastPromotion.daysAgo} days ago)`);
851
+ }
852
+
853
+ console.log('');
854
+ console.log('🧰 Harness');
855
+ console.log(` Score : ${harness.score}/100 (${harness.status})`);
856
+ console.log(` Correction Cov. : ${Math.round((harness.correctionCoverage || 0) * 100)}%`);
857
+ console.log(` Enforcement Cov. : ${Math.round((harness.enforcementCoverage || 0) * 100)}%`);
858
+ console.log(` Diagnostic Cov. : ${Math.round((harness.diagnosticCoverage || 0) * 100)}%`);
859
+ console.log(` Repeat Pressure : ${Math.round((harness.repeatFailureRate || 0) * 100)}%`);
860
+ console.log(` Error Lessons : ${harness.errorLessonCount}/${harness.lessonCount}`);
861
+ if (harness.topRecommendations[0]) {
862
+ console.log(` Top Next Fix : ${harness.topRecommendations[0].type} (${harness.topRecommendations[0].count} lessons)`);
863
+ }
864
+
865
+ console.log('');
866
+ console.log('🎯 North Star');
867
+ console.log(` Weekly Proof Runs: ${analytics.northStar.weeklyActiveProofBackedWorkflowRuns}`);
868
+ console.log(` Weekly Teams : ${analytics.northStar.weeklyTeamsRunningProofBackedWorkflows}`);
869
+ console.log(` Reviewed Runs : ${analytics.northStar.reviewedRuns}`);
870
+ console.log(` Paid Team Runs : ${analytics.northStar.paidTeamRuns}`);
871
+ console.log(` Named Pilots : ${analytics.northStar.namedPilotAgreements}`);
872
+ console.log(` Status : ${analytics.northStar.northStarReached ? 'tracking' : 'not_started'}`);
873
+ console.log(` Customer Proof : ${analytics.northStar.customerProofReached ? 'present' : 'missing'}`);
874
+ if (analytics.northStar.latestRun) {
875
+ console.log(` Latest Run : ${analytics.northStar.latestRun.workflowId} @ ${analytics.northStar.latestRun.timestamp}`);
876
+ }
877
+
878
+ console.log('');
879
+ console.log('⚙️ Efficiency');
880
+ console.log(` Context Packs : ${analytics.efficiency.contextPackRequests}`);
881
+ console.log(` Cache Hits : ${analytics.efficiency.semanticCacheHits}`);
882
+ console.log(` Hit Rate : ${analytics.efficiency.semanticCacheHitRate}`);
883
+ console.log(` Avg Similarity : ${analytics.efficiency.averageSemanticSimilarity}`);
884
+ console.log(` Tokens Reused : ${analytics.efficiency.estimatedContextTokensReused} (heuristic)`);
885
+
886
+ console.log('');
887
+ console.log('\uD83E\uDD1D Delegation');
888
+ console.log(` Attempts : ${delegation.attemptCount}`);
889
+ console.log(` Outcomes : ${delegation.acceptedCount} accepted / ${delegation.rejectedCount} rejected / ${delegation.abortedCount} aborted`);
890
+ console.log(` Verification Fail: ${Math.round((delegation.verificationFailureRate || 0) * 100)}%`);
891
+ console.log(` Avoided Starts : ${delegation.avoidedDelegationCount}`);
892
+
893
+ console.log('');
894
+ console.log('\uD83D\uDCBC Growth Analytics');
895
+ console.log(` Unique Visitors : ${analytics.trafficMetrics.visitors}`);
896
+ console.log(` Sessions : ${analytics.trafficMetrics.sessions}`);
897
+ console.log(` Page Views : ${analytics.trafficMetrics.pageViews}`);
898
+ console.log(` CTA Clicks : ${analytics.trafficMetrics.ctaClicks}`);
899
+ console.log(` Leads : ${analytics.funnel.acquisitionLeads}`);
900
+ console.log(` Sprint Leads : ${analytics.pipeline.workflowSprintLeads.total}`);
901
+ console.log(` Qualified Leads : ${analytics.pipeline.qualifiedWorkflowSprintLeads.total}`);
902
+ console.log(` Paid Provider Ev.: ${analytics.revenue.paidProviderEvents}`);
903
+ console.log(` Paid Orders : ${analytics.funnel.paidOrders}`);
904
+ console.log(` Visitor \u2192 Paid : ${analytics.funnel.visitorToPaidRate}`);
905
+ console.log(` Booked Revenue : $${(analytics.revenue.bookedRevenueCents / 100).toFixed(2)}`);
906
+ console.log(` Matched Journeys : ${analytics.reconciliation.matchedPaidOrders}/${analytics.reconciliation.telemetryCheckoutStarts}`);
907
+ console.log(` Buyer Loss : ${analytics.buyerLoss.totalSignals}`);
908
+ if (analytics.telemetry.visitors.topSource) {
909
+ console.log(` Top Source : ${analytics.telemetry.visitors.topSource.key} (${analytics.telemetry.visitors.topSource.count}\u00D7)`);
910
+ }
911
+ if (analytics.funnel.topTrafficChannel) {
912
+ console.log(` Traffic Channel : ${analytics.funnel.topTrafficChannel.key} (${analytics.funnel.topTrafficChannel.count}\u00D7)`);
913
+ }
914
+ if (analytics.buyerLoss.topReason) {
915
+ console.log(` Top Loss Reason : ${analytics.buyerLoss.topReason.key} (${analytics.buyerLoss.topReason.count}\u00D7)`);
916
+ }
917
+ if (analytics.seo.topSurface) {
918
+ console.log(` SEO Surface : ${analytics.seo.topSurface.key} (${analytics.seo.topSurface.count}\u00D7)`);
919
+ }
920
+
921
+ console.log('');
922
+ console.log('\uD83D\uDCE1 Tracking Readiness');
923
+ console.log(` Plausible : ${instrumentation.plausibleConfigured ? 'configured' : 'missing'}`);
924
+ console.log(` GA4 : ${instrumentation.ga4Configured ? 'configured' : 'missing'}`);
925
+ console.log(` Search Console : ${instrumentation.googleSearchConsoleConfigured ? 'configured' : 'missing'}`);
926
+ console.log(` Telemetry Events : ${instrumentation.telemetryEventsPresent ? instrumentation.uniqueVisitorsTracked : 0} visitors`);
927
+ console.log(` SEO Signals : ${instrumentation.seoSignalsPresent ? analytics.seo.landingViews : 0}`);
928
+ console.log(` Buyer Loss : ${instrumentation.buyerLossSignalsPresent ? analytics.buyerLoss.totalSignals : 0}`);
929
+ console.log(` Attribution : ${Math.round((instrumentation.trafficAttributionCoverage || 0) * 100)}% page-view coverage`);
930
+ console.log(` Revenue Tracking : ${instrumentation.bookedRevenueTrackingEnabled ? 'booked revenue enabled' : 'disabled'}`);
931
+ console.log(` Amount Coverage : ${Math.round((analytics.dataQuality.amountKnownCoverage || 0) * 100)}%`);
932
+ console.log(` Unreconciled Paid: ${analytics.dataQuality.unreconciledPaidEvents}`);
933
+
934
+ console.log('');
935
+ console.log('⚙️ Policy Origins');
936
+ console.log(` Active Layers : ${settingsStatus.activeLayers.filter((layer) => layer.exists).map((layer) => layer.scope).join(' -> ')}`);
937
+ console.log(` Default Profile : ${settingsStatus.resolvedSettings.mcp.defaultProfile}`);
938
+ console.log(` Review Profile : ${settingsStatus.resolvedSettings.mcp.readonlySessionProfile}`);
939
+ console.log(` Harness Runtime : ${settingsStatus.resolvedSettings.harnesses.allowRuntimeExecution ? 'enabled' : 'disabled'}`);
940
+ if (settingsStatus.routingPreview && settingsStatus.routingPreview.dashboardTool) {
941
+ console.log(` Dashboard Route : ${settingsStatus.routingPreview.dashboardTool.profile} (${settingsStatus.routingPreview.dashboardTool.reason})`);
942
+ }
943
+
944
+ console.log('');
945
+ console.log('👥 Team');
946
+ console.log(` Active Agents : ${team.activeAgents}/${team.totalAgents}`);
947
+ console.log(` Org Adherence : ${team.orgAdherenceRate}%`);
948
+ console.log(` Top Blocked Gates: ${team.topBlockedGates.length}`);
949
+ console.log(` Risk Agents : ${team.riskAgents.length}`);
950
+ console.log(` Proof-Backed Teams: ${analytics.northStar.weeklyTeamsRunningProofBackedWorkflows}`);
951
+ if (team.upgradeMessage) {
952
+ console.log(` Upgrade Path : ${team.upgradeMessage}`);
953
+ }
954
+
955
+ console.log('');
956
+ console.log('🧱 Gate Templates');
957
+ console.log(` Total Templates : ${templateLibrary.total}`);
958
+ console.log(` Categories : ${Object.keys(templateLibrary.categories || {}).length}`);
959
+ const topTemplateCategory = Object.entries(templateLibrary.categories || {})
960
+ .sort((a, b) => b[1] - a[1])[0];
961
+ if (topTemplateCategory) {
962
+ console.log(` Top Category : ${topTemplateCategory[0]} (${topTemplateCategory[1]} templates)`);
963
+ }
964
+
965
+ console.log('');
966
+ console.log('🔮 Predictive Insights');
967
+ console.log(` Pro Propensity : ${predictive.upgradePropensity.pro.band} (${predictive.upgradePropensity.pro.score})`);
968
+ console.log(` Team Propensity : ${predictive.upgradePropensity.team.band} (${predictive.upgradePropensity.team.score})`);
969
+ console.log(` Revenue Forecast : $${(predictive.revenueForecast.predictedBookedRevenueCents / 100).toFixed(2)}`);
970
+ console.log(` Opportunity Gap : $${(predictive.revenueForecast.incrementalOpportunityCents / 100).toFixed(2)}`);
971
+ console.log(` Predictive Alerts: ${predictive.anomalySummary.count} (${predictive.anomalySummary.severity})`);
972
+ if (predictive.topCreators[0]) {
973
+ console.log(` Top Creator : ${predictive.topCreators[0].key} (+$${(predictive.topCreators[0].opportunityRevenueCents / 100).toFixed(2)})`);
974
+ }
975
+ if (predictive.topSources[0]) {
976
+ console.log(` Top Channel : ${predictive.topSources[0].key} (+$${(predictive.topSources[0].opportunityRevenueCents / 100).toFixed(2)})`);
977
+ }
978
+
979
+ console.log('');
980
+ console.log('🧭 Agent Readiness');
981
+ console.log(` Overall : ${readiness.overallStatus}`);
982
+ console.log(` Runtime : ${readiness.runtime.mode}`);
983
+ console.log(` Bootstrap : ${readiness.bootstrap.requiredPresent}/${readiness.bootstrap.requiredCount} required files`);
984
+ console.log(` MCP Tier : ${readiness.permissions.profile} (${readiness.permissions.tier})`);
985
+ if (readiness.warnings[0]) {
986
+ console.log(` Top Warning : ${readiness.warnings[0]}`);
987
+ }
988
+
989
+ console.log('');
990
+ console.log('\uD83D\uDD10 Secret Guard');
991
+ console.log(` Blocks Recorded : ${secretGuard.blocked}`);
992
+ if (secretGuard.topConstraint) {
993
+ console.log(` Top Constraint : ${secretGuard.topConstraint.key} (${secretGuard.topConstraint.count}\u00D7)`);
994
+ }
995
+
996
+ console.log('');
997
+ console.log('\uD83D\uDCC8 Trend (last 10 sessions)');
998
+ const trendLabel = approval.trendDirection === 'improving' ? 'improving'
999
+ : approval.trendDirection === 'declining' ? 'declining'
1000
+ : 'stable';
1001
+ console.log(` ${trend.bars} ${trend.percentage}% \u2192 ${trendLabel}`);
1002
+
1003
+ console.log('');
1004
+ console.log('\uD83D\uDD27 System Health');
1005
+ console.log(` Feedback Log : ${health.feedbackCount} entries`);
1006
+ console.log(` Memory Store : ${health.memoryCount} memories`);
1007
+ console.log(` Gate Config : ${health.gateConfigLoaded ? 'loaded' : 'not found'} (${health.gateCount} gates)`);
1008
+ console.log(` MCP Server : running`);
1009
+ if (diagnostics.totalDiagnosed > 0) {
1010
+ console.log(` Failure Diagnoses: ${diagnostics.totalDiagnosed}`);
1011
+ if (diagnostics.categories[0]) {
1012
+ console.log(` Top Root Cause : ${diagnostics.categories[0].key} (${diagnostics.categories[0].count}\u00D7)`);
1013
+ }
1014
+ }
1015
+
1016
+ console.log('');
1017
+ console.log('\uD83D\uDCE1 Observability');
1018
+ console.log(` Diagnostic Events: ${observability.diagnosticEvents}`);
1019
+ console.log(` Secret Blocks : ${observability.secretGuardBlocks}`);
1020
+ console.log(` Telemetry Errors : ${observability.telemetryIngestErrors}`);
1021
+ console.log(` Buyer Loss : ${observability.buyerLossSignals}`);
1022
+ console.log(` SEO Views : ${observability.seoLandingViews}`);
1023
+ if (observability.topSource) {
1024
+ console.log(` Top Source : ${observability.topSource.key} (${observability.topSource.count}\u00D7)`);
1025
+ }
1026
+ if (observability.topBuyerLossReason) {
1027
+ console.log(` Top Loss Reason : ${observability.topBuyerLossReason.key} (${observability.topBuyerLossReason.count}\u00D7)`);
1028
+ }
1029
+ console.log('');
1030
+ }
1031
+
1032
+ // ---------------------------------------------------------------------------
1033
+ // Exports + CLI
1034
+ // ---------------------------------------------------------------------------
1035
+
1036
+ module.exports = {
1037
+ generateDashboard,
1038
+ printDashboard,
1039
+ computeApprovalStats,
1040
+ computeGateStats,
1041
+ computePreventionImpact,
1042
+ computeSessionTrend,
1043
+ computeSystemHealth,
1044
+ computeEfficiencyMetrics,
1045
+ computeHarnessOverview,
1046
+ computeAnalyticsSummary,
1047
+ computeSecretGuardStats,
1048
+ computeObservabilityStats,
1049
+ readJSONL,
1050
+ readJsonFile,
1051
+ };
1052
+
1053
+ if (require.main === module) {
1054
+ const { getFeedbackPaths } = require('./feedback-loop');
1055
+ const { FEEDBACK_DIR } = getFeedbackPaths();
1056
+ const data = generateDashboard(FEEDBACK_DIR);
1057
+ printDashboard(data);
1058
+ }