thumbgate 1.27.13 → 1.27.15
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/package.json +1 -1
- package/scripts/agent-readiness.js +147 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thumbgate",
|
|
3
|
-
"version": "1.27.
|
|
3
|
+
"version": "1.27.15",
|
|
4
4
|
"description": "ThumbGate self-improving agent governance: thumbs-up/down turns every mistake into a prevention rule and blocks repeat patterns. 36 pre-action checks, budget enforcement, and self-protection for Claude Code, Cursor, Codex, Gemini CLI, and Amp.",
|
|
5
5
|
"homepage": "https://thumbgate.ai",
|
|
6
6
|
"repository": {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
const fs = require('fs');
|
|
5
|
+
const os = require('os');
|
|
5
6
|
const path = require('path');
|
|
6
7
|
const {
|
|
7
8
|
getActiveMcpProfile,
|
|
@@ -165,6 +166,141 @@ function summarizePermissionTier(profileName = getActiveMcpProfile()) {
|
|
|
165
166
|
};
|
|
166
167
|
}
|
|
167
168
|
|
|
169
|
+
function flattenHookCommands(value, commands = []) {
|
|
170
|
+
if (!value) return commands;
|
|
171
|
+
if (typeof value === 'string') {
|
|
172
|
+
commands.push(value);
|
|
173
|
+
return commands;
|
|
174
|
+
}
|
|
175
|
+
if (Array.isArray(value)) {
|
|
176
|
+
value.forEach((item) => flattenHookCommands(item, commands));
|
|
177
|
+
return commands;
|
|
178
|
+
}
|
|
179
|
+
if (typeof value === 'object') {
|
|
180
|
+
if (typeof value.command === 'string') commands.push(value.command);
|
|
181
|
+
Object.entries(value)
|
|
182
|
+
.filter(([key]) => key !== 'command')
|
|
183
|
+
.forEach(([, item]) => flattenHookCommands(item, commands));
|
|
184
|
+
}
|
|
185
|
+
return commands;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function readJsonIfExists(filePath) {
|
|
189
|
+
try {
|
|
190
|
+
if (!fs.existsSync(filePath)) return null;
|
|
191
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
192
|
+
} catch (_) {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function detectHookAutoCapture(projectRoot) {
|
|
198
|
+
const effectiveRoot = projectRoot || findProjectRoot();
|
|
199
|
+
const settingsFiles = ['.claude/settings.json', '.claude/settings.local.json']
|
|
200
|
+
.map((file) => path.join(effectiveRoot, file))
|
|
201
|
+
.filter((file) => fs.existsSync(file));
|
|
202
|
+
const commands = settingsFiles.flatMap((file) => {
|
|
203
|
+
const parsed = readJsonIfExists(file);
|
|
204
|
+
return flattenHookCommands(parsed && parsed.hooks && parsed.hooks.UserPromptSubmit);
|
|
205
|
+
});
|
|
206
|
+
const wiredCommands = commands.filter((command) => /hook-auto-capture(\.sh)?\b/.test(command));
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
ready: wiredCommands.length > 0,
|
|
210
|
+
settingsFiles: settingsFiles.map((file) => path.relative(effectiveRoot, file)),
|
|
211
|
+
wiredCommands,
|
|
212
|
+
recommendation: wiredCommands.length > 0
|
|
213
|
+
? 'UserPromptSubmit auto-capture hook is wired for typed thumbs feedback.'
|
|
214
|
+
: 'Run `npx thumbgate init --wire-hooks` so typed thumbs up/down feedback is captured automatically.',
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function probeFeedbackCapture(projectRoot) {
|
|
219
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'thumbgate-doctor-feedback-'));
|
|
220
|
+
const previousFeedbackDir = process.env.THUMBGATE_FEEDBACK_DIR;
|
|
221
|
+
const previousProjectDir = process.env.THUMBGATE_PROJECT_DIR;
|
|
222
|
+
const previousNoNudge = process.env.THUMBGATE_NO_NUDGE;
|
|
223
|
+
|
|
224
|
+
process.env.THUMBGATE_FEEDBACK_DIR = tempDir;
|
|
225
|
+
// Keep this probe isolated. Direct project scope intentionally overrides
|
|
226
|
+
// THUMBGATE_FEEDBACK_DIR in the runtime resolver, which is correct for real
|
|
227
|
+
// project writes but wrong for a doctor self-test.
|
|
228
|
+
delete process.env.THUMBGATE_PROJECT_DIR;
|
|
229
|
+
process.env.THUMBGATE_NO_NUDGE = '1';
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
const {
|
|
233
|
+
captureFeedback,
|
|
234
|
+
getFeedbackPaths,
|
|
235
|
+
readJSONL,
|
|
236
|
+
} = require('./feedback-loop');
|
|
237
|
+
const result = captureFeedback({
|
|
238
|
+
signal: 'down',
|
|
239
|
+
context: 'doctor feedback loop probe: prove thumbs feedback persists to durable logs',
|
|
240
|
+
whatWentWrong: 'A runtime claimed thumbs feedback worked without verifying durable capture',
|
|
241
|
+
whatToChange: 'Run thumbgate doctor or feedback-self-test before claiming the loop is wired',
|
|
242
|
+
tags: 'doctor,feedback-loop-probe',
|
|
243
|
+
});
|
|
244
|
+
const paths = getFeedbackPaths();
|
|
245
|
+
const feedbackId = result.feedbackEvent && result.feedbackEvent.id;
|
|
246
|
+
const memoryId = result.memoryRecord && result.memoryRecord.id;
|
|
247
|
+
const feedbackStored = Boolean(feedbackId && readJSONL(paths.FEEDBACK_LOG_PATH).some((row) => row.id === feedbackId));
|
|
248
|
+
const memoryStored = Boolean(memoryId && readJSONL(paths.MEMORY_LOG_PATH).some((row) => row.id === memoryId));
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
ready: Boolean(result.accepted && feedbackStored && memoryStored),
|
|
252
|
+
accepted: Boolean(result.accepted),
|
|
253
|
+
feedbackStored,
|
|
254
|
+
memoryStored,
|
|
255
|
+
feedbackId: feedbackId || null,
|
|
256
|
+
memoryId: memoryId || null,
|
|
257
|
+
mode: 'isolated-temp-store',
|
|
258
|
+
feedbackDir: paths.FEEDBACK_DIR,
|
|
259
|
+
recommendation: (result.accepted && feedbackStored && memoryStored)
|
|
260
|
+
? 'Feedback capture writes durable feedback and memory records.'
|
|
261
|
+
: 'Feedback capture did not persist both feedback and memory records; run `npx thumbgate feedback-self-test --json`.',
|
|
262
|
+
};
|
|
263
|
+
} catch (error) {
|
|
264
|
+
return {
|
|
265
|
+
ready: false,
|
|
266
|
+
accepted: false,
|
|
267
|
+
feedbackStored: false,
|
|
268
|
+
memoryStored: false,
|
|
269
|
+
feedbackId: null,
|
|
270
|
+
memoryId: null,
|
|
271
|
+
mode: 'isolated-temp-store',
|
|
272
|
+
feedbackDir: tempDir,
|
|
273
|
+
error: error && error.message ? error.message : String(error),
|
|
274
|
+
recommendation: 'Feedback capture probe failed; run `npx thumbgate feedback-self-test --json` for details.',
|
|
275
|
+
};
|
|
276
|
+
} finally {
|
|
277
|
+
if (previousFeedbackDir === undefined) delete process.env.THUMBGATE_FEEDBACK_DIR;
|
|
278
|
+
else process.env.THUMBGATE_FEEDBACK_DIR = previousFeedbackDir;
|
|
279
|
+
if (previousProjectDir === undefined) delete process.env.THUMBGATE_PROJECT_DIR;
|
|
280
|
+
else process.env.THUMBGATE_PROJECT_DIR = previousProjectDir;
|
|
281
|
+
if (previousNoNudge === undefined) delete process.env.THUMBGATE_NO_NUDGE;
|
|
282
|
+
else process.env.THUMBGATE_NO_NUDGE = previousNoNudge;
|
|
283
|
+
try { fs.rmSync(tempDir, { recursive: true, force: true }); } catch (_) { /* ignore cleanup failure */ }
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function assessFeedbackLoop(projectRoot) {
|
|
288
|
+
const hookAutoCapture = detectHookAutoCapture(projectRoot);
|
|
289
|
+
const captureProbe = probeFeedbackCapture(projectRoot);
|
|
290
|
+
const ready = hookAutoCapture.ready && captureProbe.ready;
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
ready,
|
|
294
|
+
automatic: hookAutoCapture.ready,
|
|
295
|
+
durableCapture: captureProbe.ready,
|
|
296
|
+
hookAutoCapture,
|
|
297
|
+
captureProbe,
|
|
298
|
+
recommendation: ready
|
|
299
|
+
? 'Typed thumbs feedback is automatically detected and durable capture is verified.'
|
|
300
|
+
: 'Install hooks and verify capture before claiming thumbs feedback improves future runs.',
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
168
304
|
function generateAgentReadinessReport({
|
|
169
305
|
projectRoot = PROJECT_ROOT,
|
|
170
306
|
mcpProfile = null,
|
|
@@ -172,11 +308,13 @@ function generateAgentReadinessReport({
|
|
|
172
308
|
const runtime = detectRuntimeIsolation();
|
|
173
309
|
const bootstrap = collectBootstrapFiles(projectRoot);
|
|
174
310
|
const permissions = summarizePermissionTier(mcpProfile || getActiveMcpProfile());
|
|
311
|
+
const feedbackLoop = assessFeedbackLoop(projectRoot);
|
|
175
312
|
|
|
176
313
|
const warnings = [];
|
|
177
314
|
if (!runtime.isolated) warnings.push(runtime.recommendation);
|
|
178
315
|
if (!bootstrap.ready) warnings.push(bootstrap.recommendation);
|
|
179
316
|
if (!permissions.ready) warnings.push(permissions.recommendation);
|
|
317
|
+
if (!feedbackLoop.ready) warnings.push(feedbackLoop.recommendation);
|
|
180
318
|
|
|
181
319
|
return {
|
|
182
320
|
generatedAt: new Date().toISOString(),
|
|
@@ -185,10 +323,12 @@ function generateAgentReadinessReport({
|
|
|
185
323
|
runtime,
|
|
186
324
|
bootstrap,
|
|
187
325
|
permissions,
|
|
326
|
+
feedbackLoop,
|
|
188
327
|
articleAlignment: {
|
|
189
328
|
runtimeIsolation: runtime.isolated,
|
|
190
329
|
contextConditioning: bootstrap.ready,
|
|
191
330
|
permissionEnvelope: permissions.ready,
|
|
331
|
+
feedbackLoop: feedbackLoop.ready,
|
|
192
332
|
},
|
|
193
333
|
warnings,
|
|
194
334
|
};
|
|
@@ -208,6 +348,10 @@ function reportToText(report) {
|
|
|
208
348
|
lines.push(`Permissions: ${report.permissions.profile} (${report.permissions.tier})`);
|
|
209
349
|
lines.push(` Write-capable tools: ${report.permissions.writeCapableTools.length}`);
|
|
210
350
|
lines.push(` Recommendation: ${report.permissions.recommendation}`);
|
|
351
|
+
lines.push(`Feedback loop: ${report.feedbackLoop.ready ? 'ready' : 'needs_attention'}`);
|
|
352
|
+
lines.push(` Auto-capture hook: ${report.feedbackLoop.automatic ? 'wired' : 'missing'}`);
|
|
353
|
+
lines.push(` Durable capture: ${report.feedbackLoop.durableCapture ? 'verified' : 'failed'}`);
|
|
354
|
+
lines.push(` Recommendation: ${report.feedbackLoop.recommendation}`);
|
|
211
355
|
|
|
212
356
|
if (report.warnings.length > 0) {
|
|
213
357
|
lines.push('');
|
|
@@ -226,6 +370,9 @@ module.exports = {
|
|
|
226
370
|
detectRuntimeIsolation,
|
|
227
371
|
collectBootstrapFiles,
|
|
228
372
|
summarizePermissionTier,
|
|
373
|
+
detectHookAutoCapture,
|
|
374
|
+
probeFeedbackCapture,
|
|
375
|
+
assessFeedbackLoop,
|
|
229
376
|
generateAgentReadinessReport,
|
|
230
377
|
reportToText,
|
|
231
378
|
};
|