thumbgate 0.9.9

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 (369) 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/rlhf-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 +1483 -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 +286 -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 +1195 -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-314.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 +293 -0
  117. package/scripts/auto-wire-hooks.js +316 -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 +93 -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 +231 -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 +206 -0
  152. package/scripts/ensure-repo-bootstrap.js +129 -0
  153. package/scripts/ephemeral-agent-store.js +219 -0
  154. package/scripts/eval-harness.js +56 -0
  155. package/scripts/evolution-state.js +241 -0
  156. package/scripts/experiment-tracker.js +267 -0
  157. package/scripts/export-databricks-bundle.js +242 -0
  158. package/scripts/export-dpo-pairs.js +344 -0
  159. package/scripts/export-kto-pairs.js +309 -0
  160. package/scripts/export-training.js +450 -0
  161. package/scripts/failure-diagnostics.js +558 -0
  162. package/scripts/feedback-attribution.js +313 -0
  163. package/scripts/feedback-fallback.js +110 -0
  164. package/scripts/feedback-history-distiller.js +391 -0
  165. package/scripts/feedback-inbox-read.js +162 -0
  166. package/scripts/feedback-loop.js +1887 -0
  167. package/scripts/feedback-paths.js +145 -0
  168. package/scripts/feedback-quality.js +139 -0
  169. package/scripts/feedback-root-consolidator.js +238 -0
  170. package/scripts/feedback-schema.js +426 -0
  171. package/scripts/feedback-session.js +286 -0
  172. package/scripts/feedback-to-memory.js +185 -0
  173. package/scripts/feedback-to-rules.js +164 -0
  174. package/scripts/filesystem-search.js +405 -0
  175. package/scripts/funnel-analytics.js +35 -0
  176. package/scripts/gate-satisfy.js +42 -0
  177. package/scripts/gate-stats.js +116 -0
  178. package/scripts/gate-templates.js +70 -0
  179. package/scripts/gates-engine.js +816 -0
  180. package/scripts/generate-paperbanana-diagrams.sh +99 -0
  181. package/scripts/generate-pretool-hook.sh +40 -0
  182. package/scripts/github-about.js +350 -0
  183. package/scripts/github-outreach.js +65 -0
  184. package/scripts/gtm-revenue-loop.js +520 -0
  185. package/scripts/hallucination-detector.js +226 -0
  186. package/scripts/hf-papers.js +317 -0
  187. package/scripts/history-distiller.js +200 -0
  188. package/scripts/hook-auto-capture.sh +100 -0
  189. package/scripts/hook-stop-pr-thread-check.sh +68 -0
  190. package/scripts/hook-stop-self-score.sh +51 -0
  191. package/scripts/hook-stop-verify-deploy.sh +31 -0
  192. package/scripts/hook-thumbgate-cache-updater.js +48 -0
  193. package/scripts/hook-verify-before-done.sh +20 -0
  194. package/scripts/hosted-config.js +156 -0
  195. package/scripts/hybrid-feedback-context.js +675 -0
  196. package/scripts/install-mcp.js +159 -0
  197. package/scripts/intent-router.js +392 -0
  198. package/scripts/internal-agent-bootstrap.js +490 -0
  199. package/scripts/jsonl-watcher.js +155 -0
  200. package/scripts/lesson-db.js +613 -0
  201. package/scripts/lesson-inference.js +310 -0
  202. package/scripts/lesson-retrieval.js +95 -0
  203. package/scripts/lesson-rotation.js +137 -0
  204. package/scripts/lesson-search.js +644 -0
  205. package/scripts/lesson-synthesis.js +196 -0
  206. package/scripts/license.js +50 -0
  207. package/scripts/local-model-profile.js +384 -0
  208. package/scripts/markdown-escape.js +12 -0
  209. package/scripts/marketing-experiment.js +671 -0
  210. package/scripts/mcp-config.js +149 -0
  211. package/scripts/mcp-policy.js +99 -0
  212. package/scripts/memalign-recall.js +111 -0
  213. package/scripts/memory-firewall.js +222 -0
  214. package/scripts/memory-migration.js +296 -0
  215. package/scripts/meta-policy.js +190 -0
  216. package/scripts/metered-billing.js +16 -0
  217. package/scripts/model-tier-router.js +301 -0
  218. package/scripts/money-watcher.js +71 -0
  219. package/scripts/multi-hop-recall.js +240 -0
  220. package/scripts/natural-language-harness.js +330 -0
  221. package/scripts/obsidian-export.js +713 -0
  222. package/scripts/operational-dashboard.js +103 -0
  223. package/scripts/operational-summary.js +93 -0
  224. package/scripts/optimize-context.js +17 -0
  225. package/scripts/org-dashboard.js +201 -0
  226. package/scripts/partner-orchestration.js +146 -0
  227. package/scripts/per-step-scoring.js +165 -0
  228. package/scripts/perplexity-marketing.js +466 -0
  229. package/scripts/pii-scanner.js +153 -0
  230. package/scripts/plan-gate.js +154 -0
  231. package/scripts/post-everywhere.js +308 -0
  232. package/scripts/post-to-x-retry.sh +22 -0
  233. package/scripts/post-to-x.js +369 -0
  234. package/scripts/pr-manager.js +236 -0
  235. package/scripts/predictive-insights.js +356 -0
  236. package/scripts/principle-extractor.js +162 -0
  237. package/scripts/pro-features.js +40 -0
  238. package/scripts/pro-local-dashboard.js +174 -0
  239. package/scripts/problem-detail.js +53 -0
  240. package/scripts/product-feedback.js +134 -0
  241. package/scripts/profile-router.js +245 -0
  242. package/scripts/prompt-dlp.js +221 -0
  243. package/scripts/prompt-guard.js +83 -0
  244. package/scripts/prove-adapters.js +863 -0
  245. package/scripts/prove-attribution.js +365 -0
  246. package/scripts/prove-automation.js +653 -0
  247. package/scripts/prove-autoresearch.js +304 -0
  248. package/scripts/prove-claim-verification.js +277 -0
  249. package/scripts/prove-cloudflare-sandbox.js +163 -0
  250. package/scripts/prove-data-pipeline.js +410 -0
  251. package/scripts/prove-data-quality.js +227 -0
  252. package/scripts/prove-evolution.js +352 -0
  253. package/scripts/prove-harnesses.js +287 -0
  254. package/scripts/prove-intelligence.js +259 -0
  255. package/scripts/prove-lancedb.js +371 -0
  256. package/scripts/prove-local-intelligence.js +342 -0
  257. package/scripts/prove-loop-closure.js +263 -0
  258. package/scripts/prove-predictive-insights.js +357 -0
  259. package/scripts/prove-runtime.js +350 -0
  260. package/scripts/prove-seo-gsd.js +234 -0
  261. package/scripts/prove-settings.js +279 -0
  262. package/scripts/prove-subway-upgrades.js +277 -0
  263. package/scripts/prove-tessl.js +229 -0
  264. package/scripts/prove-training-export.js +327 -0
  265. package/scripts/prove-workflow-contract.js +116 -0
  266. package/scripts/prove-xmemory.js +332 -0
  267. package/scripts/publish-decision.js +133 -0
  268. package/scripts/pulse.js +80 -0
  269. package/scripts/rate-limiter.js +125 -0
  270. package/scripts/reddit-dm-outreach.js +182 -0
  271. package/scripts/reddit-monitor-cron.sh +26 -0
  272. package/scripts/reflector-agent.js +221 -0
  273. package/scripts/reminder-engine.js +132 -0
  274. package/scripts/revenue-status.js +472 -0
  275. package/scripts/risk-scorer.js +459 -0
  276. package/scripts/rlaif-self-audit.js +129 -0
  277. package/scripts/rlhf_session_start.sh +32 -0
  278. package/scripts/rubric-engine.js +230 -0
  279. package/scripts/schedule-manager.js +251 -0
  280. package/scripts/secret-scanner.js +414 -0
  281. package/scripts/self-heal.js +147 -0
  282. package/scripts/self-healing-check.js +188 -0
  283. package/scripts/semantic-layer.js +98 -0
  284. package/scripts/seo-gsd.js +1153 -0
  285. package/scripts/settings-hierarchy.js +214 -0
  286. package/scripts/shieldcortex-memory-firewall-runner.mjs +53 -0
  287. package/scripts/skill-exporter.js +262 -0
  288. package/scripts/skill-generator.js +446 -0
  289. package/scripts/skill-materializer.js +134 -0
  290. package/scripts/skill-packs.js +136 -0
  291. package/scripts/skill-proposer.js +99 -0
  292. package/scripts/skill-quality-tracker.js +282 -0
  293. package/scripts/slo-alert-engine.js +14 -0
  294. package/scripts/slow-loop.js +72 -0
  295. package/scripts/social-analytics/db/schema.sql +32 -0
  296. package/scripts/social-analytics/db/social-analytics.db +0 -0
  297. package/scripts/social-analytics/digest.js +256 -0
  298. package/scripts/social-analytics/generate-instagram-card.js +97 -0
  299. package/scripts/social-analytics/instagram-thumbgate-post.js +107 -0
  300. package/scripts/social-analytics/load-env.js +46 -0
  301. package/scripts/social-analytics/mcp-server.js +289 -0
  302. package/scripts/social-analytics/normalizer.js +580 -0
  303. package/scripts/social-analytics/notify.js +162 -0
  304. package/scripts/social-analytics/poll-all.js +92 -0
  305. package/scripts/social-analytics/pollers/github.js +195 -0
  306. package/scripts/social-analytics/pollers/instagram.js +253 -0
  307. package/scripts/social-analytics/pollers/linkedin.js +330 -0
  308. package/scripts/social-analytics/pollers/plausible.js +247 -0
  309. package/scripts/social-analytics/pollers/reddit.js +306 -0
  310. package/scripts/social-analytics/pollers/threads.js +233 -0
  311. package/scripts/social-analytics/pollers/tiktok.js +203 -0
  312. package/scripts/social-analytics/pollers/x.js +227 -0
  313. package/scripts/social-analytics/pollers/youtube.js +304 -0
  314. package/scripts/social-analytics/pollers/zernio.js +183 -0
  315. package/scripts/social-analytics/publish-instagram-thumbgate.js +98 -0
  316. package/scripts/social-analytics/publish-thumbgate-launch.js +316 -0
  317. package/scripts/social-analytics/publishers/devto.js +122 -0
  318. package/scripts/social-analytics/publishers/instagram.js +317 -0
  319. package/scripts/social-analytics/publishers/linkedin.js +294 -0
  320. package/scripts/social-analytics/publishers/reddit.js +390 -0
  321. package/scripts/social-analytics/publishers/threads.js +275 -0
  322. package/scripts/social-analytics/publishers/tiktok.js +217 -0
  323. package/scripts/social-analytics/publishers/x.js +259 -0
  324. package/scripts/social-analytics/publishers/youtube.js +223 -0
  325. package/scripts/social-analytics/publishers/zernio.js +378 -0
  326. package/scripts/social-analytics/run-digest.js +34 -0
  327. package/scripts/social-analytics/store.js +257 -0
  328. package/scripts/social-analytics/utm.js +143 -0
  329. package/scripts/social-pipeline.js +2628 -0
  330. package/scripts/social-quality-gate.js +18 -0
  331. package/scripts/social-reply-monitor.js +445 -0
  332. package/scripts/status-dashboard.js +155 -0
  333. package/scripts/statusline-lesson.js +16 -0
  334. package/scripts/statusline-tower.js +8 -0
  335. package/scripts/statusline.sh +116 -0
  336. package/scripts/stripe-live-status.js +115 -0
  337. package/scripts/subagent-profiles.js +79 -0
  338. package/scripts/sync-gh-secrets-from-env.sh +70 -0
  339. package/scripts/sync-github-about.js +52 -0
  340. package/scripts/sync-version.js +447 -0
  341. package/scripts/synthetic-dpo.js +234 -0
  342. package/scripts/telemetry-analytics.js +821 -0
  343. package/scripts/tessl-export.js +371 -0
  344. package/scripts/test-coverage.js +120 -0
  345. package/scripts/thompson-sampling.js +417 -0
  346. package/scripts/thumbgate-search.js +189 -0
  347. package/scripts/tool-kpi-tracker.js +12 -0
  348. package/scripts/tool-registry.js +811 -0
  349. package/scripts/train_from_feedback.py +933 -0
  350. package/scripts/user-profile.js +78 -0
  351. package/scripts/validate-feedback.js +581 -0
  352. package/scripts/validate-workflow-contract.js +287 -0
  353. package/scripts/vector-store.js +197 -0
  354. package/scripts/verification-loop.js +291 -0
  355. package/scripts/verify-obsidian-setup.sh +269 -0
  356. package/scripts/verify-run.js +269 -0
  357. package/scripts/webhook-delivery.js +62 -0
  358. package/scripts/weekly-auto-post.js +124 -0
  359. package/scripts/workflow-runs.js +154 -0
  360. package/scripts/workflow-sprint-intake.js +475 -0
  361. package/scripts/workspace-evolver.js +374 -0
  362. package/scripts/x-autonomous-marketing.js +139 -0
  363. package/scripts/xmemory-lite.js +405 -0
  364. package/skills/agent-memory/SKILL.md +97 -0
  365. package/skills/rlhf-feedback/SKILL.md +49 -0
  366. package/skills/solve-architecture-autonomy/SKILL.md +17 -0
  367. package/skills/solve-architecture-autonomy/tool.js +33 -0
  368. package/skills/thumbgate/SKILL.md +114 -0
  369. package/src/api/server.js +4206 -0
