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.
- package/.claude-plugin/README.md +134 -0
- package/.claude-plugin/bundle/icon.png +0 -0
- package/.claude-plugin/bundle/icon.svg +18 -0
- package/.claude-plugin/bundle/server/index.js +24 -0
- package/.claude-plugin/marketplace.json +36 -0
- package/.claude-plugin/plugin.json +21 -0
- package/.well-known/mcp/server-card.json +231 -0
- package/LICENSE +21 -0
- package/README.md +375 -0
- package/adapters/README.md +9 -0
- package/adapters/amp/skills/rlhf-feedback/SKILL.md +22 -0
- package/adapters/chatgpt/INSTALL.md +83 -0
- package/adapters/chatgpt/openapi.yaml +1281 -0
- package/adapters/claude/.mcp.json +14 -0
- package/adapters/codex/config.toml +9 -0
- package/adapters/gemini/function-declarations.json +224 -0
- package/adapters/mcp/server-stdio.js +788 -0
- package/adapters/opencode/opencode.json +15 -0
- package/bin/cli.js +1483 -0
- package/bin/memory.sh +64 -0
- package/bin/obsidian-sync.sh +20 -0
- package/bin/postinstall.js +37 -0
- package/config/build-metadata.json +4 -0
- package/config/e2e-critical-flows.json +45 -0
- package/config/gate-templates.json +77 -0
- package/config/gates/claim-verification.json +29 -0
- package/config/gates/computer-use.json +39 -0
- package/config/gates/default.json +117 -0
- package/config/github-about.json +25 -0
- package/config/mcp-allowlists.json +135 -0
- package/config/model-tiers.json +33 -0
- package/config/partner-routing.json +132 -0
- package/config/policy-bundles/constrained-v1.json +64 -0
- package/config/policy-bundles/default-v1.json +91 -0
- package/config/rubrics/default-v1.json +52 -0
- package/config/skill-packs/react-testing.json +23 -0
- package/config/skill-packs/stripe-integration/references/api-spec.json +1 -0
- package/config/skill-packs/stripe-integration/references/webhook-guide.md +3 -0
- package/config/skill-specs/pr-reviewer.json +9 -0
- package/config/skill-specs/release-status.json +9 -0
- package/config/skill-specs/ticket-triage.json +9 -0
- package/config/subagent-profiles.json +32 -0
- package/config/tessl-tiles.json +29 -0
- package/config/thumbgate-settings.managed.json +12 -0
- package/openapi/openapi.yaml +1281 -0
- package/package.json +286 -0
- package/plugins/amp-skill/INSTALL.md +52 -0
- package/plugins/amp-skill/SKILL.md +64 -0
- package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +22 -0
- package/plugins/claude-codex-bridge/.mcp.json +12 -0
- package/plugins/claude-codex-bridge/INSTALL.md +43 -0
- package/plugins/claude-codex-bridge/README.md +46 -0
- package/plugins/claude-codex-bridge/scripts/codex-bridge.js +288 -0
- package/plugins/claude-codex-bridge/skills/adversarial-review/SKILL.md +24 -0
- package/plugins/claude-codex-bridge/skills/result/SKILL.md +22 -0
- package/plugins/claude-codex-bridge/skills/review/SKILL.md +28 -0
- package/plugins/claude-codex-bridge/skills/second-pass/SKILL.md +27 -0
- package/plugins/claude-codex-bridge/skills/setup/SKILL.md +21 -0
- package/plugins/claude-codex-bridge/skills/status/SKILL.md +19 -0
- package/plugins/claude-skill/INSTALL.md +55 -0
- package/plugins/claude-skill/SKILL.md +46 -0
- package/plugins/codex-profile/.codex-plugin/plugin.json +43 -0
- package/plugins/codex-profile/.mcp.json +12 -0
- package/plugins/codex-profile/AGENTS.md +20 -0
- package/plugins/codex-profile/INSTALL.md +66 -0
- package/plugins/codex-profile/README.md +37 -0
- package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +23 -0
- package/plugins/cursor-marketplace/CHANGELOG.md +30 -0
- package/plugins/cursor-marketplace/LICENSE +21 -0
- package/plugins/cursor-marketplace/README.md +124 -0
- package/plugins/cursor-marketplace/agents/reliability-reviewer.md +31 -0
- package/plugins/cursor-marketplace/assets/logo-400x400.png +0 -0
- package/plugins/cursor-marketplace/commands/capture-feedback.md +33 -0
- package/plugins/cursor-marketplace/commands/check-gates.md +25 -0
- package/plugins/cursor-marketplace/commands/show-lessons.md +27 -0
- package/plugins/cursor-marketplace/hooks/hooks.json +10 -0
- package/plugins/cursor-marketplace/mcp.json +12 -0
- package/plugins/cursor-marketplace/rules/feedback-capture.mdc +34 -0
- package/plugins/cursor-marketplace/rules/pre-action-gates.mdc +30 -0
- package/plugins/cursor-marketplace/rules/session-continuity.mdc +28 -0
- package/plugins/cursor-marketplace/scripts/gate-check.sh +11 -0
- package/plugins/cursor-marketplace/skills/capture-feedback/SKILL.md +47 -0
- package/plugins/cursor-marketplace/skills/prevention-rules/SKILL.md +31 -0
- package/plugins/cursor-marketplace/skills/recall-context/SKILL.md +30 -0
- package/plugins/cursor-marketplace/skills/search-lessons/SKILL.md +33 -0
- package/plugins/gemini-extension/INSTALL.md +92 -0
- package/plugins/gemini-extension/gemini_prompt.txt +14 -0
- package/plugins/gemini-extension/tool_contract.json +45 -0
- package/plugins/opencode-profile/INSTALL.md +57 -0
- package/public/assets/instagram-card.png +0 -0
- package/public/assets/tiktok-agent-memory.mp4 +0 -0
- package/public/blog.html +400 -0
- package/public/dashboard.html +1093 -0
- package/public/guide.html +317 -0
- package/public/index.html +1195 -0
- package/public/learn/agent-harness-pattern.html +180 -0
- package/public/learn/ai-agent-persistent-memory.html +202 -0
- package/public/learn/learn.css +45 -0
- package/public/learn/mcp-pre-action-gates-explained.html +172 -0
- package/public/learn/stop-ai-agent-force-push.html +134 -0
- package/public/learn/vibe-coding-safety-net.html +142 -0
- package/public/learn.html +213 -0
- package/public/lessons.html +650 -0
- package/public/vercel.json +8 -0
- package/scripts/__pycache__/train_from_feedback.cpython-314.pyc +0 -0
- package/scripts/a2ui-engine.js +73 -0
- package/scripts/access-anomaly-detector.js +12 -0
- package/scripts/adk-consolidator.js +266 -0
- package/scripts/agent-readiness.js +220 -0
- package/scripts/agent-security-hardening.js +227 -0
- package/scripts/agentic-data-pipeline.js +847 -0
- package/scripts/analytics-report.js +328 -0
- package/scripts/analytics-window.js +158 -0
- package/scripts/async-job-runner.js +1001 -0
- package/scripts/audit-trail.js +398 -0
- package/scripts/auto-promote-gates.js +293 -0
- package/scripts/auto-wire-hooks.js +316 -0
- package/scripts/autonomous-sales-agent.js +39 -0
- package/scripts/autoresearch-runner.js +216 -0
- package/scripts/background-agent-governance.js +237 -0
- package/scripts/behavioral-extraction.js +93 -0
- package/scripts/belief-update.js +84 -0
- package/scripts/billing.js +2438 -0
- package/scripts/bot-detector.js +50 -0
- package/scripts/budget-guard.js +173 -0
- package/scripts/build-claude-mcpb.js +189 -0
- package/scripts/build-metadata.js +97 -0
- package/scripts/check-congruence.js +322 -0
- package/scripts/cli-feedback.js +135 -0
- package/scripts/cli-telemetry.js +87 -0
- package/scripts/cloudflare-dynamic-sandbox.js +315 -0
- package/scripts/code-reasoning.js +350 -0
- package/scripts/codegraph-context.js +466 -0
- package/scripts/commercial-offer.js +56 -0
- package/scripts/computer-use-firewall.js +250 -0
- package/scripts/context-engine.js +694 -0
- package/scripts/contextfs.js +1287 -0
- package/scripts/conversation-context.js +119 -0
- package/scripts/creator-campaigns.js +239 -0
- package/scripts/daemon-manager.js +108 -0
- package/scripts/daily-digest.js +11 -0
- package/scripts/dashboard-render-spec.js +395 -0
- package/scripts/dashboard.js +1058 -0
- package/scripts/data-governance.js +173 -0
- package/scripts/delegation-runtime.js +900 -0
- package/scripts/deploy-gcp.sh +44 -0
- package/scripts/deploy-policy.js +231 -0
- package/scripts/disagreement-mining.js +315 -0
- package/scripts/dispatch-brief.js +159 -0
- package/scripts/distribution-surfaces.js +44 -0
- package/scripts/dpo-optimizer.js +206 -0
- package/scripts/ensure-repo-bootstrap.js +129 -0
- package/scripts/ephemeral-agent-store.js +219 -0
- package/scripts/eval-harness.js +56 -0
- package/scripts/evolution-state.js +241 -0
- package/scripts/experiment-tracker.js +267 -0
- package/scripts/export-databricks-bundle.js +242 -0
- package/scripts/export-dpo-pairs.js +344 -0
- package/scripts/export-kto-pairs.js +309 -0
- package/scripts/export-training.js +450 -0
- package/scripts/failure-diagnostics.js +558 -0
- package/scripts/feedback-attribution.js +313 -0
- package/scripts/feedback-fallback.js +110 -0
- package/scripts/feedback-history-distiller.js +391 -0
- package/scripts/feedback-inbox-read.js +162 -0
- package/scripts/feedback-loop.js +1887 -0
- package/scripts/feedback-paths.js +145 -0
- package/scripts/feedback-quality.js +139 -0
- package/scripts/feedback-root-consolidator.js +238 -0
- package/scripts/feedback-schema.js +426 -0
- package/scripts/feedback-session.js +286 -0
- package/scripts/feedback-to-memory.js +185 -0
- package/scripts/feedback-to-rules.js +164 -0
- package/scripts/filesystem-search.js +405 -0
- package/scripts/funnel-analytics.js +35 -0
- package/scripts/gate-satisfy.js +42 -0
- package/scripts/gate-stats.js +116 -0
- package/scripts/gate-templates.js +70 -0
- package/scripts/gates-engine.js +816 -0
- package/scripts/generate-paperbanana-diagrams.sh +99 -0
- package/scripts/generate-pretool-hook.sh +40 -0
- package/scripts/github-about.js +350 -0
- package/scripts/github-outreach.js +65 -0
- package/scripts/gtm-revenue-loop.js +520 -0
- package/scripts/hallucination-detector.js +226 -0
- package/scripts/hf-papers.js +317 -0
- package/scripts/history-distiller.js +200 -0
- package/scripts/hook-auto-capture.sh +100 -0
- package/scripts/hook-stop-pr-thread-check.sh +68 -0
- package/scripts/hook-stop-self-score.sh +51 -0
- package/scripts/hook-stop-verify-deploy.sh +31 -0
- package/scripts/hook-thumbgate-cache-updater.js +48 -0
- package/scripts/hook-verify-before-done.sh +20 -0
- package/scripts/hosted-config.js +156 -0
- package/scripts/hybrid-feedback-context.js +675 -0
- package/scripts/install-mcp.js +159 -0
- package/scripts/intent-router.js +392 -0
- package/scripts/internal-agent-bootstrap.js +490 -0
- package/scripts/jsonl-watcher.js +155 -0
- package/scripts/lesson-db.js +613 -0
- package/scripts/lesson-inference.js +310 -0
- package/scripts/lesson-retrieval.js +95 -0
- package/scripts/lesson-rotation.js +137 -0
- package/scripts/lesson-search.js +644 -0
- package/scripts/lesson-synthesis.js +196 -0
- package/scripts/license.js +50 -0
- package/scripts/local-model-profile.js +384 -0
- package/scripts/markdown-escape.js +12 -0
- package/scripts/marketing-experiment.js +671 -0
- package/scripts/mcp-config.js +149 -0
- package/scripts/mcp-policy.js +99 -0
- package/scripts/memalign-recall.js +111 -0
- package/scripts/memory-firewall.js +222 -0
- package/scripts/memory-migration.js +296 -0
- package/scripts/meta-policy.js +190 -0
- package/scripts/metered-billing.js +16 -0
- package/scripts/model-tier-router.js +301 -0
- package/scripts/money-watcher.js +71 -0
- package/scripts/multi-hop-recall.js +240 -0
- package/scripts/natural-language-harness.js +330 -0
- package/scripts/obsidian-export.js +713 -0
- package/scripts/operational-dashboard.js +103 -0
- package/scripts/operational-summary.js +93 -0
- package/scripts/optimize-context.js +17 -0
- package/scripts/org-dashboard.js +201 -0
- package/scripts/partner-orchestration.js +146 -0
- package/scripts/per-step-scoring.js +165 -0
- package/scripts/perplexity-marketing.js +466 -0
- package/scripts/pii-scanner.js +153 -0
- package/scripts/plan-gate.js +154 -0
- package/scripts/post-everywhere.js +308 -0
- package/scripts/post-to-x-retry.sh +22 -0
- package/scripts/post-to-x.js +369 -0
- package/scripts/pr-manager.js +236 -0
- package/scripts/predictive-insights.js +356 -0
- package/scripts/principle-extractor.js +162 -0
- package/scripts/pro-features.js +40 -0
- package/scripts/pro-local-dashboard.js +174 -0
- package/scripts/problem-detail.js +53 -0
- package/scripts/product-feedback.js +134 -0
- package/scripts/profile-router.js +245 -0
- package/scripts/prompt-dlp.js +221 -0
- package/scripts/prompt-guard.js +83 -0
- package/scripts/prove-adapters.js +863 -0
- package/scripts/prove-attribution.js +365 -0
- package/scripts/prove-automation.js +653 -0
- package/scripts/prove-autoresearch.js +304 -0
- package/scripts/prove-claim-verification.js +277 -0
- package/scripts/prove-cloudflare-sandbox.js +163 -0
- package/scripts/prove-data-pipeline.js +410 -0
- package/scripts/prove-data-quality.js +227 -0
- package/scripts/prove-evolution.js +352 -0
- package/scripts/prove-harnesses.js +287 -0
- package/scripts/prove-intelligence.js +259 -0
- package/scripts/prove-lancedb.js +371 -0
- package/scripts/prove-local-intelligence.js +342 -0
- package/scripts/prove-loop-closure.js +263 -0
- package/scripts/prove-predictive-insights.js +357 -0
- package/scripts/prove-runtime.js +350 -0
- package/scripts/prove-seo-gsd.js +234 -0
- package/scripts/prove-settings.js +279 -0
- package/scripts/prove-subway-upgrades.js +277 -0
- package/scripts/prove-tessl.js +229 -0
- package/scripts/prove-training-export.js +327 -0
- package/scripts/prove-workflow-contract.js +116 -0
- package/scripts/prove-xmemory.js +332 -0
- package/scripts/publish-decision.js +133 -0
- package/scripts/pulse.js +80 -0
- package/scripts/rate-limiter.js +125 -0
- package/scripts/reddit-dm-outreach.js +182 -0
- package/scripts/reddit-monitor-cron.sh +26 -0
- package/scripts/reflector-agent.js +221 -0
- package/scripts/reminder-engine.js +132 -0
- package/scripts/revenue-status.js +472 -0
- package/scripts/risk-scorer.js +459 -0
- package/scripts/rlaif-self-audit.js +129 -0
- package/scripts/rlhf_session_start.sh +32 -0
- package/scripts/rubric-engine.js +230 -0
- package/scripts/schedule-manager.js +251 -0
- package/scripts/secret-scanner.js +414 -0
- package/scripts/self-heal.js +147 -0
- package/scripts/self-healing-check.js +188 -0
- package/scripts/semantic-layer.js +98 -0
- package/scripts/seo-gsd.js +1153 -0
- package/scripts/settings-hierarchy.js +214 -0
- package/scripts/shieldcortex-memory-firewall-runner.mjs +53 -0
- package/scripts/skill-exporter.js +262 -0
- package/scripts/skill-generator.js +446 -0
- package/scripts/skill-materializer.js +134 -0
- package/scripts/skill-packs.js +136 -0
- package/scripts/skill-proposer.js +99 -0
- package/scripts/skill-quality-tracker.js +282 -0
- package/scripts/slo-alert-engine.js +14 -0
- package/scripts/slow-loop.js +72 -0
- package/scripts/social-analytics/db/schema.sql +32 -0
- package/scripts/social-analytics/db/social-analytics.db +0 -0
- package/scripts/social-analytics/digest.js +256 -0
- package/scripts/social-analytics/generate-instagram-card.js +97 -0
- package/scripts/social-analytics/instagram-thumbgate-post.js +107 -0
- package/scripts/social-analytics/load-env.js +46 -0
- package/scripts/social-analytics/mcp-server.js +289 -0
- package/scripts/social-analytics/normalizer.js +580 -0
- package/scripts/social-analytics/notify.js +162 -0
- package/scripts/social-analytics/poll-all.js +92 -0
- package/scripts/social-analytics/pollers/github.js +195 -0
- package/scripts/social-analytics/pollers/instagram.js +253 -0
- package/scripts/social-analytics/pollers/linkedin.js +330 -0
- package/scripts/social-analytics/pollers/plausible.js +247 -0
- package/scripts/social-analytics/pollers/reddit.js +306 -0
- package/scripts/social-analytics/pollers/threads.js +233 -0
- package/scripts/social-analytics/pollers/tiktok.js +203 -0
- package/scripts/social-analytics/pollers/x.js +227 -0
- package/scripts/social-analytics/pollers/youtube.js +304 -0
- package/scripts/social-analytics/pollers/zernio.js +183 -0
- package/scripts/social-analytics/publish-instagram-thumbgate.js +98 -0
- package/scripts/social-analytics/publish-thumbgate-launch.js +316 -0
- package/scripts/social-analytics/publishers/devto.js +122 -0
- package/scripts/social-analytics/publishers/instagram.js +317 -0
- package/scripts/social-analytics/publishers/linkedin.js +294 -0
- package/scripts/social-analytics/publishers/reddit.js +390 -0
- package/scripts/social-analytics/publishers/threads.js +275 -0
- package/scripts/social-analytics/publishers/tiktok.js +217 -0
- package/scripts/social-analytics/publishers/x.js +259 -0
- package/scripts/social-analytics/publishers/youtube.js +223 -0
- package/scripts/social-analytics/publishers/zernio.js +378 -0
- package/scripts/social-analytics/run-digest.js +34 -0
- package/scripts/social-analytics/store.js +257 -0
- package/scripts/social-analytics/utm.js +143 -0
- package/scripts/social-pipeline.js +2628 -0
- package/scripts/social-quality-gate.js +18 -0
- package/scripts/social-reply-monitor.js +445 -0
- package/scripts/status-dashboard.js +155 -0
- package/scripts/statusline-lesson.js +16 -0
- package/scripts/statusline-tower.js +8 -0
- package/scripts/statusline.sh +116 -0
- package/scripts/stripe-live-status.js +115 -0
- package/scripts/subagent-profiles.js +79 -0
- package/scripts/sync-gh-secrets-from-env.sh +70 -0
- package/scripts/sync-github-about.js +52 -0
- package/scripts/sync-version.js +447 -0
- package/scripts/synthetic-dpo.js +234 -0
- package/scripts/telemetry-analytics.js +821 -0
- package/scripts/tessl-export.js +371 -0
- package/scripts/test-coverage.js +120 -0
- package/scripts/thompson-sampling.js +417 -0
- package/scripts/thumbgate-search.js +189 -0
- package/scripts/tool-kpi-tracker.js +12 -0
- package/scripts/tool-registry.js +811 -0
- package/scripts/train_from_feedback.py +933 -0
- package/scripts/user-profile.js +78 -0
- package/scripts/validate-feedback.js +581 -0
- package/scripts/validate-workflow-contract.js +287 -0
- package/scripts/vector-store.js +197 -0
- package/scripts/verification-loop.js +291 -0
- package/scripts/verify-obsidian-setup.sh +269 -0
- package/scripts/verify-run.js +269 -0
- package/scripts/webhook-delivery.js +62 -0
- package/scripts/weekly-auto-post.js +124 -0
- package/scripts/workflow-runs.js +154 -0
- package/scripts/workflow-sprint-intake.js +475 -0
- package/scripts/workspace-evolver.js +374 -0
- package/scripts/x-autonomous-marketing.js +139 -0
- package/scripts/xmemory-lite.js +405 -0
- package/skills/agent-memory/SKILL.md +97 -0
- package/skills/rlhf-feedback/SKILL.md +49 -0
- package/skills/solve-architecture-autonomy/SKILL.md +17 -0
- package/skills/solve-architecture-autonomy/tool.js +33 -0
- package/skills/thumbgate/SKILL.md +114 -0
- package/src/api/server.js +4206 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* History Distiller — Self-Healing Brain for ThumbGate (Pro feature).
|
|
6
|
+
*
|
|
7
|
+
* When a user gives thumbs-down, this module:
|
|
8
|
+
* 1. Takes the last N conversation messages (chatHistory)
|
|
9
|
+
* 2. Identifies the failed tool call and its context
|
|
10
|
+
* 3. Auto-proposes a lesson: "I noticed X. I've recorded a rule to NEVER do X. Correct?"
|
|
11
|
+
* 4. Creates a prevention rule if the user confirms
|
|
12
|
+
*
|
|
13
|
+
* This closes the gap from "manual guardrail" to "self-healing brain."
|
|
14
|
+
* Strategic value: justifies Pro $19/mo subscription via outcome-based intelligence.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const { createLesson, inferFromSurroundingMessages } = require('./lesson-inference');
|
|
18
|
+
const contextfs = require('./contextfs');
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Chat History Analysis
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
const ANTI_PATTERNS = [
|
|
26
|
+
{ pattern: /\b(?:tailwind|tw-)\b/i, label: 'Tailwind CSS', ruleTemplate: 'NEVER use Tailwind CSS in this project' },
|
|
27
|
+
{ pattern: /(?:force[- ]?push|push\s*--force|--force)\b/i, label: 'force push', ruleTemplate: 'NEVER force-push to any branch' },
|
|
28
|
+
{ pattern: /\b(?:rm\s+-rf|delete\s+all)\b/i, label: 'destructive deletion', ruleTemplate: 'NEVER run destructive delete commands without confirmation' },
|
|
29
|
+
{ pattern: /\bskip\s*(?:test|ci|check)/i, label: 'skipping tests', ruleTemplate: 'NEVER skip tests or CI checks' },
|
|
30
|
+
{ pattern: /\b(?:mock(?:ed|ing)?|stub(?:bed|bing)?)\b.*\b(?:database|db)\b/i, label: 'mocking database', ruleTemplate: 'NEVER mock the database — use real test instances' },
|
|
31
|
+
{ pattern: /\b(?:hardcod|hard[- ]cod)/i, label: 'hardcoded values', ruleTemplate: 'NEVER hardcode secrets, URLs, or configuration values' },
|
|
32
|
+
{ pattern: /\b(?:console\.log|print\s+debug)\b/i, label: 'debug logging', ruleTemplate: 'NEVER leave debug console.log/print statements in production code' },
|
|
33
|
+
{ pattern: /\b(?:any\b.*type|:\s*any\b)/i, label: 'TypeScript any', ruleTemplate: 'NEVER use the `any` type — use proper type annotations' },
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Analyze chat history to find the correction pattern.
|
|
38
|
+
* Looks for: user corrected the agent about something → agent did it again → user gave thumbs down.
|
|
39
|
+
*
|
|
40
|
+
* @param {Array} chatHistory - Array of {role, content} messages, most recent last
|
|
41
|
+
* @param {Object} failedToolCall - The tool call that triggered the thumbs-down
|
|
42
|
+
* @returns {{ correction, antiPattern, proposedRule, confidence, evidence }}
|
|
43
|
+
*/
|
|
44
|
+
function analyzeChatHistory(chatHistory, failedToolCall = null) {
|
|
45
|
+
const messages = Array.isArray(chatHistory) ? chatHistory : [];
|
|
46
|
+
if (messages.length === 0) return { correction: null, antiPattern: null, proposedRule: null, confidence: 0, evidence: [] };
|
|
47
|
+
|
|
48
|
+
const evidence = [];
|
|
49
|
+
let bestAntiPattern = null;
|
|
50
|
+
let userCorrection = null;
|
|
51
|
+
|
|
52
|
+
// Scan for user corrections (messages where user said "don't", "stop", "no", "never", "wrong")
|
|
53
|
+
const correctionPatterns = [
|
|
54
|
+
/\b(?:don'?t|do not|stop|never|wrong|no,?\s+(?:that|not|I said))\b/i,
|
|
55
|
+
/\b(?:I (?:told|said|asked) you|I already|we don'?t)\b/i,
|
|
56
|
+
/\b(?:should(?:n'?t| not)|must not|not supposed to)\b/i,
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
for (const msg of messages) {
|
|
60
|
+
if (msg.role !== 'user') continue;
|
|
61
|
+
const text = msg.content || msg.text || '';
|
|
62
|
+
for (const cp of correctionPatterns) {
|
|
63
|
+
if (cp.test(text)) {
|
|
64
|
+
userCorrection = text.slice(0, 200);
|
|
65
|
+
evidence.push({ type: 'user_correction', text: userCorrection });
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Scan assistant messages for anti-patterns
|
|
72
|
+
const assistantMessages = messages.filter((m) => m.role === 'assistant').map((m) => m.content || m.text || '');
|
|
73
|
+
const allAssistantText = assistantMessages.join('\n');
|
|
74
|
+
|
|
75
|
+
// Also check the failed tool call
|
|
76
|
+
const toolCallText = failedToolCall
|
|
77
|
+
? `${failedToolCall.tool || ''} ${failedToolCall.input || ''} ${failedToolCall.output || ''}`
|
|
78
|
+
: '';
|
|
79
|
+
const combinedText = `${allAssistantText}\n${toolCallText}`;
|
|
80
|
+
|
|
81
|
+
for (const ap of ANTI_PATTERNS) {
|
|
82
|
+
if (ap.pattern.test(combinedText)) {
|
|
83
|
+
bestAntiPattern = ap;
|
|
84
|
+
evidence.push({ type: 'anti_pattern', label: ap.label });
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Build proposed rule
|
|
90
|
+
let proposedRule = null;
|
|
91
|
+
let confidence = 0;
|
|
92
|
+
|
|
93
|
+
if (bestAntiPattern && userCorrection) {
|
|
94
|
+
proposedRule = bestAntiPattern.ruleTemplate;
|
|
95
|
+
confidence = 90;
|
|
96
|
+
} else if (bestAntiPattern) {
|
|
97
|
+
proposedRule = bestAntiPattern.ruleTemplate;
|
|
98
|
+
confidence = 60;
|
|
99
|
+
} else if (userCorrection) {
|
|
100
|
+
// Extract the "don't X" part as a rule
|
|
101
|
+
const dontMatch = userCorrection.match(/(?:don'?t|never|stop)\s+(.{5,60})/i);
|
|
102
|
+
if (dontMatch) {
|
|
103
|
+
proposedRule = `NEVER ${dontMatch[1].trim().replace(/[.!?]+$/, '')}`;
|
|
104
|
+
confidence = 50;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
correction: userCorrection,
|
|
110
|
+
antiPattern: bestAntiPattern ? bestAntiPattern.label : null,
|
|
111
|
+
proposedRule,
|
|
112
|
+
confidence,
|
|
113
|
+
evidence,
|
|
114
|
+
messageCount: messages.length,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
// History-Aware Distillation (the main entry point)
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Distill a lesson from chat history when thumbs-down is received.
|
|
124
|
+
* This is the "Reflector" agent — it takes conversation context and
|
|
125
|
+
* auto-proposes what went wrong + a prevention rule.
|
|
126
|
+
*
|
|
127
|
+
* @param {Object} opts
|
|
128
|
+
* @param {Array} opts.chatHistory - Last N messages [{role, content}]
|
|
129
|
+
* @param {Object} [opts.failedToolCall] - The tool call that failed
|
|
130
|
+
* @param {string} [opts.feedbackContext] - User's feedback context string
|
|
131
|
+
* @param {string} [opts.signal] - 'negative' or 'positive'
|
|
132
|
+
* @returns {{ lesson, proposedRule, confirmation, autoCreated }}
|
|
133
|
+
*/
|
|
134
|
+
function distillFromHistory({ chatHistory = [], failedToolCall = null, feedbackContext = '', signal = 'negative' } = {}) {
|
|
135
|
+
// Step 1: Analyze chat history for corrections and anti-patterns
|
|
136
|
+
const analysis = analyzeChatHistory(chatHistory, failedToolCall);
|
|
137
|
+
|
|
138
|
+
// Step 2: Infer from surrounding messages (leverage existing module)
|
|
139
|
+
const inference = inferFromSurroundingMessages({
|
|
140
|
+
priorMessages: chatHistory.slice(-10),
|
|
141
|
+
signal,
|
|
142
|
+
feedbackContext,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Step 3: Build the auto-proposed lesson
|
|
146
|
+
let proposedWhatWentWrong;
|
|
147
|
+
let proposedRule = analysis.proposedRule;
|
|
148
|
+
|
|
149
|
+
if (analysis.correction && analysis.antiPattern) {
|
|
150
|
+
proposedWhatWentWrong = `I noticed I used ${analysis.antiPattern} despite your earlier correction: "${analysis.correction.slice(0, 100)}". This has been recorded as a mistake.`;
|
|
151
|
+
} else if (analysis.antiPattern) {
|
|
152
|
+
proposedWhatWentWrong = `I detected a ${analysis.antiPattern} anti-pattern in my output that likely caused the issue.`;
|
|
153
|
+
} else if (analysis.correction) {
|
|
154
|
+
proposedWhatWentWrong = `You previously corrected me: "${analysis.correction.slice(0, 100)}". I may have repeated the same mistake.`;
|
|
155
|
+
} else if (inference.inferredAction) {
|
|
156
|
+
proposedWhatWentWrong = `The ${inference.inferredAction.type} action on ${inference.inferredAction.target} did not produce the expected result.`;
|
|
157
|
+
} else {
|
|
158
|
+
proposedWhatWentWrong = feedbackContext || 'The agent action did not meet expectations.';
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Step 4: Build confirmation message
|
|
162
|
+
const confirmation = proposedRule
|
|
163
|
+
? `I've recorded a rule: "${proposedRule}". Correct?`
|
|
164
|
+
: `Lesson captured: "${proposedWhatWentWrong.slice(0, 100)}". Any corrections?`;
|
|
165
|
+
|
|
166
|
+
// Step 5: Auto-create the lesson
|
|
167
|
+
const lesson = createLesson({
|
|
168
|
+
signal,
|
|
169
|
+
inferredLesson: proposedWhatWentWrong,
|
|
170
|
+
triggerMessage: inference.triggerMessage,
|
|
171
|
+
priorSummary: inference.priorSummary,
|
|
172
|
+
confidence: analysis.confidence || inference.confidence,
|
|
173
|
+
tags: analysis.antiPattern ? [analysis.antiPattern] : [],
|
|
174
|
+
metadata: { distilled: true, hasCorrection: !!analysis.correction, hasAntiPattern: !!analysis.antiPattern },
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Step 6: If high confidence + anti-pattern, auto-install prevention rule
|
|
178
|
+
let ruleInstalled = false;
|
|
179
|
+
if (proposedRule && analysis.confidence >= 60) {
|
|
180
|
+
try {
|
|
181
|
+
contextfs.registerPreventionRules(`# Auto-Distilled Rule\n\n- ${proposedRule}\n\nSource: history-distiller (confidence: ${analysis.confidence}%)`, { source: 'history-distiller', lessonId: lesson.id });
|
|
182
|
+
ruleInstalled = true;
|
|
183
|
+
} catch { /* non-critical */ }
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
lesson,
|
|
188
|
+
proposedWhatWentWrong,
|
|
189
|
+
proposedRule,
|
|
190
|
+
confirmation,
|
|
191
|
+
ruleInstalled,
|
|
192
|
+
analysis,
|
|
193
|
+
inference: { action: inference.inferredAction, confidence: inference.confidence },
|
|
194
|
+
autoCreated: true,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
module.exports = {
|
|
199
|
+
ANTI_PATTERNS, analyzeChatHistory, distillFromHistory,
|
|
200
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Claude Code UserPromptSubmit hook — auto-captures thumbs up/down feedback
|
|
3
|
+
# Triggered on every user message. Only acts on feedback signals.
|
|
4
|
+
# Shows full verbose output with storage paths, memory IDs, and stats.
|
|
5
|
+
|
|
6
|
+
PROMPT="$CLAUDE_USER_PROMPT"
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
8
|
+
CAPTURE="$SCRIPT_DIR/../.claude/scripts/feedback/capture-feedback.js"
|
|
9
|
+
PROMPT_GUARD="$SCRIPT_DIR/prompt-guard.js"
|
|
10
|
+
ACTIVE_CWD="${CLAUDE_PROJECT_DIR:-${PWD:-$(pwd)}}"
|
|
11
|
+
FEEDBACK_DIR="$(node -e "const path = require('path'); const { resolveFeedbackDir } = require(path.join(process.argv[1], 'feedback-paths.js')); process.stdout.write(resolveFeedbackDir({ cwd: process.argv[2] || process.cwd(), feedbackDir: process.env.THUMBGATE_FEEDBACK_DIR || undefined }));" "$SCRIPT_DIR" "$ACTIVE_CWD" 2>/dev/null)"
|
|
12
|
+
if [ -z "$FEEDBACK_DIR" ]; then
|
|
13
|
+
FEEDBACK_DIR="${THUMBGATE_FEEDBACK_DIR:-$ACTIVE_CWD/.rlhf}"
|
|
14
|
+
fi
|
|
15
|
+
FEEDBACK_LOG="$FEEDBACK_DIR/feedback-log.jsonl"
|
|
16
|
+
MEMORY_LOG="$FEEDBACK_DIR/memory-log.jsonl"
|
|
17
|
+
|
|
18
|
+
# Record the latest user prompt so statusline thumbs can distill lessons
|
|
19
|
+
# from recent conversation even when the click itself has no body payload.
|
|
20
|
+
THUMBGATE_CONVERSATION_TEXT="$PROMPT" node -e "
|
|
21
|
+
const { recordConversationEntry } = require(process.argv[1]);
|
|
22
|
+
recordConversationEntry({
|
|
23
|
+
author: 'user',
|
|
24
|
+
text: process.env.THUMBGATE_CONVERSATION_TEXT || '',
|
|
25
|
+
source: 'claude_user_prompt',
|
|
26
|
+
});
|
|
27
|
+
" "$SCRIPT_DIR/feedback-history-distiller.js" 2>/dev/null || true
|
|
28
|
+
|
|
29
|
+
# Normalize to lowercase for matching
|
|
30
|
+
LOWER=$(echo "$PROMPT" | tr '[:upper:]' '[:lower:]')
|
|
31
|
+
|
|
32
|
+
if [ -f "$PROMPT_GUARD" ]; then
|
|
33
|
+
GUARD_RESULT=$(node "$PROMPT_GUARD" 2>/dev/null || true)
|
|
34
|
+
if [ -n "$GUARD_RESULT" ]; then
|
|
35
|
+
echo "$GUARD_RESULT"
|
|
36
|
+
exit 0
|
|
37
|
+
fi
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
capture_and_report() {
|
|
41
|
+
local SIGNAL="$1"
|
|
42
|
+
|
|
43
|
+
# Capture feedback (verbose output already shows IDs, signal, storage)
|
|
44
|
+
node "$CAPTURE" --feedback="$SIGNAL" --context="$PROMPT" --tags="auto-capture,hook"
|
|
45
|
+
local CAPTURE_STATUS=$?
|
|
46
|
+
|
|
47
|
+
if [ "$CAPTURE_STATUS" -eq 2 ]; then
|
|
48
|
+
echo "Reusable memory status: signal logged only. Add one specific sentence so the MCP can promote it."
|
|
49
|
+
echo ""
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# Show storage proof
|
|
53
|
+
echo ""
|
|
54
|
+
echo "Storage Proof:"
|
|
55
|
+
echo " Feedback log : $FEEDBACK_LOG ($(wc -l < "$FEEDBACK_LOG" 2>/dev/null || echo 0) entries)"
|
|
56
|
+
echo " Memory log : $MEMORY_LOG ($(wc -l < "$MEMORY_LOG" 2>/dev/null || echo 0) entries)"
|
|
57
|
+
echo " LanceDB : $FEEDBACK_DIR/lancedb/"
|
|
58
|
+
echo ""
|
|
59
|
+
|
|
60
|
+
# Show last entry written
|
|
61
|
+
echo "Last Entry Written:"
|
|
62
|
+
tail -1 "$FEEDBACK_LOG" 2>/dev/null | node -e "
|
|
63
|
+
const d = JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));
|
|
64
|
+
console.log(' ID :', d.id);
|
|
65
|
+
console.log(' Signal :', d.signal, '(' + d.actionType + ')');
|
|
66
|
+
console.log(' Context :', (d.context||'').slice(0,80));
|
|
67
|
+
console.log(' Tags :', (d.tags||[]).join(', '));
|
|
68
|
+
console.log(' Timestamp :', d.timestamp);
|
|
69
|
+
console.log(' Domain :', (d.richContext||{}).domain || 'general');
|
|
70
|
+
" 2>/dev/null
|
|
71
|
+
|
|
72
|
+
# Show cumulative stats
|
|
73
|
+
echo ""
|
|
74
|
+
echo "Cumulative Stats:"
|
|
75
|
+
node -e "
|
|
76
|
+
const fs = require('fs');
|
|
77
|
+
const lines = fs.readFileSync('$FEEDBACK_LOG','utf8').trim().split('\n').filter(Boolean);
|
|
78
|
+
const entries = lines.map(l => { try { return JSON.parse(l); } catch(e) { return null; } }).filter(Boolean);
|
|
79
|
+
const pos = entries.filter(e => e.signal === 'positive').length;
|
|
80
|
+
const neg = entries.filter(e => e.signal === 'negative').length;
|
|
81
|
+
const promoted = entries.filter(e => e.actionType === 'store-learning' || e.actionType === 'store-mistake').length;
|
|
82
|
+
console.log(' Total feedback :', entries.length);
|
|
83
|
+
console.log(' Positive (up) :', pos);
|
|
84
|
+
console.log(' Negative (down) :', neg);
|
|
85
|
+
console.log(' Promoted to mem :', promoted);
|
|
86
|
+
console.log(' Ratio :', pos > 0 ? (pos/(pos+neg)*100).toFixed(0) + '% positive' : 'n/a');
|
|
87
|
+
" 2>/dev/null
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# Check for thumbs up signals
|
|
91
|
+
if echo "$LOWER" | grep -qE '(thumbs? ?up|that worked|looks good|nice work|perfect|good job)'; then
|
|
92
|
+
capture_and_report "up"
|
|
93
|
+
exit 0
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# Check for thumbs down signals
|
|
97
|
+
if echo "$LOWER" | grep -qE '(thumbs? ?down|that failed|that was wrong|fix this)'; then
|
|
98
|
+
capture_and_report "down"
|
|
99
|
+
exit 0
|
|
100
|
+
fi
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Stop hook — blocks "done/pushed/resolved" claims without PR thread evidence
|
|
3
|
+
# Inspects the assistant response for completion signals and requires proof
|
|
4
|
+
# that all PR review threads are resolved before allowing the claim.
|
|
5
|
+
#
|
|
6
|
+
# Environment variables (Stop hooks):
|
|
7
|
+
# CLAUDE_RESPONSE — the assistant's last response text
|
|
8
|
+
#
|
|
9
|
+
# Returns JSON with decision: "block" + message, or passes through silently.
|
|
10
|
+
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
RESPONSE="${CLAUDE_RESPONSE:-}"
|
|
14
|
+
|
|
15
|
+
# If no response, nothing to check
|
|
16
|
+
if [ -z "$RESPONSE" ]; then
|
|
17
|
+
exit 0
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
node -e '
|
|
21
|
+
"use strict";
|
|
22
|
+
|
|
23
|
+
const response = process.env.CLAUDE_RESPONSE || "";
|
|
24
|
+
|
|
25
|
+
// 1. Detect completion/done signals
|
|
26
|
+
const doneSignal = /\b(done|pushed|resolved|ready for review|all comments addressed|merged|ship it|completed|fixed and pushed)\b/i.test(response);
|
|
27
|
+
if (!doneSignal) {
|
|
28
|
+
// No completion claim — pass through silently
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 2. Check for PR thread evidence in the response
|
|
33
|
+
const threadEvidence =
|
|
34
|
+
/\b(0 unresolved|all threads resolved|no open threads|no unresolved|all comments resolved)\b/i.test(response) ||
|
|
35
|
+
/gh pr view.*review/i.test(response) ||
|
|
36
|
+
/reviewDecision.*APPROVED/i.test(response) ||
|
|
37
|
+
/comments.*:\s*0\b/i.test(response) ||
|
|
38
|
+
/unresolved.*:\s*0\b/i.test(response);
|
|
39
|
+
|
|
40
|
+
if (threadEvidence) {
|
|
41
|
+
// Evidence present — allow
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 3. Check if we are actually in a PR context (has a branch that is not main/master)
|
|
46
|
+
const { execSync } = require("child_process");
|
|
47
|
+
let branch = "";
|
|
48
|
+
try {
|
|
49
|
+
branch = execSync("git rev-parse --abbrev-ref HEAD", { encoding: "utf-8" }).trim();
|
|
50
|
+
} catch {
|
|
51
|
+
// Not in a git repo — skip check
|
|
52
|
+
process.exit(0);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (branch === "main" || branch === "master") {
|
|
56
|
+
// Not on a feature branch — no PR to check
|
|
57
|
+
process.exit(0);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 4. Block: completion claim on a feature branch without thread evidence
|
|
61
|
+
const output = {
|
|
62
|
+
decision: "block",
|
|
63
|
+
reason: "MANDATORY: Before declaring done, run `gh pr view --json reviewDecision,comments` and confirm 0 unresolved threads. Show the output in your response."
|
|
64
|
+
};
|
|
65
|
+
process.stdout.write(JSON.stringify(output));
|
|
66
|
+
' 2>/dev/null || true
|
|
67
|
+
|
|
68
|
+
exit 0
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Claude Code / Amp Stop hook — autonomous self-scoring after every agent turn
|
|
3
|
+
# Fires after the agent completes a response. Runs selfAuditAndLog to produce
|
|
4
|
+
# a RLAIF self-score entry in self-score-log.jsonl.
|
|
5
|
+
#
|
|
6
|
+
# Environment variables available in Stop hooks:
|
|
7
|
+
# CLAUDE_STOP_REASON — why the agent stopped (e.g., "end_turn", "tool_use")
|
|
8
|
+
# CLAUDE_TOOL_OUTPUT — last tool output (if any)
|
|
9
|
+
#
|
|
10
|
+
# This hook is NON-BLOCKING — it exits 0 regardless of errors.
|
|
11
|
+
|
|
12
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
13
|
+
THUMBGATE_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
14
|
+
|
|
15
|
+
# Run the self-score via Node.js — sync, no API calls, ~5ms
|
|
16
|
+
node -e '
|
|
17
|
+
"use strict";
|
|
18
|
+
const path = require("path");
|
|
19
|
+
|
|
20
|
+
// Resolve modules relative to ThumbGate package root
|
|
21
|
+
const rlhfRoot = process.env.THUMBGATE_ROOT;
|
|
22
|
+
const { selfAuditAndLog } = require(path.join(rlhfRoot, "scripts", "rlaif-self-audit"));
|
|
23
|
+
const { getFeedbackPaths } = require(path.join(rlhfRoot, "scripts", "feedback-loop"));
|
|
24
|
+
|
|
25
|
+
const stopReason = process.env.CLAUDE_STOP_REASON || "unknown";
|
|
26
|
+
|
|
27
|
+
// Build a minimal feedback event for self-scoring
|
|
28
|
+
const feedbackEvent = {
|
|
29
|
+
id: `stop_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
|
|
30
|
+
signal: "positive",
|
|
31
|
+
context: `Agent turn completed (stop_reason: ${stopReason}). Autonomous self-score checkpoint.`,
|
|
32
|
+
tags: ["stop-hook", "auto-score"],
|
|
33
|
+
whatWorked: null,
|
|
34
|
+
whatWentWrong: null,
|
|
35
|
+
whatToChange: null,
|
|
36
|
+
rubric: null,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const paths = getFeedbackPaths();
|
|
40
|
+
const result = selfAuditAndLog(feedbackEvent, paths);
|
|
41
|
+
|
|
42
|
+
// Output minimal JSON for hook response (non-blocking)
|
|
43
|
+
process.stdout.write(JSON.stringify({
|
|
44
|
+
hookSpecificOutput: {
|
|
45
|
+
hookEventName: "Stop",
|
|
46
|
+
additionalContext: `Self-score: ${result.score} (${result.constraints.filter(c => c.passed).length}/${result.constraints.length} constraints passed)`
|
|
47
|
+
}
|
|
48
|
+
}));
|
|
49
|
+
' 2>/dev/null || true
|
|
50
|
+
|
|
51
|
+
exit 0
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Hook: Stop
|
|
3
|
+
#
|
|
4
|
+
# When it fires: After Claude finishes responding (every turn).
|
|
5
|
+
# What it does: Checks if a curl to production happened this session.
|
|
6
|
+
# If not, prints a warning reminding the CTO to verify.
|
|
7
|
+
# Why: Prevents saying "deployed" without proof.
|
|
8
|
+
# Env vars:
|
|
9
|
+
# CLAUDE_STOP_REASON — why the agent stopped (set by Claude Code)
|
|
10
|
+
# Marker file:
|
|
11
|
+
# /tmp/.thumbgate-last-deploy-verify — written by hook-verify-before-done.sh
|
|
12
|
+
# Exit code: Always 0 (informational only).
|
|
13
|
+
|
|
14
|
+
PROD_URL="thumbgate-production.up.railway.app"
|
|
15
|
+
VERIFICATION_MARKER="/tmp/.thumbgate-last-deploy-verify"
|
|
16
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
17
|
+
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
18
|
+
EXPECTED_VERSION="$(node -e "console.log(require(process.argv[1]).version)" "${REPO_ROOT}/package.json")"
|
|
19
|
+
|
|
20
|
+
if [ -f "$VERIFICATION_MARKER" ]; then
|
|
21
|
+
VERIFY_TIME=$(cat "$VERIFICATION_MARKER")
|
|
22
|
+
echo "✅ Last deployment verification: $VERIFY_TIME"
|
|
23
|
+
else
|
|
24
|
+
echo "⚠️ WARNING: No deployment verification found this session."
|
|
25
|
+
echo " If you deployed to Railway, run:"
|
|
26
|
+
echo " curl -s https://${PROD_URL}/health | grep '\"version\":\"${EXPECTED_VERSION}\"'"
|
|
27
|
+
echo " curl -s https://${PROD_URL}/dashboard | grep 'ThumbGate Dashboard'"
|
|
28
|
+
echo " before claiming done."
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
exit 0
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PostToolUse hook: updates ThumbGate statusline cache after feedback_stats calls.
|
|
4
|
+
* Installed by: npx thumbgate init --agent claude-code
|
|
5
|
+
*/
|
|
6
|
+
'use strict';
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const { resolveFeedbackDir } = require('./feedback-paths');
|
|
10
|
+
|
|
11
|
+
const CACHE_PATH = path.join(resolveFeedbackDir(), 'statusline_cache.json');
|
|
12
|
+
|
|
13
|
+
let input = '';
|
|
14
|
+
process.stdin.setEncoding('utf8');
|
|
15
|
+
process.stdin.on('data', chunk => { input += chunk; });
|
|
16
|
+
process.stdin.on('end', () => {
|
|
17
|
+
try {
|
|
18
|
+
const event = JSON.parse(input);
|
|
19
|
+
const tool = event.tool_name || '';
|
|
20
|
+
if (tool !== 'mcp__rlhf__feedback_stats' && tool !== 'mcp__rlhf__dashboard') return;
|
|
21
|
+
|
|
22
|
+
const raw = event.tool_response;
|
|
23
|
+
if (!raw) return;
|
|
24
|
+
const data = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
|
25
|
+
|
|
26
|
+
const cache = {};
|
|
27
|
+
if (tool === 'mcp__rlhf__feedback_stats') {
|
|
28
|
+
cache.thumbs_up = String(data.totalPositive || 0);
|
|
29
|
+
cache.thumbs_down = String(data.totalNegative || 0);
|
|
30
|
+
cache.lessons = String((data.rubric || {}).samples || 0);
|
|
31
|
+
cache.approval_rate = String(Math.round((data.approvalRate || 0) * 1000) / 10);
|
|
32
|
+
cache.trend = data.trend || '?';
|
|
33
|
+
cache.total_feedback = String(data.total || 0);
|
|
34
|
+
} else if (tool === 'mcp__rlhf__dashboard') {
|
|
35
|
+
const approval = data.approval || {};
|
|
36
|
+
cache.thumbs_up = String(approval.totalPositive || 0);
|
|
37
|
+
cache.thumbs_down = String(approval.totalNegative || 0);
|
|
38
|
+
cache.lessons = String((data.rubric || {}).samples || 0);
|
|
39
|
+
cache.approval_rate = String(approval.approvalRate || '?');
|
|
40
|
+
cache.trend = approval.trendDirection || '?';
|
|
41
|
+
cache.total_feedback = String(approval.total || 0);
|
|
42
|
+
}
|
|
43
|
+
cache.updated_at = String(Math.floor(Date.now() / 1000));
|
|
44
|
+
|
|
45
|
+
fs.mkdirSync(path.dirname(CACHE_PATH), { recursive: true });
|
|
46
|
+
fs.writeFileSync(CACHE_PATH, JSON.stringify(cache));
|
|
47
|
+
} catch (_) { /* silent — statusline cache is best-effort */ }
|
|
48
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Hook: PreToolUse (matcher: Bash)
|
|
3
|
+
#
|
|
4
|
+
# When it fires: Before every Bash tool call in Claude Code.
|
|
5
|
+
# What it does: If the Bash command contains a curl to production,
|
|
6
|
+
# records the timestamp to a marker file.
|
|
7
|
+
# Why: The Stop hook (hook-stop-verify-deploy.sh) checks this
|
|
8
|
+
# marker to warn if no prod verification happened.
|
|
9
|
+
# Env vars:
|
|
10
|
+
# CLAUDE_TOOL_INPUT — the Bash command about to execute (set by Claude Code)
|
|
11
|
+
# Exit code: Always 0 (never blocks tool calls).
|
|
12
|
+
|
|
13
|
+
PROD_URL="thumbgate-production.up.railway.app"
|
|
14
|
+
VERIFICATION_MARKER="/tmp/.thumbgate-last-deploy-verify"
|
|
15
|
+
|
|
16
|
+
if echo "${CLAUDE_TOOL_INPUT:-}" | grep -q "curl.*${PROD_URL}"; then
|
|
17
|
+
date -u +"%Y-%m-%dT%H:%M:%SZ" > "$VERIFICATION_MARKER"
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
exit 0
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('node:crypto');
|
|
4
|
+
const {
|
|
5
|
+
PRO_MONTHLY_PAYMENT_LINK,
|
|
6
|
+
PRO_MONTHLY_PRICE_DOLLARS,
|
|
7
|
+
PRO_PRICE_LABEL,
|
|
8
|
+
} = require('./commercial-offer');
|
|
9
|
+
|
|
10
|
+
const DEFAULT_PUBLIC_APP_ORIGIN = 'https://thumbgate-production.up.railway.app';
|
|
11
|
+
const DEFAULT_CHECKOUT_FALLBACK_URL = PRO_MONTHLY_PAYMENT_LINK;
|
|
12
|
+
const DEFAULT_PRO_PRICE_DOLLARS = PRO_MONTHLY_PRICE_DOLLARS;
|
|
13
|
+
const DEFAULT_PRO_PRICE_LABEL = PRO_PRICE_LABEL;
|
|
14
|
+
const GA_MEASUREMENT_ID_PATTERN = /^G-[A-Z0-9]+$/i;
|
|
15
|
+
|
|
16
|
+
function normalizeOrigin(value) {
|
|
17
|
+
if (!value || typeof value !== 'string') {
|
|
18
|
+
return '';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const trimmed = value.trim();
|
|
22
|
+
if (!trimmed) {
|
|
23
|
+
return '';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const parsed = new URL(trimmed);
|
|
28
|
+
if (!/^https?:$/.test(parsed.protocol)) {
|
|
29
|
+
return '';
|
|
30
|
+
}
|
|
31
|
+
parsed.pathname = parsed.pathname.replace(/\/+$/, '') || '/';
|
|
32
|
+
parsed.search = '';
|
|
33
|
+
parsed.hash = '';
|
|
34
|
+
const serialized = parsed.toString();
|
|
35
|
+
return serialized.endsWith('/') ? serialized.slice(0, -1) : serialized;
|
|
36
|
+
} catch {
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function normalizeAbsoluteUrl(value) {
|
|
42
|
+
if (!value || typeof value !== 'string') {
|
|
43
|
+
return '';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const trimmed = value.trim();
|
|
47
|
+
if (!trimmed) {
|
|
48
|
+
return '';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const parsed = new URL(trimmed);
|
|
53
|
+
if (!/^https?:$/.test(parsed.protocol)) {
|
|
54
|
+
return '';
|
|
55
|
+
}
|
|
56
|
+
return parsed.toString();
|
|
57
|
+
} catch {
|
|
58
|
+
return '';
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function normalizeTrackingId(value, pattern) {
|
|
63
|
+
if (!value || typeof value !== 'string') {
|
|
64
|
+
return '';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const trimmed = value.trim();
|
|
68
|
+
if (!trimmed) {
|
|
69
|
+
return '';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (pattern && !pattern.test(trimmed)) {
|
|
73
|
+
return '';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return trimmed;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function joinPublicUrl(baseOrigin, pathname) {
|
|
80
|
+
const normalized = normalizeOrigin(baseOrigin);
|
|
81
|
+
if (!normalized) {
|
|
82
|
+
throw new Error(`Invalid public origin: ${String(baseOrigin || '')}`);
|
|
83
|
+
}
|
|
84
|
+
const cleanPath = pathname.startsWith('/') ? pathname : `/${pathname}`;
|
|
85
|
+
return `${normalized}${cleanPath}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function createTraceId(prefix = 'trace') {
|
|
89
|
+
return `${prefix}_${Date.now().toString(36)}_${crypto.randomBytes(6).toString('hex')}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function normalizePriceDollars(value) {
|
|
93
|
+
if (value === undefined || value === null || value === '') {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
const parsed = Number(value);
|
|
97
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
return Math.round(parsed);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function buildHostedSuccessUrl(appOrigin, traceId) {
|
|
104
|
+
const encodedTraceId = encodeURIComponent(String(traceId || ''));
|
|
105
|
+
const traceQuery = traceId ? `&trace_id=${encodedTraceId}` : '';
|
|
106
|
+
return `${joinPublicUrl(appOrigin, '/success')}?session_id={CHECKOUT_SESSION_ID}${traceQuery}`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function buildHostedCancelUrl(appOrigin, traceId) {
|
|
110
|
+
const encodedTraceId = encodeURIComponent(String(traceId || ''));
|
|
111
|
+
const traceQuery = traceId ? `?trace_id=${encodedTraceId}` : '';
|
|
112
|
+
return `${joinPublicUrl(appOrigin, '/cancel')}${traceQuery}`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function resolveHostedBillingConfig({ requestOrigin } = {}) {
|
|
116
|
+
const inferredOrigin = normalizeOrigin(requestOrigin) || DEFAULT_PUBLIC_APP_ORIGIN;
|
|
117
|
+
const appOrigin = normalizeOrigin(process.env.THUMBGATE_PUBLIC_APP_ORIGIN) || inferredOrigin;
|
|
118
|
+
const billingApiBaseUrl = normalizeOrigin(
|
|
119
|
+
process.env.THUMBGATE_BILLING_API_BASE_URL || process.env.THUMBGATE_CANONICAL_API_BASE_URL || appOrigin
|
|
120
|
+
) || appOrigin;
|
|
121
|
+
const proPriceDollars = normalizePriceDollars(process.env.THUMBGATE_PRO_PRICE_DOLLARS) || DEFAULT_PRO_PRICE_DOLLARS;
|
|
122
|
+
const proPriceLabel = process.env.THUMBGATE_PRO_PRICE_LABEL || DEFAULT_PRO_PRICE_LABEL;
|
|
123
|
+
const gaMeasurementId = normalizeTrackingId(process.env.THUMBGATE_GA_MEASUREMENT_ID, GA_MEASUREMENT_ID_PATTERN);
|
|
124
|
+
const googleSiteVerification = normalizeTrackingId(process.env.THUMBGATE_GOOGLE_SITE_VERIFICATION);
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
appOrigin,
|
|
128
|
+
billingApiBaseUrl,
|
|
129
|
+
checkoutEndpoint: joinPublicUrl(billingApiBaseUrl, '/v1/billing/checkout'),
|
|
130
|
+
sessionEndpoint: joinPublicUrl(billingApiBaseUrl, '/v1/billing/session'),
|
|
131
|
+
checkoutFallbackUrl: normalizeAbsoluteUrl(
|
|
132
|
+
process.env.THUMBGATE_CHECKOUT_FALLBACK_URL || DEFAULT_CHECKOUT_FALLBACK_URL
|
|
133
|
+
) || DEFAULT_CHECKOUT_FALLBACK_URL,
|
|
134
|
+
proPriceDollars,
|
|
135
|
+
proPriceLabel,
|
|
136
|
+
gaMeasurementId,
|
|
137
|
+
googleSiteVerification,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
module.exports = {
|
|
142
|
+
DEFAULT_PUBLIC_APP_ORIGIN,
|
|
143
|
+
DEFAULT_CHECKOUT_FALLBACK_URL,
|
|
144
|
+
DEFAULT_PRO_PRICE_DOLLARS,
|
|
145
|
+
DEFAULT_PRO_PRICE_LABEL,
|
|
146
|
+
GA_MEASUREMENT_ID_PATTERN,
|
|
147
|
+
normalizeAbsoluteUrl,
|
|
148
|
+
normalizeOrigin,
|
|
149
|
+
normalizeTrackingId,
|
|
150
|
+
normalizePriceDollars,
|
|
151
|
+
joinPublicUrl,
|
|
152
|
+
createTraceId,
|
|
153
|
+
buildHostedSuccessUrl,
|
|
154
|
+
buildHostedCancelUrl,
|
|
155
|
+
resolveHostedBillingConfig,
|
|
156
|
+
};
|