thumbgate 1.27.16 → 1.27.18

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 (165) hide show
  1. package/.claude-plugin/marketplace.json +85 -0
  2. package/.claude-plugin/plugin.json +3 -4
  3. package/.well-known/llms.txt +12 -33
  4. package/.well-known/mcp/server-card.json +8 -8
  5. package/README.md +30 -215
  6. package/adapters/chatgpt/openapi.yaml +1695 -0
  7. package/adapters/claude/.mcp.json +2 -2
  8. package/adapters/gemini/function-declarations.json +0 -1
  9. package/adapters/mcp/server-stdio.js +11 -263
  10. package/adapters/opencode/opencode.json +1 -1
  11. package/bench/thumbgate-bench.json +2 -2
  12. package/bin/cli.js +122 -1421
  13. package/bin/postinstall.js +27 -11
  14. package/config/gate-templates.json +0 -216
  15. package/config/gates/claim-verification.json +0 -12
  16. package/config/gates/default.json +2 -31
  17. package/config/github-about.json +2 -2
  18. package/config/mcp-allowlists.json +13 -23
  19. package/config/merge-quality-checks.json +1 -0
  20. package/config/model-candidates.json +6 -121
  21. package/openapi/openapi.yaml +0 -12
  22. package/package.json +98 -228
  23. package/public/blog.html +4 -4
  24. package/public/codex-plugin.html +20 -72
  25. package/public/compare.html +8 -31
  26. package/public/dashboard.html +166 -930
  27. package/public/federal.html +2 -2
  28. package/public/guide.html +9 -33
  29. package/public/index.html +116 -495
  30. package/public/learn.html +18 -191
  31. package/public/lessons.html +10 -168
  32. package/public/numbers.html +7 -7
  33. package/public/pro.html +11 -34
  34. package/scripts/agent-memory-lifecycle.js +0 -211
  35. package/scripts/agent-readiness.js +3 -167
  36. package/scripts/agent-reward-model.js +1 -53
  37. package/scripts/auto-promote-gates.js +10 -82
  38. package/scripts/auto-wire-hooks.js +0 -14
  39. package/scripts/billing.js +1 -93
  40. package/scripts/build-metadata.js +10 -50
  41. package/scripts/cli-feedback.js +6 -18
  42. package/scripts/cli-schema.js +0 -90
  43. package/scripts/cli-telemetry.js +1 -6
  44. package/scripts/commercial-offer.js +2 -82
  45. package/scripts/context-manager.js +6 -74
  46. package/scripts/dashboard.js +2 -68
  47. package/scripts/durability/step.js +3 -3
  48. package/scripts/export-databricks-bundle.js +1 -5
  49. package/scripts/export-dpo-pairs.js +2 -7
  50. package/scripts/feedback-loop.js +1 -123
  51. package/scripts/feedback-quality.js +0 -87
  52. package/scripts/filesystem-search.js +10 -35
  53. package/scripts/gate-stats.js +0 -89
  54. package/scripts/gates-engine.js +85 -1105
  55. package/scripts/gemini-embedding-policy.js +1 -2
  56. package/scripts/hook-runtime.js +14 -20
  57. package/scripts/hook-thumbgate-cache-updater.js +2 -18
  58. package/scripts/hybrid-feedback-context.js +7 -142
  59. package/scripts/lesson-inference.js +3 -8
  60. package/scripts/lesson-search.js +1 -17
  61. package/scripts/license.js +1 -0
  62. package/scripts/llm-client.js +9 -390
  63. package/scripts/local-model-profile.js +8 -15
  64. package/scripts/memory-scope-readiness.js +0 -159
  65. package/scripts/meta-agent-loop.js +0 -36
  66. package/scripts/operational-integrity.js +5 -39
  67. package/scripts/oss-pr-opportunity-scout.js +5 -35
  68. package/scripts/plausible-server-events.js +6 -9
  69. package/scripts/pro-local-dashboard.js +4 -4
  70. package/scripts/proxy-pointer-rag-guardrails.js +1 -42
  71. package/scripts/rate-limiter.js +13 -64
  72. package/scripts/secret-scanner.js +5 -44
  73. package/scripts/security-scanner.js +10 -260
  74. package/scripts/self-distill-agent.js +1 -3
  75. package/scripts/seo-gsd.js +2 -916
  76. package/scripts/statusline-cache-path.js +2 -17
  77. package/scripts/statusline-local-stats.js +1 -9
  78. package/scripts/statusline-meta.js +2 -28
  79. package/scripts/statusline.sh +4 -20
  80. package/scripts/telemetry-analytics.js +0 -357
  81. package/scripts/thompson-sampling.js +10 -31
  82. package/scripts/thumbgate-bench.js +1 -16
  83. package/scripts/thumbgate-search.js +19 -85
  84. package/scripts/tool-registry.js +1 -169
  85. package/scripts/vector-store.js +0 -45
  86. package/scripts/workflow-sentinel.js +53 -286
  87. package/scripts/workspace-evolver.js +2 -62
  88. package/src/api/server.js +290 -2578
  89. package/.claude/commands/dashboard.md +0 -15
  90. package/.claude/commands/thumbgate-blocked.md +0 -27
  91. package/.claude/commands/thumbgate-dashboard.md +0 -15
  92. package/.claude/commands/thumbgate-doctor.md +0 -30
  93. package/.claude/commands/thumbgate-guard.md +0 -36
  94. package/.claude/commands/thumbgate-protect.md +0 -30
  95. package/.claude/commands/thumbgate-rules.md +0 -30
  96. package/.well-known/agentic-verify.txt +0 -1
  97. package/adapters/gcp/dfcx-webhook-gate.js +0 -295
  98. package/adapters/letta/README.md +0 -41
  99. package/adapters/letta/thumbgate-letta-adapter.js +0 -133
  100. package/adapters/policy-engine/ethicore-guardian-client.js +0 -68
  101. package/adapters/policy-engine/thumbgate-policy-engine-adapter.js +0 -260
  102. package/bench/observability-eval-suite.json +0 -26
  103. package/bin/dashboard-cli.js +0 -7
  104. package/commands/dashboard.md +0 -15
  105. package/commands/thumbgate-dashboard.md +0 -15
  106. package/config/builtin-lessons.json +0 -23
  107. package/config/gate-classifier-routing.json +0 -98
  108. package/config/post-deploy-marketing-pages.json +0 -61
  109. package/public/about.html +0 -162
  110. package/public/agent-manager.html +0 -179
  111. package/public/agents-cost-savings.html +0 -153
  112. package/public/ai-malpractice-prevention.html +0 -818
  113. package/public/assets/brand/github-social-preview.png +0 -0
  114. package/public/assets/brand/thumbgate-icon-512.png +0 -0
  115. package/public/assets/brand/thumbgate-icon-pro-512.png +0 -0
  116. package/public/assets/brand/thumbgate-icon-team-512.png +0 -0
  117. package/public/assets/brand/thumbgate-logo-1200x360.png +0 -0
  118. package/public/assets/brand/thumbgate-logo-transparent.svg +0 -22
  119. package/public/assets/brand/thumbgate-mark-inline-v3.svg +0 -19
  120. package/public/assets/brand/thumbgate-mark-pro.svg +0 -23
  121. package/public/assets/brand/thumbgate-mark-team.svg +0 -26
  122. package/public/assets/brand/thumbgate-mark.svg +0 -21
  123. package/public/assets/brand/thumbgate-wordmark.svg +0 -20
  124. package/public/assets/claude-thumbgate-statusbar.svg +0 -8
  125. package/public/assets/codex-thumbgate-statusbar-test.svg +0 -9
  126. package/public/assets/legal-intake-control-flow.svg +0 -66
  127. package/public/brand/thumbgate-mark.svg +0 -19
  128. package/public/brand/thumbgate-og.svg +0 -16
  129. package/public/chatgpt-app.html +0 -330
  130. package/public/codex-enterprise.html +0 -123
  131. package/public/js/buyer-intent.js +0 -672
  132. package/public/pricing.html +0 -399
  133. package/scripts/action-receipts.js +0 -324
  134. package/scripts/activation-quickstart.js +0 -187
  135. package/scripts/agent-operations-planner.js +0 -621
  136. package/scripts/ai-component-inventory.js +0 -367
  137. package/scripts/async-eval-observability.js +0 -236
  138. package/scripts/audit.js +0 -65
  139. package/scripts/aws-blocks-guardrails.js +0 -228
  140. package/scripts/classifier-routing.js +0 -130
  141. package/scripts/dashboard-chat.js +0 -332
  142. package/scripts/feedback-aggregate.js +0 -281
  143. package/scripts/feedback-sanitizer.js +0 -105
  144. package/scripts/hob-pack.js +0 -591
  145. package/scripts/hook-stop-anti-claim.js +0 -301
  146. package/scripts/install-shim.js +0 -87
  147. package/scripts/mcp-oauth.js +0 -293
  148. package/scripts/noop-detect.js +0 -285
  149. package/scripts/omlx-smoke.js +0 -192
  150. package/scripts/parallel-workflow-orchestrator.js +0 -293
  151. package/scripts/plan-gate.js +0 -243
  152. package/scripts/plausible-domain-config.js +0 -99
  153. package/scripts/qa-scenario-planner.js +0 -136
  154. package/scripts/repeat-metric.js +0 -137
  155. package/scripts/secret-fixture-tokens.js +0 -61
  156. package/scripts/secret-redaction.js +0 -166
  157. package/scripts/self-harness-optimizer.js +0 -141
  158. package/scripts/self-healing-check.js +0 -193
  159. package/scripts/silent-failure-cluster.js +0 -531
  160. package/scripts/statusline-cache-read.js +0 -57
  161. package/scripts/sync-telemetry-from-prod.js +0 -374
  162. package/scripts/tool-contract-validator.js +0 -76
  163. package/scripts/trajectory-scorer.js +0 -63
  164. package/scripts/verify-marketing-pages-deployed.js +0 -195
  165. package/scripts/visitor-journey.js +0 -172