@@ -0,0 +1,310 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Lesson Inference — surrounding message context extraction + lesson linking.
6
+ *
7
+ * When a user gives thumbs up/down, this module:
8
+ * 1. Reads the surrounding conversation context (prior + following messages)
9
+ * 2. Infers what the lesson is from that context
10
+ * 3. Creates a structured lesson with a stable link
11
+ * 4. Provides data for the statusbar to show the most recent lesson
12
+ *
13
+ * Competing with Mem0: our advantage is local-first + structured inference,
14
+ * not just raw storage.
15
+ */
16
+
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+ const { resolveFeedbackDir } = require('./feedback-paths');
20
+ const {
21
+ buildStableId,
22
+ extractFilePaths,
23
+ extractToolCalls,
24
+ extractErrors,
25
+ } = require('./conversation-context');
26
+
27
+ const LESSONS_FILE = 'lessons-index.jsonl';
28
+ const RECENT_LESSON_FILE = 'recent-lesson.json';
29
+
30
+ function getFeedbackDir() {
31
+ return resolveFeedbackDir();
32
+ }
33
+
34
+ function getLessonsPath() { return path.join(getFeedbackDir(), LESSONS_FILE); }
35
+ function getRecentLessonPath() { return path.join(getFeedbackDir(), RECENT_LESSON_FILE); }
36
+
37
+ function readJsonl(fp) {
38
+ if (!fs.existsSync(fp)) return [];
39
+ const raw = fs.readFileSync(fp, 'utf-8').trim();
40
+ if (!raw) return [];
41
+ return raw.split('\n').map((l) => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
42
+ }
43
+
44
+ function ensureDir(p) { const d = path.dirname(p); if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true }); }
45
+
46
+ // ---------------------------------------------------------------------------
47
+ // 1. Surrounding Message Context Extraction
48
+ // ---------------------------------------------------------------------------
49
+
50
+ /**
51
+ * Extract lesson context from surrounding messages.
52
+ * Takes the conversation turns before/after the feedback signal.
53
+ *
54
+ * @param {Object} opts
55
+ * @param {Array} opts.priorMessages - Messages before the feedback (most recent first)
56
+ * @param {Array} opts.followingMessages - Messages after the feedback
57
+ * @param {string} opts.signal - 'positive' or 'negative'
58
+ * @param {string} opts.feedbackContext - User-provided context string
59
+ * @returns {{ inferredLesson, triggerMessage, priorSummary, confidence }}
60
+ */
61
+ function inferFromSurroundingMessages({ priorMessages = [], followingMessages = [], signal, feedbackContext = '' } = {}) {
62
+ const prior = priorMessages.slice(0, 5); // Last 5 messages before feedback
63
+ const following = followingMessages.slice(0, 3); // Next 3 messages after
64
+
65
+ // The trigger message is typically the last assistant message before feedback
66
+ const triggerMessage = prior.find((m) => m.role === 'assistant') || prior[0] || null;
67
+ const triggerText = triggerMessage ? (triggerMessage.content || triggerMessage.text || '') : '';
68
+
69
+ // Extract what the agent did
70
+ const actionPatterns = [
71
+ { regex: /(?:edited|modified|changed|updated)\s+(.+?)(?:\.|$)/i, type: 'edit' },
72
+ { regex: /(?:created|wrote|added)\s+(.+?)(?:\.|$)/i, type: 'create' },
73
+ { regex: /(?:ran|executed|running)\s+(.+?)(?:\.|$)/i, type: 'command' },
74
+ { regex: /(?:fixed|resolved|patched)\s+(.+?)(?:\.|$)/i, type: 'fix' },
75
+ { regex: /(?:deployed|pushed|merged)\s+(.+?)(?:\.|$)/i, type: 'deploy' },
76
+ { regex: /(?:deleted|removed|dropped)\s+(.+?)(?:\.|$)/i, type: 'delete' },
77
+ ];
78
+
79
+ let inferredAction = null;
80
+ for (const ap of actionPatterns) {
81
+ const match = triggerText.match(ap.regex);
82
+ if (match) { inferredAction = { type: ap.type, target: match[1].trim().slice(0, 100) }; break; }
83
+ }
84
+
85
+ // Build the lesson
86
+ const isNegative = signal === 'negative' || signal === 'down';
87
+ let inferredLesson;
88
+
89
+ if (isNegative && inferredAction) {
90
+ inferredLesson = `Avoid: ${inferredAction.type} on ${inferredAction.target}. ${feedbackContext || 'User signaled this approach failed.'}`;
91
+ } else if (!isNegative && inferredAction) {
92
+ inferredLesson = `Repeat: ${inferredAction.type} on ${inferredAction.target}. ${feedbackContext || 'User confirmed this approach works.'}`;
93
+ } else if (feedbackContext) {
94
+ inferredLesson = feedbackContext;
95
+ } else {
96
+ inferredLesson = `${isNegative ? 'Negative' : 'Positive'} signal on agent output. No specific action inferred.`;
97
+ }
98
+
99
+ // Summarize prior context
100
+ const priorSummary = prior.slice(0, 3).map((m) => {
101
+ const role = m.role || 'unknown';
102
+ const text = (m.content || m.text || '').slice(0, 80);
103
+ return `[${role}] ${text}`;
104
+ }).join(' → ');
105
+
106
+ // Confidence: higher if we have more context
107
+ const contextSignals = [
108
+ feedbackContext.length > 10,
109
+ !!inferredAction,
110
+ prior.length >= 2,
111
+ !!triggerMessage,
112
+ ];
113
+ const confidence = Math.round((contextSignals.filter(Boolean).length / contextSignals.length) * 100);
114
+
115
+ return { inferredLesson, triggerMessage: triggerText.slice(0, 200), priorSummary, inferredAction, confidence, signal };
116
+ }
117
+
118
+ // ---------------------------------------------------------------------------
119
+ // 2. Lesson Index & Linking
120
+ // ---------------------------------------------------------------------------
121
+
122
+ /**
123
+ * Create a lesson record with a stable link and store in the index.
124
+ */
125
+ function createLesson({ feedbackId, signal, inferredLesson, triggerMessage, priorSummary, confidence, tags = [], metadata = {} } = {}) {
126
+ const lesson = {
127
+ id: buildStableId('lesson'),
128
+ feedbackId: feedbackId || null,
129
+ signal: signal || 'unknown',
130
+ lesson: inferredLesson || '',
131
+ triggerMessage: triggerMessage || '',
132
+ priorSummary: priorSummary || '',
133
+ confidence: confidence || 0,
134
+ tags,
135
+ metadata,
136
+ createdAt: new Date().toISOString(),
137
+ link: null, // populated below
138
+ };
139
+
140
+ // Stable link: dashboard deep-link to this lesson
141
+ lesson.link = `http://localhost:9876/lessons#${lesson.id}`;
142
+
143
+ const lessonsPath = getLessonsPath();
144
+ ensureDir(lessonsPath);
145
+ fs.appendFileSync(lessonsPath, JSON.stringify(lesson) + '\n');
146
+
147
+ // Update recent lesson for statusbar
148
+ const recentPath = getRecentLessonPath();
149
+ fs.writeFileSync(recentPath, JSON.stringify(lesson, null, 2) + '\n');
150
+
151
+ return lesson;
152
+ }
153
+
154
+ /**
155
+ * Get the most recent lesson (for statusbar display).
156
+ */
157
+ function getRecentLesson() {
158
+ const p = getRecentLessonPath();
159
+ if (!fs.existsSync(p)) return null;
160
+ try { return JSON.parse(fs.readFileSync(p, 'utf-8')); } catch { return null; }
161
+ }
162
+
163
+ /**
164
+ * Search lessons by query text.
165
+ */
166
+ function searchLessons({ query = '', limit = 10, signal } = {}) {
167
+ const lessons = readJsonl(getLessonsPath());
168
+ const tokens = query.toLowerCase().split(/\s+/).filter(Boolean);
169
+
170
+ return lessons
171
+ .filter((l) => !signal || l.signal === signal)
172
+ .map((l) => {
173
+ const haystack = `${l.lesson} ${l.triggerMessage} ${l.priorSummary} ${(l.tags || []).join(' ')}`.toLowerCase();
174
+ let score = 0;
175
+ for (const t of tokens) { if (t.length > 2 && haystack.includes(t)) score += 1; }
176
+ return { ...l, _score: score };
177
+ })
178
+ .filter((l) => tokens.length === 0 || l._score > 0)
179
+ .sort((a, b) => b._score - a._score || new Date(b.createdAt) - new Date(a.createdAt))
180
+ .slice(0, limit);
181
+ }
182
+
183
+ /**
184
+ * Get lesson stats.
185
+ */
186
+ function getLessonStats() {
187
+ const lessons = readJsonl(getLessonsPath());
188
+ const positive = lessons.filter((l) => l.signal === 'positive' || l.signal === 'up').length;
189
+ const negative = lessons.filter((l) => l.signal === 'negative' || l.signal === 'down').length;
190
+ const avgConfidence = lessons.length > 0 ? Math.round(lessons.reduce((s, l) => s + (l.confidence || 0), 0) / lessons.length) : 0;
191
+ return { total: lessons.length, positive, negative, avgConfidence };
192
+ }
193
+
194
+ // ---------------------------------------------------------------------------
195
+ // 3. Statusbar Data Provider
196
+ // ---------------------------------------------------------------------------
197
+
198
+ /**
199
+ * Get data for the Claude Code statusbar.
200
+ * Returns the most recent lesson with link, formatted for display.
201
+ */
202
+ function getStatusbarLessonData() {
203
+ const recent = getRecentLesson();
204
+ if (!recent) return { hasLesson: false, text: null, link: null };
205
+
206
+ const emoji = (recent.signal === 'negative' || recent.signal === 'down') ? '👎' : '👍';
207
+ const truncated = recent.lesson.length > 60 ? recent.lesson.slice(0, 57) + '...' : recent.lesson;
208
+
209
+ return {
210
+ hasLesson: true,
211
+ text: `${emoji} ${truncated}`,
212
+ link: recent.link,
213
+ lessonId: recent.id,
214
+ confidence: recent.confidence,
215
+ createdAt: recent.createdAt,
216
+ };
217
+ }
218
+
219
+ // ---------------------------------------------------------------------------
220
+ // 4. Structured IF/THEN Lesson Extraction (v0.9.4)
221
+ // ---------------------------------------------------------------------------
222
+
223
+ function inferStructuredLesson(conversationWindow, signal, context) {
224
+ const normalizedWindow = Array.isArray(conversationWindow) ? conversationWindow : [];
225
+ const userMessages = normalizedWindow.filter(m => m.role === 'user');
226
+ const assistantMessages = normalizedWindow.filter(m => m.role === 'assistant');
227
+ const lastUser = userMessages[userMessages.length - 1]?.content || '';
228
+ const lastAssistant = assistantMessages[assistantMessages.length - 1]?.content || '';
229
+ const filePaths = extractFilePaths(normalizedWindow);
230
+ const toolCalls = extractToolCalls(normalizedWindow);
231
+ const errorPatterns = extractErrors(normalizedWindow);
232
+
233
+ return {
234
+ format: 'if-then-v1',
235
+ trigger: extractTrigger(lastUser),
236
+ action: extractAction(lastAssistant, signal),
237
+ signal,
238
+ confidence: calculateConfidence(normalizedWindow, context),
239
+ scope: inferScope(filePaths, toolCalls),
240
+ examples: [{ userIntent: lastUser.slice(0, 300), assistantAction: lastAssistant.slice(0, 300), outcome: signal === 'positive' ? 'approved' : 'rejected' }],
241
+ metadata: { toolsUsed: toolCalls, filesInvolved: filePaths.slice(0, 10), errorPatterns: errorPatterns.slice(0, 5), conversationLength: normalizedWindow.length, inferredAt: new Date().toISOString() },
242
+ };
243
+ }
244
+
245
+ function extractTrigger(userMsg) {
246
+ const text = String(userMsg || '').trim();
247
+ const lower = text.toLowerCase();
248
+ const leadingPhrases = [
249
+ { phrases: ['fix ', 'debug ', 'solve ', 'investigate '], type: 'debugging' },
250
+ { phrases: ['implement ', 'add ', 'create ', 'build '], type: 'implementation' },
251
+ { phrases: ['why ', 'how ', 'what ', 'where '], type: 'question' },
252
+ { phrases: ['don\'t ', 'do not ', 'never ', 'stop ', 'avoid '], type: 'constraint' },
253
+ ];
254
+
255
+ for (const entry of leadingPhrases) {
256
+ const match = consumePhrase(lower, text, entry.phrases);
257
+ if (match) return { condition: match, type: entry.type };
258
+ }
259
+
260
+ const errorIndex = ['error', 'fail', 'crash', 'broken', 'wrong']
261
+ .map((token) => lower.indexOf(token))
262
+ .filter((index) => index >= 0)
263
+ .sort((a, b) => a - b)[0];
264
+ if (Number.isInteger(errorIndex)) {
265
+ return {
266
+ condition: text.slice(errorIndex).replace(/^[:\-\s]+/, '').slice(0, 120).trim() || text.slice(0, 120).trim(),
267
+ type: 'error-report',
268
+ };
269
+ }
270
+
271
+ return { condition: text.slice(0, 120).trim(), type: 'general' };
272
+ }
273
+
274
+ function extractAction(assistantMsg, signal) {
275
+ return signal === 'positive'
276
+ ? { type: 'do', description: `Repeat this approach: ${assistantMsg.slice(0, 200).trim()}` }
277
+ : { type: 'avoid', description: `Avoid this approach: ${assistantMsg.slice(0, 200).trim()}` };
278
+ }
279
+
280
+ function calculateConfidence(window, context) {
281
+ let s = 0.5;
282
+ if (window.length >= 3) s += 0.1;
283
+ if (window.length >= 5) s += 0.1;
284
+ if (context && context.length > 20) s += 0.1;
285
+ if (extractFilePaths(window).length > 0) s += 0.1;
286
+ return Math.min(s, 1.0);
287
+ }
288
+
289
+ function inferScope(filePaths, toolCalls) {
290
+ if (filePaths.length === 0 && toolCalls.length === 0) return 'global';
291
+ if (filePaths.length <= 2) return 'file-level';
292
+ return 'project-level';
293
+ }
294
+
295
+ function consumePhrase(lower, original, phrases) {
296
+ for (const phrase of phrases) {
297
+ if (!lower.startsWith(phrase)) continue;
298
+ const value = original.slice(phrase.length).replace(/^[:\-\s]+/, '').slice(0, 120).trim();
299
+ return value || original.slice(0, 120).trim();
300
+ }
301
+ return null;
302
+ }
303
+
304
+ module.exports = {
305
+ inferFromSurroundingMessages, createLesson, getRecentLesson,
306
+ searchLessons, getLessonStats, getStatusbarLessonData,
307
+ getLessonsPath, getRecentLessonPath,
308
+ inferStructuredLesson, extractTrigger, extractAction, extractToolCalls,
309
+ extractFilePaths, extractErrors, calculateConfidence, inferScope,
310
+ };
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Per-action lesson retrieval.
6
+ * Given a tool name + context, returns the top-K most relevant lessons
7
+ * using keyword matching + recency decay + signal weighting.
8
+ */
9
+
10
+ const RECENCY_DECAY_DAYS = 30; // lessons older than this get down-weighted
11
+
12
+ function retrieveRelevantLessons(toolName, actionContext, options = {}) {
13
+ const { maxResults = 5, feedbackDir } = options;
14
+ const { getFeedbackPaths, readJSONL } = require('./feedback-loop');
15
+ const pathMod = require('path');
16
+ const paths = feedbackDir
17
+ ? { MEMORY_LOG_PATH: pathMod.join(feedbackDir, 'memory-log.jsonl') }
18
+ : getFeedbackPaths();
19
+
20
+ const memories = readJSONL(paths.MEMORY_LOG_PATH, { maxLines: 200 });
21
+ if (memories.length === 0) return [];
22
+
23
+ // Score each memory against the current action
24
+ const scored = memories.map((mem) => ({
25
+ ...mem,
26
+ relevanceScore: scoreRelevance(mem, toolName, actionContext),
27
+ }));
28
+
29
+ // Sort by relevance, return top-K
30
+ return scored
31
+ .filter((m) => m.relevanceScore > 0.1)
32
+ .sort((a, b) => b.relevanceScore - a.relevanceScore)
33
+ .slice(0, maxResults)
34
+ .map((m) => ({
35
+ id: m.id,
36
+ title: m.title,
37
+ content: m.content,
38
+ signal: m.tags?.includes('negative') ? 'negative' : 'positive',
39
+ rule: m.structuredRule || null,
40
+ relevanceScore: m.relevanceScore,
41
+ timestamp: m.timestamp,
42
+ }));
43
+ }
44
+
45
+ function scoreRelevance(memory, toolName, actionContext) {
46
+ let score = 0;
47
+
48
+ const memText = `${memory.title || ''} ${memory.content || ''} ${(memory.tags || []).join(' ')}`.toLowerCase();
49
+ const contextLower = (actionContext || '').toLowerCase();
50
+ const toolLower = (toolName || '').toLowerCase();
51
+
52
+ // 1. Tool name match (high weight)
53
+ if (memory.metadata?.toolsUsed?.some((t) => t.toLowerCase() === toolLower)) score += 0.4;
54
+ if (memText.includes(toolLower)) score += 0.2;
55
+
56
+ // 2. File path overlap
57
+ const contextPaths = extractPaths(actionContext);
58
+ const memPaths = memory.metadata?.filesInvolved || extractPaths(memText);
59
+ const pathOverlap = contextPaths.filter((p) =>
60
+ memPaths.some((mp) => mp.includes(p) || p.includes(mp)),
61
+ );
62
+ if (pathOverlap.length > 0) score += 0.3;
63
+
64
+ // 3. Keyword overlap (TF-IDF-lite)
65
+ const contextTokens = tokenize(contextLower);
66
+ const memTokens = tokenize(memText);
67
+ const overlap = contextTokens.filter((t) => memTokens.includes(t));
68
+ score += Math.min(overlap.length * 0.05, 0.3);
69
+
70
+ // 4. Signal weighting — negative lessons are more important to surface
71
+ if (memory.tags?.includes('negative')) score += 0.1;
72
+
73
+ // 5. Recency decay
74
+ if (memory.timestamp) {
75
+ const ageMs = Date.now() - new Date(memory.timestamp).getTime();
76
+ const ageDays = ageMs / (1000 * 60 * 60 * 24);
77
+ const decay = Math.max(0, 1 - ageDays / RECENCY_DECAY_DAYS);
78
+ score *= 0.5 + 0.5 * decay; // 50% base + 50% recency
79
+ }
80
+
81
+ // 6. Structured rule bonus — IF/THEN rules are more actionable
82
+ if (memory.structuredRule) score += 0.15;
83
+
84
+ return score;
85
+ }
86
+
87
+ function extractPaths(text) {
88
+ return [...new Set((text || '').match(/(?:src\/|scripts\/|tests\/)[^\s,)'"<>]+/g) || [])];
89
+ }
90
+
91
+ function tokenize(text) {
92
+ return (text || '').split(/[\s.,;:!?()\[\]{}"'`]+/).filter((t) => t.length > 3);
93
+ }
94
+
95
+ module.exports = { retrieveRelevantLessons, scoreRelevance };
@@ -0,0 +1,137 @@
1
+ 'use strict';
2
+
3
+ const STALE_THRESHOLD_DAYS = 60;
4
+ const ARCHIVE_THRESHOLD_DAYS = 90;
5
+
6
+ /**
7
+ * Add last_triggered and archived columns if they don't exist.
8
+ * Safe to call multiple times — uses IF NOT EXISTS logic via pragma.
9
+ */
10
+ function migrateSchema(db) {
11
+ const columns = db.pragma('table_info(lessons)').map((c) => c.name);
12
+ if (!columns.includes('last_triggered')) {
13
+ db.exec('ALTER TABLE lessons ADD COLUMN last_triggered TEXT');
14
+ }
15
+ if (!columns.includes('archived')) {
16
+ db.exec('ALTER TABLE lessons ADD COLUMN archived INTEGER DEFAULT 0');
17
+ }
18
+ if (!columns.includes('trigger_count')) {
19
+ db.exec('ALTER TABLE lessons ADD COLUMN trigger_count INTEGER DEFAULT 0');
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Record that a lesson was triggered (matched a gate or retrieved for context).
25
+ */
26
+ function recordTrigger(db, lessonId) {
27
+ migrateSchema(db);
28
+ db.prepare(
29
+ 'UPDATE lessons SET last_triggered = ?, trigger_count = COALESCE(trigger_count, 0) + 1 WHERE id = ?'
30
+ ).run(new Date().toISOString(), lessonId);
31
+ }
32
+
33
+ /**
34
+ * Score lesson staleness: 0 = fresh, 1 = completely stale.
35
+ */
36
+ function stalenessScore(lesson) {
37
+ const ref = lesson.last_triggered || lesson.timestamp;
38
+ if (!ref) return 1;
39
+ const ageDays = (Date.now() - new Date(ref).getTime()) / (1000 * 60 * 60 * 24);
40
+ return Math.min(1, ageDays / ARCHIVE_THRESHOLD_DAYS);
41
+ }
42
+
43
+ /**
44
+ * Get all stale lessons (not triggered in STALE_THRESHOLD_DAYS).
45
+ */
46
+ function findStaleLessons(db) {
47
+ migrateSchema(db);
48
+ const cutoff = new Date(Date.now() - STALE_THRESHOLD_DAYS * 86400000).toISOString();
49
+ return db.prepare(
50
+ `SELECT * FROM lessons
51
+ WHERE archived = 0 AND pruned = 0
52
+ AND (last_triggered IS NULL OR last_triggered < ?)
53
+ AND timestamp < ?
54
+ ORDER BY COALESCE(last_triggered, timestamp) ASC`
55
+ ).all(cutoff, cutoff);
56
+ }
57
+
58
+ /**
59
+ * Auto-archive lessons that haven't been triggered in ARCHIVE_THRESHOLD_DAYS.
60
+ * Returns { archived: number, reviewed: number }
61
+ */
62
+ function autoArchive(db) {
63
+ migrateSchema(db);
64
+ const cutoff = new Date(Date.now() - ARCHIVE_THRESHOLD_DAYS * 86400000).toISOString();
65
+ const result = db.prepare(
66
+ `UPDATE lessons SET archived = 1
67
+ WHERE archived = 0 AND pruned = 0
68
+ AND (last_triggered IS NULL OR last_triggered < ?)
69
+ AND timestamp < ?`
70
+ ).run(cutoff, cutoff);
71
+ return { archived: result.changes };
72
+ }
73
+
74
+ /**
75
+ * Restore a lesson from archive.
76
+ */
77
+ function restoreLesson(db, lessonId) {
78
+ migrateSchema(db);
79
+ db.prepare('UPDATE lessons SET archived = 0 WHERE id = ?').run(lessonId);
80
+ }
81
+
82
+ /**
83
+ * Get archived lessons for review.
84
+ */
85
+ function getArchivedLessons(db) {
86
+ migrateSchema(db);
87
+ return db.prepare(
88
+ 'SELECT * FROM lessons WHERE archived = 1 ORDER BY timestamp DESC'
89
+ ).all();
90
+ }
91
+
92
+ /**
93
+ * Generate a staleness report for the monthly review digest.
94
+ * Returns { stale: [...], archivable: [...], healthy: number }
95
+ */
96
+ function stalenessReport(db) {
97
+ migrateSchema(db);
98
+ const staleThresholdDate = new Date(Date.now() - STALE_THRESHOLD_DAYS * 86400000).toISOString();
99
+ const archiveThresholdDate = new Date(Date.now() - ARCHIVE_THRESHOLD_DAYS * 86400000).toISOString();
100
+
101
+ const total = db.prepare('SELECT COUNT(*) as count FROM lessons WHERE archived = 0 AND pruned = 0').get().count;
102
+ const stale = findStaleLessons(db);
103
+ const archivable = stale.filter((l) => {
104
+ const ref = l.last_triggered || l.timestamp;
105
+ return ref && ref < archiveThresholdDate;
106
+ });
107
+
108
+ return {
109
+ total,
110
+ healthy: total - stale.length,
111
+ stale: stale.map((l) => ({
112
+ id: l.id,
113
+ context: (l.context || '').slice(0, 80),
114
+ importance: l.importance,
115
+ daysSinceActive: Math.round((Date.now() - new Date(l.last_triggered || l.timestamp).getTime()) / 86400000),
116
+ triggerCount: l.trigger_count || 0,
117
+ })),
118
+ archivable: archivable.map((l) => ({
119
+ id: l.id,
120
+ context: (l.context || '').slice(0, 80),
121
+ importance: l.importance,
122
+ })),
123
+ };
124
+ }
125
+
126
+ module.exports = {
127
+ migrateSchema,
128
+ recordTrigger,
129
+ stalenessScore,
130
+ findStaleLessons,
131
+ autoArchive,
132
+ restoreLesson,
133
+ getArchivedLessons,
134
+ stalenessReport,
135
+ STALE_THRESHOLD_DAYS,
136
+ ARCHIVE_THRESHOLD_DAYS,
137
+ };