thumbgate 0.9.14 → 1.0.0

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 (63) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.well-known/mcp/server-card.json +1 -1
  4. package/README.md +1 -0
  5. package/adapters/README.md +1 -1
  6. package/adapters/chatgpt/openapi.yaml +105 -0
  7. package/adapters/claude/.mcp.json +2 -2
  8. package/adapters/codex/config.toml +2 -2
  9. package/adapters/forge/forge.yaml +28 -0
  10. package/adapters/mcp/server-stdio.js +32 -1
  11. package/adapters/opencode/opencode.json +1 -1
  12. package/bin/cli.js +18 -3
  13. package/config/mcp-allowlists.json +10 -0
  14. package/openapi/openapi.yaml +105 -0
  15. package/package.json +4 -4
  16. package/plugins/amp-skill/INSTALL.md +3 -4
  17. package/plugins/amp-skill/SKILL.md +0 -1
  18. package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +1 -1
  19. package/plugins/claude-codex-bridge/.mcp.json +1 -1
  20. package/plugins/claude-skill/INSTALL.md +1 -2
  21. package/plugins/codex-profile/.codex-plugin/plugin.json +1 -1
  22. package/plugins/codex-profile/.mcp.json +1 -1
  23. package/plugins/codex-profile/INSTALL.md +1 -1
  24. package/plugins/codex-profile/README.md +1 -1
  25. package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +1 -1
  26. package/plugins/opencode-profile/INSTALL.md +1 -1
  27. package/public/blog.html +1 -0
  28. package/public/dashboard.html +1 -1
  29. package/public/guide.html +1 -1
  30. package/public/index.html +3 -3
  31. package/public/learn/agent-harness-pattern.html +1 -1
  32. package/public/learn/ai-agent-persistent-memory.html +1 -1
  33. package/public/learn/mcp-pre-action-gates-explained.html +1 -1
  34. package/public/learn/stop-ai-agent-force-push.html +1 -1
  35. package/public/learn/vibe-coding-safety-net.html +1 -1
  36. package/public/learn.html +1 -1
  37. package/public/lessons.html +1 -1
  38. package/public/pro.html +1 -1
  39. package/scripts/__pycache__/train_from_feedback.cpython-312.pyc +0 -0
  40. package/scripts/agent-security-hardening.js +4 -4
  41. package/scripts/async-job-runner.js +84 -24
  42. package/scripts/auto-wire-hooks.js +59 -1
  43. package/scripts/context-manager.js +330 -0
  44. package/scripts/dashboard.js +1 -1
  45. package/scripts/distribution-surfaces.js +12 -0
  46. package/scripts/ensure-repo-bootstrap.js +15 -14
  47. package/scripts/gates-engine.js +96 -10
  48. package/scripts/hook-auto-capture.sh +1 -1
  49. package/scripts/hosted-job-launcher.js +260 -0
  50. package/scripts/managed-dpo-export.js +91 -0
  51. package/scripts/obsidian-export.js +0 -1
  52. package/scripts/operational-integrity.js +50 -7
  53. package/scripts/prove-lancedb.js +62 -4
  54. package/scripts/publish-decision.js +16 -0
  55. package/scripts/self-healing-check.js +6 -1
  56. package/scripts/social-analytics/load-env.js +33 -2
  57. package/scripts/social-analytics/store.js +200 -2
  58. package/scripts/sync-version.js +18 -11
  59. package/scripts/tool-registry.js +37 -0
  60. package/scripts/train_from_feedback.py +0 -4
  61. package/scripts/workflow-sentinel.js +793 -0
  62. package/src/api/server.js +205 -27
  63. /package/scripts/{rlhf_session_start.sh → thumbgate_session_start.sh} +0 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-profile",
3
- "version": "0.9.14",
3
+ "version": "1.0.0",
4
4
  "description": "ThumbGate for Codex: pre-action gates, skill packs, hallucination detection, PII scanning, progressive disclosure (82% token savings), and MCP-backed reliability memory.",