@@ -10,12 +10,12 @@
10
10
  <meta property="og:title" content="ThumbGate — The Numbers">
11
11
  <meta property="og:description" content="Generated first-party operational snapshot: configured gates, recorded interventions, and explicit zero-evidence caveats.">
12
12
  <meta property="og:type" content="website">
13
- <meta property="og:url" content="https://thumbgate.ai/numbers">
13
+ <meta property="og:url" content="https://thumbgate-production.up.railway.app/numbers">
14
14
  <meta name="twitter:card" content="summary_large_image">
15
- <link rel="canonical" href="https://thumbgate.ai/numbers">
15
+ <link rel="canonical" href="https://thumbgate-production.up.railway.app/numbers">
16
16
  <link rel="icon" type="image/png" href="/thumbgate-icon.png">
17
17
  <link rel="apple-touch-icon" href="/apple-touch-icon.png">
18
- <script defer data-domain="thumbgate.ai" src="https://plausible.io/js/script.js"></script>
18
+ <script defer data-domain="thumbgate-production.up.railway.app" src="https://plausible.io/js/script.js"></script>
19
19
 
20
20
  <script type="application/ld+json">
21
21
  {
@@ -25,8 +25,8 @@
25
25
  "alternateName": "thumbgate",
26
26
  "applicationCategory": "DeveloperApplication",
27
27
  "operatingSystem": "Cross-platform, Node.js >=18.18.0",
28
- "softwareVersion": "1.27.16",
29
- "url": "https://thumbgate.ai/numbers",
28
+ "softwareVersion": "1.20.0",
29
+ "url": "https://thumbgate-production.up.railway.app/numbers",
30
30
  "dateModified": "2026-05-07",
31
31
  "creator": {
32
32
  "@type": "Person",
@@ -46,7 +46,7 @@
46
46
  "@type": "Dataset",
47
47
  "name": "ThumbGate Operational Snapshot",
48
48
  "description": "First-party operational snapshot from the ThumbGate pre-action check runtime: configured checks, recorded block/warn events, estimated token savings from recorded blocks, and Bayes error rate when the sample supports it.",
49
- "url": "https://thumbgate.ai/numbers",
49
+ "url": "https://thumbgate-production.up.railway.app/numbers",
50
50
  "license": "https://opensource.org/licenses/MIT",
51
51
  "creator": {
52
52
  "@type": "Person",
@@ -202,7 +202,7 @@
202
202
  <main class="container">
203
203
  <h1>The Numbers</h1>
204
204
  <p class="subtitle">Generated first-party operational snapshot from the ThumbGate runtime. This is not customer traction, install volume, revenue, or proof that a configured gate has fired.</p>
205
- <div class="freshness">Updated: 2026-05-07 · Version 1.27.16</div>
205
+ <div class="freshness">Updated: 2026-05-07 · Version 1.20.0</div>
206
206
  <div class="truth-note"><strong>Read this first:</strong> configured checks are inventory. Recorded blocks and warnings are usage evidence. This snapshot currently reports 0 recorded hard-block event(s) and 0 recorded warning event(s).</div>
207
207
 
208
208
  <h2>Gate enforcement</h2>
package/public/pro.html CHANGED
@@ -11,12 +11,11 @@ __GOOGLE_SITE_VERIFICATION_META__
11
11
  <meta property="og:type" content="website">
12
12
  <meta property="og:url" content="__APP_ORIGIN__/pro">
13
13
  <link rel="canonical" href="__APP_ORIGIN__/pro">
14
- <link rel="alternate" type="text/markdown" title="ThumbGate LLM context" href="__APP_ORIGIN__/llm-context.md">
15
14
  <link rel="icon" type="image/png" href="/thumbgate-icon.png">
16
15
  <link rel="apple-touch-icon" href="/assets/brand/thumbgate-mark.svg">
17
16
  <meta property="og:image" content="/og.png">
18
17
 
19
- <script defer data-domain="thumbgate.ai" src="https://plausible.io/js/script.js"></script>
18
+ <script defer data-domain="thumbgate-production.up.railway.app" src="https://plausible.io/js/script.js"></script>
20
19
  __GA_BOOTSTRAP__
21
20
 
22
21
  <script>
@@ -37,7 +36,7 @@ __GA_BOOTSTRAP__
37
36
  <script type="application/ld+json">
38
37
  {
39
38
  "@context": "https://schema.org",
40
- "@type": "FAQPage", "mainEntity": [{ "@type": "Question", "name": "How is Pro different from the free install?", "acceptedAnswer": { "@type": "Answer", "text": "Free keeps local recall, checks, and MCP. Pro adds the personal dashboard, DPO export, auto-connect, and founder support." } }, { "@type": "Question", "name": "Does Pro require a cloud account?", "acceptedAnswer": { "@type": "Answer", "text": "No. Pro stays local-first; Enterprise is the hosted rollout lane for shared lessons, org visibility, and reviews." } }, { "@type": "Question", "name": "What happens after checkout?", "acceptedAnswer": { "@type": "Answer", "text": "You activate Pro, connect the local dashboard, and inspect blocked actions, lessons, and exports." } }, { "@type": "Question", "name": "When should I choose Enterprise instead of Pro?", "acceptedAnswer": { "@type": "Answer", "text": "Choose Enterprise when one correction needs to protect multiple developers or agents across shared repositories." } }]
39
+ "@type": "FAQPage", "mainEntity": [{ "@type": "Question", "name": "How is Pro different from the free install?", "acceptedAnswer": { "@type": "Answer", "text": "Free keeps local recall, checks, and MCP. Pro adds the personal dashboard, DPO export, auto-connect, and founder support." } }, { "@type": "Question", "name": "Does Pro require a cloud account?", "acceptedAnswer": { "@type": "Answer", "text": "No. Pro stays local-first; Team is the hosted rollout lane for shared lessons, org visibility, and reviews." } }, { "@type": "Question", "name": "What happens after checkout?", "acceptedAnswer": { "@type": "Answer", "text": "You activate Pro, connect the local dashboard, and inspect blocked actions, lessons, and exports." } }, { "@type": "Question", "name": "When should I choose Team instead of Pro?", "acceptedAnswer": { "@type": "Answer", "text": "Choose Team when one correction needs to protect multiple developers or agents across shared repositories." } }]
41
40
  }
42
41
  </script>
43
42
 
@@ -712,7 +711,7 @@ __GA_BOOTSTRAP__
712
711
  <body>
713
712
  <nav>
714
713
  <div class="container">
715
- <a href="/" class="nav-logo"><img src="/assets/brand/thumbgate-mark-inline-v3.svg" alt="ThumbGate" class="logo-mark" width="28" height="28"><span class="logo-text">ThumbGate Pro</span></a>
714
+ <a href="/" class="nav-logo"><img src="/assets/brand/thumbgate-mark-inline.svg" alt="ThumbGate" class="logo-mark" width="28" height="28"><span class="logo-text">ThumbGate Pro</span></a>
716
715
  <div class="nav-links">
717
716
  <a href="#why-pay">Why Pro</a>
718
717
  <a href="#proof">Proof</a>
@@ -731,7 +730,7 @@ __GA_BOOTSTRAP__
731
730
  <h1>Buy the operator loop that proves your AI agent stopped repeating the mistake.</h1>
732
731
  <p style="font-size:13px;opacity:0.8;margin-bottom:0.5rem;">Updated: <time datetime="2026-04-20">2026-04-20</time> · by <a href="https://github.com/IgorGanapolsky" style="color:inherit;">Igor Ganapolsky</a></p>
733
732
  <p>ThumbGate Pro is for one operator who already hit a repeated AI-agent failure and now needs proof: what was blocked, why it was blocked, and what changed before the next risky run.</p>
734
- <p>Start Pro when you want the local dashboard, DPO export, and a single proof lane for the repeated mistake you need to stop. Enterprise diagnostics and custom services are handled through intake, not this buyer path.</p>
733
+ <p>Start Pro when you want the local dashboard, DPO export, and a single proof lane for the repeated mistake you need to stop. Team diagnostics and custom services are handled through intake, not this buyer path.</p>
735
734
  <div class="hero-proof">
736
735
  <div class="proof-pill">Personal local dashboard</div>
737
736
  <div class="proof-pill">DPO export from real corrections</div>
@@ -816,28 +815,6 @@ __GA_BOOTSTRAP__
816
815
  </div>
817
816
  </section>
818
817
 
819
- <section class="section" id="deterministic-loop">
820
- <div class="container">
821
- <div class="section-label">Why Pro now</div>
822
- <h2 class="section-title">Black-box thumbs do not prove prevention. Pro gives the operator an audit loop.</h2>
823
- <p class="section-intro">Native rating buttons can tell a vendor that an answer felt wrong. ThumbGate Pro gives you the operational record: the correction, the lesson, the rule, the blocked tool call, and the export path.</p>
824
- <div class="grid-3">
825
- <div class="feature-card">
826
- <h3>Inspectable memory</h3>
827
- <p>Search the exact lesson that came from a thumbs-down and see whether it is still active, warning-only, or blocking.</p>
828
- </div>
829
- <div class="feature-card">
830
- <h3>Deterministic checks</h3>
831
- <p>The enforcement layer evaluates tool name, arguments, working directory, command shape, confidence, and required evidence before the action runs.</p>
832
- </div>
833
- <div class="feature-card">
834
- <h3>Exportable proof</h3>
835
- <p>Take the same correction history into JSONL, DPO export, review packets, and team rollout conversations instead of trusting hidden memory.</p>
836
- </div>
837
- </div>
838
- </div>
839
- </section>
840
-
841
818
  <section class="section" id="why-pay">
842
819
  <div class="container">
843
820
  <div class="section-label">Why operators pay</div>
@@ -924,11 +901,11 @@ __GA_BOOTSTRAP__
924
901
 
925
902
  <div class="pricing-sidebar">
926
903
  <div class="team-card">
927
- <div class="section-label" style="text-align:left;margin-bottom:8px;">When Enterprise is better</div>
904
+ <div class="section-label" style="text-align:left;margin-bottom:8px;">When Team is better</div>
928
905
  <h3>Need shared enforcement?</h3>
929
- <p>Choose Enterprise when one correction must protect multiple developers or agents across shared repositories, CI, approval policies, and audit trails. Enterprise is custom pricing, scoped after intake.</p>
906
+ <p>Choose Team when one correction must protect multiple developers or agents across shared repositories, CI, approval policies, and audit trails. Team is $49/seat/mo with a 3-seat minimum after qualification.</p>
930
907
  <div class="hero-actions" style="margin-top:18px;">
931
- <a class="btn-secondary" href="/#workflow-sprint-intake">Book an Enterprise Pilot Call</a>
908
+ <a class="btn-secondary" href="/#workflow-sprint-intake">Book a Team Pilot Call</a>
932
909
  </div>
933
910
  </div>
934
911
  <div class="team-card">
@@ -951,15 +928,15 @@ __GA_BOOTSTRAP__
951
928
  </div>
952
929
  <div class="faq-item">
953
930
  <button class="faq-q" type="button" onclick="toggleFaq(this)" onkeydown="handleFaqKeydown(event)" aria-expanded="false">Does Pro require a cloud account?</button>
954
- <div class="faq-a">No. Pro is still local-first for the individual operator lane. Enterprise is the hosted rollout lane for shared lessons, org visibility, and hosted review views.</div>
931
+ <div class="faq-a">No. Pro is still local-first for the individual operator lane. Team is the hosted rollout lane for shared lessons, org visibility, and hosted review views.</div>
955
932
  </div>
956
933
  <div class="faq-item">
957
934
  <button class="faq-q" type="button" onclick="toggleFaq(this)" onkeydown="handleFaqKeydown(event)" aria-expanded="false">What happens after checkout?</button>
958
935
  <div class="faq-a">You activate Pro, connect the personal local dashboard, and your running agents can appear automatically so you can inspect blocked actions, active lessons, and exportable DPO pairs without adding a separate cloud dashboard dependency.</div>
959
936
  </div>
960
937
  <div class="faq-item">
961
- <button class="faq-q" type="button" onclick="toggleFaq(this)" onkeydown="handleFaqKeydown(event)" aria-expanded="false">When should I choose Enterprise instead of Pro?</button>
962
- <div class="faq-a">Choose Enterprise when one thumbs-down should protect multiple people or agents across shared repositories, or when you need shared hosted lessons, org dashboard visibility, and a workflow hardening pilot with rollout review views.</div>
938
+ <button class="faq-q" type="button" onclick="toggleFaq(this)" onkeydown="handleFaqKeydown(event)" aria-expanded="false">When should I choose Team instead of Pro?</button>
939
+ <div class="faq-a">Choose Team when one thumbs-down should protect multiple people or agents across shared repositories, or when you need shared hosted lessons, org dashboard visibility, and a workflow hardening pilot with rollout review views.</div>
963
940
  </div>
964
941
  </div>
965
942
  </div>
@@ -987,7 +964,7 @@ __GA_BOOTSTRAP__
987
964
  <a href="__VERIFICATION_URL__" target="_blank" rel="noopener">Verification Evidence</a>
988
965
  <a href="https://github.com/IgorGanapolsky/ThumbGate" target="_blank" rel="noopener">GitHub</a>
989
966
  </div>
990
- <div class="footer-copy">ThumbGate Pro for individual operators. Enterprise stays intake-first.</div>
967
+ <div class="footer-copy">ThumbGate Pro for individual operators. Team stays intake-first.</div>
991
968
  </div>
992
969
  </footer>
993
970
 
@@ -2,40 +2,6 @@
2
2
  'use strict';
3
3
 
4
4
  const MEMORY_TYPES = new Set(['episodic', 'semantic', 'procedural', 'preference', 'working']);
5
- const MEMORY_SCOPES = new Set(['task', 'session', 'user', 'project', 'org']);
6
- const HIGH_RISK_TERMS = new Set([
7
- 'billing',
8
- 'checkout',
9
- 'compliance',
10
- 'credential',
11
- 'data-loss',
12
- 'deploy',
13
- 'deployment',
14
- 'git',
15
- 'payment',
16
- 'production',
17
- 'release',
18
- 'secret',
19
- 'security',
20
- 'stripe',
21
- 'verification',
22
- ]);
23
- const KNOWN_ENTITY_PATTERNS = [
24
- ['Claude Code', /\bclaude\s+code\b/i, 'agent'],
25
- ['Codex', /\bcodex\b/i, 'agent'],
26
- ['Cursor', /\bcursor\b/i, 'agent'],
27
- ['Gemini CLI', /\bgemini\s+cli\b/i, 'agent'],
28
- ['MCP', /\bmcp\b/i, 'protocol'],
29
- ['Stripe', /\bstripe\b/i, 'service'],
30
- ['GitHub', /\bgithub\b|\bgh\s+/i, 'service'],
31
- ['Railway', /\brailway\b/i, 'service'],
32
- ['Plausible', /\bplausible\b/i, 'service'],
33
- ['PostHog', /\bposthog\b/i, 'service'],
34
- ['SQLite', /\bsqlite\b|\bfts5\b/i, 'storage'],
35
- ['LanceDB', /\blancedb\b/i, 'storage'],
36
- ['Docker', /\bdocker\b/i, 'runtime'],
37
- ['npm', /\bnpm\b|\bnpx\b/i, 'runtime'],
38
- ];
39
5
 
40
6
  function normalizeText(value) {
41
7
  if (value === undefined || value === null) return '';
@@ -47,178 +13,6 @@ function normalizeMemoryType(value) {
47
13
  return MEMORY_TYPES.has(normalized) ? normalized : 'episodic';
48
14
  }
49
15
 
50
- function tokenize(value) {
51
- return normalizeText(value)
52
- .toLowerCase()
53
- .split(/[^a-z0-9_.:/-]+/)
54
- .filter(Boolean);
55
- }
56
-
57
- function uniqueByName(entities) {
58
- const seen = new Set();
59
- return entities.filter((entity) => {
60
- const key = normalizeText(entity.name).toLowerCase();
61
- if (!key || seen.has(key)) return false;
62
- seen.add(key);
63
- return true;
64
- });
65
- }
66
-
67
- function collectMemoryText(memory = {}) {
68
- return [
69
- memory.title,
70
- memory.content,
71
- memory.context,
72
- memory.whatWentWrong,
73
- memory.whatToChange,
74
- memory.whatWorked,
75
- memory.domain,
76
- memory.skill,
77
- Array.isArray(memory.tags) ? memory.tags.join(' ') : memory.tags,
78
- ].filter(Boolean).join(' ');
79
- }
80
-
81
- function extractMemoryEntities(memory = {}) {
82
- const text = collectMemoryText(memory);
83
- const entities = [];
84
-
85
- for (const [name, pattern, type] of KNOWN_ENTITY_PATTERNS) {
86
- if (pattern.test(text)) entities.push({ name, type });
87
- }
88
-
89
- const commandMatches = text.match(/`([^`]+)`/g) || [];
90
- for (const match of commandMatches) {
91
- const command = match.slice(1, -1).trim();
92
- if (/^(git|npm|npx|node|gh|curl|docker|python|pytest|stripe)\b/i.test(command)) {
93
- entities.push({ name: command, type: 'command' });
94
- } else if (/[./-]/.test(command)) {
95
- entities.push({ name: command, type: 'path' });
96
- }
97
- }
98
-
99
- const pathMatches = text.match(/\b(?:[a-z0-9_-]+\/)+[a-z0-9_.-]+\b/gi) || [];
100
- for (const filePath of pathMatches.slice(0, 8)) {
101
- entities.push({ name: filePath, type: 'path' });
102
- }
103
-
104
- return uniqueByName(entities).slice(0, 16);
105
- }
106
-
107
- function inferMemoryScope(memory = {}) {
108
- const explicit = normalizeText(memory.scope || memory.memoryScope).toLowerCase();
109
- if (MEMORY_SCOPES.has(explicit)) return explicit;
110
-
111
- const text = collectMemoryText(memory).toLowerCase();
112
- const tags = new Set(Array.isArray(memory.tags) ? memory.tags.map((tag) => normalizeText(tag).toLowerCase()) : []);
113
-
114
- if (tags.has('preference') || /\b(prefer|style|tone|my preference|user preference)\b/.test(text)) return 'user';
115
- if (tags.has('org') || tags.has('team') || /\b(enterprise|seat|team|shared|org|compliance|policy|approval)\b/.test(text)) return 'org';
116
- if (tags.has('repo') || tags.has('project') || tags.has('release') || tags.has('deployment')
117
- || /\b(repo|repository|branch|ci|pull request|github|deploy|production|release|publish)\b/.test(text)) return 'project';
118
- if (tags.has('session') || /\b(this session|current session|today|right now)\b/.test(text)) return 'session';
119
- return 'task';
120
- }
121
-
122
- function scoreMemoryDecay(memory = {}, options = {}) {
123
- const nowMs = options.now ? new Date(options.now).getTime() : Date.now();
124
- const timestampMs = memory.timestamp ? new Date(memory.timestamp).getTime() : NaN;
125
- const ageDays = Number.isFinite(timestampMs)
126
- ? Math.max(0, (nowMs - timestampMs) / (1000 * 60 * 60 * 24))
127
- : null;
128
- const textTokens = new Set(tokenize(collectMemoryText(memory)));
129
- const tags = Array.isArray(memory.tags) ? memory.tags.map((tag) => normalizeText(tag).toLowerCase()) : [];
130
- const highRisk = tags.some((tag) => HIGH_RISK_TERMS.has(tag))
131
- || [...textTokens].some((token) => HIGH_RISK_TERMS.has(token))
132
- || ['critical', 'high'].includes(normalizeText(memory.importance).toLowerCase());
133
-
134
- if (highRisk) {
135
- return {
136
- state: 'sticky',
137
- ageDays,
138
- score: 1,
139
- reason: 'high-risk memories stay retrievable until explicitly retired',
140
- };
141
- }
142
- if (ageDays === null) {
143
- return {
144
- state: 'review',
145
- ageDays,
146
- score: 0.6,
147
- reason: 'memory has no timestamp, so it needs review before durable promotion',
148
- };
149
- }
150
- if (ageDays > 180) {
151
- return {
152
- state: 'archive_candidate',
153
- ageDays,
154
- score: 0.2,
155
- reason: 'old low-risk memory should be consolidated or archived',
156
- };
157
- }
158
- if (ageDays > 60) {
159
- return {
160
- state: 'review',
161
- ageDays,
162
- score: 0.55,
163
- reason: 'older low-risk memory should be refreshed before it dominates recall',
164
- };
165
- }
166
- return {
167
- state: 'active',
168
- ageDays,
169
- score: 0.85,
170
- reason: 'recent memory remains eligible for recall',
171
- };
172
- }
173
-
174
- function scoreHybridMemoryMatch(query, memory = {}, options = {}) {
175
- const queryTokens = new Set(tokenize(query));
176
- const memoryTokens = new Set(tokenize(collectMemoryText(memory)));
177
- const queryText = normalizeText(query).toLowerCase();
178
- const memoryText = collectMemoryText(memory).toLowerCase();
179
- const memoryEntities = extractMemoryEntities(memory);
180
- const queryEntityNames = extractMemoryEntities({ content: query }).map((entity) => entity.name.toLowerCase());
181
-
182
- let lexicalMatches = 0;
183
- for (const token of queryTokens) {
184
- if (memoryTokens.has(token)) lexicalMatches++;
185
- }
186
- const lexicalScore = queryTokens.size > 0 ? lexicalMatches / queryTokens.size : 0;
187
- const phraseScore = queryText && memoryText.includes(queryText) ? 0.35 : 0;
188
- const entityMatches = memoryEntities.filter((entity) => queryEntityNames.includes(entity.name.toLowerCase()));
189
- const entityScore = queryEntityNames.length > 0 ? entityMatches.length / queryEntityNames.length : 0;
190
- const decay = scoreMemoryDecay(memory, options);
191
- const lifecycleScore = decay.state === 'archive_candidate' ? -0.15 : decay.state === 'sticky' ? 0.12 : 0;
192
- const score = lexicalScore + phraseScore + (entityScore * 0.45) + lifecycleScore;
193
-
194
- return {
195
- score: Number(Math.max(0, score).toFixed(4)),
196
- lexicalScore: Number(lexicalScore.toFixed(4)),
197
- entityScore: Number(entityScore.toFixed(4)),
198
- matchedEntities: entityMatches,
199
- decayState: decay.state,
200
- };
201
- }
202
-
203
- function buildMemoryLifecycleView(memory = {}, options = {}) {
204
- const scope = inferMemoryScope(memory);
205
- const entities = extractMemoryEntities(memory);
206
- const decay = scoreMemoryDecay(memory, options);
207
- const retrieval = scoreHybridMemoryMatch(options.query || '', memory, options);
208
-
209
- return {
210
- scope,
211
- entities,
212
- decay,
213
- retrievalHints: {
214
- hybridScore: retrieval.score,
215
- lexicalScore: retrieval.lexicalScore,
216
- entityScore: retrieval.entityScore,
217
- matchedEntities: retrieval.matchedEntities,
218
- },
219
- };
220
- }
221
-
222
16
  function buildMemoryLifecyclePolicy(input = {}) {
223
17
  return {
224
18
  generatedAt: normalizeText(input.generatedAt) || new Date().toISOString(),
@@ -297,11 +91,6 @@ function evaluateMemoryPromotion(memory = {}, policy = buildMemoryLifecyclePolic
297
91
 
298
92
  module.exports = {
299
93
  buildMemoryLifecyclePolicy,
300
- buildMemoryLifecycleView,
301
94
  evaluateMemoryPromotion,
302
- extractMemoryEntities,
303
- inferMemoryScope,
304
95
  normalizeMemoryType,
305
- scoreHybridMemoryMatch,
306
- scoreMemoryDecay,
307
96
  };
@@ -2,7 +2,6 @@
2
2
  'use strict';
3
3
 
4
4
  const fs = require('fs');
5
- const os = require('os');
6
5
  const path = require('path');
7
6
  const {
8
7
  getActiveMcpProfile,
@@ -95,26 +94,9 @@ function detectRuntimeIsolation() {
95
94
  };
96
95
  }
97
96
 
98
- function findProjectRoot(startDir = process.cwd()) {
99
- try {
100
- let curr = path.resolve(startDir);
101
- while (true) {
102
- const indicators = ['AGENTS.md', 'CLAUDE.md', 'GEMINI.md', '.mcp.json', '.git'];
103
- if (indicators.some((f) => fs.existsSync(path.join(curr, f)))) {
104
- return curr;
105
- }
106
- const parent = path.dirname(curr);
107
- if (parent === curr) break;
108
- curr = parent;
109
- }
110
- } catch (_) { /* fallback to startDir */ }
111
- return startDir;
112
- }
113
-
114
- function collectBootstrapFiles(projectRoot) {
115
- const effectiveRoot = projectRoot || findProjectRoot();
97
+ function collectBootstrapFiles(projectRoot = PROJECT_ROOT) {
116
98
  const files = BOOTSTRAP_FILES.map((file) => {
117
- const absolutePath = path.join(effectiveRoot, file.path);
99
+ const absolutePath = path.join(projectRoot, file.path);
118
100
  return {
119
101
  id: file.id,
120
102
  path: file.path,
@@ -136,7 +118,7 @@ function collectBootstrapFiles(projectRoot) {
136
118
  missingRequired,
137
119
  recommendation: missingRequired.length === 0
138
120
  ? 'Bootstrap context is present.'
139
- : `Add missing bootstrap files to project root (${effectiveRoot}): ${missingRequired.join(', ')}`,
121
+ : `Add missing bootstrap files: ${missingRequired.join(', ')}`,
140
122
  };
141
123
  }
142
124
 
@@ -166,141 +148,6 @@ function summarizePermissionTier(profileName = getActiveMcpProfile()) {
166
148
  };
167
149
  }
168
150
 
169
- function flattenHookCommands(value, commands = []) {
170
- if (!value) return commands;
171
- if (typeof value === 'string') {
172
- commands.push(value);
173
- return commands;
174
- }
175
- if (Array.isArray(value)) {
176
- value.forEach((item) => flattenHookCommands(item, commands));
177
- return commands;
178
- }
179
- if (typeof value === 'object') {
180
- if (typeof value.command === 'string') commands.push(value.command);
181
- Object.entries(value)
182
- .filter(([key]) => key !== 'command')
183
- .forEach(([, item]) => flattenHookCommands(item, commands));
184
- }
185
- return commands;
186
- }
187
-
188
- function readJsonIfExists(filePath) {
189
- try {
190
- if (!fs.existsSync(filePath)) return null;
191
- return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
192
- } catch (_) {
193
- return null;
194
- }
195
- }
196
-
197
- function detectHookAutoCapture(projectRoot) {
198
- const effectiveRoot = projectRoot || findProjectRoot();
199
- const settingsFiles = ['.claude/settings.json', '.claude/settings.local.json']
200
- .map((file) => path.join(effectiveRoot, file))
201
- .filter((file) => fs.existsSync(file));
202
- const commands = settingsFiles.flatMap((file) => {
203
- const parsed = readJsonIfExists(file);
204
- return flattenHookCommands(parsed && parsed.hooks && parsed.hooks.UserPromptSubmit);
205
- });
206
- const wiredCommands = commands.filter((command) => /hook-auto-capture(\.sh)?\b/.test(command));
207
-
208
- return {
209
- ready: wiredCommands.length > 0,
210
- settingsFiles: settingsFiles.map((file) => path.relative(effectiveRoot, file)),
211
- wiredCommands,
212
- recommendation: wiredCommands.length > 0
213
- ? 'UserPromptSubmit auto-capture hook is wired for typed thumbs feedback.'
214
- : 'Run `npx thumbgate init --wire-hooks` so typed thumbs up/down feedback is captured automatically.',
215
- };
216
- }
217
-
218
- function probeFeedbackCapture(projectRoot) {
219
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'thumbgate-doctor-feedback-'));
220
- const previousFeedbackDir = process.env.THUMBGATE_FEEDBACK_DIR;
221
- const previousProjectDir = process.env.THUMBGATE_PROJECT_DIR;
222
- const previousNoNudge = process.env.THUMBGATE_NO_NUDGE;
223
-
224
- process.env.THUMBGATE_FEEDBACK_DIR = tempDir;
225
- // Keep this probe isolated. Direct project scope intentionally overrides
226
- // THUMBGATE_FEEDBACK_DIR in the runtime resolver, which is correct for real
227
- // project writes but wrong for a doctor self-test.
228
- delete process.env.THUMBGATE_PROJECT_DIR;
229
- process.env.THUMBGATE_NO_NUDGE = '1';
230
-
231
- try {
232
- const {
233
- captureFeedback,
234
- getFeedbackPaths,
235
- readJSONL,
236
- } = require('./feedback-loop');
237
- const result = captureFeedback({
238
- signal: 'down',
239
- context: 'doctor feedback loop probe: prove thumbs feedback persists to durable logs',
240
- whatWentWrong: 'A runtime claimed thumbs feedback worked without verifying durable capture',
241
- whatToChange: 'Run thumbgate doctor or feedback-self-test before claiming the loop is wired',
242
- tags: 'doctor,feedback-loop-probe',
243
- });
244
- const paths = getFeedbackPaths();
245
- const feedbackId = result.feedbackEvent && result.feedbackEvent.id;
246
- const memoryId = result.memoryRecord && result.memoryRecord.id;
247
- const feedbackStored = Boolean(feedbackId && readJSONL(paths.FEEDBACK_LOG_PATH).some((row) => row.id === feedbackId));
248
- const memoryStored = Boolean(memoryId && readJSONL(paths.MEMORY_LOG_PATH).some((row) => row.id === memoryId));
249
-
250
- return {
251
- ready: Boolean(result.accepted && feedbackStored && memoryStored),
252
- accepted: Boolean(result.accepted),
253
- feedbackStored,
254
- memoryStored,
255
- feedbackId: feedbackId || null,
256
- memoryId: memoryId || null,
257
- mode: 'isolated-temp-store',
258
- feedbackDir: paths.FEEDBACK_DIR,
259
- recommendation: (result.accepted && feedbackStored && memoryStored)
260
- ? 'Feedback capture writes durable feedback and memory records.'
261
- : 'Feedback capture did not persist both feedback and memory records; run `npx thumbgate feedback-self-test --json`.',
262
- };
263
- } catch (error) {
264
- return {
265
- ready: false,
266
- accepted: false,
267
- feedbackStored: false,
268
- memoryStored: false,
269
- feedbackId: null,
270
- memoryId: null,
271
- mode: 'isolated-temp-store',
272
- feedbackDir: tempDir,
273
- error: error && error.message ? error.message : String(error),
274
- recommendation: 'Feedback capture probe failed; run `npx thumbgate feedback-self-test --json` for details.',
275
- };
276
- } finally {
277
- if (previousFeedbackDir === undefined) delete process.env.THUMBGATE_FEEDBACK_DIR;
278
- else process.env.THUMBGATE_FEEDBACK_DIR = previousFeedbackDir;
279
- if (previousProjectDir === undefined) delete process.env.THUMBGATE_PROJECT_DIR;
280
- else process.env.THUMBGATE_PROJECT_DIR = previousProjectDir;
281
- if (previousNoNudge === undefined) delete process.env.THUMBGATE_NO_NUDGE;
282
- else process.env.THUMBGATE_NO_NUDGE = previousNoNudge;
283
- try { fs.rmSync(tempDir, { recursive: true, force: true }); } catch (_) { /* ignore cleanup failure */ }
284
- }
285
- }
286
-
287
- function assessFeedbackLoop(projectRoot) {
288
- const hookAutoCapture = detectHookAutoCapture(projectRoot);
289
- const captureProbe = probeFeedbackCapture(projectRoot);
290
- const ready = hookAutoCapture.ready && captureProbe.ready;
291
-
292
- return {
293
- ready,
294
- automatic: hookAutoCapture.ready,
295
- durableCapture: captureProbe.ready,
296
- hookAutoCapture,
297
- captureProbe,
298
- recommendation: ready
299
- ? 'Typed thumbs feedback is automatically detected and durable capture is verified.'
300
- : 'Install hooks and verify capture before claiming thumbs feedback improves future runs.',
301
- };
302
- }
303
-
304
151
  function generateAgentReadinessReport({
305
152
  projectRoot = PROJECT_ROOT,
306
153
  mcpProfile = null,
@@ -308,13 +155,11 @@ function generateAgentReadinessReport({
308
155
  const runtime = detectRuntimeIsolation();
309
156
  const bootstrap = collectBootstrapFiles(projectRoot);
310
157
  const permissions = summarizePermissionTier(mcpProfile || getActiveMcpProfile());
311
- const feedbackLoop = assessFeedbackLoop(projectRoot);
312
158
 
313
159
  const warnings = [];
314
160
  if (!runtime.isolated) warnings.push(runtime.recommendation);
315
161
  if (!bootstrap.ready) warnings.push(bootstrap.recommendation);
316
162
  if (!permissions.ready) warnings.push(permissions.recommendation);
317
- if (!feedbackLoop.ready) warnings.push(feedbackLoop.recommendation);
318
163
 
319
164
  return {
320
165
  generatedAt: new Date().toISOString(),
@@ -323,12 +168,10 @@ function generateAgentReadinessReport({
323
168
  runtime,
324
169
  bootstrap,
325
170
  permissions,
326
- feedbackLoop,
327
171
  articleAlignment: {
328
172
  runtimeIsolation: runtime.isolated,
329
173
  contextConditioning: bootstrap.ready,
330
174
  permissionEnvelope: permissions.ready,
331
- feedbackLoop: feedbackLoop.ready,
332
175
  },
333
176
  warnings,
334
177
  };
@@ -348,10 +191,6 @@ function reportToText(report) {
348
191
  lines.push(`Permissions: ${report.permissions.profile} (${report.permissions.tier})`);
349
192
  lines.push(` Write-capable tools: ${report.permissions.writeCapableTools.length}`);
350
193
  lines.push(` Recommendation: ${report.permissions.recommendation}`);
351
- lines.push(`Feedback loop: ${report.feedbackLoop.ready ? 'ready' : 'needs_attention'}`);
352
- lines.push(` Auto-capture hook: ${report.feedbackLoop.automatic ? 'wired' : 'missing'}`);
353
- lines.push(` Durable capture: ${report.feedbackLoop.durableCapture ? 'verified' : 'failed'}`);
354
- lines.push(` Recommendation: ${report.feedbackLoop.recommendation}`);
355
194
 
356
195
  if (report.warnings.length > 0) {
357
196
  lines.push('');
@@ -370,9 +209,6 @@ module.exports = {
370
209
  detectRuntimeIsolation,
371
210
  collectBootstrapFiles,
372
211
  summarizePermissionTier,
373
- detectHookAutoCapture,
374
- probeFeedbackCapture,
375
- assessFeedbackLoop,
376
212
  generateAgentReadinessReport,
377
213
  reportToText,
378
214
  };