thumbgate 0.9.14 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +1 -0
- package/adapters/README.md +1 -1
- package/adapters/chatgpt/openapi.yaml +105 -0
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/codex/config.toml +2 -2
- package/adapters/forge/forge.yaml +28 -0
- package/adapters/mcp/server-stdio.js +32 -1
- package/adapters/opencode/opencode.json +1 -1
- package/bin/cli.js +18 -3
- package/config/mcp-allowlists.json +10 -0
- package/openapi/openapi.yaml +105 -0
- package/package.json +4 -4
- package/plugins/amp-skill/INSTALL.md +3 -4
- package/plugins/amp-skill/SKILL.md +0 -1
- package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-codex-bridge/.mcp.json +1 -1
- package/plugins/claude-skill/INSTALL.md +1 -2
- package/plugins/codex-profile/.codex-plugin/plugin.json +1 -1
- package/plugins/codex-profile/.mcp.json +1 -1
- package/plugins/codex-profile/INSTALL.md +1 -1
- package/plugins/codex-profile/README.md +1 -1
- package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +1 -1
- package/plugins/opencode-profile/INSTALL.md +1 -1
- package/public/blog.html +1 -0
- package/public/dashboard.html +1 -1
- package/public/guide.html +1 -1
- package/public/index.html +3 -3
- package/public/learn/agent-harness-pattern.html +1 -1
- package/public/learn/ai-agent-persistent-memory.html +1 -1
- package/public/learn/mcp-pre-action-gates-explained.html +1 -1
- package/public/learn/stop-ai-agent-force-push.html +1 -1
- package/public/learn/vibe-coding-safety-net.html +1 -1
- package/public/learn.html +1 -1
- package/public/lessons.html +1 -1
- package/public/pro.html +1 -1
- package/scripts/__pycache__/train_from_feedback.cpython-312.pyc +0 -0
- package/scripts/agent-security-hardening.js +4 -4
- package/scripts/async-job-runner.js +84 -24
- package/scripts/auto-wire-hooks.js +59 -1
- package/scripts/context-manager.js +330 -0
- package/scripts/dashboard.js +1 -1
- package/scripts/distribution-surfaces.js +12 -0
- package/scripts/ensure-repo-bootstrap.js +15 -14
- package/scripts/gates-engine.js +96 -10
- package/scripts/hook-auto-capture.sh +1 -1
- package/scripts/hosted-job-launcher.js +260 -0
- package/scripts/managed-dpo-export.js +91 -0
- package/scripts/obsidian-export.js +0 -1
- package/scripts/operational-integrity.js +50 -7
- package/scripts/prove-lancedb.js +62 -4
- package/scripts/publish-decision.js +16 -0
- package/scripts/self-healing-check.js +6 -1
- package/scripts/social-analytics/load-env.js +33 -2
- package/scripts/social-analytics/store.js +200 -2
- package/scripts/sync-version.js +18 -11
- package/scripts/tool-registry.js +37 -0
- package/scripts/train_from_feedback.py +0 -4
- package/scripts/workflow-sentinel.js +793 -0
- package/src/api/server.js +205 -27
- /package/scripts/{rlhf_session_start.sh → thumbgate_session_start.sh} +0 -0
|
@@ -2,11 +2,209 @@
|
|
|
2
2
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const fs = require('fs');
|
|
5
|
-
|
|
5
|
+
let Database = null;
|
|
6
|
+
try {
|
|
7
|
+
Database = require('better-sqlite3');
|
|
8
|
+
} catch (_) {
|
|
9
|
+
Database = null;
|
|
10
|
+
}
|
|
6
11
|
|
|
7
12
|
const DEFAULT_DB_PATH = path.join(__dirname, 'db', 'social-analytics.db');
|
|
8
13
|
const SCHEMA_PATH = path.join(__dirname, 'db', 'schema.sql');
|
|
9
14
|
|
|
15
|
+
class MemoryStatement {
|
|
16
|
+
constructor(handler) {
|
|
17
|
+
this.handler = handler;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
run(params) {
|
|
21
|
+
return this.handler.run(params);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
all(params) {
|
|
25
|
+
return this.handler.all(params);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
class MemoryDatabase {
|
|
30
|
+
constructor() {
|
|
31
|
+
this.tables = {
|
|
32
|
+
engagement_metrics: [],
|
|
33
|
+
follower_snapshots: [],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
pragma() {}
|
|
38
|
+
|
|
39
|
+
exec() {}
|
|
40
|
+
|
|
41
|
+
close() {}
|
|
42
|
+
|
|
43
|
+
prepare(sql) {
|
|
44
|
+
const normalized = sql.replace(/\s+/g, ' ').trim();
|
|
45
|
+
|
|
46
|
+
if (normalized.includes('INSERT OR REPLACE INTO engagement_metrics')) {
|
|
47
|
+
return new MemoryStatement({
|
|
48
|
+
run: (params) => {
|
|
49
|
+
const index = this.tables.engagement_metrics.findIndex(
|
|
50
|
+
(row) =>
|
|
51
|
+
row.platform === params.platform &&
|
|
52
|
+
row.post_id === params.post_id &&
|
|
53
|
+
row.metric_date === params.metric_date
|
|
54
|
+
);
|
|
55
|
+
const row = { ...params };
|
|
56
|
+
if (index >= 0) {
|
|
57
|
+
this.tables.engagement_metrics[index] = row;
|
|
58
|
+
} else {
|
|
59
|
+
this.tables.engagement_metrics.push(row);
|
|
60
|
+
}
|
|
61
|
+
return { changes: 1 };
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (normalized.includes('INSERT OR REPLACE INTO follower_snapshots')) {
|
|
67
|
+
return new MemoryStatement({
|
|
68
|
+
run: (params) => {
|
|
69
|
+
const index = this.tables.follower_snapshots.findIndex(
|
|
70
|
+
(row) => row.platform === params.platform && row.snapshot_date === params.snapshot_date
|
|
71
|
+
);
|
|
72
|
+
const row = { ...params };
|
|
73
|
+
if (index >= 0) {
|
|
74
|
+
this.tables.follower_snapshots[index] = row;
|
|
75
|
+
} else {
|
|
76
|
+
this.tables.follower_snapshots.push(row);
|
|
77
|
+
}
|
|
78
|
+
return { changes: 1 };
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (normalized.includes('SELECT * FROM engagement_metrics WHERE platform = ?')) {
|
|
84
|
+
return new MemoryStatement({
|
|
85
|
+
all: (platform) =>
|
|
86
|
+
this.tables.engagement_metrics.filter((row) => row.platform === platform),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const literalPlatformMatch = normalized.match(
|
|
91
|
+
/^SELECT \* FROM engagement_metrics WHERE platform = '([^']+)'$/i
|
|
92
|
+
);
|
|
93
|
+
if (literalPlatformMatch) {
|
|
94
|
+
const [, platform] = literalPlatformMatch;
|
|
95
|
+
return new MemoryStatement({
|
|
96
|
+
all: () =>
|
|
97
|
+
this.tables.engagement_metrics.filter((row) => row.platform === platform),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (normalized.includes('FROM engagement_metrics') && normalized.includes('GROUP BY platform')) {
|
|
102
|
+
return new MemoryStatement({
|
|
103
|
+
all: (params = {}) => {
|
|
104
|
+
const rows = this.tables.engagement_metrics.filter((row) => {
|
|
105
|
+
if (row.metric_date < params.cutoff) return false;
|
|
106
|
+
if (params.platform && row.platform !== params.platform) return false;
|
|
107
|
+
return true;
|
|
108
|
+
});
|
|
109
|
+
const grouped = new Map();
|
|
110
|
+
for (const row of rows) {
|
|
111
|
+
const bucket = grouped.get(row.platform) || {
|
|
112
|
+
platform: row.platform,
|
|
113
|
+
post_count: 0,
|
|
114
|
+
total_impressions: 0,
|
|
115
|
+
total_reach: 0,
|
|
116
|
+
total_likes: 0,
|
|
117
|
+
total_comments: 0,
|
|
118
|
+
total_shares: 0,
|
|
119
|
+
total_saves: 0,
|
|
120
|
+
total_clicks: 0,
|
|
121
|
+
total_video_views: 0,
|
|
122
|
+
_postIds: new Set(),
|
|
123
|
+
_rows: 0,
|
|
124
|
+
};
|
|
125
|
+
bucket._postIds.add(row.post_id);
|
|
126
|
+
bucket.total_impressions += row.impressions || 0;
|
|
127
|
+
bucket.total_reach += row.reach || 0;
|
|
128
|
+
bucket.total_likes += row.likes || 0;
|
|
129
|
+
bucket.total_comments += row.comments || 0;
|
|
130
|
+
bucket.total_shares += row.shares || 0;
|
|
131
|
+
bucket.total_saves += row.saves || 0;
|
|
132
|
+
bucket.total_clicks += row.clicks || 0;
|
|
133
|
+
bucket.total_video_views += row.video_views || 0;
|
|
134
|
+
bucket._rows += 1;
|
|
135
|
+
grouped.set(row.platform, bucket);
|
|
136
|
+
}
|
|
137
|
+
return [...grouped.values()]
|
|
138
|
+
.map((bucket) => ({
|
|
139
|
+
platform: bucket.platform,
|
|
140
|
+
post_count: bucket._postIds.size,
|
|
141
|
+
total_impressions: bucket.total_impressions,
|
|
142
|
+
total_reach: bucket.total_reach,
|
|
143
|
+
total_likes: bucket.total_likes,
|
|
144
|
+
total_comments: bucket.total_comments,
|
|
145
|
+
total_shares: bucket.total_shares,
|
|
146
|
+
total_saves: bucket.total_saves,
|
|
147
|
+
total_clicks: bucket.total_clicks,
|
|
148
|
+
total_video_views: bucket.total_video_views,
|
|
149
|
+
avg_impressions: Number((bucket.total_impressions / bucket._rows).toFixed(2)),
|
|
150
|
+
avg_likes: Number((bucket.total_likes / bucket._rows).toFixed(2)),
|
|
151
|
+
}))
|
|
152
|
+
.sort((a, b) => b.total_impressions - a.total_impressions);
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (normalized.includes('FROM engagement_metrics') && normalized.includes('GROUP BY platform, post_id')) {
|
|
158
|
+
return new MemoryStatement({
|
|
159
|
+
all: ({ cutoff, limit }) => {
|
|
160
|
+
const rows = this.tables.engagement_metrics.filter((row) => row.metric_date >= cutoff);
|
|
161
|
+
const grouped = new Map();
|
|
162
|
+
for (const row of rows) {
|
|
163
|
+
const key = `${row.platform}::${row.post_id}`;
|
|
164
|
+
const bucket = grouped.get(key) || {
|
|
165
|
+
platform: row.platform,
|
|
166
|
+
content_type: row.content_type,
|
|
167
|
+
post_id: row.post_id,
|
|
168
|
+
post_url: row.post_url ?? null,
|
|
169
|
+
published_at: row.published_at ?? null,
|
|
170
|
+
total_engagement: 0,
|
|
171
|
+
total_impressions: 0,
|
|
172
|
+
total_likes: 0,
|
|
173
|
+
total_comments: 0,
|
|
174
|
+
total_shares: 0,
|
|
175
|
+
total_saves: 0,
|
|
176
|
+
total_video_views: 0,
|
|
177
|
+
};
|
|
178
|
+
bucket.total_engagement +=
|
|
179
|
+
(row.likes || 0) + (row.comments || 0) + (row.shares || 0) + (row.saves || 0);
|
|
180
|
+
bucket.total_impressions += row.impressions || 0;
|
|
181
|
+
bucket.total_likes += row.likes || 0;
|
|
182
|
+
bucket.total_comments += row.comments || 0;
|
|
183
|
+
bucket.total_shares += row.shares || 0;
|
|
184
|
+
bucket.total_saves += row.saves || 0;
|
|
185
|
+
bucket.total_video_views += row.video_views || 0;
|
|
186
|
+
grouped.set(key, bucket);
|
|
187
|
+
}
|
|
188
|
+
return [...grouped.values()]
|
|
189
|
+
.sort((a, b) => b.total_engagement - a.total_engagement)
|
|
190
|
+
.slice(0, limit);
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (normalized.includes('FROM follower_snapshots')) {
|
|
196
|
+
return new MemoryStatement({
|
|
197
|
+
all: ({ platform, cutoff }) =>
|
|
198
|
+
this.tables.follower_snapshots
|
|
199
|
+
.filter((row) => row.platform === platform && row.snapshot_date >= cutoff)
|
|
200
|
+
.sort((a, b) => a.snapshot_date.localeCompare(b.snapshot_date)),
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
throw new Error(`MemoryDatabase does not support query: ${normalized}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
10
208
|
/**
|
|
11
209
|
* Opens the SQLite database, applies the schema, and returns the db instance.
|
|
12
210
|
* Idempotent — safe to call multiple times; schema uses IF NOT EXISTS guards.
|
|
@@ -23,7 +221,7 @@ function initDb(dbPath = DEFAULT_DB_PATH) {
|
|
|
23
221
|
fs.mkdirSync(dir, { recursive: true });
|
|
24
222
|
}
|
|
25
223
|
|
|
26
|
-
const db = new Database(resolvedPath);
|
|
224
|
+
const db = Database ? new Database(resolvedPath) : new MemoryDatabase();
|
|
27
225
|
db.pragma('busy_timeout = 3000');
|
|
28
226
|
|
|
29
227
|
// Enable WAL mode for better concurrent read performance.
|
package/scripts/sync-version.js
CHANGED
|
@@ -18,6 +18,7 @@ const fs = require('fs');
|
|
|
18
18
|
const path = require('path');
|
|
19
19
|
|
|
20
20
|
const PROJECT_ROOT = path.join(__dirname, '..');
|
|
21
|
+
const VERSION_PATTERN = '\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?';
|
|
21
22
|
|
|
22
23
|
function explicitPinnedServeArgs(version) {
|
|
23
24
|
return ['--yes', '--package', `thumbgate@${version}`, 'thumbgate', 'serve'];
|
|
@@ -308,7 +309,7 @@ function syncVersion(opts) {
|
|
|
308
309
|
'plugins/codex-profile/INSTALL.md',
|
|
309
310
|
'plugins/opencode-profile/INSTALL.md',
|
|
310
311
|
];
|
|
311
|
-
const pinnedPackagePattern =
|
|
312
|
+
const pinnedPackagePattern = new RegExp(`thumbgate@${VERSION_PATTERN}`, 'g');
|
|
312
313
|
for (const relPath of pinnedPackageTargets) {
|
|
313
314
|
const filePath = path.join(PROJECT_ROOT, relPath);
|
|
314
315
|
if (!fs.existsSync(filePath)) continue;
|
|
@@ -329,7 +330,7 @@ function syncVersion(opts) {
|
|
|
329
330
|
if (fs.existsSync(path.join(PROJECT_ROOT, landingPath))) {
|
|
330
331
|
const landingContent = fs.readFileSync(path.join(PROJECT_ROOT, landingPath), 'utf-8');
|
|
331
332
|
// Match any version pattern in the hero badge
|
|
332
|
-
const badgeMatch = landingContent.match(
|
|
333
|
+
const badgeMatch = landingContent.match(new RegExp(`v(${VERSION_PATTERN}) — Hosted API`));
|
|
333
334
|
if (badgeMatch && badgeMatch[1] !== version) {
|
|
334
335
|
drifted.push({ file: landingPath, field: 'hero-badge', current: badgeMatch[1] });
|
|
335
336
|
if (!checkOnly) {
|
|
@@ -337,7 +338,7 @@ function syncVersion(opts) {
|
|
|
337
338
|
}
|
|
338
339
|
}
|
|
339
340
|
// JSON snippet version
|
|
340
|
-
const jsonMatch = landingContent.match(
|
|
341
|
+
const jsonMatch = landingContent.match(new RegExp(`"version"<\\/span><span class="out">: <\\/span><span class="val">"(${VERSION_PATTERN})"`));
|
|
341
342
|
if (jsonMatch && jsonMatch[1] !== version) {
|
|
342
343
|
drifted.push({ file: landingPath, field: 'json-snippet', current: jsonMatch[1] });
|
|
343
344
|
if (!checkOnly) {
|
|
@@ -351,7 +352,7 @@ function syncVersion(opts) {
|
|
|
351
352
|
const mcpSubmPath = 'docs/mcp-hub-submission.md';
|
|
352
353
|
if (fs.existsSync(path.join(PROJECT_ROOT, mcpSubmPath))) {
|
|
353
354
|
const mcpContent = fs.readFileSync(path.join(PROJECT_ROOT, mcpSubmPath), 'utf-8');
|
|
354
|
-
const versionMatch = mcpContent.match(
|
|
355
|
+
const versionMatch = mcpContent.match(new RegExp(`## Version\\s+(${VERSION_PATTERN})`));
|
|
355
356
|
if (versionMatch && versionMatch[1] !== version) {
|
|
356
357
|
drifted.push({ file: mcpSubmPath, field: 'version-heading', current: versionMatch[1] });
|
|
357
358
|
if (!checkOnly) {
|
|
@@ -366,18 +367,18 @@ function syncVersion(opts) {
|
|
|
366
367
|
if (fs.existsSync(path.join(PROJECT_ROOT, publicIndexPath))) {
|
|
367
368
|
const publicIndexFile = path.join(PROJECT_ROOT, publicIndexPath);
|
|
368
369
|
const publicContent = fs.readFileSync(publicIndexFile, 'utf-8');
|
|
369
|
-
const heroVersionMatch = publicContent.match(
|
|
370
|
+
const heroVersionMatch = publicContent.match(new RegExp(`New in v(${VERSION_PATTERN}):?`));
|
|
370
371
|
if (heroVersionMatch && heroVersionMatch[1] !== version) {
|
|
371
372
|
drifted.push({ file: publicIndexPath, field: 'hero-release-note', current: heroVersionMatch[1] });
|
|
372
373
|
if (!checkOnly) {
|
|
373
374
|
fs.writeFileSync(
|
|
374
375
|
publicIndexFile,
|
|
375
|
-
publicContent.replace(
|
|
376
|
+
publicContent.replace(new RegExp(`New in v${VERSION_PATTERN}:?`), `New in v${version}`)
|
|
376
377
|
);
|
|
377
378
|
}
|
|
378
379
|
}
|
|
379
380
|
|
|
380
|
-
const proofMatch = publicContent.match(
|
|
381
|
+
const proofMatch = publicContent.match(new RegExp(`Versioned proof: v(${VERSION_PATTERN})`));
|
|
381
382
|
if (proofMatch && proofMatch[1] !== version) {
|
|
382
383
|
drifted.push({ file: publicIndexPath, field: 'proof-pill', current: proofMatch[1] });
|
|
383
384
|
if (!checkOnly) {
|
|
@@ -385,11 +386,17 @@ function syncVersion(opts) {
|
|
|
385
386
|
}
|
|
386
387
|
}
|
|
387
388
|
|
|
388
|
-
const footerMatch = publicContent.match(
|
|
389
|
+
const footerMatch = publicContent.match(new RegExp(`(?:Context Gateway|MIT License) [•·] v(${VERSION_PATTERN})`));
|
|
389
390
|
if (footerMatch && footerMatch[1] !== version) {
|
|
390
391
|
drifted.push({ file: publicIndexPath, field: 'footer-version', current: footerMatch[1] });
|
|
391
392
|
if (!checkOnly) {
|
|
392
|
-
|
|
393
|
+
fs.writeFileSync(
|
|
394
|
+
publicIndexFile,
|
|
395
|
+
publicContent.replace(
|
|
396
|
+
new RegExp(`((?:Context Gateway|MIT License) [•·] )v${VERSION_PATTERN}`),
|
|
397
|
+
`$1v${version}`
|
|
398
|
+
)
|
|
399
|
+
);
|
|
393
400
|
}
|
|
394
401
|
}
|
|
395
402
|
targets.push(publicIndexPath);
|
|
@@ -400,13 +407,13 @@ function syncVersion(opts) {
|
|
|
400
407
|
const serverStdioFile = path.join(PROJECT_ROOT, serverStdioPath);
|
|
401
408
|
if (fs.existsSync(serverStdioFile)) {
|
|
402
409
|
const serverStdioContent = fs.readFileSync(serverStdioFile, 'utf-8');
|
|
403
|
-
const serverInfoMatch = serverStdioContent.match(
|
|
410
|
+
const serverInfoMatch = serverStdioContent.match(new RegExp(`version:\\s*'(${VERSION_PATTERN})'`));
|
|
404
411
|
if (serverInfoMatch && serverInfoMatch[1] !== version) {
|
|
405
412
|
drifted.push({ file: serverStdioPath, field: 'server-info-version', current: serverInfoMatch[1] });
|
|
406
413
|
if (!checkOnly) {
|
|
407
414
|
fs.writeFileSync(
|
|
408
415
|
serverStdioFile,
|
|
409
|
-
serverStdioContent.replace(
|
|
416
|
+
serverStdioContent.replace(new RegExp(`version:\\s*'${VERSION_PATTERN}'`), `version: '${version}'`)
|
|
410
417
|
);
|
|
411
418
|
}
|
|
412
419
|
}
|
package/scripts/tool-registry.js
CHANGED
|
@@ -490,6 +490,21 @@ const TOOLS = [
|
|
|
490
490
|
},
|
|
491
491
|
},
|
|
492
492
|
}),
|
|
493
|
+
readOnlyTool({
|
|
494
|
+
name: 'unified_context',
|
|
495
|
+
description: 'Assemble a complete, role-aware context object in one call. Combines session state, user profile, relevant lessons, prevention guards, context pack, and code-graph impact — with tiered graceful degradation (full → warm → cold). Replaces multiple recall/retrieve/session_primer calls with a single orchestrated request.',
|
|
496
|
+
inputSchema: {
|
|
497
|
+
type: 'object',
|
|
498
|
+
required: ['query'],
|
|
499
|
+
properties: {
|
|
500
|
+
query: { type: 'string', description: 'Describe the current task to find relevant context' },
|
|
501
|
+
toolName: { type: 'string', description: 'Current tool being invoked (improves lesson matching)' },
|
|
502
|
+
toolInput: { type: 'object', description: 'Current tool input (for guard evaluation)' },
|
|
503
|
+
agentType: { type: 'string', enum: ['claude', 'cursor', 'forgecode', 'codex'], description: 'Agent type — shapes context budget and feature inclusion' },
|
|
504
|
+
repoPath: { type: 'string', description: 'Repository path for code-graph impact analysis' },
|
|
505
|
+
},
|
|
506
|
+
},
|
|
507
|
+
}),
|
|
493
508
|
destructiveTool({
|
|
494
509
|
name: 'satisfy_gate',
|
|
495
510
|
description: 'Satisfy a gate condition with optional structured reasoning. Evidence is stored with a 5-minute TTL. When structuredReasoning is provided, the premise/evidence/conclusion chain is stored in the audit trail.',
|
|
@@ -632,6 +647,28 @@ const TOOLS = [
|
|
|
632
647
|
},
|
|
633
648
|
},
|
|
634
649
|
}),
|
|
650
|
+
readOnlyTool({
|
|
651
|
+
name: 'workflow_sentinel',
|
|
652
|
+
description: 'Predict pre-action workflow risk, blast radius, and remediations before a tool call executes.',
|
|
653
|
+
inputSchema: {
|
|
654
|
+
type: 'object',
|
|
655
|
+
required: ['toolName'],
|
|
656
|
+
properties: {
|
|
657
|
+
toolName: { type: 'string', description: 'Tool being assessed, such as Bash, Edit, or Write' },
|
|
658
|
+
command: { type: 'string', description: 'Optional shell command when toolName is Bash' },
|
|
659
|
+
filePath: { type: 'string', description: 'Optional primary file path for edit-like tools' },
|
|
660
|
+
changedFiles: {
|
|
661
|
+
type: 'array',
|
|
662
|
+
items: { type: 'string' },
|
|
663
|
+
description: 'Optional affected-file list used to estimate blast radius',
|
|
664
|
+
},
|
|
665
|
+
repoPath: { type: 'string', description: 'Optional repository path used for git-aware integrity checks' },
|
|
666
|
+
baseBranch: { type: 'string', description: 'Optional protected base branch override (defaults to main)' },
|
|
667
|
+
requirePrForReleaseSensitive: { type: 'boolean', description: 'When true, release-sensitive changes on non-base branches require an open PR' },
|
|
668
|
+
requireVersionNotBehindBase: { type: 'boolean', description: 'When true, release-sensitive changes cannot lag behind the base branch package version' },
|
|
669
|
+
},
|
|
670
|
+
},
|
|
671
|
+
}),
|
|
635
672
|
destructiveTool({
|
|
636
673
|
name: 'register_claim_gate',
|
|
637
674
|
description: 'Register a custom claim verification rule in local runtime state without editing tracked repo config.',
|
|
@@ -40,10 +40,6 @@ def resolve_feedback_dir() -> Path:
|
|
|
40
40
|
if local_thumbgate.exists():
|
|
41
41
|
return local_thumbgate
|
|
42
42
|
|
|
43
|
-
local_rlhf = PROJECT_ROOT / ".rlhf"
|
|
44
|
-
if local_rlhf.exists():
|
|
45
|
-
return local_rlhf
|
|
46
|
-
|
|
47
43
|
local_legacy = PROJECT_ROOT / ".claude" / "memory" / "feedback"
|
|
48
44
|
if local_legacy.exists():
|
|
49
45
|
return local_legacy
|