5
5
  "author": {
6
6
  "name": "Igor Ganapolsky",
@@ -5,7 +5,7 @@
5
5
  "args": [
6
6
  "--yes",
7
7
  "--package",
8
- "thumbgate@0.9.14",
8
+ "thumbgate@1.0.0",
9
9
  "thumbgate",
10
10
  "serve"
11
11
  ]
@@ -31,7 +31,7 @@ The following block is appended to `~/.codex/config.toml`:
31
31
  ```toml
32
32
  [mcp_servers.thumbgate]
33
33
  command = "npx"
34
- args = ["--yes", "--package", "thumbgate@0.9.14", "thumbgate", "serve"]
34
+ args = ["--yes", "--package", "thumbgate@1.0.0", "thumbgate", "serve"]
35
35
  ```
36
36
 
37
37
  The repo-local Codex app plugin ships the same runtime path through `plugins/codex-profile/.mcp.json`, so the manual config and plugin metadata stay aligned.
@@ -29,7 +29,7 @@ That profile launches:
29
29
  ```toml
30
30
  [mcp_servers.thumbgate]
31
31
  command = "npx"
32
- args = ["--yes", "--package", "thumbgate@0.9.14", "thumbgate", "serve"]
32
+ args = ["--yes", "--package", "thumbgate@1.0.0", "thumbgate", "serve"]
33
33
  ```
34
34
 
35
35
  ## Why this exists
@@ -2,7 +2,7 @@
2
2
  "name": "thumbgate",
3
3
  "displayName": "ThumbGate",
4
4
  "description": "👍👎 Thumbs down a mistake — your AI agent won't repeat it. Thumbs up good work — it remembers the pattern.",
5
- "version": "0.9.14",
5
+ "version": "1.0.0",
6
6
  "author": {
7
7
  "name": "Igor Ganapolsky"
8
8
  },
@@ -25,7 +25,7 @@ The portable profile adds this MCP server entry:
25
25
  "mcp": {
26
26
  "thumbgate": {
27
27
  "type": "local",
28
- "command": ["npx", "--yes", "--package", "thumbgate@0.9.14", "thumbgate", "serve"],
28
+ "command": ["npx", "--yes", "--package", "thumbgate@1.0.0", "thumbgate", "serve"],
29
29
  "enabled": true
30
30
  }
31
31
  }
package/public/blog.html CHANGED
@@ -4,6 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>ThumbGate Blog — Agent Governance Engineering</title>
7
+ <script defer data-domain="thumbgate-production.up.railway.app" src="https://plausible.io/js/script.js"></script>
7
8
  <meta
8
9
  name="description"
9
10
  content="Technical breakdowns, release notes, and agent governance insights from the ThumbGate team."
@@ -8,7 +8,7 @@
8
8
  <link rel="canonical" href="https://thumbgate-production.up.railway.app/dashboard">
9
9
  <meta name="robots" content="noindex">
10
10
  <!-- Privacy-friendly analytics by Plausible -->
11
- <script defer data-domain="thumbgate-production.up.railway.app" data-api="/api/event" src="/js/analytics.js"></script>
11
+ <script defer data-domain="thumbgate-production.up.railway.app" src="https://plausible.io/js/script.js"></script>
12
12
  <style>
13
13
  *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
14
14
  :root {
package/public/guide.html CHANGED
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>How to Stop AI Coding Agents From Repeating Mistakes — ThumbGate Guide</title>
7
7
  <!-- Privacy-friendly analytics by Plausible -->
8
- <script defer data-domain="thumbgate-production.up.railway.app" data-api="/api/event" src="/js/analytics.js"></script>
8
+ <script defer data-domain="thumbgate-production.up.railway.app" src="https://plausible.io/js/script.js"></script>
9
9
  <meta name="description" content="The complete guide to preventing AI coding agent mistakes with pre-action gates, history-aware lesson distillation, and automatic prevention rules.">
10
10
  <meta name="keywords" content="AI agent mistakes, Claude Code force push, AI coding agent memory, MCP server guardrails, pre-action gates, vibe coding safety, PreToolUse hooks, ThumbGate, SpecLock alternative, Mem0 alternative">
11
11
  <meta property="og:title" content="How to Stop AI Coding Agents From Repeating Mistakes">
package/public/index.html CHANGED
@@ -27,7 +27,7 @@ __GOOGLE_SITE_VERIFICATION_META__
27
27
  <meta name="keywords" content="ThumbGate, thumbgate, self-improving AI agents, AI agent self-improvement, AI agent learning, AI agent memory, pre-action gates, human-in-the-loop, MCP server, Claude Code, Cursor, Codex, Gemini, Amp, OpenCode, vibe coding safety, SpecLock alternative, Mem0 alternative, AI coding agent feedback loop, PreToolUse hooks, prevention rules, feedback enforcement, context engineering">
28
28
 
29
29
  <!-- Privacy-friendly analytics by Plausible -->
30
- <script defer data-domain="thumbgate-production.up.railway.app" data-api="/api/event" src="/js/analytics.js"></script>
30
+ <script defer data-domain="thumbgate-production.up.railway.app" src="https://plausible.io/js/script.js"></script>
31
31
  __GA_BOOTSTRAP__
32
32
 
33
33
  <script>
@@ -578,7 +578,7 @@ __GA_BOOTSTRAP__
578
578
  <!-- HOW IT WORKS -->
579
579
  <section class="how-it-works" id="how-it-works">
580
580
  <div class="container">
581
- <div class="section-label">New in v0.9.14</div>
581
+ <div class="section-label">New in v1.0.0</div>
582
582
  <h2 class="section-title">Three steps to stop repeated AI failures</h2>
583
583
  <div class="steps">
584
584
  <div class="step">
@@ -835,7 +835,7 @@ __GA_BOOTSTRAP__
835
835
  <a href="https://www.linkedin.com/in/igorganapolsky" target="_blank" rel="noopener">LinkedIn</a>
836
836
  <a href="/blog">Blog</a>
837
837
  </div>
838
- <span class="footer-copy">© 2026 Max Smith KDP LLC · MIT License · v0.9.10</span>
838
+ <span class="footer-copy">© 2026 Max Smith KDP LLC · MIT License · v1.0.0</span>
839
839
  </div>
840
840
  </footer>
841
841
 
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>The Agent Harness Pattern: Why Your AI Needs a Seatbelt — ThumbGate</title>
7
- <script defer data-domain="thumbgate-production.up.railway.app" data-api="/api/event" src="/js/analytics.js"></script>
7
+ <script defer data-domain="thumbgate-production.up.railway.app" src="https://plausible.io/js/script.js"></script>
8
8
  <meta name="description" content="Tsinghua researchers formalized agent harnesses as first-class objects with contracts, verification gates, and durable state. ThumbGate implements this pattern today.">
9
9
  <meta name="keywords" content="agent harness pattern, natural language agent harness, NLAH, AI agent safety, pre-action gates, verification gates, agent contracts, ThumbGate, MCP hooks">
10
10
  <meta property="og:title" content="The Agent Harness Pattern: Why Your AI Needs a Seatbelt">
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>How to Give Your AI Coding Agent Persistent Memory Across Sessions — ThumbGate</title>
7
- <script defer data-domain="thumbgate-production.up.railway.app" data-api="/api/event" src="/js/analytics.js"></script>
7
+ <script defer data-domain="thumbgate-production.up.railway.app" src="https://plausible.io/js/script.js"></script>
8
8
  <meta name="description" content="AI coding agents forget everything when a session ends. Learn how to give Claude Code, Cursor, Codex, and Gemini persistent memory using an MCP memory server that survives restarts.">
9
9
  <meta name="keywords" content="ai agent memory, persistent memory, claude code memory, cursor agent memory, MCP memory server, session persistence, agent context, episodic memory, semantic memory">
10
10
  <meta property="og:title" content="How to Give Your AI Coding Agent Persistent Memory Across Sessions">
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>MCP Pre-Action Gates Explained — ThumbGate</title>
7
- <script defer data-domain="thumbgate-production.up.railway.app" data-api="/api/event" src="/js/analytics.js"></script>
7
+ <script defer data-domain="thumbgate-production.up.railway.app" src="https://plausible.io/js/script.js"></script>
8
8
  <meta name="description" content="What pre-action gates are, how they work in the Model Context Protocol, and why enforcement beats prompt rules for AI coding agent safety.">
9
9
  <meta name="keywords" content="MCP pre-action gates, PreToolUse hooks, Model Context Protocol, AI agent enforcement, Claude Code hooks, MCP server guardrails, tool call interception, ThumbGate">
10
10
  <meta property="og:title" content="MCP Pre-Action Gates Explained">
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>How to Stop AI Agents From Force-Pushing to Main — ThumbGate</title>
7
- <script defer data-domain="thumbgate-production.up.railway.app" data-api="/api/event" src="/js/analytics.js"></script>
7
+ <script defer data-domain="thumbgate-production.up.railway.app" src="https://plausible.io/js/script.js"></script>
8
8
  <meta name="description" content="Your AI coding agent just force-pushed to main. Again. Here is how to make that physically impossible with a pre-action gate in two minutes.">
9
9
  <meta name="keywords" content="AI agent force push, Claude Code force push prevention, git push force main, AI coding agent git safety, pre-action gates, ThumbGate">
10
10
  <meta property="og:title" content="How to Stop AI Agents From Force-Pushing to Main">
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>The Vibe Coding Safety Net You Are Missing — ThumbGate</title>
7
- <script defer data-domain="thumbgate-production.up.railway.app" data-api="/api/event" src="/js/analytics.js"></script>
7
+ <script defer data-domain="thumbgate-production.up.railway.app" src="https://plausible.io/js/script.js"></script>
8
8
  <meta name="description" content="Vibe coding is fast until your AI agent deletes a production table or rewrites a file you did not ask it to touch. Add guardrails without slowing down.">
9
9
  <meta name="keywords" content="vibe coding safety, vibe coding guardrails, AI coding mistakes, Claude Code safety net, Cursor agent guardrails, AI agent enforcement, ThumbGate">
10
10
  <meta property="og:title" content="The Vibe Coding Safety Net You Are Missing">
package/public/learn.html CHANGED
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>Learn — AI Agent Safety, Pre-Action Gates, and Vibe Coding Guardrails</title>
7
- <script defer data-domain="thumbgate-production.up.railway.app" data-api="/api/event" src="/js/analytics.js"></script>
7
+ <script defer data-domain="thumbgate-production.up.railway.app" src="https://plausible.io/js/script.js"></script>
8
8
  <meta name="description" content="Practical guides for stopping AI coding agent mistakes. Learn about pre-action gates, MCP guardrails, feedback-driven enforcement, and vibe coding safety for Claude Code, Cursor, Codex, and more.">
9
9
  <meta name="keywords" content="AI agent safety, pre-action gates, vibe coding guardrails, Claude Code mistakes, Cursor agent memory, MCP server hooks, AI coding agent feedback, ThumbGate guides">
10
10
  <meta property="og:title" content="Learn — AI Agent Safety Guides by ThumbGate">
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>ThumbGate — Lessons Learned</title>
7
- <script defer data-domain="thumbgate-production.up.railway.app" data-api="/api/event" src="/js/analytics.js"></script>
7
+ <script defer data-domain="thumbgate-production.up.railway.app" src="https://plausible.io/js/script.js"></script>
8
8
  <style>
9
9
  *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
10
10
  :root {
package/public/pro.html CHANGED
@@ -13,7 +13,7 @@ __GOOGLE_SITE_VERIFICATION_META__
13
13
  <link rel="canonical" href="__APP_ORIGIN__/pro">
14
14
  <meta name="keywords" content="ThumbGate Pro, AI agent reliability, pre-action gates, DPO export, local dashboard, review-ready evidence, Claude Code reliability, Codex reliability, Cursor reliability">
15
15
 
16
- <script defer data-domain="thumbgate-production.up.railway.app" data-api="/api/event" src="/js/analytics.js"></script>
16
+ <script defer data-domain="thumbgate-production.up.railway.app" src="https://plausible.io/js/script.js"></script>
17
17
  __GA_BOOTSTRAP__
18
18
 
19
19
  <script>
@@ -84,10 +84,10 @@ function getCredentialAudit({ periodHours = 24 } = {}) {
84
84
 
85
85
  // MCP profile tool allowlists (loaded from config or defaults)
86
86
  const PROFILE_ALLOWLISTS = {
87
- essential: new Set(['capture_feedback', 'recall', 'search_lessons', 'search_thumbgate', 'prevention_rules', 'enforcement_matrix', 'feedback_stats', 'estimate_uncertainty', 'org_dashboard', 'set_task_scope', 'get_scope_state', 'set_branch_governance', 'get_branch_governance', 'approve_protected_action', 'check_operational_integrity']),
88
- readonly: new Set(['recall', 'feedback_summary', 'search_lessons', 'verify_claim', 'gate_stats', 'search_thumbgate', 'feedback_stats', 'estimate_uncertainty', 'org_dashboard', 'get_scope_state', 'get_branch_governance', 'check_operational_integrity']),
89
- locked: new Set(['feedback_summary', 'search_lessons', 'diagnose_failure', 'list_intents', 'plan_intent', 'list_harnesses', 'verify_claim', 'get_scope_state', 'get_branch_governance', 'check_operational_integrity']),
90
- commerce: new Set(['capture_feedback', 'recall', 'search_thumbgate', 'commerce_recall', 'track_action', 'verify_claim', 'feedback_stats', 'set_task_scope', 'get_scope_state', 'set_branch_governance', 'get_branch_governance', 'approve_protected_action', 'check_operational_integrity']),
87
+ essential: new Set(['capture_feedback', 'recall', 'search_lessons', 'search_thumbgate', 'prevention_rules', 'enforcement_matrix', 'feedback_stats', 'estimate_uncertainty', 'org_dashboard', 'set_task_scope', 'get_scope_state', 'set_branch_governance', 'get_branch_governance', 'approve_protected_action', 'check_operational_integrity', 'workflow_sentinel']),
88
+ readonly: new Set(['recall', 'feedback_summary', 'search_lessons', 'verify_claim', 'gate_stats', 'search_thumbgate', 'feedback_stats', 'estimate_uncertainty', 'org_dashboard', 'get_scope_state', 'get_branch_governance', 'check_operational_integrity', 'workflow_sentinel']),
89
+ locked: new Set(['feedback_summary', 'search_lessons', 'diagnose_failure', 'list_intents', 'plan_intent', 'list_harnesses', 'verify_claim', 'get_scope_state', 'get_branch_governance', 'check_operational_integrity', 'workflow_sentinel']),
90
+ commerce: new Set(['capture_feedback', 'recall', 'search_thumbgate', 'commerce_recall', 'track_action', 'verify_claim', 'feedback_stats', 'set_task_scope', 'get_scope_state', 'set_branch_governance', 'get_branch_governance', 'approve_protected_action', 'check_operational_integrity', 'workflow_sentinel']),
91
91
  };
92
92
 
93
93
  /**
@@ -132,11 +132,54 @@ function serializeJobForState(job) {
132
132
  skill: job.skill || null,
133
133
  partnerProfile: job.partnerProfile || null,
134
134
  autoImprove: job.autoImprove !== false,
135
+ verificationMode: job.verificationMode === 'none' ? 'none' : 'standard',
136
+ recordFeedback: job.recordFeedback !== false,
135
137
  jobFilePath: job.jobFilePath || null,
136
138
  stages,
137
139
  };
138
140
  }
139
141
 
142
+ function queueJob(job) {
143
+ const normalizedJob = {
144
+ ...job,
145
+ id: job.id || generateJobId(),
146
+ tags: Array.isArray(job.tags) ? job.tags : [],
147
+ autoImprove: job.autoImprove !== false,
148
+ verificationMode: job.verificationMode === 'none' ? 'none' : 'standard',
149
+ recordFeedback: job.recordFeedback !== false,
150
+ stages: normalizeStages(job),
151
+ };
152
+ const previousState = readJobState(normalizedJob.id);
153
+ const currentStage = normalizedJob.stages[0] ? normalizedJob.stages[0].name : null;
154
+ return writeJobState({
155
+ jobId: normalizedJob.id,
156
+ status: 'queued',
157
+ createdAt: previousState && previousState.createdAt ? previousState.createdAt : nowIso(),
158
+ startedAt: previousState && previousState.startedAt ? previousState.startedAt : null,
159
+ resumedAt: null,
160
+ updatedAt: nowIso(),
161
+ endedAt: null,
162
+ tags: normalizedJob.tags,
163
+ skill: normalizedJob.skill || null,
164
+ partnerProfile: normalizedJob.partnerProfile || null,
165
+ autoImprove: normalizedJob.autoImprove,
166
+ verificationMode: normalizedJob.verificationMode,
167
+ recordFeedback: normalizedJob.recordFeedback,
168
+ totalStages: normalizedJob.stages.length,
169
+ nextStageIndex: 0,
170
+ currentStage,
171
+ currentContext: '',
172
+ checkpoints: [],
173
+ stageHistory: [],
174
+ jobFilePath: normalizedJob.jobFilePath || null,
175
+ jobSpec: serializeJobForState(normalizedJob),
176
+ lastError: null,
177
+ stopReason: null,
178
+ improvementExperimentId: null,
179
+ verification: null,
180
+ });
181
+ }
182
+
140
183
  function readJobState(jobId) {
141
184
  if (!jobId) return null;
142
185
  return readJson(getJobRuntimePaths(jobId).statePath);
@@ -618,6 +661,8 @@ function executeJob(job, options = {}) {
618
661
  id: job.id || generateJobId(),
619
662
  tags: Array.isArray(job.tags) ? job.tags : [],
620
663
  autoImprove: job.autoImprove !== false,
664
+ verificationMode: job.verificationMode === 'none' ? 'none' : 'standard',
665
+ recordFeedback: job.recordFeedback !== false,
621
666
  stages: normalizeStages(job),
622
667
  };
623
668
  const previousState = options.previousState || readJobState(normalizedJob.id);
@@ -643,6 +688,8 @@ function executeJob(job, options = {}) {
643
688
  skill: normalizedJob.skill || null,
644
689
  partnerProfile: normalizedJob.partnerProfile || null,
645
690
  autoImprove: normalizedJob.autoImprove,
691
+ verificationMode: normalizedJob.verificationMode,
692
+ recordFeedback: normalizedJob.recordFeedback,
646
693
  totalStages: normalizedJob.stages.length,
647
694
  nextStageIndex,
648
695
  currentStage: normalizedJob.stages[nextStageIndex] ? normalizedJob.stages[nextStageIndex].name : 'verification',
@@ -733,7 +780,7 @@ function executeJob(job, options = {}) {
733
780
 
734
781
  clearJobControl(normalizedJob.id);
735
782
 
736
- const feedback = error && error.code === 'JOB_CANCELLED'
783
+ const feedback = (error && error.code === 'JOB_CANCELLED') || normalizedJob.recordFeedback === false
737
784
  ? null
738
785
  : captureFeedback({
739
786
  signal: 'down',
@@ -756,48 +803,60 @@ function executeJob(job, options = {}) {
756
803
  }
757
804
  }
758
805
 
759
- const verification = runVerificationLoop({
760
- context: currentContext,
761
- tags: normalizedJob.tags,
762
- skill: normalizedJob.skill,
763
- partnerProfile: normalizedJob.partnerProfile,
764
- onRetry: normalizedJob.onRetry,
765
- maxRetries: normalizedJob.maxRetries,
766
- });
806
+ const verification = normalizedJob.verificationMode === 'none'
807
+ ? null
808
+ : runVerificationLoop({
809
+ context: currentContext,
810
+ tags: normalizedJob.tags,
811
+ skill: normalizedJob.skill,
812
+ partnerProfile: normalizedJob.partnerProfile,
813
+ onRetry: normalizedJob.onRetry,
814
+ maxRetries: normalizedJob.maxRetries,
815
+ });
767
816
 
768
- const improvementExperiment = verification.accepted
817
+ const improvementExperiment = !verification || verification.accepted
769
818
  ? null
770
819
  : maybeQueueImprovementExperiment(normalizedJob, state, recall, {
771
820
  type: 'verification',
772
821
  verification,
773
822
  });
774
823
 
775
- const feedback = captureFeedback({
776
- signal: verification.accepted ? 'up' : 'down',
777
- context: verification.accepted
778
- ? `Job ${normalizedJob.id} passed verification after ${verification.attempts} attempt(s)`
779
- : `Job ${normalizedJob.id} failed verification after ${verification.attempts} attempt(s): ${(verification.finalVerification.violations || []).map((violation) => violation.pattern).join('; ')}`,
780
- whatWorked: verification.accepted ? 'Verification loop accepted output' : undefined,
781
- whatWentWrong: !verification.accepted ? `Failed ${verification.attempts} verification attempts` : undefined,
782
- whatToChange: !verification.accepted ? 'Improve output to avoid known mistake patterns' : undefined,
783
- tags: [...normalizedJob.tags, 'verification-loop'],
784
- skill: normalizedJob.skill || 'async-job-runner',
785
- });
824
+ const feedback = normalizedJob.recordFeedback === false
825
+ ? null
826
+ : captureFeedback({
827
+ signal: !verification || verification.accepted ? 'up' : 'down',
828
+ context: !verification
829
+ ? `Job ${normalizedJob.id} completed without post-run verification`
830
+ : verification.accepted
831
+ ? `Job ${normalizedJob.id} passed verification after ${verification.attempts} attempt(s)`
832
+ : `Job ${normalizedJob.id} failed verification after ${verification.attempts} attempt(s): ${(verification.finalVerification.violations || []).map((violation) => violation.pattern).join('; ')}`,
833
+ whatWorked: !verification
834
+ ? 'Operational job completed successfully'
835
+ : verification.accepted
836
+ ? 'Verification loop accepted output'
837
+ : undefined,
838
+ whatWentWrong: verification && !verification.accepted ? `Failed ${verification.attempts} verification attempts` : undefined,
839
+ whatToChange: verification && !verification.accepted ? 'Improve output to avoid known mistake patterns' : undefined,
840
+ tags: !verification
841
+ ? [...normalizedJob.tags, 'async-job-runner', 'verification-skipped']
842
+ : [...normalizedJob.tags, 'verification-loop'],
843
+ skill: normalizedJob.skill || 'async-job-runner',
844
+ });
786
845
 
787
846
  const terminalState = writeJobState({
788
847
  ...(readJobState(normalizedJob.id) || state),
789
- status: verification.accepted ? 'completed' : 'failed',
848
+ status: !verification || verification.accepted ? 'completed' : 'failed',
790
849
  updatedAt: nowIso(),
791
850
  endedAt: nowIso(),
792
851
  currentStage: null,
793
852
  nextStageIndex: normalizedJob.stages.length,
794
853
  currentContext,
795
854
  improvementExperimentId: improvementExperiment ? improvementExperiment.id : null,
796
- verification: {
855
+ verification: verification ? {
797
856
  accepted: verification.accepted,
798
857
  attempts: verification.attempts,
799
858
  score: verification.finalVerification ? verification.finalVerification.score : 0,
800
- },
859
+ } : null,
801
860
  });
802
861
 
803
862
  clearJobControl(normalizedJob.id);
@@ -880,6 +939,7 @@ function runBatch(jobs) {
880
939
  module.exports = {
881
940
  recallContext,
882
941
  executeJob,
942
+ queueJob,
883
943
  runBatch,
884
944
  appendJobLog,
885
945
  readJobLog,
@@ -53,6 +53,7 @@ function detectAgent(flagAgent) {
53
53
  if (['claude-code', 'claude'].includes(normalized)) return 'claude-code';
54
54
  if (['codex'].includes(normalized)) return 'codex';
55
55
  if (['gemini'].includes(normalized)) return 'gemini';
56
+ if (['forge', 'forgecode', 'forge-code'].includes(normalized)) return 'forge';
56
57
  return null;
57
58
  }
58
59
 
@@ -61,6 +62,7 @@ function detectAgent(flagAgent) {
61
62
  if (fs.existsSync(path.join(home, '.claude'))) return 'claude-code';
62
63
  if (fs.existsSync(path.join(home, '.codex'))) return 'codex';
63
64
  if (fs.existsSync(path.join(home, '.gemini'))) return 'gemini';
65
+ if (fs.existsSync(path.join(process.cwd(), 'forge.yaml'))) return 'forge';
64
66
  return null;
65
67
  }
66
68
 
@@ -310,13 +312,64 @@ function wireGeminiHooks(options) {
310
312
  return { changed: true, settingsPath, added };
311
313
  }
312
314
 
315
+ // --- ForgeCode wiring ---
316
+
317
+ function forgeConfigPath() {
318
+ return path.join(process.cwd(), 'forge.yaml');
319
+ }
320
+
321
+ function wireForgeHooks(options) {
322
+ const dryRun = options.dryRun || false;
323
+
324
+ const preToolCmd = preToolHookCommand();
325
+ const userPromptCmd = userPromptHookCommand();
326
+
327
+ // ForgeCode uses YAML config (forge.yaml). We write a JSON-based hooks
328
+ // sidecar file (.thumbgate/forge-hooks.json) and append skill entries to
329
+ // forge.yaml if they are not already present.
330
+ const hooksPath = options.settingsPath || path.join(path.dirname(forgeConfigPath()), '.thumbgate', 'forge-hooks.json');
331
+ let existing = loadJsonFile(hooksPath) || {};
332
+ existing.hooks = existing.hooks || {};
333
+
334
+ const added = [];
335
+
336
+ if (!hookAlreadyPresent(existing.hooks.PreToolUse, preToolCmd)) {
337
+ existing.hooks.PreToolUse = existing.hooks.PreToolUse || [];
338
+ existing.hooks.PreToolUse.push({
339
+ matcher: 'Bash',
340
+ hooks: [{ type: 'command', command: preToolCmd }],
341
+ });
342
+ added.push({ lifecycle: 'PreToolUse', command: preToolCmd });
343
+ }
344
+
345
+ if (!hookAlreadyPresent(existing.hooks.UserPromptSubmit, userPromptCmd)) {
346
+ existing.hooks.UserPromptSubmit = existing.hooks.UserPromptSubmit || [];
347
+ existing.hooks.UserPromptSubmit.push({
348
+ hooks: [{ type: 'command', command: userPromptCmd }],
349
+ });
350
+ added.push({ lifecycle: 'UserPromptSubmit', command: userPromptCmd });
351
+ }
352
+
353
+ if (added.length === 0) {
354
+ return { changed: false, settingsPath: hooksPath, added: [] };
355
+ }
356
+
357
+ if (!dryRun) {
358
+ const dir = path.dirname(hooksPath);
359
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
360
+ fs.writeFileSync(hooksPath, JSON.stringify(existing, null, 2) + '\n');
361
+ }
362
+
363
+ return { changed: true, settingsPath: hooksPath, added };
364
+ }
365
+
313
366
  // --- Dispatcher ---
314
367
 
315
368
  function wireHooks(options) {
316
369
  const agent = detectAgent(options.agent);
317
370
  if (!agent) {
318
371
  return {
319
- error: 'Could not detect AI agent. Use --agent=claude-code|codex|gemini',
372
+ error: 'Could not detect AI agent. Use --agent=claude-code|codex|gemini|forge',
320
373
  agent: null,
321
374
  changed: false,
322
375
  };
@@ -333,6 +386,9 @@ function wireHooks(options) {
333
386
  case 'gemini':
334
387
  result = wireGeminiHooks(options);
335
388
  break;
389
+ case 'forge':
390
+ result = wireForgeHooks(options);
391
+ break;
336
392
  default:
337
393
  return { error: `Unsupported agent: ${agent}`, agent, changed: false };
338
394
  }
@@ -364,6 +420,7 @@ module.exports = {
364
420
  wireClaudeHooks,
365
421
  wireCodexHooks,
366
422
  wireGeminiHooks,
423
+ wireForgeHooks,
367
424
  hookAlreadyPresent,
368
425
  loadJsonFile,
369
426
  parseFlags,
@@ -372,6 +429,7 @@ module.exports = {
372
429
  codexConfigPath,
373
430
  geminiSettingsPath,
374
431
  syncClaudeStatusLine,
432
+ forgeConfigPath,
375
433
  CLAUDE_HOOKS,
376
434
  preToolHookCommand,
377
435
  userPromptHookCommand,