thumbgate 1.27.17 → 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 -221
  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 +99 -229
  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 -145
  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
@@ -1,293 +0,0 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
- const { getFeedbackPaths } = require('./feedback-loop');
6
- const { ensureDir } = require('./fs-utils');
7
- const { loadOptionalModule } = require('./private-core-boundary');
8
-
9
- const launcher = loadOptionalModule(path.join(__dirname, 'hosted-job-launcher'), () => ({
10
- launchManagedJob: () => {
11
- throw new Error('Managed jobs require ThumbGate-Core.');
12
- },
13
- resumeHostedJob: () => {
14
- throw new Error('Resuming hosted jobs requires ThumbGate-Core.');
15
- },
16
- }));
17
-
18
- const runner = loadOptionalModule(path.join(__dirname, 'async-job-runner'), () => ({
19
- readJobState: () => null,
20
- listJobStates: () => [],
21
- }));
22
-
23
- const { launchManagedJob, resumeHostedJob } = launcher;
24
- const { readJobState, listJobStates } = runner;
25
-
26
- const DEFAULT_CONCURRENCY = 3;
27
- const POLL_INTERVAL_MS = 200;
28
-
29
- function nowIso() {
30
- return new Date().toISOString();
31
- }
32
-
33
- /**
34
- * Dynamically decompose a high-level objective into parallel, specialized subtasks.
35
- * Supports rule-based fallback and can be extended to use LLM planning.
36
- */
37
- function planWorkflow(objective) {
38
- const obj = (objective || '').toLowerCase().trim();
39
- const subtasks = [];
40
-
41
- if (obj.includes('security') || obj.includes('audit') || obj.includes('leak') || obj.includes('secret')) {
42
- subtasks.push({
43
- name: 'scan_secrets',
44
- tags: ['security', 'secret-scanner'],
45
- stages: [
46
- {
47
- name: 'secret_scan',
48
- command: 'node scripts/secret-scanner.js --json || true',
49
- }
50
- ]
51
- });
52
- subtasks.push({
53
- name: 'audit_dependencies',
54
- tags: ['security', 'dependencies'],
55
- stages: [
56
- {
57
- name: 'npm_audit',
58
- command: 'npm audit --json || true',
59
- }
60
- ]
61
- });
62
- subtasks.push({
63
- name: 'check_permissions',
64
- tags: ['security', 'credentials'],
65
- stages: [
66
- {
67
- name: 'credential_gate_check',
68
- command: 'node scripts/single-use-credential-gate.js plan || true',
69
- }
70
- ]
71
- });
72
- } else if (obj.includes('performance') || obj.includes('benchmark') || obj.includes('bench')) {
73
- subtasks.push({
74
- name: 'benchmark_candidates',
75
- tags: ['performance', 'bench'],
76
- stages: [
77
- {
78
- name: 'run_bench',
79
- command: 'npx thumbgate bench --json --min-score=90 || true',
80
- }
81
- ]
82
- });
83
- subtasks.push({
84
- name: 'check_budget',
85
- tags: ['performance', 'budget'],
86
- stages: [
87
- {
88
- name: 'budget_status',
89
- command: 'node scripts/budget-guard.js --status || true',
90
- }
91
- ]
92
- });
93
- } else {
94
- // Default general-purpose fallback workflow: code search and check integrity
95
- subtasks.push({
96
- name: 'code_search',
97
- tags: ['exploration'],
98
- stages: [
99
- {
100
- name: 'search_fs',
101
- command: 'node scripts/filesystem-search.js --query="pretool" --limit=5 || true',
102
- }
103
- ]
104
- });
105
- subtasks.push({
106
- name: 'check_integrity',
107
- tags: ['integrity'],
108
- stages: [
109
- {
110
- name: 'ops_integrity',
111
- command: 'node scripts/operational-integrity.js --ci || true',
112
- }
113
- ]
114
- });
115
- }
116
-
117
- return {
118
- objective,
119
- plannedAt: nowIso(),
120
- subtasks: subtasks.map((task, idx) => ({
121
- ...task,
122
- id: `subtask_${Date.now()}_${idx}_${Math.random().toString(36).slice(2, 6)}`,
123
- autoImprove: false,
124
- verificationMode: 'none',
125
- recordFeedback: false,
126
- })),
127
- };
128
- }
129
-
130
- /**
131
- * Execute a list of planned subtasks in parallel, respecting a concurrency limit.
132
- * Polls active jobs until all complete, then consolidates the results.
133
- */
134
- async function executeWorkflow(objective, options = {}) {
135
- const plan = planWorkflow(objective);
136
- const concurrency = Number(options.concurrency) || DEFAULT_CONCURRENCY;
137
- const timeoutMs = Number(options.timeoutMs) || 60000; // 60s timeout safety
138
-
139
- const { FEEDBACK_DIR } = getFeedbackPaths();
140
- const workflowId = `wf_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
141
- const workflowDir = path.join(FEEDBACK_DIR, 'workflows', workflowId);
142
- ensureDir(workflowDir);
143
-
144
- const activeJobs = new Map();
145
- const queue = [...plan.subtasks];
146
- const results = [];
147
- const start = Date.now();
148
-
149
- const runNext = () => {
150
- while (activeJobs.size < concurrency && queue.length > 0) {
151
- const task = queue.shift();
152
- const launched = launchManagedJob(task, { cwd: options.cwd });
153
- activeJobs.set(task.id, {
154
- jobId: launched.jobId,
155
- taskName: task.name,
156
- launchedAt: Date.now(),
157
- });
158
- }
159
- };
160
-
161
- runNext();
162
-
163
- // Polling loop
164
- await new Promise((resolve) => {
165
- const interval = setInterval(() => {
166
- let allDone = true;
167
-
168
- for (const [taskId, info] of activeJobs.entries()) {
169
- const jobState = readJobState(info.jobId);
170
- if (!jobState) {
171
- allDone = false;
172
- continue;
173
- }
174
-
175
- const isTerminal = ['completed', 'failed', 'cancelled'].includes(jobState.status);
176
- if (isTerminal) {
177
- results.push({
178
- taskId,
179
- taskName: info.taskName,
180
- jobId: info.jobId,
181
- status: jobState.status,
182
- context: jobState.currentContext,
183
- stageHistory: jobState.stageHistory,
184
- lastError: jobState.lastError,
185
- });
186
- activeJobs.delete(taskId);
187
- runNext();
188
- } else {
189
- allDone = false;
190
- }
191
- }
192
-
193
- const elapsed = Date.now() - start;
194
- if (allDone && queue.length === 0) {
195
- clearInterval(interval);
196
- resolve();
197
- } else if (elapsed >= timeoutMs) {
198
- clearInterval(interval);
199
- // Timeout remaining active tasks
200
- for (const [taskId, info] of activeJobs.entries()) {
201
- results.push({
202
- taskId,
203
- taskName: info.taskName,
204
- jobId: info.jobId,
205
- status: 'timeout',
206
- lastError: { message: `Subtask timed out after ${timeoutMs}ms`, code: 'TIMEOUT' },
207
- });
208
- }
209
- resolve();
210
- }
211
- }, POLL_INTERVAL_MS);
212
- });
213
-
214
- const durationMs = Date.now() - start;
215
-
216
- // Compile final markdown report
217
- const reportPath = path.join(workflowDir, 'report.md');
218
- const reportContent = compileWorkflowReport(plan, results, durationMs, workflowId);
219
- fs.writeFileSync(reportPath, reportContent, 'utf8');
220
-
221
- // Also save the raw execution results JSON
222
- const resultsJsonPath = path.join(workflowDir, 'results.json');
223
- fs.writeFileSync(resultsJsonPath, JSON.stringify({
224
- workflowId,
225
- objective,
226
- durationMs,
227
- plan,
228
- results,
229
- }, null, 2) + '\n', 'utf8');
230
-
231
- return {
232
- workflowId,
233
- objective,
234
- durationMs,
235
- reportPath,
236
- results,
237
- };
238
- }
239
-
240
- function compileWorkflowReport(plan, results, durationMs, workflowId) {
241
- const timestamp = nowIso();
242
- const totalSubtasks = plan.subtasks.length;
243
- const completed = results.filter((r) => r.status === 'completed').length;
244
- const failed = results.filter((r) => r.status === 'failed' || r.status === 'timeout').length;
245
-
246
- let report = `# Dynamic Workflow Execution Report: ${workflowId}\n\n`;
247
- report += `**Objective:** ${plan.objective}\n`;
248
- report += `**Executed At:** ${timestamp}\n`;
249
- report += `**Duration:** ${(durationMs / 1000).toFixed(2)}s\n`;
250
- report += `**Status:** ${completed === totalSubtasks ? '✅ SUCCESS' : '⚠️ COMPLETED WITH FAILURES'}\n\n`;
251
-
252
- report += `## Summary\n`;
253
- report += `- Total planned subtasks: ${totalSubtasks}\n`;
254
- report += `- Completed successfully: ${completed}\n`;
255
- report += `- Failed/Timed out: ${failed}\n\n`;
256
-
257
- report += `## Subtask Breakdown\n\n`;
258
-
259
- for (const res of results) {
260
- const taskPlan = plan.subtasks.find((t) => t.id === res.taskId) || {};
261
- const commandUsed = taskPlan.stages && taskPlan.stages[0] ? taskPlan.stages[0].command : 'N/A';
262
-
263
- report += `### ✦ Subtask: \`${res.taskName}\`\n`;
264
- report += `- **Job ID:** \`${res.jobId}\`\n`;
265
- report += `- **Status:** ${res.status === 'completed' ? '✅ COMPLETED' : '❌ ' + res.status.toUpperCase()}\n`;
266
- report += `- **Command Run:** \`${commandUsed}\`\n`;
267
-
268
- if (res.lastError) {
269
- report += `- **Error:** \`${res.lastError.message}\` (Code: \`${res.lastError.code}\`)\n`;
270
- }
271
-
272
- if (res.context) {
273
- report += `\n**Output Context Preview:**\n\`\`\`json\n`;
274
- try {
275
- // Try parsing output context as JSON for clean formatting
276
- const parsed = JSON.parse(res.context);
277
- report += JSON.stringify(parsed, null, 2);
278
- } catch {
279
- report += res.context.slice(0, 1000) + (res.context.length > 1000 ? '\n... (truncated)' : '');
280
- }
281
- report += `\n\`\`\`\n`;
282
- }
283
- report += `\n---\n\n`;
284
- }
285
-
286
- return report;
287
- }
288
-
289
- module.exports = {
290
- planWorkflow,
291
- executeWorkflow,
292
- compileWorkflowReport,
293
- };
@@ -1,243 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- /**
5
- * Plan Gate — implementing the CodeRabbit "Planning-First" pattern.
6
- *
7
- * 1. (Static) Validates structured 'PLAN.md' / 'PRD' content (used in loop-closure).
8
- * 2. (Dynamic) Intercepts high-risk tool calls during agent execution.
9
- */
10
-
11
- const fs = require('fs');
12
- const path = require('path');
13
-
14
- const RISK_TOOLS = ['Bash', 'Write', 'Edit', 'Deploy'];
15
-
16
- // ---------------------------------------------------------------------------
17
- // Gate validators (Legacy / Loop Closure)
18
- // ---------------------------------------------------------------------------
19
-
20
- function countTableRows(content, sectionHeading) {
21
- const sectionRegex = new RegExp(
22
- `#+\\s*${sectionHeading}[^\\n]*\\n([\\s\\S]*?)(?=\\n#+\\s|$)`,
23
- );
24
- const match = content.match(sectionRegex);
25
- if (!match) return 0;
26
-
27
- const lines = match[1].split('\n').filter((l) => l.trim().startsWith('|'));
28
- // Subtract header row and separator row
29
- const dataRows = lines.filter(
30
- (l) => !/^\|\s*-+/.test(l.trim()) && !/^\|\s*:?-+/.test(l.trim()),
31
- );
32
- // First row is the header
33
- return Math.max(0, dataRows.length - 1);
34
- }
35
-
36
- function countContracts(content) {
37
- const sectionRegex = /#+\s*Contracts[^\n]*\n([\s\S]*?)(?=\n#+\s|$)/;
38
- const match = content.match(sectionRegex);
39
- if (!match) return 0;
40
-
41
- const section = match[1];
42
- // Find code blocks and look for interface/type keywords inside them
43
- const codeBlockRegex = /```[\s\S]*?```/g;
44
- let count = 0;
45
- let blockMatch;
46
- while ((blockMatch = codeBlockRegex.exec(section)) !== null) {
47
- const block = blockMatch[0];
48
- const interfaceMatches = block.match(/\b(interface|type)\s+\w+/g);
49
- if (interfaceMatches) count += interfaceMatches.length;
50
- }
51
- return count;
52
- }
53
-
54
- function countValidationScenarios(content) {
55
- const sectionRegex =
56
- /#+\s*Validation\s+Checklist[^\n]*\n([\s\S]*?)(?=\n#+\s|$)/;
57
- const match = content.match(sectionRegex);
58
- if (!match) return 0;
59
-
60
- const lines = match[1].split('\n');
61
- return lines.filter((l) => /^\s*-\s*\[\s*\]/.test(l)).length;
62
- }
63
-
64
- function getStatus(content) {
65
- const match = content.match(/#+\s*Status[^\n]*\n\s*(\S+)/);
66
- return match ? match[1].trim() : null;
67
- }
68
-
69
- function validatePlan(content) {
70
- const questionCount = countTableRows(content, 'Clarifying Questions Resolved');
71
- const contractCount = countContracts(content);
72
- const scenarioCount = countValidationScenarios(content);
73
- const status = getStatus(content);
74
-
75
- const gates = [
76
- {
77
- name: 'Clarifying Questions',
78
- pass: questionCount >= 3,
79
- detail: `${questionCount} questions resolved`,
80
- },
81
- {
82
- name: 'Contracts Defined',
83
- pass: contractCount >= 1,
84
- detail: `${contractCount} interface${contractCount !== 1 ? 's' : ''} found`,
85
- },
86
- {
87
- name: 'Validation Checklist',
88
- pass: scenarioCount >= 2,
89
- detail: `${scenarioCount} scenarios defined`,
90
- },
91
- {
92
- name: 'Status',
93
- pass: status !== 'COMPLETE',
94
- detail:
95
- status === 'COMPLETE'
96
- ? 'COMPLETE (already finished — cannot re-approve)'
97
- : `${status || 'UNKNOWN'} (not COMPLETE)`,
98
- },
99
- ];
100
-
101
- const allPass = gates.every((g) => g.pass);
102
- return { gates, allPass };
103
- }
104
-
105
- function formatReport(result) {
106
- const lines = result.gates.map(
107
- (g) => `${g.pass ? '✅' : '❌'} ${g.name}: ${g.detail}`,
108
- );
109
- lines.push('');
110
- lines.push(
111
- result.allPass
112
- ? 'RESULT: PASS — all gates satisfied'
113
- : 'RESULT: BLOCKED — resolve issues above before spawning agents',
114
- );
115
- return lines.join('\n');
116
- }
117
-
118
- // ---------------------------------------------------------------------------
119
- // Dynamic Gating (CodeRabbit Orchestration Pattern)
120
- // ---------------------------------------------------------------------------
121
-
122
- /**
123
- * Evaluates the planning state for the current tool call.
124
- */
125
- function evaluatePlanGate(toolName, toolInput, options = {}) {
126
- if (!RISK_TOOLS.includes(toolName)) return null;
127
-
128
- const projectRoot = options.projectRoot || process.cwd();
129
- const planPath = path.join(projectRoot, 'PLAN.md');
130
-
131
- // Tier 1: Existence Check
132
- if (!fs.existsSync(planPath)) {
133
- return {
134
- decision: 'warn',
135
- gate: 'plan-gate-missing',
136
- message: '⚠️ THUMBGATE: High-risk tool call without a PLAN.md. Please create a plan documenting your intent and assumptions.',
137
- severity: 'high'
138
- };
139
- }
140
-
141
- // Tier 2: Alignment Check (Simple)
142
- const planContent = fs.readFileSync(planPath, 'utf8');
143
- const action = toolName === 'Bash' ? toolInput.command : toolInput.filePath;
144
-
145
- if (action && !planContent.toLowerCase().includes(path.basename(action).toLowerCase())) {
146
- return {
147
- decision: 'warn',
148
- gate: 'plan-gate-drift',
149
- message: `⚠️ THUMBGATE: Strategic Drift detected. The action "${action}" is not mentioned in your PLAN.md.`,
150
- severity: 'medium'
151
- };
152
- }
153
-
154
- // Tier 3: Implicit Assumption Extraction
155
- const assumptions = extractAssumptions(planContent);
156
- if (assumptions.length > 0) {
157
- return {
158
- decision: 'warn',
159
- gate: 'plan-gate-assumptions',
160
- message: '🔍 THUMBGATE: Explicitly verify these implicit assumptions before proceeding:\n- ' + assumptions.join('\n- '),
161
- severity: 'medium'
162
- };
163
- }
164
-
165
- // Tier 4: Self-Critique / Risk Mitigation Check (Tip #8)
166
- const hasCritique = /(?:critique|self-critique|risk|mitigation|alternative|flaw|weakness|pitfall)/i.test(planContent);
167
- if (!hasCritique) {
168
- return {
169
- decision: 'warn',
170
- gate: 'plan-gate-critique-missing',
171
- message: '🧐 THUMBGATE: No Self-Critique/Risk Analysis detected in your PLAN.md. Please add a "Critique", "Risks", or "Mitigations" section to evaluate potential flaws in this plan before executing high-risk tools.',
172
- severity: 'medium'
173
- };
174
- }
175
-
176
- return null;
177
- }
178
-
179
- /**
180
- * Scans plan content for "Assumes" or "Implicit" keywords.
181
- */
182
- function extractAssumptions(content) {
183
- const lines = content.split('\n');
184
- const assumptions = [];
185
- const regex = /(?:assume|assumption|implicit|pre-requisite|depends on)s?[:\-]?\s*(.*)/i;
186
-
187
- for (const line of lines) {
188
- const match = line.match(regex);
189
- if (match && match[1].trim()) {
190
- assumptions.push(match[1].trim());
191
- }
192
- }
193
- return assumptions.slice(0, 5);
194
- }
195
-
196
- // ---------------------------------------------------------------------------
197
- // Main
198
- // ---------------------------------------------------------------------------
199
-
200
- function run() {
201
- const args = process.argv.slice(2);
202
- const jsonFlag = args.includes('--json');
203
- const filePath = args.find((a) => a !== '--json');
204
-
205
- if (!filePath) {
206
- console.error('Usage: node scripts/plan-gate.js <plan-file.md> [--json]');
207
- process.exit(1);
208
- }
209
-
210
- const resolved = path.resolve(filePath);
211
- if (!fs.existsSync(resolved)) {
212
- console.error(`File not found: ${resolved}`);
213
- process.exit(1);
214
- }
215
-
216
- const content = fs.readFileSync(resolved, 'utf-8');
217
- const result = validatePlan(content);
218
-
219
- if (jsonFlag) {
220
- console.log(JSON.stringify(result, null, 2));
221
- } else {
222
- console.log(formatReport(result));
223
- }
224
-
225
- process.exit(result.allPass ? 0 : 1);
226
- }
227
-
228
- // Export for testing
229
- module.exports = {
230
- validatePlan,
231
- formatReport,
232
- countTableRows,
233
- countContracts,
234
- countValidationScenarios,
235
- getStatus,
236
- evaluatePlanGate,
237
- extractAssumptions,
238
- };
239
-
240
- // Run only when executed directly
241
- if (require.main === module) {
242
- run();
243
- }
@@ -1,99 +0,0 @@
1
- 'use strict';
2
-
3
- const PRIMARY_PLAUSIBLE_DOMAIN = 'thumbgate.ai';
4
- const FALLBACK_REGISTERED_PLAUSIBLE_DOMAIN = 'thumbgate-production.up.railway.app';
5
-
6
- function splitDomains(value) {
7
- return String(value || '')
8
- .split(/[\s,]+/)
9
- .map((domain) => domain.trim().toLowerCase())
10
- .filter(Boolean);
11
- }
12
-
13
- function normalizeDomain(value) {
14
- const input = String(value || '').trim();
15
- if (!input) return '';
16
- try {
17
- return new URL(input.includes('://') ? input : `https://${input}`).hostname.toLowerCase();
18
- } catch {
19
- const withoutProtocol = input.replace(/^https?:\/\//i, '');
20
- const hostnameAndPort = withoutProtocol.split('/')[0];
21
- return hostnameAndPort.toLowerCase().split(':')[0];
22
- }
23
- }
24
-
25
- function getConfiguredRegisteredDomains(env = process.env) {
26
- const configured = [
27
- ...splitDomains(env.PLAUSIBLE_SITE_ID),
28
- ...splitDomains(env.PLAUSIBLE_SITE_IDS),
29
- ...splitDomains(env.THUMBGATE_PLAUSIBLE_REGISTERED_DOMAINS),
30
- ...splitDomains(env.PLAUSIBLE_REGISTERED_DOMAINS),
31
- ].map(normalizeDomain).filter(Boolean);
32
-
33
- return [...new Set([
34
- FALLBACK_REGISTERED_PLAUSIBLE_DOMAIN,
35
- ...configured,
36
- ])];
37
- }
38
-
39
- function isPlausibleDomainRegistered(domain, env = process.env) {
40
- const normalized = normalizeDomain(domain);
41
- if (!normalized) return false;
42
- return getConfiguredRegisteredDomains(env).includes(normalized);
43
- }
44
-
45
- function resolvePlausibleDataDomain({ host = '', env = process.env } = {}) {
46
- const explicit = normalizeDomain(env.THUMBGATE_PLAUSIBLE_DOMAIN);
47
- if (explicit) return explicit;
48
-
49
- const normalizedHost = normalizeDomain(host);
50
- if (isPlausibleDomainRegistered(normalizedHost, env)) {
51
- return normalizedHost;
52
- }
53
-
54
- // Prevent local/loopback traffic from mapping to the production Plausible site domain
55
- const isLocal = normalizedHost === 'localhost' ||
56
- normalizedHost === '127.0.0.1' ||
57
- normalizedHost === '::1' ||
58
- normalizedHost.endsWith('.local') ||
59
- normalizedHost.startsWith('192.168.') ||
60
- normalizedHost.startsWith('10.');
61
- if (isLocal) {
62
- return 'localhost';
63
- }
64
-
65
- return FALLBACK_REGISTERED_PLAUSIBLE_DOMAIN;
66
- }
67
-
68
- function analyzePlausibleDomainCoverage({
69
- emittedDomains = [],
70
- registeredDomains = [],
71
- primaryDomain = PRIMARY_PLAUSIBLE_DOMAIN,
72
- } = {}) {
73
- const emitted = [...new Set(emittedDomains.map(normalizeDomain).filter(Boolean))];
74
- const registered = [...new Set(registeredDomains.map(normalizeDomain).filter(Boolean))];
75
- const registeredSet = new Set(registered);
76
- const missingEmittedDomains = emitted.filter((domain) => !registeredSet.has(domain));
77
- const primaryRegistered = registeredSet.has(normalizeDomain(primaryDomain));
78
-
79
- return {
80
- ok: missingEmittedDomains.length === 0 && primaryRegistered,
81
- emittedDomains: emitted,
82
- registeredDomains: registered,
83
- missingEmittedDomains,
84
- primaryDomain: normalizeDomain(primaryDomain),
85
- primaryRegistered,
86
- severity: missingEmittedDomains.length > 0 || !primaryRegistered ? 'critical' : 'ok',
87
- };
88
- }
89
-
90
- module.exports = {
91
- PRIMARY_PLAUSIBLE_DOMAIN,
92
- FALLBACK_REGISTERED_PLAUSIBLE_DOMAIN,
93
- splitDomains,
94
- normalizeDomain,
95
- getConfiguredRegisteredDomains,
96
- isPlausibleDomainRegistered,
97
- resolvePlausibleDataDomain,
98
- analyzePlausibleDomainCoverage,
99
- };