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,286 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Feedback Sessions
6
+ *
7
+ * When a user gives thumbs up/down, a session opens. Follow-up messages
8
+ * within a time window enrich the session. When the window closes,
9
+ * the full context is used to infer the lesson.
10
+ *
11
+ * Flow:
12
+ * 1. User gives 👎 → openSession() → returns sessionId
13
+ * 2. User types "you lied about X" → appendToSession(sessionId, message)
14
+ * 3. User types "you forgot Y" → appendToSession(sessionId, message)
15
+ * 4. 60s passes OR next assistant response → finalizeSession(sessionId)
16
+ * 5. Lesson is re-inferred with ALL follow-up context
17
+ */
18
+
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+ const { buildStableId } = require('./conversation-context');
22
+
23
+ const SESSION_TIMEOUT_MS = 60000; // 60 seconds
24
+ const MAX_FOLLOWUP_MESSAGES = 20;
25
+
26
+ // In-memory store for active sessions (keyed by sessionId)
27
+ const activeSessions = new Map();
28
+
29
+ function scheduleTimer(callback, timeoutMs) {
30
+ const handle = setTimeout(callback, timeoutMs);
31
+ if (typeof handle.unref === 'function') {
32
+ handle.unref();
33
+ }
34
+ return handle;
35
+ }
36
+
37
+ function resetSessionTimer(sessionId, session) {
38
+ if (session.timeoutHandle) {
39
+ clearTimeout(session.timeoutHandle);
40
+ }
41
+ session.timeoutHandle = scheduleTimer(() => {
42
+ if (session.status === 'open') {
43
+ finalizeSession(sessionId);
44
+ }
45
+ }, SESSION_TIMEOUT_MS);
46
+ }
47
+
48
+ /**
49
+ * Open a new feedback session after a thumbs up/down signal.
50
+ * The session stays open for follow-up messages.
51
+ */
52
+ function openSession(feedbackEventId, signal, initialContext) {
53
+ const sessionId = buildStableId('fbs');
54
+
55
+ const session = {
56
+ sessionId,
57
+ feedbackEventId,
58
+ signal,
59
+ initialContext: initialContext || '',
60
+ followUpMessages: [],
61
+ openedAt: new Date().toISOString(),
62
+ status: 'open',
63
+ timeoutHandle: null,
64
+ finalizedAt: null,
65
+ };
66
+
67
+ resetSessionTimer(sessionId, session);
68
+
69
+ activeSessions.set(sessionId, session);
70
+
71
+ return {
72
+ sessionId,
73
+ status: 'open',
74
+ message: `Feedback session opened. Follow-up messages will be captured for ${SESSION_TIMEOUT_MS / 1000}s.`,
75
+ expiresAt: new Date(Date.now() + SESSION_TIMEOUT_MS).toISOString(),
76
+ };
77
+ }
78
+
79
+ /**
80
+ * Append a follow-up message to an open session.
81
+ * Called when user types additional context after thumbs up/down.
82
+ */
83
+ function appendToSession(sessionId, message, role = 'user') {
84
+ const session = activeSessions.get(sessionId);
85
+ if (!session) {
86
+ return { status: 'not_found', message: `No active session: ${sessionId}` };
87
+ }
88
+ if (session.status !== 'open') {
89
+ return { status: 'closed', message: `Session already finalized at ${session.finalizedAt}` };
90
+ }
91
+ if (session.followUpMessages.length >= MAX_FOLLOWUP_MESSAGES) {
92
+ return { status: 'full', message: `Session has reached max ${MAX_FOLLOWUP_MESSAGES} messages` };
93
+ }
94
+
95
+ session.followUpMessages.push({
96
+ role,
97
+ content: (message || '').slice(0, 1000), // Cap per-message
98
+ timestamp: new Date().toISOString(),
99
+ });
100
+
101
+ resetSessionTimer(sessionId, session);
102
+
103
+ return {
104
+ status: 'appended',
105
+ messageCount: session.followUpMessages.length,
106
+ sessionId,
107
+ };
108
+ }
109
+
110
+ /**
111
+ * Finalize a session — collect all follow-up messages and re-infer the lesson.
112
+ * Called automatically after timeout, or manually when assistant responds.
113
+ */
114
+ function finalizeSession(sessionId) {
115
+ const session = activeSessions.get(sessionId);
116
+ if (!session) {
117
+ return { status: 'not_found' };
118
+ }
119
+ if (session.status !== 'open') {
120
+ return { status: 'already_finalized', finalizedAt: session.finalizedAt };
121
+ }
122
+
123
+ // Clear timeout
124
+ if (session.timeoutHandle) {
125
+ clearTimeout(session.timeoutHandle);
126
+ session.timeoutHandle = null;
127
+ }
128
+
129
+ session.status = 'finalized';
130
+ session.finalizedAt = new Date().toISOString();
131
+
132
+ // Build the enriched context from follow-up messages
133
+ const followUpText = session.followUpMessages
134
+ .filter(m => m.role === 'user')
135
+ .map(m => m.content)
136
+ .join('\n');
137
+
138
+ const enrichedContext = session.initialContext
139
+ ? `${session.initialContext}\n\nFollow-up:\n${followUpText}`
140
+ : followUpText;
141
+
142
+ // Extract specific complaints/corrections from follow-ups
143
+ const complaints = extractComplaints(session.followUpMessages);
144
+
145
+ const result = {
146
+ status: 'finalized',
147
+ sessionId,
148
+ feedbackEventId: session.feedbackEventId,
149
+ signal: session.signal,
150
+ enrichedContext,
151
+ followUpMessages: session.followUpMessages,
152
+ followUpCount: session.followUpMessages.length,
153
+ complaints,
154
+ duration: new Date(session.finalizedAt) - new Date(session.openedAt),
155
+ openedAt: session.openedAt,
156
+ finalizedAt: session.finalizedAt,
157
+ };
158
+
159
+ // Persist to disk for durability
160
+ try {
161
+ persistSession(result);
162
+ } catch (_err) { /* non-critical */ }
163
+
164
+ // Clean up from active sessions after a delay (allow reads)
165
+ scheduleTimer(() => activeSessions.delete(sessionId), 5000);
166
+
167
+ return result;
168
+ }
169
+
170
+ /**
171
+ * Extract specific complaints/corrections from follow-up messages.
172
+ * These are the actual lesson content.
173
+ */
174
+ function extractComplaints(messages) {
175
+ const complaints = [];
176
+
177
+ for (const msg of messages) {
178
+ if (msg.role !== 'user') continue;
179
+ const content = msg.content || '';
180
+
181
+ const matches = extractComplaintMatches(content);
182
+ complaints.push(...matches.map((match) => ({
183
+ type: match.type,
184
+ detail: match.detail,
185
+ source: content.slice(0, 100),
186
+ timestamp: msg.timestamp,
187
+ })));
188
+
189
+ if (matches.length === 0 && isGeneralFrustration(content)) {
190
+ complaints.push({
191
+ type: 'general-frustration',
192
+ detail: content.slice(0, 200),
193
+ source: content.slice(0, 100),
194
+ timestamp: msg.timestamp,
195
+ });
196
+ }
197
+ }
198
+
199
+ return complaints;
200
+ }
201
+
202
+ function extractComplaintMatches(content) {
203
+ const normalized = String(content || '').trim();
204
+ const lower = normalized.toLowerCase();
205
+ if (!lower) return [];
206
+
207
+ const phraseSets = [
208
+ { type: 'dishonesty', phrases: ['you lied about ', 'you lying about ', 'you lied to me about '] },
209
+ { type: 'omission', phrases: ['you didn\'t ', 'you did not ', 'you forgot to ', 'you failed to ', 'you never '] },
210
+ { type: 'damage', phrases: ['you broke ', 'you ruined ', 'you messed up ', 'you screwed up '] },
211
+ { type: 'quality', phrases: ['wrong ', 'incorrect ', 'bad ', 'terrible ', 'stupid '] },
212
+ { type: 'ignored-instruction', phrases: ['i said ', 'i told you ', 'i asked you to '] },
213
+ { type: 'constraint', phrases: ['don\'t ', 'do not ', 'never ', 'stop '] },
214
+ { type: 'missed-expectation', phrases: ['should have ', 'should\'ve ', 'why didn\'t you '] },
215
+ { type: 'performance', phrases: ['too slow', 'took too long', 'waste of time', '5 minutes'] },
216
+ ];
217
+
218
+ const matches = [];
219
+ for (const entry of phraseSets) {
220
+ for (const phrase of entry.phrases) {
221
+ const index = lower.indexOf(phrase);
222
+ if (index === -1) continue;
223
+ const start = index + phrase.length;
224
+ const detail = normalized.slice(start).trim().slice(0, 200);
225
+ matches.push({
226
+ type: entry.type,
227
+ detail: detail || phrase.trim(),
228
+ });
229
+ break;
230
+ }
231
+ }
232
+ return matches;
233
+ }
234
+
235
+ function isGeneralFrustration(content) {
236
+ const lower = String(content || '').toLowerCase();
237
+ return lower.includes('!!')
238
+ || lower.includes('fuck')
239
+ || lower.includes('shit')
240
+ || lower.includes('stupid')
241
+ || lower.includes('terrible')
242
+ || lower.includes('wrong')
243
+ || lower.includes('bad')
244
+ || lower.includes('lied');
245
+ }
246
+
247
+ /**
248
+ * Get an active session by ID
249
+ */
250
+ function getSession(sessionId) {
251
+ return activeSessions.get(sessionId) || null;
252
+ }
253
+
254
+ /**
255
+ * Get the most recent open session (if any)
256
+ */
257
+ function getActiveSession() {
258
+ for (const [, session] of activeSessions) {
259
+ if (session.status === 'open') return session;
260
+ }
261
+ return null;
262
+ }
263
+
264
+ /**
265
+ * Persist finalized session to disk
266
+ */
267
+ function persistSession(result) {
268
+ const { getFeedbackPaths, appendJSONL } = require('./feedback-loop');
269
+ const paths = getFeedbackPaths();
270
+ const sessionLogPath = path.join(path.dirname(paths.FEEDBACK_LOG_PATH), 'feedback-sessions.jsonl');
271
+ appendJSONL(sessionLogPath, result);
272
+ }
273
+
274
+ module.exports = {
275
+ openSession,
276
+ appendToSession,
277
+ finalizeSession,
278
+ getSession,
279
+ getActiveSession,
280
+ extractComplaints,
281
+ SESSION_TIMEOUT_MS,
282
+ MAX_FOLLOWUP_MESSAGES,
283
+ scheduleTimer,
284
+ // For testing
285
+ _activeSessions: activeSessions,
286
+ };
@@ -0,0 +1,185 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Feedback → Memory Bridge
4
+ *
5
+ * Converts raw feedback params into a schema-validated memory object
6
+ * suitable for mcp__memory__remember. This is the validation boundary
7
+ * between external feedback signals and MCP memory storage.
8
+ *
9
+ * Usage:
10
+ * echo '{"signal":"negative","context":"...","whatWentWrong":"...","tags":["testing"]}' | node scripts/feedback-to-memory.js
11
+ * node scripts/feedback-to-memory.js --test
12
+ *
13
+ * Input (stdin JSON):
14
+ * signal: "positive" | "negative"
15
+ * context: string — what the agent was doing
16
+ * whatWentWrong: string — for negative: what failed
17
+ * whatToChange: string — for negative: how to avoid
18
+ * whatWorked: string — for positive: the pattern to repeat
19
+ * tags: string[] — optional domain tags; a fallback domain tag is inferred when omitted
20
+ *
21
+ * Output (stdout JSON):
22
+ * { ok: true, memory: { title, content, category, importance, tags } }
23
+ * { ok: false, reason: string, issues?: string[] }
24
+ */
25
+ 'use strict';
26
+
27
+ const { resolveFeedbackAction, prepareForStorage } = require('./feedback-schema');
28
+ const { buildClarificationMessage } = require('./feedback-quality');
29
+
30
+ function convertFeedbackToMemory(params) {
31
+ const action = resolveFeedbackAction({
32
+ signal: params.signal,
33
+ context: params.context || '',
34
+ whatWentWrong: params.whatWentWrong,
35
+ whatToChange: params.whatToChange,
36
+ whatWorked: params.whatWorked,
37
+ tags: params.tags || [],
38
+ });
39
+
40
+ if (!action || action.type === 'no-action') {
41
+ const clarification = buildClarificationMessage({
42
+ signal: params.signal,
43
+ context: params.context || '',
44
+ whatWentWrong: params.whatWentWrong,
45
+ whatToChange: params.whatToChange,
46
+ whatWorked: params.whatWorked,
47
+ });
48
+ return {
49
+ ok: false,
50
+ reason: action ? action.reason : 'Unknown action resolution failure',
51
+ ...(clarification || {}),
52
+ };
53
+ }
54
+
55
+ const prep = prepareForStorage(action.memory);
56
+ if (!prep.ok) {
57
+ return { ok: false, reason: `Schema validation failed: ${prep.issues.join('; ')}`, issues: prep.issues };
58
+ }
59
+
60
+ return { ok: true, actionType: action.type, memory: prep.memory };
61
+ }
62
+
63
+ // ---------------------------------------------------------------------------
64
+ // CLI: stdin mode
65
+ // ---------------------------------------------------------------------------
66
+
67
+ function runStdin() {
68
+ let input = '';
69
+ process.stdin.setEncoding('utf-8');
70
+ process.stdin.on('data', (chunk) => { input += chunk; });
71
+ process.stdin.on('end', () => {
72
+ try {
73
+ const params = JSON.parse(input.trim());
74
+ const result = convertFeedbackToMemory(params);
75
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
76
+ process.exit(result.ok ? 0 : 2);
77
+ } catch (err) {
78
+ process.stdout.write(JSON.stringify({ ok: false, reason: `Parse error: ${err.message}` }, null, 2) + '\n');
79
+ process.exit(1);
80
+ }
81
+ });
82
+ }
83
+
84
+ // ---------------------------------------------------------------------------
85
+ // Built-in Tests
86
+ // ---------------------------------------------------------------------------
87
+
88
+ function runTests() {
89
+ let passed = 0;
90
+ let failed = 0;
91
+
92
+ function assert(condition, name) {
93
+ if (condition) {
94
+ passed++;
95
+ console.log(` ✅ ${name}`);
96
+ } else {
97
+ failed++;
98
+ console.log(` ❌ ${name}`);
99
+ }
100
+ }
101
+
102
+ console.log('\n🧪 feedback-to-memory.js — Tests\n');
103
+
104
+ // Valid negative feedback → memory
105
+ const neg = convertFeedbackToMemory({
106
+ signal: 'negative',
107
+ context: 'Agent claimed fix without test evidence',
108
+ whatWentWrong: 'No tests were run before claiming the bug was fixed',
109
+ whatToChange: 'Always run tests and show output before claiming done',
110
+ tags: ['verification', 'testing'],
111
+ });
112
+ assert(neg.ok === true, 'valid negative → ok');
113
+ assert(neg.actionType === 'store-mistake', 'negative → store-mistake');
114
+ assert(neg.memory.title.startsWith('MISTAKE:'), 'negative → MISTAKE: prefix');
115
+ assert(neg.memory.category === 'error', 'negative → error category');
116
+ assert(neg.memory.tags.includes('verification'), 'preserves domain tags');
117
+
118
+ // Valid positive feedback → memory
119
+ const pos = convertFeedbackToMemory({
120
+ signal: 'positive',
121
+ whatWorked: 'Built schema-validated feedback system with prevention rules',
122
+ tags: ['architecture', 'thumbgate'],
123
+ });
124
+ assert(pos.ok === true, 'valid positive → ok');
125
+ assert(pos.actionType === 'store-learning', 'positive → store-learning');
126
+ assert(pos.memory.title.startsWith('SUCCESS:'), 'positive → SUCCESS: prefix');
127
+ assert(pos.memory.category === 'learning', 'positive → learning category');
128
+
129
+ const inferredPos = convertFeedbackToMemory({
130
+ signal: 'positive',
131
+ context: 'ThumbGate automation and Claude statusline repair',
132
+ whatWorked: 'Verified the live ThumbGate version and fixed the stale Claude statusline wiring',
133
+ });
134
+ assert(inferredPos.ok === true, 'specific positive without tags → ok');
135
+ assert(inferredPos.memory.tags.includes('thumbgate'), 'infers a non-generic domain tag');
136
+
137
+ // Bare thumbs down → rejected
138
+ const bare = convertFeedbackToMemory({ signal: 'negative' });
139
+ assert(bare.ok === false, 'bare negative → rejected');
140
+ assert(bare.reason.includes('No context') || bare.reason.includes('cannot'), 'reports missing context');
141
+
142
+ // Bare thumbs up → rejected
143
+ const bareUp = convertFeedbackToMemory({ signal: 'positive' });
144
+ assert(bareUp.ok === false, 'bare positive → rejected');
145
+
146
+ // Unknown signal → rejected
147
+ const unknown = convertFeedbackToMemory({ signal: 'maybe', context: 'test' });
148
+ assert(unknown.ok === false, 'unknown signal → rejected');
149
+
150
+ // Context-only negative → ok
151
+ const ctxNeg = convertFeedbackToMemory({
152
+ signal: 'negative',
153
+ context: 'Showed fake ThumbGate statistics panel to user',
154
+ tags: ['thumbgate'],
155
+ });
156
+ assert(ctxNeg.ok === true, 'context-only negative → ok');
157
+
158
+ // Context-only positive → ok
159
+ const ctxPos = convertFeedbackToMemory({
160
+ signal: 'positive',
161
+ context: 'Ran full test suite and showed green output before responding',
162
+ tags: ['verification'],
163
+ });
164
+ assert(ctxPos.ok === true, 'context-only positive → ok');
165
+
166
+ console.log(`\n${'═'.repeat(50)}`);
167
+ console.log(`Results: ${passed} passed, ${failed} failed, ${passed + failed} total`);
168
+ console.log(`${'═'.repeat(50)}\n`);
169
+
170
+ process.exit(failed > 0 ? 1 : 0);
171
+ }
172
+
173
+ // ---------------------------------------------------------------------------
174
+ // Exports & main
175
+ // ---------------------------------------------------------------------------
176
+
177
+ module.exports = { convertFeedbackToMemory };
178
+
179
+ if (require.main === module) {
180
+ if (process.argv.includes('--test')) {
181
+ runTests();
182
+ } else {
183
+ runStdin();
184
+ }
185
+ }
@@ -0,0 +1,163 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { getAutoGatesPath } = require('./auto-promote-gates');
6
+
7
+ const DEFAULT_LOG = path.join(__dirname, '..', '.claude', 'memory', 'feedback', 'feedback-log.jsonl');
8
+ const NEG = new Set(['negative', 'negative_strong', 'down', 'thumbs_down']);
9
+ const POS = new Set(['positive', 'positive_strong', 'up', 'thumbs_up']);
10
+
11
+ function parseFeedbackFile(filePath) {
12
+ if (!fs.existsSync(filePath)) return [];
13
+ const entries = [];
14
+ for (const line of fs.readFileSync(filePath, 'utf8').split('\n')) {
15
+ const trimmed = line.trim();
16
+ if (!trimmed) continue;
17
+ try { entries.push(JSON.parse(trimmed)); } catch { /* skip malformed */ }
18
+ }
19
+ return entries;
20
+ }
21
+
22
+ function classifySignal(entry) {
23
+ const sig = (entry.signal || entry.feedback || '').toLowerCase();
24
+ if (NEG.has(sig)) return 'negative';
25
+ if (POS.has(sig)) return 'positive';
26
+ return null;
27
+ }
28
+
29
+ function normalize(ctx) {
30
+ return (ctx || '').replace(/\/Users\/[^\s/]+/g, '~').replace(/:[0-9]+/g, '').toLowerCase().trim();
31
+ }
32
+
33
+ const HIGH_RISK_TAGS = new Set(['git-workflow', 'scope-control', 'trust-breach', 'execution-gap', 'regression', 'security']);
34
+ function analyze(entries) {
35
+ let positiveCount = 0, negativeCount = 0;
36
+ const categories = {};
37
+ const toolBuckets = {};
38
+ const contextCounts = {};
39
+
40
+ for (const e of entries) {
41
+ const cls = classifySignal(e);
42
+ if (!cls) continue;
43
+ cls === 'positive' ? positiveCount++ : negativeCount++;
44
+
45
+ const cat = e.task_category || e.category || 'uncategorized';
46
+ categories[cat] = categories[cat] || { positive: 0, negative: 0, total: 0 };
47
+ categories[cat][cls]++;
48
+ categories[cat].total++;
49
+
50
+ if (cls === 'negative') {
51
+ const tool = e.tool_name || 'unknown';
52
+ toolBuckets[tool] = (toolBuckets[tool] || 0) + 1;
53
+ const key = normalize(e.context);
54
+ if (key.length > 10) {
55
+ if (!contextCounts[key]) {
56
+ contextCounts[key] = {
57
+ raw: e.context,
58
+ count: 0,
59
+ tool,
60
+ tags: e.tags || [],
61
+ hasHighRisk: (e.tags || []).some(t => HIGH_RISK_TAGS.has(t))
62
+ };
63
+ }
64
+ contextCounts[key].count++;
65
+ }
66
+ }
67
+ }
68
+
69
+ const total = positiveCount + negativeCount;
70
+ const recurringIssues = Object.values(contextCounts)
71
+ .filter(v => v.count >= 2 || (v.count >= 1 && v.hasHighRisk)) // Lower threshold for high-risk
72
+ .sort((a, b) => b.count - a.count)
73
+ .map(v => {
74
+ // Threshold hardening: promote high-risk to block after 2 failures
75
+ const threshold = v.hasHighRisk ? 2 : 4;
76
+ const severity = v.count >= threshold ? 'critical' : v.count >= (threshold - 1) ? 'high' : 'medium';
77
+
78
+ return {
79
+ pattern: v.raw.slice(0, 120),
80
+ count: v.count,
81
+ severity,
82
+ hasHighRisk: v.hasHighRisk,
83
+ suggestedRule: `NEVER ${v.raw.slice(0, 80).replace(/CRITICAL ERROR - User frustrated: /i, '')}`,
84
+ };
85
+ });
86
+
87
+ // Auto-Gate Promotion logic
88
+ promoteToGates(recurringIssues);
89
+
90
+ return {
91
+ generatedAt: new Date().toISOString(),
92
+ totalFeedback: total,
93
+ negativeCount,
94
+ positiveCount,
95
+ negativeRate: total ? `${((negativeCount / total) * 100).toFixed(1)}%` : '0%',
96
+ recurringIssues,
97
+ categoryBreakdown: categories,
98
+ topTools: toolBuckets,
99
+ };
100
+ }
101
+
102
+ function promoteToGates(recurringIssues) {
103
+ const autoGatePath = getAutoGatesPath();
104
+ const autoGates = { version: 1, gates: [] };
105
+
106
+ for (const issue of recurringIssues) {
107
+ if (issue.severity === 'critical') {
108
+ // Extract key nouns/verbs for pattern matching
109
+ const keywords = issue.pattern
110
+ .toLowerCase()
111
+ .replace(/[^a-z0-9\s]/g, '')
112
+ .split(/\s+/)
113
+ .filter(w => w.length > 4)
114
+ .slice(0, 3);
115
+
116
+ if (keywords.length >= 2) {
117
+ const pattern = keywords.join('.*');
118
+ autoGates.gates.push({
119
+ id: `auto-${issue.hasHighRisk ? 'hardened' : 'promoted'}-${Date.now().toString(36)}`,
120
+ pattern,
121
+ action: 'block',
122
+ message: `Automatically blocked due to repeated failures: ${issue.suggestedRule}`,
123
+ severity: 'critical',
124
+ source: 'feedback-auto-promotion'
125
+ });
126
+ }
127
+ }
128
+ }
129
+
130
+ if (autoGates.gates.length > 0) {
131
+ fs.mkdirSync(path.dirname(autoGatePath), { recursive: true });
132
+ fs.writeFileSync(autoGatePath, JSON.stringify(autoGates, null, 2));
133
+ }
134
+ }
135
+
136
+ function toRules(report) {
137
+ const lines = ['# Suggested Rules from Feedback Analysis', `# Generated: ${report.generatedAt}`, ''];
138
+ lines.push(`# Negative rate: ${report.negativeRate} (${report.negativeCount}/${report.totalFeedback})`);
139
+ lines.push('');
140
+ for (const issue of report.recurringIssues) {
141
+ lines.push(`- [${issue.severity.toUpperCase()}] (${issue.count}x) ${issue.suggestedRule}`);
142
+ }
143
+ if (!report.recurringIssues.length) lines.push('- No recurring issues detected.');
144
+ return lines.join('\n');
145
+ }
146
+
147
+ if (require.main === module) {
148
+ try {
149
+ const logPath = process.argv[2] && !process.argv[2].startsWith('--') ? process.argv[2] : DEFAULT_LOG;
150
+ const entries = parseFeedbackFile(logPath);
151
+ const report = analyze(entries);
152
+ if (process.argv.includes('--rules')) {
153
+ console.log(toRules(report));
154
+ } else {
155
+ console.log(JSON.stringify(report, null, 2));
156
+ }
157
+ } catch (err) {
158
+ console.error('Warning:', err.message);
159
+ }
160
+ process.exit(0);
161
+ }
162
+
163
+ module.exports = { parseFeedbackFile, classifySignal, analyze, toRules, normalize };