thumbgate 1.27.4 → 1.27.7
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/commands/dashboard.md +15 -0
- package/.claude/commands/thumbgate-blocked.md +27 -0
- package/.claude/commands/thumbgate-dashboard.md +15 -0
- package/.claude/commands/thumbgate-doctor.md +30 -0
- package/.claude/commands/thumbgate-guard.md +36 -0
- package/.claude/commands/thumbgate-protect.md +30 -0
- package/.claude/commands/thumbgate-rules.md +30 -0
- package/.claude-plugin/plugin.json +2 -1
- package/.well-known/llms.txt +6 -2
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +49 -5
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/letta/README.md +41 -0
- package/adapters/letta/thumbgate-letta-adapter.js +133 -0
- package/adapters/mcp/server-stdio.js +16 -1
- package/adapters/opencode/opencode.json +1 -1
- package/adapters/policy-engine/ethicore-guardian-client.js +68 -0
- package/adapters/policy-engine/thumbgate-policy-engine-adapter.js +260 -0
- package/bench/observability-eval-suite.json +26 -0
- package/bin/cli.js +230 -6
- package/bin/postinstall.js +1 -1
- package/commands/dashboard.md +15 -0
- package/commands/thumbgate-dashboard.md +15 -0
- package/config/gate-templates.json +84 -0
- package/config/gates/claim-verification.json +12 -0
- package/config/gates/default.json +20 -0
- package/config/github-about.json +1 -1
- package/config/model-candidates.json +50 -0
- package/config/post-deploy-marketing-pages.json +5 -0
- package/package.json +67 -25
- package/public/agent-manager.html +41 -1
- package/public/agents-cost-savings.html +1 -1
- package/public/ai-malpractice-prevention.html +2 -1
- package/public/assets/brand/github-social-preview.png +0 -0
- package/public/assets/brand/thumbgate-icon-512.png +0 -0
- package/public/assets/brand/thumbgate-icon-pro-512.png +0 -0
- package/public/assets/brand/thumbgate-icon-team-512.png +0 -0
- package/public/assets/brand/thumbgate-logo-1200x360.png +0 -0
- package/public/assets/brand/thumbgate-mark-inline.svg +15 -0
- package/public/assets/brand/thumbgate-mark-pro.svg +23 -0
- package/public/assets/brand/thumbgate-mark-team.svg +26 -0
- package/public/assets/brand/thumbgate-mark.svg +15 -0
- package/public/assets/brand/thumbgate-wordmark.svg +20 -0
- package/public/assets/claude-thumbgate-statusbar.svg +8 -0
- package/public/assets/codex-thumbgate-statusbar-test.svg +9 -0
- package/public/assets/legal-intake-control-flow.svg +66 -0
- package/public/blog.html +1 -1
- package/public/brand/thumbgate-mark.svg +15 -0
- package/public/brand/thumbgate-og.svg +16 -0
- package/public/codex-enterprise.html +1 -1
- package/public/codex-plugin.html +1 -1
- package/public/compare.html +23 -3
- package/public/dashboard.html +316 -30
- package/public/federal.html +1 -1
- package/public/guide.html +5 -4
- package/public/index.html +167 -49
- package/public/js/buyer-intent.js +672 -0
- package/public/learn.html +88 -7
- package/public/lessons.html +2 -1
- package/public/numbers.html +3 -3
- package/public/pricing.html +63 -15
- package/public/pro.html +7 -7
- package/scripts/activation-quickstart.js +187 -0
- package/scripts/agent-memory-lifecycle.js +211 -0
- package/scripts/async-eval-observability.js +236 -0
- package/scripts/auto-promote-gates.js +75 -4
- package/scripts/billing.js +12 -1
- package/scripts/build-metadata.js +24 -3
- package/scripts/cli-schema.js +42 -10
- package/scripts/dashboard-chat.js +53 -7
- package/scripts/dashboard.js +12 -17
- package/scripts/export-databricks-bundle.js +5 -1
- package/scripts/export-dpo-pairs.js +7 -2
- package/scripts/feedback-aggregate.js +281 -0
- package/scripts/feedback-loop.js +121 -0
- package/scripts/filesystem-search.js +35 -10
- package/scripts/gates-engine.js +234 -7
- package/scripts/gemini-embedding-policy.js +2 -1
- package/scripts/hook-stop-anti-claim.js +227 -0
- package/scripts/hook-thumbgate-cache-updater.js +18 -2
- package/scripts/hybrid-feedback-context.js +1 -0
- package/scripts/lesson-inference.js +8 -3
- package/scripts/lesson-search.js +17 -1
- package/scripts/operational-integrity.js +39 -5
- package/scripts/plausible-domain-config.js +15 -2
- package/scripts/plausible-server-events.js +4 -4
- package/scripts/rate-limiter.js +12 -6
- package/scripts/secret-redaction.js +166 -0
- package/scripts/security-scanner.js +100 -0
- package/scripts/self-distill-agent.js +3 -1
- package/scripts/self-harness-optimizer.js +141 -0
- package/scripts/seo-gsd.js +635 -0
- package/scripts/statusline-cache-path.js +17 -2
- package/scripts/statusline-cache-read.js +57 -0
- package/scripts/statusline-local-stats.js +9 -1
- package/scripts/statusline-meta.js +5 -2
- package/scripts/statusline.sh +13 -1
- package/scripts/sync-telemetry-from-prod.js +374 -0
- package/scripts/telemetry-analytics.js +9 -0
- package/scripts/thumbgate-search.js +85 -19
- package/scripts/tool-contract-validator.js +76 -0
- package/scripts/vector-store.js +44 -0
- package/scripts/workspace-evolver.js +62 -2
- package/src/api/server.js +862 -146
|
@@ -151,8 +151,67 @@ function sortResults(results) {
|
|
|
151
151
|
});
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
function
|
|
155
|
-
|
|
154
|
+
function extractFeedbackId(str) {
|
|
155
|
+
if (!str) return null;
|
|
156
|
+
const match = str.match(/fb[_-]\d+[_-][a-z0-9]+/i);
|
|
157
|
+
return match ? match[0].replace(/-/g, '_').toLowerCase() : null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function deduplicateResults(results) {
|
|
161
|
+
const bestByFeedbackId = new Map();
|
|
162
|
+
|
|
163
|
+
for (const r of results) {
|
|
164
|
+
const feedId = extractFeedbackId(r.id || r.title || r.file || '');
|
|
165
|
+
if (feedId) {
|
|
166
|
+
const existing = bestByFeedbackId.get(feedId);
|
|
167
|
+
if (!existing) {
|
|
168
|
+
bestByFeedbackId.set(feedId, r);
|
|
169
|
+
} else {
|
|
170
|
+
const sourceOrder = { feedback: 4, contextfs: 3, prevention_rule: 2, document: 1 };
|
|
171
|
+
const existingOrder = sourceOrder[existing.source] || 0;
|
|
172
|
+
const currentOrder = sourceOrder[r.source] || 0;
|
|
173
|
+
if (currentOrder > existingOrder) {
|
|
174
|
+
bestByFeedbackId.set(feedId, r);
|
|
175
|
+
} else if (currentOrder === existingOrder && (r.score || 0) > (existing.score || 0)) {
|
|
176
|
+
bestByFeedbackId.set(feedId, r);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const finalResults = [];
|
|
183
|
+
const seenContent = new Set();
|
|
184
|
+
const seenIds = new Set();
|
|
185
|
+
|
|
186
|
+
for (const r of results) {
|
|
187
|
+
const feedId = extractFeedbackId(r.id || r.title || r.file || '');
|
|
188
|
+
let recordToUse = r;
|
|
189
|
+
if (feedId) {
|
|
190
|
+
recordToUse = bestByFeedbackId.get(feedId);
|
|
191
|
+
if (seenIds.has(recordToUse.id)) continue;
|
|
192
|
+
} else {
|
|
193
|
+
if (r.id && r.id !== 'null' && String(r.id).trim() !== '') {
|
|
194
|
+
if (seenIds.has(r.id)) continue;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const normTitle = String(recordToUse.title || '').trim().toLowerCase();
|
|
199
|
+
const normContext = String(recordToUse.context || '').trim().toLowerCase();
|
|
200
|
+
const contentKey = `${normTitle}|${normContext}`;
|
|
201
|
+
if (seenContent.has(contentKey)) continue;
|
|
202
|
+
|
|
203
|
+
if (recordToUse.id && recordToUse.id !== 'null' && String(recordToUse.id).trim() !== '') {
|
|
204
|
+
seenIds.add(recordToUse.id);
|
|
205
|
+
}
|
|
206
|
+
seenContent.add(contentKey);
|
|
207
|
+
finalResults.push(recordToUse);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return finalResults;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function getFeedbackResults(query, limit, signal, feedbackDir) {
|
|
214
|
+
const results = searchFeedbackLog(query, Math.max(limit * 3, limit), { feedbackDir });
|
|
156
215
|
const normalizedSignal = normalizeSignal(signal);
|
|
157
216
|
const filtered = normalizedSignal
|
|
158
217
|
? results.filter((record) => normalizeRecordSignal(record.signal) === normalizedSignal)
|
|
@@ -160,19 +219,19 @@ function getFeedbackResults(query, limit, signal) {
|
|
|
160
219
|
return filtered.slice(0, limit).map(mapFeedbackResult);
|
|
161
220
|
}
|
|
162
221
|
|
|
163
|
-
function getContextResults(query, limit) {
|
|
164
|
-
return searchContextFs(query, limit).map(mapContextResult);
|
|
222
|
+
function getContextResults(query, limit, feedbackDir) {
|
|
223
|
+
return searchContextFs(query, limit, { feedbackDir }).map(mapContextResult);
|
|
165
224
|
}
|
|
166
225
|
|
|
167
|
-
function getRuleResults(query, limit) {
|
|
168
|
-
return searchPreventionRulesSync(query, limit).map(mapRuleResult);
|
|
226
|
+
function getRuleResults(query, limit, feedbackDir) {
|
|
227
|
+
return searchPreventionRulesSync(query, limit, { feedbackDir }).map(mapRuleResult);
|
|
169
228
|
}
|
|
170
229
|
|
|
171
|
-
function getDocumentResults(query, limit) {
|
|
172
|
-
return searchImportedDocuments({ query, limit }).map(mapDocumentResult);
|
|
230
|
+
function getDocumentResults(query, limit, feedbackDir) {
|
|
231
|
+
return searchImportedDocuments({ query, limit, feedbackDir }).map(mapDocumentResult);
|
|
173
232
|
}
|
|
174
233
|
|
|
175
|
-
function searchThumbgate({ query, source = 'all', limit = 10, signal = null } = {}) {
|
|
234
|
+
function searchThumbgate({ query, source = 'all', limit = 10, signal = null, feedbackDir = null } = {}) {
|
|
176
235
|
const trimmedQuery = String(query || '').trim();
|
|
177
236
|
if (!trimmedQuery) {
|
|
178
237
|
throw new Error('query is required');
|
|
@@ -182,22 +241,29 @@ function searchThumbgate({ query, source = 'all', limit = 10, signal = null } =
|
|
|
182
241
|
const normalizedSignal = normalizeSignal(signal);
|
|
183
242
|
const normalizedLimit = normalizeLimit(limit);
|
|
184
243
|
|
|
244
|
+
const fetchLimit = Math.max(100, normalizedLimit * 5);
|
|
245
|
+
|
|
185
246
|
let results = [];
|
|
186
247
|
if (normalizedSource === 'feedback') {
|
|
187
|
-
|
|
248
|
+
const raw = getFeedbackResults(trimmedQuery, fetchLimit, normalizedSignal, feedbackDir);
|
|
249
|
+
results = deduplicateResults(raw).slice(0, normalizedLimit);
|
|
188
250
|
} else if (normalizedSource === 'context') {
|
|
189
|
-
|
|
251
|
+
const raw = getContextResults(trimmedQuery, fetchLimit, feedbackDir);
|
|
252
|
+
results = deduplicateResults(raw).slice(0, normalizedLimit);
|
|
190
253
|
} else if (normalizedSource === 'rules') {
|
|
191
|
-
|
|
254
|
+
const raw = getRuleResults(trimmedQuery, fetchLimit, feedbackDir);
|
|
255
|
+
results = deduplicateResults(raw).slice(0, normalizedLimit);
|
|
192
256
|
} else if (normalizedSource === 'documents') {
|
|
193
|
-
|
|
257
|
+
const raw = getDocumentResults(trimmedQuery, fetchLimit, feedbackDir);
|
|
258
|
+
results = deduplicateResults(raw).slice(0, normalizedLimit);
|
|
194
259
|
} else {
|
|
195
|
-
|
|
196
|
-
...getFeedbackResults(trimmedQuery,
|
|
197
|
-
...getContextResults(trimmedQuery,
|
|
198
|
-
...getRuleResults(trimmedQuery,
|
|
199
|
-
...getDocumentResults(trimmedQuery,
|
|
200
|
-
]
|
|
260
|
+
const combined = [
|
|
261
|
+
...getFeedbackResults(trimmedQuery, fetchLimit, normalizedSignal, feedbackDir),
|
|
262
|
+
...getContextResults(trimmedQuery, fetchLimit, feedbackDir),
|
|
263
|
+
...getRuleResults(trimmedQuery, fetchLimit, feedbackDir),
|
|
264
|
+
...getDocumentResults(trimmedQuery, fetchLimit, feedbackDir),
|
|
265
|
+
];
|
|
266
|
+
results = deduplicateResults(sortResults(combined)).slice(0, normalizedLimit);
|
|
201
267
|
}
|
|
202
268
|
|
|
203
269
|
return {
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tool Contract Validator
|
|
5
|
+
* Validates tool arguments against the tool's inputSchema.
|
|
6
|
+
*/
|
|
7
|
+
function validateToolContract(schema, args) {
|
|
8
|
+
const errors = [];
|
|
9
|
+
if (!schema) return { valid: true, errors };
|
|
10
|
+
|
|
11
|
+
if (schema.type === 'object') {
|
|
12
|
+
if (typeof args !== 'object' || args === null || Array.isArray(args)) {
|
|
13
|
+
errors.push(`Expected object, got ${args === null ? 'null' : Array.isArray(args) ? 'array' : typeof args}`);
|
|
14
|
+
return { valid: false, errors };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Check required fields
|
|
18
|
+
if (Array.isArray(schema.required)) {
|
|
19
|
+
for (const req of schema.required) {
|
|
20
|
+
if (args[req] === undefined || args[req] === null || args[req] === '') {
|
|
21
|
+
errors.push(`Missing required parameter: '${req}'`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Check properties
|
|
27
|
+
if (schema.properties) {
|
|
28
|
+
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
29
|
+
const value = args[key];
|
|
30
|
+
if (value === undefined || value === null) continue; // Optional field not provided
|
|
31
|
+
|
|
32
|
+
const valType = typeof value;
|
|
33
|
+
if (propSchema.type === 'string') {
|
|
34
|
+
if (valType !== 'string') {
|
|
35
|
+
errors.push(`Parameter '${key}' must be a string (got ${valType})`);
|
|
36
|
+
} else if (Array.isArray(propSchema.enum)) {
|
|
37
|
+
if (!propSchema.enum.includes(value)) {
|
|
38
|
+
errors.push(`Parameter '${key}' must be one of [${propSchema.enum.join(', ')}] (got '${value}')`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
} else if (propSchema.type === 'number') {
|
|
42
|
+
if (valType !== 'number' || isNaN(value)) {
|
|
43
|
+
errors.push(`Parameter '${key}' must be a number (got ${valType})`);
|
|
44
|
+
}
|
|
45
|
+
} else if (propSchema.type === 'boolean') {
|
|
46
|
+
if (valType !== 'boolean') {
|
|
47
|
+
errors.push(`Parameter '${key}' must be a boolean (got ${valType})`);
|
|
48
|
+
}
|
|
49
|
+
} else if (propSchema.type === 'array') {
|
|
50
|
+
if (!Array.isArray(value)) {
|
|
51
|
+
errors.push(`Parameter '${key}' must be an array (got ${valType})`);
|
|
52
|
+
}
|
|
53
|
+
} else if (propSchema.type === 'object') {
|
|
54
|
+
if (valType !== 'object' || value === null || Array.isArray(value)) {
|
|
55
|
+
errors.push(`Parameter '${key}' must be an object (got ${valType})`);
|
|
56
|
+
} else {
|
|
57
|
+
// Recurse for nested objects
|
|
58
|
+
const subRes = validateToolContract(propSchema, value);
|
|
59
|
+
if (!subRes.valid) {
|
|
60
|
+
for (const err of subRes.errors) {
|
|
61
|
+
errors.push(`Parameter '${key}': ${err}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
valid: errors.length === 0,
|
|
72
|
+
errors,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = { validateToolContract };
|
package/scripts/vector-store.js
CHANGED
|
@@ -172,6 +172,30 @@ async function embedWithGemini(text, options = {}) {
|
|
|
172
172
|
return values.map(Number);
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
+
async function embedWithCoreAI(text, options = {}) {
|
|
176
|
+
if (process.platform !== 'darwin') {
|
|
177
|
+
throw new Error('Core AI is only supported on macOS');
|
|
178
|
+
}
|
|
179
|
+
const endpoint = process.env.THUMBGATE_COREAI_ENDPOINT || 'http://localhost:8088';
|
|
180
|
+
try {
|
|
181
|
+
const res = await fetch(`${endpoint}/embed`, {
|
|
182
|
+
method: 'POST',
|
|
183
|
+
headers: { 'Content-Type': 'application/json' },
|
|
184
|
+
body: JSON.stringify({ text, options }),
|
|
185
|
+
signal: AbortSignal.timeout(2000),
|
|
186
|
+
});
|
|
187
|
+
if (res.ok) {
|
|
188
|
+
const payload = await res.json();
|
|
189
|
+
if (Array.isArray(payload.embedding)) {
|
|
190
|
+
return payload.embedding.map(Number);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
} catch (err) {
|
|
194
|
+
throw new Error(`Core AI local service unavailable: ${err.message}`);
|
|
195
|
+
}
|
|
196
|
+
throw new Error('Core AI local service did not return a valid embedding');
|
|
197
|
+
}
|
|
198
|
+
|
|
175
199
|
async function embed(text, options = {}) {
|
|
176
200
|
if (process.env.THUMBGATE_VECTOR_STUB_EMBED === 'true') {
|
|
177
201
|
// Deterministic 384-dim unit vector: first element = 1.0, rest = 0.0
|
|
@@ -180,6 +204,26 @@ async function embed(text, options = {}) {
|
|
|
180
204
|
return stub;
|
|
181
205
|
}
|
|
182
206
|
const geminiConfig = resolveGeminiEmbeddingConfig();
|
|
207
|
+
if (geminiConfig.provider === 'coreai') {
|
|
208
|
+
try {
|
|
209
|
+
const vector = await embedWithCoreAI(text, options);
|
|
210
|
+
_lastEmbeddingProfile = {
|
|
211
|
+
generatedAt: new Date().toISOString(),
|
|
212
|
+
source: 'local-coreai',
|
|
213
|
+
activeProfile: {
|
|
214
|
+
id: 'coreai',
|
|
215
|
+
model: 'Core AI local model',
|
|
216
|
+
outputDimensionality: vector.length,
|
|
217
|
+
task: options.task || 'code retrieval',
|
|
218
|
+
rationale: 'Local Core AI Apple Silicon accelerated path.',
|
|
219
|
+
},
|
|
220
|
+
fallbackUsed: false,
|
|
221
|
+
};
|
|
222
|
+
return vector;
|
|
223
|
+
} catch (coreaiError) {
|
|
224
|
+
console.warn(`Core AI embedding failed, falling back to local: ${coreaiError.message}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
183
227
|
if (geminiConfig.enabled) {
|
|
184
228
|
try {
|
|
185
229
|
const vector = await embedWithGemini(text, options);
|
|
@@ -108,14 +108,74 @@ function parseCommandScore(output = '', status = 0, approvalRate = 0.5) {
|
|
|
108
108
|
};
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
function parseCommandLine(cmdString) {
|
|
112
|
+
const args = [];
|
|
113
|
+
let current = '';
|
|
114
|
+
let inDoubleQuote = false;
|
|
115
|
+
let inSingleQuote = false;
|
|
116
|
+
let escaped = false;
|
|
117
|
+
|
|
118
|
+
for (let i = 0; i < cmdString.length; i++) {
|
|
119
|
+
const char = cmdString[i];
|
|
120
|
+
|
|
121
|
+
if (escaped) {
|
|
122
|
+
current += char;
|
|
123
|
+
escaped = false;
|
|
124
|
+
} else if (char === '\\') {
|
|
125
|
+
if (inSingleQuote) {
|
|
126
|
+
current += char;
|
|
127
|
+
} else {
|
|
128
|
+
escaped = true;
|
|
129
|
+
}
|
|
130
|
+
} else if (char === '"' && !inSingleQuote) {
|
|
131
|
+
inDoubleQuote = !inDoubleQuote;
|
|
132
|
+
} else if (char === "'" && !inDoubleQuote) {
|
|
133
|
+
inSingleQuote = !inSingleQuote;
|
|
134
|
+
} else if (char === ' ' && !inDoubleQuote && !inSingleQuote) {
|
|
135
|
+
if (current) {
|
|
136
|
+
args.push(current);
|
|
137
|
+
current = '';
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
current += char;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (current) {
|
|
144
|
+
args.push(current);
|
|
145
|
+
}
|
|
146
|
+
return args;
|
|
147
|
+
}
|
|
148
|
+
|
|
111
149
|
function runCommand(command, {
|
|
112
150
|
cwd = process.cwd(),
|
|
113
151
|
env = process.env,
|
|
114
152
|
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
115
153
|
} = {}) {
|
|
116
154
|
const startedAt = Date.now();
|
|
117
|
-
const
|
|
118
|
-
|
|
155
|
+
const args = parseCommandLine(command);
|
|
156
|
+
const exec = args.shift();
|
|
157
|
+
|
|
158
|
+
const execBase = require('node:path').basename(exec).toLowerCase();
|
|
159
|
+
let safeExec;
|
|
160
|
+
if (exec === process.execPath) {
|
|
161
|
+
safeExec = process.execPath;
|
|
162
|
+
} else if (execBase === 'node' || execBase === 'node.exe') {
|
|
163
|
+
safeExec = process.execPath;
|
|
164
|
+
} else if (execBase === 'npm') {
|
|
165
|
+
safeExec = 'npm';
|
|
166
|
+
} else if (execBase === 'npm.cmd') {
|
|
167
|
+
safeExec = 'npm.cmd';
|
|
168
|
+
} else if (execBase === 'python3') {
|
|
169
|
+
safeExec = 'python3';
|
|
170
|
+
} else if (execBase === 'python') {
|
|
171
|
+
safeExec = 'python';
|
|
172
|
+
} else if (execBase === 'pytest') {
|
|
173
|
+
safeExec = 'pytest';
|
|
174
|
+
} else {
|
|
175
|
+
throw new Error(`Binary ${exec} is not authorized for workspace evolution.`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const result = spawnSync(safeExec, args, {
|
|
119
179
|
cwd,
|
|
120
180
|
env,
|
|
121
181
|
encoding: 'utf8',
|