thumbgate 1.3.0 → 1.4.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.
- package/.claude-plugin/marketplace.json +32 -13
- package/.claude-plugin/plugin.json +15 -2
- package/.well-known/llms.txt +60 -0
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +109 -20
- package/adapters/README.md +1 -1
- package/adapters/chatgpt/openapi.yaml +168 -0
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/codex/config.toml +2 -2
- package/adapters/mcp/server-stdio.js +84 -1
- package/adapters/opencode/opencode.json +1 -1
- package/bin/cli.js +200 -13
- package/bin/postinstall.js +8 -2
- package/config/budget.json +18 -0
- package/config/gates/code-edit.json +61 -0
- package/config/gates/db-write.json +61 -0
- package/config/gates/default.json +154 -3
- package/config/gates/deploy.json +61 -0
- package/config/github-about.json +2 -1
- package/config/merge-quality-checks.json +23 -0
- package/openapi/openapi.yaml +168 -0
- package/package.json +42 -10
- package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-codex-bridge/.mcp.json +1 -1
- package/plugins/claude-codex-bridge/scripts/codex-bridge.js +1 -3
- package/plugins/codex-profile/.codex-plugin/plugin.json +1 -1
- package/plugins/codex-profile/.mcp.json +1 -1
- package/plugins/codex-profile/INSTALL.md +27 -4
- package/plugins/codex-profile/README.md +33 -9
- package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +1 -1
- package/plugins/opencode-profile/INSTALL.md +1 -1
- package/public/blog.html +73 -0
- package/public/compare/mem0.html +189 -0
- package/public/compare/speclock.html +180 -0
- package/public/compare.html +10 -2
- package/public/guide.html +2 -2
- package/public/guides/claude-code-prevent-repeated-mistakes.html +161 -0
- package/public/guides/codex-cli-guardrails.html +158 -0
- package/public/guides/cursor-prevent-repeated-mistakes.html +161 -0
- package/public/guides/pre-action-gates.html +162 -0
- package/public/guides/stop-repeated-ai-agent-mistakes.html +159 -0
- package/public/index.html +136 -50
- package/public/lessons.html +33 -24
- package/public/llm-context.md +140 -0
- package/public/pro.html +24 -22
- package/scripts/__pycache__/train_from_feedback.cpython-312.pyc +0 -0
- package/scripts/access-anomaly-detector.js +1 -1
- package/scripts/adk-consolidator.js +1 -5
- package/scripts/agent-security-hardening.js +4 -6
- package/scripts/agentic-data-pipeline.js +1 -3
- package/scripts/async-job-runner.js +1 -5
- package/scripts/audit-trail.js +1 -5
- package/scripts/background-agent-governance.js +2 -10
- package/scripts/billing.js +2 -16
- package/scripts/budget-enforcer.js +173 -0
- package/scripts/build-codex-plugin.js +152 -0
- package/scripts/check-congruence.js +132 -14
- package/scripts/commercial-offer.js +5 -7
- package/scripts/content-engine/linkedin-content-generator.js +154 -0
- package/scripts/content-engine/output/linkedin-memento-validation.md +17 -0
- package/scripts/content-engine/output/linkedin-posts-2026-04-09.md +175 -0
- package/scripts/content-engine/reddit-thread-finder.js +154 -0
- package/scripts/context-engine.js +21 -6
- package/scripts/contextfs.js +1 -21
- package/scripts/dashboard.js +20 -0
- package/scripts/decision-journal.js +341 -0
- package/scripts/delegation-runtime.js +1 -5
- package/scripts/distribution-surfaces.js +26 -0
- package/scripts/document-intake.js +927 -0
- package/scripts/ephemeral-agent-store.js +1 -8
- package/scripts/evolution-state.js +1 -5
- package/scripts/experiment-tracker.js +1 -5
- package/scripts/export-databricks-bundle.js +1 -5
- package/scripts/export-hf-dataset.js +1 -5
- package/scripts/export-training.js +1 -5
- package/scripts/feedback-attribution.js +1 -16
- package/scripts/feedback-history-distiller.js +1 -16
- package/scripts/feedback-loop.js +1 -5
- package/scripts/feedback-root-consolidator.js +2 -21
- package/scripts/feedback-session.js +49 -0
- package/scripts/feedback-to-rules.js +188 -28
- package/scripts/filesystem-search.js +1 -9
- package/scripts/fs-utils.js +104 -0
- package/scripts/gates-engine.js +149 -4
- package/scripts/github-about.js +32 -8
- package/scripts/gtm-revenue-loop.js +1 -5
- package/scripts/harness-selector.js +148 -0
- package/scripts/hosted-job-launcher.js +1 -5
- package/scripts/hybrid-feedback-context.js +7 -33
- package/scripts/intervention-policy.js +58 -1
- package/scripts/lesson-db.js +3 -18
- package/scripts/lesson-inference.js +194 -16
- package/scripts/lesson-retrieval.js +60 -24
- package/scripts/llm-client.js +59 -0
- package/scripts/managed-lesson-agent.js +183 -0
- package/scripts/marketing-experiment.js +8 -22
- package/scripts/meta-agent-loop.js +624 -0
- package/scripts/metered-billing.js +1 -1
- package/scripts/money-watcher.js +1 -4
- package/scripts/obsidian-export.js +1 -5
- package/scripts/operational-integrity.js +15 -3
- package/scripts/org-dashboard.js +6 -1
- package/scripts/per-step-scoring.js +2 -4
- package/scripts/pr-manager.js +201 -19
- package/scripts/pro-features.js +3 -2
- package/scripts/prompt-dlp.js +3 -3
- package/scripts/prove-adapters.js +1 -5
- package/scripts/prove-attribution.js +1 -5
- package/scripts/prove-automation.js +1 -3
- package/scripts/prove-cloudflare-sandbox.js +1 -3
- package/scripts/prove-data-pipeline.js +1 -3
- package/scripts/prove-intelligence.js +1 -3
- package/scripts/prove-lancedb.js +1 -5
- package/scripts/prove-local-intelligence.js +1 -3
- package/scripts/prove-packaged-runtime.js +75 -9
- package/scripts/prove-predictive-insights.js +1 -3
- package/scripts/prove-training-export.js +1 -3
- package/scripts/prove-workflow-contract.js +1 -5
- package/scripts/rate-limiter.js +3 -1
- package/scripts/reddit-dm-outreach.js +14 -4
- package/scripts/schedule-manager.js +3 -5
- package/scripts/security-scanner.js +448 -0
- package/scripts/self-distill-agent.js +579 -0
- package/scripts/semantic-dedup.js +115 -0
- package/scripts/skill-exporter.js +1 -3
- package/scripts/skill-generator.js +1 -5
- package/scripts/social-analytics/engagement-audit.js +1 -18
- package/scripts/social-analytics/pollers/linkedin.js +26 -16
- package/scripts/social-analytics/publishers/linkedin.js +1 -1
- package/scripts/social-analytics/publishers/zernio.js +51 -0
- package/scripts/social-pipeline.js +1 -3
- package/scripts/social-post-hourly.js +47 -4
- package/scripts/statusline-links.js +6 -5
- package/scripts/statusline.sh +29 -153
- package/scripts/sync-branch-protection.js +340 -0
- package/scripts/tessl-export.js +1 -3
- package/scripts/thumbgate-search.js +32 -1
- package/scripts/tool-kpi-tracker.js +1 -1
- package/scripts/tool-registry.js +106 -2
- package/scripts/vector-store.js +1 -5
- package/scripts/weekly-auto-post.js +1 -1
- package/scripts/workflow-sentinel.js +91 -0
- package/skills/thumbgate/SKILL.md +1 -1
- package/src/api/server.js +273 -4
- package/scripts/social-analytics/db/social-analytics.db-shm +0 -0
- /package/scripts/social-analytics/db/{social-analytics.db-wal → analytics.sqlite} +0 -0
|
@@ -572,13 +572,24 @@ function runGh(args) {
|
|
|
572
572
|
});
|
|
573
573
|
}
|
|
574
574
|
|
|
575
|
-
function
|
|
575
|
+
function resolveGitHubRepository(env = process.env) {
|
|
576
|
+
const repository = String(env.GITHUB_REPOSITORY || '').trim();
|
|
577
|
+
return /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repository) ? repository : null;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function findOpenPrForBranch({ branchName, runner = runGh, env = process.env } = {}) {
|
|
576
581
|
const normalizedBranch = String(branchName || '').trim();
|
|
577
582
|
if (!normalizedBranch) return null;
|
|
578
|
-
if (!
|
|
583
|
+
if (!env.GH_TOKEN && !env.GITHUB_TOKEN) {
|
|
579
584
|
return null;
|
|
580
585
|
}
|
|
581
|
-
const
|
|
586
|
+
const args = ['pr', 'list'];
|
|
587
|
+
const repository = resolveGitHubRepository(env);
|
|
588
|
+
if (repository) {
|
|
589
|
+
args.push('--repo', repository);
|
|
590
|
+
}
|
|
591
|
+
args.push('--head', normalizedBranch, '--state', 'open', '--json', 'number,state,isDraft,url');
|
|
592
|
+
const result = runner(args);
|
|
582
593
|
if (!result || result.status !== 0) {
|
|
583
594
|
return null;
|
|
584
595
|
}
|
|
@@ -846,6 +857,7 @@ module.exports = {
|
|
|
846
857
|
resolveGitBinary,
|
|
847
858
|
resolveBaseRef,
|
|
848
859
|
resolveCiBranchName,
|
|
860
|
+
resolveGitHubRepository,
|
|
849
861
|
resolveRepoRoot,
|
|
850
862
|
runCli,
|
|
851
863
|
sanitizeGlobList,
|
package/scripts/org-dashboard.js
CHANGED
|
@@ -19,6 +19,11 @@ const path = require('path');
|
|
|
19
19
|
const { resolveFeedbackDir } = require('./feedback-paths');
|
|
20
20
|
const { readAuditLog, auditStats, skillAdherence } = require('./audit-trail');
|
|
21
21
|
const { isProTier } = require('./rate-limiter');
|
|
22
|
+
const {
|
|
23
|
+
PRO_MONTHLY_PAYMENT_LINK,
|
|
24
|
+
PRO_PRICE_LABEL,
|
|
25
|
+
TEAM_PRICE_LABEL,
|
|
26
|
+
} = require('./commercial-offer');
|
|
22
27
|
|
|
23
28
|
// ---------------------------------------------------------------------------
|
|
24
29
|
// Agent Registry
|
|
@@ -181,7 +186,7 @@ function generateOrgDashboard(opts = {}) {
|
|
|
181
186
|
};
|
|
182
187
|
|
|
183
188
|
if (!pro) {
|
|
184
|
-
summary.upgradeMessage =
|
|
189
|
+
summary.upgradeMessage = `Pro checkout: ${PRO_PRICE_LABEL} — ${PRO_MONTHLY_PAYMENT_LINK} | Team: ${TEAM_PRICE_LABEL} after workflow qualification.`;
|
|
185
190
|
}
|
|
186
191
|
|
|
187
192
|
return summary;
|
|
@@ -15,11 +15,9 @@
|
|
|
15
15
|
const fs = require('fs');
|
|
16
16
|
const path = require('path');
|
|
17
17
|
const { resolveFeedbackDir } = require('./feedback-paths');
|
|
18
|
+
const { ensureParentDir, readJsonl } = require('./fs-utils');
|
|
18
19
|
|
|
19
20
|
function getFeedbackDir() { return resolveFeedbackDir(); }
|
|
20
|
-
function ensureDir(fp) { const d = path.dirname(fp); if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true }); }
|
|
21
|
-
function readJsonl(fp) { if (!fs.existsSync(fp)) return []; const raw = fs.readFileSync(fp, 'utf-8').trim(); if (!raw) return []; return raw.split('\n').map((l) => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean); }
|
|
22
|
-
|
|
23
21
|
const SCORES_FILE = 'step-scores.jsonl';
|
|
24
22
|
function getScoresPath() { return path.join(getFeedbackDir(), SCORES_FILE); }
|
|
25
23
|
|
|
@@ -62,7 +60,7 @@ function scoreStep(auditEntry) {
|
|
|
62
60
|
function scoreAuditTrail(auditEntries) {
|
|
63
61
|
const scores = auditEntries.map(scoreStep);
|
|
64
62
|
const scoresPath = getScoresPath();
|
|
65
|
-
|
|
63
|
+
ensureParentDir(scoresPath);
|
|
66
64
|
for (const s of scores) fs.appendFileSync(scoresPath, JSON.stringify(s) + '\n');
|
|
67
65
|
return { scored: scores.length, scores };
|
|
68
66
|
}
|
package/scripts/pr-manager.js
CHANGED
|
@@ -8,8 +8,19 @@
|
|
|
8
8
|
|
|
9
9
|
'use strict';
|
|
10
10
|
|
|
11
|
-
const
|
|
12
|
-
const
|
|
11
|
+
const fs = require('node:fs');
|
|
12
|
+
const path = require('node:path');
|
|
13
|
+
const { spawnSync } = require('node:child_process');
|
|
14
|
+
const PR_FIELDS = 'number,state,mergeable,mergeStateStatus,statusCheckRollup,reviewDecision,isDraft,title,url,headRefOid,baseRefName,mergeCommit,mergedAt,mergedBy';
|
|
15
|
+
const PR_CHECK_FIELDS = 'bucket,name,state,workflow,link,event';
|
|
16
|
+
const MERGE_QUALITY_CHECKS = JSON.parse(
|
|
17
|
+
fs.readFileSync(path.join(__dirname, '..', 'config', 'merge-quality-checks.json'), 'utf8')
|
|
18
|
+
);
|
|
19
|
+
const FIXED_GH_BINARIES = [
|
|
20
|
+
'/usr/bin/gh',
|
|
21
|
+
'/usr/local/bin/gh',
|
|
22
|
+
'/opt/homebrew/bin/gh',
|
|
23
|
+
];
|
|
13
24
|
const SUCCESSFUL_CHECK_CONCLUSIONS = new Set(['SUCCESS', 'SKIPPED', 'NEUTRAL']);
|
|
14
25
|
const FAILING_CHECK_CONCLUSIONS = new Set([
|
|
15
26
|
'ACTION_REQUIRED',
|
|
@@ -19,9 +30,71 @@ const FAILING_CHECK_CONCLUSIONS = new Set([
|
|
|
19
30
|
'STARTUP_FAILURE',
|
|
20
31
|
'TIMED_OUT',
|
|
21
32
|
]);
|
|
33
|
+
const PASSING_BUCKETS = new Set((MERGE_QUALITY_CHECKS.passingBuckets || []).map((value) => String(value || '').toLowerCase()));
|
|
34
|
+
const PENDING_BUCKETS = new Set((MERGE_QUALITY_CHECKS.pendingBuckets || []).map((value) => String(value || '').toLowerCase()));
|
|
35
|
+
const FAILING_BUCKETS = new Set((MERGE_QUALITY_CHECKS.failingBuckets || []).map((value) => String(value || '').toLowerCase()));
|
|
22
36
|
|
|
23
|
-
function
|
|
24
|
-
|
|
37
|
+
function assertSafeGhArgs(args) {
|
|
38
|
+
if (!Array.isArray(args) || args.length === 0) {
|
|
39
|
+
throw new Error('GH CLI args must be a non-empty array.');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return args.map((arg) => {
|
|
43
|
+
const normalized = String(arg ?? '');
|
|
44
|
+
if (!normalized || /\0/.test(normalized)) {
|
|
45
|
+
throw new Error(`Unsafe GH CLI arg: ${arg}`);
|
|
46
|
+
}
|
|
47
|
+
return normalized;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function normalizePrNumber(prNumber, { allowEmpty = true } = {}) {
|
|
52
|
+
const normalized = String(prNumber ?? '').trim();
|
|
53
|
+
if (!normalized) {
|
|
54
|
+
if (allowEmpty) {
|
|
55
|
+
return '';
|
|
56
|
+
}
|
|
57
|
+
throw new Error('PR number is required.');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!/^[1-9]\d*$/.test(normalized)) {
|
|
61
|
+
throw new Error(`Unsafe PR number: ${prNumber}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return normalized;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function resolveGhBinary(options = {}) {
|
|
68
|
+
const accessSync = options.accessSync || fs.accessSync;
|
|
69
|
+
const candidates = [];
|
|
70
|
+
const configuredBinary = String(process.env.THUMBGATE_GH_BIN || '').trim();
|
|
71
|
+
|
|
72
|
+
if (configuredBinary) {
|
|
73
|
+
if (!path.isAbsolute(configuredBinary)) {
|
|
74
|
+
throw new Error(`Unsafe GH binary path: ${configuredBinary}`);
|
|
75
|
+
}
|
|
76
|
+
candidates.push(configuredBinary);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
candidates.push(...FIXED_GH_BINARIES);
|
|
80
|
+
|
|
81
|
+
for (const candidate of candidates) {
|
|
82
|
+
try {
|
|
83
|
+
accessSync(candidate, fs.constants.X_OK);
|
|
84
|
+
return candidate;
|
|
85
|
+
} catch {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
throw new Error(`Unable to locate GH CLI in fixed paths: ${candidates.join(', ')}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function runGh(args, options = {}) {
|
|
94
|
+
return spawnSync(resolveGhBinary(options), assertSafeGhArgs(args), {
|
|
95
|
+
encoding: 'utf-8',
|
|
96
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
97
|
+
});
|
|
25
98
|
}
|
|
26
99
|
|
|
27
100
|
function formatGhError(result) {
|
|
@@ -40,13 +113,14 @@ function isMissingCurrentBranchPr(result, prNumber) {
|
|
|
40
113
|
* Fetch granular PR status using GH CLI
|
|
41
114
|
*/
|
|
42
115
|
function getPrStatus(prNumber = '', runner = runGh) {
|
|
116
|
+
const normalizedPrNumber = normalizePrNumber(prNumber);
|
|
43
117
|
const args = ['pr', 'view'];
|
|
44
|
-
if (
|
|
118
|
+
if (normalizedPrNumber) args.push(normalizedPrNumber);
|
|
45
119
|
args.push('--json', PR_FIELDS);
|
|
46
120
|
|
|
47
121
|
const result = runner(args);
|
|
48
122
|
if (result.status !== 0) {
|
|
49
|
-
if (isMissingCurrentBranchPr(result,
|
|
123
|
+
if (isMissingCurrentBranchPr(result, normalizedPrNumber)) {
|
|
50
124
|
return null;
|
|
51
125
|
}
|
|
52
126
|
|
|
@@ -55,6 +129,16 @@ function getPrStatus(prNumber = '', runner = runGh) {
|
|
|
55
129
|
return JSON.parse(result.stdout);
|
|
56
130
|
}
|
|
57
131
|
|
|
132
|
+
function getPrChecks(prNumber = '', runner = runGh) {
|
|
133
|
+
const normalizedPrNumber = normalizePrNumber(prNumber, { allowEmpty: false });
|
|
134
|
+
const result = runner(['pr', 'checks', normalizedPrNumber, '--json', PR_CHECK_FIELDS]);
|
|
135
|
+
if (result.status !== 0) {
|
|
136
|
+
throw new Error(`Failed to fetch PR checks: ${formatGhError(result)}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return JSON.parse(result.stdout || '[]');
|
|
140
|
+
}
|
|
141
|
+
|
|
58
142
|
function listOpenPrs(runner = runGh) {
|
|
59
143
|
const result = runner(['pr', 'list', '--state', 'open', '--json', PR_FIELDS]);
|
|
60
144
|
if (result.status !== 0) {
|
|
@@ -87,6 +171,23 @@ function summarizeChecks(checks = []) {
|
|
|
87
171
|
|
|
88
172
|
for (const check of checks) {
|
|
89
173
|
const name = check.name || 'unknown-check';
|
|
174
|
+
const bucket = String(check.bucket || '').toLowerCase();
|
|
175
|
+
if (bucket) {
|
|
176
|
+
if (FAILING_BUCKETS.has(bucket)) {
|
|
177
|
+
failing.push(name);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (PENDING_BUCKETS.has(bucket)) {
|
|
182
|
+
pending.push(name);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (PASSING_BUCKETS.has(bucket)) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
90
191
|
const conclusion = check.conclusion || null;
|
|
91
192
|
const status = check.status || (conclusion ? 'COMPLETED' : 'UNKNOWN');
|
|
92
193
|
|
|
@@ -108,6 +209,11 @@ function summarizeChecks(checks = []) {
|
|
|
108
209
|
return { failing, pending };
|
|
109
210
|
}
|
|
110
211
|
|
|
212
|
+
function sleep(ms) {
|
|
213
|
+
if (!ms || ms <= 0) return;
|
|
214
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
215
|
+
}
|
|
216
|
+
|
|
111
217
|
/**
|
|
112
218
|
* Diagnose and resolve blockers autonomously
|
|
113
219
|
*/
|
|
@@ -140,17 +246,29 @@ async function resolveBlockers(pr, runner = runGh) {
|
|
|
140
246
|
}
|
|
141
247
|
|
|
142
248
|
// 3. Handle CI Failures
|
|
143
|
-
|
|
249
|
+
let checks = pr.statusCheckRollup || [];
|
|
250
|
+
let checkSource = 'statusCheckRollup';
|
|
251
|
+
|
|
252
|
+
if (pr.number) {
|
|
253
|
+
try {
|
|
254
|
+
checks = getPrChecks(pr.number, runner);
|
|
255
|
+
checkSource = 'gh pr checks';
|
|
256
|
+
} catch (error) {
|
|
257
|
+
console.warn(`[PR Manager] Falling back to statusCheckRollup for PR #${pr.number}: ${error.message}`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const checkSummary = summarizeChecks(checks);
|
|
144
262
|
const failingChecks = checkSummary.failing;
|
|
145
263
|
|
|
146
264
|
if (failingChecks.length > 0) {
|
|
147
|
-
console.log(`[PR Manager] BLOCKED: ${failingChecks.length} failing
|
|
148
|
-
return { status: 'blocked', reason: 'ci_failure', checks: failingChecks };
|
|
265
|
+
console.log(`[PR Manager] BLOCKED: ${failingChecks.length} failing quality checks via ${checkSource}.`);
|
|
266
|
+
return { status: 'blocked', reason: 'ci_failure', checks: failingChecks, checkSource };
|
|
149
267
|
}
|
|
150
268
|
|
|
151
269
|
if (checkSummary.pending.length > 0) {
|
|
152
|
-
console.log(`[PR Manager] BLOCKED: ${checkSummary.pending.length}
|
|
153
|
-
return { status: 'blocked', reason: 'ci_pending', checks: checkSummary.pending };
|
|
270
|
+
console.log(`[PR Manager] BLOCKED: ${checkSummary.pending.length} quality checks still pending via ${checkSource}.`);
|
|
271
|
+
return { status: 'blocked', reason: 'ci_pending', checks: checkSummary.pending, checkSource };
|
|
154
272
|
}
|
|
155
273
|
|
|
156
274
|
// 4. Handle Review Blockers
|
|
@@ -173,25 +291,74 @@ async function resolveBlockers(pr, runner = runGh) {
|
|
|
173
291
|
return { status: 'pending', reason: 'unknown_state' };
|
|
174
292
|
}
|
|
175
293
|
|
|
294
|
+
function waitForMergeCommit(prNumber, runner = runGh, options = {}) {
|
|
295
|
+
const timeoutMs = Number.isFinite(options.timeoutMs) ? options.timeoutMs : 300000;
|
|
296
|
+
const intervalMs = Number.isFinite(options.intervalMs) ? options.intervalMs : 10000;
|
|
297
|
+
const startedAt = Date.now();
|
|
298
|
+
|
|
299
|
+
do {
|
|
300
|
+
const pr = getPrStatus(prNumber, runner);
|
|
301
|
+
if (pr && String(pr.state || '').toUpperCase() === 'MERGED' && pr.mergeCommit && pr.mergeCommit.oid) {
|
|
302
|
+
return {
|
|
303
|
+
finalized: true,
|
|
304
|
+
merged: true,
|
|
305
|
+
mergeCommit: pr.mergeCommit.oid,
|
|
306
|
+
mergedAt: pr.mergedAt || null,
|
|
307
|
+
mergedBy: pr.mergedBy && pr.mergedBy.login ? pr.mergedBy.login : null,
|
|
308
|
+
pr,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (pr && String(pr.state || '').toUpperCase() === 'CLOSED') {
|
|
313
|
+
return {
|
|
314
|
+
finalized: true,
|
|
315
|
+
merged: false,
|
|
316
|
+
reason: 'closed_without_merge',
|
|
317
|
+
pr,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (intervalMs <= 0) {
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if ((Date.now() - startedAt + intervalMs) > timeoutMs) {
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
sleep(intervalMs);
|
|
330
|
+
} while ((Date.now() - startedAt) <= timeoutMs);
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
finalized: false,
|
|
334
|
+
merged: false,
|
|
335
|
+
reason: 'merge_commit_pending',
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
176
339
|
/**
|
|
177
340
|
* Perform autonomous merge
|
|
178
341
|
*/
|
|
179
|
-
function performMerge(prNumber, runner = runGh) {
|
|
180
|
-
const
|
|
181
|
-
|
|
342
|
+
function performMerge(prNumber, runner = runGh, options = {}) {
|
|
343
|
+
const normalizedPrNumber = normalizePrNumber(prNumber, { allowEmpty: false });
|
|
344
|
+
const args = ['pr', 'merge', normalizedPrNumber, '--squash', '--delete-branch'];
|
|
345
|
+
console.log(`[PR Manager] Initiating protected squash merge for PR #${normalizedPrNumber}...`);
|
|
182
346
|
const result = runner(args);
|
|
183
347
|
if (result.status === 0) {
|
|
184
348
|
const output = `${result.stdout || ''}\n${result.stderr || ''}`;
|
|
185
|
-
const mode = /merge queue|queued
|
|
186
|
-
console.log(`[PR Manager] Merge accepted for PR #${
|
|
187
|
-
|
|
349
|
+
const mode = /merge queue|queued/i.test(output) ? 'queued' : 'merged';
|
|
350
|
+
console.log(`[PR Manager] Merge accepted for PR #${normalizedPrNumber} (${mode}).`);
|
|
351
|
+
const mergeStatus = options.waitForMerge === false
|
|
352
|
+
? { finalized: false, merged: false, reason: 'merge_commit_pending' }
|
|
353
|
+
: waitForMergeCommit(normalizedPrNumber, runner, options);
|
|
354
|
+
return { ok: true, mode, args, ...mergeStatus };
|
|
188
355
|
} else {
|
|
189
356
|
console.error(`[PR Manager] Merge failed: ${formatGhError(result)}`);
|
|
190
357
|
return { ok: false, mode: 'failed', args, error: formatGhError(result) };
|
|
191
358
|
}
|
|
192
359
|
}
|
|
193
360
|
|
|
194
|
-
async function managePrs(prNumber = '', runner = runGh) {
|
|
361
|
+
async function managePrs(prNumber = '', runner = runGh, options = {}) {
|
|
195
362
|
const prs = loadManagedPrs(prNumber, runner).filter(Boolean);
|
|
196
363
|
|
|
197
364
|
if (prs.length === 0) {
|
|
@@ -203,9 +370,18 @@ async function managePrs(prNumber = '', runner = runGh) {
|
|
|
203
370
|
for (const pr of prs) {
|
|
204
371
|
const outcome = await resolveBlockers(pr, runner);
|
|
205
372
|
if (outcome.status === 'ready') {
|
|
206
|
-
const mergeResult = performMerge(pr.number, runner);
|
|
373
|
+
const mergeResult = performMerge(pr.number, runner, options);
|
|
207
374
|
outcome.mergeRequested = mergeResult.ok;
|
|
208
375
|
outcome.mergeMode = mergeResult.mode;
|
|
376
|
+
if (mergeResult.mergeCommit) {
|
|
377
|
+
outcome.mergeCommit = mergeResult.mergeCommit;
|
|
378
|
+
}
|
|
379
|
+
if (mergeResult.finalized !== undefined) {
|
|
380
|
+
outcome.mergeFinalized = mergeResult.finalized;
|
|
381
|
+
}
|
|
382
|
+
if (mergeResult.reason) {
|
|
383
|
+
outcome.mergeResolution = mergeResult.reason;
|
|
384
|
+
}
|
|
209
385
|
}
|
|
210
386
|
|
|
211
387
|
results.push({
|
|
@@ -229,11 +405,17 @@ if (require.main === module) {
|
|
|
229
405
|
}
|
|
230
406
|
|
|
231
407
|
module.exports = {
|
|
408
|
+
assertSafeGhArgs,
|
|
232
409
|
getPrStatus,
|
|
410
|
+
getPrChecks,
|
|
233
411
|
listOpenPrs,
|
|
234
412
|
isOpenPr,
|
|
235
413
|
loadManagedPrs,
|
|
414
|
+
normalizePrNumber,
|
|
236
415
|
resolveBlockers,
|
|
416
|
+
resolveGhBinary,
|
|
417
|
+
waitForMergeCommit,
|
|
237
418
|
performMerge,
|
|
238
419
|
managePrs,
|
|
420
|
+
summarizeChecks,
|
|
239
421
|
};
|
package/scripts/pro-features.js
CHANGED
|
@@ -3,6 +3,7 @@ const { isProLicensed } = require('./license');
|
|
|
3
3
|
const {
|
|
4
4
|
PRO_MONTHLY_PAYMENT_LINK,
|
|
5
5
|
PRO_PRICE_LABEL,
|
|
6
|
+
TEAM_PRICE_LABEL,
|
|
6
7
|
} = require('./commercial-offer');
|
|
7
8
|
|
|
8
9
|
const PRO_URL = PRO_MONTHLY_PAYMENT_LINK;
|
|
@@ -30,8 +31,8 @@ function requirePro(
|
|
|
30
31
|
const desc = descriptions[featureName] || featureName;
|
|
31
32
|
write(
|
|
32
33
|
`\n 🔒 Pro Feature Required: ${desc}\n` +
|
|
33
|
-
`
|
|
34
|
-
` ${
|
|
34
|
+
` Pro: ${PRO_PRICE_LABEL} — ${PRO_URL}\n` +
|
|
35
|
+
` Team: ${TEAM_PRICE_LABEL} after workflow qualification\n` +
|
|
35
36
|
` Or run: npx thumbgate pro\n\n`
|
|
36
37
|
);
|
|
37
38
|
return false;
|
package/scripts/prompt-dlp.js
CHANGED
|
@@ -16,6 +16,7 @@ const { SECRET_PATTERNS } = require('./secret-scanner');
|
|
|
16
16
|
const fs = require('fs');
|
|
17
17
|
const path = require('path');
|
|
18
18
|
const { resolveFeedbackDir } = require('./feedback-paths');
|
|
19
|
+
const { ensureParentDir } = require('./fs-utils');
|
|
19
20
|
|
|
20
21
|
const DLP_LOG_FILE = 'dlp-events.jsonl';
|
|
21
22
|
const DEFAULT_MAX_SENSITIVITY = 'internal'; // block sensitive + restricted
|
|
@@ -24,7 +25,6 @@ function getDlpLogPath() {
|
|
|
24
25
|
return path.join(resolveFeedbackDir(), DLP_LOG_FILE);
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
function ensureDir(fp) { const d = path.dirname(fp); if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true }); }
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* Scan a tool call input for PII and secrets before execution.
|
|
@@ -75,7 +75,7 @@ function scanToolCallInput({ toolName, input, agentId, maxSensitivity } = {}) {
|
|
|
75
75
|
|
|
76
76
|
// Log the event
|
|
77
77
|
const logPath = getDlpLogPath();
|
|
78
|
-
|
|
78
|
+
ensureParentDir(logPath);
|
|
79
79
|
fs.appendFileSync(logPath, JSON.stringify(event) + '\n');
|
|
80
80
|
|
|
81
81
|
return {
|
|
@@ -123,7 +123,7 @@ function detectShadowAction({ toolName, source, agentId } = {}) {
|
|
|
123
123
|
gated: false,
|
|
124
124
|
};
|
|
125
125
|
const logPath = getShadowLogPath();
|
|
126
|
-
|
|
126
|
+
ensureParentDir(logPath);
|
|
127
127
|
fs.appendFileSync(logPath, JSON.stringify(event) + '\n');
|
|
128
128
|
return { isShadow: true, event };
|
|
129
129
|
}
|
|
@@ -8,15 +8,11 @@ const { startServer } = require('../src/api/server');
|
|
|
8
8
|
const { handleRequest } = require('../adapters/mcp/server-stdio');
|
|
9
9
|
const { validateSubagentProfiles, listSubagentProfiles } = require('./subagent-profiles');
|
|
10
10
|
const { getAllowedTools } = require('./mcp-policy');
|
|
11
|
+
const { ensureDir } = require('./fs-utils');
|
|
11
12
|
|
|
12
13
|
const ROOT = path.join(__dirname, '..');
|
|
13
14
|
const DEFAULT_PROOF_DIR = path.join(ROOT, 'proof', 'compatibility');
|
|
14
15
|
|
|
15
|
-
function ensureDir(dirPath) {
|
|
16
|
-
if (!fs.existsSync(dirPath)) {
|
|
17
|
-
fs.mkdirSync(dirPath, { recursive: true });
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
16
|
|
|
21
17
|
function check(condition, message) {
|
|
22
18
|
if (!condition) {
|
|
@@ -18,17 +18,13 @@ const path = require('path');
|
|
|
18
18
|
const os = require('os');
|
|
19
19
|
const { execSync } = require('child_process');
|
|
20
20
|
const { escapeMarkdownTableCell } = require('./markdown-escape');
|
|
21
|
+
const { ensureDir } = require('./fs-utils');
|
|
21
22
|
|
|
22
23
|
const ROOT = path.join(__dirname, '..');
|
|
23
24
|
|
|
24
25
|
// Phase 5 node-runner test baseline (before Phase 6 attribution tests)
|
|
25
26
|
const PHASE5_BASELINE = 142;
|
|
26
27
|
|
|
27
|
-
function ensureDir(dirPath) {
|
|
28
|
-
if (!fs.existsSync(dirPath)) {
|
|
29
|
-
fs.mkdirSync(dirPath, { recursive: true });
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
28
|
|
|
33
29
|
async function runProof(options = {}) {
|
|
34
30
|
const proofDir = options.proofDir || process.env.THUMBGATE_PROOF_DIR || path.join(ROOT, 'proof');
|
|
@@ -22,13 +22,11 @@ const { traceForProofCheck, aggregateTraces } = require('./code-reasoning');
|
|
|
22
22
|
const { runVerificationLoop } = require('./verification-loop');
|
|
23
23
|
const { run: runGateCheck } = require('./gates-engine');
|
|
24
24
|
const { evaluatePromptGuard } = require('./prompt-guard');
|
|
25
|
+
const { ensureDir } = require('./fs-utils');
|
|
25
26
|
|
|
26
27
|
const ROOT = path.join(__dirname, '..');
|
|
27
28
|
const DEFAULT_PROOF_DIR = path.join(ROOT, 'proof', 'automation');
|
|
28
29
|
|
|
29
|
-
function ensureDir(dirPath) {
|
|
30
|
-
if (!fs.existsSync(dirPath)) fs.mkdirSync(dirPath, { recursive: true });
|
|
31
|
-
}
|
|
32
30
|
|
|
33
31
|
function check(condition, message) {
|
|
34
32
|
if (!condition) throw new Error(message);
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const { spawnSync } = require('child_process');
|
|
7
|
+
const { ensureDir } = require('./fs-utils');
|
|
7
8
|
|
|
8
9
|
const {
|
|
9
10
|
buildCloudflareSandboxPlan,
|
|
@@ -17,9 +18,6 @@ function npmCommand() {
|
|
|
17
18
|
return process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
function ensureDir(dirPath) {
|
|
21
|
-
fs.mkdirSync(dirPath, { recursive: true });
|
|
22
|
-
}
|
|
23
21
|
|
|
24
22
|
function runCommand(command, args, cwd = ROOT) {
|
|
25
23
|
const result = spawnSync(command, args, {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const fs = require('node:fs');
|
|
4
4
|
const os = require('node:os');
|
|
5
5
|
const path = require('node:path');
|
|
6
|
+
const { ensureDir } = require('./fs-utils');
|
|
6
7
|
|
|
7
8
|
const ROOT = path.join(__dirname, '..');
|
|
8
9
|
|
|
@@ -400,9 +401,6 @@ async function run() {
|
|
|
400
401
|
}
|
|
401
402
|
}
|
|
402
403
|
|
|
403
|
-
function ensureDir(dirPath) {
|
|
404
|
-
fs.mkdirSync(dirPath, { recursive: true });
|
|
405
|
-
}
|
|
406
404
|
|
|
407
405
|
run().catch((error) => {
|
|
408
406
|
console.error(error.message);
|
|
@@ -13,15 +13,13 @@ const fs = require('fs');
|
|
|
13
13
|
const os = require('os');
|
|
14
14
|
const path = require('path');
|
|
15
15
|
const { execSync } = require('child_process');
|
|
16
|
+
const { ensureDir } = require('./fs-utils');
|
|
16
17
|
|
|
17
18
|
const ROOT = path.join(__dirname, '..');
|
|
18
19
|
function getProofDir() {
|
|
19
20
|
return process.env.THUMBGATE_PROOF_DIR || path.join(ROOT, 'proof');
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
function ensureDir(d) {
|
|
23
|
-
if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true });
|
|
24
|
-
}
|
|
25
23
|
|
|
26
24
|
// ---------------------------------------------------------------------------
|
|
27
25
|
// Run test suite and parse results
|
package/scripts/prove-lancedb.js
CHANGED
|
@@ -17,6 +17,7 @@ const path = require('path');
|
|
|
17
17
|
const os = require('os');
|
|
18
18
|
const { execSync } = require('child_process');
|
|
19
19
|
const { escapeMarkdownTableCell } = require('./markdown-escape');
|
|
20
|
+
const { ensureDir } = require('./fs-utils');
|
|
20
21
|
|
|
21
22
|
const ROOT = path.join(__dirname, '..');
|
|
22
23
|
const PKG = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf-8'));
|
|
@@ -63,11 +64,6 @@ function createInMemoryLanceLoader() {
|
|
|
63
64
|
});
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
function ensureDir(dirPath) {
|
|
67
|
-
if (!fs.existsSync(dirPath)) {
|
|
68
|
-
fs.mkdirSync(dirPath, { recursive: true });
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
67
|
|
|
72
68
|
function status(condition) {
|
|
73
69
|
return condition ? 'pass' : 'fail';
|
|
@@ -5,13 +5,11 @@ const fs = require('fs');
|
|
|
5
5
|
const os = require('os');
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const { execSync } = require('child_process');
|
|
8
|
+
const { ensureDir } = require('./fs-utils');
|
|
8
9
|
|
|
9
10
|
const ROOT = path.join(__dirname, '..');
|
|
10
11
|
const DEFAULT_PROOF_DIR = process.env.THUMBGATE_PROOF_DIR || path.join(ROOT, 'proof');
|
|
11
12
|
|
|
12
|
-
function ensureDir(dirPath) {
|
|
13
|
-
if (!fs.existsSync(dirPath)) fs.mkdirSync(dirPath, { recursive: true });
|
|
14
|
-
}
|
|
15
13
|
|
|
16
14
|
function runTests() {
|
|
17
15
|
try {
|